commit bcb704366cb5e333a626c18c308c7e0448a8e69f Author: toma Date: Wed Nov 25 17:56:58 2009 +0000 Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdenetwork@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..230af1f4 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,3 @@ +Look in the subdirs to get info about the authors. + +The package is maintained by Stephan Kulow diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..ded002ea --- /dev/null +++ b/COPYING @@ -0,0 +1,347 @@ +NOTE! The GPL below is copyrighted by the Free Software Foundation, but +the instance of code that it refers to (the kde programs) are copyrighted +by the authors who actually wrote it. + +--------------------------------------------------------------------------- + + 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 + + + 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. + + + Copyright (C) 19yy + + 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. + + , 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/COPYING-DOCS b/COPYING-DOCS new file mode 100644 index 00000000..4a0fe1c8 --- /dev/null +++ b/COPYING-DOCS @@ -0,0 +1,397 @@ + GNU Free Documentation License + Version 1.2, November 2002 + + + Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document 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. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation 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. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/INSTALL b/INSTALL new file mode 100644 index 00000000..f8bad0c1 --- /dev/null +++ b/INSTALL @@ -0,0 +1,176 @@ +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes a while. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/kde/bin', `/usr/local/kde/lib', etc. You can specify an +installation prefix other than `/usr/local/kde' by giving `configure' +the option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. + diff --git a/Mainpage.dox b/Mainpage.dox new file mode 100644 index 00000000..54c065a0 --- /dev/null +++ b/Mainpage.dox @@ -0,0 +1,12 @@ +/** @mainpage KDENetwork +* +* KDENetwork is a collection of applications that +* are network-oriented. This means that there +* are mostly communications applications in here, +* as well as some file-transfer things. +* +* +* - Kopete is a multi-protocol instant message client. +* - KSirc is an IRC client (i.e. single-protocol IM client). +* +*/ diff --git a/Makefile.am.in b/Makefile.am.in new file mode 100644 index 00000000..f4fe7e70 --- /dev/null +++ b/Makefile.am.in @@ -0,0 +1,10 @@ +AUTOMAKE_OPTIONS = foreign 1.6.1 +DISTCLEANFILES = inst-apps +COMPILE_BEFORE_dcoprss= librss +COMPILE_BEFORE_knewsticker= librss + +MAINTAINERCLEANFILES = subdirs configure.in acinclude.m4 SUBDIRS + +include admin/deps.am +include admin/Doxyfile.am + diff --git a/Makefile.cvs b/Makefile.cvs new file mode 100644 index 00000000..b4752bd8 --- /dev/null +++ b/Makefile.cvs @@ -0,0 +1,16 @@ + +all: + @echo "This Makefile is only for the CVS repository" + @echo "This will be deleted before making the distribution" + @echo "" + @if test ! -d admin; then \ + echo "Please recheckout this module!" ;\ + echo "for cvs: use checkout once and after that update again" ;\ + echo "for cvsup: checkout kde-common from cvsup and" ;\ + echo " link kde-common/admin to ./admin" ;\ + exit 1 ;\ + fi + $(MAKE) -f admin/Makefile.common cvs + +.SILENT: + diff --git a/README b/README new file mode 100644 index 00000000..aed6f722 --- /dev/null +++ b/README @@ -0,0 +1,45 @@ +In this file: + +* Common Mistakes +* Debugging +* More Info + +What it is +---------- + +* kdict: graphical client for the DICT protocol. +* kit: AOL instant messenger client, using the TOC protocol +* knewsticker: RDF newsticker applet +* kpf: public fileserver applet +* kppp: dialer and front end for pppd +* ksirc: IRC client +* ktalkd: talk daemon +* kxmlrpc: KDE XmlRpc Daemon +* lanbrowsing: lan browsing kio slave +* krfb: Desktop Sharing server, allow others to access your desktop via VNC +* krdc: a client for Desktop Sharing and other VNC servers +* wifi: Wireless LAN tools + +Common Mistakes +--------------- + +If configure claims Qt cannot be found, have a look at ftp://ftp.trolltech.com +and download the latest Qt 3.3.x release. + +Debugging +--------- + +You can use --enable-debug with the configure script, if you want to have +debug code in your KDE apps and libs. I recommend to do this, since this +is alpha software and this makes debugging things a whole lot easier. + +More Info +--------- + +Have a look at the individual subdirectories, if you want to know, what +versions of apps are included. + +Please direct any bug reports to our bug list by visiting +http://bugs.kde.org. + +General KDE discussions should go to the KDE mailing list (kde@kde.org). diff --git a/configure.in.bot b/configure.in.bot new file mode 100644 index 00000000..3cb0ade7 --- /dev/null +++ b/configure.in.bot @@ -0,0 +1,69 @@ +if test -z "$LIB_SLP"; then + echo "" + echo "You're missing OpenSLP, or the OpenSLP devel package." + echo "Browsing in krfb and krdc will not be possible." + echo "If you want browsing support in krfb, you should consider" + echo "installing it. " + echo "Have a look at http://www.openslp.org/ or find a binary" + echo "package for your platform." + echo "" + all_tests=bad +fi + +if test "$have_ssl" != yes; then + echo "" + echo "You're missing openSSL, or your version is too old (before 0.9.5a)." + echo "krdc will not be compiled. If you want to use krdc, you should consider" + echo "installing or upgrading it." + echo "Have a look at http://www.openssl.org, or find a binary package for" + echo "your platform." + echo "" + all_tests=bad +fi + +if test -z "$COMPILE_GADU"; then + echo "" + echo "You're missing libgadu or the libgadu development package." + echo "Kopete's Gadu-Gadu plugin will not be compiled." + echo "If you want Gadu-Gadu, a Polish messaging protocol, support in Kopete" + echo "you can download it from http://dev.null.pl/ekg/ or find a binary" + echo "package for your platform." + echo "You can find more information in ./kopete/protocols/gadu/README.gadu ." + echo "" + all_tests=bad +fi + +if test "X$have_libidn" = Xno; then + echo "" + echo "You're missing libidn or the libidn development package" + echo "Kopete's Jabber plugin will not be compiled." + echo "If you want Jabber support in Kopete, You can download libidn from" + echo "http://www.gnu.org/software/libidn or find a binary package" + echo "for your platform." + all_tests=bad +fi + + +if test "x$with_xmms" = xcheck && test -z "$XMMS_LIBS"; then + echo "" + echo "You're missing the XMMS libraries, or the libxmms development package." + echo "Without libxmms Kopete's NowListening plugin won't be able to talk to" + echo "the XMMS music player. If you want Kopete's NowListening plugin to" + echo "support XMMS, have a look at http://www.xmms.org/ or find a" + echo "binary package for your system." + echo "" + all_tests=bad +fi + +if test "x$with_wifi" = xcheck && test "$kde_libiw_installed" = "no"; then + echo "" + echo "You're missing the wireless tools libraries, or the wireless tools" + echo "header files. Without these, KWiFiManager and the kwireless applet" + echo "will not be compiled. You are unable to monitor wireless LAN" + echo "connections without these. If you want wireless LAN support enabled," + echo "have a look at " + echo "http://www.hpl.hp.com/personal/Jean_Tourrilhes/Linux/Tools.html" + echo "or find a binary package for your system." + echo "" + all_tests=bad +fi diff --git a/configure.in.in b/configure.in.in new file mode 100644 index 00000000..fc9ade39 --- /dev/null +++ b/configure.in.in @@ -0,0 +1,129 @@ +#MIN_CONFIG +KDE_ENABLE_HIDDEN_VISIBILITY +KDE_INIT_DOXYGEN([KDE Network API Reference], [Version $VERSION]) + + +dnl Checks for header files. +AC_CHECK_HEADERS(linux/tcp.h linux/if_ppp.h) +AC_CHECK_HEADERS(net/errno.h net/if_ppp.h) +AC_CHECK_HEADERS(asm/param.h) +AC_CHECK_HEADERS(sys/file.h sys/stat.h sys/time.h sys/cdefs.h sys/sockio.h) +AC_CHECK_HEADERS(fcntl.h unistd.h fnmatch.h sysent.h strings.h paths.h) +AC_CHECK_HEADERS(utmp.h re_comp.h getopt.h byteswap.h) +AC_CHECK_HEADER([resolv.h],,,[#include ]) + + +AC_SYS_LARGEFILE +if test "$ac_cv_sys_file_offset_bits" != no; then + CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=$ac_cv_sys_file_offset_bits" +fi + +if test "x$ac_cv_sys_large_files" != "xno"; then + CPPFLAGS="$CPPFLAGS -D_LARGE_FILES=1" +fi + +AC_CHECK_FUNCS(flock) +AC_CHECK_USLEEP + +dnl Checks for library functions. +AC_CHECK_FUNCS(socket fabsl strdup vsnprintf tzset) +AC_CHECK_SETENV +AC_CHECK_UNSETENV +AC_CHECK_GETDOMAINNAME +AC_CHECK_GETHOSTNAME +AC_C_BIGENDIAN + +AC_CHECK_FUNC(res_init) +if test "$ac_cv_func_res_init" = no; then + AC_CHECK_LIB(resolv, res_init, LIBRESOLV="-lresolv $LIBSOCKET", , $LIBSOCKET) +fi +AC_SUBST(LIBRESOLV) + +AC_CACHE_CHECK(for timezone variable, ac_cv_var_timezone, + AC_TRY_COMPILE([ + #include + ], [ + timezone = 1; + ], ac_cv_var_timezone=yes, ac_cv_var_timezone=no)) +if test $ac_cv_var_timezone = yes; then + AC_DEFINE(HAVE_TIMEZONE, 1, [define if you have a timezone variable]) +fi +AC_CACHE_CHECK(for tm_gmtoff in struct tm, ac_cv_struct_tm_gmtoff, + AC_TRY_COMPILE([ + #include + ], [ + struct tm tm; + tm.tm_gmtoff = 1; + ], ac_cv_struct_tm_gmtoff=yes, ac_cv_struct_tm_gmtoff=no)) +if test $ac_cv_struct_tm_gmtoff = yes; then + AC_DEFINE(HAVE_TM_GMTOFF, 1, [Define if you have a tm_gmtoff member in struct tm]) +fi + +# check for SLP +dnl define the configure option that disables slp +AC_ARG_ENABLE(slp, [ --disable-slp don't require libslp (Browsing in krfb and krdc not possible) ], with_slp=$enableval, with_slp=yes) +if test "$with_slp" = "yes"; then +AC_MSG_CHECKING(for SLP support) +save_slptest_LIBS="$LIBS" +save_slptest_LDFLAGS="$LDFLAGS" +save_slptest_CPPFLAGS="$CPPFLAGS" +LDFLAGS="$all_libraries $LDFLAGS" +CPPFLAGS="$CPPFLAGS $all_includes" +LIBS="-lslp" +AC_TRY_LINK( [ + #include + ],[ + SLPOpen(0, SLP_FALSE, (SLPHandle*) 0); + ],[ + AC_DEFINE(HAVE_SLP,1,[Define if SLP is available]) + LIB_SLP="-lslp" + AC_MSG_RESULT(yes) + ],[ + AC_MSG_RESULT(no) + LIB_SLP="" +]) +CPPFLAGS=$save_slptest_CPPFLAGS +LDFLAGS=$save_slptest_LDFLAGS +LIBS=$save_slptest_LIBS +fi +AC_SUBST(LIB_SLP) + +KDE_CHECK_THREADING + +dnl For apps that NEED threads +if test -z "$LIBPTHREAD" && test -z "$USE_THREADS"; then + DO_NOT_COMPILE="$DO_NOT_COMPILE kdict krfb krdc" +fi +CXXFLAGS="$CXXFLAGS $KDE_DEFAULT_CXXFLAGS" + +AH_VERBATIM(_osf_config_h, +[ +#ifdef __osf__ +/* From Tom Leitner */ +#if __STDC__ +#include +#include +#else +#include +#endif +#ifndef __OSF_INCLUDED__ +#define __OSF_INCLUDED__ +#define MSG_NOSIGNAL 0 +#ifndef AF_LOCAL +#define AF_LOCAL 1 /* is the same as AF_UNIX */ +#endif +#ifndef herror +#define herror(a) printf(a) +#endif + +#include +#ifdef __cplusplus +extern "C" int sethostname (char *name, int name_len ); +extern "C" int flock(int filedes, int operation ); +#else +int sethostname (char *name, int name_len ); +int flock(int filedes, int operation ); +#endif +#endif +#endif +]) diff --git a/dcoprss/Makefile.am b/dcoprss/Makefile.am new file mode 100644 index 00000000..2bc93d67 --- /dev/null +++ b/dcoprss/Makefile.am @@ -0,0 +1,28 @@ +bin_PROGRAMS = rssservice rssclient feedbrowser +INCLUDES = -I$(top_srcdir) $(all_includes) + +rssservice_LDFLAGS = $(KDE_RPATH) $(all_libraries) +rssservice_LDADD = $(LIB_KIO) ../librss/librss.la +rssservice_SOURCES = main.cpp service.cpp query.cpp document.cpp article.cpp query.skel service.skel xmlrpciface.cpp cache.cpp + + +# client stuff +rssclient_LDFLAGS = $(KDE_RPATH) $(all_libraries) +rssclient_LDADD = $(LIB_KDECORE) +rssclient_SOURCES = client.cpp + +feedbrowser_LDFLAGS = $(KDE_RPATH) $(all_libraries) +feedbrowser_LDADD = $(LIB_KDEUI) +feedbrowser_SOURCES = feedbrowser.skel feedbrowser.cpp + +noinst_HEADERS = service.h query.h xmlrpciface.h cache.h + +METASOURCES = AUTO + +messages: rc.cpp + $(XGETTEXT) *.cpp -o $(podir)/dcoprss.pot + + +service_DATA = rssservice.desktop +servicedir = $(kde_servicesdir) + diff --git a/dcoprss/article.cpp b/dcoprss/article.cpp new file mode 100644 index 00000000..d191bd74 --- /dev/null +++ b/dcoprss/article.cpp @@ -0,0 +1,49 @@ +/* $Id$ */ +/*************************************************************************** + article.cpp - A DCOP Service to provide RSS data + ------------------- + begin : Saturday 15 February 2003 + copyright : (C) 2003 by Ian Reinhart Geiser + email : geiseri@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. * + * * + ***************************************************************************/ +#include +#include +#include "service.h" + +RSSArticle::RSSArticle(Article *art) : + DCOPObject(), m_Art(art) +{ + kdDebug() << "New article..." << endl; + kdDebug() << m_Art->link().prettyURL() << endl; +} + +RSSArticle::~RSSArticle() +{ + kdDebug() << "Article going away..." << endl; + delete m_Art; +} + +QString RSSArticle::title() +{ + //kdDebug() << "Get title " << m_Art->title() << endl; + return m_Art->title(); +} + +QString RSSArticle::description() +{ + return m_Art->description(); +} + +QString RSSArticle::link() +{ + return m_Art->link().prettyURL(); +} diff --git a/dcoprss/cache.cpp b/dcoprss/cache.cpp new file mode 100644 index 00000000..9c80a9a3 --- /dev/null +++ b/dcoprss/cache.cpp @@ -0,0 +1,129 @@ +/* + * cache.cpp - (c) 2003 Frerich Raabe + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "cache.h" +#include "xmlrpciface.h" + +#include +#include + +#include +#include + +bool CacheEntry::isValid() const +{ + // Cache entries get invalid after on hour. One shouldn't hardcode this + // but for now it'll do. + return m_timeStamp.secsTo( QDateTime::currentDateTime() ) < 3600; +} + +Cache *Cache::m_instance = 0; + +Cache &Cache::self() +{ + if ( !m_instance ) + m_instance = new Cache; + return *m_instance; +} + +QString Cache::getCacheKey( const QString &server, const QString &method, + const QValueList &args ) +{ + QString key; + key = server + QString::fromLatin1( "__" ); + key += method + QString::fromLatin1( "__" ); + QValueList::ConstIterator it = args.begin(); + QValueList::ConstIterator end = args.end(); + for ( ; it != end; ++it ) + key += KXMLRPC::Query::marshal( *it ); + + return key; +} + +Cache::Cache() +{ + load(); +} + +Cache::~Cache() +{ + save(); +} + +void Cache::load() +{ + QFile file( cacheFileName() ); + if ( !file.open( IO_ReadOnly ) ) { + kdDebug() << "Failed to open cache file " << cacheFileName() << endl; + return; + } + + QDataStream stream( &file ); + while ( !stream.atEnd() ) { + QString key; + stream >> key; + + CacheEntry *entry = new CacheEntry; + stream >> *entry; + + QDict::insert( key, entry ); + } +} + +void Cache::save() +{ + QFile file( cacheFileName() ); + if ( !file.open( IO_WriteOnly ) ) { + kdDebug() << "Failed to open cache file " << cacheFileName() << endl; + return; + } + + QDataStream stream( &file ); + + QDictIterator it( *this ); + for ( ; it.current() != 0; ++it ) + stream << it.currentKey() << *it.current(); +} + +void Cache::touch( const QString &key ) +{ + CacheEntry *entry = find( key ); + if ( !entry ) + return; + entry->m_timeStamp = QDateTime::currentDateTime(); +} + +void Cache::insert( const QString &key, const KXMLRPC::Query::Result &result ) +{ + CacheEntry *entry = new CacheEntry; + entry->m_timeStamp = QDateTime::currentDateTime(); + entry->m_result = result; + QDict::insert( key, entry ); +} + +QString Cache::cacheFileName() const +{ + return locateLocal( "appdata", "cache/dcoprss.cache" ); +} + diff --git a/dcoprss/cache.h b/dcoprss/cache.h new file mode 100644 index 00000000..8248b609 --- /dev/null +++ b/dcoprss/cache.h @@ -0,0 +1,86 @@ +/* + * cache.h - (c) 2003 Frerich Raabe + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef CACHE_H +#define CACHE_H + +#include +#include +#include + +#include + +class CacheEntry +{ + friend class Cache; + friend QDataStream &operator>>( QDataStream &s, CacheEntry &e ); + public: + const QDateTime &timeStamp() const { return m_timeStamp; } + const KXMLRPC::Query::Result result() const { return m_result; } + bool isValid() const; + + private: + QDateTime m_timeStamp; + KXMLRPC::Query::Result m_result; +}; + +class Cache : public QDict +{ + public: + static Cache &self(); + + static QString getCacheKey( const QString &server, + const QString &method, + const QValueList &args ); + + void load(); + void save(); + + void touch( const QString &key ); + + void insert( const QString &key, const KXMLRPC::Query::Result &result ); + + private: + Cache(); + Cache( const Cache &rhs ); // disabled + Cache &operator=( const Cache &rhs ); // disabled + ~Cache(); + + QString cacheFileName() const; + + static Cache *m_instance; +}; + +inline QDataStream &operator<<( QDataStream &s, const CacheEntry &e ) +{ + return s << e.timeStamp() << e.result(); +} + +inline QDataStream &operator>>( QDataStream &s, CacheEntry &e ) +{ + return s >> e.m_timeStamp >> e.m_result; +} + +#endif // CACHE_H +// vim:ts=4:sw=4:noet diff --git a/dcoprss/client.cpp b/dcoprss/client.cpp new file mode 100644 index 00000000..b74894de --- /dev/null +++ b/dcoprss/client.cpp @@ -0,0 +1,75 @@ +/* $Id$ */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* +class rssIface : virtual public DCOPObject +{ + K_DCOP +public: + + rssIface( KApplication *app) + { + // get our DCOP client and attach so that we may use it + DCOPClient *client = app->dcopClient(); + client->attach(); + QString error; + QCString appID; + kdDebug() << "Looking for rss service..." << endl; + if (!client->isApplicationRegistered("rssservice")) + { + kdDebug() << "Could not find service so I am starting it..." << endl; + if(KApplication::startServiceByName("rssservice",QStringList(), &error, &appID )) + { + kdDebug() << "Starting rssservice failed with message: " << error << endl; + exit(0); + } + } + kdDebug ()<< "Accessing rssservice..." << endl; + + if (!connectDCOPSignal(0,0, "documentUpdated(DCOPRef)", + "refresh(DCOPRef)",false)) + kdDebug() << "Could not attach signal..." << endl; + else + kdDebug() << "attached dcop signals..." << endl; + + QString url("http://freshmeat.net/backend/fm.rdf"); + DCOPRef m_rssservice("rssservice","RSSService"); + m_rssservice.call("load(QString)", url); + QStringList returnList = m_rssservice.call("list()"); + DCOPRef doc = m_rssservice.call("document(QString)", returnList[0]); + QString title = doc.call("title()"); + QString link = doc.call("link()"); + QString description = doc.call("description()"); + kdDebug() << title << endl; + kdDebug() << link << endl; + kdDebug() << description << endl; + } + + k_dcop: + virtual void refresh(DCOPRef doc) + { + QString title = doc.call("title()"); + QString link = doc.call("link()"); + QString description = doc.call("description()"); + kdDebug() << title << endl; + kdDebug() << link << endl; + kdDebug() << description << endl; + } + + private: + +}; +*/ +int main(int argc, char **argv) +{ + KApplication *app = new KApplication(argc, argv, "client", false); + + app->exec(); +} diff --git a/dcoprss/document.cpp b/dcoprss/document.cpp new file mode 100644 index 00000000..b43f600e --- /dev/null +++ b/dcoprss/document.cpp @@ -0,0 +1,307 @@ +/* $Id$ */ +/*************************************************************************** + document.cpp - A DCOP Service to provide RSS data + ------------------- + begin : Saturday 15 February 2003 + copyright : (C) 2003 by Ian Reinhart Geiser + email : geiseri@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. * + * * + ***************************************************************************/ +#include +#include +#include +#include "service.h" + +RSSDocument::RSSDocument(const QString& url) : + QObject(), DCOPObject(), m_Url(url) +{ + + m_list.setAutoDelete( true ); + m_Doc = 0L; + m_pix = QPixmap(); + m_isLoading = false; + m_maxAge = 60; + m_Timeout = QDateTime::currentDateTime(); + m_state.clear(); +} + +RSSDocument::~RSSDocument() +{ + kdDebug() << "Document going away..." << endl; + + delete m_Doc; +} + +void RSSDocument::loadingComplete(Loader *ldr, Document doc, Status stat) +{ + + + if( m_Doc != 0L) + { + delete m_Doc; + + } + + if (stat != RSS::Success) + { + kdDebug() << "Document error! Loader:" << ldr->errorCode() << " Parser:" << stat << endl; + + m_isLoading = false; + m_Doc = 0L; + if( stat == RSS::ParseError ) + documentUpdateError(DCOPRef(this), 1); + else if( stat == RSS::RetrieveError ) + documentUpdateError(DCOPRef(this), 2); + else + documentUpdateError(DCOPRef(this), 3); + } + else + { + kdDebug() << "New Document is done..." << endl; + m_Doc = new Document(doc); + m_list.clear(); + Article::List list = doc.articles(); + for(Article::List::ConstIterator it = list.begin(); it != list.end(); ++it) + { + int state = m_state[(*it).title()]; + if( state == 0 ) m_state[(*it).title()] = 1; // new + else if( state == 1 ) m_state[(*it).title()] = 2; // old message now + m_list.append( new RSSArticle( new Article(*it))); + } + Image *img = m_Doc->image(); + if ( img ) + { + connect(img, SIGNAL(gotPixmap(const QPixmap &)), + SLOT(pixmapLoaded(const QPixmap &))); + img->getPixmap(); + pixmapUpdating(DCOPRef(this)); + } + m_isLoading = false; + documentUpdated(DCOPRef(this)); + + kdDebug() << "Old Mod time " << m_Timeout.toString() << endl; + m_Timeout = m_Timeout.addSecs(m_maxAge * 60 ); + kdDebug() << "New Mod time " << m_Timeout.toString() << endl; + + } +} + +void RSSDocument::pixmapLoaded(const QPixmap &pix ) +{ + m_pix = pix; + pixmapUpdated(DCOPRef(this)); +} + +QString RSSDocument::webMaster() +{ + if( m_Doc != 0L) + return m_Doc->webMaster(); + else + return ""; +} + +QString RSSDocument::managingEditor() +{ + if( m_Doc != 0L) + return m_Doc->managingEditor(); + else + return ""; +} + +QString RSSDocument::rating() +{ + if( m_Doc != 0L) + return m_Doc->rating(); + else + return ""; +} + +QDateTime RSSDocument::lastBuildDate() +{ + if( m_Doc != 0L) + return m_Doc->lastBuildDate(); + else + return QDateTime::currentDateTime(); +} + +QDateTime RSSDocument::pubDate() +{ + if( m_Doc != 0L) + return m_Doc->pubDate(); + else + return QDateTime::currentDateTime(); +} + +QString RSSDocument::copyright() +{ + if( m_Doc != 0L) + return m_Doc->copyright(); + else + return ""; +} + +QStringList RSSDocument::articles() +{ + if( m_Doc != 0L) + { + kdDebug() << "Document giving articles..." << endl; + Article::List list = m_Doc->articles(); + QStringList stringList; + + for(Article::List::ConstIterator it = list.begin(); it != list.end(); ++it) + stringList.append((*it).title()); + return stringList; + } + else + return QStringList(); +} + +DCOPRef RSSDocument::article(int idx) +{ + if(m_list.at(idx)) + return DCOPRef(m_list.at(idx)); + else + return DCOPRef(); +} + +int RSSDocument::count() +{ + if( m_Doc != 0L) + return m_Doc->articles().count(); + return 0; +} + +QString RSSDocument::link() +{ + if( m_Doc != 0L) + return m_Doc->link().prettyURL(); + else + return ""; +} + +QString RSSDocument::description() +{ + if( m_Doc != 0L) + return m_Doc->description(); + else + return ""; +} + +QString RSSDocument::title() +{ + if( m_Doc != 0L) + return m_Doc->title(); + else + return ""; +} + +QString RSSDocument::verbVersion() +{ + if( m_Doc != 0L) + return m_Doc->verbVersion(); + else + return ""; +} + +QString RSSDocument::pixmapURL() +{ + if( m_Doc != 0L) + if( m_Doc->image() ) + return m_Doc->image()->url().prettyURL(); + else + return ""; + else + return ""; +} + +QPixmap RSSDocument::pixmap() +{ + return m_pix; +} + +bool RSSDocument::documentValid() +{ + if (m_Doc != 0L) + return true; + else + return false; +} + +bool RSSDocument::pixmapValid() +{ + return !m_pix.isNull(); +} + +void RSSDocument::refresh() +{ + kdDebug() << "Mod time " << m_Timeout.toString() << endl; + kdDebug() << "Current time " << QDateTime::currentDateTime().toString() << endl; + + if(!m_isLoading && (QDateTime::currentDateTime() >= m_Timeout)) + { + kdDebug() << "Document going to refresh" << endl; + m_isLoading = true; + Loader *loader = Loader::create(this, + SLOT(loadingComplete(Loader *, Document, Status))); + loader->loadFrom(KURL( m_Url ), new FileRetriever()); + documentUpdating(DCOPRef(this)); + } + else + { + documentUpdated(DCOPRef(this)); + if(pixmapValid()) + pixmapUpdated(DCOPRef(this)); + /* + else + { + // Refactor this! + Image *img = m_Doc->image(); + if ( img ) + { + connect(img, SIGNAL(gotPixmap(const QPixmap &)), + SLOT(pixmapLoaded(const QPixmap &))); + img->getPixmap(); + pixmapUpdating(DCOPRef(this)); + } + } + */ + } + +} + +int RSSDocument::maxAge() +{ + return m_maxAge; +} + +void RSSDocument::setMaxAge(int _min) +{ + m_Timeout.addSecs(-m_maxAge); + m_maxAge = _min; + m_Timeout.addSecs(m_maxAge); +} + +int RSSDocument::state( const QString &title) const +{ + return m_state[title]; +} + +void RSSDocument::setState( const QString &title, int s ) +{ + m_state[title] = s; +} + +void RSSDocument::read( const QString &title) +{ + m_state[title] = 3; +} + +#include "service.moc" diff --git a/dcoprss/feedbrowser.cpp b/dcoprss/feedbrowser.cpp new file mode 100644 index 00000000..59a55040 --- /dev/null +++ b/dcoprss/feedbrowser.cpp @@ -0,0 +1,144 @@ +#include "feedbrowser.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +CategoryItem::CategoryItem( KListView *parent, const QString &category ) + : KListViewItem( parent ), + m_category( category ) +{ + init(); +} + +CategoryItem::CategoryItem( KListViewItem *parent, const QString &category ) + : KListViewItem( parent ), + m_category( category ) +{ + init(); +} + +void CategoryItem::init() +{ + m_populated = false; + m_dcopIface = 0; + + setText( 0, m_category.mid( m_category.findRev( '/' ) + 1 ).replace( '_', ' ' ) ); +} + +void CategoryItem::setOpen( bool open ) +{ + if ( open && !m_populated ) { + populate(); + m_populated = true; + } + KListViewItem::setOpen( open ); +} + +void CategoryItem::populate() +{ + m_dcopIface = new DCOPRSSIface( this, "m_dcopIface" ); + connect( m_dcopIface, SIGNAL( gotCategories( const QStringList & ) ), + this, SLOT( gotCategories( const QStringList & ) ) ); + m_dcopIface->getCategories( m_category ); +} + +void CategoryItem::gotCategories( const QStringList &categories ) +{ + delete m_dcopIface; + m_dcopIface = 0; + + QStringList::ConstIterator it = categories.begin(); + QStringList::ConstIterator end = categories.end(); + for ( ; it != end; ++it ) + new CategoryItem( this, *it ); + + if ( !categories.isEmpty() ) + KListViewItem::setOpen( true ); +} + +DCOPRSSIface::DCOPRSSIface( QObject *parent, const char *name ) : + QObject( parent, name ), DCOPObject( "FeedBrowser" ) +{ + connectDCOPSignal( "rssservice", "RSSQuery", "gotCategories(QStringList)", + "slotGotCategories(QStringList)", false ); +} + +void DCOPRSSIface::getCategories( const QString &cat ) +{ + QByteArray data; + QDataStream stream( data, IO_WriteOnly ); + stream << cat; + kapp->dcopClient()->send( "rssservice", "RSSQuery", + "getCategories(QString)", data ); +} + +void DCOPRSSIface::slotGotCategories( const QStringList &categories ) +{ + emit gotCategories( categories ); +} + +FeedBrowserDlg::FeedBrowserDlg( QWidget *parent, const char *name ) + : KDialogBase( parent, name, true, i18n( "DCOPRSS Feed Browser" ), + Close, Close, true ) +{ + m_dcopIface = new DCOPRSSIface( this, "m_dcopIface" ); + connect( m_dcopIface, SIGNAL( gotCategories( const QStringList & ) ), + this, SLOT( gotTopCategories( const QStringList & ) ) ); + + QVBox *mainWidget = makeVBoxMainWidget(); + + m_feedList = new KListView( mainWidget, "m_feedList" ); + m_feedList->setAllColumnsShowFocus( true ); + m_feedList->setRootIsDecorated( true ); + m_feedList->addColumn( i18n( "Name" ) ); + connect( m_feedList, SIGNAL( executed( QListViewItem * ) ), + this, SLOT( itemSelected( QListViewItem * ) ) ); + connect( m_feedList, SIGNAL( returnPressed( QListViewItem * ) ), + this, SLOT( itemSelected( QListViewItem * ) ) ); + + resize( 500, 400 ); + + getTopCategories(); +} + +void FeedBrowserDlg::getTopCategories() +{ + m_dcopIface->getCategories( "Top" ); +} + +void FeedBrowserDlg::gotTopCategories( const QStringList &categories ) +{ + QStringList::ConstIterator it = categories.begin(); + QStringList::ConstIterator end = categories.end(); + for ( ; it != end; ++it ) + new CategoryItem( m_feedList, *it ); +} + +void FeedBrowserDlg::itemSelected( QListViewItem *item ) +{ + item->setOpen( !item->isOpen() ); +} + +int main( int argc, char **argv ) +{ + KGlobal::locale()->setMainCatalogue( "dcoprss" ); + KAboutData aboutData( "feedbrowser", I18N_NOOP( "Feed Browser" ), "0.1" ); + KCmdLineArgs::init( argc, argv, &aboutData ); + KApplication app; + FeedBrowserDlg *dlg = new FeedBrowserDlg( 0 ); + app.setMainWidget( dlg ); + dlg->show(); + return app.exec(); +} + +#include "feedbrowser.moc" diff --git a/dcoprss/feedbrowser.h b/dcoprss/feedbrowser.h new file mode 100644 index 00000000..829ecd5e --- /dev/null +++ b/dcoprss/feedbrowser.h @@ -0,0 +1,66 @@ +#ifndef FEEDBROWSER_H +#define FEEDBROWSER_H + +#include +#include +#include +#include + +class DCOPRSSIface : public QObject, public DCOPObject +{ + K_DCOP + Q_OBJECT + public: + DCOPRSSIface( QObject *parent, const char *name = 0 ); + + k_dcop: + void slotGotCategories( const QStringList &categories ); + + public slots: + void getCategories( const QString &cat = "Top" ); + + signals: + void gotCategories( const QStringList &categories ); +}; + +class CategoryItem : public QObject, public KListViewItem +{ + Q_OBJECT + public: + CategoryItem( KListView *parent, const QString &category ); + CategoryItem( KListViewItem *parent, const QString &category ); + + virtual void setOpen( bool open ); + + private slots: + void gotCategories( const QStringList &categories ); + + private: + void populate(); + void init(); + + QString m_category; + bool m_populated; + DCOPRSSIface *m_dcopIface; +}; + +class FeedBrowserDlg : public KDialogBase +{ + Q_OBJECT + friend class CategoryItem; + public: + FeedBrowserDlg( QWidget *parent, const char *name = 0 ); + + private slots: + void itemSelected( QListViewItem *item ); + void gotTopCategories( const QStringList &categories ); + + private: + void getTopCategories(); + + DCOPRSSIface *m_dcopIface; + KListView *m_feedList; +}; + +#endif // FEEDBROWSER_H +// vim:ts=4:sw=4:noet diff --git a/dcoprss/main.cpp b/dcoprss/main.cpp new file mode 100644 index 00000000..fcebf8c0 --- /dev/null +++ b/dcoprss/main.cpp @@ -0,0 +1,39 @@ +/* $Id$ */ + +#include +#include +#include +#include +#include +#include +#include +#include "service.h" +#include "query.h" + +int main (int argc, char *argv[]) +{ + KLocale::setMainCatalogue("dcoprss"); + KAboutData aboutdata("rssservice", I18N_NOOP("KDE RSS Service"), + "0.8", I18N_NOOP("A RSS data service."), + KAboutData::License_GPL, "(C) 2003, Ian Reinhart Geiser"); + aboutdata.addAuthor("Ian Reinhart Geiser",I18N_NOOP("Developer"),"geiseri@kde.org"); + + KCmdLineArgs::init( argc, argv, &aboutdata ); + // KCmdLineArgs::addCmdLineOptions( options ); + KUniqueApplication::addCmdLineOptions(); + + if (!KUniqueApplication::start()) + { + kdDebug() << "rssservice is already running!" << endl; + return (0); + } + + KUniqueApplication app; + kdDebug() << "starting rssservice " << endl; + // This app is started automatically, no need for session management + app.disableSessionManagement(); + RSSService *service = new RSSService; + QueryService *query = new QueryService(service); + + return app.exec(); +} diff --git a/dcoprss/query.cpp b/dcoprss/query.cpp new file mode 100644 index 00000000..b2c29fdf --- /dev/null +++ b/dcoprss/query.cpp @@ -0,0 +1,271 @@ +/* $Id$ */ + +/*************************************************************************** + query.cpp - A query interface to select RSS feeds. + ------------------- + begin : Saturday 15 February 2003 + copyright : (C) 2003 by Ian Reinhart Geiser + email : geiseri@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. * + * * + ***************************************************************************/ +#include "cache.h" +#include "query.h" + +#include +#include + +#include "service.h" +#include "xmlrpciface.h" + +using KXMLRPC::Server; + +void SlotCaller::call( QObject *object, const char *slot, + const KXMLRPC::Query::Result &result ) +{ + SlotCaller caller; + connect( &caller, SIGNAL( signal( const KXMLRPC::Query::Result &) ), + object, slot ); + emit caller.signal( result ); +} + +QueryService::QueryService( RSSService *service ) : QObject(), DCOPObject( "RSSQuery" ), + m_service( service ) +{ + m_xmlrpcServer = new KXMLRPC::Server( KURL( "http://www.syndic8.com/xmlrpc.php"), this ); +} + +QStringList QueryService::listActive() +{ + if ( !m_service ) + return QStringList(); + return m_service->list(); +} + +void QueryService::cachedCall( const QString &method, + const QValueList &args, + const char *slot ) +{ + kdDebug() << "Calling " << method << endl; + + const QString cacheKey = Cache::getCacheKey( m_xmlrpcServer->url().url(), + method, args ); + + CacheEntry *cacheEntry = Cache::self().find( cacheKey ); + if ( cacheEntry != 0 && cacheEntry->isValid() ) { + kdDebug() << "Using cached result." << endl; + SlotCaller::call( this, slot, cacheEntry->result() ); + } else { + kdDebug() << "No cached result found, querying server." << endl; + m_xmlrpcServer->call( method, args, this, slot ); + } +} + +void QueryService::updateCache( const KXMLRPC::Query::Result &result ) +{ + const QString cacheKey = Cache::getCacheKey( result.server(), + result.method(), + result.args() ); + + CacheEntry *cacheEntry = Cache::self().find( cacheKey ); + if ( cacheEntry == 0 ) { + kdDebug() << "Inserting returned result into cache." << endl; + Cache::self().insert( cacheKey, result ); + } +} + +void QueryService::findFeeds( const QString &query ) +{ + kdDebug() << "QueryService::findFeeds()" << endl; + + QStringList args; + args << query << "headlines_rank"; + + cachedCall( "syndic8.FindFeeds", Server::toVariantList( args ), + SLOT( slotFoundFeeds( const KXMLRPC::Query::Result & ) ) ); +} + +void QueryService::findSites( const QString& query ) +{ + kdDebug() << "QueryService::findSites()" << endl; + + cachedCall( "syndic8.FindSites", Server::toVariantList( query ), + SLOT( slotFoundFeeds( const KXMLRPC::Query::Result & ) ) ); +} + +void QueryService::getFeedInfo( const QVariant& ids ) +{ + kdDebug() << "QueryService::getFeedInfo()" << endl; + + cachedCall( "syndic8.GetFeedInfo", Server::toVariantList( ids ), + SLOT( slotGotFeedInfo( const KXMLRPC::Query::Result & ) ) ); +} + +void QueryService::getCategories( const QString &category ) +{ + kdDebug() << "QueryService::getCategories()" << endl; + + if ( category == "Top" ) { + cachedCall( "syndic8.GetCategoryRoots", Server::toVariantList( QString::fromLatin1( "DMOZ" ) ), + SLOT( slotGotCategories( const KXMLRPC::Query::Result & ) ) ); + } else { + QStringList args; + args << "DMOZ" << category; + cachedCall( "syndic8.GetCategoryChildren", Server::toVariantList( args ), + SLOT( slotGotCategories( const KXMLRPC::Query::Result & ) ) ); + } +} + +void QueryService::getFeedsInCategory( const QString &category ) +{ + kdDebug() << "QueryService::getFeedsInCategory()" << endl; + + QStringList args; + args << "DMOZ" << category; + + cachedCall( "syndic8.GetFeedsInCategory", Server::toVariantList( args ), + SLOT( slotGotFeedsInCategory( const KXMLRPC::Query::Result & ) ) ); +} + +void QueryService::slotFoundFeeds( const KXMLRPC::Query::Result &result ) +{ + kdDebug() << "QueryService::slotFoundFeeds()" << endl; + if ( !result.success() ) { + kdWarning() << "Failed to query for feeds: " << result.errorString() << endl; + return; + } + + updateCache( result ); + + QValueList ids; + + const QValueList values = result.data()[ 0 ].toList(); + QValueList::ConstIterator it = values.begin(); + QValueList::ConstIterator end = values.end(); + for ( ; it != end; ++it ) { + ids << ( *it ).toInt(); + kdDebug() << "Found feed #" << ( *it ).toInt() << endl; + } + feedIds( ids ); +} + +void QueryService::slotGotFeedInfo( const KXMLRPC::Query::Result &result ) +{ + kdDebug() << "QueryService::slotGotFeedInfo()" << endl; + if ( !result.success() ) { + kdWarning() << "Failed to get feed info: " << result.errorString() << endl; + return; + } + + updateCache( result ); + + QMap links; + QValueList feeds; + + const QValueList feedInfos = result.data(); + QValueList::ConstIterator it = feedInfos.begin(); + QValueList::ConstIterator end = feedInfos.end(); + for ( ; it != end; ++it ) { + const QMap feedInfo = ( *it ).toMap(); + + const QString name = feedInfo[ "sitename" ].toString(); + const QString link = feedInfo[ "dataurl" ].toString(); + links[ name ] = link; + + RSSNewsFeed feed; + feed.m_id = feedInfo[ "feedid" ].toUInt(); + feed.m_name = feedInfo[ "sitename" ].toString(); + feed.m_homePage = feedInfo[ "siteurl" ].toString(); + feed.m_sourceFile = feedInfo[ "dataurl" ].toString(); + feed.m_imageUrl = feedInfo[ "imageurl" ].toString(); + feed.m_webmaster = feedInfo[ "webmaster" ].toString(); + feed.m_editor = feedInfo[ "editor" ].toString(); + feed.m_publisher = feedInfo[ "publisher" ].toString(); + feed.m_creator = feedInfo[ "creator" ].toString(); + QDateTime dateTime; + dateTime.setTime_t( KRFCDate::parseDate( feedInfo[ "date_created" ].toString() ) ); + feed.m_dateCreated = dateTime; + dateTime.setTime_t( KRFCDate::parseDate( feedInfo[ "date_approved" ].toString() ) ); + feed.m_dateApproved = dateTime; + dateTime.setTime_t( KRFCDate::parseDate( feedInfo[ "date_xml_changed" ].toString() ) ); + feed.m_dateXmlChanged = dateTime; + feed.m_fetchable = feedInfo[ "fetchable" ].toBool(); + feed.m_description = feedInfo[ "description" ].toString(); + feed.m_origin = feedInfo[ "origin" ].toString(); + feed.m_languageCode = feedInfo[ "lang_code" ].toString(); + feed.m_status = feedInfo[ "status" ].toString(); + feed.m_version = feedInfo[ "rss_version" ].toString(); + feed.m_views = feedInfo[ "views" ].toUInt(); + feed.m_headlinesPerDay = feedInfo[ "headlines_per_day" ].toUInt(); + feed.m_headlinesRank = feedInfo[ "headlines_rank" ].toUInt(); + feed.m_toolkit = feedInfo[ "toolkit" ].toString(); + feed.m_toolkitVersion = feedInfo[ "toolkit_version" ].toString(); + feed.m_pollingInterval = feedInfo[ "cur_polling_interval" ].toUInt(); + dateTime.setTime_t( feedInfo[ "last_poll_time" ].toUInt() ); + feed.m_lastPoll = dateTime; + // ### feed.m_categories missing here! + + feeds << feed; + + kdDebug() << "Retrieved data for newsfeed '" << name << "' <" << link << ">" << endl; + } + + feedInfo( links ); + feedInfo( feeds ); +} + +void QueryService::slotGotCategories( const KXMLRPC::Query::Result &result ) +{ + kdDebug() << "QueryService::slotGotCategories()" << endl; + if ( !result.success() ) { + kdWarning() << "Failed to get the list of categories: " << result.errorString() << endl; + return; + } + + updateCache( result ); + + QStringList categories; + + const QValueList cats = result.data()[ 0 ].toList(); + QValueList::ConstIterator it = cats.begin(); + QValueList::ConstIterator end = cats.end(); + for ( ; it != end; ++it ) + categories << ( *it ).toString(); + + kdDebug() << "Got categories: " << categories.join( ", " ) << endl; + gotCategories( categories ); + +} + +void QueryService::slotGotFeedsInCategory( const KXMLRPC::Query::Result &result ) +{ + kdDebug() << "QueryService::slotGotFeedsInCategory()" << endl; + if ( !result.success() ) { + kdWarning() << "Failed to get the feeds in the given category: " << result.errorString() << endl; + return; + } + + updateCache( result ); + + QValueList ids; + + const QValueList values = result.data()[ 0 ].toList(); + QValueList::ConstIterator it = values.begin(); + QValueList::ConstIterator end = values.end(); + for ( ; it != end; ++it ) { + ids << ( *it ).toInt(); + kdDebug() << "Got feed in category: #" << ( *it ).toInt() << endl; + } + + gotFeedsInCategory( ids ); +} + +#include "query.moc" +// vim:ts=4:sw=4:noet diff --git a/dcoprss/query.h b/dcoprss/query.h new file mode 100644 index 00000000..d87e0505 --- /dev/null +++ b/dcoprss/query.h @@ -0,0 +1,120 @@ +/* $Id$ */ +#ifndef _QUERY_SERVICE +#define _QUERY_SERVICE + +/*************************************************************************** + query.h - A query interface to select RSS feeds. + ------------------- + begin : Saturday 15 February 2003 + copyright : (C) 2003 by Ian Reinhart Geiser + email : geiseri@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. * + * * + ***************************************************************************/ + +#include "rssnewsfeed.h" +#include "xmlrpciface.h" + +#include + +#include +#include +#include +#include + +class RSSService; + +/** + * Helper class which just calls the slot given it's QObject/const char* + * representation. + */ +class SlotCaller : public QObject +{ + Q_OBJECT + public: + static void call( QObject *object, const char *slot, + const KXMLRPC::Query::Result &value ); + + signals: + void signal( const KXMLRPC::Query::Result &value ); + + private: + SlotCaller() { } +}; + +class QueryService : public QObject, public DCOPObject +{ + K_DCOP + Q_OBJECT + public: + QueryService( RSSService *service ); + + k_dcop_signals: + void feedIds( QValueList ids ); + void feedInfo(QMap links); + void feedInfo(QValueList feeds); + void gotCategories( const QStringList &categories ); + void gotFeedsInCategory( const QValueList &ids ); + + k_dcop: + /** + * Lists the active feeds in use... + **/ + QStringList listActive(); // just for testing... + + /** + * Query the www.syndic8.com XML-RPC interface for the + * string. The RSS ids are treturned in a integer list. + **/ + void findFeeds( const QString& query ); + + /** + * Query the www.syndic8.com XML-RPC interface for the + * string and matches it against the SiteURL feed of + * each feed in the feed list. The RSS ids are treturned + * in a integer list. + **/ + void findSites( const QString& query ); + + /** + * Query the www.syndic8.com XML-RPC interface for the + * requested RSS feed(s). Returned is a QMap with the format + * of Name (Site URL), RSS URL + **/ + void getFeedInfo( const QVariant& ids ); + + /** + * Returns the list of subcategories in the specified category. + * If no "Top" is specified, the root categories are returned. + */ + void getCategories( const QString &category ); + + /** + * Queries the database for the list of needsfeed ID's which are + * associated with the given category. + */ + void getFeedsInCategory( const QString &category ); + + + private slots: + void slotFoundFeeds( const KXMLRPC::Query::Result &result ); + void slotGotFeedInfo( const KXMLRPC::Query::Result &result ); + void slotGotCategories( const KXMLRPC::Query::Result &result ); + void slotGotFeedsInCategory( const KXMLRPC::Query::Result &result ); + + private: + void cachedCall( const QString &method, const QValueList &args, + const char *slot ); + void updateCache( const KXMLRPC::Query::Result &result ); + + RSSService *m_service; + KXMLRPC::Server *m_xmlrpcServer; +}; +#endif diff --git a/dcoprss/rssnewsfeed.h b/dcoprss/rssnewsfeed.h new file mode 100644 index 00000000..3c2e04b5 --- /dev/null +++ b/dcoprss/rssnewsfeed.h @@ -0,0 +1,115 @@ +/* + * rssnewsfeed.h + * + * Copyright (c) 2003 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef RSSNEWSFEED_H +#define RSSNEWSFEED_H + +#include +#include +#include +#include + +class QueryService; + +class RSSNewsFeed +{ + friend QDataStream &operator>>( QDataStream &stream, RSSNewsFeed &feed ); + friend QDataStream &operator<<( QDataStream &stream, const RSSNewsFeed &feed ); + friend class QueryService; + public: + unsigned int id() const { return m_id; } + QString name() const { return m_name; } + QString description() const { return m_description; } + QString origin() const { return m_origin; } + QString languageCode() const { return m_languageCode; } + QString status() const { return m_status; } + QString version() const { return m_version; } + QString homePage() const { return m_homePage; } + QString sourceFile() const { return m_sourceFile; } + QString imageUrl() const { return m_imageUrl; } + QString webmaster() const { return m_webmaster; } + QString editor() const { return m_editor; } + QString publisher() const { return m_publisher; } + QString creator() const { return m_creator; } + const QDateTime &dateCreated() const { return m_dateCreated; } + const QDateTime &dateApproved() const { return m_dateApproved; } + const QDateTime dateXmlChanged() const { return m_dateXmlChanged; } + bool fetchable() const { return m_fetchable; } + unsigned int views() const { return m_views; } + unsigned int headlinesPerDay() const { return m_headlinesPerDay; } + unsigned int headlinesRank() const { return m_headlinesRank; } + QString toolkit() const { return m_toolkit; } + QString toolkitVersion() const { return m_toolkitVersion; } + unsigned int pollingInterval() const { return m_pollingInterval; } + const QDateTime &lastPoll() const { return m_lastPoll; } + QStringList categories() const { return m_categories; } + + private: + unsigned int m_id; + QString m_name; + QString m_description; + QString m_origin; + QString m_languageCode; + QString m_status; + QString m_version; + QString m_homePage; + QString m_sourceFile; + QString m_imageUrl; + QString m_webmaster; + QString m_editor; + QString m_publisher; + QString m_creator; + QDateTime m_dateCreated; + QDateTime m_dateApproved; + QDateTime m_dateXmlChanged; + bool m_fetchable; + unsigned int m_views; + unsigned int m_headlinesPerDay; + unsigned int m_headlinesRank; + QString m_toolkit; + QString m_toolkitVersion; + unsigned int m_pollingInterval; + QDateTime m_lastPoll; + QStringList m_categories; +}; + +inline QDataStream &operator<<( QDataStream &stream, const RSSNewsFeed &feed ) +{ + return stream << feed.m_id << feed.m_name << feed.m_description + << feed.m_origin << feed.m_languageCode << feed.m_status + << feed.m_version << feed.m_homePage << feed.m_sourceFile + << feed.m_imageUrl << feed.m_webmaster << feed.m_publisher + << feed.m_creator << feed.m_dateCreated << feed.m_dateApproved + << feed.m_dateXmlChanged << feed.m_fetchable << feed.m_views + << feed.m_headlinesPerDay << feed.m_headlinesRank + << feed.m_toolkit << feed.m_toolkitVersion + << feed.m_pollingInterval << feed.m_lastPoll + << feed.m_categories; +} + +inline QDataStream &operator>>( QDataStream &stream, RSSNewsFeed &feed ) +{ + int i; + stream >> feed.m_id >> feed.m_name >> feed.m_description + >> feed.m_origin >> feed.m_languageCode >> feed.m_status + >> feed.m_version >> feed.m_homePage >> feed.m_sourceFile + >> feed.m_imageUrl >> feed.m_webmaster >> feed.m_publisher + >> feed.m_creator >> feed.m_dateCreated >> feed.m_dateApproved + >> feed.m_dateXmlChanged >> i >> feed.m_views + >> feed.m_headlinesPerDay >> feed.m_headlinesRank + >> feed.m_toolkit >> feed.m_toolkitVersion + >> feed.m_pollingInterval >> feed.m_lastPoll + >> feed.m_categories; + feed.m_fetchable = i != 0; + return stream; +} + +#endif +// vim:ts=4:sw=4:noet diff --git a/dcoprss/rssservice.desktop b/dcoprss/rssservice.desktop new file mode 100644 index 00000000..1692b267 --- /dev/null +++ b/dcoprss/rssservice.desktop @@ -0,0 +1,88 @@ +[Desktop Entry] +Type=Service +Name=rssservice +Name[bn]=আর-à¦à¦¸-à¦à¦¸ সারà§à¦­à¦¿à¦¸ +Name[ca]=Servei RSS +Name[cs]=RSS služba +Name[de]=RSS-Dienst +Name[el]=υπηÏεσία rss +Name[he]=שרות RSS +Name[hi]=आरà¤à¤¸à¤à¤¸-सरà¥à¤µà¤¿à¤¸ +Name[hu]=RSS szolgáltatás +Name[it]=Servizio RSS +Name[ja]=rssサービス +Name[lt]=rss tarnyba +Name[nb]=rss-tjeneste +Name[nds]=RSS-Deenst +Name[nl]=RSS-dienst +Name[nn]=rss-teneste +Name[pl]=UsÅ‚uga RSS +Name[pt_BR]=serviço rss +Name[ro]=Serviciu RSS +Name[sv]=RSS-tjänst +Name[ta]=rssசேவை +Name[th]=บริà¸à¸²à¸£ rss +Name[tr]=rssservisi +Exec=rssservice +X-DCOP-ServiceType=Unique +X-KDE-StartupNotify=false +Comment=RSS DCOP services +Comment[ar]= خدمات RSS DCOP +Comment[be]=СервіÑÑ‹ RSS Ð´Ð»Ñ DCOP +Comment[bn]=আর-à¦à¦¸-à¦à¦¸ ডিকপ সারà§à¦­à¦¿à¦¸ +Comment[br]=Servijoù DCOP RSS +Comment[bs]=RSS DCOP servisi +Comment[ca]=Serveis RSS de DCOP +Comment[cs]=RSS DCOP služby +Comment[cy]=Gwasanaethau DCOP RSS +Comment[da]=RSS DCOP-tjenester +Comment[de]=RSS DCOP-Dienste +Comment[el]=ΥπηÏεσίες RSS DCOP +Comment[eo]=RSS-DCOP-servoj +Comment[es]=Servicios RSS de DCOP +Comment[et]=RSS DCOP-teenused +Comment[eu]=RSS DCOP zerbitzuak +Comment[fa]=خدمات RSS DCOP +Comment[fi]=RSS-DCOP-palvelut +Comment[fr]=Services DCOP RSS +Comment[ga]=Seirbhísí DCOP RSS +Comment[gl]=Servicios RSS de DCOP +Comment[he]=שרותי RSS DCOP +Comment[hi]=आरà¤à¤¸à¤à¤¸ डीकॉप सेवाà¤à¤ +Comment[hr]=RSS DCOP servisi +Comment[hu]=RSS DCOP-szolgáltatás +Comment[is]=RSS DCOP þjónustur +Comment[it]=Servizi DCOP RSS +Comment[ja]=RSS DCOP サービス +Comment[ka]=RSS DCOP სერვისები +Comment[kk]=RSS DCOP қызметтері +Comment[km]=សáŸážœáž¶ RSS DCOP +Comment[lt]=RSS DCOP tarnyba +Comment[mk]=СервиÑи за RSS DCOP +Comment[nb]=RSS DCOP -tjenester +Comment[nds]=RSS-DCOP-Deenst +Comment[ne]=RSS DCOP सेवा +Comment[nl]=RSS DCOP-diensten +Comment[nn]=RSS DCOP-tenester +Comment[pa]=RSS DCOP ਸੇਵਾਵਾਂ +Comment[pl]=UsÅ‚uga DCOP dla RSS +Comment[pt]=Serviços DCOP de RSS +Comment[pt_BR]=Serviços DCOP RSS +Comment[ro]=Serviciu DCOP RSS +Comment[ru]=Службы RSS DCOP +Comment[se]=RSS DCOP-bálvalusat +Comment[sk]=RSS DCOP služby +Comment[sl]=Storitve RSS DCOP +Comment[sr]=RSS DCOP ÑервиÑи +Comment[sr@Latn]=RSS DCOP servisi +Comment[sv]=RSS DCOP-tjänster +Comment[ta]=RSS DCOP சேவைகள௠+Comment[tg]=Хадамотҳои RSS DCOP +Comment[th]=บริà¸à¸²à¸£ RSS DCOP +Comment[tr]=RSS DCOP hizmetleri +Comment[uk]=Служби RSS DCOP +Comment[uz]=RSS DCOP xizmatlari +Comment[uz@cyrillic]=RSS DCOP хизматлари +Comment[zh_CN]=RSS DCOP æœåŠ¡ +Comment[zh_HK]=RSS DCOP æœå‹™ +Comment[zh_TW]=RSS DCOP æœå‹™ diff --git a/dcoprss/service.cpp b/dcoprss/service.cpp new file mode 100644 index 00000000..74544dc1 --- /dev/null +++ b/dcoprss/service.cpp @@ -0,0 +1,105 @@ +/* $Id$ */ +/*************************************************************************** + service.cpp - A DCOP Service to provide RSS data + ------------------- + begin : Saturday 15 February 2003 + copyright : (C) 2003 by Ian Reinhart Geiser + email : geiseri@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. * + * * + ***************************************************************************/ +#include +#include +#include +#include "service.h" +#include "cache.h" + +RSSService::RSSService() : + DCOPObject("RSSService") +{ + m_list.setAutoDelete( true ); + + loadLinks(); +} + +RSSService::~RSSService() +{ +} + + +QStringList RSSService::list() +{ + QStringList lst; + QDictIterator itr(m_list); + for(; itr.current(); ++itr) + lst.append(itr.currentKey()); + return lst; +} + +DCOPRef RSSService::add(QString id) +{ + if(m_list.find(id) == 0L) { // add a new one only if we need to + m_list.insert(id, new RSSDocument(id)); + added(id); + saveLinks(); + } + return document(id); +} + +void RSSService::remove(QString id) +{ + m_list.remove(id); + removed(id); + saveLinks(); +} + +DCOPRef RSSService::document(QString id) +{ + if( m_list[id] ) + return DCOPRef(m_list[id]); + else + return DCOPRef(); +} + +void RSSService::exit() +{ + //Save all current RSS links. + saveLinks(); + Cache::self().save(); + kapp->quit(); +} + + +void RSSService::loadLinks() +{ + KConfig *conf = kapp->config(); + conf->setGroup("RSS Links"); + const QStringList links = conf->readListEntry ("links"); + QStringList::ConstIterator it = links.begin(); + QStringList::ConstIterator end = links.end(); + for ( ; it != end; ++it ) + add( *it ); +} + +void RSSService::saveLinks() +{ + KConfig *conf = kapp->config(); + conf->setGroup("RSS Links"); + QStringList lst; + QDictIterator itr(m_list); + for(; itr.current(); ++itr) + lst.append(itr.currentKey()); + + conf->writeEntry("links", lst); + conf->sync(); +} + + + diff --git a/dcoprss/service.h b/dcoprss/service.h new file mode 100644 index 00000000..35cf229e --- /dev/null +++ b/dcoprss/service.h @@ -0,0 +1,290 @@ +/* $Id$ */ +#ifndef _RSS_SERVICE +#define _RSS_SERVICE + +/*************************************************************************** + service.h - A DCOP Service to provide RSS data + ------------------- + begin : Saturday 15 February 2003 + copyright : (C) 2003 by Ian Reinhart Geiser + email : geiseri@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. * + * * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/** +* This is a DCOP Service do not include this header in anything +* +**/ +using namespace RSS; + +class RSSDocument; +class RSSArticle; + +class RSSService : public DCOPObject +{ + K_DCOP + + private: + + QDict m_list; + + public: + RSSService(); + ~RSSService(); + void saveLinks(); + void loadLinks(); + + + k_dcop_signals: + /** + * Emmitted when a new document has been added. You can then + * use document(QString) to get the dcop ref for the object. + * Note: this document may or may not be valid at this + * point so you should connect your dcop signals and then + * do a documentValid() on the dcop ref to make sure of its + * state. + **/ + + void added(QString); + /** + * Emmitted when the document has been removed. + * note at this point the DCOPRef for this object is + * invalid and you will cannot access it any longer. + * When in doubt call a refresh on it, since if its in the + * process of loading the document call will be safely ignored + * and you will be notified of the updates. + **/ + void removed(QString); + k_dcop: + /** + * Add a new rdf file resource. This will return a dcop reference to the resource. If its a new + * one it will be added otherwise an existing resource reference will be returned. + * once this reference has been returned you may connect dcop signals and then call + * refresh on the RSSDocument. The document will not be updated until refresh is called. + **/ + DCOPRef add(QString url); + /** + * Return a list of current rss documents + **/ + QStringList list(); + /** + * Remove an rss document resource. NOTE: Be aware that others may be using this + * resource and if you remove it they may break. Likewise be aware that someone may + * decide to remove your resource on you so you should always check to see if the resource + * is valid before you access it. + **/ + void remove(QString url); + /** + * Return the reference to a requested resource. If this resource is not present a null dcopref is + * returned. + **/ + DCOPRef document(QString url); + /** + * Exit the RSSService. This will clean everything up and exit. + **/ + void exit(); +}; + +class RSSDocument : public QObject, public DCOPObject +{ + Q_OBJECT + K_DCOP + + private: + bool m_isLoading; + QString m_Url; + Document *m_Doc; + QPixmap m_pix; + QPtrList m_list; + QMap m_state; + QDateTime m_Timeout; + int m_maxAge; + + private slots: + void pixmapLoaded(const QPixmap&); + void loadingComplete(Loader *, Document, Status); + + public: + RSSDocument(const QString& url); + ~RSSDocument(); + + k_dcop_signals: + /** + * The pixmap is currently loading + **/ + void pixmapUpdating(DCOPRef); + /** + * The pixmap is ready for viewing + * you can then use dcopref->call("pixmap()"); to return it. + * + **/ + void pixmapUpdated(DCOPRef); + /** + * The document is currently updating + **/ + void documentUpdating(DCOPRef); + /** + * The document is ready for viewing + * you can then use dcopref->call() to access its data + **/ + void documentUpdated(DCOPRef); + /** + * The document failed to update, with and error... + * 1 - RSS Parse Error + * 2 - Could not access file + * 3 - Unknown error. + **/ + void documentUpdateError(DCOPRef, int); + + k_dcop: + /** + * Return the webmaster information from the RSS::Document + **/ + QString webMaster(); + /** + * Return the manageing editor from the RSS::Document + **/ + QString managingEditor(); + /** + * Returns the rating of the RSS::Document + **/ + QString rating(); + /** + * Returns the last build date from the RSS::Document + **/ + QDateTime lastBuildDate(); + /** + * Returns the publication date from the RSS::Document + **/ + QDateTime pubDate(); + /** + * Returns the copyright information from the RSS::Document + **/ + QString copyright(); + /** + * Returns a list of article titles + **/ + QStringList articles(); + /** + * Returns the number of articles + **/ + int count(); + /** + * Returns a dcop reference to the article from the index + **/ + DCOPRef article(int idx); + /** + * Returns the link from the RSS::Document + **/ + QString link(); + /** + * Returns the description from the RSS::Document + **/ + QString description(); + /** + * Returns the title from the RSS::Document + **/ + QString title(); + /** + * Returns the text version from the RSS::Document + **/ + QString verbVersion(); + /** + * Returns the url for the pixmap from the RSS::Document + **/ + QString pixmapURL(); + /** + * Returns the actual pixmap from the RSS::Document's RSS::Image + **/ + QPixmap pixmap(); + /** + * Returns if the RSSDocument contains a valid RSS::Document yet. + **/ + bool documentValid(); + /** + * Returns if the RSSDocument contains a valid RSS::Image + **/ + bool pixmapValid(); + /** + * Refresh the current RSS::Document. + * This must be called before the document is valid. + **/ + void refresh(); + + /** + * Return the maximum age of the RSS document (Default is 60 minutes) + **/ + int maxAge(); + + /** + * Set the maximum age of the RSS document. + **/ + void setMaxAge(int minutes); + + /** + * Returns the state of the article + * 0 - not present (deleted from the rss service) + * 1 - new + * 2 - unread + * 3 - read + */ + int state( const QString &title) const; + + /** + * Set the article state + */ + void setState( const QString &title, int s ); + + /** + * Convience method that will set a title to read. + */ + void read( const QString &title); +}; + +class RSSArticle : public DCOPObject +{ + K_DCOP + + private: + Article *m_Art; + + public: + RSSArticle(Article *art); + ~RSSArticle(); + + k_dcop: + /** + * Return the articles title + **/ + QString title(); + /** + * Return the articles description + **/ + QString description(); + /** + * Return the link to the article + **/ + QString link(); +}; +#endif diff --git a/dcoprss/test.sh b/dcoprss/test.sh new file mode 100644 index 00000000..d5d3e773 --- /dev/null +++ b/dcoprss/test.sh @@ -0,0 +1,24 @@ +#!/bin/bash +ID=`dcopstart rssservice` +#dcop $ID RSSService add "http://www.kde.org/dotkdeorg.rdf" +#dcop $ID RSSService add "http://freshmeat.net/backend/fm.rdf" + +#dcop $REF1 refresh +#dcop $REF2 refresh +echo "Articles:" +DOCS=`dcop rssservice RSSService list` +for DOC in $DOCS +do + DOCREF=`dcop rssservice RSSService document "$DOC"` + TITLE=`dcop $DOCREF title` + CNT=`dcop $DOCREF count` + echo $TITLE - $CNT + echo "------------------------------------" + while let "CNT >0" + do + let "CNT=CNT-1" + ART=`dcop $DOCREF article $CNT` + TEXT=`dcop $ART title` + echo "$CNT $TEXT" + done +done diff --git a/dcoprss/xmlrpciface.cpp b/dcoprss/xmlrpciface.cpp new file mode 100644 index 00000000..e86639eb --- /dev/null +++ b/dcoprss/xmlrpciface.cpp @@ -0,0 +1,401 @@ +/* + * kxmlrpcclient.cpp - (c) 2003 Frerich Raabe + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "xmlrpciface.h" + +#include +#include +#include +#include + +#include + +using namespace KXMLRPC; + +Query *Query::create( QObject *parent, const char *name ) +{ + return new Query( parent, name ); +} + +void Query::call( const QString &server, const QString &method, + const QValueList &args, const QString &userAgent ) +{ + m_buffer.open( IO_ReadWrite ); + m_server = server; + m_method = method; + m_args = args; + + const QString xmlMarkup = markupCall( method, args ); + + QByteArray postData; + QDataStream stream( postData, IO_WriteOnly ); + stream.writeRawBytes( xmlMarkup.utf8(), xmlMarkup.length() ); + + KIO::TransferJob *job = KIO::http_post( KURL( server ), postData, false ); + job->addMetaData( "UserAgent", userAgent ); + job->addMetaData( "content-type", "Content-Type: text/xml; charset=utf-8" ); + connect( job, SIGNAL( infoMessage( KIO::Job *, const QString & ) ), + this, SLOT( slotInfoMessage( KIO::Job *, const QString & ) ) ); + connect( job, SIGNAL( data( KIO::Job *, const QByteArray & ) ), + this, SLOT( slotData( KIO::Job *, const QByteArray & ) ) ); + connect( job, SIGNAL( result( KIO::Job * ) ), + this, SLOT( slotResult( KIO::Job * ) ) ); +} + +void Query::slotInfoMessage( KIO::Job *, const QString &msg ) +{ + emit infoMessage( msg ); +} + +void Query::slotData( KIO::Job *, const QByteArray &data ) +{ + m_buffer.writeBlock( data ); +} + +void Query::slotResult( KIO::Job *job ) +{ + Result response; + response.m_server = m_server; + response.m_method = m_method; + response.m_args = m_args; + + response.m_success = false; + + if ( job->error() != 0 ) { + response.m_errorCode = job->error(); + response.m_errorString = job->errorString(); + emit finished( response ); + delete this; + return; + } + + QDomDocument doc; + if ( !doc.setContent( m_buffer.buffer() ) ) { + response.m_errorCode = -1; + response.m_errorString = i18n( "Received invalid XML markup" ); + emit finished( response ); + delete this; + return; + } + + m_buffer.close(); + + if ( isMessageResponse( doc ) ) + response = parseMessageResponse( doc ); + else if ( isFaultResponse( doc ) ) + response = parseFaultResponse( doc ); + else { + response.m_errorCode = 1; + response.m_errorString = i18n( "Unknown type of XML markup received" ); + } + + // parserMessageResponse and parseFaultResponse overwrite these fields. + response.m_server = m_server; + response.m_method = m_method; + response.m_args = m_args; + + emit finished( response ); + delete this; +} + +bool Query::isMessageResponse( const QDomDocument &doc ) const +{ + return doc.documentElement().firstChild().toElement().tagName().lower() == "params"; +} + +Query::Result Query::parseMessageResponse( const QDomDocument &doc ) const +{ + Result response; + response.m_success = true; + + QDomNode paramNode = doc.documentElement().firstChild().firstChild(); + while ( !paramNode.isNull() ) { + response.m_data << demarshal( paramNode.firstChild().toElement() ); + paramNode = paramNode.nextSibling(); + } + + return response; +} + +bool Query::isFaultResponse( const QDomDocument &doc ) const +{ + return doc.documentElement().firstChild().toElement().tagName().lower() == "fault"; +} + +Query::Result Query::parseFaultResponse( const QDomDocument &doc ) const +{ + Result response; + response.m_success = false; + + QDomNode errorNode = doc.documentElement().firstChild().firstChild(); + const QVariant errorVariant = demarshal( errorNode.toElement() ); + response.m_errorCode = errorVariant.toMap()[ "faultCode" ].toInt(); + response.m_errorString = errorVariant.toMap()[ "faultString" ].toString(); + + return response; +} + +QString Query::markupCall( const QString &cmd, + const QValueList &args ) const +{ + QString markup = ""; + + markup += "" + cmd + ""; + + if ( !args.isEmpty() ) { + markup += ""; + QValueList::ConstIterator it = args.begin(); + QValueList::ConstIterator end = args.end(); + for ( ; it != end; ++it ) + markup += "" + marshal( *it ) + ""; + markup += ""; + } + + markup += ""; + + return markup; +} + +QString Query::marshal( const QVariant &arg ) +{ + QString s = ""; + switch ( arg.type() ) { + case QVariant::String: + case QVariant::CString: + s += "" + arg.toString() + ""; + break; + case QVariant::Int: + s += "" + QString::number( arg.toInt() ) + ""; + break; + case QVariant::Double: + s += "" + QString::number( arg.toDouble() ) + ""; + break; + case QVariant::Bool: + s += ""; + s += arg.toBool() ? "true" : "false"; + s += ""; + break; + case QVariant::ByteArray: + s += "" + KCodecs::base64Encode( arg.toByteArray() ) + ""; + break; + case QVariant::DateTime: + s += "" + arg.toDateTime().toString( Qt::ISODate ) + ""; + break; + case QVariant::List: { + s += ""; + const QValueList args = arg.toList(); + QValueList::ConstIterator it = args.begin(); + QValueList::ConstIterator end = args.end(); + for ( ; it != end; ++it ) + s += marshal( *it ); + s += ""; + break; + } + case QVariant::Map: { + s += ""; + QMap map = arg.toMap(); + QMap::ConstIterator it = map.begin(); + QMap::ConstIterator end = map.end(); + for ( ; it != end; ++it ) { + s += ""; + s += "" + it.key() + ""; + s += marshal( it.data() ); + s += ""; + } + s += ""; + break; + } + default: + kdWarning() << "Failed to marshal unknown variant type: " << arg.type() << endl; + return ""; + }; + return s + ""; +} + +QVariant Query::demarshal( const QDomElement &elem ) +{ + Q_ASSERT( elem.tagName().lower() == "value" ); + + if ( !elem.firstChild().isElement() ) + return QVariant( elem.text() ); + + const QDomElement typeElement = elem.firstChild().toElement(); + const QString typeName = typeElement.tagName().lower(); + + if ( typeName == "string" ) + return QVariant( typeElement.text() ); + else if ( typeName == "i4" || typeName == "int" ) + return QVariant( typeElement.text().toInt() ); + else if ( typeName == "double" ) + return QVariant( typeElement.text().toDouble() ); + else if ( typeName == "boolean" ) { + if ( typeElement.text().lower() == "true" || typeElement.text() == "1" ) + return QVariant( true ); + else + return QVariant( false ); + } else if ( typeName == "base64" ) + return QVariant( KCodecs::base64Decode( typeElement.text().latin1() ) ); + else if ( typeName == "datetime" || typeName == "datetime.iso8601" ) + return QVariant( QDateTime::fromString( typeElement.text(), Qt::ISODate ) ); + else if ( typeName == "array" ) { + QValueList values; + QDomNode valueNode = typeElement.firstChild().firstChild(); + while ( !valueNode.isNull() ) { + values << demarshal( valueNode.toElement() ); + valueNode = valueNode.nextSibling(); + } + return QVariant( values ); + } else if ( typeName == "struct" ) { + QMap map; + QDomNode memberNode = typeElement.firstChild(); + while ( !memberNode.isNull() ) { + const QString key = memberNode.toElement().elementsByTagName( "name" ).item( 0 ).toElement().text(); + const QVariant data = demarshal( memberNode.toElement().elementsByTagName( "value" ).item( 0 ).toElement() ); + map[ key ] = data; + memberNode = memberNode.nextSibling(); + } + return QVariant( map ); + } else + kdWarning() << "Cannot demarshal unknown type " << typeName << endl; + + return QVariant(); +} + +Query::Query( QObject *parent, const char *name ) : QObject( parent, name ) +{ +} + +QValueList Server::toVariantList( const QVariant &arg ) +{ + QValueList args; + args << arg ; + return args; +} + +QValueList Server::toVariantList( int arg ) +{ + QValueList args; + args << arg ; + return args; +} + +QValueList Server::toVariantList( bool arg ) +{ + QValueList args; + args << arg ; + return args; +} + +QValueList Server::toVariantList( double arg ) +{ + QValueList args; + args << arg ; + return args; +} + +QValueList Server::toVariantList( const QString &arg ) +{ + QValueList args; + args << arg ; + return args; +} + +QValueList Server::toVariantList( const QCString &arg ) +{ + QValueList args; + args << arg ; + return args; +} + +QValueList Server::toVariantList( const QByteArray &arg ) +{ + QValueList args; + args << arg ; + return args; +} + +QValueList Server::toVariantList( const QDateTime &arg ) +{ + QValueList args; + args << arg ; + return args; +} + +QValueList Server::toVariantList( const QStringList &arg ) +{ + QValueList args; + QStringList::ConstIterator it = arg.begin(); + QStringList::ConstIterator end = arg.end(); + for ( ; it != end; ++it ) + args << QVariant( *it ); + return args; +} + +Server::Server( const KURL &url, QObject *parent, const char *name ) + : QObject( parent, name ) +{ + if ( url.isValid() ) + m_url = url; +} + +void Server::setUrl( const KURL &url ) +{ + m_url = url.isValid() ? url : KURL(); +} + +void Server::call( const QString &method, const QValueList &args, + QObject *receiver, const char *slot ) +{ + if ( m_url.isEmpty() ) { + kdWarning() << "Cannot execute call to " << method << ": empty server URL" << endl; + return; + } + + Query *query = Query::create( this ); + connect( query, SIGNAL( infoMessage( const QString & ) ), + this, SIGNAL( infoMessage( const QString & ) ) ); + connect( query, SIGNAL( finished( const KXMLRPC::Query::Result & ) ), + receiver, slot ); + query->call( m_url.url(), method, args, m_userAgent ); +} + +void Server::call( const QString &method, const QValueList &args, + QObject *receiver, const char *slot, + QObject *infoObject, const char *infoSlot ) +{ + if ( m_url.isEmpty() ) { + kdWarning() << "Cannot execute call to " << method << ": empty server URL" << endl; + return; + } + + Query *query = Query::create( this ); + connect( query, SIGNAL( infoMessage( const QString &msg ) ), + infoObject, infoSlot ); + connect( query, SIGNAL( finished( const KXMLRPC::Query::Result & ) ), + receiver, slot ); + query->call( m_url.url(), method, args, m_userAgent ); +} + +#include "xmlrpciface.moc" +// vim:ts=4:sw=4:noet diff --git a/dcoprss/xmlrpciface.h b/dcoprss/xmlrpciface.h new file mode 100644 index 00000000..f7897e10 --- /dev/null +++ b/dcoprss/xmlrpciface.h @@ -0,0 +1,185 @@ +/* + * kxmlrpcclient.h - (c) 2003 Frerich Raabe + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef KXMLRPCCLIENT_H +#define KXMLRPCCLIENT_H + +#include + +#include +#include +#include +#include +#include + +class QDomDocument; +class QDomElement; + +namespace KIO +{ + class Job; +} + +namespace KXMLRPC +{ + class Query; + class QueryResult; + class Server; + + class Query : public QObject + { + Q_OBJECT + public: + class Result + { + friend class Query; + friend QDataStream &operator>>( QDataStream &s, Query::Result &r ); + public: + Result() { } + + bool success() const { return m_success; } + int errorCode() const { return m_errorCode; } + QString errorString() const { return m_errorString; } + QValueList data() const { return m_data; } + QString server() const { return m_server; } + QString method() const { return m_method; } + QValueList args() const { return m_args; } + + private: + bool m_success; + int m_errorCode; + QString m_errorString; + QValueList m_data; + QString m_server; + QString m_method; + QValueList m_args; + }; + + static Query *create( QObject *parent = 0, const char *name = 0 ); + static QString marshal( const QVariant &v ); + static QVariant demarshal( const QDomElement &e ); + + public slots: + void call( const QString &server, const QString &method, + const QValueList &args = QValueList(), + const QString &userAgent = "KDE-XMLRPC" ); + + signals: + void infoMessage( const QString &msg ); + void finished( const KXMLRPC::Query::Result &result ); + + private slots: + void slotInfoMessage( KIO::Job *job, const QString &msg ); + void slotData( KIO::Job *job, const QByteArray &data ); + void slotResult( KIO::Job *job ); + + private: + bool isMessageResponse( const QDomDocument &doc ) const; + bool isFaultResponse( const QDomDocument &doc ) const; + + Result parseMessageResponse( const QDomDocument &doc ) const; + Result parseFaultResponse( const QDomDocument &doc ) const; + + QString markupCall( const QString &method, + const QValueList &args ) const; + + Query( QObject *parent = 0, const char *name = 0 ); + + QBuffer m_buffer; + QString m_server; + QString m_method; + QValueList m_args; + }; + + class Server : public QObject + { + Q_OBJECT + public: + Server( const KURL &url = KURL(), + QObject *parent = 0, const char *name = 0 ); + + const KURL &url() const { return m_url; } + void setUrl( const KURL &url ); + + QString userAgent() const { return m_userAgent; } + void setUserAgent( const QString &userAgent) { m_userAgent = userAgent; } + + template + void call( const QString &method, const QValueList &arg, + QObject *object, const char *slot ); + + static QValueList toVariantList( const QVariant &arg ); + static QValueList toVariantList( int arg ); + static QValueList toVariantList( bool arg ); + static QValueList toVariantList( double arg ); + static QValueList toVariantList( const QString &arg ); + static QValueList toVariantList( const QCString &arg ); + static QValueList toVariantList( const QByteArray &arg ); + static QValueList toVariantList( const QDateTime &arg ); + static QValueList toVariantList( const QStringList &arg ); + + signals: + void infoMessage( const QString &msg ); + + public slots: + void call( const QString &method, const QValueList &args, + QObject *object, const char *slot ); + void call( const QString &method, const QValueList &args, + QObject *object, const char *slot, + QObject *infoObject, const char *infoSlot ); + + private: + KURL m_url; + QString m_userAgent; + }; + + inline QDataStream &operator>>( QDataStream &s, Query::Result &r ) + { + return s >> r.m_errorCode >> r.m_errorString >> r.m_data + >> r.m_server >> r.m_method >> r.m_args; + } +} + +template +void KXMLRPC::Server::call( const QString &method, const QValueList &arg, + QObject *object, const char *slot ) +{ + QValueList args; + + typename QValueList::ConstIterator it = arg.begin(); + typename QValueList::ConstIterator end = arg.end(); + for ( ; it != end; ++it ) + args << QVariant( *it ); + + call( method, args, object, slot ); +} + +inline QDataStream &operator<<( QDataStream &s, const KXMLRPC::Query::Result &r ) +{ + return s << r.errorCode() << r.errorString() << r.data() + << r.server() << r.method() << r.args(); +} + +#endif // KXMLRPCCLIENT_H +// vim:ts=4:sw=4:noet diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 00000000..6812bd2d --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,5 @@ + +KDE_LANG = en +KDE_DOCS = AUTO +SUBDIRS = $(AUTODIRS) + diff --git a/doc/kcontrol/Makefile.am b/doc/kcontrol/Makefile.am new file mode 100644 index 00000000..930c270c --- /dev/null +++ b/doc/kcontrol/Makefile.am @@ -0,0 +1,6 @@ + +SUBDIRS = $(AUTODIRS) + +KDE_LANG = en +KDE_DOCS = AUTO + diff --git a/doc/kcontrol/kcmktalkd/Makefile.am b/doc/kcontrol/kcmktalkd/Makefile.am new file mode 100644 index 00000000..0f511040 --- /dev/null +++ b/doc/kcontrol/kcmktalkd/Makefile.am @@ -0,0 +1,2 @@ +KDE_LANG = en +KDE_DOCS = kcontrol/kcmtalkd diff --git a/doc/kcontrol/kcmktalkd/index.docbook b/doc/kcontrol/kcmktalkd/index.docbook new file mode 100644 index 00000000..3d444f1d --- /dev/null +++ b/doc/kcontrol/kcmktalkd/index.docbook @@ -0,0 +1,55 @@ + + + +]> + + +
+Talk + + + + +Lauri +Watts + + + + +2002-10-08 +3.01.00 + + +KDE +KControl +talk + + + + + +Talk Configuration + + + + +Introduction + +Please see the &ktalkd; manual for more information (you can +read it by entering help:/ktalkd/ in a +&konqueror; window.) + + +Section Author +This section +written by: + + + + + +
diff --git a/doc/kcontrol/lanbrowser/Makefile.am b/doc/kcontrol/lanbrowser/Makefile.am new file mode 100644 index 00000000..09726472 --- /dev/null +++ b/doc/kcontrol/lanbrowser/Makefile.am @@ -0,0 +1,2 @@ +KDE_LANG = en +KDE_DOCS = kcontrol/lanbrowser diff --git a/doc/kcontrol/lanbrowser/index.docbook b/doc/kcontrol/lanbrowser/index.docbook new file mode 100644 index 00000000..183d6d04 --- /dev/null +++ b/doc/kcontrol/lanbrowser/index.docbook @@ -0,0 +1,15 @@ + + + +]> +
+ +<acronym>LAN</acronym> Browsing + +Not yet documented + + + +
\ No newline at end of file diff --git a/doc/kdict/Makefile.am b/doc/kdict/Makefile.am new file mode 100644 index 00000000..41691557 --- /dev/null +++ b/doc/kdict/Makefile.am @@ -0,0 +1,3 @@ +KDE_LANG = en +KDE_DOCS = AUTO + diff --git a/doc/kdict/applet.png b/doc/kdict/applet.png new file mode 100644 index 00000000..95e79613 Binary files /dev/null and b/doc/kdict/applet.png differ diff --git a/doc/kdict/conf.png b/doc/kdict/conf.png new file mode 100644 index 00000000..ed75dd9f Binary files /dev/null and b/doc/kdict/conf.png differ diff --git a/doc/kdict/index.docbook b/doc/kdict/index.docbook new file mode 100644 index 00000000..b5454480 --- /dev/null +++ b/doc/kdict/index.docbook @@ -0,0 +1,1052 @@ + + + + + +]> + + + + +The &kdict; Handbook + + + +Christian +Gebauer + +
gebauer@kde.org
+
+
+ + +Christian +Gebauer + +
gebauer@kde.org
+
+Developer +
+ + + + +
+Reviewer +
+ +
+ + + +2002 +Christian Gebauer + +&FDLNotice; + +2003-09-30 +0.5.6 + +&kdict; is a graphical client for the +DICT protocol. It enables you to search +through dictionary databases for a word or phrase, then displays +suitable definitions. + + +KDE +kdict +kdenetwork +dictionary +translation + + +
+ + +Introduction + +&kdict; is a graphical client for the +DICT Protocol. It enables you to search +through dictionary databases for a word or phrase, then displays +suitable definitions. &kdict; tries to ease basic as well as advanced +queries. A separate list offers a convenient way to deal with the +enormous number of matching words that a advanced query can return. + + + +The remainder of &kdict;'s user interface resembles a web browser. For +instance, you can jump to the definition of a synonym by simply clicking +on the highlighted word. The back/forward functionality is also +implemented, enabling you to quickly go back to the result of previous +queries. + + + +&kdict; is able to process the content of the clipboard, so +it's easy to combine &kdict; with your web browser or text +editor. + +If your machine is behind a firewall, has no permanent internet +connection or the server of dict.org is too slow for you, you can set up +your own local server, all you need is available at www.dict.org. The advantages of a +local server are optimal performance and the ability to install +additional databases of your choice. This handbook contains a small tutorial for installation and links to +databases. + + + + +Using &kdict; + + +Getting Started + +After you have started &kdict; (using the panel menu, or by typing +kdict at the command prompt) the &kdict; main window +shows up: + + +Starting &kdict; + + +The &kdict; main window. +The &kdict; main window. + + + +There are two different methods to lookup a word or phrase with +&kdict;. You can use a define query. &kdict; will look for exact +matches in the databases and displays all matching definitions in the +lower left view. Or you can use a match query, in this case all matching +database entries will be displayed in the list view on the right +side. You can then decide what definitions you want to look at. This +method has the advantage that you can use more sophisticated search +strategies like prefix, suffix or regular expressions. + + + +Configuring the <systemitem>DICT</systemitem> server + +Before you can make your first query, you have to check if the +preset server settings work with your setup. You can modify the settings +in the preferences dialog. The +preset server is dict.org, which is a public server, so you don't have +to change anything if you have a working internet connection. + +You can test your configuration by selecting Server +Information from the Server menu, this +displays some status information about the server. + + +Now you should use ServerGet +Capabilities, this will fetch a list of all +available databases and strategies from the server. &kdict; is now able +to show you all features of the server in its menus. (You should always +call Get Capabilities after switching to a +new server) + + + + + +Basic Queries: Define + +A define query will search for words/phrases in the selected +database(s) that match the given text exactly. The definitions belonging +to these words are displayed in the main view. If the server finds +nothing suitable, &kdict; will use a search strategy optimized for spell +checking to display a list of of similar words. + +In most databases some words (especially synonyms) are marked with +brackets {}. These words are highlighted and by +clicking on them with the &LMB; you start an new define-query +with this word. + +You can select a part of the text with the &LMB;. The +selection is automatically copied into the clipboard. This is very +convenient, because you can use the &MMB; anywhere in the +main view to define the content of the clipboard. So, if you want to +get a definition for a word/phrase that is not tagged as synonym, you +just mark it with the &LMB; and get the definition when you +press the &MMB;. + +The &RMB; shows a popup menu, where you can choose to +start queries with the selected text, the clipboard or the tagged +synonym under the mouse pointer. The menu has also two entries +Back and +Forward. You can use them to browse through +the results of previous queries. + + + + +Advanced Queries: Match + +A match query uses the currently selected search strategy (the +strategy selector is is located above the match list) to search in the +selected database(s) for words similar to the given text. The result is +a list of similar words that appears in the match list on the right. The +entries are grouped according to the database they belong to. Now you +have multiple options: + + + +You can use the Get All button (located +below the match list) to fetch all definitions. Please note that the +number of listed words and fetched definitions may differ, because in +some cases two or more words share one definition and &kdict; removes +the duplicated definitions. + + + +You can use the mouse or the keyboard to select the most interesting +words in the list and then press the Get +Selected button (located below the match list) to fetch only +them. If you want to get all definitions from one database just select +the list item which contains the database name. + + + +When you press Enter (keyboard) or double click +(mouse) on a list item, &kdict; will ignore the selection and fetch the +definition for this item. When you do this with a +root item (an item that contains the database name), you +will get all definitions belonging to this database. + + + +You can use one of the words as a starting point for a new query, +this is done via the popup menu (&RMB;). + + + +A click with the &MMB; anywhere in the list will +start a new match query with the content of the clipboard (similar to +the main view). + +The &RMB; calls the popup menu for a list item, which +has the following entries: + + + +Get +Shows the definition for the current +item. + + +Match, +Define +Starts a match/define query with the current +item. + + + + +Match Clipboard Content, Define Clipboard Content + +Starts a match/define query with the current content of +the clipboard. + + + + +Get Selected, Get +All +Same functions as the buttons below the +list. + + + +Expand List, Collapse +List +(Un)folds all databases. + + + + + + +Database Sets + +Sometimes it's useful to restrict a query to a subset of the +available databases, for example all english-german dictionaries. This +is achieved by defining database sets. These sets appear +in the database selector as virtual databases. + +You can access the configuration dialog via +ServerEdit Database +Sets or the toolbar icon. + + +Editing database sets + + +The database set editor. +The database set editor. + + + +The dialog has the following elements: + + + +Set +You must use this selector to select the set you want to +modify. You can also rename a set here by entering a new name and +pressing the Save button. + + + +Save +Saves changes you made in the current set. You must use +this button before you select another set or leave the dialog, because +otherwise all changes will be lost. + + + +New +This button creates a new database set. + + + +Delete +Deletes the currently selected set. + + + +Close +Closes the dialog without saving your +changes. + + + +The two lists (Selected Databases and +Available Databases) show which databases are +currently in the database set. You can use the arrow buttons between the +lists to transfer items from one list to another. + +By the way, you can leave this configuration dialog open and +continue your work with &kdict;. This is a nice way to test your changes +immediately. + + + + +Preferences + +You can modify many aspects of &kdict;'s behavior in the +preferences dialog. The dialog can be opened via +SettingsConfigure +Kdict or the the toolbar icon. + + +Configuring &kdict; + + +The preferences dialog. +The preferences dialog. + + + +The dialog is divided into several pages. The +Default button restores the default values for +the current page. The Apply button will apply +your changes on all pages. The OK button will +apply the changes and close the dialog. The +Cancel button does this without saving the +changes. By the way, you can leave the preferences dialog open and +continue your work with &kdict;. This is a nice way to test your +changes. + + +The <guilabel>Server</guilabel> Page + + + +Hostname +The internet hostname or the ip address of the +DICT server. + + + +Port +This is the port number the server listens on. 2628 is +the default port and is used by the most servers. + + + +Hold connection for +&kdict; is able to keep the connection open in short +periods of inactivity. This feature avoids the lengthy login procedure +before every query. A value of 0 seconds disables this feature. Very +large values aren't useful, because in most cases the +DICT server will close the connection after a +couple of minutes. + + + +Timeout +This value determines how long &kdict; will wait for a +answer from the server. + + + +Command Buffer +The DICT protocol allows the +client to send multiple commands in one network packet. The size of the +internal command buffer determines how many commands are send in +parallel by &kdict;. You can try to tune this value for your network +connection, but in most cases it is not worth the +effort. + + + +Encoding +With this selector the text encoding method of the databases can be +specified. The default value is "utf8", this setting should work on +most servers. If an encoding is selected that doesn't match the encoding +used by the databases, you will see broken characters. + + + +Server requires authentication +Activate this if you want to provide a authentication +with username and password. (a server may require this for access to +all databases) You have to enter a valid Username +and Password combination below. + + + + + + +The <guilabel>Appearance</guilabel> Page + +On this page you can customize the colors and fonts of the definition +view. A proportional font for the normal text will increase readability, +but will also destroy the hardcoded layout of tables and similar things +in the definitions of some databases. + + + + +The <guilabel>Layout</guilabel> Page + +The layout of the result isn't really configurable yet. But you +can decide how many headings (a heading states which database the +definition belongs to) &kdict; should place in the result. The choices +should be selfexplaining. Note that changes on this page won't have any +effect until you start a new query. + + + + +The <guilabel>Miscellaneous</guilabel> Page + +On this page you can modify various limits that prevent &kdict; +from eating up insane amounts of memory. + + + +Definitions +This limits the number of definitions you can fetch at once by +selecting them in the match list. + + + +Cached Results +This number determines how many previous results are held in a internal cache for fast access. +You can set this to 0, but this will disable your ability to browse back to old results. + + + +History Entries +This is the number of past entries the input line remembers. +Large values will cause a slower start and shutdown of +&kdict;. + + + +Save history on exit +If this is selected, &kdict; will remember your +history between sessions. + + + +Define selected text on start +If this is selected, &kdict; will immediately try to +define the contents of the clipboard when it is +started. + + + + + + + + + + + +The Panel Applet + +If you use &kdict; frequently you may find it useful to use the +included panel applet. You can get the applet via +K-MenuConfigure +PanelAddAppletDictionary. + + +The Panel Applet + + +The Panel Applet +The Panel Applet + + + +The input field behaves like the input field of the &kdict; main +window. When you press Enter &kdict; opens and the +query starts. Instead of typing you can also select a phrase you +entered before from the drop down list. + +Additionally the applet features three push buttons: + + + +C +Define the current content of the clipboard. + + +D +Define the current content of the input field. + +M +Start a match query with the current content of the input field. + + + + + +Command Reference + + +The <guimenu>File</guimenu> Menu + + + + + +&Ctrl;S + +File +Save + +Saves the current result as an html document. + + + + +&Ctrl; +P +File +Print + +Prints the current result.. + + + + +File +Start Query + +Defines the content of the input field.. + + + + +File +Stop Query + +Aborts the current query.. + + + + +&Ctrl;Q +File +Quit + +Quits &kdict;. + + + + + + + +The <guimenu>Edit</guimenu> Menu + + + + + +&Ctrl;C +Edit +Copy + + +Copies the currently selected text into the clipboard. + + + + + +&Ctrl;C +Edit +Select All + + +Selects the complete text. + + + + +Edit +Define Clipboard Content + + +Defines the current content of the clipboard. + + + + + +Edit +Match Clipboard Content + + +Find database entries which match the current content of the +clipboard. + + + + + +&Ctrl;F +Edit +Find... + + +Finds a string in the displayed definitions. + + + + + + + + +The <guimenu>History</guimenu> Menu + + + + +&Alt;Left +History +Back + +Displays the previous search result. + + + + +&Alt;Right +History +Forward + +Displays the next search result. + + + + +History +Clear History + +Clears the list of past queries. + + + + +At the bottom of the History the ten last queries +are listed. + + + + +The <guimenu>Server</guimenu> Menu + + + + + Server +Get Capabilities + Determines which databases +and strategies are available on the +DICT server. You must call this once to be +able to specify search strategy and database for a +query. + + + + +Server +Edit Database Sets... + +Opens the database set editor. + + + + +Server +Database Information + +Submenu which offers a summary of the databases available, and detailed information for every database. + + + + +Server +Strategy Information + +Displays a list with short descriptions of the search strategies +available on the current server. + + + + +Server +Server Information + +Displays some status information (uptime, &etc;) about the current +DICT server. + + + + + + + +The <guimenu>Settings</guimenu> Menu +This menu provides options for configuring &kdict;, changing its +appearance, shortcuts and standard behavior. + + + + +Settings +Toolbars + +Submenu which toggles the toolbars on or off. You can +toggle either the Main toolbar or the Query toolbar +independently. + + + + +Settings +Show Statusbar + +Toggles the statusbar on/off. + + + + +Settings +Show Match List + +Hides (or shows) the match list. + + + + +Settings +Swallow Match List + +This (un-)swallows the match list into the main window. + + + + +Settings +Configure Shortcuts... + +Opens a dialog for changing the key bindings. +Using this option you can change the standard key shortcut for &kdict;'s commands +or create new ones. + + + + +Settings +Configure Toolbars... + +Opens a dialog for configuring the toolbar. You +can add and remove toolbuttons for &kdict;'s commands with this option. + + + + +Settings +Configure Kdict... + +Opens the preferences dialog. + + + + + + + +The <guimenu>Help</guimenu> Menu + +&help.menu.documentation; + + + + + + +Command Line Options + +&kdict; can be started directly from a terminal like &konsole; or +xterm. Several command line options are +available. + + + +kdict +lookup the given text. You will have to put the phrase into double quotes, +if the phrase contains more than one word. For example: kdict "double quote" + + +kdict +define the current content of the clipboard. + + +kdict +Displays the version number of &kdict; (and that of +&Qt;/&kde;). + + +kdict +Shows under which licenses &kdict; is being +published. + + + +&kdict; also supports all other command line options common to +&kde; and &Qt; programs. You can get a list of these options with +, + and + + + + + +Credits and License + +&kdict; - The &kde; Dictionary Client + +Copyright (c) 1999-2001, Christian Gebauer +Copyright (c) 1998, Matthias Hölzer-Klüpfel + + +&kdict; was originally written in 1998 by Matthias +Hölzer-Klüpfel hoelzer@kde.org. Currently it is +maintained by Christian Gebauer gebauer@kde.org. + +&underFDL; +&underArtisticLicense; + + + + +Installation + + +How to obtain &kdict; + +&install.intro.documentation; +&install.compile.documentation; + + + +Requirements + +Besides a working &kde; installation &kdict; requires the posix +threads library which is available on all modern unices. + + + + + +Mini-Howto: Installing a local <productname>DICT</productname> server + + +Obtaining and installing <command>dictd</command> + +At first the dictd daemon has to be installed. +The easiest way to install dictd is using a +precompiled package. Such a package is included both in Debian and SuSE &Linux;. You can find packages +that should work on all RPM based &Linux; distributions on rpmfind.net. + +If you want to compile dictd yourself, download +ftp://ftp.dict.org/pub/dict/dictd-1.9.1.tar.gz. +Compilation is easy, just unpack the archive and run +./configure, +make and +make +in the dictd folder. You might +want to use the option of the configure script +to install dictd in a different folder. By default +dictd will be installed in /usr/local. + + + + +Obtaining databases + +Now you need to download some databases. The standard set +(webster, wordnet, jargon file, foldoc, ...) that is present on the +DICT server of dict.org, is available from ftp://ftp.dict.org/pub/dict/pre/. +These are also available as Debian and rpm packages. + + +Additional preformatted databases + + + + +www.freedict.de +Translating dictionaries for Africaans, Czech, Danish, +English, French, German, Greek, Hungarian, Irish, Italian, Japanese, +Latin, Nederlands (Dutch), Portuguese, Russian, Serbo-Croatian, Swedish, +Slovak, Spanish, Swahili, Swedish, Turkish and Welsh + + + +http://www.wh9.tu-dresden.de/~heinrich/dict/ +Translating dictionaries for English, French, German, Italian, Latin, Portugue and Spanish. + + + +http://purl.oclc.org/NET/voko/revodict.tgz +Esperanto dictionary + + + +http://www.bainsware.com/downloads/obi-bio.tar.gz +OBI's Online Biographical Dictionary + + + +http://www.bainsware.com/downloads/inaug.tar.gz +The Inaugural Addresses of all the US presidents + + + + +You can find more databases on the link page of www.dict.org, but the majority of them +are not formatted for dictd. + +Each database consists of two files: The *.index file contains the index and the +*.dict.dz file the actual +data. Unpack all packages into a folder of your choice, for example +/usr/share/dict/. + + + + + +Configuration + +You have to create/modify two configuration files. Both are stored +in /usr/local/etc if you used the default +installation prefix (/usr/local). + +dict.conf belongs to the basic +dict client. It contains only one line: +server localhost. This tells +dict to use the local server. + +dictd.conf configures the server. First you +must add the access statement: access {allow localhost +deny *}. + + This example grants only local access and blocks all external +connections. You can use more than one allow and deny rule, for +example: access {allow localhost allow *.workgroup deny +*} The database statement configures the location of +the index and the data file for a database: + + + + + database web1913 { data "/usr/share/dict/web1913.dict.dz" + index "/usr/share/dict/web1913.index" } + + +You must add a statement for each database you want to use. + +Now you should be able to start dictd +and to use it with dict and &kdict;. + +Please consult the man page of dictd +for a complete description of dictd.conf. + + + +Starting <command>dictd</command> automatically + +If you want to use the dict server +frequently, you might want to start it automatically during the startup +process of your system. Some of the precomplied packages install a +suitable script, but you can also adapt the generic SYSV style script +included in the source distribution: ftp://ftp.dict.org/pub/dict/INITSCRIPT. + + + + +&documentation.index; + +
+ + diff --git a/doc/kdict/mainwin.png b/doc/kdict/mainwin.png new file mode 100644 index 00000000..77aa4782 Binary files /dev/null and b/doc/kdict/mainwin.png differ diff --git a/doc/kdict/seteditor.png b/doc/kdict/seteditor.png new file mode 100644 index 00000000..a089b850 Binary files /dev/null and b/doc/kdict/seteditor.png differ diff --git a/doc/kget/Makefile.am b/doc/kget/Makefile.am new file mode 100644 index 00000000..41691557 --- /dev/null +++ b/doc/kget/Makefile.am @@ -0,0 +1,3 @@ +KDE_LANG = en +KDE_DOCS = AUTO + diff --git a/doc/kget/fileopen.png b/doc/kget/fileopen.png new file mode 100644 index 00000000..037c2da9 Binary files /dev/null and b/doc/kget/fileopen.png differ diff --git a/doc/kget/index.docbook b/doc/kget/index.docbook new file mode 100644 index 00000000..eb8a2bab --- /dev/null +++ b/doc/kget/index.docbook @@ -0,0 +1,751 @@ + + + KGet"> + + + + +]> + + + + +The &kget; Handbook + + + + +Jonathan +E. +Drews + +j.e.drews@att.net + + + + + + +2003 +Jonathan E. Drews + +&FDLNotice; + +2005-08-31 +0.8.4 + + + + + +&kget; allows you to group downloads. In some cases, &kget; can resume these +downloads even if you shutdown your computer before the downloads have completed. + + + + +KDE +kdeutils +kget +kppp +download + + + + + +Introduction + + +To download a document or package, drag and drop the &URL; on to &kget;. + + + + + +Using &kget; + + +&kget; Tutorial + +Here is a brief tutorial that uses some of the features of &kget;. Below +are three software packages that are to be downloaded. Suppose you want to +download the middle one first and then the top and bottom ones second. + + + + + + +Place &kget; in offline mode, by choosing the menu item +OptionsOffline +Mode. + + + + + +Click on the top entry with the &LMB;. Hold down the &Ctrl; key and click on +the bottom entry. &kget; should look similar to this: + + +Screenshot of Kget + + + + + + Items to be downloaded from Konqueror + + + + + + + + + +Click on the delay button to prevent these items from being +downloaded. + + + +Now click on the middle entry to highlight it. The top and bottom items will no +longer be highlighted. + +Screenshot of Kget + + + + + + Items to be downloaded from Konqueror + + + + + + +Put &kget; back online by unchecking +OptionsOffline +Mode and &kget; will download the middle item. + + + +Click on the top item, hold down the &Shift; key and click on the bottom item. &kget; +should look like this: + +Screenshot of Kget + + + + + + Items to be downloaded from Konqueror + + + + + + +Holding down the &Ctrl; key allows you to select individual items; +holding down the &Shift; key allows you to select consecutive items, +while clicking with the &LMB;. + + + + + +Now click on the Queue icon or choose +TransferQueue to +download the two highlighted items. + + + + + + + +Special Configurations + +Configuration of Auto-Disconnect + + +This feature is used for automatically disconnecting your modem once a +download has been completed. To configure &kget; for auto-disconnect do: + + + + +Go to OptionsAuto-Disconnect Mode + + to disconnect the modem. Usually this +would be &kppp;. Expert Mode must be on to use this feature. + + + +For &SuSE; users the command cinternet + +ppp0 must be +substituted for kppp +in the Settings +Configure &kget;... + Automations menu. + + + + For Fedora Core users the command should be +/usr/sbin/usernetctl +ppp0 + + + + + + +Downloading Into Designated Folders + + +To download JPEG files into a designated folder do: + + + + Go to Settings +Configure &kget;... +Folders menu. + + + +Enter the files you wish to download using the extensions +.jpg and .jpeg as shown below: + + +Screenshot of Kget + + + + + + Downloading into designated files + + + + + + +Click on Apply and +OK. When you download any +.jpeg files they will be stored in +/home/kdecvs/pics/JPEG. + + + + + + + + +Command Reference + + +The main &kget; window + + +The <guimenu>File</guimenu> Menu + + + + + +&Ctrl;O + +File +Open + +Opens the transfer window where you can paste &URL;'s. + + + + + + +&Ctrl;V + +File +Paste + +Pastes the contents of the clipboard into the +transfer window. + + + + +File +Export Transfer List... + +Opens a Save As window that allows you to save +highlighted &URL;'s to a .kget file. To use this feature: + + + +Place &kget; in offline mode by clicking on the offline mode button in +the toolbar or choosing +OptionsOffline +Mode. + + +Drag the &URL;'s you wish to download on to &kget;. + + + +Next click on the top entry so that it is highlighted. + + + +Hold down the +&Shift; +key and click on the bottom &URL; to highlight the entries like so: + +Picture of kget saving to export file + + + + + + + + + +Now click on File +Export Transfer List... and enter the name of the +.kget file for your downloads. + + + + +This feature is used to save items that will be downloaded on a +regular basis, such as the &kde; snapshots above. + + + + + + + +File +Import Transfer List... + +Loads .kget files that were created with +File +Export Transfer List... + + + + + +File +Import Text File... + + +Imports &URL;'s that are in text files. This is a powerful feature that allows +you to parse &URL;'s from text files and emails you may have received. It can +discriminate between ordinary text and &URL;'s, provided the &URL; begins at the +left margin of the document. &kget; will find these &URL;'s and load them into +its main window for you. +This feature only ignores regular text when you have clicked on +the Expert Mode icon. If the Expert Mode is not used then the text file must +contain only &URL;'s. + + + + + + + +&Ctrl;Q + +File +Quit + +Quits &kget; + + + + + + +The <guimenu>View</guimenu> Menu + + + + +View +Show Log Window + + +Opens a log window that shows the events that have occurred. This is useful +for seeing what happened during a lengthy download. Here you can see if any +packages were skipped or if a connection timed out. + + + + + + + + +The <guimenu>Transfer</guimenu> Menu + +In order for the entries in this menu to become active (not grayed out), you +must highlight a download by clicking on the entry with the &LMB;. + + + + + +Transfer +Copy &URL; to Clipboard + + +This pastes a highlighted line into the &kde; clipboard (Klipper). + + + + + +Transfer +Open Individual Window + + Displays the selected download in its own window. + + + + + +Transfer +Move To Beginning + +Moves a highlighted entry to the top of the download +list. +The topmost &URL; in &kget; is downloaded first. + + + + + + +Transfer +Move To End + +Moves a highlighted entry to the bottom of the download +list. + + + + +Transfer +Resume + +Resumes a download that has been paused. + + + + + +Transfer +Pause + +Pauses a download that is running. + + + + + +Transfer +Delete + +Deletes a highlighted item from the &kget; main +window. + + + + +Transfer +Restart + +Combines the functions of Resume and Pause in one +button. + + + + +Transfer +Queue + +Causes the highlighted entries in &kget; to begin +downloading. + + + + +Transfer +Timer + +Delays the download of the highlighted items by one +minute. This is useful if you need to pause the download to check email or go +to a website. + + + + +Transfer +Delay + +Delays the download of the highlighted items +indefinitely. +Queue, +Timer and Delay are +mutually exclusive; only one of them may be selected at a time. + + + + + + + + +The Options Menu + + + + + +Options +Use Animations + + +Toggle use of animations to display &kget;'s +state. + + + + + +Options +Use Sound + + +Toggle the use of sound to indicate events, ⪚ a +file being added to the download list, or a download completing. + + + + + + +Options +Expert Mode + +Turns off prompting. + + + + +Options +Use-Last-Folder Mode + +&kget; will ignore the current folder settings and +place all new transfers in the folder where the last transfer was placed. + + + + + +Options +Offline Mode + +Toggles &kget; from being online (ready to download) +to offline. The offline mode is used when you want to copy &URL;'s into &kget; +without them being downloaded immediately. + + + + +Options +Auto-Disconnect Mode + +Causes &kget; to disconnect the modem. Usually this +would be &kppp;. Expert Mode must be on to use this feature. + +You can find more information about the Auto-Disconnect Mode in . + + + + + +Options +Auto Shutdown Mode + +Closes &kget; after all the downloads are +completed. The Expert Mode must be turned on. + + + +OptionsAuto-Paste +Mode +Enable grabbing of files to download from the +clipboard. + + + + + + + +The <guimenu>Settings</guimenu> Menu + + + + +Settings +Hide Statusbar + +Hide the statusbar. The statusbar normally +displays statistics about the currently downloading files. + + + + + +Settings +Drop Target + +The drop target is a desktop icon that allows for +hiding and restoring of &kget;. This is used on a cluttered desktop when you +want to periodically check the status of a download. + + + + +Settings +Configure Shortcuts... + + +Display the the familiar &kde; Keyboard Shortcut Configuration +Dialog. + + + + + +Settings +Configure Toolbars + + + +Display the the familiar &kde; Toolbar Configuration Dialog. + + + + + + +Settings +Configure &kappname; + + + +Launch the main configuration dialog. + + + + + + + +The <guimenu>Help</guimenu> Menu + +&help.menu.documentation; + + + + + + + + +Credits and License + + +&kget; + + +Program copyright 1998 Matej Moss + + +Contributors: + +Patrick Charbonnier pch@freeshell.org + +Carsten Pfeiffer pfeiffer@kde.org + + + + + +Documentation Copyright © 2003 Jonathan Drews j.e.drews@att.net + + + + +&underFDL; + + +&underGPL; + + + + +Installation + + +How to obtain &kget; + +&install.intro.documentation; + + + + + + +Compilation and Installation + +&install.compile.documentation; + + + + + +&documentation.index; + + + diff --git a/doc/kget/kget1.png b/doc/kget/kget1.png new file mode 100644 index 00000000..21407a43 Binary files /dev/null and b/doc/kget/kget1.png differ diff --git a/doc/kget/kget2.png b/doc/kget/kget2.png new file mode 100644 index 00000000..d9b19a2c Binary files /dev/null and b/doc/kget/kget2.png differ diff --git a/doc/kget/kget3.png b/doc/kget/kget3.png new file mode 100644 index 00000000..5980418a Binary files /dev/null and b/doc/kget/kget3.png differ diff --git a/doc/kget/kget4.png b/doc/kget/kget4.png new file mode 100644 index 00000000..067dccf8 Binary files /dev/null and b/doc/kget/kget4.png differ diff --git a/doc/kget/kget5.png b/doc/kget/kget5.png new file mode 100644 index 00000000..6d576c69 Binary files /dev/null and b/doc/kget/kget5.png differ diff --git a/doc/knewsticker/Makefile.am b/doc/knewsticker/Makefile.am new file mode 100644 index 00000000..085981d9 --- /dev/null +++ b/doc/knewsticker/Makefile.am @@ -0,0 +1,4 @@ + +KDE_LANG = en +KDE_DOCS = AUTO + diff --git a/doc/knewsticker/TODO b/doc/knewsticker/TODO new file mode 100644 index 00000000..72208730 --- /dev/null +++ b/doc/knewsticker/TODO @@ -0,0 +1,4 @@ +TODO +---- +* Make the developer's chapter use the stuff if somebody finds out + how it works :-] diff --git a/doc/knewsticker/about-icon.png b/doc/knewsticker/about-icon.png new file mode 100644 index 00000000..d2866407 Binary files /dev/null and b/doc/knewsticker/about-icon.png differ diff --git a/doc/knewsticker/checknews-icon.png b/doc/knewsticker/checknews-icon.png new file mode 100644 index 00000000..ce2468a8 Binary files /dev/null and b/doc/knewsticker/checknews-icon.png differ diff --git a/doc/knewsticker/contextmenu.png b/doc/knewsticker/contextmenu.png new file mode 100644 index 00000000..c3b313a0 Binary files /dev/null and b/doc/knewsticker/contextmenu.png differ diff --git a/doc/knewsticker/help-icon.png b/doc/knewsticker/help-icon.png new file mode 100644 index 00000000..d21faa26 Binary files /dev/null and b/doc/knewsticker/help-icon.png differ diff --git a/doc/knewsticker/index.docbook b/doc/knewsticker/index.docbook new file mode 100644 index 00000000..1532f8b1 --- /dev/null +++ b/doc/knewsticker/index.docbook @@ -0,0 +1,1399 @@ + + + + + +RSS"> +]> + + + + +The &knewsticker; Handbook + + + &Frerich.Raabe; &Frerich.Raabe.mail; + &Jonathan.Singer; &Jonathan.Singer.mail; + + + + + 2001, 2002, 2003 + &Frerich.Raabe; + + +&FDLNotice; + +2003-10-14 +2.00.00 + + + &knewsticker; is a news ticker applet for the &kde; panel (also + known as &kicker;). + + + + KDE + KNewsTicker + kdenetwork + news ticker + applet + + + + +Introduction + +&knewsticker; is an applet for the &kde; panel (also known as &kicker;) +which provides an easy and convenient way to access the news as reported by +many news sites (such as Slashdot, +&Linux; Weekly News or +Freshmeat). + +To achieve this, &knewsticker; requires the news sites to provide a +special &RSS; file, which contains the headlines as well as +pointers to the corresponding full articles. Such files are very common these +days, and &knewsticker; already comes with a selection of good news sources +which provide such files. + + +Brief Info On &RSS; Files + +&RSS; files are becoming more and more popular these +days, and this applet is not the first application which takes advantage of +them. But what are &RSS; files? This section tries to give +a brief answer to this question, as well as pointers to other sources for +further reference. + +The short answer: &RSS; is an +&XML;-based format for syndicating web content. + +&RSS; is often used as an acronym for Rich Site +Summary – that's not a common definition but it gives an idea on what +the creators of &RSS; had in mind. There is no consensus on +what &RSS; stands for, so it's actually not an acronym, it's a +name. + +&RSS; originated in 1999 and was invented by +NetScape as a syndication format +for their my.netscape.com website; +this very first &RSS; release was version 0.9. A few months +after that, &Netscape; introduced &RSS; version 0.91, which +incorporated many features of the <scriptingNews> +format. + +The basic concept of all &RSS; files is to provide a +clean, simple and portable way to distribute web content, in particular news: +the news sites provide an &RSS; file which basically contains +a set of records, and each record consists of a headline and a +&URL; which points to the complete article. The +&RSS; file also contains other general information about the +particular news site, such as its name and the homepage, which is evaluated +by &knewsticker;. + +Nowadays there are a few additional, more sophisticated versions of the +&RSS; format +(0.91, +0.92, +0.93, +1.0 and the current +format version +2.0) but the first two +versions still make up about 85% of the files provided on the web. Nevertheless, +all versions up to 2.0 can be processed with &knewsticker;! + +Of course, this is only a short and highly incomplete attempt at +explaining the basic ideas behind &RSS; files. If you're +interested in this topic, you might want to visit any of the following links +which point to further and more complete sources on this: + + + + http://www.webreference.com/authoring/languages/xml/rss/intro/ + A very pragmatic introduction to the &RSS; + format, with concrete examples and guidelines. Together with the + authoritative specifications, this makes a good guide for people + who are thinking + about providing an &RSS; newsfeed on their home + page. + + + http://www.oreillynet.com/rss/ + The &RSS; page from O'Reilly features a + lot of general articles about employing and using &RSS; files, + up to date news about the &RSS; development community as well + as vital information for web developers who are considering taking advantage of + &RSS;. + + + http://blogspace.com/rss/ + This page is another excellent source of news about the + &RSS; development, which is especially interesting to + developers working with &RSS;-based technology. + + + http://www.w3.org/RDF/ + The authoritative source about + RDF, an &XML;-based language from which + modern &RSS; versions are derived, published by the + World Wide Web Consortium. It features + a comprehensive list of links to other sites on the topic as well as a timeline + of the RDF development, an overview over the architecture, an + archive with articles about RDF as well as a carefully + assembled list of tools for developers who intend to work with + RDF. + + + +Developers will also want to check the authoritative specifications for +the various &RSS; versions: + + +&RSS; Specifications + + Version 0.90: http://www.purplepages.ie/rss/netscape/rss0.90.html + + + Version 0.91: http://backend.userland.com/rss091 + + + Version 0.92: http://backend.userland.com/rss092 + + + Version 0.93: http://backend.userland.com/rss093 + + + Version 1.0: http://web.resource.org/rss/1.0/ + + + Version 2.0: http://backend.userland.com/rss + + + +If you find any other sites or documents on this topic, and think they are +worth being mentioned here, don't hesitate to send them to &Frerich.Raabe; +&Frerich.Raabe.mail; so that they can be included in this document and +help everybody. + +Thank you very much! + + + + +Starting &knewsticker; + + + Here is a screenshot of &knewsticker; in &kde;'s + panel. + + + + Here is a screenshot of &knewsticker; in &kde;'s + panel. + + + + + +&knewsticker; is started like every other &kicker; applet. You just have to add +it to the panel (or any child panel of the main one). To do so, just right-click +on the &kde; panel and choose Add +Applet&knewsticker; +. + + + Here is a screenshot of &knewsticker; in its own child + panel. + + + + Here is a screenshot of &knewsticker; in its own child + panel. + + + + +Another good way to use &knewsticker; is to put it into its own child +panel. Just add a new child panel by choosing Add +ExtensionChild Panel + from the panel menu;. Now you can simply right-click on the child +panel and select &knewsticker; as described above. + + + Here is a screenshot of &knewsticker; in its own + window. + + + + Here is a screenshot of &knewsticker; in its own + window. + + + + +A third, popular, way to run &knewsticker; is by selecting +Internet&knewsticker; (News +Ticker) from the K menu. This +will start &knewsticker; and make it run in its own window which you can then +resize and move around as you wish. + + + +Configuring &knewsticker; + +You can access &knewsticker;'s configuration dialog by right-clicking +onto the scroll text, or by clicking on the button with the arrow on it and +choosing the entry labelled + + Preferences in the menu. + + +General Options + + + This is what the General tab of the + preferences dialog looks like. + + + + This is what the General tab of the + preferences dialog looks like. + + + + +Here you can define how fast the text should be scrolled around, what it +should look like as well as other options for the applet. Here is a brief info +on what each of the switches and buttons on this tab does: + + + + Mousewheel sensitivity: + + This slider allows you to define how fast/slow the text should be + scrolled when using the mousewheel. + + + + News query interval + + Here you can define in what intervals &knewsticker; queries the + configured news sources for new headlines. This depends generally on + how fast you'd like to hear about news and how much load you want to + put on the network: + + + A lower value (lower than 15 minutes) enables you to be + notified about news very quickly if you want or need to. Please + note, that it increases the network traffic significantly, + though. Therefore, such low values shouldn't be used if you + query popular news sites (such as + Slashdot or + Freshmeat) as they + have generally already enough work with processing the incoming + queries. + + + A higher value (higher than 45 minutes) won't make you + hear about news that quick. For non-time-critical applications, + it should be suitable, though. The positive aspect of longer + intervals is that only very little load is put on the network; + this saves resources and nerves, for you and the system + administrators of the news sites you query. + + + The default value (30 minutes) should be appropriate and + reasonable in most cases. + + + + Use custom names for news sites + + Check this box to make the news ticker use the names you + specified in the list of news sources (available on the tab + labeled News sources) instead of the ones + the news sites themselves report. This can be handy for news sites + which report a very long or useless name. + + + + + + +Configuration of the news sources + + + This is what the News Sources tab of the + preferences dialog looks like. + + + + This is what the News Sources tab of + the preferences dialog looks like. + + + + +On this tab you can manage and maintain the list of news sites +&knewsticker; queries for news. Click on any entry with the right mouse button +to open a context menu which lets you remove the current entry, or add a new +entry. At the bottom of the page you can also find three buttons which have +the same effect. + + +Adding a news site +There are four ways to add a new news site to the +list: + + + You can click on the button at the bottom labeled + Add.... + + + You can click with the right mouse button on the table and choose + Add news source. + + + You can drag any &RSS; file from another + application (such as &konqueror;) onto the table. This adds a new entry + to the list, sets the name to Unknown and sets the + maximum number of articles to 10. + + + And finally, you can just click on any &RSS; + file in the &konqueror; filemanager to have it added to the list + immediately. + + +Either way will cause the News site +dialog to show up, presenting you with a form to enter the properties of +the news site to add. + + + +Modifying an existing news site +There are two ways to adjust the properties of an existing news +site: + + + You can click on the button at the bottom labeled + Modify.... + + + You can right-click with the right mouse button on the news site + you'd like to edit and choose Modify '...' + from the menu. + + +No matter which way you chose, it will cause the +News site dialog to pop up, +showing the properties of the selected news site. + + + +Removing a news site +Of course, you want to remove a news source from +the list sometimes. To do this, you can either + + + click on the button at the bottom labeled + Remove, or + + + right-click with the right mouse button on the news site + you'd like to edit and choose Remove '...' + from the menu. + + +In both cases, a confirmation box will pop up and make sure you didn't +select the wrong entry. +You can also remove multiple news sites at once by holding &Ctrl; +while clicking on the entries you wouldd like to remove, or by clicking the +left mousebutton and dragging the mouse over all +the entries you'd like to select. + + + +The news site dialog + + + This is what the dialog for adding and editing news sites looks + like. + + + + This is what the dialog for adding and editing news sites + looks like. + + + + +When adding or modifying a news site, the news site dialog (shown above) +pops up and provides input facilities to edit the various properties of a news +site: + + + + Name: + + This is the name of the news source. + This text will only be used if Use custom names + for news sites on the tab labeled + General is + activated. + + + + Source file: + + Here you can set the &URL; which references + the &RSS; file of this news site; this can either + be a local file, or a file saved on a remote server. You can also + click on the button at the right to open a convenient file-selection + dialog and browse to the file you would like to use, instead of typing + the &URL; by hand. + + + + The file is a program + + If this file is checked, &knewsticker; will not assume that the + &URL; (which was specified in the Source + file field) references an &RSS; file, but + rather that the &URL; refers a program (usually a + script). When querying this news site, &knewsticker; will execute the + program and treat whatever the program prints to stdout as + &RSS; markup. This is very convenient for conversion + scripts which download an &HTML; file and process it, + producing &RSS; markup which is suitable for use with + &knewsticker;. + You can find some scripts which ⪚ download stock data in + the kdeaddons module, in the + knewsticker-scripts + directory. + + + + Category: + + Here you can specify into which category the news site belongs. + Arranging the news sites into categories makes it much easier to + maintain large lists of news sites. + + + + Max. articles: + + This option lets you define how many articles &knewsticker; will + cache for this news site; the value will never be exceeded. + This is particularly handy for news sites which provide only + three news items at once, but you'd like to see the last ten items (for + instance); &knewsticker; will always download the three items and merge + them into its list, caching the last seven items. + + + + Icon: + + Here you can specify a &URL; to an image file + (preferably 16x16 pixels in size) which should be used for this news + site. Icons make it much easier to distinguish multiple news sites, and + see which news site a headline appeared on as it scrolls by in + &knewsticker;. + + + + +At the button you will find the usual buttons. One of them deserves an +extra note: clicking on the Suggest button will make +&knewsticker; try to guess suitable values for most of the fields if you +specify a &URL; to a valid source file. + +This means that you can usually just paste a &URL; to +an &RSS; file in the input field labeled Source +file, press the Suggest button and then modify +the suggested values as needed. + + + + +Filters + + + This is what the Filters tab of the + preferences dialog looks like. + + + + This is what the Filters tab of + the preferences dialog looks like. + + + + +This tab allows you to define various filters which should be applied +before showing the headlines in the scroll text. + +These filters only affect the headlines which are shown in the +scrolltext; the menu will always show all of the headlines. +The major part of the tab is occupied by a table which lists the +currently configured filters. Each filter has a small checkable box - checking +that box enables the filter, unchecking it temporarily disables it without +removing it from the list. + +By default there are no filters, so chances are that the table is +completely empty for you. Of course, this dialog provides you with ways +to add new filters, and manage them in general: + + + + To add a filter, simply enter the filter + properties (see Filter + Components for a detailed description of the various filter + properties) using the input fields in the box labeled Filter + properties and then press the button labeled + Add. + + + To modify an existing filter, select the + filter you would like to edit in the table by left-clicking on it and + then change its properties in the box at the bottom. + + + To remove a filter, select it in the table + and then press the button labeled Remove. + + + + +Filter Components + +Each filter consists of four components: + + +Filter Components + + Action – this can be either Show or + Hide and defines what should happen to a headline + in case this filter matches. + + + News sources – here you can define whether the filter affects only + single news sources, or whether this filter should be applied to the + headlines of all news sources. + + + Condition – this is a verb which defines, together with the + Expression, whether a filter matches. A condition can be ⪚ + contains, doesn't equal or + matches. See below for a more detailed + description. + + + Expression - this is a user-defined string which forms the body + of the filter, together with the Condition. See below for a more detailed + description of this component. + + + +All these components can be configured using the facilities in the frame +labeled Filter properties. The possible states of the +Condition component deserve a special explanation: + + + + contains, doesn't contain: + this filter matches if the headline contains / doesn't contain the + specified expression. + The expression isn't treated case-sensitively, so the expressions + KDE, kDE or kde will all match + headlines which contain KDE. + + + equals, doesn't equal: + this filter matches if the headline equals / doesn't equal the + specified expression. + The expression is treated case-sensitively, so of the expressions + &Linux;, linux or LINUX, only the + first will match &Linux;. + + + matches: using this condition will make + &knewsticker; treat the given expression as a regular + expression. For further information on regular expressions you + might want to read + this + article which was published at + www.evolt.org. + + + + + + +Scroller Preferences + + + This is what the Scroller Preferences tab + of the preferences dialog looks like. + + + + This is what the Scroller Preferences + tab of the preferences dialog looks like. + + + + +This tab lets you define various options which affect &knewsticker;'s +scroll text: + + + + Scrolling speed + + This slider lets you define how fast the scrolltext should be + scrolling. If you have rather little space on your taskbar (and + therefore a rather small news ticker), you should probably set this + to a lower value so that you have a chance to read the headlines. + For wider news tickers (and better eyes), a faster text is probably + appropriate so that you have to wait for the next headline only as + little as possible. + + + + Direction of scrolling + + These options allow you to define in what direction the text + should be scrolled, ⪚ to the left or to the right, upwards or + downwards. You can also rotate the text by 90 or 270 degrees here, + which is not exactly readable but it makes sense for vertically + aligned panels. + + + + Scrolltext font + + Click on the button at the right labeled Choose + Font... to choose the font which will be used for the + scrolling text. + Certain fonts are harder to read than others, especially + when they are used for a scrolltext, so you should probably choose a font + which can even easily be read if it's moving. + + + + Foreground color + + Click this button open a convenient color-selection dialog which + lets you choose the color which will be used for the foreground of the + scrolling text (&ie; the color of the text itself). + + + + Background color + + Click this button to open a convenient color-selection dialog + which lets you choose the color which will be used for the background + of the scrolling text. + + + + Highlighted color + + Click this button to open a convenient color-selection dialog + which lets you choose the color which will be used for the color of + the headlines when they are highlighted (when you move the mouse over + them). + + + + Scroll the most recent headlines only + + Check this button to make the scrolltext show just the most + recent headline for each news site, instead of showing every + headline available from every news site. + + + + Show icons + + Checking this box will make &knewsticker; show an icon (if + available) in front of each headline which is scrolled along; this + makes determining which news site provided each headline much + easier. + + + + Temporarily slowed scrolling + + Check this box to make &knewsticker; slow the scrolling down + when you move the mouse cursor over the scrolling text. This makes + clicking on items and dragging away the icons (if enabled) a lot + easier. + + + + Underline highlighted headline + + Check this box to have the currently highlighted headline + (&ie; the headline which is currently under the mouse cursor) + underlined. + + + + + + + +Using &knewsticker; + +Using &knewsticker; is fairly straightforward and should give you no +big problems, assuming that you have already +configured it. No matter +whether you are running &knewsticker; in the main panel, in its own child +panel or in its own window, it appears as an area with a scrolling text and +a button with a small arrow next to it. + + +The Main Interface + +The area (it is white by default, but you can change the background +color easily using the preferences dialog) with the scrolling text in it is +called the news scroller. It keeps scrolling the downloaded +headlines (or just the most recent headlines) continuously and provides easy +access to the articles. If you see an interesting article, just click on it +to open the &konqueror; web browser, showing the full article which belongs to +the headline you clicked on. If you feel that a possibly interesting headline +just scrolled out of view, you have some ways to influence the +scrolling: + + + + You can click on the news scroller with the + left mouse button and move the mouse around + (while holding the left mouse button pressed + down). The news scroller will continue scrolling as usual if you release + the left mouse button again. + + + If you own a so-called wheel mouse, you can use + the wheel on your mouse to scroll the headlines back and forth. + + + +There's also a very powerful context +menu, which you can access either by clicking on the news scroller with +the right mouse button, or by clicking on the +arrow button with the left button. This context menu +is the most interesting part of the applet, as it contains about all the +functionality. + + + +The Context Menu + + + This is what the context menu of &knewsticker; looks like, when + using just the default news sites. + + + + This is what the context menu of &knewsticker; looks like, + when using just the default news sites. + + + + +The context menu is basically split into two functional parts: + +The upper part shows a list of entries, each entry having a small icon like +this + + next to it. This list represents +the list of currently configured news sites. You can click on any of the news +sources to open another menu which contains a listing of headlines which are +available for that particular news site as well as an entry labeled + Check news to +refresh the headline list for this news site. Each of the headlines has an + + + icon next to it indicating that that you +have read that article already or the + + if it is still +unread. + +The lower part currently shows five entries (explained from top to +bottom): + + + + Check news + + This entry has a little + + next to it. If you want to force &knewsticker; + to check the configured news sites for new articles, you can click + here. + This does not reset the internal timer which queries the + news sites for new headlines automatically in certain + intervals. + + + + Offline mode + + This entry in the context menu has no icon associated with it. + Click this button to enable a special offline mode + which pauses the internal timer for querying the news sites and + prevents any automatic download of new headlines. The offline mode + comes in handy if you have to leave your computer for a while + during which the system isn't connected to the Internet, as it + saves you from all the error messages which pop up if any of the + news sites couldn't be connected. + You can still force a reload for single news sites as + well as for all the news sites by selecting the respective + + Check news entry. + + + + Help + + This entry is marked with a small + + . Clicking on this entry opens + the &knewsticker; documentation (which you're reading in this + moment) which details all the features and abilities of + &knewsticker;. + + + + About + + This entry is marked with a small + + . Clicking on this entry opens + a small dialog showing who's to blame for &knewsticker; and credits + people who contributed significant enhancements for + it. + + + + Preferences + + This entry is easily recognizable due to the + + icon which is next to it. Select + this entry to open the preferences + dialog which lets you customize all of the properties of + &knewsticker; + + + + + + + + +Frequently Asked Questions + +&reporting.bugs; + + + + + Where do I find the &RSS; file for the news + site XYZ? + + + It's possible that the news site you're referring to + doesn't provide any &RSS; file at all! Here's a short list of + websites which provide thousands of &RSS; feeds, sorted by + language and/or topic - for free: + + + + WebReference.com + + + NewsIsFree + + + MoreOver + + + + If you have found any interesting news sites which provide + such a backend, don't hesitate to send them to &Frerich.Raabe; + &Frerich.Raabe.mail; so that they can be included in + future releases. Thank you! + + + + + How can I make &knewsticker; open articles in another browser + (⪚ Mozilla)? + + + &knewsticker; will use whatever browser you have associated with + the text/html &MIME; type; the default browser used for viewing + &HTML; pages is &konqueror;. + You can find the dialog for changing this association by opening + the &kde; control center and browsing to KDE + Components File + Associations. + + + + + + +Credits And License + +&knewsticker; + +Program copyright 2000, 2001, 2002, 2003 &Frerich.Raabe; +&Frerich.Raabe.mail; + +Contributors: + + + + Malte Starostik malte.starostik@t-online.de + + + &Wilco.Greven; &Wilco.Greven.mail; + + + Adriaan de Groot adrig@sci.kun.nl + + + +Documentation copyright 2001, 2002, 2003 &Frerich.Raabe; +&Frerich.Raabe.mail; + + +&underFDL; +&underBSDLicense; + + + +Glossary + +This chapter is intended to explain the various acronyms which have been +used throughout the &knewsticker; documentation. If you feel any acronyms or +terms are missing here, please don't hesitate to send an email to &Frerich.Raabe; +&Frerich.Raabe.mail; so that they can be added. Thank you! + + + RDF + + Resource Description Framework. A language derived from + &XML; which describes metadata. Commonly used as a + backend format for articles and other publications. For more detailed + information on RDF files, you might want to go + directly to the official page on + RDF files at the + World Wide Web + Consortium. + + + + &RSS; + + The RDF Site Summary is actually an + extension to the RDF language. Quoting the official + &RSS; v1.0 + specification: + RDF Site Summary + (&RSS;) is a lightweight multipurpose extensible + metadata description and syndication format. &RSS; is + an &XML; application, conforms to the + W3C's RDF specification and is + extensible via &XML;-namespace and/or + RDF based modularization. + + + + &XML; + + The Extensible Markup Language is the universal format + for structured documents and data on the Web. It's a derivative of + SGML which fits the needs of the world wide web. You + might want to check the the + Extensible Markup Language + page at the World Wide Web + Consortium for further information. + + + + W3C + + An abbreviation for World Wide Web Consortium. + Quoting the official homepage of + the W3C, the World Wide Web Consortium + (W3C) develops interoperable technologies + (specifications, guidelines, software, and tools) to lead the Web to + its full potential as a forum for information, commerce, communication, + and collective understanding + + + + &URL; + + &URL; stands for Uniform Resource Locator, + a specially formatted string which can reference resources like images, + documents and other things on the Internet. Please refer to the + corresponding webpage + for more detailed information on this topic. + + + + &DCOP; + + The Desktop COmmunication Protocol is a way for applications + to communicate to each other. For instance, &knewsticker;'s + configuration dialog uses + &DCOP; to tell the applet itself about the current + configuration. + &knewsticker; provides an extensive + &DCOP; interface, + which makes it possible to control many of &knewsticker;'s functions + from the commandline. + For more detailled information on &DCOP; + you might want to visit + http://developer.kde.org/documentation/library/2.0-api/dcop/HOWTO.html + for a complete explanation. + + + + + +Information For Developers And Advanced Users + +&knewsticker; features a currently rather extensive, and steadily growing +&DCOP; interface. This is not only used to communicate with +other applications, it makes it possible to control &knewsticker; with a +shell script as well. The more the interface is extended, the more useful it will +become and the more flexible &knewsticker; will be controllable from a +script. + +To use these &DCOP; functions you can either use the +dcop commandline program or use the more convenient +KDCOP application. Both provide the same +functionality so it's actually just a matter of taste which program you prefer. +:-) + +This chapter assumes that you're using the commandline program +dcop. To access &knewsticker;'s +&DCOP; functions make sure that &knewsticker; is started and then just enter something like +this at the console: + + +% dcop + + +If an error appears that tells you that dcop +couldn't be found or executed, please check whether the file +dcop exists in +$KDEDIR/bin and make sure its +permissions are set appropriately. + +In that command line, just replace [function] with the +appropriate function name, &ie; type + + +% dcop + + +to make &knewsticker; check for new news items and download them if +necessary. + + +&DCOP; Reference + +In this section, all methods which are accessible via &knewsticker;'s +&DCOP; interface are listed. + + + + updateNews + + This function forces &knewsticker; to update the internal list + of articles (&ie; it queries the list of news sources which has been + configured for new news) and + downloads them when necessary. + This also works if &knewsticker; is currently in offline + mode. + Example: + + % dcop + + + + + reparseConfig + + The reparseConfig command makes &knewsticker; + reload its configuration from the configuration file. This function is + used by the configuration dialog + to talk to &knewsticker; but you can use it if you modified the + configuration file by hand. + The configuration file is saved in + ~/.kde/share/config/knewsticker_appletrc + Example: + + % dcop + + + + + setOfflineMode [bool] + + You can call this function to define whether &knewsticker; is + currently in the offline mode (&ie; whether &knewsticker; should query + the configured news sites for + new news). + Example: + + % dcop + + to enable the offline mode, or type + + % dcop + + to disable offline mode. + + + + interval + + Returns the currently configured news query interval in + minutes. + Example: + + % dcop + 30 + % + + + + + scrollingSpeed + + Returns the currently configured scrolling speed. The returned + scrolling speed is specified in pixels per second. + Example: + + % dcop + 20 + % + + + + + mouseWheelSpeed + + Returns the number of pixels the scrolltext gets shifted + per mousewheel step. + Example: + + % dcop + 15 + % + + + + + scrollingDirection + + Returns an integer which corresponds to the direction the + scrolltext is scrolling in: + + 1 = To the left + 2 = To the right + 3 = Upwards + 4 = Downwards + 5 = Upwards, rotated + 6 = Downwards, rotated + + Example: + + % dcop + 1 + % + + + + + customNames + + Returns either 'true' or 'false', depending on whether &knewsticker; + uses custom names for the news sites. + Example: + + % dcop + false + % + + + + + endlessScrolling + + Returns either 'true' or 'false', depending on whether &knewsticker; + has the endless scrolling option enabled. + Example: + + % dcop + true + % + + + + + scrollMostRecentOnly + + Returns either 'true' or 'false', depending on whether &knewsticker; + currently only scrolls the most recent headlines for each news + site. + Example: + + % dcop + false + % + + + + + offlineMode + + Returns either 'true' or 'false', depending on whether &knewsticker; + is currently in offline mode. + Example: + + % dcop + false + % + + + + + underlineHighlighted + + Returns either 'true' or 'false', depending on whether &knewsticker; + was told to underline the headline which is currently below the mouse + cursor. + Example: + + % dcop + true + % + + + + + showIcons + + Returns either 'true' or 'false', depending on whether &knewsticker; + currently shows the icon of the news site each particular headline + was published at in front of the headline. + Example: + + % dcop + true + % + + + + + slowedScrolling + + Returns either 'true' or 'false', depending on whether &knewsticker; + has the slowed scrolling feature activated. + Example: + + % dcop + false + % + + + + + foregroundColor + + Returns the currently configured foreground color as a string + in the format #rrggbb, where rr, + gg and bb are two-digit hexadecimal + values representing the intensity of the red, green and blue components + in a scale of 00-ff. + Example: + + % dcop + #804000 + % + + + + + backgroundColor + + Returns the currently configured background color as a string + in the format #rrggbb, where rr, + gg and bb are two-digit hexadecimal + values representing the intensity of the red, green and blue components + in a scale of 00-ff. + Example: + + % dcop + #0030ff + % + + + + + highlightedColor + + Returns the currently configured highlight color as a string + in the format #rrggbb, where rr, + gg and bb are two-digit hexadecimal + values representing the intensity of the red, green and blue components + in a scale of 00-ff. + Example: + + % dcop + #000080 + % + + + + + newsSources + + Returns the list of currently registered news sources. Note that + this returns all news sources, not just the selected ones. In the + output, each news source name will be printed on a line. + Example: + + % dcop + Freshmeat + GNOME News + dot.kde.org + Slashdot.org + % + + + + + + + +&documentation.index; + + diff --git a/doc/knewsticker/kcmnewsticker-filters.png b/doc/knewsticker/kcmnewsticker-filters.png new file mode 100644 index 00000000..6386a113 Binary files /dev/null and b/doc/knewsticker/kcmnewsticker-filters.png differ diff --git a/doc/knewsticker/kcmnewsticker-general.png b/doc/knewsticker/kcmnewsticker-general.png new file mode 100644 index 00000000..e0b3e58a Binary files /dev/null and b/doc/knewsticker/kcmnewsticker-general.png differ diff --git a/doc/knewsticker/kcmnewsticker-newssitedialog.png b/doc/knewsticker/kcmnewsticker-newssitedialog.png new file mode 100644 index 00000000..cc3e0fdf Binary files /dev/null and b/doc/knewsticker/kcmnewsticker-newssitedialog.png differ diff --git a/doc/knewsticker/kcmnewsticker-newssources.png b/doc/knewsticker/kcmnewsticker-newssources.png new file mode 100644 index 00000000..da54789c Binary files /dev/null and b/doc/knewsticker/kcmnewsticker-newssources.png differ diff --git a/doc/knewsticker/kcmnewsticker-scrollerprefs.png b/doc/knewsticker/kcmnewsticker-scrollerprefs.png new file mode 100644 index 00000000..7f2538b1 Binary files /dev/null and b/doc/knewsticker/kcmnewsticker-scrollerprefs.png differ diff --git a/doc/knewsticker/kcontrol-icon.png b/doc/knewsticker/kcontrol-icon.png new file mode 100644 index 00000000..7fc0da9b Binary files /dev/null and b/doc/knewsticker/kcontrol-icon.png differ diff --git a/doc/knewsticker/knewsticker-childpanel.png b/doc/knewsticker/knewsticker-childpanel.png new file mode 100644 index 00000000..a1eeb83d Binary files /dev/null and b/doc/knewsticker/knewsticker-childpanel.png differ diff --git a/doc/knewsticker/knewsticker-icon.png b/doc/knewsticker/knewsticker-icon.png new file mode 100644 index 00000000..714e9834 Binary files /dev/null and b/doc/knewsticker/knewsticker-icon.png differ diff --git a/doc/knewsticker/knewsticker-kicker.png b/doc/knewsticker/knewsticker-kicker.png new file mode 100644 index 00000000..75bbf87d Binary files /dev/null and b/doc/knewsticker/knewsticker-kicker.png differ diff --git a/doc/knewsticker/knewsticker-ownwindow.png b/doc/knewsticker/knewsticker-ownwindow.png new file mode 100644 index 00000000..f0da9e27 Binary files /dev/null and b/doc/knewsticker/knewsticker-ownwindow.png differ diff --git a/doc/knewsticker/newarticle-icon.png b/doc/knewsticker/newarticle-icon.png new file mode 100644 index 00000000..43b02977 Binary files /dev/null and b/doc/knewsticker/newarticle-icon.png differ diff --git a/doc/knewsticker/newssite-icon.png b/doc/knewsticker/newssite-icon.png new file mode 100644 index 00000000..a4be959f Binary files /dev/null and b/doc/knewsticker/newssite-icon.png differ diff --git a/doc/knewsticker/oldarticle-icon.png b/doc/knewsticker/oldarticle-icon.png new file mode 100644 index 00000000..a16644e7 Binary files /dev/null and b/doc/knewsticker/oldarticle-icon.png differ diff --git a/doc/knewsticker/preferences-icon.png b/doc/knewsticker/preferences-icon.png new file mode 100644 index 00000000..0c95c52c Binary files /dev/null and b/doc/knewsticker/preferences-icon.png differ diff --git a/doc/kopete/Makefile.am b/doc/kopete/Makefile.am new file mode 100644 index 00000000..41691557 --- /dev/null +++ b/doc/kopete/Makefile.am @@ -0,0 +1,3 @@ +KDE_LANG = en +KDE_DOCS = AUTO + diff --git a/doc/kopete/chatstyle.docbook b/doc/kopete/chatstyle.docbook new file mode 100644 index 00000000..991bee25 --- /dev/null +++ b/doc/kopete/chatstyle.docbook @@ -0,0 +1,363 @@ + + + + +&kopete; Chat Window Style Guide + +&kopete; Chat Window Style reference. + +Beginning with &kopete; 0.12, we are now using Adium format for our +Chat Window style. The theme format is based on HTML templates and CSS. They +are easier to make and develop, only a knowledge of HTML and CSS is needed. Also, styles can have variants (defined as CSS file) which add more customisation value :). + + + + Reference guide. + +Adium format consist of a directory structure, HTML templates, CSS files and keywords that are replaced each time the template is processed. The final conversation is a XHTML page where messages are added with DOM operations. The central element is a div element named Chat. Before and after this div element goes the Header and Footer template. Messages are childs of the Chat div element. + + + + Directory Structure +A style must respect this directory structure. Code in &kopete; are thinking around this directory structure. When archiving the style, archive the styleName directory. The directory structure is a structure of a Mac OS X bundle for those familiar with that operating system. Also you must respect the case displayed here, because a UNIX system is case-sensitive. + +styleName\ (can have .adiumMessageStyle as suffix, because in Mac OS X it is a bundle) + Contents\ + Info.plist + Resources\ + main.css + Header.html + Footer.html + Status.html + Incoming\ (and Outgoing\) + Content.html + NextContent.html (for consecutive messages) + Context.html (for message history) + NextContext.html + Action.html + Variants\ + *.css + + + + + About <div id="insert"></div> + This is a special div element used internally. It is a placeholder to indicate where to insert the next message. If it's a new message, it is removed and the new message take place. But if it's a consecutive message, the div element is replaced with the content of new message. This special div element is required in Content,Context,NextContent,NextContext templates. Though it not harm to put it also in Action and Status template. + + + + + HTML templates. + +Template description. + +Header.html (Required) + + +Use the Header template to display a nice header to the conversation. This template is insered before Chat div element. If you don't use it, just put an empty file. + + + + +Footer.html (Required) + + +This is mostly the same as Header but it is for the fotter of a conversation. This template is insered after Chat div element. If you don't use it, just put an empty file. + + + + +Status.html (Required) + + +This template is used to display a internal message. Internal messages such as status change, message from Kopete(ex: Incoming File Transfert). When the style do not supply a Action template, it is used to display Action message. + + + + +Incoming/Content.html + Outgoing/Content.html (Required) + + +The content template is the message core. Think it as a block that will hold messages.. Make sure it is ready to receive consecutive messages, don't design it to only display one message. Consecutive messages will be inserted at the div insert element. + + + + +Incoming/NextContent.html + Outgoing/NextContent.html (Required) + + +The NextContent template is a message fragment for consecutive messages. It will be inserted into the main message block. The HTML template should contain the bare minimum to display a message. + + + + + +Incoming/Action.html + Outgoing/Action.html (Optional) (&kopete; Extension) + + +This template is a &kopete; extension to the Adium format. It is available for Incoming and Outgoing direction. Action messages are special message to tell we are doing a action. Example: "/me is installing &kopete;" would be displayed as "DarkShock is installing &kopete;". + + + + +Incoming/Context.html + Incoming/NextContext.html + Outgoing/Context.html + Outgoing/NextContext.html (Optional) + + +These templates are not used in Kopete. In Adium, they are used to display history. It is mostly the same thing as Content and NextContent but with some differences to distinguist from normal messages. + + + + + + + + + + About CSS styles and Variants + HTML template are used to describe how the structure is made. But all the style is described in CSS files. main.css is the main style, where variants are just alterations of the main style. Examples of variants are differents colors, no display of user photo. Both main.css and variants and imported in final XHTML page. + + -<filename>main.css</filename> + This is the main CSS file which is common for all variants. This file should contain all the main description of the style. + + + -Variants + Variants are CSS files located in Variants/ directory. Each variant is a single CSS file that include the main.css and do alteration to the main style. + + + + + Debugging styles + Here is two tips for testing a style while creating it. + + -Save a sample conversation. + In Chat Window, you can save a conversation. This is a copy of the internal XHTML page displayed. Use it in Konqueror to test your CSS files. + + + -Disable style cache. + A little configuration switch exist to disable the style cache. When enabled, it reload the HTML templates each time the style is asked. Add the following lines to your kopeterc. Very useful when testing a style in &kopete; + +[KopeteStyleDebug] +disableStyleCache=true + + + + + + + + Keywords reference + Keywords are likes holes to fill with details. On each new message, they are replaced with the correct value corresponding to their context. To fully support all features of Kopete, we added some keywords extentions to the Adium. Also some keywords are only available in certain context. + +Keywords list for Header and Footer templates. +There keywords are processed at the beginning of the chat. +%chatName% + + +This is the name of the current chat session. For a typical session, it display the name of the contact and his status. For IRC, it display the topic of a channel. + + + + +%sourceName% + %destinationName% + + +These are the name of the contacts for a chatsession. %sourceName% is your name. %destinationName% is the name of the contact you are chatting with. Prefer %chatName% over those, because they could be confusing for groupchat and IRC. + + + + +%incomingIconPath% + %outgoingIconPath% + + +These are the image/photo/avatar of the contacts for a chatsession. Incoming represent the contact photo and Outgoing represent your own photo. If they are no photo available, it use buddy_icon.png image which is located in Incoming or Outgoing directory. + + + + +%timeOpened% + %timeOpened{X}% + + +It is the time when the chat session begin. %timeOpened% use the default time format for the current locale. If you want to use a specific time format, use %timeOpened{X}% where X is a string containing the time format. The time parameters are the same as the glibc function strftime. Do man strftime to get the list of available parameters. + + + + + + +Keywords list for Content, NextContent, Context, NextContext, Action template +There keywords are processed for each message. + +%userIconPath% + + +This is the image/photo/avatar of the contact associated with the message. If they are no photo available, it use buddy_icon.png image which is located in Incoming and Outgoing directory depending of the message direction. + + + + +%senderScreenName% + + +This is the contact ID of the contact associated with the message. Examples: me@hotmail.com, 45566576, JohnSmith. + + + + +%sender% + + +This is the name of the contact associated with the message. It use MetaContact display name as a source. + + + + +%service% + + +Display the name of the service associated with the message. Examples: Jabber, Yahoo, MSN. + + + + +%textbackgroundcolor{X}% + + +In &kopete;, this keyword is used to represent the highlight background color. Ignore parameter in the braces and only use it as %textbackgroundcolor{}. + + + + +%senderStatusIcon% (&kopete; extension) + + +Display the status icon of the contact associated with the message. It's a file path. + + + + +%senderColor% %senderColor{N}% (&kopete; extension) + + +Generate a color from the sender contact id. Can be used to display a different color for contact nickname. + + +%senderColor{N}% where N is a positive number. If N is greater than 100, it represent a lighter color than the contact's color. +If N equal 150 it is a color which is 50% brighter. If N is less than 100 then it is a darker color. Usefull for having a background coloured differently for each contact. + + +If you want to use theses colors in a variant, but not in the main style, you have to workaround. + +

...

+]]> +
+you can apply color ro the p.message element in your main.css file, and in your varient put something like + + p.message { color:inherit; border-color:inherit; } + + +
+
+
+ +
+ +Keyword list common for messages and Status.html + +%message% + + +The message itself. This is a HTML fragment. + + + + +%time% + %time{X}% + + +The time when the message was received. %time% use the default time format for the current locale. If you want to use a specific time format, use %time{X}% where X is a string containing the time format. The time parameters are the same as the glibc function strftime. Do man strftime to get the list of available parameters. + + + + +%messageDirection% (&kopete; Extension) + + +Represent the message direction, if the message must be displayed right-to-left or left-to-right. The values are either "rtl" or "ltr". Read Message Direction guideline to see how to use this keyword properly. + + + + + +
+
+ + + &kopete; Chat Window Style Guideline +The &kopete; Chat Window Style Guideline is a set of things that your Chat Window style must support to be compilant with Kopete. + + Support highlight + Your style must show hightlight. In Kopete and Adium, the %textbackgroundcolor{}% is replaced with the hightlight color. Add this style attribute: background-color: %textbackgroundcolor{}% to the HTML element that display the message. + + + Consecutive message templates are required. + This guideline is for people rewriting old XSL styles to the new format. All styles must supply a template for consecutive messages. It is now a default feature. + + + + Use <acronym>UTF-8</acronym> encoding. + The title said it all. You must save your files to UTF-8. + + + + Supply <filename>Contents/Info.plist</filename> for interopability with Adium + The Contents/Info.plist file is not used in Kopete yet. But if you want your style to be compatible with Adium, you must supply that file. Here a basic example file. Strings to replace are enclosed with "$". + + + + + + CFBundleDevelopmentRegion + English + CFBundleGetInfoString + $Your style full name$ + CFBundleIdentifier + $Your style ID in the form: com.adiumx.smooth.operator.style$ + CFBundleInfoDictionaryVersion + 1.0 + CFBundleName + $Your style name here$ + CFBundlePackageType + AdIM + MessageViewVersion + 3 + + +]]> + + + + + + Supply <filename>buddy_icon.png</filename> + You must place a file named buddy_icon.png in the Incoming and Outgoing. These images will be used when the contact have no photo. + + + + Support right-to-left languages with <filename>%messageDirection%</filename> keyword. + %messageDirection% keyword is present for languages in the world that write right-to-left. It define the message direction, if it's "rtl"(right-to-left) or "ltr"(left-to-right). Add this style attribute to the HTML element that display the message: direction: %messageDirection%. Style preview in appearance config include a right-to-left to check if your style display it correctly. It should begin the string from the right. + + +
diff --git a/doc/kopete/index.docbook b/doc/kopete/index.docbook new file mode 100644 index 00000000..c6fba958 --- /dev/null +++ b/doc/kopete/index.docbook @@ -0,0 +1,1025 @@ + +Kopete"> + WillStephenson"> + lists@stevello.free-online.co.uk"> + MattRogers"> + mattr@kde.org"> + MichaëlLarouche"> + michael.larouche@kdemail.net"> + + + IM"> + + + http://kopete.kde.org"> + + +]> + + + + +The &kopete; Handbook + + + +&Will.Stephenson; &Will.Stephenson.mail; +&Matt.Rogers; &Matt.Rogers.mail; +&Michael.Larouche; &Michael.Larouche.mail; + + + + +&FDLNotice; + +2006-12-15 +0.12 + + +2003, 2004, 2005, 2006 + + + + + + +&kopete; is &kde;'s multi-protocol instant messenger client. + + + + + +KDE +IM +Instant +Messaging +Jabber +IRC +MSN +ICQ +AIM +Yahoo +Gadu-Gadu +GroupWise +Novell +WinPopup +SMS + + + + + +Introduction + + +&kopete;, the &kde; instant messaging client + +Before starting... +If you're not familiar with Instant Messaging, please read the Getting Started section to learn about this wonderful world before continuing. + + +What is &kopete;? +&kopete; is the &kde; instant messaging (&im;) client. It allows you to communicate with your friends and colleagues using various instant messaging services. A single program is easy to learn and convenient if your friends or colleagues use more than one &im; service. +&kopete; is designed to integrate well with your &kde; desktop; to make it immediately familiar. The user interface is clean and simple, without any frills to distract the user. At the same time, &kopete; aims to make communication the focus of &im;, by removing the differences between different &im; systems. One feature &kopete; introduced to support this is the Metacontact, combining the various means there are to contact someone into a single person in your contact list. Other multiprotocol instant messengers list the same person's various &im; accounts separately, making it confusing for non experienced people. &kopete; makes life easy: a metacontact is a person, and contacts are ways to communicate with that person. You will recognize contacts in a metacontact easily as small icons representing the &im; services you could use to communicate with that person. +&kopete; is intended for all levels of users. Out of the box, it supports a minimal set of functions to make chatting as easy as possible. More advanced users can add extra functions such as Cryptography with &kopete;'s plugin system. + + +More &kopete; Information on the Web +For more info about the &kopete; project; the team maintains a website at &kopetewww;. The latest news and updates are always available there. +If you need to contact the team, the &kopete; developers' mailing list is hosted at https://mail.kde.org/mailman/listinfo/kopete-devel. +If you want live support, there is an Internet Relay Chat channel for &kopete; where you can find the team discussing technical (well, not always) issues or just hanging out. You can use any IRC client to join the channel (including &kopete;), just add an IRC contact and use irc.kde.org as the server and #kopete as the channel name. See you there! + + + +Introduction to Instant Messaging +What is Instant Messaging (&im;)? &im; is a way for you to communicate instantly with your friends over the Internet. That might not sound so different to email. Have you ever noticed how cumbersome it is to have a brief conversation via email? You have to click Reply to each message, then find the right spot in the message to type something new, then send it. Then you have to wait for the next message to arrive! &im; lets you to have a conversation almost as naturally as on the phone or face to face, by typing messages into a window shared between you and your friend's screens. +Another difference between &im; and email is that with &im; you can see your friends' presence, that is, whether they are actually on-line at the same time as you. This lets you send messages truly instantly, instead of sending off a mail and having to wait for your friend to check their mailbox. An &im; message pops up on the other person's screen as soon as you send it. Of course, if you'd rather not be interrupted, you can change your own presence so others will know not to disturb you. +There are lots of other fun and useful &im; features you can explore with &kopete;, like group chats, file transfers and emoticons that reflect your mood. Read on to find out more! + + + + +Getting Started +To use &kopete; you need to set up one or more accounts for the instant messaging services you wish to use. +You've probably already chosen a messaging service, either because you already use &im;, or you need to use the same service as your friends. If you don't fit into either of these categories, please consider using a messaging service based on open standards, because these are designed for use by Free Software. Other messaging services are prone to changing the underlying technology without making the details freely available, making them harder for Free Software developers to support. +The messaging services that &kopete; supports that are based on open standards are Jabber and IRC. +The following section assumes you are registered with an &im; service already. If not, you can register with Gadu-Gadu, Jabber, and MSN from inside &kopete;; for other services, you'll have to register using their respective web site before creating an account in &kopete;. + +Creating Accounts +To create an account, use Settings Configure &kopete;... to display the Configure window. +The Configure window is the main way to set up and customize &kopete;. On the left a column of icons control which aspect of &kopete; is being configured. Click the Accounts icon. The main pane will change to display the account management pane. This is currently empty, but will soon list your &im; accounts. Click New to display the Account Wizard. +The Account Wizard helps you create an &im; account. After the Introduction page, you are asked to select the messaging service that you'd like to use. Click one of the services shown and then click Next. On the following page, you should enter your registration details for that instant messaging service. +Most services just require you to enter a username or unique identifying number (UIN) and password. The special purpose services Winpopup and SMS work slightly differently, so please see their specific sections. There are a couple of other options that apply to most services that you should look at: + +Remember passwordWhen this is checked, &kopete; will store the password for you, so you don't have to enter it every time you connect to the &im; service. If you are security-conscious or want to limit access to the &im; account you can leave this unchecked. +Connect at startupWhen this is checked, &kopete; will try to connect to the &im; service as when it starts. If you use a LAN, DSL or other always-on connection, this is appropriate; dial-up modem users should turn this off and connect manually when you have dialed up. + +Once you've entered your &im; details, you can proceed to the Finished! page and then dismiss the wizard and the Configure window. + + +Go Online and Start Chatting! +Now you'll notice that an icon representing the account has appeared in the status bar at the bottom of the &kopete; Contact List window. This represents your current presence for this account. Right click on it and you can go online from the menu that appears. The status bar icon will animate while &kopete; connects to the &im; service. +Once you're online, if you've used this &im; service before, your contacts will be fetched from the server and displayed in the Contact List. To start a chat with a contact, just click their name and a Chat window will appear. The upper part of the window is where the conversation appears - to say something, type into the bottom part of the window and click Send. +If you've just created a new account you won't have any contacts. See Adding Contacts for details on how to add contacts. +The shortcut for Send is set to &Ctrl;&Enter; by default; you can change it in the Chat window using SettingsConfigure +Shortcuts.... + + + + +Using &kopete; +This chapter gives an overview of &kopete;'s basic features. We will look first at the contact list, where your contacts are displayed, and then at the Chat window, where you carry out a conversation. + +The Contact List +The Contact List appears when you start &kopete;. It's the main window where you can set your presence, start a chat, organize your contacts, configure &kopete; and quit. + +Layout of the Contact List window +MenuYou will usually find the menu bar at the top of the contact list. If it's not there, you might have turned it off; you can re-enable it with &Ctrl;M. Details on each menu item can be found in the chapter on menu structure. + +Tool barThe toolbar holds the most frequently used &kopete; actions. You can customize it with Settings Configure Toolbars.... Notice the Show Offline Users and Show Empty Groups buttons. With these you can hide contacts and groups that are offline. + &kopete; makes it even easier to set a status message to let your contacts know about your mood or why you're busy at the moment. Click on the Set Status Message button and start typing to enter a new message, or choose from one of the previous messages you have used. + The Quick Search Toolbar quickly filters the contact list, by typing a few letters from a contact's name. + +Contact ListThe Contact List takes up the main part of the window. All your contacts are listed here, in the groups you have chosen for them. You can open or close groups by clicking the plus symbol adjacent to the group. You can reverse the order the groups are sorted in by clicking the Contacts label above the list. +The context menu in the Contact List changes depending on the item under the mouse. Groups, Metacontacts and &im; system specific contacts have their own options. In empty areas of the Contact List, the context menu allows you to add contacts or groups, or change the viewing options for the list. + +Status barThe status bar shows an icon for each &im; account you have created. The icons represent the current presence of each account, which can be changed by right-clicking the account icon. + Kopete also shows your current status message in the Status Bar>. By clicking on the note icon in the corner, you can change or clear the status message as well. + + + +Setting Your Presence +We introduced you to setting presence in the previous chapter. 'Presence' determines how visible you are on the &im; network. To use the network at all, you have to connect to the network, so you can send and receive messages and see others' presence. Once you are connected, most &im; systems allow you to indicate what you're doing and whether you want to chat by setting special types of presence such as Away or Free For Chat. The difference presence settings are particular to each away system; but &kopete; allows you some control all your &im; systems at once by setting them to Away or Available at the same time. +You set your presence for individual &im; accounts by right clicking the account's icon in the status bar at the bottom of the Contact List. The context menu for each account lets you choose the possible presence settings for each &im; system. +To change all your accounts' presence together, click the Status, or use the File Status menu. + + +Start A Chat From The Contact List +To start a chat from the Contact List, simply click a contact. A Chat window will appear. +You can also right click a contact and select either Send Message or Start Chat. Send Message works differently in that it just sends a single message without opening the Chat window, using a simple dialog. Use it for fire-and-forget messages. + + + Send A File + You can send files from the Contact List, using the context menu on the person you want to send to. If &kopete; supports file transfer on their &im; system, there will be a Send File... item. Alternatively, you can drag a file from anywhere else in KDE onto their name to start a file transfer. + + +Organising Contacts + +A Word about Metacontacts +One of the principles behind &kopete; is that it offers a standardized way to use &im; systems. Differences between &im; systems are smoothed over, making it easier to communicate. We follow this principle in the way contacts are organized. When you use &kopete; you just find contacts by name; the actual &im; system used is less important. Some people have more than one &im; account - &kopete; puts the person using the account first. +To support this, &kopete; introduced Metacontacts, which represent the person you want to chat with. One Metacontact contains all the different &im; IDs they may have, making it easy to see with a glance at the Metacontact 'smiley icon' whether someone is available, regardless of which &im; system they are using right now. + + +A Word about Grouping Contacts +&kopete; lets you create groups to sort your contacts. A contact may be in more than one group. Where possible, groupings are saved on server side contact lists, so if you use other &im; programs, group memberships are kept in sync. However, if you change groups in another &im; program, &kopete; cannot know to move a metacontact automatically; it is up to you to resolve this by hand. +To change the group a metacontact appears in, you can use its context menu to move it or copy it to a new group, or remove it from a group. You can also use drag and drop here - just drop the metacontact on a different group name. + +Adding Contacts +To add a contact, either select FileAdd Contact... or click the Add Contact button on the toolbar. This brings up the Add Contact Wizard. +The Add Contact Wizard creates a new Metacontact using one or more &im; systems, by leading you through the following pages. + + +Welcome Page. Here you can choose whether you want to use the &kde; Address Book for this contact. Storing &im; information in the &kde; Address Book will enable other &kde; &im; programs to share contact information with &kopete; and in future &kde; applications may use &kopete; to send information via &im;. If you prefer to keep your &im; contacts separated, clear the checkbox here. +Choose &kde; Address Book entry. By choosing an entry from your &kde; Address Book, you can use its name as a Display Name in &kopete;. You can also create a new entry here. This page doesn't show if you chose not to use the &kde; Address Book. +Select Display Name and Group. Here you can enter a Display Name (the name used for this person inside &kopete;), and choose the groups they are a member of. +Select &im; Accounts. Here you can choose which accounts you want to use to chat to the new contact. If you only have one &im; account, you won't see this screen. +Account-specific Add Contact Pages. For each account, you'll get one page where you can enter the UIN, buddy name or E-mail address, depending on the &im; system in use. +Finish Screen. All done. Except if the &im; system requires authorisation (such as ICQ) to add a contact to your list - in which case, you'll be prompted after the wizard exits. + + +You can add contacts to an existing Metacontact using its context menu. + + + +Renaming Contacts +You can rename a contact using EditRename Contact or with the same item on the metacontact context menu. +Some &im; systems allow you to set a Display Name that is different to your username, such as Alice loves crypto!. If you change a contact's name manually, this will override their Display Name. To get it back, open the Properties dialog for that contact and check the Use the name given by the server checkbox. + + +Removing Contacts +If you no longer want a contact to be in the contact list, you can remove a Metacontact and all the contacts under it with Metacontact context menuRemove Contact. + + +Moving Contacts between Metacontacts +You can change the metacontact a contact belongs to. In practice, you only have to do this when you have just added multiple accounts to &kopete;, and you know that HotDog76 and mikejones@hotmail.com are both the same person. +There are two ways to do this: + +Drag and DropThe contact icon to the right of the metacontact name may be dragged from one metacontact to another. +Contact Context MenuThe context menu for contacts (Right-click the contact icon) allows you to choose the new metacontact from a dialog. + +If the move would leave a Metacontact empty (with no contacts), you'll be asked if you want to delete this contact. + + +Removing Contacts from Metacontacts +To remove a contact from a Metacontact, choose Contact context menuDelete Contact.... + + + +Configure &kopete; +You can configure &kopete; using SettingsConfigure &kopete;.... See the next chapter for details. + + +Exiting &kopete; +To exit &kopete; you should use FileQuit, &Ctrl;Q, or the &kopete; System Tray icon's context menu. If you just close the Contact List window, &kopete; will continue to run in the &kde; System Tray. + + +Keyboard shortcuts +The following keyboard shortcuts are supported in the Contact List window: + + + + +Keyboard Shortcut +Action + + + + +Up Arrow +Select the previous item in the contact list. + + +Down Arrow +Select the next item in the contact list. + + +Left Arrow +Close the current group. + + +Right Arrow +Open the current group. + + +Enter +Start a chat with the selected contact. + + +&Ctrl;M +Show/Hide the menu bar. + + +&Ctrl;U +Show/Hide offline users. + + +&Ctrl;G +Show/Hide empty groups. + + + + + + + + +The Chat Window +Layout of the Chat Window +The Chat ViewThe Chat View usually takes up most of the Chat window and is where the conversation between you and your contacts takes place. Messages appear in the order they are received, with the earliest messages at the top of the view. +You can control the appearance of the Chat View, making it look like other &im; clients or create a completely individual look. + +Chat Members List +Since some &im; systems allow you to chat as a group, it's useful to see who's chatting at the moment. The Chat Members List appears to the left or the right of the Chat View. You can change this using SettingsChat Members List. The contact context menu is available in the Chat Members List. +Input AreaThe Input Area is below the Chat View. This is where you type messages before sending them. You can change the font and color of the message using the usual tools on the toolbar. If the &im; system supports this, your messages will appear in color when your contacts read them.By default, the keyboard shortcut to send messages is &Enter;. +Status BarThe Status Bar contains temporary messages, such as notification that someone else is typing, as well as the Send button. +Tabbing&kopete; lets you carry on multiple conversations in one window, by putting each one in its own tab within the window. The tab titles change color to show when a new message has been received: + +RedSomeone typed a message. +GreenSomeone is typing a message. +BlueSomeone typed a message containing your nickname. + +There are several different ways to control grouping. To configure this behavior, go to the Chat tab of the Behavior page of the Configure &kopete; dialog. You can also move chats between windows using the Tabs menu, and control the placement of the tabs in the window. + + +Group Chats in &kopete; +You can use &kopete; to chat one to one, or in a group, where the &im; system supports this. +To invite others into a chat, drag them from the Contact List into the chat window, or use ChatInvite<contact name>. + + +File Transfers +Some &im; systems allow you to send and receive files. You can access this function from the contact's context menu. If you're already chatting, and want to send a file, simply drag the file from any other part of &kde; into the Chat Window, or select the ChatSend File menu. + + +Keyboard Shortcuts +The following keyboard shortcuts are supported in the Chat window: + + + + +Keyboard Shortcut +Action + + + + +&Enter; +Send the message in the Input Area. + + +&Ctrl;P +Print the contents of the Chat View. + + +&Ctrl;S +Save the contents of the Chat View. + + +&Ctrl;W +Close the current Chat View. The Chat window will close unless there is more than one tab in the window. + + +&Alt;Left Arrow +Change to the previous tab. + + +&Alt;Right Arrow +Change to the next tab. + + +&Ctrl;&Shift;B +Detach a tabbed chat into a separate window. + + +Tab +Complete a partially typed nickname belonging to someone you're chatting with. + + + + + + + + + + +Configuring &kopete; +To configure &kopete;, look in the Settings menu. + +Global Shortcuts +&kopete; defines some shortcuts which are valid in any &kde; application. + + + + +Global Keyboard Shortcuts +Action + + + + +&Ctrl;&Shift;I +Read Message. This is useful if you have hidden the Contact List window and the system tray icon is animating to tell you you have a new message. + + +&Ctrl;&Shift;C +Show/hide (Dock) the Contact List window. Warning: If you have disabled &kopete;'s System Tray icon or don't have a system tray, this can make the Contact List vanish - the only way to restore it is to repeat this shortcut. + + + + + + + +The Configure &kopete; Dialog + +Adding and Editing Accounts +We briefly showed you how to add an account in Getting Started. To change an account's settings later, open up the Configure &kopete; dialog, with SettingsConfigure &kopete;. Much like the &kde; Control Center, the configuration is separated into sections. The icons on the left side of the dialog switch between sections. +On the Accounts page, you can Add, Remove, or Modify accounts. Editing accounts is much the same as adding them, but note that you can't change the UIN, buddy name, or whatever account identifier your &im; system uses. This is intrinsic to the account. If you want to change this, you will have to add another account with the new account identifier and (optionally) remove the old account. +You can quickly distinguish between multiple accounts using the same &im; system by giving a custom color to each account's status bar entry and contact icons. To do so, select the account and click the color selector on the right side of the page. +You can control the priority of accounts using the Up and Down icons on this page. If you have more than one way to message a contact, this determines the &im; system &kopete; will use to communicate them. + + +Global Identity +&kopete;'s Global Identity lets you set your own nickname and photo once for all your &im; accounts. You can read these details from the KDE address book entry for yourself, from a single one of your contacts, or add a completely new nickname or photo. If you have an exciting dual life, you can create multiple identites and switch between them in theIdentity section. + + +Behavior +Behavior covers the way &kopete; integrates with your desktop, Away settings, and chat user interface options. + +The General tab +Here you can customize &kopete;'s desktop integration, and control the way the contact list is laid out. + + +Show system tray icon + +By default, &kopete; places an icon in the &kde; System Tray. If you prefer, you can turn this off here. + + + +Start with hidden main window + +This causes &kopete; to start with the Contact List window hidden (docked). You can make it visible by clicking the system tray icon or with the Show Contact List global shortcut. + + + +Open messages instantly + +New messages open chat windows as soon as they arrive. + + + +Use message queue + +The message queue is where &kopete; puts messages when there is no chat window open. This allows you to be notified of new messages with popup speech bubbles; or by animating the System Tray icon. If you choose to disable the message queue, chat windows will open as soon as you receive a new message. + + + +Use message stack + +If you use a message stack, &kopete; shows recently received messages starting with the last message received. + + + + +Notifications + + +Show a bubble on new message + +This option shows a speech bubble; coming from the System Tray icon when you receive a new message. You can start a chat or ignore the message. URLs are extracted from the message; if you click a link, your preferred browser will open the link and the message will be dismissed. + + + +Flash the system tray on new messages + +This option causes the System Tray icon to animate when you receive a new message. Clicking the icon will show the message in a chat window. + + + +Enable events while away + +If you do not wish to be distracted by these notifications while you are set Away, uncheck this box. + + + +Configure Sounds & Events + +Sounds, flashing taskbar entries, passive popups and more exotic notifications are supported in &kopete; using the &kde; notification system. Type help:/kcontrol/kcmnotify in &konqueror; or select the Help tab in the System Notifications section of the &kcontrol; for more information. +To add custom notifications for a contact, right click that contact in the Contact List and select Properties. This lets you start chats, play a custom sound effect, or display a message for that contact or group. Otherwise you can use the Execute a program notification to perform custom notifications. As an example, if you have XOSD (X On-Screen Display) installed, you can get OSD online notifications by executing the following command when the User goes online event takes place: +echo %s | osd_cat -o 100 -p bottom -A center -f -*-helvetica-*-r-*-*-24-*-*-*-*-*-*-* -O 2 -c gold +OhReally at the KDE Forum suggests having your online notifications read out by a speech synthesiser, using MBROLA like so: +echo %s | sed -e 's/online/onlaain/i' | /usr/local/bin/mbrdico.dutch.female +The 'sed' in the middle phoneticises &kopete;'s output to so the synthesizer has a better Dutch pronunciation. + + + + + + +Away Settings + + +Notify all open chats when I go away +Be careful if you enable this item; it will cause a message to be sent to open chats when you become away, which some people may find intrusive. + + + +Auto Away +Here you can choose to have &kopete; set you away after a period of inactivity. + + +Predefined Away Messages +You can define as many custom away messages as you like here, and choose from them when you go Away using the Status button on the main toolbar. + + + +Chat Settings + + +Raise window/tab on new messages +This causes a chat window to pop up when it receives a new message. + + +Show events in chat window +Some &im; systems can give extra information, like people joining or leaving chats. This option lets you receive these messages in your chat window. + + +Highlight messages containing your nickname +This simply emphasizes messages in a chat that contain your nickname. For more powerful control over highlighting and other events, see the Highlight plugin. + + + +Interface Preference +&kopete; can send messages using either a fire and forget interface that does not wait for a reply, or a chat window where the conversation is visible as it unfolds. Here you can choose which style to use by default. + + +Chat Window Grouping Policy +If you wish to group chats within tabs in a single window, &kopete; lets you choose several ways to determine the grouping. + +Open all messages in a new chat window +Group all messages from the same account in the same chat window +Group all messages in the same chat window +Group all messages in the same group in the same chat window +Group all messages from the same metacontact in the same chat window + + + +Chat Window Line Limit + + +Maximum number of chat window lines +This limits the number of lines of text the chat window can display. + + + + + + +Appearance +Appearance governs the style of the Chat window, its colors and fonts, and lets you choose your preferred emoticons. + +Emoticons +Emoticons (also known as smileys) are combinations of characters such as ;-)that look like a face, and communicate moods or expressions. &kopete; can optionally use graphical emoticons in place of the characters themselves. +On this tab, you can select which emoticon set you prefer, or turn off graphical emoticons altogether. +See Specialized Actions for details of how to install extra emoticon sets. + + +Chat Window + +Styles +The style of the chat view can be altered to look like other clients. Installed styles are shown in the list on the left and are previewed in the main panel. See Chat Window Style guide for a document how to make your own style. +Third party styles are available at http://kde-look.org. &kopete; 0.12 now supports styles from Adium(an &im; program on Mac OS X). So you can download styles from Adium here: Adium Xtras and select Message View Styles. +To install a style, clickInstal.... Select a archive file containing the style. To delete a style, select a style in the list and click Delete. +Group consecutive messages is a useful option to make your chats more readable. If you receive several messages in a row from teh same contact, they are grouped without repeating the sender name. + + + +Contact List + + + Arrange metacontacts by group + +By disabling this, &kopete;'s groups are hidden, and contacts are divided only into Online Contacts and Offline Contacts. + + + +Show tree branch lines + +Usually &kopete; displays contacts and groups as a tree, where group members are indented. For a simpler appearance, you can disable this, so the contact list becomes a flat list. You can also control whether branches are indented here. + + + +Contact Display Mode + + There are several ways you can present the contact list here. Of particular interest may be the Use contact photos when available option, that shows the contact list using photos chosen by your contacts or the KDE Address Book + + + +Contact List Animations + + This controls the degree of animation of the contact list. Turning this off will make &kopete; more responsive on slower machines. + + + +Contact List Auto-Hide + + By enabling this, the contact list will automatically disappear a few seconds after the pointer leaves the window. + + + +Change Tooltip Contents... + + You have a lot of control over how much or how little detail appears inthe tooltips shown on the contact list using this dialog. + + + + + +Colors and Fonts + +Chat Window Colors +Here you can alter the base font and text colors used for chatting. + + +Formatting Overrides +If your contacts tend to choose fonts and colors that you dislike, you can tell &kopete; to ignore these and use your regular font. + + +Contact List +Some &im; systems let you see whether contacts are idle at their computers. This option enables you to change the color used for idle contacts. + + + + +Devices +The Devices section allows you to choose and configure which multimedia devices are used for A/V chatting. Whether this works for you is highly dependent on the hardware you have and how well it is supported by your operating system. + +Video +&kopete; uses the Video4Linux 2 system for video. This shows a blue square if no video device is found, or a preview if the camera is working. For up-to-date information on &kopete; webcam support, see the Kopete Webcam Support wiki page. + + +Audio + Audio support in &kopete; is at an experimental stage. If you have an Audio tab, you are probably using a preview build of &kopete;. + + + + + +Loading Plugins +You can customize &kopete; with special functions that may be useful or just a bit of fun. Bring up the Configure Plugins dialog with SettingsConfigure Plugins.... +Plugins can be turned on or off in the list on the left side of the page. Each plugin may be configured on the right side. See the chapter on plugins for details on each plugin. + + + + + &kopete;'s Protocols + &kopete; calls different &im; systems 'Protocols'. When you add an account, it is specific to a single protocol. Although &kopete; tries to make instant messaging appear the same, no matter what protocol you use, there are some differences in the level of support for advanced features such as file transfer and multimedia. + + Protocols + + AIM + AIM supports chatrooms. Use the Join Chat... command on the AIM account menu to join a chatroom. Contact pictures and custom emoticons are also supported. + + + ICQ + ICQ has an Invisibility feature which allows you to hide from selected contacts. You may also search the ICQ user directory when adding a contact. A wide range of contact details can be set using the Properties option. + + + MSN + MSN supports the sending and receiving of webcams, if your camera is supported by the Video4Linux 2 (v4l2) standard. To view someone's webcam, right click on their MSN buttefly icon and select View Contact's Webcam. File transfer and multi user chats work. To transfer a file, drag the file from Konqueror or the desktop into the chat window. To invite someone else into a chat, drag them from the Contact List into the chat window. The File menu also contains these commands. In addition, MSN supports custom emoticons. + To use file transfer or a webcam, make sure port 6891 is forwarded to your computer. + + + Yahoo + Yahoo can send and receive webcam video. It also supports Yahoo mail and the Yahoo addressbook from the account menu. Conferencing is also possible. + + + Jabber + Jabber, also known as XMPP, supports file transfer, conferencing and any other services supplied by the Jabber server. For example, many Jabber servers have a user directory, and some provide transports to other messaging systems. To access services, use Services... on the account menu. Jabber file transfer can work without port forwarding, but enjoys better performance where a direct connection is possible. By default, port 8010 is used for port forwarding, but this is configurable in each account's settings. + + + Google Talk + Since Google Talk is based upon Jabber, it is well supported in &kopete; with the exception of voice chat, which is worked upon. + To configure &kopete; for Google Talk: Use your complete Google Mail address as the user name. Check Use protocol encryption (SSL), Allow plain-text password authentication and Override default server information. The server is talk.google.com or gmail.com and ports 443 or 5223 should be used. + + + Novell GroupWise + GroupWise Messenger is an enterprise messenging system from Novell Inc. The full range of features are supported, including privacy, group chat, rich text and user search. + + + Gadu-Gadu + Gadu-Gadu is a chat system originating from Poland. At present, &kopete; supports basic chat functions. + + + WinPopup + WinPopup is a way to use &kopete; to send and receive messages with Windows computers on the local network. The WinPopup protocol only supports single, plain-text messages. + + + Other protocols + As well as the protocols named above, &kopete; has support for several other protocols. In most cases, this is not enabled by default or an additional plugin must be installed. Meanwhile, SMS, Skype and SILC are provided in this way. See &kopetewww; for details, however, the &kopete; team are not responsible for these protocols. + + + + + +&kopete;'s Plugins +&kopete; offers plugins that provide functions that aren't essential for messaging, but are useful for some people. + +Plugins + +Alias +Alias lets you define your own commands, eg /hello, in &kopete; that run scripts and output the result in the chat window. If you know how the alias Unix command works, this is just the same + + +Auto Replace +Auto Replace allows you to correct frequently misspelled words or save typing certain words using abbreviations. + + +Bookmarks +The Bookmarks plugin creates bookmarks in your KDE bookmarks list from URLs that are received in &im; messages. + + +Connection Status +Connection Status is useful for modem users or others who don't have a permanent Internet connection. It watches for an active Internet connection and sets your accounts online when it detects you have dialed up. + + +Contact Notes +Contact Notes allows you to note down any useful bits of information on a metacontact. + + +Cryptography +Cryptography lets you use GnuPG to encrypt conversations. Note that this is not the same as an SSL secured chat session. SSL protects the message from alteration and snooping of the message contents while it is in transit, but it doesn't guarantee the person reading the message is the intended recipient. Cryptography encrypts the message to a single individual - only the holder of the matching key can read it. However, it doesn't guarantee that the message the recipient reads is the message you sent! Since anyone who has the recipient's public key can encrypt to him/her, it is possible that your message could be replaced en route by a completely different message, and the recipient would not know. +To configure Cryptography, select your GnuPG key in the configuration page. If you select Encrypt outgoing messages with this key, then messages will be encrypted to you as well as the recipient, which is useful if you want to read your own chat logs later. Then, using Select Cryptography Public Key from each contact's context menu, choose their public key. You will be prompted for your passphrase when using this plugin. + + +Highlight +Highlight works a little like e-mail filters, in that it allows you to make things happen in response to particular messages. As well as highlighting the text, you can play sounds. + + +History +The History plugin, when activated, records conversations using any &im; system and allows you to view old conversations later. A History item appears in each Metacontact's context menu so you can view the message history for that metacontact. +The following item is added to the Contact List's menus: + + + + +Edit +View History + + + +(Enabled when a contact is selected) This displays the History browser for the selected contact. + + + +The following items are added to the Chat window's menus: + + + + +&Alt;&Shift;Left Arrow + +Tools +History Previous + + + +This enables you to view the next oldest set of messages from the History in the Chat window. + + + + + +&Alt;&Shift;Right Arrow + +Tools +History Next + + + +This shows the next newest set of messages from the History in the Chat window. + + + + + +Tools +History Last + + + +This shows the most recent set of messages from the History in the Chat window. + + + + + + +KopeteTeX +KopeteTeX allows scientists and mathematicians to hold conversations using the LaTeX markup language. Expressions entered within $$ are rendered as a graphic in the chatwindow, and can be cut and pasted as the original Latex. To use this plugin you must have LaTeX installed + + +Motion Auto-Away +In conjunction with a webcam and the Video4Linux package, this lets you detect when you're no longer at your computer and have &kopete; automatically become Away. + + +Now Listening +With the Now Listening plugin, let people you're chatting with know what you're listening to, by typing /media in a chat, or with ToolsSend Media Info in the Chat window. + + +Statistics +This plugin uses a database to gather information about your contacts' activity patterns. You can use this to see when a contact is usually online, for example. + + +Text Effect +Text Effect applies funny effects to your messages before sending them, like coloring them or changing the case of the words. Just don't forget you have activated it - we've had bug reports from forgetful Text Effect users! + + +Translator +The Translator plugin lets you define a preferred language for each Metacontact, and then translates messages to or from them using web based translation services such as Google and Babelfish. Set your own preferred language in the Configure Plugins dialog. Each contact's preferred language can be set on its context menu. +The following item is added to the Chat window's menus: + + + + +&Ctrl;T + +Tools +Translate + + + +If you did not turn on automatic translation, this translates the current chat. + + + + + +Web Presence +Web Presence allows you to publicize your &im; presence on the Web. Give it the path to a file on an FTP server (for example), and it will upload a short piece of HTML to that file, which you can include in your homepage. &kde;'s network transparency makes this simple. Useful for bloggers to make friends with, or you could use it to use &im; in your business. +Example: sftp://username@somehost.org/path/to/homes/user/im.html uses the SFTP protocol to upload your presence directly onto the webserver. +See the KIO manuals for tips on specific network protocols. + + + +Contributing a plugin +&kopete; is designed to make it easy to create plugins that give it extra functions. So if you've got a great idea to make &kopete; even better, get in touch! + + + +&kopete-menus; + + +Frequently Asked Questions + + +What does &kopete; mean? How do I pronounce it? +&kopete;'s name comes from the chilean word Copete, meaning a drink with your friends. Duncan, the original author, recorded an audio sample. + + +When I have more than one messaging service under a user's name in my contact list and I click on that user's name, it will message them on the wrong messaging service. + +You can change the order of accounts &kopete; tries to message people with by using the Up and Down arrows in the bottom right corner of the account configuration screen. &kopete; will try to connect with accounts starting from the top. However, if one service has a higher status value than the others for that user, &kopete; will use that one. For example, if a person has three services and two are marked as away and the third is marked as online, &kopete; will always try to message the user using the online service.If you click on the small protocol icon on the right of the menu item, instead of on the person's name, you will always try to contact the person using that service! + + + +I need to connect via a SOCKS proxy, but I can't find any proxy configuration options in &kopete;. How do I set up &kopete; to use SOCKS? + + +MSN, ICQ, AIM, Jabber, and Yahoo use the &kde; network infrastructure. Their SOCKS proxy details are configured with the rest of &kde;, in Control Center, Internet & NetworkProxy. + + + + +Is it possible to customize the icons I see in &kopete;? +You can switch between different emoticons using the Emoticons tab of the Appearance page of the Configure &kopete; dialog. +To install new emoticons, first look at KDE-Look.org, where there are a lot of additional emoticon sets to download. + +The emoticons are easy to install - you just place a directory containing +the icon files along with an XML file describing the mapping from text to picture in +$KDEDIR/share/apps/kopete/pics/emoticons (or $KDEHOME, for example, in /home/joeuser/.kde/). + + +Copy the extracted directory to $KDEDIR/share/apps/kopete/pics/emoticons or + $HOME/.kde/share/apps/kopete/pics/emoticons (or wherever $KDEHOME is) +Select Configure &kopete; from the Settings menu and click on Appearance in the left panel of the Preferences window and click on the Emoticons tab +Select the emoticons set you just installed from the list +Now you can use the newly installed emoticons in &kopete; + +To replace the protocol icons, you'll have to replace the icons in +$KDEDIR/share/apps/kopete/icons, or provide replacements to override them in +the same dir under $KDEHOME. At present there aren't any complete replacement +sets that you can simply extract there. + + + + + + +Specialized Actions +Command line parameters +Installing emoticon sets + + + +Credits and Licenses + +&kopete;: copyright 2001-2005, &kopete; Developers + + +&underFDL; +&underGPL; + +Current Development Team + + +Duncan Mac-Vicar Prett (duncan at kde org): Original author, developer, and project leader +Till Gerken (till at tantalo net): Developer, Jabber maintainer +Olivier Goffart (ogoffart at tiscalinet be): Lead Developer, MSN Plugin Maintainer +Andy Goossens (andygoossens at telenet be): Developer +Grzegorz Jaskiewicz (gregj at pointblue com pl): Developer, Gadu-gadu Plugin Maintainer +Jason Keirstead (jason at keirstead org): Developer +Matt Rogers (mattr at kde org): Lead Developer, AIM and ICQ plugin maintainer +Richard Smith (lilachaze at hotmail com): Developer, UI maintainer +Will Stephenson (lists at stevello free-online co uk): Developer, icons, plugins, manual author +Michel Hermier (michel.hermier at wanadoo fr): IRC Plugin Maintainer +Andre Duffeck (andre at duffeck de): Developer: Developer, Yahoo plugin maintainer +Michaël Larouche (michael.larouche at kdemail net): Developer, MSN, Chat Window. + + + +Former Developers (&kopete; Hall Of Fame) +These people have moved on from &kopete;, so don't contact them for &kopete; support. We're eternally grateful for their contributions. + +Christopher TenHarmsel (tenharmsel at users sourceforge net)Developer, Oscar hacker +Ryan Cumming (ryan at kde org): Core developer +Richard Stellingwerff (remenic at linuxfromscratch org): Developer +Hendrik vom Lehn (hennevl at hennevl de): Developer +Stefan Gehn (sgehn at gmx net): Developer +Robert Gogolok (robertgogolock at gmx de): Developer +Nick Betcher (nbetcher at kde org): Original author of ICQ, AIM and IRC plugins +Daniel Stone (dstone at kde org): Original Jabber plugin author +James Grant (topace at lightbox org): Developer, importer Plugin author +Zack Rusin (zack at kde org): Developer, old Gadu-gadu Plugin author +Gav Wood (gav at kde org): WinPopup Plugin author +Martijn Klingens (klingens at kde org): Developer, MSN hacker + + + +Documentation + +Documentation copyright 2003,2004,2005 Will Stephenson (lists at stevello free-online co uk), copyright 2005 Matt Rogers (mattr at kde org), copyright 2005,2006 Michaël Larouche (michael.larouche at kdemail net). + + + + + +Installation + +How to obtain &kopete; +&install.intro.documentation; +Development versions may be downloaded at &kopetewww;. + + +Required Libraries +If you installed &kopete; as part of your distribution, you probably have these installed already. +The Gadu-gadu plugin requires the libgadu package, see this page for details. +The Now Listening plugin needs libxmms if you want to access what xmms is currently playing; this should be available in your distribution but is available as part of the xmms package at the xmms homepage. + + +Compilation and Installation +&install.compile.documentation; + + + +&kopete-chatstyle; + +&documentation.index; + + + diff --git a/doc/kopete/menus.docbook b/doc/kopete/menus.docbook new file mode 100644 index 00000000..334b1114 --- /dev/null +++ b/doc/kopete/menus.docbook @@ -0,0 +1,876 @@ + + +Menu Entries + +Each menu item is discussed below. When there is a keyboard shortcut that +performs a menu item function, the default shortcut is listed with the menu item. + +The Contact List Window's Menus + +<guimenu>File</guimenu> Menu + + + + + +File +Set Status +Online + + + +Go online with all accounts + + + + + + +File +Set Status +Away + + + +Set all connected accounts away + + + + + + +File +Set Status +Offline + + + +Set all accounts offline + + + + + + +File +Add Contact... + + + +This displays the Add Contact Wizard, which allows you to add a new contact to your list + + + + + + +File +Create New Group... + + + +Prompts you for the new group's name and adds it to the contact list. + + + + + + +&Ctrl;Q + +File +Quit + + + +Disconnects you from all &im; systems, closes all the windows and exits the application. + + + + + + +<guimenu>Edit</guimenu> Menu + + + + + +&Ctrl;Z + +Edit +Undo + + + +Reverts the last change that was made to the contact list. + + + + + + +&Ctrl;&Shift;Z + +Edit +Redo + + + +Reverts the last change that was made to the contact list by EditUndo + + + + + + +Edit +Send Single Message... + + + +Opens an email-style message window with the selected contact, to send a single message. + + + + + + +Edit +Start Chat... + + + +Opens a chat window with the selected contact, to have a conversation. + + + + + + +Edit +Send File... + + + +If supported by the &im; system, this opens a file selector to choose a file to send to the selected contact. + + + + + + +Edit +Move To + + + +(Enabled when a contact is selected) Choose another group from the sub-menu, and the contact will move to that group. + + + + + + +Edit +Copy To + + + +(Enabled when a contact is selected) Choose a group from the sub-menu, and the contact will be copied to group. &im; systems that allow contacts to be in more than one group on the server contact list will be updated accordingly. + + + + + + +Delete + +Edit +Remove + + + +(Enabled when a contact is selected) Removes a contact from the contact list entirely. + + + + + + +Edit +Rename Contact + + + +(Enabled when a contact is selected) Renames the contact on the contact list. If you do this, the contact list entry will no longer change if the contact changes their display name remotely. You can re-enable this using the contact's Properties dialog. + + + + + + +Edit +Add Contact... + + + +(Enabled when a contact is selected) Choose another account from the submenu, and you can add another way to message that person using that account. + + + + + + +Edit +Add to Your Contact List... + + + +(Enabled when a contact is selected) Sometimes people will message you who aren't on your contact list already. In this case, &kopete; creates a temporary entry for them, but to keep them on your contact list, use this function. + + + + + + +Edit +Properties + + + +The Properties dialog lets you choose custom icons for the selected item, and change its name. +For contacts, you can change the &kde; Address Book entry that they are associated with. + + + + + + +Edit +Remove Group + + + +(Enabled when a group is selected) Removes a group from the contact list entirely. Any contacts that are only in this group are moved to the top level. + + + + + + +Edit +Rename Group + + + +(Enabled when a group is selected) Renames the group. + + + + + + + +<guimenu>Settings</guimenu> Menu + + + + + +&Ctrl;M + +Settings +View Menubar + + + +Shows/hides the menu bar. + + + + + + +Settings +View Toolbar + + + +Shows/hides the tool bar. + + + + + + +Settings +View Statusbar + + + +Shows/hides the status bar. + + + + + + +&Ctrl;U + +Settings +Show Offline Users + + + +This shows/hides contacts who are currently offline. They will become visible when they go online again. + + + + + + +&Ctrl;G + +Settings +Show Empty Groups + + + +This shows/hides groups which do not have any members, or where all the members are offline and hidden (see above). + + + + + + +Settings +Configure Shortcuts... + + + +Shows the &kde; standard Configure Shortcuts dialog, where you can change keyboard shortcuts that work in the Contact List's windows. + + + + + + +Settings +Configure Global Shortcuts... + + + +Displays the &kde; standard Configure Global Shortcuts dialog, where you can change keyboard shortcuts that work all the time under &kde;. + + + + + + +Settings +Configure Toolbars... + + + +Displays the &kde; standard Configure Toolbars dialog, which lets you customise the Contact List's toolbars. + + + + + + +Settings +Configure Kopete... + + + +Displays the Configure Kopete dialog. + + + + + + +Settings +Configure Plugins... + + + +Displays the Configure Plugins dialog. + + + + + + +<guimenu>Help</guimenu> Menu +These are the &kde; standard items for the Help menu: + +&help.menu.documentation; + + + + + +The Chat Window's Menus + +<guimenu>Chat</guimenu> Menu + + + + +&Ctrl;&Enter; + +Chat +Send Message + + + +Sends a message. + + + + + + +&Ctrl;S + +Chat +Save + + + +Saves the content of the chat to a file. + + + + + + +&Ctrl;P + +Chat +Print... + + + +Prints off a hard copy of the chat. + + + + + + +Chat +Contacts + + + +This menu lists all the people in the chat. You have access to the same contact menu you get by right-clicking a contact's name in the Contact List, allowing you to perform contact specific actions such as sending them a file, viewing their user info or blocking them. + + + + + + +&Ctrl;W + +Chat +Close + + + +Closes the current chat. If there are chats taking place in other tabs in this window, the window will stay open. + + + + + + +&Ctrl;Q + +Chat +Quit + + + +Closes all chats taking place in this window. + + + + + +<guimenu>Edit</guimenu> Menu + + + + +&Ctrl;X +Edit +Cut + + + +Cutting text works as with most editors: the selected text is +removed and put into the clipboard. Note that you can also select text and +drag it to a new position. + + + + + + +&Ctrl;C + +Edit +Copy + + + +Copying text works as with most editors: the selected text is copied to the clipboard. +Note that you can also select text while holding the &Ctrl; key, and drag it to a new +position to copy it. + + + + + + +&Ctrl;V +Edit +Paste + + + +Pasting works the same as with most editors: the text from the +clipboard is pasted at the current cursor position. + + + + + +<guimenu>Format</guimenu> Menu + + + + +Format +Add Smiley + + + +This menu contains all the smileys/emoticons that the current emoticon scheme includes. You can change the scheme in the Configure Kopete Dialog. + + + + + + +Format +Text Color... + + + +Opens a color selector that modifies the text color. + + + + + + +Format +Background Color... + + + +Opens a color selector that modifies the background color. + + + + + + +Format +Font + + + +This menu allows you to change the font used in the chat. + + + + + + +Format +Font Size + + + +This menu allows you to change the font size used in the chat. + + + + + + +<guimenu>Tabs</guimenu> Menu + + + + +Tabs +Tab Placement + + + +This menu allows you to change whether tabs appear at the top or the bottom of the chat view. + + + + + + +&Ctrl;&Shift;B + +Tabs +Detach Chat + + + +Separates the current chat into its own window. + + + + + + +Tabs +Move Chat to Window + + + +You can move chats between windows using this menu. Choose the chat window the tab should move to from the menu. + + + + + + +<guimenu>Tools</guimenu> Menu +This menu contains items added by the plugins you have loaded. See the plugins chapter for details. + + +<guimenu>Settings</guimenu> Menu + + + + +&Ctrl;M + +Settings +Show Menubar + + + +Separates the current chat into its own window. + + + + + + +Settings +Toolbars +Show Main Toolbar (Kopete) + + +Shows/hides the main toolbar. + + + + + + +Settings +Toolbars +Show Status (Kopete) + + +Shows/hides &kopete;'s status bar. This is where buddy pictures appear. + + + + + + +Settings +Toolbars +Show Format Toolbar (Kopete) + + +Shows/hides the text formatting toolbar. + + + + + + +Settings +Show Statusbar + + + +Separates the current chat into its own window. + + + + + + +Settings +Show Chat Members List + + + +This menu controls whether the Chat Members List appears on the left or right of the Chat View, and whether it is visible at all. + + + + + + +Settings +Configure Shortcuts... + + + +Shows the &kde; standard Configure Shortcuts dialog, where you can change keyboard shortcuts that work in the chat window. + + + + + + +Settings +Configure Toolbars... + + + +Displays the &kde; standard Configure Toolbars dialog, which lets you customise the chat window's toolbars. + + + + + + +<guimenu>Help</guimenu> Menu +These are the &kde; standard items for the Help menu: + +&help.menu.documentation; + + + + + + + diff --git a/doc/kpf/Makefile.am b/doc/kpf/Makefile.am new file mode 100644 index 00000000..e786da56 --- /dev/null +++ b/doc/kpf/Makefile.am @@ -0,0 +1,3 @@ + +KDE_LANG = en +KDE_DOCS = AUTO diff --git a/doc/kpf/index.docbook b/doc/kpf/index.docbook new file mode 100644 index 00000000..5dff1c74 --- /dev/null +++ b/doc/kpf/index.docbook @@ -0,0 +1,410 @@ + + + + + +]> + + + + + + The &kpf; Handbook + + + + + Rik + Hemsley + +
&Rik.Hemsley.mail;
+
+
+ + + +
+ + + 2002 + &Rik.Hemsley; + + + &FDLNotice; + + 2003-09-30 + 1.0.1 + + + + &kpf; allows you to share files over a network. + + + + + KDE + public + fileserver + HTTP + + +
+ + + + Introduction + + + &kpf; provides simple file sharing using &HTTP; (the Hyper Text + Transfer Protocol,) which is the same protocol used by web sites to + provide data to your web browser. &kpf; is strictly a public + fileserver, which means that there are no access restrictions to shared + files. Whatever you select for sharing is available to anyone. + + + + &kpf; is designed to be used for sharing files with friends, not to + act like a fully-fledged web server such as + Apache. &kpf; was primarily conceived + as an easy way to share files with others while chatting on + IRC (Internet Relay Chat, or chat + rooms.) + + + + Typical usage: &kpf; is set up to serve files from the public_html folder in your home + folder. You wish to make a file available to some people with + whom you are chatting online. Rather than send them each an + email with the file attached (some may not even be interested,) + you copy the file into your public_html folder and announce + to those listening that your file is available at + http://www.mymachine.net:8001/thefile + + + + + + + Using &kpf; + + + + &kpf; basics + + + &kpf; runs as an applet inside &kicker;. This means that it + takes up little space on your screen and its status is always + visible. To start the &kpf; applet, + right click on &kicker; and choose + Add Applet to Panel... to open the Add + Applet dialog. Select Public File Server and + click the Add to Panel button. + + + + &kpf; employs the concept of shared folders. You may choose + one or more folders to make public, and all files in that folder + (and any subfolders) will be shared. + + + + Please be extremely careful about which folders you + share. Remember that all files in the folder and its + subfolders, including hidden files + (dotfiles to the techies) will be made + available to the world, so be careful not to share sensitive + information, such as passwords, cryptographic keys, your + addressbook, documents private to your organization, &etc;. + + + + Once &kpf; is running, you will see a square applet with a + thin sunken bevel and an icon depicting an hot air + balloon. The balloon is visible when no folders are being + shared. + + + + To share a folder, right click + on the balloon icon and a popup menu will appear, containing + only one item, New + Server.... Selecting this entry will cause a + wizard to appear, which will ask you a few + simple questions. Completing the questions will set up a + folder for sharing. + + + + There is an alternative to using the applet directly when you + want to share a folder. &kpf; is integrated with &konqueror;. + + + + With &konqueror; open and displaying a folder, + right click on the background and + bring up the Properties dialog. On install, + &kpf; added a Sharing tab to this + dialog. You will be offered the option of starting &kpf; if it + is not running. Choosing Ok will send a + signal to the &kpf; applet, asking it to add a new share. + + + + + + + + + Share configuration + + + + Listen port + + + For each folder that is shared by &kpf;, a new network + port is opened. A port is simply a number used to uniquely identify a + network service. When someone uses a program (⪚ a web browser) + to connect to a machine, it is directed to the service by specifying + the address of the machine and the port on which the service is + running. + + + + The port concept allows one machine to run more + than one network service. Services you might use every day + include &HTTP; (the web,) usually connected to by port 80, + &SMTP; (mail sending,) usually on port 25, + and POP3 (mail receiving,) usually on port 110. + + + + Usually, when you connect to a network service, you do not need + to specify which port you want to connect + to. This is because the ports are standardized, so anyone + connecting to port 80 on a network machine expects to find an + &HTTP; (web) server. + + + + &kpf; is not a standard service, so 8001 was + chosen for the default port. + + + + The second folder you share will offer to listen on port 8002, + with the number being incremented each time you start a new share. + + + + Within certain boundaries, you are free to choose whichever port + number you wish, for a share. + + + + It is usual for port numbers below 1000 to be reserved for + system services, &ie; those under the control + of the machine's administrator, therefore you may find that + attempting to use anything below 1000 will simply not work. + + + + &kpf; tries to warn you when it cannot listen + on a port. It does this by displaying a broken + connection icon over the top-left corner of the + graph. &kpf; attempts to stop you assigning more than one + share to the same port, but it will not attempt to stop you + setting a share to listen on a port which is already occupied + by another service, for example your real web + server. + + + + If you see the broken connection icon, + right click on the bandwidth graph + and choose Configure... Now try + changing the listen port and pressing + Ok. Assuming that this time you picked + a free port, you should see that the broken + connection icon disappears, and you should now be + able to connect to the share. + + + + + + + Bandwidth limit + + + The term bandwidth refers to the amount of data + that may be transmitted over a connection during a period of + time. Techies may be overheard referring to how + fat their pipe is. The analogy + is apt. + + + + &kpf; allows you to set a limit on how much bandwidth will be + used by a particular share. This is useful when you want to + avoid your network connection being saturated by people + downloading from your shares. If you are on a modem, for + example, you only have a few kilobytes per second to + yourself. Limiting the bandwidth used by your &kpf; shares + will allow you to keep a portion of the bandwidth for your own + use. + + + + As just mentioned, &kpf; measures bandwidth in kilobytes per + second, or kB/s for short. A typical modem transfers about 5kB/s on + average, so limiting the total use of all &kpf; shares to a value + less than this may be wise, depending on how you are using &kpf;. + + + + + + + Follow symbolic links + + + A symbolic link is a special file which is a reference to another + file (or folder) in your filesystem. By following the link, + you reach the file or folder referred to - the link is generally + transparent. + + + + By default, a &kpf; share does not allow the following of + symbolic links. This means that, for example, if you have a + share pointing to /your/home/folder/public_html + and you create a link inside public_html, pointing to + /tmp, then anyone + requesting /tmp will + see the contents of your /tmp folder. + + + + In general, it's a bad idea to allow the following of symbolic + links in this way. The main reason this is allowed is so that + you may have symbolic links inside the shared folder, which + point to another place inside the shared folder. This can + be useful if you're serving up an entire + website - which as mentioned previously, is not + the intended use of &kpf;. + + + + Just be careful not to link to anywhere on your file system that + might hold sensitive information (or have a symbolic link in it + somewhere that points to sensitive information!) + + + + + + + + + Questions and Answers + + + + + + + Why does &kpf; not include any security mechanisms? + + + + + + In truth, &kpf; does include various measures designed to help + prevent the user accidentally providing access to sensitive + information. There is no password protection and no encryption. + This is by design, as will be explained. + + + + The more security measures that are added to a service, the + safer people feel when using it. Sadly, to ensure real security, + the user needs to have a good understanding of the issues involved. + For example, providing password protection is no use if the user + does not know how to pick a good password. Therefore the decision + was made to provide zero security, in the hope that the user will + find it easier to understand what this means than to spend months + or years learning about the complexities of network security. + + + + The concept is simple. If you specify that a folder is + shared, it's shared to the world. If you don't want to make + it available to the world, don't share it. + + + + + + + + + + + + + Credits and License + + + &kpf; + + + + Program copyright 2002 &Rik.Hemsley; &Rik.Hemsley.mail; + + + + Documentation copyright 2002 by &Rik.Hemsley; &Rik.Hemsley.mail; + + + + &underFDL; + + + &kpf; is released under the MIT license. + + + + + + + Installation + + + + How to obtain &kpf; + + &install.intro.documentation; + + + + + + &documentation.index; + +
+ + + + diff --git a/doc/kppp/Makefile.am b/doc/kppp/Makefile.am new file mode 100644 index 00000000..085981d9 --- /dev/null +++ b/doc/kppp/Makefile.am @@ -0,0 +1,4 @@ + +KDE_LANG = en +KDE_DOCS = AUTO + diff --git a/doc/kppp/accounting.docbook b/doc/kppp/accounting.docbook new file mode 100644 index 00000000..ec6db43c --- /dev/null +++ b/doc/kppp/accounting.docbook @@ -0,0 +1,158 @@ + +An example template for Telephone cost accounting. + +If you can't find a rule for your region you will have to write one by +following the following template. Don't be afraid though it is really +easy. + +Don't forget to submit your newly created rules file to &kppp;'s +maintainer. The newly created rules file can be checked for valid syntax with +the +rule_file command line option to &kppp; +and must be installed in ${KDEDIR}/share/apps/kppp/Rules or in ${HOME}/.kde/share/apps/kppp/Rules before you will +be able to select it in this dialog. + + +################################################################ +# +# Disclaimer/License +# This Template ist (c) by Mario Weilguni <mweilguni@kde.org> +# It ist licensed under the same terms as the kppp package, +# which it is part of +# +################################################################ +# +# This is a sample rule set for kppp. You can use it as a +# template when you have to create your own ruleset. If you do +# so, remove all comments and add your own. This will allow +# other users to check your ruleset more easily. +# +# Please sign the the tarif file with your name an email address +# so that I can contact you if necessary. +# +# NOTE: the rules in this rule set do not make much sense and +# are only for demonstration purposes +# +# NOTE ON FILENAMES: +# when you create your own ruleset, use "_" in filename +# instead of spaces and use ".rst as extension +# i.e. "Austria city calls" +# --> file should be saved as "Austria_city_calls.rst" +# +# Thanks, Bernd Wuebben +# wuebben@math.cornell.edu / wuebben@kde.org +################################################################ + + +################################################################ +# +# NAME OF THE RULESET. This is NEEDED for accounting purposes. +# +################################################################ +name=default + +################################################################ +# currency settings +################################################################ + +# defines ATS (Austrian Schilling) to be used as currency +# symbol (not absolutely needed, default = "$") +currency_symbol=ATS + +# Define the position of the currency symbol. +# (not absolutely needed, default is "right") +currency_position=right + +# Define the number of significant digits. +# (not absolutely needed, default is "2" +currency_digits=2 + + + +################################################################ +# connection settings +################################################################ + +# NOTE: rules are applied from top to bottom - the +# LAST matching rule is the one used for the +# cost computations. + +# This is charged whenever you connect. If you don't have to +# pay per-connection, use "0" here or comment it out. +per_connection=0.0 + + +# minimum costs per per connection. If the costs of a phone +# call are less than this value, this value is used instead +minimum_costs=0.0 + + +# You pay .74 for the first 180 seconds ( 3 minutes) no matter +# whether you are connected for 1 second or 180 seconds. +# This rule will take priority during the first 180 seconds +# over any other rule, in particular the 'default' rule. +# have a look at costgraphs.gif in the docs directory +# of the kppp distribution for a graphic illustration. +flat_init_costs=(0.74,180) + +# This is the default rule which is used when no other rule +# applies. The first component "0.1" is the price of one +# "unit", while "72" is the duration in seconds. +# Therefore the following rule means: "Every 72 seconds 0.1 +# ATS are added to the bill" +default=(0.1, 72) + +# +# more complicated rules: +# + +# "on monday until sunday from 12:00 am until 11:59 pm the costs +# are 0.2 each 72 seconds" +on () between () use (0.2, 2) + +# same as above +on (monday..sunday) between () use (0.2, 2) + +# same as above. You must use 24 hour notation, or the accounting +# will not work correctly. (Example: write 15:00 for 3 pm) +on (monday..sunday) between (0:00..23:59) use (0.2, 2) + +# applies on friday, saturday, sunday and monday 8am until 1pm +on (friday..monday) between (8:00..13:00) use(0.3,72) + +# ATTENTION: +on(monday..friday) between (21:00..5:00) use (0.4,2) +# does NOT include saturday 0:00-5:00, just monday..friday, as it says. + +# applies on a given date (christmas) +on (12/25) between () use (0.3,72) + +# a range of dates and one weekday +on (12/25..12/27, 12/31, 07/04, monday) between () use (0.4, 72) + +# use this for easter +on (easter) between () use (0.3,72) + +# easter + 50 days (Pfingstmontag/ Pentecost Monday ) +on (easter+50) between () use (0.3,72) + +on (thursday) between (20:00..21:52) use (8.2, 1) + + +# The "on()" rules above all relates to current time only. You can also +# make a rule depend on the number of seconds you have been connected +# by specifying this time as a third argument to "use()". +# For instance, let's say normal rate in the evening is 0.20 per minute, +# and it drops by 20% after one hour of connect time. This can be modelled +# like: + +on () between (19:30..08:00) use (0.20, 60) +on () between (19:30..08:00) use (0.16, 60, 3600) + +# Note that these rules, just like other rules, are sensitive to the +# order in which they appear. + + + diff --git a/doc/kppp/callback.docbook b/doc/kppp/callback.docbook new file mode 100644 index 00000000..93f79238 --- /dev/null +++ b/doc/kppp/callback.docbook @@ -0,0 +1,268 @@ + +Configuring &kppp; for callback + +This chapter is based on material provided by Martin Häfner, +mh@ap-dec717c.physik.uni-karlsruhe.de + + +&UNIX; or &Linux; callback server + +This section introduces &UNIX; (&Linux;) callback, and how &kppp; can be +configured to connect to a &UNIX; callback server, especially to a script based +&Linux; callback server + + +An Introduction to callback + +There are several reasons to consider using callback. Some of these are: + + + +To increase the security of your local network + + +To reduce expenses of external co-workers + + +To control telephone costs where calls are claimed as business +expenses + + + +Think about someone calling the number of your dial in server, and then +cracking a password. Why bother to maintain a firewall for your internet +connection, if access to your network is that easy?. + +Callback software generally asks for your name, and then hangs up the +line. It then calls you back, usually at a number that is stored +on the server in a database. The client then picks up the +phone line and continues with the dial-in as if nothing had happened. The +server now requests your username and password, knowing that you are who you +said you were when you first dialled in, or at the least, you are where you said +you were. The connection is established normally, and the +pppd is started. + +Now the big question is, how to tell the client to pick up the phone, when +the server calls you back. Do you need a special program, such as +mgetty? The answer is, no, you +don't need a special client program. In general, any client can be used for +callback connections, you could even use an ordinary terminal program such as +minicom to connect. + +The only thing you have to do is tell your modem to +AutoAnswer the phone when a +RING is detected by the modem. This is done +with the following modem command: + + +AT&SO=1 + + +This tells the modem to pick the phone up after one +RING. + +Like a lot of other client programs, &kppp; checks to see if the +connection is closed by the server, and then stops the current session if a +NO CARRIER is detected. This, then, is the +real problem when setting up callback. NO +CARRIER will of course be detected the moment the callback +server hangs up the line. Some servers therefore use a special login program. +So how do you solve this problem? You tell your modem to show +CARRIER UP at all times (which causes no +problems if you tell the client to hang up the line.) You can do this with the +following modem command: + + +AT&C0 + + +If you want to test this, you can first use an ordinary terminal program +such as minicom, and call your callback server, to +see what hapens. + + + + +The &kppp; setup + +So, now that you've seen the theory in action, how do you go about setting +up &kppp; to handle the connection? + +The procedure is quite straightforward, as follows. + + + +First tell the modem to accept connections, and to not stop the +negotiation when the callback server hangs up the line for the first time. You +can add both these options in the Modem tab of the &kppp; +configuration, by adding to the option Dial String the +string AT&C0S0=1DT +There are no other changes with configuration for &kppp;. If you meet +trouble with modem init and reset, check the Troubleshooting section for more +information. + + +Think about your server for a moment. Remember that &UNIX;, &Windows; and +Macintosh operating systems have differing opinions about how to end a line in a +text file, and therefore, in login procedures too. If you are connecting to a +&Windows; server, use CR/LF, if you are connecting to a +&UNIX; server, use CR, and if you are connecting to a +Macintosh server, use LF + + + + +We are assuming for these instructions that you are calling a &Linux; +callback package which uses ordinary login (not PAP or +such). +Set the Authentication style in the +Dial tab of the account configuration to +Script-based + + +Now you have to build the login script. Editing of login scripts is one +of the very cool features of &kppp; You can find it in the Login +Script tab of the Edit Account dialog. + +In this example, the user userxyz needs the +following script to be called. The callback server already knows the table of +names and their applicable phone numbers, so you select the phone number to be +used with an alias, for security purposes. + +For each line, choose the criteria from the drop down list on the left of +the dialog, and type in the action in the text box on it's right. Choose the +Add to add each line to the script. You can use +Insert to add a line into the middle of the script, and +Remove to delete a line if you made a mistake. + +The entire script should look something like this (without the comments, +shown here starting with a #) + + +Expect ogin: # remember, we do ordinary terminal login +ID "" # kppp sends the id you configured in +the main dialog +Expect for userxyz: # a list of available numbers is +shown, the user should choose one +Send userxyz-home # the user wants to be called back +on their home number +Expect ogin: # The callback process is now +running, a new connection, and so a new login. +ID +Expect assword: # Now send your password +Expect > # Wait for the command prompt (the +prompt may vary) +Send start_ppp # this command starts the pppd + + +After waiting for the login request, the user sends his ID and waits for a +list of available phone numbers for that username. Then he tells the server +which of the numbers offered he would like to be called back on. &kppp; can +open a dialog for this, if your location changes often, ⪚ you are a sales +representative and move from hotel to hotel. Now the server is expecting login +and password for authentication, but in the meantime, the server hangs up and +calls the user back. The authentication information is sent, and &kppp; waits +for a command prompt, and then starts a small script (here called +start_ppp which fires up pppd on +the server. + +The start_ppp script might look something like the +following: + + +#!/bin/sh +stty -echo +exec /usr/sbin/pppd -detach silent modem + + +Of course, setting up a PPP server is not within the +scope of this document. For detailed information, see the +pppd man pages. An excellent description of a +callback server can be found at +http://ap-dec717c.physik.uni-karlsruhe.de/~mh/callback + + + +All other configuration issues, such as pppd +configuration or IP settings work as normal, and no special +software is required to pick up the line. + + +&kppp; callback and other programs such as +mgetty or any other faxgetty can be run on the same +serial port. There are no problems with the dial in, as &kppp; creates a lock +file which will tell the getty program that another application (in this case, +&kppp; of course,) is using the line at that time. + + + + + +Troubleshooting + +There are some known problems with &kppp; in callback mode: + + + +As you initialize the modem to auto answer, you need to reset the modem +after your connection is closed. Otherwise, your modem will continue to pick up +the line for you, which is not a good idea if the line in question is your main +phone line. + + +&kppp; has some small problems when sharing a line with another program, +such as mgetty. If mgetty +is running on the same modem line, &kppp; is not able to initialize the modem +correctly. + + + +&kppp; is unable to prompt for certain user input during a scripting based +login. Unfortunately, when using the example script above, &kppp; also asks for +the user name the second time the callback server requests it. You can get rid +of this by hardcoding your userid into the login script (not very portable or +nice, but it works. + + + + + + +Internet Resources for server software + +&Linux; callback server software bundles are available in many +places. + +The well known mgetty is a very powerful +program, and is also able to handle callback connections. A description of how +to set up mgetty for this purpose is maintained at + +http://www.dyer.demon.co.uk/slug/tipscrip.htm, by Colin McKinnon, +colin@wew.co.uk. + +There is also a ready to use package for &Linux; at +http://www.icce.rug.nl/docs/programs/callback/callback.html. This +package is maintained by Frank B. Brokken, frank@icce.rug.nl. As +the setup, although straightforward, is not very easy, I have written a short +introduction for it at http://ap-dec717c.physik.uni-karlsruhe.de/~mh/callback/, +which also contains a more general introduction to callback. + + + + + +&Windows; NT <acronym>RAS</acronym> callback + +&Windows; NT uses a completely different approach than the one described +above. NT requires an extension to the PPP protocol itself, +called CBCP (Call Back Control Protocol). +pppd has support for this protocol, but you must +recompile pppd. If anybody has experience with +successfully connecting to an NT callback server, please let us know. + + + diff --git a/doc/kppp/chap.docbook b/doc/kppp/chap.docbook new file mode 100644 index 00000000..ebbdd3b9 --- /dev/null +++ b/doc/kppp/chap.docbook @@ -0,0 +1,191 @@ + +<acronym>PAP</acronym> and <acronym>CHAP</acronym> + +Starting with version 0.9.1, &kppp; has supported directly the most +commonly used form of PAP authentication. + + +<acronym>PAP</acronym> with &kppp; + +There are two different ways to use PAP. + + +Client side authentication + +This variant is used by many commercial ISP's. It +basically means that you (or rather, your computer) must authenticate yourself +to the ISP's PPP server. The +PPP server does not need to authenticate itself to your +computer. This is no security issue, as you should know which computer you just +tried to dial to. + +If your ISP gives you a username and password, and +tells you to use PAP authentication, this is the variant you +should choose. + + + + +Two way authentication + +As above, but in this case your computer requires the +ISP PPP server to authenticate itself. In +order to establish a connection, you must chose the authentication method +Script based, not PAP, and you will +have to manually edit /etc/ppp/pap-secrets. While &kppp; +doesn't provide built in support for this variant, it is nevertheless easy to +establish a connection. + + + + +Preparing &kppp; for <acronym>PAP</acronym> + + + +Make sure that the file /etc/ppp/options (and +˜/.ppprc if it exists) do not +contain one of the following arguments: + + + + + + + + + + + + + + + + + + + + + + + + + +It is very unlikely that any of these options are already there, but just +to be sure, please check. + + +Start &kppp; + + +Click Setup + + +Choose the account you want to use PAP with and click +Edit + + +Choose the Dial tab + + +Select PAP in the Authentication +drop down box. + + +If you do not want to retype the password each time you dial in, select +Store password. This will save the password to a file, so +make sure that nobody else has access to your account. + + +That's it. Close the dialogs, type in the username and password your +ISP supplied, and click +Connect. + + + + + + + + + +An alternative method of using <acronym>PAP</acronym> and +<acronym>CHAP</acronym> with &kppp; + +This section is based on an email from Keith Brown +kbrown@pdq.net and explains how to make &kppp; work with a +generic PAP or CHAP account. If your +ISP just gave you a user id and a password for an account, +you probably can skip this section, and the instructions in the previous one +will be all you need. + +PAP seems a lot more complicated at first glance than +it really is. The server (the machine you are connecting to) basically tells +the client (your machine) to authenticate using PAP. The +client (pppd) looks in a specific file for an entry +that contains a matching server name, and a client name for this connection, and +then sends the password it finds there. That's about it! + +Now here's how to make that happen. I am assuming a +pppd version of 2.2.x or better and a standard installation +of configuration files under /etc/ppp. + +For the purposes of illustration, imagine that you have an internet +account with glob.net with the username +userbaz and the password +foobar + +First, you need to add all this to a file called +/etc/ppp/pap-secrets. The format of an entry for our +purposes is: + +USERNAME SERVERNAME PASSWORD + +So you would add the following line to +/etc/ppp/pap-secrets and then save it : + +userbaz glob foobar + + +You can use any name for the server you wish, so long as you use the +same name in the pppd arguments, as you'll see +shortly. Here it's been shortened to glob, but this name +is only used to locate the correct password. + + +Next you need to set up the connection in &kppp;. The basics are the same +as any other connection, so we won't go into details here, except to say that +you probably want to make sure that /etc/ppp/options is +empty, and you don't want to create a login script either. + +In the &kppp; settings dialog, at the bottom of the +Dial tab, is a pppd arguments +button. This brings up an editing dialog. Here you can enter values that will +be sent to pppd as command line arguments, and in the +case of multiple value arguments, you need to enter each value as a separate +entry in the listbox, in the correct order. + +You can put in any other arguments you want first. Then add the arguments +that pppd uses to handle PAP +authentication. In this example, we are going to add +user, userbaz, +remotename and glob in that +order. + +The tells the pppd what +user name to look for in the pap-secrets file and then to +send to the server. The remotename is used by pppd +to match the entry in the pap-secrets file, so again, it +can be anything you want so long as it is consistent with the entry in the +pap-secrets file. + +That's all there is to it, and you should now be able to set up your own +connection to a server with PAP authentication. +CHAP is not much different. You can see the &Linux; Network +Administrators Guide for a chap-secrets file format, and +the pppd arguments used, and the rest should be +simple. + + + diff --git a/doc/kppp/costsgraphs.fig b/doc/kppp/costsgraphs.fig new file mode 100644 index 00000000..0c1f5d66 --- /dev/null +++ b/doc/kppp/costsgraphs.fig @@ -0,0 +1,55 @@ +#FIG 3.2 +Landscape +Center +Inches +Letter +100.00 +Single +0 +1200 2 +2 2 0 1 0 7 0 0 -1 0.000 0 0 -1 0 0 2 + 900 900 900 900 +2 2 0 1 0 7 0 0 -1 4.000 0 0 -1 0 0 5 + 900 900 6000 900 6000 3900 900 3900 900 900 +2 1 0 1 0 7 0 0 -1 4.000 0 0 -1 0 0 2 + 900 2700 2400 2700 +2 1 1 1 0 7 0 0 -1 4.000 0 0 -1 0 0 2 + 900 3900 2400 2700 +2 1 0 1 0 7 0 0 -1 4.000 0 0 -1 0 0 2 + 2400 2700 4800 900 +2 2 0 1 0 7 0 0 -1 4.000 0 0 -1 0 0 5 + 900 4500 6000 4500 6000 7500 900 7500 900 4500 +2 1 0 1 0 7 0 0 -1 4.000 0 0 -1 0 0 2 + 900 6300 2700 4500 +2 2 0 1 0 7 0 0 -1 4.000 0 0 -1 0 0 5 + 6900 900 12000 900 12000 3900 6900 3900 6900 900 +2 1 0 1 0 7 0 0 -1 4.000 0 0 -1 0 0 3 + 6900 3000 9600 3000 10800 900 +2 1 1 1 0 7 0 0 -1 4.000 0 0 -1 0 0 2 + 9600 3000 9075 3900 +2 1 0 1 0 7 0 0 -1 0.000 0 0 -1 0 1 2 + 2 1 1.00 60.00 120.00 + 9150 4050 9675 4275 +4 0 0 0 0 0 12 0.0000 4 180 4740 7125 5475 These graphs illustrate, why the new keyword 'flat_init_costs'\001 +4 0 0 0 0 0 12 0.0000 4 180 4830 7125 6150 minimum_cost and pre_connection alone. The situation depicted\001 +4 0 0 0 0 0 12 0.0000 4 180 165 750 525 a)\001 +4 0 0 0 0 0 12 0.0000 4 180 165 6975 600 c)\001 +4 0 0 0 0 0 12 0.0000 4 180 165 600 4350 b)\001 +4 0 0 0 0 0 12 0.0000 4 180 2055 8625 4500 Note: This is not the origin!\001 +4 0 0 0 0 0 12 0.0000 4 135 330 11400 4200 time\001 +4 0 0 0 0 0 12 0.0000 4 135 330 5325 4125 time\001 +4 0 0 0 0 0 12 0.0000 4 135 330 5475 7725 time\001 +4 0 0 0 0 0 12 0.0000 4 105 330 6300 975 cost\001 +4 0 0 0 0 0 12 0.0000 4 105 330 300 975 cost\001 +4 0 0 0 0 0 12 0.0000 4 105 330 300 4650 cost\001 +4 0 0 0 0 0 12 0.0000 4 135 1215 10650 7500 Bernd Wuebben\001 +4 0 0 0 0 0 12 0.0000 4 180 1395 10650 7725 wuebben@kde.org\001 +4 0 0 0 0 0 12 0.0000 4 180 4875 7125 6675 c) could also not be generated with 'rules' since rules depend on\001 +4 0 0 0 0 0 12 0.0000 4 180 4860 7125 6900 'absolut times 'such as '3:00pm', not on 'relative times' such as\001 +4 0 0 0 0 0 12 0.0000 4 180 2925 7125 7125 'the first 3 minutes' during connection.\001 +4 0 0 0 0 0 12 0.0000 4 180 1560 2550 675 minimum_cost graph\001 +4 0 0 0 0 0 12 0.0000 4 180 1620 2775 4275 per_connection graph\001 +4 0 0 0 0 0 12 0.0000 4 180 1560 8850 675 flat_init_costs graph\001 +4 0 0 0 0 0 12 0.0000 4 180 4920 7125 5700 was necessry. A cost graph such as the one show above, labeled\001 +4 0 0 0 0 0 12 0.0000 4 180 4425 7125 5925 'flat_init_cost graph' could not be generated with the rules\001 +4 0 0 0 0 0 12 0.0000 4 180 4455 7125 6375 in the graph c) above is the one encounted in France today.\001 diff --git a/doc/kppp/costsgraphs.png b/doc/kppp/costsgraphs.png new file mode 100644 index 00000000..2e5d88fd Binary files /dev/null and b/doc/kppp/costsgraphs.png differ diff --git a/doc/kppp/dialog-setup.docbook b/doc/kppp/dialog-setup.docbook new file mode 100644 index 00000000..ce76ac49 --- /dev/null +++ b/doc/kppp/dialog-setup.docbook @@ -0,0 +1,765 @@ + +Setting up a connection with the dialogs + +Setting up a connection with the dialog based setup is not too much more +difficult than using the wizard. + +You can reach the setup dialog the same way you did the wizard. Start +&kppp; from your K menu, where you will find its entry in the +Internet as Internet +Dialer. + +The following dialog will appear: + + +The &kppp; dialer startup screen + + + + +The &kppp; dialer startup screen + +The &kppp; dialer startup screen + + + +It will probably not have any entries to begin with, and that's what we're +about to do now. + +Click the Setup button to begin setting up a new +Internet connection. + +This time, choose Dialog setup and you'll see the +following Dialog appear: + + +The New Account Dialog + + + + + +The New Account Dialog + + +The New Account Dialog + + + + + + +The New Account dialog contains the following +sections: + + + +Dial + + +IP + + +Gateway + + +DNS + + +Login +Script + + +Execute + + +Accounting + + + +You normally won't need to fill in all these, although each of them is +described in the following sections. + + +The <guilabel>Dial</guilabel> tab + + +The Accounts Dial tab + + + + + +The Accounts Dial tab + +The Accounts Dial tab + + + + +The Dial tab has the following options: + + + +Connection Name: + +You must give the account a name. This can be anything you like, but if +you have more than one account, each name must be unique. + + + +Phone Number: + +Specify the phone number to dial. You can use characters such as +- to make the number more legible. If you concatenate a series +of numbers separated by a colon (⪚ +1111111:2222222:3333333, &kppp; will try these numbers one +after the other whenever it receives a busy signal. You can use the +Add button to add another number, +Remove to remove a number from the list, and the +up and down arrows to change the order of +the list. + + + +Authentication + +Choose the appropriate method of authentication that &kppp; should use to +log into the server. Check with your provider for more information. Use of +PAP and CHAP are described in the chapter +. + + + +Store password + +Check this option if you want &kppp; to remember your password between +sessions. + + + +Customize pppd arguments... + +This will bring up the pppd arguments dialog. +You can use this dialog to add any desired options that you want &kppp; to hand +to pppd. See the pppd man +page for a list of available options, but unless you know exactly what you are +doing, you should probably restrain yourself from tinkering with these. + + + + + + + +The <guilabel>IP</guilabel> tab + + +The Accounts IP tab + + + + + +The Accounts IP tab + +The Accounts IP tab + + + + + + +Dynamic IP Address + +Check this if your ISP uses dynamic +IP address assignment. In this case, your +IP address will change every time you establish a +connection. + + + +Static IP Address + +Check this if your ISP has given you a static +IP address. In that case you will also need to fill in that +address in the IP Address box, and any Subnet +Mask if applicable. Ask your ISP if +unsure. Dynamically assigned addresses are used in the huge majority if +ISP's and leaving this checked will in most cases be the +right choice. + + + +Auto-configure hostname from this IP + +Select this option if you want &kppp; to set the hostname and domain for +your machine after a successful ppp connection. +This is done by querying the defined Domain Name Server with the +IP assigned for the ppp link. +This option is useful for those stand-alone machines which want to use +protocols such as talk, which require the hostname to be the same as your +machine is known on the internet. It overrides the Domain +Name option in the DNS section, and the machine +defaults are restored to their original values when you close the +ppp connection. +This option is not useful if you just want to connect +to the internet and surf, check mail, or chat. It has the side-effect of +disallowing any new connections to your X server - in other words, you can't +open any more GUI programs. +Only turn this on if you are absolutely sure you need it. + + + + + + + +The <guilabel>Gateway</guilabel> tab + + +The Accounts Gateway tab + + + + + +The Accounts Gateway tab + +The Accounts Gateway tab + + + + + + +Default Gateway + +Check this if you want pppd to use the default +Gateway for your machine. This is the default. + + + +Static Gateway + +Check this if you want to specify the Gateway to be used in place of the +default. + + + +Assign the Default Route to this Gateway + +You almost certainly will need this to be checked (the default). + + + + + + + +The <guilabel>DNS</guilabel> tab + + +The Accounts DNS tab + + + + + +The Accounts DNS tab + +The Accounts DNS tab + + + + + + +Domain Name: + +Specify the domain name for your machine. As with DNS +addresses, it is restored to the original specified in +/etc/resolv.conf when the connection goes down. If it is +left blank, no changes are made to the domain name specified in +/etc/resolv.conf + + + +Configuration: + +Choose between Automatic (the ISP +will automatically issue you DNS server addresses when you +connect) and Manual. If you choose manual, the +DNS IP Address section is then enabled. + + + +DNS IP Address + +This section is only enabled if you chose Manual in +the previous option. Add the Domain Name Servers assigned to you by your +ISP. You must specify at least one Domain Name Server for +your OS to be able to resolve human readable +IP addresses such as +ftp.kde.org. The DNS server +addresses supplied must be in numeric form, ⪚ +128.231.231.233. These addresses will be added at +runtime to /etc/resolv.conf. +Choose the Add button to add each new +DNS server address to the list box below. Choose +Remove to remove an entry from the list. + + + +Disable existing DNS Servers during Connection + +If you check this box, any DNS servers listed in +/etc/resolv.conf will be disabled while the connection +remains up. + + + + + + + +The <guilabel>Login Script</guilabel> tab + + +The Accounts Login Script tab + + + + + +The Accounts Login Script tab + +The Accounts Login Script tab + + + + +Use this dialog to compose a dial in script for your +ISP dialup connection. You can use the mini-terminal and the +information supplied by your ISP to find out what sequence of +actions needs to be executed. + +Choose an option from the drop down box on the left, and then add any +parameters for that action in the edit box on the right. Use +Add to add each entry to the bottom +of the script, which is displayed in the lower part of the dialog. Use +Insert to insert an entry anywhere in the script, and use +Remove to delete a line from the script. + +The options available are: + + + +Expect + +&kppp; will wait for the specified string to be received. + + + +Send + +&kppp; will send the specified string. + + + +Scan + +&kppp; will scan the input stream for the specified string, and will +store any character from the end of the string up to the next newline, in an +internal buffer. Trailing and leading whitespace will be stripped off. + + + +Save + +Permanently store the previously scanned string in the specified register. +Currently the only valid register is password. + + + +Pause + +Pause for the specified number of seconds. + + + +Hangup + +&kppp; will send the hangup to the modem. + + + +Answer + +&kppp; will set the modem into answer mode. + + + +Timeout + +Change the default timeout to the specified number of seconds dynamically +during the script. You can change the timeout several times during script +execution if necessary. + + + +Prompt + +Prompt the &kppp; user to enter a string, given the specified string as a +hint. The user will see what is typed. If the specified string includes the +mark ##, the mark will be replaced with the current +content of the internal scan buffer, as previously stored with the +scan command. + + + +PWPrompt + +Prompt the &kppp; user to enter a string, given the specified string as a +hint. An asterisk will be printed for each character the user types. + + + +ID + +If the Login ID field on &kppp;'s main dialog is filled in, +send that ID. If the Login ID field is not +filled in, prompt the &kppp; user to enter an ID, given the +specified string as a hint. The user will see what is typed. On a second pass, +such as in a loop on a second iteration, or during callback authentication, the +prompt will be displayed regardless of whether the Login ID field +is filled in. + + + +Password + +If the Password field on &kppp;'s main dialog is +filled in, send that password. If the Password field is +not filled in, prompt the &kppp; user to enter a password, with the specified +string as a hint. An asterisk will be printed for each character typed. On a +second pass, such as in a loop on a second iteration, or during callback +authentication, the prompt will be displayed regardless of whether the +Password field is filled in. + + + +LoopStart + +&kppp; will wait for the specified string to be received. It will save +the string for use by LoopEnd. + + + +LoopEnd + +&kppp; will wait for the specified string to be received to exit the loop. +If the string given by the corresponding LoopStart is +received first, it will trigger a jump to the line after +LoopStart, enabling repetition of username/password style +paired dialogs. + + + + + +Example Scripts + + +A simple example login script +Here is a simple example script I could use to connect to my +ISP + + + Expect ID: # wait for ID: + Send myid # you have to substitute myid with your id + Expect word: # wait for 'password' + Send 4u3fjkl # send my password '4u3fjkl' + Expect granted # My ISP send 'Permission granted' on login success. + Send ppp # This starts a ppp connection for + # me on the ISP side. + + + + + +A login script that prompts for ID and password, and has loops. + +Here is a script for the same account with an ID and +password prompt. This script will prompt for ID and password +each time, no matter what is typed into the Login ID and +password fields on &kppp;'s main screen. + +This script also illustrates the use of the LoopStart/LoopEnd structure. +If something goes wrong during the login procedure, for example, I mistype the +password, my ISP will print an error message and restart the +id/password loop by issuing the string ID: +again. If the string ID: is caught before the +LoopEnd keyword was parsed, &kppp; will start the script again, from the line +after the LoopStart keyword. + + + LoopStart ID: # wait for ID: + Prompt Enter ID: # Prompt me for my ID and send it off. + Expect word: # wait for 'password' + PWPrompt Enter Password: # Prompt me for my password and send it off. + LoopEnd granted # My ISP send 'Permission granted' on login success. + Send ppp # This starts a ppp connection for me + + + + +Prompts for information not filled in on the main dialog. + +Here is the script that I actually use to connect to my +ISP. This script will prompt for ID and +password only if I haven't filled in the respective fields on &kppp;'s main +dialog. + + + LoopStart ID: # wait for ID: + ID Enter ID: # Prompt me for my ID and send it off. + Expect word: # wait for 'password' + Password Enter Password # Prompt me for my password and send it off. + LoopEnd granted # My ISP send 'Permission granted' on login success. + Send ppp # This starts a ppp connection for me + # on the ISP side + + + + + +A script for an <acronym>ISP</acronym> using challenge/response +authentication. + +Here is a script that I use to connect to an ISP which +is using some sort of challenge/response authentication. Usually you got a +hardware token (a smart card with a display and calculator like keypad) from the +ISP. You have to know a password to use the token. After +dialing in your ISP displays your challenge. You have to +type in the challenge to your token and get a dynamic password as a +response. Then you have to enter that password. + + + LoopStart ID: # wait for ID: + ID Enter ID: # Prompt me for my ID and send it off. + Scan Challenge: # Scan for 'Challenge' and store everything behind up to the next newline. + Expect Password: # wait for 'password' + Prompt Your token is ## - Enter Password # Prompt me for my password and send it off. + LoopEnd granted # My ISP sends 'Permission granted' on login success. + Send ppp # This starts a ppp connection for me + # on the ISP side + + + + +Using Scan and Save in scripts + +The following log shows the login procedure of a fictitious +ISP that provides a new password on each login. The new +password has to be verified and recorded for the next session. + + University of Lummerland + + Login:mylogin + Password: + The password for your next session is: YLeLfkZb + Please record and enter it for verification. + Verification:YLeLfkZb + + 1 = telnet + 2 = SLIP + 3 = PPP + + Your choice: + + +&kppp; can be used to this cumbersome task for you, eliminating the risk +of losing that little sheet of paper that holds your current password at the +same time. The key part of the following script is the combination of Scan/Save +keywords. + + +7 Expect Login: # wait for login prompt + ID # send ID + Expect Password: # wait for password prompt + Password # send password + Scan is: # wait for '... next session is:' and + # scan the preceding password + Save password # save the new password for next login + Expect Verification: # wait for 'Verification:' + Password # send new password + Expect choice: # wait for a prompt that let's you choose + # between different options (telnet, SLIP, PPP) + Send 3 # choose option 3, i.e. PPP + + + + + + + + +The <guilabel>Execute</guilabel> tab + + +The Accounts Execute tab + + + + + +The Accounts Execute tab + +The Accounts Execute tab + + + + +Here you can select commands to run at certain stages of the connection. +These commands are run with your real user id, so you cannot run any commands +here requiring root permissions, unless you are of course dialled in as root (a +bad thing to do for many reasons!) + +Make sure to supply the whole path to the program, otherwise &kppp; may +not be able to find it. + +You can add commands to be run at four distinct times during the +connection process: + + + +Before Connect + +Run this command before the dialing is initiated, so it is already +running when you connect to your ISP. + + + +Upon Connect + +Run this command only after a successful connection is +made. + + + +Before disconnect + +Run this command while still connected, before hanging up the +modem. + + + +Upon disconnect + +Run this command after the connection has been closed. + + + + +You might for example want to run leafnode as +soon as you have connected, or check your mail. You might want to make sure any +mail in your queue is sent, before you close your connection down. You might +want a clean-up script to tidy up logs and clear your cache after +you have disconnected. + + + + +The <guilabel>Accounting</guilabel> tab + + +The Accounts Accounting tab + + + + + +The Accounts Accounting tab + +The Accounts Accounting tab + + + + +Check the Enable Accounting box to enable or disable +telephone cost accounting for this account. + +Select from the list the applicable rule for your telecoms +provider. + +If you can't find one, you can write one yourself by copying the supplied +template, which you will find in an appendix. + +The final option on this page is Volume Accounting, +described below. + + +Volume Accounting + + +What is volume accounting? + +Basically, it means to count the number of bytes transmitted to and from +the Internet. &kppp; can count incoming bytes, outgoing bytes, or both. It's +up to you what you want (or must) use. + + + + +Why should I use volume accounting? + +Many Internet Service Providers bill their customers based on the number +of bytes transferred. Even more commonly, ISP's offer a flat +rate up to some arbitrary transfer limit, and then charge more for every +megabyte above this limit. &kppp; shows you your current volume and can help +you keep your bills to the minimum. Of course, even if you're not billed based +on volume, you can turn on volume accounting just to satisfy your own +curiosity. + + + + +What type of volume accounting should I select? + +That depends mainly on your provider. Many of them only count how many +megabytes you download from the Internet,and ignore how much you send. In that +case you should choose Bytes In. If you have to pay for +both, you should choose Bytes In and Out. Bytes +Out is really only here for completeness, as we're not aware of any +providers using it as a billing basis. It might be useful to those of you +running a web or &FTP; server at home though. + + + + +Drawbacks + +Unfortunately, there is a drawback on volume accounting. &kppp; will only +count the number of bytes, regardless of their origin. Many providers set their +limit only for Internet access, and not for data on their own network. Some +providers set different limits for data that is on their own network, in the +same country, and coming from overseas. So, if you're doing not much +websurfing, and getting most of your pages from your ISP's +own proxy cache, then your provider is probably not charging you for that data. +&kppp; will not know these IP packets are coming from the +proxy, and so it will count them. So if you this situation applies to you, or, +as another example, your provider uses a caching news server such as +nntpcached, then the volume reported by &kppp; may be +higher than the amount you are going to be billed for. On the bright side, at +least &kppp; will never underestimate your bills. + + + + + + + + diff --git a/doc/kppp/getting-online.docbook b/doc/kppp/getting-online.docbook new file mode 100644 index 00000000..97d2ba66 --- /dev/null +++ b/doc/kppp/getting-online.docbook @@ -0,0 +1,52 @@ + +Getting online the easy way + + +A few things you should have ready before you start + +If you have a fairly modern &Linux; distribution, you might find the rest +of this document superfluous. &kppp; comes with a clever little wizard that in +many cases can have you up and running with an internet connection in just a few +minutes. + +Whether using the wizard or not, you should know the following information +before you begin: + + +Your ISP modem pool phone +number. +Your username and password for your +ISP. +Your ISP's DNS servers +(one is sufficient, but two is better). + + +Other optional information you should find out to fully access your +ISP's services are: + + +The incoming mail server address (often pop.yourisp.com or mail.yourisp.com)Also find out if +your ISP uses the POP3 protocol or IMAP. +The outgoing (SMTP) mail server address (it +could be the same as the incoming mail server, or it is often called something +like smtp.yourisp.com). +The Usenet News (NNTP) server address (possibly +news.yourisp.com or nntp.yourisp.com). +Any proxy servers your ISP has set +up. + + +All this information is probably available on any paperwork you received +from your ISP when you signed up with them, or you can find +it out from your ISP's support telephone line. + +Armed with the above, and a fairly recent default installation of &Linux;, +you may well find that setting up an internet connection is as simple as running +the &kppp; wizard. + + + diff --git a/doc/kppp/global-settings.docbook b/doc/kppp/global-settings.docbook new file mode 100644 index 00000000..a11cc8d4 --- /dev/null +++ b/doc/kppp/global-settings.docbook @@ -0,0 +1,385 @@ + +Global &kppp; settings + +The changes made here affect all accounts you have set up in &kppp; + + +The <guilabel>Accounts</guilabel> tab + + +The Accounts tab + + + + + +The Accounts tab + +The Accounts tab + + + + +In this dialog, you can manage the accounts themselves. The names of the +accounts appear in a list on the left of the dialog. + +To delete an account, select the Delete button. +You will be asked to confirm before the account is finally deleted. + +You can make a copy of an account with the Copy +button. You could use this for example, to separate different users in the +family, although that would normally be better done by having them be different +users in the OS as well! Or perhaps you just have more than one account with +the same ISP and wish to use both of them. + +Choosing Edit... will take you to the dialog +described in Dialog Setup, but with the +selected accounts details. + +Choosing New... will offer you the choice between +the Wizard or the Dialog Setup already described. + +If you select an account, and you have turned on accounting then the accumulated information +for that account will appear in the two panels labelled Phone +Costs: and Volume: respectively. + +To the left of the accounting display, are two buttons: +Reset... and View Logs. + +Pressing Reset... will reset the Phone +Costs: and Volume: information to 0. You would +typically want to do this once a month or quarter, when you have received your +phone bill and reconciled the telephone costs. You can reset either +independently, and are offered the choice of which item you want to reset, when +you press the Reset button. + +Pressing View Logs will open another window, where +a log of all the calls made with &kppp; will be displayed. If you have kept +logs, you can move forward and backward, in monthly steps. This might be useful +if you have received an abnormally large phone bill and are investigating +why! + + + + +The <guilabel>Device</guilabel> tab + + +The Device tab + + + + + +The Device tab + +The Device tab + + + + +Here you can select and configure your modem. + + + +Modem Device + +Choose the device appropriate for your hardware. + + +/dev/ttys0 + +DOS or &Windows; users will know this as COM1, while COM2 is +/dev/ttys1 and so on. These devices are +the ones normally used on &Linux; systems. + + + +/dev/cua0 + +The first serial line (COM1). COM2 is usually +/dev/cua1 and so on. These devices are commonly used on +BSD systems, namely FreeBSD, NetBSD and OpenBSD. Older &Linux; systems may also +have these, although on &Linux; they were renamed some time ago to /dev/ttySx. + + + +/dev/ttyI0 + +On &Linux; these belong to internal ISDN cards. These +devices emulate a common Hayes compatible modem. +/dev/ttyI0 is for the first, +/dev/ttyI1 is for the second +ISDN card and so on. These devices are only available in the +&Linux; version. + + + +/dev/modem + +Many &Linux; distributions make a symbolic link from the real modem device +to /dev/modem. You should avoid +using this one.. Use the real device that it is pointing to +instead. + + + + + + +Flow Control + +Select from Hardware (CRTSCTS), Software (XON/XOFF) and no flow control. +The recommended setting is Hardware flow control. + + + +Line Termination + +Choose the correct Enter character sequence for your +modem. Most modems will use CR/LF, however some modems need a +different setting. If you experience trouble while running a login script, play +with this parameter. + + + +Connection Speed +Choose from the list of connection speeds supported by your +serial port. Note that the serial port supports much higher speeds than your +modem in most cases. You should probably start with the highest number +available, and only reduce it if you have connection problems. + + + +Use Lock File + +Activate this option if you want &kppp; to create a lockfile. Under +&Linux; the folder for such a file will be /var/lock. Programs such as +mgetty depend on the existence of such lock files, +and &kppp; will not work with mgetty if the lock file +is not set. Make sure that you don't use the option for +pppd if you want &kppp; to lock the modem, since the +pppd option will induce +pppd to try to lock the modem device. Since &kppp; +will have already locked the device, pppd will fail, +and &kppp; will display the error pppd died +unexpectedly. + + + +Modem Timeout + +This is the time in seconds that &kppp; will wait for the +CONNECT response from your modem. A setting of about +30 seconds should be sufficient for most purposes. + + + + + + + +The <guilabel>Modem</guilabel> tab + + +The Modem tab + + + + + +The Modem tab + +The Modem tab + + + + + + +Busy Wait + +This is the length of time the modem should wait before redialing, after +it has received a busy signal. Note there are requirements by telecom providers +in some countries, which ask you to not set this too low. + + + + +Modem volume + +Use the slider to set the modem volume. Left is low volume, center is +medium volume, and right is high volume. On some modems, low volume is the same +as turning the volume off, and on other modems, medium and high are effectively +the same thing. + + + +Modem Commands + +In this dialog you can fill in any particular commands appropriate for +your modem. If you own a Hayes compatible modem, you most likely won't need to +change any of the defaults, but you are encouraged to read the Hayes Commands Appendix in this help file. The +information supplied there can be very helpful in cases where you experience +trouble setting up a stable connection with your ISP's +modems. In particular the two settings for Pre-Init Delay +and for Post-Init Delay if you are experiencing modem +lockups. These settings make &kppp; pause a little just before and just after +sending the initialization string to your modem. The Pre-Init +Delay will by default also send a CR, unless you have set it the +delay interval to 0. + + + +Query Modem + +Pushing this button will make &kppp; ask your modem to identify itself. +On success, your modems response will be displayed in a dialog. This may or may +not prove to be informative, depending on your modem. + + + +Terminal + +Pushing the Terminal button will bring up a mini +terminal. You can use the mini terminal to test your modem and to experiment +with the negotiation protocol for initializing a ppp connection with your +ISP. You no longer need a terminal program such as +minicom or Seyon. + + + + + + + +The <guilabel>Graph</guilabel> tab + + +The Graph tab + + + + + +The Graph tab + +The graph tab + + + + +Here you can set the colors used by the &kppp; graph. You can set +different colors for Background color, Text +color, Input bytes color and Output +bytes color. + + + + +The <guilabel>Misc</guilabel> tab + + +The Misc. tab + + + + + +The Misc. tab + +The Misc. tab + + + + +Here are some options that don't really fit in with other sections, but +can be very useful nonetheless. + + + +pppd Version + +The version number of the pppd daemon on your system. + + + +pppd Timeout + +&kppp; will wait this amount of time after running the script and starting +pppd for pppd to establish +a valid ppp link before giving up and killing +pppd + + + +Dock into Panel on Connect + + +If this option is chosen, &kppp; will dock into the panel where it will be +symbolized by a small animated icon. Use the left +mouse button on this icon to restore &kppp;'s window. The +right mouse button will open a popup menu that offers +to restore the window, show transfer statistics, or close the connection. This +option overrides Minimize Window on Connect. + + + + +Automatic Redial on Disconnect + +Selectintg this will have &kppp; try to reconnect if you are +disconnected. + + + +Show Clock on Caption + +This will have &kppp; display the time connected on the caption of the +&kppp; window, while you are online. + + + +Disconnect on X-server shutdown + +Checking this will cause &kppp; to terminate the ppp +link, disconnect the modem, and terminate accounting in an orderly fashion, when +the X-server shuts down. This is useful if you are prone to forgetting you are +online, when you shut down the X-server, or if you simply don't want to worry +about manually disconnecting your session. If you don't want &kppp; to hang up +the modem on X-server exit, you should leave this checkbox empty. Beware that +if you have accounting enabled, and you leave this option turned off, you will +have an unterminated accounting entry in your logs, from each time the X-server +exits and &kppp; terminates. + + + +Quit on Disconnect + +If enabled, &kppp; will exit when you disconnect from the internet. If disabled, &kppp; will stay open after disconnection. + + + +Minimize Window on Connect + +If this option is chosen, &kppp; will be minimized after a connection is +established. The elapsed connection time will be shown in the taskbar. + + + + + + + +The <guilabel>About</guilabel> tab + +The About tab shows version, license, and author +information about &kppp;. + + + + diff --git a/doc/kppp/hayes.docbook b/doc/kppp/hayes.docbook new file mode 100644 index 00000000..a8f6db7f --- /dev/null +++ b/doc/kppp/hayes.docbook @@ -0,0 +1,927 @@ + +The Hayes Modem Command Set + +Here is a description of the Hayes Command Set. Most modems follow this +command set to large extent. If you lost your modem manual or never had one in +the first place, this reference might come in handy. I for instance finally found +out how to turn my modems speaker off: ATM0 -- Finally: +Silence! + +The modem initialization string consists of a series of commands. It +prepares the modem for communications, setting such features as dialing mode, +waits, detection of the busy signal and many other settings. Newer modem +communications programs reset the initializations string for you according to +which menu options you select, which features you enable, &etc;. + +For many years Hayes modems have been the standard. As the field of modem +manufactures has grown, most have adhered at least loosely to the Hayes +standard. The following is a partial list of the Hayes command set. (called the +AT commands). The Hayes Command Set can be divided into four +groups: + + + +Basic Command Set +A capital character followed by a digit. For example, +M1. + + +Extended Command Set +An & (ampersand) and a capital character +followed by a digit. This is an extension of the basic command set. For example, +&M1. Note that M1 is different from +&M1. + + +Proprietary Command Set +Usually started by either a backslash (\), or a +percent sign (%), these commands vary widely among modem +manufacturers. For that reason, only a few of these commands are listed +below. + + +Register Commands +Sr=n +where r is the number of the register to be changed, +and n is the new value that is +assigned. + +A register is computerese for a specific physical location +in memory. Modems have small amounts of memory onboard. This fourth set of +commands is used to enter values in a particular register (memory location). The +register will be storing a particular variable (alpha-numeric +information) which is utilized by the modem and communication software. For +example, S7=60 instructs your computer to Set register +#7 to the value 60. + + + +Although most commands are defined by a letter-number combination +(L0, L1 &etc;), the user of a zero is +optional. In this example, L0 is the same as a plain +L. Keep this in mind when reading the table +below! + +Here are some of the most important characters that may appear in the +modem initialization string. These characters normally should not be +changed. + + + +AT +Tells the modem that modem commands follow. This must begin +each line of commands. + + +Z +Resets the modem to it's default state + + +, (a comma) +makes your software pause for a second. You can use more than +one , in a row. For example, ,,,, tells +the software to pause four seconds. (The duration of the pause is governed by +the setting of register S8. + + +^M +Sends the terminating Carriage Return character to the modem. +This is a control code that most communication software translates as +Carriage Return + + + +The Basic Hayes Command Set + +In alphabetical order: + + + Basic Hayes Command Set + + + + Command + Description + Comments + + + + + A0 or A + Answer incoming call + + + + A/ + Repeat last command + Don't preface with AT. Enter usually + aborts. + + + B0 or B + Call negotiation + V32 Mode/CCITT Answer Seq. + + + B1 + Call negotiation + Bell 212A Answer Seq. + + + B2 + Call negotiation + Verbose/Quiet On Answer + + + D + Dial + Dial the following number and then handshake in originate + mode. + + P + Pulse Dial + + + T + Touch Tone Dial + + + W + Wait for the second dial tone + + + , + Pause for the time specified in register + S8 (usually 2 seconds + + + ; + Remain in command mode after dialing. + + + ! + Flash switch-hook (Hang up for a half second, as in + transferring a call. + + + L + Dial last number + + + + + E0 or E + No Echo + Will not echo commands to the computer + + + E1 + Echo + Will echo commands to the computer (so one can see what one + types) + + + H0 + Hook Status + On hook - Hang up + + + H1 + Hook status + Off hook - phone picked up + + + I0 or I + Inquiry, Information, or Interrogation + This command is very model specific. I0 + usually returns a number or code, while higher numbers often provide much + more useful information. + + + L0 or L + Speaker Loudness. Modems with volume control knobs will not have + these options. + Off or low volume + + + L1 + + Low Volume + + + L2 + + Medium Volume + + + L3 + + Loud or High Volume + + + M0 or M + Speaker off + M3 is also common, but different on many + brands + + + M1 + + Speaker on until remote carrier detected (&ie; until the other + modem is heard) + + + M2 + + Speaker is always on (data sounds are heard after CONNECT) + + + N0 or N + Handshake Speed + Handshake only at speed in S37 + + + N1 + + Handshake at highest speed larger than S37 + + + O0 or O + Return Online + See also X1 as dial tone + detection may be active. + + + O1 + + Return Online after an equalizer retrain sequence + + + Q0 or Q1 + Quiet Mode + Off - Displays result codes, user sees command responses (⪚ + OK) + + + Q1 + Quiet Mode + On - Result codes are suppressed, user does not see + responses. + + + Sn? + + Query the contents of S-register + n + + + Sn=r + Store + Store the value of r in S-register + n + + + V0 or V + Verbose + Numeric result codes + + + V1 + + English result codes (⪚ + CONNECT, + BUSY, NO + CARRIER &etc;) + + + X0 or X + Smartmodem + Hayes Smartmodem 300 compatible result codes + + + X1 + + Usually adds connection speed to basic result codes (⪚ + CONNECT 1200 + + + X2 + + Usually adds dial tone detection (preventing blind dial, and + sometimes preventing AT0) + + + X3 + + Usually adds busy signal detection + + + X4 + + Usually adds both busy signal and dial tone detection + + + Z0 or Z + Reset + Reset modem to stored configuration. Use Z0, + Z1 &etc; for multiple profiles. This is the same as + &F for factory default on modems without + NVRAM (non voltaile memory) + + + +
+
+ + + The Extended Hayes Command SetAmpersand Commands + + + The Extended Hayes Command Set + + + + Command + Description + Comments + + + + + &B0 or &B + Retrain Parameters + Disable auto retrain function + + + &B1 + Retrain Parameters + Enable auto retrain function + + + &B2 + Retrain Parameters + Enable auto retrain, but disconnect if no line improvement over + the period dictated by S7 + + + &C0 or &C1 + Carrier detect + Signal always on + + + &C1 + Carrier detect + Indicates remote carrier (usual preferred default) + + + &D0 or &D + Data Terminal Ready (DTR + Signal ignored (This is modem specific, you must see your manual + for information on this one!) + + + &D1 + Data Terminal Ready (DTR + If DTR goes from On to Off the modem goes into + command mode (Some modems only) + + + &D2 + Data Terminal Ready (DTR + Some modems hang up on DTR On to Off transition + (This is the usual preferred default) + + + &D3 + Data Terminal Ready (DTR + Hang up, reset the modem, and return to command mode upon + DTR + + + &F0 or &F + Factory defaults + Generic Hayes-compatible defaults. This is + usually a good thing to use in your init string, since the + &F1-&F3 settings can vary + among modems, and they may actually be the cause of connection + problems. (Since you never know exactly what Brand X's + &F2 really changes.On the other hand, + it pays to try out the other options below; many people's problems can be + solved by replacing a complicated init string with a simple + &F2 or the like. However, if you're building an + init string, it's best to start with a simple &F, + and not use the customized form of + defaults. + + + &F1 + Factory Defaults + Factory Defaults tailored to an IBM PC + compatible user + + + &F2 + Factory Defaults + Factory defaults for a Mac w/software handshaking + + + &F3 + Factory Defaults + Factory defaults for a Mac w/hardware handshaking + + + &G0 or &G + Guard tones + Disable guard tones + + + &K0 or &K + Local flow control + Disable local flow control + + + &K1 + Local flow control + Enable RTS/CTS hardware local flow control + + + &K2 + Local flow control + Enable XON/XOFF software local flow control + + + &K3 + Local flow control + Enable RTS/CTS hardware local flow control + + + &K4 + Local flow control + Enable XON/XOFF software local flow control + + + &L0 or &L + Dial mode + Select dial-up mode + + + &M0 or &M + Error control mode + Select asynchronous non-EC mode (the same as + &Q0) + + + &P0 or &P + Pulse dialing ratio + U.S./Canada pulse dialing 39% make / 61% break ratio + + + &P1 + Pulse dialing ratio + U.K./Hong Kong pulse dialing 33% make / 67% break ratio + + + &Q0 or &Q + Error control mode + Asynchronous non-EC more. No data + buffering. ASB disabled. + + + &Q5 + Error control mode + Select V.42 EC operation (requires flow + control) + + + &Q6 + Error control mode + Asynchronous mode with ASB (requires flow + control) + + + &Q8 + Error control mode + Select alternate EC protocol + (MNP) + + + &Q9 + Error control mode + Conditional data compression: V.42bis = yes, MNP5 = no. + + + &S0 or &S + DSR action select + Always on (default) + + + &S1 + DSR action select + Follows EIA specification (Active following + carrier tone, and until carrier is lost.) + + + &T0 or &T + Self test + Model specific self test on some modems + + + &U0 or &U + Trellis code modulation + Enable V.32 TCM + + + &U1 + Trellis code modulation + Disable V.32 TCM + + + &V0 or &V1 + View active + (and often stored) configuration profile settings (or + ATI4 + + + &W0 or &W + Store profile + In NVRAM (&W0, + &W1 etc. for multiple profiles) Some settings + cannot be stored. These often don't show on &V or + ATI4 + + + &Y0 or &Y + Select configuration loaded at power-up + Load profile 0 (default) + + + &Y1 + Select configuration loaded at power-up + Load profile 1 + + + &Zn=x + Soft reset and load stored profile number + n + Note that all items after the &Z on the + command line are ignored + + + +
+ +
+ + + Backslash and Percent Commands + + + Backslash and Percent Commands + + + + Command + Description + Comments + + + + + \A0 or \A + Character maximum MNP block size + 64 character maximum + + + \A1 + Character maximum MNP block size + 128 character maximum + + + \A2 + Character maximum MNP block size + 192 character maximum + + + \A3 + Character maximum MNP block size + 256 character maximum + + + %C0 or + %C + Data Compression Enable/Disable + Disabled + + + %C1 + Data Compression Enable/Disable + MNP5 enabled + + + %C2 + Data Compression Enable/Disable + V.42bis (BTLZ) Enabled + + + %C3 + Data Compression Enable/Disable + MNP5 & V.42bis (BTLZ) Enabled + + + %D0 or + %D + Data compression + 512 BLTZ dictionary size + + + %D1 + Data compression + 1024 BLTZ dictionary size + + + %D2 + Data compression + 2048 BLTZ dictionary size + + + %D3 + Data compression + 4096 BLTZ dictionary size + + + %E0 or + %E1 + Escape method + ESCAPE DISABLED + + + %E1 + Escape method + +++AT method (default) + + + %E2 + Escape method + Break AT + method + + + %E3 + Escape method + BOTH methods enabled + + + %E4 + Escape method + Disable OK to + +++ + + + %E5 + Escape method + Enable OK to + +++ + + + \J0 or \J + DTE Auto Rate Adjustment + Disabled + + + \J1 + DTE Auto Rate Adjustment + DTE rate is adjusted to match carrier rate. + + + \N0 or \N + Connection type + Normal connection (see below for definitions) + + + \N1 + Connection type + Direction connection + + + \N2 + Connection type + MNP Auto-reliable connection + + + \N3 + Connection type + Auto-reliable connection + + + \N4 + Connection type + V.42bis reliable link with phase detection + + + \N5 + Connection type + V.42bis auto-reliable link with phase detection + + + \N6 + Connection type + V.42 reliable link with phase detection + + + \N7 + Connection type + V.42 auto-reliable link with phase detection + + + +
+ +A direct connection is a simple straight-through connection without any +error connection or data compression. In this case, the computer-to-modem and +modem-to-modem speeds must be identical. + +A normal connection uses flow control (either software or hardware) to +buffer the data being sent or received, so that the modem can transmit data at a +different rate than the computer is actually sending or receiving it. For +example, a computer may send actual data at 57kbps, but using compression, the +modem only actually sends 28.8kbps. This is the mode use by most modems. + +A reliable connection is a type of normal connection; if, for some reason, +data compression or error correction cannot be established or maintained, the +connection will hang up. (In essence, such a modem ensures that all connections +are reliable, for it will hang up if the connection isn't.) + +Likewise, an auto-reliable connection is virtually the same, except that +the modem will try to renegotiate the connection in order to establish a +reliable connection. Again, this is the mode that most modems use. + +
+ + +S-Registers + + + S Registers + + + + Register + Range + Default + Function + + + + + S0 + 0-255 rings + 1-2 + Answer on ring number. Don't answer if 0 + + + S1 + 0-255 rings + 0 + if S0 is greater than + 0 this register counts the incoming + rings. + + + S2 + 0-127 ASCII + 43 + + Escape to command mode character + + + S2 + >127 + + no ESC + + + S3 + 0-127 ASCII + 13 CR + Carriage return character + + + S4 + 0-127 ASCII + 10 LF + Line feed character + + + S5 + 0-32, 127 ASCII + 8 BS + Backspace character + + + S6 + 2-255 seconds + 2 + Dial tone wait time (blind dialing, see Xn + + + S7 + 1-255 seconds + 30-60 + Wait time for remote carrier + + + S8 + 0-255 seconds + 2 + Comma pause time used in dialing + + + S9 + 1-255 1/10ths second + 6 + Carrier detect time required for recognition + + + S10 + 1-255 1/10ths second + 7-14 + Time between loss of carrier and hangup + + + S11 + 50-255 milliseconds + 70-95 + Duration and spacing of tones when tone dialing + + + S12 + 0-255 1/50th seconds + 50 + Guard time for pause around +++ command + sequence + + + S36 + Fallback options when error correction link + fails: + 0 - Disconnect + + 1 - Establish Direct Connection + + 3 - Establish Normal Connection + + 4 - Establish an MNP connection if + possible, else disconnect + + 5 - Establish an MNP connection if + possible, else Direct Connection. + + 7 - Establish an MNP connection if + possible, else Normal connection + + + 7 + Negotiation Failure Treatment + + + S37 + + 1 = 300 bps + + 5 = 1200 bps + + 6 = 2400 bps + + 7 = 1200/75 bps (v.23 + mode) + + 8 = 4800 bps + + 9 = 9600 bps + + 10 = 12000 bps + + 11 = 14400 bps + + 12 = 7200 bps + + + 0 + Negotiation Speed (Initial handshake) + + + +
+ +Many modems have dozens, even hundreds, of S registers, but only the first +dozen or so are fairly standard. They are changed with a command like +ATSn=N, +and examined with ATSn? (⪚ +AT S10 +S1? would tell the modem not to hang up for seven +seconds should it not hear the answering modem, and return the number of times +the phone last rang.) + +
+
diff --git a/doc/kppp/index.docbook b/doc/kppp/index.docbook new file mode 100644 index 00000000..ea172828 --- /dev/null +++ b/doc/kppp/index.docbook @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + +]> + + + + +The &kppp; Handbook + + + +Lauri +Watts + +
lauri@kde.org
+
+
+ +
+ + +2001 +Lauri Watts + + +&FDLNotice; + +2001-06-11 +1.01.00 + + + + +&kppp; is a dialer and front end for pppd, +allowing for interactive script generation and network setup. + + + +KDE +kppp +kdenetwork +dialer +internet +ppp + + +
+ + +Introduction + +&kppp; is a dialer and front end for pppd. It +allows for interactive script generation and network setup. It will automate the +dialing in process to your ISP while letting you conveniently +monitor the entire process. + +Once connected &kppp; will provide a rich set of statistics and keep track +of the time spent online for you. + +A built-in terminal and script generator will enable you to set up your +connection with ease. You will no longer need an additional terminal program +such as seyon or minicom +to test and setup your connection. + +&kppp; features elaborate phone cost accounting, which enables you to +easily track your online costs. + +We hope you enjoy this dialer, and that it eases your way onto the +internet. + + + +&getting-online; + +&wizard; + +&dialog-setup; + +&global-settings; + +&security; + +&chap-and-pap; + +&tricks; + +&callback; + +&kppp-faq; + + + +Credits and License + +&kppp; + +&kppp; is derived from ezppp 0.6, by Jay +Painter. However, nearly everything in &kppp; was rewritten so +ezppp and &kppp; do not have much in common any +longer. + +Primary Developers: + + +Bernd Johannes Wuebben wuebben@kde.org + +Mario Weilguni mweilguni@sime.com + +Harri Porten porten@kde.org (Current +maintainer) + + + +Many thanks to the following people who have contributed code to +&kppp; + + +Jesus Fuentes Saaverdra +jesus.fuentes@etsi.tel.uva.esfor implementing several options and +miscellaneous work. + +Markus Wuebben wuebben@eure.de for the ATI query +dialog + +Peter Silva peter.silva@videotron.ca for pop up +dialogs and other contributions + +Martin A. Brown MABrown@etcconnect.org + +Martin Häfner +mh@ap-dec717c.physik.uni-karlsruhe.de for the section on callback. + +Olaf Kirch okir@caldera.de for the introduction +to the mysteries of file descriptor passing. + + + + +Documentation copyright 2001 Lauri Watts +lauri@kde.org, although largely based on the original by +Bernd Johannes Wuebben wuebben@kde.org + +&underFDL; + +&underGPL; + + + + +Installation + + +How to obtain &kppp; + +&install.intro.documentation; + + + + +Compilation and Installation + +&install.compile.documentation; + + + + +Preparing your Computer for a <acronym>PPP</acronym> Connection + +The following sections contain some fairly generic information for several +common operating systems which might run &kppp;. The following sites may be of +interest for further information about the ppp protocol, +pppd and networking in general: + + +The &Linux; PPP &FAQ;: +http://metalab.unc.edu/mdw/FAQ/PPP-FAQ.html +The &Linux; PPP HOWTO: +http://metalab.unc.edu/mdw/HOWTO/PPP-HOWTO.html + +http://www.thoughtport.com:8080/PPP/index.html +The Network Administrators' Guide: +http://metalab.unc.edu/mdw/LDP/nag/nag.html + + + +Preparing a &Linux; system for <acronym>PPP</acronym> + +In order for &kppp; (or indeed, pppd) to work, +your kernel must have ppp support compiled in. If this is not the case, get +yourself the latest version of pppd from any of the +popular &Linux; archives (such as ftp://sunsite.unc.edu/pub/Linux/system/Network/serial/ppp/, +and recompile your kernel with ppp support enabled. + +Don't fret, since this sounds a lot scarier than it actually is. Don't +forget to install pppd afterwards. + +If you're not sure if you have a kernel with ppp support, issue the +dmesg at the command prompt and look for something like +this: + + + +PPP: version 2.3.0 (demand dialing) +TCP compression code copyright 1989 Regents of the University of California +PPP Dynamic channel allocation code copyright 1995 Caldera, Inc. +PPP line discipline registered + + + +&kppp; tries to find out for itself if your kernel supports +PPP. If not, you will be notified as soon as &kppp; starts +up. + +For &Linux; 2.x kernels, the pppd daemon should +be version 2.3 or greater. You can find out what version your system has, by +issuing the command pppd + on the command line. None of the +pppd daemons actually have a +, but putting the option in will cause the +pppd daemon to issue an error message, and then to +print out a list of options and other information, which includes the version of +the ppd daemon. + + + + + + + + + +&hayes-reference; + +&accounting; + +&documentation.index; +
+ + diff --git a/doc/kppp/kppp-account-accounting-tab.png b/doc/kppp/kppp-account-accounting-tab.png new file mode 100644 index 00000000..a0c4321d Binary files /dev/null and b/doc/kppp/kppp-account-accounting-tab.png differ diff --git a/doc/kppp/kppp-account-dial-tab.png b/doc/kppp/kppp-account-dial-tab.png new file mode 100644 index 00000000..2c0dc3ae Binary files /dev/null and b/doc/kppp/kppp-account-dial-tab.png differ diff --git a/doc/kppp/kppp-account-dns-tab.png b/doc/kppp/kppp-account-dns-tab.png new file mode 100644 index 00000000..b6215c2f Binary files /dev/null and b/doc/kppp/kppp-account-dns-tab.png differ diff --git a/doc/kppp/kppp-account-execute-tab.png b/doc/kppp/kppp-account-execute-tab.png new file mode 100644 index 00000000..ef0e5ed1 Binary files /dev/null and b/doc/kppp/kppp-account-execute-tab.png differ diff --git a/doc/kppp/kppp-account-gateway-tab.png b/doc/kppp/kppp-account-gateway-tab.png new file mode 100644 index 00000000..cebf1f5a Binary files /dev/null and b/doc/kppp/kppp-account-gateway-tab.png differ diff --git a/doc/kppp/kppp-account-ip-tab.png b/doc/kppp/kppp-account-ip-tab.png new file mode 100644 index 00000000..ccc9b7db Binary files /dev/null and b/doc/kppp/kppp-account-ip-tab.png differ diff --git a/doc/kppp/kppp-account-login-script-tab.png b/doc/kppp/kppp-account-login-script-tab.png new file mode 100644 index 00000000..293cfb2f Binary files /dev/null and b/doc/kppp/kppp-account-login-script-tab.png differ diff --git a/doc/kppp/kppp-config.png b/doc/kppp/kppp-config.png new file mode 100644 index 00000000..d9a5000d Binary files /dev/null and b/doc/kppp/kppp-config.png differ diff --git a/doc/kppp/kppp-device-tab.png b/doc/kppp/kppp-device-tab.png new file mode 100644 index 00000000..3d3b5257 Binary files /dev/null and b/doc/kppp/kppp-device-tab.png differ diff --git a/doc/kppp/kppp-dialler-tab.png b/doc/kppp/kppp-dialler-tab.png new file mode 100644 index 00000000..7dea2838 Binary files /dev/null and b/doc/kppp/kppp-dialler-tab.png differ diff --git a/doc/kppp/kppp-faq.docbook b/doc/kppp/kppp-faq.docbook new file mode 100644 index 00000000..57ef26ff --- /dev/null +++ b/doc/kppp/kppp-faq.docbook @@ -0,0 +1,477 @@ + +Questions and Answers + +&reporting.bugs; + + + + +Questions about Dialing + +I can't get &kppp; to work. &kppp; tells me +pppd has died or that a timeout has expired. What's +going on? + +Did you read this manual carefully? Here are once more the most common pitfalls: + + + + Click on the Details button. &kppp; will +you give an excerpt from the PPP log messages (may not work +on non-&Linux; systems, or even on some &Linux; distributions). The log will +help you to track down the bug. + + Make sure that pppd is the actual +pppd binary not a script + + Make sure that pppd is setuid +root. You may set this mode by issuing +chmod as +root. + + Make sure that your /etc/ppp/options file +exists and that it doesn't contain any conflicting entries. If in doubt: Leave +this file empty. + + Make sure that you don't use the option + as an argument for pppd (&kppp; +is already taking care of device locking). + + Remove the option from +both your /etc/ppp/options +and ˜/.ppprc +files! + Using the symbolic link /dev/modem may cause some conflicts. Eliminate this +source of trouble by using the real device, &ie; /dev/cuaX +or /dev/ttySX. +COM1 equals ttyS0, +COM2 is ttyS1 and so +on. + +Make sure you set the right permission. In case of trouble you +might want to run it as root first and then later, when everything is working +fine give it less harmful permission if you can not afford to run &kppp; setuid +root. The proper way to proceed would +probably be creating a modem +group. + +You might be launching pppd too +early, &ie; before the remote server is ready to negotiate a +PPP connection. If you are using a login script, you should +use the built-in terminal to verify your login procedure. Some providers will +require you to issue a simple Send or Send +ppp to launch PPP. Some users even reported, that +they had to append Pause 1 or Pause 2 to +their script to solve timing conflicts. + + + +If nothing helps, you might obtain some debugging info from your systems +log by issuing: + +# tail /var/log/messages + + + + + +pppd died - The remote system is required to authenticate itself ... + + +Typical error message in system log: + +pppd[699]: The remote system is required to authenticate itself +pppd[699]: but I couldn't find any suitable secret (password) for it to use to do so. +pppd[699]: (None of the available passwords would let it use an IP address.) + +As far as I can tell there are two causes for this problem: + +/etc/ppp/options contains the + option. Simply put a # comment in +front and try again. Your system already +has a default route. Have you set up a local network? In this case recent +versions of pppd will behave as if had been +specified. To override this you may add to the pppd +arguments in kppp' setup dialog. Alternatively you could take down the local +network prior to dialing in. I'd be thankful if someone could provide +instructions on how to peacefully combine the two network +connections. + + + + + +pppd dies with 2.4.x Linux kernel + +Typical error messages in the system log: + + +pppd[1182]: pppd 2.3.11 started by user, uid 500 +pppd[1182]: ioctl(PPPIOCGFLAGS): Invalid argument +pppd[1182]: tcsetattr: Invalid argument +pppd[1182]: Exit. + +Install pppd 2.4.0b1 or better. See +Documentation/Changes in the kernel sources for more +info. + + + + +Why does &kppp; tell me Unable to open the +modem? + +This means that &kppp; doesn't have permissions to open the modem +device or that you selected a modem device on the Modem Tab +Dialog that is not valid. First make sure you selected the right modem +device. Once you are sure you have selected the right modem device, you must +give &kppp; the right permission to access the modem device and to be able to +modify /etc/resolv.conf in case you want &kppp; to +configure DNS correctly for you. If you can afford to run +&kppp; setuid root this would solve all access problems +for you, if not you will have to figure out what the right permissions are for +your purposes. In order to give &kppp; setuid root +permissions do the following: + +% su +# chown +# chmod +# exit + + + + + +Why does &kppp; tell me it can't create a modem lock +file? + +This in most instances means that you have installed &kppp; +without SETUID bit on while you, the person executing &kppp;, doesn't have write +access to the lock file folder which by default is /var/lock. This for example is the case on &RedHat; +systems. Check the modem dialog for the precise location you have chosen. The +solution is easy -- either run &kppp; SETUID if you can afford to, or give +regular users write access to /var/lock +or create a modem group that will have access to the /var/lock file. + + + +Why is &kppp; installed with the SETUID bit +on? + +para>There is no need for the SETUID bit, if you know a bit of +&UNIX; systems administration. Simply create a modem +group, add all users that you want to give access to the modem to that group and +make the modem device read/writable for that group. Also if you want +DNS configuration to work with &kppp;, then +/etc/resolv.conf must be read/writable by the members of +that group. The same counts for /etc/ppp/pap-secrets and +/etc/ppp/chap-secrets if you want to use the built-in +PAP or CHAP support, respectively. + +The &kppp; team has lately done a lot of work to make +&kppp; setuid-safe. But it's up to you to decide if you +install and how you install it. + +You might also want to read the Security +section. + + + +What do I do when &kppp; just sits there and waits with the +message: Expecting OK + +Have you played with the CR/LF setting? Try CR, LF or +CR/LF. + +Alternatively, your modem might need some time to respond to its +initialization. Open the Modem Commands dialog on the +Modem tab and adjust the Pre-Init and +Post-Init delays. See if you are successful when +drastically increasing their values, and then do some fine-tuning +later. + + + +The connection works fine, but I can't start any +applications! + +You have probably selected the Auto Configure Host Name option, and +the X Server has problems connecting to your newly named host. If you really +need this option (and chances are you really don't), you are unfortunately on +your own to set up the appropriate authorizations. Issuing +xhost before +starting the connection would do the job, but be warned of the security risks +involved, since this effectively gives everyone else access to your X +Server. + + + +&kppp; reports a successful connection, but &konqueror; just says +Unknown host hostname, and +&Netscape; reports The server does not have a DNS +entry. + +Try pinging another server by its IP number, +⪚ ping +. If that works, you could try the +following: + + +Check if you have provided &kppp; with at least one +DNS address. + +Check the contents of /etc/host.conf. There +should be a line saying something similar to order hosts, +bind. The keyword advises the resolver library +to include a name server query when performing an address lookup. If such a +line is not there, try adding it. + + + + +How do I make &kppp; send a \n or a +\r + +Just send an empty string such as in the following script: + + + +Send # send an empty string +Expect ID: +Send itsme +Expect word: +Send forgot +Expect granted +Send ppp + + + + + + +How can I stop &kppp; complaining: Can't create lock +file? +This happens because you don't have permissions to create a lock +file. If you chose to use a lock file, you must have write permission to the +folder (typically /var/lock). This is +of course no problem if you have given &kppp; setuid permissions. Please read +the section on Lock files. + + + +Why is my modem making so much noise when +dialing? + +Click on Setup, then +Modem. You can control the modem volume here in three +steps: Off, medium and high. For most modems, medium or high result in the same +volume. If changing this setting doesn't work, make sure the correct settings +for your modem are specified in Setup, +Modem, Modem +Commands. + + + +I turned the modem volume to Off and verified the +modem commands, but I still hear that awful noise during dialing. +Why? + +The volume initialization string can get lost if your modem can't +cope with the speed it is receiving commands from &kppp;. Increase the value of +Post-Init Delay in Setup, +Modem, Modem +Commands. + + + +&kppp; keeps reporting unusual modem speeds like +115200 or 57600 + +Many modems only report the speed of the serial line and not the +speed over the telephone line as default. You must configure these modems to +report the true line speed by adding some commands to the modem init or dial +strings. For many modems this command is ATW2. If you want +to add it to the dial string (which normally starts with +ATD), the new dial string would be +ATW2D. + + + +Why does &kppp; report Unknown +speed + +New modems often have very complex connection messages like +CONNECT LAP.M/V42.bis/115000:RX/31200:TX, and +&kppp; cannot parse this message correctly. Turn on Show +Log and you'll see the connection speed. + + + +I get a slow connection speed + +If you are not satisfied with the modem speed, make sure you've +set the connection speed (you can reach it by clicking on +Setup, Device, Connection +Speed) to 57600 or higher. Make sure your serial ports support +higher speeds. Many older systems based on i486 do not work correctly if you +set the speed to 115200. If you have an old 8250 UART +chip, it won't work. If you have a 16550 or +16550A it should work flawlessly. + +Additionally, you should consult your modem manual to look for init +strings that enable a high speed mode. + + + +I get a REALLY slow connection +speed! + +If data drips on at a rate of just a few bytes per second, you +should check your hardware setup. If moving your mouse speeds up the +transmission this is definitely a hardware issue! + +You can obtain some information about your serial port with +setserial and check for interrupt +conflicts with other components of your system. The &kcontrol; module +Information might also be of help here. + + + +My phone line needs pulse dialing instead of tone dialing (or +vice-versa). How do I change that? +You must modify your modem dial string. Nearly all modems support +the following AT commands: + + + +ATDT +Selects tone dialing + + +ATDP +Selects pulse dialing + + + + + + + + + +Questions about Telephone Cost Rules + +How do I write a telephones cost rules file? +Just follow the TEMPLATE rules file supplied +with &kppp;. You should be able to find a copy in $KDEDIR/doc/HTML/yourlang/kppp/. +Use the &kppp; command line option to check the syntax of +your proposed rules file. + + + +I have written a telephone cost rules for my region. Where can +I submit it so that others can make use of it? + + + + + +Can my phone cost rulefile contain fractional time units like +"(0.17, 45.5)"? +Yes this is possible. But you shouldn't use unusually small time +units below a tenth of a second, because this would result in higher +CPU load, although you probably won't notice on a modern +CPU. + + + +My country observes other moving holidays than +Easter. +In that case, you need to write new code that allows for the +computation of that holiday. Please have a look at +ruleset.cpp and emulate the easter example. +Then send in the patches!. + + + + +Questions about the System Logs + +I see a message saying Serial line is looped +back. What does this mean? + +Short answer: You didn't start the PPP software +on the peer system. + + + + + + + + +The logs show Signal 15 +If you see the following lines, you've probably just received a +timeout error from &kppp;. &kppp; has been waiting for the +PPP interface to come up and gave up after the specified +timeout. pppd was signalled to shut down, with signal +number 15, &ie; SIGTERM. + + + +pppd[26921]: pppd 2.3.5 started by me, uid 500 +pppd[26921]: Using interface ppp0 +pppd[26921]: Connect: ppp0 <--> /dev/ttyS0 +pppd[26921]: Terminating on signal 15. +pppd[26921]: Connection terminated. +pppd[26921]: Exit. + + + + + + +What about Receive serial link is not 8-bit +clean +The PPP daemon is alarmed by the fact that all the +data it receives has bit 8 set to zero. In most cases this simply indicates +that the remote PPP server isn't running yet. You might +still be confronted by a login prompt that echoes back all the data sent by your +pppd. + + + +and can't locate module ppp-compress? +What's this? +Do you see the following messages? + + + +modprobe: can't locate module ppp-compress-21 +modprobe: can't locate module ppp-compress-26 +modprobe: can't locate module ppp-compress-24 + + + +Just add the lines: + + +alias ppp-compress-21 bsd_comp +alias ppp-compress-24 ppp_deflate +alias ppp-compress-26 ppp_deflate + + to your /etc/conf.modules file. + + + + + + + + diff --git a/doc/kppp/kppp-graph-tab.png b/doc/kppp/kppp-graph-tab.png new file mode 100644 index 00000000..0597810a Binary files /dev/null and b/doc/kppp/kppp-graph-tab.png differ diff --git a/doc/kppp/kppp-misc-tab.png b/doc/kppp/kppp-misc-tab.png new file mode 100644 index 00000000..b8bc0840 Binary files /dev/null and b/doc/kppp/kppp-misc-tab.png differ diff --git a/doc/kppp/kppp-modem-tab.png b/doc/kppp/kppp-modem-tab.png new file mode 100644 index 00000000..58534642 Binary files /dev/null and b/doc/kppp/kppp-modem-tab.png differ diff --git a/doc/kppp/kppp-wizard.png b/doc/kppp/kppp-wizard.png new file mode 100644 index 00000000..e648d413 Binary files /dev/null and b/doc/kppp/kppp-wizard.png differ diff --git a/doc/kppp/kppp.faq.question b/doc/kppp/kppp.faq.question new file mode 100644 index 00000000..e4800ab0 --- /dev/null +++ b/doc/kppp/kppp.faq.question @@ -0,0 +1,54 @@ +X-RDate: Fri, 12 Dec 1997 19:59:12 -0500 (EST) +X-UIDL: 26006 +Return-Path: +Received: from cornell.edu (cornell.edu [132.236.56.6]) by + postoffice2.mail.cornell.edu (8.8.5/8.8.5) with ESMTP id BAA01394 for + ; Fri, 12 Dec 1997 01:06:00 -0500 (EST) +Received: (from daemon@localhost) by cornell.edu (8.8.5/8.8.5) id BAA05503 for + bw18@postoffice3.mail.cornell.edu; Fri, 12 Dec 1997 01:06:00 -0500 (EST) +Received: from polygon.math.cornell.edu (POLYGON.MATH.CORNELL.EDU + [128.84.234.110]) by cornell.edu (8.8.5/8.8.5) with SMTP id BAA05461 for + ; Fri, 12 Dec 1997 01:05:58 -0500 (EST) +Received: from sun03.berlin2.debis-sfi.de (proxy.debis-sfi.de) by + polygon.math.cornell.edu (5.x/SMI-SVR4) id AA14318; + Fri, 12 Dec 1997 01:05:52 -0500 +Received: from merlin.debis-sfi.de(138.201.4.1) by sun03 via smap (V2.0) id + xma001088; Fri, 12 Dec 97 07:04:21 +0100 +Received: by b1.debis.com id HAA28426; Fri, 12 Dec 1997 07:05:37 +0100 +Received: by c1.debis.com id HAA16797; Fri, 12 Dec 1997 07:05:39 +0100 (MET) +Message-Id: <199712120605.HAA16797-c1@debis.com> +X-PH: V4.1@cornell.edu (Cornell Modified) +Date: Fri, 12 Dec 97 07:04:48 +0100 +Reply-To: "Torsten Uhlmann" +X-Mailer: debis Systemhaus's Registered PMMail 1.9 For OS/2 +Mime-Version: 1.0 +Content-Type: text/plain; charset="iso-8859-1" +Content-Transfer-Encoding: 7bit +XFMstatus: 0002 +From: Torsten Uhlmann +To: Bernd Johannes Wuebben +Subject: RE: kppp + +On Wed, 10 Dec 1997 14:07:24 -0500 (EST), Bernd Johannes Wuebben wrote: + +> +>Hello Torsten, +> +>On 10-Dec-97 Torsten Uhlmann wrote: +>> I've got a problem using kppp (I think it's the latest version, I got it +>> recently from your project page). I use S.u.S.E. Linux 5.0 and KDE +>> beta 2. +>> +> +>OK this is a problem with you pppd options and PAP configuration. +>However lots of Germans are using kppp and I imagine that if you +>were to post your question to kde@kde.org and kde-user@kde.org +>someone will be able to help you. Let me know... +> +>Bernd +> + +I figured it out. The problem was I handed over USER "LOGIN" as it has to be in ppp-up. +(At T-Online LOGIN is # which has to be quoted in order +to not be substituted by any shell. Well between kppp and pppd there is probably no shell, +so the quotes are obsolete :-) diff --git a/doc/kppp/security.docbook b/doc/kppp/security.docbook new file mode 100644 index 00000000..d3012f8b --- /dev/null +++ b/doc/kppp/security.docbook @@ -0,0 +1,96 @@ + +&kppp; and security issues + +This section is mainly for superusers (root) +people with high security demands, or simply technically interested people. It +is not necessary to read this if you only use &Linux; at home for yourself, +although you may learn a thing or two in any case. + + +Restricting access to &kppp; + +A system administrator might want to restrict access as to who is allowed +to use &kppp;. There are two ways to accomplish this. + + +Restricting access with group permissions + +Create a new group (you might want to name it +dialout or similar), and put every user that should be +allowed to use &kppp; into that group. Then type at the prompt: + +# chown /opt/kde/bin/kppp +# chmod /opt/kde/bin/kppp + + +This assumes that &kde; was installed in +/opt/kde/ and that your new group is named +dialout. + + + + +Restricting access &kppp;'s way + +Before doing anything, &kppp; checks if there is a file named +/etc/kppp.allow. If such a file exists, only users named in +this file are allowed to dial out. This file must be readable by everyone (but +of course NOT writable.) Only login names are recognized, +so you cannot use UID's in this file. Here is a short +example: + + +# /etc/kppp.allow +# comment lines like this are ignored +# as well as empty lines + +fred +karl +daisy + + +In the example above, only the users fred, +karl and daisy are allowed to +dial out, as well as every user with a UID of 0 (so you don't +have to explicitly list root in the file). + + + + + + +&kppp; has the <acronym>SUID</acronym> bit on? What about +security? + +It's virtually impossible to write a dialer without the +SUID bit that is both safe and easy to use for inexperienced +users. &kppp; addresses the security issues with the following strategy. + + + +Immediately after the program starts, &kppp; forks. + + +The master process, which handles all the GUI operations +(such as user interaction), drops the SUID state after the +fork, and runs with normal user privileges. + + +The slave process keeps its privileges, and is responsible for all +actions that need root privileges. To +keep this part safe, no &kde; or &Qt; library calls are used here, just simple +library calls. The source code for this process is short (around 500 lines) and +well documented, so it's easy for you to check it for security holes. + + +Master and slave processes communicate with standard &UNIX; +IPC. + + + +Special thanks to Harri Porten for writing this excellent piece of code. +It was thought to be impossible, but he managed it within a week. + + + + diff --git a/doc/kppp/tricks.docbook b/doc/kppp/tricks.docbook new file mode 100644 index 00000000..c2abc3bf --- /dev/null +++ b/doc/kppp/tricks.docbook @@ -0,0 +1,175 @@ + +Modem Tricks and Hints + +This section should get the fearful started on the (not so) arcane art of +modem tweaking. The commands here are all Hayes AT standard, but all modems are +not equal, so your mileage may vary. + + +Modem Sessions + +A Modem session allows you to interact with the modem directly. You type +commands, and it will respond. To obtain a modem session, when no connection is +active, go into Setup, then Modem +Terminal dialog. This will open a window for interactive +configuration of the modem. Try typing +ATZ (which resets your modem) Your +should get an OK response. Use +FileClose +to end the session. + + + + +Modem Profiles + +One reason you might want to send the modem commands directly is if you +have a set of modem configurations you want to keep, and not have to specify for +every connection. A good way to do that is via modem profiles. Modems can have +several stored profiles numbered 0,1,... AT&V can be used to +view them all. The default profile is usually 0 (this can be changed via +AT&Y.) The profile currently in use is called the +active profile. + +When you change a setting, the active profile is modified. The +ATZ command will have the modem load the default profile, +erasing any changes you have made. To save changes, Load the profile you want to +change via ATZn (where +n is the profile number). Make the changes you want, +then save it with AT&Wn. To +have kppp use the profile you want, change the modem initialization string +(Setup Modem Modem +Commands Initialization String.) For example +ATZ1 will have the kppp reset the modem and use stored +profile #1. + +If you want reset you modem to get back to some known starting point, use +AT&F&W to set the active profile to the factory +defaults, and store those settings as the default profile. + +Examples of profile changes are in the next section + + + + +Getting the modem to hang up + +Sometimes you may find that &kppp; has difficulties hanging up the modem. +This is likely the result of a mismatch between &kppp; settings and those of the +modem. A standard modem uses two methods to decide to hangup: Command, and DTR. The Command method involves +sending an escape sequence to the modem, which puts it in command mode, then +issuing the hangup command (ATH). + +Outside of &kppp;, when configuring the pppd +package manually, it's often helpful to use the command method, so that one can +exit a terminal session, and then start pppd without +having the modem hangup. In most other situations, the DTR +method is preferred, as it is simpler. + + +<acronym>DTR</acronym> (<command>AT&Dn</command>) method + +The DTR method will have the modem hangup whenever +&kppp; stops using the modem. If you obtain a modem session, and query the +state via AT&V, and you can see among the displayed +settings for the active profile a &D0, then the +DTR hangup method is disabled. To enable the +DTR method, use the Terminal button to +get a modem session, then: + + +ATZ # reset to default profile +AT&D2 # Set to hang up on DTR drop +AT&W # Write to default profile + + + +How the <acronym>DTR</acronym> method works + +Whenever the Data Terminal Ready (DTR) line on the +serial line between the host computer and the modem goes high, the modem hangs +up. When &kppp; opens the serial port, the DTR line is pulled +low, on an external modem, you can see the DTR (or +TR) light come on when this happens. When the +TR light goes out (because &kppp; has closed the serial port, +or something worse!), the modem will hangup. + + + + + +Command method + +The other way to have a modem hang up when connected (used when +AT&Dn where +n is not 2) is to have the +modem accept the command when a session is in progress. To have it hang up +properly, get a modem session, and set the guard time to a short interval like +so: + + +ATZ +ATS12=5 +AT&W + + +Then use the Guard Time slider in the Modem commands +section to match the register (S12 to this value +5. The modem should then hangup properly. + + +How the Command Method Works + +When the local modem is connected to a remote modem, it is in the +connect state, where it passes all characters it receives to the +remote modem without interpretation. To have the modem accept the characters +as commands for itself, one must put the modem into the command state. The +escape code does this. + +The escape code is defined as being three intervals of time whose length +is defined by S12 in fiftieths of a second. + + + +Quiet (must last more than S12/50 seconds) + + +Escape character (defined by the register S2, the +default is +), repeated three times (less than +S12/50 seconds between each. + + +Quiet (must last more than S12/50 seconds) + + + +Once the modem is in the command state, you can send it commands. To have +it hang up, send the command ATH. The escape codes and the +hangup string used by &kppp; are shown in the Modem Commands dialog. +These should match your modem. + + + + + + +Make Tone dialing faster + +If you can use tone dialing, the amount of time it takes to dial can be +changed using the S11 register. It gives the duration (in +100hundreds of a second) to send each tone while dialing. The default is +usually 95 (almost a second.) How fast you can dial depends on the phone +company's switching equipment which handles your line. The minimum duration is +50, almost twice as fast, and that speed often works. + + +ATZ # reset to default profile +ATS11=50 # fastest possible dialing, use a higher number if it doesn't work +AT&W # write to default profile + + + + diff --git a/doc/kppp/ttyS-cua.txt b/doc/kppp/ttyS-cua.txt new file mode 100644 index 00000000..2369fd5e --- /dev/null +++ b/doc/kppp/ttyS-cua.txt @@ -0,0 +1,46 @@ +From: "Theodore Y. Ts'o" +To: Tony Nugent +Cc: linux-net@vger.rutgers.edu, linux-ppp@vger.rutgers.edu +Subject: Re: /dev/cua? Vs /dev/ttyS? (was: Re: co-existence of pppd and mgetty ?) +Date: Mon, 13 May 1996 19:51:04 +0200 +Status: ROr + + Date: Mon, 13 May 1996 07:57:09 +1000 + From: Tony Nugent + + Can someone kindly explain the difference between the /dev/cua? and + /dev/ttyS? devices? + +/dev/ttySxx devices are fully POSIX-compliant TTY devices. If you are +only going to be using one set of tty devices, you should be using +/dev/ttySxx. + +/dev/cuaXX devices are different from /dev/ttySXX in two ways --- first +of all, they will allow you to open the device even if CLOCAL is not set +and the O_NONBLOCK flag was not given to the open device. This allows +programs that don't use the POSIX-mondated interface for opening +/dev/ttySxx devices to be able to use /dev/cuaXX to make outgoing phone +calls on their modem (cu stands for "callout", and is taken from SunOS). + +The second way in which /dev/cuaXX differs from /dev/ttySXX is that if +they are used, they will trigger a simplistic kernel-based locking +scheme: If /dev/ttySXX is opened by one or more processes, then an +attempt to open /dev/cuaXX will return EAGAIN. If /dev/cuaXX is opened +by one or more processes, then an attempt to open /dev/ttySXX will +result the open blocking until /dev/cuaXX is closed, and the carrier +detect line goes high. + +While this will allow for simple lockouts between a user using a modem +for callout and a getty listening on the line for logins, it doesn't +work if you need to arbitrate between multiple programs wanting to do +dialout --- for example, users wanting to do dialout and UUCP. + +I originally implemented the cuaXX/ttySXX lockout mechanism back before +FSSTND established a standard convention for the use of tty lock files. +Now that it's there, people should use the tty lock files and not try +using /dev/cuaXX. The only reason why /dev/cuaXX hasn't disappeared yet +is for backwards compatibility reasons. + + - Ted + + diff --git a/doc/kppp/wizard.docbook b/doc/kppp/wizard.docbook new file mode 100644 index 00000000..6f26e711 --- /dev/null +++ b/doc/kppp/wizard.docbook @@ -0,0 +1,117 @@ + +The &kppp; wizard + + +Starting the Wizard. + +You can start the wizard from &kppp;'s initial screen. Start &kppp; from +your K menu, where you will find it's entry in the +Internet as Internet +Dialer. + +The following dialog will appear: + + +The &kppp; dialer startup screen + + + + +The &kppp; dialer startup screen + +The &kppp; dialer startup screen + + + +It will probably not have any entries to begin with, and that's what we're +about to do now. + +Click the Setup button to begin setting up a new +Internet connection. + +The wizard will offer you three choices, Wizard, +Dialog Setup and Cancel + + +The wizard asks you what you want to do... + + + + +The wizard asks you what you want to +do... +The wizard asks you what you want to do + + + + + +Cancel +Choose this if you really don't want to be setting up a new +account right now. The message box will go away, and you will be left with the +dialer screen as before. + + + +Wizard +If you have a fairly standard modem, and use one of the larger +ISP's for your country, the wizard will probably be able to set you up +immediately with a working Internet Connection. Try this first, before you try +to set up the connection manually. + + + +Dialog Setup +If you don't succeed with the Wizard, or you just want to do +things yourself, choose this. The wizard currently is only useful for a small +subset of countries and Internet Providers. + + + + +For the purposes of this chapter, we'll assume you are choosing +Wizard, and the dialog based setup will be described in a +later chapter. + + + + +The Rest of the Wizard + +The first screen you see contains just introductory text, explaining the +things you read about in the first section of this chapter. Press +Next to move on. + +The second screen asks you to choose the country you live in. Not all +countries are represented here, and if the country you live in is not listed, +you will have to press Cancel, in which case the Dialog based setup will start for you to continue +with. + +On the next screen, you will be given a choice of Internet Providers that +&kppp; knows about, based on your choice of location in the previous screen. +Again, if your ISP is not listed here, you will have to press +Cancel and do your setup in the Dialog based setup + +You will now be asked to enter your username and password for your +internet connection. Please note, that for some ISPs this +differs from your mail account user name and password, so make sure you use the +right one. Choose Next to continue. + +On the next screen, you have a chance to enter any special dial prefixes +you might have - for example, if you must dial 0 for an outside +line, or if have a prefix you can dial to turn off call waiting. Choose +Next to continue. + +And that's all! If you want to revisit any of your choices, you can use +the Back and Next buttons to move +back and forth through the dialogs. When you're happy, press the +Finish button, and you're all done. + +Of course, any of this information can be edited at a later time, from the +&kppp; Configuration dialog. + + + + diff --git a/doc/krdc/Makefile.am b/doc/krdc/Makefile.am new file mode 100644 index 00000000..085981d9 --- /dev/null +++ b/doc/krdc/Makefile.am @@ -0,0 +1,4 @@ + +KDE_LANG = en +KDE_DOCS = AUTO + diff --git a/doc/krdc/authentication.eps b/doc/krdc/authentication.eps new file mode 100644 index 00000000..4b730630 --- /dev/null +++ b/doc/krdc/authentication.eps @@ -0,0 +1,219 @@ +%!PS-Adobe-1.0 +%%BoundingBox: 0 0 380 183 +%%BoundingBox: 0 0 595 841 +%%Creator: KDE 3.1.92 (alpha2, CVS >= 20030921) +%%CreationDate: Sat Oct 11 17:09:39 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 10.416 3.125 ] [ 3.125 10.416 ] [ 3.125 3.125 ] [ 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 3.125 3.125 ] ] d +/pageinit { +35.52 24 translate +% 185*280mm (portrait) +0 793.92 translate 0.96 -0.96 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 637]ST +B P1 +NB +W BC +/mask 4416 string uc +î½*¾î1:7*¾-M.õ0*Þåñ/:ÞëA*î÷º¿*öõ85<ØÀÉ8*I°ØÉÑ9*1°ØÉÑ9*+ÐØÉÑ9:ý+Qú/îYäY:½*Pú/ +îMèYÆÖ5.¾1íý5Üýí»ý½úüýöûýïùýáõýÅíéÝ* +d +/sl 34960 string uc +î½¼**ûEH*˲ÚF1*ÒL;ó+ÞìûçøºØ6òÚÄúóòèãõÍýü»YYHùñãÇÝ@D¯4Öìûùí5 +ô/ÞY6Ùõë7Gºúùé¹ÅâÃ=ÝWõÃ3Öûû¿øº7*ìÓ°ÒLO0ºÅ7/5ùï33ÂB·ÍJýÕÕâñQçôü¼îNá-úí÷øG1HY½ +°ôç±°÷úãMHLJýýùºß-Qâ*¯Äû½öá5=¾·ÛõíI½×*GÃY¸¯ß/F.3ÖÚì÷1½Î0Ìù½ÇÃöãÚç;Ö¹Â-ĵúûÉ9 +GÞÃPæÍÉßñí9½CL2Þ?OFRÀê,*Oõ16=çED,*0*XçÆAXüúøùûóåõöé½QÜúöà>=P+AÙ½½áïøõI6Â@*öÑ +À¿EýüܼúºÜ¸íßÅ5Hø*/RýüEÍ-¾é6ÛÑâ¿øàGMÚè°ºL,+SüÑñýZ5ëúúÝ1ÃäýXÉýý½ó8ýù1Úïý÷ðöïý +ÃSÓûI´7+*¸ß-AâJµìNøIºYAPJéýõPçEMá,0ÚùPèÀ+Âãýµî*5Yú÷ýíT-UýÝÒ¹ñ*ÕT+ïÌú½¼ÚE1¶Ï3 +Ú÷ýäÚDï¹²ä;0î´5°-*NÙý´ÇHÍæRçÉ**îÆÄÒêïÞÅÉÅILÃë½1üù¶×6ÍϾ**î>/.**=/,*¾E>îîùÍý? +½üÓI5*ºQçâýY9ý»Yí»¶ìá;Ýê×R¾øõÅ÷F*úâäH1è=AëIàEöà=ZùùÝV÷F*ÚéÑöäßÂFôKÞöP.àµBÖúæÑÏV×ý:Êæå>Ü-LÜB*í +ýI;ÄAýíå²ÄîÂ1XÆ¿ÔV×òýñÀXʾÀëûýýö²Á/VôûÝ08@Æüý9ß¿¾²CÆ´è:ì5RÌ-Fá¿SÄE1¶Ï3¯²Ñ=A° +îʶZRCÏ*î6ùLïáåº**ôYöíÙYæ5è0 +HúIÄÃ,Ò½½Çõ+Þ·°ÔøBïÁ6AH4¿ö6A÷õýÜ8O¼Õ99+,.ÆÎ÷¾5-îïýå4çñ½ñÀè-XöÞ3CJÇÐý±Ûòâ.ºI¹ +ÃýÍÍõý½ñäÝòÀÑ-õî¾¾üAÜè¾Vý1×êï81Ì7ÞÃPÖöý¿Ãº¿6Q?Ïî;×*Jáûí@F÷ÂÄ2¸öðMÄ*øMDðý¼É´7 +û¹öïGúõæWÉüû9ßE>¶,ÖõRJνI<¶?/F=¼úÔÚ¹µ÷**îFìÞ±9Y²Îæ·-AûWßL+¹Ý½ãÅå+Þ?ôëEFõÒ³/5 +Ê5ÍÂJºÄ½6Cú½Ç9ûüµ°UÔ>ÙHîà*ºîý°?Àüíð<úæüýW9YöÞ+CZ¼¹»ýÅüçÖ5=Þ¼½ÄÂ÷½öLÏ0´÷½Ê9-5 +ìÞ¿ýýÍ3ãèýK´D³îGßÙ½4±NÏÆYïÞ˲Ò-Úæ.?úIòÆ?ÝÔ·FEÆ=OÆ7*ZòüQ3ÄF/TD,êMÍÞùUDðð¾Í´ûº +¹õíÛùôåWÍ0XÌÞE>¶°YõRJ̵I<¶?/êðú÷ÔÐQI÷**²ÛÙß±õÄH¾¾ÛMõWúWZ+5/Ê÷üOCH1*Ý6HîâXóüG +SæOº<,ïCÌ;MÎùß1â2?0åY½ù¸ñ+Q0ÞÙLåýÛçJèB<ÜôýIï¿-°Þý÷UIÙçÐÁAâ¾×Ýõ9ýóXÊL6êâõñ÷7@ +F¿ÁÆ,òýGSJêTDFNØåõíIÝÙ×MF¶>7Ó:1D¿EXGÌý¼÷õÄ;Ì7â˺³,,2ÎJ +*åQÆ;¿3ׯ=L¿4ÛðµàÞ4LTNîZß/F.@CJ¿>O¹;7 +FÎÕNÀ0KLóP?ÌOM?âÃZJ³Ê.3ÀÒäïêJì>KT,Î7ñõAAZá±D4õ»NM@5¶ìØÍ2Õï0H5ÝÚR*Ö9ûÜK+0SÛ¸U +àõº+*XÊSîܹ=åHHÁÕ̺óPß.Ô3ÞÒûÑQÂöB*öKÖòMÁVÕîó6VÎ.àÜôº7µ:Jãô1ÛWJÄÕ²N**¶PMÔ3öî +ÜÍBÉ39ÜÇÜÒR:D6ÜÝÈ,ìöú·UÜ»Uº+*8Y>ÏÝÁ9Í8HÑÍì²ÒMé2HJ+-ÜÝêÀÕ+Þ*8ºÅî÷5êî.àÜ´Û714¾ +9Xíͱھ3U¿ÌéÆ9,8ÚöðæÑUõéÝÍÝÜF÷Kõ°P+ÓRÚ¶U×±I·HµæWеJ;5ýÊ×ÔáÁÆÔ¹09Î*ÐëÍüá¾Ë+*, +2ÎÛÉ-5ìý·Ö¸,9äKMX1¶îßÅEµüÚMÝüÛúôëóÒÉC/>LíÜW×´ûPøºÞ·ÎöÎ;Áä7òÁSÕ¶*ÍÎ*ÐìýºL¾Û9* +ú³ýüýÐã=öL¿XéÆÎ<ñ6*ÂÍ·CÑ*AÊGÙ?çÐ1H+*AöÍÛ±ÚR¿L/·G±õÍÞ=>Î4¿õÅÌÐÝÔ1*1¶¶ýü<ÁÇéõ +¿Ìé2ÓÑÓç;*2WóÐP+VÛØ?åÌY¯ÍýÙåÌF¶ðêë-ýXË*±âÛ½½ä;÷³¹*¶¿RÓõíY°A´*íÓ-GÔ;èµìÙM*:ñø +OË,2D²UÈ?Q³¾ÜY¯úûù±ÅêBºëî*J¹;Ý8íßÌï÷ØÔìܼÙÒ³Úº×?áÄñ°JÚµ¯¯ðûùG*º±ËׯMAíܳðDH +¼êÈZßÇC¿éÁÅÌßÏNûð?ºóô´*öÔD?@-ÔêضàÌÜ»è5ñ=VÖ?ݼÙO0*Ì7E³åÈ35ô5öèë*îݼ+5´Êײé*Ù +êX²7L:¼ûÌ»+ôí9*Üý»»ýõûüíùûÝõù½íõýÜíý»Ýýù¼ýõÛð»ÏÄ2=ü¾ùð²CôÛWÕNļççÑ´½Ü»÷î7Ã,Ý +ëD°ìÌÚàUÕNKDäöÕÙAÅBжîÛÅ5Xê/ñä4îÚWÄÔ3PñÜÚ»Ïò6S1»÷òëü±74Ù±ºÌG¹êðÉíëÜÚßåÔEÑ@ú;NÔöÝKÊWÓ¼¸¾,²òÐJíëܺßáËKÍIIHMìÖýPÆNÁT¸Ï,NÈÒÀÝëÜVÝäÆå9*¶ÙæÕ´ÒS +Ñ;ê1T¾ä4<òÕë5L>Õد÷;Y¾¼:¾XX2°NKÉYÞ*+бöMÙA9ÈíìýáMÅAíåÉõI½ú÷ñý×Â2AǺK->W<¿íêÜ +úöÁKõÙý5A¹8Y¼ûãQéñ9ú°+-ØDô,1æåHå¼×»÷Ã5×ÙQÉ´YñHÛùöñϱ/Tôî5×ÙÂêÃ8:Ö´*û¯ùðAE89I +µTã¼G³ðãÕÁ9íü°Ý1:Lûïåýô.¶Ûö+°»ãÍ9´YÌ@âÙíëÔâê×õû¸¹öñä×ÅõðíýNµÌâúïSû¼UY.+óVáçý +ØA98ÐSò½?4ôë=íûÈÒììàÉùòìáÉIÍ:ÚµR÷»÷ÅCÜB,ص1ÖêÀ+*NÈ;1Ó:D:ÝGâ46SÁâ4ò°9ùð6Y¶;-1::,îÊ5ìZßË+F+Á-JRé¶ÆÞ9X6æ7-ÛÐÐMÇÆJ-8TãÏGF´ÄMÈ·N8Q6ZIùðAñ8áêü¶ì×ÓäW¿¿úß°LJ-PüËÚ·ó+ÝXܺÕÝ +¼»UQtܺÇ2ÇC°ùñù¸K+1=ôä.ìíé·4ÚüÁÑN¸C9åáõî:Ì·ãÓGF´ÈÙì¹Ö/¹×A1÷C¼ôܯåTXÍ»5ÀÛKK ++,8=åÝÛ¿AµÜ»îÉÍIYFàWÙü:¯Nàò¶@ÛYÂÊ-*ÕõóN/è-XS½0ù¸ß/µÖî.ÂìZÞ+:FÜÕEA¶Ô3õ´îRIT.X +ëßÏ2Ú°ÈÓìɵVÜÚZó¸·×MÎÊJ·*,Ì7Zâñ±5µüÚ²9Iöãó9ÛCÊÍ@¿È+èç÷àÛÊÜ÷¾=7¶0.÷êéëS**1Ç:- +0ÚÒ¾Éù¸·:ǾßßáF2¾?ìÉMºGý8ãð2?Kã×5X9îÙÖÌÓ9C´íá÷ó+ÝäüòÊ/9¸KÌùÈKJÍìS,ù¸È,C:òï» +ÛJ¿Á0¶¶-1ßZ>:+UFÁÔÉÇ×л÷Ç´99=>8B>îö+1¶Ì½»ÜÅúBóëXG¸ñêßÍQIÏ>W?SYFTM*î¾QÌDÞÏ,F; +ͱ7¶ÁFÅ´ñL,X>Þ+8F+ÀÙȸó6AÐîÈ2ì7â32FçÃMP·=UA4îÎ@X÷Lìü¸¼Y¸CK¸ÅBï¸Ð+¼ÚèåɹYW6Øñ +HçùùóíéÑ>LSO»6éùÀÚÏ=YBâÛ5÷½ß÷»Sôê±ÄÀðóÇ/öÐóI൸óXèÌ>Q²çZÕIÚÐÖ¹µÝB×C»³Ø»ÛÐ*Å· +úßóñÅ9OéåÜÊõ7¹LÕ:4ÌÍôùYû¯³9OBÃÍUÜ°ØAµ2ÚCøÝÓÉQÄX3L +òóëÉAÂFëµðÁíĵô8±åùñëɵH÷=OõÔ½YMŸ+øÜð?Í»·Â»KÇIûùø²ôÏDIÛÝÜ»·ÍGÏåìEïîÇ:éýÀÕÂô +ã×ýéUØö¹µì*HííÜFù¿Ê³±Ùúº?Jøûë5°ÝµûëBI6ÜðØAâ8ãÙKVÝÞ»Cô¶æ6ü-õóËßÞùÝÕõýÜíý»Ýåù, +d +380 92[1 0 0 1 0 0]sl 8 mask 0 0 di +/mask 4368 string uc +*Jà**9ÜýAºýÝùüýöûýïùýáõýÅíý9ÝýI¼Oý,% +d +/sl 34580 string uc +äÕí´êÝI*Ö´¼ûØøüõóûÝõù½íõýÜíý»Ýýù¼ýõû:í5Rû7**ýA»Yîýí*¾9½Y*ÞùÝÕ@¸ÅÂô/*6*NºVS¶ß +ñ×Õ²Dóãëé/ýHT¾L¿--¶Z-*Þ+/ºXù¸ÐVÅ4îæÜì.êC-ºQõG*¶òZ1>îP+ì2Þ?-úVõ»ÀZã*1Bîëëé;TÛ +Lí-+öðôóò¸ÍGÞ3-úWù4иZÚ/,A4îâ1ä0ìRÞ3Á»PõÛÓ/ºGù4ÏW,öüôó7ÜõØ>*R»7÷øïõó·æéÐõóÏÖ +àùÝÕõýÜíý»Iéù,T97*ð×AöýÏ+JHùøò>îûíéù½íõýÜíý»ù-Aö°ÏöFë×MQ°*Þ¹ôêï.ÍæãÝèܾä:Ó:¾ +.õ³.*²üSù,ðXÂTÐù<¹âGè-ùÜH¼û¹+*ÀÝüùüÝAJC@ÃÎûGO=Ïé¯Ï/B*ÈQøÞÛÁQÝÜýùíÍ5ðßÕ**ìEõ +íÚ½1åì»õí5J8Ç,@5ΰ¹Ð-P,@Á6Jï-»îçÁ1ýûÚ»ýøS»ùóÕ**8H·êÓ±åÌëEJíúù°Dûö×ô*ΰ¹Ð5P°ì +úöÏ÷J9FõéØYå@ͽ½Ø½½ü?*¾ÝI½ú²æÔ±ÝPÕÉYíøùðë2;øø¸´éÖǺüô=-*YíFóåÒ±Ý0U2;¸ÏLìC´éÍ +O½ÝÝÝòå½1*îûõÁÝË·ñéÖÇ°èäÙüܺ>PÊ4áPÛ·²ðHýüê*ÞãõäÊ·³êÃC3PÏ8ò¿1¼7D°åéëéÍì8ж+ÕÍ +¼öãÆKÝ8V.-íøùðÒRÏè5Ù±áËøøåH.*.*Ò-ÚK¾D*Ž¼¶ãÄGáìEæîJ9ºWéÉ1Y¼¼Ü»¿YýC*ÊHÚ-¿á6¶ +ò-8Ýû÷ß;9ÜêDÓÓ¹¹éíÉñàÃ5VèÇ+M@âãIÑÕ=.¶°*A.ÞåÉ9üBîâÒß0DFæ-ÍTø¯ÝÅ¿Á9Yèѽ/**Þ7,N +°*źÞéÅ1ýÙ¯ÝÅOÑÒ¶³µû»·±äÊ+7»öXà²Eùøµ***Ú¯¾-,¶=*ìGùëÂõ887²Îß¿HÖËÎ1é7¸JëáÁ½.** +ÞÏ.**±+*JFÞÏ0ÚK¿=*:ïÙ1TJÛÁKíºÙõõãáÃ5Að±ß·1=ëçáÝ+**îÄ+*¾=5¶Î*¼»¸ëÁñ4üF°Îß¿HöÅ +Í-á?FE¸óåL+*FÁ¾B**Z0îR-8CÞ/+¶óæ±5ÄEöñÅ˽Y¼íÜ»1G1¼EBßPÜû7***µ6:ÆøÆ/ºùôåÇá8ú´Z +ïúZÎß¿H¾ÀM1ôD÷ñçY+**¶Ç+ñ+A6îL18³Þ6*GQüøÎ,>êúõ³19Hâµ*ÃTý¼ü0**¾E0¶J*µ,¾¾ýX»Ê.ö +/DFæÁ-¸Jܽ-½/**ÞÏ..*Fö¾50¶¯,ÅDZ0J¼ù¼¾4ËýÜXÐNÇîF-´5ÖÎìû´Í8ÛçÇ*öìÛS-´5Ö±¶¿01ø* +àGÎ-îû6**¾±22-¶ã*Ÿî03À*8Î,Âéúù°H»ø²@*ãúÈÌÛù;***Q2î0,82¾çÌWØíZSÏJ½¼¹*JFµäº¯Y4=äËJÁ,ûô +**»ìºçÐã¸Y5S + + + + + + +]> + + + + + + + +The &krdc; Handbook + + + +&Brad.Hards; +&Brad.Hards.mail; + + + + + + +2003 +&Brad.Hards; + + + +&FDLNotice; + +2003-09-27 +1.0.0 + + + +&krdc; is a client application that allows you to view or even control +the desktop session on another machine that is running a compatible +(VNC) server. + + + + +KDE +kdenetwork +krfb +VNC +RFB +krdc +Desktop Sharing +Remote Control +Remote Assistance +Remote Desktop + + + + + +Introduction + + +&krdc; is a client application that allows you to view or even control +the desktop session on another machine that is running a compatible +(VNC) server. + + + +You would typically use &krdc; with the &kde; VNC server, which is +&krfb;, since it closely matches the special features of &krdc;. + + + +Please report any problems or feature requests to the &kde; mailing +lists or file a bug at http://www.bugs.kde.org. + + + + +The Remote Frame Buffer protocol + + +This chapter provides a brief description of the Remote Frame Buffer +protocol used by &krdc; and by other compatible systems. If you are +already familiar with Remote Frame Buffer, you can safely skip this +chapter. + + + +The high level implementation of a system using the Remote Frame +Buffer protocol is known as Virtual Network Computer, or more often +just as VNC. + + + +Remote Frame Buffer (or RFB for short) is a simple +protocol for remote access to graphical user interfaces. It works at +the frame-buffer level, which roughly corresponds to the rendered +screen image, which means that it can be applied to all windowing +systems (including X11, &MacOS; and &Microsoft; &Windows;). Remote +Frame Buffer applications exist for many platforms, and can often be +free re-distributed. + + + +In the Remote Frame Buffer protocol, the application that runs on the +machine where the user sits (containing the display, keyboard and +pointer) is called the client. The application that runs on the +machine where the framebuffer is located (which is running the +windowing system and applications that the user is remotely +controlling) is called the server. &krdc; is the &kde; client for the +Remote Frame Buffer protocol. &krfb; is the &kde; server for the +Remote Frame Buffer protocol. + + + +It takes a reasonable amount of network traffic to send an image of +the framebuffer, so Remote Frame Buffer works best over high +bandwidth links, such as a local area network. It is still possible to +use &krdc; over other links, but performance is unlikely to be as good. + + + + + +Using &krdc; + + +It is very easy to use &krdc; - it has a simple interface, as shown in +the screenshot below. + + + + +Here's a screenshot of &krdc; + + + + + + + + + &krdc; main window + + + + + + +If you click on the Browse << button, you +will get an even simpler interface, as shown below. + + + + +&krdc; main window, without browse functionality + + + + + + + + + &krdc; main window, without browse functionality + + + + + + +If you click on the Browse >> button, you +will get the normal interface back. + + +Connecting &krdc; to compatible servers + + +&krdc; is a client, and it needs to be used with compatible +servers. There are three ways to connect to those servers: + + + + +Directly typing the server name (or IP address) into the +Remote desktop: combo box. + + +By using an invitation that you received. &krfb; uses +invitations, and can send them by email. + + +By using Service Location Protocol browsing. + + + + +Let's look at each of these in turn. + + +Server name entry + +If you know the host name (or IP address) of the server you want to +connect to, you can enter it directly into the Remote +desktop: combo box. + + + +If you want to connect to a machine named megan, which is +running a VNC server on screen 1, you can enter +megan:1 or alternatively as +vnc:/megan:1 into the Remote +desktop: combo box. + + + + +Entering a hostname into &krdc; + + + + + + + + + Entering a hostname into &krdc; + + + + + + +Similary, if you are using a RFB server on that +machine, you can enter rfb:/megan. RFB does not +need the screen number to be specified. + + + + +Using an invitation + +Within the &krfb; server application, it is possible to send +invitations over email (and in other ways, although email is the most +useful). If you receive this type of email invitation, you can just +click on the link provided in the mail. +This will start &krdc; if it is not already running, and connect to +the server specified in the invitation. + + + + +Using Service Location Protocol + +The third way to use &krdc; is to browse using Service Location +Protocol. A list of compatible servers that are registered with the +Service Location Protocol system is shown in a list in the center of +the main window: + + + + +&krdc; showing service browsing + + + + + + + + + &krdc; showing service browsing + + + + + + + +If your main window doesn't contain the table, remember that you can +use the Browse >> to get back to the full +&krdc; window. Also, not all compatible servers support automatic +registration - one that does is the &krfb; server which is part of +&kde;. + + + + +If you click once on an entry in the table, it will be selected, and +you can use the Connect to establish a +connection to the server. As a short-cut, you can just double-click on +an entry, which will also establish a connection. + + + +While Service Location Protocol will usually detect new servers +becoming available, you can also force the &krdc; to scan for new +servers. This is done using the Rescan. When +you ask for a scan, the button is disabled (greyed out) while the scan +is being performed - this typically takes a few seconds. + + + +When using Service Location Protocol, the concept of Scopes is +important. If there are a lot of services being advertised, it can +become unwieldy to scan through a list. A re-scan can also produce +a lot of network traffic. To avoid this problem, administrator's can +configure Service Location Protocol with a set of Scopes, and only +register services in certain scopes. For example, a host may be +registered in the "third_floor" scope and the "logistics" scope, but +not in the "engineering" scope or "maintenance" scope. In smaller +setups, everything is only registered in the "DEFAULT" scope. &krdc; +supports selection of a scope other than "DEFAULT", using the +Scope: drop-down box in the top right hand corner +of the main window. + + + + + +What happens when you connect + + +No matter how you select the server to connect to, the next thing that +happens is that &krdc; asks you about the network connection to the +server, as shown below: + + + + +&krdc; connection speed selection + + + + + + + + + &krdc; connection speed selection + + + + + + +There are three speed settings: + + + +High Quality (LAN, direct connection), which is the +default, and you should evaluate how well this setting performs before +selecting a lower performance option that uses less bandwidth. + +Medium Quality (DSL, Cable, fast +Internet). +Low Quality (Modem, ISDN, slow +Internet). + + + +If you always operate over the same link type, you can deselect the +checkbox labelled Show this dialog again for this +host, which means that you won't be asked about the +connection type again for this host, providing you identify it in the +same way. For example, if a host has two names, and deselect the +checkbox when connecting using one name, you won't get asked if you +connect using that name, although you will be asked if you use the +other name, or the IP address. + + + +You select the appropriate speed setting, and select the +Connect to proceed. + + + +You will then see a small window containing a progress bar, which +fills in as &krdc; negotiates the connection. + + + +Depending on the configuration of the server, you may (and almost +certainly will) need to provide a password to authenticate to the +server. &krdc; will provide a password dialog similar to that shown +below. + + + + +&krdc; password entry + + + + + + + + + &krdc; password entry + + + + + + +After authentication, you will be connected to the remote server, and +can begin using &krdc; to observe or control the remote desktop. + + + + +Controlling the remote desktop connection + + +Having connected to the remote server, you would normally use the +keyboard and mouse to control the windowing system and applications on +that remote machine. + + + +You can view the remote desktop either as a full screen, or as a +window on the local desktop. You can change between these modes using +icons shown below. + + + + +&krdc; full screen mode selection + + + + + + &krdc; full screen mode selection + + + + + + + +&krdc; window mode selection + + + + + + &krdc; window mode selection + + + + + + +Full screen mode is normally better when you are helping a remote +user, because you can see all of what they can see. Window mode is +most useful when you are working both remotely and locally - perhaps +referring to some local documentation and then using those +instructions on the remote machine. + + +Using window mode + + +&krdc; in window mode looks something like the screenshot below. + + + + +&krdc; window + + + + + + + + + &krdc; window + + + + + + +In window mode, you can terminate the connection by closing the window. + + + + +Using full screen mode + + +In full screen mode, you can terminate the connection by selecting the +red "close" icon, which is shown below. + + + + +&krdc; close icon + + + + + + &krdc; close icon + + + + + + + + + +Managing &krdc; configuration + +Using the Preferences... button in the bottom +left hand corner of the the &krdc; main window, you can open a dialog +to modify the behaviour of &krdc;. Selecting that button brings up a +window as shown below: + + + + +&krdc; preferences - Host Profiles tab + + + + + + + + + &krdc; preferences - Host Profiles tab + + + + + + + +&krdc; preferences - VNC Defaults tab + + + + + + + + + &krdc; preferences - VNC +Defaults profiles tab + + + + + + + +&krdc; preferences - RDP Defaults tab + + + + + + + + + &krdc; preferences - RDP +Defaults profiles tab + + + + + + + + + + + +Developer's Guide to &krdc; + + +&krdc; supports a small number of &DCOP; commands, which are described +in this chapter. If you aren't familiar with &DCOP;, then you don't +need to worry about this. However if you'd like to automate some of +your &krdc; (or other &kde; application) actions, &DCOP; is a useful +tool. You can find out more about &DCOP; in its on-line documentation, +and in tutorials on http://developer.kde.org. + + + +You can shut down the &krdc; application using the quit command, as +shown in this example: + + + + +%dcop krdc-25550 MainApplication-Interface quit + + + + + +You will need to change the krdc-25550 in the +example to match the instance of &krdc; that you actually want to +shutdown. If you run dcop with no options, you will +get a list of all applications that are running and &DCOP; can +control. + + + + + + +Questions and Answers + + + + +&reporting.bugs; +&updating.documentation; + + + + +When I start &krdc;, I get a message box that reads + Browsing the network is not possible. You probably +did not install SLP support correctly. +What is wrong? + + +SLP is Service Location Protocol, and is typically provided by +OpenSLP, or by The +Knot. + +If you compiled &krdc; yourself, this probably +means that &krdc; has been compiled against the SLP libraries, but +the server (probably called slpd or +knotd) isn't running. You normally need to start +these servers as the superuser, which may mean requesting that your +system administrator does this, if you can't do this yourself. + + +If you are running a packaged version of &krdc;, then you may have some missing +dependencies. There are so many ways this can happen that you'd need +to seek support from whoever did the packaging. + + + + + + + + + + +Credits and License + + +&krdc; + + +Program copyright 2002 Tim Jansen tim@tjansen.de + + +Contributors: + +Ian Reinhart Geiser geiseri@kde.org + + + + + +Documentation Copyright © 2003 &Brad.Hards; &Brad.Hards.mail; + + + + +&underFDL; + +&underGPL; + + + + +Installation + + +How to obtain &krdc; + + + +&install.intro.documentation; + + + + +Compilation and Installation + + + + + +&install.compile.documentation; + + + + + +&documentation.index; + + + diff --git a/doc/krdc/krdc_window.eps b/doc/krdc/krdc_window.eps new file mode 100644 index 00000000..fde2af12 --- /dev/null +++ b/doc/krdc/krdc_window.eps @@ -0,0 +1,514 @@ +%!PS-Adobe-1.0 +%%BoundingBox: 0 0 677 308 +%%BoundingBox: 0 0 595 841 +%%Creator: KDE 3.1.92 (alpha2, CVS >= 20030921) +%%CreationDate: Sat Oct 11 17:24:58 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 10.416 3.125 ] [ 3.125 10.416 ] [ 3.125 3.125 ] [ 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 3.125 3.125 ] ] d +/pageinit { +35.52 24 translate +% 185*280mm (portrait) +0 793.92 translate 0.96 -0.96 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 512]ST +B P1 +NB +W BC +/mask 6545 string uc +î½*¾î1:W*¾1M0Ç7*¾ÍA?Þ¾.S+¾åÉ4*J´ÏÆB>ÐE,ö:ñQãÅ.ZJäÍÈïÅ°üÒúÆJü+ÜGT¾IéM/÷+îôR+H +ê9?´µ4,Þ¿õÍ4í½ýÜýü»ýûùüùõûõíùíÝõݽí½ýÜýü»ýûÉMT*% +d +/sl 52129 string uc +î½¼**ûýG*N¶°IM3HHÏÌ+ºIMÓùÇY0º´ê11*@ÕZêñÝ2ÑPñø.HîÕý8*òêêûùñåIËƳö7âTÒÔ½ôÖWÝH* +ÞÝîùéÝýC>óCÑÄ0æûâA-HEô¯*BÙòùÅÌÉÒ²TÓUãÐOøY??ÌGÝIõÕÕIÔåOÞð4Ä»¼Ú>î½³Ò.HîÕ3*¾+íÍ +úè²*ÞÓ¼G¹õ¯çÙ·=àÓÁäñ.½11Zìûûé´Ë+ã¸Ú/âêê;ÑZøËÏÈ꯱*»NÙýü×RÑ+*9UÙ°äÀé4OÇ<ÀòDÍ= +ºHF¾ÛùáÄȳ0@éð@¾½ßõíýô/ÐÈæVY**AHÜûúô×1Iï*ãÈ?ñüÞÉä-*̳´âÄï°/È+Ê?ôöùûíµI;*ÛEÅ5ÏÖÎ+BöûË@»ðZ»ô*ÎõïKøñòéé½YHºõê +GùòåËE½H¯4ÞÚùù¹ÜAúÄôÍÃØ*Þíä´8ü¼úîÓáñã¿áXù¾.:T÷û×,Ë1*úåß¼çX?èðJ5µêêÊQPJ8å5VñJ +¿ðü9ÐàýPCRâÜøûÕGð?6¶ÓõÓÍáÂë:θü½·ýá9VBμýɳ÷Ýåо5X¶R/å*:X*5Gúݻ߲TÀüýýõÐ6ZÑÄ +õë9»Ù.Ï>á*õÖî0,úTù÷½äÀNÊOÆ>Ï*ÛE1*ÒL¯K-¾ÛùÑó÷òÃÁ³C-FIN½GÛ¸÷ûñݽíéÉåüº·ðÕÃPÊ,Ü +¹ýüùüõí9F>»**ܵµòîïíYíG¼çÃ/Aî:NÞð¸åIú¿D¶ìýO@àAñPÅÞì;8BÜïõY¼ò*ADK½X0 +ͽ½úã2K*Õ>ïJ3ô:*R¸½ÞBÇܶÖ@±¾CN*Z*îè:»ïðåÕÝíY½¼ÅÝü¹ðãÅAPC¯4¶×ûùE8Õ½ýßJ²+¾I?6Ú +÷õõóáåñÓù8¼ÚîÏ1R¾øõÝC=*¶ËϹÈFRYÚ±ÂUWÄÅR:TæµìâýÝ;ÍýéñTEµÄûIÚÎUBîðýíÂí»éËEÓ¯ýã +ûÕÊÒV¾ïýµ²ðýö×86ß/AFÑÀ±+2AîIGý1Ùæäåý»R²üõØúýýUõöݽÞ7ýÍAÅ9ýYÊTëý¯é2*Î5H¶á;Äò½ +öÜXʾÔýíËеÄÄ/6¶õËÒ/-2ÉýíÞ+@ÜöñýÝÓ0Ôü½Sõä+XÓ,àG÷ýú¶µ8îL=¶ñýË·³àõæÊM6Þë@ê+*:ë +üÙOøºÞZWÔ*±Æ³Ò,*¾MÁÒ°Å-IXHüÎQ´ýGöíáã°ºÄ,,*¾Ñ>:**Ê>2**ìξÁí½üÕú÷ÕüV*îGO=Aü»»ú +õ»»õß·9Åú°åL+èÝIäÊ*ÞQ´ô·ÎH·çü;Ü¿AHL¿*EÅ3ñ÷ýúøüÝÍ5ß64üý¾ÌXß¾õý9ïäýÕ;àÒQéÕý9µÜ +°+ìý½ÎÈý÷IT5¶à/åÒÎFJüý½K2;*üöΰBçåI.ÍĵÜêýýÈä*ú×ýý7ÃýÝTC=øÝ÷¶ã6AºîÄMÊäýù9ÛB+ +³ýݲÖA105JÁüý·TCÖî48>ÑWCýÁIô*Oâ:Æñ*Õ¶*A/ùýÝÏE1¶Ï3ºü½UÝLÈêµÆïÎ<8>ß8**=õÕÉÏä-E +é°/BÁ>**ÂK4**87ÐõFHEµõóëÝõôé¹åGùöÌ3YÏÞ²ôO4Â2¾ù¯Ò*Þ74îìûùÙTÝHå-*J8»1½¿ÞßÞòØLÉ +Ã,´½ýÉ?±,*õø>Ü·B;Å»2BÜD¿ÞDY×<èïóóü½Ì/óêü½QJöXß¾éýIÇÜýøSÓââý=ùúIóÆá,îüæÍÂA²>* +¸HïÀ±TB**¾,*X2,-É9»DÈ?ùÍÔ4ûÊà3Jâ5-îûý=àöðýûÊè8XöÞ7ÅÖÐýÍ5¸¯,4´õýýáÑ7@Z×õýD¶ÖJ +øý½.1,ÐãæêS-UýÝ>Èñ*å¶ZÈïä+X²LZôêåÃ6FÑÂTÀG*¾³ìI@à-/é±.2Á²Ý×ÍY½ö3²ÝCNí2FóêéãÍQ +íåÉÝHE³î79HL¿-êÊ3/*åJT4I¼ä-*ZýÀýÜûàRÏ´îßý¯V>JûùÉÊ4*FQäT9=¼Æïà±=2F5/î-GÃù9@?ó +ý3RJëÌß¾GýYÇâ2SA·ý½L8ýéµ³V¾ÁýÍóñ½äÄL,*äEX¶P/QµßýüíéÏâõø9ÃÙ,Kñ+A0ÞáýÍ?Ðåýä/Ó1 +Úî¿E8¯Ðý±Ûòâ.ºI¹ÃýÍÍõý½ñäÝòÀÑ-õî¾¾üAÜè¾Vý1×Rñ81Ì7ÞÃPÖöý¿Ãº¿6Q?ïÞ¿´J*æ¶ýÙ/QÜÞ +×8±¾CNÀ8Üú:N¾üæ:ûÝíÐWMý8¼º1ýG¸éнýMö0/X²=*¶Å,,ûù;¿USÞCïáµãÛ¹É1**åï0HùöL-ÃÌE¶ +çí4@4ÖùûU¼Ê3*ÚàÜ»ÁñÌØÔZ¿·ïÏ3.Ì÷Å9ÑÌò3ûºTR÷ýí¾Ô?3F¿+ÍùýßÕýëDøRÓýAQóýã¿ÊüïM×ýS +ìûí9Ö44:×ýIîÀÁT¶0*ñ,H°öýäÆL;Ýà¿EçR1¶Ï3Tö½æ;U¼Wñ¶´:QtZ1*¾ÏùIOBð/ÌóB-Ò +K,ôßÊîûÓK¸·ÞQÙ¼ÜÛùõì»ùñÔQ-AQî74FKè¹>:åÙ93ÚÈ,´·üºUçÑͺ**Øìëî×¹M9JÞìÏù@ü@Ö¾Ã,ä +ºýÐ6±,*Ý6HîâXóüGSæOº<,î½+*±Òí½üêç8ÛÞ¾J+*ò.0úêôøO±ðÃÏQ?GýûæÎT/â·Ä6çÄQý?:¸K+ÅÚ +îÊ4Ì+J»¿-Û9¼JÄRÁñQøÙÕ¹*510ÞÙLåýÛçJèB<ÜôýIï¿5:ÏúÓÍë²çß5°ÞêíùÅý¸AP;0´ðù÷ú05ÌÞK +N+øýÌ>:´?78<ëñùõÍíëê;8FÈÄTÆ-ËÞ7Õ8å½Ýú¹á2å0ðP3Ô:*æ¹ýÈ,IÜÞ×8±¾-ÁNëÝÁ½´ÆÜV¶éÖÍY +åÜÛ±ÝüF´éßÉûÉ3/8Þó;4îÇíÝÙ°ìFâ-*îý2,íG¹öò*ºúõæÃ1YRéÝL+ÙÚIïÂ3+¾9òX¼¹íËKÐ-/YË** +T*Î31/LòÈÝ=621¶--ôêÏKÖà5ÒÝSÀ2çLÖZ*=H±â2ØDÀMX¶P/å,ÎÜÞ÷3°NZL´Ê.ÇKÊXÞà¿+J²Ç.SÐ@ +ºJÔ;46¶Þ-AJà.W4ÄZNÞ¿3BPÂ,?TD¿35LTNîZß/FÂTDJ¿>O¹;7FÎÕNÀ0KLóP?ÌOM?âÃZJ³Ê.3ÀÒäï +êJì>ß,.JEäíYXÞÄA¶Ôá6îâ:+¾±Á²×¾ñÔûJH?**Ú¿åàAA42ôëÍÏ*ÁÒXÛ?ïàEÜ**Aä>¶íÛDZ9ÍßUQÜ +¸ÑZ,é.Zèüç=LÊ1*öKÖòMÁVÕZLNæ±G2Në<4Ï°ùÁì@:á=Fé¯1¶ZÆ,*¾±Á²Ë¾1õûâMËöîËòOÃ,´Gùôí +YO+µºüÚ?íÜ?Ü**ÅAÈæíßÅQ1ÍçQµDèϳ.9ξ+íY´ßÂ*Þ*8ºÅZPLæ²H2Në;3ß1ÕõåCXÞÂQ¶Ôá8îú:û +:îÞÃAÅHÛ¸ÝüûúóãÇ9¸A±4RÃàÃÙÈGù3Û¹Áì<»-4×öåʳGH²۱ö*/:ëüòI0êó*:¾*ó2ÂZßûI1UQîß +=Fé¯1*0Y9Ðé+8ºöïäËIõæÕ½AÝ»¹Gó°P+ÃæÙµUÔWÝß¹9¶8>H²âη-ûZè?8Þ=>¾ÒùIÍ2îñ-*üؽý½ç +ð3º±±W0+Õ1SRÇ·0*àåÚÊS¾5äÌëȲç-Í*¾5úåìCXÒJÏÀÚÌ×ùQî34RÃÞùMQçíå-*1¶¶ýü<ÁÇéõPßøê ++Ì9<ÓÑÓç;*2WóÐP+VÛØ?åÌYûÛýÙåÌF¶ðêë-ýXË*±âÛ½½ä;÷û¸*î,ÏTíÝÝâXê*ܱ0é±4¶ºÆÒíÚµÅ* +JäóÉD/:²æÔ>UÌÈó»Ýàöùõå8ׯöøÝ+*ëÃú¸¶5¹ÄåéظùøOO±ûû-¶÷°UÄ7å?ù¶íààâù½¹*î˳ä<Ä7Û +ºùðÈ×ü0,ºúÖ?ßÀ=±,Õ19GÁMÇøãUöèûê*ÞÙèÒÖ6Ö°éà3¹ø¼ÂÏ°¼À/ÙðéµÙâ*ÞׯÑôVC¯ß¼+YÍ7*úÕ +YÞÀ7±@ëDÞ@Eɳ¶;2ݼ1SOüûíI*¸ÊS6ï´¼û8Ð,úíI*ØêSÌ:?Öýü+ÎãÌð.SKû»-¾Yµº?SÐTïÐÜö¸êÀ +8¶¯âØM=0ۻͻOáLã3ÐÀ÷õ*Þ×TòR¯K¾ùê÷¶êÎ3ñ/5ÓQTSÏJ½î깶.Ùæ*QG>S×¼--äÉÜÈG¼QÑÖRN¶ä +ÍýýýÍ»YëGä?Gé>QÆèâÉýÍö.*ÆY*î4ýäGÙÓçÍB»âÂ,óËÙÄ6AOãSÑPÄäFÖ¿ã>¾½X×óYáÈ?AÄÌK5Pèú +00Kàú1*:ñÈ´<Ã52áÈMSÇüÛÝÕø6ýáAÌ:Ó¾1ý*S+ú,*1CAWÕRÃìNçEHúõ8ڲ߲SSËD³ÀÒä¿7êO+úö +ÈV1ÉäûÙ´ëêB²ëÁÑì*;.NÉå*J2üÝÏËN<-L¶ïRT3íóXÍï»*á3Ð4õì9:?¶Y*îB³ÝPGØñãÆGïÞ»óÜ?ùó +T³ÈòRÏG=GÖ¿Û>¾Íë·µ²ðêÛÕääSáÜWÚSë5J.ùæðýôµ*¾¶ùø½ME·¼,Lõõ°*»EøýýüûHÖ¾ÍÅ*QÆTص½ +¾Ò1èQ¾ü½0*ì½ýû÷íÙ1íéÏ4ìM,ºàÖÁ³±Qíü²ÌHÚ÷ñæK±Û*L:Å@ÙI,4ø÷èý7ëâ÷3:ÏJ·9.±ÔðõíÝí× +ïWLá9ÏÃ=N?éÕIJTÖÜ*J½üݵíܽA.BÄT5йÔÊùX1´îV+*¾<¾Ê-Þñç»Ê¹¹ùé»ñõèÕ±å¼ÛáÚÜî¼BJáJ +TQGÙöïåÛÅIíêý6ó½à,ÇAWúçîD³YÕÛ>¹WÝH»øÐ÷äÙ>ÉßÅýZ/QÆTÒU½¾ÒJ»+*ö6N/-97?å½YZÚ°ê¹/ +6**1*îJ3À-È*°8*ååµ°ãËYñÔÌãµý8ü¹õ19ß¾èÃM¿*/Dì¼ßûZ*Ä¿ÒX¸öóëÝÉQíÜßý·çý-?ÆK:8³ãK +S6-:²µÆåò?8òùë<1ùVìû**Þ»+NA4¾à+ÆEÍÐââ:Y0,Ì:ÓJÅü*S+ø,*ë÷ÝIÁ:±HDTÃTÐIµè·/6**1¼ +îN+À-XÄÞ6*:½V,å@Þá+ÄÊØôëèá±õîåÑIIüÝMBÎJ8ô8XØéÙY¶G6.GøÿÒìù¸õîãѵóÝëýÇæýº,ï@. +XÆLB*;N,¹TVÎ<ÍMIËÑõ-5:BGIÉ+U8ÁÃX÷>ODúö³4î.Sã÷ñ1Æ4ÌA*ZÌý=üõ3?ë5MóöMéGDüHòÙ,0* +¾-I*åå+à*82¾S²K¾¿ö*>ÌV.+4à.úÞ.àÍþÎR³¶üÀ+ò·*WÏRÎõ,8À*ÚÙÛÉ+ÍüGG¹0½ÜûùôSѺ.-<¿ +ò52*à9N,ì;¯*ÄÆ=6AHï6*ÚLÉÛ½Ìú·øºYýÜÓýû½ø÷ýJ/ì.,,23LÇáµìîNPâ÷ñµNïL¯Rå5ÈåE8F+=L +ôú·ñFÞH÷Ê=,JJí4½+*ø¹ðà<75½>-B²ÉÉ6ºÅ-æíÔ>>*îº÷¿ò*ùäB8Òå¿»FÇÆ-Sö¸*î.SO÷ð1Æ4ÈA* +*ËßÑðýëK¾CTµÍ¼ñöíÛEÕ?Z**Fö+R¼8:0î¾*Ê,ÛàYÅ1J´¾>5QGPÞ±ëäô,íéÃN¿ðA6JÉ-¶-öí0Aî:. +ÞIÜÚF÷ñìçÙñöóñíÍí:ðMBJñÂ6ºÎ+¾ë¿NJÏοEûZÀïà=Ì+¾Q²Ç:KÀÕýÕõõñÙøýýQFý9ÒÞËJJ*¾JÑÁ +ò0JÌâÂ>YüÄ>8>+Ç?ï´»á¿=¶È?ÄAÍØ-÷ðAHñÂ*과ͷA*¶ë¾ù<ÔTÓSî¯,îºã*<4CúÃÅï*.7@ìK4á1 +Ð?ÆM´·ÎH1@ñNL8Nßé+¾=N?âÇIJTJÜ**°ÌÍÌ+FBTHIü¹õëÇå4B**4*à+ä,Ì2¾JÏÔê*ZFÞ-1JE÷5éÆ +=1TD:½ß*ÂF¾æ1ÞîÀ*δZÁSɼ¿ÒÒØÓèÅZ¾ÇYOÃLǼÝíNÀ;I8¸ï4Ä=ƶ?*;>:±:?ßý¿ý:ÓÆ÷ýÀ/Ì., +,BËÚò1µ*ðNPà*ÌÆà+2ÎC¾ÄÍ-Ú¶,ÓG+TðÎÜÛ÷ÊE-¾Ëñ³?ë¸*¾µÄÚCTÎ?á47ÝR+ÎÊYúPìÌ˾*Ï;÷ÆÀ +èÌî>÷Uò87á¿øFÖÅ=3·:/Y1*Ì:Ó2Aü*S¿ö,*¾÷öRç¸ï×,0*¾-I*àå+à*82¾MÄ?,5ØàÃ9*ÝÍæ*6RÈ/ +ÜYBÞ3@Ú3*9Ùë°ìÜͱ1YåÙI76KßÕZJÃ598´ÏMï1?3B¶î.SOöî1Æ4ÀA*¾É=ÒðôýEMÓ´ÙYÍ8FÔ¾+*îÞ1Jö¶*îF0î¾*ºöîÝMô2Hº0öùÂ0â1²?+ +A6ÎÂî.84+FV°T»IÝìGºHÈÉàÏ@-,»P->úó,Q¾í·@ºö0ÊÁÁ8ÐZEERÊñÊWÈ+8êáÇZ¾0 +1Ðîñ.Þ3ÐÈíÝ9:?úX*î@ýíùÍðçÛù¿ü-Mã³ÍèÊXîµV+-*Þ¿9Þí7òìW=@î¾*úQäÙÇÐöû¹EÙñ¶ôÙ³,4î +ÀÊÕµµ¼à*Ã0QîîJ+ÚóÂçÎSÙÄWSìÚöK¹M9àºÏ,8íèäÚSíMñEú¼:3Ú¿åëÇ;,ÆFòî.<80*B6N»**òñÅý +1NîP::¾íÔW?¹-:µ°ðÆ>4*À+RU.óìº/üÔÛ¿À¾ß,îñ¿åÚ7*úÈ°Uس-*ÊéÒ=³112ÎJ0/FK4Ù½E,µ×ÍÖ +.ÑÛ¿ÁÄKMÕÄ;0îÞHÌ.áÃ>úF+¶ÀÒàÙõ-N/Ü5*>ãýùôGº÷âRÛùôBA0²ÕEÔ¾+*îÞ1ε¸3Æ:75¶J*X.À+ +RÆøôWÙóàÎÞÄ-L¾*½6¾îá7-Ê2F¿ÁÑ0Îé<ÕôTÎâ-5Þ´´R:*À=âQ<¿±ôB¾NHPBÄ8XJàß*¾ÀÅÆT°ú½J, +µ,++6Æ*TóE*·==ÕN¯,Z6¾¾@*öæAR?,ËM+Xºäë*¶ê:ÍøóùõíÛ¼AXÑ9¯ùôû2NE>îÈ/ÖòûÕÆ16å¾NÈòÞ*T*Bé¾áS¹óQ¯øÅ*.9;ÓÆöý +ð0*¾/æÂÎL>99¶é5ÉDÄúõëÔP³ëÌAA*69R7Ì:ÓêÜû*Sßô,*:·Æ4úGDTëµìذٯGÖ¿Û>*N4-*Ò1+È*ö +S»âSµËµZôÅZ-3Üü÷**RL0ýYì**ƽP2ó/==,,YëÎæåµß0ÃÈÄÐÛA½ZúJ²¶ÀÒðùõ-N/Ý5*FNû÷ëÑAIü +ùöS½ÜúöîÞ½õäË´ÕÄóÞ0ÔÔ*êòFðÑ×ÐñA·FöÀ.»÷Ø.Q332***0üÝÚ+*:½ÃÎÇ7°å>.8ÙJͳëð>T>ºËû +UHÏBFKTC¼ú+ÐÀï/*X8LÀ*÷ßM18µÉäÊBYØZG**ÖñÞ0ôÔ*ØòÙðÐÇ@Èú7NäýÕ*ZÁE·5,ÌùÝÆW3½Ëè>Î +Ù,J.SA+VCVØñCÌ:ÓF½ü*Sß÷ã,*N¸Á*A0*͸ÁÛéÙëàá¼2ß»í¹¶>SÎñÈXß9Ì:³É»Q¹6â3ÐÀóÉ0*Ç>ç +¿+F4*ñÅ2D¾Ù0ÆÃò¿49ÖÊÙ³NAï11ÄØåÚÑG0ð.SßùQ1*±Ò9QÊÌ:?Êýó*J**ö·Þò.Sßûõ9*Wô>QÆ4ô½ +ý*æ°Q·ÀÒ:ñ/**ý·+*óå½½°NöüÁZUÈ:DïÛ-*Î÷8SñèÁ2è*¾õñX´Iýüûý6@·ºôóùÕí¼ûøÌH»öéÏE½8 +í?ÐÄ8RüèWµ@Fö»*¶åìûéÅìEóâîö1,*üêø÷ùúû5Í*Õ¼Ä4¶ºíÕÙõÝIýûÈÝüøéÅõ¼ÄÒ¯Ò²¼±ÞS*¾ÌôÝ +I»èÃI<*æâæõõíܶ8àø>Z*ïWÅH¼ýSTÙíHùëåÆDÄÈÀ1ôE/*Rçüùõ¼Ê¶@*îÇí4ÄFC°â°ÑCÀåZ-¶¸ûüç +÷ä¿V½,0*ÜJTåÝI½*úóÕÝX¶õ¼ÅÒ°¹í5*ÞÎ͹IíûõåQF:.*-Ùí͹îº*ÞGåXêÕ³éÎ/Éн=¸ÀÍÎ-öÖûü +àõÜÀÓ1L+1Æ4+**:Iû¹N³üûMÝù**ºðëëïÉå8û·¸XEð5;ιõ@íÝúíº*Þ3ÉäûE´èÐAEá¶ÁôËR4¼À8Jå +øû-ÌùäÓ¼2.*ºÁÒ¯Ò,*¾ÉNOú/éW*î±E1½I¼öèë÷ðÞ¯Ý:+ÓùÕ½ûÅ÷P-*ìõ²éÖ¹õÔËÖäã5õéͶñÏÀ5 +0:ýýQò,SúÞK7âÐ.U¿;ųÖ-PííEëÚAXóÃBÞ:³7?+*Zû?ÐDýüËá»**X¶µòîïíYíÛJýý¹ÈÃ6ÌòJFJ+Þó5Úè,¾ùP,ƵêÖXçÞ×<Â6*Ù÷>OÔFBÕîÔ÷í²óòýýý7äÒµëI¹ÄÖȲ==7:øüçÜ +ZÓIÐ*2í¸òï÷AÏ4øéY;ÓçÕÍõê,*ATD·ïíéá½íé¹9Ìù¶âHá@4Ö÷ú+ÑË0*FïåÕO½8GØR´R°éÑM³Ìº¾, +;·ó*Ö¸ûüòUÅ-öØÔåõõIIWõPݽøÉ9óÞç<Ê1Îèóû18¸Q²éôÄÍý÷ÚK,üD½×ÇáùõY½ú*ËQøX2+ëËݹÂß +Û<*Ø;-µ´ß@ÑåíøÏ-F32÷ýýÇ+F¶¿5;·ß6å*:Â+EùíDTå¾*²Ì×LßóåCìîà;ØâÍ*B»½ÞSãHÞÔAÐ*C:? +Û½Iô+*:ÅÜÁÕ8Ê+ÖîÕCPW>JúõÍ»ß-õ7ÞóX8ë·¯áÌõ¸ÃFÇüÊD³âÈ1T:<µ6ÞSýíVÊìý¹áøÖÖýÑÌ´ÉßT +è·EºýáźÐ-µÉ:H*Oý9»6Ô·+ÍûIU¸ü/Yûíýù1IèûýùX¯»ÒàôÛY6ã:,ºHü@Øý9@ßâÞËWÞÜNýÑIöâ+Ì +ëàÃ+Fö¿5;·+2åLÁýýåÕÝYR<1,:WûîXÙH·¶/AQö<½,Þîú79û¸3¼æì·Æ+S3»øX9*¾û5üùô5MÃØ¿1ý1 +²Ï*÷ÝݳD*îSåÌ7Ù@¯ãEGàʵ+áG¹>ã-?2ÇE0îòý5UÅôýñ,»éøýÅ·±øòMûèÑíýýWG翶¾3¾MO2³¾ðý +A51óú¾ÙÕ9Tà÷åëûõýÝ¿Ðóüýè»ûTÉWQ-IOó½úýϽÄÛÌ@ßâÞËWJVïüýQóâ+ÌëàÃ+Fö¿5;·+2ABðLOÊ +÷ý:5@.J¿ý5´9¸ðî4XÌîOü.¾ÚõQä·óôû:02N/î9:ÓÒåü4SSúöV9**Ðá7ºôïó-4GµîÐáßV>Zö÷ÁÓD +5*öÊT5éó5CëÀÜÏßÒ¸Ñ<ËÇï¿4Ââ7-öüýM+Ûøý¼W++8èQÓT7åííûâÞÃ8Ê-6*ÆüýY>Kº¾óýýÇåø+æ½æ +Rôé4<îCÓü/üý½÷ÀËèóYÕ/7BìúÓ׺Ø.;-Õ´+ÈÜBÄ·¾E+·ð8Q¼JøýI;·,2Ñ;M;1TàèãÝí²K*ÐØ9Cü° +Ï/åîð.YðQ*F¼ÝXÊQY²é1¯*ŵÛ÷óø÷ñÝYõéÕI¼õíµÆTÚ¹½KRØíëî×¹M9MãìÏù@üÔCÈÃ,´G½ÝQÑ+*ý +¶¶ìÜÂGø=ÇSÈÚDZPÂ49ÎJNìB¾ÍýÝVçÖýÍÁ42îéÊH»øü½·ÎP0XòÞë<Â1Júý±Gã¸X¾âíQ?æÖÚÞÍH/·Z +ðýòUÐüì60;ÖºüUåº×.îUâÞ×WÚ¸¾±Ù*UÍë¾Êý½LðÂCÌ¿Pò¾×óÍüæÀ*Uü½ñSß*Q·¯1*¾é÷Y³Çò<ÄÙÛ +ñÞïW½HÛºùôëñøóéíÝìúø=NOéÕM»**ì;KîÛ±1Í8¾Åý8Ûöî/ÇÓV>ZÔùéHD7*¶ÉNíàã¸8HÄ6òTÎNÉL6 +÷*/LðÄ+Fñýµ·àüݱ@HØ÷ýçLÏô7é±ZýýýóN1ÌFÞË<Â1Þëý¹DG¼XJ=ýÅù²TÞ1ÂÞ=ÁWøòýï>Í5RGå-Æ +òýLY@+B8C¶´5Õ0îä@Vüýç¶IÙ¿-;·à6å*Z¸ÞøüÝ8òJ¾SüýïØ-N»Å5U**ÃD½ýÀ@ÇÜæÔ9à*8í¹ôêÝÁ1 +ÍG½ýüGûûôëIKTÀ8»ç5*îèÊ,õÛE·ôïCºóâU¹4õ°Ë*ìóYÜRÍç*¾EÒD@ßµüôÛÒ¸ùÀÓ¸±à-=*ý9*¾ù²E +»ýöYIëñH»ÐWÌI½øX×ùùÐSíÅû°FíÃW1ÂÞµËð²èDûúîG:-¹çèÒÅåȾXßåA°îê@Ì7ÞÇWֹϰ +7ºô,1LðÂCÌ+JÛÀ»ñÒéã5H30¶È552*ô·¼½Ö/¯,»ÐW9â+.K¿õ**2ÜJT4HFæ5*îH+Àø0>°ïÍC鸾FWé +æñ+4ÞßüíIÃC7*ö¿÷à?>3ÑO¾Æ8F¶òüã=/éÞ+¹é»>ÅSåJåÒ*ÙäIùÚÃD½HçÔ1ð*ÀÎú±*¶¾Ò +d +677 77[1 0 0 1 0 0]sl 8 mask 0 0 di +/mask 6545 string uc +*Þô**8ºýÒöüùõûõíùíÝõݽí½ýÜýü»ýûùüùõûõíùíÝõݽí¸ý* +d +/sl 52129 string uc +èÕí´Ü»ùº,*XÑLX¿êƯÁ1HÐãËKÊQR*¹õÕ8>ãV*ÞK,FæïNÈ>QLÃ/3<ÂB7>Vâ0âÝVÇåJÝÚ*IäíIP½;µ +ÜßÔ1,+ÌκàÛÛQµìÚ=í7×°Ù±/<²ëúø³?ÆÈ´íä÷.*úPÊ7÷·Mô5CòÄYMÊEÍξ0³Ì½¹Ô9Ý<*öÇÔ÷ùÍÈX +àȵVóÁF¾=>9ÖòòéÍIIN/Y9;?ßÄWñ;*¶Ê=EPºB¯áÇ*ñÓ3îÏáùñÝ7?áµ.*¼ðY÷úûM/Q:¾ºôTýÁ,0SÛ +÷òåË=å7C°2ù2SSóèB±,*µC¸üüúåíÔë2=Púõõ+*1»Ù·úúùá03á+êÆðãÒÔó?ú*:FN7M?àµÝMT+.ñJTñÛ¹µíÍ9*Jõõ+*¾Z8N³Ý»ùüû÷+ÞýƹJB1ÐDîݽ-ýú*î½âG:Ê-SKöõ9 +*7Ò<¾Z8N³áÃ5Iü÷+*2ýH**2ä¿ÒÄúöïâÍ,**¶E,AG:A:êä1X6á¸ÓïÐFÔçCA4î½âG:Ê-SK÷=***8*¾ +>1:>î0/8KÞ39FÞ¾<*æ÷ê1âBAã*ÐQàù»¶O+ÊCA8.2¶2<ÑZÚKÆ>D¿*¶Ö»6*>ãÒ7Cîßã³F¯¿áSδëN: +:¿ÄM9BñC-ÆÉG8*ß;=âý>7½0ÌÎF±èÜ»ùÄË°Ù0WB¯ðÌ;?B½,**ÞË.*ñÕ>îÚ1Ì*ßG3:Þ*ìÄ,:ã5²ÝÜÁ +±5îDZæéºÚ´ÞD±4ÄEÛ¶Ô,JúC92.1M+N°/åÕÚûù±Ô1ë:I·F?Ð?FOÕÙ¿:>ý6*Ì;ÓK½üF +ø,**î¾+*ø¶*1*ûö±<:>°ûL.ì÷*L1ìÈÀ,³.*J*Þ?:ÆEØ,+NÚU=-AøÞÓFÂ̹+·æÍÉ3ÁYC¿ÒèõN,ÒöX +6Bî4ü¶BXÚJ¶Þ+³F×2,ZZÀ±9;ÙÈÂ,2±Ú8û¾W²K¾¿:¿50¾¹N6¾;°²/Óо޷åÚæÄ5é¶N8AøîÄ*NÀç18 +îL:äºè»P¾ÊU>4òG3Ä=ô¶÷5Õø<.ÞNÀR,÷¿8¾ñí-*FK·äÒ»ùô7Eä´7ØZàâC¸ÃÒ:û3***Å8Þ+NõôàYë +ÄRÛ׿UHZçÑã,üÞ28ÇJ;*ÕÒ¾¹øñCâÌ6Úæ-9@TøÌ5êNÝ5´F±,S;øÇ.æ³ÍZá0?ëïZÚ¶¿î¾-趰;.¾Ôí +åHÀý,ÆD»õ0Á9û¾SæéÚô¾¿50îOS9ÍøKøòæÞÔ±À¶ùCAÔïÆFXòß7+Ö¸L4°H1¶Á:AòZúMï6@ÚKøò1Àï +GXüàÓÜ.+ÖJÒëO¸Å:9--.K÷å**.ÜLTÎü.**¾,¾¶¾;*»ß˸óNEÚ׿UHJ赺¼¯.Ñ*Õ,JÇŶRûS<=/Jû +ÃH¶ËCÒÝàÅÞµ¼ÎéC=2,äÍN¾àîVÛ¸åB81áÃ5Â.FÎÄ=5;*¾Ýù2ÓX1;ó-/Ûø+ÈÐWð¿¿50ZèÑÙÜF.K°¾Ì +¾±XîãóF<Å=»¶Á*4ÔÓ4P*<½Â:AòZú-XA-F2ÄEô¶ÖCÕø<.ÞZUÅûßßXú57î.ãCÑôëÙµê>Ù³æÀ3=õ6S +ë¸ôêÕA***16*ÔÅ-:*é3ÒZ+ñANîÊ1æø+æY6ß;3FR*Ê0ãøOüT4Xò¿Í0¾÷/ëOÊì+HÍ?¿ÊæºÂ**ÂG*,+ +3,G¿á߶ï,ß+1ÒïâL8¸--Öü±¾áHÒÜØ40»É.A¹ÕY½Þ*2C=JÓºÃBF>¹½¶³7Åâï,;Ì/¾S½ùÆй.·¯*A +6îàÁÌàß¿YÚéÉ:¾=í+êÝ1*2ÀðÍ+ÞCÐÀôQ***F¸*-λƴµXîÐ1Z¹1ÕÙíXM¶N,14*=-*4ßóÈÀ=»îIÀã +çÜVÕ.ÜÍ?¿¶¸ôÂ=²0æëÙ½Ìï¶BJÄ-A28¶J7QDïÎE>¾ôèµJ¿ðîTæøÊHÌB¾:ßùJ66Âèë+IõÊ5˶¯:Åâï +0;Ì7¾6Yî8KX6Þ/-F2ÄMû¶õ5õø<.JJí¶5;Í,.FK·äÒ»ùô7Eä´7ØZàâC¸ÃÒÞû3***Å8Þ+¶ÍÆßRñè³Ý +׿UH¾ìAßÁWR7+·Õ*ùC:Jõìõ²ÆòJ4Ìò+áLJÎùÑ.êÚM,ºÚKÕÞåÉ9òÙRÛ¹ÅØô¶BXÚJ¶Þ+³FçÂE+·Ð/â +9*ÆåØ+ÓäÌü³Ã=0î8ÐP½G<¾éìÏ=¹Ò-/¶ëCADïàD8Çá/LÚ3*0öÔôóà1Ü·Á6±H±*1,²6îJBÌòáãYÚé5 +.Þ»÷Wê8ÈçÝæ*KRüC**¿ÕÆèÜÁ1ý»+**¶Þ*î»FJ2*P6ÀJÀ:ALî¾/8*¾¼ýÜ·32A96¿RÁî62îùõF+F»+ +2Aø¾O,ÖúG÷,õ8@¿Y´?-¿.L̺åíÑýØÎKÕñÕÀïà+XÚJ¶Þ/³ÚæÂ=I¶é?A¼*ÉNJZOê=0îO½:¶LÎ>7í7ò +=;Ð0ÑÚóÊ5½¶¯7Åâï,;ì.*íç¯ß¾2ÒXÝKÄCÍC¾-+D0¶;6QïîÞAìó/,JJß?ñ1¸*Þ½9*î.ãCÑôëÙµê>Ù +³æÀ3=õ6S3úöïõÎ47´W°¸Æ³³àÊåçùâB6*Þ;/¾öí-*.K÷å*î6SÍÊ**¶+*õÎîR,ÌöJ4IÆ62XDKTÆÆÆ; +XGLLS¾ô°=YÓV8»Æ3CðUËÂÀï68<ñÄÅ*.ÌÎF±èÜ»ùÄ7±Ù0WB¯ð6ÛLTEÍHû÷Û6*æ6Â.ÍÈõÆZ**Å>*æ½ +9**.íÇ?2ºÃÒ¼ûøýMÊ@Ø5Îüù**QRÌC³íÜ»áP×WÁ@ÊB·åÆÈèÙµý½ÂçL¹+ê½9*:.ãI-¾±N?õï½ý*æI +>ï.ãCÑôëÙµêBÙ³æÀ3=åæ?Ð4ú÷ݽ*ò9È2ÀðÍ+*2ºÃÒ<Ýüõ9*ûÁ<ÌκÞÐËIQ8ÚðÜ7CðÖ±Í;óùíÞÜMT +ǽýù1¾üK3JB1Ð,úíI*DRO*ßFÆ4ê½ý*æJ?+.ñJTRý6*Nòç+*æCðàµíܱ¿>C¾BýWF*J398FBèò·:¿· +åéîäÊ8;9;ê+1Ú¸ÙôBSÈòÆ;4õÒø¹ARò;ÑQåæH×÷ãï2¶÷ä¯É@ûD÷öÇñJ2*AÄV¹²àÎÙåË8»10Ç:?ʽ0 +*¼øýýýùÙIýGË@üôÛ5âÆ+À·ùö-üÍIü÷òã,Í¿2¶¹Ý6½Ø7ƹDÃÆÅ2@¿-XC@UòH0ËD@È/È9ÔÛQç=Oùâ= +6йç6èÞQ=ËB9G¸ö8<ÏÎáåÊ-±*À÷ñî3àÜÕôʲÐC»*2ä¿ÒPÜúýÂQ*¾ú+¾LTÄͼúçæÑÝW:¶Æ*¼¹îܾç +¸ÛøÓYWD²åÊ>KÐ=:ÍIú+æºÆÈõÕ1IÛ¸ÖÝYù¿ÉAÝúêðÝíQÖÕ8»Ý-Ã*÷2ß<¶ÞãMôÂÍâÆEÇ°ÑPϼ/úAØ +-ì·¾*AÆD3ùNâ9*ô3.Bô°?í¼Ü0ì¹R·ó:ì.Ù@5ã2ÍÙÃÍßåÈ-A6J6U8I;øõPJTÛÈ,Ç:Ó¾1ý*S+ú+¾øD +Ý9Iûçðõý¼ÙLî8+¶Zðâý½6æ¯ÏX2*ïû2îß92äìM9Ö»öèÇ=1ÙùïH¶öãëÀV5ü½ýB½ðíîÎÝô¿C¾ºÂZ3F2 +,ÞàúË6çÐRÑ+*2³VܽÁÛÈ¿-NËû<ð1ÙBÔðë/+0Bùú¸Á+*ðÉ´îÝGXñÆÖ¶÷òG»øåIíê´Y¯îHQ8Ú¿*;Ú +¶úÂÂõDÅÊòR6NÀÒàÛù-N/ì1*ì:?àýý¼øÙO,å2¾õðäûIÕ³/Í:?·û2çß9.ÐÈûóáR˵íX·Ýã-Ê»ôäáïÅ +AZûÞêMлé-±*÷2ß<¶Î2Þß+H×ðPÏOÉ°ÑPÍÄÓE¶ÔUC¶ÃÒ3ÖQLü¾³ÕèúÇú2ÂUüýýîí¸ã+-ÄíÛ¿ñìÖXè +ìèÕÍó±åXݼûöÚ÷RHåëíð¾XA½½ìóéÍðÞ6¾æûÛ6JíÄüýݽÝ1@ÎÇ3:3ÐÈôë9:?²I*¶¾Ò>Ý»Ó1*ÎPî4+ +ÖëéÖý±ñæò3Ð<Ò*/Gú+âêG½ùíáR7´êY·ÝÒ-ÒG@æâø17BýúØUQIúìSWNO.÷úK´Úîëó»HCñPÏN9: +óT0Ì:?ãµYð+*â¯=¸û¯Ö°?ɼC6üEPÒDß´í8GÆ7ÁÎæé°í×òß¹·îFÕCãGºö×êû½ø±¿19ü¹WùäзôA·Þ +Á1ýýÝüÚßÁÝâ¿C*;4òJÈñäTüüûùGE¼üDÛ¸,Ç:ÓVÝü*Sá÷ãøóó³ÜݼúîßµAÛ×Z0×Pî4+>VçÑýY7FTÝ +,Þøé6ååG9¶ð¼ÅÁåìF¶ëØøñäO1A½@-@DêÓÒU;MáB»ÞÝË19AöçWâM.ÉÞ¿E2ZóúAÔ·ÆÈÉD»DôôØÃXãÞ +/Ðܯ9¾õô8X.Áõ7×=éÕW»Ô»C6Ü>YÎÁûXõÉÛýýí½ý×÷ïÀ?ÕÀÝà+íFXMäÔWÍ»ìöýýíø**ÈÔ3XZò1·:, +:¶ßòå³ëøG8>,öÑß³èÜX¹Sôؼ»øòOñ»ëDëT,1ÆÈÊ=Aü@Søó1Æ4Ò9*FJTÝ9ýúôàMÁü6LÔ3ÚL*ÝØC± +öíÛµåìÕUÍ8G¸ðÑ34²¹ì÷¾.ÔE0åÕL?è±PÉ5éÀÜå°JÄ,8NU+îúïÅQøF÷íL¿ÞÑÙÆÅ2@¿-µ:ÞåÃåY½òÛ +¯ÇðÖ¼¹QõÇ7ÏUC¶ÀÒQê-îÛÛÁ-ßî´SÀ/ÚBZÞÅãTÚ1Á¿E5óçóõÜÁåIöûèسé.÷ñÏ7öñIçÃÓV÷Vî¿-àÛ +ð+*ÂÊôÛÈÜíïÞ.*ÍXýý½ú·?Iá¿-/îJÝí@·Öåðì1**ì-Méâ.Î.ãõ/ëú÷RTá7C°×õÆTÎM½¾Ò*û*J»Ëñ +Õí¼¸ïܱÝ@ÔMÔ3ÚL*Y4CZß¼óÜGéÝX»T²äÃ*ß3Zµ8ë¾HYÌùZûò-*ú¹òàµÕüú·RçÛú;8æÖÓ³Ñ8¼½êÀù +ÍÍüùÑãÁíPÍÃÛ¼·5÷2ß<¶î0,ºÚôæñíåY½»ÌH÷áµëÖ7¸?-ÁH +óÁßÎ2éèâÌAùß3XÍüWøPßì¸N-¼¹ß¾·ýÌË»ÝýõYûîݽýðÓSKH²åYñ±F:À19**äïAмì±F¶Î*Tð·òÑ· +ø¼Z+,ÑñáÓçÕ;î.ãýOÊú·ñâï¶ðÐOÍ»ÅÒðú÷-N/å1*ä麽úôåIIìEµàWÙâD¿U.¾I1áO5´*Óõ²ç¯Jºï +Ò;=ÖæÚÏ+FHØ°éóŶ۽üùóåÅí¼»âì×ZåãÍò¾YXøýýî³/õôøõÕ9IEUðÝW´è,WÜÚúÂZ3F¶-+88ùñõë +µÕûA7ÃRì:óÓ6.ø:õÌIÅ×ö÷ÅAȲËÙ´ºµõÎÀß9ìâ5úùñÕ»EíßEµ»B6XÔTÎÁå9üýÝé½IÜùßä71?ðÞÜÏ ++ëúìá×±S15Çû±9**ðáAÍFÜÕ¶*,ADGB*Sýð?8FR*,øÔö5*ÎÝ=A@çÖöáÕçÓ;Î.ãùGêDõ¾QêËF·à½Í; +Ó>Yü*SK÷+¾LTîö>*È0ä*ÚPÝ´ùè³5ÔSõçÐáÁ+õÓ5PL÷M-RرäÃñIøÔC*À9ÖòEHîIÇÉÕV¼¸ÛH»Wèí +5ÉëWÙ³=Ûõ@쵶îJ+îQðýÉýÍÀ2,1Þý/îÞÇ-öµêÍÙYÝ»´ÝLòßJT×PæÓ6.øöæÃù0ôÜÛðÃDîÛÝà½á¹¹1 +ã9X;éÞÞ×å¼8ÛOÍ8ÛãÁßîÜ+H/øIWûûG¼øíÕ-YºÚP¯Èë77æáñRLÓºýÏ3SùR÷çùÄô?86ÞÜ;*Rú418>* +Ûäõ·ÒëÞ7-ìÉÖTÓP¿Å3¾ñŽÐKMJ2Ƚ0*åÆTÆ=½¾Ò¾ú*Jº7óµíïG³<üû¿?7Ê+¶È·ùüíÍüøð8ÚôèÍ?= +ô5ËNèðÄ0>ïðÅóØÝý8*2½ê½íö¾Á6²Å;GÆø¸õ¶=ÈF÷Õ¯âÐëñãéÖÜß¿-/Î:?ëÏMQÓ6ÂJôàW6Ç*1ðÇÁ= +1îëÔEµÝ¼¹ê»Kæ-N»?-¿Hȼî×SÝD8MùÇ=A8íëG>½¶À°+EÔFFúóäûùô8Y0ÁÕú4òóíÖñÙõî×MõXô¸Ø +XO=åQ°*7ÍáBÑò±ÌÀ»÷ëù´ô?8ÆÜö9ë¾âõø41XB*´ÌÕÓ²é+*ÜCðäR¿Ô6U>ú¼Ê=»D,ÀÎÞ¹*¶ÃÒLÚö-N +/à1*ÞòBTQíïºõÙ÷ý½íß½åHû÷ð¼÷ÈàÜÈ0ä*úMØ·õÉÍÜڵͻøñâÂûà?P0S·M-ÂÔùøµ+Jƹ»Ð-Ìì9Và +ÂLÙÓÅåM¼ÕWÃ׳Á´¿÷@P÷Æ?4+ºLHÖ´3ò/É8:÷Ü2FßJ¸òV,ÉI¼Q¼õßXÔLÓTö61P8âK·SâßÇ-¶´èÉÝY +½ûòØ6ÑH³êÖW5òT0,»ZýõX¿Ô6MÓ»ÑIµÌFÜÙ¹îà¯Y.ýýNÓ¸ûZìæÓÁQݼ³¾ö°BÚ°ÆHÛüQ5ýºõXÛ÷òZÓ +ÅÔSDPº<-D·G¾2¿Ô½òÎøGã¼ùÞÓ»Ã-üîTØYݸëØ?â9F:´÷Ûà¿=0îÈ+Ŷ<ß½ÆûóÒG³4ëç³Qæû¶Qز. +.ßÝS:Ó.9ü*SKö+*Iûý»ùôGüIì÷TìÌHºõèÙUçXY°»êUçÞ6¾Mú4ÔõáÀûäó³ß°³T²åÐÙ.Í=µ²ÞÛ,9Üú +·òçÓÝ**Ì8û·ðß¹-»¿-HÓ9YÜòûýèÉùèñ±ôðßµáÔÒIQ@ù9ÑE¹B-îSõö°äùýËÜÂAGåÃÞOSð³ÍÚWÁò·ø +Ò?0ÑÄYI³ð3ØMF2·ÎÜøûYÍÍÕù·¶ñ*¼Ë¸öùíYIË@ÙºAÊÕYçüÚÈ¿J9¹ýÜ÷˯Ò,µù¶··8íëG.°¶ÞýCÕU +6öòðÈÛºúø9ð»D6ÜíUËêE=1½ÛIÕXAµòäÉ°/ÈìêÀ9²AZÞÉìýýQö¯³ìàÙÕÝíý/*¾ÑÅÆT¼ý¼¾Òîù**Ëü9ý:ÓíAݶ¯çù×¹åéãÐ +ê¹·óYO-±*¼:TÐÜMY´VÖõ»¹´éÒMÇÔ<µÈÚÏ+åø¸óèÓWÝÄWZå8*Z֯ݴ»F*¸ÜÞ+÷.é/ÐøW¾È=ýG¹³æ¶ +÷ãVì>Ý×ÁC*÷.U6ÓÔAåÉAVÈ85¿IÊâUãýÊ»÷ð¾ÌÄ>PëJøØËD³Æ4Ûß¿ß/¿@º>ó÷FF°*ÝÄHûûöÓÉXɯè +Eä5ÕW»XãJÎ1ßÅI¸6Ó¹ñÅFùòæËá¹¹1üùõâ:ýýAæѹXÛúøöõAA,¹Í0Áµ?4²Wû¿ÙUõä¹9ÔüÚ·ßJÃÙÑA +Z¾¶å.A²è»Äýý½øÇúöï½,/5ðF¿+ÕË°ÚÈD³èú4áýýY±ä¾*VC8LâÔYð*èÒÇù½ßµ·îð+ÚóÃO7Müù´UÑØ´ +åÉC2KUØJPÒúµXØYÛèÐJµØ6Cð:¯àá77ö +ÜZíÀðîÆãÉ+*ìðNÆ6;84;6ãW4ô/8JGéÀýAI6çéýÑÑØòÇÚ4,çµêî0,¾Ì:,ýYµêÖ¯Õç+8NI?,+>°?>Ô +½R1?Å1JTZH·>1*WÒ¿¸?ý½û½¸8åßÏ.¶¯æýùÅY»³ÛêÑJÀAÝD1ôT0,Sûå¾õ2ÑIݸB¸ó4íJèñ¼ÞÓó?Ií +Gòð?6æÌëã5ÑÛ²0ÝÇïSÕ¶ÅM@êÅèÒS³ã¯àÆÜ¿+ź:ÝÌMÅ8GUâI8/ÞM¹ðéµÔ²ëQ·¾ÄLGùD1XL¾Õ´Ròñ +ÙZÁLOMçìÉÀ2¸ð/ÞçFÆT¼ý¼¾Òîù*¾Íü½½üú8ýõôVÛÁÕìÚ¶îÜóõè51õüÓ²Z0ÞÉïÉåAì×XϸÐI³Ô.·Ï +KEÐ-ÅEDÞÎÉ4ëÇÐïú-*¼Üùñà·ÅGC=ó²Ç÷/8ÖQÞâ79×òô5,ù.ó׺ÃíÎß?.>;BïïÎK¹X.ìQ×Íýé1VÔÊ +1ñâ5DÎýé=²æÎ3éñT86D²ãøäÁ5±*XÅïîïýýÝÝHYAIGúýë×ÚÈ¿JT1ý¯¯ùòåÍAËÀÓËHÆøü9²æÌî½í8Ü +À¿U5MU×KZÓ9ü¾õáS?êÙ°Í<ÃNù?TB÷Ó5ZíÈ1ܹSóè,*ÔøðG½µMõÌ;1ÌP¾á@æ±WÙFÃ4?YûO°.ûB-î² +8N?áÅIJTB¼*ÞWýê×±QÅýÕ¸÷íÓMYÌÚöí¸ù´ê8/ùTDB-îN?;ÐóÈ?TÏFÒPÉàO7¾åÇ-QAÞ·ý5×?ïÕSË,¸ç:KAìJ,ʹ +ðÝKÇX2ß×,öëμKú4>àV8èCÏÔºKúø>K>?êßF¾Á¿5ZæÇFÚäÃC*NæPØÏÓ-S;÷ÖëÖö¹0-¼øýûùõåÍý¼F +ÓÆ1IÊ4îOÞEô:çÃåÌê.ìP/@SFA½¾MÕÐóÍ?¾Î9á¿E1ÜAã¼ñìÛáðô»>E¸õð×ðÞç0¶íù88O4òÚÐ3?K9ô +JNÆ:ÛKQúÇ1FZâ¹¼¶T.å8Þ½±VÒT +?PÅR08æº:;øàíÛ8âÄ5±*̸ßT°Þ¾÷à/¿,¶øÛ52éöX,J,Ü;¿Ã7½F+3T³ÄÓ.ÊîØ.ÚøñÜGùÃùÔX²Êøõ +ðèñã¿Ñ9Îà-@ñ´7çFïÏñÎ:3ÐÀõ7**áÆ7Æ5Í6@ÞçFÆ4äõ**2**Ø:AüBW¾Ñ·:?Úýü+οT,Ìä¿ÒZýù1¾ +0T3¶D1Ð4ßµøC*Æ2Ëöì+*çâÌT**Zíçóç¾³+QñJTçí»I-¾±ÂåܼýøëÕõóçÑ1½G¸ñåCHTýýýQÊTQÕQ- +Ô/FË-Sýüû6*X<ÜõéëùíÍIíåY½ûóá¹9ÐLýü3ÑðÝÏéÑ+é,ÌPÀÒ77*¸<ÜóæáõIO³ùë±Ýû*S+ùJèõ3* +î²ÆøÀ1X +Ôå¯üÚî¿ýP*î²-S+ú-NOó×ñ¼ÂÒ±-ÓA1Õñ2½ÌúÞË0º´/*Q=KT:H***6,.+Ê- +Â+Ú:¾-0¶Ï+µ-ZìõØ/¸Ä.úOºéËYÕÍI½Ü½Ý¼ûï¹ý¼ÁÒµèñè¸8ãåË5Fç¾ýP*îò³Ð,%% +d +677 77[1 0 0 1 0 0]sl 8 mask 0 77 di +/mask 6545 string uc +*Þô**8ºýÒöüùõûõíùíÝõݽí½ýÜýü»ýûùüùõûõíùíÝõݽí¸ý* +d +/sl 52129 string uc +ìÕí´ÉA9êͺ***B.R2**Ä,è**¾50¶,,µ/ΰÎÓ.Þ±àÙüº¸÷ôë¹ùóéÕݽÛ÷ó69Óýü×=PÓ豶-*F¶*JH +8:2î2½XÚÞÃ.»¶*îQSñ@Ü»ûûüY**HëäÕYUAÆ4Èõ±*öÌöܱåüGºöÌûùõíçíµíü3ÐP<0*JMÁRAçN@N/ +çùC*2RËöì+¾æNó*S=3-*ÎÏKÒɲ,ÌPÀÒ¾»I-*>»èñ¾è+Ðä+0*JM½REëâ9JT¾D³ÐüÀBÆ/Sßø¼0*ÆD- +дòø²ÞØNÀÒÀýö¼0*¾D+Ðôòø²ÞØNÀÒ,ºè¼0*JD+Ðôòø²ÞØNÀÒî½8**Â9H¾ýÀ7¹´ê×YáPÚúð9*:É·/ +3:***FJ¾2¾Æ¾5:B7¶L+µLÎ1*:+Ð,øí=*ÐMèÑǺÇÒÔÉñåÚ0*êUü´é0**X1ò·F¸>øN,X³Þ×2ò7Ðø×Æ +;=PúÕí¼YASÍP*:8ë6+2N/*L*ò½WÃY/ã½*¾¾0/D*JZû9@*ýÍ:óË.Ú辸¾M;æBNó¹ãÅÕXº³è*9öÆ+* +¾H¾EU¸>+é¾¾:ÃB¾3¾ùGÆY:Ì>ßCLVRQø:SÍP<:¼ö*úèXS9G¸ò¶0*F0,*S+0³Öå¸ø0.ÌRJGÞ¯Wò0Ðø +åH³ôòÊDÞ¿ãM-ȵԲM0ºµ4Õ·:ÙIÚZÀM3¶ZÝí*SÍPÞOÐøT±A÷ùõÍݽÞY>JB*V¸MÍÕÆø0.XTJõ:ÓÙý8 +ÛôâͳTYO»*Èÿ*Þ/-ÚLéؾ¶¿U0¶Á,Aîî6/8ëÞËKÚ=ÂÁá¶Í,APð2:ÌRà+<ú,N»DÇAÇèO¯O²ìF·ñâÛ +**JòVJYWòÏ0Ú6¿ÌKTýêòæÌE?åÒXòû4H¸*ËA:îÂUYæçK5F¯¾=²Æ//ð:¹á¶Í,Õæî2-8ÎÞ73º*N»D+ +¾ö3*Fî*-Ã>ÍüÅ*JàðCü>**Ò,¾ýµVø*.Ä58RJCÞ¿9ò6иøë»ÙìV÷ãß<ôÓîÏ5LV2R6¶K+ÕÄö´J=J¿¹ +Á2ú·Þ4åÙ:ËUÚ°ÂE5¶ó/Ý*SÍQðÓ3:QNéFMTRÑ3ì·INóîÉøÒSÓðÄ+/NRßMµÇظ÷ÀB9BJÔN»-0µJ÷Ú: +зÃU4·L+AOî/:³P3ZÊ°¸3ù4*¾ÉÌNTý8ù°ãÈ=íùRØܸàÁµöö¾+ì*±ÀG»ÃÒµ6îå*ÎáÄD1ÐøóL³TÔV·ßÁðÑÓîÏ-HºÏ*1ÕBÓSÜðÀáEÆÀ:ìÑßOºÚPè=@ö/ÆØ¿=:-иÛå· +áHë×ðÄÂ>MQ1×2*Fƾ3á5+B5¶Ï,µÊî.28¶ßW4ÚàÀáÀ¶´6õSî47XÊNSÞóÖ´¼Ð,*ÕÃõú.ø=SûLÌC¯ÔF +CÕÁ@ +·º@åÇøJ7XûL-:ó÷½â²:KGøÇÀ¾-6BֹϹ16:UÐÜ +,Á=ÀR-¶=/S:óG7ÚCÀÉS¼ß,µùïð6ìïßKÐÜ<̱C¶ÈÒåöÔ@ñøNôÌÒ×Q¾¿LÈQýM*:¯÷F¶×T*öýîÌXºÞ+ +òÚ¿Ù5:Ö¼ÐøõÖAUÔF÷ÜÅÐóÓ-5·.@AñFáÌH.ݱ:¶4ε::·ÐÜCÈéû¶¶01õîÎL=ÞÈÙ϶ÆÒQÏÏ-ÆøúI°D +3ð²˱N/³´CBUMTÝXÒPãÈ?AºR¼Ø¸÷ZñäK*M+·úëÓ0ÚðÀáE¶ÃºÅÐÎ:îL,ÌÔïG7ÚPèMR¶.4A2ð43ÌZ +ß×ÝFõÄÂ:ñ8S±BÇÛ=³ô3ZÊ°¸3ù4*¾ÉW0ääÆèóHå7ù²åÈE<Ð6GGêØ-9-*8×YWÞÃ4Ú¯À=J¶Þ/µSî45° +¾¾TÀ57¶ñ/A³Z6îP´9+ßÃ4FξM.·Ã.óBµòîæ°ìÛáS-FNÎEó¶*,ÅXïN/¸*SÍýMT=;ÈÎF-¾W,äèÆèAð +Ê3±ãÇ;×;RÉäâJIU;¿³¸XìQÉûNìYßHS=Gдî.â¾ÒÉÞÙ»:í4¿¸·N˾4ø,SÍ-âÌÑÓ=Ƹ·å×GSNL7·Xá +PÎZÏÑÇ8SE¶,ðMè1ç+ç;ó·=ÝE+èÄWÙ*¾üNTJý»,J¸NËT,ÚØúöñáÅ9ÝØõÍÒ»ÀÒ*Þ³ïÛD+º@¼úöðM* +H²+*ð6ØÌNTI5C°àÂýøëõó<³P3:ãÒ9*ÞòèÑQÍH4JÒA¹Ö-ÅÁùúºE/SûX÷ïÓ5YËDÞ-H°**Î1îäÈ8OÞD +::N+æÕNÊ-6*F8À6ìîâ/XOÞH:ðê9XNÞÀ±ûN»ÜÈÕPJÂ/½+*¾é+*â+/׶LÕ:Ó½ôS±QåÒ+8VH¸8·¹Sè +<¾¯¾¸¾À¾ÑJ¶Ý0AZîR,ÔM;WÈÒCO¾ýÀ7¹´ê×YáPÚúð9**É1SáøYîÞNÞ.Þ33>1F-ÀÜKTµýG¸ëÇIéÒÜÈ +ø*>É/+¾50¶/¹³+ï,Õ6î0/XÞßC4FØ¿=ù»Á*å>Ï@îì@ì¾áSÐܾÇ=6¶Ó6õ6ú°OìGâçàF+¿ÔMèñͺ³,Ô +ÉñåÚ0*êUü´é0**X1Þî.2èQíÆÞß0ò9ÐøåPÅôÚS·PóLíÕîÏ-æº++Õôõ45YÖÞ3-FKÁVRîâ>8;óWAÚ>Å +-V¶@ѵ>îR2ì¯áKÐÜ/ÆM·ÆÀHØ8S=Æ**îL*ò½WÃY/ã½*¾¾0/D**Z7769F¯ÀÑ4æSNóòÙù´Û×ðÉÁOêUß +MAúÔ:ÎBîÀ,ìÞ¯<*ºÇ,õ0+*Ú*嶾Ⱦñ»ºë7õà÷ÈIÌøàK¿ÜÓÈM7¶æ2õòÏóÆøÚ=YË*ÝØUèMÅ8GÌ+*Å¿ +*JTîÞ*J?Þ;ÐÜç¾4¿-7æINóîÉøÒSÓðÄ+/NRßMµÇØT÷Â@É+°OèEZ¶°,µ¾øÒ<å2ïä0ÌZö+¹ +Ú¸ÆM6¶L0AÔÎñÆDÑ.öT±A÷ùõÍݽÞY>JB*V¸ÖØÅ°øÈ,Ø,RÑKTý8ù°ãÈ=íùRØÜ4HTµ¹MÇ16îÊ6=PèE0 +¶?4Q¿:/ÐHоÉ>¶Û1QêðàNÍ»áßãFÍÈÌ+è>ÇøÚ>Y˾ÉàÈæÚ·ðäǹ+*¾æÏÈÜQT*îT.Ø1Sû¸ÏD?UÔÚîK +÷çTöæ×ã+*1éöÖõX°YDE¶ÄÑÕ°:-GÜèĹ3¶Ô3µåîØT8¿áGêÚÕÅÔMèÊ<ÞJ;ãÑý;*ÆÖº0JQLY:Ó*öÝ@Z +Nî¾RUR9+Kå:Óé»ÌEôçÌ?µNNC¸4H¸Õ¶?+Ñ·A,Z@îL/ìBß3:FîÀÙ>¶Ã/-+ÅäöL08BJ·áW²H4¿E-·Ð/ +ÅBî4EXëß/ÓÚ¿Å=¶·à/?*Å»ÏëÆøú=YË*·LØäCÐÀDWÏÍ5KTÝƱPÊ?QÄâÄJWD=º²»XÉâ>Ø01Ù7S±È*á +>ÑÉÖÕ**Ú²NÐP3ÈY=S+¾é๴,L3ȱ*æ¯MË;SAõÄ×ÆàELè0̾úÆøK¸J¸N/*X5í÷3²QÐôOé²YÞNÅÒ7H +IICS+¾éµ¶µ,Ò7Ù´+*7ÈÒ¼@°âÂ3ýóÙYå³äYJèÒ<ÞÓãÅII¼úöÓóQÐøÛðáUAÜD³¾A@Ì*ú1îP6̱»ð<2 +жá4O+;*1XîN,XZÞÛ3F+¿É³2Ŷ¯.1ðî>U8ûß34F¯Ã±ä¶ã31ÈîâK8=ãÛXFËËñ>¶201*ðP+´ÃÒåEïè +.î×öïáÉ9+:øÎ/*ƱæðÒÅ8LTüêÑäÌÌS-ºîIõ@?çTãÁ-ÅÂôÊG9JÞ;;ÚKÀá:¶û*QÒî468èÞCÐHN¿¹ù» +Ó.å´î24Ìî´0ÖÇ0»ì?KÜÉÈ8S±È*SPû**PÕH»øòÏ*L»×á;·Ý@3JQ±:ÓÙý8ÛôâͳTY*¸/È1êõÊW8VJÚ +Þ?-ÚK¿5¶¶Ä,Å´î<59³ÞÇVâî*×:ó3·Ú¿ÀéD¶Ò9åTïÒWYKà¯ÐFNèUζLD1ÑÑâîXÇÌ>ßGQ㲿ç;ó÷JÚ +D+;@ú/**X1*:-=FüÁ°Ð/ÆèIEGØñPãCÓ5»ÝìJñ/.FðÖ¹K¹+YÕHùô08ÐÞ?UFçÂÉNW¶¶S7ÅJ:EÐHâä +ÑüºÖ5÷:óWÐÚßå±,¸.E1ßñºÀXLÞ;0V.S±È*ý/EôêÖ±ÝÄ˶÷ãI**@1ÐH<ó502/¶BTÃ*÷ÞN9:ÓÑíìÙó +äÈY8@¯YGðÓÀ¾à¾54¶OBµÏ@+4ÚE°ÃÑýU*¶>51D÷Þ.8=óÃ?F=ÓE¸ºN7Ÿï².ìOò7QÚKÂ=D¶É>A0;1 +ÐH/¿M¶¶´IÕÛZ?N»¼2ÕPÞ¸·üÊé*¾»ãí´ë**ÞóÀ»ìCÞÛàâà4Û¿Ò8¼SÍDÓTìBξ.ó.îÌ·V³ìÇ:Ü+âê6 +îà5Ì4ëÃ7FÍØÁã¹±,åZïÊ:ì/àGàHSÀÑV¶ÃÒÅZîT6 +RUïUK÷9*:ÎK¿0**VõØðÌ+ì¯NLAç.Nóí¯YÔV·ð»9OéUQôðîßì+PàN»á*QË÷Ú@ÌËÞ»ÐH¹¾ÁO¶È4AÈî +H9ÌDà×,ÚQtúÆøÔ9°Jè5Ķ.4¹Jèñ¾¶³,úèXS9G¸ò¶0*F0,*SÇZ7úYÑ8*ÆTOØ+Sû¸ÏD?UÔÚîK÷çèçU +ÛÀÊêÆøÈ5°¾Ø¹1¶á4QHî01ìÀÞ÷HÚKÀMX¶ø-ÅSîB,X?ß·¹ÚßÀáB·ÉÒåêõê1XLÞ;0V.S±È*Y³ô<½ýü÷ +ù1úÃ,.-¾O3çëCì²9иÛå·áHë×ðÄÂ>MQ1õ,*FƾçÔ5+B5¶Ï,µÊî.28¶ßW4FÃÎRÀ+¾5F¶Â5A>îæ.XH +Þ¿/F¸¾5ʶ==Q4îì>ì>Þ74ÚSÃÁI¶M3ÅÇðè*Ì/ßÇôF.ÍEòS+¶¶ò?0AÌîÞ0@ÀÒå-ïè.Ú>ÔÂäË=µÌÚ5* +*Â̳ãòáË0F5ÓÂTÐ5ÆèAðÊ3±ãÇ;×;RÉäâJIU;¿59èQ,æBJÇ8S±È*-Ã>ÍüÅ*Jàð·ÅÒãÒAàú8SY3½Ý: +óK*ºØN»H@ÕP¾F;Wñ6SK˼÷ËÓ:óðØÔÚÜÑÈر¼+ÄSPA¯Y+*¶çAÝ×3мÆ8Æ8SÍÙø×32ORÌ+J5ä +å+Ð,øí=*ÐMè0̾ÍËÒ*Þ³ïGI¿üNTI5C°àÂýøëõó<³-QJ0Nó¹ãÅÕXº³è*9öÆ+*¾H¾EU¸>+é¾¾:ÃB¾2 +¾U<Æí7ðÊÀFîïN9Xïá4J¯2=ÉJââ41øîP+ÔÁÒ*Þ³ïÛE+èÄWÙ*¾8OTýêÑäÌÌS-Föðà8ZÞ¼ÈØ.Î8îR.ð +±Á.Ï:ÕIÚȾ±TÌI¯ÌNà¯9N³;åD°ENËT,ÚØúöñáÅ9ÝØ0OTµýG¸ëÇIéÒÜÈø2ùÁñ8BÞÓWÔ/Â5Ú°¾E>¶+ +1åRîæ0ì:óÓ@ÚÊÀU6¶çÀõêï°GX:ãÛGÚDÅÑN»<,Å3îÈHìæà/ÙÚMʺÄñ°¿ìæá6ßÇ:ÚÖ¾F:Óýü×=PÃÜíûêòæÌE?åâèóûL +ÍH»*M*¿*/+Ã+O+?*1WîÂ+ÌÎK@>¶K2µMî>.XËJOÞ+.ÚÓÃÙì¶?,åÞîT.048ïÞ³+FòÁ-ØB,¶ÉB·6åÇJ +Ú°ÇU;¶Ù*SJN;ÞSÓ×´¼ûظÈÒQ=*2I+¾-8:ãÒ9*ÞòèÑQÍH4JÒA5Ó»âØÕ>ÎáÆèçµõ븱ãA1ÉÖÕ83é÷ò +Û4Úò6îZæ0*8ÄÞç4Hè¿=è¶;Éå³ï°Qì¼àß2F¹ÃV´öÈ9Ì?ã³0Fº¿-V·I9ÙNèÑ庿ÑîýÃ-2¯ßY**î¸** +GÇÒ8¼SÍDÓTìBξ.óFSUA5+8ZJÜN»-0µD÷R.ìËà+¸FC¿5»¶ï2QKøî;0/ÌìßÃ2ÚB¾±3¶ÃÓÕXñä-ÌRÞ +KOIMÊU˶O:AäîÒTìÃÞï,V.SÍPÞOÐøYÖ¯åÄGù³êÂÉYü+*Z÷/ÐÜB¾-0B/æ°Nóí¯YÔV·ð»9OéUQJóÈö +ÌWTÄG»æ·ÕJî80XÄí³ÚGÁä¹÷¹36ÅÜðR8Ìëß×.FGÄÙê¶ô=õ¼ûÔVÌTå»Óâ¾ÒAÚZ9N»DÊAÇ踷üÊé*¾» +ãí´ë**Þó4QåQÍRJÉ;ÓYATÑ°ãÈ5H>IWG/ÓÁíÈÈOY·ßï7FÐÝÙù¹ù¿ÕÚ;Ñ>FDæÁ¹¶C,3=óûSHǾ߾º* +Ѹî45Ì»ßÜÆø²,*ï×Q+*J:³.>ýýYUïUK÷9*:ÎK¿0*2VýÉ1à30+à*D8SÛìñÚ¯ÍôV·MLÈÏÑÁô¸*S;EÅ +<ÕPîZåÌ*ß+AÚO¿UV¶/-AÃîLðÌóÞ/<ÚÆÆE±¶*7AÑîL+ÌKãÓ2FVÅ=Ú¶Ù*A8ð¾,8TøÇøÚ·ÇÉ··Ó=ANî +L2XÛæ3JFÏÌ>6ÔÂäË=µÌÚ5* +*ÂÌó>*¶óÓûÅÒÉ5ãëÇLU<³TJÆ8SÍP*:¼,*1FÞJ;ãÑý;*ÆÖºXÆòÇÐÌ.òÌÏPÆÒ9Ð-ÐMèñߺ³,â3çDåÆ +4è²ÌÅäÈÒGÄA7í>ç;³P3ZÊ°¸3ù4*¾ÉõÍÒË¿Ò*ÞóEFE+Ï.ãC*²ßúÚJTI5C°àÂýøëÕã6ñO:³Áåå/ÐøÛ +ðáUAÜD³¾A@Ì*ú1îP6̱»ð<2ÐÆX3ì:JÇKD3¶Ø0ÇÛLù<ó·êòIôâÅÒú×QËGGÑ0öÞ½À·ÔÄÒÕJ;=PÜ÷æE +5¶H+I,S=Í*¾ÛÙ*:/иùíÏéÌú×Pó/J1ÆØL2*ÚZ¾U²W52A¶ã*µRî,8ÌÏÞ×7FCÁ-8¶õ+QÆî0D8èòç3Ú +VÁÁé¶ø9Õ,ϱÇøF´0ÚξU½ºÙÀ3ÎÞßKF?âÁÛRó¶ÍÒµ +Úî6SíçàK6ÚáÀѺçÕN»8E3˾à¼@¼úöðM*ÂH²+*ð6Ø´MT1ÝÒQËT?µ6RJÀ¸àÃå̵*Î4Îâö.7±NèUZ¶< ++QIîÐEÍ<óó.Ú÷Á5³¶Å=ß*AïÏÃÇD÷.:êãÒ9*ÞòèÑQÍH4JÒAé³Nóí¯YÔV·ð»9OéUQÚöî8í˺àN»á*õ +<øÔ5ÌSÞ?²â:+A?î@/ÌÍJõ<ó·êN²,.ÙÖîA**¶G*¾XMTÝXÒPãÈ?AºR¼Ø¸DÇ3ê¯40¶ã/ŵîÒ@ì+ß3/Ú +1åÙ3¶×0åÕï:ODBS=Í*Åû5´êׯå¼ëZñðɽ**VHNO***B/21¶â*µRZLîÔÂäË=µ8ƹ+*¾RX@/*ÐÖ¿Ë=?ÎýH+î*S;***ðÑ +Í-S=Í*Â74SHû9+¾Âã±ÞC8¸,:<Î.î¾,0QÅ+ìÑÞDÅøõ<³¶Î**:3Ð,*Õ7DÏ3BêÌÆÔ·ÄÒÎäDÙ·¯Nó±;M +PÊöYÝûÜPÐÔÏ4**ðJèâ=*¯±ÈçOêU**XD7SûX÷ïÓ5YËDÞ-H4¹1*ìVçÏ.V-,À>*ûÈÂK¾·ÕUD¶á*QÄîÊ +7̾ÞGC²·ÐÜBT,**õ6Æ4*Ú´æÂPÞZáÔ.ãC*òÌÏä¿Òü×QËGGÑ0¶Z÷æG@ìæ0YÊàÇ+>.ÚÇ¿M:¶Ö7µØîT, +ØGSOºüOÒÂü¸ìC:³ð3æH>Ë»:,×:óW´Úɾ½¿Ò*Þ³ïã²,öVúöïãÅ*2» +æ,*âC²7ÂÒ̶µõçËEQÜÙKÔêôêMNóéÃèÒV×ðÀ1ãQUßMõâöTA±MèUX¶¼ÎQ9ÎÚî°RY³áYKèê=JÈSI*¾ç +ÓQÍH»>¾RY@Çêô,ÐTÞÐÑY½üú:?º»ÂÒXõ·ìÖQ¹ÔÚ;;ãæçKW,*1.:1ÐHÝãÊPJÂ/½+*¾é+*NÀ0û¾ÒìºøñäÇñÄ6C»ÂÒ¼;åÊBUÌ6Ç7¿Ø²PöæøF +Öó÷KNãúùí½íÜ»ø·:³ô3îÝZáÌ7¹ôéÖ3AÝú-*ÞðKT-*Þ½*ã*OÓA6Î?ÆDÄTÂí¼ÅÒ1+îç<ó·Qä²,ÔÉñ +åÚ0*êUü´é0**X/0ñÐóÒµ6ÎCÆÈÆ=åG9ÐXX>µQ4í+S=Ë**îL*ò½WÃY/ã½*¾¾0/D**Z7769F±¾Ì:*Jö +*S/¶ìç:óÁò¾LT*Z*:3ÐH9ãÊP¾Yë?óÏMÅ8å*¾áJ*:ÓåÆØCXð¹.N¸NË»,ºÓ×ÉúûùåíIîA4:6*@»ýNÛ +¶ÐH9ãÊPÞO¯O²ìF·ñâÛ**JòJP9<ó·K²0Ðø×Æ;=PúÕí¼7¿=Í1S±Í*-Ã>ÍüÅ*JàðC=O*ü-R0RF»ß*ËÁ +Ò¼ÕúöèÃÕP7îùIJÉ56Ö͹.SÊü6-Ú;¿úì:å>â+9Qøîº,<öÄÂHðÀ2ìQKP¼¶Î1µ4:AÐÔBË+*ìC:ó·Lâ +²,â3çDåÆ4èNã:ÛÀÒú×QËGGÑ0öÞ½ì3èÀÆøä>°ÄÃ4üÃÖáÃIF;éXÆÍCÍ>à·9âÒ1õ4ÑÉÖÕ**Úê-=@ÌÒìü¹òØ=½ÔS»+æ;ó-*1è@ÌòP:8VJÚÞ?-ÚK¿5¶¶Ä,Å´î0SY³ßã>Úà¾5ë¶ß01ë +ï°GX:ãÛGÚDÅñDË7Síîà/ÚڱʺÈñð¿ìîá6ßÇ:ÚÖ¾>:ð,S+ýZ,,**ûFÆøº9ÐD+Ï.ãC*âß9ÛÁÒü×çÏG +µTÌSÛæùó0ÖXJÞÛòÛ×¾5@¶à*Qæ:54F×ÜÑ;¶4,µ?öà+XèàWµÚ4¿±Z¶?,-/Õ4ñØ*XLå+X6+Ú³ÊFÈñÂ: +XßâÓ2ÚW¾><ð*S3ºüûMWÊüøê·å¾Ò7F+¾Z0NóòÙù´Û×ðÉÁOêUžÓ:ÎBîÀ,XWìWYP¾,õ0;ÜÓVï@SYËà +S´FÇãñض×=µIïZ.ÌPù·+FHÀÁA»Ç+1Yî¾Uìíá0ÆèËYÕÍI½*ùóEûï¹ýÌ*SÍõKÈ=æºNóîÉøÒSÓðÄ+/N +RåÇظ×2î*-8ãJP0¶ï/1QôT.ìËà+¸FC¿5»¶ï2QKøî;0/ÌìßÃ2ÚB¾±3¶<=Aìïê6Ìë`F°Å±Ä·ó3Õ2 +ðà6Ì°ãÛ2FX¾NÆèÍQAíÜ»ùóéÉøùáUí=³-QJ4Nóí¯YÔV·ð»9OéUQFóÈöÌOTÄG»Æ·ÕJî80X¼íW´ÚÐÞ¹ +ض66ÅÜðR8Ìëßç.ÚA¾ÑH¶ØÇåÇø>¹ì±O0N»ß/S:Ó½åÌû¹öî×½ÕíÝÑÝíÜûùN»ì1·Ë*³áÔW*J<öæMNóé +ÃèÒV×ðÀ1ãQUQGóZÙаçUX¶º-Åîõ´üìËá7´FIÁñµº//Åôî°.8PÞ»+Úñ¿ùѺ<+Á+÷2PóÞ?@FùÀ6ÇTä +à¹1*Zô/Ð,Ë1W¾îçNöC,*Fëá*¶ÇÎQEJ²°½3*AÆÈî»Ýü4*î¹ùö.ÈíÀ**ðX0J1,*3úɸ9-Þß´ÜÚ*øìH +Ü>ÆèÖIµ8û7*Æóø-S/¯Þ72*àÊúÁ+æ»Àß»ýø?YÎJì»6**íÛ´ÜʾÞåFÆèÐY1Iü3*2ùü-SOµç72*LËúÁ ++æ»ÀKÎ398N/àùB**ܹëº3+¿ÍS:?½AYI½üú+Â+JüúùùÕIÆÈÚQµÜý*JÜǽB*ëµBÀÚóJTæÜÅ+*îGéäM +.R9BúÊ,SGºùúùõ1*Jý+*ðÝÝûJTɼ»øñë1*QÇØý2-¶1ÐÀø»6**í×´0.F·¾¿¾ßåVÆTö-¶**Ľ½üó-S +Ï÷ñåË=í»*¶ÈÒËABâú·ñÁÒZ½9-Þ:óÇ´01Ò+ÚÖåàå>ÆTÖÙ½ð.+/Zý+ÐDñâÇ-íº*:êâI6¾ä56LìF÷KT +îÝÅ+*îÛèP¸ýüûD¾±úÊ,SãùøQÊ*+îÝÍIûJè:»;̹õ+ÞSдÜÞBïâÅéìæN»ÓËâÜ»ù³*Q*ZöC3Ð,ûQʾ +òíÕ½Æè¸õ仹ù+¾V=ýZ*¸ÍZ2µðÛÙDSí²ñOÐ,ûÁÍ×:óÇìØøôéÁ9N/ƹ:*.D÷1-Îû0MN=íéN»ÐËÂH»ø +³*ADÎöC/иøùá±Ý8*¾ó-*Âûøó¿ÒL×ðôñGÁ6·B÷1-Îû0ÑÓÇøVEãøóµ8Z:ZöC/Ððûé¹ñäËE¾B*âËùô +ïæ-SÇëÕ¯ù9*ÂB÷1-Îù.ÁâÌOÚÏÇøVE9;J»Þî±4N³ôËýìû2*¾÷JTBÙ-+JâǽB*ûÅBîµM=óß´ÜÒ*ZH3 +ÐÈóåÍ+¾Þ=Ö5+*6ÛF¾8>¾´PèÁëºÇ+S:ÓæÂõ:*ÂPÀÍ;*ÞçH0*Bù°EY¸*æÊ*S+ä-**Eݽ*²ÒÌF¾Sïéµ +:Ó4?ÈÒRÎIݽ*²ÓÍÖÌFµäº¿Ò4=äËJÁ,ûü+ÎÕ¾À»ìºçÐOݼ-S¯NÇ>¯ìúü+ÎÙÂL¯Ð>CÐYÝ¿ÒدáÅ:MÌ +úü+ÎÕ¾ÀSÐ6÷ïMݼ-SWàÂ3?¼ý*æWL;5C0*2ݼ-SÝý*²ÚÔ*UíI*ø8V*%%% +d +677 77[1 0 0 1 0 0]sl 8 mask 0 231 di + +QP +%%Trailer +%%Pages: 1 +%%DocumentFonts: +%%EOF diff --git a/doc/krdc/krdc_window.png b/doc/krdc/krdc_window.png new file mode 100644 index 00000000..423233cf Binary files /dev/null and b/doc/krdc/krdc_window.png differ diff --git a/doc/krdc/preferences_profilestab.eps b/doc/krdc/preferences_profilestab.eps new file mode 100644 index 00000000..b9358718 --- /dev/null +++ b/doc/krdc/preferences_profilestab.eps @@ -0,0 +1,284 @@ +%!PS-Adobe-1.0 +%%BoundingBox: 0 0 602 516 +%%BoundingBox: 0 0 595 841 +%%Creator: KDE 3.1.92 (alpha2, CVS >= 20030921) +%%CreationDate: Sun Oct 5 20:09:35 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 10.416 3.125 ] [ 3.125 10.416 ] [ 3.125 3.125 ] [ 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 3.125 3.125 ] ] d +/pageinit { +35.52 24 translate +% 185*280mm (portrait) +0 793.92 translate 0.96 -0.96 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 304]ST +B P1 +NB +W BC +/mask 7904 string uc +î½*¾î1îD*J¾Ç¿â1*ÞIÝ.+îYüÉ*ÞíY,úÆF<ìILø>:ýüÝÝÐúQ*¼âZOçÑ*FÆßÈÐQ+DîIîGW,.ÖÓO:*ý ++Xè<ÞݳWàÐ>K*ú÷üAïûIìùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ùÝ.% +d +/sl 62608 string uc +î½¼**ûÙ6*<òÊI×.»ôȱ*9A@ó¼ðÉ+Í7ÙÕ*Þð4Ä»üñÚãùý½ß*.HîÕW0*¸´ôüû÷ñÍ0±ÜØ7âøÈÔ½ôÖW¹ +7*îYöûóíý0³÷ê@M-ò¼ðÉ+Í7ùB*ÊWøûMå÷DÓèÓ°ç<ë<رåõåûó³ãú´¾·õõõº +Y6:ëI5³+¾ÝËëFúûößUÅåÉ-ÅÛô+3JÒðù±/D9*öÍÁûÐÛTÒã¿@ìÖ×CÍʾýý9æ:½ýÙ5F5*KÍξ÷ýYRàE +K*JíÖÞ,9*¹õQéñç5À÷V6îFüúóòèãõÍýü»YYHùñãÇÝ@D¯4öìûùíùݽ½îÎò+*öÜÛÏÁŽ½»õÆÚ¶ðÙóÜ +B+ÙÝIRÍ3+¾¹Ó6ÓRÇ+í¯Mß༺L,;Éæ=æýö?/.¶äù·ü>ûü¼îÀòý**î÷¾,Ì×JV¾ñ7½ý°ÍH½X´î÷õÅýN +2ZXõûùåÍ3HÐ×+>ÛIZÊNY2䱯¾¿I*Z*î4üºïðåÕÝíY½¼ÅÝü¹ðãÅAPC¯4¶×ûùE8Õ½ýßJ²+¾I?6Ú÷õõ +óáåñÓù8¼ÚîÏ1R¾øõÝC=*¶ËϹÈFRYÚ±ÂUWÄQNÞýý1.òñýÂ1/.¶ýì3»üI>Dý°ÕãîP*òÄ,¾ýU@î¶QüI +Iìääý¯±XËöG¸Ï,Øü½µÈÜCÈÈH<Ç+NÙý´ÇH9.ä±S¾¿ÝÀO+*Þ;úS×á¿9A9½º;Ùý8úõï°CHM++*ÞS42* +*P4.**µRÞßõIýUüúU½@*öÌÐÇ5ýÜHüùÜÜùîÚÅM¼×±Ï*óí9±P*î=EùFRÍÚ²ý2íÞ5ÍÆ*ú9¸²ò5¹Ø?:Þ +Å3ؼüKöà5ñãL@+äE4¶ä,¼SÖýý7ßFýIØø?Ðâ¼ATJëýA7ôã¿SçMÜ· +B;Å»2BÜ3¿JN.>R<¯91,RßÈû-Dç½ûÊ,/XSÞC5¾Æ1²*07JÚR+/ÙùýýïçP?9Çâ¾³ìI@à-¾÷W2:îå¼±I +ÝüÂ;æ¼-½Ü;¶è×ÕÉIÍÜÍA½»´èÞEHºÂ,1ÀB=4*Ì;É>¼úÊ1*Þü/ý»ù/VLëÞÁýáÖR¾øõAC?*¶ÌÊÓHPú: +áÃÅL:Ðö4,ö¹ûÅ*+,NæVÞ?/F±¿C*5ýIÖÆ;0AT¾Oô±Õöý÷÷ðØÅ>X*7õ½Ùîâ5Õ÷U6ZîÅÎÁAIì1ÎIÀIý +õÙ²éæÝÁ9íß½ÅìØòíý2ÜK¿5L.*ìϾJý¼NîT4¶PúöW·õìð+*Þ·Ú¿åHÜæJÏñ0XøÙÀÃ,ô¼ýÈ9±,*ÕÖµí +îÚÑÔ?VZXº<,;ÃÕ:JûüAÙ°O׳+Õ>îÊ/ä*ÒîýÎMÔúøN2ÖíÍ;¯ü9ÜRTøÆ-0ZòüQ3ĺïD½Ã+4ü*ùÖKöü +4üÚFîÑWIíìû¹õÜû·éÑ¿É=ö0/ÌâðG4ÆñëÅ.XO+ÙF½ÜÓòçQH**Wõ´öêÛÏ1:Zõæ»5½5VÞL+±Ü½SÄC+¾ +Y09¶°Õ¸ýÌ>ò¶ÎËÅR¾Q¼ýìÈ·ñ:9*¾ýM2ºõêßÑ-îïÝMAHºKWý4/êðüÂ=P.*¼Î +¹÷뻵0Ç8>º³,,2ι2¾êB3DÂç¿/ÚоU@R9¾èB3<.çÇ:RPÂ,?¸ãYPøÆ-0²áúûã/êöà³ü4-F¿I*î6üV +@îÚ³IîL/**µî×öãÃ,À»ù±>Þ:ÓUµOÚöÄA*ÞÃC/ì¹µðêá±öèÇÕì>ÊÞD+ÊؽøÂ2ñ+*¼2@ûæ:é?Ê23´Ü +-T´½æë,°ºðD½ã+8ÞÍ**î6üV=îß»IKN=Hº=»SϾWåÜûùUSÞ7ÍIµãÙµÃA*ÞïÃ<ø¹öïçß±øçËĸ²7ß- +>ZÞùÉWB;*B¾-í;ÊÇ2´ì-úîøüFá/Êöâ³üÈ-FÅII.F¶ïãÏQÕìÕ½I½»·ðÁìãÊ,T϶ïÔ°å¼<ûìÎÙNí¾L +@üC±WÅ1;Wõ6HJ+NÙIûÅ+¹8*ÖîüÍÁÓ=öîÇéú?1*0YáÍé+8ºöïäËIõæÕ½AÝ»¹Gó°P+ÃæÙµUÔWÝ×¼9¶ +8>H²âη-ûZè?8Þ=>¾ÒùIÍ2ZÝ+*½ëÝýݲ÷.ܯËå,+ÕïQRÇ·0*àåÚÊS¾5äÌëȲç-Í*¾5úåìCXÒJÏÀÚ +Ì×ùQî34RÃÞùMQçYÑ+¾-FÚ½½Çßâó¹-BÝã+Ìá9ÓÑÓç;*2WóÐP+VÛØ?åÌY·ÝýÙåÌF¶ðêë-ýXË*±âÛ½½ +ä;÷³B*¶¿RÓõíY°A´*í×Þú@4¶6½ÒíÚµÅ*JäóÉD/:²æÔ>UÌâý9>¾õU6½ýüê;E0YÙ4*æXâÕMTÖ±ºÜÔÓ +ÙÕYTÓ÷ç¼*AY´ãÖï>üý0,îÜÃÃÇõí¯*î˳ä<Ä7ÛºùðLËÍ0.öö¯UÀ/Qä.X9H¸0Å<óÉÕîÓÙÍ*ÞÙèÒÖ6Ö +°éà3¹øüLV°¼À/ÙðéµÙâ*ÞׯÑôVC¯ß¼+Yû.*üéAZßÄCÉ´7ZÉW²ÜD;2ݼQ@+ùõ9*5ëÃõIÉí½@ÝýV¼ý +×úü±÷ûÑàùÍãõIÉí½@ÝýV¼ý×úü?;ãVàÞùNûï±·*Z½Në5úûåðÏ*RÈÖî°í½ûð**ÏÍìè4ãõIÉQ.*+ýàW +üýó+¾X½ìFôY¼ÝýVÜB*-¶ô·ôùÉOÜíÚ¶ñ³û×èÈ*:ÕB¼K×ò×´AAìýýý*ô>*µ*JåñX¼¹ÔêÉ/*æ³ÍÛ*˽ +6WHÌO±Óºõì?ÉE,¾MÆÎù³ñXìèöã,*òØåX:äËYûJ×ÂØÈáÄËýÈV÷8-ÒüéÕÉéÀ¼×**Ôô×5Ä·±U6IÎVøY +ÞSÕÌíݽ*O*;*14î2+Xº**B*¶T-=,CºK2¾àYËDöYåA5*ÞèH¹ßDYôÉé-ÈOWÔ¹òçóù9***Õ4î2+XºJC +Þß7Nî/Åɼ˴åÎãíÝ +½¾ëÝ9Þü+¾S¾:ÖоR¾54¶¾-ñ+Ã*Q°ÎJîì5Þü¾Â5+ÎðK¹Ô»7÷=***Ì>**R3B.¶-+AAîÈ/8?Þâßç3FR +*Zó:7ÌJßÄÀKDQüÀ°Çßãäê×ùúõ-**¾K*¶I4Z:î¾+XZÞ3861Úü¿¹9¶=*FݹåʱÁ5ü0**¾E-¶;+á*õ¸ +îà1*¾×¿E.¶Ò/APîV74+¾ÓÁ¶=5Q:îL6´:6ÒCðãIR@»ÛêÉE±µÝ¼,SݼÚè2JJ*Ò96¶Þ*1BZHî¾0ÌêÞ +Ã9ÚS¾MY2ƶ:*êHùäɱÕñû0**¾E-¶;+á*õ¸îP/8BÞ?=Ú+¿Ñ´B-*Ô8<à7;F¿¾5ZÖ¿ZÎäÆ?ýJ×âùZUä +FGûõ+**Þ:*º9/B2R-¶,+µ¶îè0Ì3Þ*ÞÇAÚ;*PIG±ãÖíï¼-**ÞÏ+Ú;¾ÁG¶Ñ,Ŷî8.ÌÌß/-*VÅ2¯*î8 +=X¾Þ@À¿BIÌú-ãÐ1ºW÷ðñîÕÍZéÕ-Ö¼*:4Z:î8-ìºÞÏ7FÆ¿E/¶Ò-+31,¾ßIÝV÷ó¹Ö9+**2+R0¶K+Ó+ +Ŷîà04-Y>ßÏ.F;¾B¿±³R.*Ûì;àÇ+Ú3ÁË.-S´6÷1ÈOPö¸°ããÕM9***0+*øR¾¿¾-0¶¿+µ¶îä0ìûÞÛ1 +Ú3*ÜÜÚðâÕíÓ¼.**J.JCÞ//Ò0Fà¿5/¶Ï,A<îÌ+ì6ßß*¾X13ï64ÌWß0À+<ýM×òáÑAÍϺ·*ã1*P+.** +³+Ó*1>î,1X:ß¿9Â+F:¾¹Y2ƶ:*FûöãȯÝ5û4**¾M/**P+8NÞÓ7FK¿B¾-,¶ß*Ñ/å.îZ*JÕÁÞ¶L.µÀ +ï<1°80Æú3ãÐàÔVC·óáA1*øÚ¶äɱéÉK@Íú°/ñ3/ÚÆ¿EóF3¶Î¼Õúõ@@Kòé׺À3õ°·+ßJÆ5Iü¿°ç7ÜÔ +º×·ôà·*ï?Y±*JúH**¶F¿*´HF±ãÖ+´ÜV*¾XùêÃQ>ÉÊïóâÈÅQ9¼·*ÞãÁYÔúØßåA*îäW;ÞÐ8*¸ã;-é +CÅQ>ÉH<´ãÈÃI9ÜáÈJ÷0<ö÷ïÈ=ÝÜ2ø°+*¸ë±AR°áƽÎVðÊØCY8ìÚöã+*QµX×°ê;OY@*JÕ»ôÀV¼ú/ã +Ð5ëû·ñîãÉåÖÑ*F+*ìXû°äرÐÜV*¾Xùê/Þú1ã*;EÆOçíûÜ**Bäø3çXßAÊ,Þ¶-×>ü÷í5*¾âVI;ë?ú +ÀS*<Õ:É5-*B·ÁUö/Ó*PXK@Ký»+*¾Êó=ÐÛÀYB/*°Áâ<ÂÊó=ÐÛÀYB/:ð4°3ö1***Á*¾ý¿,FL¾Ñ9ÆÌ- +¾â÷.*î**ðÙÀ-W¶Å81.îQ0*ÆÚÀV,ú+**Þ:*Þ½Î*Å2îò-Ðñ*2×Hïö<̹޿?Ò>FðÀƾòíïÉÇ+ÂÚÀVÀ÷ ++**Þ:*ÊI/:**Ò/Ê,ÚξÂ1*NÔQÚ2Ã,Ã;¾=Ò¶241Ôîä.È1,+ä+ìÓß/ÊNÌ;9T6¾â7âÐòýH***6,¾õÏ* +-+16î,-Ð@*:×HïL>̲à;QÚ=Ã5UBH¶Ã/µ4î¸,üÝ*êK@ßø***î**,,0-8BÞ+0NW*J°»àÃJÚèÂM0¶>* +QÔ:úï85ì>ÞO-ú¾,ÖÁVÀñ+**Þ:*F9/B2R-¶,+S5*ÊVͶ;4åJ¯P;,îú4ì÷ÞC+Ú7ÇýQ*Zð4°3È1***Á +*κ>ÞJÞG0NW*¾°»à;LF+Ãñ̶Z/ã,źîJ,8SâÛÆú½+*>Õ:ÉJº***B.*àM+0,8BÞ/0NW*î°»àÏR>RF +ð¹жZ/O,Õóïè-@ß;íÇ6Þã?â<:9***0+*÷S*+*JWJ?Þ+/Nº*:±»àÇRF¾/*îL*ïß×*òÚÀVÀòù,**øì=*¾U/¶Ê*1Õ:Á/6HN+29Ì +/*æ?âð?0êÄ,Jç±Í**ìCJLUÆÁ,.**Súßű+Røì5ùJ@+èVI;ë?úÀS*RÕ:ÉZûÜ**Ðäø3çXßAÊ,Ƹ-×> +úÅí5**èVI;ë?úÀS*Rµ:É,ºè»+*:Ëó=ÐÛÀYB/¾ò0°3ü½ý*Æ×5ÜýV¼ý×ú:°3*ÒI2**è+XBJ@A¶F+1Ï +îÂ.XëJæÞËTÚ¹¾5BÆ*1ì.ßS9â¾3ùJ¿.Âð¾¯ìàÞãïF<¿ÔÍO%%% +d +602 104[1 0 0 1 0 0]sl 8 mask 0 0 di +/mask 7904 string uc +*ÞÐ**üºýÉöü9õûýðùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ýÝúÐ81%%% +d +/sl 62608 string uc +äÕí´ê9+*ú½³+Êèýý***öI*Z2:0îÀ+0-ж¿Ñ3¶2/Å´îÎ/XÚÞK,F¿¿ñE¶S.QOZ±-Æ¿R8KQT@ÖHÈò2S +FùàU5Zëúø³?ÍVÜTÓ5G¸ÀNåØóº@98ßË3F<äU?˾*8Oá/Á8-Ú?è5¶ÀÖÅ2î4/84à?-FâÄD¾áD¶ÓF1É +î*HÀ,XÕåË@FRÆÁÎöI°÷Ì2ÍÄëC>FëÝ5KƳ@íÆß¿<ÚUÁÁÙ¶L.AÊîN,ìTÞ?·Ú+¿Á¶·Æ2õ8=5JFÒʱ׺ +°93KåAÆãÏáEàºJ*µ²ýRÔìÏíK²ÛÏÓUCºE¶Å=Îæ;Ë1ܶÇÙÓ¶ã*AMî:/8Oá·>ÚØ¿ÁÐÆJ@íÎã/ÉFÔÆí; +ÉÕøôÈ-8áçK»ÛÃÀȾU:ÆÊðíôêßVHÙÇM̹=+õ>îR78ÎJFÞï±ÚÔ¿Á÷¶á.Å·÷È5XÀßYÇãÑì¸6Q1=î@4T +,ÐÊã=Q¶DÉ1,î8C8Àô?8FҾɳ¶Å91P;-7FáÈñâ»-<½Q@9äêCµÛ·¿>±÷Ê10ü9ûÞS+N:Éå>ïÄ7XÛÞ»B +Nß*õ±÷Ô08XQè׺/0ÝM@ÁÅ4Å0*XÆÞ×5F￱3¶¯/AÏîæ-8WÞ3>ÚG¾5B¶R5µFîR;8GàSÀÚM¿ÑX30¶O2 +QKñÊA8Nâ+JÚ·ÂȾ5·º¯7õ:ïNFìNÞ3Pº3×êñ-¶G½Ç°ZDSæßÐú±°·¿ÖFBöü±CýLP5Éï4*JMáð±ÇãIN +Gí½@ÝýV¼ý×úü±÷ûåðùYÀõIÉí½@ÝýV¼ý×úü±÷ûíÞùÍãõIÉí½@ÝýV¼ý×úüMîûåðùÍãõIÉí½@ÝýV¼ý1 +öü±÷ûåðùÍãõIÉí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ÝýV¼ý×úü±÷ûåðùÍãE+¶A/í½4ÝýV¼ý×úü±÷ûåðù +ÍãõIÉí½@ÝýV¼ý×úü±÷ûåðÃ+ÚÉÀõIÃí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ÝýV¼ý×úü±÷à*ìOßùÍàõIÉí +½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ÝýV¼ý׺¯*õÐîûQïùÍãõIÉí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ÝýVÜB¾ +¹SöüÑöûåðùÍãõIÉí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@Y6ÞÛ>ºýSúü±÷ûåðùÍãõIÉí½@ÝýV¼ý×úü±÷Õâ +<%%% +d +602 104[1 0 0 1 0 0]sl 8 mask 0 104 di +/mask 7904 string uc +*ÞÐ**üºýÉöü9õûýðùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ýÝúÐ81%%% +d +/sl 62608 string uc +äÕí´ê9+*ú½³+Àèýõ9*C¹ß×AÜØ*éäÕYUí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí +½@ÝýV¼ý×úü×îûåðùÍãõIÉí½@ÝýV¼ýLöü±÷ûåðùÍãõIÉí½@ݽ6ºý×úü±÷ûåðùÍãõIÉíÝ-ÜýV¼ý×úü +±÷ûåðùÍãõÍ*í½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ÝýV¼ý×úü±SÖ±½A/í½4ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ +ÝýV¼ý×úü±÷ûåðÃ+ÚÉÀõIÃí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ÝýV¼ý×úü±÷à*ìOßùÍàõIÉí½@ÝýV¼ý× +úü±÷ûåðùÍãõIÉí½@ÝýV¼ý׺¯*õÐîûQïùÍãõIÉí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ÝýVÜB¾¹SöüÑöûå +ðùÍãõIÉí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@Y6ÞÛ>ºýSúü±÷ûåðùÍãõIÉí½@ÝýV¼ý×úü±÷ûåðÅÊO +d +602 104[1 0 0 1 0 0]sl 8 mask 0 208 di +/mask 7904 string uc +*ÞÐ**üºýÉöü9õûýðùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ýÝúÐ81%%% +d +/sl 62608 string uc +äÕí´ê9+*ú½³+Àèýõ9*C¹ß×AÜØ*éäÕYUí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí +½@ÝýV¼ý×úü×îûåðùÍãõIÉí½@ÝýV¼ýLöü±÷ûåðùÍãõIÉí½@ݽ6ºý×úü±÷ûåðùÍãõIÉíÝ-ÜýV¼ý×úü +±÷ûåðùÍãõÍ*í½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ÝýV¼ý×úü±SÖ±½A/í½4ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ +ÝýV¼ý×úü±÷ûåðÃ+ÚÉÀõIÃí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ÝýV¼ý×úü±÷à*ìOßùÍàõIÉí½@ÝýV¼ý× +úü±÷ûåðùÍãõIÉí½@ÝýV¼ý׺¯*õÐîûQïùÍãõIÉí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@ÝýVÜB¾¹SöüÑöûå +ðùÍãõIÉí½@ÝýV¼ý×úü±÷ûåðùÍãõIÉí½@Y6ÞÛ>ºýSúü±÷ûåðùÍãõIÉí½@ÝýVìº@ݽ>X¶@ݽ/Vá8Üý +VX¶CÌä<% +d +602 104[1 0 0 1 0 0]sl 8 mask 0 312 di +/mask 7600 string uc +*ÞÐ**üºýÉöü9õûýðùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ýÝÊàO*%%% +d +/sl 60200 string uc +äÕí´ê9+*ú½³+íÝ,Ô¯1¾èÓ/Ö´¼ûØøü±·¿Óöü±·ßãöü±·ßãöü±·ßãöü±·ßãöQ°çÜÕÅY½5+*ßYý»ÙRã +OÅ.+º=×òìßÑYíýûõå/÷îG-*ìEõíÚ½1åì»ÅRã¯ÅÊ+ú<×òçÁ1ýûÚ»ýøS»ùó¹0*FºðÖUåÌGÙµ¾ÜKÒðÔ +M4+¼ÇV¸ìÕ³ÝÌWHýü²ýüúG-*üüúïÑKÙÌûDÛX¼»¹RãWÅ>,ú<×ò̯ÙüVøùûûO½üW-*Üý¼öÙCµ¼7ESPë +º¹RãWÅ>,ú<×ò¿YÁÄê÷¸ùøßüä+*ÕͼöãÆKÝ8V.-í9T+øY¸N;ÞAÇOYéøðßÉÍ͵Yîé½.**Þ*ÞÓ1NP+1 +ßîô88ÀÞÃ0FZ¾ÑU¶ò+8Ýû÷ß;9ÜêDÓÓ¹ùØÒð4H°ÂÆ6FÐ2îÉâÐQ?ûÖíáÞßÅAóçI,**î0+°F¾ÉZ¶U11K +îL-8BÞç?Fø*YIHúíÄûH@ËÐàÓ»UTC°¶À0CLN08S.öO°çµÀHVAïëêÙÉöî9+**¶À*,*¾T¾×¾-=¶ï+å¸ +î06X,ß+8>-FL¾¯¾=W¶Þ*õÔîê>Fúöë×íPVõZ,»ñë5ÈÙ9ÓÚBÀ°¿Ú:ðT78¹âUÇOýö·ZÚÀ±MÕüê.*¾5. +B1B0**È*8»ÞG-F2Á-2¶>-µÓîT,XÎß7CÚMÀ=Ï2,îèÏå@6µîå9ÍRìÄÒðìSð¿ÆÂFÐ2îÉâÐAòFöëÞÕÇI +1çY+JºÛ**Z8îÞ*Ì3Þ¯-ÚCÁÑZ¶à*SÎK?áÖßÛ?F4/ÍüF÷é¿í0ô64HÇã/Sãß@â-;Õ@ðØM4+¼ÇV,¼á³Ô +ØõíÞY+**¶°*-*O+µ4îà0XóÞ×DÚ,ÁE3¶U-ÕRîÀ0ì¶á;8Ê+ÎíÜU9ã+¼Q°·KT·°+õ¸<-ÂÚùËÑ8S.öO° +çá·0ê@ZèÛ»I-**î,+è+ÌNÞÃ1FB¾=3Æ06ìÞÞS2ÚÙ¿UÔ¶ô.Ã*ÚE³Þ´+¹8õÂð@O?ÕÆ:=Ââà;Ã*ÝN@+Ø +îDû¹ØôëÚµI,**î,+ä+8BÞË,Ú׿U7Æ+6ì0à37FMÁñä¶;*Æù´ëÑý0ã+²Q>µST·¶7·JNL8S.öO°3¼/Õ +SÝЯåÔGù;***Q2J**R/¶L*AHî,2ì²Þ+/FJÁ=Z¶À-AJZüî..Xòß3LFÙÀ=.R,¾Ø±á@ëµR8ú¸L·ùÏÈE +?¶ï.÷Jâ;ÄÚôÆL¾Y<Éú4ò߶ýÀÄÛ¿*JM-ĹBòUQ>ÕAÒðÎM4+¼ÇV¸QÒ9ñ76DôêÕ*?ðÜ.Î50Õ2Þ±á@GB +íáÒGGÏÚéTÇÙ,Ìê0*Ö6Å>,ú<×òÄRµ8VBòé×S´êÕ¹0*Àð>³áÀEÕ´òÂðDÀTì¸N;ÞAÇO1/ײâÂGÕÌÛ¹* +Þ·ñ¼»×°çÇ;GÏÚÌÎÈÑ8S.öO°çÝOOGØ°äÐY5ëØÙJêðÆFý7Ú´æÏUUÃOðèì8T·é;Ã*ÝN@GìOÈSÍü6ø²¸ +WEõÔ+*ýËG·ìØG±°Rôðì9T·è;Ã*ÝN@GùÃÁÈPX븳Së¸é*¾åôëÙõæV5GMW55í8T·ê;YO@;ùØàêRçL* +Þ²L?òØÕóñWL?AG<.îÍâɾèù1¾?GLH×ú +ü?Z»Ñöü±÷ûåðùÍãõIÉí½@ݽTºý×úü±÷ûåðùÍãÍ+@º×RºÌôêÏ=C*îG¹ô¶ÀQòðõIÉ÷Ñ+æ6Z6,ÆZ@Å* +¾äõIÉ×É+ƹïÃÇäÐê;õR9êùøöóí/*2üøí½õV,È;Ã.WêéS<:Û85W¾+κÐ1ìGºøóûÝýüÚ:9¼/*îÜ·ýä +û¹¶ôìýôV,âïÂÞ°÷Z´/º¾¾Þ°Æ+>¼Î1H»öîÝÍÙõYIÓµÝü4*Þß=ÅÛ¸ôê×»-òY¹@ËPI¼ÔGîßê4ö,+ËEI +¼²Hæ-åûØôé×ãñíÍôíͽ/*îõå9I7ØóêÕçóðéIôVø´Â2»»GÙ³êNH½ùÇ+¾ÕµÌø±è×YÁ?Â2ÛBIõ6ÙóåÐ +ÝííYøñÝ-*öüùßíäÚ÷³êNC³ñ9¹@»>PÊ4áPÛ·²ðHýüê*ÞãõäÊ·³êÃC3PÏ8ò¿1¼7D°åéëéÍì8ж+Õͼ +öãÆKÝ8V.-ýÖ±çSÏLÓA´åÄE5óòͳ9*2*R1¶Á*³*8ýúîÈ7¹ÄÛµÎß¿HöØÔA9Üúúºù,Ýü±*B»¶1,ÅCîæ +1F¼ùñÁMHº×³TUõôÅGÉÌ5AXZSQ06×:Aý6@Í:ÞÇ+Ú:*IY¼öá¾=Ñ0EæîJ9ºTçÅûH08¼ºSÍü?***µ2:Ç ++Fð+YIHúíÄûH@ËÐàÓû¯åÐE±ä6ææÁõ*¾±22-**ä*8³Þ;-Ú:*Õ8»´ß¶-½üö³åÐíÙµíKå¿í0PÖǵ½-**Þ7-2**XVÞ¿,öñâ¯1¼E +ÖñÁ¿01ø*48GÖéâÍQ½/**ÞO0Ê1ÚZ¾5G¶Ò+±*òF÷éÁ4ÐîëÍGÉôîßÁúÄ1Sóíåí***¶á*1.îP*2öíÓ1+ ++ÝJÁ-»¶J1âùµîÕ***FȾ*¾-9¶ß*APîR-ä*âµí1R+ìüê×òÑHËHÁäGC*öíêÇñÔëCÑÜ +âÜûö1øÞÝOOGØ°äÐY5ëØA+ÉüE¶ëÎMÕÔ4ÉâÓû¯åÐÁõíݽÇÁÈP@ÓWå0*êÙøöÏ×@Ç+¾TÏLO͵À-6¾û=»*55OKóúXD¿4ÖêÜ»ù8X³åØ@@ü±åÐZÅIÐB±+* +úÅø¾è4±=Ü·=,Þ²L?òØÕóñY¹@/¹ùSá<Ö½Ã1º»÷éÃõ໹=*ζõ@¼ÛúøÝGÉÆGúøö÷Q×ÈÄIüøîíï+XÅX +Fµë¹+*àWFø±Öúб׹NðûåðùÍãõIÉí½@ÝýV¼ýÒöü±÷ûåðùÍãõIÉí½@ݽOºý×úü±×ß±3ZH**Ö÷û-Þô +NïJ=ÃYÝ;ÉTÒRÐL¿üúü+Z½ÒZG·ìÊ÷L@?PÊE¿0/øû-Þ,U¿»ìºçÐOݼM@¯NÇ>¯ìúü+ZÃÕÎBSÈ6çÕíÆO +ëÖïMÆ;Q¼ý*ÖÞTZÈ>M¼úæÕíÆOÉB¯LÃíI*Ôö4°;á**ïéµâðù9¾ë4-Öøü+Zßã*% +d +602 100[1 0 0 1 0 0]sl 8 mask 0 416 di + +QP +%%Trailer +%%Pages: 1 +%%DocumentFonts: +%%EOF diff --git a/doc/krdc/preferences_profilestab.png b/doc/krdc/preferences_profilestab.png new file mode 100644 index 00000000..5b735f77 Binary files /dev/null and b/doc/krdc/preferences_profilestab.png differ diff --git a/doc/krdc/preferences_rdpdefaultstab.eps b/doc/krdc/preferences_rdpdefaultstab.eps new file mode 100644 index 00000000..ba5ce6ec --- /dev/null +++ b/doc/krdc/preferences_rdpdefaultstab.eps @@ -0,0 +1,314 @@ +%!PS-Adobe-1.0 +%%BoundingBox: 0 0 602 516 +%%BoundingBox: 0 0 595 841 +%%Creator: KDE 3.1.92 (alpha2, CVS >= 20030921) +%%CreationDate: Sun Oct 5 20:10:23 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 10.416 3.125 ] [ 3.125 10.416 ] [ 3.125 3.125 ] [ 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 3.125 3.125 ] ] d +/pageinit { +35.52 24 translate +% 185*280mm (portrait) +0 793.92 translate 0.96 -0.96 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 304]ST +B P1 +NB +W BC +/mask 7904 string uc +î½*¾î1îD*J¾Ç¿â1*ÞIÝ.+îYüÉ*ÞíY,úÆF<ìILø>:ýüÝÝÐúQ*¼âZOçÑ*FÆßÈÐQ+DîIîGW,.ÖÓO:*ý ++Xè<ÞݳWàÐ>K*ú÷üAïûIìùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ùÝ.% +d +/sl 62608 string uc +î½¼**ûÙ6*<òÊI×.»ôȱ*9A@ó¼ðÉ+Í7ÙÕ*Þð4Ä»üñÚãùý½ß*.HîÕW0*¸´ôüû÷ñÍ0±ÜØ7âøÈÔ½ôÖW¹ +7*îYöûóíý0³÷ê@M-ò¼ðÉ+Í7ùB*ÊWøûMå÷DÓèÓ°ç<ë<رåõåûó³ãú´¾·õõõº +Y6:ëI5³+¾ÝËëFúûößUÅåÉ-ÅÛô+3JÒðù±/D9*öÍÁûÐÛTÒã¿@ìÖ×CÍʾýý9æ:½ýÙ5F5*KÍξ÷ýYRàE +K*JíÖÞ,9*¹õQéñç5À÷V6îFüúóòèãõÍýü»YYHùñãÇÝ@D¯4öìûùíùݽ½îÎò+*öÜÛÏÁŽ½»õÆÚ¶ðÙóÜ +B+ÙÝIRÍ3+¾¹Ó6ÓRÇ+í¯Mß༺L,;Éæ=æýö?/.¶äù·ü>ûü¼îÀòý**î÷¾,Ì×JV¾ñ7½ý°ÍH½X´î÷õÅýN +2ZXõûùåÍ3HÐ×+>ÛIZÊNY2䱯¾¿I*Z*î4üºïðåÕÝíY½¼ÅÝü¹ðãÅAPC¯4¶×ûùE8Õ½ýßJ²+¾I?6Ú÷õõ +óáåñÓù8¼ÚîÏ1R¾øõÝC=*¶ËϹÈFRYÚ±ÂUWÄQNÞýý1.òñýÂ1/.¶ýì3»üI>Dý°ÕãîP*òÄ,¾ýU@î¶QüI +Iìääý¯±XËöG¸Ï,Øü½µÈÜCÈÈH<Ç+NÙý´ÇH9.ä±S¾¿ÝÀO+*Þ;úS×á¿9A9½º;Ùý8úõï°CHM++*ÞS42* +*P4.**µRÞßõIýUüúU½@*öÌÐÇ5ýÜHüùÜÜùîÚÅM¼×±Ï*óí9±P*î=EùFRÍÚ²ý2íÞ5ÍÆ*ú9¸²ò5¹Ø?:Þ +Å3ؼüKöà5ñãL@+äE4¶ä,¼SÖýý7ßFýIØø?Ðâ¼ATJëýA7ôã¿SçMÜ· +B;Å»2BÜ3¿JN.>R<¯91,RßÈû-Dç½ûÊ,/XSÞC5¾Æ1²*07JÚR+/ÙùýýïçP?9Çâ¾³ìI@à-¾÷W2:îå¼±I +ÝüÂ;æ¼-½Ü;¶è×ÕÉIÍÜÍA½»´èÞEHºÂ,1ÀB=4*Ì;É>¼úÊ1*Þü/ý»ù/VLëÞÁýáÖR¾øõAC?*¶ÌÊÓHPú: +áÃÅL:Ðö4,ö¹ûÅ*+,NæVÞ?/F±¿C*5ýIÖÆ;0AT¾Oô±Õöý÷÷ðØÅ>X*7õ½Ùîâ5Õ÷U6ZîÅÎÁAIì1ÎIÀIý +õÙ²éæÝÁ9íß½ÅìØòíý2ÜK¿5L.*ìϾJý¼NîT4¶PúöW·õìð+*Þ·Ú¿åHÜæJÏñ0XøÙÀÃ,ô¼ýÈ9±,*ÕÖµí +îÚÑÔ?VZXº<,;ÃÕ:JûüAÙ°O׳+Õ>îÊ/ä*ÒîýÎMÔúøN2ÖíÍ;¯ü9ÜRTøÆ-0ZòüQ3ĺïD½Ã+4ü*ùÖKöü +4üÚFîÑWIíìû¹õÜû·éÑ¿É=ö0/ÌâðG4ÆñëÅ.XO+ÙF½ÜÓòçQH**Wõ´öêÛÏ1:Zõæ»5½5VÞL+±Ü½SÄC+¾ +Y09¶°Õ¸ýÌ>ò¶ÎËÅR¾Q¼ýìÈ·ñ:9*¾ýM2ºõêßÑ-îïÝMAHºKWý4/êðüÂ=P.*¼Î +¹÷뻵0Ç8>º³,,2ι2¾êB3DÂç¿/ÚоU@R9¾èB3<.çÇ:RPÂ,?¸ãYPøÆ-0²áúûã/êöà³ü4-F¿I*î6üV +@îÚ³IîL/**µî×öãÃ,À»ù±>Þ:ÓUµOÚöÄA*ÞÃC/ì¹µðêá±öèÇÕì>ÊÞD+ÊؽøÂ2ñ+*¼2@ûæ:é?Ê23´Ü +-T´½æë,°ºðD½ã+8ÞÍ**î6üV=îß»IKN=Hº=»SϾWåÜûùUSÞ7ÍIµãÙµÃA*ÞïÃ<ø¹öïçß±øçËĸ²7ß- +>ZÞùÉWB;*B¾-í;ÊÇ2´ì-úîøüFá/Êöâ³üÈ-FÅII.F¶ïãÏQÕìÕ½I½»·ðÁìãÊ,T϶ïÔ°å¼<ûìÎÙNí¾L +@üC±WÅ1;Wõ6HJ+NÙIûÅ+¹8*ÖîüÍÁÓ=öîÇéú?1*0YáÍé+8ºöïäËIõæÕ½AÝ»¹Gó°P+ÃæÙµUÔWÝ×¼9¶ +8>H²âη-ûZè?8Þ=>¾ÒùIÍ2ZÝ+*½ëÝýݲ÷.ܯËå,+ÕïQRÇ·0*àåÚÊS¾5äÌëȲç-Í*¾5úåìCXÒJÏÀÚ +Ì×ùQî34RÃÞùMQçYÑ+¾-FÚ½½Çßâó¹-BÝã+Ìá9ÓÑÓç;*2WóÐP+VÛØ?åÌY·ÝýÙåÌF¶ðêë-ýXË*±âÛ½½ +ä;÷³B*¶¿RÓõíY°A´*í×Þú@4¶6½ÒíÚµÅ*JäóÉD/:²æÔ>UÌâý9>¾õU6½ýüê;E0YÙ4*æXâÕMTÖ±ºÜÔÓ +ÙÕYTÓ÷ç¼*AY´ãÖï>üý0,îÜÃÃÇõí¯*î˳ä<Ä7ÛºùðLËÍ0.öö¯UÀ/Qä.X9H¸0Å<óÉÕîÓÙÍ*ÞÙèÒÖ6Ö +°éà3¹øüLV°¼À/ÙðéµÙâ*ÞׯÑôVC¯ß¼+Yû.*üéAZßÄCÉ´7ZÉW²ÜD;2ݼQ@+ùõ9*5ëÃõIÉí½@ÝýV¼ý +×úü±÷ûÑàùÍãõIÉí½@ÝýV¼ý×úü?Ïç±3-í=óÅÍû+¾û;³YîÌãе=ÉÈ<@ÚêùYÝI*JòÑÙØLׯÙòéâ<,üà +ÙúýE.*ÛüÚ·êÝúôUAW¸@7áUÕVóM*B¾±Í±ÝàVèûäÃEYéí-ÈOâöÈõï×õ2*Îé<³ï0,ºÔëãÃùýýI¾»/Þ3 +*Þ÷ÚÉíÌó+ã<Ü4Wøôï¹.*æ³Çض-+äT÷ìÛU@µ/*µB+õéäÛÚÓ1>ÉæTñåÓÏí;*¾ÓOèà7.JWã¯áä½WÔº´ ++Rýóéã³»K×NËQIÝM**UÉÒÃE2îݾÑXGݽý*À*0*¾ýM1¶.+AAî,.̳Þ+JÚؾ-+¾öUÑ0üé1ÈOÙÊ×òð/ +**ÞÓ,FL¾59RA¹T-=,QSîZ-¾ÓÉŶ-0Ó*ÕPïôAÌ´M<èÎã¸ÅHÛ¼ý+**î,+ÌFÞ;.ÚÚ¾U@¶Ò*Qîîì*Zûç +ϹAù¿°ç>Uüʺ+**¶Ó*Å2îà-È+8ìJ,Y¶Ö+Þèï@**µ>ïê>ÌüàWUÊFNZ6¼Ë´åÎãíݽ¾ëÝ9Þü*¾J¾¸¾E +2¶Î+ÅR*ôÅßHM¾-=B,¶¯,ß+¿31,*ãÁôVùØ9>É@Cðäß5**¾E/2ßT42/¶K+1¶ZHÎ2îN04.ì»ßã**UÅ> +ÎÎÎ8îL,ì³ßO¼FL¾MW¶¯,µÅ¿ßãäê×ùúõ-**¾5.R1¶-+1Dî:/8BÞÏ.F,¿E¼R.Î÷Û¹äFEÚQ>ÉÞ¹+**¶ +Ð*-+16î,-ÌîÞ¶Þó9Úù¾Á+*Ô8SÞÃ<Â-FøÀÁG¶Ë3C²áG+ö¸×Aµäì¼û.мûÖQ,*Jû¶¾B¾æ¾M:¶ß+ARî +Æ+ÌÀÞ75.PÒ*RùêEYÌÛì5ÈO¸ºCõ,¾°B+*JJÞZÞ+-FÞ¾=F¶ß+żîP+8»ßK,*VÅ>îº88ôÞ³PÚ-¿6Ä¿õ +ßÔÊ·¸øí-**¾U-R3¶*+12î2/0*XJßÏ.Ú÷À3¿E+ÎòѱÔ6ùÛ9>É0ׯãÖ5**¾=/B2R-¶,+µ¶îè0Ì3Þ*Þ +ÇAF>*æóè,*¾-¶Î1AÊï41Ìîá+PFöÂE3¶L*åÅß-ÜÔº÷·öéQÖóé+V9*2.R1¶.+12î>/XNÞG-ÚCÀÞÂ4 +*.ýú°ãÕýÎVÐàÇUA***Q4Z:î8-ìºÞÏ7FÆ¿E/¶Ó-õ.*¸ÛØ**î¾18òßGENî7ÅÊïìCì8-=ºG×ððéÏ1** +*A2îâ+8JÞÃ+ÚM¿-N¶?+µJîÂ986¾WYX·ðY>É,·ò,**îN+0,8BÞ/0Úß¿EE¶õ-õHîZ*JÕ±6¶-6K+Q8ï +ìAìðà3UFëĹ,B×òáÑAÍϺ·*ã-*,+*¾+¾55¶=*µRZ<îà0P,è5T+4,è*J÷ÞÂ4*ʼú°ãÖÝÎVÀáµ±C<õ +ÈýX»îÏ5ÜúÝUY¶R*SâðËMÚ·ÃE5¶Á5?+1X÷4B99-=ÂW×°ðèÅí9*ò·ïÊAåÔõ3ã<²ÜM*¾XÉÒÃE2î0YUÜ +êF¹ïF¾öÙ*RX7,¶F-SÃÞ3PRñßCUÄ7î=ÈOöº*îÂß*¾è¾7;*ò¸,+ùéÃ-/Þ6YQÔººøîù-*WHú°ãÕ7üJ× +²Æ7Q0»Å**Û@S5µ:ÞMàÔV×öñß¹VU>ÍZÀHIX·ðé5ÝÎVÐáÉOí;*JÕOèà7.îÊØCY8ìÚöû+¾PµX×°ê;ýÎ +V4äÒõ2*Îé<³ï0,ÚæìÍE1YܺĻ/Þ/*æôèA±ÔGñ5ÈOÀÊ·óG,*òØNWÚ¿*Ô4úòåàËIÝI*æøñE½ìGò-ÈO +Æ@°3Xí;*JÕOèà7.î×ݯÉ8üÚ÷û+¾àÅYø²îTIÎVLÕÆ;UÄ»Å**Û@S5µ:¾-éôëصîÛõ1*D¼¼êÖ¿Íû¾°3 +üõO*JæÕ»ñÔ»Ã*¾ÛÍùÜ7¶±8ç,U+RO>Éí/â½25»ý×Úî°ºý×Úî°ºý×Úî°ºý×Úî°ºý×Úî°ºý×Úî°ºý×Ú +î°ºý×Úî°ºý×Úî°ºý×Úî°ºý×Úî°ºý×Úî°êJ@+¾*¾.¿;Ê:á>úêOÖ¾VÔ?P2ÄíÝ?5+ðÍãMW>**Ê/**ï* +Å6î,/8BÞ:JëÞÛ:ºõ-ÖÆVHæàðàU0¶¿,µ6î4-XRÞ¹A¾/ã°40¶ÄÉÕ6îÝ1JÐ×.ÒX*J°×F*¶Á.õ±÷â<Üô +-ÚùàN±3üI*¾â°÷æ0ÐÆã5ÈöØ7Z1׺²+ÓÄõO÷F/8åð1A¾ëâÐZ1:*îà+P+8Üïß7**ÕPîâ*ÜýVäBÉíÝ, +ÜýV¼ý×úü±÷ûåðùÍãõ9*í½@ÝýV¼ý×úü±CÏ8Öõóïéý?*¾îÕýÜ5BÔW±çÛÁQÝÜýùíÍ5ðßI-*¶íÚ½ñüGF +¹õËίÛDÉH»öîÝÍÙõYIÓµÝü±**1ÍFôè×±åô7Îõ+ÅóèV¸ìÕ³ÝÌWHýü²ýüúÍ+*úû÷áQÁ´Gù³¸ÛúøH*× +HËOý6ù³æÐßííYøñíø**åU*ì+¾õÝ9Ü´ä̯áPÉWIí-:éÑ×òÂYÅÌú÷øùùEI*Þ÷4¾É*îòÁYÌFøóßÃKöH +*×HËOÕ5¹ñàÊÑͽ9ðõûV,ï?*õ*ÞñÝIIºðà˯=Ûº¹õ+ÆOIÉ%%% +d +602 104[1 0 0 1 0 0]sl 8 mask 0 0 di +/mask 7904 string uc +*ÞÐ**üºýÉöü9õûýðùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ýÝúÐ81%%% +d +/sl 62608 string uc +äÕí´ê9+*ú½³+¾èC0VÚ,ºVèÈ-QXXH»·9½é-*êX+¶1*üH»÷ß91ÔÊDÃËU¹9¾V0@¶Q¾èÓ/Ö´¼ûØèI×ò +PMAìéÖµõí7»÷*âI*¾=1¶Î*Ï+1Kîð0*¾è¿Á0¶Ï-ų:ÅNVý/¾ò4¾¹*îòݹíÛ¶íàÏÃçÙëEÉKZ,*¾ÙÖ¶ +:,ÅY:52FàÀƾFðÞ³ÀHVAïëêÙáöî+**½*îÀ+Ì:Þß0Úá¾Î¿ç¿Ù0¶.1õÁÎO.*@Õ*F:Í5+îïÙ±ÝËÖìßÎ +¿ä0ÌãE.ÖÔVä;*8ðñÞ¶/åÄXG´í8*¾ô-*ã*ÏVA@:8î¾*8BÞÃ/FÆÀUA¶Ó+1ðîÂ381KÉ2*BX+¶2*XGøê +Àï0ü6°=å°¯ZöXSJWÞ2J¿Þ¿460Ú+ÁE?¶¿,QÂîJ;È-8»àÓFÊ+öÁÌ-Ý76¹÷ñ>»¸*;*A4î*,T,XBÞ/2 +8FZÀ-9B;¶K-AëÎ:ïÖ0ÌêK¹>**X+¶2*üº·ê¿í0ôú:Úëå׺ÒÁ1V÷Z/XRÞÃPÜÚÀU6¶ê,ÅDZ.**ÌVTéÞ +Ã=Q8J.ÞÃ,F¾¾U0¶A+Á+1HîB48RÞ3,ÚÔ¿-¹æ8/*¸U*XVä*JF*31ìøâÐ*ºÙá±÷À9ÍÔÞÇVHD¾E¹ÆP@9 +ãß6*ÞE×ÒÓ»ý08F¾;¾L**ÜÎÓ<î..ÌOÞZÞ¿1Ú¸¿U02¼ÆÂ08êK¹>*ÎW+¶¯C1Þá¾ýôÛøâÐÞ¹Ùϱ·*î.? +ÑÈã¹UÆ´@ûEå¾ë,PÛ¹µðëE¾;¾L¾S¾3¾¯¾M:¶=+1¹îÎ18@ßW-F×ÁôR*îØ,îà°0*à+ºõìÒ+Ù/ê÷ÙÌÌã +±<**XàîçVHÚÀV±3ÌÃVøÝѳíä»E¾ë°ùô5-¶â**Þ/-Úɾæ¿MU¶ö/ß*ã-16îÒ0ìêK¹>*JW+RV·,*Æù´ +ëÑýÄVÀ±ËFÉQúõÊ/9üî;»HÝÀU6ÆS@+ÖÆOõUDôêر-ÆW°å¼±**Þ>JBÞ²Þ+2Úƾ59¶ï.ÅÎîR-8NÞ»7Ú +×ÁôR*:Ø,î¾°8*¾äÔGE´ç¼M@+Ö0Ìã+¾=3¶Þ*Á*³*ãÂAHî,38æÞÛ?Ú;Àø¾-Ûº¾+Å<ïJ7À*ºÔåÀíø7û +Dôê+**êÕ6ôB0î±/Þ/ã6*6.öéÏýÜ?ú·ÔÙëEÉ3PÞ36ìÃ+Ò>çÃó,0G½ËóâV,Í?*Ų÷*,²²Þ¸5É@óIâð +²¹è»¶O+XSóâ¼-ÁÄGù¼³CµBê·@YVÄåÄWøîÝÇUUø9°·=2îÃP¹@VBòé×S´êÕ¹H*¾å4¾¹*2×ÒÒ9=ôÊDÏ +Y:ɹø,9ãöòâÂGÕÌÛ÷+*¾Ô*F.*åËùóãÉQIÌòGãнÉȸ²ãÊOÝ@سµûV,Ï?*1¯ç½-Õ7¸òèT¯ÇÚØù*âø +²@GìOÈSÍü6ø²¸WEõÌ1*2Ø,î:*úµôâ·éôÊÇK×ɹ9¾VÍäбݶRçâÓWÕ@ÓWý0*î×»ùôGDéLåRéààE+â +ø²@;ùØàêR÷?*¾çÂTæ³Yéå¹+âø²@÷øðÕ5íÃùôÍ**ùµïéãÕõ,ÆóçVTôéÏ5åWùÕ**àWFøÜ¿ã0Êãý¸9 +í½@ÝýV¼ý×úü±÷ûåðùAÀ1;ɾèØ+Î3ÞÒ-ÊñXº*ÓÌÍOJºç**å¹K@E¾·HÀVXòAú´ìß¿ñÄ7E´UGD²æû:É +5ûÃ6ÊñËãÐÉH7ù¶ïßÁý´×KÁüÚɱC-ñº±×òÍEÅ0ÌÚ¶ïà7õäÊGõðVäÞFûEÉ*ÞÏ+6-ÚǾ56¶AÉÀBïÀÕÁ +0ÖÎ+ÆäÃ-+J¾B*ÀÚ¶ñËãð¿âFøÊÙ3¶OGGÞFWGÉÅL:¿VÔ¸G³¾7/¾RüEZS,.2Ô:½?í²@ÍäÞçVìܷظ@É ++*¾Z**îL+´GÉàöì3A7:*ùÞ¹7í¯@å³ÌNÞ+ö62FR¿Á׺¯EU´ÌüËã-ßD/¶ß*ÃWÅ6îÈÚ2¹àÂÍFéìâO4á +WïÀ+,òÃÙö4±÷è¶XúäÏ0F×ØÀËÛEQ5åð¿*Ê.FâÕè¾¹ð¸´WE=A¶ù,,.íÙ¹Éã-*22B6¶L*A7îâè0´8îä +ØËñËã<;´Eû+*:Û´3YW@Í>Þ;ZP>Éá;Q7ZÏäçÞ׺ðU7;É°¶ë=ð<³1í¯@íºJ9â°0äçî׺æø¹äº¶ùÂÚÞ +ë±ÊÝÐíF´ñ5ä·ð,QÔÔ+ÊÑÙ±·Þó¾ÛXÔéÆ5¯Qʶ7éK-Væ*æ¯Û+8**óÓùö4±CVñâ¸XBåÃïFæÌ=×Ú»ñòî +VäÅ.OXÙ>*Ð6¶ø4AÉHBZãEÉãºÞ÷<+¸¾·Øµ@ÁïÍ2JMâÐF̼ä/ÑQÅ6÷ð7äð?ñ6,>.ê»@=ZÌôËã>ÍZÕ× +>ÉÑÅ·Ã+:çÌEíZ@Y@±-*¸,ä÷ä°çAYÜúC²æÎQEêØUÑÜëV´+JåÞFýJ@ß÷W-JAâ°-BåEÆOö½í*æøä÷0° +÷Ï3äùÍãõIÉí½@ÝýV¼ý×úüÃîûåð9ÇOùÜH¼ûÝ,*2üøíÕHZGFÉìGºøóûÝýüÚ:9üC*¾ùäûEõíß׹ݷ¿³ +îVøòßÁý¼ìܽûÒÜûøÍ+*FºðÖUåÌGÙµ¾¼ï-éÞ×òÚYé¼GÙºüûçüû÷I-*öùñÅÍ0ë¸ôéò¸÷ó5YÖôËOý6ù +³æÐßííYøñíø**åU*ì+¾õÝ9Ü´ä̯áPÉWIÝöÀ³íV¸àÕMQüºûûûË9*îº/ÞO*¶øßAQ8ûøîà:úà5Ô»±çó +àìÚBñòñíÁú»IÉJº/ÞG*öúõå1ÍÚ¶±êNAíì5YÖôËO9ÉØðÞÇÉÉQYìáíø**ÙU*ì+¾ÝQY¼öá¿?ÑP¯ñèÜö +À³íV¸ÑÏ5õ³êÙùõÂܺ*/*JüO*.2ÚC¿UFR4B.¶µ0µùî¸=øN*¾ò4¾¹*îòݹíÛ¶íàÏÃçÙGGÉ*Þ7,FE¾Ù +:¶´+A¶:Í;Ú=.Õ3øZÛµ±íìàÁ1*ü·*FU¿U5R42.¶ð*µÕ:-/âP.U8,ÖÚ,Îìð.*ºúôêµ=Ô¹B²Î×Û±÷ö +2A@É8ðñÞ¶/åÄXÛ³í8Jù-*J*Ø-R42/21¶J*1NîN0È,è-ìÎß74ÂÍGÊÀ-?¶N2»,*Þ¶/ÞK*¶óæ±5ÄEö± +ÆÛGÉ10BÚô¾-8ÆJ³Þ/4Ú׿ǿT¾5·¶Æ0õDîL8è*úKå¿í0ÄÛú·ÉHG¾ò1***..Ú:¾M3¶ô+Ã.Q2ZPî.88 +7ÞÓ>Ú;¿MJæ:,*¾U*8,¾IY8¹îÙ¿GI.µù¸@YÞðÏ°Höá±7¶Æ,Å°÷ð1ÌÐÞÇW,¾=ÉæD¶ïâÇŵ0+Â-*ð¯+ +å6î,-ìÆÞ3/FS¿5<¶á-ÅöîÊ/ÌßK½6*æW+¶¯>1òáÆ51ìøâÐ*ºÙÓ±÷,4ÑKàÁź?,S+ñ/+*º=ÉÚØõíÞ¿ +-*ü**ä+à*8CJOÞ/1FSÀ°¾±¯¶à--*QÐîê,X2àËC²0-*Ø?*Aòð¾T6öíÜUÍV4ºµÙ¸@É*TBåB9,ð7/F× +¿E;¶4-ÅçîT@ûEå¾ë,PÛ¹µñëE*í1*Z8:0îÆ+8ÞÞ;3FKÀEE¶³,Å?îè,ÌÓJ±B*¶W+¶¯>-*ï*ܹµè¾ë, +ôúWµåðCAÄ/N¾Âõ±÷R.ðÇO¶@°çýÌÔ»Eõë*´I*¾·¾B¾;¾50¶Ó*QtÎ6îÀ.ÌËÞ;HFùÂMƶÑ,Ëß**³/J/ +ã3*JìÙ³ÍüE°3̳õVÜPà-;Æ*B=Éã5¼¶<**VâÐø?7¹´ë×9îÈâÌG.*°Õ+**á*QJîP/à*Ä*8VßÏ-FÏ¿- +8¶@/A:ï¾:8ÓJYF*2W+¶ÁCK*DÙôêÖOù7×.Þø¹@9²î7/>?H¶¾M0¶ÎÅñ,Å°÷,0ì>àÇ@FöÂ=Rîê±»@úÔ +íæÕ+ôù***T:EAâF4éê,ö×,îLX0*0,úóæ½í4üFéëÌÌãÙ3¶à¿÷DJL²ÎÐNAÔ5BòéöDæèå×.HU*8 +çð+.ææ¾ó@@WTQå°K¸2Þ;2Ný:CÏ¿UTG×íÞίå¼Ë¾QÖ³UØê?ÖVÕ.HYèÃåÄWøîÝÇUUèç×Ê÷2ñÅOåÜæ² +åÀ/Á¼ë°åÄû¸å-*öV+¶1*;ÉÈø¯àÌS=è÷×ÚI6öRÄQQPê·óêË9**²/ÞK*¶ìÚYYܺøïNíS@ûÙÒÒMUäVø +³ÏW¹éå<2W+¶2ÉüE¶ëÎMÕÔ4ÉâÓ»ï/éÜ×ò¶ASP»ø±æÑç³ëÚ¹H*Jæ4¾Á*îÝ×=åWÙ±P0ãXì5YÖôËOå¼ï +ÎÐÇUÙXWTÙü7*Þ±ùôë¹²ÔÃÌÏÔÃÃíºßص@;ùØàêR÷?*¾çÂTæ³YéåÕÜZëEɺ»÷éÃõà»ùQ*¾àÛÙöóðéÕ +ÜZëEÉÈû¸òà·éü?*¾¶5ÅÜËZ×é°÷óÀáùÍãõIÉí½@ÝýV¼ý×úüÃîûåðùÍãõIÉíY@ݽ*âê/5ÆOöýû-JèY +¶óÃÇäðùÕ¾³êßùÍã/AáùÍã/AáùÍã/AáùÍã/AáùÍã/AáùÍã/AMÐ×>îáI-**ýû±¯ÜVƲ@3ôüü9+¾îÅû +û±¯ÜVƲ@Wïûü5I+*ñí½ïðí½5ÑÛÀN±3éðõIUÕ5*ZüûÕOûJ/*¾+¾ºÀïX1°ÏÀοÂ:ïâ3ðUÀº¯+ÀÖ*D +N÷DW¹.Q±3%%% +d +602 104[1 0 0 1 0 0]sl 8 mask 0 104 di +/mask 7904 string uc +*ÞÐ**üºýÉöü9õûýðùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ýÝúÐ81%%% +d +/sl 62608 string uc +äÕí´ê9+*ú½³+¶>½ýà¾Ý**ñíýRä1T**ÚK¾>0î81°ÇÀοº8;×Jâ=-SDâì°Zëéæ@è*ÒXW¾³ñéÕÓN°3K +ðõýËý=*:ûKHÉãè=+*ÞÇK+*¯+ó*á*µVÎ8îÎ0ÌâßÇ2Ú鿱ÈBD¶ã31¼î2/Ìàß7-F×ÉØÃUO·ó?1²îL3Ì +WLHW·S7ÅÊðÔK8Úä¯ÒFÏÇH¼¾N±3,HÉOßIîçVÜMÕ±;¶À,1.îÊ08Xëç8Úѹ̶<+µ?îTF8¿J0K¶?+ÅÈ +îÊ?ÌLäëÔFÔÆV6ð@TøØ@=ËOTµÝýM×>ùéO=ÉQ±ó84ìÑéëBÚÕÂÙ>ÊÂDìëëW9Nµ7·³ãû:Úê¾6ÚÏÕWC.: +çVвõûIÈOÏIWè×.êÅé±Êã+¾áȶÎ,żî:/ÌOàÃIFKÂÑöW»QÉîZ.ÌÏß?7â;ÉÅRðê·Ìïà7˲°-NDÉÒ +É»ýÅ°Sâõȹ@Í5êÃ*Úì¿M°¶Ê51ËïÐ38ÏÞ·9F;.êðÚ?8´O<׺T.åÛÏÕ³CçV.Q8ÏÅ9:çV¸IãÑU +I½GøóÊG¹óâ¹ñôñL@µØ*ù/ÖäXÜóÍMÐ×ò´°ñäûE÷ñã··òê×MÅìPä×6°½;÷3ÙKì¯Æ×50BÜàVȼYÌ+*Ú +È·@µ+½EÓãêÝùݾKÐײêS=Ûö2*.Sè9êáD³°°ËOTÌDñM*:ÒɽýWÊô4°Ëãõ½+ÙÎÁõIÉ5XÄõIÉ5XÄõIÉ +5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõ +IÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5X +ÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ +5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõ +IÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5X +ÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ +5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄõIÉ5XÄÑ×. +d +602 104[1 0 0 1 0 0]sl 8 mask 0 208 di +/mask 7904 string uc +*ÞÐ**üºýÉöü9õûýðùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ýÝúÐ81%%% +d +/sl 62608 string uc +äÕí´ê9+*ú½³+íÝ,Ô¯1¾èÓ/Ö´¼ûØøü±·¿Óöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãö +ü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ß +ãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü± +·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãö +ü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ß +ãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü± +·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãö +ü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ß +ãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßã¶DÉ +d +602 104[1 0 0 1 0 0]sl 8 mask 0 312 di +/mask 7600 string uc +*ÞÐ**üºýÉöü9õûýðùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ýÝÊàO*%%% +d +/sl 60200 string uc +äÕí´ê9+*ú½³+íÝ,Ô¯1¾èÓ/Ö´¼ûØøü±·¿Óöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãö +ü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ß +ãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü± +·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãÆR@+Óõ9*U¸Âº°÷ûUÞøPïûåðùÍãõIÉí½ +@ÝýV¼ýÒöü±÷ûåðùÍãõIÉI-Vö°ÏöFë×MQ°*Þ¹ôêï.Íæãí½@ñQ-ÎCÞB.:ßV8+*Ëí½@±A-:õà5=ËO×M +ìÏHÖõóïéÝ5*:úóÝýì×.>M43ØÖÕÑNJ¸G@Ø*-J÷N9Ú¹öòéù½ýû·KHú4*Þ»ñüËùõîêÛýë×.Æá3¿ãðßê +4ö*+¿ã:-RúJ9ºøîß½IµíݼTí¼û?*¾ÁQ8¹óê×±ù0æÝôVD˼úV¹ÞÁ×?î/,Dµ¼úæºÎ1Ìù³ëÕ±ÉåÝIëÝ +Iý4*ÞíÍI¼D²é×YÑéãÕ½ê×òë2;øø¸´éÖǺüô=-*YíFóåÒ±Ý0U2;¸¯¼ìC´éÍO½ÝÝÝòå½1*îûõÁÝË·ñ +éÖÇ°èäIôVøRÊB?Ä˸ñæâ»üû×+¾ÉíËCñèÖ5±<ÊLGæ-9úD²âÌÕÙÕIÛGNï,XIûîÉ;Á¼GÖ20ü¯åÐÑLÃT +YêÌ7µ@èçIéH*:*Î8î0+è*Fü÷ß?Eô6¹íJÁ-»î³WYHº÷÷÷ô/¼ûå*Zøî8.8±ÞÏ9¶úôå1źö°éÒÔìë9¹ +@GAXÚÞÐÌ6B°KXüCVHK¾=-¶K*¼ÜúîÅ+QP7´Îß¿HöÒÐ9ù»6FúöÐHûU***ì:J<-¶â-ܼºöÝ7ù»VDOÃU +ùáÌOµäÊCÎÏ1íOÊ´±Yý0**¾E0¶J*µ,ÞÛ±ÝËöìßÏ¿0DFæ-Ù3øZÛK1Ò»ºöÅ***FÈ**Z@**Â-Fè¾E>¶, +*<¼ùJ/Ò¹Bò˵½ôVø¶ïàÃúV¶ìßN¹øöµ***Ú¯**îâ,8>¾YÍXùîÚÀIÅ6²¶Î1üïñÞ¶/åÄXû×R**å::0* +*Ê+Fè¾M0¶K*XGøêÀï0üúïéÌOݵíÜÁÌ-Ý7ÊZ=íü0**¾E0:**ÚÖ¾-/îåÇá8ú´Zå1-78ò+>F¸ZÕÇIÍü +4**¾É6B9¶ß*A¸îR-ä*æ·ðÕ1?NßÙI¹@ëßÁ1÷79ÐèÝÍÝ+**îÄ+82ÞË*:îÝU9,,¼¿01øî¾8ÆõíÞY+** +¶>++*1HîÀ+XÊÞÏ0Ê+ÆíÜ9Î,Úû×±çOÈ>3̱޴+5èÛíìîÏ4H3*íÛE³Þ´+¹0AæîJ9JÚ-¸*ÜÍ+**¶0+¯ +*Õ6îR1ì:K2Þ+T¾¶Y¹@ËQÍHË/9DÝбõ¼.**Þ3,Ú¿¾-,ZرÉôE22Ê/DFæ-¾@òJýÌÔ»EõëÕ***ÚJ¾·¾ +²6î¾*:õëØQý7Ò*æÜôVøÔWÙôøFöWÝ>ø³ëÕ***.*Ò-Fæ¾=@îÖYÅüù´îåÛ,78ò+úT>³Ö±éÌû6*P9XF* +âH+23¶Ï+µ6Î>Î0¾²åÄÛÚ4¼»ë×ò»ùô?OD¯Û¾/²ê÷+¾Ä0VA¯çÐ,78ò+Ü,»BìÝCèKâÐ;=ôÚ¸´¸ÆN0FµìÕAU@GCÐÕGÉHÜ»ù»ASP»D»0˹°*îÝ×=åWÙ±P»Ç»ùï9ò¿½Éȸ²ãÊOÝ@ +سY,@ûµîØKÅXW?@ÇUùáÌO1íݽý<1?ËVTÙÌ7*ÖµóïM±WN°Æ+¾½Î1*õ+ÈÐçÕ³åôÐ3·?CÚÉùáÌOßì¼F +Ï4üÔÅ4?S<-*ÓLÃÈHí.1B*ùQø*@@ÈÀè÷Û²,?Z×»ùôGÚè̳WVúåÌOß8½N¯ä,*ö9ó+Ó?äPºñP.¾çÂTæ +³YéåÝôV4ôôÑÄOZý49öøðÕ5íÃùôP*JïìWú¸÷ó½¹@;¹öóïñÍ°?7½úóßÝá-Ú8Û¶ìØõ,*ÂÙ¶òåZ÷Oå°õ +ÆâùÍãõIÉí½@ÝýV¼ý×úüSïûåðùÍãõIÉí½@ÝýV¼ýÈöü±÷ûå°Áå<Þº**Zñù1¾ëÇà¿P4ݼM@ÓRÏNÃ,û÷ +û-ÞüR߸ðÚCñÃVTÊBµ,74òù1¾/Ô,ùÚ÷ÐOɼûÄVàÆCÐYÝ;ÉدáÅ:MÌúü+Z¿ÓÞ>SÄ +úöÏYÝ;É@¯àÂ4ݽ*Vï?âLÄ+*àÕíÆãõI*Ù?0Zóû-ÞÀÉ+%% +d +602 100[1 0 0 1 0 0]sl 8 mask 0 416 di + +QP +%%Trailer +%%Pages: 1 +%%DocumentFonts: +%%EOF diff --git a/doc/krdc/preferences_rdpdefaultstab.png b/doc/krdc/preferences_rdpdefaultstab.png new file mode 100644 index 00000000..9a8d939d Binary files /dev/null and b/doc/krdc/preferences_rdpdefaultstab.png differ diff --git a/doc/krdc/preferences_vncdefaultstab.eps b/doc/krdc/preferences_vncdefaultstab.eps new file mode 100644 index 00000000..1289a805 --- /dev/null +++ b/doc/krdc/preferences_vncdefaultstab.eps @@ -0,0 +1,307 @@ +%!PS-Adobe-1.0 +%%BoundingBox: 0 0 602 516 +%%BoundingBox: 0 0 595 841 +%%Creator: KDE 3.1.92 (alpha2, CVS >= 20030921) +%%CreationDate: Sun Oct 5 20:09:54 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 10.416 3.125 ] [ 3.125 10.416 ] [ 3.125 3.125 ] [ 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 3.125 3.125 ] ] d +/pageinit { +35.52 24 translate +% 185*280mm (portrait) +0 793.92 translate 0.96 -0.96 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 304]ST +B P1 +NB +W BC +/mask 7904 string uc +î½*¾î1îD*J¾Ç¿â1*ÞIÝ.+îYüÉ*ÞíY,úÆF<ìILø>:ýüÝÝÐúQ*¼âZOçÑ*FÆßÈÐQ+DîIîGW,.ÖÓO:*ý ++Xè<ÞݳWàÐ>K*ú÷üAïûIìùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ùÝ.% +d +/sl 62608 string uc +î½¼**ûÙ6*<òÊI×.»ôȱ*9A@ó¼ðÉ+Í7ÙÕ*Þð4Ä»üñÚãùý½ß*.HîÕW0*¸´ôüû÷ñÍ0±ÜØ7âøÈÔ½ôÖW¹ +7*îYöûóíý0³÷ê@M-ò¼ðÉ+Í7ùB*ÊWøûMå÷DÓèÓ°ç<ë<رåõåûó³ãú´¾·õõõº +Y6:ëI5³+¾ÝËëFúûößUÅåÉ-ÅÛô+3JÒðù±/D9*öÍÁûÐÛTÒã¿@ìÖ×CÍʾýý9æ:½ýÙ5F5*KÍξ÷ýYRàE +K*JíÖÞ,9*¹õQéñç5À÷V6îFüúóòèãõÍýü»YYHùñãÇÝ@D¯4öìûùíùݽ½îÎò+*öÜÛÏÁŽ½»õÆÚ¶ðÙóÜ +B+ÙÝIRÍ3+¾¹Ó6ÓRÇ+í¯Mß༺L,;Éæ=æýö?/.¶äù·ü>ûü¼îÀòý**î÷¾,Ì×JV¾ñ7½ý°ÍH½X´î÷õÅýN +2ZXõûùåÍ3HÐ×+>ÛIZÊNY2䱯¾¿I*Z*î4üºïðåÕÝíY½¼ÅÝü¹ðãÅAPC¯4¶×ûùE8Õ½ýßJ²+¾I?6Ú÷õõ +óáåñÓù8¼ÚîÏ1R¾øõÝC=*¶ËϹÈFRYÚ±ÂUWÄQNÞýý1.òñýÂ1/.¶ýì3»üI>Dý°ÕãîP*òÄ,¾ýU@î¶QüI +Iìääý¯±XËöG¸Ï,Øü½µÈÜCÈÈH<Ç+NÙý´ÇH9.ä±S¾¿ÝÀO+*Þ;úS×á¿9A9½º;Ùý8úõï°CHM++*ÞS42* +*P4.**µRÞßõIýUüúU½@*öÌÐÇ5ýÜHüùÜÜùîÚÅM¼×±Ï*óí9±P*î=EùFRÍÚ²ý2íÞ5ÍÆ*ú9¸²ò5¹Ø?:Þ +Å3ؼüKöà5ñãL@+äE4¶ä,¼SÖýý7ßFýIØø?Ðâ¼ATJëýA7ôã¿SçMÜ· +B;Å»2BÜ3¿JN.>R<¯91,RßÈû-Dç½ûÊ,/XSÞC5¾Æ1²*07JÚR+/ÙùýýïçP?9Çâ¾³ìI@à-¾÷W2:îå¼±I +ÝüÂ;æ¼-½Ü;¶è×ÕÉIÍÜÍA½»´èÞEHºÂ,1ÀB=4*Ì;É>¼úÊ1*Þü/ý»ù/VLëÞÁýáÖR¾øõAC?*¶ÌÊÓHPú: +áÃÅL:Ðö4,ö¹ûÅ*+,NæVÞ?/F±¿C*5ýIÖÆ;0AT¾Oô±Õöý÷÷ðØÅ>X*7õ½Ùîâ5Õ÷U6ZîÅÎÁAIì1ÎIÀIý +õÙ²éæÝÁ9íß½ÅìØòíý2ÜK¿5L.*ìϾJý¼NîT4¶PúöW·õìð+*Þ·Ú¿åHÜæJÏñ0XøÙÀÃ,ô¼ýÈ9±,*ÕÖµí +îÚÑÔ?VZXº<,;ÃÕ:JûüAÙ°O׳+Õ>îÊ/ä*ÒîýÎMÔúøN2ÖíÍ;¯ü9ÜRTøÆ-0ZòüQ3ĺïD½Ã+4ü*ùÖKöü +4üÚFîÑWIíìû¹õÜû·éÑ¿É=ö0/ÌâðG4ÆñëÅ.XO+ÙF½ÜÓòçQH**Wõ´öêÛÏ1:Zõæ»5½5VÞL+±Ü½SÄC+¾ +Y09¶°Õ¸ýÌ>ò¶ÎËÅR¾Q¼ýìÈ·ñ:9*¾ýM2ºõêßÑ-îïÝMAHºKWý4/êðüÂ=P.*¼Î +¹÷뻵0Ç8>º³,,2ι2¾êB3DÂç¿/ÚоU@R9¾èB3<.çÇ:RPÂ,?¸ãYPøÆ-0²áúûã/êöà³ü4-F¿I*î6üV +@îÚ³IîL/**µî×öãÃ,À»ù±>Þ:ÓUµOÚöÄA*ÞÃC/ì¹µðêá±öèÇÕì>ÊÞD+ÊؽøÂ2ñ+*¼2@ûæ:é?Ê23´Ü +-T´½æë,°ºðD½ã+8ÞÍ**î6üV=îß»IKN=Hº=»SϾWåÜûùUSÞ7ÍIµãÙµÃA*ÞïÃ<ø¹öïçß±øçËĸ²7ß- +>ZÞùÉWB;*B¾-í;ÊÇ2´ì-úîøüFá/Êöâ³üÈ-FÅII.F¶ïãÏQÕìÕ½I½»·ðÁìãÊ,T϶ïÔ°å¼<ûìÎÙNí¾L +@üC±WÅ1;Wõ6HJ+NÙIûÅ+¹8*ÖîüÍÁÓ=öîÇéú?1*0YáÍé+8ºöïäËIõæÕ½AÝ»¹Gó°P+ÃæÙµUÔWÝ×¼9¶ +8>H²âη-ûZè?8Þ=>¾ÒùIÍ2ZÝ+*½ëÝýݲ÷.ܯËå,+ÕïQRÇ·0*àåÚÊS¾5äÌëȲç-Í*¾5úåìCXÒJÏÀÚ +Ì×ùQî34RÃÞùMQçYÑ+¾-FÚ½½Çßâó¹-BÝã+Ìá9ÓÑÓç;*2WóÐP+VÛØ?åÌY·ÝýÙåÌF¶ðêë-ýXË*±âÛ½½ +ä;÷³B*¶¿RÓõíY°A´*í×Þú@4¶6½ÒíÚµÅ*JäóÉD/:²æÔ>UÌâý9>¾õU6½ýüê;E0YÙ4*æXâÕMTÖ±ºÜÔÓ +ÙÕYTÓ÷ç¼*AY´ãÖï>üý0,îÜÃÃÇõí¯*î˳ä<Ä7ÛºùðLËÍ0.öö¯UÀ/Qä.X9H¸0Å<óÉÕîÓÙÍ*ÞÙèÒÖ6Ö +°éà3¹øüLV°¼À/ÙðéµÙâ*ÞׯÑôVC¯ß¼+Yû.*üéAZßÄCÉ´7ZÉW²ÜD;2ݼQ@+ùõ9*5ëÃõIÉí½@ÝýV¼ý +×úü±÷ûÑàùÍãõIÉí½@ÝýV¼ý×úü?ÏK°3-í=óÅÍ?,¾û;³YîWYäÝÊOO35XôûÕY;*:øçWWÏÚ²äºÉ×¾½ï +@ýý<+*ÕIµ8ùÕG±äéÇOáöè?U¹.*-¶ô·ôÙÊOÚíÚ¶ñ³A×A±*ð4áA>ÉÆï?íá±í;*ºÔëãÃùýýI¾Ë/Þ7*Þ +÷ÚÉíì³Ç¼3,*˽6WHÌOº?Øòëáõ2*äT÷ìÛU@õ/*ÅF+õéäÛºÕOøO2*¯ìúè1ÈO²Ó÷ñèæõ2*RU·êÖ÷YÊ? +í¯*è½û¸·UÉòÉ:*áÚ×?ÄYR@Sñçåõ2*ü5¶èÓñùõ9J3J.ÞC../.*Ò0Fξ=5¶+,åDîT8ÌîK7*àYËD¶UÉ +òÉ:*éº×YXIR@ë°äÐÉA***Õ4î2+XºJãíS7Ê2FO¿Á7Îã¸ÅHÛ¼ý+**îN+*¾È¾K¾E2¶·+ÕVîR+P8ÌÌ*õ +íÝüòæÛWG-ÇLÈEV·Ô@µ-ñò±<Þ6áVC±UIR@Ë°ãÍÇA***Õ4î2+XºJCÞß7Nî/1EÞÕçô6Øúûù1Öûùõ-¶± +*AÉ@Cðäß5**¾E/2Á +U42/¶K+1¶ZHÎ2îN04.ì»ßã*Z¶×C¹ôHýû+**îä*XNÞBÞ7+FÞ¾5?¶Þ*ÕÊî0.XØ¿äõìCñêÔì²á;ÐFÁ¾ +5?·çEÅ2îÞ.È´8ÊÞÛÞÖäÆAÙø°3ºA***Q4Z:î¾+XZÞ3861Úü¿¹9¶B*ÜXôãË×ÙYI¿ÓYÝH¾M-¶M*Å,î +*-XÒÞ;8Ú-¿E@BÌR,Îô×µÜFÙÓEë¶Á>å<ñXZ8¹O0Í·M*ÒC°ã½R@çïãÚ5*LêÅ*,,0-8BÞ+0Fß¿58¶ò- +µ4îæ58<*íÁWCñòòÝA***;*ã*A>ÎDîâ*8ZÞÒÞ*ÞÃúñÉ?ÝìóPEXïâÏâF῱X·:@µ<îR¼8@0S´6÷9ÈOTF·ó,**îN+0,ì¯ÞË1FØ¿-C¶Ñ*Õ¸îÌ +*BßéÇ?AÝ8û+***Å4**Ê,FƾUE¶À,¯*1.îÀ+P5Ì3ÞË*æóã?Q¼ÛéK½Ú+ǹC·IDÅ:ñX°ì?0NüÄ°S:ÅX +***Ì>ÞJÞ+-ÚÞ¾EF¶±-õ¼îì-80Þâ<8ÍܺÄð-**Þç,**+*Ó+1>îL0è+è*L,à+X>JKJ7*>½Þ3K*Dûöã +ȯõ¸Ñ7ã9Q6ðNTÌðÞOÕÚƾÑÍ·M*:÷=ÈO¶ú´@á×8Bê¿/Úà¿=I¹:-Ÿôè5XäÐàÔVC·óáÕ2*öÚ¶äɱÝÌ +ã+¾±³·¿@Q@ñÂYT,8Úð?ZHV6ÀÚöïY>ÉÎ÷8+Þ7ÜԺ׷ôà·*ïó¸±*Jú´**¶F¿*³HF±ãÖ¹WÐYÀ*HÐVÀå +-*µôÕDZM.Þ6YQÔººøîU.*WHú°ãÕ¹ãÏ9é3ËY>É0×ïâÎõ2*H<´ãÈÃI9ÜáàJ÷Ä=ò÷ïÈ=Ýì³Ç¼3,JÇ7M +üÁ°Ç8Y@»Å*ÞC³±ÜFÚ·ïU.*ËìÚ°ãÖ¹WÐYÀ*âðáÝR@/±è¹.*AØùñäßÉAͯ±+¶,*ØÛøãʳõØNÝ.+*äÝ +R@3°äÓõ2*V?öçÍÃE½¼Ã*ÎóåµüÚ¹ÕOøO2¾¶Ìú/ãÉæ?°âȯí;*¶ZëÜWù0Üù4+JáåÅ7EöXÉòÉ:*EÛ·±Õ9R@ßý»,*À¼ëEµëÙ.*éûÚõë57ʵÐ5¾TL×úXJ +üÁÖæùÍã/AáùÍã/AáùÍã/AáùÍã/AáùÍã/AáùÍã/AáùÍã/AáùÍã/AáùÍã/AáùÍã/AáùÍã/AáùÍã/Aá +0°3*.*N26ß.CPßåXÀ/â°W¯ÀZúüW×2:ýVHèL+*ÞV**21¶â*ARî¾+0+è0ì/ß¹I¾Ãâð¿@@AÜ°¾5>¶á* +ÕBîÀ,ܹ+VR@ÑZ¾±Úºã*ý»*<ËOJó,*<Éã+¾EN¶IÉQ@ï³IÞÛAPDÉîý+*N@ÉÅ´:ÃVÜÎÂÕë*¹ÆãM5R³º +YÈåSî¶@½¶+êP@/¹*+*A8Z4îî79ì**¶³,Q0îûå°Ä×úü?îûåðùÍãõIÉí½@ÝýV¼ý*öü±÷ûåðùÍãõIÉÕ +OÞÜÕÅYýÙ**ÀÝüùÓ5V-åй1ͼ»ýõÝIAâÁý0*îÜ·ýäû¹¶ôìµ¼ZûEÉH»öîÝÍÙõYIÓµÝüµ**1ÍFôè×±å +ô7ÎYFßøµ@GõéØYå@ͽ½Ø½½üí**üüúïÑKÙÌûDÛX¼ûà7Ô¹±çIá¼7زöùùÉüúùH*Þ÷4¾¹*îûõÁÕË×±ê +Öç°éåAìZÛEÉÌöóæÇIY½½½õ-*FY+¶3*̼öãÇMÝHÚB.ݶÁóìV¸ÛÖEAäFûúù:½ñå<2Y+¶1*Hýû÷ß=Aì +6EçãÙùà7´Q×. +d +602 104[1 0 0 1 0 0]sl 8 mask 0 0 di +/mask 7904 string uc +*ÞÐ**üºýÉöü9õûýðùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ýÝúÐ81%%% +d +/sl 62608 string uc +äÕí´ê9+*ú½³+¾èC0êÔ,ºVèÈ-QXXH»·9½í-*êX+¶1*üH»÷ß91ÔÊDÃËUùà7@ë?Ú=LJóè,VEݼW³ÈV¸ +ÑÏ5õ³êÙùõÄܺ*ð1*L+8JÞ/0F4**:*îê-ì2ßGCFS¿Ñ¶Ô*A÷îH?<8¾ù,Ç4*¾ò4¾¹*îòݹíÛ¶íàÏÃç +ÙWGÉÑ·3¸MBßû>Nã1X=ò߸3íäÜÛÃ19*ú+¾-/¶¿*åBÎ@îX1X3ßã5.1Ú3+*îL8Ìý߯QF¼Åú>îF6ðL.Ö +Ú,î*QX.¾ÅíÌû´à¸1Á0DÙ¸@É/=Òã>È:ÓH¶ÆÍ-á?FE¸óÔ»¹*ë1*L+0,.**1<:8î**XFÞ39>;ÚƾSÀð +À=N¶Ð.A@Î>î:2T.X2á+7FæÂ-0¶¯+1Sî.MÌÊâSBF=Ç=CÇR*¾ï4¾Á*îèÏå@6µîå:¹¸@9ÓZÆS»ÞC-ÚJ +¿-0B.RE¶A.ÕXîÔ7X6¾IòFöëÞÖÉIµçÑ-Ƽ*:4îÀ*Ì6J×ÞÃ8ÚÖ¿Á0¶Ñ*åÔîÀ,ìV**¶Õ,AôîTDììàÏG +F¯¾¹WÆKKX¼LH+*¾U*8,¾IY8¹îÙ¿GI.µ¹º@ÍêZûî7-ÚJ¿E0¶Ã+A>îZ2ìØß2**F×ÂÔ¿5Q̺Âñ2JJJ3 +Þ?-Ê*F°¾Á>¶P*åÔîÀ,ìVÞ;*íôúVPß*å¶÷È+ +8ÛJ,EB-*öP@·³íÝ¿-1*ú*J>Þ++Ú°¾5A¶¶-*úVêÓ3ÚÆ¿UU¶Z+1Êîà.ÌÒÞ¯RÚçÂ=E¶X9å6î>68Õã?´ +ÚÍ*JÙ,îÎSæµíÓQ@/ÜÙýJ@1*°Êã=:¶â0Q,ÞÝ·0ê@ZèÛ¹EíÜ+öH*2/¶J*Õ6îà/ä,ììÞ×AÚÎÀÙ´¶F.Å +Æî¸C̵Þ÷¹F>Àá³·Ã7õ=*ÚÔ*Ú*Â+¾¶*íÛE³Þ´+¹üô;âðG<â<ÉK+å°÷ÎSÞ×ÆOý¹ØôëÚµ-Ö»*:4îÀ*8 +BÞ/5FÖ¾=:¶³-µ.:6îÊ4XVÞCTF¶Á=+¶â2QÚî8?̳M¼0¶¹,µFî» +SHQ¿Ò°÷Ì2Ì.**5×ò¼OáÌW¹´*×1*L+0,.**µ<ÎVÎNî*/XêÞºJFÞ¿<./FÉÀ5W¶;2µRï:>ì2M¿J³ß3- +FÖ¾Ñ>¶Ó=Õßîæ.Ì7JD,*LU*ìêäî+èÌ7Ù²Ý;É*VUå°²îºZGÞ>ÞßEHÚ+*îè.Ì6Þ¿5ÚÖ¾*ÀÉÉB,î×H5¼ +éµòéÖ9-**¶»,÷ÆÞ;BâM1·Ýà0Ü:Õ±¾Í?*AR°*Z:ÞYÅüùÔîå×ì´óVìæ,Xâð·*RSÐ5é/6ø÷ÈOG¾ÕÜ:7 +GVC@=ù5î×,îÆ@9J¾Ä0Vµ¯çÐHÉV0B/,FξùJîÈTQôEB²ê×ÍØê3²W*ðôêÅC¼N°×ºK@HE´çÁýPXWç±Ç +ãUÐÊJ@»ÏçÌ/50û¸´È7ùóÔ1*î×,î8*L@?óáÂGÑPòßâ°HFîÏ6ÍÌÊÖñè×¹I**æ4¾Á*îÚ·Ýܺ÷òáǼÑV +øµSSÅÔÊ×òéLÙôÜÍO:Ø,î:@ûµîØKÅXW?@ÇUùà7Ô¹±çïXÐÊøòåÎQÑéØ·µ»*¾Î?*1+Þ½±QÌÙ´åÊ6ÈÛÚ +AìZÛEÉÌûàKO=Õ´ÛØÒ´ûG*¾åôëÙõæV5GMW55Ý·ÁóìVLô³Ã×ÏðW**Ñ3ÓÎéÜÔÍYÛ߸µ@÷øðÕ5íÃùôÑ* +*ùµïéãÕYÛ߸µ@?ùóæÃñÔûW**ï@8»Aß°Õãðé/ÅõIÉí½@ÝýV¼ý×úü±÷û5ßùÍãäÉOKÜùC**ðíI;µBÉ +>D÷ñÅ*JÆÅüü3·ÁײÁÃIIú.*ê÷ðÁɽýÃÚÞVL×áÍÅTó,*ê÷ð°SIR+JÚ*î²EX¾Þë5Nõ+¿0CÚKè̶ö/õ +D:QOÚÅÀVZ¯5ì·ñ25+ÃäÉO6Èú÷VÆY*¾GYH>ñ58,ý5*̸ç+2ÚZ¾ÊÓ?æîü7*ï¹Cǯ<ìÌâ·7â·2ÇßLð4 +*ý9µSÝçð°°3G<½Üê÷=*:âGÜÇã51Jê3=X1R0:ÂýÝZ¾=:¶Z.1RîZ4T*ä+XÞÞÖJ¿ÞG@FÏı¸¶M.Qøî8 +EX?âÃMÚοÁC·?2åÃðâµìÏã?OÚÞÌU0·´<Åöð:»8÷à¿CFëÐËV³éã<*ºAÉ*îçV4àê1Ö2¯ôýÉ+X²Jºú+ +2>;FÚ¿U7BÐ4T¶À0-,1×îÀ9P/Xçß/-FéÁE궴îæ0P5ÌÚßSDFF¿U¼R±¶¯-A,ïX4X²ß¿BFÒÂE +Ķå,QNõâ¼84ãËDÚÔÂEW¶Ã2žîÀ+ÐöÁ=G¶ÝOÕ2ÑÙ±ÇÎ3½üR@ö²Q°3üA*Â-Ê/FÆ䱿TöºðüAµîÞ98 +²JÒá?DÚUÀUS2Á¶23Õ,ï,IìãðSñGZÆ-D¶é7QÄÜ· +ÛÇãÁ¸Lêýý½×´-*Õ2øè0ìú+:¼À,µæ:ÃV4ôýMáXKàSØÛßÄÙC¶=?A.ïÆ/LIìVàSÝÚQÄáW¶Á2ANîP>äD +8ýßÇ5FËÆE¾·±?ûGÉ>4öñÅ°S²ÅÈK@YB*:¼²O;+ÕÂîZ4XúKÞê/âå>ÉÕ<ô8.XOß/´ÒBF,ñ8ºB6Á¿-À +3*ðÞß»6FO¿5¶>âå¼ñÊLÈ+Ì?Þ÷@³úVDMÛÍÕ¹=ÉÀØÏ=×V*¶ÑVñ+¾û¾»BÞ/>Ó.F×À±ú¸Ñ+¯+M.QæZú +î>5̲é+PÚBÃE0¶Ñ»QLî°í+ßÇ4ÚÊÇUà¶âAQÂð<;ÍÒâ7OÚéÀ=+SEVò·±IÅ>ðNNÌ>Jçã÷DzøV¸IÖµñ +ü»Ù´ëÊEµìÕUÑôñL@/ùý/*1èÎ0:BÎ,î6.8Wß34Úé¾5>¹À0á1Q×ZÆî¾:8Kà;-6;Ú4¿=B¹+4Q¾îN/Xä +â?¯FðÉ=K¶<-AçðÄ<ì³ß;3F2ËMR¶°EÕâðTLÌ´çÇÅôöVøDÐSÑ´û¸´ê*¹ê<çÔÛä¿VįA+V.QX*½PÌÈ= +ð9¶LI2ÞS¿â·A1øÎéZ·E×>ûÚF-*ZóJ@åÖÉ*´áZ·»T̽ëEÖÊê´ðçÁÌÌOáÌDçÃÅ*JÆÐÜ?ÌÚJÄÚHÓçÞó +´TðÓÉ´Áãóâ½ýà¾Ý**ñíýRäµì>ÑFëV@ÕβSDâø°×ý-:çVLÆúû½ñý.*N½Æå°×øN**¶°R +*JVJGÞBÞ75Ò-FÒ¿=·¶<,õ¸îÊ<Ä0X·à¿9FL¿Ñ¶¶á*QôдïTSX»ã+7ÚRÀ±ÉÆQUXTá;ÑÚ³Æ-Õ·6?Q²Ð +Å9:çVÀ¾å°Sö1ºDÉõæóV.ÌÎÞ++FÑ¿ÁU¹Ø-õ>ïÌ=ÌNÞW/Ú4ÅM:ÆK2XOÞÏë»ýÅ°SòQɸ@+ùïø6± +·*îÖ<8ÒÞÏ9FN¿±Ç¶ï1Q2ï²;XUíç¶ì-å=:+ÙFµÃÁWÇN@Í4ß·µò³WPDÉNT»ýÅ°SÎɸ@ɵ<üæ.ÌIé³ÛILÁéGB7¶å0·:L40¶ +³9ÇâðæÞòÐÇ5:çVØMîû-º=ÉÀùÏ=×F*¶<+AÐîPßT.ìÞëGÎGR¿¹U-*ÌôJ³Þ7EFï¹ +3¶Ä,ÕVïÂ+Ìêã¿WF3À5æ¶Á.Å,ðê<<èÈÁè¶TÎõ´îÊE8/ßÇMòï-NDÉìQ÷òèåYżG±åXG÷ìÚûÚÆãË@Þ¼ ++Ô×ɵûñƲ@ÛËêÚ·ýPüº·X8G¹ôæÏÙÇ·@ÁêYN¼à´Æ¹6°ôÀ+ÄÕBÉÐíɱ**Õ°ÌãWÞíP³CùõüAZƲ@7¹è +NAH,*KÈø-ùÖPËÊV±ÇÈÑäú2*.óðí½5ÑÛÀV±÷ûYÞ@òîûåðÀÉïûåðÀÉïûåðÀÉïûåðÀÉïûåðÀÉïûåðÀÉ +ïûåðÀÉïûåðÀÉïûåðÀÉïûåðÀÉïûåðÀÉïûåðÀÉïûåðÀÉïûåðÀÉïûåðÀÉïûåðÀÉïûåðÀÉïûåðÀÉïûåð +ÀÉïûåðÀÉïûåðÀÉïûåðÀÉï²@+ +d +602 104[1 0 0 1 0 0]sl 8 mask 0 104 di +/mask 7904 string uc +*ÞÐ**üºýÉöü9õûýðùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ýÝúÐ81%%% +d +/sl 62608 string uc +äÕí´ê9+*ú½³+íÝ,Ô¯1¾èÓ/Ö´¼ûØøü±·¿Óöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãö +ü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ß +ãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü± +·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãö +ü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ß +ãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü± +·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãö +ü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ß +ãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßã¶DÉ +d +602 104[1 0 0 1 0 0]sl 8 mask 0 208 di +/mask 7904 string uc +*ÞÐ**üºýÉöü9õûýðùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ýÝúÐ81%%% +d +/sl 62608 string uc +äÕí´ê9+*ú½³+íÝ,Ô¯1¾èÓ/Ö´¼ûØøü±·¿Óöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãö +ü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ß +ãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü± +·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãö +ü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ß +ãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü± +·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãö +ü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ß +ãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßã¶DÉ +d +602 104[1 0 0 1 0 0]sl 8 mask 0 312 di +/mask 7600 string uc +*ÞÐ**üºýÉöü9õûýðùýãõýÉíýAÝýY¼ýÝúü½÷ûýðùýãõýÉíýAÝýY¼ýÝÊàO*%%% +d +/sl 60200 string uc +äÕí´ê9+*ú½³+íÝ,Ô¯1¾èÓ/Ö´¼ûØøü±·¿Óöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãö +ü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ß +ãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü± +·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãöü±·ßãÆR@+Óõ9*U¸Âº°÷ûUÞøPïûåðùÍãõIÉí½ +@ÝýV¼ýÒöü±÷ûåðùÍãõIÉI-Vö°ÏöFë×MQ°*Þ¹ôêï.Íæãí½@ñQ-ÎCÞB.:ßV8+*Ëí½@±A-:õà5=ËO×M +ìÏHÖõóïéÝ5*:úóÝýì×.>M43ØÖÕÑNJ¸G@Ø*-J÷N9Ú¹öòéù½ýû·KHú4*Þ»ñüËùõîêÛýë×.Æá3¿ãðßê +4ö*+¿ã:-RúJ9ºøîß½IµíݼTí¼û?*¾ÁQ8¹óê×±ù0æÝôVD˼úV¹ÞÁ×?î/,Dµ¼úæºÎ1Ìù³ëÕ±ÉåÝIëÝ +Iý4*ÞíÍI¼D²é×YÑéãÕ½ê×òë2;øø¸´éÖǺüô=-*YíFóåÒ±Ý0U2;¸¯¼ìC´éÍO½ÝÝÝòå½1*îûõÁÝË·ñ +éÖÇ°èäIôVøRÊB?Ä˸ñæâ»üû×+¾ÉíËCñèÖ5±<ÊLGæ-9úD²âÌÕÙÕIÛGNï,XIûîÉ;Á¼GÖ20ü¯åÐÑLÃT +YêÌ7µ@èçIéH*:*Î8î0+è*Fü÷ß?Eô6¹íJÁ-»î³WYHº÷÷÷ô/¼ûå*Zøî8.8±ÞÏ9¶úôå1źö°éÒÔìë9¹ +@GAXÚÞÐÌ6B°KXüCVHK¾=-¶K*¼ÜúîÅ+QP7´Îß¿HöÒÐ9ù»6FúöÐHûU***ì:J<-¶â-ܼºöÝ7ù»VDOÃU +ùáÌOµäÊCÎÏ1íOÊ´±Yý0**¾E0¶J*µ,ÞÛ±ÝËöìßÏ¿0DFæ-Ù3øZÛK1Ò»ºöÅ***FÈ**Z@**Â-Fè¾E>¶, +*<¼ùJ/Ò¹Bò˵½ôVø¶ïàÃúV¶ìßN¹øöµ***Ú¯**îâ,8>¾YÍXùîÚÀIÅ6²¶Î1üïñÞ¶/åÄXû×R**å::0* +*Ê+Fè¾M0¶K*XGøêÀï0üúïéÌOݵíÜÁÌ-Ý7ÊZ=íü0**¾E0:**ÚÖ¾-/îåÇá8ú´Zå1-78ò+>F¸ZÕÇIÍü +4**¾É6B9¶ß*A¸îR-ä*æ·ðÕ1?NßÙI¹@ëßÁ1÷79ÐèÝÍÝ+**îÄ+82ÞË*:îÝU9,,¼¿01øî¾8ÆõíÞY+** +¶>++*1HîÀ+XÊÞÏ0Ê+ÆíÜ9Î,Úû×±çOÈ>3̱޴+5èÛíìîÏ4H3*íÛE³Þ´+¹0AæîJ9JÚ-¸*ÜÍ+**¶0+¯ +*Õ6îR1ì:K2Þ+T¾¶Y¹@ËQÍHË/9DÝбõ¼.**Þ3,Ú¿¾-,ZرÉôE22Ê/DFæ-¾@òJýÌÔ»EõëÕ***ÚJ¾·¾ +²6î¾*:õëØQý7Ò*æÜôVøÔWÙôøFöWÝ>ø³ëÕ***.*Ò-Fæ¾=@îÖYÅüù´îåÛ,78ò+úT>³Ö±éÌû6*P9XF* +âH+23¶Ï+µ6Î>Î0¾²åÄÛÚ4¼»ë×ò»ùô?OD¯Û¾/²ê÷+¾Ä0VA¯çÐ,78ò+Ü,»BìÝCèKâÐ;=ôÚ¸´¸ÆN0FµìÕAU@GCÐÕGÉHÜ»ù»ASP»D»0˹°*îÝ×=åWÙ±P»Ç»ùï9ò¿½Éȸ²ãÊOÝ@ +سY,@ûµîØKÅXW?@ÇUùáÌO1íݽý<1?ËVTÙÌ7*ÖµóïM±WN°Æ+¾½Î1*õ+ÈÐçÕ³åôÐ3·?CÚÉùáÌOßì¼F +Ï4üÔÅ4?S<-*ÓLÃÈHí.1B*ùQø*@@ÈÀè÷Û²,?Z×»ùôGÚè̳WVúåÌOß8½N¯ä,*ö9ó+Ó?äPºñP.¾çÂTæ +³YéåÝôV4ôôÑÄOZý49öøðÕ5íÃùôP*JïìWú¸÷ó½¹@;¹öóïñÍ°?7½úóßÝá-Ú8Û¶ìØõ,*ÂÙ¶òåZ÷Oå°õ +ÆâùÍãõIÉí½@ÝýV¼ý×úüSïûåðùÍãõIÉí½@ÝýV¼ýÈöü±÷ûå°Áå<Þº**Zñù1¾ëÇà¿P4ݼM@ÓRÏNÃ,û÷ +û-ÞüR߸ðÚCñÃVTÊBµ,74òù1¾/Ô,ùÚ÷ÐOɼûÄVàÆCÐYÝ;ÉدáÅ:MÌúü+Z¿ÓÞ>SÄ +úöÏYÝ;É@¯àÂ4ݽ*Vï?âLÄ+*àÕíÆãõI*Ù?0Zóû-ÞÀÉ+%% +d +602 100[1 0 0 1 0 0]sl 8 mask 0 416 di + +QP +%%Trailer +%%Pages: 1 +%%DocumentFonts: +%%EOF diff --git a/doc/krdc/preferences_vncdefaultstab.png b/doc/krdc/preferences_vncdefaultstab.png new file mode 100644 index 00000000..630e9ab7 Binary files /dev/null and b/doc/krdc/preferences_vncdefaultstab.png differ diff --git a/doc/krdc/snapshot.eps b/doc/krdc/snapshot.eps new file mode 100644 index 00000000..2e224e1b --- /dev/null +++ b/doc/krdc/snapshot.eps @@ -0,0 +1,301 @@ +%!PS-Adobe-1.0 +%%BoundingBox: 0 0 551 339 +%%BoundingBox: 0 0 595 841 +%%Creator: KDE 3.1.92 (alpha2, CVS >= 20030921) +%%CreationDate: Sun Oct 5 15:04:38 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 10.416 3.125 ] [ 3.125 10.416 ] [ 3.125 3.125 ] [ 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 3.125 3.125 ] ] d +/pageinit { +35.52 24 translate +% 185*280mm (portrait) +0 793.92 translate 0.96 -0.96 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 481]ST +B P1 +NB +W BC +/mask 7797 string uc +î½*¾î1:?*¾+á.37*¾Í5;*¾â2+¾QÄ2îQL¿Æ6:<ÙÌâH>ÙóâôK·++úÇëÁð,,÷+ö´2+HV1;â+¾ÙK.BZI +ÂÖËK2*+ܽLºýQúüÍöûIïù½àõýÂíý3Ýý=¼ýQúüÍöûIïù½àõýÂýù3+ +d +/sl 62263 string uc +î½¼**û½0*NÖPGÝ.Y-3å*ÜÅ<èûâA-HEôÉ**ÉUBô÷Y*4ÑDZ,9öéÍ+*¸´ôüû÷ñÍäÊDò0°OLéI¹êÔÍ-* +îYöûóííêQº0ÃÏ+¸I÷ã¾åÄ»6*ä@ûüÏñ<=ËTóTײÇôú=à×ñùñüø¸ÊøÒ.VXßÖåÅ6,üÕÈÆÞ-üD+*BîûÇ +YË0*ÚèÕ±õH@»EµLÚ¸æºÌι·*ñìíÝ´é6êÒMWJää8E.Ñí>Ïâ8E-CMØûõÍÃD7*öÖØ?±@ÚÔRÈ>;NÕ÷FK +ùð*ôì9ETÔCÞSÉØ*ü/ݽýÙAÊRL±»+*ÚööôñÙåHúÂ->UÔÊù-YD9*¶Ô×;EÄÉ?RÈ.ÏÜÙ3KKüG:ýýµÑRJ +×ûÙ1ËøæÈøN,ì@ÌÖPý6*¾óºS*ãÌíݽàTL;Xó+*öÜÕÙáAÍGøñòFóÛAL*Óüì½öæº*Þ¹ØÜÙõVÒGMнèê +ÜC¿ØQ÷ܵHµ:¶/Ýñ¾3-TÝEêÑOJýý-¸Ë/Å<¾×à8½Ýýûô=<ȹñ¶;45D*-ÝÝçÐQÐÒQ8*øHXF½Ì¼»ûõãÁ +õÌÛ±ÍüÚ·ñå=P+ʹ½½åùÀåæü×R5*úëêäßíÅ9ܸºÜF¶ê³9Æ*L?ÝIUÎ÷*¾ýFêAëÓLW8²ïÄËË÷â.ÀàZÏ +ÁýÙAÊä7æýýý8¸¾,èÒï»ùMí@+*¸Þ*õÖî0,úTù÷½äÀNÊOÆ>ÏÏÚE1*ÒL¯K-¾ÛùÑó÷òÃ>³3-FYF½GÛ¸÷ +ûñݽíéÉåüº·ðÕÃPÊ,ܹýüùüõí9F>»**ܵµòîïíYíGî°ùõµFXýüÁ¿æ,*½TB¶ñííéÅÍåUõGú¶ßM9Î*óí½±P*îDMõ>·Îܶå2ÕØ6ÍÆJÊÍý +òúIêLEIýõKÏ÷ÝéðýýYÛáýü+µú½ÜF¼ú½¯T³ýÅWM*¾XöÞ/CæËEÇü1íéÃ3æøýÛSøäæÖ¾+õÜÇØZ*¯÷ý7 +FÞà5½üýÙèÞèýAóìFÞÓTJÚ=ýÍ9õä+X²,µüÝCÕPú8Ë·Â+ÚëÀG**Ðë½ÙNÍ1ÎÒO4Z4ÕÈR**¶Ò¶Tô¶îáã +á9úáôÝ-ý»ÚVÄåRJ**¶È,,*¾Ç,+*Þ74¶öûåýÈIýèÍ/*ÜѲðàÝÕÅýÜÕõHFõïÆYôV>JûùáÊ3*úâäH1è +=AëIàEöà=Èñ*å¶ZÈïä+X²L¶ðòâÃ6FÑÂTÀG*¾³ìI@à-Qè=.2ñÝ×ÍY½ú1²ÝSFí +2FóêéãÍQíåÉÝHE³î79HL¿-ìÉ3/*åZL4I¼ä-*ZýÀýÜûL3Ï´îßý¯V>JûùÉÊ4*FQäT9=¼ÆïàM;2øð<ý +üíéÏâõøï;J¾*3D9¶¯*HüýV;»üµXR»Þ/9F;->Sø½ÔÉÌʾõ±Õöý÷÷üýµìúÉX¶¸î882æýàU7Æóý:ÉGì +ïÞ×+Úæ.5ýIÖÆAÆïÒ<4HÔ:*æ¶ýÙ/QÜîÑ8¯¾SFÀ8ÜúJF¾üRïúÝíÐWMý8¼º1ýG¸éнýMö0/Xº9*¶Å,, +ûù;¿USÞCïáµãÛ¹É1**åï0HùöL-ÃÌE¶çí4@4ÖùûU¼Ê3*ÚàÜ»ÁñÌØÔZ¿·ïÇ2.»÷Å;ÁýA÷ÏíýP´ãÈ°é +-X6¾1üYÔLæýWXÀ9ëýÝóï/9F:-äµÕõýæI7¹¯,êõåÆBý1Y>èÞ@ýÅ÷¯Zß7FöýýCÏVûIâPÑ0Ü=ºùQ¯Ä> +4÷?8ÚÇĸ¾5ËÎÀýÁ8×ðéDű-·Ð.¹**6ÛýSÀ;YöSÅBJ?8Z9æ-íY?ÌÉ-¼ØóñëÛ½õìÙIµ»D¶·,íÎÞ3=Ü +M+·ÜûÆÞÓ>ZÉñåÙÈHûâ-*Îóì0ÍÛEø*,ô8۵ﵿ/V>ZäùAãB=*öãî+QôTõíÇÀXà=;2øð6C,Û9¼JÄR¯ +¶üøÙÕE9¶°*Ü?¸üíË0ÐÄ;ï³ýý39F:-üÍÝø×Ë@GºK0Êû»ýöYõà>àÞP¼IÍíZß3FÂ>JýíÇ,àÔÐßKл¼ý +ü÷ÛËGà¿MçR/·Î3Úïèïúõɽµ6ïêJì>ß4,*»õYSîá5¼è;0¶Î¶Òû¹öå4åÉ/µ7ù÷óêÙ¹ôéÍá´Gº×½CÏÞ ++GHL+H÷û¹UäËÁº**ü9KîÛ±1Í8¾Åý8Ûöî/³ûU>ZÉù=ÌB;*ö¿ìãµõÛCN¸ÎîÓ32J¾,+FIKÄ.PâÄÇ:C +S»ÞÇ+J²Ç.SÐ@ºJÔ;46¶Þ-1â*+3Ø>6ßƾ-=ÎÊ2/TòÈÝ3-U2S;¿Á/Aî:Xå*/Ð>ëòî¾Ü<3D.3ÓEÓ¶@ +7Ó:AÀ+Ó¯Ì**îVïV=îß +»I3K=Hº=»SϾWåÜûùUSÞ7ÍIµãÙµÃA*ÞïÃ<ø¹öïçß±øçËĸ²7ß->ZÞùÉWB;*B¾-í;ÊÇ2Ôã-ßÂÙìIJ +´¾9Xíͱھ3ÍöSÅFÞ÷à÷KÞ¿5Y8»¸ó¼ûù÷éÉ=IòXä>Î4Ã5µ?¹ô=Fô0ÛOø0>°ïÍC鸺2ç¸åî+4JØûç½ +6ÖY*J*+è;2ßÀù½8ÔÌÞÁQúÒM8*6ÜXFÕ-FöîáËE½ìÏYýX¼ùô¸èãÊ,4ϵíÔVÙ¼ááHîFRºæÆKñ0øßÒUF +¾QR*Sõ½H;Þõ+*ú³ýüýÐã=öPÍØ.,XAÌÎ<ñ6*ÂÍ·CÑ*AÊGÙ?çÐ1H+*AöÍÛ±ÚR¿L/·G±õÍÞ=>Î4¿õÅÌ +ÐÝÝ+*8îîüûO0=ÕíË=óÇ-FYFTQUÑM*:ØèOË,Ö¸³UÌGÝ4°ýµÍG·îâ×Ù1üÛD+äƹýüÊMðùA*Þ/LÓܽ½ +ÇÛÖ+ºåÎÒQ>î¶ïRÝ·í8+¾ÊéA³4JæÎWSÔF?Fù¼ÃïõíÍG°áîóÅ**Ø5÷ó*;ô6ÍÕ³óôóÉ4Íøñ1îðãÔ6EÌ +éÌïÜÃÃÇõýB*ÞEéÊO6E¸÷ôã³Qû3.öö¯UÀ/Qä.X9H¸0Å<óÉÕîÓùG*¾µÓS¯CZãÔÃ=ôòóö<°¼À/ÙðéµÙ +â*ÞׯÑôVC¯ß¼+Yí+*úÕYÞÀ7±@ëDÞ@EF³R;2ݼÅ3+ùõ9*×TßùµàõíÂíÝ3ݽ=¼ýPúüËöûEïùµàõíÂí +Ý3Ýý:ºåP²íéáÕ½Á*Jöé½ýÐËæÛÁQÝÜýùíÍ5ðßA0*ìEõíÚ½1åì»°PÚÏÑHHçYã+H»öîÝÍÙõYIÓµÝüà* +Þß=ÅÛ¸ôê×»-òSCï¸ÌD¿ÇüT7îÚYé¼GÙºüûçüû÷A0*üüúïÑKÙÌûDÛX¼»HPÚÍÑPZÐ5å*J³ß**ýõÍEüC +ôéÎOÁÝÝÝòå½7**åU*ì+¾õÝ9Ü´ä̯áPÉWIÍÅ3õñÒ3Êòà?JRûÝZ*êIµFïéÏ=½ÜüüüÃ**FY+¶3*̼öã +ÇMÝHÚB.ÍÅ3Ó±êÔIÉü¼=QóûõÁÝË·ñéÖÇ°èäIB=¸ÛÖEAäFûúù:ýHÊ2ÂÕ*Ú-¾2¼9H·ïäÖóðëG9=øÞÕK +MôºÛ»û5·TÎIPT;Á¼GÖZî9Ê3»VèÈ-QXXH»·9ýD**´Õ*Ú-*½ÍÜúîÅ-UPËàäÓG9=øØÔA9Üúúºù,ÝüM* +**0*X»JX4îöëÍ98÷îãÔSWÛÙIB=¸ÑÏ5õ³êÙùõÀÜúØC:I*Î::<îâ+Ä.8?ÞÞÞ¿0Úø+:Û,î8*Fûõì¹Aì +Ù¶²¯ø´LPòSOIôùB¶öïO»øÅ***ÚK¾61ÞéÅ1ýÙ¯ÝÅOÉҶͯáP>Z¸=Í9Fò¾ä±àÒ¾ñÈ,ÚPæÁó<ÜË»¹59 +H*ö+*Á*/+A@î¾**¾MFB3¶--µ:*@Õ*>9F+*9ÝGùëÂó807²äâÂZñ7¾F,¾M4+*8RÞÃ,Ú°¾:ÇMI+îÍZ- +êºÅ7EKÒE.Æ02¸Ï3ñ,ÌU4î:I1ÄU¶´òèWùô*Ø9*Â,F¾¾=:¶¾,³+Q2î00ìJ*BX+¶2*XGøêÀï0ü6°PP. +9Y*â·ÞÊ-FZ¾¹6R0¶K+A.î,EÞáÓCï/¸I°**î*IXRÞC°³J=H1ÌU7¶L*üÎñÞµ-áXüÚOÍ8Jø+¾R¾-2¶, +,1PÎBÎJî428/+*X+¶2*üº·ê¿í0ôúZPP./1Jâ³ð+:Å,:Â,*ÚLÅEC·ó8µHð,E.÷éÁ7ÎîÔÊöÀÉ*ù.ºø +Æ,̲TíC¯HÞ·Ã,M**î>7Ù0XF·ð*Æ9*Â,>ÐG·¾5=RG¶À+1Ðî..ä*¾ì4¾-*¶à0NF¶é=µ*ZPP.ú0JV³í +Ý¿Ý,**îâ*0*è+X¾JïJËÞ×2.+ÎíÜU9´*¼é°à5Ÿ°*õBóL-ÌRJ-Dï?îÊ/FêàMBB-*öì>ë+¸Û2Ö=V¹- +±µVô428/+ÎW+¶Ö/Að0îãâÂH̱޴+ÑìëùC***A2Î8îN.XZÞBÞÃ0Fð+õë¹µè¾ë,ô6àWÊ3Yö¸.JÇè/² +94ÚÖ¿=4B-æ¼Pº*Fµ<îÊ3YÒå/+¾µ1È@Êô*ÛXïêß-F¿¿±»¹-+Þ³/Þ/A6*6ÚGÆEö@==,²+:øõ²ëÙ·í¼ +.**Þ/,Ê-FZ¾¹6¶Ó+A,*·íÔ»øíÞ0¾´UPVNÈLÏXÃÞÇ*òG=ä-FÕZ±0*ÌÛSØ=E¹ÑY19îÀäìFÞ×:ÆëRU* +È1Ì*ÞÇÚ¿±CC;ú4*ϼOáÌW¹ôM***Ì:JFLSÞÃ,Ú¯¾3¾5PR-¾Ø±á@ëµZ-úØK=ܳ¾±4¶K*ûæÂÕ6î¾,Xð +0*¹Ñ²Ã¹A8Ô<îNß8FÞÃØG¯¾EÇU,*LU*8Fß/IFÇE*@==ðUÎ1áû5DôÛ*¾Ä06õZæÕ´ãÂ+PMPó*Q6Z,îP +.XܹíCï·îÂ,Â-Fé¾-*Þ×H5¼éµòéECDö×,î**T31,öóæ½í4üFUPPòÌRIäEB²ê×Y³àò@Lâ¶*HE´ç¿ +÷HPëçØÊB±A²H=PZÅõU*OÇXVAîÛZHöV+¶ÒºQ¾íò@@WTPPòÄRµ8VBòé×S´êÕA+*ßÂÈدßËUEØ>=H×è +FßÎYËö>½ÊÐèÇûÜâúÓB;Þ¼ÎÂ+ú¶öíâÓ?ãâÂ84°çÆ3¹XGùL*Þ·ñ¼»×°çÇ;WÈ3ñ0Á¼³àÑ.ZÃP¹@ºAÊA +Õ»Ô*Ú/¼ºÊֱ谰Lý@?óæÈCɼW²éÜÄ<øíÞ³Á8ÛØTV:õY°à.TÞLCHG÷°B9¾Ô*F0¼ÙºøïNCCÏáÛNCó +çÍKÍPÕ³ñÜ/*öíêÇñÔëC=Á°Aõ1äÂùߺÇW²/J´Þ½êÂÌûàKO=Õ´ÛØÒ´»5*î×»ùôGDéLåRéààù¾3µÚÂî +ýü·-îì1ñKæ4¾ê¾ýØ3;ùØàêR×7*ZDÏ4¸ëéø÷HZàÄÄÅóZ¼Ð޽캻÷éÃõ໹A*ζõ@¼Ûúø9ä¾ûÄBý +ú.8-ý·=TôéÏ5åWù´*¾¶5ÅüÅ?ïÌBIIîݵà1Ù+Uàúò¾ý5EïØÕïûEïùµàõíÂíÝ3ݽ=ü7P.Òܽ*FI+öI +;=4ßµD1*¾ÆåÅûÛµ8**ÄÍ9O*áÊÊRúñíF**3@ç<:C-¶Â<µVð,,¸¿S8ÎâóÜO¯-F¯¿Û +9Þ/ÈF+ÆU<·N+ã+Õ4ZáS¸ðNµ5<+-ÚíËÒÊ2Ô1***Q.î8-XÆÞ/5ê¸+¾âLÇ5:·K:17Zù+¶¿·,:QHðÈ+ÌâÞ: +ÞÃ,V¶ÈÌRáßÉÚÐǹAÆJµÐ°LRH***Ò+Â,F¾¾53¶ï+ÉA*J×RðJMXÞâÓÊò¾*FJÇ=<·ô;ÅDîL*ì:ßÄÑòN +D8HçßBãòQ¾°TP.à-**¾L¾R¾¿¾-0¶Ñ*1¶Zí-*A5OXÖNúM?NE¾â6Þ+ÈF;ÇØƹ0¶½Q5ÌÐ=ç¶Ù:3BÞûì +N㾺***B.*âQ+2***à+L,82J»Þì+*°@ÈF¯Ç-G3>·î*é1ÇC¾->·@OÕ6îÊPÌ?ÞÀÑòÂDX?â:ÞËÅF¹¿ +>:;?=ÀðË-*ÎñKÈ**µ2Î70î»Ä<*ÞG-ò2 +Ȳ3/¾ÐUL3RØùÊ2μ¼>+RÑ°LÖÜ·**м¼>+S±°Lß½ºñ**JûúR,ÎäâÂ,ºèä+*Jø÷Ï.:ËÇ3/¾ýí½*8ÅJ +XPúü+î@.*¾ÊÊöûÍî»4NâÂ*Þ/*Æý*Xéß÷3âßÏ¿/ÕÜ:UGNî3±ÊÊÊöÞ2ð +ÞÁNËï>5@î¶UÔ¾ÞÓ3ÚÙÈUÁÖÇ=ÜÞÝùï¶M71ÄîòT +ÌÚç÷ÞV0ãXØàË.ò¾*Ú/Ø4¿â/:;=ܷù.ºO:µ²îæ08Lçß2VTÌ8âû÷ÀFѾ.ÊöÂ98ÙìPPðFJô=5U;ǹù +Ǽ;<°à=çÆÁ3+¾*¾53R7¶K,ɳàÁBº>8õ÷ï*58æJFÞ3,êÊ3ÍÆàC´Ú+¿@2**:,Z4Z<Î6Z+,ÆÆ3í6åS= +HXè¹³½>.1×ZMPÊÞö8ÅáË?Fï¿E<¶ÑQU²à¹?æÈP.*°°à-MÆÐ3í;Z=Bï/°F±àÉD¶2+QØZ=ʹñÏå@ðV, +ØÞ3ÍÕNè.ÆÆ3íÚàS=ÄUG¶¿Z¾3¾5,·±,AHîà48æÞÐʶÂïêB8?NSJCÞ7¼ÚÖÀ±D¶<1ÃÁAHî,3@±<µ3²A +×Ö2ÉÚ3ÖOV.*ÆÇ3ÕO?Å÷ÐU0γHгF<;=ìRÍÜ°=,*CCïÐÌïòGø0ÀH4*NâÂ*JúçùI¾Ñ9¶è +d +551 113[1 0 0 1 0 0]sl 8 mask 0 0 di +/mask 7797 string uc +*Þ´**,ºýÂöüÍöûIïù½àõýÂíý3Ýý=¼ýQúüÍöûIïù½àõýÂíý3Ýý=¼ýQêì=,%%% +d +/sl 62263 string uc +äÕí´ê9+¾Îøýû-Þ÷3ÚLKéäÕYUíÝ3ݽ=¼ýPúüËöûEïùµàõíÂíÝ3ݽ=¼ýPúüËöûEïùµàõíÂíÝ3ݽ=¼ý +PúüËöûEïùµàõíÂíý-ܽ=¼ýPúüËöûEïùµàõíÂíÝ3ݽ=¼ýPúüËöûAÞùµàõíÂíÝ3ݽ=¼ýPúüËöûEïùµ +àõíÂíÝ3Á*ܽ=¼ýPúüËöûEïùµàõíÂíÝ3ݽ=¼ýPúüËöûEï;*ºýPúüËöûEïùµàõíÂíÝ3ݽ=¼ýPúüËöû +EïùµàM*öüËöûEïùµàõíÂíÝ3ݽ=¼ýPúüËöûEïùµàõíÂÅ*îûEïùµàõíÂíÝ3ݽ=¼ýPúüËöûEïùµàõí +íÝ39+ÞùµàõíÂ5´àõ9À×EïùÕ¾Ú¾îûEïùµàõíÂíÝ3ݽ=¼½P²íéáÕýý**ßYýûàÑÔ7ÊæÛÁQÝÜýùíÍ5ðß +Ý7*¶íÚ½ñüGF¹õùÔØXZLÍHºöíåëùJLÔÊ÷K*J´Ô½8*8H·êÓ±åÌëEJ½>Sô»»úøÕ1*.½ûõËßÂÌù³ëÕ±É +QÐÊÚéö¿**C»Eý·*öùñÅÍ0ë¸ôéò¸÷óA:ãÛÁQÝÜýùíÍ5ðßµ/*ìEõíÚ½1åì»YBϽÄûD²çîÕÚ-ý½2*Jà +Ö÷ñ½Ü,î8*ºýûî³±ô6E´Øêø÷-ÅðçÁ1ýûÚ»ýøS»ùóµ/*8H·êÓ±åÌëEJí4PòÂYÅÌú÷øC7Ý2=àñýõí½Ü +,î<*FûîÉ=ż»¶¯2üÞÏ>õéØYå@IúRôåÝ?*öùñÅÍ0ë¸ôéò¸÷ó7ßÂTAôäÃCQýRÊ2ÂÕ*Ú-¾*F9H·ïäÖó +ðëûÞÏÚ±êÔIÉü¼½½çÍýØ*Þùí1½EñäÕ¯=ãÒ˵.=èÖÒ?1ÌÚúE¯LÀ9ýô4¾¹*îõçÉ9ü¶ZãÒSêÚØ9FÈ1¼7 +D°åéëé½Öǹç*ÕͼöãÆKÝ8V.-í2PÒPMAìéÖõµàÂNܺç?*õ*Þç½õܹïÜÃM5ѵYBÏÕ³»÷îãååÙAöóI, +**Z*îè-<Õ*ÅíüúîÆ1Y´ËèèÛG;=@³Nкųî².8ºJ8ÀÎDK9ÔÉöôµàÂ.Húã?*-*Ó¯±*H¼¹ôÙ3éG6Dæº +àÂHÓòá¼9-1IÜÒQý2**¾E.ÖÀ+öøïßý´êõ¯ç×ÔZ8îæ*Ì3ÞÓ6FæÀ± +0¶ÐÏÀúó¯0RBA=8*ÚCæ±ûɾ39ìJ+àO²HðÀC*¾µº-T½=PNÞ¿ÑW+¶**ó¯ï*òÙõèñµ*í3=,¼óRàùõ5P¶ +Z/A2Z8îà*ì2JJàÓPÚî¾E0¶Á*AÊ+öããÂQ0î¾5ÍEï/NÜ°à=غÁ*ÇBϼñ6ÖWÞÒÝ3=<íÜÛÔ*F*¾ØÓ¶*í +ÛE³Þ´+¹üôâíJÚ¸>æûL3XVß/,Ê-ÚB**îL*XãÞË@ÚR¿±=2åRä*ïбà=Û¹>ÁõÊöô>9ÃJ¯Þß9ܲL¶@ºM +üÌüPÊÆ´íÄØ,Z*Îƶ0*·íÔ»øíöE¶ôâí*ظ>лEõ¸À-.**X:ÞFÞó+ÚÈ¿5Y¶J+A6Z0îÂC*زËöê29Dï +?LPZ/ÕTîZ@+Öî7ïUøE¯LÈGÙæ4¾,¾5OÒ׳åÄWØíöE*ôâíÞÕ¸>¾GZî2*8RKFÞ/0>-Fø¾U?Æà*ÌB-úÈ +Ð3ÁõXäî¿1>úÛ¯ÝƾMERF¶ÏÁÅÌîÞ,ìNÏäøVöWÝÏÝ3=<êÖ½Ô*F*¾ÐÝ¿*¼GøíÙÀIå¸àÂø4ò߶ýÀ¼¿.é +°*77BìßÎYOã¹پEкÎ*TÈòà¸+ÁüRÊ2ºÔ*F¸à-2ÎÏ+éWVØÒ=½ñÌRIäEB²ê×Y+YÃð7AVJ+º´êÐ-ñ» +ÊØѳCï/ÉâÂ.1G*?ÓÌêµîµáÂ*ù+ùÃÝßß½=ÕÔòâíÚ;èË-50û¸´È7ùóW+JBÁдV¶ñèÄÔãÂ3XðK1ÞLÑG +5@úÍB;Û¼ë±/ÞëÝßáÂGÑPòâíF>âÐ;=ôÚ¸ôË+¾ñäûø°ãÐ=Mع=Üò*9ãöòâÂGͯT2IýK*ÞüåÌ7²/ÞïÝ +×ãÑ9Q¸ðµüWTèÏ?±@ûØæÔ»YOòÝ¿é0G¸³ÓÖJìÌZà-ÜKEíàìÑýÝØQÎê×ÃÔ*FVàѽñ¶ASP»ø±æÑç³ëÚÕ +/*üE¹ðÚ³¹äâÎêãÛ;ʶÁÜçÕÊ*KU*8,ÞëÝ÷ÃÁÈPXë¸óèÞøØ*Þ±ùôë¹²ÔÃÌÏÔÃÃݲL% +d +551 113[1 0 0 1 0 0]sl 8 mask 0 113 di +/mask 7797 string uc +*Þ´**,ºýÂöüÍöûIïù½àõýÂíý3Ýý=¼ýQúüÍöûIïù½àõýÂíý3Ýý=¼ýQêì=,%%% +d +/sl 62263 string uc +äÕí´êÕ7*¼ïÎÐÇUÙXÛDÖüµ*Þ±ùôë¹²ÔÃÌÏÔÃÃI¿>UUÊ7?é-*P=TKÕ»WI½O´ÔËYÝÔÔì¹òËÜPÃɽáé +ÉÝXºAìÙÉ-*ùµïéãÕµ/=ìϵøØ>Xü7XÅXFµëé-*àWFø±Oê*PÚNíò³TFûÙ7YJ-BïíóÄõíÂíÝ3ݽ=¼ý +PúüËöûEïùµàõíÂíÝ3ݽ=¼ýKÆÉ3+¾,¾M8¶ù0;,ÅSîî1<¾ÁùVRPÆÈ>ðàÄáüÖÀNâã1çüL´ÕÇß1ЯÁñE +¶¹,UZÎFËB91êÊ39Wß5´L*ÎÐ9*ÚK¾+¿-ýI2R;¶+-1ÚîN.T38BÞ+,Ú¿¿E/¶ñ/µ´:;SÚçÅ52¶Þ*1>îÚ +QÐKÂU0¶µ-ÕAîÎ:XúàÓKFçÀE7·TB3ÚäÛ2.,ÚùÁ=ضï4µó°4î20Ì÷J0À·Ø1õ°±GPº:?Å8ò0HÌëÞ3AF +CÀME¶N+Å6ï2½8×J´Ð¶ï-M*QÏ:+ÍâÀ2õ/ïÐL8÷ßCKÚ·Õ±Á24¶ÃÔÕùïø³XôáÛIF<ÁMÓº÷21Ïñö¿8CÞ +÷HÚ·ÃÓB¯+J0ýRJ¶D<µðîÚTìïß;@N¯±AÁõX4°æÎFºðH;.SÆLÌPÊÀQì=àï³ÚËÀGB¯ÂàÓöNñDõø;==HÍÓ5B¶Ò7׶ +P,/×G=Üÿɷ¸Õ/Å7î8@8ùODÐÆÍ3í·ßã¹NÎOQ÷ïZ48Øãã@ÚFÂ-4¶:*Õú:M?ÛúÁ¹ØÆÞEìÈèKÙâ0IS5 +JÜPºöNÅåðT5ìCÞûðÛ±¿êNï::XQJ,H·Í2÷²ÞWIFÔÈÉó¶ø*µù;ÙéÚHÂ=0¶íJõ¶ïÜç°çÁ=BÖ5=@*FÈà +D¾±Ý26¶±DQâð¾,8¶ß+6>0F¿¿-;¶Ñ*ÅVî.±ð:ñS¶L*µ>;/ÍNÁ2õ²ôì08Æß?IF4ùݶ:/õ´ñæ-XÓJ +4Õ·B,ó71Iïê*XÎÞ×MÚÉ̹±¶ëCQOïÌ°ÔßÂWó+÷8åHÔZççæPæöTÊöK>ïY¯à9ï6GæÂYíUܽ=¼ýPúüËÖ +óÊ2*FS¾E2242*Æá;XâÞ6ßRÞÇ-FÀ¾>1îÞF̶LØ·ÖPRFºÁM7*H4*°àÁÒ¼ÎGËö*ùìR¯4ŶäZQT@×Dâ+ +3Ù°àU:S3ÖÀJFúÂ=PÊ89D×3í29*¶<+AÏÒúB¶î¾+9>ß/CFèÀ±YºÒ0ÅöîÄB8×Þ:JÛÞÃ;ÚKÁ5å¶å11Ãî +*8XKâ×°Ú¯¿±Á¶Â4µÆñð³Ì²Þ3ËÚáÌMPV02.:HºÊ.*9O+1:î¾+ÌâÞ¿1ò·=HC¿E,¹Ô±ÅØîÐ3YOà+CÚ: +ßMƹõ3õVîÆ=8ìáï2ÚÏÅùPÊ9?XÄâëÍÚ³à5Aº¯À¯¿M*/+A±îR6ØÔ39HéÛ:F²Ø-05+¶á+1.üPÓì´¯<± +¹µ.åîî@GìEâïZÚÛŲR;Ù0Fïß,¾À¾=;¶R*ÅBîÂ+øÓ3ÁÁ4ÁÌÃú×8>üO¾X1ËöÌ>ð¯àEѶSõã,AèïÀ3Ì +Þà+ÐÚ5¿EU¶Ê6Õôï@?°ùß¹ñ·ÂÀQÜöN7=ZàÑËêíPºýRQZîR5ð³àÑ@¶Ó-õ.ïZDÌìáÓ0F2¾.ÊöF?°±¾M +¿¶çµWöö-XÄKÅDïÏòÛ,õ5>¶G¹åËöZÒÌÕßËGÚ³¿ÑZºò4õèôêCX4î3FF²àNRϲ:ÁÆN¾GÅÊÖ6*ºF´àº +ÞÖµËö.59ûæRÞ.ÞÃHGξER¶Z/åX÷R7ìBÞ?.H°¿Ø¾M=¶¾5ÕèïÌ98÷âÏ2ÚàÀM-¶J*1Rîê:ìÏàG¹FËÄ- +ÁºA/1¼Ö*JÖùR5R8¶ÎÃQ.:HÎ<ΰÎ6îÂGØÒ3õV,õ<×;@î6¹µ±*G¶À½É3¿Åò+ÎSÚñãC¯TÃÚÏMú1PúXõ +ïûEïùµàõíÂíÝ3ݽ=¼ýPúüËöûEïùµàõíÂí½2ܽ=¼ýPúüËöûEïùµàõíÂíÝ3ݽ=¼ýPúüËöûÝÞùµàõí +íÝ3ݽ=Pû/º×æ¹Ëôêϱ=*Þ¹ôêçòÌæã½Ú3¿ä*Ü:¾ëBï2*æê*P²íéáÕ½Ý*Jöé½íýO.ðXúìÄÊRôÆÙ×ÛG +¼×éPòìßÑYíýûõå/÷îé.*õËùµìÝÁ±õÜùÉ2RÄê×±YS5Ý8*:èYÍÃP*ÈÉØÙ.XôÖ3»øîß½IµíݼTí¼û³, +¾ÁQ8¹óê×±ù0æù¼3+ð·Gû7ºöðà¯í.*ò³éÕWáì»è34IÎÃëêW,A¹êÂÌù³ëÕ±ÉåÝIëÝIýD+ÞíÍI¼D²é× +YÑéãÕõº=¸Ë¼úRÁ7¹ôë<º÷GÆý/-¶ëÓOÕXÛDô´âÅ9Ë1²¯E¹ÉÞã8EϹÄëø±çíõõAû÷í=*ºýûîµ±ìúD´ +ÐÊØ÷̵LåÄ2ÏÔÒWÍPW¼=Ý4=øîÙMµ@ÛD³ÕÂ2;´1²¯E¹ÉÞã8EÏ1¼7D°åéëé1ùµ4*XIûîÉ;Á¼GÖ20ÜñË +æIWزB³çÉG5íáõ,*üùóâÇOÑHºÇVø²S¹-øÍÝ6³ñéõO=øØÔA9Üúúºù,ÝüM***0*X»Þ¿3Ú.¿ÂGÞïÙII +FðßÉWÑظµõº=¸SÐNëO»÷ZãÀ¹ö±ï-Â-*<ÚßMPîà¿á¼ºö°è¹M><×Ù-»ÉSá8ûJ,A¹êÂHÓòá¼9-1IÜÒQ +ý2**¾E.¶R,ÙB*YIHúíÄûH@ËÐàÓÛñËæµëØã³²á¼75ëÝíë9ûýý½ÀQ>ÂýIÇ*1üEôà¼7ÅôDL;ÇÛGåÙÙ6 +¶â-3Úê¯=øËÎ1éOºEùôXC@/FȾ¹4¶á/AR:8îê<ÌF*ûôÛø¶.îéùP²/5@ÒµÊÆNáäû0*.ïýÕ::0îÐ+0*8²Þ×?BìׯÉHP +.øL><ÙÙ-9ÉßæX/ëûÉG´L8åCZÙ¾Sõ¼>ÛK**ÞÓ-ÚÖ¾Z¾M3¶S+ÕDîî9ìC*õP7ÖWÞÌáó»=¸Æ:K¼áÚAëÜ +>D´ëµ*J2ý½/+¯*Q7Z*î.3ìV¿éÄ7¹òܲù°0íL><ÙÙ±F¹¹²å¾ôúã8E;J5èνQéÜËùôM***XGÞÃ+F:¾ +U0¶F,1ÑîÐ*:õëØQý78+æøÜ37QÌFÉ/=ØÛÍWá¼.*¾ö/,2àýé*XEÞÃ7F:À-+ZÕW½ÜEÊ2ÈÃRN°µ1ö@5Ì +G.Ø.XôÖ3ßÕÎ0óUDôêرI,**îê+.**á*³*µ<î6/ì²Þ;-FZ¿-,¶;*R¹´êÐû0žY»í¼óèÓÙDùW¹A»P +ûø?**à+*ðøý¸*¹ûý³à=+µ6î,/Ìê*:¼°0ÜDóåºéøëºUÂL<¹U0¹á¯=*ÕÅ@ÓÉG´L»/øZÚ½KÝÀìGT¾Ä0 +6õZæÕøÜ3ûìÛ¹M»@6µYïÚøG*¾ý+*A8Z4îÞ*ìRÞÛ7Ò/Ò+æå»ï4üFTÂàØôôøã8EϹLûBíÞίå¼ó0áϱ +*ùLLÍ7ÙòÞº9ÑôÒõËæ9I¼6Dzâ»ûܲàL*Jú**XåÞ·3¶éÓK5ì5ײN;4·ëW̺¹ý°ÅËæ7ÏìFÖZæÕ±ÑêÖY +Y=*ÀM>³áÀEÕ´ÒõËæQÉ@WS°åÁ-ÑäÑSI0*öý**XåÞ³3öéÕQIXê×°ONØòç¹ØÙ.XôÖ3G>âÐ;=ôÚ¸ôÓ,¾ +ñäûø°ãÐ=MèùPòݵíÄÔ³òäÄÇ°æÔµ,*HÙ´éËGÉô47ëÜûù+WVåììÝê¯=øµSSÅÔÊ×òéLÙôÃ@Ïý0XEòæÓ +Óà,·ÙÜ3û·ïàóT¸FD²çê÷8âýÀ+ºôìØYÕäºÇX°º,V½J5é×ÙùA¹êÂÌûàKO=Õ´ÛØÒ´»Í*î×»ùôGDéLåRéàà¹Ü3 +/¹óï¸øÕÙHSÐJ×3*æ>SÐòïôµ²,R½L5É×Ù.XôØ3;ùØàêR×Y*ZDÏ4¸ëéø÷ÔµLßH»âìØ.*ÐIÐÃûêëïÉG +µLÜÜúóà¹ïÜÛW*RÚ¹5ÝX¼»íEÏZݼYOº0R5ØÙÙA¹ìÂÈû¸òà·éÜY*:ìàÏõü<0+2óçµ²Â<ÛøóëëçÉÛµà +õÝ+3óÞùµàõíÂíÝ3ݽ=¼ýPúüËöûEïùµàõíÂíÝ3Ýý:F³L¾÷**Þäõ9*SûÞ+74ݼÁ3ÓRÏNÃ,û÷û-J°KZ +G·ìÊ÷À3?PÊE¿0/øû-J´LÞHµÜ²çÐYÝßÂBÐNÈBµ¼ý*Æ93>PÈ= 20030921) +%%CreationDate: Sun Oct 5 17:50:37 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 10.416 3.125 ] [ 3.125 10.416 ] [ 3.125 3.125 ] [ 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 3.125 3.125 ] ] d +/pageinit { +35.52 24 translate +% 185*280mm (portrait) +0 793.92 translate 0.96 -0.96 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 616]ST +B P1 +NB +W BC +/mask 6018 string uc +î½*¾î1:M*¾IÑ/õ0*ÞQS1îßWG*îSè+6¶ö0SØßç1âøFQI*üNøFQI*HNøFQI¾8ú1º/¸*-@K1>Þ½¾UÎ- +*Z7¸ÖÁF2*ýܽGºýÕùüE÷ûµðùíâõÝÇí½=ÝýP¼ýËúüE÷+/5%%% +d +/sl 47430 string uc +î½¼**û½=*NBWùWÀõâ·±*Ëð·¸I÷ã¾åÄûÄ*Þð4Ä»üàOAËV<ö¾ÝüP*α±õíÍIý7õ½HUJ×á´ûÎ**ýÀíY +ýü?ÄìCçÃ0æûâA-HEô¯*BÙòùÅÌäR²TÓUãÐOøãÂ8ÌGÝIõÕÕɳ=8ʼñ·<¹ê6N3çôݽ@è6*ÞáèÔÊ×îØLSÐÂ:ÛPK.U¸+θ +ñµÖNEÞìû¯M㸽ûŽßõíýô/æÇæVY**AHÜûúô×1Iï*ãÈ?ñüÞÉP+*̳´âÄï°/Ⱦ·õõõºY*4ëI5³+¾ÝËëFúûößUÅåÉ-ÅÛô+3JÒðù±/D/*öÍÁûÐÛTÒã¿@ìÖ×CY +ƾñû9ÙGÎJAéõÅ*üý½ùIÎýû½JJÕØÍý±@ÁNÊãNñ--,12îZ*æýýýXF¾,Dâíýô,å,Jæ=FZüý5Èö.B.RÎ +µV:Õ-FºÁ5Í:Åñ?½¶ìýKTúí>RâæUÝýØMÜÀ+ÕøÜø·ÊÖ7,TåýAõ½ú¿/-èõ½×@ýùV4¶ß/1Ó:±*Þìûçøº +Dŵ÷éçÓÉíIýûùÜܺôåÉ=½W²à>ZÛùõÝA¾X¾A»**ܵµòîïíYíG:**Ê>2**ìξÁí½üÕú÷ÕüV*îGO= +Aü»»úõ»»õß·9Åú°åL+èÝIäN*ÞQ´ô·ÎH·çü;Ü¿AH;+íøIø³Ëë=ðQ¯üýÍëý»KN2±ýýÁJÜ3Á×C*>2Þá +ýYÓÊæýäá¯ÊôýÉ0ùðÞ·¿5IÎýûÑYêçÔ¾*IPæ<ø÷1ß5YÊAEöM+åVÞA/ùýÝ;ÊñýMUÝLÈêõL/X¾à/´Ú· +ÄÀÁE-ïB¯HýÍYíýù·;ÊKßý9æç;̶ÊûýMÚ÷ý³Â6ñçøóýáËÕV¾ùý9Òð½üÅè,̶ßC?â6*B·½Û6³H÷@+* +JºÉ**îæÙºåï×·¹Yíü»µÝܹì×ÅõÊö4/PüXÐJÂ*Ú9Í-*ìÎ*õìÝÝÓøó·H**ðæ¹ö5Z6°NÕ?ØV>ZùùÝVC +3*¶ÕQïËÁ3·êÁ¾ï;3¶ôýñ¿IúæâÈKH<ºïJ*RCÀ8RÜç¿+¿K*íýI;ÄAýíåÂUÀëûýýö²9ÚÞ¿9JJ¿<3ù¶: +¾4Ôê½DYò±DF¶N+åV*LHÎ+B²üïL/X¾à/PÆüýYFM»àÓ¿¶ÒáééûýÚÎÌÖûý̾îÛÀ+Õý½<»ýóÑTÇÇýQô÷ +½è;Å/ÞûJMøIÜÎÏ*æ÷Â5ÌSKä*ZËùÅÃÖ´?Üã¼±IÝüJ7æ¼ñWÜ;¶è×ÕÉIÍÜÍA½»´èÞEHºÂ,1Z@=4*Ì+¸ +>¼úÊ1*Þü/ý»ùÃåKëÞÁýáÖR¾øõAC5*¶ÌÊÓHPú:áÃÕNÞ9ýåûýÛZ4:Õ8626,öÞýãT.ûÝãOüÏíÍ;¯ü9Ü2×ý½éÏ9Ú¿*óùÝêÈ?ãÔ/Úо±@B- +ÎÀýÁ8×ðé=K*¼9¾àÃKÚ·ÄEÖ¶K5HóNÍæåÑÐúIû0²WÇÞ36öÚý9¼ûíÔÏÅTû½·TýY8²ò9IÈûÍðìýü¾ÓÎ* +Çûý+AFÒÀÊ+*Mõ½ÈZâÏÓø@ÞHN0ÜÝø@A1ú²éåÙ¹ýìÛµ½ìø²îð.ÜK¿=ιÅ,ðºù;¿USÞ@å͵?»øÇ1*Jé +Û7H¹µò+.êG¸íàí,5ÖRÞÊõYȯ<*îÉß-ÌêÓìÝ=/ÛÂÅPÞÀýÍDSBJÆÅZV¹2ÞCÀNùúüûñ¹-±Á-/,Á*Ü?¸ +üíË0ÐÄϱ½íIüéÛBïOôý1ºñ-Å:JÏ0åY½ù¸³+Õ>îÈ/ä*¶áÓá÷íAýìÃ:Ú*Ã=Á¶ð6ÕZ¯,¾CèõI½ô²ÅXZ +JOßÃ/ö×ëóÉäâ5MÍT¸üùÏKÓT´Î²áÑý42ÛξMX¶P/C-*øìÝÐÞÅÅÜÃؽ1ýêÙº×îÔ¯IÝÌ»¹å¼û·êÔÁAù +A=4FJÜM>Þ=ݽµãÚ·Æ1*Þý;.ܹôîç+ööíÏ59ÜÎÔ½Ã,´·½à3ñ**IæÛúôÜEÁN14ÜU+Ì6àDZ +ÚÀÃ3*ɶR:LÏ.ABîÀ<è,ê×MÁZ/æR½Ñ.;ÐÃî+EÖ±Ä6KUM¶â/åÒ:í*æÄ÷ùÉ5Öï×µÃÎIø@É/FµëQBì¶- +ÍÃYX>*ê1çíÎ*ÁÒXÛ?ïàEÜ**Aä>¶íÛDZ9ÍßUQܸÑZ,é.Zèüç=LÊ,*öKÖòMÁVÕZ8IöHZJãô1ÛWJÄÕÜ +O**¶ôWÔ3öîÜÍêË39ÜÇÜÒR:ì8ÜÝÈ,ìöú·UÜ»Uº+*8Y>ÏÝÁ9Í8HÑÍì²ÒMé2HJ+-ÜÝêÀ·*¾+Fö8ßN¸î +»â*IÚÜIå¶+=@WGý@I.F¶ïãÏQÕìÕ½I½»·ðÁìãÊ,T϶ïÔ°å¼ÀÅìÎÙNí¾L@üC±WÅ1;Wõ6HJ+NÙIûÅ+I +?*ZßûI1UQîßFÜãó*1Hú¶ñä͹òéÝÉíÜÛ̸CѾLòëÙ?éÔíöÏ1F149D°æÚ¿¼Bó41î34JèûÍQ.öO+*½ë +Ýýݲ÷.ÜÎÓéí<ÒâF-*ï±Xä>Þ/±å´OØò¿Q*Þ/ü±õ6ATÎRKXåêû=ö./ÒLîûÏÑòõI+¾-FÚ½½Çßâó¹°Uå +ÝÃèçèò2*ÂÔ¸SÑ*ÔXëȱåÕ,çýë±Q8F·ôô¿½ÕP¾CðìÝIñÆúü<*¶¿RÓõíY°A´*í?XGü@èµìÙM*:ñøOË +,2D²UÈ?Q/ÉÜY¯úûù±ÅêBºûÃ*¾ôL¼G2ÃGáñ³WÛ»û@öýÃ*¶÷°UÄ7å³É¶íààâù½P*î˳ä<Ä7ÛºùôõýA +6*9I´OÖîâÊÞóßMåîRм÷4ܸÝ:*öÔD?@-ÔêضàÌÜ»¼8ñ=VÖ?ݼÙO0*Ì7E³åÈ35ô5öøÀ*Þ½û,@êB±ç +Ô+´×¯éDÂJúøGÎ-êý²*ºýÎùüÁüû1ûù9øõIòí½æÝýνýKýüÁüû1ûù9øËÁ8*2*Æ:<À3°ÊÀµ2¸O²3Hë½ +êÚÒJõ2ç8JG*21¶â*ARî¾+0+è0ì/ß9/Û-NUO¹ã*ARîÄ+X¯Þ//úÓÎÉÀ+ÌËì?-ºÆÎ-ÒX*J°ÎE*¶Á.õK +õâ<¼Ø-¾ý3¿8ú½**Ç¿ìÏ7N+¸A>ïÑCJXι²+óõ=õF/8ÁìÁ=¾VKQÖ-2*¶ï*Ñ*1Ûôî0*¾U=¶°*Í=òüU +/ºýÎùüÁüû1ûù9øõIòí½æÝýÎý¹Î±íéáÕýW**ßYý»IÀ̹1ͼ»ýõÝIAâÁý0*îÜ·ýäû¹¶ôì¹;òºøîß½I +µíݼTí¼ûí**8H·êÓ±åÌëEJíÂÎñÚYé¼GÙºüûçüû÷½-*öùñÅÍ0ë¸ôéò¸÷óW2¸üCôéÎOÁ1*¹í1*ïåýº +/ÞG*öüùßéäêC´ê²×óñW2¸ÌöóæÇIY1*¹í-*ëÝýº/ÞO*¶øßAQ8ûøîà:º5ÀÌèÃÛ·¯äçEýñ6¾ûùÃÕ*Ú- +*ÍýüúîÇ5µÄËòðëÛ/KQáÐÔF¶ððÛIò.Iüë?*õ*ÞíÑAIúïÞÈSÑÖ·³µKæSÑÏ5õ³ê¹Ëó¿ÚH¿¾M<¶°Ø+*Å +YòÞ5Ìà**¶É2å4î<9ìÑLäí¼X:3Ã+ÇAY¸/ÞG*¶øíÛõXÚµïæàòë7Àì:TÀÔ2P¶I/S÷¿è.»ÖXàÙQ½XÉéC +0Ò/FXÐÚÎôàô8Zîï=Nï±·ÁXèÂJîßYX+B*BäT-îïÙ±ÝËÖìßοä7ÀX³VÀÏÉL3Ðè-/÷·ZÚÀ±Ý7øöÕȾE +>½ï*QºîJê;ÞRÞÏ3ÜSÀ¹Î¹1.AÂõÂõ1+XÂíBÞÃ/FN¿=6·°8Þ¹õP7ÖWÞÌݳ¿Îå +*µ1KA>î6ò8¸.F5Î=½QIøÍMM¹RWQLõò0Ð/Û¸ÁEϸá2Õ*ô¶éìQXHÔºô3×.*ëÜ7³/Þ*J·ì6*ðÜWùòÝ+ +¸îê,Kõ*ÝìQì·4N/¸õÂîN**Ö*GïUø¯PIáö5Ü4*¶³-ñ+12õÀäXOßÃ@ÚÏ×±2¶á.µÂïàøXCÞ¿/FO¿ÉX¹ +30ÅPîê*ð:*-:¹´Ï?*/*AHQ±éÌ7Ù²Ý+¸*ê0KA2´6Ô8Z4îÎé8ì**¶³,åJõä,,2XXà2¾äøVöWÝÏÍíQH +9¸½N3èKìÕÖ¿Aâó6î±/ÞÏÔ@BîéÏýÜ?ú·Ô70ûÈ>î,òÌ-¾È<ÛÖXÞÎͱRèö¸ó/çæßÈIAÑÝÂ8*Í?*ÅLõ* +,²²Þ¸5É@ÓÅJÕFü¾ð<*GÓÒ¸°Ý¿KÝãÍEÝ×¾Ië·ë°úÆù5¶1¸Aâà±á@»öíâÓ?óÑÎÝÆÚ.JQ5ËôVÖZæËïY +¹ýH¸ûØå4¾¹*îÖ±áPûB¯åÒ+ϼ,Ûº¶JMCHGC¯åÛ;èUý0ÈGEæ4¾Á*îÚ·Ýܺ÷òáÇD;òúÙÒÒMUä6E¾Ü9* +ÈGÙæ4¾ÁÍñÝ¿é0G¸³ÓÖJìH.GŹǰèÑIÁHËXéäÛó+*ÀÔ*F.*ýËG·ìØG±°RôðìÀÎñêA8TD·³éÓO³éí+ +*º´íÜ»ÅäØæCèØÖöH.GâÜÔBETÜ/*ZDÏ4¸ëéø÷P.GÜÜúóà¹ïÜû>*JïìWú¸÷óË2¸Èû¸òà·éü@*¾¶5ÅH +çåó8æûNîöüKõû1ûù9øõIòí½æÝ?æ3ðÛÍ+*:ýÊKÅL=½ü2*.÷æÍCòêZïå1I+*¹Üúîð½CòÎêïåMÓ¸+*ô +º·×Ò9Ò*:X*¶Ø4AJîô/Ðù¾JÁ6ì:³QFúÀ¹7ÆÑ<ìMK@ÖÖ/õڷ»¾LÐÎÁKÇIÍ/÷/*ÖñãÁ°¼Ï:î½+ÞçÐF +*¿50Æ·¶,7ìÝ¿:äH±QFòŹ7ƱPìMK@ÖÆÚ,ÞýáWØÄæO4¸æ;÷ñãÌI+*Kìï7æÛ¶¾Öø5¯YE·ß*QJîNñ9Q +K/ÞFÞ/665>2ÚáÀ=è¶P1µÂîN9ì¯áÓÀÚ¶Â=2¼A=Õ,ïÚRìZé׺ÚÐÂ5ê·ã:åPð2Y8Ðå;YFBÁÑWØ0;P4¸ +*îGæ+¾QKQ2±IÕ3ÃX0¶Ï+ÏÍ1JÎÂîî0X³ÞNPRß3D62FâÀ5¼B?¶O1A6îV9ìZá×ÇÚ*,öÝÒð°PXîÞÃáÚ? +4Jú.ò8äá7OFæ¾M¶·<Åöð²X8?Þ?úF÷¿¶¾O¿8 +d +465 102[1 0 0 1 0 0]sl 8 mask 0 0 di +/mask 6018 string uc +*Þ8**ü»ý¸öüYõûµðùíâõÝÇí½=ÝýP¼ýËúüE÷ûµðùíâYû¸ +d +/sl 47430 string uc +äÕí´ê9+¾ÎøýÄ,:ÛïåÉG½,*²FGä1T*2A*Â-Æýýá*.¹ôK*ß*ADî¾*è/Ä4ÌìÞ²ßÇEFOÁÁ=¶ã1³0A´îL +:ìOßÃ@F2ÁÁȶá2õVîL+8òÞWÏÚSÁéȶÑ/Õ2ï22XBJ4½¶Ð-õÙò4¯ÐľU¯Ñê×µñéÕÓFJQÎ3½/¸LFØ=Î +-ý5*à+ä,8*íCÚÒõÛJËÑE¶î11Dîìç8@ßÓ>ÂKFÂÂU¿¶¿9õJõ²Õ8Bâ+7ÚóÄ=MÇ,IÌUàÃ@ÚDÉE<2Ï·Z7 +Åýîà/XQâ/ÞêAò.ÕîÕÀXºñ¸-¸1áÀÖýýý<É0*XëíÓ7Ú÷-Jú./ìÎK4Î=êý1,ÛÀÂÑU¸Á7µ±îPTX2à;5 +¼Ú×ÂѼ·Í6ÅÙî0;XÆÞËRʳFüÁ=A¶D;µ*ñäTÔ3¸<4öUKAØMã*¸A6îPT8òä;;FÊÀ5½29¸ßÔC¿ì?*Û-¿ +US¶¿7?0ÅJïVß80á6êFV,µE¶¶9-QSî,;8ßò×íFÑÆ°¾±/¶ÜOù/G±Òù÷¸µJQRE¸,¸/*µ¶±H*Ö9Í0-Úö +ÇȾ=U¶´HµD:@ZÂî.7Ä9Ì°ß3ðFÞÂ5Ѷá*µÆóN.Ì.ðG:FS¿ÉC·O6ÅúðÂRXìëÇÈÚϹW¶<:O°ÃHµôñÆ +LÌÂâÇ,>ÛFIÇÛÀÌù¿»EõíÙ³éäÚ·ñ¼ÛD³IAJQÖýY*21B5¶î+ï*¯*åJîæ4ÌÎÞÛ0ÚÖÎ=²B¼¶È/Á.1.ï2 +<8CÞ2ßÓ2F÷Î5ζ,.QTî°PX3á;ÝF3À=C¶Ç@µ>ïX58ÇÞ¿æFÀÀ=Ù·O@Õ<ð²ÉÌBîLKåéÆPËØóéׯ-êK¸ +NÙôD1JAL×.Z1ý¶Î21FEÑàâçÔéöÎ7ßßS¿NVìÙ*øVKQø··0*ÞèÃÎÅR@+VLJìòQXÄÇÑ7ùäñ<Ù˺øZ¿Î +±êS=Ûö2*.SèÉBÅ>*@Aáò/ÒOGDEÎɳFQÔÎMãS±¼.*À¸÷WÎáEÑÁVKõßPÞù1ÛõIòí½æÝýνýKýüÁüû1 +ûù9øõIòí½æÝýÎYGòHY*N.¸îýõ/¾ºJõ=UÞù1ÛõIòí½æÝýνýKý¼JQÞŽ0**ü±0GÀÛÝýM*:ú¯½äαà +õùYú.*ʽýÃÉýãÎÁ³÷ùÍÓé/*Býüéмß,*J*ÞBJ<È2D¶Ï-µÇîÆ0<<Âá´¶Õ2Qñ:Á¶F1ÄNçϵKÅÅðõYV +¾*Þ-*²¾*üû9ãú4ÇìÛV@ιX-õÌ:ý*F¼Á6àï±¾8ÀâíýÙ*±·Á*ÐIâ±ÎUB¸îâóóQâüP.XòJ7Þ7KÓ5ÚB¿ +MG¶¯0ÅZïX.ìNßǵF3Å5AÆQFì7ã¿5Úξü1G2BJµBÕRQö1º+¸QÚòàï8×èÇ+FZ¿EG¶¯.QWîÈ0XNÞÏ<Ú +4¿á7¶-,Açî¼FX¹á/7²½æ׸ùû½UH9ÍMüõÕ¹,¸A6óRÇ8IèO5F¯ÁMD¶7¸ÕÎî°.ìOßëYâB8µXî5¾ÌN¹I +Àü.òÂü²ÅJõØà<+Û-ü¹ß,ÅPî@ÍÍîßÏ7ÚÍÀáÝƯFÌÚÞA*Gçã¼.ûºìÂÌÆíó,ò4ÂXÁì¿*F;Àñ>¼¶æS¾ì +ïTå7¸CVàKCò¼æCSóû½UÊE.:æOã-¸µ8î´18à»<ιàè1M:»æÛ5Å>²ÎûKå8¿õ9îºìÂ8÷åûæ/æñ3**Ä ++Ì6J³Þ71F2À¹È¸à,AXî.7X:ß/IFæ¾5ݶ߷õÐî>ïí:N@ﶸ>1GÐõKåû>ÍXûúóæÕ±ôêÓ=íËÙÍ=ÎÙL- +FÄÔ@ÕÕ?ÁÌÙÇËEõíâÍAåãϱå8G¸E1Jµ¼üàæµÄ·1Oï¶.H=æǼYÌ+*ÚØåÎI@àÛÏï±*GáÌDçÃÅ*JÆÐüÆ +¼ÓGJÅÈÑäú2*.óðÍCòHð/ºýÎùüÁüû1ûù9øõIòí½æµä3º×RºÌôêϱ=*Þ¹ôêï.ÍæãÝ;ò¾äÎÖ:¾ë¾ì2* +æú5K1âÛÆÖ7¿Ìê;õÆ@êùøöóí/*2üøíý7æ+ȯZ.WêéÉ¿Õ:*¸G@Øæ.J÷Â@Ú¹öòéù½ýû·KHú4*Þ»ñüËù +õîêÛýDÎ-ÆMÞ¾ãÀÕGÒé/-FÖAZCH+4IÎ/ÍHºöíåëùÕÍèÙY½/*îîÇáXG¹ôêÜ+øQ-¸PÑ9Ié8Þé8èóÀ+X +êÉ·ñáÅä-ØîÚYé¼GÙºüûçüû÷Õ**üüúïÑKÙÌûDÛX¼ûÉ+GíÄ2ÏÕÕ±ÙÄËâñµ½=*îóËÑüʸôéZ/;LAÒãG +á´û×òõùùÉüúõ**Üý¼öÙCµ¼7ESPëúÉ+G9Ã3ÑàÖSµ8ËÚåݽ?*îð¹±äÚDôàÊ.ÑR1²ß-Ý07×ñóôóYá>Ú +¼øßAM0û¸Z;6úÉ+GQÃ4ÓÜÖGEìVÒѽôR*J*JÀï¸â.8ýúîÈ7¹ÄÛµÂà+WöØÔA9Üúúºù,Ýü±*Bض1,ÅCî +æ1F¼ùñÁMHº×³TUõôA-¸Ì5AXZSQ06×:Aý²ç<6ÛMOîñã1½6¶°èZM>8æÀQ?ûÖíáÞßÅAóçÝ,**î0+¶Þ*A@Z0ÞÛ±ÝËöìßÏ¿ÄRFÎ/Ù3 +øZÛï4Ò»ºöÅ***FÈ**Z@**Â-Fè¾E>¶,*<¼ùJ/Ò¹Bò˵ý6æ÷¶ïàÃúV¶ìßN¹øöµ***FÁ¾B**Z0îÆ+XÎ +Þ;-F:*ÝHÛôß·/½8CLï¾@úáå¿ï4Ì7Ûø±ì**ÌKJ6**B-¶Ò+Å6îÀ*Ú¸ò×/á7ú÷áQ-¸¼íÜ»1G1¼EBßPÜ +û7***å::0:1-FÙ,íüF·ê¿í0ô68ÄRFÎ/R¶XßX=½Hû?***ACZHîÀ+XòÞÏ0Ê+ÎñâY9T¾ÀÍPæëßÁ1÷7º +ÏèÝÍÝ+**î6,à*XDÞ*Þ7<Ú×,¹8úõè±Y¾YÂà+WF¶/ðûEöU***8OJ*Þ¿1ÚZ¾5=¶Ò+±*ðÙõ-Òö²ÀìOÈ> +3̱޴+5èÛYÕé?µGäÕÑKñÛ¹Ñ0ê@ZåîL>8æ,¹î4ÞùC***å::0îÈ+8óÞ7<.+FJ/2õç¾Ì÷òçÑ×¾AñU¸ +ôìÅ***ÚJ¾·¾ê6î¾*ÊôêÐûÄ»+±;41²+ÎÃUæÝѳíä»ù?***A2Z8:7-F:*â»Ùôç½-?*ØQ-¸Ü³éÔÛH1¼ +éAüWù?***+*A8Z4îÞ*ìRÞÛ7Ò/Ê+¶êÕMý»EöñX;41²+¼?>Dê׳å¼0*M5A8*°Í*Â.Úæ¾E0R4R-Jر +áXO.ÝH7æ÷Ü»ùÈ<ËÖXÞ,Dôã*¾Ä0VA¯çÐL>8æ,Ü,»Bìݹ·/77BìßÎY»DÎñÇ=QÄÒØðÝ¿ÍáéZ¸2JÄ÷ð× +¯ÍP6ÖðèÆL>8æÀ·LûBí¶/Æû¶>ðFÃJ/?GBíáÒG»DÎñÓSÑÜ·²áÁç±éÕµ,*¼Ù´éÇ;Åìº3ÏPÛø+W¶MÒE +1@6øóêâÐ;=ôÚøHRß·ñ¼»×°ç +Ç;»DÎñïëÙí¹Ç°èÑç±æÚµ,*üE¹ðÚ³¹äÒA×µIü+WöµSSÅÔÊ×òéLÙôOºÌý0XEòæÓÓà<·³½CÎñîûùõYð +ÎÐÇO³éU+*ôW»úæV5Ç6Í*î9æ,Þ»*òø³ë¹SL°¶AõA-¸ÀÛû2B?úW9?TÐê.*Ò4?Tøº=FD+êIÏ/ÖÖÎ2T +íà4W´íÜ»Å<رW55ý±JQÖá9-Ù@+*úŲ¿è4±=ʵ=,Þ²L?òØÕóñÍ0òèæAO*µ¼H.¾ÕA½ÛöXÚµµ,*4õìà +ÕÉY½Ò4îýU+Gâ8¼»úúõÁ·ÄIüøîíã,XÅXFµë¹+*àWFø±Í-Òü4æóEÔÆýÒH´æÃå+ôíDòäñÞýY7¸ó·¾èÑ +0Û-ÍùÒKõàñ*ùA7¸QH1üýæιñ·¾èÁ0ÛMÍùÎKõèñ*ùõ6¸åH1üI*G¾÷**ÞäÅ6*12ÕÀXÃ4OT>æÍYC*VQ +ÌE±Ü*GÃ3ñPÎK¿üã*¾HµÜ²ç°/ÛÃ>Ç<äËûýýñ.ÁÇÐâÆ?òدáÅ:MÌúÉ+*SÐ6÷ïá4¸ÉB¯LÃÝC*âLÄ+*? +ò9¹*¾ÓÝ7*%%% +d +465 102[1 0 0 1 0 0]sl 8 mask 0 102 di + +QP +%%Trailer +%%Pages: 1 +%%DocumentFonts: +%%EOF diff --git a/doc/krdc/snapshot_connectionspeed.png b/doc/krdc/snapshot_connectionspeed.png new file mode 100644 index 00000000..2f7390fd Binary files /dev/null and b/doc/krdc/snapshot_connectionspeed.png differ diff --git a/doc/krdc/snapshot_nobrowse.eps b/doc/krdc/snapshot_nobrowse.eps new file mode 100644 index 00000000..37a7afb9 --- /dev/null +++ b/doc/krdc/snapshot_nobrowse.eps @@ -0,0 +1,294 @@ +%!PS-Adobe-1.0 +%%BoundingBox: 0 0 551 189 +%%BoundingBox: 0 0 595 841 +%%Creator: KDE 3.1.92 (alpha2, CVS >= 20030921) +%%CreationDate: Sat Sep 27 14:33:09 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 10.416 3.125 ] [ 3.125 10.416 ] [ 3.125 3.125 ] [ 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 3.125 3.125 ] ] d +/pageinit { +35.52 24 translate +% 185*280mm (portrait) +0 793.92 translate 0.96 -0.96 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 631]ST +B P1 +NB +W BC +/mask 6555 string uc +î½*¾î1:?*¾+á.37*¾Í5;*¾â2+¾QÄ2îQL¿Æ6:<ÙÌâH>ÙóâôK·++úÇëÁð,,÷+ö´2+HV1;â+¾ÙK.BZI +ÂÖËK2*+ܽLºýQúüÍöûIïù½àõýÂíý3Ýý=¼ýQúüÍöûI¯µÍ2%%% +d +/sl 52345 string uc +î½¼**û½0*NÖPGÝ.Y-3å*ÜÅ<èûâA-HEôÉ**ÉUBô÷Y*4ÑDZ,9öéÍ+*¸´ôüû÷ñÍäÊDò0°OLéI¹êÔÍ-* +îYöûóííêQº0ÃÏ+¸I÷ã¾åÄ»6*ä@ûüÏñ<=ËTóTײÇôú=à×ñùñüø¸ÊøÒ.VXßÖåÅ6,üÕÈÆÞ-üD+*BîûÇ +YË0*ÚèÕ±õH@»EµLÚ¸æºÌι·*ñìíÝ´é6êÒMWJää8E.Ñí>Ïâ8E-CMØûõÍÃD7*öÖØ?±@ÚÔRÈ>;NÕ÷FK +ùð*ôì9ETÔCÞSÉØ*ü/ݽýÙAÊRL±»+*ÚööôñÙåHúÂ->UÔÊù-YD9*¶Ô×;EÄÉ?RÈ.ÏÜÙ3KKüG:ýýµÑRJ +×ûÙ1ËøæÈøN,ì@ÌÖPý6*¾óºS*ãÌíݽàTL;Xó+*öÜÕÙáAÍGøñòFóÛAL*Óüì½öæº*Þ¹ØÜÙõVÒGMнèê +ÜC¿ØQ÷ܵHµ:¶/Ýñ¾3-TÝEêÑOJýý-¸Ë/Å<¾×à8½Ýýûô=<ȹñ¶;45D*-ÝÝçÐQÐÒQ8*øHXF½Ì¼»ûõãÁ +õÌÛ±ÍüÚ·ñå=P+ʹ½½åùÀåæü×R5*úëêäßíÅ9ܸºÜF¶ê³9Æ*L?ÝIUÎ÷*¾ýFêAëÓLW8²ïÄËË÷â.ÀàZÏ +ÁýÙAÊä7æýýý8¸¾,èÒï»ùMí@+*¸Þ*õÖî0,úTù÷½äÀNÊOÆ>ÏÏÚE1*ÒL¯K-¾ÛùÑó÷òÃ>³3-FYF½GÛ¸÷ +ûñݽíéÉåüº·ðÕÃPÊ,ܹýüùüõí9F>»**ܵµòîïíYíGî°ùõµFXýüÁ¿æ,*½TB¶ñííéÅÍåUõGú¶ßM9Î*óí½±P*îDMõ>·Îܶå2ÕØ6ÍÆJÊÍý +òúIêLEIýõKÏ÷ÝéðýýYÛáýü+µú½ÜF¼ú½¯T³ýÅWM*¾XöÞ/CæËEÇü1íéÃ3æøýÛSøäæÖ¾+õÜÇØZ*¯÷ý7 +FÞà5½üýÙèÞèýAóìFÞÓTJÚ=ýÍ9õä+X²,µüÝCÕPú8Ë·Â+ÚëÀG**Ðë½ÙNÍ1ÎÒO4Z4ÕÈR**¶Ò¶Tô¶îáã +á9úáôÝ-ý»ÚVÄåRJ**¶È,,*¾Ç,+*Þ74¶öûåýÈIýèÍ/*ÜѲðàÝÕÅýÜÕõHFõïÆYôV>JûùáÊ3*úâäH1è +=AëIàEöà=Èñ*å¶ZÈïä+X²L¶ðòâÃ6FÑÂTÀG*¾³ìI@à-Qè=.2ñÝ×ÍY½ú1²ÝSFí +2FóêéãÍQíåÉÝHE³î79HL¿-ìÉ3/*åZL4I¼ä-*ZýÀýÜûL3Ï´îßý¯V>JûùÉÊ4*FQäT9=¼ÆïàM;2øð<ý +üíéÏâõøï;J¾*3D9¶¯*HüýV;»üµXR»Þ/9F;->Sø½ÔÉÌʾõ±Õöý÷÷üýµìúÉX¶¸î882æýàU7Æóý:ÉGì +ïÞ×+Úæ.5ýIÖÆAÆïÒ<4HÔ:*æ¶ýÙ/QÜîÑ8¯¾SFÀ8ÜúJF¾üRïúÝíÐWMý8¼º1ýG¸éнýMö0/Xº9*¶Å,, +ûù;¿USÞCïáµãÛ¹É1**åï0HùöL-ÃÌE¶çí4@4ÖùûU¼Ê3*ÚàÜ»ÁñÌØÔZ¿·ïÇ2.»÷Å;ÁýA÷ÏíýP´ãÈ°é +-X6¾1üYÔLæýWXÀ9ëýÝóï/9F:-äµÕõýæI7¹¯,êõåÆBý1Y>èÞ@ýÅ÷¯Zß7FöýýCÏVûIâPÑ0Ü=ºùQ¯Ä> +4÷?8ÚÇĸ¾5ËÎÀýÁ8×ðéDű-·Ð.¹**6ÛýSÀ;YöSÅBJ?8Z9æ-íY?ÌÉ-¼ØóñëÛ½õìÙIµ»D¶·,íÎÞ3=Ü +M+·ÜûÆÞÓ>ZÉñåÙÈHûâ-*Îóì0ÍÛEø*,ô8۵ﵿ/V>ZäùAãB=*öãî+QôTõíÇÀXà=;2øð6C,Û9¼JÄR¯ +¶üøÙÕE9¶°*Ü?¸üíË0ÐÄ;ï³ýý39F:-üÍÝø×Ë@GºK0Êû»ýöYõà>àÞP¼IÍíZß3FÂ>JýíÇ,àÔÐßKл¼ý +ü÷ÛËGà¿MçR/·Î3Úïèïúõɽµ6ïêJì>ß4,*»õYSîá5¼è;0¶Î¶Òû¹öå4åÉ/µ7ù÷óêÙ¹ôéÍá´Gº×½CÏÞ ++GHL+H÷û¹UäËÁº**ü9KîÛ±1Í8¾Åý8Ûöî/³ûU>ZÉù=ÌB;*ö¿ìãµõÛCN¸ÎîÓ32J¾,+FIKÄ.PâÄÇ:C +S»ÞÇ+J²Ç.SÐ@ºJÔ;46¶Þ-1â*+3Ø>6ßƾ-=ÎÊ2/TòÈÝ3-U2S;¿Á/Aî:Xå*/Ð>ëòî¾Ü<3D.3ÓEÓ¶@ +7Ó:AÀ+Ó¯Ì**îVïV=îß +»I3K=Hº=»SϾWåÜûùUSÞ7ÍIµãÙµÃA*ÞïÃ<ø¹öïçß±øçËĸ²7ß->ZÞùÉWB;*B¾-í;ÊÇ2Ôã-ßÂÙìIJ +´¾9Xíͱھ3ÍöSÅFÞ÷à÷KÞ¿5Y8»¸ó¼ûù÷éÉ=IòXä>Î4Ã5µ?¹ô=Fô0ÛOø0>°ïÍC鸺2ç¸åî+4JØûç½ +6ÖY*J*+è;2ßÀù½8ÔÌÞÁQúÒM8*6ÜXFÕ-FöîáËE½ìÏYýX¼ùô¸èãÊ,4ϵíÔVÙ¼ááHîFRºæÆKñ0øßÒUF +¾QR*Sõ½H;Þõ+*ú³ýüýÐã=öPÍØ.,XAÌÎ<ñ6*ÂÍ·CÑ*AÊGÙ?çÐ1H+*AöÍÛ±ÚR¿L/·G±õÍÞ=>Î4¿õÅÌ +ÐÝÝ+*8îîüûO0=ÕíË=óÇ-FYFTQUÑM*:ØèOË,Ö¸³UÌGÝ4°ýµÍG·îâ×Ù1üÛD+äƹýüÊMðùA*Þ/LÓܽ½ +ÇÛÖ+ºåÎÒQ>î¶ïRÝ·í8+¾ÊéA³4JæÎWSÔF?Fù¼ÃïõíÍG°áîóÅ**Ø5÷ó*;ô6ÍÕ³óôóÉ4Íøñ1îðãÔ6EÌ +éÌïÜÃÃÇõýB*ÞEéÊO6E¸÷ôã³Qû3.öö¯UÀ/Qä.X9H¸0Å<óÉÕîÓùG*¾µÓS¯CZãÔÃ=ôòóö<°¼À/ÙðéµÙ +â*ÞׯÑôVC¯ß¼+Yí+*úÕYÞÀ7±@ëDÞ@EF³R;2ݼÅ3+ùõ9*×TßùµàõíÂíÝ3ݽ=¼ýPúüËöûEïùµàõíÂí +Ý3Ýý:ºýPêÂP²íéáÕýå**ßYý»->WUBϹ1ͼ»ýõÝIAâÁ½?*îÜ·ýäû¹¶ôìõí²GßÂH»öîÝÍÙõ¿ÂVCñU* +¾êVýF*FºðÖUåÌGÙµ¾üRÍêùøöóY;*2üøíEÃ3GõéØYå@ÍNC·ÕïÑ**°ø´üñ*îõå9I7ØóêÕçóðéYîǹ1 +ͼ»ýõÝIAâÁí6*ÚµìÝ·ý8ÌÛùH¯Lý6ù³æÐßY·1üý4*¾Â¯ñåýº/ÞG*öüùßéäêC´ê²×óñ1ôâÑ19üù·ùü +óÑøôéí6*FºðÖUåÌGÙµ¾ÜOÊæ3Ý@W÷±ô±DX>Ü*¾¼±LºüýüûåU*X,¾Ñ1¼×°çÕ3E0à9êÇð¼WùóêðÁ9ãü +úE-*½½üöçÎWå¼ËXAÝÜKÊRõàìÚ¶ñòÍÁ3ßö4¾¹*î÷ïÍ9H·ïäÖçìÜÛ9êǽÄûD²çîõõAû÷Y7*ºýûî³±ô +6E´Øêø÷0¯LGÙØðÞÇ˽7PNöïõX+¶2*ÌÜúîÆ-UX»ÔâÛûÞÌöîéÏ=½ÜüüKPìR-FûîÉ=ż»B¯2ÜKÊRÑÈ´ +êÕ¯îáÂNHë¶3ÒS8B¶F,SRKéC;ÏQݸ/ÞG*öøîÝù´êÕ¯ç>óWë1ôâñàäÊBñòñíÁú¹MLÍýüúîÇ3±ÄËðìã +»¹P.*ð,¿Ù9ÎHM9ÜÉöô¯L·ÏçZ8ÈÅO¿/Àï*R1Â,´NIRÑ-BîýýMO°ÞÁýýÓ³N66Ò÷ùJ02ß3=ä4À*X¶ßK +?FDÀÔ±L4I¼ñ4¾+**î.*Úºõêµ=ܹ¶²î÷Â3ûéçÇ+MPXüº¶½·*îñã1½6¶ðçY+ì5èÆÀ3ÁOR¾7IÑ+H:DS +=ÜÔÀ=1Î=I5̹Öô¯LîÝBåÃË5YF<ÎýýMÚéïÍ.ñJË8Þ¾ýA6ÆõûåýýÕQÀÐ9ý<ÃÀ+»TäöîYÐ@/ÚîÀÁTB+ +¶Z.WåÂòû¹â?*1+Þ×UÕ»¶XßÍQH¯LIDD¯Ü÷µõ8äAQHJ¾¶EÈÞÃèU:ÆÝ,úºõë·Aäɶ²BøÂDðZLâÌG·Ç +?ЯàÙ=ÊÂ3YUßÇ-æÃÌ-á?FùB;úYàÑ:àýýYºÌè<¿ýÞÇEÓðÇR×é,ÒÃãýýÅHÑ>J+ÛûýÝëJQÆî¾5ÌSßVÞ +¿;²G=<óè/Õ*F.*Å캴߶-¹ÜöÂ3ÃOæMê²»¹öÕ*J+¾ýHºN4O+¾û×5âº+Ðûø3S+ë°´PòêÃMÞÌû÷²<2À +M×á-ºS5:B<»ß¿KåÌ»÷Y7DöTHê4*Fß¿3*N8éÀ,*춿·ºK¾¿T.EËLZôM/AèJ×-R3LÇãÕîÆ3¸8ú´îé± +ìBÏHöüýýö6TÉÂZGQìÒÈåÕF?ðÞÎîýÛà3ñ6ä°ÜV*JÂ?2õ;Ì:ß+AFÑÀ+¾-Mæ¸PNæÏñW+¶**ñQ*JF¾EI +ü¸îÙ¿G=º°µÌÏñÞ¶/å¼HGV½/**J6¾*í:2Þ+>ÊZ»÷Y6B¶YâÓNÚü¹+¸¼Û¸4ÄEØ:Þ*WÝìº6ËBÀû¾5âÒá +19îN+Nøñ?о¯°PòéYMñÂG¹¶AÊîìÅTÃ1¶OÐï@JOÙG1Ï/2èôëÏÔËòGß¿3*ØPéö:¿=;Z5*QíÛ4K.Û¹¯ +ÌQèÞ.ÛĵçÓ±*¼áÂÎø;=èMìäYIPß²*·íÁøG=¸/áÒÉíÔæýî20:-ûðA5R+ûýýå½ÞÃ;ÚîÀ±TB*¶Z.Ó,Û +äÂ.F¶Ù?*1*ÎÊB8¾¼ùX»Ê2öG×Ù-à@áËÝæâ-ÐÂæÉIPãíJ¾éð7ûØQÖ¯ÁðçÀÃÀÌñ,Eæûì7æ?îWHE¸Äìë +/Ý×7ϸÞ/@ÏÅSÃÍGÞÇ+F7çFÊB*ÞË*åÌûõê×-ѯÁ,*÷B*TÌÛç2IÃïC¹ôͯÊûGß¿3*×+ø¶îÇ2Î>ì@Õ +L:ÅWÎ9-Â+FS-ÑË»ô1ÕÌKç·0ê@ZèÅÊRÇ=°:âSAMèLÇóEµTã=5âÅ/ø-ý3×ûý·ÍWã,,àùæK2ïÀÔX¶ßC +?6*FBÀÔ±Løû¹ê4¾-*RP61JÜ»Ñ8V<ÌçG×Ù-à@ܽÝæâEÐ.å4,Ú³îÞY>08º¯½¿<0îÑê;RWëã,ü.ØÞ;- +èLDîÞ¹´¶ï/¶¹ß8Íøܸ¾5,¶DзBϱåHô*Õê×=RM+åUøà*Ä3?¿òê×QQZDî.10*Þ¾R+*îÄ5ZòW<Úî7: +:TäÛÞÂU³2/¶.*¾WBÏúGûÆ3Å*ÖµSýýUÍÑùÏE?6öEàýý1Ià½-Þü7+TK´ýýÅURSÞ7AFÑÀMMæ¸PNÛ¹±W ++B*RÏD-¾Û·ñHûµB;Z¹°µZX3ÙöôûL=9Æ<üÜWÕÐZ/+üùõ+Ú½ý仾M0:¶ÃåM¸àï@½E¼?,5×Ýß¾è.Öû, +6Zô»1Xß¼ó*1.îLNßÚêÊR×=Á¯Eù:ATîÈ3³Äåê׹įDî.1ä*Î-éê³-¶á/ÆG¹óC»ÂB:Ú2æÜÞÁM³:ÍµÚ +¸0J@ÒMúȼãÂüF½îÁ0ýýY3ZV¯,*è.4+ºåMàãÉ-ô½6.ÅGú¿¸40òèAßÉÍÇØ>îÀ58Tß*Þ¿;²G=<ëØËÔ* +>*ÚRLÓ»ÙôêÐûÄÛ+°Qô0Ò?¯ÉëÚÍ+**2-:Ï;.,6.¶6¿·ñ0ú,õ8VU7ìÜXKîFLJ³¹â¿ùÅ°ÅÚ*WùÑKèíU +1¶M*Z¹Ã>RÉÌ3Ûö9ËÖMâJ>·¿8ùG=¸µÎ´í0O7¹/àDù=FB+¾ÉìÛ8.ÞË2ÞÒÕ͹W¯à*ùÛZËGùÎ,ÞüL7:* +ôá³W¹ó6êéñÞµûÄH=ÊÖ@NýýýZ*3ÒKOADîÞ*È,ú½¿?>Þ,ýIµ²>+ÁRâ50:P:ºµ.1ÓZ<îÞ2ØÌ33´ê +KU*8*JÓÄáÄGEôéÏýÜO¿Iù¸àÂJV3ÙßÌ»¹ôM*Î*°ÝÔ6;ÄÀ*ÍG¹×NÇ=Q4ä±ÁDîX´Ê.àöê×µDX7òÂêØí +ÀÈÏWI¼Þ/N-ÒÛõ8î,+æôêÖUN¿ÖòÊæ×É7J<¹ÂK2Æ.KìCÏEõ³K-Xâ×M=2ÖIß¿3*ÙÊ.ÂÈ¿-AL*ÇNJJãé +3-ÙìÅÌòO/Aè*+:ÎÞèö5ÜB-¾ÕJ5ÄùµCïD-Î÷Ê2ºÔ*F¸à¸¾ZLÁUBòUQô0´?¯ÁéQtîÂ4æ¾9F1,à*ú²U +4@Î,7EK1*A´å.ÖßÄëÃJBî.RêÖ÷C9-:ö¶Þ/Jõ/EF·¾E0¶:ÐZUTPÊë,ÞøòÓîä1YB¾À<ÛöXÞν4=Ô÷7 ++ææ¿õ@@7èÇÛçSÏ3å/6øSîëæ-²²Þ·3ÅH³EPºÅ+ÏÖM*ÇGßã1ÞNÓ=ý7úÕBï¿,öêÖO9üE·óNQôPAÓÄêµ +Zæß,Å*îÀß*¾=ÕÚ,-öêÖO5ô5·óOU±àFG÷ð-Î;çÍ15ÜU=´ÚXÞñýYÝü×,î8*àì>óáÂIÍXòâìF;èÌ/50 +û¸´Ç7ùK>VP-ÈDêZåÓ3³+=Hû*9ãöòâÂGͯT2Iý?*ÞüåÌG²/ÞK*¶ìÚYYܺøïNQôôÞÌJóÉOÖ91FÓÄÔZ +Lý@?óæÈCíº5ZõáýYë=RôêLU*8èËý0XEòæÓÓà,·Yêë¿GCçÕ³éÜÙ<4IÈÄGîP0P×ÅON02÷¸O¸ÝÇÀF´áÂEîÎ>ÊBË1*òµÂ:Q=H:ÁêØ:CUNÅ2AË°9ÉÆ0 +:X4M°ãÇîµ<ö+¾HIµã>3EîÎÒÊÆ+ÂðKIô*,*ò÷²<:FÈGLJ6PRB2*:ê+Ò8FÇ¿3*å±,+6>×±QÌZWR:0 +ïÀ+2*Á3Ì6¿·ºK¾¿ZÀ50B,Îã½³æÞU÷ðÂNV+ËI=KÞÀ=Uîê+>³+,MÐAÂ-Áï/Ô<Â<ÉÍîU4P·J6å;ÏÎïð +E82ßGÆÚ,ÂM°·XÔäG*GÉÆÂËÖ*Z:*ZI÷Aº +ï7¹ôPåÒ8êæ¿õÒ8FÇ¿4*ØüDñÆ+×+¸+ß/-2*àí6¿Nï@ìWÖ±½ß*±*4á±ôêIá¶æµW+ZFîÀ=U*âá1R·ô +PÅ@Æ´ô×ÅÉìBï·ÑÚZÁ4ÂÁƶÏEõÜïàDÌ¿â?JFéÁ=ද:ß+1°Þ·äÞ8UÝ,ßù¿XAÚñV°<öËÁ+3M¶N2ÕÖß +*¶ê,ûÇ¿F¶Ô5¾·ïVÅ°óR<°R˹*¸@;3EïC.FöAJÎð2ÌIúÚ¸¾=C2*¾ÇC+TãUÒ¾6ÆܶîÀ+Xó+ÅÜOÀ5Üê +×ÝZ¾3*J:ÄJÐ=ÆÆ?è=8XK6JC¯ïKPP·ß0-*õ;ïîEX2K0¾Æ¾VÌ6â¿6¶è*BÝÖËâçG*ò/õFÉÊ.¼ñZ.L-8 +3àCÓ2?ñXÆáDZÛæÂ5åD+¶B8·ÒäG*GÉÆÓB;.·¿.ÅIõN0À*âÆÛ1TÞFÖäXù;-Úè-âÌÛøÊHXC*¯åHMÚ@¶ +8ÕËÄ?2¹7ÇÉ+XWß;=ÈåET-*ôNÊðÀ70*ìMàßµÚ:À6*;+×¾å10ð.4úÅZ·ÖAé?Kõ5ì·@C3úäß2Ê0Ú;± +TK¶;λ:.QÌóL=XFé;ZGÈÂ6Tñ8¾ÌOâHÊæ?Öä8;-ÏâÏ-êŲ:NîL1ÌâJ.¾¹¾øºóÆ*T±µÉìÛ8>¶îÀ+À* +Ì6+P»ûÚé4µ6Z,JÛ¿Ø»E>³3×À±QåÅòðÀ×±½³/µÊÆÄéØ@ì¿4EÄ=3ÑÚZÁ+¾ùƶö7AÂZîî<:*¾5Ò¶S@A +0°FîL0B@,,×EWÎä3õFÉÊ.¼ñZ.ä2ì2àGÓƼA8Ò.¶¶ïVA*ñà×P*8ÄáÏSÚV˹ӶÑ/µêAO=H*¾=½KêÏK +2ç±K2N2ðJ.Ä*Ä.XòÞÃ6Â*öÏI2B3BT0ô´C32?-0XBÞBßÃ;J<32R÷øÄ+P*Ò·Y5DîLH;U¶ôåųRÎ-¹ô +ÕÔîÂ3G+/ÔıåÌÍ,¯ð³PP8ÐàÇ**91*:CKòT=Ðà*öÄ +,îå6¾±1¾¾ì-8ðϱÎñ6ÛöãT±¶ÂMýLâOÔR2ZæSQÊ:ÌBLÞÐáÌBMÜ>·Æ·3Ë8JÍW.ìOïÃ.NI**ûÇIÚ4ÃE +PÊ<;¸Ó33¹FÙ,+.ÛV+TNÇ2ïÏ*ÄGùVôX÷*ðC¿*0È¿-@@È/û,03Ô//XûÉ2Åî.ÎÂÞI1LJÖ-I<2,îäÍÇ: +Z+¹ôãûÕ07º?å8JS´+,MÐAFB6OZ?K>×úâ?*¾*¾DÛ->î*ÖIÍP,ÀM×92,¸ºK¾+ôµLÞ+BÊÞæCôêÕ59³Y +ÃÞÃ;Ê*¶?4×-YÎ8ùóÀàï-Mºà>?5ÅWϼîTH8BÞ3Õ2Á3PPÌÒßÓøFéIY·O´ôU3Så8Q»3°äåJKõD05ÊWã +RCC>Þ¾°÷ñ÷J¿MÕå/3XÚ×±µDãúJð2:ØÍ3CP/Ý×UÍÏ´N3ßXïU>¾¯åìLòUH¾RYEåøKî¾å¯ÊÖ-ôêÕç,á +Q28BÏW½7ËÑÀ¾**BG¶::¾Ê9ü²N:.*Ý3,GÛôJ1.³ùôRéÀJÓ²Ú/Rç´µÈÞ.ÛĵçÏUÚÞØ->î*ÖÁ¹K¶õMÓ +82,X*F»µôµLÞ+êòÝVýGðVõÀTÀ,6@ë1¿7>¶¾°8ýä/7¹ôôN¯óÝ9+LùCÏ@9+;ùÒMèÊæç*RÅÁ.+òå¼ +¾ÔÝäºD/Z9::ôEJÍ=FöÕ**¯,ºÛK*¾FYÎLÞ4.×@ú+9*6¯ñ;**MÎD¼Ä¿G*ðß-ÒF¹Ñ=3NEùêïÁ¿Êó4AÖ +ô*/B*ôAܯåì²Zð.¾Nï´0ïN.F*ïêÃQîÈÛ@¾õV.*ù-MB,îF±ZÅÓY¿îûJÕøÂMPS׶ê4Qñï¾+̶OâLêÞ +ÇDÚTÍ°à,¾¾*ùÊ.Ò±ONLEòî*ïϺ6*CåìóR*óäÌùÞÏ°Añ9¹ôXá7Ã:½+5ûæÂÆ»D>êCÃäÊ5;Y@°Å,¾4Ï +LQ,*=±,ÒÈÕçÒ-J:-ÐIξ-7J¯1¶»*+ÄG¹çDìÞÞ¿,¾+;/;*¾JB¾NQîÂ9È3/¹ô+,AY;.:R:<*2ʼ4*å +é8²òW<ÚîãüN.P4:>WáGº¼±.=,1ÄZ*ÞS-Þúà´³1ËÆÄéØôä?¾À*èE> +23Î2C2O8Gô-ÀÎSYIõØ,ù=;BÀ¾äGºRÇûË;Ê2FBÀ,*ÄþJôP5Ò·ï3Åʶ°ïÖ?:¹1Ã7á·@9ã*ÕÚ<°ðN7 +XÒ1*¶K4QÊæÉáïUCÇÓêÓRÖæ¯ñò+ùK=Ððêô7-öÈRÔ@ͼE¹ôBÔ´ò´ßÚZÁ@²LØ3Jç=õ=êéË,îæܶ>Î:â +Ò÷*Hø-+=+Mã¶ðº´C32?YCϯAE¾âæ=-*LÑâQLîâ*î÷¯74ò2ÖÁÜ@²ÞÃúG=D,4V7½ñ4,7À-ÑU>B3¾2Á +:Æ:â+KJ<32R÷øP.TAÆÔ/àM+XÃÞÃ;Ê*îÆ*<±9ܾÛ-ͶâÂã7ÕWßöSZðóÓºFZ¾UXCC·Ï0AèñÄ39*¾Z1 +BXç?Èï˳Ì:½FØ°²ÖùBD:ê×A=Ø:PÐ?Y7î?õ.S°1¹ô¼VÇ:ÁêôN=Xº.ì¯<¯å¼»?J.ë0-:ÓVFMíÁ-¶:= +÷=Þ¯54J-C¯ÅÒ³Ô?+Hô**7=IIË3ÑÉ⻳LýEïUûÅõíÂíÝ3ݽ=¼ýPúüËöûEïùµàõíÂíÝ3ݽ=¼ýKöüË +öûEïùµàõíÂíÝ3ݽ=¼ýPúüËöûEïùµàõý,íÝ3ݽ=¼ýPúüËöûEï°Á¿A5ÃåçÌG¸Ô,*ºåÌGÌî3ÛúÃE;æ6 +Ý5,Ö8=8+*Ë7ÞÂô»»úøµ7*.½ûõ7ÝÂJì/íWÇ3ã8·EÙÙá;¹ëÂìGºøóûÝýüÚ:9¼×*îÜ·ýäû¹¶ôìÙ»3+ã +¾GÀ@ôó/7EÛ85W¶,ÎÚÒÛO:°ÅËæÑ19üù·ùüóÑøôéY@*FºðÖUåÌGÙµ¾ÜçÉ2:1é+É?ï¹Kô-Ï-@->Jï-Ù +ÖËÛ+öõMôÖ3GõéØYå@ͽ½Ø½½üã,¾ÝI½ú²æÔ±ÝPÕÉYíðP²Dûö×ôJñôÀêALåÚ÷ïMñ¿Wêäì<2×áPòÍ¯Ý +0WDúûûO½üÙ/*í½IºëÊGáÄË@Ù¼ÜãËæß;LÂó³ëÔ¯5÷úõàÂÌH·èÎUå¼Û?;L.ì¿Wêäì<2×áPòÁYÅÌú÷ø +ùùµ6*̼öãÇMÝHÊB.íðPòÏ>ST6ÙóåÏã¹û¹+*ÝIü´äËYáXÊ>S4³U¹¹H°ÒËYÝAËæñàäÊBñòñíÁú·»,º +Ý6<øúõå1ÍÊB±ê>õXìWìÂXÈ>S¾Y½¼ê×Rµ½3ð½ß*XIûîÈ9½¼7¶ÆLè¹É·âREݼYDϽGGCZââãÍ1õ±,* +¸3àÀÒÂ:Ù9ö÷ðÁI0F÷òÕ,Õô@µLÍ?UÔØÔA5ÔVÌÍÝ5°ùøñÁM@º×óØK43íëW2**CÃê¯=øÏÏ3ñWʹ¹õ°¼ +úM***,*æ¿?¯YþÃ9YLð¹8Ö*0ö÷ìÙñXÊAïæ°òëHµL9@VÖÒÐ7ù³Ê¾½Íý2*¶ö²L8:î:âÓ2âK+Zó;/¶ö +í»EôéÖòÒK4ÇêËÅÕëV2-.F*Çåß*¾ÊLôÖ3ÛOæÀñ8Ô»»¹*ý4*B*öîÙñÜRáR*Z½³,Å,îP/м*í¼Ûôß·/ +Á´×çËæÓSÑ´=ò߸3ÑëêåÝ+*½WáGèíÏÅôøR.<Ç**îÆ,º¹ôÙ3éG6øä:ÃNôäUéZQD¹ñÌ×äáZÕBõ¼â8EÏ +±òFBXß×ÍQ1éY+**B.îÝ;VÝ8Jß.ä·D326ßÜ8J*Þ//Æó*ìÒ¾Y1*@È/úÔYÕãËÇ:Zßø=>Z7W2ZîÊ=XS¾ +Q½HÙZÚ¿G±êòPÂòæÏùö÷25J½N±L5Ü52îçÁ³æ*6¸ö*6ë>5ü/Ü?-öóéNJ¾ÒEºöLGØB?*½ìóÂ7¸òÁQð +÷á:ÃNôPZÓ¹++Æå.,BPä:²/º×Âê¯=¸ÀÌ-Ý7úø¶ñ*û?*¶*æÏ91êS=ò8G*WÜÛÛîEÙBLÓPîÄ,XÓ¾áCF¶ +-òæÙ/LËÉݼñ-P+íð×Í·à?/¶ñáY1¼EÖ±ÀËìÂÄ9I¼/ÉÖ*÷M=:@LIïB1³ùôRéÖ-Æ-7=ì¾Gù-0îîX.ìû +R?ôêëEKÎÑÅ<+*ÅêJÊ=Yº,/Á><×ESß÷/.JÐÑÅMû:ÁÎ/äã=°ÅË2ö7³Öèܽ-½/*;ÖÔÞ1±Ó-,Ú³î@Ð+1 +ܱÁB++8ú7/ÚT+æÚ°JX.H´¶À,ð.Øð¾ñ-JÝ»ÕH>+úDÛ3çßÁ1Y2é;ËÇ+âYJ¾áÞ6G¸äBÎ-*ÓUì8B*Üæ0 +GøÏ+¹,-ð=°½<*Qö>NïÞ@ÌíË6ÞûäØN±ßK±õÁ@J:ÍÆG´LIåCZÙ¾Sùô7¼Ý,**î4,Þ¾BM,*ÞôN>ÎF*¶Í +@FK+²úT.@°Í»àÏ.¶íÜS-´5Öñ·ËìÂ04I¶*ò5S9R×Àäì1ë7îã:°ÅË2Ò/³ÖÝѵñ¼0**Þ?.Æ9ã°Ð¾ï*¿¯õZÞ34JìÚ³¹âï8>8O*ëHûµÎ.Réò +P²EµìÂÔÂO²ù¼-*<4Ø52îB0ùôWö/í*À,à*8B¾ú¶ôÞ0NÄÜã*Á,A.J´éXº,æÀ><×E+óê¿-Æ:X9æêûÎÛ ++Ü6LôÖ3¿ÖÎ0óUDôêÙ±I,**î4,²Ý/?MÙ¼ÚP:+/61J³·,Í@ùä1îPáÓ¿Ì2A2B¶°*A,*µåÄWØíJ3:ÔçË +æÎLÃøé+¯¿ëµ*ÞýÌ*@A´ç±ý÷ëä>WÜVÇÖë*JÊJ6Þ+-¶½5VÌÄG÷ôë¼ËÌßÓåìÜG+B¼á0Aö.¼/SN°µÌõ +YµWÁ@é9µÎ×øZÀ*0Q:²SÃê¯=øWÍ-ÝóEø³êå***FÁ*âåá*.H8ÊJ,42-¾Ò³WRZáË9J;20F+D´:Zîâ*ì +.*Ý8ûõëÞ͵³¸=¸Ù´ëÜ,WBìÝ<øóUÉñ¾WçJ:¿1P×?³U4@Ò4ûÂðXB*òEÃL×±ÕHÑ°×±-¹îP70Í6XßÎY/ +SN°µ.ùYU³5Ú<Å+MèS¸;¿ÎO?°ÅËRQÃPFAZæ1ѯË7ÎÏ+åOFøÒPµLAVÖZQÑ7ñ7º1à¸Ò¾HE´ç¿÷DHëSÆ +àØôô¸â8EÏWT7×íB6Æûè7*¾=*Ö4¾F8+Í7ٲ߼;ÕÜÒçËæC±äê2³ä¿/-æAçý4Ø<ã=GÛ¶ZäÓ7/SVèìàH +9Åê¯=DLÒG58Á¯×±á¼»×*2>ÏÒ99ôÚDÏPµLÅëسQÇOMPVFÕÜÉH÷ëÙWUÌVØ°Qâسê¹ØÙ.LôÖ3W>áÑ=Å +ß¾ê50*åËùóãÉQIÌÒçËæÍAYÜ7UèÎ=5äÐõÀQüE¶ëÍKÕìØ7?YüúÐôô¼â8EϽÉȸ²ãÊOÝ@سYµ3M¸>òæ +ÓÓàÈÙåËæáÙµÝõ<ãÒQÑåηY/*úµôâ·éôÊSY°í¼ú-ÙÖËÛO:°ÅË2îHB6DûØôÛBPÐçK0õðìðPòîûùõYð +ÎÐÇO³é¹+*ôW»úæV5Ç6Î*î9êà¸ôô¼â8EÏZÝÖ0-Tíį<5,ÉëÙµí;GôVéààÙÚ3/¹ù߸TöÙHTÒN×4*R? +TÒò÷P¶¶,Ö½°5É×Ù.LôØ37ùØàê.¾ê×*BËR/Ûô³ûºÍEÏZÅIZµY,*ö9ëà¼ôô¶âÌE;YYü¸ïÛ¶íìC*>ìÛ +ÃYAÝÜçËRÖÙÝÖ<,ù¹ÙÙÙ;¹ìÂÈû¸òà·éܳ*:ìàϵùSÏJ½¼ý*ÆV2VQÌ +E±ÜZLÃ3ñPÎK¿¼ý*ÆË2ÖåËAWØòéµB;Ä>Ç<äËíI*ðM,ÃÇÐâƼû0=èÄ7IÀ6·ðù1¾ê¾SÐ6÷ïMݼÁ3Wà +Â3?¼ý*Æ93âLÄ+*àÕíZàõI*S½+Öøü+:¹C*%%% +d +551 94[1 0 0 1 0 0]sl 8 mask 0 95 di + +QP +%%Trailer +%%Pages: 1 +%%DocumentFonts: +%%EOF diff --git a/doc/krdc/snapshot_nobrowse.png b/doc/krdc/snapshot_nobrowse.png new file mode 100644 index 00000000..161e9a64 Binary files /dev/null and b/doc/krdc/snapshot_nobrowse.png differ diff --git a/doc/krdc/snapshot_vncentry.eps b/doc/krdc/snapshot_vncentry.eps new file mode 100644 index 00000000..05d85b85 --- /dev/null +++ b/doc/krdc/snapshot_vncentry.eps @@ -0,0 +1,248 @@ +%!PS-Adobe-1.0 +%%BoundingBox: 0 0 561 183 +%%BoundingBox: 0 0 595 841 +%%Creator: KDE 3.1.92 (alpha2, CVS >= 20030921) +%%CreationDate: Sun Oct 5 16:06:17 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 10.416 3.125 ] [ 3.125 10.416 ] [ 3.125 3.125 ] [ 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 ] [ 5.208 3.125 3.125 3.125 3.125 ] [ 3.125 5.208 3.125 3.125 3.125 3.125 ] ] d +/pageinit { +35.52 24 translate +% 185*280mm (portrait) +0 793.92 translate 0.96 -0.96 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 637]ST +B P1 +NB +W BC +/mask 6532 string uc +î½*¾î1:?*¾+á.S7*¾ÍÉ;*¾ò6+¾Qä2îQTÁÆÊ:<5ÀòX>E:¾IçÛR°K*9йÏâÀ¾8ú1ºGÄ*-@â2>Þ½¾é¯ +.*ZOÄÖÍL2*ýܽMºýµúüíöûÝïù½áõýÄíý7ÝýE¼ýµúüíöûݯùë2%%% +d +/sl 51612 string uc +î½¼**û½0*NÖP½J3Ü0AÌ+º>YÒùÇY0º´êA+*@ÕZêñI-CPÑç.HîÕI-*òêêûùñåIËC³ë7âèÃÔ½ôÖWI1* +ÞÝîùéÝÝëáöÊ6M-ò¼ðÉ+Í7ùB*ÊWøûMåÃQDÓèÓ°ç<ë÷¯Â±åõåûóóBóX3ÖÚÀ¯Í9M.úY»;¿1ú³,*ZÞù= +ÝD7*¶ÓYåì»Vø´ì¶óÎ÷FKõð*äÛݽëÔCÖSíؾÊËGÈ2PÝ÷LÇG´0ÄIJùíI5³D*UäV¶WÏ>SLÆXñ·À +ôã+êÛI´ÒV±¾Ñä³+ú5¼ýüµY¾ÏÂäø,*¶ïïëåµÍ»ö31RÔVCõ1ܲH*îV±M´6AUÎ>3L»µ=ÀÀú¹JüýíPϾ +°ùµ9DóÏ?óÌ.ÚûF¯ËüC**é÷Ð*ÈGݽýÂóÃLÚè-*î»YµÅYH¹òåç·è¹YÂ*TûÛýîÏ÷*¾õ²»µí×R¹ÄNýÒ× +»±,ÓÎð»íºìJî4¼å+=0Ò¼µÖQɾüý1òD58O*±ÃGü¼ýùëQN>õäïL>@²*0¼½ÑOÍNSõF*ò»ú·üFûøøíÉ1 +íG¹åHû·ñäÍQÊ,BõüüÌõ/ÒÏû±Ï@*öÙ×ËÁÝ9Iºóöº·îÖéH:+ÂT¼½ÔJñ+*ý·ÖYØUÃØFæà7EEñÇ3.ÈßL +1ýµYBËEÎýýýGò*/ÒSáùôÅÜW,*ò¾+ì¯ß7.öÓôñýÊ/ÇBÉ:SLM·µ8*RÃàÀ0*¹õQéñç5SèF0¶ü·ü¸¸óð +ùå½ýÜÕAÍû÷ðâY5ËB/ºõüûõûíÝI¶Rø**ºíìæßáÝÝܹNXF÷ë¸Yʾëí9ÒåÂ*ÞÛTÄTÒâ¾õÖÏZ¯IH;¿ZH +õ3òýøȺü>ëìí1̲º¾ýGYÝñÞñóQäH»P@ÍݺâM.òý½ÍAºîÂ6:äÖý1½º¯,BÕùüûñA5ÎÑöOA@Ú¾Á:+Éé +Gýî1èÞ·ýA×2ÝÊEG,¹Å¶ÞöÄݽ÷Ï-Úæ.û·CºüûïAM.+ÚÐÂ-OÖÁ*Jçû+áNøLÈDÅ+äñ*¾+¾UÍñÃÉIÝü¼ +½û÷GüøíÇAIÜBãÂSÞãôíí¶Úüû1-Ï/*üÒZîäÝÝÕ9IÍÕì¹öïÀÅHJ+èÝýäÊ*Þ³ÄìSðJ»ïÌ;X³CH;¿æIý +ç÷½Öô¼üíÁLñ½ÕãýýݸÅýû-ìöýº·úöýàÒèü9ÙÄ**Ûî¿5°ÎEµ<û9ÜÕ5=Îóý¹ÑòËϯ+-ì»=³ß*àðýE +¶¾ÃAüúýµÓ¿ÓýYèÛ·¾UÓ¾¶QüIIìË-Úæ.ìú½±XËöGDñ2-¶Ù/¹**NÙý´ÇH9JSñ>ÞºX»Ï**îæñÒêïÞÅÉ +ÅI6Æë½1üù¶×6ÍϾ**î>/.**=/,*¾E>îîùÍý?½üÓI5*ºQçâýY9ý»Yí»¶ìá;Ýê×R¾øõÅC=*öÇË»8Ò +QXؽµîÃQNZ>íýIHÒ6Pø÷1¯ºïËAùýÝÐC*½ôý½MïýÕTäÂüµHX6¿*À÷À18Æ+-À3Ñäýù9ÛB+³ýݲÖA1 +05JÁüý·TCÖî48>ÑWCýÁI´èÞøýI.ðFÞ?8ÞOßüýµòä+X²,ÍýíèÕÆ<ù7°FÒÂ-OÖ-*Þâûó°Ò÷JÍDQ+äñ +.**ï·,*¾MÍñGÅåäë»»ùõÛûøì·åHܱàU>B׺Ç,;,;*¶Á,Ú¹ýüÔèÕå÷**ÆMíßY¾²Æ>ÛÔæ°Ï¾ëíý +°ãÊ*ÞÛHõ8Pâ¯9,ĵL:RÄ¿ãÅH7ãÈûQ8üP¿²:ðÃ+öüý3¯º÷ý¼P±.QºîÀ6²ãÈOýIAòà.>êìýýÅQEV +Þ°íý³îZ¿òýý28.NÉ;ÉÑ0Ôü½S>å+ÌïÞ>áË-Úæ¾ãçÇ5C¶P3Ó.¹**éÚ½VÂ1ÌÒ¹2:Hå¼±IÝüJ:æ¼ñ·Ü +;¶è×ÕÉIÍÜÍA½»´èÞEHºÂ,1îA=4*Ì+Ä>¼úÊ1*Þü/ý»ùÃALëÞÁýáÖR¾øõAC?*¶ÌÊÓHPú:áÃÅL:2äOü +ûÝÕMÇíóáM¾*+<²Hîà*ºúý×LøúíÚÎø¾5H¶L0RÐòýVAGC+íåXïýññûýíÚ÷AÛîòÞGF:ÎýÃÕD:éýK@¹Ú +῱-¶Ï3@ü½Z;Y:áSO>ºVK*Îïüµ5̺ßQ»á*ñ·.Gº÷ß·*ûÏå÷½ÝOÙÄüGúö8ü¹òÔOýüÅî74ÚÆI*î8/. +øõM,Õо±àÅíȹõ@9**Ìá7ºôïÃ04GµîÐÝ?V>ZõùÕúB=*¶Ã»ù0åG³Wß,ñà=;2.ñ9M0ýYðMÝýËêÈ?ãÔ +1ÚB*9úÝVÃÎýÙÚ.IØý½éá5H¶J0ÊíXíýϽDôà.ÖíÍ;¯ü9ÜRÒ¿Wü9ñáÞÀE¶îýý±L×ø½ÆËP7ºQöôÍà6S +>ðUF¶=7ó*ADK/ý1G°ãÕ³8å0ðN3ô**B¸ýÑ.MÜîÑܯ¾øFÞHâ1ÜÝøFA1ú²éåÙ¹ýìÛµ½ìø²îð.ÜK¿=Zº +Å,ðºù;¿USÞ@å͵?»øÇ1*JéÛ7H¹µò+.êG¸íàí,5ÖRÞÊõYȯP*îÉß-ÌêÓìÝ=/ÛÂQL:2äC°.¸Iú¾6Ïà +îúóµYµHîâ*ºUòúÝE7N7Màéüý=H¶J0úI½ó±EW¸öÀ6BùùüïÝìÃS¿Ëú¼HÝßÀ=¶2S¾üÝ=/ÂWOÁÁNùúü +ûñ¹E¹Â-ÅÐÏ4ðJ=¶áÓá÷íAýìBà׿ÚSÀ?.*øìÝÐÞÅAúÒµ6îÞñRùõîÍ»ÌA5ìDôñé×µõêÕIÅë¸ö°ý°L¿ +-ºÂ,ºðùõÔÊE1÷**úIÀÞ¹å8HG*9ýG¸ïß5èøÕRÞ@õQF¯L*î-ÛÉíì¹±ÆòJßU=:¾*/,¶¼À63ÊÆ7=K°2 +Ñø¾=-¾æ<3ÐNWö¾VM>Bî¾18Æ+,<²SBÀ;+1PJC;4Òæ?½=0Ô:ÐL,15XÞK.Í+4NSØ5çÞ+»O<²2¾²ùÑý +BZÝ*¾*,ÒM:À/õýFVG¿1ÍöSíF*Bºû·X1¶îßÅEµüÚMÝüÛúôëóÒÉC/>LíÜW×´ûÄʻ޷ÎöÎ;Áä7òÁSÕ¶ +*ÍÎ*ÐìýºL¾í-*öéüûýOÉQîËH³8.ÚüFKOäC*2Iñ°P+XB¹´UÐO9º,*XîI¹å¶Ï,Ã4ð¸äìI¿QRJ?,í9G +O½½-*FÞßûùÉ6PXÝEQèG1¶ü·ÒÌÔPÅ*J²ÓÉD/ZóèÔF¹¼ÓäüíH¹ðÞDZµ9ú¹³,Ê;õüûCÅâõY*¾5ÂT»ýü +<¹¯-öÌKSõRÞïåϼñÜG,*CÕYè>¾ÎKÙÐV·TÊôû4áíÝI¹âÄßé9+*²Aðé+LêCHYéèëéA?Hóê9ÞãÉWC´F +ÕQá»55=íý¯*¾µÔCÉB´òðëÉé̸@2îïáÔ.5ÌÊ3ÚHºò68OèAYßUõ¹**íTÑà°ÞÈW5QêçéùYâú.5´ãÕí´ +Ç+¾±áPë×°àÀû,ÜÜ-*öYݾ/EäVس¾W´¶èØL:¼û8B,ôíI*ðQÁõ9ÄíI6ݽB¼ýZúüßöûÁïù1áõ9ÄíI6Ý +½,ºýZúüßöӾϻY9ÝüÍ.*.½ûõ5IÙ´*Mõ8Hûøüí½½XÆ1ý¾*Þ»ñüËùõîêÛíEë¸+6»øîß½Iµí-3×°äY+ +*××ü·*¶öâ¯ÕÌG¹´í*ûÏS×õóïéÝH*:úóݵ0B¸ìÕ³ÝÌWHÇ°ðXáÕ,*âòëúå+ÞíÍI¼D²é×YÑéãÕÝ:?õ8 +Hûøüí½½XÆ1Ý?*¶íÚ½ñüGF¹õ»+ÄüCôéÎOÁÝð8úýU**3áäÍý÷4¾¹*îûõÁÕË×±êÖç°éå9ÂÈQ9Húõñôû +éQóëÕÝ?*¶öâ¯ÕÌG¹´í*»U¾Ï=¼G¸ðåéå²úVÞ2Íýݽý÷4¾É*îòÁYÌFøóßÃKö-ÏÐÚYé¼GÙú¾N×IýÔ*Þ +íÍI¼D²é×YÑéãÕµ.BTÛÖEAäF»4Z.*¼0B,ï?*õ*:>åÁQXÚC´Ûº¹I2?õ6ÙóåÐÝííYøñÝ@*öüùßíäÚ÷³ +êNC³ñ7+ÄD@³ã¿=AýZÞÆîáÝÛ,î8*ú»øðÁI8VC³4EÕô-Ïð¿YÁÄê÷¸ùø9ÜðXDÞóÑ9¼C°æÕ-5ßÞE,6Óç +æÃùDôG;BPéÄËVÊöú½÷ðOÕ*Ú-*QýÛùìÃù@8WÊÜ»+ÄÜÔóãÁAIIíÜÞÕý2**¾+¾U9ÆÚ,F¼ùñÁMHº×³TU +õôÂÞÖÒ;ÇõGÔ¿Ñ;¶î+·2Àè.»ÖXàÙÕ.į?Û6Ä.Húã?*-*ó°±*H¼¹ôÙ3éG6Dæº,ÄHÓòá¼9-1IÜÒQý2* +*¾E.ÖÀ+öøïßý´êõ¯ç¶À+Q¸îÞ5T-ÌÆß/@òî*Þñ +ýÙµµÚ,î:*Ú¸ò×/á7úCâ=Z.EÉÞ3ùôï5@9:**¶¿**J;**R3¶Î,ó+A,ø46ìTÞÇÉÀËQÊ;AB0RÒ.Â,FB¿ +÷¾M.¶Ï/ó-QLîL6078GßZÞÓYæÁÌ-Ý76ùCBHÎßU2º¾,M*ÅÆîä3XÈKÇJ-5*HýI»¸Þ?*1+ÞÍAÅGöëÞÌ9 +H+MöÖ.XÎU½Ü¶ãZQ6õR,ìÒÞ3àÛ°¾=.¶ïÐî¯ÈZº°½ó»ÕHîÎDÌÇâ79Fð¾UJ¶Ð;*î¾;Ö¼0ÄÅ6î0-X¾Þß +8ÚÎßÁ7¶Ð,û5ÄÂF·Û?*1*:üW**21Jà¿ÙHZ.*;;>FÏLÕÇI½Äȳ/ñ*Å0îÊ*XãÞ/AF±¾5A?âY9±¾À·¿ +ï*ÞËÄH¹Þ.Þöî04:XÈñ;AÊ+*ºáEÎø¾¯Víß9Hà¾UZF*¶ó,õDîN6ØB63¶î²U*8*JSRF*ûôÛøN8Þ¹,ľ +YÃÈöüûÀ38Vß/,Ê-ÚB¾E,2ƶÓ3µ¶ïÊ+ä*XB-îÉ56Í6Þ+FH1á5к/ÄQÆ÷0+<+MûäCZÙ¾SQ4Ä1öò43¸ +G6ǵíXU*8*J?UF¾õìËDZÙ¾G½¹<·:XÃ4ò¼Ï.AÔî,+ä+X6**¶;*Õ°îÆ5XÖÞG4FZ02AGBH·ÜMMº1ĵ¹ +÷R.4-8PïOBÀ±ßEðõò,á-ê¹Å¾QÂö86YNJñÁ;ëÜ7³/Þ*J·ï6*ðÜWùòÝ;·îêÇâ+²?TNù´ìó.12**ÚJ¾ +·¾é-¶?-O5å6î,+ÌB-JÉ@6ͳïKBÜðâ.ÔîT/88=*5Ü;¼OI0á*ß-?º2Ä1Vî00P-XNÞ?I²Z+Þñý±å@Ø, +Î*îà5³´ëׯÉôE<1¾GÇFöÓL/Z16F,¾-T21¶¿+ã*ÅHîT/ÐB¾=ä*ÝÐ.á¾ÜÉMºî+O¾µ*B<îæ041XFïÏ= +FÖ¾E±Ò×H5¼éµò-Zºº*çöYDEæÞZNÖ¯ýV+¶**þ-+ú¸òݵ/½Ìó.6ó?æÁïü/û,3Ôã*DDZÚÁKÝÈ46õ´+ +µ¶÷J+Ò>çÃó,0G³BÌ5N¯-ë6ÄÞ±/ÞÏBH¾*77ÖìàÐOGÇFõÂø¯Ü¿KáÌûÈøè@í¶ß,4Þ±á@GBíáÒGW56YÖ +NPN¶æ+RSóâ¼-å7Äî½BMå½;ú½5Ä*ù+*Äâßß½=ÕÔòNðÚ;èË-50û¸´È7ùóW+JBÁдV¶ñèÄÔ/Ä3ÔðK1Þ +LÑG5@F1B¼5?¾åýݼë±/ÞëâßáÂGÑPòNðF>âÐ;=ôÚ¸ôË+¾ñäûø°ãÐ=MعBÜò*9ãöòâÂGͯT2IýU*Þü +åÌ7²/Þïâ×ãÑ9Q¸<·üWTèÏ?±@ûØæÔ»YYòÝ¿é0G¸³ÓÖJìÌ*á-ÜKEíàìÇýÝì¯Îê×ÃÔ*FúàÑÇñ¶ASP»ø +±æÑç³ëÚÕ/*üE¹ðÚ³¹äâÎêãÛ;Þ¶ÁÜçßÊ*KU*8,Þëâ÷ÃÁÈPXë¸óèÞøØ*Þ±ùôë¹²ÔÃÌÏÔÃõ.B,ôùñË +XåÂéDZ³Ã×Ï°»*Þ²L?òØÕóñG+ľû4»ÑéSï°ðöøðÕ5íÃùôð*JïìWú¸÷óW+ĹòËXAÃÉÇÁéÏ5åWùø*¾¶ +5Åüê5E*6AçEÛÌ4Q=µá5æ*ZúÃÛïûÁïù1áõ9ÄíI6ݽB¼ýZúüßöûÁïù1áõÍ*íI6ݽB´PB,*/*ÅFîô7L +.8ÑÞß9N*1õ×ÎÊ:?SâÃ7Åû¯/ÇÆÉ9ÐûÃêX=Á9Ná0åµîô.°±È-L¶±>1<ôêü°.áºFîVBð·ÆÂÄîö4üKBH +Sù5²Ú*B,*ó2/Ì1èCJîàJYêÞ36FîÀ=;¶*9ã*12î00ì>ÞËAÚ׿ÂNïPIXJÞ3ë6/FõDz2ïÈ+ìëÞó/F> +5ݶ³2QØîPMÌ4P,Õ·A,¿*õüîÂ@XÚàWÛÊ,FÀ¿±HÆ¿ZÌôß×êÚ¸Ò5ý¶Ã-ÇÁï+JF4ÏE,·´-QÚîRFÌêÞ;/ +F°ÂM=·B>CÇàÃ96+FÃÀ.º<5LÚAÂÉ4·â1å.ïPÞÌ7LRÞG¹ÛëÅéÕ·×9õüîÆ68ûðãMFÂÌá1¸â*åùîä?8ï +ÞÓºFØ¿VßÖ*:ÁÂFÀÈU0¶ó9Åçð898¯K4O¹·»õÐ:Á5ãß=õ3;+-ÚÉÀ6ÞöÖ-Ìéâç¼NîDQ+ñÖ´ìëW@IÈà¿ +пÍUÔ¶é:×ÁïW6Û4âÉ»·*-1Tîä2Ìäå¿;F¾ÀMX¶>.SÆLÌZÊÀQì=àï³ÚËÀG¾¯ÂàÓöNñDõø;=BHÍÓ5B¶ +Ò7׶P,/·Ï-Ó+1¸îè¶Ô,ĵ¯îðÎì³ß¯-ÚÁÃáÆÇÄ>ð-á¹X¶÷83ÚçS¼FÊÀÁ´·×/A1ï¾,8.Þ?IâROAýîÌ +@<öÄÙиæ@×ßåC+ÚRÄó¿ïßBGìÉUW¶å*17ïγÐ*á>ÔñîAÌOJ,H·Í2÷²ÞWIFÔÈÉó¶ø*µù;ÙéÚHÂ=0¶í +Jõ¶ïÜç°çÁ=B¶â-ANÎVïJ,Ì/K´ZV*¶²6³*åÐ;BîPµÌîã+/FîÀ-BR6¶,-1LîP+8×Þ3éâK4åÐîÂ*ìRL +4HÇ0;ì;ëÛ7ÚÞÑM¼¶>4õ¼ïJ4ìëäÏ1ÚT¿>XñZ.èE8¼à×+ÚJ¿±Å¶AGõäîرÌÈàGãF;ÄU/2J¶Ò-3À¯U» +J8Í×Ù5V¸DD6DÜ4B¼7YVZ.%%% +d +561 92[1 0 0 1 0 0]sl 8 mask 0 0 di +/mask 6461 string uc +*Þ¼**ü»ýÄöüíöûÝïù½áõýÄíý7ÝýE¼ýµúüíöûÝïù½áõýÄ9°è* +d +/sl 51051 string uc +äÕí´ê¹8**¾Ô-ÉHû4éäÕYUÛ1ÄÍîUܽB¼ýZúüßÖÓß2*ÚZ¾+¾U1¶.+S8JZß++ÚTû>Þ¼Jïî78³*öR*Æ +39Ðø¿½*ÄÅÂô*-ìJJàDÙ?BÜ,ÇȾ?.àßQF+áòöîG¾;+**Þ>JFÞ¿0./F¶¿¹I¹@ÅÅèî29ÌÊð?56,Ò5ÚB +ÀE²¶ï6õúîÎ28Þß?ÂFUÄE@¶´2ÅRïà*ìÖáã²ÚÏǹê·.Äã*¿*àÁÅÇ*îÏ>î*,8BÞÇ6Fö¾ì1á=ݸÁ¯åÌô +À.XKÞKàGàÁ;¾±Ã¶À3õ°ïì9ì÷߯RÚÑÅùZÊ9?XÄâëÍÚÎÁMZº;ÂA¸BT¶.Î:î°08ÃKáÁïϲ۰¾5=½=ôA +¶ó*.X¶ÞÃ4ÚÈßÁ¼¶4¶Q¹îÌ9XTÞÓ>F8ÄÙùÆ0?°é¾=RV*R2¶<,1/î2-ÌBÞ-*á-06,*ê9CJÛÞÓ+Ú׿E² +Æ26íÊàOåA5ÚRÅM=W=**ìÞßË2Ú×Àáⶳ9µS;Ù>ÜEÍMHº<ÅQüB+BH»ãÌ1áFæûÀïX¶JÜZºè,Õ¸îÌ:8P +áç»Úè¾-+Æ*6ÍÑL´0¶>2Aßö@29½ÞãBú.ZºðóõÞöîÔÌÕßËGÚ³¿Ñ¸ºò4õüôêCX°î3FF.áNRϲ:ÁÆN¾G +ÅÞÖ6*ºF0áºòÖëßBäN*ZI+23R7R0¶Ï+1P³Àî..ÌÓß7-Ê<ÚR¾M@R5¶²,1¶ïä:Xøß7YÚ4¿UV¶²*1.î* +/Ì7àGTÚ±ÅÑç¶ÎÇõÐîJ45*.¹9³+ã+1ìöÂ*à-T,T0È+ÌÖMåÁïØJîì>Eà,Ì×õ´0ÎÓ¹ö÷¾;öÆH*DùHI +BPóÇE¸ÂÍ+ÄýDAܽB¼ýZúüßöûÁïù1áõ9ÄíI6ݽB¼ýZúü3îûÁïù1áõ9ÄíI6ݽB¼ýZúüßöûÁïù1¿õ9Ä +íI6ݽB¼ýZúüßöûÁïù1áõ9Äý61ö°ÏöFë×MåP*¾õê×á3HÏɽ¹B,ËK»K*Ù+á;*Î×+ÞæÜÕÅYý¼+¾îÕýü +=º;ƹ;»E,M×Mìä4õ¶æXÁϹ1ͼ»ýõÝIAâÁY=*ÚµìÝ·ý8ÌÛù½îþ°69K5ùø¼øËXÅÃ@D+RXTï<2ØáZò +çÁ1ýûÚ»ýøS»ùóÕ3*8H·êÓ±åÌëEJý/Ü2:Åî+ÉãñUïõ-Ù-@á=Jï-ØÖËà+öõÁôB6GõéØYå@ͽ½Ø½½ü³ +,¾ÝI½ú²æÔ±ÝPÕÉYý-Þæ²øï±ë+êWÃÝYêÌ·ñáÅä-ØÖËàO:²ÅßæGá´û×òõùùÉüúõ3*Üý¼öÙCµ¼7ESPë +ú9*MíÄ2ÏÕÕ±ÙÄËâñµ½=*îóËÑüʸôéZ/;LA>µEÃÉJæ8ÁÏ1¼7D°åéëé1ùµ4*XIûîÉ;Á¼GÖ20ü1¾ÏÅà +ÂS¯êÒEÅ@ìñíOÑÚúìÊCÕÄÛB=ß>èÐ41òI9DèäÕíÉZò³WYHº÷÷÷ô/¼ûÅ***6*Úø¾-=¶3,3¹¾áµ½¼¶âÁ +AÙP³óìü-ÞæSÏLÓA´åÄE5óòÍGëVUÀϹâ.8ýúîÈ7¹ÄÛµÂL<ùî¶CÛêÏ9N:²ÅßæÑȼêõ¯îîáÉøò9+** +¶Á*1OZ5-îóáÁýëÖíáÐSTÚØý*B¸XÚ¶¿OGCZâÀÚúÑ/ÊZ69?¿ÍAIú¯ÞÇSÁ7ÏÆNïÌEìW0Fð¿.ÀôB6ûPæ +Áó<ÜË»¹39ü;***A2**>,**õ2î4/Ä,8RßO-ÚÐÂ=1îïÙ±ÝËÖìßÎãäÙÍ*6×C±ä¾ÄHºµ²5Ò½-**ÞÏ.** +±+*JFÞ;-ÚJ¿-0¶¯+±*Þ»ZÈëGFDæ2ÃÊ4õÞñ<çØMNÆJæ8ÁϽúFBXß×ÏU½@E*Þ/,**±*A>îR,ìTÞ;;Ú +ÈÀÑ,îèÏå@6µîå9½+Bø¶ïàÃúV¶ìßN¹øöµ***FÁ¾B**Z0îÆ+XÎÞ;-F:*ÝHÛôß·/½8CL;ÑàGïÚVØ@ÍÎ +G0MYæ·îÙ¿Y=½8ÐÝ,¾ÞQ+*ÞFÞÏ+F:¾U0¶C,1SîØ*ú·ðÕ-Ý7ê·ßY*Ä´íÜ»1QÙ2´ðëí***¶0+¯*·BÞ× +?ÖôåÇáHÊ,1M>°SÚÉ6ÆTéì1ë¯B,¼M7ÔØõíÞY+**¶ó*ÃÅO+Å<îÌ,ì¶ß/4Â-FÙÂ=1JÝ»ÕH8+ú¼+BÐÁ1 +9Hæ@÷ûú7***å::0îÐ+0*ì:ßË?ÎñÞ½IÞÒ2Ã-ØÆPï8âV0W¹³³õÁôB6»Få¾ë,PÛÙõ±***XGÞÃ/.-FȾ +É4¶Ó+1ýîÌ+¶µè¾ë,ôFìA*M=SÐN¶1Øó¸óX·ì¯ìG7Jæû*Ñ*Ú2Ãâ4õÊçì´ëCÓG9ØáZ.æÀDòíçصñÜû2 +**¾U1¶¯*1.îÈ+8ÅÞß>Ú>*â»Ùôç½ÁM*ØY*ÄäÑQÍäLÙÊYç×¹I,**î,+ä+°±¾-,ZرÉôEB/ÊÃRNж16 +@5ÑGÒØ.ÀôB6ßÕ<6ÁGÙôêÅ***F¹*+*ÞBJ7ÞË.FM¿E7¶â*1Vî¾*X.*çÌ7¹71úø/ÞæÙ´ëØéñÞµûÐæÕ³ +Ý,¾R=*îà+P+86Þ7/ÚٿؾC*Åû¸òݵ-½ì?L;Çà?ÆÛ¿×3¾éWÉTæ8Á;Ý,»BìÝίí@*²²Þ¶1Á¼ó5¾Ïõë +ÙÕ>çÂó,ÜÌ´çæ¾ó<@WTÂàØ4õ¸æ8ÁϹLûBíÞίå¼ó0-ѱ*ùðàöU6Êõ¯è̼+BøâÇ=MTW÷íÞå¯A¯·÷êÖ +Q=0V·³N;4·?XÌÄ=ׯÝ8ØáZòÄRµ8VBòé×S´êÕÕ3*ß;ÈدßËUEø/ÞæUÑP»3ñæÄ1ÉåÔÕÐS¼Ù´éÇ;Åìº +3ÏPÛøÌ4A¿ÎG0M1/ײâÂGÕÌ»Ó*îÚ·ÝÜV×òâƼ+BøðâÇQùÈøñâÀ÷òÓZÈùüGù±æÓßPMâÇAÝÒàÛ9ØáZò +íÐÐ8WC±çÕôë5ûLý7Ú´æÏUUÃ/ð¼,ZòïëÙí¹Ç°èÑç±æÚµ,*üE¹ðÚ³¹äÒA×µIü+WêPï<2ØáZ.¶920Ë +¼WùT4=Ýï:Á¹·ý+ZòîûùõYðÎÐÇO³éU+*ôW»úæV5Ç6Í*î9æà¸4õ¼æ8ÁÏZÝÖ.-TYP¯ðß+ÉëÙµí¯FôVé +ààý*B4ôôMöÒîµ»ÒRÇ°=*ÎTÒRçñËîæ.ZýÂ@@Ѷ3.ë³BDô³Ã×3*×W+ZDÏ4¸ëéø÷Q*ÄÀGüBܳ3*Þ½N5 +ùSÚáK¹8ĺ»÷éÃõ໹Ù*ζõ@¼ÛúøY*ÄÀÛû»»;ÖÝÙàÛ7ØñZÒêÕMAÌÙôû,*ï@8ûIöLãÌGúøö÷í157½ú +óßíXïÌ0ë»B¼ý7Æð3ܽB¼ýZúüßöûÁïù1áõ9ÄíI6ݽB¼ýZúü3îûÁïù1áõ9ÄíI6½7B,Ê1**´¼ý*Ʋ48 +:ÏöóEÞRÃ4OT>æÍíI*ÐÛ,éÇñäÊAÞR¯àÚ3²RÎíI*ðã,ô·ñOÉ@ûø7ZÂ;Oзñù1¾ÊÛJ¯Ð>CÐYÝ+Äدá +Å:MÌúü+:Y@ZÈ>M¼úæÕí*MÉB¯LÃíI*°ô,×Ư*¾öóEÞöû1Þ<ð*V»ý*Ö1H* +d +561 91[1 0 0 1 0 0]sl 8 mask 0 92 di + +QP +%%Trailer +%%Pages: 1 +%%DocumentFonts: +%%EOF diff --git a/doc/krdc/snapshot_vncentry.png b/doc/krdc/snapshot_vncentry.png new file mode 100644 index 00000000..b55c3fa3 Binary files /dev/null and b/doc/krdc/snapshot_vncentry.png differ diff --git a/doc/krdc/window_fullscreen.png b/doc/krdc/window_fullscreen.png new file mode 100644 index 00000000..5c9864f5 Binary files /dev/null and b/doc/krdc/window_fullscreen.png differ diff --git a/doc/krdc/window_nofullscreen.png b/doc/krdc/window_nofullscreen.png new file mode 100644 index 00000000..bff406c3 Binary files /dev/null and b/doc/krdc/window_nofullscreen.png differ diff --git a/doc/krfb/Makefile.am b/doc/krfb/Makefile.am new file mode 100644 index 00000000..085981d9 --- /dev/null +++ b/doc/krfb/Makefile.am @@ -0,0 +1,4 @@ + +KDE_LANG = en +KDE_DOCS = AUTO + diff --git a/doc/krfb/configuration_access.eps b/doc/krfb/configuration_access.eps new file mode 100644 index 00000000..93169561 --- /dev/null +++ b/doc/krfb/configuration_access.eps @@ -0,0 +1,341 @@ +%!PS-Adobe-1.0 +%%BoundingBox: 0 0 342 453 +%%BoundingBox: 0 0 595 842 +%%Creator: KDE 3.1.91 (CVS >= 20030907) +%%CreationDate: Sun Sep 21 21:21:52 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 9.305 2.791 ] [ 2.791 9.305 ] [ 2.791 2.791 ] [ 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 2.791 2.791 ] ] d +/pageinit { +35.4627 23.6418 translate +% 185*281mm (portrait) +0 795.224 translate 1.07463 -1.07463 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 367]ST +B P1 +NB +W BC +/mask 6493 string uc +î½*¾î1:-*¾*Ñ-õ0*ÞQK/,ÞÝÑT*ÞñÎ,öïì3ðZÀÉ8*1<·ÈáµÇâS̼*úÉâS̼¾ú+FQ?¾9ÇT¿º*BQ?¾- +×TKÌS:*0ºýTöü1öû9îùIÞõ½¾íý*Ýý+¼ý-úü1öû9îùIÞ½¼¾%% +d +/sl 51642 string uc +î½¼**ûÙ>*<òÌû@KÍÌè6JÑÎÈìQ¼Cîׯù<*VXßÖåYúÓÐïG,9öé@/*GE¹ýüú÷íö¿ñ×,ÈG´éX/*ú5¼½û +ùÕæTåX?D:ÃïEÙòùżODHRÄ-2G°,+ìïÞO0ÎÞößQá-ëZ-0¼½ÑOY.å³øö +ñòéé½YHºõêGùòåËE½H¯4ÞÚùù¹ÜATóâöûëêäßíÅ9ܸºÜF¶ê³YV×ÇíÍ?æ±**B-*ú?XËúÞÂ÷Ð3:C@îJ +/²ìðöûñ½ý84À*ÒúýîJûýý²/X½Jæ8ïí¼öç3Ê,2ÐWY¹+äóܽ½ûQÀîÔ6ðE*êÛIWÍQñJ9½GÛ¸÷ûñݽíé +Éåüº·ðÕÃPÊ,Ô¹ýüùÀT+ö5Aô»¹M19ýüøì;·ïâµé»¯,´½½ÎHÍ+*Ö6*î;òLÒæÚ<êä,4/ÚXýé÷SD³9Ð +¹SîH»øýIÆL;*ÎÉ?¶ë+úý½JÛ¶ýíøò.T+JâáXÎòý½Á.WÏSÁÉBÆìD8>*Èì9BPÐ/FÁH*Þ*Þ?øöàãÍY½ +ÝÝüú8½ûõâÉ9YÊ°à>î°ùõµFXýüÁ¿æ,*½TB¶ñííéÅÍåUõGú¶ßM9Î*óí½ñG**È-*Èë7-:GGT4R>*ç-O +GàÊ<+0ÛWݼÝ7òäËýýµÔ9XÅ,öý¾²üý½±éöν>J¶Þ2J6*IÕÇ>·üïãÜLÃö;Ú3ó½ì÷8Ú¾G¼íýüD+3º¼2 +,ú½Â4ôBüöWÅ5Òüý4CàÂBJÞ¿è½»ñPõúñÁâ1VÏÀ¯ðïÂF<3*NÙý´ÇHÍÎQÏÈ**î2»ÒêïÞÅÉÅIÔÂë½1üù +¶×6ÍϾ**î>/.**=/,*¾E>îîùÍý?½üÓI5*ºQçâýY9ý»Yí»¶ìá;Ýê×RJøõIñ½²*Jð*¾ÀMLW̯8É,4 +/êüýÄ.Ì-¶ëý¯LúÝöNÊøýý¼ýï950*¾ä*æ¶ÙËL+P,8¾¾Ñ±óÙöZų=ºÂ4á-Pñ½ûÎ8Ú*ìýÝÉÒK¯,Ô4üÌ +ÉL6ÞìEÒ¾9³üQ7¯¶¿PôýýùàCÍ>-X7ôIüµã4?6³,Aâ;3*Þâûó°Ò÷.»B**öíÕ1*Þ;øöÌá±ñôÜÜûùì¼» +õÚ±9íCï?46½ÛN¿2+¶IR/*ÚK+ìÛ½½Uóéñº**âÏõîAè@ìÄXU²×RÞôõ½×ðC**Æ+*ܯHMëîû÷ÏïVJϾç +ýIçâ51¾ûüÕýÓ°÷06D0B-Jø»Ó5/¶¾,/,àíü:RÈYTãÖ*öÝüZXÞ²üYYæ¿5JôýSíòBPÞ:ù´âY²ß?TÖ¸ý +ñÕDYÎ3õìý½Cã,/EòñðçAÔ¯ÖîLHXÆM<*ZËùÅÃÖ¾=æá¼±IÝü²3æ¼ÁHÜ;¶è×ÕÉIÍÜÍA½»´èÞEHºÂ,1¸ +>=4*ÌÏT>¼úÊ1*Þü/ý»ù/TKëÞÁýáÖRJøõY<»Á*ί*¾Ù;ÑÏLUÈB÷C5R>JàýíU·/÷MÕêýøÂNHî˯¾C* +Ø9Sâ57¶À,DÝXJ¾*74Z20BµB=ZZýµPòÞ/îäâýÇI6=JZï½EC¶Ã4ôðý÷À4FÎÞºýïù¸*4Nû½ÐÕÔï,CXÆ +M<*¾ÄùÝW¶ð3ø:òîãÅ÷*òÅæáýû@ëDøõîá¹öíÏÙ@ûùIÀµRî.GíϾJý¼NîT4¶PúöW·õìð+*Þ·Ú¿åHÜæ +JÏñ0XøÙÀÃ,ô¼ýÈ9Í+¾YÚ¶;O=3AXÂçP/øàRZJϾEÃíGSÉ;¹äóÕý=VùÝXL99êäBÞ6*O½Aö²*ëýÍí34 +Þèô/Ìòø4?úýýÚàÄõIXÓ-A¾OWüYÝSÊ*YøÌ·ßúýIÛL/Ùù½íLß/VÔäÆZýýHòJCÒüýàüýÕòâDXâá/·N, +*B¸ýÑ.MÜK9ÞHX.ÜÝL9A1ú²éåÙ¹ýìÛµ½ìø²îð.ÜK¿=Ó¸Å,ðºù;¿USÞ@å͵?»øÇ1*JéÛ7H¹µò+.êG¸ +íàí,5ÖRÞÊõYÈïC*æÂ3Ï3¯@¯*¾KÑÓ2Ͼ¾-?î;óTå½½»ïö¶¯ùõA±àÃï¿ë6-Ê+¶×óõüÅ92MçàÜå=>Î< +/ÑÝíûÛMÒîýýýÕíÙÜJ÷8Ú*ÈåIÜKL-16ô½Ç¿éýíö0?ZýûÑIç4XÞøùù½ý¸ÖP1È¿3ÝÕêD½Ý3YæàúÚ:Å +N**»õYSîá÷Ë-òKëÝÁ½ÄHÜV¶éÖÍYåÜÛ±ÝüF´éßÉûÉ3/8Né;4îÇíÝÙ°ìFâ-*îý2,íG¹öò*ºúõæÃ1YR +éÝL+ØÚIïCÒE-¾07ØEâZ+¾L+AÄ0Á0YÖ¾ùÙ?Ç:KÅL?òØ2;ØÓZà¾*Î;¾5-îKë/úíøÎÂ3SALãÌÎ*DÚà +ÆÎOÕT¯îYN×5ÛRAZÇ.OðÔÌJá+øô6;JAR;Øâ,3¸³î¶¿8<>RÏTûX>ÁDEÒ4@K×̺á++F;Å>,JEäíYXÞÄ +åæ=Ôø2»V@îÚ³I¯Sá7º@¼·Ï*ZIX»Á,6иóÔÂ5íö,*ÚBÑÞ»õPÌ»º0YG÷èËÀ3V=¾SùQÍ2ï?*ÖÀRà>º+ +*Öè/>»4HÙ¿6@JáíÝÍÙÕQ×KKLG2ÓÈñ4Ð<1?ÙIò´+CÜL9+*ÞCøÖPÞÁù¼,RPºöPøÐL¿2VöüT4¶áñåW÷ +ôWï/*¶ºÏÂü8¼º¸öʼ¸ÑÏ8WMö*/2öü°ÍBè6*7ò´î3*Þ¯¿,7Dôê0ÐÖ*ÂNÆÎRøÐÁÀ¸J*+Èò4<<1¼¶» +½Ìï,PVÝ·ÌH¼2¶îàÉMÍXÛYý¼üøðâ1ÛÉC/ÒLïàWãÌûNîÚKµÇÜ+ÃVú±äØ89LØìCº¾,Æ´½ø9-ôV*ZßûI +1UQîßFæáó*1Hú¶ñä͹òéÝÉíÜÛ̸CѾLòëÙ?éÔí6Ì1F149D°æÚ¿¼Bó41î34JèûÍQ.ÖY+¾ÝôíýYغ, +Yúä³ø2T°Ì+¾öCA±4æÀ×±ÙTÆéC¹ý.HK+Ó2º½ò>û³T*¶¾ÁùõACÊÚ¼Q¾ñF»RGWË8+ +¾æTA³4ÞèÓW·ôúÈïúÝ»ôâ¿=åìHöõè.BMìûù±8ÇYÁ,¾5ÂT»ýü<¹¯-öX/å¯øμñÜG,*CÕYè>¾ÎKÙÐV· +TÛñû4áíÝI¹âÄßéÔ.*Ø5÷óÆ6ô6ÍÕ³óôóÙO1áãÉWC´FAîß»55=íÝM+ÞEéÊO6E¸÷ôW=ÂïïáÔ.5ÌÊ3ÚH +ºò68OèAYßUµI,¾µÓS¯CZãÔÃ=ôòë=ÓBIßÀW÷óÙW°*îêÖS¹ÔÊÖZݾռ.*ýó5ÖZáÊOÙ0Öã@Hé:,ïéõÒ +4ZýÖ*îûÕèùÝèõ½ÓíýTÝýÓ¼ýUûÀÕ4-í=óÅÍÚ+ÞüÆØAöAS?O35XôûÕå7*2ûòÔÔRü;ÓÀ¾½ï@ý½ô*ÎéÍ +EÅûÉíAÐT0îêñêÑY?ÜíÚ¶ñ³û×è.*:ÕBôÐTßVÙÉ5õI»Uç÷ÚÉíÌ?ùH**¸W-9JñP¯ÓáÓ°ÔÈõìGýTçûØ· +Aµ³úH**¸W-92ñäÕDSÓÎÔðÕ¯ñͼÓÒüéÕÉéÀ¼9**ÔÔ¿1·±UWÀÓÅUO¿ûHÛD¼ý×-¾àY»øµZåõ+*ÞèDö +ÞDYXFU9ÓÈDá7زøû5*2ø,<9JJ¼/ÎòOÁäëV÷»**ÖØ0HBäÈCÙèMÓÐê²µüVüü,*ض,Ë-2ÆY+Òúå˳±E +ü.*:ä9-6Ò¶Ñ+Õ:**FQ¿50*ÔìN¾*öõÊ4*ðò:KDQìOÓàï°±ôÚG¼ù-*Ì/U,ÌëÙµûÄ:ÖîL+Ú´I0ÀÞ³íÜ +E¿*KòÞ¿,Ú;*ÄÝÛ±ä×á+½,*ö¿PìÕ1Ýç¾6;*VÛÇ*,ÎßïJ¾E7¶V/ÎùÏ8ÚZ*é6×°U»ÒTúXôãÊ×ÕQI*Þ3 +=KYSFøò3ǹë--T+úÏÀìÝHKÍü+¶»ºîJ+ì6*ÕåÌ÷ðêõºI+*·ïõ>¾AÄѾ1ÑÜ»==28.0FR*1îC=/4F¿ô +ë*òáJèË@ÚZ*ZóP,2¾XÂ,+¾õ-½î***¶ðÓµOÅƵٷÌÙ:¸J:̸Ä1Ô÷ïâÔøRÓ²IêÈCII½<ÞÉçáü=0 +óê;¾îÁ+Ó**KúÄ0ÆÌûÎV×--8>Þ7+î÷åCUÄûÌô=*îE¿6ÁUÙôIõ¶³ÌM²é¼:öÆÞ¿,¶*ȸ·Ü65=ë0*Ùß/ +ì²ß/-¾Wµ<¾+¶´¿Ð´*ØYú¿-ÎGUÜéLPÞ@öæGß-ÜäØ´*Àò¼Ä1²×¯âËÒTÌî³ãÉ͵ÕXGSYHL64,ÎT»,ì +¿+J»?¾ZJ¹>ÞÒEA2äË,Ú;*ìüú°ãÕíÛ¼-*øCL=ñXIB0ÁÒYõ+Ó¶ÂÞ¿,¶*3@ݼ7×Àé0¾óé*8³ß¿,¾Xµ< +¾+Ú8ç,±*ÕYú¿-¶»;ÏÎÖäZ?ºØHßÁ;,×-ðGH¾â7ÓTH×°ðèËEµ¼=Æ3.îÔLCQÒW¶êä¿÷T*QOåðïÓËE9W +@Q2D¶Î*µ.¾Ó=ÕF÷ó¹ÑY+*4ÜZØ,Ï?+òCÛ?µ4Þ+R¶ºø²á4P-*K+åØZ0*¸ÛǾÎ+,>5¾=½î*Pé?ïéO³é +Æ0ç4ìî¿Ë0ÞÈÒ¹ºæRÓðÖ?U@8ûöïáã:Æ¿*Ì@ÀéÆÝ6R¯F6Y/îÔMSR¾áöïA.,¶K-14îÄ*B¸ïÊAå¼Aø3* +¶È¼EŲ;D=DüôñÞÏMõ»5KÆÞ¿,¶*Ų?X6éÏæ*JRÞË@ÚZ*æóP,À0Vì/*LÝö-QZ¶ò5A@1ûJ<üÂ1BD*ã +M2Há-ÁÚö¯UÛ²ÓR÷ðñëÃå7*꺶äȯõùú.*ºÈ0,¸Öúº¶<ÍÎJÄåÍUFZ@NîJ+F*ÞÂ:N¶>ö±*:>îP5XB* +ò¸=+M-Â8¾åAüÞGOö6ÇÎåÊA2¿Í9ïK2+WóZó?HòÑT¯ÃÕ=UPX»öù***4*<Ö+XHú°ãÕ3¸Ü1*¾XSÞ*²Ì7 +9øSÓòMêÈ?5ÅH¿FOÑ,êÚ¶ãÇYU.û,*8ú5*¶à+CH*ò¸ç1÷ü+NìPÓÐA±YÜFÚ÷âUÃÏAÕVCôÅÐõ+*ÎéDö¾ +@EÄÚÇTÿðôÊ·öðâñ0*Ü8ûðã×YÌÜ1*¾XéÞ-ßú¶AÓ4Q/Xø±ïäÍå7*ÊܺäÌ·-W»9**ÛÔ¿1ÐCðXT??5ú +³çßÏQÜTFIüæпQ³»9**ÛÔ¿1ô×°âÞøRÓBZëÜWù0ܹì*ÞðóÝÄ7ÚVêù**æ³7ºZÐE±ìØÅT/KÝôËÙôò+J +¹Iõ»Eîý½***ÓºT/üõÆõ³GÁºýÓøüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûúÕ4*Fó**ûÂ,,,XðÞÑ×èÙ¶¸-,ý¿Ò@ +¯Ó8Jß0ÖÖ1ØÇG°Õ.@¹*+*Æû5/4êË.¶ô*¶ÅØZßÒìÀG¾3,áX7À-9OÓ*Ó5*¶ÎTÜìò,êQPàXÙ.×HF:JÞH +IÅFß¿6¶RãºíüO4¹*=.Âû+æõôÎË9îìÈ +Î*ÏDîL>Ä*ìÂá/@²íT·Rä¼ùP04ù,ÀDÛUÓð1,Aå5¾µôE»3Cê×ÙØñ·¸ÎûÆ0*Ò-Y¶-12*ùðëNÛBÀÙÓT¾ +25:/¶Ý2/U¾E¹*A,ÏÍUãµµÈZK>ìêYÇ01YÓÈ9ú*àAJ¹Ö5õ3<°åìEÓ//JÅMD¶ï/µî*+ÎƾÇô5M¶ÙT/2 +¯+LéÀ5Ï*ØàRÍÞßÓAòéTWå,<²;Öü@,øå+¸XÓ¸63øNÓøó=ÜýT»ýUûüÕøûYóáÜTùÜH¼ûÛ0*.½ûõÛÓTì +GºøóûÝýüÚ:9Ü×*Þ»ñüËùõîêÛõÓT÷òßÁý¼ìܽûÒÜûøË/*8H·êÓ±åÌëEJíTÓðÚYé¼,,Ð-.QýÛùìÄù@@ËÌÜÓÛÏTãYÀ@ÚæZëéÙ-öÅ*¾×Ð*¸ºùíÆÅ,*ùU@Jîß-¶Ä ++Nß°.1:J+ÆÖ7HºBõ´:Á/ÚGÀE:ÆX9ä*ºùóØ1A<6øãÔT?Õæ·ZÚÀ±EÅ8ÒI,*Sáñ/.¶*ó@ºÜÑ*ÆÕXÛøA +4>ÎÑ8¾**îÀ-P*æÀÊ°X>¿é°RÛWY*ÒUYN¾è+:âîè/BRó¾Á²*JûÏCÂ;**Ñ-DRPóè=·GÍ**ìâÀU1JEÒ/ +5Në¼ï1>7À-ÕLð0,F¸ñ×/á7ê×àÔT?9æ·îM¿W1I8ÈI,*¿Zù/.¶*î¾5è1Fç<ܺ÷âÞ/QRÜÚ*RüÀ-¶÷ðà +AXR<÷Ã<æÒ°Èýø4Þ7<ζË5F+-Ï=ð×CÂIÆð*P1òMMãS-KHFî27À*Ò=¯ÕUõ,óà9â+ãÕJðR,æöîÔÁUÂñ +¾WÓTü̱޴+ÑôëEú¼.*,Kó52Z*¼ýÄ:6S-îìWòF°¿+*Þ/1.7îSåÜÐæFöÄT,ìëÀYAÜ+ú¼À.Þ¹FßÃ/2. +ÖÍËߺ,*BG¾ÐCÆ0Rë¶Þ;D.+æ.ѽýüVß¿:Öí¼R³¼?<µ:Þ»ùP7ÖWÞÌí³ÓT·´Ì+Mø¹DµìÎù;*2B+/MÁWêÞ7ÝÇ/Í7JÐXËCíÈòL22¯Ý28¿â7.Þëׯ +ÉôEÔ4³ÓT÷VÎ/EøEø³êå*Þ·ÔϾJ/IÖ¯¹*Ö¯YìNK¿78ùG19Xë¯-ÙÆÐî¾-,*öíJ1@ÚׯíRJ>K.¾*¾û +,3À-*¾=ZZ>Aº¹îÀ:XNß++Îå2ø?12S4ìâÀá0*×;.4>O¼;.Òà+³åÞîP*öóæ½730GUUÓȼDëöXÞÎíÏT +<6¶ÆYׯ*ÓÔÖOHI¶-4·äÀÄ8ºÕïçÍØTÓÚ;èÊù20û¸´È7ùó/***¶,*ÕµÞ<2*àò0VïH/FôêÖQAìÁ·³ZÒ +ÕÔÈ;ÉÄÊÖñè×E6*úôëÔ?U@GCÐÔT?ý@?ó@ÈãƼà²=ÇÛNY/¾5÷ëÁ´AÏUUOÖðèìRÓð¶ASP»ø±æÑç³ëÚ¹ +5*úµôâ·éôÊÇK×ɹMÓȱݶRçâÓWÕ@ÓWõ@*öêÜ»ù8˳ϱҳ¯ïGÓTÆ»W¯´ÒòD*ZDÏ4¸ëéø÷ÔT?ÜÜúóà +¹ïÜë±*ζõ@¼ÛúøÔT?ã¼G¸ïÚ³õD*:ìàÏí=Ó@ÒT½á,ÜýT»ýUûüÕøûYóùÝèõÐTI/*@ÎTîýU+¾UU¸ØT% +d +342 151[1 0 0 1 0 0]sl 8 mask 0 0 di +/mask 6493 string uc +*JÌ**-ܽ?ºý-úü1öû9îùIÞõ½¾íý*Ýý+¼ý-úü1öû9îùIÞ庾% +d +/sl 51642 string uc +äÕí´ê9+*ú½³+íÄ,*ÓU5Zëúø³óûÕèùÝèõ½ÓíýTÝýÓ¼7U/*ì:ÞË0ÚØ+*û²4B¹Õ¼F2ÎJMT33µ¶R3G½Ó +1Uó¼1ÌO+UOßñõòÙ5Q?;»TTE×T-ÎZ-V-+*ÞF¾1ØÇG¹OZ×-2*îÏ÷>*<@¹ëÊÂ,4?/Ð0æ-´ÎÚMWÖZTõ= +³B/¶*/Æ»±D.@6F2ô*ì3+SÁîJ±Õ4θ+*ÕTÃèËXÎT³8JQPàX¹ÆóÑ+ÌGϾäÓ@ßXȶUüJ4.,ï1*KK?ñ» +ùASQèZ4î*46ùJòÈÝ*ùL:1JݹÅÔ¸¾¹UȲÓÞý-*¶ÓTÁRñRñ*æ¹-í2áÓ02ô2àGW¾ò7ºÝÆ,¿Jú+JJ/Ý +×±ÝGRøJ7P+8J+¯Å:ú3RÚ=,<>5ôDÆ0-¾GÒèÇ.¾-Kɾº?óÃTÓ-ÖN=áJ¼Î+À¿ëS,UJFùóÓT°På-´*5À +ÊJõ:ÑM¶¶*/»ÀìQT/@F=,¸¼:ÊUüæT4ÖÉTSIð¸=åAäçTÏËOâÂ7¶*ê5î´³åñòTEùD2·éTÓðQKÎâûFó +Òê?>±Ý»=-óP7L+8J¿¯Y.Ì÷¶?U¾E¹*õ¾V½ÌÎëU/CýÇÞ*͸ç±ÓLà=N6Eî*ÖéØÞEÓ//²1ô»½¶¾ÖTã× +YÓ±K¿5ÂÖáëËÞ+=*ôÓ-Ï0QÌïÀ2ÆD.1D°ÓNØù¯ÓQTOòQ¹ÏTæÕ2ôÅÚ?D·RãºíüOTÖ¿4 +GÇ4è/øEE**82*ÓEÛJ+ßF¾ÌÊÂÛô:éQ-F¹L+ËUWÀÛ>ïÔú:K.*0LÕF¹ô12ïâ6P+8B+ZYGR̶Q.ð*ò´J +ÓëÒÈò°IÔ¸R³VOâõ¸ÏTØÊHôJ¯RIPð4¼±å¼ùÀÊɲêZ¶ø0ø¹´îLÓ0Ï02ä+Îõ0Ü;ÄU7:ê;ºØ*çEöXN2 ++:2À1,FJÂ=±B/¶Þ.ÀGÀç¿L¿¹LJÀ°ßìÆÓ°ÐèùýÔL³2JÎɸÎTàºÂߺãÈG*ô*@Âôöì5JÑÎPóGºÐáøß+ +-¾û¸*Ñ*++-.ó*@çâBÆ9²¾ZZ¹Ä¾42å¼Æ:,J<ò8.*54;ìÊK>Þ+=*ôÓ-?,1Ìïà1æ÷¿ZD°Óê;öü+Ôü1U +;ýÚ9GR?+¸P:¾âD³ÊâKCùëECRê³J7ãíÄBÓ¶Þ4ÎP1ÞçTÏËOâÒ8÷,ÚWë÷FÛ´ôÔ0LìØÀ/L*²;¶ä¹±1ó* +;X×ß+=*ôÓ-Ï0µÌïÞ1Æóå3D°ÓÖÍðçÓÍÝ8»>>TQÜEµI1TO4×6.J¹;ú8²ðÙEOÖ:4µôÐî<ôæEÎVFJÕDF +ºÙTNºÀÂ;-8*¹W5ºÑØξôÞÜõ1129Ò4¼0Á¾/ôA2ïÈ78B+ZYG:ê³ÃÏÜ=,¸+KÛÄV?µ=EµìÝÇIYÌÉMåÌG +¸ò´8Òè<2:¸@ù*VJ7¸SÓNÝAå**XOÔT*J+JáBRíÒȯQËòàM*:NSõ±AW¸ÓR·èÊ9+¾ÎXüMÓèõƾõÝTíý +TÝýÓ¼ýUë*Ó<îáI-**ýMÓ4ßìíý;*2üÖ½ÒT×àùûA¼,*äÝýàãIÒTßØúûåèó,*ʽý³S½RÓàM÷ùA@J*î+ +*DJ*ýüÅ°üÎT/KðõýW¾+ÓK*ç9ðÇT;ê/+á¿DÒZ-VA4¾·ÅØZßÒìê9:æ¿5öä²2VÞHDÍ,,Nê<-Ò0ß+ +ôêY¶¾-ZÅKÎËê×U,ZM58DßÇMÒJ.>2**8²àÏMÚð¾æ+ÍLA¹QÊÖ5ìñâ7ÂG0K4L°ÔÌT?Çô¼VóÇT3ûçXÒ4 +ø?Àû;µ+FÒìò,A4¾K?ñ»ùASêYÒTÛ¾1LÕF¹ôÂ,DATÉÒC¾-áóAFÞ+ι¼°<ôØÁ¾óÏ/1Ëîâ;T:äÎX²àÏM +Fñ*1¾»Ö¿æÇÒÎX.ø¹ð0Êì·ô*L=çÛÑTO¸×9UGÒDÔ<ð¹ÛR?YP9¹ÎBÒ±1ZTòJ+ÒÀíê×íÜÎF:.ÎXJ:.9S +*2à=O¾ò±0NR@ê¯,1F:¸*»µî7¯Úç,¾îÌ3ÌFL/àÃTË/F¸ÂпEÅîCAÃ70QíJ½9ûÁ¯1*æå»Ðè´TãNÓõý +WÃè:¾JAóJÓÂ÷2¿D2*Uô*ÖI¯>ß¼KÒ0òHÓ+éíÎ*NB¯>6êHÇ.º¼:6ôêY¶Jò*°Í²°6âñ.1Ë*@F·ÂDÀMÝ +¶ò2åFÞ+Z1ËÀDÓõîÇ8éíúMξ.áÓÓTã8¿õ9Zó½Ö4÷åûæ¿T3α,+NÔSåTãKCùëECRê52èòìξA,庵ô +Ôôº@ç³ADJéÍ=ZåüÀ-L16ùËOâÒ5G²,*óÞ;8.L.¾>ÞûÞ,8GàC1Â8R7DÈÝ7¯èÓEJô*ÆIS×ÑTãû>ÍXûú +óæÕ±ôêÓ=íËÙÍ-Ó<ÃV0,ÎÛ2¼ÝT?ÝÛSÀ/϶±½ODFºÁ,üC6,@ÖùP³¿>æÏæ¾XJRAìý¾-0½Æö.3éÆ8Dßà +MÚS?ÎóàW9GàC1Â8RSÉÖMí¾ðÓEÂ8:ß:0µÒT·E×·ñüËú÷ðÚF¸ôêÏMµñJÓÔË*ï·×.JLGæMÓ<ûÜF-*¶³ +ÕÓ,*4*ÔØ6WÏTÄG³Ð59+¾:OÛÏÜÑåTÃÈÑäú2*.óðITÓ¼Å.ºýÓøüÕøûYóùÝè0R?+IüC**öITÓ2ôüü9+ +¾îÅûMÓÈ?Üí½ï=*ZüûAXüKÓ4Ôãí½ÔXA*ÞúùYÉøÁT?HÈݽ¯+,*9*Î+,öõ½>ñNÔKÖÛæ?ö×QÄVPÓ:Nüü +Ý/BÎD.:ûKÜR?óèÅ-+4²FÚ0ñ¾C¹*÷Zâß8:*êKå¯-çZJÒK0V¶ä,¿;·-J<×ÖÙT¿¾ÏT=T?UÀ½Þ-VßTSö +úGûôàõS-Îå×Bå.=çTOá¿PÚÎ+*Zðî,Zè>Æî:8°CÔãÒÈN¹IÔøNÓÂü²1TO:2½¾-8TCíÙ½Çà +8*ÓEÛÞ/J·*ò̱1*¯²*Ò:²-åTó,244òâNê³1×¼DÞ×=4æÚè<êO3µã13L*R:¸ãÍÐà>Þ+=*ôÓ-ã3õÂ*8 +ÂÚIÖ0ɳELEAÔ**Ú¯.Kõú/3Â*Öåå;Q¶ð2Ó*QØZðî,Zè>.ÍÈAL°ÁÓ<ÛêÅÓ8T7U3÷Û8Ô4TöËGùëïűN +¾û+ÈÚÇÒ¹Ó¸+.//æ¹-í2áGÖ,¾:¼æ,NíÞ¹;ßÚ,.½É4Ñ*1ÊJÖMBGR>¶Q.NR@êATãÔ±AÜÞÃ?**µ6;Tï¾ +.>¹ôè×èÉ´HÎ8Ï4îN5P8X3ÃZÍîâ÷´ÖTPT»ýíTÒ0+.¸S1TOäòº¾-ZKâ/òX·ÇNÓÞøÁG2,ZÇPÄ¿úJ-.- +Ù±.Ò¾¶ôÁ.;*I¼ÍÓ +°ÅÞù1ÖøIVÃúñ¼òJÓúS2>·IQG?¹/ãêµíâJOÑTÒ*³¸µUô@;ÑÈÞ5î´³åñòØ×ÙD2·é¸ZBÔ2FñÛYÑà+= +*ôÓ-Ï0ÀG¾¹õJÔIQ¶ÐTÒºESAÔ**Ú°Â5<îê×I2ÚFY*Ü9>á¿PÚç,TÜËÒÈÃ5ÑJZÌØÙåT·IãÑUI½Gøóïå +ĺµì¼8ÒȱÝTåÀ,A2¯ÅÙEOÖ:èVN8ÓLÕDFºÙTNºÀÂÓ.Xé,íÒ@>æ¿Ûóûß+à-Á¯Íß:ZïD?8B+ZYG¾ê³W +Þ8ÞG;Î52èÛR?M;+=ëAÔ**Ú°Â5<îê×9ú*àAJ¹8>á¿PFçÀÇÁöÆEL¯LEæÁZËÓð´°ñäûE÷ñã··òê×MÅì +äÃTWR+Àè°9¾Fè*Èí+LXÚòÓVÎòAßV±ÓNÝAå**XëUÓ@´3*J+Þ,òî>.ÊÀ+MìX±Ó6¹èNAH,*Kȸ=ÓèÐÑ +¯EÓLãS±¼.*¾ø<Èû¯ÒèõƾõÝTíýTÝýÓ¼ýUë*Ó<îáI-**ýMÓ4ßìíý;*2üÖ½ÒT×àùûA¼,*äÝýàãIÒTß +Øúûåèó,*ʽý³S½RÓàM÷ùA@J*î+*DJ*ýüÅ°üÎT/KðõýW¾+ÓK*ç9ðÇT;ê/<Ä,**;*÷*ÞK9:*êKå¯-ç +ZJÒK0V¶ô3õíîÐEìQàã2Fî¾²°¯CÓ,+MÓPÒTÔ.ý¾QTÃ2È*=ÛTO-BÇèKÞ+=*ôÓ-Ï0QÌïÀ2ÆD.18æ1*¶ð,å=ÏìJ꯶L,1ÜîJ:Xö,Z½+;AFî¾?6*ô<±ÛÈ/.¾ +ÎÁ:¾ÕÔT<ëûÖè=Ó<øÑÛR?òU.ùMì4ËFҰܵýÎ/ÆÜ¿õL +À2*¿:º:Þ+LFCÁS¾-P:ê;Ê8>4Ú=,<>5ô=Ó¸ï,3NL3Þ33FÇÂUÜ:ê×I¾Æ58¶Þ4.*ÎÍå°;>+ÂÅÎWVÓÊÒ +øüÝÓR7,2òÐ8ÒÈB9¯B9Ö8=¿/¯9Üù,æ>ÒS»1ÍòÖüJXÞøEÏ3JR1ZÇPÄ¿úJ-.-Ù±.Ò¾¶ôÁ.;*:ßòGVR +¶±0Ï*1Ê*ÖÕ8Ò2F¶Ä5IJå1À·ÐTAò:ÇNÚ<¿M>¶ß5ÀGù-2·ß+1V¿*.áóV@à5Nµ1AØZQÓ°ÅÞù1ÖøIVÃú +ñ¼òJÓ.æC¿*<éÒ¯<1ãêµíâJ¯Õ+³>½EàÒà+T¾DÛÙÓLEÉÆSSï/¶ÙØñ·¸ëêW7ÂÚ³GR?W.Ì÷ìêKGJÂU´¶ +Þ.¾»óJÒß˱Fö+°û·À5>,ÚYæßûNÊE*ôÕNîJ<Ä0XöÀZåü*LX¶Þ+UB*âÍÒº?52*ùðëNWWÓÖÍðçÓÍÝ8» +>>TQÜEµI1TO4×6.J¹;ú8²ðÙEOÖ:4µôÐî<ôæEÎVFJÕDFºÙTNºÀÂÓTXé,íÒ@>æ¿Ûóûß+à-ÓÀÍß:Z¿û +/LÚ°Á-P*êU12¹W5ìFá¿9Î52èGæ3ϳßÃ5ÚÍ·ÁRúE<¶:3ÕºKÖ±½*;AFî¾?:*ôÀK6¼>50*Ã.Ê4ÞÔÓT +ìP´ìÚ½=½ÜFAÅÌG¹òæëFRÓO:JòVô+ÆN7<øÂáGÆòIÌNß+üëVÓNÝAå**XÛU?*î¸Fø´.ôÕTÄG³Ð59+¾: +OÛÙ9?A:êPÓR·èÊ9+¾ÎXüMÓèõƾõÝTíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýT9ØßùÝè¼V?/**¾+D¾Pç8*JÈ +Rö4T/×±9ìÐÄêà+ÎýFÓðêÅEÞüÃ,/ô:¹ôè:F28R/îSò°õÂ3¹ß»FõR5@ÊèËG*³Å¿´P4RÂ@²±VWUÓú³° +¾Ö½¼8·,áÌû+æM,Ó*HêJòNTRAæÍÁTäùôYÐÖ-¾òÌÉðÔú²ßÁ×T1*Þ-QÓÓ±ÓÅÑ+ÞÌÛÎVÖÁ.T+ºäO¯Áßç +EæYÔîE¹ôÙAZ9F:7ºÅ@ºCÓT/ZËä3¼G<¾Ì9.=BÎ4ÞKÑÎPóGºGÔÁ/ÏTιÓÈ?:ÌûøÅÑTüJîðº+öäìØºÌ +:BîJ+Ú07ãíÄBÃG>²ôÙµ°ÎÄ3V7ÂÚ³»FÓð-I¾ö/æ8ôA.À¸¿+14ÞOWà8»-ÈêµáP3@Âà»Tãøüî*¯BJEá +èÌTóYºÞXU/*Ú*¾>ÝîßÒè-ÙèõÝTíýTÝýÓôßT+Tý**ì´æÈT/üý-*öüUÓ/5ïCX? +d +342 151[1 0 0 1 0 0]sl 8 mask 0 151 di +/mask 6493 string uc +*JÌ**-ܽ?ºý-úü1öû9îùIÞõ½¾íý*Ýý+¼ý-úü1öû9îùIÞ庾% +d +/sl 51642 string uc +äÕí´ê9+*ú½³+¾èC0F*¿½**µEò6-XW¾³ñéÕÓõÝTë<8íýTÙZ;ÜýÓ´ßLºýUëÀÃöüÕØ/5ïíXóMJÓ?Ôèá +î¾õÝTíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýT9ØßÑTÓÍ,*5R?öýÓ*ÞÓ?OØTíÄ,ÜýT»ýUûüÕøûYóùÝèõ½Óíý +TÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕ +øûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèÔÛTJó/+JHÓøÌ>îûÕèùÝèõ½ÓíýTAÒT¾è5,¾ºT/ü½K*ÆQÓÜ=/ +ºýÓøüÕøûYó-U?ÜV>HQ¹ôæÇ4*öÌG¹Fß=¸·IROÑÓ4æ6X4,ÆXÓÄ*¾ä´ÖÈWÒÈ»Y9ÝüÛ**ßYý»À++ðX²S +ÈÓ<¹âGØ-ÉÜú¶ïç-*7YHûÔÞñÖ¸XÕȹ1ͼ»ýõÝIAâÁY+*ÚµìÝ·ý8ÌÛùòR/RÄ=;²¯YQöAò¸V²ß/¾ðÇG +¶ñÝ¿1YXÚõëLEô²+¾YÙ¼ëDôìäA.H3RåïT÷òßÁý¼ìܽûÒÜûøU**1ÍFôè×±åô7ÎQÄT*·>3ÖVÄÉ5Z8ú/ +Ö6Z*Ã9ê¿MÅ7¹ôëßÉ5Íö¿úµëÓOÕXÛD´Rü0ºá.ô*H*ü»óFX?åûØôé×ãñíÍôíͽ/*îõå9I7ØóêÕçóðé +ÑT?ÑçÅͳ1T=AÞÚö5ñäÅ9Ë1ô޵Ѵ˸óíâÇÁï÷,¾51XE²åÑUÑ0ÔÍîâHÞ=+óFX?í6ÙóåÏÝéåYøñÝ.*ö +ûïµ±ìúD´ãZøÓ±Q:+¼WÏL¯YåXûDÃåí¼T*îóËÕüʸôé¯SÏLé8ê¿UP»¸ñåÝÅA1âñ/*Y¼úµéÇAÍP7:Iß +Çþå»óFX?ýùøñáÌÓÙÕYòíÝ-*¶ûøç1ÕúöñéòµîíSÓÈÁèÒ3ïéÒCÁ@ëñQä4XÜõãÉSÝPÚDz,óÞ8¶YèË5 +±äWºàT7,ÞÞ¯IXÚCóÜQ¶°9î;øóIÕ8æôôTGëèÈ/U´X¼»¾IÅø4·B°°¯ÅÝÜúîÆ/UXËæäÛGÓTßTÓTëÃG¶ +NÐÑÍ5ZFü÷ß=Aì6ùì:Á-¹î°OIüùÖ´îß¿öÂ+ÞÁýÄÛöíáÐãHßÇâÞ=µóFX?½T¸ïÜÄ»¹ýXæͽ.*4*¾+Hú +ýÐ+ð¿òUD2F:ñWÝù*I½Hûõì¹Eì55ɼøÔôÒTã¼ûø³óî2/1Yý;*JÌO4>çC8J+æÕ9ØÇìQTÞàÁý붲2V +7°¶Z1Üòñß¹3åôûE.½0*âEïèõH¾À1Qúð-úµíÖ3íGú·ç=F·8H¸@:1,>ê¶çDõä=9óFX?½3ØîÚÁ³Yí8Þ +Ý,*/*H¸ÛK.Hè¾¹***ÌÀ¾Á1Ýû¸ëÁñ4@ÎYPÔTHWزEK9ÔÉBëäÌü7*öÏJ*¹ýù-SøÈH*øÞÏ.¼á,ÜGùë +ó80»±Æß¿GöÈÌ+Ý7ê¸õìîùC*BèÄM±½1,òEïý1IÞ¹õ´GöWÞÌUH·ÎéîßVS*GÜûµâQWñêºðÏÌáÓð4G1Ä +U¶´ñæ¿øU***à*úT<èIíS*âUÕ8***èÝRÎ*æ?56+62β28Úø,Å캴߶-¹ÔB×T׸óè³Vî*å9ÇÃÎçD2J +ÖÔ3¹Kçíúºâ,<û÷L/æBâîÞ8FLä¼ëü³»¹õ¹*îÇ¿A0ݹµàÐǯâ×Í>¾ÔPÛ¹õäÕ93+í´ûÕWÝÊ;HßÇéî;³ +ºÜ¾¾5åï.CÚÐXG±D.ÞÀ5,T:Èø8Õ4/òF¶.:/IÜú?***¯*ÜE¿ðÀø+QØÂ>YHDÞ<-AFK×J3J:¾FÎúê5â +öîß¿>Öñ¾ÇÓ4.6*¶*8ÅPM829.:ß;ÂÛ2TíXSå3T.*FÖ-ýÅ×íؼQåÔ7´Ý,*µ7C¼åÞÁíP6³IYHØö5/Þç +SIÍ7C»ðQÂÞ³åHëµWÝÊýGßÇéî;2´Ý¾¾5åïïDARÜú9¶ÝO÷ðÅ.>7óFX?½åCZÙ¾Sùôû6Ë+¾B*±Wßµ.ÏÐ +<*õæ*éWòF°¯,Q.:2¾úÎèúè5ÚµíÒ+Ù/ê÷ÛâTãB°âæì+¶¾ïµ*J:ê8.îØ=6I8ÞÙ@Ö9×Þ/T*¹K0AÌéCõ +OÙµ²êÖ±Ý,*íº¸¼çÈÙåP6ýÁÝÛÖû5/Îù7Á1GDüELJÖ¯Å8µ:ÊéGßÇéî;2TI¶¾=å+ÜŶ.,è=-Q.>9óFX +?Þ@ÆßüÌäËùCU-*¯*¶>Ëì5ÒÜæ*Á²ËWã¯,.JµìÓ*¿*>ÙHAÕÜÞ¶ñHûµR-ZÉVÓVäÊCÓ?¶¾ëÅPMà*7.:L +áÊÕÇSº8Û¾îÜÛR+Ò/CFÖ-ÝÄ·í׺KÝÄû6*¯à+*¼G´àý¯Ù´êýÇ,Jäå=EÝüæ2ö³æºåôãºë3ÌÚ09+Mð¿ +-Ú·Ì;8¿î·ÂÞ7+ãMGÅU/Â/°JÝЯåÜûT*G¾êÔÓ,CCù=±áÕóâÑ+ìJêçW+ZÌÛ*3Ô0Þ¶¾<ÎFE¯Ý28·+ìÌ +7Ù²ÝK7JV°Ó°SÑPûó*F8?ÜÙêÏó³ÔHPìÔÙ+ê¼WÅÑEÞ½¿4îUÆß¿G¾Z¯Ó¸õñèÔYÙ¼-*8¸ðûýýéÌGÚ<üÝ +D±äúÍ>*ÀÔê÷»ëQÂ*YÙXû÷ìÖT2ô.QXÁ1åú*ÏðèÀµH·ÌL8ÊûÅCèÉ93RHè·ÚTD5²ß¶ýļB¾8**+J6àÄ +»×³ÐÎÞÄæ*ÅûÌÁ1ÕôÖ¿ëHWÖ1Þ¶¾ÈÐ*¹Ã-èÌ¿+Ý8ûAXßαOÔT8AXÚÔJ9ÄùA¾¼Ô1BÏ1,J:8¹JêÖCE3ô +æ/²²Þ¶1ÁX?âîÞ8ú?å½ëðûʸóU*îÈYÙYÛزáì?¯ãÃ?M1ýL+îýèÄÑͽÃ.Êø±Ü´ý¸¼ó.QÌÁ1ñVM+¿4¼ +ZݶÌL8ÃëµÉ+>ZÝ;κÒñ¶ÓîÑçÄõ,¼ÅFÇWÞKÇ>*ßÞ¹7Íü>×T÷áÅ9UTG÷íÞ׿MÒ:8üE·³OK01ôJG¿HF +µYååRãTÕHͺ:¾ùìÍöýûûáá¼Î*Øø½èÅ÷5½Ä.ø×íÚÁK¹¸À=çÁå×/ÚÔÌáÓðÄR±8VBòé×S´êÕÍ***¶+* +µJ:=..*ççÃ-±XëR°ÓðÒQÍÔÒCòàÁã±éÕZúL*XôÃéÔÛ¸ðáÎGULÑQѼ;?HذݽGÑ´ëØÒPû.**ï-*JÒ3 +ýY7R/¾ÒS±ìFý;;öèÔUÁHúÕ°çÞæ3Ìê-º<ÎÌéUãÆOMPê·óêé**ݻٳãÈOQÄ>×T·ðáÅM5Éø±âÐ÷òÚPæ +»ýÌû÷±è¯ÏÏïáÇÝÖT¯ú·¾ÄÆ´û6*Þý-*:ÔýIæ¾5?:é×I4,½7ùóçÅ9½¼68Û×軾å»óFX?ý@?óæÈCɼW +²éÜ-T·ìÝ¿é0G¸³Ó,UìÚTãáÙµÝõ<ãÒQÑåηQ/*üE¹ðÚ³¹äÒA×µIü¿GîµQM½¼F÷ñÞè²-Þ¯åÔëØñåÐÏ +ÀUQ¶,ÜW-º<ÎÌáÓ,¶9:1˼W¹XåÇ4¾ÆßÛÚÑT?1íݽý<1?Ë6TQäKêÙøöÏ×@ùóæÃñÔ»A*JÚÃMÍ8Ó> +¹ôîéáåIßRCüöéÁÝÎÒFâÇÓ¾åSóÚYÓÝÒ+í½ÓÜýÓ¼ýUûüÕøûYóùÝèQ¹Ó*ñ+*¾ËÝ3*8NTWÓ4?ÈÒRÎIý= +*ÖÌFµäºÏT4=äËJÁ,ûÎ**»ìºçÐãØT5S= 20030907) +%%CreationDate: Sun Sep 21 21:22:29 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 9.305 2.791 ] [ 2.791 9.305 ] [ 2.791 2.791 ] [ 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 2.791 2.791 ] ] d +/pageinit { +35.4627 23.6418 translate +% 185*281mm (portrait) +0 795.224 translate 1.07463 -1.07463 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 367]ST +B P1 +NB +W BC +/mask 6493 string uc +î½*¾î1:-*¾*Ñ-õ0*ÞQK/,ÞÝÑT*ÞñÎ,öïì3ðZÀÉ8*1<·ÈáµÇâS̼*úÉâS̼¾ú+FQ?¾9ÇT¿º*BQ?¾- +×TKÌS:*0ºýTöü1öû9îùIÞõ½¾íý*Ýý+¼ý-úü1öû9îùIÞ½¼¾%% +d +/sl 51642 string uc +î½¼**ûÙ>*<òÌû@KÍÌè6JÑÎÈìQ¼Cîׯù<*VXßÖåYúÓÐïG,9öé@/*GE¹ýüú÷íö¿ñ×,ÈG´éX/*ú5¼½û +ùÕæTåX?D:ÃïEÙòùżODHRÄ-2G°,+ìïÞO0ÎÞößQá-ëZ-0¼½ÑOY.å³øö +ñòéé½YHºõêGùòåËE½H¯4ÞÚùù¹ÜATóâöûëêäßíÅ9ܸºÜF¶ê³YV×ÇíÍ?æ±**B-*ú?XËúÞÂ÷Ð3:C@îJ +/²ìðöûñ½ý84À*ÒúýîJûýý²/X½Jæ8ïí¼öç3Ê,2ÐWY¹+äóܽ½ûQÀîÔ6ðE*êÛIWÍQñJ9½GÛ¸÷ûñݽíé +Éåüº·ðÕÃPÊ,Ô¹ýüùÀT+ö5Aô»¹M19ýüøì;·ïâµé»¯,´½½ÎHÍ+*Ö6*î;òLÒæÚ<êä,4/ÚXýé÷SD³9Ð +¹SîH»øýIÆL;*ÎÉ?¶ë+úý½JÛ¶ýíøò.T+JâáXÎòý½Á.WÏSÁÉBÆìD8>*Èì9BPÐ/FÁH*Þ*Þ?øöàãÍY½ +ÝÝüú8½ûõâÉ9YÊ°à>î°ùõµFXýüÁ¿æ,*½TB¶ñííéÅÍåUõGú¶ßM9Î*óí½ñG**È-*Èë7-:GGT4R>*ç-O +GàÊ<+0ÛWݼÝ7òäËýýµÔ9XÅ,öý¾²üý½±éöν>J¶Þ2J6*IÕÇ>·üïãÜLÃö;Ú3ó½ì÷8Ú¾G¼íýüD+3º¼2 +,ú½Â4ôBüöWÅ5Òüý4CàÂBJÞ¿è½»ñPõúñÁâ1VÏÀ¯ðïÂF<3*NÙý´ÇHÍÎQÏÈ**î2»ÒêïÞÅÉÅIÔÂë½1üù +¶×6ÍϾ**î>/.**=/,*¾E>îîùÍý?½üÓI5*ºQçâýY9ý»Yí»¶ìá;Ýê×RJøõIñ½²*Jð*¾ÀMLW̯8É,4 +/êüýÄ.Ì-¶ëý¯LúÝöNÊøýý¼ýï950*¾ä*æ¶ÙËL+P,8¾¾Ñ±óÙöZų=ºÂ4á-Pñ½ûÎ8Ú*ìýÝÉÒK¯,Ô4üÌ +ÉL6ÞìEÒ¾9³üQ7¯¶¿PôýýùàCÍ>-X7ôIüµã4?6³,Aâ;3*Þâûó°Ò÷.»B**öíÕ1*Þ;øöÌá±ñôÜÜûùì¼» +õÚ±9íCï?46½ÛN¿2+¶IR/*ÚK+ìÛ½½Uóéñº**âÏõîAè@ìÄXU²×RÞôõ½×ðC**Æ+*ܯHMëîû÷ÏïVJϾç +ýIçâ51¾ûüÕýÓ°÷06D0B-Jø»Ó5/¶¾,/,àíü:RÈYTãÖ*öÝüZXÞ²üYYæ¿5JôýSíòBPÞ:ù´âY²ß?TÖ¸ý +ñÕDYÎ3õìý½Cã,/EòñðçAÔ¯ÖîLHXÆM<*ZËùÅÃÖ¾=æá¼±IÝü²3æ¼ÁHÜ;¶è×ÕÉIÍÜÍA½»´èÞEHºÂ,1¸ +>=4*ÌÏT>¼úÊ1*Þü/ý»ù/TKëÞÁýáÖRJøõY<»Á*ί*¾Ù;ÑÏLUÈB÷C5R>JàýíU·/÷MÕêýøÂNHî˯¾C* +Ø9Sâ57¶À,DÝXJ¾*74Z20BµB=ZZýµPòÞ/îäâýÇI6=JZï½EC¶Ã4ôðý÷À4FÎÞºýïù¸*4Nû½ÐÕÔï,CXÆ +M<*¾ÄùÝW¶ð3ø:òîãÅ÷*òÅæáýû@ëDøõîá¹öíÏÙ@ûùIÀµRî.GíϾJý¼NîT4¶PúöW·õìð+*Þ·Ú¿åHÜæ +JÏñ0XøÙÀÃ,ô¼ýÈ9Í+¾YÚ¶;O=3AXÂçP/øàRZJϾEÃíGSÉ;¹äóÕý=VùÝXL99êäBÞ6*O½Aö²*ëýÍí34 +Þèô/Ìòø4?úýýÚàÄõIXÓ-A¾OWüYÝSÊ*YøÌ·ßúýIÛL/Ùù½íLß/VÔäÆZýýHòJCÒüýàüýÕòâDXâá/·N, +*B¸ýÑ.MÜK9ÞHX.ÜÝL9A1ú²éåÙ¹ýìÛµ½ìø²îð.ÜK¿=Ó¸Å,ðºù;¿USÞ@å͵?»øÇ1*JéÛ7H¹µò+.êG¸ +íàí,5ÖRÞÊõYÈïC*æÂ3Ï3¯@¯*¾KÑÓ2Ͼ¾-?î;óTå½½»ïö¶¯ùõA±àÃï¿ë6-Ê+¶×óõüÅ92MçàÜå=>Î< +/ÑÝíûÛMÒîýýýÕíÙÜJ÷8Ú*ÈåIÜKL-16ô½Ç¿éýíö0?ZýûÑIç4XÞøùù½ý¸ÖP1È¿3ÝÕêD½Ý3YæàúÚ:Å +N**»õYSîá÷Ë-òKëÝÁ½ÄHÜV¶éÖÍYåÜÛ±ÝüF´éßÉûÉ3/8Né;4îÇíÝÙ°ìFâ-*îý2,íG¹öò*ºúõæÃ1YR +éÝL+ØÚIïCÒE-¾07ØEâZ+¾L+AÄ0Á0YÖ¾ùÙ?Ç:KÅL?òØ2;ØÓZà¾*Î;¾5-îKë/úíøÎÂ3SALãÌÎ*DÚà +ÆÎOÕT¯îYN×5ÛRAZÇ.OðÔÌJá+øô6;JAR;Øâ,3¸³î¶¿8<>RÏTûX>ÁDEÒ4@K×̺á++F;Å>,JEäíYXÞÄ +åæ=Ôø2»V@îÚ³I¯Sá7º@¼·Ï*ZIX»Á,6иóÔÂ5íö,*ÚBÑÞ»õPÌ»º0YG÷èËÀ3V=¾SùQÍ2ï?*ÖÀRà>º+ +*Öè/>»4HÙ¿6@JáíÝÍÙÕQ×KKLG2ÓÈñ4Ð<1?ÙIò´+CÜL9+*ÞCøÖPÞÁù¼,RPºöPøÐL¿2VöüT4¶áñåW÷ +ôWï/*¶ºÏÂü8¼º¸öʼ¸ÑÏ8WMö*/2öü°ÍBè6*7ò´î3*Þ¯¿,7Dôê0ÐÖ*ÂNÆÎRøÐÁÀ¸J*+Èò4<<1¼¶» +½Ìï,PVÝ·ÌH¼2¶îàÉMÍXÛYý¼üøðâ1ÛÉC/ÒLïàWãÌûNîÚKµÇÜ+ÃVú±äØ89LØìCº¾,Æ´½ø9-ôV*ZßûI +1UQîßFæáó*1Hú¶ñä͹òéÝÉíÜÛ̸CѾLòëÙ?éÔí6Ì1F149D°æÚ¿¼Bó41î34JèûÍQ.ÖY+¾ÝôíýYغ, +Yúä³ø2T°Ì+¾öCA±4æÀ×±ÙTÆéC¹ý.HK+Ó2º½ò>û³T*¶¾ÁùõACÊÚ¼Q¾ñF»RGWË8+ +¾æTA³4ÞèÓW·ôúÈïúÝ»ôâ¿=åìHöõè.BMìûù±8ÇYÁ,¾5ÂT»ýü<¹¯-öX/å¯øμñÜG,*CÕYè>¾ÎKÙÐV· +TÛñû4áíÝI¹âÄßéÔ.*Ø5÷óÆ6ô6ÍÕ³óôóÙO1áãÉWC´FAîß»55=íÝM+ÞEéÊO6E¸÷ôW=ÂïïáÔ.5ÌÊ3ÚH +ºò68OèAYßUµI,¾µÓS¯CZãÔÃ=ôòë=ÓBIßÀW÷óÙW°*îêÖS¹ÔÊÖZݾռ.*ýó5ÖZáÊOÙ0Öã@Hé:,ïéõÒ +4ZýÖ*îûÕèùÝèõ½ÓíýTÝýÓ¼ýUûüÕ¸?½¾¿õÇøáQø*Z½Në5úÃÒÈÐÂ/AùüéÑ1*¼¸UU>IÆTKÞݶÉý½+* +ÎéÍEÅûÉíPT?áöè?UG**0îêñêµÎT¸Ý·ïäéøéMÓÀ6Y÷HHù4**¸ëK84+ZÔëãÃùÝ+Uß÷ÚÉíÌÓÅT/YÃ@» +ùö?**òØÁF>,BUã¹õÜÀTßûØ·AµÓÅT/W³üÚDø?**ÖØÁF>,JWãYáä½-ÓÀøYÝXXUAÓ4³Hûú4**VëK84+ +>ÖíÑUáõ½+*¾öUÍü7V=ÓÈWäV¸÷?**ÖØÁF>,R±ÚKÉPÝý*¾*æ¶2Î>:.*¼I¿¾±1¶Ñ-14*çÉ0ËÙ×PT?ÑF +÷ñï1*¾J=BöL,в*ZóP,:ä9-6Ò¶X.*J@¼Îê²µüVüü+*,Þ¿õJ¾Ð¾ç¾=2¶ò*îüÏ8FR*>ý·±ëêDSÓV°â +Ë¿I**èAÀÉ53ÆÐ+¾ÓE3¾9²ð¼»¶ä»îÞáãäê·¹úô-*2¾±É,+Ú»ë¼6.¿éH*1*ÞÈÞôO»°í=ýSÜèÀ/ÕÊEù +*ô¯2G:A·7?FB*ÃÝÛ±ä×áóÐTÏáÊ»I**Å°ñ*ÛÜ»18M0R¶Ï*ÜBÌ,HÊøôËÊJ¶:¸îJ+84*Ö¸=+ðàíS¾éZ +î*H±ÐJ¿=æÜ7¾¼6NGó-¼Aù°äêéÑ9*î*ÊáRÈH*øÅVKõYÍÂ0¶øâ8ÀµÐÀºÁXî>²ãÅ5UÃÜ-¶õG»*ÖË>IT +¶Z*ØH¹äɱÝéNÓ°Æ7YÔÛ*ÞÝ,ÏWÆCëØùƳÒ--T+ú͹ӷ8ÏWù+2ººîJ+88*ê¸=+¸=Ư¶³é¼708¾ÐXÌA +áàNÙ+Þ´¶¿Ñ¼ÎûZUäúúúS21¾ûQ6Þ+ZÅ:ÊT,N97îCAÃ70QYK½ÓÀíê×ñÜ:º:ÂJ,÷ºã@¶ü·äȯµÑTÏàÇ +YA*öWíXŲÏøó-Î÷ZJ?*FBâ¿ÒYõ+Ó¶ZÞ¿,F>*æóÆ,î±ÂPäÛ¼708JÀãÕYMÔîØ+ZûD¾Ñ¼Þ.YUÜúº¹ó+ +*8¾¸óÅÄ+ÎÌ+ÎG627îÍSNÁèìÛ8çóÀìèÓñÜZºÞÒ-*´UïÞ+,ÞôåAU¼ÛÍTO:ÍH*:ÌI.3K*CQMZºDÎ4J> +2WCJÍçW·Ñ*1/*¸Gá*<ºßòéZî*>FH»Ø¯/Ñ+¾Î¾Ñ¼Þ.YQÔÊÚ¸ñ+*8¾·ñÊC*ZÌÈé׺º³ñä3Úôä³æ*X´ +ÂRäÓѯر1ñ²º*Ë-îëNâÏÀ--ZèÇUÌÚËTã8AÄFø*¾²ò<2ÞGO4ÛåBäÂMÁæÓ*Ì3ËÙÍ-Å8ÛÕÚÃ:²îJ+84 +*ò¸=+ÈÐíä;W¼708Þ;WÃ5á¸>D*24¶è1Ê:´ãÈÃMIH***Â+¶¾*ÄÙ8OK+8¼ú¶U4ĽÀ²Ú-åîïöçÉ: +M+æðßAU¼ËZEÓÈN0G-*FÎÌ>¸:.*¸GÊÀ²Òîú.òMêÈ?5ÅHSOÛÐ*³XÚðâÕ?´ÐTCâÄAÉÜ,*¾X¹ÆÁL*»Cù +ðãßËåÝT4»·ãȯ½êOÓLÅCÍÜ,*¾X¹ÆÁL*0ÈÙ±äßÉ=ÍH*ò¸òÉAå¼×ÉTÃ:±X»/**Ûô:1Ã*F?¶çÍÃE½H» +*RøðC¹ä7²9Ó4*5Ó,ÔÕ**Îé8ðÎ2Jã,½W¸¶ò÷ÕÈäÅIزîSùRÓZUâÆ?áÜ,*¾X¹ÆÁL*0VÙµéÜ¿õÌH*Òº +ûÕ¯-AÙÅT/üõ,*JæÕ»ñÔ»-*¾ÛÍùÜ7¶Aê/ÎÈRÓ¸âùØÌKÜýT»ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼üU?*28,F +ç**ûÜ2ìJ*,*Z¾0ÎÕ°îÜ5ÜÁÓ<*8L1,Úç¾áJ*æíó*/µSá÷6ú7U?á:/,RôÏ÷>*<ôZÒQÏÉÛ=Pê/õïÚMW +ÖZTµô*ÂG0Ï-L°Ô-U2-:á7FÈ¿îÀ½ÏT¾è/*Â>÷íA¾°1¾GϾâÚè<Ï+ֿ̻æÇÒÎXÎù-ÓÊõëÝN¯õ*æGå8 +J²<ÛU7>*ôêY=-òV0XSLÝHÞÛ?OØTîý+*Ê>SÎB¾-Þ8KÚÑ.ÆHDÞ±X4E6ÌÜ¿üT/ÝױݻKÛ3+*òñÃéL** +H¶TûV0Ä?¸A*ôÎTQ<:ZN9;-¾G-ÙADÞKÑÎPóGºGÔÁ/¯QÎùÃGÇZÚìVK.*V¶?-ÕNÏW?ZGÓðÏE*ÞìèÌåç +Û´ôê5îäXÁ0Yü´ÂSêKCù˺=-³º*×-JüNã=U2+*Î8ãJ¶ß9S?åÌ:*ôÜSWRÞ¹ZåXé,ôÐ@·ôæEÎâ¼¹Ñ.² +K>ì1TãÖ50¯öÇ?ÂüIÈ¿îÀ1ÐTA9*ÔQê9C¹ß5RÓ1ß,í½ÓÜýÓ¼ýUûüÕØ+TOÞŽ0**¼Â*Ææ3+æÌÓLÖùù½ +.*ÀIôTÓå²ÓÔö¼ýÃÍ*¾÷õݶ÷Ô2Ë0ÛTVÉÝýVÛX*¾÷õÝ@ó0·ËÔV?HÈݽ¯+,*9*Î+,öõ½>ñ1³Ó2<½ýí, +6R7,Æü:MÓè³2ô,3YT-*ÆèL*òêLîP48´,*¶÷+»×T:ZÒèÂTãÈî9öâT3K3Z<Ì2¾ü<È8ZS,*¶ù,ÕTÓKÕ +èèëõýWK³Î4÷ÝÝìÇÓ¸?O3BäÌÛD.îJVÞ¿,J*ê/1ï:öá*>ê*Uã¿-JæK2ÞÚMWî5Póä8¾G0**¾È;ùL¿,¹ +Á/=O,*¾=HÎ÷:6Á1KÕ5GBSÎ*ÖµPÁÞÀMXÞ*,ëâÌ»Ð×ÛKTùS?Çô¼VóÇT3ûçXVÓù?Àû;µê×IRÁ½Î*0*¹ +H?UÉRÛç/è2F¾VÙ;Ø+ÓʵÜûÉ>ô*æGIE2+ZÍÍMÌZÖÎÒ*ÖIï+ÃBD¾ÀÎ7Úé+4ÂX÷UXÎ*PPàü¸ÇHÛTçã¼ +VóÎèÖÈÆíóàT»C»ê¿àÎÌÛ:Ç/¹ZÞ¿,B*ê=>Y9,éQ+0.ú¸ôà5RöËG¹×»KÃJÓ¾SåTYØ=Ð4/¶â-K*à*ù± +G:2ÀIñ,/Þ¹Áì-÷óÄRWRÓÊÒøüÝÓR7,2òÐ8VÓB9;ÈX+ZãüúJBÏ+14:8ÂÈ91Z8:>ÎT/×±íëßÀZ=æûTÓ +VÑ·ÀYÕ:¾óÓ*Zß79ÒTÛç¿CDæûÊ5>*ôÇN´KÛQÕè8¿õ9Zó½Ö4÷åûæ¿Ó4αL0ZÔSêW½8H1ê9>¾,Þ8äS¹ +*ä+À,8ÒÈAíP4>±Ý»=-ÎT.N+ì¿*üJîðº+öäܾÎPAN¾4î0óÄ1TÓÌò*î88ZÃÞCíû..PÓæä8ø-Ò´ÒTì +Q÷òèåYż»²Ú;íËÙÍ-U?ÃV0,æÛ2¼Ý¾ÎXJÞ¿,R*êôÎ1ßFJRJÓÈÑƾÇôëËMàOU0ÆâòØWÓ6¹èNAH,*KȸDUëðBÖËTÏðÒ +CI,*KÛº·¯E.JÁÔèõƾõÝTíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýT9ØßùÝèõ½ÓíýTQYô9*õ;òX/¸°Ó*ïë-* +FíêÓد²¶ëÙ¿1åG¹ôêÔ¸²æÎIÓT÷æÐY9IXÚ¶îPE²æÍIÓTßåÈEùHHXã¸YÌê÷ÑT?ßV¶:*.2-JÄ/±ÜìTS +àÂ3AÄúù?2¾ñõËÎßT?B=LßÉYX6ظôÔ9ÙáÕ×±½Î*ÂûïÛ¿TFJ*ôê-í»ºÝÓÐýýñͼ×ú=8Zé×=ôüü3DVÓ +8ÞÒùÜ8Ù³ëÌÏMA̵IR*áÌ»ïÚÁTDJ*ô¼ñ@ÜîT/,¶áöOE´V¸¸ôÂB´áÑ»U¼÷7ÔÈAÜÌÛøâÉDZQêDZ¼Î*Ï +Tî´Ï402¾õëàEX?ÍÜD²êÓéååWYµQFF»ý¿ÕõìÎëTã5QHGG¹ôƹùóÙ5AÔÌG¹óÞö¿,Âð2Ùç9**ç³Éö¼U +ã3=¼VØúáóíñôè9áQúÝû¯ÔG-ÞèTã×MµûC¹ôÜ9ÙâÌ7E8Í<é5IR*áÌûîØQTOè8ÜîÓøýýùáµV½¹ô<Ûï¶ +è+¿ùQæZÓ4ÆT0JÏG±8ùQáC±X/2Â/µYÒ4ë»ÙöÀU³4JMùÜX»³çÓTÐöüùQæZÓLé³YH/îÞ5ýÜë·¸òÌßô- +/¾ê×QìEÞ+*ÎÝ»=Y¸Ó¶¸éÅ7ÉH9ýZ*ϼÚûì,·ãÓLÄ5µ8FóÄ5QäËG¹éÅãïéQ4ÞÖUPF»ÏÈOQÊöðóÔèÃ; +æôöûùÅÝ»¿¿Þõýì7ØWÓÞø>Z¾>Y̼V/ÑQ1ëCñûÔè¿Ö@*â,.7ÔT·Þ*V8Ó,ç-ÓV¸ëÒQY¸Ó.ùý/*ç.ßüT +/ÌYI*@ÛT7Z-1@µõ/æçÓè?4÷üUÓ½U/%%% +d +342 151[1 0 0 1 0 0]sl 8 mask 0 0 di +/mask 6493 string uc +*JÌ**-ܽ?ºý-úü1öû9îùIÞõ½¾íý*Ýý+¼ý-úü1öû9îùIÞ庾% +d +/sl 51642 string uc +äÕí´ê9+*ú½³+¾èC0º84¶åM¾èÓ/Ö´¼ûØøüUóûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýT¹ZÓ¼4*V +JÓÞýÕ,*ÕÔ>çT»E4öüUóûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝ +ýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøû +YóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½Óíý +TÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½Ó7°Ó% +d +342 151[1 0 0 1 0 0]sl 8 mask 0 151 di +/mask 6493 string uc +*JÌ**-ܽ?ºý-úü1öû9îùIÞõ½¾íý*Ýý+¼ý-úü1öû9îùIÞ庾% +d +/sl 51642 string uc +äÕí´ê9+*ú½³+íÄ,*ÓU5Zëúø³óûÕèùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ +½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ý +UûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝýÓ¼ýUûüÕøûYóùÝèõ½ÓíýTÝñÓ,TY2*ðSÓõN+í +½ÓÜýÓ¼ýUûüÕ¸KÓ,ÒÜ:*âÑTîý5,¾ºTóGSÞùYÓõ½ÓíýTIÖTïãLï·Ø±ÅÌÒ*¾õê×á3HÏÉù¿TDWÓ¾äîÐ: +¾òT³.*²´ÁÓêLÓêùøöóí/*2üøí=62:õLÉÐV?×MìÏGÖòãÅ5ÍH*æöðçµ/HÅÓñ¸ÓÚ¹öòéù½ýû·KHú4*Þ +»ñüËùõîêÛQÅT¾°F3K5ù¸À½KÕáL5X*>Íê¿Eý7ºöðà»í@دQ5*úØóéÔ¯õ´¼KîǾ¸8ÕÈQ9HúõñôûéQó +ëÕÝ,*¶öâ¯ÕÌG¹´í*»°Ò,ÆMǾã°ÚÚ*ñÞY¾ã*-RúÞ8F¸êÖ±í8ÜÚöÂ9â½éXW¸óèÓ¯ÁðµÞEPZ1î+îíUå +ïT·ìÕ³ÝÌWHýü²ýüúU**½½üöçÎWå¼ËXAÝHÓTC˼úV¹ÎD·+äÃÝBµ¼úæºZ1¼E³éÒUýPÌF6ÍA*Ú¶îØK¹ +HÛD³Zû.Qô+H3RåïT÷äÖW½8ûÛ¼üÐIýO*¾í9½EñäÕ¯U0ÑYG¹,3îé4?4ù¸ôèÕSºüôÑ,*YíºóåÒ±Ý8É4 +?ØôÞ8Ú°èÑE½ü»º·JHY*öðá½Ù̺÷²è*÷3ÌV.ºìUåïT÷ÝÕIIôVÛ»ûLýüG*¾éÕͼ¶ãÅIÝP½-ýÌTÓFÒP +Ë2ÝPËC²çH½°Ôîð»YÜÊø³àËN=R1ô¾ùÐìÚB±ìà=Ôè:*08ùïàËSõ»À=ù*9ÏYý·ó¾´µÕèçÔÔVÖ°ñðé-ú +·ÒÕÆÁ;=5¹úóã1ÅVÖðè´ìëSÓ4ÔTÓèXê¿Mº¶àAIÞ½ùÄËöìßÍÉHß +Çñîϱ+·:JßÄÉÔ·µH÷RåïT÷ÉÎ/åGVùøô*ü?*R*îÏé7LîÏ0Ú-**î>.FºöëÕéHFÕZ+û°°ÓðçÐOÙ3øZÛ +Âç´ôòí*¾9/,ÖùÝIÆÐÕò+Î18OîE@îë×íPVõZæEÁ68ê¿Õò6öëÞÔ¹õ0ÝÍ+¾Ï´FDù¹:JÝ3ý½ö+ܹµé¿í +,ôÚðÇ-Û09äÈ,æïë½M¼èDåä=9óFX?Õæ·ZÚÀ±EÅ8ÒÝ,**:0ÞÕ<Ïü÷Ì,Jܸó***ÎüÃ,/¾X×Þ2Þ¾*OÁî +ÞÓA¶òä±5ÄEÖ±ÃÇÓÈÓUÕXá,1¶üÆV.ËÔ¿*À³ËÖ5ÈüäåM@:çÍAT¾ÄK01ôÞ;°ôéôYéÙ¹Ý-*Í6º¯öÛ¹=@ +Ë6MÌûN+²³èÛ¹µ¼ûÆ2ö´é½ëøã6ï3ÌÚ09Sáñ/.Ú¶8QÆß?óèCÑK*@Ú:Î,ÏVÒñ¶ÓRJå¿M*SöïãY+**2- +îÛ3>AÒ9¶ÐON÷ðÏ+@C¶ß3È/È*,+â+ãåÜJÄ198N¿G1ÊVÓJÞ*¾-Dð¾öJ*38KêÃÎøôȸÌÎL*Þ¿Gö½ +ÊûÔ󹸴ëZù?*¶éÆï¹0Hú´àRù÷ðÏÃÝR*ÌÌø÷êÆç=½L.X¹ôç¼éøãúí3ÌÚ09¿Zù/.Ú¶89Õ·¿ðãý¾ùWÈ +A½NJçRåïT÷¹Ì+Ù/Êصíåæ6*Â+Bé4¼M2??+¶Å0ÖìLå;5=¶L*¿*â1ÓäÕÜÞ»ùP7ÖWÞÌíOÔTÄ;MÄô8¾- +6½-*,ßôJ*ÕGßöï*ܳ¿ûÆ/XÎ,Ö5°¶ïÚÌ·YØ»MáÄGù?*öäÑñÉÔÚ»´àöIúëÃëÝR*ÛíBºæÏïÝ;,À7¹ò¶ +-ßÚì3ÌÚ09¿Îø¿-F·8î»ÂM:ÎHC¶LJ÷RåïT+´¿6ôõ²ìÚÍ×D*2-¾MçòÜJóÃ0BNéêT8=J*¸ñX/2.J×ó· +·ó/ÄEõç½ÁD*ÙâTã°äÊSW¿-ê¼²D<0æJ*;DäºËÊàñæ/.õëÃ4JWÆß¿Gö³ÊùÌã5ø³êå*J6=8*îé¯=ü9Ù +³áüÍ>*°¼H×÷óÅľYÁä¹´YäéÌîâ³ö2B<9FÞËñ6ï2.ÍM.ì2RHè·ÚTJV:-ø?7¹ôëÕ,æ+â´W?ÆÇ×ICE¼ +WQH7î0àÌì4*ñê/ÆZ³*Ä->+ã×3ùÃîÆ5îôêÖOù7è*àð +Þê½ÊÏÜúƾðÏÌáÓÐ×J5ÄùµòÁ+ò**2*à:´êÉW=//´Â0¶êõFº¶³Å7êôçÀ»*Ä-Ò>/ÖUFÎô66öóæ½ï40G +U°Óð¶ïà³/øZÚ½+ò±»¾3»:*,ïÖ-àÄË×ÇZÅXJM-ĹBòTK01ôÞY·üé@íåÒUÝ,*ÕúØûèÓOEôX3UXVC¸ö +A4*ýÕ´FûúUNÞÒIñ³ùÕñUQ¶ðFºBåD42Òî-ùÃñ>ðRê¼Ù6J+ù7+ãMGÅU/IË´ºAî¹âÇê,8ÈN+20ÜéöòQ +ÇÓÈI¼úÖÐèÇý0Ì7FL/ïîÝÇUU4°¶Z1è3òß·ù¸¼ÄTÔ¸ó÷â-+ÚõüÂýííIHô-/ÎÓýѼÊÝö±NÎÍûäG6ØÑ= +FÇHºÌWÞ³óFX?µÂDñÞÀKÝÌË°á¼û.**¾5*¶-,CKJ*ÆÌXFBñèÄæ1/*æñäÉ@WÔÓØÆC° +WÓ´êö;âÆ=I>*JÈ>M¼úÖÔè= 20030907) +%%CreationDate: Sun Sep 21 21:22:12 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 9.305 2.791 ] [ 2.791 9.305 ] [ 2.791 2.791 ] [ 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 2.791 2.791 ] ] d +/pageinit { +35.4627 23.6418 translate +% 185*281mm (portrait) +0 795.224 translate 1.07463 -1.07463 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 367]ST +B P1 +NB +W BC +/mask 6493 string uc +î½*¾î1:-*¾*Ñ-õ0*ÞQK/,ÞÝÑT*ÞñÎ,öïì3ðZÀÉ8*1<·ÈáµÇâS̼*úÉâS̼¾ú+FQ?¾9ÇT¿º*BQ?¾- +×TKÌS:*0ºýTöü1öû9îùIÞõ½¾íý*Ýý+¼ý-úü1öû9îùIÞ½¼¾%% +d +/sl 51642 string uc +î½¼**ûÙ>*<òÌû@KÍÌè6JÑÎÈìQ¼Cîׯù<*VXßÖåYúÓÐïG,9öé@/*GE¹ýüú÷íö¿ñ×,ÈG´éX/*ú5¼½û +ùÕæTåX?D:ÃïEÙòùżODHRÄ-2G°,+ìïÞO0ÎÞößQá-ëZ-0¼½ÑOY.å³øö +ñòéé½YHºõêGùòåËE½H¯4ÞÚùù¹ÜATóâöûëêäßíÅ9ܸºÜF¶ê³YV×ÇíÍ?æ±**B-*ú?XËúÞÂ÷Ð3:C@îJ +/²ìðöûñ½ý84À*ÒúýîJûýý²/X½Jæ8ïí¼öç3Ê,2ÐWY¹+äóܽ½ûQÀîÔ6ðE*êÛIWÍQñJ9½GÛ¸÷ûñݽíé +Éåüº·ðÕÃPÊ,Ô¹ýüùÀT+ö5Aô»¹M19ýüøì;·ïâµé»¯,´½½ÎHÍ+*Ö6*î;òLÒæÚ<êä,4/ÚXýé÷SD³9Ð +¹SîH»øýIÆL;*ÎÉ?¶ë+úý½JÛ¶ýíøò.T+JâáXÎòý½Á.WÏSÁÉBÆìD8>*Èì9BPÐ/FÁH*Þ*Þ?øöàãÍY½ +ÝÝüú8½ûõâÉ9YÊ°à>î°ùõµFXýüÁ¿æ,*½TB¶ñííéÅÍåUõGú¶ßM9Î*óí½ñG**È-*Èë7-:GGT4R>*ç-O +GàÊ<+0ÛWݼÝ7òäËýýµÔ9XÅ,öý¾²üý½±éöν>J¶Þ2J6*IÕÇ>·üïãÜLÃö;Ú3ó½ì÷8Ú¾G¼íýüD+3º¼2 +,ú½Â4ôBüöWÅ5Òüý4CàÂBJÞ¿è½»ñPõúñÁâ1VÏÀ¯ðïÂF<3*NÙý´ÇHÍÎQÏÈ**î2»ÒêïÞÅÉÅIÔÂë½1üù +¶×6ÍϾ**î>/.**=/,*¾E>îîùÍý?½üÓI5*ºQçâýY9ý»Yí»¶ìá;Ýê×RJøõIñ½²*Jð*¾ÀMLW̯8É,4 +/êüýÄ.Ì-¶ëý¯LúÝöNÊøýý¼ýï950*¾ä*æ¶ÙËL+P,8¾¾Ñ±óÙöZų=ºÂ4á-Pñ½ûÎ8Ú*ìýÝÉÒK¯,Ô4üÌ +ÉL6ÞìEÒ¾9³üQ7¯¶¿PôýýùàCÍ>-X7ôIüµã4?6³,Aâ;3*Þâûó°Ò÷.»B**öíÕ1*Þ;øöÌá±ñôÜÜûùì¼» +õÚ±9íCï?46½ÛN¿2+¶IR/*ÚK+ìÛ½½Uóéñº**âÏõîAè@ìÄXU²×RÞôõ½×ðC**Æ+*ܯHMëîû÷ÏïVJϾç +ýIçâ51¾ûüÕýÓ°÷06D0B-Jø»Ó5/¶¾,/,àíü:RÈYTãÖ*öÝüZXÞ²üYYæ¿5JôýSíòBPÞ:ù´âY²ß?TÖ¸ý +ñÕDYÎ3õìý½Cã,/EòñðçAÔ¯ÖîLHXÆM<*ZËùÅÃÖ¾=æá¼±IÝü²3æ¼ÁHÜ;¶è×ÕÉIÍÜÍA½»´èÞEHºÂ,1¸ +>=4*ÌÏT>¼úÊ1*Þü/ý»ù/TKëÞÁýáÖRJøõY<»Á*ί*¾Ù;ÑÏLUÈB÷C5R>JàýíU·/÷MÕêýøÂNHî˯¾C* +Ø9Sâ57¶À,DÝXJ¾*74Z20BµB=ZZýµPòÞ/îäâýÇI6=JZï½EC¶Ã4ôðý÷À4FÎÞºýïù¸*4Nû½ÐÕÔï,CXÆ +M<*¾ÄùÝW¶ð3ø:òîãÅ÷*òÅæáýû@ëDøõîá¹öíÏÙ@ûùIÀµRî.GíϾJý¼NîT4¶PúöW·õìð+*Þ·Ú¿åHÜæ +JÏñ0XøÙÀÃ,ô¼ýÈ9Í+¾YÚ¶;O=3AXÂçP/øàRZJϾEÃíGSÉ;¹äóÕý=VùÝXL99êäBÞ6*O½Aö²*ëýÍí34 +Þèô/Ìòø4?úýýÚàÄõIXÓ-A¾OWüYÝSÊ*YøÌ·ßúýIÛL/Ùù½íLß/VÔäÆZýýHòJCÒüýàüýÕòâDXâá/·N, +*B¸ýÑ.MÜK9ÞHX.ÜÝL9A1ú²éåÙ¹ýìÛµ½ìø²îð.ÜK¿=Ó¸Å,ðºù;¿USÞ@å͵?»øÇ1*JéÛ7H¹µò+.êG¸ +íàí,5ÖRÞÊõYÈïC*æÂ3Ï3¯@¯*¾KÑÓ2Ͼ¾-?î;óTå½½»ïö¶¯ùõA±àÃï¿ë6-Ê+¶×óõüÅ92MçàÜå=>Î< +/ÑÝíûÛMÒîýýýÕíÙÜJ÷8Ú*ÈåIÜKL-16ô½Ç¿éýíö0?ZýûÑIç4XÞøùù½ý¸ÖP1È¿3ÝÕêD½Ý3YæàúÚ:Å +N**»õYSîá÷Ë-òKëÝÁ½ÄHÜV¶éÖÍYåÜÛ±ÝüF´éßÉûÉ3/8Né;4îÇíÝÙ°ìFâ-*îý2,íG¹öò*ºúõæÃ1YR +éÝL+ØÚIïCÒE-¾07ØEâZ+¾L+AÄ0Á0YÖ¾ùÙ?Ç:KÅL?òØ2;ØÓZà¾*Î;¾5-îKë/úíøÎÂ3SALãÌÎ*DÚà +ÆÎOÕT¯îYN×5ÛRAZÇ.OðÔÌJá+øô6;JAR;Øâ,3¸³î¶¿8<>RÏTûX>ÁDEÒ4@K×̺á++F;Å>,JEäíYXÞÄ +åæ=Ôø2»V@îÚ³I¯Sá7º@¼·Ï*ZIX»Á,6иóÔÂ5íö,*ÚBÑÞ»õPÌ»º0YG÷èËÀ3V=¾SùQÍ2ï?*ÖÀRà>º+ +*Öè/>»4HÙ¿6@JáíÝÍÙÕQ×KKLG2ÓÈñ4Ð<1?ÙIò´+CÜL9+*ÞCøÖPÞÁù¼,RPºöPøÐL¿2VöüT4¶áñåW÷ +ôWï/*¶ºÏÂü8¼º¸öʼ¸ÑÏ8WMö*/2öü°ÍBè6*7ò´î3*Þ¯¿,7Dôê0ÐÖ*ÂNÆÎRøÐÁÀ¸J*+Èò4<<1¼¶» +½Ìï,PVÝ·ÌH¼2¶îàÉMÍXÛYý¼üøðâ1ÛÉC/ÒLïàWãÌûNîÚKµÇÜ+ÃVú±äØ89LØìCº¾,Æ´½ø9-ôV*ZßûI +1UQîßFæáó*1Hú¶ñä͹òéÝÉíÜÛ̸CѾLòëÙ?éÔí6Ì1F149D°æÚ¿¼Bó41î34JèûÍQ.ÖY+¾ÝôíýYغ, +Yúä³ø2T°Ì+¾öCA±4æÀ×±ÙTÆéC¹ý.HK+Ó2º½ò>û³T*¶¾ÁùõACÊÚ¼Q¾ñF»RGWË8+ +¾æTA³4ÞèÓW·ôúÈïúÝ»ôâ¿=åìHöõè.BMìûù±8ÇYÁ,¾5ÂT»ýü<¹¯-öX/å¯øμñÜG,*CÕYè>¾ÎKÙÐV· +TÛñû4áíÝI¹âÄßéÔ.*Ø5÷óÆ6ô6ÍÕ³óôóÙO1áãÉWC´FAîß»55=íÝM+ÞEéÊO6E¸÷ôW=ÂïïáÔ.5ÌÊ3ÚH +ºò68OèAYßUµI,¾µÓS¯CZãÔÃ=ôòë=ÓBIßÀW÷óÙW°*îêÖS¹ÔÊÖZݾռ.*ýó5ÖZáÊOÙ0Öã@Hé:,ïéõÒ +4ZýÖ*îûÕèùÝèõ½ÓíýTÝýÓ¼ýUûüÕT×1ÜQè9I×-¾û;³YîO?NåÔÈÐÂ/Aùüé±1*¼¸UU>õâ:7ÙT2öAìð +ýå9*¶û·ñÖ½WÇK¸QÓ6áUÕVW+*6Þ×å×YJÓî¼ñàËÕÛÁ¹Ê+ÆU6¹SÓ:áUÜÅåÜ,*ÞVÙÉ5õ½+ÔÀñ·AÝÛé8î +;.*Dµ4UGX?º?ØòëáÕ**äT÷ìÛµÎT¶Ý´ìã¹ë+HÀ*2ñäÕDSÓÎÓäÍUMÝ,*JWãYáä½+ÓÀøYÝXìè8î;.¾B +µ´ÓOÖøºÓRñçåÕ**4êõçÓïùÍI*:üèѽÙØ-º.+ÖäÉ5åèNÓ°ÉCÉX¼/*Îä¶Á@˼ý+*ʸâ¾-AT,X<**¶Ì +,K*ÆܲæÚWë+HÀ*6±ãÊW³ÐTCãÇI9½**.D¿¿A<:O-R´ØE½@½ý**ÄÞè¼úê?+C<*ÈýÚ×´Uµ:K*úE.¶Ð+ +*÷÷³Úç¿50:Ë=µÑT×âÆE-½**ÒY.AA<:O-î¶×C¹ì8Ýû**Ç¿ø>¾éôáJ2Úôë¹=2:.0FR*1κÓ/5XCµí* +VêZé2ßÃ+RúëG±Ìëè7N:*781,Úç¾áç*Û9óÞ/-ÖäÆ?ÙèOÓLÅCù¼**8ãä+¸»ù8FÄ6ÎîL+º¯F/ºBóëEC +¿îJòÞ¿,F>*¼Aù°äêéÑ9*îXǶéÊHûM/×ùÕQæѽîøÆÞ¿,¶*ÑÎ?IXV?ø0*Hë0ä-X6¾ÔåÌ÷ðêÔìJ,+æà +R:*ö¼*88***QÜøÔòÆù´Eå±P4>¶Àλ-ö´Kò¾JFóêHRñáǹSÓ6°áɳA*öÕJ²5°PÙôH°7óÞ¾T*ýñX³Ì +M²é¼*,99FR¾Á-ÎûZUäúúú¸.OìÐȽê;.2ç*ç×9È?¹ÂÞ¿,¶*4¼±åÌù,ô0Þ¸ô*X×Á̽Ì×°êÙìJ,-îê, +Oë*²Ý*11J¹ÔºÕÃʾëïÏÑÒÉñãÖB·ë*Ô×Ô7¾Z;Î÷×»ÎãÄ;µÑTÏàÇYA*öWíXŲÏøó-Î÷ZJ?*FBNÀÒYõ ++Ó¶ZÞ¿,F>*PöØðãåÙU½**ö¸-ó6æÔ.:ñ,ßÚS*1ZKµóè·YÎ8*./Ú¾ÀC*ëüú°ãÕ¹Û¿./î¸N5Ê+F+11î +øLLK¯Ëßè÷²ËZ÷óéÆ?.9.;ÆG:íë-NìSÓ<ÆQ9*2å9À²:¾ÊÑ;BH7R/:4ÂÔ6ÎåòÔÚS¾Á,Þ.YQÔÊÚ¸ñ+* +¼Ò²ïI±BQOÅYHÜØ·ñÏAQ2<¶Î*8¾Ñ¯Ø±1ñâF*24BR¶¯*TG·ãÇYõ¸-36Þòç¾°+Bû,FF¾ç¸Oú¸SëDÕKØ +ÀáÑQEÔZºÞP-ZOèÛ»²ÒT×áÃ;ÅH*:7Û.,¶QÇLõCÄCïæ:¸?¾±LñôñÞÏMõ?µ;.7FR¾Á,Þ.¯UÔVFøï+*Ü +ãÁ+JÈ8µGÈUR¾áöïA:¾5<¶Î*8ÞòO3>¶L»F*24BR¶¯*4Û¶äɱõ¸-34Jòæ·A*¶,8ÌÞîæAXV8ø¿Q4ûÆÔ +àïGDH¾Ï+RHÀî×»BâÃ9ÙèQÓ,Í1*NÍ¿¿*Ìò+à´Õ6BïCºX/îÁ=OR:áñå?â+±K-14îÎ*BßéÈAIíXºô* +ÎäÁ±Ô6¹ÕEÆ**à*2G*ÍY¾Á¹ÈîCX+Då´9WÓT¾-ÞGèÀ/Ѹ±µ*4´¾ÓÐZ2OÅEÞ8¾G01Ø:ôøÀ7*¾=<:ê+>@Öä²2V¶R4 +7ÜT¾è/*¶ôêÑ4×ôB¹ôÌRJñTÉZåü*¾HF¶Î*8*Ұܵý<ÃÛ+Rµ¸¿DÁR?¼Ë6:ô*æG½Ï/*Þ3E**ÅNÎÖ¾,Ï +UüÐ>ì¾MÅGÞÛ?OØTîý+*./Rð@Óúá8KÞÑ0ÆÌÛÎVÖM.8>¾-ÎîE¹ôÙAZA*ÔÍQ72J**H¶T»BîL5*¾=´¶O +,Ã/,KöîÀ¹Ð*êYºMÅCÞ8Tó4-:ô0êCÖW3Jå1À·Ñ*,ÞÀR?æGT?>îâTYÆóÃ@**QêîÊ.0-NÀìK+õNÏñ.Z +AÓðRYIQD-¹U¹úñòØ×±µû·À:¿-/î*JCùëECRêYÕèÃ@**QêîÊ.0-V;¶ä¹I>ÃÔY?ýðß¾:V×ùPãA>æ¿G +ù52è/3FR*1Z¹Ñ.5Lïäü¸ÓX²+*¶à0åNZBÞHÁKÞàKS²ûTû91öüUóûYóùÝèõ½ÓíøT/úöå**ÞÅ2*3Û@* +WÒT.¹½ýÅ*Jöá8;ÖàÓ²ùÓØàùûA¼,*äÝýàãÙW½DçT¯Wüü±ó¸+*äÝýØÒA·?ë¼Ó¶>½ýà¾Ý**ñíýRä9èT +;NüüÝ÷½,*ÐIâRÓØ4ÀÛJàR**:A¾Â:FNÀ-7¶³,Å1;×TF½Â¼ÙT2öVÓ.ý¾QÔÈ2È*=ãTãÒKSÓ4JÍ;@øëT +OÛûÕÔTôÕ¹°ÓPOS,Äὺ4ÛêG¯àX¶9åüÃ,/ôÖ1ØçױͲ@¹³28>GBëÊ>ÎRU»*,O.ÌG*À8À*ä8*ÄäÑ*Æ +Ö1ºÒJî*1V±D.@6BZÇ×Ö±AFÂÁß-µÅ+õR5@ÊèGT:Z*×-RÖ+Ô=ÖBºîê+âÁZ6H.ÛÌöLÓß;X²ÏTDzõYWÓ +æÍôàTÏY;ê9·5Òïß0LQÐÀ¸¿üFùåå;QÞ¼°<äü¸3ôê9¾¼¾.NÈÙð1@ò*.È2F¹*×´M²ÁG*M>ηÓK/-M/, +1¶¾,ÏU¼L¾M=çËG¹H?59FÛñÎÈ÷ÜûÉÒ-Ǿê+ÒEÛÞ3V6?¶ô*:Ä0Î:²-QºÏèî.ADçTOAùÕÔTâõ¸¯ÓÜÊH +ôJ¯Rå8¾åÚ>´BF»-ûóè×èÉ0*¹í7ºõ¿úÖ±A>5ôÐ-çÞù+.+ô/,1ÂÞ×-ÖÌÛö,ê+R.***2:û>+*B:¶¿-, +Kö4++ò±>*N¶K°ÊÛÀíê×íÜîNÓÞøR¾5ÖBT¶ß,ÞÞ3YÒDFàÃ@T?;ÓüÓÓ4òÐ8VÓB9;ìà+Zã8*²4èÒø¹ö¸ +Ví²Ìîé*JöÝÉêÀD¹ôå1ÀSOZ/±ÝÜæÑJîÀ2À*²µ1ê+ZZù.B4.Xî*N2MG+0ÚL+2Àðá9¶+V2Ó,ÛQÓ8ZL +JÞ/VÚUÓ5¿R³¶À5˲Óô2ºý*íÒÈî¼òÞT/æC;-<éÒö6ÕKM÷Ù;P¯½Þ-Aå5¾µôE¹¾L+-âøñ.O1Ö0LìØ*6 +ø>2Ú:,1¾»÷´ÐÖ-ZãôºÌXù/6/2ÚÞ+T.Ì÷¶?1¾úÇÈê×Á·4ê?H¶ôÎÊûôË6>ôÒ*³¸QTó,@ìÓèÃJÒDFàà +@T?õѺ¸óñÕMÝH.íÆõäëå¿ÓÈL@-+úX.Ý+ZA³3¹ÅÜ+Oô-I¾öO+ëßXº,Î+-ò,À¸Ó.Ôõ1125»4.X2¿-J +;+=ëê+ö´Ò@>æï*Ä0.Xî¾»0Á¾Ã+-A6àZôLEæÁZ@Þ×íì>ßÀRÚ×ÑU78HSÓ5ÖBº¶*,ÏTA,Ïèî.ADçTÛË +êÚ·ýPüº·X8G¹ôæÏÙ·ZÓS.*ÛÜïºT;+ãñ,¿=1JçYÀÇ4=XÉÞ,èZóÓ<ûÜF-*¶óÞTÑJ*3AWìK+*4*Xá¾S +BZâëÚE+ÖÝUÁVüÕ=TãÖÑP¸ï;*2ÐÒñ×è8Í¿ëÒ4?QË÷;**èñóJLÍ/J=ÓèõƾõÝTíýTÝýÓ¼ýUûüÕøûY³ +ÁÒèQ+¾/Ò4úýT*îèÈÍê¿Eý7ºöðà»í@دQ5*úØóéÔ¯õ´¼KîǾ¸8ÕÈQ9HúõñôûéQó +ëÕÝ,*¶öâ¯ÕÌG¹´í*»°Ò,ÆMǾã°ÚÚ*ñÞY¾ã*-RúÞ8F¸êÖ±í8ÜÚöÂ9â½éXW¸óèÓ¯ÁðµÞEPZ1î+îíUå +ïT·ìÕ³ÝÌWHýü²ýüúU**½½üöçÎWå¼ËXAÝHÓTC˼úV¹ÎD·+äÃÝBµ¼úæºZ1¼E³éÒUýPÌF6ÍA*Ú¶îØK¹ +HÛD³Zû.Qô+H3RåïT÷äÖW½8ûÛ¼üÐIýO*¾í9½EñäÕ¯U0ÑYG¹,3îé4?4ù¸ôèÕSºüôÑ,*YíºóåÒ±Ý8É4 +?ØôÞ8Ú°èÑE½ü»º·JHY*öðá½Ù̺÷²è*÷3ÌV.ºìUåïT÷ÝÕIIôVÛ»ûLýüG*¾éÕͼ¶ãÅIÝP½-ýÌTÓFÒP +Ë2ÝPËC²çH½°Ôîð»YÜÊø³àËN=R1ô¾ùÐìÚB±ìà=Ôè:*08ùïàËSõ»À=ù*9ÏYý·ó¾´µÕèçÔÔVÖ°ñðé-ú +·ÒÕÆÁ;=5¹úóã1ÅVÖðè´ìëSÓ4ÔTÓèXê¿Mº¶àAIÞ½ùÄËöìßÍÉHß +Çñîϱ+·:JßÄÉÔ·µH÷RåïT÷ÉÎ/åGVùøô*ü?*R*îÏé7LîÏ0Ú-**î>.FºöëÕéHFÕZ+û°°ÓðçÐOÙ3øZÛ +Âç´ôòí*¾9/,ÖùÝIÆÐÕò+Î18OîE@îë×íPVõZæEÁ68ê¿Õò6öëÞÔ¹õ0ÝÍ+¾Ï´FDù¹:JÝ3ý½ö+ܹµé¿í +,ôÚðÇ-Û09äÈ,æïë½M¼èDåä=9óFX?Õæ·ZÚÀ±EÅ8ÒÝ,**:0ÞÕ<Ïü÷Ì,Jܸó***ÎüÃ,/¾X×Þ2Þ¾*OÁî +ÞÓA¶òä±5ÄEÖ±ÃÇÓÈÓUÕXá,1¶üÆV.ËÔ¿*À³ËÖ5ÈüäåM@:çÍAT¾ÄK01ôÞ;°ôéôYéÙ¹Ý-*Í6º¯öÛ¹=@ +Ë6MÌûN+²³èÛ¹µ¼ûÆ2ö´é½ëøã6ï3ÌÚ09Sáñ/.Ú¶8QÆß?óèCÑK*@Ú:Î,ÏVÒñ¶ÓRJå¿M*SöïãY+**2- +îÛ3>AÒ9¶ÐON÷ðÏ+@C¶ß3È/È*,+â+ãåÜJÄ198N¿G1ÊVÓJÞ*¾-Dð¾öJ*38KêÃÎøôȸÌÎL*Þ¿Gö½ +ÊûÔ󹸴ëZù?*¶éÆï¹0Hú´àRù÷ðÏÃÝR*ÌÌø÷êÆç=½L.X¹ôç¼éøãúí3ÌÚ09¿Zù/.Ú¶89Õ·¿ðãý¾ùWÈ +A½NJçRåïT÷¹Ì+Ù/Êصíåæ6*Â+Bé4¼M2??+¶Å0ÖìLå;5=¶L*¿*â1ÓäÕÜÞ»ùP7ÖWÞÌíOÔTÄ;MÄô8¾- +6½-*,ßôJ*ÕGßöï*ܳ¿ûÆ/XÎ,Ö5°¶ïÚÌ·YØ»MáÄGù?*öäÑñÉÔÚ»´àöIúëÃëÝR*ÛíBºæÏïÝ;,À7¹ò¶ +-ßÚì3ÌÚ09¿Îø¿-F·8î»ÂM:ÎHC¶LJ÷RåïT+´¿6ôõ²ìÚÍ×D*2-¾MçòÜJóÃ0BNéêT8=J*¸ñX/2.J×ó· +·ó/ÄEõç½ÁD*ÙâTã°äÊSW¿-ê¼²D<0æJ*;DäºËÊàñæ/.õëÃ4JWÆß¿Gö³ÊùÌã5ø³êå*J6=8*îé¯=ü9Ù +³áüÍ>*°¼H×÷óÅľYÁä¹´YäéÌîâ³ö2B<9FÞËñ6ï2.ÍM.ì2RHè·ÚTJV:-ø?7¹ôëÕ,æ+â´W?ÆÇ×ICE¼ +WQH7î0àÌì4*ñê/ÆZ³*Ä->+ã×3ùÃîÆ5îôêÖOù7è*àð +Þê½ÊÏÜúƾðÏÌáÓÐ×J5ÄùµòÁ+ò**2*à:´êÉW=//´Â0¶êõFº¶³Å7êôçÀ»*Ä-Ò>/ÖUFÎô66öóæ½ï40G +U°Óð¶ïà³/øZÚ½+ò±»¾3»:*,ïÖ-àÄË×ÇZÅXJM-ĹBòTK01ôÞY·üé@íåÒUÝ,*ÕúØûèÓOEôX3UXVC¸ö +A4*ýÕ´FûúUNÞÒIñ³ùÕñUQ¶ðFºBåD42Òî-ùÃñ>ðRê¼Ù6J+ù7+ãMGÅU/IË´ºAî¹âÇê,8ÈN+20ÜéöòQ +ÇÓÈI¼úÖÐèÇý0Ì7FL/ïîÝÇUU4°¶Z1è3òß·ù¸¼ÄTÔ¸ó÷â-+ÚõüÂýííIHô-/ÎÓýѼÊÝö±NÎÍûäG6ØÑ= +FÇHºÌWÞ³óFX?µÂDñÞÀKÝÌË°á¼û.**¾5*¶-,CKJ*ÆÌXFBñèÄæ1/*æñäÉ@WÔÓØÆC° +WÓ´êö;âÆ=I>*JÈ>M¼úÖÔè= 20030921) +%%CreationDate: Tue Sep 23 20:29:37 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 9.305 2.791 ] [ 2.791 9.305 ] [ 2.791 2.791 ] [ 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 2.791 2.791 ] ] d +/pageinit { +35.4627 23.6418 translate +% 185*281mm (portrait) +0 795.224 translate 1.07463 -1.07463 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 403]ST +B P1 +NB +W BC +/mask 7506 string uc +î½*¾î1:C*Ê=FF**IÙD¾Iµé*¾åÔ0îßKCâ@0QI*,âµDµK*üOµDµK*¼NµDµKJH*̼7Þ¿úéJ0*ʼ7J¾/ +ÒKø²öüåîûÍíùIÝõ½¼íýúÜý÷»ýñùüåõûÍíùIÝõ½¼íýúÜý÷»·å,%%% +d +/sl 60048 string uc +î½¼**ûý2*NöQ5YÀQг±*Û᳸I÷ã¾åÄûF*Þð4Ä»¼1ÂWD¿Nî+½ûM*JåäìÝI½üUQúºÔ¾°ÅëøË**ü/ÝÝ +üû¹/Ù±¯57¾ùÇY0ºôóöEÙòùÅüQØ@.*åÌíÍùéé¹Ø<3ÖÚÀ¯Í9L-úY·Ë¾1F-õEîûÇYË.*.=²õ**BãL/ë +V-Bõüü³Ùä*R5°+CE¹:7ʼá³<¹ê6ò2çôݽ@èN*JÎ;ºä³À:ØVÒÎͳ*¹õÅ7?éÃUÔÊù-YD5*2²æP+Ä=8³Jù9*ÈëÝ»æÒÅè>Í.ÚG@¯ËüC**éSÎ*ÈGݽýÂã³LÚè-*î»YµÅY +H¹òåç·è¹YÂ*TûÛýîÏ×*¾MÁ´òTÓ:3ð±0÷CÒØDî/*¾¿íí²ç=±Èå+X9ÉIQÝÜüùðß¹åì×Q½ìÚ÷ñ·Î*äÛ +ÝÝñ»ï³°ýê-Òõµ±E1½I¼öèï÷ðÞ¯Õ»+ûP¼½ÔJ±+*Å07RÊúÒ3=0ïMÅXèç¾,*÷9ø:Þ÷AæÚãïùåý¼D϶ +ß*À²éZÂùÀ¶:6öý½üë1IG¿=¶¶E*BÕùüûóÝ0ÃVÀ;±6ÞìûçøºøàÊЯ¾ñáðÕÑUAݽüùõ»»÷êÍAQüØæFR +Þ¸õí½YöD*½¿øܵµòîïíYíGX2,üÅæÑQ*Õ/ïÂ1:¾=Îýõº¸,Qîî²*>ûýÂ>Ò¿Zèî<0<3ÄÊY*Oõ16=ç5±ÈÄ+äÖ*¾+¾UáðÃÉI +Ýü¼½û÷GüøíÇAIÜBãFRÞãôíí¶Úüû1-Ï/*üÒZîäÝÝÕ9IÍÕì¹öïÀÅHJ+èÝýä²*ÞÏKÉÕë°;E°ö7JAæËN»åõI½úCú9@ +¶÷.Å,:ð/µ,ûI¸Ö40Ú÷ö»ýùé.Nî÷-1ÅèÞ8á9Ýó¶¿èý½ÊJÉù,.*Þ·0ñ̽üâÑ-ðÚäLÄÐ/èD¼ýÞ¾O**Ð +ë½ÙNÍ1â>Ë,ʯ?Õ3**87ÉÈG8ú¶÷öÁäÖûõÞ½9AÉï742**0/.**µRÞßõIýUüúU½@*öÌÐÇ5ýÜHüùÜÜùî +ÚÅM¼×±Ï*óí9±D*îæÎ7ù½ÐÅ7Zú09=ýýI07Ø-?îýQûY4ð½*Bûõî±æ¼0ø½5Aû½IÅí9JÂ3¯ìý9¿Ú+,Õû +½ÝZýëÀÂSýý9óý»HQ6ÎÚ¾é+ýÝÐÂ<2,Ý:¾ýýÇü½õ.Å*:º*¯èúIýÄ-°¾ùýM¯ÏJʾ뾽¯Â¾ÁM³¾è4üµÊ +Y¶¿PôýI¹àCÍB,X/ñý»R¿+ÐRÇZÂ5=JáÝÔBÑÖ-*Þâûó°Ò÷ÞÏÐS¾C@+*J6É**îæÖºåï×·¹Yíü»µÝܹì +×ÅõÊö0/LóÞûîD*î0/¶õüûWÓYÍñ+*:ÅÜÁÝÎÝÞI¸WÏãL+ÙÝýãÈ×**KÁPêËÕ×ãàÎK¹Î,üI*áýíû1Î<ê +9êóößöÝ?G°ØMï5+-Pýý/;12à+î×ýðéÒVY,.ÌBJ·+ùÎý½ÁP:+Ä6.5Æ÷ýíGKâÀúýýJBÀ1Êäýð1F:- +âüݯÈ2á.ÊWì3Ô/?Úè/ÙóIü;ñ/B8Sûý+1S8MÎÍÉIÝJ1æÔJβ¿òÁß³P¯1*ZËùÅÃöJÊðK¾B@ùËûúøß³ +,¾ôEáð7¿ÑÌ»Ûú÷òûº÷é±Ñ0ÜïßM>¶¾¯ÅR*¶ÄÕÏöï±H**ùAúõëAÚ0³-9ü9áL+èÝÝâ²*ÞÏK=·YO:E°Þ +7°°Y8´DÎ*:ÎÈ˱ý9¾âúMáZõùÕú +B5*¶²æH¼ºõéÇ=ôâÚYõ8²Þ.*ü1î;çü¿*Üøú¯=Û¼>ÛÝØGñý¼EÝ,0ÚSùçYØ6×±X¼ûÝÔ×ýÅHBµ4,¶à*1 +ÚÞ±ýåBÏåÅ÷ý-4ß,°òý¹Q¶.*üýý<¯áõ9Òâ+CÞÐ<ýɵ;PÞFVÝÉYÞßÓXÖÛýÍL2XÞ°³Ä.ù½ó,16Kóýåð +ý½0Ñ-AX¯²¿ÐU4úZB°Ô*6ÛýSÀ;YJT@0Rá,êÁßÞùUáðð¾Í´ûº¹õíÛùôåWÍ0XÌÞU>B7¹Ã,êðú÷ÔÐQI÷ +**²ÛÙß±õÄH¾¾ÛMõWúWZ+5/Ê÷üOCä,*,7Øñ7¾ý+ZâRµ:¾ÉFEü¼õãèüÕæâÒ9G¾ÞÇ+µ¶ò¶üùé-9Äíе +½öÁC69Zç3-Fî,QçÔºüôó/@Yü56*¹ö.Å*ÞýýýIÝ´R+ÐG:->ÍIºY103Réýñ,8*àE³¾ûõYP+P¶+éííý +»çUXFZ.PÚHXçúÏDº.9M5Rôº×ýíI5ÄÊ?*»õYSîá5ÆÈÃ+865³ý8üׯ÷°ßWá¼¼GùôÌûøñÖW1Yôí;4ÒH +-*ÅR¾Q¼ýìÈ·ñ:9*¾ýM2ºõêßÑ-îïÝMAHºKWý4/êðüÂ=ä,*07KZæ8+¾:ÂôÀ/*¾B*ö=2Þ/Ì·ZÞÂÂÖ*B +S3=Þ¶:-¼ÌÊ1+0VöK:À¯îJʾ+/*Þ2Þ3-*¿ã/8:Ê-+>NÊòÞÊÞã=F4¾Z6T*.×=°Z9,ºZÓƾ¶+¾?îîLA +àßV¾+Sú7Aæ,+*N¶Lì/MæM/ÊV3îã?º.+æÎàXûÅÊJDèÇå0²áúûã/êö,а¯¾=á4ݼ²V@5¶ìØ9âÊ,*¾E +öVúÌÏ*ZIà¼Á,6иóÔÂ5íö,*ÚBÑÞ»õPÌ»º0YG÷èËÀ3V=¾SùQÍ2¯<*îæ:@ê2;RÆBGPÊJKÀÂ3>ØVÒZ÷ +D2YÉñ4²úûùYMç¯1èΰùÁì@:á=ÆÈã+86É**îêÖV=îß»I÷D=Hº=»ÕR:èËÜ-*î0/¶áñåW÷ôWï/*¶ºÏ +ü8¼º¸öʼ¸ÑÏ8WMö*/6öü°5P,*JéO*âL/ÙÕ++*JÐU¾806*,ì4ο9Xíͱھ3ÍJT@1¶¼V¼2¶îàÉMÍXÛ +Yý¼üøðâ1ÛÉ./ÒLïàWãÌû.3ÛKµÇÜ+ÃVú±äØ89LØìCº¾,Æ´½ø9-¼?*ÞÀù½8ÔÌÞÁ×KFBº»VX1¶îßÅEµ +üÚMÝüÛúôëóÒÉ./>LíÜW×´ûÄ3»Þ·ÎöÎ;Áä7òÁSÕ¶*ÍÎ*ÐìýºL¾9/*öéüûýOÉQZ¼4½»VÀÈʱ*:¼äOÀ +,ÚZôêÔNÉHö.*ÚÞ½ôÌïL/4?âóÊÛ½,ÍξT.ÜI¸Èü¼3*¶¾ÁùõACÊÚ¼µà>Í-FI@TQUÑM*:ØèãÀ,Ö¸³UÌ +GÝÈLýµÍG·îâ×Ù1üÛD+äƹýüÊMðùK*Þ/LÓܽ½ÇÛÖ+ºåâ>9/FH@èµìÙM*:ñøãÀ,2D²UÈ?Q/ÆÜY¯úûù +±ÅêBºûE*¾ôL¼G²ÂGáñ³WÛ»ûÔ¹÷¶÷°UÄ7å³Æ¶íààâù½;*î˳ä<Ä7ÛºùôÙ÷öö¯UÀ/Qä.X9H¸0Å<óÉÕ +îÓù¯*¾µÓS¯CZãÔÃ=ôòWÖéBIßÀW÷óÙW°*îêÖS¹ÔÊÖZݾÕÅ+*üéAZßÄCÉ´7ZÉËÎôM.àÕí÷DÞüã,Þùý +ØõýéíýÕÝýY½ýÝüü½ûûýøùýóåó³¾èÐ+*üýQY-*Ô6*ÞýÅøDî>1*¾Q16ÞKÓ¸/U@UÍ:CLÀ.74+2/*ÁL+ +à*FÏLÄ8OðNè?Æó6*ÚÔVÖZÛ°ãÈ?YÓ4ÓSÐNÇ,ãF³èâS6¾²ðòO»ËõèÃUÕXÛ¶ï4,2+B4B728¶*+ÜÉAZã +ÌKÉä6ïL/èO*¾RÃ*ÌÈÔXÞÂ;±ô6OÑ@ë+**³++27رåÊ/*Þ±äÈ3ñ³D¿´óàóQûø/MINóU?îÏVßÈ?ÉÓPà +ÀÓç:KÂ0+È*È-L*B;LÃ8WøÂT6ß+YA°9-*V¯ê;*Ì4?TÑH³øÒç-ùØÞ*öò*+è/Ä+¾ÒöDÐã4ÑÇDC²æÃ,6 +ÑGÖ¾ÈEY.ç +ùN+æ4*¶îé1ìPX×D*JÓXÓøÐÜ0J7*ÆQÍà*L*Ì*J·Þ/-.EéWÙX¯TÂRT*ÞÄ>¯ð.4?VÏ/Õ,î.í*ÔAó³Îé +3**Xµ*EÀNçÐPÎRËCÚ4¾*ô½ó³Kâ·ºËÎÀ?ÞÃRST>MÖ/+ZMÆB¿LãèÓ·-ϳé3×»YݼĴA@JKÃD/ÉÔW·ºW0 +?ÎÄ:OØÒSÒ5â8°5,öÐÜäLÃ4OT2J*ÞNÞ+*Ê2Ò*Â/öSÕ²ïàÃUÓOðÆÏÊ+Þ9W4ÓÈÓU׶ÏXÛ¸Ã+*XYèéÀÐ +ÄÒ8>J:Jî6+ã*üÉB±æÎG-ÔTºE¾4*2õ8-B22/¶J*QVZLüL0è+ºÒSÒRÛè³êC¾@*JÐOùÐÜÔÀÃL߸ó +***Ó-îTö¸ZZHT?Vزß4?Ç?TÂRÏ+*ºÒSÑPÂXVJ?ÞZ*:T;B/R0¶±+ÌR;OÌJÇLã4ÛгéÙøD1óTãÈ>UÐ +NSSÈ>2WAS1Èúà*úÑRÔ°ëØó4>¾Ò¾@.JWWòæïÎK=³á°12î¾*8>ë;âGB+²â2ZÅ>³øòO»7ÊD*îäüýåÝG¸òæG¹úýõ¹8Õç= +:ÓT:.ÞOÏÐÓÉÕYÜ°;RÈ:Û**JAÜäâÖ÷ÑR¯¾á4ùÝúÑ¿0C,ÅÌò¾JÀòÚ,,,JÐAó³Ü5AXÚ¸»ùýݹQçìÛº2 +-ÅåýõA@0èÓWÏ,M*¼ÉA°æÎOÁ@6ß;Â/6,Â*Â0>-6.º +SYñÏLÃ,?4×ýýûöQHÄFÇÐRUÕýݷ¹6³,ãUûÈÔËدÔN·VÙ´ïØóÉB°ÆêAWÈ4/*ö¶ôýýíÏÂ7K6UÔVÕHG +Á¹¼ë+ó;ÒRÏO×À·°ÝVSTâRÏLÌS?VÑ<¿à³Ê3Á/3Ï;Á:+SâæÕøD3/TºË5õ.â4C´ê6ñÝýííD1ÎOÜFKÓÈ +µÅÌ5ÏAµOé@Zñ¾,Pë¯Q´5ÎÔLÂ4+·ï³¼á2EàæÌû4ÐÙÑ5úýúÔ»Ðñ¶,KàãZWåÖëðÒúT/Ì·²JåÀÂPÐYè@ +¸V¸É4êÜÓÕ+R-´ñé8T3D¾6CØÃÔýýÍB>FÔ¸H1ý1*Ú¹ÏÝTçØQC¼ýÍHúúüëÍ/O,7TYñO°Sü½I³·096¿Ü +>ñQ×Ú÷é,ú¸ÐØ.7ÈAù½ÈÚµùï*»Ü9ÏJÂ6/*ºTYÑÓVÓTãTØöDAOO9OÐP/ÙèýýõÚé˽àýùöÕ=ÍôÜݹÅÜ +4AÙóýåÌØÑáëÜýý¸½.Ëñõýýõ+æïýûGõ´³Ø»Ìüý¼AÜÔÛ@ÍÒäÝíÓ½XVøùýíW4OÀàéúû÷Ø=±PXý½W7éÍ +KÆQËT?:ôýá»ÙUÊùý9V¯Cµ/é@Q¹ÖÖçäëýý9òýýïù·:JÌ¿>óýõÌ°0:º´üݽÔéï+.3Qº +ýÍõ*ẹûýº±IFƱQ¼ë +O»ÑúýýÚ¶Î÷íýë5ÍWù´ì¼ÙôêåàÖD¸Ø:YIâ0ýÙìôPÌøDÕýIüÍQÒÞ3âS»û9üO:UT÷ÛýÏ1òïâ6H8Ræýý +Áì·äØýIú22@¶ü6âçý±¼MO8KíèéÞÓ+*Î@ÜäQ¯÷ñÇ?W1Ñùýôµ4Kôø½ììÏ׳îâÃñÄË9TÁÆ=³-ÇðóÍÕñ +ýÝC;Hïúûýý½ÕûýIºAôÃÍMØàü1í¯4÷ÐTëýíKLSàòO»7î +D**ìàéº?AÁPóÈõõ½ýIùD=ÓúüíëãKÏ<äÏYä²·KÛ4¼>åÅÈDÜ4QöýýAÈMXºËÉ=ÁùýûÅ3÷ïǸÇû9¼ä<° +º=Ìúýé?NÄ:õÂíòýïé7¿äWýAýÌÊFÅÀ÷ãøýÑúÀÃ9OôçÑQÎÜÒÕ·SÑNËDãêýýXÛÝZíòWÚÉÜýAÝò7=ÃÆä +õøñ<-ãéOåÚÛTW3ËýÝW9<ý=ÆHóýÅÙßàÏ×ýýýÃ7÷ðñýñ;ÔçÁèéî,QñP͹àý9Qç³ç<åôúýÃM·:ãTýýÍ +¶FVÌ6çõýëU±<ÃàCâÆÏMÆHÓÕ/*ÂÕ·ûÞ³õ8ìûýýÌHìRKÔæ¯åØñÕY¹Yåïýµõ=G;¹Fý½êTGæÑ×óé93ã3 +¶@Üä±ö¾=øýüÖU·EÎÔüÕíøá±8Å>Ííý½ÝºÕдýIøÀXäÕûûùÚ+M@ýý½ýÙä1@¹0G¸N8OøÐÜ´Ú´ÜÔ,Þ÷¹ +µW?Ô:QæJKÑ<´üHHÞïùôCÈ8L+5ÉóúQ1î¶ØÅÀNCÝôEÀÆÇÕü½ùU3ÍL6M3ڹʳöæ°DFü2,Q9¹NHJÐZ±ë +ý=åéùüýýã±ãØÄ:;ñQÏöØIóé:é*¾=éùôUÓSÓ°óRMTñá.5EÂúÑS×àãÒUöX½Dí:Cº>¿èäU°ã5BíÕýü¶ +ðÆ.³·ðÂ4C¼ÚÏL¹ÖJ+ñå;IòäÈ=Wð2TâC³L.16 +HÃ6÷H·0OØMOÇ=¼/ËIø²ýÅWÈý*Ñ2Í>Nʶ¯¸4¯ø>**L»SY=ÒPË*öQÅöÒC±ä6DÌP7F»2W,³ÈÍ<ÃèA¯Û +TKðºö:ðâè?UÒºÏCWÔV×àÃ6Í-2ÏKºäÂSáû:<²åÊA¯îöI¾ôÆßÀOÊF;ð°æÊ:,åðO7HÀîãüíYÉ,,OÉ@U +Ö¶B´ú:¸.3ÇÑAó³ZU+*æ¸Þ*Q4Þá÷ë¼õݯ×Ä·½¸áYH1É@ÄÊFëS¼4WðòF*ÞÑT*Íã¯ë¹»,GàÒBÞÑ㺸;:?¸8¿ +P¯+á-öç=QÏX»SYáÛ¸ïèÃ5AXÓ*ÎUZJG¾µVH×CI¸òêï»ÂGP9,óçÑ;¾°¾-5¶+,VÈÔ +XÈô*¸>²@DÞGOè-ä4C¼Ê>BRÍNçøÐÜÄTÓL?,Z4î4*úOúT÷ʯÁ1:AÂ2äì:CC<æý³.TÛÀýÚ;PÆ<7 +*Z±öµà@O<ÊT»TY¹B*ó´HñüÚÊ +æò·Âǹ<ãËK°Á,+òàå,.5E÷ëÈ»OPÄ:OøN¯ZYëº÷é¸5P¸â;ÛÐ×@FÇÎCùºÏHüëÅ1:ÀìAMÅÁ»Eµ7ÕéÁÒ +1:LíØÌôáÁU´·ìîÅÈÆÏPÕüÒÕ3UÔPæ¾Ó2¾J¾S¾2*Õöú·ÏíÀÓ<ãUçÐOè;LÂ*¸ïÚ±Aô÷ÐéôÜûFKÂ:ßMX +Û¹ËCñWóÙÑ+ððËÃ8@µì°ÛDQQÓ½ÚñåPý¼ØòÝI¿4üìC9ñ±Æ×Ýï?¹ÎAåøüé¼æL>æÐVYèéJÕ**Þ?,î** +ä+à*ÚÚÙóÝÕ@ê?QÝRÍDZØXðæÅýÅé68DÉÐøøV37Ø´ýÉÜÇËDÓÐÐíÇÅAèÓ@üëWéïðÅBGÛüïÌJGû¼óP +±ëç=ê¯Q½îÎNPá-ôäùÊï>±¹ýHûÎI;»SY-Úì-¾ÉMÈCøûùáýôËç@NÔé?ÂâG÷¯VÔƯÑW¹½ú1ÍMHçPLá +áÊ×Mî»Ö±øöV³íñËÒãÉC¼ÇSó6çê×÷Ý-´Ã/6D·¾Èõ?1ÓøFÛÚFZºÊDÄRñÅ×ä7ÞòQ»7Ö¸**îèÃ-FR*Y/ +È?³úT¼»êÍíYíCÝUÎ2øï¹3?×Àä̱̻ñF¯úæ¸/Ź,?ëFÜõÝTï±LÇÓüÎ3>YÚ7QQÑMêÀäOýÍñ5=XìîKç +×Ýò=7õßÍÖö@²ôîCèÜOA.ÄÏWíèé8Z7Ú¸°´ï*ÌT=L¸ø²ð/Í=û7½TXÈ04õX-K;Ñ1;µêÛñFYôR,ö>Yõ5 +ø²òõÛL3áIÃOÜìÇ?´üÖTTÑIFYñ<ÍRS-R;ËêGäHñ¾Á³Âã6íÁRÏÄñ¯¼÷L8ïøÐÜ0X/Ñ7+>ØÓ-öUÕÊûMA +UÑMH2ÍF¶BÃFH×9-òͶÕÁØÞïÁ<Ãéü×ÌA¸Úäìí=ÉËØ°TµÝÓÇAÌà<µQÓÂçáÆÔÓ1ÜäQP÷°ÅÏNEÙñʼä+ +õØÆÉB-Í>ðúʯÞJèóO»7ú¸7*XZ¯ÙðZÛT³ÈÁøâÇEæ×Ï÷ïýÂÁÃÒP:PÏFHõÎûÊ;Pû¶µÖÊÒO¾ôÛõèÓÎKà +@÷º¯ò¶MÍ»ÓXÏ6GXñýÈYê:ðNÈ-óÌÑ:±Ø-¹4±îÎL×Níñ7E;×Aó³ÔUÕXÚL*,+8+¾ýEGºLRÒZãðײç +ØÅرZ¶*Ý÷D1²çѹìIõ;á=èý4ÅÊB±øZÇõÛPCMèêøDõ.Êéò3ßÞÇçýÕòàä:PͼøôE-3ØÚFÖDüÅÐQÍòà +íç0×çÂêÉøD¶ó2*.@VÓ*üè?²³ëõÓÄÇÁT/5Õ´XÙÁóÍäÞ5ìÓÏBºÚ;OðؽÜñOLÕýÔ5@¯òZEGÜäߺ<ÉúÛ +A´.÷NÚÖP½ÖO7ÊüýN@°üJØÂѵÕçQ6Ïì¹4ð·2S<2Ûý˱RÊNÝÜÒÕ+Vß²*é/ÓG;EÕÓZôöÉÅTNæA0OVG´ +»»æÞºßÛ@4=ÜPä³9YñÚÞUÝBËÚÎÌ»²û̽÷ï>@·»åã7KÇ´GûDý=ÄAáßÙè¹å¹èPíIPB¯>»TYUW¹;*1Þä +¯Ûè³îXñïÁ¹ìÝÕ=õùYÀâæ9GìPËE¸øîÇ=Åñ;²2Î +IÁ:WàÊ>?,âÅ:M¿*>æÏOï/HÆÍKQ³Mº.ÅÙ¯M7öôÇÔÚ;Ì»SY5F5X5¯U*B0B¾ñ×X×<ã3=QÑF?UÙ²Ïø +ç<¿âR²Ù±àN30:::6Þ?OÈNçÐOÉ>7,>3Â36/Ò=6=¶PÉBç44Føî8Fúé>7·3MÆÖ +TôO»76¹1*JêÓ-¶¯âηù08F³Üé?UÓTÓ°ã4ÒD0B3¶ã*A@:BîÂ1ì>¾ÍøÓëKÈAó;áÄèÒ+NT׶YèéJÕ5* +*ÜL*X,¾ùD0ËD³èÄÛ<ÏJO9*üç3>WáÌûYY¹Æ+ñ³òÊ×XUöD +íÒ3=PÉ>SØNC³DÃU×±YèéJ×É**ä8S¾Iá@8Û·ðÌë,Õð,*Z?,:5*BYçàËùÐÜä»÷èãÉAYÛ¸¿X¯*á*Ñ+M +*++8êÖ²éÔKù³4¿4?è2Þ+*./ÚǾM*25¶ß+16îR6Ä+ÞÓ¾Â.ÜÄò*ÆB;×2**¼ÄÕ¿WÇ*¾õ4ä6رâ´Ã,Ì, +*ò=QÍH·è>0JF³D82,B1*Ëà/Ð+¾BÀ3-¹³6D²õÝäD1**PG*Ö»Z*A¹î´.¼ÃÕ+XÕÝPÃGÁôêBWó³1ä´ê´ +:Îø´ö´HîÀêÞÑ÷¾=S¶¾+12ÞF»ÀÃV0YÁôZÊ+ÎöÃLIÌò½Zé·ºËîà?Ô´2:à´æ´8îJ*ÂA¯ïé>6ÔRÑY10Î +:ôî*ìÖêòë72ÚÎÀ55:ÖÊOû1²/+ZEB¹:*DúÔ10îFH9Z5µRZµL:>¾ËÁÉ¿¾Ä¶äM,0/üÀÕC²æÎG5ÄH +*R/*æXZ¾I¹äÊصñèϱíÄëÈóD*¾Ôñ>Æà.6×·óèGýDH5*ìè,XNIJU-ÎÓ*òñÛÂÌÏ*EÕC*/AT¾Á+2ÕÚE +*ßL,4/ÜÀÕ×Yݺ÷4,:ܼÓ*6*FR¾:*9äVسèÌ÷°ãPÇö4Üä·óèÃUAXÚ¶Ë+.+¾Xá+¯+Ü5ÕWÛÂ?±0ËQ¹ÄêÕWÕRÃ<ÎHÛ2¾**÷òSBJ:J¯ÞNÞ+ +,Ò2ÚR¿58¶+/´UزçX½Ë6åZ¼P*¾¸-²â+*Ï/-õÜ0FÂÕ¿íýàé8úVßë+-ÒØ·XÞÈCUäêײH7D±Þ¸ã°ÃÈÊ +4ó¾åXÊëC0F²¿E<¶À+B4A²çÐÛüDµçÞ³ôêÍݶ*¹±Ý*JôC*»0*ÊåáËÜ÷ØS+1*¾B*ïÄÅ-*ÀGK¿Õî³á³å +1Þ·ï4äÊC°âÈ÷²èÒOÁôVC°@VAVÐìêVà2¾Ï¶OÉPëóÕ3IØP+BI*6×ýá*Þ6¾LÅõ-+/NìºëÒ¾-*îúÙ5-² +¾-äÍÎÞUöDδ*:R0Ò7Ê/*Ò-Ò.¶µñäÇ5µXÛ¹ð8Ü,*YåëׯàÀ÷à/´?*ÚZ¾L¾±5¶É-K*àÉØ:ÂéYùUJîú +éÞÕ+*î?,î,,È-à-ÚAYáÌG±ô6ØÏFÛ´7E´êÔ˱±¯ÙVÇîÀ-X¾Þ¿.FJ+-OÈÓZ¿1½èÕ¿Õ×ò;¾· +¾U7BY±äÊGÉ´*:à@â@Ê7Ô/ÚK²áºãTó,ÍUб,*;óèåøDùãéAYÑ*Aè´LZJ¾ÆK¹´º7ÒÓÖËZI2EåÄëرâ +ÀûàO°×¸O°¿´1ìôPí8C8ÏгVÖßÉøDÖ@1*Z¶óæËUåìÛ=*:öîË+ÞºøôçÙµ**ïËÉPVÕ**Þ?A*RZæäãÙµ +íÍøóF*¾Ã;=´ÊC³éèÕUÕUÖ7E´êØ7*¶´éÌ7506AU°ÈÒÒ*¾T÷°@Üîúéîå:-6ÖÆßT*¾NFîÐ0<è**ûÔ<° +ì¾ñ¾2=æ0Y-Þè*P+X2J¿*.5/:âÎ7ÓÛðD×´W.ìÒ7²5+îêØG-´éÓTÒÞÒºæÄX+Á@Öîúé:.ÏåűÌúéìS* +JÍ,0<¶ÂâEîF:ÐLÃÒR°7Y-Þ,*LåP+XRÞÃ0ÊÕSZàÈKÙÄë+Ð/ÔGöØ+¾ÖDõÔWEµêÎGMÄÂ÷Ø?Í°éUàÒ³á +äë÷Õ36ÉQ+FõR5@ÊèËG*Í1ßBº2¸¯9ëö>*<´ÆÆ;ò:.î-DîØCSJ+*ùJE7¹ôÇäHæ<Ð0ß¿5¶<ôBFöÀ-M2 +D2G¶;2Ã..***;,8Jß0ÖÖ±AF.¶¶*WôÃ,/ô4,V±D.@Ú÷ÅDÀT¾5¾RGR¾¶Q.Ã4ALJÖ±½Þô*:Äô*4À´¾GÏ +¾äÃè<¿H@é´Â´8æ¾ÉðÔF¹*êQP<êYÅ5ÒÕÇ-ÎÍJÕιüÞ586KæJ>ÞÃJFJÀ4+1Îìò,ê×ñS·D:,ÞHIÅFKK +*/LÕúϽÒ;Ò,Ú*Âè¿,¹LR=¶;,¾û/ÉF+À-X:òÌYðéNE´;*¿*Ñ*;*ïä;*¿*Ñ*ï*ÐëØ¿C7Å8H»÷ð7+ ++ƺöà*Ä,L*úÜ»úöïÙA9H´ÕÌÚ´ä¾÷4*:Û¸/+¾X-+ñ*M*ÐVÛKÀñYñêÇÙÒð9¹/Ý3¹ôÙ-°é¸Æ+ô2LY»¾ +òê+VåXº¿-D:7º97Jí7¸9âõLÉÆÎVÖYº7Z½ï/1Äî*;X.à+çSôêÓ±ÓAë+Ú3.ß-+ÌúM7KO +ß/J*.Ñ-Ã,õÂÎPîL.*ù5@·µB1ÄZÃÝ0ßPÐ9:Þ*JGÞ/3¾ZÁ*?ëLV×S´Öô*ïàØÅ+*Ù¯¾S´ö´.ÞÙOMäé@ +WÖT3SÑ,*8:J?¾Ë<³ÉØKöùYÑê×áì¾ûÀÖ8æûæé.-ÙQ<ĸ¹ÖβÞ42å¼»¶¾O<×/ó5DJå1À·ñ³1Üîà2*¾ +ÇÀ5¿¶¾.1ÜôJôN÷ïOSÔçFßÕ9ÊÈNæG2îìIT+X*LóJÃÞGÏÒ3ÚJÁ*¾°ÇEJ¶¾:ÙúØ5NE.JßÂ3U7B:2+¶* +*¼V×±áÄ/ù?60=´6ײñíZW*J·W;Þʺ²6º5+îêÔSÁ´ê@UÒ.?Tó²µ2¾´Sù@LõúéFèéåÇäÞ8ÈCùëECRµ +Ü0Wë÷FÛôK9UÜË>RO-Ô0LìØÀ0æä8ÃÌ:7×üFß3/*ô1Üîà2V+èò*F3Â-N:/¶Ý÷D,ñÒÛÐ>»:¶·Ü*ºÍ.Ö +LîÊõ¼ö9Ó*A*ϸÎ*ïÌR4?XÎK*J×â/:¾,á9AÄZËY-ß,è¾ÖJÙBCTÄêÖ5´E0îåæ;æáRNÞNù+7ÖÜIFJ6D +Jà=*»À²ò,À¸Q>îà5X6¿Ûèî:¾=¿¶¾.JéÒõ¿WBÏ7òKB²Î-I¾ö/æÌÇ*ù60+51ýÏ4î,:T1*ù+¹LRÒ¶; +,+*ãÚ°àÄ7åÇ°æ,æèÒÍG7¾E-Jï +àÃ51îìÖGEÔ5@UÔNÓT;ë¾T>¾îè´ZîúéÐ3+Rü´.æó7å=<Á.Ì.ÔIÐ*Èü<Êêéò4:ÌÑLX@×ÖÕû/+öµðà +·ÙPWD´ü7Gºöê×MQ̾ù´RäÙW54ùöñ俹èÊ9=üEÕJº:X.WV¿Ã5ÕÌÜ+*ÇAYC*êU/îøôëáµ9I¼úºÝ¼úò +ÛM1XEÝ0ëÖYܼ÷ðã½÷¸09ݼúøOû³ÅMJàOÉ32иWÑZÞÆÙÉMÓÚV°¯Y-àäÓÛB±çÐ?-ÔÉA4P6ØJDåÄËر +ãÄ35äÃ3EµÔµñéÙùõïÝMÕWØðâ̶ëºòÐ3ù/Ê+5´ÊØçFßÄë+²1,¾µ=ëQÖ»0*Û¯±ôUÔJ´NÓ4?XêLÛñüó +õÎ/åÝ0ÄËÕ÷°èÖWÉäVC°ÞVF6SÉ´WZ¾QÑ07×ZàðÓóßÉ-ÕHýûøóÛüøê¿ÑԺׯµùXD²áÆßð/ÌËÓ?+ÒÕG +*Ù-**î8*¾µÑä6C¯ÜZÓLÔ¸¯E8öÐÜ0Æ7YÁ±K²Ì7Ù³èØ¿-Å7K¹PW¶Ë±ñäËD±ã20ôî3í+ÃÔµõøûýýüûí +1Iº´9GD°ÚöøßUPÂS¹¿éÕ.ZÔ0ÜÃÜG3E11ÌE²Þ²ç<0Ú»UYñäÆ7MäVD³è*Ù;°5QXFµ+µÔËD³ëPËGEÕÝ +ãÑ/-**ILíííí÷ùõYüý9°ñÖO¹Üú¶é¶ò6D¶ìÖÃÊ¿¶K*<ÓÈùÍ +ÖÊY¹°âÈCÉ´WE´LWØγ״WFÙ²é@DÞÏSá07èïó·,;ó³8**,VBP8̼6¹ÆËÍAÜE´èÊý<>÷êËÄÅ°9°5¶í +L*FFµéÔ³ÙPWEê´WE¶î0,OF·1ðGýÕÛà<¾ü¾+ºì59WUÅHÚóU<¶-Xõý:ɵOô³åýîZGÌSKH+R´Æ<ýÐ÷D +µÔVC±æÖ·é0ÓOÑ´ëضEïäWÂÙ½éÄëD´éäóæñµÒïàéä*Î;ÌÊ¿ÅI¯ñýûͽ8F´èÏ»YÀøà;ÙÄëE+AX.J; +*º5½¾êÒ¯áPëÞÍ»5QÌÚJF3QX,YñËç:Ö¾GõR5úÀSÛ×-Æ1ßBÀ1KÕB7NæK2ÞêUÈLFZ²H/¾Â75:ôBF:-ç +áñ7:/3ÖA4>Ö53J/EÞDUB¯B4Zê/1ïΰÒPÎL*+ZÕ0ß¿°-ÍLA¹QÊÖ5ì±ÖZÈ+ÖIK.ß8Þ¿ÂÚ+ÀU½¶Ï:1è +ïÂNX¾ã¿DFÐÈKÀ<¾4Ź½3N¶ÏGÉöØÞ³TôêØöîÎØRó·´Þ´Â7ÚÃ×S¹²Ñé½ÕßÓÕ·**8²<×·õú?´ÌI»ñܱ +á8û@óUAWÂKÒê*ÞNE¾5»*ë,èáûÊÚ·ïÜÓ°ÝýD9ÂX·ôêK?ñëµý<ÃG*ÂÏ+ÎÀÎ7.èÞVÙ;Øßü¾ºB*ôÂ,DÕ +ØNJ¾Î¹ü¾6N,ïµÜ@NÞHIÅFKN*ÓEÛÞ/A**O+ÀQãç¾CÀMF:1JIZ-9´:¸´BF»-ûGVRO¶À2ÀG¾-3·-.ÕüîLK8 +ÂÞ;Ç>ÖÚ÷ɹN2,Rï¶öA*ÞÇ@VàéÚÚ¶ðèßÉ1YGTõ8ÜJ*Á1ÍÛ¸/+üXHºôì3ÒEÜâíóÒéä*>À±ZK9ÅÝâä÷ +ŵü¹ôéÌã¸Å+ã×ÁQíܼ1*Þ÷ïáµíܺ÷ô×¼üöç¹¾,åÝP<ò8.*5LZJõ±Y±ê×õô:@*ÔÕ220´ÍÞé¿*ÆD3í +JÊ°åÜçFßÕÍNëÃÕG2?S¼úÏÕÛR*¸¼:Ê<+0ËÀDÓõîóB/¹î³µ2K*VR3·-.ÕüîLK8ÂÞ;Ç>ÖÚ÷ɱNBºSï¶ +öA*ÞÇ@Vàéú×±çØ¿ùÔëDíôWE´éØ·áäÕ¯ñäWE´êÚ/1æ´+ýÌÝÌè÷Õ·**.:¯´óðïÎý7×SëÖó¸NöXÖ¿Ã5 +µ8Û2¾2ô÷³DµXÚÎøç¾ñYñÃÞCíËG>±Ýǵ°ÎìöQJ*AíP4ù,Òç/1î,ÚWë÷FÛôп-+¾X;ʹêú3@ç-¿Éñ/ +Þù/0Æ,Úíº7¿·èì+BÒ3FJ+°û·À-¿ÎD²>½EàÒ-ÚEO¶Á2,*Ò-3·,.;*QüîLK8Þé;ÇÒÕÛöÉETC´RºSï¶ +öAÐ*õ5áÆòé*% +d +432 139[1 0 0 1 0 0]sl 8 mask 0 0 di +/mask 7506 string uc +*Þù1¾õ½¼íýúÜý÷»ýñùüåõûÍíùIÝõ½¼íýúÜý÷»ýñùüåõûÍíùIÝL*% +d +/sl 60048 string uc +äÕí´ê9+¾Ñ,Îê׳ëدñéØ·ùÔ7Ù´ì²E´ëØ·á@:ÞÝ·éäËE¾¼çÑëÈ*N+*XÞ1²ºCYÝý½*¾ùÍÍG·ñèÁ-óæ +ÐWáä7Ú¶ðêÚ·òä?*¶·ñâ»ùPÌF·´ÌFýýY×á/-öåZ2ÖÞ¼¹Ñ.ºK>ìê+ZåH.Þ7Zå8*Ã.ʲEôÕDF68ÞØ@ö +P³K¿8O-á-;*6õÐ4->>ÇÐNJ1¼*ï5ÎGAòJ²W22¶J+P²-»245è.8Î*Û¾Îì+¸RSÉÖMí¾ðÒJ·ß;L**A<ð +.2XûßÃÀFJÆMÙÕ÷PýýùQI»ùñ4ÓC±æÖÃ5Y5+JôæÇ5¯5Z-ê,*êسçÐÛüD9´:¸µã--FϾäÓ@ßÌû+*Û°ËL=ç»ÀîN5VÔë2ëòíÕ3²æ8>B» +5QX?ÙÊ5µàäßÃù´ëÖÄÒí5â¾<ñD9îçýýúáQåìÆ9»ôêËÕËÖ+¶Oêß5Á*ÕÝä±X4E6ÌÜ¿üDóVóO¹Þ¯,KöØ +AÖ¼@Þ×±õÛKõ¿úÖU/ÝױݻßÇÕ?FÆ0Ü1-*À×1ÊÌÛÎVÖå6.ÎÍÑ*µØ¾Ô±AÜäÕµÝÄÚ¯éÔ**ÜÃÔ·¹öðãÑQ +YHºüÌG¹ôâ·±@Ó³Åý@ËâÜä5æøýýüõæ×ùíJÛ¿MõÌH¼-*Ü9ݼûöç¹µÌÁñÔWE´ëÖ¯=ìÜÛüD1ËÀDÓõîóÖ +ìDË=úø¿Ã¿.¯âÕXP=ÇêÀDù/;ZÎùº7V½ñ,Ò¾¶ô¿+À¿ëà*¸¼:ÊC-3ÄUã+V¶Ð/»µ1ûù³B8ÚµíÞ/Qɵð +LÕëÛºøô˲·¼ùòÛ=åëUöNýç>ǻ˶ö@ÜäßYíìEæ·ööC½ýüûE-R,¾üô×MQXºÛ¯ÙäWÚéYñê³æRü´ÂSêû +¿Þã÷,îË»+âË»;¶äMêìÚÊíêÏ2¹ôÎÊûäÜÇ¿àÜ*³¸µXÞéJâW5*ôÔôöåç³±åìøñ.=³ÂúÇÈ+·ÀEÒ-W¾*Ö +AíPTý¼ËUÑ@ËD³èÐKáåÎSéäËEµKµL8ÛÆ´½ÕÌÚµYãñY3¸Nô³H-áýýóµÕÌÛõüçËÚ¸õîß-÷LçOHºï*-ó +ï³ØWD±âÈK¹ü³Xè<ôæEÎâ¼Î½*ï5ÎGê³W¾êIÁKÞàÓÕ·ï3¾°å¼¹Ñ.ºK>ìêY³¶öä/ºí98:,ÞìE?ÝÎJ1ô +êYJRµÜÄ×50¯B¹W5X²+,J;+=ëAØγÝPÒWEZ-çàëúسèÔ¯é0ÌFBµÞÄëÏ=Éü½Â><ùDµLóýýöÓUåìµíâ +ä7Ú¶ñæK¹õîãÑÕæ´.*í×E1XÙöÞñY¹ç+ZûS2ÎéEÌåFFQ.Ï?Ñ@ÉÕ¿³/*±³>7ز+*´ËE¶í0××Û»µçùåÅ +K°YñY.ýýýÜøóéW°¿äÖ/õKµïä×¹õ,éÆH»øP×WÛ·íÔãô³µKJàOÙÀò²ðÕK¶ïÞÃ5A.J࿹**îWZËÁ5QX +Û¹÷*ä³òñµU:×ÕÇEÔåéÚøó·çÇMQÌF9AÌÚ·óìçáåù÷ùåÍݽ*ý/*J½üöãMAûÕ»/¯¶ü³?VJX3J:JZJÊW +¾ËÍUå8ÞÜôÝùÈÊé2¾Iæá麸óçíìéÅYHÀåìÚÖÝîHÁµíI¾Ï³ÞY**ðÙA9ܸYèéæD´êÀ*L*8J*V¹.*öÍZ +ùâWãô³¼2ûýüëQÝüÚ³AØPéË-Õ7E³Ï1Ì7Ú¹øòãÁõÌÙUåXÛ¶íدùÐÜÄÖ¯é´Ø@2îâ+T,úD³è8P´íøÐê +Üä7ëýÝIûòé¯5ÍõPåFG·îܯÑ07DÊôËG»øäíàêßÙöÙYó³/*16î2-BE³E·ÙYúQ°Xñ-NûýÍÝúòé7ðÁóè +/Ì/Õï´éÒOÙäëÚïEMÅÌÚµ¯3+õYèéêÚ·òäG**JÕ:*æÓ1Â+æ´SÕÙµÙP°Yñ6Îûý½Ý¹òè/¯ÀßçÏ1ß2XìF +¸öô÷ñÅÍðÑQíHºòàîL>ÜÒÕËO/ZÃ*6,Ò-ÊY··ñÖKã8Ýè?Ç»ËÑöõý½ûêÍUõÀû×UQ8F¶îºF·òêçáåÍIí +µÍܺøòç±QàÃEÏ*ðÜÒÕSµëÚ³éäë+*Fξ;¾3+âèÛ¼ÊH<ùDÕ;ðýýöÕMÝì-åùÔëزèÖ×µïä×5R5éôóèÓ +EµÌE³ë,*ÂÙ´½èéÐ**?õ³ºü0MNô³+æ·ZãùµÕJ°YñRæüýý¼øòè¿ëR²,Q8Û×óïØÖ³3õWâDZUX=íÚ»SY±ôê×Uµ0:*::îà*à,ÒÛ»±ááÍ +@CÕ;*W2ÅÕ·òèÕÝ?Õ8ܼ8ZJ¾*û÷éÅ9ðÙQÍHºMêO3Á5Yèé>Û·òäËEA4Î*:<î.,îHØïÓùÈëéº7ëýûÙÍ +Ìû¸´ÙXH¼üü÷ESMùð×EÎRÊBØBÞöÐÜ0Ü?**¶±*Á*?+O*¶9WöÓGãô³ÜåùýüéMÝHEõºQíH»÷ì?PE·íÚ· +¹ð2A6îQ»7¾5*RVE2*îäé4,²ÙºµáÕì>CÕ÷*ÃöýåY¼¸ôâ·áº+*8íG¹ôèË-å7²5XEÇBɶ¾¿YèéXFëG, +ÊÜ·³9?5ͼAGHîKPYñ.Jûý½Iºòë3±ÃöÀè.Û·+*<ÌÙðó5Ƕ;4Yèé2ܹöÌ*ÞN¾Ú¹A4Z:Þå3?Û·÷úã;E +ÔýæFÆäéú¿VýýéõH»¹Ó6Ïݼúð/IÊIÊ,6+ÊNFö/¶õO»7ÕUÅXG¸òæÃ+¾¸-*/+?*ÜÒ²ãÚÕµ½½ÝXYúóÙ> +0ðöDçZWºË±ÍÜÛï¾õì×UÜ8ËRжÎ41,JèÏùÐÜ0àG***õà+ìB¾ÝÉ4¸±íãÇQõìáéõYºîÎæJÉ6âäQöùý½ +üóç×¹ßøÞ»ùôWF¶-*ÅHÏ6Î2îO»7ƹղÏ+1JÞãÓÉØ÷°íâ¿ùîâÏYýHü¼ú4ÚïÒéV.¶û³¼MãýýïQÍXÛúà +õLè°:¾Ió³µ*:Æ0â/+Ú¾*Yõ;äÖÜÅ-AìÅAÅ8û¸öîçùüøÁùÓTè/+QÝPƾ7YáèÓYíùWDµ/2³¼Q:ïÀ,¼Ó +YÉ»ø8*JGÞ:*ê5.B,î*ó;ò3·¶ïß1êÚ¸òéÝ͵ÝݼÂGÖNT78¼Ë¹ïúݽùåÓUµÌQÜÁõ*;×Ò³ÂìF¶ïäáÖÛùöðëõýí¼´ñé<Ê-QÜäùêýÍÝúòéÏUçIçÇEQìÒß3QFÖ¾¿¾C¾Õó³QÌÃã×±æ²UÇøÌ +º¾Å-ÅXÜÂBËÈôßÅQÍ8ûøôïûºùòñõýY¼²,VîÎ-Yñ½üýÝ9ÜøôçS´Ü@íÎß7*H¿Ý:¾Qó³¾X.Þ>J*¾ÕÙ¶Ó +ÝVòÚèäÆ÷àÊåëF¸1XÞçϱAìIÄÈËùñæÍMåìÛ¹÷ü»ûöõùùåHE÷ËÆã¿Õ÷úêϵåÜØN¯²ìEµQÖ@òÎ,:,Ï4 +ïS»ËïéÕ9Ý5+îüè3Ê5ØYÓºG¶á¸910Û³KÙ1YI¼ß-+.XÍܼ+/õ8üFGÕüºÂ˹9YHüóøýõõíUíãÄLÒýüÛ +¼õèÙ¹µ7Î>EµìE¹+0¾í*Jãß+0F*¿Aó³Þì/*ƹ÷àÁûMͯOµô7»°µºÕWàÅKÅGÇHè5õâ1FØæë»éX8»¸Ý +Ì»¹ôêãÍÝéµý8½ý½üåMùìü÷áåX»ùõ4øHOâü+ºá/µ9îO»ËÕMÅ8Û¸òè×ñìèãéR:MÒËÛ¯ÚÄ1ÕüÛزÍEõ +íظíPÂÅò·5µ¼ÞéÛÒ̽ºíÓ¯¹íçßÍåÜÛ¹ùÜõ³ÚØ;½ÍüÝõÍ9½½úê×½õëAÑÁâUJ¿ìÞÚöÂ-ж-*?,ÎìÍø +Ø+*½µXÛ¼2-Dòæ¹4HÊ@°Þ¼9¹ßÒQÑÌ»öYàüÏÙ¸0·8Â1ÌÍUåë²üýù¹õÔ3ýWF¹÷öó»ú÷èÕÁ´ËÕ³íÚ½»î +óïÕÅìÕµÕ8BCLä@<Ë+ãÃPÂìÛ2¾Æ¾-,*óHÓÕ·¹óæ»ÁQÎÙXX:OÊF1½ÜÉÖ=äECíâÅ+ÅôÇQù8H@H²ñúG¼ +6óßÁAî¶ýýý½ü÷×ÍÜéÛÐûUÙ¸û÷ñ¹øñËù¼ËݺíÜH¼÷è×¹ÕXÖõéÕÑSÏ0áÓ-ôì²ëG.ºSYñýüGÚE9Ñ·ïº +ÝÄò.°Ûúµ¯ÕVÕ°Þ½9ñãê79´ÚÕòéÒíøÇûÞ¯EQ:Wæ<¼ý*EýÝIüõÒé0ûçÛ,8̼ýüùå¹÷òÛñHÝHºòÌG¹ð +ÆÑHÃØÒ-»Bíݽüüû-ýä*83Þ²ÞÉøDÅÅÏRãƽ,úä×çUõîäÌPõ¸ÕÃ39ìVÖ°ãìÕðÝÉ==´ÛØ9Å»AO¾BÙ´ô +ÛQÁAÝIºèâGýôÛíÐêAÎLý·µõøûùéÍì¿ÕÅIܸõí½¹àQÇéàÔ½ +ÍHÊÕíO7D²æ¿OMôÃSEÜ»øñÜûúÞÛËU¹äUØQõ1±TééýùÅíÓ½/RíÞÂ1E¸Ë²Y±íÌÜúëä²G·ñ,åð*¾Aó³H +ÎÞ¾+ý+ʵÌéù1ÑRÖÇ»AõÓ÷PO4´±ñæÛôÚZGɼ7¸°AX¹ôà°YͼêÝT²¶òòáóÁø·ëô363´÷ýìýûîK?X· +Ñõ²3ñPÏUí´O8í/Ê1*ÜÒÕ+º1*ÞíÓÁÔëGÄAɸÚÌ·9µËµçÑܵëÂ4IðòÙ×µä4óèÀ2ÇPóýå?æZÛ¼;GVÖ +H;ÜáÄ6RõYýýüï+µNæOØNÇïÞ¼úÚÇ°îXJL40ö×Ü@àäíÏÉÔWF½KÙ¸ÝÒÉIµÛ¶ä´XÌHÄ=G@²1ÏíN3¯K¿ö +6Aô-UÀêö9ÞÃOûìéKýÜõͼ¼ýýýûéBáC-äÉCNBîJ¾÷Ü÷µíÚ·éÔëEÆúBùæ4ýGâõÇ»7âEÍ4.î÷ìÛE1ËÛ +ÅFÛRÕ¾YYYÜÕOÕAXÛ¿>+ÌùåOØò@9WöàAø·îB°ò·:Þ¿ìñUäéûáÝIݹåÍýºéSÝÇðå¾÷¸´VÖZÞÚD¸ïÜ· +Ù@¯Rý÷Cûú÷ï5ÓõLèÈAüÒÕ¿ºïS:Y*J;¾åÉ1ÍG¶éÚ;òMÏÔëúúùì¸Gã»ø.ÌêBWHåç·´×ÈãEåè;øÁ÷±X +ñWM¸µ+WLÔÌý»úHý½ýúßý?Õ1¯TõX*û+Ë÷ºøFüÒ×;Pï´ÝûÒüTéP,/ܺYèé:KÀ-18F-B,R1î,ýË-Õìß +ÍV»YèéßíÄÙ;HÍæZKÉG=T-åãÌíÍýZÙÚIùêÓ×µ8éîEÀ¼ó²ùôã½AüCôã3ÌÚ¹AúZ,îJ,²ßÄÌíµÅ÷R?ÜÒ +Õ+»¿èJí:ÞFÞ7/¶»ðÒ·Éæ²êÞX¹¹ôѵ½ã1ÌõÁBÏLØëÅYôèM¹´ºúññÚÛôÓ?á÷T廼ËWÖXÒòâ7FÚöÁ-Ä +Rþ;³HíIð¿2.+úSYEÛTR¹:+³+-+HXD´ì@<7ÉÖÉì7Ûô×1çÛñ2Ñ°ô6·³äÚGñèÂùÌ.·ÐIíôϲëÆó°WQ +â×UI¹¿1?+ó+M+@Ú½ÆùXÞÞÉøDÖõÀ×ÌE¾ÉÙÄWÚÄSëHôÛÙÅI7óíçYÛYîî9µPºBO½æ6D¹G±Íµ7ýèÃEòÙ +VüJñ+úɼñ¿1>îQ»W<*îøôëáå9G¶4ÛRÓ²95õü¼ºÍÛôUߺ1µâå9ì=ÅÏôôÓóTÚ°ÏPòÔX>KS+æU2¶³1? +*Ö<¹ý4óÃ0úSÝ0.±+*YæÎKÂ2=äUåïçÙ3³VÜÎÏù÷çÇ-´çVڴ̱ôòÍûܳ¹>³ÚAî;@¾¿36ÀóòìÝã·¾íô +éßéÔíݽ*-*ºFµñæë²4ÔYA»úúïPMXXìKÕ75ôSµîú3½û8¿øßYI°´U¸ü½¼ß*Ýèé*Ý;*ßÅYCÞÃÑ?1B¼ú +øóEX-üÀëºJ·ïæ».T>UÕé¹¹ìÁÝXóè·¼.A8Þ½*ý¿¼QâZ²ôÞ8ìÂÞ¿,æ¾Çí19ÆíÍøØ-Q¹Þ±A,îÞ-,.ºÝ +¼ü*46ö9I½õÌH½*+06åã×áÆÑåÔVÃDÐóäËEô0*QÝãݽ¿´Ï11Òô,<8Öë++T¿Ëõ9ÚÅíÉøDæõ<*¾Ý±/î* +-4FR:J¼÷êGºøôËÛ÷¿ïäÛMõ=Ü·ÀÍÞ=**+1*äêîݽüøï5+¶Ô2A2*IíµÖYÝAó³ÎîO**ÁÅ>Þ2@ÂR¯*¾J +NBJ@8ë-<º2¯àÂ3翸Á,µ0:L¾RÖ3Õ¸,3Ì +á×¾UÍÅVʶü³*J*¾Â:C1ÕÆîÉñçÊʺZ1F:**N¿+ëï³:Z:ö6J2Þ//Ò0>,ö¿/7HºÖ:Þ¼¾í³*¾»Õ@Þùç= +AYJÀ-ù¯·ëÜÃùôVÕ*´õ>SÓ·.7DV2+̴ܲàÆ´3RZ,Y±êï,:;KFãë-×ð*Ví³Y-1=+*ÅBïLÄ7EXºÖ2Õ* +:6Zôó,-XF¾1úµíG»1GÀò:?ø5Õ6D·êFBVÒN»Ð.æ=QA;MÂ5ÍUäÆ>TÊB*:ÁÕ·ñ*>ZåìØÍ,<Âëæ*õ@¿> +µ4¼¯õ7S¯Þ¾èõèµÞü¾0,*úÞ2+:ZÕÂ+·á¼¯7ô³+ÕºîP8P>ð0*1*J8ôO6²æÅ×±½ÛÇ-+7à-ÆÛ/Û8ÏNåº +Ë+6B:Þ*¾K¾=-222+2.î,3@²æ:K½ùÊÜ»ùò?*.-./ÚÞ¾ç¾3*YAë×·/MÜâç³ÌÂR9Û»úÞ÷¯ØTÇ,³Ç:HH +3F¾4IåÜçÎ>NB**0äî³H>*Lê×Mè-ÚB,=D*Ì8+¾-¿<@ÑO*ÓÈ1*ù-D2*î2*¾¼ËÛQ*H7¹>F×Õ/Úð¿E·R +=ÆúéVUô½QŹRôÅ*æM¼·º*.GȶL5Gñ³=±C.¾3FÆÎß=YE¯¹:³±+=+?,+*ü8DXäêONÇÑMWèðLÁ7C,òð +ÈëTóS=N¾F9GÏò÷GÛ=à:.RB*:¿ÕC**>ÊH5-*ÀÉ.**A.JA*:XF*0á-á²*ÞîDZåH/*DÉ×AÕ6>ÇÃ+GùØ +¾UH¶=1÷-JÚ+ÓPÅèò+O**ú¿1FÈÅä÷Ø=**,Y½Ë?²öîßöز*Þó˱07ظ2Q¸ËNÃÔʶ¯ãÅñ³X4-/8*Ò,Fξ5.R6îýêë¹ö:H»1ÛáÅ9?ðÑAÏLë1ìÖVÍ:ÃÕïG·ìîÇÕÏá=*Æ +à麷@?T-ÅÓK+2ðRÛúõS4BÒðËG¹ñÅ+*ô1²:*Þ±:ÆÏ<¹ôçS¾Vµ@îÆ1ìâK7JôYñø<.ز*>ôÚË>0-LFÏ +ÂEØæBY¹0ZAAÞ1<º2ïKÁ-ýS¼ùôïÙÅí*ÞFJN*êE:2>B*îöØ×=ÆÏK¹öFÞÁ7=à¹õZKÅÅWBVÍ87,âùê¿á +P7².9ÀÁAöõúé<9+ðºßíXöÑ0÷د°BB2îäÌßÞ3¾++RBÖÃ8<úÕXô8+Èè8ºËÙ±0XHÀ4ù·ñí0èÂ3îG¿ùº +Ú±ÛPKDN2:åM1ÌD²¶÷U¶0¯QYMéùÚçóX¹ý³+±Zî·*Ê×·Á1=Lº¶îݼÅßôR,ºÛZçâçM2ÏGÅ@ÎäÈ1ùëEå +Ó/Á³ÈÐLÃ,·G·ìÒ¯=ÉܾÁò0¯àÂ3õñ³GY2åÝ06H+*¶öÆ+Þß*¾Z*ͶïLÄ5?D>Æ,,.B.Î,îÞ+̳¾Ýá³Ê +D·÷0ôñæâCºR:Ý·êFCXÓP»Øâæ9ÍñÄ7D¼Âï=R.<6ßP5E¼úÐÜPN¾ÞÌî³¹ñæC¾±/Ö:LÄ:Q¼SY¹0Z=Z/¯?,Âîß¿·èÎ +Þ½üøó¹J*ï*Q4*ú/*6,æ¼ñRÛà?VRËYé?È>VÚ¼óðÖRÇ4³ÇÎH¹BÌE´,»¸PY:¿ôéAÎKÃ6ÝÒÕGÎ×ñ/î/9 +PÒR¯àÁ,ëݽüú/+F2DöAüÓ>:LÎ:ÞåÇLãÈ@YàÀ;VÎD/*öSÔRÇ,ãçÏI¾QöDäÇÂ4Ù»³/@îKÃ4ýÓÕ¿Àµ4 +îJ,XºËRÒÖ¼»Ö0ü7*6-.ÒË+ÚK*DAÈÒ2EÅÓÈ=PÉ@WÐÂGÇSÑRÇðË/¶À/ù÷àòçÒ³ì<ÞQt8G¸>ǵøÂSSÏLÆ@COÅ.ë3XñµøâKá +R×·û44îJÁ3?TúÐÜä6D²Öî/+F2¾È¾;E·BïºË>6¶ÐôNéÈê84¾éUÅ?òÙQíË4ÖäÂ6CDòQ8Dß³Êå;L¸LW +ÑõJ2îJÂ4AXúÓÜôÈ*àç8FJ6*KÒƯûøëÎGÙ*ý1ÎãâÛ¯¸èâ=;T²ÆÎJ¼ööF´æί=ÅÍL4ÈÓSáæ÷UL:+58 +ú»SY5È<5?ÄýH3¾Ï*ζ,Þ?°ò2OÆ>¯ðÍNÓØOVD +µñJÜMXM,1LúÔÜ´@³0*FH±¾T*üBÐNÇ;IXºÕ¶µ0J7HÊêHXÉÅVì94Â1ÌD²æп³¯³ÔíÝ¿0Y6»ïÛ²>C*î +Q»ë>DZ6.R0B3¶¯*¾¯ºËÃ.çÏàRA?2:Þ¾+70.F<*7Ç÷»×/3Ü=+UR:J½+/²:KÁO/Å2JDÈ6ÇöÐÜ0<ä** +8ïLÅ7G¸êBÏEN¶îIÃ?±äÒ>Q0â1îÞ=*Z+OÝÝø0-°À*LBÏá*¾Aó³J¯êV6ÞBJ3ÞVJ¯*XòæïáÅï³¼îJͺ +99äÙÔÈGØ=ã½üÀÍM*ÎßÇ÷åüE4-.9:Þ/-Ê,Ò/Fƾ5*Î/?¼òöÏáIó³Q*ZÔ0>0+Fß*ßö¯**Z6Ýô+ãäüë +EôæÇG¯Ý´ÅüúõÍÝ:CMC¹ûÀ+²Q,îÈ,ä*äìT+ä+êJÀ0AÌNºSYEÉL9IÀºÞ*å4JLÐ6*îéD»AùNÇPôøÄííýùõ +ÝI¼÷êÁùéË1Á/¹³P¾ÎYÆ7?VæîÃÊOÞ*òîàÅ=3Yèé*á××L¾U-B9*8Q×àéºÝÜáíA?,**ý¾Ä=À½ñùýYüý +9°ñìÇÙ8»C¯×MËܽûøÃÇ3Á0Á*8ÖW>Þ++Fæ*±Ö:LÄï³@°NýýIÒÕ¿Æ1+*NÜ¿*IçÐOÉ?O¸âÆùãOü¼O8* +0*òÃCÑò·äIàäýùÕ½HºðÖQùâ³ß87ÆîK¾¯@*J8OÔ6Ç*@ÝÒÕ¿Å/*Þ¯.Þ+,Ê,Ò/öÆ=QÐF3ÐàÂD:Yò÷5 +ØRYñ**2:;åÑÆÚQêÀêF»öäµÑÔVAX³TZÞ½+=8**ÂÓ*±**ïÃ/¶Â3CÀ>Ç<ãÇZðÉøØFX6H8´¹Ä27DúUÛ¼ +µÞßÃÕË+J8ºRÛNûÀêºûöå³ÑÔʵWõÇíI¿07H²Æ8HºB4:*:8Z4:,J>¸VYýÐÜäD´êÆï6J:J6J.Þ:Þ>¾½ +6SÐâÆ7=HÒ2+ÁíIÉ¿UYñ**6î+1AÇêUêJý;EåÛøðÜN+ô=¶Ú´äÔ8Î::0¾>¼>C:ïW»ë3+ÊÙ7+ÊÛÏÄ7K8 +±÷Ä39T6º¼½O:ÞÛÕ/+æ-FüÖðØûÇ°¹öã·¹ì¹ÕWÌNîÝ¿O³å2*@ÄùÃ*ºSY±=SÐFC**R@-FC¾ï*µ3=PÊA +Q¸ò2QLXIÜÎÑXE-Î/È.àÔóø5°-ôÁêF³ã½¿HÒÆÏMæÖ,:*Î2JNÜ6Æ:B±¼SY-99**¾6+¶<+ã+:°3̽ÚR +ÏNãô»í·ü³8**+8ÆòSP@êY°ñûóÙõHFõéÐ7ðÎ3?0*¾ÂÓ*-+úïâÈ?SÐV÷O»7¶V407-+8ÇöR¾¾6CÊëM° +Ê2¯OäôG-D3ºW2*1Þ¾NζÇéA°¹üõÝUõܺUÂ-*:ïÃ+BÂ2EÈVZýÒÜ@¿*öÅ3<á¸îâåöû´ÇáéP×ó +Á>Äø+´IàÄõÁÝÌG¶Ó39F7JF12-¶î³ZðåøDööòéºâÇ>SÌ6×ÏLãåÔ÷úPÇNñ³K*̾.<ÍâüýIݴͼ»µÒ3 +9,*·FÇðRàÉøDR÷0*¾ã5-¶J*HñPËE±ÜFCÐ8.ÚÛüÆE°øØ:*ýîíýýô×Må8µIû2ðá,**×:ÞMÔÊSÑäËD± +¼SY-;-**â*¾·¾Ï¾Þ¾K*9×ÐOÉ@UÈòÆC<66ü¼»<<ùDå¯ðýýõÙYåìPù¶¯**SÒRïB¾WÚƯâ-5úÐÜôÞÀ +í@¾ÅFÇUÐN×<ãµó³,<â:**C8Î6Z.JIÄN×° +OÈñ³øZ¯úýW7ÆêéFÍßýýÙåÜû¸Ø6ñß-R¹Î*°¯áø³KÇðÍøØ:ØÙ,Z9IÈFCÞäQ¼â¶;áàôï-OÇ·Ë-ÞNùýý +ñϵµéï@Nö*è8>¾ZúÖÜPTÔúÐÜ0XP**HñPÌF·è²·ÐÈò2C¼õðùDåOüýýóÓ±ÍììÀ6Sð/*¾Æ;+²°ãÊ+3 +K*YèéJâ*¾ÇÓ51¶<*¼ÐãÉAWÐ.×Ï°6C÷ûë»°ùDQäýýýðϱ½Xä°º-*ºïLÄ6EÀN·úQVØúÔÜPLÀºñ²1<Þ +KÀF×ÐOÉ@Q¸Å67Ì.7½ðD×»ËAùýI½÷èÛAAYAÄÑÃ*ÊÔ/-ÂÕ/N:É@UüUYU.¶ÎéA4*RÔÕ÷Å6ùãË8IW@Ç· +Ë9Æôý½ûëͱAÜÓHøóC*ïBð¿ÕûSÝ0<-æÄ*8Fë3×+ÇïéîðúØøñ5ü¼ÐN<ùD×îXºËÀÝìûôÏ39¼7Æ6ö-A½ +ÓÕ¿Ê3*¾íêçñQÍEWÐÒ//ÅÝÍÏÄâÜ䳺úý½üôèÙEëRMк0*æÐäÌI½0O*ͽèÕïâÇúüý´6<ùDÕ +MöýýóÓYÝÌRõ²öLè¾á-YEÆ*BM*ÜÒÕ?Ï7WÕ+>/Ê+¾ÈÏà˴Vêܽ¯1<ùDS²¸ºËXÝÌ»ë¼.ÁèàèÈ*Æá +Ä:5<¼ÓYI+ع1:>S8±·ÉSÌV0¶Î*ÆÔáÅQ¾Û:*OàÊC=äÖ.¾÷W»ëë+ÆÇ?áéºPÊ?M¼ºøø»¯êP´Yñßüýí½ü¸ôÆKäZDö¶Ð0H0 +*.-¾Çï2=**ñ5øD%% +d +432 139[1 0 0 1 0 0]sl 8 mask 0 139 di +/mask 7506 string uc +*Þù1¾õ½¼íýúÜý÷»ýñùüåõûÍíùIÝõ½¼íýúÜý÷»ýñùüåõûÍíùIÝL*% +d +/sl 60048 string uc +äÕí´ê9+*Ð,¾°OÈA**TÔ*F**µ3ñOÊC±à²ÇI4Ô8½V@*ð,*Í:ìýý÷ÛMÍÌúÀúB**F8,.+Â-ÞÇB³äÊC.¾ +°øýýÈG¼6/Ö´¼ûظø³Îð³*.Õ×ÊB¯è6*Î/÷°ÍͲÇâÜPXJ6YñêÍYõ5-ÎÊD±8 +*ZË:-ÑõI7ÀâÜäáÒûý½»óçÓÉÚ6áèX3¾ÃVCñäæDÖ÷üéZWüü±ó¸+*äÝýØÒýÉÕ+Éå*:ÐÔ6*XHæDÔC±Nß +óùÝÈÊéF¾GÅýýµIÛ¸×ü´ºëöø3Õ3Ç?1éÄêX½79ãíÝÖ*+¾1*æ*+úùIÈ÷Í÷D¸÷ðã5ZE2ZAWÜÂC¾O²ì6* +¾?/Ñ1íS´Yñ¿Nýý½ü·óèçXÆ;Í+¾ÈN÷ðPÌF1<ÞäéYÁ:÷ùý@ÞRë:¾ò1÷âé2ô,3M+*¾2¾N+*JY03ìóJë +᯾âK<õ¯:WÞâ÷³Ò=å̵*î¾,FåÌF»ü.ø=R9D>æÎE5Ñí72ãô³üSøýüçQåìFÙ³3=PÊA¯0*Ô-æÊE»07T +ò2**»û³:ZºëVWËËî9öâé2K3Z<÷D¾HéAÛо°ÁÂÆθîÂA°ûØN²¯½Y5ËXÞH,RÝHèÄB±èÊC±*Ùè·öºá +¶°ùDÅ/ýýüïÍUÕ6¶Üñ³5S°OÊáé=êÕÝPèë9YUZÁüûùÝÝìÇÕC?O3BXYñÚMWÖZTõ>¸?¹QÊÖ5ìÑ,Âë¯2Ë +Q+:¯M,¶H.áîKæÃû¿;¾ڶ¿KÀ<*Ò0ß+ôêÍZ¿ÖáZX**Ú2ÃK,éÊ0¿/Aö*×-RÖ+Ôç¿ïÃ=<¶*3N°@ô1.ß +-ëâÌ»Ð×ê+BÀ8À*¼K.ôÃ,/ôÈ@Ê´P4RFÎ×5+Æ-ÎÁ,ÌZ¿Î¹ü*0X²K*¾/LÕFùEºJóPA¶Þ0Àì÷EÍBYJÏÇò¼ï4´°<ôÕ1¶E?ñ,å¸<õÕÛ@*¾C.:¼ë;Ä?ûØúø¶å×FI@.ÆäéF,³ýýéí8»¹W +êÐâÆ0:.µÆ*çYÖÅ +BÞüÞ2À-XÚÞËÕ;K×ZIáÀ-B¶Ï/+*,KöJ¾=º*éYÊK2ÞËQÊA2JIÍ2,øìÎùJ¼;,´G¹ï/82¿SåTYØ-°:7º +ÕF2ôêÍÆÞ¶à74FÌÈï¾±GÇöéJä,*¾÷¾+8?J¾¾éÒ·=MØÎ99õõóñÅí¶CDEJÀNâäEÒùýIüôèÛ¹à>QÐF·ð +NÈÁ*M*äÐPÌH1*RåÌGY½ËÇTíýÙØØK*¿Xè¿Õ3ñ;0>*é÷DÖ:Àá¹Y*ËÀDÓõîÇ8éYêÅZZùX¿ÞÀDÀî¾5AR +D*éÁ0æ÷¿Z̯ß+6F2Ã×*Çι:Þ7HFøØEÍBY:߶Û÷0ÉN°5è/.ÐÙÁ-³ZËä3¼û¾6R+ïê1F.¹ôÅNJ*Z½Á, +ÅÑ°XîVQ<üDï×±¶èÆT?Þ3..,.*¶Í;íÀìêE÷ðH¼üûûýµã/0îÆL×ãÃÁﳶ»¸ô¿W8ò3ÊBM+è*ÊäÍKÃ@* +ÆÎKY½ËG,íIÞïØ+EðÍùÏ-Y=Jå.,ÆVÑäéF>±Ý»=-ÃÛ0ÒÏúë2ÑÌê1ØØñ·¸ëÅ4.JGB,S¾ùÃ5Â*Î,Òç/7 +:éÍ=FáÀÀ+ÞûL>,*V;¶ä¹IßÁ5ô,Ú1,X.ß¿Õ;ðQTH;.Ø×MÏø1TÜËÒ-³Î1,Aå5¾õ¿,U-;µWó/ÅNJ*Z½ +Á,µÑð´Q<üD¾÷0*ÞãM4¶Î*üðOƶEµ8ÌÚ9IüÚøøøóõÕçÍ8UÑ-¿ñYñJZüýíܸóé·íÅJÀK6¼K2Nº,Î=,ÄL,PØ92¿8ú*àAJ¹04Ví98 +:ÔAF<++¾û74FËÈáG×¾ÕÃ4Ý3¾;ð,Eõ2´YâÝÅý8ìÇEµHGùùø÷±ýîÏHLáL¿ÙY=VÚÄÕïòçÓñëö¯â-¯E, +Z=QÔÚ×Ê̸ðê÷úéÚËêÚ·ýPüº·X8G¹ôæÏÙ·î³EJ¾JÝâ,:;<âÑ-òøÄñÏ*ÎúDÂ<÷BÚýDÆ÷JåJä4Ù-R¹- +,H°OÆVGñ8XÚ9Q8»¸óêÓ¹õöõñýIÜàQW.Ä+1Ý0ñFüí½ûö³-ºÏKÅá³K³O*à<å-Ý8ß¼û³Ðíɱ**Õôï³C +¹ß+Ùâ40ú½ýòÍUÕ8OUÃç°ãU*îB¹üú3òåÍIÇQÍIY½Ë¯QËòàM*:NSí¼ÂDÈF̽WRT¶ÈùêçñQÎKÁ,?I»Ì*JÍF +³ìâCRÎ.ÞUXâö°íäÓÅùòäÃ5ÍÌû¸ôHüúûúûõýëÎ/,ñ¿¾=YñÎæýí½Høóç7²ÓF¹0ò/×A+BÊFÁ8W*JLÃ4 +»ü³ÒF³äÅ*JRAýYÜ0°8O-Ê+*@ñäÌGUÖÛäÊAKTêÖ=SÑ÷°äÊ1òãEDòƱ¯æÑ·ùîâÓEÝÌûùõÌüüûûûíõ6 +8KàÐà¿Õ÷Uýýé1YG¹ñÄØUÊDá9FPÉ?SÐFÇÄê*äæØ2¾µó³=T8>RÎI¿074Òýâ÷ÐãÃ,±ÜóÈIEÜ7ûYݼۯë¿Õܲ·=OÇÇñ/*ö +¾ÌÅùHüº¸ñÌG¹õëßÉ99ÙµIY½ýýëÉ0ñCûõÅíû¸õíûñ·ÛúOêB±æ+*ûÇÑPËF½@/.W*ÎYèéàèºRÏMÃ4?È +>S1T=PÅ6Mì-ç½èã6C´éÎOùÞ¾íHH˵éÒÖ;ãÊ@¹@>¹Æëé=Íû¹¶ñPÛùõêáÖGXèÌËQÅýü½õÍý¼üôÕYõì +ÛEü´SBÁ>³ÈÏN-*æRÏK1?FSÐMÇ8GTòæAó³ÏäÒÑQÍM*ü<ãÃ3KüéBÏÙʹCíܼûÈôÓ±Õ@Ú¶XÛÁûSÂ0?¸ +FÇ°NÅSÑâà÷ýáIìOµ0ËE¸õìßùõïQí0V¸îîìÙýúíëíYYÛ±õÌ»í°ÇÆON@Uôº:ô=**²ò»SíÍøØÎDõ×=å +ÌFQ´Êæ7ÐÁR¯áɱ=¹Üº;ùOêAïä¼·ôèÉEÅìæ˽=öÐLÄ-OÐò0ÛýýýI»ðÏûZÜÄKùÜü»»µIü¶îÍSÍäíÝ- +YýÜúòêÙ¹óµÍ+5RñåÉJñ*ë+ÕÓÙ70úSY=H½ÌÕ·ÌR5ðéÕËØÀXÏâÒÈ9-ÄêÕ9HêÕðÝÄ?=ôÏO-1ú±ß½ðöG +¿,=ô긼ºËýý9I÷å¶Ý¸ó¾M91Iý¼ûñë·ñ÷ÙÅÕ+öïéìÚíÆBÑH7È<ÚVYUÙX¯E1öÐÜä¿07DòåÍEÅøáHÙ× +=Å9K·ìZTÙ³/õ/úÖ-¼VB¯ä¿;ýó¿CÉü7·ìÏ7Gܺ9÷ää¼Û¼ÍýIüóòñ-QêÉÔNCòìæàß¹ûøñÕÅ8F»ùü»¸ +õê¿9ÜD?DGȱâʯ4HòæÏLÁ0?TÒ3GTÒ毾Aó³ßTX²RÎ<µÜ»ºÕMO·ÎH¶üT4ó·-ÑóÉõïâÂÛ°ßÇ91ìF¶° +õVD·êÑçDóéã´Ô6·÷îöÃùêëõ¹YìCVÂììF¾?Ý°@¼ÜéÝÜúûùíѽQì·ñ7DñIYÔò·òæÏN3çÐN9>Þ+*ºSY +ñI½üòçñP¿ýÚâйEµü¹ÓM92ñVáË;58ê3õGʵ¯æÁIùâÆI=07·±î8ù¶ïÌCÙóV×QôÛC·÷æÑÕõ÷ÙMXTZD +±íÚÞ˸WùXHI¼?ÝÛúõÙïÄÔMÌF½,/D.¾¾0/øJ+8*ÞÕøDý÷±åÄAOD6絸OVD±Ý¾ýø׳ýLüº÷ðßܵïßÌ +U¹HËÖµäÚöóâϳ¹ôÎË@ûúBñá½G²ÞV/éôìܽÍÝØèÂùîïMæSTGº¹ù÷ðéEöØãð6U1K-*äçP+ÌB¾*7øUû +¸¶ÖùòC=NÉ:AÜòJç@8Ëر޳»VÚ¿EÅXë×°µF¶²åÉOE´ëWµH7ÖóÞ?U¸ÐX-ÉôùúN·XÎNÖÁ¹ÑõýÜÅÔÈ;à +Ã7MàòNñDܺسàO/äÍѳA0:Xïà+¼ÓÕKÎKÃ47D0ÞÃ4/¸=OÉ5QøËNåDPËø°ß8õVÜÉYMäCÒÁÓù·ñãÉ/A +ôL¹<ô¹÷ïÊýºãÒNAAóRîIÍÅYáâü÷õýì51ãB<äÊ=I¸Ä8O̺çñåÎRòæÏNÉÄ*.*¾ÏK+1*:8îO»74GÈòR +Ï¿V=*ÌÒQÇAY¸.CÑIÓECóéÍ/íó¾¿àùAÎOÓYÛèÄDÁDºÖ¶ÊA¹ðî¯>A·0*ÖÌG»ø6Å2Z1KM+,î82ÞÉøD,4²æ¾Ø +U,îI¿è6×ðNÇBûR׿QñHGBTISîIÃý0@êå?8²¸÷òé¸ÑøÂ55ø.×ÑVÐ8>RÚËÃÍͽÜéÝIݸæU5HÁÌÐÓâ× +ñ¾ëÇÖ7*ÂØ7ÚÃ-úSÝ85¼ZU;³/*ïµì=äÇ=KÌÒ÷ÒéãFÙ´ç¾Ïàò09XNAZQÛZ»àÎØáÉ0Ú=T16CöÞ¸µØòæ +»èW7ûûù÷ëùõÝ÷HFÄÍQÎð±åÞØ2*î×à@VßÂ8G¸ò¿×»SY5Í4Ñ,N5*RSÐNÍR¾=/4ÑãÊAWøòWýÀÔ»ù²ÛM +»WÉý2¸ºöXIÅùHºðÔ/ÝÜê¹ÕÌÌØZÉ*ó÷ÀVåÕY8¹ñÛ8?RËHÅ@O¸²Vø¿ü7XÅOÖÐOÉDG,*PÝÒÕ¿Ð-´J²Ë +7>7*BT¹L´ÌòäÈ:MàúçÓ-0GùòíÇÑÀò*ù+ÊB´öéÆ»öäÔ?íÜ6øWIÙÑKµ÷Ô<íË;Ý?ØñãÍP1*Jç4Ù=YUÔ +BÓ2¯ùÐÜ0ÀHö?*¸*ZÀÓ.:FZÜãÁ4Ë×ÐãÆ:UøÍ°-YÄÛE²Ü¼ÒMѾ÷H¼ÛÚIX7×ðÒH÷ïñ.GLWÊø°Þ·ëçÄB +»Üæ×ß-÷ç±.J+**G+RÉ*KIÓÕ7Y9ØG¶ÓDÙ¶Ôà¾×*åç°ãÉ@µPÃé5Íì˹ñíâ-ùÙűýÃê>QÄìíؼW=ÝPÛ +Bä7U²âÇK²Ï»ï³M´;3óѳQê0¾¼SY5AECXÓHãH7*Ká×8»¹Ò¾Ý²C<ãÉIëDôϳ5AüØñ踻ç·óî³ÚD¶³ +IÛÖXÖM»Ü6S´ø6**Jáé*ç;¾<Á-¿R±UêüÐÜ´ÆãÉ@³èÒçÑPËÐÐOÉÌ/ÆÐMáí,Èá,P*0,FSÊ@¯TFS±R9É +µ±êÝËñÜé/0NøB³ä½íðÖJ¹0ûS>çCD=ÈÒçÑQ*JÊ@7<¶.*ѼYèéäÎ+ç**RØæ@4*Æ0Õ¿Î1æÛÜ6Â0/>·· +É?¯TN÷+ÂPÂHºSYÑI¿üú3>æEJU8@J¿Ì4.5.6F +À¾Æ*Q×ðãÅA±@óèû´0VôTËEYDÌA¯ü6¹2¸:KÃÄÙG5¾RÂÆ-æC*:6:.îO»ëBÊûúC²æM°Eé,KÅåÈçL,X¾ +7.G1îLYìâ×ÏâÈGóTÉ>QÜ·ÊδZÜô>êàâGæÍH¹øò÷Ñå*¾Aó³AàóJ+82E:7+2°UÙ¼ï¯ó³Q¼ÞYäÒ×îâ +ÊA¹05.åËñGR¾ç¿5V=OÉÄ+2ÐNáÛU×áêÜÒÕ7XÛC¯Sè²+*JG*JQËD»,-вçÇ*WØÒçÑQÏ09æÑRÍìâç²Ê +JÂJÂ+F»L/ß.î²ç+NW.2*BÄ:OÇ0¼5ÏMÝÒÕ¿ÏßàLòæ2¾**2D:ZÄ6ÈÔØÏÑPÉÌÀ?3><*ÐáäK+IJçÑQÅæ +P1JçZ¾Ï¾Ö¾JÂ×Á3Ú÷¿K¾¸,JDôW¾0ûÐÜ@BóÄ**æ+¾¶*:D.ÎòÓ4´DZ2î*0>RÎIÁ0GD²:¶Z¼ô,*4-äã +äÐYöÞ*JÒ*2WÔöÜÜôÄëàÛXVÊ>WDâ³¾Û@ô¾1Jæ2ô5Ü0N13,,Jçè¾TÀæÁö¿-9:NÇܾH2øYûø.ø¿TGçÓ +KÕ¾ðBATè²ç<×-ö¸¾*1H´*üàÔéê0ô¾æÆ-ù3²RÐÃ01Jî,@ÜÒÕ¿Ð-*Þ².ÞK*ÊÆ÷ÐPÍHÃ4³TÒ:ó**KÃø +òË;2ÑQ9ÂJ7*J»+*î²*JÒ¹æ7FW¯RZB°R-*PÝÒÕ¿Ïõ*:NÚ,*ÂÜ++LȲçZÕÆÙßBR¸¼:L*ÈÄ2Â*.3*ÑÁ +°=°Qð:èÛ´:VîÄ.ä*ÜÒÕCÏLÃ07-¶S*O,>>æÎ+*Kµ¿´À²çCØæ±;T²ÂÊ:OD2îÞ-Ä0äõ¾æ×ÀÞÀ¿ÀT¿Z¿ +MÖ:KÁüTYE,*MÉ*ÌÕëîJÆ7>»Þ䱯Ï*Îò+ֿп*+,=1:²îì²WêJËÞ¿4F<¾½ó³OUA¼ó¾+8¯UNÞJV+*> +W±±QÍDWØN@4:î´*ÎÜîJé8NKÖJÎøõøD¶ø.*Àß8.*B»N+å¶JËHÃ4³TÓ1**ÔÄ*:ÓSÑ0+:ÒRÍÜBÚ5G*S +1*8ÒÞ¿DÚÞÂAó³AFF--ÒU1,.KA.ÆÐOÅÖÁÝ*è:C¶¸TôÞÕMÔÉó0*à2ÌâßÓ+ºSY5ê,LÉÚ8:Þ×/>,>2FJ +*ÒËåË1Â2F¾ÂMÌöÓÜ@O+.Ô7Â+ÏMåÎòDz5ÑOÅÛ5,0**çSÀñºöÞÜ0ÆÜK¾ +É-¶²,ñ¯NòæÐ/*+´,óçÈó-+*NÅéÌÀßáø³¾D?*¾ÑA6îà+XJ¾N˸0ÞÓTÛÈ?UÔUÕDÓTÓPW*¶Ñ/ÅîîL+Ü +ÒÕ3ÐOÁÃÍ6J:J6ÞBJ¿Þ¿-Ò,FÖ¿;¾ø*JØ*Z¼»*:6*Ê83¾ÏÏ-Ã/14ïä,0,üÒÕ3ÐM9îé6*6W5*Má+T,, ++ä,XçÞ//F2ÀLÀB*Røâ.T+XºLûß>Þ/>ºWY¹;³QNôæîXQÞÇã7*BÏMÇ@»Þ³A¿î:6ÞæQó³ç1ÞÓ·ÝÒØ>@ +îO»7TËT³èÒ1.îL*P,X²Þ34Úà+Îøð.ÊèÒRÏHOÀUŶ¾,Ï*YèéJçã**:ËQ/XFÞÉøDKز¾4:7/B2¶¯*¯ ++Q2î8,è,8.ÞÃ06+¾ÐQTôP18ÖÞ+=Úö*RøS»ë/´ÛJÖ¹J¼¯Wó*ARîÄåXÊê¿ÌÛ¶×-J¶K·ã4µÞîW»ë/×Û +=¾TØEB¶;¯ÅPîè0Xëì?1F:ÁM¯ùÐÜ0ÒH,*Öøð+ôã*YèéJç7-ÞÝùDZD.:0îê-4/<ÎÀÉV¶Þ,YèéÀÔP*J +ç;Ô<¾-1¶äYµLÎBîÆ,ÌÖëÇ/FÇ¿E@¶ï2ÞòÊßÙøDZ¸.JÈ@»:+1FîN,ìJì+0ÚÎôD¿=*RG¶Á2ñ,AÀÎÔîN +0ÜÒÕKÔU×°ÛD*:2îÌ*8ÎJOJ.J+K/Þ:JãJKÞ/GÒ0F3Á°¿DÁ-µ¶:0ÍèÕÇSÑOÍJ÷7*6/ÚÇðE>¶5.Qîî0 +:Ì.ßÃGÚ,¿9ó³ÞýÌ+îI»7úݳ*ºQYùR?îû½ëùýóõýéíýÕÝýY½ýÝüü½ûûý¸ñK-Y@4ÍÑG¹òêE*öÌG¹Fß +=¸÷ù÷DÎCô>.ZõYE,*DEëéêùøöóíI*2üøíÍG.ƹKQùýD×MXɻ˹1ͼ»ýõÝIAâÁYE*ÚµìÝ·ý8ÌÛùM1 ++>-Ë2ØÖÕµýÄò¸V²¯9¾°Á»ËQ9HúõñôûéQóëÕÝ´*¶öâ¯ÕÌG¹´í*ûÇÁ*âKPà<úÏá»Ý;.½îÄÞ°ò->ìYY +ñÚYé¼GÙºüûçüû÷Õ7*üüúïÑKÙÌûDÛX¼û¿Á¶Ë¼úV5HúÅñ0¿ý.óÕG*X-ô>×Éâôë7÷ïM±½»ËµÄëøñæíó +ñAû÷YF*úüöÙCµ¼7Ù°Cëú¿Áö³Ã4ûG¹Èé>H½ùù0*YíºóåÒ±Ý8É4?ØÔÝÜäýÜüúÖ±óôó5ûùõ7*Xݼøß +?IüúDû7úù¿Á¶RËD;¼ËD±æлüÃMÂíAíùðCÒY=PËD¿¸CMÎWýúØÑWW×ZãäãÕAöñíE*FüøðÁM@V·³LEõ +ô-1ïÓTÓØ5¹ñFÊO¸øSÞÈÊÁ¯»Xñöå1ÍÚ¶±ê¹áIøééúSçÃùDôëû¹K½ü?*Îí/DHñ¾É5*ÔU+òVJ8.RÖ;5 +=>,Â?**åYÞç½õÜÙïÜÃOÑѵ³I¶2ýóéÕÓÐ9ý»ÊÀÁÕý0*>***2Z¯ùN-âÁêÅ°îV,.ìJ+;¾M,E¶>+¾¹¶à +÷5ööî½EüñÖòÓï9ûó³ü<²ß·1éÜÜG¾½0*ÒÎ.CõHî:-*¾K*äÈ,æïë½MÌÃ34+è4Ì9¾µÝXùîÚÀKݺð³HW +زEK9PÂBëäÌü7*R*òçEåBáÓ0F9*üÈÀæñïűÌ*1ìWàû*öôêµ=Ô¹BòËï9ûó³XÏñÞ¶/å´8GR½0*XãR, +ÃÓUåÊÄ:ÖîL+úñG/¾ºÓXG*EÖK2ÝÏÜK.ÐÄå-TîØKWÖJè´ó*2K0Õà1P+XVKÛßV+*¶Q.Q,ÞÏEÍWBìÞÌ? +H÷DUÕXÛÄO;îúKô÷ñ5ÞÕìÑ*2Ø*¼Rà¾J2¾¯Áî*ú5>2*:´ÂHÞ,1XèÀ/ÒÀÁ1¾8´Æ?ÈZ4îÀ2V²E.@FæÁ¹ +L¶;*Hû÷êÀ7/ôÚ¯úM½»Ë1òFöëÞÔÁ91ãY+¾á?кϳãÉý;î¼10Ò,¶Kû¾=Wк·*èT³À³È-BM:2´IÖ,õN +êóèÛJ?1¾à³X:¼Z4îà48òKV+*¶Q.Q,¾Ä1µGîKF1ºð³:Z**·8úRRâÏç·*èϵäOËøKô9·ÒOP-,.P+â+ +å¯-ARÞÒUÅ*ú+@öQâ¶åô?ȸ*²Xò,Ï.Ñ*A¾,ÇMÜ@KÒ-¶Q.A,îâK,*øééúGå¾ë,PëÙµöû4*úÕ·ÝWÜæ +ûE,J»BÎ4*:ÂÍø*Ú½-îû¹ó,ðE¾°+RûÞ4²Àçݽ½ÔZ°:V:¼**F×À-»B>¶Q.Q,Þ»ùP7ÖWÞÌíG÷DáÆ;M¹ +1>,ÄÝ+¾¾.-Zîöï/4.ú¶*+,O*2R?æáÅÝJ¿æ+Æû*5º,éáÅÍÔZF*:ÜÈZ4îÀ2.,ê³3K*õÂîL*äK6<ÞÌë +ýòÕÕ+´¿Nöõ²ìÚ¹I,*¿7àJÇ+ë7ßñç¾T*×J³ã¾,æÛùñ0ÛM+VõìZNHÞ,Ó:¸õìÍ?Ê6F;¾5U¶Î1=,JßC +;F3*áËùòÝï³¾ëÇÕ÷ÊC±Tñ6BÐÝ°øôMÑ5ÜÑÛôòü»ßè=7J*¼ù8/¾Ùõ·÷ûù/4ÖíÝáʺî*JKÒ:Â8?º+/Ï +*AÂJ:¼Ð1JßC;Ú2*ß»¹G.R5ýæYY-ààéÒYçÖ±íÌû0*ÂâA¿*H4Ñê½ÊÏÜFEâX/îÍêö³G·ôê*¶´çâ@ãP +J·ú¾åú*ÏðèÀµäÔǸ³äºUÐÆ¿Þ¿-/¶¯/1ø:ÔJ*N¸P.A,*µåÄWØí¶<:ôâéVèÒS½GÎ2Öû0*F¾ëÙUY¿´ù +=±-B´ãÓ+Ò*ï³Ï*äBZ.ÙÍξíÜWñÁÂÊY=ôFµUðó×±XÈP3Ì2Þ/;¶JåVµÜ»E+ä²E*OåÂîL*äKàÕßÕöÅü +øDÍ/ØZÚ½MÝÄû4*âÍ¿*¼ó+ä¼Ù6J+EÞX/îÉESRZ-´ê*Ö..Ã:á@Oß>¿ñVM+¿4¼ZMäÄ:RÎ>C´*¿-A.î +à48òKV+*¶Q.µ,¾YÅü5ÕZæ×̺ËáÃ5Õ?æÁ7û/ÎY3߶+çëÞð3¼JêÖIWÖA4ÞTFÎ0ùÇ°Â*V/L²²Þ¶1ÁX? +üÏÝÜ0IË´ºAî±NÈÄ,XBåÛìã²+ÇV¿Ä8ºÕïçÍ̺ËI¼úÖÐèÇý0ÌÐLð´À6:<¼71ýÇÚØÐöÅüøDµÂDñÞÀKÝ +ÌË°á¼û.**¾-*¶ñ+Å:îú.@J,ïN¸¸ïÞÊUE¸ð³ÌøòçÈDÁֺ̱´ûR1Y*Þ4-Õ2Þ³éXG÷¯æÌ?ÏçÑSµÖY±â +Ð;=ôÚ¸ôÓ-¾íÜëØ°ãÐ=M¸ð³ÌúöïæàÐü6ײH»ÍH¹5,ìùõêÍÝÎ8577I̺½»Ë½Éȸ²ãÊOÝ@سíÙéFõíÞ +³Á8ÛØTÀ?Õô-1ïïëÙí¹Ç°?Ñë5æL¶Y¹*ú1ôD¶éôÊSY°í¼úüøDŹǰèÑIÁHËXéä»í*JÝ×=IQL0õð¼î: +8ܽýüO8TD×Ò´»ï*ÞÜÓÅ9˳;ÇÏ9*¼½»ËZÝÖÆ.TíúÇV´íÜ»Å=×±W55½ï:Þ¼ÆÇ4üÔÅ4?Sô7*TÃ4?»Ü3 +8ù*ôÍàR1»Ë¯YUÊËJÉë5*Ñ3ÓÎéÜÔÍI·2¾IæÒ3.*îÝ»Ï@øDÜÜúóà¹ïÜÛ»*RÚ¹5ÝX¼û7Â*ùÛÞ³µ¹ÛÇÜ +P×YÅXFµëù1*àWFøMXÑê×ÁY9IýëYËïÅÝHúé@èMYùKLðû½ëùýóõýéíýÕÝýY½ýÝüü½ûûýøùýóõýéíýÕ +ÝýYYÞé*ä-**EýS*FBØø³?T>SÏJ½üÒ*ZG·ìÊ÷àé>PÊE¿0/øÓ,*øÚ÷ÐOÉóé@ÐNÈBùäýI1ÆBÊ>OÄVÝÔ +7E¼.CðâÕ4*Æ<ãÆ9IÔYÙÂ3=TúV+J@°6*RÝüµ-*V»Û**%% +d +432 139[1 0 0 1 0 0]sl 8 mask 0 278 di + +QP +%%Trailer +%%Pages: 1 +%%DocumentFonts: +%%EOF diff --git a/doc/krfb/connection.png b/doc/krfb/connection.png new file mode 100644 index 00000000..c4d236e1 Binary files /dev/null and b/doc/krfb/connection.png differ diff --git a/doc/krfb/email_invitation.eps b/doc/krfb/email_invitation.eps new file mode 100644 index 00000000..e1a5381f --- /dev/null +++ b/doc/krfb/email_invitation.eps @@ -0,0 +1,473 @@ +%!PS-Adobe-1.0 +%%BoundingBox: 0 0 532 447 +%%BoundingBox: 0 0 595 842 +%%Creator: KDE 3.1.91 (CVS >= 20030907) +%%CreationDate: Sat Sep 20 14:07:13 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 9.305 2.791 ] [ 2.791 9.305 ] [ 2.791 2.791 ] [ 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 2.791 2.791 ] ] d +/pageinit { +35.4627 23.6418 translate +% 185*281mm (portrait) +0 795.224 translate 1.07463 -1.07463 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 373]ST +B P1 +NB +W BC +/mask 7504 string uc +î½*¾î1:I*¾1±/C6*¾ÍÎ:Þ¾V.+¾QR2*J4½ÆÏ9ð¾+ö;ã-O<-îJÈ1ÈN0J¾F9>É:+Þ¹;.öµCÀ:½*ìÃ:Þ +Åë2àX,K*ºöü/ïûÝîù½ßõýÀíý/Ýý5¼ýAúüYöûÝîù½ßõýÀíý/Ýý5´ç/+%% +d +/sl 59584 string uc +î½¼**ûý9*N¶U7Ö.9R.å*¸×.ó¼ðÉ+Í7ùï*¾ã?6ùúÌVß²ØÉÞ-üøý*¾ÌËÛ½½üúÑÇӼ׿VMæøå8Ùó5,* +ú5¼½ûùù6/ÍÓZ²¾ìQ¼Cîׯ9-¾×OݽòÂêáõèÒ4BðàÃO¼:>±åõåûóóU³73ÖÚÀ¯Í90.úYÇ:¿1ú³,*ZÞ +ù=ÝD9*¶ÔÓá9ÔÉG¹îNöIÒ3Kß1æ+ʹ½ýØW±ÞÓį+CE¹J:ʼMÀ<¹ê6Ê5çôݽ@èº*Þõ<ÅýIúòãYÙä¯ï; +ËR4¼¾8Þìû¯MãD-Yó65ÞÝîùõ½ùà.O²ÔA*¾59í¼¼ùêÁͶ¾°ãÈ·½îOñ+*åøFúøýýååVëðOUòÛÍ3Wß-æ ++>Ù½ùÎS9ÃèX=JVѽ0*Þ¸Ü>¾°åõíI¯ÇÎ2Õø**ºíéëïÉå8û·¸Ìøì5;¾T½õIº²H*îÆåÁYYý¹óRÓæâÂ÷ +äêAÏ?VQ1¾ë>Z*ðXôH´Þ00¼½ÑOͺè´9ÞÍÑYYý»÷îÛ±õêÑIµìú÷ÂS*ñìíí÷ÜBKؽ@è,*½EÙ·öùïÁÕ8 +ÍA18¹ë-<¾Rãõå4²D*îWÁÅ1½G¹÷Ò×ÕÊÆá<Ë2ë+4.²<üë1IÂ,÷ýý>¾:ëøø:ZXõûùé½<.µãîî*Fîõ6Á +õÅ4>R6ÉÝçýÎå2FRηÉáõÍý»,øGë.è:HîRGP,ÌÙàÓ¶¶Ööó,ûýHRÎî÷Ï=Â.âÀ0·ß*ôìÍÔåÑAH³Ù0îÕ +ÑUAݽüùõ»»÷êÍAQüØæÂSÞ¹õí½íýüû¿ÁÐ1*ÞùôÄ8HüûóÛMðàÇíÔùà.êüüJ»P-*Ì×øûîÓ½õH<á,õÒä +ÇýÈGJ+üýIÎXÚýµ»1/¯,Ó*:ðïKRøýÝKÀÔæÒßO6*JQKÞ»´GY;ÒÝýO¸ÉHKõ@òI½Å+48ÆXýé÷SD³ÍK°ø +úá.èJ*+¸Ò8?+ÅWï4Súç÷º9;úíÝDçCûÓÅ6Kâ¿0·Û*Èì9BPÐAH³Ù0îÃÉIÝü¼½û÷GüøíÇAIÜBãÂSÞãô +íí¶Úüû1-Ï/*üÒZîäÝÝÕ9IÍÕì¹öïÀÅHJ+èÝýä²*Þã@AÝÛôìë5ù°2Y¸>D²E´1T:*ãµLû͹HÐâ÷ñö°µ +H¼íýüD+4ºüý**7¸Ù¼ûÙ¿82ÖáöáµGXÎøýUâ2=à*+1TýóËEÛñÍ9<Í8ÖñDýý90*Ëå.Réöû--,*ºôÛÉN +üÊÍDGÝ3ÜæWýß=íæµü1>û½¶Î:ðÊ<+ìòUÕá-ú2,B³ÎÝóùÚCöøÞ¾3*Ö×VÏGòOô´ +àýÙYÈÚ¼4ñ.MȲÔ**Æ´ýë<»HîÒó@¾ÈG8ú¶÷öMøÖûõÞ½9AÉïC42**Ìо**ZÐJ**¶Á,8¼ý÷ÝðåÝø±+¾ +õ>ËÚöõóïÝõóÛ1å»FðÉG5/RýüBÑ+*ÝSÕìÙ¼·MÍæÅC>ºD¯ÛAÙGR¿*>ÅSñ½ûμ59ä9éP¶ýýXÐ0Ã3ÖTö +¹X4Øà¹ýáÂö½ïÇ+,öÏäýÒ;0Aæâüý½YF±¾¾õù9ýBÊUÁûAöÝ/02¸ÔÀ;âýý³,²ÚëP±48=ä÷ðèßÂSÙýÍ´ +?ýVöüýñýóÌú9æüýľÞT1DøûÖ5ú¶:3Võ½À?ÇÍ?Ó4K*+¸à4MüÅ.иï2*òýý¾ßÕ¸@8Þδô×÷ÂáËDFîÅ +/2*PìYAMË1ijÛ/îGÅåäë»»ùõÛûøì·åHܱàU>**,*XÐJÂ*Ú9<.*ìÎ*õìÝÝÓøó·H**ðæ¹öCÁD*5üØL +ÉÃ,´½ýÉ?±+*ÍA?¶ãÑÍùóãá7Á¸·TÍ3ÃGR¿*>WSñýøÀô1ÉñúÐÖýQ»ÑáB+ÁêÙ;½KUVýýNûüÕý³ÆÞèûÍ +Ý躿PÚ¹ýýã>UÜêÎÍÉQÅ,Ê86@FB+USÝÕ¸±4Ü°ãüÊBÔVçÔýÕ3÷´K:ý-<ÌýÅ3ÄQ¾ÇÓ8ò<½OAöïJ<¶Pú +Â9ð½ôë¿1F°ÃB*RýÆJPAä6Þý5¶3X.>á+ÊGú¾¹Ä?FTÁ-üÖ*+ÖäûáLºÎSùO*í2FóêéãÍQíåÉÝHE³î79 +HL¿-ÙÉ3/*åBK4I¼ä-*ZýÀýÜûÀ.Ï´îßý¯V>JûùÉÊ0*ºN¶ÜAÉ8Ìì>íA:IÆ:A<5ôJ/,ÎÚNÖýE=ÈYA:1 +.æÎËÇý=½BP¾Þàý´°*ðÅßÏýó37/ÖÉýå5TZ¾,ñûÅí鿺;óýÇ8IÚB¾-,Z<õM4·6ÞOÈVITNÇÜ´ÃBß=,·Þ4J°Fã¿GBMÙõÙ61ZÇöÍQô8MXFKü-¾ÄùÝW¶ð/°ë +Ô+úÝíÐWMý8¼º1ýG¸éнýMö0/̾ýG42òíÅ.XO+åÂ9Ý?õìWH**FÅEöêá57>¸ìÞO½UÖRÞìõY÷¯D*îöÄ +PäÊÔ9Nò¶Q×GË@³ôë+4öýý·Ã7í½ÚTÜØ»¿=Û¼Vè<ûYÝSʾYøÌ·ßúýIÛÔý3ÔûYAÏ*+.²õýÜÃÀ5ÖVË;ß +üý»æ¿ºSûýÃûýYçòÞ6Þ++ÖÅçí/H0¾9IàÄB¯>¾Ôü½ý?·9á/úçüý+CÃíGSµC°åÚ9ëؼò*IÌÒ+¸ûÍIæT +ñ+Ï8AV;<:JJòü½M:1VïÂ*L-êãóñG0WÎïÇ5ÛáÆÁQÆÖ,ZòüQ3ĺ¿VÙ³*A1ú²éåÙ¹ýìÛµ½ìø²îð.ÜK¿ +==ºÅ,ðºù;¿USÞ@å͵?»øÇ1*JéÛ7H¹µò+.êG¸íàí,5ÖRÞÊõYȯH*îåì<¼PÅ>S9Ó³ì¿ëà²ôÞ.úýýýó +ùÔARHASBûûìíÇåÍàEN>PÞ+¿ûY°îøýEèàôíYäÂU¾*ôݽø=Wß/V½½ýõY1éòJÜBïùÈWñõE¯NÞÆß++ZÄ +°éíû9IÎÎ,DغÜR*ëøI°K2CÄ52Ä¿-+ºNÛè÷íYYÚGßìïZàÓDJÅñï±,µH:è+,IZÃö1¶B¸øÄJè38+Þ²¾ +ïºÍÚàZ*CãTZÞ;ÄFºÀÂ?*»õYSîá5âØW-ö°ßWá¼¼GùôÌûøñÖW1YôYP>¶îÂÅR¾Q¼ýìÈ·ñ:9*¾ýM2ºõê +ßÑ-îïÝMAHºKWý4/êðüÂ=ä-*H9J˶>C±QÊÔ?ÊÒäÁØXξ-,.æ:Sô*Ñ+Ä9;P.5/ì¯âß=N¹+دüüð,´º¿VÙ³*É/Fµë1ÎQ+*Þ +7º@¼·Ï¾Zíü6/BNóèW3AÜï/*¶¯P¿ùìËFùö6ܸðÓE/=ÖP*ÑôÍH;àº*ÞÇßLòVÝÁ-½KOÄ2WÐ?;?Úá¾Bß +FÕJáíÝÍÙÕQO+,=Ò6ľÖÀÀ@é¼êØ4ÒêüÎÙ/âö.×@ù*Ô3öîÜÍöÍ39ÜÇÜÒRÞÔ±íüûÓ>îÄåÍÙðëÙà5*îö +L3ûGúöòîCûòPMGØÄî+4Bîûã@Ê1*ºIZãì7VÖîJFAëC±í<ÐÀÁ72XÍ=*:Sݺ/YÛÍ0*ºUË4É+U´WF¯QYáÇ:ñ=VÖ?ݼÙO0*Ì7E³åÈ35ô5öø²*Þ½û,@êB±çÔ ++´×÷TÝ2¿öó¹P2²½ý*Æ¿/*ï5=.ÙݽüãÞ,QPR¸ñüü+:¿4FCKñÛ¹µíÝ2*Jõ´/¾==²Þ½ý0ü5+*úìÔ*¶° +À@F¶îß½;*òUîHCK:üû-J0@¶°ÀÄúöïâ½;*¾ïë4*QP2ÈI+*+ÞQ:¾¾-îä-°Â**Î*ÎÝÐ+¯ãV:*; +1з<Ѿ:Ã::<ðË0ÂGâá9ND¿ÀÏ*Ï.åÆï*HìÀâã3ÚÊ.Ò3â*-?X×MT÷¶ðE3²L͸ÞÇ3ØñäËIY*Þ*LʼüÚ +5*1B:ç,F,ÁȾ±½ÞZQ2½üúUIÇê+íòß´öØG´HÔÂ.:¾*Ä*ñJBXLßëEâ/,2ÀQïÜ;ÝèÀÒðïδ̳+BíÔ*¶ +°À*û3*¶*×4LQÙ5*òì÷Ì,JÚLÅHËJ¶:RîJ+Ú5/4äKÅìA2æîP18/**R/ZÕ?SB;MÒðFËÅLËEú3TBY·Q8ûÇ+QP2ÐI+*1¾óÊòßâ,Æì1ûEü²Çòç9*åÁ,14ÞE= +µFKøò¹PÀòµ¸îR*TÒVøRëÂ18Â9.,F4*åµRøîB¾Q1î¾>FÖ¾ãÇζ8:ÝMI:1À1,2î2à76@ê0ë>JöQâ´á +ô?T¸*²8ò,Qøïâ*êX;ç7/Õ>±Ú:3,Nìö;ÆàÀ±=3-¶*3QFïÐ<ÎS;è4Q½°C1J;òÐ6ÏÈñ°H8:å7ü.?ÒÕò +W*FCKÖü.*F¾òA:ÅÏ8Þ¸¾:øM-JÝX2Ôé¿,Ó*HÛèºç*VÝH¿;Ý8Þø5G¶.*+-;*ÀKõ*Â;¾-8¶¿*ÆØ¿çC± +Ã4FJ-ÅS3VZ¶ö0ðùæ1.Àï¿*LºÀ2,Ѽ»ö,/2X¿õóé³YÖ8*RI7¶Z:Q.JÇãÙì=Äò1Ê+ÐI:3¿*º3N¯ßãÇ +F¾Â=̶ßCµ.ÞÄÕÒàíÇã>R6Û>M¾.ÑFAñï¾AìîQSKºOí,ÞÇ3DôëÙ±Y*î*ÖM¹±8¹*ê/ÁØã+òÚÞBQ4ÞEÇ +=6E*ôê;Öðá+Z½Ï-Å2:Z:,Þ42å¼»N**¶ß+Q2Þ8ë5>´ÒÞ+D¶æ>ÒO»1YÅóźJæN7.:Uç>LÆì8ä8B¾/Ý +×±í»KSÞÇ¿Ú:*¯HàVGV»J-ANL<ò¼K:1ë¯D*@>NÚï¹ÈB-:ÖCF×Ê-¼R¸·ÐF1ñïÀAìîQSKºã**²íÔ*¶ +°ÀÞû3*J*ì÷,ÎF4ÞWë3»Gµõì¯Éæ3,/T+ú+ßåù,*ìG6¿øìË8F4**Î4ÞêNòWUN¾+î̾õ±FZM±*çHÂî, +7òÄ7åïÄBóÊ1ò1Üçõ,+¸ìÞʹ=ã»ê+-2ãV¶îâLËT.+FBÆ5,:Jç.,ÆVÑPB.+íANà4¿Áü¹Oß¿ÀFE32Ó: +ïÄ=ìSÀ¸Í4¹øÐDÌVP¿LÊß+MÒðÚòÀ-ܶÁIM/ñ?»5*QPæÜÁ1ý»+**¶Ï*HºÓ//¸ºý8ÁJZAFÎ4Þ3¼*+V: +HíÇØ0ù:º½Ï-Å,ZöX+*öùÁÁÂ+20Þ¹W5Ìï*WY¿ì2ß/DæSÉÜYíÞð×9ÞÉK46··2+SNÞÒùùÖ-0ÞÙÑ.5Nû +H63,¶Z:A.*67>ÎÜ2ÂÅ<2W5XÆÂÇ@Á¾ÃÈÉ-º·±<¯*1:ïÄ=ìSÀ¼1ñß¾:WÍVP¿LÊß+MÒðFÊÅ5ܶMMK/ó +?-**¼Ù,ÞÇ3ÀöÇ,*·ÕSP°N*î±/ÃE2**8BJÀ¹2OÆIC°.ÃòÈñàUØ´+ÌÊÎÇI½Üú+¾-í9,R7¶Ì*Pß¾<¹í +ÐJD²Æ4>8ÇM:ÏQ1*JåÆ5Ý»EÏ=JôÀODÂCñWâ3/.¼òáQO*NÝ***DÃC2ÛP-*Jì?Ä.ß/ï0CÞÒX +QÍH»ÙÖ¿*µ/*úÝ°,AH@RÔU»Äë»ÃJ·ß*QP2äå**ÚÛYÄIë0âG+8ÆéæQ+JùÙAîÑýýýÝÝû÷íç²ïµÑ¶Ã:* +9»³æÞäÏE6H5;VÁ0Æ°ýÃÍÈ*Æ8AÏ÷ëUåXDæ.âJØì6JJý³°Ëá>úS*QåÍ1***êÁúúàHP+Ì-¾òXD+HJÚÓ +J³ÈB+7Ê3LËçTêî÷ø.9?¿Y8TÚýýýùý@GûìÎÊXBÞÇ3D÷ðý=H+*ðí+¾AI¹Ïêõù¯Y/M9ØÞ¿,¶çQÍä?½Û +°ÆZðë6Ò3°úòáÍÄHûëñ·2,î×õèGËÜ»÷ë3¸âS±2ÀÓÖ*OüYõB*ê¼OD±ÀÊIüôêOÕ0øÔ5>¹Þ¯±¸ú½Ê6Ù¹ +ÂC@î.PßÓ+ÎPܺ*³éø@1ŽÆåÒźղ*ÞH¿*áXÔðëåõ¾ñàð?CEO1íÝÀÑ°7G¸²×ü2ÀTTê±8ûK**ýµÆÞ9 +íºÍÝúòåÓEûõµ0ÜßCGR¾==>îßIBK*ý4*º·Ù:éÀýí½¼ÉúEFòåXÎ2ÞQY/ÚGöͺMØÖÉM̼¹-áë¼I¼øÑã +íýûôëF>,¾Õ;ÜZýÊ:;Qöº,KÖÈ4ÎãýÝ8ÃÆåÞ9ÇJËÎã1ÍGæ*¾ÑXÎM1éÄÖöö,*VúδßMíÍÀù¾å-ºö*ÞÁ +õçîÓê,HºÐöXç¾W6U÷ôÀ*-Ê»¼Ûß**÷OGöÚÚÀË0íFØLZî÷U¶TÐÅUØ@Q;RÚUÕMFV¯HüàK4+¾ý=MîàCØ +ÅÝESÇ6³äàëåU>¼¿±¸Î*QPRÚ¹½ZÀÞû7*Þê±µH8½´1é1äH<ëMAR.îÒÙ8ôXýøÚDM+ƲÞNYð*¶½±À2ýý +ùçíHë¿L*ï¹ûÇ34×Qí-¿·íùWå.ZZ0ÞUõúõWåÂí8,ÜÇ2¿ýËYÒ¹õÉ/ý1Î9ÇÞÊÎõÍYHùñ×A-»è/¶ñøó- +5Ý**=¶»øÊåOß-¹îìKZõ*öJH*Lݹõð0ZëÓD½¾ÅÒÒ?¸òÆL,2*îJÏ5.*¾-Ü¿3ÚN/íö¾LVU=¹Üêåʶ»øµçE/¹é×GÖÈÝìûH1ÞU/KèùT6¸A±¿ +RÃ8Ü*Þù*ñõÎÉS³*CËI½Ö»:,<Úý=MZÌ7E´úJ,VÚßÜùOZH5T¿==>åÍIBK¶Ü*ÞAAûý½öØÀ¹¹ûùéÅÍË? +÷í@E³¿@9âJ2¾õWXýºÕâÕ±ôÈÃùÕÝü¹ÓâËõê²ýÛß¿L¾CKí9Ýû÷åÅùä˶é@÷:1ÌF¾Ù¼¹ÝºðÒ1¹ó?1ÔÁ +P¯ïýý/GÚµ²Þ>íTÕï+*:¿ÑÚH»-øATç53JÛÙS5L:ñSæMHî°Ó,GºüîýýÛüùïÛ9´ì¶R;1ôCHBÇÚäóH1ÞSù>Ø +¹T5¶1±¿RÇ@ü**Ú:¯1ó×éñá3<¶ZÅ5;*ü9åÇÃ5ê¸Ýá?ÖÒ2FCKSÜú¿3Àð1*J¼<9CðçóøVÉøùà°·µëÔO +õÛÃù/7öðÓýèÕíDYýìýºáRùÌäÜÑôÜÛD²±ÒÍîJ;JË:¶º4*õÂÞɵüDAºðÑ÷øÒECPòøÚýüÔIýáüìê¾á4 +/5@W-18ÒëZßDóSðÁµ:ã¯G++ýùÕWVï08ÂQÔû7*¶ò-üØNÕÇOõWøÖßÚÚYÛE±·Î*XøýüòãÁå¼ûÖÍõÍYH +ùñ×A-»°-,ìñ5*¶Ì-Q8Jü+¹ÒBèµÚPÔ@S»Ã-ìEÞÑJýÑÓÝÝáQýû¸îÃÅé>P×GØ+Wº7±ÆÖÜ?ÅRѸ*ßû.O +¾éÓ*2TÓÍN¾É¿Õ:ãËëV2KÈVø*,Þý+-2âV9+¾¸>øÒNÔÆÍìâÐHîâ./·ð1P2ÆI**×5µâÆýýíGìÂTåôõíÕ +YÝãQ͹ÒEFBâñçæóéÅËÓؽßí5=:ÅWÛù-QÌFðPÏRÏÈÄùóé15?TÍ8ï¾ñXRµèÍ°IõÃÀÉ4N´ùOCBçýóͽ +°ÀìIý÷í·Áô6òÇ7¶µÈ?UXúAíÛZõ·8F·*¹è8CÃ1<:Læ¶ëTÓõÛ8öÈY¿Üü7¹¼ù8H¹òìãXQéÓ²ðúÄ0Bï¯ +æ>±VÓW¿.=WÁÅWPNÐGÉ-KãøÇϹ۹ÆÊ3óÌJN@À3=è4.¾ý-KZ6¯±Ýü6*.³ÄQµ°S9¶°ÀLÚö-=.à9*¾=ã +.7D¼Õ÷áõâõØÓBÙ´æ¿7íà>»ÛÎ+AXºðàøï5Hêûùåíüµâ¿÷TëÐ3é³Eé78Ê+¾çKÀÌÜá÷ìµñýýÜÁýÌ»øñ +ãûð/Ã9ûß6¾AFë¸ôóÍùTôáñáÅýü»ùëWñÚ°=Û-,QBVܺ÷VGÉûÀ-2æOøì÷J2*SëâW2ß2*;MãöË4öÈQE +ð?8DXçú¸òÒLý<ôûX6ÖòýôåÉ9ý»ËÎýå½HùéÒO-»°-ÖW¯à·ë8*Få¿=1¾Ï¿ãê0¯F¾õ7å3A±ÙN?¼Ö-XC +NÍXýÑýܹÕ캸ÖèMé>7=HØ+Hù5Câ3KOUÌßKáÞKéÆÅ°Õð¾RÛÄü2*º±ÌáìÊϱà+Ï*ò²*ö½:.Òíöêõ* +J,õ¹¸=Éø¾==>ßÁIBK2Ü*Þå3ÀÓ>ÝT´Ø¸øÒ±A9¼E´èö·°×ýBGο:*íËòúùäSøõíñÉýúÙöìÕMEäQ½3º +Á-±*¼øýùôë¹ú½ÚñÓÚG»öìÓµÕÐÛÜâøÖÕ¹Lî,=ÍäûÙùöô»ûøòçÍý7öZÉëöXMõ¾Þõ´4Å8G¸ÑN÷ÈÌÁ1õ +ê=ÕNL4ô¹HB-JRAS5LZòæ/=óÀWä*FHÑFÁ1Ýë?±½úñòßÅ1-/û66ÖòýñåÅ1ÝûÊÎùÍíHøîܹé0JÍ꾯- +Ê´ê,¾ñF¶ñ*ìêáÇä4ð?14,ÁäÊ?±ÕèïJ+ºÝÕD×µP8ü¸½XÛXìæËAã4>ÛØ+0É5Bá1ECUÃÏHÙÎ;¹RÄUFJ +ÒZU½2*êEèUH86¶C<*úI2,ð0ùò±*J¼Ý¸¹¹=¹,¿==>ݽIBKö»*ÞO<ðOöýøúº·EÊ7AQü¹ôè4øðÉ×ÚSX +DR.îµQÕåHò²üóOùãÏ-¶ØíµêöHØ¿C*DûIüCKÝY¼ïàÐõ±õÌÕÉO×õðèÝì3¿üáËC²îèMë»úôÔ÷XäûôIÀ +ÄEÏÁÂJìL¿5*îáLO¸òæÜ»IBKî»**6/3Àý¿ÀÜTFçã¿í´ËسÛôé +éÀ¿5.îÒ½Ì18¸äùðÛµëßµ¹ÜæÉè±EB-2=.õá.ûûú÷Gµ/8½²@T@±åеý¸Q.ÌY¶ïÑA=õìÛIÕÚµ³éµ3À² +ÀYUNó:;V³RÐÒÛ**±P4ÒÞQÅÏ2¾ÉP4P7ÔM¶ÍǾáET8OÃJãÄòÖËP¼¸¶UÛZ+îUE*JY*¾Éüý½üöíÕQµäà +11øâ0NDÒA*îF1ìF¾ÅGôÎÞ»óÚK±ãLÛMûÖMºó´XµÌHúú·õÜô¸òèGÍ/TÐà¸Ô-º³ÓVÁ-@ø³ì*1LÏéYÄU +DJÒ¶Á½**ÊGöéÉ9Å0Ü@¾¾4UF4*úI2,4Ó×ïE*οå´ÛD³Òù:Ø94FCKßËù¿3,ì-*8æéýÙýÕýÝÜÉé?F÷ì +×1ù=ØT@5FL*Å4E³Îú¹IWÅù0TÊPèA·J¶ñ¼¯ÀÌü¼ìÀ»áø=I?3ÖóéÛ½Û=,ÕAø·òÝ5åPê5ñPûóã×ãã +MôæÇíûöZ<Ú°°ÉT7UÔVCê?T<¹Ñí8á¿,JÐÇNGLøX.ÚÜÇMÍ°O×ѳåë±ìÙÚ·õÚ5ÒÜX-Aû½½øðß±ÝìÌA9 +8òÇ32°MY*Þ·8F¸*¯óYÆÎGUô3Þù8ï·ïK7È·²óÁÛµòÉÍèÒ÷ÑVÆñX1ºTRÔ,õ=²É²äËÙJ>¶ùã¶?-ÀTõK +JõVîBZÕNãØãN»D/ðº*çó:ºÊî¿:ÎQÄýH**íÉÕWÕVÓL³4Hã:LÙ¯¿==>ÙµIBKÖ»*Þ1?»ÚêýéEMù´ÓÈ- +A»<ÕÀJ¸ÓÞ?,¶âBO0ì豸éäAÛÀÉÍìJ2¾ÍüíÜ»ø8ýéõØÝ°9íØXÝÏ?ñÆ¿¯éüóÌ2Úî¾¼×µÍXD¶XÛÔèéô +óÙI=NâCɹ÷2ÌòÖMÇDGÐó9*¼»6A6¾üGLø:.Ò²Áõî/úD:ÞQ°?ºù´Æ¸ÍÜP-=ûÝü÷á¼0Û·ïJÍʾ6ñ÷,* +ÌñÞÏ-¶»êæ·¹@ê=ÇC¶ÂíâöÒUÚá;5äM³èÒØðB¯AµàÍ´D/öÜHÖâS¿,ÏÚãAá?6Ò3ླྀN°¼¼@B.JFW6.J +Ø·J8=º:.üSZÚ¹õä»ÙÔÍ7ù³éÓSÍ@?MIO°LUÇ¿==>ݽIBKö»*Þû:MÄê½êâÆïXâÀM»@YïD¿Á.î½³ýÄ» +íïÁ÷ÞíéEîݽÍü½½üú8ûýìVÛÁÕìÚ¶1ìùõè51õüóî2Úð¿OáÍÝXFXUúý½ý³ÀÄØN¾µíµà0RÞC°O÷ùóÁQ +ÉÜÔ+@ûÝû÷ðá½Yô¹°,ÂTãÊ1*îF18G¾JÓ?NR@GËMGºIÌTßÜ?ù>SNØYÏÀÇ9õ¿UøN¶HG³à²¯RòøÉïè/F +?ïñåÁµLúºÉ»@ö÷-*8ÖÜäñåMG;VR*ö½:.üÇYܸïØOUÔÁóÈÓTÒPÇ4?JIWéÈ8´ÞÇ3Èïá9Ê:6I*îûXËE +õIõ·ñÎ;ÇøÖÛW;¶Ì*ò°Ù×Câ¾Ùü×±åÌ8ýYóðÝUÅÜF·ïÝóôëÖG4ôó<3FµÅ<ÛYõGµN+ºýÊÎÏXCÎîFÐ;½ +6¶ÈO?4Vå±àãÕ³ÞOýI½¼ºöëÕÁñß¼µRJR/¹**õ·î0,>òPåºÓÈDǸöêãÊäÃé´æ¯<ÉåùH¾S5WLêµ+¾*Ê +ïÈ/ä.ûú»½Ò¼ôèµZíE´åÆNé<óø2S·º2úI2,ýN¶ìÙ³åÄûèÙXËø²çÏK½ÈÌÍ@ÔOå@îâ.ã÷ñ1P2Ì9*öë +ÓÆKºé<àS.èCÏó+÷=¾ÅÊ9EöëرÑôÐM½Ü6·ðÞÒßÕÙKîO÷×˵9VâT?ºûÓEز»3çÆJ/Ö³¸LÇ<Ë3ï·7¶ç +úåÍA9Hùó*»ÖYàFÎåÍI±³Tð²ê¿MÕÐóÍ?¾ÎÑî+ÕT2<Æ8CTÂÆ4ÀÈïT/2Óïò-*FXYº:CLâ2<¼.R7FÍô? +HSÏJ»ð>6IOÁ:5׿==.éí+¾ý8ÖæÔTÄ@Ï.à9ØJÜ7J8¿û¸,*ÞU*¾úàóÖ2 +RëÁÄM+Z´ÎNÑìÜ/²Nï,ö×Ú»ïHÜ96*ø4*<ä+Ú¼ÛäXÊõÏ<¼³9G³à²¿ÀÂöÆï4/8ÊZ2¾=YëCñäҿɽß+K +*Þý¿:2ÃÍX,*RÚÅÀâÁ-QP2ÜýÁ*¶±ÀÅ7*úY¼MÌÊ:Êý1+¾R?µI/**ýÉíÆ==.õýK*:Tàûå+*ÉíÆ==.ùÝ +½*âÞ,QP2Ö½ý*Æà/Üý3¼ý=úüQÖÍP2R»Ò**ýõ½ÀîýM/¾-O±óíåÕIG*Îúõé½÷=ÒíãÕåý¼öâ**½ý8ÌGú +9½ÀDÚöòùûÕÍ6*>µìرù̼E=.*üè3°é×µý@ÝÝü**×ͽH/ÎêµÕÄûDóý¼Àú¶ôèÕßåÝIC*RøâMͼëö½I +K¼ù¸±æéçéI**÷ñS*O9ûñäÕíÜFK*î**¼ô3°ÒÐ=µX8Hû+*RIÍ,RöâEM@7óã385ä4ÀK*íó.·OæÃ?ùäü +»°*Îê»UX6¸ã.PR·ÅØZßÒì÷÷î¹GKÄæ÷ZâØÍQÍ6*ò·ëÆ1¹ÄB¿3TRãºíüOT´;>ê×½Õ=ÒùF9¼6¹öò**¯ +IHê÷Ü.PR?úäÌûô/½ø.×EåÀ9Ýüë¹°*ÎÝYI@ê×Ù.P2@Â3ÀìÍø.×ÖåÀ5ÕÜWù*JP/¾Ó55üVV,=ÒÎÊûôË +6ÒI»Àú4²àÄUåÄGC*æêÓ9A0ûT,=ÒÙEOÖ:4õðßÒ.îÛÌίLG·ðÑ=.*Ì2ÞÛ3LéÈ=Ñäòö.±´¾õ¸Àææ±æÉ +QÝFßù7ÎÛ±½87DÏý¼ÀÊÉVèÎMÝÌ»**²åÑ*ÈúµéÔç´ØË3¼Ò*BMá-ASXû¸³ìÇ+¾¼åüù±PÞüù3Tôî0;Ëê +¸´*¾èW¹W²TÃß±IKã¼:æ¶ò*·/Þ¹GKÄT¸ðäãßÑI**óñS*/ͺ±âÐÏÜFKÔÈÀåíçµA¼ñ*Ç+Þ¹GKX3¸¯ãÜ×½Í6*>¹íÈ5Á +X÷ð.O7Î1Ðõâ+.¹*í÷.CLåÁ;é¼H»°*Îå³MHêCàÉ=>2ÂIÆÜ=1ÆV+Üé3è»Ì1IÄGº¸*¾Ö99ôºí½IKôä× +îáÕ½ùÌ6*òõéÅ/¹ôôð.ãÊÌ1Ðõâ+·IQGóË-N0*íó.÷?æÂ7 +ÕÌ7¹°*Î×UIX6øÓÉ=âêÕè×ÎÞ/LÅ8**ºå=²ÊRQÌúò.+¾=,¶µÀæØðâÒCGGßÚ3îÛÌ:Dø6øðçAQö¼-²õê +ÍMÁPòð.+¾5*öÖQÆ·ÇO7¸òé×1*¾ôº4JÇý´ëDëPAQB÷+ºÕ=â-ASXû¸³ìÇ+¾¼åüù±PÞüù3Tôî0;Ë긴 +*¾èW¹W²TÃß±IKã¼:ÊÏÌÞü1@¶õÖBZÎóû½*ôï-<Í +ÅRâ0ì??8*N1ÝüýI±¿VöAÒJ*´2X**T.R²<98Æ̵ðúý½=WýýÕáÆü1ZËÑ÷.ÏÁóÃð2ÞØ82à+LæMQýý1ã +õRÁ*öÝH¯:îÄG4?ü¿*êä.WT;é8IDKͳELEê+:4æËUWÀGùJòÈIÆ¿¹+¶¯À켿ýÈ@ä½EÃûý9KöÏ2-JFÈ +õ=-üTEöëÍãø9¾+é¼ðÌ·+±é.Ûéá-ì9·-ýý-Õ9ER2N:ûò9îÓ2ÆJKU=úýý?äýý9Ï<ýíüÄøý+ÂÕ+ØÄ4Æ +.òY>źÕïâ.Å6òåQý×=ï½DJüý½ï3K*QêÏRï9.Þ´P2×=µSãÝE=âé×5Y¾G*.KíBJ¸ô2ÀÍ9âZÀÔHÁý3Ä +3:ýýƾS99J*6WG/îýAÂýý½¹0ý7*ýýçIæC±YÒÉÂ+-ѶԽXÀð0î8*íÂ*ûÕî¼·¾ýý½¾ýýýËIJãË0EEJ +VµÆ½³ÀÐI¿==F°¾îG¾ISY³ÞÓQ**Åêï¾=H+,Vå.7¹ôÖEÊú¿ó1VR¹¹Þ8*-Ùó,¾úå:¶0Ü1ÌÃ,*2AÈLÒ¹R°Î*üÑàQ¸øM=V2J3ìÕ½¾¶LÀûJ*éÒîÏÝã,Ø3E*ýïN»ï1LÜÃ==VEZÍ +¼Ø¿ûJ-8»À*ÞîÄG¼Ñ3¸ÒÕÍ=Ë¿Gù,ÀDÛDKYÜË>ô*Vë÷ÊÑéäÌ»;¶äM/O+ìÕQæ÷M´ÐóýX¾¾±üêýý½8ÚÀ +Î21ÞüÈÎýýAîXY·59HÙUK.òÜÛVM>Ö@¸û¯·6C.3R.ÎïY0*üVÞ7î;D²+HJF·â¸:¾»Û¯Âý°+Aì+¾ý²O* +óÌóAµùý½âÏT2ÌÆ+GËðP³¾>îüà=V+õý+Ú¯ÅèõJ*ÙCKýðß¾:V×±½ODFF=ò>ûýYÐóWK¶L0ÂÜ,ÈÓ+<6Á5ÍZßBú1îÂG8òßE:¾ëÊ +îG,¾ðÁ*,¸ÁÁ>*é9¸JÊZ@+2ä½+¶C,*ïIØPG*÷Yã»0K:¹ßã²7µæïIBÞÌP2*>*>/Î464äÁ13ËîÇ-N·À +´ß¾=Ã9N¾¶QH-;¾äýÀúð,ÀGËî?/¾ðCïKÀ=ú.ÇZ»=ÊT/Üý3PÓ,íý.ÑT¿ÍTÀùIß9Ü0íý.Ýý3¼ý=Úó3, +TíI*X¸*ú½¸Àîýù1¾É8öüQöûÍîùIßõ½À½ë.OÛ6»UDéIK3ÅR:¾²ÃòÒ4*¾N4;Q7:*ýÁÝÛ¿±;¶ð*àÐæJ +*òýWùU³á+úÓýýQ960ÉAÎ@ÍâFIÆB++Ϲܯ¶ß*¯**45Ä08Úß¿B6T-*1ÀÞÖÉØÞLÛLÂÞò.*ÒX6*¶¶ÇýýÅ·öýUα:ÈÛøÝ9ðM¯KñÆÛQ8èÃ+ö +Ý=ÖU+,*äÏDæ.û½NÊ0÷ýýTE-Ýû×VÕ.ãû;µîý½îø¯¹/ý½¼øÍG3ýRçàúµÑFÎ+-ÔûâG1Fî,/*¾àÀÏ¿-µ +¶:0Ñ+îý¿:ÎVõìI·/öýLD,*TA0*FVÂýñQÁýý?Y9WðýÕüõ¸ýRV.Àî¿*AÈÏ@îJ.â½*QÜðNHÈ@86MòÁN +ÍRVRD¶î0*¼Ãß²Þ/E21îíÚ,ú½;7îýÃX6*JõÚØ= +¼·.0;Q±ýèý±=²Æ¾SýýÂHè,8¾ú1îâUÌâMçà¿/Jö8ܵ²BYæÚîÌM;JÕÐäE.C1>ö½7ÊÚCÄ**B÷PN8î, +F¶ÂQõP¿üÕåîÎX2*MUÍ/RÂ.ν-ØG±ÀóA¾KF/ºIÔ+üÍêWV¾ÏàQ¸ø¾*üòÑ7êýE:¼Ð2;,ðGÂ-8¶¿,Åîó +À,T6T-8.+CJýýËU*01Ð+üIçÃ3*ÑæÓßèOYTÏåUý÷C¼>@N½áDµÈÏ@îJ.â½*µÜðLHÌ?»èCP÷ÊKÑ.îR +.îÓý@3ìY:KýQýUB·N/²7Ïü1í-8FØĹÑJý÷ð·RA.JD°å,Ò8óƸDå.û?µïß±¹óÝ+SÜí?K·.ÎïY0*üý +ÑHãËýý¹+,ÌIZ½K-òÜÛV1Fî,/ìÌZÃKV¾>îüJ2ÊÌÇìÁG2,SÞûòÆýýYòà*¾SÌäËýB2ýýý¸ZÕüµH0+Í +ÝÝûñÀ+¸8ÚÀEøR5¶:,ðI¾5ö½Àò>ÆO÷ÀÄ>îüJ18þÜø½IßÇü-NV½ëîÆ;Xîô.YÙMØW÷Q +PRòZ»Z-ìG@06²»ý9*¿òîUÊè>¾59âÞÆÝýýùô·2Zö9***ý+¾¹L2+2¾UØBýë>ýýI<ûîAÀÇý*ì3KËÞ¿@2.ß9²*ü1XI´/ã,1ò¾°Û¹½TîJ*L/è2ìZK.Þ3W**Q +<¾ÂM÷üôLXîáÓÆÚBÁ5T·¯0øI*ZëQì·;.AÒàѳÚ9ãÃü6;¼K=ò5àýýÝGÁý*Âýý=ZÑ*:KúF0Þý½À÷á-Á +,õÂî*äÈ,2ûîIÆ,×2ò2JâíÖÞ3;Â*ÞýÞóáÔî¾*óÓ´îÙIÉó,8L*8J¾JZ½Ö´L½Æ:T* +÷à÷Yëî-=6;J×ìÛYºîÞØÍÚñIÍÛ+³ÛõMÕ¼íîN5:1ÍôQ<21:NÑÛ*ÉJ+áJRÆ1¯ÐR÷*AõúÅ2*-+>,ý¿-G +¶Þ.ÅÄJüý½Ô-Ýæ.;º1îQè5ûDñMý=ʽ;/°2ø;¾RÞ¯+*ýÀ2L*VýÚ.ãËç/+æ/ïýýíÌß/FÂ*F2ÂDKZÝÈö +Þ.¯½7×éö9¸**È7Â-ö1öÞûýQíÞý.öOæðAM2YôêÇ@BÑ9¾*4JNÏWÛ½ß:¿,¼*OùH¹úýíÊËVö,æ2+¶:èQ +ìMRF¶ß.ÅÄJüý½Ó-íæ.ïýQIëÞ¼*³ôý½/í9J¿Æͱ.Î4¶VîÂ2>¾ýÒöÇÌüà.+ÁBKöHPÖDîÞäÀ*âËÙK+ð +ôÉ5:BYüTK÷Èí2ÞË3H;,YÞG,æöA-*I:Þý-¶ãWºöêXÛ2ÀE¿ÞTÃRÔ9·2ÊA +JZN¾>-ÆûL2à9ÊÌÇìÁ?>öùô°ûÕ¶.ìùµ´°ÂÞùñQÙ¾èýÂHIM+ýÄ9ûдüýå߰ܵTN¾Ìòñº¸L?å0*Ï5P +¶².ðýýMGöÆPÆæUJ÷Pò6öD.ZÊ/2±ÃÅ*ìÊî3;öZ6DÄ:ïÊ;J++úI¾ôÒ>Z¾?´öâ8Xî:ÔÚ+**º/5APöÄ, +Þýö-áV.ÂÇ-/ìÀ,SêýýÕS/;Æßð+:.*ÞýýE2*Z<²F¾/ýýI¾6î:Ã5VÉ/ÂOÏ¿*æýÙôÕèÄ-öUýýÍHB6@YJë?:**;*å*:ÜðX1Ìûãïã22Û±ZÂÙSƯ-8 +øMáÌ:Y?<·¼âKÖ@LGê;Ö½íF;ÕËõRé.ýÝ4´ûÊ:½´ô¶ûüñÅåLµÕIîÄ.ìXäC7⺾CBæÓLVFÈ´¹Àð0ìJ +*½5å½***ú1ÞK¸öóÝöJ0ÄöÝýý-*ÖKÉâü½?SýM5ø9*üIA+ÀýQ°Þýý½òòýýÍ,UõÖBZÎó»îJúÞ*¾ùÌÁ* +ÁýýåV:5üÃHÊNFè×Á¼*ýY7ß+O64¶¶ÇýýÅ·öýUÐÑàCKç1÷ÏÙF¾ÃîÇ=DC¶BY;ýýI<ÈE½Cò¶·C?,µÚ,* +FƾUÛ·î0>DÇ11:-+ºú¾âñ=N;*EFï4GBßD¿3ÈÛBÃ*ÃMR¸ÞMA6ÒEQR:Ö9.:ó=FæEõÅÚ¿ýÈ@ä½5ÐÂ/ö +¼øѳÚ9ãÃüÀ;àÎ+¿:º>ßÃ6öýý½¼øI»ÈýøTEöëÍãTµRÇJÅøß9/ZÞóáÔ:LZ<ïæ.9ø+ú½³.1>¯*ÞßLúI +½DúýY÷øæ<ý½ó½ÕùÅìð*@ÀìÌæNïLÏá?ÏF÷É=ó¶³C?,µÚ,*Fƾ±¸Rµ:¿Àܾ6.*VùǹÌJÄòÑýÂUòNï* +J0½îäÅXKç¿íòI+ÚIKÆÎP¶ä=.*úÅ,¾.KÞ<*ýï*YDèí²»BIðF@¾:+ÑHÒîJ0úýýîGºíæî½Û:üýýëCÒE +FÆ.BI2ÞøåORìÂÞNàÏ3HòÁ±M¶J3-*èGÕôÂYü98ÂÆÑ×½óý×3À.-5ß2*Þ3Ë>XÒYÚ<ÈÑó¶³C?,µÚ,*FÆ +¾±¸Rµ:4ìIç-ñýI»-QÆðÆUÈ*Þ¿MI¸.O1¼ÑSQRîÚ7.:·ïFC9-î½-àó,¯+FÚ÷µ-4.°Þ.óµVñýý5C2, +Î͸ÄSüIÁÎ?ö¼Æ/úIÎ>Ïî»:¾ÏàQ¸øE¾TÄBG¶æ1õÄÎ>Ï*Þ>¸³ÖøÊ:ýÕæñÓýú6I¸ÉÀÃÏÙMD·ð5ÕÂðZG +ìWPÃ*Z¶à?*Þ+ÚÚÓ-Èî¼ûE»:íKFLȹX+18ûæÏÄFîÏPGKýT°ûÇÅZûÍüAîü¾»ZAJÞ÷±I2îýýõæ+½ï3 +Ø0/î·î4üý½;-;*ò>ÆOϵìWºöêXûµÎFò¹JS¼SÜúíýM*7IW0*ü¿N6*Fæ¿Á¼¶µ.O3?*P·DµüM?úýýé¿ +ÛøÝ÷B.ºüüôÍ9ßL¶+*¶N=K*ï?õÂðâY0*XXPãë/Õ.MÚ¾ÉM¸¶î0Â0W¾QPÆìÙÉó2Sì·/F¶àBоùURTÝç +Ò=ÒýÃOÒ*:üäÐÄ,Â3Èý³-+>ò¾Ý²KÁÖZÎZ;Ú**FÐÇ:¾öȹL·ðA-*AAÑÌõÀUÀ;XN +Þ;GF¶-ùè+ß.1ÔÞVR:Ãû;ÐÚñ4¶î¯NB0ÕÎÒ¼ñàHØñ.í>J:»È+Jæ»æÝ<ÈVÀ<̼ã×1â¸CÅ<¾ö9Wñà9Ñ +õÅ0ò¾ÊPBU12»ðôÂ<ÙÐÂËö¿WBîNïÀ¾ôùüäBßQG1íý.Ýý3¼ý=úüQöûÍîùIßõ½Àíý.Ýý3¼ý=úüQöûÍî +ùIßõ½Àíý.Ýý3¼ý-ÖÇ34ùÁÑN¿òîîãÌ:*¶Á*Å0*ÀI¿*·-äâ¾Éôà*ÑöýÛJ:BØÜL+µ3â¾0ýýéÛÀ46;DD +JùÍ-+àGýÑ-1Êîê2âýýŸî3Íî71¾ø?-ÜäSøúÜûÕGÝÆËï2/Úè,±õíýýI/Ñ5ýê/¯,ȯ9ñäûÊFG¶Þ.åÄ +Jüý½Ò-ýô.Û¹V6BæøüýFÇ*K²¾**î¾/Xö*<¶ýýº9Á¿¾ß;42HÑT,:ÈÓý×ü41ÎV<ýýY÷É5ÆVP¶².,Þý± +GöYQÖ*ÞýÝè0¼ù±×ü5.ßé.8ËßR*ðîçüN,ï*À²ØM>¾6./÷î²FÎ7îðÌý2+³*ýýýö9Fæ¿-P¶².ðýý±Gö +YQö*+úÀöýýµåîI<+PûÇ3FDKµø+2J7üý9WVÛ½Â,¼*OùH¹úýíÊËVö,æ2+¶:èQìMRF¶ß.ÅÄJüý½Ó-íô +.+ÁBKûÌß½û7éFC¿M=6,¾ÑøúA¼TâìË3¸9ñýÝYîýý0ôü½ë9L.ò5âýýY0±´îÂ387¿úýýR1üë3øUìàÁå +ôèýñ,ÚÀî*SÞûËC69öüúç@ÎøIÏååRÞÝïMÝÒËýý·ÖÊõË43;Î=ûFÍÌRã×+JòÀ387¿úýýR1üë3øôÒ>Z¾ +ãû½7<¿Ýî*3Äß+5Æý*,*òýý7.*¿÷â,Ä*0,ºúO0ÚÆüý½YÁÀ¾Éá¿,èIÛ1;ð÷½Ñ-14îà2ìòÞÍGßù.öJý +Ô;PöçåÞùIßõ½Àíý.Ýý3ôÍ3T***6æϳÈÀ**ÌJ*Ú¸Þ*8öÙWï0ýýýè+*ÎýÕ4Ä3¾ÒA´*ÔI¿À59ö³Q2ýý +IY²Åêø*ôÝHK¼ÚëôûØÛ¯¼ÁÂø4Cöýý幯¾16î-ÍÎýYýÞýC5@úÁýýÍÉ*K¶J?¾µÇú½?SýM5ð쯯ÞJéùý +*>Þ0êOWZ;Øî¾/û?ÙÞVüý½íñÎ*öFîîâ.ïãÙý±éº¿¿øè?÷7»CE=²³Á»ùûñÌÎMåäæ½îø¯UZýýM2¼NÎ4 +ÞѳÚ9ãÃü6;øUëÞµ½@é½*ÖÍüVõìÍ@F2*½O³ùWýýñÈI>JîùT:R.¾Ñ÷ûí÷ø¿3Ä2:ÇOñÍ5ÞU@åáóõíÌÎ +-*:L?ñûñ<âð*úý93BóK,Ó*ܲÒÝçøZ¼âCY2ýýýô6ü1821îM5ìÊÎöýIØÕP2ð1802>,æüæÊéñýèÎྠ+*UÝX:Ô*ìÕ;ú×=V/ZÒöÇÌH<¾òµV=BÎ4ÞÁÙBõ´J¿Jú-0¯Àî»ËÎCJýÝýM=V,:ùH1/ÆåôX¾ÞÆ*/ûAó+/ +60ÎWõý1:Ì12øàHí3¸.ÎïY0*üýñ,ÚÀÞ¾-/îG¸ÞYºÀÔýÕÊÙöî×ÃúéýýóÊòHêã3ðáë¶íÕÛëì5:B2ï´EÌÍß×:ÚÄÂáÈ·A,1Bî@DÂOÏ¿*æýÙôÕèÄ-öUýýÍHB6@YJWPÌçÞ=N8SÒ·1YK*ZýGEÚÀ¿ëÊ +ZTÂ2A¶¹Àµå:Ñ3H@¿U;*ùYöÞW²Æéäà»Ò³ÀýY/Ù¼PÆIE¹Ú¼ý÷áA¯GÛ¶ÀäÃV,ÔMÆ,*ÎöJ0Äú74î*Eú1 +*ÐÁYýïýC5@úECîý+F¸øý½R¼Í*ß²¾º4Fôî*,Ú¹V6BæøüI¾Æ¿4/ñG1+0AöJü-RÆ2+¶¾,MÍQK+ÜÍüý*ú;µîIü1XI´/M-üI¾ÍÍü±Qàý3 +:Â0¾òï*ðýý-2ÎÕØ¿íüVÔ¸ÀSÆ.ñ-+ÌîßJà*JÏÞ+ +±FÆ*¯²³ÛÞ3HF·À=±¶¯7Aäî¾1*¾5±BK:ò7FçÆ-¿îOMûËÔ1ñMÌ9ÂßÃT.*:NÞÒÕKFàÊFÎ+ÛñèÌöãRTû +9·DùÕüIEßLÂ5R¶*4AVï²Yøñ.Oü3;G0Jìý*L*N-àã.*Þ/BßýÒöÇÌüà.ïýQIëÞ¼..ôSåBãÉ,4,*Á¾; +ö¼P2ú9GÎýý?6äIáÁ+¾L¿°Ä53:çÆÓ5*0Qöîà3ÌÊßóÚBÁ5?¶¯0±+ðGÂ=7·Þ9HÚ÷µ-4.àý+9ÚRÃ** +ÐݯË,ÁM9Ç-Dî½ÃX7ÔH=âý?øºóIIáµX*üALÚ¾À-ζß4QùÐIQRÖà3.:Þ²Ãèî,ì¹+,ÌIùU=ò2JáÝ6*ú +ý/¾ùÖ*ºDãËýýí*+G/°ç+S>.öUìàÁåôè½-кÝ-BÀÉðãýüý/HÚçÐL¿èÛ53JôÕ.GA*Õ8îßË=Ò´Â/ÚÖÄ +Á=º¯0¯+òÜÛVQDðL:²Å8ö5ÍÎÓºJ4¶½Ï4+*òF¶/Ó4ÅH°ÂïHDê½ãçÚÃS0EöóÞXûíº6î÷²½ùå/-Ã2AÎî +*>8>ÞÇ»²ê3ÈOL-+Úð.ï+ú9Ä+ºÙÿ+:ÕPæEÄ:ïÊ;JýÁË-Mø4Rüýý022»/W9ÆLK¾µQO+-Vìýù<8NH¾ +;-Ü0+6QöîPÉ4/TõÌN*Ç@¯¸ÚÞ/HÚ·ÀèÄÖ¾5鶶Àîý¿Cæý*VR:ÃûÇÄÚ2.Ë?èýû8¾ÔBÆZ6XÊJ**çÆÞY +ÃÞÏÅ6¯ÚÅ0ùÍÝ×ÒÑÒ¯*δ:¾À8öÉ@²2504;XJß+RFR¾=ùæÖ=º³*Kâ3.Þ¯6:òHòݾO?J80·O+î¼PI¶ +º2õLî*-ÐÁÄÜ@ÀÊPBU12»ðÌLÞ5´¾OËîÑÌÁõ½Àíý.Ýý3ôÁ3<*ýÁ9,FξÒ+îP6<ÕÁµ¸À/¹÷Ç8êÍë.Y9 +KL¿ö@QæÙNüÝÈÒýÏ/Çý*,*ö½*:GúøýýÇ-öýý9GûýýÑÞèG@06²»9@2CZý+*ê:ZîÂ7ÎöJ0Äú7AZ9I:÷ +ÚPÖ:¾**êîÞ6ìò,ÌÉ*ßýýQÖÎ,ïÉÌÎQé¶IÈ5ûCLF2Òýý-îÅÚ¿R*÷9JG¾ýýýñÑýçÑúÕÕØ¿íüVÔø¿:*ç +Q½ÞÞ3EJO5ñÝË»¼íüéÌYâäRCR>îT5ÊÛ½ýýý>ÊXú±AÂ3RÄ»ÊEõ±ðòÞ+=FEÀEàRV¶ +:/QÜ:Èï¿Ì:¿òîUOÜ-*îI¾Å9ƺÔý+úýÍøÓ;¾Þ¿B*Nîüý÷H0-+Á=¯ZýýñCÓ<*ÏVûÍóÕ¶*ã<÷ýýÈÝFæ +¿-P¶².µØ×ÖîJ4XºK3áýFKðÅÚ¿R¾-,ZÞØÍÚ±RJ@,G,R=Îý·ö²½<¿¶*KDëÇBîýýC24ðßçæGEÞãGý;, +è*üýýïI¶Î-1Êîæ2L*ê¾9ÛÎ1,À+¿/1Ðîà5ÈBüå34ýýY¾Z2*÷ýͼ*Ïûý½2ø;¾D+ýíò-:¾DúýÍLÙ¸ýN +6L÷FOùH¹úýíêNSöàÚ8F2á=õ;>8ÚZÀMM2+ÎüÑÙEòýýàÀ-S¶ï/Ï6Iò.94¾º1;PJâåC,¶¾-=*¶TÍýàQ +ÃáÛ3-õ¯üýù/üýíÞìýÕûK¾¼¿úýýãZä0ÌBßÏ;Ò*¾ø;ËÃ/1ÐîJ+82á=GKSÞû2*,öýÜÛV¯,ð?Ý;¾-Fî +ýùñQÙ¾èýÂÞñ±¯îíöÏYèäýýFVäùPÃÂ2æǼÌQQÒðê*:¸ß.ÅÄZ,*ñÅÚðýýàÀ-S¶Î*1ÂïÇÌÎ.áZ.*+ÖÎ +J4ùî*¯Ôö2¾ú1:*:¼Í.*¿÷â,Ä*0,ºúO0ÚÆüý½YÁÀ¾Éá¿,èIÛ1;ð÷½Ñ-14îà2À-2Á,µÀ4558Nß¿A6¯ +ºÎ=ºß*Þý2JúW/¾ºß½.ZQ*Æ¿<ÔO6ýé.5+XÊÎμ³.ZÓ*ÖW¯ºQ=úãÏîûÍîùIßõ½Àíý.Ýý3¼ý=úüQöûÍ +îùIßõ½Àíý.Ýý3¼ý=úüQöûÍîùIßõ½*Ýç.ÃûßW½À*Þ*JÔÂJ*ü1ݾ¼.EF¶ÄUë¶29Õ²ï05ì1â.J0DÆ*H +L*Ú5.BØüí½8.0ïÌýýýË3¼*+RØ5-Êà112àX8óÞÃ;Ö3-VÒV¶:µÞÊTFSÐ5GÆCËØì.98+êÍÆ:Ä18ÄKPZ +¶õ³·Cè7:N>/·ÊîûÞFÖ,ÕGÝê6ýCô¯ÌËùC÷ýýA39J*Ô·ÈHݵß+¾ýÓ8ÚB,ÁÝýY0/1μ¯á3¶Úæ¿0/ßÂP +æÛá྿Ôõý+2¾7P-úÚ.2üýAHÆ+HðõýýÜñ:2E¸*¸á-=âøæçýýI/X»:*EÚ¶,G±ßÀ½ýýÕFFß.,çßÕ5ɶ +*ëðI¾4+*îJ38îM;ߺÀ¾ðÙ²+üÕM>@Ú±.ýý½ÉâýUÇøYL5D²R³·*9XÏ9ýàÙÞý9¾»ú¾âñ¿¿E:3+¶±3/* +ÜHÁýV<ýýY×»÷ÍøÓ52î.ìýIíá½èÕÌóÞ/7ÂL,CAìòæ.+¾=¿¶±ÀüTëZí¼ý½Ó?ôèdzÕÊQRÎKQæY³-Ýû× +Vý+ƼøQ-.Àî¿*XçàúµÑúýýÆòÁý0÷Å1äáêåQýÑ17Ê5ƼøYF¿+Õ9ÙÀ5·¶À3â9·-A@ï,D*¾4+*îJ38Ê +J;Kº,¯Qà½ç,ú½î2Á/õ6ßýýÍÔï¼>Aø5Ä2íÊî+ºJü±TÜ4EØÞ¾³I·åÞÞ7ÆÂ*Ú×Â4*MDë½-¯C2ý¿ý5¿Ü +¾*åßÑûÇ.üY.øV1Xæ¾GP³Q÷M·¿ý1úæ/ûVÞÃ8Fï*ýýÈã÷¶ûû½OïX½ãô¸XAÜÎCQÖ¸ìAÂýý½¹0ý+F¾,à +.Êøýý=Â5Þ*úýQBÝÈîÂ*èá:º1íEDºÎ1QÌÞøåORXZJãß3PÊðGÖÂ=ÀR;**8>ß¿3>ò×,N=3äý½ø08ÞÞÃ5Æýý9Wà.*Ä´óüý-;¸öHÀýÁÎݾ*´ø+2J7ü94ܹÒ-A² +ÞæÅ´´ê+F>´Býçë7D5¶Ï-QFÞýÝðýýûWýY/.ÆýýEó.U+*ÁöAöüä.×üijüýí¾¶W¾RöüJ3¾ýU=²ýIó6ñ +*Ïá,8F¿ÏàQ¸øÞ¾È-Þî0=À*T.ì×<*¶Î.APÖNÞHû²-ù¿0Ú¶Â;¾M=S@ßßWʹEÎJ:=Â1Pö,R2S7È8òÀº +1ï*0XÚÞËÇFJ7;¼T²Ì½¿úýýãZâå+ý9JÞÚ¾Êè=F*üÝè¿ED:ó;ÚDKùý½»-Ñ*ŸÎFZ>Ræ5éÞ=ÒÕÚÃ1Íë +Ó5ûïýÍ-ÕîýýX¾F¶üêýýÝ>YüRK°ÎºÝE=Ò/Úý½4âÐ-S2;¶î.6+²úÉ+¾ýÝðî2=À*T.P*À7ÌÊîË3FCK¸ +DãË5ضï2Á/ÅPð¾O²Å8ö5ÍÎó*7P.8Þ-ÈåÐú3ÐÁ,òÜÛV1Zî,XÞý==3É2*:ÂH1,öǼÌQQÒðU¼A:Jýõå +Í´+Óß+å¸îÀ0VüûKÉÚ¶Ñð1ÈIðȯ¾5G*ýY¶*ý½ó½üÐ<ýõí3àôÒ>Z¾Óæ´,Þ³Nîõý½öÙB¶-Jüý1Ü0+6ö +ý½Y8NÜCK7Äß½ø4ØHÆÀ2¿-QÎ7Mƶä2Î-·¶Â3K*?,=*ß0QPöä.ÌÊ:ÏÕ¾ð/WÚ¶ÂßÀ¹Ä2+Î×LûÝí+æ-² +B;¶*9¼XãýÃWRý9¾×ÎJ4ù-B¶+AîýÇÇÂO6X.*öýüç»2;ͽ±Îö/.æý7.*¿÷½¶Þ×8ÚÎ+ÉJÞ3à1Ä°²Þü± +RîPÖïÇ;¾ý1FZP2Ë÷èRüýE=º+RG·À×=*øý9Û1ûçÙK¸ØÆ?5ÌWJD6¹¸3ð/ý²F:+0ÚFÀE824êÅ+ÖÐ3Øý +ýóÇéDÙÇ?æ+óÁŹç+C±ÞÑBßQG1íý.Ýý3¼ý=úüQöûÍîùIßõ½Àíý.Ýý3¼ý=úüQöûÍîùIßõ½Àíý.Ýý3¼ +ý-öüQöûÍîùIßõ½Àíý.Ýý3¼ý=úüQöÉÊîùAÞÒPöû9Þ»4ºý=úüQöûÍîùIßõ½Àíý.ùò.+ÓÝ4*îý<Æ@SG +Â>ÚDßÝèõ-¹¹ÓÁõ½À×;áùIßÒÆïÓÍÎ:YEã3HÈÀõ¹ÀèÓ91,>æ¶Ð,*JD°:°ÖFî¿=¸¶Î+åãîT4¼í3°Ó>º +ü̼¹ïúP2ÄÝöÝÁ=ºÌ*ÕÐîùÌÎÒ+ø¾->õR5@ÊèGÊABµ.JñKB09ÀX++ÒZ-V?,Õâöî.Á6ä2Z²H/¾Â»G/Ý +õ./,ï1FJK?ñ»ùASÅPR;Ä0Î:²-++Òìò,Åæ:Á3Ì;JL+ËUWð¸4¼ï3T/ÝױݻßÏ3Ô,Ê3Àó-+CËî6¿L** +*¾*ç9QßõGKCÑñ+1¾/¯ÀÎù¿3Ðó*ÖÅ,..*ô±ÀO*CËî:¾ZZùÌAÜï3øÑ:Rð¼Ì¸Tô4È×íÜÇ¿4ÙM/RÑÉý. +ÏÓGÍÑé¼G/Ýõ.GùÓ¸@>¶¿2¹ÛÛSÀ/϶ÅëÏU78HHß5û5*¶P1ñ+=À/.æEÓ//¸ÚÌÀõ¹ÀÙîâúÆïûÍZTâöü +QB?°FDKöý×,Þ@Æ@SGFEßõI*CÖÞ¿3,Ê1**´¼ý*¶Å+18Rúø7=ÒLà + + + + + + +]> + + + + + + + +The &krfb; Handbook + + + +&Brad.Hards; +&Brad.Hards.mail; + + + + + + +2003 +&Brad.Hards; + + + +&FDLNotice; + + + +2003-09-17 +1.0.1 + + + + + +&krfb; is a server application that allows you to share your current +session with a user on another machine, who can use a VNC client to +view or even control the desktop. + + + + + + +KDE +kdenetwork +krfb +VNC +RFB +krdc +Desktop Sharing +Remote Control +Remote Assistance +Remote Desktop + + + + + + + +Introduction + + + + +&krfb; is a server application that allows you to share your current +session with a user on another machine, who can use a VNC client to +view or even control the desktop. + + + +You would typically use &krfb; with the &kde; VNC client, which is +&krdc;, since it closely matches the special features of &krfb;. + + + +&krfb; doesn't require you to start a new X session - it can share +the current session. This makes it very useful when you want someone +to help you perform a task. + + + +Please report any problems or feature requests to the &kde; mailing +lists or file a bug at http://bugs.kde.org. + + + + +The Remote Frame Buffer protocol + + +This chapter provides a brief description of the Remote Frame Buffer +protocol used by &krfb; and by other compatible systems. If you are +already familiar with Remote Frame Buffer, you can safely skip this +chapter. + + + +The high level implementation of a system using the Remote Frame +Buffer protocol is known as Virtual Network Computer, or more often +just as VNC. + + + +Remote Frame Buffer (or RFB for short) is a simple +protocol for remote access to graphical user interfaces. It works at +the frame-buffer level, which roughly corresponds to the rendered +screen image, which means that it can be applied to all windowing +systems (including X11, &MacOS; and &Microsoft; &Windows;). Remote +Frame Buffer applications exist for many platforms, and can often be +free re-distributed. + + + +In the Remote Frame Buffer protocol, the application that runs on the +machine where the user sits (containing the display, keyboard and +pointer) is called the client. The application that runs on the +machine where the framebuffer is located (which is running the +windowing system and applications that the user is remotely +controlling) is called the server. &krfb; is the &kde; server for the +Remote Frame Buffer protocol. &krdc; is the &kde; client for the +Remote Frame Buffer protocol. + + + +It takes a reasonable amount of network traffic to send an image of +the framebuffer, so Remote Frame Buffer works best over high +bandwidth links, such as a local area network. It is still possible to +use &krfb; over other links, but performance is unlikely to be as good. + + + + + +Using &krfb; + + + + +It is very easy to use &krfb; - it has a simple interface, as shown in +the screenshot below. + + + + +Here's a screenshot of &krfb; + + + + + + + + + &krfb; main window + + + + + + +When you want to allow someone to access your desktop, you can create +an personal invitation using the Create Personal +Invitation... button, which will bring up a window +containing the information needed to access your desktop. An example +is shown below. + + + + +Example &krfb; personal invitation + + + + + + + + + Example &krfb; personal invitation + + + + + + +To increase security, the invitation is only valid for an +hour after it is created, and of course the person connecting has to +have the correct password. + + + +Since you may want to invite someone to access your desktop by email, +&krfb; can create invitations as email messages. You can create such +an invitation using the Invite via Email... +button on the &krfb; main window. This will usually bring up an email +message that looks like the following, ready for you to type in the +email address of the person you are sending the invitation to. + + + + +Example &krfb; email invitation + + + + + + + + + Example &krfb; email invitation + + + + + + + +&krfb; will warn you about the security implications of sending this +information across an insecure link. You must heed those warnings. + + +If you cannot encrypt the email (or otherwise secure the link), +sending invitations by email is a very serious security risk, since +anyone can read the password and address from the email as it passes +over the network. This means that they can potentially take control of +your machine. + + +If you cannot encrypt the email message, it may be better to use a +personal invitation, telephone the person you are giving access to, +verify the identity of that person, and provide the required +invitation information that way. + + + + +Managing &krfb; invitations + + +Having created an invitation (either a personal invitation or one that +was sent by email), &krfb; allows you to manage those invitations. The +dialog to control these is available using Manage +Invitations... on the &krfb; main window. If you select +that button, &krfb; will bring up a window as shown below. + + + + +&krfb; invitation management + + + + + + + + + &krfb; invitation management + + + + + + +The invitation management window allows you to create more invitations +(using the New Personal Invitation... and +New Email Invitation... buttons, which have the +same effect as the Create Personal Invitation... +and Invite via Email... buttons on the &krfb; main +window. + + + +The invitation managment window also allows you to delete existing +invitations. To just delete one of the invitations, select it with the +mouse or keyboard tabs (it should become highlighted), and then select +the Delete. To delete all invitations, just +select the Delete All button. + + + + +Selecting Close closes this dialog. + + + + + +Configuring &krfb; + +In addition to the main &krfb; interface shown and described above, you can also +control &krfb; using its control module, which you can access using +the normal &kde; control center, and you can also access using the +Configure... on the &krfb; main window. The &krfb; +configuration is controlled using a tabbed window, as shown in the +screenshot below: + + + + +&krfb; Configuration (Access Tab) + + + + + + + + + &krfb; Configuration (Access Tab) + + + + + + +The Access tab allows you configure settings +related to access to the &krfb; server. + + + +The Create and Manage Invitations... takes you to +the &krfb; invitation management window, +which was described previously. + + + +The Announce service on the network checkbox +controls whether &krfb; announces invitations over the network using +Service Location Protocol. This is normally a good idea, but only +works really well with a Service Location Protocol aware client, such +as &krdc;. + + + +The Allow uninvited connections checkbox controls +whether &krfb; allows connection without an invitation. If uninvited +connections are allowed, then you should probably specify a +password. You can also use the checkboxes here to choose whether you +have to confirm the connection before it proceeds, and whether the +person connecting can control the desktop, or only view. + + + +If the machine is a workstation, and you choose to allow uninvited +connections, you probably want to select the Confirm +uninvited connections before accepting. Conversely, if the +machine is a server and you are using &krfb; for remote +administration, you probably want to deselect Confirm +uninvited connections before accepting. + + + + +&krfb; uses the normal RFB password system, which does not transfer +your password in the clear across the network. Instead, it uses a +challenge-response system. This is reasonably secure, as long as the +password is securely guarded. + + + + +&krfb; allows you to control whether the background image is passed to +the client, or not. This is controlled using a checkbox in the +Session tab, as shown below. + + + + +&krfb; Configuration (Session Tab) + + + + + + + + + &krfb; Configuration (Session Tab) + + + + + + +If you check the box, &krfb; will not transfer the background +image. If you leave it blank, it is up to the client whether the +background image is transferred or not transferred. + + + +The Network tab allows control over the port that +&krfb; uses, as shown below. + + + + +&krfb; Configuration (Network Tab) + + + + + + + + + &krfb; Configuration (Network Tab) + + + + + + +If you select the Assign port automatically +checkbox, then &krfb; will locate a suitable port, and invitations +will match this port. If you deselect the Assign port +automatically checkbox, you can specify a particular +port. Specifying a particular port may be useful if you are using +port-forwarding on the firewall. Note that if Service Location +Protocol is turned on, this will automatically deal with identifying +the correct port. + + + + + +What happens when someone connects to &krfb; + + +When someone connects to &krfb; on your machine, you will get a pop-up +notification that looks like the following screenshot, unless you are +accepting uninvited connections without warning. + + + +&krfb; Connection Window + + + + + + + + + &krfb; Connection Window + + + + + + +If you Accept Connection, the client can +proceed to authenticate (which requires the correct password for a +personal invitation or email invitation). If you Refuse +Connection, then the attempt to connect will be terminated. + + + +The Allow remote user to control keyboard and +mouse checkbox determines whether this client can only +observe, or can take control of your machine. + + + +If the client connection is successful, and used the password from a +personal invitation or email invitation, then that invitation is +deleted and cannot be used again. You will also get a small pop-up +window in the dock, that shows that the connection has been made. + + + + + + +Developer's Guide to &krfb; + + +&krfb; supports a small number of &DCOP; commands, which are described +in this chapter. If you aren't familiar with &DCOP;, then you don't +need to worry about this. However if you'd like to automate some of +your &krfb; (or other &kde; application) actions, &DCOP; is a useful +tool. You can find out more about &DCOP; in its on-line documentation, +and in tutorials on http://developer.kde.org. + + + +You can shut down the &krfb; application using the quit command, as +shown in this example: + + + + +%dcop krfb-1507 MainApplication-Interface quit + + + + + +You will need to change the krfb-1507 in the +example to match the instance of &krfb; that you actually want to +shutdown. If you run dcop with no options, you will +get a list of all applications that are running and &DCOP; can +control. + + + + + + +Questions and Answers + + + + +&reporting.bugs; +&updating.documentation; + + + + + + + + +Credits and License + + +&krfb; + + +Program copyright 2002 Tim Jansen tim@tjansen.de + + +Contributors: + +Ian Reinhart Geiser geiseri@kde.org + + + + + +Documentation Copyright © 2003 &Brad.Hards; &Brad.Hards.mail; + + + + +&underFDL; + +&underGPL; + + + + +Installation + + +How to obtain &krfb; + + + +&install.intro.documentation; + + + + +Compilation and Installation + + + + + +&install.compile.documentation; + + + + + +&documentation.index; + + + diff --git a/doc/krfb/invitation_management.eps b/doc/krfb/invitation_management.eps new file mode 100644 index 00000000..2c258a9d --- /dev/null +++ b/doc/krfb/invitation_management.eps @@ -0,0 +1,292 @@ +%!PS-Adobe-1.0 +%%BoundingBox: 0 0 596 253 +%%BoundingBox: 0 0 595 842 +%%Creator: KDE 3.1.91 (CVS >= 20030907) +%%CreationDate: Sun Sep 21 20:40:17 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 9.305 2.791 ] [ 2.791 9.305 ] [ 2.791 2.791 ] [ 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 2.791 2.791 ] ] d +/pageinit { +35.4627 23.6418 translate +% 185*281mm (portrait) +0 795.224 translate 1.07463 -1.07463 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 567]ST +B P1 +NB +W BC +/mask 6375 string uc +î½*¾î1:E*¾1=/·7*¾ÍÌ<Þ¾ú>+¾QQ3*J°ÅÆÍ;°=,ö;ß=Oµ.îJÀQtì2J¾6Í>ÝO+Þ¹Ð.öµSÈ:½*ìÇ<Þ +ÅWSàÌ×áäÞìÆ´ýû±ÏP,*Â4ãºÕ*J:ìÈÃ4øÚ0ZõüÖϰ˿ѸØ/îYöû +ùÝ»¯È¹2*NÙý´ÇH9öSQ?ÞÆYÇÐ**îÆúÒêïÞÅÉÅIÐÆë½1üù¶×6ÍϾ**î>/.**= +/,*¾E>îîùÍý?½üÓI5*ºQçâýY9ý»Yí»¶ìá;Ýê×R¾øõÅC7*¶TøÞZ8ê.0>¼MŵýãÝ°³é¿4ÞýREùêFÒ +å¯X7ôIüµBýéýûüáI0ñ+0Îï´E/ö¯ÊÝÃ1¹VEß×ÀCã÷+ÔùIßÈâåÈTá>¾*GÇ-*>D¾õüÅ3ïýà=ÍF,ìýÝÉ +ÒKÕ0°Fß²üÕ9è+SFæ+ûý½?úGA/IFØ-úRñ½è2Á5²°ýýÝAÌ÷:OêýýòVÛ¿Æ=L×-*Þâûó°Ò÷ÞÒ¸T+Jå:I +0**üùó+*¶N¼ÜñöêÚÛÕµýÜÙYíGõêá¹PºÃ,ÁÍAS:L*ì1S,*µR¾¹õííTûøF9**·òGúÃ×ÄØÐé4W@4Öûû +YÔÊ/*à>GõØJ´¶+ÐáEòͺ;UÙ8Ò*üµ1IîÌß°úñðçýý9T×Ø00ÛÞ*ÐíUÛÆ+X·ýýýýñÉDURÁ6IÞCï=¼Æý +×µ1HîR8à*¶ûýÇøûYýéÃMæÈêýÑÜç¯ã+ú½Î7¿.PñýøÀ8Æà¿8æýý;ù´âY¾¿æýýä8VÛý÷UËAæ¹õýÝÊð +GÈÂ86@Ú*Ä5L×-*ÖäûáLºÎQWÃ*K¼ôEù÷óå4¿ôEðñ7¿ÑÌ»Ûú÷òûº÷é±Ñ0ÜïßM>¶ZÁCÏ*î6ÑNïáåº** +ôYöíÙYÆ<è0HúIÄÃ,Ò½½Çç*¾ÑÐÕéÚÍW²êîCÐFO8UAUÙ8Ò*üÍÂýº°ß°Nû½ÐÏ*Jð¿ÔÛÞ*Ø9Sâ¯*ËYA: +J¾0Ë?ÚÎF1NгûY¿5Ðö*-8ãK6JÊÐËúÝHÏ5TÎ<µPüÍú߯Ä2ÂèÄB+-ù½±L1NïJ1îýýZï½EC:÷ýÝ×-¹÷ +ýºKã=RZÜýö»ÛMÇMOC7¶K>¹**²ÚýëÀ=YÎTMCÞÆ9K1Y¼Â9J½N¼üíµçÔϽ1IÜÁý8Û³çÝý;ºÁ,A¶2*ÚM ++¿üûÆÞÓ>îÊöïÙðìÛã-*¾ñ¶-Í»ºÏ¿Lå7Úòµ/5/êûü?Iä,*ÌÈH¶ùPÓSÐÂA¿0É<Á9Wë¸Î,öIÎïÇ5/ɯ +ûýÃûýYçá-T¹¿+>ûÛÞï+ø¯³ù,Q¸WU¶L·øÏéýú¿ÔD9J÷ý½¸¿/;++,Võ½À³ýéOòÀ4òáÐ@ýÕíòèJúýýÚ +àÄõIXÓ-жÎ-øýýOíCA6¼:ÕýÝ×-Ùù½íLß/VÔäÆZýýHò8OÄ?8Kâ/Ïê+*ZòüQ3ĺ¿T7ã*O¼¾»Ò:º½O¼ +X8öçÔ͵õüÛ¹íüÚóçÞã3ºÁ,QP÷8/â÷ôM,ÕоWÌIíTøò=9*¾Ô¹Eºôìæ-2Ö¹òÜÃÝ/@ZϾCíÝ>áV*¾Rç +ñ@4*Ü/*R¿6CX²³é¿4Þý¾BÌ°?VÄ3ÝÕêD½Ý39Rë/0¶×óõüÅ92MÇRBûûìíË?FÏFãæïºúÊÞË1ÖÞóý1¼¾ +/@¶ûý1Á1³¯ùõA±àóÀ4>M÷úáENòèJúýýýóùÔARÜ-жÎ-K/.ô½Çß1ÎïËGÞüùQ½Ð?Ú¾óõõýüóZË8ZàÏ +ÎÚÀÆ5L×-*JíûU4úö,ÓDÈ+8Æ9³ý8üC÷÷°ßWá¼¼GùôÌûøñÖW1YôYP>¶îÊÅR¾Q¼ýìÈ·ñ:9*¾ýM2ºõêß +Ñ-îïÝMAHºKWý4/êðüÂ=ä,*ÊèßRÒT×T?*Z2JÜë¸ß,*R;¾-ÈæêÎTÖ.ãD32,¶¾+HÒÛ¾ýW9ÈB;DÚÃÆÎO +AÊî¾*ZLÂÅ0?V9Jå2+¸;1;++,ÖÎ@L´Gο·,ÉNR<µå2à++,²Æ>¹3=H:Ã-G2QJï*»5¼*/9êKVÏ/<òè +Þï,GNRÎLÓøÛMÇ°ÃE2·L>E2*ëB½½·+EÜJÓÄ°¾=ð-*ÌÇÍã,ÌÙô-RÒ**î0H5ÝÚRJÖµ½Ä,6иóÔÂ5íö, +*ÚBÑÞ»õPÌ»º0YG÷èËÀ3V=¾SùQÍ2¯<*îÒHøùBZæÆ>ÛPÈKM¼NCòÔVÒîÀ+²úûù·ùø>×=êV2à>ñÚÀË2à +Èë9¸Ù¾6ÍÎTMGÞ+÷3**ÌÇͳ,HÚõ=¼²àÁµÐA?4ÖóêÙ½½?/Úï÷ñÔºùÔö,*FÜRL½1IHGºPIÛçRÅÔ;º¾, +0º½×/=+*SMæG*¾@×RßÇ-JNÀ.ÃÐAÙ=êÊ2.:JÇXßHÂÀ9Xíͱھ3ÍÎTMGÞ·÷÷KÞ¿5Y8»¸ó¼ûù÷éÉ=Iò +Xä>Î4Ã5µ?¹ô=Þô0ÛOø0>°ïÍC鸺2ç¸åî+4JØûç½6öë**5ìý·Ö¸,9HKÓĸ*ZöØúÚ8îÞÁ9µìú·Å¼û¹ +÷ëÙéSA±4RÂٰܻëø7¯ù¾ñJïKM0ËEæ1ÑXï*HK+NÛý÷Â*I7*îÕûùýÉ@ÍÞEÑèS2¶W¼ÀÈʱ*:¼äãÊ,ÚZ +ôêÔNÉHö.*ÚÞ½ôÌïL/4?âóÊÛ½,ÍξT.ÜI¸Èü¼6*¶¾ÁùõACÊÚ¼µøÒÓ8îÚúRGWË8+¾æTA³4ÞèÓW·ôúT +×ûÝ»ôâ¿=åìHöõè.BMìûù±8Çíµ+*A2ÓøüûOôà0îGñÑÔоáðMû仹.*°XÝÒS*KÁ´O×ðÒöëù?Äݽ½ôÆ +7ÁÕý.*æXâÕÁÇÖ±ºÜÔÓÙÕYTÐèäI¾ÉAÙ°ê¶XýÄù@@PÜý¹+*íV±@¯êæãÙAÕR³±:ÞáÅW3@FC=¶»öæCFÈ +ÒYÜÀÕìQ,*ÜÓPÃã¾?Ù@ÌÖÑõ;Æ<ñ=VÖ?ݼÙO0*Ì7E³åÈ35ô5öøN*Þ½û,@êB±çÔ+´×·TI5¿öó¹P3Ö½ý +*ÖàUºýÑúüQ÷ûÍðùIãõ½Èíý>ݽ0ºýÑúüQ÷ûÍðùIãżȾèá.*öÝIQS.Ý×½**;Gî87Ó8JNÁÌÐòõ¼ý** +6¹ÞGDTG*á0GOïÍýü+*Nô¾¹²Ò¸*ÇG¸È7**J°1ÚMM×-ÆêÏÑ.ûõ9**°1ÚMMÓ-¾úÅP3Lü9Ñ.;¹ò>¿ü** +ÃEÆ-+4¶Ê.FF*Þûè,*¾áHÖÃ2¾Æó8*J+ÞG6Fé¾îÅÃÓMU¯LÉÌÐÕYÝHV4MÍÉAâCÅ.Æøï1+ì6**Æß,@M+ +JðÚáÛYÚо=.×B·ò@P*Ç7¸ÈØIºöîë27.B,î*î¯RÛQ+:²GºöZ>R¾1H*+*Þ+1JíË>Î*îà2@â*JðÚ-¾½ +Z4XÎÌõÓÓN19¾Cò:âî:¿J2*ýÂIFº*ÞOWÖ¾T¶.9Þß0×ô/:ûÁ3W¸È¶ûøó2À1,6+¶*ñÊ=,Öºè<¼û¸åä9 +QÖ¼Û*Öü¾-Z>µâÕ-¶¯.5°*:·ì+ÔÅD¸**ÔMý<;ÒÏ¿+8ÉôÁ´Ô-Ö8¾Ê±üÄIú¸*ÎÆóÔûU>Ú8*Gç/ô/:ûÁ +3J°ÅÌÍ0JÜúç¹å,-.¾ñ1FJBâXEÖÓ*¾Çñ8ñ7=*²µÀJÁ9äú÷ì/À.,/ìöÁI1J¿ôñ +åYUJå+B½À/ëÚ*Z°ÅÌ</ê5Jø1=Jã8GOI¼ú¶Î9FÐüëC5G¼F¾ïáO÷FÅ¿ÉHüJììØ@*éá3μÞ+ÄÄJèÔà0´ +?**ñÚáºM.*ܼÀ×2ÇÁ9¹ØQK,¿,µúßÅ-æNʳéÞD74V6Jø1=*ä8Gã÷¯Q4Å,,@.;Qì+:Qü¸>//¶¶ðüH +¾ÞóQ8ðàÈÀ-8Zµ»¶BB´ZU+*EéG*¾,*½Ô:·Ö=L=1JQNƲD·*;*¾ú7½¶ð*RYÈJ¿ÑèÁ,5-Æüß.Ö×MQ3Ì +Q-*ÞºHGÖ/8¾ÍÅí+*êØI¸æ?Þå8GOÖ¼ý**M9Ý-*æ*3ç@ó*M¹ò>EJLÞç8¶ÅÄT1¶ØMQ×+L8Ðô¾¹²Ò¸¾Ò +7¸ÈÀýöû-*Jë+õæRó*REò>/öÒù1*JØ-ìÏÏè+Æ´æS4*ýÝã*¾ýG¸ÈÝ<*.¾ýû·ÈíÐ>Á*øñ>+¾6¶Mê9ì* +Òè,õæ9ó:*üõåSøýûå¿*ïÌýýXØ5-ÊàýýíÊ=ô,+ÄÔýýÝ+Z4EY¶Þ¿;ÚÆ,E¯¾Ãȱ-AF*óXSZÂæ÷¾-A¶× +-*Þ*J·Ç-*ZJÅÔý+B45*ûP.X*ß8ìï,8ð¼ÅÄ<¾üFO1¾¯9ñäûÊúíéFOÍíÙýýݶüØñÉËïýýø¯ä+*AFîÞ +2XN¿ßíýAÁ׿58¾>òàI¹ßº*ÞYA¶×-+*àÔÒ-,õýå5ëïI*4ççýý±ä@Ôð8Aî:õ¹²=+2*ýùñ>û½@ÇýýÕú +ã½Qîüý÷HüѼ?°-èAÌ˽Gï¾-M¶;/ôý½ë°02X¶*L×øIéYÏK+*1XZ4**F¯/úýýûAãͶÀÕ½îI*¯<ýE0ä +0-Ôð81î:÷¹²>+òåS<*0î1ðù-ý·ö²½<¿ý°;æ¶7KDë3ÄGï¾-M¶;/òBÓGÓ³AF*?9îðÌý÷¾-A¶×-ôýý¼ +35ÉÀ=F¸´Äü-Þ5å½Ï´öêà/:êßG8ÞKðõæP,:*üõåSÈý1,ßú».ý˼*,R-ý½2øýýMÙ¸ýÞµÞýE8¶Z.AÐÞ +æÅ´´ê*ß/1B±=5ÜB,ºï,A¹¾üí@ÐDßý-**JÂTäýU*û³âXáýêà/:êßG8ÞKðõæR,æÍÑâ*ÜJüýý°BñC¼T +9÷·üýA¼TÒê¿ëÛï¾-M¶=/μÊë34æý7ìýÕûß+2G¶¿-/¶À/,Tá=,½Ï4.8ͼÃ+U·ÅÀÁêíÏË.J*úíÍÑòý +Qø·HGÏÈý»½ü²5RûÈí½øÍæñ=Ý+ýí¶Þ¿;ÚÆ,ɽ½æ<-.AF*·âºíÒÆ÷¾-A¶×->ù+õµFïJ?Æ°*×5-X*ß8 +ìÏî:ù¹²>+òåS¸*üç»2;ÍýýÚ¿*:BýEUFø?*îL+ì¶*öYM¶;/@¿¾=ÂŽXÞ¿ôÃÀ¾ÉáýÁ68ÚÞ¿,F;-øÍ; +Î5V4JB+¶Þ*Ñ*AúZ1õR¶ÆûGØÇ*.¾ýû·ÈõPMÜîÍÄ-@Ç+Wî8Ëá6û·ÈåòXRݸõEAGZÇ6æ,õæEä:*üõåS +¼+¼éK@çÍÑ.*,ÅIã+¾ýû·È¾üÝ.¾õ½ÈUûÃõ½ÈUûÃÄHOß¼õT¼²+¿æ,õæ:±¶³Èº×RºÌôêÏÑU*¾õê×á3H +ÏɱEOAíëÁ1ðäÑÍêÕB¾òF»øßöø?1V>OÍHË@*Þ6å/1FBÀ5SZ<0Òö¶´îÀ-ÎV>/ÀêF9¶î,Q¸*æI2Â5Ôî +òç-**Þ¾7O3»*Áà,*ÜÇ¿ÓÚáËVâú8ûN*ÅP3²ÄÞ/+òÊS8+*ËÇËÐEÒÁEõG7çÉïJÓÝÑÄH»@ïG²õTYÎQ½G +ûøÕ58¶Z.AÐÞØɽ¹ßêÞ/1Þ3°ÙYÓZHîÞ/Ìò*ν:2AV;´åçÑͼñ0ø*PÖíòçÁCCÖȹÚÇÃÊíÏÍ.îä>¿Ú5 +¾éGÑ>¹âã³ÈHOZøò/±KÍ*µQIG1øÉQË0@Ì˽Gï¾-M¶;/ÄH»æY02X¶*DKȼçW1<öÞ/P+È-0+8Öà/Qöó +1æ-îÃ;»Ý:êZÞTìï*8<½Å¼<¾=Ñ.>Áì6ØÖÕóµ7*Îé¯ÏÃÊ*ÈSËÐBæHÆí9ÎÕï<ùÄ.æÉÀC-´NQ°QBôÂ-8 +6ßÃ>ÖÝÎîêHJ¶ß+Rö¾?ðäÍ1F¶¿=G¶ß·1Vï¾=Úõ±Áæ-JÙ0»S´õâÞTìïP@°¼Å¼<¾5Ñ.Æ-ìÖOöòéù½Iº +Õ**ñüËùõîêWÄ˾àAQ3%% +d +596 85[1 0 0 1 0 0]sl 8 mask 0 0 di +/mask 6375 string uc +*ÞÌ**HºýÈöü-õûÝðùåÞõýÈíý?ݽ0¼ýÕúüY÷û±îù½ãõýÈíäÈ* +d +/sl 50660 string uc +äÕí´ê9+¾ÑLýJ¹øòÙIîò½¸îÏ1-ø¾Îµøò;É4;Jòç-2-¶à+1ÄîL4ʸâß¼7:Úî*6ÔïÝ4Cå-8ÚÞÇ8Ú2Â- +ÔBÌî**¾2ÃE»*ð70æ°äÍ6VãGìâLäõæQ,*ý9ÒÊéòáåØîß½IQôûÕí?*îâ¯ÕÌG¹´íìÆ9IDY/гñéÕÓÑÑ +ÒV*ÑQY¶É+7ýN1¸EòçáüÒ²µ°ôÄ-86ßË>Æó102X¶*°æÌH²11LòÞ/8>ÞÃë?UÚîÂ-=JéðÞU6ÖȹÚÇÃÊí +ÏÍ.îà>ûØLÃàÜÌÛø³4ÍÝûX+*YíºóåÒ±Ý8É4?Ø@ÑSøŸý8ò;»÷HHé,MÍÜMÍÜ.°úPäì¶Þ¿;ÚÆ,ñ8Üä +;-.AF*³Êêü=Æ÷¾-A¶Ð-AFÕVï.-è.ÆZ*Î5-Ôð8µ°;ñ¹ò=+ÚBO1ÓS=àÕS±0ËØåÝÝW*ÞãùܺCóéÂEÇP +ÎX/<³ñéõëSø÷íF¾JµH»0T¾1ÇòÁó7û4*¶Ï*µFîÞ2XN¿K:Î.í÷Í5¶Î³Ë:ÖðAû:Á-A¶Î*Q,+æAU6*Â/ +Ê*FZ¾S¾ÓÚá¿VNü8ûN*APçUÓT³AôäÃCÉòòÙÃ>Gü÷ß=Aì6ùìæÇU¿Nëúø»¹Èå*JÖÂÍÄ-@Ç+Wî8»á6AP +ç½ûø³Óòá½9ÑîîóÝ+*+¾ï:ÞÏ1FÀ+.*Þ*8SYáÞW>ÚK¾±=*ÚÍRJJÞ·YFÈ*99üµá½9ÉÜD¸<çSÈãÑì+åO +1V<-ØÞGDÅCXÊÐQÉ@ëPæÁó<@Ù×IÐ+,Þ+ôJ¾é±2=Jæµì¶á+*¼8ÚRº¶E2µJîú@쯾µå¼E¯ÛÁKí6GÇò> +Iá/ÚBOYÕXÛÄÌ-á?Ê°I1ý0*2*°Ç,+Fúè»4*K5Ü4ì5üÑPî5ùSB2-:å7F¶+19*H0î×K¶ÞßÁ1XèÀ/ÒÀ¹ +ö*8´Æ/HºBN¿,º1ÎZØ**Ê3öKGßÁݲ@º*+**¼÷ÞJ**¶Õ0Q-ÞÑIÍWBìÞÌEÄÌúÜÑ.+-*ÖñÞµ-Ù8Xû7*¶ +*ÛYöÒ8¾øKü9·ÒOÒ-5æFó*úÚæ/ÒUíÚÚ¯ÆÎÎÎØ**Ê3R +ÇÆÒá8<»¶J¾Þ79**õèî²*ööîÔ-Ý7ê÷îNLÑ.ýIP*FRCåÆ;M¹Ì+Ù/ÊRùHü0*F¾ïC¯Ú0ZF,1**¾*ÞQLÐ +4»×¯ùåëÀ+.*ÞÁOË9ÚJ>¿9I8R?æáÅQîÔÅ9IW߶*àÁ.V¶ÐóÈ/6Ê76:**±,æD-é-×71Fñ+*îV7ì,*÷ô +˸ʶ´A¸ðùÌÐãÊCÑØBOù¹øôíµ*Þ+úü»ÆA*ÝA¾ÓÁ+Üö.°JßÃÙßù/6Â*F¶¿S,ý»ùHAݽUBë¼ý8Wßî¾ +,öG3ÙÙÊ5,8*ß++BL5ÝòG5:*¼Ið++JßÓDÚ/*á»ùòݯÈJWòâõGOÉD³èY¯ÈäÕ²êÙí*¾-Ö»Ùî/*´½ð´* +áÔºù´6BVóÒ:DG¿¿2¾5AB6îÄ+MÖ»Ù/¹ù»NÈÞüGS¼ÙÑÂêßÕÅͽíºÐï +ÅÝHúÁÐúµQ×ã<îûÍðùIãÁ7öüQ·ÓÝöüQ·ÓÝöüQ·ÓÝöüQ·ÓÝöüQ·ÓÝöüQ·Óݶð°ô»»úøÙ1*2üøíÝîS¸ +õîçÕõýüùñÀºöX+*õËùµìÝÁ±õü+ÍÐQ9HúõñôûéQóëÕí?*îîÇáXG¹ôêÜ+øý¹ÈÌù³ëÕ±ÉåÝIëÝI½Õ*¾ +ÝI½ú²æÔ±ÝPÕÉYý»QçEá´û·òõø÷Éüúù/*úüöÙCµ¼7Ù°CëúõGOýùøñáÌÓÙÕYòíí@*îøóÑ9X÷ïåÕçíÞ +ÝY/<³ñéõ½GOQÙØ°ßÈËÉYYîåí@*îöéÉ98×ZãÓ3ëÚÙ½íSøÑÐ5õ³êÙùõÀüúá*:*:8,FÅ¿,¾Ù?¶Z/AY¾ +*ʵûÂ,,,̵à³3¶øíÛõ´Úµ¯çòòWëÝõ>ûOæÀñ8Ô»»¹*ýD*2*1Ó-+ÌáÞ÷@6BÚå¿E:ÆÀ7XɾµÝXùîÚÀK +Ýú½Qç?¸86ÕîêäÏ-óÕ+¾*2¹>2ÞÓ½ÜÑ*ÆÕèÑZÚèGWÐ8BÍÀ¿5Iîö:2¹1KAóèë5/4æË4Ò5.,¶*.CÚÐHR +PóR»<Õè+.**ó/.-+8¾´R5@ÆØWG*BKÆ/*F¸ñ×/á7ê×àý¹ÈHÎñÞµ-Ù8¼F>½7*8*ô5EÉ-RXÆõ-̲ÇðÜ +ú/ÅNR6íQYßÞ*JB¾=Ú6JJã0ÈAµÇ¿5Ì:F:,Z¶:.Þá³XÎúMMãS5:â9ÒÞ3:öð*F3¹Û¸ARÐF¾3ÀV*ÞÅ1µ +GöëÞÌ-üÝÑòõF-´5B³íÜßõé*î*¼Ùº¿D¾í2,:»*ì:7GãÓÄVïÁô+6Úö¿Ç*³î¹µÏñÆÓ/Ò5Ê.*íÏ*RD-ç +*ÓGR¿EJîÝ+NBûùõå48.K@*îÜ»Ñ0ê@ZåõüíS,´¯Èöõ²ìÚ¹Ý0*1ιµKÛ*òÜ*Ò80îß<Ä÷ú0ìÄÀÀ¾¿5I +2,Î0¸MØäÔðؾ¸¿-?2,ZÂ<¼×ùJ/ì*¿õ0JKòìÛIU¶;.Ç.¾ïä»øíBOÞôýÌ<25ÑÒ¼OáÌ»¹ôá*î*°å0º+ +¾êÍÚ7ÝÔ=åÌ7-0Eûò-PQZîà1À*Ú¿ê0àÌì4*ñÖ¾¸¿-?2,J8ŶS8:.>Ý*,QJ·´*Ǹ³äV´O¸;.·.*ìÌ7 +ٲݯÈJêýGOÍ/ØZÚ½MÝÄûF**¾B*Í1*ôãË>>Ö߯áÜÂ2ôè58H¾¿,¾÷¿:*µ*ùG19Xë¯=FZÞîL/À*æ±.û +È-Â>/A¾ÞOS¾7*°M2>2OÇ9;ß¿,F8*¼GøYÚÀKåø½QSIË´ºAZæõO·Q>¶=Ô3æ³,>ÎÏ-í³ºøÑý¹Èì2³ä¿ +/Á¼GEãÄûøS***A*î@0@5,8¹´êÑ51äÚDBø½QçIK@7C¯åÓ±õ6*öëÙWUÔV¸°Nýõ>ûÙÒÒMUäVø³ÏW¹ÙÆ +Ðñü7Ú´æÏUU?Öð¼ÝÑò¶ASP»ø±æÑç³ëÚÙ/*úµôâ·éôÊÇK×ÉùõGOå¼ïÎÐÇUÙXWTÙì@*î×»ùôGDéLåRé +ààÝíSLô³Ã×Ïл*¾çÂTæ³YéåýíSðóãYAÜ5õëä+*ùµïéãÕýÝÑÒêÕMAÌÙôô+*àWFø±Ðú´Q×å:îûÍðù +IãÁ7öüQ·ÓÝöüQ·ÓÝöüQ·ÓÝöüQ·ÓÝöüQ·ÓÝöüQ·Óݶð°ô»»úøÙ1*2üøíÝîS¸õîçÕõýüùñÀºöX+*õË +ùµìÝÁ±õü+ÍÐQ9HúõñôûéQóëÕí?*îîÇáXG¹ôêÜ+øý¹ÈÌù³ëÕ±ÉåÝIëÝI½Õ*¾ÝI½ú²æÔ±ÝPÕÉYý»Qç +Eá´û·òõø÷Éüúù/*úüöÙCµ¼7Ù°CëúõGOýùøñáÌÓÙÕYòíí@*îøóÑ9X÷ïåÕçíÞÝY/<³ñéõ½GOQÙØ°ßÈ +ËÉYYîåí@*îöéÉ98×ZãÓ3ëÚÙ½íSøÑÐ5õ³êÙùõÀüúÚ+J+*BÞÁÝöµDîà+¾ó±LJÝ5²FR¶Á-SD¾Ñý컵á +»5ÉPQíèü»QçYÀ@FõZëéÙ-öÇ-¾,*9Ûì:,9³ÞÇ4Ú,*TRÀÊп¹>Æì,ºùóØ1å?6øãý¹ÈXÏñÞ¶/å´8GRÍ +F**¾B*ÝÈÂøå¹4*÷ÈÒU1îÞ+2***X¾Þ.¾SCÈJï¿ÀÏÀº/ÞÏEÍWBìÞÌ?üÝÑò+G1¼EÖóîáK÷â+**Â+öì. +È5è1Fç<Òº·*Q8î..L*:X;ã5WERBTÆÕ*úöîÔ-Ý7ê·J¶ùûÌÐÝñ6ÖWÞÒ»ù0Ý=1**:0ÞWUö7+***,:ìÝ ++F·¾=:2*ÎÝäû´÷Lõ:¿ÏÀº/Þ»ùP7ÖWÞÌíûÝÑ.êà>ïíçÚ·õÌF**¾B*FÈPõ/T9²ÞÇ-F++éÛE´2ÏÉ,*æ +:¿ÏÀº/Þ¶ñHûµÊ**Cé,ìÌ +7ٲݯÈJêýGOÍ/ØZÚ½MÝÄG¹*¶**.*¯2EôãÔÇÀ,EL-XF¾ÆÐî,,6´ê½<0,æÓ1B-¾7JìÚÞÕMýÃUBòVýõ +>óÑçÄõ,0G»SPï,÷ýòï2ææ¿õDHûçýGOµÂDñÞÀKÝÌË°á¼ÛU*Þ¯åÄ»¸ïÞÊU-Mýõ>û=âÐ;=ôÚ¸ôÌ+*Ý» +Ù³ãÈOQÄòýÌнÉȸ²ãÊOÝ@سµ5S¸ìÝ¿é0G¸³Ó,Uìõ¹È8õ<ãÒQ½0»DÛÔ˹Ù*¾ýäÌFµëÌCC>¹·ýÜÑòê +A8TD·³éÓO³éë,*HÙµíÜM±Wò6³WVúùGOâÜÔBETØ1*BËR/Ûô³ûúýGOÜÜúóà¹ïÜëF*ζõ@¼Ûúøý¹ÈÈû +¸òà·éìH*JÚÃM½PȳÈ%%% +d +596 85[1 0 0 1 0 0]sl 8 mask 0 85 di +/mask 6225 string uc +*ÞÌ**HºýÈöü-õûÝðùåÞõýÈíý?ݽ0¼ýÕúüY÷û±îù½ãõýÈ7Ò<% +d +/sl 49468 string uc +äÕí´ê9+¾Îøý¯,Þ?ËóB/Ö´¼ûØøüQ÷ûÍðÏ.ºýÑÚèYºýÑÚèYºýÑÚèYºýÑÚèYºýÑÚèYºýÑÚèYF·CÉÜú¶ +ïë-*²º÷òYö>Û÷íÞÁAAìù´Ï7¹Ù**Ý´ûسêÛ˽ÞÑÂæ¯áÌ»¹Î<.íA*îݳÕ@Û¸óèÖK÷ý¹È¼E³éÒUýPÌF +6íA*îà¿é0ë÷òèÒÇóðé½íS¸ãÒQµüúùöð¾ºÝ*¾AIüÙóâÉQÑPÆQEý»QçùÐìÚB±ìàŵ7*¶îÖ9AìʸíÞî +ùõGOAUøïÝų-98µ6*öîݯAüùÖ²×<õôí¹ÈÜòñß¹3åôûE.Ý-*JàÂ5MPûR-XF*òÜ.RÊ+öíܯ=ܹöñÐ +ðÔôí¹ÈÜб޵-ÙìÛEúÜ-*RàýùÅåºùÏ0Ú¶*B¼3Ï2,îÛ¹Ù8ú@Zå?ßëõ¹È8;±Ý´ýØÜÛù<*Þ3ýíëEõäëµ +ÙãÃ;Ý87*öé+2¯àÂY¾Þ0,¾µÙüéÔYä2ýíSø½ÊûÔ󹸴ëZù8*Þ3ýÝ»Ùôà÷AùçòÙµP¼E¯éA8*üݾ¾/** +çÌ»¸ÚÂZüõýÌÐÕáÚAëÜϯá0×õ**ÌöýêׯAôÝB:8*Zõ±=´úà+Þý5:Ö**¶êÖMõOÙAñ¸ýõ>ûDä»åð/ûD +ôO*¾=ü9EôçÇùA0î1?2´àÅYF*ú½+,E**¼7Dí×»CYù½QçÁ¯ìÉôìåÓWÉéÔÕ**ÌöýèÑ9áX½×ÇäÛ¸óæ*Î +ûà+Þü5:ƽ*ú³éÓIíOÙA±2ôýÌÐÝðúÕWÜÍSÕÜ.*¶àÂ5IX7ݶèÌøïàįÕPºý¿-ÎãÅYI¾¾7**ÑüÊÕëÝÌ +YûÝÑÒÌJ9ÌÙÕñWÆÐÕXûý½ýöÇÕÔÓYÁíí½üæ¾51:íùݾ¾7.¾¼ôEõZæÌüíS¸MÏ?ùûéC³é⸳è5**Jú,* +´Þ13Ò¾Õ´ÛDòß½?É0ÄüíS¸QÄIA8ÊײéÉ*¾=ÍÖ/KÚ/+½7ùóçÅ9½¼68ûÝÑòíÌÄüú¶ðåÒÕ?ÀÉ3.ÅGÙ´é +ÌIÉÄTXãû¼QçïT¸FDòäÌK=èÔYõ4*¶ê×¹é¼ÛDñOâîXìÝõ>GùÃÁÄD@»Ø²;»¸Ù*¾ÕÄ7EôåT5GMW55ý¼Q +3×Õ7äPÈô+*P=TKÕ»WIý½QÇÕA½ÛöXÚµE1*>ìÛÃYAÝHÍSüÊк±ÈÈû¸òà·éìH*JÚÃM½PÈÕQ÷ûUÞàDïû +ÍðùIãõ½Èíý>ÝýS¼ýÑúüCîûÍðùIãã0Á»Y9Ýüô+*ÀÝüùAºÈìGºøóûÝýüÚ:9ÜU*Þ»ñüËùõîêÛ½ÞÑòçÁ +1ýûÚ»ýøS»ùó¹/*FºðÖUåÌGÙµ¾üýÌÐñ¼Wùóêð÷õQùõåí?*îõå9I7ØóêÕçóðé½íSøäÖW½8ûÛ¼üÐIý¼ ++*ÝIü´äËYáÔVPEý»QçýÜüúÖ±óôó5ûùÙ/*Ú¼ûòÁU¼ú÷³øEöõUUKëúøûYQççÔÔVÖ°ñðé5ú÷Ù/*Füøð +ÁM@V·³LEõôí¹ÈüÒòà»7ùôüÛÎI½1*î±+·:JßÄ÷ñ+¾O2àç1¶øíÛõ´Úµ¯çòòWëÝõ>ûOæÀñ8Ô»»¹*½1* +îVS*GÜûµâî/º*ÉBÂÙ:îëÕéHFÕZæAýíS¸ÃÌ-á?FÙ·òJû8*Jèöº++8¾´R5@ÆØWÛQÁóÍõ>:JÜX,? +F¸ñ×/á7ê×àý¹ÈHÎñÞµ-Ù8¼F>Ý-*2.ÙYJ¾-îNêóèÛJ?A1>10JKû¾=W°3ÒîáÁÙ8ú´Zå+ßëõ¹Èü̱޴ ++ÑôëEúÜ-*2.ÓYJ¾-ÆZøõíÍ?úô¼R³üûE,**2¾*Úõ¾4ìÙµè¾ë,ôúìýGOÞ@P3ýÌä˹õG*¾¾;÷,,F*/Ï +¹õüVKµÌWÍÉìÛARÛÂR.+BìÚQý7Ñ.êüíS,¯¯ÈèÕ²ê×µåÜ,*öÊ9:TXWöäêHE¾NÛD±Ü?ÇåPêAÀê×I×K¼ +WQÜÁSÞëׯÉôEP3ÂûÝÑò×J5ÄùµòéÖµ+*̱7/.Tö¿7Ù-¾ê2,/È<áô?8RÁ7ùðP4N¿7ÙJ/¼GøYÚÀKåø½ +QSIË´ºAZæé:ã7ì¿Ä8ºÕïçÍüíS¸MÒC1@6øóê>EôéE***î,*Ôî1åQÞ¯åÄ»¸ïÞÊU-Mýõ>û=âÐ;=ôÚ¸ô +Ì+*ݻٳãÈOQÄòýÌнÉȸ²ãÊOÝ@سµ5S¸ìÝ¿é0G¸³Ó.UXëÝõ>GìOÈSÍü6ø²¸WEõ´+*ýËG·ìØG±°Rô +ðü»Qç×YFÒ²ðèÔUÉèÔÙ/*º´íÜ»ÅäØæCèØÖöõ¹ÈÆ»W¯´Ò²9*ZDÏ4¸ëéø÷ý¹Èº»÷éÃõà»Ù·*JïìWú¸÷ +óýõ>?ùóæÃñÔÛ»*¾¶5ÅüÊ>ÝëSôCÒºýÑúüQ÷ûÍðùIãõ½Èíý>ݽ0ºýÑúüQ÷ûÍðùIãå»È*ñ+*¾ËíI*ôà +318Súø7ÑÒLÃÝý*ê=A¾Óõ9*Uû/%% +d +596 83[1 0 0 1 0 0]sl 8 mask 0 170 di + +QP +%%Trailer +%%Pages: 1 +%%DocumentFonts: +%%EOF diff --git a/doc/krfb/invitation_management.png b/doc/krfb/invitation_management.png new file mode 100644 index 00000000..e85298b5 Binary files /dev/null and b/doc/krfb/invitation_management.png differ diff --git a/doc/krfb/personal_invitation.eps b/doc/krfb/personal_invitation.eps new file mode 100644 index 00000000..133a34eb --- /dev/null +++ b/doc/krfb/personal_invitation.eps @@ -0,0 +1,708 @@ +%!PS-Adobe-1.0 +%%BoundingBox: 0 0 643 413 +%%BoundingBox: 0 0 595 842 +%%Creator: KDE 3.1.91 (CVS >= 20030907) +%%CreationDate: Sat Sep 20 11:30:58 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 9.305 2.791 ] [ 2.791 9.305 ] [ 2.791 2.791 ] [ 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 2.791 2.791 ] ] d +/pageinit { +35.4627 23.6418 translate +% 185*281mm (portrait) +0 795.224 translate 1.07463 -1.07463 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 407]ST +B P1 +NB +W BC +/mask 6723 string uc +î½*¾î1:E*¾9=/C8*¾Í8>Þ¿VK+¾Q14:JÈËÆ8=ç;Î*ý+Y6>*¾Û¿Àú-¶± +Î*½ô04Eù.+îáù1ÃõݺíýKÝýÁ¼ý1ûü9øûIòù½æõýÎíýKÝýÁü,Á,%% +d +/sl 53369 string uc +î½¼**ûÝ5*NRS9R3ü5¾Ì+ú°+ÓùÇY0º´ê½,*@ÕZêñ½<ÀPÁù.HîÕí/*òêêûùñåI7Nû5EÆ?JWýê¯ÙüB* +¾½ßõÕ½ýÍCæ±.77ÎùÇY0º´êá*Z´çõ9ûã*²2*ÌGÝIõÕÕ7ó³Æ¾ã?6ùúRQÞýTJ3ºÞY=**-ÜI÷ÓÇ*¾îKó +8*¾ËVÒÎá³*ñìíÝ´é6@ó¯Ö¾ÊËGì3PÝ3RÇG´0ÓŲùíI5³<*JÒRä¹/*,ïÔVÒÎã³*¹õÅ7?é6Dó±Ö*ü/Ý +½ýÙA.TL±»+*ÚööôñÙåHúÂ->UÔÊù-YD5*¶TøÈûù1ÎNÇN°â½+*àèÔ²ÔÎáµ*ãôíH²èM¾èØ3¶EÂàDû±* +*ÔñO+>¹¼ýü3UJöÓ1*ÞùÜì8ݺôæÍÑñÒõÜ2+Òø¹ýßM±+*QOYÝÖµMXÒæEäéB@åÎÔ´O±/W¼-0¼½ÑOÍö +SéHÞó:øñòéé½YHºõêGùòåËE½H¯4ÞÚùù¹ÜA.ÈôÍÃØ*Þíä´8ü¼úîÓáñã¿áXù¾.:T÷û×,Ë/*F?»ùÌPF +åVCY´WG½QÃØØóJ/à³ë½üíóP-1Q8Jüý½Ò*õTJÆ´»À+=+Q¿îì4ê½üë1IYÃMÀ¶ó*BÕùüûóÝ0ÃVÀ;±6Þ +ìûçøºøÀ͸Ô+Ì1ÏÝÌXÛúü÷íÝõóã±½ÜF÷éL=P+íÛ½ý»ýùõ18ÈH**íÙE¸ööõÕõ8Ç5Q¼Ùì5=Zùù-óC5* +¶Tø<,V?26B8ä8PÔÊG³³é¿4öýý+?LöI¹Àçâß/-.?ÚS¾U,¾¾*ûýIð7;Â.*JA¿îX:VAìýÙÜYÃ=À¶ã*È +üýµÆÎ?¸F8×.MäA¾Ðù-ÄÇò/IóV-Ê:,*-*ÕÀæ5A½¼ûûüøñ¹úóÝ=Y¼º¯È3ѾÉëÝÝï¶ûù90L5*úSßÞ˽ +½YI¼HYÛõîá/9»¾,Ò½ýËç*¾ÑÐå4BÌÁ¸æîEÀæÚÙËDUÙ8ÒJúÌíâ÷IüWâ÷Å1°--ú·ËÅíùݽ±RÚWÝüëJQ +1º±ûÕµìò̽¼ÆºB**G*Á-?ÎÝóùÚCöøö¾3*Ö×ÂÎ/ +.**=/,*¾E>îîùÍý?½üÓI5*ºQçâýY9ý»Yí»¶ìá;Ýê×R¾øõÅC7*¶TøÞZ8ê.0>¼MŵýãÝ°³é¿4Â4øÍ +Îý1XVºÖáXEßýýç×ØýÝõ<³ÆôýB;üAÃQÄ8ö¼ýï9Á·³Öý,ýÙÞ**ò*6âýýÑ,Võ½À?ÇÍ?ÓNT*+¸M.Ï+Zõ +ßHF,¾3.Ò÷ݼRÝUETDà.V?úGA/FFT-úRñ½è2Á5²°ýýÝAÌ÷>K.GUµ.àZ8µÙç¿Èê¾*Zðü¸CèºJTGÙ*Î +±.;-**ýûø**F4Lí·º´ììéÙ½íëÕõ̹ôïG=ÜL¿×èÉ>2;*õMJ+¾E>ÞÛùõµÓ¼»Ì1*¾Fø8üÀ¶-·ç³Ã@5/ +êüüAUä,*KÎ̹W:EÚ*çï7øQÜÆÓW1TÞýýýݽúÝÉðÅ-¾úáÇì9ùýÒµ¸ÒýIPèõ½ûYÏ-¿»´æåãÑM+0-46Ä +FM/¶PúÂ9ð½ôë¿1Ú<,ùý½ûýåøî*CXZ--TSñýøÀô12ÆúÐTDà.6°íÄú/?ÚD1Uõ½üTñ/DïìûýAѺÈ:Á/ +A²¿òùTõJO´J*Ô·ýÖ29îSé*FÁJSúöC9*¾û5 +üùô5+ÃØ¿1ýůÏ*óíY°D*îÒòóDõ±57EºÄ>QÇáèãèÔ-?2¿ñ¾.SD0ÝXJ¾.ÎK¿+XðäâýÇIÊýIPD÷½»LÑ +-K*Nû½Ð=-±*/01.¾OØÔý?¶K49ZÞ?;>¿ÜèÁSÄÞÃ5êÎ<ßüíÊRüèÄÈÄB+,5ýÙÇ¿UñZAýÍAÒ¸,/äù9ÝÕ +Ù.·N:²IÐÆAÀ¯+-¾ÄùÝW¶ðÃS³V-Ê:<òîã1:,òÅÀæýû@ëDøõîá¹öíÏÙ@ûùIÀµRîà<+¾¹>:æÝ93ÚÈ,Ì +3I¼UìÛÙº**¶8µî×ÅADRòÚ¿ÉüÔZϾÛíÝðáV*ÞSç-µíÒDOÈö<æZS°öÏÓW1T:,µ6*UÙA¶Ðéà*YèðýøË +XýQæèüÕÉæ¾J2¾èýYêýýÈXRßËýçÅXýñ1Øèº*ãýýç/;Â..22ÛýÍóß?¿öýý·Ã7í½ÚT1ØÏÐ;PÞ@»åÚZÞ +ßÓ·ÖÛýù@4Xΰ³Ä.ùýõ¿ïÁ.·N:>ûÛÞ1Á;±/ÞæûÍ<6÷àÒDÔ+T2+ùJNöüT2ÛFîÑWIíìû¹õÜû·éÑ¿É=ö +0/Ì2òG4ÆñëÅ.XO+ÙF½ÜÓòçQH**Wõ´öêÛÏ1:Zõæ»5½5VÞL+±Ü½SÄ×**ÏÐåW>*º5*Î,C°ÚæèÔ-?2¿E +0ÎZÉù=ÌB5*ZT1M +ÏTãTÓ*¾K*÷´é/52J¾È+ÎßWÂ.WT6îÇ.Oð@¯KWÂ2ÏW/U1À*Ò×KÓZ3ÈóÀ48ÆÞV*Kº²æ+;αÂ*Û:Àƾ* ++RöX9X+¾-,.æ:Sô8ε+1Aä;²ç,3¸³î¶¿8<>RÏTû8=â7¿æÎô,üµ»RDóÇå0²áúûã/êöà +ÒDÔ+Ì.;*¶@Â@5¶ìØ9ÚÐ,*¾EöVúðL+ßÜûC4ZÆèÓÙ**F5Lé.º¶íQØçÂ1íNYT>Z +é×µýüT4¶áñåW÷ôWï/*¶ºÏÂü8¼º¸öʼ¸ÑÏ8WMö*/6öü°5P,*ÐÄι**W°ÏÀÄÎ++.RÐU¾Æ.34Oí4ο9 +Xíͱھ3ÍÊTáHÞ÷:øKÞ¿5Y8»¸ó¼ûù÷éÉ=IòXä>Î4Ã5µ?¹ô=½õ0ÛOø0>°ïÍC鸺2ç¸åî+4JØûç½6öé +**5ìý·Ö¸,9HCÓÄ»*Zö¸2Û8îÞÁ9µìú·Å¼û¹÷ëÙéSA±4RÂٰܻëøËÞù¾ñJïKM0ËEæ1ÑXï*HK+NÛý÷ +*ý6*îÕûùýÉ@ÍÞEÐè×2¶GÂÀÈʱ*:¼äãÊ,ÚZôêÔNÉHö.*ÚÞ½ôÌïL/4?âóÊÛ½,ÍξT.ÜI¸ÈüH6*¶¾Áù +õACÊÚ¼µöÒ³9îº2SGWË8+¾æTA³4ÞèÓW·ôú4ïûÝ»ôâ¿=åìHöõè.BMìûù±8Çí±+*A2ÓøüûOôà0îGéÑÔ +Ó¾áKNû仹.*°XÝÒS*KÁ´O×ðÒ´íù?Äݽ½ôÆ7ÁÕí.*æXâÕMÇÖ±ºÜÔÓÙÕYTÏø@¼*AY´ãÖïÚº9õWVʺý +í,*Ü×äVàÖÏɵYXÎø@¾*9I´OÖîâÊÞóßMåîRм÷4ܸý2*öÔD?@-ÔêضàÌÜÅ=ÎÊÍZßÔúøë@C*¶´êÒGU +PVÖYÞé9+*ýó5ÖZáÊOÙ0ÖãÔSýÀ2ÂYÝMξûù1¾8¯ÆõYÎíÝJݽ¿¼ý,ûüýîû5òùAæõYÎíÝKܽ¿¼ý,ûü/ +øû5òùõÁõYÎËXξèÐ+*ü½ýïÊÌJ+¾?-*öýü/DK¿¸TÌR·DãçÒQQ,JN¿T+*Ï4*À,ÚÒTÔ´ë,@é>Íòæ:Mà +63¸.2Ó*¾JÑ*8ç;OÈPÛè³5,éã5ÕY¯*âUAXÏ*¼5AWÚ°çÀ³ÉÙÐãÈ?TÖHÃ4Û*ÐL*ܽ¿äRÎÈUBXÞ¶+5Ä. +ÝÀ;ãAXï*ÔéAYÜä*ÂAYïÈìéAZäÐOÑ0Ëî°?Zø2TͶÓÍLÃ5A¯ßÊG=èÔÛ,*Ï@W*¾MÉô6D±çÈ;ñݾëø³é +AXßܽ¿äRÎÊÈÑUÌP»°óÂ?W>SÏR7´LSÏNÇ4?0ÞQø³UAWÝ°×RÅ6?4+:Â8Ë*¾JÏ*RæÏLÄ.Ë3¶NÅ@·L/ +5AXæUÕXÛº?,ÆWÜà*ºÕWÛXëȳÉ?Ýó4ATÕFÏðòû5²Ä,óÌN·,ã3ÒQÏP*L,ÞѶ*+óS**¾4*R1îÑ°çèOÉ +@OÃÎ:LÀ434÷.¿L**Î2R9âÆ:L¿23°â1?<âÆ;PÍL·SÒ²ëسUÔV¾U6¾íÐÃÉÔWÖZßðÙXã°OÉÓVÐR>QÏÜ +½¿äRÎè4ÒSÍTÃT-îÎR»TÐä*2>SÑÀ-F?UÖ¶ëøóè=½âæ:MÃ6;°N2G¸;*ÞæºÞÏ*öMÇH×ÐÃ5Õ +Xì5ÕXÛºïðÃÉúØ?4*³÷ØÓ5Ô2*TãéÓõYÎC4Ò¼L/4?RÑ8.*¸:*NT>Sµ*ÞMÇDOé@YÚZÏPÅ0ùÀK=*2ç +:MÁ:Ë.*KÑ+ÑЯ*ÏÎüæÐQÎNÛÐÓÉïèÃÉÕ÷QàãéÔ2RÕÓéÔXÕ´çèóû5²Ä,óà¼3-PêÕZßJÖC**Ô,JÀ+¹´ +*ÌVDµêÜGá³ÈÓDãÈÒB*5ãÈ>U10¾PÓ<³0*TË+¶TÒPÏLÃè>TüTÕ¯äÔSéÄWÆ¿²ù@0JÞ¯5+¾çÚSÙäêùAæ +6/THǸ:*2TÒQÏJÛ5¾52îÒ°çè³UÓNÅâ:M=*LæÏ5+:Ä.Ó1Â+¾L=*ÂR;L/*ìÆÐOÊFð?ÉðÐÃéÔXØX.¾ +ãà?5ÕõYÎC4ÒÙ,³4>UÑTÇðÓPËDÓè>SÎä+2ÓSÑà-ºÒSÒPÃLÃ4@ÕãÉÕYÕBC°.4Ã*2LÅÌ.Jº¾J°ò**ä, +È*P*JÏæ*ÝâSÓUØ°÷àã½ïøó5BWÝ´CZ×¾çøü/DM¿è>ÞÃ+.*.-¶SÐRãÐÃéÔTNÇÏK?* +Òæ;K±*ðÆÎè¾C*NâRÏLÂP*ÀI+ÏÀ*YÒæ;NÉ@»VÒTËÀÓÈÝLóT?TÒX× +,Ôè*ÎÓTÑRÓ4*îÔXÓÐÃUBYÞ2@QÆ0*JÐC*ZÒ>î0+à.Þϸ¿:¾5-¶:*ìÇÒVÖ¶ç4@Ê8é³6Õ¯Ûܽ¿äRÎÔé +ÕYÝì6*ö¸5*ÌéA°åÐSÉ@Ê×R++M*¯+;,HTÒTÓX×ðã6YÑ@WD³èÒ +»ý,ËÏJÛ?SÔNÓDÓU@í³5AWÚ²ãDãUËLO4?TÒP?+.-öTØ´÷ðÓÉ>OòÆ;MK*èæÏNÌF¿D1¶Z*îæFÞRJ÷Þ3 +,Ú¸+ÕNÈ>VÓ´ß,0º»ý,ËÏJû=SÏN»ÐÓçÏ;J1@*6Ã3:MÂ6Ó+ÖLÂ6CD-:Â4/1Ê+FÏ+Ä.çÐPÑVóøü/DM¿ðTÏVÇTÓêý5*îüÙÑGÅ4ÒSÑ2?TÒ +à*úÒSÐRÛØÓéAí³U>OÅ8G°.6/+ö¸õýýÝ-ÆÎLàæÏZRÒ²ÉرØÀ,Ä0°C*ɲç>R¹>R+*Þ<À:×Gºµ,óÑT× +TÓTØýýG½õçÑUíXý½õÝDGRÏR×°ãTÏLÓT6¾Ùèó5BZÞ²Ë<ÊS@ÙýåYGÂ0KÀ²²J +:G4<23¶±*AâJ9OÀ>3ÐOS2>*B*Z/Xúø»/Ò·âÆ+3C/KÐCöô.èÃû4ÔÉÖ¶ýý¹õCùç-¶ïï÷üÝýãåTëð/Ê +1-ô5ÖZÞº7ñçÔSé´W×VÔÊÒTÏTË+¾¼KÎRO7KÏLÃ/¶SãìüQÝ?ÎMìÇ>SÒNËDÃ4ÊD*24R+:ÏRG;->Ã4>U +Ïð;æ:*J³/ÒÇÊJ5+Â@Fç¶ÞI>ææLA³T×ýýÅÁèßÇJÇ,çVüýÓùŹ:·4/ÉÖ³HÌÙ±ÖPçàÓÉõèOV×±äÀïÐÉ +6C¸ÒRÛ:RõÏâËKÙ²çÀãMOáùýºÓ¿.÷IÁ6CèN5×±¼VÔOÄ8G°ÒÇÝØÃT,*97¸ÒæÏN.È×±9 +8<**7XÜ*>Í<³0:°=æÉEÆÄ4ÜH¿àUÏXËTÓ.èÃZSñ0Ì°âýùü°õ.ð.6LÖHØöé½G8ÑãÉýLä8TáÙ¹Åä¸5ì +¸Òæ;¸KμM3Eâêè?éÐé5ÚúýûÛÁèñ·0Sø³;YèÜ»òÕýXK°âæDµø´O´C1í´ëïàéÒæ;MÅ6Ã3*Mç2>°A8* +*.ß*T·=21Ø*G=î¼Ý6ÞÌG³WD6¿+2P.6ZéÜZ>¾ßÉóÇ+í7ë/,Wø°åÌC3çZJÃTÓY*à;¿4**X.+ç,ÎßB@ +ËSJ¹/KîJBÞÓDFÞÀ·ÁéÛöõ.TOÇDÃÈ>±KÎXÈ3E¾8GèÓÔýýݶÒFÖ¼L1ý1*úúÑáVëð1ؼýýüºüýëÝGÃ. +7TA.è·:ßO÷ýûÒã¿Â³.ú»äB¾Î÷óÝ@ÞÕIãÃRÏ»ØýÑãÁÝ91æôý83TòR*¾?¸âýýF.èѱÔ/*¾Fê×Éã:00 +Ã*ׯ,¾LÖ¾ZQ=D¾2ƶ<Ä2²66:*DVÅ6*:=+Îã¿K<**ô>JF>æ5KZ¯+*æCTU*R¸:.Á,õèî¾-PJYõàÝ=R +Ýóè@UÙÒûýíÅÑØÀQ=TÕÄ·ÖýýÅ8ôÄÞïü½úéÇS½í½ÜÉÊóUYúý³ÅãN+ÁõýÝÜJOÎÜÁ:âýùñì0ŹñVûýûà +ïìñWN»ó½ÝøåÃÛòõýãíâÂRIÝû½½úØ=±´ìõýåË5ÎLÈ@¿OK*Ç2²/¾M¾ÉGùS*4¾³Ûå*Þ>PJ²Ó8DZ/*Z³ +ÜÄÞ»+*:ê¶Z***Y;-8ÄTð²+6ÞÌGõXHGøÛ*26JÚÐÂK¾5Kîéµ*ZÂG¹¸*Jæ=,GL¶×0Ó*å¹ïí3ÒùðãéAY +îüýõ⯻KÂTAYèºéýýQEÁÒó½Åý´ÔBÌиýí7UÙÔøýý-÷ö>ZÄÄ@ëZÍJ*Y2ÒíKø½½íûÐVï³>³ûIüO7KÚ +ʺêýEµCR3½ÒÒùýÎÕ+èå·ýýýöÕA¶0¸ñüý9.ÒæÐRøÇÒQÑúýÍÂJË¿:Ú81/ö3*,¯åü9*7¯,O-¶4*NUÉ9 +.ö5**¶äÜC***Mâ*NY±Þê×YíE°I¹1ä÷È<0+Ì.¿Å-¾*ôóHLB½PÎAéÎ4îºAüG¿øUÏXË°Óêýý¼ú>@¼BË +°O6V7,óß´·üÍÛäUÔú?ÁÙýE1QU±íýIZÄæZç4äK+àðäEùýýùÎýýÅõQ.2>HäÐÔýãíÒKÂù÷÷½½Ú¼¼2ïP +Hóû9IKDñºôýñËû2àÂ5ÝEýýÔ¼ì.°â:CÀòR<,»M+ÎÞ¶>+*ÎÞÇ3öóN7;ÎÎçSôÌG+**ʱÅ8**¶<-Ï*ØB +4*,NÞ¶YÎQT¿9-*î:W*àXÆJ;ÞçDÒ,ÚìÃÅQÎTÈÒSÓNG5,óÐÐöײ4?±¯ûýýÇOO5üI¼QXÙ´ìàåýííëß; +4Üýýý÷¹î¼Kô4ÁÃ˶KÎüý½ýæ9óJ,Äí÷>ôýöB»FïÌæüQÝÐ1;Tº6IPýýÅ÷PEÅûI³QDƶKáG°ýݸ9C¹4 +çMÅ8Oðý½X,OF*Æ·¸,üDD**²¯½+*-æÊÛ²**ZFLLù,*âï1Î:1¿°ôZ*¾ß/*ì±,»Ø0Ó*Õ¹ïï3>ØLãè>U +MμÈ4IÌPÓ<õ×ýý͵ͳâýÝõâ³íìËEõëÙ×ØCÙÐдì¼>>ýÍ7ÌËGñì1.¶ûýï-ðO8KüTêýð¹,çðÈýûÍû +K9Rèöï¿ÀüýY²Í³ÕûýâÈÀ²Ï=ÆLÝü½³GTð6ÀÂ8O¸>E¿0µ<µÓÕID+9üúøñV+*¶Þ×ù,*JBðâ*2*Ü8;ÏA +*¾=·ðÃ+**é±;Rº2**.LÌZ*8**G¹ôÅèÎFîJ0Xõàá=R¿OU.*Z5,óÒÖ*,?UÕÃèûýùFÃ>ÉûýµA3WÙ·óä +¿-å5ׯòçDÂQ÷¸çëùýõÊ29¸KÎüEìýýáAùÐÅMàÃôýöFÅÂïÖîüAYð2A»FÐTZýý½·ÄD½ûý²SQê:Ðâµ¼ý +½Ö7?·8CPÉD·ÈËJ/µ=¿ÆñÆAPîJ-:¾PêUéR2:DVILLîñ,1ÂXóÇ2>àP**¾ºQB*Ý**èúëM³R8¶:-Õ¹ïï +3Òüè?ÊÕYîÀJ»?BÁT/ñµö½ý½ÙE=Õ.ýýëéKÏ°0<¯æ´÷KÝ<À²1RÉD¼ÉæöýýAбX2ÒÍUñù½üÌ5ûïɾ×û +Yüå<²ú=Ïüýí?NÄ:1/Îóýïé?×äYý½YY7GÈÂ÷æùýÕ>ÁÃÍNT4ÒSÏúýÍÉJG¹îðÆBRÞàË==î´°N:Z+ʾä +M3ó@5.Zö°û4BG´T84Z¯×X,CJº<,Ãê7+2î÷30Þá¼<*:îR7È-XBßïXºº,óÒVÏ°ÓÈØýýÛ¸¿áà÷ùƵ¼ý +ͼTFP6CÛíùù³@ÈÖÊн¹ÔÙFHý͹ÝOH2ϼéýEÅÁñ±ýý½6Eú÷åýåµ²ñQ?Øß/ÜÄÌIøÌýIÌÐíàãììùý7 +ÍðKÐÛüý½¯¸Øú×ÑíýéåäN4ñæ;NÅ>OKÎI½/Ì5>ØTãTÓUKμÝ÷îßÍÉýýÝÙõÙTÆ@ºðçôúùõ0ºØúýë»ã +Qä:åýIùUAØ?µûû¯TÏ,Ô/¿¸7IîÖüݽõU81ÒÔý÷í@×ËíK/òùýíGýó×ñýÍ8;Ê<êúóýöó9AÚC»ýý½¯FK +ØòSâçùAæ6/4RþWì>4óè@QµÑXߺÓðUâãòß²TFËYù¸Þ»Ý<úÑ.4EìÉVØU¿N¿=DZû¸³PJ¹´7ó¼-ÙÓAÄ9KÒºKÔ¹+áóCÌ8 +ILO7¹×ýñ´äù·ó5AQURÄöB½:³èÒÇÑQíÝJ±>RõóUÕYܺÛÈâéR¿°ã4@VÖ¶A±ôFWð:°âE·¸>1ÊHÄ6·I¸2SàMÃÇ=¼OËJú´ýÝËÈý2á2Í>Nʶï +¹8³4óÇ>RÐܽ¿äRÎüèÓUÔXÛèNT?UÒú?¯â¼ßØÒÅ9µ>4ÔVÖXCTáÙ²×àã5D·,ïL +öÒ3áäÍZ*QMä6G¿,ëC-ÚÂØøLÐT;LñãÈ?Ö/ÊD»0/¿DìÒÀJ3¯îVàÚ+ÂÆSÐNÈ<·øü/DO¿ÐTÕX/ÁDTÒNËT+îÒR׸óèÔÀËG¹íÅ+Uü7H³ +Í2VÕW8Éݳã+/DÒÆÏMÆ>çNÈ>G°.SF¾N¸>FÞ;OÀòRÏLÃ8÷WWÙÙú-æÎLM.83Ýëý·Õ°Ð0*47B*/ÉÀ6âÐ +QíÝJ±>RêÓÉAXÛ,*¾ÕD¾S¾:*YµÖúÞ.Éë¸ä+ð79øGWÚHøðµS+ÈÂç=R,TÒ2*:Ó4îæ*0*ºÕƶõ÷;NçÑ* ++1/4ÙÌRE0ØLôN¸Þ/ +I³T>SÐNÃÔ·ZÞCû3ÑãÊIÊNGð,SÃéùAæ6/4±×Àó4ÔB*·óè¿+*Uñ*8±¼Ó»æ +ÖçMÆ=°²=öÇÎ:?òáãHÃ*ö²ýF°êÅ6ÐNXÇÐQæÖÓL?Í=FÔçA³Ððòï×âëøNËßÄûÚÕKµêÞ+ +.SAåůà¶ÍPÇMäBO,óèùAæ6/4¯×È/5ÔB*¸óTÔ:RÈ/ÉÓ·*Ý×½XP²úJèâMÓ4Q;ØLÁ.øïµ1Àã7G¶ãH±= +NÇ@ÇèRÖñÔH»ôÝ3AGðȲûºÙìOèÎûÝS;-1ò=æKQFÓëSô3â·ÐÅËØîÎM7¹öÎË+T>WZøü×æʲÇ=¼ÝÒH<ÒÇ@ýýY×áùÕ¾-/Zëúø³óÒJëÕYݾÃ* +FZ¾Ç¾¯*µÕA¼öÖ3ÅGèKðé´6ÕUÑBøðýõLɱPÑÕì¼êMÄDãë½Ñ½¸á0@ÔóÅøïüëÓIFVºÞNÐQKÎÌÅ:Íü× +MãÃÅ-T0×ûÀÂ>Sóð1ÅùÁ0>ÌïÓýϽ*ÇDôû5²È,/Ü8*>ú¸î*1@Þ¿×ÑøC¹ýûéùîÍïPº0ÊÔÄÜÚ÷°VÖÊ·= +̹ýFÆ9NNñTð´àÊïÑ:üÙ³úÍW¶ñ1Üâ/6QáÇèô:éòßçý9Ìã/ÊD¹Æ¸øB5ãDû»ÛFHä6EÅXõíç0ß÷NêùAæ +6/T±ÛÈ?U@VA0Z>:8Þ¯ÛD/UÙ½B߸ãè÷áQûY@SüîöÞÅÄZ¯±ÅUáÜø8C¾¸ëð¶JÃÐäXZú±û4DÑèXÕæN4ý +ðP>ÓêC´KáYüíCäÇA¶ø¶Ëì³,àÔù¯SÜÃÉDºøR»9Ñ»;ñ2ÕõYÎS4òî=æ4*7ø.Ø8>²2¾ÙøÒæ8IÍÂëñPÅù +íèéÐÀâÛêKRäö¯>X¹Q»QÊHÉ4Hãé+ÁXëGü×Qâ¶S³Éá¹Ð/ÌJðTé-,ó=ÏR¯=ÆÏÌíûäIöÂÑÃÒ/7ýÁæ;Æñ +µÀ+M>÷øü/DM¿4HõÖùÊïæóÑÓ±P6C¼Tøù¯ÓÊKó´÷»XÄÌMÍ¿ãÜÏÅ·=ñ½ÈYì:ðÌL7ûÜñÎKÊG¸¯´,¯4OT +ZË9òËEÌÈ»ý,ËÏJ7AWØ°Ë+*Wï*ZU:J6¾åØ0A¼ßìHËðÖ´û,µ@úØÐF7»àãLDá1<íÒXùè¹à¶Òíû¸çLäÌ +µÖïù×Sâ¸ÙÚHAüÄ:OÜÞ°åæÃÕ·÷IO@²*SðÊÀ¼õ3RÊÏìûåK¸6S¸¶Û½ê×PÊPàܽ¿äRÎM̱*ÏÍßÍ?ÏX5 +AÁ×åÁÄØÏI6÷²ïôÜûûï´ú79ù2ËHÅú<¹ìWñ;áé»ýHÐظMKÑÜÍQ5ñ-TWð÷ÄJ»AÕé?¸S-ñ½IÇã6JÇD/û +ì·,4ñ5°ïYù·»L»éýí=6B°çÖõYÎC4ÒùÈÓÉÔX׶ëÀÛ²ïØÃU6¾íÐÃÉÔVÞ6üóéå5íWº¹ñÁûJP¶ðúEX@< +I-æ;QôØÙìáßöǷܹéÙǹÑôÆÕA6ÉÑØÜúHA¶0Sð/ÔZå¶.èS´C-æÐPL߹;äú+QA¹íX¹¹ã¼íå.MIJ,ô +û5²Ä,ÃáÄ;M@V6*252¾ßÄ7EPV.¾1-äÉD»7½<óN¯4ÊäÇ:KÃFB¾èüê¯>4?Aä:HGµ,Óøå±ðòöÌÒJðL¸½ +À2³ùµèËIÌç>Xô2è°ÑÅAÉÖ5ËGÇPÓðï:4á±Å;OâºJÌI/4ÉǼP==1Ó³íÝJ±>R=0VBY±*66B¯ÝÀ+5@0Þ +½û,ôéÔRÃ0·OÉDSTÂR;N83ÑPÇ*ëçñƵD?èÐJ·ò:ðÆ4G°>QËD¶ú7K¶âæC>æ8Õ=ÆQÎV×ð׺÷¸³è=QÇF³à²4*B/µØPÉB¯4/2-BòF7ZËBWà²,Ø2èµ3WÌÛ¶ïï±ñÊÆÜÆKòè×õ +YÎC4ÒEUôVC±äÌ?-åè*L,ÖC±ãÊ?UäêV¾±=QXÛ¸ë¼çð×Z×È?UÔU×êÓUÕ²Ë*¶VØVçÀ?ÉÓVò4@V1X:8¾ +ZÛÀÓ0ZBÞ¯ÛÈ/5ÔTáÞ÷Ã@·è±ôæËDPæÒ´ñܽ¿äRÎìÉBYݺ/ñó5ú¸´*8ÊÕYÞ¾û,äÊYéÔWÙ¯ÖPËÐÏNÃ< +OÈÒS¾D¾ö*<³TÒJ*öÒTZ°îì+43ºÒOÁ274òÄ6@ÑÄ8LÔȳùü/DM¿ÈYÙP*¾Õ2*ýÓÉAXܶóàã»óèÃUÕXÛ +¶÷XÜÂGÉP7D¯ØTÒQÎH¿D*:ÍLÛ6¾-?¶=*1GÞIGDÒQÌE¶îRJÁFãTôêùAæ6/ȳëÐãÉÔXÙP*ÊAWÚ¶Ã-2X +Úä*L*ÚÕZåÎKÁô5?ö>L3F/ZRÎJÃ+¾Q;+µ0*NÃ4F¶+2?>D,î0*008îJNÒ¹è.èÑUÛÌOùü/DM¿ +¸±æÊK¹0Ë×±îê6*F¹´*ü6DµðæÛ±QËñÀ?U@VÖ²ß<ÖZGC*Öóè,à*Ä1Ì9Þ7;BYçÞ×ùü/DM¿ø¯ÝÄ+EôÉB +ZA,JÀ/¹Z*XVBZßÄGùô˽áÄÊÓTÑTÏðÓPÏ<ÓÈ>TÏF?SÒPÓ<ÓTÒÖ¾E+B5JÑPÏ4,B-B2¶Þ,O0Á-ÔU×´é +ܽ¿äRθÉÕXܶ÷D-*ÚL,NÕXï+ß*àÉÕZ*ý³6D³çÌóD/I¿,?4ÒJ**³0ÞK¿4óçÑQÎF?/FB¿Ö*ö²0:>*H +G·¾5XBPÎÌHÛT0WØõYÎC4Òýø/6ÕYݼó,ßà*êAYݸÃ/JYÞ¼÷ð?0Þ¿?Ñ´WE²ÝP·SÎN¿D?è>R?*@4ÓQÒ +à*â>SÓ+ï+Ñ-JT6?È4OÈÒR/*-,ß/³*K.TT>XãàSùü/DM¿ÖZݾ+-´0îÄ*êÕYáÎ/ø²ÞZÓL³4?SÄÈ>SÐP+8PJ6*ʸ9*¯.ÅÎÞ¸»áÔëØ.TÖÛö16QÆáÜÒO*äÉÐ +ÂG2Ò9ùOÊA¯ÞÀû¸°*:Vò=.5.2,îåÔSÉä7Ù±ßúÕVÖPÓDÃ4>VO4?RÔà+îÓRÒJ×45îÒRÏ4ÓÈÒSÏLK¿ÒB +QCÃè:¾ÊóUD¸èÐ:RQÊTÄ,.BêÞXÖ-¶D-.D¹ôAQîÂ-¾ÁVîò¶78DàW8>²âá1× +»ÞOEF5Ã.ùî<788.*¶24ôè³ôH<¾.JG±ÙBèê½ì¹²8¾î:J´>È·µð*Ã4?è6Þ++*SÁ*ã,A,Þ¶WÙÄW0,ó·Yº²WGøîÔYéJÒQ0 +7ºã,*»@E¶ü/å½îÐ1ð;æÙí¶í0CAô+ANÁ0å´ÞÆÐáÌ»0,Ë¿/ðR¿ö,¯ÃõËÀ21Ï-+ì²Þ̸=A¿¸¯ÜÄ+=ôÉ +BZ:êÇ+BZßÀ+5-¾Ý¼?ÙPW*¾Y-ÕËÙ²ìÒSñâÂã°³ÈÓSÒÂÒUA0¾WÇL³È?TÒà.ÞÒ¶+DÃTÓC*P³T?TÒNÏ4 +@21:ÑT7;.?ÖZèÞOÉ¿JO9͵Zá-ëâÌÛ<ÖÏ÷Î,JàÔ1¶**Ö-RÖ+@ôµL*ÖÝ*¿Æ+åGèÀ/Ѹ±1Þë¯Þ8JH.á +î·.ÛÖ±ýL¿,¹1èîN1058úH·2,ö/MÚÎ-ÍLA¹QÊÖ5ìñ*DX¾K,ò¶Ä1ï7R7À-ê×Ý6ÄE´¶Â:ÀGù÷6DzôÇ> +ÊÜ.*Ú·ÅÀÀEÍ·ç:Qб.K>¿GD**8¿ÂFøEôæOP×ÄÛõ°Q̶±Ý//äX6BôðöÒ3Oïßá¿36=ÖæÉÀF·K.1Úî* +7²óSMòÞãüJ1à5.¹×-11ÎMÄKDö¶¾:åàï¼µ´OÎP7E³ëL*îE³êÔ¯5î6-¾êØWá´WöѽUÕ8G¸ñèã-ø4*º +ܸðÎC=ÄVAðOéÕ4*î³,Þ´÷ØÓé¿**X1,Î2:0îÀ/,2À3.Ö²ï1C2ÒN:üP¾úâNê³1×ÌRJ°X³Ç-:2,ZóPA +ÞüV.*¾+¿¶*1ÓÊõëÝN¯1ÞçGå8*àæ*4¸æ0Ö±ýúFð.1èîN145ä7VÔë2ëÚZÂ5Fîß0LQÐÀ¸¿ü8Öà/BN¾8 +åö:ÜK8¾8>¶E6Aêî2K.¹ôïCð¸ÇES¶¿,µìï,4Xºä¯Á>QF:Ïç¾U:;=ÀÅP¸ÀÕÅZQä8¶íHÌ×·:Ñù+±Ûºâ +¶²JºZ½Z,±+ôèJñêY2ß+AFJ-Ìãï·×½Ö±YG2YJê¹ÝRÒBñJÝY*»OUõì󶵴OÎÜÊÖ°ÞÆ/M06=EÄÊB°àÄ +Û2*:@4ÞË·éPËDµëÚ·´îÚÃé´ÌÚ¶ÌWF³ìÆ/¹ã4ËÀÃT7¾Ï¸ÓTÓ=-¾ÓVÓLÓT6*Ö41B-¶O-Ê4@UãÒÓ-A- +¿,.=ôóè8¾õ0Ü;Äá8KZ¾.Æü.¼ÄÝÌÃ**B:21*4À7âô/±+1H¾×EÞXGU»ç8V**¶Ð-Ã/M.´G¹ï/XBà/8ö +ÊÉà0ÁѵÎÝ1Vï,6SÐJËDOTÒC +¾Ö*=³èÒRï*:èFJ>J6¾Íø²È>WßÞS-ï°:RS=ï7ß·ôÇPÄ¿ÊG-ÙÅNò¾/ÌÃÞÇ3¾/ïNÎùK¿ØPñ.ݼß-ZßË +8Ú+Lçëß/MÚÞ+0ËÀDÓõîó2˹ã3CÎÁZíû09à*ê÷¿ZÖÝøÅM,G/¶Â:;*@Çâ@Ò9O+VY>¶ð8ÕØðæ:ÌCâ+5 +¾NMLÓ+ÅJLJ:Y¹NHÖI3ÝòÝÍÎ0MÍVÁ1+ÌÈÊ==B5*O,=̶J·ß¿,2>º5;T1P..¶4ÕÎM8;¹OUÅ=ôXµ@OÎ +Ì6B°ßÆ+EP675´êÇ+ίàÂÃ+ö¯äÔSÙÄWE´8ÌEµéÞ·ù´ËÅá@ìD¸íàSññÎSùãUÓUÐú?TÒP×LÃ4?¸ÃÈÓ2 +*J?4JPÓT*R1BDB-2PÎ×V¯áPÌØ6.4ÛLBõÖ÷5ßïEÉÆßÌÛÓGÍO¸ëO-Ð*õ-@¶>,Å.TKCùëECR±+µ.T9. +XÌA*íÓ0*èË8FßGTÜËÒ5ĶÀ-TÁÄðµMʸ+7AÞ:-¸¾ðÅö:HÞV½Ì¾ûÓ½F2Ä=´¶Â:+*²±ÅLµÐ¾4ÎPõJ4X +Úá/>F*Â-X¶ä:QÐ-´83Ã/Æ,Ú9¿Â.°úL=G׹߿ÕMÄ»7Váºí@äÈ0Æ2íâ8ÊJ*¾ç,öúõ=L¶OÎFËÒçè¹±å +ܸ:ܾÖåÛÒðî*ÐNò0µ²óVFìõäÐ.è¿/-PÊÕZÝÀ¯ZÞ¼+ù?VÖ-3îß¾ûø?ÊײæöײçÔWá@W/¾Rñ@,*ÞÃ* +ö¶çÎ?EôÉAWNÉÒSµ,JNÃ4-¶:*-+°TÒ·¾5+ÎÒ¶7-AWÚ6.è×áÛÒà*/OÐÊî¶ï3<°åìEÓ/ÐJÅÈ+γ>HVî +R.83>ìì>ßÀR/Mµ.È1¼*ï5ÎGêQ78óÞ/¿D43Bç¼ß2Q¶JOWà8»-ÈAÎïà.ñËÛC.Ä»ø>IÉ02Ù=Ü9ÊJ*¾ +ËÄ>NÊ=L¶OÎ6ú×=MîZåܸ:ܾÖ1ùøPÇà**ßEظ@8õ¹±S,ãôÚËÙÔìE·¯*HXÙ·ëà³=åËÁñ´8ÚµíÞ»ñòô +çÁ1YI¼úúH½úøëùµYIÕõíܽø0ðEæ÷CÚµ:IÀ÷ìC³ë¾3¹:*>ÊBZ¿ÕÅ.î¾.X.*ý°@7H¼/÷OKÎ×Ò*VÍI· +XÕ+>ÍÓ6ìøß¿ÍãÚ8ã+òøÄñ÷ëßÇHNñGÕ´JÔ¹TOËÈ3<Â1ï4Wô>Ø*ÊãKÎ,VÖîM:´ìöè*È,*×**ÍÄËE´ê +Ò³ÑÄÖWé@,¾ÛSé´7F³ïP*úD¶çÎ7E0VAµãÈÒSÐNÃDOPÏ4123B.¶µ*Bè>TßÌ/DâXÎ7,034ò÷3ƶç@B< +Âù²9¹,*î¹5ÌÚ<¿¸°ÞÊ3U06C¯ÜÊÖ²àÄ/EP6<=ÄêB°¯*XÊC²ãÄGÑÔ7±518Ú³ïÜÓ+¶³ðÚÇÑ´XÙ·HËÛ¶ +ñÞÓ5QÌÅU1Ë×XÛXãàÔZ׸Óè?UA**X//öUÓZÓÀÃ4ÔT8éÒVÒº/=AÌÊùÔ1/ø¯×ã·¿4ZàX.*.5/:ßÀË+¾ +Z³*.ÊB¯¯*¶6زëÔÃ*B³êÒ·5+*îL*êÙ²ïÐÃ-¶µîÖ¿ùä7D³ÜVCZÚX×<³T.¾ÇLÃÈÒÀ*N³è>SÓÌ6¾ØT@ +7Ú²ïܽ¿äRÎÌËB±ßÔ3UPÊU=ÔêÖ²áÈ;ñçÄ;U07C°äúC°áÌCEPÊCY0WD¶éÚWEåßÇñÄìE·éæ÷µñØ×ñ´X +Ù¸üËÚµóâÇ5åÌÉEA8Ú±â¼÷ÐÚ²Û¸óÈÓ:*R?4ÞUß°/5?VÓZ·T×RãDÓÉC·,8ÜõYÎS4Ò½5Õ7F¶ïÔ¿ñëÞ· +ùä7F´ìRÙµëà*¾Ù¶*î@:ÞÛ·ñÄëEµïâK¹ööãéA,*úË**ºÓ*±*¼YI¼*÷ûáÅÍåÉåÌÚ´çÀûðܾûðã5BZÜ +ÚÕYÞºûøóUAýóUÖXÝÊßÙÅ*ì5ö13ÒZ¹ÏÓ/èË+ÙPV×Zè·²àÒ;¹´ÊØ°Ì6C²âÈ?¹ÄÊ9±ä6ׯåÈ?ñáÊ;M +P6D±éºØ¶èܯMAìFõÔXÙ¶êè»=åÝÏùPÌE¸ïâ÷¶ôâË=ÕÌÚ·X8F±ßº÷èó4Ü°óTÓU¯*8U?VÒ°ÓÀ³ÈéL/ê +عïè»ÉÜJ+¾ºFîÚ2ÌW*¾¼¶+×NKè2Ǹ-ÌONĺ·>+à+ñ/;N=3Ò.MôJ*ÎÖ¯ßÆ75**àì.J;¾1UäVدãÂ3 +58îäÆWñôËD¶èÒE³íØ¿ÙÔ,*ÚÃ*æµîÞÇ-AXV>¸éÄËØ°±ÎêT?TÒVϸ?4ÞYÏ°³è?WëÞÏ·íäOUÎ*û¼.øÊ +0à,´:¶³;EçÝ>ú:9UÚOÎH@RÝP6D¯èÂKEäÓ?U´VD±çη´æÎGÉäV×°8Ë×°åÊCÙôϽã5õéÎúÔëñæÌ7Ùô +6Ù±ïFÙ¶êèÃ=Å7G1AXE¸ïà¿MÅâË5A0ÞäÏ=Qì6¾ÉáÔÊÖZݸ߰ÖTÛL?0ÞQãT/éÒYíæ;¶õàãJÎHDÍ,Jà +°9²Ñ¯åX3ê-5Zê+ÖáZHÞ=Ëã8*K1K*¹ìÏ@VBÓÙ´PN+/*ó.ï,ÌGù>¸×¹àÂÛ5ìÁG¾À7Z¯½J.á3P¶21*X +Ê,-***¹³28FæÙÖ**ZÐîÀBÄ.ä@>ùA4>ÖáüÀ3T+ÌÞà/SÂÃFÆÅ+*êÊ0¿/êYÆKÛá*JÏÞÇ@>4Ò,Ú¾Äè¿ç +Å-*Rà¶Ò?³:ÅNðìAÂG0Å6ñ*ÎùÖJËÖ°áÈÃ+¶±âÄ7MPVC²ÌëD´æÌCUÄÊ:U@0ÞÇ7UTÙ¶éÎ5¹âÀ-IOÕêB +¯+/ÜÊزïØÃÙP̽=åËÚµïØÇMîâù:*:XF²B>Æ5DóçÆ+É/é?VÔºÔTÕTßT/UA*AÆ4æãJÎÌRJñTÉZICÇ +¯ÕGâø-5Zê+ιHH*XÍ.öVÙ;Ø+ÓÊõëúÉ>Sìȱ1ZÌZXÞ×±9´:¸Þ+CÒ¹ÚK¿=WR>R/¶+71âî09ÉB8³O7â +;ÇÚù/Kõú;áò²¿¸¸ìì¿EõËÛ¸Ü8Ú·ìÜ¿U1ÍùÙÕYܺõìÏ5Ñ6ÌòTñA9ܸêÓG²åÀ/EúÅXG2AÒÞãßMåYÝ +¾ü.ø¼ýú/ò1îI¿îí6¾ýùAîI¾*/06,î+ùóÑÕ8F·éºØZåÀ7ù/WBèPíÞ*À6.4:Âðñ*绵î7¯Ú55îê+Zå +ì¹Áì-+ÝJAÝÌÛö,4¼±åì´YæÞù6êÃ5¶ôê±X4E6ÌÜ<æí;,+¾û+;Ê5¾.áÃÅ8¿.ãÀûÆJQ4XæÞÂÞêÀSåTY +Øο-PR/¶,5AN;¶îJHèTJöJ¾5°RËR¾C>¶Ð/Ã,åÎÏâîL4À*ÈB8³O7â;Ç>OF÷Ã=DT϶L*7VÎH6ׯâÀ? +¹äËQÁ@êÕZßÆSñìÜ·-õ7Ù²åÚׯáÂ7MAõ¶ýü»û÷öãÁíëµ¹HûBî»ôÒÏ°ÜÂ/Cîµíà»-µ7ÚÎ,*Õ**âÛB* +3QìÃ*¶¶ëÐ/éãTÔTüT?VÜÜ¿E18ÊÉ¿J/-Ùã*BçâBÆ×ÝÖ¾Ï@ÉN°5èÃXÒÙÁ/;´ØùÀ,à×Y98¶î,ÌG¹æ>Ò +S»19ò¶J¼ÀûÀ<ÀTFT¼F¾=Ñ2DJå1À·NÎÑ*A²îJ.²µ1Ëä3¼G:î¾3T+ÌÞ࿸Ê<ÚÆÅ÷WæG2îÀ6ìÆZÏÞ/¾ +Úèà5æRG¶;/K*O6ÅØÐ0ðÆN458ûà;á**³I»VÎÜWC²ßÒKùôËÍé´ËC´çÚ»ñðسùPìÙ´ìFF´äÊKñQA¸õ¼ +ÚøúøùñõíñÙµÜù²éÇý¸»íÐW±äéA¶üËÚµòàÃ5µÌÆEµ0Þå×5A8Û¸ñâÃ+ö·óèÏEåXG¸ü7Ú²çÆ3áOÉàT0 +8G·÷Ð:Rµ³åñòØ×±ÝçÈÆßÌûÃ/6³ÏËG4Ó¼÷ðëN¿°ôÙµ°2T7ÂÚ³/6F¶¿:*ÓKM÷Ù;PÛ*D*ÓÂZVÞÞF³1Þ +ÇSæ,Ú×ÙüFß+AÂ/Ò>Úο=ÙÞ1,Aå5¾µô1ÊîJ+XÞ࿸6DÚÆÅ2*V.Ì÷ìêYÆß73@4Ú*ÆU,»+7Ó-Ñ91*Π+ïRUèJ8ÇâãYFð:T¾DÛ´@RPôVB°àÒW5òòìÚ¯-AÌÚµéFD´ëÔWù08Fí07ØÁÇéQôáKÏó7Hýýýû¹ýùó9AÌ +DõãD¶Ü½ùÌ;5N?ëÞG+*3Aì¿,ƶð,,ÞÙZ*:ATD2¾áO5P5A¯ïà,Ð:RõËèÀ,GÖ±AXYºí+;65BÏ7òK¶ð2Té,ôA:Ò¹Þ/¯+?/A²îâ@òÞ1:¼+ØñÀ38>Þ +/VÚ:ÃJÁ5÷2+¾Íß:Z¿û/CÚ;ßL¿MW¶L*AÎϸîLIÌ2áÏÔÒíF<ÇàÀMݶâKòTÁÁÃ4ÛÇÎå/TÖC5+îçà³=Q +ìG·ñ:Ûºòâ¿5Ã.ìÄ=ûF·íæPYÍ@61*ÞƾĿÁí9ü1*öü÷Ë9ÕG¹ñæ¼ÔG¹ìâÇùôÌÑUÅXH¸òæßñòæ×ÁåXG +¹õÆG¹ó,JM,RÛºõà*ÚÛ¸íä·=ÅìHTA9Û6.D-4ÎéEÌÅ,¾ðCÚJ@,JçYÀ·±+ZûÓ5FÙ¾-,Û´ºÏóê/Gµ¿áÝ +çé:¶XÎHÌÚµïàÏ5ÕÌáá1Yܺúöëñ÷òçÙAíÛ¹÷ÚÛ¸öôÃéÈÛ¼à¸/¾+*OáZðìðûÕÀøüàÍÙôW¸ñÂöZÞ·öô +/C¶¼ùöóéÅÍݼ¼9I¼üüûÑå8ÙÙQYH»ûøÃ/Ú2**¶2:¯¿È¶ç<¼¿=1Z406·EßZêÙJ3¹ôð@ì7ûPÎÌWC³ãÖ +SéôËÕ-õ7Ù¶îÞ·ñðâÏ=Å8F¶ïúE´ëÞ¯ùØ»¼ßSN3Ò-*¾*5ÈÈÁÙY/óý÷I½8ºõåÇGL½øâ»ù0ÌGAÕ8G¹òè +ÓUåçÓ±ÕÌFµîâS¶ëäÇ-åWƾÍÑÅ8Ü·÷æßÁðòËñýýÅJÖåKÎü6D±çÊWé@̽ñÄ7E´ìÖ³ÑìÞ¿ùôËÚï*íÔW +GØîùµÒïÀJå*ÎÏÐË¿ÍY.óýóMͼÙõèÅÛß»ñ̳ù@ìÚ**<µ8ÛS*9AXÚ´êÚ»Ù@T¾áÇMÕ8G·ï00*ÛõYÎC4 +ÒU±äËØ´ëêÃÑíÖÇ-1ÌÙ2*ý08Ú¶ñæÃ5åçÏ-QíX÷ûí¿0Õ.è+*¶Î3åäÛõIRRå½G¸ïÞWý?Rô·5øâòêÓUÕ +ÌG¹M*¼8G·ñÞ¿-Q̽EµìÛ¸÷âÓÁðîÏÁQìùAæ6/èÕ¯Ñô7F¸ò⿵+ÍHXÚ¶ðàÃ-µÌÊ=A2èçË=Å9X÷ýÏ¿, +õ.D.*K>ÐUC÷5/óøÏIýGÚZÅ-ãJ»+1ÌXÛ¹õê×Á1íZ>Ò5>óñäÓ¹AÍG¸òà.âG¹íÝJ±>RÕµìÛºýöóÉå÷ë +éµ9ݺúö÷»÷òóéAYܼì9Ý»ú80XYIò¸Dα*4ß×ÚÎÇéY.óüí±ÍXÚµè¶ÛâÀôöïñAÎJ9BîJ¿+ý76æ+ø5Ö +ÍI½.-ø½ú,ø5æÍK½íÝJ±>RÍ´WÙ¶ïâ»ñäÝÇ-åWÙµîÜÃ+¶¶ìÚ»5AìÙµ48ßßòÙÕ¸XÎ8**+NÌËÛºº;Òýõ +í¼¸ñÞ½õó<;äñ0ÌÚ·óºÛ¸ôì×±Õ8GOQÌÚ¸òêGR*CåìùAæ>/èÑË-µÌE¸îâǶòàGûÚµíàÓ-õWFQµÌF¶ñ +ä7VÊì1õ>+¿0+Z+IÐûãÊÛýTRí½üøõñÏEëäÙ³5µXG¸õî·ºôðãÁÅXܸüXÛºñèËÑÅÌßEåýýõIÌ÷3ÒIÙ´ +WE´éØÃ=íÚþ¸¶ìÜ¿518F¶>ÌÚ¶¯Ï4ÌKáôI5ÈÁJÁ*>¿>.õÀ¼¼4èûÝAíÜùÔçÎײéÖ³5¸ÎñèÓ¹õìÛ2R» +µ8Û¶õæÃ.2ýýÝý¶äQÎBìE¶íä/C¸ºïæϹÅÌÛ¸BÍG¹óîÃ+¶¹õê¿E÷ؼßJQD,7,î5òâáùùýýûûû÷òÑMó +ÔWEåPËÙµíÜÏUåïßáµYI¼ýô÷ºõôïáµÍI»íÝJ±>RíÄìF¹ôðïéÅóãÁAíÛ¹ùX*¾éÑÅYÝ»ñìÈQ÷ÓXÇÇJÁ +*NþKÎÌüù÷âÑÜE1̽ùä7F¶ïàË=òì/Kö¾*/üñÕÍÞ½NîݽíÝJ±>RÍ@7Ú´ñÞÛUåçǹQ8Úµòâ··ïæË=Q +ìF¸ÌìÚ·òðÔí½=.ÇÊJG-µýýíY½H»1ü=Û¶ìØWÁäËGÁ0ëC³éØ·´ñæ×UA9Û¹öÌHºøè»ý,ËÏJ»E´ìÜ¿ùô +ËF;Åì*¾ÍE18Ú¶ðÞ¿ÁðæË-AX2>ïIRL°,?EJ0¿ø÷ðçáÌÇÌG¹¼ìE³çÊC¹@WBÒQÑÄWÙµðæÓÁöêß¹1íùA +æ6/4êôø¸ïà»EAìF·ðXÛÖ¾¶RMQìÚ·Ï*DYYúýçOÆÊJ/4ÔKÎÌHû÷ÔYàÑQÍåUÅXFµéÔWñçÐWÑPWØ´êº +F¶óèÛ±µYÜõYÎC4ÒåÙQíHºøèÏEó²óìßÉA2çñß¹õXÛ¸öòS¾ÀóùÅPJ°,?´@1¿ð÷òãµì2YÝ**ÝQ9H¸ïÜ +·ñPÜ¿5×óïâÇ=ÕÌGºö*ÝõYÎC4Òõá1Îݽü,ìñøòïÁ1YH¼ùFI¼ýø÷-2:IÝåYÝ¿¿ïå9@×¾¸*¿ÈýýI½úø +̼ì¹÷ü+FÆÎ9>RîI¼÷ìÓñòæßUÕXÛ¹ôÒÛ¸øîëÉõû5²Ä,ÓìÜ»EÕìÛ+*UµXF¶+*HÌF¸ôîß¹å8Ñ5µYZûë +±ãPÎÌSýýýóÉIIÜÁãÔËÚ¸õðçñøðãÁõÌFµë:E´êÚ»5ݾíÚ³ñäëùAæ6/èÙ¯-õWÛ¸óæߺõì×ÉA8ÞéÃ5A +8Û¸óì·ºñØ/ÊFýôP×3ÒµøýݽúïéÁõXÑOáPXÛºùúH»øôçÁÅ8G1õëصìÞ·51ÛÃá08ÙõYÎC4ÒYÙäWÙ´ì +Ú¿ñïä×¹19HºõæG¸ð,KÛÚ¸ñÜG5Çù¼ßVR°+ó+HûýÝIüöñûøÑéÒW-ÕXHÈQíÜRRÝQíÛ·îÚWáÄÙ³áÔËÙ´ +êܽ¿PSÎ8ìÙ·íÞ¿E18ÏUµYêÏùéåÍݽúòãñøðÓ=µëéøúOìDÇ3Ò>Î?.èíÉQýºç·ôì÷»ý,üùAZJ¾üÍÞ¾ +ýøë¹ÅÌÄ=A8ÚµðL¶Ý½¿äRÎÌìÚ¶ðæËUQÌÇUõ8ÜBRëåíI¾*ýø¼õàË»íÝè?Ç3Ò½2õý½üôåÑõãÇÔÙõÍ +JÀ,ËÞ¿,+0,¾¾ß*ÌÍܹòæË-õ7¯5ÅìùAæ6/è߯éÄëE´ëÒ·´êÜ»EQXÚ·Ü8Û¸õðãÑQÍí¹Q8ëùüUôÆÊJû +ÄÅýýíQ½H»µÜÒÚ¸÷ôïÙåùóáQYI¼ùô·½ûöãÑõÌÛ·TXÙ²ãÖSùü/DM¿<µêÞ/¿¸µêÖ¯ñÔWEµ+*üëÚ¸óîã +ÑA9éÁåôûý2SNPÎüNéýýóÉA½H91øËÚ·òêÃGJ¼ûöïáA<4øÃĸ¼øêË-õ7Ù³íÝJÑ>RÝ@XÙµêÞ·ñ@:Ø6è +ÙË=Q8G¹ôìïºýöÜõÍ9Áâ.TàJH,óòåÁ´RëEµìFF·òèçÙÅíHêåYÝ»üàJÛݼùðç¹QÌÙõYÎC4>ÔEÅXG¹¯ +*ï*Ì8Û·ñèÓ=µÌÕMÕÌÛºùø3»íõ1ÕJ°,?·²9¿¸÷ð+á±õÌG¹Ì8G¹öðçñ1Î-6¶ÎÞ¾+186,¾,+,âõìùA +æ6/DææéÛ±ÕÌG¹óèǸõ0:+ÛØRµQYH¿¿ïÉ1@×¾¸*¾ÖýýI½ú÷HüY¾+û÷é19ÖEÏZ÷ôëñAôç-<>B:Þ¿*ù +;¼úî»ý,ËÏJûÙµîÜ»ñ0ìEðä7Ú¾*ùÔëEµ;*üËÙ´ìÜ7²FÝFÅ?E¿¸LõýùÍIH»õ8õH½úòã¹Å8Â5ÉîìØ¿M +µÍI½úFݺùîã¹ÕXF+åëùAæN/Dü×6D2Z4Ø6ÞÛ³éÄï±ú×GãPÎ,Çý +H»óæÓEA²ùû5²Ä,Wö.ïàKá*JÚ¶¾C¾B+11ÌÙÎáïQ@>×¾ø+Ðüýõý¼ú÷ìCM»÷ì×±ÕÌÍ51ÌE´ëÚÇñóôëá +AÍܹô¶Ú¶îܳ-1ìùAæ6/Èï×¹Õ8ܹô86*Þ5-2Äòöê×±19ÚÕñS¼Ûµ<=Rå*ëýýûé½IY;EòÅ9ܹß1+3üX +H¼*+4.öÍóùåÍÜö/¹µ8H¹íÝJ±>æöL°å8ÛB¾·¾ïá=1ÎîÜÐÝíÓÉâ.èSúùýI½øìã¹á*ïßUÅÌÚ·ÌXG¹ùü ++6¶Î-.ÖÍI¼ûöë-òTA*ÛõYÎC4>Á5AXF¶îâÛB*4AìÙB¾-=¾±ëñå3N°,?ER0¿ø÷íåAìA8ÚµÜ7Ùµðä×Ñ +ÅÍõéÅYܺôðßñõèÇùôëÙ¶ð.F¶íÝJ±:R%% +d +643 83[1 0 0 1 0 0]sl 8 mask 0 83 di +/mask 6723 string uc +*Þä**¼ºýÎöüÉõûIòù½æõýÎíýKÝýÁ¼ý1ûü9øûIòù½æõýÎíýKEPR*% +d +/sl 53369 string uc +äÕí´ê9+¾Ñ,JÌF·ïäÇ-ïà*ÂÚ¶±+M*¼XÚ¶ïâ×¹éÜïµB,¾Ú*ÞÃ:ûýIý÷ðç¹àùäÃùôWÚ¸89I½üôïÑAÍÝ +Éõ8ܸõä¿-ëàOUýXöGHîKP,ó.JûýíI +üöó·óÆûðßÉõX*¾ÖMõà¯Q=æF¹ñTAJÚõYÎC4ÒáÙµ9Iºúòïá÷öçÙAíH»+*²íH»ï*ÌÌSëÒÕù½8×»7Æä¿ +².è9âøýý¼øðå¹ç7/,òÅÍܺ-2;3Ï*Ï3ZYÓLòÀÞÙõû5²Ä,Ãúîß¹QíË+Ö¹öðß¹Z*8íTêÏÉÑQYIùåÜöé +µS6âKÎÜßØýý÷ÑAÝܶ´BÖçêßÁåXö+Û¹QY3ÀÞ¹A6*ìË+2¼öܽ¿äRÎÚ8G·ñæÏEA*:8:4îâ*ÚÜÔÔQAåÌ +»ùRRûYºîÑèØâÎμÀÝýýóÁAYÜTäQìÚ¶ïTROµXG¸ñä +ÛB¾îLBõÞ*FÛ¹=¹Ô³HG¸ÅÌû¸öìçͽíëõíYÚíÖÒX*+Q/èÉÆûý½¼öñãÙàòÔ·5ÅOñæÏ5Ä*ò8F*ÒU-¶Î5 +íÝJ±>RAÖ9J½+ûûñõà*âI¾³*ì9J¾.ùKV4ØUÕXû¸õïáÍõïçÙÕ½Ýýü×WîÊÓDF>R1ÁøýýøßÍIíVåâåYæ ++ú¹¾**Z6¾/òåYÝ-ÓZ*ù+êõû5²Ä,ÓöîÛÁ19ÜB*ºõìGº÷0+À*è*FÛ¸ô.ÈNèظ1Ý»úôìÑÑ9íïÉÝýÜý÷ +Ù+5ÔËÌ3ÒÝ×üý½úëåÁAÙ·ÌF.E*¹Ï*Ó2=*1NZ2îû5²Ä,ó÷äëM19G»óè*¶ÛºóðóMÖYÂïJ<Ö5.óó2àÒØ +¹úõçûøõñßѵ9I¼õýýýú×ûü.-8F;Rí±ý½ýöåÕ1Y´AG±:ôêõÀ¸;+±*ÀXÜ-ÓÞòôÏɵÌýýí½Fñ=RKåX +â=×UõX²±6ÞÚ;XêTôO½ï+ìXÛ¸óìË=åÌñáƱèëã×¹õñ×Õ9YÝ»ýóÛýüýéÅ8ºïÎ-,?½ü1¿ðõóÝ11VñÜ3 +Á¶.RA@;4JæÓ5Ñ»:,.9Û¸íÝJ±>RÚÅXH¸ùà4¿ÜöLÕ1YâÖ/¹ç=éÅÉ@ʱI·óî;ºöêË+öºøê;FÇøùöHüù +öòíÝÍýÜî½Iê3Ûå0V<É-,óúçŵÍܵÊåFÜ·ïîß¹å8H·õXç¿å¹AÍG»ôîÓÑùèãMµ9ÛõYÎC4Òý-æíݽýú ++6.î+ïGòEÙîÖ»»öã¾CI´WôKçµYÎJö¾-úÅYݽ15MõãáYQYÛ»ööGºûòû±ýý½½9ݸY¸>LûõíùÙ1Ýüúì +,QC¹ôÞßEÅB°B´¶î**Z/-*úü/DM¿ø¼õúßá1ÍI»ÌYݺ÷ôDzèÔé@ëÚ¹æÈ/ñóÈEåÌù°O½óºH¼ùöãáQ9I +Õ主òÛÛÅIíçÙµíH»ûø3ùìçïýýÍÝøæÜÙýüùéѵýÜå8æSØÀ/çç-ùÀ5æH»ùÀ6âHºß0,YÝB*ñAYݹíÝJ +±>RYA9Ûºôð×ÁåÛãÙµðÞÄVSùâ»CYÔÛú´ê̵íà¸+ñãRÍñ-ÕXÛ¹óî×ñF×ùIýF´ïà»û÷ûÛå1YIZ/¼A·ã +õÑÅÀøúü÷Õ9íÜúçìXÐY,5À±19à±õÌÛ¹M0á0ÐÌÛæ/ÂÅXÜ·÷ܽ¿äRÎÌíG»óöãÑõÌAÒR¯àÈI=Mô»MIHê +÷ïë×ÛµïÇ5¹ÄSìHÝRòß¼óìÛUåWñýÝI»îÄ;ùâáÛååI½ü÷X¼´íùÕì9ÝQåÍݼ÷óëÑõÈÓOà0ç+ZQî,¾÷UÑ4?Û¹õîß¹A2Z,¾êß +¹A¸;À¿ð×ÑAE¿4**86ÞÛ*ÆîÉÕAKL0É·0AVO+MÝ×U·JV*»Ã,¼¯Å+6Ö±íø.?*à°²¾TÕä¿-µéQ18Bß×; +2ôê9óJ¼-ZÕX±õ¯8Î2Z0î,,âå»éEÌH=RYRZâÞ*GóEå7ÀêåZWåÇI¹ç½SU¼Ú×ïèÜ·°äÁµ1å7ÖáNÆ8Û¹ +4÷ä@ôûÅ¿øýýñÑõGõèÎ8µîóøûýÝýÜÝÕÍüµûùñÅõôéYí6ôèÕUÛçÃü4@>RZKQ¶ÎÞÀ-54²æ1<>æÎJÀ,9 +L¿./øý½+,»5,ùï*CÌûÖÀÁ×SúÚ±ê?EAOYFL+õ×¾Sòæ×±AßØ@µTKʾ4ÂѸóF2Q18Bß×;2ôê9óJ¸-:N +çÛæåÑ,OêWïÛHð²C¿ø¹:ÉÔÔFÓÞ·ìëHº7»4ÓÈÖÉëF÷ðçÇKÅôÃKA8GØ°íØG÷êÀ±0ÒçÔ²YÜØû÷ûßùXì÷ +½½Hõä½2C¸Ï»ÍÕõíݼô8G¼üóåÉYÍÚYÔ4?èÑ8ãÏÑ÷ðßÉAYÜB*Ï1íÃ*î¹øìë¹µÍýF/è»Ñà¾+Óíê×Ù¿; +,J292;ÖHÁ8SJ,ã×1ݯô3@FL+·ÁSO*<÷ÇGRVï*4ÃXôêõX·ïÔã.ÅÄJ*Z½Ô-Ç?¾FøùÄJ¯óSMòÞG2îHL +T×ÑB¹×ÑÐFÂô?RýõWÞ˱EíêÝ<.ÕËOØ×¹¹à¹ñ°ì6øðßÌE²ãÏ;åôºØéHWÛ·íë7ÁôºCÙÔËÜö÷ø»½úôë +EíØÌEYÔìÛ̹Õñ½íÕõÝIIºúõÏùáRÅH/çìÛ»¼íܼúôãéµ9î5-Îùò÷ÑÅ9ÜB*ÐõÍÜú.ØÞçS+Æç<¾Tê×E +.*JÝH>¶Z*é=RÊK<·Â.à+.äZÅR*Aòïßßä¾OEJÃÙó;,ØJ·ôêÉE¶Î-1Êîæ2.¹ôå¸:×,Æ3ß-G5*>ùV8Í +Éû=DÞ×ùÞä¹ãPê¹Á5É-ÌP=RYõêE¹7½XóêµUÍFÓæØ°C¹êàY±ìÉBíæHøðìѯÉÜêعÄGÙñòÕÇÕôů½ÄV +¸ðêáûöïûýõ½ÍÛWõÕëG»+A°»êÓåõÝݼúõÅùàPɸNÅ8H»,YÜ4*ÅõXHºøìã±Åò×ÙA4Jð×ÉÎJé*ú,*àà +-Þ¾G¹É,*Ëã1RZ»+BÔZDGòÖ=ÎK.½4Ǻ::ÌGù+3ÇÉÒ¿Û°õ/ïÜ4>M.¾¿KP¶Î¶îÀ387¿ZåüT1°M*B:P5 +º6.:9êÇ6Â,óMθG¹ðMÖUI2ü-.è÷ßñå8F¹ûV¸Ó²U9õ¼F°Ò8³êåÞÓõìêÖ1ÔW¹´éƵÉôÉ·ÁPìD´Þ/ûK +àÕ¿ÅXº¹TýQÒTïðýýÅíÛå7RÎKÇ4W¸Í¼½ÙµÍ½¼ïXóܸõôïÙµ9î5-¾ûöïÑÕYZ¾ôÑÅÍH½ø86JÜ,/âÅJ7 +´N*ÆFJ¿?4²2*°ÀÁ»²PQ,4Ö14ö7ß×AY¾G¹0ÔF¹ã8L.òÈÚê×ñã°´îÂ387ß×8Nµ*,,÷E,?RöÔ2.¿°ôê +½Uß8FÃ/¶Î2NB8*6Ì+ƾ,=?RY/JäÉ*T¯åXüÄH2O5*âXB +ÖH¿¸óçÍ@îÇÛ9ÞåRÞÍË=H±6ôê±BÅã³ü2;Î2ñÖ¼ëÅà×+îñÀ387¿ZåüR1ÐE*3âö7¿à²ÐPUíû¾JÓ÷¼äÖ +åÛ²·Þç²,ó*ù÷é1ZݼúúܲñôËÚ×?YÍÛF¹îÂÕ¸Gêõ¼.çðæàÐSùÖZß´âÖ?éäüFïÀ9ïGFSÕÅËDÏJ´íÀü +õúýùAI×ãÁEGPÌ:A0F¶XEQÅíI½ûø÷ùA.:8:6*ùûáÕ9ݶ*ÖÕÀ+öOTÎüù>/6T÷äÌÛåJ:MOôêû¯**40´ +0Î+*ÈG¹0,¸²º°+³9Ñ9ÜR+?À×±ýÈ.;Î,µÎ¾XÝäß-¶éíòÞ¿,ÚBÀEGÆ;*âä¾µ.4¿I¹°.ÌÇ4Õæøú¾Ì5Ò÷á1 +:ÝC*íÕíI¼úößÑäóÏN4Óëàßã¹ó÷,ºÅ9àËÜîÜ¿D³ÜáÅÕÍÕܯݶ@EPó.C<Òø?´÷ýû½ýûéýåݼµíøX? +µâÊSÁäÇC=äëHºóà÷¶:Ó<»G½üú8ýæ½úüïéÅ9úUÛ*üÜ-¿DôÚQù¶9*àõø½JÎX9I»ùòëÙµ9Þ5-2,îøìÇ +á0ìßÑWGíçëç19V=JõQîâÇêðïºë·áëVü÷×47øÊPóÈõèðEÂüðòóüïõÍýÝùåÍH°·¼FÛ=öò/*ö»ùðçÙÅ +9IÇXÒåÆFÅíÅí?/ÚAYêL.¾î±õI2òµ2Îñ,ó85XVÖ¯KÃ07MÁ39Ì+¶Ã0;8ÚåÌHÄÌòêßØååý7ÖÉúÕÏáÂI +ÁÈòëòÏGÝIýß¾»÷ëÚÃÑÍWÔN9õ¯UíùûíÍíÓá0Ê>Û¹ù>øÁ2=TÂ2¯àÂ8ïKÃ3=HÚ2ÏY.÷¸¼ùFCDÂ47´²R +¯.áùAæ6/èòïѵí¿*Ú-*IåëÙÄTïD0ìáͽËúV¾ðø·Â9AôûÌ»±ø¼ØôáìKÍåÛÚYãCÌÕ¹õõ9ÅÄ1Q»QìKG +F·*FÇ=øüF+*0M+¾ß-*î/7Hº2¯ +àÃ4ÛH¸ùð7ÛTAZÍW»üóí/ùºäá·MTE»æÇñàÞOOðEöÆ6ÌÖKÚßZÞ¿NöÀ1¹¼õIñÞÁÀ:àõYÎC4Ò8òÕÎI¿ü +ä*FK½++8òAîII2ZÞ¾,-,6B0Þ.0N¶Z¾Ð9ÂAÌG¹NÏè󾷱ͽGVÇÕÛùë´÷Ä7EÌG.æîݾü8Æ¿-ú1îݾ+ +-,>*/0,ÂJƽ-Å0ºÝ½ü4XGýIKàµÎÝõYÎC4ÒíñµYÞ»ûôóáúøóñÅYI¼¯*ÎYV¾öùAF:.Þ-0FRîJ¾ûø÷¾ +*éñ1ðSÐì@YòíÍAÔÉ?äã·<Ú¸KËLÎݾÁ5+*úÄ8NI¼;1ã+AD¿õCÒ6½÷Áùܽ¿PSÎìÎÝ¿ü4ü=öÍ:úA0Þ ++4.Æ:Þ¾+/°¾+/462ÎÞR*VöÎK¿.á<»ßÀ05èUQYMµø=ìÔXÇøEåó;²BZLÀ0/¸Â.38ºBÎÞ¾°:Þ÷À¾¾¯- +2¶¶¸DÏTK?ÔäÍZK½íÝJ±>RñQZݽïÂøíݾýúû¹¯*6ZÞ½ý*,úA8Þ38N¶îJ½ùî÷»úú÷=BÎß¿XìF»óÎä¹ +å9B6û.0á*RK¿.M*æÞ¾--I/Þ½M0.ÎI½;1àYÞ¶*6æíI½,á,:´ü1û3úü/DM¿øÃ1GPÒ2¯MÃX;àÄ2?PâB +ÏUʶ¯àÂ4CTò3?XÒRÏáÅ:ûMÇ7=<ÂÆÎKI2ÏßÂ3K°6çCøÉÅ<áÈ6SàÄ>EÌò¶,¾L¸.C,JI´.ÇÏLÃM1N¯à +Â;1*¯Nà6*N6Ó¾Çó=ãöîùAæ6/Tú/V+**Q*ÞÝM+îýü÷ùõ9Þ¿/-*úJ½ùðãÑAYHÝQíÜ»*ù3>25<²ÆZß3 +¾Æ¾î¿B*N¶:ß¾,-*ZÞ¾+ýóE¸¶Z2Þí¿TR½¿çA0BZá2ÒZ¹ïU4>IòÅîIÁ*1Ì+¶¾/+86Æ:J¿Jî¾Ð9>BÎÞ +¾-7Lò174FæYÝ»ýÞÜC*åQÍJ½./PV27DÚö:àC*¸RïßB*µæîKÂ/5DÂ27DÂÖîÞZ-32Z.L²,/úµZ.¾DêU +½íÁü°=R*:*¾4>×DQå2**ô:Kâ+±ôÃ1¿*èÓ91,>æ¶Î9ÕJïÂ1VÂ+>>ì¿CH6¿ð¼+û/òQîÝC*,æ9ÞRM/2 +:Þ½++0-ö½*-8NÖZK¿XÎÝ»øðãÉQ9ëÉQYF¾ñáÅíܾü4@â15@VöZßÁ+.Îî6J2¾;F¶ÎÞ:*;2Z6>ú5»B- +*üà<ÒÝÖ÷á,6BI¿Lôä±,¾;Ûâ¾óAÜZ/*2·¶¯ÊV?/ÍÝÑíXúà-Ì6*J½à-Æ»·ó¯Aê¿CH6¿TÁ*;8VBZRÐ5 +ºRZK¿0/<Â-7¹ô-M.¾îL+ºD7ê¿,Öøú,HëE?06RûHR¾·°M¸¾Þºß +,64*.EÚBPS-òÇ3Úð+¯Åß39.*2ãX¼D¿0C0±HC¾;¾M9î4A¸òæ¯áÂ.+ß¾SJöÞ*ÊJÀ+30V¸Á,7@ÂB¯MÄ +ô;áÅ7Q*à3L4FMÃ29DVÆ:K·R¯Î²ÖòÎÒEÊ2<»â6=PÊÎJGµ´*BûG7Þ+ãÌG,*ÎYÝR*A4*ò°*îÛ?<Þê°Ø +.ÎèJ¿³7/¾â÷ÀD¾ò1Ø.Z8J:2½N,µ¸JæG½à-ÆGìP´Rý²,ó038FæîÞ¿,å*ºK¿-1@NRÎÞB*IÆîJ¿-5@² +æ?P²RZÞ¼ûö×¾úøë5-Îú.ìéQÍÞ·*àÕYÝSS»æîßÂ5;Ô+ö¿+÷×ù´7×W85A³÷0D²æÎMVÆZJÁ+1°Ò¿Õ¹. +æÎÞú.è+:Ö¿6°¸Ò*Z*ê×Q+¾äð5/î×-ÞÒLRϺÞBù/*Â51¾ßJ¿¯í*Þö¯¾?*Þ·¿8*Ê,Fο5H¾êÃ5ô³éQ +=RñÅîÝ¿1,*+8úQ:Þ2*2B4*,8*ö¿.7HºöÎK¿Ú9Ý»ùòïÙõP*XV*ê5?óúü/6ÖZßÁ0»ß¾÷äËMQYI½18D +ZÖR˸OÆãE½?,50í°áã×4Fæ9ßú.¸4¾Ô**·:ÞZM,¾âÇ,¶=Ì°Ú>ÏÅQôKξú/¿Ð,*ÊÝöÀ?2ÒIRÌG¹ð/Ö +Í+G9ÇÞÓ8*ôAº:*JÄIÖ-±ºB,³-3HNBÔÇ2¶¿RNàÛ8 +*ôQºÞ:2BàôQ=R1CïáÂ9?¸ÒÆI°ò¶ïáÃ7Q*L,2MÄ7Á*úMÆ:Qt>·;LÅöÎKÂ07@Âæ6HVBï¿+ο33Ì/BÁ +.;@6Xóýô÷á¶ÐãÈ;»Þ·çÔÓù´6AÅÃÈ>PÀHçéæO°FÇÛ²Ææû÷ñÕYI¼ûø¿¼á*¯*ÐíÝB*ÞA,¾úóáA6ÞíWÑÄ8LÈC»¸Í@?ð±ÕÍI·ÜVAUÑN³D²ÅQ +<µÎMèß»PÒ17HºöÎßõYÎC4ÒENB;ßÀ.;<>/5Ì+¾ÁA0*60½¸Á/7TÒ¶ÏLÃTîݼ+ý/V-Þ,û+âµZݾ±*ÔÍ +J½ýè*ºI³ÝÐëáöðQõDóäÅ5MÀVæëKñ/U>RÈ.÷HºL+²ºùQÅF¯MÂ79°ºöû5²Ä,ó-3äÄ8±Èò-éGñÃTÐMÃFÎH¸ôö=4 +Ñà2.¾B.*>¼ý,ËÏJÛàÁ39XÂ2;Lζ0*9LÊB;àM-Þ4E´.çÏLÀ+ÑHZK¿-+@6¶Î-²B:K¾0+NÃ,·J»öâßòYâÅH°MÇ6M°>ÇïùAæ6/4=°âÆïà=-:5AH×*ÝƯMÄ6 +EÀ>çOÄòBÏKÁ/5DÁ/3@6*B425B9¶ï*XZܽ4=èéQÏÉFSÐá½ûD¸òýâ?á?TG²ÆZ¾ñ×+6¾Ì+¶À0=Tê¶ÏLÁNîݽ;ÌQ,¾úû5ö +æ-à+ÚܲðöCêRZF9åÎåË,ñ?úåçOùÓT/4<Ü5*϶¯6¾RÒB4Þ=PâæÏáÅ8GTÃ018NÆZ2¾:FB,*.Ô*Ê-ÚB+ÕÕÉ×´óøC +úåÑËÉB¯K½óÚ÷°ÜXÏ<ã3ÐJX1ÌE³ü6ùåÎAºBïàÄ9MÈúü/DM¿¸Æ6M°.×;âÄð¯áB*ß2°Î±Ö=²6/î7IÈF +ÇðâÇ:ÛMÃ19PºöÎßZ*¶B0Þ7P²öÎßÃ09ÀÁó+ìîß»ÙÀCáÄÌåÙ1XײíèÏñìÌ/Ñó4ÓSÈÚÎH¹îÚÇM3EIöî +LÂ5;À.SZÜúü/DM¿À¯È*ïÇ*¾Á=*-*Ï*Ü:àÃ7C´â2ÏA>BÎJ¾,/0V*22Þ*-0>B:Þ¾¯*¯+8ÎÞ¹Ô²÷D0 +˹5åWÕRÒ°ûðà¼ë°Ã4?QÁÚÍG·ìÚCNÈDîÕâÞ2Tò2ðùAæ6/èCHÒæÎLÂ47,Ãó*@;LÃ21*Jà2*¸¶Ïá**Õ +ÖÎÞ¿-/4Fæ.86¶ZF>:6øÂ-186ÆZ6JÖ¾1F2YÓTظ+ñâÄç4ãçÑPÌÚÓVÕRÇ4?S:íM18E¹@ßø0*@ABÀ3K +´úü/DM¿ØÂ1=D6îò0=HÂöZLÂ2ßßöåÆ*¿öZʾRêB*J>@6÷6ý62?+-Ä+Ê-*Àñ+ïͱ*89?TÓTÛÐóȽà +3ÑOÉJÏðÒP¿àÂRÍG¶Ö7D¼LóT1ZêÓ0NöîàÅ7ݽ¿äRÎøïLÆ5GX6-î6I°òÖÏMÄ7KáR¾B¾**1SðâÇSÐFçÏLÅB¯LÂ4;Lº2:0,...¾<Â1?0ÊÄ0Â3¶¾ÞHÇTóËä¾H4ÑNÆ:S +ð>ȽØÒÆ9E±èÖÏ´æ6ð/ÌÞ<,ÚöÏâÇ?±=R*Î*îò.X¯¾.Ýú<.XBJ4AÆM.Ú¸³8F¶ÙÌÛZÕоÏíê×ù°5ÓFZ ++>ù5>¶æ+ARÞL6JK¯ã¿Æß°ÌϾZ*î*,êà8-BÚ3Ê·*öBWÄú/,ó5=X¶¯LÃ3á*è+¾àæOÏRïF¾UâöïáÅ8 +=8ò-5BH:P:HÞ50òÔSÕÇÀÕùÔÕFS¸òRÐPÜÇ*êí·Þ/4ÖðèáXR·:á×ͳ>:6*>.JÍ/ÊLÅ +ü*L8öÀæë<³Æú-,»ßË1æS>P6./39º¾UÚæïáÅ:GP..-E+ß*å3¾*渾ؾ,SIÆZݸXWíËèõÓø²Ç;MÆ:÷ +L¿òÎKÑ0ÌH4çÏGÒÜó5L2+¾0A¸.ç¯Ì,ÓD+ú¹ÜNàÞ*E2àÅÞ±,8*¾FêÉBæÎYJJ³¾øPÃ+-TÝÅ5ÆÛ/Û8ÚK +ÛJ2¾¹ÉJÈE/ÝÖ±ÅÔû>2àôÃQFI*ÅLA¹ô¹ÕZQ¾É±9¼-¹àÎÄ@ã,ôARîÎ-XÎJZ=¶·Ð>1>DLØ<::JìZ¸,- +1´¾ZE·Fźº9,Ã5?°Ò¶ï7¿@»Ü¶*áæïMÆKÎæZß¿.5ij,Á10J2D6ÓV-2+/0áBûPãÛµÌøÒç;½Òæ:K¾øâ +·áéη±åÍMвSUåî7NÖZR¿<ÂæïMÆ9ñ=æ×NãüÅÀ±VÐ10BÃS+Þ302,ÎW×ýG7Þ¿+ÀC*O/JPÉ*TË.ÆÎ*, +/48è*2**QÅ*à.:ÐHÞ7¿S@ÆJ*º¿ðê×Aµ¾»0@ÇFL,ê-Ð6;JBÁ¯½¿,1Gî,/6ìP´RISÈT¾*À-*22¾5øö +îZ±¯¾9Þßè³ôçùö.êñÆÅõKÎ,ðáÁ*BX.8M*úáÅ:QÔV×æ==*2à°9ò²,.*00@û+ôDGòÖÁ+¹õñD¹ôÀI?åü,/8¸Þ/4BïÀ»ë¸Ð>Á*ñ//*-+¶JLð<³Ò0îó +ÕÙ÷ßX7ØæƵMÎ:°Æñ¿+ÎÆ:MÌ.2Æ:M-À+âáÆMÎÚðMÄ4ATÚö*I96*KâöîÃ-öÂ<å¸ì8CSÁìVÎC²äÊCáÄ +å·á0ÌF».OàÎTóÈP8HÂQÞ*IÔ>ç<ãN/è4A*J²¯*31ÞßÃSÐ?*JÜ=RJH=¿È0Ô<È0ÛBñæU4·Ð>¯*ó/M*/+öêFïCáFö*°ìîÇ-È÷K,D-¿0µÀÃMÌÏÍï*1F¸HZ.4M¸ÂöÎß:R±æ +:KÁïÇK͵6È7ÈÚÙÚóÙEÁ0ºKÀ*Í·,ÒQ9õ-ÆÎLÆBÇÀO»Ké´XH¿1ðD6D.TLÄÊÎJQ*Òà:ãÛ,Î@B¾.B¶òN +@ÛÜ.¿@7*ÃÛIÞZDJ½ÍоK?íá/M*-*1XîÆ*>¹îÖD>2ðN1X=¾YÝäß-¶é×±Î,¸²âìéë-÷¶¿,AGîL46,+ +MêÜCÈE/¶++ö¹Ï,T.A1Î//âFÞç½MÎÐ;á3¾-*B1¶°+Ï+;ÎBÐáÂ07ÄN4Á21JWKÁ.;Ä+¾Â+Ï8ïÞÂÏɽä +·ÎINÖîßÁ.54òôÄÓàÂSÑQϺÓUÜʳEõÌI:ö¾6À5²KÂ6OÄ>CG¿Üúæ7ÑÆVÇH71NÊ+éÖÁQJÎLðâ3*3Sð¿ +-*Ç-*<ðâ¸*Q÷ðOÉ@S¸Úæ?°ÚRïLÄ6?ÀÃ+*X¯áÃ4?´ÒR¯Z¾°´IÕPÄ5é0À-FMÁúä³ÑPëDü0ÌÛ¼*1H6S +R°*6á1²áÇBWàº×Ã.T2ÁÈÄ-ü@0ÚAR.S°NÇ;1*à+ÀIå,À+,+Jã2RT·ÐáJQOÖîJÁ.C0½8¼,ų*ìðñù² +;HêB;R6J»;*HÏß¿-1<²ö:Oʶï¶JÞ*²V°2,O?±ÔÊØJ½ö@FARØƯá¶LçÖ¯B*²6-*6í6*¾6:óO¸ò3=HNØÖ±âÁCíÚPÃèVBZÂÐEÒöZáÈ÷ë@N0=Hº;**¯>¾KÂö:à,¾Z¾5<¶:*è +îLÅ>MÔúü/DS¿ÀÃKÎÏÎÜïàÄ7C°êæÏZâ¶BÈD¸.C.èQÀâB;LÀ¶Åùðâ»åôº÷ZÝ8Óç¿+/DºBÏäÊëÜàÁ1M +EáDá+äG-,Jà¶æ·ÐâõYÎC4ÒÙ>ÇðNÉOãDZ*;+ì°PËCµàN3ÐÁê2 +Ö÷ôðáÁõéÏ-Aë·ð×R»èÀ3?4Ûý´M*÷J*ÂáÅ?*ï*+*ÞïVJ>*³ú毾¾²úB¾ÞI¸>ç6AP³*ÌÏáÅ>Yܲçðù +Aæ6.4%%% +d +643 83[1 0 0 1 0 0]sl 8 mask 0 166 di +/mask 6723 string uc +*Þä**¼ºýÎöüÉõûIòù½æõýÎíýKÝýÁ¼ý1ûü9øûIòù½æõýÎíýKEPR*% +d +/sl 53369 string uc +äÕí´ê9+¾Ñ,*ÐMÆ8O¸.9Í.¾º.÷L,ÚMÇ?WØVC°MͶ;äãñWé±èÛíõÍݽûùãùõä¹ÕäºAëÑXÎKº+;Xʶ; +KÒB¯.*RV/21:3=H¾*ÉR;àÃã-¼;MÅ:Wĺ·ÐöýýÈGܽ/FR*éäÕYUQ/DÖ÷¾FY.Þ3ĸÅ:SÐN·<âÃH¯àÚ +ÚëÕÃ,ý°ÅÖúüýýùõüùÑ9ÍûôèÆGì×±¹86öYLÇ2¯ÒÑJºB0*:@/Fö¾¿¾5-Þ6GÈF·ðNÈíÝJ±>R;ç¯/¾½ +6÷ïâÅ:IȲ9KÀ6·0ÞF¼>ç°*¾ÉêRÏJáêñYóN**ö*å=ÀÅ»¼ýýýýíÝݺI½ÛµëÓCÑÓèû?.RZàÃ4?Ô+*Ã1 +**@H2*ÆöJ*FMÄ=MÜV÷ðâõYÎC4Òϲ×ÐãU-2**>1+ºãÈCµèÚS=PA÷;ÞÞèñáê+*6*æ5µRçUÛ½:²BÒÕQ +í»õëÌ÷ÔÓT3LÒÆ;âæU*·2îâ+T,è+FâÈ@±èêCÑäõYÎC4ÒÚ>·,¾R̲÷J**ã2æD*°÷°ä**µCÐMÂÂßñÍ2 +Z>¿¸**.ÆÞñSNðÃÎìI½øóÙAí»Ø-ÜÉÓKÂ-9¸â8E¸úÖ¯áÅß*âïMÄ?*18¾Mà·±PËBݽ¿äRÎ8ÐMÆ9K¼ú +2оòB0*H@**Æ-+HÐNÈ?UÐ.·;UÒööùûîFÚ:æC*â+2?ÊÙUKûýüöëÁý»ù±IºAS¾,5TÒæ>PÊRïÇ*BÂ4= +X6-Z4;´>CLUTÔúü/DM¿¸È8U¼N÷;OÆZð6J.¾É.·°âÇ;OÄò=Sغ3ÑOÇ8·MÆÇåõÍãÞ?¿¸**0öÞÁéâø± +Îìݽüõë¹QìÙÑH6óß¿1?´>7CH**ë¶;áÄ7AÔ*BÅ@Uä6+öü/DM¿ØÅ9K¼6*¾:IÀ.3°6*ºî*ü;âÆ +ûâÈ=QtV·<ã×*¼÷±QÏE½ôúü/DM¿àÆ>MÈ6CÔROÔ6SÐâDZ*J°F¾É>×ÐOÊC¯Øò;G¸NSؼñ¼Ì5Ò-*J¾-< +¾ÈËW.óýýñÅݼ¸ïÚ;ôäNC´6**8U+À*úàÆAµà²÷<åõYÎC4ÒØ6×<âÈ;M*àJKãÆæ4*I·ðâÉA¯äÂçYÈúÖ +;åè÷KÁD32²2*1Þ¾ÂζÊõÍ¿¸ýüóÑQíÜù×ïÍ;*î¯>J7*ÁÒçðäÈE¯¼ý,ËÏJ¯NÇ;QÄF-*:M,,,0+*âÖ* +I×<äÉ@S¼âR7Ø»ÝîA·2²2*5öJ/мÁ÷1ÀðûôáͽÜóà-*Z6?Xê¶0¾MÔÊç°PÉDݽ¿äRÎò°NÉ=*R°ãÆ? +OÌ*2È=M*ÚNÈ>YÜÊ·±P½SʼýÆZMP,7,î9*üýýÜúõñ»öÓNPμOèýýéÅ9YHÍüǯáÄ7A0, +*Ä?*ì;ãÉD¯èÂCñùAæ6/è±ÌÊC°PÈBODÊ>YÌF-ZAUØNC0JTì6*îDWÈ6çÏñûýÃÓ+Ç3Ò-ÈúýýûôëÍõÒ¹ +´ú2ðMÆ8»âÅ;GÄêRðMY×°åÊH¯øÊ÷û5²Ä,ó:UÀVC°ãÆ=WâÈ;SÄ*JÇÐOÇÅ,JTÌF+*>-*ÚOËEµìº·C<äÊI³,OC½äúü/DM¿øÇ@KØF×°NÉÌ°OÇ?SÐ +>×<ÓNS°.¾ÑVCÑPËDYÄâ8ù+ôH½W@Ç/Ò9ÆôýýýðÛÉõâá¸úöÏMÅ8ßá5+î·Îïúü×,>Ç3ÒY¶õýýü÷éÝõßÓ¸>C¸Ò9û3±äÁÆëíA7³NPÎXááýýûÍ1½Ü+õØðãöV·3±Ò@Þ6ÙóE· +07ȲæÐJ¿æõYÎS4>ÚF+ZDYèÂ÷P*Jä**Ý×±åÍIµØ>S7DÌýÝÞ4Ç3ÒU¿øý½ûôéÙõáÝÀN·°OÇ?ûNÉ;WȲ +3ðOI·±äÎGÁä>4°¼ý,ËÏJ/OÉA,:ìæRÔF×°7¾ÏVCñä**µ3ðáÁÈ÷ýí0BNPÎÜåïýýõÁ1IÜíHTïáÅ9K¸º +<*ÜÇÐâÉ=»ãÈI·0óC²Q;DñùAæ6/TQØVçE¿øWõýýÅÝü»÷2AãÌGµÌ*¶ÍD»ðòSÑåÌ8ÓRÓNÓ4ÓDòùAæ6/èUìVS±OË>³øÈ@UزCÑãÊÜÐãÉC +YܲCÑíâÇÑPÈ:Iè²Õù-I>E¿øYûýõÅýGºóÌ@NÆ:OÄ.3ÐÊúB0ÞGÀúÖ°PÍE½DÊIYüúü/DM¿ØÌC½Ü6-îC +·àò3=QÊGGäËC·äÒS±Qç3²*¾íÊçÐUîø7ÙÇËJ»ÖýýýáYíܲ/Ø°ãÈ>WÄ*ÖÉ>YÌ6+î@¿ø>èÑæÌLKåõYÎ +C4ÒÕÚçÐäÈDWè>@±Ì*¾É?*ìÐãËFµðâÇÑõÚS<¯÷ûí¼ÇVJ»À¶ýýåQýGºYºÐNÇ;KÀ>çMÄ6·<âÆ:Q¸ÆC· +üÒ÷=åÍ<ñåõYÎC4Ò-Ë÷ñãÍD½àòH³ðÚ×±PÍESåËFM,ºRÎH¹,7DÒRÓÆÖ»üNÉNPÎHMáýýùÕ9ÝÜQÄ÷ÐOÉ +>¯ÔÂASÜNC±.¾ÝÊSòQÏHÅø>N»¼ý,ËÏJ?QÏFÃô6øL*.RÍ1,¾Â4GD.ÞÅR,Dñå2*õCÑQËH³ôÒǺìêC,Þµð.øñQÍIÁ¸ÎKMPÌÝýH3Ç3Òµïøý +½üóçÙåÄUغ3±ãÉ;*,=ã4*ÍCòQÏHÃøF4¸¼ý,ËÏJGäËB¹àÒ3ÑPµS=äÊA³äÚç¯äÂC±PËE¹¸ÌF·øòç=ã +ßÔüý²2<=R9áôýýôßÁ1íý¼FÇðâÇM*ºOÇ=Mìê3ÒQú×±æÌIݽ¿äRÎð±ä3*ïSñ¿*öÌD¹ìê·=QÌü +ñQÎI½0/4>ÔÊF½ýUÇ/Ò-öÑ÷ýýøåÑõó-Ų×ðOÉB»ãÉ?¯ÐÂ×°PY·ÑäÎIÃüFȸ8ë÷û5²Ä,óC»äÚC±åÊE +MIûPËF³ìÚÇÑPÕ·±åÌI½øòC0*±@P9üÑê.è/ËüýI»õïáÙÞ=SÔF+*=M+úNÉ=WÜòç±æ2*,è=æõYÎC4Ò; +/Ȳæ3*CTòÓ*BÐPÉD7*ÞOË,ã6I·N×3ÒÍ,ýý½ûòé¹A½µÄÚøÌI¹ôâ÷=åÌÌ=æÌLÃL³T?<ËزèÏíÝJ±>R +ÂS=PÌCÍ.*çÒ·±/*ðò×±QÍÁÇÖñ¯øÅñâPÎ85üýýç¹AíCûN·ðÇ*öÈ=UÈV3ÑåÌ6ÒQÎGÁô6øû5²Ä,óJ±, +Ó3ÒäÍE9.¾öâ÷J*úQÍI½,?T²Põ3Ø»îß´²B:Ѿø+àüýíýHûùâ·QÊ/*îð.*â6+îI»4/Ø>æÐHOSÍíÝJ± +>RÍç±äÍC·äâS²ôF-*EM*ÂäË/*̱QÍICàÃûÙÙåÍõÚFJ@.ÆÄJG,³ýýùQýG»À6=ã-**?-*úâÈ=Wàòç= +æ2*4رæõYÎC4Ò4ÛS²åÎFQ*6RÌJ¹Ì*¾Í;*Þ±*¾-7DÒNÝØCMõùóõí9·CDEJÀN4èEÒùýI½ùòíÙâC±èº +@*¼=æÎOÃD7è>/OøñùAæ6/T¾47躰*A-ÞÃÌê鸳ðêÓùøúùùýÝ?ÕCÀáÀ64èYFúýý¼÷îå¹áF·ðê×=QÌ +æ=åÌF»Ä/¶ÎKË<»¸òSÐ2óRÑüý½ýïÊÎJGåÍE¿ôú·ñåD*ïDZ6¾óòç±åD*íç°YèÕÕ¹íì×ÅY9IýûüáMÒá +ìòJîÚJG2ÊýýéQ½¼»QÚD²ç3*,Ø>æõYÎC4ÒæúC,¾¸ìò·0J´ô6+îF·ôâÇ=åÌFK +åZ*I3ÕZåâÕ=ÍìÑÁÝXHºûûùñý÷ÑH¸áL¿Ù,óVÚüý½½ºöðûYÆ-ó÷ÑåÍF½Ä+Â.ÖÌI»øºZ*î±>¾ýêçÐäÈYUAõèÕ±íHܺøóûºùøûñýYýLù.0Ä+1/èñJüí½ü÷òçù +áS4Ø>æõYÎC4>÷.ȱ.*ô.è=æͳÎ=Î;*14Þ»ø>÷ÏOÞͯùîêÓÉíXܸ÷ +ìG¼ùúûùýIÙ»ºâäñ,óÈÎýõ9ÝùöïGôÖF³èÊ÷SÒNË8óOÅQÍD¹,Ë+ÄH>æÍI½øê×ÑéFǯâÎR»ì.G5*ÚQÌ:;ÈúÇC +³å7ú¸òòÕÕ1íñÉíÝIýýý÷Méî:»Sï¾JûÓýýõÙA½Ü¹ÑË@åÌï1ÌÑPÊAWܲCÑÝê3>çÎNÁ8³KÁ0Gøû5²Ä, +óI»¸ÒM½VÏîæ´Ó4ËCVJ¼ü6BóH½¼Ò×ñ¯åäÛú÷îãѽ9½¼ÅIݽûýûÝYê*<4á +ß+¿¸üóãÙIýEìËð=æJÂJRÝCÑPÊDµÇô°ÍóH¸ã +ÙSż5ÓQËüðäÍG»ôòÇÑùò·ÏàƺÉMõîÓÅõÜHúúîW¼÷ûùÅÀ¸ùÄÝ?¸,KÕýü½üöîçÍYËÍYÁçðãÎLÁ,óK½ +ôÒS±äÌJá³Q´OæÎíÝJ±>RµTòSÏRÅHGèË@»È²çÍE³øÆ.»ð?7EñìÌG°àÁG5µFÔðâCòPÍMM?ßæ2*ÅBøø +ñéßÉ1íçÙYíüûúõ¿¹÷ôóýý9ÜèZHѽýùëѵýÜͼA6KÅ<¹HóOÍHøÒRÍK·æÑOÏ@ø².*2ûü/DM¿<ÐOÇ +0BÌ*¶ÒO½ì.÷;ã¼ÜðRÞÑK-åûØåÌê¶íåØ/ÉóWKäêC=RÐMûRÏOWȵݻµÕÜFºõöãÝ1íóÁ16Ù³óê÷ùúýý +ùíݽ¼÷¼H»øÉáWD´;ßò3³+¾E³ØòSÒRÑL/SÁ@âçÑíÝJ±>R94ÒæÎO¿A2ûü/DM¿¸ÎKÃ8?D²RÏüñãÆ6=Ôá2ÐÁÂ3A²ßÀGAôÈ3UH»DöëÏÛóé8ã¿UÆñM1ÇZOÉ3Ý +ýýíýÑÝܸ±ÞËUùðîïáåÍÜøïüØ´ëûãAÝYÝÙ1½Hûö¹Õ/Ñ-»<Û×ñæ**M¸²çÏ;Ù²²SÏÏ*LÒçõYÎC4>ç¶RRøû5²Ä,óMÍ8OȲSÎQ»ñÏîìÛ5öîFÕæóìÛÆ1Iüê7µÔ +º×òáÎ7ùâѵEýÊÕSÇH9ÝŽ¿ÅõÜÝñý½IúùøÓMôT±äºT¶³ðóû¼ûùïÁµXݼµ9¼û÷å=ÅûTÙP/È<åÐ+Íâò +çÑ¿*ì²SÑQË<»È>Fûü/DM¿øÑQÍ@»Ø>PÍÜÕ±ßX·P.RÌ9ºCóíá¸79ôÌKµ@7C²æÈÛ³ãÐYáXìù´ÕÙóZÛÐ +MÅÔìÕáÝHÚúûô×¹çµEðMÕÎãØü÷öùúáYݽÝÝAý¼ûð¿Ï@ÃRÇìÊS²ÎÀUÃT?TÒQÑHÃUÏX»D³.ÞùAæ6/èà +SÐLɼý,ËÏJ+çBRYT>ãÌB=ØúçëXÄ»¸ïàÃñ¸ +ÜÄIÙ8GØðßì÷ðçÙYíÌÊØÕì6FòëàSÝôU×àüêøñàÕG°Ødz͵ýݼÕGôM¿ú,DÒçÝT08¼¼úùíéï´ÁH³Ø²2 +¾>³D0JÌDG*75¾OÉ8WØòùAæ6/D,èÇ8G¸>SÍ=³¸È:·DÓU׳ìܸ¯ÜµùHHGÙñXW¸ñáÕUÑô×Cíü˸´áÞû +¯ÉDÕÌ»úD°½8ñÛÁ@íLôëïùÅýØéÆ6E¸Ç@¹PûÉCòêÊÙðÒFÅR±È>èЯ*ì +>TÑQ½èÊ3ÐÙ.øÒìäÙ¹ÅôÅûèGºøõåKÛç×½¯Õ8û÷°åºòRåÄM½üç5ÔúÈó°Ó>5øÀø.¸úêIýý¼üïÑ;Wðú3 +ÑÍúBÐOÊFÇ<óPËHË4³èÒS+èJ*Á4óSÒó*P³TÓSݽ¿PSÎ0?TÑP-JM*úSÒLWìÒC°Oý·?ZåؽÝÄê-M¼Ø9 +ß¿DßøØZQ0Ã4ïàËX=èîÁ/¼>ö:SFø²Ê¿Q>×·ýûýýáõØçÀNRîݽ0×Þ½üD4Oز6ÒF»D0TÐTG27-21B-û +ü/DM¿°ÐOÉç3*JD4ÞÉüNCÑãÅFÍø×ÃSý0ìEZÉ8¯IÁ/E¸êAÎÍÂÇ<Ü¿EÑÙô0=H>SÏNÄ?ûèà±OÙõY +ݽåYýüôÅÃ@²äÅç¹QíMÌKÃ4ÏLá*ÞæÎRTØòçD*±ØòSÑñ*ÞýùAæ6/DêÇÈ@G¸8¸6èÉZ×ü;èëó¯-HèÓE8ëúïÞKµ¼òN-áü¼ý¼ûø³úðÀ +?D-Bü¾Ã,/D²æÎMáLßæ»D6øû5²Ä,/Rá1+è2*Éø²TÒ;*Ó*8?èÒSÉðÊ×ÑõâÇòêàÔ¿1õÙ-IÛ +5=KÂ>Gà¼*ûäYÍFµåÚÙòïÝÝù¼êYDúÅÏêùø×¹îÑ/µWøñRÒR³TÒQÉÜ*¾àåæ.ÕDÓTÒTÓXÓøû5²Ä,/U-4 ++èD*ÆDÖÖ6ÈÐTËØ**ÏüÊ·ÐãRRµFùõêãI½Oç70VRïëòÛGùñÕ·Á8ܸóÝ8ÕæÃíüÃËÌF½üé²RÌNÕ0**ÓÁ +ÎôÒSÑO-JSSÑP5µ+è:RÆøû5²Ì,?QÉ0BðÐOÉç20Zøû5²Ä,OPÉH7ð.Q-5-Lå4 +ãçÑ++OÎ1BÞÁèÚ·ÑPÍYû¸ãÙÃ1AëÚ¸âܵ²íÃG¹?ØÏ9òÄ:WæÞ¯¹ô¼ñHO÷ÐæÏL3SÑPÉJ/èÑßÌÁβ>çÏï +2Á+´²SÐOݽ¿äRÎÎòJÑB»¸4JÈD7ùF7B·642D2,óQËDWȱPÉE»QлAÝä7G¹Ý7EõßVíïÉåS¼ËêÙ´ã¿í +àÒN·Ð.D.TÄ<7WB./RQ6ÀJÉIå*¾æ¯æõYÎC4ÒEÃ4?èÒTÑPOTÕT7/:RÍÌ/6º8PÀÒ/*+.8óSÍE¹´ÂçÑE +ÃUö³ìè×5õ¾7HÚÈCµçÇ·íÙO¿è¯*Røû5²È,OOÏ@Gä>TÑÄ¿ +ÌÀÌÁÈÐPÅ7MHM<ãçÐÏÆ=ÈX±PË9±ô.UÕ1üV¹øèÁG¹äÁõÈÓèÑâÌOÖ+/>?èÒ+ÎVóTÔVÕXÛD:ÉÊLWD8+Ç +DO4?ç:.Røû5²Ä,?MÇÄ*¾Ð-*ßÌò>èÑKÍâ>çÏ-*Ð>ç¿¿×¾¸¾î*A4±PÌ=¹ìÊèã<ÄÛBìÕF½¸ËKµì>ȲS +Ðä>TÑO19,*8>ÞF*B7°ÞLÁ0/4òRÏ+2JòùAæ6/èÈ8WTòú°:<:W.óRÑLÃèÒçÐN-*êRÐNÉ@¼Ä+.9öÐKÉ +@»T±RÍH°ãËNáðÒçÐîâ÷L1¾ç**¹øòçÒ-*.?TÓ¯*:ó>ÀI»¸ÒSÏL¿@ÃKÇ4WDòùAæ6/¸Î×,ÞÈ8?¸²Z°R +°êï/À86ÒÂ4ÐNÁ-æRÐPá0FèÑFµøÊ2±QÖCÚ:T*ÈÄC2ÑOéL1L+èUÎD7À¾L¿4GÈòùAæ6/ÈÒLÛ +DÚ¶0ÞÑHÃDóèÒTÓøÓU×°ëسUÔÄóTÓTÁ;3TÒTÁ/P>ãèÒ+-X?UÒXÕ¸ó·>NW´2CJU×´GSBù.Sé8¾é¹+î +WÑPWè²SÒRݽ¿äRÎäòçÑQ96¾C³ØÒç+*¸D³è¿æK*ÇD³ÛJú*>7+2BJTÍL762ç6UGòF+BT2L/R-;L5à- +RèÓTM*ÞèR¾ÎSÑDÓSÐ+*ÎòùAæ6/èÌ@³¸²êñ6*;WȲ6J*ÞʲÂKÓ4ó6?WÃT³Æ+G?ز.ÞùAæ6/4ÈÜÆå-+BÁ.Ná;+çJæÆNÇ4ó6ÒB³D.ÎÊÎ +Ö:8:Ä3ÓPÃT³èCÀ2¾CQÆD8ZòZø¾ÈHËD*ØZ*Ì0ÛÔVºý,ËÏJCèÏQÉLWø²îð*=GWØò6J±Ú=G³D³**Wà +T³RÒJ³¸B3Ì0>*Ò;*HóSÔSÕHÛ4?VGú>RÕÄ9*Ó¯ÓÁÓ³ÖâòSÓKοÎÞò+E*ÑK0íÝJ±>æÏMƸ*CØ÷ä*4* +à+ÊTÔU×8*æÔVÕ°Û¸ó+6.@2ÓTá.L.Ì?¾VÛD2+Ò0¹0È4ÔWÅ.ÂTÔ³Õ;4Á0íÝJ±:R%%% +d +643 83[1 0 0 1 0 0]sl 8 mask 0 249 di +/mask 6561 string uc +*Þä**¼ºýÎöüÉõûIòù½æõýÎíýKÝýÁ¼ý1ûü9øûIòù½æõýÎY»÷* +d +/sl 52083 string uc +äÕí´ê9+¾Ð,JóSÓNá*öçÒQÑDË4ó¿*BÒRÍHG-*PM,ºTÓS×TÛD³UÒØÓTÔS-4¾çÞ,ĸ³TÔSÕT7=:S×Ä8 +¾Ô¯.¿0îòÞß¾¿Uã¸?UÓTÑXORÑ@7ÓZQÏHÓDæJúý½ã8íÝ,8>¾³ñéÕÓÑ,ãNÍçÀ +*ZD@DL4Î8TÄÉ,ÐßÑ=*,³èJä-*JQËH7WR+JPÍP7*B¼7MR>*P9.ÞùAæ6/TÎ@W+*PU+ZSÒPÍDW½6+R. +B-2FîQÕLÛ4?éÒVççÔQá*6èÔQÑÌ2Â1ÎÓRÓ0¿4ÒPÁWKçÖ,ÎD*JÔTG<2-:OÍHBÃT¾õYÎC4Ò:»¸,¾ÉDO +èÒçÐNM*,ïNò.Þ3º8.ÚJ+RØ4*ÆHîNåøÒéÑWÏ´ËèÙTëT?é +ÑRÉÈÑQÅHãTÓ;Î1ú÷PIá<49À7ܽ¿äRδòSÑQå**綾B*³ØòSÑOÉ0-ÎÐPÍÜC*ZDºÎ0*ÇHGøòæJã,¾R +å2*ÎDÞ3ÍPÓTÓè->ÎRÍDOزçM°¶;5°òçõYÎC4ÒN»¸,:V·0¾ÊL»øJ*ì*J·Þ38BÑRÉPW+îT͸»Ø?èÔS +»éÒX×°Ó¸³èB¾3¾3æ**ZØê:æ*ÐÌ+Â/ÂZ¾ÐíÝJ±>RÏسî¯6*XëTÓèÒKǶ?UÓTÓÄ+ÆÓUå*æTÓVM*ä+R +éÒTÅ32èÔRͶK6*YóèóéÕYÛ0**Ô-*ñ.Ñ1Z³Î²F,²G´¶Î5L³éõYÎC4ÒV»D,*ÏX»T?è2*ÆD4*ÐÜ0¾à¿ +:ÀZÀ2¾*-µ¸ÓSÓPÕPóD,*Ø0¯0¶àÒSÏL»øÂ5,-À+07À6PAâèÑíÝJ±>RW4³SÒQÍÄ+Ê,ÆÑPÍ.*@WèÒçÑ +++á*=*ÎòÇ*.AÊ+¾Ï¯+¾ò/@ÂFÂ*>CÒ+Ú:ÁK-JØÊ:Ú*μý,ËÏJÃèÑÅ,JÐLG+¶5*ã,µÆîÈ5úçÒOÑDË4 +ÓèᲶJ0=*á.Á/íÝJ±>R·4³.¾FW¸4:8¾ÌH³Ø0Z4î,,XZJ++>71*OU*8¶+67-*N9·KÇ+B»:2M0Ðòç3 +¾;-·4óùAæ6/TãT?U³êÓ;*ÌóUÔZ×Àëø³/*·ëè³+JJÞ¶¾¿óD@é2*ÚD´éÖT16à*À62êÓZ-7æUÔXQ*Né +ÕAàZø:JZDï,7Þè+ÁõYÎC4>JË+*Þç¹0¶Õ*ÞòO;*Ñ;+µÌZ:îÈ-ìFÞùAæ6/4ÙDÓøòèB*ÕøòTÒTÍPÃ4Ô +Ä+Ú**ÎD<ÞÔTËøò.*V7BZVÍT³øN*Ä7à-ä08Æ+NW´:TÑ8*Â0>CÎÒQ×ÄS.?Â*ºý,ËÏJÏSÒPÏH»B*õ*î +<1Ä5Ì6J,´¶è,AÚîû5²Ä,óTË´»¸óSÕQûTÒXÏTøóSÍDóTÒRÑPÃÈÎTÓD0ÞÑP˸?èÒQ×07Â+.=¶ÓP×D +ÓèòUÑJ³Î+UÃèóçÒR×0C*ÔK0á/Z³ºK2JÖKºà;C*Õ¿0íÝJÑ>RõTóéÓYÕ¸ÛèÙXû¸³UÔY×ÐÕWÛÄ*ÎÔWÛ +@,Â*¾ÕÁ,¾ó:K7J¾+·Ó4´OJÆß¿7*Ôñ.¯*À?é-°JX׸7@:W×ÌSÊ*:ÖTß¼ý,ËÏJGêÑW͸»ÈóSå4?éÒW +ÏXË4ÙPÓT?é2*ÝD³èÓTÓPãD0JÙPÛ4³éÒ+.ß.γ.*¿»ø³.¾ZÃDê*ÎH:-ØT?é¯-ÉTóèÓQâ¾ÓLã4óTÒX +-7ä*âéÑíÝJ±>R¯4ó.*F»@*A+î*/Jè-F¶Þ/O0J³<²¶õ31Úîû5²Ä,ÃWËÀ»ø³.*¯Ãè³.*´ÃTÓTÔ=*D³ +èÓUÑPW4ÎTÓLë4óèî+ÞDÎÞÖH/é²Æ¿BãøÀ6ä/êèÒVÑ8E¾ÓM0²ÓTÓß0P³èÔSÉ0âSÕK0î³ÆßùAæ6/TÊT +7+îRÍT»4óçÒQ+è3*¿D³.¾J»D:Z4:8îL*È1¾ç3¾Ù°2-¶-*1øîN*,3ÆSÓQÁ5ܽ¿äRÎÌôUØYå¸GÉÓÍó +¸@V×Xá´ó°ßÄûD@Vׯ»VÖYÝÀ7EÔV4U@ê2*î¸,:¾ÔÌûDÞ¾ØÐãDæ*ÔH:-=ÙÓêÕQâ*à0B*Öß*M0.ôVÕ +±*¼@VØXåTOùóùAæ6/4ËT³Tóç3*Éø>èѯ*,³è3*¶Ø*JÎLG/B8B+*RM*,406à*è,È6XÎß3CÒI>C6B6 +=*Òá/íÝJ=>Rî½å*öü/DG¿ÀýÙ0¾õIÌ×3òùÕ¾õYÎíÝJݽ¿¼ýÏöü/øû5òùAæõYÎí½,ܽ¿´åßîãLï·Ø± +Å̲+¾õê×á3HÏÉݽ¿Xí>¾äöâ:¾Ò/¸.*²¼ý,ÛÚR*·AFC»¿ÈGðܽ¿Xë=*ã¾NÁ@ô³û·-*Û85Wæ+Îúü/¸ +¯Ë*ÆÁ;×Oöòéù½IºØ*ÞÚ½ñüGF¹É¿1ÎöùA滯ÀéòáåØîß½IQôûÕÍ@*F·êÓ±åÌëÙEðáÅäõYÎùà.ÝÔRÏ +ÖÕ±Õ¼ËàñµIW*öøäU½PGùóÖÒRϳíÝJõÃ38TÑPÂYÑä6D³Í½½Ü*îð»YÜÊø³àËN=RíÝJõÃ3ÜTÓT¹¯µXÊ +×NQIí¾¸úñÁQXÚCôÛß1íÝJõÃ3üéÕYUOIüùB/1Yý7*¶Ú¿F2ξÁ¼û*Þ;3FË*99üµá½9ÉÜDúîû5òÜBßÑ +OÉ´=ò߸3ÑëêçI+*ýÈÀæñïűPYæ-¶M,µ;ÞÙ±ÝËÖìßε0Iºý,ÛAPö¸óè³òFBXß>ù·öQ*Jê2»++8*¶S +5@Î48ܱÁî*ºöÓù>:JæÜC,Íüº´ß¶-¹ìBúîû5òÜB+,0*Zå¿í0´GÚø=*:2¾Ý¾*1î3Cì×»ÃÐX8¶86¾Áü +;ÅØâË;îïßW1¼EÖñçÁõYÎùà.åÆ;M¹Ì+Ù/ÊRùHü.*.ÀêA:Þ+NZ»úöç4ĹJÓJ9I8+**.J*îQľºùPGæ +JFÙ5¼öü/¸YÊZC±äÒ@.èüÌÜûù3*ÆB=YJ¾-ÎZøõíÏ?úäÙõ·÷ûùÀÓµ¾:.Þ¶íHûµ2>R5¼öü/¸YÊZSÑPû +?.èúÈÄ»ù3*¶Ë9<°¼ËBåë¼E*OÜH¹äOÇí´6BÀëÙI×K¾¯U¼±.@E´ç¼ÁJßÕîÁõYÎùà.Å5AXUÎ1áûUJá¼ +.*̱7/.Tö¿7Ù-¾ê2,/È<áô?8RÁ7ùðP4N¿7ÕÆJM-ĹBòTß1íÝJõÃ3üöïáÈTQüEöC¿äP+HE´çÁýPXW +SößùA滯ÀÑH»ØÐäÎ59@ÍWÝïO*î,*Ð;RÕW¹óâÅK¹ÔÂPÍPûû5òÜBßÇ9I8WVèÍ;ÉäÑÑ3åÙ:îܽåüúCó +Öææöïâݽ¿ì5=ººùôùXÐÊø²ø6Dõ²+Þ½±QÌÙ´åÊø<ùôá½ý,ÛAP¶öüûùA·RçâÐØóº+¾»U9IDéL<-9*ú +õYÎ5é.ZI6S/½éMÃÈ>ç,¾TÏLO͵ÀÁ8¾û±Éöü/¸GÁ*ö-<õJ**ýGOºý,Û9L*ùÉ2RZýZ<ܽ¿XR;6úøö÷ +QìÍÄIüøîYLàùAæÌïÆõYÎíÝJݽ¿¼ý,ûüýîû5òùAæõYÎíÝKܽ¿¼ý,ûü/øû4>Þº**Zñù1¾´ÖÅ-ô>¼û0 +¿TÏLÇ4/øñù1¾Äæ1éÇñäÊA.T¯àÚ3²RÎíI*¸ÀIÖåËAWØòéµ2>Ä>Ç<äËíI*ø,J>PÈ= 20030907) +%%CreationDate: Sat Sep 20 11:23:48 2003 +%%Orientation: Portrait +%%Pages: 1 +%%DocumentFonts: + +%%EndComments +%%BeginProlog +% Prolog copyright 1994-2003 Trolltech. You may copy this prolog in any way +% that is directly related to this document. For other use of this prolog, +% see your licensing agreement for Qt. +/d/def load def/D{bind d}bind d/d2{dup dup}D/B{0 d2}D/W{255 d2}D/ED{exch d}D +/D0{0 ED}D/LT{lineto}D/MT{moveto}D/S{stroke}D/F{setfont}D/SW{setlinewidth}D +/CP{closepath}D/RL{rlineto}D/NP{newpath}D/CM{currentmatrix}D/SM{setmatrix}D +/TR{translate}D/SD{setdash}D/SC{aload pop setrgbcolor}D/CR{currentfile read +pop}D/i{index}D/bs{bitshift}D/scs{setcolorspace}D/DB{dict dup begin}D/DE{end +d}D/ie{ifelse}D/sp{astore pop}D/BSt 0 d/LWi 1 d/PSt 1 d/Cx 0 d/Cy 0 d/WFi +false d/OMo false d/BCol[1 1 1]d/PCol[0 0 0]d/BkCol[1 1 1]d/BDArr[0.94 0.88 +0.63 0.50 0.37 0.12 0.06]d/defM matrix d/nS 0 d/GPS{PSt 1 ge PSt 5 le and{{ +LArr PSt 1 sub 2 mul get}{LArr PSt 2 mul 1 sub get}ie}{[]}ie}D/QS{PSt 0 ne{ +gsave LWi SW true GPS 0 SD S OMo PSt 1 ne and{BkCol SC false GPS dup 0 get +SD S}if grestore}if}D/r28{{CR dup 32 gt{exit}if pop}loop 3{CR}repeat 0 4{7 +bs exch dup 128 gt{84 sub}if 42 sub 127 and add}repeat}D/rA 0 d/rL 0 d/rB{rL +0 eq{/rA r28 d/rL 28 d}if dup rL gt{rA exch rL sub rL exch/rA 0 d/rL 0 d rB +exch bs add}{dup rA 16#fffffff 3 -1 roll bs not and exch dup rL exch sub/rL +ED neg rA exch bs/rA ED}ie}D/uc{/rL 0 d 0{dup 2 i length ge{exit}if 1 rB 1 +eq{3 rB dup 3 ge{1 add dup rB 1 i 5 ge{1 i 6 ge{1 i 7 ge{1 i 8 ge{128 add}if +64 add}if 32 add}if 16 add}if 3 add exch pop}if 3 add exch 10 rB 1 add{dup 3 +i lt{dup}{2 i}ie 4 i 3 i 3 i sub 2 i getinterval 5 i 4 i 3 -1 roll +putinterval dup 4 -1 roll add 3 1 roll 4 -1 roll exch sub dup 0 eq{exit}if 3 +1 roll}loop pop pop}{3 rB 1 add{2 copy 8 rB put 1 add}repeat}ie}loop pop}D +/sl D0/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{/colorimage where{pop false 3 +colorimage}{exec/QCIcolor ED/QCIgray QCIcolor length 3 idiv string d 0 1 +QCIcolor length 3 idiv 1 sub{/QCIindex ED/x QCIindex 3 mul d QCIgray +QCIindex QCIcolor x get 0.30 mul QCIcolor x 1 add get 0.59 mul QCIcolor x 2 +add get 0.11 mul add add cvi put}for QCIgray image}ie}D/di{gsave TR 1 i 1 eq +{false eq{pop true 3 1 roll 4 i 4 i false 4 i 4 i imagemask BkCol SC +imagemask}{pop false 3 1 roll imagemask}ie}{dup false ne{/languagelevel +where{pop languagelevel 3 ge}{false}ie}{false}ie{/ma ED 8 eq{/dc[0 1]d +/DeviceGray}{/dc[0 1 0 1 0 1]d/DeviceRGB}ie scs/im ED/mt ED/h ED/w ED/id 7 +DB/ImageType 1 d/Width w d/Height h d/ImageMatrix mt d/DataSource im d +/BitsPerComponent 8 d/Decode dc d DE/md 7 DB/ImageType 1 d/Width w d/Height +h d/ImageMatrix mt d/DataSource ma d/BitsPerComponent 1 d/Decode[0 1]d DE 4 +DB/ImageType 3 d/DataDict id d/MaskDict md d/InterleaveType 3 d end image}{ +pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie grestore}d/BF{gsave BSt 1 eq{BCol SC +WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt 2 sub get/sc ED BCol{ +1. exch sub sc mul 1. exch sub}forall 3 array astore SC WFi{fill}{eofill}ie} +if BSt 9 ge BSt 14 le and{WFi{clip}{eoclip}ie defM SM pathbbox 3 i 3 i TR 4 +2 roll 3 2 roll exch sub/h ED sub/w ED OMo{NP 0 0 MT 0 h RL w 0 RL 0 h neg +RL CP BkCol SC fill}if BCol SC 0.3 SW NP BSt 9 eq BSt 11 eq or{0 4 h{dup 0 +exch MT w exch LT}for}if BSt 10 eq BSt 11 eq or{0 4 w{dup 0 MT h LT}for}if +BSt 12 eq BSt 14 eq or{w h gt{0 6 w h add{dup 0 MT h sub h LT}for}{0 6 w h +add{dup 0 exch MT w sub w exch LT}for}ie}if BSt 13 eq BSt 14 eq or{w h gt{0 +6 w h add{dup h MT h sub 0 LT}for}{0 6 w h add{dup w exch MT w sub 0 exch LT +}for}ie}if S}if BSt 24 eq{}if grestore}D/mat matrix d/ang1 D0/ang2 D0/w D0/h +D0/x D0/y D0/ARC{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED mat CM pop x w 2 div +add y h 2 div add TR 1 h w div neg scale ang2 0 ge{0 0 w 2 div ang1 ang1 +ang2 add arc}{0 0 w 2 div ang1 ang1 ang2 add arcn}ie mat SM}D/C D0/P{NP MT +0.5 0.5 rmoveto 0 -1 RL -1 0 RL 0 1 RL CP fill}D/M{/Cy ED/Cx ED}D/L{NP Cx Cy +MT/Cy ED/Cx ED Cx Cy LT QS}D/DL{NP MT LT QS}D/HL{1 i DL}D/VL{2 i exch DL}D/R +{/h ED/w ED/y ED/x ED NP x y MT 0 h RL w 0 RL 0 h neg RL CP BF QS}D/ACR{/h +ED/w ED/y ED/x ED x y MT 0 h RL w 0 RL 0 h neg RL CP}D/xr D0/yr D0/rx D0/ry +D0/rx2 D0/ry2 D0/RR{/yr ED/xr ED/h ED/w ED/y ED/x ED xr 0 le yr 0 le or{x y +w h R}{xr 100 ge yr 100 ge or{x y w h E}{/rx xr w mul 200 div d/ry yr h mul +200 div d/rx2 rx 2 mul d/ry2 ry 2 mul d NP x rx add y MT x y rx2 ry2 180 -90 +x y h add ry2 sub rx2 ry2 270 -90 x w add rx2 sub y h add ry2 sub rx2 ry2 0 +-90 x w add rx2 sub y rx2 ry2 90 -90 ARC ARC ARC ARC CP BF QS}ie}ie}D/E{/h +ED/w ED/y ED/x ED mat CM pop x w 2 div add y h 2 div add TR 1 h w div scale +NP 0 0 w 2 div 0 360 arc mat SM BF QS}D/A{16 div exch 16 div exch NP ARC QS} +D/PIE{/ang2 ED/ang1 ED/h ED/w ED/y ED/x ED NP x w 2 div add y h 2 div add MT +x y w h ang1 16 div ang2 16 div ARC CP BF QS}D/CH{16 div exch 16 div exch NP +ARC CP BF QS}D/BZ{curveto QS}D/CRGB{255 div 3 1 roll 255 div 3 1 roll 255 +div 3 1 roll}D/BC{CRGB BkCol sp}D/BR{CRGB BCol sp/BSt ED}D/WB{1 W BR}D/NB{0 +B BR}D/PE{setlinejoin setlinecap CRGB PCol sp/LWi ED/PSt ED LWi 0 eq{0.25 +/LWi ED}if PCol SC}D/P1{1 0 5 2 roll 0 0 PE}D/ST{defM SM concat}D/MF{true +exch true exch{exch pop exch pop dup 0 get dup findfont dup/FontName get 3 +-1 roll eq{exit}if}forall exch dup 1 get/fxscale ED 2 get/fslant ED exch +/fencoding ED[fxscale 0 fslant 1 0 0]makefont fencoding false eq{}{dup +maxlength dict begin{1 i/FID ne{def}{pop pop}ifelse}forall/Encoding +fencoding d currentdict end}ie definefont pop}D/MFEmb{findfont dup length +dict begin{1 i/FID ne{d}{pop pop}ifelse}forall/Encoding ED currentdict end +definefont pop}D/DF{findfont/fs 3 -1 roll d[fs 0 0 fs -1 mul 0 0]makefont d} +D/ty 0 d/Y{/ty ED}D/Tl{gsave SW NP 1 i exch MT 1 i 0 RL S grestore}D/XYT{ty +MT/xyshow where{pop pop xyshow}{exch pop 1 i dup length 2 div exch +stringwidth pop 3 -1 roll exch sub exch div exch 0 exch ashow}ie}D/AT{ty MT +1 i dup length 2 div exch stringwidth pop 3 -1 roll exch sub exch div exch 0 +exch ashow}D/QI{/C save d pageinit/Cx 0 d/Cy 0 d/OMo false d}D/QP{C restore +showpage}D/SPD{/setpagedevice where{1 DB 3 1 roll d end setpagedevice}{pop +pop}ie}D/SV{BSt LWi PSt Cx Cy WFi OMo BCol PCol BkCol/nS nS 1 add d gsave}D +/RS{nS 0 gt{grestore/BkCol ED/PCol ED/BCol ED/OMo ED/WFi ED/Cy ED/Cx ED/PSt +ED/LWi ED/BSt ED/nS nS 1 sub d}if}D/CLSTART{/clipTmp matrix CM d defM SM NP} +D/CLEND{clip NP clipTmp SM}D/CLO{grestore gsave defM SM}D + +/LArr[ [] [] [ 9.305 2.791 ] [ 2.791 9.305 ] [ 2.791 2.791 ] [ 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 ] [ 4.652 2.791 2.791 2.791 2.791 ] [ 2.791 4.652 2.791 2.791 2.791 2.791 ] ] d +/pageinit { +35.4627 23.6418 translate +% 185*281mm (portrait) +0 795.224 translate 1.07463 -1.07463 scale/defM matrix CM d } d +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +QI +%%EndPageSetup +[1 0 0 1 -36 405]ST +B P1 +NB +W BC +/mask 7488 string uc +î½*¾î1:9*¾I±.ç7*¾Íæ;ÞÁ>9+¾Qò2ZJ¼ÂÆæ:°ê¿Ú´>á8¾ISëRÄG*9ÐØÏ6¹¾8ú1ºIÅ*-´ò2>Þ½¾ù· +.*ZQÅÖ:M2*ýÜýMºýYùüýöûýïùýáõýÅíý9ÝýI¼ý½úüýöûýïùýáõýÅëî;% +d +/sl 59176 string uc +î½¼**û¹I*<ÊϽÐ.ëíı*Íç7ó¼ðÉ+Í7Ùë+Þð4Ä»üÁÏá²ðËÞ-üØù+JåäìÝI½üçÚRùíJÔ´²û±Åëø+** +ü/ÝÝüûYM8Í篲¾ìQ¼Cîׯ9-¾×Oݽò9ÞÄK*¶ôúüÚÝÜXÏ9J,@ÕZêñá4+ýUµNî+½Ë**6öüâÕP,*À1Ó¹ +**´°ÏÀ0Ò+ʹ½ýØW±ÎÏüµ+CE¹²;ʼñÄ<¹ê6â7çôݽ@èN*¾RÏÊõ4*.àW×RK1è*ôì9ETÔCîæ-Z+ú5¼ý +üµYÞÏÂäø,*¶ïïëåµÍ»ö31RÔVCõ1ܲ@*îÒò?ùõ9JÇ<ÇâÆý,*ÂÓWçVK-ì*ÈëÝ»æÒÅHÒ-=îôó³øå** +VåÉ,Rôúüû=ÔE4ïT9*¾õ»ÛG¼÷êÏIQåSí»;,RóõüÁÅä,*ÌÈܼ¯íÄÚRϵÊÕ¯VÌKWëÈä4¸î06úüPÉHçÎ +8º¾éÕñåçÕÕýܺöì×¹ôçÍEµüºà>¾·õõõºYÞ;ëI5³+¾ÝËëFúûößUÅåÉ-ÅÛô+3JÒðù±/D5*¶TøôGË¶Ì +×°ÜêظüÌ4³³é¿4Æýý9@Kº¾:ëø.-P,Ì,ßÛ?ÖýúÙ9¼ù1+*Å.ïè+ZXõûùé½74×.MäB¾ÛùÑó÷òÃ0³J-F +½G½GÛ¸÷ûñݽíéÉåüº·ðÕÃPÊ,ܹýüùüõí9F>»**ܵµòîïíYíG/.**=/,*¾E>îîùÍý?½üÓI5*ºQçâýY9ý»Yí»¶ìá;Ýê×R¾ +øõÅC7*¶TøÞZ8ê.0>¼MŵýãÝ°³é¿4>Fø¼ýï9ÑùIßÈÂúVS/Ç62ÎÓßüáý´¶:D¾õüÅ3ïýà=M-ä3XÃLö¾ +áÒ÷ݼRÝÃ1ñų=Úý½ASÁà.V?úGA/FFT-úRñ½è2Á5²°ýýÝAÌCJÎ6ïNB.GUµ.àZ8µÙçÊà8**=õÕÉÏä- +Kè¾/*GáóB**öíÕ1*ÞÏé÷Ìá±ñôÜÜûùì¼»õÚ±9íCï?4ÊZÜN¿2+¶Iì2*ÚK+ìÛ½½Uóéñº**âÏõîAZD²È +XU²×RÞôõ½×°@*2¶òÚé,×ß/Æ8íνðÃVë¸Î,1ÎæýýI>=¼à1÷ɱí8º¾üÚºøýýÁ01à*¶ûýÇøûYýé+?¾K +³0îýÇGæÈDýÕA²½¾*åAÃû½èEGÄ3BâÜ7÷5T:øýÝS-Uõ½üTñ/DïìûýAѺÞ2ÅÀ¯ÖîÀ@ƼYóä>ô**Ô·ýÖ +29VR1*F-8SúöC9*¾û5üùô5µÂØ¿1ýůÏ*óí +Y°D*îÒòóDõ±57EºÄ>QÇáèãèÔ-?¶Ð´8ÆNéøÝ,ANï+0:óýÇÄ,68Â+.FØÇýé=¸K/Á0,2ΰ¾ç*/ù½±L÷ +ó02æKE=ýQüZÊ*¿Ãýëâ¾âýýP7êãýñ/?´J¿÷üáõó¾;ì/á3¿ÎûÆLÜ2Ä/6*Dìý´ßÇAÂÒý6ZXÅÎÁAIV1Î +I¸GýõÙ²éæÝÁ9íß½ÅìØòíý2ÜK¿5+.*ìϾJý¼NîT4¶PúöW·õìð+*Þ·Ú¿åHÜæJÏñ0XøÙÀÃ,ô¼ýÈ9±+* +QO9Ú»Ñè>SàÉJÁOÇßÅÔ´GR¿E³Ñ9Ûüå9²Ó÷*Íýý=ûýÃûýY>»632:Þ¸ý=VùÝXLAÒîT>úýýÚàÄõIX?Yë +ÜÞÇXI@³ÇüÕí>PÞ@»åÚZÞ¿ÚüýP7êìý»5/AZ×DMÀûýùÎ-Ķ;6-4Å.ÀÐÝ5úZB°Ô*6ÛýSÀ;Y2SýCJÛ8Z +9ø-íYÛÌÉ-¼ØóñëÛ½õìÙIµ»D¶·,íÎÞ3FÜM+·ÜûÆÞÓ>ZÉñåÙÈHûâ-*Îóì0ÍÛEø*,ô8۵ﵿ/V>ZäùA +ãB5*:TøÚÃ,¾µ+*T:MÄ5Wë¸Î,µèPC¸F9I=¶ñ+H¶ûý¿íU´ËÝøËB¶ûý1M,³¯ùõA±àóÀ4XÃÀýýýݼíø, +ãñÛPÈKM¼NCòÔVÒZWD2¶Êñ4²úûù·ùø²1èΰùÁì@:á=Lè½1¶î¸,*¾±Õ±Ë¾1õûâ2ËöîËòOÃ,´Gùôí +YO+µºüÚ?íÜ?Ü**ÅAÈæíßÅQ1ÍçQµDèϳ.9ξ+íY´ßN*Jè2¸1*ÎO@TÖA72J¾<Û,ÂNÆÎRøÔ*7úîøüFá +/ÊöÂÆøí+ÌÝÌ9,8ÚöðæÑUõéÝÍÝÜF÷Kõ°P+ÓRÚ¶U×±Iß³µæWеJ;5ýÊ×ÔáÁÆÔ¹09Î*ÐëÍüá¾Í**Öîü +ÍÁÓ=öî3;óÝ-*ÁÕYåó*1Hú¶ñä͹òéÝÉíÜÛ̸CѾLòëÙ?éÔíöØ1F149D°æÚ¿¼Bó41î34JèûÍQ.ö/** +½ëÝýݲ÷.Ü3Ð0¾éÕ=ÒâF-*ï±Xä>Þ/±å´OØò¿Q*Þ/ü±õ6ATÎRKXåêû=ö./ÒLîûÏÑòÕý+Þ+8ìÝÝâZð +øÛ×Î8º¾ñÕM󲳸.*LUÛÒS*UÕ´ã×ñUßôýô×=1ÌF¹ùÞÝU=Þ6·õíÍ·N¼»9*X:TóûùÉêÃ7Þ¹ÄKGR+ÅYÅ +øËùô2*âÚ¼SÑ*À0ëÈ°ãSßÙõU6½ýüê;E0Y¹G*æXâÕ-»Ö±ºÜÔÓÙÕYTJèûI¾ÉAÙ°ê¶XùÄù@@Pܽñ-¾µÔ +CÉB´òðëÉéŸI2îïáÔ.5ÌÊ3ÚHºò68OèAYßUµÉ-¾µÓS¯CZãÔÃ=ôòû9µâú.5´ãÕí´Ç+¾±áPë×°àÀû,Ü +ØF*úÕYÞÀ7±@ëDÞ@E8³½;2ݼ18+ùõ9*ùÞÁõ9ÅíI8ݽF¼ý¶úüïöûáïùÅáõ9Åí½,ܽF¼ý¶úüïöûáïùÅ +áÍ4žèÐ+*ü½ê¶.*Ô6*ÞýéÄM3ãç.*¾¸<*îç.ÞC*¶RÐTãÈÓ5ÔR8Ç;KÃ2?4â2:T+R1îÅ8OÐ.T?WغAW +Ú²ïØÃéÔ,*Õ?É@V×X߸ã¯ÓÀÓèÒSÓH¿QÏ5îS.ÞíÄMå/6ÕYÚ¼ëøã·÷àãUAYÚ¼Ã+Ê-2YÛ,*È,FB±æÐO¹ +ÄU?.?4ÒRÍP*¾Ò4¾Ø*1O4ÓWÚÀ+UÄÌOÑô4*ND²;*ü6رåÊKU´6÷ð³U.¾î,ôµáÏÅðÂÈÑRÌP·,ÎT*º>R +ÎJÇøòç=./èÑRÏè+F>UײãèóÈ=°Ò2Ï3*Z²,*,Ã,Â+ÆLÀL.²ÏMÅ>³4?ÆJ¶ë¸,*L5Õ2*Õ?ÉÔUÖXß°/± +×ÈÃèÒ2ÄÝ7ÅÏÄîS.Þ¿EÄ.ÞQÎH¿ð>*:è2HË-¶RÑZãà?U@OÈÆÎK¿2/4YF-î¾6/T.RÏJÁL*º;KÂ,?°Âç +=-O4ÓV¿Ä85ÔWØZßÐ?5î4µ>ÕXÃGÄF4VÐà@ü´FàSÍL·D?Ø2O¿,óÇÒSá**2?öB0*LÃ-ÖRÐRÛ40òÔDCT +NR;LÃ.;Mµ,*4Ó1FC*±ò2ÐP?Ÿé@XÙ¶ç¸;*óÅTÉÓWÓ´×øÙïRHË4Y>ÐJG5*2ó8*úÒVظë¸ÓÇ;B>Ø20 +Ë*ÞMÁ47¸²<*.Ó1FRâ2*¼NÇÑQÏVã4µNÚ¸ë¸<*¯ÅT5ÕUزïøÙï2¼O/*XêC´éØCѳÈÊ4ó¼*¾Ò-+¶+*Ü +èÔZâÐOÙPWÆßÃ*¶µéÚOÑ07D±½E8ÃÑR=*ð3Ò±+2*2023*Íà,ú>V׶çL?Ç;;ÒR.¾C<âRÎ4¾Æ¾÷*S²æ¿ +**LQ*Þ5OвÇ=RÓZÏWضG3*ËÃUÔW×´ûë¶ÒÑHÃ,³TÒ3*7OèÒRÎHË-6+:SÏJÃ,¶RÐN×ÈÃ5ÕXæèÐLÃ2Ë +**MQ,¾6CT>0îÀ*à/ÂÏL+*Ñ*ß+HSÑSÓZßà³ÉõØÓUAYظã,ÜL*ü´FDQÏHÃD-:ÍJG3¾-*:ÍHÓ.öRÐRã +ÐÃÉ@TVS;KÃ0C<>à2.Ã<,K1,J47T+ZÃ27L>*:8î,*º;MÆ>Wð>4@ųU@Xظçàã°ïÈÓé³F¸IQÖ±½2ÞX +ô²ù*ç1JJ**ÆÉZNöãåDZÞ¾2ÁÖD8Þ*>ÖÀðºÞ.Þ¿6**?,QÙJÖ±½B2A6*ÛTKZ*Áé*ÇWNÜ*FøSÐVËDOT? +T;*¸èÒSÓRÏT-JÒPÇ47*Ò0*FÔWܸ÷°ÃçÏ+*¿.S6*BD2R8¶J,±,18Î>Z²ÎºÞAøãÈ@WݼSZÛ¼ë,Äé³ +Fø»*X±Õ.*KôбÇNËôÎ2L-æ³84é+6ð?*RÜè¿Æ¾4¿á¹¶Í62-¾ßã÷ÜÛ+ÖüIÒ¯0¶²Úº÷ðóä*JÕÙ*îÚº7 +±@WD²ÞVÓRÍJ/*BSÎJ»4+:ÎPÓ,Ò*ÆRÐ8N¾Â4óÞ*XBK.¾ÑLóÈÓXãÒO=æÐ/*ÚÄMÅ5*Qó9F²àì³KáìØÍ +,KêÌÙ÷éËF2æÞE?0Â+Óíñ¶ìW,Sì>úàÌû¿9Ê22**ÌòJ·*;9áVG*°åÜ@*?ó*ÅƶLî*8êÞ2+:Ò32ÇDÌ/ +À,*Z:Â9¿Å56-*;ÑÕD¶¹5¸¯åÜ¿0ÎíZ*âC°:**Ò3:ßJ2FèÃõ*żTÒSÏNÇØ?U/*߸OT*JºÞÏ*öSÕ°ï +à³UÒNöR;MÄ6Ã+JNËF»°òà,XFÞ.ÞÃ-FÞ¾U@îÈJðÃ4@VÛJÕé¶òæAöðÉÀÕ5ê»ÄÒê;ó+ì2ʯD½.¾HðÉ +S+ÆÇV,R³ò0Ì8+Î-ê×Iö¿-:ºÐ-Ó+ÀSÆ-îÁê×A1ÎJÛð¿ì7-Ê2F¾-Á**òé±,²ØÁÓ=-żß>P/*嶿ì5* +¾0Àæ-.NR*Ï7ðõòS*,*Jé-¶V=:¼NÔSº4¶òÍL¿4óSÔÈAÛOÉ>UØNSZDí³4ÒQÎLÃ,óÞ*F>RÑVëØÃ5@-? +ÇÏKÏÀXÆ;LÖ<ÄòÖ2+TÂæV*>4=*ÃX6¾:Lòî*Ìæ¾=¸òÆÐPÐVï¸7ÅÌÛ:;,KÛ.ÎÒBBH°1*ÂÌ·**ÖQ**F¹ +ô¸+*ïà+ZRû¹J*;½*ÞòWÇÞÇ8Ò0ÎS*:HR+Þ¼PîÆôP,P.8Þ¿O+¾äðC+CNRãÞÓY:ñ*6ÒA:ÝÒ4.OHæ0TË +8ÓïÁ5-¶;:LßöUÛö8îÆOÃD?È>°ýY*¾õÕH´FSÍLÃ,Ð8*JJ¾ÙȳUAX×JOðÅ6CTâÆ;MúÛùýýí+N2Ï×* +³>çÔ²å²/RÜÖ¿E0¶÷*0ÇÐRÎÌáÏý-¾ãMå*Øö¯ê×µZóP/ßôÀMJü:89úÞ7FÔ8Ò0Öð±2²W?ZêÕ,æÑ8îä3 +*ù3¿-¶ZÖ;+J°P¾ÂKÖúV:À,ƶ*8XÂ+70*ßôÎ?*JHÄM¸ÞºøÍ+ÅÌT?TÒÎûýÍݵÍ8»¸ô÷ýõõÏã»,OTÓ +TÒæÒSÒà*º?Xܼ+ùOÉ>×.ç+*ÆLô,8W6D¿0Ã/¶MÆZËû½ÜTK±+K*ðæÏÁ-*ÄXÆ¿DÀò2ÐMÈÌáÏí/*Þ°ôC +**âê×áØÞ´÷Ó-²ü3*³ãEÕ,:=Ú6ý½ã´1¼:*¾@*ÆûN18öÎÛÔß+ð¶*ÒÚ²1+ZÉ4ÌÞï¿2F¾-÷Eø¿=ÐíAüF +5Âî,43JÒï,Gèø·Ò*ÊÚæ0Ó*ÞZ**ÀÝÂMè°?ÞMÛö8îæ½÷ðÓéA¶ýý¹õAñãáQïZ÷ø½ýâãPç4X>ܸ/+öXß +ÌOÉ´Wد@5?RÐL*ê>RÑö/FD?².·49îÑJ3±ùýúÒ¾Î;PÎLǸ:*NTÒRQ>ÎÞî*7JÒÎÀñ¶òçEêÞÌÛ>*¿C¹ +ôÞ¯³R:â*Ëñ@Ãß¾2ÝQSJ6T÷¼ä´1¼:*¾ú¯*îN18Þϱ9V¿Æë***J3ì¶G9B+¶:-*JÃÞ+FÖ6.°ÒVWJI+Þ +º?24**+08ãHXÎ30.ö¾2ï5ëÎø<.ز*ÒɶÂJCÖ-îÞÙ¾M3³TRÍ=ùýýöQGÆFïS6¿ÛýIøùF5ÔVÕ2>²¹-8»ÎJ¼.?TÂS@EäVÖUO.ÌÇÓVÑ<»àOÊ5É?3ÏL +6;ÀÄ0CLâæ·F´Ã2FBä.1*ù@14¾Êµ+ÅÄTÒTÐ8ÞÏ5ßÀÄ7FâÀýõùÝè3ÂçAÁW»±íÒ÷X605AíO6ÛЯíìÊ +ï4ÒãR;Mñ,8û0<´Æ×»EÔNÅÅÕöýø±-»åï2ÐÒ5àÚÏ·ùåVûÛ¸âR;³¼²WÇçT0¼ËDÓWÅSJÐB°>PÎAï2±>Z +:åïµ+ÅBT>SÑL7F¸SOèú¯âæ@Øüý½àÎðÚð3GúI*îîÇ4U³UIäöûýøðóýµíÔ>4²2Û¾Må¿â@ãýõÑ@1:Ô; +ZõBá*¸ÄõüØ*ÝìòÔKÂôçüÍ>-ô¼F*Ñõ±N2ÏÏá0ÀáI=<åïRZÓ4+*ç,8GÓ?ºBËLOVUýýݱYµ,8ýíáÝ̺ +ØùüîIøUÜòØýY¹UÎÊÕ¹ýýï+<²îæ¹.Àú½½ìë춵ÌÑýÝYÚWºÜ/>ÔùõUýÚBÉüüIúÊÉ2/F¸ûô±QäÚÚüýD +ÙÔIÂòýýL÷N±ëýEÌ.N5G¿6¸óýMåÏ,åÎMæ2ÐMüýýê¶>Õ°Ã**´+żè4 +JÍZëÐRÛýµÍÑ2°Õýùô:ͯ=ÅìÚµëú:EÄHWMæ>Y1Ë»ýýÌ3ëKåíýýýùëýýáù¯YöJسôýöXñÆBÕêü99Ð9 +ïÐåÏSYýýõAåJ±û½òRPÚËKDz¸ý9¶7Ô<Å;PÈB³øÙïR¹óøÓUÕµ+ÅÜèÉKÒ¾ï5úÝýÝÛËÇT¾ýõôòÎR;-SÖ +±ØúÎX1KÔ-ÂO7Åó±úýý5ãCAîæåÇçûýüãÃú¶OÜæü9ݱ3Dìçåüýõ0R½E8ãÒT;*Hëýýè+0-µÍµÚùüý÷Qí>ïÃÈèݽACRÞFäåÛ×ìÐæú½åÛY²ý9*¾ñýüDéWLÌÒ¿ÁPWúú»º¾å18²ÒGà +0H@ë÷Í8Þðµ93Ï°îæµ.3=ÍûýôÕçí +ÄMÕ?éÔWØÆóÃéÛT˸óTÒ>Ù÷JRÖÖK.T?WXìPÛÀ÷PÛÅÆÉß±Ç>¶BÛôó¾ù¼?ö1Ç=³ÚA×±Õ,Ç,1<Íøóèʾ +òæ,èù0XùXÊHÀP·ÀTõ,ÄÙ±EF¼NÈDó°ýÍÛÊééèAìÊÒÌ6ïZúFàÒ3ÑPÍü´FøXÛ¶÷èÓU?NÌÄ4>UÔZßÀõ +CMÅ;RÖ´ßðË*G,Ó27A°Ú8MÒ²Ãøñä;ͲÆ5>QÒÆïá9CàÑã3B¾6·L´ÔJç<åÎLÌÇÑM¶ÞòSâÆõÇÁÆ*>6**1Lî<5L**òÙ¾IÁ6ZÞLWà +³AÚ@Å6Z:Á4FùÄÁ·**XYN4ã·ö0ç6á,@ðä;ìØJ<Ù¶=-C¿æ·FQî°5°ÅMQÓT?UÑTÇ,/?GTÒÆÏ**ÍÂ3>SÒTïTå·ðÃêBîÕ@å·WÖ¸ë°Õ²ßNX1ÌF·öúCâÆMÀ² +Æ9I¾2KðÅ.ûçáQ:LÃú:I¹òò+.ÈJºTX9G¸À26:LÄö/F¸TNÞÞ×=å8HBÆÏMÆ<Ã*¾P·Þ;?µÑñÃÚ2ÕM8àÏ +ZåHLÞMñS*NVôêÓ3.1T3ÚâVê+**¾¹>ÝZßÒìê+:òJÀ*HU:B@I:-YÖ+@ôêÝRÎ*ê/?2¹6P2U.Æî:88Úà +/72ô*8*ÀQÏÉÛ=Pê/õ+ê9òÀ±åHæPôê=Ë»Ð5ADðæE8ÎñÆàJ¿R,¹²**Ú*È2¾TÂ=ºÎ÷:6Á1KÕ=¼SS·J- +RE=/>*ÚÆÄE=3Ŷò=á6à8Á=·¶+31öîìEÌÖ³PÁÞÀUŶÑM³I1ýðPºì²ß+HF¶ÐÂïæSÓ°ÃÈÓTÒN¿R/*X +èÒSÓTÓгÎQÌÜù¯ÞÈ»½õÏ;üà?5áð³?ÓI¿.;T>2B**670ØNÆ8?41*ÆÀ+à/ÚÔÓêSø÷±2Ï:,·.Sß;CSñ +Q²*@¸>S;*Ó2QöJ;Ä0Î:²-QúÐÐð¾0Æ>µãM78Ná×ÇFøǯ0KõúÇF*Â1:ï0O8»áÃàBô¿æ?½ô2QVîÞ¼8üã +ËöÚçÀ-º¶îN÷àÏñØÓé@XÙ¶ï°Û¶ïà³0Î.Þå+üìÕJ°ØÊ·¾¹á¹XAÉÔÄܺëS½8Wðò3¹,ó3ÒQÏHË,FD¾+* +Ý/ð뺼.OðÒBÞÕëÂȯÎ?¸:¿Q1Â*JÃ,ÆRÖð¾MLõOöà7àÌûöÏíMB2-2;*2,<³¾òê/ãÀ»PJ*ûVCî4¼±å +¼ù,ô*æ¸ôà9ºÅ;µSãݾ¿ÏFÑÌÓܲAäAXµLJÖQBT5*¶M-*Þ+JöÊÉà0ÁѵÎݾû¿XZô껵æ»:ÝEÛ¶.ÅAL +îÆ>Îõ0Ü;ÄU7¶,>+*Ó2õö*6FïÉÈÈ-D:7º97FÆÄ*¾UX·ð=1öñN8XJàËUFøÄ5Ã;ê;6JG¸Â=@¶ZI1ýðN +º8³KïßÃ=ã5ÅôUAYÛ0*P+¾Õ=5î9ÏUÄäÅÌG»ºÍ¯ÆëŲã4MQ¼´=GÂ@¿4OL¿T+*ÐT+*Ò÷¾J¾3*@³UAOº +*K4OF8ÞÈúÂçLÄÍOÕÚJPëÈ-87¹V½ÌI¾=ïøäSFÄ220´U-æ÷¿Z05NBX?-ZKJ2òXG=ÛGÚî,¿Ûß¿DKôËä +3¼û289îÆ>IÜXïÄ0.¾//28D/³Tíßé¯GZ½Î5K*@Çâ@Ò9ó,V½,YAÜïÈ>VÐǯ:Iæ¾5J·°>ŲÐÜõL1TT8 +>,?:ÌûÚ,ND綴<żðÞ58ãKKàËUFøÄ5ÃKê=ò¼õ2QVî4¼íúãÇöFèÀàÁMUÆ>8ã?U1+*T7,öÉ÷ÓðCá0Í +J¼R;ËÜM¸¸ÂÎ99éR>¹1+À±Ì:SÈ>,ÞÏïÙB;I¯Æ:÷NÈF?Ù´è>¹XZBPÜÐÃÈÑÅáø´é=²-AðñÚ.Û¯-åÍMÛ +ºÜÂ0ß»<ó7Cá>çÑUÇÞ;GõÁèºÕOÍU.æÁ¶V;îÔôºÌXE¹ôÔãñ.±,ÌúÇÈê×AÞ74DùëECRK.ÒºES±/2óóñ +N±Úî9»:¶·Ü*ºÝÁM,ñÒÛ¸ÃED:*JG*.ÕKM÷Ù;P¯åüJAÀ*îä8ÃÖ±½Ï5Ò*³¸1DðäEXÇ,ëVLP³¾>Þû,RL +*T;ìæâ¿õÛæ¿ÈÈ-ÃZ³:ðÔ?²ÎÐïæNN+ìÅ@ÎZï.¼ÌJà3ÈÚøÄ=ïÃÞCíûè;ÌÖÞ70HùÉ5ï·Ò/Ã1ÅÔ:SF< +UÔXÃG8IHB*R³8Þ³ôÍÉ3ËHÅB·RÒÜKVÄÆξ¼ºW¿Z³EíW×åâòRßÀRKNÂL,P³RöãÁ+KÖÜî9Gö+ÂY¾ôÊ/ÚSH=Ø¿H.á.µ²**Ú*.?³3¹ò7RHçÃ:*;HKîó +/8ÎU78HÞÒEí¶O4NºÀÂÓ+LéÀ5J3+RöL=AÜõL1TT8æ¾óûß+à,ÂÆÄ2¾5=;Ø@Fؾ-?¸<1A:ïà?Ì»áÇà +ÖåZ2Ö¶ó2QVî0-ÍûãÃöFèÀàÁMUÖ-FHÖ*íBÛ÷ÏïÄÛLãYëس4ÐKÂ,¸ïÛµE0øðéõìGGLÃ>ç±¼ÛÁÓS±Xô +ÛÕ/ðöÛüôµí²GERR×Éê-æPAíøòÞJÃ<0íIAùÁ2ØÞñC¹ÏBç4AúHSM>2ÑVSÞ¯ø?*é9ϱÀ+JçYÀÇ:4æ +ó7åYÕÞǵN±;3C*>½E,QÑ;ßMNô7å½:ÇCâÖ8åïæº÷ðóU.Þ*ÞFÞ¿/¶¸¸ë½µ»BVÎì;I´à´Û¯éÀ +á=1Æ<8ËÀ+MìPB/YÚËQRÕUêÀäOýÍ +=Ê=Yîî¿çÙáú±7õàÐضA³ôî¯èüãA>äÏWÍE8+Ô¾àC*¸óTÔ-0*Ö,*ºÓPÂôöï.ÊHµDú¼ÓÚ>;Fìß4ÀLPÍ +LîØùæ¸àîÏNîSÜüAòèêíÁ»<ÄÁDɺÛ=Wî.<ÔÔH¾MI÷ÂÈD¾6ïòì±ôSAÚêMWä¶IÂ6·FYñ5B,SÕå7Å:éÆ +¯æ¯.Þ3Fø±+éçø.ÇÎHXåËP=ÇUP@Æ1>8ô:A·0OLòØÍ8¶òG½ÉÅV´TÉUëûÕPÞ.Fø¹Uâò¯²5Uó<¯É¿MÍà +ãÌLȺ¸ÕçûÎ-8Ô;A·2·OÈNÌíCP6NîéíÄM5äUÖY;*>VBZ?*H6ÖYß¾7²úÔÕÉóÈ?TÐJ·ðîLíÔôQÞﰻР+DÁDÇØåG³ýÁ0Côã;@EãC/èõHV?³T÷ÙãõÇÏá2Ò¯8ô¸Ä°ðæúÝÐÁ·Ñæ=SMÝõòÂÌç@úÇ7KÌÚÒSAç-Ãñä= +Æ@MF,*CóÞ¿9âÑ.*¸ÐͶV31FîÌ:ÌF¾öZ?2J0øÆã<8í*μ:1AÉîJ/8öJDÍæQ¶òÛ¸óðÓUÕYÜVAYܺÓ+ +Â1ö¾ÓÅY.Ó4@WXéÔWúTÅ@0Åñ=X@ÐD¿B»ðõÚÑì±0Mû°Û8C¯âöC÷üUÕLäÒØâñÃÏá7·4ôó¸Ä¯ìöÊýS¼· +ÁR=RXòùËÀ.¯´HÊÙ+²ç=R¼ñ-³µì²L´I¶º,¹÷>Kï*JÍè=°*ÀV¼K»¶ÏËGùMÕÎ1ÐÝ7Àᱶ½9µTÎÝèB-F +àVÖZßÈ?,Z6:4Z0Þ¯O¹ùüY´ÌN÷U׸·-ùûVRÌPÜî¯9ËçñƱDÕÛ²G¯@èõ¸Û²;MåÙèõGê>¯JÏOüÌ5µMR +;ÊìIôN=äÐZSÞÏ»ÏJ¿ÛÆDYÖ+@ôØCSJï,ìFK1K*ñ8³ß,GÓ +E¹³28Â2FR*,¾G¾Ç+å8JÎËê×QD¹Ò*EÑKZ+ù5T**P;.**Ï.à8Á=<2L¶¾/ÅJZ.Jê¯2B¶*8BDÍ,JàH40 +;Cé2àÇ@>µÚîHåÄ<¹ÝîºZõëÉG·àêµôH4T>ÓQ7Ï-µë¾G°àNß2,5SãÌàQº:F¾ÖùîϲB:ÞOP×ÄûÓÐê×É +Yµ1/Â1ô/0-+ÞáÇAÊ:BôøÆ:籶6³6WV+R¶6.îÙ4ä@í×SÄ0ÛMÐÂ×íÜåQ7ÝQºVM±øBØâé³I»Q6Xñ×ùÄ +èæ7ûºøüæ.ÊÍQ-ÅîäÞ;TÍæO7Ê-ÅüQDÇì=.QØÎÚï,8ÛëOêçïó¿GCµ1Úõ¶äÖùBRÏåðöÚêI³-²DøäK2æ-Eí?EGï¿î¾<¾Ç-åì-ßéM +ÖùòãÖõܱû²¾Zåü,6À*̺ß.+¯QÀ¸ùÚï»F¸XÖ²ßÐ?ÉÔV°UÔS*ÐOUÔR*½ãUKÜæã5íëÁµY8BJO¶îZ½àá +HDñåϾMAËåÉHAß±µë³-µØXöø¿U÷P¯Çáñðä:OÊÉéó4ÛÒ·÷08GåI±úºÑÉÛµÕGÛ¯ÛõY¼;MB+É+8˸Ôø +Ð7âçóê+ZíÞ¹JÞ+FøóêÃAÎîE¹ô¸ù,ôÀ°ß8;¶Î*+Å=-ÊG*í7¸9âÕÒJ:.9.¶K/Ó2A²öÂ.L18Zß;2F:¾ +2¾-îÎ2***:Jø5åJ**ûVå¶Ð/ã7AîæÕÉï6ç¸9ê˶:íQÖ1ø3´êÖ¯Þà*VB¯àÂGB*9@6ÖYÛÐãéæ»8Oø°Þ±Ë.÷J¼+ÊäÅÌL84?¯0ëã· +=ÆÑð1=åÄóN+ðîLô5²æD½ÐÚ7IÉPçLµ:@YÁQ×ÂÛæê¿âOÏ,å:ÖG¸4·Oá,ìúSZÖɸHäB·PSÉ+8WÑ·ÀYI +×FUJFùóAî2×±íëßÀÖEë»+ùñ-ßÂ2FR¾¾áÇ¿¶*ÈN°É-FA3¸1,¾/1Òî0/Á*XòK»L¯ßË;ʯFZÄ-î:6ê@ +0Ú203ÄUGá;IÆì8ÌÞϯÍåçÞæG¶<Ð1¼Õ64>ºYæ¾/=2×ô-1NÝÓF·à-¯Ê=,1îÖ>Þº÷ÊÒ÷¿0F,-îïÆ=À* +2¸ÆÆXÆLYßÏ-ñóÉÕYݼ÷<ܼÓ.Â+6*öVÍ47Ȳ3=OÜæÎLÄ8SØ.Ç-ÐÁÅ;PÏJSð¿îæC.3ÏMú8D³ìÚ3.Q +6ÍM2:G¸*S,ãK·TÂQÌIÄ4·H¶úJ,?è=NìQÌKð0Ì,âÆAT±ÄVúòÚÞ;ÐÌã*ÅøJîðº+ö¼¾»Ô0LìØÌÞÏAíP +4>±Ý»=ÁÎÖÙüFKÂÞ¿,6FÄ6Â-ÎËG4G*.W.Ì÷ìêKGÎÀ-;>/¶9.ÚæÁøÂàÀ5MJ/Rѹ-Å1ÞKWë÷FÛ´ôAÌß +*:½Ðð2à7I6µF¿M<êÁñR*OôX¶îóÆ/1JôçNËÔ¼ìÈ×=¯êÚZ9ÞÁ.Ê÷¼äÆGî+D,ùÞ2,;àùCFÐн>Ó02;Ü +Y¾Kö*ö¶â3K*>ùòÅ7E궶õÈÔÞ+ÃV0¶Ò-I¾ö/æÌ-ð±»½¶¾ö,8GÃ.ʲíì> +ß,7ÃEù52èÃ2FR¾¾áÇ¿¶*·OÅ6OàÂS=P¼ÇÐMÆLP9üÑýÙAÒóøBéºK¹R?É+8=J¿-S¹Ì*ÎúÃ-Îé +EÌå¯.ZûS2NÁ/3ÒÞ3KN,;åQ*Êå´;1Á<+9ò74FÅMË?UÔX×°Ã>ÀV¯*;ÄLÉÔ2¿¾*Í?éÓUؾ3EäÉ+¹óÇ= +PÊ@·PËF³ð²3=OÀçÐS*Ø>çÐؾö¾Ö+èÒSÑ3À2¾R¿Þ¾;*åNÉRïçÁ-9í±ÝºòÃÊKÔÌë28ÍF¾ÂÉ@UQâÉ/S +.@öÖº*VKWô5ÅÄ6×°ãä*J×D¾K¾R¾5:îèÞ¿=QÌDY×ú?UÓXÛ¸ãè?¯ãè>Þ*J>¾ÞTÓHîä/ä*JÓ-JR5îÒ +·Éæ<äºäö;E³,ÇÈPXܶúDÓîF8¯AZÛºó4-*Üä*ÞÕM-îݼ÷øÓêسêFE¯ÕLÃ,/4>2ó´*à.ÞÑE32MBPR1 +¶¾/8ÈÐKÀ0+T=0ÉS=1ÏTâÖ»ì¶>ضG2*Ö³,*´Ë,6-6+¶XßÊGÁô6BTøÇÑQÌF»¸-*Ï+¯*µ>îR-Ì?Jξµ +°NR9FµìÞϽ.³À³ê×Ý7ÅBéÔVÛ°ÓF¸WÚ°çسÉÔW¯ÆAîÆ¿C¹ôêCYÑP7KÑD*æ²0îL*L0Q1íÊÞ?*ÂE85¶ +PÆ87D>R;MÆS>XàÐûã¶.% +d +569 104[1 0 0 1 0 0]sl 8 mask 0 0 di +/mask 7488 string uc +*ÞÀ**ü»ýÅöüÝôûýïùýáõýÅíý9ÝýI¼ý½úüýöûýïùýáõýÅíý9ÝýI¼ÓY-%% +d +/sl 59176 string uc +äÕí´ê9+*Ð,¾é@XS*¾ùD07D³ç¼ËLÍF»D,¶<*ÞS/Þ70Ò2Ú/¾-AîÌB³àÒÇ=QÒZA±æúý½ã8YÕ,VEݼWG +ÃM±äVC±36¾E¹ÔËF¸óæOñÙXãÀóèÓVÕâÓUC6*ÞD-2L¶´*õâÞ¸KùÔìíÄM.ä5BYÝì,¾*-@2ÞÁCáÄ7Ù³à +TÇSÐP*NÒR±*HT>SÐNÃ.6-Â5F:ÁÞ+>O5C³éü´FØWÛ¶óT-*ÚP+2AXØà*2AXÛP+ +ÚA¯åÐO±ÄÉ>òâçÑQÎà*ÞÑC¾Î*ö>8*D?/FB¿;*æ>À*FÓ+¾QQ0îÀ0öÑPÌV;¹@ëíÄMçãUº¯6¾öð?Ú¶,: +ä¶êB0*½;ÁPËؾM*?ÔRJ¿D/¾ÏHÃ,?è>JºJ?Þ¿5Ò7Ò-Ú+¿E@BNJØƯùÚïÆ´÷ØÓÜöð*,*úÔYáÌOÁ´V@ +2³,ö0*T9á+X6ÞâÞöÞ;F@2ÚÎMîÒÎ*Kß°0ËØÝ7ÅT7D´æÒO¹D*-*8ëضôì×ñÔÊíØOéÔWÚ²ï,×8.¾øÈÓ +U@YÖè,à.à0ä*X.¾îàóÄ,T+XÆ¿KUõìÛÝ7Åà5Ö2*öóR*ì*¾ùðOêC³éÔGñÝXÏDOè>SÐÊÒRÏPÛ2¾=32> +ÆÀ*à2À1à*046AµêÔû붺ÒÄÞéZïÇ*Ê/¶°æÎKÙ@WCZÔé?UÐL5ßÑD*A?4>TÎL¿DÃJÃø>T2MË,/4Òæß* +¾50:ÏJË?./¾Q+-ò3>Sá0ÄUDµçü´FTXݶûðÓU>J6*ôðÓUÕX10:4Þ¿C±ÔÊC²éÒ÷²äÂïÐóÈ?T³Ã.ÈÒQ +Ï*îSBïÇ-ÊFÄ+6JHZ¾=8¶N.´UD³èü´FàYÜÀ÷,@,*¼Ã,ÆYÝè*ÞÕJ¾JNI@WD²åÎSéäÕWÁPëױᾯVÒN +ÏûØ´êܯùôËEÝÔ7سáºÓ<ÃPÓDóî**Ó¶¾B*J?8JRϸá+ó*O.ÖÈB³ïÜûë¶òܾï,äéAXÝ86*ò41ZÛ¼ûð +?0Þ¿;Á@ëC±æÒDzåL9+Ù2*õ´ËE³éÄ3Ñã¹×ÈOÈÒRÐLÇRÏä*NÒQ¿+:è6Þ.¾ÇÈ>VßÚ +SùÚïRÀ+ÉJ*6ÖZâÎ/*Ö³éÖW5³F-îèÚSñÔëÙ²ìúױݴÏLO4?SÐLÃ<³4Ó¶¾ñ,¾ÏN +Ï,ôV¾ßíÄM40êÖ2*60ÊÖZãL+RÖZà8.¾--´6ׯßÄG±éÔ³é@Ü2ÖGB*èÄëÙC*Çôë+*¶¶íÌ;áOUÓUÚÈÓS +ÓRÓLó,*À,ÞÒM+¾ÔPë4ÄWVßíÄáçàD**@4*¾Û·¾Ï¾¶*APêC²êÔ¯Ñä×SÙP7E³ëÒçµèÜOé@WâÏUùäëE´ +íÚ³ñæÐ?Môé@UÓ¾Ò,*C³è¿-*Tï*¾èFÞ6¾ÖD´ÞîµáÏE-´VB±ßÄ/±ãÀ3=´0*ÆGB*IÄVÖZâÄ/5äÃ/U0ë +زêÒ;µëÜË**³Ó*10ÞÝ»-18Ú¶îà÷µêÈ3ñ³É@VæT?TÒTË+BTÓP×4-B+*ÔL*æ@°íÀ3ü´FеîÖÃ*À³+Á +¾ë6¾µáä7Ù´ëÚ³MëØ·54B/2DÞîàÓÁµYH¼;*ÔYܼöT*L,L-úH¼úüëñµ9IÍ1ÍÛ¶ëÎ/ñûûðãUÕYÝä*J +ÕÖ¾B¾¶*WôÌܽø*üë¶òá¾C-´6Ö°ßL*¾ÖR*IPÊB°àÄ7EPÀ/¹Z*ï+HÊÖZâÆKÁ´ËJá@ÂSÜ·EÚ>íÚÓ*¾´ +ï+,XÚ4*1õëD°Ý¶ëÐÃVÓLÓ,¾P×DóÞ*úÓSÚÔÃù@ìÙÝ7ÅÏÄ;*:êöZCïÃ*.1¾Zó*ÂVC²KDZ+Ü6C³ëÚSñ +P˵ÑÔ7ÙµèÜS=ìÔÃFÌG05,¶;ÅÜ7Ù³çÈï°ãÈÓTÃ4ÓSMK³Tã;ÕȳëÙ¶ìàûë¶òäÀC=Ô6Ö°àº×¯àÀ?MôÊ +C½äÊC°ãÆ7E0Æ/*ö¯âЯâ4ÕëÓüÑܲõÊ;Eä6AÉô6E³ìÖ/?<´íÚÓC¸µíÞÃùô7Ú¶+ÅO*ì7د޼óÐóÈÖL +?ð¶øBðƶ·5A²÷µáÏ0M@6C¯âä7ÛÖ¯àÆCÙ´ëD¾@ò¶î2Ä/2öÎÖ½ÑܺB¯Þصç·âÀ/5JîãÎK-Õ7Ú³ïÂE¶ +=*ÀWÚBáC¾**9QÌÚ¶ïâÃùäß³ÁÄêAVÖV÷UÓXÓ°Ãè?TúTAµîäÃEõµáÏÂñ´,ÞãÏ=õËE´êÜ÷¶ôôãÉõÌÛ¸ +ÌXF¶ìÚ¿ùäËAëûºùóãWÅôÉ=-ìÉÎH·îÞÙ2M9ÕìÚºúüïéå÷ïáÕ9ݼûúÓ*μúú/*¾½/*¼YܹñÜ·ÁäÊ-= +ô5ÖYá¼SÁöü÷ùåííÄM9´ÊÖ¯ÞÆGéäÏG-äéAZáÒ÷´ìØ»ñ´7D±ÀÊÖ**Iå@¶óëáŵíá±åëø°åÄõøºóÒ;ñ +?6Ö¯XÊEµíÚ·Ùô˲ùô,**Ú:¾D¾-+îïÜ»á07ÖWÔF@TÔTÛðãëE4õWÚÝ7ÅúêÖ°ßÈGé@6æÑCÁôêDµìÖËG +ø³êØSUÄV×»ü´÷òßÓåÕYÝéí9½úïÖIÁôÆóèá0Ë×°ÙâA³ßÉ1âÖÜRáÇùôWF·ï*:XR¾É5AìF·ïÚ³ñåÌ7- +PéÓUßêÙ¶ïæ»ì¶òâÊ/=PVD³ìFÙ´ëدñ@XFõP7سêÒOéäÝ»áôêCÁÃãùáºJÏó7Hýýüݽýùé5í»ØÙÔºA +H½øÈ;MêØ»5M¶;*ÓÅÏ*.XF¶ïÇï*LXÚæáRNÂ06ÔWßT-ü´Fø²ãÌ;É07Ù³ü7F·îàËUµ8ÁñÔËEµêÖÓ0¸µ +¯ÇÑQ50**ºNIàÞßõýI*ÞùÕQìÙóçÊC¹×ñæO5õËÙ·TìÚ·òâǹZ*¾XV¾ÑMQÌÙµïâÇMòæÓ55*óL-ÚE´íä +ÓMåìÚÝ7ÅßÁ88Ú·ïæÛÙõÌáÁµ9I»öðãñøì×¹1YG¸õÚH¶VÏáå¸/¾+*N=:ðìï9êÏåAXÚóêÏ9Mò/+ȹQ9 +H¼úúH»úöïÙÕ9Iݵ9ݼüúãUåèãÉ1YÆJVÞ×*úë¶>åÆÃ*ö²éØÃñÄëØ´èËE´ìàÇ5ÉòíܳɴËE³Uë÷ùFà +>F¸**2Z÷B·öøÞýêÏýÍÍÚ¶ìÖG=ô0÷ßéäWÙµðVF·ðäÃ*Â,ö·ðÞ³éôWE´*XBJº*ÈUµXG·1,Júýý×ïæË +GU0ËC³ëÜÃEø³êÖWáäëEµàËÙÎMæ@DJçËã¼½M7Aî¶0*7+ã6ÚúÐMõ9I¸ðرÍ@Ê5è=Õ6EµîÀ8Á9å8+Ú* +¾;RÕÄëE¶íàËMQäÃ5FJñäËùÙïæÍCÉ@ËD´ïÜ+´¿»äWE´êà*ÚÙµîà»ù@XFô@9Xõùí³¶/Å8*¾-ËX´ìWý +¿GøúæÅõä»øì½@9E¶îÀ7á7á3-3M9¯ÚµëØ»5ÁZòæÓ-µZJèÃùÙïæÓWÉÔëE·ñà·µìàÇ-18Ú¶*X¶ÍÆ5AØ +åçÇ5µíëöýÏ¿,õîB.*K>°UC÷5¹òõÍ=íë¹ZÄ+ÃÞº¯1X8G¹ôè×¹õ8ÓMÅX*°ÊL¾¾á±µÌF¸óê×-òü´Fø¸ +ðæÛÙµÍHº4ÍÜ»÷îß5-îõîçÉõìG»ùÚܺ÷2øGY½ÕѵF-Î2ÌÏ/»ºüØMíݼ¸ðܵÅXéI4NAíH¼üü?*¶½*û +÷áå9Ý»Ü9Þ½ûôóéå9ó5ÖííÄMÅP7Ùµïà·éäÝÃùÔ7EµíÚË+ö´ëÚ¿-åËÙµRZZøéSGÅM1*¾*<åÚXH8úIÖ +MQ9ûöìÚ÷À²2îÃL¶·ñæÏUåXÛ¸ÌìÚ·ïàËEÕìÃ,θüýýê¶òåлù@XE¶ìFÚµîÚ»-1XEè0ÌÚSN2ALJá/> +êÜõO3ÞïêÏüÆäA±ôúé8ûüõåÓ½Y¼ÒDÝÄËE¶ñäϱ1éϱåÌÛ÷LKÕXÚïNÞíÄMYPËØ´èÒ¯ùÄÛ¯ñôëÙµì09 +¿ÙBM÷ÔWR<Ú·óðóùQû÷ñõíI½ß*ÊYÜ +¸ðÞ·ù@*¾Ø»-õµáÏÅ=1XÚ¶ðäÃñíä×¹õXH»úJÝRIèÕ9ÝJI/A@»üRÓN0Åà+Ú¿Mý8ÜöÎòòó-2/0.B:Þ* +*2B**ýïÉåìÚÎ/ñ´ëE·½E8WÙ³éÔÃ+ö²çÒ³ñ0XF¶¯ÆÌÌG¹õîßÁÕÌÄ=Ù¼ýÈG<ÁMÕßïýýùá¹1íÃ5/µ8Ü +ºøð;»øòÃ+öº÷ôëÑAíÛ¸ÜÌFµéÌ;Á@ëíÄMÞ@êÆØWá@úæÕWáÄ7Ù´ëÚ϶ñæÓFè¹²ëýÅÆL°¶òPÖüýIݹõ +î7öÍëÞGÖ-ϵYί6¾êáµöRéÃéÔëD³íE8+ÙÎMçÄWîÏRÑ´ôæÝÃ5QìF¸óî缸ëùåÅK°¶òZ4ýý9ÝùõíW± +ÀæØúT¶ðæßÑQYÚÏîÑAîB,Jñã¹Õ8Ú´½E8ÏF·ðäË5-B1¾ðàÃEµXÒÏÍEÅìG»ú,ì³ôõ¹U:CF4FÖ18Ûùõ +ºëÓ±Å8ÛBQµõXH¼üü+.2*Ä+ö¾ý,06öíݺÞìíÄMGÕìF¸òèË,Â-*¸14C,ÞçÛÁAîíùî½OôÄ.Þ9ó+ÅÜÛù +ô¹ûûéÅÍç¹Õ8ÛÆ.³AÍܼ*/ÄEø¾ýú+òµíܺîìíÄMçäW.*²5/îêØ»éä7E´ëJÙD*ÝÔWÙ¾ÃíA½?EF¸Lôý +ù½ÝûùóÜÔH¼øðßUQ8¾5É2ÁîððóÙµÍHºöÖÛ¹ôâ¿ùÔëùÄMöÔî2Ø7FHZNî@0¾ÕWÉÆ÷»êË°ÁM·Ò½ß;ï@D +YÚÏá±ÅXÚµìÖWñçÔÃÉÅíH»÷îÛ¹òâÏEÈúÚï2ÜÃ*B¶ëÜ·E-ZíÚ·ñ´0:.¾Ù¯ñ3úûá¿<¹MIJèýýûéµýìA +UÂõÌG¸òæ;·íÚøø´îèãÉQ9ܹÜ8Û¶ìܳáÔ7¶ùÙïæéÓUÅXG¹óæ3¹ôà*à÷à+4*ÖÛµVÝáÅéP°¶ò6ÎûýÍ +ýúóìϯÀùòÛ¹Z1+4XÍݽ*ûóáÅÍñÉÕ8G¸óâÓ-óü´F,¸+¹Q-:òåçÏñijºûVÙN0ÅÌàÝýýõÁÝìÛ=üõÌÛ· +ðàÃñðäÓÁÅíI¾+ÛI½úôçÙµ9HCµXËàíÄMô0ìE¶ìÜ»U-*í8FJ/*¹ñôÓúûD¿N0żÁâýýó½åüÛ9Ü1ìÙ´ê +Ô³ñîâÓÁµ9I¼øFܹôî×±Å8FçÔWÓàíÄM÷0ì*ÞÏ,.IHR*OQôºü0MN0Å<âæ¾MòÜûùÞö,8¯Ùµòìã5³.÷À +7ÛÛ¸ôèÏ5å7Ù-ÙöÙï2Ú??,µAî2Ü7*ƶïP+²Û×òûÙU:CF¸>³ýýÍí»¹õ8ÕEµïà˱AÍèÙÅíܹõ,8OG·á +2ÐËÙ÷/2õ·áÏÞ±õT*ÞÛ-*R1¾úêÐÍYW°µò*Ú>üýI¼ôìû¹Ûòîçé1îJIB:Þ½*ûóéQøï5¼²óì×¹Õ°Kð×ù +ÙïÆéÓ±ÅXG¸ôÀ*¾ÛD*RÕ0ÞéÏUÕíWøëÁã0Åì0õýüïµýüÛá³µYݽ*ýûMüöë5±VÁ*ôÀ@0*Ä+ü´Fè¶ïà¿ +-1X>ÞS+Þ¼¶ãÝü>EFÈIñ-8GùõÛ¿ÜáÕ9IÚA9Hºõà5;F¶íÞÃ5ÓB.B0:îà»ì¶.ðL*NÚµÑ*ï*ãÆã*ô7IÛ +òÑÁM9ÞRýýûéµ1YO½ÒQíܺ+*üÌÛ¸ïÞ·ùô7Ã5Qì6Þ.ÞòàíÄM>AìF·ð,ô,*0+ä÷è,FEÇÌ˱ýé89ö:=F +<,Îü9FàöÎEìÁAä2éÓMÕìÚÞåçQÞ++¶¼úø÷=ÆYKÏÌ··ñãÍUíÜÛ¹åü +Ûºù÷ûñÅÖ-ÙVÓßKFøAÏýýÅÍÛ¹õP¸ãºù08P*ì>àÊL7¾îáõµáÏÖ¹A,*êÃ,6*ιôèÃ,Ö¸õìÓ5-îòæÏ52 +ÐRçåûøòêÑQ½üû¹IIHüùüãí0VÌ´¸0ÅüPøýýíUýÜÛÚÔ÷¾8ÂÛ¸ï*O+å0JèÛùÙï2íǹµÌÛ=+îòèϹµYÞ +»óà+JÛJ*Iµ8Û·,UHÓëÙUYìÚ¸õêß¹÷ôééõýIüêäõPB1ÌÞÏõ×ý½½ùìݱQÕÃØE²B´¶<*µXßìǹQìíÄá +×àRÜCMµÅ8Û¸ôæßÁRѼOÇYG¸/*ÜÌÚ¶óôWÒ3ÓÝH8G¹õèß½õôçéQ½½ýýñW²Þ11G¾MýñýýüóÙÅåì¸õ»S +í¿CÚR¾RåT/BµDTæÏùÙïÆí˹ÅÌÛ·ô86¾Ùéæ³ÞWõàüÄÊýèÚAX><Ò5*îôè×¹ÅÌÞÅÍû¸ôêÕ±1Yüº·½¼ê +;ýõÝ»ôÝ8×F¾MÝ9Hùöî³ÑNÄèÃ-µÞ*èÃ>@EθôêGÆ/¹ÅìÛ·½E8ÓI½ûú÷ñµ0Þû+²Õîìè¾ÛøÜß;õ˺ö² +Öò:ݺÑ,M-¼9I½/19I¼GQý8»úõòÝÅGøóúýõ1ݸY¶ÌLÒýüõÍíXÜÅÝWÎÒ¸óÞÛ¹Ð3îíÇTF·¾Ý7ÅNíÛº= +*8ÍH¼ôìÛ5·Òѳ7ØöóÊ;ùó·9UPÛ´OÇúòFºöðãÁA2ÞÑÓ@½FµóêÛùõïáÅ1ýü»ÝÌE·øüýñÅHØõôÝÝüöí +ãÑåÔû³¸PÎJ·AÚZ.ZT¯°¯¼KòÛùÙïæéßE1YÛ¹óê·µöò¿FÆïTæ8÷ìâů5½ÛØéÛUöëÜ·AäÁùÜËUåX.¾á +ÚÜݺïÔ¹-õìáåí¼ü¹û¼GµÛÝ=ÍÍüݽÝIݽûîÝÍ1íIÕÉòUJÀíè+¹Á/10ÞíϹÅXÛ·õèǹñàBü´F¸ºôìÓ +ÁAÍÛ¹8ZLÁ2OøVêÖáëʶîãÃYÙôعE@F÷N¶÷VLÍ/ñ/G¸¶Ò÷ýíYHõà¼ö¯íçíå½ýÜÁýÌØôÞ×MõõõÉéÝ9 +½¹öðûùàå6OéÖ=KVõÌG¹ôì6J²JÚï6ÞñÄMY1YÛ¹íò7òä½;²·ÑåáÏý¸áÆû´ôÉ·ðßÌDñéÞÇÍÃAËÙÁÕYá +»öð/òáýýýͽüöÓGZÖ¸+-ÝÜݼåíÜ»òà±õIíÃÙåÍÜùõñÙùàüú+ËÈòß·³ÄA,*èG3*ÀÕ8ÜÝ7ÅXZÞüõ³ +ÀQÌ5ò29KÖÊ3YôË÷¸XºöðÞÎÛ°áÈ+ÙäûDYµCÎF¶ó6àOWæù9·òýûåQYùëÐJûêÞãïùõ½½»Q9ݺíöóÕíì +ᎼCéÒUÏøÏ4÷3>2:J¿HZÞ¾,-86RîÇ*J¾++øýý±áÏÙùÆòñâN-Äñëò×ÑÖ=RÒ½GêâÅ9Á´Ú×±9Ú÷ZåË +9é¼ëÅåôù<ÞʯIùîÒçÍí9HôðÌݹﵯøU¶Ï1ãUº»ýüóYõâíñIíGú÷àû¯ÒPË@ëBìFÀ1íÛ5+JöðÛ¹;*Þ +X.ÞíÄMÕõ7I˽U5åêÓ<â´WN×Ò¯¹Þ¶éPĺC°ÞüD±áÌ3Ñä6ØÙü6Û¶ëç3±ô¸;ÑXG¼õõøûü÷ïå1ÅÈ8DY0 +ÌGʸÍñµíÏñÕÝHù÷òÅùÞNÁ<ë¶8Gº:í**Ú¹¯*1@Jîç5+öÙïæç?ùÔXáËT?ùêã9=/D@VâÜصèÇõ0Ìé×¹ +¼FEòèÍ;ÑôÈUÙäʺóîÐûZéÉ7µÄÊø¶õWF½ýûë±Qéó¯±1ÍÝÂS7¹ñ÷ûñÅý¼»îHBçÏ7çϹ1í¿*¶¹õêß¹A +ÍGºÚXH¹÷êßUA,*ú½ë¶òøêãMÕ7Gº>ÛS×ͽQõ»ÖSQèÓ°ëã¯M8êûÜ´Ûø²àÔE¹âÖA-YËØY¾ÜîYçÙI-¼Ë +ÒýéS?õõýýáõëç0òÕÎMÂ;»QÛÙáÉÍíüµÂ4íÚ¸÷ì-¾æÁµ,îµáÏ1VB:K¾+ñÏñø.èVT´ñíçG´ÞUÍÌ7Gû¸ +íËõVæÓµÑäêËõäûÖWêÕ;¹Ø6=<Ã7׻ϺXÙ½,MÀÓHòæË1ÓÆïäÌJY¸ËFɸÛé?⺲ZJ¿A*::¾,0FB2*10 +6B:ßB*TBZÞÝ7ÅZY+¾éÑõXض÷HäòSñÐ88û´ÞRÛTÞ¶C´>3ÒZ17øUÔX=PÂèÓPHìõJÃô0¸Ç³á8ÃÆ9Ù²8 +ò½üýùÉQVÐEºçJ6ÞF*>º**Ì:KÁ07PÒBÏéEQYHÍSëøóÓÑÝÝHÛZËäºFµJ +±¹ýÊBXÚÉòM¼,9ÇK7Þ7.ÚZ+Qæ:Qõü¯8²R0HúÙï2+ð-ÖíÝ3*Üõ9ݽûúï5+R*:ûø?-ö¾+/8FR:Ûµ¼XG +ÎQéüÃ7Ûå½¼³âæçWÕãµá°û´Ìݾ1ÞJ÷ûñÕ9I¼+0Ã0ðÍÝ:+çõíÞ¾íéÅíÝÒñýýMø*ðùÙïRõïÙ1îH¼=*Ò +YݻϽ¯*Ä9Ý»ûL,Ûݼùø+.RZJAö9I¼*ýÏ5åé»F¸²VÜáÙùîÂéXOÉÑN¶¶ìI¿ý,0BÌA.+¾»±2Q6;@:°* +ô//î»/=áYìîÜå7Å2ZI¾ûüG3*îA4¾ú÷ñµ0Î.Þý+>Æ:K¾ýò÷ºú*8F¶ÎÛ·ü8áÎHç´ÃÈÒåEÕÌÞ¿*38ò- ++8>B:J¿ý,8ÆI½ûÌÒKúKOàÃ*æÀÏõ9.ÖíYÄM%% +d +569 104[1 0 0 1 0 0]sl 8 mask 0 104 di +/mask 7488 string uc +*ÞÀ**ü»ýÅöüÝôûýïùýáõýÅíý9ÝýI¼ý½úüýöûýïùýáõýÅíý9ÝýI¼ÓY-%% +d +/sl 59176 string uc +äÕí´ê9+¾Ñ,ÞYJ¼ûú+>üúÃ+ƼýÌ6*üñõíݽ¯+HîJ¿-/4òQÍÝɵYI½++<ò,áÇÉÅìRºôÎÝÀ-30F-î.- +86RZJ¿*ÇJ¾+17ÎI¼ýüó502Ï21:*ù/DÂ/Zèú7ø5æýýUôö¸?ÞØ÷óéèSF¸Â0;HÂæ:LÂðÎß2¾E*B92Aî +5C°âÖÏàÁ,ßÞöKI¶îKÂ6A°Ú2ïßñ¶ïá4*ïÆï.¾²âBTJCXÒÆïLÂ+1Õ0ïÞ=à@î¯ûM=DúÙïRù+ê1ÎݽQ- +Þý÷ñÕÍI½ý0,À+*RZݺ÷îÃ*¶»÷òçùÕYÞ¾ÀîÞ2¾+*:Ö4:.Þ18>BZÞ¾,-T¾*+ôáåYæß¿LμûüÓ.ÞÜòý +ü-öµáÏ.êA,*úÃ,2½*Q*ÞÝ-/Zýü+.Bä1,Ì4D¾ýôçE³>÷ô/Jؼú,,F¶N05DF-*/5*8Ð7NRZÞJQö +õÞ<¾Ý¶áç/ÚUIY¿ûü´Fà¼*ù+òµè2,ÄõÃAÄ+ÂD8,¶½ý*4FÖ:ß¾BZI»÷îÃ*2»÷,8·Ü»øôçùÕYâ¯F¯êï +Êï¿+ÞÀ-146B.J,,Vñº?*Ö³¼Õý*üë¶Â-ý7>¶Z6*4.RZJ¿10J.0V-î+/8²öîßÁ0»Þ½úöëáµÍÝT*æÕ, +J*ø5Ð>/5Ä8Ú;*À2Ïß=**/á:M:ÂÞ½¯3;*¸îX÷01üùÙïR8LV+2÷5ù¹:*-+M,8¯LÃ7E´âBÏ7>BîÞ¹+Z +.-@6Bêæ;°ÚÖ;MÅ6GøÄ8C´êæ¯MÄÊïàÂ17@NBÜÇÖÎKÁ030Núôç5¸V-B+¶ñ*ZY>¾ù-2îJÀ/3<ò*ïË=µ8Ü»õú +E²ÝXÏ<ãèB¿QZâÏ1N¶ÎJÀÀÅI/+áAü´Fؾ,/4ºZ*=*-*14Z0Þ1H²öîßÀ*û÷»úôëÙÕíH¼Zí¿+F3*ÞµD +¾ø÷ùAÀç÷ËEÕXÛ¶ëæ·»ôÚ?áã4?SìSÕº.9@ºÖ:@>ö?øÈ?5ÍE8GßÀ.-¶8:0Þ1 +ç¯ôâÇ°à»óö÷¼ïÈï°³T>PXæÌNï.°>ÛEîFõN05<úÛï220DÄ+F*¾¹1î.9LÂ2ïK¾ûîܼûúï5-îùúïéµí +I¼ý86¾õéÅÍݼûôãñçºGÁµ¯ãÌUGèÊ7;¸òRîFµÔé?RÌHòøÚ7É/4ÐK¿ÚÍG·ìÖÏä:à--?5;¼ì¶V³º¼*PZKÂ258Oݼ +±ÄàíÝBMûÅYݼýöÃHÔ/Â1ö¼÷ÎÃÑÕîL¾ü7FÀF±,µÎõE1ëÕTÇ27ð¿øæ¿á07YýúßÁ4;XÂÆ;LÖöµá;F6 +L²03²öN+FKÁ2?TÚRï +KEBîI¾=*2îI½*ý7-FJ¾=-îôTãèOê×´îºÙXÎPãð/ÊAÍÓè>SÊ.óÏÁñÚ¯MR>³ùT3ÊÞÂ6Iüë¶â27HNöÎ +KÁKÃàZ߶*ZÖ@:H:8Þ;°Ú¶;MÂ-+°¾++,.BZÞ-¼F¶V92320î*ëÏL/é@YàÚÖVÎF·èâ3?ÅÓèÒRÍ:+à=ì +RóRTÓWݾ+ÅBÎJÂ6Cüë¶ò17HNöîßÁ/U*ÞßJáçá+N½öZLÄ5C°Â2.HZà3¾5À>*/ÜØ*½µè>TÒVã°ÃF¯Ø +>*ÞIËDOÈ=OÁø¶G¶èÎç-èÔ·³ÊRZßÂ6Cü붲5?°Òöê*ÂàÃ/*J¯J¾µúö;âÅ57@Ò/3@²Æ:ßR*Kæî>JK* +?FÆZ+¾AÒåÈÒSÑT×ðÒDSÐ>S5EÄ=Ä+*ĵ0*B0*Â3¶Ä8MÄ> +31=Ä;ÀÂ;*Õø2:Ä.¶Â2=°êæÏMÄîZúZ3ÞJïÆï+BHæ¾C*ÝÅXéç·ÏäMãEOT +ÒÆÏMÂ*·G²äηÑå;MPÅÒ´ùèF³JÀ3E´êöµá¯*¯:¯2ÞV¯ÃïÃ12Â49Þ;66Fºº*:îË-¶Êȹå6ãçX@ëE¼/-7Ä6ZMÇ;OÈúÙï2F0F0*¾ÄÁ*K*ïà +¯*¾¯ÊÞ+FèÇ7?LÂ2ï¶ïÃ+Ê/.FÐÂ2=Ä,öÅZA±ÅVòÞ°HæÊC±âʯ-ÅÙSñP8H¿:·ÐÒ¶GùôXß=´2¶>=Qüë +¶6+N5=T6+*2M+>àÂ5=PÒöà,úàÄ8I¼êö:KH¶îÞÀ-346+B/¶:+±+¼ÎáÔÇÑÝ05Ð8FÖ9CK´,*FÌF·òò3 +â2=9/5B³îêûE200E.GÆÀ4á;Þáå7Å;Â*¯Þ;J6¯F-R¶6.2ÁF¶²59XV+¶**Z¯Òï6Þ+0 +EÈÅ6-8K߶àCÞ3*ZB4*60F¸Á+7ÄXì·ä¾¿Æ=Å8îF¯Ñ@SàÒÇÁDã4A°éàÓ±ù,8NÖêB¶Î³î2²ÄË/+²±æàÜÔÝÉ*¾XÅÃ@Æ,Rô>FØÂ5A°6-:5?Ì+2Ä +69FJ2*Sê2°*¾¹ÚöZßÀ+AÜòFµØV·ÎI¿/;KÀ.5@6,Þ,QÝÝÈîJÀ1Hî¶*,0P3P4ºßÀ0CÄ.Cðá8¿M¾FNÄ +WÀQÝÜýùßE2*BõíÚ½1åì°B/>ìR¶Ò4EXâR;áB¾E¶.6M,À*ÚMÆ:MÄú¶ïKµÖZRÞÈ5-Äéé@û3±J¾.5¸Á/ +7DÊÆ°üØ:î/<Ì1Â,FH¾R¾4*¶ÖWîæ³øï±WöîIÌE.ü=ÒíI+îâËÒX<¹´íìÆ9IDE483áÄ3Á5ëàÄ6AHÏMÖ +öÞ*ÚáÆ;OÄòR;Lµ¶ZWíàÁíXëAI8ºµSÏ++,¾K¿ô°´ù3É6SKÁ113å+L+ä*Ä*Ä=9Öï>*IêB<âÆ:ûµ2Fø +³Ã4ûG¹Èé>H½ù7.*ÜÜ÷èÍÓâ¼G@?T²WMß;Æ6Ó±C8*N0?4Ç<Á-¾â·*I÷ÐãÉBUÄòæEXêUº¸õë×¹ +òâ·ÁÔÊöéÑÚòÞÀ3»èQE¸çD°âÆï6Þ2Þ×/.º@2¾Ãñ*̯MÆ?¯Ø²×°ÅÞÏÁèÒ3ïéÒCÁPëñííK*îð»YÜÊø³ +àËN=R7/Å6ðáÅ:M¼.C,:**JÔ,Ê+¶Å7MÔN×°OÇÜïLÃ;/=ÝÜÜéÕíÜ»÷ìÍ-õÔE9@ÙÓçĵ»ÞÂ1ñ;ÊRïLÉ +BÏLÃÇ6*V6-Î7CÈN×ðâ@¿MYÓTÓÛÖEåßVÐÑíKµòöå1ÍFQ±ê¹-TD48?áÄ6E¸ê¶4JB°F/*7YV¾Yú¶ðâÇ +;:î<1X´¾Å1ýÙïÝÅOY7RçR +¶FR.8Á5Y4¾±âæÏNÇF,ÅßÄ10*HÄAÀÅ1DJG¼êÆïáÇ+*üÏàÁý,IÅ9Ó9+*Þ-EÍ24ô÷ûýýý½ýûð»ºòØWÁĺôSãQÎݽ1; +H4¾E2¶ñ*@ïàÅ:-<¾â8¿MYÕXÛÄÌ-á?Ê°I1ý.*Â7ÏA:Þ+ÞáθÍ,Jæ¸öîßRÎ*9º*,*¾58BE2-:å7Úö¾ +¾,1Í7À-îß-AãAVJ?Fº*1EÎVîßAD>>Ê@**±,¸RQöîAËGÇ**ìÊÀ¾1îX7Ì0¾Q½HÙZÚ¿Gµ6RçR¶²¾Oк3ÑäÉ +B¹.8KÞ**ôFöÏÞÔÁU½,*,+ö52î*â?Iò9·ÒOÚ¸óËÉEÌJÛ¸+Jû/1Ê7JóèU9-òà->*·ãÓIîFTÞI;áGÙÕ +Rè+Þ·Ï5RÕü.78²+*B=Î<;SÅGNøî¾*¿EH**ìÓßç*ÎïßW1Í*åñ>7/Å°<â3*53ÐâÆ1-¾PвC*ÞQ¸âÖ: +îóø9À5/Å8**+<ôº43ºçÏùåÍüúóÞ¯¹ô»ñPWÆZJÀ6çàÃ6Y7J/¾ZÒB,¾KÔ²çðOÉ@¹.8GMÄ6ë·0ê@ZLë +ùö=*:2°Ý¾*-Zú¶.¯FD*öîNé?56:*ÄIî¾Ø+.*ÞÁOËYºJê¾91Ò4òïá=ÖäáÅÍÔZF*ïK,µæîJ<*¾C+²Ë +¿ó¿êÄ-Ì÷**¶Ô0µ+Þ»ùP7ÖWÞÌë/Ò²>F<Å7G0*.,F¶*æöâ+úMÆç¯LÌ2ïZôøÝ·¶+ű*ÆÁ9TAYÝÖ +M½½üøìÖCýÃéÁø-¶:àÂ29DÂ3;L612*î1;LÒöZàÁ3ÛKÄ:QtFÇðâ8¿MÍC±P³Ì+MøÉPíüû.*NÊÇA:*+ú +ü»ßè¿*ÉÂëWä¿+,Þõ-8¶¯-;*AHZBÞµíñðùµ/ѽÑíÏ?682*ÝíÎß+:F:*M²ïÉܱ+*ÞÏSÎí*>ÚTÁÙ*îÚ +µÍü53Zåè,TD48GâÄ;EÀòB°MöB0JK¼.çïMÅÏ*üïMÅ:MÐV×ÐÍúRÏLÄÃãõA×6ÚÀM1*Þ*9¸¶Ç.Ýòæûñµ +Íü¸îØI±ÞP3,NöZà5**496*JF5Z6Oв×SîæÑPËøɱ޴û<çÖµI+*Õ÷KLäÕ3ñÚëµ-Ö»ù=E¾¯¹ãÙ9ÆWë +?*å²E+ä1FZ*:´:,îà-0-Ú¯Þ2´íÔâé±ðÊÉÐß++Zù/D>>>2Ê,ÎÅMÜ>12,ÊíÊ,ë0JçÓDÚ/*Í7ٲݴ+¹ +ü?ÎÐÏî2FÜ-¾2á=3î;QÔNSÐáÂ3³MáóûY4î88Á*ò+8¾V»7½ôæýùåýüúòÝM¹Þ½ñøNRZKÂßÃ8¯àÁ2;Pº +BÏMÒæZàÁ9QÌN>SÈê<¶ò¶ïà³/øZM½/²êÅ*Þ·ÔϾJ/IÖ¯¹*Ö¯YìNK¿78ùG19Xë¯-ÙÆÐî¾-0*à0L*Xº +ÞZ¾Õ¶J7´êÆ7KJ4>OHF:*ZÝJÁÀÀ,¿S*ø6KÝð*;OßËS2ê*ÌÓßÏ,Ú3*77ÖÏßÎU/Ò²>FDÉAWÔ6+*Aé+æ +OÉ@U,P+ºPËEµì²C°N²Ç×û÷ÁÌÜï6¾@RL2EÕì9êÏ齽ܸîÖG5ôM?°ò2°âÆ:ÇâÆ9á*FâÆ:K¼V·ñPæC> +ÏMîæ9I¼VSóâ½-IÁáF>*XQÅ.38ß7ON³4HE´çÁýPXWSJOMßÏÁF3°NÆâê;¶òÒQÍÔÒCòFÁã±é÷ +Üí/öâ¹05Þ,ÕW¹óâµJ¹ÔÂPÍPÛÏîRL¼V+2³61*:í*¾ÅF×°OÉ?Q¼â6Aà.7ý±D3Þ¯2*1Þ¾ÂζÉïAGøü÷ +ã½IYGVÃLïà,¾¯*Õ¶ÏLÅ?SØFSVØê;¶òâÅ9Å@@óåÆOñçÙOÞòåÞ»ýÌû÷±è¯ÏÏïáǵ>FÄB<Ç:MÄ*¾Åáà +;*8ðMÆ8IÀ6SÐѲ×WÄ*¾ÉG,ÅHÜ +»ù»ASÔ°Ø»+W.õü3*ýW,·;ØG±èÉêÙ9ÝÏîRN¼6µ.9M6å*8.JîÏÁ6·°ãÉAWÌò9CøÁ¹ÛüÑNпá:*NJL3 +S³-8×úôîÁUã¶ï×*ÆÄ9á;ÆOÊ>¹.8GºýüûÉFÒ²°Së¸1+*ùÔH¼²ÔÃN0Ñ*ö7/Å,ñã±+¶.*¼=QÌFµè²SÐ +D>FHýÆE°Àá:*ý*îýýõÝY1ͽU7·ÐNÇ;í.¾É²SÑQËHµìê;¶>êë1ÇSßíøRÏÇðãÊC±øÉ>M¸²æBûüLÍÂâîæ¹Òúý½üöíáUêKC¸êÖ¯2¾µòÖÏMÄ8G¼ +âJ7Þ+*JÆ:M̲C*ÞSÈòÆÏKßóû5±4<ÁMÝ<óýýóÙµõìIõ +ÚÆ;MÄ4A,Äß*Á*ÔïNÉAM*ÆOýýÙN¿¾ûô¸MZÝWìöH8GOÇ>IÌ>·M*êNÆÇ°ºJR*ÖF¶òAM¸Òæ<³ûËßËêîæÉûýݽûõñÑM×7K¼êB**FÄHÀÅÏ*¼ðãËBµÜÊ÷ðíÄMA×ðMÈ< +SÀN3NÌ*ÆÇ;M7¯NÇ>UÜ2*J +C4*Rܶ*YÇðOÊDµì²çO¼úAÖûüRËN0żßÛýý÷Õ1½Üý¼¸ÏMÅ:I¼Â8M¸úÖ¯6¾½²÷ðPÊE³ì.Aý´FàËEµô +ÊC0¾µèÚÇñPËD96¾íâDZåÍKÁôòAW@NÊüüÄD<ÁMÏï¯ÞÏÅíüÛõÓ=WØÂ@YÔV×ð¿+öÉD½,7øñæÍîñíÄáî +Ûñ,îB±èÚ·±PÇ9óÞÞõýýM;CF¸?RýýÅ9ü¹÷8ºèÄ;KÄúBп>÷ÏâC*9çå*P*XVJöÏÛÊSÑä¶M/ǯ°ûôGOôÄ8JÒñýýáݼ۳Y +UÏá*¾RMæöJ*6ãÉEWÄG4ÉF¹.8»øîß½IµíݼTí¼û0,*1ÍFôè×±åô7ε?FØÎH½ðF-îI»øâç±QÎGûåÌH +¹üú3òæ**9ØÑäÊGOé½86ã0ÅXUûýýëÁ9ÝÛÕ7SÑäÊC¯èF+*CM+úäÐKË4³4ÒS*ØMîæ·ÝÔû¸´÷úùÑûùñµ +:*öùñÅÍ0ë¸ôéò¸÷óû+P·ÞÏÒºC,ÞVк×ð**×Nç°:J*¾ÕºSñäËBS¼Ò8±ÌÔÍEÎêîæ»ïýÝÝ»ôëËù×9I¼ +63ðMÆLÐáD*üÆ°PÌD·Ü6+æ;¶ò˯ÙüFø¹ûúO½üW.*¼½úëÊEÝÄW×Ê´ÜIÞ>1FØÉA³Ô6-¾AYØÊ×°6¾×²÷ +<äB*ÍS=åÌG·àVSOTÛGݯE×ßÏ1ùýý½ùîáµ´FMÄ>÷V*2ãËF9*JÅÞÏý»û÷¯åèëéAøõõ:*¶ûøç1Õúöñé +òµîí7/ÅØðãÈASÜF/R-*@1*VOÉC³0*öËE±ÄFêÛ¼ØÎO°µò3àûýI½ºôêKîÖL1÷ïáÆ:IÀ>çKÀúR*7-îMüò¹¼ýÇG<ÁM1,ñýýõÙUíÌ0 +ÙÊ·*:ú×,Þ¯äú3ÒRÎOÁÐÐJÅô2FøÉÎ/åGVùøô*¼¼***ð,¿ºØ:GDÚ/+í¼Ûôß·/Á¼×Óî2³ÜÚ÷°ä3*ÀS= +ä¾ÜC*½÷ÐPÌFµðêç¹ôò÷;ïøûõ+3ÇßÏUÏøýýúíÝÅõ¾OÈN·ÐâÇ;í.*Ûò×ñ¿Æô2F¸ÃÌ-á?FÙ·òJ»¼*** +À*FK0ÕÒ1ÈSÛ¸ÈâÓøÇ8úºQ+:Yå0ÌÊ*TýL¿,¸QØÎæÞïK:ô8ÀXèÓI¾+*îâ/°V*Å캴߶-¹ÔÖÓîRZÜ6¯ +²AYܲ×0Þ¯à²çÐãÉBYDËEµè6»âFQ4Xý½D.ÇßÏÅSùýýøëÙµQ¹GÈ6*:;OܶM>3Í9**¾:*9Õ·¿ð97?QW¾:98Þ*È5è1Fç<±-µPÞ<-AFß¿@6*.-¶ÀÉ+.ÒêJ÷ð1ZZæ:W*öïßW1¼E +Öñ¾W/ŲñãË;Å:±òïË+¾Ê¯Äα*¾ùò×ñâàõùÝÈê7G¾HÈýýÅíÛ¹÷̵ÞÏÊN÷J7ûâÇ>QÜÂç±Q÷Çñ.JÅÞÏ +Ýñ6ÖWÞÒ»ù0Ýñ-**:.*ºÅ,Jç6N-Ò´FJ*úHÃ@+¶Å7*çAPÞ¸5GÅVFß¿@FK-ùûÙ¿íAZZæ:W*¶íÜS-´5Ö +ñ¹W/Å;Ã*±²;ÞF1*CÍ6*ê6¶F-ÎG¯²ÅÞ;Z5¶Â½QñäÛ¹¼** +*Ì2¾;Èöãì×*-Îù,ÓQ¶îà.6г´?Å:FæÀM²:0¸Ý¾¿KÁ²+Þ¶ñHûµÞ;ZéT¶ÂJ½,/4òÃ+¾ÍQ0¾ÀD³Ì+öÌE½,OØòçÏIJçÐM¹.8¿Ö¾MóUDôêÙ±Q1**Þ3+:ÅMÚ> +1Z,ÈI×ïé¸ðà¿5=Î0àÌì4*ñ5W¶Â0âJ´-1Z**F·¿.5*µåÄWØí¾M:ÔÓîR¯ÜÂS=äÊA,JXäº**V±QÌE·ô +êC4Þ4ͽ6Q´¶òÖîýýÍܸõÊSåÇ<Í/¾Ñ·±åÍG»ôNI¹ôê;¶ò×J5ÄùµòéÖ=.**îÂ*æ±.ûÈ-Ö>/ÉESRZM +ïÞÃ3æ¹8HÚØáP³K+ÞÃB2-ÖYÞÞ¾ÞËMNA*ú¸òݶ/ÁÌÓÓîR¶àº;*HñäÊF³ìÊ·ñ6*ãâDZÚÏý.ØðPäñ·ùL +DD6Æç7û*¯ýýõYíûºäV±OÈ?UÔºZ*=*¼=QÍHÁü6È=.ë<¶ÒÍPEÜ5Bò6íöØ.Ð>å³B¿Ä8ºÕïçÍØ48á6ãä +Ê-*Ñ*²±PËïÄ/*++ÜÑQòUíPIÝèGA1Æ/+пM¿JWÞ;ÅåHüïÉ=OÔ*ÖÇ>QÐ61î>¯ðêç±åÍGÇåÌH¹.8Û; +èÊ-50û¸´È7ùóÓ+ÞÃ/ëN;FôêÖQA8Ê·³ZÒÓîæ¹äê·±åÊG±4ÌDí.*êº:*ÜÑQÍ9ñÔÔêÚåÕÝI½âãæê*3< +×MíÎìýýøã¹Ií7Õ²çðOÉ@WÌ+..öÉB·ü64²RÎN>æÍW,Åü3°çÆ3¹XG¹=+¾íÜëØ°ãÐ=MèT¶.I1**æM-* +JÍ7¾Éʹ¸òîæÍÑõù÷ùýÝ?ACFÞÀ6ôRXF6¶òí×ÁQFÑPËEãP˯ͱ*¼ñPÍJÉ8³T>B7-JNûýí9,ðíÐÐ8WC +±çÕôëGòÄÌùµîØKÅXW?ßÈU¹½¾S8¶²F³ðÂCÄ1°HC¾îÜ:¾-ýñDOè7W¸÷òæG¸ôíííÕí½¹Â¸ÊË8.ÌáÏ- +¿÷ý½úìÝÁõ¿OÔV×ÐOÉ>+ãZ¾Ç*½÷ñQÏIÁü6Ⱥ0ó×MîæïXÐÊøòåÎQÑéØ·õ:*öíêÇñÔëC=Á°AõÄÀMÉ×± +PÌ=*NñP˱*Õ,:2ÞMÐ/º×öñàɹñéϵõHý¼üê½ûÒÅèÖRî@8G5ÕýýåíÌÛ¹ñù¯âǿı*¿Åìû6è±ÉÞ;°YUÊ7?G/*ÊPÒÀX +ùؼÜÑîƸìò·,Þ¹üúç±QÍF¹<ÌH¹ÄJ4ÌG-*Ä+ºNÅ@÷´XûE¸ÅÜÛøôæ×µAíçííÝýýè99ÇDAÌßÏMLüݽù +èÙµõÓÛèÊCñOÉAãOÊK*ìÐãÉG¿87ÈÒ0GDòRÎKù.8÷øðÕ5íÃù´I+¾àÛÙöóðéE48ãRÎ;¹8ÒæÎLÃ8GØ>6 +7á-Ká,îåÌH½0G22-îAA¼ÒC¶òê廹óå͵19¼ºµ9ý¼ýüû5µæ,Ã÷JÞ3FøRôýõYÝGúô̸éÍJ»øò×±úÑî +ÚC0¾¾@³D**ÉDOè²çH¿Mã¼G¸ïÚ³õ>*:ìàÏõÈEÜÅMÕ×±åÍE·äÚ3³ðò÷=æ*áîHåCÑâÃ9¿DãDzðÚ×,Þ· +¼ºBÐQâЯ¹íä˹ÅüÛùøHÜüüúûýÝYØ¿ÁÒOÃ-¶òÕüýݽÜùõèdzÖDé6GPÉ?WÔV÷ÐOÝ÷ÑRÎM¿0ûǼü6ØS +îöùFâ·F4ÌGáóºQÌC·ð6D²Rý3>RÎF±0/èAðÁöµñ×Q¿ÀÉß,α*JZйº·ñîãàÛµõéÛ½YÝü»ùõ»½ûüûÙ +A6:;+áß/F¸÷ìã½Õ»@äÌÞ±JÏñÊ3=äÉAWÜâEÁ0?¸ÒRÎ;*îññÄMý3²RÎI»ôêç¿,?øÑQÍI±¸Ç:ãLÔÇìM +âü¸²å³ÍðÂ×<æF´òFµðêS;àŸG¶ñèϱåüÛ¹µýÛûöø÷åõíûÕIú7;=2¸÷ýýñ½í8HºçDYØÉ>Wü6ÎîI¹ì +Ê÷ðãËIÇRÎMM*,.ü´F¸ÏKÇ,OT>SϼòæÐLÅøÊ3ѵ>¶ÑXåÑ=ñôè310V×µàW;PÊH¯ô6×òI¿ü.4òJæåûö +ñêÛU1Iüº9Íü»øÚßÕÕíûýýü²ÖÊüûùûðßÉIY¸ÛYHîàÅEÇ·øN3·üýXý÷éÏSýì¼ÜÍYͼûèÝ1ÙôÁÉ5õYݺûúG¼õïãÁ1ÚV<µÄ;PÊIÇ8GÈÃæ*N*D4J¾<ûÙïBÀå¿4/4ÒQÔ2ý· +½ïÖKÚÖðTå¼·ïä¿9E8Ú×5Üê÷¯èÔÇ5ôL94NQíJÀ4çQÖñ-8ûýýùÍÉܹ@VQÚØ÷÷ùùñYíÓMÕ9¼·úøç¹õï +ãU9ÊðäÓTóæÉDÃ47´òMÇ8?¸>SÏNãæÏß+¾òñÄM¯D²úÌùV¸Cæµõæûñ0ãGâ4Aìà¾ûZäÁ?EÄúC¯åº¶¯æÕ +ÅYÔéÉÀʵíáÜÚÛ¹ôúóýÍÝÛûúÜÚ¯ÒAW<ó¹Ø9I½Ý¼ûõÙ9õöñÑåHHúïÄÛUÔSËô>S=SIÈ>çÐPË8W4ÆÄ*æ +ÐOÃD?è²ÍÞÏ»Y9ÝüÌ,*ÀÝüù8ßÐVîæÇHOØ>SÐ>¹øÛÉûXÃçZݶ8Îãк3Ý?úÕUäF×ñàÊAAôÐ7¹HëD÷ëÕ +GXÓ¿ë¼ôÚ·¸µ8ý÷ìîõÍÍìKÑÓB9Û¿?å¸åàéíý8û¼û̼ù÷ðÁùW¸ÒA?DñãÍPÉDóQÍH³èÒçÑPSèÑRÅ,Þç +@¿Mõ8Hûøüí½½XÆ1íJ*îÜ·ýäû¹¶ôìENÉ°¿M9T²æÎK½@>æSPó5DòàWIø¿BéP8Ë·ïßü¶Zá½=ѼVصÜV +ø¯èÉM9õÛËAý6¸XçËÛ±îÏËÑÝì»»Ýíû°Ñ.ÓG5å/ôò5¹öòùó¹ùîëÉý»ÕRϲòQÍ¿*XòæÏNÅ<7ÈÒ5OÈ>S +ÏNÃ8?KÇô2FøòßÁý¼ìܽûÒÜûø7.*8H·êÓ±åÌëEJíU¶6*îLÁÄÚÇðKÈGÛVâÓK5HVÖW͹BñçÌGE0ê=E0 +ûøòéÅKùæÆ·±´û¹òçH³èçÊMY@ÊØ=0éÖ´öôýéõÕÙ°òQÍÝÀA»éáÜÝݵII»õËôåÐMÅ@W4Ä0AÐÏNÉ@EÌ/ +ÂN´3F¸ìÕ³ÝÌWHýü²ýüú7.*üüúïÑKÙÌûDÛX¼»½¾S<¶NLÁ87·òL·Èº·°áÊMGXâÒ¯ÉHFAWÕÉBòè×O¹Ô +ê3Ñ@û×óâ׳ùæÔ1íXê<äÑ8@ïèÁÍÕNÅÎÑFÙ¶µùüíÑôVGPâ2ðNËQGíâÌYá¼úòäBLSزίî;?ODòÎ¯ï¯ +ÍÞϵÄëøñæíóñAû÷µ;*öûïµ±ìúD´ã°Ø÷ü*ÑE¶.%%% +d +569 104[1 0 0 1 0 0]sl 8 mask 0 208 di +/mask 7416 string uc +*ÞÀ**ü»ýÅöüÝôûýïùýáõýÅíý9ÝýI¼ý½úüýöûýïùýáõýÅíý9ÝýI¼¯Y-%% +d +/sl 58607 string uc +äÕí´ê9+¾Ð,¾òRÑNá*ºçÐNÇôº÷ÐMQçÑçÙȯáôê5í»ùõñëɽ¸Ð°÷Ð8ë×ñáH×RÎÇ/¹ÜÊÑ5V×?èãQM<ò ++ñ+òÆ×½üüÛºáNGÌÚDZO9ç;NÈ@³4?èÆ<³ø²+ÞB¾D³èòSÑRM,:èÑRûý½ã8ËJ+üõóåÅGUµYÝæÝÝÁ*Þó +éQIÚðáÍYÑÝ¿½õ1B?,Ö´¼ûظ5ÅâÒçÐϻβ/¾-WS±äÅ<·LóºCÝÜ»¸¯ÝÂGç»,/àêT´U1×=çÁ3¯ÜVè»- +0æïÝÀ1½¸ÐOÅ8»R°ñüH½ýýö±Áó¶Î-êÕ9ß¾üöWÂJÅRÏNÅ*ß±ó8ÝëÁ1ïâÉÏýÐÏ/QýÛùìÄù@@ËÌÜÓÛIÞ>3F +,ÏÁº@>SÏN19ÃæÏßÆÌñâÉ?SèVØÔ=´ëù³àTU8ò+C4NöîÝÄ-»èåÕ·íãÈ;MÅBÒâͳË8Gèó¸Xüüüùûù¹û +éK±»2ë=³îðÖÏöú×òRÀ47°òLÇ@W¸²çÐO1:¹.8ûOæÀñ8Ô»»¹*ýD*B*3ÙßÁ1-Þ·7FËÀFJ:ÇH¶ÑôÞ9Ê- +E´GP;úU½Ñ×/í¼Ûôß·/Á¼×ßîÖ,:ä2Ä0F¸ÐMÅ0Óç°ãÇÜVAº¸µíÔù@ÃéY4²BðK»ýô¸úñ +Çå¼VÙ±îXEùêÃY8â1ϽW9I¸ìÎýTóL¹4³4óçÑO+ç,¾RMRDâBDßÏLÃø>èÒRÑô2Fø¾Ì-Ý7ê¸öïÎú³*¶* +Sã>ýó?¾ðÖº°îÀ=.1Þ¶Ë5>YüÞ,ÚÌÆã+>AÒ9¶KØâ¿>.+Ê8æÅÄÈÑ0Àº¶Þ;D.+RQàXÕì/èÃIÆ-ÈYòÀÉÎ +Ûº¿íãÉý´0ÈݼïºIÌF¾99ì¸îÙ¿G1êT¶NRË@7´F-2⹿Äó*¿*ìñãÆ=³4/¹ÖÕäëØöäVO8òý.TNéÚôãâ +ÛòêË»Á@»ÕW1DïE¼YÍ1åVó<ûCÑæÒ+*ÏÂà²çØáS*RØND:;VÎOîæí·0ê@ZèÜ»ÁíÕ+Þ+ò´ßÕ5í*¼áìLå +;-:FÞºý0îùÃ/2.Öá,>,***.ÞÛÐ:.Z¸*O±:7ÎØï¾M²2,Î3Pýüû×À-KZÝûÎèúJAú*íÝ7âüûE´¹î¹õ+ +åùïâ+ÚµíÒ+Ù/ê÷ÛÔÀMR¸ÊQÇ8OTòæÏ?+̲SÐOÉ67/îNÇìºC=ãÇBûæØÂGí0ÌøYQ4<èß½?í¼ì?á +ìÚCéÎüü·Á<ÓPÌëC¯ÜPÓMËJá5Ä3»çÐOÅ074²R;T²6*.7Ó:LÅ0XÂTò2F,´+Åöõ²ìÚ¹Ý0*1ιBDê¸-æ +7;W±UC¾ï¾M5Þ1VÑ0Îù,ÓÅÐîJ1ZµL3ÝêG5¶Å0TKòìÛIU¶;.>ÙHA1äÞIÒì7OéìÛßØ>ÛOɶÜ9Q8Þ¶ñH +ûµÞ;ZéT¶6ÑE*2TF¼67*P-6ßæ5ÄFCîI±àÒ3±QÔ¹GðéݽÁPüº¯ÕÉ÷´ßÉÓüâæ-´-B@±ìÕ?¹Ü³ÉÌVC²æ +ΰ>çÇáB*W¸ÒRÏLÁ0GÈÏL58ÓæÎLÇ4OØMî2Â/FTÝЯåÜGù¯*J*××,¾E4J7ÂGÛ?*ä++Å@Þ×ââ0JôQ´ú +4éÇYNß¿8Öê8ŶS8:.>ݾ,QJ·´*Ǹ³äV´O¸;.ÂÖY.1¸ß÷ÖËÛ97àÄñB½×Ó<¸Ë9Q8*µåÄWØí¾M:ÔÓîB +¾2È0*Ê,Â>ÄA8LÄú¿Ï-*=-ñ,H=PÈA³4ËE×ÑÜÛú·ëѱýóRëãÁ1°éàØûóá»çD7DñNé3>RÎßÁ=ÅM*ï21 +îöJ**æR*:ØMî汿@6õíæÕ¯I1**îL+òG19X믱FJ*Þ¼¯+ÌðÀµ1ZYìN¿>ÖïN482*»ÄÎY·¾ÆÐî,4Ú>/ +Ö+J´.¿,Ï>÷Ã:Î41ÒGæ/íÖ8ôéàB¶ZäXáÌ/òÓJF8*¼GøYÚÀKåèT¶.N-øúçÒQÏLË4³T·4³W¾FWD**ÊÄ ++ÎÑQÏÄ*¾Ñ±¿Ì±PË6UðFÈÔý?»¹÷òßû<ò.ÍTÌûC¯Û²ëæÍLÁ09ÊBÖÐQÏP7*ZTÕTËD0+ÊÔJÒNÊÊ,ÐG,Å +øç²á¹+Á¼ýÄ÷Î*ÓÔÖOHI¶MÏ3Êò.¿FÈú*,ÂQYCWÁÄ8ºÕïçÍø48?SÑNËL³¸ÂÆÎHËD0:ä1Æ0*ÎÐNÉÄú7 +3Ä/Â-¶ÐO±ÜÒÇÏOËìÓWÝɳE9ûÕYÔ6öëÕP³¼.F1H-F¿ç¾-ÆDü:ðBøBFßÉ@GØòæÏLÅÀÏï*Ï3G,Åì2³ä +¿/Á¼GEãÄûøS***Q*î´0òóé3.*¯øØ/ð?+¿6ö¿-ÆÔÔÑS,ÅGEôçÃ-±X76³?FÈÎMM*.çÏMÃñÁÞ²>¾:?D +*JÀ07ó5ôE5B12,òK¿ØºSñNËEÛSÖÀQ5XÙóPêCñåÊCå0JçÎä×-¯øò+,>-*ÐK1F>æÍI½0/DÀ+Àô2FøQ +ÆOMPê·óêË.*¼ù´éÈ?ÉÌ6SUßï6¯î927¸NMÇ@G¹>QËÄGÈÏLM/P6åýÌF¾1?È>SËH»¸ò=Y0óTñOÈC3QÏ +NM8à0,6L8Þç,*¯4³ÚïF¾BWD¸ÞÁüúS>RÎKÃ,ÎG,ÅüWTèÏ?±@ûØæÔÛÁìæ·ý0XEòæÓÓ:7S²NÇ@³ø**VSÐMÅ@CÔ,.,Â1ÂE@9öÊDµP²Ç±Pί,?*¯/ÏÌ´>çÐOÉGå6M+à*ÊSÐPÇ0µ4Î +IM8¹.8GìOÈSÍü6ø²¸WEõ8,*ýËG·ìØG±°Rôðì9ZÈ-8ççÑRáöà*,*RçÑR-*ºèÒTÕXÓ¸ÓTÐD?è-@RL2 +*2D2>îTÏTÓT³åÍRKè°-ÆDPJÔT7³BP*Qå8,9Ì/¾SÃè>çöR¶ØMîæ×YFÒ²ðèÔUÉèÔ¹2*º´íÜ»ÅäØæCè +ØÖö0+Ñ>îæÆDWè²6J+*67-:NÉÄ1018*.FH¾+°Ø>ç2*RDÌÖÖ:ºBÀ:î¶*:Ò*Î0¶0WÌ=Ê»ÔGÆÒTå*L-FÈÐNáóÌ6Þ**çÐßÅàÒçöJ¸øÒç*,J¸*:¾ÈDW¸2B8 +JÍP³DÓSÒ¯.ïÃÞ²6ÞÇ>.KÂH>G.O0U0CÎÐKÅÄ*ò3FðóãYAÜ5õë¼,*ùµïéãÕõ2Bã*Å°>çÎÞB¾¸¾ÈMC +Tòö9>7;6<6ù=PÉ8**Ðï,KÆÁ.L>çÆKÇ4³òî6ß+H8FÀѯ.¿1¯ÎÔòçÑRÉ/ÂSÑAâJÄøí<¶ÒêÕMAÌÙôÌ, +*áWFøóWðOCÞ;>Gå5;6Ù5õ-N96*:71:NÉÌýÏÑQÏÄ*Ê+0<*Ï-/ß,+Å:³Ò;KÃ4óÖïúÞ.JVÞòðJLæJF° +Ê¿BWøJ@NSÐË,ÅÝåKÌáïÒYBYCJJJNJ²Þ+1Â/BÓTÓXW*ZSÕPÓøÂ/:TÒSÕTW+BAB/25ÞTÑTÃD³èÒOËL +?é;¿*¿+Á°O¿4óíÄM»èòçÏQËÌ*2ÑN9FJN*CWØò6*:7/2ó-SÁ4á/Q8ççÑO-2,,KèSã×àæ฿öÀ¿Á*Á +¶OÆDºZà2Æ01:ÑOË8Dúë¶òLË4G¸>çÏOçRÐMY**:W3¶á+²²çб½/À-ÁJ²÷ZÆ°ºÞ¿°ÀÑï-ÎòÞK2*>7@ +2ÃF°:PËÔ32ÐLU*ü´FØÐOÉ<7+R.*N5+XÂÞ/061.GLÒPÏHËDð2ÐÄ.:ÓRÓÄ4,Ò?Ç*³ç¯Âß.=;W4³+Jâ +;B7-*M1G´20:PÏÌ +M86Ê*:ÐKÃÌNÂXʯúë¶>TÑH2*Ç4óÃ../ÆÒRM+à+è,4/,1P,2UÔWQ*ÚéÔVÝXë¸?UáȳèÓ¿/H?TÒRÑT +ÛT³ºÞ/+.F.3ÊR*ÒM1ñ*½E8ÛçÑNÍD»ØòS²è²çÑQá*à+À*ä-¾çZ¿Þ¾D*UØÒSÐÑ*öòSÒQÏ00ÀÒ;Ä;*. +óSÒÑ,¾²Æ+FGå6¿NRÏH7*¶J01,*ÆÔZºì¶.OQöLü8¾ïÓ*61F3+KÈò>*2W6²PÏHÓ¸,:ø2ÓTËè²çο-Í +ºZÄA3ïÂ+ü´F,ÏK¿°ÒçD*S¸ò¿:ÜB*Zøà*è-À0ÂçÏÏ*°Òç+-¶¸,ÞÎLË4³òïö°N*JG=2*JOÇ8»à2O0á +0ÔÒSÏPý´FÀÐÁ»K*³*Á*ó+K+O*³,18îä.,-ÖSÑTËT»Dóâ;VG±6-B+B¸2*2íº=41¸;:îµáÏUãTÓTÓV +ÓP71JSÑX75*Uá,à*Ä+à+¾èƾ¾¾Î*¾ØV*ÎH¶¾:-¾¸ê*×´óè³é**߸ó*J>+N»Þ0,óèξ;Â-Û2<öÙï2 +Ìä9*β/*KÓ4³F¯/¾P»èÒçÐO9+¿B³ø°6ü´FèÐPÉD³èò6*B73¶=*Ð>ç-::NÉÜL¿U3*LÁ2à*T*Þç-=* +Ná4Ä5È6XÖ+671BP:LËüë¶V¯ºË*ÎòG-Úõ¿B*RD,JÌL»²,Å2øÌ3à+¼¶FÀÐAÓõà-0,ìZÞ?8Fï+:D0*Ä +Ü5À56¶*,QÒZ0îµá϶Ӹ,*ØÄ*:ÔUÙÄ**ÔA,JÔ°G**Uá+L*NUÔK-Á.¾ó+?¾ÔÃ0¾³.*V7/:UÓ8=Ú*¾+ +Á**ÎØ@ïÀ4ä*ü´FÀÑÇ-*>»æ+Þòã1NÎ+½E87TÑPËÌ+BÑQÏH71B-¶<*¯Àà>è592F¶P-O0AHZÊîR+ÈèÑTÍL7/: +RÏÄ-2ÒSå*P*îçÑSÏL»±*J³6ÞÃ*BÑUËXG°B´R°¶á0î²:Þ3C*ÑÃ/AʾÌX»øµá;BË;*.èѵM:OG>,úë +¶òRËX³TÓSÓQ3èÑTU*2TÒSQ*Þç¾*·ø²SJJÞ+9Â+Â@ÀÓ;ı*µPZâ:Ú:ÌZâî¾7XÇ¿FÃD¼KÎDûÙïB.ÆÎ +D³D¸1ÊÌ+Ê*¾Ñ/*18îJ.8>*J»Ñ+O0Qêî²*8ØßÎK·ßíÄMö¸*ÞÙ¸Ûè³éÕYÛÄ-Â,.*Ê,2ÔVå+8.*²7DB +-*TU.RéÕV-2ìÆKãJÇßÏ,FJÁ5-ZYÓ¼ûøµáÏBø,*ÌÄ*2ÑP96*F7**P5*8FJïJ6JWÞË*Ò5>AÚJÁ=°B ++¶.0M0á0ÞòéÄMî½å*öÖï2üíD*ü³F¼ý4öüïöûáïùÅáõ9ÅíI8ݽF¼ý¶úüµîûáïùÅáõ9ÅíI8EJGêùøö +óY@*2üøíõÁ8A9ï5àϹ1ͼ»ýõÝIAâÁí;*ÚµìÝ·ý8ÌÛùÓÎáïMúÀ¶òçÁ1ýûÚ»ýøS»ùóõ2*8H·êÓ±åÌë +EJÝ3¸Ú»Âµ2ÅÌù³ëÕ±ÉåÝIëÝIý8+ÞíÍI¼D²é×YÑéãÕí-GìHàEÂMí6ÙóåÏÝéåYøñÝO*öûïµ±ìúD´ã° +Ø÷ÃÎáçMºÁ¶ò½Y½¼ê׸ùøÃüûÉ.*ÕYIûîÈ9½¼Ëü0ü»9ò:øóݺÂL*èäÕíõ1ÅÌ´³ãÀ?EAÝÜÞÍI;*2·B° +,ÀV²ÞïÕAIF°ßÈU=Ø·µQÊ8SGàEÂM½T¸ïÜÄ»¹ýXæͽ.*¸@:1,>ê¶/,K*ô¿ò÷*8ÄÞ7.:éñåQK-14Þç½ +õÜÙïÜÃOÑѵ³QÄ8×RàëMºÁ¶òAK5̹Öôóë+úM*ÞVS*GÜûµâÕL:.**Fò¾C*Þüà+XéÞO1öôé³9ÌUBòÉQ +ÞA7¸NÑ3Q9ï7àÏUòFBXß×ËM1è9+¾Òï÷Þ+Ú´R5@ÆØWG*QÆß?ó***ÎÌ+R1FN-*èá+À,Ú¸*ó@RýL¿,Jõ +¾0ÌW߯*¶òä±5ÄEÖ±ÃQÞA5¸ÚÏ.Í0ß+óèIíSJ¾ºËâ;ÜKFø¾Ì-Ý7ê¸öïÎú;*2.Ù9F¾¹<ô¸óXÎÈ-ÎïDA +RÜF¾;*Ï=ð¿-Ò,Z¶ÞÉQ0X¶À-6¶µ/µ-¾Ä1µGÈKF1º,¼×Îá7OÆ-È--ÎμÞÇ@Çâ;ÜKFø¹Ì+Ù/Êصíîù; +*2.Ó9F¾QBûùõå4¼-¾Ü½-öû¹3¾J¾ûÄ,P+VµÏñÆÛÞ¾¹U¶E*ìÙµè¾ë,ôúì3öãÀ8µ>¿úÎèF>,+**:*õF +8ï7à;Z5ÁÁ½QñäÛù;*2B<9F¾U2Û¹õå4¸-:Û¹I;*¸ÞDó¸*Þû*.6г´?ÝÎ+Îîê4ì0¾ïä»øí*IÞô.ú°K +ÅEÈÞ×ó·çÛù,Ó÷FàEÂM:@0QûÈÄGùôêµ*Þ±¼¾R·³ßE±ùNÛD±Ü?S´*<¹ô=,JV°°51J/.U¹*,ìJêçW+Z +Ì˾SÌ7JбU¶D*¶¹´êÐû0ö+¯QÞA5¸ÚÏ.QêAÀê×I×ïUVQÌâ;ÜKFøVÎ/áûEø³êå*Þ·ÔϾJ/IÖ;¹M2>R +È6E*ê*ù3+¶È5ÜG¾7ùÑ/Zí+2ö¹8HÚØá0´SN+´*ÅÕZ4îP*öóæ½ï40Gé3öãÀ8,E¾5Èî?8RÁ7ùðP4N¿7 +ÇFàEÂMû²ØïÛ¾KÝ=ÚVB¾TUêð¸/80î»0ææ¿õDHûç3öOÊ8å@îî;ÜKF¸MÒC1@6øóê>EôéQ+**>*F5+ÉJ +*2ѵË:C2¶êׯÍXFBñèÞæ3öÛPÅ-ê¶æ7·GàEÂMý.ײâÂGÕÌ»Í*îÙµÙÔV×òâÆ̾í4æïò;ÜKFøµSSÅÔÊ +×òéLÙôOÜÏñü7Ú´æÏUUOÖðèÜ1¸Ú»Âµ2Å8õ<ãÒQ½0»DÛÔËù<+Þ½±QÌÙ´åÊ6ÈÛÚAÀ8Q9ï7àϱݶRçâÓ +WÕ@ÓWY<*º´íÜ»ÅäØæCèØÖö»ÎáïMºÁ¶Âêé4±Mã°+¾çÂTæ³YéåõÀ8A9ï7à;YYü¸ïÛ¶íX?*>ìÛÃYAݼ +Cò¶ö2í:8?ùóæÃñÔ»×*JÚÃM½ó»ÉJÅÁÅö0ïÖ=ûðûáïùÅáõ9ÅíI8ݽF¼ý¶úüïöûáïù;õ9ÅíI8Ù=8+ä +-**Eݽ*NH01ÖRúø7¶ÒLÃæõ9*÷Ñ+ùÚ÷ÐOɼû0FàÆCÐYÝ+ÅدáÅ:MÌúü+:½DZÈ>M¼úæÕí¾MÉB¯LÃíI*´Þ0°;á**ïéµÞïù9¾ã.,Öøü+ZIÂ*% +d +569 103[1 0 0 1 0 0]sl 8 mask 0 312 di + +QP +%%Trailer +%%Pages: 1 +%%DocumentFonts: +%%EOF diff --git a/doc/krfb/screenshot.png b/doc/krfb/screenshot.png new file mode 100644 index 00000000..54472e52 Binary files /dev/null and b/doc/krfb/screenshot.png differ diff --git a/doc/ksirc/Makefile.am b/doc/ksirc/Makefile.am new file mode 100644 index 00000000..085981d9 --- /dev/null +++ b/doc/ksirc/Makefile.am @@ -0,0 +1,4 @@ + +KDE_LANG = en +KDE_DOCS = AUTO + diff --git a/doc/ksirc/index.docbook b/doc/ksirc/index.docbook new file mode 100644 index 00000000..abead63a --- /dev/null +++ b/doc/ksirc/index.docbook @@ -0,0 +1,1446 @@ + +mIrc"> + + + IRC"> + CTCP"> + + +]> + + + + +The &ksirc; Handbook + + +&Andrew.Stanley-Jones; +&Andrew.Stanley-Jones.mail; + + +&Philip.Rodrigues; +&Philip.Rodrigues.mail; + + +&Anne-Marie.Mahfouf; +&Anne-Marie.Mahfouf.mail; + + + + +1997 +Andrew Stanley-Jones + + + +200120022004 + +Philip Rodrigues + + + + + +2005 + +Anne-Marie Mahfouf + + + + + +2005-05-10 +1.3.12 + +&FDLNotice; + + +This documentation describes &ksirc; 1.2.1, the &kde; &irc; +client. + + + +KDE +KSirc +irc +Internet relay chat + + + + +Introduction + +&ksirc; is the default &kde; &irc; client. It supports scripting +with Perl and has a lot of compatibility with &mirc; +for general use. + + + + +Using &ksirc; + + +If you haven't used &irc; before... + +Simply put, &irc; is a chat protocol, defined by official Internet +standards, and capable of stealing many hours of your life. To use +&irc;, you must connect to a server, and then join a channel (equivalent +to a chat room). + +To connect to a server, open &ksirc; and select +ConnectionsNew +Server... or press F2. In the +dialog box that is displayed, select a group from the leftmost drop-down +box and then a server from the middle drop-down box. The port, shown on +the right, will almost always be 6667. A new window will be displayed +showing the messages sent by the server to the client. When these +messages have finished scrolling past, you want to choose your +channel. To display a list of available channels, type +/list. To join a channel, +type: /join +#channelname. To leave a channel, +just type /part. + +That's the basics; for more detailed information, read +on... + + + + +&ksirc; Basics + + +Servers + +&ksirc; allows you to connect to any number of +servers. To connect to a server, select +ConnectionsNew +Server or press F2.The +Connect to Server Dialog appears, and you can select a +Group, then a Server, and also +a Port to connect to. + +Just click Connect when you're done, and a +new window for the server is displayed. You can keep track of the +servers you're connected to in the &ksirc; Server +Control window. + +You can get help on most &irc; commands by typing /help +commandname. + Typing /help on its own gives a list of available +commands. + + + +Channels + +You can connect to channels with the normal &irc; command +/join #channelname +, by selecting +ConnectionsJoin +Channel... or by pressing +F3. + +Channels you are connected to are shown in the Server +Control window under the server to which the they belong. To +leave a channel, you can /part, select +ChannelClose + or press &Ctrl;W. + + + + +Channel Options + +The Channel menu, available in any channel +window, contains some useful options: + + + + + +&Ctrl;S + +Save to Logfile... + + +Saves the contents of the channel to a +file. + + + + + +Time Stamp + + +If selected, prepends each thing said in the channel with the +time it was said, in the form [HH:MM:SS] + + + + + &Alt;C +T +Ascii Table + + +Displays a table of Ascii characters, from which you can +choose any characters you require. + + + +&Alt;C +P +Beep on Change + + +If selected, &ksirc; emits a beep every time something is said on +a channel. + + + + + + + + +User Options + +In a channel, the user list is displayed along the right hand +side. Right-clicking a nickname in that list gives you a pop-up menu of +options, most of which are self-explanatory. Those actions can be configured (changing the order, adding or removing some) in the Configure KSirc... dialog in the User Menu tab. The default actions are the following: + + + + +Refresh Nicks + +Sends a message to the server to request the list of users in the +channel. This ensures that the list of nicknames is up-to-date. + + + + +Follow + +Highlights the user's nick in every message they send to +the channel. This way, you can follow someone's thread of conversation +in a busy channel. The user's nick color is picked at random. + + + + +unFollow + +Stops following the user. + + + + +Whois + +Sends a &CTCP; whois query to the user, which +provides information about the user, such as his hostname and what +server he is connected to. + + + + +Ping + +Sends a &CTCP; ping request to the user, to +determine his echo time. + + + + +Version + +Sends a &CTCP; version request to the user, to +determine what software he is using. + + + + +Abuse + +Abuses the user in one of various amusing +ways. + + + + + +The following commands are only available if you are an operator +on the channel: + + + +Kick + +Removes the user off the channel. + + + + +Ban + +Bans the user from joining the channel. + + + + +unBan + +Removes the ban on the user joining the channel. + + + + +Op + +Gives the user operator privileges. + + + + +DeOp + +Removes operator privileges from the user. + + + + + + + + + +Autoconnecting +If you have a number of servers and/or channels which you +connect to every time you use &ksirc;, then you can set up &ksirc; to +automatically connect to them: In the Server +Control window, select +SettingsConfigure +KSirc..., and in the dialog which appears, +select the Auto Connect page. To add a channel on +a new server, fill in the Server and +Channel text boxes, and then click on +Add. To add another channel on this server, +select the server name in the tree view, enter the channel name in the +Channel text box, and click on +Update/Add. You can repeat this process as many +times as you like to add several servers and channels for &ksirc; to +connect to at startup. + + + + + +&ksirc; Configuration + + +Introduction to Configuration +The &ksirc; configuration dialog is available from +OptionsPreferences... +in the &ksirc; Server Control window. + + + + +The <guilabel>Look and Feel</guilabel> Tab + +<guilabel>Window Mode</guilabel> + + + + +SDI Mode (old behavior) +Causes &ksirc; to use single document interface mode, in which +each new channel or /query window has its own, separate +window. + + + +Paged MDI mode (XChat) +Causes &ksirc; to use multiple document interface mode, in the +style of XChat, in which each new channel or +/query window has a tab in one main &ksirc; window. Each tab +can be brought to the front by clicking its tab, and you can switch tabs using +&Alt;Left Arrow and +&Alt;Right Arrow. + + + + + + + + +<guilabel>Wallpaper</guilabel> + +In the Wallpaper section, you can change the image placed +in the background of your &ksirc; windows. You can enter the name of the file +to use in the text box, or browse for files using the button to the right of +the textbox. Image files used must be in &GIF;, JPEG or +PNG format. A preview of the image is shown at the right of +the dialog box. +If you don't want a wallpaper, simply right-click in the path field and choose clear in the context menu. Or remove the wallpaper location to leave the field empty and click Apply. No wall paper will be set then. + + + + + +The <guilabel>General</guilabel> Tab + + +<guilabel>Global Options</guilabel> + + + +Dock in system tray +This allows KSirc to be docked in the system tray. By default this is not enabled. When KSirc is docked in the system tray, you are able to access several options by clicking on the KSirc icon. When you close KSirc window, the icon stays in the systray until you quit KSirc. + + + +Color picker popup +If selected, a popup window from which to select the color of +your text is presented when you press +&Ctrl;K. If not, you have +to type the color codes manually. See also . This option is selected by default. + + + +Auto create window If +selected, &ksirc; will automatically create a new window for each user who +sends a /msg command to you. If not selected, any text sent +to you with /msg is displayed in the current window and you +can use /query username to create +a window to chat to that user. It is selected by default. + + + +Auto-rejoin +Rejoins channels automatically if you are +disconnected. + + + +Auto create on notice +If someone sends you a /notice then if this option is checked it will create a new window. + + + +Announce away messages +If this is checked, you will see the messages when a user selects the away option. By default this option is not checked. It is checked by default. + + + +Nick completion +If selected, switches nickname completion on. Nickname completion works as +follows: + + +Type the first letters of a user's nickname. + +Press TAB. The text you typed will be +completed to match the username, including changes in capitalization if +necessary. ⪚ phi<TAB> becomes +PhilRod if there is a user called +PhilRod on the channel. + +If more than one user's nickname on the channel matches the +text you have typed, the first name in the list is chosen. Pressing +TAB subsequent times displays the next nickname in the +list. For example, if there are users PhilA and +PhilRod on a channel, and you type +phi<TAB>, the text will first be completed to +PhilA, and if you press TAB +again, it will change to PhilRod. + + + + + + + + +Use color nick list +If selected, it will use the colors set in the Colors tab of the Configure KSirc dialog for coloring the nicknames. + + + +Display topic in caption +Displays the topic of the current channel in the window +caption. If not selected, the topic is only displayed inside the +window. + + + +Dock passive popups +This option is only available if Dock in system tray is checked. If it checked, the passive popups will be displayed docked to the system tray. + + + +One line text entry box +If this is not selected, the entry box where you write your text in each channel will expand on several lines depending of the text length. If this is selected, only one line will be used and that means you will not see the beginning of the text you are writing if it is longer than the line. + + + +History Length +Stores up to this many lines of chat from each window, allowing +you to scroll upwards and see what has already been said. + + + +Auto save history +If this is selected, private message history is saved. When you open a private window the second time with the same user, you will see a log (the history) of your previous chat with him. + + + + + + + +<guilabel>Per Channel Options</guilabel> + + + + + +Override existing channels options +If this is selected, the settings in this tab will override each channel options so these settings will be applied in each channel, independently of your channel settings in the Channel menu. This setting will only work until next time you open the configuration dialog and it will be reset unchecked then. This is because you probably don't want to override the existing channels options all the time. + + + +Time Stamp Prepends +each thing said in the channel with the time it was said, in the form +[HH:MM:SS]. + + + + +Show topic +Displays the channel topic on top of each channel window. + + + +Beep on change +If selected, a system beep will be generated whenever a user on +your /notify list signs onto the &irc; server you are +on. + + + +Enable logging +Creates log files for each channel per day. These log files are stored in $KDEHOME/share/apps/ksirc/logs/ and the files look like: /$KDEHOME/share/apps/ksirc/logs/#channelname_year_month_day_servername.log + + + +Hide part/join messages +Don't show part/join messages. This is useful in a channel with many people. + + + +Default encoding +Fixes the default encoding for all channels. Choose the encoding in the drop-down list. + + + + + + + + + + +The <guilabel>Startup</guilabel> Tab + + +Here you can configure &ksirc;'s startup settings. + + + +<guilabel>Server</guilabel> + + + + + + + +<guilabel>Name Settings</guilabel> + + + + + + +Nick Name +Set your &irc; nickname. + + + +Alternative Nick +Set the nickname to use if your first choice is already in use +by another user. + + + +Real Name +Your real name, as returned by a /whois +query on you. + + + +User ID +Your real name, as returned by a /whois +query on you. + + + + + + + +<guilabel>Notify List</guilabel> + + +Here you can enter a list of users who you want added to your +/notify list at startup. You will be alerted when each of +the users in the list is online. + + + + + +The <guilabel>Colors</guilabel> Tab + +Here you can configure &ksirc;'s use of color. + + + +<guilabel>Chat colors</guilabel> Configuration + +Here you can set colors for each of the types of text. Clicking the color +button to the right of each text type's label will display a color selector box +in which you can choose the color in which you want this type of text to be +displayed. + + + + +<guilabel>Nick Colors</guilabel> + +Foreground: and Background: set the +colors for the foreground and background of users' nicknames in chat +windows. The Color for messages containing your nick: +option sets the color for any message appearing in a channel which contains +your nickname. + + + +<guilabel>Color codes</guilabel> + +Here you can set whether &ksirc; allows native color codes (see ) and also whether &mirc; color codes are allowed. + + + + + + + +The <guilabel>User Menu</guilabel> Tab +This page allows configuration for the RMB (Right Mouse Button) menu for the nick list located on the right in each channel. When you right click on a nickname, some actions are defined by default. These are the actions listed on the left. You can change these actions order, delete some and add others. + + + + +The <guilabel>Server/Channel</guilabel> Tab +Here are listed the servers and channels you join to using the New Server (F2) and New Channel (&Ctrl;N) dialogs via the Channel menu. + + +Server +You add there the servers you want to use. Write the server name in the field and click the Add Server to List button. If you want to delete a server from the list, you click on the server name and then on the Delete Server from List button. + + +Channels +You add there the channels you want to connect to. Write the channel name in the field and click the Add Channel to List button. If you want to delete a channel from the list, you click on the channel name and then on the Delete Channel from List button. + + + + +The <guilabel>Auto Connect</guilabel> Tab + + + +Auto Connect Setup +You set here the server and channels names that you want to connect directly when KSirc starts. +You need to use the Auto Connect Setup dialog first to add new channels. You enter there the server name. The server port is set by default for most servers. If you are not sure, just leave it as it is. Usually servers don't ask for passwords so if you don't have a password, leave the Password field empty. Then add a channel name and click the Add button. The channel will be displayed in the Auto Connect List. + +If you want to add more channel for a same server, click on the server in the Auto Connect List and the server name will be displayed in the Auto Connect Setup dialog. Write the channel name in the Channel field and click on Update/Add. + + +In order to remove a channel form the Auto Connect List, click on the channel name in the list and then click the Delete button. + + + + + +The <guilabel>Fonts</guilabel> Tab + +You can change here the font for the main window. Select the font you want then click the Apply button to preview the change. Ok applies the changes and quits the dialog. + + + + + + + + + +&ksirc; Colors + +&ksirc; follows the color scheme used by +&mirc; and a slight modification for more +powerful in house use. + +<fg> == foreground +<bg> == background +[] == optional + + + +&mirc; compatibility + +Format: + + +0x03<fg>[,<bg>] +sets the foreground and background + + +0x03 +resets to defaults for that line + + + + + +&ksirc; native + +Format: + + + +˜<fg>[,<bg>] +sets the foreground and background + + +˜c resets to defaults + + +˜b sets bold font + + +˜u sets underline + + +˜i sets italics + + +˜r sets reverse video + + + +Why did I change &ksirc; to use ˜ instead of 0x03 +(&Ctrl;C)? +Well, it's hard to use 0x03 in scripts and not all C functions seem to +like it. ˜letter +also allows more commands while not stomping on &mirc;'s future changes. + + + + +Color Numbers + + + + white + + + black + + + blue + + + green + + + red + + + brown + + + purple + + + orange + + + yellow + + + lightGreen + + + cyan + + + lightCyan + + + lightBlue + + + pink + + + gray + + + lightGray + + + + + +Sending Bold, Underline, Reverse, and Color + +You can use the following key combinations to insert control codes +in text: + + + +&Ctrl;B for +bold text + + +&Ctrl;U for +underlined text + + +&Ctrl;R for +reverse text + + +&Ctrl;K for +colored text + + +&Ctrl;O for +plain text + + + + + + +Examples + +To underline a single word in a sentence: + + + +Type &Ctrl;U + + +Type in the word + + +Type &Ctrl;U +again + + + +Only the text that is enclosed by the start and end codes will be +affected. You can use this method with all of the other control +codes. + +The &Ctrl;K +control code is slightly different because it allows you to specify a +color number. To color a single word in a sentence: + + + +Type &Ctrl;K + + +Type a number between 0 and 15 + + +Type the word + + +Type &Ctrl;K again + + + +If you also want to change the background color of a word, you +would need to type two numbers separated by a comma instead of just one +number. The first number is the text color, the second number is the +background color. The colors range from 0 to 15, the index is in the +previous section. + +You can enclose text in multiple control codes, so for example you +could have a bold, underlined, and colored word. + + + + + +Filters + + +Filter Rules and How to Make them + +If you just can't figure it out, wait. I want to build a nice +filter builder where you can just click your way through +it. Though, it might be a while. + +The filter tries to find the match string then use +the From and To as a substitution. The +match, from and to are all Perl regex +expressions. Rules are evaluated in descending order. The top rule is +evaluated first, then the second from the top, &etc; All strings are +evaluated as: + +$name is expanded to +the environment variable +name. This is done +immediately when you insert the rule, and will not change after that +time. Therefore it's probably of limited value. + +$$name is +substituted with the Perl +$name variable during the +match. This can be substrings such as $1, +$2 in the substitution, or normal variables available +under sirc (such as $nick, +$channel, &etc;). + +˜name˜ +prepended once and only once to the line will send +the line of text to the window called name. +If the window does not exist it will go to the last window which had +focus. There are several special windows, all prefixed by a single +!: + + + +!default + +The current default window. Guaranteed to exist. + + + +!all + +Send to every window. This might not show up on all windows, +depending on how the text is parsed. For example, channel windows won't +show a /part or /quit unless the nickname is on the channel. + + + +!discard + +discards the text. + + + + + +The rest of the expression is dealt with as normal +Perl regex. A +good understanding of the perlre man page will +definitely help, but a basic understanding of regex is most certainly +required. + + + + +Examples: + + +Want to convert all boren from boren to BoreN + +Match: .* +From: boren +To: BoreN + +Pretty straight forward, match anything, then substitute boren +with BoreN. + + + +You want to match everything with boren in it and send to the +window called boren + +Match: boren +From: ˆ +To: ˜boren˜ + +Looks for boren if found, substitutes the beginning +of the string (ˆ) with ˜boren˜. + + + + +Though the previous example works, if the string already has +˜somewindow˜ on it, you'll now have two +˜boren˜˜somewindow˜... So you +can do this instead. + +Match: boren +From: ^(?:˜\S+˜) +To: ˜boren˜ + +Ok, the from line is a little bit more complicated. It means: match +0 or 1 copies of ˜\S+˜. Which is 1 +tilde, one or more non-whitespaces, and then another tilde. The +paranoid might do (*:˜\S+˜), which means: +match 0 or more channel directives in case prior rules are +broken. + + + + +Server kill messages tend to be long, ugly, annoying, &etc; A basic +message on dalnet looks like: + +*** Notice -- Received KILL message for +BOBO!ANDY@line82-basel.datacomm.ch from NickServ +Path: empire.ny.us.dal.net[209.51.168.14]!trapdoor.ca.us.dal.net +[206.86.127.252]!caris.ca.us.dal.net[208.1.222.221] +!services.dal.net[2008.1.222.222]!services.dal.net +(NickServ Enforcement) + +When you're +s you get tons of them; you don't +want all of them flying across your screen. I'm going to make 3 rules +to deal with them one bit at a time. You could do it in less rules, but +it'll show you the basic rule structure, in nice steps, and how to use +multiple rules to parse a message. The first step is to remove the +Path: portion of the message: + +Match: ^\*\*\*.* KILL message for.* +From: Path: \S+ +To: . + + +Match looks for the message starting with ***, +the *'s have to be quoted with \ +since by themselves they mean 0 or more of the prior character. +.* means: match anything until you find +KILL message for. This allows us to avoid typing in +-- Received... &etc; The trailing +.* means: match anything to the end of the line. (not +needed, I think) + +The From line means: substitute " Path: " and any +non-whitespace characters with the To. To is a "." +therefore the entire path turns into a single period. + +The message now looks like: + +*** Notice -- Received KILL message for BOBO!ANDY@line82-basel.datacomm.ch +from NickServ. (NickServ Enforcement) + +Notice the new "." after +NickServ? + + + +Ok, the message is a lot cleaner, but KILLs from nickserv aren't +really that important, so let's forward them to the +!discard window. + +Match: ^\*\*\*.*KILL message.*\(NickServ Enforcement\) +From: ^(?:˜\S+˜) +To: ˜!discard˜ + +Match rule searches for the KILL message and makes sure it's from +NickServ. Notice the \( and \) +are both used in regex, therefore we have to quote +them. This is very similar to what we said two examples before. + + + +We've now filtered out all the nickserv kills, but the message is +still pretty hard to read by simply glancing at it. So let's reorder it +to something like: + +*** [KILL] KILLER; killed KILLED; (REASON) + +Match: \*\*\*.*KILL message +From: \*\*\*.*for (.*?) from (.*?)\. \((.*?)\).* +To: *** [KILL] $$2 killed $$1 ($$3) + + +Ok, the match looks for +***something KILL message. +We can't use ˆ since we may have just appended +˜<window>˜. + +The from line gets a little more interesting. The "for +(.*?) " looks for the word "for" then some text. +.*? means: match zero or more of anything except +newline, but isn't greedy. The rule is to stop when the first terminating condition is +found, not the last. In other words it matches anything until a space is encountered. +The surrounding () means: save the contents. Each +() saves the matched data in $# where +# starts at 1 for the first substring, &etc; In +this case, $1 gets the nick/user-info of the person +killed. $2 is then filled with the name of the +killer. Between the () we have the reason for the +kill. Here the ( and \( get a +little confusing. Remember \( matches the actual +character '('. + + + +How to colorize your life. + +Ok, you want to add some color to +&ksirc;. See the Colors section for +color info, but here's a filter rule to highlight the nickname between +<NICK> on each line: + +Match: ^(?:˜\S+˜)<\S+> +From: <(\S+)> +To: <˜4$$1˜c> + +Takes the nickname and adds color #4 between the two +<>. ˜c clears the color. + + + + + + + + +Keys + +This is a listing of the shortcut and command keys available +under &ksirc;. + + +Keybindings + + + +Keybinding +Action + + + + + + +&Ctrl;N +Open a new Channel/Query Window. + + + +&Ctrl;Q +Quit + + + +&Ctrl;Enter +Begin a message to the most recent person to send you a +message. + + + +&Ctrl;&Shift;Enter +Begin a message to the last-but-one person to send you a +message. + + + +&Ctrl;K +Start color code with a number. Text you type after this will be +colored. + + + +&Ctrl;U +Begin or end underlining text. Text after the first occurrence +of this will be underlined until you type the shortcut again. + + + +&Ctrl;I +Begin or end italic text. Text after the first occurrence +of this will be italic until you type the shortcut again. + + + +&Ctrl;B + +Begin or end bold text. Text after the first occurrence +of this will be bold until you type the shortcut again. + + + +&Ctrl;R +Begin or end reverse video text. Text after the first occurrence +of this will be reversed until you type the shortcut again. + + + +TAB +Tries to complete the nickname of someone on the channel, based +on the text you entered. You are probably familiar with this from your +commandline shell, although it does not necessarily work in exactly the same +way. See . + + + + +
+ +
+ + + +Tips + + +Joining &kde; channels + + +&kde; &irc; channel are hosted on the Freenode network. Use the Server/Channel tab or the +Auto Connect tab in Configure KSirc to add these channels. The network +should be irc.freenode.org on port 6667. Below are listed the most +important &kde; channels and their goals. Choose the channels you are +interested in. + + +You can find some tips about asking questions on &kde; channels on +the &kde; +community wiki. + + + + KDE IRC channels + + + + Channel + Target + + + + + #kde + User help + + + #kde-devel + &kde; development + + + #kdevelop + KDevelop questions + + + #kontact + Kontact (&kmail;, &korganizer;, &knotes;, ...) related questions + + + #amarok + Community channel about amaroK: user questions, development + + + #debian-kde + Debian KDE related questions + + + #kde-freebsd + FreeBSD KDE related questions + + + +
+
+ + +Auto identify + +If you intend to use &irc; on a regular basis you will probably +want to register your nickname. This protects your nickname so that +only you can use it. On DALnet, after you log on, you register your +nickname using the command /msg +nickserv register PASSWORD, +where PASSWORD is your password. You'll get a +confirmation your nickname has been registered. + + + +Then each time you log on to DALnet you get a message asking you to +identify yourself. You will then need to type +/msg nickserv identify +PASSWORD. + + + +&ksirc; can identify you automatically when you log into any +channel. You simply need to add the following Perl script in your home +directory: + + + +The following script will work on Freenode. This script should be +saved as .sircrc.pl in your home +directory. When you run &ksirc;, this script will automatically have +you identified. + + + +sub hook_nick_notice { +my $n = shift; +my $m = shift; + + +if($who =~ /NickServ/){ +if($m =~ /If this is your nickname/){ +$silent = 1; +&msg("nickserv", "identify XXXXXX"); +$silent = 0; +} +} +} +&addhook("notice", "nick_notice"); # join on the "end of MOTD" numeric + + +Replace XXXXXX with your password. + + + +The above script will work on other networks than Freenode where +nickserv sends you the request "identify" but on some networks like +Undernet you will need the following script: + + + +sub hook_connected { +if($server =~ /undernet.org$/){ + &msg("X\@channels.undernet.org", "login YOUR_NAME XXXX"); + &docommand("umode +x"); +} +} +&addhook("255", "connected"); # join on the "end of MOTD" numeric + + + +Replace YOUR_NAME with your login name and +XXXXXX with your password. + + + + You can find more about SIRC programming on this page: http://www.iagora.com/~espel/sirc/PROGRAMMING + + +
+ + + +Credits and Licenses + +&ksirc; copyright 1997-2002, the &ksirc; developers. + +Portions of documentation copyright 1997, &Andrew.Stanley-Jones; + +Documentation updated for &kde; 3.0 by &Philip.Rodrigues; +&Philip.Rodrigues.mail;. + + was written by &Anne-Marie.Mahfouf; &Anne-Marie.Mahfouf.mail;. + + +&underFDL; +&underGPL; + + + + +Installation + + +Where to get &ksirc; + +&install.intro.documentation; + + + + + +Compilation and Installation + +&install.compile.documentation; + + + + +&documentation.index; + +
+ + + + + + diff --git a/doc/ktalkd/Makefile.am b/doc/ktalkd/Makefile.am new file mode 100644 index 00000000..085981d9 --- /dev/null +++ b/doc/ktalkd/Makefile.am @@ -0,0 +1,4 @@ + +KDE_LANG = en +KDE_DOCS = AUTO + diff --git a/doc/ktalkd/index.docbook b/doc/ktalkd/index.docbook new file mode 100644 index 00000000..7cfbbb6f --- /dev/null +++ b/doc/ktalkd/index.docbook @@ -0,0 +1,608 @@ + + + + +]> + + + + +The &ktalkd; Handbook + + +David +Faure + +
faure@kde.org
+
+
+ + +
+ + +&FDLNotice; + + + +2001 +David Faure + + +2001-05-02 +1.05.02 + + + +&ktalkd; is an enhanced talk daemon - a program +to handle incoming talk requests, announce them and +allow you to respond to it using a talk client. + + + + +KTALKD +talk +talkd +otalk +ntalk +ktalkdlg +kcmktalkd + +
+ + +Introduction + + +&ktalkd; is an enhanced talk daemon - a program to +handle incoming talk requests, announce them and +allow you to respond to it using a talk client. + + + + +Note that &ktalkd; is designed to run on a single-user workstation, and +shouldn't be run on a multi-user machine: since it reads users' +configuration files, users can get the talk daemon to +run any command, which is particularly dangerous. Do not use &ktalkd; if +you create accounts on your machine, to people you don't fully trust. + + + + +In this document, if somebody wants to talk to you, you are designated +as the callee. + + +&ktalkd; has the following features : + + + +Answering machine + + +If the callee isn't logged on, or doesn't answer after +the second announcement, an answering machine is launched, takes the +message, and mails it to the callee. + + + + +Sound + + +If desired, a sound is played with the announcement. + + + + + +X Announce + + + +If compiled with &kde; installed, &ktalkd; will use +ktalkdlg, a &kde; dialog, for announcement. If +&ktalk; is running, it will be asked to make the announcement +itself. (New since 0.8.8). + + + + +Multiple displays announcement + + +If you are logged remotely (⪚ with an +export +DISPLAY=... +command), the X announcement will be made on this display too. Answer on +the one you want! If you're also logged in a text terminal, and if +you're not using xterms (internal restriction), +then you'll see a text announcement too, in case you're using the text +terminal at the time of the announcement. + + + + + +Forwarding (New since 0.8.0) + + +You can set up a forward to another user even to another host +if you're away. There are 3 different forwarding methods. See section +Usage. + + + + + +Configuration + + +If &ktalkd; is compiled for &kde;, it reads config from &kde; config +files, the sitewide +($KDEDIR/share/config/ktalkdrc) and +the user one, in its home folder. The sitewide one has to be manually +edited by the administrator, but there is now a configuration dialog for +the user one. It's called kcmktalkd and can be found +in the &kcontrol; after installing &ktalkd;. On non-&kde; systems, +&ktalkd; will read /etc/talkd.conf. + + + + + +Internationalization + + +Under &kde;, the announcement will be in your language provided that you +set it in the &kde; menus and that someone translated +ktalkdlg to your language. The same goes for the +configuration dialog, kcmktalkd. + + + + + +Support for otalk and ntalk +(New since 0.8.1) + + +&ktalkd; now supports both protocols, even when forwarding. &ktalk; +supports both protocols as well. + + + + + +I hope you will enjoy this talk daemon, + +David Faure faure@kde.org + + + + +Usage + + +To use &ktalkd;, you need a talk client. The +text-based talk is available on most &UNIX; systems. Try talk your_username to see what happens when you +receive a talk request. + + + +You can also try the answering machine the same way: initiate a +talk to yourself, ignore the announcement twice, and +you'll see the answering machine. + + + +There is a talk client with a graphical interface for +&kde;, &ktalk;. It's not yet shipped with &kde; packages, but you can +find it on ftp://ftp.kde.org. It should be in ftp://ftp.kde.org/pub/kde/stable/latest/apps/network + + + +The announcement dialog box is trivial: respond +or ignore. + + + +The configuration dialog should be rather straight forward, except for +setting up a forward to another user (or even to another host). + + + +Choosing a Forwarding Method + + +None is perfect, they all have pros (+) and cons (-). + + + + +FWA - Forward announcement only. + + +Direct connection. Not recommended. + + + + +(+) You know who the caller is, but + + + + +(-) Caller will have to respond to an announcement from you. Annoying. + + + + +(-) Don't use if you have an answering machine on your +away location. (The answering machine can't popup an +announcement, it would be confusing!) + + + + + + + +FWR - Forward all requests, changing info when +necessary + + +Direct connection. + + + + +(+) Caller won't know that you're away, but + + + + +(-) You won't really know who the caller is - only his username, (so you +might see talk from +Wintalk@my_host) + + + + + + + +FWT - Forward all requests and take the + talk. + +No direct connection. + + + + +(+) Same as above, but also works if you and caller can't be in +direct contact one with the other (⪚ firewall). + + + + +(+) You'll be told who's really talking to you when you accept the talk + + + + +(-) But as in FWR, you won't know his machine name in +the announcement + + + + + + + + +In short, use FWT it you want to use it behind a +firewall (and if &ktalkd; can access both networks), and +FWR otherwise. + + + + + + +Questions and Answers + + + + + +Why doesn't root receive &kde; +announcements? + + + + + +Because this would be security hole, with the current user +detection. You can bypass the limitation by adding two lines in +xdm config files (which are the same as &kdm; ones). + + + + +The S.u.S.E &Linux; distribution includes those lines by default. + + + + +Those config files are normally in a folder such as /etc/X11/xdm, or /usr/X11R6/lib/X11/xdm on other +systems. The following supposes that they are in /etc/X11/xdm, so you might have to +translate them for another folder. + +Here is what you have to do: + + + +Edit the file Xstartup, or create it, (in the +xdm config folder) so that it reads: + +#!/bin/sh +/etc/X11/xdm/GiveConsole +sessreg -a -l $DISPLAY -x /etc/X11/xdm/Xservers $USER + + +and the file Xreset so that it reads: + +#!/bin/sh +/etc/X11/xdm/TakeConsole +sessreg -d -l $DISPLAY $USER + + + +Make sure that xdm-config make reference to those +two files: + +DisplayManager._0.startup: /etc/X11/xdm/Xstartup +DisplayManager._0.reset: /etc/X11/xdm/Xreset + + + + +This will make &kdm; (or xdm) log +the user into utmp, which is the right thing to do. It's not up to +&konsole;, nor xterm, to log the user, but to +xdm and &kdm;, in my +opinion. However, this will not log the user as an X user when using +startx... Any hint about that ? + + + + + + + +Why don't I, as a normal user, receive &kde; announcements? + + + + + +If you're running a &Linux; system (with /proc enabled), this behavior is a +bug. Please send me a description of it so that I correct it. + + + +If you're running &Linux; 2.0.35, this is a known bug in the kernel, +which doesn't let root read +/proc. The solution is the same +as in the previous question, provided that you run +&kdm; or xdm to log into X. Or +upgrade! + + + +Otherwise, this is normal. &ktalkd; can't find the user, as &kde; +doesn't log him into utmp and the &Linux; based (/proc) detection is disabled. The solution +is the same as in the previous question, provided that you run +kdm or xdm to log into X. Another +solution is to make sure you always have an +xterm running. + + + + + + + +How do I get debug output from &ktalkd;? + + + + + +As it is a daemon, there is no debug output on standard output. To get +debugging output (for instance before submitting me a bug report!), +update the lines in inetd.conf which launches +&ktalkd; and &kotalkd; to be: + + +talk dgram udp wait root /usr/sbin/tcpd /opt/kde/bin/ktalkd -d +ntalk dgram udp wait root /usr/sbin/tcpd /opt/kde/bin/ktalkd -d + +Notice the option. + + + +Then edit /etc/syslog.conf to add the following +line: + + +*.* /var/log/all_messages + +To make it work, you then have to restart inetd +and syslogd: + +% killall +% killall + +Finally, run a talk session and see the result +in /var/log/all_messages + +When submitting a bug report, never forget to include the +debugging output, but also &ktalkd;'s version number and the +./configure output. Thanks. + + + + + + + + +Copyright and Licenses + + +&ktalkd; is maintained and improved by David Faure, +faure@kde.org + + + +The original program was written by Robert Cimrman, +cimrman3@students.zcu.cz + + + + +&underFDL; +&underGPL; + + + + +Installation + + +How to obtain &ktalkd; + + +&ktalkd; is now a core application of the &kde; project http://www.kde.org, part of the +kdenetwork package. + + + +You can always download the latest &ktalkd; from the main &FTP; site of +the &kde; project, ftp://ftp.kde.org/pub/kde and +from its mirrors. It's usually found in ftp://ftp.kde.org/pub/kde/stable/latest/apps/network + + + + +Requirements + + +In order to successfully compile &ktalkd;, you need the latest versions +of the &kde; libraries as well as the &Qt; C++ library. All required +libraries as well as ktalkd itself can be found on ftp://ftp.kde.org/pub/kde/. + + + + +Compilation and Installation + + +In order to compile and install &ktalkd; on your system, type the +following in the base folder of the &ktalkd; distribution: + + +% ./configure +% make +% make + +As &ktalkd; is a daemon, make + will require root privileges. + + +Don't forget to update /etc/inetd.conf. For +example, on a &Linux; system, if &kde; is in /opt/kde, change the lines concerning +talk and ntalk to: + + +talk dgram udp wait root /usr/sbin/tcpd /opt/kde/bin/ktalkd +ntalk dgram udp wait root /usr/sbin/tcpd /opt/kde/bin/ktalkd + +A script is provided, to make the necessary change automatically. +Update your inetd.conf file just by running + +% ./post-install.sh + + +Anyway, you'll have to restart inetd after this. +On most &Linux; systems, do: + + +% killall + + +On newer systems, using xinetd, there is no more +/etc/inetd.conf, and you should edit or create +/etc/xinetd.d/talk instead, with those lines: + + +service talk +{ + socket_type = dgram + wait = yes + user = root + server = /usr/bin/ktalkd +} + +service ntalk +{ + socket_type = dgram + wait = yes + user = root + server = /usr/bin/ktalkd +} + + +then restart xinetd. + + + +Please inform me of any modification you had to undertake in order to +get &ktalkd; to compile or work on your platform. + + + + + +
+ + + + diff --git a/doc/kwifimanager/Makefile.am b/doc/kwifimanager/Makefile.am new file mode 100644 index 00000000..085981d9 --- /dev/null +++ b/doc/kwifimanager/Makefile.am @@ -0,0 +1,4 @@ + +KDE_LANG = en +KDE_DOCS = AUTO + diff --git a/doc/kwifimanager/index.docbook b/doc/kwifimanager/index.docbook new file mode 100644 index 00000000..07cc908d --- /dev/null +++ b/doc/kwifimanager/index.docbook @@ -0,0 +1,549 @@ + +KWiFiManager"> + LAN"> + + + + +]> + + + The &kwifimanager; Handbook + + + Stefan + Winter + +
+ swinter@kde.org +
+
+
+ +
+ &FDLNotice; + + 2002-05 + Stefan Winter + + 2005--05-11 + 3.5.0 + + The &kwifimanager; suite can be used to configure and monitor wireless &LAN; + cards. It consists of a stand-alone application and a module for the &kde; Control + Center. + + + KDE + wireless + Wi-Fi + wlan + WEP + wlan-ng + +
+ + Introduction + The &kwifimanager; suite is a set of tools which allows you to manage your wireless + &LAN; Network Interface card (PC-Card, PCI or miniPCI) under the K Desktop Environment. It + provides information about your current connection and lets you set up up to ten independent + configurations and use up to four configrations that are pre-configured by distribution-specific + scripts. If you are in a place where none of your preconfigured networks is available, + you can also dynamically switch to an available network with almost no configuration effort. + &kwifimanager; supports every wireless &LAN; card that uses the wireless extensions + interface. This includes virtually all wireless &LAN; cards that are operational at all + under the &Linux; operating system. + + + Using the &kwifimanager; suite + + The &kwifimanager; application + Purpose of the main &kwifimanager; application is to show the currently active + network configuration and to display connection quality and access points. + The main application is launched by either typingkwifimanager at + the command prompt of a console window or via the K Menu, where it is located by default in + theApplications group. If &kwifimanager; is already running + but minimised to the system tray then it can be restored by clicking once on the system tray icon. If there is more than one wireless &LAN; + card in your system, just open more than one instance of &kwifimanager;: every instance + will show information about a different card automatically. + The &GUI; elements of the application + are explained in the following subsections. + + Main window + The &kwifimanager; main window consists of five parts: + + Signal quality display + Here you can see the quality and type of the active connection. The uppermost icon + displays the general state of the wireless network via a set of pictograms: + + a wireless &LAN; card with a question tag means that no card was detected + or its state could not be determined + + + a single laptop means that a wireless &LAN; card is inserted and in + Infrastructure mode, but there is no radio signal from access-points. The card is + out of range and can not communicate to the infrastructure network. + + + a laptop that is connected to an access point means that a connection to an + access point is established. + + + two laptops mean that your system is in Ad-Hoc mode without access points. It + may or may not have established a Peer-to-Peer connection. + + + + Below these pictograms is a small quality meter. It displays, in a cellular-like + manner, the quality level of the current connection. This information is only available + in Infrastructure mode. In Ad-Hoc mode, the level is always 0. + This graphical information is supplemented by an integer value below the icon. It + shows the signal quality, and is computed in one of two ways: + + a directly reported value from the card if the card supports + Quality reporting + + + + (signal strength in dBm) - (noise level in dBm) for cards that + do not support that. + + You can manually change the method used by turning + File, Use alternate strength calculation + on or off. Turning the option on means to use the second method. If your card is out of + range, the value is 0; if no card is inserted or your card is in Ad-Hoc mode it will + show N/A. + + + Connection speed + An indicator for the current connection speed is shown at the right-hand side of + the main window above the configuration info. If the speed settings are set to AUTO, the + value will change once in a while as the card adjusts the connection speed according to + the signal quality. The scale of the bar graph will automatically adjust to up to 108 + MBit/s when the current connection speed exceeds 11 MBit/s. + + + Current configuration + Here you can find information about your card configuration. It displays the + following information: + + + the network with which the card is connected to / tries to connect to + (Searching for network: or Connected to + network:) + + + the MAC address of the access point to which the card is + connected. + If the card is in Infrastructure mode but out of range, an appropriate warning + (- no access point -) is displayed to indicate + that no connection is established. + In Ad-Hoc mode, the field shows an address that is associated with one of the + cards in the Ad-Hoc network. It displays a MAC address that has a non-global scope: + its second bit is set to 1, which often results in a prefix of 02: + instead of 00:. Many people think this is an error, but in fact it is + done on purpose to show that the cell you are connected to is not an actual physical + device, but rather an imaginary access point without a real physical address. + + Your card is the first card that enters Ad-Hoc mode with a given SSID. Then all other cards + entering Ad-Hoc mode with the same SSID will see your MAC-address, slightly modified: instead + of00:xx:yy:zz:aa:bb it will + show02:xx:yy:zz:aa:bb. This behavior is + intentional. + + + + on most cards (those that have the capability to report it), the frequency on + which the card is transmitting data and the corresponding channel number is + displayed. + + + your local IP (version 4) address, if available. If no address could be + retrieved from the networking subsystem, the word unavailable + is displayed. + + + encryption status (only if you have started &kwifimanager; asroot). The display will only show + off or active, but never the real key. + This is intentional in order to not reveal the WEP key to people + passing by the users screen. + + + + + Access Point information (bottom area) + The last line of the main window shows information about your AccessPoint. This + requires that your system administrator provided a list of MAC addresses with a + corresponding information. An example for such a list can be found + in$KDEDIR/share/apps/kwifimanager/locations/DE_BW_Karlsruhe_University.loc + + If you want to set up a new list, simply create a file in the same format and copy + it into the folder $KDEDIR/share/apps/kwifimanager/locations/ + + It will be automatically parsed at the next start of &kwifimanager;. If you have + a list and want to have it included in future releases of &kwifimanager;, simply + send it to the author or current maintainer. + + + Information about available networks + The lower-left area of the main window contains a button named Scan for + networks.... If you click on this button, &kwifimanager; will attempt + to retrieve a list of all networks that are in range of your card. The outcome of this + scan depends on two factors: + + the overall ability of your card and driver to perform network scans + + + if you have root permissions or not + + If your card or driver arent able to scan the network, your scanning + results will always be empty. If you are not the root user, the list may be incomplete + or outdated. + In order to receive a reliable, current list of access points you will need to + start the scan with root privileges, for example by using the &kdesu; utility to + start &kwifimanager; + If at least one network was found, you are presented with a table showing details + of the network. It has four columns that inform you about + + the network name (or the string (hidden cell) if the name + is not disclosed by the access point during the scan) + + + the type (whether is a Managed or an + Ad-Hoc network) + + + the signal strength of the network + + + and whether or not WEP encryption is used + + In case of an active WEP encryption, you can click on + that column and enter the network key. &kwifimanager; will automatically try to + guess if the key is a hexadecimal number or a string. + If the network information for the highlighted network is complete (&ie; all + columns contain meaningful information), you can use the button Switch to + network to enter the selected network. If &kwifimanager; has no root + privileges, you will be prompted with a password prompt to enter the root password in + order to change the network. + Clicking on Close dismisses the network information screen + without changes to the existing settings. + + + + Statistics Viewer + Optionally, by selecting Connection statistics in the + File menu, a separate window can be shown which displays the signal + level and noise level graphs of the last 240 seconds. The signal level is displayed in + blue and the noise level in red. The difference (SIGNAL minus NOISE) is the connection + quality which is displayed in the main window. + Some cards do not report meaningful noise information. If this is the case for your + card and you get annoyed by the irrelevant red line, you can disable showing the noise + level in the statistics window by unselecting + Config + Show noise level in statistics + in the &kwifimanager; main window. + + + Configuration Editor + By selecting + Config + Configuration Editor + you are taken to the control center + module of &kwifimanager;. In case you are not the root user, a window will pop up requesting the root password. This is because the configuration module + allows you to change network connectivity und uses ifconfig to make + changes, which requires root privileges. + + + Miscellaneous + There are some minor additional features worth of being mentioned. + + Acoustic Scanning + First, there is a feature named Acoustic Scanning. If this + option is enabled, the connection quality is converted into an acoustic signal. A higher + signal quality leads to a higher frequency of the beep output and to a + more rapid beeping. If you've ever seen the Star Trek(tm) series you will see some + parallels to their tricorders + + + + Network logging + A second feature is network logging. It just means that &kwifimanager; will log + the name of the network you are connecting to every time your network changes. + This option is most useful when searching for the special network name + any. In this mode, the card will log into any network it finds. The + logfile's position is $HOME/.kde/share/apps/kwifimanager/wireless-log + + + + Disabling the wireless network + You can completely disable the card by selecting the option File + Disable radio. Using this option will turn off the cards + transmitter which effectively turns it off and saves a little bit of energy. This will + only work for your card if it accepts changes to its + txpower property. + + + + + The system tray icon + When &kwifimanager; is launched, it installs a small icon in the system tray. The icon contains + parts of the information of the main window, namely the bar graph and optionally the signal strength + number. If you hover over the icon with the mouse for a few seconds, a tooltip will appear that + contains the currently connected network name. Whether or not the strength number shall be + shown can be configured via Config, + Show Strength Number in System Tray. + If you have configured &kwifimanager; to stay in the system tray when clicking on the + X button, the icon will stay in the tray persistently unless you really exit + the application by clicking on File, Quit. + You can always hide the main application to the system tray by clicking on the tray icon. Similarly, + to restore the main application from the tray, just click on it once. + + + The Control Center module + The configuration module in the &kcontrolcenter; is perhaps the most useful part of + the &kwifimanager; suite. Here you can actually change the basic settings of your + wireless &LAN; card. The module can manage up to ten independent configurations for the + card. If you dont need that many configurations, you can reduce the number of configs + shown at any time by changing the Number of Configurations entry. + If you have configured your wireless settings with a distribution-specific tool, chances are good + that the &kcontrolcenter; module will automatically detect this and also read in and show that + configuration. In any case these configurations will be read-only, because it is the distribution's + job to handle updating these settings and the module should not interfere with their internal magic. + Up to five additional preset configurations can be shown in addition to the ten + that are self-definable. These configurations will have the name Vendor x + to distinguish them from the others. + The &kcontrolcenter; can even automatically set your card up whenever you start the module. + Since establishing (or bringing down) a network connection is a security sensitive operation, + any changes to the configuration can only be done by root. + + The Configuration Tabs + The configurations are split up in three parts: + + general configuration settings (like the network name) + + + encryption settings + + + power saving settings + + + These parts are explained in the following sections. + + + General settings + The upper part of the control center module consists of one to ten tabs labelled + Config 1 through Config 10. Each of these tabs + can hold a configuration for your WLAN card. In addition (as explained above) up to five vendor-specific + configurations may be visible, labelled Vendor 1 through Vendor 5. + The most important settings are always visible, the cryptography and power management + options are only shown when activated. The perhaps most important element in each + configuration tab is the fieldNetwork name. Here you can specify + which network you would like to log into. You can either specify the name of your network + directly, or you can try a scan on all available networks by setting the network name to + any. + In addition to the network name, you have to specify the type of network to log into. + That's the purpose of the button groupOperation mode. The + optionManaged means that the network consists of designated + base stations, so-called access points or sometimes residential + gateways. This is the most common operation mode for company networks. The second + option,Ad-hoc means that your network is just a direct + connection between computers, without access points. The three other options + (Repeater, Master and Secondary) + are only very seldomly used. If you want to use them, please be aware that these settings are simply + passed to the iwconfig program and have not been tested extensively. In case something doesnt + work as expected, you are welcome to send a bug report. + You can optionally set the connection speed for your connection. The setting + auto should do for most uses, since the card will determine the + appropriate speed itself. However, if you find that the speed changes every few seconds, + for example when you have a weak connection, you can set the speed manually. + Below these configuration items you will find a field namedExecute script on + connect:. Here you can enter the name of a script to execute after setting up + the network connection. It will be + executed whenever you hit the Activate configuration button and, + optionally, automatically when you start the Control Center module. The script will have + root rights. This may lead to problems + if you want to start an X application in the script and the X server belongs to someone + else than root. You can make such scripts work correctly if you execute the X application + via + kdesu + + USERNAME + + COMMAND + . Or, you can instruct your X server to also allow connections coming from + root. You can do this with the xhost program. + + + Cryptography settings + The checkbox Use encryption determines whether or not encryption + shall be activated. If it is checked, a button labelled Configure... becomes + available which allows you to configure the details of encryption. + After pushing the button, you are presented the following settings in a new dialog: + + + + Key to use: + + + You can define up to four secret keys for each configuration; in this field you + can set which one you want to use to send encrypted packets. The card can always + receive packets that are encrypted with any of the keys. + You can achieve asymetrical encryption (different keys for sending and receiving) + if you configure your access point to send packets with a different key than the card. Just make + sure that the partner station has the required key in any one of its key slots. + + + + + Crypto mode: + + + When encryption is activated, there are two ways to deal with incoming + non-encrypted packets: discard or allow. When you set your card for Open, + the card will also listen to non-encrypted packets. + Restricted will only allow encrypted network packets, + any other packets are discarded. + + + + + Crypto keys: + + + This box lets you specify the secret keys to use for cryptography. To protect + your passwords, only asterisks will be shown when you enter a password. The &kcontrolcenter; + module will automatically try to guess whether you want to set an encryption string + or a hexadecimal number by checking the input length: string keys are usually 5 or 13 + characters long (for 64- or 128-Bit key lengths) whereas hex values are 10 or 26 characters + long (please do not put a 0x in front of hex keys). + + + + Be aware that the built-in cryptography support (named WEP for Wired Equivalent + Privacy) is not very safe at all. See for details. + + + Power saving settings + The last configuration element that remains to be described is the power management. + When checking the box Enable Power Management a button for the + configuration of the setting will become active. After clicking this button, a new dialog will open + and you will be presented + some options that can help you save energy. The first two input fields named + Sleep timeout and Wakeup period + describe the periodicity of network online times for your wireless &LAN; card. The card will + turn the radio antenna off for the time period (in seconds) specified in + Sleep timeout. Afterwards it will be active for Wakeup + period and will in that time establish the network connection and send/receive + packets that queued up during the sleep time. If no network + connection is found, it will go to sleep again immediately and the cycle begins again. The box named + Receive packets below lets you specify which packets the card + should listen to when awaken. You can either select Unicast only + (which will only let your card listen to packets sent specifically to your card), + Multi/Broadcast only (will only listen to packets sent to multiple + machines and discard packets directed to your card) or Both. Most + people should select the default value Both. + + + + Auto-configuration on &kcontrolcenter; Module startup + If you wish, you can make &kwifimanager; initialize your wireless &LAN; card + whenever you start the &kcontrolcenter; module. To do so, check the box Load + preset configuration on startup and select the configuration you want to use + in the listbox below. If you want to set the card to + these settings at once, push the button Activate + configuration. + + + Autodetecting your device + &kwifimanager; needs to know the interface name of your wireless &LAN; card to + apply any settings. You can either enter the information (⪚ + eth1 orwlan0) manually in the input field + on the right-hand side ofSettings apply to interface:, or let &kwifimanager; + auto-detect the interface. To do so, push the buttonAutodetect + interface. This will perform a scan on all interfaces listed in /proc/net/dev to find your card. The result of the scan + will show up in the input field beside the button. If the field remains empty, no card was + found. Please note that &kwifimanager; uses the wireless extensions to detect cards. + If you use a card controlled by the wlan-ng package, &kwifimanager; only shows correct + results if your driver has a compatibility layer for the wireless extensions + built-in. In the case that there are multiple wireless LAN cards present on the system, + scanning stops after the first card found. So, if you want to apply the settings to a different + card than the one that was detected during the scan, you need to enter its interface name + by hand. + + + + + License and contributors + Documentation copyright © Stefan + Wintermail@stefan-winter.de. &underFDL; &underGPL; + + + + Further Information + This appendix contains some extra information of items concerning wireless &LAN; that + are not directly related to &kwifimanager;. + + Notes on the MAC address display in Ad-hoc mode + At first glance, the MAC address in the field Access Point seems to + be wrong in Ad-hoc mode because it changes the first two digits of the MAC address + to02. But actually, this is a hardcoded feature in + wireless &LAN; cards. + Usually a card is connected to a real access point. Then the correct MAC + address is shown. If you change to Ad-hoc (or Peer-to-peer) mode, one of the + computers must act as a server for the other computers. The first computer that enters a + network will set itself as server. So, all other computers connecting to the same Ad-hoc + network will see that first computer as network server. But since this computer is not a + real server (that is, it is not a permanently available access point), + clients should be aware that the network they are connecting to is not a permanent one. IEEE + standards for MAC addresses have a place reserved for such (rare) occasions: MAC addresses that are + not globally valid have a bit set to one that shows that these addresses are + locally administered. This bit is the second bit in transmit order, and the seventh + bit in logical order and will hence raise the number of the MAC's first digit block from 00 to 02. + You can compare this sort of address to the non-global IP addresses like192.168.*.*. + + So, the implementors of wireless networking agreed to give thesevirtual + network servers a MAC address that is within the locally administered scope. + To keep this virtual MAC address unique, they used a little trick: they only changed the + first segment of the MAC address of the wireless &LAN; card, and since the remaining + segments are still unique in the world, they have a unique address to use as network + server. + + + Security considerations on <acronym>WEP</acronym> cryptography + + WEP cryptography is not very secure at all. A paper from + cryptography analysts called the encryption algorithm kindergarten + cryptography. Actually, software exists that exploits a huge security hole in the + encryption standard. This software listens to the encrypted network traffic, analyzes it, + and after only a few hours it reveals the password to enter the network in clear text. The + more traffic on the network, the easier it is to find out the password because some packets are + particularly weak because they carry a bad so-called initialisation vector (IV). Recent access + points try to avoid these bad IVs, so it is getting harder to exploit the hole. + If you are truly concerned about your security, donot use plain + WEP. If you are just setting up a two-computer home network, well, then + I guessWEP should do. + There are many alternatives to WEP encryption. Its successors WPA and + WPA2 are better designed and do a better job protecting your traffic, for example by dynamically changing + the keys after a while. + If you dont want to rely on the basic safety of the network link you could use + SSH to communicate over the network. SSH is a + program suite that encrypts data with its own algorithm, which is very secure. Another + option is to use PPTP, the Point-to-Point-Tunneling protocol. However, + even PPTP seems to be a bit leaky concerning encryption security. And + finally, you could set up an IPSec tunnel (VPN connection) for your encrypted connections. As of yet, this + encryption seems to be very safe and flexible. + + + + Compilation and Installation &install.intro.documentation; + &install.compile.documentation; &documentation.index;
diff --git a/doc/lisa/Makefile.am b/doc/lisa/Makefile.am new file mode 100644 index 00000000..085981d9 --- /dev/null +++ b/doc/lisa/Makefile.am @@ -0,0 +1,4 @@ + +KDE_LANG = en +KDE_DOCS = AUTO + diff --git a/doc/lisa/index.docbook b/doc/lisa/index.docbook new file mode 100644 index 00000000..d93e31d5 --- /dev/null +++ b/doc/lisa/index.docbook @@ -0,0 +1,694 @@ + +LISa"> + + resLISa"> + + + +]> + + + + + +The &lisa; Handbook + + + +Alexander +Neundorf + +
neundorf@kde.org
+
+
+ + + +
+ + +2001 +Alexander Neundorf + + +2001-07-07 +0.01.00 + + +&lisa; is intended to provide a kind of network +neighborhood, but only relying on the TCP/IP protocol stack, no +SMB or anything else required. + +This is the handbook to both the LAN +Information Server (&lisa;) and the Restricted LAN +Information Server (&reslisa;) + + + + +KDE +kdenetwork +LAN +network +network neighborhood + + +
+ + +Introduction + +&lisa; is intended to provide a kind of network +neighborhood, but only relying on the TCP/IP protocol stack, no +smb or whatever. + +It is completely independent from &kde;/&Qt;. + +The list of running hosts is provided via TCP +port 7741. + +&lisa; supports two ways of finding hosts: + + + +You give &lisa; a range of IP addresses, then +&lisa; will send ICMP echo requests to all given +IP addresses, and wait for the answers. + + +You can tell &lisa; to execute nmblookup +. The command line tool +nmblookup must be installed from the Samba package. +nmblookup sends a broadcast to +the attached networks, and all hosts running SMB +services will answer this broadcast. + + + + + + + +How it works + +In the configuration file you provide a range of IP-addresses +which &lisa; should check to see whether they are running. + +In the most simple case this could be your network +address/subnetmask, then &lisa; would check every possible host of your +network to see if it is running. + +The hosts are checked using ICMP echo +requests. To be able to send and receive ICMP echo +requests and replies the program has to open a so-called raw +socket. Therefore it needs root privileges. This socket is opened +right after the start of the program, after successfully opening the +socket root privileges are dropped immediately (see +main.cpp and +strictmain.cpp). + +If you configure &lisa; so that it also uses +nmblookup, it will popen("nmblookup +\"*\"") and then parse the results. + +Since the ICMP requests and the broadcasts can +cause some network traffic if there are more than one such server +running in one network, the servers cooperate with each other. Before +they start pinging (or nmblookup), they send a +broadcast on port 7741. + +If somebody answers this broadcast, they will retrieve the +complete list of running hosts via TCP port 7741 from +this host and will not start to ping (or +nmblookup). + +If nobody answers, the host which sent the broadcast will start +pinging the hosts (or nmblookup) and then open a +socket which listens for the mentioned broadcasts. If the host received +an answer to his broadcast, it won't have the socket for listening to +the broadcasts open. So usually exactly one of the servers will have +this socket open and only this one will actually ping (or +nmblookup) the hosts. + +In other words, the servers are lazy, they work like I will +only do something if nobody else can do it for me. + +There is another feature which reduces the network load. + +Let's say you configured &lisa; to update every 10 minutes. Now +you don't access your server very often. If nobody accesses the server +for the last update period, the server will update (either itself or +from the one which actually does the work) and then double its update +period, &ie; the next update will happen after 20 minutes. + +This will happen 4 times, so if nobody accesses the server with +update period 10 minutes for a long time, its update interval will +increase up to 160 minutes, almost three hours. If then somebody +accesses the data from the server, he will get an old list ( up to 160 +minutes old). With accessing the server will reset its update interval +to its initial value, &ie; 10 minutes and immediately start updating if +the last update is more than these 10 minutes over. This means if you +get a very old list, you can try some seconds later again and you should +get a current version. + +This will have fast effect for the servers, which don't ping (or +nmblookup) theirselves, since only one user usually accesses them, and +it will have less effect for the server which does the pinging (or +nmblookup), since this server is accessed from all +other servers in the network. + +This way it is possible that many hosts in a network run this +server, but the net load will remain low. For the user it is not +necessary to know wether there is a server (&ie; a name server or +fileserver or whatever) in the network which also runs &lisa;. He can +always run &lisa; locally and &lisa; will detect if there is one, +transparently to the user. + +The first client for &lisa; is an ioslave for &kde; 2, so the user +can enter there lan://localhost/ or +lan:/, which will both contact &lisa; on the own +system. + +If there is a machine which runs all the time and the user knows +that this machine also runs &lisa;, he can use his &lisa; client +directly with this server (would be with the mentioned ioslave +lan://the_server_name/). + +If you don't want that your &lisa; takes part in the broadcasting, +but always does the pinging itself, make it use another port with the +command line option or . This +is not recommended! + +If you send SIGHUP to &lisa;, it will reread +its configfile. If you send SIGUSR1 to &lisa;, it +will print some status information to stdout. + +The data provided over the socket has a simple format: + +<decimal ip address in network byte order><one space +0x20><full name of the host><a terminating +'\0'><newline '\n'< +and the last line +0 succeeded<'\n'> + + +For example, + +17302538 some_host.whatever.de +18285834 linux.whatever.de +17827082 nameserver.whatever.de +0 succeeded + +This should make it easy parseable. + +If there are very strict security rules in your network, some +people might consider the pinging a potential attack. If you have +problems with this, try the restricted version, &reslisa;. + + + + +&reslisa; + +If you hav very strict security rules in your network or you don't +want to have another port open or whatever, you can use +&reslisa;. + +With &reslisa; you can't ping whole networks and address ranges, +you can give &reslisa; up to currently 64 hosts by their names in its +config file. These will be pinged. You are still able to use +nmblookup. + +&reslisa; will also only provide the information over a unix +domain socket, &ie; not over the network. The name of the socket is +/tmp/resLisa-YourLoginname so &reslisa; can be +safely run by more users on one machine. + +Since it should also not produce a security risk of any kind it is +safe to install &reslisa; setuid root. root privileges will be dropped right +after startup (see strictmain.cpp), they are only +needed to create a raw socket for sending the ICMP +echo requests. + +It will also not send or receive broadcasts. The first client for +this is also an ioslave for &kde; 2 (rlan:/ in +&konqueror; for example.) + + + + +The Configuration File + +Now an example config file: + + +PingAddresses = 192.168.100.0/255.255.255.0;192.168.100.10-192.168.199.19;192.168.200.1;192-192.168-168.100-199.0-9; +PingNames = bb_mail; +AllowedAddresses = 192.168.0.0/255.255.0.0 +BroadcastNetwork = 192.168.100.0/255.255.255.0 +SearchUsingNmblookup = 1 #also try nmblookup +FirstWait = 30 #30 hundredth seconds +SecondWait = -1 #only one try +#SecondWait = 60 #try twice, and the second time wait 0.6 seconds +UpdatePeriod = 300 #update after 300 secs +DeliverUnnamedHosts = 0 #don't publish hosts without name +MaxPingsAtOnce = 256 #send up to 256 ICMP echo requests at once + + + +<option>PingAddresses</option> + +This is probably the most important entry. + +Here you say which addresses will be pinged. You can specify +multiple ranges, they are divided by semicolons. + +There are four possible ways to define addresses: + + + +net address/network mask + +192.168.100.0/255.255.255.0, &ie; an IP address + and the assigned network mask. + +This doesn't have to be the network address and netmask of your +machine. For example, if you have 10.0.0.0/255.0.0.0 as your own +address, you could specify 10.1.2.0/255.255.255.0 if you are only +interested in these addresses. The combination IP +address-network mask must be divided by a slash / and the +address does not have to be a real network address, it can also be a +host address of the desired network, &ie; 10.12.34.67/255.0.0.0 is the +same as 10.0.0.0/255.0.0.0 . + + + + +a range of IP addresses + +For example: 192.168.100.10-192.168.199.19 + +An IP-address where pinging will start and an +IP-address where pinging will end. + +Both addresses must be divided by a -. + +In this example this would produce 199-100+1=100, 100*256=25.600, +25.600+(19-10+1)=25.590 addresses + + + + +An IP address, as represented by ranges of each +of the four decimal numbers + +An IP address can be represented by its four +decimal numbers, and you can specify ranges four each of these four +numbers: 192-192.169-171.100-199.0-9 + + +In this example all IP addresses with first +number 192, second number from 168 to 168, third number from 100 up to +199 and last number from 0 up to 9 will be pinged. This would give +1*1*100*10=1.000 addresses. + +This is probably only useful in very seldom cases. Here you have +to provide ranges for every four numbers, always divided by +-. + + + + +Single IP addresses or host names + +The IP address or host name of any machine you +are particularly interested in. + + + + +It is also valid to leave this entry empty. + + + + +<option>PingNames</option> + +Here you can additionally specify hosts to ping using their names. +The names have to be divided by semicolons. + +It is also valid to leave this entry empty. + + + + +<option>AllowedAddresses</option> + +This is also very important. &lisa; will only ping addresses, +accept clients and answer broadcasts from addresses, which are covered +by the addresses given in this line. You can add up to 32 network +addresses/network masks or single addresses. Divide them by ; and don't +put empty space between the addresses! + +For example, 192.168.0.0/255.255.0.0;192.169.0.0 + +A complete network and a single address are valid. Always make +this as strict as possible, usually your network address/subnetmask is a +good choice. + + + + +<option>BroadcastNetwork</option> + +This entry contains exactly one network address/subnet mask. To +this network broadcasts will be sent. Usually this should be your own +network address/subnetmask, for example: 192.168.0.0/255.255.0.0 + + + + +<option>SearchUsingNmblookup</option> + +Here you can give 0 or +1. 1 means that &lisa; +will execute nmblookup and parse +the output from this command. This produces less network traffic than +the pinging, but you will only get hosts which have a +SMB service running (&Windows; machines or machines +running samba). + +If you enable this option and also give IP +addresses to ping, then nmblookup will be executed +first and then the pinging will start. Then only addresses will be +pinged, which were not already delivered from +nmblookup. This should slightly decrease the network +load. + + + + +<option>FirstWait</option> + +If &lisa; pings, &ie; if it sends the ICMP echo +requests, it sends a bunch of requests at once, and the it will wait for +the number of hundredth seconds you specify here. Usually values from 5 +to 50 should be good, the maximum is 99 (gives 0.99 seconds, a very long +time). Try to make this value as small as possible while still finding +all running hosts. + + + + +<option>SecondWait</option> + +After &lisa; has sent the echo requests the first time, it can be +possible that some hosts were not found. To improve the results, &lisa; +can ping a second time. This time it will only ping hosts, from which it +didn't receive answers. If you have good results with pinging only once, +you can disable the second time with setting SecondWait to +-1. + +Otherwise it might be a good idea to make this value a little bit +bigger than the value for , since the hosts +which were not found on the first try, are probably slower or further +away so they might take some milliseconds longer to answer. Usually +values from 5 to 50 should be good or -1 to disable the second scan. +The maximum is 99 (gives 0.99 seconds, a very long time). + + + + +<option>UpdatePeriod</option> + +This is the interval after which &lisa; will update. After this +time &lisa; will again ping or nmblookup or get the +list of hosts from the &lisa; server which actually does the +pinging. + +Valid values are between 30 seconds and 1800 seconds (half an +hour). If you have a big network, don't make the interval too small (to +keep network load low). Values from 300 to 900 seconds (5 to 15 minutes) +might be a good idea. + +Keep in mind that the update period is doubled if nobody accesses +the server, up to 4 times, so the interval will become 16 times the +value given here and will be reseted to the value given here if somebody +accesses the server. + + + + +<option>DeliverUnnamedHosts</option> + +If an answer to an echo request from an IP address was received, +were &lisa; could not determine a name, it will be only delivered over +the port if you set this to 1. + +I am not really sure if this is a useful feature, but maybe there +are some infrastructure devices in your network without assigned names, +so they don't have to be published. Set this to 0 if you want to keep +them secret ;-) If unsure, say 0. + + + + +MaxPingsAtOnce + +When sending the pings (echo requests), &lisa; sends a bunch of +these at once and then waits for the answers. By default there are 256 +pings sent at once, usually you should not need the change this +value. If you make it much bigger, the internal receive buffers for the +answers to the echo requests may become to small, if you make it to +small, the updating will be slower. + + + + +Three more example files + + +FIXME + +You are member of a small network with 24 bit network mask, &ie; +up to 256 hosts: + + +PingAddresses = 192.168.100.0/255.255.255.0 +AllowedAddresses = 192.168.100.0/255.255.255.0 +BroadcastNetwork = 192.168.100.0/255.255.255.0 +SearchUsingNmblookup = 0 #don't use nmblookup +FirstWait = 20 #20 hundredth seconds +SecondWait = 30 #30 hundredth seconds on the seconds try +UpdatePeriod = 300 #update after 300 secs +DeliverUnnamedHosts = 0 #don't publish hosts without name + + + + + +Configuration file for hosts running <acronym>SMB</acronym> +only + +You are only interested in hosts running SMB +services and you don't have routers in your network: + + +AllowedAddresses = 192.168.100.0/255.255.255.0 +BroadcastNetwork = 192.168.100.0/255.255.255.0 +SearchUsingNmblookup = 1 #use nmblookup +UpdatePeriod = 300 #update after 300 secs +DeliverUnnamedHosts = 0 #don't publish hosts without name + + + + +Configuration file using both <command>nmblookup</command> and +pinging + +The same network, but here both nmblookup and pinging is +used. + + +PingAddresses = 192.168.100.0/255.255.255.0 +PingNames = bb_mail +AllowedAddresses = 192.168.0.0/255.255.0.0 +BroadcastNetwork = 192.168.100.0/255.255.255.0 +SearchUsingNmblookup = 1 #also try nmblookup +FirstWait = 30 #30 hundredth seconds +SecondWait = -1 #only one try +#SecondWait = 60 #try twice, and the second time wait 0.6 seconds +UpdatePeriod = 300 #update after 300 secs +DeliverUnnamedHosts = 0 #don't publish hosts without name +MaxPingsAtOnce = 256 #send up to 256 ICMP echo requests at once + + + + + +Configuration file for &reslisa; + +And now a configuration file for &reslisa;, PingAddresses is not +used by &reslisa;, neither is BroadcastNetwork. + + +PingNames = bb_mail;some_host;some_other_host +AllowedAddresses = 192.168.0.0/255.255.0.0 +SearchUsingNmblookup = 1 # use nmblookup +FirstWait = 30 #30 hundredth seconds +SecondWait = -1 #only one try +#SecondWait = 60 #try twice, and the second time wait 0.6 seconds +UpdatePeriod = 300 #update after 300 secs +DeliverUnnamedHosts = 1 #also publish hosts without name +MaxPingsAtOnce = 256 #send up to 256 ICMP echo requests at once + + + + + + + +Command Line Options and Other Usage + +The following command line options are supported: + + + +, + +Prints brief version information. + + + + +, + +Gives an overview of the command line options + + + + +, + +Search at first for +$HOME/.lisarc, then for +/etc/lisarc. This is the default behavior. + + + + +, + +Search first for +$HOME/.kde/share/config/lisarc, then +for +$KDEDIR/share/config/lisarc. + + + + +, + +Looks for the file lisarc in every folder +returned by running kde-config + config + + + + +, +FILE + +Read FILE and no other configuration +file. + + + + +, +PORTNR + +Start the server on this portnumber. If you use this, &lisa; +won't be able to cooperate with other &lisa;'s on the network. This +option is not available for &reslisa; + + + + + +If you send the Hangup-Signal to &lisa; or &reslisa;, it will reread its +configuration file (killall ). + +If you send the User1-Signal to &lisa; or &reslisa;, it will print +some status information to the standard output +(killall ). You won't see anything if the console from +which &lisa;/&reslisa; was started has terminated. + + + + + + +Credits and Licenses + +&lisa; and &reslisa; copyright 2000, 2001, Alexander +Neundorf + + + + + +Have fun, Alexander Neundorf neundorf@kde.org + +&underFDL; +&underGPL; + + + + +Installation + +&lisa; and &reslisa; need a libstdc++ (it uses only the +string-class from it), it does not need either &Qt; +nor &kde;. + +&install.compile.documentation; + + +Other Requirements + +Both &reslisa; and &lisa; open a so called raw +socket to send and receive ICMP echo requests +(pings). To do this, they need root privileges. + + +&lisa; offers a service on TCP port 7741, and +should be installed by root +and started when the system comes up. It depends greatly on your &OS; +how to achieve this. + +&reslisa; is intended to be started per user, it doesn't offer +anything to the network. It needs to be installed setuid root. + +If you use the rlan ioslave from &kde; 2, +&reslisa; can be started automatically. + +&lisa; reads the file lisarc, &reslisa; reads +the file reslisarc. If you want to be able to +configure both from &kcontrol;, you have to start them using the command +line switch . + +For more information where they look for configuration files read +the chapter on . + + + +
diff --git a/filesharing/Makefile.am b/filesharing/Makefile.am new file mode 100644 index 00000000..61d66854 --- /dev/null +++ b/filesharing/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = advanced simple + +messages: rc.cpp + $(EXTRACTRC) `find . -name '*.ui'` > rc.cpp + $(XGETTEXT) rc.cpp `find . -name '*.cpp' -or -name '*.h'` -o $(podir)/kfileshare.pot diff --git a/filesharing/advanced/Makefile.am b/filesharing/advanced/Makefile.am new file mode 100644 index 00000000..135d9702 --- /dev/null +++ b/filesharing/advanced/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = nfs kcm_sambaconf propsdlgplugin diff --git a/filesharing/advanced/kcm_sambaconf/ChangeLog b/filesharing/advanced/kcm_sambaconf/ChangeLog new file mode 100644 index 00000000..218c7bd3 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/ChangeLog @@ -0,0 +1,53 @@ +- 0.5 + * New features : + - Added new options of Samba 3.0.2 + * Bugfixes : + - Fixed a crash if files are deleted from a directory + that is configured by KSambaPlugin at the same time. +- 0.5 Beta 2 + * New features : + - Support of Samba 3.0.1 + * Bugfixes : + - Should now be compileable with Qt 3.0 +- 0.5 Beta + * New features : + - Works with Samba 3.x + - Supports all options of Samba 3.0 and Samba 2.x + - Supports loading and saving of remote Samba configuration files + with KIO slaves (e.g. fish) + - Complete rewrite of the user permission tab + - Accelerator keys added where possible + - Tab ordering fixed everywhere + - Redesigned big parts of the interface + + * Bugfixes : + - Fixed crash, when smb.conf wasn't found + - Some other bugs fixed + +- 0.4.1 + * Bugfixes : + - "user only" changed to "only user" + - Changings in the advanced tab will enable the apply button + * New options : + - Many new options have been added to the Share dialog + - The layout of the Share dialog has been changed + * Automake : + - Doesn't depend on automake 1.7, but on version 1.5.x + +- 0.4b + * Bugfixes : + - General : + * adding a blank line between two sections in the smb.conf + - Konqueror Plugin : + * availabe changed to available + * when changing a share to be not shared the entry is removed from the smb.conf + - KControl Center Module : + * Changing of the socket options works now + * New layout : + - The KControl Center module layout has changed much + - The Konqueror plugin layout has been changed a little + * New options : + - Many new options have been added to the KControl Center module + + + diff --git a/filesharing/advanced/kcm_sambaconf/Makefile.am b/filesharing/advanced/kcm_sambaconf/Makefile.am new file mode 100644 index 00000000..55cd699d --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/Makefile.am @@ -0,0 +1,46 @@ +KDE_ICON = AUTO +noinst_LTLIBRARIES = libfilesharesamba.la +kde_module_LTLIBRARIES = kcm_kcmsambaconf.la + +AM_CPPFLAGS = $(all_includes) +METASOURCES=AUTO + +kcm_kcmsambaconf_la_SOURCES = \ + kcminterface.ui \ + kcmprinterdlg.ui \ + printerdlgimpl.cpp \ + kcmsambaconf.cpp \ + joindomaindlg.ui +kcm_kcmsambaconf_la_COMPILE_FIRST = usertab.h share.h + + +kcm_kcmsambaconf_la_LIBADD = \ + -lkdeprint \ + libfilesharesamba.la + +kcm_kcmsambaconf_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +libfilesharesamba_la_SOURCES = \ + sambafile.cpp \ + share.ui \ + sharedlgimpl.cpp \ + sambashare.cpp \ + socketoptionsdlg.ui \ + common.cpp \ + userselectdlg.ui \ + groupselectdlg.ui \ + usertab.ui \ + usertabimpl.cpp \ + filemodedlg.ui \ + filemodedlgimpl.cpp \ + smbpasswdfile.cpp \ + passwd.cpp \ + hiddenfileview.cpp \ + dictmanager.cpp \ + qmultichecklistitem.cpp \ + smbconfconfigwidget.cpp \ + linuxpermissionchecker.cpp \ + expertuserdlg.ui + + +xdg_apps_DATA = kcmsambaconf.desktop diff --git a/filesharing/advanced/kcm_sambaconf/TODO b/filesharing/advanced/kcm_sambaconf/TODO new file mode 100644 index 00000000..01abf9f2 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/TODO @@ -0,0 +1,2 @@ +- Translations +- Wizards diff --git a/filesharing/advanced/kcm_sambaconf/common.cpp b/filesharing/advanced/kcm_sambaconf/common.cpp new file mode 100644 index 00000000..1a8a8c13 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/common.cpp @@ -0,0 +1,72 @@ +/*************************************************************************** + common.cpp - description + ------------------- + begin : Tue June 6 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#include +#include +#include + +#include "common.h" + +void setComboToString(QComboBox* combo,const QString & s) +{ + int i = combo->listBox()->index(combo->listBox()->findItem(s,Qt::ExactMatch)); + combo->setCurrentItem(i); +} + +bool boolFromText(const QString & value, bool testTrue) +{ + QString lower = value.lower(); + + if (testTrue) { + if (lower=="yes" || + lower=="1" || + lower=="true" || + lower=="on") + return true; + else + return false; + } else { + if (lower=="no" || + lower=="0" || + lower=="false" || + lower=="off") + return false; + else + return true; + } +} + +QString textFromBool(bool value) +{ + if (value) + return "yes"; + else + return "no"; +} + + diff --git a/filesharing/advanced/kcm_sambaconf/common.h b/filesharing/advanced/kcm_sambaconf/common.h new file mode 100644 index 00000000..a839e883 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/common.h @@ -0,0 +1,40 @@ +/*************************************************************************** + common.h - description + ------------------- + begin : Tue June 6 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#ifndef COMMON_H +#define COMMON_H + +class QString; +class QComboBox; + + +void setComboToString(QComboBox*,const QString &); +bool boolFromText(const QString & value,bool testTrue=true); +QString textFromBool(bool value); + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/dictmanager.cpp b/filesharing/advanced/kcm_sambaconf/dictmanager.cpp new file mode 100644 index 00000000..51f63266 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/dictmanager.cpp @@ -0,0 +1,216 @@ +/*************************************************************************** + dictmanager.cpp - description + ------------------- + begin : Wed Jan 1 2003 + copyright : (C) 2003 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sambashare.h" +#include "dictmanager.h" +#include "common.h" + + +DictManager::DictManager(SambaShare* share): + lineEditDict(40,false), + checkBoxDict(40,false), + urlRequesterDict(40,false), + spinBoxDict(40,false), + comboBoxDict(20,false), + comboBoxValuesDict(20,false) +{ + _share = share; +} + +DictManager::~DictManager() { +} + +void DictManager::handleUnsupportedWidget(const QString & s, QWidget* w) { + w->setEnabled(false); + QToolTip::add(w,i18n("The option %1 is not supported by your Samba version").arg(s)); +} + +void DictManager::add(const QString & key, QLineEdit* lineEdit) { + if (_share->optionSupported(key)) { + lineEditDict.insert(key,lineEdit); + connect(lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(changedSlot())); + } else + handleUnsupportedWidget(key,lineEdit); +} + +void DictManager::add(const QString & key, QCheckBox* checkBox){ + if (_share->optionSupported(key)) { + checkBoxDict.insert(key,checkBox); + connect(checkBox, SIGNAL(clicked()), this, SLOT(changedSlot())); + } else + handleUnsupportedWidget(key,checkBox); +} + +void DictManager::add(const QString & key, KURLRequester* urlRq){ + if (_share->optionSupported(key)) { + urlRequesterDict.insert(key,urlRq); + connect(urlRq, SIGNAL(textChanged(const QString &)), this, SLOT(changedSlot())); + } else + handleUnsupportedWidget(key,urlRq); +} + +void DictManager::add(const QString & key, QSpinBox* spinBox){ + if (_share->optionSupported(key)) { + spinBoxDict.insert(key,spinBox); + connect(spinBox, SIGNAL(valueChanged(int)), this, SLOT(changedSlot())); + } else + handleUnsupportedWidget(key,spinBox); +} + +void DictManager::add(const QString & key, QComboBox* comboBox, QStringList* values){ + if (_share->optionSupported(key)) { + comboBoxDict.insert(key,comboBox); + comboBoxValuesDict.insert(key,values); + connect(comboBox, SIGNAL(activated(int)), this, SLOT(changedSlot())); + } else + handleUnsupportedWidget(key,comboBox); +} + + +void DictManager::load(SambaShare* share, bool globalValue, bool defaultValue){ + QDictIterator checkBoxIt( checkBoxDict ); + + for( ; checkBoxIt.current(); ++checkBoxIt ) { + checkBoxIt.current()->setChecked(share->getBoolValue(checkBoxIt.currentKey(),globalValue,defaultValue)); + } + + QDictIterator lineEditIt( lineEditDict ); + + for( ; lineEditIt.current(); ++lineEditIt ) { + lineEditIt.current()->setText(share->getValue(lineEditIt.currentKey(),globalValue,defaultValue)); + } + + QDictIterator urlRequesterIt( urlRequesterDict ); + + for( ; urlRequesterIt.current(); ++urlRequesterIt ) { + urlRequesterIt.current()->setURL(share->getValue(urlRequesterIt.currentKey(),globalValue,defaultValue)); + } + + QDictIterator spinBoxIt( spinBoxDict ); + + for( ; spinBoxIt.current(); ++spinBoxIt ) { + spinBoxIt.current()->setValue(share->getValue(spinBoxIt.currentKey(),globalValue,defaultValue).toInt()); + } + + loadComboBoxes(share,globalValue,defaultValue); + +} + +void DictManager::loadComboBoxes(SambaShare* share, bool globalValue, bool defaultValue) { + QDictIterator comboBoxIt( comboBoxDict ); + + for( ; comboBoxIt.current(); ++comboBoxIt ) { + QStringList *v = comboBoxValuesDict[comboBoxIt.currentKey()]; + QString value = share->getValue(comboBoxIt.currentKey(),globalValue,defaultValue); + + if (value.isNull()) + continue; + + value = value.lower(); + + + int comboIndex = 0; + + QStringList::iterator it; + for ( it = v->begin(); it != v->end(); ++it ) { + QString lower = (*it).lower(); + if ( lower == "yes" && + boolFromText(value)) + break; + + if ( lower == "no" && + ! boolFromText(value,false)) + break; + + if ( lower == value ) + break; + + comboIndex++; + } + + comboBoxIt.current()->setCurrentItem(comboIndex); + } +} + + +void DictManager::save(SambaShare* share, bool globalValue, bool defaultValue){ + QDictIterator checkBoxIt( checkBoxDict ); + + for( ; checkBoxIt.current(); ++checkBoxIt ) { + share->setValue(checkBoxIt.currentKey(),checkBoxIt.current()->isChecked(), globalValue, defaultValue ); + } + + QDictIterator lineEditIt( lineEditDict ); + + for( ; lineEditIt.current(); ++lineEditIt ) { + share->setValue(lineEditIt.currentKey(),lineEditIt.current()->text(), globalValue, defaultValue ); + } + + QDictIterator urlRequesterIt( urlRequesterDict ); + + for( ; urlRequesterIt.current(); ++urlRequesterIt ) { + share->setValue(urlRequesterIt.currentKey(),urlRequesterIt.current()->url(), globalValue, defaultValue ); + } + + QDictIterator spinBoxIt( spinBoxDict ); + + for( ; spinBoxIt.current(); ++spinBoxIt ) { + share->setValue(spinBoxIt.currentKey(),spinBoxIt.current()->value(), globalValue, defaultValue ); + } + + QDictIterator comboBoxIt( comboBoxDict ); + + for( ; comboBoxIt.current(); ++comboBoxIt ) { + QStringList* values = comboBoxValuesDict[comboBoxIt.currentKey()]; + + int i = comboBoxIt.current()->currentItem(); + share->setValue(comboBoxIt.currentKey(),(*values)[i], globalValue, defaultValue ); + } + +} + +void DictManager::changedSlot() { + emit changed(); +} + + + +#include "dictmanager.moc" + + diff --git a/filesharing/advanced/kcm_sambaconf/dictmanager.h b/filesharing/advanced/kcm_sambaconf/dictmanager.h new file mode 100644 index 00000000..765c8c6e --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/dictmanager.h @@ -0,0 +1,80 @@ +/*************************************************************************** + dictmanager.h - description + ------------------- + begin : Wed Jan 1 2003 + copyright : (C) 2003 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#ifndef _DICTMANAGER_H_ +#define _DICTMANAGER_H_ + +class SambaShare; +class QLineEdit; +class QCheckBox; +class QSpinBox; +class QComboBox; +class QString; +class SambaShare; +class QStringList; + +/** + * @author Jan Schäfer + **/ +class DictManager : public QObject +{ +Q_OBJECT + public : + DictManager(SambaShare *share); + virtual ~DictManager(); + + void add(const QString &, QLineEdit*); + void add(const QString &, QCheckBox*); + void add(const QString &, KURLRequester*); + void add(const QString &, QSpinBox*); + void add(const QString &, QComboBox*, QStringList*); + + void load(SambaShare* share, bool globalValue=true, bool defaultValue=true); + void save(SambaShare* share, bool globalValue=true, bool defaultValue=true); + + protected : + QDict lineEditDict; + QDict checkBoxDict; + QDict urlRequesterDict; + QDict spinBoxDict; + QDict comboBoxDict; + QDict comboBoxValuesDict; + + SambaShare* _share; + + void handleUnsupportedWidget(const QString &, QWidget*); + void loadComboBoxes(SambaShare*, bool, bool); + + protected slots: + void changedSlot(); + + signals: + void changed(); +}; + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/expertuserdlg.ui b/filesharing/advanced/kcm_sambaconf/expertuserdlg.ui new file mode 100644 index 00000000..a088dcae --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/expertuserdlg.ui @@ -0,0 +1,263 @@ + +ExpertUserDlg + + + ExpertUserDlg + + + + 0 + 0 + 463 + 221 + + + + User Settings + + + true + + + + unnamed + + + + TextLabel12 + + + + 1 + 5 + 0 + 0 + + + + &Valid users: + + + validUsersEdit + + + + + validUsersEdit + + + + + TextLabel12_2_2_2 + + + + 1 + 5 + 0 + 0 + + + + &Admin users: + + + adminUsersEdit + + + + + adminUsersEdit + + + + + TextLabel12_2_2_2_2 + + + + 1 + 5 + 0 + 0 + + + + &Invalid users: + + + invalidUsersEdit + + + + + invalidUsersEdit + + + + + frame16 + + + HLine + + + Raised + + + + + Layout1 + + + + unnamed + + + 0 + + + 6 + + + + Horizontal Spacing2 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + buttonOk + + + &OK + + + + + + true + + + true + + + + + buttonCancel + + + &Cancel + + + + + + true + + + + + + + spacer97 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + TextLabel12_2 + + + + 1 + 5 + 0 + 0 + + + + &Write list: + + + writeListEdit + + + + + writeListEdit + + + + + readListEdit + + + + + TextLabel12_2_2 + + + + 1 + 5 + 0 + 0 + + + + &Read list: + + + readListEdit + + + + + + + buttonOk + clicked() + ExpertUserDlg + accept() + + + buttonCancel + clicked() + ExpertUserDlg + reject() + + + + validUsersEdit + readListEdit + writeListEdit + adminUsersEdit + invalidUsersEdit + buttonOk + buttonCancel + + + diff --git a/filesharing/advanced/kcm_sambaconf/filemodedlg.ui b/filesharing/advanced/kcm_sambaconf/filemodedlg.ui new file mode 100644 index 00000000..a5b77c92 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/filemodedlg.ui @@ -0,0 +1,430 @@ + +FileModeDlg + + + FileModeDlg + + + + 0 + 0 + 370 + 220 + + + + Access Modifiers + + + true + + + + unnamed + + + + GroupBox1 + + + + + + + Access Permissions + + + + unnamed + + + 11 + + + 6 + + + + Line4 + + + VLine + + + Sunken + + + Vertical + + + + + Spacer37 + + + Vertical + + + Expanding + + + + 20 + 43 + + + + + + Spacer38 + + + Horizontal + + + Expanding + + + + 70 + 20 + + + + + + TextLabel3 + + + + + + + Others + + + + + TextLabel4 + + + + + + + Read + + + + + othersReadChk + + + + + + + + + + + + TextLabel6 + + + + + + + Exec + + + + + TextLabel5 + + + + + + + Write + + + + + groupWriteChk + + + + + + + + + + + + othersWriteChk + + + + + + + + + + + + ownerWriteChk + + + + + + + + + + + + othersExecChk + + + + + + + + + + + + groupReadChk + + + + + + + + + + + + ownerReadChk + + + + + + + + + + + + TextLabel1 + + + + + + + Owner + + + + + groupExecChk + + + + + + + + + + + + TextLabel2 + + + + + + + Group + + + + + ownerExecChk + + + + + + + + + + + + stickyBitChk + + + + + + + Sticky + + + + + setGIDChk + + + + + + + Set GID + + + + + setUIDChk + + + + + + + Set UID + + + + + TextLabel8 + + + + + + + Special + + + + + + + Layout1 + + + + unnamed + + + 0 + + + 6 + + + + buttonHelp + + + &Help + + + F1 + + + true + + + + + Horizontal Spacing2 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + buttonOk + + + &OK + + + true + + + true + + + + + buttonCancel + + + &Cancel + + + true + + + + + + + + + buttonOk + clicked() + FileModeDlg + accept() + + + buttonCancel + clicked() + FileModeDlg + reject() + + + + ownerReadChk + ownerWriteChk + ownerExecChk + groupReadChk + groupWriteChk + groupExecChk + othersReadChk + othersWriteChk + othersExecChk + setUIDChk + setGIDChk + stickyBitChk + buttonHelp + buttonOk + buttonCancel + + + diff --git a/filesharing/advanced/kcm_sambaconf/filemodedlgimpl.cpp b/filesharing/advanced/kcm_sambaconf/filemodedlgimpl.cpp new file mode 100644 index 00000000..7c61751d --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/filemodedlgimpl.cpp @@ -0,0 +1,118 @@ +/*************************************************************************** + filemodedlgimpl.cpp - description + ------------------- + begin : Thu Jul 18 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#include + +#include + +#include +#include + +#include "filemodedlgimpl.h" + +FileModeDlgImpl::FileModeDlgImpl(QWidget* parent, QLineEdit* edit) + : FileModeDlg(parent) +{ + assert(edit); + _edit = edit; + + init(); +} + +FileModeDlgImpl::~FileModeDlgImpl() +{ +} + +void FileModeDlgImpl::init() +{ + bool ok; + int mod = _edit->text().toInt(&ok,8); + + if (!ok) + mod = 0; + + stickyBitChk->setChecked( mod & 01000 ); + setGIDChk->setChecked( mod & 02000 ); + setUIDChk->setChecked( mod & 04000 ); + + ownerExecChk->setChecked( mod & 0100 ); + ownerWriteChk->setChecked( mod & 0200 ); + ownerReadChk->setChecked( mod & 0400 ); + + groupExecChk->setChecked( mod & 010 ); + groupWriteChk->setChecked( mod & 020 ); + groupReadChk->setChecked( mod & 040 ); + + othersExecChk->setChecked( mod & 01 ); + othersWriteChk->setChecked( mod & 02 ); + othersReadChk->setChecked( mod & 04 ); + +} + +void FileModeDlgImpl::accept() +{ + QString s = ""; + + s += QString::number( + (stickyBitChk->isChecked() ? 1 : 0) + + (setGIDChk->isChecked() ? 2 : 0) + + (setUIDChk->isChecked() ? 4 : 0) + ); + + s += QString::number( + (ownerExecChk->isChecked() ? 1 : 0) + + (ownerWriteChk->isChecked() ? 2 : 0) + + (ownerReadChk->isChecked() ? 4 : 0) + ); + + s += QString::number( + (groupExecChk->isChecked() ? 1 : 0) + + (groupWriteChk->isChecked() ? 2 : 0) + + (groupReadChk->isChecked() ? 4 : 0) + ); + + s += QString::number( + (othersExecChk->isChecked() ? 1 : 0) + + (othersWriteChk->isChecked() ? 2 : 0) + + (othersReadChk->isChecked() ? 4 : 0) + ); + + // it's an octal number so start with a 0 + // but remove all the other trailing 0's + s = QString::number( s.toInt()); + s = "0"+s; + + _edit->setText(s); + + + + FileModeDlg::accept(); +} + +#include "filemodedlgimpl.moc" + diff --git a/filesharing/advanced/kcm_sambaconf/filemodedlgimpl.h b/filesharing/advanced/kcm_sambaconf/filemodedlgimpl.h new file mode 100644 index 00000000..e855d288 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/filemodedlgimpl.h @@ -0,0 +1,64 @@ +/*************************************************************************** + filemodedlgimpl.cpp - description + ------------------- + begin : Thu Jul 18 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#ifndef FILEMODEDLGIMPL_H +#define FILEMODEDLGIMPL_H + +/** + *@author Jan Schäfer + */ + +#include "filemodedlg.h" + +class QLineEdit; + +/** + * Small dialog to change the UNIX access rights + * It is called with a QLineEdit as parameter. + * The class then takes the text from the QLineEdit and + * interprets it as an oktal UNIX access right string + * e.g. 0744 + * After the user has changed the access rights with the dialog + * the class sets the new access rights as a new octal string + * of the QLineEdit + * Implements the filemodedlg.ui interface + **/ +class FileModeDlgImpl : public FileModeDlg +{ +Q_OBJECT +public: + FileModeDlgImpl(QWidget* parent, QLineEdit* edit); + ~FileModeDlgImpl(); +protected: + QLineEdit* _edit; + void init(); +protected slots: + virtual void accept(); +}; + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/groupselectdlg.ui b/filesharing/advanced/kcm_sambaconf/groupselectdlg.ui new file mode 100644 index 00000000..1722dd18 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/groupselectdlg.ui @@ -0,0 +1,445 @@ + +GroupSelectDlg + + + GroupSelectDlg + + + + 0 + 0 + 406 + 370 + + + + Select Groups + + + true + + + + unnamed + + + 11 + + + 6 + + + + Layout1 + + + + unnamed + + + 0 + + + 6 + + + + Horizontal Spacing2 + + + Horizontal + + + Expanding + + + + 285 + 20 + + + + + + buttonOk + + + &OK + + + 0 + + + true + + + true + + + + + buttonCancel + + + &Cancel + + + 0 + + + true + + + + + + + frame16 + + + HLine + + + Raised + + + + + spacer90 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + groupBox87 + + + + 7 + 5 + 100 + 0 + + + + Selec&t Groups + + + + unnamed + + + 11 + + + 6 + + + + + Name + + + true + + + true + + + + + GID + + + true + + + true + + + + groupListView + + + + 7 + 7 + 100 + 0 + + + + Extended + + + + + + + accessBtnGrp + + + + 1 + 5 + 0 + 0 + + + + Acc&ess + + + + unnamed + + + 11 + + + 6 + + + + defaultRadio + + + + 0 + 0 + 0 + 0 + + + + &Default + + + 8388676 + + + true + + + + + readRadio + + + + 0 + 0 + 0 + 0 + + + + &Read access + + + 8388690 + + + + + writeRadio + + + + 0 + 0 + 0 + 0 + + + + &Write access + + + 8388695 + + + + + adminRadio + + + + 0 + 0 + 0 + 0 + + + + &Admin access + + + 8388673 + + + + + noAccessRadio + + + + 0 + 0 + 0 + 0 + + + + &No access at all + + + 8388686 + + + + + + + kindBtnGrp + + + &Kind of Group + + + + unnamed + + + 11 + + + 6 + + + + unixRadio + + + + 0 + 0 + 0 + 0 + + + + &UNIX group + + + 8388693 + + + false + + + 0 + + + + + nisRadio + + + + 0 + 0 + 0 + 0 + + + + NI&S group + + + 8388691 + + + false + + + 1 + + + + + bothRadio + + + + 0 + 0 + 0 + 0 + + + + UNIX and NIS gr&oup + + + 8388687 + + + true + + + + + + + + + buttonOk + clicked() + GroupSelectDlg + accept() + + + buttonCancel + clicked() + GroupSelectDlg + reject() + + + + groupListView + defaultRadio + bothRadio + buttonOk + buttonCancel + + + qstringlist.h + passwd.h + groupselectdlg.ui.h + + + QString groupKind; + int access; + QStringList selectedGroups; + + + init( const QStringList & specifiedGroups ) + accept() + getSelectedGroups() + getAccess() + getGroupKind() + + + diff --git a/filesharing/advanced/kcm_sambaconf/groupselectdlg.ui.h b/filesharing/advanced/kcm_sambaconf/groupselectdlg.ui.h new file mode 100644 index 00000000..090e270f --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/groupselectdlg.ui.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename functions or slots use +** Qt Designer which will update this file, preserving your code. Create an +** init() function in place of a constructor, and a destroy() function in +** place of a destructor. +*****************************************************************************/ + + +void GroupSelectDlg::init(const QStringList & specifiedGroups) +{ + QStringList unixGroups = getUnixGroups(); + + for (QStringList::Iterator it = unixGroups.begin(); it != unixGroups.end(); ++it) + { + if ( ! specifiedGroups.contains(*it)) + new QListViewItem(groupListView, *it, QString::number(getGroupGID(*it))); + } +} + +void GroupSelectDlg::accept() +{ + QListViewItemIterator it( groupListView); + + for ( ; it.current(); ++it ) { + if ( it.current()->isSelected() ) + selectedGroups << it.current()->text(0); + } + + access = accessBtnGrp->id(accessBtnGrp->selected()); + + if (unixRadio->isChecked()) + groupKind = "+"; + else + if (nisRadio->isChecked()) + groupKind = "&"; + else + if (bothRadio->isChecked()) + groupKind = "@"; + + QDialog::accept(); +} + + + + +QStringList GroupSelectDlg::getSelectedGroups() +{ + return selectedGroups; +} + + +int GroupSelectDlg::getAccess() +{ + return access; +} + + +QString GroupSelectDlg::getGroupKind() +{ + return groupKind; +} diff --git a/filesharing/advanced/kcm_sambaconf/hi16-app-kcmsambaconf.png b/filesharing/advanced/kcm_sambaconf/hi16-app-kcmsambaconf.png new file mode 100644 index 00000000..e587589b Binary files /dev/null and b/filesharing/advanced/kcm_sambaconf/hi16-app-kcmsambaconf.png differ diff --git a/filesharing/advanced/kcm_sambaconf/hiddenfileview.cpp b/filesharing/advanced/kcm_sambaconf/hiddenfileview.cpp new file mode 100644 index 00000000..988e2594 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/hiddenfileview.cpp @@ -0,0 +1,610 @@ +/*************************************************************************** + hiddenfileview.cpp - description + ------------------- + begin : Wed Jan 1 2003 + copyright : (C) 2003 by Jan Sch�er + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hiddenfileview.h" +#include "sharedlgimpl.h" +#include "sambashare.h" + + +#define COL_NAME 0 +#define COL_HIDDEN 1 +#define COL_VETO 2 +#define COL_VETO_OPLOCK 3 +#define COL_SIZE 4 +#define COL_DATE 5 +#define COL_PERM 6 +#define COL_OWNER 7 +#define COL_GROUP 8 + +#define HIDDENTABINDEX 5 + +HiddenListViewItem::HiddenListViewItem( QListView *parent, KFileItem *fi, bool hidden=false, bool veto=false, bool vetoOplock=false ) + : QMultiCheckListItem( parent ) +{ + setPixmap( COL_NAME, fi->pixmap(KIcon::SizeSmall)); + + setText( COL_NAME, fi->text() ); + setText( COL_SIZE, KGlobal::locale()->formatNumber( fi->size(), 0)); + setText( COL_DATE, fi->timeString() ); + setText( COL_PERM, fi->permissionsString() ); + setText( COL_OWNER, fi->user() ); + setText( COL_GROUP, fi->group() ); + + setOn(COL_HIDDEN,hidden); + setOn(COL_VETO,veto); + setOn(COL_VETO_OPLOCK,vetoOplock); + + _fileItem = fi; +} + +HiddenListViewItem::~HiddenListViewItem() +{ +} + +KFileItem* HiddenListViewItem::getFileItem() +{ + return _fileItem; +} + + +void HiddenListViewItem::paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int alignment) +{ + QColorGroup _cg = cg; + + if (isOn(COL_VETO)) + _cg.setColor(QColorGroup::Base,lightGray); + + if (isOn(COL_HIDDEN)) + _cg.setColor(QColorGroup::Text,gray); + + QMultiCheckListItem::paintCell(p, _cg, column, width, alignment); +} + + + + +HiddenFileView::HiddenFileView(ShareDlgImpl* shareDlg, SambaShare* share) +{ + _share = share; + _dlg = shareDlg; + + _hiddenActn = new KToggleAction(i18n("&Hide")); + _vetoActn = new KToggleAction(i18n("&Veto")); + _vetoOplockActn = new KToggleAction(i18n("&Veto Oplock")); + + initListView(); + + _dlg->hiddenChk->setTristate(true); + _dlg->vetoChk->setTristate(true); + + connect( _dlg->hiddenChk, SIGNAL(toggled(bool)), this, SLOT( hiddenChkClicked(bool) )); + connect( _dlg->vetoChk, SIGNAL(toggled(bool)), this, SLOT( vetoChkClicked(bool) )); + connect( _dlg->vetoOplockChk, SIGNAL(toggled(bool)), this, SLOT( vetoOplockChkClicked(bool) )); + + _dlg->hiddenEdit->setText( _share->getValue("hide files") ); + connect( _dlg->hiddenEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateView())); + + _dlg->vetoEdit->setText( _share->getValue("veto files") ); + connect( _dlg->vetoEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateView())); + + _dlg->vetoOplockEdit->setText( _share->getValue("veto oplock files") ); + connect( _dlg->vetoOplockEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateView())); + +// new QLabel(i18n("Hint")+" : ",grid); +// new QLabel(i18n("You have to separate the entries with a '/'. You can use the wildcards '*' and '?'"),grid); +// new QLabel(i18n("Example")+" : ",grid); +// new QLabel(i18n("*.tmp/*SECRET*/.*/file?.*/"),grid); + + _dir = new KDirLister(true); + _dir->setShowingDotFiles(true); + + connect( _dir, SIGNAL(newItems(const KFileItemList &)), + this, SLOT(insertNewFiles(const KFileItemList &))); + + connect( _dir, SIGNAL(deleteItem(KFileItem*)), + this, SLOT(deleteItem(KFileItem*))); + + connect( _dir, SIGNAL(refreshItems(const KFileItemList &)), + this, SLOT(refreshItems(const KFileItemList &))); + + connect( _hiddenActn, SIGNAL(toggled(bool)), this, SLOT(hiddenChkClicked(bool))); + connect( _vetoActn, SIGNAL(toggled(bool)), this, SLOT(vetoChkClicked(bool))); + connect( _vetoOplockActn, SIGNAL(toggled(bool)), this, SLOT(vetoOplockChkClicked(bool))); +} + +void HiddenFileView::initListView() +{ + _dlg->hiddenListView->setMultiSelection(true); + _dlg->hiddenListView->setSelectionMode(QListView::Extended); + _dlg->hiddenListView->setAllColumnsShowFocus(true); + + _hiddenList = createRegExpList(_share->getValue("hide files")); + _vetoList = createRegExpList(_share->getValue("veto files")); + _vetoOplockList = createRegExpList(_share->getValue("veto oplock files")); + + _popup = new KPopupMenu(_dlg->hiddenListView); + + _hiddenActn->plug(_popup); + _vetoActn->plug(_popup); + _vetoOplockActn->plug(_popup); + + connect( _dlg->hiddenListView, SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); + connect( _dlg->hiddenListView, SIGNAL(contextMenu(KListView*,QListViewItem*,const QPoint&)), + this, SLOT(showContextMenu())); + + connect( _dlg->hideDotFilesChk, SIGNAL(toggled(bool)), this, SLOT(hideDotFilesChkClicked(bool))); + connect( _dlg->hideUnreadableChk, SIGNAL(toggled(bool)), this, SLOT(hideUnreadableChkClicked(bool))); + + connect( _dlg->hiddenListView, SIGNAL(mouseButtonPressed(int,QListViewItem*,const QPoint &,int)), + this, SLOT(slotMouseButtonPressed(int,QListViewItem*,const QPoint &,int))); +} + +HiddenFileView::~HiddenFileView() +{ + delete _dir; +} + +void HiddenFileView::load() +{ + if (_dlg->hiddenListView) + _dlg->hiddenListView->clear(); + + _dir->openURL( _dlg->pathUrlRq->url() ); +} + +void HiddenFileView::save() +{ + QString s = _dlg->hiddenEdit->text().stripWhiteSpace(); + // its important that the string ends with an '/' + // otherwise Samba won't recognize the last entry + if ( (!s.isEmpty()) && (s.right(1)!="/")) + s+="/"; + _share->setValue("hide files", s); + + s = _dlg->vetoEdit->text().stripWhiteSpace(); + // its important that the string ends with an '/' + // otherwise Samba won't recognize the last entry + if ( (!s.isEmpty()) && (s.right(1)!="/")) + s+="/"; + _share->setValue("veto files", s); + + s = _dlg->vetoOplockEdit->text().stripWhiteSpace(); + // its important that the string ends with an '/' + // otherwise Samba won't recognize the last entry + if ( (!s.isEmpty()) && (s.right(1)!="/")) + s+="/"; + _share->setValue("veto oplock files", s); + +} + +void HiddenFileView::insertNewFiles(const KFileItemList &newone) +{ + if ( newone.isEmpty() ) + return; + + KFileItem *tmp; + + int j=0; + + for (KFileItemListIterator it(newone); (tmp = it.current()); ++it) + { + j++; + + bool hidden = matchHidden(tmp->text()); + bool veto = matchVeto(tmp->text()); + bool vetoOplock = matchVetoOplock(tmp->text()); + + new HiddenListViewItem( _dlg->hiddenListView, tmp, hidden, veto, vetoOplock ); + + } +} + + + +void HiddenFileView::columnClicked(int column) { + + + switch (column) { + case COL_HIDDEN : hiddenChkClicked( !_dlg->hiddenChk->isOn() );break; + case COL_VETO : vetoChkClicked( !_dlg->vetoChk->isOn() );break; + case COL_VETO_OPLOCK : vetoOplockChkClicked( !_dlg->vetoOplockChk->isOn() );break; + default : break; + } +} + +void HiddenFileView::deleteItem( KFileItem *fileItem ) +{ + HiddenListViewItem* item; + for (item = dynamic_cast(_dlg->hiddenListView->firstChild());item; + item = dynamic_cast(item->nextSibling())) + { + if (item->getFileItem() == fileItem) + { + delete item; + break; + } + } + +} + +void HiddenFileView::refreshItems( const KFileItemList& /*items*/ ) +{ + updateView(); +} + + +void HiddenFileView::showContextMenu() +{ + _popup->exec(QCursor::pos()); +} + + +void HiddenFileView::selectionChanged() +{ + bool veto = false; + bool noVeto = false; + bool hide = false; + bool noHide = false; + bool vetoOplock = false; + bool noVetoOplock = false; + + int n = 0; + + HiddenListViewItem* item; + for (item = static_cast(_dlg->hiddenListView->firstChild());item; + item = static_cast(item->nextSibling())) + { + if (!item->isSelected()) + continue; + + n++; + + if (item->isOn(COL_VETO)) + veto = true; + else + noVeto = true; + + if (item->isOn(COL_VETO_OPLOCK)) + vetoOplock = true; + else + noVetoOplock = true; + + if (item->isOn(COL_HIDDEN)) + hide = true; + else + noHide = true; + } + + + _dlg->selGrpBx->setEnabled(n>0); + + if (veto && noVeto) + { + _dlg->vetoChk->setTristate(true); + _dlg->vetoChk->setNoChange(); + _dlg->vetoChk->update(); + } + else + { + _dlg->vetoChk->setTristate(false); + _dlg->vetoChk->setChecked(veto); + } + + if (vetoOplock && noVetoOplock) + { + _dlg->vetoOplockChk->setTristate(true); + _dlg->vetoOplockChk->setNoChange(); + _dlg->vetoOplockChk->update(); + } + else + { + _dlg->vetoOplockChk->setTristate(false); + _dlg->vetoOplockChk->setChecked(vetoOplock); + } + + + if (hide && noHide) + { + _dlg->hiddenChk->setTristate(true); + _dlg->hiddenChk->setNoChange(); + _dlg->hiddenChk->update(); + } + else + { + _dlg->hiddenChk->setTristate(false); + _dlg->hiddenChk->setChecked(hide); + } +} + +void HiddenFileView::checkBoxClicked(QCheckBox* chkBox,KToggleAction* action,QLineEdit* edit, int column,QPtrList & reqExpList,bool b) { + // We don't save the old state so + // disable the tristate mode + chkBox->setTristate(false); + action->setChecked(b); + chkBox->setChecked(b); + + HiddenListViewItem* item; + for (item = static_cast(_dlg->hiddenListView->firstChild());item; + item = static_cast(item->nextSibling())) + { + if (!item->isSelected()) + continue; + + if (b == item->isOn(column)) + continue; + + if (!b) { + QRegExp* rx = getRegExpListMatch(item->text(0),reqExpList); + + // Perhaps the file was hidden because it started with a dot + if (!rx && item->text(0)[0]=='.' && _dlg->hideDotFilesChk->isChecked()) { + int result = KMessageBox::questionYesNo(_dlg,i18n( + "Some files you have selected are hidden because they start with a dot; " + "do you want to uncheck all files starting with a dot?"),i18n("Files Starting With Dot"),i18n("Uncheck Hidden"), i18n("Keep Hidden")); + + if (result == KMessageBox::No) { + QPtrList lst = getMatchingItems(QRegExp(".*",false,true)); + deselect(lst); + } else { + _dlg->hideDotFilesChk->setChecked(false); + } + continue; + } else { + if (rx) { + // perhaps it is matched by a wildcard string + QString p = rx->pattern(); + if ( p.find("*") > -1 || + p.find("?") > -1 ) + { + // TODO after message freeze: why show three times the wildcard string? Once should be enough. + // TODO remove and use instead + int result = KMessageBox::questionYesNo(_dlg,i18n( + "Some files you have selected are matched by the wildcarded string '%1'; " + "do you want to uncheck all files matching '%1'?").arg(rx->pattern()).arg(rx->pattern()).arg(rx->pattern()), + i18n("Wildcarded String"),i18n("Uncheck Matches"),i18n("Keep Selected")); + + QPtrList lst = getMatchingItems( *rx ); + + if (result == KMessageBox::No) { + deselect(lst); + } else { + setState(lst,column,false); + reqExpList.remove(rx); + updateEdit(edit, reqExpList); + } + continue; + } else { + reqExpList.remove(rx); + updateEdit(edit, reqExpList); + } + } + } + } + else { + reqExpList.append( new QRegExp(item->text(0)) ); + updateEdit(edit, reqExpList); + } + + item->setOn(column,b); + } + + _dlg->hiddenListView->update(); +} + +void HiddenFileView::hiddenChkClicked(bool b) +{ + checkBoxClicked(_dlg->hiddenChk, _hiddenActn, _dlg->hiddenEdit, COL_HIDDEN,_hiddenList,b); + +} + +void HiddenFileView::vetoOplockChkClicked(bool b) { + checkBoxClicked(_dlg->vetoOplockChk, _vetoOplockActn, _dlg->vetoOplockEdit, COL_VETO_OPLOCK,_vetoOplockList,b); +} + +void HiddenFileView::vetoChkClicked(bool b) +{ + checkBoxClicked(_dlg->vetoChk, _vetoActn, _dlg->vetoEdit, COL_VETO,_vetoList,b); +} + +/** + * Sets the text of the QLineEdit edit to the entries of the passed QRegExp-List + **/ +void HiddenFileView::updateEdit(QLineEdit* edit, QPtrList & lst) +{ + QString s=""; + + QRegExp* rx; + for(rx = static_cast(lst.first()); rx; + rx = static_cast(lst.next()) ) + { + s+= rx->pattern()+QString("/"); + } + + edit->setText(s); +} + + +void HiddenFileView::setState(QPtrList & lst, int column, bool b) { + HiddenListViewItem* item; + for(item = static_cast(lst.first()); item; + item = static_cast(lst.next()) ) + { + item->setOn(column,b); + } +} + + +void HiddenFileView::deselect(QPtrList & lst) +{ + HiddenListViewItem* item; + for(item = static_cast(lst.first()); item; + item = static_cast(lst.next()) ) + { + item->setSelected(false); + } +} + + +QPtrList HiddenFileView::getMatchingItems(const QRegExp & rx) +{ + QPtrList lst; + + HiddenListViewItem* item; + for (item = static_cast(_dlg->hiddenListView->firstChild());item; + item = static_cast(item->nextSibling())) + { + if (rx.exactMatch(item->text(0))) + lst.append(item); + } + + return lst; +} + +void HiddenFileView::updateView() +{ + _hiddenList = createRegExpList(_dlg->hiddenEdit->text()); + _vetoList = createRegExpList(_dlg->vetoEdit->text()); + _vetoOplockList = createRegExpList(_dlg->vetoOplockEdit->text()); + + HiddenListViewItem* item; + for (item = static_cast(_dlg->hiddenListView->firstChild());item; + item = static_cast(item->nextSibling())) + { + item->setOn(COL_HIDDEN,matchHidden(item->text(0))); + item->setOn(COL_VETO,matchVeto(item->text(0))); + item->setOn(COL_VETO_OPLOCK,matchVetoOplock(item->text(0))); + } + + _dlg->hiddenListView->repaint(); +} + + +QPtrList HiddenFileView::createRegExpList(const QString & s) +{ + QPtrList lst; + bool cs = _share->getBoolValue("case sensitive"); + + if (!s.isEmpty()) + { + QStringList l = QStringList::split("/",s); + + for ( QStringList::Iterator it = l.begin(); it != l.end(); ++it ) { + lst.append( new QRegExp(*it,cs,true) ); + } + } + + return lst; +} + +bool HiddenFileView::matchHidden(const QString & s) +{ + QPtrList hiddenList(_hiddenList); + + if (_dlg->hideDotFilesChk->isChecked()) + hiddenList.append( new QRegExp(".*",false,true) ); + + return matchRegExpList(s,hiddenList); +} + +bool HiddenFileView::matchVeto(const QString & s) +{ + return matchRegExpList(s,_vetoList); +} + +bool HiddenFileView::matchVetoOplock(const QString & s) +{ + return matchRegExpList(s,_vetoOplockList); +} + +bool HiddenFileView::matchRegExpList(const QString & s, QPtrList & lst) +{ + if (getRegExpListMatch(s,lst)) + return true; + else + return false; +} + + +QRegExp* HiddenFileView::getHiddenMatch(const QString & s) +{ + return getRegExpListMatch(s,_hiddenList); +} + +QRegExp* HiddenFileView::getVetoMatch(const QString & s) +{ + return getRegExpListMatch(s,_vetoList); +} + +QRegExp* HiddenFileView::getRegExpListMatch(const QString & s, QPtrList & lst) +{ + QRegExp* rx; + + for ( rx = lst.first(); rx; rx = lst.next() ) + { + if (rx->exactMatch(s)) + return rx; + } + + return 0L; +} + +void HiddenFileView::hideDotFilesChkClicked(bool) +{ + updateView(); +} + +void HiddenFileView::hideUnreadableChkClicked(bool) +{ + updateView(); +} + +void HiddenFileView::slotMouseButtonPressed( int, QListViewItem*, const QPoint&, int c ) { + columnClicked(c); +} + + +#include "hiddenfileview.moc" diff --git a/filesharing/advanced/kcm_sambaconf/hiddenfileview.h b/filesharing/advanced/kcm_sambaconf/hiddenfileview.h new file mode 100644 index 00000000..c15b772f --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/hiddenfileview.h @@ -0,0 +1,140 @@ +/*************************************************************************** + hiddenfileview.h - description + ------------------- + begin : Wed Jan 1 2003 + copyright : (C) 2003 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#ifndef _HIDDENFILEVIEW_H_ +#define _HIDDENFILEVIEW_H_ + +#include +#include + +#include "qmultichecklistitem.h" + +class KDirLister; +class QRegExp; +class ShareDlgImpl; +class SambaShare; + +class HiddenListViewItem : public QMultiCheckListItem +{ +Q_OBJECT +public: + HiddenListViewItem( QListView *parent, KFileItem *fi, bool hidden, bool veto, bool vetoOplock ); + ~HiddenListViewItem(); + + virtual void paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int alignment); + + KFileItem* getFileItem(); +protected: + KFileItem *_fileItem; +}; + +class KToggleAction; +class KPopupMenu; +class ShareDlgImpl; + + +/** + * Widget which shows a list of files + * from a directory. + * It gets the directory from the SambaShare + * It also interprets the hidden files parameter + * of the SambaShare an offers the possibility of + * selecting the files which should be hidden + **/ +class HiddenFileView : public QObject +{ +Q_OBJECT + +public: + HiddenFileView(ShareDlgImpl* shareDlg, SambaShare* share); + ~HiddenFileView(); + + /** + * Load the values from the share and show them + **/ + void load(); + + /** + * Save changings to the share + **/ + void save(); + +protected: + SambaShare* _share; + ShareDlgImpl* _dlg; + + KDirLister* _dir; + QPtrList _hiddenList; + QPtrList _vetoList; + QPtrList _vetoOplockList; + + KToggleAction* _hiddenActn; + KToggleAction* _vetoActn; + KToggleAction* _vetoOplockActn; + + KPopupMenu* _popup; + + void initListView(); + + QPtrList createRegExpList(const QString & s); + bool matchHidden(const QString & s); + bool matchVeto(const QString & s); + bool matchVetoOplock(const QString & s); + bool matchRegExpList(const QString & s, QPtrList & lst); + + QRegExp* getHiddenMatch(const QString & s); + QRegExp* getVetoMatch(const QString & s); + QRegExp* getRegExpListMatch(const QString & s, QPtrList & lst); + + QPtrList getMatchingItems(const QRegExp & rx); + + void setState(QPtrList & lst,int column, bool b); + void deselect(QPtrList & lst); + + void updateEdit(QLineEdit* edit, QPtrList & lst); + +protected slots: + // slots for KDirListener : + void insertNewFiles(const KFileItemList &newone); + void deleteItem( KFileItem *_fileItem ); + void refreshItems( const KFileItemList& items ); + + void selectionChanged(); + void hiddenChkClicked(bool b); + void vetoChkClicked(bool b); + void vetoOplockChkClicked(bool b); + void checkBoxClicked(QCheckBox* chkBox,KToggleAction* action,QLineEdit* edit,int column,QPtrList &reqExpList,bool b); + void columnClicked(int column); + void showContextMenu(); + void updateView(); + void hideDotFilesChkClicked(bool); + void hideUnreadableChkClicked(bool); + void slotMouseButtonPressed( int button, QListViewItem * item, const QPoint & pos, int c ); +}; + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/joindomaindlg.ui b/filesharing/advanced/kcm_sambaconf/joindomaindlg.ui new file mode 100644 index 00000000..c928a409 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/joindomaindlg.ui @@ -0,0 +1,239 @@ + +JoinDomainDlg + + + JoinDomainDlg + + + + 0 + 0 + 427 + 215 + + + + Join Domain + + + true + + + + unnamed + + + + Layout1 + + + + unnamed + + + 0 + + + 6 + + + + Horizontal Spacing2 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + buttonOk + + + &OK + + + + + + true + + + true + + + + + buttonCancel + + + &Cancel + + + + + + true + + + + + + + domainEdit + + + + + domainControllerEdit + + + + + usernameEdit + + + + + textLabel5_2_2 + + + &Verify: + + + verifyEdit + + + + + textLabel5_2 + + + &Password: + + + passwordEdit + + + + + textLabel5 + + + &Username: + + + usernameEdit + + + + + textLabel4_2 + + + Domain co&ntroller: + + + domainControllerEdit + + + + + textLabel4 + + + &Domain: + + + domainEdit + + + + + passwordEdit + + + + + verifyEdit + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + spacer65 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + + + buttonOk + clicked() + JoinDomainDlg + accept() + + + buttonCancel + clicked() + JoinDomainDlg + reject() + + + + domainEdit + domainControllerEdit + usernameEdit + passwordEdit + verifyEdit + buttonOk + buttonCancel + + + kpassdlg.h + kmessagebox.h + klocale.h + joindomaindlg.ui.h + + + accept() + + + + kpassdlg.h + kpassdlg.h + + diff --git a/filesharing/advanced/kcm_sambaconf/joindomaindlg.ui.h b/filesharing/advanced/kcm_sambaconf/joindomaindlg.ui.h new file mode 100644 index 00000000..bcc5fe53 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/joindomaindlg.ui.h @@ -0,0 +1,17 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename functions or slots use +** Qt Designer which will update this file, preserving your code. Create an +** init() function in place of a constructor, and a destroy() function in +** place of a destructor. +*****************************************************************************/ + +void JoinDomainDlg::accept() { + if (passwordEdit->text() != verifyEdit->text()) { + KMessageBox::sorry(this, "Sorry", "You entered two different passwords. Please try again."); + return; + } + + QDialog::accept(); +} diff --git a/filesharing/advanced/kcm_sambaconf/kcminterface.ui b/filesharing/advanced/kcm_sambaconf/kcminterface.ui new file mode 100644 index 00000000..0c8b31b3 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/kcminterface.ui @@ -0,0 +1,8507 @@ + +KcmInterface + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + + + + KcmInterface + + + + 0 + 0 + 714 + 578 + + + + + 1 + 1 + 0 + 0 + + + + + unnamed + + + 0 + + + 6 + + + + mainTab + + + <qt> +Here you can edit the Samba users, stored in the smbpasswd file. +<p> +Every Samba user must have a corresponding UNIX user. +On the right you can see all UNIX users, stored in the passwd file and not configured as Samba users. +You can see the Samba users on the left-hand side. +<p> +To add new Samba users simply press the <em>&lt; add</em> button. +The selected UNIX users will then become Samba users and will be +removed from the UNIX users list (but they will remain UNIX users). +<p> +To remove Samba users click the <em>&gt; remove</em> button. +The selected Samba users will be removed from the smbpasswd file +and reappear on the right-hand side, as UNIX users which are not Samba users. +</qt> + + + + tab + + + &Base Settings + + + + unnamed + + + 11 + + + 6 + + + + layout38 + + + + unnamed + + + 0 + + + 6 + + + + TextLabel1_6 + + + MShape + + + MShadow + + + Samba config file: + + + configUrlRq + + + + + configUrlRq + + + + + loadBtn + + + true + + + &Load + + + + + + + GroupBox1_2 + + + + + + + Server Identification + + + + unnamed + + + 11 + + + 6 + + + + TextLabel1_2_2_2 + + + + + + + Wor&kgroup: + + + workgroupEdit + + + + + workgroupEdit + + + + + + + Enter here the name of your workgroup/domain. + + + + + serverStringEdit + + + + + + + + + netbiosNameEdit + + + + + + + + + TextLabel4_3 + + + + + + + Server strin&g: + + + serverStringEdit + + + + + TextLabel3_6 + + + + + + + NetBIOS name: + + + netbiosNameEdit + + + + + + + securityLevelBtnGrp + + + + + + + Securit&y Level + + + + unnamed + + + 11 + + + 6 + + + + layout33 + + + + unnamed + + + 0 + + + 6 + + + + shareRadio + + + + + + + Share + + + Alt+ + + + true + + + + + userRadio + + + + + + + User + + + Alt+ + + + + + serverRadio + + + + + + + Server + + + Alt+ + + + + + domainRadio + + + + + + + Domai&n + + + + + adsRadio + + + + + + + ADS + + + Alt+ + + + + + + + securityLevelHelpLbl + + + + 7 + 5 + 0 + 0 + + + + + + + + Use the <i>share</i> security level if you have a home network or a small office network.<br> It allows everyone to read all share names before a login is required. + + + RichText + + + WordBreak|AlignTop + + + 5 + + + + + + + GroupBox12 + + + + + + + Further Options + + + + unnamed + + + 11 + + + 6 + + + + passwordServerLabel + + + false + + + + + + + Password server address/name: + + + passwordServerEdit + + + + + passwordServerEdit + + + false + + + + + realmLabel + + + false + + + + + + + Real&m: + + + realmEdit + + + + + allowGuestLoginsChk + + + false + + + + + + + Allo&w guest logins + + + + + layout39 + + + + unnamed + + + 0 + + + 6 + + + + TextLabel6_5 + + + false + + + + 0 + 5 + 0 + 0 + + + + + + + + Guest acc&ount: + + + guestAccountCombo + + + + + guestAccountCombo + + + false + + + + + + + + + spacer44 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + realmEdit + + + false + + + + + + + + + + + GroupBox10 + + + + + + + Help + + + + unnamed + + + 11 + + + 6 + + + + TextLabel2_3 + + + + 3 + 5 + 0 + 0 + + + + + + + + For detailed help about every option please look at: + + + AlignVCenter + + + + + KURLLabel1 + + + + 5 + 5 + 0 + 0 + + + + man:smb.conf + + + + + Spacer38 + + + Horizontal + + + Expanding + + + + 30 + 0 + + + + + + + + Spacer36 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + + + shareTab + + + &Shares + + + + unnamed + + + 11 + + + 6 + + + + + Name + + + true + + + true + + + + + Path + + + true + + + true + + + + + Comment + + + true + + + true + + + + + Properties + + + true + + + true + + + + shareListView + + + true + + + true + + + false + + + AllColumns + + + + + Layout54 + + + + unnamed + + + 0 + + + 6 + + + + editDefaultShareBtn + + + Edit Defau&lts... + + + + + Spacer21 + + + Horizontal + + + Expanding + + + + 20 + 0 + + + + + + addShareBtn + + + Add &New Share... + + + + + editShareBtn + + + Edit Share... + + + Alt+ + + + + + removeShareBtn + + + Re&move Share + + + + + + + + + tab + + + Prin&ters + + + + unnamed + + + 11 + + + 6 + + + + + Name + + + true + + + true + + + + + Printer + + + true + + + true + + + + + Comment + + + true + + + true + + + + + Properties + + + true + + + true + + + + printerListView + + + true + + + true + + + false + + + AllColumns + + + + + Layout53 + + + + unnamed + + + 0 + + + 6 + + + + editDefaultPrinterBtn + + + Edit Defau&lts + + + + + Spacer21_2 + + + Horizontal + + + Expanding + + + + 20 + 0 + + + + + + addPrinterBtn + + + Add Ne&w Printer + + + + + editPrinterBtn + + + Edit Pri&nter + + + + + removePrinterBtn + + + Re&move Printer + + + + + + + + + tab + + + &Users + + + + unnamed + + + 11 + + + 6 + + + + layout56 + + + + unnamed + + + 0 + + + 6 + + + + groupBox51_2 + + + Sa&mba Users + + + + unnamed + + + 11 + + + 6 + + + + + Name + + + true + + + true + + + + + UID + + + true + + + true + + + + + Disabled + + + true + + + true + + + + + No Password + + + true + + + true + + + + sambaUsersListView + + + + 7 + 7 + 10 + 0 + + + + + + + + layout55 + + + + unnamed + + + 0 + + + 6 + + + + spacer71_3 + + + Vertical + + + Expanding + + + + 20 + 30 + + + + + + addSambaUserBtn + + + + 5 + 0 + 0 + 0 + + + + Add + + + + + + + + spacer71 + + + Vertical + + + Expanding + + + + 20 + 30 + + + + + + removeSambaUserBtn + + + + 5 + 0 + 0 + 0 + + + + Rem&ove + + + + + spacer71_2 + + + Vertical + + + Expanding + + + + 20 + 30 + + + + + + + + groupBox52_2 + + + UNI&X Users + + + + unnamed + + + 11 + + + 6 + + + + + Name + + + true + + + true + + + + + UID + + + true + + + true + + + + unixUsersListView + + + + 5 + 7 + 5 + 0 + + + + + + + + + + layout55 + + + + unnamed + + + 0 + + + 6 + + + + sambaUserPasswordBtn + + + Chan&ge Password... + + + + + joinADomainBtn + + + false + + + &Join Domain + + + + + Spacer26 + + + Horizontal + + + Expanding + + + + 240 + 20 + + + + + + + + + + advanced + + + Advan&ced + + + + unnamed + + + 11 + + + 6 + + + + advancedFrame + + + StyledPanel + + + Raised + + + + + Frame26 + + + HLine + + + Sunken + + + + + Layout92 + + + + unnamed + + + 0 + + + 6 + + + + advancedWarningPixLbl + + + + 0 + 0 + 0 + 0 + + + + true + + + + + TextLabel1_4 + + + + 1 + + + + Here you can change advanced options of the SAMBA server. +Only change something if you know what you are doing. + + + + + + + + + advancedDumpTab + + + Advanced Dump + + + + unnamed + + + 11 + + + 6 + + + + advancedTab + + + Rounded + + + + tab + + + Security + + + + unnamed + + + 0 + + + 6 + + + + tabWidget4 + + + + tab + + + &General + + + + unnamed + + + 11 + + + 6 + + + + spacer90 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + groupBox66 + + + PAM + + + + unnamed + + + 11 + + + 6 + + + + obeyPamRestrictionsChk + + + + + + + Obey PAM restrictions + + + Alt+ + + + + + pamPasswordChangeChk + + + + + + + PAM password change + + + Alt+ + + + + + + + groupBox67 + + + Other Switches + + + + unnamed + + + 11 + + + 6 + + + + allowTrustedDomainsChk + + + + + + + A&llow trusted domains + + + + + paranoidServerSecurityChk + + + Paranoid server security + + + Alt+ + + + + + + + groupBox68 + + + General + + + + unnamed + + + 11 + + + 6 + + + + TextLabel8_2_2_2_2_2_2 + + + + + + + Auth methods: + + + authMethodsEdit + + + + + TextLabel8_2_2_2_2_2 + + + + + + + Root director&y: + + + rootDirectoryEdit + + + + + TextLabel1_2_5_2 + + + I&nterfaces: + + + interfacesEdit + + + + + TextLabel2_5_2 + + + + 0 + 5 + 0 + 0 + + + + + + + + Map to guest: + + + mapToGuestCombo + + + + + bindInterfacesOnlyChk + + + Bind interfaces only + + + Alt+ + + + + + + Never + + + + + Bad User + + + + + Bad Password + + + + mapToGuestCombo + + + + + TextLabel8_2_3_2 + + + + + + + Hosts e&quiv: + + + hostsEquivUrlRq + + + + + rootDirectoryEdit + + + + 1 + + + + + + authMethodsEdit + + + + 1 + + + + + + interfacesEdit + + + + + Spacer37_2_2 + + + Horizontal + + + Expanding + + + + 180 + 20 + + + + + + TextLabel6_7_2 + + + + + + + Algorithmic rid base: + + + algorithmicRidBaseSpin + + + + + algorithmicRidBaseSpin + + + + + + + 2147483647 + + + + + Spacer37_2_2_2 + + + Horizontal + + + Expanding + + + + 117 + 20 + + + + + + hostsEquivUrlRq + + + + 1 + + + + + + TextLabel8_2_2_3_2_2 + + + + + + + Private dir: + + + privateDirUrlRq + + + + + privateDirUrlRq + + + + + + + + + + + + + TabPage + + + Pass&word + + + + unnamed + + + 11 + + + 6 + + + + spacer57 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + groupBox73 + + + Migration + + + + unnamed + + + 11 + + + 6 + + + + updateEncryptedChk + + + + + + + Update encr&ypted + + + + + + + groupBox75 + + + Samba Passwords + + + + unnamed + + + 11 + + + 6 + + + + encryptPasswordsChk + + + + + + + E&ncrypt passwords + + + + + + + + TextLabel8_2_2_2 + + + + + + + Smb passwd file: + + + smbPasswdFileUrlRq + + + + + smbPasswdFileUrlRq + + + + + + + + + TextLabel3_5_3_2 + + + + + + + Passdb bac&kend: + + + passdbBackendEdit + + + + + passwdChatEdit + + + + + + + + + passdbBackendEdit + + + + + + + + + TextLabel3_5_3 + + + + + + + Passwd chat: + + + passwdChatEdit + + + + + passwdChatDebugChk + + + + + + + Passwd chat debug + + + + + TextLabel5_2_3_4_3 + + + Sec + seconds + + + + + TextLabel3_5_3_3 + + + + + + + Passwd chat timeout: + + + passwdChatEdit + + + + + passwdChatTimeoutSpin + + + 2147483647 + + + + + + + groupBox74 + + + Password + + + + unnamed + + + 11 + + + 6 + + + + TextLabel6_7 + + + + + + + Password level: + + + passwordLevelSpin + + + + + TextLabel6_3_2 + + + + + + + Min password length: + + + minPasswdLengthSpin + + + + + TextLabel1 + + + Machine password timeout: + + + machinePasswordTimeoutSpin + + + + + machinePasswordTimeoutSpin + + + 2147483647 + + + + + minPasswdLengthSpin + + + + + + + 2147483647 + + + + + Spacer31_2 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + TextLabel5_2_3_4_2 + + + Sec + seconds + + + + + nullPasswordsChk + + + + + + + Nu&ll passwords + + + + + passwordLevelSpin + + + + + + + 2147483647 + + + + + + + groupBox76 + + + UNIX Passwords + + + + unnamed + + + 11 + + + 6 + + + + TextLabel8_2_2_3_2 + + + + + + + Passwd program: + + + passwdProgramUrlRq + + + + + passwdProgramUrlRq + + + + + + + + + unixPasswordSyncChk + + + + + + + UNI&X password sync + + + + + + + + + TabPage + + + Userna&me + + + + unnamed + + + 11 + + + 6 + + + + TextLabel3_5_2_2 + + + + + + + User&name map: + + + usernameMapUrlRq + + + + + usernameMapUrlRq + + + + + + + + + TextLabel6_4_2 + + + + + + + Username &level: + + + usernameLevelSpin + + + + + usernameLevelSpin + + + + + + + 255 + + + + + spacer52 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + hideLocalUsersChk + + + + + + + Hide local users + + + + + restrictAnonymousChk + + + + + + + Restrict anon&ymous + + + + + useRhostsChk + + + + + + + Use rhosts + + + + + spacer53 + + + Vertical + + + Expanding + + + + 20 + 170 + + + + + + + + tab + + + Authenticati&on + + + + unnamed + + + 11 + + + 6 + + + + groupBox13 + + + Client + + + + unnamed + + + 11 + + + 6 + + + + TextLabel3_5_2_3 + + + C&lient signing: + + + clientSigningCombo + + + + + clientPlaintextAuthChk + + + + + + + Client plainte&xt authentication + + + + + clientLanmanAuthChk + + + + + + + Client lanman authentication + + + + + + Auto + + + + + Mandatory + + + + + Disabled + + + + clientSigningCombo + + + + + + Yes + + + + + No + + + + + Auto + + + + clientSchannelCombo + + + + + TextLabel3_5_2 + + + Client channel: + + + clientSchannelCombo + + + + + clientUseSpnegoChk + + + + + + + Client use spnego + + + + + clientNTLMv2AuthChk + + + + + + + Client NTLMv&2 authentication + + + + + spacer54 + + + Horizontal + + + Expanding + + + + 16 + 20 + + + + + + + + groupBox13_2 + + + Server + + + + unnamed + + + 11 + + + 6 + + + + TextLabel3_5_2_3_3 + + + Server signing: + + + serverSigningCombo + + + + + lanmanAuthChk + + + + + + + Lanman authentication + + + + + + Auto + + + + + Mandatory + + + + + Disabled + + + + serverSigningCombo + + + + + + Yes + + + + + No + + + + + Auto + + + + serverSchannelCombo + + + + + TextLabel3_5_2_5 + + + Server channel: + + + serverSchannelCombo + + + + + useSpnegoChk + + + + + + + Use sp&nego + + + + + ntlmAuthChk + + + + + + + NTLM authentication + + + + + spacer54_2 + + + Horizontal + + + Expanding + + + + 120 + 20 + + + + + + + + spacer55 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + + + + + + tab + + + Logging + + + + unnamed + + + 0 + + + 6 + + + + loggingFrame + + + + 7 + 5 + 100 + 0 + + + + NoFrame + + + Plain + + + + unnamed + + + 0 + + + 6 + + + + groupBox36_2 + + + General + + + + unnamed + + + 11 + + + 6 + + + + TextLabel4_3_2_2_2_2 + + + L&og file: + + + logFileUrlRq + + + + + logFileUrlRq + + + + + TextLabel3_3_2_2_2_2 + + + KB + + + + + TextLabel2_2_2_2_2_2 + + + Ma&x log size: + + + maxLogSizeSpin + + + + + spacer60_3 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + TextLabel5_3_2_2_2_2 + + + S&yslog: + + + syslogSpin + + + + + maxLogSizeSpin + + + 2147483647 + + + + + TextLabel6_2_2_2_2_2 + + + + 0 + 5 + 0 + 0 + + + + + 76 + 0 + + + + Log &level: + + + logLevelEdit + + + + + logLevelEdit + + + + + syslogSpin + + + 4 + + + + + + + groupBox48 + + + S&witches + + + + unnamed + + + 11 + + + 6 + + + + syslogOnlyChk + + + + 0 + 0 + 0 + 0 + + + + Syslog o&nly + + + + + statusChk + + + Status + + + true + + + + + timestampChk + + + + 0 + 0 + 0 + 0 + + + + Ti&mestamp + + + false + + + + + microsecondsChk + + + false + + + microseconds + + + + + debugPidChk + + + + 0 + 0 + 0 + 0 + + + + Debug pid + + + + + debugUidChk + + + Debu&g uid + + + + + + + Spacer114_2 + + + Vertical + + + Expanding + + + + 20 + 60 + + + + + + + + + + tab + + + Tuning + + + + unnamed + + + 0 + + + 6 + + + + groupBox37_2 + + + Modules + + + + unnamed + + + 11 + + + 6 + + + + TextLabel4_2_2 + + + Pre&load modules: + + + preloadModulesEdit + + + + + preloadModulesEdit + + + + + + + groupBox47 + + + Numbers + + + + unnamed + + + 11 + + + 6 + + + + TextLabel4_2_3_2_2_4 + + + Max smbd processes: + + + maxSmbdProcessesSpin + + + + + TextLabel4_2_3_2_2_2_2_4 + + + Max open files: + + + maxOpenFilesSpin + + + + + maxSmbdProcessesSpin + + + 2147483647 + + + + + maxOpenFilesSpin + + + 2147483647 + + + + + spacer61 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + Spacer60 + + + Vertical + + + Expanding + + + + 20 + 50 + + + + + + groupBox46 + + + Sizes + + + + unnamed + + + 11 + + + 6 + + + + TextLabel4_2_3_2_2_2_5 + + + Max disk size: + + + maxDiskSizeSpin + + + + + TextLabel4_2_3_2_2_2_2_2_3 + + + Read si&ze: + + + readSizeSpin + + + + + TextLabel4_2_3_2_2_2_3_3 + + + Stat cache size: + + + statCacheSizeSpin + + + + + readSizeSpin + + + 65536 + + + + + statCacheSizeSpin + + + 2147483647 + + + + + maxDiskSizeSpin + + + 2147483647 + + + + + spacer62 + + + Horizontal + + + Expanding + + + + 16 + 20 + + + + + + TextLabel5_2_3_2_2_3 + + + MB + mega byte + + + + + maxXmitSpin + + + 65535 + + + 0 + + + + + TextLabel4_4_2_7_2_2_2 + + + + + + + Ma&x xmit: + + + maxXmitSpin + + + + + + + groupBox45 + + + Times + + + + unnamed + + + 11 + + + 6 + + + + TextLabel4_2_5 + + + Change notify timeout: + + + changeNotifyTimeoutSpin + + + + + TextLabel5_2_3_4 + + + Sec + seconds + + + + + TextLabel4_2_3_4 + + + &Keepalive: + + + keepaliveSpin + + + + + changeNotifyTimeoutSpin + + + 2147483647 + + + + + TextLabel5_2_5 + + + Sec + seconds + + + + + keepaliveSpin + + + 2147483647 + + + + + TextLabel5_2_2_3 + + + Min + minutes + + + + + TextLabel4_2_2_3 + + + Deadtime: + + + deadtimeSpin + + + + + deadtimeSpin + + + 2147483647 + + + + + TextLabel4_2_3_2_4 + + + Lp&q cache time: + + + lpqCacheTimeSpin + + + + + lpqCacheTimeSpin + + + 2147483647 + + + + + TextLabel5_2_3_2_4 + + + Sec + seconds + + + + + TextLabel5_2_3_2_4_2 + + + Sec + seconds + + + + + TextLabel4_2_3_2_4_2 + + + &Name cache timeout: + + + nameCacheTimeoutSpin + + + + + nameCacheTimeoutSpin + + + 2147483647 + + + + + + + groupBox44 + + + Switches + + + + unnamed + + + 11 + + + 6 + + + + getwdCacheChk + + + &Getwd cache + + + + + useMmapChk + + + Use &mmap + + + + + kernelChangeNotifyChk + + + Kernel change notif&y + + + + + hostnameLookupsChk + + + H&ostname lookups + + + + + readRawChk + + + + + + + Read ra&w + + + true + + + + + writeRawChk + + + + + + + Write raw + + + true + + + + + + + + + tab + + + Printing + + + + unnamed + + + 0 + + + 6 + + + + groupBox52 + + + &General + + + + unnamed + + + 11 + + + 6 + + + + TextLabel1_3 + + + Total print &jobs: + + + totalPrintJobsSpin + + + + + totalPrintJobsSpin + + + 2147483647 + + + + + Spacer22 + + + Horizontal + + + Expanding + + + + 20 + 16 + + + + + + + + groupBox51 + + + Drivers + + + + unnamed + + + 11 + + + 6 + + + + TextLabel4_2_4 + + + OS&2 driver map: + + + os2DriverMapUrlRq + + + + + TextLabel1_3_4 + + + Printcap na&me: + + + printcapNameUrlRq + + + + + printcapNameUrlRq + + + + + os2DriverMapUrlRq + + + + + printerDriverLbl + + + + 1 + + + + Pri&nter driver file: + + + printerDriverFileUrlRq + + + + + printerDriverFileUrlRq + + + + + + + groupBox50 + + + Commands + + + + unnamed + + + 11 + + + 6 + + + + TextLabel2_2_2 + + + Enumports command: + + + enumportsCommandEdit + + + + + TextLabel3_3_3 + + + Addprinter command: + + + addprinterCommandEdit + + + + + TextLabel3_3_2 + + + Deleteprinter command: + + + deleteprinterCommandEdit + + + + + addprinterCommandEdit + + + + + deleteprinterCommandEdit + + + + + enumportsCommandEdit + + + + + + + groupBox49 + + + S&witches + + + + unnamed + + + 11 + + + 6 + + + + loadPrintersChk + + + L&oad printers + + + + + disableSpoolssChk + + + Disab&le spools + + + + + showAddPrinterWizardChk + + + Show add printer wi&zard + + + + + + + Spacer48 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + + + TabPage + + + Domain + + + + unnamed + + + 0 + + + 6 + + + + spacer58 + + + Vertical + + + Expanding + + + + 20 + 100 + + + + + + osLevelSpin + + + + 0 + 0 + 0 + 0 + + + + 2147483647 + + + + + localMasterChk + + + + 0 + 0 + 0 + 0 + + + + L&ocal master + + + + + domainMasterChk + + + Domai&n master + + + + + domainLogonsChk + + + Domain lo&gons + + + + + preferredMasterChk + + + + 0 + 0 + 0 + 0 + + + + Preferred &master + + + + + Spacer14 + + + Horizontal + + + Preferred + + + + 323 + 20 + + + + + + TextLabel5 + + + + 0 + 5 + 0 + 0 + + + + OS &level: + + + osLevelSpin + + + + + TextLabel3_3_3_3 + + + + + + + Domain admin group: + + + domainAdminGroupEdit + + + + + TextLabel3_3_3_4 + + + + + + + Domain guest group: + + + domainGuestGroupEdit + + + + + domainGuestGroupEdit + + + + + domainAdminGroupEdit + + + + + + + TabPage + + + WINS + + + + unnamed + + + 0 + + + 6 + + + + buttonGroup4 + + + NoFrame + + + Plain + + + 0 + + + + + + + unnamed + + + 7 + + + 6 + + + + noWinsSupportRadio + + + Deactivate &WINS + + + true + + + true + + + + + winsSupportRadio + + + Act as a WI&NS server + + + + + otherWinsRadio + + + Use an&other WINS server + + + + + + + groupBox35 + + + false + + + WINS Server Settin&gs + + + + unnamed + + + 11 + + + 6 + + + + TextLabel4_4_2_4_3_2_2_2 + + + + + + + Max WINS tt&l: + + + maxWinsTtlSpin + + + + + TextLabel4_4_2_5_3_2_2_2 + + + + + + + &Min WINS ttl: + + + minWinsTtlSpin + + + + + maxWinsTtlSpin + + + false + + + 2147483647 + + + 0 + + + + + minWinsTtlSpin + + + false + + + 2147483647 + + + 0 + + + + + TextLabel6_5_2_4_2_2_2 + + + Sec + seconds + + + + + TextLabel6_5_2_2_3_2_2_2 + + + Sec + seconds + + + + + spacer59_2 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + winsHookLbl + + + false + + + WINS hoo&k: + + + winsHookEdit + + + + + winsHookEdit + + + false + + + + + dnsProxyChk + + + false + + + DNS prox&y + + + false + + + + + + + groupBox36 + + + false + + + WINS Server IP or DNS Name + + + + unnamed + + + 11 + + + 6 + + + + winsServerEdit + + + false + + + + + + + groupBox37 + + + General Options + + + + unnamed + + + 11 + + + 6 + + + + TextLabel3_3_3_2 + + + WINS partners: + + + winsPartnersEdit + + + + + winsPartnersEdit + + + + + winsProxyChk + + + true + + + + 5 + 0 + 0 + 0 + + + + WINS pro&xy + + + false + + + + + + + spacer60_2 + + + Vertical + + + Expanding + + + + 20 + 120 + + + + + + + + tab + + + Filenames + + + + unnamed + + + 0 + + + 6 + + + + groupBox54 + + + Ge&neral + + + + unnamed + + + 11 + + + 6 + + + + stripDotChk + + + Strip d&ot + + + + + + + groupBox55 + + + &Mangling + + + + unnamed + + + 11 + + + 6 + + + + mangledStackSpin + + + + 4 + 0 + 0 + 0 + + + + 2147483647 + + + + + TextLabel6_2_2 + + + + 0 + 5 + 0 + 0 + + + + Mangled stac&k: + + + mangledStackSpin + + + + + TextLabel6_2_2_2 + + + + 0 + 5 + 0 + 0 + + + + Mangle prefi&x: + + + manglePrefixSpin + + + + + manglePrefixSpin + + + 6 + + + 1 + + + + + spacer56 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + groupBox53 + + + Specia&l + + + + unnamed + + + 11 + + + 6 + + + + statCacheChk + + + Stat cache + + + + + + + Spacer61 + + + Vertical + + + Expanding + + + + 20 + 280 + + + + + + + + tab + + + Locking + + + + unnamed + + + 0 + + + 6 + + + + groupBox59 + + + &General + + + + unnamed + + + 11 + + + 6 + + + + kernelOplocksChk + + + Use ker&nel oplocks + + + + + + + groupBox56 + + + Direct&ories + + + + unnamed + + + 11 + + + 6 + + + + TextLabel5_2 + + + Loc&k directory: + + + lockDirectoryUrlRq + + + + + lockDirectoryUrlRq + + + + + TextLabel6_2 + + + Pid director&y: + + + pidDirectoryUrlRq + + + + + pidDirectoryUrlRq + + + + + + + groupBox57 + + + Lock Spin + + + + unnamed + + + 11 + + + 6 + + + + lockSpinTimeSpin + + + 2147483647 + + + + + lockSpinCountSpin + + + 2147483647 + + + + + textLabel3_2 + + + microseconds + + + + + textLabel2_2 + + + &Lock spin count: + + + lockSpinCountSpin + + + + + textLabel2_2_2 + + + Lock spin ti&me: + + + lockSpinTimeSpin + + + + + spacer60 + + + Horizontal + + + Expanding + + + + 30 + 20 + + + + + + + + groupBox58 + + + Very Advanced + + + + unnamed + + + 11 + + + 6 + + + + textLabel2 + + + + + + + Oplock break &wait time: + + + AlignVCenter + + + oplockBreakWaitTimeSpin + + + + + oplockBreakWaitTimeSpin + + + 2147483647 + + + + + textLabel3 + + + milliseconds + + + + + spacer101 + + + Horizontal + + + Expanding + + + + 50 + 20 + + + + + + + + spacer59 + + + Vertical + + + Expanding + + + + 20 + 120 + + + + + + + + tab + + + Charset + + + + unnamed + + + 0 + + + 6 + + + + groupBox40 + + + Samba &3.x + + + + unnamed + + + 11 + + + 6 + + + + TextLabel1_2_4_2_2_2_2_3 + + + + + + + D&OS charset: + + + dosCharsetEdit + + + + + dosCharsetEdit + + + + + + + + + TextLabel1_2_4_2_2_2_2_2 + + + + + + + UNI&X charset: + + + unixCharsetEdit + + + + + unixCharsetEdit + + + + + + + + + TextLabel1_2_4_2_2_2_2 + + + + + + + Displa&y charset: + + + displayCharsetEdit + + + + + displayCharsetEdit + + + + + + + + + unicodeChk + + + U&nicode + + + + + + + groupBox39 + + + Samba &2.x + + + + unnamed + + + 11 + + + 6 + + + + TextLabel5_2_4 + + + Character set: + + + characterSetEdit + + + + + characterSetEdit + + + + + clientCodePageEdit + + + + + + + + + TextLabel12 + + + Va&lid chars: + + + validCharsEdit + + + + + codePageDirUrlRq + + + + + + + + + validCharsEdit + + + + + TextLabel1_2_4_3_2 + + + + + + + Code page directory: + + + codePageDirUrlRq + + + + + codingSystemEdit + + + + + + + + + TextLabel1_2_4_2_2_2 + + + + + + + Codin&g system: + + + codingSystemEdit + + + + + TextLabel1_2_3_2_2 + + + + + + + Client code page: + + + clientCodePageEdit + + + + + + + Spacer185 + + + Vertical + + + Expanding + + + + 20 + 80 + + + + + + + + tab + + + Logon + + + + unnamed + + + 0 + + + 6 + + + + groupBox60 + + + Add Scripts + + + + unnamed + + + 11 + + + 6 + + + + TextLabel7_2 + + + Add user script: + + + addUserScriptEdit + + + + + addUserScriptEdit + + + + + TextLabel7_2_5_2 + + + Add user to group script: + + + addUserToGroupScriptEdit + + + + + TextLabel7_2_5 + + + Add gr&oup script: + + + addGroupScriptEdit + + + + + addGroupScriptEdit + + + + + addUserToGroupScriptEdit + + + + + TextLabel7_2_6 + + + Add machine script: + + + addMachineScriptEdit + + + + + addMachineScriptEdit + + + + + + + Spacer64 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + groupBox61 + + + Delete Scripts + + + + unnamed + + + 11 + + + 6 + + + + deleteUserScriptEdit + + + + + deleteUserFromGroupScriptEdit + + + + + deleteGroupScriptEdit + + + + + TextLabel7_2_2_2 + + + Delete group script: + + + deleteGroupScriptEdit + + + + + TextLabel7_2_2 + + + Delete user script: + + + deleteUserScriptEdit + + + + + TextLabel7_2_2_2_2 + + + Delete user from group script: + + + deleteUserFromGroupScriptEdit + + + + + + + groupBox64 + + + Primary Group Script + + + + unnamed + + + 11 + + + 6 + + + + TextLabel7_2_5_3 + + + Set primar&y group script: + + + SetPrimaryGroupScriptEdit + + + + + SetPrimaryGroupScriptEdit + + + + + + + groupBox62 + + + Shutdown + + + + unnamed + + + 11 + + + 6 + + + + TextLabel7_2_3_2 + + + Shutdo&wn script: + + + shutdownScriptEdit + + + + + TextLabel7_2_3_3 + + + Abort shutdown script: + + + abortShutdownScriptEdit + + + + + shutdownScriptEdit + + + + + abortShutdownScriptEdit + + + + + + + groupBox63 + + + Logon + + + + unnamed + + + 11 + + + 6 + + + + TextLabel8_2_2 + + + Logo&n path: + + + logonPathUrlRq + + + + + logonHomeUrlRq + + + + + TextLabel8_2 + + + Logon ho&me: + + + logonHomeUrlRq + + + + + logonDriveEdit + + + + + TextLabel7_2_4 + + + &Logon drive: + + + logonDriveEdit + + + + + logonPathUrlRq + + + + + TextLabel7_2_3 + + + Lo&gon script: + + + logonScriptEdit + + + + + logonScriptEdit + + + + + + + + + tab + + + Socket + + + + unnamed + + + 0 + + + 6 + + + + Layout25 + + + + unnamed + + + 0 + + + 6 + + + + TextLabel15 + + + Socket address: + + + socketAddressEdit + + + + + socketAddressEdit + + + + + + + GroupBox7 + + + Socket Options + + + + unnamed + + + 11 + + + 6 + + + + SO_KEEPALIVEChk + + + SO_&KEEPALIVE + + + + + SO_SNDBUFChk + + + SO_S&NDBUF: + + + + + SO_RCVLOWATSpin + + + false + + + 2147483647 + + + + + SO_SNDLOWATSpin + + + false + + + 2147483647 + + + + + SO_BROADCASTChk + + + SO_BROADCAST + + + + + TCP_NODELAYChk + + + TCP_NODELA&Y + + + + + IPTOS_LOWDELAYChk + + + IPTOS_LOWDELAY + + + + + SO_RCVLOWATChk + + + SO_RCV&LOWAT: + + + + + SO_REUSEADDRChk + + + S&O_REUSEADDR + + + + + SO_SNDLOWATChk + + + SO_SNDLO&WAT: + + + + + SO_SNDBUFSpin + + + false + + + 2147483647 + + + + + IPTOS_THROUGHPUTChk + + + IPTOS_THROU&GHPUT + + + + + SO_RCVBUFChk + + + SO_RCVBUF: + + + + + SO_RCVBUFSpin + + + false + + + 2147483647 + + + + + Spacer69 + + + Horizontal + + + Expanding + + + + 80 + 0 + + + + + + + + Spacer62 + + + Vertical + + + Expanding + + + + 0 + 70 + + + + + + + + tab + + + SSL + + + + unnamed + + + 0 + + + 6 + + + + sslChk + + + E&nable SSL + + + Enables or disables the entire SSL mode + + + This enables or disables the entire SSL mode. If it is set to no, the SSL-enabled Samba behaves exactly like the non-SSL Samba. If set to yes, it depends on the variables ssl hosts and ssl hosts resign whether an SSL connection will be required. + +This is only available if the SSL libraries have been compiled on your system and the configure option --with-ssl was given at configure time. + + + + + SSLFrame + + + false + + + NoFrame + + + Plain + + + + unnamed + + + 0 + + + 6 + + + + Layout81 + + + + unnamed + + + 0 + + + 6 + + + + TextLabel2_4_2 + + + SSL h&osts: + + + sslHostsEdit + + + + + Layout232_2 + + + + unnamed + + + 0 + + + 6 + + + + sslEntropyFileUrlRq + + + + + TextLabel12_2_2 + + + SSL entrop&y bytes: + + + sslEntropyBytesSpin + + + + + sslEntropyBytesSpin + + + 2147483647 + + + + + + + sslHostsResignEdit + + + + + sslCACertDirUrlRq + + + + + sslCiphersEdit + + + + + sslEgdSocketEdit + + + + + TextLabel14_2_2 + + + SSL ciphers: + + + sslCiphersEdit + + + + + TextLabel3_7 + + + SSL hosts resi&gn: + + + sslHostsResignEdit + + + + + Layout80 + + + + unnamed + + + 0 + + + 6 + + + + + ssl2 + + + + + ssl3 + + + + + ssl2or3 + + + + + tls1 + + + + sslVersionCombo + + + + + sslCompatibilityChk + + + SSL co&mpatibility + + + + + Spacer223_2 + + + Horizontal + + + Expanding + + + + 140 + 0 + + + + + + + + TextLabel4_4 + + + SSL CA certDir: + + + sslCACertDirUrlRq + + + + + sslCACertFileUrlRq + + + + + TextLabel11_2_2 + + + SSL entropy file: + + + sslEntropyFileUrlRq + + + + + TextLabel10_2_2 + + + SSL egd socket: + + + sslEgdSocketEdit + + + + + TextLabel15_2_2 + + + SSL version: + + + sslVersionCombo + + + + + sslHostsEdit + + + + + TextLabel5_4_3 + + + SSL CA certFile: + + + sslCACertFileUrlRq + + + + + + + Layout231 + + + + unnamed + + + 0 + + + 6 + + + + sslClientCertUrlRq + + + + + sslRequireClientcertChk + + + SSL require clientcert + + + + + TextLabel9_2_2 + + + SSL client key: + + + sslClientKeyUrlRq + + + + + sslRequireServercertChk + + + SSL re&quire servercert + + + + + sslClientKeyUrlRq + + + + + TextLabel6_6_2 + + + SS&L server cert: + + + sslServerCertUrlRq + + + + + TextLabel8_4 + + + SSL client cert: + + + sslClientCertUrlRq + + + + + sslServerCertUrlRq + + + + + sslServerKeyUrlRq + + + + + TextLabel7_5 + + + SSL server &key: + + + sslServerKeyUrlRq + + + + + + + + + Spacer46 + + + Vertical + + + Expanding + + + + 0 + 30 + + + + + + + + tab + + + Protocol + + + + unnamed + + + 0 + + + 6 + + + + Spacer63 + + + Vertical + + + Expanding + + + + 20 + 50 + + + + + + groupBox69 + + + Limits + + + + unnamed + + + 11 + + + 6 + + + + maxTtlSpin + + + 2147483647 + + + 0 + + + + + TextLabel4_4_4_2_2_2 + + + + + + + Ma&x mux: + + + maxMuxSpin + + + + + TextLabel4_4_2_3_3_2_2_2 + + + + + + + Max tt&l: + + + maxTtlSpin + + + + + maxMuxSpin + + + 2147483647 + + + 0 + + + + + spacer86 + + + Horizontal + + + Expanding + + + + 330 + 20 + + + + + + TextLabel6_5_4_2_2_2 + + + Sec + seconds + + + + + + + groupBox70 + + + S&witches + + + + unnamed + + + 11 + + + 6 + + + + timeServerChk + + + Ti&me server + + + + + largeReadWriteChk + + + Lar&ge readwrite + + + + + unixExtensionsChk + + + UNIX extensions + + + Alt+ + + + + + readBmpxChk + + + + + + + Read bmpx + + + + + + + groupBox71 + + + Protocol Versions + + + + unnamed + + + 11 + + + 6 + + + + TextLabel1_3_2_2 + + + Max protocol: + + + maxProtocolCombo + + + + + TextLabel2_3_2 + + + Announce version: + + + announceVersionEdit + + + + + TextLabel3_6_2 + + + A&nnounce as: + + + announceAsCombo + + + + + TextLabel1_3_3_2 + + + Min protocol: + + + minProtocolCombo + + + + + TextLabel1_3_4_2 + + + Pr&otocol: + + + protocolCombo + + + + + spacer87 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + NT1 + + + + + LANMAN2 + + + + + LANMAN1 + + + + + CORE + + + + + COREPLUS + + + + protocolCombo + + + + 0 + 0 + 0 + 0 + + + + + + + NT1 + + + + + LANMAN2 + + + + + LANMAN1 + + + + + CORE + + + + + COREPLUS + + + + maxProtocolCombo + + + + 0 + 0 + 0 + 0 + + + + + + + NT1 + + + + + LANMAN2 + + + + + LANMAN1 + + + + + CORE + + + + + COREPLUS + + + + minProtocolCombo + + + + 0 + 0 + 0 + 0 + + + + + + + NT + + + + + NT Workstation + + + + + win95 + + + + + WfW + + + + announceAsCombo + + + + 0 + 0 + 0 + 0 + + + + + + announceVersionEdit + + + + 0 + 0 + 0 + 0 + + + + 4.2 + + + + + + + groupBox72 + + + Listening SMB Ports + + + + unnamed + + + 11 + + + 6 + + + + TextLabel5_4_2_2 + + + SMB ports: + + + smbPortsEdit + + + + + smbPortsEdit + + + + 7 + 0 + 0 + 0 + + + + + 150 + 0 + + + + + + + + + + + + + tab + + + Browsing + + + + unnamed + + + 0 + + + 6 + + + + TextLabel2 + + + LM i&nterval: + + + lmIntervalSpin + + + + + TextLabel3_5 + + + L&M announce: + + + lmAnnounceCombo + + + + + Spacer40 + + + Horizontal + + + Expanding + + + + 340 + 20 + + + + + + remoteBrowseSyncEdit + + + + + TextLabel27_2 + + + Sec + + + + + lmIntervalSpin + + + 2147483647 + + + + + TextLabel14 + + + Remote browse s&ync: + + + remoteBrowseSyncEdit + + + + + + Yes + + + + + No + + + + + Auto + + + + lmAnnounceCombo + + + + + browseListChk + + + Bro&wse list + + + + + enhancedBrowsingChk + + + Enhanced browsin&g + + + + + Spacer39 + + + Vertical + + + Expanding + + + + 20 + 300 + + + + + + TextLabel4_2 + + + Pre&load: + + + preloadEdit + + + + + preloadEdit + + + + + + + tab + + + Winbind + + + + unnamed + + + 0 + + + + TextLabel21 + + + &Winbind/Idmap UID: + + + winbindUidEdit + + + + + winbindUidEdit + + + + + winbindGidEdit + + + + + TextLabel21_2 + + + Winbind/Idmap &GID: + + + winbindGidEdit + + + + + TextLabel22 + + + Template h&omedir: + + + templateHomedirEdit + + + + + templateHomedirEdit + + + + + TextLabel23 + + + Temp&late shell: + + + templateShellEdit + + + + + templateShellEdit + + + + + TextLabel24 + + + Winbind separator: + + + winbindSeparatorEdit + + + + + winbindSeparatorEdit + + + + + TextLabel24_2 + + + Template primary group: + + + templatePrimaryGroupEdit + + + + + templatePrimaryGroupEdit + + + + + TextLabel27 + + + Sec + + + + + TextLabel25 + + + Winbind cache ti&me: + + + winbindCacheTimeSpin + + + + + winbindCacheTimeSpin + + + 2147483647 + + + 0 + + + + + Spacer35 + + + Horizontal + + + Expanding + + + + 370 + 20 + + + + + + + Windows NT 4 + + + + + Windows 2000 + + + + + Auto + + + + aclCompatibilityCombo + + + + + textLabel3_2_2 + + + Acl compatibilit&y: + + + aclCompatibilityCombo + + + + + winbindEnumUsersChk + + + Wi&nbind enum users + + + + + winbindEnumGroupsChk + + + Winbind enum groups + + + + + winbindUseDefaultDomainChk + + + Winbind use default domain + + + + + winbindEnableLocalAccountsChk + + + Winbind enable local accounts + + + + + winbindTrustedDomainsOnlyChk + + + Winbind trusted domains only + + + + + winbindNestedGroupsChk + + + Winbind nested groups + + + + + Spacer34 + + + Vertical + + + Expanding + + + + 20 + 100 + + + + + + + + tab + + + NetBIOS + + + + unnamed + + + 0 + + + 6 + + + + Spacer220 + + + Vertical + + + Expanding + + + + 20 + 280 + + + + + + TextLabel3_4 + + + + + + + NetBIOS sc&ope: + + + netbiosScopeEdit + + + + + TextLabel3_2 + + + + + + + &NetBIOS aliases: + + + netbiosAliasesEdit + + + + + disableNetbiosChk + + + Disab&le netbios + + + + + TextLabel5_4_2 + + + Na&me resolve order: + + + nameResolveOrderEdit + + + + + netbiosScopeEdit + + + + + + + + + netbiosAliasesEdit + + + + + + + + + nameResolveOrderEdit + + + + 7 + 0 + 0 + 0 + + + + + 150 + 0 + + + + lmhosts host wins bcast + + + + + + + tab + + + VFS + + + + unnamed + + + 0 + + + 6 + + + + hostMsdfsChk + + + H&ost msdfs + + + + + Spacer221 + + + Vertical + + + Expanding + + + + 0 + 30 + + + + + + + + TabPage + + + LDAP + + + + unnamed + + + 0 + + + 6 + + + + TextLabel3_2_2 + + + + + + + LDAP suffi&x: + + + ldapSuffixEdit + + + + + TextLabel3_2_2_2 + + + + + + + LDAP machine suffix: + + + ldapMachineSuffixEdit + + + + + TextLabel3_2_2_2_2 + + + + + + + LDAP user suffix: + + + ldapUserSuffixEdit + + + + + TextLabel3_2_2_2_3 + + + + + + + LDAP &group suffix: + + + ldapGroupSuffixEdit + + + + + TextLabel3_2_2_2_4 + + + + + + + LDAP idmap suffix: + + + ldapIdmapSuffixEdit + + + + + TextLabel3_2_2_2_5 + + + + + + + LDAP filter: + + + ldapFilterEdit + + + + + TextLabel3_2_2_2_6 + + + + + + + LDAP ad&min dn: + + + ldapAdminDnEdit + + + + + spacer46 + + + Vertical + + + Expanding + + + + 20 + 130 + + + + + + ldapDeleteDnChk + + + LDAP delete d&n + + + + + TextLabel3_5_4_2 + + + LDAP s&ync: + + + ldapSyncCombo + + + + + TextLabel3_5_4 + + + &LDAP ssl: + + + ldapSslCombo + + + + + TextLabel3_4_2 + + + + + + + Idmap bac&kend: + + + idmapBackendEdit + + + + + TextLabel3_4_2_2 + + + + + + + LDAP replication sleep: + + + idmapBackendEdit + + + + + idmapBackendEdit + + + + + + + + + ldapAdminDnEdit + + + + + + + + + ldapFilterEdit + + + + + + + + + ldapIdmapSuffixEdit + + + + + + + + + ldapGroupSuffixEdit + + + + + + + + + ldapUserSuffixEdit + + + + + + + + + ldapMachineSuffixEdit + + + + + + + + + ldapSuffixEdit + + + + + + + + + ldapReplicationSleepSpin + + + 2147483647 + + + + + textLabel3_3 + + + milliseconds + + + + + + Off + + + + + Start_tls + + + + + On + + + + ldapSslCombo + + + + + + Yes + + + + + No + + + + + Only + + + + ldapSyncCombo + + + + + spacer88 + + + Horizontal + + + Expanding + + + + 330 + 20 + + + + + + + + TabPage + + + Commands + + + + unnamed + + + 0 + + + 6 + + + + TextLabel1_5 + + + Add share c&ommand: + + + addShareCommandEdit + + + + + TextLabel2_2 + + + Change share command: + + + changeShareCommandEdit + + + + + TextLabel3_3 + + + De&lete share command: + + + deleteShareCommandEdit + + + + + TextLabel10 + + + Messa&ge command: + + + messageCommandEdit + + + + + TextLabel11 + + + Dfree co&mmand: + + + dfreeCommandEdit + + + + + TextLabel1_5_2 + + + Set &quota command: + + + setQuotaCommandEdit + + + + + TextLabel2_2_3 + + + Get quota command: + + + getQuotaCommandEdit + + + + + spacer52_2 + + + Vertical + + + Expanding + + + + 20 + 230 + + + + + + TextLabel20 + + + Pa&nic action: + + + panicActionEdit + + + + + messageCommandEdit + + + + + dfreeCommandEdit + + + + + setQuotaCommandEdit + + + + + getQuotaCommandEdit + + + + + panicActionEdit + + + + + deleteShareCommandEdit + + + + + changeShareCommandEdit + + + + + addShareCommandEdit + + + + + + + tab + + + Misc + + + + unnamed + + + 0 + + + 6 + + + + groupBox12 + + + Miscellaneous + + + + unnamed + + + 11 + + + 6 + + + + TextLabel17 + + + Time &offset: + + + timeOffsetSpin + + + + + sourceEnvironmentEdit + + + + + defaultServiceEdit + + + + + TextLabel9 + + + Default service: + + + defaultServiceEdit + + + + + TextLabel13 + + + Remote a&nnounce: + + + remoteAnnounceEdit + + + + + remoteAnnounceEdit + + + + + TextLabel19 + + + Source environment: + + + sourceEnvironmentEdit + + + + + CheckBox68 + + + Hide &local users + + + + + timeOffsetSpin + + + 1440 + + + 0 + + + + + TextLabel18 + + + Min + minurtes + + + + + spacer63 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + groupBox11 + + + NIS + + + + unnamed + + + 11 + + + 6 + + + + nisHomedirChk + + + NIS homedir + + + + + TextLabel16 + + + Homedir map: + + + homedirMapEdit + + + + + homedirMapEdit + + + + + + + groupBox9 + + + UTMP + + + + unnamed + + + 11 + + + 6 + + + + TextLabel7_3 + + + Utmp director&y: + + + utmpDirectoryUrlRq + + + + + wtmpDirectoryUrlRq + + + + + TextLabel8_3 + + + &Wtmp directory: + + + wtmpDirectoryUrlRq + + + + + utmpDirectoryUrlRq + + + + + CheckBox51 + + + Ut&mp + + + + + + + Spacer33 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + + + tab + + + Debug + + + + unnamed + + + 0 + + + 6 + + + + ntStatusSupportChk + + + &NT status support + + + + + ntSmbSupportChk + + + + + + + NT S&MB support + + + true + + + + + ntPipeSupportChk + + + + + + + NT pipe supp&ort + + + true + + + + + spacer81 + + + Vertical + + + Expanding + + + + 20 + 320 + + + + + + + + + + + + + + + + shareListView + doubleClicked(QListViewItem*) + editShareBtn + animateClick() + + + printerListView + doubleClicked(QListViewItem*) + editPrinterBtn + animateClick() + + + KURLLabel1 + leftClickedURL() + KcmInterface + KURLLabel1_leftClickedURL() + + + serverRadio + toggled(bool) + passwordServerEdit + setEnabled(bool) + + + domainRadio + toggled(bool) + passwordServerEdit + setEnabled(bool) + + + lmAnnounceCombo + activated(int) + KcmInterface + lmAnnounceCombo_activated(int) + + + allowGuestLoginsChk + toggled(bool) + KcmInterface + allowGuestLoginsChk_toggled(bool) + + + allowGuestLoginsChk + pressed() + KcmInterface + changedSlot() + + + SO_SNDBUFChk + toggled(bool) + SO_SNDBUFSpin + setEnabled(bool) + + + SO_RCVBUFChk + toggled(bool) + SO_RCVBUFSpin + setEnabled(bool) + + + SO_SNDLOWATChk + toggled(bool) + SO_SNDLOWATSpin + setEnabled(bool) + + + SO_RCVLOWATChk + toggled(bool) + SO_RCVLOWATSpin + setEnabled(bool) + + + shareRadio + toggled(bool) + allowGuestLoginsChk + setDisabled(bool) + + + userRadio + toggled(bool) + allowGuestLoginsChk + setEnabled(bool) + + + serverRadio + toggled(bool) + allowGuestLoginsChk + setEnabled(bool) + + + domainRadio + toggled(bool) + allowGuestLoginsChk + setEnabled(bool) + + + shareRadio + stateChanged(int) + KcmInterface + changedSlot() + + + userRadio + stateChanged(int) + KcmInterface + changedSlot() + + + serverRadio + stateChanged(int) + KcmInterface + changedSlot() + + + domainRadio + stateChanged(int) + KcmInterface + changedSlot() + + + passwordServerEdit + textChanged(const QString&) + KcmInterface + changedSlot() + + + sslChk + toggled(bool) + SSLFrame + setEnabled(bool) + + + timestampChk + toggled(bool) + microsecondsChk + setEnabled(bool) + + + adsRadio + toggled(bool) + passwordServerEdit + setEnabled(bool) + + + adsRadio + toggled(bool) + passwordServerLabel + setEnabled(bool) + + + adsRadio + toggled(bool) + realmLabel + setEnabled(bool) + + + adsRadio + toggled(bool) + realmEdit + setEnabled(bool) + + + allowGuestLoginsChk + toggled(bool) + TextLabel6_5 + setEnabled(bool) + + + allowGuestLoginsChk + toggled(bool) + guestAccountCombo + setEnabled(bool) + + + domainRadio + toggled(bool) + passwordServerLabel + setEnabled(bool) + + + domainRadio + toggled(bool) + passwordServerEdit + setEnabled(bool) + + + serverRadio + toggled(bool) + passwordServerLabel + setEnabled(bool) + + + serverRadio + toggled(bool) + passwordServerEdit + setEnabled(bool) + + + adsRadio + toggled(bool) + allowGuestLoginsChk + setEnabled(bool) + + + shareRadio + toggled(bool) + TextLabel6_5 + setDisabled(bool) + + + shareRadio + toggled(bool) + guestAccountCombo + setDisabled(bool) + + + winsSupportRadio + toggled(bool) + groupBox35 + setEnabled(bool) + + + otherWinsRadio + toggled(bool) + groupBox36 + setEnabled(bool) + + + winsSupportRadio + toggled(bool) + dnsProxyChk + setEnabled(bool) + + + winsSupportRadio + toggled(bool) + winsHookEdit + setEnabled(bool) + + + winsSupportRadio + toggled(bool) + winsHookEdit + setEnabled(bool) + + + winsSupportRadio + toggled(bool) + maxWinsTtlSpin + setEnabled(bool) + + + winsSupportRadio + toggled(bool) + minWinsTtlSpin + setEnabled(bool) + + + otherWinsRadio + toggled(bool) + winsServerEdit + setEnabled(bool) + + + winsSupportRadio + toggled(bool) + winsHookLbl + setEnabled(bool) + + + + ntStatusSupportChk + ntSmbSupportChk + ntPipeSupportChk + CheckBox68 + defaultServiceEdit + timeOffsetSpin + remoteAnnounceEdit + sourceEnvironmentEdit + nisHomedirChk + homedirMapEdit + CheckBox51 + utmpDirectoryUrlRq + wtmpDirectoryUrlRq + advancedTab + addShareCommandEdit + changeShareCommandEdit + deleteShareCommandEdit + messageCommandEdit + dfreeCommandEdit + setQuotaCommandEdit + getQuotaCommandEdit + panicActionEdit + ldapSuffixEdit + ldapMachineSuffixEdit + ldapUserSuffixEdit + ldapGroupSuffixEdit + ldapIdmapSuffixEdit + ldapFilterEdit + ldapAdminDnEdit + idmapBackendEdit + ldapSslCombo + ldapSyncCombo + ldapDeleteDnChk + hostMsdfsChk + disableNetbiosChk + netbiosAliasesEdit + netbiosScopeEdit + nameResolveOrderEdit + winbindUidEdit + winbindGidEdit + templateHomedirEdit + templateShellEdit + winbindSeparatorEdit + templatePrimaryGroupEdit + winbindCacheTimeSpin + aclCompatibilityCombo + winbindEnumUsersChk + winbindEnumGroupsChk + winbindUseDefaultDomainChk + winbindEnableLocalAccountsChk + winbindTrustedDomainsOnlyChk + enhancedBrowsingChk + browseListChk + lmAnnounceCombo + lmIntervalSpin + remoteBrowseSyncEdit + preloadEdit + timeServerChk + largeReadWriteChk + unixExtensionsChk + readBmpxChk + announceAsCombo + announceVersionEdit + protocolCombo + maxProtocolCombo + minProtocolCombo + maxMuxSpin + maxTtlSpin + smbPortsEdit + sslChk + sslVersionCombo + sslCompatibilityChk + sslHostsEdit + sslHostsResignEdit + sslCACertDirUrlRq + sslCACertFileUrlRq + sslEgdSocketEdit + sslEntropyFileUrlRq + sslEntropyBytesSpin + sslCiphersEdit + sslRequireServercertChk + sslServerCertUrlRq + sslServerKeyUrlRq + sslRequireClientcertChk + sslClientCertUrlRq + sslClientKeyUrlRq + socketAddressEdit + TCP_NODELAYChk + IPTOS_LOWDELAYChk + IPTOS_THROUGHPUTChk + SO_KEEPALIVEChk + SO_REUSEADDRChk + SO_BROADCASTChk + SO_SNDBUFChk + SO_SNDBUFSpin + SO_RCVBUFChk + SO_RCVBUFSpin + SO_SNDLOWATChk + SO_SNDLOWATSpin + SO_RCVLOWATChk + SO_RCVLOWATSpin + addUserScriptEdit + addGroupScriptEdit + addUserToGroupScriptEdit + addMachineScriptEdit + shutdownScriptEdit + abortShutdownScriptEdit + SetPrimaryGroupScriptEdit + deleteUserScriptEdit + deleteGroupScriptEdit + deleteUserFromGroupScriptEdit + logonScriptEdit + logonDriveEdit + logonPathUrlRq + logonHomeUrlRq + unicodeChk + displayCharsetEdit + unixCharsetEdit + dosCharsetEdit + characterSetEdit + clientCodePageEdit + codePageDirUrlRq + codingSystemEdit + validCharsEdit + mainTab + configUrlRq + loadBtn + workgroupEdit + netbiosNameEdit + serverStringEdit + shareRadio + userRadio + serverRadio + domainRadio + adsRadio + passwordServerEdit + realmEdit + guestAccountCombo + allowGuestLoginsChk + shareListView + editDefaultShareBtn + addShareBtn + editShareBtn + removeShareBtn + printerListView + editDefaultPrinterBtn + addPrinterBtn + editPrinterBtn + removePrinterBtn + sambaUsersListView + addSambaUserBtn + removeSambaUserBtn + unixUsersListView + sambaUserPasswordBtn + joinADomainBtn + tabWidget4 + mapToGuestCombo + interfacesEdit + bindInterfacesOnlyChk + rootDirectoryEdit + authMethodsEdit + hostsEquivUrlRq + privateDirUrlRq + algorithmicRidBaseSpin + obeyPamRestrictionsChk + pamPasswordChangeChk + allowTrustedDomainsChk + paranoidServerSecurityChk + encryptPasswordsChk + smbPasswdFileUrlRq + passdbBackendEdit + passwdChatEdit + passwdChatDebugChk + passwdProgramUrlRq + unixPasswordSyncChk + nullPasswordsChk + passwordLevelSpin + minPasswdLengthSpin + machinePasswordTimeoutSpin + updateEncryptedChk + usernameLevelSpin + usernameMapUrlRq + hideLocalUsersChk + restrictAnonymousChk + useRhostsChk + lanmanAuthChk + ntlmAuthChk + useSpnegoChk + serverSchannelCombo + serverSigningCombo + clientLanmanAuthChk + clientPlaintextAuthChk + clientNTLMv2AuthChk + clientUseSpnegoChk + clientSchannelCombo + clientSigningCombo + logFileUrlRq + logLevelEdit + maxLogSizeSpin + syslogSpin + syslogOnlyChk + statusChk + timestampChk + microsecondsChk + debugPidChk + debugUidChk + preloadModulesEdit + getwdCacheChk + useMmapChk + kernelChangeNotifyChk + hostnameLookupsChk + readRawChk + writeRawChk + changeNotifyTimeoutSpin + deadtimeSpin + keepaliveSpin + lpqCacheTimeSpin + nameCacheTimeoutSpin + maxDiskSizeSpin + readSizeSpin + statCacheSizeSpin + maxXmitSpin + maxSmbdProcessesSpin + maxOpenFilesSpin + totalPrintJobsSpin + printcapNameUrlRq + os2DriverMapUrlRq + printerDriverFileUrlRq + addprinterCommandEdit + deleteprinterCommandEdit + enumportsCommandEdit + loadPrintersChk + disableSpoolssChk + showAddPrinterWizardChk + preferredMasterChk + localMasterChk + domainMasterChk + domainLogonsChk + osLevelSpin + domainAdminGroupEdit + domainGuestGroupEdit + noWinsSupportRadio + dnsProxyChk + winsHookEdit + maxWinsTtlSpin + minWinsTtlSpin + winsServerEdit + winsProxyChk + winsPartnersEdit + stripDotChk + mangledStackSpin + manglePrefixSpin + statCacheChk + kernelOplocksChk + lockDirectoryUrlRq + pidDirectoryUrlRq + lockSpinCountSpin + lockSpinTimeSpin + oplockBreakWaitTimeSpin + + + qptrlist.h + share.h + kprocess.h + kiconloader.h + kcminterface.ui.h + + + changed() + + + init() + changedSlot() + securityLevelCombo_activated( int i ) + KURLLabel1_leftClickedURL() + lmAnnounceCombo_activated( int i ) + allowGuestLoginsChk_toggled( bool b ) + mapToGuestCombo_activated( int i ) + updateSecurityLevelHelpLbl() + + + + kurlrequester.h + klineedit.h + kpushbutton.h + klineedit.h + klineedit.h + klineedit.h + kurllabel.h + klistview.h + klistview.h + klistview.h + klistview.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + klineedit.h + klineedit.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + klineedit.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + klineedit.h + klineedit.h + klineedit.h + klineedit.h + klineedit.h + klineedit.h + klineedit.h + klineedit.h + klineedit.h + klineedit.h + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + + diff --git a/filesharing/advanced/kcm_sambaconf/kcminterface.ui.h b/filesharing/advanced/kcm_sambaconf/kcminterface.ui.h new file mode 100644 index 00000000..708667ec --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/kcminterface.ui.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename slots use Qt Designer which will +** update this file, preserving your code. Create an init() slot in place of +** a constructor, and a destroy() slot in place of a destructor. +*****************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +void KcmInterface::init() +{ + addShareBtn->setIconSet(SmallIconSet("filenew")); + editShareBtn->setIconSet(SmallIconSet("edit")); + removeShareBtn->setIconSet(SmallIconSet("editdelete")); + editDefaultShareBtn->setIconSet(SmallIconSet("queue")); + + addPrinterBtn->setIconSet(SmallIconSet("filenew")); + editPrinterBtn->setIconSet(SmallIconSet("edit")); + removePrinterBtn->setIconSet(SmallIconSet("editdelete")); + editDefaultPrinterBtn->setIconSet(SmallIconSet("print_class")); + + advancedWarningPixLbl->setPixmap(SmallIcon("messagebox_warning")); + +} + + + +void KcmInterface::changedSlot() +{ + emit changed(); +} + + +void KcmInterface::securityLevelCombo_activated( int i ) +{ + passwordServerEdit->setDisabled(i<2); + allowGuestLoginsChk->setDisabled(i==0); + updateSecurityLevelHelpLbl(); +} + +void KcmInterface::KURLLabel1_leftClickedURL() +{ + KProcess* p = new KProcess(); + + *p << "konqueror"; + *p << "man:smb.conf"; + p->start(); + + + +} + + + + +void KcmInterface::lmAnnounceCombo_activated( int i) +{ + lmIntervalSpin->setEnabled(i==0); +} + + + + +void KcmInterface::allowGuestLoginsChk_toggled( bool b) +{ + int i = 0; + if (b) + i = 1; + + mapToGuestCombo->setCurrentItem(i); +} + + +void KcmInterface::mapToGuestCombo_activated( int i) +{ + allowGuestLoginsChk->setChecked(i>0); +} + +void KcmInterface::updateSecurityLevelHelpLbl() +{ + if (shareRadio->isChecked()) { + securityLevelHelpLbl->setText(i18n("Use the share security level if you have a home network " + "or a small office network.
It allows everyone to read the list " + "of all your shared directories and printers before a login is required.")); + + } else if (userRadio->isChecked()) { + securityLevelHelpLbl->setText(i18n("Use the user security level if you have a bigger network " + "and you do not want to allow everyone to read your list of shared " + "directories and printers without a login.

" + "If you want to run your Samba server as a Primary Domain controller (PDC) " + "you also have to set this option.")); +} else if (serverRadio->isChecked()) { + securityLevelHelpLbl->setText(i18n("Use the server security level if you have a big network " + "and the samba server should validate the username/password " + "by passing it to another SMB server, such as an NT box.")); + } else if (domainRadio->isChecked()) { + securityLevelHelpLbl->setText(i18n("Use the domain security level if you have a big network " + "and the samba server should validate the username/password " + "by passing it to a Windows NT Primary or Backup Domain Controller.")); + } else if (adsRadio->isChecked()) { + securityLevelHelpLbl->setText(i18n("Use the ADS security level if you have a big network " + "and the samba server should act as a domain member in an ADS realm.")); + } + +} diff --git a/filesharing/advanced/kcm_sambaconf/kcmprinterdlg.ui b/filesharing/advanced/kcm_sambaconf/kcmprinterdlg.ui new file mode 100644 index 00000000..7aefef4f --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/kcmprinterdlg.ui @@ -0,0 +1,1211 @@ + + KcmPrinterDlg + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + + + + KcmPrinterDlg + + + + 0 + 0 + 533 + 381 + + + + Printer Settings + + + + unnamed + + + 11 + + + 6 + + + + _tabs + + + + tab + + + &Base Settings + + + + unnamed + + + + Layout51 + + + + unnamed + + + 0 + + + 6 + + + + pixFrame + + + Box + + + Sunken + + + + unnamed + + + 11 + + + 6 + + + + printerPixLbl + + + + 0 + 0 + 0 + 0 + + + + Pixmap + + + true + + + + + + + printerGrp + + + true + + + Pr&inter + + + + unnamed + + + 11 + + + 6 + + + + Layout49 + + + + unnamed + + + 0 + + + 6 + + + + pathUrlRq + + + + + TextLabel7 + + + Pa&th: + + + pathUrlRq + + + + + TextLabel4_2 + + + &Queue: + + + queueCombo + + + + + Layout48 + + + + unnamed + + + 0 + + + 6 + + + + queueCombo + + + + + printersChk + + + true + + + Sha&re all printers + + + + + + + + + + + + + identifierGrp + + + I&dentifier + + + + unnamed + + + 11 + + + 6 + + + + Lbl_shareName + + + &Name: + + + shareNameEdit + + + + + TextLabel3 + + + Comm&ent: + + + commentEdit + + + + + commentEdit + + + + + shareNameEdit + + + + + + + GroupBox4 + + + Main Properties + + + + unnamed + + + 11 + + + 6 + + + + availableBaseChk + + + A&vailable + + + + + browseableBaseChk + + + Bro&wseable + + + + + publicBaseChk + + + Pub&lic + + + + + + + Spacer18 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + + + tab + + + &Printing + + + + unnamed + + + + Spacer24 + + + Vertical + + + Expanding + + + + 20 + 50 + + + + + + TextLabel5_2_8 + + + + 1 + + + + Printer dri&ver: + + + printerDriverEdit + + + + + printerDriverEdit + + + + + TextLabel5_2_2_2 + + + + 1 + + + + Print&er driver location: + + + printerDriverLocationEdit + + + + + printerDriverLocationEdit + + + + + postscriptChk + + + PostScr&ipt + + + + + PrintingLbl + + + Printin&g: + + + printingCombo + + + + + + sysv + + + + + aix + + + + + hpux + + + + + bsd + + + + + qnx + + + + + plp + + + + + lprng + + + + + softq + + + + + cups + + + + + nt + + + + + os2 + + + + printingCombo + + + + + TextLabel3_2_2 + + + Max reported print &jobs: + + + maxReportedPrintJobsSpin + + + + + TextLabel3_2 + + + Ma&x print jobs: + + + maxPrintJobsSpin + + + + + maxReportedPrintJobsSpin + + + + 1 + + + + 2147483647 + + + + + maxPrintJobsSpin + + + + 1 + + + + 2147483647 + + + + + Spacer26 + + + Horizontal + + + Expanding + + + + 200 + 20 + + + + + + useClientDriverChk + + + Use c&lient driver + + + + + defaultDevmodeChk + + + De&fault devmode + + + + + + + tab + + + &Security + + + + unnamed + + + + TextLabel6 + + + Hosts &deny: + + + hostsDenyEdit + + + The opposite of hosts allow - hosts listed here are NOT permitted access to services unless the specific services have their own lists to override this one. Where the lists conflict, the allow list takes precedence. + + + + + hostsDenyEdit + + + The opposite of hosts allow - hosts listed here are NOT permitted access to services unless the specific services have their own lists to override this one. Where the lists conflict, the allow list takes precedence. + + + + + hostsAllowEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + TextLabel5 + + + Hosts a&llow: + + + hostsAllowEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + printerAdminEdit + + + The opposite of hosts allow - hosts listed here are NOT permitted access to services unless the specific services have their own lists to override this one. Where the lists conflict, the allow list takes precedence. + + + + + Spacer21 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + TextLabel6_2 + + + P&rinter admin: + + + printerAdminEdit + + + The opposite of hosts allow - hosts listed here are NOT permitted access to services unless the specific services have their own lists to override this one. Where the lists conflict, the allow list takes precedence. + + + + + TextLabel1_2 + + + &Guest account: + + + guestAccountCombo + + + This is a username which will be used for access to services which are specified as guest ok. Whatever privileges this user has will be available to any client connecting to the guest service. Typically this user will exist in the password file, but will not have a valid login. The user account \"ftp\" is often a good choice for this parameter. If a username is specified in a given service, the specified username overrides this one. + + + + + guestAccountCombo + + + + + Spacer27 + + + Horizontal + + + Expanding + + + + 263 + 20 + + + + + + + + tab + + + Co&mmands + + + + unnamed + + + 11 + + + 6 + + + + TextLabel5_2 + + + Pr&int command: + + + printCommandEdit + + + + + printCommandEdit + + + + + TextLabel5_2_2 + + + lpq comma&nd: + + + lpqCommandEdit + + + + + lpqCommandEdit + + + + + lprmCommandEdit + + + + + TextLabel5_2_3 + + + lprm comman&d: + + + lprmCommandEdit + + + + + lpresumeEdit + + + + + lppauseEdit + + + + + TextLabel5_2_5 + + + lp&resume: + + + lpresumeEdit + + + + + TextLabel5_2_6 + + + &queuepause: + + + queuepauseEdit + + + + + TextLabel5_2_4 + + + &lppause: + + + lppauseEdit + + + + + queuepauseEdit + + + + + queueresumeEdit + + + + + TextLabel5_2_7 + + + qu&eueresume: + + + queueresumeEdit + + + + + Spacer23 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + + + tab + + + &Advanced + + + + unnamed + + + + GroupBox31 + + + Miscella&neous + + + + unnamed + + + 11 + + + 6 + + + + postExecEdit + + + + + preExecEdit + + + + + TextLabel6_3 + + + p&reexec: + + + preExecEdit + + + + + TextLabel6_3_3 + + + root pr&eexec: + + + rootPreExecEdit + + + + + rootPreExecEdit + + + + + rootPostExecEdit + + + + + TextLabel6_3_4 + + + root postexec: + + + rootPostExecEdit + + + + + TextLabel6_3_2 + + + poste&xec: + + + postExecEdit + + + + + + + Spacer22 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + GroupBox27 + + + Tunin&g + + + + unnamed + + + 11 + + + 6 + + + + TextLabel1 + + + + 0 + 5 + 0 + 0 + + + + M&in print space: + + + minPrintSpaceSpin + + + + + minPrintSpaceSpin + + + + 1 + + + + 2147483647 + + + + + textLabel2 + + + kB + kilo Byte + + + + + + + GroupBox17 + + + &Logging + + + + unnamed + + + 11 + + + 6 + + + + statusChk + + + S&tatus + + + + + + + + + + Layout1 + + + + unnamed + + + 0 + + + 6 + + + + buttonHelp + + + &Help + + + F1 + + + true + + + + + Horizontal Spacing2 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + buttonOk + + + &OK + + + true + + + true + + + + + buttonCancel + + + &Cancel + + + true + + + + + + + + + printersChk + toggled(bool) + queueCombo + setDisabled(bool) + + + printersChk + toggled(bool) + shareNameEdit + setDisabled(bool) + + + buttonCancel + clicked() + KcmPrinterDlg + reject() + + + buttonOk + clicked() + KcmPrinterDlg + accept() + + + printersChk + toggled(bool) + KcmPrinterDlg + printersChkToggled(bool) + + + + queueCombo + printersChk + pathUrlRq + shareNameEdit + commentEdit + availableBaseChk + browseableBaseChk + publicBaseChk + minPrintSpaceSpin + statusChk + preExecEdit + postExecEdit + rootPreExecEdit + rootPostExecEdit + printCommandEdit + lpqCommandEdit + lprmCommandEdit + lppauseEdit + lpresumeEdit + queuepauseEdit + queueresumeEdit + guestAccountCombo + printerAdminEdit + hostsAllowEdit + hostsDenyEdit + printingCombo + maxPrintJobsSpin + maxReportedPrintJobsSpin + printerDriverEdit + printerDriverLocationEdit + postscriptChk + useClientDriverChk + defaultDevmodeChk + buttonOk + buttonCancel + buttonHelp + _tabs + + + kiconloader.h + kcmprinterdlg.ui.h + + + init() + accept() + reject() + printersChkToggled( bool ) + + + + kurlrequester.h + klineedit.h + kpushbutton.h + kcombobox.h + klineedit.h + klineedit.h + knuminput.h + knuminput.h + knuminput.h + + diff --git a/filesharing/advanced/kcm_sambaconf/kcmprinterdlg.ui.h b/filesharing/advanced/kcm_sambaconf/kcmprinterdlg.ui.h new file mode 100644 index 00000000..a02204dd --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/kcmprinterdlg.ui.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename slots use Qt Designer which will +** update this file, preserving your code. Create an init() slot in place of +** a constructor, and a destroy() slot in place of a destructor. +*****************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +void KcmPrinterDlg::init() +{ + printerPixLbl->setPixmap(DesktopIcon("printer1")); +} + +void KcmPrinterDlg::accept() +{ + QDialog::accept(); +} + +void KcmPrinterDlg::reject() +{ + QDialog::reject(); +} + +void KcmPrinterDlg::printersChkToggled( bool ) +{ + +} diff --git a/filesharing/advanced/kcm_sambaconf/kcmsambaconf.cpp b/filesharing/advanced/kcm_sambaconf/kcmsambaconf.cpp new file mode 100644 index 00000000..f6ee4f75 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/kcmsambaconf.cpp @@ -0,0 +1,1442 @@ +/*************************************************************************** + kcmsambaconf.cpp - description + ------------------- + begin : Mon Apr 8 13:35:56 CEST 2002 + copyright : (C) 2002 by Christian Nitschkowski, + email : segfault_ii@web.de + + copyright : (C) 2002-2004 by Jan Schaefer + email : janschaefer@users.sourceforge.net +***************************************************************************/ + +/****************************************************************************** +* * +* This file is part of KSambaPlugin. * +* * +* KSambaPlugin 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. * +* * +* KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +******************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sambashare.h" +#include "sambafile.h" +#include "sharedlgimpl.h" +#include "printerdlgimpl.h" +#include "dictmanager.h" +#include "kcmsambaconf.h" +#include "smbpasswdfile.h" +#include "passwd.h" +#include "qmultichecklistitem.h" +#include "joindomaindlg.h" +#include "smbconfconfigwidget.h" + + +#define COL_DISABLED 2 +#define COL_NOPASSWORD 3 + + +ShareListViewItem::ShareListViewItem(QListView * parent, SambaShare* share) + : QListViewItem(parent) +{ + setShare(share); +} + +SambaShare* ShareListViewItem::getShare() const +{ + return _share; +} + +void ShareListViewItem::setShare(SambaShare* share) +{ + assert(share); + _share = share; + updateShare(); +} + +void ShareListViewItem::updateShare() +{ + assert(_share); + + setText(0,_share->getName()); + setText(2,_share->getValue("comment")); + + if (_share->isPrinter()) + { + if ( _share->getName() == "printers" ) + setPixmap(0,SmallIcon("print_class")); + else + setPixmap(0,SmallIcon("print_printer")); + setText(1,_share->getValue("printer name")); + } + else + { + if ( _share->getName() == "homes" ) + setPixmap(0,SmallIcon("folder_home")); + else + setPixmap(0,SmallIcon("folder")); + setText(1,_share->getValue("path")); + } + + setPixmap(3,createPropertyPixmap()); +} + +QPixmap ShareListViewItem::createPropertyPixmap() +{ + // Create a big pixmap wich holds the + // icons which are needed + + int numberOfPix = 4; // the max number of pixmaps to join + + int w = 22; // Standard size of one pixmap + int margin = 4; // Margin between pixmaps + int h = 22; + + int totalWidth = (w+margin)*numberOfPix; + + QPixmap pix(totalWidth,h); + + pix.fill(); // Fill with white + + QPainter p(&pix); + + int x = 0; + + if (_share->getBoolValue("public")) + { + p.drawPixmap(x,0,SmallIcon("network")); + x = x+w+margin; + } + + if (!_share->getBoolValue("read only")) + { + p.drawPixmap(x,0,SmallIcon("edit")); + x = x+w+margin; + } + + if (_share->getBoolValue("printable")) + { + p.drawPixmap(x,0,SmallIcon("fileprint")); + x = x+w+margin; + } + + if (_share->getBoolValue("browseable")) + { + p.drawPixmap(x,0,SmallIcon("run")); + x = x+w+margin; + } + + if (!_share->getBoolValue("available")) + p.drawPixmap(x,0,SmallIcon("no")); + + + p.end(); + + return QPixmap(pix); +} + +KcmSambaConf::KcmSambaConf(QWidget *parent, const char *name) + : KCModule(parent,name) +{ + _dictMngr = 0L; + _sambaFile = 0L; + m_smbConfConfigWidget = 0L; + + + QBoxLayout * l = new QHBoxLayout( this ); + l->setAutoAdd( TRUE ); + + QString smbFile = SambaFile::findSambaConf(); + if (smbFile.isNull()) { + createSmbConfigWidget(); + return; + } + + slotSpecifySmbConf(smbFile); + +} + + +KcmSambaConf::~KcmSambaConf() { + delete _dictMngr; +} + +void KcmSambaConf::createSmbConfigWidget() { + m_smbConfConfigWidget = new SmbConfConfigWidget(this); + connect( m_smbConfConfigWidget, SIGNAL(smbConfChoosed(const QString &)), + this, SLOT(slotSpecifySmbConf(const QString &))); +} + +void KcmSambaConf::slotSpecifySmbConf(const QString & smbConf) { + if (m_smbConfConfigWidget) { + m_smbConfConfigWidget->hide(); + } + + init(); + initAdvancedTab(); + load(smbConf); + + + if (getuid() != 0) { + for (int i=0;i<_interface->mainTab->count();i++) { + QWidget* w = _interface->mainTab->page(i); + w->setEnabled(false); + } + } + + _interface->show(); +} + +void KcmSambaConf::init() { + + _interface = new KcmInterface(this); + + + connect ( _interface->sambaUserPasswordBtn, SIGNAL(clicked()), this, SLOT(sambaUserPasswordBtnClicked())); + + connect ( _interface->editShareBtn, SIGNAL(clicked()), this, SLOT(editShare())); + connect ( _interface->addShareBtn, SIGNAL(clicked()), this, SLOT(addShare())); + connect ( _interface->removeShareBtn, SIGNAL(clicked()), this, SLOT(removeShare())); + + connect ( _interface->editPrinterBtn, SIGNAL(clicked()), this, SLOT(editPrinter())); + connect ( _interface->addPrinterBtn, SIGNAL(clicked()), this, SLOT(addPrinter())); + connect ( _interface->removePrinterBtn, SIGNAL(clicked()), this, SLOT(removePrinter())); + + connect ( _interface->editDefaultPrinterBtn, SIGNAL(clicked()), this, SLOT(editPrinterDefaults())); + connect ( _interface->editDefaultShareBtn, SIGNAL(clicked()), this, SLOT(editShareDefaults())); + + connect( _interface->domainRadio, SIGNAL(toggled(bool)), + _interface->joinADomainBtn, SLOT( setEnabled(bool) )); + + connect(_interface->nullPasswordsChk,SIGNAL(toggled(bool)), + this, SLOT(nullPasswordsEnabled(bool))); + + connect( _interface->addSambaUserBtn, SIGNAL(clicked()), + this, SLOT( addSambaUserBtnClicked() )); + + connect( _interface->removeSambaUserBtn, SIGNAL(clicked()), + this, SLOT( removeSambaUserBtnClicked() )); + + _interface->removeSambaUserBtn->setIconSet(SmallIconSet("1rightarrow")); + _interface->addSambaUserBtn->setIconSet(SmallIconSet("1leftarrow")); + + + connect( _interface->sambaUsersListView, SIGNAL(mouseButtonPressed(int,QListViewItem*,const QPoint &,int)), + this, SLOT(slotMouseButtonPressed(int,QListViewItem*,const QPoint &,int))); + + connect( _interface->joinADomainBtn, SIGNAL(clicked()), + this, SLOT( joinADomainBtnClicked() )); + + connect( _interface->loadBtn, SIGNAL(clicked()), + this, SLOT( loadBtnClicked() )); + + connect( _interface, SIGNAL(changed()), this, SLOT(configChanged())); +} + + +void KcmSambaConf::initAdvancedTab() +{ + QVBoxLayout *l = new QVBoxLayout(_interface->advancedFrame); + l->setAutoAdd(true); + l->setMargin(0); + _janus = new KJanusWidget(_interface->advancedFrame,0,KJanusWidget::TreeList); + _janus->setRootIsDecorated(false); + _janus->setShowIconsInTreeList(true); + + QWidget *w; + QFrame *f; + QString label; + QPixmap icon; + + for (int i=0;i<_interface->advancedTab->count();) + { + w = _interface->advancedTab->page(i); + label = _interface->advancedTab->label(i); + + if (label == i18n("Security")) + icon = SmallIcon("password"); + else + if (label == i18n("Logging")) + icon = SmallIcon("history"); + else + if (label == i18n("Tuning")) + icon = SmallIcon("launch"); + else + if (label == i18n("Filenames")) + icon = SmallIcon("folder"); + else + if (label == i18n("Locking")) + icon = SmallIcon("lock"); + else + if (label == i18n("Printing")) + icon = SmallIcon("fileprint"); + else + if (label == i18n("Logon")) + icon = SmallIcon("kdmconfig"); + else + if (label == i18n("Protocol")) + icon = SmallIcon("core"); + else + if (label == i18n("Charset")) + icon = SmallIcon("charset"); + else + if (label == i18n("Socket")) + icon = SmallIcon("socket"); + else + if (label == i18n("SSL")) + icon = SmallIcon("encrypted"); + else + if (label == i18n("Browsing")) + icon = SmallIcon("konqueror"); + else + if (label == i18n("Misc")) + icon = SmallIcon("misc"); + else + if (label == i18n("Commands")) + icon = SmallIcon("konsole"); + else { + icon = QPixmap(16,16); + icon.fill(); + } + //SmallIcon("empty2"); + + f = _janus->addPage( label,label,icon ); + l = new QVBoxLayout(f); + l->setAutoAdd(true); + l->setMargin(0); + + _interface->advancedTab->removePage(w); + + w->reparent(f,QPoint(1,1),TRUE); + + } + + w = _interface->mainTab->page(5); + _interface->mainTab->removePage(w); + delete w; + _interface->advancedWarningPixLbl->setPixmap(DesktopIcon("messagebox_warning")); + + +} + +void KcmSambaConf::editShare() +{ + ShareListViewItem* item = static_cast(_interface->shareListView->selectedItem()); + + if (!item) + return; + + ShareDlgImpl* dlg = new ShareDlgImpl(_interface,item->getShare()); + connect(dlg, SIGNAL(changed()), this, SLOT(configChanged())); + dlg->exec(); + item->updateShare(); + + disconnect(dlg, SIGNAL(changed()), this, SLOT(configChanged())); + + delete dlg; + +} + +void KcmSambaConf::addShare() +{ + SambaShare* share = _sambaFile->newShare(_sambaFile->getUnusedName(),""); + ShareListViewItem* item = new ShareListViewItem( _interface->shareListView, share ); + _interface->shareListView->setSelected(item,true); + + ShareDlgImpl* dlg = new ShareDlgImpl(_interface,share); + dlg->exec(); + + if (dlg->result() == QDialog::Rejected ) + removeShare(); + else { + item->updateShare(); + emit changed(true); + } + + delete dlg; +} + +void KcmSambaConf::removeShare() +{ + ShareListViewItem* item = static_cast(_interface->shareListView->selectedItem()); + + if (!item) + return; + + SambaShare *share = item->getShare(); + delete item; + _sambaFile->removeShare(share); + + emit changed(true); +} + + +void KcmSambaConf::editPrinter() +{ + ShareListViewItem* item = static_cast(_interface->printerListView->selectedItem()); + + if (!item) + return; + + PrinterDlgImpl* dlg = new PrinterDlgImpl(_interface,item->getShare()); + dlg->exec(); + item->updateShare(); + + delete dlg; + + emit changed(true); +} + +void KcmSambaConf::addPrinter() +{ + SambaShare* share = _sambaFile->newPrinter(_sambaFile->getUnusedName(),""); + ShareListViewItem* item = new ShareListViewItem( _interface->shareListView, share ); + _interface->printerListView->setSelected(item,true); + + PrinterDlgImpl* dlg = new PrinterDlgImpl(_interface,share); + dlg->exec(); + + if (dlg->result() == QDialog::Rejected ) + removePrinter(); + else + { + item->updateShare(); + emit changed(true); + } + + delete dlg; +} + +void KcmSambaConf::removePrinter() +{ + ShareListViewItem* item = static_cast(_interface->printerListView->selectedItem()); + + if (!item) + return; + + SambaShare *share = item->getShare(); + delete item; + _sambaFile->removeShare(share); + + emit changed(true); +} + +void KcmSambaConf::editShareDefaults() +{ + SambaShare* share = _sambaFile->getShare("global"); + + ShareDlgImpl* dlg = new ShareDlgImpl(_interface,share); + dlg->directoryGrp->setEnabled(false); + dlg->identifierGrp->setEnabled(false); + dlg->exec(); + delete dlg; + + emit changed(true); +} + +void KcmSambaConf::editPrinterDefaults() +{ + SambaShare* share = _sambaFile->getShare("global"); + + PrinterDlgImpl* dlg = new PrinterDlgImpl(_interface,share); + dlg->printerGrp->setEnabled(false); + dlg->identifierGrp->setEnabled(false); + dlg->exec(); + + delete dlg; + + emit changed(true); +} + + + +void KcmSambaConf::loadBtnClicked() { + load( _interface->configUrlRq->url()); +} + +void KcmSambaConf::load(const QString & smbFile) +{ + kdDebug(5009) << "loading " << smbFile << endl; + _smbconf = smbFile; + + if (_sambaFile) + delete _sambaFile; + + + _sambaFile = new SambaFile(_smbconf,false); + + connect( _sambaFile, SIGNAL(completed()), this, SLOT(fillFields())); + connect( _sambaFile, SIGNAL(canceled(const QString &)), this, SLOT(loadCanceled(const QString &))); + + _sambaFile->load(); + +} + +void KcmSambaConf::loadCanceled(const QString & msg) { + KMessageBox::sorry(0L,msg,i18n("Error while opening file")); +} + +void KcmSambaConf::fillFields() +{ + // Fill the ListViews + + SambaShareList* list = _sambaFile->getSharedDirs(); + + SambaShare *share = 0L; + _interface->shareListView->clear(); + for ( share = list->first(); share; share = list->next() ) + { + new ShareListViewItem(_interface->shareListView, share); + } + + share = 0L; + _interface->printerListView->clear(); + list = _sambaFile->getSharedPrinters(); + for ( share = list->first(); share; share = list->next() ) + { + new ShareListViewItem(_interface->printerListView, share); + } + + share = _sambaFile->getShare("global"); + + if ( !share) + share = _sambaFile->newShare("global"); + + Q_ASSERT( share); + if (_dictMngr) + delete _dictMngr; + + _dictMngr = new DictManager(share); + + + _interface->configUrlRq->setURL( _smbconf ); + _interface->configUrlRq->setMode( KFile::File | KFile::ExistingOnly); + + + loadBaseSettings( share ); + loadSecurity( share ); + loadTuning( share ); + loadLogging( share ); + loadDomain( share ); + loadWins( share ); + loadPrinting( share ); + loadFilenames( share ); + loadLocking( share ); + loadProtocol( share ); + loadSocket( share ); + loadSSL( share ); + loadLogon( share ); + loadCharset( share ); + loadWinbind( share ); + loadNetbios( share ); + loadVFS( share ); + loadLDAP( share ); + loadBrowsing( share ); + loadCommands( share ); + loadMisc( share ); + loadDebug( share ); + + _dictMngr->load( share, false,true ); + + loadUserTab(); + + connect(_dictMngr, SIGNAL(changed()), this, SLOT(configChanged())); + +} + + +void KcmSambaConf::loadBaseSettings(SambaShare* share) +{ + + _dictMngr->add("workgroup", _interface->workgroupEdit); + _dictMngr->add("server string", _interface->serverStringEdit); + _dictMngr->add("netbios name", _interface->netbiosNameEdit); + _dictMngr->add("netbios aliases", _interface->netbiosAliasesEdit); + _dictMngr->add("netbios scope", _interface->netbiosScopeEdit); + _dictMngr->add("interfaces", _interface->interfacesEdit); + + _interface->guestAccountCombo->insertStringList( getUnixUsers() ); + setComboIndexToValue(_interface->guestAccountCombo,"guest account",share); + + QString value = share->getValue("map to guest",false,true); + + _interface->allowGuestLoginsChk->setChecked( value.lower()!="never" ); + + _dictMngr->add("guest ok",_interface->allowGuestLoginsChk); + + _dictMngr->add("bind interfaces only",_interface->bindInterfacesOnlyChk); + + QString s = share->getValue("security",false,true).lower(); + int i = 0; + + if ( s == "share" ) i = 0; else + if ( s == "user" ) i = 1; else + if ( s == "server" ) i = 2; else + if ( s == "domain" ) i = 3; else + if ( s == "ads" ) i = 4; + + _interface->securityLevelBtnGrp->setButton(i); + _interface->updateSecurityLevelHelpLbl(); + +} + + +void KcmSambaConf::loadSecurity(SambaShare*) +{ + + _dictMngr->add("map to guest",_interface->mapToGuestCombo, + new QStringList(QStringList() << "Never" << "Bad User" << "Bad Password" )); + + + _dictMngr->add("password server", _interface->passwordServerEdit); + _dictMngr->add("passwd chat", _interface->passwdChatEdit); + _dictMngr->add("root directory", _interface->rootDirectoryEdit); + _dictMngr->add("passdb backend", _interface->passdbBackendEdit); + _dictMngr->add("auth methods", _interface->authMethodsEdit); + _dictMngr->add("realm", _interface->realmEdit); + + _dictMngr->add("password level", _interface->passwordLevelSpin); + _dictMngr->add("min passwd length", _interface->minPasswdLengthSpin); + _dictMngr->add("username level", _interface->usernameLevelSpin); + _dictMngr->add("algorithmic rid base", _interface->algorithmicRidBaseSpin); + _dictMngr->add("passwd chat timeout", _interface->passwdChatTimeoutSpin); + + _dictMngr->add("encrypt passwords",_interface->encryptPasswordsChk); + _dictMngr->add("update encrypted",_interface->updateEncryptedChk); + _dictMngr->add("passwd chat debug",_interface->passwdChatDebugChk); + _dictMngr->add("unix password sync",_interface->unixPasswordSyncChk); + _dictMngr->add("use rhosts",_interface->useRhostsChk); + _dictMngr->add("hide local users",_interface->hideLocalUsersChk); + + + _dictMngr->add("allow trusted domains",_interface->allowTrustedDomainsChk); + _dictMngr->add("obey pam restrictions",_interface->obeyPamRestrictionsChk); + _dictMngr->add("pam password change",_interface->pamPasswordChangeChk); + _dictMngr->add("restrict anonymous",_interface->restrictAnonymousChk); + _dictMngr->add("null passwords",_interface->nullPasswordsChk); + _dictMngr->add("paranoid server security",_interface->paranoidServerSecurityChk); + + _dictMngr->add("smb passwd file",_interface->smbPasswdFileUrlRq); + _dictMngr->add("passwd program",_interface->passwdProgramUrlRq); + _dictMngr->add("username map",_interface->usernameMapUrlRq); + _dictMngr->add("hosts equiv",_interface->hostsEquivUrlRq); + _dictMngr->add("private dir",_interface->privateDirUrlRq); + + // Authentification + + _dictMngr->add("lanman auth",_interface->lanmanAuthChk); + _dictMngr->add("ntlm auth",_interface->ntlmAuthChk); + _dictMngr->add("use spnego",_interface->useSpnegoChk); + _dictMngr->add("server schannel",_interface->serverSchannelCombo, + new QStringList(QStringList() << "Yes" << "No" << "Auto" )); + _dictMngr->add("server signing",_interface->serverSigningCombo, + new QStringList(QStringList() << "Auto" << "Mandatory" << "Disabled" )); + + _dictMngr->add("client lanman auth",_interface->clientLanmanAuthChk); + _dictMngr->add("client plaintext auth",_interface->clientPlaintextAuthChk); + _dictMngr->add("client ntlmv2 auth",_interface->clientNTLMv2AuthChk); + _dictMngr->add("client use spnego",_interface->clientUseSpnegoChk); + _dictMngr->add("client schannel",_interface->clientSchannelCombo, + new QStringList(QStringList() << "Yes" << "No" << "Auto" )); + _dictMngr->add("client signing",_interface->clientSigningCombo, + new QStringList(QStringList() << "Auto" << "Mandatory" << "Disabled" )); + + + + + +} + +void KcmSambaConf::loadLogging(SambaShare* ) +{ + _dictMngr->add("log file",_interface->logFileUrlRq); + + _dictMngr->add("max log size", _interface->maxLogSizeSpin); + _dictMngr->add("syslog", _interface->syslogSpin); + _dictMngr->add("log level", _interface->logLevelEdit); + + _dictMngr->add("status",_interface->statusChk); + _dictMngr->add("debug uid",_interface->debugUidChk); + _dictMngr->add("debug pid",_interface->debugPidChk); + _dictMngr->add("debug hires timestamp",_interface->microsecondsChk); + _dictMngr->add("syslog only",_interface->syslogOnlyChk); + _dictMngr->add("debug timestamp",_interface->timestampChk); + _dictMngr->add("use mmap",_interface->useMmapChk); + + +} + +void KcmSambaConf::loadTuning(SambaShare* ) +{ + _dictMngr->add("change notify timeout", _interface->changeNotifyTimeoutSpin); + _dictMngr->add("deadtime", _interface->deadtimeSpin); + _dictMngr->add("keepalive", _interface->keepaliveSpin); + _dictMngr->add("lpq cache time", _interface->lpqCacheTimeSpin); + _dictMngr->add("max open files", _interface->maxOpenFilesSpin); + _dictMngr->add("read size", _interface->readSizeSpin); + _dictMngr->add("max disk size", _interface->maxDiskSizeSpin); + _dictMngr->add("stat cache size", _interface->statCacheSizeSpin); + _dictMngr->add("max smbd processes", _interface->maxSmbdProcessesSpin); + _dictMngr->add("name cache timeout", _interface->nameCacheTimeoutSpin); + + _dictMngr->add("getwd cache",_interface->getwdCacheChk); + _dictMngr->add("use mmap",_interface->useMmapChk); + _dictMngr->add("hostname lookups",_interface->hostnameLookupsChk); + _dictMngr->add("kernel change notify",_interface->kernelChangeNotifyChk); + +} + +void KcmSambaConf::loadLocking(SambaShare* ) +{ + _dictMngr->add("kernel oplocks",_interface->kernelOplocksChk); + _dictMngr->add("lock directory",_interface->lockDirectoryUrlRq); + _dictMngr->add("pid directory",_interface->pidDirectoryUrlRq); + _dictMngr->add("oplock break wait time",_interface->oplockBreakWaitTimeSpin); + _dictMngr->add("lock spin time",_interface->lockSpinTimeSpin); + _dictMngr->add("lock spin count",_interface->lockSpinCountSpin); + + +} + +void KcmSambaConf::loadDomain(SambaShare*) +{ + _dictMngr->add("preferred master",_interface->preferredMasterChk); + _dictMngr->add("local master",_interface->localMasterChk); + _dictMngr->add("domain master",_interface->domainMasterChk); + _dictMngr->add("domain logons",_interface->domainLogonsChk); + _dictMngr->add("machine password timeout", _interface->machinePasswordTimeoutSpin); + _dictMngr->add("os level", _interface->osLevelSpin); + _dictMngr->add("domain admin group", _interface->domainAdminGroupEdit); + _dictMngr->add("domain guest group", _interface->domainGuestGroupEdit); + +} + +void KcmSambaConf::loadWins(SambaShare* share) +{ + _dictMngr->add("wins proxy",_interface->winsProxyChk); + _dictMngr->add("dns proxy",_interface->dnsProxyChk); + _dictMngr->add("wins server", _interface->winsServerEdit); + _dictMngr->add("wins hook", _interface->winsHookEdit); + _interface->winsSupportRadio->setChecked( share->getBoolValue("wins support",false,true)); + _interface->otherWinsRadio->setChecked( !share->getValue("wins server",false,true).isEmpty() ); +} + + +void KcmSambaConf::loadPrinting(SambaShare* ) +{ + _dictMngr->add("load printers",_interface->loadPrintersChk); + _dictMngr->add("disable spoolss",_interface->disableSpoolssChk); + _dictMngr->add("show add printer wizard",_interface->showAddPrinterWizardChk); + + _dictMngr->add("addprinter command", _interface->addprinterCommandEdit); + _dictMngr->add("deleteprinter command", _interface->deleteprinterCommandEdit); + _dictMngr->add("enumports command", _interface->enumportsCommandEdit); + + _dictMngr->add("printcap name",_interface->printcapNameUrlRq); + _dictMngr->add("os2 driver map",_interface->os2DriverMapUrlRq); + _dictMngr->add("printer driver file",_interface->printerDriverFileUrlRq); + + _dictMngr->add("total print jobs", _interface->totalPrintJobsSpin); +} + +void KcmSambaConf::loadFilenames(SambaShare* ) +{ + _dictMngr->add("strip dot",_interface->stripDotChk); + _dictMngr->add("stat cache",_interface->statCacheChk); + + + _dictMngr->add("mangled stack", _interface->mangledStackSpin); + _dictMngr->add("mangle prefix", _interface->manglePrefixSpin); + +} + +void KcmSambaConf::loadProtocol(SambaShare*) +{ + // Protocol + + _dictMngr->add("write raw",_interface->writeRawChk); + _dictMngr->add("read raw",_interface->readRawChk); + _dictMngr->add("read bmpx",_interface->readBmpxChk); + _dictMngr->add("large readwrite",_interface->largeReadWriteChk); + _dictMngr->add("nt smb support",_interface->ntSmbSupportChk); + _dictMngr->add("nt pipe support",_interface->ntPipeSupportChk); + _dictMngr->add("time server",_interface->timeServerChk); + _dictMngr->add("unix extensions",_interface->unixExtensionsChk); + + _dictMngr->add("max mux", _interface->maxMuxSpin); + _dictMngr->add("max xmit", _interface->maxXmitSpin); + _dictMngr->add("max ttl", _interface->maxTtlSpin); + _dictMngr->add("max wins ttl", _interface->maxWinsTtlSpin); + _dictMngr->add("min wins ttl", _interface->minWinsTtlSpin); + + _dictMngr->add("announce version", _interface->announceVersionEdit); + _dictMngr->add("name resolve order", _interface->nameResolveOrderEdit); + _dictMngr->add("smb ports", _interface->smbPortsEdit); + + _dictMngr->add("announce as", _interface->announceAsCombo, + new QStringList(QStringList() << "NT" << "NT workstation" << "win95" << "WfW")); + + _dictMngr->add("protocol", _interface->protocolCombo, + new QStringList(QStringList() << "NT" << "lanman2" << "lanman1" << "core" << "coreplus" )); + + _dictMngr->add("max protocol", _interface->maxProtocolCombo, + new QStringList(QStringList() << "NT" << "lanman2" << "lanman1" << "core" << "coreplus")); + + _dictMngr->add("min protocol", _interface->minProtocolCombo, + new QStringList(QStringList() << "NT" << "lanman2" << "lanman1" << "core" << "coreplus")); + +} + +void KcmSambaConf::loadSocket(SambaShare* share) +{ + // SOCKET options + + _dictMngr->add("socket address", _interface->socketAddressEdit); + + QString s = share->getValue("socket options"); + s = s.simplifyWhiteSpace(); + + // The string s has now the form : + // "OPTION1=1 OPTION2=0 OPTION3=2234 OPTION4" + + _interface->SO_KEEPALIVEChk->setChecked(getSocketBoolValue( s, "SO_KEEPALIVE") ); + _interface->SO_REUSEADDRChk->setChecked( getSocketBoolValue( s, "SO_REUSEADDR") ); + _interface->SO_BROADCASTChk->setChecked( getSocketBoolValue( s, "SO_BROADCAST") ); + _interface->TCP_NODELAYChk->setChecked( getSocketBoolValue( s, "TCP_NODELAY") ); + _interface->IPTOS_LOWDELAYChk->setChecked( getSocketBoolValue( s, "IPTOS_LOWDELAY") ); + _interface->IPTOS_THROUGHPUTChk->setChecked( getSocketBoolValue( s, "IPTOS_THROUGHPUT") ); + + _interface->SO_SNDBUFChk->setChecked( getSocketBoolValue( s, "SO_SNDBUF") ); + _interface->SO_RCVBUFChk->setChecked( getSocketBoolValue( s, "SO_RCVBUF") ); + _interface->SO_SNDLOWATChk->setChecked( getSocketBoolValue( s, "SO_SNDLOWAT") ); + _interface->SO_RCVLOWATChk->setChecked( getSocketBoolValue( s, "SO_RCVLOWAT") ); + + _interface->SO_SNDBUFSpin->setValue( getSocketIntValue( s, "SO_SNDBUF") ); + _interface->SO_RCVBUFSpin->setValue( getSocketIntValue( s, "SO_RCVBUF") ); + _interface->SO_SNDLOWATSpin->setValue( getSocketIntValue( s, "SO_SNDLOWAT") ); + _interface->SO_RCVLOWATSpin->setValue( getSocketIntValue( s, "SO_RCVLOWAT") ); + +} + +void KcmSambaConf::loadSSL(SambaShare*) +{ + // SSL + + _dictMngr->add("ssl version",_interface->sslVersionCombo, + new QStringList(QStringList() << "ssl2" << "ssl3" << "ssl2or3" << "tls1" )); + + _dictMngr->add("ssl",_interface->sslChk); + _dictMngr->add("ssl require server cert",_interface->sslRequireServercertChk); + _dictMngr->add("ssl compatibility",_interface->sslCompatibilityChk); + _dictMngr->add("ssl require clientcert",_interface->sslRequireClientcertChk); + + _dictMngr->add("ssl hosts edit", _interface->sslHostsEdit); + _dictMngr->add("ssl hosts resign", _interface->sslHostsResignEdit); + _dictMngr->add("ssl egd socket", _interface->sslEgdSocketEdit); + _dictMngr->add("ssl ciphers edit", _interface->sslCiphersEdit); + + _dictMngr->add("ssl CA cert dir",_interface->sslCACertDirUrlRq); + _dictMngr->add("ssl CA cert file",_interface->sslCACertFileUrlRq); + _dictMngr->add("ssl entropy file",_interface->sslEntropyFileUrlRq); + _dictMngr->add("ssl client cert",_interface->sslClientCertUrlRq); + _dictMngr->add("ssl client key",_interface->sslClientKeyUrlRq); + _dictMngr->add("ssl server cert",_interface->sslServerCertUrlRq); + _dictMngr->add("ssl server key",_interface->sslServerKeyUrlRq); + + _dictMngr->add("ssl entropy bytes", _interface->sslEntropyBytesSpin); + +} + +void KcmSambaConf::loadLogon(SambaShare* ) +{ + // Logon + + _dictMngr->add("add user script", _interface->addUserScriptEdit); + _dictMngr->add("add group script", _interface->addGroupScriptEdit); + _dictMngr->add("add machine script", _interface->addMachineScriptEdit); + _dictMngr->add("add user to group script", _interface->addUserToGroupScriptEdit); + _dictMngr->add("delete user script", _interface->deleteUserScriptEdit); + _dictMngr->add("delete group script", _interface->deleteGroupScriptEdit); + _dictMngr->add("delete user from group script", _interface->deleteUserFromGroupScriptEdit); + _dictMngr->add("set primary group script", _interface->addGroupScriptEdit); + _dictMngr->add("shutdown script", _interface->shutdownScriptEdit); + _dictMngr->add("abort shutdown script", _interface->abortShutdownScriptEdit); + _dictMngr->add("logon script", _interface->logonScriptEdit); + _dictMngr->add("logon drive", _interface->logonDriveEdit); + _dictMngr->add("logon path",_interface->logonPathUrlRq); + _dictMngr->add("logon home",_interface->logonHomeUrlRq); + +} + + +void KcmSambaConf::loadCharset(SambaShare* ) +{ + _dictMngr->add("coding system", _interface->codingSystemEdit); + _dictMngr->add("client code page", _interface->clientCodePageEdit); + _dictMngr->add("code page directory",_interface->codePageDirUrlRq); + _dictMngr->add("display charset", _interface->displayCharsetEdit); + _dictMngr->add("unix charset", _interface->unixCharsetEdit); + _dictMngr->add("dos charset", _interface->dosCharsetEdit); + _dictMngr->add("character set", _interface->characterSetEdit); + _dictMngr->add("valid chars", _interface->validCharsEdit); + + _dictMngr->add("unicode",_interface->unicodeChk); +} + +void KcmSambaConf::loadWinbind(SambaShare* ) +{ + _dictMngr->add("winbind uid", _interface->winbindUidEdit); + _dictMngr->add("winbind gid", _interface->winbindGidEdit); + _dictMngr->add("template homedir", _interface->templateHomedirEdit); + _dictMngr->add("template shell", _interface->templateShellEdit); + _dictMngr->add("winbind separator", _interface->winbindSeparatorEdit); + _dictMngr->add("template primary group", _interface->templatePrimaryGroupEdit); + + _dictMngr->add("winbind cache time", _interface->winbindCacheTimeSpin); + _dictMngr->add("acl compatibility",_interface->aclCompatibilityCombo, + new QStringList(QStringList() << "winnt" << "win2k" << "")); + + _dictMngr->add("winbind enum users",_interface->winbindEnumUsersChk); + _dictMngr->add("winbind enum groups",_interface->winbindEnumGroupsChk); + _dictMngr->add("winbind use default domain",_interface->winbindUseDefaultDomainChk); + _dictMngr->add("winbind trusted domains only",_interface->winbindTrustedDomainsOnlyChk); + _dictMngr->add("winbind enable local accounts",_interface->winbindEnableLocalAccountsChk); + _dictMngr->add("winbind nested groups",_interface->winbindNestedGroupsChk); + + +} + +void KcmSambaConf::loadNetbios(SambaShare* ) +{ + _dictMngr->add("disable netbios",_interface->disableNetbiosChk); + + _dictMngr->add("netbios aliases", _interface->netbiosAliasesEdit); + _dictMngr->add("netbios scope", _interface->netbiosScopeEdit); +} + +void KcmSambaConf::loadVFS(SambaShare*) +{ + _dictMngr->add("host msdfs",_interface->hostMsdfsChk); + +} + +void KcmSambaConf::loadLDAP(SambaShare*) +{ + _dictMngr->add("ldap suffix", _interface->ldapSuffixEdit); + _dictMngr->add("ldap machine suffix", _interface->ldapMachineSuffixEdit); + _dictMngr->add("ldap user suffix", _interface->ldapUserSuffixEdit); + _dictMngr->add("ldap group suffix", _interface->ldapGroupSuffixEdit); + _dictMngr->add("ldap idmap suffix", _interface->ldapIdmapSuffixEdit); + _dictMngr->add("ldap filter", _interface->ldapFilterEdit); + _dictMngr->add("ldap admin dn", _interface->ldapAdminDnEdit); + _dictMngr->add("idmap backend", _interface->idmapBackendEdit); + + _dictMngr->add("ldap replication sleep",_interface->ldapReplicationSleepSpin); + + _dictMngr->add("ldap delete dn",_interface->ldapDeleteDnChk); + + _dictMngr->add("ldap ssl", _interface->ldapSslCombo, + new QStringList(QStringList() << "No" << "Start_tls" << "Yes")); + + _dictMngr->add("ldap sync", _interface->ldapSyncCombo, + new QStringList(QStringList() << "Yes" << "No" << "Only")); + + +} + +void KcmSambaConf::loadBrowsing(SambaShare*) +{ + _dictMngr->add("enhanced browsing",_interface->enhancedBrowsingChk); + _dictMngr->add("browse list",_interface->browseListChk); + _dictMngr->add("lm interval", _interface->lmIntervalSpin); + _dictMngr->add("remote browse sync", _interface->remoteBrowseSyncEdit); + _dictMngr->add("preload", _interface->preloadEdit); + + _dictMngr->add("lm announce", _interface->lmAnnounceCombo, + new QStringList(QStringList() << "Yes" << "No" << "Auto")); +} + +void KcmSambaConf::loadCommands(SambaShare*) +{ + _dictMngr->add("add share command", _interface->addShareCommandEdit); + _dictMngr->add("change share command", _interface->changeShareCommandEdit); + _dictMngr->add("delete share command", _interface->deleteShareCommandEdit); + _dictMngr->add("message command", _interface->messageCommandEdit); + _dictMngr->add("dfree command", _interface->dfreeCommandEdit); + _dictMngr->add("set quota command", _interface->setQuotaCommandEdit); + _dictMngr->add("get quota command", _interface->getQuotaCommandEdit); + _dictMngr->add("panic action", _interface->panicActionEdit); + +} + +void KcmSambaConf::setComboIndexToValue(QComboBox* box, const QString & value, SambaShare* share) +{ + int i = box->listBox()->index(box->listBox()->findItem(share->getValue(value,false,true),Qt::ExactMatch)); + box->setCurrentItem(i); +} + + +void KcmSambaConf::loadMisc(SambaShare*) +{ + _dictMngr->add("preload modules", _interface->preloadModulesEdit); + _dictMngr->add("default service", _interface->defaultServiceEdit); + _dictMngr->add("remote announce", _interface->remoteAnnounceEdit); + _dictMngr->add("homedir map", _interface->homedirMapEdit); + _dictMngr->add("source environment", _interface->sourceEnvironmentEdit); + + _dictMngr->add("utmp directory",_interface->utmpDirectoryUrlRq); + _dictMngr->add("wtmp directory",_interface->wtmpDirectoryUrlRq); + + _dictMngr->add("NIS homedir", _interface->nisHomedirChk); + _dictMngr->add("time offset", _interface->timeOffsetSpin); +} + +void KcmSambaConf::loadDebug(SambaShare*) { + _dictMngr->add("nt status support", _interface->ntStatusSupportChk); +} + + + +void KcmSambaConf::loadUserTab() +{ + // Remote editing of users isn't supported yet + if ( _sambaFile->isRemoteFile()) { + _interface->mainTab->page(3)->setEnabled(false); + return; + } else + _interface->mainTab->page(3)->setEnabled(true); + + + SambaShare* share = _sambaFile->getShare("global"); + + QStringList added; + + SmbPasswdFile passwd( KURL(share->getValue("smb passwd file",true,true)) ); + SambaUserList sambaList = passwd.getSambaUserList(); + _interface->sambaUsersListView->clear(); + SambaUser *user; + for ( user = sambaList.first(); user; user = sambaList.next() ) + { + QMultiCheckListItem* item = new QMultiCheckListItem(_interface->sambaUsersListView); + item->setText(0,user->name); + item->setText(1,QString::number(user->uid)); + item->setOn(COL_DISABLED,user->isDisabled); + item->setOn(COL_NOPASSWORD,user->hasNoPassword); + + if ( ! _interface->nullPasswordsChk->isOn()) + item->setDisabled(COL_NOPASSWORD, true); + + added.append(user->name); + + + } + + _interface->unixUsersListView->clear(); + + UnixUserList unixList = getUnixUserList(); + UnixUser *unixUser; + for ( unixUser = unixList.first(); unixUser; unixUser = unixList.next() ) + { + QStringList::Iterator it; + + it=added.find(unixUser->name); + if (it == added.end()) + new KListViewItem(_interface->unixUsersListView, unixUser->name, QString::number(unixUser->uid)); + } + + _interface->unixUsersListView->setSelectionMode(QListView::Extended); + _interface->sambaUsersListView->setSelectionMode(QListView::Extended); + +} + +void KcmSambaConf::joinADomainBtnClicked() { + JoinDomainDlg *dlg = new JoinDomainDlg(); + dlg->domainEdit->setText(_interface->workgroupEdit->text()); + dlg->domainControllerEdit->setText(_interface->passwordServerEdit->text()); + + int result = dlg->exec(); + + if (result == QDialog::Accepted) { + SmbPasswdFile passwd; + if (!passwd.joinADomain(dlg->domainEdit->text(), + dlg->domainControllerEdit->text(), + dlg->usernameEdit->text(), + dlg->passwordEdit->text())) + { + KMessageBox::sorry(0,i18n("Joining the domain %1 failed.").arg(dlg->domainEdit->text())); + } + } + delete dlg; +} + + +void KcmSambaConf::slotMouseButtonPressed(int,QListViewItem* item,const QPoint &,int col) { + if (col < 2) + return; + + SambaShare* share = _sambaFile->getShare("global"); + SmbPasswdFile passwd( KURL(share->getValue("smb passwd file",true,true)) ); + QMultiCheckListItem* i = static_cast(item); + SambaUser user( item->text(0), item->text(1).toInt() ); + user.isDisabled = i->isOn(COL_DISABLED); + user.hasNoPassword = i->isOn(COL_NOPASSWORD); + + if (!i->isDisabled(col)) + { + + switch(col) { + case COL_DISABLED : + if (i->isOn(col)) + passwd.enableUser(user); + else + passwd.disableUser(user); + break; + case COL_NOPASSWORD : + if (i->isOn(col)) { + sambaUserPasswordBtnClicked(); + return; // the item is already set off by the btnClicked method + } + else + passwd.setNoPassword(user); + break; + } + + i->toggle(col); + } +} + +void KcmSambaConf::nullPasswordsEnabled(bool b) +{ + QListViewItemIterator it( _interface->sambaUsersListView ); + for ( ; it.current(); ++it ) { + QMultiCheckListItem* sambaItem = static_cast(it.current()); + sambaItem->setDisabled(COL_NOPASSWORD,!b); + + } +} + +void KcmSambaConf::saveUserTab() +{ +} + +void KcmSambaConf::addSambaUserBtnClicked() +{ + QPtrList list = _interface->unixUsersListView->selectedItems(); + + SambaShare* share = _sambaFile->getShare("global"); + SmbPasswdFile passwd( KURL(share->getValue("smb passwd file",true,true)) ); + + QListViewItem* item; + for ( item = list.first(); item; item = list.first() ) + { + SambaUser user( item->text(0), item->text(1).toInt() ); + + QCString password; + int passResult = KPasswordDialog::getNewPassword(password, + i18n("Please enter a password for the user %1").arg(user.name)); + if (passResult != KPasswordDialog::Accepted) { + list.remove(item); + continue; + } + + if (!passwd.addUser(user,password)) + { + KMessageBox::sorry(0,i18n("Adding the user %1 to the Samba user database failed.").arg(user.name)); + break; + } + + QMultiCheckListItem* sambaItem = new QMultiCheckListItem(_interface->sambaUsersListView); + sambaItem->setText(0,user.name); + sambaItem->setText(1,QString::number(user.uid)); + sambaItem->setOn(COL_DISABLED,false); + sambaItem->setOn(COL_NOPASSWORD,false); + if ( ! _interface->nullPasswordsChk->isOn()) + sambaItem->setDisabled(COL_NOPASSWORD, true); + + + list.remove(item); + delete item; + } +} + +void KcmSambaConf::removeSambaUserBtnClicked() +{ + QPtrList list = _interface->sambaUsersListView->selectedItems(); + + SambaShare* share = _sambaFile->getShare("global"); + SmbPasswdFile passwd( KURL(share->getValue("smb passwd file",true,true)) ); + + QListViewItem* item; + for ( item = list.first(); item; item = list.first() ) + { + SambaUser user( item->text(0), item->text(1).toInt() ); + if (!passwd.removeUser(user)) + { + KMessageBox::sorry(0,i18n("Removing the user %1 from the Samba user database failed.").arg(user.name)); + continue; + } + + new KListViewItem(_interface->unixUsersListView, item->text(0), item->text(1)); + list.remove(item); + delete item; + } +} + +void KcmSambaConf::sambaUserPasswordBtnClicked() +{ + QPtrList list = _interface->sambaUsersListView->selectedItems(); + + SambaShare* share = _sambaFile->getShare("global"); + SmbPasswdFile passwd( KURL(share->getValue("smb passwd file",true,true)) ); + + QListViewItem* item; + for ( item = list.first(); item; item = list.next() ) + { + SambaUser user( item->text(0), item->text(1).toInt() ); + + QCString password; + int passResult = KPasswordDialog::getNewPassword(password, + i18n("Please enter a password for the user %1").arg(user.name)); + if (passResult != KPasswordDialog::Accepted) + return; + + if (!passwd.changePassword(user,password)) + { + KMessageBox::sorry(0,i18n("Changing the password of the user %1 failed.").arg(user.name)); + } else { + static_cast(item)->setOn(COL_NOPASSWORD,false); + } + + } + +} + + +void KcmSambaConf::defaults() { + // insert your default settings code here... + emit changed(true); +} + +#define FILESHARECONF "/etc/security/fileshare.conf" + +void KcmSambaConf::save() { + SambaShare *share = _sambaFile->getShare("global"); + assert(share); + + kdDebug(5009) << "saving ... " << endl; + + // Base settings + + _smbconf = _interface->configUrlRq->url(); + KSimpleConfig config(QString::fromLatin1(FILESHARECONF),false); + config.writeEntry("SMBCONF",_smbconf); + config.sync(); + + // Security + + QString s; + + switch (_interface->securityLevelBtnGrp->id(_interface->securityLevelBtnGrp->selected())) { + case 0 : s = "share";break; + case 1 : s = "user";break; + case 2 : s = "server";break; + case 3 : s = "domain";break; + case 4 : s = "ads";break; + } + + share->setValue("security",s); + + +// share->setValue("security",_interface->securityLevelCombo->currentText()); + share->setValue("map to guest",_interface->mapToGuestCombo->currentText()); + + share->setValue("guest account",_interface->guestAccountCombo->currentText()); + + if (_interface->otherWinsRadio->isChecked()) + share->setValue("wins server",_interface->winsServerEdit->text(), false,true); + else + share->setValue("wins server",QString(""), false,true); + + // socket options + + s = socketOptions(); + share->setValue("socket options",s,false,true); + + + _dictMngr->save( share,false,true ); + + _sambaFile->slotApply(); + +} + +bool KcmSambaConf::getSocketBoolValue( const QString & str, const QString & name ) +{ + QString s = str; + int i = s.find(name ,0,false); + + if (i > -1) + { + s = s.remove(0,i+1+QString(name).length()); + + if ( s.startsWith("=") ) + { + s = s.remove(0,1); + if ( s.startsWith("0")) + return false; + else + return true; + } + else + return true; + } + + return false; +} + +int KcmSambaConf::getSocketIntValue( const QString & str, const QString & name ) +{ + QString s = str; + int i = s.find(name ,0,false); + + if (i > -1) + { + s = s.remove(0,i+name.length()); + if ( s.startsWith("=") ) + { + s = s.remove(0,1); + + i = s.find(" "); + if (i < 0) + i = s.length(); + else + i++; + + s = s.left( i ); + + return s.toInt(); + } + else + return 0; + } + + return 0; +} + +QString KcmSambaConf::socketOptions() +{ + QString s = ""; + + if ( _interface->SO_KEEPALIVEChk->isChecked() ) + s+="SO_KEEPALIVE "; + + if ( _interface->SO_REUSEADDRChk->isChecked() ) + s+= "SO_REUSEADDR "; + + if ( _interface->SO_BROADCASTChk->isChecked() ) + s+= "SO_BROADCAST "; + + if ( _interface->TCP_NODELAYChk->isChecked() ) + s+= "TCP_NODELAY "; + + if ( _interface->IPTOS_LOWDELAYChk->isChecked() ) + s+= "IPTOS_LOWDELAY "; + + if ( _interface->IPTOS_THROUGHPUTChk->isChecked() ) + s+= "IPTOS_THROUGHPUT "; + + if ( _interface->SO_SNDBUFChk->isChecked() ) { + s+= "SO_SNDBUF="; + s+= QString::number( _interface->SO_SNDBUFSpin->value() ); + s+= " "; + } + + if ( _interface->SO_RCVBUFChk->isChecked() ) { + s+= "SO_RCVBUF="; + s+= QString::number( _interface->SO_RCVBUFSpin->value() ); + s+= " "; + } + + if ( _interface->SO_SNDLOWATChk->isChecked() ) { + s+= "SO_SNDLOWAT="; + s+= QString::number( _interface->SO_SNDLOWATSpin->value() ); + s+= " "; + } + + if ( _interface->SO_RCVLOWATChk->isChecked() ) { + s+= "SO_RCVLOWAT="; + s+= QString::number( _interface->SO_RCVLOWATSpin->value() ); + s+= " "; + + } + + return s; + +} + + +int KcmSambaConf::buttons () { + // KCModule::Default|KCModule::Apply|KCModule::Help; + return KCModule::Apply; +} + +void KcmSambaConf::configChanged() { + // insert your saving code here... + emit changed(true); +} + +QString KcmSambaConf::quickHelp() const +{ + return i18n("

Samba Configuration

here you can configure your SAMBA server."); +} + +// ------------------------------------------------------------------------ + +extern "C" +{ + KDE_EXPORT KCModule *create_KcmSambaConf(QWidget *parent, const char *name) + { + KGlobal::locale()->insertCatalogue("kfileshare"); + return new KcmSambaConf(parent, name); + } +} + +#include "kcmsambaconf.moc" diff --git a/filesharing/advanced/kcm_sambaconf/kcmsambaconf.desktop b/filesharing/advanced/kcm_sambaconf/kcmsambaconf.desktop new file mode 100644 index 00000000..f180d216 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/kcmsambaconf.desktop @@ -0,0 +1,97 @@ +[Desktop Entry] +Exec=kcmshell kcmsambaconf +Icon=kcmsambaconf +Type=Application + +Terminal=false +X-KDE-FactoryName=KcmSambaConf +X-KDE-Library=kcmsambaconf +X-KDE-ModuleType=Library +X-KDE-SubstituteUID=false +X-KDE-HasReadOnlyMode=true +X-KDE-RootOnly=true +X-KDE-ParentApp=kcontrol +Categories=Qt;KDE;X-KDE-settings-network;Settings; +OnlyShowIn=KDE; + +Comment=A module to configure shares for Microsoft Windows +Comment[be]=Модуль наÑÑ‚Ð°ÑžÐ»ÐµÐ½Ð½Ñ Ð°Ð³ÑƒÐ»ÑŒÐ½Ñ‹Ñ… Ñ€ÑÑурÑаў Ð´Ð»Ñ Microsoft Windows +Comment[bg]=ÐаÑтройване ÑподелÑнето на реÑурÑи Ñ ÐœÐ°Ð¹ÐºÑ€Ð¾Ñофт Ð£Ð¸Ð½Ð´Ð¾ÑƒÑ +Comment[bn]=মাইকà§à¦°à§‹à¦¸à¦«à§à¦Ÿ উইনà§à¦¡à§‹à¦œà§‡à¦° জনà§à¦¯ ভাগাভাগি কনফিগার করতে à¦à¦•à¦Ÿà¦¿ মডিউল +Comment[bs]=Modul za konfigurisanje dijeljenja direktorija sa Microsoft Windowsom +Comment[ca]=Un mòdul per configurar els recursos compartits de Microsoft Windows +Comment[cs]=Modul pro konfiguraci sdílených prostÅ™edků MS Windows +Comment[da]=Et modul til at indstille shares delt med Microsoft Windows +Comment[de]=Ein Modul zum Einrichten von Microsoft Windows-Freigaben +Comment[el]=Ένα άÏθÏωμα για τη ÏÏθμιση κοινόχÏηστων για τα Microsoft Windows +Comment[es]=Un módulo para configurar recursos compartidos de Microsoft Windows +Comment[et]=Microsoft Windowsi jagatud ressursside seadistusmoodul +Comment[eu]=Microsoft Windows-en partekatzeak konfiguratzeko modulu bat +Comment[fa]=یک پیمانه جهت پیکربندی مشترکات برای میکروساÙت ویندوز +Comment[fi]=Moduli Microsoft Windows jakojen asetuksiin +Comment[fr]=Un module permettant de configurer les partages Microsoft Windows +Comment[gl]=Un módulo para configurar comparticións con Microsoft Windows +Comment[he]=מודול להגדרת ×©×™×ª×•×¤×™× ×¢× ×—×œ×•× ×•×ª של מיקרוסופט +Comment[hu]=Beállítómodul Microsoft Windows-os megosztásokhoz +Comment[is]=Eining til að stilla sameignir fyrir Microsoft Windows +Comment[it]=Un modulo per configurare le condivisioni per Microsoft Windows +Comment[ja]=Microsoft Windows ã¨ã®å…±æœ‰è¨­å®š +Comment[ka]=Microsoft Windows-ის სáƒáƒ–იáƒáƒ áƒáƒ”ბის დáƒáƒ™áƒáƒœáƒ¤áƒ˜áƒ’ურირების მáƒáƒ“ული +Comment[kk]=Microsoft Windows-пен ортақ реÑурÑтарды баптау модулі +Comment[km]=ម៉ូឌុល​ដើម្បី​កំណážáŸ‹â€‹ážšáž…នា​សម្ពáŸáž“្ធ​ការ​ចែក​រំលែក​សម្រាប់​ម៉ៃក្រូសូហ្វ​វ៉ីនដូ +Comment[lt]=Modulis MS Windows dalinamų diskų konfigÅ«ravimui +Comment[nb]=En modul som setter opp delte ressurser for Microsoft Windows +Comment[nds]=Moduul för't Instellen vun Microsoft-Windows-Ressourcen +Comment[ne]=माइकà¥à¤°à¥‹à¤¸à¤«à¥à¤Ÿ विनà¥à¤¡à¥‹à¤œà¤•à¤¾ लागि साà¤à¥‡à¤¦à¤¾à¤°à¥€ कनà¥à¤«à¤¿à¤—र गरà¥à¤¨ मोडà¥à¤¯à¥à¤² +Comment[nl]=Een module voor het instellen van shares voor Microsoft Windows +Comment[nn]=Ein modul for oppsett av delte Microsoft Windows-ressursar +Comment[pl]=ModuÅ‚ do konfigurowania udziałów Microsoft Windows +Comment[pt]=Um módulo para configurar partilhas para o Microsoft Windows +Comment[pt_BR]=Um módulo para configurar compartilhamentos com o Microsoft Windows +Comment[ru]=Модуль наÑтройки общих реÑурÑов Ð´Ð»Ñ Microsoft Windows +Comment[sk]=Modul na konfiguráciu zdieľania pre Microsoft Windows +Comment[sl]=Modul za nastavitev souporabe map za Microsoft Windows +Comment[sr]=Модул који подешава дељења за Microsoft Windows +Comment[sr@Latn]=Modul koji podeÅ¡ava deljenja za Microsoft Windows +Comment[sv]=En modul för att ställa in kataloger delade med Microsoft Windows +Comment[tr]=Microsoft Windows paylaşımlarını yapılandıran modül +Comment[uk]=Модуль Ð´Ð»Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñпільних реÑурÑів Ð´Ð»Ñ Microsoft Windows +Comment[zh_CN]=é…ç½® Microsoft Windows å…±äº«çš„æ¨¡å— +Comment[zh_HK]=設定 Microsoft Windows 分享資æºçš„模組 +Comment[zh_TW]=設定 Microsoft Windows 分享的模組 +Keywords=KcmSambaConf,kcmsambaconf,samba,windows,share +Keywords[be]=агульны Ñ€ÑÑурÑ,KcmSambaConf,kcmsambaconf,samba,windows,share +Keywords[bg]=Самба, УиндоуÑ, мрежа, ÑподелÑне, KcmSambaConf, kcmsambaconf, samba, windows, share +Keywords[ca]=KcmSambaConf,kcmsambaconf,samba,windows,share,recurs compartit +Keywords[cs]=Samba,nastavení,windows,sdílení +Keywords[de]=KcmSambaConf,kcmsambaconf,samba,windows,freigabe +Keywords[et]=KcmSambaConf,kcmsambaconf,samba,windows,jagatud ressursid +Keywords[eu]=KcmSambaConf,kcmsambaconf,samba,windows,partekatze +Keywords[fa]=KcmSambaConfØŒ kcmsambaconfØŒ sambaØŒ ویندوز، مشترک +Keywords[fi]=KcmSambaConf,kcmsambaconf,samba,windows,jako +Keywords[fr]=KcmSambaConf,kcmsambaconf,samba,windows,partage +Keywords[gl]=KcmSambaConf,kcmsambaconf,samba,widows,compartir +Keywords[he]=KcmSambaConf,kcmsambaconf,samba,windows,share, סמבה, חלונות, שיתוף, שיתופי חלונות +Keywords[hu]=KcmSambaConf,kcmsambaconf,samba,windows,megosztás +Keywords[is]=KcmSambaConf,kcmsambaconf,samba,windows,sameign +Keywords[it]=KcmSambaConf,kcmsambaconf,samba,windows,condivisione +Keywords[ja]=KcmSambaConf,kcmsambaconf,samba,windows,共有 +Keywords[km]=KcmSambaConf,kcmsambaconf,samba,បង្អួច,ចែក​រំលែក +Keywords[lt]=KcmSambaConf,kcmsambaconf,samba,windows,share,dalinimasis,dalintis,pasidalinti +Keywords[nb]=KcmSambaConf,kcmsambaconf,samba,windows,delt,ressurs +Keywords[nds]=KcmSambaConf,kcmsambaconf,samba,windows,share,freegaav,freegaven +Keywords[ne]=KcmSambaConf,kcmsambaconf,सामà¥à¤¬à¤¾,विनà¥à¤¡à¥‹à¤œ,साà¤à¥‡à¤¦à¤¾à¤°à¥€ +Keywords[nl]=KcmSambaConf,kcmsambaconf,samba,windows netwerk,share,gedeelde mappen,gedeelde printers +Keywords[nn]=KcmSambaConf,kcmsambaconf,samba,Windows,delt ressurs +Keywords[pl]=KcmSambaConf,kcmsambaconf,samba,Samba,serwer,SMB,smb,windows,udziaÅ‚,dysk sieciowy +Keywords[pt]=KcmSambaConf,kcmsambaconf,Samba,Windows,partilha +Keywords[sl]=KcmSambaConf,kcmsambaconf,Samba,samba,souporaba,deljeno +Keywords[sv]=IM SAMBA-inställning,IM samba-inställning,Windows,dela +Keywords[uk]=KcmSambaConf,kcmsambaconf,samba,windows,share,Ñпільні реÑурÑи +Keywords[zh_CN]=KcmSambaConf,kcmsambaconf,samba,windows,share,共享 +Name=Samba +Name[bg]=Самба +Name[bn]=সামà§à¦¬à¦¾ +Name[he]=סמבה +Name[ne]=सामà¥à¤¬à¤¾ +Name[pa]=ਸਾਂਬਾ diff --git a/filesharing/advanced/kcm_sambaconf/kcmsambaconf.h b/filesharing/advanced/kcm_sambaconf/kcmsambaconf.h new file mode 100644 index 00000000..32e37784 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/kcmsambaconf.h @@ -0,0 +1,159 @@ +/*************************************************************************** + kcmsambaconf.h - description + ------------------- + begin : Mon Apr 8 13:35:56 CEST 2002 + copyright : (C) 2002 by Christian Nitschkowski + email : segfault_ii@web.de + copyright : (C) 2002-2003 by Jan Schäfer + email : janschaefer@users.sourceforge.net +***************************************************************************/ + +/****************************************************************************** +* * +* This file is part of KSambaPlugin. * +* * +* KSambaPlugin 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. * +* * +* KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +******************************************************************************/ + +#ifndef KCMSAMBACONF_H_ +#define KCMSAMBACONF_H_ + +#include +#include +#include +#include +#include + + +class SambaShare; +class SambaFile; +class QPixmap; +/** +* A QListViewItem which holds a SambaShare object +**/ +class ShareListViewItem : public QListViewItem +{ +public: + ShareListViewItem(QListView * parent, SambaShare* share); + + SambaShare* getShare() const; + void setShare(SambaShare* share); + void updateShare(); + +protected: + SambaShare* _share; + QPixmap createPropertyPixmap(); +}; + + +class KJanusWidget; +class QLineEdit; +class QCheckBox; +class QSpinBox; +class DictManager; +class SmbConfConfigWidget; + +class KcmSambaConf: public KCModule +{ + Q_OBJECT + public: + KcmSambaConf(QWidget *parent = 0L, const char *name = 0L); + virtual ~KcmSambaConf(); + + void load(const QString &); + void save(); + void defaults(); + int buttons(); + QString quickHelp() const; + + public slots: + void configChanged(); + + void editShare(); + void addShare(); + void removeShare(); + + void editPrinter(); + void addPrinter(); + void removePrinter(); + + void editShareDefaults(); + void editPrinterDefaults(); + + + protected: + /** + * The path of the smb.conf file + **/ + QString _smbconf; + SambaFile* _sambaFile; + + DictManager* _dictMngr; + + void init(); + void loadUserTab(); + void saveUserTab(); + bool getSocketBoolValue( const QString & str, const QString & name ); + int getSocketIntValue( const QString & str, const QString & name ); + QString socketOptions(); + void setComboIndexToValue(QComboBox* box, const QString & value, SambaShare* share); + + void loadBaseSettings(SambaShare*s=0L); + void loadSecurity(SambaShare* ); + void loadLogging(SambaShare* ); + void loadTuning(SambaShare* ); + void loadDomain(SambaShare* ); + void loadWins(SambaShare* ); + void loadPrinting(SambaShare* ); + void loadFilenames(SambaShare* ); + void loadLocking(SambaShare* ); + void loadCharset(SambaShare*); + void loadLogon(SambaShare* ); + void loadSocket(SambaShare* ); + void loadSSL(SambaShare* ); + void loadProtocol(SambaShare* ); + void loadWinbind(SambaShare* ); + void loadNetbios(SambaShare* ); + void loadVFS(SambaShare* ); + void loadBrowsing(SambaShare* ); + void loadCommands(SambaShare*); + void loadMisc(SambaShare* ); + void loadDebug(SambaShare* ); + void loadLDAP(SambaShare*); + + void initAdvancedTab(); + + void createSmbConfigWidget(); + + protected slots: + void addSambaUserBtnClicked(); + void removeSambaUserBtnClicked(); + void sambaUserPasswordBtnClicked(); + void slotMouseButtonPressed(int,QListViewItem*,const QPoint &,int); + void joinADomainBtnClicked(); + void nullPasswordsEnabled(bool); + void loadBtnClicked(); + void loadCanceled(const QString & msg); + void fillFields(); + void slotSpecifySmbConf(const QString &); + + private: + KcmInterface* _interface; + KJanusWidget* _janus; + SmbConfConfigWidget* m_smbConfConfigWidget; +}; + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/konqinterface.ui b/filesharing/advanced/kcm_sambaconf/konqinterface.ui new file mode 100644 index 00000000..e3a11b7e --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/konqinterface.ui @@ -0,0 +1,543 @@ + +KonqInterface + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +Jan Schäfer <janschaefer@users.sourceforge.net> + + + konqinterface + + + + 0 + 0 + 368 + 417 + + + + + unnamed + + + 0 + + + 6 + + + + btnGrp + + + 0 + + + + + + + unnamed + + + 5 + + + 5 + + + + notSharedRadio + + + Not share&d + + + false + + + 1 + + + + + sharedRadio + + + &Shared + + + 0 + + + + + + + baseGrp + + + + + + + Bas&e Options + + + + unnamed + + + 11 + + + 6 + + + + commentEdit + + + + + + + Comment + + + This is a text field that is seen next to a share when a client queries the server, either via the network neighborhood or via net view, to list what shares are available. + + + + + nameEdit + + + + + + + Name of the share + + + This is the name of the share + + + + + TextLabel1_2 + + + + + + + &Name: + + + PlainText + + + nameEdit + + + + + + This is the name of the share + + + + + TextLabel5_2 + + + + + + + Commen&t: + + + commentEdit + + + This is a text field that is seen next to a share when a client queries the server, either via the network neighborhood or via net view, to list what shares are available. + + + + + + + securityGrp + + + + + + + Security Options + + + + unnamed + + + 11 + + + 6 + + + + denyEdit + + + + + + + The opposite of hosts allow - hosts listed here are NOT permitted access to services unless the specific services have their own lists to override this one. Where the lists conflict, the allow list takes precedence. + + + + + allowEdit + + + + + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + TextLabel6 + + + + + + + Hosts den&y: + + + denyEdit + + + The opposite of hosts allow - hosts listed here are NOT permitted access to services unless the specific services have their own lists to override this one. Where the lists conflict, the allow list takes precedence. + + + + + TextLabel5 + + + + + + + &Hosts allow: + + + allowEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + TextLabel1 + + + + + + + Guest &account: + + + guestAccountCombo + + + This is a username which will be used for access to services which are specified as guest ok. Whatever privileges this user has will be available to any client connecting to the guest service. Typically this user will exist in the password file, but will not have a valid login. The user account \"ftp\" is often a good choice for this parameter. If a username is specified in a given service, the specified username overrides this one. + + + + + readOnlyChk + + + + + + + &Read only + + + If this is checked, then users of a service may not create or modify files in the service's directory. + + + + + guestOkChk + + + + + + + G&uests allowed + + + + + + If this is checked , then no password is required to connect to the service. Privileges will be those of the guest account. + + + + + guestAccountCombo + + + false + + + + + + + This is a username which will be used for access this directory if guests are allowed + + + + + + + otherGrp + + + + + + + Other Options + + + + unnamed + + + 11 + + + 6 + + + + browseableChk + + + + + + + Bro&wseable + + + This controls whether this share is seen in the list of available shares in a net view and in the browse list. + + + + + availableChk + + + + 1 + 0 + 10 + 0 + + + + + + + + A&vailable + + + Checkbox lets you \"turn off\" a service. If not checked, then ALL attempts to connect to the service will fail. Such failures are logged. + + + + + + + Layout10 + + + + unnamed + + + 0 + + + 6 + + + + Spacer9 + + + Horizontal + + + Expanding + + + + 20 + 0 + + + + + + moreOptionsBtn + + + More Opt&ions + + + + + + + Spacer8 + + + Vertical + + + Expanding + + + + 0 + 20 + + + + + + + + nameEdit + textChanged(const QString&) + konqinterface + changedSlot() + + + commentEdit + textChanged(const QString&) + konqinterface + changedSlot() + + + readOnlyChk + clicked() + konqinterface + changedSlot() + + + guestOkChk + clicked() + konqinterface + changedSlot() + + + allowEdit + textChanged(const QString&) + konqinterface + changedSlot() + + + denyEdit + textChanged(const QString&) + konqinterface + changedSlot() + + + browseableChk + clicked() + konqinterface + changedSlot() + + + availableChk + clicked() + konqinterface + changedSlot() + + + moreOptionsBtn + clicked() + konqinterface + moreOptionsPressed() + + + guestOkChk + toggled(bool) + guestAccountCombo + setEnabled(bool) + + + + notSharedRadio + sharedRadio + nameEdit + commentEdit + readOnlyChk + guestOkChk + guestAccountCombo + allowEdit + denyEdit + browseableChk + availableChk + moreOptionsBtn + + + changed() + + + changedSlot() + moreOptionsPressed() + + + diff --git a/filesharing/advanced/kcm_sambaconf/konqinterface.ui.h b/filesharing/advanced/kcm_sambaconf/konqinterface.ui.h new file mode 100644 index 00000000..195d9c72 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/konqinterface.ui.h @@ -0,0 +1,39 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename slots use Qt Designer which will +** update this file, preserving your code. Create an init() slot in place of +** a constructor, and a destroy() slot in place of a destructor. +*****************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +void KonqInterface::changedSlot() +{ + emit changed(); +} + +void KonqInterface::moreOptionsPressed() +{ + +} + + diff --git a/filesharing/advanced/kcm_sambaconf/linuxpermissionchecker.cpp b/filesharing/advanced/kcm_sambaconf/linuxpermissionchecker.cpp new file mode 100644 index 00000000..17b782ab --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/linuxpermissionchecker.cpp @@ -0,0 +1,202 @@ +/*************************************************************************** + begin : Tue May 17 2003 + copyright : (C) 2003 by Jan Sch�er + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ +#include +#include + +#include +#include +#include + +#include "passwd.h" +#include "sambashare.h" +#include "linuxpermissionchecker.h" + +LinuxPermissionChecker::LinuxPermissionChecker(SambaShare* share,QWidget* parent = 0L) +{ + m_sambaShare = share; + m_parent = parent; + + if (!share) { + kdWarning() << "WARNING: LinuxPermissionChecker: share is null !" << endl; + return; + } + + m_fi = QFileInfo(share->getValue("path")); + + if ( ! m_fi.exists()) { + kdDebug(5009) << "LinuxPermissionChecker: path does not exists !" << endl; + } + +} + + +LinuxPermissionChecker::~LinuxPermissionChecker() +{ +} + +bool LinuxPermissionChecker::checkAllPermissions() { + if (! m_sambaShare ) + return true; + + if ( ! m_fi.exists()) + return true; + + if (! checkPublicPermissions()) + return false; + + if (! checkAllUserPermissions()) + return false; + + return true; +} + +bool LinuxPermissionChecker::checkAllUserPermissions() { + if (! m_sambaShare ) + return true; + + if ( ! m_fi.exists()) + return true; + + QStringList readList = QStringList::split(QRegExp("[,\\s]+"),m_sambaShare->getValue("read list")); + + for ( QStringList::Iterator it = readList.begin(); it != readList.end(); ++it ) + { + if (!checkUserReadPermissions(*it)) + return false; + } + + QStringList writeList = QStringList::split(QRegExp("[,\\s]+"),m_sambaShare->getValue("write list")); + + for ( QStringList::Iterator it = writeList.begin(); it != writeList.end(); ++it ) + { + if (!checkUserWritePermissions(*it)) + return false; + } + + return true; +} + +bool LinuxPermissionChecker::checkPublicPermissions() { + if (! m_sambaShare ) + return true; + + bool isPublic = m_sambaShare->getBoolValue("public"); + if (!isPublic) + return true; + + QString guestAccount = m_sambaShare->getValue("guest account"); + + if ( ! checkUserReadPermissions(guestAccount,false)) + { + if (KMessageBox::Cancel == KMessageBox::warningContinueCancel( + 0L,i18n( + "You have specified public read access for this directory, but " + "the guest account %1 does not have the necessary read permissions;
" + "do you want to continue anyway?
").arg(guestAccount) + ,i18n("Warning") + ,KStdGuiItem::cont() + ,"KSambaPlugin_guestAccountHasNoReadPermissionsWarning")) + return false; + } + + + if ( ! checkUserWritePermissions(guestAccount,false)) + { + if (KMessageBox::Cancel == KMessageBox::warningContinueCancel( + 0L,i18n( + "You have specified public write access for this directory, but " + "the guest account %1 does not have the necessary write permissions;
" + "do you want to continue anyway?
").arg(guestAccount) + ,i18n("Warning") + ,KStdGuiItem::cont() + ,"KSambaPlugin_guestAccountHasNoWritePermissionsWarning")) + return false; + } + + return true; +} + +bool LinuxPermissionChecker::checkUserPermissions(const QString & user) { + if ( ! checkUserReadPermissions(user)) + return false; + + if ( ! checkUserWritePermissions(user)) + return false; + + return true; +} + +bool LinuxPermissionChecker::checkUserWritePermissions(const QString & user, bool showMessageBox) { + // If no write permissions are given, we don't need to check them. + if (m_sambaShare->getBoolValue("read only")) + return true; + + if (! ((m_fi.permission(QFileInfo::WriteOther)) || + (m_fi.permission(QFileInfo::WriteUser) && user == m_fi.owner()) || + (m_fi.permission(QFileInfo::WriteGroup) && isUserInGroup(user, m_fi.group()))) + ) + { + if (!showMessageBox) + return false; + + if (KMessageBox::Cancel == KMessageBox::warningContinueCancel( + 0L,i18n( + "You have specified write access to the user %1 for this directory, but " + "the user does not have the necessary write permissions;
" + "do you want to continue anyway?
").arg(user) + ,i18n("Warning") + ,KStdGuiItem::cont() + ,"KSambaPlugin_userHasNoWritePermissionsWarning")) + return false; + } + + return true; +} + +bool LinuxPermissionChecker::checkUserReadPermissions(const QString & user, bool showMessageBox) { + if (! ((m_fi.permission(QFileInfo::ReadOther)) || + (m_fi.permission(QFileInfo::ReadUser) && user == m_fi.owner()) || + (m_fi.permission(QFileInfo::ReadGroup) && isUserInGroup(user, m_fi.group()))) + ) + { + if (!showMessageBox) + return false; + + if (KMessageBox::Cancel == KMessageBox::warningContinueCancel( + 0L,i18n( + "You have specified read access to the user %1 for this directory, but " + "the user does not have the necessary read permissions;
" + "do you want to continue anyway?
").arg(user) + ,i18n("Warning") + ,KStdGuiItem::cont() + ,"KSambaPlugin_userHasNoReadPermissionsWarning")) + return false; + + } + + return true; +} + diff --git a/filesharing/advanced/kcm_sambaconf/linuxpermissionchecker.h b/filesharing/advanced/kcm_sambaconf/linuxpermissionchecker.h new file mode 100644 index 00000000..f18494b1 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/linuxpermissionchecker.h @@ -0,0 +1,103 @@ +/*************************************************************************** + begin : Tue May 17 2003 + copyright : (C) 2003 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ +#ifndef LINUXPERMISSIONCHECKER_H +#define LINUXPERMISSIONCHECKER_H + +#include + +class SambaShare; +class QWidget; + +/** + * A class to check if the Samba permissions specified in the SambaShare fit + * to the un?x permissions of the directory. E.g. a user specifies a SambaShare as writeable + * for the public, but the guest account has no write permissions for the directory of the + * SambaShare. This will cause a dialog (which can be turned of) to popup and inform the + * user about the missing rights and ask him to continue nevertheless or to cancel. + * This class works for all un?x systems not only for Linux, but I couldn't call the + * class Un?xPermissionChecker ;-) + * @author Jan Schaefer + */ +class LinuxPermissionChecker{ +public: + LinuxPermissionChecker(SambaShare*,QWidget* parent ); + ~LinuxPermissionChecker(); + + /** + * Checks all possible errors that the user could made + * @returns
    + *
  • false if the un?x permissions of the directory doesn't fit + * to the specified permissions in the Samba share and the user has pushed + * the cancel button of one of the dialogs, that informed her. + *
  • true if either there haven't been any problems with the un?x + * permissions, or there have been problems, but the user pressed always the continue button. + */ + bool checkAllPermissions(); + + /** + * Checks only the public permissions of the directory. First checks + * if the Samba share is specified public and then checks if the guest account + * has the appropriate rights. + * @return @see #check + */ + bool checkPublicPermissions(); + + /** + * Checks all permissions of all users specified in + * the Samba share + * @return @see #check + */ + bool checkAllUserPermissions(); + +protected: + + + /** + * Checks all permissions of the given un?x user + * @return @see #check + */ + bool checkUserPermissions(const QString & user); + + /** + * Checks the write permissions of the given un?x user + * Does not show a dialog if showMessageBox is false + * @return @see #check + */ + bool checkUserWritePermissions(const QString & user,bool showMessageBox = true); + + /** + * Checks the read permissions of the given un?x user + * Does not show a dialog if showMessageBox is false + * @return @see #check + */ + bool checkUserReadPermissions(const QString & user,bool showMessageBox = true); + + QWidget* m_parent; + SambaShare* m_sambaShare; + QFileInfo m_fi; +}; + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/passwd.cpp b/filesharing/advanced/kcm_sambaconf/passwd.cpp new file mode 100644 index 00000000..532e42db --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/passwd.cpp @@ -0,0 +1,164 @@ +/*************************************************************************** + passwd.cpp - description + ------------------- + begin : Tue June 6 2002 + copyright : (C) 2002 by Jan Schaefer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + + +#include +#include +#include +#include + +#include "passwd.h" + +UnixUserList getUnixUserList() +{ + UnixUserList list; + + struct passwd* p; + + while ((p = getpwent())) + { + if (!p) continue; + + UnixUser *u = new UnixUser(); + u->name = p->pw_name; + u->uid = p->pw_uid; + list.append(u); + } + + endpwent(); + + list.sort(); + + return list; +} + +QStringList getUnixUsers() +{ + QStringList list; + + struct passwd* p; + + while ((p = getpwent())) + { + if (p) + list.append(QString(p->pw_name)); + } + + endpwent(); + + list.sort(); + + return list; +} + +QStringList getUnixGroups() +{ + QStringList list; + + struct group* g; + + while ((g = getgrent())) + { + if (g) + list.append(QString(g->gr_name)); + } + + endgrent(); + + list.sort(); + + return list; +} + +int getUserUID(const QString & name) +{ + if (name.isNull()) return -1; + + struct passwd* p; + + p = getpwnam(name.local8Bit()); + + if (p) + return p->pw_uid; + + return -1; +} + +int getUserGID(const QString & name) +{ + if (name.isNull()) return -1; + + struct passwd* p; + + p = getpwnam(name.local8Bit()); + + if (p) + return p->pw_gid; + + return -1; +} + +int getGroupGID(const QString & name) +{ + if (name.isNull()) return -1; + + struct group* g; + + g = getgrnam(name.local8Bit()); + + if (g) + return g->gr_gid; + + return -1; +} + +bool isUserInGroup(const QString & user, const QString & group) { + struct group* g; + + while ((g = getgrent())) + { + if (g && QString(g->gr_name) == group) { + char** names = g->gr_mem; + + int i = 0; + char* name = names[0]; + while (name != 0L) { + i++; + if (QString(name) == user) { + endgrent(); + return true; + } + name = names[i]; + } + break; + } + } + + endgrent(); + return false; +} diff --git a/filesharing/advanced/kcm_sambaconf/passwd.h b/filesharing/advanced/kcm_sambaconf/passwd.h new file mode 100644 index 00000000..490e0381 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/passwd.h @@ -0,0 +1,56 @@ +/*************************************************************************** + passwd.h - description + ------------------- + begin : Tue June 6 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#ifndef PASSWD_H +#define PASSWD_H + +#include +#include +#include + + + +class UnixUser +{ +public: + QString name; + int uid; +}; + +typedef QPtrList UnixUserList; + +UnixUserList getUnixUserList(); +QStringList getUnixUsers(); +QStringList getUnixGroups(); +int getUserUID(const QString &); +int getUserGID(const QString &); +int getGroupGID(const QString &); +bool isUserInGroup(const QString &, const QString &); + + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/printerdlgimpl.cpp b/filesharing/advanced/kcm_sambaconf/printerdlgimpl.cpp new file mode 100644 index 00000000..2910a1a7 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/printerdlgimpl.cpp @@ -0,0 +1,241 @@ +/*************************************************************************** + printerdlgimpl.cpp - description + ------------------- + begin : Tue June 6 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + + +/** + * @author Jan Schäfer + **/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "sambafile.h" +#include "printerdlgimpl.h" +#include "usertabimpl.h" +#include "passwd.h" +#include "dictmanager.h" + + +PrinterDlgImpl::PrinterDlgImpl(QWidget* parent, SambaShare* share) + : KcmPrinterDlg(parent,"sharedlgimpl") +{ + if (!share) { + kdWarning() << "PrinterDlgImpl::Constructor : share parameter is null!" << endl; + return; + } + + _share = share; + _dictMngr = new DictManager(share); + + initDialog(); +} + +void PrinterDlgImpl::initDialog() +{ + // Base settings + if (!_share) + return; + + QPtrList *printerList = KMManager::self()->printerListComplete(); + + for (QPtrListIterator it(*printerList); it.current(); ++it) + { + if (!it.current()->isSpecial()){ + queueCombo->insertItem(it.current()->printerName()); + } + } + + setComboToString(queueCombo,_share->getValue("printer name")); + + + _dictMngr->add("path",pathUrlRq); + printersChk->setChecked( _share->getName() == "printers" ); + + shareNameEdit->setText( _share->getName() ); + _dictMngr->add("comment",commentEdit); + + _dictMngr->add("available",availableBaseChk); + _dictMngr->add("browseable",browseableBaseChk); + _dictMngr->add("public",publicBaseChk); + + // Users + + _userTab = new UserTabImpl(this,_share); + _tabs->insertTab(_userTab,i18n("&Users"),1); + _userTab->load(); + connect(_userTab, SIGNAL(changed()), this, SLOT(changedSlot())); + + + // Printing + + _dictMngr->add("postscript",postscriptChk); + _dictMngr->add("use client driver",useClientDriverChk); + _dictMngr->add("default devmode",defaultDevmodeChk); + + _dictMngr->add("max print jobs",maxPrintJobsSpin); + _dictMngr->add("max reported print jobs",maxReportedPrintJobsSpin); + _dictMngr->add("printing",printingCombo, + new QStringList(QStringList() + << "sysv" << "aix" << "hpux" << "bsd" << "qnx" + << "plp" << "lprng" << "softq" << "cups" << "nt" << "os2")); + + _dictMngr->add("printer driver",printerDriverEdit); + _dictMngr->add("printer driver location",printerDriverLocationEdit); + + // Commands + + _dictMngr->add("print command",printCommandEdit); + _dictMngr->add("lpq command",lpqCommandEdit); + _dictMngr->add("lprm command",lprmCommandEdit); + _dictMngr->add("lppause",lppauseEdit); + _dictMngr->add("lpresume",lpresumeEdit); + _dictMngr->add("queuepause",queuepauseEdit); + _dictMngr->add("queueresume",queueresumeEdit); + + // Security + + _dictMngr->add("printer admin",printerAdminEdit); + _dictMngr->add("hosts allow",hostsAllowEdit); + _dictMngr->add("hosts deny",hostsDenyEdit); + + guestAccountCombo->insertStringList( getUnixUsers() ); + setComboToString(guestAccountCombo,_share->getValue("guest account")); + + // Advanced + + _dictMngr->add("min print space",minPrintSpaceSpin); + _dictMngr->add("status",statusChk); + _dictMngr->add("preexec",preExecEdit); + _dictMngr->add("postexec",postExecEdit); + _dictMngr->add("root preexec",rootPreExecEdit); + _dictMngr->add("root postexec",rootPostExecEdit); + + _dictMngr->load( _share ); + connect(_dictMngr, SIGNAL(changed()), this, SLOT(changedSlot())); + +} + +void PrinterDlgImpl::accept() +{ + if (!_share) + return; + + // Security + + _share->setValue("guest account",guestAccountCombo->currentText( ) ); + _share->setValue("printer name",queueCombo->currentText()); + + if (printersChk->isChecked()) + { + _share->setName("printers"); + } + else + _share->setName(shareNameEdit->text()); + + + _userTab->save(); + _dictMngr->save( _share ); + + + KcmPrinterDlg::accept(); +} + +PrinterDlgImpl::~PrinterDlgImpl() +{ +} + +void PrinterDlgImpl::printersChkToggled(bool b) +{ + if (b) + { + shareNameEdit->setText("printers"); + shareNameEdit->setEnabled(false); + + int dist = 10; + int w = 64 + dist; + int h = 64 + 2*dist; + + QPixmap pix(w,h); + pix.fill(); // fill with white + + QPixmap pix2 = DesktopIcon("printer1"); + + // Draw the printericon three times + QPainter p(&pix); + p.drawPixmap(dist+dist/2,0,pix2); + p.drawPixmap(dist/2,dist,pix2); + p.drawPixmap(dist+dist/2,2*dist,pix2); + p.end(); + + QBitmap mask(w,h); + + mask.fill(Qt::black); // everything is transparent + + p.begin(&mask); + + p.setRasterOp(Qt::OrROP); + p.drawPixmap(dist+dist/2,0,*pix2.mask()); + p.drawPixmap(dist/2,dist,*pix2.mask()); + p.drawPixmap(dist+dist/2,2*dist,*pix2.mask()); + p.end(); + + pix.setMask(mask); + + printerPixLbl->setPixmap(pix); + pixFrame->layout()->setMargin( 2 ); + } + else + { + shareNameEdit->setEnabled(true); + shareNameEdit->setText( _share->getName() ); + printerPixLbl->setPixmap(DesktopIcon("printer1")); + pixFrame->layout()->setMargin( 11 ); + } +} + +#include "printerdlgimpl.moc" diff --git a/filesharing/advanced/kcm_sambaconf/printerdlgimpl.h b/filesharing/advanced/kcm_sambaconf/printerdlgimpl.h new file mode 100644 index 00000000..d7488985 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/printerdlgimpl.h @@ -0,0 +1,78 @@ +/*************************************************************************** + printerdlgimpl.h - description + ------------------- + begin : Tue June 6 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#ifndef PRINTERDLGIMPL_H +#define PRINTERDLGIMPL_H + + +/** + * @author Jan Schäfer + **/ + +#include "kcmprinterdlg.h" + +class SambaShare; +class QWidget; +class UserTabImpl; +class DictManager; + +/** + * This class implements the tcmprinterdlg.ui interface + **/ +class PrinterDlgImpl : public KcmPrinterDlg +{ +Q_OBJECT + +public : + + PrinterDlgImpl(QWidget* parent, SambaShare* share); + ~PrinterDlgImpl(); + +protected : + + /** + * Fills all dialog fields with the values + * of the SambaShare object + **/ + void initDialog(); + + /** + * The share object to change with this dialog + **/ + SambaShare* _share; + + UserTabImpl* _userTab; + DictManager* _dictMngr; + +protected slots: + void accept(); + virtual void printersChkToggled(bool); +}; + + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/programmingconventions.txt b/filesharing/advanced/kcm_sambaconf/programmingconventions.txt new file mode 100644 index 00000000..b9019e74 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/programmingconventions.txt @@ -0,0 +1,10 @@ +Programming conventions : + +Class member variable : starting with _ e.g. _name + +QCheckBox variable : ending to Chk e.g. nameChk +QLineEdit variable : ending to Edit e.g. nameEdit +KUrlRequerster variable : ending to UrlRq e.g. pathUrlRq +QPushButton : ending to Btn e.g. addBtn + +QListView: ending to ListView e.g. sharesListView diff --git a/filesharing/advanced/kcm_sambaconf/qmultichecklistitem.cpp b/filesharing/advanced/kcm_sambaconf/qmultichecklistitem.cpp new file mode 100644 index 00000000..44383f6d --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/qmultichecklistitem.cpp @@ -0,0 +1,159 @@ +/*************************************************************************** + qmultichecklistitem.cpp - description + ------------------- + begin : Sun Jan 26 2003 + copyright : (C) 2003 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#include +#include +#include +#include + +#include +#include + +#include "qmultichecklistitem.moc" +#include "qmultichecklistitem.h" + +static const int BoxSize = 16; + + +QMultiCheckListItem::QMultiCheckListItem( QListView *parent=0) : + QListViewItem(parent) { +} + +void QMultiCheckListItem::setOn(int column, bool b) { + if (column >= (int) checkBoxColumns.size()) { + checkBoxColumns.resize(column*2); + checkStates.resize(column*2); + } + + checkStates.setBit(column,b); + checkBoxColumns.setBit(column); + kdDebug(5009) << "setOn : " << column << endl; + repaint(); +} + +bool QMultiCheckListItem::isOn(int column) { + return checkStates.testBit(column); +} + +bool QMultiCheckListItem::isDisabled(int column) { + return disableStates.testBit(column); +} + +void QMultiCheckListItem::toggle(int column) { + if (column >= (int) checkBoxColumns.size()) { + checkBoxColumns.resize(column*2); + checkStates.resize(column*2); + } + + checkBoxColumns.setBit(column); + checkStates.toggleBit(column); + emit stateChanged(column,checkStates.testBit(column)); + + repaint(); +} + +void QMultiCheckListItem::setDisabled(int column, bool b) { + if (column >= (int) disableStates.size()) { + disableStates.resize(column*2); + } + + disableStates.setBit(column,b); +// KMessageBox::information(0L,QString("setDisabled"),QString("disable %1 ").arg(column)); + repaint(); +} + +void QMultiCheckListItem::paintCell(QPainter *p,const QColorGroup & cg, int col, int width, int align) +{ + + if ( !p ) + return; + + QListView *lv = listView(); + if ( !lv ) + return; + + QListViewItem::paintCell(p,cg,col,width,align ); + + int marg = lv->itemMargin(); +// int width = BoxSize + marg*2; + // use a provate color group and set the text/highlighted text colors + QColorGroup mcg = cg; + + if (checkBoxColumns.testBit(col)) { + // Bold/Italic/use default checkboxes + // code allmost identical to QCheckListItem + Q_ASSERT( lv ); //### + // I use the text color of defaultStyles[0], normalcol in parent listview +// mcg.setColor( QColorGroup::Text, ((StyleListView*)lv)->normalcol ); + int x = 0; + if ( align == AlignCenter ) { + QFontMetrics fm( lv->font() ); + x = (width - BoxSize - fm.width(text(0)))/2; + } + int y = (height() - BoxSize) / 2; + + if ( !isEnabled() || disableStates.testBit(col)) + p->setPen( QPen( lv->palette().color( QPalette::Disabled, QColorGroup::Text ), 2 ) ); + else + p->setPen( QPen( mcg.text(), 2 ) ); + + if ( isSelected() && lv->header()->mapToSection( 0 ) != 0 ) { + p->fillRect( 0, 0, x + marg + BoxSize + 4, height(), + mcg.brush( QColorGroup::Highlight ) ); + if ( isEnabled() ) + p->setPen( QPen( mcg.highlightedText(), 2 ) ); // FIXME! - use defaultstyles[0].selecol. luckily not used :) + } + p->drawRect( x+marg, y+2, BoxSize-4, BoxSize-4 ); + x++; + y++; + if ( checkStates.testBit(col) ) { + QPointArray a( 7*2 ); + int i, xx, yy; + xx = x+1+marg; + yy = y+5; + for ( i=0; i<3; i++ ) { + a.setPoint( 2*i, xx, yy ); + a.setPoint( 2*i+1, xx, yy+2 ); + xx++; yy++; + } + yy -= 2; + for ( i=3; i<7; i++ ) { + a.setPoint( 2*i, xx, yy ); + a.setPoint( 2*i+1, xx, yy+2 ); + xx++; yy--; + } + p->drawLineSegments( a ); + } + + } + + +} + + + diff --git a/filesharing/advanced/kcm_sambaconf/qmultichecklistitem.h b/filesharing/advanced/kcm_sambaconf/qmultichecklistitem.h new file mode 100644 index 00000000..0f0d2410 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/qmultichecklistitem.h @@ -0,0 +1,64 @@ +/*************************************************************************** + qextendedchecklistitem.h - description + ------------------- + begin : Sun Jan 26 2003 + copyright : (C) 2003 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#ifndef _QMULTICHECKLISTITEM_H_ +#define _QMULTICHECKLISTITEM_H_ + +#include + +#include + +class QMultiCheckListItem : public QObject, public QListViewItem { +Q_OBJECT + + public: + QMultiCheckListItem( QListView *parent); + ~QMultiCheckListItem() {}; + + virtual bool isOn(int column); + virtual bool isDisabled(int column); + + + protected: + /* reimp */ + virtual void paintCell(QPainter *, const QColorGroup &, int, int, int); + + public slots: + virtual void setOn(int, bool); + virtual void toggle(int); + virtual void setDisabled(int, bool); + + signals: + void stateChanged(int, bool); + private: + QBitArray checkStates; + QBitArray checkBoxColumns; + QBitArray disableStates; +}; + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/sambafile.cpp b/filesharing/advanced/kcm_sambaconf/sambafile.cpp new file mode 100644 index 00000000..f1f48263 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/sambafile.cpp @@ -0,0 +1,701 @@ +/* + Copyright (c) 2002-2004 Jan Schaefer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "sambafile.h" + +#define FILESHARE_DEBUG 5009 + +SambaConfigFile::SambaConfigFile(SambaFile* sambaFile) +{ + QDict(10,false); + setAutoDelete(true); + _sambaFile = sambaFile; +} + +QString SambaConfigFile::getDefaultValue(const QString & name) +{ + SambaShare* defaults = _sambaFile->getTestParmValues(); + QString s = defaults->getValue(name,false,false); + + return s; +} + +SambaShare* SambaConfigFile::addShare(const QString & name) +{ + SambaShare* newShare = new SambaShare(name,this); + addShare(name,newShare); + return newShare; +} + + +void SambaConfigFile::addShare(const QString & name, SambaShare* share) +{ + insert(name,share), + _shareList.append(name); +} + +void SambaConfigFile::removeShare(const QString & name) +{ + remove(name); + _shareList.remove(name); +} + + +QStringList SambaConfigFile::getShareList() +{ + return _shareList; +} + +SambaFile::SambaFile(const QString & _path, bool _readonly) + : readonly(_readonly), + changed(false), + path(_path), + localPath(_path), + _sambaConfig(0), + _testParmValues(0), + _sambaVersion(-1), + _tempFile(0) +{ +} + +SambaFile::~SambaFile() +{ + delete _sambaConfig; + delete _testParmValues; + delete _tempFile; + +} + +bool SambaFile::isRemoteFile() { + return ! KURL(path).isLocalFile(); +} + +/** No descriptions */ +QString SambaFile::findShareByPath(const QString & path) const +{ + QDictIterator it(*_sambaConfig); + KURL url(path); + url.adjustPath(-1); + + for ( ; it.current(); ++it ) + { + SambaShare* share = it.current(); + + QString *s = share->find("path"); + if (s) { + KURL curUrl(*s); + curUrl.adjustPath(-1); + + kdDebug(5009) << url.path() << " =? " << curUrl.path() << endl; + + if (url.path() == curUrl.path()) + return it.currentKey(); + } + } + + return QString::null; +} + +bool SambaFile::save() { + return slotApply(); +} + + +bool SambaFile::slotApply() +{ + if (readonly) { + kdDebug(FILESHARE_DEBUG) << "SambaFile::slotApply: readonly=true" << endl; + return false; + } + + // If we have write access to the smb.conf + // we simply save the values to it + // if not we have to save the results in + // a temporary file and copy it afterwards + // over the smb.conf file with kdesu. + if (QFileInfo(path).isWritable()) + { + saveTo(path); + changed = false; + return true; + } + + // Create a temporary smb.conf file + delete _tempFile; + _tempFile = new KTempFile(); + _tempFile->setAutoDelete(true); + + if (!saveTo(_tempFile->name())) { + kdDebug(5009) << "SambaFile::slotApply: Could not save to temporary file" << endl; + delete _tempFile; + _tempFile = 0; + return false; + } + + QFileInfo fi(path); + KURL url(path); + + if (KURL(path).isLocalFile()) { + KProcess proc; + kdDebug(5009) << "SambaFile::slotApply: is local file!" << endl; + + QString suCommand=QString("cp %1 %2; rm %3") + .arg(_tempFile->name()) + .arg(path) + .arg(_tempFile->name()); + proc << "kdesu" << "-d" << suCommand; + + if (! proc.start(KProcess::Block)) { + kdDebug(5009) << "SambaFile::slotApply: saving to " << path << " failed!" << endl; + //KMessageBox::sorry(0,i18n("Saving the results to %1 failed.").arg(path)); + delete _tempFile; + _tempFile = 0; + return false; + } + else { + changed = false; + delete _tempFile; + _tempFile = 0; + kdDebug(5009) << "SambaFile::slotApply: changes successfully saved!" << endl; + return true; + } + } else { + kdDebug(5009) << "SambaFile::slotApply: is remote file!" << endl; + _tempFile->setAutoDelete(true); + KURL srcURL; + srcURL.setPath( _tempFile->name() ); + + KIO::FileCopyJob * job = KIO::file_copy( srcURL, url, -1, true ); + connect( job, SIGNAL( result( KIO::Job * ) ), + this, SLOT( slotSaveJobFinished ( KIO::Job * ) ) ); + return (job->error()==0); + } + + return true; +} + + /** + * Returns a name which isn't already used for a share + **/ +QString SambaFile::getUnusedName(const QString alreadyUsedName) const +{ + + QString init = i18n("Unnamed"); + if (alreadyUsedName != QString::null) + init = alreadyUsedName; + + QString s = init; + + int i = 2; + + while (_sambaConfig->find(s)) + { + s = init+QString::number(i); + i++; + } + + return s; +} + + + +SambaShare* SambaFile::newShare(const QString & name) +{ + if (_sambaConfig->find(name)) + return 0L; + + SambaShare* share = new SambaShare(name,_sambaConfig); + _sambaConfig->addShare(name,share); + + changed = true; + + return share; + +} + +SambaShare* SambaFile::newShare(const QString & name, const QString & path) +{ + SambaShare* share = newShare(name); + if (share) + { + share->setValue("path",path); + } + + return share; +} + +SambaShare* SambaFile::newPrinter(const QString & name, const QString & printer) +{ + SambaShare* share = newShare(name); + + if (share) + { + share->setValue("printable",true); + share->setValue("printer name",printer); + } + + return share; +} + + +/** No descriptions */ +void SambaFile::removeShare(const QString & share) +{ + changed = true; + + _sambaConfig->removeShare(share); +} + +void SambaFile::removeShare(SambaShare* share) +{ + removeShare(share->getName()); +} + +void SambaFile::removeShareByPath(const QString & path) { + QString share = findShareByPath(path); + removeShare(share); +} + +/** No descriptions */ +SambaShare* SambaFile::getShare(const QString & share) const +{ + SambaShare *s = _sambaConfig->find(share); + + return s; +} + +/** +* Returns a list of all shared directories +**/ +SambaShareList* SambaFile::getSharedDirs() const +{ + SambaShareList* list = new SambaShareList(); + + QDictIterator it(*_sambaConfig); + + for( ; it.current(); ++it ) + { + if (!it.current()->isPrinter() && + it.current()->getName() != "global") + { + list->append(it.current()); + } + } + + return list; +} + +/** +* Returns a list of all shared printers +**/ +SambaShareList* SambaFile::getSharedPrinters() const +{ + SambaShareList* list = new SambaShareList(); + + QDictIterator it(*_sambaConfig); + + for( ; it.current(); ++it ) + { + if (it.current()->isPrinter()) + list->append(it.current()); + } + + return list; +} + +int SambaFile::getSambaVersion() { + if (_sambaVersion > -1) + return _sambaVersion; + + KProcess testParam; + testParam << "testparm"; + testParam << "-V"; + _parmOutput = QString(""); + _sambaVersion = 2; + + connect( &testParam, SIGNAL(receivedStdout(KProcess*,char*,int)), + this, SLOT(testParmStdOutReceived(KProcess*,char*,int))); + + + + if (testParam.start(KProcess::Block,KProcess::Stdout)) { + if (_parmOutput.find("3") > -1) + _sambaVersion = 3; + } + + kdDebug(5009) << "Samba version = " << _sambaVersion << endl; + + return _sambaVersion; +} + + +SambaShare* SambaFile::getTestParmValues(bool reload) +{ + if (_testParmValues && !reload) + return _testParmValues; + + + KProcess testParam; + testParam << "testparm"; + testParam << "-s"; + + if (getSambaVersion() == 3) + testParam << "-v"; + + + testParam << "/dev/null"; + _parmOutput = QString(""); + + connect( &testParam, SIGNAL(receivedStdout(KProcess*,char*,int)), + this, SLOT(testParmStdOutReceived(KProcess*,char*,int))); + + if (testParam.start(KProcess::Block,KProcess::Stdout)) + { + parseParmStdOutput(); + } else + _testParmValues = new SambaShare(_sambaConfig); + + return _testParmValues; +} + +void SambaFile::testParmStdOutReceived(KProcess *, char *buffer, int buflen) +{ + _parmOutput+=QString::fromLatin1(buffer,buflen); +} + +void SambaFile::parseParmStdOutput() +{ + + QTextIStream s(&_parmOutput); + + if (_testParmValues) + delete _testParmValues; + _testParmValues = new SambaShare(_sambaConfig); + + QString section=""; + + while (!s.atEnd()) + { + QString line = s.readLine().stripWhiteSpace(); + + // empty lines + if (line.isEmpty()) + continue; + + // comments + if ('#' == line[0]) + continue; + + // sections + if ('[' == line[0]) + { + // get the name of the section + section = line.mid(1,line.length()-2).lower(); + continue; + } + + // we are only interested in the global section + if (section != KGlobal::staticQString("global")) + continue; + + // parameter + // parameter + int i = line.find('='); + + if (i>-1) { + QString name = line.left(i).stripWhiteSpace(); + QString value = line.mid(i+1).stripWhiteSpace(); + _testParmValues->setValue(name,value,false,false); + } + + } + + + +} + +/** +* Try to find the samba config file position +* First tries the config file, then checks +* several common positions +* If nothing is found returns QString::null +**/ +QString SambaFile::findSambaConf() +{ + return KSambaShare::instance()->smbConfPath(); +} + +void SambaFile::slotSaveJobFinished( KIO::Job * job ) { + delete _tempFile; + _tempFile = 0; +} + +void SambaFile::slotJobFinished( KIO::Job * job ) +{ + if (job->error()) + emit canceled( job->errorString() ); + else + { + openFile(); + emit completed(); + } +} + +bool SambaFile::load() +{ + if (path.isNull() || path.isEmpty()) + return false; + + kdDebug(FILESHARE_DEBUG) << "SambaFile::load: path=" << path << endl; + KURL url(path); + + if (!url.isLocalFile()) { + KTempFile tempFile; + localPath = tempFile.name(); + KURL destURL; + destURL.setPath( localPath ); + KIO::FileCopyJob * job = KIO::file_copy( url, destURL, 0600, true, false, true ); +// emit started( d->m_job ); + connect( job, SIGNAL( result( KIO::Job * ) ), this, SLOT( slotJobFinished ( KIO::Job * ) ) ); + return true; + } else { + localPath = path; + bool ret = openFile(); + if (ret) + emit completed(); + return ret; + } +} + +bool SambaFile::openFile() { + + QFile f(localPath); + + if (!f.open(IO_ReadOnly)) { + //throw SambaFileLoadException(QString("Could not open file %1 for reading.").arg(path)); + return false; + } + + QTextStream s(&f); + + delete _sambaConfig; + + _sambaConfig = new SambaConfigFile(this); + + SambaShare *currentShare = 0L; + bool continuedLine = false; // is true if the line before ended with a backslash + QString completeLine; + QStringList comments; + + while (!s.eof()) + { + QString currentLine = s.readLine().stripWhiteSpace(); + + if (continuedLine) + { + completeLine += currentLine; + continuedLine = false; + } else + completeLine = currentLine; + + // is the line continued in the next line ? + if ( completeLine[completeLine.length()-1] == '\\' ) + { + continuedLine = true; + // remove the ending backslash + completeLine.truncate( completeLine.length()-1 ); + continue; + } + + // comments or empty lines + if (completeLine.isEmpty() || + '#' == completeLine[0] || + ';' == completeLine[0]) + { + comments.append(completeLine); + continue; + } + + + // sections + if ('[' == completeLine[0]) + { + // get the name of the section + QString section = completeLine.mid(1,completeLine.length()-2); + currentShare = _sambaConfig->addShare(section); + currentShare->setComments(comments); + comments.clear(); + + continue; + } + + // parameter + int i = completeLine.find('='); + + if (i>-1) + { + QString name = completeLine.left(i).stripWhiteSpace(); + QString value = completeLine.mid(i+1).stripWhiteSpace(); + + if (currentShare) + { + currentShare->setComments(name,comments); + currentShare->setValue(name,value,true,true); + + comments.clear(); + } + } + } + + f.close(); + + // Make sure there is a global share + if (!getShare("global")) { + _sambaConfig->addShare("global"); + } + + return true; +} + +bool SambaFile::saveTo(const QString & path) +{ + QFile f(path); + + if (!f.open(IO_WriteOnly)) + return false; + + QTextStream s(&f); + + QStringList shareList = _sambaConfig->getShareList(); + + for ( QStringList::Iterator it = shareList.begin(); it != shareList.end(); ++it ) + { + SambaShare* share = _sambaConfig->find(*it); + + // First add all comments of the share to the file + QStringList comments = share->getComments(); + for ( QStringList::Iterator cmtIt = comments.begin(); cmtIt != comments.end(); ++cmtIt ) + { + s << *cmtIt << endl; + + kdDebug(5009) << *cmtIt << endl; + } + + // If there are no lines before the section add + // a blank line + if (comments.isEmpty()) + s << endl; + + // Add the name of the share / section + s << "[" << share->getName() << "]" << endl; + + // Add all options of the share + QStringList optionList = share->getOptionList(); + + for ( QStringList::Iterator optionIt = optionList.begin(); optionIt != optionList.end(); ++optionIt ) + { + + // Add the comments of the option + comments = share->getComments(*optionIt); + for ( QStringList::Iterator cmtIt = comments.begin(); cmtIt != comments.end(); ++cmtIt ) + { + s << *cmtIt << endl; + } + + // Add the option + s << *optionIt << " = " << *share->find(*optionIt) << endl; + } + + + } + + f.close(); + + return true; +} + + +SambaConfigFile* SambaFile::getSambaConfigFile(KSimpleConfig* config) +{ + QStringList groups = config->groupList(); + + SambaConfigFile* samba = new SambaConfigFile(this); + + for ( QStringList::Iterator it = groups.begin(); it != groups.end(); ++it ) + { + QMap entries = config->entryMap(*it); + + SambaShare *share = new SambaShare(*it,samba); + samba->insert(*it,share); + + for (QMap::Iterator it2 = entries.begin(); it2 != entries.end(); ++it2 ) + { + if (!it2.data().isEmpty()) + share->setValue(it2.key(),QString(it2.data()),false,false); + } + + } + + return samba; + +} + +KSimpleConfig* SambaFile::getSimpleConfig(SambaConfigFile* sambaConfig, const QString & path) +{ + KSimpleConfig *config = new KSimpleConfig(path,false); + + QDictIterator it(*sambaConfig); + + for ( ; it.current(); ++it ) + { + SambaShare* share = it.current(); + + config->setGroup(it.currentKey()); + + QDictIterator it2(*share); + + for (; it2.current(); ++it2 ) + { + config->writeEntry(it2.currentKey(), *it2.current()); + } + + } + + return config; +} + +#include "sambafile.moc" diff --git a/filesharing/advanced/kcm_sambaconf/sambafile.h b/filesharing/advanced/kcm_sambaconf/sambafile.h new file mode 100644 index 00000000..4eba9705 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/sambafile.h @@ -0,0 +1,178 @@ +/* + Copyright (c) 2002-2004 Jan Schaefer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef SAMBAFILE_H +#define SAMBAFILE_H + + +#include +#include +#include +#include + +#include "sambashare.h" + + +class KSimpleConfig; +class KProcess; +class KConfig; +class KTempFile; + +class SambaFile; + +class SambaFileLoadException +{ + public: + SambaFileLoadException(const QString & msg) { message = msg; }; + + QString getMessage() { return message; }; + protected: + QString message; +}; + +class SambaConfigFile : public QDict +{ +public: + SambaConfigFile(SambaFile*); + QString getDefaultValue(const QString & name); + QStringList getShareList(); + void addShare(const QString & name, SambaShare* share); + SambaShare* addShare(const QString & name); + void removeShare(const QString & name); +protected: + SambaFile* _sambaFile; + QStringList _shareList; +}; + +class SambaFile : public QObject +{ +Q_OBJECT +public: + SambaFile(const QString & _path, bool _readonly=true); + ~SambaFile(); + + /** No descriptions */ + QString findShareByPath(const QString & path) const; + void removeShareByPath(const QString & path); + + + SambaShare* newShare(const QString & name); + SambaShare* newShare(const QString & name, const QString & path); + SambaShare* newPrinter(const QString & name, const QString & printer); + + void removeShare(const QString & share); + void removeShare(SambaShare* share); + + void renameShare(const QString & oldName, const QString & newName); + + SambaShare* getShare(const QString & share) const; + + /** + * Returns a list of all shared directories + **/ + SambaShareList* getSharedDirs() const; + + /** + * Returns a list of all shared printers + **/ + SambaShareList* getSharedPrinters() const; + + /** + * Returns a name which isn't already used for a share + * if the alreadyUsedName parameter is given, then + * a name based on this name is returned. + * E.g.: if public is already used, the method could return + * public2 + **/ + QString getUnusedName(const QString alreadyUsedName=QString::null) const; + + /** + * Returns all values of the global section + * which are returned by the testparam program + * if the values were already loaded then these + * values are returned except the reload parameter + * is true + **/ + SambaShare* getTestParmValues(bool reload=false); + + static QString findSambaConf(); + + + int getSambaVersion(); + + /** + * Load all data from the smb.conf file + * Can load a remote file + **/ + bool load(); + + bool save(); + + /** + * Reads the local smb.conf + */ + bool openFile(); + + bool isRemoteFile(); + + /** + * Save all data to the specified file + * if successful returns true otherwise false + **/ + bool saveTo(const QString & path); + +protected: + bool readonly; + bool changed; + QString path; + QString localPath; + SambaConfigFile *_sambaConfig; + SambaShare* _testParmValues; + QString _parmOutput; + int _sambaVersion; + + void parseParmStdOutput(); + SambaConfigFile* getSambaConfigFile(KSimpleConfig* config); + KSimpleConfig* getSimpleConfig(SambaConfigFile* sambaConfig, const QString & filename); + + + +private: + void copyConfigs(KConfig* first, KConfig* second); + KTempFile * _tempFile; +public slots: + + /** + * Saves all changes to the smb.conf file + * if the user is not allowed she'll be asked + * for a root password + **/ + bool slotApply(); +protected slots: + void testParmStdOutReceived(KProcess *proc, char *buffer, int buflen); + void slotJobFinished( KIO::Job *); + void slotSaveJobFinished( KIO::Job *); + +signals: + void canceled(const QString &); + void completed(); + +}; + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/sambashare.cpp b/filesharing/advanced/kcm_sambaconf/sambashare.cpp new file mode 100644 index 00000000..b30d7db7 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/sambashare.cpp @@ -0,0 +1,334 @@ +/*************************************************************************** + sambashare.cpp - description + ------------------- + begin : Mon Jun 12 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net +***************************************************************************/ + +/****************************************************************************** +* * +* This file is part of KSambaPlugin. * +* * +* KSambaPlugin 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. * +* * +* KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +******************************************************************************/ + +#include +#include + +#include "sambafile.h" +#include "sambashare.h" +#include "common.h" + +SambaShare::SambaShare(SambaConfigFile* sambaFile) + : QDict(10,false) +{ + _sambaFile = sambaFile; + setName("defaults"); + setAutoDelete(true); +} + +SambaShare::SambaShare(const QString & name, SambaConfigFile* sambaFile) + : QDict(10,false) +{ + _sambaFile = sambaFile; + setName(name); + setAutoDelete(true); +} + +const QString& SambaShare::getName() const +{ + return _name; +} + +bool SambaShare::setName(const QString & name, bool testWetherExists) +{ + + if ( testWetherExists && + _sambaFile->find(name) && + _sambaFile->find(name) != this) + return false; + + _name = name; +return true; +} + +bool SambaShare::optionSupported(const QString & name) +{ + QString defaultValue = _sambaFile->getDefaultValue(name); +// kdDebug(5009) << name << " = " << defaultValue << " null : " << defaultValue.isNull() << endl; + return ! defaultValue.isNull(); +} + +/** +* Returns the value of the given parameter +* if no value is set yet the default value +* will be returned. +**/ +QString SambaShare::getValue(const QString & name, bool globalValue, bool defaultValue) +{ + QString synonym = getSynonym(name); + + QString* str = find(synonym); + QString ret; + + if (str) { + ret = *str; + } + else + if (globalValue) + ret = getGlobalValue(synonym,defaultValue); + else + if (defaultValue) + ret = getDefaultValue(synonym); + + + if (name=="writable" || name=="write ok" || name=="writeable") + ret = textFromBool( ! boolFromText(ret) ); + + return ret; +} + +bool SambaShare::getBoolValue(const QString & name, bool globalValue, bool defaultValue) +{ + return boolFromText(getValue(name,globalValue,defaultValue)); +} + + +QString SambaShare::getGlobalValue(const QString & name, bool defaultValue) +{ + if (!_sambaFile) + return getValue(name,false,defaultValue); + + SambaShare* globals = _sambaFile->find("global"); + + QString s = globals->getValue(name,false,defaultValue); + + return s; +} + + +/** +* Returns the default synonym for the given parameter +* if no synonym exists the original parameter in lower +* case is returned +**/ +QString SambaShare::getSynonym(const QString & name) const +{ + QString lname = name.lower().stripWhiteSpace(); + + if (lname == "browsable") return "browseable"; + if (lname == "allow hosts") return "hosts allow"; + if (lname == "auto services") return "preload"; + if (lname == "casesignames") return "case sensitive"; + if (lname == "create mode") return "create mask"; + if (lname == "debuglevel") return "log level"; + if (lname == "default") return "default service"; + if (lname == "deny hosts") return "hosts deny"; + if (lname == "directory") return "path"; + if (lname == "directory mode") return "directory mask"; + if (lname == "exec") return "preexec"; + if (lname == "group") return "force group"; + if (lname == "lock dir") return "lock directory"; + if (lname == "min passwd length") return "min password length"; + if (lname == "only guest") return "guest only"; + if (lname == "prefered master") return "preferred master"; + if (lname == "print ok") return "printable"; + if (lname == "printcap") return "printcap name"; + if (lname == "printer") return "printer name"; + if (lname == "protocol") return "max protocol"; + if (lname == "public") return "guest ok"; + if (lname == "writable") return "read only"; + if (lname == "write ok") return "read only"; + if (lname == "read only") return "read only"; + if (lname == "root") return "root directory"; + if (lname == "root") return "root dir"; + if (lname == "timestamp logs") return "debug timestamp"; + if (lname == "user") return "username"; + if (lname == "users") return "username"; + if (lname == "idmap uid") return "winbind uid"; + if (lname == "idmap gid") return "winbind gid"; + if (lname == "vfs object") return "vfs objects"; + + + return lname; +} + +void SambaShare::setValue(const QString & name, const QString & value, bool globalValue, bool defaultValue) +{ + QString synonym = getSynonym(name); + + QString newValue = value; + + if (newValue.isNull()) + newValue = ""; + + if (getName().lower() == "global") + globalValue = false; + + if (name=="writable" || name=="write ok" || name=="writeable") + { + synonym = "read only"; + newValue = textFromBool(!boolFromText(value)); + } + + QString global = ""; + + if (globalValue && !hasComments(synonym)) + { + global = getGlobalValue(synonym, false); + + if ( newValue.lower() == global.lower() ) + { + remove(synonym); + _optionList.remove(synonym); + return; + } + } + + // If the option has a comment we don't remove + // it if the value is equal to the default value. + // That's because the author of the option has thought about it. + if (defaultValue && global.isEmpty() && !hasComments(synonym)) + { + if ( newValue.stripWhiteSpace().lower() == getDefaultValue(synonym).stripWhiteSpace().lower() ) + { + kdDebug(5009) << getName() << " global: " << global << " remove " << synonym << endl; + remove(synonym); + _optionList.remove(synonym); + return; + } + + } + + if (!find(synonym)) + { + _optionList.append(synonym); + } + + replace(synonym,new QString(newValue)); +} + +void SambaShare::setValue(const QString & name, bool value, bool globalValue, bool defaultValue) +{ + setValue(name,textFromBool(value),globalValue, defaultValue); +} + +void SambaShare::setValue(const QString & name, int value, bool globalValue, bool defaultValue) +{ + setValue(name,QString::number(value),globalValue, defaultValue); +} + +/** +* Returns the default value of the parameter +**/ +QString SambaShare::getDefaultValue(const QString & name) +{ + QString defaultValue = _sambaFile->getDefaultValue(name); + if (defaultValue.isNull()) + defaultValue = ""; + + return defaultValue; +} + +bool SambaShare::getDefaultBoolValue(const QString & name) +{ + + return boolFromText(getDefaultValue(name)); +} + +/** +* Sets the comments for the passed option +**/ +void SambaShare::setComments(const QString & name, const QStringList & commentList) +{ + // Only add inempty lists + if (commentList.empty()) + return; + + QString synonym = getSynonym(name); + + _commentList.replace(name,new QStringList(commentList)); +} + +/** +* Returns the comments of the passed option +**/ +QStringList SambaShare::getComments(const QString & name) +{ + QStringList* list = _commentList.find(getSynonym(name)); + + if (list) + return QStringList(*list); + else + return QStringList(); +} + + +bool SambaShare::hasComments(const QString & name) +{ + return 0L != _commentList.find(getSynonym(name)); +} + +/** +* Returns the comments of the share +* e.g. the text above the [...] section +**/ +QStringList SambaShare::getComments() +{ + return _comments; +} + +/** +* Sets the comments for the share +* e.g. the text above the [...] section +**/ +void SambaShare::setComments(const QStringList & commentList) +{ + _comments = commentList; +} + +QStringList SambaShare::getOptionList() +{ + return _optionList; +} + +/** +* Returns true if this share is a printer +**/ +bool SambaShare::isPrinter() +{ + QString* str = find("printable"); + + if (!str) + str = find("print ok"); + + return str!=0; +} + +/** +* Returns true if the share name is +* global, printers or homes +**/ +bool SambaShare::isSpecialSection() +{ + if ( _name.lower() == "global" || + _name.lower() == "printers" || + _name.lower() == "homes" ) + return true; + else + return false; +} diff --git a/filesharing/advanced/kcm_sambaconf/sambashare.h b/filesharing/advanced/kcm_sambaconf/sambashare.h new file mode 100644 index 00000000..d6c0e3b7 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/sambashare.h @@ -0,0 +1,231 @@ +/*************************************************************************** + sambashare.h - description + ------------------- + begin : Mon 12 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#ifndef SAMBASHARE_H +#define SAMBASHARE_H + +#include +#include +#include +#include + +class SambaConfigFile; +class QStringList; + +/** + * A class which represents a Samba share + * @author Jan Schäfer + **/ +class SambaShare :public QDict +{ +public: + + /** + * Creates a new SambaShare with an empty name + **/ + SambaShare(SambaConfigFile* sambaFile); + + /** + * Creates a new SambaShare with the given name + * @param name the name of the share + **/ + SambaShare(const QString & name, SambaConfigFile* sambaFile); + + /** + * Returns the name of the share + **/ + const QString& getName() const; + + + /** + * Sets the name of the share + * returns false if the name already exists and the testWetherExists + * parameter is set to true + **/ + bool setName(const QString & name, bool testWetherExists=true); + + /** + * Tests wether or not the given option is supported by Samba. + * It does this by testing the output of testparm for this option + **/ + bool optionSupported(const QString & name); + + /** + * Returns the value of the given parameter + * if the parameter doesn't exists, the + * global and then the default value is returned. + * @param name the name of the parameter + * @param gobalValue if the gobal value should be returend + * @param defaultValue if the default value should be returned or a null string + **/ + QString getValue(const QString & name, bool globalValue=true, bool defaultValue=true); + + /** + * Same as above but for boolean parameters + * Don't use defaultValue = false with this function + * because you can't distinguish an unset parameter + * from a false parameter + **/ + bool getBoolValue(const QString & name, bool globalValue=true, bool defaultValue=true); + + /** + * Returns the value from the [globals] section + **/ + QString getGlobalValue(const QString & name, bool defaultValue=true); + + /** + * Sets the parameter name to the given value + * @param value the value of the parameter + * @param name the name of the parameter + * @param globalValue if true then the value is only set if it is different to the global value + * @param defaultValue if true then the value is only set if it is different to the default value + * if globalValue and defaultValue is true then the value is set when a global value + * exists and it it is different to it. If no global value exists then it is only + * set if different to the default value. + **/ + void setValue(const QString & name,const QString & value, bool globalValue=true, bool defaultValue=true); + + /** + * Same as above but for boolean parameters + **/ + void setValue(const QString & name, bool value, bool globalValue=true, bool defaultValue=true); + + /** + * Same as above but for integer parameters + **/ + void setValue(const QString & name, int value, bool globalValue=true, bool defaultValue=true); + + /** + * Returns the default value of the parameter + * @param name the name of the parameter + **/ + QString getDefaultValue(const QString & name); + + /** + * Same as above but for booleans + **/ + bool getDefaultBoolValue(const QString & name); + + /** + * Returns the default synonym for the given parameter + * if no synonym exists the original parameter in lower + * case is returned + **/ + QString getSynonym(const QString & name) const; + + /** + * Returns the comments of the share + * e.g. the text above the [...] section + **/ + QStringList getComments(); + + /** + * Sets the comments for the share + * e.g. the text above the [...] section + **/ + void setComments(const QStringList & commentList); + + /** + * Sets the comments for the passed option + **/ + void setComments(const QString & name, const QStringList & commentList); + + /** + * Returns the comments of the passed option + **/ + QStringList getComments(const QString & name); + + /** + * Returns true if the passed option has comments + * otherwise returns false + **/ + bool hasComments(const QString & name); + + /** + * Returns the list of all options + * the order of the options is exactly the + * order of the insertion of the options + **/ + QStringList getOptionList(); + + /** + * Returns true if this share is a printer + * that's if printable = true + **/ + bool isPrinter(); + + /** + * Returns true if the share name is + * global, printers or homes + **/ + bool isSpecialSection(); + +protected: + /** + * The name of the share + * could be also printers, global and homes + **/ + QString _name; + SambaConfigFile* _sambaFile; + + /** + * This attribute stores all option comments. + * the comments which stood above the option name + * are stored in this QStringList + **/ + QDict _commentList; + + /** + * The comments for this share + **/ + QStringList _comments; + + /** + * An extra list which holds + * all stored options + * You might say, hey for what is this ? + * We have them already stored in the QDict. + * That's right, but there is a problem : + * QDict doesn't preserve the order of + * the inserted items, but when saving + * the options back to the smb.conf + * we want to have exactly the same order + * so this QStringList is only for saving + * the order of the options. + * On the other side we need a very fast lookup + * of the options, because we lookup very frequently + * so this is the best way to do both. + **/ + QStringList _optionList; + +}; + +typedef QPtrList SambaShareList; + + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/share.ui b/filesharing/advanced/kcm_sambaconf/share.ui new file mode 100644 index 00000000..91d16843 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/share.ui @@ -0,0 +1,3300 @@ + +KcmShareDlg + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + + + + KcmShareDlg + + + + 0 + 0 + 706 + 535 + + + + Add/Edit Share + + + true + + + + unnamed + + + 11 + + + 6 + + + + _tabs + + + Top + + + + baseTab + + + &Base Settings + + + + unnamed + + + + Layout52 + + + + unnamed + + + 0 + + + 6 + + + + pixmapFrame + + + Box + + + Sunken + + + + unnamed + + + 11 + + + 6 + + + + directoryPixLbl + + + + 0 + 0 + 0 + 0 + + + + Pixmap + + + true + + + + + + + directoryGrp + + + D&irectory + + + + unnamed + + + 11 + + + 6 + + + + Layout2 + + + + unnamed + + + 0 + + + 6 + + + + TextLabel1 + + + true + + + &Path: + + + pathUrlRq + + + + + pathUrlRq + + + true + + + + + + + homeChk + + + Share all home &directories + + + + + + + + + identifierGrp + + + Iden&tifier + + + + unnamed + + + 11 + + + 6 + + + + Lbl_shareName + + + Na&me: + + + shareNameEdit + + + + + TextLabel3 + + + Comme&nt: + + + commentEdit + + + + + commentEdit + + + + + shareNameEdit + + + + + + + GroupBox4 + + + Main P&roperties + + + + unnamed + + + 11 + + + 6 + + + + readOnlyBaseChk + + + Read onl&y + + + true + + + + + publicBaseChk + + + Pub&lic + + + + + browseableBaseChk + + + Bro&wseable + + + + + availableBaseChk + + + A&vailable + + + + + + + Spacer42 + + + Vertical + + + Expanding + + + + 20 + 90 + + + + + + + + securityTab + + + &Security + + + + unnamed + + + 11 + + + 6 + + + + groupBox19 + + + Gu&ests + + + + unnamed + + + 11 + + + 6 + + + + guestAccountCombo + + + + + Spacer27 + + + Horizontal + + + Expanding + + + + 50 + 20 + + + + + + guestAccountLbl + + + false + + + &Guest account: + + + guestAccountCombo + + + This is a username which will be used for access to services which are specified as guest ok. Whatever privileges this user has will be available to any client connecting to the guest service. Typically this user will exist in the password file, but will not have a valid login. The user account \"ftp\" is often a good choice for this parameter. If a username is specified in a given service, the specified username overrides this one. + + + + + guestOnlyChk + + + false + + + Only allow guest connect&ions + + + If this is checked , then no password is required to connect to the service. Privileges will be those of the guest account. + + + + + + + groupBox18 + + + Hos&ts + + + + unnamed + + + 11 + + + 6 + + + + hostsDenyEdit + + + The opposite of hosts allow - hosts listed here are NOT permitted access to services unless the specific services have their own lists to override this one. Where the lists conflict, the allow list takes precedence. + + + + + TextLabel5 + + + Hosts allo&w: + + + hostsAllowEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + TextLabel6 + + + Hosts &deny: + + + hostsDenyEdit + + + The opposite of hosts allow - hosts listed here are NOT permitted access to services unless the specific services have their own lists to override this one. Where the lists conflict, the allow list takes precedence. + + + + + hostsAllowEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + + + groupBox17 + + + Sy&mbolic Links + + + + unnamed + + + 11 + + + 6 + + + + followSymlinksChk + + + Allow following of symbolic lin&ks + + + + + wideLinksChk + + + false + + + Allow following of symbolic links that &point to areas outside the directory tree + + + + + + + TextLabel2_4_2 + + + <qt>Validate password against the following usernames if the client cannot supply a username:</qt> + + + userNameEdit + + + + + userNameEdit + + + + + onlyUserChk + + + Only allow connections with use&rnames specified in this username list + + + If this is checked , then no password is required to connect to the service. Privileges will be those of the guest account. + + + + + Spacer31 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + + + hiddenFilesTab + + + Hidden &Files + + + + unnamed + + + 11 + + + 6 + + + + + Name + + + true + + + true + + + + + Hidden + + + true + + + true + + + + + Veto + + + true + + + true + + + + + Veto Oplock + + + true + + + true + + + + + Size + + + true + + + true + + + + + Date + + + true + + + true + + + + + Permissions + + + true + + + true + + + + + Owner + + + true + + + true + + + + + Group + + + true + + + true + + + + hiddenListView + + + true + + + + + selGrpBx + + + Se&lected Files + + + + unnamed + + + 11 + + + 6 + + + + hiddenChk + + + Hi&de + + + + + vetoChk + + + &Veto + + + + + vetoOplockChk + + + Veto oploc&k + + + + + + + GroupBox13_2 + + + &Manual Configuration + + + + unnamed + + + 11 + + + 6 + + + + layout18 + + + + unnamed + + + 0 + + + 6 + + + + TextLabel2_2 + + + Ve&to files: + + + vetoEdit + + + + + TextLabel2_2_2 + + + Veto oplock f&iles: + + + vetoOplockEdit + + + + + hiddenEdit + + + + + vetoOplockEdit + + + + + vetoEdit + + + + + TextLabel1_3 + + + Hidde&n files: + + + hiddenEdit + + + + + + + layout52 + + + + unnamed + + + 0 + + + 6 + + + + hideUnwriteableFilesChk + + + Hide un&writable files + + + + + hideSpecialFilesChk + + + Hide s&pecial files + + + + + hideDotFilesChk + + + Hide files startin&g with a dot + + + + + hideUnreadableChk + + + Hide un&readable files + + + + + + + + + + + advancedTab + + + &Advanced + + + + unnamed + + + 11 + + + 6 + + + + advancedFrame + + + StyledPanel + + + Raised + + + + + Frame26 + + + HLine + + + Sunken + + + + + Layout92 + + + + unnamed + + + 0 + + + 6 + + + + PixmapLabel1 + + + + 0 + 0 + 0 + 0 + + + + true + + + + + TextLabel1_4 + + + + 1 + + + + Here you can change advanced options of the SAMBA server. +Only change something if you know what you are doing. + + + + + + + + + advancedDumpTabPage + + + Advanced Dump + + + + unnamed + + + + advancedDumpTab + + + + tab + + + Security + + + + unnamed + + + 0 + + + + GroupBox11_2 + + + Force Modes + + + + unnamed + + + 11 + + + 6 + + + + TextLabel5_2_3_2_2_2_2 + + + Forc&e directory security mode: + + + forceDirectorySecurityModeEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + TextLabel5_2_3_2_3 + + + Fo&rce security mode: + + + forceSecurityModeEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + TextLabel5_2_3_2_2_3 + + + Force director&y mode: + + + forceDirectoryModeEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + TextLabel5_2_3_3 + + + Force create mo&de: + + + forceCreateModeEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + forceDirectoryModeBtn + + + + 1 + 1 + 0 + 0 + + + + + 20 + 0 + + + + ... + + + + + forceDirectorySecurityModeBtn + + + + 1 + 1 + 0 + 0 + + + + + 20 + 0 + + + + ... + + + + + forceCreateModeEdit + + + + 40 + 0 + + + + 01234567 + + + + + forceSecurityModeEdit + + + + 40 + 0 + + + + 01234567 + + + + + forceDirectoryModeEdit + + + + 40 + 0 + + + + 01234567 + + + + + forceDirectorySecurityModeEdit + + + + 40 + 0 + + + + 01234567 + + + + + forceSecurityModeBtn + + + + 1 + 1 + 0 + 0 + + + + + 20 + 0 + + + + ... + + + + + forceCreateModeBtn + + + + 1 + 1 + 0 + 0 + + + + + 20 + 0 + + + + ... + + + + + + + Spacer62 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + GroupBox10_2 + + + Masks + + + + unnamed + + + 11 + + + 6 + + + + TextLabel5_2_4_2_2_2 + + + Directory security mask: + + + directorySecurityMaskEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + TextLabel5_2_4_3 + + + Security &mask: + + + securityMaskEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + TextLabel5_2_4_2_3 + + + Direc&tory mask: + + + directoryMaskEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + securityMaskBtn + + + + 1 + 1 + 0 + 0 + + + + + 20 + 0 + + + + ... + + + + + directoryMaskBtn + + + + 1 + 1 + 0 + 0 + + + + + 20 + 0 + + + + ... + + + + + directorySecurityMaskBtn + + + + 1 + 1 + 0 + 0 + + + + + 20 + 0 + + + + ... + + + + + securityMaskEdit + + + + 40 + 0 + + + + 01234567 + + + + + directoryMaskEdit + + + + 40 + 0 + + + + 01234567 + + + + + directorySecurityMaskEdit + + + + 40 + 0 + + + + 01234567 + + + + + TextLabel5_2_2 + + + Create mas&k: + + + createMaskEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + createMaskEdit + + + + 40 + 0 + + + + 01234567 + + + + + createMaskBtn + + + + 1 + 1 + 0 + 0 + + + + + 20 + 0 + + + + ... + + + + + + + groupBox56 + + + ACL + + + + unnamed + + + 11 + + + 6 + + + + profileAclsChk + + + &Profile acls + + + + + inheritAclsChk + + + Inherit ac&ls + + + + + ntAclSupportChk + + + &NT ACL support + + + true + + + + + TextLabel5_2_2_2 + + + Force unkno&wn acl user: + + + forceUnknownAclUserEdit + + + This parameter is a comma, space, or tab delimited set of hosts which are permitted to access a service. + + + + + mapAclInheritChk + + + Map acl &inherit + + + + + forceUnknownAclUserEdit + + + + 40 + 0 + + + + 01234567 + + + + + + + groupBox58 + + + General + + + + unnamed + + + 11 + + + 6 + + + + inheritPermissionsChk + + + Inherit permissions from parent directory + + + + + deleteReadonlyChk + + + Allow deletion of readonly files + + + + + + + groupBox57 + + + DOS Attribute Mapping + + + + unnamed + + + 11 + + + 6 + + + + mapArchiveChk + + + Map DOS archi&ve to UNIX owner execute + + + + + mapHiddenChk + + + Map DOS hidden to UNI&X world execute + + + + + mapSystemChk + + + Map DOS system to UNIX &group execute + + + + + storeDosAttributesChk + + + Store DOS attributes onto extended attribute + + + + + + + + + + groupBox76 + + + OS/2 + + + + unnamed + + + + eaSupportChk + + + OS/2 style extended attributes support + + + + + + + + + tab + + + Tuning + + + + unnamed + + + 0 + + + 6 + + + + syncAlwaysChk + + + false + + + Sync al&ways + + + + + strictSyncChk + + + Strict s&ync + + + + + strictAllocateChk + + + St&rict allocate + + + + + useSendfileChk + + + Use sen&dfile + + + + + Spacer53 + + + Vertical + + + Expanding + + + + 20 + 120 + + + + + + textLabel1 + + + Bloc&k size: + + + blockSizeSpin + + + + + textLabel3 + + + Client-side cachin&g policy: + + + cscPolicyCombo + + + + + TextLabel1_7_2 + + + bytes + + + + + TextLabel5_3_2_2 + + + Write cache si&ze: + + + writeCacheSizeSpin + + + + + + manual + + + + + documents + + + + + programs + + + + + disable + + + + cscPolicyCombo + + + + + Spacer55 + + + Horizontal + + + Expanding + + + + 143 + 16 + + + + + + TextLabel1_7 + + + bytes + + + + + maxConnectionsSpin + + + UpDownArrows + + + 2147483647 + + + + + blockSizeSpin + + + UpDownArrows + + + 2147483647 + + + + + TextLabel5_3_4 + + + &Maximum number of simultaneous connections: + + + maxConnectionsSpin + + + + + writeCacheSizeSpin + + + UpDownArrows + + + 2147483647 + + + + + + + tab + + + Filenames + + + + Layout54 + + + + 330 + 1 + 323 + 270 + + + + + unnamed + + + 0 + + + 6 + + + + GroupBox9 + + + + 7 + 5 + 0 + 0 + + + + Other Options + + + + unnamed + + + + hideTrailingDotChk + + + Hide traili&ng dot + + + + + + + GroupBox15 + + + DOS + + + + unnamed + + + 11 + + + 6 + + + + dosFilemodeChk + + + &DOS file mode + + + + + dosFiletimesChk + + + DOS f&ile times + + + + + dosFiletimeResolutionChk + + + DOS file time resolution + + + + + + + + + Spacer61 + + + Vertical + + + Expanding + + + + 20 + 30 + + + + + 152 + 277 + 20 + 119 + + + + + + GroupBox5 + + + + 1 + 1 + 323 + 270 + + + + + 5 + 5 + 0 + 0 + + + + Name Mangling + + + + TextLabel3_2_2 + + + + 11 + 179 + 168 + 24 + + + + + 5 + 5 + 0 + 0 + + + + Mangling cha&r: + + + manglingCharEdit + + + + + Layout14 + + + + 185 + 179 + 127 + 24 + + + + + unnamed + + + 0 + + + 6 + + + + manglingCharEdit + + + + 40 + 32767 + + + + + + Spacer40_2 + + + Horizontal + + + Expanding + + + + 20 + 0 + + + + + + + + TextLabel1_5 + + + + 11 + 209 + 168 + 22 + + + + Mangled ma&p: + + + mangledMapEdit + + + + + mangledMapEdit + + + + 185 + 209 + 127 + 22 + + + + + + mangledNamesChk + + + + 11 + 21 + 168 + 20 + + + + Enable na&me mangling + + + + + mangleCaseChk + + + + 11 + 47 + 168 + 20 + + + + Man&gle case + + + + + textLabel2 + + + + 11 + 237 + 168 + 22 + + + + Mangling method: + + + manglingMethodCombo + + + + + + hash + + + + + hash2 + + + + manglingMethodCombo + + + + 185 + 237 + 127 + 22 + + + + + + preserveCaseChk + + + + 10 + 70 + 168 + 20 + + + + Preser&ve case + + + true + + + + + shortPreserveCaseChk + + + + 10 + 90 + 168 + 20 + + + + Short pr&eserve case + + + + + TextLabel2_3 + + + + 10 + 150 + 168 + 22 + + + + Defau&lt case: + + + defaultCaseCombo + + + + + + Lower + + + + + Upper + + + + defaultCaseCombo + + + + 185 + 151 + 127 + 22 + + + + + 1 + 0 + 0 + 0 + + + + + + + Automatic + + + + + Yes + + + + + No + + + + caseSensitiveCombo + + + + 185 + 121 + 127 + 22 + + + + + 1 + 0 + 0 + 0 + + + + + + TextLabel2_3_3 + + + + 10 + 120 + 168 + 22 + + + + Case sensi&tive: + + + caseSensitiveCombo + + + + + + + tab + + + Locking + + + + unnamed + + + 0 + + + + Spacer51 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + groupBox47 + + + false + + + Box + + + Locki&ng + + + + unnamed + + + 11 + + + 6 + + + + oplocksChk + + + Issue oppo&rtunistic locks (oplocks) + + + + + groupBox59 + + + O&plocks + + + + unnamed + + + + oplockContentionLimitSpin + + + UpDownArrows + + + 2147483647 + + + + + spacer23 + + + Horizontal + + + Expanding + + + + 299 + 20 + + + + + + TextLabel5_3_3_2_2 + + + + + + + Oplock contention li&mit: + + + oplockContentionLimitSpin + + + + + level2OplocksChk + + + Le&vel2 oplocks + + + + + + + fakeOplocksChk + + + Fak&e oplocks + + + true + + + + + shareModesChk + + + Share mo&des + + + + + posixLockingChk + + + Posi&x locking + + + + + layout9 + + + + unnamed + + + + TextLabel2_3_2 + + + S&trict locking: + + + strictLockingCombo + + + + + + Automatic + + + + + Yes + + + + + No + + + + strictLockingCombo + + + + 1 + 0 + 0 + 0 + + + + + + spacer15 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + blockingLocksChk + + + Blockin&g locks + + + + + + + lockingChk + + + Enable lock&ing + + + + + + + tab + + + VFS + + + + unnamed + + + 0 + + + 6 + + + + Spacer14 + + + Vertical + + + Expanding + + + + 20 + 260 + + + + + + vfsObjectsEdit + + + + + TextLabel1_6 + + + Vfs ob&jects: + + + vfsObjectsEdit + + + + + TextLabel1_6_2 + + + Vfs o&ptions: + + + vfsOptionsEdit + + + + + vfsOptionsEdit + + + + + + + tab + + + Exec + + + + unnamed + + + 0 + + + 6 + + + + preexecCloseChk + + + preexec c&lose + + + + + rootPreexecCloseChk + + + root pree&xec close + + + + + Spacer16 + + + Vertical + + + Expanding + + + + 16 + 284 + + + + + + TextLabel4 + + + Pos&texec: + + + rootPreexecEdit + + + + + TextLabel5_2 + + + Root pr&eexec: + + + postexecEdit + + + + + TextLabel2 + + + P&reexec: + + + preexecEdit + + + + + preexecEdit + + + + + TextLabel5_2_4 + + + Root &postexec: + + + rootPostexecEdit + + + + + postexecEdit + + + + + rootPreexecEdit + + + + + rootPostexecEdit + + + + + + + tab + + + Misc + + + + unnamed + + + 0 + + + + Spacer15 + + + Vertical + + + Expanding + + + + 20 + 50 + + + + + + TextLabel6_2_2 + + + Fst&ype: + + + fstypeEdit + + + + + fstypeEdit + + + + + magicScriptEdit + + + + + TextLabel6_2_3 + + + Ma&gic script: + + + magicScriptEdit + + + + + TextLabel6_2 + + + &Volume: + + + volumeEdit + + + + + magicOutputEdit + + + + + TextLabel6_2_2_2 + + + Mag&ic output: + + + magicOutputEdit + + + + + fakeDirectoryCreateTimesChk + + + Fa&ke directory create times + + + + + msdfsRootChk + + + Ms&dfs root + + + + + setDirectoryChk + + + Setdir command allo&wed + + + + + TextLabel6_2_3_2 + + + Do &not descend: + + + dontDescendEdit + + + + + dontDescendEdit + + + + + TextLabel6_2_3_2_2 + + + Msdfs pro&xy: + + + msdfsProxyEdit + + + + + msdfsProxyEdit + + + + + volumeEdit + + + + + + + + + + + Layout1 + + + + unnamed + + + 0 + + + 6 + + + + buttonHelp + + + &Help + + + F1 + + + true + + + + + Horizontal Spacing2 + + + Horizontal + + + Expanding + + + + 20 + 0 + + + + + + buttonOk + + + &OK + + + true + + + true + + + + + buttonCancel + + + &Cancel + + + true + + + + + + + + + buttonCancel + clicked() + KcmShareDlg + reject() + + + shareNameEdit + textChanged(const QString&) + KcmShareDlg + checkValues() + + + buttonOk + clicked() + KcmShareDlg + accept() + + + homeChk + toggled(bool) + KcmShareDlg + homeChkToggled(bool) + + + strictSyncChk + toggled(bool) + syncAlwaysChk + setEnabled(bool) + + + inheritPermissionsChk + toggled(bool) + forceCreateModeEdit + setDisabled(bool) + + + inheritPermissionsChk + toggled(bool) + forceCreateModeBtn + setDisabled(bool) + + + inheritPermissionsChk + toggled(bool) + forceDirectoryModeEdit + setDisabled(bool) + + + inheritPermissionsChk + toggled(bool) + forceDirectoryModeBtn + setDisabled(bool) + + + inheritPermissionsChk + toggled(bool) + createMaskEdit + setDisabled(bool) + + + inheritPermissionsChk + toggled(bool) + createMaskBtn + setDisabled(bool) + + + inheritPermissionsChk + toggled(bool) + directoryMaskEdit + setDisabled(bool) + + + inheritPermissionsChk + toggled(bool) + directoryMaskBtn + setDisabled(bool) + + + followSymlinksChk + toggled(bool) + wideLinksChk + setChecked(bool) + + + followSymlinksChk + toggled(bool) + wideLinksChk + setEnabled(bool) + + + publicBaseChk + toggled(bool) + KcmShareDlg + publicBaseChk_toggled(bool) + + + pathUrlRq + textChanged(const QString&) + KcmShareDlg + pathUrlRq_textChanged(const QString&) + + + lockingChk + toggled(bool) + KcmShareDlg + lockingChk_toggled(bool) + + + vetoOplockChk + stateChanged(int) + KcmShareDlg + changedSlot() + + + lockingChk + toggled(bool) + groupBox47 + setEnabled(bool) + + + oplockContentionLimitSpin + valueChanged(int) + KcmShareDlg + oplockContentionLimitSpin_valueChanged(int) + + + fakeOplocksChk + toggled(bool) + KcmShareDlg + fakeOplocksChk_toggled(bool) + + + oplocksChk + toggled(bool) + groupBox59 + setEnabled(bool) + + + forceCreateModeBtn + clicked() + KcmShareDlg + accessModifierBtnClicked() + + + forceSecurityModeBtn + clicked() + KcmShareDlg + accessModifierBtnClicked() + + + forceDirectoryModeBtn + clicked() + KcmShareDlg + accessModifierBtnClicked() + + + forceDirectorySecurityModeBtn + clicked() + KcmShareDlg + accessModifierBtnClicked() + + + createMaskBtn + clicked() + KcmShareDlg + accessModifierBtnClicked() + + + securityMaskBtn + clicked() + KcmShareDlg + accessModifierBtnClicked() + + + directoryMaskBtn + clicked() + KcmShareDlg + accessModifierBtnClicked() + + + directorySecurityMaskBtn + clicked() + KcmShareDlg + accessModifierBtnClicked() + + + storeDosAttributesChk + toggled(bool) + KcmShareDlg + storeDosAttributesChk_toggled(bool) + + + buttonHelp + clicked() + KcmShareDlg + buttonHelp_clicked() + + + + volumeEdit + fstypeEdit + magicScriptEdit + magicOutputEdit + dontDescendEdit + setDirectoryChk + fakeDirectoryCreateTimesChk + msdfsRootChk + preexecEdit + rootPreexecEdit + postexecEdit + rootPostexecEdit + preexecCloseChk + rootPreexecCloseChk + lockingChk + blockingLocksChk + posixLockingChk + shareModesChk + oplocksChk + level2OplocksChk + oplockContentionLimitSpin + fakeOplocksChk + mangledNamesChk + mangleCaseChk + preserveCaseChk + shortPreserveCaseChk + defaultCaseCombo + manglingCharEdit + mangledMapEdit + manglingMethodCombo + hideTrailingDotChk + dosFilemodeChk + dosFiletimesChk + dosFiletimeResolutionChk + strictSyncChk + syncAlwaysChk + strictAllocateChk + useSendfileChk + maxConnectionsSpin + writeCacheSizeSpin + blockSizeSpin + cscPolicyCombo + inheritPermissionsChk + deleteReadonlyChk + mapArchiveChk + mapHiddenChk + mapSystemChk + forceCreateModeEdit + forceSecurityModeEdit + forceDirectoryModeEdit + forceDirectorySecurityModeEdit + ntAclSupportChk + inheritAclsChk + profileAclsChk + mapAclInheritChk + forceUnknownAclUserEdit + createMaskEdit + securityMaskEdit + directoryMaskEdit + directorySecurityMaskEdit + advancedDumpTab + hiddenListView + hiddenChk + vetoChk + vetoOplockChk + hiddenEdit + vetoEdit + vetoOplockEdit + hideUnreadableChk + hideUnwriteableFilesChk + hideDotFilesChk + hideSpecialFilesChk + pathUrlRq + homeChk + shareNameEdit + commentEdit + readOnlyBaseChk + publicBaseChk + browseableBaseChk + availableBaseChk + guestAccountCombo + guestOnlyChk + hostsAllowEdit + hostsDenyEdit + followSymlinksChk + wideLinksChk + userNameEdit + onlyUserChk + _tabs + vfsObjectsEdit + vfsOptionsEdit + buttonHelp + buttonOk + buttonCancel + + + kiconloader.h + qptrlist.h + kmessagebox.h + kprocess.h + share.ui.h + + + checkValues() + init() + trytoAccept() + homeChkToggled( bool ) + addAllowedUserBtnClicked() + removeAllowedUserBtnClicked() + guestOnlyChk_toggled( bool b ) + userOnlyChk_toggled( bool b ) + accessModifierBtnClicked() + changedSlot() + publicBaseChk_toggled( bool b ) + pathUrlRq_textChanged( const QString & ) + oplocksChk_toggled( bool b ) + lockingChk_toggled( bool b ) + fakeOplocksChk_toggled( bool b ) + oplockContentionLimitSpin_valueChanged( int i ) + storeDosAttributesChk_toggled( bool b ) + buttonHelp_clicked() + + + + kurlrequester.h + klineedit.h + kpushbutton.h + klineedit.h + klineedit.h + klistview.h + krestrictedline.h + krestrictedline.h + krestrictedline.h + krestrictedline.h + krestrictedline.h + krestrictedline.h + krestrictedline.h + krestrictedline.h + krestrictedline.h + knuminput.h + knuminput.h + knuminput.h + knuminput.h + + diff --git a/filesharing/advanced/kcm_sambaconf/share.ui.h b/filesharing/advanced/kcm_sambaconf/share.ui.h new file mode 100644 index 00000000..414d055f --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/share.ui.h @@ -0,0 +1,258 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename slots use Qt Designer which will +** update this file, preserving your code. Create an init() slot in place of +** a constructor, and a destroy() slot in place of a destructor. +*****************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +void KcmShareDlg::checkValues() +{ +/* + bool state = true; + // Check if the ok-button should be enabled + if (directory->isChecked()){ + if (homes->isChecked()){ + state = true; + }else{ + if (shareName->text() == "") + state = false; + if (path->url() == "") + state = false; + } + }else{ + if (printers->isChecked()){ + state = true; + }else{ + if (shareName->text() == ""){ + state = false; + } + } + } + buttonOk->setEnabled(state); +*/ +} + + + + + + +void KcmShareDlg::init() +{ + directoryPixLbl->setPixmap(DesktopIcon("folder")); + PixmapLabel1->setPixmap(SmallIcon("messagebox_warning")); +/* + QListBoxItem* item = 0; + QPtrList* selectedList = new QPtrList; + selectedList->setAutoDelete(false); + for (int i=0; i < possible->count(); i++){ + item = possible->item(i); + if (item->isSelected()){ + selectedList->append(item); + } + } + selected->clearSelection(); + for (QPtrListIterator it(*selectedList); it.current(); ++it){ + possible->takeItem(it.current()); + selected->insertItem(it.current()); + } + delete selectedList; +*/ +} + + + + +void KcmShareDlg::trytoAccept() +{ +/* + bool error = false; + if (directory->isChecked()){ + if (!homes->isChecked()){ + if (shareName->text() == "[homes]"){ + KMessageBox::sorry(this, i18n("Sorry, but you can't create a share named \"[homes]\".\nIf you want to share your home-directorys, please click on \"Share homes\" on the \"Base settings\"-tab.")); + error = true; + } + } + }else{ + if (!printers->isChecked()){ + if (shareName->text() == "[printers]"){ + KMessageBox::sorry(this, i18n("Sorry, but you can't create a share named \"[printers]\".\nIf you want to share all your printers, please click on \"Share all printers\" on the \"Base settings\"-tab.")); + error = true; + } + } + } + if (!error){ + updateShareData(); + accept(); + } +*/ +} + + + + + +void KcmShareDlg::homeChkToggled( bool ) +{ + +} + +void KcmShareDlg::addAllowedUserBtnClicked() +{ + +} + +void KcmShareDlg::removeAllowedUserBtnClicked() +{ + +} + +void KcmShareDlg::guestOnlyChk_toggled( bool b) +{ + if (b) + { + onlyUserChk->setChecked(false); + publicBaseChk->setChecked(true); + } + + onlyUserChk->setDisabled(b); + publicBaseChk->setDisabled(b); +} + + +void KcmShareDlg::userOnlyChk_toggled( bool b) +{ + if (b) + { + guestOnlyChk->setChecked(false); + publicBaseChk->setChecked(false); + } + + guestOnlyChk->setDisabled(b); + publicBaseChk->setDisabled(b); +} + +void KcmShareDlg::accessModifierBtnClicked() +{ + + +} + + +void KcmShareDlg::changedSlot() +{ + +} + + +void KcmShareDlg::publicBaseChk_toggled( bool b) +{ + guestOnlyChk->setEnabled(b); + if (!b) { + guestOnlyChk->setChecked(false); + } + guestAccountCombo->setEnabled(b); + guestAccountLbl->setEnabled(b); + +} + + +void KcmShareDlg::pathUrlRq_textChanged( const QString & ) +{ + +} + + +void KcmShareDlg::oplocksChk_toggled( bool b) +{ + if (b) + fakeOplocksChk->setChecked(false); +} + + +void KcmShareDlg::lockingChk_toggled( bool b) +{ + // Its Dangerous to disable locking ! +/* + if (!b) { + enableLockingWarnPix->setPixmap(SmallIcon("messagebox_warning")); + enableLockingWarnPix->show(); + } else { + enableLockingWarnPix->hide(); + } +*/ + +} + + +void KcmShareDlg::fakeOplocksChk_toggled( bool b) +{ +/* + if (b) { + fakeOplocksWarnPix->setPixmap(SmallIcon("messagebox_info")); + fakeOplocksWarnPix->setText(i18n("Better use the real oplocks support than this parameter")); + fakeOplocksWarnPix->showMaximized(); + fakeOplocksWarnPix->show(); + } else { + fakeOplocksWarnPix->hide(); + } +*/ +} + + +void KcmShareDlg::oplockContentionLimitSpin_valueChanged( int i) +{ +/* + oplockContentionLimitWarnPix->setMaximumWidth(32767); + oplockContentionLimitWarnPix->setPixmap(SmallIcon("messagebox_critical")); +*/ + //oplockContentionLimitWarnPix->show(); + +} + + +void KcmShareDlg::storeDosAttributesChk_toggled( bool b) +{ + mapArchiveChk->setDisabled(b); + mapSystemChk->setDisabled(b); + mapHiddenChk->setDisabled(b); + + if (b) { + mapArchiveChk->setChecked(false); + mapSystemChk->setChecked(false); + mapHiddenChk->setChecked(false); + } +} + + +void KcmShareDlg::buttonHelp_clicked() +{ + KProcess* p = new KProcess(); + *p << "konqueror"; + *p << "man:smb.conf"; + p->start(); +} + + diff --git a/filesharing/advanced/kcm_sambaconf/sharedlgimpl.cpp b/filesharing/advanced/kcm_sambaconf/sharedlgimpl.cpp new file mode 100644 index 00000000..aca5ea39 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/sharedlgimpl.cpp @@ -0,0 +1,478 @@ +/*************************************************************************** + sharedlgimpl.cpp - description + ------------------- + begin : Tue June 6 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + + +/** + * @author Jan Schäfer + **/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smbpasswdfile.h" +#include "sambafile.h" +#include "common.h" +#include "passwd.h" +#include "usertabimpl.h" +#include "filemodedlgimpl.h" +#include "hiddenfileview.h" +#include "dictmanager.h" + +#include "sharedlgimpl.h" + + + + +ShareDlgImpl::ShareDlgImpl(QWidget* parent, SambaShare* share) + : KcmShareDlg(parent,"sharedlgimpl") +{ + if (!share) { + kdWarning() << "ShareDlgImpl::Constructor : share parameter is null!" << endl; + return; + } + + _dictMngr = new DictManager(share); + _share = share; + + initDialog(); + initAdvancedTab(); +} + +void ShareDlgImpl::initDialog() +{ + if (!_share) + return; + + // Base settings + _fileView = 0L; + + pathUrlRq->setMode(2+8+16); + + homeChk->setChecked(_share->getName().lower() == "homes"); + shareNameEdit->setText( _share->getName() ); + + _dictMngr->add("path",pathUrlRq); + + _dictMngr->add("comment",commentEdit); + _dictMngr->add("available",availableBaseChk); + _dictMngr->add("browseable",browseableBaseChk); + _dictMngr->add("public",publicBaseChk); + + _dictMngr->add("read only",readOnlyBaseChk); + + // User settings + + _userTab = new UserTabImpl(this,_share); + _tabs->insertTab(_userTab,i18n("&Users"),1); + _userTab->load(); + connect(_userTab, SIGNAL(changed()), this, SLOT(changedSlot())); + + // Filename settings + + _dictMngr->add("case sensitive",caseSensitiveCombo, + new QStringList(QStringList() << "auto" << "yes" << "no")); + _dictMngr->add("preserve case",preserveCaseChk); + _dictMngr->add("short preserve case",shortPreserveCaseChk); + _dictMngr->add("mangled names",mangledNamesChk); + _dictMngr->add("mangle case",mangleCaseChk); + _dictMngr->add("mangling char",manglingCharEdit); + _dictMngr->add("mangled map",mangledMapEdit); + + + _dictMngr->add("mangling method",manglingMethodCombo, + new QStringList(QStringList() << "hash" << "hash2")); + + _dictMngr->add("default case",defaultCaseCombo, + new QStringList(QStringList() << "Lower" << "Upper")); + + _dictMngr->add("hide dot files",hideDotFilesChk); + _dictMngr->add("strip dot",hideTrailingDotChk); + _dictMngr->add("hide unreadable",hideUnreadableChk); + _dictMngr->add("hide unwriteable files",hideUnwriteableFilesChk); + _dictMngr->add("hide special files",hideSpecialFilesChk); + _dictMngr->add("dos filemode",dosFilemodeChk); + _dictMngr->add("dos filetimes",dosFiletimesChk); + _dictMngr->add("dos filetime resolution",dosFiletimeResolutionChk); + + // Security tab + + _dictMngr->add("guest only",guestOnlyChk); + _dictMngr->add("hosts allow",hostsAllowEdit); + + _dictMngr->add("only user",onlyUserChk); + _dictMngr->add("username",userNameEdit); + + + guestAccountCombo->insertStringList( getUnixUsers() ); + setComboToString(guestAccountCombo,_share->getValue("guest account")); + + _dictMngr->add("hosts deny",hostsDenyEdit); + _dictMngr->add("force directory security mode",forceDirectorySecurityModeEdit); + _dictMngr->add("force directory mode",forceDirectoryModeEdit); + _dictMngr->add("force security mode",forceSecurityModeEdit); + + _dictMngr->add("force create mode",forceCreateModeEdit); + _dictMngr->add("directory security mask",directorySecurityMaskEdit); + _dictMngr->add("directory mask",directoryMaskEdit); + _dictMngr->add("security mask",securityMaskEdit); + _dictMngr->add("create mask",createMaskEdit); + _dictMngr->add("inherit permissions",inheritPermissionsChk); + _dictMngr->add("inherit acls",inheritAclsChk); + _dictMngr->add("nt acl support",ntAclSupportChk); + _dictMngr->add("delete readonly",deleteReadonlyChk); + + _dictMngr->add("wide links",wideLinksChk); + _dictMngr->add("follow symlinks",followSymlinksChk); + + _dictMngr->add("map hidden",mapHiddenChk); + _dictMngr->add("map archive",mapArchiveChk); + _dictMngr->add("map system",mapSystemChk); + _dictMngr->add("store dos attributes",eaSupportChk); + + _dictMngr->add("ea support",eaSupportChk); + + + _dictMngr->add("force unknown acl user",forceUnknownAclUserEdit); + _dictMngr->add("profile acls",profileAclsChk); + _dictMngr->add("map acl inherit",mapAclInheritChk); + + + // Advanced + + _dictMngr->add("blocking locks",blockingLocksChk); + _dictMngr->add("fake oplocks",fakeOplocksChk); + _dictMngr->add("locking",lockingChk); + _dictMngr->add("level2 oplocks",level2OplocksChk); + _dictMngr->add("posix locking",posixLockingChk); + _dictMngr->add("strict locking",strictLockingCombo, + new QStringList(QStringList() << "Auto" << "yes" << "no")); + _dictMngr->add("share modes",shareModesChk); + _dictMngr->add("oplocks",oplocksChk); + + + _dictMngr->add("oplock contention limit",oplockContentionLimitSpin); + _dictMngr->add("strict sync",strictSyncChk); + + // Tuning + + _dictMngr->add("strict allocate",strictAllocateChk); + + _dictMngr->add("max connections",maxConnectionsSpin); + _dictMngr->add("write cache size",writeCacheSizeSpin); + _dictMngr->add("block size",blockSizeSpin); + + + _dictMngr->add("sync always",syncAlwaysChk); + _dictMngr->add("use sendfile",useSendfileChk); + + _dictMngr->add("csc policy",cscPolicyCombo, + new QStringList(QStringList() << "manual" << "documents" << "programs" << "disable")); + + + + // VFS + + _dictMngr->add("vfs objects",vfsObjectsEdit); + _dictMngr->add("vfs options",vfsOptionsEdit); + + // Misc + + _dictMngr->add("preexec",preexecEdit); + _dictMngr->add("postexec",postexecEdit); + _dictMngr->add("root preexec",rootPreexecEdit); + _dictMngr->add("root postexec",rootPostexecEdit); + + _dictMngr->add("preexec close",preexecCloseChk); + _dictMngr->add("root preexec close",rootPreexecCloseChk); + + _dictMngr->add("volume",volumeEdit); + _dictMngr->add("fstype",fstypeEdit); + _dictMngr->add("magic script",magicScriptEdit); + _dictMngr->add("magic output",magicOutputEdit); + _dictMngr->add("dont descend",dontDescendEdit); + _dictMngr->add("set directory",setDirectoryChk); + _dictMngr->add("fake directory create times",fakeDirectoryCreateTimesChk); + + _dictMngr->add("msdfs root",msdfsRootChk); + _dictMngr->add("msdfs proxy",msdfsProxyEdit); + + _dictMngr->load( _share ); + + + connect( _tabs, SIGNAL(currentChanged(QWidget*)), this, SLOT(tabChangedSlot(QWidget*))); + connect(_dictMngr, SIGNAL(changed()), this, SLOT(changedSlot())); +} + +ShareDlgImpl::~ShareDlgImpl() +{ + delete _fileView; +} + +void ShareDlgImpl::initAdvancedTab() +{ + + QVBoxLayout *l = new QVBoxLayout(advancedFrame); + l->setAutoAdd(true); + l->setMargin(0); + _janus = new KJanusWidget(advancedFrame,0,KJanusWidget::TreeList); + _janus->setRootIsDecorated(false); + _janus->setShowIconsInTreeList(true); + + QWidget *w; + QFrame *f; + QString label; + QPixmap icon; + + for (int i=0;icount();) + { + w = advancedDumpTab->page(i); + label = advancedDumpTab->label(i); + + if (label.lower() == "security") + icon = SmallIcon("password"); + else + if (label.lower() == "tuning") + icon = SmallIcon("launch"); + else + if (label.lower() == "filenames") + icon = SmallIcon("folder"); + else + if (label.lower() == "printing") + icon = SmallIcon("fileprint"); + else + if (label.lower() == "locking") + icon = SmallIcon("lock"); + else + if (label.lower() == "logon") + icon = SmallIcon("kdmconfig"); + else + if (label.lower() == "protocol") + icon = SmallIcon("core"); + else + if (label.lower() == "coding") + icon = SmallIcon("charset"); + else + if (label.lower() == "socket") + icon = SmallIcon("socket"); + else + if (label.lower() == "ssl") + icon = SmallIcon("encrypted"); + else + if (label.lower() == "browsing") + icon = SmallIcon("konqueror"); + else + if (label.lower() == "misc") + icon = SmallIcon("misc"); + else { + icon = QPixmap(16,16); + icon.fill(); + } + //SmallIcon("empty2"); + + f = _janus->addPage( label,label,icon ); + l = new QVBoxLayout(f); + l->setAutoAdd(true); + l->setMargin(0); + + advancedDumpTab->removePage(w); + + w->reparent(f,QPoint(1,1),TRUE); + + } + + w = _tabs->page(5); + _tabs->removePage(w); + delete w; + + +} + + +void ShareDlgImpl::tabChangedSlot(QWidget* w) +{ + // We are only interrested in the Hidden files tab + if ( QString(w->name()) == "hiddenFilesTab" ) + loadHiddenFilesView(); + +} + +void ShareDlgImpl::loadHiddenFilesView() +{ + + if (_fileView) + return; + + _fileView = new HiddenFileView( this, _share ); + + if ( ! _share->isSpecialSection()) + _fileView->load(); + + +} + +void ShareDlgImpl::accept() +{ + // Base settings + if (!_share) + return; + + if (homeChk->isChecked()) + _share->setName("homes"); + else + _share->setName(shareNameEdit->text()); + + // User settings + + _userTab->save(); + + // Security + + _share->setValue("guest account",guestAccountCombo->currentText( ) ); + + + // Hidden files + if (_fileView) + _fileView->save(); + + _dictMngr->save( _share ); + + KcmShareDlg::accept(); +} + +void ShareDlgImpl::homeChkToggled(bool b) +{ + shareNameEdit->setDisabled(b); + pathUrlRq->setDisabled(b); + + if (b) + { + shareNameEdit->setText("homes"); + pathUrlRq->setURL(""); + directoryPixLbl->setPixmap(DesktopIcon("folder_home",48)); + + } + else + { + shareNameEdit->setText( _share->getName() ); + pathUrlRq->setURL( _share->getValue("path") ); + directoryPixLbl->setPixmap(DesktopIcon("folder")); + } +} + +void ShareDlgImpl::accessModifierBtnClicked() +{ + if (!QObject::sender()) { + kdWarning() << "ShareDlgImpl::accessModifierBtnClicked() : QObject::sender() is null!" << endl; + return; + } + + + QString name = QObject::sender()->name(); + + QLineEdit *edit = 0L; + + if (name == "forceCreateModeBtn") + edit = forceCreateModeEdit; + else + if (name == "forceSecurityModeBtn") + edit = forceSecurityModeEdit; + else + if (name == "forceDirectoryModeBtn") + edit = forceDirectoryModeEdit; + else + if (name == "forceDirectorySecurityModeBtn") + edit = forceDirectorySecurityModeEdit; + else + if (name == "createMaskBtn") + edit = createMaskEdit; + else + if (name == "securityMaskBtn") + edit = securityMaskEdit; + else + if (name == "directoryMaskBtn") + edit = directoryMaskEdit; + else + if (name == "directorySecurityMaskBtn") + edit = directorySecurityMaskEdit; + + if (!edit) { + kdWarning() << "ShareDlgImpl::accessModifierBtnClicked() : edit is null! name=" << name << endl; + return; + } + + FileModeDlgImpl dlg(this, edit); + + dlg.exec(); +} + +void ShareDlgImpl::changedSlot() { + m_changed = true; + kdDebug(5009) << "ShareDlgImpl::changedSlot()" << endl; + emit changed(); +} + +void ShareDlgImpl::pathUrlRq_textChanged( const QString & s) +{ + if (_fileView && ! _share->isSpecialSection()) + _fileView->load(); +} + + +#include "sharedlgimpl.moc" diff --git a/filesharing/advanced/kcm_sambaconf/sharedlgimpl.h b/filesharing/advanced/kcm_sambaconf/sharedlgimpl.h new file mode 100644 index 00000000..da6e178d --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/sharedlgimpl.h @@ -0,0 +1,100 @@ +/*************************************************************************** + sharedlgimpl.h - description + ------------------- + begin : Tue June 6 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#ifndef SHAREDLGIMPL_H +#define SHAREDLGIMPL_H + + +/** + * @author Jan Schäfer + **/ + +#include + +#include "share.h" + +class SambaShare; +class QWidget; +class KPopupMenu; +class KToggleAction; +class QGroupBox; +class UserTabImpl; +class HiddenFileView; +class DictManager; +class KJanusWidget; + +/** + * This class implements the share.ui interface + **/ +class ShareDlgImpl : public KcmShareDlg +{ +Q_OBJECT + +public : + + ShareDlgImpl(QWidget* parent, SambaShare* share); + ~ShareDlgImpl(); + + bool hasChanged() { return m_changed; }; + +protected : + + /** + * Fills all dialog fields with the values + * of the SambaShare object + **/ + void initDialog(); + + /** + * The share object to change with this dialog + **/ + SambaShare* _share; + + HiddenFileView* _fileView; + UserTabImpl* _userTab; + KJanusWidget* _janus; + bool m_changed; + DictManager* _dictMngr; + + void loadHiddenFilesView(); + void initAdvancedTab(); + +protected slots: + virtual void accept(); + virtual void homeChkToggled(bool); + virtual void accessModifierBtnClicked(); + virtual void changedSlot(); + virtual void pathUrlRq_textChanged( const QString & ); + + void tabChangedSlot(QWidget* w); +signals: + void changed(); +}; + + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/smbconfconfigwidget.cpp b/filesharing/advanced/kcm_sambaconf/smbconfconfigwidget.cpp new file mode 100644 index 00000000..ce276979 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/smbconfconfigwidget.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + smbconfconfigwidget.cpp - description + ------------------- + begin : Tue May 16 2003 + copyright : (C) 2003 by Jan Sch�er + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include + + +#include "smbconfconfigwidget.h" + +SmbConfConfigWidget::SmbConfConfigWidget(QWidget* parent) + : QWidget(parent,"configWidget") +{ + + QVBoxLayout *layout = new QVBoxLayout(this,5); + + QLabel *lbl = new QLabel(i18n("

    The SAMBA configuration file 'smb.conf'" \ + " could not be found;

    " \ + "make sure you have SAMBA installed.\n\n"), this); + + QHBoxLayout *hbox = new QHBoxLayout(this); + QPushButton *btn = new QPushButton(i18n("Specify Location"), this); + connect(btn, SIGNAL(pressed()), this, SLOT( btnPressed())); + + btn->setDefault(false); + btn->setAutoDefault(false); + + hbox->addStretch(); + hbox->addWidget(btn); + + layout->addWidget(lbl); + layout->addLayout(hbox); + layout->addStretch(); +} + +void SmbConfConfigWidget::btnPressed() { + QString smbConf = KFileDialog::getOpenFileName("/", + "smb.conf|Samba conf. File\n" + "*|All Files",0,i18n("Get smb.conf Location")); + + if (smbConf.isEmpty()) + return; + if ( ! QFileInfo(smbConf).isReadable() ) { + KMessageBox::sorry(this,i18n("The file %1 could not be read.").arg(smbConf),i18n("Could Not Read File")); + return; + } + + KConfig config("ksambaplugin"); + + config.setGroup("KSambaKonqiPlugin"); + config.writeEntry("smb.conf",smbConf); + config.sync(); + + emit smbConfChoosed(smbConf); + +} + +#include "smbconfconfigwidget.moc" diff --git a/filesharing/advanced/kcm_sambaconf/smbconfconfigwidget.h b/filesharing/advanced/kcm_sambaconf/smbconfconfigwidget.h new file mode 100644 index 00000000..bd00357a --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/smbconfconfigwidget.h @@ -0,0 +1,46 @@ +/*************************************************************************** + smbconfconfigwidget.h - description + ------------------- + begin : Tue May 16 2003 + copyright : (C) 2003 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#ifndef SMBCONFCONFIGWIDGET_H +#define SMBCONFCONFIGWIDGET_H + +#include + +class SmbConfConfigWidget : public QWidget { +Q_OBJECT +public: + SmbConfConfigWidget(QWidget*); + +protected slots: + void btnPressed(); +signals: + void smbConfChoosed(const QString &); +}; + + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/smbpasswdfile.cpp b/filesharing/advanced/kcm_sambaconf/smbpasswdfile.cpp new file mode 100644 index 00000000..594864b9 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/smbpasswdfile.cpp @@ -0,0 +1,229 @@ +/*************************************************************************** + smbpasswdfile.cpp - description + ------------------- + begin : Tue June 6 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "sambafile.h" +#include "smbpasswdfile.h" +#include "passwd.h" + + +QStringList SambaUserList::getUserNames() +{ + QStringList list; + + SambaUser *user; + for ( user = first(); user; user = next() ) + { + list.append(user->name); + } + + return list; +} + + +SmbPasswdFile::SmbPasswdFile() { +} + + +SmbPasswdFile::SmbPasswdFile(const KURL & url) +{ + setUrl(url); +} + +SmbPasswdFile::~SmbPasswdFile() +{ +} + +void SmbPasswdFile::setUrl(const KURL & url) { + _url = url; +} + +/** + * Returns a list of all users stored in + * the smbpasswd file + **/ +SambaUserList SmbPasswdFile::getSambaUserList() +{ + QFile f(_url.path()); + + SambaUserList list; + + if ( f.open(IO_ReadOnly) ) + { + QTextStream t( &f ); + QString s; + while ( !t.eof() ) + { + s = t.readLine().stripWhiteSpace(); + + // Ignore comments + if (s.left(1)=="#") + continue; + + QStringList l = QStringList::split(":",s); + + SambaUser* user = new SambaUser(l[0],l[1].toInt()); + user->gid = getUserGID(l[0]); + user->isUserAccount = l[4].contains('U'); + user->hasNoPassword = l[4].contains('N');; + user->isDisabled = l[4].contains('D');; + user->isWorkstationTrustAccount = l[4].contains('W');; + list.append(user); + } + f.close(); + } + + return list; +} + +bool SmbPasswdFile::executeSmbpasswd(const QStringList & args) { + KProcess p; + p << "smbpasswd" << args; + + connect( &p, SIGNAL(receivedStdout(KProcess*,char*,int)), + this, SLOT(smbpasswdStdOutReceived(KProcess*,char*,int))); + + _smbpasswdOutput = ""; + + bool result = p.start(KProcess::Block,KProcess::Stdout); + + if (result) + { + kdDebug(5009) << _smbpasswdOutput << endl; + } + + return result; +} + +/** + * Tries to add the passed user to the smbpasswd file + * returns true if successful otherwise false + **/ +bool SmbPasswdFile::addUser(const SambaUser & user,const QString & password) +{ + KProcess p; + p << "smbpasswd" << "-a" << user.name; + + p << password; + + connect( &p, SIGNAL(receivedStdout(KProcess*,char*,int)), + this, SLOT(smbpasswdStdOutReceived(KProcess*,char*,int))); + + _smbpasswdOutput = ""; + + bool result = p.start(KProcess::Block,KProcess::Stdout); + + if (result) + { + kdDebug(5009) << _smbpasswdOutput << endl; + } + + return result; +} + +/** + * Tries to remove the passed user from the smbpasswd file + * returns true if successful otherwise false + **/ +bool SmbPasswdFile::removeUser(const SambaUser & user) +{ + QStringList l; + l << "-x" << user.name; + return executeSmbpasswd(l); +} + +bool SmbPasswdFile::changePassword(const SambaUser & user, const QString & newPassword) +{ + return addUser(user,newPassword); +} + + +void SmbPasswdFile::smbpasswdStdOutReceived(KProcess *, char *buffer, int buflen) +{ + _smbpasswdOutput+=QString::fromLatin1(buffer,buflen); +} + + +/** + * Returns the Url of the smbpasswd file + * specified in the [global] section of + * the smb.conf file. + * If there is no entry in the [global] section + * it tries to guess the location. + **/ +KURL SmbPasswdFile::getUrlFromSambaFile(const SambaFile * /*file*/) +{ + kdWarning() << "SmbPasswdFile::getUrlFromSambaFile unimplemeneted!" << endl; + return KURL(""); +} + +bool SmbPasswdFile::enableUser(const SambaUser & user) { + QStringList l; + l << "-e" << user.name; + return executeSmbpasswd(l); +} + +bool SmbPasswdFile::disableUser(const SambaUser & user) { + QStringList l; + l << "-d" << user.name; + return executeSmbpasswd(l); +} + +bool SmbPasswdFile::setNoPassword(const SambaUser & user) { + QStringList l; + l << "-n" << user.name; + return executeSmbpasswd(l); +} + +bool SmbPasswdFile::setMachineTrustAccount(const SambaUser & user) { + QStringList l; + l << "-m" << user.name; + return executeSmbpasswd(l); +} + +bool SmbPasswdFile::joinADomain(const QString & domain, const QString & server, + const QString & user, const QString & password) { + QStringList l; + l << "-j" << domain; + l << "-r" << server; + l << "-U" << user << "%" << password; + return executeSmbpasswd(l); +} + + +#include "smbpasswdfile.moc" diff --git a/filesharing/advanced/kcm_sambaconf/smbpasswdfile.h b/filesharing/advanced/kcm_sambaconf/smbpasswdfile.h new file mode 100644 index 00000000..6a15c8c0 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/smbpasswdfile.h @@ -0,0 +1,141 @@ +/*************************************************************************** + smbpasswdfile.h - description + ------------------- + begin : Tue June 6 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#ifndef SMBPASSWDFILE_H +#define SMBPASSWDFILE_H + + +/** + *@author Jan Schäfer + */ + +#include +#include + +class SambaFile; +class KProcess; + +/** + * Simple class to store a Samba User + **/ +class SambaUser +{ +public: + SambaUser(const QString & aName = QString::null, int anUid = -1) {name = aName; uid = anUid;}; + + QString name; + int uid; + int gid; + bool isUserAccount; + bool hasNoPassword; + bool isDisabled; + bool isWorkstationTrustAccount; +}; + +/** + * A QPtrList of SambaUser + **/ +class SambaUserList : public QPtrList +{ +public: + QStringList getUserNames(); +}; + +/** + * This class represents the SAMBA smbpasswd file. + * It provides : + * - getting a list of all SAMBA users + * - adding a new user -> uses smbpasswd program + * - removing an existing user -> uses smbpasswd program + **/ +class SmbPasswdFile : public QObject +{ +Q_OBJECT +public: + SmbPasswdFile(); + SmbPasswdFile(const KURL &); + ~SmbPasswdFile(); + + void setUrl(const KURL &); + + /** + * Returns a list of all users stored in + * the smbpasswd file + **/ + SambaUserList getSambaUserList(); + + /** + * Calls smbpasswd with the given arguments + **/ + bool executeSmbpasswd(const QStringList & args); + + /** + * Tries to add the passed user to the smbpasswd file + * returns true if successful otherwise false + **/ + bool addUser(const SambaUser &, const QString & password); + + /** + * Tries to remove the passed user from the smbpasswd file + * returns true if successful otherwise false + **/ + bool removeUser(const SambaUser &); + + /** + * Tries to change the password of the passed user + * if it fails returns false otherwise true + **/ + bool changePassword(const SambaUser &, const QString & password); + + bool enableUser(const SambaUser &); + + bool disableUser(const SambaUser &); + + bool setNoPassword(const SambaUser &); + + bool setMachineTrustAccount(const SambaUser &); + + bool joinADomain(const QString &, const QString &, const QString &, const QString &); + + /** + * Returns the Url of the smbpasswd file + * specified in the [global] section of + * the smb.conf file. + * If there is no entry in the [global] section + * it tries to guess the location. + **/ + static KURL getUrlFromSambaFile(const SambaFile *file); +protected: + KURL _url; + QString _smbpasswdOutput; + +protected slots: + void smbpasswdStdOutReceived(KProcess*,char*,int); +}; + +#endif diff --git a/filesharing/advanced/kcm_sambaconf/socketoptionsdlg.ui b/filesharing/advanced/kcm_sambaconf/socketoptionsdlg.ui new file mode 100644 index 00000000..acfaf91f --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/socketoptionsdlg.ui @@ -0,0 +1,332 @@ + +SocketOptionsDlg + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + + + + SocketOptionsDlg + + + + 0 + 0 + 457 + 240 + + + + Socket Options + + + true + + + + unnamed + + + 11 + + + 6 + + + + Layout106 + + + + unnamed + + + 0 + + + 6 + + + + SO_SNDLOWATChk + + + SO_SNDLOWAT: + + + + + IPTOS_THROUGHPUTChk + + + IPTOS_THROUGHPUT + + + + + SO_SNDBUFChk + + + SO_SNDBUF: + + + + + SO_KEEPALIVEChk + + + SO_KEEPALIVE + + + + + SO_RCVBUFChk + + + SO_RCVBUF: + + + + + SO_SNDBUFSpin + + + false + + + 100000 + + + + + SO_RCVLOWATSpin + + + false + + + 100000 + + + + + SO_BROADCASTChk + + + SO_BROADCAST + + + + + IPTOS_LOWDELAYChk + + + IPTOS_LOWDELAY + + + + + TCP_NODELAYChk + + + TCP_NODELAY + + + + + SO_RCVLOWATChk + + + SO_RCVLOWAT: + + + + + SO_RCVBUFSpin + + + false + + + 100000 + + + + + SO_SNDLOWATSpin + + + false + + + 100000 + + + + + SO_REUSEADDRChk + + + SO_REUSEADDR + + + + + + + Frame8 + + + HLine + + + Sunken + + + + + Layout1 + + + + unnamed + + + 0 + + + 6 + + + + buttonHelp + + + &Help + + + F1 + + + true + + + + + Horizontal Spacing2 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + buttonOk + + + &OK + + + true + + + true + + + + + buttonCancel + + + &Cancel + + + true + + + + + + + + + buttonOk + clicked() + SocketOptionsDlg + accept() + + + buttonCancel + clicked() + SocketOptionsDlg + reject() + + + SO_RCVLOWATChk + toggled(bool) + SO_RCVLOWATSpin + setEnabled(bool) + + + SO_SNDLOWATChk + toggled(bool) + SO_SNDLOWATSpin + setEnabled(bool) + + + SO_RCVBUFChk + toggled(bool) + SO_RCVBUFSpin + setEnabled(bool) + + + SO_SNDBUFChk + toggled(bool) + SO_SNDBUFSpin + setEnabled(bool) + + + + assert.h + sambashare.h + socketoptionsdlg.ui.h + + + class SambaShare; + + + SambaShare* _share; + + + setShare( SambaShare * share ) + getBoolValue( const QString & str, const QString & name ) + getIntValue( const QString & str, const QString & name ) + + + diff --git a/filesharing/advanced/kcm_sambaconf/socketoptionsdlg.ui.h b/filesharing/advanced/kcm_sambaconf/socketoptionsdlg.ui.h new file mode 100644 index 00000000..bc8c5d49 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/socketoptionsdlg.ui.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename slots use Qt Designer which will +** update this file, preserving your code. Create an init() slot in place of +** a constructor, and a destroy() slot in place of a destructor. +*****************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +void SocketOptionsDlg::setShare( SambaShare * share ) +{ + assert(share); + + _share = share; + + QString s = _share->getValue("socket options"); + + s = s.simplifyWhiteSpace(); + + // The string s has now the form e.g. : + // "OPTION1=1 OPTION2=0 OPTION3=2234 OPTION4" + +SO_KEEPALIVEChk->setChecked(getBoolValue( s, "SO_KEEPALIVE") ); +SO_REUSEADDRChk->setChecked( getBoolValue( s, "SO_REUSEADDR") ); +SO_BROADCASTChk->setChecked( getBoolValue( s, "SO_BROADCAST") ); +TCP_NODELAYChk->setChecked( getBoolValue( s, "TCP_NODELAY") ); +IPTOS_LOWDELAYChk->setChecked( getBoolValue( s, "IPTOS_LOWDELAY") ); +IPTOS_THROUGHPUTChk->setChecked( getBoolValue( s, "IPTOS_THROUGHPUT") ); +SO_SNDBUFChk->setChecked( getBoolValue( s, "SO_SNDBUF") ); +SO_RCVBUFChk->setChecked( getBoolValue( s, "SO_RCVBUF") ); +SO_SNDLOWATChk->setChecked( getBoolValue( s, "SO_SNDLOWAT") ); +SO_RCVLOWATChk->setChecked( getBoolValue( s, "SO_RCVLOWAT") ); + +SO_SNDBUFSpin->setValue( getIntValue( s, "SO_SNDBUF") ); +SO_RCVBUFSpin->setValue( getIntValue( s, "SO_RCVBUF") ); +SO_SNDLOWATSpin->setValue( getIntValue( s, "SO_SNDLOWAT") ); +SO_RCVLOWATSpin->setValue( getIntValue( s, "SO_RCVLOWAT") ); + +} + + + +bool SocketOptionsDlg::getBoolValue( const QString & str, const QString & name ) +{ + QString s = str; + int i = s.find(name ,0,false); + + if (i > -1) + { + s = s.remove(0,i+1+QString(name).length()); + if ( s.startsWith("=") ) + { + s = s.remove(0,1); + if ( s.startsWith("0")) + return false; + else + return true; + } + else + return true; + } + + return false; +} + +int SocketOptionsDlg::getIntValue( const QString & str, const QString & name ) +{ + QString s = str; + int i = s.find(name ,0,false); + + if (i > -1) + { + s = s.remove(0,i+1+QString(name).length()); + if ( s.startsWith("=") ) + { + s = s.remove(0,1); + + i = s.find(" "); + if (i < 0) + i = s.length(); + else + i++; + + s = s.left( i ); + + return s.toInt(); + } + else + return 0; + } + + return 0; +} diff --git a/filesharing/advanced/kcm_sambaconf/userselectdlg.ui b/filesharing/advanced/kcm_sambaconf/userselectdlg.ui new file mode 100644 index 00000000..8fa38372 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/userselectdlg.ui @@ -0,0 +1,301 @@ + +UserSelectDlg + + + UserSelectDlg + + + + 0 + 0 + 485 + 269 + + + + Select Users + + + true + + + + unnamed + + + 11 + + + 6 + + + + groupBox87 + + + Select &Users + + + + unnamed + + + 11 + + + 6 + + + + + Name + + + true + + + true + + + + + UID + + + true + + + true + + + + + GID + + + true + + + true + + + + userListView + + + Extended + + + + + + + accessBtnGrp + + + + 0 + 5 + 0 + 0 + + + + Acc&ess + + + + unnamed + + + 11 + + + 6 + + + + defaultRadio + + + &Default + + + 8388676 + + + true + + + + + readRadio + + + &Read access + + + 8388690 + + + + + writeRadio + + + &Write access + + + 8388695 + + + + + adminRadio + + + &Admin access + + + 8388673 + + + + + noAccessRadio + + + &No access at all + + + 8388686 + + + + + + + spacer90 + + + Vertical + + + Expanding + + + + 20 + 50 + + + + + + frame16 + + + HLine + + + Raised + + + + + Layout1 + + + + unnamed + + + 0 + + + 6 + + + + Horizontal Spacing2 + + + Horizontal + + + Expanding + + + + 285 + 20 + + + + + + buttonOk + + + &OK + + + 0 + + + true + + + true + + + + + buttonCancel + + + &Cancel + + + 0 + + + true + + + + + + + + + buttonOk + clicked() + UserSelectDlg + accept() + + + buttonCancel + clicked() + UserSelectDlg + reject() + + + + sambashare.h + smbpasswdfile.h + kurl.h + userselectdlg.ui.h + + + QStringList selectedUsers; + int access; + + + init( const QStringList & specifiedUsers, SambaShare * share ) + accept() + getSelectedUsers() + getAccess() + + + diff --git a/filesharing/advanced/kcm_sambaconf/userselectdlg.ui.h b/filesharing/advanced/kcm_sambaconf/userselectdlg.ui.h new file mode 100644 index 00000000..af323fe6 --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/userselectdlg.ui.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename functions or slots use +** Qt Designer which will update this file, preserving your code. Create an +** init() function in place of a constructor, and a destroy() function in +** place of a destructor. +*****************************************************************************/ + + +void UserSelectDlg::init(const QStringList & specifiedUsers, SambaShare* share) +{ + SmbPasswdFile passwd( KURL(share->getValue("smb passwd file",true,true)) ); + SambaUserList sambaList = passwd.getSambaUserList(); + + for (SambaUser * user = sambaList.first(); user; user = sambaList.next() ) + { + if (! specifiedUsers.contains(user->name)) + new QListViewItem(userListView, user->name, QString::number(user->uid), QString::number(user->gid)); + } + +} + + +void UserSelectDlg::accept() +{ + QListViewItemIterator it( userListView); + + for ( ; it.current(); ++it ) { + if ( it.current()->isSelected() ) + selectedUsers << it.current()->text(0); + } + + access = accessBtnGrp->id(accessBtnGrp->selected()); + + QDialog::accept(); + +} + + +QStringList UserSelectDlg::getSelectedUsers() +{ + return selectedUsers; +} + + +int UserSelectDlg::getAccess() +{ + return access; +} diff --git a/filesharing/advanced/kcm_sambaconf/usertab.ui b/filesharing/advanced/kcm_sambaconf/usertab.ui new file mode 100644 index 00000000..43e556dc --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/usertab.ui @@ -0,0 +1,352 @@ + +UserTab + + + UserTab + + + + 0 + 0 + 548 + 396 + + + + Users + + + + unnamed + + + + groupBox53 + + + All U&nspecified Users + + + + unnamed + + + + + Allow + + + + + Reject + + + + allUnspecifiedUsersCombo + + + + + + + groupBox77 + + + + 7 + 7 + 0 + 0 + + + + Spec&ified Users + + + + unnamed + + + + + Name + + + + + UID + + + + + GID + + + + + Access Rights + + + + userTable + + + + 7 + 7 + 100 + 50 + + + + 0 + + + 4 + + + false + + + true + + + MultiRow + + + FollowStyle + + + + + addUserBtn + + + A&dd User... + + + + + expertBtn + + + E&xpert + + + + + addGroupBtn + + + Add &Group... + + + + + removeSelectedBtn + + + Remo&ve Selected + + + + + spacer88 + + + Vertical + + + Expanding + + + + 20 + 100 + + + + + + + + groupBox35 + + + + 1 + 1 + 0 + 0 + + + + All Users Should be Forced to the Follo&wing User/Group + + + + unnamed + + + + TextLabel12 + + + + 1 + 5 + 0 + 0 + + + + Forc&e user: + + + forceUserCombo + + + + + forceUserCombo + + + + 7 + 0 + 0 + 0 + + + + + + TextLabel13 + + + + 1 + 5 + 0 + 0 + + + + Fo&rce group: + + + forceGroupCombo + + + + + forceGroupCombo + + + + 7 + 0 + 0 + 0 + + + + + + + + + + addUserBtn + clicked() + UserTab + addUserBtnClicked() + + + addGroupBtn + clicked() + UserTab + addGroupBtnClicked() + + + removeSelectedBtn + clicked() + UserTab + removeSelectedBtnClicked() + + + expertBtn + clicked() + UserTab + expertBtnClicked() + + + allUnspecifiedUsersCombo + textChanged(const QString&) + UserTab + changedSlot() + + + addUserBtn + clicked() + UserTab + changedSlot() + + + addGroupBtn + clicked() + UserTab + changedSlot() + + + removeSelectedBtn + clicked() + UserTab + changedSlot() + + + expertBtn + clicked() + UserTab + changedSlot() + + + forceUserCombo + textChanged(const QString&) + UserTab + changedSlot() + + + forceGroupCombo + textChanged(const QString&) + UserTab + changedSlot() + + + + allUnspecifiedUsersCombo + userTable + addUserBtn + addGroupBtn + removeSelectedBtn + expertBtn + forceUserCombo + forceGroupCombo + + + userselectdlg.h + groupselectdlg.h + usertab.ui.h + + + changed() + + + addUserBtnClicked() + removeSelectedBtnClicked() + addGroupBtnClicked() + expertBtnClicked() + changedSlot() + + + diff --git a/filesharing/advanced/kcm_sambaconf/usertab.ui.h b/filesharing/advanced/kcm_sambaconf/usertab.ui.h new file mode 100644 index 00000000..088f468a --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/usertab.ui.h @@ -0,0 +1,39 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename functions or slots use +** Qt Designer which will update this file, preserving your code. Create an +** init() function in place of a constructor, and a destroy() function in +** place of a destructor. +*****************************************************************************/ + + +void UserTab::addUserBtnClicked() +{ + +} + + +void UserTab::removeSelectedBtnClicked() +{ + +} + + +void UserTab::addGroupBtnClicked() +{ + +} + + + +void UserTab::expertBtnClicked() +{ + +} + + +void UserTab::changedSlot() +{ + emit changed(); +} diff --git a/filesharing/advanced/kcm_sambaconf/usertabimpl.cpp b/filesharing/advanced/kcm_sambaconf/usertabimpl.cpp new file mode 100644 index 00000000..b6d2b0da --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/usertabimpl.cpp @@ -0,0 +1,418 @@ +/*************************************************************************** + usertabimpl.cpp - description + ------------------- + begin : Mon Jul 15 2002 + copyright : (C) 2002 by Jan Sch�er + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#include // for getuid + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "usertabimpl.h" +#include "sambashare.h" +#include "smbpasswdfile.h" +#include "passwd.h" +#include "groupselectdlg.h" +#include "userselectdlg.h" +#include "expertuserdlg.h" + +#include + +/** + * @pre share is not null + * @post _share = share + */ +UserTabImpl::UserTabImpl(QWidget* parent, SambaShare* share) + : UserTab(parent) +{ + if (share == 0L) { + kdWarning() << "WARNING: UserTabImpl constructor: share parameter is null!" << endl; + return; + } + + _share = share; + userTable->setLeftMargin(0); + // userTable->setColumnStretchable ( 3, true ); + +} + +UserTabImpl::~UserTabImpl() +{ +} + +void UserTabImpl::load() +{ + if (_share == 0L) + return; + + loadForceCombos(); + loadUsers(_share->getValue("valid users"), + _share->getValue("read list"), + _share->getValue("write list"), + _share->getValue("admin users"), + _share->getValue("invalid users")); +} + +void UserTabImpl::loadUsers(const QString & validUsersStr, + const QString & readListStr, + const QString & writeListStr, + const QString & adminUsersStr, + const QString & invalidUsersStr) +{ + userTable->setNumRows(0); + QStringList validUsers = QStringList::split(QRegExp("[,\\s]+"),validUsersStr); + QStringList readList = QStringList::split(QRegExp("[,\\s]+"),readListStr); + QStringList writeList = QStringList::split(QRegExp("[,\\s]+"),writeListStr); + QStringList adminUsers = QStringList::split(QRegExp("[,\\s]+"),adminUsersStr); + QStringList invalidUsers = QStringList::split(QRegExp("[,\\s]+"),invalidUsersStr); + + // if the valid users list contains no entries + // then all users are allowed, except those that are + // in the invalid list + if (validUsers.empty()) + allUnspecifiedUsersCombo->setCurrentItem(0); + else + allUnspecifiedUsersCombo->setCurrentItem(1); + + removeDuplicates(validUsers,readList,writeList,adminUsers,invalidUsers); + + addListToUserTable(adminUsers,3); + addListToUserTable(writeList,2); + addListToUserTable(readList,1); + addListToUserTable(validUsers,0); + addListToUserTable(invalidUsers,4); + +} + + +void UserTabImpl::loadForceCombos() { + forceUserCombo->insertItem(""); + forceGroupCombo->insertItem(""); + + QStringList unixGroups = getUnixGroups(); + + forceUserCombo->insertStringList( getUnixUsers() ); + forceGroupCombo->insertStringList( unixGroups ); + + setComboToString(forceUserCombo, _share->getValue("force user")); + setComboToString(forceGroupCombo, _share->getValue("force group")); +} + +/** + * Remove all duplicates of the different list, so that + * all lists are disjunct. + */ +void UserTabImpl::removeDuplicates( QStringList & validUsers, QStringList & readList, + QStringList & writeList, QStringList & adminUsers, + QStringList & invalidUsers) +{ + removeAll(adminUsers, writeList); + + removeAll(writeList, readList); + removeAll(adminUsers, readList); + + removeAll(readList, validUsers); + removeAll(writeList, validUsers); + removeAll(adminUsers, validUsers); + + removeAll(invalidUsers, validUsers); + removeAll(invalidUsers, readList); + removeAll(invalidUsers, writeList); + removeAll(invalidUsers, adminUsers); +} + +/** + * Remove all entries of entryList from the fromList + */ +void UserTabImpl::removeAll(QStringList & entryList, QStringList & fromList) { + for (QStringList::Iterator it = entryList.begin(); it != entryList.end(); ++it) { + fromList.remove((*it)); + } +} + + +void UserTabImpl::addListToUserTable(const QStringList & list, int accessRight) { + + for (QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) { + addUserToUserTable(*it,accessRight); + } +} + +void UserTabImpl::addUserToUserTable(const QString & user, int accessRight) { + int row = userTable->numRows(); + userTable->setNumRows(row+1); + setAllowedUser(row, user); + + QComboTableItem* comboItem = static_cast(userTable->item(row,3)); + comboItem->setCurrentItem(accessRight); +} + +QString UserTabImpl::removeQuotationMarks(const QString & name) { + QString result = name; + if (name.left(1) == "\"") { + result = result.left(result.length()-1); + result = result.right(result.length()-1); + } + return result; +} + +QString UserTabImpl::removeGroupTag(const QString & name) { + QString result = name; + + + if (nameIsGroup(result)) { + result = result.right(result.length()-1); + if (nameIsGroup(result)) + result = result.right(result.length()-1); + } + + return result; +} + + +bool UserTabImpl::nameIsGroup(const QString & name) { + QString quoteless = removeQuotationMarks(name); + if (quoteless.left(1) == "@" || + quoteless.left(1) == "+" || + quoteless.left(1) == "&") + return true; + + return false; + +} + +void UserTabImpl::setAllowedUser(int i, const QString & name) +{ + QStringList accessRights; + accessRights << i18n("Default") << i18n("Read only") << i18n("Writeable") << i18n("Admin") << i18n("Reject"); + + QString uid; + QString gid; + + QString name2 = name; + + if (nameIsGroup(name2)) + { + QString name3 = removeGroupTag(name2); + uid = ""; + gid = QString::number(getGroupGID(name3)); + _specifiedGroups << name3; + } + else + { + uid = QString::number(getUserUID(name2)); + gid = QString::number(getUserGID(name2)); + _specifiedUsers << name2; + } + + if (name2.contains(' ')) + name2 = "\""+name2+"\""; + + + QTableItem* item = new QTableItem( userTable,QTableItem::Never, name2 ); + userTable->setItem(i,0,item); + + item = new QTableItem( userTable,QTableItem::Never, uid ); + userTable->setItem(i,1,item); + + item = new QTableItem( userTable,QTableItem::Never, gid ); + userTable->setItem(i,2,item); + + QComboTableItem* comboItem = new QComboTableItem( userTable,accessRights); + userTable->setItem(i,3,comboItem); + +} + +void UserTabImpl::addUserBtnClicked() +{ + if (getuid() == 0) { + UserSelectDlg *dlg = new UserSelectDlg(); + dlg->init(_specifiedUsers,_share); + + QStringList selectedUsers = dlg->getSelectedUsers(); + + if (dlg->exec()) { + for (QStringList::Iterator it = selectedUsers.begin(); it != selectedUsers.end(); ++it) + { + addUserToUserTable(*it,dlg->getAccess()); + } + } + + delete dlg; + } else { + bool ok; + QString name = KInputDialog::getText(i18n("Add User"),i18n("Name:"), + QString::null,&ok ); + + if (ok) + addUserToUserTable(name,0); + } +} + +void UserTabImpl::removeSelectedBtnClicked() +{ + QMemArrayrows; + + int j=0; + + for (int i=0; inumRows(); i++) + { + if (userTable->isRowSelected(i)) + { + if (nameIsGroup(userTable->item(i,0)->text())) { + _specifiedGroups.remove( removeGroupTag(removeQuotationMarks(userTable->item(i,0)->text()))); + } else + _specifiedUsers.remove(userTable->item(i,0)->text()); + + rows.resize(j+1); + rows[j] = i; + j++; + } + } + + userTable->removeRows(rows); +} + + +void UserTabImpl::addGroupBtnClicked() +{ + GroupSelectDlg *dlg = new GroupSelectDlg(); + dlg->init(_specifiedGroups); + QStringList selectedGroups = dlg->getSelectedGroups(); + if (dlg->exec()) { + for (QStringList::Iterator it = selectedGroups.begin(); it != selectedGroups.end(); ++it) + { + kdDebug(5009) << "GroupKind: " << dlg->getGroupKind() << endl; + QString name = dlg->getGroupKind() + (*it); + addUserToUserTable(name,dlg->getAccess()); + } + } + + delete dlg; +} + +void UserTabImpl::expertBtnClicked() { + ExpertUserDlg *dlg = new ExpertUserDlg(); + QString validUsersStr; + QString readListStr; + QString writeListStr; + QString adminUsersStr; + QString invalidUsersStr; + + saveUsers(validUsersStr,readListStr,writeListStr,adminUsersStr,invalidUsersStr); + + dlg->validUsersEdit->setText(validUsersStr); + dlg->readListEdit->setText(readListStr); + dlg->writeListEdit->setText(writeListStr); + dlg->adminUsersEdit->setText(adminUsersStr); + dlg->invalidUsersEdit->setText(invalidUsersStr); + + if (dlg->exec()) { + loadUsers(dlg->validUsersEdit->text(), + dlg->readListEdit->text(), + dlg->writeListEdit->text(), + dlg->adminUsersEdit->text(), + dlg->invalidUsersEdit->text()); + + } + + delete dlg; +} + +void UserTabImpl::save() +{ + QString validUsersStr; + QString readListStr; + QString writeListStr; + QString adminUsersStr; + QString invalidUsersStr; + + saveUsers(validUsersStr,readListStr,writeListStr,adminUsersStr,invalidUsersStr); + + _share->setValue("valid users",validUsersStr); + _share->setValue("read list", readListStr); + _share->setValue("write list", writeListStr); + _share->setValue("admin users", adminUsersStr); + _share->setValue("invalid users", invalidUsersStr); + + _share->setValue("force user",forceUserCombo->currentText( ) ); + _share->setValue("force group",forceGroupCombo->currentText( ) ); +} + +void UserTabImpl::saveUsers(QString & validUsersStr, + QString & readListStr, + QString & writeListStr, + QString & adminUsersStr, + QString & invalidUsersStr) +{ + QStringList validUsers; + QStringList writeList; + QStringList readList; + QStringList adminUsers; + QStringList invalidUsers; + + bool allowAllUsers = allUnspecifiedUsersCombo->currentItem() == 0; + + for (int i=0; inumRows(); i++) + { + QTableItem* item = userTable->item(i,0); + QComboTableItem* comboItem = static_cast(userTable->item(i,3)); + + if (! allowAllUsers && comboItem->currentItem() < 4) + validUsers.append(item->text()); + + switch (comboItem->currentItem()) + { + case 0 : break; + case 1 : readList.append(item->text());break; + case 2 : writeList.append(item->text());break; + case 3 : adminUsers.append(item->text());break; + case 4 : invalidUsers.append(item->text());break; + } + } + + validUsersStr = validUsers.join(","); + readListStr = readList.join(","); + writeListStr = writeList.join(","); + adminUsersStr = adminUsers.join(","); + invalidUsersStr = invalidUsers.join(","); +} + + + +#include "usertabimpl.moc" diff --git a/filesharing/advanced/kcm_sambaconf/usertabimpl.h b/filesharing/advanced/kcm_sambaconf/usertabimpl.h new file mode 100644 index 00000000..78d3697a --- /dev/null +++ b/filesharing/advanced/kcm_sambaconf/usertabimpl.h @@ -0,0 +1,95 @@ +/*************************************************************************** + usertabimpl.h - description + ------------------- + begin : Mon Jul 15 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.net + ***************************************************************************/ + +/****************************************************************************** + * * + * This file is part of KSambaPlugin. * + * * + * KSambaPlugin 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. * + * * + * KSambaPlugin 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 KSambaPlugin; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + * * + ******************************************************************************/ + +#ifndef USERTABIMPL_H +#define USERTABIMPL_H + +#include "usertab.h" +#include "common.h" +#include "qstringlist.h" + +/** + *@author Jan Schäfer + */ + +class KListViewItem; +class SambaShare; + +/** + * Implements the usertab.ui interface + * A Widget where you can add SambaUsers to the valid users, rejected users, + * write list, read list and admin list parameters of the smb.conf file + **/ +class UserTabImpl : public UserTab +{ +Q_OBJECT +public: + UserTabImpl(QWidget* parent, SambaShare* share); + ~UserTabImpl(); + + void load(); + void save(); +private: + SambaShare* _share; + QStringList _specifiedUsers; + QStringList _specifiedGroups; + + void setAllowedUser(int, const QString &); + void removeDuplicates( QStringList & validUsers, QStringList & readList, + QStringList & writeList, QStringList & adminUsers, + QStringList & invalidUsers); + + void removeAll(QStringList & entryList, QStringList & fromList); + void addListToUserTable(const QStringList & list, int accessRight); + void loadForceCombos(); + + void loadUsers(const QString & validUsersStr, + const QString & readListStr, + const QString & writeListStr, + const QString & adminUsersStr, + const QString & invalidUsersStr); + + void saveUsers(QString & validUsersStr, + QString & readListStr, + QString & writeListStr, + QString & adminUsersStr, + QString & invalidUsersStr); + + bool nameIsGroup(const QString & name); + QString removeGroupTag(const QString & name); + QString removeQuotationMarks(const QString & name); + void addUserToUserTable(const QString & user, int accessRight); + +protected slots: + virtual void addUserBtnClicked(); + virtual void addGroupBtnClicked(); + virtual void removeSelectedBtnClicked(); + virtual void expertBtnClicked(); +}; + +#endif diff --git a/filesharing/advanced/nfs/Makefile.am b/filesharing/advanced/nfs/Makefile.am new file mode 100644 index 00000000..f9395529 --- /dev/null +++ b/filesharing/advanced/nfs/Makefile.am @@ -0,0 +1,13 @@ +METASOURCES = AUTO +AM_CPPFLAGS= $(all_includes) + +noinst_LTLIBRARIES = libfilesharenfs.la + +libfilesharenfs_la_SOURCES = \ + nfsdialog.cpp \ + nfshostdlg.cpp \ + hostprops.ui \ + nfsdialoggui.ui \ + nfsfile.cpp \ + nfsentry.cpp + diff --git a/filesharing/advanced/nfs/hostprops.ui b/filesharing/advanced/nfs/hostprops.ui new file mode 100644 index 00000000..4ff97e28 --- /dev/null +++ b/filesharing/advanced/nfs/hostprops.ui @@ -0,0 +1,558 @@ + +HostProps + + + HostProps + + + + 0 + 0 + 333 + 332 + + + + + unnamed + + + 0 + + + 6 + + + + propertiesGrp + + + + 5 + 0 + 0 + 0 + + + + + 180 + 0 + + + + Box + + + Sunken + + + Host Properties + + + + unnamed + + + 11 + + + 6 + + + + TextLabel1 + + + &Name/address: + + + nameEdit + + + + + nameEdit + + + LineEditPanel + + + Sunken + + + <b>Name / Address field</b> +<p> +Here you can enter the host name or address.<br> +The host may be specified in a number of ways: +<p> +<i>single host</i> +<p> + This is the most common format. You may specify a host either by an abbreviated name recognized by the resolver, the fully qualified domain name, or an IP address. +</p> + +<i>netgroups</i> +<p> + NIS netgroups may be given as @group. Only the host part of each netgroup members is consider in checking for membership. Empty host parts or those containing a single dash (-) are ignored. +</p> + +<i>wildcards</i> +<p> + Machine names may contain the wildcard characters * and ?. This can be used to make the exports file more compact; for instance, *.cs.foo.edu matches all hosts in the domain cs.foo.edu. However, these wildcard characters do not match the dots in a domain name, so the above pattern does not include hosts such as a.b.cs.foo.edu. +</p> + +<i>IP networks</i> +<p> + You can also export directories to all hosts on an IP (sub-) network simultaneously. This is done by specifying an IP address and netmask pair as address/netmask where the netmask can be specified in dotted-decimal format, or as a contiguous mask length (for example, either `/255.255.252.0' or `/22' appended to the network base address result in identical subnetworks with 10 bits of host). +</p> + + + + + publicChk + + + &Public access + + + <b>Public access</b> +<p> +If you check this, the host address will be a single wildcard , which means public access. +This is just the same as if you would enter a wildcard in the address field. +</p> + + + + + + + GroupBox7 + + + + 5 + 5 + 0 + 0 + + + + Options + + + + unnamed + + + 11 + + + 6 + + + + readOnlyChk + + + &Writable + + + <b>Writable</b> +<p> +Allow both read and write requests on this NFS volume. +</p> +<p> +The default is to disallow any request which changes the filesystem +</p> + + + + + secureChk + + + &Insecure + + + <b>Insecure</b> +<p> +If this option is checked, it is not required that requests originate on an internet port less than IPPORT_RESERVED (1024). +</p> +<p> +If unsure leave it unchecked. +</p> + + + + + syncChk + + + &Sync + + + <b>Sync</b> +<p> +This option requests that all file writes be committed to disk before the write request completes. This is required for complete safety of data in the face of a server crash, but incurs a performance hit. +</p> +<p> +The default is to allow the server to write the data out whenever it is ready. +</p> + + + + + wdelayChk + + + false + + + No w&delay + + + <b>No wdelay</b> +<p> +This option only has effect if sync is also set. The NFS server will normally delay committing a write request to disk slightly if it suspects that another related write request may be in progress or may arrive soon. This allows multiple write requests to be committed to disk with the one operation which can improve performance. If an NFS server received mainly small unrelated requests, this behavior could actually reduce performance, so no wdelay is available to turn it off. </p> + + + + + hideChk + + + No &hide + + + <b>No hide</b> +<p> +This option is based on the option of the same name provided in IRIX NFS. Normally, if a server exports two filesystems one of which is mounted on the other, then the client will have to mount both filesystems explicitly to get access to them. If it just mounts the parent, it will see an empty directory at the place where the other filesystem is mounted. That filesystem is "hidden". +</p> +<p> +Setting the nohide option on a filesystem causes it not to be hidden, and an appropriately authorized client will be able to move from the parent to that filesystem without noticing the change. +</p> +<p> +However, some NFS clients do not cope well with this situation as, for instance, it is then possible for two files in the one apparent filesystem to have the same inode number. +</p> +<p> +The nohide option is currently only effective on single host exports. It does not work reliably with netgroup, subnet, or wildcard exports. +</p> +<p> +This option can be very useful in some situations, but it should be used with due care, and only after confirming that the client system copes with the situation effectively. +</p> + + + + + subtreeChk + + + No su&btree check + + + <b>No subtree check</b> +<p> +This option disables subtree checking, which has mild security implications, but can improve reliability is some circumstances. +</p> +<p> +If a subdirectory of a filesystem is exported, but the whole filesystem is not, then whenever a NFS request arrives, the server must check not only that the accessed file is in the appropriate filesystem (which is easy) but also that it is in the exported tree (which is harder). This check is called the subtree_check. +</p> +<p> +In order to perform this check, the server must include some information about the location of the file in the "filehandle" that is given to the client. This can cause problems with accessing files that are renamed while a client has them open (though in many simple cases it will still work). +</p> +<p> +subtree checking is also used to make sure that files inside directories to which only root has access can only be accessed if the filesystem is exported with no_root_squash (see below), even the file itself allows more general access. +</p> +<p> +As a general guide, a home directory filesystem, which is normally exported at the root and may see lots of file renames, should be exported with subtree checking disabled. A filesystem which is mostly read-only, and at least does not see many file renames (e.g. /usr or /var) and for which subdirectories may be exported, should probably be exported with subtree checks enabled. +</p> + + + + + secureLocksChk + + + Insecure loc&ks + + + <b>Insecure locks</b> +<p> +This option tells the NFS server not to require authentication of locking requests (i.e. requests which use the NLM protocol). Normally the NFS server will require a lock request to hold a credential for a user who has read access to the file. With this flag no access checks will be performed. +</p> +<p> +Early NFS client implementations did not send credentials with lock requests, and many current NFS clients still exist which are based on the old implementations. Use this flag if you find that you can only lock files which are world readable. +</p> + + + + + + + GroupBox3 + + + + 5 + 7 + 0 + 0 + + + + User Mapping + + + AlignAuto + + + + unnamed + + + 11 + + + 6 + + + + allSquashChk + + + All s&quash + + + <b>All squash</b> +<p> +Map all uids and gids to the anonymous user. Useful for NFS-exported public FTP directories, news spool directories, etc. </p> + + + + + rootSquashChk + + + No &root squash + + + <b>No root squash</b> +<p> +Turn of root squashing. This option is mainly useful for diskless clients. +</p> +<i>root squashing</i> +<p> +Map requests from uid/gid 0 to the anonymous uid/gid. Note that this does not apply to any other uids that might be equally sensitive, such as user bin. +</p> + + + + + layout15 + + + + unnamed + + + + TextLabel1_2 + + + Anonym. &UID: + + + anonuidEdit + + + <b>Anonym. UID/GID</b> <p> These options explicitly set the uid and gid of the anonymous account. This option is primarily useful for PC/NFS clients, where you might want all requests appear to be from one user. </p> + + + + + anonuidEdit + + + + 5 + 0 + 100 + 0 + + + + + 50 + 0 + + + + FF + + + + + + + layout16 + + + + unnamed + + + + TextLabel2 + + + Anonym. &GID: + + + anongidEdit + + + <b>Anonym. UID/GID</b> <p> These options explicitly set the uid and gid of the anonymous account. This option is primarily useful for PC/NFS clients, where you might want all requests appear to be from one user. </p> + + + + + anongidEdit + + + + 4 + 0 + 0 + 0 + + + + + 50 + 0 + + + + FF + + + + + + + + + spacer3 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + + + publicChk + toggled(bool) + nameEdit + setDisabled(bool) + + + readOnlyChk + clicked() + HostProps + setModified() + + + secureChk + clicked() + HostProps + setModified() + + + syncChk + clicked() + HostProps + setModified() + + + wdelayChk + pressed() + HostProps + setModified() + + + subtreeChk + pressed() + HostProps + setModified() + + + secureLocksChk + pressed() + HostProps + setModified() + + + allSquashChk + pressed() + HostProps + setModified() + + + rootSquashChk + pressed() + HostProps + setModified() + + + nameEdit + textChanged(const QString&) + HostProps + setModified() + + + hideChk + clicked() + HostProps + setModified() + + + syncChk + toggled(bool) + wdelayChk + setEnabled(bool) + + + anonuidEdit + textChanged(const QString&) + HostProps + setModified() + + + anongidEdit + textChanged(const QString&) + HostProps + setModified() + + + + hostprops.ui.h + + + modified() + + + setModified() + + + diff --git a/filesharing/advanced/nfs/hostprops.ui.h b/filesharing/advanced/nfs/hostprops.ui.h new file mode 100644 index 00000000..5d456a22 --- /dev/null +++ b/filesharing/advanced/nfs/hostprops.ui.h @@ -0,0 +1,13 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename slots use Qt Designer which will +** update this file, preserving your code. Create an init() slot in place of +** a constructor, and a destroy() slot in place of a destructor. +*****************************************************************************/ + + +void HostProps::setModified() +{ + emit modified(); +} diff --git a/filesharing/advanced/nfs/nfsdialog.cpp b/filesharing/advanced/nfs/nfsdialog.cpp new file mode 100644 index 00000000..7e88f2be --- /dev/null +++ b/filesharing/advanced/nfs/nfsdialog.cpp @@ -0,0 +1,216 @@ +/* + Copyright (c) 2004 Jan Schaefer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "nfsdialog.h" +#include "nfsentry.h" +#include "nfshostdlg.h" +#include "nfsfile.h" +#include "nfsdialoggui.h" + +NFSDialog::NFSDialog(QWidget * parent, NFSEntry* entry) + : KDialogBase(Plain, i18n("NFS Options"), Ok|Cancel, Ok, parent), + m_nfsEntry(entry), + m_modified(false) +{ + if (m_nfsEntry) + m_workEntry = m_nfsEntry->copy(); + else + kdWarning() << "NFSDialog::NFSDialog: entry is null!" << endl; + + initGUI(); + initSlots(); + initListView(); +} + +NFSDialog::~NFSDialog() +{ + delete m_workEntry; +} + +void NFSDialog::initGUI() { + QWidget* page = plainPage(); + m_gui = new NFSDialogGUI(page); + + QVBoxLayout *layout = new QVBoxLayout( page ); + layout->addWidget( m_gui ); + + KAccel* accel = new KAccel( m_gui->listView ); + accel->insert( "Delete", Qt::Key_Delete, this, SLOT(slotRemoveHost())); +} + +void NFSDialog::initSlots() +{ + connect( m_gui->removeHostBtn, SIGNAL(clicked()), this, SLOT( slotRemoveHost())); + connect( m_gui->addHostBtn, SIGNAL(clicked()), this, SLOT( slotAddHost())); + connect( m_gui->modifyHostBtn, SIGNAL(clicked()), this, SLOT( slotModifyHost())); + connect( m_gui->listView, SIGNAL(doubleClicked(QListViewItem*)), + this, SLOT( slotModifyHost())); + +} + +void NFSDialog::initListView() +{ + if (m_workEntry) { + HostIterator it = m_workEntry->getHosts(); + + NFSHost* host; + while ( (host = it.current()) != 0 ) { + ++it; + createItemFromHost(host); + } + } +} + +QListViewItem* NFSDialog::createItemFromHost(NFSHost* host) +{ + if (!host) + return 0; + + QListViewItem* item = new QListViewItem(m_gui->listView); + updateItem(item, host); + return item; +} + +void NFSDialog::updateItem(QListViewItem* item, NFSHost* host) +{ + item->setText(0,host->name); + item->setText(1,host->paramString()); +} + +void NFSDialog::slotAddHost() +{ + NFSHost *host = new NFSHost(); + + // Set some secure parameters + //host->allSquash=true; + host->readonly=true; + + HostList hostList; + hostList.append(host); + + NFSHostDlg *dlg = new NFSHostDlg(this, &hostList, m_workEntry); + dlg->exec(); + + + if (dlg->result()==QDialog::Accepted) { + m_workEntry->addHost(host); + createItemFromHost(host); + setModified(); + } else { + delete host; + } + + delete dlg; +} + +void NFSDialog::slotOk() { + if (m_modified) { + m_nfsEntry->copyFrom(m_workEntry); + } + KDialogBase::slotOk(); +} + +void NFSDialog::slotRemoveHost() +{ + QPtrList items = m_gui->listView->selectedItems(); + if (items.count()==0) + return; + + QListViewItem *item; + for ( item = items.first(); item; item = items.next() ) { + QString name = item->text(0); + m_gui->listView->takeItem(item); + + NFSHost* host = m_workEntry->getHostByName(name); + if (host) { + m_workEntry->removeHost(host); + } else { + kdWarning() << "NFSDialog::slotRemoveHost: no host " + << name << " << found!" << endl; + } + + } + + m_gui->modifyHostBtn->setDisabled(true); + m_gui->removeHostBtn->setDisabled(true); + setModified(); +} + +void NFSDialog::slotModifyHost() +{ + QPtrList items = m_gui->listView->selectedItems(); + if (items.count()==0) + return; + + HostList hostList; + + QListViewItem *item; + for ( item = items.first(); item; item = items.next() ) { + + NFSHost* host = m_workEntry->getHostByName(item->text(0)); + if (host) + hostList.append(host); + else + kdWarning() << "NFSDialog::slogModifyHost: host " + << item->text(0) << " is null!" << endl; + } + + NFSHostDlg *dlg = new NFSHostDlg(this, &hostList, m_workEntry); + if (dlg->exec() == QDialog::Accepted && + dlg->isModified()) + { + setModified(); + } + + delete dlg; + + NFSHost* host = hostList.first(); + for ( item = items.first(); item; item = items.next() ) { + if (item && host) + updateItem( item,host); + host = hostList.next(); + } +} + +void NFSDialog::setModified() +{ + m_modified = true; +} + +bool NFSDialog::modified() { + return m_modified; +} + +#include "nfsdialog.moc" + diff --git a/filesharing/advanced/nfs/nfsdialog.h b/filesharing/advanced/nfs/nfsdialog.h new file mode 100644 index 00000000..f6128230 --- /dev/null +++ b/filesharing/advanced/nfs/nfsdialog.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2004 Jan Schaefer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef NFSDIALOG_H +#define NFSDIALOG_H + +#include + +class NFSEntry; +class NFSHost; +class QListViewItem; +class NFSFile; +class NFSDialogGUI; + +class NFSDialog : public KDialogBase +{ +Q_OBJECT +public: + NFSDialog(QWidget * parent, NFSEntry* entry); + ~NFSDialog(); + bool modified(); +protected: + NFSEntry * m_nfsEntry; + NFSEntry * m_workEntry; + NFSDialogGUI* m_gui; + + bool m_modified; + QListViewItem* createItemFromHost(NFSHost* host); + void updateItem(QListViewItem* item, NFSHost* host); + void initGUI(); + void initListView(); + void initSlots(); + void initWorkEntry(); +protected slots: + void slotAddHost(); + void slotRemoveHost(); + void slotModifyHost(); + virtual void slotOk(); + void setModified(); +}; + +#endif diff --git a/filesharing/advanced/nfs/nfsdialoggui.ui b/filesharing/advanced/nfs/nfsdialoggui.ui new file mode 100644 index 00000000..002ae30a --- /dev/null +++ b/filesharing/advanced/nfs/nfsdialoggui.ui @@ -0,0 +1,156 @@ + +NFSDialogGUI + + + NFSDialogGUI + + + + 0 + 0 + 466 + 338 + + + + + unnamed + + + 0 + + + 6 + + + + groupBox + + + true + + + Allowed &Hosts + + + A list of allowed hosts + + + Here you can see a list of hosts which are allowed to access this directory via NFS. +The first column shows the name or address of the host, the second column shows the access parameters. The name '*' donates public access. + + + + unnamed + + + 11 + + + 6 + + + + addHostBtn + + + &Add Host... + + + + + modifyHostBtn + + + false + + + Mo&dify Host... + + + + + removeHostBtn + + + false + + + &Remove Host + + + + + spacer3 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + + Name/Address + + + true + + + true + + + + + Parameters + + + true + + + true + + + + listView + + + Extended + + + true + + + + + + + + + listView + selectionChanged() + NFSDialogGUI + listView_selectionChanged() + + + + addHostBtn + modifyHostBtn + removeHostBtn + + + nfsdialoggui.ui.h + + + listView_selectionChanged() + + + diff --git a/filesharing/advanced/nfs/nfsdialoggui.ui.h b/filesharing/advanced/nfs/nfsdialoggui.ui.h new file mode 100644 index 00000000..85894340 --- /dev/null +++ b/filesharing/advanced/nfs/nfsdialoggui.ui.h @@ -0,0 +1,18 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename slots use Qt Designer which will +** update this file, preserving your code. Create an init() slot in place of +** a constructor, and a destroy() slot in place of a destructor. +*****************************************************************************/ + + + + + +void NFSDialogGUI::listView_selectionChanged() +{ + bool empty = listView->selectedItems().isEmpty(); + modifyHostBtn->setDisabled(empty); + removeHostBtn->setDisabled(empty); +} diff --git a/filesharing/advanced/nfs/nfsentry.cpp b/filesharing/advanced/nfs/nfsentry.cpp new file mode 100644 index 00000000..4144cc7c --- /dev/null +++ b/filesharing/advanced/nfs/nfsentry.cpp @@ -0,0 +1,381 @@ +/* + Copyright (c) 2004 Jan Schaefer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include + +#include "nfsentry.h" + +NFSHost::NFSHost(const QString & hostString) +{ + readonly = true; + + QString s = hostString; + + int l = s.find('('); + int r = s.find(')'); + + initParams(); + + // get hostname + if (l>=0) + name = s.left(l); + else + name = s; + + kdDebug(5009) << "NFSHost: name='" << name << "'" << endl; + + if (l>=0 && r>=0) + { + QString params = s.mid(l+1,r-l-1); + + parseParamsString(params); + } +} + +NFSHost::NFSHost() { + initParams(); + name=""; +} + +NFSHost::~NFSHost() +{ +} + +/** + * Set the parameters to their default values + */ +void NFSHost::initParams() +{ + readonly = true; + sync = false; + secure = true; + wdelay = true; + hide = true; + subtreeCheck = true; + secureLocks = true; + allSquash = false; + rootSquash = true; + + anonuid = 65534; + anongid = 65534; +} + + +void NFSHost::parseParamsString(const QString & s) +{ + + if (s.isEmpty()) + return; + + int i; + + QString rest = s; + QString p; + + do + { + i = rest.find(",",0); + + if (i==-1) + p = rest; + else + { + p = rest.left( i ); + rest = rest.mid(i+1); + } + + setParam(p); + } + while (i>-1); + +} + +QString NFSHost::paramString() const +{ + QString s; + + if (!readonly) s+="rw,"; + if (!rootSquash) s+="no_root_squash,"; + if (!secure) s+="insecure,"; + if (!secureLocks) s+="insecure_locks,"; + if (!subtreeCheck) s+="no_subtree_check,"; + if (sync) + s+="sync,"; + else + s+="async,"; + + if (!wdelay) s+="wdelay,"; + if (allSquash) s+="all_squash,"; + if (!hide) s+="nohide,"; + + if (anongid!=65534) + s+=QString("anongid=%1,").arg(anongid); + + if (anonuid!=65534) + s+=QString("anonuid=%1,").arg(anonuid); + + // get rid of the last ',' + s.truncate(s.length()-1); + + return s; +} + +QString NFSHost::toString() const +{ + QString s = name; + + s+='('; + s+=paramString(); + s+=')'; + + return s; + +} + +NFSHost* NFSHost::copy() const { + NFSHost* result = new NFSHost(); + + result->name = name; + + result->readonly = readonly; + result->sync = sync; + result->secure = secure; + result->wdelay = wdelay; + result->hide = hide; + result->subtreeCheck = subtreeCheck; + result->secureLocks = secureLocks; + result->allSquash = allSquash; + result->rootSquash = rootSquash; + + result->anonuid = anonuid; + result->anongid = anongid; + + return result; +} + +bool NFSHost::isPublic() const { + return name == "*"; +} + +void NFSHost::setParam(const QString & s) +{ + QString p = s.lower(); + + if (p=="ro") { + readonly = true; + return; } + + if (p=="rw") { + readonly = false; + return; } + + if (p=="sync") { + sync = true; + return; } + + if (p=="async") { + sync = false; + return; } + + if (p=="secure") { + secure = true; + return; } + + if (p=="insecure") { + secure = false; + return; } + + if (p=="wdelay") { + wdelay = true; + return; } + + if (p=="no_wdelay") { + wdelay = false; + return; } + + if (p=="hide") { + hide = true; + return; } + + if (p=="nohide") { + hide = false; + return; } + + if (p=="subtree_check") { + subtreeCheck = true; + return; } + + if (p=="no_subtree_check") { + subtreeCheck = false; + return; } + + if (p=="secure_locks" || + p=="auth_nlm") { + secureLocks = true; + return; } + + if (p=="insecure_locks" || + p=="no_auth_nlm" ) { + secureLocks = true; + return; } + + if (p=="all_squash") { + allSquash = true; + return; } + + if (p=="no_all_squash") { + allSquash = false; + return; } + + if (p=="root_squash") { + rootSquash = true; + return; } + + if (p=="no_root_squash") { + rootSquash = false; + return; } + + int i = p.find("=",0); + + // get anongid or anonuid + if (i>-1) + { + QString name = p.left(i).lower(); + kdDebug(5009) << name << endl; + + QString value = p.mid(i+1); + kdDebug(5009) << value << endl; + + if (name=="anongid") + anongid = value.toInt(); + + if (name=="anonuid") + anonuid = value.toInt(); + } + +} + +NFSEntry::NFSEntry(const QString & path) +{ + _hosts.setAutoDelete(true); + setPath(path); +} + +NFSEntry::~NFSEntry() +{ +} + +void NFSEntry::clear() { + _hosts.clear(); +} + +NFSEntry* NFSEntry::copy() { + NFSEntry* result = new NFSEntry(path()); + result->copyFrom(this); + return result; +} + +void NFSEntry::copyFrom(NFSEntry* entry) { + clear(); + HostIterator it = entry->getHosts(); + + NFSHost* host; + while ( (host = it.current()) != 0 ) { + ++it; + addHost(host->copy()); + } +} + +QString NFSEntry::toString() const +{ + QString s = _path.stripWhiteSpace(); + + if (_path.find(' ') > -1) { + s = '"'+s+'"'; + } + + s += ' '; + + HostIterator it = getHosts(); + + NFSHost* host; + + while ( (host = it.current()) != 0 ) + { + ++it; + s+= host->toString() ; + if (it.current()) + s+= " \\\n\t "; + } + + + return s; +} + +void NFSEntry::addHost(NFSHost * host) +{ + _hosts.append(host); +} + +void NFSEntry::removeHost(NFSHost * host) +{ + _hosts.remove(host); +} + +NFSHost* NFSEntry::getHostByName(const QString & name) const +{ + HostIterator it = getHosts(); + NFSHost* host; + + while ( (host = it.current()) != 0 ) + { + ++it; + + if (host->name==name) + return host; + } + + return 0; +} + +NFSHost* NFSEntry::getPublicHost() const +{ + NFSHost* result = getHostByName("*"); + if (result) + return result; + + return getHostByName(QString::null); +} + + +HostIterator NFSEntry::getHosts() const +{ + return HostIterator(_hosts); +} + +QString NFSEntry::path() const +{ + return _path; +} + +void NFSEntry::setPath(const QString & path) +{ + _path = path; +} + diff --git a/filesharing/advanced/nfs/nfsentry.h b/filesharing/advanced/nfs/nfsentry.h new file mode 100644 index 00000000..6003412d --- /dev/null +++ b/filesharing/advanced/nfs/nfsentry.h @@ -0,0 +1,120 @@ +/* + Copyright (c) 2004 Jan Schaefer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef NFSENTRY_H +#define NFSENTRY_H + + +#include +#include + + +class NFSHost +{ +public: + NFSHost(const QString & hostString); + ~NFSHost(); + NFSHost(); + + bool readonly; + bool sync; + bool secure; + bool wdelay; + bool hide; + bool subtreeCheck; + bool secureLocks; + bool allSquash; + bool rootSquash; + + int anonuid; + int anongid; + + QString name; + + QString paramString() const; + QString toString() const; + bool isPublic() const; + + /** returns a copy of this host */ + NFSHost* copy() const; +protected: + void parseParamsString(const QString &); + void setParam(const QString &); + void initParams(); +}; + + + +typedef QPtrList HostList; +typedef QPtrListIterator HostIterator; + +class NFSLine { +public: + virtual QString toString() const = 0; +}; + +typedef QPtrList NFSLineList; +typedef QPtrListIterator NFSLineIterator; + +class NFSEmptyLine : public NFSLine { +public: + virtual QString toString() const { return QString::fromLatin1("\n"); } + virtual ~NFSEmptyLine() {}; +}; + +class NFSComment : public NFSLine { +public: + NFSComment(const QString & s) { comment = s; } + virtual ~NFSComment() {}; + QString comment; + virtual QString toString() const { return comment; } +}; + +class NFSEntry : public NFSLine { +public: + NFSEntry(const QString & path); + virtual ~NFSEntry(); + + void addHost(NFSHost * host); + void removeHost(NFSHost * host); + NFSHost* getHostByName(const QString & name) const; + NFSHost* getPublicHost() const; + HostIterator getHosts() const; + + /** Creates a deep copy of this entry */ + NFSEntry * copy(); + + /** clears all hosts and makes a deep copy of + * the given entry + */ + void copyFrom(NFSEntry* entry); + + /** removes all hosts */ + void clear(); + QString path() const; + void setPath(const QString &); + + virtual QString toString() const; + +protected: + HostList _hosts; + QString _path; +}; + +#endif diff --git a/filesharing/advanced/nfs/nfsfile.cpp b/filesharing/advanced/nfs/nfsfile.cpp new file mode 100644 index 00000000..229eb983 --- /dev/null +++ b/filesharing/advanced/nfs/nfsfile.cpp @@ -0,0 +1,261 @@ +/* + Copyright (c) 2004 Jan Schaefer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "nfsfile.h" + +NFSFile::NFSFile(const KURL & url, bool readonly) +{ + _lines.setAutoDelete(true); + _entries.setAutoDelete(false); + _url = url; + _readonly = readonly; +} + +NFSFile::~NFSFile() +{ +} + +void NFSFile::addEntry(NFSEntry *entry) +{ + _lines.append(entry); + _entries.append(entry); +} + +void NFSFile::removeEntry(NFSEntry *entry) +{ + _entries.remove(entry); + _lines.remove(entry); +} + +bool NFSFile::hasEntry(NFSEntry *entry) +{ + return 0 < _entries.contains(entry); +} + + +NFSEntry* NFSFile::getEntryByPath(const QString & path) +{ + QString testPath = path.stripWhiteSpace(); + if ( testPath[testPath.length()-1] != '/' ) + testPath += '/'; + + for (NFSEntry* entry = _entries.first(); entry; entry = _entries.next()) + { + if (entry->path()==testPath) + return entry; + } + + return 0L; +} + +bool NFSFile::removeEntryByPath(const QString & path) { + NFSEntry* entry = getEntryByPath(path); + if (!entry) + return false; + + removeEntry(entry); + return true; +} + +EntryIterator NFSFile::getEntries() +{ + return EntryIterator(_entries); +} + + + + +bool NFSFile::load() +{ + QFile f(_url.path()); + + if ( !f.open(IO_ReadOnly) ) { + kdError() << "NFSFile::load: Could not open " << _url.path() << endl; + return false; + } + + _entries.clear(); + _lines.clear(); + + QTextStream s( &f ); + + bool continuedLine = false; // is true if the line before ended with a backslash + QString completeLine; + + while ( !s.eof() ) + { + QString currentLine = s.readLine().stripWhiteSpace(); + + if (continuedLine) { + completeLine += currentLine; + continuedLine = false; + } + else + completeLine = currentLine; + + // is the line continued in the next line ? + if ( completeLine[completeLine.length()-1] == '\\' ) + { + continuedLine = true; + // remove the ending backslash + completeLine.truncate( completeLine.length()-1 ); + continue; + } + + // empty lines + if (completeLine.isEmpty()) { + _lines.append(new NFSEmptyLine()); + continue; + } + + // comments + if ('#' == completeLine[0]) { + _lines.append(new NFSComment(completeLine)); + continue; + } + + QString path; + QString hosts; + + // Handle quotation marks + if ( completeLine[0] == '"' ) { + int i = completeLine.find('"',1); + if (i == -1) { + kdError() << "NFSFile: Parse error: Missing quotation mark: " + << completeLine << endl; + continue; + } + path = completeLine.mid(1,i-1); + hosts = completeLine.mid(i+1); + + } else { // no quotation marks + int i = completeLine.find(' '); + if (i == -1) + i = completeLine.find('\t'); + + if (i == -1) + path = completeLine; + else { + path = completeLine.left(i); + hosts = completeLine.mid(i+1).stripWhiteSpace(); + } + } + + // normalize path + if ( path[path.length()-1] != '/' ) + path += '/'; + + kdDebug(5009) << "KNFSShare: Found path: '" << path << "'" << endl; + NFSEntry *entry = new NFSEntry(path); + QStringList hostList = QStringList::split(' ', hosts); + + if (hostList.isEmpty()) { + NFSHost* host = new NFSHost("*"); + entry->addHost(host); + } else { + QStringList::iterator it; + for ( it = hostList.begin(); it != hostList.end(); ++it ) { + NFSHost* host = new NFSHost((*it).stripWhiteSpace()); + entry->addHost(host); + kdDebug(5009) << "KNFSShare: Found host: " << (*it) << " name='" + << host->name << "'" << endl; + } + } + + kdDebug(5009) << "KNFSShare: Found hosts: " << hosts << "'" << endl; + this->addEntry(entry); + } + + f.close(); + + + return true; + +} + +void NFSFile::saveTo(QTextStream * stream) { + QPtrListIterator it(_lines); + + NFSLine *line; + while ( (line = it.current()) != 0 ) { + ++it; + *stream << line->toString() << endl; + } +} + +bool NFSFile::saveTo(const QString& fileName) { + QFile file(fileName); + if (!file.open(IO_WriteOnly)) + return false; + + QTextStream stream(&file); + saveTo(&stream); + file.close(); + return true; +} + +bool NFSFile::save() +{ + if (QFileInfo(_url.path()).isWritable() ) { + saveTo(_url.path()); + } else + { + + KTempFile tempFile; + saveTo(tempFile.name()); + tempFile.close(); + tempFile.setAutoDelete( true ); + + KProcIO proc; + + QString command = QString("cp %1 %2") + .arg(KProcess::quote( tempFile.name() )) + .arg(KProcess::quote( _url.path() )); + + if (restartNFSServer) + command +=";exportfs -ra"; + + if (!QFileInfo(_url.path()).isWritable() ) + proc<<"kdesu" << "-d" << "-c"< + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef NFSFILE_H +#define NFSFILE_H + + +#include +#include "nfsentry.h" + +typedef QPtrList EntryList; +typedef QPtrListIterator EntryIterator; + +class QTextStream; + +class NFSFile { +public: + NFSFile(const KURL & , bool readonly = true); + ~NFSFile(); + + void addEntry(NFSEntry *); + void removeEntry(NFSEntry *); + bool hasEntry(NFSEntry *); + NFSEntry* getEntryByPath(const QString &); + bool removeEntryByPath(const QString &); + EntryIterator getEntries(); + + bool save(); + bool saveTo(const QString& fileName); + void saveTo(QTextStream * stream); + bool load(); + + bool restartNFSServer; + + +protected: + KURL _url; + bool _readonly; + EntryList _entries; + NFSLineList _lines; +}; + +#endif diff --git a/filesharing/advanced/nfs/nfshostdlg.cpp b/filesharing/advanced/nfs/nfshostdlg.cpp new file mode 100644 index 00000000..073170d6 --- /dev/null +++ b/filesharing/advanced/nfs/nfshostdlg.cpp @@ -0,0 +1,226 @@ +/*************************************************************************** + nfshostdlg.cpp - description + ------------------- + begin : Mon Apr 29 2002 + copyright : (C) 2002 by Jan Sch�er + email : janschaefer@users.sourceforge.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. * + * * + ***************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "nfshostdlg.h" +#include "hostprops.h" +#include "nfsentry.h" + + +NFSHostDlg::NFSHostDlg(QWidget* parent, HostList* hosts, NFSEntry* entry) + : KDialogBase(Plain, i18n("Host Properties"), Ok|Cancel, Ok, parent), + m_hosts(hosts), m_nfsEntry(entry), m_modified(false) +{ + + QWidget* page = plainPage(); + + m_gui = new HostProps(page); + + QVBoxLayout *layout = new QVBoxLayout( page, 0, 6 ); + layout->addWidget( m_gui ); + + connect( m_gui, SIGNAL(modified()), this, SLOT(setModified())); + + init(); + +} + +NFSHostDlg::~NFSHostDlg() +{ +} + +void NFSHostDlg::init() +{ + if (m_hosts->count()==1) { + NFSHost* host = m_hosts->first(); + assert(host); + m_gui->nameEdit->setText(host->name); + m_gui->publicChk->setChecked(host->isPublic()); + + setHostValues(host); + + m_gui->nameEdit->setFocus(); + } else { + m_gui->nameEdit->setDisabled(true); + m_gui->publicChk->setDisabled(true); + + m_gui->readOnlyChk->setTristate(); + m_gui->allSquashChk->setTristate(); + m_gui->rootSquashChk->setTristate(); + m_gui->hideChk->setTristate(); + m_gui->secureChk->setTristate(); + m_gui->secureLocksChk->setTristate(); + m_gui->subtreeChk->setTristate(); + m_gui->syncChk->setTristate(); + m_gui->wdelayChk->setTristate(); + + for (NFSHost* host = m_hosts->first(); host; host = m_hosts->next()) { + setHostValues(host); + } + } +} + +void NFSHostDlg::setHostValues(NFSHost* host) { + setCheckBoxValue(m_gui->readOnlyChk, ! host->readonly); + setCheckBoxValue(m_gui->allSquashChk, host->allSquash); + setCheckBoxValue(m_gui->rootSquashChk, ! host->rootSquash); + setCheckBoxValue(m_gui->hideChk, ! host->hide); + setCheckBoxValue(m_gui->secureChk, ! host->secure); + setCheckBoxValue(m_gui->secureLocksChk, ! host->secureLocks); + setCheckBoxValue(m_gui->subtreeChk, ! host->subtreeCheck); + setCheckBoxValue(m_gui->syncChk, host->sync); + setCheckBoxValue(m_gui->wdelayChk, ! host->wdelay); + + setEditValue(m_gui->anonuidEdit,QString::number(host->anonuid)); + setEditValue(m_gui->anongidEdit,QString::number(host->anongid)); +} + +void NFSHostDlg::setEditValue(QLineEdit* edit, const QString & value) { + if (edit->text().isEmpty()) + return; + + if (edit->text() == "FF") + edit->setText(value); + else + if (edit->text() != value) + edit->setText(QString::null); +} + +void NFSHostDlg::setCheckBoxValue(QCheckBox* chk, bool value) { + if (chk->state() == QButton::NoChange) + return; + + if (chk->isChecked()) { + if (! value) + chk->setNoChange(); + } else { + if (value) + chk->setChecked(true); + } +} + + +void NFSHostDlg::slotOk() +{ + if (m_hosts->count()==1) { + NFSHost* host = m_hosts->first(); + if (! saveName(host)) + return; + + saveValues(host); + } else { + for (NFSHost* host = m_hosts->first(); host; host = m_hosts->next()) { + saveValues(host); + } + } + + KDialogBase::slotOk(); +} + +bool NFSHostDlg::saveName(NFSHost* host) { + if (m_gui->publicChk->isChecked()) { + NFSHost* publicHost = m_nfsEntry->getPublicHost(); + if (publicHost && publicHost != host) { + KMessageBox::sorry(this,i18n("There already exists a public entry."), + i18n("Host Already Exists")); + m_gui->publicChk->setChecked(false); + return false; + } + host->name="*"; + } else { + QString name = m_gui->nameEdit->text().stripWhiteSpace(); + if (name.isEmpty()) { + KMessageBox::sorry(this, + i18n("Please enter a hostname or an IP address.").arg(name), + i18n("No Hostname/IP-Address")); + m_gui->nameEdit->setFocus(); + return false; + } else { + NFSHost* host2 = m_nfsEntry->getHostByName(name); + if (host2 && host2 != host) { + KMessageBox::sorry(this,i18n("The host '%1' already exists.").arg(name), + i18n("Host Already Exists")); + m_gui->nameEdit->setFocus(); + return false; + } + } + host->name=name; + } + + return true; +} + + +void NFSHostDlg::saveValues(NFSHost* host) { + + saveCheckBoxValue(host->readonly,m_gui->readOnlyChk,true); + saveCheckBoxValue(host->allSquash,m_gui->allSquashChk,false); + saveCheckBoxValue(host->rootSquash,m_gui->rootSquashChk,true); + saveCheckBoxValue(host->hide,m_gui->hideChk,true); + saveCheckBoxValue(host->secure,m_gui->secureChk,true); + saveCheckBoxValue(host->secureLocks,m_gui->secureLocksChk,true); + saveCheckBoxValue(host->subtreeCheck,m_gui->subtreeChk,true); + saveCheckBoxValue(host->sync,m_gui->syncChk,false); + saveCheckBoxValue(host->wdelay,m_gui->wdelayChk,true); + + saveEditValue(host->anonuid,m_gui->anonuidEdit); + saveEditValue(host->anongid,m_gui->anongidEdit); +} + +void NFSHostDlg::saveEditValue(int & value, QLineEdit* edit) { + if ( edit->text().isEmpty()) + return; + + value = edit->text().toInt(); +} + +void NFSHostDlg::saveCheckBoxValue(bool & value, QCheckBox* chk, bool neg) { + if (chk->state() == QButton::NoChange) + return; + + if (neg) + value = ! chk->isChecked(); + else + value = chk->isChecked(); +} + + + +bool NFSHostDlg::isModified() { + return m_modified; +} + + +void NFSHostDlg::setModified() +{ + m_modified = true; +} + +#include "nfshostdlg.moc" + + diff --git a/filesharing/advanced/nfs/nfshostdlg.h b/filesharing/advanced/nfs/nfshostdlg.h new file mode 100644 index 00000000..6580b2bd --- /dev/null +++ b/filesharing/advanced/nfs/nfshostdlg.h @@ -0,0 +1,59 @@ +/*************************************************************************** + nfshostdlg.h - description + ------------------- + begin : Mon Apr 29 2002 + copyright : (C) 2002 by Jan Schäfer + email : janschaefer@users.sourceforge.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. * + * * + ***************************************************************************/ + +#ifndef NFSHOSTDLG_H +#define NFSHOSTDLG_H + +#include +#include "nfsentry.h" + +class NFSHost; +class HostProps; +class NFSEntry; +class QCheckBox; +class QLineEdit; + + +class NFSHostDlg : public KDialogBase +{ +Q_OBJECT +public: + NFSHostDlg(QWidget* parent, HostList* hosts, NFSEntry* entry); + virtual ~NFSHostDlg(); + bool isModified(); +protected: + HostList* m_hosts; + NFSEntry* m_nfsEntry; + HostProps* m_gui; + bool m_modified; + + void init(); +protected slots: + virtual void slotOk(); + void setModified(); + +private: + void setHostValues(NFSHost* host); + void setEditValue(QLineEdit* edit, const QString & value); + void setCheckBoxValue(QCheckBox* chk, bool value); + bool saveName(NFSHost* host); + void saveValues(NFSHost* host); + void saveEditValue(int & value, QLineEdit* edit); + void saveCheckBoxValue(bool & value, QCheckBox* chk, bool neg); +}; + +#endif diff --git a/filesharing/advanced/propsdlgplugin/Makefile.am b/filesharing/advanced/propsdlgplugin/Makefile.am new file mode 100644 index 00000000..7322f0a8 --- /dev/null +++ b/filesharing/advanced/propsdlgplugin/Makefile.am @@ -0,0 +1,25 @@ +METASOURCES = AUTO + + +kde_module_LTLIBRARIES = fileshare_propsdlgplugin.la + +fileshare_propsdlgplugin_la_SOURCES = \ + propsdlgshareplugin.cpp + +fileshare_propsdlgplugin_la_COMPILE_FIRST = propertiespagegui.h + +fileshare_propsdlgplugin_la_LIBADD = \ + libpropsdlgplugin_common.la \ + ../nfs/libfilesharenfs.la \ + ../kcm_sambaconf/libfilesharesamba.la \ + $(LIB_KIO) + +fileshare_propsdlgplugin_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +AM_CPPFLAGS = -I../kcm_sambaconf $(all_includes) + +kde_services_DATA = fileshare_propsdlgplugin.desktop + +# Shared with the 'simple' kcm +noinst_LTLIBRARIES = libpropsdlgplugin_common.la +libpropsdlgplugin_common_la_SOURCES = propertiespage.cpp propertiespagegui.ui diff --git a/filesharing/advanced/propsdlgplugin/fileshare_propsdlgplugin.desktop b/filesharing/advanced/propsdlgplugin/fileshare_propsdlgplugin.desktop new file mode 100644 index 00000000..f6064ed5 --- /dev/null +++ b/filesharing/advanced/propsdlgplugin/fileshare_propsdlgplugin.desktop @@ -0,0 +1,102 @@ +[Desktop Entry] +Type=Service +Icon=fileshare +Name=Fileshare Konqueror Directory Properties Page +Name[bg]=ÐаÑтройки за приÑтавката за браузъра за ÑподелÑна на файлове +Name[bn]=ফাইল ভাগাভাগি কনকরার ডিরেকà§à¦Ÿà¦°à§€ বৈশিষà§à¦Ÿà§à¦¯à¦¾à¦¬à¦²à§€ পাতা +Name[bs]=Stranica sa osobinama dijeljenja direktorija u Konqueroru +Name[ca]=Pàgina de propietats de compartició de fitxers Konqueror +Name[cs]=Stránka vlastností adresáře sdílení Konqueroru +Name[da]=Fileshare Konqueror-mappens side med egenskaber +Name[de]=Dateifreigabe Konqueror Ordner-Eigenschaftenseite +Name[el]=Σελίδα ιδιοτήτων κοινόχÏηστου καταλόγου του Konqueror +Name[eo]=dosierujo-eco-paÄo +Name[es]=Página de propiedades del directorio para compartir archivos de Konqueror +Name[et]=Failijagamise Konquerori kataloogi omaduste lehekülg +Name[eu]=Fitxategiak partekatzeko Konqueror direktorio propietateen orria +Name[fa]=صÙØ­Û€ ویژگیهای Ùهرست راهنمای اشتراک پروندۀ Konqueror +Name[fi]=Konquerorin tiedostojakojen ominaisuussivu +Name[fr]=Page des propriétés d'un dossier de partage de Konqueror +Name[gl]=Páxina coas Propiedades do Directorio de Ficheiros Compartidos de Konqueror +Name[he]=דף מ×פייני ספרית ×§×‘×¦×™× ×ž×©×•×ª×¤×ª של Konqueror +Name[hu]=Fájlmegosztási lap a Konqueror könyvtártulajdonságainál +Name[is]=Skráastjóraeiginleikar Konqueror +Name[it]=Pagina delle proprietà di Konqueror per la condivisione +Name[ja]=Konqueror ã§ãƒ•ã‚¡ã‚¤ãƒ«å…±æœ‰ã™ã‚‹ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®ãƒ—ロパティã®ãƒšãƒ¼ã‚¸ +Name[ka]=Konqueror-ის ფáƒáƒ˜áƒšáƒ—რგáƒáƒ–იáƒáƒ áƒ”ბის თვისებების გვერდი +Name[kk]=Konqueror файлдарын ортақтаÑтыру каталогының қаÑиеттер беті +Name[km]=ទំពáŸážšâ€‹áž›áž€áŸ’ážážŽáŸˆâ€‹ážŸáž˜áŸ’áž”ážáŸ’ážáž·â€‹ážáž Konqueror សម្រាប់​ការ​ចែក​រំលែក +Name[lt]=Bylų dalinimosi Konqueror aplanko nustatymų puslapis +Name[mk]=Страница Ñо параметри за делен именик во Konqueror +Name[nb]=Fildeler – Side i Konquerors mappegenskaper +Name[nds]=Konqueror Ornerfreegaav-Egenschappensiet +Name[ne]=फाइल साà¤à¥‡à¤¦à¤¾à¤°à¥€ कनà¥à¤•à¥à¤µà¥‡à¤°à¤° डाइरेकà¥à¤Ÿà¤°à¥€ विशेषता पृषà¥à¤  +Name[nl]=Configuratiepagina Konqueror bestanden delen +Name[nn]=Side for katalogeigenskapar til fildeling i Konqueror +Name[pa]=ਫਾਇਲ ਸਾਂਠਕੋਨਕਿਊਰੋਰ ਡਾਇਰੈਕਟਰੀ ਵਿਸ਼ੇਸ਼ਤਾ ਸਫ਼ਾ +Name[pl]=Strona współdzielenia we wÅ‚aÅ›ciwoÅ›ciach katalogu w Konquerorze +Name[pt]=Página de Propriedades da Pasta de Partilha do Konqueror +Name[pt_BR]=Página de Propriedades do Diretório de Compartilhamento de Arquivos do Konqueror +Name[ru]=Страница ÑвойÑтв каталога Ð´Ð»Ñ Ð¾Ð±Ñ‰ÐµÐ³Ð¾ доÑтупа +Name[sk]=Nastavenie zdieľania prieÄinkov pre Konqueror +Name[sl]=Stran z lastnostmi za souporabo mape +Name[sr]=Страна Konqueror-а Ñа ÑвојÑтвима директоријума за дељење фајлова +Name[sr@Latn]=Strana Konqueror-a sa svojstvima direktorijuma za deljenje fajlova +Name[sv]=Konquerors fildelningssida med katalogegenskaper +Name[ta]=Fileshare Konqueror அடைவ௠பணà¯à®ªà¯à®•à®³à®¿à®©à¯ பகà¯à®•à®®à¯ +Name[tg]=Саҳифаи ФеҳриÑти ХуÑуÑиÑтҳо оиди Konqueror ИÑтифодабарии Муштараки Файлҳо +Name[tr]=Dosya Paylaşımı Konqueror Dizin Özellikleri Sayfası +Name[uk]=Сторінка влаÑтивоÑтей каталогу Ð´Ð»Ñ Ñпільного доÑтупу +Name[zh_CN]=文件共享 Konqueror 目录属性页 +Name[zh_HK]=檔案分享 Konqueror ç›®éŒ„å±¬æ€§é  +Name[zh_TW]=Konqueror æª”æ¡ˆåˆ†äº«ç›®éŒ„å±¬æ€§é  +Comment=Konqueror properties dialog plugin to share a directory with the local network +Comment[bg]=ÐаÑтройка на приÑтавката за браузъра Konqueror за ÑподелÑна на директории в локалната мрежа +Comment[bn]=সà§à¦¥à¦¾à¦¨à§€à§Ÿ নেটওয়ারà§à¦•à§‡à¦° সঙà§à¦—ে à¦à¦•à¦Ÿà¦¿ ডিরেকà§à¦Ÿà¦°à§€ ভাগাভাগি করতে কনকরার বৈশিষà§à¦Ÿà§à¦¯à¦¾à¦¬à¦²à§€ ডায়ালগ পà§à¦²à¦¾à¦—িন +Comment[bs]=Dodatak za prozor Konqueror postavki za dijeljenje direktorija u lokalnoj mreži +Comment[ca]=Diàleg de propietats Konqueror de l'endollable per compartir un directori amb la xarxa local +Comment[cs]=Modul dialogu vlastností Konqueroru pro sdílení adresářů v lokální síti +Comment[da]=Konqueror egenskaber-dialog plugin til at dele en mappe med det lokale netværk +Comment[de]=Konqueror Eigenschaftendialog-Plugin zur Freigabe eines Ordners in einem lokalen Netzwerk +Comment[el]=ΠÏόσθετος διάλογος Ïυθμίσεων του Konqueror για την κοινή χÏήση ενός καταλόγου με το τοπικό δίκτυο +Comment[en_GB]=Konqueror properties dialogue plugin to share a directory with the local network +Comment[es]=Complemento de diálogo de propiedades de Konqueror para compartir un directorio con la red local +Comment[et]=Konquerori omaduste dialoogi plugin kataloogi jagamiseks kohtvõrgus +Comment[eu]=Konqueror-en propietateen elkarrizketa-koadro plugina direktorio bat sare lokalean partekatzeko +Comment[fa]=ویژگیهای وصلۀ محاورۀ Konqueror برای اشتراک Ùهرست راهنما با شبکۀ محلی +Comment[fi]=Konqueroroin laajennus, jolla voi jakaa kansioita lähiverkossa +Comment[fr]=Module de Konqueror pour le partage d'un dossier sur le réseau local +Comment[gl]=Diálogo de propiedades do plugin de Konqueror para compartir coa rede local +Comment[he]=תוסף מ×פייני דו-שיח של Konqueror כדי לשתף סיפריה ×¢× ×¨×©×ª מקומית +Comment[hu]=Konqueror tulajdonságablak-modul könyvtár megosztásához a helyi hálózaton +Comment[it]=Plugin delle proprietà di Konqueror per condividere una cartella con la rete locale +Comment[ja]=ローカルãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã§ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’共有ã™ã‚‹ Konqueror プロパティダイアログã®ãƒ—ラグイン +Comment[ka]=Konqueror თვისებების დიáƒáƒšáƒáƒ’ის მáƒáƒ“ული ლáƒáƒ™áƒáƒšáƒ£áƒ  ქსელთáƒáƒœ გáƒáƒ¡áƒáƒ–იáƒáƒ áƒ”ბლáƒáƒ“ +Comment[kk]=Каталогты жергілікті желімен ортақтаÑтыратын Konqueror қаÑиеттер диалогының плагин модулі +Comment[km]=ប្រអប់​លក្ážážŽáŸˆâ€‹ážŸáž˜áŸ’áž”ážáŸ’ážáž· Konqueror ដើម្បី​ចែក​រំលែក​ážážâ€‹áž“ៅ​ក្នុង​បណ្ដាញ​មូលដ្ឋាន +Comment[lt]=Konqueror nustatymų dialogo įskiepis, skirtas dalintis aplanku vietiniame tinkle +Comment[mk]=Приклучок Ñо дијалог за ÑвојÑтва во Konqueror за делење на датотеки Ñо локалната мрежа +Comment[nb]=Programtillegg for Konquerors egenskapsdialog for Ã¥ dele en mappe pÃ¥ lokalnettet +Comment[nds]=Moduul för en Konqueror-Egenschappendialoog för't Freegeven vun Ornern in't lokale Nettwark +Comment[ne]=सà¥à¤¥à¤¾à¤¨à¥€à¤¯ सञà¥à¤œà¤¾à¤²à¤¸à¤à¤— डाइरेकà¥à¤Ÿà¤°à¥€ साà¤à¥‡à¤¦à¤¾à¤° गरà¥à¤¨ कनà¥à¤•à¥à¤µà¥‡à¤°à¤° विशेषता संवाद पà¥à¤²à¤—इन +Comment[nl]=Konqueror-plugin met instellingen om bestanden te delen via het lokale netwerk +Comment[nn]=Konqueror-vising av eigenskapar til ein delt katalog i det lokale nettverket +Comment[pl]=Wtyczka wÅ‚aÅ›ciwoÅ›ci dla Konquerora umożliwiajÄ…ca współdzielenie katalogu w sieci lokalnej +Comment[pt]='Plugin' de janela de propriedades do Konqueror para partilhar uma pasta na rede local +Comment[pt_BR]=Plug-in de diálogo de propriedades do Konqueror para o compartilhamento de uma pasta em uma rede local +Comment[ru]=Модуль ÑвойÑтв Konqueror Ð´Ð»Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¾Ð±Ñ‰ÐµÐ³Ð¾ доÑтупа к каталогу из локальной Ñети +Comment[sk]=Modul Konquerora pre zdieľanie prieÄinku v lokálnej sieti +Comment[sl]=Vstavek za Konqueror s pogovornim oknom za lastnosti za souporabo mape v krajevnem omrežju +Comment[sr]=Прикључак Konqueror-а за подешавање ÑвојÑтава дељења директоријума у локалној мрежи +Comment[sr@Latn]=PrikljuÄak Konqueror-a za podeÅ¡avanje svojstava deljenja direktorijuma u lokalnoj mreži +Comment[sv]=Konqueror-insticksprogram med egenskapsdialogruta för att dela en katalog i det lokala nätverket +Comment[ta]=Konqueror பணà¯à®ªà¯à®•à®³à®¿à®©à¯ உரையாடல௠செரà¯à®•à¯à®•à®³à¯ அடைவை சமà¯à®ªà®¾ சேவையகதà¯à®¤à¯à®Ÿà®©à¯ பகிரவேணà¯à®Ÿà®¿à®¯ செரà¯à®•à¯à®•à®³à¯ +Comment[tg]=Модули муколамаи хуÑуÑиÑтҳои Konqueror барои иÑтифодабарии муштараки феҳриÑÑ‚ бо шабакаи маҳалллӣ +Comment[tr]=Yerel aÄŸ ile dizin paylaşımı için Konqueror iletiÅŸim eklentisi özellikleri +Comment[uk]=Втулок діалогу влаÑтивоÑтей Konqueror Ð´Ð»Ñ Ð´Ð¾Ð·Ð²Ð¾Ð»Ñƒ Ñпільного доÑтупу до каталогу з локальної мережі +Comment[zh_CN]=å¯å°†ç›®å½•ä¸Žå±€åŸŸç½‘共享的 Konqueror 属性页对è¯æ¡†æ’件 +Comment[zh_HK]=用於與本地網絡分享目錄的 Konqueror 屬性å°è©±ç›’æ’件 +Comment[zh_TW]=Konqueror 屬性å°è©±æ¡†å¤–掛程å¼ï¼Œç”¨æ–¼åœ¨æœ¬åœ°ç«¯ç¶²è·¯ä¸Šåˆ†äº«ç›®éŒ„ +X-KDE-Library=fileshare_propsdlgplugin +X-KDE-Protocol=file +ServiceTypes=KPropsDlg/Plugin,inode/directory diff --git a/filesharing/advanced/propsdlgplugin/propertiespage.cpp b/filesharing/advanced/propsdlgplugin/propertiespage.cpp new file mode 100644 index 00000000..e5b349da --- /dev/null +++ b/filesharing/advanced/propsdlgplugin/propertiespage.cpp @@ -0,0 +1,612 @@ +/* + Copyright (c) 2004 Jan Schaefer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// NFS related +#include "../nfs/nfsfile.h" +#include "../nfs/nfsentry.h" +#include "../nfs/nfsdialog.h" + +// Samba related +#include "../kcm_sambaconf/sambafile.h" +#include "../kcm_sambaconf/sambashare.h" +#include "../kcm_sambaconf/sharedlgimpl.h" + +#include "propertiespage.h" + +#define FILESHARE_DEBUG 5009 + +PropertiesPage::PropertiesPage(QWidget* parent, KFileItemList items,bool enterUrl) + : PropertiesPageGUI(parent), + m_enterUrl(enterUrl), + m_items(items), + m_nfsFile(0), + m_nfsEntry(0), + m_sambaFile(0), + m_sambaShare(0), + m_sambaChanged(false), + m_nfsChanged(false), + m_loaded(false) +{ + if (m_items.isEmpty()) { + shareFrame->setDisabled(true); + } else { + shareFrame->setEnabled(true); + // currently only one dir is allowed + m_path = m_items.first()->url().path(1); + } + + if (m_enterUrl) { + shareChk->hide(); + urlRq->setMode(KFile::Directory | + KFile::ExistingOnly | + KFile::LocalOnly ); + urlRq->setURL(m_path); + connect( urlRq, SIGNAL(textChanged(const QString&)), + this, SLOT(urlRqTextChanged(const QString&))); + } else { + urlRq->hide(); + folderLbl->hide(); + } + + + enableSamba(false,i18n("Reading Samba configuration file ...")); + enableNFS(false,i18n("Reading NFS configuration file ...")); + + + //QTimer::singleShot(1, this, SLOT(load)); + load(); +} + +PropertiesPage::~PropertiesPage() +{ + delete m_nfsFile; + delete m_sambaFile; +} + +void PropertiesPage::urlRqTextChanged(const QString&) { + if (!m_enterUrl) + return; + + KURL url(urlRq->url()); + if (url.isLocalFile()) { + QFileInfo info(url.path(1)); + if (info.exists() && + info.isDir()) + { + shareFrame->setEnabled(true); + return; + } + } + + shareFrame->setDisabled(true); +} + +void PropertiesPage::load() { + loadNFS(); + loadSamba(); + + bool nfsShared = KNFSShare::instance()->isDirectoryShared(m_path); + bool sambaShared = KSambaShare::instance()->isDirectoryShared(m_path); + + nfsChk->setChecked(nfsShared); + sambaChk->setChecked(sambaShared); + + if (!m_enterUrl) + shareChk->setChecked(nfsShared || sambaShared); + + m_loaded = true; +} + +void PropertiesPage::enableNFS(bool b, const QString & message) { + nfsChk->setEnabled(b); + nfsGrp->setEnabled(b); + QToolTip::add(nfsChk,message); + QToolTip::add(nfsGrp,message); +} + +void PropertiesPage::enableSamba(bool b, const QString & message) { + sambaChk->setEnabled(b); + sambaGrp->setEnabled(b); + QToolTip::add(sambaChk,message); + QToolTip::add(sambaGrp,message); +} + + +bool PropertiesPage::save() { + if (!hasChanged()) { + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::save: nothing changed." << endl; + return true; + } + + if (!checkURL()) { + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::save: url check failed." << endl; + return false; + } + + updateNFSEntry(); + if (!updateSambaShare()) { + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::save: updateSambaShare failed!" << endl; + return false; + } + + return save(m_nfsFile, m_sambaFile, m_nfsChanged, m_sambaChanged); +} + +bool PropertiesPage::save(NFSFile* nfsFile, SambaFile* sambaFile, bool nfs, bool samba) +{ + QString nfsFileName = KNFSShare::instance()->exportsPath(); + bool nfsNeedsKDEsu = false; + + if (nfs) { + if (QFileInfo(nfsFileName).isWritable()) { + nfsFile->saveTo(nfsFileName); + } else { + nfsNeedsKDEsu = true; + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::save: nfs needs kdesu." << endl; + } + } else + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::save: nfs has not changed." << endl; + + + QString sambaFileName = KSambaShare::instance()->smbConfPath(); + bool sambaNeedsKDEsu = false; + if (samba) { + if (QFileInfo(sambaFileName).isWritable()) { + sambaFile->saveTo(sambaFileName); + } else { + sambaNeedsKDEsu = true; + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::save: samba needs kdesu." << endl; + } + } else + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::save: samba has not changed." << endl; + + + if (nfsNeedsKDEsu || sambaNeedsKDEsu) { + KTempFile nfsTempFile; + nfsTempFile.setAutoDelete(true); + KTempFile sambaTempFile; + sambaTempFile.setAutoDelete(true); + + KProcIO proc; + + QString command; + + if (nfsNeedsKDEsu) { + nfsFile->saveTo(nfsTempFile.name()); + command += QString("cp %1 %2;exportfs -ra;") + .arg(KProcess::quote( nfsTempFile.name() )) + .arg(KProcess::quote( nfsFileName )); + } + + if (sambaNeedsKDEsu) { + sambaFile->saveTo(sambaTempFile.name()); + command += QString("cp %1 %2;") + .arg(KProcess::quote( sambaTempFile.name() )) + .arg(KProcess::quote( sambaFileName )); + } + + proc<<"kdesu" << "-d" << "-c"<save(); + } + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::saveSamba: samba has not changed." << endl; + return true; +} + +bool PropertiesPage::saveNFS() { + updateNFSEntry(); + if (!m_nfsChanged) { + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::saveNFS: NFS did not change." << endl; + return true; + } + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::saveNFS: saving..." << endl; + return m_nfsFile->save(); +} + + + + +bool PropertiesPage::checkURL() { + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::checkURL" << endl; + + if (!m_enterUrl) + return true; + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::checkURL: enterUrl=true" << endl; + + KURL url(urlRq->url()); + QString path = url.path(1); + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::checkURL: m_path='" + << m_path << "'" << endl; + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::checkURL: path='" + << path << "'" << endl; + + if (m_path == path) { + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::checkURL: paths are equal" << endl; + return true; + } + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::checkURL: different path" << endl; + + + if (!url.isValid()) { + KMessageBox::sorry(this,i18n("Please enter a valid path.")); + urlRq->setFocus(); + urlRq->lineEdit()->selectAll(); + return false; + } + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::checkURL: url is valid" << endl; + + if (!url.isLocalFile()) { + KMessageBox::sorry(this,i18n("Only local folders can be shared.")); + urlRq->setFocus(); + urlRq->lineEdit()->selectAll(); + return false; + } + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::checkURL: url is local file" << endl; + + QFileInfo info(path); + + if (!info.exists()) + { + KMessageBox::sorry(this,i18n("The folder does not exists.")); + urlRq->setFocus(); + urlRq->lineEdit()->selectAll(); + return false; + } + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::checkURL: folder exits" << endl; + + + if (!info.isDir()) + { + KMessageBox::sorry(this,i18n("Only folders can be shared.")); + urlRq->setFocus(); + urlRq->lineEdit()->selectAll(); + return false; + } + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::checkURL: path is dir" << endl; + + if (KSambaShare::instance()->isDirectoryShared(path) || + KNFSShare::instance()->isDirectoryShared(path)) + { + KMessageBox::sorry(this,i18n("The folder is already shared.")); + urlRq->setFocus(); + urlRq->lineEdit()->selectAll(); + return false; + } + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::checkURL: folder not shared yet" << endl; + m_path = path; + + return true; +} + +bool PropertiesPage::loadNFS() { + if (!KFileShare::nfsEnabled()) { + enableNFS(false,i18n("The administrator does not allow sharing with NFS.")); + return false; + } + + delete m_nfsFile; + m_nfsFile = new NFSFile(KNFSShare::instance()->exportsPath()); + + if (!m_nfsFile->load()) { + enableNFS(false,i18n("Error: could not read NFS configuration file.")); + return false; + } + + enableNFS(true,""); + + loadNFSEntry(); + return true; +} + +void PropertiesPage::loadNFSEntry() { + m_nfsEntry = m_nfsFile->getEntryByPath(m_path); + m_nfsChanged = false; + + if (!m_nfsEntry) { + nfsChk->setChecked(false); + return; + } + + + NFSHost* publicHost = m_nfsEntry->getPublicHost(); + + if (publicHost) { + publicNFSChk->setChecked(true); + writableNFSChk->setChecked(!publicHost->readonly); + } else + publicNFSChk->setChecked(false); +} + +void PropertiesPage::updateNFSEntry() { + if (shareChk->isChecked() && + nfsChk->isChecked()) + { + if (!m_nfsEntry) { + m_nfsEntry = new NFSEntry(m_path); + m_nfsFile->addEntry(m_nfsEntry); + m_nfsChanged = true; + } + + NFSHost* publicHost = m_nfsEntry->getPublicHost(); + + if (publicNFSChk->isChecked()) { + if (!publicHost) { + publicHost = new NFSHost("*"); + publicHost->allSquash=true; + m_nfsEntry->addHost(publicHost); + m_nfsChanged = true; + } + + if (publicHost->readonly != !writableNFSChk->isChecked()) { + publicHost->readonly = !writableNFSChk->isChecked(); + m_nfsChanged = true; + } + } else { + if (publicHost) { + m_nfsEntry->removeHost(publicHost); + m_nfsChanged = true; + } + } + } else { // unshare + if (m_nfsEntry) { + m_nfsFile->removeEntry(m_nfsEntry); + m_nfsEntry = 0; + m_nfsChanged = true; + } + } +} + +void PropertiesPage::moreNFSBtn_clicked() { + updateNFSEntry(); + NFSDialog* dlg = new NFSDialog(this,m_nfsEntry); + if (dlg->exec()==QDialog::Accepted && + dlg->modified()) + { + kdDebug(FILESHARE_DEBUG) << "NFSDialog::ok" << endl; + loadNFSEntry(); + m_nfsChanged = true; + emit changed(); + } + delete dlg; +} + +bool PropertiesPage::loadSamba() { + if (!KFileShare::sambaEnabled()) { + enableSamba(false,i18n("The administrator does not allow sharing with Samba.")); + return false; + } + + delete m_sambaFile; + m_sambaFile = new SambaFile(KSambaShare::instance()->smbConfPath(), false); + if (! m_sambaFile->load()) { + enableSamba(false,i18n("Error: could not read Samba configuration file.")); + return false; + } + + enableSamba(true,""); + QString shareName = m_sambaFile->findShareByPath(m_path); + if (shareName.isNull()) { + sambaChk->setChecked(false); + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::loadSamba: shareName is null!" + << endl; + return false; + } + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::loadSamba: shareName=" + << shareName << endl; + + m_sambaShare = m_sambaFile->getShare(shareName); + + loadSambaShare(); + return true; +} + + +void PropertiesPage::loadSambaShare() { + if (! m_sambaShare) { + sambaChk->setChecked(false); + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::loadSambaShare: no share with name " + << m_sambaShare->getName() << endl; + return; + } + + if (m_sambaShare->getBoolValue("public")) { + publicSambaChk->setChecked(true); + writableSambaChk->setChecked(m_sambaShare->getBoolValue("writable")); + } else + publicSambaChk->setChecked(false); + + + sambaNameEdit->setText(m_sambaShare->getName() ); +} + +void PropertiesPage::sambaChkToggled( bool b ) { + if (!m_loaded) + return; + + if (sambaNameEdit->text().isEmpty()) + sambaNameEdit->setText(getNewSambaName()); +} + +bool PropertiesPage::updateSambaShare() { + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::updateSambaShare" << endl; + + if (shareChk->isChecked() && + sambaChk->isChecked()) + { + if (m_enterUrl) { + if (m_path != urlRq->url()) { + m_path = urlRq->url(); + } + } + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::updateSambaShare: m_path" + << m_path << endl; + + if (!m_sambaShare) { + createNewSambaShare(); + m_sambaChanged = true; + } + + setSambaShareBoolValue("public", publicSambaChk); + setSambaShareBoolValue("writable", writableSambaChk); + + if (sambaNameEdit->text().isEmpty()) { + KMessageBox::sorry(this, i18n("You have to enter a name for the Samba share.")); + sambaNameEdit->setFocus(); + return false; + } + + if (sambaNameEdit->text() != m_sambaShare->getName()) { + SambaShare* otherShare = m_sambaFile->getShare(sambaNameEdit->text()); + if (otherShare && otherShare != m_sambaShare) { + // There is another Share with the same name + KMessageBox::sorry(this, i18n("There is already a share with the name %1.
    Please choose another name.
    ").arg(sambaNameEdit->text())); + sambaNameEdit->selectAll(); + sambaNameEdit->setFocus(); + return false; + } + m_sambaShare->setName(sambaNameEdit->text()); + m_sambaChanged = true; + } + + if (m_sambaShare->getValue("path") != m_path) { + m_sambaShare->setValue("path", m_path); + m_sambaChanged = true; + } + + } else { + if (m_sambaShare) { + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::updateSambaShare: removing share" << endl; + m_sambaFile->removeShare(m_sambaShare); + m_sambaShare = 0; + m_sambaChanged = true; + } + } + return true; +} + +void PropertiesPage::setSambaShareBoolValue(const QString & value, + QCheckBox* chk) +{ + bool v = m_sambaShare->getBoolValue(value); + if (v == chk->isChecked()) + return; + + m_sambaShare->setValue(value,chk->isChecked()); + m_sambaChanged = true; +} + +QString PropertiesPage::getNewSambaName() { + QString path = m_path; + if (path.isNull() && m_enterUrl) { + path = urlRq->url(); + } + + QString shareName = KURL(path).fileName(); + + if (!sambaNameEdit->text().isEmpty()) + shareName = sambaNameEdit->text(); + + // Windows could have problems with longer names + shareName = shareName.left(12).upper(); + + if ( m_sambaFile->getShare(shareName) ) + shareName = m_sambaFile->getUnusedName(shareName); + + return shareName; +} + +void PropertiesPage::createNewSambaShare() { + + m_sambaShare = m_sambaFile->newShare(getNewSambaName(),m_path); + + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::createNewSambaShare: " + << m_sambaShare->getName() << endl; + +} + + +void PropertiesPage::moreSambaBtnClicked() { + kdDebug(FILESHARE_DEBUG) << "PropertiesPage::moreSambaBtnClicked()" << endl; + updateSambaShare(); + ShareDlgImpl* dlg = new ShareDlgImpl(this,m_sambaShare); + dlg->directoryGrp->hide(); + dlg->pixmapFrame->hide(); + if (dlg->exec() == QDialog::Accepted && + dlg->hasChanged()) { + m_sambaChanged = true; + changedSlot(); + loadSambaShare(); + } + delete dlg; +} + +#include "propertiespage.moc" + diff --git a/filesharing/advanced/propsdlgplugin/propertiespage.h b/filesharing/advanced/propsdlgplugin/propertiespage.h new file mode 100644 index 00000000..3becf6e7 --- /dev/null +++ b/filesharing/advanced/propsdlgplugin/propertiespage.h @@ -0,0 +1,87 @@ +/* + Copyright (c) 2004 Jan Schaefer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef PROPERTIESPAGE_H +#define PROPERTIESPAGE_H + +#include +#include "propertiespagegui.h" + +class NFSFile; +class NFSEntry; +class SambaFile; +class SambaShare; +class QCheckBox; + +class PropertiesPage : public PropertiesPageGUI +{ +Q_OBJECT +public: + PropertiesPage(QWidget* parent, KFileItemList items, bool enterUrl=false); + virtual ~PropertiesPage(); + + bool save(); + + static bool save(NFSFile* nfsFile, SambaFile* sambFile, bool nfs, bool samba); +public slots: + void load(); + +protected: + + bool m_enterUrl; + QString m_path; + KFileItemList m_items; + NFSFile *m_nfsFile; + NFSEntry *m_nfsEntry; + + SambaFile *m_sambaFile; + SambaShare *m_sambaShare; + bool m_sambaChanged; + bool m_nfsChanged; + bool m_loaded; + +protected slots: + // inherited from PropertiesPageGUI + virtual void moreNFSBtn_clicked(); + virtual void moreSambaBtnClicked(); + virtual void sambaChkToggled( bool b ); + virtual void urlRqTextChanged(const QString&); + +private: + bool loadNFS(); + void loadNFSEntry(); + void updateNFSEntry(); + bool saveNFS(); + + bool loadSamba(); + void loadSambaShare(); + bool updateSambaShare(); + bool saveSamba(); + + bool checkURL(); + void setSambaShareBoolValue(const QString & value, QCheckBox* chk); + void createNewSambaShare(); + QString getNewSambaName(); + + void enableNFS(bool b,const QString & message); + void enableSamba(bool b,const QString & message); + +}; + +#endif diff --git a/filesharing/advanced/propsdlgplugin/propertiespagegui.ui b/filesharing/advanced/propsdlgplugin/propertiespagegui.ui new file mode 100644 index 00000000..4f756d3b --- /dev/null +++ b/filesharing/advanced/propsdlgplugin/propertiespagegui.ui @@ -0,0 +1,478 @@ + +PropertiesPageGUI + + + PropertiesPageGUI + + + + 0 + 0 + 433 + 348 + + + + + unnamed + + + 0 + + + + layout6 + + + + unnamed + + + + folderLbl + + + Folder: + + + + + urlRq + + + + + + + shareChk + + + S&hare this folder in the local network + + + true + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + shareFrame + + + NoFrame + + + Raised + + + + unnamed + + + 0 + + + + nfsChk + + + Share with &NFS (Linux/UNIX) + + + true + + + + + nfsGrp + + + NFS Options + + + + unnamed + + + + layout6 + + + + unnamed + + + + publicNFSChk + + + Pu&blic + + + true + + + + + writableNFSChk + + + W&ritable + + + false + + + + + spacer5 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + layout4 + + + + unnamed + + + + moreNFSBtn + + + More NFS Op&tions + + + + + spacer3 + + + Horizontal + + + Expanding + + + + 156 + 20 + + + + + + + + + + sambaChk + + + Share with S&amba (Microsoft(R) Windows(R)) + + + true + + + + + sambaGrp + + + Samba Options + + + + unnamed + + + + layout3 + + + + unnamed + + + + textLabel1 + + + Name: + + + + + sambaNameEdit + + + + + + + layout5 + + + + unnamed + + + + publicSambaChk + + + P&ublic + + + true + + + + + writableSambaChk + + + &Writable + + + false + + + + + spacer4_3 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + layout3 + + + + unnamed + + + + moreSambaBtn + + + Mor&e Samba Options + + + + + spacer4_2 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + + + + + spacer4 + + + Vertical + + + Expanding + + + + 20 + 1 + + + + + + + + sambaChk + toggled(bool) + sambaGrp + setEnabled(bool) + + + nfsChk + toggled(bool) + nfsGrp + setEnabled(bool) + + + publicSambaChk + toggled(bool) + writableSambaChk + setEnabled(bool) + + + publicNFSChk + toggled(bool) + writableNFSChk + setEnabled(bool) + + + shareChk + toggled(bool) + shareFrame + setEnabled(bool) + + + shareChk + clicked() + PropertiesPageGUI + changedSlot() + + + sambaChk + clicked() + PropertiesPageGUI + changedSlot() + + + publicSambaChk + clicked() + PropertiesPageGUI + changedSlot() + + + writableSambaChk + clicked() + PropertiesPageGUI + changedSlot() + + + nfsChk + clicked() + PropertiesPageGUI + changedSlot() + + + publicNFSChk + clicked() + PropertiesPageGUI + changedSlot() + + + writableNFSChk + clicked() + PropertiesPageGUI + changedSlot() + + + moreNFSBtn + clicked() + PropertiesPageGUI + moreNFSBtn_clicked() + + + sambaChk + toggled(bool) + PropertiesPageGUI + sambaChkToggled(bool) + + + sambaNameEdit + textChanged(const QString&) + PropertiesPageGUI + changedSlot() + + + publicSambaChk + toggled(bool) + PropertiesPageGUI + publicSambaChkToggled(bool) + + + publicNFSChk + toggled(bool) + PropertiesPageGUI + publicNFSChkToggled(bool) + + + moreSambaBtn + clicked() + PropertiesPageGUI + moreSambaBtnClicked() + + + + propertiespagegui.ui.h + + + bool m_hasChanged; + + + changed() + + + changedSlot() + moreNFSBtn_clicked() + sambaChkToggled( bool ) + publicSambaChkToggled( bool b ) + publicNFSChkToggled( bool b ) + moreSambaBtnClicked() + + + hasChanged() + + + diff --git a/filesharing/advanced/propsdlgplugin/propertiespagegui.ui.h b/filesharing/advanced/propsdlgplugin/propertiespagegui.ui.h new file mode 100644 index 00000000..89d4a56e --- /dev/null +++ b/filesharing/advanced/propsdlgplugin/propertiespagegui.ui.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename functions or slots use +** Qt Designer which will update this file, preserving your code. Create an +** init() function in place of a constructor, and a destroy() function in +** place of a destructor. +*****************************************************************************/ + + +void PropertiesPageGUI::changedSlot() +{ + m_hasChanged = true; + emit changed(); +} + + +void PropertiesPageGUI::moreNFSBtn_clicked() +{ + +} + + +bool PropertiesPageGUI::hasChanged() +{ + return m_hasChanged; +} + + +void PropertiesPageGUI::sambaChkToggled( bool ) +{ + +} + + +void PropertiesPageGUI::publicSambaChkToggled( bool b) +{ + if (!b) { + writableSambaChk->setChecked(false); + } +} + + +void PropertiesPageGUI::publicNFSChkToggled( bool b) +{ + if (!b) { + writableNFSChk->setChecked(false); + } + +} + + +void PropertiesPageGUI::moreSambaBtnClicked() +{ + +} diff --git a/filesharing/advanced/propsdlgplugin/propsdlgshareplugin.cpp b/filesharing/advanced/propsdlgplugin/propsdlgshareplugin.cpp new file mode 100644 index 00000000..70d94519 --- /dev/null +++ b/filesharing/advanced/propsdlgplugin/propsdlgshareplugin.cpp @@ -0,0 +1,126 @@ +/* + Copyright (c) 2004 Jan Schaefer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "propertiespage.h" +#include "propsdlgshareplugin.h" + +typedef KGenericFactory PropsDlgSharePluginFactory; + +K_EXPORT_COMPONENT_FACTORY( fileshare_propsdlgplugin, + PropsDlgSharePluginFactory("fileshare_propsdlgplugin") ) + +class PropsDlgSharePlugin::Private +{ + public: + PropertiesPage* page; +}; + +PropsDlgSharePlugin::PropsDlgSharePlugin( KPropertiesDialog *dlg, + const char *, const QStringList & ) + : KPropsDlgPlugin(dlg), d(0) +{ + KGlobal::locale()->insertCatalogue("kfileshare"); + + if (KFileShare::shareMode() == KFileShare::Simple) { + kdDebug(5009) << "PropsDlgSharePlugin: Sharing mode is simple. Aborting." << endl; + return; + } + + + QVBox* vbox = properties->addVBoxPage(i18n("&Share")); + properties->setFileSharingPage(vbox); + + if (KFileShare::authorization() == KFileShare::UserNotAllowed) { + + QWidget* widget = new QWidget( vbox ); + QVBoxLayout * vLayout = new QVBoxLayout( widget ); + vLayout->setSpacing( KDialog::spacingHint() ); + vLayout->setMargin( 0 ); + + + if (KFileShare::sharingEnabled()) { + vLayout->addWidget( + new QLabel( i18n("You need to be authorized to share directories."), + widget )); + } else { + vLayout->addWidget( + new QLabel( i18n("File sharing is disabled."), widget)); + } + + KPushButton* btn = new KPushButton( i18n("Configure File Sharing..."), widget ); + connect( btn, SIGNAL( clicked() ), SLOT( slotConfigureFileSharing() ) ); + btn->setDefault(false); + QHBoxLayout* hBox = new QHBoxLayout( (QWidget *)0L ); + hBox->addWidget( btn, 0, Qt::AlignLeft ); + vLayout->addLayout(hBox); + vLayout->addStretch( 10 ); // align items on top + return; + } + + + d = new Private(); + + d->page = new PropertiesPage(vbox, properties->items(),false); + connect(d->page, SIGNAL(changed()), + this, SIGNAL(changed())); + + kdDebug(5009) << "Fileshare properties dialog plugin loaded" << endl; + +} + +void PropsDlgSharePlugin::slotConfigureFileSharing() +{ + KProcess proc; + proc << KStandardDirs::findExe("kdesu") << locate("exe", "kcmshell") << "fileshare"; + proc.start( KProcess::DontCare ); +} + + +PropsDlgSharePlugin::~PropsDlgSharePlugin() +{ + delete d; +} + +void PropsDlgSharePlugin::applyChanges() +{ + if (!d->page->save()) { +// KMessageBox::sorry(d->page, +// i18n("Saving the changes failed")); + + properties->abortApplying(); + } +} + + +#include "propsdlgshareplugin.moc" + diff --git a/filesharing/advanced/propsdlgplugin/propsdlgshareplugin.h b/filesharing/advanced/propsdlgplugin/propsdlgshareplugin.h new file mode 100644 index 00000000..acf889fa --- /dev/null +++ b/filesharing/advanced/propsdlgplugin/propsdlgshareplugin.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2004 Jan Schaefer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef KONQFILESHAREPLUGIN_H +#define KONQFILESHAREPLUGIN_H + +#include + +class PropsDlgSharePlugin : public KPropsDlgPlugin +{ +Q_OBJECT +public: + PropsDlgSharePlugin( KPropertiesDialog *dlg, const char *, const QStringList & ); + virtual ~PropsDlgSharePlugin(); + virtual void applyChanges(); + +protected slots: + void slotConfigureFileSharing(); + +private: + class Private; + Private *d; + +}; + +#endif + + diff --git a/filesharing/simple/Makefile.am b/filesharing/simple/Makefile.am new file mode 100644 index 00000000..7a3e2994 --- /dev/null +++ b/filesharing/simple/Makefile.am @@ -0,0 +1,19 @@ +METASOURCES = AUTO + +kde_module_LTLIBRARIES = kcm_fileshare.la + +kcm_fileshare_la_SOURCES = fileshare.cpp \ + controlcenter.ui \ + groupconfiggui.ui \ + groupconfigdlg.cpp \ + krichtextlabel.cpp + +kcm_fileshare_la_LDFLAGS = $(all_libraries) -module -avoid-version -no-undefined +kcm_fileshare_la_LIBADD = ../advanced/propsdlgplugin/libpropsdlgplugin_common.la \ + ../advanced/nfs/libfilesharenfs.la \ + ../advanced/kcm_sambaconf/libfilesharesamba.la \ + $(LIB_KIO) + +AM_CPPFLAGS = -I$(srcdir)/../advanced/propsdlgplugin -I../advanced/propsdlgplugin $(all_includes) + +xdg_apps_DATA = fileshare.desktop diff --git a/filesharing/simple/controlcenter.ui b/filesharing/simple/controlcenter.ui new file mode 100644 index 00000000..eea9166e --- /dev/null +++ b/filesharing/simple/controlcenter.ui @@ -0,0 +1,486 @@ + +ControlCenterGUI + + + ControlCenterGUI + + + + 0 + 0 + 525 + 535 + + + + + unnamed + + + 0 + + + + infoLbl + + + 11 + + + SMB and NFS servers are not installed on this machine, to enable this module the servers must be installed. + + + WordBreak|AlignVCenter + + + + + shareGrp + + + Enable Local Networ&k File Sharing + + + true + + + true + + + + unnamed + + + + simpleRadio + + + Si&mple sharing + + + true + + + 1 + + + + + layout4 + + + + unnamed + + + + frame4_2 + + + + 0 + 5 + 0 + 0 + + + + + 11 + 0 + + + + NoFrame + + + Raised + + + 0 + + + + + textLabel1 + + + + 1 + + + + Enable simple sharing to allow users to share folders from their HOME folder, without knowing the root password. + + + WordBreak|AlignVCenter + + + + + + + advancedRadio + + + Advanced sharin&g + + + 1 + + + + + layout4_2 + + + + unnamed + + + + frame4_2_2 + + + + 0 + 5 + 0 + 0 + + + + + 11 + 0 + + + + NoFrame + + + Raised + + + 0 + + + + + textLabel1_2 + + + + 1 + + + + Enable advanced sharing to allow users to share any folders, as long as they have write access to the needed configuration files, or they know the root password. + + + WordBreak|AlignVCenter + + + + + + + layout3 + + + + unnamed + + + + frame4 + + + + 0 + 5 + 0 + 0 + + + + + 11 + 0 + + + + NoFrame + + + Raised + + + 0 + + + + + nfsChk + + + false + + + Use &NFS (Linux/UNIX) + + + true + + + + + sambaChk + + + false + + + Use Sam&ba (Microsoft(R) Windows(R)) + + + true + + + + + + + layout10 + + + + unnamed + + + + allowedUsersBtn + + + Allo&wed Users + + + + + spacer9 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + + + sharedFoldersGroupBox + + + Shared Folders + + + + unnamed + + + + + Path + + + true + + + true + + + + + Samba + + + true + + + true + + + + + NFS + + + true + + + true + + + + listView + + + NoSelection + + + + + shareBtnPnl + + + false + + + NoFrame + + + Plain + + + + unnamed + + + 0 + + + + addShareBtn + + + A&dd... + + + + + changeShareBtn + + + false + + + Chang&e... + + + + + removeShareBtn + + + false + + + Rem&ove + + + + + spacer2 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + + + + + + + advancedRadio + toggled(bool) + nfsChk + setEnabled(bool) + + + advancedRadio + toggled(bool) + sambaChk + setEnabled(bool) + + + shareGrp + clicked(int) + ControlCenterGUI + changedSlot() + + + simpleRadio + clicked() + ControlCenterGUI + changedSlot() + + + advancedRadio + clicked() + ControlCenterGUI + changedSlot() + + + sambaChk + clicked() + ControlCenterGUI + changedSlot() + + + nfsChk + clicked() + ControlCenterGUI + changedSlot() + + + listView + selectionChanged() + ControlCenterGUI + listView_selectionChanged() + + + + sambaChk + nfsChk + listView + + + controlcenter.ui.h + krichtextlabel.h + + + changed() + + + changedSlot() + listView_selectionChanged() + + + + krichtextlabel.h + klistview.h + kpushbutton.h + + diff --git a/filesharing/simple/controlcenter.ui.h b/filesharing/simple/controlcenter.ui.h new file mode 100644 index 00000000..6fb3f62f --- /dev/null +++ b/filesharing/simple/controlcenter.ui.h @@ -0,0 +1,22 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename functions or slots use +** Qt Designer which will update this file, preserving your code. Create an +** init() function in place of a constructor, and a destroy() function in +** place of a destructor. +*****************************************************************************/ + + +void ControlCenterGUI::changedSlot() +{ + emit changed(); +} + +void ControlCenterGUI::listView_selectionChanged() +{ + bool empty = listView->selectedItems ().isEmpty(); + changeShareBtn->setDisabled(empty ); + removeShareBtn->setDisabled(empty ); + +} diff --git a/filesharing/simple/fileshare.cpp b/filesharing/simple/fileshare.cpp new file mode 100644 index 00000000..a0c5bb06 --- /dev/null +++ b/filesharing/simple/fileshare.cpp @@ -0,0 +1,442 @@ +/* + Copyright (c) 2002 Laurent Montel + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../advanced/propsdlgplugin/propertiespage.h" +#include "../advanced/nfs/nfsfile.h" +#include "../advanced/kcm_sambaconf/sambafile.h" + +#include "controlcenter.h" +#include "fileshare.h" +#include "groupconfigdlg.h" + +typedef KGenericFactory ShareFactory; +K_EXPORT_COMPONENT_FACTORY (kcm_fileshare, ShareFactory("kcmfileshare") ) + + +#define FILESHARECONF "/etc/security/fileshare.conf" +#define FILESHARE_DEBUG 5009 + +KFileShareConfig::KFileShareConfig(QWidget *parent, const char *name, const QStringList &): + KCModule(ShareFactory::instance(), parent, name) +{ + KGlobal::locale()->insertCatalogue("kfileshare"); + + QBoxLayout* layout = new QVBoxLayout(this,0, + KDialog::spacingHint()); + +/* + QVButtonGroup *box = new QVButtonGroup( i18n("File Sharing"), this ); + box->layout()->setSpacing( KDialog::spacingHint() ); + layout->addWidget(box); + noSharing=new QRadioButton( i18n("Do ¬ allow users to share files"), box ); + sharing=new QRadioButton( i18n("&Allow users to share files from their HOME folder"), box); +*/ + m_ccgui = new ControlCenterGUI(this); + connect( m_ccgui, SIGNAL( changed()), this, SLOT(configChanged())); + connect( m_ccgui->allowedUsersBtn, SIGNAL( clicked()), + this, SLOT(allowedUsersBtnClicked())); + + QString path = QString::fromLocal8Bit( getenv( "PATH" ) ); + path += QString::fromLatin1(":/usr/sbin"); + QString sambaExec = KStandardDirs::findExe( QString::fromLatin1("smbd"), path ); + QString nfsExec = KStandardDirs::findExe( QString::fromLatin1("rpc.nfsd"), path ); + + if ( nfsExec.isEmpty() && sambaExec.isEmpty()) + { + m_ccgui->shareGrp->setDisabled(true); + m_ccgui->sharedFoldersGroupBox->setDisabled(true); + } + else + { + if (nfsExec.isEmpty()) { + m_ccgui->nfsChk->setDisabled(true); + m_ccgui->nfsChk->setChecked(false); + QToolTip::add(m_ccgui->nfsChk,i18n("No NFS server installed on this system")); + } + + if (sambaExec.isEmpty()) { + m_ccgui->sambaChk->setDisabled(true); + m_ccgui->sambaChk->setChecked(false); + QToolTip::add(m_ccgui->sambaChk,i18n("No Samba server installed on this system")); + } + + m_ccgui->infoLbl->hide(); + layout->addWidget(m_ccgui); + updateShareListView(); + connect( KNFSShare::instance(), SIGNAL( changed()), + this, SLOT(updateShareListView())); + connect( KSambaShare::instance(), SIGNAL( changed()), + this, SLOT(updateShareListView())); + + + } + + if((getuid() == 0) || + ((KFileShare::shareMode() == KFileShare::Advanced) && + (KFileShare::authorization() == KFileShare::Authorized))) + { + connect( m_ccgui->addShareBtn, SIGNAL(clicked()), + this, SLOT(addShareBtnClicked())); + connect( m_ccgui->changeShareBtn, SIGNAL(clicked()), + this, SLOT(changeShareBtnClicked())); + connect( m_ccgui->removeShareBtn, SIGNAL(clicked()), + this, SLOT(removeShareBtnClicked())); + m_ccgui->listView->setSelectionMode(QListView::Extended); + m_ccgui->shareBtnPnl->setEnabled(true); + } + + + if (getuid()==0) { + setButtons(Help|Apply); + } else { + setButtons(Help); + m_ccgui->shareGrp->setDisabled( true ); + } + + load(); +} + +void KFileShareConfig::updateShareListView() +{ + m_ccgui->listView->clear(); + KNFSShare* nfs = KNFSShare::instance(); + KSambaShare* samba = KSambaShare::instance(); + + QStringList dirs = nfs->sharedDirectories(); + QStringList sambaDirs = samba->sharedDirectories(); + + for ( QStringList::ConstIterator it = sambaDirs.begin(); it != sambaDirs.end(); ++it ) { + // Do not insert duplicates + if (nfs->isDirectoryShared(*it)) + continue; + + dirs += *it; + } + + QPixmap folderPix = SmallIcon("folder",0,KIcon::ShareOverlay); + QPixmap okPix = SmallIcon("button_ok"); + QPixmap cancelPix = SmallIcon("button_cancel"); + + for ( QStringList::Iterator it = dirs.begin(); it != dirs.end(); ++it ) { + KListViewItem* item = new KListViewItem(m_ccgui->listView); + item->setText(0,*it); + item->setPixmap(0, folderPix); + + if (samba->isDirectoryShared(*it)) + item->setPixmap(1,okPix); + else + item->setPixmap(1,cancelPix); + + if (nfs->isDirectoryShared(*it)) + item->setPixmap(2,okPix); + else + item->setPixmap(2,cancelPix); + + } + +} + +void KFileShareConfig::allowedUsersBtnClicked() { + GroupConfigDlg dlg(this,m_fileShareGroup,m_restricted,m_rootPassNeeded, + m_ccgui->simpleRadio->isChecked()); + if (dlg.exec() == QDialog::Accepted) { + m_fileShareGroup = dlg.fileShareGroup().name(); + m_restricted = dlg.restricted(); + m_rootPassNeeded = dlg.rootPassNeeded(); + configChanged(); + } + +} + + +void KFileShareConfig::load() +{ + KSimpleConfig config(QString::fromLatin1(FILESHARECONF),true); + + m_ccgui->shareGrp->setChecked( config.readEntry("FILESHARING", "yes") == "yes" ); + + m_restricted = config.readEntry("RESTRICT", "yes") == "yes"; + + if (config.readEntry("SHARINGMODE", "simple") == "simple") + m_ccgui->simpleRadio->setChecked(true); + else + m_ccgui->advancedRadio->setChecked(true); + + m_fileShareGroup = config.readEntry("FILESHAREGROUP", "fileshare"); + + m_ccgui->sambaChk->setChecked( + config.readEntry("SAMBA", "yes") == "yes"); + + m_ccgui->nfsChk->setChecked( + config.readEntry("NFS", "yes") == "yes"); + + m_rootPassNeeded = config.readEntry("ROOTPASSNEEDED", "yes") == "yes"; + + m_smbConf = KSambaShare::instance()->smbConfPath(); +} + +bool KFileShareConfig::addGroupAccessesToFile(const QString & file) { + KProcess chgrp; + chgrp << "chgrp" << m_fileShareGroup << file; + KProcess chmod; + chmod << "chmod" << "g=rw" << file; + + if (!chgrp.start(KProcess::Block) && chgrp.normalExit()) { + kdDebug(FILESHARE_DEBUG) << "KFileShareConfig::addGroupAccessesToFile: chgrp failed" << endl; + return false; + + } + + if(!chmod.start(KProcess::Block) && chmod.normalExit()) { + kdDebug(FILESHARE_DEBUG) << "KFileShareConfig::addGroupAccessesToFile: chmod failed" << endl; + return false; + } + + return true; + +} + +bool KFileShareConfig::removeGroupAccessesFromFile(const QString & file) { + KProcess chgrp; + chgrp << "chgrp" << "root" << file; + KProcess chmod; + chmod << "chmod" << "g=r" << file; + + if (!chgrp.start(KProcess::Block) && chgrp.normalExit()) { + kdDebug(FILESHARE_DEBUG) << "KFileShareConfig::removeGroupAccessesFromFile: chgrp failed" << endl; + return false; + + } + + if(!chmod.start(KProcess::Block) && chmod.normalExit()) { + kdDebug(FILESHARE_DEBUG) << "KFileShareConfig::removeGroupAccessesFromFile: chmod failed" << endl; + return false; + } + + return true; +} + + +bool KFileShareConfig::setGroupAccesses() { + if (m_rootPassNeeded || ! m_ccgui->sambaChk->isChecked()) { + if (!removeGroupAccessesFromFile(KSambaShare::instance()->smbConfPath())) + return false; + } + + if (m_rootPassNeeded || ! m_ccgui->nfsChk->isChecked()) { + if (!removeGroupAccessesFromFile(KNFSShare::instance()->exportsPath())) + return false; + } + + if (! m_rootPassNeeded && m_ccgui->sambaChk->isChecked()) { + if (!addGroupAccessesToFile(KSambaShare::instance()->smbConfPath())) + return false; + } + + if (! m_rootPassNeeded && m_ccgui->nfsChk->isChecked()) { + if (!addGroupAccessesToFile(KNFSShare::instance()->exportsPath())) + return false; + } + + + return true; +} + +void KFileShareConfig::save() +{ + setGroupAccesses(); + + QDir dir("/etc/security"); + if ( !dir.exists()) + dir.mkdir("/etc/security"); + + QFile file(FILESHARECONF); + if ( ! file.open(IO_WriteOnly)) { + KMessageBox::detailedError(this, + i18n("Could not save settings."), + i18n("Could not open file '%1' for writing: %2").arg(FILESHARECONF).arg( + file.errorString() ), + i18n("Saving Failed")); + return; + } + + + QTextStream stream(&file); + + stream << "FILESHARING="; + stream << (m_ccgui->shareGrp->isChecked() ? "yes" : "no"); + + stream << "\nRESTRICT="; + stream << (m_restricted ? "yes" : "no"); + + stream << "\nSHARINGMODE="; + stream << (m_ccgui->simpleRadio->isChecked() ? "simple" : "advanced"); + + stream << "\nFILESHAREGROUP="; + stream << m_fileShareGroup; + + stream << "\nSAMBA="; + stream << (m_ccgui->sambaChk->isChecked() ? "yes" : "no"); + + stream << "\nNFS="; + stream << (m_ccgui->nfsChk->isChecked() ? "yes" : "no"); + + stream << "\nROOTPASSNEEDED="; + stream << (m_rootPassNeeded ? "yes" : "no"); + + stream << "\nSMBCONF="; + stream << m_smbConf; + + file.close(); +} + +void KFileShareConfig::defaults() +{ + m_ccgui->shareGrp->setChecked( false ); +} + +QString KFileShareConfig::quickHelp() const +{ + return i18n("

    File Sharing

    This module can be used " + "to enable file sharing over the network using " + "the \"Network File System\" (NFS) or SMB in Konqueror. " + "The latter enables you to share your files with Windows(R) " + "computers on your network.

    "); +} + +void KFileShareConfig::addShareBtnClicked() { + showShareDialog(KFileItemList()); +} + + +PropertiesPageDlg::PropertiesPageDlg(QWidget*parent, KFileItemList files) + : KDialogBase(parent, "sharedlg", true, + i18n("Share Folder"), Ok|Cancel, Ok, true) +{ + QVBox* vbox = makeVBoxMainWidget(); + + m_page = new PropertiesPage(vbox,files,true); +} + +bool PropertiesPageDlg::hasChanged() { + return m_page->hasChanged(); +} + +void PropertiesPageDlg::slotOk() { + if (hasChanged()) { + if (!m_page->save()) + return; + } + + KDialogBase::slotOk(); +} + + + +void KFileShareConfig::showShareDialog(const KFileItemList & files) { + PropertiesPageDlg* dlg = new PropertiesPageDlg(this,files); + if (dlg->exec() == QDialog::Accepted) { + if ( dlg->hasChanged() ) { + updateShareListView(); + } + } + delete dlg; +} + +void KFileShareConfig::changeShareBtnClicked() { + KFileItemList files; + QPtrList items = m_ccgui->listView->selectedItems(); + + QListViewItem* item; + for ( item = items.first(); item; item = items.next() ) { + files.append(new KFileItem(KURL::fromPathOrURL(item->text(0)),"",0)); + } + + showShareDialog(files); +} + +void KFileShareConfig::removeShareBtnClicked() { + + QPtrList items = m_ccgui->listView->selectedItems(); + QListViewItem *item; + + bool nfs = false; + bool samba = false; + + for ( item = items.first(); item; item = items.next() ) { + + if (KNFSShare::instance()->isDirectoryShared(item->text(0))) + nfs = true; + + if (KSambaShare::instance()->isDirectoryShared(item->text(0))) + samba = true; + } + + NFSFile nfsFile(KNFSShare::instance()->exportsPath()); + if (nfs) { + kdDebug(FILESHARE_DEBUG) << "KFileShareConfig::removeShareBtnClicked: nfs = true" << endl; + nfsFile.load(); + for ( item = items.first(); item; item = items.next() ) { + nfsFile.removeEntryByPath(item->text(0)); + } + } + + SambaFile smbFile(KSambaShare::instance()->smbConfPath(),false); + if (samba) { + kdDebug(FILESHARE_DEBUG) << "KFileShareConfig::removeShareBtnClicked: samba = true" << endl; + smbFile.load(); + for ( item = items.first(); item; item = items.next() ) { + smbFile.removeShareByPath(item->text(0)); + } + } + + PropertiesPage::save(&nfsFile, &smbFile, nfs,samba); + + updateShareListView(); +} + +#include "fileshare.moc" diff --git a/filesharing/simple/fileshare.desktop b/filesharing/simple/fileshare.desktop new file mode 100644 index 00000000..6ed8dd8b --- /dev/null +++ b/filesharing/simple/fileshare.desktop @@ -0,0 +1,169 @@ +[Desktop Entry] +Exec=kcmshell fileshare +Icon=share +Type=Application + +X-KDE-ModuleType=Library +X-KDE-Library=fileshare +X-KDE-ParentApp=kcontrol +X-KDE-RootOnly=true + +Name=File Sharing +Name[be]=ÐŸÑƒÐ±Ð»Ñ–ÐºÐ°Ñ†Ñ‹Ñ Ñ„Ð°Ð¹Ð»Ð°Ñž +Name[bg]=СподелÑне на файлове +Name[bn]=ফাইল ভাগাভাগি +Name[br]=Rannañ restroù +Name[bs]=Dijeljenje datoteka +Name[ca]=Compartició de fitxers +Name[cs]=Sdílení souborů +Name[cy]=Rhannu Ffeiliau +Name[da]=Fildeling +Name[de]=Dateifreigabe +Name[el]=Κοινή χÏήση αÏχείων +Name[eo]=Dosierkomunigado +Name[es]=Compartir archivos +Name[et]=Failijagamine +Name[eu]=Fitxategi partekatzea +Name[fa]=اشتراک پرونده +Name[fi]=Tiedostojen jakaminen +Name[fr]=Partage de fichiers +Name[ga]=Roinnt na gComhad +Name[gl]=Compartición de ficheiros +Name[he]=שיתוף ×§×‘×¦×™× +Name[hu]=Fájlmegosztás +Name[is]=Deila skrám +Name[it]=Condivisione di file +Name[ja]=ファイル共有 +Name[ka]=ფáƒáƒ˜áƒšáƒ—რგáƒáƒ–იáƒáƒ áƒ”ბრ+Name[kk]=Файлдарды ортақтаÑтыру +Name[km]=ការ​ចែក​រំលែក​ឯកសារ +Name[lt]=Dalinimasis bylomis +Name[mk]=Делење на датотеки +Name[nb]=Fildeling +Name[nds]=Dateifreegaav +Name[ne]=फाइल साà¤à¥‡à¤¦à¤¾à¤°à¥€ +Name[nl]=Bestanden delen +Name[nn]=Fildeling +Name[pa]=ਫਾਇਲ ਸ਼ਾਂਠ+Name[pl]=Współdzielenie plików +Name[pt]=Partilha de Ficheiros +Name[pt_BR]=Compartilhamento de Arquivos +Name[ro]=Partajare fiÅŸiere +Name[ru]=СовмеÑтное иÑпользование файлов +Name[se]=Fiilajuohkkin +Name[sk]=Zdieľanie súborov +Name[sl]=Souporaba datotek +Name[sr]=Дељење фајлова +Name[sr@Latn]=Deljenje fajlova +Name[sv]=Filutdelning +Name[ta]=கோபà¯à®ªà¯ பகிரà¯à®µà¯ +Name[tg]=ИÑтифодабарии муштараки файлҳо +Name[tr]=Dosya Paylaşımı +Name[uk]=Спільний доÑтуп до файлів +Name[uz]=Fayl bilan boÊ»lishish +Name[uz@cyrillic]=Файл билан бўлишиш +Name[zh_CN]=文件共享 +Name[zh_HK]=檔案分享 +Name[zh_TW]=檔案分享 + +Comment=Enable or disable file sharing +Comment[be]=Уключыць/выключыць выкарыÑтанне агульных Ñ€ÑÑурÑаў +Comment[bg]=ÐаÑтройване ÑподелÑнето на файлове +Comment[bn]=ফাইল ভাগাভাগি সকà§à¦°à¦¿à§Ÿ অথবা নিষà§à¦•à§à¦°à¦¿à§Ÿ করà§à¦¨ +Comment[bs]=UkljuÄi ili iskljuÄi dijeljenje datoteka +Comment[ca]=Habilita o deshabilita la compartició de fitxers +Comment[cs]=Povolit nebo zakázat sdílení souborů +Comment[da]=Aktivér eller deaktivér fildeling +Comment[de]=Dateifreigabe aktivieren/deaktivieren +Comment[el]=ΕνεÏγοποίηση ή απενεÏγοποίηση της κοινής χÏήσης αÏχείων +Comment[eo]=Åœalti aÅ­ malÅalti dosierkomunigadon +Comment[es]=Habilitar o deshabilitar el compartir archivos +Comment[et]=Failide jagamise lubamine või keelamine +Comment[eu]=Gaitu edo ezgaitu fitxategi partekatzea +Comment[fa]=Ùعال یا غیرÙعال‌سازی اشتراک پرونده +Comment[fi]=Ota tiedostojen jakaminen käyttöön tai pois käytöstä +Comment[fr]=Activer ou désactiver le partage de fichiers +Comment[gl]=Habilitar ou deshabilitar a compartición de fichieros +Comment[he]=×פשר ×ו מנע שיתוף ×§×‘×¦×™× +Comment[hu]=A fájlmegosztás ki-be kapcsolása +Comment[is]=Virkja eða slökkva á skráardeilingu um net +Comment[it]=Abilita o disabilita la condivisione dei file +Comment[ja]=ファイル共有を有効ã¾ãŸã¯ç„¡åŠ¹ã«ã—ã¾ã™ +Comment[ka]=ფáƒáƒ˜áƒšáƒ—რგáƒáƒ–იáƒáƒ áƒ”ბის ჩáƒáƒ áƒ—ვრáƒáƒœ გáƒáƒ›áƒáƒ áƒ—ვრ+Comment[kk]=Файл ортақтаÑтыруды Ñ€Ò±Ò›Ñат ету/етпеу +Comment[km]=អនុញ្ញាហឬ មិន​អនុញ្ញាážâ€‹áž€áž¶ážšâ€‹áž…ែក​រំ​លែក​ឯកសារ +Comment[lt]=Ä®jungti ar iÅ¡jungti bylų dalinimÄ…si +Comment[mk]=Овозможува или оневозможува делење на датотеки +Comment[nb]=SlÃ¥ pÃ¥ og av fildeling +Comment[nds]=Dateifreegaven an- oder utmaken +Comment[ne]=फाइल साà¤à¥‡à¤¦à¤¾à¤°à¥€ सकà¥à¤·à¤® वा अकà¥à¤·à¤® पारà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Bestanden delen in- en uitschakelen +Comment[nn]=SlÃ¥ pÃ¥ eller av fildeling +Comment[pl]=WÅ‚Ä…cza lub wyÅ‚Ä…cza współdzielenie plików +Comment[pt]=Activa ou desactiva a partilha de ficheiros +Comment[pt_BR]=Habilita ou desabilita o compartilhamento de arquivos +Comment[ro]=Activează sau dezactivează partajarea de fiÅŸiere +Comment[ru]=Включить или выключить общий доÑтуп к файлам +Comment[se]=Suova dahje ale suova fiilajuohkkima +Comment[sk]=Povolenie alebo zakázanie zdieľania súborov +Comment[sl]=OmogoÄi ali onemogoÄi souporabo datotek +Comment[sr]=Укључи или иÑкључи дељење фајлова +Comment[sr@Latn]=UkljuÄi ili iskljuÄi deljenje fajlova +Comment[sv]=Aktivera eller inaktivera filutdelning +Comment[ta]=கோபà¯à®ªà¯ பகிரà¯à®µà¯ˆ செயலà¯à®ªà®Ÿà¯à®¤à¯à®¤à¯ அலà¯à®²à®¤à¯ செயலà¯à®¨à¯€à®•à¯à®•à¯ +Comment[tg]=Фаъол Ñохтан Ñ‘ хомӯш кардани иÑтифобадарии муштараки файлҳо +Comment[tr]=Dosya paylaşımını etkinleÅŸtir/kapat +Comment[uk]=Ð£Ð²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ð°Ð±Ð¾ Ð²Ð¸Ð¼Ð¸ÐºÐ°Ð½Ð½Ñ Ñпільного доÑтупу до файлів +Comment[zh_CN]=å¯ç”¨æˆ–ç¦ç”¨æ–‡ä»¶å…±äº« +Comment[zh_HK]=啟用或åœç”¨æª”案分享 +Comment[zh_TW]=開啟或關閉檔案分享 + +Keywords=Share +Keywords[be]=Ðгульны Ñ€ÑÑÑƒÑ€Ñ +Keywords[bg]=наÑтройки, локална, мрежа, ÑподелÑне, share +Keywords[br]=Rannañ +Keywords[bs]=dijeljenje +Keywords[ca]=Compartit +Keywords[cs]=sdílení +Keywords[da]=Del +Keywords[de]=Freigabe +Keywords[el]=ΚοινόχÏηστο +Keywords[eo]=Komunaĵo +Keywords[es]=Compartir +Keywords[et]=Jagamine +Keywords[fa]=مشترک +Keywords[fi]=Jakaminen,Jako +Keywords[fr]=partage +Keywords[gl]=Compartir +Keywords[he]=שיתוף, share +Keywords[hu]=Megosztás +Keywords[is]=Deild +Keywords[it]=Condivisione +Keywords[ja]=共有 +Keywords[ka]=სáƒáƒ–იáƒáƒ áƒ +Keywords[km]=ការ​ចែក​រំ​លែក +Keywords[lt]=Share,dalinimasis,dalintis,pasidalinti +Keywords[mk]=Делена +Keywords[nb]=Dele +Keywords[nds]=Delen,Freegaav,Freegaven +Keywords[ne]=साà¤à¥‡à¤¦à¤¾à¤° +Keywords[nl]=delen +Keywords[nn]=deling +Keywords[pa]=ਸਾਂਠ+Keywords[pl]=współdzielenie,pliki +Keywords[pt]=Partilhar +Keywords[pt_BR]=compartilhamento de arquivos +Keywords[ro]=partajare +Keywords[ru]=Общий реÑÑƒÑ€Ñ +Keywords[sk]=zdieľaný disk,share +Keywords[sl]=Souporaba +Keywords[sr]=Share,дељење +Keywords[sr@Latn]=Share,deljenje +Keywords[sv]=Dela +Keywords[ta]=பகிரà¯à®µà¯ +Keywords[tg]=Муштаракан доштан +Keywords[tr]=PaylaÅŸ +Keywords[uk]=Спільний реÑÑƒÑ€Ñ +Keywords[zh_CN]=Share,共享 + +Categories=Qt;KDE;X-KDE-settings-network;Settings; diff --git a/filesharing/simple/fileshare.h b/filesharing/simple/fileshare.h new file mode 100644 index 00000000..120b93f6 --- /dev/null +++ b/filesharing/simple/fileshare.h @@ -0,0 +1,82 @@ +/* + Copyright (c) 2002 Laurent Montel + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef __fileshare_h__ +#define __fileshare_h__ + +#include + +#include "kcmodule.h" +class QRadioButton; +class QLabel; +class QBoxLayout; +class ControlCenterGUI; +class QListViewItem; + +class KFileShareConfig : public KCModule +{ + Q_OBJECT + + public: + KFileShareConfig(QWidget *parent, const char *name, const QStringList &); + + virtual void load(); + virtual void save(); + virtual void defaults(); + virtual QString quickHelp() const; + + protected: + ControlCenterGUI* m_ccgui; + QString m_fileShareGroup; + bool m_restricted; + bool m_rootPassNeeded; + QString m_smbConf; + void showShareDialog(const KFileItemList & files); + bool addGroupAccessesToFile(const QString & file); + bool removeGroupAccessesFromFile(const QString & file); + bool setGroupAccesses(); + + protected slots: + void configChanged() { emit changed( true ); }; + void updateShareListView(); + void allowedUsersBtnClicked(); + + virtual void addShareBtnClicked(); + virtual void changeShareBtnClicked(); + virtual void removeShareBtnClicked(); + +}; + +class PropertiesPageDlg : public KDialogBase +{ +Q_OBJECT +public: + PropertiesPageDlg(QWidget * parent, KFileItemList files); + ~PropertiesPageDlg() {}; + bool hasChanged(); +protected: + PropertiesPage* m_page; + +protected slots: + virtual void slotOk(); + +}; + + + +#endif diff --git a/filesharing/simple/groupconfigdlg.cpp b/filesharing/simple/groupconfigdlg.cpp new file mode 100644 index 00000000..4d62faa9 --- /dev/null +++ b/filesharing/simple/groupconfigdlg.cpp @@ -0,0 +1,418 @@ +/* + Copyright (c) 2004 Jan Schaefer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "groupconfiggui.h" +#include "groupconfigdlg.h" + + +static QString groupListToString(const QValueList & list); +static QString prettyString(const KUser &user); +static QString fromPrettyString(const QString & s); +static void removeList(QValueList & from, const QValueList & that); +static bool userMod(const QString & user, const QValueList & groups); + + + +GroupConfigDlg::GroupConfigDlg(QWidget * parent, + const QString & fileShareGroup, bool restricted, + bool rootPassNeeded, bool simpleSharing) + : KDialogBase(parent,"groupconfigdlg", true, + i18n("Allowed Users"), Ok|Cancel, Ok, true) , + m_fileShareGroup(fileShareGroup), + m_restricted(restricted) , + m_rootPassNeeded(rootPassNeeded), + m_simpleSharing(simpleSharing) + +{ + initGUI(); + + setFileShareGroup(m_fileShareGroup); +} + +GroupConfigDlg::~GroupConfigDlg() { +} + +void GroupConfigDlg::initUsers() { + m_origUsers = m_fileShareGroup.users(); + m_users = m_origUsers; +} + +void GroupConfigDlg::initGUI() { + m_gui = new GroupConfigGUI(this); + setMainWidget(m_gui); + setFileShareGroup(m_fileShareGroup); + + m_gui->allUsersRadio->setChecked(!m_restricted); + m_gui->groupUsersRadio->setChecked(m_restricted); + m_gui->writeAccessChk->setChecked(!m_rootPassNeeded); + + connect( m_gui->addBtn, SIGNAL(clicked()), + this, SLOT(slotAddUser())); + connect( m_gui->removeBtn, SIGNAL(clicked()), + this, SLOT(slotRemoveUser())); + connect( m_gui->otherGroupBtn, SIGNAL(clicked()), + this, SLOT(slotChangeGroup())); + + if (m_simpleSharing) { + // if simple sharing users never need the root password + m_gui->writeAccessChk->setDisabled(true); + } +} + +void GroupConfigDlg::updateListBox() { + m_gui->listBox->clear(); + QValueList::iterator it; + for ( it = m_users.begin(); it != m_users.end(); ++it ) { + m_gui->listBox->insertItem(prettyString(*it)); + kdDebug(5009) << "GroupConfigDlg::updateListBox: " << (*it).loginName() << endl; + } +} + +QString prettyString(const KUser &user) { + return user.fullName()+" ("+user.loginName()+")"; +} + +QString fromPrettyString(const QString & s) { + // Jan Schaefer (jan) + // i j + int i = s.find('('); + int j = s.find(')'); + QString loginName = s.mid(i+1,j-i-1); + return loginName; +} + +bool GroupConfigDlg::restricted() { + return m_restricted; +} + +void GroupConfigDlg::slotAddUser() { + QValueList allUsers = KUser::allUsers(); + + removeList(allUsers,m_users); + + if (allUsers.count()==0) { + KMessageBox::information(this, + i18n("All users are in the %1 group already.") + .arg(m_fileShareGroup.name())); + return; + } + + QStringList stringList; + + QValueList::iterator it; + for ( it = allUsers.begin(); it != allUsers.end(); ++it ) { + QString s = (*it).fullName()+" ("+(*it).loginName()+")"; + stringList.append(s); + } + + stringList.sort(); + + bool ok; + QString userName = KInputDialog::getItem( + i18n("Select User"), + i18n("Select a user:"), + stringList, + 0, + false, + &ok); + + if (!ok) + return; + + QString loginName = fromPrettyString(userName); + KUser user(loginName); + m_users.append(KUser(loginName)); + updateListBox(); +} + +void removeList(QValueList & from, const QValueList & that) { + QValueList::ConstIterator it; + for ( it = that.begin(); it != that.end(); ++it ) { + from.remove(*it); + } + +} + +bool GroupConfigDlg::addUser(const KUser & user, const KUserGroup & group) { + QValueList groups = user.groups(); + groups.append(group); + if (!userMod(user.loginName(),groups)) { + KMessageBox::sorry(this,i18n("Could not add user '%1' to group '%2'") + .arg(user.loginName()).arg(group.name())); + return false; + } + return true; +} + + +bool GroupConfigDlg::removeUser(const KUser & user, const KUserGroup & group) { + QValueList groups = user.groups(); + groups.remove(group); + if (!userMod(user.loginName(),groups)) { + KMessageBox::sorry(this,i18n("Could not remove user '%1' from group '%2'") + .arg(user.loginName()).arg(group.name())); + return false; + } + return true; +} + +bool GroupConfigDlg::rootPassNeeded() { + return m_rootPassNeeded; +} + +void GroupConfigDlg::slotOk() { + m_restricted = m_gui->groupUsersRadio->isChecked(); + m_rootPassNeeded = ! m_gui->writeAccessChk->isChecked(); + if (m_restricted && !m_fileShareGroup.isValid()) { + KMessageBox::sorry(this,i18n("You have to choose a valid group.")); + return; + } + + QValueList addedUsers = m_users; + removeList(addedUsers,m_origUsers); + QValueList removedUsers = m_origUsers; + removeList(removedUsers,m_users); + + QValueList::ConstIterator it; + for ( it = addedUsers.begin(); it != addedUsers.end(); ++it ) { + addUser(*it, m_fileShareGroup); + } + + for ( it = removedUsers.begin(); it != removedUsers.end(); ++it ) { + removeUser(*it, m_fileShareGroup); + } + + + KDialogBase::slotOk(); +} + +bool userMod(const QString & user, const QValueList & groups) { + KProcess proc; + proc << "usermod" << "-G" << groupListToString(groups) << user; + return proc.start(KProcess::Block) && proc.normalExit(); +} + +void GroupConfigDlg::slotRemoveUser() { + QListBoxItem* item = m_gui->listBox->selectedItem(); + if (!item) + return; + + QString loginName = fromPrettyString(item->text()); + KUser user(loginName); + m_users.remove(KUser(loginName)); + updateListBox(); + m_gui->removeBtn->setEnabled(false); +} + +QString groupListToString(const QValueList & list) { + QValueList::ConstIterator it; + QString result; + + for ( it = list.begin(); it != list.end(); ++it ) { + result+=(*it).name()+","; + } + + // remove last , + result.truncate(result.length()-1); + return result; +} + +void GroupConfigDlg::slotChangeGroup() { + QValueList allGroups = KUserGroup::allGroups(); + + QStringList stringList; + + QValueList::iterator it; + for ( it = allGroups.begin(); it != allGroups.end(); ++it ) { + QString s = (*it).name(); + stringList.append(s); + } + + stringList.sort(); + + KDialogBase* dlg = new KDialogBase(this,"groupconfigdlg", true, + i18n("Allowed Users"), Ok|Cancel, Ok, true); + + QVBox* vbox = dlg->makeVBoxMainWidget(); + + QHBox* hbox = new QHBox(vbox); + QLabel* lbl = new QLabel(i18n("New file share group:"),hbox); + KComboBox* combo = new KComboBox(hbox); + combo->insertStringList(stringList); + combo->setEditable(true); + combo->setCurrentText(m_fileShareGroup.name()); + + QCheckBox* addChk = new QCheckBox( + i18n("Add users from the old file share group to the new one"), + vbox); + + QCheckBox* removeUsersChk = new QCheckBox( + i18n("Remove users from old file share group"), + vbox); + + QCheckBox* removeGroupChk = new QCheckBox( + i18n("Delete the old file share group"), + vbox); + + if (dlg->exec() == QDialog::Accepted) { + QString groupName = combo->currentText(); + if (groupName != m_fileShareGroup.name()) { + QString oldGroup = m_fileShareGroup.name(); + if (allGroups.contains(KUserGroup(groupName))) + setFileShareGroup(KUserGroup(groupName)); + else { + if (!createFileShareGroup(groupName)) { + delete dlg; + return; + } + } + + if (removeGroupChk->isChecked()) + deleteGroup(oldGroup); + else + if (removeUsersChk->isChecked()) + emptyGroup(oldGroup); + + if (addChk->isChecked()) { + addUsersToGroup(m_users,KUserGroup(groupName)); + // reread the users + m_fileShareGroup = KUserGroup(groupName); + } + + + initUsers(); + updateListBox(); + + } + } + + delete dlg; + +} + +void GroupConfigDlg::setFileShareGroup(const KUserGroup & group) { + m_fileShareGroup = group; + + if (m_fileShareGroup.isValid()) { + initUsers(); + updateListBox(); + m_gui->groupUsersRadio->setText( + i18n("Only users of the '%1' group are allowed to share folders") + .arg(m_fileShareGroup.name())); + m_gui->usersGrpBx->setTitle(i18n("Users of '%1' Group") + .arg(m_fileShareGroup.name())); + m_gui->otherGroupBtn->setText(i18n("Change Group...")); + m_gui->usersGrpBx->show(); + } else { + m_gui->groupUsersRadio->setText(i18n("Only users of a certain group are allowed to share folders")); + m_gui->otherGroupBtn->setText(i18n("Choose Group...")); + m_gui->usersGrpBx->hide(); + } + + + +} + +bool GroupConfigDlg::addUsersToGroup(QValueList users,const KUserGroup & group) { + QValueList::ConstIterator it; + bool result = true; + for ( it = users.begin(); it != users.end(); ++it ) { + if (!addUser(*it, group)) + result = false; + } + return result; +} + +bool GroupConfigDlg::emptyGroup(const QString & s) { + if (KMessageBox::No == KMessageBox::questionYesNo(this, + i18n("Do you really want to remove all users from group '%1'?").arg(s), QString::null, KStdGuiItem::del(), KStdGuiItem::cancel())) { + return false; + } + + QValueList allUsers = KUser::allUsers(); + bool result = true; + KUserGroup group(s); + QValueList::ConstIterator it; + for ( it = allUsers.begin(); it != allUsers.end(); ++it ) { + if (!removeUser(*it, group)) + result = false; + } + return result; +} + +bool GroupConfigDlg::deleteGroup(const QString & s) { + if (KMessageBox::No == KMessageBox::questionYesNo(this, + i18n("Do you really want to delete group '%1'?").arg(s), QString::null, KStdGuiItem::del(), KStdGuiItem::cancel())) { + return false; + } + + KProcess proc; + proc << "groupdel" << s; + bool result = proc.start(KProcess::Block) && proc.normalExit(); + if (!result) { + KMessageBox::sorry(this,i18n("Deleting group '%1' failed.").arg(s)); + } + + return result; +} + +bool GroupConfigDlg::createFileShareGroup(const QString & s) { + if (s.isEmpty()) { + KMessageBox::sorry(this,i18n("Please choose a valid group.")); + return false; + } + + if (KMessageBox::No == KMessageBox::questionYesNo(this, + i18n("This group '%1' does not exist. Should it be created?").arg(s), QString::null, i18n("Create"), i18n("Do Not Create"))) + return false; + + //debug("CreateFileShareGroup: "+s); + KProcess proc; + proc << "groupadd" << s; + bool result = proc.start(KProcess::Block) && proc.normalExit(); + if (!result) { + KMessageBox::sorry(this,i18n("Creation of group '%1' failed.").arg(s)); + } else { + setFileShareGroup(KUserGroup(s)); + } + + return result; +} + + +#include "groupconfigdlg.moc" diff --git a/filesharing/simple/groupconfigdlg.h b/filesharing/simple/groupconfigdlg.h new file mode 100644 index 00000000..79df8664 --- /dev/null +++ b/filesharing/simple/groupconfigdlg.h @@ -0,0 +1,68 @@ +/* + Copyright (c) 2004 Jan Schaefer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef GROUPCONFIGDLG_H +#define GROUPCONFIGDLG_H + +#include +#include +#include + +class GroupConfigGUI; + +class GroupConfigDlg : public KDialogBase +{ +Q_OBJECT +public: + GroupConfigDlg(QWidget * parent, const QString & fileShareGroup, bool restricted, + bool rootPassNeeded, bool simpleSharing); + ~GroupConfigDlg(); + KUserGroup fileShareGroup() { return m_fileShareGroup; } + bool restricted(); + bool rootPassNeeded(); +protected: + GroupConfigGUI* m_gui; + + void initGUI(); + void initUsers(); +protected slots: + void slotAddUser(); + void slotRemoveUser(); + void slotChangeGroup(); + void updateListBox(); + virtual void slotOk(); + +private: + bool createFileShareGroup(const QString & s); + bool deleteGroup(const QString & s); + bool emptyGroup(const QString & s); + bool addUser(const KUser & user, const KUserGroup & group); + bool removeUser(const KUser & user, const KUserGroup & group); + bool addUsersToGroup(QValueList users,const KUserGroup & group); + void setFileShareGroup(const KUserGroup & group); + + QValueList m_origUsers; + QValueList m_users; + KUserGroup m_fileShareGroup; + bool m_restricted; + bool m_rootPassNeeded; + bool m_simpleSharing; +}; + +#endif diff --git a/filesharing/simple/groupconfiggui.ui b/filesharing/simple/groupconfiggui.ui new file mode 100644 index 00000000..c12e1d2c --- /dev/null +++ b/filesharing/simple/groupconfiggui.ui @@ -0,0 +1,200 @@ + +GroupConfigGUI + + + GroupConfigGUI + + + + 0 + 0 + 521 + 371 + + + + + unnamed + + + 0 + + + + buttonGroup1 + + + NoFrame + + + + + + + unnamed + + + 0 + + + + allUsersRadio + + + Allow all users to share folders + + + true + + + + + groupUsersRadio + + + Only users of the '%1' group are allowed to share folders + + + + + + + usersGrpBx + + + false + + + Users of '%1' Group + + + + unnamed + + + + listBox + + + + + spacer6 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + removeBtn + + + false + + + Remove User + + + + + addBtn + + + Add User + + + + + writeAccessChk + + + Group members can share folders without root password + + + + + + + layout2 + + + + unnamed + + + + spacer8 + + + Horizontal + + + Expanding + + + + 180 + 20 + + + + + + otherGroupBtn + + + false + + + + 1 + 0 + 0 + 0 + + + + Change Group... + + + + + + + + + groupUsersRadio + toggled(bool) + usersGrpBx + setEnabled(bool) + + + groupUsersRadio + toggled(bool) + otherGroupBtn + setEnabled(bool) + + + listBox + selectionChanged(QListBoxItem*) + GroupConfigGUI + listBox_selectionChanged(QListBoxItem*) + + + + groupconfiggui.ui.h + + + listBox_selectionChanged( QListBoxItem * i ) + + + diff --git a/filesharing/simple/groupconfiggui.ui.h b/filesharing/simple/groupconfiggui.ui.h new file mode 100644 index 00000000..35e82f7f --- /dev/null +++ b/filesharing/simple/groupconfiggui.ui.h @@ -0,0 +1,14 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename functions or slots use +** Qt Designer which will update this file, preserving your code. Create an +** init() function in place of a constructor, and a destroy() function in +** place of a destructor. +*****************************************************************************/ + + +void GroupConfigGUI::listBox_selectionChanged( QListBoxItem * i) +{ + removeBtn->setEnabled(i); +} diff --git a/filesharing/simple/krichtextlabel.cpp b/filesharing/simple/krichtextlabel.cpp new file mode 100644 index 00000000..0157a8f0 --- /dev/null +++ b/filesharing/simple/krichtextlabel.cpp @@ -0,0 +1,112 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005 Waldo Bastian + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include "krichtextlabel.h" + +#include +#include +#include + +#include + +static QString qrichtextify( const QString& text ) +{ + if ( text.isEmpty() || text[0] == '<' ) + return text; + + QStringList lines = QStringList::split('\n', text); + for(QStringList::Iterator it = lines.begin(); it != lines.end(); ++it) + { + *it = QStyleSheet::convertFromPlainText( *it, QStyleSheetItem::WhiteSpaceNormal ); + } + + return lines.join(QString::null); +} + +KRichTextLabel::KRichTextLabel( const QString &text , QWidget *parent, const char *name ) + : QLabel ( parent, name ) { + m_defaultWidth = QMIN(400, KGlobalSettings::desktopGeometry(this).width()*2/5); + setAlignment( Qt::WordBreak ); + setText(text); +} + +KRichTextLabel::KRichTextLabel( QWidget *parent, const char *name ) + : QLabel ( parent, name ) { + m_defaultWidth = QMIN(400, KGlobalSettings::desktopGeometry(this).width()*2/5); + setAlignment( Qt::WordBreak ); +} + +void KRichTextLabel::setDefaultWidth(int defaultWidth) +{ + m_defaultWidth = defaultWidth; + updateGeometry(); +} + +QSizePolicy KRichTextLabel::sizePolicy() const +{ + return QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum, false); +} + +QSize KRichTextLabel::minimumSizeHint() const +{ + QString qt_text = qrichtextify( text() ); + int pref_width = 0; + int pref_height = 0; + QSimpleRichText rt(qt_text, font()); + pref_width = m_defaultWidth; + rt.setWidth(pref_width); + int used_width = rt.widthUsed(); + if (used_width <= pref_width) + { + while(true) + { + int new_width = (used_width * 9) / 10; + rt.setWidth(new_width); + int new_height = rt.height(); + if (new_height > pref_height) + break; + used_width = rt.widthUsed(); + if (used_width > new_width) + break; + } + pref_width = used_width; + } + else + { + if (used_width > (pref_width *2)) + pref_width = pref_width *2; + else + pref_width = used_width; + } + + return QSize(pref_width, rt.height()); +} + +QSize KRichTextLabel::sizeHint() const +{ + return minimumSizeHint(); +} + +void KRichTextLabel::setText( const QString &text ) { + QLabel::setText(text); +} + +void KRichTextLabel::virtual_hook( int, void* ) +{ /*BASE::virtual_hook( id, data );*/ } + +#include "krichtextlabel.moc" diff --git a/filesharing/simple/krichtextlabel.h b/filesharing/simple/krichtextlabel.h new file mode 100644 index 00000000..35087fac --- /dev/null +++ b/filesharing/simple/krichtextlabel.h @@ -0,0 +1,65 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005 Waldo Bastian + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef KRICHTEXTLABEL_H +#define KRICHTEXTLABEL_H + +#include + +#include + +/** + * @short A replacement for QLabel that supports richtext and proper layout management + * + * @author Waldo Bastian + */ + +/* + * QLabel + */ +class KDEUI_EXPORT KRichTextLabel : public QLabel { + Q_OBJECT + +public: + /** + * Default constructor. + */ + KRichTextLabel( QWidget *parent, const char *name = 0 ); + KRichTextLabel( const QString &text, QWidget *parent, const char *name = 0 ); + + int defaultWidth() const { return m_defaultWidth; } + void setDefaultWidth(int defaultWidth); + + virtual QSize minimumSizeHint() const; + virtual QSize sizeHint() const; + QSizePolicy sizePolicy() const; + +public slots: + void setText( const QString & ); + +protected: + int m_defaultWidth; + +protected: + virtual void virtual_hook( int id, void* data ); +private: + class KRichTextLabelPrivate; + KRichTextLabelPrivate *d; +}; + +#endif // KRICHTEXTLABEL_H diff --git a/install-sh b/install-sh new file mode 100755 index 00000000..67c94290 --- /dev/null +++ b/install-sh @@ -0,0 +1,238 @@ +#! /bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. +# + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f "$src" -o -d "$src" ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd "$src" $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/kdenetwork.lsm b/kdenetwork.lsm new file mode 100644 index 00000000..0f60d3f2 --- /dev/null +++ b/kdenetwork.lsm @@ -0,0 +1,11 @@ +Begin4 +Title: kdenetwork +Version: 3.5.10 +Entered-date: 2008-08-26 +Description: Network related utilities for the K Desktop Environment (KDE) +Keywords: KDE X11 desktop Qt +Author: http://bugs.kde.org/ (KDE Bugtracking System) +Primary-site: http://www.kde.org/download/ +Platforms: Unix, Qt +Copying-policy: GPL, Artistic +End diff --git a/kdict/AUTHORS b/kdict/AUTHORS new file mode 100644 index 00000000..b81599fe --- /dev/null +++ b/kdict/AUTHORS @@ -0,0 +1,25 @@ +Developers: +Christian Gebauer +Matthias Hoelzer-Kluepfel + +Documentation: +Christian Gebauer + +Translators: +Wolfram Diestel +Otto Bruggeman +Mattias Newzella +Andris Maziks +Jaime Robles +Pedro Morais +Andrey S. Cherepanov +Claudiu Costin +Erik Kjær Pedersen +Stanislav Visnovsky +Andy Rysin +Dimitris Kamenopoulos +Meni Livne +Matthias Kiefer +Sinohara +Tamas Szanto +Görkem Çetin \ No newline at end of file diff --git a/kdict/ChangeLog b/kdict/ChangeLog new file mode 100644 index 00000000..d9513c75 --- /dev/null +++ b/kdict/ChangeLog @@ -0,0 +1,79 @@ +0.5.3 + + recognize http and ftp urls in cross references + + convert characters that are incompatible with html + like <,>,& into &, etc. + +0.5.2 + + Kdict is now part of the offical kdenetwork package + + support for IPv6 and socks-proxies + + up-to-date docbook-handbook + + code cleanup + +0.5 + + dcop interface + + panel applet + + bugfixes: session management, small problems + with the toolbar editor + +0.4.1 + + fixed CSS-issues with KDE 2.0.x + + fixed saving of results as html file + +0.4 + + ported to KDE2 + + full unicode support + + improved portability, should work in non-linux environments now + + the german gui-translation is missing in this release, + I will add it later on + + documentation is not yet converted to docbook + +0.3.1 + + essential bugfix for older, libc5-based linux systems + (Thanks to Osvaldo Fornaro for testing) + + mini-howto for installing a local DICT-server + +0.3 + + integrated implementation of the DICT-protocol, + dict(1) is no longer needed. + + match mode: At first all matches for a query are + displayed in a separate list, the user can then + choose the most interesting definitions. + + database sets: The user can create individual + compilations of the available databases and use + them in the same way as a single database. + + nicer/more configurable html-layout + + find dialog for locating text in the definitions + + the user can paste text into Kdict with the middle + mouse button. Kdict starts a new query with the pasted text + (analogue to Netscape). + + command to show detailed database information is + implemented ("show info") + + speed improvements: + * faster html-generation + * results are stored in a local cache, so they + are displayed instantly when the users wants to + browse back. + * Kdict holds the connection to the server + for a configurable amount of time. + + various gui improvements: + * improved statusbar + * unified preferences dialog + * all toolbars are now removable and remember + their position. + * new toolbar icons + + ... + +0.2 + fixed some bugs (and introduced new ones ;-) + added various features: + + saving/printing of the queryresult + + query history + + improved html output + + improved command line interface + + html help + + Preferences dialogbox for fonts, colors, etc. + + Synonyms get displayed and handled like hyperlinks + + Automatically lookup of a word in the X clipboard + + ... + +0.1 Initial release diff --git a/kdict/LICENSE b/kdict/LICENSE new file mode 100644 index 00000000..4def90fc --- /dev/null +++ b/kdict/LICENSE @@ -0,0 +1,125 @@ +kdict - The KDE Dictionary Client + +Copyright (C) 2000-2001 Christian Gebauer +copyright (C) 1998 by Matthias Hoelzer + + + Preamble + + The intent of this document is to state the conditions under which a + Package may be copied, such that the Copyright Holder maintains some + semblance of artistic control over the development of the package, + while giving the users of the package the right to use and + distribute the Package in a more-or-less customary fashion, plus the + right to make reasonable modifications. + + Definitions: + + * "Package" refers to the collection of files distributed by the + Copyright Holder, and derivatives of that collection of files + created through textual modification. + + * "Standard Version" refers to such a Package if it has not been + modified, or has been modified in accordance with the wishes of + the Copyright Holder. + + * "Copyright Holder" is whoever is named in the copyright or + copyrights for the package. + + * "You" is you, if you're thinking about copying or distributing + this Package. + + * "Reasonable copying fee" is whatever you can justify on the + basis of media cost, duplication charges, time of people + involved, and so on. (You will not be required to justify it to + the Copyright Holder, but only to the computing community at + large as a market that must bear the fee.) + + * "Freely Available" means that no fee is charged for the item + itself, though there may be fees involved in handling the item. + It also means that recipients of the item may redistribute it + under the same conditions they received it. + + 1. You may make and give away verbatim copies of the source form of + the Standard Version of this Package without restriction, provided + that you duplicate all of the original copyright notices and + associated disclaimers. + + 2. You may apply bug fixes, portability fixes and other + modifications derived from the Public Domain or from the Copyright + Holder. A Package modified in such a way shall still be considered + the Standard Version. + + 3. You may otherwise modify your copy of this Package in any way, + provided that you insert a prominent notice in each changed file + stating how and when you changed that file, and provided that you do + at least ONE of the following: + + a) place your modifications in the Public Domain or + otherwise make them Freely Available, such as by posting + said modifications to Usenet or an equivalent medium, or + placing the modifications on a major archive site such as + ftp.uu.net, or by allowing the Copyright Holder to include + your modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation + or organization. + + c) rename any non-standard executables so the names do not + conflict with standard executables, which must also be + provided, and provide a separate manual page for each + non-standard executable that clearly documents how it + differs from the Standard Version. + + d) make other distribution arrangements with the Copyright + Holder. + + 4. You may distribute the programs of this Package in object code or + executable form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and + library files, together with instructions (in the manual + page or equivalent) on where to get the Standard Version. + + b) accompany the distribution with the machine-readable + source of the Package with your modifications. + + c) accompany any non-standard executables with their + corresponding Standard Version executables, giving the + non-standard executables non-standard names, and clearly + documenting the differences in manual pages (or + equivalent), together with instructions on where to get + the Standard Version. + + d) make other distribution arrangements with the Copyright + Holder. + + 5. You may charge a reasonable copying fee for any distribution of + this Package. You may charge any fee you choose for support of this + Package. You may not charge a fee for this Package itself. However, + you may distribute this Package in aggregate with other (possibly + commercial) programs as part of a larger (possibly commercial) + software distribution provided that you do not advertise this + Package as a product of your own. + + 6. The scripts and library files supplied as input to or produced as + output from the programs of this Package do not automatically fall + under the copyright of this Package, but belong to whomever + generated them, and may be sold commercially, and may be aggregated + with this Package. + + 7. C or perl subroutines supplied by you and linked into this + Package shall not be considered part of this Package. + + 8. The name of the Copyright Holder may not be used to endorse or + promote products derived from this software without specific prior + written permission. + + 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + + The End + + + diff --git a/kdict/Makefile.am b/kdict/Makefile.am new file mode 100644 index 00000000..5cb60755 --- /dev/null +++ b/kdict/Makefile.am @@ -0,0 +1,33 @@ +## Makefile.am for kdict + +KDE_CXXFLAGS = $(USE_THREADS) + +SUBDIRS = applet pics + +bin_PROGRAMS = +lib_LTLIBRARIES = +kdeinit_LTLIBRARIES = kdict.la + +# set the include path for X, qt and KDE +AM_CPPFLAGS = $(all_includes) + +kdict_la_LDFLAGS = $(KDE_RPATH) $(all_libraries) -module $(KDE_PLUGIN) +kdict_la_LIBADD = $(LIB_KFILE) $(LIB_KHTML) $(LIBPTHREAD) $(LIBRESOLV) +kdict_la_SOURCES = dcopinterface.skel main.cpp actions.cpp dict.cpp options.cpp \ + queryview.cpp toplevel.cpp sets.cpp matchview.cpp application.cpp + +# these are the headers for your project +noinst_HEADERS = actions.h dict.h options.h queryview.h toplevel.h sets.h matchview.h application.h + +METASOURCES = AUTO + +messages: rc.cpp + $(XGETTEXT) *.cpp *.h -o $(podir)/kdict.pot + +KDE_ICON = AUTO + +# this is where the kdelnk file will go +xdg_apps_DATA = kdict.desktop + +rcdir = $(kde_datadir)/kdict +rc_DATA = kdictui.rc diff --git a/kdict/README b/kdict/README new file mode 100644 index 00000000..894983ab --- /dev/null +++ b/kdict/README @@ -0,0 +1,47 @@ +Kdict - The KDE Dictionary Client +Copyright (C) 2000-2001 Christian Gebauer +copyright (c) 1998 by Matthias Hoelzer + +maintained by Christian Gebauer + +Homepage: http://www.rhrk.uni-kl.de/~gebauerc/kdict/ + +See LICENSE for the license of this programm. + +************************************************************************** + +Kdict is a graphical client for the DICT Protocol. +It enables you to search through dictionary-like databases +for a word or phrase, then displays suitable definitions. + +Kdict trys to ease basic as well as advanced queries. A separate list +offers a convenient way to deal with the enormous number of matching +words that a advanced query can return. + +The remainder of Kdict's user interface resembles a web browser: +For instance, you can jump to the definition of a synonym by +simply clicking on the highlighted word. The back/forward functionality +is also implemented, enabling you to quickly go back to the result of +previous queries. + +Kdict is able to process the content of the X clipboard, so it's easy +to combine Kdict with your web browser or text editor. + +Kdict is now a real client, no additional software is needed. + +If your machine is behind a firewall, has no permanent internet +connection or the server of dict.org is too slow for you, you can +set up your own local server, all you need is available at +http://www.dict.org. +The advantages of a local server are optimal performance and +the ability to install additional databases of your choice. +The handbook contains a small tutorial for installation +and links to databases. + +************************************************************************** + +For installing Kdict you need: + +- KDE 3.x +- POSIX Threads + diff --git a/kdict/TODO b/kdict/TODO new file mode 100644 index 00000000..5a1d2446 --- /dev/null +++ b/kdict/TODO @@ -0,0 +1,2 @@ +* offline-support: + accessing local database-files without running a dictd-server \ No newline at end of file diff --git a/kdict/actions.cpp b/kdict/actions.cpp new file mode 100644 index 00000000..5ca1aade --- /dev/null +++ b/kdict/actions.cpp @@ -0,0 +1,335 @@ +/* ------------------------------------------------------------- + + actions.cpp (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + DictComboAction, special KAction subclasses used + DictLabelAction, in the toolbar + DictButtonAction + + ------------------------------------------------------------- */ + +#include "actions.h" + +#include +#include + +#include +#include + + +DictComboAction::DictComboAction( const QString &text, QObject *parent, const char *name, + bool editable, bool autoSized ) + : KAction( text, 0, parent, name ), m_editable(editable), m_autoSized(autoSized), m_compMode(KGlobalSettings::completionMode()) +{ +} + + +DictComboAction::~DictComboAction() +{ +} + + +int DictComboAction::plug( QWidget *widget, int index ) +{ + if ( widget->inherits( "KToolBar" ) ) + { + KToolBar* bar = static_cast( widget ); + int id_ = KAction::getToolButtonID(); + + m_combo = new KComboBox(m_editable,bar); + m_combo->setCompletionMode(m_compMode); + + bar->insertWidget( id_, m_combo->sizeHint().width(), m_combo, index ); + bar->setItemAutoSized(id_,m_autoSized); + + if ( m_combo ) { + connect(bar->getCombo(id_), SIGNAL(activated(const QString&)), SLOT(slotComboActivated(const QString&))); + connect(bar->getCombo(id_), SIGNAL(activated(int)), SLOT(slotComboActivated(int))); + + if (m_editable) + m_combo->setInsertionPolicy( QComboBox::NoInsertion ); + } + + addContainer( bar, id_ ); + connect( bar, SIGNAL( destroyed() ), this, SLOT( slotDestroyed() ) ); + return containerCount() - 1; + } + + return -1; +} + + +void DictComboAction::unplug( QWidget *widget ) +{ + if ( widget->inherits( "KToolBar" ) ) + { + KToolBar *bar = (KToolBar *)widget; + + int idx = findContainer( bar ); + + if ( idx != -1 ) + { + bar->removeItem( itemId( idx ) ); + removeContainer( idx ); + } + + return; + } +} + + +QWidget* DictComboAction::widget() +{ + return m_combo; +} + + +void DictComboAction::setFocus() +{ + if (m_combo) + m_combo->setFocus(); +} + + +QString DictComboAction::currentText() const +{ + if (m_combo) + return m_combo->currentText(); + else + return QString::null; +} + +void DictComboAction::selectAll() +{ + if (m_combo) + { + m_combo->lineEdit()->selectAll(); + m_combo->lineEdit()->setFocus(); + } +} + + +void DictComboAction::setEditText(const QString &s) +{ + if (m_combo && m_editable) + m_combo->setEditText(s); +} + + +void DictComboAction::setCurrentItem(int index) +{ + if (m_combo) + m_combo->setCurrentItem(index); +} + + +void DictComboAction::clearEdit() +{ + if (m_combo && m_editable) + m_combo->clearEdit(); +} + + +void DictComboAction::clear() +{ + if (m_combo) { + m_combo->clear(); + if (m_editable && m_combo->completionObject()) + m_combo->completionObject()->clear(); + } +} + + +void DictComboAction::setList(QStringList items) +{ + if (m_combo) { + m_combo->clear(); + m_combo->insertStringList(items); + if (m_editable && m_combo->completionObject()) + m_combo->completionObject()->setItems(items); + if (!m_autoSized) + m_combo->setFixedWidth(m_combo->sizeHint().width()); + } +} + + +KGlobalSettings::Completion DictComboAction::completionMode() +{ + if (m_combo) + return m_combo->completionMode(); + else + return m_compMode; + } + + +void DictComboAction::setCompletionMode(KGlobalSettings::Completion mode) +{ + if (m_combo) + m_combo->setCompletionMode(mode); + else + m_compMode = mode; +} + + +void DictComboAction::slotComboActivated(int i) +{ + emit(activated(i)); +} + + +void DictComboAction::slotComboActivated(const QString &s) +{ + emit(activated(s)); +} + + +//********************************************************************************* + + +DictLabelAction::DictLabelAction( const QString &text, QObject *parent, const char *name ) + : KAction( text, 0, parent, name ) +{ +} + + +DictLabelAction::~DictLabelAction() +{ +} + + +int DictLabelAction::plug( QWidget *widget, int index ) +{ + if ( widget->inherits( "KToolBar" ) ) + { + KToolBar *tb = (KToolBar *)widget; + + int id = KAction::getToolButtonID(); + + QLabel *label = new QLabel( text(), widget, "kde toolbar widget" ); + label->setMinimumWidth(label->sizeHint().width()); + label->setBackgroundMode( Qt::PaletteButton ); + label->setAlignment(AlignCenter | AlignVCenter); + label->adjustSize(); + + tb->insertWidget( id, label->width(), label, index ); + + addContainer( tb, id ); + + connect( tb, SIGNAL( destroyed() ), this, SLOT( slotDestroyed() ) ); + + m_label = label; + + return containerCount() - 1; + } + + return -1; +} + + +void DictLabelAction::unplug( QWidget *widget ) +{ + if ( widget->inherits( "KToolBar" ) ) + { + KToolBar *bar = (KToolBar *)widget; + + int idx = findContainer( bar ); + + if ( idx != -1 ) + { + bar->removeItem( itemId( idx ) ); + removeContainer( idx ); + } + + return; + } +} + + +void DictLabelAction::setBuddy(QWidget *buddy) +{ + if (m_label && buddy) + m_label->setBuddy(buddy); +} + + +//********************************************************************************* + + +DictButtonAction::DictButtonAction( const QString& text, QObject* receiver, + const char* slot, QObject* parent, const char* name ) + : KAction( text, 0, receiver, slot, parent, name ) +{ +} + + +DictButtonAction::~DictButtonAction() +{ +} + + +int DictButtonAction::plug( QWidget *widget, int index ) +{ + if ( widget->inherits( "KToolBar" ) ) + { + KToolBar *tb = (KToolBar *)widget; + + int id = KAction::getToolButtonID(); + + QPushButton *button = new QPushButton( text(), widget ); + button->adjustSize(); + connect(button,SIGNAL(clicked()),this,SLOT(activate())); + tb->insertWidget( id, button->width(), button, index ); + + addContainer( tb, id ); + + connect( tb, SIGNAL( destroyed() ), this, SLOT( slotDestroyed() ) ); + + m_button = button; + + return containerCount() - 1; + } + + return -1; +} + + +void DictButtonAction::unplug( QWidget *widget ) +{ + if ( widget->inherits( "KToolBar" ) ) + { + KToolBar *bar = (KToolBar *)widget; + + int idx = findContainer( bar ); + + if ( idx != -1 ) + { + bar->removeItem( itemId( idx ) ); + removeContainer( idx ); + } + } +} + + +int DictButtonAction::widthHint() +{ + if (m_button) + return m_button->sizeHint().width(); + else + return 0; +} + + +void DictButtonAction::setWidth(int width) +{ + if (m_button) + m_button->setFixedWidth(width); +} + +#include "actions.moc" diff --git a/kdict/actions.h b/kdict/actions.h new file mode 100644 index 00000000..568a9f7c --- /dev/null +++ b/kdict/actions.h @@ -0,0 +1,111 @@ +/* ------------------------------------------------------------- + + actions.h (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + DictComboAction, special KAction subclasses used + DictLabelAction, in the toolbar + DictButtonAction + + ------------------------------------------------------------- */ + +#ifndef _ACTIONS_H_ +#define _ACTIONS_H_ + +#include +#include +#include +#include + +class KComboBox; +class QLabel; +class QPushButton; + + +class DictComboAction : public KAction +{ + Q_OBJECT + + public: + DictComboAction( const QString& text, QObject* parent, + const char* name, bool editable, bool autoSized ); + ~DictComboAction(); + + virtual int plug( QWidget *w, int index = -1 ); + virtual void unplug( QWidget *w ); + + QWidget* widget(); + void setFocus(); + + QString currentText() const; + void selectAll(); + void setEditText(const QString &s); + void setCurrentItem(int index); + void clearEdit(); + + void clear(); + void setList(QStringList items); + + KGlobalSettings::Completion completionMode(); + void setCompletionMode(KGlobalSettings::Completion mode); + + signals: + void activated(int); + void activated(const QString&); + + private slots: + void slotComboActivated(int); + void slotComboActivated(const QString&); + + private: + QGuardedPtr m_combo; + bool m_editable, m_autoSized; + KGlobalSettings::Completion m_compMode; +}; + + +class DictLabelAction : public KAction +{ + Q_OBJECT + + public: + DictLabelAction( const QString &text, QObject *parent = 0, const char *name = 0 ); + ~DictLabelAction(); + + virtual int plug( QWidget *widget, int index = -1 ); + virtual void unplug( QWidget *widget ); + + void setBuddy(QWidget *buddy); + + private: + QGuardedPtr m_label; + +}; + + +class DictButtonAction : public KAction +{ + Q_OBJECT + + public: + DictButtonAction( const QString& text, QObject* receiver, + const char* slot, QObject* parent, const char* name ); + ~DictButtonAction(); + + virtual int plug( QWidget *w, int index = -1 ); + virtual void unplug( QWidget *w ); + + int widthHint(); + void setWidth(int width); + + private: + QGuardedPtr m_button; +}; + +#endif diff --git a/kdict/applet/Makefile.am b/kdict/applet/Makefile.am new file mode 100644 index 00000000..db1f4bc2 --- /dev/null +++ b/kdict/applet/Makefile.am @@ -0,0 +1,19 @@ +INCLUDES = $(all_includes) + +kde_module_LTLIBRARIES = kdict_panelapplet.la + +kdict_panelapplet_la_SOURCES = kdictapplet.cpp + +METASOURCES = AUTO +noinst_HEADERS = kdictapplet.h + +lnkdir = $(kde_datadir)/kicker/applets +lnk_DATA = kdictapplet.desktop + +EXTRA_DIST = $(lnk_DATA) + +kdict_panelapplet_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module +kdict_panelapplet_la_LIBADD = $(LIB_KSYCOCA) $(LIB_KDEUI) + +messages: rc.cpp + $(XGETTEXT) *.cpp *.h -o $(podir)/kdictapplet.pot diff --git a/kdict/applet/kdictapplet.cpp b/kdict/applet/kdictapplet.cpp new file mode 100644 index 00000000..ad907864 --- /dev/null +++ b/kdict/applet/kdictapplet.cpp @@ -0,0 +1,405 @@ +/* ------------------------------------------------------------- + + kdictapplet.h (part of The KDE Dictionary Client) + + Copyright (C) 2001 Christian Gebauer + + The applet is loosely based on the "Run" applet included in KDE. + Copyright (c) 2000 Matthias Elter (Artistic License) + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + PopupBox helper class + DictApplet a small kicker-applet + + ------------------------------------------------------------- */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kdictapplet.h" + +//********* PopupBox ******************************************** + +PopupBox::PopupBox() + : QHBox(0, 0, WStyle_Customize | WType_Popup ), popupEnabled(true) +{ +} + + +PopupBox::~PopupBox() +{} + + +bool PopupBox::showBox() +{ + if (!popupEnabled) // prevents that the popup is shown again immediatly + return false; + else { + show(); + return true; + } +} + + +void PopupBox::hideEvent(QHideEvent *) +{ + emit(hidden()); + popupEnabled = false; + QTimer::singleShot(100, this, SLOT(enablePopup())); +} + + +void PopupBox::enablePopup() +{ + popupEnabled = true; +} + + +//********* DictApplet ******************************************** + +extern "C" +{ + KDE_EXPORT KPanelApplet* init(QWidget *parent, const QString& configFile) + { + KGlobal::locale()->insertCatalogue("kdictapplet"); + return new DictApplet(configFile, KPanelApplet::Stretch, 0, parent, "kdictapplet"); + } +} + + +DictApplet::DictApplet(const QString& configFile, Type type, int actions, QWidget *parent, const char *name) + : KPanelApplet(configFile, type, actions, parent, name), waiting(0) +{ + // first the widgets for a horizontal panel + baseWidget = new QWidget(this); + QGridLayout *baseLay = new QGridLayout(baseWidget,2,6,0,1); + + textLabel = new QLabel(i18n("Dictionary:"), baseWidget); + textLabel->setBackgroundOrigin(AncestorOrigin); + QFont f(textLabel->font()); + f.setPixelSize(12); + textLabel->setFont(f); + baseLay->addWidget(textLabel,0,1); + QToolTip::add(textLabel,i18n("Look up a word or phrase with Kdict")); + + iconLabel = new QLabel(baseWidget); + iconLabel->setBackgroundOrigin(AncestorOrigin); + QPixmap pm = KGlobal::iconLoader()->loadIcon("kdict", KIcon::Panel, KIcon::SizeSmall, KIcon::DefaultState, 0L, true); + iconLabel->setPixmap(pm); + baseLay->addWidget(iconLabel,1,0); + iconLabel->setAlignment(Qt::AlignCenter | Qt::AlignVCenter); + iconLabel->setFixedWidth(pm.width()+4); + QToolTip::add(iconLabel,i18n("Look up a word or phrase with Kdict")); + + f.setPixelSize(10); + clipboardBtn = new QPushButton(i18n("C"),baseWidget); + clipboardBtn->setBackgroundOrigin(AncestorOrigin); + clipboardBtn->setFont(f); + clipboardBtn->setFixedSize(16,16); + connect(clipboardBtn, SIGNAL(clicked()), SLOT(queryClipboard())); + baseLay->addWidget(clipboardBtn,0,3); + QToolTip::add(clipboardBtn,i18n("Define selected text")); + + defineBtn = new QPushButton(i18n("D"),baseWidget); + defineBtn->setBackgroundOrigin(AncestorOrigin); + defineBtn->setFont(f); + defineBtn->setFixedSize(16,16); + defineBtn->setEnabled(false); + connect(defineBtn, SIGNAL(clicked()), SLOT(startDefine())); + baseLay->addWidget(defineBtn,0,4); + QToolTip::add(defineBtn,i18n("Define word/phrase")); + + matchBtn = new QPushButton(i18n("M"),baseWidget); + matchBtn->setBackgroundOrigin(AncestorOrigin); + matchBtn->setFont(f); + matchBtn->setFixedSize(16,16); + matchBtn->setEnabled(false); + connect(matchBtn, SIGNAL(clicked()), SLOT(startMatch())); + baseLay->addWidget(matchBtn,0,5); + QToolTip::add(matchBtn,i18n("Find matching definitions")); + + completionObject = new KCompletion(); + + internalCombo = new KHistoryCombo(baseWidget); + internalCombo->setBackgroundOrigin(AncestorOrigin); + internalCombo->setCompletionObject(completionObject); + internalCombo->setFocus(); + internalCombo->clearEdit(); + internalCombo->lineEdit()->installEventFilter( this ); + connect(internalCombo, SIGNAL(returnPressed(const QString&)), SLOT(startQuery(const QString&))); + connect(internalCombo, SIGNAL(textChanged(const QString&)), SLOT(comboTextChanged(const QString&))); + QToolTip::add(internalCombo,i18n("Look up a word or phrase with Kdict")); + + baseLay->addMultiCellWidget(internalCombo,1,1,1,5); + + baseLay->setColStretch(2,1); + + // widgets for a vertical panel + verticalBtn = new QPushButton(this); + connect(verticalBtn, SIGNAL(pressed()), SLOT(showExternalCombo())); + QToolTip::add(verticalBtn,i18n("Look up a word or phrase with Kdict")); + + popupBox = new PopupBox(); + popupBox->setFixedSize(160, 22); + connect(popupBox, SIGNAL(hidden()), SLOT(externalComboHidden())); + externalCombo = new KHistoryCombo(popupBox); + externalCombo->setCompletionObject(completionObject); + connect(externalCombo, SIGNAL(returnPressed(const QString&)), SLOT(startQuery(const QString&))); + externalCombo->setFixedSize(160, externalCombo->sizeHint().height()); + + connect(internalCombo, SIGNAL(completionModeChanged(KGlobalSettings::Completion)), + this, SLOT(updateCompletionMode(KGlobalSettings::Completion))); + connect(externalCombo, SIGNAL(completionModeChanged(KGlobalSettings::Completion)), + this, SLOT(updateCompletionMode(KGlobalSettings::Completion))); + + // restore history and completion list + KConfig *c = config(); + c->setGroup("General"); + + QStringList list = c->readListEntry("Completion list"); + completionObject->setItems(list); + int mode = c->readNumEntry("Completion mode", + KGlobalSettings::completionMode()); + internalCombo->setCompletionMode((KGlobalSettings::Completion)mode); + externalCombo->setCompletionMode((KGlobalSettings::Completion)mode); + + list = c->readListEntry("History list"); + internalCombo->setHistoryItems(list); + externalCombo->setHistoryItems(list); +} + + +DictApplet::~DictApplet() +{ + // save history and completion list + KConfig *c = config(); + c->setGroup("General"); + + QStringList list = completionObject->items(); + c->writeEntry("Completion list", list); + c->writeEntry("Completion mode", (int) internalCombo->completionMode()); + + list = internalCombo->historyItems(); + c->writeEntry("History list", list); + c->sync(); + + delete completionObject; +} + + +int DictApplet::widthForHeight(int height) const +{ + if (height >= 38) + return textLabel->sizeHint().width()+55; + else + return textLabel->sizeHint().width()+25; +} + + +int DictApplet::heightForWidth(int width) const +{ + return width; +} + + +void DictApplet::resizeEvent(QResizeEvent*) +{ + if (orientation() == Horizontal) { + verticalBtn->hide(); + baseWidget->show(); + baseWidget->setFixedSize(width(),height()); + + if (height() < internalCombo->sizeHint().height()) + internalCombo->setFixedHeight(height()); + else + internalCombo->setFixedHeight(internalCombo->sizeHint().height()); + + if (height() >= 38) { + textLabel->show(); + clipboardBtn->show(); + defineBtn->show(); + matchBtn->show(); + iconLabel->hide(); + internalCombo->setFixedWidth(width()); + } else { + textLabel->hide(); + clipboardBtn->hide(); + defineBtn->hide(); + matchBtn->hide(); + iconLabel->show(); + internalCombo->setFixedWidth(width()-iconLabel->width()-1); + } + + baseWidget->updateGeometry(); + } else { // orientation() == Vertical + verticalBtn->show(); + baseWidget->hide(); + verticalBtn->setFixedSize(width(),width()); + + KIcon::StdSizes sz = width() < 32 ? KIcon::SizeSmall : (width() < 48 ? KIcon::SizeMedium : KIcon::SizeLarge); + QPixmap pm = KGlobal::iconLoader()->loadIcon("kdict", KIcon::Panel, sz, KIcon::DefaultState, 0L, true); + verticalBtn->setPixmap(pm); + } +} + + +bool DictApplet::eventFilter( QObject *o, QEvent * e) +{ + if (e->type() == QEvent::MouseButtonRelease) + emit requestFocus(); + + return KPanelApplet::eventFilter(o, e); +} + + +void DictApplet::sendCommand(const QCString &fun, const QString &data) +{ + if (waiting > 0) { + waiting = 1; + delayedFunc = fun.copy(); + delayedData = data; + return; + } + + DCOPClient *client = kapp->dcopClient(); + if (!client->isApplicationRegistered("kdict")) { + KApplication::startServiceByDesktopName("kdict"); + waiting = 1; + delayedFunc = fun.copy(); + delayedData = data; + QTimer::singleShot(100, this, SLOT(sendDelayedCommand())); + return; + } else { + QCStringList list = client->remoteObjects("kdict"); + if (list.findIndex("KDictIface")==-1) { + waiting = 1; + delayedFunc = fun.copy(); + delayedData = data; + QTimer::singleShot(100, this, SLOT(sendDelayedCommand())); + return; + } + } + + client->send("kdict","default",fun,data); +} + + +void DictApplet::sendDelayedCommand() +{ + if (waiting > 100) { // timeout after ten seconds + waiting = 0; + return; + } + + DCOPClient *client = kapp->dcopClient(); + if (!client->isApplicationRegistered("kdict")) { + waiting++; + QTimer::singleShot(100, this, SLOT(sendDelayedCommand())); + return; + } else { + QCStringList list = client->remoteObjects("kdict"); + if (list.findIndex("KDictIface")==-1) { + waiting++; + QTimer::singleShot(100, this, SLOT(sendDelayedCommand())); + return; + } + } + + client->send("kdict","default",delayedFunc,delayedData); + waiting = 0; +} + + +void DictApplet::startQuery(const QString &s) +{ + QString query = s.stripWhiteSpace(); + if (query.isEmpty()) + return; + + internalCombo->addToHistory(query); + externalCombo->addToHistory(query); + internalCombo->clearEdit(); + externalCombo->clearEdit(); + + sendCommand("definePhrase(QString)",query); + + if (orientation() == Vertical) + popupBox->hide(); +} + + +void DictApplet::comboTextChanged(const QString &s) +{ + defineBtn->setEnabled(!s.isEmpty()); + matchBtn->setEnabled(!s.isEmpty()); +} + + +void DictApplet::queryClipboard() +{ + sendCommand("defineClipboardContent()",QString::null); +} + + +void DictApplet::startDefine() +{ + startQuery(internalCombo->currentText()); +} + + +void DictApplet::startMatch() +{ + QString query = internalCombo->currentText().stripWhiteSpace(); + internalCombo->addToHistory(query); + externalCombo->addToHistory(query); + internalCombo->clearEdit(); + externalCombo->clearEdit(); + + sendCommand("matchPhrase(QString)",query); +} + + +void DictApplet::showExternalCombo() +{ + QPoint p; + if (position() == pLeft) + p = mapToGlobal(QPoint(-popupBox->width()-1, 0)); + else + p = mapToGlobal(QPoint(width()+1, 0)); + popupBox->move(p); + if (popupBox->showBox()) + externalCombo->setFocus(); + else + verticalBtn->setDown(false); +} + + +void DictApplet::externalComboHidden() +{ + verticalBtn->setDown(false); +} + +void DictApplet::updateCompletionMode(KGlobalSettings::Completion mode) +{ + internalCombo->setCompletionMode(mode); + externalCombo->setCompletionMode(mode); +} + +//-------------------------------- + +#include "kdictapplet.moc" diff --git a/kdict/applet/kdictapplet.desktop b/kdict/applet/kdictapplet.desktop new file mode 100644 index 00000000..23d26f3f --- /dev/null +++ b/kdict/applet/kdictapplet.desktop @@ -0,0 +1,147 @@ +[Desktop Entry] +Comment=Lookup phrases in a dictionary +Comment[af]=Opkyk frases in 'n woordeboek +Comment[ar]=ابحث عن الكلمات ÙÙŠ القاموس +Comment[az]=LüğətdÉ™n kÉ™limÉ™lÉ™rÉ™ baxın +Comment[be]=Пошук выразаў у Ñлоўніку +Comment[bg]=ТърÑене на фрази в речника +Comment[bn]=à¦à¦•à¦Ÿà¦¿ অভিধানে শবà§à¦¦à¦¸à¦®à¦·à§à¦Ÿà¦¿à¦° খোà¦à¦œ করো +Comment[bs]=Potražite fraze u rjeÄniku +Comment[ca]=Cerca expressions en un diccionari +Comment[cs]=VyhledávaÄ pojmů ve slovníku +Comment[cy]=Edrych am ddywediadau mewn geiriadur +Comment[da]=SlÃ¥ sætninger op i en ordbog +Comment[de]=Ausdrücke in einem Lexikon nachschlagen +Comment[el]=Αναζήτηση φÏάσεων σε λεξικό +Comment[eo]=Serĉi kapvortojn en vortaroj +Comment[es]=Busca expresiones en un diccionario +Comment[et]=Fraaside otsimine sõnaraamatust +Comment[eu]=Bilatu esaldiak hiztegi batean +Comment[fa]=مراجعه به واژه‌نامه برای عبارتها +Comment[fi]=Hae lauseita sanakirjasta +Comment[fr]=Recherche de phrases dans un dictionnaire +Comment[ga]=Cuardaigh frásaí i bhfoclóir +Comment[gl]=Buscar expresións no diccionario +Comment[he]=חיפוש ×‘×™×˜×•×™×™× ×‘×ž×™×œ×•×Ÿ +Comment[hi]=शबà¥à¤¦à¤•à¥‹à¤¶ में वाकà¥à¤¯à¤¾à¤‚शों को देखे +Comment[hr]=Potraži fraze u rjeÄniku +Comment[hu]=SzótárkezelÅ‘ alkalmazás +Comment[is]=Fletta upp í orðabók +Comment[it]=Cerca frasi in un dizionario +Comment[ja]=辞書ã§èªžå¥ã‚’検索 +Comment[ka]=ფრáƒáƒ–ების ლექსიკáƒáƒœáƒ¨áƒ˜ ძებნრ+Comment[kk]=Сөздікте іздеу +Comment[km]=រក​មើល​ប្រយោគ​នៅ​ក្នុង​វចនានុក្រម​មួយ +Comment[ko]=사전ì—ì„œ 글귀를 찾아ì¤ë‹ˆë‹¤ +Comment[lt]=IeÅ¡koti frazių žodyne +Comment[lv]=SkatÄ«t frÄzes vÄrdnÄ«cÄ +Comment[mk]=Барајте за изрази во речник +Comment[mn]=Толь бичигÑÑÑ Ò¯Ð³ харах +Comment[ms]=Mencari frasa di dalam kamus +Comment[mt]=Fittex frażijiet fid-dizzjunarju +Comment[nb]=Finn fraser i en ordbok +Comment[nds]=Begrepen in en Wöörbook nakieken +Comment[ne]=शबà¥à¤¦à¤•à¥‹à¤¶à¤®à¤¾ वाकà¥à¤¯à¤¾à¤‚श खोजी गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Zoek bepaalde uitdrukkingen op in een woordenboek +Comment[nn]=SlÃ¥ opp ord i ei ordboka +Comment[nso]=Nyaka mantsu ka gare ga pukuntsu +Comment[pl]=Szukanie zwrotów w sÅ‚owniku +Comment[pt]=Procura por frases num dicionário +Comment[pt_BR]=Busca frases em um dicionário +Comment[ro]=Caută fraze într-un dicÅ£ionar +Comment[ru]=ПоиÑк Ñлов в Ñловаре +Comment[se]=Oza sániid sátnegirjjis +Comment[sk]=Hľadanie fráz v slovníku +Comment[sl]=Iskanje Izrazov v slovarju +Comment[sr]=Потражите фразе у речнику +Comment[sr@Latn]=Potražite fraze u reÄniku +Comment[sv]=Leta upp fraser i en ordlista +Comment[ta]=ஒர௠அகராதியிலà¯à®³à¯à®³ தேடறà¯à®šà¯Šà®±à¯à®¤à¯Šà®Ÿà®°à¯à®•à®³à¯ +Comment[tg]=ҶуÑтуҷӯи ибораҳо дар луғат +Comment[th]=ค้นหาวลีในพจนานุà¸à¸£à¸¡ +Comment[tr]=Sözlükten kelimelere bakın +Comment[uk]=Пошук фраз у Ñловнику +Comment[ven]=Todani fhungo kha bugu talutshedza maipfi +Comment[wa]=Cweri des frÃ¥zes dins on diccionaire +Comment[xh]=Jonga amabinzana kwincwadi enekcazelo zamagama +Comment[zh_CN]=在字典中查找短语 +Comment[zh_HK]=在字典中尋找片語 +Comment[zh_TW]=在字典中尋找片語 +Comment[zu]=Bheka amagama kwisichazamagama +Icon= +Name=Dictionary +Name[af]=Woordeboek +Name[ar]=القاموس +Name[az]=Lüğət +Name[be]=Слоўнік +Name[bg]=Речник +Name[bn]=অভিধান +Name[br]=Geriadur +Name[bs]=RjeÄnik +Name[ca]=Diccionari +Name[cs]=Slovník +Name[cy]=Geiriadur +Name[da]=Ordbog +Name[de]=Lexikon +Name[el]=Λεξικό +Name[eo]=Vortaro +Name[es]=Diccionario +Name[et]=Sõnaraamat +Name[eu]=Hiztegia +Name[fa]=واژه‌نامه +Name[fi]=Sanakirja +Name[fr]=Dictionnaire +Name[ga]=Foclóir +Name[gl]=Diccionario +Name[he]=מילון +Name[hi]=शबà¥à¤¦à¤•à¥‹à¤¶ +Name[hr]=RjeÄnik +Name[hu]=Szótár +Name[is]=Orðabók +Name[it]=Dizionario +Name[ja]=辞書 +Name[ka]=ლექსიკáƒáƒœáƒ˜ +Name[kk]=Сөздік +Name[km]=វចនានុក្រម +Name[ko]=사전 +Name[lt]=Žodynas +Name[lv]=VÄrdnÄ«ca +Name[mk]=Речник +Name[mn]=Толь бичиг +Name[ms]=Kamus +Name[mt]=Dizzjunarju +Name[nb]=Ordbok +Name[nds]=Wöörbook +Name[ne]=शबà¥à¤¦à¤•à¥‹à¤¶ +Name[nl]=Woordenboek +Name[nn]=Ordbok +Name[nso]=Pukuntsu +Name[pa]=ਸ਼ਬਦ-ਕੋਸ਼ +Name[pl]=SÅ‚ownik +Name[pt]=Dicionário +Name[pt_BR]=Dicionário +Name[ro]=DicÅ£ionar +Name[ru]=Словарь +Name[se]=Sátnegirji +Name[sk]=Slovník +Name[sl]=Slovar +Name[sr]=Речник +Name[sr@Latn]=ReÄnik +Name[sv]=Ordlista +Name[ta]=அகராதி +Name[tg]=Луғат +Name[th]=พจนานุà¸à¸£à¸¡ +Name[tr]=Sözlük +Name[uk]=Словник +Name[uz]=LugÊ»at +Name[uz@cyrillic]=Луғат +Name[ven]=Bugu yau talutshedza maipfi +Name[wa]=Motî +Name[xh]=Incwadi eneenkcazelo zamagama +Name[zh_CN]=å­—å…¸ +Name[zh_HK]=å­—å…¸ +Name[zh_TW]=å­—å…¸ +Name[zu]=Isichazamagama +Icon=kdict +X-KDE-Library=kdict_panelapplet +X-KDE-UniqueApplet=true diff --git a/kdict/applet/kdictapplet.h b/kdict/applet/kdictapplet.h new file mode 100644 index 00000000..a9738148 --- /dev/null +++ b/kdict/applet/kdictapplet.h @@ -0,0 +1,101 @@ +/* ------------------------------------------------------------- + + kdictapplet.h (part of The KDE Dictionary Client) + + Copyright (C) 2001 Christian Gebauer + + The applet is loosely based on the "Run" applet included in KDE. + Copyright (c) 2000 Matthias Elter (Artistic License) + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + PopupBox helper class + DictApplet a small kicker-applet + + ------------------------------------------------------------- */ + +#ifndef _DICTAPPLET_H_ +#define _DICTAPPLET_H_ + +#include +#include + +class QLabel; +class QPushButton; +class KHistoryCombo; + + +//********* PopupBox ******************************************** + +class PopupBox : public QHBox +{ + Q_OBJECT + +public: + PopupBox(); + ~PopupBox(); + + bool showBox(); + +signals: + void hidden(); + +public slots: + void enablePopup(); + +protected: + void hideEvent(QHideEvent *); + +private: + bool popupEnabled; + +}; + +//********* DictApplet ******************************************** + +class DictApplet : public KPanelApplet +{ + Q_OBJECT + +public: + DictApplet(const QString& configFile, Type t = Stretch, int actions = 0, QWidget *parent = 0, const char *name = 0); + virtual ~DictApplet(); + + int widthForHeight(int height) const; + int heightForWidth(int width) const; + +protected: + void resizeEvent(QResizeEvent*); + bool eventFilter( QObject *, QEvent * ); + + void sendCommand(const QCString &fun, const QString &data); + +protected slots: + void sendDelayedCommand(); + void startQuery(const QString&); + void comboTextChanged(const QString&); + void queryClipboard(); + void startDefine(); + void startMatch(); + void showExternalCombo(); + void externalComboHidden(); + void updateCompletionMode(KGlobalSettings::Completion mode); + +private: + KHistoryCombo *internalCombo, *externalCombo; + KCompletion *completionObject; + QLabel *textLabel, *iconLabel; + QPushButton *verticalBtn, *clipboardBtn, *defineBtn, *matchBtn; + QWidget *baseWidget; + PopupBox *popupBox; + + int waiting; + QCString delayedFunc; + QString delayedData; + +}; + +#endif diff --git a/kdict/application.cpp b/kdict/application.cpp new file mode 100644 index 00000000..28e3a398 --- /dev/null +++ b/kdict/application.cpp @@ -0,0 +1,71 @@ +/* ------------------------------------------------------------- + + application.cpp (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- */ + +#include +#include +#include + +#include "application.h" +#include "toplevel.h" + + +Application::Application() + : KUniqueApplication() +{ + m_mainWindow = new TopLevel( 0, "mainWindow" ); +} + + +Application::~Application() +{ + delete m_mainWindow; +} + + +int Application::newInstance() +{ + kdDebug(5004) << "Application::newInstance()" << endl; + KUniqueApplication::newInstance(); + + // process parameters... + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + m_mainWindow->show(); + + if (args->isSet("clipboard")) + { + m_mainWindow->defineClipboard(); + } + else + { + if (args->count()>0) + { + QString phrase; + for (int i=0;icount();i++) + { + phrase += QString::fromLocal8Bit(args->arg(i)); + if (i+1 < args->count()) + phrase += " "; + } + m_mainWindow->define(phrase); + } + else + { + m_mainWindow->normalStartup(); + } + } + + return 0; +} + +//-------------------------------- + +#include "application.moc" diff --git a/kdict/application.h b/kdict/application.h new file mode 100644 index 00000000..eddb7f44 --- /dev/null +++ b/kdict/application.h @@ -0,0 +1,39 @@ +/* ------------------------------------------------------------- + + application.h (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- */ + +#ifndef APPLICATION_H +#define APPLICATION_H + +#include +#include + +#define KDICT_VERSION "0.6" + +class TopLevel; + +class Application : public KUniqueApplication +{ + Q_OBJECT + + public: + Application(); + ~Application(); + + /** Create new instance of Kdict. Make the existing + main window active if Kdict is already running */ + int newInstance(); + + private: + QGuardedPtr m_mainWindow; + +}; + +#endif diff --git a/kdict/dcopinterface.h b/kdict/dcopinterface.h new file mode 100644 index 00000000..f088a842 --- /dev/null +++ b/kdict/dcopinterface.h @@ -0,0 +1,54 @@ +/* ------------------------------------------------------------- + + dcopinterface.h (part of The KDE Dictionary Client) + Copyright (C) 2000-2001 Christian Gebauer + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + KDictDCOPInterface abstract base class that defines the + DCOP interface of Kdict + + ------------------------------------------------------------- */ + +#ifndef _DCOPINTERFACE_H +#define _DCOPINTERFACE_H + +#include +#include + +class KDictIface : virtual public DCOPObject +{ + K_DCOP + +k_dcop: + + /** Quit Kdict **/ + virtual void quit() = 0; + virtual void makeActiveWindow() = 0; + + /** define/match a word/phrase **/ + virtual void definePhrase(QString phrase) = 0; + virtual void matchPhrase(QString phrase) = 0; + virtual void defineClipboardContent() = 0; + virtual void matchClipboardContent() = 0; + + /** get info **/ + virtual QStringList getDatabases() = 0; + virtual QString currentDatabase() = 0; + virtual QStringList getStrategies() = 0; + virtual QString currentStrategy() = 0; + + /** set current database/strategy (returns true on success) **/ + virtual bool setDatabase(QString db) = 0; + virtual bool setStrategy(QString strategy) = 0; + + /** navigate in history **/ + virtual bool historyGoBack() = 0; + virtual bool historyGoForward() = 0; + +}; + +#endif diff --git a/kdict/dict.cpp b/kdict/dict.cpp new file mode 100644 index 00000000..b36f1ac1 --- /dev/null +++ b/kdict/dict.cpp @@ -0,0 +1,1632 @@ +/* ------------------------------------------------------------- + + dict.cpp (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + (C) by Matthias Hölzer 1998 + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + JobData used for data transfer between Client and Interface + DictAsyncClient all network related stuff happens here in asynchrous thread + DictInterface interface for DictAsyncClient, job management + + ------------------------------------------------------------- */ + +#include + +#include "application.h" +#include "options.h" +#include "dict.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +//********* JobData ****************************************** + + +JobData::JobData(QueryType Ntype,bool NnewServer,QString const& Nserver,int Nport, + int NidleHold, int Ntimeout, int NpipeSize, QString const& Nencoding, bool NAuthEnabled, + QString const& Nuser, QString const& Nsecret, unsigned int NheadLayout) + : type(Ntype), error(ErrNoErr), canceled(false), numFetched(0), newServer(NnewServer),server(Nserver), port(Nport), + timeout(Ntimeout), pipeSize(NpipeSize), idleHold(NidleHold), encoding(Nencoding), authEnabled(NAuthEnabled), + user(Nuser), secret(Nsecret), headLayout(NheadLayout) +{} + + +//********* DictAsyncClient ************************************* + +DictAsyncClient::DictAsyncClient(int NfdPipeIn, int NfdPipeOut) +: job(0L), inputSize(10000), fdPipeIn(NfdPipeIn), + fdPipeOut(NfdPipeOut), tcpSocket(-1), idleHold(0) +{ + input = new char[inputSize]; +} + + +DictAsyncClient::~DictAsyncClient() +{ + if (-1!=tcpSocket) + doQuit(); + delete [] input; +} + + +void* DictAsyncClient::startThread(void* pseudoThis) +{ + DictAsyncClient* newthis = (DictAsyncClient*) (pseudoThis); + + if (0!=pthread_setcanceltype(PTHREAD_CANCEL_ENABLE,NULL)) + qWarning("pthread_setcanceltype failed!"); + if (0!= pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL)) + qWarning("pthread_setcanceltype failed!"); + + signal(SIGPIPE,SIG_IGN); // ignore sigpipe + + newthis->waitForWork(); + return NULL; +} + + +void DictAsyncClient::insertJob(JobData *newJob) +{ + if (!job) // don't overwrite existing job pointer + job = newJob; +} + + +void DictAsyncClient::removeJob() +{ + job = 0L; +} + + +void DictAsyncClient::waitForWork() +{ + fd_set fdsR,fdsE; + timeval tv; + int selectRet; + char buf; + + while (true) { + if (tcpSocket != -1) { // we are connected, hold the connection for xx secs + FD_ZERO(&fdsR); + FD_SET(fdPipeIn, &fdsR); + FD_SET(tcpSocket, &fdsR); + FD_ZERO(&fdsE); + FD_SET(tcpSocket, &fdsE); + tv.tv_sec = idleHold; + tv.tv_usec = 0; + selectRet = KSocks::self()->select(FD_SETSIZE, &fdsR, NULL, &fdsE, &tv); + if (selectRet == 0) { + doQuit(); // nothing happend... + } else { + if (((selectRet > 0)&&(!FD_ISSET(fdPipeIn,&fdsR)))||(selectRet == -1)) + closeSocket(); + } + } + + do { + FD_ZERO(&fdsR); + FD_SET(fdPipeIn, &fdsR); + } while (select(FD_SETSIZE, &fdsR, NULL, NULL, NULL)<0); // don't get tricked by signals + + clearPipe(); + + if (job) { + if ((tcpSocket!=-1)&&(job->newServer)) + doQuit(); + + codec = QTextCodec::codecForName(job->encoding.latin1()); + input[0] = 0; //terminate string + thisLine = input; + nextLine = input; + inputEnd = input; + timeout = job->timeout; + idleHold = job->idleHold; + + if (tcpSocket==-1) + openConnection(); + + if (tcpSocket!=-1) { // connection is ready + switch (job->type) { + case JobData::TDefine : + define(); + break; + case JobData::TGetDefinitions : + getDefinitions(); + break; + case JobData::TMatch : + match(); + break; + case JobData::TShowDatabases : + showDatabases(); + break; + case JobData::TShowDbInfo : + showDbInfo(); + break; + case JobData::TShowStrategies : + showStrategies(); + break; + case JobData::TShowInfo : + showInfo(); + break; + case JobData::TUpdate : + update(); + } + } + clearPipe(); + } + if (write(fdPipeOut,&buf,1) == -1) // emit stopped signal + ::perror( "waitForJobs()" ); + } +} + + +void DictAsyncClient::define() +{ + QString command; + + job->defines.clear(); + QStringList::iterator it; + for (it = job->databases.begin(); it != job->databases.end(); ++it) { + command = "define "; + command += *it; + command += " \""; + command += job->query; + command += "\"\r\n"; + job->defines.append(command); + } + + if (!getDefinitions()) + return; + + if (job->numFetched == 0) { + job->strategy = "."; + if (!match()) + return; + job->result = QString::null; + if (job->numFetched == 0) { + resultAppend("\n

    \n"); + resultAppend(i18n("No definitions found for \'%1'.").arg(job->query)); + resultAppend("

    \n"); + } else { + // html header... + resultAppend("\n

    \n"); + resultAppend(i18n("No definitions found for \'%1\'. Perhaps you mean:").arg(job->query)); + resultAppend("

    \n\n"); + + QString lastDb; + QStringList::iterator it; + for (it = job->matches.begin(); it != job->matches.end(); ++it) { + int pos = (*it).find(' '); + if (pos != -1) { + if (lastDb != (*it).left(pos)) { + if (lastDb.length() > 0) + resultAppend("\n"); + lastDb = (*it).left(pos); + resultAppend("
    ");
    +            resultAppend(lastDb);
    +            resultAppend(":
    ");
    +          }
    +          if ((*it).length() > (unsigned int)pos+2) {
    +            resultAppend("");
    +            resultAppend((*it).mid(pos+2, (*it).length()-pos-3));
    +            resultAppend(" ");
    +          }
    +        }
    +      }
    +      resultAppend("\n
    \n"); + job->numFetched = 0; + } + } +} + + +QString htmlString(const QString &raw) +{ + unsigned int len=raw.length(); + QString ret; + + for (unsigned int i=0; i' : ret+=">"; break; + default : ret+=raw[i]; + } + } + + return ret; +} + + +QString generateDefineLink(const QString &raw) +{ + QRegExp http("http://[^\\s<>()\"|\\[\\]{}]+"); + QRegExp ftp("ftp://[^\\s<>()\"|\\[\\]{}]+"); + int matchPos=0, matchLen=0; + bool httpMatch=false; + QString ret; + + matchPos = http.search(raw); + matchLen = http.matchedLength(); + if (-1 != matchPos) { + httpMatch = true; + } else { + matchPos = ftp.search(raw); + matchLen = ftp.matchedLength(); + httpMatch = false; + } + + if (-1 != matchPos) { + ret = htmlString(raw.left(matchPos)); + ret += ""; + ret += htmlString(raw.mid(matchPos, matchLen)); + ret += ""; + ret += htmlString(raw.right(raw.length()-matchLen-matchPos)); + } else { + ret = ""; + ret += htmlString(raw); + ret += ""; + } + + return ret; +} + + +bool DictAsyncClient::getDefinitions() +{ + QCString lastDb,bracketBuff; + QStrList hashList; + char *s; + int defCount,response; + + // html header... + resultAppend("\n"); + + while (job->defines.count()>0) { + defCount = 0; + cmdBuffer = ""; + do { + QStringList::iterator it = job->defines.begin(); + cmdBuffer += codec->fromUnicode(*it); + defCount++; + job->defines.remove(it); + } while ((job->defines.count()>0)&&((int)cmdBuffer.length()pipeSize)); + + if (!sendBuffer()) + return false; + + for (;defCount > 0;defCount--) { + if (!getNextResponse(response)) + return false; + switch (response) { + case 552: // define: 552 No match + break; + case 150: { // define: 150 n definitions retrieved - definitions follow + bool defineDone = false; + while (!defineDone) { + if (!getNextResponse(response)) + return false; + switch (response) { + case 151: { // define: 151 word database name - text follows + char *db = strchr(thisLine, '\"'); + if (db) + db = strchr(db+1, '\"'); + char *dbdes = 0; + if (db) { + db+=2; // db points now on database name + dbdes = strchr(db,' '); + if (dbdes) { + dbdes[0] = 0; // terminate database name + dbdes+=2; // dbdes points now on database description + } + } else { + job->error = JobData::ErrServerError; + job->result = QString::null; + resultAppend(thisLine); + doQuit(); + return false; + } + + int oldResPos = job->result.length(); + + if (((job->headLayout<2)&&(lastDb!=db))||(job->headLayout==2)) { + lastDb = db; + resultAppend("

    \n"); + if (dbdes) + resultAppend(codec->toUnicode(dbdes,strlen(dbdes)-1)); + resultAppend(" ["); + resultAppend(db); + resultAppend("]

    \n"); + } else + if (job->headLayout==1) + resultAppend("
    \n"); + + resultAppend("

    \n"); + + KMD5 context; + bool bodyDone = false; + while (!bodyDone) { + if (!getNextLine()) + return false; + char *line = thisLine; + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double periode into one + else + if (line[1]==0) + bodyDone = true; + } + if (!bodyDone) { + context.update(QCString(line)); + if (!bracketBuff.isEmpty()) { + s = strchr(line,'}'); + if (!s) + resultAppend(bracketBuff.data()); + else { + s[0] = 0; + bracketBuff.remove(0,1); // remove '{' + bracketBuff += line; + line = s+1; + resultAppend(generateDefineLink(codec->toUnicode(bracketBuff))); + } + bracketBuff = ""; + } + s = strchr(line,'{'); + while (s) { + resultAppend(htmlString(codec->toUnicode(line,s-line))); + line = s; + s = strchr(line,'}'); + if (s) { + s[0] = 0; + line++; + resultAppend(generateDefineLink(codec->toUnicode(line))); + line = s+1; + s = strchr(line,'{'); + } else { + bracketBuff = line; + bracketBuff += "\n"; + line = 0; + s = 0; + } + } + if (line) { + resultAppend(htmlString(codec->toUnicode(line))); + resultAppend("\n"); + } + } + } + resultAppend("

    \n"); + + if (hashList.find(context.hexDigest())>=0) // duplicate?? + job->result.truncate(oldResPos); // delete the whole definition + else { + hashList.append(context.hexDigest()); + job->numFetched++; + } + + break; } + case 250: { // define: 250 ok (optional timing information here) + defineDone = true; + break; } + default: { + handleErrors(); + return false; } + } + } + break; } + default: + handleErrors(); + return false; + } + } + } + + resultAppend("\n"); + return true; +} + + +bool DictAsyncClient::match() +{ + QStringList::iterator it = job->databases.begin(); + int response; + cmdBuffer = ""; + + while (it != job->databases.end()) { + int send = 0; + do { + cmdBuffer += "match "; + cmdBuffer += codec->fromUnicode(*(it)); + cmdBuffer += " "; + cmdBuffer += codec->fromUnicode(job->strategy); + cmdBuffer += " \""; + cmdBuffer += codec->fromUnicode(job->query); + cmdBuffer += "\"\r\n"; + send++; + ++it; + } while ((it != job->databases.end())&&((int)cmdBuffer.length()pipeSize)); + + if (!sendBuffer()) + return false; + + for (;send > 0;send--) { + if (!getNextResponse(response)) + return false; + switch (response) { + case 552: // match: 552 No match + break; + case 152: { // match: 152 n matches found - text follows + bool matchDone = false; + while (!matchDone) { + if (!getNextLine()) + return false; + char *line = thisLine; + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double period into one + else + if (line[1]==0) + matchDone = true; + } + if (!matchDone) { + job->numFetched++; + job->matches.append(codec->toUnicode(line)); + } + } + if (!nextResponseOk(250)) // match: "250 ok ..." + return false; + break; } + default: + handleErrors(); + return false; + } + } + } + + return true; +} + + +void DictAsyncClient::showDatabases() +{ + cmdBuffer = "show db\r\n"; + + if (!sendBuffer()) + return; + + if (!nextResponseOk(110)) // show db: "110 n databases present - text follows " + return; + + // html header... + resultAppend("\n

    \n"); + resultAppend(i18n("Available Databases:")); + resultAppend("\n

    \n\n"); + + bool done(false); + char *line; + while (!done) { + if (!getNextLine()) + return; + line = thisLine; + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double periode into one + else + if (line[1]==0) + done = true; + } + if (!done) { + resultAppend("\n"); + } + } + resultAppend("
    toUnicode(line,space-line));
    +        resultAppend("\">");
    +        resultAppend(codec->toUnicode(line,space-line));
    +        resultAppend("
    ");
    +        line = space+1;
    +        if (line[0]=='"') {
    +          line++;                  // remove double quote
    +          char *quote = strchr(line, '\"');
    +          if (quote)
    +            quote[0]=0;
    +        }
    +      } else {       // hmmm, malformated line...
    +        resultAppend("\">
    "); + } + resultAppend(line); + resultAppend("
    \n"); + + if (!nextResponseOk(250)) // end of transmission: "250 ok ..." + return; +} + + +void DictAsyncClient::showDbInfo() +{ + cmdBuffer = "show info "; + cmdBuffer += codec->fromUnicode(job->query); + cmdBuffer += "\r\n"; + + if (!sendBuffer()) + return; + + if (!nextResponseOk(112)) // show info db: "112 database information follows" + return; + + // html header... + resultAppend("\n

    \n"); + resultAppend(i18n("Database Information [%1]:").arg(job->query)); + resultAppend("

    \n

    \n"); + + bool done(false); + char *line; + while (!done) { + if (!getNextLine()) + return; + line = thisLine; + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double periode into one + else + if (line[1]==0) + done = true; + } + if (!done) { + resultAppend(line); + resultAppend("\n"); + } + } + + resultAppend("

    \n"); + + if (!nextResponseOk(250)) // end of transmission: "250 ok ..." + return; +} + + +void DictAsyncClient::showStrategies() +{ + cmdBuffer = "show strat\r\n"; + + if (!sendBuffer()) + return; + + if (!nextResponseOk(111)) // show strat: "111 n strategies present - text follows " + return; + + // html header... + resultAppend("\n

    \n"); + resultAppend(i18n("Available Strategies:")); + resultAppend("\n

    \n\n"); + + bool done(false); + char *line; + while (!done) { + if (!getNextLine()) + return; + line = thisLine; + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double periode into one + else + if (line[1]==0) + done = true; + } + if (!done) { + resultAppend("\n"); + } + } + resultAppend("
    ");
    +      char *space = strchr(line,' ');
    +      if (space) {
    +        resultAppend(codec->toUnicode(line,space-line));
    +        resultAppend("
    ");
    +        line = space+1;
    +        if (line[0]=='"') {
    +          line++;                  // remove double quote
    +          char *quote = strchr(line, '\"');
    +          if (quote)
    +            quote[0]=0;
    +        }
    +      } else {       // hmmm, malformated line...
    +        resultAppend("
    ");
    +      }
    +      resultAppend(line);
    +      resultAppend("
    \n"); + + if (!nextResponseOk(250)) // end of transmission: "250 ok ..." + return; +} + + +void DictAsyncClient::showInfo() +{ + cmdBuffer = "show server\r\n"; + + if (!sendBuffer()) + return; + + if (!nextResponseOk(114)) // show server: "114 server information follows" + return; + + // html header... + resultAppend("\n

    \n"); + resultAppend(i18n("Server Information:")); + resultAppend("\n

    \n

    \n"); + + bool done(false); + char *line; + while (!done) { + if (!getNextLine()) + return; + line = thisLine; + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double periode into one + else + if (line[1]==0) + done = true; + } + if (!done) { + resultAppend(line); + resultAppend("\n"); + } + } + + resultAppend("

    \n"); + + if (!nextResponseOk(250)) // end of transmission: "250 ok ..." + return; +} + + +void DictAsyncClient::update() +{ + cmdBuffer = "show strat\r\nshow db\r\n"; + + if (!sendBuffer()) + return; + + if (!nextResponseOk(111)) // show strat: "111 n strategies present - text follows " + return; + + bool done(false); + char *line; + while (!done) { + if (!getNextLine()) + return; + line = thisLine; + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double periode into one + else + if (line[1]==0) + done = true; + } + if (!done) { + char *space = strchr(line,' '); + if (space) space[0] = 0; // terminate string, hack ;-) + job->strategies.append(codec->toUnicode(line)); + } + } + + if (!nextResponseOk(250)) // end of transmission: "250 ok ..." + return; + + if (!nextResponseOk(110)) // show db: "110 n databases present - text follows " + return; + + done = false; + while (!done) { + if (!getNextLine()) + return; + line = thisLine; + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double periode into one + else + if (line[1]==0) + done = true; + } + if (!done) { + char *space = strchr(line,' '); + if (space) space[0] = 0; // terminate string, hack ;-) + job->databases.append(codec->toUnicode(line)); + } + } + + if (!nextResponseOk(250)) // end of transmission: "250 ok ..." + return; +} + + +// connect, handshake and authorization +void DictAsyncClient::openConnection() +{ + if (job->server.isEmpty()) { + job->error = JobData::ErrBadHost; + return; + } + + KExtendedSocket ks; + + ks.setAddress(job->server, job->port); + ks.setTimeout(job->timeout); + if (ks.connect() < 0) { + if (ks.status() == IO_LookupError) + job->error = JobData::ErrBadHost; + else if (ks.status() == IO_ConnectError) { + job->result = QString::null; + resultAppend(KExtendedSocket::strError(ks.status(), errno)); + job->error = JobData::ErrConnect; + } else if (ks.status() == IO_TimeOutError) + job->error = JobData::ErrTimeout; + else { + job->result = QString::null; + resultAppend(KExtendedSocket::strError(ks.status(), errno)); + job->error = JobData::ErrCommunication; + } + + closeSocket(); + return; + } + tcpSocket = ks.fd(); + ks.release(); + + if (!nextResponseOk(220)) // connect: "220 text capabilities msg-id" + return; + + cmdBuffer = "client \"Kdict "; + cmdBuffer += KDICT_VERSION; + cmdBuffer += "\"\r\n"; + + if (job->authEnabled) + if (strstr(thisLine,"auth")) { // skip auth if not supported + char *msgId = strrchr(thisLine,'<'); + + if ((!msgId)||(!job->user.length())) { + job->error = JobData::ErrAuthFailed; + closeSocket(); + return; + } + + KMD5 context; + context.update(QCString(msgId)); + context.update(job->secret.local8Bit()); + + cmdBuffer += "auth " + job->user.local8Bit() + " "; + cmdBuffer += context.hexDigest(); + cmdBuffer += "\r\n"; + } + + if (!sendBuffer()) + return; + + if (!nextResponseOk(250)) // client: "250 ok ..." + return; + + if (job->authEnabled) + if (!nextResponseOk(230)) // auth: "230 Authentication successful" + return; +} + + +void DictAsyncClient::closeSocket() +{ + if (-1 != tcpSocket) { + ::close(tcpSocket); + tcpSocket = -1; + } +} + + +// send "quit" without timeout, without checks, close connection +void DictAsyncClient::doQuit() +{ + fd_set fdsW; + timeval tv; + + FD_ZERO(&fdsW); + FD_SET(tcpSocket, &fdsW); + tv.tv_sec = 0; + tv.tv_usec = 0; + int ret = KSocks::self()->select(FD_SETSIZE, NULL, &fdsW, NULL, &tv); + + if (ret > 0) { // we can write... + cmdBuffer = "quit\r\n"; + int todo = cmdBuffer.length(); + KSocks::self()->write(tcpSocket,&cmdBuffer.data()[0],todo); + } + closeSocket(); +} + + +// used by getNextLine() +bool DictAsyncClient::waitForRead() +{ + fd_set fdsR,fdsE; + timeval tv; + + int ret; + do { + FD_ZERO(&fdsR); + FD_SET(fdPipeIn, &fdsR); + FD_SET(tcpSocket, &fdsR); + FD_ZERO(&fdsE); + FD_SET(tcpSocket, &fdsE); + FD_SET(fdPipeIn, &fdsE); + tv.tv_sec = timeout; + tv.tv_usec = 0; + ret = KSocks::self()->select(FD_SETSIZE, &fdsR, NULL, &fdsE, &tv); + } while ((ret<0)&&(errno==EINTR)); // don't get tricked by signals + + if (ret == -1) { // select failed + if (job) { + job->result = QString::null; + resultAppend(strerror(errno)); + job->error = JobData::ErrCommunication; + } + closeSocket(); + return false; + } + if (ret == 0) { // Nothing happend, timeout + if (job) + job->error = JobData::ErrTimeout; + doQuit(); + return false; + } + if (ret > 0) { + if (FD_ISSET(fdPipeIn,&fdsR)) { // stop signal + doQuit(); + return false; + } + if (FD_ISSET(tcpSocket,&fdsE)||FD_ISSET(fdPipeIn,&fdsE)) { // broken pipe, etc + if (job) { + job->result = QString::null; + resultAppend(i18n("The connection is broken.")); + job->error = JobData::ErrCommunication; + } + closeSocket(); + return false; + } + if (FD_ISSET(tcpSocket,&fdsR)) // all ok + return true; + } + + if (job) { + job->result = QString::null; + job->error = JobData::ErrCommunication; + } + closeSocket(); + return false; +} + + +// used by sendBuffer() & connect() +bool DictAsyncClient::waitForWrite() +{ + fd_set fdsR,fdsW,fdsE; + timeval tv; + + int ret; + do { + FD_ZERO(&fdsR); + FD_SET(fdPipeIn, &fdsR); + FD_SET(tcpSocket, &fdsR); + FD_ZERO(&fdsW); + FD_SET(tcpSocket, &fdsW); + FD_ZERO(&fdsE); + FD_SET(tcpSocket, &fdsE); + FD_SET(fdPipeIn, &fdsE); + tv.tv_sec = timeout; + tv.tv_usec = 0; + ret = KSocks::self()->select(FD_SETSIZE, &fdsR, &fdsW, &fdsE, &tv); + } while ((ret<0)&&(errno==EINTR)); // don't get tricked by signals + + if (ret == -1) { // select failed + if (job) { + job->result = QString::null; + resultAppend(strerror(errno)); + job->error = JobData::ErrCommunication; + } + closeSocket(); + return false; + } + if (ret == 0) { // nothing happend, timeout + if (job) + job->error = JobData::ErrTimeout; + closeSocket(); + return false; + } + if (ret > 0) { + if (FD_ISSET(fdPipeIn,&fdsR)) { // stop signal + doQuit(); + return false; + } + if (FD_ISSET(tcpSocket,&fdsR)||FD_ISSET(tcpSocket,&fdsE)||FD_ISSET(fdPipeIn,&fdsE)) { // broken pipe, etc + if (job) { + job->result = QString::null; + resultAppend(i18n("The connection is broken.")); + job->error = JobData::ErrCommunication; + } + closeSocket(); + return false; + } + if (FD_ISSET(tcpSocket,&fdsW)) // all ok + return true; + } + if (job) { + job->result = QString::null; + job->error = JobData::ErrCommunication; + } + closeSocket(); + return false; +} + + +// remove start/stop signal +void DictAsyncClient::clearPipe() +{ + fd_set fdsR; + timeval tv; + int selectRet; + char buf; + + tv.tv_sec = 0; + tv.tv_usec = 0; + do { + FD_ZERO(&fdsR); + FD_SET(fdPipeIn,&fdsR); + if (1==(selectRet=select(FD_SETSIZE,&fdsR,NULL,NULL,&tv))) + if ( ::read(fdPipeIn, &buf, 1 ) == -1 ) + ::perror( "clearPipe()" ); + } while (selectRet == 1); +} + + +bool DictAsyncClient::sendBuffer() +{ + int ret; + int todo = cmdBuffer.length(); + int done = 0; + + while (todo > 0) { + if (!waitForWrite()) + return false; + ret = KSocks::self()->write(tcpSocket,&cmdBuffer.data()[done],todo); + if (ret <= 0) { + if (job) { + job->result = QString::null; + resultAppend(strerror(errno)); + job->error = JobData::ErrCommunication; + } + closeSocket(); + return false; + } else { + done += ret; + todo -= ret; + } + } + return true; +} + + +// set thisLine to next complete line of input +bool DictAsyncClient::getNextLine() +{ + thisLine = nextLine; + nextLine = strstr(thisLine,"\r\n"); + if (nextLine) { // there is another full line in the inputbuffer + nextLine[0] = 0; // terminate string + nextLine[1] = 0; + nextLine+=2; + return true; + } + unsigned int div = inputEnd-thisLine+1; // hmmm, I need to fetch more input from the server... + memmove(input,thisLine,div); // save last, incomplete line + thisLine = input; + inputEnd = input+div-1; + do { + if ((inputEnd-input) > 9000) { + job->error = JobData::ErrMsgTooLong; + closeSocket(); + return false; + } + if (!waitForRead()) + return false; + + int received; + do { + received = KSocks::self()->read(tcpSocket, inputEnd, inputSize-(inputEnd-input)-1); + } while ((received<0)&&(errno==EINTR)); // don't get tricked by signals + + if (received <= 0) { + job->result = QString::null; + resultAppend(i18n("The connection is broken.")); + job->error = JobData::ErrCommunication; + closeSocket(); + return false; + } + inputEnd += received; + inputEnd[0] = 0; // terminate *char + } while (!(nextLine = strstr(thisLine,"\r\n"))); + nextLine[0] = 0; // terminate string + nextLine[1] = 0; + nextLine+=2; + return true; +} + + +// reads next line and checks the response code +bool DictAsyncClient::nextResponseOk(int code) +{ + if (!getNextLine()) + return false; + if (strtol(thisLine,0L,0)!=code) { + handleErrors(); + return false; + } + return true; +} + + +// reads next line and returns the response code +bool DictAsyncClient::getNextResponse(int &code) +{ + if (!getNextLine()) + return false; + code = strtol(thisLine,0L,0); + return true; +} + + +void DictAsyncClient::handleErrors() +{ + int len = strlen(thisLine); + if (len>80) + len = 80; + job->result = QString::null; + resultAppend(codec->toUnicode(thisLine,len)); + + switch (strtol(thisLine,0L,0)) { + case 420: + case 421: + job->error = JobData::ErrNotAvailable; // server unavailable + break; + case 500: + case 501: + job->error = JobData::ErrSyntax; // syntax error + break; + case 502: + case 503: + job->error = JobData::ErrCommandNotImplemented; // command not implemented + break; + case 530: + job->error = JobData::ErrAccessDenied; // access denied + break; + case 531: + job->error = JobData::ErrAuthFailed; // authentication failed + break; + case 550: + case 551: + job->error = JobData::ErrInvalidDbStrat; // invalid strategy/database + break; + case 554: + job->error = JobData::ErrNoDatabases; // no databases + break; + case 555: + job->error = JobData::ErrNoStrategies; // no strategies + break; + default: + job->error = JobData::ErrServerError; + } + doQuit(); +} + + +void DictAsyncClient::resultAppend(const char* str) +{ + if (job) + job->result += codec->toUnicode(str); +} + + +void DictAsyncClient::resultAppend(QString str) +{ + if (job) + job->result += str; +} + + + +//********* DictInterface ****************************************** + +DictInterface::DictInterface() +: newServer(false), clientDoneInProgress(false) +{ + if (::pipe(fdPipeIn ) == -1 ) { + perror( "Creating in pipe" ); + KMessageBox::error(global->topLevel, i18n("Internal error:\nFailed to open pipes for internal communication.")); + kapp->exit(1); + } + if (::pipe(fdPipeOut ) == -1 ) { + perror( "Creating out pipe" ); + KMessageBox::error(global->topLevel, i18n("Internal error:\nFailed to open pipes for internal communication.")); + kapp->exit(1); + } + + if (-1 == fcntl(fdPipeIn[0],F_SETFL,O_NONBLOCK)) { // make socket non-blocking + perror("fcntl()"); + KMessageBox::error(global->topLevel, i18n("Internal error:\nFailed to open pipes for internal communication.")); + kapp->exit(1); + } + + if (-1 == fcntl(fdPipeOut[0],F_SETFL,O_NONBLOCK)) { // make socket non-blocking + perror("fcntl()"); + KMessageBox::error(global->topLevel, i18n("Internal error:\nFailed to open pipes for internal communication.")); + kapp->exit(1); + } + + notifier = new QSocketNotifier(fdPipeIn[0],QSocketNotifier::Read,this); + connect(notifier,SIGNAL(activated(int)),this,SLOT(clientDone())); + + // initialize the KSocks stuff in the main thread, otherwise we get + // strange effects on FreeBSD + (void) KSocks::self(); + + client = new DictAsyncClient(fdPipeOut[0],fdPipeIn[1]); + if (0!=pthread_create(&threadID,0,&(client->startThread),client)) { + KMessageBox::error(global->topLevel, i18n("Internal error:\nUnable to create thread.")); + kapp->exit(1); + } + + jobList.setAutoDelete(true); +} + + +DictInterface::~DictInterface() +{ + disconnect(notifier,SIGNAL(activated(int)),this,SLOT(clientDone())); + + if (0!=pthread_cancel(threadID)) + kdWarning() << "pthread_cancel failed!" << endl; + if (0!=pthread_join(threadID,NULL)) + kdWarning() << "pthread_join failed!" << endl; + delete client; + + if ( ::close( fdPipeIn[0] ) == -1 ) { + perror( "Closing fdPipeIn[0]" ); + } + if ( ::close( fdPipeIn[1] ) == -1 ) { + perror( "Closing fdPipeIn[1]" ); + } + if ( ::close( fdPipeOut[0] ) == -1 ) { + perror( "Closing fdPipeOut[0]" ); + } + if ( ::close( fdPipeOut[1] ) == -1 ) { + perror( "Closing fdPipeOut[1]" ); + } +} + + +// inform the client when server settings get changed +void DictInterface::serverChanged() +{ + newServer = true; +} + + +// cancel all pending jobs +void DictInterface::stop() +{ + if (jobList.isEmpty()) { + return; + } else { + while (jobList.count()>1) // not yet started jobs can be deleted directly + jobList.removeLast(); + + if (!clientDoneInProgress) { + jobList.getFirst()->canceled = true; // clientDone() now ignores the results of this job + char buf; // write one char in the pipe to the async thread + if (::write(fdPipeOut[1],&buf,1) == -1) + ::perror( "stop()" ); + } + } +} + + +void DictInterface::define(const QString &query) +{ + JobData *newJob = generateQuery(JobData::TDefine,query); + if (newJob) + insertJob(newJob); +} + + +void DictInterface::getDefinitions(QStringList query) +{ + JobData *newjob = new JobData(JobData::TGetDefinitions,newServer,global->server,global->port, + global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled, + global->user,global->secret,global->headLayout); + newjob->defines = query; + newServer = false; + insertJob(newjob); +} + + +void DictInterface::match(const QString &query) +{ + JobData *newJob = generateQuery(JobData::TMatch,query); + + if (newJob) { + if (global->currentStrategy == 0) + newJob->strategy = "."; // spell check strategy + else + newJob->strategy = global->strategies[global->currentStrategy].utf8(); + + insertJob(newJob); + } +} + + +// fetch detailed db info +void DictInterface::showDbInfo(const QString &db) +{ + QString ndb = db.simplifyWhiteSpace(); // cleanup query string + if (ndb.isEmpty()) + return; + if (ndb.length()>100) // shorten if necessary + ndb.truncate(100); + JobData *newjob = new JobData(JobData::TShowDbInfo,newServer,global->server,global->port, + global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled, + global->user,global->secret,global->headLayout); + newServer = false; + newjob->query = ndb; // construct job... + insertJob(newjob); +} + + +void DictInterface::showDatabases() +{ + insertJob( new JobData(JobData::TShowDatabases,newServer,global->server,global->port, + global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled, + global->user,global->secret,global->headLayout)); + newServer = false; +} + + +void DictInterface::showStrategies() +{ + insertJob( new JobData(JobData::TShowStrategies,newServer,global->server,global->port, + global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled, + global->user,global->secret,global->headLayout)); + newServer = false; +} + + +void DictInterface::showInfo() +{ + insertJob( new JobData(JobData::TShowInfo,newServer,global->server,global->port, + global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled, + global->user,global->secret,global->headLayout)); + newServer = false; +} + + +// get info about databases & stratgies the server knows +void DictInterface::updateServer() +{ + insertJob( new JobData(JobData::TUpdate,newServer,global->server,global->port, + global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled, + global->user,global->secret,global->headLayout)); + newServer = false; +} + + +// client-thread ended +void DictInterface::clientDone() +{ + QString message; + + cleanPipes(); // read from pipe so that notifier doesn´t fire again + + if (jobList.isEmpty()) { + kdDebug(5004) << "This shouldn´t happen, the client-thread signaled termination, but the job list is empty" << endl; + return; // strange.. + } + + clientDoneInProgress = true; + QStringList::iterator it; + JobData* job = jobList.getFirst(); + if (!job->canceled) { // non-interupted job? + if (JobData::ErrNoErr == job->error) { + switch (job->type) { + case JobData::TUpdate : + global->serverDatabases.clear(); + for (it = job->databases.begin(); it != job->databases.end(); ++it) + global->serverDatabases.append(*it); + global->databases = global->serverDatabases; + for (int i = global->databaseSets.count()-1;i>=0;i--) + global->databases.prepend(global->databaseSets.at(i)->first()); + global->databases.prepend(i18n("All Databases")); + global->currentDatabase = 0; + + global->strategies.clear(); + for (it = job->strategies.begin(); it != job->strategies.end(); ++it) + global->strategies.append(*it); + global->strategies.prepend(i18n("Spell Check")); + global->currentStrategy = 0; + message = i18n(" Received database/strategy list "); + emit stopped(message); + emit infoReady(); + break; + case JobData::TDefine: + case JobData::TGetDefinitions: + if (job->type == JobData::TDefine) { + switch (job->numFetched) { + case 0: + message = i18n("No definitions found"); + break; + case 1: + message = i18n("One definition found"); + break; + default: + message = i18n("%1 definitions found").arg(job->numFetched); + } + } else { + switch (job->numFetched) { + case 0: + message = i18n(" No definitions fetched "); + break; + case 1: + message = i18n(" One definition fetched "); + break; + default: + message = i18n(" %1 definitions fetched ").arg(job->numFetched); + } + } + emit stopped(message); + emit resultReady(job->result, job->query); + break; + case JobData::TMatch: + switch (job->numFetched) { + case 0: + message = i18n(" No matching definitions found "); + break; + case 1: + message = i18n(" One matching definition found "); + break; + default: + message = i18n(" %1 matching definitions found ").arg(job->numFetched); + } + emit stopped(message); + emit matchReady(job->matches); + break; + default : + message = i18n(" Received information "); + emit stopped(message); + emit resultReady(job->result, job->query); + } + } else { + QString errMsg; + switch (job->error) { + case JobData::ErrCommunication: + errMsg = i18n("Communication error:\n\n"); + errMsg += job->result; + break; + case JobData::ErrTimeout: + errMsg = i18n("A delay occurred which exceeded the\ncurrent timeout limit of %1 seconds.\nYou can modify this limit in the Preferences Dialog.").arg(global->timeout); + break; + case JobData::ErrBadHost: + errMsg = i18n("Unable to connect to:\n%1:%2\n\nCannot resolve hostname.").arg(job->server).arg(job->port); + break; + case JobData::ErrConnect: + errMsg = i18n("Unable to connect to:\n%1:%2\n\n").arg(job->server).arg(job->port); + errMsg += job->result; + break; + case JobData::ErrRefused: + errMsg = i18n("Unable to connect to:\n%1:%2\n\nThe server refused the connection.").arg(job->server).arg(job->port); + break; + case JobData::ErrNotAvailable: + errMsg = i18n("The server is temporarily unavailable."); + break; + case JobData::ErrSyntax: + errMsg = i18n("The server reported a syntax error.\nThis shouldn't happen -- please consider\nwriting a bug report."); + break; + case JobData::ErrCommandNotImplemented: + errMsg = i18n("A command that Kdict needs isn't\nimplemented on the server."); + break; + case JobData::ErrAccessDenied: + errMsg = i18n("Access denied.\nThis host is not allowed to connect."); + break; + case JobData::ErrAuthFailed: + errMsg = i18n("Authentication failed.\nPlease enter a valid username and password."); + break; + case JobData::ErrInvalidDbStrat: + errMsg = i18n("Invalid database/strategy.\nYou probably need to use Server->Get Capabilities."); + break; + case JobData::ErrNoDatabases: + errMsg = i18n("No databases available.\nIt is possible that you need to authenticate\nwith a valid username/password combination to\ngain access to any databases."); + break; + case JobData::ErrNoStrategies: + errMsg = i18n("No strategies available."); + break; + case JobData::ErrServerError: + errMsg = i18n("The server sent an unexpected reply:\n\"%1\"\nThis shouldn't happen, please consider\nwriting a bug report").arg(job->result); + break; + case JobData::ErrMsgTooLong: + errMsg = i18n("The server sent a response with a text line\nthat was too long.\n(RFC 2229: max. 1024 characters/6144 octets)"); + break; + case JobData::ErrNoErr: // make compiler happy + errMsg = i18n("No Errors"); + } + message = i18n(" Error "); + emit stopped(message); + KMessageBox::error(global->topLevel, errMsg); + } + } else { + message = i18n(" Stopped "); + emit stopped(message); + } + + clientDoneInProgress = false; + + client->removeJob(); + jobList.removeFirst(); // this job is now history + if (!jobList.isEmpty()) // work to be done? + startClient(); // => restart client +} + + +JobData* DictInterface::generateQuery(JobData::QueryType type, QString query) +{ + query = query.simplifyWhiteSpace(); // cleanup query string + if (query.isEmpty()) + return 0L; + if (query.length()>300) // shorten if necessary + query.truncate(300); + query = query.replace(QRegExp("[\"\\]"), ""); // remove remaining illegal chars... + if (query.isEmpty()) + return 0L; + + JobData *newjob = new JobData(type,newServer,global->server,global->port, + global->idleHold,global->timeout,global->pipeSize, global->encoding, global->authEnabled, + global->user,global->secret,global->headLayout); + newServer = false; + newjob->query = query; // construct job... + + if (global->currentDatabase == 0) // all databases + newjob->databases.append("*"); + else { + if ((global->currentDatabase > 0)&& // database set + (global->currentDatabase < global->databaseSets.count()+1)) { + for (int i = 0;i<(int)global->serverDatabases.count();i++) + if ((global->databaseSets.at(global->currentDatabase-1))->findIndex(global->serverDatabases[i])>0) + newjob->databases.append(global->serverDatabases[i].utf8().data()); + if (newjob->databases.count()==0) { + KMessageBox::sorry(global->topLevel, i18n("Please select at least one database.")); + delete newjob; + return 0L; + } + } else { // one database + newjob->databases.append(global->databases[global->currentDatabase].utf8().data()); + } + } + + return newjob; +} + + +void DictInterface::insertJob(JobData* job) +{ + if (jobList.isEmpty()) { // Client has nothing to do, start directly + jobList.append(job); + startClient(); + } else { // there are other pending jobs... + stop(); + jobList.append(job); + } +} + + +// start client-thread +void DictInterface::startClient() +{ + cleanPipes(); + if (jobList.isEmpty()) { + kdDebug(5004) << "This shouldn´t happen, startClient called, but clientList is empty" << endl; + return; + } + + client->insertJob(jobList.getFirst()); + char buf; // write one char in the pipe to the async thread + if (::write(fdPipeOut[1],&buf,1) == -1) + ::perror( "startClient()" ); + + QString message; + switch (jobList.getFirst()->type) { + case JobData::TDefine: + case JobData::TGetDefinitions: + case JobData::TMatch: + message = i18n(" Querying server... "); + break; + case JobData::TShowDatabases: + case JobData::TShowStrategies: + case JobData::TShowInfo: + case JobData::TShowDbInfo: + message = i18n(" Fetching information... "); + break; + case JobData::TUpdate: + message = i18n(" Updating server information... "); + break; + } + emit started(message); +} + + +// empty the pipes, so that notifier stops firing +void DictInterface::cleanPipes() +{ + fd_set rfds; + struct timeval tv; + int ret; + char buf; + tv.tv_sec = 0; + tv.tv_usec = 0; + + do { + FD_ZERO(&rfds); + FD_SET(fdPipeIn[0],&rfds); + if (1==(ret=select(FD_SETSIZE,&rfds,NULL,NULL,&tv))) + if ( ::read(fdPipeIn[0], &buf, 1 ) == -1 ) + ::perror( "cleanPipes" ); + } while (ret == 1); + + do { + FD_ZERO(&rfds); + FD_SET(fdPipeOut[0],&rfds); + if (1==(ret=select(FD_SETSIZE,&rfds,NULL,NULL,&tv))) + if ( ::read(fdPipeOut[0], &buf, 1 ) == -1 ) + ::perror( "cleanPipes" ); + } while (ret == 1); +} + +//-------------------------------- + +#include "dict.moc" diff --git a/kdict/dict.h b/kdict/dict.h new file mode 100644 index 00000000..adb87ed9 --- /dev/null +++ b/kdict/dict.h @@ -0,0 +1,204 @@ +/* ------------------------------------------------------------- + + dict.h (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + (C) by Matthias Hölzer 1998 + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + JobData used for data transfer between Client and Interface + DictAsyncClient all network related stuff happens here in an asynchrous thread + DictInterface interface for DictAsyncClient, job management + + -------------------------------------------------------------*/ + +#ifndef _DICT_H_ +#define _DICT_H_ + +#include +#include +#include + +class QSocketNotifier; +struct in_addr; + + +//********* JobData ****************************************** + + +class JobData +{ + +public: + + enum QueryType { //type of transaction + TDefine=0, + TGetDefinitions, + TMatch, + TShowDatabases, + TShowDbInfo, + TShowStrategies, + TShowInfo, + TUpdate + }; + + enum ErrType { // error codes + ErrNoErr=0, + ErrCommunication, // display result! + ErrTimeout, + ErrBadHost, + ErrConnect, // display result! + ErrRefused, + ErrNotAvailable, + ErrSyntax, + ErrCommandNotImplemented, + ErrAccessDenied, + ErrAuthFailed, + ErrInvalidDbStrat, + ErrNoDatabases, + ErrNoStrategies, + ErrServerError, // display result! + ErrMsgTooLong + }; + + JobData(QueryType Ntype,bool NnewServer,QString const& Nserver,int Nport, + int NidleHold, int Ntimeout, int NpipeSize, QString const& Nencoding, bool NAuthEnabled, + QString const& Nuser, QString const& Nsecret, unsigned int NheadLayout); + + QueryType type; + ErrType error; + + bool canceled; + int numFetched; + QString result; + QStringList matches; + + QString query; + QStringList defines; + + bool newServer; + QString server; + int port, timeout, pipeSize, idleHold; + QString encoding; + bool authEnabled; + QString user, secret; + QStringList databases,strategies; + QString strategy; + unsigned int headLayout; +}; + + +//********* DictAsyncClient ****************************************** + + +class DictAsyncClient +{ + +public: + + DictAsyncClient(int NfdPipeIn, int NfdPipeOut); + ~DictAsyncClient(); + + static void* startThread(void* pseudoThis); + + void insertJob(JobData *newJob); + void removeJob(); + +private: + + void waitForWork(); // main loop + void define(); + bool getDefinitions(); + bool match(); + void showDatabases(); + void showDbInfo(); + void showStrategies(); + void showInfo(); + void update(); + + void openConnection(); // connect, handshake and authorization + void closeSocket(); + void doQuit(); // send "quit" without timeout, without checks, close connection + bool waitForRead(); // used by getNextIntoBuffer() + bool waitForWrite(); // used by sendBuffer() & connect() + void clearPipe(); // remove start/stop signal + + bool sendBuffer(); // send cmdBuffer to the server + bool getNextLine(); // set thisLine to next complete line of input + bool nextResponseOk(int code); // reads next line and checks the response code + bool getNextResponse(int &code); // reads next line and returns the response code + void handleErrors(); + + void resultAppend(const char* str); + void resultAppend(QString str); + + JobData *job; + char *input; + QCString cmdBuffer; + const unsigned int inputSize; + char *thisLine, *nextLine, *inputEnd; + int fdPipeIn,fdPipeOut; //IPC-Pipes to/from async thread + int tcpSocket,timeout,idleHold; + QTextCodec *codec; +}; + + +//********* DictInterface ************************************************* + +class DictInterface : public QObject +{ + Q_OBJECT + +public: + + DictInterface(); + ~DictInterface(); + +public slots: + + void serverChanged(); // inform the client when server settings get changed + void stop(); // cancel all pending jobs + + void define(const QString &query); + void getDefinitions(QStringList query); + void match(const QString &query); + void showDbInfo(const QString &db); // fetch detailed db info + void showDatabases(); // fetch misc. info... + void showStrategies(); + void showInfo(); + void updateServer(); // get info about databases & strategies the server knows + +signals: + + void infoReady(); // updateServer done + void resultReady(const QString &result, const QString &query); // define done + void matchReady(const QStringList &result); // match done + void started(const QString &message); // Client is active now, activate indicator + void stopped(const QString &message); // Client is now halted, deactivate indicator + +private slots: + + void clientDone(); + +private: + + JobData* generateQuery(JobData::QueryType type, QString query); + void insertJob(JobData* job); // insert in job list, if nesscary cancel/remove previous jobs + void startClient(); // send start signal + void cleanPipes(); // empty the pipes, so that notifier stops firing + + QSocketNotifier *notifier; + int fdPipeIn[2],fdPipeOut[2]; //IPC-Pipes to/from async thread + pthread_t threadID; + DictAsyncClient *client; + QPtrList jobList; + bool newServer,clientDoneInProgress; +}; + +extern DictInterface *interface; + +#endif diff --git a/kdict/hi128-app-kdict.png b/kdict/hi128-app-kdict.png new file mode 100644 index 00000000..fd57f9ab Binary files /dev/null and b/kdict/hi128-app-kdict.png differ diff --git a/kdict/hi16-app-kdict.png b/kdict/hi16-app-kdict.png new file mode 100644 index 00000000..643500fc Binary files /dev/null and b/kdict/hi16-app-kdict.png differ diff --git a/kdict/hi32-app-kdict.png b/kdict/hi32-app-kdict.png new file mode 100644 index 00000000..fa3a0537 Binary files /dev/null and b/kdict/hi32-app-kdict.png differ diff --git a/kdict/hi48-app-kdict.png b/kdict/hi48-app-kdict.png new file mode 100644 index 00000000..40efecd0 Binary files /dev/null and b/kdict/hi48-app-kdict.png differ diff --git a/kdict/hi64-app-kdict.png b/kdict/hi64-app-kdict.png new file mode 100644 index 00000000..ba6ed634 Binary files /dev/null and b/kdict/hi64-app-kdict.png differ diff --git a/kdict/hisc-app-kdict.svgz b/kdict/hisc-app-kdict.svgz new file mode 100644 index 00000000..584e1968 Binary files /dev/null and b/kdict/hisc-app-kdict.svgz differ diff --git a/kdict/kdict.desktop b/kdict/kdict.desktop new file mode 100644 index 00000000..d274d233 --- /dev/null +++ b/kdict/kdict.desktop @@ -0,0 +1,153 @@ +[Desktop Entry] +Type=Application +Exec=kdict -caption "%c" %i %m +Icon=kdict +Terminal=false +Name=Dictionary +Name[af]=Woordeboek +Name[ar]=القاموس +Name[az]=Lüğət +Name[be]=Слоўнік +Name[bg]=Речник +Name[bn]=অভিধান +Name[br]=Geriadur +Name[bs]=RjeÄnik +Name[ca]=Diccionari +Name[cs]=Slovník +Name[cy]=Geiriadur +Name[da]=Ordbog +Name[de]=Lexikon +Name[el]=Λεξικό +Name[eo]=Vortaro +Name[es]=Diccionario +Name[et]=Sõnaraamat +Name[eu]=Hiztegia +Name[fa]=واژه‌نامه +Name[fi]=Sanakirja +Name[fr]=Dictionnaire +Name[ga]=Foclóir +Name[gl]=Diccionario +Name[he]=מילון +Name[hi]=शबà¥à¤¦à¤•à¥‹à¤¶ +Name[hr]=RjeÄnik +Name[hu]=Szótár +Name[is]=Orðabók +Name[it]=Dizionario +Name[ja]=辞書 +Name[ka]=ლექსიკáƒáƒœáƒ˜ +Name[kk]=Сөздік +Name[km]=វចនានុក្រម +Name[ko]=사전 +Name[lt]=Žodynas +Name[lv]=VÄrdnÄ«ca +Name[mk]=Речник +Name[mn]=Толь бичиг +Name[ms]=Kamus +Name[mt]=Dizzjunarju +Name[nb]=Ordbok +Name[nds]=Wöörbook +Name[ne]=शबà¥à¤¦à¤•à¥‹à¤¶ +Name[nl]=Woordenboek +Name[nn]=Ordbok +Name[nso]=Pukuntsu +Name[pa]=ਸ਼ਬਦ-ਕੋਸ਼ +Name[pl]=SÅ‚ownik +Name[pt]=Dicionário +Name[pt_BR]=Dicionário +Name[ro]=DicÅ£ionar +Name[ru]=Словарь +Name[se]=Sátnegirji +Name[sk]=Slovník +Name[sl]=Slovar +Name[sr]=Речник +Name[sr@Latn]=ReÄnik +Name[sv]=Ordlista +Name[ta]=அகராதி +Name[tg]=Луғат +Name[th]=พจนานุà¸à¸£à¸¡ +Name[tr]=Sözlük +Name[uk]=Словник +Name[uz]=LugÊ»at +Name[uz@cyrillic]=Луғат +Name[ven]=Bugu yau talutshedza maipfi +Name[wa]=Motî +Name[xh]=Incwadi eneenkcazelo zamagama +Name[zh_CN]=å­—å…¸ +Name[zh_HK]=å­—å…¸ +Name[zh_TW]=å­—å…¸ +Name[zu]=Isichazamagama +GenericName=Online Dictionary +GenericName[af]=Aan-lyn Woordeboek +GenericName[ar]=قاموس على الإنترنت +GenericName[be]=Сеткавы Ñлоўнік +GenericName[bg]=Мрежови речник +GenericName[bn]=অনলাইন অভিধান +GenericName[br]=Geriaoueg enlinenn +GenericName[bs]=Online rjeÄnik +GenericName[ca]=Diccionari en línia +GenericName[cs]=Online slovník +GenericName[cy]=Geiriadur Ar-lein +GenericName[da]=Online-ordbog +GenericName[de]=Online-Lexikon +GenericName[el]=Διαδικτυακό λεξικό +GenericName[eo]=Vortaro +GenericName[es]=Diccionario en la red +GenericName[et]=Võrgusõnaraamat +GenericName[eu]=On line hiztegia +GenericName[fa]=واژه‌نامۀ برخط +GenericName[fi]=Sanakirja +GenericName[fr]=Dictionnaire électronique +GenericName[ga]=Foclóir ar líne +GenericName[gl]=Diccionario en liña +GenericName[he]=מילון מקוון +GenericName[hi]=ऑनलाइन शबà¥à¤¦à¤•à¥‹à¤¶ +GenericName[hr]=Online rjeÄnik +GenericName[hu]=SzótárkezelÅ‘ +GenericName[is]=Orðabók á Netinu +GenericName[it]=Dizionario in linea +GenericName[ja]=オンライン辞書 +GenericName[ka]=ხáƒáƒ–ის ლექსიკáƒáƒœáƒ˜ +GenericName[kk]=Онлайн Ñөздік +GenericName[km]=វចនានុក្រម​លើ​បណ្ដាញ +GenericName[lt]=Žodynas tinkle +GenericName[lv]=TieÅ¡saites VÄrdnÄ«ca +GenericName[mk]=Речник на линија +GenericName[mn]=Онлайн-Толь бичиг +GenericName[ms]=Kamus Talian +GenericName[mt]=Dizzjunarju online +GenericName[nb]=Ordbok pÃ¥ nettet +GenericName[nds]=Online-Wöörbook +GenericName[ne]=अनलाइन शबà¥à¤¦à¤•à¥‹à¤¶ +GenericName[nl]=Online woordenboek +GenericName[nn]=Internettordbok +GenericName[nso]=Pukuntsu ya Online +GenericName[pa]=ਆਨਲਾਇਨ ਡਿਕਸ਼ਨਰੀ +GenericName[pl]=SÅ‚ownik w sieci +GenericName[pt]=Dicionário na Rede +GenericName[pt_BR]= Dicionário On-line +GenericName[ro]=DicÅ£ionar on-line +GenericName[ru]=Онлайн-Ñловарь +GenericName[se]=Fierpmádatsátnegirji +GenericName[sk]=On-line slovník +GenericName[sl]=Spletni slovar +GenericName[sr]=Онлајн речник +GenericName[sr@Latn]=Onlajn reÄnik +GenericName[sv]=Online-ordlista +GenericName[ta]=இணைய அகராதி +GenericName[tg]=Луғати Шабакавӣ +GenericName[th]=พจนานุà¸à¸£à¸¡à¹à¸šà¸šà¸­à¸­à¸™à¹„ลน์ +GenericName[tr]=Çevrimiçi Sözlük +GenericName[uk]=Словник в мережі +GenericName[uz]=Internet lugÊ»at +GenericName[uz@cyrillic]=Интернет луғат +GenericName[ven]=Bugu talutshedza maipfi ine yavha kha mutevhe wau tshimbila +GenericName[wa]=Motî so les fyis +GenericName[xh]=Incwadi eneenkcazelo zamagama Esemgceni +GenericName[zh_CN]=在线字典 +GenericName[zh_HK]=線上字典 +GenericName[zh_TW]=線上字典 +GenericName[zu]=Isichaza magama esixhumekile +X-KDE-StartupNotify=true +X-DCOP-ServiceType=Unique +DocPath=kdict/index.html +Categories=Qt;KDE;Network;X-KDE-More;Office;Dictionary; diff --git a/kdict/kdictui.rc b/kdict/kdictui.rc new file mode 100644 index 00000000..15b3c414 --- /dev/null +++ b/kdict/kdictui.rc @@ -0,0 +1,59 @@ + + + + + &File + + + + + + + + + &Edit + + + + + + + + + Hist&ory + + + + + + + + Ser&ver + + + + Database &Information + + + + + + + + &Settings + + + + +Main Toolbar + + + + + + + + + + + diff --git a/kdict/main.cpp b/kdict/main.cpp new file mode 100644 index 00000000..235f9048 --- /dev/null +++ b/kdict/main.cpp @@ -0,0 +1,56 @@ +/* ------------------------------------------------------------- + + main.cpp (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + (C) by Matthias H�zer 1998 + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- */ + +#include + +#include +#include +#include +#include + +#include "application.h" +#include "toplevel.h" + +static KCmdLineOptions knoptions[] = +{ + { "c", 0, 0 }, + { "clipboard", I18N_NOOP("Define X11-clipboard content (selected text)"), 0 }, + { "+[word/phrase]", I18N_NOOP("Lookup the given word/phrase"), 0 }, + KCmdLineLastOption +}; + + +extern "C" KDE_EXPORT int kdemain(int argc, char* argv[]) +{ + KAboutData aboutData("kdict", + I18N_NOOP("Dictionary"), + KDICT_VERSION, + I18N_NOOP("The KDE Dictionary Client"), + KAboutData:: License_Artistic, + "Copyright (c) 1999-2001, Christian Gebauer\nCopyright (c) 1998, Matthias Hoelzer", + 0, + 0); + + aboutData.addAuthor("Christian Gebauer",I18N_NOOP("Maintainer"),"gebauer@kde.org"); + aboutData.addAuthor("Matthias Hoelzer",I18N_NOOP("Original Author"),"hoelzer@kde.org"); + + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineArgs::addCmdLineOptions( knoptions ); + KUniqueApplication::addCmdLineOptions(); + + if (!Application::start()) + return 0; + + Application app; + + return app.exec(); +} diff --git a/kdict/matchview.cpp b/kdict/matchview.cpp new file mode 100644 index 00000000..32cd04dd --- /dev/null +++ b/kdict/matchview.cpp @@ -0,0 +1,473 @@ +/* ------------------------------------------------------------- + + matchview.cpp (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + MatchView This widget contains the list of matching definitions + + ------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "dict.h" +#include "options.h" +#include "matchview.h" + + +//********* MatchViewItem ******************************************** + + +MatchViewItem::MatchViewItem(QListView *view, const QString &text) + : QListViewItem(view,text) +{ +} + + +MatchViewItem::MatchViewItem(QListView *view,QListViewItem *after,const QString &text) + : QListViewItem(view,after,text) +{ +} + + +MatchViewItem::MatchViewItem(QListViewItem *item,const QString &text,const QString &commandStr) +: QListViewItem(item,text), command(commandStr) +{ +} + + +MatchViewItem::MatchViewItem(QListViewItem *item,QListViewItem *after,const QString &text,const QString &commandStr) +: QListViewItem(item,after,text), command(commandStr) +{ +} + + +MatchViewItem::~MatchViewItem() +{ +} + + +void MatchViewItem::setOpen(bool o) +{ + if (o && !childCount()) { + listView()->setUpdatesEnabled(false); + + MatchViewItem *sub=0; + QString command, label; + QRegExp exp("\"*\"", true, true); + QStringList::iterator it; + for (it = subEntrys.begin(); it != subEntrys.end(); ++it) { + command = "define "; + command += (*it); + command += "\r\n"; + exp.search((*it)); + label = exp.cap(); + label = label.mid(1, label.length()-2); // remove quotes + if (sub) + sub = new MatchViewItem(this, sub, label, command); + else + sub = new MatchViewItem(this, label, command); + } + + subEntrys.clear(); + + listView()->setUpdatesEnabled(true); + } + + if (childCount()) + QListViewItem::setOpen(o); +} + + +void MatchViewItem::paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int alignment) +{ + if(command.isEmpty()) { + QFont font=p->font(); + font.setBold(true); + p->setFont(font); + } + QListViewItem::paintCell(p,cg,column,width,alignment); +} + + +//********* MatchView ****************************************** + + +MatchView::MatchView(QWidget *parent, const char *name) + : QWidget(parent,name),getOn(false),getAllOn(false) +{ + setCaption(kapp->makeStdCaption(i18n("Match List"))); + + QVBoxLayout * boxLayout = new QVBoxLayout(this, 1, 0); + + boxLayout->addSpacing(1); + w_strat = new QComboBox(false,this); + w_strat->setFixedHeight(w_strat->sizeHint().height()); + connect(w_strat,SIGNAL(activated(int)),this,SLOT(strategySelected(int))); + boxLayout->addWidget(w_strat,0); + boxLayout->addSpacing(1); + + w_list = new QListView(this); + w_list->setFocusPolicy(QWidget::StrongFocus); + w_list->header()->hide(); + w_list->addColumn("foo"); + w_list->setColumnWidthMode(0,QListView::Maximum); + w_list->setColumnWidth(0,0); + w_list->setSelectionMode(QListView::Extended); + w_list->setTreeStepSize(18); + w_list->setSorting(-1); // disable sorting + w_list->setMinimumHeight(w_strat->sizeHint().height()); + connect(w_list,SIGNAL(selectionChanged()),SLOT(enableGetButton())); + connect(w_list,SIGNAL(returnPressed(QListViewItem *)),SLOT(returnPressed(QListViewItem *))); + connect(w_list,SIGNAL(doubleClicked(QListViewItem *)),SLOT(getOneItem(QListViewItem *))); + connect(w_list,SIGNAL(mouseButtonPressed(int, QListViewItem *, const QPoint &, int)), + SLOT(mouseButtonPressed(int, QListViewItem *, const QPoint &, int))); + connect(w_list,SIGNAL(rightButtonPressed(QListViewItem *,const QPoint &,int)),SLOT(buildPopupMenu(QListViewItem *,const QPoint &,int))); + boxLayout->addWidget(w_list,1); + + boxLayout->addSpacing(1); + w_get = new QPushButton(i18n("&Get Selected"),this); + w_get->setFixedHeight(w_get->sizeHint().height()-3); + w_get->setMinimumWidth(w_get->sizeHint().width()-20); + w_get->setEnabled(false); + connect(w_get, SIGNAL(clicked()), this, SLOT(getSelected())); + boxLayout->addWidget(w_get,0); + + w_getAll = new QPushButton(i18n("Get &All"),this); + w_getAll->setFixedHeight(w_getAll->sizeHint().height()-3); + w_getAll->setMinimumWidth(w_getAll->sizeHint().width()-20); + w_getAll->setEnabled(false); + connect(w_getAll, SIGNAL(clicked()), this, SLOT(getAll())); + boxLayout->addWidget(w_getAll,0); + connect(interface,SIGNAL(matchReady(const QStringList &)),this,SLOT(newList(const QStringList &))); + rightBtnMenu = new KPopupMenu(); +} + + +MatchView::~MatchView() +{ +} + + +void MatchView::updateStrategyCombo() +{ + w_strat->clear(); + w_strat->insertStringList(global->strategies); + w_strat->setCurrentItem(global->currentStrategy); +} + + +bool MatchView::selectStrategy(const QString &strategy) const +{ + int newCurrent = global->strategies.findIndex(strategy); + if (newCurrent == -1) + return false; + else { + global->currentStrategy = newCurrent; + w_strat->setCurrentItem(global->currentStrategy); + return true; + } +} + + +void MatchView::match(const QString &query) +{ + interface->match(query.utf8()); +} + + +void MatchView::closeEvent ( QCloseEvent * e ) +{ + e->accept(); // hides the widget + emit(windowClosed()); +} + + +void MatchView::strategySelected(int num) +{ + global->currentStrategy = num; +} + + +void MatchView::enableGetButton() +{ + if (w_getAll->isEnabled()) { + w_get->setEnabled(true); + getOn = true; + } +} + + +void MatchView::mouseButtonPressed(int button, QListViewItem *, const QPoint &, int) +{ + if (button == MidButton) + emit(clipboardRequested()); +} + + +void MatchView::returnPressed(QListViewItem *) +{ + getSelected(); +} + + +void MatchView::getOneItem(QListViewItem *i) +{ + QStringList defines; + + if ((!i->childCount())&&(i->parent())) + defines.append(((MatchViewItem *)(i))->command); + else { + i = i->firstChild(); + while (i) { + defines.append(((MatchViewItem *)(i))->command); + i = i->nextSibling(); + } + } + + doGet(defines); +} + + +void MatchView::getSelected() +{ + QStringList defines; + MatchViewItem *top = static_cast(w_list->firstChild()); + MatchViewItem *sub; + + while (top) { + if (top->isSelected()&&(!top->subEntrys.isEmpty())) { + QString command; + QStringList::iterator it; + for (it = top->subEntrys.begin(); it != top->subEntrys.end(); ++it) { + command = "define "; + command += (*it); + command += "\r\n"; + defines.append(command); + } + } else { + sub = static_cast(top->firstChild()); + while (sub) { + if (top->isSelected()||sub->isSelected()) + defines.append(sub->command); + sub = static_cast(sub->nextSibling()); + } + } + top = static_cast(top->nextSibling()); + } + doGet(defines); +} + + +void MatchView::getAll() +{ + QStringList defines; + MatchViewItem *top = static_cast(w_list->firstChild()); + MatchViewItem *sub; + + while (top) { + if (!top->subEntrys.isEmpty()) { + QString command; + QStringList::iterator it; + for (it = top->subEntrys.begin(); it != top->subEntrys.end(); ++it) { + command = "define "; + command += (*it); + command += "\r\n"; + defines.append(command); + } + } else { + sub = static_cast(top->firstChild()); + while (sub) { + defines.append(sub->command); + sub = static_cast(sub->nextSibling()); + } + } + top = static_cast(top->nextSibling()); + } + doGet(defines); +} + + +void MatchView::doGet(QStringList &defines) +{ + if (defines.count() > 0) { + if (defines.count() > global->maxDefinitions) { + KMessageBox::sorry(global->topLevel,i18n("You have selected %1 definitions,\nbut Kdict will fetch only the first %2 definitions.\nYou can modify this limit in the Preferences Dialog.") + .arg(defines.count()).arg(global->maxDefinitions)); + while (defines.count()>global->maxDefinitions) + defines.pop_back(); + } + interface->getDefinitions(defines); + } +} + + +void MatchView::newList(const QStringList &matches) +{ + MatchViewItem *top=0; + bool initialOpen = (matches.count()<200); + int numDb = 0; + + rightBtnMenu->hide(); + w_list->clear(); + w_list->setColumnWidth(0,0); + w_list->setUpdatesEnabled(false); + w_get->setEnabled(false); + getOn = false; + + if (matches.isEmpty()) { + w_list->setColumnWidth(0,w_get->width()-5); + w_list->setRootIsDecorated(false); + w_getAll->setEnabled(false); + getAllOn = false; + top = new MatchViewItem(w_list,top,i18n(" No Hits")); + } else { + w_list->setRootIsDecorated(true); + w_getAll->setEnabled(true); + getAllOn = true; + QString lastDb, db, match; + + QStringList::const_iterator it; + for (it = matches.begin(); it != matches.end(); ++it) { + db = (*it).section(' ', 0, 0); + + if (db != lastDb) { + numDb++; + if (top) { + top->setOpen(initialOpen); + top = new MatchViewItem(w_list, top, db); + } else + top = new MatchViewItem(w_list, db); + top->setExpandable(true); + lastDb = db; + } + + if (top) + top->subEntrys.append(*it); + } + + if ((numDb == 1)||(initialOpen)) + top->setOpen(true); + } + + w_list->setUpdatesEnabled(true); + w_list->repaint(); + w_list->setFocus(); +} + + +// construct the right-mouse-button-popup-menu on demand +void MatchView::buildPopupMenu(QListViewItem *i, const QPoint &_point, int) +{ + rightBtnMenu->clear(); + + if ((i!=0L)&&(i->isExpandable()||i->parent())) { + popupCurrent = (MatchViewItem *)(i); + rightBtnMenu->insertItem(i18n("&Get"),this,SLOT(popupGetCurrent())); + if (!i->isExpandable()) { // toplevel item -> only "get" + rightBtnMenu->insertItem(i18n("&Match"),this,SLOT(popupMatchCurrent())); + rightBtnMenu->insertItem(i18n("&Define"),this,SLOT(popupDefineCurrent())); + } + rightBtnMenu->insertSeparator(); + } + + kapp->clipboard()->setSelectionMode(false); + QString text = kapp->clipboard()->text(); + if (text.isEmpty()) { + kapp->clipboard()->setSelectionMode(true); + text = kapp->clipboard()->text(); + } + if (!text.isEmpty()) { + popupClip = kapp->clipboard()->text(); + rightBtnMenu->insertItem(i18n("Match &Clipboard Content"),this,SLOT(popupMatchClip())); + rightBtnMenu->insertItem(SmallIcon("define_clip"),i18n("D&efine Clipboard Content"),this,SLOT(popupDefineClip())); + rightBtnMenu->insertSeparator(); + } + + int ID = rightBtnMenu->insertItem(i18n("Get &Selected"),this,SLOT(getSelected())); + rightBtnMenu->setItemEnabled(ID,getOn); + ID = rightBtnMenu->insertItem(i18n("Get &All"),this,SLOT(getAll())); + rightBtnMenu->setItemEnabled(ID,getAllOn); + + if (w_list->childCount()) { + rightBtnMenu->insertSeparator(); + rightBtnMenu->insertItem(i18n("E&xpand List"),this,SLOT(expandList())); + rightBtnMenu->insertItem(i18n("C&ollapse List"),this,SLOT(collapseList())); + } + + rightBtnMenu->popup(_point); +} + + +void MatchView::popupGetCurrent() +{ + getOneItem(popupCurrent); +} + + +void MatchView::popupDefineCurrent() +{ + emit(defineRequested(popupCurrent->text(0))); +} + + +void MatchView::popupMatchCurrent() +{ + emit(matchRequested(popupCurrent->text(0))); +} + + +void MatchView::popupDefineClip() +{ + emit(defineRequested(popupClip)); +} + + +void MatchView::popupMatchClip() +{ + emit(matchRequested(popupClip)); +} + + +void MatchView::expandList() +{ + QListViewItem *top = w_list->firstChild(); + + while (top) { + w_list->setOpen(top,true); + top = top->nextSibling(); + } +} + + +void MatchView::collapseList() +{ + w_list->setCurrentItem(w_list->firstChild()); + QListViewItem *top = w_list->firstChild(); + + while (top) { + w_list->setOpen(top,false); + top = top->nextSibling(); + } +} + +//-------------------------------- + +#include "matchview.moc" diff --git a/kdict/matchview.h b/kdict/matchview.h new file mode 100644 index 00000000..f619820e --- /dev/null +++ b/kdict/matchview.h @@ -0,0 +1,105 @@ +/* ------------------------------------------------------------- + + matchview.h (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + MatchView This widget contains the list of matching definitions + + ------------------------------------------------------------- */ + + +#ifndef _MATCHVIEW_H_ +#define _MATCHVIEW_H_ + +#include +class KPopupMenu; + + +//********* MatchViewItem ******************************************** + +class MatchViewItem : public QListViewItem +{ + +public: + + MatchViewItem(QListView *view,const QString &text); + MatchViewItem(QListView *view,QListViewItem *after,const QString &text); + MatchViewItem(QListViewItem *item,const QString &text,const QString &commandStr); + MatchViewItem(QListViewItem *item,QListViewItem *after,const QString &text,const QString &commandStr); + ~MatchViewItem(); + + void setOpen(bool o); + void paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int alignment); + + QString command; + QStringList subEntrys; +}; + + +//********* MatchView ****************************************** + + +class MatchView : public QWidget +{ + Q_OBJECT + +public: + + MatchView(QWidget *parent=0,const char *name=0); + ~MatchView(); + + void updateStrategyCombo(); + bool selectStrategy(const QString &strategy) const; + void match(const QString &query); + +signals: + + void defineRequested(const QString &query); + void matchRequested(const QString &query); + void clipboardRequested(); + void windowClosed(); + +protected: + + void closeEvent ( QCloseEvent * e ); + +private slots: + + void strategySelected(int num); + void enableGetButton(); + void mouseButtonPressed(int, QListViewItem *, const QPoint &, int); + void returnPressed(QListViewItem *i); + void getOneItem(QListViewItem *i); + void getSelected(); + void getAll(); + void doGet(QStringList &defines); + void newList(const QStringList &matches); + void buildPopupMenu(QListViewItem *, const QPoint &, int); + void popupGetCurrent(); + void popupDefineCurrent(); + void popupMatchCurrent(); + void popupDefineClip(); + void popupMatchClip(); + void expandList(); + void collapseList(); + +private: + + QComboBox *w_strat; + QListView *w_list; + QPushButton *w_get,*w_getAll; + + bool getOn, getAllOn; + + KPopupMenu *rightBtnMenu; + MatchViewItem *popupCurrent; + QString popupClip; // needed for rightbtn-popup menu +}; + +#endif diff --git a/kdict/options.cpp b/kdict/options.cpp new file mode 100644 index 00000000..a157fbf5 --- /dev/null +++ b/kdict/options.cpp @@ -0,0 +1,947 @@ +/* ------------------------------------------------------------- + + options.cpp (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + GlobalData manages all global data of Kdict + + OptionsDialog the "Preferences" dialog + + DbSetsDialog dialog for editing the user defined database sets + + ------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "options.h" + + +//********* DictOptions ****************************************** + + +void GlobalData::read() +{ + KConfig *config=KGlobal::config(); + + // general... + config->setGroup("General"); + defineClipboard = config->readBoolEntry("Lookup_Clipboard",false); + headLayout = config->readNumEntry("Heading_Layout",0); + if ((headLayout > 2)||(headLayout < 0)) + headLayout = 0; + saveHistory = config->readBoolEntry("Save_History",true); + maxHistEntrys = config->readNumEntry("Max_History_Entrys",500); + if ((maxHistEntrys < 10)||(maxHistEntrys >5000)) + maxHistEntrys = 200; + maxBrowseListEntrys = config->readNumEntry("Max_Browse_Entrys",15); + if ((maxBrowseListEntrys < 1)||(maxBrowseListEntrys > 50)) + maxBrowseListEntrys = 15; + maxDefinitions = config->readNumEntry("Max_Definitions",2000); + if ((maxDefinitions < 100)||(maxDefinitions > 10000)) + maxDefinitions = 2000; + + //colors + config->setGroup("Colors"); + useCustomColors=config->readBoolEntry("customColors", false); + + QColor defCol=KGlobalSettings::textColor(); + c_olors[Ctext]=config->readColorEntry("textColor",&defCol); + c_olorNames[Ctext]=i18n("Text"); + + defCol=KGlobalSettings::baseColor(); + c_olors[Cbackground]=config->readColorEntry("backgroundColor",&defCol); + c_olorNames[Cbackground]=i18n("Background"); + + defCol=KGlobalSettings::highlightedTextColor(); + c_olors[CheadingsText]=config->readColorEntry("headingsTextColor",&defCol); + c_olorNames[CheadingsText]=i18n("Heading Text"); + + defCol=KGlobalSettings::highlightColor(); + c_olors[CheadingsBackground]=config->readColorEntry("headingsBackgroundColor",&defCol); + c_olorNames[CheadingsBackground]=i18n("Heading Background"); + + defCol=KGlobalSettings::linkColor(); + c_olors[Clinks]=config->readColorEntry("linksColor",&defCol); + c_olorNames[Clinks]=i18n("Link"); + + defCol=KGlobalSettings::visitedLinkColor(); + c_olors[CvisitedLinks]=config->readColorEntry("linksColor",&defCol); + c_olorNames[CvisitedLinks]=i18n("Followed Link"); + + //fonts + config->setGroup("Fonts"); + useCustomFonts=config->readBoolEntry("customFonts", false); + + QFont defFont=KGlobalSettings::generalFont(); + f_onts[Ftext]=config->readFontEntry("textFont",&defFont); + f_ontNames[Ftext]=i18n("Text"); + + defFont.setBold( true ); + defFont.setPointSize(defFont.pointSize()+2); + f_onts[Fheadings]=config->readFontEntry("headingsFont",&defFont); + f_ontNames[Fheadings]=i18n("Headings"); + + // geometry... + config->setGroup("Geometry"); + QSize invalid(-1,-1); + optSize = config->readSizeEntry("Opt_Size",&invalid); + setsSize = config->readSizeEntry("Sets_Size",&invalid); + matchSize = config->readSizeEntry("Match_Size",&invalid); + showMatchList = config->readBoolEntry("Show_MatchList",false); + splitterSizes = config->readIntListEntry("Splitter_Sizes"); + + config->setGroup("Query Combo"); + queryComboCompletionMode = (KGlobalSettings::Completion)config->readNumEntry("Completion_mode", + KGlobalSettings::completionMode()); + + config->setGroup("Query_History"); + queryHistory = config->readListEntry("History"); + + config->setGroup("DictServer"); + server = config->readEntry("Server", "dict.org"); + port = config->readNumEntry("Port", 2628); + if (port < 0) + port = 2628; + timeout = config->readNumEntry("Timeout",60); + if (timeout < 0) + timeout = 60; + pipeSize = config->readNumEntry("Pipe_Size",256); + if (pipeSize < 0) + pipeSize = 256; + idleHold = config->readNumEntry("Idle_Hold",30); + if (idleHold < 0) + idleHold = 30; + encoding=config->readEntry("encoding", "utf8"); + authEnabled = config->readBoolEntry("Auth_Enabled",false); + user = config->readEntry("User"); + secret = encryptStr(config->readEntry("Secret")); + serverDatabases = config->readListEntry("Server_Databases"); + currentDatabase = config->readNumEntry("Current_Database",0); + strategies = config->readListEntry("Strategies"); + if (strategies.isEmpty()) { + strategies.append(i18n("Spell Check")); + strategies.append(i18n("Exact")); + strategies.append(i18n("Prefix")); + } else { + strategies.remove(strategies.begin()); + strategies.prepend(i18n("Spell Check")); + } + + currentStrategy = config->readNumEntry("Current_Strategy",0); + if (currentStrategy >= strategies.count()) + currentStrategy = 0; + + config->setGroup("Database_Sets"); + databaseSets.setAutoDelete(true); + int num = config->readNumEntry("Num_Sets",0); + QStringList* temp; + QString strNum; + while (num > 0) { + temp = new QStringList(); + strNum.setNum(num); + *temp = config->readListEntry(strNum); + if (!temp->isEmpty()) { + databaseSets.prepend(temp); + num--; + } else { + delete temp; + num = 0; // stop reading... + } + } + databases = serverDatabases; + for (int i = databaseSets.count()-1;i>=0;i--) + databases.prepend(databaseSets.at(i)->first()); + databases.prepend(i18n("All Databases")); + if (currentDatabase >= databases.count()) + currentDatabase = 0; +} + + +void GlobalData::write() +{ + KConfig *config=KGlobal::config(); + + config->setGroup("General"); + config->writeEntry("Lookup_Clipboard",defineClipboard); + config->writeEntry("Heading_Layout",headLayout); + config->writeEntry("Save_History",saveHistory); + config->writeEntry("Max_History_Entrys",maxHistEntrys); + config->writeEntry("Max_Browse_Entrys",maxBrowseListEntrys); + config->writeEntry("Max_Definitions",maxDefinitions); + + config->setGroup("Colors"); + config->writeEntry("customColors",useCustomColors); + config->writeEntry("textColor", c_olors[Ctext]); + config->writeEntry("backgroundColor", c_olors[Cbackground]); + config->writeEntry("headingsTextColor", c_olors[CheadingsText]); + config->writeEntry("headingsBackgroundColor", c_olors[CheadingsBackground]); + config->writeEntry("linksColor", c_olors[Clinks]); + config->writeEntry("visitedLinksColor", c_olors[CvisitedLinks]); + + config->setGroup("Fonts"); + config->writeEntry("customFonts", useCustomFonts); + config->writeEntry("textFont", f_onts[Ftext]); + config->writeEntry("headingsFont", f_onts[Fheadings]); + + config->setGroup("Geometry"); + config->writeEntry("Opt_Size",optSize); + config->writeEntry("Sets_Size",setsSize); + config->writeEntry("Match_Size",matchSize); + config->writeEntry("Show_MatchList",showMatchList); + config->writeEntry("Splitter_Sizes",splitterSizes); + + config->setGroup("Query Combo"); + config->writeEntry("Completion_mode", (int)queryComboCompletionMode); + + config->setGroup("Query_History"); + QStringList copy; + if (saveHistory) + copy = queryHistory; + config->writeEntry("History",copy); + + config->setGroup("DictServer"); + config->writeEntry("Server", server); + config->writeEntry("Port", port); + config->writeEntry("Timeout",timeout); + config->writeEntry("Pipe_Size",pipeSize); + config->writeEntry("Idle_Hold",idleHold); + config->writeEntry("encoding", encoding); + config->writeEntry("Auth_Enabled",authEnabled); + config->writeEntry("User", user); + config->writeEntry("Secret", encryptStr(secret)); + config->writeEntry("Server_Databases",serverDatabases); + config->writeEntry("Current_Database",currentDatabase); + config->writeEntry("Strategies",strategies); + config->writeEntry("Current_Strategy",currentStrategy); + + config->setGroup("Database_Sets"); + config->writeEntry("Num_Sets",databaseSets.count()); + QString strNum; + for (unsigned int i = 0;iwriteEntry(strNum.setNum(i+1),*databaseSets.at(i)); +} + + +QColor GlobalData::defaultColor(int i) +{ + switch(i) { + case Ctext: + return KGlobalSettings::textColor(); + break; + + case Cbackground: + return KGlobalSettings::baseColor(); + break; + + case CheadingsText: + return KGlobalSettings::highlightedTextColor(); + break; + + case CheadingsBackground: + return KGlobalSettings::highlightColor(); + break; + + case Clinks: + return KGlobalSettings::linkColor(); + break; + + case CvisitedLinks: + return KGlobalSettings::visitedLinkColor(); + break; + + } + + return KGlobalSettings::baseColor(); +} + + +QColor GlobalData::textColor() +{ + if(useCustomColors) + return c_olors[Ctext]; + else + return defaultColor(Ctext); +} + + +QColor GlobalData::backgroundColor() +{ + if(useCustomColors) + return c_olors[Cbackground]; + else + return defaultColor(Cbackground); +} + + +QColor GlobalData::headingsTextColor() +{ + if(useCustomColors) + return c_olors[CheadingsText]; + else + return defaultColor(CheadingsText); +} + + +QColor GlobalData::headingsBackgroundColor() +{ + if(useCustomColors) + return c_olors[CheadingsBackground]; + else + return defaultColor(CheadingsBackground); +} + + +QColor GlobalData::linksColor() +{ + if(useCustomColors) + return c_olors[Clinks]; + else + return defaultColor(Clinks); +} + + +QColor GlobalData::visitedLinksColor() +{ + if(useCustomColors) + return c_olors[CvisitedLinks]; + else + return defaultColor(CvisitedLinks); +} + + +QFont GlobalData::defaultFont(int i) +{ + QFont font = KGlobalSettings::generalFont(); + + if (font.pointSize() < 5) + font.setPointSize(12); + + if (i==Fheadings) + font.setPointSize(font.pointSize()+5); + + return font; +} + + +QFont GlobalData::textFont() +{ + if(useCustomFonts) + return f_onts[Ftext]; + else + return defaultFont(Ftext); +} + + +QFont GlobalData::headingsFont() +{ + if(useCustomFonts) + return f_onts[Fheadings]; + else + return defaultFont(Fheadings); +} + + +// simple password scrambling... +QString GlobalData::encryptStr(const QString& aStr) +{ + uint i,val,len = aStr.length(); + QString result; + + for (i=0; ikey()==Key_Enter)||(e->key()==Key_Return))) + e->ignore(); + else + QListBox::keyPressEvent(e); +} + + +//********* OptionsDialog::ColorListItem ***************************** + + +OptionsDialog::ColorListItem::ColorListItem( const QString &text, const QColor &color ) + : QListBoxText(text), mColor( color ) +{ +} + + +OptionsDialog::ColorListItem::~ColorListItem() +{ +} + + +void OptionsDialog::ColorListItem::paint( QPainter *p ) +{ + QFontMetrics fm = p->fontMetrics(); + int h = fm.height(); + + p->drawText( 30+3*2, fm.ascent() + fm.leading()/2, text() ); + + p->setPen( Qt::black ); + p->drawRect( 3, 1, 30, h-1 ); + p->fillRect( 4, 2, 28, h-3, mColor ); +} + + +int OptionsDialog::ColorListItem::height(const QListBox *lb ) const +{ + return( lb->fontMetrics().lineSpacing()+1 ); +} + + +int OptionsDialog::ColorListItem::width(const QListBox *lb ) const +{ + return( 30 + lb->fontMetrics().width( text() ) + 6 ); +} + + +//********* OptionsDialog::FontListItem ***************************** + + +OptionsDialog::FontListItem::FontListItem( const QString &name, const QFont &font ) + : QListBoxText(name), f_ont(font) +{ + fontInfo = QString("[%1 %2]").arg(f_ont.family()).arg(f_ont.pointSize()); +} + + +OptionsDialog::FontListItem::~FontListItem() +{ +} + + +void OptionsDialog::FontListItem::setFont(const QFont &font) +{ + f_ont = font; + fontInfo = QString("[%1 %2]").arg(f_ont.family()).arg(f_ont.pointSize()); +} + + +void OptionsDialog::FontListItem::paint( QPainter *p ) +{ + QFont fnt = p->font(); + fnt.setWeight(QFont::Bold); + p->setFont(fnt); + int fontInfoWidth = p->fontMetrics().width(fontInfo); + int h = p->fontMetrics().ascent() + p->fontMetrics().leading()/2; + p->drawText(2, h, fontInfo ); + fnt.setWeight(QFont::Normal); + p->setFont(fnt); + p->drawText(5 + fontInfoWidth, h, text() ); +} + + +int OptionsDialog::FontListItem::width(const QListBox *lb ) const +{ + return( lb->fontMetrics().width(fontInfo) + lb->fontMetrics().width(text()) + 20 ); +} + + +//********* OptionsDialog ****************************************** + + +OptionsDialog::OptionsDialog(QWidget *parent, const char *name) + : KDialogBase(IconList, i18n("Configure"), Help|Default|Ok|Apply|Cancel, Ok, parent, name, false, true) +{ + + //******** Server ************************************ + serverTab = addPage(i18n("Server"),i18n("DICT Server Configuration"), BarIcon("network", KIcon::SizeMedium )); + QGridLayout* grid = new QGridLayout(serverTab,10,3,0,spacingHint()); + + w_server = new KLineEdit(serverTab); + w_server->setText(global->server); + QLabel *l = new QLabel(w_server, i18n("Host&name:"), serverTab); + grid->addWidget(l,0,0); + grid->addMultiCellWidget(w_server,0,0,1,2); + connect( w_server, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotChanged() ) ); + + w_port = new KLineEdit(serverTab); + w_port->setValidator(new KIntValidator(0,65536,this)); + w_port->setText(QString::number(global->port)); + l = new QLabel(w_port, i18n("&Port:"), serverTab); + grid->addWidget(l,1,0); + grid->addWidget(w_port,1,1); + connect( w_port, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotChanged() ) ); + + w_idleHold = new KIntSpinBox(0,300,5,0,10,serverTab); + w_idleHold->setSuffix(i18n(" sec")); + w_idleHold->setValue(global->idleHold); + l = new QLabel(w_idleHold, i18n("Hold conn&ection for:"), serverTab); + grid->addWidget(l,2,0); + grid->addWidget(w_idleHold,2,1); + connect( w_idleHold, SIGNAL( valueChanged(int) ), this, SLOT( slotChanged() ) ); + + w_timeout = new KIntSpinBox(5,600,5,5,10,serverTab); + w_timeout->setSuffix(i18n(" sec")); + w_timeout->setValue(global->timeout); + l = new QLabel(w_timeout, i18n("T&imeout:"), serverTab); + grid->addWidget(l,3,0); + grid->addWidget(w_timeout,3,1); + connect( w_timeout, SIGNAL( valueChanged(int) ), this, SLOT( slotChanged() ) ); + + w_pipesize = new KIntSpinBox(100,5000,2,2,10,serverTab); + w_pipesize->setSuffix(i18n(" bytes")); + w_pipesize->setValue(global->pipeSize); + l = new QLabel(w_pipesize, i18n("Command &buffer:"), serverTab); + grid->addWidget(l,4,0); + grid->addWidget(w_pipesize,4,1); + connect( w_pipesize, SIGNAL( valueChanged(int) ), this, SLOT( slotChanged() ) ); + + QStringList encodingNames = KGlobal::charsets()->descriptiveEncodingNames(); + int i=0,x=0; + for ( QStringList::Iterator it = encodingNames.begin(); it != encodingNames.end(); ++it ) { + if (KGlobal::charsets()->encodingForName(*it)==global->encoding) { + x = i; + break; + } + i++; + } + w_encoding = new QComboBox(serverTab); + w_encoding->insertStringList(encodingNames); + w_encoding->setCurrentItem(x); + l = new QLabel(w_encoding, i18n("Encod&ing:"), serverTab); + grid->addWidget(l,5,0); + grid->addMultiCellWidget(w_encoding,5,5,1,2); + connect( w_encoding, SIGNAL( activated(int) ), this, SLOT( slotChanged() ) ); + + w_auth = new QCheckBox(i18n("Server requires a&uthentication"),serverTab); + w_auth->setChecked(global->authEnabled); + grid->addMultiCellWidget(w_auth,6,6,0,2); + connect( w_auth, SIGNAL( toggled(bool) ), this, SLOT( slotChanged() ) ); + connect(w_auth,SIGNAL(toggled(bool)),SLOT(slotAuthRequiredToggled(bool))); + + w_user = new KLineEdit(serverTab); + w_user->setText(global->user); + l_user = new QLabel(w_user, i18n("U&ser:"),serverTab); + grid->addWidget(l_user,7,0); + grid->addMultiCellWidget(w_user,7,7,1,2); + connect( w_user, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotChanged() ) ); + + w_secret = new KLineEdit(serverTab); + w_secret->setEchoMode(QLineEdit::Password); + w_secret->setText(global->secret); + l_secret = new QLabel(w_secret, i18n("Pass&word:"), serverTab); + grid->addWidget(l_secret,8,0); + grid->addMultiCellWidget(w_secret,8,8,1,2); + connect( w_secret, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotChanged() ) ); + + slotAuthRequiredToggled( w_auth->isChecked() ); + + grid->setColStretch(1,2); + grid->setColStretch(2,2); + + //************ Appearance *************************** + appTab = addPage(i18n("Appearance"),i18n("Customize Visual Appearance"), BarIcon("appearance", KIcon::SizeMedium )); + + QGridLayout *topL=new QGridLayout(appTab, 8, 3, 0, spacingHint()); + + //color-list + c_List = new DialogListBox(false, appTab); + topL->addMultiCellWidget(c_List,1,3,0,1); + connect(c_List, SIGNAL(selected(QListBoxItem*)),SLOT(slotColItemSelected(QListBoxItem*))); + connect(c_List, SIGNAL(selectionChanged()), SLOT(slotColSelectionChanged())); + + c_olorCB = new QCheckBox(i18n("&Use custom colors"),appTab); + topL->addWidget(c_olorCB,0,0); + connect(c_olorCB, SIGNAL(toggled(bool)), this, SLOT(slotColCheckBoxToggled(bool))); + connect(c_olorCB, SIGNAL(toggled(bool) ), this, SLOT( slotChanged())); + + c_olChngBtn=new QPushButton(i18n("Cha&nge..."), appTab); + connect(c_olChngBtn, SIGNAL(clicked()), SLOT(slotColChangeBtnClicked())); + topL->addWidget(c_olChngBtn,1,2); + + c_olDefBtn=new QPushButton(i18n("Default&s"), appTab); + connect(c_olDefBtn, SIGNAL(clicked()), SLOT(slotColDefaultBtnClicked())); + topL->addWidget(c_olDefBtn,2,2); + connect(c_olDefBtn, SIGNAL(clicked()), SLOT(slotChanged())); + + //font-list + f_List = new DialogListBox(false, appTab); + topL->addMultiCellWidget(f_List,5,7,0,1); + connect(f_List, SIGNAL(selected(QListBoxItem*)),SLOT(slotFontItemSelected(QListBoxItem*))); + connect(f_List, SIGNAL(selectionChanged()),SLOT(slotFontSelectionChanged())); + + f_ontCB = new QCheckBox(i18n("Use custom &fonts"),appTab); + topL->addWidget(f_ontCB,4,0); + connect(f_ontCB, SIGNAL(toggled(bool)), SLOT(slotFontCheckBoxToggled(bool))); + connect(f_ontCB, SIGNAL(toggled(bool)), SLOT(slotChanged())); + + f_ntChngBtn=new QPushButton(i18n("Chang&e..."), appTab); + connect(f_ntChngBtn, SIGNAL(clicked()), SLOT(slotFontChangeBtnClicked())); + topL->addWidget(f_ntChngBtn,5,2); + + f_ntDefBtn=new QPushButton(i18n("Defaul&ts"), appTab); + connect(f_ntDefBtn, SIGNAL(clicked()), SLOT(slotFontDefaultBtnClicked())); + topL->addWidget(f_ntDefBtn,6,2); + connect(f_ntDefBtn, SIGNAL(clicked()), SLOT(slotChanged())); + + topL->setColStretch(1,2); + topL->setColStretch(2,0); + topL->setRowStretch(3,1); + topL->setRowStretch(7,1); + topL->setResizeMode(QLayout::Minimum); + + //init + c_olorCB->setChecked(global->useCustomColors); + slotColCheckBoxToggled(global->useCustomColors); + for(int i=0; icolorCount(); i++) + c_List->insertItem(new ColorListItem(global->colorName(i), global->color(i))); + + f_ontCB->setChecked(global->useCustomFonts); + slotFontCheckBoxToggled(global->useCustomFonts); + for(int i=0; ifontCount(); i++) + f_List->insertItem(new FontListItem(global->fontName(i), global->font(i))); + + //************ Layout *************************** + layoutTab = addPage(i18n("Layout"),i18n("Customize Output Format"), BarIcon("text_left", KIcon::SizeMedium )); + + QVBoxLayout *vbox = new QVBoxLayout(layoutTab, 0, spacingHint()); + + QButtonGroup *bGroup = new QButtonGroup(i18n("Headings"),layoutTab); + QVBoxLayout *bvbox = new QVBoxLayout(bGroup,8,5); + + bvbox->addSpacing(fontMetrics().lineSpacing()-4); + w_layout[0] = new QRadioButton(i18n("O&ne heading for each database"),bGroup); + w_layout[0]->setChecked(global->headLayout == 0); + bvbox->addWidget(w_layout[0],1); + w_layout[1] = new QRadioButton(i18n("A&s above, with separators between the definitions"),bGroup); + w_layout[1]->setChecked(global->headLayout == 1); + bvbox->addWidget(w_layout[1],1); + w_layout[2] = new QRadioButton(i18n("A separate heading for &each definition"),bGroup); + w_layout[2]->setChecked(global->headLayout == 2); + bvbox->addWidget(w_layout[2],1); + connect(w_layout[0], SIGNAL(toggled(bool)), SLOT(slotChanged())); + connect(w_layout[1], SIGNAL(toggled(bool)), SLOT(slotChanged())); + connect(w_layout[2], SIGNAL(toggled(bool)), SLOT(slotChanged())); + + vbox->addWidget(bGroup,0); + vbox->addStretch(1); + + //************ Other *************************** + otherTab = addPage(i18n("Miscellaneous"),i18n("Various Settings"), BarIcon("misc", KIcon::SizeMedium )); + + vbox = new QVBoxLayout(otherTab, 0, spacingHint()); + + QGroupBox *group = new QGroupBox(i18n("Limits"),otherTab); + + grid = new QGridLayout(group,4,2,8,5); + grid->addRowSpacing(0, fontMetrics().lineSpacing()-4); + + w_MaxDefinitions = new KIntSpinBox(100,10000,100,100,10,group); + w_MaxDefinitions->setValue(global->maxDefinitions); + l = new QLabel(w_MaxDefinitions, i18n("De&finitions:"), group); + grid->addWidget(l,1,0); + grid->addWidget(w_MaxDefinitions,1,1); + connect(w_MaxDefinitions, SIGNAL(valueChanged(int)), SLOT(slotChanged())); + + w_Maxbrowse = new KIntSpinBox(1,100,1,1,10,group); + w_Maxbrowse->setValue(global->maxBrowseListEntrys); + l = new QLabel(w_Maxbrowse, i18n("Cached &results:"), group); + grid->addWidget(l,2,0); + grid->addWidget(w_Maxbrowse,2,1); + connect(w_Maxbrowse, SIGNAL(valueChanged(int)), SLOT(slotChanged())); + + w_Maxhist = new KIntSpinBox(10,5000,10,10,10,group); + w_Maxhist->setValue(global->maxHistEntrys); + l = new QLabel(w_Maxhist, i18n("Hi&story entries:"), group); + grid->addWidget(l,3,0); + grid->addWidget(w_Maxhist,3,1); + connect(w_Maxhist, SIGNAL(valueChanged(int)), SLOT(slotChanged())); + + grid->setColStretch(1,1); + + vbox->addWidget(group,0); + + group = new QGroupBox(i18n("Other"),otherTab); + + QVBoxLayout *vbox2 = new QVBoxLayout(group, 8, 5); + + vbox2->addSpacing(fontMetrics().lineSpacing()-4); + + w_Savehist = new QCheckBox(i18n("Sa&ve history on exit"),group); + w_Savehist->setChecked(global->saveHistory); + vbox2->addWidget(w_Savehist,0); + connect(w_Savehist, SIGNAL(toggled(bool)), SLOT(slotChanged())); + + w_Clipboard = new QCheckBox(i18n("D&efine selected text on start"),group); + w_Clipboard->setChecked(global->defineClipboard); + vbox2->addWidget(w_Clipboard,1); + connect(w_Clipboard, SIGNAL(toggled(bool)), SLOT(slotChanged())); + + vbox->addWidget(group,0); + vbox->addStretch(2); + + setHelp("preferences"); + + if (global->optSize.isValid()) + resize(global->optSize); + else + resize(300,200); + enableButton( Apply, false ); + configChanged = false; +} + + +OptionsDialog::~OptionsDialog() +{ + global->optSize = size(); +} + + +void OptionsDialog::slotApply() +{ + global->server = w_server->text(); + global->port = w_port->text().toInt(); + global->timeout = w_timeout->value(); + global->idleHold = w_idleHold->value(); + global->pipeSize = w_pipesize->value(); + global->encoding = KGlobal::charsets()->encodingForName(w_encoding->currentText()); + global->authEnabled = w_auth->isChecked(); + global->user = w_user->text(); + global->secret = w_secret->text(); + global->useCustomColors=c_olorCB->isChecked(); + for(int i=0; icolorCount(); i++) + global->c_olors[i] = (static_cast(c_List->item(i)))->color(); + + global->useCustomFonts=f_ontCB->isChecked(); + for(int i=0; ifontCount(); i++) + global->f_onts[i] = (static_cast(f_List->item(i)))->font(); + if (w_layout[0]->isChecked()) + global->headLayout = 0; + else + if (w_layout[1]->isChecked()) + global->headLayout = 1; + else + global->headLayout = 2; + global->maxDefinitions = w_MaxDefinitions->value(); + global->maxBrowseListEntrys = w_Maxbrowse->value(); + global->maxHistEntrys = w_Maxhist->value(); + global->defineClipboard = w_Clipboard->isChecked(); + global->saveHistory = w_Savehist->isChecked(); + emit(optionsChanged()); + enableButton( Apply, false ); + configChanged = false; +} + + +void OptionsDialog::slotOk() +{ + if( configChanged ) + slotApply(); + KDialogBase::slotOk(); +} + + +void OptionsDialog::slotDefault() +{ + QStringList encodingNames; + int i=0,x=0; + + switch(activePageIndex()) { + case 0: + w_server->setText("dict.org"); + w_port->setText("2628"); + w_idleHold->setValue(30); + w_timeout->setValue(60); + w_pipesize->setValue(256); + encodingNames = KGlobal::charsets()->descriptiveEncodingNames(); + for ( QStringList::Iterator it = encodingNames.begin(); it != encodingNames.end(); ++it ) { + if (KGlobal::charsets()->encodingForName(*it)=="utf8") + x = i; + i++; + } + w_encoding->setCurrentItem(x); + w_auth->setChecked(false); + w_user->clear(); + w_user->setEnabled(false); + w_secret->clear(); + w_secret->setEnabled(false); + break; + case 1: + c_olorCB->setChecked(false); + slotColCheckBoxToggled(false); + slotColDefaultBtnClicked(); + f_ontCB->setChecked(false); + slotFontCheckBoxToggled(false); + slotFontDefaultBtnClicked(); + break; + case 2: + w_layout[0]->setChecked(true); + break; + case 3: + w_MaxDefinitions->setValue(2000); + w_Maxbrowse->setValue(15); + w_Maxhist->setValue(500); + w_Savehist->setChecked(true); + w_Clipboard->setChecked(false); + } +} + + +void OptionsDialog::slotAuthRequiredToggled( bool enable ) +{ + l_user->setEnabled( enable ); + l_secret->setEnabled( enable ); + w_user->setEnabled( enable ); + w_secret->setEnabled( enable ); +} + + +void OptionsDialog::slotColCheckBoxToggled(bool b) +{ + c_List->setEnabled(b); + c_olDefBtn->setEnabled(b); + c_olChngBtn->setEnabled(b && (c_List->currentItem()!=-1)); + if (b) c_List->setFocus(); +} + + +// show color dialog for the entry +void OptionsDialog::slotColItemSelected(QListBoxItem *it) +{ + if (it) { + ColorListItem *colorItem = static_cast(it); + QColor col = colorItem->color(); + int result = KColorDialog::getColor(col,this); + + if (result == KColorDialog::Accepted) { + colorItem->setColor(col); + c_List->triggerUpdate(false); + slotChanged(); + } + } +} + + +void OptionsDialog::slotColDefaultBtnClicked() +{ + ColorListItem *colorItem; + for(int i=0; i < global->colorCount(); i++) { + colorItem=static_cast(c_List->item(i)); + colorItem->setColor(global->defaultColor(i)); + } + c_List->triggerUpdate(true); + c_List->repaint(true); +} + + +void OptionsDialog::slotColChangeBtnClicked() +{ + if(c_List->currentItem()!=-1) + slotColItemSelected(c_List->item(c_List->currentItem())); +} + + +void OptionsDialog::slotColSelectionChanged() +{ + c_olChngBtn->setEnabled(c_List->currentItem()!=-1); +} + + +void OptionsDialog::slotFontCheckBoxToggled(bool b) +{ + f_List->setEnabled(b); + f_ntDefBtn->setEnabled(b); + f_ntChngBtn->setEnabled(b && (f_List->currentItem()!=-1)); + if (b) f_List->setFocus(); +} + + +// show font dialog for the entry +void OptionsDialog::slotFontItemSelected(QListBoxItem *it) +{ + if (it) { + FontListItem *fontItem = static_cast(it); + QFont font = fontItem->font(); + int result = KFontDialog::getFont(font,false,this); + + if (result == KFontDialog::Accepted) { + fontItem->setFont(font); + f_List->triggerUpdate(false); + slotChanged(); + } + } +} + + +void OptionsDialog::slotFontDefaultBtnClicked() +{ + FontListItem *fontItem; + for(int i=0; i < global->fontCount(); i++) { + fontItem=static_cast(f_List->item(i)); + fontItem->setFont(global->defaultFont(i)); + } + f_List->triggerUpdate(false); +} + + +void OptionsDialog::slotFontChangeBtnClicked() +{ + if(f_List->currentItem()!=-1) + slotFontItemSelected(f_List->item(f_List->currentItem())); +} + + +void OptionsDialog::slotFontSelectionChanged() +{ + f_ntChngBtn->setEnabled(f_List->currentItem()!=-1); +} + +void OptionsDialog::slotChanged() +{ + enableButton( Apply, true ); + configChanged = true; +} + + +//-------------------------------- + +#include "options.moc" diff --git a/kdict/options.h b/kdict/options.h new file mode 100644 index 00000000..ecef7606 --- /dev/null +++ b/kdict/options.h @@ -0,0 +1,229 @@ +/* ------------------------------------------------------------- + + options.h (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + GlobalData manages all global data of Kdict + DialgoListBox a list box which ignores Enter, usefull for dialogs + OptionsDialog the "Preferences" dialog + + ------------------------------------------------------------- */ + +#ifndef _KDICT_OPTIONS_H_ +#define _KDICT_OPTIONS_H_ + +#include +#include +#include + +class QLineEdit; +class QCheckBox; +class QComboBox; +class QRadioButton; + +class KColorButton; +class KLineEdit; +class KIntSpinBox; + + +//********* GlobalData ****************************************** + +#define COL_CNT 6 +#define FNT_CNT 2 + +class GlobalData +{ + +public: + + enum ColorIndex { Ctext=0, Cbackground=1, CheadingsText=2, CheadingsBackground=3, Clinks=4, CvisitedLinks=5 }; + enum FontIndex { Ftext=0, Fheadings=1 }; + + void read(); + void write(); + + // colors... + const QColor& color(int i) { return c_olors[i]; } + const QString& colorName(int i) { return c_olorNames[i]; } + int colorCount() const { return COL_CNT; } + QColor defaultColor(int i); + bool useCustomColors; + QColor textColor(); + QColor backgroundColor(); + QColor headingsTextColor(); + QColor headingsBackgroundColor(); + QColor linksColor(); + QColor visitedLinksColor(); + + // fonts... + const QFont& font(int i) { return f_onts[i]; } + const QString& fontName(int i) { return f_ontNames[i]; } + int fontCount() const { return FNT_CNT; } + QFont defaultFont(int); + bool useCustomFonts; + QFont textFont(); + QFont headingsFont(); + + QString encryptStr(const QString& aStr); + + bool defineClipboard; // define clipboard content on startup? + + QSize optSize,setsSize,matchSize; // window geometry + bool showMatchList; + QValueList splitterSizes; + + KGlobalSettings::Completion queryComboCompletionMode; + + QStringList queryHistory; + bool saveHistory; // save query history to disk on exit? + unsigned int maxHistEntrys, maxBrowseListEntrys, maxDefinitions; + int headLayout; + + QString server; // network client... + int port,timeout,pipeSize,idleHold; + QString encoding; + bool authEnabled; + QString user, secret; + QStringList serverDatabases, databases, strategies; + QPtrList databaseSets; + unsigned int currentDatabase, currentStrategy; + + QColor c_olors[COL_CNT]; + QString c_olorNames[COL_CNT]; + QFont f_onts[FNT_CNT]; + QString f_ontNames[FNT_CNT]; + + QWidget *topLevel; +}; + +extern GlobalData *global; + + +//********* OptionsDialog ****************************************** + + +class OptionsDialog : public KDialogBase +{ + Q_OBJECT + +public: + + OptionsDialog(QWidget *parent=0, const char *name=0); + ~OptionsDialog(); + + //=================================================================================== + + class DialogListBox : public QListBox { + + public: + // alwaysIgnore==false: enter is ignored when the widget isn't visible/out of focus + DialogListBox(bool alwaysIgnore=false, QWidget * parent=0, const char * name=0); + ~DialogListBox(); + + protected: + void keyPressEvent( QKeyEvent *e ); + + bool a_lwaysIgnore; + }; + + //=================================================================================== + + class ColorListItem : public QListBoxText { + + public: + ColorListItem( const QString &text, const QColor &color=Qt::black ); + ~ColorListItem(); + const QColor& color() { return mColor; } + void setColor( const QColor &color ) { mColor = color; } + + protected: + virtual void paint( QPainter * ); + virtual int height( const QListBox * ) const; + virtual int width( const QListBox * ) const; + + private: + QColor mColor; + }; + + //=================================================================================== + + class FontListItem : public QListBoxText { + + public: + FontListItem( const QString &name, const QFont & ); + ~FontListItem(); + const QFont& font() { return f_ont; } + void setFont( const QFont &); + protected: + virtual void paint( QPainter * ); + virtual int width( const QListBox * ) const; + + private: + QFont f_ont; + QString fontInfo; + }; + + //=================================================================================== + +signals: + + void optionsChanged(); + +protected slots: + void slotApply(); + void slotOk(); + void slotDefault(); + void slotChanged(); + + //server + void slotAuthRequiredToggled( bool ); + + //colors + void slotColCheckBoxToggled(bool b); + void slotColItemSelected(QListBoxItem *); // show color dialog for the entry + void slotColDefaultBtnClicked(); + void slotColChangeBtnClicked(); + void slotColSelectionChanged(); + + //fonts + void slotFontCheckBoxToggled(bool b); + void slotFontItemSelected(QListBoxItem *); // show font dialog for the entry + void slotFontDefaultBtnClicked(); + void slotFontChangeBtnClicked(); + void slotFontSelectionChanged(); + +private: + + QFrame *serverTab; + QLabel *l_user, *l_secret; + KLineEdit *w_server, *w_user, *w_secret, *w_port; + QComboBox *w_encoding; + QCheckBox *w_auth; + KIntSpinBox *w_idleHold,*w_timeout,*w_pipesize; + + QFrame *appTab; + DialogListBox *c_List, + *f_List; + QCheckBox *c_olorCB, + *f_ontCB; + QPushButton *c_olDefBtn, + *c_olChngBtn, + *f_ntDefBtn, + *f_ntChngBtn; + + QFrame *layoutTab; + QRadioButton *w_layout[3]; + + QFrame *otherTab; + QCheckBox *w_Clipboard, *w_Savehist; + KIntSpinBox *w_Maxhist, *w_Maxbrowse, *w_MaxDefinitions; + bool configChanged; +}; + +#endif diff --git a/kdict/pics/Makefile.am b/kdict/pics/Makefile.am new file mode 100644 index 00000000..dfb183ec --- /dev/null +++ b/kdict/pics/Makefile.am @@ -0,0 +1,2 @@ +kdicticondir = $(kde_datadir)/kdict/icons +kdicticon_ICON = define_clip query_erase diff --git a/kdict/pics/cr16-action-define_clip.png b/kdict/pics/cr16-action-define_clip.png new file mode 100644 index 00000000..dd83280b Binary files /dev/null and b/kdict/pics/cr16-action-define_clip.png differ diff --git a/kdict/pics/cr16-action-query_erase.png b/kdict/pics/cr16-action-query_erase.png new file mode 100644 index 00000000..0fb00f91 Binary files /dev/null and b/kdict/pics/cr16-action-query_erase.png differ diff --git a/kdict/pics/cr22-action-define_clip.png b/kdict/pics/cr22-action-define_clip.png new file mode 100644 index 00000000..3ab1b0cc Binary files /dev/null and b/kdict/pics/cr22-action-define_clip.png differ diff --git a/kdict/pics/cr32-action-define_clip.png b/kdict/pics/cr32-action-define_clip.png new file mode 100644 index 00000000..f0c56ca4 Binary files /dev/null and b/kdict/pics/cr32-action-define_clip.png differ diff --git a/kdict/queryview.cpp b/kdict/queryview.cpp new file mode 100644 index 00000000..a2674d68 --- /dev/null +++ b/kdict/queryview.cpp @@ -0,0 +1,618 @@ +/* ------------------------------------------------------------- + + queryview.cpp (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + BrowseData data structure used for caching definitions + DictHTMLPart handling of middle mouse button clicks + QueryView widget that displays the definitions + + ------------------------------------------------------------- */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "actions.h" +#include "options.h" +#include "dict.h" +#include "queryview.h" +#include +#include + + +//******** SaveHelper ******************************************* + +QString SaveHelper::lastPath; + +SaveHelper::SaveHelper(const QString &saveName, const QString &filter, QWidget *parent) + : p_arent(parent), s_aveName(saveName), f_ilter(filter), file(0), tmpFile(0) +{ +} + + +SaveHelper::~SaveHelper() +{ + if (file) { // local filesystem, just close the file + delete file; + } else + if (tmpFile) { // network location, initiate transaction + tmpFile->close(); + if (KIO::NetAccess::upload(tmpFile->name(),url, p_arent) == false) + KMessageBox::error(global->topLevel, i18n("Unable to save remote file.")); + tmpFile->unlink(); // delete temp file + delete tmpFile; + } +} + + +QFile* SaveHelper::getFile(const QString &dialogTitle) +{ + url = KFileDialog::getSaveURL(lastPath+s_aveName,f_ilter,p_arent,dialogTitle); + + if (url.isEmpty()) + return 0; + + lastPath = url.url(-1); + lastPath.truncate(lastPath.length()-url.fileName().length()); + + if (url.isLocalFile()) { + if (QFileInfo(url.path()).exists() && + (KMessageBox::warningContinueCancel(global->topLevel, + i18n("A file named %1 already exists.\nDo you want to replace it?").arg(url.path()), + dialogTitle, i18n("&Replace")) != KMessageBox::Continue)) { + return 0; + } + + file = new QFile(url.path()); + if(!file->open(IO_WriteOnly)) { + KMessageBox::error(global->topLevel, i18n("Unable to save file.")); + delete file; + file = 0; + } + return file; + } else { + tmpFile = new KTempFile(); + if (tmpFile->status()!=0) + KMessageBox::error(global->topLevel, i18n("Unable to create temporary file.")); { + delete tmpFile; + tmpFile = 0; + return 0; + } + return tmpFile->file(); + } +} + + +//**** BrowseData **************************************************** + + +BrowseData::BrowseData(const QString &Nhtml, const QString &NqueryText) +: html(Nhtml),queryText(NqueryText),xPos(0),yPos(0) +{} + + +//********* DictHTMLPart ****************************************** + +DictHTMLPart::DictHTMLPart(QWidget *parentWidget, const char *widgetname) + : KHTMLPart(parentWidget,widgetname) +{} + + +DictHTMLPart::~DictHTMLPart() +{} + + +void DictHTMLPart::khtmlMouseReleaseEvent(khtml::MouseReleaseEvent *event) +{ + if (event->qmouseEvent()->button()==MidButton) + emit(middleButtonClicked()); + else + KHTMLPart::khtmlMouseReleaseEvent(event); +} + + +//********* QueryView ****************************************** + + +QueryView::QueryView(QWidget *_parent) +: QVBox( _parent), actBack(0L), actForward(0L), actQueryCombo(0L), browsePos(0), isRendering(false) +{ + browseList.setAutoDelete(true); + + part=new DictHTMLPart(this); + part->setDNDEnabled(false); + part->setJScriptEnabled(false); + part->setJavaEnabled(false); + part->setURLCursor(KCursor::handCursor()); + setFocusPolicy(QWidget::NoFocus); + connect(part, SIGNAL(completed()), SLOT(partCompleted())); + connect(part, SIGNAL(middleButtonClicked()), SLOT(middleButtonClicked())); + rightBtnMenu = new KPopupMenu(this); + connect(part,SIGNAL(popupMenu(const QString &, const QPoint &)),this,SLOT(buildPopupMenu(const QString &, const QPoint &))); + connect(part->browserExtension(),SIGNAL(openURLRequest(const KURL &,const KParts::URLArgs &)), + this,SLOT(slotURLRequest(const KURL &,const KParts::URLArgs &))); + connect(part->browserExtension(),SIGNAL(enableAction(const char *,bool)),this,SLOT(enableAction(const char *,bool))); + QTimer::singleShot( 0, this, SLOT(optionsChanged()) ); + connect(interface,SIGNAL(resultReady(const QString &, const QString &)), SLOT(resultReady(const QString &, const QString &))); +} + + +QueryView::~QueryView() +{} + + +void QueryView::setActions(KToolBarPopupAction* NactBack, KToolBarPopupAction* NactForward, DictComboAction* NactQueryCombo) +{ + actBack = NactBack; + connect(actBack->popupMenu(),SIGNAL(activated(int)),SLOT(browseBack(int))); + actForward = NactForward; + connect(actForward->popupMenu(),SIGNAL(activated(int)),SLOT(browseForward(int))); + actQueryCombo = NactQueryCombo; +} + + +bool QueryView::browseBackPossible() const +{ + return (browsePos > 0)? true:false; +} + + +bool QueryView::browseForwardPossible() const +{ + return (browsePos+1 < browseList.count()) ? true:false; +} + + +void QueryView::optionsChanged() +{ + saveCurrentResultPos(); + + currentHTMLHeader = QString("\n"); + + showResult(); // apply changes directly +} + + +void QueryView::stop() +{ + if (isRendering == true) { + part->closeURL(); + isRendering = false; + emit(renderingStopped()); + } +} + + +// print current result +void QueryView::printQuery() +{ + part->view()->print(); +} + + +// save the current result in an .html file +void QueryView::saveQuery() +{ + if (!browseList.isEmpty()) { + BrowseData* brw = browseList.at(browsePos); + QString fName = brw->queryText+".html"; + fName.replace(QRegExp("[\\s/]"),"_"); + SaveHelper helper(fName,"*.html",global->topLevel); + QFile *file = helper.getFile(QString::null); + + if (file) { + QTextStream stream(file); + stream.setEncoding(QTextStream::Locale); + stream << currentHTMLHeader+brw->html; + } + } +} + + +void QueryView::browseBack() +{ + if (browseBackPossible()) { + saveCurrentResultPos(); + browsePos--; + actQueryCombo->setEditText(browseList.at(browsePos)->queryText); + showResult(); + updateBrowseActions(); + } +} + + +void QueryView::browseForward() +{ + if (browseForwardPossible()) { + saveCurrentResultPos(); + browsePos++; + actQueryCombo->setEditText(browseList.at(browsePos)->queryText); + showResult(); + updateBrowseActions(); + } +} + + +void QueryView::selectAll() +{ + part->selectAll(); +} + + +void QueryView::copySelection() +{ + kapp->clipboard()->setText(part->selectedText()); +} + + +void QueryView::showFindDialog() +{ + KAction *act = part->actionCollection()->action("find"); + if (act) + act->activate(); +} + + +void QueryView::paletteChange ( const QPalette & ) +{ + + optionsChanged(); +} + + +void QueryView::fontChange ( const QFont & ) +{ + optionsChanged(); +} + + +void QueryView::saveCurrentResultPos() +{ + if (!browseList.isEmpty()) { + browseList.at(browsePos)->xPos = part->view()->contentsX(); + browseList.at(browsePos)->yPos = part->view()->contentsY(); + } +} + + +void QueryView::showResult() +{ + if (!isRendering) { + isRendering = true; + emit(renderingStarted()); + } + + part->begin(); + if (browseList.isEmpty()) { + part->write(currentHTMLHeader+""); + part->end(); + } else { + BrowseData* brw = browseList.at(browsePos); + emit(newCaption(getShortString(brw->queryText.simplifyWhiteSpace(),70))); + part->write(currentHTMLHeader+brw->html); + part->end(); + part->view()->setFocus(); + } +} + + +void QueryView::resultReady(const QString &result, const QString &query) +{ + BrowseData* brw = new BrowseData(result,query); + + if (browseList.isEmpty()) { + browsePos = 0; + browseList.append(brw); + } else { + saveCurrentResultPos(); + while (browseList.count()>browsePos+1) + browseList.removeLast(); + browseList.append(brw); + browsePos++; + while (browseList.count()>global->maxBrowseListEntrys) { + browseList.removeFirst(); + browsePos--; + } + } + + showResult(); + emit(enablePrintSave()); + actQueryCombo->selectAll(); + updateBrowseActions(); +} + + +void QueryView::partCompleted() +{ + if (!browseList.isEmpty()) + part->view()->setContentsPos(browseList.at(browsePos)->xPos,browseList.at(browsePos)->yPos); + if (isRendering) { + emit(renderingStopped()); + isRendering = false; + } +} + + +void QueryView::slotURLRequest (const KURL &url, const KParts::URLArgs &) +{ + QString type = url.host(); + QString urlText = url.prettyURL(); + urlText.remove(0,8+type.length()); + + if (type.length()) { // valid url + if(type=="define") + emit(defineRequested(urlText)); + if(type=="dbinfo") + interface->showDbInfo(urlText.utf8()); + if(type=="realhttp") + kapp->invokeBrowser("http://"+urlText); + if(type=="realftp") + kapp->invokeBrowser("ftp://"+urlText); + } +} + + +void QueryView::middleButtonClicked() +{ + emit(clipboardRequested()); +} + + +// construct the right-mouse-button-popup-menu on demand +void QueryView::buildPopupMenu(const QString &url, const QPoint &point) +{ + rightBtnMenu->clear(); + + if (!url.isEmpty()) { // menuitem if mouse is over link + KURL u(url); + QString type = u.host(); + popupLink = u.prettyURL(); + popupLink.remove(0,8+type.length()); + + if (type.length()) { // valid url + if(type=="define") { + rightBtnMenu->insertItem(i18n("Define &Synonym"), + this,SLOT(popupDefineLink())); + rightBtnMenu->insertItem(i18n("M&atch Synonym"), + this,SLOT(popupMatchLink())); + rightBtnMenu->insertSeparator(); + } + if(type=="dbinfo") { + rightBtnMenu->insertItem(i18n("D&atabase Information"),this,SLOT(popupDbInfo())); + rightBtnMenu->insertSeparator(); + } + if(type=="realhttp") { + popupLink.prepend("http://"); + rightBtnMenu->insertItem(SmallIcon("fileopen"), + i18n("&Open Link"), + this,SLOT(popupOpenLink())); + rightBtnMenu->insertSeparator(); + } + if(type=="realftp") { + popupLink.prepend("ftp://"); + rightBtnMenu->insertItem(SmallIcon("fileopen"), + i18n("&Open Link"), + this,SLOT(popupOpenLink())); + rightBtnMenu->insertSeparator(); + } + } + } + + if (part->hasSelection()) { + popupSelect = part->selectedText(); + rightBtnMenu->insertItem(i18n("&Define Selection"), + this,SLOT(popupDefineSelect())); + rightBtnMenu->insertItem(i18n("&Match Selection"), + this,SLOT(popupMatchSelect())); + rightBtnMenu->insertSeparator(); + } else { + kapp->clipboard()->setSelectionMode(false); + QString text = kapp->clipboard()->text(); + if (text.isEmpty()) { + kapp->clipboard()->setSelectionMode(true); + text = kapp->clipboard()->text(); + } + if (!text.isEmpty()) { + popupSelect = QApplication::clipboard()->text(); + rightBtnMenu->insertItem(SmallIcon("define_clip"), + i18n("&Define Clipboard Content"), + this,SLOT(popupDefineSelect())); + rightBtnMenu->insertItem(i18n("&Match Clipboard Content"), + this,SLOT(popupMatchSelect())); + rightBtnMenu->insertSeparator(); + } + } + + int ID; + + if (browseBackPossible()) { // if possible, show string + if (browseList.at(browsePos-1)->queryText.isEmpty()) + rightBtnMenu->insertItem(SmallIcon("back"), + i18n("&Back: Information"), + this,SLOT(browseBack())); + else + rightBtnMenu->insertItem(SmallIcon("back"), + i18n("&Back: '%1'").arg(getShortString(browseList.at(browsePos-1)->queryText,25)), + this,SLOT(browseBack())); + } else { + ID = rightBtnMenu->insertItem(SmallIcon("back"), i18n("&Back"), this, SLOT(browseBack())); + rightBtnMenu->setItemEnabled(ID,false); + } + + if (browseForwardPossible()) { // if possible, show string + if (browseList.at(browsePos+1)->queryText.isEmpty()) + rightBtnMenu->insertItem(SmallIcon("forward"), + i18n("&Forward: Information"), + this,SLOT(browseForward())); + else + rightBtnMenu->insertItem(SmallIcon("forward"), + i18n("&Forward: '%1'").arg(getShortString(browseList.at(browsePos+1)->queryText,25)), + this,SLOT(browseForward())); + } else { + ID = rightBtnMenu->insertItem(SmallIcon("forward"),i18n("&Forward"),this,SLOT(browseForward())); + rightBtnMenu->setItemEnabled(ID,false); + } + + rightBtnMenu->popup(point); +} + + +void QueryView::popupDefineLink() +{ + emit(defineRequested(popupLink)); +} + + +void QueryView::popupMatchLink() +{ + emit(matchRequested(popupLink)); +} + + +void QueryView::popupOpenLink() +{ + kapp->invokeBrowser(popupLink); +} + + +void QueryView::popupDefineSelect() +{ + emit(defineRequested(popupSelect)); +} + + +void QueryView::popupMatchSelect() +{ + emit(matchRequested(popupSelect)); +} + + +void QueryView::popupDbInfo() +{ + + interface->showDbInfo(popupLink.utf8()); +} + + +void QueryView::enableAction(const char * name, bool enabled) +{ + if (!strcmp(name,"copy")) + emit(enableCopy(enabled)); +} + + +void QueryView::browseBack(int index) +{ + int x = browsePos-index; + if (x>=0) { + saveCurrentResultPos(); + browsePos = x; + actQueryCombo->setEditText(browseList.at(browsePos)->queryText); + showResult(); + QTimer::singleShot(0, this, SLOT(updateBrowseActions())); // don't clear the menu in this slot + } +} + + +void QueryView::browseForward(int index) +{ + int x = browsePos+index; + if (x < (int)(browseList.count())) { + saveCurrentResultPos(); + browsePos = x; + actQueryCombo->setEditText(browseList.at(browsePos)->queryText); + showResult(); + QTimer::singleShot(0, this, SLOT(updateBrowseActions())); // don't clear the menu in this slot + } +} + + +void QueryView::updateBrowseActions() +{ + if (browseBackPossible()) { + actBack->setEnabled(true); + if (browseList.at(browsePos-1)->queryText.isEmpty()) + actBack->setText(i18n("&Back: Information")); + else + actBack->setText(i18n("&Back: '%1'").arg(getShortString(browseList.at(browsePos-1)->queryText,25))); + + actBack->popupMenu()->clear(); + int i = browsePos-1; + int num = 1; + QString s; + while ((i>=0)&&(num<=10)) { + s = browseList.at(i)->queryText; + if (s.isEmpty()) s = i18n("Information"); + actBack->popupMenu()->insertItem(s,num); + num++; + i--; + } + } else { + actBack->setEnabled(false); + actBack->setText(i18n("&Back")); + actBack->popupMenu()->clear(); + } + + if (browseForwardPossible()) { + actForward->setEnabled(true); + if (browseList.at(browsePos+1)->queryText.isEmpty()) + actForward->setText(i18n("&Forward: Information")); + else + actForward->setText(i18n("&Forward: '%1'").arg(getShortString(browseList.at(browsePos+1)->queryText,25))); + + actForward->popupMenu()->clear(); + int i = browsePos+1; + int num = 1; + QString s; + while ((i<(int)(browseList.count()))&&(num<=10)) { + s = browseList.at(i)->queryText; + if (s.isEmpty()) s = i18n("Information"); + actForward->popupMenu()->insertItem(s,num); + num++; + i++; + } + } else { + actForward->setEnabled(false); + actForward->setText(i18n("&Forward")); + actForward->popupMenu()->clear(); + } +} + +//-------------------------------- + +#include "queryview.moc" diff --git a/kdict/queryview.h b/kdict/queryview.h new file mode 100644 index 00000000..e942d297 --- /dev/null +++ b/kdict/queryview.h @@ -0,0 +1,178 @@ +/* ------------------------------------------------------------- + + queryview.h (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + SaveHelper network transparent file saving + BrowseData data structure used for caching definitions + DictHTMLPart handling of middle mouse button clicks + QueryView widget that displays the definitions + + ------------------------------------------------------------- */ + +#ifndef _QUERYVIEW_H_ +#define _QUERYVIEW_H_ + +#include +#include + +class QFile; +class KTempFile; +class KPopupMenu; +class DictComboAction; + + +QString getShortString(QString str,unsigned int length); + + +//******** SaveHelper ******************************************* + + +class SaveHelper { + +public: + + SaveHelper(const QString &saveName, const QString &filter, QWidget *parent); + ~SaveHelper(); + + // returns a file open for writing + QFile* getFile(const QString &dialogTitle); + +private: + + QWidget *p_arent; + QString s_aveName, f_ilter; + KURL url; + QFile* file; + KTempFile* tmpFile; + static QString lastPath; + +}; + + +//******** BrowseData ****************************************** + + +class BrowseData +{ + +public: + + BrowseData(const QString &Nhtml, const QString &NqueryText); + + QString html; + QString queryText; + int xPos,yPos; +}; + + +//********* DictHTMLPart *************************************** + +class DictHTMLPart : public KHTMLPart +{ + Q_OBJECT + +public: + + DictHTMLPart(QWidget *parentWidget = 0, const char *widgetname = 0); + ~DictHTMLPart(); + +signals: + void middleButtonClicked(); + +protected: + + virtual void khtmlMouseReleaseEvent(khtml::MouseReleaseEvent *event); + +}; + +//********* QueryView ****************************************** + + +class QueryView : public QVBox +{ + Q_OBJECT + +public: + + QueryView(QWidget *_parent = 0L); + ~QueryView(); + + void setActions(KToolBarPopupAction* NactBack, KToolBarPopupAction* NactForward, DictComboAction* NactQueryCombo); + + bool browseBackPossible() const; + bool browseForwardPossible() const; + + void stop(); + +public slots: + void optionsChanged(); + void printQuery(); + void saveQuery(); + void browseBack(); + void browseForward(); + void selectAll(); + void copySelection(); + void showFindDialog(); + +signals: + + void defineRequested(const QString &query); + void matchRequested(const QString &query); + void clipboardRequested(); + void enableCopy(bool selected); // emited when the user selects/deselects text + void enablePrintSave(); + void renderingStarted(); + void renderingStopped(); + void newCaption(const QString&); + +protected: + + void paletteChange ( const QPalette & ); + void fontChange ( const QFont & ); + + void saveCurrentResultPos(); + void showResult(); + +protected slots: + + void resultReady(const QString &result, const QString &query); + void partCompleted(); + void slotURLRequest (const KURL &url, const KParts::URLArgs &args); + void middleButtonClicked(); + void buildPopupMenu(const QString &url, const QPoint &point); + void popupDefineLink(); + void popupMatchLink(); + void popupOpenLink(); + void popupDefineSelect(); + void popupMatchSelect(); + void popupDbInfo(); + void enableAction(const char *, bool); + void browseBack(int); + void browseForward(int); + void updateBrowseActions(); + +private: + + DictHTMLPart *part; // Widgets + + KToolBarPopupAction *actBack, *actForward; + DictComboAction *actQueryCombo; + + KPopupMenu *rightBtnMenu; + QString popupLink,popupSelect; // needed for rightbtn-popup menu + + QPtrList browseList; + unsigned int browsePos; // position in browseList + QString currentHTMLHeader; + + bool isRendering; +}; + +#endif diff --git a/kdict/sets.cpp b/kdict/sets.cpp new file mode 100644 index 00000000..0a252cec --- /dev/null +++ b/kdict/sets.cpp @@ -0,0 +1,326 @@ +/* ------------------------------------------------------------- + + sets.cpp (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + DbSetsDialog dialog for editing the user defined database sets + + ------------------------------------------------------------- */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "options.h" +#include "sets.h" + + +//********* DbSetsDialog ****************************************** + + +DbSetsDialog::DbSetsDialog(QWidget *parent, const char *name) + : KDialogBase(Plain, i18n("Database Sets"),Close | Help, Close, parent, name, false, true) +{ + QFrame* page=plainPage(); + + QStringList sets; + for(unsigned int i=1;idatabaseSets.count()+1;i++) + sets.append(global->databases[i]); + + QVBoxLayout * topLayout = new QVBoxLayout(page, 0, 0); + + QHBoxLayout * subLayout1 = new QHBoxLayout(5); + topLayout->addLayout(subLayout1,0); + + w_set = new QComboBox(true,page); + w_set->setFixedHeight(w_set->sizeHint().height()); + w_set->setInsertionPolicy (QComboBox::NoInsertion); + w_set->insertStringList(sets); + connect(w_set, SIGNAL(activated(int)),this, SLOT(activateSet(int))); + QLabel *l = new QLabel(w_set, i18n("&Set:"),page); + l->setMinimumSize(l->sizeHint()); + subLayout1->addWidget(l,0); + subLayout1->addWidget(w_set,1); + + subLayout1->addSpacing(8); + + w_save = new QPushButton(i18n("S&ave"),page); + connect(w_save,SIGNAL(clicked()),this, SLOT(transferSet())); + subLayout1->addWidget(w_save,0); + + QPushButton *btn = new QPushButton(i18n("&New"),page); + btn->setMinimumSize(btn->sizeHint()); + connect(btn, SIGNAL(clicked()),this, SLOT(newPressed())); + subLayout1->addWidget(btn,0); + + w_delete = new QPushButton(i18n("&Delete"),page); + w_delete->setMinimumSize(w_delete->sizeHint()); + connect(w_delete, SIGNAL(clicked()),this, SLOT(deletePressed())); + subLayout1->addWidget(w_delete,0); + + topLayout->addSpacing(8); + + KSeparator *sep = new KSeparator(page); + topLayout->addWidget(sep,0); + + topLayout->addSpacing(8); + + QGridLayout * subLayout2 = new QGridLayout(7,3,6); + topLayout->addLayout(subLayout2,1); + + w_leftBox = new QListBox(page); + connect(w_leftBox, SIGNAL(selected(int)),this, SLOT(leftSelected(int))); + connect(w_leftBox, SIGNAL(highlighted(int)),this, SLOT(leftHighlighted(int))); + QLabel *leftLabel = new QLabel(w_leftBox, i18n("S&elected databases:"),page); + leftLabel->setMinimumSize(leftLabel->sizeHint()); + subLayout2->addWidget(leftLabel,0,0); + subLayout2->addMultiCellWidget(w_leftBox,1,6,0,0); + + w_allLeft = new QPushButton(page); + w_allLeft->setIconSet(BarIconSet("2leftarrow")); + connect(w_allLeft, SIGNAL(clicked()),this, SLOT(allLeftPressed())); + subLayout2->addWidget(w_allLeft,2,1); + + w_left = new QPushButton(page); + w_left->setIconSet(BarIconSet("1leftarrow")); + connect(w_left, SIGNAL(clicked()),this, SLOT(leftPressed())); + subLayout2->addWidget(w_left,3,1); + + w_right = new QPushButton(page); + w_right->setIconSet(BarIconSet("1rightarrow")); + connect(w_right, SIGNAL(clicked()),this, SLOT(rightPressed())); + subLayout2->addWidget(w_right,4,1); + + w_allRight = new QPushButton(page); + w_allRight->setIconSet(BarIconSet("2rightarrow")); + connect(w_allRight, SIGNAL(clicked()),this, SLOT(allRightPressed())); + subLayout2->addWidget(w_allRight,5,1); + + w_rightBox = new QListBox(page); + connect(w_rightBox, SIGNAL(selected(int)),this, SLOT(rightSelected(int))); + connect(w_rightBox, SIGNAL(highlighted(int)),this, SLOT(rightHighlighted(int))); + QLabel *rightLabel = new QLabel(w_rightBox, i18n("A&vailable databases:"),page); + rightLabel->setMinimumSize(rightLabel->sizeHint()); + subLayout2->addWidget(rightLabel,0,2); + subLayout2->addMultiCellWidget(w_rightBox,1,6,2,2); + + subLayout2->setRowStretch(1,1); + subLayout2->setRowStretch(6,1); + subLayout2->setColStretch(0,1); + subLayout2->setColStretch(2,1); + + setHelp("database-sets"); + + if (global->setsSize.isValid()) + resize(global->setsSize); + else + resize(300,200); + + if ((global->currentDatabase>=1)&&(global->currentDatabase<=global->databaseSets.count())) + activateSet(global->currentDatabase-1); + else + activateSet(0); + w_set->setFocus(); +} + + +void DbSetsDialog::hideEvent(QHideEvent *) +{ + global->setsSize = size(); + emit(dialogClosed()); +} + + +void DbSetsDialog::newPressed() +{ + QStringList *temp = new QStringList; + temp->append(i18n("New Set")); + global->databaseSets.append(temp); + global->databases.insert(global->databases.at(global->databaseSets.count()),i18n("New Set")); + if (global->currentDatabase >= global->databaseSets.count()) + global->currentDatabase++; + + QStringList sets; // reread sets, because w_sets internal list is not correct in all cases + for(unsigned int i=1;idatabaseSets.count()+1;i++) + sets.append(global->databases[i]); + w_set->clear(); + w_set->insertStringList(sets); + emit(setsChanged()); + activateSet(global->databaseSets.count()-1); + w_set->setFocus(); +} + + +void DbSetsDialog::deletePressed() +{ + int pos = w_set->currentItem(); + if (pos>=0) { + global->databaseSets.remove(global->databaseSets.at(pos)); + global->databases.remove(global->databases.at(pos+1)); + if ((int)global->currentDatabase >= pos+1) + global->currentDatabase--; + w_set->removeItem(pos); + if (pos >= w_set->count()) + pos--; + emit(setsChanged()); + activateSet(pos); + w_set->setFocus(); + } +} + + +void DbSetsDialog::allLeftPressed() +{ + while (w_rightBox->count()) { + w_leftBox->insertItem(w_rightBox->text(0)); + w_rightBox->removeItem(0); + } + w_leftBox->sort(); + checkButtons(); +} + + +void DbSetsDialog::leftPressed() +{ + int pos = w_rightBox->currentItem(); + if (pos>=0) { + w_leftBox->insertItem(w_rightBox->text(pos)); + w_leftBox->sort(); + w_rightBox->removeItem(pos); + if (pos >= (int)w_rightBox->count()) + pos--; + if (pos>=0) + w_rightBox->setCurrentItem(pos); + checkButtons(); + } +} + + +void DbSetsDialog::rightPressed() +{ + int pos = w_leftBox->currentItem(); + if (pos>=0) { + w_rightBox->insertItem(w_leftBox->text(pos)); + w_rightBox->sort(); + w_leftBox->removeItem(pos); + if (pos >= (int)w_leftBox->count()) + pos--; + if (pos>=0) + w_leftBox->setCurrentItem(pos); + checkButtons(); + } +} + + +void DbSetsDialog::allRightPressed() +{ + while (w_leftBox->count()) { + w_rightBox->insertItem(w_leftBox->text(0)); + w_leftBox->removeItem(0); + } + w_rightBox->sort(); + checkButtons(); +} + + +void DbSetsDialog::closePressed() +{ + accept(); + global->setsSize = size(); + emit(dialogClosed()); +} + + +void DbSetsDialog::transferSet() +{ + global->databaseSets.at(w_set->currentItem())->clear(); + global->databaseSets.at(w_set->currentItem())->append(w_set->currentText()); + for (unsigned int i = 0;icount();i++) + global->databaseSets.at(w_set->currentItem())->append(w_leftBox->text(i)); + global->databases.remove(global->databases.at(w_set->currentItem()+1)); + global->databases.insert(global->databases.at(w_set->currentItem()+1),w_set->currentText()); + w_set->changeItem(w_set->currentText(),w_set->currentItem()); + emit(setsChanged()); +} + + +void DbSetsDialog::activateSet(int num) +{ + w_leftBox->clear(); + w_rightBox->clear(); + + if ((num < 0)||(num>=(int)global->databaseSets.count())) { + w_set->clearEdit(); + w_delete->setEnabled(false); + w_save->setEnabled(false); + w_rightBox->repaint(true); // Workaround for repaint-bug + w_leftBox->repaint(true); // Workaround for repaint-bug + } else { + w_set->setCurrentItem(num); + for (unsigned int i=0;iserverDatabases.count();i++) + if (global->databaseSets.at(num)->findIndex(global->serverDatabases[i])>0) + w_leftBox->insertItem(global->serverDatabases[i]); + else + w_rightBox->insertItem(global->serverDatabases[i]); + w_leftBox->sort(); + w_rightBox->sort(); + w_delete->setEnabled(true); + w_save->setEnabled(true); + if (w_rightBox->count()==0) + w_rightBox->repaint(true); // Workaround for repaint-bug + if (w_leftBox->count()==0) + w_leftBox->repaint(true); // Workaround for repaint-bug + w_leftBox->clearSelection(); + w_leftBox->centerCurrentItem(); + w_rightBox->clearSelection(); + w_rightBox->centerCurrentItem(); + } + checkButtons(); +} + + +void DbSetsDialog::leftSelected(int) +{ + rightPressed(); +} + + +void DbSetsDialog::rightSelected(int) +{ + leftPressed(); +} + + +void DbSetsDialog::leftHighlighted(int) +{ + w_right->setEnabled(true); +} + + +void DbSetsDialog::rightHighlighted(int) +{ + w_left->setEnabled(true); +} + +void DbSetsDialog::checkButtons() +{ + w_allLeft->setEnabled((w_rightBox->count()>0)); + w_allRight->setEnabled((w_leftBox->count()>0)); + w_right->setEnabled((w_leftBox->currentItem()>=0)); + w_left->setEnabled((w_rightBox->currentItem()>=0)); +} + +//-------------------------------- + +#include "sets.moc" diff --git a/kdict/sets.h b/kdict/sets.h new file mode 100644 index 00000000..3e874a6e --- /dev/null +++ b/kdict/sets.h @@ -0,0 +1,68 @@ +/* ------------------------------------------------------------- + + sets.h (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + dbSetsDialog dialog for editing the user defined database sets + + ------------------------------------------------------------- */ + +#ifndef _KDICT_SETS_H_ +#define _KDICT_SETS_H_ + +#include + +class QListBox; + + +//********* DbSetsDialog ****************************************** + + +class DbSetsDialog : public KDialogBase +{ + Q_OBJECT + +public: + + DbSetsDialog(QWidget *parent=0, const char *name=0); + +signals: + + void setsChanged(); + void dialogClosed(); + +protected: + void hideEvent(QHideEvent *); + +private slots: + + void newPressed(); + void deletePressed(); + void allLeftPressed(); + void leftPressed(); + void rightPressed(); + void allRightPressed(); + void closePressed(); + void transferSet(); + void activateSet(int num); + void leftSelected(int index); + void rightSelected(int index); + void leftHighlighted(int index); + void rightHighlighted(int index); + +private: + + void checkButtons(); + + QComboBox *w_set; + QListBox *w_leftBox, *w_rightBox; + QPushButton *w_delete,*w_save,*w_allLeft,*w_left,*w_right,*w_allRight; +}; + +#endif diff --git a/kdict/toplevel.cpp b/kdict/toplevel.cpp new file mode 100644 index 00000000..ee334cbd --- /dev/null +++ b/kdict/toplevel.cpp @@ -0,0 +1,778 @@ +/* ------------------------------------------------------------- + + toplevel.cpp (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + (C) by Matthias Hölzer 1998 + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + TopLevel The toplevel widget of Kdict. + + ------------------------------------------------------------- */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "actions.h" +#include "dict.h" +#include "options.h" +#include "queryview.h" +#include "matchview.h" +#include "sets.h" +#include "toplevel.h" + + +// cut a QString and add "..." +QString getShortString(QString str,unsigned int length) +{ + if (str.length()>length) { + str.truncate(length-3); + str.append("..."); + } + return str; +} + + +DictInterface *interface; +GlobalData *global; + + +TopLevel::TopLevel(QWidget* parent, const char* name) + : DCOPObject("KDictIface"), KMainWindow(parent, name, WType_TopLevel), + optDlg(0L), setsDlg(0L), stopRef(0) +{ + kapp->dcopClient()->setDefaultObject(objId()); + kapp->setMainWidget(this); + + global = new GlobalData(); + global->topLevel = this; + global->read(); + interface = new DictInterface(); + connect(interface,SIGNAL(infoReady()),SLOT(stratDbChanged())); + connect(interface,SIGNAL(started(const QString&)),SLOT(clientStarted(const QString&))); + connect(interface,SIGNAL(stopped(const QString&)),SLOT(clientStopped(const QString&))); + + queryView = new QueryView(this); + connect(queryView,SIGNAL(defineRequested(const QString&)),SLOT(define(const QString&))); + connect(queryView,SIGNAL(matchRequested(const QString&)),SLOT(match(const QString&))); + connect(queryView,SIGNAL(clipboardRequested()),SLOT(defineClipboard())); + connect(queryView,SIGNAL(enableCopy(bool)),SLOT(enableCopy(bool))); + connect(queryView,SIGNAL(enablePrintSave()),SLOT(enablePrintSave())); + connect(queryView,SIGNAL(renderingStarted()),SLOT(renderingStarted())); + connect(queryView,SIGNAL(renderingStopped()),SLOT(renderingStopped())); + connect(queryView,SIGNAL(newCaption(const QString&)),SLOT(newCaption(const QString&))); + + matchView = new MatchView(); + connect(matchView,SIGNAL(defineRequested(const QString&)),SLOT(define(const QString&))); + connect(matchView,SIGNAL(matchRequested(const QString&)),SLOT(match(const QString&))); + connect(matchView,SIGNAL(clipboardRequested()),SLOT(matchClipboard())); + connect(matchView,SIGNAL(windowClosed()),SLOT(toggleMatchListShow())); + connect(&resetStatusbarTimer,SIGNAL(timeout()),SLOT(resetStatusbar())); + + setupStatusBar(); + setupActions(); + recreateGUI(); + buildHistMenu(); + + if (global->showMatchList) + { // show splitter, html view & match list + splitter = new QSplitter(QSplitter::Horizontal,this); + splitter->setOpaqueResize( KGlobalSettings::opaqueResize() ); + queryView->reparent(splitter,0,queryView->pos(),true); + matchView->reparent(splitter,0,matchView->pos(),true); + setCentralWidget(splitter); + splitter->setResizeMode(matchView,QSplitter::KeepSize); + adjustMatchViewSize(); + } + else + { // show only html view + setCentralWidget(queryView); + matchView->hide(); + } + + //apply settings + resize(600,390); + applyMainWindowSettings(KGlobal::config(),"toplevel_options"); + stratDbChanged(); // fill combos, build menus + + actQueryCombo->setFocus(); // place cursor in combobox +} + + +TopLevel::~TopLevel() +{ +} + + +void TopLevel::normalStartup() +{ + if (global->defineClipboard) + defineClipboard(); +} + +// ******* DCOP Interface ******************************************************** + +void TopLevel::quit() +{ + kdDebug(5004) << "*DCOP call* TopLevel::quit()" << endl; + kapp->closeAllWindows(); +} + + +void TopLevel::makeActiveWindow() +{ + kdDebug(5004) << "*DCOP call* TopLevel::makeActiveWindow()" << endl; + raiseWindow(); +} + + +void TopLevel::definePhrase(QString phrase) +{ + kdDebug(5004) << "*DCOP call* TopLevel::definePhrase()" << endl; + define(phrase); + raiseWindow(); +} + + +void TopLevel::matchPhrase(QString phrase) +{ + kdDebug(5004) << "*DCOP call* TopLevel::matchPhrase()" << endl; + match(phrase); + raiseWindow(); +} + + +void TopLevel::defineClipboardContent() +{ + kdDebug(5004) << "*DCOP call* TopLevel::defineClipboardContent()" << endl; + defineClipboard(); + raiseWindow(); +} + + +void TopLevel::matchClipboardContent() +{ + kdDebug(5004) << "*DCOP call* TopLevel::matchClipboardContent()" << endl; + matchClipboard(); + raiseWindow(); +} + + +QStringList TopLevel::getDatabases() +{ + kdDebug(5004) << "*DCOP call* TopLevel::getDatabases()" << endl; + return global->databases; +} + + +QString TopLevel::currentDatabase() +{ + kdDebug(5004) << "*DCOP call* TopLevel::currentDatabase()" << endl; + return global->databases[global->currentDatabase]; +} + + +QStringList TopLevel::getStrategies() +{ + kdDebug(5004) << "*DCOP call* TopLevel::getStrategies()" << endl; + return global->strategies; +} + + +QString TopLevel::currentStrategy() +{ + kdDebug(5004) << "*DCOP call* TopLevel::currentStrategy()" << endl; + return global->strategies[global->currentStrategy]; +} + + +bool TopLevel::setDatabase(QString db) +{ + kdDebug(5004) << "*DCOP call* TopLevel::setDatabase()" << endl; + + int newCurrent = global->databases.findIndex(db); + if (newCurrent == -1) + return false; + else { + global->currentDatabase = newCurrent; + actDbCombo->setCurrentItem(global->currentDatabase); + return true; + } +} + + +bool TopLevel::setStrategy(QString strategy) +{ + kdDebug(5004) << "*DCOP call* TopLevel::setStrategy()" << endl; + + return matchView->selectStrategy(strategy); +} + + +bool TopLevel::historyGoBack() +{ + kdDebug(5004) << "*DCOP call* TopLevel::historyGoBack()" << endl; + + if (!queryView->browseBackPossible()) + return false; + else { + queryView->browseBack(); + return true; + } +} + + +bool TopLevel::historyGoForward() +{ + kdDebug(5004) << "*DCOP call* TopLevel::historyGoForward()" << endl; + + if (!queryView->browseForwardPossible()) + return false; + else { + queryView->browseForward(); + return true; + } +} + +// ******************************************************************************* + +void TopLevel::define(const QString &query) +{ + kdDebug(5004) << "TopLevel::define()" << endl; + actQueryCombo->setEditText(query); + doDefine(); +} + + +void TopLevel::defineClipboard() +{ + kdDebug(5004) << "TopLevel::defineClipboard()" << endl; + kapp->clipboard()->setSelectionMode(true); + QString text = kapp->clipboard()->text(); + if (text.isEmpty()) { + kapp->clipboard()->setSelectionMode(false); + text = kapp->clipboard()->text(); + } + define(text); +} + + +void TopLevel::match(const QString &query) +{ + kdDebug(5004) << "TopLevel::match()" << endl; + actQueryCombo->setEditText(query); + doMatch(); +} + + +void TopLevel::matchClipboard() +{ + kdDebug(5004) << "TopLevel::matchClipboard()" << endl; + kapp->clipboard()->setSelectionMode(true); + QString text = kapp->clipboard()->text(); + if (text.isEmpty()) { + kapp->clipboard()->setSelectionMode(false); + text = kapp->clipboard()->text(); + } + match(text); +} + + +bool TopLevel::queryClose() +{ + kdDebug(5004) << "TopLevel::queryClose()" << endl; + + saveMainWindowSettings(KGlobal::config(),"toplevel_options"); + saveMatchViewSize(); + global->queryComboCompletionMode = actQueryCombo->completionMode(); + + global->write(); + + return true; +} + + +void TopLevel::setupActions() +{ + // file menu... + actSave = KStdAction::save(queryView, SLOT(saveQuery()), actionCollection()); + actSave->setText(i18n("&Save As...")); + actSave->setEnabled(false); + actPrint = KStdAction::print(queryView, SLOT(printQuery()), actionCollection()); + actPrint->setEnabled(false); + actStartQuery = new KAction(i18n("St&art Query"),"reload", 0 , this, + SLOT(doDefine()), actionCollection(), "start_query"); + actStopQuery = new KAction(i18n("St&op Query"),"stop", 0 , this, + SLOT(stopClients()), actionCollection(), "stop_query"); + actStopQuery->setEnabled(false); + KStdAction::quit(kapp, SLOT(closeAllWindows()), actionCollection()); + + // edit menu... + actCopy = KStdAction::copy(queryView, SLOT(copySelection()), actionCollection()); + actCopy->setEnabled(false); + KStdAction::selectAll(queryView, SLOT(selectAll()), actionCollection()); + new KAction(i18n("&Define Clipboard Content"), "define_clip", 0 , this, + SLOT(defineClipboard()), actionCollection(), "define_clipboard"); + new KAction(i18n("&Match Clipboard Content"), 0 , this, + SLOT(matchClipboard()), actionCollection(), "match_clipboard"); + KStdAction::find(queryView, SLOT(showFindDialog()), actionCollection()); + + // history menu... + actBack = new KToolBarPopupAction(i18n("&Back"), "back", KStdAccel::shortcut(KStdAccel::Back), + queryView, SLOT(browseBack()), actionCollection(),"browse_back"); + actBack->setDelayed(true); + actBack->setStickyMenu(false); + actBack->setEnabled(false); + actForward = new KToolBarPopupAction(i18n("&Forward"), "forward", KStdAccel::shortcut(KStdAccel::Forward), + queryView, SLOT(browseForward()), actionCollection(),"browse_forward"); + actForward->setDelayed(true); + actForward->setStickyMenu(false); + actForward->setEnabled(false); + new KAction(i18n("&Clear History"), 0 , this, + SLOT(clearQueryHistory()), actionCollection(), "clear_history"); + + // server menu... + new KAction(i18n("&Get Capabilities"), 0 , interface, + SLOT(updateServer()), actionCollection(), "get_capabilities"); + new KAction(i18n("Edit &Database Sets..."), "edit", 0 , this, + SLOT(showSetsDialog()), actionCollection(), "edit_sets"); + new KAction(i18n("&Summary"), 0 , interface, + SLOT(showDatabases()), actionCollection(), "db_summary"); + new KAction(i18n("S&trategy Information"), 0 , interface, + SLOT(showStrategies()), actionCollection(), "strategy_info"); + new KAction(i18n("&Server Information"), 0 , interface, + SLOT(showInfo()), actionCollection(), "server_info"); + + // settings menu... + createStandardStatusBarAction(); + setStandardToolBarMenuEnabled(true); + + actShowMatchList = new KToggleAction(i18n("Show &Match List"), 0 , this, + SLOT(toggleMatchListShow()), actionCollection(), "show_match"); + actShowMatchList->setCheckedState(i18n("Hide &Match List")); + actShowMatchList->setChecked(global->showMatchList); + KStdAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), +actionCollection()); + KStdAction::configureToolbars(this, SLOT(slotConfToolbar()), actionCollection()); + KStdAction::preferences(this, SLOT(showOptionsDialog()), actionCollection()); + + // toolbar... + new KAction(i18n("Clear Input Field"), "query_erase", 0 , this, + SLOT(clearInput()), actionCollection(), "clear_query"); + + actQueryLabel = new DictLabelAction(i18n("&Look for:"), actionCollection(), "look_label"); + actQueryCombo = new DictComboAction(i18n("Query"), actionCollection(), "query_combo",true,true); + connect(actQueryCombo,SIGNAL(activated(const QString &)), SLOT(define(const QString&))); + actQueryCombo->setCompletionMode(global->queryComboCompletionMode); + actDbLabel = new DictLabelAction(i18n("&in"), actionCollection(), "in_label"); + actDbCombo = new DictComboAction(i18n("Databases"), actionCollection(), "db_combo",false,false); + connect(actDbCombo,SIGNAL(activated(int)),SLOT(databaseSelected(int))); + actDefineBtn = new DictButtonAction(i18n("&Define"), this, SLOT(doDefine()), actionCollection(), "define_btn"); + actMatchBtn = new DictButtonAction(i18n("&Match"), this, SLOT(doMatch()), actionCollection(), "match_btn"); + + queryView->setActions(actBack,actForward,actQueryCombo); +} + + +void TopLevel::setupStatusBar() +{ + statusBar()->insertItem(i18n(" Ready "),0,2); + statusBar()->setItemAlignment(0,AlignLeft | AlignVCenter); + + QString serverInfo; + if (global->authEnabled) + serverInfo = QString(" %1@%2:%3 ").arg(getShortString(global->user,50)) + .arg(getShortString(global->server,50)) + .arg(global->port); + else + serverInfo = QString(" %1:%3 ").arg(getShortString(global->server,50)) + .arg(global->port); + statusBar()->insertItem(serverInfo, 1,3); + statusBar()->setItemAlignment(1,AlignLeft | AlignVCenter); +} + + +void TopLevel::recreateGUI() +{ + kdDebug(5004) << "TopLevel::recreateGUI()" << endl; + createGUI("kdictui.rc", false); + actQueryCombo->setList(global->queryHistory); + actQueryCombo->clearEdit(); + actQueryLabel->setBuddy(actQueryCombo->widget()); + + actDbCombo->setList(global->databases); + actDbCombo->setCurrentItem(global->currentDatabase); + actDbLabel->setBuddy(actDbCombo->widget()); + int bwidth; + if (actDefineBtn->widthHint() > actMatchBtn->widthHint()) + bwidth = actDefineBtn->widthHint(); + else + bwidth = actMatchBtn->widthHint(); + actDefineBtn->setWidth(bwidth); + actMatchBtn->setWidth(bwidth); +} + + +// add text in the query-combobox to the history +void TopLevel::addCurrentInputToHistory() +{ + QString text(actQueryCombo->currentText()); + + // maintain queryHistory + global->queryHistory.remove(text); // no double entrys + global->queryHistory.prepend(text); // prepend new item + while (global->queryHistory.count()>global->maxHistEntrys) // shorten list + global->queryHistory.remove(global->queryHistory.fromLast()); + + actQueryCombo->setList(global->queryHistory); + actQueryCombo->setCurrentItem(0); + buildHistMenu(); +} + + + +// erase text in query-combobox +void TopLevel::clearInput() +{ + actQueryCombo->clearEdit(); + actQueryCombo->setFocus(); // place cursor in combobox +} + + +// define text in the combobox +void TopLevel::doDefine() +{ + QString text(actQueryCombo->currentText()); + + if (!text.isEmpty()) + { + addCurrentInputToHistory(); + actQueryCombo->selectAll(); + interface->define(text); + } +} + + +void TopLevel::doMatch() +{ + QString text(actQueryCombo->currentText()); + + if (!text.isEmpty()) + { + addCurrentInputToHistory(); + actQueryCombo->selectAll(); + + if (!global->showMatchList) + { + toggleMatchListShow(); + } + + matchView->match(text); + setCaption(getShortString(text.simplifyWhiteSpace(),70)); + } +} + + +void TopLevel::stopClients() +{ + interface->stop(); + queryView->stop(); +} + + +// rebuild history menu on demand +void TopLevel::buildHistMenu() +{ + unplugActionList("history_items"); + + historyActionList.setAutoDelete(true); + historyActionList.clear(); + + unsigned int i = 0; + while ((i<10)&&(iqueryHistory.count())) { + historyActionList.append( new KAction(getShortString(global->queryHistory[i],70), 0, this, SLOT(queryHistMenu()), + (QObject*)0, global->queryHistory[i].utf8().data()) ); + i++; + } + + plugActionList("history_items", historyActionList); +} + + +// process a query via the history menu +void TopLevel::queryHistMenu() +{ + QCString name = sender()->name(); + if (!name.isEmpty()) + define(QString::fromUtf8(name)); +} + + +void TopLevel::clearQueryHistory() +{ + global->queryHistory.clear(); + actQueryCombo->clear(); + buildHistMenu(); +} + + +// fill combos, rebuild menus +void TopLevel::stratDbChanged() +{ + actDbCombo->setList(global->databases); + actDbCombo->setCurrentItem(global->currentDatabase); + matchView->updateStrategyCombo(); + + unplugActionList("db_detail"); + + dbActionList.setAutoDelete(true); + dbActionList.clear(); + + for (unsigned int i=0;iserverDatabases.count();i++) + dbActionList.append( new KAction(global->serverDatabases[i], 0, this, SLOT(dbInfoMenuClicked()), + (QObject*)0, global->serverDatabases[i].utf8().data()) ); + + plugActionList("db_detail", dbActionList); +} + + +void TopLevel::dbInfoMenuClicked() +{ + QCString name = sender()->name(); + if (!name.isEmpty()) + interface->showDbInfo(name); +} + + +void TopLevel::databaseSelected(int num) +{ + global->currentDatabase = num; +} + + +void TopLevel::enableCopy(bool selected) +{ + actCopy->setEnabled(selected); +} + + +void TopLevel::enablePrintSave() +{ + actSave->setEnabled(true); + actPrint->setEnabled(true); +} + + +void TopLevel::clientStarted(const QString &message) +{ + statusBar()->changeItem(message,0); + resetStatusbarTimer.stop(); + stopRef++; + actStopQuery->setEnabled(stopRef>0); // enable stop-icon + kapp->setOverrideCursor(waitCursor); +} + + +void TopLevel::clientStopped(const QString &message) +{ + statusBar()->changeItem(message,0); + resetStatusbarTimer.start(4000); + if (stopRef > 0) + stopRef--; + actStopQuery->setEnabled(stopRef>0); // disable stop-icon + kapp->restoreOverrideCursor(); +} + + +void TopLevel::resetStatusbar() +{ + resetStatusbarTimer.stop(); + statusBar()->changeItem(i18n(" Ready "),0); +} + + +void TopLevel::renderingStarted() +{ + stopRef++; + actStopQuery->setEnabled(stopRef>0); // disable stop-icon + kapp->setOverrideCursor(waitCursor); +} + + +void TopLevel::renderingStopped() +{ + if (stopRef > 0) + stopRef--; + actStopQuery->setEnabled(stopRef>0); // disable stop-icon + kapp->restoreOverrideCursor(); +} + + +void TopLevel::newCaption(const QString &s) +{ + setCaption(s); +} + +void TopLevel::toggleMatchListShow() +{ + saveMatchViewSize(); + if (global->showMatchList) // list is visible -> hide it + { + global->showMatchList = false; + queryView->reparent(this,0,queryView->pos(),true); + matchView->reparent(this,0,matchView->pos(),true); + matchView->hide(); + delete splitter; + setCentralWidget(queryView); + } + else // list is not visible -> show it + { + global->showMatchList = true; + splitter = new QSplitter(QSplitter::Horizontal,this); + splitter->setOpaqueResize( KGlobalSettings::opaqueResize() ); + setCentralWidget(splitter); + splitter->show(); + queryView->reparent(splitter,0,queryView->pos(),true); + matchView->reparent(splitter,0,matchView->pos(),true); + splitter->setResizeMode(matchView,QSplitter::KeepSize); + adjustMatchViewSize(); + } + + actShowMatchList->setChecked(global->showMatchList); +} + + +void TopLevel::saveMatchViewSize() +{ + if (global->showMatchList) + { + global->splitterSizes = splitter->sizes(); + } +} + + +void TopLevel::adjustMatchViewSize() +{ + if (global->splitterSizes.count()==2) + { + splitter->setSizes(global->splitterSizes); + } +} + + +void TopLevel::slotConfToolbar() +{ + saveMainWindowSettings(KGlobal::config(),"toplevel_options"); + KEditToolbar dlg(actionCollection(), "kdictui.rc"); + connect(&dlg,SIGNAL( newToolbarConfig() ), this, SLOT( slotNewToolbarConfig() )); + dlg.exec(); +} + + +void TopLevel::slotNewToolbarConfig() +{ + recreateGUI(); + applyMainWindowSettings(KGlobal::config(),"toplevel_options"); + buildHistMenu(); // actionlists must be inserted + stratDbChanged(); +} + + +void TopLevel::showSetsDialog() +{ + if (!setsDlg) { + setsDlg = new DbSetsDialog(this); + connect(setsDlg,SIGNAL(setsChanged()),this,SLOT(setsChanged())); + connect(setsDlg,SIGNAL(dialogClosed()),this,SLOT(hideSetsDialog())); + setsDlg->show(); + } else { + KWin::activateWindow(setsDlg->winId()); + } +} + + +void TopLevel::hideSetsDialog() +{ + if (setsDlg) { + setsDlg->delayedDestruct(); + setsDlg = 0L; + } +} + + +void TopLevel::setsChanged() +{ + actDbCombo->setList(global->databases); + actDbCombo->setCurrentItem(global->currentDatabase); +} + + +void TopLevel::showOptionsDialog() +{ + if (!optDlg) { + optDlg = new OptionsDialog(this); + connect(optDlg,SIGNAL(optionsChanged()),this,SLOT(optionsChanged())); + connect(optDlg,SIGNAL(finished()),this,SLOT(hideOptionsDialog())); + optDlg->show(); + } else { + KWin::activateWindow(optDlg->winId()); + } +} + + +void TopLevel::hideOptionsDialog() +{ + if (optDlg) { + optDlg->delayedDestruct(); + optDlg=0; + } +} + + +void TopLevel::optionsChanged() +{ + QString serverInfo; + if (global->authEnabled) + serverInfo = QString(" %1@%2:%3 ").arg(getShortString(global->user,50)) + .arg(getShortString(global->server,50)) + .arg(global->port); + else + serverInfo = QString(" %1:%3 ").arg(getShortString(global->server,50)) + .arg(global->port); + statusBar()->changeItem(serverInfo,1); + interface->serverChanged(); // inform client + queryView->optionsChanged(); // inform html-view +} + +void TopLevel::raiseWindow() +{ + // Bypass focus stealing prevention + kapp->updateUserTimestamp(); + + KWin::WindowInfo info = KWin::windowInfo( winId() ); + + if ( !info.isOnCurrentDesktop() ) + { + KWin::setOnDesktop( winId(), KWin::currentDesktop() ); + } + + KWin::activateWindow(winId()); +} + + +//-------------------------------- + +#include "toplevel.moc" diff --git a/kdict/toplevel.h b/kdict/toplevel.h new file mode 100644 index 00000000..cd8fa25d --- /dev/null +++ b/kdict/toplevel.h @@ -0,0 +1,149 @@ +/* ------------------------------------------------------------- + + toplevel.h (part of The KDE Dictionary Client) + + Copyright (C) 2000-2001 Christian Gebauer + (C) by Matthias Hölzer 1998 + + This file is distributed under the Artistic License. + See LICENSE for details. + + ------------------------------------------------------------- + + TopLevel The toplevel widget of Kdict. + + ------------------------------------------------------------- */ + +#ifndef _TOPLEVEL_H_ +#define _TOPLEVEL_H_ + +#include +#include +#include "dcopinterface.h" + +class QSplitter; + +class KToggleAction; +class KToolBarPopupAction; + +class DictLabelAction; +class DictComboAction; +class DictButtonAction; +class MatchView; +class QueryView; +class OptionsDialog; +class DbSetsDialog; + + +class TopLevel : public KMainWindow, virtual public KDictIface +{ + Q_OBJECT + + friend class QueryView; + +public: + + TopLevel(QWidget* parent = 0, const char* name = 0); + ~TopLevel(); + + void normalStartup(); // called when started without commandline parameters + + // DCOP-Interface... + void quit(); + void makeActiveWindow(); + void definePhrase(QString phrase); + void matchPhrase(QString phrase); + void defineClipboardContent(); + void matchClipboardContent(); + QStringList getDatabases(); + QString currentDatabase(); + QStringList getStrategies(); + QString currentStrategy(); + bool setDatabase(QString db); + bool setStrategy(QString strategy); + bool historyGoBack(); + bool historyGoForward(); + +public slots: + + void define(const QString &query); + void defineClipboard(); + + void match(const QString &query); + void matchClipboard(); + +protected: + bool queryClose(); + +private: + + void setupActions(); + void setupStatusBar(); + void recreateGUI(); + void raiseWindow(); + + void addCurrentInputToHistory(); // add text in the query-combobox to the history + +private slots: + void clearInput(); // erase text in query-combobox + + void doDefine(); // define text in the combobox + void doMatch(); // match text in the combobox + + void stopClients(); + + void buildHistMenu(); + void queryHistMenu(); // process a query via the history menu + void clearQueryHistory(); + + void stratDbChanged(); + void dbInfoMenuClicked(); + void databaseSelected(int num); + + void enableCopy(bool selected); + void enablePrintSave(); + + void clientStarted(const QString &message); + void clientStopped(const QString &message); + void resetStatusbar(); + void renderingStarted(); + void renderingStopped(); + + void newCaption(const QString&); + + void toggleMatchListShow(); + void saveMatchViewSize(); + void adjustMatchViewSize(); + + void slotConfToolbar(); + void slotNewToolbarConfig(); + + void showSetsDialog(); + void hideSetsDialog(); + void setsChanged(); + + void showOptionsDialog(); + void hideOptionsDialog(); + void optionsChanged(); + +private: + + KAction *actSave, *actPrint, *actStartQuery, *actStopQuery, *actCopy; + KToggleAction *actShowMatchList; + DictLabelAction *actQueryLabel, *actDbLabel; + DictComboAction *actQueryCombo, *actDbCombo; + DictButtonAction *actDefineBtn, *actMatchBtn; + QPtrList historyActionList, dbActionList; + KToolBarPopupAction *actBack, *actForward; + + QSplitter *splitter; // widgets.... + QueryView *queryView; + MatchView *matchView; + OptionsDialog *optDlg; + DbSetsDialog *setsDlg; + + QTimer resetStatusbarTimer; + int stopRef; // remember how many "clients" are running +}; + +#endif diff --git a/kdnssd/Makefile.am b/kdnssd/Makefile.am new file mode 100644 index 00000000..cdc3548c --- /dev/null +++ b/kdnssd/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = ioslave kdedmodule diff --git a/kdnssd/ioslave/CONFIG_FORMAT b/kdnssd/ioslave/CONFIG_FORMAT new file mode 100644 index 00000000..43e34001 --- /dev/null +++ b/kdnssd/ioslave/CONFIG_FORMAT @@ -0,0 +1,53 @@ +Every DNS-SD service type used with zeroconf:/ ioslave needs +configuration file in $DATADIR/zeroconf with name matching service type. +Used entries: + +Type - obligatory, must match file name +Name - obligarory, specifies user friendly name shown in konqueror/ file dialog +Name[language] - optional, contains the localized friendly name. E.g. Name[gb]=Colour server +Exec - optional, specifies executable for helper service - used if service has no + corresponding helper protocol +Protocol - optional, specifies real protocol name, will be taken from service name + if not set (for example _ssh._tcp => ssh://) +Icon - optional, if not set will be taken from protocol +PathEntry - optional, contains name of attribute carrying path +UserEntry - optional, contains name of attribute carrying user name +PasswdEntry - optional, contains name of attribute carrying password + + +Examples: + +Name=FTP servers +Type=_ftp._tcp +PathEntry=path +UserEntry=u +PasswordEntry=p + +Protocol is taken from service type (ftp), if service is announced with attributes path, u or p, it +will be inserted into resolved URL. +Service published with: Name="Public files", type="_ftp._tcp", port=7773, attributes: path=/home/test/public_files, +u=test, p=public on hostname "Storage" will be resolved into ftp://test:public@Storage.local/home/test/public_files + +----- + + +Name=Remote shell (ssh) +Type=_ssh._tcp +UserEntry=u +PasswordEntry=p + + +Resolved URL will be ssh://user:password@machine:port/ . ssh is helper protocol so ktelnetservice will be launched for +this URL. + + + +----------- + +Name=Battleship games +Type=_kbattleship._tcp +Exec=kbattleship %u + +URL will be resolved into kbattleship://host:port, then "kbattleship kbattleship://host:port" will be launched + + diff --git a/kdnssd/ioslave/Makefile.am b/kdnssd/ioslave/Makefile.am new file mode 100644 index 00000000..7b2cbb9a --- /dev/null +++ b/kdnssd/ioslave/Makefile.am @@ -0,0 +1,21 @@ +INCLUDES = $(all_includes) + +kde_module_LTLIBRARIES = kio_zeroconf.la + +kio_zeroconf_la_SOURCES = dnssd.cpp +kio_zeroconf_la_LIBADD = $(LIB_KDNSSD) $(LIB_KIO) +kio_zeroconf_la_LDFLAGS = -avoid-version -module $(all_libraries) $(KDE_PLUGIN) + +protocol_DATA = zeroconf.protocol invitation.protocol +protocoldir = $(kde_servicesdir) + +services_DATA = _http._tcp _ftp._tcp _ldap._tcp _webdav._tcp _nfs._tcp _ssh._tcp +servicesdir = $(kde_datadir)/zeroconf + +remote_DATA = zeroconf.desktop +remotedir = $(kde_datadir)/remoteview + +messages: rc.cpp + $(XGETTEXT) *.cpp -o $(podir)/kio_zeroconf.pot + +METASOURCES = AUTO diff --git a/kdnssd/ioslave/_ftp._tcp b/kdnssd/ioslave/_ftp._tcp new file mode 100644 index 00000000..27655cb4 --- /dev/null +++ b/kdnssd/ioslave/_ftp._tcp @@ -0,0 +1,5 @@ +Name=FTP servers +Type=_ftp._tcp +PathEntry=path +UserEntry=u +PasswordEntry=p diff --git a/kdnssd/ioslave/_http._tcp b/kdnssd/ioslave/_http._tcp new file mode 100644 index 00000000..8ce740b7 --- /dev/null +++ b/kdnssd/ioslave/_http._tcp @@ -0,0 +1,5 @@ +Name=WWW servers +Type=_http._tcp +PathEntry=path +UserEntry=u +PasswordEntry=p diff --git a/kdnssd/ioslave/_ldap._tcp b/kdnssd/ioslave/_ldap._tcp new file mode 100644 index 00000000..74a3a0cf --- /dev/null +++ b/kdnssd/ioslave/_ldap._tcp @@ -0,0 +1,2 @@ +Name=Lightweight Directory Access Protocol +Type=_ldap._tcp diff --git a/kdnssd/ioslave/_nfs._tcp b/kdnssd/ioslave/_nfs._tcp new file mode 100644 index 00000000..4748fe5b --- /dev/null +++ b/kdnssd/ioslave/_nfs._tcp @@ -0,0 +1,3 @@ +Name=NFS Remote directory +Type=_nfs._tcp +PathEntry=path diff --git a/kdnssd/ioslave/_ssh._tcp b/kdnssd/ioslave/_ssh._tcp new file mode 100644 index 00000000..9deadb3c --- /dev/null +++ b/kdnssd/ioslave/_ssh._tcp @@ -0,0 +1,4 @@ +Name=Remote shell (ssh) +Type=_ssh._tcp +UserEntry=u +PasswordEntry=p diff --git a/kdnssd/ioslave/_telnet._tcp b/kdnssd/ioslave/_telnet._tcp new file mode 100644 index 00000000..4a8c676b --- /dev/null +++ b/kdnssd/ioslave/_telnet._tcp @@ -0,0 +1,4 @@ +Name=Remote shell (telnet) +Type=_telnet._tcp +UserEntry=u +PasswordEntry=p diff --git a/kdnssd/ioslave/_webdav._tcp b/kdnssd/ioslave/_webdav._tcp new file mode 100644 index 00000000..3c99b54a --- /dev/null +++ b/kdnssd/ioslave/_webdav._tcp @@ -0,0 +1,5 @@ +Name=WebDav remote directory +Type=_webdav._tcp +PathEntry=path +UserEntry=u +PasswordEntry=p diff --git a/kdnssd/ioslave/dnssd.cpp b/kdnssd/ioslave/dnssd.cpp new file mode 100644 index 00000000..650a37ff --- /dev/null +++ b/kdnssd/ioslave/dnssd.cpp @@ -0,0 +1,369 @@ +/*************************************************************************** + * Copyright (C) 2004, 2005 by Jakub Stachowski * + * qbast@go2.pl * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "dnssd.h" + +static const KCmdLineOptions options[] = +{ + { "+protocol", I18N_NOOP( "Protocol name" ), 0 }, + { "+pool", I18N_NOOP( "Socket name" ), 0 }, + { "+app", I18N_NOOP( "Socket name" ), 0 }, + KCmdLineLastOption +}; + +ZeroConfProtocol::ZeroConfProtocol(const QCString& protocol, const QCString &pool_socket, const QCString &app_socket) + : SlaveBase(protocol, pool_socket, app_socket), browser(0),toResolve(0), + configData(0) +{} + +ZeroConfProtocol::~ZeroConfProtocol() +{ + delete configData; +} + +void ZeroConfProtocol::get(const KURL& url ) +{ + if (!dnssdOK()) return; + UrlType t = checkURL(url); + switch (t) { + case HelperProtocol: + { + resolveAndRedirect(url,true); + mimeType("text/html"); + QString reply= "\n"; + reply+="\n\n

    "+i18n("Requested service has been launched in separate window."); + reply+="

    \n"; + data(reply.utf8()); + data(QByteArray()); + finished(); + break; + } + case Service: + resolveAndRedirect(url); + break; + default: + error(ERR_MALFORMED_URL,i18n("invalid URL")); + } +} +void ZeroConfProtocol::mimetype(const KURL& url ) +{ + resolveAndRedirect(url); +} + +UrlType ZeroConfProtocol::checkURL(const KURL& url) +{ + if (url.path()=="/") return RootDir; + QString service, type, domain; + dissect(url,service,type,domain); + const QString& proto = type.section('.',1,-1); + if (type[0]!='_' || (proto!="_udp" && proto!="_tcp")) return Invalid; + if (service.isEmpty()) return ServiceDir; + if (!domain.isEmpty()) { + if (!setConfig(type)) return Invalid; + if (!configData->readEntry("Exec").isNull()) return HelperProtocol; + return (KProtocolInfo::isHelperProtocol( configData->readEntry( "Protocol", + type.section(".",0,0).mid(1)))) ? HelperProtocol : Service; + } + return Invalid; +} + +// URL zeroconf://domain/_http._tcp/some%20service +// URL invitation://host:port/_http._tcp/some%20service?u=username&root=directory +void ZeroConfProtocol::dissect(const KURL& url,QString& name,QString& type,QString& domain) +{ + type = url.path().section("/",1,1); + domain = url.host(); + name = url.path().section("/",2,-1); + +} + +bool ZeroConfProtocol::dnssdOK() +{ + switch(ServiceBrowser::isAvailable()) { + case ServiceBrowser::Stopped: + error(KIO::ERR_UNSUPPORTED_ACTION, + i18n("The Zeroconf daemon (mdnsd) is not running.")); + return false; + case ServiceBrowser::Unsupported: + error(KIO::ERR_UNSUPPORTED_ACTION, + i18n("KDE has been built without Zeroconf support.")); + return false; + default: + return true; + } +} + +void ZeroConfProtocol::stat(const KURL& url) +{ + UDSEntry entry; + if (!dnssdOK()) return; + UrlType t = checkURL(url); + switch (t) { + case RootDir: + case ServiceDir: + buildDirEntry(entry,""); + statEntry(entry); + finished(); + break; + case Service: + resolveAndRedirect(url); + break; + case HelperProtocol: + { + QString name,type,domain; + dissect(url,name,type,domain); + buildServiceEntry(entry,name,type,domain); + statEntry(entry); + finished(); + break; + } + default: + error(ERR_MALFORMED_URL,i18n("invalid URL")); + } +} +QString ZeroConfProtocol::getAttribute(const QString& name) +{ + QString entry = configData->readEntry(name); + return (entry.isNull()) ? QString::null : toResolve->textData()[entry]; +} + +void ZeroConfProtocol::resolveAndRedirect(const KURL& url, bool useKRun) +{ + QString name,type,domain; + dissect(url,name,type,domain); + if (url.protocol()=="invitation") { + delete toResolve; + toResolve=0; + toResolve= new RemoteService(url); + if (!toResolve->isResolved()) error(ERR_MALFORMED_URL,i18n("Invalid URL")); + } else { + kdDebug() << "Resolve for " << name << ", " << type << ", " << domain << "\n"; + if (toResolve!=0) + if (toResolve->serviceName()==name && toResolve->type()==type && + toResolve->domain()==domain && toResolve->isResolved()) { + } else { + delete toResolve; + toResolve = 0; + } + if (toResolve==0) { + toResolve = new RemoteService(name,type,domain); + // or maybe HOST_NOT_FOUND? + if (!toResolve->resolve()) error(ERR_SERVICE_NOT_AVAILABLE,i18n("Unable to resolve service")); + } + } + KURL destUrl; + kdDebug() << "Resolved: " << toResolve->hostName() << "\n"; + destUrl.setProtocol(getProtocol(type)); + destUrl.setUser(getAttribute("UserEntry")); + destUrl.setPass(getAttribute("PasswordEntry")); + destUrl.setPath(getAttribute("PathEntry")); + destUrl.setHost(toResolve->hostName()); + destUrl.setPort(toResolve->port()); + // get exec from config or try getting it from helper protocol + if (useKRun) KRun::run(configData->readEntry("Exec",KProtocolInfo::exec(getProtocol(type))),destUrl); + else { + redirection(destUrl); + finished(); + } +} + +bool ZeroConfProtocol::setConfig(const QString& type) +{ + kdDebug() << "Setting config for " << type << endl; + if (configData) + { + if (configData->readEntry("Type")!=type) + { + delete configData; + configData=0L; + } + else + return true; + } + configData = new KConfig("zeroconf/"+type,false,false,"data"); + return (configData->readEntry("Type")==type); +} + +inline void buildAtom(UDSEntry& entry,UDSAtomTypes type, const QString& data) +{ + UDSAtom atom; + atom.m_uds=type; + atom.m_str=data; + entry.append(atom); +} +inline void buildAtom(UDSEntry& entry,UDSAtomTypes type, long data) +{ + UDSAtom atom; + atom.m_uds=type; + atom.m_long=data; + entry.append(atom); +} + + +void ZeroConfProtocol::buildDirEntry(UDSEntry& entry,const QString& name,const QString& type, const QString& host) +{ + entry.clear(); + buildAtom(entry,UDS_NAME,name); + buildAtom(entry,UDS_ACCESS,0555); + buildAtom(entry,UDS_SIZE,0); + buildAtom(entry,UDS_FILE_TYPE,S_IFDIR); + buildAtom(entry,UDS_MIME_TYPE,"inode/directory"); + if (!type.isNull()) buildAtom(entry,UDS_URL,"zeroconf:/"+((!host.isNull()) ? "/"+host+"/" : "" )+type+"/"); +} +QString ZeroConfProtocol::getProtocol(const QString& type) +{ + setConfig(type); + return configData->readEntry("Protocol",type.section(".",0,0).mid(1)); +} + +void ZeroConfProtocol::buildServiceEntry(UDSEntry& entry,const QString& name,const QString& type,const QString& domain) +{ + setConfig(type); + entry.clear(); + buildAtom(entry,UDS_NAME,name); + buildAtom(entry,UDS_ACCESS,0666); + QString icon=configData->readEntry("Icon",KProtocolInfo::icon(getProtocol(type))); + if (!icon.isNull()) buildAtom(entry,UDS_ICON_NAME,icon); + KURL protourl; + protourl.setProtocol(getProtocol(type)); + QString encname = "zeroconf://" + domain +"/" +type+ "/" + name; + if (KProtocolInfo::supportsListing(protourl)) { + buildAtom(entry,UDS_FILE_TYPE,S_IFDIR); + encname+="/"; + } else buildAtom(entry,UDS_FILE_TYPE,S_IFREG); + buildAtom(entry,UDS_URL,encname); +} + +void ZeroConfProtocol::listDir(const KURL& url ) +{ + + if (!dnssdOK()) return; + UrlType t = checkURL(url); + UDSEntry entry; + switch (t) { + case RootDir: + if (allDomains=url.host().isEmpty()) + browser = new ServiceBrowser(ServiceBrowser::AllServices); + else browser = new ServiceBrowser(ServiceBrowser::AllServices,url.host()); + connect(browser,SIGNAL(serviceAdded(DNSSD::RemoteService::Ptr)), + this,SLOT(newType(DNSSD::RemoteService::Ptr))); + break; + case ServiceDir: + if (url.host().isEmpty()) + browser = new ServiceBrowser(url.path(-1).section("/",1,-1)); + else browser = new ServiceBrowser(url.path(-1).section("/",1,-1),url.host()); + connect(browser,SIGNAL(serviceAdded(DNSSD::RemoteService::Ptr)), + this,SLOT(newService(DNSSD::RemoteService::Ptr))); + break; + case Service: + resolveAndRedirect(url); + return; + default: + error(ERR_MALFORMED_URL,i18n("invalid URL")); + return; + } + connect(browser,SIGNAL(finished()),this,SLOT(allReported())); + browser->startBrowse(); + kapp->eventLoop()->enterLoop(); +} +void ZeroConfProtocol::allReported() +{ + UDSEntry entry; + listEntry(entry,true); + finished(); + delete browser; + browser=0; + mergedtypes.clear(); + kapp->eventLoop()->exitLoop(); +} +void ZeroConfProtocol::newType(DNSSD::RemoteService::Ptr srv) +{ + if (mergedtypes.contains(srv->type())>0) return; + mergedtypes << srv->type(); + UDSEntry entry; + kdDebug() << "Got new entry " << srv->type() << endl; + if (!setConfig(srv->type())) return; + QString name = configData->readEntry("Name"); + if (!name.isNull()) { + buildDirEntry(entry,name,srv->type(), (allDomains) ? QString::null : + browser->browsedDomains()->domains()[0]); + listEntry(entry,false); + } +} +void ZeroConfProtocol::newService(DNSSD::RemoteService::Ptr srv) +{ + UDSEntry entry; + buildServiceEntry(entry,srv->serviceName(),srv->type(),srv->domain()); + listEntry(entry,false); +} + + +extern "C" +{ + int KDE_EXPORT kdemain( int argc, char **argv ) + { + // KApplication is necessary to use other ioslaves + putenv(strdup("SESSION_MANAGER=")); + KCmdLineArgs::init(argc, argv, "kio_zeroconf", 0, 0, 0, 0); + KCmdLineArgs::addCmdLineOptions( options ); + KApplication::disableAutoDcopRegistration(); + KApplication app; + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + ZeroConfProtocol slave( args->arg(0), args->arg(1), args->arg(2) ); + slave.dispatchLoop(); + return 0; + } +} + + +#include "dnssd.moc" + diff --git a/kdnssd/ioslave/dnssd.h b/kdnssd/ioslave/dnssd.h new file mode 100644 index 00000000..0f24dbee --- /dev/null +++ b/kdnssd/ioslave/dnssd.h @@ -0,0 +1,87 @@ +/*************************************************************************** + * Copyright (C) 2004, 2005 by Jakub Stachowski * + * qbast@go2.pl * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef _dnssd_H_ +#define _dnssd_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +class QCString; +using namespace KIO; +using namespace DNSSD; + +enum UrlType { RootDir, ServiceDir, Service, HelperProtocol, Invalid }; + +class ZeroConfProtocol : public QObject, public KIO::SlaveBase +{ + Q_OBJECT +public: + ZeroConfProtocol(const QCString& protocol, const QCString &pool_socket, const QCString &app_socket); + ~ZeroConfProtocol(); + virtual void get(const KURL& url); + virtual void mimetype(const KURL& url); + virtual void stat(const KURL& url); + virtual void listDir(const KURL& url ); +private: + // Create UDSEntry for zeroconf:/ or zeroconf:/type/ paths + void buildDirEntry(UDSEntry& entry,const QString& name,const QString& type=QString::null, + const QString& host=QString::null); + // Create UDSEntry for single services: dnssd:/type/service + void buildServiceEntry(UDSEntry& entry,const QString& name,const QString& type, + const QString& domain); + // Returns root dir, service dir, service or invalid + UrlType checkURL(const KURL& url); + // extract name, type and domain from URL + void dissect(const KURL& url,QString& name,QString& type,QString& domain); + // resolve given service and redirect() to it or use KRun on it (used for helper protocols) + void resolveAndRedirect(const KURL& url, bool useKRun = false); + bool dnssdOK(); + QString getAttribute(const QString& name); + QString getProtocol(const QString& type); + // try to load config file for given service type (or just return if already loaded) + bool setConfig(const QString& type); + + ServiceBrowser* browser; + // service types merged from all domains - to avoid duplicates + QStringList mergedtypes; + // last resolved or still being resolved services - acts as one-entry cache + RemoteService *toResolve; + // Config file for service - also acts as one-entry cache + KConfig *configData; + // listDir for all domains (zeroconf:/) or one specified (zeroconf://domain/) + bool allDomains; +private slots: + void newType(DNSSD::RemoteService::Ptr); + void newService(DNSSD::RemoteService::Ptr); + void allReported(); + +}; + +#endif diff --git a/kdnssd/ioslave/invitation.protocol b/kdnssd/ioslave/invitation.protocol new file mode 100644 index 00000000..ef6b068c --- /dev/null +++ b/kdnssd/ioslave/invitation.protocol @@ -0,0 +1,62 @@ +[Protocol] +exec=kio_zeroconf +protocol=invitation + +input=none +output=filesystem +listing=Name,Link,Type +reading=true +writing=false +makedir=false +deleting=false +linking=false +moving=false +Icon=network_local +Description=SD invitations +Description[be]=ЗапрашÑнні SD +Description[bg]=Покани SD +Description[bn]=à¦à¦¸à¦¡à¦¿ আমনà§à¦¤à§à¦°à¦£ +Description[bs]=SD pozivi +Description[ca]=Invitacions SD +Description[cs]=SD pozvánky +Description[da]=SD-invitationer +Description[de]=SD-Einladungen +Description[el]=ΠÏοσκλήσεις SD +Description[es]=Invitaciones SD +Description[et]=SD kutsed +Description[eu]=SD gonbidapenak +Description[fa]=دعوتهای SD +Description[fi]=SD-kutsut +Description[fr]=Invitations SD +Description[gl]=Invitacións SD +Description[hu]=SD meghívók +Description[is]=SD boð +Description[it]=Inviti SD +Description[ja]=SD 招待 +Description[ka]=SD მáƒáƒ¬áƒ•áƒ”ვრ+Description[kk]=SD шақырулар +Description[km]=លិážáž·ážâ€‹áž¢áž‰áŸ’ជើញ SD +Description[lt]=SD pakvietimai +Description[nb]=SD invitasjoner +Description[nds]=SD-Inladen +Description[ne]=SD निमनà¥à¤¤à¥à¤°à¤£à¤¾ +Description[nl]=SD-uitnodigingen +Description[nn]=SD-innbydingar +Description[pa]=SD ਸੱਦਾ +Description[pl]=Zaproszenia SD +Description[pt]=Convites de SD +Description[pt_BR]=solicitações SD +Description[ru]=ÐŸÑ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ SD +Description[sk]=SD pozvánky +Description[sl]=Povabila SD +Description[sr]=SD позивнице +Description[sr@Latn]=SD pozivnice +Description[sv]=SD-inbjudningar +Description[tr]=SD davetleri +Description[uk]=Ð—Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ SD +Description[zh_CN]=SD 邀请 +Description[zh_HK]=SD 邀請 +Description[zh_TW]=SD 邀請 +maxInstances=4 +Class=:remote + diff --git a/kdnssd/ioslave/zeroconf.desktop b/kdnssd/ioslave/zeroconf.desktop new file mode 100644 index 00000000..abc41151 --- /dev/null +++ b/kdnssd/ioslave/zeroconf.desktop @@ -0,0 +1,56 @@ +[Desktop Entry] +Icon=network_local +Name=Network Services +Name[be]=Ð¡ÐµÑ‚ÐºÐ°Ð²Ñ‹Ñ ÑервіÑÑ‹ +Name[bg]=Мрежови уÑлуги +Name[bn]=নেটওয়ারà§à¦• সারà§à¦­à¦¿à¦¸ +Name[br]=Servijoù rouedad +Name[bs]=Mrežni servisi +Name[ca]=Serveis de xarxa +Name[cs]=Síťové služby +Name[da]=Netværkstjenester +Name[de]=Netzwerkdienste +Name[el]=ΥπηÏεσίες δικτÏου +Name[eo]=Retservoj +Name[es]=Servicios de red +Name[et]=Võrguteenused +Name[eu]=Sare zerbitzuak +Name[fa]=خدمات شبکه +Name[fi]=Lähiverkkopalvelut +Name[fr]=Services réseaux +Name[ga]=Seirbhísí Líonra +Name[gl]=Servicios de Rede +Name[he]=שרותי רשת +Name[hu]=Hálózati szolgáltatások +Name[is]=Netþjónustur +Name[it]=Servizi di rete +Name[ja]=ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‚µãƒ¼ãƒ“ス +Name[ka]=ქსელის სერვისები +Name[kk]=Желі қызметтері +Name[km]=សáŸážœáž¶â€‹áž”ណ្ដាញ +Name[lt]=Tinklo tarnybos +Name[nb]=Nettverkstjenester +Name[nds]=Nettwarkdeensten +Name[ne]=सञà¥à¤œà¤¾à¤² सेवा +Name[nl]=Netwerkdiensten +Name[nn]=Nettverkstenester +Name[pa]=ਨੈੱਟਵਰਕ ਸੇਵਾਵਾਂ +Name[pl]=UsÅ‚ugi sieciowe +Name[pt]=Serviços de Rede +Name[pt_BR]=Serviços de Rede +Name[ro]=Servicii de reÅ£ea +Name[ru]=Сетевые Ñлужбы +Name[sk]=SieÅ¥ové služby +Name[sl]=Omrežne storitve +Name[sr]=Мрежни ÑервиÑи +Name[sr@Latn]=Mrežni servisi +Name[sv]=Nätverkstjänster +Name[tr]=AÄŸ Servisleri +Name[uk]=Мережеві Ñлужби +Name[uz]=Tarmoq xizmatlari +Name[uz@cyrillic]=Тармоқ хизматлари +Name[zh_CN]=网络æœåŠ¡ +Name[zh_HK]=網絡æœå‹™ +Name[zh_TW]=網路æœå‹™ +Type=Link +URL=zeroconf:/ diff --git a/kdnssd/ioslave/zeroconf.protocol b/kdnssd/ioslave/zeroconf.protocol new file mode 100644 index 00000000..5343b282 --- /dev/null +++ b/kdnssd/ioslave/zeroconf.protocol @@ -0,0 +1,63 @@ +[Protocol] +exec=kio_zeroconf +protocol=zeroconf + +input=none +output=filesystem +listing=Name,Link,Type +reading=true +writing=false +makedir=false +deleting=false +linking=false +moving=false +Icon=network_local +Description=A kioslave for ZeroConf +Description[be]=Модуль kioslave Ð´Ð»Ñ ZeroConf +Description[bn]=জিরো-কনà§à¦« à¦à¦° জনà§à¦¯ à¦à¦•à¦Ÿà¦¿ কে-আই-ও সà§à¦²à§‡à¦­ +Description[br]=Ur c'hioslave evit ZeroConf +Description[bs]=kioslave za ZeroConf +Description[ca]=Un kioslave per ZeroConf +Description[cs]=Pomocný protokol pro Zeroconf +Description[da]=En kioslave for Zeroconf +Description[de]=Ein Ein-/Ausgabemodul für ZeroConf +Description[el]=Ένα kioslave για το ZeroConf +Description[es]=Un «kioslave» para ZeroConf +Description[et]=ZeroConfi I/O-moodul +Description[eu]= kioslave bat ZeroConf-erako +Description[fa]=یک kioslave برای ZeroConf +Description[fi]=Siirräntätyöskentelijä ZeroConfille +Description[fr]=Un module d'entrée / sortie pour ZeroConf +Description[gl]=Un kioslabe para ZeroConf +Description[he]=kioslave בשביל ZeroConf +Description[hu]=KDE-protokoll a Zeroconf használatához +Description[is]=kioslave fyrir ZeroConf +Description[it]=Un kioslave per Zeroconf +Description[ja]=ZeroConf ã® kioslave +Description[ka]= kioslave ZeroConfსთვის +Description[kk]=ZeroConf-Ñ‚Ñ‹Ò£ kioslave-Ñ– +Description[km]=kioslave មួយ​សម្រាប់ ZeroConf +Description[lt]=AntrinÄ— KDE programa skirta ZeroConf +Description[nb]=En kioslave for ZeroConf +Description[nds]=In-/Utgaavmoduul för ZeroConf +Description[ne]=जेरोकनà¥à¤«à¤•à¤¾ लागि कियोसà¥à¤²à¤¾à¤­ +Description[nl]=Een kioslave voor ZeroConf +Description[nn]=Ein kio-slave for ZeroConf +Description[pl]=Wtyczka protokoÅ‚u ZeroConf +Description[pt]=Um 'kioslave' para o ZeroConf +Description[pt_BR]=Um IO-Slave para o ZeroConf +Description[ro]=Un dispozitiv de I/E pentru ZeroConf +Description[ru]=Kioslave Ð´Ð»Ñ ZeroConf +Description[sk]=kioslave pre ZeroConf +Description[sl]=Kioslave za ZeroConf +Description[sr]=kioslave за ZeroConf +Description[sr@Latn]=kioslave za ZeroConf +Description[sv]=En I/O-slav för Zeroconf +Description[tr]=Zeroconf kioslave'i +Description[uk]=Kioslave Ð´Ð»Ñ ZeroConf +Description[zh_CN]=ZeroConf çš„ kioslave +Description[zh_HK]=用於 ZeroConf çš„ kioslave +Description[zh_TW]=ZeroConf çš„ kioslave +maxInstances=4 +Class=:local + diff --git a/kdnssd/kdedmodule/Makefile.am b/kdnssd/kdedmodule/Makefile.am new file mode 100644 index 00000000..c01297db --- /dev/null +++ b/kdnssd/kdedmodule/Makefile.am @@ -0,0 +1,13 @@ +kde_module_LTLIBRARIES = kded_dnssdwatcher.la + +METASOURCES = AUTO +INCLUDES = $(all_includes) + +kded_dnssdwatcher_la_SOURCES = dnssdwatcher.cpp dnssdwatcher.skel watcher.cpp +kded_dnssdwatcher_la_LDFLAGS = $(all_libraries) -module -avoid-version +kded_dnssdwatcher_la_LIBADD = $(LIB_KDNSSD) $(LIB_KIO) + + +servicesdir = $(kde_servicesdir)/kded +services_DATA = dnssdwatcher.desktop + diff --git a/kdnssd/kdedmodule/dnssdwatcher.cpp b/kdnssd/kdedmodule/dnssdwatcher.cpp new file mode 100644 index 00000000..5647d29c --- /dev/null +++ b/kdnssd/kdedmodule/dnssdwatcher.cpp @@ -0,0 +1,94 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Jakub Stachowski + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include "dnssdwatcher.h" + +#include +#include +#include +#include +#include +#include "watcher.h" + + +DNSSDWatcher::DNSSDWatcher(const QCString& obj) + : KDEDModule(obj) +{ + connectDCOPSignal("","KDirNotify","enteredDirectory(KURL)","enteredDirectory(KURL)",false); + connectDCOPSignal("","KDirNotify","leftDirectory(KURL)","leftDirectory(KURL)",false); + watchers.setAutoDelete(true); +} + +QStringList DNSSDWatcher::watchedDirectories() +{ +//TODO +// return watchers.keys(); + QStringList keys; + for (QDictIterator it(watchers) ; it.current(); ++it ) { + keys << it.currentKey(); + kdDebug() << it.currentKey() << " " << (*it)->refcount << "\n"; + } +return keys; +} + + +// from ioslave +void DNSSDWatcher::dissect(const KURL& url,QString& name,QString& type,QString& domain) +{ + type = url.path().section("/",1,1); + domain = url.host(); + name = url.path().section("/",2,-1); +} + + + +void DNSSDWatcher::enteredDirectory(const KURL& dir) +{ + if (dir.protocol()!="zeroconf") return; + if (watchers[dir.url()]) watchers[dir.url()]->refcount++; + else createNotifier(dir); +} + + +void DNSSDWatcher::leftDirectory(const KURL& dir) +{ + if (dir.protocol()!="zeroconf") return; + if (!watchers[dir.url()]) return; + if ((watchers[dir.url()])->refcount==1) watchers.remove(dir.url()); + else watchers[dir.url()]->refcount--; +} + + +void DNSSDWatcher::createNotifier(const KURL& url) +{ + QString domain,type,name; + dissect(url,name,type,domain); + if (type.isEmpty()) type = DNSSD::ServiceBrowser::AllServices; + Watcher *w = new Watcher(type,domain); + watchers.insert(url.url(),w); +} + +extern "C" { + KDE_EXPORT KDEDModule *create_dnssdwatcher(const QCString &obj) + { + KGlobal::locale()->insertCatalogue("dnssdwatcher"); + return new DNSSDWatcher(obj); + } +} + +#include "dnssdwatcher.moc" diff --git a/kdnssd/kdedmodule/dnssdwatcher.desktop b/kdnssd/kdedmodule/dnssdwatcher.desktop new file mode 100644 index 00000000..31e7640e --- /dev/null +++ b/kdnssd/kdedmodule/dnssdwatcher.desktop @@ -0,0 +1,94 @@ +[Desktop Entry] +Type=Service +Name=DNS-SD Services Watcher +Name[be]=ÐглÑдальнік ÑервіÑаў DNS-SD +Name[bn]=ডিà¦à¦¨à¦à¦¸-à¦à¦¸à¦¡à¦¿ সারà§à¦­à¦¿à¦¸ পরà§à¦¯à¦¬à§‡à¦•à§à¦·à¦• +Name[bs]=Nadzor DNS-SD servisa +Name[ca]=Vigilant de serveis DNS-SD +Name[cs]=Sledování DNS-SD služeb +Name[da]=DNS-SD overvÃ¥gning af tjenester +Name[de]=Ãœberwachung von DNS-SD-Diensten +Name[el]=ΠαÏατηÏητής υπηÏεσιών DNS-SD +Name[es]=Observador de servicios DNS-SD +Name[et]=DNS-SD teenuste jälgija +Name[eu]=DNS-SD zerbitzu jarraitzailea +Name[fa]=پایندۀ خدمات DNS-SD +Name[fi]=DNS-SD-palvelujen tarkkailija +Name[fr]=Surveillance des services DNS-SD +Name[gl]=Observador de Servicios DNS-SD +Name[hu]=DNS-SD szolgáltatásfigyelÅ‘ +Name[is]=DNS-SD þjónustuvaktari +Name[it]=Sentinella dei servizi DNS-SD +Name[ja]=DNS-SD サービス監視 +Name[ka]=DNS-SD სერვისების მეთვáƒáƒšáƒ§áƒ£áƒ áƒ” +Name[kk]=DNS-SD қызметтер бақылаушыÑÑ‹ +Name[km]=កម្មវិធី​ឃ្លាំមើល​សáŸážœáž¶â€‹ DNS-SD +Name[lt]=DNS-SD tarnybos stebÄ—jimas +Name[nb]=OvervÃ¥ker for DNS-SD-tjenester +Name[nds]=DNS-SD-Deenstkieker +Name[ne]=DNS-SD सेवा दरà¥à¤¶à¤• +Name[nl]=DNS-SD-diensten observatie +Name[nn]=DNS-SD-tenesteovervakar +Name[pa]=DNS-SD ਸੇਵਾਵਾਂ ਵਾਂਚਰ +Name[pl]=Nadzorca usÅ‚ug DNS-SD +Name[pt]=Vigia de Serviços DNS-SD +Name[pt_BR]=Monitor dos Serviços DNS-SD +Name[ru]=Служба DNS-SD +Name[sk]=SledovaÄ DNS-SD služieb +Name[sl]=Opazovalec storitev DNS-SD +Name[sr]=Пратилац DNS-SD ÑервиÑа +Name[sr@Latn]=Pratilac DNS-SD servisa +Name[sv]=DNS-SD tjänstbevakning +Name[tr]=DNS-SD Servisi Ä°zleyicisi +Name[uk]=СпоÑÑ‚ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð° Ñлужбами DNS-SD +Name[zh_CN]=DNS-SD æœåŠ¡ç›‘视器 +Name[zh_HK]=DNS-SD æœå‹™ç›£å¯Ÿå™¨ +Name[zh_TW]=DNS-SD æœå‹™ç›£çœ‹å™¨ +Comment=Keeps track of DNS-SD services and updates directory listings +Comment[be]=Ðазірае за ÑпіÑам ÑервіÑаў DNS-SD Ñ– абнаўлÑе выглÑд дырÑкторыÑÑž +Comment[bn]=ডিà¦à¦¨à¦à¦¸-à¦à¦¸à¦¡à¦¿ সারà§à¦­à¦¿à¦¸à§‡à¦° খোà¦à¦œ রাখে à¦à¦¬à¦‚ ডিরেকà§à¦Ÿà¦°à§€ তালিকা আপডেট করে +Comment[bs]=Prati DNS-SD servise i ažurira spiskove direktorija +Comment[ca]=Segueix els serveis DNS-SD i actualitza les llistes de directoris +Comment[cs]=Udržuje pÅ™ehled o DNS-SD službách a aktualizuje výpisy adresářů +Comment[da]=Holder styr pÃ¥ DNS-SD-tjenester og opdaterer lister med mapper +Comment[de]=Ãœberwacht DNS-SD-Dienste und aktualisiert Verzeichniseinträge +Comment[el]=ΔιατηÏεί το ίχνος των υπηÏεσιών του DNS-SD και ενημεÏώνει τις λίστες του καταλόγου +Comment[es]=Vigila los servicios DNS-SD y actualiza los listados de directorio +Comment[et]=Jälgib DNS-SD teenuseid ja uuendab kataloogide nimekirju +Comment[eu]=DNS-SD zerbitzuak jarraitu eta direktorio zerrendak eguneratzen ditu +Comment[fa]=رد خدمات DNS-SD را نگهداری می‌کند Ùˆ Ùهرست برنامه‌های Ùهرست راهنما را به‌روزرسانی می‌کند +Comment[fi]=Pitää kirjaa DNS-SD-palveluista ja päivittää kansiolistaukset +Comment[fr]=Conserve une trace des services DNS-SD et actualise les listes de dossiers +Comment[gl]=Deixa constancia dos servicios DNS-SD e anova as listaxes de directorios +Comment[hu]=Követi a DNS-SD szolgáltatások állapotát és frissíti a hálózati listákat +Comment[is]=Fylgist með DNS-SD þjónustum og uppfærir möppulista +Comment[it]=Mantiene traccia dei servizi DNS-SD e aggiorna le liste delle directory +Comment[ja]=DNS-SD サービスã®ç®¡ç†ã¨ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªä¸€è¦§ã®æ›´æ–° +Comment[ka]=DNS-SD სერვისების ჩáƒáƒœáƒáƒ¬áƒ”რების შენáƒáƒ®áƒ•áƒ დრდირექტáƒáƒ áƒ˜áƒ˜áƒ¡ ჩáƒáƒ›áƒáƒœáƒáƒ—ვლის გáƒáƒœáƒáƒ®áƒšáƒ”ბრ+Comment[kk]=DNS-SD қызметтердің протоколын жүргізіп, каталогын жаңарту +Comment[km]=ážáž¶áž˜ážŠáž¶áž“​សáŸážœáž¶ DNS-SD និង​ធ្វើឲ្យ​ការ​រាយ​ážážâ€‹áž‘ាន់​សមáŸáž™ +Comment[lt]=Seka DNS-SD tarnybas ir atnaujina aplankų sÄ…raÅ¡us +Comment[nb]=Holder øye med DNS-SD-tjenester og oppdaterer katalogoppføringer +Comment[nds]=Kiekt na DNS-SD-Deensten un frischt Orneroplisten op +Comment[ne]=DNS-SD सेवाको टà¥à¤°à¤¯à¤¾à¤• राखà¥à¤¦à¤› र डाइरेकà¥à¤Ÿà¤°à¥€ सूची अदà¥à¤¯à¤¾à¤µà¤§à¤¿à¤• गरà¥à¤¦à¤› +Comment[nl]=Houdt de DNS-SD-diensten bij en actualiseert mappenweergaven +Comment[nn]=Held auge med DNS-SD-tenester og oppdaterer katalogar +Comment[pl]=Åšledzi usÅ‚ugi DNS-SD services i uaktualnia zawartość katalogu +Comment[pt]=Vigia os serviços DNS-SD e actualiza listas de pastas +Comment[pt_BR]=Monitora os serviços DNS-SD +Comment[ru]=Обновление каталогов DNS-SD +Comment[sk]=Sleduje DNS-SD služby a aktualizuje výpisy prieÄinku +Comment[sl]=Spremlja storitve DNS-SD in posodablja sezname imenikov +Comment[sr]=Ðадгледа DNS-SD ÑервиÑе и ажурира лиÑтинге директоријума +Comment[sr@Latn]=Nadgleda DNS-SD servise i ažurira listinge direktorijuma +Comment[sv]=HÃ¥ller ordning pÃ¥ DNS-SD tjänster och uppdaterar kataloglistor +Comment[tr]=DNS-SD servisi ve klasör listesini güncelleyisi +Comment[uk]=СпоÑтерігає за Ñлужбами DNS-SD та оновлює ÑпиÑки каталогів +Comment[zh_CN]=跟踪 DNS-SD æœåŠ¡å¹¶æ›´æ–°ç›®å½•åˆ—表 +Comment[zh_HK]=監察 DNS-SD æœå‹™ä¸¦æ›´æ–°ç›®éŒ„清單 +Comment[zh_TW]=追蹤 DNS-SD æœå‹™ä¸¦æ›´æ–°ç›®éŒ„清單 +ServiceTypes=KDEDModule +X-KDE-ModuleType=Library +X-KDE-Library=dnssdwatcher +X-KDE-FactoryName=dnssdwatcher +X-KDE-Kded-autoload=true diff --git a/kdnssd/kdedmodule/dnssdwatcher.h b/kdnssd/kdedmodule/dnssdwatcher.h new file mode 100644 index 00000000..e624532a --- /dev/null +++ b/kdnssd/kdedmodule/dnssdwatcher.h @@ -0,0 +1,49 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Jakub Stachowski + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef _DNSSDWATCHER_H_ +#define _DNSSDWATCHER_H_ + +#include +#include +#include +#include +#include + +class Watcher; +class DNSSDWatcher : public KDEDModule +{ +Q_OBJECT +K_DCOP +public: + DNSSDWatcher(const QCString& obj); + +k_dcop: + QStringList watchedDirectories(); + void enteredDirectory(const KURL& dir); + void leftDirectory(const KURL& dir); + +private: + QDict watchers; + + void createNotifier(const KURL& url); + void dissect(const KURL& url,QString& name,QString& type,QString& domain); + +}; + +#endif diff --git a/kdnssd/kdedmodule/watcher.cpp b/kdnssd/kdedmodule/watcher.cpp new file mode 100644 index 00000000..9fc32b14 --- /dev/null +++ b/kdnssd/kdedmodule/watcher.cpp @@ -0,0 +1,72 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Jakub Stachowski + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include "watcher.h" + +#include + +#include +#include + + + +Watcher::Watcher(const QString& type, const QString& domain) + : refcount(1), updateNeeded(false), m_type(type), m_domain(domain) +{ + if (domain.isEmpty()) browser = new ServiceBrowser(type); + else browser = new ServiceBrowser(type,domain); + connect(browser,SIGNAL(serviceAdded(DNSSD::RemoteService::Ptr)), + SLOT(serviceAdded(DNSSD::RemoteService::Ptr))); + connect(browser,SIGNAL(serviceRemoved(DNSSD::RemoteService::Ptr)), + SLOT(serviceRemoved(DNSSD::RemoteService::Ptr))); + connect(browser,SIGNAL(finished()),SLOT(finished())); + browser->startBrowse(); +} + +Watcher::~Watcher() +{ + delete browser; +} + +void Watcher::serviceAdded(DNSSD::RemoteService::Ptr) +{ + updateNeeded=true; +} + +void Watcher::serviceRemoved(DNSSD::RemoteService::Ptr srv) +{ + if (!updateNeeded) removed << srv; +} + + +void Watcher::finished() +{ + KDirNotify_stub st("*","*"); + kdDebug() << "Finished for " << m_type << "@" << m_domain << "\n"; + if (updateNeeded || removed.count()) { + QString url = "zeroconf:/"; + if (!m_domain.isEmpty()) url+="/"+m_domain+"/"; + if (m_type!=ServiceBrowser::AllServices) url+=m_type; + kdDebug() << "Sending update: " << url << "\n"; + st.FilesAdded(url); + } + removed.clear(); + updateNeeded=false; +} + +#include "watcher.moc" diff --git a/kdnssd/kdedmodule/watcher.h b/kdnssd/kdedmodule/watcher.h new file mode 100644 index 00000000..703680fc --- /dev/null +++ b/kdnssd/kdedmodule/watcher.h @@ -0,0 +1,50 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Jakub Stachowski + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef _WATCHER_H_ +#define _WATCHER_H_ + +#include +#include +#include + +using namespace DNSSD; + +class Watcher : public QObject +{ +Q_OBJECT +public: + Watcher(const QString& type, const QString& domain); + ~Watcher(); + + unsigned int refcount; +private: + ServiceBrowser* browser; + bool updateNeeded; + QString m_type; + QString m_domain; + QValueList removed; + +private slots: + void serviceRemoved(DNSSD::RemoteService::Ptr srv); + void serviceAdded(DNSSD::RemoteService::Ptr); + void finished(); + +}; + +#endif diff --git a/kfile-plugins/Makefile.am b/kfile-plugins/Makefile.am new file mode 100644 index 00000000..d092f6a0 --- /dev/null +++ b/kfile-plugins/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=torrent diff --git a/kfile-plugins/RETURNED_ITEMS b/kfile-plugins/RETURNED_ITEMS new file mode 100644 index 00000000..13a579ca --- /dev/null +++ b/kfile-plugins/RETURNED_ITEMS @@ -0,0 +1,15 @@ +If you make a new plugin, please add the list of returned items to this list. + +torrent plugin: +=========== + +type tag comment +============================================================================== +String name Default file/directory name. +Int length In Bytes, not the size of the .torrent. +String announce URL of the tracker server. +Date creation date Date the .torrent was generated. +Int NumFiles The number of files the .torrent will download. +Int piece length The block size used for this torrent. Each block + is downloaded and hashed separately. +String comment diff --git a/kfile-plugins/torrent/Makefile.am b/kfile-plugins/torrent/Makefile.am new file mode 100644 index 00000000..9727be90 --- /dev/null +++ b/kfile-plugins/torrent/Makefile.am @@ -0,0 +1,22 @@ +## Makefile.am for folder file meta info plugin + +INCLUDES = $(all_includes) + +# these are the headers for your project +noinst_HEADERS = kfile_torrent.h bbase.h bdict.h bytetape.h \ + bstring.h bint.h blist.h + +kde_module_LTLIBRARIES = kfile_torrent.la + +kfile_torrent_la_SOURCES = bytetape.cpp bint.cpp bstring.cpp blist.cpp bdict.cpp kfile_torrent.cpp +kfile_torrent_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +kfile_torrent_la_LIBADD = $(LIB_KIO) + +# let automoc handle all of the meta source files (moc) +METASOURCES = AUTO + +services_DATA = kfile_torrent.desktop +servicesdir = $(kde_servicesdir) + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kfile_torrent.pot diff --git a/kfile-plugins/torrent/README b/kfile-plugins/torrent/README new file mode 100644 index 00000000..c7147c9d --- /dev/null +++ b/kfile-plugins/torrent/README @@ -0,0 +1,7 @@ +This is a meta information plugin for BitTorrent files (*.torrent). + +It doesn't depend on BitTorrent or any non-standard library being installed. + +The .torrent files used by BitTorrent are coded in what is called a b-encoding +by the BitTorrent author. Please see the actual kfile plugin source code for +more information on the encoding. diff --git a/kfile-plugins/torrent/bbase.h b/kfile-plugins/torrent/bbase.h new file mode 100644 index 00000000..19455523 --- /dev/null +++ b/kfile-plugins/torrent/bbase.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2003, 2004 Michael Pyne + * + * This software 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 software 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 library; see the file COPYING. + * If not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _BBASE_H +#define _BBASE_H + +#include + +class QIODevice; + +/** + * Abstract base class for the b-encoded types. Re-implemented + * by BInt, BList, BDict, and BString. Derive from this class in + * order to make a new type for the b-encoding suite. Classes + * derived from this one should not throw exceptions. Instead, + * they should implement isValid(), and allow calling modules to + * check error status that way. + * + * @author Michael Pyne + * @see BInt, BList, BDict, BString, KSharedPtr + */ + +// Derive from KShared to enable use of KSharedPtr. +class BBase : public KShared +{ + public: + + /** + * Identifies the particular class that has been instantiated. All + * subclasses of this class should have an identifier here. + */ + enum classID { + bBase, /**< This class is BBase. No subclass should return this. */ + bString, /**< This class is a BString. */ + bInt, /**< This class is a BInt. */ + bList, /**< This class is a BList. */ + bDict /**< This class is a BDict. (Dictionary/Map) */ + }; + + /** + * Returns the type identification of the object. It will + * be a value in the classID enum. A subclass of this class + * must implement this function. + * + * @return type identifier of the class + * @see classID + */ + virtual classID type_id() const = 0; + + /** + * Destructor for the class. This function must be reimplemented + * in subclasses. + */ + virtual ~BBase () { ; } + + /** + * Returns the validity status of the object. Newly constructed + * objects are invalid unless the initialization sequence completed + * successfully. + * + * @return the validity status of the object + */ + virtual bool isValid() const = 0; + + /** + * Outputs the b-encoded representation of the object to the given + * QIODevice. + * @param device the QIODevice to write to + * @return true on a successful write, false otherwise + */ + virtual bool writeToDevice (QIODevice &device) = 0; +}; + +#endif /* _BBASE_H */ + +// vim: set et sw=4 ts=4: diff --git a/kfile-plugins/torrent/bdict.cpp b/kfile-plugins/torrent/bdict.cpp new file mode 100644 index 00000000..47b3bcad --- /dev/null +++ b/kfile-plugins/torrent/bdict.cpp @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2003, 2004 Michael Pyne + * + * This software 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 software 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 library; see the file COPYING. + * If not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include + +#include + +#include "bbase.h" +#include "bdict.h" +#include "bstring.h" +#include "bint.h" +#include "blist.h" + +BDict::BDict (QByteArray &dict, int start) + : m_map(), m_valid(false) +{ + ByteTape tape(dict, start); + + init (tape); +} + +BDict::BDict (ByteTape &tape) + : m_map(), m_valid (false) +{ + init (tape); +} + +void BDict::init (ByteTape &tape) +{ + if (*tape != 'd') + { + kdDebug(7034) << "This isn't a dictionary!" << endl; + return; // This isn't a dictionary + } + + tape++; + + // We need to loop and read in a string, then read in some data + while (*tape != 'e') + { + BBase *temp_item = 0; + + // Read in string + KSharedPtr str (new BString (tape)); + + // Ensure str will be automatically deleted + if (!str || !str->isValid()) + { + kdDebug(7034) << (str ? "Invalid string" : "Unable to read String!") << endl; + return; + } + + // Read in data + switch (*tape) + { + case 'l': + temp_item = new BList (tape); + break; + + case 'i': + temp_item = new BInt (tape); + break; + + case 'd': + temp_item = new BDict (tape); + break; + + default: + // Hopefully this is a string + temp_item = new BString (tape); + } + + if (!temp_item || !temp_item->isValid()) + { + kdDebug(7034) << (temp_item ? "Invalid item!" + : "Unable to create keyed data!") << endl; + return; + } + + m_map.insert(str->get_string(), temp_item); + } + + // Move past the 'e' + tape++; + + // Let the map delete the items + m_map.setAutoDelete (true); + + // Oh yeah, we're valid now, too. :-) + m_valid = true; +} + +BDict::~BDict () +{ + // QDict will take care of deleting each entry that + // it holds. +} + +BInt *BDict::findInt (const char *key) +{ + BBase *base = find(key); + + if (base && base->type_id() == bInt) + return dynamic_cast(base); + + return 0; +} + +BList *BDict::findList (const char *key) +{ + BBase *base = find(key); + + if (base && base->type_id() == bList) + return dynamic_cast(base); + + return 0; +} + +BDict *BDict::findDict (const char *key) +{ + BBase *base = find(key); + + if (base && base->type_id() == bDict) + return dynamic_cast(base); + + return 0; +} + +BString *BDict::findStr (const char *key) +{ + BBase *base = find(key); + + if (base && base->type_id() == bString) + return dynamic_cast(base); + + return 0; +} + +bool BDict::writeToDevice(QIODevice &device) +{ + if (!isValid()) + return false; + + const char *d_str = "d"; + const char *e_str = "e"; + Q_LONG written = 0, result = 0; + + written = device.writeBlock (d_str, 1); + while (written < 1) + { + if (written < 0 || result < 0) + return false; + + result = device.writeBlock (d_str, 1); + written += result; + } + + // Strings are supposed to be written in the dictionary such that + // the keys are in sorted order. QDictIterator doesn't support an + // ordering, so we have to get a list of all the keys, sort it, and + // then go by the list. + + BBaseHashIterator iter (m_map); + QStringList key_list; + + for ( ; iter.current(); ++iter) + key_list.append(iter.currentKey()); + + key_list.sort(); + + QStringList::Iterator key_iter; + for (key_iter = key_list.begin(); key_iter != key_list.end(); ++key_iter) + { + QCString utfString = (*key_iter).utf8(); + QString str = QString("%1:").arg(utfString.size() - 1); + + QCString lenString = str.utf8(); + + // Write out length of key + device.writeBlock(lenString.data(), lenString.size() - 1); + + // Write out actual key + device.writeBlock(utfString.data(), utfString.size() - 1); + + // Write out the key's data + BBase *base = m_map.find(*key_iter); + if (!base->writeToDevice (device)) + return false; + } + + written = device.writeBlock (e_str, 1); + while ((uint) written < 1) + { + if (written < 0 || result < 0) + return false; + + result = device.writeBlock (e_str, 1); + written += result; + } + + return true; +} + +// vim: set et sw=4 ts=4: diff --git a/kfile-plugins/torrent/bdict.h b/kfile-plugins/torrent/bdict.h new file mode 100644 index 00000000..34c471c6 --- /dev/null +++ b/kfile-plugins/torrent/bdict.h @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2003, 2004 Michael Pyne + * + * This software 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 software 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 library; see the file COPYING. + * If not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _BDICT_H +#define _BDICT_H + +#include +#include // QByteArray +#include "bytetape.h" +#include "bbase.h" + +// Some useful typedefs +typedef QDict BBaseHash; +typedef QDictIterator BBaseHashIterator; + +// Forward declarations +class BInt; +class BList; +class BString; + +/** + * Class to handle the BitTorrent b-encoded dictionary. It is keyed + * using const char *strings, and stores pointers to a class descended + * from BBase, such as BInt, BString, BList, or even more BDicts. + * + * @author Michael Pyne + * @see BBase, BInt, BString, BList + */ +class BDict : public BBase +{ + public: + + /** + * Construct a dictionary based on the b-encoded data contained in + * @p dict. You need to pass a value for @p start if the b-encoded + * dictionary doesn't start at the beginning of @p dict. + * + * @param dict the buffer containing the b-encoded dictionary + * @param start the position of the data within the buffer + */ + BDict (QByteArray &dict, int start = 0); + + /** + * Construct a dictionary from a ByteTape. The data and current + * position of @p tape will be shared with all objects using it. + * @p tape should already be positioned to the b-encoded dictionary. + * After construction, @p tape will point to the byte after the + * dictionary if successful. If not successful, @p tape will have + * an undefined position. + * + * @param tape the ByteTape to read from + * @see ByteTape + */ + BDict (ByteTape &tape); + + /** + * Destroys the object and frees all memory allocated to it. + */ + virtual ~BDict(); + + /** + * Returns the type of this class. + * + * @return bDict. This value is only returned by this class. + */ + virtual classID type_id() const { return bDict; } + + /** + * Returns the number of keyed values contained within this + * dictionary. + * + * @return the number of items in this dictionary + */ + virtual int count() const { return m_map.count(); } + + /** + * This function should be called to determine whether the + * dictionary was successfully created, since no exceptions + * are thrown. + * + * @return true if this is a valid (possibly empty!) dictionary, + * false otherwise + */ + virtual bool isValid() const { return m_valid; } + + /** + * This function determines whether or not a value with a + * certain key exists in the dictionary. The key is case-sensitive. + * + * @param key the key to search for a value for + * @return true, if there is a value for the @p key in the + * dictionary, false otherwise + */ + virtual bool contains (const char *key) { return m_map.find(key) != 0; } + + /** + * Returns a pointer to the BBase descendant keyed by @p key. You + * can use the type_id() method to determine the type of the + * object returned. + * + * @param key the key to search the dictionary for + * @return a pointer to the matching object, or 0 if no object + * matches the key + * @see BBase + */ + virtual BBase *find (const char *key) { return m_map.find(key); } + + /** + * Convienience function to find and return a BInt keyed by @p key. + * + * @param key the key to find a value for + * @return 0 if the key doesn't match a value, or if the value isn't + * a BInt. Otherwise, a pointer to the matching BInt is + * returned. + * @see BInt + */ + BInt* findInt (const char *key); + + /** + * Convienience function to find and return a BList keyed by @p key. + * + * @param key the key to find a value for + * @return 0 if the key doesn't match a value, or if the value isn't + * a BList. Otherwise, a pointer to the matching BList is + * returned. + * @see BList + */ + BList* findList (const char *key); + + /** + * Convienience function to find and return a BDict keyed by @p key. + * + * @param key the key to find a value for + * @return 0 if the key doesn't match a value, or if the value isn't + * a BDict. Otherwise, a pointer to the matching BDict is + * returned. + * @see BDict + */ + BDict* findDict (const char *key); + + /** + * Convienience function to find and return a BString keyed by @p key. + * + * @param key the key to find a value for + * @return 0 if the key doesn't match a value, or if the value isn't + * a BString. Otherwise, a pointer to the matching BString is + * returned. + * @see BString + */ + BString* findStr (const char *key); + + /** + * Outputs the b-encoded representation of the object to the given + * QIODevice. + * @param device the QIODevice to write to + * @return true on a successful write, false otherwise + */ + virtual bool writeToDevice (QIODevice &device); + + /** + * Returns a QDictIterator that you can use to iterate through + * the items in the dictionary. + * + * @return QDictIterator, which can be used to iterate through + * the items in the dictionary. + */ + BBaseHashIterator iterator() const + { + return BBaseHashIterator(m_map); + } + + private: + + /** + * This function handles the actual initialization of the object upon + * construction, and set the m_valid flag if successful. + * + * @param tape the ByteTape to read from + */ + void init (ByteTape &tape); + + BBaseHash m_map; /// The QDict that actually store the data + bool m_valid; /// Store initialization status +}; + +#endif /* _BDICT_H */ + +// vim: set et sw=4 ts=4: diff --git a/kfile-plugins/torrent/bint.cpp b/kfile-plugins/torrent/bint.cpp new file mode 100644 index 00000000..8f273e86 --- /dev/null +++ b/kfile-plugins/torrent/bint.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2003, 2004 Michael Pyne + * + * This software 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 software 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 library; see the file COPYING. + * If not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include +#include + +#include "bytetape.h" +#include "bint.h" + +// A bencoded int is (approximately) as follows: +// i(\d)+e +BInt::BInt (QByteArray &dict, int start) + : m_value (0), m_valid(false) +{ + ByteTape tape (dict, start); + init (tape); +} + +BInt::BInt (ByteTape &tape) + : m_value(0), m_valid(false) +{ + init(tape); +} + +void BInt::init (ByteTape &tape) +{ + if (*tape != 'i') + return; + + tape ++; // Move to start of digits + + QByteArray &dict (tape.data()); + if (dict.find('e', tape.pos()) == -1) + return; + + // Copy the part from the start to the e. The values in-between + // should be digits, perhaps preceded by a negative sign. + int length = dict.find('e', tape.pos()) - tape.pos(); + char *ptr = dict.data(); // Get start of buffer + ptr += tape.pos(); // Advance to current position in tape + + // Allocate temporary data buffer + QByteArray buffer(length + 1); + + qmemmove (buffer.data(), ptr, length); + buffer[length] = 0; // Null-terminate + + QString numberString (buffer); + bool a_isValid; // We want to make sure the string is a valid number + + m_value = numberString.toLongLong(&a_isValid); + + tape += length; // Move to 'e' + tape ++; // Move to next char + + m_valid = a_isValid; // Now we're good, if it was a number +} + +BInt::~BInt() +{ + /* Nothing yet */ +} + +bool BInt::writeToDevice (QIODevice &device) +{ + if (!m_valid) + return false; + + /* Write out i234e, and such */ + QString str = QString("i%1e"). + arg (m_value); + + Q_LONG written = 0, result = 0; + written = device.writeBlock (str.latin1(), str.length()); + while ((uint) written < str.length()) + { + if (written < 0 || result < 0) + return false; + + result = device.writeBlock(str.latin1() + written, + str.length() - written); + written += result; + } + + return true; +} + +// vim: set et ts=4 sw=4: diff --git a/kfile-plugins/torrent/bint.h b/kfile-plugins/torrent/bint.h new file mode 100644 index 00000000..1d4a4614 --- /dev/null +++ b/kfile-plugins/torrent/bint.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2003, 2004 Michael Pyne + * + * This software 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 software 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 library; see the file COPYING. + * If not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _BINT_H +#define _BINT_H + +#include +#include "bbase.h" +#include "bytetape.h" + +/** + * Class to represent a b-encoded integer. + * + * @author Michael Pyne + * @see BBase + */ +class BInt : public BBase +{ + public: + + /** + * Constructs a BInt by reading a b-encoded integer from @p dict. + * You can start reading from a position other than the beginning + * by passing that position to the constructor. + * + * @param dict the buffer to read from + * @param start the position within the buffer of the beginning + * of the b-encoded integer. + */ + BInt (QByteArray &dict, int start = 0); + + /** + * Constructs a BInt by reading a b-encoded integer from @p tape. + * + * @param tape the ByteTape to read from. It should already be + * positioned at the beginning of the b-encoded integer data. + * After construction, @p tape will point to the byte after + * the b-encoded integer on success. If construction was + * not successful, @p tape will have an undefined position. + */ + BInt (ByteTape &tape); + + /** + * Destructor for this class. No special action is taken. + */ + virtual ~BInt (); + + /** + * Returns the integer value of the data used to construct this + * object. + * + * @return this object's integer value + */ + Q_LLONG get_value () const { return m_value; } + + /** + * Returns the type of this class. + * + * @return bInt. This value is only returned by this class. + */ + virtual classID type_id() const { return bInt; } + + /** + * This function should be called to determine whether the + * integer was successfully created, since no exceptions + * are thrown. + * + * @return true if this is a valid integer, false otherwise + */ + virtual bool isValid() const { return m_valid; } + + /** + * Outputs the b-encoded representation of the object to the given + * QIODevice. + * @param device the QIODevice to write to + * @return true on a successful write, false otherwise + */ + virtual bool writeToDevice (QIODevice &device); + + private: + + /** + * Initialization function for the class, called to handle the + * actual work of reading the b-encoded data from @p tape. + * + * @param tape the ByteTape to read from + */ + void init(ByteTape &tape); + + Q_LLONG m_value; + bool m_valid; +}; + +#endif /* _BINT_H */ + +// vim: set et ts=4 sw=4: diff --git a/kfile-plugins/torrent/blist.cpp b/kfile-plugins/torrent/blist.cpp new file mode 100644 index 00000000..1aaa8b7c --- /dev/null +++ b/kfile-plugins/torrent/blist.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2003, 2004 Michael Pyne + * + * This software 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 software 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 library; see the file COPYING. + * If not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include + +#include "bytetape.h" +#include "blist.h" +#include "bdict.h" +#include "bstring.h" +#include "bint.h" + +BList::BList (ByteTape &tape) + : m_valid(false), m_array() +{ + init (tape); +} + +BList::BList (QByteArray &dict, unsigned int start) + : m_valid(false), m_array() +{ + ByteTape tape (dict, start); + + init (tape); +} + +void BList::init (ByteTape &tape) +{ + BBase *temp; + + if (*tape != 'l') + return; + + tape++; + + /* Repeat circling over the string until the list is over */ + while (*tape != 'e') + { + switch (*tape) + { + case 'd': + temp = new BDict (tape); + break; + + case 'l': /* This element is a list */ + temp = new BList (tape); + break; + + case 'i': /* This element is an int */ + temp = new BInt (tape); + break; + + default: /* Maybe a string? */ + temp = new BString (tape); + } + + if (!temp || !temp->isValid()) + return; // Invalid list element + + m_array.append (temp); + } + + m_valid = true; + + // Only way out is to detect 'e', so we need to increment tape past that. + tape++; +} + +BList::~BList() +{ + BBaseVectorIterator iter; + + for (iter = begin(); iter != end(); ++iter) + delete *iter; +} + +BBase* BList::index (unsigned int i) +{ + if (i >= count()) + return 0; + else + return m_array[i]; +} + +BList * BList::indexList (unsigned int i) +{ + BBase *base = index(i); + + if (base && base->type_id() == bList) + return dynamic_cast(base); + + return 0; +} + +BInt * BList::indexInt (unsigned int i) +{ + BBase *base = index(i); + + if (base && base->type_id() == bInt) + return dynamic_cast(base); + + return 0; +} + +BDict * BList::indexDict (unsigned int i) +{ + BBase *base = index(i); + + if (base && base->type_id() == bDict) + return dynamic_cast(base); + + return 0; +} + +BString * BList::indexStr (unsigned int i) +{ + BBase *base = index(i); + + if (base && base->type_id() == bString) + return dynamic_cast(base); + + return 0; +} + +bool BList::writeToDevice(QIODevice &device) +{ + if (!m_valid) + return false; + + const char *l_str = "l"; + const char *e_str = "e"; + Q_LONG written = 0, result = 0; + + written = device.writeBlock (l_str, 1); + while (written < 1) + { + if (written < 0 || result < 0) + return false; + + result = device.writeBlock (l_str, 1); + written += result; + } + + BBaseVectorIterator iter; + for (iter = begin(); iter != end(); ++iter) + { + if (!((*iter)->writeToDevice (device))) + return false; + } + + written = device.writeBlock (e_str, 1); + while (written < 1) + { + if (written < 0 || result < 0) + return false; + + result = device.writeBlock (e_str, 1); + written += result; + } + + return true; +} + +// vim: set et sw=4 ts=4: diff --git a/kfile-plugins/torrent/blist.h b/kfile-plugins/torrent/blist.h new file mode 100644 index 00000000..88a52bc4 --- /dev/null +++ b/kfile-plugins/torrent/blist.h @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2003, 2004 Michael Pyne + * + * This software 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 software 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 library; see the file COPYING. + * If not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _BLIST_H +#define _BLIST_H + +#include +#include +#include "bbase.h" +#include "bytetape.h" + +typedef QValueList BBaseVector; +typedef QValueList::iterator BBaseVectorIterator; + +// Predeclare the following classes +class BDict; +class BString; +class BInt; + +/** + * Class to construct a list of BBase objects from a b-encoded + * list. + * + * @author Michael Pyne + * @see BBase + */ +class BList : public BBase +{ + public: + + /** + * Construct a BList from @p dict. + * + * @param dict the buffer to read from + * @param start the position in the buffer to start + * reading from + */ + BList (QByteArray &dict, unsigned int start = 0); + + /** + * Construct a BList from @p tape. Any changes made to + * @p tape, such as its current position and data, will be + * shared with the object that called this constructor. @p tape + * should already be positioned at the position to read from. + * If construction was successful, @p tape will point to the + * byte after the list data. If construction was unsuccessful, + * the position of the tape is undefined. + * + * @param tape the ByteTape to read from. + */ + BList (ByteTape &tape); + + /** + * Destroys the list, and deletes all of the items that had + * been contained within. + */ + virtual ~BList (); + + /** + * Returns the type of this class. + * + * @return bList. This value is only returned by this class. + */ + virtual classID type_id() const { return bList; } + + /** + * Returns the number of items contained within the list. + * + * @return number of items in the list + */ + virtual unsigned int count() const { return m_array.count(); } + + /** + * This function should be called to determine whether the + * list was successfully created, since no exceptions + * are thrown. + * + * @return true if this is a valid list, false otherwise + */ + virtual bool isValid() const { return m_valid; } + + /** + * This function returns a pointer to the appropriate + * item in the list. + * + * @param i index of the item to return + * @return pointer to the appropriate BBase, or 0 if + * the index is out-of-bounds + */ + inline BBase * index (unsigned int i); + + /** + * Convience function to return a pointer to the appropriate + * item in the list, already casted. + * + * @param i index of the item to return + * @return pointer to the appropriate BBase, downcasted to + * BList. If the element is not a BList, 0 + * will be returned instead, even if it was a valid + * BBase. + */ + BList * indexList (unsigned int i); + + /** + * Convience function to return a pointer to the appropriate + * item in the list, already casted. + * + * @param i index of the item to return + * @return pointer to the appropriate BBase, downcasted to + * BInt. If the element is not a BInt, 0 + * will be returned instead, even if it was a valid + * BBase. + */ + BInt * indexInt (unsigned int i); + + /** + * Convience function to return a pointer to the appropriate + * item in the list, already casted. + * + * @param i index of the item to return + * @return pointer to the appropriate BBase, downcasted to + * BDict. If the element is not a BDict, 0 + * will be returned instead, even if it was a valid + * BBase. + */ + BDict * indexDict (unsigned int i); + + /** + * Convience function to return a pointer to the appropriate + * item in the list, already casted. + * + * @param i index of the item to return + * @return pointer to the appropriate BBase, downcasted to + * BString. If the element is not a BString, 0 + * will be returned instead, even if it was a valid + * BBase. + */ + BString * indexStr (unsigned int i); + + /** + * Returns an iterator to the first element in the list. + * There is no particular sorting associated with the list + * at this time. + * + * @return iterator pointing to the beginning of the list + * @see QValueList + */ + BBaseVectorIterator begin(void) { return m_array.begin(); } + + /** + * Returns an iterator pointing one element past the end of + * the list. Although this element belongs to the list, + * you should never dereference this iterator. Instead, treat + * it as a boundary condition to avoid. + * + * @return iterator pointing one element past the end of the list + * @see QValueList + */ + BBaseVectorIterator end(void) { return m_array.end(); } + + /** + * Outputs the b-encoded representation of the object to the given + * QIODevice. + * @param device the QIODevice to write to + * @return true on a successful write, false otherwise + */ + virtual bool writeToDevice (QIODevice &device); + + private: + + /** + * This function handles the actual initialization of the object upon + * construction, and set the m_valid flag if successful. + * + * @param tape the ByteTape to read from + */ + void init(ByteTape &tape); + + bool m_valid; + BBaseVector m_array; +}; + +#endif /* _BLIST_H */ + +// vim: set et sw=4 ts=4: diff --git a/kfile-plugins/torrent/bstring.cpp b/kfile-plugins/torrent/bstring.cpp new file mode 100644 index 00000000..b888af72 --- /dev/null +++ b/kfile-plugins/torrent/bstring.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2003, 2004 Michael Pyne + * + * This software 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 software 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 library; see the file COPYING. + * If not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include + +#include + +#include "bstring.h" +#include "bytetape.h" + +BString::BString (QByteArray &dict, int start) : + m_data(), m_valid(false) +{ + ByteTape tape (dict, start); + init (tape); +} + +BString::BString (ByteTape &tape) + : m_data(), m_valid(false) +{ + init (tape); +} + +// The reason we don't store stuff in a QString is because BitTorrent +// b-encoded strings may contain zeroes within the string, which makes +// a BString more of a buffer than a true string. +void BString::init (ByteTape &tape) +{ + QByteArray &dict(tape.data()); + + if (dict.find(':', tape.pos()) == -1) + { + kdDebug(7034) << "Can't find : for string!" << endl; + return; + } + + // Copy the part from start to :, as it will be a number + // That number is the number of characters to read + int length = dict.find(':', tape.pos()) - tape.pos(); + char *ptr = dict.data(); + + ptr += tape.pos(); + + QByteArray buffer (length + 1); + qmemmove (buffer.data(), ptr, length); + buffer[length] = 0; + + QString numberString (buffer); + bool a_isValid; + ulong len = numberString.toULong (&a_isValid); + + if (!a_isValid) + { + kdDebug(7034) << "Invalid string length!" << endl; + return; + } + + // Now that we have the length, we need to advance the tape + // past the colon + tape += length; // Move to colon + if (*tape != ':') + { + // Sanity check + kdError(7034) << "SANITY CHECK FAILED. *tape != ':'!" << endl; + return; + } + + tape++; // Move past colon + + // Time to copy the data + char *textBuffer = tape.at(tape.pos()); + if (!m_data.resize(len + 1)) + return; + + qmemmove (m_data.data(), textBuffer, len); + m_data[len] = 0; // Null terminate for convienience + + tape += len; + m_valid = true; +} + +BString::~BString () +{ +} + +bool BString::writeToDevice(QIODevice &device) +{ + if (!m_valid) + return false; + + QString str = QString("%1:"). + arg(get_len()); + + QCString utfString = str.utf8(); + + /* Don't write null terminator */ + device.writeBlock (utfString.data(), utfString.size() - 1); + + // Output the actual data + device.writeBlock (m_data.data(), m_data.size() - 1); + + // Done + return true; +} + +bool BString::setValue (const QString &str) +{ + m_data = str.utf8(); + return true; +} + +// vim: set et ts=4 sw=4: diff --git a/kfile-plugins/torrent/bstring.h b/kfile-plugins/torrent/bstring.h new file mode 100644 index 00000000..eb98fef8 --- /dev/null +++ b/kfile-plugins/torrent/bstring.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2003, 2004 Michael Pyne + * + * This software 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 software 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 library; see the file COPYING. + * If not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _BSTRING_H +#define _BSTRING_H + +#include +#include + +#include "bytetape.h" +#include "bbase.h" + +/** + * A class to handle the b-encoded strings used by BitTorrent. + * It implements BBase, and although the class is referred to + * as a string, it can hold arbitrary data, since b-encoded strings + * are stored with a length, instead of using a terminator character. + * + * @author Michael Pyne + * @see BBase + */ +class BString : public BBase +{ + public: + + /** + * Construct a BString from @p dict. + * + * @param dict the buffer to read from + * @param start the position in the buffer to start + * reading from + */ + BString (QByteArray &dict, int start = 0); + + /** + * Construct a BString from @p tape. Any changes made to + * @p tape, such as its current position and data, will be + * shared with the object that called this constructor. @p tape + * should already be positioned at the position to read from. + * If construction was successful, @p tape will point to the + * byte after the string data. If construction was unsuccessful, + * the position of the tape is undefined. + * + * @param tape the ByteTape to read from. + */ + BString (ByteTape &tape); + + /** + * Destroys the BString, and deallocates all memory that had + * been used. + */ + virtual ~BString (); + + /** + * Returns a QString representation of the data in the + * BString. It is the responsibility of the caller to ensure + * that the data is convertible to a QString. More specifically, + * the data should not contain any embedded NULLs. + * + * @return QString containing the data from this BString. + */ + QString get_string() const { return QString::fromUtf8(m_data.data()); } + + /** + * Returns the amount of data held by the string. It would be + * perhaps more appropriate to call this size(), since this is + * a buffer, not a true text string. + * + * @return the size of the string, not including the NULL + * terminator. + */ + const int get_len() const { return m_data.size() - 1; } + + /** + * Returns the type of this class. + * + * @return bString. This value is only returned by this class. + */ + virtual classID type_id() const { return bString; } + + /** + * This function should be called to determine whether the + * string was successfully created, since no exceptions + * are thrown. + * + * @return true if this is a valid string, false otherwise + */ + virtual bool isValid() const { return m_valid; } + + /** + * Outputs the b-encoded representation of the object to the given + * QIODevice. + * @param device the QIODevice to write to + * @return true on a successful write, false otherwise + */ + virtual bool writeToDevice (QIODevice &device); + + /** + * Changes the value of the string to the given QString. + * + * @param str the QString containing the new value + * @return true if the value was successfully changed, + * false otherwise. + */ + bool setValue (const QString &str); + + private: + + /** + * This function handles the actual initialization of the object upon + * construction, and set the m_valid flag if successful. + * + * @param tape the ByteTape to read from + */ + void init (ByteTape &tape); + + QByteArray m_data; + bool m_valid; +}; + +#endif /* _BSTRING_H */ + +// vim: set et ts=4 sw=4: diff --git a/kfile-plugins/torrent/bytetape.cpp b/kfile-plugins/torrent/bytetape.cpp new file mode 100644 index 00000000..f2289293 --- /dev/null +++ b/kfile-plugins/torrent/bytetape.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2003, 2004 Michael Pyne + * + * This software 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 software 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 library; see the file COPYING. + * If not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include + +#include "bytetape.h" + +ByteTape::ByteTape (QByteArray &array, int pos) + : m_array(array), m_shared(new ByteTapeShared) +{ + m_shared->pos = pos; +} + +ByteTape::ByteTape (const ByteTape &tape) + : m_array(tape.m_array), m_shared(tape.m_shared) +{ +} + +ByteTape& ByteTape::operator += (const unsigned int i) +{ + m_shared->pos += i; + if (m_array.size() <= m_shared->pos) + { + kdDebug(7034) << "Whoops. Advanced too far." << endl; + m_shared->pos = m_array.size() - 1; + } + + return *this; +} + +ByteTape& ByteTape::operator -= (const unsigned int i) +{ + if (i > m_shared->pos) + { + kdDebug(7034) << "Whoops, tried to back up too far." << endl; + m_shared->pos = 0; + } + else + m_shared->pos -= i; + + return *this; +} + +char ByteTape::operator [] (const unsigned int i) +{ + if (i < m_array.size()) + return m_array[i]; + else + { + kdWarning() << "Can't dereference tape at " << i + << ", size is " << m_array.size() << endl; + return 0; + } +} + +char &ByteTape::operator * () +{ + return m_array[m_shared->pos]; +} + +// Postfix increment +ByteTape ByteTape::operator ++ (int) +{ + // Can't use copy ctor, as we'll be copying shared data, which defeats + // the semantics of the postfix operator. + ByteTape temp(m_array, m_shared->pos); + m_shared->pos ++; + + if (m_shared->pos >= m_array.size()) + { + m_shared->pos = m_array.size() - 1; + kdDebug(7034) << "Tape already at end!" << endl; + kdDebug(7034) << "Tape size is " << m_array.size() << endl; + } + + return temp; +} + +// Prefix increment +ByteTape & ByteTape::operator ++() +{ + m_shared->pos ++; + if (m_shared->pos >= m_array.size()) + { + m_shared->pos = m_array.size() - 1; + kdDebug(7034) << "Tape already at end!" << endl; + kdDebug(7034) << "Tape size is " << m_array.size() << endl; + } + + return *this; +} + +// Postfix decrement +ByteTape ByteTape::operator -- (int) +{ + // Can't use copy ctor, as we'll be copying shared data, which defeats + // the semantics of the postfix operator. + ByteTape temp(m_array, m_shared->pos); + + if (m_shared->pos != 0) + m_shared->pos --; + else + kdDebug(7034) << "Tape already at beginning!" << endl; + + return temp; +} + +// Prefix decrement +ByteTape & ByteTape::operator -- () +{ + if (m_shared->pos != 0) + m_shared->pos --; + else + kdDebug(7034) << "Tape already at beginning!" << endl; + + return *this; +} + +bool ByteTape::setPos (unsigned int pos) +{ + if (pos >= m_array.size()) + { + kdDebug(7034) << "Can't set tape to " << pos << endl; + return false; + } + + m_shared->pos = pos; + return true; +} + +char* ByteTape::at (const unsigned int i) +{ + if (i >= m_array.size()) + { + kdDebug(7034) << "Access to tape at " << i << " out-of-range." << endl; + return 0; + } + + return m_array.data() + i; +} + +// vim: set et ts=4 sw=4: diff --git a/kfile-plugins/torrent/bytetape.h b/kfile-plugins/torrent/bytetape.h new file mode 100644 index 00000000..1d8c8db2 --- /dev/null +++ b/kfile-plugins/torrent/bytetape.h @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2003, 2004 Michael Pyne + * + * This software 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 software 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 library; see the file COPYING. + * If not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _BYTETAPE_H +#define _BYTETAPE_H + +#include + +#include + +class ByteTapeShared : public KShared +{ + public: + + unsigned int pos; +}; + +/** + * Class to simulate a seekable byte stream. Very similar to QByteArray, + * but the difference is that this class "knows" what a current position + * is. Also, the copy constructor will share the byte stream of the + * ByteTape being copied. This means that any copies made of an object of + * this class will share BOTH the data and the current position. This is + * by design. + * + * @author Michael Pyne + * @see QByteArray + */ +class ByteTape +{ + public: + + /** + * Constructs a new ByteTape object from @p array. The + * current position will be set to @p pos. + * + * @param array a data buffer to use for reading and writing. + * The tape will be fixed to the size of this buffer. + * @param pos the initial position of the tape head. It must be + * a position within the buffer contained in @p array. + */ + ByteTape (QByteArray &array, int pos = 0); + + /** + * Creates a ByteTape as a copy of @p tape. The newly created + * object will share both data and the current position with @p tape. + * This is done using reference counts. + * + * @param tape the ByteTape to copy + */ + ByteTape (const ByteTape &tape); + + /** + * Increments the current position by @p i. It is the responsibility + * of the function caller to ensure that the new position is in bounds. + * The position will be capped to remain in bounds regardless. + * + * @param i the amount to increment the current position by + * @return t reference to the object incremented + */ + ByteTape & operator += (const unsigned int i); + + /** + * Decrements the current position by @p i. It is the responsibility + * of the function caller to ensure that the new position is in bounds. + * Unlike a real Turing machine, attempting to decrement past the + * start will be capped to the beginning instead of crashing the + * computer. + * + * @param i the amount to decrement the current position by + * @return a reference to the object decremented + */ + ByteTape & operator -= (const unsigned int i); + + /** + * Increments the current position by 1. This is a postfix + * operator (i++). The current position will be capped to the end + * of the buffer. + * + * @return the object before the increment operation + */ + ByteTape operator ++ (int); + + /** + * Increments the current position by 1. This is a prefix + * operator (++i). The current position will be capped to the end + * of the buffer. + * + * @return a reference to the object incremented + */ + ByteTape & operator ++ (); + + /** + * Decrements the current position by 1. This is a postfix + * operator (i--). The current position will be capped to the start + * of the buffer. + * + * @return the object before the decrement operation + */ + ByteTape operator -- (int); + + /** + * Decrements the current position by 1. This is a prefix + * operator (--i). The current position will be capped to the start + * of the buffer. + * + * @return a reference to the object decremented + */ + ByteTape & operator -- (); + + /** + * Returns the byte within the array indexed by @p i. It is + * the responsibility of the caller to ensure that @p i is in bounds. + * Although out-of-range errors will be detected, no exceptions will + * be thrown. Since a reference is not returned, you won't be able + * to assign to the result. Example: tape[i] = 'a' will not work. + * + * The reason a reference isn't returned is because no exceptions are + * thrown by this class, and we can't return a reference to an out-of- + * bounds character. + * + * @param i the index of the byte to return + * @return the byte at the given index. 0 may be returned on error, + * but does not necessarily indicate an error. + */ + char operator [] (const unsigned int i); + + /** + * Returns the byte at the tape's current position. You can assign + * to the reference returned. + * + * @return a reference to the byte at the tape head's current position + */ + char &operator * (); + + /** + * Returns the position in memory of data at the given index, @p i. + * Unlike operator [], this function returns a pointer, so it can be + * used to access memory. + * + * @param i index of the byte to lookup. + * @return 0 if @p i is out of range, else the address of memory + * at that index + */ + char *at(const unsigned int i); + + /** + * Returns the current position of the tape head. + * + * @return the tape head's current position + */ + unsigned int pos() const { return m_shared->pos; } + + /** + * Sets the current position of the tape head to @p pos. If the + * position given is out-of-bounds, it will be capped to be within + * the array. + * + * @param pos the new position of the tape head + * @return whether the set operation was successful + */ + bool setPos(unsigned int pos); + + /** + * Returns a reference to the QByteArray used to hold all the data. + * + * @return the QByteArray used to hold the data + * @see QByteArray + */ + QByteArray &data() { return m_array; } + + private: + QByteArray &m_array; + KSharedPtr m_shared; +}; + +#endif /* _BYTETAPE_H */ + +// vim: set et ts=4 sw=4: diff --git a/kfile-plugins/torrent/kfile_torrent.cpp b/kfile-plugins/torrent/kfile_torrent.cpp new file mode 100644 index 00000000..08007e11 --- /dev/null +++ b/kfile-plugins/torrent/kfile_torrent.cpp @@ -0,0 +1,404 @@ +/* + * Copyright (c) 2003, 2004 2004 Michael Pyne + * + * This software 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 software 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 library; see the file COPYING. + * If not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include +#include +#include +#include + +#include +#include + +#include "kfile_torrent.h" +#include "bdict.h" +#include "blist.h" +#include "bint.h" +#include "bstring.h" + +typedef KGenericFactory TorrentFactory; +K_EXPORT_COMPONENT_FACTORY(kfile_torrent, TorrentFactory("kfile_torrent")) + +QStringList filesList (BList *list); +Q_ULLONG filesLength (BList *list); + +KTorrentPlugin::KTorrentPlugin (QObject *parent, const char *name, + const QStringList &args) + : KFilePlugin (parent, name, args), m_failed(true), m_dict(0) +{ + KFileMimeTypeInfo *info = addMimeTypeInfo ("application/x-bittorrent"); + if (!info) + { + kdError() << "Error creating application/x-bittorrent mime type info!\n"; + return; + } + + KFileMimeTypeInfo::GroupInfo* group = + addGroupInfo (info, "TorrentInfo", i18n("Torrent Information")); + if (!group) + { + kdError() << "Error creating TorrentInfo group!\n"; + return; + } + setAttributes (group, KFileMimeTypeInfo::Modifiable); + + KFileMimeTypeInfo::ItemInfo *item = 0; + + item = addItemInfo(group, "name", i18n("Name"), QVariant::String); + if (!item) + { + kdError() << "Error adding Name to group!\n"; + return; + } + setHint (item, KFileMimeTypeInfo::Name); + setAttributes (item, KFileMimeTypeInfo::Modifiable); + + item = addItemInfo(group, "length", i18n("Torrent Length"), QVariant::ULongLong); + if (!item) + { + kdError() << "Error adding Length to group!\n"; + return; + } + setHint (item, KFileMimeTypeInfo::Length); + setUnit (item, KFileMimeTypeInfo::Bytes); + + item = addItemInfo(group, "announce", i18n("Tracker URL"), QVariant::String); + if (!item) + { + kdError() << "Error adding Announce to group!\n"; + return; + } + + item = addItemInfo(group, "creation date", i18n("Date Created"), QVariant::DateTime); + if (!item) + { + kdError() << "Error adding DateCreated to group!\n"; + return; + } + + item = addItemInfo(group, "NumFiles", i18n("Number of Files"), QVariant::Int); + if (!item) + { + kdError() << "Error adding NumFiles to group!\n"; + return; + } + + item = addItemInfo(group, "piece length", i18n("File Piece Length"), QVariant::Int); + if (!item) + { + kdError() << "Error adding PieceLength to group!\n"; + return; + } + setUnit (item, KFileMimeTypeInfo::Bytes); + + item = addItemInfo(group, "comment", i18n("Comment"), QVariant::String); + if (!item) + { + kdError() << "Error adding Comment to group!\n"; + return; + } + setAttributes (item, KFileMimeTypeInfo::MultiLine | KFileMimeTypeInfo::Modifiable); + + m_failed = false; +} + +bool KTorrentPlugin::readInfo (KFileMetaInfo &info, unsigned int) +{ + /* Since we don't throw during the ctor, check here whether we actually + * are valid are not. If not, die. + */ + if (m_failed) + { + kdError() << "Construction of KTorrentPlugin failed for " << info.path() << endl; + kdError() << "Aborting meta-info read.\n"; + return false; + } + + QFile file (info.path()); + if (!file.open(IO_ReadOnly)) + { + kdError() << "Unable to open given file!\n"; + return false; + } + + // We need to read in the entire file to parse the dictionary structure. + QByteArray buf = file.readAll(); + file.close(); + + if (!buf) + { + kdError() << "Empty file: " << info.path() << endl; + return false; + } + + m_dict = new BDict(buf); + + if (!m_dict) + { + kdError() << "Error creating dictionary from open file: " << info.path() << endl; + return false; + } + + if (!m_dict->isValid()) + { + kdDebug(7034) << "Invalid torrent file: " << info.path() << endl; + return false; + } + + KFileMetaInfoGroup group = appendGroup(info, "TorrentInfo"); + + // The remainder of this function will consist of a lot of redundancy checks. + // If a torrent has a key, but it is of the wrong type, then it isn't a valid + // torrent, and so we should just die. + + if (m_dict->contains("announce")) + { + BString *str = m_dict->findStr ("announce"); + if (!str) + return false; + appendItem (group, "announce", QString(str->get_string())); + } + + if (m_dict->contains("creation date")) + { + BInt *the_data = m_dict->findInt ("creation date"); + QDateTime my_date; + + if (!the_data) + return false; + + unsigned int the_time = the_data->get_value(); + + /* Hopefully the_time is UTC, because that's what QDateTime does. */ + my_date.setTime_t (the_time); + appendItem (group, "creation date", my_date); + } + + // A valid torrent must have the info dict, no reason to check twice for + // it. + BDict *info_dict = m_dict->findDict("info"); + int num_files = 1; + Q_ULLONG length = 0; + + if (!info_dict) + return false; + + if (!info_dict->contains("length")) + { + /* Has more than one file. The list of files is contained in a + * list called, appropriately enough, 'files' + */ + BList *info_list = info_dict->findList("files"); + if (!info_list) + return false; + + num_files = info_list->count(); + length = filesLength (info_list); + } + else + { + /* Only one file, let's put its length */ + BInt *blength = info_dict->findInt("length"); + if (!blength) + return false; + + length = blength->get_value(); + } + + appendItem (group, "NumFiles", num_files); + appendItem (group, "length", length); + + if (info_dict->contains("name")) + { + BString *str = info_dict->findStr("name"); + if (!str) + return false; + + QString real_str (str->get_string()); + + if (num_files > 1 && !real_str.endsWith("/")) + real_str.append('/'); + + appendItem (group, "name", real_str); + } + + // piece length is required as well + BInt *piece_length = info_dict->findInt("piece length"); + if (!piece_length) + return false; + + appendItem (group, "piece length", piece_length->get_value()); + + if (m_dict->contains("comment")) + { + BString *comment = m_dict->findStr("comment"); + if (!comment) + return false; + + appendItem (group, "comment", comment->get_string()); + } + else + appendItem (group, "comment", QString()); + + return true; +} + +/* Returns a QStringList containing file names within the list. The list + * should be the one contained within the info dictionary of the torrent, + * keyed by 'files' + */ +QStringList filesList (BList *list) +{ + QStringList str_list, failList; + + for (unsigned int i = 0; i < list->count(); ++i) + { + /* Each item in this list is a dictionary, composed as follows: + * length -> BInt (size of file) + * path -> BList (list of strings) + * The list of strings is used to construct directory paths. The + * last element of the list is the file name. + */ + + BDict *list_dict = list->indexDict(i); + if (!list_dict) + return failList; + + BList *list_path = list_dict->findList("path"); + if (!list_path) + return failList; + + QString str; + BString *temp_str; + + if (list_path->count() > 0) + { + temp_str = list_path->indexStr (0); + if (!temp_str) + return failList; + + str.append (temp_str->get_string()); + } + + /* Construct QString consisting of path and file name */ + for (unsigned int j = 1; j < list_path->count(); ++j) + { + str.append (QDir::separator()); + temp_str = list_path->indexStr (j); + if (!temp_str) + return failList; + + str.append (temp_str->get_string()); + } + + str_list += str; + } + + return str_list; +} + +/* This function determines the total length of a torrent stream. + * The list provided should be the same one provided for filesList. + */ +Q_ULLONG filesLength (BList *list) +{ + Q_ULLONG length = 0; + + for (unsigned int i = 0; i < list->count(); ++i) + { + /* Each item in this list is a dictionary, composed as follows: + * length -> BInt (size of file) + * path -> BList (list of strings) + */ + + BDict *list_dict = list->indexDict(i); + if (!list_dict) + return 0; + + BInt *bfile_len = list_dict->findInt("length"); + if (!bfile_len) + return 0; + + length += bfile_len->get_value(); + } + + return length; +} + +bool KTorrentPlugin::writeInfo(const KFileMetaInfo &info) const +{ + if (m_failed || !m_dict) + return false; + + // The m_dict is ready, all we have to do is open a file, and + // let 'er go. + QStringList list = info.groups(); + QStringList::Iterator it = list.begin(); + + for (; it != list.end(); ++it) + { + QStringList list2 = info[*it].keys(); + QStringList::Iterator it2 = list2.begin(); + + for (; it2 != list2.end(); ++it2) + { + QString key = *it2; + + if (info[*it][key].isModified()) + { + // Re-enter the entry in the dictionary. + if (key == "comment") + { + BString *b_str = m_dict->findStr("comment"); + if (!b_str) + return false; + + b_str->setValue (info[*it][key].value().toString()); + } + else if (key == "name") + { + BDict *info_dict = m_dict->findDict ("info"); + if (!info_dict) + return false; + + BString *name_str = info_dict->findStr ("name"); + if (!name_str) + return false; + + QString the_name = info[*it][key].value().toString(); + + // Remove trailing slashes + the_name.replace (QRegExp("/*$"), ""); + + name_str->setValue (the_name); + } + } + } + } + + QFile output (info.path()); + + if (!output.open(IO_WriteOnly | IO_Truncate)) + return false; + + return m_dict->writeToDevice(output); +} + +#include "kfile_torrent.moc" + +// vim: set ts=4 sw=4 et: diff --git a/kfile-plugins/torrent/kfile_torrent.desktop b/kfile-plugins/torrent/kfile_torrent.desktop new file mode 100644 index 00000000..854985d4 --- /dev/null +++ b/kfile-plugins/torrent/kfile_torrent.desktop @@ -0,0 +1,58 @@ +[Desktop Entry] +Type=Service +Name=Bit Torrent Info +Name[be]=ЗвеÑткі Bit Torrent +Name[bn]=বিট টোরেনà§à¦Ÿ তথà§à¦¯ +Name[br]=Titouriñ diwar-benn Bit Torrent +Name[bs]=Bit Torrent informacije +Name[ca]=Informació de Bit Torrent +Name[cs]=Bit Torrent informace +Name[da]=Info om bittorrent +Name[de]=Bit Torrent Informationen +Name[el]=ΠληÏοφοÏίες Bit Torrent +Name[es]=Información Bit Torrent +Name[et]=Bit Torrenti info +Name[eu]=Bit-torrent informazioa +Name[fa]=اطلاعات جریان بیتی +Name[fi]=Bit Torrent +Name[fr]=Infos Bit Torrent +Name[gl]=Información de Bit Torrent +Name[he]=מידע ביטטורנט +Name[hu]=Bit Torrent-jellemzÅ‘k +Name[is]=Bit Torrent upplýsingar +Name[it]=Informazioni Bittorrent +Name[ja]=Bit Torrent 情報 +Name[ka]=Bit Torrent ინფáƒáƒ áƒ›áƒáƒªáƒ˜áƒ +Name[kk]=Bit Torrent мәліметі +Name[km]=áž–áŸážáŸŒáž˜áž¶áž“ Bit Torrent +Name[lt]=Bit Torrent Informacija +Name[mk]=Информации за Bit Torrent +Name[nds]=BitTorrent-Info +Name[ne]=बिट टोरेनà¥à¤Ÿ सूचना +Name[nl]=Bittorent-informatie +Name[nn]=Bit Torrent-info +Name[pa]=Bit Torrent ਜਾਣਕਾਰੀ +Name[pl]=Informacja o Bit Torrent +Name[pt]=Informações Bit Torrent +Name[pt_BR]=Informações do Bit Torrent +Name[ro]=InformaÅ£ii Bit Torrent +Name[ru]=Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ BitTorrent +Name[sk]=Bit Torrent informácie +Name[sl]=Podatki o BitTorrentu +Name[sr]=Bit Torrent информације +Name[sr@Latn]=Bit Torrent informacije +Name[sv]=Information om Bit Torrent +Name[ta]=பிட௠டோரà¯à®°à¯†à®£à¯à®Ÿà¯ தகவல௠+Name[tr]=Bit Torrent Bilgisi +Name[uk]=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Bit Torrent +Name[zh_CN]=BitTorrent ä¿¡æ¯ +Name[zh_HK]=Bit Torrent 資訊 +Name[zh_TW]=Bit Torrent 資訊 +ServiceTypes=KFilePlugin +X-KDE-Library=kfile_torrent +# change MimeType here! (example: inode/directory) +MimeType=application/x-bittorrent +# change PreferredGroups here! (example: FolderInfo) +PreferredGroups= +# change PreferredItems here! (example: Items;Size) +PreferredItems= diff --git a/kfile-plugins/torrent/kfile_torrent.h b/kfile-plugins/torrent/kfile_torrent.h new file mode 100644 index 00000000..3e6d3ec4 --- /dev/null +++ b/kfile-plugins/torrent/kfile_torrent.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2003, 2004 Michael Pyne + * + * This software 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 software 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 library; see the file COPYING. + * If not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _KFILE_TORRENT_H +#define _KFILE_TORRENT_H + +#include + +#include "bdict.h" + +class QStringList; + +/** + * Class to provide meta info for BitTorrent files within Konqueror. + * Handles the mime type application/x-bittorrent, files typically + * have the extension .torrent + * + * @author Michael Pyne + * @todo Handle editing the meta info as well + * @bug The comment meta info should be Multi Line, but that only + * works when the comment can also be edited. + */ +class KTorrentPlugin : public KFilePlugin +{ + Q_OBJECT + + public: + /** + * Constructs the class, and prepares for reading info on a torrent. + * + * @param parent the parent of this object + * @param name the name of this object (not user-readable) + * @param args unused by this class + */ + KTorrentPlugin (QObject *parent, const char *name, const QStringList &args); + + /** + * Destructor that closes the dictionary holding the torrent information. + */ + ~KTorrentPlugin () { delete m_dict; } + + /** + * Reads information on a torrent file given by @p info. + * + * @param info information on the file to decode + * @return true if the meta info was successfully detected and added, + * false otherwise. + */ + virtual bool readInfo (KFileMetaInfo& info, unsigned int); + + /** + * Writes information on a torrent file given by @p info. + * BitTorrent are practically nothing but meta information. + * Therefore, the entire file might be changed. + * + * @param info information on the file to encode + * @return true if the meta info was successfully updated, + * false otherwise + */ + virtual bool writeInfo (const KFileMetaInfo& info) const; + + private: + bool m_failed; + BDict *m_dict; +}; + +#endif /* _KFILE_TORRENT_H */ + +// vim: set et ts=4 sw=4: diff --git a/kget/AUTHORS b/kget/AUTHORS new file mode 100644 index 00000000..c12f6a54 --- /dev/null +++ b/kget/AUTHORS @@ -0,0 +1,4 @@ +Patrick Charbonnier +Carsten Pfeiffer +Matej Koss + diff --git a/kget/Makefile.am b/kget/Makefile.am new file mode 100644 index 00000000..433ee66d --- /dev/null +++ b/kget/Makefile.am @@ -0,0 +1,50 @@ +SUBDIRS = . pics icons sounds kget_plug_in +INCLUDES = $(all_includes) + + +bin_PROGRAMS = kget + +kget_LDFLAGS = $(all_libraries) $(KDE_RPATH) +kget_LDADD = $(LIB_KFILE) $(LIB_KDEUI) $(LIB_KDECORE) $(LIBSOCKET) +kget_SOURCES = getfilejob.cpp slaveevent.cpp slave.cpp transferlist.cpp \ + transfer.cpp settings.cpp logwindow.cpp \ + kmainwidget.cpp kfileio.cpp droptarget.cpp docking.cpp \ + dlgconnectionbase.ui dlgautomationbase.ui dlglimitsbase.ui \ + dlgadvancedbase.ui dlgdirectoriesbase.ui dlgsystembase.ui \ + dlgSystem.cpp dlgPreferences.cpp dlgLimits.cpp \ + dlgIndividual.cpp dlgDirectories.cpp dlgConnection.cpp \ + dlgAutomation.cpp dlgAdvanced.cpp \ + main.cpp dockindividual.cpp kget_iface.skel safedelete.cpp + +noinst_HEADERS = common.h \ + dlgAdvanced.h dlgAutomation.h dlgConnection.h \ + dlgDirectories.h dlgIndividual.h dlgLimits.h \ + dlgPreferences.h dlgSystem.h docking.h droptarget.h \ + kfileio.h kmainwidget.h logwindow.h settings.h \ + transfer.h transferlist.h version.h slave.h \ + slaveevent.h http_defaults.h getfilejob.h dockindividual.h + +kget_METASOURCES = AUTO + +xdg_apps_DATA = kget.desktop + +mimedir = $(kde_mimedir)/application +mime_DATA =x-kgetlist.desktop + +servicemenudir = $(kde_datadir)/konqueror/servicemenus +servicemenu_DATA = kget_download.desktop + +EXTRA_DIST = kget.desktop $(mime_DATA) + +rcdir = $(kde_datadir)/kget +rc_DATA = kgetui.rc + +events_DATA = eventsrc +eventsdir = $(kde_datadir)/kget + +KDE_ICON = AUTO + +messages: rc.cpp + $(EXTRACTRC) *.ui > rc.cpp + $(EXTRACTRC) *.rc */*.rc >> rc.cpp + $(XGETTEXT) *.cpp */*.cpp *.h -o $(podir)/kget.pot diff --git a/kget/README b/kget/README new file mode 100644 index 00000000..f121641b --- /dev/null +++ b/kget/README @@ -0,0 +1,30 @@ + KGet 0.8.0-RC-1 + ========== + +New working release of kget for kde3. + +INSTALLATION +============= +checkout the admin directory from kde-common then: +cd kget +ln -s kde-common/admin . +make -f Makefile.cvs +./configure --disable-debug --enable-mt +make +make install + +BUGS +===== + Please report all bugs to pch@freeshell.org + +TODO +===== + o Download segments + + +Happy downloading + +____________ +Patrick Charbonnier +E-mail: pch@freeshell.org + diff --git a/kget/TODO b/kget/TODO new file mode 100644 index 00000000..2fe91449 --- /dev/null +++ b/kget/TODO @@ -0,0 +1,10 @@ +Add a plug-in for konqueror +Add support for mirrors +Add bandwidth limiting +Splits download in 2 segments +Better icons +Add Help & Guide + + + + diff --git a/kget/common.h b/kget/common.h new file mode 100644 index 00000000..b436902a --- /dev/null +++ b/kget/common.h @@ -0,0 +1,41 @@ +/*************************************************************************** +* common.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* +* email : pch@freeshell.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. * + * * + ***************************************************************************/ +#ifndef _COMMON_H +#define _COMMON_H + + +#include + +#define mDebugIn mDebug << ">>>>Entering " +#define mDebugOut mDebug << ">>>>Leaving " + +#define sDebugIn sDebug << ">>>>Entering " +#define sDebugOut sDebug << ">>>>Leaving " + + +#define mDebug kdDebug(DKGET)< +#include + +#include + +#include +#include + +#include "settings.h" +#include "kmainwidget.h" +#include "dlgAdvanced.h" + + + +DlgAdvanced::DlgAdvanced(QWidget * parent) + : DlgAdvancedBase(parent) +{ + cb_partial->hide(); +} + + +void +DlgAdvanced::setData() +{ + rb_queued->setChecked(ksettings.b_addQueued); + rb_delayed->setChecked(!ksettings.b_addQueued); + cb_iconify->setEnabled(ksettings.b_showIndividual); + cb_individual->setChecked(ksettings.b_showIndividual); + cb_iconify->setChecked(ksettings.b_iconifyIndividual); + cb_advanced->setChecked(ksettings.b_advancedIndividual); + cb_remove->setChecked(ksettings.b_removeOnSuccess); + cb_getsizes->setChecked(ksettings.b_getSizes); + cb_expertmode->setChecked(ksettings.b_expertMode); + cb_partial->setChecked(KProtocolManager::markPartial()); + cb_konqiIntegration->setChecked(ksettings.b_KonquerorIntegration); + cb_ShowMain->setChecked(ksettings.b_showMain); +} + + +void DlgAdvanced::applyData() +{ + ksettings.b_addQueued = rb_queued->isChecked(); + ksettings.b_showIndividual = cb_individual->isChecked(); + ksettings.b_iconifyIndividual = cb_iconify->isChecked(); + ksettings.b_advancedIndividual = cb_advanced->isChecked(); + ksettings.b_removeOnSuccess = cb_remove->isChecked(); + ksettings.b_getSizes = cb_getsizes->isChecked(); + ksettings.b_showMain=cb_ShowMain->isChecked(); + + if (ksettings.b_expertMode != cb_expertmode->isChecked()) { + kmain->slotToggleExpertMode(); + } + + bool bIsKonquiEnable=cb_konqiIntegration->isChecked(); + + if (ksettings.b_KonquerorIntegration!=bIsKonquiEnable) + { + ksettings.b_KonquerorIntegration=!ksettings.b_KonquerorIntegration; + KConfig cfg("konquerorrc", false, false); + cfg.setGroup("HTML Settings"); + cfg.writePathEntry("DownloadManager",QString((bIsKonquiEnable)?"kget":"")); + cfg.sync(); + } +} + +void DlgAdvanced::slotChanged() +{ + emit configChanged(); +} + +#include "dlgAdvanced.moc" diff --git a/kget/dlgAdvanced.h b/kget/dlgAdvanced.h new file mode 100644 index 00000000..5af58c2a --- /dev/null +++ b/kget/dlgAdvanced.h @@ -0,0 +1,51 @@ +/*************************************************************************** +* dlgAdvanced.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ +#ifndef _DLGADVANCED_H +#define _DLGADVANCED_H + +#include "dlgadvancedbase.h" + +class DlgAdvanced : public DlgAdvancedBase +{ + +Q_OBJECT + +public: + + DlgAdvanced(QWidget * parent); + ~DlgAdvanced() {} + + void applyData(); + void setData(); + +protected slots: + void slotChanged(); + +signals: + void configChanged(); +}; + +#endif // _DLGADVANCED_H diff --git a/kget/dlgAutomation.cpp b/kget/dlgAutomation.cpp new file mode 100644 index 00000000..98ed4f3e --- /dev/null +++ b/kget/dlgAutomation.cpp @@ -0,0 +1,135 @@ +/*************************************************************************** +* dlgAutomation.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include "common.h" + +#include +#include + +#include "settings.h" +#include "kmainwidget.h" +#include "dlgAutomation.h" + + +DlgAutomation::DlgAutomation(QWidget * parent) + : DlgAutomationBase(parent) +{ +} + + +void DlgAutomation::disconnectToggled(bool flag) +{ + le_autoDisconnect->setEnabled(flag); + cb_timedDisconnect->setEnabled(flag); + if (cb_timedDisconnect->isChecked()) { + spins->setEnabled(flag); + } +} + + +void DlgAutomation::slotTypeChanged(int type) +{ + if (type == PERMANENT) { + disconnectToggled(false); + cb_autoDisconnect->setEnabled(false); + cb_autoDisconnect->setChecked(false); + cb_timedDisconnect->setChecked(false); + } else { + cb_autoDisconnect->setEnabled(true); + } +} + + +void DlgAutomation::setData() +{ + // auto save + le_autoSave->setEnabled(ksettings.b_autoSave); + cb_autoSave->setChecked(ksettings.b_autoSave); + + le_autoSave->setValue(ksettings.autoSaveInterval); + + // auto disconnect + le_autoDisconnect->setEnabled(ksettings.b_autoDisconnect); + cb_timedDisconnect->setEnabled(ksettings.b_autoDisconnect); + spins->setEnabled(ksettings.b_autoDisconnect); + cb_autoDisconnect->setChecked(ksettings.b_autoDisconnect); + le_autoDisconnect->setText(ksettings.disconnectCommand); + + cb_timedDisconnect->setChecked(ksettings.b_timedDisconnect); + spins->setEnabled(ksettings.b_timedDisconnect); + + disconnectDateTime.setDate(ksettings.disconnectDate); + disconnectDateTime.setTime(ksettings.disconnectTime); + spins->setDateTime(disconnectDateTime); + + // auto shutdown + cb_autoShutdown->setChecked(ksettings.b_autoShutdown); + + // auto paste + cb_autoPaste->setChecked(ksettings.b_autoPaste); +} + + +void DlgAutomation::applyData() +{ + if (cb_autoSave->isChecked() != ksettings.b_autoSave || (uint) le_autoSave->value() != ksettings.autoSaveInterval) { + ksettings.b_autoSave = cb_autoSave->isChecked(); + ksettings.autoSaveInterval = le_autoSave->value(); + kmain->setAutoSave(); + } + + if (cb_autoDisconnect->isChecked() != ksettings.b_autoDisconnect) { + kmain->slotToggleAutoDisconnect(); + kmain->setAutoDisconnect(); + } + + ksettings.disconnectCommand = le_autoDisconnect->text(); + ksettings.b_timedDisconnect = cb_timedDisconnect->isChecked(); + ksettings.disconnectDate = spins->dateTime().date(); + ksettings.disconnectTime = spins->dateTime().time(); + + if (cb_autoShutdown->isChecked() != ksettings.b_autoShutdown) { + kmain->slotToggleAutoShutdown(); + } + + if (cb_autoPaste->isChecked() != ksettings.b_autoPaste) { + kmain->slotToggleAutoPaste(); + } +} + +void DlgAutomation::slotChanged() +{ + emit configChanged(); +} +#include "dlgAutomation.moc" diff --git a/kget/dlgAutomation.h b/kget/dlgAutomation.h new file mode 100644 index 00000000..e9f629bc --- /dev/null +++ b/kget/dlgAutomation.h @@ -0,0 +1,61 @@ +/*************************************************************************** +* dlgAutomation.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#ifndef _DLGAUTOMATION_H +#define _DLGAUTOMATION_H + +#include +#include + +#include "dlgautomationbase.h" + +class DlgAutomation:public DlgAutomationBase +{ + +Q_OBJECT public: + + DlgAutomation(QWidget * parent); + ~DlgAutomation() {} + void applyData(); + void setData(); + +private: + + QDateTime disconnectDateTime; + +signals: + void configChanged(); + +public slots: + void slotTypeChanged(int); + +protected slots: + void disconnectToggled(bool); + void slotChanged(); +}; + +#endif // _DLGAUTOMATION_H diff --git a/kget/dlgConnection.cpp b/kget/dlgConnection.cpp new file mode 100644 index 00000000..c5ffae56 --- /dev/null +++ b/kget/dlgConnection.cpp @@ -0,0 +1,137 @@ +/*************************************************************************** +* dlgConnection.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#include "kmainwidget.h" +#include "settings.h" +#include "dlgConnection.h" + + +DlgConnection::DlgConnection(QWidget * parent) + :DlgConnectionBase(parent, "", 0) +{ + // TODO: these are not supported yet, so hide them + gb_timeout->hide(); + le_nodata->hide(); + le_noresume->hide(); +} + + +void DlgConnection::comboActivated(int Index) +{ + if (Index == 0) { + lb_linknum->setEnabled(false); + le_linknum->setEnabled(false); + cb_offlinemode->setEnabled(true); + } else { + lb_linknum->setEnabled(true); + le_linknum->setEnabled(true); + cb_offlinemode->setEnabled(false); + cb_offlinemode->setChecked(false); + } + + emit typeChanged(Index); +} + + +void DlgConnection::setData() +{ + lb_after->setEnabled(ksettings.b_reconnectOnError); + le_after->setEnabled(ksettings.b_reconnectOnError); +// lb_retries->setEnabled(ksettings.b_reconnectOnError); +// le_retries->setEnabled(ksettings.b_reconnectOnError); + cb_onerror->setChecked(ksettings.b_reconnectOnError); + + le_after->setValue(ksettings.reconnectTime); + le_retries->setValue(ksettings.reconnectRetries); + + cb_onbroken->setChecked(ksettings.b_reconnectOnBroken); + +// cb_autoresume->setChecked(KProtocolManager::autoResume()); + + le_nodata->setValue(ksettings.timeoutData); + le_noresume->setValue(ksettings.timeoutDataNoResume); + + cmb_type->setCurrentItem(ksettings.connectionType); + + if (cmb_type->currentItem() == 0) { + le_linknum->setValue(0); + lb_linknum->setEnabled(false); + le_linknum->setEnabled(false); + } else { + le_linknum->setValue(ksettings.linkNumber); + lb_linknum->setEnabled(true); + le_linknum->setEnabled(true); + } + + cb_offlinemode->setChecked(ksettings.b_offlineMode); + if (ksettings.connectionType == 0) + cb_offlinemode->setChecked(ksettings.b_offlineMode); + else { + cb_offlinemode->setEnabled(false); + cb_offlinemode->setChecked(false); + } +} + + +void DlgConnection::applyData() +{ + ksettings.b_reconnectOnError = cb_onerror->isChecked(); + ksettings.reconnectTime = le_after->value(); + ksettings.reconnectRetries = le_retries->value(); + ksettings.b_reconnectOnBroken = cb_onbroken->isChecked(); + + // KProtocolManager::setAutoResume(cb_autoresume->isChecked()); + + ksettings.timeoutData = le_nodata->value(); + ksettings.timeoutDataNoResume = le_noresume->value(); + + ksettings.connectionType = cmb_type->currentItem(); + ksettings.linkNumber = le_linknum->value(); + + if (cb_offlinemode->isChecked() != ksettings.b_offlineMode) { + kmain->slotToggleOfflineMode(); + } +} + +void DlgConnection::slotChanged() +{ + emit configChanged(); +} + +#include "dlgConnection.moc" diff --git a/kget/dlgConnection.h b/kget/dlgConnection.h new file mode 100644 index 00000000..0420b4f0 --- /dev/null +++ b/kget/dlgConnection.h @@ -0,0 +1,59 @@ +/*************************************************************************** +* dlgConnection.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#ifndef _DLGCONNECTION_H +#define _DLGCONNECTION_H + +#include + +#include "dlgconnectionbase.h" + +class DlgConnection : public DlgConnectionBase +{ + +Q_OBJECT public: + + DlgConnection(QWidget * parent); + ~DlgConnection() {} + void applyData(); + void setData(); + + int type() const + { + return cmb_type->currentItem(); + } + +signals: + void typeChanged(int type); + void configChanged(); + +protected slots: + void comboActivated(int Index); + void slotChanged(); +}; + +#endif // _DLGCONNECTION_H diff --git a/kget/dlgDirectories.cpp b/kget/dlgDirectories.cpp new file mode 100644 index 00000000..6c4c42e3 --- /dev/null +++ b/kget/dlgDirectories.cpp @@ -0,0 +1,203 @@ +/*************************************************************************** +* dlgDirectories.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "settings.h" +#include "dlgDirectories.h" +#include + +DlgDirectories::DlgDirectories(QWidget * parent) + : DlgDirectoriesBase(parent) +{ + connect( le_ext, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotDirectoryChanged( ) ) ); + connect( le_dir, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotDirectoryChanged( ) ) ); + + le_dir->setMode( KFile::Directory ); + lv_entries->setSortColumn( -1 ); + + slotDirectoryChanged(); +} + +void DlgDirectories::slotDirectoryChanged( ) +{ + pb_add->setEnabled(!le_ext->text().isEmpty() &&!le_dir->url().isEmpty() ); +} + +void DlgDirectories::selectEntry(QListViewItem * item) +{ + if (item) { + le_ext->setText(item->text(0)); + le_dir->setURL(item->text(1)); + + } else { + le_ext->clear(); + le_dir->clear(); + } + updateUpDown(); +} + + +void DlgDirectories::updateUpDown() +{ + QListViewItem *item = lv_entries->selectedItem(); + + pb_up->setEnabled( item && item->itemAbove() ); + pb_down->setEnabled( item && item->itemBelow() ); +} + +void DlgDirectories::addEntry() +{ + QString ext = le_ext->text(); + QString dir = le_dir->url(); + + if (ext.contains(",") || dir.contains(",") || ext.isEmpty() || dir.isEmpty()) { + KMessageBox::error(this, i18n("Each row consists of exactly one\nextension type and one folder."), i18n("Error")); + return; + } + + QDir f(dir); + + if (!f.exists()) { + KMessageBox::error(this, i18n("Folder does not exist:\n%1").arg(dir), i18n("Error")); + return; + } + + new QListViewItem(lv_entries, ext, dir); + updateUpDown(); + + emit configChanged(); +} + + +void DlgDirectories::deleteEntry() +{ + QListViewItem *item = lv_entries->selectedItem(); + delete item; + updateUpDown(); + emit configChanged(); +} + + +void DlgDirectories::changeEntry() +{ + QListViewItem *old_item = lv_entries->selectedItem(); + + if (old_item) { + QString ext = le_ext->text(); + QString dir = le_dir->url(); + + if (ext.contains(",") || dir.contains(",") || ext.isEmpty() || dir.isEmpty()) { + KMessageBox::error(this, i18n("Each row consists of exactly one\nextension type and one folder."), i18n("Error")); + return; + } + + QDir f(dir); + + if (!f.exists()) { + KMessageBox::error(this, i18n("Folder does not exist:\n%1").arg(dir), i18n("Error")); + return; + } + + new QListViewItem(lv_entries, old_item, ext, dir); + delete old_item; + emit configChanged(); + } +} + + +void DlgDirectories::downEntry() +{ + QListViewItem *item = lv_entries->selectedItem(); + + if ( !item ) + return; + + item->moveItem( item->itemBelow() ); + + updateUpDown(); + emit configChanged(); +} + + +void DlgDirectories::upEntry() +{ + QListViewItem *item = lv_entries->selectedItem(); + + if ( !item || !item->itemAbove() ) + return; + + item->moveItem( item->itemAbove()->itemAbove() ); + + updateUpDown(); + emit configChanged(); +} + + +void DlgDirectories::setData() +{ + DirList::Iterator it; + + if (ksettings.defaultDirList.count() > 0) { + // we need to insert items in the reverse order + // because "new QListViewItem" puts itself at the beginning + for (it = ksettings.defaultDirList.fromLast(); it != ksettings.defaultDirList.begin(); it--) { + new QListViewItem(lv_entries, (*it).extRegexp, (*it).defaultDir); + } + new QListViewItem(lv_entries, (*it).extRegexp, (*it).defaultDir); + } +} + + +void DlgDirectories::applyData() +{ + ksettings.defaultDirList.clear(); + QListViewItemIterator it(lv_entries); + + for (; it.current(); ++it) { + QListViewItem *item = it.current(); + + DirItem ditem; + + ditem.extRegexp = item->text(0); + ditem.defaultDir = item->text(1); + ksettings.defaultDirList.append(ditem); + } +} + +#include "dlgDirectories.moc" diff --git a/kget/dlgDirectories.h b/kget/dlgDirectories.h new file mode 100644 index 00000000..deb403e3 --- /dev/null +++ b/kget/dlgDirectories.h @@ -0,0 +1,62 @@ +/*************************************************************************** +* dlgDirectories.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#ifndef _DLGDIRECTORIES_H +#define _DLGDIRECTORIES_H + +#include "dlgdirectoriesbase.h" + +class DlgDirectories : public DlgDirectoriesBase +{ + +Q_OBJECT + +public: + + DlgDirectories(QWidget * parent); + ~DlgDirectories() {} + void applyData(); + void setData(); + +signals: + void configChanged(); + +protected slots: + void selectEntry(QListViewItem * item); + void addEntry(); + void deleteEntry(); + void changeEntry(); + + void upEntry(); + void downEntry(); + void slotDirectoryChanged( ); +protected: + void updateUpDown(); + +}; + +#endif // _DLGDIRECTORIES_H diff --git a/kget/dlgIndividual.cpp b/kget/dlgIndividual.cpp new file mode 100644 index 00000000..72fc6c7d --- /dev/null +++ b/kget/dlgIndividual.cpp @@ -0,0 +1,379 @@ +/*************************************************************************** +* dlgIndividual.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#include + +#include "dockindividual.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "common.h" + +#include "transfer.h" + +#include "settings.h" +#include "dlgIndividual.h" + + +DlgIndividual::DlgIndividual(Transfer * _item) + : QWidget(0, "dialog", WDestructiveClose) +{ + item = _item; + + //create dock + m_pDockIndividual =new DockIndividual(this); + + + // Actions + + m_paDock = new KToggleAction(i18n("&Dock"),"tool_dock.png", 0, this, SLOT(slotToggleDock()), this, "dockIndividual"); + + + QVBoxLayout *topLayout = new QVBoxLayout( this, KDialog::marginHint(),KDialog::spacingHint() ); + topLayout->addStrut( 360 ); // makes dlg at least that wide + + QGridLayout *grid = new QGridLayout( 2, 3 ); + topLayout->addLayout(grid); + grid->addColSpacing(1, KDialog::spacingHint()); + + grid->addWidget(new QLabel(i18n("Source:"), this), 0, 0); + + sourceLabel = new KSqueezedTextLabel(this); + grid->addWidget(sourceLabel, 0, 2); + sourceLabel->setText(i18n("Source Label")); + grid->addWidget(new QLabel(i18n("Destination:"), this), 1, 0); + + destLabel = new KSqueezedTextLabel(this); + grid->addWidget(destLabel, 1, 2); + destLabel->setText(i18n("Source Label")); + + m_pProgressBar = new KProgress(this); + topLayout->addWidget( m_pProgressBar ); + + // processed info + QHBoxLayout *hBox = new QHBoxLayout(); + topLayout->addLayout(hBox); + + sizeLabel = new QLabel(this); + hBox->addWidget(sizeLabel); + resumeLabel = new QLabel(this); + hBox->addWidget(resumeLabel); + + speedLabel = new QLabel(this); + speedLabel->setText(i18n("0 B/s")); + topLayout->addWidget(speedLabel); + + // setup toolbar + KToolBar *toolBar = new KToolBar(this); + toolBar->setIconText(KToolBar::IconOnly); + toolBar->setBarPos(KToolBar::Bottom); + toolBar->setMovingEnabled(false); + toolBar->setFlat(true); + + topLayout->addWidget( toolBar ); + + // insert toolbar actions + item->m_paResume->plug(toolBar); + item->m_paPause->plug(toolBar); + item->m_paDelete->plug(toolBar); + + toolBar->insertLineSeparator(); + + item->m_paQueue->plug(toolBar); + item->m_paTimer->plug(toolBar); + item->m_paDelay->plug(toolBar); + + toolBar->insertLineSeparator(); + m_paDock->plug(toolBar); + + + + QCheckBox * keepOpen = new QCheckBox( i18n("&Keep this window open after the operation is complete."), this); + connect( keepOpen, SIGNAL( toggled(bool) ), SLOT( slotKeepOpenToggled(bool) ) ); + topLayout->addWidget(keepOpen); + + QFrame *line3 = new QFrame( this ); + line3->setFrameShape( QFrame::HLine ); + line3->setFrameShadow( QFrame::Sunken ); + topLayout->addWidget( line3 ); + + hBox = new QHBoxLayout(); + topLayout->addLayout(hBox); + + openFile = new KPushButton( i18n("Open &File"), this ); + connect( openFile, SIGNAL( clicked() ), SLOT( slotOpenFile() ) ); + hBox->addWidget( openFile ); + openFile->setEnabled(false); + + openLocation = new KPushButton( i18n("Open &Destination"), this ); + connect( openLocation, SIGNAL( clicked() ), SLOT( slotOpenLocation() ) ); + hBox->addWidget( openLocation ); + + hBox->addStretch(1); + + pbAdvanced = new KPushButton( i18n("Advanced"), this ); + + connect(pbAdvanced, SIGNAL(clicked()), SLOT(slotToggleAdvanced())); + + hBox->addWidget( pbAdvanced ); + + + // setup tab dialog + panelAdvanced = new QTabWidget(this); + + // if the time was already set somewhere in the future, keep it + // otherwise set it to the current time + QDateTime dt; + + if (item->getStartTime() < QDateTime::currentDateTime() && item->getMode() != Transfer::MD_SCHEDULED) + { + dt = QDateTime::currentDateTime(); + } + else + { + dt = item->getStartTime(); + } + + spins = new KDateTimeWidget(dt, this, "spins"); + + panelAdvanced->addTab(spins, i18n("Timer")); + panelAdvanced->hide(); + + connect(spins, SIGNAL(valueChanged(const QDateTime &)), item, SLOT(slotStartTime(const QDateTime &))); + + // adding item log + ml_log = new QTextEdit(panelAdvanced); + ml_log->setTextFormat(LogText); + ml_log->setReadOnly(true); + // ml_log->setFixedSize(sizeHint()); + ml_log->setVScrollBarMode(QScrollView::Auto); + ml_log->setWordWrap(QTextEdit::NoWrap); + + // ml_log->setSizePolicy(policy); + + panelAdvanced->addTab(ml_log, i18n("Log")); + // panelAdvanced->setFixedSize(sizeHint()); + + + + topLayout->addWidget(panelAdvanced); + advanced = ksettings.b_advancedIndividual; + slotToggleAdvanced(); + + resize( minimumSizeHint() ); + setMaximumHeight( height() ); + + //bool keepOpenChecked = false; + //bool noCaptionYet = true; + setCaption(i18n("Progress Dialog")); + + bKeepDlgOpen=false; +} + + +void DlgIndividual::setTotalSize(KIO::filesize_t bytes) +{ + m_iTotalSize = bytes; +} + + +void DlgIndividual::setPercent(unsigned long percent) +{ + m_pProgressBar->setValue(percent); + m_pDockIndividual->setValue(percent); + setCaption(i18n("%1% of %2 - %3").arg(percent).arg(KIO::convertSize(m_iTotalSize)).arg(m_location.fileName())); +} + + +void DlgIndividual::setProcessedSize(KIO::filesize_t bytes) +{ + sizeLabel->setText(i18n("%1 of %2").arg(KIO::convertSize(bytes)).arg(KIO::convertSize(m_iTotalSize))); +} + + +void DlgIndividual::setSpeed(QString speed) +{ + speedLabel->setText(speed); + m_pDockIndividual->setTip(speed); +} + + +void DlgIndividual::setCopying(const KURL & from, const KURL & to) +{ + m_location=to; + setCaption(m_location.fileName()); + + sourceLabel->setText(from.prettyURL()); + destLabel->setText(to.prettyURL()); +} + + +void DlgIndividual::setCanResume(bool resume) +{ + if (resume) + resumeLabel->setText(i18n("Resumed")); + else + resumeLabel->setText(i18n("Not resumed")); +} + +//void DlgIndividual::slotToggleAdvanced(bool advanced) +void DlgIndividual::slotToggleAdvanced() +{ +#ifdef _DEBUG + sDebugIn<show(); + else + { + panelAdvanced->hide(); + adjustSize(); + } + advanced = !advanced; + +#ifdef _DEBUG + sDebugOut<isChecked()) + { + m_pDockIndividual->show(); + hide(); + } + else + m_pDockIndividual->hide(); + +#ifdef _DEBUG + sDebugOut<setText(_msg); +} + +void DlgIndividual::appendLog(const QString & _msg) +{ + ml_log->append(_msg); +} + + +void DlgIndividual::slotKeepOpenToggled(bool bToggled) +{ +#ifdef _DEBUG + sDebugIn <<"bToggled= "<getStatus()==Transfer::ST_FINISHED) + { + hide(); + m_pDockIndividual->hide(); + } + +#ifdef _DEBUG + sDebugOut<invokeBrowser( location.url() ); + +#ifdef _DEBUG + sDebugOut<setEnabled(true); + + if (!bKeepDlgOpen) + { + hide(); + m_pDockIndividual->hide(); + } + +#ifdef _DEBUG + sDebugOut< + +#include + +#include + +class Transfer; + +class QLabel; +class QRadioButton; +class QTabWidget; +class QTextEdit; +class QCheckBox; +class KPushButton; +class KProgress; +class KToggleAction; +class KDateTimeWidget; + +class DockIndividual; + +class DlgIndividual:public QWidget +{ +Q_OBJECT +public: + DlgIndividual(Transfer * _item); + ~DlgIndividual() + {} + void setLog(const QString & _msg); + void appendLog(const QString & _msg); + void enableOpenFile(); + +public slots: + void setTotalSize(KIO::filesize_t bytes); + + void setProcessedSize(KIO::filesize_t bytes); + + void setSpeed(QString speed); + void setPercent(unsigned long percent); + + void setCopying(const KURL & src, const KURL & dest); + void setCanResume(bool); + void slotKeepOpenToggled(bool); + void slotOpenLocation(); + void slotOpenFile(); + + bool keepDialogOpen() const { return bKeepDlgOpen; } + +protected slots: + void slotToggleAdvanced(); + void slotToggleDock(); + +protected: + QLabel *sourceLabel; + QLabel *destLabel; + QLabel *speedLabel; + QLabel *sizeLabel; + QLabel *resumeLabel; + QTextEdit *ml_log; + + KProgress *m_pProgressBar; + DockIndividual * m_pDockIndividual; + + KPushButton * openFile; + KPushButton * openLocation; + KPushButton * pbAdvanced ; + + QTabWidget * panelAdvanced; + KToggleAction * m_paDock; + + QDateTime qdt; + KDateTimeWidget *spins; + + Transfer *item; + + KURL m_location; + + bool bKeepDlgOpen; + + KIO::filesize_t m_iTotalSize; + + bool advanced; +} + +; + +#endif // __dlgprogress_h__ diff --git a/kget/dlgLimits.cpp b/kget/dlgLimits.cpp new file mode 100644 index 00000000..e31326c7 --- /dev/null +++ b/kget/dlgLimits.cpp @@ -0,0 +1,72 @@ +/*************************************************************************** +* dlgLimits.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#include +#include + +#include +#include +#include + +#include "settings.h" +#include "kmainwidget.h" +#include "dlgLimits.h" + + +DlgLimits::DlgLimits(QWidget * parent) + : DlgLimitsBase(parent) +{ + // TODO: these are not supported yet, so hide them + lb_maxband->hide(); + le_maxband->hide(); + lb_minband->hide(); + le_minband->hide(); +} + + +void DlgLimits::setData() +{ + le_maxnum->setValue(ksettings.maxSimultaneousConnections); + le_minband->setValue(ksettings.minimumBandwidth); + le_maxband->setValue(ksettings.maximumBandwidth); +} + + +void DlgLimits::applyData() +{ + ksettings.maxSimultaneousConnections = le_maxnum->value(); + ksettings.minimumBandwidth = le_minband->value(); + ksettings.maximumBandwidth = le_maxband->value(); + kmain->checkQueue(); +} + +void DlgLimits::slotChanged() +{ + emit configChanged(); +} + +#include "dlgLimits.moc" diff --git a/kget/dlgLimits.h b/kget/dlgLimits.h new file mode 100644 index 00000000..a020f221 --- /dev/null +++ b/kget/dlgLimits.h @@ -0,0 +1,51 @@ +/*************************************************************************** +* dlgLimits.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#ifndef _DLGLIMITS_H +#define _DLGLIMITS_H + +#include "dlglimitsbase.h" + +class DlgLimits : public DlgLimitsBase +{ + +Q_OBJECT public: + + DlgLimits(QWidget * parent); + ~DlgLimits() {} + void applyData(); + void setData(); + +signals: + void configChanged(); + +protected slots: + void slotChanged(); + +}; + +#endif // _DLGLIMITS_H diff --git a/kget/dlgPreferences.cpp b/kget/dlgPreferences.cpp new file mode 100644 index 00000000..f35b8add --- /dev/null +++ b/kget/dlgPreferences.cpp @@ -0,0 +1,164 @@ +/*************************************************************************** +* dlgPreferences.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#include + +#include +#include +#include +#include + +#include "dlgConnection.h" +#include "dlgAutomation.h" +#include "dlgLimits.h" +#include "dlgAdvanced.h" +#include "dlgDirectories.h" +#include "dlgSystem.h" + +#include "settings.h" + +#ifdef index +#undef index +#endif + +#include "kmainwidget.h" +#include "dlgPreferences.h" + + +DlgPreferences::DlgPreferences(QWidget * parent): + KDialogBase(Tabbed, i18n("Configure"), Ok|Apply|Help|Cancel, Ok, parent, "DlgPreferences", true) +{ + // add pages + QFrame *page = addPage(i18n("Connection")); + QVBoxLayout *topLayout = new QVBoxLayout(page, 0, spacingHint()); + + conDlg = new DlgConnection(page); + topLayout->addWidget(conDlg); + + page = addPage(i18n("Automation")); + topLayout = new QVBoxLayout(page, 0, spacingHint()); + autDlg = new DlgAutomation(page); + topLayout->addWidget(autDlg); + topLayout->addStretch(); + + page = addPage(i18n("Limits")); + topLayout = new QVBoxLayout(page, 0, spacingHint()); + limDlg = new DlgLimits(page); + topLayout->addWidget(limDlg); + topLayout->addStretch(); + + page = addPage(i18n("Advanced")); + topLayout = new QVBoxLayout(page, 0, spacingHint()); + advDlg = new DlgAdvanced(page); + topLayout->addWidget(advDlg); + topLayout->addStretch(); + + // page = addPage(i18n("Search")); + // topLayout = new QVBoxLayout(page, 0, spacingHint()); + // seaDlg = new DlgSearch(page); + // topLayout->addWidget(seaDlg); + + page = addPage(i18n("Folders")); + topLayout = new QVBoxLayout(page, 0, spacingHint()); + dirDlg = new DlgDirectories(page); + topLayout->addWidget(dirDlg); + topLayout->addStretch(); + + page = addPage(i18n("System")); + topLayout = new QVBoxLayout(page, 0, spacingHint()); + sysDlg = new DlgSystem(page); + topLayout->addWidget(sysDlg); + topLayout->addStretch(); + + // type of connection influences autoDisconnect & timedDisconnect features + connect(conDlg, SIGNAL(typeChanged(int)), autDlg, SLOT(slotTypeChanged(int))); + + loadAllData(); + + connect( conDlg, SIGNAL( configChanged() ), this, SLOT( slotChanged() ) ); + connect( autDlg, SIGNAL( configChanged() ), this, SLOT( slotChanged() ) ); + connect( limDlg, SIGNAL( configChanged() ), this, SLOT( slotChanged() ) ); + connect( advDlg, SIGNAL( configChanged() ), this, SLOT( slotChanged() ) ); + connect( dirDlg, SIGNAL( configChanged() ), this, SLOT( slotChanged() ) ); + connect( sysDlg, SIGNAL( configChanged() ), this, SLOT( slotChanged() ) ); +} + + +void DlgPreferences::slotChanged() +{ + changed = true; + enableButton( Apply, true ); +} + +void DlgPreferences::applySettings() +{ + conDlg->applyData(); + autDlg->applyData(); + limDlg->applyData(); + advDlg->applyData(); + // seaDlg->applyData(); + dirDlg->applyData(); + sysDlg->applyData(); + + ksettings.save(); + changed = false; + enableButton( Apply, false ); +} + +void DlgPreferences::slotOk() +{ + if ( changed ) + applySettings(); + accept(); +} + +void DlgPreferences::slotCancel() +{ + if ( changed ) + loadAllData(); + reject(); +} + +void DlgPreferences::slotApply() +{ + applySettings(); +} + +void DlgPreferences::loadAllData() +{ + conDlg->setData(); + autDlg->setData(); + limDlg->setData(); + advDlg->setData(); + // seaDlg->setData(); + dirDlg->setData(); + sysDlg->setData(); + changed = false; + enableButton( Apply, false ); +} + +#include "dlgPreferences.moc" diff --git a/kget/dlgPreferences.h b/kget/dlgPreferences.h new file mode 100644 index 00000000..8e2e1307 --- /dev/null +++ b/kget/dlgPreferences.h @@ -0,0 +1,77 @@ +/*************************************************************************** +* dlgPreferences.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + +#ifndef _DLGPREFERENCES_H +#define _DLGPREFERENCES_H + +#include +#include + +#include + +class DlgConnection; +class DlgAutomation; +class DlgLimits; +class DlgAdvanced; +class DlgSearch; +class DlgDirectories; +class DlgSystem; + + +class DlgPreferences:public KDialogBase +{ + +Q_OBJECT public: + + DlgPreferences(QWidget * parent); + ~DlgPreferences() + {} + +private: + + DlgConnection * conDlg; + DlgAutomation *autDlg; + DlgLimits *limDlg; + DlgAdvanced *advDlg; + + // DlgSearch *seaDlg; + DlgDirectories *dirDlg; + DlgSystem *sysDlg; + + bool changed; + + void loadAllData(); + +protected slots: + void applySettings(); + void slotChanged(); + virtual void slotOk(); + virtual void slotCancel(); + virtual void slotApply(); + +}; + +#endif // _DLGPREFERENCES_H diff --git a/kget/dlgSystem.cpp b/kget/dlgSystem.cpp new file mode 100644 index 00000000..d908724a --- /dev/null +++ b/kget/dlgSystem.cpp @@ -0,0 +1,77 @@ +/*************************************************************************** +* dlgSystem.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#include +#include + +#ifdef index +#undef index +#endif + +#include + +#include "settings.h" +#include "kmainwidget.h" +#include "dlgSystem.h" + +#include +#include + + +DlgSystem::DlgSystem(QWidget * parent) + : DlgSystemBase(parent) +{ + bg_window->hide(); + textLabel4->hide(); +} + + +void DlgSystem::setData() +{ + cb_useAnimation->setChecked(ksettings.b_useAnimation); + + le_font->setFont(ksettings.listViewFont); +} + + +void DlgSystem::applyData() +{ + if (cb_useAnimation->isChecked() != ksettings.b_useAnimation) + { + kmain->slotToggleAnimation(); + } + + ksettings.listViewFont = le_font->font(); + kmain->setListFont(); +} + +void DlgSystem::slotChanged() +{ + emit configChanged(); +} + +#include "dlgSystem.moc" diff --git a/kget/dlgSystem.h b/kget/dlgSystem.h new file mode 100644 index 00000000..677481b3 --- /dev/null +++ b/kget/dlgSystem.h @@ -0,0 +1,51 @@ +/*************************************************************************** +* dlgSystem.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + + + +#ifndef _DLGSYSTEM_H +#define _DLGSYSTEM_H + +#include "dlgsystembase.h" + +class DlgSystem : public DlgSystemBase +{ +Q_OBJECT public: + DlgSystem(QWidget * parent); + ~DlgSystem() {} + + void applyData(); + void setData(); + +signals: + void configChanged(); + +protected slots: + void slotChanged(); +}; + +#endif // _DLGSYSTEM_H diff --git a/kget/dlgadvancedbase.ui b/kget/dlgadvancedbase.ui new file mode 100644 index 00000000..dd9b5c59 --- /dev/null +++ b/kget/dlgadvancedbase.ui @@ -0,0 +1,274 @@ + +DlgAdvancedBase + + + DlgAdvancedBase + + + + 0 + 0 + 368 + 285 + + + + + unnamed + + + 0 + + + + gb_advanced + + + Advanced Options + + + + unnamed + + + + lb_adding + + + Add new transfers as: + + + + + spacer2 + + + Horizontal + + + Preferred + + + + 40 + 20 + + + + + + cb_iconify + + + Iconified + + + + + cb_advanced + + + Advanced individual windows + + + + + cb_partial + + + Mark partial downloads + + + + + cb_remove + + + Remove files from a list after success + + + + + cb_getsizes + + + Get file sizes + + + + + cb_expertmode + + + Expert mode (do not prompt for Cancel or Delete) + + + + + cb_konqiIntegration + + + Use KGet as Download Manager for Konqueror + + + + + cb_ShowMain + + + Show main window at startup + + + + + cb_individual + + + Show individual windows + + + + + bg_adding + + + NoFrame + + + Sunken + + + + + + false + + + + unnamed + + + 0 + + + + rb_queued + + + Queued + + + + + rb_delayed + + + Delayed + + + + + + + spacer1 + + + Horizontal + + + Expanding + + + + 35 + 20 + + + + + + + + + + cb_ShowMain + toggled(bool) + DlgAdvancedBase + slotChanged() + + + cb_advanced + toggled(bool) + DlgAdvancedBase + slotChanged() + + + cb_expertmode + toggled(bool) + DlgAdvancedBase + slotChanged() + + + cb_getsizes + toggled(bool) + DlgAdvancedBase + slotChanged() + + + cb_iconify + toggled(bool) + DlgAdvancedBase + slotChanged() + + + cb_individual + toggled(bool) + DlgAdvancedBase + slotChanged() + + + cb_konqiIntegration + toggled(bool) + DlgAdvancedBase + slotChanged() + + + cb_partial + toggled(bool) + DlgAdvancedBase + slotChanged() + + + cb_remove + toggled(bool) + DlgAdvancedBase + slotChanged() + + + rb_delayed + toggled(bool) + DlgAdvancedBase + slotChanged() + + + rb_queued + toggled(bool) + DlgAdvancedBase + slotChanged() + + + cb_individual + toggled(bool) + cb_iconify + setEnabled(bool) + + + + kdialog.h + + + slotChanged() + + + + diff --git a/kget/dlgautomationbase.ui b/kget/dlgautomationbase.ui new file mode 100644 index 00000000..071a089b --- /dev/null +++ b/kget/dlgautomationbase.ui @@ -0,0 +1,256 @@ + +DlgAutomationBase + + + DlgAutomationBase + + + + 0 + 0 + 401 + 217 + + + + + unnamed + + + 0 + + + + gb_automation + + + Automation Options + + + + unnamed + + + + le_autoSave + + + min + + + 3600 + + + 1 + + + + + cb_autoDisconnect + + + Auto disconnect after completing downloads + + + + + cb_autoSave + + + Autosave file list every: + + + + + cb_timedDisconnect + + + Timed disconnect + + + + + lb_autoDisconnect + + + Disconnect command: + + + + + le_autoDisconnect + + + + + spins + + + + + cb_autoPaste + + + Auto paste from clipboard + + + + + cb_autoShutdown + + + Auto shutdown after completing downloads + + + + + spacer18 + + + Horizontal + + + Preferred + + + + 40 + 20 + + + + + + spacer16 + + + Horizontal + + + Preferred + + + + 50 + 20 + + + + + + spacer17 + + + Horizontal + + + Preferred + + + + 50 + 20 + + + + + + + + + + + + cb_autoDisconnect + toggled(bool) + lb_autoDisconnect + setEnabled(bool) + + + cb_autoDisconnect + toggled(bool) + le_autoDisconnect + setEnabled(bool) + + + cb_timedDisconnect + toggled(bool) + spins + setEnabled(bool) + + + cb_autoSave + toggled(bool) + le_autoSave + setEnabled(bool) + + + cb_autoSave + toggled(bool) + DlgAutomationBase + slotChanged() + + + le_autoSave + valueChanged(int) + DlgAutomationBase + slotChanged() + + + cb_autoDisconnect + toggled(bool) + DlgAutomationBase + slotChanged() + + + le_autoDisconnect + textChanged(const QString&) + DlgAutomationBase + slotChanged() + + + cb_timedDisconnect + toggled(bool) + DlgAutomationBase + slotChanged() + + + spins + valueChanged(const QDateTime&) + DlgAutomationBase + slotChanged() + + + cb_autoShutdown + toggled(bool) + DlgAutomationBase + slotChanged() + + + cb_autoPaste + toggled(bool) + DlgAutomationBase + slotChanged() + + + cb_autoDisconnect + toggled(bool) + DlgAutomationBase + disconnectToggled(bool) + + + + kdialog.h + + + slotChanged() + disconnectToggled(bool b) + + + + + kdatetimewidget.h + kdatewidget.h + ktimewidget.h + + diff --git a/kget/dlgconnectionbase.ui b/kget/dlgconnectionbase.ui new file mode 100644 index 00000000..fa9bc377 --- /dev/null +++ b/kget/dlgconnectionbase.ui @@ -0,0 +1,401 @@ + +DlgConnectionBase + + + DlgConnectionBase + + + + 0 + 0 + 319 + 355 + + + + + unnamed + + + 0 + + + + gb_reconnect + + + Reconnect Options + + + + unnamed + + + + cb_onerror + + + On login or timeout error + + + + + lb_after + + + Reconnect after: + + + + + lb_retries + + + Number of retries: + + + + + le_after + + + min + + + 3600 + + + 1 + + + + + le_retries + + + 1 + + + + + cb_onbroken + + + On broken connection + + + + + spacer2 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + spacer3 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + gb_timeout + + + Timeout Options + + + + unnamed + + + + spacer3_2 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + lb_nodata + + + If no data arrives in: + + + + + lb_cannot + + + If server cannot resume: + + + + + spacer4 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + le_noresume + + + min + + + 3600 + + + 1 + + + + + lb_or + + + or + + + + + le_nodata + + + min + + + 3600 + + + 1 + + + + + + + gb_type + + + Connection Type + + + + unnamed + + + + + Permanent + + + + + Ethernet + + + + + PLIP + + + + + SLIP + + + + + PPP + + + + + ISDN + + + + cmb_type + + + + + cb_offlinemode + + + Offline mode + + + + + spacer5 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + lb_linknum + + + Link number: + + + + + le_linknum + + + + 1 + 0 + 0 + 0 + + + + + + + + + + cb_onerror + toggled(bool) + lb_after + setEnabled(bool) + + + cb_onerror + toggled(bool) + le_after + setEnabled(bool) + + + cb_onerror + toggled(bool) + DlgConnectionBase + slotChanged() + + + le_after + valueChanged(int) + DlgConnectionBase + slotChanged() + + + le_retries + valueChanged(int) + DlgConnectionBase + slotChanged() + + + cb_onbroken + toggled(bool) + DlgConnectionBase + slotChanged() + + + le_nodata + valueChanged(int) + DlgConnectionBase + slotChanged() + + + le_noresume + valueChanged(int) + DlgConnectionBase + slotChanged() + + + cmb_type + activated(int) + DlgConnectionBase + slotChanged() + + + le_linknum + valueChanged(int) + DlgConnectionBase + slotChanged() + + + cb_offlinemode + toggled(bool) + DlgConnectionBase + slotChanged() + + + cmb_type + activated(int) + DlgConnectionBase + comboActivated(int) + + + + kdialog.h + + + slotChanged() + comboActivated(int) + + + + diff --git a/kget/dlgdirectoriesbase.ui b/kget/dlgdirectoriesbase.ui new file mode 100644 index 00000000..ccad9d15 --- /dev/null +++ b/kget/dlgdirectoriesbase.ui @@ -0,0 +1,263 @@ + +DlgDirectoriesBase + + + DlgDirectoriesBase + + + + 0 + 0 + 437 + 275 + + + + + unnamed + + + 0 + + + + layout1 + + + + unnamed + + + + pb_down + + + false + + + + + + "down" + + + + + spacer5 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + pb_up + + + false + + + + + + "up" + + + + + + Extension + + + false + + + true + + + + + Default Folder + + + false + + + true + + + + lv_entries + + + true + + + + + + + layout12 + + + + unnamed + + + + pb_add + + + true + + + + 1 + 0 + 5 + 0 + + + + Add + + + + + pb_delete + + + + 1 + 0 + 5 + 0 + + + + Remove + + + + + pb_change + + + + 1 + 0 + 5 + 0 + + + + Change + + + + + + + le_ext + + + + 0 + 0 + 0 + 0 + + + + + + lb_ext + + + Extension (* for all files): + + + + + le_dir + + + + + lb_dir + + + Default folder: + + + + + + + + + pb_down + clicked() + DlgDirectoriesBase + downEntry() + + + pb_up + clicked() + DlgDirectoriesBase + upEntry() + + + lv_entries + selectionChanged(QListViewItem*) + DlgDirectoriesBase + selectEntry(QListViewItem*) + + + pb_add + clicked() + DlgDirectoriesBase + addEntry() + + + pb_delete + clicked() + DlgDirectoriesBase + deleteEntry() + + + pb_change + clicked() + DlgDirectoriesBase + changeEntry() + + + + kdialog.h + kiconloader.h + + + selectEntry(QListViewItem *) + upEntry() + downEntry() + addEntry() + deleteEntry() + changeEntry() + +BarIconSet + + + + klineedit.h + kurlrequester.h + + diff --git a/kget/dlglimitsbase.ui b/kget/dlglimitsbase.ui new file mode 100644 index 00000000..faf2309f --- /dev/null +++ b/kget/dlglimitsbase.ui @@ -0,0 +1,189 @@ + +DlgLimitsBase + + + DlgLimitsBase + + + + 0 + 0 + 568 + 141 + + + + + unnamed + + + 0 + + + + gb_limits + + + Limits Options + + + + unnamed + + + + lbl_maxnum + + + Maximum open connections: + + + + + lb_minband + + + Minimum network bandwidth: + + + + + lb_maxband + + + Maximum network bandwidth: + + + + + le_maxnum + + + 1 + + + + + le_minband + + + bytes/sec + + + 100000 + + + 100 + + + 100 + + + + + le_maxband + + + bytes/sec + + + 100000 + + + 100 + + + 100 + + + + + spacer19 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + spacer20 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + spacer21 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + + + + + le_maxband + valueChanged(int) + DlgLimitsBase + slotChanged() + + + le_maxnum + valueChanged(int) + DlgLimitsBase + slotChanged() + + + le_minband + valueChanged(int) + DlgLimitsBase + slotChanged() + + + + kdialog.h + + + slotChanged() + + + + + knuminput.h + knuminput.h + knuminput.h + + diff --git a/kget/dlgsystembase.ui b/kget/dlgsystembase.ui new file mode 100644 index 00000000..beee27eb --- /dev/null +++ b/kget/dlgsystembase.ui @@ -0,0 +1,169 @@ + +DlgSystemBase + + + DlgSystemBase + + + + 0 + 0 + 389 + 137 + + + + + unnamed + + + 0 + + + + cb_useAnimation + + + Use animation + + + + + textLabel4 + + + false + + + Window style: + + + + + textLabel5 + + + Font: + + + + + le_font + + + + + bg_window + + + false + + + NoFrame + + + + + + + unnamed + + + 0 + + + + rb_normal + + + Normal + + + + + rb_docked + + + Docked + + + + + rb_droptarget + + + Drop target + + + + + + + spacer8 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + spacer9 + + + Vertical + + + Expanding + + + + 40 + 10 + + + + + + + + + + cb_useAnimation + toggled(bool) + DlgSystemBase + slotChanged() + + + bg_window + toggled(bool) + DlgSystemBase + slotChanged() + + + le_font + fontSelected(const QFont&) + DlgSystemBase + slotChanged() + + + + kdialog.h + + + slotChanged() + + + + + kfontrequester.h + + diff --git a/kget/dockindividual.cpp b/kget/dockindividual.cpp new file mode 100644 index 00000000..1ecf61fa --- /dev/null +++ b/kget/dockindividual.cpp @@ -0,0 +1,99 @@ +/*************************************************************************** +* dockindividual.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : +* email : pch@freeshell.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. + * + ***************************************************************************/ + +#include + +#include + +#include "dockindividual.h" +DockIndividual::DockIndividual(QWidget *parent, const char *name ) : KSystemTray(parent,name) +{ + nPic=0; + setPixmap( loadIcon("bar0") ); +} + +DockIndividual::~DockIndividual() +{ + QToolTip::remove(this); +} + + +void DockIndividual::setValue(int value){ +#ifdef _DEBUG + //sDebugIn<<" value ="<=96) + tmpPic=6; + + if (tmpPic!=nPic) + { + nPic=tmpPic; + QString str = "bar" + QString::number( nPic ); + setPixmap( loadIcon( str ) ); + } + +#ifdef _DEBUG + //sDebugOut<removeItemAt (3); +} diff --git a/kget/dockindividual.h b/kget/dockindividual.h new file mode 100644 index 00000000..39203272 --- /dev/null +++ b/kget/dockindividual.h @@ -0,0 +1,48 @@ +/*************************************************************************** +* dockindividual.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#ifndef DOCKINDIVIDUAL_H +#define DOCKINDIVIDUAL_H + +#include +#include "common.h" + +class KPopupMenu; + +class DockIndividual : public KSystemTray { + Q_OBJECT +public: + DockIndividual(QWidget *parent=0, const char *name=0); + ~DockIndividual(); + int nPic; + void setTip(const QString &); + void setValue(int value); + /** No descriptions */ + virtual void contextMenuAboutToShow ( KPopupMenu* menu ); +}; + +#endif diff --git a/kget/docking.cpp b/kget/docking.cpp new file mode 100644 index 00000000..cd3309c3 --- /dev/null +++ b/kget/docking.cpp @@ -0,0 +1,131 @@ +/*************************************************************************** +* docking.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include "kmainwidget.h" +#include "settings.h" +#include "docking.h" + + +DockWidget::DockWidget(KMainWidget * _parent):KSystemTray(_parent) +{ + parent = _parent; + + setPixmap( loadIcon( "kget_dock" )); + + // popup menu for right mouse button + KPopupMenu *popupMenu = contextMenu(); + parent->action("paste_transfer")->plug(popupMenu); + parent->action("drop_target")->plug(popupMenu); + parent->action("konqueror_integration")->plug(popupMenu); + popupMenu->insertSeparator(); + parent->m_paPreferences->plug(popupMenu); + + // Enable dropping + setAcceptDrops(true); + + dtip = new DynamicTip( this ); + dtip->setStatus( kapp->aboutData()->shortDescription() ); + +} + + +DockWidget::~DockWidget() +{ + delete dtip; + dtip = 0; +} + + +void DockWidget::dragEnterEvent(QDragEnterEvent * event) +{ + event->accept(KURLDrag::canDecode(event) + || QTextDrag::canDecode(event)); +} + + +void DockWidget::dropEvent(QDropEvent * event) +{ + KURL::List list; + QString str; + + if (KURLDrag::decode(event, list)) { + parent->addTransfers(list); + } else if (QTextDrag::decode(event, str)) { + parent->addTransfer(str); + } +} + + +void DockWidget::mousePressEvent(QMouseEvent * e) +{ + if (e->button() == MidButton) { + parent->slotPasteTransfer(); + } else { + KSystemTray::mousePressEvent(e); + } +} + + +void DockWidget::updateToolTip( const QString& _status ) +{ + dtip->setStatus( _status ); +} + + +void DockWidget::changeIcon( const QString& icon ) +{ + setPixmap( loadIcon( icon )); +} + + +DynamicTip::DynamicTip( QWidget * parent ) + : QToolTip( parent ) +{ + // no explicit initialization needed +} + + +void DynamicTip::setStatus( const QString & _status ) +{ + status = _status; +} + +void DynamicTip::maybeTip( const QPoint & _pos ) +{ + QRect r( parentWidget()->rect() ); + tip( r, status ); +} + +#include "docking.moc" diff --git a/kget/docking.h b/kget/docking.h new file mode 100644 index 00000000..df4c75ec --- /dev/null +++ b/kget/docking.h @@ -0,0 +1,81 @@ +/*************************************************************************** +* docking.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + + +#ifndef _DOCKING_H_ +#define _DOCKING_H_ + +#include + +#include + +#include + +class KPopupMenu; +class KMainWidget; + +class DynamicTip : public QToolTip +{ + public: + DynamicTip( QWidget * parent ); + virtual ~DynamicTip() {}//TODO workaround for qt-bug, can be removed after 4.0 + void setStatus( const QString & _status ); + + protected: + void maybeTip( const QPoint & ); + + private: + QString status; +}; + +class DockWidget:public KSystemTray +{ + +Q_OBJECT public: + DockWidget(KMainWidget * parent); + ~DockWidget(); + /** No descriptions */ + void updateToolTip( const QString& ); + void changeIcon( const QString& ); + + + +private slots: + void mousePressEvent(QMouseEvent * e); + +protected: + // drag and drop + void dragEnterEvent(QDragEnterEvent *); + void dropEvent(QDropEvent *); + +private: + KMainWidget *parent; + DynamicTip * dtip; + +}; + +#endif diff --git a/kget/droptarget.cpp b/kget/droptarget.cpp new file mode 100644 index 00000000..97bb01e2 --- /dev/null +++ b/kget/droptarget.cpp @@ -0,0 +1,231 @@ +/*************************************************************************** +* droptarget.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kmainwidget.h" +#include +#ifdef Q_WS_X11 +#include +#include +#include +#include +#undef Bool +#undef Status +#endif +#include "settings.h" +#include "droptarget.h" + +DropTarget::DropTarget(KMainWindow * mainWin):QWidget() +{ + int x = ksettings.dropPosition.x(); + int y = ksettings.dropPosition.y(); + + QRect desk = KGlobalSettings::desktopGeometry(this); + QPixmap bgnd = UserIcon( "target" ); + + if (x != -1 && + x >= desk.left() && y >= desk.top() && + (x + bgnd.width()) <= desk.right() && + (y + bgnd.height()) <= desk.bottom() ) + { + move(ksettings.dropPosition); + resize(bgnd.width(), bgnd.height()); + KWin::setState(winId(), ksettings.dropState); + } + else + { + setGeometry(desk.x()+200, desk.y()+200, bgnd.width(), bgnd.height()); + KWin::setState(winId(), NET::SkipTaskbar | NET::StaysOnTop); + } + + b_sticky = ksettings.dropState & NET::Sticky; + + // setup pixmaps + + if (!bgnd.mask()) + kdError(5001) << "Drop target pixmap has no mask!\n"; + else + mask = *bgnd.mask(); + + setBackgroundPixmap( bgnd ); + + // popup menu for right mouse button + popupMenu = new KPopupMenu(); + popupMenu->insertTitle(kapp->caption()); + popupMenu->setCheckable(true); + + pop_Max = popupMenu->insertItem(i18n("Maximize"), this, SLOT(toggleMinimizeRestore())); + pop_Min = popupMenu->insertItem(i18n("Minimize"), this, SLOT(toggleMinimizeRestore())); + + pop_sticky = popupMenu->insertItem(i18n("Sticky"), this, SLOT(toggleSticky())); + popupMenu->insertSeparator(); + mainWin->action("drop_target")->plug(popupMenu); + popupMenu->insertSeparator(); + + popupMenu->setItemChecked(pop_sticky, b_sticky); + kmain->m_paPreferences->plug(popupMenu); + popupMenu->insertSeparator(); + kmain->m_paQuit->plug(popupMenu); + + isdragging = false; + + // Enable dropping + setAcceptDrops(true); +} + + +DropTarget::~DropTarget() +{ + delete popupMenu; +} + + +void +DropTarget::mousePressEvent(QMouseEvent * e) +{ + if (e->button() == LeftButton) + { + // toggleMinimizeRestore (); +// oldX = 0; +// oldY = 0; + isdragging = true; + dx = QCursor::pos().x() - pos().x(); + dy = QCursor::pos().y() - pos().y(); + } + else if (e->button() == RightButton) + { + popupMenu->setItemEnabled(pop_Min, kmain->isVisible()); + popupMenu->setItemEnabled(pop_Max, kmain->isHidden()); + + popupMenu->popup(QCursor::pos()); + } + else if (e->button() == MidButton) + { + kmain->slotPasteTransfer(); + } +} + + +void DropTarget::resizeEvent(QResizeEvent *) +{ +#ifdef Q_WS_X11 + XShapeCombineMask(x11Display(), winId(), ShapeBounding, 0, 0, mask.handle(), ShapeSet); +#endif +} + + +void DropTarget::dragEnterEvent(QDragEnterEvent * event) +{ + event->accept(KURLDrag::canDecode(event) + || QTextDrag::canDecode(event)); +} + + +void DropTarget::dropEvent(QDropEvent * event) +{ + KURL::List list; + QString str; + + if (KURLDrag::decode(event, list)) + { + kmain->addTransfers(list); + } + else if (QTextDrag::decode(event, str)) + { + kmain->addTransfer(str); + } +} + + +void DropTarget::toggleSticky() +{ + b_sticky = !b_sticky; + popupMenu->setItemChecked(pop_sticky, b_sticky); + updateStickyState(); +} + +void DropTarget::updateStickyState() +{ + if (b_sticky) + { + KWin::setState(winId(), NET::SkipTaskbar | NET::StaysOnTop | NET::Sticky); + } + else + { + KWin::clearState(winId(), NET::Sticky); + } +} + +void DropTarget::toggleMinimizeRestore() +{ + if (kmain->isVisible()) + kmain->hide(); + else + kmain->show(); +} + +/** No descriptions */ +void DropTarget::mouseMoveEvent(QMouseEvent * e) +{ +/* + if (oldX == 0) + { + oldX = e->x(); + oldY = e->y(); + return; + } ++*/ + if (isdragging) + move( QCursor::pos().x() - dx, QCursor::pos().y() - dy ); + +// move(x() + (e->x() - oldX), y() + (e->y() - oldY)); // <<-- +} + +void DropTarget::mouseReleaseEvent(QMouseEvent *) +{ + isdragging = false; +} + +/** No descriptions */ +void DropTarget::mouseDoubleClickEvent(QMouseEvent * e) +{ + if (e->button() == LeftButton) + toggleMinimizeRestore(); +} + +#include "droptarget.moc" diff --git a/kget/droptarget.h b/kget/droptarget.h new file mode 100644 index 00000000..debce208 --- /dev/null +++ b/kget/droptarget.h @@ -0,0 +1,87 @@ +/*************************************************************************** +* droptarget.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + +#ifndef _DROPTARGET_H +#define _DROPTARGET_H + +#include +#include +#include + +class KPopupMenu; +class KMainWidget; +class KMainWindow; + +class DropTarget:public QWidget +{ +Q_OBJECT + +public: + DropTarget(KMainWindow *); + ~DropTarget(); + + void updateStickyState(); + +protected: + virtual void resizeEvent(QResizeEvent *); + + // drag and drop + virtual void dragEnterEvent(QDragEnterEvent *); + /** No descriptions */ + virtual void mouseDoubleClickEvent(QMouseEvent * e); + virtual void dropEvent(QDropEvent *); + /** No descriptions */ + virtual void mouseMoveEvent(QMouseEvent *); + virtual void mousePressEvent(QMouseEvent * e); + virtual void mouseReleaseEvent(QMouseEvent *); + +private slots: + void toggleSticky(); + void toggleMinimizeRestore(); + +private: + KPopupMenu * popupMenu; + KMainWidget *parent; + + bool b_sticky; + + int pop_sticky; + int pop_Max; + int pop_Min; + + int size[4]; + + QBitmap mask; +public: // Public attributes + /** */ +// int oldX; +// int oldY; + int dx; + int dy; + bool isdragging; +}; + +#endif // _DROPTARGET_H diff --git a/kget/eventsrc b/kget/eventsrc new file mode 100644 index 00000000..98c9ab38 --- /dev/null +++ b/kget/eventsrc @@ -0,0 +1,360 @@ +[!Global!] +IconName=kget +Comment=KGet +Comment[bn]=কে-গেট +Comment[ne]=केडीई गेट +Comment[pa]=ਕੇ-ਗੈੱਟ +Comment[sv]=Kget + +[added] +Name=TransferAdded +Name[bg]=Ðов файл за изтеглÑне +Name[ca]=Transferència afegida +Name[cs]=PÅ™idán pÅ™enos +Name[el]=ΠÏοστέθηκε μεταφοÏά +Name[es]=TransferenciaAñadida +Name[et]=Ãœlekanne lisatud +Name[eu]=DeskargaGehituta +Name[fa]=انتقال اÙزوده +Name[fi]=SiirtoLisätty +Name[fr]=Transfert ajouté +Name[gl]=TransferenciaEngadida +Name[he]=העברה הוספה +Name[hu]=ÃtvittHozzáadva +Name[is]=Færslu bætt við +Name[it]=Aggiunto trasferimento +Name[ka]=ჩáƒáƒ›áƒáƒ¥áƒáƒ©áƒ•áƒ დáƒáƒ”მáƒáƒ¢áƒ +Name[kk]=Берілім қоÑылды +Name[km]=បាន​បន្ážáŸ‚ម​ការផ្ទáŸážš +Name[nds]=ÖverdregenToföögt +Name[ne]=सà¥à¤¥à¤¾à¤¨à¤¾à¤¨à¥à¤¤à¤°à¤£ थपिà¤à¤•à¥‹ +Name[nl]=OverdrachtToegevoegd +Name[pl]=Dodany transfer +Name[pt_BR]=TransferênciaAdicionada +Name[ru]=ÐовоеЗадание +Name[sk]=Pridaný transfer +Name[sl]=PrenosDodan +Name[sr]=ÐŸÑ€ÐµÐ½Ð¾Ñ Ð´Ð¾Ð´Ð°Ñ‚ +Name[sr@Latn]=Prenos dodat +Name[sv]=Överföring tillagd +Name[tr]=Ä°ndirme Eklendi +Name[uk]=Додано перенеÑÐµÐ½Ð½Ñ +Name[zh_CN]=添加了任务 +Name[zh_HK]=傳輸已新增 +Name[zh_TW]=新增傳輸 +Comment=A new download has been added +Comment[be]=Ðовы файл Ð´Ð»Ñ ÑцÑÐ³Ð²Ð°Ð½Ð½Ñ Ð´Ð°Ð´Ð°Ð½Ñ‹ Ñž чаргу +Comment[bg]=Добавен е нов файл за изтеглÑне +Comment[bn]=à¦à¦•à¦Ÿà¦¿ নতà§à¦¨ ডাউনলোড যোগ করা হয়েছে +Comment[bs]=Dodan je novi download +Comment[ca]=S'ha afegit una descàrrega nova +Comment[cs]=Byl pÅ™idán nový pÅ™enos +Comment[da]=En ny download er blevet tilføjet +Comment[de]=Ein neuer Transfer wurde hinzugefügt +Comment[el]=ΠÏοστέθηκε μια νέα λήψη αÏχείου +Comment[es]=Se ha añadido una nueva descarga +Comment[et]=Lisati uus allalaadimine +Comment[eu]=Deskarga berri bat gehitu da +Comment[fa]=بارگیری جدیدی اضاÙÙ‡ شده است +Comment[fi]=Uusi tiedoston haku lisätty +Comment[fr]=Un nouveau téléchargement a été ajouté +Comment[gl]=Engadiuse unha nova descarga +Comment[he]=הורדה חדשה הוספה +Comment[hu]=Új letöltés lett megadva +Comment[is]=Nýju niðurhali hefur verið bætt við +Comment[it]=È stato aggiunto un nuovo scaricamento +Comment[ja]=æ–°ã—ã„ダウンロードãŒè¿½åŠ ã•ã‚Œã¾ã—㟠+Comment[ka]=áƒáƒ®áƒáƒšáƒ˜ ჩáƒáƒ›áƒáƒ¥áƒáƒ©áƒ•áƒ დáƒáƒ”მáƒáƒ¢áƒ +Comment[kk]=Жаңа жүктеп алу тапÑырмаÑÑ‹ қоÑылды +Comment[km]=បាន​បន្ážáŸ‚ម​ការ​ទាញយកážáŸ’មី​មួយ +Comment[lt]=PridÄ—tas naujas atsisiuntimo įraÅ¡as +Comment[nb]=En ny nedlasting er lagt til +Comment[nds]=En niege Överdregen wöör toföögt +Comment[ne]=à¤à¤‰à¤Ÿà¤¾ नयाठडाउलोड थपिà¤à¤•à¥‹ छ +Comment[nl]=Er is een nieuwe download toegevoegd +Comment[nn]=Ei ny nedlasting er lagd til +Comment[pl]=Nowe zlecenie pobrania pliku zostaÅ‚o dodane +Comment[pt]=Foi adicionada uma nova transferência +Comment[pt_BR]=Um novo download foi adicionado +Comment[ru]=Добавлено новое задание загрузки +Comment[sk]=Pridané nové sÅ¥ahovanie +Comment[sl]=Dodan je bil nov prenos +Comment[sr]=Ðово преузимање је додато +Comment[sr@Latn]=Novo preuzimanje je dodato +Comment[sv]=En ny nerladdning har lagts till +Comment[tr]=Yeni dosya eklendi +Comment[uk]=Додано нове Ð·Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ +Comment[uz]=Yangi yozib olish qoÊ»shildi +Comment[uz@cyrillic]=Янги ёзиб олиш қўшилди +Comment[zh_CN]=添加了新下载 +Comment[zh_HK]=已新增新的下載 +Comment[zh_TW]=已增加新的下載 +default_sound=KGet_Added.ogg +default_presentation=17 + +[started] +Name=DownloadStarted +Name[bg]=Ðачало на изтеглÑне +Name[bn]=ডাউনলোড আরমà§à¦­ +Name[br]=Kroget eo an enkargañ +Name[ca]=S'ha iniciat la descàrrega +Name[cs]=Stahování zahájeno +Name[el]=Εκκίνηση λήψης αÏχείου +Name[es]=DescargaComenzada +Name[et]=Allalaadimine alustatud +Name[eu]=DeskargaHasita +Name[fa]=آغاز بارگیری +Name[fi]=HakuAlkoi +Name[fr]=Téléchargement démarré +Name[gl]=DescargaComezada +Name[he]=הורדההתחילה +Name[hu]=LetöltésKezdÅ‘dött +Name[is]=Niðurhal sett í gang +Name[it]=Scaricamento avviato +Name[kk]=Жүктеп алу баÑталды +Name[km]=បាន​ចាប់ផ្ដើម​ការ​ទាញយក +Name[nb]=NedlastingStartet +Name[nds]=DaalladenHettStart +Name[ne]=डाउनलोड सà¥à¤°à¥ गरियो +Name[nl]=DownloadGestart +Name[nn]=NedlastingStarta +Name[pa]=ਡਾਊਨਲੋਡ ਸ਼à©à¨°à©‚ +Name[pl]=Pobieranie rozpoczÄ™te +Name[pt_BR]=DownloadIniciado +Name[ru]=ЗапуÑкЗагрузки +Name[sk]=Spustené sÅ¥ahovanie +Name[sl]=PrenosPriÄetek +Name[sr]=Преузимање покренуто +Name[sr@Latn]=Preuzimanje pokrenuto +Name[sv]=Nerladdning startad +Name[tr]=Ä°ndirme BaÅŸladı +Name[uk]=Почато Ð·Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ +Name[zh_CN]=开始下载 +Name[zh_HK]=下載已開始 +Name[zh_TW]=開始下載 +Comment=Downloading started +Comment[be]=СцÑгванне пачалоÑÑ +Comment[bg]=ИзтеглÑнето започна +Comment[bn]=ডাউনলোড আরমà§à¦­ করল +Comment[br]=Kroget eo an enkargañ +Comment[bs]=Pokrenut download +Comment[ca]=S'ha iniciat la descàrrega +Comment[cs]=Bylo zahájeno stahování +Comment[da]=Download startet +Comment[de]=Herunterladen gestartet +Comment[el]=Η λήψη των αÏχείων ξεκίνησε +Comment[es]=Una descarga comenzó +Comment[et]=Allalaadimine alustatud +Comment[eu]=Deskarga hasi da +Comment[fa]=بارگیری آغاز شد +Comment[fi]=Tiedoston hakeminen alkoi +Comment[fr]=Téléchargement démarré +Comment[gl]=Comezou a descarga +Comment[he]=ההורדה התחילה +Comment[hu]=Letöltés kezdÅ‘dött +Comment[is]=Byrjað á niðurhali +Comment[it]=Scaricamento avviato +Comment[ja]=ダウンロード開始 +Comment[ka]=ჩáƒáƒ›áƒáƒ¥áƒáƒ©áƒ•áƒ დáƒáƒ˜áƒ¬áƒ§áƒ +Comment[kk]=Жүктеп алу тапÑырмаÑÑ‹ баÑталды +Comment[km]=បាន​ចាប់ផ្ដើម​ការ​ទាញយក +Comment[lt]=Atsisiuntimas pradÄ—tas +Comment[nb]=Nedlasting startet +Comment[nds]=Daalladen hett start +Comment[ne]=डाउनलोडिङ सà¥à¤°à¥ भयो +Comment[nl]=Downloaden is gestart +Comment[nn]=Nedlasting starta +Comment[pl]=Pobieranie pliku zostaÅ‚o rozpoczÄ™te +Comment[pt]=Transferência iniciada +Comment[pt_BR]=Download iniciado +Comment[ru]=Загрузка выполнÑетÑÑ +Comment[sk]=Spustené sÅ¥ahovanie +Comment[sl]=PrenaÅ¡anje se je priÄelo +Comment[sr]=Покренуто је преузимање +Comment[sr@Latn]=Pokrenuto je preuzimanje +Comment[sv]=Nerladdning startad +Comment[tr]=Dosya indirme baÅŸladı +Comment[uk]=Ð—Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‡Ð°Ñ‚Ð¾ +Comment[uz]=Yozib olish boshlandi +Comment[uz@cyrillic]=Ðзиб олиш бошланди +Comment[zh_CN]=下载已开始 +Comment[zh_HK]=下載已開始 +Comment[zh_TW]=開始下載 +default_sound=KGet_Started.ogg +default_presentation=16 + +[finished] +Name=DownloadFinished +Name[bg]=Край на изтеглÑне +Name[bn]=ডাউনলোড শেষ +Name[br]=Echu eo an enkargañ +Name[ca]=Ha acabat la descàrrega +Name[cs]=Stahování ukonÄeno +Name[el]=Η λήψη ολοκληÏώθηκε +Name[es]=DescargaAcabada +Name[et]=Allalaadimine lõpetatud +Name[eu]=DeskargaAmaituta +Name[fa]=پایان بارگیری +Name[fi]=HakuValmistui +Name[fr]=Téléchargement terminé +Name[gl]=DescargaRematada +Name[he]=הורדההסתיימה +Name[hu]=LetöltésBefejezve +Name[is]=Niðurhali lokið +Name[it]=Scaricamento completato +Name[ka]=ჩáƒáƒ›áƒáƒ¥áƒáƒ©áƒ•áƒ დáƒáƒ¡áƒ áƒ£áƒšáƒ“რ+Name[kk]=Жүктеп алу аÑқталды +Name[km]=បាន​បញ្ចប់​ការ​ទាញយក +Name[nb]=NedlastingFerdig +Name[nds]=DaaladenBeendt +Name[ne]=डाउनलोड समापà¥à¤¤ भयो +Name[nl]=DownloadVoltooid +Name[nn]=Nedlasting ferdig +Name[pa]=ਡਾਊਨਲੋਡ ਮà©à¨•à©°à¨®à¨² ਹੋਇਆ +Name[pl]=Pobieranie zakoÅ„czone +Name[pt_BR]=DownloadConcluído +Name[ru]=ЗагрузкаЗавершена +Name[sk]=SÅ¥ahovanie ukonÄené +Name[sl]=PrenosKonec +Name[sr]=Преузимање завршено +Name[sr@Latn]=Preuzimanje zavrÅ¡eno +Name[sv]=Nerladdning klar +Name[tr]=Ä°ndirme Bitti +Name[uk]=Ð—Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¾ +Name[zh_CN]=ä¸‹è½½å®Œæˆ +Name[zh_HK]=ä¸‹è¼‰å·²å®Œæˆ +Name[zh_TW]=ä¸‹è¼‰å®Œæˆ +Comment=Downloading finished +Comment[be]=СцÑгванне ÑкончылаÑÑ +Comment[bg]=ИзтеглÑнето завърши +Comment[bn]=ডাউনলোড শেষ করল +Comment[br]=Echu eo an enkargañ +Comment[bs]=ZavrÅ¡en download +Comment[ca]=Ha acabat la descàrrega +Comment[cs]=Stahování bylo dokonÄeno +Comment[da]=Download afsluttet +Comment[de]=Herunterladen abgeschlossen +Comment[el]=Η λήψη των αÏχείων ολοκληÏώθηκε +Comment[es]=Una descarga acabó +Comment[et]=Allalaadimine lõpetatud +Comment[eu]=Deskarga amaitu da +Comment[fa]=بارگیری پایان یاÙت +Comment[fi]=Tiedoston haku valmistui +Comment[fr]=Téléchargement terminé +Comment[gl]=Rematou a descarga +Comment[he]=ההורדה הסתיימה +Comment[hu]=Egy letöltés befejezÅ‘dött +Comment[is]=Niðurhali lokið +Comment[it]=Scaricamento completato +Comment[ja]=ダウンロード完了 +Comment[ka]=ჩáƒáƒ›áƒáƒ¥áƒáƒ©áƒ•áƒ დáƒáƒ¡áƒ áƒ£áƒšáƒ“რ+Comment[kk]=Жүктеп алу тапÑырмаÑÑ‹ аÑқталды +Comment[km]=បាន​បញ្ចប់​ការទាញ​យក +Comment[lt]=Atsisiuntimas baigtas +Comment[nb]=Nedlasting ferdig +Comment[nds]=Daalladen beendt +Comment[ne]=डाउनलोडिङ समापà¥à¤¤ भयो +Comment[nl]=Downloaden is voltooid +Comment[nn]=Nedlasting ferdig +Comment[pl]=Pobieranie pliku zostaÅ‚o zakoÅ„czone +Comment[pt]=Terminou uma transferência +Comment[pt_BR]=Download concluído +Comment[ru]=Загрузка завершена +Comment[sk]=SÅ¥ahovanie ukonÄené +Comment[sl]=PrenaÅ¡anje se je zakljuÄilo +Comment[sr]=Завршено је преузимање +Comment[sr@Latn]=ZavrÅ¡eno je preuzimanje +Comment[sv]=Nerladdning klar +Comment[tr]=Dosya indirme tamamlandı +Comment[uk]=Ð—Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¾ +Comment[uz]=Yozib olish tugadi +Comment[uz@cyrillic]=Ðзиб олиш тугади +Comment[zh_CN]=ä¸‹è½½å·²å®Œæˆ +Comment[zh_HK]=ä¸‹è¼‰å·²å®Œæˆ +Comment[zh_TW]=ä¸‹è¼‰å·²å®Œæˆ +default_sound=KGet_Finished.ogg +default_presentation=16 + +[finishedall] +Name=AddDownloadsFinished +Name[bg]=Ð’Ñички файлове Ñа изтеглени +Name[ca]=Han acabat totes les descàrregues +Name[cs]=VÅ¡echna stahování dokonÄena +Name[el]=ΟλοκληÏώθηκε η Ï€Ïοσθήκη λήψεων +Name[es]=AñadirDescargasAcabadas +Name[et]=Kõik allalaadimised lõpetatud +Name[eu]=GehituDeskargakAmaituta +Name[fa]=پایان بارگیریهای اÙزوده +Name[fi]=LisääHautValmistui +Name[fr]=Téléchargements ajoutés terminés +Name[hu]=FelvettLetöltésBefejezÅ‘dött +Name[is]=Lokið að bæta við niðurhölum +Name[it]=Aggiunti scaricamenti completati +Name[ja]=ダウンロード追加完了 +Name[kk]=Барлық жүктеу аÑқталды +Name[km]=បាន​បញ្ចប់​ការ​បន្ážáŸ‚ម​ការ​ទាញ​យក +Name[nds]=AllDaalladenBeendt +Name[ne]=डाउनलोड थपà¥à¤¨à¥‡ समापà¥à¤¤ भयो +Name[nl]=DownloadsToevoegenVoltooid +Name[pl]=Wszystkie transfery zakoÅ„czone +Name[pt_BR]=AdicionaraosDonwloadsConcluídos +Name[ru]=Ð’ÑеЗагружено +Name[sk]=Pridanie sÅ¥ahovania ukonÄené +Name[sl]=VsiPrenosiKonec +Name[sr]=Додавање преузимања завршено +Name[sr@Latn]=Dodavanje preuzimanja zavrÅ¡eno +Name[sv]=Tillagda nerladdningar klara +Name[tr]=Biten Ä°ndirmelere Ekle +Name[uk]=Ð’ÑÑ– Ð·Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ñ– +Name[zh_CN]=æ·»åŠ ä¸‹è½½å®Œæˆ +Name[zh_HK]=æ–°å¢žçš„ä¸‹è¼‰å·²å®Œæˆ +Name[zh_TW]=æ–°å¢žä¸‹è¼‰å®Œæˆ +Comment=All downloads finished +Comment[be]=УÑе файлы ÑцÑÐ³Ð½ÑƒÑ‚Ñ‹Ñ +Comment[bg]=ИзтеглÑнето на вÑичките файлове завърши +Comment[bn]=সব ডাউনলোড শেষ করল +Comment[bs]=ZavrÅ¡eni svi downloadi +Comment[ca]=Han acabat totes les descàrregues +Comment[cs]=VÅ¡echna stahování byla dokonÄena +Comment[da]=Alle download afsluttet +Comment[de]=Alle Datentransfers sind abgeschlossen +Comment[el]=ΟλοκληÏώθηκαν όλες οι λήψεις +Comment[es]=Todas las descargas acabaron +Comment[et]=Kõik allalaadimised lõpetatud +Comment[eu]=Deskarga guztiak amaituta +Comment[fa]=همۀ بارگیریها پایان یاÙت +Comment[fi]=Kaikki tiedostojen haut ovat valmistuneet +Comment[fr]=Tous les téléchargements sont terminés +Comment[gl]=Tódalas descargar están rematadas +Comment[he]=כל ההורדות הסתיימו +Comment[hu]=Minden letöltés befejezÅ‘dött +Comment[is]=Öllum niðurhölum lokið +Comment[it]=Tutti gli scaricamenti completati +Comment[ja]=ã™ã¹ã¦ã®ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ãŒå®Œäº† +Comment[ka]=ყველრჩáƒáƒ›áƒáƒ¥áƒáƒ©áƒ•áƒ დáƒáƒ¡áƒ áƒ£áƒšáƒ“რ+Comment[kk]=Барлық жүктеу тапÑырмалары аÑқталды +Comment[km]=បាន​បញ្ចប់​ការ​ទាញយក​ទាំងអស់ +Comment[lt]=Visi atsisiuntimai baigti +Comment[nb]=Alle nedlastinger ferdig +Comment[nds]=All Daalladen beendt +Comment[ne]=सबै डाउनलोड समापà¥à¤¤ भयो +Comment[nl]=Alle downloads zijn voltooid +Comment[nn]=Alle nedlastingane er ferdige +Comment[pl]=Pobieranie wszystkich plików zostaÅ‚o zakoÅ„czone +Comment[pt]=Todas as transferências terminaram +Comment[pt_BR]=Todos os downloads foram concluídos +Comment[ru]=Ð’Ñе загрузки выполнены. +Comment[sk]=VÅ¡etky sÅ¥ahovania ukonÄené +Comment[sl]=Vsi prenosi so se zakljuÄili +Comment[sr]=Сва преузимања Ñу завршена +Comment[sr@Latn]=Sva preuzimanja su zavrÅ¡ena +Comment[sv]=Alla nerladdningar klara +Comment[tr]=Tüm dosyaların indirilmesi tamamlandı +Comment[uk]=Ð’ÑÑ– Ð·Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ñ– +Comment[uz]=Hamma yozib olishlar tugadi +Comment[uz@cyrillic]=Ҳамма ёзиб олишлар тугади +Comment[zh_CN]=æ‰€æœ‰ä¸‹è½½éƒ½å·²å®Œæˆ +Comment[zh_HK]=æ‰€æœ‰ä¸‹è¼‰å·²å®Œæˆ +Comment[zh_TW]=æ‰€æœ‰ä¸‹è¼‰çš†å·²å®Œæˆ +default_sound=KGet_Finished_All.ogg +default_presentation=16 diff --git a/kget/getfilejob.cpp b/kget/getfilejob.cpp new file mode 100644 index 00000000..3710dda1 --- /dev/null +++ b/kget/getfilejob.cpp @@ -0,0 +1,37 @@ +/*************************************************************************** + getfilejob.cpp - description + ------------------- + begin : Wed May 8 2002 + copyright : (C) 2002 by Patrick Charbonnier + email : pch@freeshell.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. * + * * + ***************************************************************************/ + +#include "getfilejob.h" + +namespace KIO +{ + + +GetFileJob::GetFileJob(const KURL & m_src, const KURL & m_dest):FileCopyJob(m_src, m_dest,-1, false, false, false, false) +{} + +GetFileJob::~GetFileJob() +{} + +/** Return true if the file has been resumed */ +bool GetFileJob::getCanResume()const +{ + return m_canResume; +} + + +} diff --git a/kget/getfilejob.h b/kget/getfilejob.h new file mode 100644 index 00000000..04c44258 --- /dev/null +++ b/kget/getfilejob.h @@ -0,0 +1,36 @@ +/*************************************************************************** + getfilejob.h - description + ------------------- + begin : Wed May 8 2002 + copyright : (C) 2002 by Patrick Charbonnier + email : pch@freeshell.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. * + * * + ***************************************************************************/ + +#ifndef GETFILEJOB_H +#define GETFILEJOB_H + +#include + + +namespace KIO +{ + +class GetFileJob:public FileCopyJob +{ +public: + GetFileJob(const KURL & m_src, const KURL & m_dest); + ~GetFileJob(); + bool getCanResume() const; +}; +} + +#endif diff --git a/kget/http_defaults.h b/kget/http_defaults.h new file mode 100644 index 00000000..2937dc5b --- /dev/null +++ b/kget/http_defaults.h @@ -0,0 +1,59 @@ +/*************************************************************************** +* http_defaults.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* +* email : pch@freeshell.org +* +***************************************************************************/ + +/* This file is part of the KDE libraries + Copyright (C) 2001 Waldo Bastian + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef _KIO_HTTP_SLAVE_DEFAULTS_H +#define _KIO_HTTP_SLAVE_DEFAULTS_H + +// CACHE SETTINGS +#define DEFAULT_MAX_CACHE_SIZE 5120 // 5 MB +#define DEFAULT_MAX_CACHE_AGE 60*60*24*14 // 14 DAYS +#define DEFAULT_CACHE_EXPIRE 10 // 10 SECS +#define DEFAULT_CLEAN_CACHE_INTERVAL 30*60 // 30 MINS +#define DEFAULT_CACHE_CONTROL KIO::CC_Verify // Verify with remote +#define CACHE_REVISION "7\n" // Cache version + +// DEFAULT USER AGENT KEY - ENABLES OS NAME +#define DEFAULT_USER_AGENT_KEYS "o" // Show OS + +// MAXIMUM AMOUNT OF DATA THAT CAN BE SAFELY SENT OVER IPC +#define MAX_IPC_SIZE 1024*8 + +// AMOUNT OF DATA TO OBTAIN FROM THE SERVER BY DEFAULT +#define DEFAULT_BUF_SIZE 1024*4 + +// SOME DEFAULT HEADER VALUES +#define DEFAULT_LANGUAGE_HEADER "en" +#define DEFAULT_MIME_TYPE "text/html" +#define DEFAULT_FULL_CHARSET_HEADER "iso-8859-1, utf-8, *" +#define DEFAULT_PARIAL_CHARSET_HEADER ", utf-8, *" + +#define DEFAULT_ACCEPT_HEADER "text/*, image/jpeg, image/png, image/*, */*" + +#endif diff --git a/kget/icons/Makefile.am b/kget/icons/Makefile.am new file mode 100644 index 00000000..9b3651ce --- /dev/null +++ b/kget/icons/Makefile.am @@ -0,0 +1,2 @@ +kgeticon_ICON = AUTO +kgeticondir = $(kde_datadir)/kget/icons diff --git a/kget/icons/cr22-action-bar0.png b/kget/icons/cr22-action-bar0.png new file mode 100644 index 00000000..234e8527 Binary files /dev/null and b/kget/icons/cr22-action-bar0.png differ diff --git a/kget/icons/cr22-action-bar1.png b/kget/icons/cr22-action-bar1.png new file mode 100644 index 00000000..ac7bd14c Binary files /dev/null and b/kget/icons/cr22-action-bar1.png differ diff --git a/kget/icons/cr22-action-bar2.png b/kget/icons/cr22-action-bar2.png new file mode 100644 index 00000000..ff6c7237 Binary files /dev/null and b/kget/icons/cr22-action-bar2.png differ diff --git a/kget/icons/cr22-action-bar3.png b/kget/icons/cr22-action-bar3.png new file mode 100644 index 00000000..c741bd4c Binary files /dev/null and b/kget/icons/cr22-action-bar3.png differ diff --git a/kget/icons/cr22-action-bar4.png b/kget/icons/cr22-action-bar4.png new file mode 100644 index 00000000..7390352c Binary files /dev/null and b/kget/icons/cr22-action-bar4.png differ diff --git a/kget/icons/cr22-action-bar5.png b/kget/icons/cr22-action-bar5.png new file mode 100644 index 00000000..418c1149 Binary files /dev/null and b/kget/icons/cr22-action-bar5.png differ diff --git a/kget/icons/cr22-action-bar6.png b/kget/icons/cr22-action-bar6.png new file mode 100644 index 00000000..930bd77a Binary files /dev/null and b/kget/icons/cr22-action-bar6.png differ diff --git a/kget/icons/cr22-action-kget_dock.png b/kget/icons/cr22-action-kget_dock.png new file mode 100644 index 00000000..6b0f14f3 Binary files /dev/null and b/kget/icons/cr22-action-kget_dock.png differ diff --git a/kget/icons/cr22-action-kget_dock_download.png b/kget/icons/cr22-action-kget_dock_download.png new file mode 100644 index 00000000..7242c864 Binary files /dev/null and b/kget/icons/cr22-action-kget_dock_download.png differ diff --git a/kget/icons/cr22-action-tool_clipboard.png b/kget/icons/cr22-action-tool_clipboard.png new file mode 100644 index 00000000..3695bdf0 Binary files /dev/null and b/kget/icons/cr22-action-tool_clipboard.png differ diff --git a/kget/icons/cr22-action-tool_delay.png b/kget/icons/cr22-action-tool_delay.png new file mode 100644 index 00000000..96919575 Binary files /dev/null and b/kget/icons/cr22-action-tool_delay.png differ diff --git a/kget/icons/cr22-action-tool_disconnect.png b/kget/icons/cr22-action-tool_disconnect.png new file mode 100644 index 00000000..437b9b6d Binary files /dev/null and b/kget/icons/cr22-action-tool_disconnect.png differ diff --git a/kget/icons/cr22-action-tool_dock.png b/kget/icons/cr22-action-tool_dock.png new file mode 100644 index 00000000..6dce81ca Binary files /dev/null and b/kget/icons/cr22-action-tool_dock.png differ diff --git a/kget/icons/cr22-action-tool_drop_target.png b/kget/icons/cr22-action-tool_drop_target.png new file mode 100644 index 00000000..6b0f14f3 Binary files /dev/null and b/kget/icons/cr22-action-tool_drop_target.png differ diff --git a/kget/icons/cr22-action-tool_expert.png b/kget/icons/cr22-action-tool_expert.png new file mode 100644 index 00000000..8e5a3472 Binary files /dev/null and b/kget/icons/cr22-action-tool_expert.png differ diff --git a/kget/icons/cr22-action-tool_logwindow.png b/kget/icons/cr22-action-tool_logwindow.png new file mode 100644 index 00000000..7596e67a Binary files /dev/null and b/kget/icons/cr22-action-tool_logwindow.png differ diff --git a/kget/icons/cr22-action-tool_normal.png b/kget/icons/cr22-action-tool_normal.png new file mode 100644 index 00000000..02511b78 Binary files /dev/null and b/kget/icons/cr22-action-tool_normal.png differ diff --git a/kget/icons/cr22-action-tool_offline_mode_off.png b/kget/icons/cr22-action-tool_offline_mode_off.png new file mode 100644 index 00000000..66c4e7f5 Binary files /dev/null and b/kget/icons/cr22-action-tool_offline_mode_off.png differ diff --git a/kget/icons/cr22-action-tool_offline_mode_on.png b/kget/icons/cr22-action-tool_offline_mode_on.png new file mode 100644 index 00000000..b98d1945 Binary files /dev/null and b/kget/icons/cr22-action-tool_offline_mode_on.png differ diff --git a/kget/icons/cr22-action-tool_pause.png b/kget/icons/cr22-action-tool_pause.png new file mode 100644 index 00000000..d529cee7 Binary files /dev/null and b/kget/icons/cr22-action-tool_pause.png differ diff --git a/kget/icons/cr22-action-tool_queue.png b/kget/icons/cr22-action-tool_queue.png new file mode 100644 index 00000000..43e2110e Binary files /dev/null and b/kget/icons/cr22-action-tool_queue.png differ diff --git a/kget/icons/cr22-action-tool_restart.png b/kget/icons/cr22-action-tool_restart.png new file mode 100644 index 00000000..3fa8db76 Binary files /dev/null and b/kget/icons/cr22-action-tool_restart.png differ diff --git a/kget/icons/cr22-action-tool_resume.png b/kget/icons/cr22-action-tool_resume.png new file mode 100644 index 00000000..1c71524e Binary files /dev/null and b/kget/icons/cr22-action-tool_resume.png differ diff --git a/kget/icons/cr22-action-tool_shutdown.png b/kget/icons/cr22-action-tool_shutdown.png new file mode 100644 index 00000000..119e5ef7 Binary files /dev/null and b/kget/icons/cr22-action-tool_shutdown.png differ diff --git a/kget/icons/cr22-action-tool_timer.png b/kget/icons/cr22-action-tool_timer.png new file mode 100644 index 00000000..e325e6ef Binary files /dev/null and b/kget/icons/cr22-action-tool_timer.png differ diff --git a/kget/icons/cr22-action-tool_uselastdir.png b/kget/icons/cr22-action-tool_uselastdir.png new file mode 100644 index 00000000..12f0413d Binary files /dev/null and b/kget/icons/cr22-action-tool_uselastdir.png differ diff --git a/kget/kfileio.cpp b/kget/kfileio.cpp new file mode 100644 index 00000000..bfd03864 --- /dev/null +++ b/kget/kfileio.cpp @@ -0,0 +1,174 @@ +/*************************************************************************** +* kfileio.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* +* email : pch@freeshell.org +* +***************************************************************************/ + +// Author: Stefan Taferner + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "kfileio.h" + +//----------------------------------------------------------------------------- +QString kFileToString(const QString & aFileName, bool aEnsureNL, bool aVerbose) +{ + QCString result; + + QFileInfo info(aFileName); + unsigned int readLen; + unsigned int len = info.size(); + QFile file(aFileName); + + // assert(aFileName!=NULL); + if (aFileName == NULL) + return ""; + + if (!info.exists()) { + if (aVerbose) + KMessageBox::error(qApp->mainWidget(), i18n("The specified file does not exist:\n%1").arg(aFileName)); + return QString::null; + } + if (info.isDir()) { + if (aVerbose) + KMessageBox::error(qApp->mainWidget(), i18n("This is a folder and not a file:\n%1").arg(aFileName)); + return QString::null; + } + if (!info.isReadable()) { + if (aVerbose) + KMessageBox::error(qApp->mainWidget(), i18n("You do not have read permission for the file:\n%1").arg(aFileName)); + return QString::null; + } + if (len <= 0) + return QString::null; + + if (!file.open(IO_Raw | IO_ReadOnly)) { + if (aVerbose) + switch (file.status()) { + case IO_ReadError: + KMessageBox::error(qApp->mainWidget(), i18n("Could not read file:\n%1").arg(aFileName)); + break; + case IO_OpenError: + KMessageBox::error(qApp->mainWidget(), i18n("Could not open file:\n%1").arg(aFileName)); + break; + default: + KMessageBox::error(qApp->mainWidget(), i18n("Error while reading file:\n%1").arg(aFileName)); + } + return QString::null; + } + + result.resize(len + (int) aEnsureNL + 1); + readLen = file.readBlock(result.data(), len); + if (aEnsureNL && result[len - 1] != '\n') { + result[len++] = '\n'; + readLen++; + } + result[len] = '\0'; + + if (readLen < len) { + QString msg = i18n("Could only read %1 bytes of %2.").arg(KGlobal::locale()->formatNumber(readLen, + 0)).arg(KGlobal::locale()->formatNumber(len, 0)); + + KMessageBox::error(qApp->mainWidget(), msg); + return QString::null; + } + + kdDebug() << "kFileToString: " << readLen << " bytes read" << endl; + return result; +} + + +//----------------------------------------------------------------------------- +static bool kBytesToFile(const char *aBuffer, int len, const QString & aFileName, bool aAskIfExists, bool aBackup, bool aVerbose) +{ + QFile file(aFileName); + QFileInfo info(aFileName); + int writeLen, rc; + + // assert(aFileName!=NULL); + if (aFileName.isNull()) + return false; + + if (info.exists()) { + if (aAskIfExists) { + QString str = i18n("File %1 exists.\nDo you want to replace it?").arg(aFileName); + + rc = KMessageBox::questionYesNo(qApp->mainWidget(), str, QString::null, i18n("Replace"),KStdGuiItem::cancel()); + if (rc != KMessageBox::Yes) + return FALSE; + } + if (aBackup) { + // make a backup copy + QString bakName = aFileName; + + bakName += '~'; + QFile::remove(bakName); + rc = rename(QFile::encodeName(aFileName), QFile::encodeName(bakName)); + if (rc) { + // failed to rename file + if (!aVerbose) + return FALSE; + rc = KMessageBox::warningContinueCancel(qApp->mainWidget(), i18n("Failed to make a backup copy of %1.\nContinue anyway?").arg(aFileName)); + if (rc != KMessageBox::Continue) + return FALSE; + } + } + } + + if (!file.open(IO_Raw | IO_WriteOnly)) { + if (aVerbose) + switch (file.status()) { + case IO_WriteError: + KMessageBox::error(qApp->mainWidget(), i18n("Could not write to file:\n%1").arg(aFileName)); + break; + case IO_OpenError: + KMessageBox::error(qApp->mainWidget(), i18n("Could not open file for writing:\n%1").arg(aFileName)); + break; + default: + KMessageBox::error(qApp->mainWidget(), i18n("Error while writing file:\n%1").arg(aFileName)); + } + return FALSE; + } + + writeLen = file.writeBlock(aBuffer, len); + + if (writeLen < 0) { + KMessageBox::error(qApp->mainWidget(), i18n("Could not write to file:\n%1").arg(aFileName)); + return FALSE; + } else if (writeLen < len) { + QString msg = i18n("Could only write %1 bytes of %2.").arg(KGlobal::locale()->formatNumber(writeLen, + 0)).arg(KGlobal::locale()->formatNumber(len, + 0)); + + KMessageBox::error(qApp->mainWidget(), msg); + return FALSE; + } + + return TRUE; +} + +bool kCStringToFile(const QCString & aBuffer, const QString & aFileName, bool aAskIfExists, bool aBackup, bool aVerbose) +{ + return kBytesToFile(aBuffer, aBuffer.length(), aFileName, aAskIfExists, aBackup, aVerbose); +} + +bool kByteArrayToFile(const QByteArray & aBuffer, const QString & aFileName, bool aAskIfExists, bool aBackup, bool aVerbose) +{ + return kBytesToFile(aBuffer, aBuffer.size(), aFileName, aAskIfExists, aBackup, aVerbose); +} diff --git a/kget/kfileio.h b/kget/kfileio.h new file mode 100644 index 00000000..9b2780ad --- /dev/null +++ b/kget/kfileio.h @@ -0,0 +1,44 @@ +/*************************************************************************** +* kfileio.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* +* email : pch@freeshell.org +* +***************************************************************************/ + +/* Load / save entire (local) files with nice diagnostics dialog messages. + * These functions load/save the whole buffer in one i/o call, so they + * should be pretty efficient. + * + * Author: Stefan Taferner + * This code is under GPL. + */ +#ifndef kfileio_h +#define kfileio_h + +/** Load a file. Returns a pointer to the memory-block that contains + * the loaded file. Returns a NULL string if the file could not be loaded. + * If withDialogs is FALSE no warning dialogs are opened if there are + * problems. + * The string returned is always zero-terminated and therefore one + * byte longer than the file itself. + * If ensureNewline is TRUE the string will always have a trailing newline. + */ +QString kFileToString(const QString & fileName, bool ensureNewline = TRUE, bool withDialogs = TRUE); + +/** Save a file. If withDialogs is FALSE no warning dialogs are opened if + * there are problems. Returns TRUE on success and FALSE on failure. + * Replaces existing files without warning if askIfExists==FALSE. + * Makes a copy if the file exists to filename~ if createBackup==TRUE. + */ +bool kCStringToFile(const QCString & buffer, const QString & fileName, bool askIfExists = FALSE, bool createBackup = TRUE, bool withDialogs = TRUE); + +// Does not stop at NUL +bool kByteArrayToFile(const QByteArray & buffer, const QString & fileName, bool askIfExists = FALSE, bool createBackup = TRUE, bool withDialogs = TRUE); + + +#endif /* kfileio_h */ diff --git a/kget/kget.desktop b/kget/kget.desktop new file mode 100644 index 00000000..7230b416 --- /dev/null +++ b/kget/kget.desktop @@ -0,0 +1,77 @@ +# KDE Config File +[Desktop Entry] +Type=Application +Exec=kget -caption "%c" %i %m +Icon=kget +Terminal=false +Name=KGet +Name[ar]=Ùƒ.جيت +Name[bn]=কে-গেট +Name[cy]=KNôl +Name[eo]=Prenilo +Name[hi]=के-गेट +Name[ne]=केडीई गेट +Name[sv]=Kget +Name[ta]=கேகெட௠+Name[th]=ดาวน์โหลด K +Name[ven]=Wana ha K +GenericName=Download Manager +GenericName[ar]=إدارة التحميل +GenericName[be]=Праграма ÑцÑÐ³Ð²Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ð°Ñž +GenericName[bg]=ИзтеглÑне на файлове +GenericName[bn]=ডাউনলোড মà§à¦¯à¦¾à¦¨à§‡à¦œà¦¾à¦° +GenericName[br]=Merour enkargañ +GenericName[bs]=Upravitelj downloadom +GenericName[ca]=Gestor de descàrregues +GenericName[cs]=Správce stahování +GenericName[cy]=Rheolydd Lawrlwytho +GenericName[da]=HÃ¥ndtering af download +GenericName[de]=Datentransfer-Verwaltung +GenericName[el]=ΔιαχειÏιστής λήψεων αÏχείων +GenericName[eo]=ElÅut-administrilo +GenericName[es]=Gestor de descargas +GenericName[et]=Allalaadimiste haldur +GenericName[eu]=Deskarga kudeatzailea +GenericName[fa]=مدیر بارگیری +GenericName[fi]=Tiedostonlataaja +GenericName[fr]=Gestionnaire de téléchargements +GenericName[ga]=Bainisteoir Thíosluchtaithe +GenericName[gl]=Xestor de Descargas +GenericName[he]=מנהל הורדות +GenericName[hi]=डाउनलोड पà¥à¤°à¤¬à¤‚धक +GenericName[hr]=Upravitelj preuzimanja +GenericName[hu]=LetöltéskezelÅ‘ +GenericName[is]=Niðurhalsstjóri +GenericName[it]=Gestore degli scaricamenti +GenericName[ja]=ダウンロードマãƒãƒ¼ã‚¸ãƒ£ +GenericName[ka]=ჩáƒáƒ›áƒáƒ¥áƒáƒ©áƒ•áƒáƒ—რმმáƒáƒ áƒ—ველი +GenericName[kk]=Жүкеп алу менеджері +GenericName[km]=កម្មវិធី​គ្រប់គ្រង​ការ​ទាញយក +GenericName[lt]=Siuntimų valdymas +GenericName[mk]=Менаџер за Ñимнувања +GenericName[nb]=Nedlastingsbehandler +GenericName[nds]=Daallaadpleger +GenericName[ne]=डाउनलोड पà¥à¤°à¤¬à¤¨à¥à¤§à¤• +GenericName[nl]=Downloadmanager +GenericName[nn]=Nedlastingshandsamar +GenericName[pa]=ਡਾਊਨਲੋਡ ਮੈਨੇਜਰ +GenericName[pl]=Menedżer pobierania +GenericName[pt]=Gestor de Transferências +GenericName[pt_BR]=Gerenciador de Download +GenericName[ru]=ДиÑпетчер загрузок +GenericName[sk]=Správca sÅ¥ahovania +GenericName[sl]=Upravitelj prenosov +GenericName[sr]=Менаџер преузимања +GenericName[sr@Latn]=Menadžer preuzimanja +GenericName[sv]=Nerladdningshanterare +GenericName[ta]=பதிவிறகà¯à®• மேலாளர௠+GenericName[tg]=Мудири Боркунӣ +GenericName[tr]=Ä°ndirme Yöneticisi +GenericName[uk]=Менеджер звантажень +GenericName[uz]=Yozib olish boshqaruvchisi +GenericName[uz@cyrillic]=Ðзиб олиш бошқарувчиÑи +GenericName[zh_CN]=下载管ç†å™¨ +GenericName[zh_HK]=下載管ç†å“¡ +GenericName[zh_TW]=下載管ç†å“¡ +Categories=Qt;KDE;Network;FileTransfer; +X-DCOP-ServiceType=Unique diff --git a/kget/kget_download.desktop b/kget/kget_download.desktop new file mode 100644 index 00000000..89654b8e --- /dev/null +++ b/kget/kget_download.desktop @@ -0,0 +1,57 @@ +[Desktop Entry] +Actions=KGetDownload; +Icon= +ServiceTypes=all/allfiles +ExcludeServiceTypes=kdedevice/* + +[Desktop Action KGetDownload] +Exec=kget %U +Icon=kget +Name=Download with KGet +Name[be]=СцÑгнуць праз KGet +Name[bg]=ИзтеглÑне Ñ KGet +Name[bn]=কে-গেট দিয়ে ডাউনলোড +Name[br]=Enkargañ gant KGet +Name[bs]=Download sa KGet-om +Name[ca]=Descarrega amb el KGet +Name[cs]=Stáhnout pomocí KGet +Name[da]=Download med KGet +Name[de]=Mit KGet herunterladen +Name[el]=Λήψη αÏχείου με το KGet +Name[es]=Descarga con KGet +Name[et]=Laadi alla KGeti abil +Name[eu]=Deskargatu KGet-ekin +Name[fa]=بارگیری با KGet +Name[fi]=Hae KGet ohjelmalla +Name[fr]=Télécharger avec KGet +Name[ga]=Ãosluchtaigh le KGet +Name[gl]=Descargar con KGet +Name[he]=הורד בעזרת KGet +Name[hu]=Letöltés a KGettel +Name[is]=Sækja með KGet +Name[it]=Scarica con KGet +Name[ja]=KGet ã§ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ +Name[ka]=KGet-ით ჩáƒáƒ›áƒáƒ¥áƒáƒ©áƒ•áƒ +Name[kk]=KGet көмегімен жүктеп алу +Name[km]=ទាញយក​ដោយ​ប្រើ KGet +Name[lt]=Atsisiųsti su KGet +Name[nb]=Nedlasting med KGet +Name[nds]=Dalladen mit KGet +Name[ne]=केडीई गेटसà¤à¤— डाउनलोड गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Name[nl]=Met KGet downloaden +Name[nn]=Last ned med KGet +Name[pa]=KGet ਨਾਲ ਡਾਊਨਲੋਡ +Name[pl]=Pobierz za pomocÄ… KGet +Name[pt]=Obter com o KGet +Name[pt_BR]=Baixar com o KGet +Name[ru]=Загрузить Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ KGet +Name[sk]=StiahnuÅ¥ pomocou KGet +Name[sl]=Prenos s KGet +Name[sr]=Преузми Ñа KGet-ом +Name[sr@Latn]=Preuzmi sa KGet-om +Name[sv]=Ladda ner med Kget +Name[tr]=KGet ile Ä°ndir +Name[uk]=Звантажити з KGet +Name[zh_CN]=用 KGet 下载 +Name[zh_HK]=以 KGet 下載 +Name[zh_TW]=用 KGet 下載 diff --git a/kget/kget_iface.h b/kget/kget_iface.h new file mode 100644 index 00000000..015bef2f --- /dev/null +++ b/kget/kget_iface.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** $Id$ +** +** Copyright (C) 2002 Carsten Pfeiffer +** +****************************************************************************/ + +#ifndef KGET_IFACE_H +#define KGET_IFACE_H + +#include +#include + +class KGetIface : public DCOPObject +{ + K_DCOP + +protected: + KGetIface( QCString objId ) : DCOPObject( objId ) {} + +k_dcop: + /** + * @param src The urls to download + * @param destDir The destination direction or QString::null if unspecified + */ + virtual ASYNC addTransfers( const KURL::List& src, const QString& destDir = QString::null ) = 0; + + virtual bool isDropTargetVisible() const = 0; + + virtual void setDropTargetVisible( bool setVisible ) = 0; + + virtual void setOfflineMode( bool offline ) = 0; + + virtual bool isOfflineMode() const = 0; +}; + +#endif // KGET_IFACE_H diff --git a/kget/kget_plug_in/Makefile.am b/kget/kget_plug_in/Makefile.am new file mode 100644 index 00000000..4e2afd67 --- /dev/null +++ b/kget/kget_plug_in/Makefile.am @@ -0,0 +1,13 @@ +INCLUDES = $(all_includes) + +kde_module_LTLIBRARIES = khtml_kget.la + +khtml_kget_la_METASOURCES = AUTO +khtml_kget_la_SOURCES = kget_plug_in.cpp kget_linkview.cpp links.cpp +khtml_kget_la_LIBADD = $(LIB_KHTML) +khtml_kget_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module + +KDE_ICON = AUTO + +part_DATA = kget_plug_in.rc kget_plug_in.desktop +partdir = $(kde_datadir)/khtml/kpartplugins diff --git a/kget/kget_plug_in/cr22-action-khtml_kget.png b/kget/kget_plug_in/cr22-action-khtml_kget.png new file mode 100644 index 00000000..6b0f14f3 Binary files /dev/null and b/kget/kget_plug_in/cr22-action-khtml_kget.png differ diff --git a/kget/kget_plug_in/kget_linkview.cpp b/kget/kget_plug_in/kget_linkview.cpp new file mode 100644 index 00000000..179cc81b --- /dev/null +++ b/kget/kget_plug_in/kget_linkview.cpp @@ -0,0 +1,150 @@ +#include "kget_linkview.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define COL_NAME 0 +#define COL_DESC 1 +#define COL_MIME 2 +#define COL_URL 3 + +LinkViewItem::LinkViewItem( QListView *parent, const LinkItem *lnk ) + : QListViewItem( parent ), + link( lnk ) +{ + QString file = link->url.fileName(); + if ( file.isEmpty() ) + file = link->url.host(); + + setPixmap( COL_NAME, SmallIcon( link->icon ) ); + setText( COL_NAME, file ); + + setText( COL_DESC, link->text ); + setText( COL_MIME, link->mimeType ); + setText( COL_URL, link->url.prettyURL() ); +} + +/////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////// + +KGetLinkView::KGetLinkView( QWidget *parent, const char *name ) + : KMainWindow( parent, name ) +{ + setPlainCaption( i18n( "KGet" ) ); + + KAction* actionDownload = new KAction( i18n("Download Selected Files"), + "kget", CTRL+Key_D, + this, SLOT( slotStartLeech() ), + actionCollection(), "startDownload" ); + + KAction* actionSelectAll = KStdAction::selectAll( this, SLOT( slotSelectAll() ), + actionCollection() ); + + m_links.setAutoDelete( true ); + actionDownload->plug( toolBar() ); + toolBar()->insertLineSeparator(); + actionSelectAll->plug( toolBar() ); + + QWidget *mainWidget = new QWidget( this ); + QVBoxLayout *layout = new QVBoxLayout( mainWidget ); + setCentralWidget( mainWidget ); + + m_view = new KListView( mainWidget, "listview" ); + m_view->setSelectionMode( QListView::Extended ); + m_view->addColumn( i18n("File Name") ); + m_view->addColumn( i18n("Description") ); + m_view->addColumn( i18n("File Type") ); + m_view->addColumn( i18n("Location (URL)") ); + m_view->setShowSortIndicator( true ); + + KListViewSearchLineWidget *line = new KListViewSearchLineWidget( m_view, mainWidget, "search line" ); + layout->addWidget( line ); + layout->addWidget( m_view ); + + // setting a fixed (not floating) toolbar + toolBar()->setMovingEnabled( false ); + // setting Text next to Icons + toolBar()->setIconText( KToolBar::IconTextRight ); +} + +KGetLinkView::~KGetLinkView() +{ +} + +void KGetLinkView::setLinks( QPtrList& links ) +{ + m_links = links; // now we 0wn them + showLinks( m_links ); +} + +void KGetLinkView::showLinks( const QPtrList& links ) +{ + m_view->clear(); + + QPtrListIterator it( links ); + for ( ; it.current(); ++it ) + (void) new LinkViewItem( m_view, *it ); + + m_view->sort(); +} + +void KGetLinkView::slotStartLeech() +{ + KURL::List urls; + QListViewItemIterator it( m_view->firstChild() ); + for ( ; it.current(); ++it ) + { + if ( it.current()->isSelected() ) + urls.append( static_cast( it.current() )->link->url ); + } + + if ( urls.isEmpty() ) + KMessageBox::sorry( this, + i18n("You did not select any files to download."), + i18n("No Files Selected") ); + else + { + DCOPClient* p_dcopServer = new DCOPClient(); + p_dcopServer->attach(); + + if ( !p_dcopServer->isApplicationRegistered( "kget" ) ) + { + KApplication::startServiceByDesktopName( "kget" ); + } + kapp->updateRemoteUserTimestamp( "kget" ); + + QByteArray data; + QDataStream stream( data, IO_WriteOnly ); + stream << urls << QString::null; + bool ok = DCOPClient::mainClient()->send( "kget", "KGet-Interface", + "addTransfers(KURL::List, QString)", + data ); + + kdDebug() << "*** startDownload: " << ok << endl; + + p_dcopServer->detach(); + delete p_dcopServer; + } +} + +void KGetLinkView::setPageURL( const QString& url ) +{ + setPlainCaption( i18n( "Links in: %1 - KGet" ).arg( url ) ); +} + +void KGetLinkView::slotSelectAll() +{ + m_view->selectAll( true ); +} + +#include "kget_linkview.moc" diff --git a/kget/kget_plug_in/kget_linkview.h b/kget/kget_plug_in/kget_linkview.h new file mode 100644 index 00000000..a6d7961c --- /dev/null +++ b/kget/kget_plug_in/kget_linkview.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** $Id$ +** +** Copyright (C) 2002 Carsten Pfeiffer +** +****************************************************************************/ + +#ifndef KGET_LINKVIEW_H +#define KGET_LINKVIEW_H + +#include + +#include +#include +#include + +#include "links.h" + +class LinkViewItem : public QListViewItem +{ +public: + LinkViewItem( QListView *parent, const LinkItem * lnk ); + const LinkItem *link; +}; + + +class KGetLinkView : public KMainWindow +{ + Q_OBJECT + +public: + KGetLinkView( QWidget *parent = 0L, const char *name = 0L ); + ~KGetLinkView(); + + void setLinks( QPtrList& links ); + void setPageURL( const QString& url ); + +signals: + void leechURLs( const KURL::List& urls ); + +private slots: + void slotStartLeech(); + void slotSelectAll(); + +private: + void showLinks( const QPtrList& links ); + + QPtrList m_links; + + KListView *m_view; + +}; + +#endif // KGET_LINKVIEW_H diff --git a/kget/kget_plug_in/kget_plug_in.cpp b/kget/kget_plug_in/kget_plug_in.cpp new file mode 100644 index 00000000..dc5f5b9f --- /dev/null +++ b/kget/kget_plug_in/kget_plug_in.cpp @@ -0,0 +1,189 @@ +/*************************************************************************** + kget_plug_in.cpp - description + ------------------- + begin : Wed Jul 3 22:09:28 CEST 2002 + copyright : (C) 2002 by Patrick + email : pch@valleeurpe.net + + Copyright (C) 2002 Carsten Pfeiffer + + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "kget_plug_in.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include "links.h" +#include "kget_linkview.h" + +KGet_plug_in::KGet_plug_in( QObject* parent, const char* name ) + : Plugin( parent, name ) +{ + QPixmap pix = KGlobal::iconLoader()->loadIcon("kget", + KIcon::MainToolbar); + KActionMenu *menu = new KActionMenu( i18n("Download Manager"), pix, + actionCollection(), "kget_menu" ); + menu->setDelayed( false ); + connect( menu->popupMenu(), SIGNAL( aboutToShow() ), SLOT( showPopup() )); + + m_paToggleDropTarget=new KToggleAction(i18n("Show Drop Target"), + KShortcut(), + this, SLOT(slotShowDrop()), + actionCollection(), "show_drop" ); + + menu->insert( m_paToggleDropTarget ); + + KAction *action = new KAction(i18n("List All Links"), KShortcut(), + this, SLOT( slotShowLinks() ), + actionCollection(), "show_links"); + menu->insert( action ); + + p_dcopServer= new DCOPClient(); + p_dcopServer->attach (); +} + + +KGet_plug_in::~KGet_plug_in() +{ + p_dcopServer->detach(); + delete p_dcopServer; +} + + +void KGet_plug_in::showPopup() +{ + bool hasDropTarget = false; + + if (p_dcopServer->isApplicationRegistered ("kget")) + { + DCOPRef kget( "kget", "KGet-Interface" ); + hasDropTarget = kget.call( "isDropTargetVisible" ); + } + + m_paToggleDropTarget->setChecked( hasDropTarget ); +} + +void KGet_plug_in::slotShowDrop() +{ + if (!p_dcopServer->isApplicationRegistered ("kget")) + KRun::runCommand("kget --showDropTarget"); + else + { + DCOPRef kget( "kget", "KGet-Interface" ); + kget.send( "setDropTargetVisible", m_paToggleDropTarget->isChecked()); + } +} + +void KGet_plug_in::slotShowLinks() +{ + if ( !parent() || !parent()->inherits( "KHTMLPart" ) ) + return; + + KHTMLPart *htmlPart = static_cast( parent() ); + KParts::Part *activePart = 0L; + if ( htmlPart->partManager() ) + { + activePart = htmlPart->partManager()->activePart(); + if ( activePart && activePart->inherits( "KHTMLPart" ) ) + htmlPart = static_cast( activePart ); + } + + DOM::HTMLDocument doc = htmlPart->htmlDocument(); + if ( doc.isNull() ) + return; + + DOM::HTMLCollection links = doc.links(); + + QPtrList linkList; + std::set dupeCheck; + for ( uint i = 0; i < links.length(); i++ ) + { + DOM::Node link = links.item( i ); + if ( link.isNull() || link.nodeType() != DOM::Node::ELEMENT_NODE ) + continue; + + LinkItem *item = new LinkItem( (DOM::Element) link ); + if ( item->isValid() && + dupeCheck.find( item->url.url() ) == dupeCheck.end() ) + { + linkList.append( item ); + dupeCheck.insert( item->url.url() ); + } + else + delete item; + } + + if ( linkList.isEmpty() ) + { + KMessageBox::sorry( htmlPart->widget(), + i18n("There are no links in the active frame of the current HTML page."), + i18n("No Links") ); + return; + } + + KGetLinkView *view = new KGetLinkView(); + QString url = doc.URL().string(); + view->setPageURL( url ); + + view->setLinks( linkList ); + view->show(); +} + +KPluginFactory::KPluginFactory( QObject* parent, const char* name ) + : KLibFactory( parent, name ) +{ + s_instance = new KInstance("KPluginFactory"); +} + +QObject* KPluginFactory::createObject( QObject* parent, const char* name, const char*, const QStringList & ) +{ + QObject *obj = new KGet_plug_in( parent, name ); + return obj; +} + +KPluginFactory::~KPluginFactory() +{ + delete s_instance; +} + +extern "C" +{ + KDE_EXPORT void* init_khtml_kget() + { + KGlobal::locale()->insertCatalogue("kget"); + return new KPluginFactory; + } + +} + +KInstance* KPluginFactory::s_instance = 0L; + +#include "kget_plug_in.moc" diff --git a/kget/kget_plug_in/kget_plug_in.desktop b/kget/kget_plug_in/kget_plug_in.desktop new file mode 100644 index 00000000..8976c2f4 --- /dev/null +++ b/kget/kget_plug_in/kget_plug_in.desktop @@ -0,0 +1,77 @@ +[Desktop Entry] +X-KDE-Library=khtml_kget +X-KDE-PluginInfo-Author=Patrick Charbonnier, Carsten Pfeiffer +X-KDE-PluginInfo-Email=pch@valleeurope.net, pfeiffer@kde.org +X-KDE-PluginInfo-Name=kget +X-KDE-PluginInfo-Version=3.4 +X-KDE-PluginInfo-Website=http://kget.sourceforge.net +X-KDE-PluginInfo-Category=Tools +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true +Name=KGet +Name[ar]=Ùƒ.جيت +Name[bn]=কে-গেট +Name[cy]=KNôl +Name[eo]=Prenilo +Name[hi]=के-गेट +Name[ne]=केडीई गेट +Name[sv]=Kget +Name[ta]=கேகெட௠+Name[th]=ดาวน์โหลด K +Name[ven]=Wana ha K +Comment=Download Manager +Comment[be]=Праграма ÑцÑÐ³Ð²Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ð°Ñž +Comment[bg]=ИзтеглÑне на файлове +Comment[bn]=ডাউনলোড মà§à¦¯à¦¾à¦¨à§‡à¦œà¦¾à¦° +Comment[br]=Merour enkargañ +Comment[bs]=Upravitelj downloadom +Comment[ca]=Gestor de descàrregues +Comment[cs]=Správce stahování +Comment[cy]=Trefnydd Lawrlwytho +Comment[da]=HÃ¥ndtering af download +Comment[de]=Herunterladen von Dateien +Comment[el]=ΔιαχειÏιστής λήψεων αÏχείων +Comment[eo]=ElÅut-administrilo +Comment[es]=Gestor de descargas +Comment[et]=Allalaadimiste haldur +Comment[eu]=Deskarga kudeatzailea +Comment[fa]=مدیر بارگیری +Comment[fi]=Tiedostonlataaja +Comment[fr]=Gestionnaire de téléchargements +Comment[ga]=Bainisteoir Ãosluchtaithe +Comment[gl]=Xestor de Descargas +Comment[he]=מנהל הורדות +Comment[hu]=LetöltéskezelÅ‘ +Comment[is]=Niðurhalsstjóri +Comment[it]=Gestore degli scaricamenti +Comment[ja]=ダウンロードマãƒãƒ¼ã‚¸ãƒ£ +Comment[ka]=ჩáƒáƒ›áƒáƒ¥áƒáƒ©áƒ•áƒáƒ—რმმáƒáƒ áƒ—ველი +Comment[kk]=Жүктеп алу менеджері +Comment[km]=កម្មវិធី​គ្រប់គ្រង​ការ​ទាញ​យក +Comment[lt]=Siuntimų valdymas +Comment[nb]=Nedlastingsbehandler +Comment[nds]=Daallaadpleger +Comment[ne]=डाउनलोड पà¥à¤°à¤¬à¤¨à¥à¤§à¤• +Comment[nl]=Downloadmanager +Comment[nn]=Nedlastingshandsamar +Comment[pa]=ਡਾਊਨਲੋਡ ਮੈਨੇਜਰ +Comment[pl]=Menedżer pobierania +Comment[pt]=Gestor de Transferências +Comment[pt_BR]=Gerenciador de Download +Comment[ro]=Manager de transferuri FTP +Comment[ru]=ДиÑпетчер загрузок +Comment[sk]=Správca sÅ¥ahovania +Comment[sl]=Upravitelj prenosov +Comment[sr]=Менаџер преузимања +Comment[sr@Latn]=Menadžer preuzimanja +Comment[sv]=Nerladdningshanterare +Comment[tr]=Ä°ndirme Yöneticisi +Comment[uk]=Менеджер звантажень +Comment[uz]=Yozib olish boshqaruvchisi +Comment[uz@cyrillic]=Ðзиб олиш бошқарувчиÑи +Comment[zh_CN]=下载管ç†å™¨ +Comment[zh_HK]=下載管ç†å“¡ +Comment[zh_TW]=下載管ç†å“¡ +Icon=khtml_kget + diff --git a/kget/kget_plug_in/kget_plug_in.h b/kget/kget_plug_in/kget_plug_in.h new file mode 100644 index 00000000..75888b10 --- /dev/null +++ b/kget/kget_plug_in/kget_plug_in.h @@ -0,0 +1,58 @@ +/*************************************************************************** + kget_plug_in.h - description + ------------------- + begin : Wed Jul 3 22:09:28 CEST 2002 + copyright : (C) 2002 by Patrick + email : pch@valleeurpe.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. * + * * + ***************************************************************************/ +#ifndef __plugin_kget_plug_in_h +#define __plugin_kget_plug_in_h + +#include +#include +#include +#include + +class KInstance; + +class KGet_plug_in : public KParts::Plugin +{ + Q_OBJECT +public: + KGet_plug_in( QObject* parent = 0, const char* name = 0 ); + KToggleAction *m_paToggleDropTarget ; + DCOPClient* p_dcopServer; + virtual ~KGet_plug_in(); + +private slots: + void slotShowDrop(); + void slotShowLinks(); + void showPopup(); +}; + + +class KPluginFactory : public KLibFactory +{ + Q_OBJECT +public: + KPluginFactory( QObject *parent = 0, const char *name = 0 ); + ~KPluginFactory() ; + + virtual QObject* createObject( QObject* parent = 0, const char* pname = 0, + const char* name = "QObject", + const QStringList &args = QStringList() ); + +private: + static KInstance* s_instance; +}; + +#endif diff --git a/kget/kget_plug_in/kget_plug_in.rc b/kget/kget_plug_in/kget_plug_in.rc new file mode 100644 index 00000000..f40ee8b7 --- /dev/null +++ b/kget/kget_plug_in/kget_plug_in.rc @@ -0,0 +1,11 @@ + + + + &Tools + + + + + + + diff --git a/kget/kget_plug_in/links.cpp b/kget/kget_plug_in/links.cpp new file mode 100644 index 00000000..a597257d --- /dev/null +++ b/kget/kget_plug_in/links.cpp @@ -0,0 +1,41 @@ +#include "links.h" + +#include +#include + +#include +#include + +LinkItem::LinkItem( DOM::Element link ) + : m_valid( false ) +{ + DOM::NamedNodeMap attrs = link.attributes(); + DOM::Node href = attrs.getNamedItem( "href" ); + + // qDebug("*** href: %s", href.nodeValue().string().latin1() ); + + QString urlString = link.ownerDocument().completeURL( href.nodeValue() ).string(); + if ( urlString.isEmpty() ) + return; + + url = KURL::fromPathOrURL( urlString ); + if ( !KProtocolInfo::supportsReading( url ) ) + return; + + + // somehow getElementsByTagName("#text") doesn't work :( + DOM::NodeList children = link.childNodes(); + for ( uint i = 0; i < children.length(); i++ ) + { + DOM::Node node = children.item( i ); + if ( node.nodeType() == DOM::Node::TEXT_NODE ) + text.append( node.nodeValue().string() ); + } + + // force "local file" mimetype determination + KMimeType::Ptr mt = KMimeType::findByURL( url, 0, true, true); + icon = mt->icon( QString::null, false ); // dummy parameters + mimeType = mt->comment(); + + m_valid = true; +} diff --git a/kget/kget_plug_in/links.h b/kget/kget_plug_in/links.h new file mode 100644 index 00000000..89ba9ab5 --- /dev/null +++ b/kget/kget_plug_in/links.h @@ -0,0 +1,32 @@ +/**************************************************************************** +** $Id$ +** +** Copyright (C) 2002 Carsten Pfeiffer +** +****************************************************************************/ + +#ifndef LINKS_H +#define LINKS_H + +#include + +#include + +class LinkItem +{ +public: + LinkItem( DOM::Element link ); + + KURL url; + QString icon; + QString text; + QString mimeType; + + bool isValid() const { return m_valid; } + +private: + bool m_valid : 1; +}; + + +#endif // LINKS_H diff --git a/kget/kgetui.rc b/kget/kgetui.rc new file mode 100644 index 00000000..6f746e79 --- /dev/null +++ b/kget/kgetui.rc @@ -0,0 +1,71 @@ + + + + &File + + + + + + + + + + &Transfer + + + + + + + + + + + + + + + + &View + + + &Options + + + + + + + + + + + + &Settings + + + + + &Help + + +Main Toolbar + + + + + + + + + + + + + + + + + + diff --git a/kget/kmainwidget.cpp b/kget/kmainwidget.cpp new file mode 100644 index 00000000..420d44a1 --- /dev/null +++ b/kget/kmainwidget.cpp @@ -0,0 +1,2548 @@ +/*************************************************************************** +* kmainwidget.cpp +* ------------------- +* +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.org +* Copyright (C) 2002 Carsten Pfeiffer +* +****************************************************************************/ + +/*************************************************************************** + * + * 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. + * + ***************************************************************************/ +#include +#include +#include +#include +#include + +#ifdef __svr4__ +#define map BULLSHIT // on Solaris it conflicts with STL ? +#include +#undef map +#include // needed for SIOCGIFFLAGS +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "safedelete.h" +#include "settings.h" +#include "transfer.h" +#include "transferlist.h" +#include "kmainwidget.h" +#include "kfileio.h" +#include "dlgPreferences.h" +#include "logwindow.h" +#include "docking.h" +#include "droptarget.h" +#include + + + +#include +#include +#include + +#include "version.h" +#include "slave.h" +#include "slaveevent.h" + +struct KURLPair +{ + KURL dest; + KURL src; +}; + +KMainWidget *kmain = 0L; + +#define LOAD_ICON(X) KGlobal::iconLoader()->loadIcon(X, KIcon::MainToolbar) + +DropTarget *kdrop = 0L; + +Settings ksettings; // this object contains all settings + +static int sockets_open(); + + +// socket constants +int ipx_sock = -1; /* IPX socket */ +int ax25_sock = -1; /* AX.25 socket */ +int inet_sock = -1; /* INET socket */ +int ddp_sock = -1; /* Appletalk DDP socket */ + + + +KMainWidget::KMainWidget(bool bStartDocked) + : KGetIface( "KGet-Interface" ), + KMainWindow(0, "kget mainwindow",0), + prefDlg( 0 ), kdock( 0 ) +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + { + KConfig cfg( "kioslaverc", false, false); + cfg.setGroup(QString::null); + cfg.writeEntry("AutoResume", true); + cfg.sync(); + } + + b_connected = TRUE; + b_viewLogWindow = FALSE; + b_viewPreferences = FALSE; + + myTransferList = 0L; + kmain = this; + + // Set log time, needed for the name of log file + QDate date = QDateTime::currentDateTime().date(); + QTime time = QDateTime::currentDateTime().time(); + QString tmp; + + tmp.sprintf("log%d:%d:%d-%d:%d:%d", date.day(), date.month(), date.year(), time.hour(), time.minute(), time.second()); + + logFileName = locateLocal("appdata", "logs/"); + logFileName += tmp; + + lastClipboard = QApplication::clipboard()->text( QClipboard::Clipboard ).stripWhiteSpace(); + // Load all settings from KConfig + ksettings.load(); + + // Setup log window + logWindow = new LogWindow(); + + m_showDropTarget = false; + + setCaption(KGETVERSION); + + setupGUI(); + setupWhatsThis(); + + log(i18n("Welcome to KGet")); + + setCentralWidget(myTransferList); + + connect(kapp, SIGNAL(saveYourself()), SLOT(slotSaveYourself())); + + // Enable dropping + setAcceptDrops(true); + + // Setup connection timer + connectionTimer = new QTimer(this); + connect(connectionTimer, SIGNAL(timeout()), SLOT(slotCheckConnection())); + + // setup socket for checking connection + if ((_sock = sockets_open()) < 0) { + log(i18n("Could not create valid socket"), false); + } else { + connectionTimer->start(5000); // 5 second interval for checking connection + } + + checkOnline(); + ksettings.b_offline=( !b_connected || ksettings.b_offlineMode ); + + // Setup animation timer + animTimer = new QTimer(this); + animCounter = 0; + connect(animTimer, SIGNAL(timeout()), SLOT(slotAnimTimeout())); + + if (ksettings.b_useAnimation) { + animTimer->start(400); + } else { + animTimer->start(1000); + } + + // Setup transfer timer for scheduled downloads and checkQueue() + transferTimer = new QTimer(this); + connect(transferTimer, SIGNAL(timeout()), SLOT(slotTransferTimeout())); + transferTimer->start(10000); // 10 secs time interval + + // Setup autosave timer + autosaveTimer = new QTimer(this); + connect(autosaveTimer, SIGNAL(timeout()), SLOT(slotAutosaveTimeout())); + setAutoSave(); + + // Setup clipboard timer + clipboardTimer = new QTimer(this); + connect(clipboardTimer, SIGNAL(timeout()), SLOT(slotCheckClipboard())); + if (ksettings.b_autoPaste) { + clipboardTimer->start(1000); + } + + readTransfers(); + + // Setup special windows + kdrop = new DropTarget(this); + kdock = new DockWidget(this); + connect(kdock, SIGNAL(quitSelected()), SLOT(slotQuit())); + + // Set geometry + if (ksettings.mainPosition.x() != -1) { + resize(ksettings.mainSize); + move(ksettings.mainPosition); + KWin::setState(winId(), ksettings.mainState); + } else { + resize(650, 180); + } + + // update actions + m_paUseAnimation->setChecked(ksettings.b_useAnimation); + m_paExpertMode->setChecked(ksettings.b_expertMode); + m_paUseLastDir->setChecked(ksettings.b_useLastDir); + + if (ksettings.connectionType != PERMANENT) { + m_paAutoDisconnect->setChecked(ksettings.b_autoDisconnect); + } + setAutoDisconnect(); + + m_paAutoShutdown->setChecked(ksettings.b_autoShutdown); + + + m_paOfflineMode->setChecked(ksettings.b_offline); + if (!ksettings.b_offlineMode) + m_paOfflineMode->setIconSet(LOAD_ICON("tool_offline_mode_on")); + + if (ksettings.b_offline) { + setCaption(i18n("Offline"), false); + log(i18n("Starting offline")); + } else + setCaption(QString::null, false); + + m_paAutoPaste->setChecked(ksettings.b_autoPaste); + m_paShowLog->setChecked(b_viewLogWindow); + + if (!bStartDocked && ksettings.b_showMain) + show(); + + kdock->show(); + + KNotifyClient::startDaemon(); + + setStandardToolBarMenuEnabled(true); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +KMainWidget::~KMainWidget() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + delete prefDlg; + delete kdrop; + writeTransfers(); + writeLog(); + +#ifdef _DEBUG + sDebugOut << endl; +#endif + delete logWindow; +} + + +void KMainWidget::log(const QString & message, bool statusbar) +{ +#ifdef _DEBUG + sDebugIn <<" message= "<< message << endl; +#endif + + logWindow->logGeneral(message); + + if (statusbar) { + statusBar()->message(message, 1000); + } + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotSaveYourself() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + writeTransfers(); + ksettings.save(); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::setupGUI() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + // setup transfer list + myTransferList = new TransferList(this, "transferList"); + myTransferList->setSorting(-1); + setListFont(); + + KActionCollection *coll = actionCollection(); + + connect(myTransferList, SIGNAL(selectionChanged()), this, SLOT(slotUpdateActions())); + connect(myTransferList, SIGNAL(transferSelected(Transfer *)), this, SLOT(slotOpenIndividual())); + connect(myTransferList, SIGNAL(popupMenu(Transfer *)), this, SLOT(slotPopupMenu(Transfer *))); + + // file actions + m_paOpenTransfer = KStdAction::open(this, SLOT(slotOpenTransfer()), coll, "open_transfer"); + m_paPasteTransfer = KStdAction::paste(this, SLOT(slotPasteTransfer()), coll, "paste_transfer"); + + m_paExportTransfers = new KAction(i18n("&Export Transfer List..."), 0, this, SLOT(slotExportTransfers()), coll, "export_transfers"); + m_paImportTransfers = new KAction(i18n("&Import Transfer List..."), 0, this, SLOT(slotImportTransfers()), coll, "import_transfers"); + + m_paImportText = new KAction(i18n("Import Text &File..."), 0, this, SLOT(slotImportTextFile()), coll, "import_text"); + + m_paQuit = KStdAction::quit(this, SLOT(slotQuit()), coll, "quit"); + + + // transfer actions + m_paCopy = new KAction(i18n("&Copy URL to Clipboard"), 0, this, SLOT(slotCopyToClipboard()), coll, "copy_url"); + m_paIndividual = new KAction(i18n("&Open Individual Window"), 0, this, SLOT(slotOpenIndividual()), coll, "open_individual"); + + m_paMoveToBegin = new KAction(i18n("Move to &Beginning"), 0, this, SLOT(slotMoveToBegin()), coll, "move_begin"); + + m_paMoveToEnd = new KAction(i18n("Move to &End"), 0, this, SLOT(slotMoveToEnd()), coll, "move_end"); +#ifdef _DEBUG + sDebug << "Loading pics" << endl; +#endif + m_paResume = new KAction(i18n("&Resume"),"tool_resume", 0, this, SLOT(slotResumeCurrent()), coll, "resume"); + m_paPause = new KAction(i18n("&Pause"),"tool_pause", 0, this, SLOT(slotPauseCurrent()), coll, "pause"); + m_paDelete = new KAction(i18n("&Delete"),"editdelete", Qt::Key_Delete, this, SLOT(slotDeleteCurrent()), coll, "delete"); + m_paRestart = new KAction(i18n("Re&start"),"tool_restart", 0, this, SLOT(slotRestartCurrent()), coll, "restart"); + + m_paQueue = new KRadioAction(i18n("&Queue"),"tool_queue", 0, this, SLOT(slotQueueCurrent()), coll, "queue"); + m_paTimer = new KRadioAction(i18n("&Timer"),"tool_timer", 0, this, SLOT(slotTimerCurrent()), coll, "timer"); + m_paDelay = new KRadioAction(i18n("De&lay"),"tool_delay", 0, this, SLOT(slotDelayCurrent()), coll, "delay"); + + m_paQueue->setExclusiveGroup("TransferMode"); + m_paTimer->setExclusiveGroup("TransferMode"); + m_paDelay->setExclusiveGroup("TransferMode"); + + // options actions + m_paUseAnimation = new KToggleAction(i18n("Use &Animation"), 0, this, SLOT(slotToggleAnimation()), coll, "toggle_animation"); + m_paExpertMode = new KToggleAction(i18n("&Expert Mode"),"tool_expert", 0, this, SLOT(slotToggleExpertMode()), coll, "expert_mode"); + m_paUseLastDir = new KToggleAction(i18n("&Use-Last-Folder Mode"),"tool_uselastdir", 0, this, SLOT(slotToggleUseLastDir()), coll, "use_last_dir"); + m_paAutoDisconnect = new KToggleAction(i18n("Auto-&Disconnect Mode"),"tool_disconnect", 0, this, SLOT(slotToggleAutoDisconnect()), coll, "auto_disconnect"); + m_paAutoShutdown = new KToggleAction(i18n("Auto-S&hutdown Mode"), "tool_shutdown", 0, this, SLOT(slotToggleAutoShutdown()), coll, "auto_shutdown"); + m_paOfflineMode = new KToggleAction(i18n("&Offline Mode"),"tool_offline_mode_off", 0, this, SLOT(slotToggleOfflineMode()), coll, "offline_mode"); + m_paAutoPaste = new KToggleAction(i18n("Auto-Pas&te Mode"),"tool_clipboard", 0, this, SLOT(slotToggleAutoPaste()), coll, "auto_paste"); + + m_paPreferences = KStdAction::preferences(this, SLOT(slotPreferences()), coll); + + KStdAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), coll); + KStdAction::configureToolbars(this, SLOT(slotConfigureToolbars()), coll); + KStdAction::configureNotifications(this, SLOT(slotConfigureNotifications()), coll); + + m_menubarAction = KStdAction::showMenubar(this, SLOT(slotShowMenubar()), coll, "settings_showmenubar" ); + m_menubarAction->setChecked( !menuBar()->isHidden() ); + + // view actions + createStandardStatusBarAction(); + + m_paShowLog = new KToggleAction(i18n("Show &Log Window"),"tool_logwindow", 0, this, SLOT(slotToggleLogWindow()), coll, "toggle_log"); + m_paShowLog->setCheckedState(i18n("Hide &Log Window")); + m_paDropTarget = new KAction(i18n("Show Drop &Target"),"tool_drop_target", 0, this, SLOT(slotToggleDropTarget()), coll, "drop_target"); + m_paKonquerorIntegration = new KAction(i18n("Enable &KGet as Konqueror Download Manager"), "konqueror", 0, this, SLOT(slotKonquerorIntegration()), coll, "konqueror_integration"); + if (ksettings.b_KonquerorIntegration) { + m_paKonquerorIntegration->setText(i18n("Disable &KGet as Konqueror Download Manager")); + } + + menuHelp = new KHelpMenu(this, KGlobal::instance()->aboutData()); + KStdAction::whatsThis(menuHelp, SLOT(contextHelpActivated()), coll, "whats_this"); + + createGUI("kgetui.rc"); + + // setup statusbar + statusBar()->insertFixedItem(i18n(" Transfers: %1 ").arg(99), ID_TOTAL_TRANSFERS); + statusBar()->insertFixedItem(i18n(" Files: %1 ").arg(555), ID_TOTAL_FILES); + statusBar()->insertFixedItem(i18n(" Size: %1 KB ").arg("134.56"), ID_TOTAL_SIZE); + statusBar()->insertFixedItem(i18n(" Time: %1 ").arg(KIO::convertSeconds(0)), ID_TOTAL_TIME); + statusBar()->insertFixedItem(i18n(" %1 KB/s ").arg("123.34"), ID_TOTAL_SPEED); + + setAutoSaveSettings( "MainWindow", false /*Settings takes care of size & pos & state */ ); + + slotUpdateActions(); + + updateStatusBar(); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::setupWhatsThis() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + QString tmp; + + tmp = i18n("Resume button starts selected transfers\n" "and sets their mode to queued."); + m_paResume->setWhatsThis(tmp); + + tmp = i18n("Pause button stops selected transfers\n" "and sets their mode to delayed."); + m_paPause->setWhatsThis(tmp); + + tmp = i18n("Delete button removes selected transfers\n" "from the list."); + m_paDelete->setWhatsThis(tmp); + + tmp = i18n("Restart button is a convenience button\n" "that simply does Pause and Resume."); + m_paRestart->setWhatsThis(tmp); + + tmp = i18n("Queued button sets the mode of selected\n" "transfers to queued.\n" "\n" "It is a radio button -- you can choose between\n" "three modes."); + m_paQueue->setWhatsThis(tmp); + + tmp = i18n("Scheduled button sets the mode of selected\n" "transfers to scheduled.\n" "\n" "It is a radio button -- you can choose between\n" "three modes."); + m_paTimer->setWhatsThis(tmp); + + tmp = i18n("Delayed button sets the mode of selected\n" "transfers to delayed." "This also causes the selected transfers to stop.\n" "\n" "It is a radio button -- you can choose between\n" "three modes."); + m_paDelay->setWhatsThis(tmp); + + tmp = i18n("Preferences button opens a preferences dialog\n" "where you can set various options.\n" "\n" "Some of these options can be more easily set using the toolbar."); + m_paPreferences->setWhatsThis(tmp); + + tmp = i18n("Log window button opens a log window.\n" "The log window records all program events that occur\n" "while KGet is running."); + m_paShowLog->setWhatsThis(tmp); + + tmp = i18n("Paste transfer button adds a URL from\n" "the clipboard as a new transfer.\n" "\n" "This way you can easily copy&paste URLs between\n" "applications."); + m_paPasteTransfer->setWhatsThis(tmp); + + tmp = i18n("Expert mode button toggles the expert mode\n" "on and off.\n" "\n" "Expert mode is recommended for experienced users.\n" "When set, you will not be \"bothered\" by confirmation\n" "messages.\n" "Important!\n" "Turn it on if you are using auto-disconnect or\n" "auto-shutdown features and you want KGet to disconnect \n" "or shut down without asking."); + m_paExpertMode->setWhatsThis(tmp); + + tmp = i18n("Use last folder button toggles the\n" "use-last-folder feature on and off.\n" "\n" "When set, KGet will ignore the folder settings\n" "and put all new added transfers into the folder\n" "where the last transfer was put."); + m_paUseLastDir->setWhatsThis(tmp); + + tmp = i18n("Auto disconnect button toggles the auto-disconnect\n" "mode on and off.\n" "\n" "When set, KGet will disconnect automatically\n" "after all queued transfers are finished.\n" "\n" "Important!\n" "Also turn on the expert mode when you want KGet\n" "to disconnect without asking."); + m_paAutoDisconnect->setWhatsThis(tmp); + + tmp = i18n("Auto shutdown button toggles the auto-shutdown\n" "mode on and off.\n" "\n" "When set, KGet will quit automatically\n" "after all queued transfers are finished.\n" "Important!\n" "Also turn on the expert mode when you want KGet\n" "to quit without asking."); + m_paAutoShutdown->setWhatsThis(tmp); + + tmp = i18n("Offline mode button toggles the offline mode\n" "on and off.\n" "\n" "When set, KGet will act as if it was not connected\n" "to the Internet.\n" "\n" "You can browse offline, while still being able to add\n" "new transfers as queued."); + m_paOfflineMode->setWhatsThis(tmp); + + tmp = i18n("Auto paste button toggles the auto-paste mode\n" "on and off.\n" "\n" "When set, KGet will periodically scan the clipboard\n" "for URLs and paste them automatically."); + m_paAutoPaste->setWhatsThis(tmp); + + tmp = i18n("Drop target button toggles the window style\n" "between a normal window and a drop target.\n" "\n" "When set, the main window will be hidden and\n" "instead a small shaped window will appear.\n" "\n" "You can show/hide a normal window with a simple click\n" "on a shaped window."); + m_paDropTarget->setWhatsThis(tmp); + /* + tmp = i18n("Dock widget button toggles the window style\n" "between a normal window and a docked widget.\n" "\n" "When set, the main window will be hidden and\n" "instead a docked widget will appear on the panel.\n" "\n" "You can show/hide a normal window by simply clicking\n" "on a docked widget."); + m_paDockWindow->setWhatsThis(tmp); + + tmp = i18n("Normal window button sets\n" "\n" "the window style to normal window"); + m_paNormal->setWhatsThis(tmp); + */ + + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotConfigureToolbars() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + saveMainWindowSettings( KGlobal::config(), "MainWindow" ); + KEditToolbar edit(factory()); + connect(&edit, SIGNAL( newToolbarConfig() ), this, SLOT( slotNewToolbarConfig() )); + edit.exec(); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotNewToolbarConfig() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + createGUI("kgetui.rc"); + applyMainWindowSettings( KGlobal::config(), "MainWindow" ); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotImportTextFile() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + QString tmpFile; + QString list; + int i, j; + + KURL filename = KFileDialog::getOpenURL(ksettings.lastDirectory); + if (!filename.isValid()) + return; + + if (KIO::NetAccess::download(filename, tmpFile, this)) { + list = kFileToString(tmpFile); + KIO::NetAccess::removeTempFile(tmpFile); + } else + list = kFileToString(filename.path()); // file not accessible -> give error message + + i = 0; + while ((j = list.find('\n', i)) != -1) { + QString newtransfer = list.mid(i, j - i); + addTransfer(newtransfer); + i = j + 1; + } + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotImportTransfers() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + readTransfers(true); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::readTransfers(bool ask_for_name) +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + KURL url; + + if (ask_for_name) + url = KFileDialog::getOpenURL(ksettings.lastDirectory, i18n("*.kgt|*.kgt\n*|All Files")); + else + url.setPath( locateLocal("appdata", "transfers.kgt") ); + + readTransfersEx(url); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + +void KMainWidget::readTransfersEx(const KURL & file) +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + if (!file.isValid()) { + +#ifdef _DEBUG + sDebugOut<< " string empty" << endl; +#endif + return; + } +#ifdef _DEBUG + sDebug << "Read from file: " << file << endl; +#endif + myTransferList->readTransfers(file); + checkQueue(); + slotTransferTimeout(); + myTransferList->clearSelection(); + + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotExportTransfers() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + writeTransfers(true); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + +void KMainWidget::writeTransfers(bool ask_for_name) +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + QString str; + QString txt; + + if (ask_for_name) + txt = KFileDialog::getSaveFileName(ksettings.lastDirectory, i18n("*.kgt|*.kgt\n*|All Files")); + else + txt = locateLocal("appdata", "transfers.kgt"); + + + + if (txt.isEmpty()) + { +#ifdef _DEBUG + sDebugOut<< " because Destination File name isEmpty"<< endl; +#endif + return; + } + if (!txt.endsWith(".kgt")) + txt += ".kgt"; + +#ifdef _DEBUG + sDebug << "Writing transfers " << txt << endl; +#endif + + myTransferList->writeTransfers(txt); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::writeLog() +{ +#ifdef _DEBUG + sDebugIn << "Writing log to file : " << logFileName.ascii() << endl; +#endif + + + kCStringToFile(logWindow->getText().local8Bit(), logFileName, false, false); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotQuit() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + Transfer *item; + TransferIterator it(myTransferList); + + log(i18n("Quitting...")); + + for (; it.current(); ++it) { + item = it.current(); + if (item->getStatus() == Transfer::ST_RUNNING && !ksettings.b_expertMode) { + if (KMessageBox::warningContinueCancel(this, i18n("Some transfers are still running.\nAre you sure you want to quit KGet?"), i18n("Warning"), KStdGuiItem::quit()) != KMessageBox::Continue) { +#ifdef _DEBUG + sDebugOut << endl; +#endif + + return; + } + break; + } + } + + ksettings.save(); + +#ifdef _DEBUG + sDebugOut << endl; +#endif + + kapp->quit(); +} + + +void KMainWidget::slotResumeCurrent() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + TransferIterator it(myTransferList); + + for (; it.current(); ++it) + if (it.current()->isSelected()) + it.current()->slotResume(); + slotUpdateActions(); + + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotPauseCurrent() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + TransferIterator it(myTransferList); + + m_paPause->setEnabled(false); + m_paRestart->setEnabled(false); + update(); + + for (; it.current(); ++it) + if (it.current()->isSelected()) + it.current()->slotRequestPause(); + slotUpdateActions(); + + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + + +void KMainWidget::slotRestartCurrent() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + TransferIterator it(myTransferList); + + for (; it.current(); ++it) + if (it.current()->isSelected()) + it.current()->slotRequestRestart(); + slotUpdateActions(); + + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotDeleteCurrent() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + m_paDelete->setEnabled(false); + m_paPause->setEnabled(false); + update(); + TransferIterator it(myTransferList); + QValueList > selectedItems; + QStringList itemNames; + + while (it.current()) { + if (it.current()->isSelected()) { + selectedItems.append( QGuardedPtr( it.current() )); + itemNames.append( it.current()->getSrc().prettyURL() ); + } + ++it; + } + + if (!ksettings.b_expertMode) + { + if ( selectedItems.count() > 1 ) + { + if (KMessageBox::warningContinueCancelList(this, i18n("Are you sure you want to delete these transfers?"), + itemNames, i18n("Question"), + KStdGuiItem::del(), + QString("multiple_delete_transfer")) + != KMessageBox::Continue) + return; // keep 'em + } + else + { + if (KMessageBox::warningContinueCancel(this, i18n("Are you sure you want to delete this transfer?"), + i18n("Question"), KStdGuiItem::del(), + QString("delete_transfer")) + != KMessageBox::Continue) + return; + } + } + + // If we reach this, we want to delete all selected transfers + // Some of them might have finished in the meantime tho. Good that + // we used a QGuardedPtr :) + + int transferFinishedMeanwhile = 0; + QValueListConstIterator > lit = selectedItems.begin();; + while ( lit != selectedItems.end() ) + { + if ( *lit ) + (*lit)->slotRequestRemove(); + else + ++transferFinishedMeanwhile; + + ++lit; + } + + checkQueue(); // needed ! + + if ( !ksettings.b_expertMode && transferFinishedMeanwhile > 0 ) + KMessageBox::information(this, i18n("The transfer you wanted to delete completed before it could be deleted.", + "%n transfers you wanted to delete completed before they could be deleted.", + transferFinishedMeanwhile ), + QString::null, "completedBeforeDeletion" ); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::stopAll() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + log(i18n("Stopping all jobs"), false); + + TransferIterator it(myTransferList); + Transfer::TransferStatus Status; + for (; it.current(); ++it) { + Status = it.current()->getStatus(); + if (Status == Transfer::ST_TRYING || Status == Transfer::ST_RUNNING) + it.current()->slotStop(); + } + slotUpdateActions(); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotQueueCurrent() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + TransferIterator it(myTransferList); + + for (; it.current(); ++it) { + if (it.current()->isSelected()) { + it.current()->slotQueue(); + } + } + + // myTransferList->clearSelection(); + slotUpdateActions(); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotTimerCurrent() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + + TransferIterator it(myTransferList); + + for (; it.current(); ++it) + if (it.current()->isSelected()) + it.current()->slotRequestSchedule(); + +#ifdef _DEBUG + sDebugOut << endl; +#endif + +} + + +void KMainWidget::slotDelayCurrent() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + TransferIterator it(myTransferList); + + for (; it.current(); ++it) + if (it.current()->isSelected()) + it.current()->slotRequestDelay(); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotOpenTransfer() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + QString newtransfer; + bool ok = false; + +#ifdef _DEBUG + // newtransfer = "ftp://localhost/home/pch/test.gz"; + // newtransfer = "http://www.kernel.org/pub/linux/kernel/v2.4/linux-2.4.18.tar.gz"; + newtransfer = "ftp://darkmoon/pub/test.gz"; +#endif + + while (!ok) { + newtransfer = KInputDialog::getText(i18n("Open Transfer"), i18n("Open transfer:"), newtransfer, &ok, this); + + // user presses cancel + if (!ok) { + return; + } + + KURL url = KURL::fromPathOrURL(newtransfer); + + if (!url.isValid()) { + KMessageBox::error(this, i18n("Malformed URL:\n%1").arg(newtransfer), i18n("Error")); + ok = false; + } + } + + addTransfer(newtransfer); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + + +void KMainWidget::slotCheckClipboard() +{ +#ifdef _DEBUG + //sDebugIn << endl; +#endif + + QString clipData = QApplication::clipboard()->text( QClipboard::Clipboard ).stripWhiteSpace(); + + if (clipData != lastClipboard) { + sDebug << "New clipboard event" << endl; + + lastClipboard = clipData; + if ( lastClipboard.isEmpty() ) + return; + + KURL url = KURL::fromPathOrURL( lastClipboard ); + + if (url.isValid() && !url.isLocalFile()) + slotPasteTransfer(); + } + +#ifdef _DEBUG + //sDebugOut << endl; +#endif +} + + +void KMainWidget::slotPasteTransfer() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + QString newtransfer; + + newtransfer = QApplication::clipboard()->text(); + newtransfer = newtransfer.stripWhiteSpace(); + + if (!ksettings.b_expertMode) { + bool ok = false; + newtransfer = KInputDialog::getText(i18n("Open Transfer"), i18n("Open transfer:"), newtransfer, &ok, this); + + if (!ok) { + // cancelled +#ifdef _DEBUG + sDebugOut << endl; +#endif + return; + } + + } + + if (!newtransfer.isEmpty()) + addTransfer(newtransfer); + + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + +// destFile must be a filename, not a directory! And it will be deleted, if +// it exists already, without further notice. +void KMainWidget::addTransferEx(const KURL& url, const KURL& destFile) +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + if ( !sanityChecksSuccessful( url ) ) + return; + + KURL destURL = destFile; + + // Malformed destination url means one of two things. + // 1) The URL is empty. + // 2) The URL is only a filename, like a default (suggested) filename. + if ( !destURL.isValid() ) + { + // Setup destination + QString destDir = getSaveDirectoryFor( url.fileName() ); + bool b_expertMode = ksettings.b_expertMode; + bool bDestisMalformed = true; + + while (bDestisMalformed) + { + if (!b_expertMode) { + // open the filedialog for confirmation + KFileDialog dlg( destDir, QString::null, + 0L, "save_as", true); + dlg.setCaption(i18n("Save As")); + dlg.setOperationMode(KFileDialog::Saving); + + // Use the destination name if not empty... + if (destURL.isEmpty()) + dlg.setSelection(url.fileName()); + else + dlg.setSelection(destURL.url()); + + if ( dlg.exec() == QDialog::Rejected ) + { +#ifdef _DEBUG + sDebugOut << endl; +#endif + return; + } + else + { + destURL = dlg.selectedURL(); + ksettings.lastDirectory = destURL.directory(); + } + } + else { + // in expert mode don't open the filedialog + // if destURL is not empty, it's the suggested filename + destURL = KURL::fromPathOrURL( destDir + "/" + + ( destURL.isEmpty() ? + url.fileName() : destURL.url() )); + } + + //check if destination already exists + if(KIO::NetAccess::exists(destURL, false, this)) + { + if (KMessageBox::warningYesNo(this,i18n("Destination file \n%1\nalready exists.\nDo you want to overwrite it?").arg( destURL.prettyURL()), QString::null, i18n("Overwrite"), i18n("Do Not Overwrite") ) + == KMessageBox::Yes) + { + bDestisMalformed=false; + SafeDelete::deleteFile( destURL ); + } + else + { + b_expertMode=false; + ksettings.lastDirectory = destURL.directory(); + } + } + else + bDestisMalformed=false; + } + } + + else // destURL was given, check whether the file exists already + { + // simply delete it, the calling process should have asked if you + // really want to delete (at least khtml does) + if(KIO::NetAccess::exists(destURL, false, this)) + SafeDelete::deleteFile( destURL ); + } + + // create a new transfer item + Transfer *item = myTransferList->addTransfer(url, destURL); + KNotifyClient::event(kdock->winId(), "added", i18n("%1 has been added.").arg(url.prettyURL())); + item->updateAll(); // update the remaining fields + + if (ksettings.b_showIndividual) + item->showIndividual(); + + myTransferList->clearSelection(); + + checkQueue(); +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::addTransfers( const KURL::List& src, const QString& destDir ) +{ + QValueList urls_orig; + + for ( KURL::List::ConstIterator it = src.begin(); it != src.end(); ++it ) + { + KURLPair url; + url.src = *it; + if ( url.src.fileName().endsWith( ".kgt" ) ) + readTransfersEx(url.src); + else + urls_orig.append( url ); + } + + if ( urls_orig.isEmpty() ) + return; + + if ( urls_orig.count() == 1 ) // just one file -> ask for filename + { + KURL destFile; + + if ( !destDir.isEmpty() ) + { + // create a proper destination file from destDir + KURL destURL = KURL::fromPathOrURL( destDir ); + QString fileName = urls_orig.first().src.fileName(); + + // in case the fileName is empty, we simply ask for a filename in + // addTransferEx. Do NOT attempt to use an empty filename, that + // would be a directory (and we don't want to overwrite that!) + if ( !fileName.isEmpty() ) + { + destURL.adjustPath( +1 ); + destURL.setFileName( fileName ); + if(KIO::NetAccess::exists(destURL, false, this)) + { + if (KMessageBox::warningYesNo(this,i18n("Destination file \n%1\nalready exists.\nDo you want to overwrite it?").arg( destURL.prettyURL()), QString::null, i18n("Overwrite"), i18n("Do Not Overwrite") ) + == KMessageBox::Yes) + { + SafeDelete::deleteFile( destURL ); + destFile = destURL; + } + } + else + { + destFile = destURL; + } + } + } + + addTransferEx( urls_orig.first().src, destFile ); + return; + } + + // multiple files -> ask for directory, not for every single filename + + bool dir_accepted = false; + QValueList::Iterator it; + QValueList urls; + KURL::List urlsToDelete; + while ( !dir_accepted ) + { + urlsToDelete.clear(); + urls = urls_orig; // copy the list here, urls might be changed, yet when we return here (Cancel), + // we want to start again with the original list + dir_accepted = true; //Set to false later when Cancel is pressed + KURL dest; + if ( destDir.isEmpty() || !QFileInfo( destDir ).isDir() ) + { + if ( !destDir.isEmpty() ) + dest.setPath( destDir ); + else + dest.setPath( getSaveDirectoryFor( src.first().fileName() ) ); + + // ask in any case, when destDir is empty + if ( destDir.isEmpty() || !QFileInfo( dest.path() ).isDir() ) + { + QString dir = KFileDialog::getExistingDirectory( dest.path() ); + if ( dir.isEmpty() ) // aborted + return; + + dest.setPath( dir ); + ksettings.lastDirectory = dir; + } + } + + // dest is now finally the real destination directory for all the files + dest.adjustPath(+1); + + // create new transfer items + bool skip_all = false; + bool overwrite_all = false; + it = urls.begin(); + KIO::RenameDlg_Result result; + while ( it != urls.end() ) + { + + if ( !sanityChecksSuccessful( (*it).src ) ) + { + it = urls.erase( it ); + continue; // shouldn't we notify the user?? + } + + (*it).dest = dest; + QString fileName = (*it).src.fileName(); + if ( fileName.isEmpty() ) // simply use the full url as filename + fileName = KURL::encode_string_no_slash( (*it).src.prettyURL() ); + + (*it).dest.setFileName( fileName ); + + if( KIO::NetAccess::exists((*it).dest, false, this)) + { + QString newdest; + if (skip_all) + result = KIO::R_SKIP; + else if( overwrite_all ) + result = KIO::R_OVERWRITE; + else + { + QFileInfo finfo( (*it).dest.path() ); + QString caption = i18n( "File Already exists" ) + " - KGet"; + result = KIO::open_RenameDlg( caption, (*it).src.url(), (*it).dest.url(), KIO::RenameDlg_Mode(KIO::M_OVERWRITE|KIO::M_SKIP|KIO::M_MULTI), newdest, (KIO::filesize_t) -1, (KIO::filesize_t)finfo.size(), (time_t) -1, (time_t) -1, (time_t) -1, finfo.lastModified().toTime_t()); + } + switch (result) + { + case KIO::R_RENAME: + (*it).dest = KURL::fromPathOrURL( newdest ); + break; + case KIO::R_OVERWRITE_ALL: + overwrite_all = true; //fall through + case KIO::R_OVERWRITE: + urlsToDelete.append( (*it).dest ); + break; + case KIO::R_AUTO_SKIP: + skip_all = true; + case KIO::R_SKIP: //fall through + it = urls.erase( it ); + continue; + break; + default: // Cancel, ask again for directory + dir_accepted = false; + } + if ( !dir_accepted ) + break; + } // if(KIO::NetAccess::exists + ++it; + } // while ( it != urls.end() ) + } // while ( !dir_accepted ) + + KURL::List::Iterator it_1 = urlsToDelete.begin(); + for ( ; it_1 != urlsToDelete.end(); ++it_1 ) + { + SafeDelete::deleteFile( *it_1 ); + } + + int numdl = 0; + it = urls.begin(); + for ( ; it != urls.end(); ++it ) + { + Transfer *item = myTransferList->addTransfer((*it).src, (*it).dest); + item->updateAll(); // update the remaining fields + numdl++; + } + + KNotifyClient::event(kdock->winId(), "added", i18n("1 download has been added.", "%n downloads have been added.", numdl)); + + myTransferList->clearSelection(); + + checkQueue(); +} + +void KMainWidget::addTransfer(const QString& src) +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + if ( src.isEmpty() ) + return; + + addTransferEx( KURL::fromPathOrURL( src ) ); + +#ifdef _DEBUG + sDebugOut << endl; +#endif + +} + + +void KMainWidget::checkQueue() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + uint numRun = 0; + int status; + Transfer *item; + TransferIterator it(myTransferList); + + if (!ksettings.b_offline) { + + // count running transfers + for (; it.current(); ++it) { + status = it.current()->getStatus(); + if (status == Transfer::ST_RUNNING || status == Transfer::ST_TRYING) + numRun++; + } + sDebug << "Found " << numRun << " Running Jobs" << endl; + it.reset(); + bool isRunning; + bool isQuequed; + for (; it.current() && numRun < ksettings.maxSimultaneousConnections; ++it) { + item = it.current(); + isRunning = (item->getStatus() == Transfer::ST_RUNNING) || ((item->getStatus() == Transfer::ST_TRYING)); + + isQuequed = (item->getMode() == Transfer::MD_QUEUED || item->getMode() == Transfer::MD_NEW); + + if (!isRunning && isQuequed && !ksettings.b_offline) + { + log(i18n("Starting another queued job.")); + item->slotResume(); + numRun++; + } + } + + slotUpdateActions(); + + updateStatusBar(); + +// } else {//TODO this has to be solved different +// log(i18n("Cannot continue offline status")); + } + + it.reset(); + for (; it.current(); ++it) + { + item = it.current(); + if (item->getMode() == Transfer::MD_NEW && item->getStatus() == Transfer::ST_STOPPED) + { + item->checkCache(); + } + } +#ifdef _DEBUG + sDebugOut << endl; +#endif + +} + + +void KMainWidget::slotAnimTimeout() +{ +#ifdef _DEBUG + //sDebugIn << endl; +#endif + + bool isTransfer; + + animCounter++; + if (animCounter == myTransferList->getPhasesNum()) { + //updateStatusBar(); + animCounter = 0; + } + // update status of all items of transferList + isTransfer = myTransferList->updateStatus(animCounter); + + //if (this->isVisible()) { + updateStatusBar(); + //} +#ifdef _DEBUG + //sDebugOut << endl; +#endif + +} + + +void KMainWidget::slotTransferTimeout() +{ +#ifdef _DEBUG + //sDebugIn << endl; +#endif + + Transfer *item; + TransferIterator it(myTransferList); + + bool flag = false; + + for (; it.current(); ++it) { + item = it.current(); + if (item->getMode() == Transfer::MD_SCHEDULED && item->getStartTime() <= QDateTime::currentDateTime()) { + item->setMode(Transfer::MD_QUEUED); + flag = true; + } + } + + if (flag) { + checkQueue(); + } + + if (ksettings.b_autoDisconnect && ksettings.b_timedDisconnect && ksettings.disconnectTime <= QTime::currentTime() && ksettings.disconnectDate == QDate::currentDate()) { + onlineDisconnect(); + } + +#ifdef _DEBUG + //sDebugOut << endl; +#endif +} + + +void KMainWidget::slotAutosaveTimeout() +{ +#ifdef _DEBUG + //sDebugIn << endl; +#endif + + writeTransfers(); + +#ifdef _DEBUG + //sDebugOut << endl; +#endif +} + + +void KMainWidget::slotStatusChanged(Transfer * item, int _operation) +{ +#ifdef _DEBUG + //sDebugIn << endl; +#endif + + switch (_operation) { + + case Transfer::OP_FINISHED: + { + QString srcurl = item->getSrc().prettyURL(); + if (ksettings.b_removeOnSuccess && !item->keepDialogOpen() ) + { + delete item; + item = 0L; + } + else + item->setMode(Transfer::MD_NONE); + + if (!myTransferList->areTransfersQueuedOrScheduled()) { + // no items or only delayed and finished items in the TransferList + if (ksettings.b_autoDisconnect) + onlineDisconnect(); + + if (ksettings.b_autoShutdown) { + slotQuit(); + return; + } + KNotifyClient::event(kdock->winId(), "finishedall", i18n("All the downloads are finished.")); + } + else + { + KNotifyClient::event(kdock->winId(), "finished", i18n("%1 successfully downloaded.").arg(srcurl)); + } + + if ( item ) + item->slotUpdateActions(); + + break; + } + case Transfer::OP_RESUMED: + slotUpdateActions(); + item->slotUpdateActions(); + // play(ksettings.audioStarted); + break; + case Transfer::OP_PAUSED: + break; + case Transfer::OP_REMOVED: + delete item; + return; // checkQueue() will be called only once after all deletions + + case Transfer::OP_ABORTED: + break; + case Transfer::OP_DELAYED: + case Transfer::OP_QUEUED: + slotUpdateActions(); + item->slotUpdateActions(); + break; + case Transfer::OP_SCHEDULED: + slotUpdateActions(); + item->slotUpdateActions(); + slotTransferTimeout(); // this will check schedule times + return; // checkQueue() is called from slotTransferTimeout() + } + checkQueue(); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::dragEnterEvent(QDragEnterEvent * event) +{ +#ifdef _DEBUG + //sDebugIn << endl; +#endif + + event->accept(KURLDrag::canDecode(event) || QTextDrag::canDecode(event)); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::dropEvent(QDropEvent * event) +{ +#ifdef _DEBUG + //sDebugIn << endl; +#endif + + KURL::List list; + QString str; + + if (KURLDrag::decode(event, list)) { + addTransfers(list); + } else if (QTextDrag::decode(event, str)) { + addTransfer(str); + } + sDebugOut << endl; +} + + +void KMainWidget::slotCopyToClipboard() +{ +#ifdef _DEBUG + //sDebugIn << endl; +#endif + + Transfer *item = (Transfer *) myTransferList->currentItem(); + + if (item) { + QString url = item->getSrc().url(); + QClipboard *cb = QApplication::clipboard(); + cb->setText( url, QClipboard::Selection ); + cb->setText( url, QClipboard::Clipboard); + myTransferList->clearSelection(); + } + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotMoveToBegin() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + myTransferList->moveToBegin((Transfer *) myTransferList->currentItem()); + + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotMoveToEnd() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + myTransferList->moveToEnd((Transfer *) myTransferList->currentItem()); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotOpenIndividual() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + Transfer *item = (Transfer *) myTransferList->currentItem(); + if (item) + item->showIndividual(); + + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + +bool KMainWidget::queryClose() +{ + if( kapp->sessionSaving()) + return true; + hide(); + return false; +} + +void KMainWidget::setAutoSave() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + autosaveTimer->stop(); + if (ksettings.b_autoSave) { + autosaveTimer->start(ksettings.autoSaveInterval * 60000); + } + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + + +void KMainWidget::setAutoDisconnect() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + // disable action when we are connected permanently + m_paAutoDisconnect->setEnabled(ksettings.connectionType != PERMANENT); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + +void KMainWidget::slotPreferences() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + if ( !prefDlg ) + prefDlg = new DlgPreferences(this); + + prefDlg->show(); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + +void KMainWidget::slotConfigureNotifications() +{ + KNotifyDialog::configure(this); +} + +void KMainWidget::slotToggleLogWindow() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + b_viewLogWindow = !b_viewLogWindow; + if (b_viewLogWindow) + logWindow->show(); + else + logWindow->hide(); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotToggleAnimation() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + ksettings.b_useAnimation = !ksettings.b_useAnimation; + + if (!ksettings.b_useAnimation && animTimer->isActive()) { + animTimer->stop(); + animTimer->start(1000); + animCounter = 0; + } else { + animTimer->stop(); + animTimer->start(400); + } + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + +void KMainWidget::slotToggleOfflineMode() +{ +#ifdef _DEBUG + sDebugIn "ksettings.b_offlineMode = " << ksettings.b_offlineMode << endl; +#endif + + ksettings.b_offlineMode = !ksettings.b_offlineMode; + ksettings.b_offline=(ksettings.b_offlineMode || !b_connected); + if (ksettings.b_offline) { + log(i18n("Offline mode on.")); + stopAll(); + setCaption(i18n("Offline"), false); + m_paOfflineMode->setIconSet(LOAD_ICON("tool_offline_mode_off")); + } else { + log(i18n("Offline mode off.")); + setCaption(QString::null, false); + m_paOfflineMode->setIconSet(LOAD_ICON("tool_offline_mode_on")); + } + + m_paOfflineMode->setChecked(ksettings.b_offline); + + + slotUpdateActions(); + checkQueue(); +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotToggleExpertMode() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + ksettings.b_expertMode = !ksettings.b_expertMode; + + if (ksettings.b_expertMode) { + log(i18n("Expert mode on.")); + } else { + log(i18n("Expert mode off.")); + } + m_paExpertMode->setChecked(ksettings.b_expertMode); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotToggleUseLastDir() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + ksettings.b_useLastDir = !ksettings.b_useLastDir; + + if (ksettings.b_useLastDir) { + log(i18n("Use last folder on.")); + } else { + log(i18n("Use last folder off.")); + } + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotToggleAutoDisconnect() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + ksettings.b_autoDisconnect = !ksettings.b_autoDisconnect; + + if (ksettings.b_autoDisconnect) { + log(i18n("Auto disconnect on.")); + } else { + log(i18n("Auto disconnect off.")); + } + m_paAutoDisconnect->setChecked(ksettings.b_autoDisconnect); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotToggleAutoShutdown() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + ksettings.b_autoShutdown = !ksettings.b_autoShutdown; + + if (ksettings.b_autoShutdown) { + log(i18n("Auto shutdown on.")); + } else { + log(i18n("Auto shutdown off.")); + } + + m_paAutoShutdown->setChecked(ksettings.b_autoShutdown); + + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotToggleAutoPaste() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + ksettings.b_autoPaste = !ksettings.b_autoPaste; + + if (ksettings.b_autoPaste) { + log(i18n("Auto paste on.")); + clipboardTimer->start(1000); + } else { + log(i18n("Auto paste off.")); + clipboardTimer->stop(); + } + m_paAutoPaste->setChecked(ksettings.b_autoPaste); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotToggleDropTarget() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + m_showDropTarget = !m_showDropTarget; + + if (m_showDropTarget) { + kdrop->show(); + kdrop->updateStickyState(); + m_paDropTarget->setText(i18n("Hide Drop &Target")); + } + else { + kdrop->hide(); + m_paDropTarget->setText(i18n("Show Drop &Target")); + } + + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotKonquerorIntegration() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + bool bIsKonquiEnable=!ksettings.b_KonquerorIntegration; + ksettings.b_KonquerorIntegration=!ksettings.b_KonquerorIntegration; + KConfig cfg("konquerorrc", false, false); + cfg.setGroup("HTML Settings"); + cfg.writePathEntry("DownloadManager",QString((bIsKonquiEnable)?"kget":"")); + cfg.sync(); + if (bIsKonquiEnable) + { + m_paKonquerorIntegration->setText(i18n("Disable &KGet as Konqueror Download Manager")); + } + else + { + m_paKonquerorIntegration->setText(i18n("Enable &KGet as Konqueror Download Manager")); + } + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotPopupMenu(Transfer * item) +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + // select current item + myTransferList->setCurrentItem(item); + + // set action properties + slotUpdateActions(); + + // popup transfer menu at the position + QWidget *menu = guiFactory()->container("transfer",this); + ((QPopupMenu *) menu)->popup(QCursor::pos()); + + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::setListFont() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + myTransferList->setFont(ksettings.listViewFont); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + +void KMainWidget::slotUpdateActions() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + + // disable all signals + m_paQueue->blockSignals(true); + m_paTimer->blockSignals(true); + m_paDelay->blockSignals(true); + + // at first turn off all buttons like when nothing is selected + m_paQueue->setChecked(false); + m_paTimer->setChecked(false); + m_paDelay->setChecked(false); + + m_paQueue->setEnabled(false); + m_paTimer->setEnabled(false); + m_paDelay->setEnabled(false); + + m_paDelete->setEnabled(false); + m_paResume->setEnabled(false); + m_paPause->setEnabled(false); + m_paRestart->setEnabled(false); + + m_paCopy->setEnabled(false); + m_paIndividual->setEnabled(false); + m_paMoveToBegin->setEnabled(false); + m_paMoveToEnd->setEnabled(false); + + Transfer *item; + Transfer *first_item = 0L; + TransferIterator it(myTransferList); + int index = 0; + int totals_items = 0; + int sel_items = 0; + + for (; it.current(); ++it, ++totals_items) { + + // update action on visibles windows + if (it.current()->isVisible()) + it.current()->slotUpdateActions(); + + if (it.current()->isSelected()) { + item = it.current(); + sel_items = totals_items; + index++; // counting number of selected items + if (index == 1) { + first_item = item; // store first selected item + if (totals_items > 0) + m_paMoveToBegin->setEnabled(true); + + m_paMoveToEnd->setEnabled(true); + } else { + + m_paMoveToBegin->setEnabled(false); + m_paMoveToEnd->setEnabled(false); + } + // enable PAUSE, RESUME and RESTART only when we are online and not in offline mode +#ifdef _DEBUG + sDebug << "-->ONLINE= " << ksettings.b_offline << endl; +#endif + if (item == first_item && !ksettings.b_offline) { + switch (item->getStatus()) { + case Transfer::ST_TRYING: + case Transfer::ST_RUNNING: + m_paResume->setEnabled(false); + m_paPause->setEnabled(true); + m_paRestart->setEnabled(true); + break; + case Transfer::ST_STOPPED: + m_paResume->setEnabled(true); + m_paPause->setEnabled(false); + m_paRestart->setEnabled(false); +#ifdef _DEBUG + sDebug << "STATUS IS stopped" << item->getStatus() << endl; +#endif + break; + case Transfer::ST_FINISHED: + m_paResume->setEnabled(false); + m_paPause->setEnabled(false); + m_paRestart->setEnabled(false); + break; + + + } //end switch + + } else if (item->getStatus() != first_item->getStatus()) { + // disable all when all selected items don't have the same status + m_paResume->setEnabled(false); + m_paPause->setEnabled(false); + m_paRestart->setEnabled(false); + } + + + if (item == first_item) { + m_paDelete->setEnabled(true); + m_paCopy->setEnabled(true); + m_paIndividual->setEnabled(true); + if (item->getStatus() != Transfer::ST_FINISHED) { + m_paQueue->setEnabled(true); + m_paTimer->setEnabled(true); + m_paDelay->setEnabled(true); + + switch (item->getMode()) { + case Transfer::MD_QUEUED: +#ifdef _DEBUG + sDebug << "....................THE MODE IS MD_QUEUED " << item->getMode() << endl; +#endif + m_paQueue->setChecked(true); + break; + case Transfer::MD_SCHEDULED: +#ifdef _DEBUG + sDebug << "....................THE MODE IS MD_SCHEDULED " << item->getMode() << endl; +#endif + m_paTimer->setChecked(true); + break; + case Transfer::MD_DELAYED: +#ifdef _DEBUG + sDebug << "....................THE MODE IS MD_DELAYED " << item->getMode() << endl; +#endif + m_paDelay->setChecked(true); + break; + } + } + } else if (item->getMode() != first_item->getMode()) { + // unset all when all selected items don't have the same mode + m_paQueue->setChecked(false); + m_paTimer->setChecked(false); + m_paDelay->setChecked(false); + m_paMoveToBegin->setEnabled(false); + m_paMoveToEnd->setEnabled(false); + m_paQueue->setEnabled(false); + m_paTimer->setEnabled(false); + m_paDelay->setEnabled(false); + } + + } // when item is selected + } // loop + + + + if (sel_items == totals_items - 1) + m_paMoveToEnd->setEnabled(false); + + // enable all signals + + + + m_paQueue->blockSignals(false); + m_paTimer->blockSignals(false); + m_paDelay->blockSignals(false); + + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::updateStatusBar() +{ +#ifdef _DEBUG + //sDebugIn << endl; +#endif + + Transfer *item; + QString tmpstr; + + int totalFiles = 0; + KIO::filesize_t totalSize = 0; + int totalSpeed = 0; + unsigned int remTime = 0; + + TransferIterator it(myTransferList); + + for (; it.current(); ++it) { + item = it.current(); + if (item->getTotalSize() != 0) { + totalSize += (item->getTotalSize() - item->getProcessedSize()); + } + totalFiles++; + totalSpeed += item->getSpeed(); + + if (item->getRemainingTime() > remTime) { + remTime = item->getRemainingTime(); + } + } + + statusBar()->changeItem(i18n(" Transfers: %1 ").arg(myTransferList->childCount()), ID_TOTAL_TRANSFERS); + statusBar()->changeItem(i18n(" Files: %1 ").arg(totalFiles), ID_TOTAL_FILES); + statusBar()->changeItem(i18n(" Size: %1 ").arg(KIO::convertSize(totalSize)), ID_TOTAL_SIZE); + statusBar()->changeItem(i18n(" Time: %1 ").arg(KIO::convertSeconds(remTime)), ID_TOTAL_TIME); + statusBar()->changeItem(i18n(" %1/s ").arg(KIO::convertSize(totalSpeed)), ID_TOTAL_SPEED); + //update size for each statusbar field + statusBar()->setItemFixed(ID_TOTAL_TRANSFERS, -1); + statusBar()->setItemFixed(ID_TOTAL_FILES, -1); + statusBar()->setItemFixed(ID_TOTAL_SIZE, -1); + statusBar()->setItemFixed(ID_TOTAL_TIME, -1); + statusBar()->setItemFixed(ID_TOTAL_SPEED, -1); + + if (kdock) { + tmpstr = i18n("Transfers: %1 ").arg(myTransferList->childCount()) + + i18n("
    Files: %1 ").arg(totalFiles) + + i18n("
    Size: %1 ").arg(KIO::convertSize(totalSize)) + + i18n("
    Time: %1 ").arg(KIO::convertSeconds(remTime)) + + i18n("
    Speed: %1/s").arg(KIO::convertSize(totalSpeed)); + kdock->updateToolTip( tmpstr ); + //trayicon changes if download is in progress + if (totalSpeed == 0) + { + kdock->changeIcon( "kget_dock" ); + } + else + { + kdock->changeIcon( "kget_dock_download" ); + } + } +#ifdef _DEBUG + //sDebugOut << endl; +#endif +} + + +void KMainWidget::onlineDisconnect() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + if (!b_connected) { + return; + } + + if (!ksettings.b_expertMode) { + if (KMessageBox::questionYesNo(this, i18n("Do you really want to disconnect?"), + i18n("Question"), + i18n("Disconnect"), i18n("Stay Connected"), + "kget_AutoOnlineDisconnect") + != KMessageBox::Yes) { + return; + } + } + log(i18n("Disconnecting...")); + system(QFile::encodeName(ksettings.disconnectCommand)); + +#ifdef _DEBUG + sDebugOut << endl; +#endif +} + + +void KMainWidget::slotCheckConnection() +{ +#ifdef _DEBUG + //sDebugIn << endl; +#endif + bool old = b_connected; + checkOnline(); + if (b_connected != old) { + if (b_connected) { + log(i18n("We are online.")); + setCaption(QString::null, false); + ksettings.b_offline=ksettings.b_offlineMode; + checkQueue(); + } else { + log(i18n("We are offline.")); + setCaption(i18n("Offline"), false); + ksettings.b_offline=true; + stopAll(); + } + } +#ifdef _DEBUG + //sDebugOut << endl; +#endif +} + + +void KMainWidget::checkOnline() +{ +#ifdef _DEBUG + //sDebugIn << endl; +#endif + + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifreq)); + + // setup the device name according to the type of connection and link number + snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d", ConnectionDevices[ksettings.connectionType].ascii(), ksettings.linkNumber); + + bool flag = false; + + if (ksettings.connectionType != PERMANENT) { + // get the flags for particular device + if (ioctl(_sock, SIOCGIFFLAGS, &ifr) < 0) { + flag = true; + b_connected = false; + } else if (ifr.ifr_flags == 0) { +#ifdef _DEBUG + sDebug << "Can't get flags from interface " << ifr.ifr_name << endl; +#endif + b_connected = false; + } else if (ifr.ifr_flags & IFF_UP) { // if (ifr.ifr_flags & IFF_RUNNING) + b_connected = true; + } else { + b_connected = false; + } + } else { + b_connected = true; // PERMANENT connection + } + + m_paOfflineMode->setEnabled(b_connected); + + if (flag) { +#ifdef _DEBUG + sDebug << "Unknown interface " << ifr.ifr_name << endl; +#endif + } +#ifdef _DEBUG + //sDebugOut << endl; +#endif +} + + +// Helper method for opening device socket + + + + +static int sockets_open() +{ +#ifdef _DEBUG + sDebugIn << endl; +#endif + + inet_sock = socket(AF_INET, SOCK_DGRAM, 0); +#ifdef AF_IPX + ipx_sock = socket(AF_IPX, SOCK_DGRAM, 0); +#else + ipx_sock = -1; +#endif + +#ifdef AF_AX25 + ax25_sock = socket(AF_AX25, SOCK_DGRAM, 0); +#else + ax25_sock = -1; +#endif + + ddp_sock = socket(AF_APPLETALK, SOCK_DGRAM, 0); + /* + * Now pick any (exisiting) useful socket family for generic queries + */ + + sDebug << "<<< sockets_open () " << endl; + if (inet_sock != -1) + return inet_sock; + if (ipx_sock != -1) + return ipx_sock; + if (ax25_sock != -1) + return ax25_sock; + /* + * If this is -1 we have no known network layers and its time to jump. + */ + +#ifdef _DEBUG + sDebugOut << endl; +#endif + + return ddp_sock; +} + + +/** No descriptions */ +void KMainWidget::customEvent(QCustomEvent * _e) +{ +#ifdef _DEBUG + //sDebugIn << endl; +#endif + + + SlaveEvent *e = (SlaveEvent *) _e; + unsigned int result = e->getEvent(); + + switch (result) { + + // running cases.. + case Slave::SLV_PROGRESS_SIZE: + e->getItem()->slotProcessedSize(e->getData()); + break; + case Slave::SLV_PROGRESS_SPEED: + e->getItem()->slotSpeed(e->getData()); + break; + + case Slave::SLV_RESUMED: + e->getItem()->slotExecResume(); + break; + + // stopping cases + case Slave::SLV_FINISHED: + e->getItem()->slotFinished(); + break; + case Slave::SLV_PAUSED: + e->getItem()->slotExecPause(); + break; + case Slave::SLV_SCHEDULED: + e->getItem()->slotExecSchedule(); + break; + + case Slave::SLV_DELAYED: + e->getItem()->slotExecDelay(); + break; + case Slave::SLV_CONNECTED: + e->getItem()->slotExecConnected(); + break; + + case Slave::SLV_CAN_RESUME: + e->getItem()->slotCanResume((bool) e->getData()); + break; + + case Slave::SLV_TOTAL_SIZE: + e->getItem()->slotTotalSize(e->getData()); + break; + + case Slave::SLV_ERROR: + e->getItem()->slotExecError(); + break; + + case Slave::SLV_BROKEN: + e->getItem()->slotExecBroken(); + break; + + case Slave::SLV_REMOVED: + e->getItem()->slotExecRemove(); + break; + + case Slave::SLV_INFO: + e->getItem()->logMessage(e->getMsg()); + break; + + case Slave::SLV_NOTINCACHE: + e->getItem()->NotInCache(); + break; + default: +#ifdef _DEBUG + sDebug << "Unknown Result..die" << result << endl; +#endif + assert(0); + } + + +#ifdef _DEBUG + //sDebugOut << endl; +#endif +} + +QString KMainWidget::getSaveDirectoryFor( const QString& filename ) const +{ + // first set destination directory to current directory ( which is also last used ) + QString destDir = ksettings.lastDirectory; + + if (!ksettings.b_useLastDir) { + // check wildcards for default directory + DirList::Iterator it; + for (it = ksettings.defaultDirList.begin(); it != ksettings.defaultDirList.end(); ++it) { + QRegExp rexp((*it).extRegexp); + + rexp.setWildcard(true); + + if ((rexp.search( filename )) != -1) { + destDir = (*it).defaultDir; + break; + } + } + } + + return destDir; +} + +bool KMainWidget::sanityChecksSuccessful( const KURL& url ) +{ + if (!url.isValid() || !KProtocolInfo::supportsReading( url ) ) + { + if (!ksettings.b_expertMode) + KMessageBox::error(this, i18n("Malformed URL:\n%1").arg(url.prettyURL()), i18n("Error")); + + return false; + } + // if we find this URL in the list + Transfer *transfer = myTransferList->find( url ); + if ( transfer ) + { + if ( transfer->getStatus() != Transfer::ST_FINISHED ) + { + if ( !ksettings.b_expertMode ) + { + KMessageBox::error(this, i18n("Already saving URL\n%1").arg(url.prettyURL()), i18n("Error")); + } + + transfer->showIndividual(); + return false; + } + + else // transfer is finished, ask if we want to download again + { + if ( ksettings.b_expertMode || + (KMessageBox::questionYesNo(this, i18n("Already saved URL\n%1\nDownload again?").arg(url.prettyURL()),i18n("Question"),i18n("Download Again"),KStdGuiItem::cancel() ) + == KMessageBox::Yes) ) + { + transfer->slotRequestRemove(); + checkQueue(); + return true; + } + } + + return false; + } + + // why restrict this to ftp and http? (pfeiffer) +// // don't download file URL's TODO : uncomment? +// if (url.protocol() == "http" && url.protocol() != "ftp") { +// KMessageBox::error(this, i18n("File protocol not accepted!\n%1").arg(url.prettyURL()), i18n("Error")); +// #ifdef _DEBUG +// sDebugOut << endl; +// #endif +// return false; +// } + + return true; +} + +bool KMainWidget::isDropTargetVisible() const +{ + return m_showDropTarget; +} + +void KMainWidget::setDropTargetVisible( bool setVisible ) +{ + if ( setVisible != isDropTargetVisible() ) + { + m_paDropTarget->activate(); + } +} + +void KMainWidget::setOfflineMode( bool offline ) +{ + if ( ksettings.b_offlineMode != offline ) + slotToggleOfflineMode(); +} + +bool KMainWidget::isOfflineMode() const +{ + return ksettings.b_offlineMode; +} + +void KMainWidget::activateDropTarget() +{ + setDropTargetVisible( true ); +} + +void KMainWidget::slotShowMenubar() +{ + if(m_menubarAction->isChecked()) + menuBar()->show(); + else + menuBar()->hide(); +} + +#include "kmainwidget.moc" diff --git a/kget/kmainwidget.h b/kget/kmainwidget.h new file mode 100644 index 00000000..ce7c9ea8 --- /dev/null +++ b/kget/kmainwidget.h @@ -0,0 +1,232 @@ +/*************************************************************************** +* kmainwidget.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#ifndef _KMAINWIDGET_H_ +#define _KMAINWIDGET_H_ + +#include +#include +#include +#include "common.h" + +#include "kget_iface.h" + +class KAction; +//class KToggleAction; +class KRadioAction; + +class DockWidget; +class DropTarget; +class LogWindow; +class DlgPreferences; + +class Transfer; +class TransferList; +class Settings; + + +class KMainWidget:public KMainWindow, virtual public KGetIface +{ + +Q_OBJECT + +public: + enum StatusbarFields { ID_TOTAL_TRANSFERS = 1, ID_TOTAL_FILES, ID_TOTAL_SIZE, + ID_TOTAL_TIME , ID_TOTAL_SPEED }; + + KMainWidget(bool bShowMain = false); + ~KMainWidget(); + + void addTransfer( const QString& src ); + void addTransferEx( const KURL& url, + const KURL& destFile = KURL()); + + // dcop interface + virtual void addTransfers( const KURL::List& src, const QString& destDir = QString::null ); + virtual bool isDropTargetVisible() const; + virtual void setDropTargetVisible( bool setVisible ); + + + void checkQueue(); + + void setListFont(); + void setAutoSave(); + void setAutoDisconnect(); + + LogWindow *logwin()const { return logWindow;} + friend class Settings; + + // Actions + KToggleAction *m_paShowLog; + KToggleAction *m_menubarAction; + KAction *m_paPreferences; + KAction *m_paQuit; + bool b_viewLogWindow; + + void readTransfersEx(const KURL & url); + + void activateDropTarget(); + +public slots: + void slotPasteTransfer(); + void slotToggleLogWindow(); + void slotPreferences(); + void slotConfigureNotifications(); + void slotToggleExpertMode(); + void slotToggleOfflineMode(); + void slotToggleUseLastDir(); + void slotToggleAutoDisconnect(); + void slotToggleAutoShutdown(); + void slotToggleAutoPaste(); + void slotToggleDropTarget(); + void slotToggleAnimation(); + void slotUpdateActions(); + void slotKonquerorIntegration(); + +protected slots: + void slotQuit(); + + void slotOpenTransfer(); + void slotExportTransfers(); + void slotImportTransfers(); + void slotImportTextFile(); + + void slotSaveYourself(); + void slotCheckConnection(); + + + void slotStatusChanged(Transfer * item, int _operation); + + void slotResumeCurrent(); + void slotPauseCurrent(); + void slotDeleteCurrent(); + void slotRestartCurrent(); + + void slotQueueCurrent(); + void slotTimerCurrent(); + void slotDelayCurrent(); + + void slotOpenIndividual(); + + void slotAnimTimeout(); + void slotTransferTimeout(); + void slotAutosaveTimeout(); + + void slotMoveToBegin(); + void slotMoveToEnd(); + + void slotCopyToClipboard(); + void slotCheckClipboard(); + + void slotConfigureToolbars(); + void slotNewToolbarConfig(); + void slotShowMenubar(); + + void slotPopupMenu(Transfer * item); + +protected: + virtual void setOfflineMode( bool online ); + virtual bool isOfflineMode() const; + virtual bool queryClose(); + void writeLog(); + + // drag and drop + virtual void dragEnterEvent(QDragEnterEvent *); + virtual void dropEvent(QDropEvent *); + + void readTransfers(bool ask_for_name = false); + void writeTransfers(bool ask_for_name = false); + + + void setupGUI(); + void setupWhatsThis(); + + void updateStatusBar(); + + // some flags + bool b_connected; + bool b_viewPreferences; + + // utility functions + void onlineDisconnect(); + void checkOnline(); + void stopAll(); + void log(const QString & message, bool statusbar = true); + + /** No descriptions */ + virtual void customEvent(QCustomEvent * e); + + // various timers + QTimer *animTimer; // animation timer + QTimer *connectionTimer; // timer that checks whether we are online + QTimer *transferTimer; // timer for scheduled transfers + QTimer *autosaveTimer; // timer for autosaving transfer list + QTimer *clipboardTimer; // timer for checking clipboard - autopaste function + + QString logFileName; + + + +private: + QString getSaveDirectoryFor( const QString& filename ) const; + bool sanityChecksSuccessful( const KURL& url ); + + TransferList * myTransferList; + KHelpMenu *menuHelp; + + LogWindow *logWindow; + DlgPreferences *prefDlg; + DockWidget *kdock; + + QString lastClipboard; + + uint animCounter; + + int _sock; + + // Actions + KAction *m_paOpenTransfer, *m_paPasteTransfer, *m_paExportTransfers, *m_paImportTransfers; + KAction *m_paImportText; + + KAction *m_paMoveToBegin, *m_paMoveToEnd, *m_paCopy, *m_paIndividual; + KAction *m_paResume, *m_paPause, *m_paDelete, *m_paRestart; + KRadioAction *m_paQueue, *m_paTimer, *m_paDelay; + + KToggleAction *m_paUseAnimation; + KToggleAction *m_paExpertMode, *m_paUseLastDir, *m_paOfflineMode; + KToggleAction *m_paAutoDisconnect, *m_paAutoShutdown, *m_paAutoPaste; + + KAction *m_paDropTarget; + KAction *m_paKonquerorIntegration; + bool m_showDropTarget; + +}; + +extern KMainWidget *kmain; +extern DropTarget *kdrop; + +#endif // _KMAINWIDGET_H_ diff --git a/kget/logwindow.cpp b/kget/logwindow.cpp new file mode 100644 index 00000000..5d6f77ad --- /dev/null +++ b/kget/logwindow.cpp @@ -0,0 +1,218 @@ +/*************************************************************************** +* logwindow.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#include +#include + +#include +#include +#include + +#include "transfer.h" +#include "kmainwidget.h" +#include "logwindow.h" + +#include +#include + +// // Replace regular space with nbsp +// QString replaceSpaces(const QString &str) { +// QString res = str; +// res.simplifyWhiteSpace(); + +// int pos; +// while ( (pos = res.find(' ')) != -1) { +// res.replace(pos, 1, new QChar( 0x00a0 ), 1); +// } + +// return res; +// } + + +static QString removeHTML(const QString & str) +{ + QString res = str; + int pos; + + // replace
    with a newline + while ((pos = res.find("
    ")) != -1) { + res.replace(pos, 4, "\n"); + } + + // remove all tags + while ((pos = res.find('<')) != -1) { + int pos2 = res.find('>', pos); + + if (pos2 == -1) { + pos2 = res.length() + 1; + } + res.remove(pos, pos2 - pos + 1); + } + + return res; +} + + +SeparatedLog::SeparatedLog(QWidget * parent):QWidget(parent) +{ + idSelected = 0; + + QGridLayout *topGridLayout = new QGridLayout(this, 1, 2, 0, KDialog::spacingHint()); + + topGridLayout->setRowStretch(0, 5); + + topGridLayout->setColStretch(0, 3); + topGridLayout->setColStretch(1, 10); + + lv_log = new QListView(this); + lv_log->setMultiSelection(false); + lv_log->setAllColumnsShowFocus(true); + lv_log->setSorting(-1); + + lv_log->addColumn(i18n("Id"), 40); + lv_log->addColumn(i18n("Name"), 100); + + topGridLayout->addWidget(lv_log, 0, 0); + + connect(lv_log, SIGNAL(selectionChanged(QListViewItem *)), SLOT(transferSelected(QListViewItem *))); + + ml_log = new QTextEdit(this); + ml_log->setTextFormat(LogText); + ml_log->setMinimumSize(300, 200); + ml_log->setVScrollBarMode(QScrollView::Auto); + ml_log->setWordWrap(QTextEdit::NoWrap); + + topGridLayout->addWidget(ml_log, 0, 1); +} + + +void SeparatedLog::addLog(uint id, const QString & filename, const QString & message) +{ + if (!trMap.contains(id)) { + trMap.insert(id, message); + QListViewItem *last=lv_log->lastItem(); + new QListViewItem(lv_log, last); + last=lv_log->lastItem(); + last->setText(0, QString::number(id)); + last->setText(1, filename); + // if I don't do this, ID#10 gets sorted between ID#1 and ID#2, ugly. + } else { + trMap[id] += ("\n"+message); + } + + if (idSelected == id) { + refresh(); + } +} + + +void SeparatedLog::transferSelected(QListViewItem * item) +{ + if (item) { + idSelected = item->text(0).toUInt(); + // kapp->lock(); + ml_log->setText(trMap[idSelected]); + // kapp->unlock(); + } +} + + +void SeparatedLog::refresh() +{ + if (idSelected > 0) { + // kapp->lock(); + ml_log->setText(trMap[idSelected]); + // kapp->unlock(); + } +} + + +//////////////////////// + + + + +LogWindow::LogWindow():KDialogBase(Tabbed, i18n("Log Window"), Close, Close, 0, "", false) +{ + + // add pages + QFrame *page = addPage(i18n("Mixed")); + QVBoxLayout *topLayout = new QVBoxLayout(page, 0, spacingHint()); + + mixed_log = new QTextEdit(page); + mixed_log->setTextFormat(LogText); + mixed_log->setVScrollBarMode(QScrollView::Auto); + mixed_log->setWordWrap(QTextEdit::NoWrap); + topLayout->addWidget(mixed_log); + + page = addPage(i18n("Separated")); + topLayout = new QVBoxLayout(page, 0, spacingHint()); + sep_log = new SeparatedLog(page); + topLayout->addWidget(sep_log); + + connect(this, SIGNAL(closeClicked()), this, SLOT(close())); + + // resize( 500, 300 ); +} + + +void LogWindow::closeEvent(QCloseEvent *e) +{ + kmain->m_paShowLog->setChecked(false); + kmain->b_viewLogWindow = false; + KDialogBase::closeEvent( e ); +} + + +void LogWindow::logGeneral(const QString & message) +{ + QString tmps = "" + QTime::currentTime().toString() + " : " + message + ""; + + mixed_log->append(tmps); +} + + +void LogWindow::logTransfer(uint id, const QString & filename, const QString & message) +{ + QString mixed_msg, single_msg, job_id; + + job_id = QString("Job[%1] : ").arg(id); + mixed_msg = "" + QTime::currentTime().toString() + " : " + job_id + message; + + single_msg = "" + QTime::currentTime().toString() + " : " + message; + + mixed_log->append(mixed_msg); + sep_log->addLog(id, filename, single_msg); +} + + +QString LogWindow::getText() const +{ + return removeHTML(mixed_log->text()); +} + +#include "logwindow.moc" diff --git a/kget/logwindow.h b/kget/logwindow.h new file mode 100644 index 00000000..a2aa13d5 --- /dev/null +++ b/kget/logwindow.h @@ -0,0 +1,85 @@ +/*************************************************************************** +* logwindow.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#ifndef _LOGWINDOW_H +#define _LOGWINDOW_H + +#include + +#include + +class QListView; +class QTextEdit; + + +class SeparatedLog:public QWidget +{ + +Q_OBJECT public: + SeparatedLog(QWidget * parent); + ~SeparatedLog() + {} + void addLog(uint id, const QString & filename, const QString & message); + void refresh(); + +protected slots: + void transferSelected(QListViewItem * item); + +private: + QListView * lv_log; + QTextEdit *ml_log; + + typedef QMap < uint, QString > TransferMap; + TransferMap trMap; + + uint idSelected; +}; + + +class LogWindow:public KDialogBase +{ + +Q_OBJECT public: + LogWindow(); + ~LogWindow() + {} + void logGeneral(const QString & message); + QString getText() const; + +public slots: + void logTransfer(uint id, const QString & filename, const QString & message); + +protected: + void closeEvent(QCloseEvent *); + +private: + QTextEdit * mixed_log; + SeparatedLog *sep_log; +}; + + +#endif // _LOGWINDOW_H diff --git a/kget/main.cpp b/kget/main.cpp new file mode 100644 index 00000000..cd3341b5 --- /dev/null +++ b/kget/main.cpp @@ -0,0 +1,225 @@ +/*************************************************************************** +* main.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "kmainwidget.h" +#include "version.h" + + + +static const char description[] = I18N_NOOP("An advanced download manager for KDE"); + +static const char version[] = KGETVERSION; + + +static KCmdLineOptions option[] = { + { "showDropTarget", I18N_NOOP("Start KGet with drop target"), 0 }, + {"+[URL(s)]", I18N_NOOP("URL(s) to download"), 0}, + KCmdLineLastOption + }; + +static void cleanup(void); +static void setSignalHandler(void (*handler) (int)); + +//static msg_handler oldMsgHandler = 0L; + +//----------------------------------------------------------------------------- +// Crash recovery signal handler +static void signalHandler(int sigId) +{ + fprintf(stderr, "*** KGet got signal %d\n", sigId); + + if (sigId != SIGSEGV && kmain) { + fprintf(stderr, "*** KGet saving data\n"); + delete kmain; + } + // If Kget crashes again below this line we consider the data lost :-| + // Otherwise Kget will end in an infinite loop. + setSignalHandler(SIG_DFL); + cleanup(); + exit(1); +} + + +//----------------------------------------------------------------------------- +static void setSignalHandler(void (*handler) (int)) +{ + signal(SIGSEGV, handler); + signal(SIGKILL, handler); + signal(SIGTERM, handler); + signal(SIGHUP, handler); + signal(SIGFPE, handler); + signal(SIGABRT, handler); + + // catch also the keyboard interrupt + signal(SIGINT, handler); +} + + +static void cleanup(void) +{ + qInstallMsgHandler(0L /*oldMsgHandler*/); +// QString cmd; +} + + +class KGetApp : public KUniqueApplication +{ +private: + KMainWidget *kmainwidget; + +public: + KGetApp() : KUniqueApplication() + { +#ifdef _DEBUG + sDebugIn << endl; +#endif + + kmainwidget=0; + +#ifdef _DEBUG + sDebugOut << endl; +#endif + } + + ~KGetApp() + { +#ifdef _DEBUG + sDebugIn << endl; +#endif + delete kmainwidget; +#ifdef _DEBUG + sDebugOut << endl; +#endif + } + + + int newInstance() + { +#ifdef _DEBUG + sDebugIn <<"kmainwidget="<count()>0) + kmainwidget=new KMainWidget(true); + else + kmainwidget=new KMainWidget(); + setMainWidget(kmain); + } + + else + KStartupInfo::setNewStartupId( mainWidget(), kapp->startupId()); + + if (args->isSet("showDropTarget")) + kmain->activateDropTarget(); + + if (args->count()==1) + { +#ifdef _DEBUG + sDebug <<"args(0)= "<arg(0) << endl; +#endif + QString txt(args->arg(0)); + if ( txt.endsWith( ".kgt" ) ) + kmain->readTransfersEx(KURL::fromPathOrURL( txt )); + else + kmain->addTransferEx( KURL::fromPathOrURL( txt ), + KURL()); + } + else if(args->count()>=2) + { + KURL::List urls; + for( int i=0; i < args->count(); ++i){ + urls.append(KURL::fromPathOrURL( args->arg(i))); + } + + // Sometimes valid filenames are not recognised by KURL::isLocalFile(), they are marked as invalid then + if ( args->count()==2 && ( urls.last().isLocalFile() || !urls.last().isValid())) + { + kmain->addTransferEx( urls.first(), urls.last() ); + } + else + { + kmain->addTransfers( urls, QString() ); + } + } + args->clear(); + +#ifdef _DEBUG + sDebugOut << endl; +#endif + + return 0; + } +}; + + +///////////////////////////////////////////////////////////////// + +int main(int argc, char *argv[]) +{ + KAboutData aboutData("kget", I18N_NOOP("KGet"), version, description, KAboutData::License_GPL, "(C) 2001 - 2002, Patrick Charbonnier \n(C) 2002, Carsten Pfeiffer\n(C) 1998 - 2000, Matej Koss", "kget@kde.org", 0); + + aboutData.addAuthor("Patrick Charbonnier", 0, "pch@freeshell.org"); + aboutData.addAuthor("Carsten Pfeiffer", 0, "pfeiffer@kde.org"); + aboutData.addAuthor("Matej Koss", 0); + + + KCmdLineArgs::init(argc, argv, &aboutData); + KCmdLineArgs::addCmdLineOptions(option); + + KGetApp::addCmdLineOptions(); + + if (!KGetApp::start()) { + fprintf(stderr, "kget is already running!\n"); + return 0; + } + + KGetApp kApp; + +// disabling he custom signal handler, so at least we have the backtraces for +// crashes... +// setSignalHandler(signalHandler); + kApp.exec(); + + cleanup(); +} diff --git a/kget/pics/Makefile.am b/kget/pics/Makefile.am new file mode 100644 index 00000000..6ca36d36 --- /dev/null +++ b/kget/pics/Makefile.am @@ -0,0 +1,11 @@ +pics_DATA = connect0.png connect1.png connect2.png connect3.png \ + connect4.png connect5.png connect6.png connect7.png \ + try0.png try1.png try2.png try3.png \ + try4.png try5.png try6.png try7.png \ + md_delayed.png md_finished.png md_queued.png md_scheduled.png \ + retrying.png target.png + +picsdir = $(kde_datadir)/kget/pics + +EXTRA_DIST = $(pics_DATA) + diff --git a/kget/pics/connect0.png b/kget/pics/connect0.png new file mode 100644 index 00000000..658ce773 Binary files /dev/null and b/kget/pics/connect0.png differ diff --git a/kget/pics/connect1.png b/kget/pics/connect1.png new file mode 100644 index 00000000..9d2e472a Binary files /dev/null and b/kget/pics/connect1.png differ diff --git a/kget/pics/connect2.png b/kget/pics/connect2.png new file mode 100644 index 00000000..3e948c8d Binary files /dev/null and b/kget/pics/connect2.png differ diff --git a/kget/pics/connect3.png b/kget/pics/connect3.png new file mode 100644 index 00000000..1e700ddf Binary files /dev/null and b/kget/pics/connect3.png differ diff --git a/kget/pics/connect4.png b/kget/pics/connect4.png new file mode 100644 index 00000000..5f651e5f Binary files /dev/null and b/kget/pics/connect4.png differ diff --git a/kget/pics/connect5.png b/kget/pics/connect5.png new file mode 100644 index 00000000..eb951247 Binary files /dev/null and b/kget/pics/connect5.png differ diff --git a/kget/pics/connect6.png b/kget/pics/connect6.png new file mode 100644 index 00000000..23b7ef14 Binary files /dev/null and b/kget/pics/connect6.png differ diff --git a/kget/pics/connect7.png b/kget/pics/connect7.png new file mode 100644 index 00000000..02ddd04f Binary files /dev/null and b/kget/pics/connect7.png differ diff --git a/kget/pics/md_delayed.png b/kget/pics/md_delayed.png new file mode 100644 index 00000000..17d135a9 Binary files /dev/null and b/kget/pics/md_delayed.png differ diff --git a/kget/pics/md_finished.png b/kget/pics/md_finished.png new file mode 100644 index 00000000..5b1dac57 Binary files /dev/null and b/kget/pics/md_finished.png differ diff --git a/kget/pics/md_queued.png b/kget/pics/md_queued.png new file mode 100644 index 00000000..93e004b7 Binary files /dev/null and b/kget/pics/md_queued.png differ diff --git a/kget/pics/md_scheduled.png b/kget/pics/md_scheduled.png new file mode 100644 index 00000000..79f751df Binary files /dev/null and b/kget/pics/md_scheduled.png differ diff --git a/kget/pics/retrying.png b/kget/pics/retrying.png new file mode 100644 index 00000000..7c107550 Binary files /dev/null and b/kget/pics/retrying.png differ diff --git a/kget/pics/target.png b/kget/pics/target.png new file mode 100644 index 00000000..e71e668e Binary files /dev/null and b/kget/pics/target.png differ diff --git a/kget/pics/try0.png b/kget/pics/try0.png new file mode 100644 index 00000000..6d0cf4d1 Binary files /dev/null and b/kget/pics/try0.png differ diff --git a/kget/pics/try1.png b/kget/pics/try1.png new file mode 100644 index 00000000..5041101c Binary files /dev/null and b/kget/pics/try1.png differ diff --git a/kget/pics/try2.png b/kget/pics/try2.png new file mode 100644 index 00000000..f6f67818 Binary files /dev/null and b/kget/pics/try2.png differ diff --git a/kget/pics/try3.png b/kget/pics/try3.png new file mode 100644 index 00000000..b9af2b38 Binary files /dev/null and b/kget/pics/try3.png differ diff --git a/kget/pics/try4.png b/kget/pics/try4.png new file mode 100644 index 00000000..0948b759 Binary files /dev/null and b/kget/pics/try4.png differ diff --git a/kget/pics/try5.png b/kget/pics/try5.png new file mode 100644 index 00000000..f6f67818 Binary files /dev/null and b/kget/pics/try5.png differ diff --git a/kget/pics/try6.png b/kget/pics/try6.png new file mode 100644 index 00000000..5041101c Binary files /dev/null and b/kget/pics/try6.png differ diff --git a/kget/pics/try7.png b/kget/pics/try7.png new file mode 100644 index 00000000..9acacca1 Binary files /dev/null and b/kget/pics/try7.png differ diff --git a/kget/safedelete.cpp b/kget/safedelete.cpp new file mode 100644 index 00000000..c4cb6915 --- /dev/null +++ b/kget/safedelete.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +#include + +#include "safedelete.h" + +bool SafeDelete::deleteFile( const KURL& url ) +{ + if ( url.isLocalFile() ) + { + QFileInfo info( url.path() ); + if ( info.isDir() ) + { + KMessageBox::information(0L, + i18n("Not deleting\n%1\nas it is a " + "directory.").arg( url.prettyURL() ), + i18n("Not Deleted")); + return false; + } + + KIO::NetAccess::del( url, 0L ); + return true; + } + + else + KMessageBox::information( 0L, + i18n("Not deleting\n%1\nas it is not a local" + " file.").arg( url.prettyURL() ), + i18n("Not Deleted") ); + + return false; +} diff --git a/kget/safedelete.h b/kget/safedelete.h new file mode 100644 index 00000000..c93ebb67 --- /dev/null +++ b/kget/safedelete.h @@ -0,0 +1,19 @@ +/**************************************************************************** +** $Id$ +** +** Copyright (C) 2002 Carsten Pfeiffer +** +****************************************************************************/ + +#ifndef SAFEDELETE_H +#define SAFEDELETE_H + +class KURL; + +class SafeDelete +{ +public: + static bool deleteFile( const KURL& url ); +}; + +#endif // SAFEDELETE_H diff --git a/kget/settings.cpp b/kget/settings.cpp new file mode 100644 index 00000000..da066a6a --- /dev/null +++ b/kget/settings.cpp @@ -0,0 +1,287 @@ +/*************************************************************************** +* settings.cpp +* ------------------- +* +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* +* email : pch@freeshell.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. + * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "kmainwidget.h" +#include "transferlist.h" +#include "droptarget.h" +#include "settings.h" +#include "common.h" +#include "version.h" + +static const unsigned long DEFAULT_DOCK_STATE = (NET::SkipTaskbar | NET::StaysOnTop); + +QString ConnectionDevices[6] = { + "", + "eth", + "plip", + "slip", + "ppp", + "isdn" + }; + + +void +Settings::load() +{ + sDebug << "Loading settings" << endl; + + KConfig *config = kapp->config(); + + // read system options + config->setGroup("System"); + + // read connection options + config->setGroup("Connection"); + + b_reconnectOnError = config->readBoolEntry("ReconnectOnError", DEF_ReconnectOnError); + reconnectTime = config->readNumEntry("ReconnectTime", DEF_ReconnectTime); + reconnectRetries = config->readNumEntry("ReconnectRetries", DEF_ReconnectRetries); + b_reconnectOnBroken = config->readBoolEntry("ReconnectOnBroken", DEF_ReconnectOnBroken); + + timeoutData = config->readNumEntry("TimeoutData", DEF_TimeoutData); + timeoutDataNoResume = config->readNumEntry("TimeoutDataNoResume", DEF_TimeoutDataNoResume); + + connectionType = config->readNumEntry("ConnectionType", DEF_ConnectionType); + linkNumber = config->readNumEntry("LinkNumber", DEF_LinkNumber); + b_offlineMode = config->readBoolEntry("OfflineMode", DEF_OfflineMode); + + // read automation options + config->setGroup("Automation"); + + b_autoSave = config->readBoolEntry("AutoSave", DEF_AutoSave); + autoSaveInterval = config->readNumEntry("AutoSaveInterval", DEF_AutoSaveInterval); + + b_autoDisconnect = config->readBoolEntry("AutoDisconnect", DEF_AutoDisconnect); + disconnectCommand = config->readPathEntry("DisconnectCommand", DEF_DisconnectCommand); + + b_timedDisconnect = config->readBoolEntry("TimedDisconnect", DEF_TimedDisconnect); + disconnectTime.setHMS(config->readNumEntry("DisconnectTimeHour"), config->readNumEntry("DisconnectTimeMinute"), 0); + + disconnectDate = QDate::currentDate(); // doesn't make sense to save it + + b_autoShutdown = config->readBoolEntry("AutoShutdown", DEF_AutoShutdown); + b_autoPaste = config->readBoolEntry("AutoPaste", DEF_AutoPaste); + + // read limits options + config->setGroup("Limits"); + + maxSimultaneousConnections = config->readNumEntry("MaxSimConnections", DEF_MaxSimConnections); + minimumBandwidth = config->readNumEntry("MinimumBandwidth", DEF_MinimumBandwidth); + maximumBandwidth = config->readNumEntry("MaximumBandwidth", DEF_MaximumBandwidth); + + // read advanced options + config->setGroup("Advanced"); + + b_addQueued = config->readBoolEntry("AddQueued", DEF_AddQueued); + b_showMain = config->readBoolEntry("ShowMain", DEF_ShowMain); + b_showIndividual = config->readBoolEntry("ShowIndividual", DEF_ShowIndividual); + b_iconifyIndividual = config->readBoolEntry("IconifyIndividual", DEF_IconifyIndividual); + b_advancedIndividual = config->readBoolEntry("AdvancedIndividual", DEF_AdvancedIndividual); + + b_removeOnSuccess = config->readBoolEntry("RemoveOnSuccess", DEF_RemoveOnSuccess); + b_getSizes = config->readBoolEntry("GetSizes", DEF_GetSizes); + b_expertMode = config->readBoolEntry("ExpertMode", DEF_ExpertMode); + + // read if the integration whith konqueror is enabled + + KConfig *cfg = new KConfig("konquerorrc", false, false); + cfg->setGroup("HTML Settings"); + QString downloadManager=cfg->readPathEntry("DownloadManager"); + + b_KonquerorIntegration=(downloadManager==KGET_APP_NAME)?true:false; + + // check if we already asked about konqueror integration + if(config->readBoolEntry("FirstRun",true)) + { + config->writeEntry("FirstRun",false); + bool bAnswerYes = KMessageBox::questionYesNo(0L,i18n("This is the first time that you have run KGet.\nDo you want to use KGet as Download Manager for Konqueror?"), i18n("Konqueror Integration"), i18n("Enable"), i18n("Do Not Enable")) == KMessageBox::Yes; + if (bAnswerYes) + { + cfg->writePathEntry("DownloadManager", QString(KGET_APP_NAME)); + cfg->sync(); + b_KonquerorIntegration=true; + } + } + delete cfg; + + + // read search options + config->setGroup("Search"); + b_searchFastest = config->readBoolEntry("SearchFastest", DEF_SearchFastest); + searchItems = config->readNumEntry("SearchItems", DEF_SearchItems); + timeoutSearch = config->readNumEntry("TimeoutSearch", DEF_TimeoutSearch); + b_switchHosts = config->readBoolEntry("SwitchHosts", DEF_SwitchHosts); + + // read directory options + config->setGroup("Directories"); + + b_useLastDir = config->readBoolEntry("UseLastDirectory", DEF_UseLastDir); + lastDirectory = config->readPathEntry("LastDirectory", + QString("file:") + QDir::currentDirPath() ); + + QStringList strList; + + strList = config->readPathListEntry("Items"); + + defaultDirList.clear(); + QStringList::Iterator it = strList.begin(); + for (; it != strList.end(); ++it) { + DirItem item; + + item.extRegexp = *it; + ++it; + item.defaultDir = *it; + defaultDirList.append(item); + } + + // read misc settings + config->setGroup("Misc"); + + QFont font = KGlobalSettings::generalFont(); + + listViewFont = config->readFontEntry("Font", &font); + + // read main window geometry settings + config->setGroup("MainGeometry"); + const QPoint point(-1,-1); + mainPosition = config->readPointEntry("Position", &point); + mainSize = config->readSizeEntry("Size"); + mainState = config->readUnsignedLongNumEntry("State", 0); + + // read drop target geometry settings + config->setGroup("DropGeometry"); + dropPosition = config->readPointEntry("Position", &point); + dropState = config->readUnsignedLongNumEntry("State", DEFAULT_DOCK_STATE ); + + // flushing pending changes + config->sync(); +} + + +void Settings::save() +{ + sDebug << "Saving settings" << endl; + + KConfig *config = kapp->config(); + + // write connection options + config->setGroup("Connection"); + config->writeEntry("ReconnectOnError", b_reconnectOnError); + config->writeEntry("ReconnectTime", reconnectTime); + config->writeEntry("ReconnectRetries", reconnectRetries); + config->writeEntry("ReconnectOnBroken", b_reconnectOnBroken); + config->writeEntry("TimeoutData", timeoutData); + config->writeEntry("TimeoutDataNoResume", timeoutDataNoResume); + config->writeEntry("ConnectionType", connectionType); + config->writeEntry("LinkNumber", linkNumber); + config->writeEntry("OfflineMode", b_offlineMode); + + // write automation options + config->setGroup("Automation"); + config->writeEntry("AutoSave", b_autoSave); + config->writeEntry("AutoSaveInterval", autoSaveInterval); + config->writeEntry("AutoDisconnect", b_autoDisconnect); + config->writePathEntry("DisconnectCommand", disconnectCommand); + config->writeEntry("TimedDisconnect", b_timedDisconnect); + config->writeEntry("DisconnectTimeHour", disconnectTime.hour()); + config->writeEntry("DisconnectTimeMinute", disconnectTime.minute()); + config->writeEntry("AutoShutdown", b_autoShutdown); + config->writeEntry("AutoPaste", b_autoPaste); + + // write limits options + config->setGroup("Limits"); + config->writeEntry("MaxSimConnections", maxSimultaneousConnections); + config->writeEntry("MinimumBandwidth", minimumBandwidth); + config->writeEntry("MaximumBandwidth", maximumBandwidth); + + // write advanced options + config->setGroup("Advanced"); + config->writeEntry("ShowMain", b_showMain); + config->writeEntry("AddQueued", b_addQueued); + config->writeEntry("ShowIndividual", b_showIndividual); + config->writeEntry("IconifyIndividual", b_iconifyIndividual); + config->writeEntry("AdvancedIndividual", b_advancedIndividual); + config->writeEntry("RemoveOnSuccess", b_removeOnSuccess); + config->writeEntry("GetSizes", b_getSizes); + config->writeEntry("ExpertMode", b_expertMode); + config->writeEntry("KonquerorIntegration",b_KonquerorIntegration); + + + // write search options + config->setGroup("Search"); + config->writeEntry("SearchFastest", b_searchFastest); + config->writeEntry("SearchItems", searchItems); + config->writeEntry("TimeoutSearch", timeoutSearch); + config->writeEntry("SwitchHosts", b_switchHosts); + + // write directory options + config->setGroup("Directories"); + config->writeEntry("UseLastDirectory", b_useLastDir); + config->writePathEntry("LastDirectory", lastDirectory ); + DirList::Iterator it; + QStringList lst; + + for (it = defaultDirList.begin(); it != defaultDirList.end(); ++it) { + lst.append((*it).extRegexp); + lst.append((*it).defaultDir); + } + config->writePathEntry("Items", lst); + + // write system options + config->setGroup("System"); + config->writeEntry("UseAnimation", b_useAnimation); + + // write misc options + config->setGroup("Misc"); + config->writeEntry("Font", listViewFont); + + // write main window geometry properties + config->setGroup("MainGeometry"); + config->writeEntry("Position", kmain->pos()); + config->writeEntry("Size", kmain->size()); + config->writeEntry("State", KWin::windowInfo(kmain->winId()).state()); + + // write drop target geometry properties + config->setGroup("DropGeometry"); + config->writeEntry("Position", kdrop->pos()); + unsigned long state = KWin::windowInfo(kdrop->winId()).state(); + // state will be 0L if droptarget is hidden. Sigh. + config->writeEntry("State", state ? state : DEFAULT_DOCK_STATE ); + + + config->sync(); +} diff --git a/kget/settings.h b/kget/settings.h new file mode 100644 index 00000000..c66b00e6 --- /dev/null +++ b/kget/settings.h @@ -0,0 +1,186 @@ +/*************************************************************************** +* settings.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#ifndef _SETTINGS_H +#define _SETTINGS_H + +// common connection types +enum ConnectionType { PERMANENT = 0, ETHERNET, PLIP, SLIP, PPP, ISDN }; + +extern QString ConnectionDevices[]; + + +// Connection settings +#define DEF_ReconnectOnError true +#define DEF_ReconnectTime 1 +#define DEF_ReconnectRetries 10 +#define DEF_ReconnectOnBroken true + +#define DEF_TimeoutData 5 +#define DEF_TimeoutDataNoResume 15 + +#define DEF_ConnectionType PERMANENT +#define DEF_LinkNumber 0 +#define DEF_OfflineMode false + +// Automation settings +#define DEF_AutoSave true +#define DEF_AutoSaveInterval 10 +#define DEF_AutoDisconnect false +#define DEF_DisconnectCommand "kppp -k" +#define DEF_TimedDisconnect false +#define DEF_AutoShutdown false +#define DEF_AutoPaste false + +// Limits settings +#define DEF_MaxSimConnections 2 +#define DEF_MinimumBandwidth 1000 +#define DEF_MaximumBandwidth 10000 + +// Advanced settings +#define DEF_AddQueued true +#define DEF_ShowMain false +#define DEF_ShowIndividual false +#define DEF_IconifyIndividual false +#define DEF_AdvancedIndividual false +#define DEF_RemoveOnSuccess true +#define DEF_GetSizes true +#define DEF_ExpertMode false + +// Search settings +#define DEF_SearchFastest false +#define DEF_SearchItems 20 +#define DEF_TimeoutSearch 30 +#define DEF_SwitchHosts false + +// Directories settings +#define DEF_UseLastDir false + +// System settings +#define DEF_UseAnimation true + +#define DEF_WindowStyle NORMAL + + +#include + +#include +#include +struct DirItem +{ + QString extRegexp; + QString defaultDir; +}; + +typedef QValueList < DirItem > DirList; + +class Settings +{ + +public: + + Settings() + { + } + ~Settings() + { + } + + void load(); + void save(); + + // connection options + bool b_reconnectOnBroken; + bool b_reconnectOnError; + + uint reconnectTime; + uint reconnectRetries; + + uint timeoutData; + uint timeoutDataNoResume; + + uint connectionType; + uint linkNumber; + bool b_offlineMode;// If we want to be offline + bool b_offline; // If we really are offline + + + // automation options + bool b_autoSave; + uint autoSaveInterval; + bool b_autoDisconnect; + QString disconnectCommand; + bool b_timedDisconnect; + QDate disconnectDate; + QTime disconnectTime; + bool b_autoShutdown; + bool b_autoPaste; + + // limits options + uint maxSimultaneousConnections; + uint minimumBandwidth; + uint maximumBandwidth; + + // advanced options + bool b_addQueued; + bool b_showIndividual; + bool b_iconifyIndividual; + bool b_advancedIndividual; + bool b_removeOnSuccess; + bool b_getSizes; + bool b_expertMode; + bool b_KonquerorIntegration; + bool b_showMain; + // search options + bool b_searchFastest; + uint searchItems; + uint timeoutSearch; + bool b_switchHosts; + + // directories options + bool b_useLastDir; + QString lastDirectory; + + DirList defaultDirList; + + // system options + bool b_useAnimation; + QFont listViewFont; + + + // geometry settings + QPoint mainPosition; + QSize mainSize; + unsigned long int mainState; + + QPoint dropPosition; + unsigned long int dropState; +}; + +extern Settings ksettings; + +#endif // _SETTINGS_H diff --git a/kget/slave.cpp b/kget/slave.cpp new file mode 100644 index 00000000..944a7f37 --- /dev/null +++ b/kget/slave.cpp @@ -0,0 +1,331 @@ +/*************************************************************************** +* slave.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#include +#include + +#include "getfilejob.h" +#include "slave.h" +#include "slaveevent.h" +#include "transfer.h" + +#include + +Slave::Slave(Transfer * _parent, const KURL & _src, const KURL & _dest) + : QObject(), + QThread() +{ + mDebug << ">>>>Entering" << endl; + copyjob = NULL; + m_src = _src; + m_dest = _dest; + m_parent = _parent; + + nPendingCommand = 0; + + mDebug << ">>>>Leaving" << endl; +} + +Slave::~Slave() +{} + +void Slave::Op(SlaveCommand _cmd) +{ + mDebugIn << " _cmd = " << _cmd << endl; + + if ( !running() ) // start on demand + start(); + + mutex.lock(); + stack.push(_cmd); + nPendingCommand++; + worker.wakeOne(); + mutex.unlock(); + + mDebugOut << endl; +} + +/** No descriptions */ +void Slave::PostMessage(SlaveResult _event, Q_ULLONG _data) +{ + SlaveEvent *e1 = new SlaveEvent(m_parent, _event, _data); + + QApplication::postEvent(kapp->mainWidget(), (QEvent *) e1); +} + +void Slave::PostMessage(SlaveResult _event, const QString & _msg) +{ + SlaveEvent *e1 = new SlaveEvent(m_parent, _event, _msg); + + QApplication::postEvent(kapp->mainWidget(), (QEvent *) e1); + mDebug << "Msg:" << "_msg = " << _msg << endl; +} + +void Slave::InfoMessage(const QString & _msg) +{ + SlaveEvent *e1 = new SlaveEvent(m_parent, SLV_INFO, _msg); + + QApplication::postEvent(kapp->mainWidget(), (QEvent *) e1); + mDebug << "Infor Msg:" << "_msg = " << _msg << endl; +} + + + +void Slave::run() +{ + mDebugIn << endl; + + SlaveCommand cmd; + bool running = true; + + while (running) + { + if (!nPendingCommand) + worker.wait(); + switch (cmd = fetch_cmd()) + { + case RESTART: + if (copyjob) { + copyjob->kill(true); + copyjob = 0L; + } + // fall through + case RETR: + mDebug << " FETCHED COMMAND RETR" << endl; + assert(!copyjob); + KIO::Scheduler::checkSlaveOnHold( true ); + copyjob = new KIO::GetFileJob(m_src, m_dest); + Connect(); + PostMessage(SLV_RESUMED); + break; + + case RETR_CACHE: + mDebug << " FETCHED COMMAND RETR_CACHE" << endl; + assert(!copyjob); + KIO::Scheduler::checkSlaveOnHold( true ); + copyjob = new KIO::GetFileJob(m_src, m_dest); + copyjob->addMetaData("cache", "cacheonly"); + Connect(); + break; + + case PAUSE: + mDebug << " FETCHED COMMAND PAUSE" << endl; + if (copyjob) { + copyjob->kill(true); + copyjob = 0L; + } + PostMessage(SLV_PAUSED); + break; + + case KILL: + mDebug << " FETCHED COMMAND KILL" << endl; + running = false; + if (copyjob) { + copyjob->kill(true); + copyjob = 0L; + } + // no message posted + break; + + case REMOVE: + mDebug << " FETCHED COMMAND REMOVE" << endl; + running = false; + if (copyjob) { + copyjob->kill(true); + copyjob = 0L; + } + PostMessage(SLV_REMOVED); + break; + + case SCHEDULE: + mDebug << " FETCHED COMMAND SCHEDULE" << endl; + if (copyjob) { + copyjob->kill(true); + copyjob = 0L; + } + PostMessage(SLV_SCHEDULED); + break; + + case DELAY: + mDebug << " FETCHED COMMAND DELAY" << endl; + if (copyjob) { + copyjob->kill(true); + copyjob = 0L; + } + PostMessage(SLV_DELAYED); + break; + + case NOOP: + mDebug << "FETCHED COMMAND NOOP, i.e. empty stack" << endl; + if (copyjob) { + copyjob->kill(true); + copyjob = 0L; + } + running = false; + break; + + default: + mDebug << " UNKNOWN COMMAND DIE...." << endl; + assert(0); + } + } + + assert(!copyjob); + mDebugOut << endl; +} + + +Slave::SlaveCommand Slave::fetch_cmd() +{ + mutex.lock(); + SlaveCommand cmd = NOOP; + if ( !stack.isEmpty() ) + { + nPendingCommand--; + cmd = stack.pop(); + } + mutex.unlock(); + return cmd; +} + + +void Slave::Connect() +{ + mDebugIn << endl; + + + connect(copyjob, SIGNAL(canceled(KIO::Job *)), SLOT(slotCanceled(KIO::Job *))); + connect(copyjob, SIGNAL(connected(KIO::Job *)), SLOT(slotConnected(KIO::Job *))); + connect(copyjob, SIGNAL(result(KIO::Job *)), SLOT(slotResult(KIO::Job *))); + + connect(copyjob, SIGNAL(totalSize(KIO::Job *, KIO::filesize_t)), SLOT(slotTotalSize(KIO::Job *, KIO::filesize_t))); + + connect(copyjob, SIGNAL(processedSize(KIO::Job *, KIO::filesize_t)), SLOT(slotProcessedSize(KIO::Job *, KIO::filesize_t))); + + connect(copyjob, SIGNAL(speed(KIO::Job *, unsigned long)), SLOT(slotSpeed(KIO::Job *, unsigned long))); + + connect(copyjob, SIGNAL(infoMessage(KIO::Job *, const QString &)), SLOT(slotInfoMessage(KIO::Job *, const QString &))); + + mDebugOut << endl; +} + + +void Slave::slotCanceled(KIO::Job *) +{ + mDebugIn << endl; + + + mDebugOut << endl; +} + +void Slave::slotConnected(KIO::Job *) +{ + mDebugIn << endl; + + + mDebugOut << endl; +} + +void Slave::slotResult(KIO::Job * job) +{ + mDebugIn << endl; + + assert(copyjob == job); + copyjob=0L; + KIO::Error error=KIO::Error(job->error()); + if (!error) { + PostMessage(SLV_FINISHED); + } + else { + if (m_parent->getMode() == Transfer::MD_NEW) + { + PostMessage(SLV_NOTINCACHE); + return; + } + QString tmsg=" " + job->errorString() + \ + ""; + InfoMessage(tmsg); + if (m_parent->retryOnError() && \ + ((error==KIO::ERR_COULD_NOT_LOGIN) || (error==KIO::ERR_SERVER_TIMEOUT))) { + //Timeout or login error + PostMessage(SLV_ERROR); + } + else if (m_parent->retryOnBroken() && (error==KIO::ERR_CONNECTION_BROKEN)) { + // Connection Broken + PostMessage(SLV_BROKEN); + } + else { + job->showErrorDialog(); + PostMessage(SLV_DELAYED); + } + } + mDebugOut << endl; +} + + +void Slave::slotSpeed(KIO::Job *, unsigned long lSpeed) +{ + // mDebugIn< +#include +#include +#include +#include +#include + +#include "common.h" + +namespace KIO +{ + class GetFileJob; +} + +class Transfer; + +class Slave:public QObject, public QThread +{ + Q_OBJECT + +public: + enum SlaveCommand { + RETR, RETR_CACHE, PAUSE, RESTART, ABORT, DELAY, + SCHEDULE, REMOVE, KILL, NOOP + }; + + enum SlaveResult { + + SLV_TOTAL_SIZE, SLV_PROGRESS_SIZE, SLV_PROGRESS_SPEED, + SLV_CAN_RESUME, SLV_CONNECTED, + + SLV_RESUMED, SLV_PAUSED, SLV_ERROR, SLV_BROKEN, SLV_SCHEDULED, SLV_DELAYED, + SLV_FINISHED, SLV_INFO, SLV_REMOVED, SLV_KILLED, SLV_NOTINCACHE + }; + +public: + Slave(Transfer * _parent, const KURL & _src, const KURL & _dest); + ~Slave(); + void Op(SlaveCommand _cmd); + +protected: + virtual void run(); + +private slots: + void slotCanceled(KIO::Job *); + void slotConnected(KIO::Job *); + void slotResult(KIO::Job *); + void slotTotalSize(KIO::Job *, KIO::filesize_t); + void slotProcessedSize(KIO::Job *, KIO::filesize_t); + void slotSpeed(KIO::Job *, unsigned long); + void slotInfoMessage(KIO::Job *, const QString &); + +private: + void Connect(); + + void PostMessage(SlaveResult _event, Q_ULLONG _data = 0L); + void PostMessage(SlaveResult _event, const QString & _msg); + void InfoMessage(const QString & _msg); + + Transfer * m_parent; + + KURL m_src; + KURL m_dest; + + Slave::SlaveCommand fetch_cmd(); + int nPendingCommand; + + QValueStack < SlaveCommand > stack; + QWaitCondition worker; + QMutex mutex; + KIO::GetFileJob * copyjob; + +}; + +#endif diff --git a/kget/slaveevent.cpp b/kget/slaveevent.cpp new file mode 100644 index 00000000..e0d41661 --- /dev/null +++ b/kget/slaveevent.cpp @@ -0,0 +1,72 @@ +/*************************************************************************** +* slaveevent.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* emai : pch@freeshell.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. + * + ***************************************************************************/ + + +#include "slaveevent.h" + +#define EVENT_TYPE (QEvent::User + 252) + +SlaveEvent::SlaveEvent(Transfer * _item, unsigned int _event, Q_ULLONG _ldata):QCustomEvent(EVENT_TYPE) +{ + m_event = _event; + m_item = _item; + m_ldata = _ldata; + m_msg = QString(""); +} + + +SlaveEvent::SlaveEvent(Transfer * _item, unsigned int _event, const QString & _msg):QCustomEvent(EVENT_TYPE) +{ + m_event = _event; + m_item = _item; + m_ldata = 0L; + m_msg = _msg; +} + + +SlaveEvent::~SlaveEvent() +{ +} + +unsigned int +SlaveEvent::getEvent() const +{ + return m_event; +} + +Transfer *SlaveEvent::getItem() const +{ + return m_item; +} + +Q_ULLONG SlaveEvent::getData() const +{ + return m_ldata; +} + +const QString & SlaveEvent::getMsg() const +{ + return m_msg; +} diff --git a/kget/slaveevent.h b/kget/slaveevent.h new file mode 100644 index 00000000..6fb5f96b --- /dev/null +++ b/kget/slaveevent.h @@ -0,0 +1,59 @@ +/*************************************************************************** +* slaveevent.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* email :pch@freeshell.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. + * + ***************************************************************************/ + + +#ifndef SLAVEEVENT_H +#define SLAVEEVENT_H + +#include + +class Transfer; + +/** + *@author Patrick Charbonnier + */ + +class SlaveEvent:public QCustomEvent +{ +public: + SlaveEvent(Transfer * _item, unsigned int _event, Q_ULLONG _ldata = 0L); + SlaveEvent(Transfer * _item, unsigned int _event, const QString & _msg); + ~SlaveEvent(); + + unsigned int getEvent() const; + Transfer *getItem() const; + Q_ULLONG getData() const; + const QString & getMsg() const; + + +private: + unsigned int m_event; + Transfer *m_item; + Q_ULLONG m_ldata; + QString m_msg; + +}; + +#endif diff --git a/kget/sounds/KGet_Added.ogg b/kget/sounds/KGet_Added.ogg new file mode 100644 index 00000000..8647db9c Binary files /dev/null and b/kget/sounds/KGet_Added.ogg differ diff --git a/kget/sounds/KGet_Finished.ogg b/kget/sounds/KGet_Finished.ogg new file mode 100644 index 00000000..fd06b360 Binary files /dev/null and b/kget/sounds/KGet_Finished.ogg differ diff --git a/kget/sounds/KGet_Finished_All.ogg b/kget/sounds/KGet_Finished_All.ogg new file mode 100644 index 00000000..45fbfacd Binary files /dev/null and b/kget/sounds/KGet_Finished_All.ogg differ diff --git a/kget/sounds/KGet_Started.ogg b/kget/sounds/KGet_Started.ogg new file mode 100644 index 00000000..8b139ad8 Binary files /dev/null and b/kget/sounds/KGet_Started.ogg differ diff --git a/kget/sounds/Makefile.am b/kget/sounds/Makefile.am new file mode 100644 index 00000000..745b5a70 --- /dev/null +++ b/kget/sounds/Makefile.am @@ -0,0 +1,4 @@ +kde_sound_DATA = KGet_Added.ogg KGet_Finished.ogg KGet_Finished_All.ogg \ + KGet_Started.ogg + +EXTRA_DIST = $(kde_sound_DATA) diff --git a/kget/transfer.cpp b/kget/transfer.cpp new file mode 100644 index 00000000..44e76fae --- /dev/null +++ b/kget/transfer.cpp @@ -0,0 +1,1025 @@ +/*************************************************************************** +* transfer.cpp +* ------------------- +* +* begin : Tue Jan 29 2002 +* copyright : (C) 2002, 2003, 2004, 2005 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.org +* +****************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public Lkio/global.h:icense 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. + * + ***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "safedelete.h" +#include "settings.h" +#include "logwindow.h" +#include "kmainwidget.h" +#include "dlgIndividual.h" +#include "transferlist.h" +#include "transfer.h" + +#include +#include +#include +#include + + +extern Settings ksettings; + + +Transfer::Transfer(TransferList * _view, const KURL & _src, const KURL & _dest, const uint _id) + : QObject( _view ), + KListViewItem(_view), + dlgIndividual( 0 ) +{ + sDebugIn << endl; + + src = _src; + dest = _dest; + + view = _view; + init(_id); + + sDebugOut << endl; +} + + +Transfer::Transfer(TransferList * _view, Transfer * after, const KURL & _src, const KURL & _dest, const uint _id) + : QObject( _view ), + KListViewItem(_view, (QListViewItem *) after), + src(_src), dest(_dest), view(_view), + dlgIndividual( 0 ) +{ + sDebugIn << endl; + + view = _view; + src = _src; + dest = _dest; + init(_id); + + sDebugOut << endl; +} + + +Transfer::~Transfer() +{ + sDebugIn << endl; + + synchronousAbort(); + delete dlgIndividual; + + sDebugOut << endl; +} + + +void +Transfer::init(const uint _id) +{ + sDebugIn << endl; + remainingTimeSec = 0; + totalSize = 0; + processedSize = 0; + percent = 0; + id = _id; + m_pSlave = new Slave(this, src, dest); + canResume = false; + startTime = QDateTime::currentDateTime(); + speed = 0; + // retryCount = ksettings.reconnectRetries-1; + retryCount = 0; + //first off all we need to know if resume is supported... + + status = ST_STOPPED; + + + connect(this, SIGNAL(statusChanged(Transfer *, int)), kmain, SLOT(slotStatusChanged(Transfer *, int))); + connect(this, SIGNAL(statusChanged(Transfer *, int)), this, SLOT(slotUpdateActions())); + + connect(this, SIGNAL(log(uint, const QString &, const QString &)), kmain->logwin(), SLOT(logTransfer(uint, const QString &, const QString &))); + + // setup actions + m_paResume = new KAction(i18n("&Resume"), "tool_resume", 0, this, SLOT(slotResume()), this, "resume"); + + m_paPause = new KAction(i18n("&Pause"), "tool_pause", 0, this, SLOT(slotRequestPause()), this, "pause"); + + m_paDelete = new KAction(i18n("&Delete"), "editdelete", 0, this, SLOT(slotRequestRemove()), this, "delete"); + + m_paRestart = new KAction(i18n("Re&start"), "tool_restart", 0, this, SLOT(slotRequestRestart()), this, "restart"); + + m_paQueue = new KRadioAction(i18n("&Queue"), "tool_queue", 0, this, SLOT(slotQueue()), this, "queue"); + + m_paTimer = new KRadioAction(i18n("&Timer"), "tool_timer", 0, this, SLOT(slotRequestSchedule()), this, "timer"); + + m_paDelay = new KRadioAction(i18n("De&lay"), "tool_delay", 0, this, SLOT(slotRequestDelay()), this, "delay"); + + m_paQueue->setExclusiveGroup("TransferMode"); + m_paTimer->setExclusiveGroup("TransferMode"); + m_paDelay->setExclusiveGroup("TransferMode"); + + // Actions + + // m_paDock = new KAction(i18n("&Dock"),"tool_dock", 0, this,SLOT(slotRequestDelay()), this, "dockIndividual"); + + // setup individual transfer dialog + + mode = MD_NEW; + + sDebugOut << endl; +} + + +void Transfer::synchronousAbort() +{ + if ( m_pSlave ) + { + if ( m_pSlave->running() ) + { + m_pSlave->Op(Slave::KILL); + m_pSlave->wait(); + } + + if ( m_pSlave->running() ) + m_pSlave->terminate(); + + delete m_pSlave; + m_pSlave = 0L; + + status = ST_STOPPED; + slotUpdateActions(); + } + +} + +void Transfer::slotUpdateActions() +{ + sDebugIn << "the item Status is =" << status << "offline=" << ksettings.b_offline << endl; + //if we are offline just disable Resume and Pause and return + if (ksettings.b_offline) { + m_paResume->setEnabled(false); + m_paPause->setEnabled(false); + m_paRestart->setEnabled(false); + if(dlgIndividual) + dlgIndividual->update(); + return; + } + + UpdateRetry(); + + switch (status) { + + case ST_TRYING://fall-through + case ST_RUNNING: + m_paResume->setEnabled(false); + m_paPause->setEnabled(true); + m_paRestart->setEnabled(true); + break; + + case ST_STOPPED: + m_paResume->setEnabled(true); + m_paPause->setEnabled(false); + m_paRestart->setEnabled(false); + break; + case ST_FINISHED: + m_paResume->setEnabled(false); + m_paPause->setEnabled(false); + m_paRestart->setEnabled(false); + break; + } + + // disable all signals + m_paQueue->blockSignals(true); + m_paTimer->blockSignals(true); + m_paDelay->blockSignals(true); + + switch (mode) { + case MD_QUEUED: + m_paQueue->setChecked(true); + break; + case MD_SCHEDULED: + m_paTimer->setChecked(true); + break; + case MD_DELAYED: + m_paDelay->setChecked(true); + break; + case MD_NEW: //fall through + case MD_NONE: + m_paQueue->setChecked(false); + m_paTimer->setChecked(false); + m_paDelay->setChecked(false); + + m_paQueue->setEnabled(false); + m_paTimer->setEnabled(false); + m_paDelay->setEnabled(false); + break; + + } + + + // enable all signals + m_paQueue->blockSignals(false); + m_paTimer->blockSignals(false); + m_paDelay->blockSignals(false); + + if (dlgIndividual) + dlgIndividual->update(); + + sDebugOut << endl; +} + + + +void Transfer::setSpeed(unsigned long _speed) +{ + // sDebugIn <lv_url, src.prettyURL()); + + // destination + setText(view->lv_filename, dest.fileName()); + + if(dlgIndividual) + { + dlgIndividual->setCopying(src, dest); + dlgIndividual->setCanResume(canResume); + dlgIndividual->setTotalSize(totalSize); + dlgIndividual->setPercent(0); + dlgIndividual->setProcessedSize(0); + } + + if (totalSize != 0) { + //logMessage(i18n("Total size is %1 bytes").arg((double)totalSize)); + setText(view->lv_total, KIO::convertSize(totalSize)); + } else { + //logMessage(i18n("Total size is unknown")); + setText(view->lv_total, i18n("unknown")); + } + + + sDebugOut << endl; +} + + +bool Transfer::updateStatus(int counter) +{ + //sDebug<< ">>>>Entering"<setUpdatesEnabled(false); + + switch(status) + { + case ST_RUNNING: + pix = view->animConn.at(counter); + isTransfer = true; + break; + case ST_TRYING: + pix = view->animTry.at(counter); + isTransfer = true; + break; + case ST_STOPPED: + if(mode == MD_QUEUED) + pix = &view->pixQueued; + else if(mode == MD_SCHEDULED) + pix = &view->pixScheduled; + else + pix = &view->pixDelayed; + break; + case ST_FINISHED: + pix = &view->pixFinished; + } + + setPixmap(view->lv_pixmap, *pix); + view->setUpdatesEnabled(true); + + if(prevStatus!=status || prevMode != mode || status==ST_RUNNING || status==ST_TRYING) + { + QRect rect = view->header()->sectionRect(view->lv_pixmap); + + int x = rect.x(); + int y = view->itemRect(this).y(); + int w = rect.width(); + int h = rect.height(); + + view->QScrollView::updateContents(x,y,w,h); + + prevStatus = status; + prevMode = mode; + } + + return isTransfer; +} + + +void Transfer::UpdateRetry() +{ + QString retry; + QString MaxRetry; + + retry.setNum(retryCount); + MaxRetry.setNum(ksettings.reconnectRetries); + retry += " / " + MaxRetry; + + setText(view->lv_count, retry); +} + + +void Transfer::slotResume() +{ + sDebugIn << " state =" << status << endl; + + retryCount++; + if (retryCount > ksettings.reconnectRetries) + retryCount = 1; + UpdateRetry(); + assert(status == ST_STOPPED); + + sDebug << "src: " << src.prettyURL() << endl; + sDebug << "dest " << dest.prettyURL() << endl; + + m_paResume->setEnabled(false); + + status = ST_TRYING; + mode = MD_QUEUED; + logMessage(i18n("Attempt number %1").arg(retryCount)); + + sDebug << "sending Resume to slave " << endl; + m_pSlave->Op(Slave::RETR); + + sDebugOut << endl; +} + + + void Transfer::slotStop() +{ + sDebugIn << endl; + + logMessage(i18n("Stopping")); + + assert(status <= ST_RUNNING && ksettings.b_offline); + + m_pSlave->Op(Slave::KILL); // KILL doesn't post a Message + sDebug << "Killing Slave" << endl; + + slotSpeed(0); + mode = MD_QUEUED; + status=ST_STOPPED; + m_paQueue->setChecked(true); + + slotUpdateActions(); + + sDebugOut << endl; +} + + +void Transfer::slotRequestPause() +{ + sDebugIn << endl; + + logMessage(i18n("Pausing")); + + assert(status <= ST_RUNNING); + + //stopping the thead + + m_paPause->setEnabled(false); + m_paRestart->setEnabled(false); + + + m_pSlave->Op(Slave::PAUSE); + sDebug << "Requesting Pause.." << endl; + + sDebugOut << endl; +} + + + + +void Transfer::slotRequestRestart() +{ + sDebugIn << endl; + m_pSlave->Op(Slave::RESTART); + slotSpeed(0); + sDebugOut << endl; +} + + +void Transfer::slotRequestRemove() +{ + sDebugIn << endl; + if (dlgIndividual && !ksettings.b_expertMode) + { + if (KMessageBox::warningContinueCancel(0, i18n("Are you sure you want to delete this transfer?"), + QString::null, KStdGuiItem::del(), + QString("delete_transfer")) + != KMessageBox::Continue) + return; + } + m_paDelete->setEnabled(false); + m_paPause->setEnabled(false); + if (dlgIndividual) + dlgIndividual->close(); + + + if ( status != ST_FINISHED ) + { + KURL file = dest; + // delete the partly downloaded file, if any + file.setFileName( dest.fileName() + ".part" ); // ### get it from the job? + + if ( KIO::NetAccess::exists( file, false, view ) ) // don't pollute user with warnings + { + SafeDelete::deleteFile( file ); // ### messagebox on failure? + } + } + if (status == ST_RUNNING) + m_pSlave->Op(Slave::REMOVE); + else + emit statusChanged(this, OP_REMOVED); + + sDebugOut << endl; +} + + +void Transfer::slotQueue() +{ + sDebug << ">>>>Entering with mode = " << mode << endl; + + logMessage(i18n("Queueing")); + + assert(!(mode == MD_QUEUED)); + + mode = MD_QUEUED; + m_paQueue->setChecked(true); + emit statusChanged(this, OP_QUEUED); + sDebugOut << endl; +} + + +void Transfer::slotRequestSchedule() +{ + sDebugIn << endl; + + logMessage(i18n("Scheduling")); + assert(!(mode == MD_SCHEDULED)); + + // if the time was already set somewhere in the future, keep it + // otherwise set it to the current time + 60 seconds + if (startTime < QDateTime::currentDateTime()) { + QDateTime dt = QDateTime::currentDateTime(); + startTime = dt.addSecs(60); + } + if (status == ST_RUNNING) { + m_paPause->setEnabled(false); + m_paRestart->setEnabled(false); + m_pSlave->Op(Slave::SCHEDULE); + + } else + slotExecSchedule(); + sDebugOut << endl; +} + + +void Transfer::slotRequestDelay() +{ + sDebugIn << endl; + + logMessage(i18n("Delaying")); + + assert(!(mode == MD_DELAYED)); + if (status == ST_RUNNING) { + m_paPause->setEnabled(false); + m_paRestart->setEnabled(false); + m_pSlave->Op(Slave::DELAY); + } else + slotExecDelay(); + sDebugOut << endl; +} + + + +/* +void Transfer::slotCanceled(KIO::Job *) +{ + sDebugIn << endl; + + logMessage(i18n("Canceled by user")); + emit statusChanged(this, OP_CANCELED); + sDebugOut << endl; +} + +*/ + +void Transfer::slotFinished() +{ + sDebugIn << endl; + + logMessage(i18n("Download finished")); + mode = MD_NONE; + status = ST_FINISHED; + slotProcessedSize(totalSize); + + slotSpeed(0); + if(dlgIndividual) + dlgIndividual->enableOpenFile(); + emit statusChanged(this, OP_FINISHED); + sDebugOut << endl; +} + + + + +/* +void Transfer::slotRenaming(KIO::Job *, const KURL &, const KURL & to) +{ + sDebugIn << endl; + + dest = to; + + logMessage(i18n("Renaming to %1").arg(dest.prettyURL().ascii())); + + // destination + setText (view->lv_filename, dest.fileName ()); + + dlgIndividual->setCopying (src, dest); + + sDebugOut << endl; +} + */ + + + + +void Transfer::slotSpeed(unsigned long bytes_per_second) +{ + //sDebugIn <lv_speed, i18n("Stalled")); + setText(view->lv_remaining, i18n("Stalled")); + if(dlgIndividual) + dlgIndividual->setSpeed(i18n("Stalled")); + } else if (speed == 0 && status == ST_FINISHED) { + + setText(view->lv_progress, i18n("OK as in 'finished'","OK")); + setText(view->lv_speed, i18n("Finished")); + setText(view->lv_remaining, i18n("Finished")); + if(dlgIndividual) + dlgIndividual->setSpeed(i18n("Finished")); + + } else if (speed == 0 && status == ST_STOPPED) { + + + setText(view->lv_speed, i18n("Stopped")); + setText(view->lv_remaining, i18n("Stopped")); + if(dlgIndividual) + dlgIndividual->setSpeed(i18n("Stopped")); + + } else { + QString tmps = i18n("%1/s").arg(KIO::convertSize(speed)); + setText(view->lv_speed, tmps); + setText(view->lv_remaining, remainingTime); + if(dlgIndividual) + dlgIndividual->setSpeed(tmps + " ( " + remainingTime + " ) "); + } + + //sDebugOut<lv_total, KIO::convertSize(totalSize)); + if(dlgIndividual) + { + dlgIndividual->setTotalSize(totalSize); + dlgIndividual->setPercent(0); + dlgIndividual->setProcessedSize(0); + } + } + } else { + +#ifdef _DEBUG + sDebug<<"totalSize="<>>>Entering"<lv_total, KIO::convertSize(totalSize)); + if(dlgIndividual) + dlgIndividual->setTotalSize(totalSize); + } + else { + percent = (int) (((float) processedSize / (float) totalSize) * 100.0); + } + if(dlgIndividual) + dlgIndividual->setProcessedSize(processedSize); + + if (percent != old) { + QString tmps; + if (percent == 100) { + tmps = i18n("OK as in 'finished'","OK"); + } else { + tmps.setNum(percent); + } + + setText(view->lv_progress, tmps); + + if(dlgIndividual) + dlgIndividual->setPercent(percent); + } + //sDebug<< "<<<setLog(transferLog); + dlgIndividual->setCopying(src, dest); + dlgIndividual->setCanResume(canResume); + dlgIndividual->setTotalSize(totalSize); + dlgIndividual->setPercent(percent); + dlgIndividual->setProcessedSize(processedSize); + } + + dlgIndividual->raise(); + + + if (ksettings.b_iconifyIndividual) { + KWin::iconifyWindow( dlgIndividual->winId() ); + } + + // update the actions + slotUpdateActions(); + // then show the single dialog + KWin::deIconifyWindow( dlgIndividual->winId() ); + dlgIndividual->show(); + + sDebugOut << endl; +} + + +void Transfer::logMessage(const QString & message) +{ + sDebugIn << message << endl; + + emit log(id, src.fileName(), message); + + QString tmps = "" + QTime::currentTime().toString() + " : " + message; + + transferLog.append(tmps + '\n'); + + if(dlgIndividual) + dlgIndividual->appendLog(tmps); + + sDebugOut << endl; +} + + + +bool Transfer::read(KSimpleConfig * config, int id) +{ + sDebugIn << endl; + + + QString str; + str.sprintf("Item%d", id); + config->setGroup(str); + + if (src.isEmpty() || dest.isEmpty()) { + return false; + } + + if (!src.isValid() && !ksettings.b_expertMode) { + KMessageBox::error(kmain, i18n("Malformed URL:\n") + src.prettyURL(), i18n("Error")); + return false; + } + + mode = (TransferMode) config->readNumEntry("Mode", MD_QUEUED); + status = (TransferStatus) config->readNumEntry("Status", ST_RUNNING); + startTime = config->readDateTimeEntry("ScheduledTime"); + canResume = config->readBoolEntry("CanResume", true); + totalSize = config->readUnsignedNum64Entry("TotalSize", 0); + processedSize = config->readUnsignedNum64Entry("ProcessedSize", 0); + + if (status != ST_FINISHED && totalSize != 0) { + //TODO insert additional check + status = ST_STOPPED; + } + + updateAll(); + sDebugOut << endl; + return true; +} + + +void Transfer::write(KSimpleConfig * config, int id) +{ + sDebugIn << endl; + + QString str; + str.sprintf("Item%d", id); + + config->setGroup(str); + config->writePathEntry("Source", src.url()); + config->writePathEntry("Dest", dest.url()); + config->writeEntry("Mode", mode); + config->writeEntry("Status", status); + config->writeEntry("CanResume", canResume); + config->writeEntry("TotalSize", totalSize ); + config->writeEntry("ProcessedSize", processedSize ); + config->writeEntry("ScheduledTime", startTime); + sDebugOut << endl; +} + + + +/** No descriptions */ +void Transfer::slotExecPause() +{ + sDebugIn << endl; + slotSpeed(0); + + mode = MD_DELAYED; + m_paDelay->setChecked(true); + status = ST_STOPPED; + + m_paPause->setEnabled(false); + m_paRestart->setEnabled(true); + m_paResume->setEnabled(true); + slotUpdateActions(); + //TODO WE NEED TO UPDATE ACTIONS.. + kmain->slotUpdateActions(); + emit statusChanged(this, OP_PAUSED); + sDebugOut << endl; +} + +void Transfer::slotExecError() +{ + sDebugIn << endl; + + status = ST_STOPPED; + mode = MD_SCHEDULED; + startTime=QDateTime::currentDateTime().addSecs(ksettings.reconnectTime * 60); + emit statusChanged(this, OP_SCHEDULED); + + sDebugOut << endl; +} + +void Transfer::slotExecBroken() +{ + sDebugIn << endl; + + status = ST_STOPPED; + mode = MD_QUEUED; + emit statusChanged(this, OP_QUEUED); + + sDebugOut << endl; +} + + +void Transfer::slotExecRemove() +{ + sDebugIn << endl; + + m_pSlave->wait(); + emit statusChanged(this, OP_REMOVED); + sDebugOut << endl; +} + + +void Transfer::slotExecResume() +{ + sDebugIn << endl; + emit statusChanged(this, OP_RESUMED); + sDebugOut << endl; +} + +void Transfer::slotExecConnected() +{ + sDebugIn << endl; + if (mode == MD_NEW) + { + if (ksettings.b_offline)// when we're offline and arrive here, then the file is in cache + return; // Slave::slotResult will be called immediately, so we do nothing here + status = ST_STOPPED; + m_pSlave->Op(Slave::KILL); + if (ksettings.b_addQueued) + { + mode = MD_QUEUED; + emit statusChanged(this, OP_QUEUED); + } + else + { + mode = MD_DELAYED; + emit statusChanged(this, OP_DELAYED); + } + return; + } + + status = ST_RUNNING; + emit statusChanged(this, OP_CONNECTED); + sDebugOut << endl; +} + + +void Transfer::slotCanResume(bool _bCanResume) +{ + sDebugIn << endl; + + canResume = _bCanResume; + + if (canResume) { + logMessage(i18n("Download resumed")); + setText(view->lv_resume, i18n("Yes")); + } else { + setText(view->lv_resume, i18n("No")); + } + + //dlgIndividual->setCanResume(canResume); + + sDebugOut << endl; +} + + +/** No descriptions */ +void Transfer::slotExecDelay() +{ + sDebugIn << endl; + + mode = MD_DELAYED; + status = ST_STOPPED; + slotSpeed(0); //need??????? + m_paDelay->setChecked(true); + emit statusChanged(this, OP_DELAYED); + + sDebugOut << endl; +} + +/** No descriptions */ +void Transfer::slotExecSchedule() +{ + sDebugIn << endl; + + mode = MD_SCHEDULED; + status = ST_STOPPED; + m_paTimer->setChecked(true); + emit statusChanged(this, OP_SCHEDULED); + + sDebugOut << endl; +} + +/** No descriptions */ +void Transfer::slotStartTime(const QDateTime & _startTime) +{ + sDebugIn << endl; + + setStartTime(_startTime); + sDebugOut << endl; +} + +/** return true if the dlgIndividual is Visible */ +bool Transfer::isVisible() const +{ + return dlgIndividual ? dlgIndividual->isVisible() : false; +} + +bool Transfer::keepDialogOpen() const +{ + return dlgIndividual ? dlgIndividual->keepDialogOpen() : false; +} + +void Transfer::maybeShow() +{ + if ( ksettings.b_showIndividual && getStatus() != Transfer::ST_FINISHED ) + { + if(dlgIndividual) + dlgIndividual->show(); + } +} + +bool Transfer::retryOnError() +{ + return (ksettings.b_reconnectOnError && (retryCount < ksettings.reconnectRetries)); +} + +bool Transfer::retryOnBroken() +{ + return (ksettings.b_reconnectOnBroken && (retryCount < ksettings.reconnectRetries)); +} + +void Transfer::checkCache() +{ + assert (mode == MD_NEW); + + if (src.protocol()=="http") + { + status = ST_TRYING; + m_pSlave->Op(Slave::RETR_CACHE); + } + else + NotInCache(); +} + +void Transfer::NotInCache() +{ + logMessage(i18n("checking if file is in cache...no")); + if (ksettings.b_addQueued) + mode = MD_QUEUED; + else + mode = MD_DELAYED; + status = ST_STOPPED; +} +#include "transfer.moc" + diff --git a/kget/transfer.h b/kget/transfer.h new file mode 100644 index 00000000..845995d8 --- /dev/null +++ b/kget/transfer.h @@ -0,0 +1,241 @@ +/*************************************************************************** +* transfer.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + + +#ifndef _Transfer_h +#define _Transfer_h + +#include +#include +#include +#include + +#include +#include + +#include "slave.h" + +class QTimer; + +class KSimpleConfig; +class KAction; +class KRadioAction; + + + +class DlgIndividual; +class TransferList; + + +class Transfer:public QObject, public KListViewItem +{ + Q_OBJECT +public: + enum TransferMode { MD_QUEUED, MD_DELAYED, MD_SCHEDULED, MD_NONE, MD_NEW }; + + enum TransferStatus { ST_TRYING, ST_RUNNING, ST_STOPPED, ST_FINISHED }; + + enum TransferOperation { + OP_FINISHED, OP_RESUMED, OP_PAUSED, OP_REMOVED, OP_ABORTED, + OP_QUEUED, OP_SCHEDULED, OP_DELAYED, OP_CONNECTED + }; + + + + Transfer(TransferList * view, const KURL & _src, const KURL & _dest, const uint _id=0); + Transfer(TransferList * view, Transfer * after, const KURL & _src, const KURL & _dest, const uint _id=0); + ~Transfer(); + + void synchronousAbort(); + + bool read(KSimpleConfig * config, int id); + void write(KSimpleConfig * config, int id); + void logMessage(const QString & message); + + bool keepDialogOpen() const; + + + QDateTime getStartTime()const + { + return startTime; + } + unsigned int getRemainingTime()const + { + return remainingTimeSec; + } + + KIO::filesize_t getTotalSize()const + { + return totalSize; + } + KIO::filesize_t getProcessedSize()const + { + return processedSize; + } + + KURL getSrc()const + { + return src; + } + KURL getDest()const + { + return dest; + } + int getPercent()const + { + return percent; + } + + int getSpeed()const + { + return speed; + } + TransferStatus getStatus()const + { + return status; + } + int getMode()const + { + return mode; + } + + void setMode(TransferMode _mode) + { + mode = _mode; + } + void setStatus(TransferStatus _status) + { + status = _status; + }; + void setStartTime(QDateTime _startTime) + { + startTime = _startTime; + }; + void setSpeed(unsigned long _speed); + + // update methods + void updateAll(); + bool updateStatus(int counter); + + void showIndividual(); + void UpdateRetry(); + + + // actions + KAction *m_paResume, *m_paPause, *m_paDelete, *m_paRestart; + //KAction *m_paDock; + KRadioAction *m_paQueue, *m_paTimer, *m_paDelay; + +public: + void slotExecPause(); + void slotExecResume(); + void slotExecRemove(); + void slotExecDelay(); + void slotExecSchedule(); + void slotExecConnected(); + void slotExecError(); + void slotExecBroken(); + void slotCanResume(bool _bCanResume); + void slotSpeed(unsigned long); + void checkCache(); + + bool isVisible() const; + void maybeShow(); + + bool retryOnError(); + bool retryOnBroken(); + +public slots: + // operation methods + void slotResume(); + void slotRequestPause(); + void slotRequestRemove(); + void slotRequestSchedule(); + void slotRequestDelay(); + void NotInCache(); + void slotRequestRestart(); + void slotUpdateActions(); + + void slotQueue(); + void slotFinished(); + + void slotTotalSize(KIO::filesize_t bytes); + void slotProcessedSize(KIO::filesize_t); + + void slotStartTime(const QDateTime &); + void slotStop(); // stop all transfers when going offline + +signals: + void statusChanged(Transfer *, int _operation); + void log(uint, const QString &, const QString &); + +private: + void init(const uint _id); + + Slave *m_pSlave; + + KURL src; + KURL dest; + + /* the tranfer id number */ + uint id; + + static uint idcount; + + // log + QString transferLog; + + // schedule time + QDateTime startTime; + + KIO::filesize_t totalSize; + KIO::filesize_t processedSize; + int percent; + + int speed; + unsigned int remainingTimeSec; + QString remainingTime; + + TransferStatus status; + TransferMode mode; + + TransferStatus prevStatus; + TransferMode prevMode; + + // how many times have we retried already + unsigned int retryCount; + + bool canResume; + + TransferList *view; + + // individual download window + QGuardedPtr dlgIndividual; + +}; + + +#endif // _Transfer_h diff --git a/kget/transferlist.cpp b/kget/transferlist.cpp new file mode 100644 index 00000000..1d9ed902 --- /dev/null +++ b/kget/transferlist.cpp @@ -0,0 +1,283 @@ +/*************************************************************************** +* transferlist.cpp +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "transfer.h" +#include "transferlist.h" + +#define NUM_COLS 9 + +static int defaultColumnWidth[] = { + 26, // PIXMAP + 160, // LOCAL FILENAME + 40, // RESUME + 60, // COUNT + 30, // PROGRESS + 65, // TOTAL + 70, // SPEED + 70, // REMAINING TIME + 450 // URL + }; + + +TransferList::TransferList(QWidget * parent, const char *name) + : KListView(parent, name) +{ + // enable selection of more than one item + setSelectionMode( QListView::Extended ); + + // // disable sorting and clicking on headers + // setSorting( -1 ); + + setAllColumnsShowFocus(true); + + lv_pixmap = addColumn(i18n("S")); + lv_filename = addColumn(i18n("Local File Name")); + lv_resume = addColumn(i18n("Resumed")); + lv_count = addColumn(i18n("Count")); + lv_progress = addColumn(i18n("%")); + lv_total = addColumn(i18n("Total")); + lv_speed = addColumn(i18n("Speed")); + lv_remaining = addColumn(i18n("Rem. Time")); + lv_url = addColumn(i18n("Address (URL)")); + + jobid=1; + + // initial layout + KConfig *config = KGlobal::config(); + config->setGroup("ListView"); + if ( config->readListEntry("ColumnWidths").isEmpty() ) + { + for (int i = 0; i < NUM_COLS; i++) + setColumnWidth(i, defaultColumnWidth[i]); + } + else + restoreLayout( KGlobal::config(), "ListView" ); + + QString connectPath = "pics/connect%2.png"; + QString tryPath = "pics/try%2.png"; + + // Load animations + QPixmap* curPix; + if (animConn.count() == 0) { + animConn.setAutoDelete(true); + animTry.setAutoDelete(true); + for (int i = 0; i < 8; i++) { + curPix = new QPixmap(); + curPix->load(locate("appdata", connectPath.arg(i))); + animConn.append(curPix); + + curPix = new QPixmap(); + curPix->load(locate("appdata", tryPath.arg(i))); + animTry.append(curPix); + } + } + + pixQueued = UserIcon("md_queued"); + pixScheduled = UserIcon("md_scheduled"); + pixDelayed = UserIcon("md_delayed.png"); + pixFinished = UserIcon("md_finished"); + pixRetrying = UserIcon("retrying"); + + phasesNum = animConn.count(); + + connect(this, SIGNAL(doubleClicked(QListViewItem *)), SLOT(slotTransferSelected(QListViewItem *))); + connect(this, SIGNAL(rightButtonPressed(QListViewItem *, const QPoint &, int)), SLOT(slotPopupMenu(QListViewItem *))); +} + + +TransferList::~TransferList() +{ + saveLayout( KGlobal::config(), "ListView" ); +} + + +Transfer *TransferList::addTransfer(const KURL & _source, const KURL & _dest, + bool canShow) +{ + Transfer *last = static_cast( lastItem() ); + Transfer *new_item = new Transfer(this, last, _source, _dest, jobid); + jobid++; + if ( canShow ) + new_item->maybeShow(); + + return new_item; +} + + +void TransferList::slotTransferSelected(QListViewItem * item) +{ + emit transferSelected((Transfer *) item); +} + + +void TransferList::slotPopupMenu(QListViewItem * item) +{ + if (!item) + return; + emit popupMenu((Transfer *) item); +} + + +void TransferList::setSelected(QListViewItem * item, bool selected) +{ + bool tmpb = selected; + + if (tmpb && item->isSelected()) { + tmpb = false; + } + + QListView::setSelected(item, tmpb); +} + + +void TransferList::moveToBegin(Transfer * item) +{ + // ASSERT(item); + + Transfer *oldfirst=static_cast(firstChild()); + item->moveItem(oldfirst); //move item after oldfirst + oldfirst->moveItem(item); //move oldfirst after item +} + + +void TransferList::moveToEnd(Transfer * item) +{ + // ASSERT(item); + + Transfer *oldlast=static_cast(lastItem()); + item->moveItem(oldlast); +} + + +bool TransferList::updateStatus(int counter) +{ + bool isTransfer = false; + + TransferIterator it(this); + + for (; it.current(); ++it) { + isTransfer |= it.current()->updateStatus(counter); + } + + return isTransfer; +} + + +bool TransferList::areTransfersQueuedOrScheduled() +{ + TransferIterator it(this); + + if (childCount() > 0) { + for (; it.current(); ++it) { + if ((it.current()->getMode() == Transfer::MD_QUEUED)|| \ + (it.current()->getMode() == Transfer::MD_SCHEDULED)) + return true; + } + } + return false; +} + + +Transfer * TransferList::find(const KURL& _src) +{ + TransferIterator it(this); + + for (; it.current(); ++it) { + if (it.current()->getSrc() == _src) { + return it.current(); + } + } + + return 0L; +} + + +void TransferList::readTransfers(const KURL& file) +{ + QString tmpFile; + + if (KIO::NetAccess::download(file, tmpFile, (QWidget*)parent())) { + KSimpleConfig config(tmpFile); + + config.setGroup("Common"); + int num = config.readNumEntry("Count", 0); + + Transfer *item; + KURL src, dest; + + for ( int i = 0; i < num; i++ ) + { + QString str; + + str.sprintf("Item%d", i); + config.setGroup(str); + + src = KURL::fromPathOrURL( config.readPathEntry("Source") ); + dest = KURL::fromPathOrURL( config.readPathEntry("Dest") ); + item = addTransfer( src, dest, false ); // don't show! + + if (!item->read(&config, i)) + delete item; + else + { + // configuration read, now we know the status to determine + // whether to show or not + item->maybeShow(); + } + } + } +} + +void TransferList::writeTransfers(const QString& file) +{ + sDebug << ">>>>Entering with file =" << file << endl; + + KSimpleConfig config(file); + int num = childCount(); + + config.setGroup("Common"); + config.writeEntry("Count", num); + + TransferIterator it(this); + + for (int id = 0; it.current(); ++it, ++id) + it.current()->write(&config, id); + config.sync(); + + sDebug << "<<< +#include + +#include + +class Transfer; + +class TransferIterator:public QListViewItemIterator +{ + +public: + + TransferIterator(QListView * view):QListViewItemIterator(view) + { + } + Transfer *current() const + { + return (Transfer *) QListViewItemIterator::current(); + } + void reset() + { + curr = listView->firstChild(); + } + +}; + + +class TransferList:public KListView +{ +Q_OBJECT public: + + + TransferList(QWidget * parent = 0, const char *name = 0); + virtual ~ TransferList(); + + Transfer *addTransfer(const KURL & _source, const KURL & _dest, + bool canShow = true ); + + virtual void setSelected(QListViewItem * item, bool selected); + + void moveToBegin(Transfer * item); + void moveToEnd(Transfer * item); + + uint getPhasesNum()const + { + return phasesNum; + } + bool updateStatus(int counter); + Transfer * find(const KURL& _src); + bool areTransfersQueuedOrScheduled(); + + void readTransfers(const KURL& file); + void writeTransfers(const QString& file); + + friend class Transfer; + +signals: + void transferSelected(Transfer * item); + void popupMenu(Transfer * item); + +protected slots: + void slotTransferSelected(QListViewItem * item); + void slotPopupMenu(QListViewItem * item); + +protected: + + void readConfig(); + void writeConfig(); + + // ListView IDs + int lv_pixmap, lv_filename, lv_resume, lv_count, lv_progress; + int lv_total, lv_speed, lv_remaining, lv_url; + + QPtrList < QPixmap > animConn; + QPtrList < QPixmap > animTry; + QPixmap pixQueued; + QPixmap pixScheduled; + QPixmap pixDelayed; + QPixmap pixFinished; + QPixmap pixRetrying; + + uint phasesNum; + uint jobid; +}; + + +#endif // _TransferList_h diff --git a/kget/version.h b/kget/version.h new file mode 100644 index 00000000..1374e278 --- /dev/null +++ b/kget/version.h @@ -0,0 +1,35 @@ +/*************************************************************************** +* version.h +* ------------------- +* +* Revision : $Id$ +* begin : Tue Jan 29 2002 +* copyright : (C) 2002 by Patrick Charbonnier +* : Based On Caitoo v.0.7.3 (c) 1998 - 2000, Matej Koss +* email : pch@freeshell.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. + * + ***************************************************************************/ + +#ifndef _VERSION_H_ +#define _VERSION_H_ + + +#define KGETVERSION "v0.8.5" +#define KGET_APP_NAME "kget" + + +#endif diff --git a/kget/x-kgetlist.desktop b/kget/x-kgetlist.desktop new file mode 100644 index 00000000..e6a79308 --- /dev/null +++ b/kget/x-kgetlist.desktop @@ -0,0 +1,74 @@ +[Desktop Entry] +X-KDE-AutoEmbed=false +Comment=Kget Download List +Comment[ar]=قائمة تنزيل Ùƒ.جيت +Comment[be]=Ð¡Ð¿Ñ–Ñ ÑцÑÐ³Ð²Ð°Ð½Ð½Ñ Kget +Comment[bg]=СпиÑък за изтеглÑне на Kget +Comment[bn]=কে-গেট ডাউনলোড তালিকা +Comment[br]=Roll enkargañ Kget +Comment[bs]=KGet download lista +Comment[ca]=Llista de descàrrega del Kget +Comment[cs]=Seznam stahování KGet +Comment[cy]=Rhestr Lawrlwytho KNôl +Comment[da]=KGet-download-liste +Comment[de]=Kget Dateiliste zum Herunterladen +Comment[el]=Λίστα λήψεων αÏχείων του Kget +Comment[eo]=Kget DeÅutolisto +Comment[es]=Lista de descarga de KGet +Comment[et]=KGeti allalaadimise nimekiri +Comment[eu]=Kget deskargatzeko zerrenda +Comment[fa]=Ùهرست بارگیری Kget +Comment[fi]=Kget-hakulista +Comment[fr]=Liste de téléchargement de KGet +Comment[ga]=Liosta Thíosluchtaithe Kget +Comment[gl]=Lista de Descargas de KGet +Comment[he]=רשימת הורדה של Kget +Comment[hi]=के-गेट डाउनलोड सूची +Comment[hr]=Kget lista za preuzimanje +Comment[hu]=KGet letöltési lista +Comment[is]=Kget niðurhalslisti +Comment[it]=Lista scaricamenti di KGet +Comment[ja]=Kget ダウンロードリスト +Comment[ka]=Kget ჩáƒáƒ›áƒáƒ¥áƒáƒ©áƒ•áƒáƒ—რსირ+Comment[kk]=Kget жүктеу тізімі +Comment[km]=បញ្ជី​ការ​ទាញយក​របស់ Kget +Comment[lt]=Kget atsisiuntimų sÄ…raÅ¡as +Comment[mk]=ЛиÑта на Ñимнувања во Kget +Comment[mn]=KGet жагÑаалтыг Татаж авах +Comment[ms]=Senarai Muat turun Kget +Comment[mt]=Lista ta' downloads KGet +Comment[nb]=Kget nedlastingsliste +Comment[nds]=KGet-Daalladenlist +Comment[ne]=केडीई गेट डाउनलोड सूची +Comment[nl]=Kget-downloadlijst +Comment[nn]=Nedlastingsliste for Kget +Comment[nso]=Palo ya Ngwalollo ya Kget +Comment[pa]=Kget ਡਾਊਨਲੋਡ ਸੂਚੀ +Comment[pl]=Lista plików do pobrania przez KGet +Comment[pt]=Lista de Transferências do KGet +Comment[pt_BR]=Lista de Download do Kget +Comment[ro]=Listă de transfer KGet +Comment[ru]=СпиÑок заданий KGet +Comment[se]=Kget:a viežžanlisttu +Comment[sk]=Kget zoznam sÅ¥ahovania +Comment[sl]=Seznam datotek za prenos s KGet +Comment[sr]=Kget-ова лиÑта преузимања +Comment[sr@Latn]=Kget-ova lista preuzimanja +Comment[sv]=Nerladdningslista för Kget +Comment[ta]=கேகெட௠பதிவிறகà¯à®• படà¯à®Ÿà®¿à®¯à®²à¯ +Comment[tg]=Рӯйхати Боркунии Kget +Comment[th]=รายà¸à¸²à¸£à¸”าวน์โหลด K +Comment[tr]=KGet Ä°ndirme Listesi +Comment[uk]=СпиÑок Ð·Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ kget +Comment[ven]=Mutevhe wau hwesulula wa Kget +Comment[wa]=Djivêye des aberwetaedjes di Kget +Comment[xh]=Uluhlu lwe Kget loku Layishela ezantsi +Comment[zh_CN]=Kget ä¸‹è½½æ¸…å• +Comment[zh_HK]=Kget 下載清單 +Comment[zh_TW]=Kget 下載清單 +Comment[zu]=I-Kget ye-listi Yokugcwalisela phansi +Icon=kget_list +Type=MimeType +MimeType=application/x-kgetlist +Patterns=*.kgt; +DefaultApp=KGet diff --git a/knewsticker/AUTHORS b/knewsticker/AUTHORS new file mode 100644 index 00000000..5c626223 --- /dev/null +++ b/knewsticker/AUTHORS @@ -0,0 +1,2 @@ +Frerich Raabe +Malte Starostik diff --git a/knewsticker/COPYING b/knewsticker/COPYING new file mode 100644 index 00000000..cca2a5c9 --- /dev/null +++ b/knewsticker/COPYING @@ -0,0 +1,20 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/knewsticker/Makefile.am b/knewsticker/Makefile.am new file mode 100644 index 00000000..eea97be4 --- /dev/null +++ b/knewsticker/Makefile.am @@ -0,0 +1,34 @@ +SUBDIRS = common kntsrcfilepropsdlg knewstickerstub +INCLUDES = -I$(top_srcdir)/knewsticker/common $(all_includes) + +kde_module_LTLIBRARIES = knewsticker_panelapplet.la + +noinst_HEADERS = newsscroller.h knewsticker.h + +METASOURCES = AUTO + +knewsticker_panelapplet_la_SOURCES = newsscroller.cpp \ + knewsticker.cpp knewsticker.skel \ + knewstickerconfig.cpp knewstickerconfigwidget.ui \ + newssourcedlgimpl.cpp newssourcedlg.ui +knewsticker_panelapplet_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module +knewsticker_panelapplet_la_LIBADD = common/libknewstickercommon.la \ + $(LIB_KIO) $(LIB_KDEUI) + +appdatadir = $(kde_datadir)/knewsticker +appdata_DATA = eventsrc + +updatedir = $(kde_datadir)/kconf_update +update_DATA = knewsticker.upd +update_SCRIPTS = knt-0.1-0.2.pl + +lnkdir = $(kde_datadir)/kicker/applets +lnk_DATA = knewsticker.desktop + +xdg_apps_DATA = knewsticker-standalone.desktop + +KDE_ICON = AUTO + +messages: rc.cpp + $(EXTRACTRC) *.ui */*.ui >> rc.cpp + $(XGETTEXT) *.cpp *.h */*.cpp */*.h -o $(podir)/knewsticker.pot diff --git a/knewsticker/TODO b/knewsticker/TODO new file mode 100644 index 00000000..6d586831 --- /dev/null +++ b/knewsticker/TODO @@ -0,0 +1,10 @@ +TODO +---- +* Make KNewsTicker choose one of the rotated modes as soon as Kicker is moved + to one of the sides. +* Fix the highlighting of the headlines, right now the headlines stay + highlighted sometimes even if the mouse is moved away (i.e. with the context + menu) +* Make the "Suggest" function in the news source dialog work for program news + sources. +* Make it possible to pass parameters to external program newssources. diff --git a/knewsticker/common/Makefile.am b/knewsticker/common/Makefile.am new file mode 100644 index 00000000..60fddef7 --- /dev/null +++ b/knewsticker/common/Makefile.am @@ -0,0 +1,13 @@ +INCLUDES = $(all_includes) + +noinst_LTLIBRARIES = libknewstickercommon.la + +noinst_HEADERS = configiface.h configaccess.h newsiconmgr.h xmlnewsaccess.h \ + newsengine.h + +METASOURCES = AUTO + +libknewstickercommon_la_SOURCES = newsiconmgr.cpp xmlnewsaccess.cpp \ + configaccess.cpp newsiconmgr.skel newsengine.cpp +libknewstickercommon_la_LIBADD = $(LIB_KIO) $(LIB_KDECORE) +libknewstickercommon_la_LDFLAGS = $(all_libraries) -no-undefined diff --git a/knewsticker/common/configaccess.cpp b/knewsticker/common/configaccess.cpp new file mode 100644 index 00000000..942b3f70 --- /dev/null +++ b/knewsticker/common/configaccess.cpp @@ -0,0 +1,674 @@ +/* + * configaccess.cpp + * + * Copyright (c) 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include "configaccess.h" +#include "newsengine.h" + +#include + +#include +#include +#include + +static NewsSourceBase::Data NewsSourceDefault[DEFAULT_NEWSSOURCES] = { + // Arts --------------- + NewsSourceBase::Data( + QString::fromLatin1("Bureau 42"), + QString::fromLatin1("http://www.bureau42.com/rdf/"), + QString::fromLatin1("http://www.bureau42.com/favicon.ico"), + NewsSourceBase::Arts, 5, false, false), + NewsSourceBase::Data( + QString::fromLatin1("eFilmCritic"), + QString::fromLatin1("http://efilmcritic.com/fo.rdf"), + QString::fromLatin1("http://efilmcritic.com/favicon.ico"), + NewsSourceBase::Arts, 3, false, false), + NewsSourceBase::Data( + QString::fromLatin1("superhits.ch"), + QString::fromLatin1("http://www.superhits.ch/cgi-bin/superhits.cgi?page=rdf"), + QString::fromLatin1("http://www.superhits.ch/favicon.ico"), + NewsSourceBase::Arts, 10, false, false, QString::fromLatin1("de")), + // Business ----------- + NewsSourceBase::Data( + QString::fromLatin1("Internet.com Business"), + QString::fromLatin1("http://headlines.internet.com/internetnews/bus-news/news.rss"), + QString::null, + NewsSourceBase::Business, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("TradeSims"), + QString::fromLatin1("http://www.tradesims.com/AEX.rdf"), + QString::null, + NewsSourceBase::Business, 10, false, false), + // Computers ---------- + NewsSourceBase::Data( + QString::fromLatin1( "linuxartist.org" ), + QString::fromLatin1( "http://www.linuxartist.org/backend.php"), + QString::fromLatin1( "http://www.linuxartist.org/favicon.ico"), + NewsSourceBase::Computers, 10, false, false ), + NewsSourceBase::Data( + QString::fromLatin1("KDE Deutschland"), + QString::fromLatin1("http://www.kde.de/nachrichten/nachrichten.rdf"), + QString::fromLatin1("http://www.kde.de/favicon.ico"), + NewsSourceBase::Computers, 10, true, false, QString::fromLatin1("de")), + NewsSourceBase::Data( + QString::fromLatin1("KDE France"), + QString::fromLatin1("http://www.kde-france.org/backend-breves.php3"), + QString::null, + NewsSourceBase::Computers, 10, true, false, QString::fromLatin1("fr")), + NewsSourceBase::Data( + QString::fromLatin1("FreeBSD Project News"), + QString::fromLatin1("http://www.freebsd.org/news/news.rdf"), + QString::fromLatin1("http://www.freebsd.org/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("dot.kde.org"), + QString::fromLatin1("http://www.kde.org/dotkdeorg.rdf"), + QString::fromLatin1("http://www.kde.org/favicon.ico"), + NewsSourceBase::Computers, 10, true, false), + NewsSourceBase::Data( + QString::fromLatin1("GNOME News"), + QString::fromLatin1("http://www.gnomedesktop.org/backend.php"), + QString::null, + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("Slashdot"), + QString::fromLatin1("http://slashdot.org/slashdot.rdf"), + QString::fromLatin1("http://slashdot.org/favicon.ico"), + NewsSourceBase::Computers, 10, true, false), + NewsSourceBase::Data( + QString::fromLatin1("Ask Slashdot"), + QString::fromLatin1("http://slashdot.org/askslashdot.rdf"), + QString::fromLatin1("http://slashdot.org/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("Slashdot: Features"), + QString::fromLatin1("http://slashdot.org/features.rdf"), + QString::fromLatin1("http://slashdot.org/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("Slashdot: Apache"), + QString::fromLatin1("http://slashdot.org/apache.rdf"), + QString::fromLatin1("http://slashdot.org/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("Slashdot: Books"), + QString::fromLatin1("http://slashdot.org/books.rdf"), + QString::fromLatin1("http://slashdot.org/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("Jabber News"), + QString::fromLatin1("http://www.jabber.org/news/rss.xml"), + QString::null, + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("Freshmeat"), + QString::fromLatin1("http://freshmeat.net/backend/fm.rdf"), + QString::fromLatin1("http://freshmeat.net/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("Linux Weekly News"), + QString::fromLatin1("http://www.lwn.net/headlines/rss"), + QString::fromLatin1("http://www.lwn.net/favicon.ico"), + NewsSourceBase::Computers, 10, true, false), + NewsSourceBase::Data( + QString::fromLatin1("heise online news"), + QString::fromLatin1("http://www.heise.de/newsticker/heise.rdf"), + QString::fromLatin1("http://www.heise.de/favicon.ico"), + NewsSourceBase::Computers, 10, true, false, QString::fromLatin1("de")), + NewsSourceBase::Data( + QString::fromLatin1("RUS-CERT Ticker"), + QString::fromLatin1("http://cert.uni-stuttgart.de/ticker/rus-cert.rdf"), + QString::fromLatin1("http://cert.uni-stuttgart.de/favicon.ico"), + NewsSourceBase::Computers, 10, true, false, QString::fromLatin1("de")), + NewsSourceBase::Data( + QString::fromLatin1("RUS-CERT Elsewhere"), + QString::fromLatin1("http://cert.uni-stuttgart.de/ticker/rus-cert-elsewhere.rdf"), + QString::fromLatin1("http://cert.uni-stuttgart.de/favicon.ico"), + NewsSourceBase::Computers, 10, false, false, QString::fromLatin1("de")), + NewsSourceBase::Data( + QString::fromLatin1("Kuro5hin"), + QString::fromLatin1("http://kuro5hin.org/backend.rdf"), + QString::fromLatin1("http://kuro5hin.org/favicon.ico"), + NewsSourceBase::Computers, 10, true, false), + NewsSourceBase::Data( + QString::fromLatin1("Prolinux"), + QString::fromLatin1("http://www.pl-forum.de/backend/pro-linux.rdf"), + QString::fromLatin1("http://www.prolinux.de/favicon.ico"), + NewsSourceBase::Computers, 10, false, false, QString::fromLatin1("de")), + NewsSourceBase::Data( + QString::fromLatin1("Linuxde.org"), + QString::fromLatin1("http://www.linuxde.org/backends/news.rdf"), + QString::fromLatin1("http://www.linuxde.org/favicon.ico"), + NewsSourceBase::Computers, 10, false, false, QString::fromLatin1("de")), + NewsSourceBase::Data( + QString::fromLatin1("LinuxSecurity.com"), + QString::fromLatin1("http://www.linuxsecurity.com/linuxsecurity_hybrid.rdf"), + QString::null, + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("Linux Game Tome"), + QString::fromLatin1("http://happypenguin.org/html/news.rdf"), + QString::null, + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("Telefragged"), + QString::fromLatin1("http://www.telefragged.com/cgi-bin/rdf.pl"), + QString::null, + NewsSourceBase::Computers, 10, false, false), +/* NewsSourceBase::Data( + QString::fromLatin1("Gimp News"), + QString::fromLatin1("http://www.xach.com/gimp/news/channel.rdf"), + QString::null, + NewsSourceBase::Computers, 10, false, false),*/ + NewsSourceBase::Data( + QString::fromLatin1("Mozilla"), + QString::fromLatin1("http://www.mozilla.org/news.rdf"), + QString::fromLatin1("http://www.mozillazine.org/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("MozillaZine"), + QString::fromLatin1("http://www.mozillazine.org/contents.rdf"), + QString::fromLatin1("http://www.mozillazine.org/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("Daemon News"), + QString::fromLatin1("http://daily.daemonnews.org/ddn.rdf.php3"), + QString::null, + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("use Perl;"), + QString::fromLatin1("http://use.perl.org/useperl.rdf"), + QString::null, + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("desktopian.org"), + QString::fromLatin1("http://www.desktopian.org/includes/headlines.xml"), + QString::fromLatin1("http://www.desktopian.org/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("Root prompt"), + QString::fromLatin1("http://www.rootprompt.org/rss/"), + QString::fromLatin1("http://www.rootprompt.org/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("SecurityFocus"), + QString::fromLatin1("http://www.securityfocus.com/rss/news.xml"), + QString::fromLatin1("http://www.securityfocus.com/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("LinuxNewbie"), + QString::fromLatin1("http://www.linuxnewbie.org/news.cdf"), + QString::fromLatin1("http://www.linuxnewbie.org/favicon.ico"), + NewsSourceBase::Computers, 5, false, false), + NewsSourceBase::Data( + QString::fromLatin1("Arstechnica"), + QString::fromLatin1("http://arstechnica.com/etc/rdf/ars.rdf"), + QString::fromLatin1("http://arstechnica.com/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("amiga-news.de - deutschsprachige Amiga Nachrichten"), + QString::fromLatin1("http://www.amiga-news.de/de/backends/news/index.rss"), + QString::fromLatin1("http://www.amiga-news.de/favicon.ico"), + NewsSourceBase::Computers, 10, false, false, QString::fromLatin1("de")), + NewsSourceBase::Data( + QString::fromLatin1("amiga-news.de - english Amiga news"), + QString::fromLatin1("http://www.amiga-news.de/en/backends/news/index.rss"), + QString::fromLatin1("http://www.amiga-news.de/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("RadioTux)"), + QString::fromLatin1("http://blog.radiotux.de/feed/"), + QString::null, + NewsSourceBase::Computers, 10, false, false, QString::fromLatin1("de")), + NewsSourceBase::Data( + QString::fromLatin1("kdenews.unixcode.org"), + QString::fromLatin1("http://kdenews.unixcode.org/?node=news&action=rss"), + QString::null, + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("FreshPorts - the place for ports"), + QString::fromLatin1("http://www.freshports.org/news.php"), + QString::fromLatin1("http://www.freshports.org/favicon.ico"), + NewsSourceBase::Computers, 20, false, false), + NewsSourceBase::Data( + QString::fromLatin1("NetPhoenix"), + QString::fromLatin1("http://www.netphoenix.at/rss/netphoenix.php"), + QString::fromLatin1("http://www.netphoenix.at/favicon.ico"), + NewsSourceBase::Computers, 10, false, false, QString::fromLatin1("de")), + NewsSourceBase::Data( + QString::fromLatin1("ShortNews - by www.netphoenix.at"), + QString::fromLatin1("http://www.netphoenix.at/rss/shortnews.php"), + QString::fromLatin1("http://www.netphoenix.at/favicon.ico"), + NewsSourceBase::Computers, 10, false, false, QString::fromLatin1("de")), + NewsSourceBase::Data( + QString::fromLatin1("zez.org - about code "), + QString::fromLatin1("http://zez.org/article/rssheadlines"), + QString::null, + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("BSDatwork.com"), + QString::fromLatin1("http://BSDatwork.com/backend.php"), + QString::fromLatin1("http://BSDatwork.com/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("FreshSource - the place for source"), + QString::fromLatin1("http://www.freshsource.org/news.php"), + QString::fromLatin1("http://www.freshsource.org/favicon.ico"), + NewsSourceBase::Computers, 20, false, false), + NewsSourceBase::Data( + QString::fromLatin1("The FreeBSD Diary"), + QString::fromLatin1("http://www.freebsddiary.org/news.php"), + QString::fromLatin1("http://www.freebsddiary.org/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("MaximumBSD"), + QString::fromLatin1("http://www.maximumbsd.com/backend/mb.rdf"), + QString::fromLatin1("http://www.maximumbsd.com/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("BR-Linux.org"), + QString::fromLatin1("http://br-linux.org/noticias/index.rdf"), + QString::fromLatin1("http://br-linux.org/noticias/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("OSNews.com"), + QString::fromLatin1("http://www.osnews.com/files/recent.rdf"), + QString::fromLatin1("http://www.osnews.com/favicon.ico"), + NewsSourceBase::Computers, 10, false, false), + // Miscellaneous ------ + NewsSourceBase::Data( + QString::fromLatin1("tagesschau.de"), + QString::fromLatin1("http://www.tagesschau.de/newsticker.rdf"), + QString::fromLatin1("http://www.tagesschau.de/favicon.ico"), + NewsSourceBase::Misc, 10, true, false, QString::fromLatin1("de")), + NewsSourceBase::Data( + QString::fromLatin1("N24.de"), + QString::fromLatin1("http://www.n24.de/rss/?rubrik=home"), + QString::fromLatin1("http://www.n24.de/favicon.ico"), + NewsSourceBase::Misc, 10, true, false, QString::fromLatin1("de")), +/* + NewsSourceBase::Data( + QString::fromLatin1("CNN"), + QString::fromLatin1("http://www.cnn.com/cnn.rss"), + QString::fromLatin1("http://www.cnn.com/favicon.ico"), + NewsSourceBase::Misc, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("CNN Europe"), + QString::fromLatin1("http://europe.cnn.com/cnn.rss"), + QString::fromLatin1("http://europe.cnn.com/favicon.ico"), + NewsSourceBase::Misc, 10, false, false), +*/ + NewsSourceBase::Data( + QString::fromLatin1("HotWired"), + QString::fromLatin1("http://www.hotwired.com/webmonkey/meta/headlines.rdf"), + QString::fromLatin1("http://www.hotwired.com/favicon.ico"), + NewsSourceBase::Misc, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1("The Register"), + QString::fromLatin1("http://www.theregister.co.uk/tonys/slashdot.rdf"), + QString::fromLatin1("http://www.theregister.co.uk/favicon.ico"), + NewsSourceBase::Misc, 10, false, false), + NewsSourceBase::Data( + QString::fromLatin1( "Christian Science Monitor" ), + QString::fromLatin1( "http://www.csmonitor.com/rss/csm.rss"), + QString::fromLatin1( "http://www.csmonitor.com/favicon.ico"), + NewsSourceBase::Misc, 10, false, false ), + // Magazines ------ + NewsSourceBase::Data( + QString::fromLatin1("Revista do Linux"), + QString::fromLatin1("http://www.revistadolinux.com.br/noticias/arquivo/noticias.rdf"), + QString::fromLatin1("http://www.revistadolinux.com.br/favicon.ico"), + NewsSourceBase::Magazines, 10, false, false /*, QString::fromLatin1("br")*/ ), + NewsSourceBase::Data( + QString::fromLatin1("Spiegel.de"), + QString::fromLatin1("http://www.spiegel.de/schlagzeilen/rss/0,5291,,00.xml"), + QString::fromLatin1("http://www.spiegel.de/favicon.ico"), + NewsSourceBase::Magazines, 10, true, false, QString::fromLatin1("de")), + NewsSourceBase::Data( + QString::fromLatin1("FAZ.de"), + QString::fromLatin1("http://www.faz.net/s/Rub/Tpl~Epartner~SRss_.xml"), + QString::fromLatin1("http://www.faz.net/favicon.ico"), + NewsSourceBase::Magazines, 10, true, false, QString::fromLatin1("de")), + // Recreation + NewsSourceBase::Data( + QString::fromLatin1("Segfault"), + QString::fromLatin1("http://segfault.org/stories.xml"), + QString::fromLatin1("http://segfault.org/favicon.ico"), + NewsSourceBase::Recreation, 10, false, false), + // Society + NewsSourceBase::Data( + QString::fromLatin1("nippon.it"), + QString::fromLatin1("http://www.nippon.it/backend.it.php"), + QString::fromLatin1("http://www.nippon.it/favicon.ico"), + NewsSourceBase::Society, 10, false, false, QString::fromLatin1("it")), + NewsSourceBase::Data( + QString::fromLatin1( "gflash" ), + QString::fromLatin1( "http://www.gflash.de/backend.php"), + QString::fromLatin1( "http://www.gflash.de/favicon.ico"), + NewsSourceBase::Society, 10, false, false, QString::fromLatin1( "de" ) ), + NewsSourceBase::Data( + QString::fromLatin1( "Quintessenz" ), + QString::fromLatin1( "http://quintessenz.at/cgi-bin/rdf"), + QString::fromLatin1( "http://quintessenz.at/favicon.ico"), + NewsSourceBase::Society, 9, false, false, QString::fromLatin1( "de" ) ) +}; + +ArticleFilter::ArticleFilter(const QString &action, const QString &newsSource, + const QString &condition, const QString &expression, bool enabled) + : m_action(action), + m_newsSource(newsSource), + m_condition(condition), + m_expression(expression), + m_enabled(enabled) +{ +} + +bool ArticleFilter::matches(Article::Ptr a) const +{ + if (!enabled() || + (a->newsSource()->data().name != newsSource() && + newsSource() != i18n("all news sources"))) + return false; + + bool matches; + + if (condition() == i18n("contain")) + matches = a->headline().contains(expression()); + else if (condition() == i18n("do not contain")) + matches = !a->headline().contains(expression()); + else if (condition() == i18n("equal")) + matches = (a->headline() == expression()); + else if (condition() == i18n("do not equal")) + matches = (a->headline() != expression()); + else { // condition() == i18n("match") + QRegExp regexp = QRegExp(expression()); + matches = regexp.exactMatch(a->headline()); + } + + if (action() == i18n("Show")) + matches = !matches; + + return matches; +} + +ConfigAccess::ConfigAccess() + : m_defaultCfg(new KConfig(QString::null, true, false)) +{ + m_cfg = m_defaultCfg; +} + +ConfigAccess::ConfigAccess(KConfig *config) + : m_cfg(config), m_defaultCfg(0L) +{ + m_cfg->setGroup("KNewsTicker"); +} + +ConfigAccess::~ConfigAccess() +{ + delete m_defaultCfg; +} + +unsigned int ConfigAccess::interval() const +{ + return m_cfg->readNumEntry("Update interval", 30); +} + +void ConfigAccess::setInterval(const unsigned int interval) +{ + m_cfg->writeEntry("Update interval", interval); + m_cfg->sync(); +} + +unsigned int ConfigAccess::mouseWheelSpeed() const +{ + return m_cfg->readNumEntry("Mouse wheel speed", 5); +} + +void ConfigAccess::setMouseWheelSpeed(const unsigned int mouseWheelSpeed) +{ + m_cfg->writeEntry("Mouse wheel speed", mouseWheelSpeed); + m_cfg->sync(); +} + +QFont ConfigAccess::font() const +{ + QFont font = KGlobalSettings::fixedFont(); + return m_cfg->readFontEntry("Font", &font); +} + +void ConfigAccess::setFont(const QFont &font) +{ + m_cfg->writeEntry("Font", font); + m_cfg->sync(); +} + +bool ConfigAccess::customNames() const +{ + return m_cfg->readBoolEntry("Custom names", false); +} + +void ConfigAccess::setCustomNames(bool customNames) +{ + m_cfg->writeEntry("Custom names", customNames); + m_cfg->sync(); +} + +bool ConfigAccess::scrollMostRecentOnly() const +{ + return m_cfg->readBoolEntry("Scroll most recent headlines only", false); +} + +void ConfigAccess::setScrollMostRecentOnly(bool scrollMostRecentOnly) +{ + m_cfg->writeEntry("Scroll most recent headlines only", scrollMostRecentOnly); + m_cfg->sync(); +} + +bool ConfigAccess::offlineMode() const +{ + return m_cfg->readBoolEntry("Offline mode", false); +} + +void ConfigAccess::setOfflineMode(bool offlineMode) +{ + m_cfg->writeEntry("Offline mode", offlineMode); + m_cfg->sync(); +} + +QStringList ConfigAccess::newsSources() const +{ + QStringList tempList = m_cfg->readListEntry("News sources"); + if (tempList.isEmpty()) + for (unsigned int i = 0; i < DEFAULT_NEWSSOURCES; i++) + tempList << NewsSourceDefault[i].name; + return tempList; +} + +ArticleFilter::List ConfigAccess::filters() const +{ + return m_cfg->readIntListEntry("Filters"); +} + +void ConfigAccess::setNewsSources(const QStringList &newsSources) +{ + m_cfg->writeEntry("News sources", newsSources); + m_cfg->sync(); +} + +void ConfigAccess::setFilters(const ArticleFilter::List &filters) +{ + m_cfg->writeEntry("Filters", filters); + m_cfg->sync(); +} + +unsigned int ConfigAccess::scrollingSpeed() const +{ + return m_cfg->readNumEntry("Scrolling speed", 20); +} + +void ConfigAccess::setScrollingSpeed(const unsigned int scrollingSpeed) +{ + m_cfg->writeEntry("Scrolling speed", scrollingSpeed); + m_cfg->sync(); +} + +unsigned int ConfigAccess::scrollingDirection() const +{ + return m_cfg->readNumEntry("Scrolling direction", 0); +} + +void ConfigAccess::setScrollingDirection(const unsigned int scrollingDirection) +{ + m_cfg->writeEntry("Scrolling direction", scrollingDirection); + m_cfg->sync(); +} + +QColor ConfigAccess::foregroundColor() const +{ + return m_cfg->readColorEntry("Foreground color", &Qt::black); +} + +void ConfigAccess::setForegroundColor(const QColor &foregroundColor) +{ + m_cfg->writeEntry("Foreground color", foregroundColor); + m_cfg->sync(); +} + +QColor ConfigAccess::backgroundColor() const +{ + return m_cfg->readColorEntry("Background color", &Qt::white); +} + +void ConfigAccess::setBackgroundColor(const QColor &backgroundColor) +{ + m_cfg->writeEntry("Background color", backgroundColor); + m_cfg->sync(); +} + +QColor ConfigAccess::highlightedColor() const +{ + return m_cfg->readColorEntry("Highlighted color", &Qt::red); +} + +void ConfigAccess::setHighlightedColor(const QColor &highlightedColor) +{ + m_cfg->writeEntry("Highlighted color", highlightedColor); + m_cfg->sync(); +} + +bool ConfigAccess::underlineHighlighted() const +{ + return m_cfg->readBoolEntry("Underline highlighted headlines", true); +} + +void ConfigAccess::setUnderlineHighlighted(bool underlineHighlighted) +{ + m_cfg->writeEntry("Underline highlighted headlines", underlineHighlighted); + m_cfg->sync(); +} + +NewsSourceBase *ConfigAccess::newsSource(const QString &newsSource) +{ + NewsSourceBase::Data nsd; + + if (m_cfg->hasGroup(newsSource)) { + m_cfg->setGroup(newsSource); + nsd.name = newsSource; + nsd.sourceFile = m_cfg->readPathEntry("Source file"); + nsd.isProgram = m_cfg->readBoolEntry("Is program", false); + nsd.subject = static_cast + (m_cfg->readNumEntry("Subject", NewsSourceBase::Computers)); + nsd.icon = m_cfg->readEntry("Icon"); + nsd.maxArticles = m_cfg->readNumEntry("Max articles", 10); + nsd.enabled = m_cfg->readBoolEntry("Enabled", true); + nsd.language = m_cfg->readEntry("Language", QString::fromLatin1("C")); + m_cfg->setGroup("KNewsTicker"); + } else for (unsigned int i = 0; i < DEFAULT_NEWSSOURCES; i++) + if (NewsSourceDefault[i].name == newsSource) { + nsd = NewsSourceDefault[i]; + if (nsd.enabled) + nsd.enabled = (nsd.language == QString::fromLatin1("C") || + KGlobal::locale()->languageList().contains(nsd.language)); + break; + } + + if (nsd.isProgram) + return new ProgramNewsSource(nsd, this); + else + return new SourceFileNewsSource(nsd, this); + + return 0L; +} + +ArticleFilter ConfigAccess::filter(const unsigned int filterNo) const +{ + ArticleFilter f; + f.setId(filterNo); + + if (m_cfg->hasGroup(QString::fromLatin1("Filter #%1").arg(filterNo))) { + m_cfg->setGroup(QString::fromLatin1("Filter #%1").arg(filterNo)); + f.setAction(m_cfg->readEntry("Action", i18n("Show"))); + f.setNewsSource(m_cfg->readEntry("News source", i18n("all news sources"))); + f.setCondition(m_cfg->readEntry("Condition", i18n("contain"))); + f.setExpression(m_cfg->readEntry("Expression")); + f.setEnabled(m_cfg->readBoolEntry("Enabled", true)); + m_cfg->setGroup("KNewsTicker"); + } + + return f; +} + +void ConfigAccess::setNewsSource(const NewsSourceBase::Data &ns) +{ + m_cfg->setGroup(ns.name); + m_cfg->writePathEntry("Source file", ns.sourceFile); + m_cfg->writeEntry("Is program", ns.isProgram); + m_cfg->writeEntry("Max articles", ns.maxArticles); + m_cfg->writeEntry("Subject", ns.subject); + m_cfg->writeEntry("Icon", ns.icon); + m_cfg->writeEntry("Enabled", ns.enabled); + m_cfg->writeEntry("Language", ns.language); + m_cfg->setGroup("KNewsTicker"); + m_cfg->sync(); +} + +void ConfigAccess::setFilter(const ArticleFilter &f) +{ + m_cfg->setGroup(QString::fromLatin1("Filter #%1").arg(f.id())); + m_cfg->writeEntry("Action", f.action()); + m_cfg->writeEntry("News source", f.newsSource()); + m_cfg->writeEntry("Condition", f.condition()); + m_cfg->writeEntry("Expression", f.expression()); + m_cfg->writeEntry("Enabled", f.enabled()); + m_cfg->setGroup("KNewsTicker"); + m_cfg->sync(); +} + +bool ConfigAccess::showIcons() const +{ + return m_cfg->readBoolEntry("Show icons", true); +} + +void ConfigAccess::setShowIcons(bool showIcons) +{ + m_cfg->writeEntry("Show icons", showIcons); + m_cfg->sync(); +} + +bool ConfigAccess::slowedScrolling() const +{ + return m_cfg->readBoolEntry("Slowed scrolling", false); +} + +void ConfigAccess::setSlowedScrolling(bool slowedScrolling) +{ + m_cfg->writeEntry("Slowed scrolling", slowedScrolling); + m_cfg->sync(); +} + diff --git a/knewsticker/common/configaccess.h b/knewsticker/common/configaccess.h new file mode 100644 index 00000000..3cdf80e5 --- /dev/null +++ b/knewsticker/common/configaccess.h @@ -0,0 +1,135 @@ +/* + * configaccess.h + * + * Copyright (c) 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef CONFIGACCESS_H +#define CONFIGACCESS_H + +#include "configiface.h" +#include "newsengine.h" + +#include +#include +#include + +#include +#include + +#define DEFAULT_NEWSSOURCES 63 +#define DEFAULT_SUBJECTS 13 + +class QColor; +class QFont; +class KURL; + +class ArticleFilter { + public: + typedef QValueList List; + + ArticleFilter(const QString & = I18N_NOOP(QString::fromLatin1("Show")), + const QString & = I18N_NOOP(QString::fromLatin1("all newssources")), + const QString & = I18N_NOOP(QString::fromLatin1("contain")), + const QString & = QString::null, + bool = true); + + QString action() const { return m_action; } + void setAction(const QString &action) { m_action = action; } + + QString newsSource() const { return m_newsSource; } + void setNewsSource(const QString &newsSource) { m_newsSource = newsSource; } + + QString condition() const { return m_condition; } + void setCondition(const QString &condition) { m_condition = condition; } + + QString expression() const { return m_expression; } + void setExpression(const QString &expression) { m_expression = expression; } + + bool enabled() const { return m_enabled; } + void setEnabled(bool enabled) { m_enabled = enabled; } + + unsigned int id() const { return m_id; } + void setId(const unsigned int id) { m_id = id; } + + bool matches(Article::Ptr) const; + + private: + QString m_action; + QString m_newsSource; + QString m_condition; + QString m_expression; + bool m_enabled; + unsigned int m_id; +}; + +class ConfigAccess : public ConfigIface +{ + public: + ConfigAccess(); + ConfigAccess(KConfig *); + virtual ~ConfigAccess(); + + virtual unsigned int interval() const; + virtual unsigned int scrollingSpeed() const; + virtual unsigned int mouseWheelSpeed() const; + virtual unsigned int scrollingDirection() const; + virtual bool customNames() const; + virtual bool scrollMostRecentOnly() const; + virtual bool offlineMode() const; + virtual bool underlineHighlighted() const; + virtual bool showIcons() const; + virtual bool slowedScrolling() const; + virtual QColor foregroundColor() const; + virtual QColor backgroundColor() const; + virtual QColor highlightedColor() const; + QFont font() const; + virtual QStringList newsSources() const; + NewsSourceBase *newsSource(const QString &); + ArticleFilter::List filters() const; + ArticleFilter filter(const unsigned int) const; + + static bool horizontal(Direction d) { return d == Left || d == Right; } + static bool vertical(Direction d) { return d == Up || d == Down; } + static bool rotated(Direction d) { return d == UpRotated || d == DownRotated; } + + inline bool horizontalScrolling() const + { + return horizontal((Direction) scrollingDirection()); + }; + + inline bool verticalScrolling() const + { + return vertical((Direction)scrollingDirection()); + }; + + virtual void setInterval(const unsigned int); + virtual void setScrollingSpeed(const unsigned int); + virtual void setMouseWheelSpeed(const unsigned int); + virtual void setScrollingDirection(const unsigned int); + virtual void setCustomNames(bool); + virtual void setScrollMostRecentOnly(bool); + virtual void setOfflineMode(bool); + virtual void setUnderlineHighlighted(bool); + virtual void setShowIcons(bool); + virtual void setSlowedScrolling(bool); + virtual void setForegroundColor(const QColor &); + virtual void setBackgroundColor(const QColor &); + virtual void setHighlightedColor(const QColor &); + void setFont(const QFont &); + virtual void setNewsSources(const QStringList &); + void setNewsSource(const NewsSourceBase::Data &); + void setFilters(const ArticleFilter::List &); + void setFilter(const ArticleFilter &); + void reparseConfiguration() { m_cfg->reparseConfiguration(); } + + private: + KConfig *m_cfg; + KConfig *m_defaultCfg; +}; + +#endif // CONFIGACCESS_H diff --git a/knewsticker/common/configiface.h b/knewsticker/common/configiface.h new file mode 100644 index 00000000..64bc312d --- /dev/null +++ b/knewsticker/common/configiface.h @@ -0,0 +1,56 @@ +/* + * configface.h + * + * Copyright (c) 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef CONFIGIFACE_H +#define CONFIGIFACE_H + +class QColor; +class QStringList; +class QFont; + +class KURL; + +class ConfigIface +{ + public: + enum Direction {Left = 0, Right, Up, Down, UpRotated, DownRotated}; + + virtual unsigned int interval() const = 0; + virtual unsigned int scrollingSpeed() const = 0; + virtual unsigned int mouseWheelSpeed() const = 0; + virtual unsigned int scrollingDirection() const = 0; + virtual bool customNames() const = 0; + virtual bool scrollMostRecentOnly() const = 0; + virtual bool offlineMode() const = 0; + virtual bool underlineHighlighted() const = 0; + virtual bool showIcons() const = 0; + virtual bool slowedScrolling() const = 0; + virtual QColor foregroundColor() const = 0; + virtual QColor backgroundColor() const = 0; + virtual QColor highlightedColor() const = 0; + virtual QStringList newsSources() const = 0; + + virtual void setInterval(const unsigned int) = 0; + virtual void setScrollingSpeed(const unsigned int) = 0; + virtual void setMouseWheelSpeed(const unsigned int) = 0; + virtual void setScrollingDirection(const unsigned int) = 0; + virtual void setCustomNames(bool) = 0; + virtual void setScrollMostRecentOnly(bool) = 0; + virtual void setOfflineMode(bool) = 0; + virtual void setUnderlineHighlighted(bool) = 0; + virtual void setShowIcons(bool) = 0; + virtual void setSlowedScrolling(bool) = 0; + virtual void setForegroundColor(const QColor &) = 0; + virtual void setBackgroundColor(const QColor &) = 0; + virtual void setHighlightedColor(const QColor &) = 0; + virtual void setNewsSources(const QStringList &) = 0; +}; + +#endif // CONFIGIFACE_H diff --git a/knewsticker/common/newsengine.cpp b/knewsticker/common/newsengine.cpp new file mode 100644 index 00000000..cce7895c --- /dev/null +++ b/knewsticker/common/newsengine.cpp @@ -0,0 +1,324 @@ +/* + * newsengine.cpp + * + * Copyright (c) 2000, 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include "newsengine.h" +#include "configiface.h" +#include "configaccess.h" +#include "xmlnewsaccess.h" +#include "newsiconmgr.h" + +#include +#include +#include +#include +#include +#include + +#include + +Article::Article(NewsSourceBase *parent, const QString &headline, + const KURL &address) + : XMLNewsArticle(headline, address), + m_parent(parent), + m_read(false) +{ +} + +bool Article::operator==(const Article &other) const +{ + return headline() == other.headline() && address() == other.address(); +} + +void Article::open() +{ + (void) new KRun(address()); + m_read = true; +} + +NewsSourceBase::NewsSourceBase(const Data &nsd, ConfigIface *config) + : XMLNewsSource(), + m_data(nsd), + m_icon(QPixmap()), + m_cfg(dynamic_cast(config)), + m_newsIconMgr(NewsIconMgr::self()) +{ + connect(this, SIGNAL(loadComplete(XMLNewsSource *, bool)), + SLOT(slotProcessArticles(XMLNewsSource *, bool))); +} + +void NewsSourceBase::getIcon() +{ + connect(m_newsIconMgr, SIGNAL(gotIcon(const KURL &, const QPixmap &)), + this, SLOT(slotGotIcon(const KURL &, const QPixmap &))); + m_newsIconMgr->getIcon( KURL( m_data.icon ) ); +} + +QString NewsSourceBase::newsSourceName() const +{ + if (m_cfg->customNames() || m_name.isEmpty()) + return m_data.name; + else + return m_name; +} + +QString NewsSourceBase::subjectText(const Subject subject) +{ + switch (subject) { + case Arts: return i18n("Arts"); + case Business: return i18n("Business"); + case Computers: return i18n("Computers"); + case Games: return i18n("Games"); + case Health: return i18n("Health"); + case Home: return i18n("Home"); + case Recreation: return i18n("Recreation"); + case Reference: return i18n("Reference"); + case Science: return i18n("Science"); + case Shopping: return i18n("Shopping"); + case Society: return i18n("Society"); + case Sports: return i18n("Sports"); + case Misc: return i18n("Miscellaneous"); + case Magazines: return i18n("Magazines"); + default: return i18n("Unknown"); + } +} + +void NewsSourceBase::slotProcessArticles(XMLNewsSource *, bool gotEm) +{ + if (!gotEm) { + emit invalidInput(this); + return; + } + + Article::List oldArticles = m_articles; + + // Truncate the list of articles if necessary. + m_articles.clear(); + XMLNewsArticle::List::ConstIterator it = XMLNewsSource::articles().begin(); + XMLNewsArticle::List::ConstIterator end = XMLNewsSource::articles().end(); + for (; it != end; ++it) + m_articles.append(new Article(this, (*it).headline(), (*it).address())); + + // Fill the list with old articles until maxArticles is reached. + if (m_articles.count() < m_data.maxArticles) { + Article::List::ConstIterator oldArtIt = oldArticles.begin(); + Article::List::ConstIterator oldArtEnd = oldArticles.end(); + bool isNewArticle; + for (; oldArtIt != oldArtEnd; ++oldArtIt) { + isNewArticle = true; + Article::List::ConstIterator newArtIt = m_articles.begin(); + Article::List::ConstIterator newArtEnd = m_articles.end(); + for (; newArtIt != newArtEnd; ++newArtIt) { + Article newArt = **newArtIt; + Article oldArt = **oldArtIt; + if (newArt == oldArt) + isNewArticle = false; + } + + if (isNewArticle) + m_articles.append(*oldArtIt); + if (m_articles.count() == m_data.maxArticles) + break; + } + } else + while (m_articles.count() > m_data.maxArticles) + m_articles.remove(m_articles.fromLast()); + + // Copy the read flag of known articles + Article::List::ConstIterator oldArtIt = oldArticles.begin(); + Article::List::ConstIterator oldArtEnd = oldArticles.end(); + for (; oldArtIt != oldArtEnd; ++oldArtIt) { + Article::List::Iterator newArtIt = m_articles.begin(); + Article::List::Iterator newArtEnd = m_articles.end(); + for (; newArtIt != newArtEnd; ++newArtIt) + if (**oldArtIt == **newArtIt) + (*newArtIt)->setRead((*oldArtIt)->read()); + } + + emit newNewsAvailable(this, oldArticles != m_articles); +} + +Article::Ptr NewsSourceBase::article(const QString &headline) +{ + Article::List::ConstIterator it = m_articles.begin(); + Article::List::ConstIterator end = m_articles.end(); + for (; it != end; ++it) + if ((*it)->headline() == headline) + return *it; + + return 0L; +} + +void NewsSourceBase::slotGotIcon(const KURL &url, const QPixmap &pixmap) +{ + if (url.url() == m_data.icon) { + m_icon = pixmap; + + disconnect(m_newsIconMgr, SIGNAL(gotIcon(const KURL &, const QPixmap &)), + this, SLOT(slotGotIcon(const KURL &, const QPixmap &))); + } +} + +SourceFileNewsSource::SourceFileNewsSource(const NewsSourceBase::Data &nsd, + ConfigIface *config) + : NewsSourceBase(nsd, config) +{ +} + +void SourceFileNewsSource::retrieveNews() +{ + loadFrom(KURL( m_data.sourceFile )); +} + +ProgramNewsSource::ProgramNewsSource(const NewsSourceBase::Data &nsd, + ConfigIface *config) : NewsSourceBase(nsd, config), + m_program(new KProcess()), + m_programOutput(0) +{ + connect(m_program, SIGNAL(processExited(KProcess *)), + SLOT(slotProgramExited(KProcess *))); + connect(m_program, SIGNAL(receivedStdout(KProcess *, char *, int)), + SLOT(slotGotProgramOutput(KProcess *, char *, int))); + + m_data.sourceFile = KURL(m_data.sourceFile).encodedPathAndQuery(); +} + +ProgramNewsSource::~ProgramNewsSource() +{ + delete m_program; + delete m_programOutput; +} + +void ProgramNewsSource::retrieveNews() +{ + m_programOutput = new QBuffer; + m_programOutput->open(IO_WriteOnly); + + *m_program << m_data.sourceFile; + m_program->start(KProcess::NotifyOnExit, KProcess::Stdout); +} + +void ProgramNewsSource::slotGotProgramOutput(KProcess *, char *data, int length) +{ + m_programOutput->writeBlock(data, length); +} + +void ProgramNewsSource::slotProgramExited(KProcess *proc) +{ + bool okSoFar = true; + + QString errorMsg; + + if (!proc->normalExit()) { + errorMsg = i18n("

    The program '%1' was terminated abnormally.
    This can" + " happen if it receives the SIGKILL signal.

    "); + okSoFar = false; + } else { + ErrorCode error = static_cast(proc->exitStatus()); + if (error != NOERR) { + errorMsg = errorMessage(error).arg(m_data.sourceFile); + okSoFar = false; + } + } + + if (!okSoFar) { + QString output = QString(m_programOutput->buffer()); + if (!output.isEmpty()) { + output = QString::fromLatin1("\"") + output + QString::fromLatin1("\""); + errorMsg += i18n("

    Program output:
    %1
    ").arg(output); + } + KMessageBox::detailedError(0, i18n("An error occurred while updating the" + " news source '%1'.").arg(newsSourceName()), errorMsg, + i18n("KNewsTicker Error")); + } + + processData(m_programOutput->buffer(), okSoFar); + + delete m_programOutput; + m_programOutput = 0; +} + +QString ProgramNewsSource::errorMessage(const ErrorCode errorCode) +{ + switch (errorCode) { + case EPERM: return i18n("The program '%1' could not be started at all."); + case ENOENT: return i18n("The program '%1' tried to read or write a file or" + " directory which could not be found."); + case EIO: return i18n("An error occurred while the program '%1' tried to" + " read or write data."); + case E2BIG: return i18n("The program '%1' was passed too many arguments." + " Please adjust the command line in the configuration dialog."); + case ENOEXEC: return i18n("An external system program upon which the" + " program '%1' relied could not be executed."); + case EACCESS: return i18n("The program '%1' tried to read or write a file or" + " directory but lacks the permission to do so."); + case ENODEV: return i18n("The program '%1' tried to access a device which" + " was not available."); + case ENOSPC: return i18n("There is no more space left on the device used by" + " the program '%1'."); + case EROFS: return i18n("The program '%1' tried to create a temporary file" + " on a read only file system."); + case ENOSYS: return i18n("The program '%1' tried to call a function which" + " is not implemented or attempted to access an external resource which" + " does not exist."); + case ENODATA: return i18n("The program '%1' was unable to retrieve input data and" + " was therefore unable to return any XML data."); + case ENONET: return i18n("The program '%1' tried to access a host which is not" + " connected to a network."); + case EPROTO: return i18n("The program '%1' tried to access a protocol which is not" + " implemented."); + case EDESTADDRREQ: return i18n("The program '%1' requires you to configure a" + " destination address to retrieve data from. Please refer to the" + " documentation of the program for information on how to do that."); + case ESOCKTNOSUPPORT: return i18n("The program '%1' tried to use a socket" + " type which is not supported by this system."); + case ENETUNREACH: return i18n("The program '%1' tried to access an unreachable" + " network."); + case ENETRESET: return i18n("The network the program '%1' was trying to access" + " dropped the connection with a reset."); + case ECONNRESET: return i18n("The connection of the program '%1' was reset by" + " peer."); + case ETIMEDOUT: return i18n("The connection the program '%1' was trying to" + " establish timed out."); + case ECONNREFUSED: return i18n("The connection the program '%1' was trying to" + " establish was refused."); + case EHOSTDOWN: return i18n("The host the program '%1' was trying to reach is" + " down."); + case EHOSTUNREACH: return i18n("The host the program '%1' was trying to reach is" + " unreachable, no route to host."); + case ENOEXECBIT: return i18n("KNewsTicker could not execute the program '%1'" + " because its executable bit was not set. You can mark that program as" + " executable by executing the following steps:

      " + "
    • Open a Konqueror window and browse to the program
    • " + "
    • Click on the file with the right mouse button, and select 'Properties'
    • " + "
    • Open the 'Permissions' tab and make sure that the box in the column" + " 'Exec' and the row 'User' is checked to ensure that the current user" + " is allowed to execute that file.
    "); + case EBADREQ: return i18n("The program '%1' sent a bad request which was not" + " understood by the server."); + case ENOAUTH: return i18n("The program '%1' failed to issue an authorization for" + " an area which needs some form of authorization before it can be" + " accessed."); + case EMUSTPAY: return i18n("The program '%1' aborted because it could not access" + " the data without paying for it."); + case EFORBIDDEN: return i18n("The program '%1' tried to access a forbidden" + " source."); + case ENOTFOUND: return i18n("The program '%1' tried to access data which" + " could not be found."); + case ETIMEOUT: return i18n("The HTTP request of the program '%1' timed out."); + case ESERVERE: return i18n("A server error has been encountered. It is likely" + " that you cannot do anything about it."); + case EHTTPNOSUP: return i18n("The HTTP protocol version used by the program" + " '%1' was not understood by the HTTP server or source."); + default: return i18n("KNewsTicker was unable to detect the exact reasons for" + " the error."); + } +} + +#include "newsengine.moc" diff --git a/knewsticker/common/newsengine.h b/knewsticker/common/newsengine.h new file mode 100644 index 00000000..342dd7fe --- /dev/null +++ b/knewsticker/common/newsengine.h @@ -0,0 +1,203 @@ +/* + * newsengine.h + * + * Copyright (c) 2000, 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef NEWSENGINE_H +#define NEWSENGINE_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "configiface.h" +#include "xmlnewsaccess.h" + +#include +#include +#include + +#include +#include +#include + +class ConfigAccess; +class NewsIconMgr; +class NewsSourceBase; + +class QBuffer; +class QDomDocument; + +class KProcess; +class KURL; + +class KDE_EXPORT Article : public XMLNewsArticle, public KShared +{ + public: + typedef KSharedPtr
    Ptr; + typedef QValueList List; + + Article(NewsSourceBase *, const QString & = QString::null, + const KURL & = KURL()); + bool operator==(const Article &other) const; + + bool read() const { return m_read; } + void setRead(bool read) { m_read = read; } + + NewsSourceBase *newsSource() const { return m_parent; } + + void open(); + + private: + NewsSourceBase *m_parent; // don't use KSharedPtr to avoid circular refs! + bool m_read; +}; + +class KDE_EXPORT NewsSourceBase : public XMLNewsSource, public KShared +{ + Q_OBJECT + + public: + enum Subject { + Arts = 0, Business, Computers, Games, Health, Home, Recreation, + Reference, Science, Shopping, Society, Sports, Misc, Magazines + }; + struct Data { + Data(const QString &_name = I18N_NOOP("Unknown"), + const QString &_sourceFile = QString::null, + const QString &_icon = QString::null, + const Subject _subject = Computers, + unsigned int _maxArticles = 10, + bool _enabled = true, bool _isProgram = false, + const QString &_language = QString::fromLatin1("C")) + { + name = _name; + sourceFile = _sourceFile; + icon = _icon; + maxArticles = _maxArticles; + subject = _subject; + enabled = _enabled; + isProgram = _isProgram; + language = _language; + } + + QString name; + QString sourceFile; + QString icon; + Subject subject; + unsigned int maxArticles; + bool enabled; + bool isProgram; + QString language; + }; + typedef KSharedPtr Ptr; + typedef QValueList List; + + NewsSourceBase(const Data &, ConfigIface *); + + virtual QString newsSourceName() const; + QString sourceFile() const { return m_data.sourceFile; } + unsigned int maxArticles() const { return m_data.maxArticles; } + QPixmap icon() const { return m_icon; } + void getIcon(); + + Data data() const { return m_data; } + + Article::List articles() const { return m_articles; } + Article::Ptr article(const QString &); + + static QString subjectText(const Subject); + + signals: + void newNewsAvailable(const NewsSourceBase::Ptr &, bool); + void invalidInput(const NewsSourceBase::Ptr &); + + public slots: + virtual void retrieveNews() = 0; + + protected slots: + void slotProcessArticles(XMLNewsSource *, bool); + void slotGotIcon(const KURL &, const QPixmap &); + + protected: + Data m_data; + QPixmap m_icon; + ConfigAccess *m_cfg; + NewsIconMgr *m_newsIconMgr; + Article::List m_articles; +}; + +class KDE_EXPORT SourceFileNewsSource : public NewsSourceBase +{ + Q_OBJECT + + public: + SourceFileNewsSource(const NewsSourceBase::Data &, ConfigIface *); + + public slots: + virtual void retrieveNews(); +}; + +// Make sure compilers don't translate +// ProgramNewsSource::ErrorCode into +// enum ErrorCode { 1 = 2, ... } +#undef NOERR +#undef EPERM +#undef ENOENT +#undef EIO +#undef E2BIG +#undef ENOEXEC +#undef EACCESS +#undef ENODEV +#undef ENOSPC +#undef EROFS +#undef ENOSYS +#undef ENODATA +#undef ENONET +#undef EPROTO +#undef EDESTADDRREQ +#undef ESOCKTNOSUPPORT +#undef ENETUNREACH +#undef ENETRESET +#undef ECONNRESET +#undef ETIMEDOUT +#undef ECONNREFUSED +#undef EHOSTDOWN +#undef EHOSTUNREACH + +class KDE_EXPORT ProgramNewsSource : public NewsSourceBase +{ + Q_OBJECT + + public: + enum ErrorCode { NOERR = 0, EPERM, ENOENT, EIO = 5, E2BIG = 7, + ENOEXEC, EACCESS = 13, ENODEV = 19, ENOSPC = 28, EROFS = 30, + ENOSYS = 38, ENODATA = 61, ENONET = 64, EPROTO = 71, EDESTADDRREQ = 89, + ESOCKTNOSUPPORT = 94, ENETUNREACH = 101, ENETRESET = 102, + ECONNRESET = 104, ETIMEDOUT = 110, ECONNREFUSED, EHOSTDOWN, EHOSTUNREACH, + ENOEXECBIT = 126, EBADREQ = 400, ENOAUTH, EMUSTPAY, EFORBIDDEN, ENOTFOUND, + ETIMEOUT = 408, ESERVERE = 500, EHTTPNOSUP = 505 }; + + ProgramNewsSource(const NewsSourceBase::Data &, ConfigIface *); + virtual ~ProgramNewsSource(); + + public slots: + virtual void retrieveNews(); + + protected slots: + void slotGotProgramOutput(KProcess *, char *, int); + void slotProgramExited(KProcess *); + + private: + static QString errorMessage(const ErrorCode); + + KProcess *m_program; + QBuffer *m_programOutput; +}; + +#endif // NEWSENGINE_H diff --git a/knewsticker/common/newsiconmgr.cpp b/knewsticker/common/newsiconmgr.cpp new file mode 100644 index 00000000..69617490 --- /dev/null +++ b/knewsticker/common/newsiconmgr.cpp @@ -0,0 +1,159 @@ +/* + * newsiconmgr.cpp + * + * Copyright (c) 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include "newsiconmgr.h" + +#include + +#include +#include +#include + +#include +#include +#include + +struct KIODownload +{ + KURL url; + QByteArray data; + QIODevice::Offset dataOffset; +}; + +NewsIconMgr *NewsIconMgr::m_instance = 0; + +NewsIconMgr *NewsIconMgr::self() +{ + if (!m_instance) + m_instance = new NewsIconMgr(); + + return m_instance; +} + +NewsIconMgr::NewsIconMgr(QObject *parent, const char *name) + : QObject(parent, name), DCOPObject("NewsIconMgr"), + m_stdIcon(SmallIcon(QString::fromLatin1("news"))) +{ + connectDCOPSignal("kded", + "favicons", "iconChanged(bool, QString, QString)", + "slotGotIcon(bool, QString, QString)", + false); +} + +NewsIconMgr::~NewsIconMgr() +{ + delete m_instance; +} + +void NewsIconMgr::getIcon(const KURL &url) +{ + if (url.isEmpty()) { + emit gotIcon(url, m_stdIcon); + return; + } + + if (url.isLocalFile()) { + if (QFile::exists(url.encodedPathAndQuery())) { + QPixmap icon(url.encodedPathAndQuery()); + if (!icon.isNull()) { + if (icon.size() != QSize(16, 16)) { + if (!icon.convertFromImage(icon.convertToImage().smoothScale(16, 16, QImage::ScaleMin))) { + emit gotIcon(url, m_stdIcon); + return; + } + } + emit gotIcon(url, icon); + return; + } + } + emit gotIcon(url, m_stdIcon); + return; + } + + if (url.encodedPathAndQuery() == "/favicon.ico") { + if (favicon(url).isNull()) { + QByteArray data; + QDataStream ds(data, IO_WriteOnly); + ds << url; + kapp->dcopClient()->send("kded", "favicons", "downloadHostIcon(KURL)", data); + } else { + emit gotIcon(url, QPixmap(KGlobal::dirs()->findResource("cache", + QString::fromLatin1("favicons/%1.png").arg(url.host())))); + } + } else { + KIO::Job *job = KIO::get(url, true, false); + connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)), + SLOT(slotData(KIO::Job *, const QByteArray &))); + connect(job, SIGNAL(result(KIO::Job *)), SLOT(slotResult(KIO::Job *))); + + KIODownload download; + download.url = url; + download.dataOffset = 0; + m_kioDownload.insert(job, download); + } +} + +bool NewsIconMgr::isStdIcon(const QPixmap &pixmap) const +{ + if (!pixmap.isNull()) + return pixmap.convertToImage() == m_stdIcon.convertToImage(); + else + return false; +} + +void NewsIconMgr::slotData(KIO::Job *job, const QByteArray &data) +{ + QBuffer buf(m_kioDownload[job].data); + buf.open(IO_WriteOnly); + buf.at(m_kioDownload[job].dataOffset); + buf.writeBlock(data); + m_kioDownload[job].dataOffset = buf.at(); +} + +void NewsIconMgr::slotResult(KIO::Job *job) +{ + emit gotIcon(m_kioDownload[job].url, QPixmap(m_kioDownload[job].data)); + m_kioDownload.remove(job); +} + +void NewsIconMgr::slotGotIcon(bool isHost, QString hostOrURL, QString iconName) +{ + KURL url = KURL(hostOrURL); + if (!isHost) + url.setProtocol(QString::fromLatin1("http")); + + if (iconName.isNull()) + emit gotIcon(url, m_stdIcon); + else + emit gotIcon(url, QPixmap(KGlobal::dirs()->findResource("cache", + QString::fromLatin1("favicons/%1.png").arg(url.host())))); +} + +QString NewsIconMgr::favicon(const KURL &url) const +{ + QByteArray data, reply; + QCString replyType; + QDataStream ds(data, IO_WriteOnly); + + ds << url; + + kapp->dcopClient()->call("kded", "favicons", "iconForURL(KURL)", data, replyType, reply); + + if (replyType == "QString") { + QDataStream replyStream(reply, IO_ReadOnly); + QString result; + replyStream >> result; + return result; + } + + return QString::null; +} + +#include "newsiconmgr.moc" diff --git a/knewsticker/common/newsiconmgr.h b/knewsticker/common/newsiconmgr.h new file mode 100644 index 00000000..e4c6b657 --- /dev/null +++ b/knewsticker/common/newsiconmgr.h @@ -0,0 +1,60 @@ +/* + * newsiconmgr.h + * + * Copyright (c) 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef NEWSICONMGR_H +#define NEWSICONMGR_H + +#include +#include + +#include +#include +#include + +class DCOPClient; + +struct KIODownload; +typedef QMap KIODownloadMap; + +class NewsIconMgr : public QObject, public DCOPObject +{ + Q_OBJECT + K_DCOP + + public: + static NewsIconMgr *self(); + + void getIcon(const KURL &); + bool isStdIcon(const QPixmap &) const; + + k_dcop: + void slotGotIcon(bool, QString, QString); + + signals: + void gotIcon(const KURL &, const QPixmap &); + + protected: + NewsIconMgr(QObject * = 0L, const char * = 0L); + ~NewsIconMgr(); + + private slots: + void slotData(KIO::Job *, const QByteArray &); + void slotResult(KIO::Job *); + + private: + QString favicon(const KURL &) const; + + QPixmap m_stdIcon; + KIODownloadMap m_kioDownload; + + static NewsIconMgr *m_instance; +}; + +#endif // NEWSICONMGR_H diff --git a/knewsticker/common/xmlnewsaccess.cpp b/knewsticker/common/xmlnewsaccess.cpp new file mode 100644 index 00000000..1c892e8e --- /dev/null +++ b/knewsticker/common/xmlnewsaccess.cpp @@ -0,0 +1,132 @@ +/* + * xmlnewsaccess.cpp + * + * Copyright (c) 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include "xmlnewsaccess.h" + +#include +#include +#include + +#include +#include +#include + +XMLNewsArticle::XMLNewsArticle(const QString &headline, const KURL &address) + : m_headline(headline), + m_address(address) +{ +} + +XMLNewsArticle &XMLNewsArticle::operator=(const XMLNewsArticle &other) +{ + m_headline = other.m_headline; + m_address = other.m_address; + return *this; +} + +bool XMLNewsArticle::operator==(const XMLNewsArticle &a) +{ + return m_headline == a.headline() && m_address == a.address(); +} + +XMLNewsSource::XMLNewsSource() : QObject(), + m_name(QString::null), + m_link(QString::null), + m_description(QString::null), + m_downloadData(0) +{ +} + +XMLNewsSource::~XMLNewsSource() +{ + delete m_downloadData; // Might exist if we are in the middle of a download +} + +void XMLNewsSource::loadFrom(const KURL &url) +{ + if ( m_downloadData != 0 ) { + kdDebug( 5005 ) << "XMLNewsSource::loadFrom(): Busy, ignoring load " + "request for " << url << endl; + return; + } + m_downloadData = new QBuffer; + m_downloadData->open(IO_WriteOnly); + + KIO::Job *job = KIO::get(url, false, false); + job->addMetaData(QString::fromLatin1("UserAgent"), + QString::fromLatin1("KNewsTicker v0.2")); + connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)), + SLOT(slotData(KIO::Job *, const QByteArray &))); + connect(job, SIGNAL(result(KIO::Job *)), SLOT(slotResult(KIO::Job *))); +} + +void XMLNewsSource::slotData(KIO::Job *, const QByteArray &data) +{ + m_downloadData->writeBlock(data.data(), data.size()); +} + +void XMLNewsSource::slotResult(KIO::Job *job) +{ + kdDebug(5005) << "XMLNewsSource::slotResult(): Finished downloading data (" << job->error() << ")." << endl; + processData(m_downloadData->buffer(), !job->error()); + delete m_downloadData; + m_downloadData = 0; +} + +void XMLNewsSource::processData(const QByteArray &data, bool okSoFar) +{ + bool validContent = okSoFar; + kdDebug(5005) << "XMLNewsSource::processData(): validContent = " << validContent << endl; + + if (okSoFar) { + /* + * Some servers prepend whitespace before the declaration. + * Since QDom doesn't like that we strip this first. + */ + QDomDocument domDoc; + + const char *charData = data.data(); + int len = data.count(); + + while (len && (*charData == ' ' || *charData == '\n' || *charData == '\t' || *charData == '\r') ) { + len--; + charData++; + } + + QByteArray tmpData; + tmpData.setRawData(charData, len); + + if (validContent = domDoc.setContent(tmpData)) { + QDomNode channelNode = domDoc.documentElement().namedItem(QString::fromLatin1("channel")); + + m_name = channelNode.namedItem(QString::fromLatin1("title")).toElement().text().simplifyWhiteSpace(); + kdDebug(5005) << "XMLNewsSource::processData(): Successfully updated " << m_name << endl; + m_link = channelNode.namedItem(QString::fromLatin1("link")).toElement().text().simplifyWhiteSpace(); + m_description = channelNode.namedItem(QString::fromLatin1("description")).toElement().text().simplifyWhiteSpace(); + + QDomNodeList items = domDoc.elementsByTagName(QString::fromLatin1("item")); + m_articles.clear(); + QDomNode itemNode; + QString headline, address; + for (unsigned int i = 0; i < items.count(); i++) { + itemNode = items.item(i); + headline = KCharsets::resolveEntities(itemNode.namedItem(QString::fromLatin1("title")).toElement().text().simplifyWhiteSpace()); + address = KCharsets::resolveEntities(itemNode.namedItem(QString::fromLatin1("link")).toElement().text().simplifyWhiteSpace()); + m_articles.append(XMLNewsArticle(headline, KURL( address ))); + } + } + kdDebug(5005) << "XMLNewsSource::processData(): validContent = " << validContent << endl; + tmpData.resetRawData(charData, len); + } + + emit loadComplete(this, validContent); +} + +#include "xmlnewsaccess.moc" diff --git a/knewsticker/common/xmlnewsaccess.h b/knewsticker/common/xmlnewsaccess.h new file mode 100644 index 00000000..91cbf77f --- /dev/null +++ b/knewsticker/common/xmlnewsaccess.h @@ -0,0 +1,86 @@ +/* + * xmlnewsaccess.h + * + * Copyright (c) 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef XMLNEWSACCESS_H +#define XMLNEWSACCESS_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +class QBuffer; +class QDomDocument; +class XMLNewsSource; + +class XMLNewsArticle +{ + public: + typedef QValueList List; + + XMLNewsArticle() {} + XMLNewsArticle(const QString &headline, const KURL &addresss); + XMLNewsArticle(const XMLNewsArticle &other) { (*this) = other; } + XMLNewsArticle &operator=(const XMLNewsArticle &other); + + QString headline() const { return m_headline; } + void setHeadline(const QString &headline) { m_headline = headline; } + + KURL address() const { return m_address; } + void setAddress(const KURL &address) { m_address = address; } + + bool operator== (const XMLNewsArticle &a); + bool operator!= (const XMLNewsArticle &a) { return !operator==(a); } + + private: + QString m_headline; + KURL m_address; +}; + +class XMLNewsSource : public QObject +{ + Q_OBJECT + + public: + XMLNewsSource(); + virtual ~XMLNewsSource(); + + void loadFrom(const KURL &); + + virtual QString newsSourceName() const { return m_name; } + virtual QString link() const { return m_link; } + virtual QString description() const { return m_description; } + const XMLNewsArticle::List &articles() const { return m_articles; } + + signals: + void loadComplete(XMLNewsSource *, bool); + + protected: + void processData(const QByteArray &, bool = true); + + QString m_name; + QString m_link; + QString m_description; + + private slots: + void slotData(KIO::Job *, const QByteArray &); + void slotResult(KIO::Job *); + + private: + XMLNewsArticle::List m_articles; + QBuffer *m_downloadData; +}; + +#endif // XMLNEWSACCESS_H diff --git a/knewsticker/eventsrc b/knewsticker/eventsrc new file mode 100644 index 00000000..e3fdee2f --- /dev/null +++ b/knewsticker/eventsrc @@ -0,0 +1,335 @@ +[!Global!] +IconName=knewsticker +Comment=News Ticker +Comment[af]=Nuus Tikker +Comment[ar]=تجديد الأخبار +Comment[az]=XÉ™bÉ™r GözlÉ™yici +Comment[be]=ÐглÑдальнік навінаў +Comment[bg]=Четец на новини +Comment[bn]=সংবাদ টিকার +Comment[br]=Kliker keleier +Comment[bs]=Traka s vijestima +Comment[ca]=Teletip de notícies +Comment[cs]=Novinky +Comment[cy]=Ticer Newyddion +Comment[da]=Nyhedstelegraf +Comment[de]=Newsticker +Comment[el]=ΠÏοβολή ειδήσεων +Comment[eo]=Novaĵprezentilo +Comment[es]=Teletipo de noticias +Comment[et]=Uudiste jälgija +Comment[eu]=Berri markatzailea +Comment[fa]=تیکر اخبار +Comment[fi]=Uutisnäyttäjä +Comment[fr]=Téléscripteur +Comment[gl]=Teletipo +Comment[he]=צג חדשות +Comment[hi]=नà¥à¤¯à¥‚ज टिकर +Comment[hr]=Ticker sa novostima +Comment[hu]=HírmegjelenítÅ‘ +Comment[id]=Ticker Berita +Comment[is]=Fréttastrimill +Comment[it]=Ticker notizie +Comment[ja]=ニュースティッカー +Comment[ka]=სიáƒáƒ®áƒšáƒ”ების მიმღები +Comment[kk]=Жаңалық таÑпаÑÑ‹ +Comment[km]=កម្មវិធី​ទទួល​ពáŸážáŸŒáž˜áž¶áž“ +Comment[ko]=ìƒˆì†Œì‹ í‘œì‹œê¸° +Comment[lt]=Naujienų praneÅ¡Ä—jas +Comment[lv]=Ziņu TIkkers +Comment[mk]=Лента Ñо веÑти +Comment[mn]=ÐœÑдÑÑний ДÑлгÑц +Comment[ms]=Detik Berita +Comment[mt]=News ticker +Comment[nb]=Nyhetstelegraf +Comment[nds]=Narichten-Ticker +Comment[ne]=समाचार टिकर +Comment[nl]=Nieuwslezer +Comment[nn]=Nyhendetelegraf +Comment[nso]=Seswai sa Ditaba +Comment[pt]=Notícias +Comment[pt_BR]=Mini-aplicativo de notícias +Comment[ro]=Åžtiri Internet +Comment[ru]=Монитор новоÑтей +Comment[se]=OÄ‘astelegráfa +Comment[sk]=SledovaÄ správ +Comment[sl]=Prikazovalnik novic +Comment[sr]=Пратилац веÑти +Comment[sr@Latn]=Pratilac vesti +Comment[sv]=Nyhetsövervakare +Comment[ta]=செயà¯à®¤à®¿ அறிவிபà¯à®ªà®¾à®©à¯ +Comment[tg]=Дидабони Ðхборот +Comment[th]=ตั๋วข่าว +Comment[tr]=Haber Gözlemcisi +Comment[uk]=Стрічка новин +Comment[ven]=Musengulusi wa Mafhungo +Comment[zh_CN]=新闻简报 +Comment[zh_HK]=æ–°èžå¿«å ± +Comment[zh_TW]=æ–°èžç°¡å ± +Comment[zu]=Umlungiseleli Wezindaba + +[NewNews] +Name=New News Available +Name[af]=Nuwe Nuus Beskikbaar +Name[ar]=توجد أخبار جديدة +Name[az]=Mövcud Yeni XÉ™bÉ™rlÉ™r +Name[be]=ÐÑць навіны +Name[bg]=ПриÑтигнаха нови новини +Name[bn]=নতà§à¦¨ সংবাদ পাওয়া যাচà§à¦›à§‡ +Name[br]=Keleier nevez da gaout +Name[bs]=Nove vijesti su dostupne +Name[ca]=Noves notícies disponibles +Name[cs]=Jsou dostupné nové zprávy +Name[cy]=Newyddion Newydd ar Gael +Name[da]=Nye nyheder tilgængelige +Name[de]=Neu eingetroffene Nachrichten +Name[el]=ΚαινοÏÏιες ειδήσεις διαθέσιμες +Name[eo]=Novaj diskutmesaÄoj +Name[es]=Nuevas noticias disponibles +Name[et]=Uus uudis +Name[eu]=Azken berriak eskuragarri +Name[fa]=خبرهای جدید در دسترس +Name[fi]=Uutisia saatavilla +Name[fr]=Des nouvelles sont arrivées +Name[ga]=Nuacht Nua ar Fáil +Name[gl]=Novas noticias dispoñibles +Name[he]=ישנן חדשות זמינות +Name[hi]=नया समाचार उपलबà¥à¤§ +Name[hr]=Novosti su raspoložive +Name[hu]=Új hír érkezett +Name[id]=Berita baru tersedia +Name[is]=Nýjar fréttir komnar +Name[it]=Nuove notizie disponibili +Name[ja]=æ–°ç€ãƒ‹ãƒ¥ãƒ¼ã‚¹ +Name[ka]=áƒáƒ®áƒáƒšáƒ˜ სიáƒáƒ®áƒšáƒ”ები ხელმისáƒáƒ¬áƒ•áƒ“áƒáƒ›áƒ˜áƒ +Name[kk]=Жаңалықтар бар +Name[km]=មាន​ពáŸážáŸŒáž˜áž¶áž“​ážáŸ’មី +Name[ko]=새로운 소ì‹ì´ 있습니다 +Name[lt]=Yra Å¡viežių naujienų +Name[lv]=Jaunas Ziņas Pieejamas +Name[mk]=Има нови веÑти +Name[mn]=Ð¨Ð¸Ð½Ñ ÐœÑдÑÑ Ð³Ð°Ñ€Ð»Ð°Ð° +Name[ms]=Ada Berita Mutakhir +Name[mt]=Aħbarijiet Ä¡odda +Name[nb]=Nye artikler tilgjengelige +Name[nds]=Nieg Narichten verföögbor +Name[ne]=नयाठसमाचार उपलबà¥à¤§ छ +Name[nl]=Nieuw nieuws beschikbaar +Name[nn]=Nyhende tilgjengeleg +Name[nso]=Ditaba tse Diswa di Gona +Name[pa]=ਨਵੀਆਂ ਖ਼ਬਰਾਂ ਉਪਲੱਬਧ ਹਨ +Name[pl]=DostÄ™pne nowe wiadomoÅ›ci +Name[pt]=Estão novas notícias disponíveis +Name[pt_BR]=Novas notícias +Name[ro]=Nu există ÅŸtiri noi +Name[ru]=ЕÑÑ‚ÑŒ новоÑти +Name[se]=OÄ‘Ä‘a oÄ‘Ä‘asat olamuttus +Name[sk]=Nové správy +Name[sl]=Na voljo so sveže novice +Name[sr]=ДоÑтупне Ñу нове веÑти +Name[sr@Latn]=Dostupne su nove vesti +Name[sv]=Det har kommit nya nyheter +Name[ta]=பà¯à®¤à®¿à®¯ செயà¯à®¤à®¿à®•à®³à¯ உளà¯à®³à®© +Name[tg]=Ðхборотҳои Ðав ДаÑтраÑанд +Name[th]=ข่าวใหม่ที่มี +Name[tr]=Mevcut Yeni Haberler +Name[uk]=ОÑтанні новини +Name[ven]=Mafhungo maswa are hone +Name[xh]=Iindaba Ezintsha Ezikhoyo +Name[zh_CN]=有新的新闻 +Name[zh_HK]=æœ‰æ–°çš„æ–°èž +Name[zh_TW]=æœ‰æ–°çš„æ–°èž +Name[zu]=Izindaba Ezintsha Ziyatholakala manje +Comment=There is new news available +Comment[af]=Daar is nuwe nuus beskikbaar +Comment[ar]=هناك أخبار جديدة +Comment[az]=Yeni xÉ™bÉ™rlÉ™r mövcuddur +Comment[be]=ÐÑць навіны +Comment[bg]=ПриÑтигнаха нови новини +Comment[bn]=নতà§à¦¨ সংবাদ পাওয়া যাচà§à¦›à§‡ +Comment[bs]=Sada ima novih vijesti +Comment[ca]=Hi ha noves notícies disponibles +Comment[cs]=Jsou dostupné nové zprávy +Comment[cy]=Mae newyddion newydd ar gael +Comment[da]=Der er nye nyheder tilgængelige +Comment[de]=Es sind neue Nachrichten eingetroffen +Comment[el]=ΥπάÏχουν καινοÏÏιες ειδήσεις διαθέσιμες +Comment[eo]=Alvenis novaj diskutmesaÄoj +Comment[es]=Hay nuevas noticias disponibles +Comment[et]=Uusi uudiseid pole +Comment[eu]=Azken berriak eskuragarri +Comment[fa]=اخبار جدید در دسترس است +Comment[fi]=Uutisia saatavilla +Comment[fr]=De nouvelles informations sont arrivées. +Comment[ga]=Tá nuacht nua ar fáil +Comment[gl]=Hai novas noticias dispoñibles +Comment[he]=ישנן חדשות זמינות +Comment[hi]=वहाठनया समाचार उपलबà¥à¤§ है +Comment[hr]=Postoje nove novosti +Comment[hu]=Új hír érkezett. +Comment[id]=Ada berita baru tersedia +Comment[is]=Nýjar fréttir hafa borist +Comment[it]=C'è una nuova notizia disponibile +Comment[ja]=æ–°ã—ã„ニュースãŒå±Šãã¾ã—㟠+Comment[ka]=სიáƒáƒ®áƒšáƒ”ები áƒáƒ  áƒáƒ áƒ˜áƒ¡ ხელმისáƒáƒ¬áƒ•áƒ“áƒáƒ›áƒ˜ +Comment[kk]=Жаңалықтар бар +Comment[km]=មាន​ពáŸážáŸŒáž˜áž¶áž“​ážáŸ’មី​ហើយ +Comment[ko]=새로운 소ì‹ì´ 있습니다 +Comment[lt]=Yra Å¡viežių naujienų +Comment[lv]=Å eit ir jaunas ziņas pieejamas +Comment[mk]=Има нови веÑти +Comment[mn]=ÐœÑдÑÑ Ð³Ð°Ñ€Ð»Ð°Ð° +Comment[ms]=Berita mutakhir hari ini +Comment[mt]=Hemm aħbarijiet Ä¡odda +Comment[nb]=Det er nye artikler tilgjengelig +Comment[nds]=Dat gifft niege Narichten +Comment[ne]=तà¥à¤¯à¤¹à¤¾à¤ नयाठसमाचार उपलबà¥à¤§ छ +Comment[nl]=Er is nieuw nieuws beschikbaar +Comment[nn]=Nyhende tilgjengeleg +Comment[nso]=Gona le ditaba tse diswa tseo di lego gona +Comment[pl]=Jest nowa wiadomość +Comment[pt]=Existem novas notícias disponíveis +Comment[pt_BR]=Há novas notícias +Comment[ro]=Nu există ÅŸtiri noi +Comment[ru]=ЕÑÑ‚ÑŒ новоÑти +Comment[se]=Leat varas oÄ‘Ä‘asat olamuttus +Comment[sk]=Žiadne nové správy +Comment[sl]=Na voljo so sveže novice +Comment[sr]=Има нових веÑти +Comment[sr@Latn]=Ima novih vesti +Comment[sv]=Det har kommit nya nyheter +Comment[ta]=பà¯à®¤à®¿à®¯ செயà¯à®¤à®¿à®•à®³à¯ உளà¯à®³à®© +Comment[tg]=Ðхборотҳои нав даÑтраÑанд +Comment[th]=นี่เป็นข่าวใหม่ที่มี +Comment[tr]=Yeni haberler var +Comment[uk]=Ðадійшли Ñвіжі новини +Comment[ven]=Huna mafhungo maswa are hone +Comment[xh]=Kukho iindaba ezintsha ezikhoyo +Comment[zh_CN]=有新的新闻 +Comment[zh_HK]=æœ‰æ–°çš„æ–°èž +Comment[zh_TW]=æœ‰æ–°çš„æ–°èž +Comment[zu]=Kukhona izindaba ezintsha ezitholakalayo +default_sound= +default_presentation=1 + +[InvalidRDF] +Name=Invalid RDF file +Name[af]=Ongeldige Rdf lêer +Name[ar]=مل٠RDF غير صالح +Name[az]=XÉ™talı RDF faylı +Name[be]=ÐÑправільны файл RDF +Name[bg]=Ðевалиден файл RDF +Name[bn]=অবৈধ আর-ডি-à¦à¦« ফাইল +Name[br]=N'eo ket mat ar restr RDF +Name[bs]=Neispravna RDF datoteka +Name[ca]=Fitxer RDF no vàlid +Name[cs]=Neplatný RDF soubor +Name[cy]=Ffeil RDF annilys +Name[da]=Ugyldig RDF-fil +Name[de]=Ungültige RDF-Datei +Name[el]=Μη έγκυÏο αÏχείο RDF +Name[eo]=Nevalida RDF-dosierojn +Name[es]=Archivo RDF no válido +Name[et]=Vigane RDF-fail +Name[eu]=RDF fitxategia baliogabea da +Name[fa]=پروندۀ RDF نامعتبر +Name[fi]=Virheellinen RDF-tiedosto +Name[fr]=Fichier RDF non valable +Name[ga]=Comhad RDF Neamhbhailí +Name[gl]=Fichero RDF inválido +Name[he]=קובץ RDF ×œ× ×ª×§×£ +Name[hi]=अवैध RDF फ़ाइल +Name[hr]=Nevažeća RDF datoteka +Name[hu]=Érvénytelen RDF fájl +Name[id]=Berkas RDF rusak +Name[is]=Ógild RDF skrá +Name[it]=File RDF non valido +Name[ja]=ä¸æ­£ãª RDF ファイル +Name[ka]=áƒáƒ áƒáƒ¡áƒ¬áƒáƒ áƒ˜ RDF ფáƒáƒ˜áƒšáƒ˜ +Name[kk]=ЖарамÑыз RDF файл +Name[km]=ឯកសារ RDF មិនážáŸ’រឹមážáŸ’រូវ​ +Name[ko]=ìž˜ëª»ëœ RDF íŒŒì¼ +Name[lt]=Neteisinga RDF byla +Name[lv]=Nepareizs RDF fails +Name[mk]=Ðевалидна RDF-датотека +Name[mn]=Буруу RDF файл +Name[ms]=Fail RDF tidak sah +Name[mt]=Fajl RDF invalidu +Name[nb]=Ugyldig RDF-fil +Name[nds]=Leeg RDF-Datei +Name[ne]=अवैध आर डी à¤à¤« फाइल +Name[nl]=Ongeldig RDF-bestand +Name[nn]=Ugyldig RDF-fil +Name[nso]=Faele ya RDF yeo esa dumelelwago +Name[pl]=NieprawidÅ‚owy plik RDF +Name[pt]=Ficheiro RDF inválido +Name[pt_BR]=Arquivo RDF inválido +Name[ro]=FiÅŸier RDF eronat +Name[ru]=Ðеверный RDF файл +Name[se]=Gustomeahttun RDF-fiila +Name[sk]=Neplatný súbor RDF +Name[sl]=Neveljavna datoteka PDF +Name[sr]=Погрешан RDF фајл +Name[sr@Latn]=PogreÅ¡an RDF fajl +Name[sv]=Ogiltig RDF-fil +Name[ta]=வலிதறà¯à®± RDF ஆவணம௠+Name[tg]=RDF файли нодуруÑÑ‚ +Name[th]=à¹à¸Ÿà¹‰à¸¡ RDF ไม่ถูà¸à¸•à¹‰à¸­à¸‡ +Name[tr]=Hatalı RDF dosyası +Name[uk]=Ðевірний файл RDF +Name[ven]=Faela ya RDF isina tshithu +Name[wa]=Fitchî RDF nén valide +Name[xh]=Ifayile ye RDF engasebenziyo +Name[zh_CN]=无效的 RDF 文件 +Name[zh_HK]=無效的 RDF 檔案 +Name[zh_TW]=無效的 RDF 檔案 +Name[zu]=Ifayela Engasebenziyo ye RDF +Comment=The downloaded RDF file could not be parsed +Comment[be]=Ðемагчыма апрацаваць ÑцÑгнуты файл RDF +Comment[bg]=ИзтеглениÑÑ‚ файл RDF не може да бъде анализиран +Comment[bn]=ডাউনলোডকৃত আর-ডি-à¦à¦« ফাইল পারà§à¦¸ করতে পারল না +Comment[bs]=Ne mogu protumaÄiti pribavljenu RDF datoteku +Comment[ca]=El fitxer RDF descarregat no s'ha pogut analitzar +Comment[cs]=Analýza stáhnutého RDF souboru selhala +Comment[da]=Den hentede RDF-fil kunne ikke fortolkes +Comment[de]=Die heruntergeladene RDF-Datei kann nicht eingelesen werden +Comment[el]=Το αÏχείο που λήφθηκε RDF ήταν αδÏνατο να αναλυθεί +Comment[eo]=La elÅutita RDF-dosiero ne estis analizebla +Comment[es]=El archivo RDF descargado no se pudo analizar +Comment[et]=Allalaaditud RDF-faili pole võimalik parsida +Comment[eu]=Deskargatutako RDF fitxategia ezin izan da aztertu +Comment[fa]=پروندۀ بارگیری‌شدۀ RDF نتوانست تجزیه شود +Comment[fi]=Haettua RDF-tiedostoa ei saatu jäsennettyä +Comment[fr]=Le fichier RDF téléchargé n'a pas pu être analysé +Comment[ga]=Ní féidir an comhad RDF íosluchtaithe a pharsáil +Comment[gl]=O ficheiro RDF descargado non se puido interpretar +Comment[he]=×ין ×פשרות לנתח ×ת קובץ ×”-RDF שהורד +Comment[hu]=A letöltött RDF-fájlt nem sikerült feldolgozni +Comment[is]=Gat ekki þáttað RDF skrána sem var sótt +Comment[it]=Il file RDF scaricato non può essere interpretato +Comment[ja]=ダウンロードã•ã‚ŒãŸ RDF ファイルã¯è§£èª­ã§ãã¾ã›ã‚“ +Comment[ka]=ჩáƒáƒ›áƒáƒ¥áƒáƒ©áƒ£áƒšáƒ˜ RDF ფáƒáƒ˜áƒšáƒ˜áƒ¡ გáƒáƒáƒœáƒáƒšáƒ˜áƒ–ებრვერ გáƒáƒœáƒ®áƒáƒ áƒªáƒ˜áƒ”ლდრ+Comment[kk]=ТүÑіріп алынған RDF талдауға келмеді +Comment[km]=មិន​អាច​ញែក​ឯកសារ RDF ដែលបាន​ទាញយក​បាន​ឡើយ +Comment[lt]=Nepavyko apdoroti atsisiųstos RDF bylos +Comment[nb]=Kunne ikke tolke RDF-fila som ble lastet ned +Comment[nds]=De daallaadt RDF-Datei lett sik nich inlesen +Comment[ne]=डाउनलोड गरिà¤à¤•à¥‹ आर डी à¤à¤« फाइल पद वरà¥à¤£à¤¨ गरà¥à¤¨ सकेन +Comment[nl]=Het gedownloade RDF-bestand kon niet worden ontleed +Comment[nn]=Klarte ikkje tolka den nedlasta RDF-fila +Comment[pl]=ÅšciÄ…gniÄ™ty plik RDF ma zÅ‚Ä… skÅ‚adniÄ™ +Comment[pt]=Não foi possível processar o ficheiro RDF transferido +Comment[pt_BR]=O arquivo RDF obtido não pôde ser interpretado +Comment[ro]=FiÅŸierul RDF transferat nu poate fi analizat +Comment[ru]=Ðе удалоÑÑŒ разобрать загруженный RDF-файл +Comment[sk]=Stiahnutý RDF súbor nie je možné analyzovaÅ¥ +Comment[sl]=Prenesene datoteke RDF ni moÄ razÄleniti +Comment[sr]=Преузети RDF фајл не може да Ñе рашчлани +Comment[sr@Latn]=Preuzeti RDF fajl ne može da se raÅ¡Älani +Comment[sv]=Den nerladdade RDF-filen kunde inte tolkas +Comment[tr]=Ä°ndirilen RDF dosyası ayrıştırılamadı +Comment[uk]=Ðе вдалоÑÑŒ проаналізувати звантажений файл RDF +Comment[zh_CN]=下载的 RDF æ— æ³•åˆ†æž +Comment[zh_HK]=下載的 RDF æª”æ¡ˆç„¡æ³•è§£æž +Comment[zh_TW]=下載的 RDF ç„¡æ³•åˆ†æž +default_presentation=0 diff --git a/knewsticker/hi16-app-knewsticker.png b/knewsticker/hi16-app-knewsticker.png new file mode 100644 index 00000000..a450d096 Binary files /dev/null and b/knewsticker/hi16-app-knewsticker.png differ diff --git a/knewsticker/hi32-app-knewsticker.png b/knewsticker/hi32-app-knewsticker.png new file mode 100644 index 00000000..36b0f702 Binary files /dev/null and b/knewsticker/hi32-app-knewsticker.png differ diff --git a/knewsticker/hi48-app-knewsticker.png b/knewsticker/hi48-app-knewsticker.png new file mode 100644 index 00000000..447b6813 Binary files /dev/null and b/knewsticker/hi48-app-knewsticker.png differ diff --git a/knewsticker/knewsticker-standalone.desktop b/knewsticker/knewsticker-standalone.desktop new file mode 100644 index 00000000..d31a44b1 --- /dev/null +++ b/knewsticker/knewsticker-standalone.desktop @@ -0,0 +1,95 @@ +[Desktop Entry] +Name=KNewsTicker +Name[af]=K-nuustikker +Name[ar]=مجدد أخبار كيدي +Name[bn]=কে-নিউজ-টিকার +Name[cy]=KTicerNewyddion +Name[eo]=Novaĵprezentilo +Name[he]=KNewsTicker - צג חדשות +Name[hi]=के-नà¥à¤¯à¥‚ज-टिकर +Name[lv]=KZiņuTikkers +Name[nb]=Nyhetstelegraf +Name[ne]=केडीई नà¥à¤¯à¥‚ज टिकर +Name[nso]=Seswai sa KDitaba +Name[pt_BR]=Mostrador de Notícias +Name[sv]=Knewsticker +Name[ta]=கேசெயà¯à®¤à®¿ அறிவிபà¯à®ªà®¾à®©à¯ +Name[tg]=KДидабони Ðхборот +Name[th]=ตั๋วข่าว - K +Name[tr]=KDE Haber Gözlemcisi +Name[ven]=Musengulusi wa mafhungo a K +Name[zu]=Umlungiseleli Wezindaba ze K +Type=Application +Exec=appletproxy knewsticker.desktop +Icon=knewsticker +DocPath=knewsticker/index.html +GenericName=News Ticker +GenericName[af]=Nuus Tikker +GenericName[ar]=تجديد الأخبار +GenericName[az]=XÉ™bÉ™r GözlÉ™yici +GenericName[be]=ÐглÑдальнік навінаў +GenericName[bg]=Четец на новини +GenericName[bn]=সংবাদ টিকার +GenericName[br]=Kliker keleier +GenericName[bs]=Traka s vijestima +GenericName[ca]=Teletip de notícies +GenericName[cs]=Novinky +GenericName[cy]=Ticer Newyddion +GenericName[da]=Nyhedstelegraf +GenericName[de]=Newsticker +GenericName[el]=ΠÏοβολή ειδήσεων +GenericName[eo]=Novaĵprezentilo +GenericName[es]=Teletipo de noticias +GenericName[et]=Uudiste jälgija +GenericName[eu]=Berri markatzailea +GenericName[fa]=تیکر اخبار +GenericName[fi]=Uutisnäyttäjä +GenericName[fr]=Téléscripteur +GenericName[gl]=Teletipo de noticias +GenericName[he]=צג חדשות +GenericName[hi]=नà¥à¤¯à¥‚ज टिकर +GenericName[hr]=Ticker sa novostima +GenericName[hu]=RSS hírbejelentÅ‘ +GenericName[id]=Ticker Berita +GenericName[is]=Fréttastrimill +GenericName[it]=Ticker notizie +GenericName[ja]=ニュースティッカー +GenericName[ka]=სიáƒáƒ®áƒšáƒ”ების მიმღები +GenericName[kk]=Жаңалық таÑпаÑÑ‹ +GenericName[km]=កម្មវិធី​ទទួល​ពáŸážáŸŒáž˜áž¶áž“ +GenericName[ko]=ìƒˆì†Œì‹ í‘œì‹œê¸° +GenericName[lt]=Naujienų praneÅ¡Ä—jas +GenericName[lv]=Ziņu TIkkers +GenericName[mk]=Лента Ñо веÑти +GenericName[mn]=ÐœÑдÑÑний ДÑлгÑц +GenericName[ms]=Detik Berita +GenericName[nb]=Nyhetstelegraf +GenericName[nds]=Narichten-Ticker +GenericName[ne]=समाचार टिकर +GenericName[nl]=Nieuws-lichtkrant +GenericName[nn]=Nyhendetelegraf +GenericName[nso]=Seswai sa Ditaba +GenericName[pt]=Notícias +GenericName[pt_BR]=Mini-aplicativo de notícias +GenericName[ro]=Åžtiri Internet +GenericName[ru]=Монитор новоÑтей +GenericName[se]=OÄ‘astelegráfa +GenericName[sk]=SledovaÄ správ +GenericName[sl]=Prikazovalnik novic +GenericName[sr]=Пратилац веÑти +GenericName[sr@Latn]=Pratilac vesti +GenericName[sv]=Nyhetsövervakare +GenericName[ta]=செயà¯à®¤à®¿ அறிவிபà¯à®ªà®¾à®©à¯ +GenericName[tg]=Дидабони Ðхборот +GenericName[th]=ตั๋วข่าว +GenericName[tr]=Haber Gözlemcisi +GenericName[uk]=Стрічка новин +GenericName[ven]=Musengulusi wa Mafhungo +GenericName[zh_CN]=新闻播报器 +GenericName[zh_HK]=æ–°èžå¿«å ± +GenericName[zh_TW]=æ–°èžç°¡å ± +GenericName[zu]=Umlungiseleli Wezindaba +Terminal=false +X-KDE-StartupNotify=true +X-DCOP-ServiceType=Unique +Categories=Qt;KDE;Network;X-KDE-More;News; diff --git a/knewsticker/knewsticker.cpp b/knewsticker/knewsticker.cpp new file mode 100644 index 00000000..6d2f90cb --- /dev/null +++ b/knewsticker/knewsticker.cpp @@ -0,0 +1,546 @@ +/* + * knewsticker.cpp + * + * Copyright (c) 2000, 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include "knewsticker.h" +#include "newsengine.h" +#include "newsscroller.h" +#include "configaccess.h" +#include "newsiconmgr.h" +#include "knewstickerconfig.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +KNewsTicker::KNewsTicker(const QString &cfgFile, Type t, int actions, QWidget *parent, const char *name) + : ConfigIface(), DCOPObject("KNewsTicker"), + KPanelApplet(cfgFile, t, actions, parent, name), + m_instance(new KInstance("knewsticker")), + m_dcopClient(new DCOPClient()), + m_cfg(new ConfigAccess(config())), + m_newsTimer(new QTimer(this)), + m_updateTimer(new QTimer(this)), + m_newsIconMgr(NewsIconMgr::self()), + m_aboutData(new KAboutData("knewsticker", I18N_NOOP("KNewsTicker"), "v0.2", + I18N_NOOP("A news ticker applet."), KAboutData::License_BSD, + I18N_NOOP("(c) 2000, 2001 The KNewsTicker developers"))) +{ + QHBoxLayout *layout = new QHBoxLayout(this); + + m_contextMenu = new KNewsTickerMenu(this); + connect(m_contextMenu, SIGNAL(aboutToHide()), + SLOT(slotContextMenuAboutToHide())); + setCustomMenu(m_contextMenu); + + m_arrowButton = new KArrowButton(this); + QToolTip::add(m_arrowButton, i18n("Show menu")); + connect(m_arrowButton, SIGNAL(clicked()), SLOT(slotArrowButtonPressed())); + m_arrowButton->setFocusPolicy(NoFocus); + setupArrowButton(); + layout->addWidget(m_arrowButton); + + m_scroller = new NewsScroller(this, m_cfg); + layout->addWidget(m_scroller); + + m_dcopClient->registerAs("knewsticker", false); + + QToolTip::add(m_scroller, QString::null); + connect(m_scroller, SIGNAL(contextMenu()), SLOT(slotOpenContextMenu())); + + connect(m_newsTimer, SIGNAL(timeout()), SLOT(slotUpdateNews())); + + connect(m_updateTimer, SIGNAL(timeout()), SLOT(slotNotifyOfFailures())); + + m_aboutData->addAuthor("Frerich Raabe", I18N_NOOP("Original author"), + "raabe@kde.org"); + m_aboutData->addAuthor("Malte Starostik", I18N_NOOP("Hypertext headlines" + " and much more"), "malte@kde.org"); + m_aboutData->addAuthor("Wilco Greven", I18N_NOOP("Mouse wheel support"), + "greven@kde.org"); + m_aboutData->addAuthor("Adriaan de Groot", I18N_NOOP("Rotated scrolling text" + " modes"), "adridg@sci.kun.nl"); + + reparseConfig(); + + KStartupInfo::appStarted(); +} + +KNewsTicker::~KNewsTicker() +{ + delete m_cfg; + delete m_dcopClient; +} + +int KNewsTicker::heightForWidth(int) const +{ + return m_scroller->sizeHint().height() + m_arrowButton->height(); +} + +int KNewsTicker::widthForHeight(int) const +{ + return m_scroller->sizeHint().width() + m_arrowButton->width(); +} + +void KNewsTicker::preferences() +{ + KNewsTickerConfig dlg(m_cfg, this); + if (dlg.exec() == QDialog::Accepted) { + reparseConfig(); + } +} + +void KNewsTicker::about() +{ + KAboutApplication aboutDlg(m_aboutData); + aboutDlg.exec(); +} + +void KNewsTicker::help() +{ + kapp->invokeHelp(QString::null, QString::fromLatin1("knewsticker")); +} + +void KNewsTicker::reportBug() +{ + KBugReport bugReport(this, true, m_aboutData); + bugReport.exec(); +} + +void KNewsTicker::reparseConfig() +{ + m_cfg->reparseConfiguration(); + m_newsSources.clear(); + + QStringList newsSources = m_cfg->newsSources(); + QStringList::ConstIterator it = newsSources.begin(); + QStringList::ConstIterator end = newsSources.end(); + for (; it != end; ++it) { + NewsSourceBase::Ptr ns = m_cfg->newsSource((*it)); + if (!ns->data().enabled) + continue; + + connect(ns, SIGNAL(newNewsAvailable(const NewsSourceBase::Ptr &, bool)), + SLOT(slotNewsSourceUpdated(const NewsSourceBase::Ptr &, bool))); + connect(ns, SIGNAL(invalidInput(const NewsSourceBase::Ptr &)), + SLOT(slotNewsSourceFailed(const NewsSourceBase::Ptr &))); + m_newsSources.append(ns); + } + + setOfflineMode(m_cfg->offlineMode()); + if (!m_cfg->offlineMode()) + slotUpdateNews(); +} + +void KNewsTicker::slotUpdateNews() +{ + kdDebug(5005) << "slotUpdateNews()" << endl; + m_newNews = false; + + m_updateTimer->start(KProtocolManager::responseTimeout(), true); + + m_failedNewsUpdates.clear(); + m_pendingNewsUpdates.clear(); + + m_scroller->clear(); + + NewsSourceBase::List::Iterator it = m_newsSources.begin(); + NewsSourceBase::List::Iterator end = m_newsSources.end(); + for (; it != end; ++it) { + m_pendingNewsUpdates += (*it)->data().name; + (*it)->retrieveNews(); + (*it)->getIcon(); + } + kdDebug(5005) << "m_pendingNewsUpdates = " << m_pendingNewsUpdates.join(",") + << endl; +} + +void KNewsTicker::slotNewsSourceUpdated(const NewsSourceBase::Ptr &ns, + bool newNews) +{ + kdDebug(5005) << "slotNewsSourceUpdate()" << endl; + if (newNews) + m_newNews = true; + + if (!ns->articles().isEmpty()) + if (m_cfg->scrollMostRecentOnly()) + m_scroller->addHeadline(ns->articles().first()); + else { + Article::List articles = ns->articles(); + Article::List::ConstIterator artIt = articles.begin(); + Article::List::ConstIterator artEnd = articles.end(); + for (; artIt != artEnd; ++artIt) + m_scroller->addHeadline(*artIt); + } + + m_scroller->reset(true); + + m_pendingNewsUpdates.remove(ns->data().name); + kdDebug(5005) << "Updated news source: '" << ns->data().name << "'" << "\n" + << "m_pendingNewsUpdates = " << m_pendingNewsUpdates.join(",") << "\n" + << "m_failedNewsUpdates = " << m_failedNewsUpdates.join(",") + << endl; + + if (!m_pendingNewsUpdates.isEmpty()) + return; + + m_updateTimer->stop(); + + if (!m_failedNewsUpdates.isEmpty()) + slotNotifyOfFailures(); + + if (m_newNews) { + KNotifyClient::Instance instance(m_instance); + KNotifyClient::event(winId(), QString::fromLatin1("NewNews")); + } +} + +void KNewsTicker::slotNewsSourceFailed(const NewsSourceBase::Ptr &ns) +{ + m_failedNewsUpdates += ns->newsSourceName(); + slotNewsSourceUpdated(ns); +} + +void KNewsTicker::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == QMouseEvent::RightButton) + slotOpenContextMenu(); +} + +void KNewsTicker::slotOpenContextMenu() +{ + m_contextMenu->setFullMenu(true); + m_contextMenu->exec(QCursor::pos()); +} + +void KNewsTicker::slotArrowButtonPressed() +{ + QPoint pos(m_arrowButton->mapToGlobal(QPoint(0, 0))); + QSize size(m_arrowButton->size()); + + if (position() == KPanelApplet::pTop) { + pos.setY(pos.y() + size.height() + 2); + } else if (position() == KPanelApplet::pBottom) { + const int y = pos.y() - m_contextMenu->sizeHint().height() - 2; + pos.setY(QMAX(0, y)); + } else if (position() == KPanelApplet::pLeft ) { + pos.setX(pos.x() + size.width() + 2); + } else { // position() == KPanelApplet::pRight + const int x = pos.x() - m_contextMenu->sizeHint().width() - 2; + pos.setX(QMAX(0, x)); + } + + m_contextMenu->setFullMenu(true); + m_contextMenu->exec(pos); +} + +void KNewsTicker::positionChange(Position) +{ + delete layout(); + + QBoxLayout *layout; + + if (orientation() == Horizontal) + layout = new QHBoxLayout(this); + else + layout = new QVBoxLayout(this); + + if (m_arrowButton) { + layout->addWidget(m_arrowButton); + setupArrowButton(); + } + + layout->addWidget(m_scroller); +} + +void KNewsTicker::slotContextMenuAboutToHide() +{ + if (m_arrowButton) + m_arrowButton->setDown(false); +} + +void KNewsTicker::slotNotifyOfFailures() +{ + KNotifyClient::Instance instance(m_instance); + QString notification = QString::null; + + if (m_failedNewsUpdates.count() == 1) + notification = i18n("Could not update news site '%1'.
    " + "The supplied resource file is probably invalid or" + " broken.
    ").arg(m_failedNewsUpdates.first()); + else if (m_failedNewsUpdates.count() > 1 && m_failedNewsUpdates.count() < 8) { + notification = i18n("The following news sites had problems. Their" + " resource files are probably invalid or broken.
      "); + QStringList::ConstIterator it = m_failedNewsUpdates.begin(); + QStringList::ConstIterator end = m_failedNewsUpdates.end(); + for (; it != end; ++it) + notification += QString::fromLatin1("
    • %1
    • ").arg(*it); + notification += QString::fromLatin1("
    "); + } else + notification = i18n("Failed to update several news" + " sites. The Internet connection might be cut."); + + KNotifyClient::event(winId(), QString::fromLatin1("InvalidRDF"), notification); +} + +void KNewsTicker::setInterval(const uint interval) +{ + m_cfg->setInterval(interval); + if ( interval > 4 ) + m_newsTimer->changeInterval(interval * 60 * 1000); +} + +void KNewsTicker::setScrollingSpeed(const uint scrollingSpeed) +{ + m_cfg->setScrollingSpeed(scrollingSpeed); + m_scroller->reset(true); +} + +void KNewsTicker::setMouseWheelSpeed(const uint mouseWheelSpeed) +{ + m_cfg->setMouseWheelSpeed(mouseWheelSpeed); +} + +void KNewsTicker::setScrollingDirection(const uint scrollingDirection) +{ + m_cfg->setScrollingDirection(scrollingDirection); + m_scroller->reset(true); +} + +void KNewsTicker::setCustomNames(bool customNames) +{ + m_cfg->setCustomNames(customNames); +} + +void KNewsTicker::setScrollMostRecentOnly(bool scrollMostRecentOnly) +{ + m_cfg->setScrollMostRecentOnly(scrollMostRecentOnly); + m_scroller->reset(true); +} + +void KNewsTicker::setOfflineMode(bool offlineMode) +{ + if (offlineMode) + m_newsTimer->stop(); + else + if ( m_cfg->interval() > 4 ) + m_newsTimer->start(m_cfg->interval() * 1000 * 60); + + m_cfg->setOfflineMode(offlineMode); +} + +void KNewsTicker::setUnderlineHighlighted(bool underlineHighlighted) +{ + m_cfg->setUnderlineHighlighted(underlineHighlighted); + m_scroller->reset(true); +} + +void KNewsTicker::setShowIcons(bool showIcons) +{ + m_cfg->setShowIcons(showIcons); + m_scroller->reset(true); +} + +void KNewsTicker::setSlowedScrolling(bool slowedScrolling) +{ + m_cfg->setSlowedScrolling(slowedScrolling); +} + +void KNewsTicker::setForegroundColor(const QColor &foregroundColor) +{ + m_cfg->setForegroundColor(foregroundColor); + m_scroller->reset(false); +} + +void KNewsTicker::setBackgroundColor(const QColor &backgroundColor) +{ + m_cfg->setBackgroundColor(backgroundColor); + m_scroller->reset(false); +} + +void KNewsTicker::setHighlightedColor(const QColor &highlightedColor) +{ + m_cfg->setHighlightedColor(highlightedColor); + m_scroller->reset(false); +} + +void KNewsTicker::setupArrowButton() +{ + ArrowType at; + + if (orientation() == Horizontal) { + m_arrowButton->setFixedWidth(12); + m_arrowButton->setMaximumHeight(128); + at = (position() == KPanelApplet::pTop ? DownArrow : UpArrow); + } else { + m_arrowButton->setMaximumWidth(128); + m_arrowButton->setFixedHeight(12); + at = (position() == KPanelApplet::pLeft ? RightArrow : LeftArrow); + } + m_arrowButton->setArrowType(at); +} + +KNewsTickerMenu::KNewsTickerMenu(KNewsTicker *parent, const char *name) + : KPopupMenu(parent, name), + m_parent(parent), + m_fullMenu(false) +{ + populateMenu(); +} + +void KNewsTickerMenu::populateMenu() +{ + clear(); + + /* + * Perhaps this hardcoded stuff should be replaced by some kind of + * themeing functionality? + */ + const QPixmap lookIcon = SmallIcon(QString::fromLatin1("viewmag")); + const QPixmap newArticleIcon = SmallIcon(QString::fromLatin1("info")); + const QPixmap oldArticleIcon = SmallIcon(QString::fromLatin1("mime_empty")); + const QPixmap noArticlesIcon = SmallIcon(QString::fromLatin1("remove")); + + unsigned int articleIdx = 0; + const NewsSourceBase::List sources = m_parent->m_newsSources; + NewsSourceBase::List::ConstIterator nIt = sources.begin(); + for (; nIt != sources.end(); ++nIt) { + NewsSourceBase::Ptr ns = *nIt; + + KPopupMenu *submenu = new KPopupMenu; + int checkNewsId = submenu->insertItem(lookIcon, i18n("Check News"), this, SLOT(slotCheckNews(int)), 0, sources.findIndex(ns) + 1000); + setItemParameter(checkNewsId, sources.findIndex(ns)); + + submenu->insertSeparator(); + + if (m_parent->m_pendingNewsUpdates.contains(ns->newsSourceName())) { + submenu->insertItem(noArticlesIcon, i18n("Currently Being Updated, No Articles Available")); + } else if (!ns->articles().isEmpty()) { + const Article::List articles = ns->articles(); + Article::List::ConstIterator artIt = articles.begin(); + for (; artIt != articles.end(); ++artIt) { + Article::Ptr a = *artIt; + QString headline = a->headline().replace('&', "&&"); + int id; + if ( a->read() ) + id = submenu->insertItem(oldArticleIcon, headline, this, SLOT(slotOpenArticle(int)), 0, articleIdx+2000); + else + id = submenu->insertItem(newArticleIcon, headline, this, SLOT(slotOpenArticle(int)), 0, articleIdx+2000); + kdDebug( 5005 ) << "Setting articles index for " << a->headline() << " to " << articleIdx << endl; + setItemParameter( id, articleIdx++ ); + } + } else { + submenu->insertItem(noArticlesIcon, i18n("No Articles Available")); + } + + insertItem(ns->icon(), ns->newsSourceName().replace('&', "&&"), submenu); + } + + if (!m_parent->m_cfg->newsSources().isEmpty()) + insertSeparator(); + + insertItem(lookIcon, i18n("Check News"), m_parent, SLOT(slotUpdateNews())); + int i = insertItem(i18n("Offline Mode"), this, SLOT(slotToggleOfflineMode()), 0, 4711 ); + setItemChecked(i, m_parent->m_cfg->offlineMode()); + + if (m_fullMenu) { + insertSeparator(); + + const QPixmap logoIcon = SmallIcon(QString::fromLatin1("knewsticker")); + const QPixmap helpIcon = SmallIcon(QString::fromLatin1("help")); + const QPixmap confIcon = SmallIcon(QString::fromLatin1("configure")); + + insertTitle(logoIcon, i18n("KNewsTicker"), 0, 0); + + insertItem(helpIcon, i18n("Help"), this, SLOT(slotShowHelp())); + insertItem(logoIcon, i18n("About KNewsTicker"), this, SLOT(slotShowAbout())); + insertSeparator(); + insertItem(confIcon, i18n("Configure KNewsTicker..."), this, SLOT(slotConfigure())); + } +} + +void KNewsTickerMenu::slotShowHelp() +{ + m_parent->help(); +} + +void KNewsTickerMenu::slotShowAbout() +{ + m_parent->about(); +} + +void KNewsTickerMenu::slotConfigure() +{ + m_parent->preferences(); +} + +void KNewsTickerMenu::slotToggleOfflineMode() +{ + m_parent->setOfflineMode(!m_parent->m_cfg->offlineMode()); + setItemChecked( indexOf( 4711 ), !m_parent->m_cfg->offlineMode() ); +} + +void KNewsTickerMenu::slotCheckNews(int idx) +{ + m_parent->m_newsSources[ idx - 1000 ]->retrieveNews(); +} + +void KNewsTickerMenu::slotOpenArticle(int idx) +{ + unsigned int i = idx - 2000; + const NewsSourceBase::List sources = m_parent->m_newsSources; + NewsSourceBase::List::ConstIterator it = sources.begin(); + while ( it != sources.end() ) { + if ( ( *it )->articles().isEmpty() ) { + ++it; + continue; + } + + if ( i <= ( *it )->articles().count() - 1 ) + break; + + i -= ( *it )->articles().count(); + + ++it; + } + + if ( it == sources.end() ) + return; + + ( *it )->articles()[ i ]->open(); +} + +extern "C" +{ + KDE_EXPORT KPanelApplet* init(QWidget *parent, const QString &configFile) + { + KGlobal::locale()->insertCatalogue(QString::fromLatin1("knewsticker")); + return new KNewsTicker(configFile, KPanelApplet::Stretch, + KPanelApplet::Preferences | KPanelApplet::About | + KPanelApplet::Help | KPanelApplet::ReportBug, + parent, "knewsticker"); + } +} + +#include "knewsticker.moc" diff --git a/knewsticker/knewsticker.desktop b/knewsticker/knewsticker.desktop new file mode 100644 index 00000000..c27cee6b --- /dev/null +++ b/knewsticker/knewsticker.desktop @@ -0,0 +1,104 @@ +[Desktop Entry] +Name=News Ticker +Name[af]=Nuus Tikker +Name[be]=ÐглÑдальнік навінаў +Name[bn]=সংবাদ টিকার +Name[br]=Kliker keleier +Name[bs]=ÄŒitaÄ vijesti +Name[ca]=Teletip de notícies +Name[cs]=KNewsTicker +Name[cy]=Ticer Newyddion +Name[da]=Nyhedstelegraf +Name[de]=KNewsTicker +Name[el]=ΠÏοβολή ειδήσεων +Name[eo]=Novaĵprezentilo +Name[et]=Uudiste jälgija +Name[eu]=Berri-markatzailea +Name[fa]=تیکر اخبار +Name[fr]=Téléscripteur de nouvelles +Name[gl]=Visor de Novas +Name[he]=צג חדשות +Name[hu]=KNewsTicker +Name[id]=Ticker Berita +Name[is]=Fréttastrimill +Name[it]=Gestore notizie +Name[ja]=ニュースティッカー +Name[ka]=სიáƒáƒ®áƒšáƒ”თრმიმღები +Name[kk]=Жаңалық таÑпаÑÑ‹ +Name[km]=កម្មវិធី​ទទួល​ពáŸážáŸŒáž˜áž¶áž“ +Name[ko]=ìƒˆì†Œì‹ í‘œì‹œê¸° +Name[lt]=Naujienų praneÅ¡Ä—jas +Name[lv]=Ziņu Tikkers +Name[mn]=ÐœÑдÑÑний ДÑлгÑц +Name[ms]=Detik Berita +Name[nb]=Nyhetstelegraf +Name[nds]=Narichten-Ticker +Name[ne]=समाचार टिकर +Name[nl]=Nieuwsticker +Name[nn]=Telegraf +Name[nso]=Seswai sa Ditaba +Name[pt]=Extractor de Notícias +Name[pt_BR]=Mostrador de Notícias +Name[ru]=Монитор новоÑтей +Name[sk]=SledovaÄ správ +Name[sl]=Prikazovalnik novic +Name[sv]=Nyhetsläsare +Name[ta]=செயà¯à®¤à®¿à®•à®³à¯ அறிவிபà¯à®ªà®¾à®©à¯ +Name[th]=ตั๋วข่าว +Name[tr]=Haber Gözlemcisi +Name[uk]=Стрічка новин +Name[ven]=Musengulusi wa Mafhungo +Name[zh_CN]=新闻播报器 +Name[zh_HK]=æ–°èžå¿«å ± +Name[zh_TW]=æ–°èžç°¡å ± +Name[zu]=Umlungiseleli Wezindaba +X-KDE-Library=knewsticker_panelapplet +X-KDE-UniqueApplet=true +Icon=knewsticker +Comment=A scrolling RDF news ticker +Comment[be]=БÑгучы радок навінаў +Comment[bg]=Четец на новини RDF +Comment[bn]=à¦à¦•à¦Ÿà¦¿ অবিরাম আর-ডি-à¦à¦« সংবাদ টিকার +Comment[bs]=Traka s vijestima iz RDF datoteka +Comment[ca]=Teletip de notícies RDF amb desplaçament +Comment[cs]=Rolující RDF novinky +Comment[da]=En rullende RDF nyhedstelegraf +Comment[de]=Ein durchlaufender RDF-Newsticker +Comment[el]=Μια κυλιόμενη Ï€Ïοβολή ειδήσεων RDF +Comment[eo]=Rulumanta RDF-novaĵprezentilo +Comment[es]=Teletipo de noticias RDF +Comment[et]=RDF uudiste jälgija +Comment[eu]=RDF berri-markatzailea +Comment[fa]=یک لغزاندن تیکر اخبار RDF +Comment[fi]=Skrollaava RDF-uutisnäyttäjä +Comment[fr]=Défilement de nouvelles RDF +Comment[gl]=Un visor de novas RDF deslizante +Comment[he]=צג חדשות +Comment[hu]=HírbejelentÅ‘ RSS hírforrásokhoz +Comment[is]=Skrunandi RDF fréttastrimill +Comment[it]=Un ticker notizie RDF a scorrimento +Comment[ja]=スクロールã™ã‚‹ RDFニュースティッカー +Comment[ka]=მáƒáƒ¡áƒ áƒ˜áƒáƒšáƒ” RDF სიáƒáƒ®áƒšáƒ”თრმიმღები +Comment[kk]=Ðқтарылатын RDF жаңалық таÑпаÑÑ‹ +Comment[km]=កម្មវិធី​ទទួល​ពáŸážáŸŒáž˜áž¶áž“ RDF រមូរ +Comment[lt]=Slenkantis RDF naujienų praneÅ¡iklis +Comment[nb]=En rullende RDF nyhetstelegraf +Comment[nds]=En rullen RDF-Narichtenticker +Comment[ne]=सà¥à¤•à¥à¤°à¥‹à¤² गरिरहेको आर डी à¤à¤« टिकर +Comment[nl]=Een verschuivende RDF-nieuwsticker +Comment[nn]=RDF-nyhendetelegraf +Comment[pl]=PrzewijajÄ…cy siÄ™ pasek wiadomoÅ›ci RDF +Comment[pt]=Um extractor de notícias deslizante +Comment[pt_BR]=Um mini-aplicativo de notícias +Comment[ru]=Индикатор новоÑтей RDF Ñ Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‚ÐºÐ¾Ð¹ +Comment[sk]=Rolujúci sledovaÄ RDF správ +Comment[sl]=Prikazovalnik novic v obliki RDF +Comment[sr]=Клизајући приказивач RDF веÑти +Comment[sr@Latn]=Klizajući prikazivaÄ RDF vesti +Comment[sv]=En RDF-nyhetsövervakare med rullning +Comment[tr]=Akan bir RDF haber gözlemcisi +Comment[uk]=Стрічка новин RDF +Comment[zh_CN]=滚动的 RDF 新闻播报器 +Comment[zh_HK]=æ²è»¸å¼ RDF æ–°èžç°¡å ±å™¨ +Comment[zh_TW]=æ²è»¸çš„ RDF æ–°èžç°¡å ± + diff --git a/knewsticker/knewsticker.h b/knewsticker/knewsticker.h new file mode 100644 index 00000000..bbb228f6 --- /dev/null +++ b/knewsticker/knewsticker.h @@ -0,0 +1,146 @@ +/* + * knewsticker.h + * + * Copyright (c) 2000, 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef KNEWSTICKER_H +#define KNEWSTICKER_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include +#include + +#include "configiface.h" +#include "configaccess.h" +#include "newsengine.h" + +class KInstance; +class NewsSource; +class KArrowButton; +class NewsIconMgr; +class NewsScroller; +class KNewsTickerMenu; +class KAboutData; +class QTimer; + +class KNewsTicker : public KPanelApplet, virtual public ConfigIface, + virtual public DCOPObject +{ + Q_OBJECT + K_DCOP + + friend class KNewsTickerMenu; + + public: + KNewsTicker(const QString &, Type, int, QWidget * = 0, const char * = 0); + virtual ~KNewsTicker(); + + virtual int widthForHeight(int) const; + virtual int heightForWidth(int) const; + + k_dcop: + virtual void reparseConfig(); + virtual void updateNews() { slotUpdateNews(); } + virtual uint interval() const { return m_cfg->interval(); } + virtual uint scrollingSpeed() const { return m_cfg->scrollingSpeed(); } + virtual uint mouseWheelSpeed() const { return m_cfg->mouseWheelSpeed(); } + virtual uint scrollingDirection() const { return m_cfg->scrollingDirection(); } + virtual bool customNames() const { return m_cfg->customNames(); } + virtual bool scrollMostRecentOnly() const { return m_cfg->scrollMostRecentOnly(); } + virtual bool offlineMode() const { return m_cfg->offlineMode(); } + virtual bool underlineHighlighted() const { return m_cfg->underlineHighlighted(); } + virtual bool showIcons() const { return m_cfg->showIcons(); } + virtual bool slowedScrolling() const { return m_cfg->slowedScrolling(); } + virtual QColor foregroundColor() const { return m_cfg->foregroundColor(); } + virtual QColor backgroundColor() const { return m_cfg->backgroundColor(); } + virtual QColor highlightedColor() const { return m_cfg->highlightedColor(); } + virtual QStringList newsSources() const { return m_cfg->newsSources(); } + virtual void setInterval(const uint); + virtual void setScrollingSpeed(const uint); + virtual void setMouseWheelSpeed(const uint); + virtual void setScrollingDirection(const uint); + virtual void setCustomNames(bool); + virtual void setScrollMostRecentOnly(bool); + virtual void setOfflineMode(bool); + virtual void setUnderlineHighlighted(bool); + virtual void setShowIcons(bool); + virtual void setSlowedScrolling(bool); + virtual void setForegroundColor(const QColor &); + virtual void setBackgroundColor(const QColor &); + virtual void setHighlightedColor(const QColor &); + virtual void setNewsSources(const QStringList &) {} + + public slots: + void slotUpdateNews(); + void slotOpenContextMenu(); + + protected: + virtual void preferences(); + virtual void about(); + virtual void help(); + virtual void reportBug(); + virtual void mousePressEvent(QMouseEvent *); + virtual void positionChange(Position); + + protected slots: + void slotArrowButtonPressed(); + void slotNewsSourceUpdated(const NewsSourceBase::Ptr &, bool = false); + void slotNewsSourceFailed(const NewsSourceBase::Ptr &); + void slotContextMenuAboutToHide(); + void slotNotifyOfFailures(); + + private: + void setupArrowButton(); + + KInstance *m_instance; + DCOPClient *m_dcopClient; + ConfigAccess *m_cfg; + KArrowButton *m_arrowButton; + QTimer *m_newsTimer; + QTimer *m_updateTimer; + NewsIconMgr *m_newsIconMgr; + NewsScroller *m_scroller; + KAboutData *m_aboutData; + KNewsTickerMenu *m_contextMenu; + bool m_newNews; + NewsSourceBase::List m_newsSources; + QStringList m_failedNewsUpdates; + QStringList m_pendingNewsUpdates; +}; + +class KNewsTickerMenu : public KPopupMenu +{ + Q_OBJECT + + public: + KNewsTickerMenu(KNewsTicker *, const char * = 0); + void setFullMenu(bool full) { m_fullMenu = full; populateMenu(); } + + protected slots: + void populateMenu(); + + private slots: + void slotShowHelp(); + void slotShowAbout(); + void slotConfigure(); + void slotToggleOfflineMode(); + void slotCheckNews(int idx); + void slotOpenArticle(int idx); + + private: + KNewsTicker *m_parent; + bool m_fullMenu; +}; + +#endif // KNEWSTICKER_H diff --git a/knewsticker/knewsticker.upd b/knewsticker/knewsticker.upd new file mode 100644 index 00000000..80720bdb --- /dev/null +++ b/knewsticker/knewsticker.upd @@ -0,0 +1,13 @@ +# Updates from version 0.1 to version 0.2 +Id=KNewsTicker-0.2 +File=knewstickerrc,knewstickerappletrc +Script=knt-0.1-0.2.pl,perl +# Update for KDE 3.0, Kicker applets use a different naming scheme +Id=KNewsTicker-0.2-Rename-KDE3 +File=knewstickerappletrc,knewsticker_appletrc +AllGroups +# Update for KDE 3.1, seems like they changed the nameing scheme *AGAIN* +Id=KNewsTicker-0.2-Rename-KDE3.1 +File=knewsticker_appletrc,knewsticker_panelappletrc +AllGroups +# End of file diff --git a/knewsticker/knewstickerconfig.cpp b/knewsticker/knewstickerconfig.cpp new file mode 100644 index 00000000..528269a0 --- /dev/null +++ b/knewsticker/knewstickerconfig.cpp @@ -0,0 +1,582 @@ +/* + * knewstickerconfig.cpp + * + * Copyright (c) 2000, 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include "knewstickerconfig.h" +#include "configaccess.h" +#include "newsengine.h" +#include "newsiconmgr.h" +#include "newssourcedlgimpl.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +CategoryItem::CategoryItem(QListView *parent, const QString &text) + : QListViewItem(parent, text) +{ + setOpen(true); +} + +void CategoryItem::setOpen(bool open) +{ + if (open) + setPixmap(0, SmallIcon(QString::fromLatin1("folder_open"))); + else + setPixmap(0, SmallIcon(QString::fromLatin1("folder"))); + + QListViewItem::setOpen(open); +} + +NewsSourceItem::NewsSourceItem(KNewsTickerConfig *kcm, CategoryItem *parent, + const NewsSourceBase::Data &nsd) + : QCheckListItem(parent, QString::null, QCheckListItem::CheckBox), + m_parent(parent), + m_kcm(kcm) +{ + setData(nsd); +} + +NewsSourceBase::Data NewsSourceItem::data() const +{ + NewsSourceBase::Data nsd; + nsd.enabled = isOn(); + nsd.name = text(0); + nsd.sourceFile = text(1); + nsd.maxArticles = text(2).toUInt(); + nsd.icon = m_icon; + nsd.isProgram = m_isProgram; + nsd.subject = m_subject; + return nsd; +} + +void NewsSourceItem::setData(const NewsSourceBase::Data &nsd) +{ + setOn(nsd.enabled); + setText(0, nsd.name); + setText(1, nsd.sourceFile); + setText(2, QString::number(nsd.maxArticles)); + + m_icon = nsd.icon; + m_isProgram = nsd.isProgram; + m_subject = nsd.subject; + m_kcm->getNewsIcon(this, KURL( m_icon )); +} + +void NewsSourceItem::setIcon(const QPixmap &pixmap) +{ + setPixmap(0, pixmap); +} + +KNewsTickerConfig::KNewsTickerConfig(ConfigAccess *cfg, QWidget *parent, const char *name) + : KDialogBase(parent, name, true, i18n("Configuration"), Ok|Close), + m_cfg(cfg), + m_child(new KNewsTickerConfigWidget(this)), + m_newsIconMgr(NewsIconMgr::self()) +{ + setMainWidget(m_child); + + // Change various properties of the view which the Qt Designer cannot + // set at design time yet. + m_child->niInterval->setLabel(i18n("News query interval:")); + m_child->niInterval->setRange(4, 180); + + m_child->lvNewsSources->setShowSortIndicator(true); + m_child->lvNewsSources->setSelectionModeExt(KListView::Extended); + m_child->lvNewsSources->setAcceptDrops(true); + m_child->lvNewsSources->viewport()->setAcceptDrops(true); + m_child->lvNewsSources->viewport()->installEventFilter(this); + m_child->lvNewsSources->installEventFilter(this); + + connect(m_newsIconMgr, SIGNAL(gotIcon(const KURL &, const QPixmap &)), SLOT(slotGotNewsIcon(const KURL &, const QPixmap &))); + connect(m_child->bChooseFont, SIGNAL(clicked()), SLOT(slotChooseFont())); + + connect(m_child->lvNewsSources, SIGNAL(contextMenu(KListView *, QListViewItem *, const QPoint &)), + SLOT(slotNewsSourceContextMenu(KListView *, QListViewItem *, const QPoint &))); + connect(m_child->lvNewsSources, SIGNAL(selectionChanged()), + SLOT(slotNewsSourceSelectionChanged())); + connect(m_child->lvNewsSources, SIGNAL(doubleClicked(QListViewItem *, const QPoint &, int)), + SLOT(slotModifyNewsSource(QListViewItem *, const QPoint &, int))); + connect(m_child->bAddNewsSource, SIGNAL(clicked()), SLOT(slotAddNewsSource())); + connect(m_child->bRemoveNewsSource, SIGNAL(clicked()), SLOT(slotRemoveNewsSource())); + connect(m_child->bModifyNewsSource, SIGNAL(clicked()), SLOT(slotModifyNewsSource())); + + connect(m_child->lvFilters, SIGNAL(selectionChanged(QListViewItem *)), + SLOT(slotFilterSelectionChanged(QListViewItem *))); + connect(m_child->comboFilterAction, SIGNAL(activated(const QString &)), + SLOT(slotFilterActionChanged(const QString &))); + connect(m_child->comboFilterNewsSource, SIGNAL(activated(const QString &)), + SLOT(slotFilterNewsSourceChanged(const QString &))); + connect(m_child->comboFilterCondition, SIGNAL(activated(const QString &)), + SLOT(slotFilterConditionChanged(const QString &))); + connect(m_child->leFilterExpression, SIGNAL(textChanged(const QString &)), + SLOT(slotFilterExpressionChanged(const QString &))); + connect(m_child->bAddFilter, SIGNAL(clicked()), SLOT(slotAddFilter())); + connect(m_child->bRemoveFilter, SIGNAL(clicked()), SLOT(slotRemoveFilter())); + + load(); + + m_child->show(); +} + +void KNewsTickerConfig::load() +{ + m_child->comboFilterNewsSource->clear(); + m_child->comboFilterNewsSource->insertItem(i18n("All News Sources")); + + m_child->niInterval->setValue(m_cfg->interval()); + m_child->sliderMouseWheelSpeed->setValue(m_cfg->mouseWheelSpeed()); + m_child->cbCustomNames->setChecked(m_cfg->customNames()); + m_child->cbScrollMostRecentOnly->setChecked(m_cfg->scrollMostRecentOnly()); + m_child->cbSlowedScrolling->setChecked(m_cfg->slowedScrolling()); + + m_child->sliderScrollSpeed->setValue(m_cfg->scrollingSpeed()); + m_child->comboDirection->setCurrentItem(m_cfg->scrollingDirection()); + + m_font = m_cfg->font(); + m_child->colorForeground->setColor(m_cfg->foregroundColor()); + m_child->colorBackground->setColor(m_cfg->backgroundColor()); + m_child->colorHighlighted->setColor(m_cfg->highlightedColor()); + m_child->cbUnderlineHighlighted->setChecked(m_cfg->underlineHighlighted()); + m_child->cbShowIcons->setChecked(m_cfg->showIcons()); + + m_child->lvNewsSources->clear(); + QStringList nsList = m_cfg->newsSources(); + for (QStringList::Iterator it = nsList.begin(); it != nsList.end(); ++it) + addNewsSource(m_cfg->newsSource((*it))->data()); + + ArticleFilter::List filterList = m_cfg->filters(); + ArticleFilter::List::ConstIterator it = filterList.begin(); + ArticleFilter::List::ConstIterator end = filterList.end(); + for (; it != end; ++it) + addFilter(m_cfg->filter((*it))); + + slotNewsSourceSelectionChanged(); +} + +void KNewsTickerConfig::save() +{ + m_cfg->setInterval(m_child->niInterval->value()); + m_cfg->setMouseWheelSpeed(m_child->sliderMouseWheelSpeed->value()); + m_cfg->setCustomNames(m_child->cbCustomNames->isChecked()); + m_cfg->setScrollMostRecentOnly(m_child->cbScrollMostRecentOnly->isChecked()); + m_cfg->setSlowedScrolling(m_child->cbSlowedScrolling->isChecked()); + + m_cfg->setScrollingSpeed(m_child->sliderScrollSpeed->value()); + m_cfg->setScrollingDirection(static_cast(m_child->comboDirection->currentItem())); + + m_cfg->setFont(m_font); + m_cfg->setForegroundColor(m_child->colorForeground->color()); + m_cfg->setBackgroundColor(m_child->colorBackground->color()); + m_cfg->setHighlightedColor(m_child->colorHighlighted->color()); + m_cfg->setUnderlineHighlighted(m_child->cbUnderlineHighlighted->isChecked()); + m_cfg->setShowIcons(m_child->cbShowIcons->isChecked()); + + QStringList newsSources; + for (QListViewItemIterator it(m_child->lvNewsSources); it.current(); it++) + if (NewsSourceItem *item = dynamic_cast(it.current())) { + newsSources += item->data().name; + m_cfg->setNewsSource(item->data()); + } + m_cfg->setNewsSources(newsSources); + + ArticleFilter::List filters; + ArticleFilter f; + unsigned int i = 0; + for (QListViewItemIterator it(m_child->lvFilters); it.current(); it++) + if (QCheckListItem *item = dynamic_cast(it.current())) { + filters.append(i); + f.setAction(item->text(0)); + f.setNewsSource(item->text(2)); + f.setCondition(item->text(4)); + f.setExpression(item->text(5)); + f.setEnabled(item->isOn()); + f.setId(i++); + m_cfg->setFilter(f); + } + m_cfg->setFilters(filters); +} + +bool KNewsTickerConfig::eventFilter(QObject *o, QEvent *e) +{ + // + // "if ( e->type() == QEvent::DragEnter ) {" shoult normaly be enough. but there must be a bug somewhere in KListView. + if ( e->type() == QEvent::DragMove ) { + // + QDragEnterEvent *d = (QDragEnterEvent*)e; + d->accept(QTextDrag::canDecode(d)); + return true; // eat event + } + else if ( e->type() == QEvent::Drop) { + QDropEvent *d = (QDropEvent*)e; + QString newSourceUrl; + if ( QTextDrag::decode(d, newSourceUrl) ) { + // + // This is just for http://www.webreference.com/services/news/ + newSourceUrl = newSourceUrl.replace( QRegExp("^view-source:http%3A//"), "http://" ); + // + newSourceUrl = newSourceUrl.stripWhiteSpace(); + + //look for a new Name of Source: + QString name = i18n("Unknown"); + bool validName = false; + for (QListViewItemIterator it(m_child->lvNewsSources); it.current(); it++) { + if (it.current()->text(0) == name) { + validName = false; + break; + } else { + validName = true; + } + } + int i = 0; + while (validName == false) { + name = i18n("Unknown %1").arg(i); + for (QListViewItemIterator it(m_child->lvNewsSources); it.current(); it++) { + if (it.current()->text(0) == name) { + i++; + validName = false; + break; + } else { + validName = true; + } + } + } + + NewsSourceBase::Data nsd(name, newSourceUrl, "" , NewsSourceBase::Computers , 10, true, false); + NewsSourceDlgImpl nsDlg(this, 0L, true); + connect(&nsDlg, SIGNAL(newsSource(const NewsSourceBase::Data &)), + SLOT(slotAddNewsSource(const NewsSourceBase::Data &))); + nsDlg.setup(nsd, false); + nsDlg.exec(); + } + return true; // eat event + } + return QWidget::eventFilter( o, e ); +} + +void KNewsTickerConfig::resizeEvent(QResizeEvent *) +{ + m_child->resize(width(), height()); +} + +void KNewsTickerConfig::addNewsSource(const NewsSourceBase::Data &nsd, + bool select) +{ + CategoryItem *catItem = 0L; + + for (QListViewItemIterator it(m_child->lvNewsSources); it.current(); it++) { + if (it.current()->text(0) == NewsSourceBase::subjectText(nsd.subject)) { + catItem = static_cast(it.current()); + break; + } + } + + if (!catItem) + catItem = new CategoryItem(m_child->lvNewsSources, + NewsSourceBase::subjectText(nsd.subject)); + + NewsSourceItem *item = new NewsSourceItem(this, catItem, nsd); + if (select) + m_child->lvNewsSources->setCurrentItem(item); + + m_child->comboFilterNewsSource->insertItem(item->data().name); +} + +void KNewsTickerConfig::addFilter(const ArticleFilter &fd) +{ + QCheckListItem *item = new QCheckListItem(m_child->lvFilters, fd.action(), QCheckListItem::CheckBox); + item->setOn(fd.enabled()); + item->setText(1, m_child->lArticles->text()); + item->setText(2, fd.newsSource()); + item->setText(3, m_child->lHeadlines->text()); + item->setText(4, fd.condition()); + item->setText(5, fd.expression()); +} + +void KNewsTickerConfig::removeNewsSource() +{ + if (KMessageBox::warningContinueCancel(this, i18n("

    Do you really want to remove %n news" + " source?

    ", + "

    Do you really want to remove these %n news" + " sources?

    ", + m_child->lvNewsSources->selectedItems().count()), QString::null, KStdGuiItem::del()) == KMessageBox::Continue) { + int itemCount = m_child->lvNewsSources->selectedItems().count(); + for (int j = 0; j < itemCount; j++) { + if (m_child->lvNewsSources->selectedItems().isEmpty()) { break; } + QListViewItem *item = m_child->lvNewsSources->selectedItems().take(0); + for (int i = 0; i < m_child->comboFilterNewsSource->count(); i++) + if (m_child->comboFilterNewsSource->text(i) == item->text(0)) { + m_child->comboFilterNewsSource->removeItem(i); + break; + } + if (dynamic_cast(item) && item->parent()->childCount() == 1) + delete item->parent(); + else + delete item; + } + m_child->bRemoveNewsSource->setEnabled(false); + m_child->bModifyNewsSource->setEnabled(false); + } +} + +void KNewsTickerConfig::removeFilter(QListViewItem *item) +{ + if (KMessageBox::warningContinueCancel(this, i18n("

    Do you really want to remove the selected" + " filter?

    "), QString::null, KStdGuiItem::del()) == KMessageBox::Continue) { + delete item; + m_child->bRemoveFilter->setEnabled(false); + } +} + +void KNewsTickerConfig::slotNewsSourceContextMenu(KListView *, QListViewItem *item, const QPoint &) +{ + if (!dynamic_cast(item)) + return; + + KPopupMenu *menu = new KPopupMenu(); + + QPixmap addIcon = SmallIcon(QString::fromLatin1("news_subscribe")); + QPixmap modifyIcon = SmallIcon(QString::fromLatin1("edit")); + QPixmap removeIcon = SmallIcon(QString::fromLatin1("news_unsubscribe")); + QPixmap logoIcon = SmallIcon(QString::fromLatin1("knewsticker")); + + menu->insertTitle(logoIcon, i18n("Edit News Source")); + menu->insertItem(addIcon, i18n("&Add News Source"), 0); + if (item) { + menu->insertItem(modifyIcon, i18n("&Modify '%1'").arg(item->text(0)), 1); + if (m_child->lvNewsSources->selectedItems().count() == 1) { + menu->insertItem(removeIcon, i18n("&Remove '%1'").arg(item->text(0)), 2); + } else { + menu->insertItem(removeIcon, i18n("&Remove News Sources"), 2); + } + } else { + menu->insertItem(modifyIcon, i18n("&Modify News Source"), 1); + menu->insertItem(removeIcon, i18n("&Remove News Source"), 2); + menu->setItemEnabled(1, false); + menu->setItemEnabled(2, false); + } + + switch (menu->exec(QCursor::pos())) { + case 0: slotAddNewsSource(); break; + case 1: modifyNewsSource(item); break; + case 2: removeNewsSource(); break; + } + + delete menu; +} + +void KNewsTickerConfig::slotChooseFont() +{ + KFontDialog fd(this, "Font Dialog", false, true); + + fd.setFont(m_font); + + if (fd.exec() == KFontDialog::Accepted) { + if (m_font != fd.font()) { + m_font = fd.font(); + } + } +} + +void KNewsTickerConfig::slotAddNewsSource() +{ + NewsSourceDlgImpl nsDlg(this, 0L, true); + connect(&nsDlg, SIGNAL(newsSource(const NewsSourceBase::Data &)), + SLOT(slotAddNewsSource(const NewsSourceBase::Data &))); + nsDlg.exec(); +} + +void KNewsTickerConfig::slotAddNewsSource(const NewsSourceBase::Data &nsd) +{ + addNewsSource(nsd); +} + +void KNewsTickerConfig::slotModifyNewsSource() +{ + if ((m_modifyItem = dynamic_cast(m_child->lvNewsSources->selectedItems().take(0)))) + openModifyDialog(); +} + +void KNewsTickerConfig::slotModifyNewsSource(const NewsSourceBase::Data &nsd) +{ + if (m_modifyItem->data().subject != nsd.subject) { + QListViewItem *parentItem = m_modifyItem->parentItem(); + parentItem->takeItem(m_modifyItem); + if (parentItem->childCount() == 0) + delete parentItem; + + CategoryItem *catItem = 0L; + + for (QListViewItemIterator it(m_child->lvNewsSources); it.current(); it++) { + if (it.current()->text(0) == NewsSourceBase::subjectText(nsd.subject)) { + catItem = static_cast(it.current()); + break; + } + } + + if (!catItem) + catItem = new CategoryItem(m_child->lvNewsSources, + NewsSourceBase::subjectText(nsd.subject)); + + catItem->insertItem(m_modifyItem); + } + + m_modifyItem->setData(nsd); +} + +void KNewsTickerConfig::slotModifyNewsSource(QListViewItem *item, const QPoint &, int) +{ + if (dynamic_cast(item)) + modifyNewsSource(item); +} + +void KNewsTickerConfig::modifyNewsSource(QListViewItem *item) +{ + if ((m_modifyItem = dynamic_cast(item))) + openModifyDialog(); +} + +void KNewsTickerConfig::openModifyDialog() +{ + NewsSourceDlgImpl nsDlg(this, 0L, true); + connect(&nsDlg, SIGNAL(newsSource(const NewsSourceBase::Data &)), + SLOT(slotModifyNewsSource(const NewsSourceBase::Data &))); + nsDlg.setup(m_modifyItem->data(), true); + nsDlg.exec(); +} + +void KNewsTickerConfig::slotAddFilter() +{ + ArticleFilter fd; + fd.setAction(m_child->comboFilterAction->currentText()); + fd.setNewsSource(m_child->comboFilterNewsSource->currentText()); + fd.setCondition(m_child->comboFilterCondition->currentText()); + fd.setExpression(m_child->leFilterExpression->text()); + fd.setEnabled(true); + addFilter(fd); +} + +void KNewsTickerConfig::slotRemoveNewsSource() +{ + removeNewsSource(); +} + +void KNewsTickerConfig::slotRemoveFilter() +{ + QListViewItem *item=m_child->lvFilters->selectedItem(); + if(!item) + return; + removeFilter(item); +} + +void KNewsTickerConfig::slotNewsSourceSelectionChanged() +{ + m_child->bRemoveNewsSource->setEnabled(! m_child->lvNewsSources->selectedItems().isEmpty()); + m_child->bModifyNewsSource->setEnabled(m_child->lvNewsSources->selectedItems().count() == 1); +} + +void KNewsTickerConfig::slotFilterSelectionChanged(QListViewItem *item) +{ + for (int i = 0; i < m_child->comboFilterAction->count(); i++) + if (m_child->comboFilterAction->text(i) == item->text(0)) { + m_child->comboFilterAction->setCurrentItem(i); + break; + } + + for (int i = 0; i < m_child->comboFilterNewsSource->count(); i++) + if (m_child->comboFilterNewsSource->text(i) == item->text(2)) { + m_child->comboFilterNewsSource->setCurrentItem(i); + break; + } + + for (int i = 0; i < m_child->comboFilterCondition->count(); i++) + if (m_child->comboFilterCondition->text(i) == item->text(4)) { + m_child->comboFilterCondition->setCurrentItem(i); + break; + } + + m_child->leFilterExpression->setText(item->text(5)); + + m_child->bRemoveFilter->setEnabled(item); +} + +void KNewsTickerConfig::slotFilterActionChanged(const QString &action) +{ + QListViewItem *item = m_child->lvFilters->selectedItem(); + + if (item) { + item->setText(0, action); + } +} + +void KNewsTickerConfig::slotFilterNewsSourceChanged(const QString &newsSource) +{ + QListViewItem *item = m_child->lvFilters->selectedItem(); + + if (item) { + item->setText(2, newsSource); + } +} + +void KNewsTickerConfig::slotFilterConditionChanged(const QString &condition) +{ + QListViewItem *item = m_child->lvFilters->selectedItem(); + + if (item) { + item->setText(4, condition); + } +} + +void KNewsTickerConfig::slotFilterExpressionChanged(const QString &expression) +{ + QListViewItem *item = m_child->lvFilters->selectedItem(); + + if (item) { + item->setText(5, expression); + } +} + +void KNewsTickerConfig::getNewsIcon(NewsSourceItem *item, const KURL &url) +{ + m_itemIconMap[url.url()] = item; + m_newsIconMgr->getIcon(url); +} + +void KNewsTickerConfig::slotGotNewsIcon(const KURL &url, const QPixmap &pixmap) +{ + if (m_itemIconMap.find(url.url()) == m_itemIconMap.end()) { + kdDebug(5005) << "Got unknown icon for URL " << url << endl; + return; + } + m_itemIconMap[url.url()]->setIcon(pixmap); + m_itemIconMap.remove(url.url()); +} + +void KNewsTickerConfig::slotOk() +{ + save(); + accept(); +} + +#include "knewstickerconfig.moc" diff --git a/knewsticker/knewstickerconfig.h b/knewsticker/knewstickerconfig.h new file mode 100644 index 00000000..ec475236 --- /dev/null +++ b/knewsticker/knewstickerconfig.h @@ -0,0 +1,110 @@ +/* + * knewstickerconfig.h + * + * Copyright (c) 2000, 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef KCMNEWSTICKER_H +#define KCMNEWSTICKER_H + +#include "knewstickerconfigwidget.h" +#include "configaccess.h" +#include "newsengine.h" + +#include + +#include +#include +#include + +class KNewsTickerConfig; +class KNewsTickerConfigWidget; +class KConfig; +class NewsIconMgr; +class ConfigAccess; + +class CategoryItem : public QListViewItem +{ + public: + CategoryItem(QListView *, const QString &); + + void setOpen(bool); +}; + +class NewsSourceItem : public QCheckListItem +{ + public: + NewsSourceItem(KNewsTickerConfig *, CategoryItem *, const NewsSourceBase::Data &); + + NewsSourceBase::Data data() const; + void setData(const NewsSourceBase::Data &); + void setIcon(const QPixmap &); + + QListViewItem *parentItem() { return QCheckListItem::parent(); } + + private: + QString m_icon; + bool m_isProgram; + NewsSourceBase::Subject m_subject; + CategoryItem *m_parent; + KNewsTickerConfig *m_kcm; +}; + +class KNewsTickerConfig : public KDialogBase +{ + Q_OBJECT + friend class NewsSourceItem; + public: + KNewsTickerConfig(ConfigAccess *, QWidget * = 0, const char * = 0); + + void load(); + void save(); + void defaults(); + + protected: + void addNewsSource(const NewsSourceBase::Data &, bool = false); + void modifyNewsSource(QListViewItem *); + void removeNewsSource(); + void addFilter(const ArticleFilter &); + void removeFilter(QListViewItem *); + void resizeEvent(QResizeEvent *); + void openModifyDialog(); + bool eventFilter(QObject *o, QEvent *e); + void getNewsIcon(NewsSourceItem *, const KURL &); + + protected slots: + void slotNewsSourceContextMenu(KListView *, QListViewItem *, const QPoint &); + void slotChooseFont(); + void slotAddNewsSource(); + void slotAddFilter(); + void slotAddNewsSource(const NewsSourceBase::Data &); + void slotRemoveNewsSource(); + void slotRemoveFilter(); + void slotModifyNewsSource(); + void slotModifyNewsSource(const NewsSourceBase::Data &); + void slotModifyNewsSource(QListViewItem *, const QPoint &, int); + void slotNewsSourceSelectionChanged(); + void slotFilterSelectionChanged(QListViewItem *); + void slotFilterActionChanged(const QString &); + void slotFilterNewsSourceChanged(const QString &); + void slotFilterConditionChanged(const QString &); + void slotFilterExpressionChanged(const QString &); + virtual void slotOk(); + + private slots: + void slotGotNewsIcon(const KURL &, const QPixmap &); + + private: + ConfigAccess *m_cfg; + KNewsTickerConfigWidget *m_child; + QFont m_font; + NewsSourceItem *m_modifyItem; + NewsIconMgr *m_newsIconMgr; + QMap m_itemIconMap; +}; + +#endif // KCMNEWSTICKER_H diff --git a/knewsticker/knewstickerconfigwidget.ui b/knewsticker/knewstickerconfigwidget.ui new file mode 100644 index 00000000..3afa390d --- /dev/null +++ b/knewsticker/knewstickerconfigwidget.ui @@ -0,0 +1,1178 @@ + +KNewsTickerConfigWidget +The core widget of the KCMNewsTicker KControl module. +Frerich Raabe <raabe@kde.org> + + + KNewsTickerConfigWidget + + + + 0 + 0 + 597 + 525 + + + + + 3 + 5 + 0 + 0 + + + + + + + + + unnamed + + + 4 + + + 4 + + + + tabScrollerPreferences + + + + + + + + tabGeneral + + + General + + + + unnamed + + + 4 + + + 4 + + + + gbGeneral + + + General + + + + + + + + unnamed + + + 11 + + + 6 + + + + niInterval + + + true + + + 30 + + + min + + + Never + + + Interval of news queries + + + Here you can define at what interval KNewsTicker queries the configured news sources for new headlines. This depends generally on how fast you would like to hear about news and how much load you want to put on the network:<ul> +<li>A lower value (lower than <b>15 minutes</b>) enables you to get notified about news very quickly if you want or need to. Please note that it increases the network traffic significantly, though. Therefore, such low values should not be used if you query popular news sites (such as <a href="http://slashdot.org">Slashdot</a> or <a href="http://freshmeat.net">Freshmeat</a>) as they have generally already enough work processing the incoming queries.</li> +<li>A higher value (higher than <b>45 minutes</b>) will make you hear about news less quickly. For non-timecritical applications, it should be suitable, though. The positive aspect of longer intervals is that only very little load is put on the network; this saves resources and nerves, for you and the system administrators of the news sites you query.</li></ul> +The default value (30 minutes) should be appropriate and reasonable in most cases. + + + + + Layout5 + + + + unnamed + + + 0 + + + 6 + + + + lNonSensitive + + + Nonsensitive + + + Mousewheel sensitivity + + + This slider allows you to define how quickly/slowly the text should be scrolled when using the mousewheel. + + + + + sliderMouseWheelSpeed + + + 1 + + + 10 + + + Horizontal + + + 150 + + + Mousewheel sensitivity + + + This slider allows you to define how fast/slow the text should be scrolled when using the mousewheel. + + + + + lSensitive + + + Sensitive + + + Mousewheel sensitivity + + + This slider allows you to define how fast/slow the text should be scrolled when using the mousewheel. + + + + + + + lMouseWheelSensitivity + + + &Mousewheel sensitivity: + + + sliderMouseWheelSpeed + + + Mousewheel sensitivity + + + This slider allows you to define how fast/slow the text should be scrolled when using the mousewheel. + + + + + cbCustomNames + + + &Use custom names for news sites + + + Use the names defined in the list of news sources + + + Check this box to make the news ticker use the names you specified in the list of news sources (available on the tab labeled <i>News sources</i>) instead of the ones the news sites themselves report.<br>This can be handy for news sites which report a very long or useless name. + + + + + + + Spacer5_2 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + + + tabNewsSources + + + News Sources + + + + unnamed + + + 4 + + + 4 + + + + + Name of Site + + + true + + + true + + + + + Source File + + + true + + + true + + + + + Max. Articles + + + true + + + true + + + + lvNewsSources + + + true + + + true + + + News sources to be queried + + + This list lets you manage the list of news sites the news ticker will query for headlines. The news sources are arranged in a tree-like hierarchy and sorted by topic.<br>The column labeled "Max. articles" shows how many articles will be cached for the news sites (read: how many articles will be accessible through the context menu).<ul> +<li>To add a site, you can either drag the URL of the RDF or RSS file to this list from Konqueror or any other application, or use the <i>Add...</i> button in the bottom right corner.</li> +<li>To modify a site, just double-click on the particular news source you would like to edit and an input field will pop up which lets you edit the respective property.</li> +<li>To remove a site, simply select a news source in the list and click on the <i>Remove</i> button in the lower right corner.</li></ul> +Note that you can also right-click on the list to open a menu which lets you add and remove news sources. You can also enable or disable certain news sources temporarily by checking or unchecking the box next to it; those news sources whose boxes are checked are considered activated and will be processed by KNewsTicker. + + + + + bRemoveNewsSource + + + false + + + R&emove + + + Remove selected site + + + Click this button to remove the currently selected news site from the list. + + + + + Spacer3 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + bAddNewsSource + + + Add... + + + Add a new site + + + Click this button to add a new site to the list. Note that you can also drag a RDF or RSS file to this list (i.e. from Konqueror) to add it to the list. + + + + + bModifyNewsSource + + + false + + + &Modify... + + + Modify selected news source + + + Click this button to open a dialog which lets you edit the properties (such as the name, the source file, or the icon) of the currently selected news source. + + + + + + + tabFilters + + + Filters + + + + unnamed + + + 4 + + + 4 + + + + + Action + + + false + + + true + + + + + + + + false + + + true + + + + + Affects + + + false + + + true + + + + + + + + false + + + true + + + + + Condition + + + false + + + true + + + + + Expression + + + false + + + true + + + + lvFilters + + + true + + + true + + + Currently configured filters + + + Here you can see the list of currently configured filters and manage them as well as add new filters. Managing them is fairly easy:<ul> +<li>To <b>add</b> a new filter, specify its properties in the box below labeled <i>Filter properties</i> and press the <i>Add</i> button in the lower right corner.</li> +<li><b>Modifying</b> an existing filter is done in a similar manner: simply select the filter you would like to edit in the list and change its properties in the box below.</li> +<li>Finally, to <b>remove</b> a filter, select it in the list and press the button labeled <i>Remove</i> in the lower right corner.</li></ul> +You can also enable or disable certain filters temporarily by checking or unchecking the box next to them; those filters whose boxes are checked are considered enabled and will be honored by KNewsTicker.<br> +Note that the filters are processed from the top to the bottom so that of two filters which might nullify each other (like "Show...does not contain KDE" and "Show...contains KDE") only the one which is lower in the list will take effect. + + + + + Spacer9 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + bRemoveFilter + + + false + + + R&emove + + + Remove selected filter + + + Press this button to remove the selected filter from the list. + + + + + bAddFilter + + + A&dd + + + Add configured filter + + + Press this button to add the configured filter to the list. + + + + + gbFilterProperties + + + Filter Properties + + + + unnamed + + + 11 + + + 6 + + + + + Hide + + + + + Show + + + + comboFilterAction + + + Action for this filter + + + Here you can define what should happen if this filter matches (e.g. whether the matching articles should be shown or hidden). + + + + + Spacer5 + + + Horizontal + + + Fixed + + + + 20 + 20 + + + + + + lArticles + + + articles from + + + + + + all news sources + + + + comboFilterNewsSource + + + Affected news sources + + + Here you can specify which news sources (or all of them) are affected. Note that only the news sources which have been activated on the <i>News sources</i> tab are shown in this combo box. + + + + + lwhose + + + whose + + + + + leFilterExpression + + + Keyword/Expression + + + Here you can type a keyword or expression to be used for this filter which depends on the condition you selected in the combo box at the right:<ul> +<li><b>contain</b>, <b>does not contain</b> - you should probably enter a keyword here, like "KDE", "Baseball" or "Business". The keyword is not case-sensitive so it does not matter whether you type "kde", "KDE" or "kDe".</li> +<li><b>equals</b>, <b>does not equal</b> - enter a phrase or expression here to have the filter match only those articles whose headlines match <b>exactly</b> the text you typed. The phrase you type will be considered to be case-sensitive, so it makes a difference whether you show articles which contain "Boeing" or "BOEING".</li> +<li><b>matches</b> - a regular expression is expected. Recommended only if you are familiar with regular expressions, i.e. it should be used by advanced users only.</li></ul> + + + + + Spacer15 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + lHeadlines + + + headlines + + + + + + contain + + + + + do not contain + + + + + equal + + + + + do not equal + + + + + match + + + + comboFilterCondition + + + Condition for this filter + + + This combo box lets you specify the condition under which the keyword/expression you entered in the input field at the right will match. You can select one of the following values:<ul> +<li>contain - the filter matches if the headline contains the keyword.</li> +<li>does not contain - the filter matches if the headline does not contain the keyword.</li> +<li>equals - the filter matches if the headline equals the expression.</li> +<li>does not equal - the filter matches if the headline does not equal the expression.</li> +<li>matches - the filter matches if the expression matches the headline. The expression you typed at the right will be considered a regular expression in this mode.</li> + + + + + + + + + tabScrollerPreferences + + + Scroller + + + + unnamed + + + 4 + + + 4 + + + + gbScrollerBehaviour + + + Behavior + + + + unnamed + + + 11 + + + 6 + + + + Layout5 + + + + unnamed + + + 0 + + + 6 + + + + lScrollingSpeed + + + &Scrolling speed: + + + sliderScrollSpeed + + + Scrolling speed + + + Here you can define how fast the text should be scrolling. If you only have a little space on your taskbar (and therefore a rather small news ticker), you should probably set this to a slower speed so that you have a chance to read the headlines. For wider news tickers (and better eyes), faster scrolling is probably appropriate so that you do not have to wait too long for the next headline. + + + + + Layout6_2 + + + + unnamed + + + 0 + + + 6 + + + + lSlow + + + Slow + + + Scrolling speed + + + Here you can define how fast the text should be scrolling. If you only have a little space on your taskbar (and therefore a rather small news ticker), you should probably set this to a slower speed so that you have a chance to read the headlines. For wider news tickers (and better eyes), faster scrolling is probably appropriate so that you do not have to wait too long for the next headline. + + + + + sliderScrollSpeed + + + 1 + + + 100 + + + Horizontal + + + 10 + + + Scrolling speed + + + Here you can define how fast the text should be scrolling. If you only have a little space on your taskbar (and therefore a rather small news ticker), you should probably set this to a slower speed so that you have a chance to read the headlines. For wider news tickers (and better eyes), faster scrolling is probably appropriate so that you do not have to wait too long for the next headline. + + + + + lFast + + + Fast + + + Scrolling speed + + + Here you can define how fast the text should be scrolling. If you only have a little space on your taskbar (and therefore a rather small news ticker), you should probably set this to a slower speed so that you have a chance to read the headlines. For wider news tickers (and better eyes), faster scrolling is probably appropriate so that you do not have to wait too long for the next headline. + + + + + + + + + lDirectionOfScrolling_2 + + + Di&rection of scrolling: + + + comboDirection + + + Direction of scrolling + + + These options allow you to define in what direction the text should be scrolled, e.g. to the left or to the right, upwards or downwards. + + + + + + To the Left + + + + + To the Right + + + + + Upwards + + + + + Downwards + + + + + Upwards, Rotated + + + + + Downwards, Rotated + + + + comboDirection + + + Direction of scrolling + + + These options allow you to define in what direction the text should be scrolled, e.g. to the left or to the right, upwards or downwards. Rotated means the text is rotated 90 degrees. + + + + + + + gbScrollerAppearance + + + true + + + Appearance + + + + unnamed + + + 11 + + + 6 + + + + lHighlightedColor_2 + + + H&ighlighted color: + + + colorHighlighted + + + Highlighted color + + + Click the button at the right to open a convenient color-selection dialog which lets you choose the color of the headlines when they are highlighted (when you move the mouse over them). + + + + + colorHighlighted + + + + + + Highlighted color + + + Click this button to open a convenient color-selection dialog which lets you choose the color of the headlines when they are highlighted (when you move the mouse over them). + + + + + lBackgroundColor_2 + + + &Background color: + + + colorBackground + + + Background color + + + Click the button at the right to open a convenient color-selection dialog which lets you choose the background color of the scrolling text. + + + + + colorBackground + + + + + + Background color + + + Click this button to open a convenient color-selection dialog which lets you choose the background color of the scrolling text. + + + + + lForegroundColor_2 + + + &Foreground color: + + + colorForeground + + + Foreground color + + + Click the button at the right to open a convenient color-selection dialog which lets you choose the color of the scrolling text. + + + + + colorForeground + + + + + + Foreground color + + + Click this button to open a convenient color-selection dialog which lets you choose the color of the scrolling text. + + + + + lFont_2 + + + F&ont: + + + bChooseFont + + + Scrolling text font + + + Click on the button at the right labeled <i>Choose Font...</i> to choose the font which will be used for the scrolling text. Please note that certain fonts are harder to read than others, especially when they are used as scrolling text. You should probably choose a font which can be easily read while it is moving. + + + + + bChooseFont + + + Choose Font... + + + Scrolling text font + + + Click here to choose the font which will be used for the scrolling text. Please note that certain fonts are harder to read than others, especially when they are used as a scrolling text. You should probably choose a font which can be easily read while it is moving. + + + + + + + gbMiscScrollingOptions + + + Miscellaneous + + + + unnamed + + + 11 + + + 6 + + + + cbScrollMostRecentOnly + + + Scroll the most recent headlines onl&y + + + Show only the most recent headline for each news site in the scroller + + + Check this button to show only the most recent headline for each news site. + + + + + cbShowIcons + + + Show icons + + + Show icons in the scrolling text + + + Click this button to make KNewsTicker show the icons of the news site to which each headline belongs. This makes associating a headline to a news site very easy but takes up some space in the text. + + + + + cbSlowedScrolling + + + &Temporarily slowed scrolling + + + Slow the scrolling down when mouse points at the scroller + + + Check this box to make KNewsTicker slow the scrolling down when you move the mouse cursor over the scrolling text. This makes clicking on items and dragging the icons (if enabled) away a lot easier. + + + + + cbUnderlineHighlighted + + + &Underline highlighted headline + + + Underline the currently highlighted headline + + + Check this box to have the currently highlighted headline (e.g. the headline which is currently under the mouse cursor) underlined. + + + + + + + Spacer6 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + + + + + sliderMouseWheelSpeed + niInterval + cbCustomNames + lvNewsSources + bAddNewsSource + bModifyNewsSource + bRemoveNewsSource + lvFilters + comboFilterAction + comboFilterNewsSource + comboFilterCondition + leFilterExpression + bAddFilter + bRemoveFilter + sliderScrollSpeed + comboDirection + bChooseFont + colorForeground + colorBackground + colorHighlighted + cbScrollMostRecentOnly + cbShowIcons + cbSlowedScrolling + cbUnderlineHighlighted + tabScrollerPreferences + + + kcolorbutton.h + kcombobox.h + klineedit.h + klistview.h + knuminput.h + + + + knuminput.h + knuminput.h + klistview.h + klistview.h + kcombobox.h + kcombobox.h + klineedit.h + kcombobox.h + kcombobox.h + kcolorbutton.h + kcolorbutton.h + kcolorbutton.h + + diff --git a/knewsticker/knewstickerstub/Makefile.am b/knewsticker/knewstickerstub/Makefile.am new file mode 100644 index 00000000..200e2eac --- /dev/null +++ b/knewsticker/knewstickerstub/Makefile.am @@ -0,0 +1,13 @@ +INCLUDES = -I$(top_srcdir)/knewsticker/common $(all_includes) + +bin_PROGRAMS = knewstickerstub + +servicedir = $(kde_appsdir)/.hidden +service_DATA = knewstickerstub.desktop + +METASOURCES = AUTO + +knewstickerstub_SOURCES = knewstickerstub.cpp +knewstickerstub_LDADD = $(LIB_KIO) ../common/libknewstickercommon.la +knewstickerstub_LDFLAGS = $(all_libraries) $(KDE_RPATH) + diff --git a/knewsticker/knewstickerstub/knewstickerstub.cpp b/knewsticker/knewstickerstub/knewstickerstub.cpp new file mode 100644 index 00000000..7f5960a4 --- /dev/null +++ b/knewsticker/knewstickerstub/knewstickerstub.cpp @@ -0,0 +1,80 @@ +/* + * knewstickerstub.cpp + * + * Copyright (c) 2000, 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ + +#include +#include +#include +#include + +#include + + +#include "configaccess.h" + +static const char name[] = "knewstickerstub"; +static const char verbname[] = I18N_NOOP("KNewsTickerStub"); +static const char version[] = "0.3"; +static const char description[] = I18N_NOOP("A frontend to the KNewsTicker configuration"); +static const char copyright[] = I18N_NOOP("(c)2000, 2001 Frerich Raabe"); + +static const KCmdLineOptions options[] = +{ + {"a", 0, 0}, + {"addrdf ", I18N_NOOP("Add the RDF/RSS file referenced by "), 0}, + KCmdLineLastOption +}; + +int main(int argc, char **argv) +{ + KLocale::setMainCatalogue("knewsticker"); + + KAboutData aboutData(name, verbname, version, description, + KAboutData::License_BSD, copyright); + aboutData.addAuthor("Frerich Raabe", I18N_NOOP("Author"), "raabe@kde.org"); + + KCmdLineArgs::init(argc, argv, &aboutData); + KCmdLineArgs::addCmdLineOptions(options); + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + KApplication app(false, false); + + DCOPClient *dcopClient = app.dcopClient(); + dcopClient->attach(); + + KConfig cfg(QString::fromLatin1("knewsticker_panelappletrc"), false, false); + ConfigAccess configFrontend(&cfg); + + if (args->isSet("addrdf")) { + QStringList newsSources = configFrontend.newsSources(); + + // TODO: Use the "Suggest" functionality from addnewssourcedlgimpl.cpp here. + QString name = i18n("Unknown"); + if (newsSources.contains(name)) + for (unsigned int i = 0; ; i++) + if (!newsSources.contains(i18n("Unknown %1").arg(i))) { + name = i18n("Unknown %1").arg(i); + break; + } + + newsSources += name; + + QString sourceFile = QString(args->getOption("addrdf")); + configFrontend.setNewsSource(NewsSourceBase::Data(name, sourceFile)); + configFrontend.setNewsSources(newsSources); + + QByteArray data; + dcopClient->send("knewsticker", "KNewsTicker", "reparseConfig()", data); + } + + args->clear(); + + return 0; +} diff --git a/knewsticker/knewstickerstub/knewstickerstub.desktop b/knewsticker/knewstickerstub/knewstickerstub.desktop new file mode 100644 index 00000000..1ce2238b --- /dev/null +++ b/knewsticker/knewstickerstub/knewstickerstub.desktop @@ -0,0 +1,202 @@ +[Desktop Entry] +Name=KNewsTicker Config Frontend +Name[be]=Праграма наÑÑ‚Ð°ÑžÐ»ÐµÐ½Ð½Ñ KNewsTicker +Name[bg]=ÐаÑтройване четеца на новини +Name[bn]=কে-নিউজ-টিকার কনফিগ ফà§à¦°à¦¨à§à¦Ÿ-à¦à¦¨à§à¦¡ +Name[bs]=KNewsTicker konfiguracijski frontend +Name[ca]=Interfície de configuració per a KNewsTicker +Name[cs]=Nastavení KNewsTickeru +Name[da]=KNewsTicker config-grænseflade +Name[de]=Newsticker-Einrichtung +Name[el]=ΠÏόγÏαμμα ÏÏθμισης KNewsTicker +Name[eo]=Agordilo por Novaĵprezentilo +Name[es]=Interfaz de configuración de KNewsTicker +Name[et]=Uudistejälgija seadistamine +Name[eu]=KNewsTicker konfigurazio interfazea +Name[fa]=پایانۀ پیکربندی KNewsTicker +Name[fi]=KNewsTickerin asetusten käyttöliittymä +Name[fr]=Interface pour la configuration du téléscripteur +Name[gl]=Interface de Configuración de KNewsTicker +Name[he]=ממשק לקביעת התצורה של KNewsTicker +Name[hu]=Grafikus felületű beállítóprogram a KNewsTickerhez +Name[is]=Stillingatól KNewsTicker +Name[it]=Interfaccia per la configurazione di KNewsTicker +Name[ja]=ニュースティッカー設定フロントエンド +Name[ka]=KNewsTicker კáƒáƒœáƒ¤áƒ˜áƒ’ურáƒáƒªáƒ˜áƒ +Name[kk]=KNewsTicker баптауы +Name[km]=ផ្នែក​ážáž¶áž„​មុážâ€‹ážŸáž˜áŸ’រាប់​កំណážáŸ‹â€‹ážšáž…នាសម្ពáŸáž“្ធ KNewsTicker +Name[lt]=KNewsTicker derinimo naudotojo sÄ…saja +Name[mk]=Конфигурација на интерфејÑот на KNewsTicker +Name[nb]=Oppsettsmodul for KNewsTicker +Name[nds]=KNewsTicker-Inrichten +Name[ne]=केडीई नà¥à¤¯à¥‚ज टिकर कनà¥à¤«à¤¿à¤— फà¥à¤°à¤¨à¥à¤Ÿà¤‡à¤¨à¥à¤¡ +Name[nl]=KNewsTicker configuratie-interface +Name[nn]=Oppsett av KNewsTicker +Name[pl]=Interfejs konfiguracji KNewsTickera +Name[pt]=Configuração das Notícias +Name[pt_BR]=Interface de configuração do KNewsTicker +Name[ro]=Interfaţă configurare Åžtiri Internet +Name[ru]=Параметры монитора новоÑтей +Name[se]=Heivet KNewsTicker-lavtta +Name[sk]=Nastavenie KNewsTicker +Name[sl]=Nastavitveni vmesnik programa KNewsTicker +Name[sr]=Ð˜Ð½Ñ‚ÐµÑ€Ñ„ÐµÑ˜Ñ Ð·Ð° подешавање KNewsTicker-а +Name[sr@Latn]=Interfejs za podeÅ¡avanje KNewsTicker-a +Name[sv]=Gränssnitt för anpassning av nyhetsövervakaren +Name[ta]=கேசெயà¯à®¤à®¿ அறிவிபà¯à®ªà®¾à®©à¯ கடà¯à®Ÿà®®à¯ˆ à®®à¯à®©à¯à®©à®¿à®²à¯ˆ +Name[tg]=KПешохири Танзими Дидабони Ðхборот +Name[tr]=KDE Haber Gözlemcisi Yapılandırma Önyüzü +Name[uk]=Ð†Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ KNewsTicker +Name[zh_CN]=KNewsTicker é…ç½®å‰ç«¯ +Name[zh_HK]=KNewsTicker 設定å‰ç«¯ +Name[zh_TW]=KNewsTicker 設定å‰ç«¯ +Comment=A frontend for the KNewsTicker configuration +Comment[af]='n voorprogram vir die K-nuustikker opstelling +Comment[ar]=واجهة لضبط مجدد الأخبار +Comment[az]=KNewsTicker quraÅŸdırması üçün bir axtar üz +Comment[be]=Праграма наÑÑ‚Ð°ÑžÐ»ÐµÐ½Ð½Ñ KNewsTicker +Comment[bg]=Графичен Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð·Ð° наÑтройки на четеца на новини +Comment[bn]=কে-নিউজ-টিকার কনফিগারেশনের জনà§à¦¯ à¦à¦•à¦Ÿà¦¿ ফà§à¦°à¦¨à§à¦Ÿ-à¦à¦¨à§à¦¡ +Comment[bs]=Frontend za podeÅ¡avanje KNewsTicker-a +Comment[ca]=Una interfície per a la configuració del KNewsTicker +Comment[cs]=Rozhraní pro nastavení KNewsTickeru +Comment[cy]=Blaen-wyneb i'r ffurweddiad KTicerNewyddion +Comment[da]=En grænseflade til indstilling af KNewsTicker +Comment[de]=Eine Oberfläche zur Einrichtung von KNewsTicker +Comment[el]=Ένα Ï€ÏόγÏαμμα για τη ÏÏθμιση του KNewsTicker +Comment[eo]=Agordilofasado por Novaĵprezentilo +Comment[es]=Un interfaz para la configuración de KNewsTicker +Comment[et]=Uudistejälgija seadistamise kasutajaliides +Comment[eu]=KNewsTicker konfigurazioaren interfazea +Comment[fa]=یک پایانه برای پیکربندی KNewsTicker +Comment[fi]=Käyttöliittymä KNewsTicker-asetuksiin +Comment[fr]=Interface pour la configuration de KNewsTicker +Comment[ga]=Comhéadan le haghaidh chumraíocht KNewsTicker +Comment[gl]=Unha interfaz para a configuración de KNewsTicker +Comment[he]=ממשק לקביעת התצורה של KNewsTicker +Comment[hi]=के-नà¥à¤¯à¥‚ज टिकर कॉनà¥à¤«à¤¼à¤¿à¤—रेशन के लिठफà¥à¤°à¤¨à¥à¤Ÿà¤à¤£à¥à¤¡ +Comment[hr]=SuÄelje za podeÅ¡avanje KNewsTicker-a +Comment[hu]=ElÅ‘tétprogram a KNewsTicker beállításához +Comment[is]=Stillingatól fyrir Fréttastrimilinn +Comment[it]=Un'interfaccia grafica per la configurazione di KNewsTicker +Comment[ja]=KNewsTicker (ニュースティッカー) ã®è¨­å®šãƒ•ãƒ­ãƒ³ãƒˆã‚¨ãƒ³ãƒ‰ +Comment[ka]=KNewsTicker კáƒáƒœáƒ¤áƒ˜áƒ’ურáƒáƒªáƒ˜áƒ˜áƒ¡ +Comment[kk]=KNewsTicker баптауының интерфейÑÑ– +Comment[km]=ផ្នែក​ážáž¶áž„​មុážâ€‹ážŸáž˜áŸ’រាប់​កំណážáŸ‹â€‹ážšáž…នាសម្ពáŸáž“្ធ KNewsTicker +Comment[ko]=Kìƒˆì†Œì‹ í‘œì‹œê¸° 설정 ë„구 +Comment[lt]=Naudotojo sÄ…saja, skirta KNewsTicker derinimui +Comment[lv]=KZiņuTikkera konfigurÄ“Å¡anas frontends +Comment[mk]=Ð˜Ð½Ñ‚ÐµÑ€Ñ„ÐµÑ˜Ñ Ð·Ð° конфигурацијата на KNewsTicker +Comment[mn]=KNewsTicker-ийн Тохируулгыг ҮзүүлÑÑ… +Comment[ms]=Bahagian depan untuk penyelarasan KNewsTicker +Comment[mt]=FaÄ‹Ä‹ata għall-konfigurazzjoni ta' KNewsTicker +Comment[nb]=Program for oppsett av KNewsTicker +Comment[nds]=En Böversiet för de KNewsTicker-Instellen +Comment[ne]=केडीई नà¥à¤¯à¥‚ज टिकर कनà¥à¤«à¤¿à¤—रेसनका लागि फà¥à¤°à¤¨à¥à¤Ÿà¤‡à¤¨à¥à¤¡ +Comment[nl]=Een grafische schil voor de configuratie van KNewsTicker +Comment[nn]=Program for oppsett av KNewsTicker +Comment[nso]=Mafelelo a kapele a peakanyo ya Seswai sa KDitaba +Comment[pl]=Konfiguracja KNewsTickera +Comment[pt]=Configuração do extractor de notícias +Comment[pt_BR]=Uma interface para configuração do KNewsTicker +Comment[ro]=O interfaţă de configurare pentru Åžtiri Internet +Comment[ru]=ГрафичеÑÐºÐ°Ñ Ð¾Ð±Ð¾Ð»Ð¾Ñ‡ÐºÐ° Ð´Ð»Ñ ÐºÐ»Ð¸ÐµÐ½Ñ‚Ð° новоÑтей +Comment[se]=Prográmma mainna heiveha KNewsTicker +Comment[sk]=Nastavenie KNewsTicker +Comment[sl]=Vmesnik za nastavitve programa KNewsTicker +Comment[sr]=Ð˜Ð½Ñ‚ÐµÑ€Ñ„ÐµÑ˜Ñ Ð·Ð° подешавање KNewsTicker-а +Comment[sr@Latn]=Interfejs za podeÅ¡avanje KNewsTicker-a +Comment[sv]=Gränssnitt för anpassning av nyhetsövervakaren +Comment[ta]=செயà¯à®¤à®¿ அறிவிபà¯à®ªà®¤à®±à¯à®•à®¾à®© à®®à¯à®©à¯ அமைபà¯à®ªà¯ +Comment[tg]=Муқоваи графикӣ барои танзими KДидабони Ðхборот +Comment[th]=ฟรอนต์เอนด์สำหรับปรับà¹à¸•à¹ˆà¸‡à¸•à¸±à¹‹à¸§à¸‚่าว - K +Comment[tr]=KDE Haber Gözlemcisi yapılandırması için önyüz +Comment[uk]=Зовнішній Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ KNewsTicker +Comment[ven]=U iswa phanda uitela Mavhekanyele a musengulusi wa mafhungo a K +Comment[xh]=Umphambili wesiphelo soqwalaselo se KNewsTicker +Comment[zh_CN]=KNewsTicker é…置的å‰ç«¯ +Comment[zh_HK]=設定 KNewsTicker çš„å‰ç«¯ç¨‹å¼ +Comment[zh_TW]=設定 KNewsTicker çš„å‰ç«¯ +Comment[zu]=Isiqalo sesiphelo sezindaba zesiphelo senhlanganiso lomlekeleli wezindaba ze K +MimeType=text/rdf;text/rss +Exec=knewstickerstub -a %u +Icon=knode +NoDisplay=true +Type=Application +Terminal=false +InitialPreference=8 +Actions=Add; +X-KDE-StartupNotify=false + +[Desktop Action Add] +Exec=knewstickerstub -a %u +Name=Use with KNewsTicker +Name[af]=Gebruik met K-nuustikker +Name[ar]=استخدمه مع مجدد الأخبار +Name[az]=KNewsTicker ilÉ™ birgÉ™ iÅŸlÉ™din +Name[be]=ВыкарыÑтаць з KNewsTicker +Name[bg]=Използване Ñ Ñ‡ÐµÑ‚ÐµÑ†Ð° на новини +Name[bn]=কে-নিউজ-টিকারের সঙà§à¦—ে বà§à¦¯à¦¬à¦¹à¦¾à¦° +Name[br]=Implijit gant KNewsTicker +Name[bs]=Koristite sa KNewsTicker-om +Name[ca]=Empra amb el KNewsTicker +Name[cs]=Použít s KNewsTickerem +Name[cy]=Defnyddio efo KTicerNewyddion +Name[da]=Brug med KNewsTicker +Name[de]=Zur Benutzung mit KNewsticker +Name[el]=ΧÏήση με το KNewsTicker +Name[eo]=Uzu kun Novaĵprezentilo +Name[es]=Usar con KNewsTicker +Name[et]=Kasutamine KNewsTickeriga +Name[eu]=Erabili KNewsTicker-ekin +Name[fa]=استÙاده با KNewsTicker +Name[fi]=Käytä KNewsTicker-ohjelman kanssa +Name[fr]=Utiliser avec KNewsTicker +Name[gl]=Usar con KNewsTicker +Name[he]=השתמש ב- KNewsTicker +Name[hi]=के-नà¥à¤¯à¥‚ज टिकर के साथ इसà¥à¤¤à¥‡à¤®à¤¾à¤² करें +Name[hr]=Koristi sa KNewsTicker-om +Name[hu]=A KNewsTicker egyik alkotórésze +Name[id]=Gunakan dengan KNewsTicker +Name[is]=Notist með 'Fréttastrimli' +Name[it]=Utilizza con KNewsTicker +Name[ja]=ニュースティッカーを使用 +Name[ka]=KNewsTicker გáƒáƒ›áƒáƒ§áƒ”ნებრ+Name[kk]=KNewsTicker-мен бірге қолданылады +Name[km]=ប្រើ​ជា​មួយ KNewsTicker +Name[ko]=Kìƒˆì†Œì‹ í‘œì‹œê¸°ì™€ ê°™ì´ ì”€ +Name[lt]=Naudojama su KNewsTicker +Name[lv]=Lieto ar KZiņuTikkeru +Name[mk]=КориÑти Ñо KNewsTicker +Name[mn]=KNewsTicker Ñ‚Ñй Ñ…ÑÑ€ÑглÑÑ… +Name[ms]=Digunakan bersama KNewsTicker +Name[mt]=Uża ma' KNewsTicker +Name[nb]=Bruk med KNewsTicker +Name[nds]=Bruuk dat mit KNewsTicker +Name[ne]=केडीई नà¥à¤¯à¥‚ज टिकरसà¤à¤— पà¥à¤°à¤¯à¥‹à¤— गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Name[nl]=Gebruik met KNewsTicker +Name[nn]=Bruk med KNewsTicker +Name[nso]=Somisa le Seswai sa KDitaba +Name[pl]=Użyj za pomocÄ… KNewsTickera +Name[pt]=Usar com as Notícias +Name[pt_BR]=Usar com KNewsTicker +Name[ro]=Utilizează cu Åžtiri Internet +Name[ru]=ИÑпользуетÑÑ Ñ KNewsTicker +Name[se]=Geavat ovttas KNewsTicker:in +Name[sk]=PoužiÅ¥ s KNewsTicker +Name[sl]=Uporabite s KNewsTicker +Name[sr]=КориÑти Ñа KNewsTicker-ом +Name[sr@Latn]=Koristi sa KNewsTicker-om +Name[sv]=Använd med nyhetsövervakaren +Name[ta]=கேசெயà¯à®¤à®¿ அறிவிபà¯à®ªà®¾à®©à¯ +Name[tg]=Бо KДидабони Ðхборот иÑтифода мегардад +Name[th]=ใช้à¸à¸±à¸šà¸•à¸±à¹‹à¸§à¸‚่าว - K +Name[tr]=KDE Haber Gözlemcisi ile birlikte kullanın +Name[uk]=Ð”Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ Ð· KNewsTicker +Name[ven]=Shumisani na Musengulusi wa mafhungo +Name[xh]=Yisebenzise ne KNewsTicker +Name[zh_CN]=å’Œ KNewsTicker 一起使用 +Name[zh_HK]=å’Œ KNewsTicker 一起使用 +Name[zh_TW]=å’Œ KNewsTicker 一起使用 +Name[zu]=Yisebenzise nomlungiseleli wezindaba ze K +X-KDE-StartupNotify=false diff --git a/knewsticker/knt-0.1-0.2.pl b/knewsticker/knt-0.1-0.2.pl new file mode 100755 index 00000000..2ca9e30c --- /dev/null +++ b/knewsticker/knt-0.1-0.2.pl @@ -0,0 +1,104 @@ +#!/usr/bin/perl +# convert which file to which file? +# I did nto bother to build in any checks. so that file better exists and it is readable and the data will +# be written to a writeable directory. Since this is an old config file, it is safe to assume, that is the +# case. + +#$file = $ARGV[0]; + +# Take your hands off the rest unless you darn well knwo what you are doing +################################################################################## +################################################################################## + +open(INFO, $file); + my ($section, %data); + +#read in all the data, split it up into hashes. Thanks again to malte for much input +while (<>) { + if (/\[(.*)\]/) { + $sections{$section} = {%data} if $section; + $section = $1; + undef %data; + next; + } +$data{$1} = $2 if /^([^=]*)=(.*)$/; +} + +$sections{$section} = {%data} if $section; + +# do the data writing magic +#first of all be check how many old news souerces we have + +$sources = $sections{'General'}->{'News sources'}; +#gather all news sources into a very pretty string before we write the global section +#also give some feedback to the user +for my $i (0..($sources-1)) { + $all .= "," .$sections{"News source #$i"}->{'Name'}; + $all =~s/^,//; +} + +# write the main section +print "[KNewsTicker]\n"; + +while (($key,$dat) = each(%{$sections{'General'}})) { + if ($key ne "News sources") { + if ($key eq "Interval") { + $key="Update interval"; + } + if ($key eq "Scroll most recent only") { + $key="Scroll most recent headlines only"; + } + print "$key=$dat\n"; + } else { + print "News sources=".$all."\n"; + } + +} + +# next merge the old scrolling section into the KNewticker Section +while (($key,$dat) = each(%{$sections{'Scrolling'}})) { + if ($key eq "Background") { + $key="Background color"; + + } + if ($key eq "Foreground") { + $key="Foreground color"; + } + if ($key eq "Highlighted") { + $key="Highlighted color"; + } + if ($key eq "Underline highlighted") { + $key="Underline highlighted headlines"; + } + if (($key eq "Direction") && ($dat eq "Left")) { + $key ="Scrolling direction"; + $dat = "0"; + +} +if (($key eq "Direction") && ($dat ne "Left")) { + + $key ="Scrolling direction"; + +} +if ($key eq "Speed") { + $key="Scrolling speed"; +} + + + + print "$key=$dat\n"; +} + +# next we write the news sources, making sure we have the correct headers +for my $i (0..($sources-1)) { + +print "\n[" .$sections{"News source #$i"}->{'Name'} ."]\n"; + while (($key,$dat) = each(%{$sections{"News source #$i"}})) { + if ($key ne "Address") { + print "$key=$dat\n"; + } else { + print "Source file=".$dat."\n"; + } + } +} + diff --git a/knewsticker/kntsrcfilepropsdlg/Makefile.am b/knewsticker/kntsrcfilepropsdlg/Makefile.am new file mode 100644 index 00000000..9782b90c --- /dev/null +++ b/knewsticker/kntsrcfilepropsdlg/Makefile.am @@ -0,0 +1,12 @@ +INCLUDES = -I$(top_srcdir)/knewsticker/common -I$(top_srcdir)/librss $(all_includes) + +kde_module_LTLIBRARIES = libkntsrcfilepropsdlg.la + +libkntsrcfilepropsdlg_la_SOURCES = kntsrcfilepropsdlg.cpp \ + kntsrcfilepropsdlgwidget.ui +libkntsrcfilepropsdlg_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) +libkntsrcfilepropsdlg_la_LIBADD = $(LIB_KIO) ../common/libknewstickercommon.la ../../librss/librss.la +libkntsrcfilepropsdlg_la_METASOURCES = AUTO + +service_DATA = kntsrcfilepropsdlg.desktop +servicedir = $(kde_servicesdir) diff --git a/knewsticker/kntsrcfilepropsdlg/kntsrcfilepropsdlg.cpp b/knewsticker/kntsrcfilepropsdlg/kntsrcfilepropsdlg.cpp new file mode 100644 index 00000000..93bc71cc --- /dev/null +++ b/knewsticker/kntsrcfilepropsdlg/kntsrcfilepropsdlg.cpp @@ -0,0 +1,130 @@ +/* + * kntsrcfilepropsdlg.cpp + * + * Copyright (c) 2001, 2002, 2003 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include "kntsrcfilepropsdlg.h" +#include "kntsrcfilepropsdlgwidget.h" +#include "xmlnewsaccess.h" +#include "newsiconmgr.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace RSS; + +class ArticleListBoxItem : public QListBoxText +{ + public: + ArticleListBoxItem( QListBox *listbox, const Article &article ); + + const Article &article() const { return m_article; } + + private: + Article m_article; +}; + +ArticleListBoxItem::ArticleListBoxItem( QListBox *listbox, const Article &article ) + : QListBoxText( listbox ), + m_article( article ) +{ + setText( article.title() ); +} + +KntSrcFilePropsDlg::KntSrcFilePropsDlg(KPropertiesDialog *props) + : KPropsDlgPlugin(props) +{ + m_child = new KntSrcFilePropsDlgWidget(properties->addVBoxPage(i18n("News Resource"))); + connect(m_child->urlName, SIGNAL(leftClickedURL(const QString &)), + SLOT(slotOpenURL(const QString &))); + connect(m_child->lbArticles, SIGNAL(executed(QListBoxItem *)), + SLOT(slotClickedArticle(QListBoxItem *))); + + Loader *loader = Loader::create(); + connect(loader, SIGNAL(loadingComplete(Loader *, Document, Status)), + SLOT(slotConstructUI(Loader *, Document, Status))); + loader->loadFrom(props->item()->url(), new FileRetriever); + + connect(NewsIconMgr::self(), SIGNAL(gotIcon(const KURL &, const QPixmap &)), + SLOT(slotGotIcon(const KURL &, const QPixmap &))); + + m_child->show(); +} + +void KntSrcFilePropsDlg::slotConstructUI(Loader *, Document doc, Status status) +{ + if (status != RSS::Success) + return; + + KURL iconURL = doc.link(); + iconURL.setEncodedPathAndQuery(QString::fromLatin1("/favicon.ico")); + NewsIconMgr::self()->getIcon(iconURL); + + m_child->urlName->setText(doc.title()); + m_child->urlName->setURL(doc.link().url()); + + m_child->mleDescription->setText(doc.description()); + + Article::List::ConstIterator it = doc.articles().begin(); + Article::List::ConstIterator end = doc.articles().end(); + for (; it != end; ++it) + new ArticleListBoxItem(m_child->lbArticles, *it); +} + +void KntSrcFilePropsDlg::slotOpenURL(const QString &url) +{ + kapp->invokeBrowser(url); +} + +void KntSrcFilePropsDlg::slotGotIcon(const KURL &, const QPixmap &pixmap) +{ + m_child->pixmapIcon->setPixmap(pixmap); +} + +void KntSrcFilePropsDlg::slotClickedArticle(QListBoxItem *item) +{ + ArticleListBoxItem *articleItem = static_cast(item); + slotOpenURL(articleItem->article().link().url()); +} + +QObject *KntSrcFilePropsFactory::createObject(QObject *parent, const char *, + const char *classname, const QStringList &) +{ + if (QString::fromLatin1(classname) == "KPropsDlgPlugin") + { + if (!parent->inherits("KPropertiesDialog")) + return 0L; + + QObject *obj = new KntSrcFilePropsDlg(static_cast(parent)); + return obj; + } + return 0L; +} + +extern "C" +{ + KDE_EXPORT void *init_libkntsrcfilepropsdlg() + { + KGlobal::locale()->insertCatalogue( "knewsticker" ); + return new KntSrcFilePropsFactory(); + } +} + +#include "kntsrcfilepropsdlg.moc" diff --git a/knewsticker/kntsrcfilepropsdlg/kntsrcfilepropsdlg.desktop b/knewsticker/kntsrcfilepropsdlg/kntsrcfilepropsdlg.desktop new file mode 100644 index 00000000..6902d9b3 --- /dev/null +++ b/knewsticker/kntsrcfilepropsdlg/kntsrcfilepropsdlg.desktop @@ -0,0 +1,69 @@ +[Desktop Entry] +Type=Service +Name=KNewsticker Source File Properties Page +Name[af]=K-nuustikker Bron Lêer Eienskappe Bladsy +Name[ar]=صÙحة خصائص مل٠مصدر مجدد الأخبار +Name[az]=KNewsticker MÉ™nbÉ™ Faylı XüsusiyyÉ™tlÉ™ri SÉ™hifÉ™si +Name[bg]=ÐаÑтройване на файла източник на KNewsticker +Name[bn]=কে-নিউজ-টিকার উতà§â€à¦¸ ফাইল বৈশিষà§à¦Ÿà§à¦¯à¦¾à¦¬à¦²à§€ পাতা +Name[bs]=KNewsticker stranica sa osobinama izvorne datoteke +Name[ca]=Pàgina de propietats dels fitxers font del KNewsticker +Name[cs]=Popis vlastností zdrojového souboru programu KNewsticker +Name[cy]=Tudalen Priodweddau Ffeil Tarddiad KTicerNewyddion +Name[da]=KNewsTickers side med egenskaber for kildefil +Name[de]=KNewsticker: Einstellungsseite für Quelldateien +Name[el]=Σελίδα ιδιοτήτων αÏχείου πηγής KNewsticker +Name[eo]=EcopaÄo por Novaĵprezentilaj fontdosieroj +Name[es]=Página de propiedades de archivos fuente de KNewsticker +Name[et]=KNewstickeri lähtefaili omaduste dialoog +Name[eu]=KNewsticker iturburu fitxategiaren propietateen orria +Name[fa]=صÙØ­Û€ ویژگیهای پروندۀ مبدأ KNewsticker +Name[fi]=KNewsticker lähdetiedostojen asetussivu +Name[fr]=Page des propriétés du fichier source de KNewsTicker +Name[gl]=Páxina de propiedades dos arquivos fonte de KNewsTicker +Name[he]=דף מ×פייני קובץ מקור של KNewsTicker +Name[hi]=के-नà¥à¤¯à¥‚ज टिकर सà¥à¤°à¥‹à¤¤ फ़ाइल गà¥à¤£ पृषà¥à¤  +Name[hr]=Stranica sa postavkama KNewsTicker izvorne datoteke +Name[hu]=A KNewsTicker forrásfájlok tulajdonságlapja +Name[is]=Stillisíða fyrir frumskrár Fréttastrimils +Name[it]=Pagina proprietà file sorgente di notizie per KNewsticker +Name[ja]=KNewsticker ソースファイルプロパティページ +Name[ka]=KNewsticker წყáƒáƒ áƒáƒ¡ ფáƒáƒ˜áƒšáƒ˜áƒ¡ თვისებáƒáƒ—რგვერდი +Name[kk]=KNewsticker файлының қаÑиеттер беті +Name[km]=ទំពáŸážšâ€‹áž›áž€áŸ’ážážŽáŸˆâ€‹ážŸáž˜áŸ’áž”ážáŸ’ážáž·â€‹áž¯áž€ážŸáž¶ážšâ€‹áž”្រភព KNewsticker +Name[lt]=KNewsticker pirminio teksto bylos parinkÄių puslapis +Name[lv]=KZiņutikkera Avota Faila ĪpaÅ¡Ä«bu Lapa +Name[mk]=Страница за ÑвојÑтвата на изворната датотека на KNewsticker +Name[mn]=KNewsticker Ñурвалж файлын Тодорхойлолт Ñ…ÑƒÑƒÐ´Ð°Ñ +Name[ms]=Laman Ciri-ciri Fail Sumber KNewsticker +Name[mt]=PaÄ¡na ta' Propjetajiet Fajl ta' Sors KNewsticker +Name[nb]=Egenskaper for kildefiler til KNewsticker +Name[nds]=KNewsTicker Borndateien-Egenschappensiet +Name[ne]=केडीई नà¥à¤¯à¥‚ज टिकर सà¥à¤°à¥‹à¤¤ फाइल विशेषता पृषà¥à¤  +Name[nl]=KNewsticker Bronbestand Eigenschappenpagina +Name[nn]=Eigenskapsside for KNewsticker-kjeldefil +Name[nso]=Letlakala la Dithoto tsa Faele ya Mothopo wa seswai sa KDitaba +Name[pl]=Strona wÅ‚aÅ›ciwoÅ›ci pliku źródÅ‚owego KNewsTickera +Name[pt]=Página de Propriedades do Ficheiro da Fonte do KNewsticker +Name[pt_BR]=Página de Propriedades do Arquivo Fonte do KNewsTicher +Name[ro]=Pagină de proprietăţi pentru fiÅŸier sursă KNewsticker +Name[ru]=Страница ÑвойÑтв Ñведений о файле KNewsticker +Name[se]=KNewsTicker-gáldofiilla ieÅ¡vuohtasiidu +Name[sk]=Popis vlastností konfiguraÄného súboru pre KNewsticker +Name[sl]=Stran z lastnostmi za izvorno datoteko KNewsTicker +Name[sr]=KNewsticker Ñтрана за подешавање ÑвојÑтава изворних фајлова +Name[sr@Latn]=KNewsticker strana za podeÅ¡avanje svojstava izvornih fajlova +Name[sv]=Nyhetsövervakarens sida för källfilsegenskaper +Name[ta]=கேசெயà¯à®¤à®¿ அறிவிபà¯à®ªà®¾à®©à®¿à®©à¯ மூல கோபà¯à®ªà®¿à®©à¯ பணà¯à®ªà¯à®•à®³à¯ˆ அறியà¯à®®à¯ பகà¯à®•à®®à¯. +Name[tg]=Саҳофаи ХуÑуÑиÑтҳои Файли Сарчашмавии KДидабони Ðхборот +Name[th]=หน้าคุณสมบัติของà¹à¸Ÿà¹‰à¸¡à¸•à¹‰à¸™à¸‰à¸šà¸±à¸šà¸•à¸±à¹‹à¸§à¸‚่าว - K +Name[tr]=KDE Haber Gözlemcisi Kaynak Dosyası Özellikler Sayfası +Name[uk]=Сторінка влаÑтивоÑтей вихідного файла KNewsticker +Name[ven]=Siatari la tshishumiswa tshavhubvo ha faela la tshauthika mafhungo a K +Name[xh]=Iphepha Leempahla Lemvelaphi Yefayile ye KNewsticker +Name[zh_CN]=KNewsticker æ¥æºæ–‡ä»¶å±žæ€§é¡µ +Name[zh_HK]=KNewsticker 來æºæª”æ¡ˆå±¬æ€§é  +Name[zh_TW]=KNewsticker 來æºæª”æ¡ˆå±¬æ€§é  +Name[zu]=Umlekeleli wezindaba ze K wephepha lefayela Yemvelaphi Yempahla +X-KDE-Library=libkntsrcfilepropsdlg +ServiceTypes=KPropsDlg/Plugin,text/rdf,text/rss diff --git a/knewsticker/kntsrcfilepropsdlg/kntsrcfilepropsdlg.h b/knewsticker/kntsrcfilepropsdlg/kntsrcfilepropsdlg.h new file mode 100644 index 00000000..8fe1be58 --- /dev/null +++ b/knewsticker/kntsrcfilepropsdlg/kntsrcfilepropsdlg.h @@ -0,0 +1,57 @@ +/* + * kntsrcfilepropsdlg.h + * + * Copyright (c) 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef KNTSRCFILEPROPSDLG_H +#define KNTSRCFILEPROPSDLG_H + +#include + +#include +#include + +class KntSrcFilePropsDlgWidget; +class QListBoxItem; + +namespace RSS { + class Loader; + class Document; +} + +using RSS::Loader; +using RSS::Document; +using RSS::Status; + +class KntSrcFilePropsFactory : public KLibFactory +{ + Q_OBJECT + + public: + virtual QObject *createObject(QObject * = 0, const char * = 0, + const char * = "QObject", const QStringList & = QStringList()); +}; + +class KntSrcFilePropsDlg : public KPropsDlgPlugin +{ + Q_OBJECT + + public: + KntSrcFilePropsDlg(KPropertiesDialog *); + + protected slots: + void slotOpenURL(const QString &); + void slotConstructUI(Loader *loader, Document doc, Status status); + void slotGotIcon(const KURL &, const QPixmap &); + void slotClickedArticle(QListBoxItem *); + + private: + KntSrcFilePropsDlgWidget *m_child; +}; + +#endif // KNTSRCFILEPROPSDLG_H diff --git a/knewsticker/kntsrcfilepropsdlg/kntsrcfilepropsdlgwidget.ui b/knewsticker/kntsrcfilepropsdlg/kntsrcfilepropsdlgwidget.ui new file mode 100644 index 00000000..b8d1827e --- /dev/null +++ b/knewsticker/kntsrcfilepropsdlg/kntsrcfilepropsdlgwidget.ui @@ -0,0 +1,215 @@ + +KntSrcFilePropsDlgWidget +The widget to be used for the RDF/RSS file properties dialog plugin. +Frerich Raabe <raabe@kde.org> + + + KntSrcFilePropsDlgWidget + + + + 0 + 0 + 311 + 274 + + + + + + + + + unnamed + + + 4 + + + 4 + + + + Spacer5 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + pixmapIcon + + + + 0 + 0 + 0 + 0 + + + + image0 + + + Icon of this news site + + + Here you can see the icon of this news site. + + + + + urlName + + + heise online news + + + http://www.heise.de/newsticker/ + + + + + lDescription + + + Description: + + + Brief description of the news site + + + Here you can see a brief description about the news site and its contents. + + + + + lName + + + Name: + + + leName + + + Name of the news site + + + This is the name of the news site. + + + + + mleDescription + + + + 7 + 4 + 0 + 0 + + + + WidgetWidth + + + true + + + Brief description of the news site + + + Here you can see a brief description about the news site and its contents. + + + + + Spacer_2 + + + Vertical + + + Maximum + + + + 20 + 63 + + + + + + Line1 + + + 5 + + + + + lArticles + + + Available articles: + + + Articles contained within this source file + + + This list shows the headlines and links to the corresponding complete articles which have been stored in the source file whose properties you are watching. + + + + + lbArticles + + + + 7 + 7 + 0 + 0 + + + + Articles contained within this source file + + + This list shows the headlines and links to the corresponding complete articles which have been stored in the source file whose properties you are watching.<p>You can open the corresponding full article for each headline by, depending on the global KDE settings, clicking or double-clicking on a headline + + + + + + + + + 89504e470d0a1a0a0000000d49484452000000100000001008060000001ff3ff610000021c49444154388d8592316813511cc67f910c1770b8408704742838248260c63ad943974041533a84d0a1440a9ad6418a83dc22a853b62693c40c25e9503c07e93908ed601b3b354be90916ae43e10e2c5c06e1fed007714873b56daadff28ef7eefbf17dfff762db9d6dfe567db9dee73faa2c5662c3eff8c5c3dcdd1ca56209518216d7a2d5d9dba5b1d2c6b86f505faef787907300f393d91f9f4a621e7c8fccee5761e3dd3afa7503fbcb26e5f97294b4b25889c52f9aad9716e96c1614783f5d8c171318af0c365e6f401c76bbbb1164e1d9423f020ccc36b93b93983326fa988e73e462ab0fb8471ddc4397542a457ba54da3d9e0d0393c3f03e79b873e9e227f2b4f3693257d338d9be9e2adb9c82f8dfd837d961697f08e5ddc1ffb746e7469af5a5c1b02aa93d5583223f8be0f0ad64f5ad83b36c435509020017148eb29b299dbdccbe500ce0000b5871f639eefd149d9585b16aee7e2343d6a73358c0713a04242d543d334e4b77f193094bd639f99e76b4ccfe46935eb84aa87bdd64224403b2d3f12303457e7aa4c3fca8302949038bddae4581a11b91ac0964e32077a62d03f941ea562191414a60aa0403bfdf5d24b04683dad22aa070cccd66a83c2e30228419420be8f9c045727308fdf527eff865069241494662b589f6d44044d0112460946cf602da0dbddc4f75d421510f65c0ac53220880451922b2b548b26cc9a8804387bcea08eea8204034828ff9ec193e74ba3b647ea0f69200af223e8435e0000000049454e44ae426082 + + + + klistbox.h + kurllabel.h + kseparator.h + + + + kurllabel.h + kseparator.h + klistbox.h + + diff --git a/knewsticker/newsscroller.cpp b/knewsticker/newsscroller.cpp new file mode 100644 index 00000000..9d11f19e --- /dev/null +++ b/knewsticker/newsscroller.cpp @@ -0,0 +1,596 @@ +/* + * newsscroller.cpp + * + * Copyright (c) 2000, 2001 Frerich Raabe + * Copyright (c) 2001 Malte Starostik + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "configaccess.h" +#include "newsscroller.h" +#include "newsengine.h" +#include + +class Headline +{ +public: + Headline(NewsScroller *scroller, const Article::Ptr &article) + : m_scroller(scroller), + m_article(article), + m_normal(0), + m_highlighted(0) + { + }; + + virtual ~Headline() + { + reset(); + } + + Article::Ptr article() const { return m_article; } + + int width() { return pixmap()->width(); } + int height() { return pixmap()->height(); } + + QPixmap *pixmap(bool highlighted = false, bool underlineHighlighted = true) + { + QPixmap *result = highlighted ? m_highlighted : m_normal; + if (!result) { + const QFontMetrics &metrics = m_scroller->fontMetrics(); + + int w, h; + if (m_scroller->m_cfg->showIcons()) { + w = m_article->newsSource()->icon().width() + 4 + metrics.width(m_article->headline()); + h = QMAX(metrics.height(), m_article->newsSource()->icon().height()); + } else { + w = metrics.width(m_article->headline()); + h = metrics.height(); + } + + if (ConfigAccess::rotated(static_cast(m_scroller->m_cfg->scrollingDirection()))) + result = new QPixmap(h, w); + else + result = new QPixmap(w, h); + + result->fill(m_scroller->m_cfg->backgroundColor()); + QPainter p(result); + QFont f = m_scroller->font(); + + if (highlighted) + f.setUnderline(underlineHighlighted); + + p.setFont(f); + p.setPen(highlighted ? m_scroller->m_cfg->highlightedColor() : m_scroller->m_cfg->foregroundColor()); + + if (ConfigAccess::rotated(static_cast(m_scroller->m_cfg->scrollingDirection()))) { + if (m_scroller->m_cfg->scrollingDirection() == ConfigAccess::UpRotated) { + // Note that rotation also + // changes the coordinate space + // + p.rotate(90.0); + if (m_scroller->m_cfg->showIcons()) { + p.drawPixmap(0, -m_article->newsSource()->icon().height(), m_article->newsSource()->icon()); + p.drawText(m_article->newsSource()->icon().width() + 4, -metrics.descent(), m_article->headline()); + } else + p.drawText(0, -metrics.descent(), m_article->headline()); + } else { + p.rotate(-90.0); + if (m_scroller->m_cfg->showIcons()) { + p.drawPixmap(-w, h - m_article->newsSource()->icon().height(), m_article->newsSource()->icon()); + p.drawText(-w + m_article->newsSource()->icon().width() + 4, h - metrics.descent(), m_article->headline()); + } else + p.drawText(-w, h - metrics.descent(), m_article->headline()); + } + } else { + if (m_scroller->m_cfg->showIcons()) { + p.drawPixmap(0, + (result->height() - m_article->newsSource()->icon().height()) / 2, + m_article->newsSource()->icon()); + p.drawText(m_article->newsSource()->icon().width() + 4, result->height() - metrics.descent(), m_article->headline()); + } else + p.drawText(0, result->height() - metrics.descent(), m_article->headline()); + } + + if (highlighted) + m_highlighted = result; + else + m_normal = result; + } + return result; + } + + void reset() + { + delete m_normal; + m_normal = 0; + delete m_highlighted; + m_highlighted = 0; + } + +private: + NewsScroller *m_scroller; + Article::Ptr m_article; + QPixmap *m_normal; + QPixmap *m_highlighted; +}; + +NewsScroller::NewsScroller(QWidget *parent, ConfigAccess *cfg, const char *name) + : QFrame(parent, name, WNoAutoErase), + m_cfg(cfg), + m_scrollTimer(new QTimer(this)), + m_activeHeadline(0), + m_mouseDrag(false), + m_totalStepping(0.0) +{ + if (!kapp->dcopClient()->isAttached()) + kapp->dcopClient()->attach(); + + setFrameStyle(StyledPanel | Sunken); + + m_headlines.setAutoDelete(true); + + connect(m_scrollTimer, SIGNAL(timeout()), SLOT(slotTimeout())); + + setAcceptDrops(true); + + reset(); +} + +QSize NewsScroller::sizeHint() const +{ + return QSize(fontMetrics().width(QString::fromLatin1("X")) * 20, fontMetrics().height() * 2); +} + +QSizePolicy NewsScroller::sizePolicy() const +{ + return QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +void NewsScroller::clear() +{ + m_headlines.clear(); + reset(); +} + +void NewsScroller::dragEnterEvent(QDragEnterEvent* event) +{ + event->accept(QTextDrag::canDecode(event)); +} + +void NewsScroller::dropEvent(QDropEvent* event) +{ + QString newSourceUrl; + if ( QTextDrag::decode(event, newSourceUrl) ) { + // + // This is just for http://www.webreference.com/services/news/ + newSourceUrl = newSourceUrl.replace(QRegExp( + QString::fromLatin1("^view-source:http%3A//")), + QString::fromLatin1("http://")); + // + newSourceUrl = newSourceUrl.stripWhiteSpace(); + if (!isHeadline(newSourceUrl) && KMessageBox::questionYesNo(this, i18n("

    Do you really want to add '%1' to" + " the list of news sources?

    ") + .arg(newSourceUrl), QString::null, i18n("Add"), KStdGuiItem::cancel()) == KMessageBox::Yes) { + KConfig cfg(QString::fromLatin1("knewsticker_panelappletrc"), false, false); + ConfigAccess configFrontend(&cfg); + QStringList newsSources = configFrontend.newsSources(); + + QString name = i18n("Unknown"); + if (newsSources.contains(name)) + for (unsigned int i = 0; ; i++) + if (!newsSources.contains(i18n("Unknown %1").arg(i))) { + name = i18n("Unknown %1").arg(i); + break; + } + + newsSources += name; + configFrontend.setNewsSource(NewsSourceBase::Data(name, newSourceUrl)); + configFrontend.setNewsSources(newsSources); + + QByteArray data; + kapp->dcopClient()->send("knewsticker", "KNewsTicker", "reparseConfig()", data); + } + } +} + +bool NewsScroller::isHeadline(const QString &location) const +{ + for (Headline *h = m_headlines.first(); h; h = m_headlines.next()) + if (h->article()->address() == location) + return true; + + return false; +} + +void NewsScroller::addHeadline(Article::Ptr article) +{ + for (unsigned int i = 0; i < m_cfg->filters().count(); i++) + if (m_cfg->filter(i).matches(article)) + return; + + m_headlines.append(new Headline(this, article)); +} + +void NewsScroller::scroll(int distance, bool interpret_directions) +{ + unsigned int t_dir; + if ( interpret_directions ) t_dir = m_cfg->scrollingDirection(); + else t_dir = m_cfg->horizontalScrolling() ? ConfigAccess::Left : ConfigAccess::Up; + switch (t_dir) { + case ConfigAccess::Left: + m_offset -= distance; + if (m_offset <= - scrollWidth()) + m_offset = m_offset + scrollWidth() - m_separator.width(); + break; + case ConfigAccess::Right: + m_offset += distance; + if (m_offset >= contentsRect().width()) + m_offset = m_offset + m_separator.width() - scrollWidth(); + break; + case ConfigAccess::Up: + case ConfigAccess::UpRotated: + m_offset -= distance; + if (m_offset <= - scrollHeight()) + m_offset = m_offset + scrollHeight() - m_separator.height(); + break; + case ConfigAccess::Down: + case ConfigAccess::DownRotated: + m_offset += distance; + if (m_offset >= contentsRect().height()) + m_offset = m_offset + m_separator.height() - scrollHeight(); + } + + QPoint pt = mapFromGlobal(QCursor::pos()); + + if (contentsRect().contains(pt)) + updateActive(pt); + + update(); +} + +void NewsScroller::enterEvent(QEvent *) +{ + if (m_cfg->slowedScrolling() && m_cfg->scrollingSpeed() > 1) + m_scrollTimer->changeInterval(speedAsInterval(m_cfg->scrollingSpeed() / 2)); +} + +void NewsScroller::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == QMouseEvent::LeftButton || e->button() == QMouseEvent::MidButton) { + m_dragPos = e->pos(); + + if (m_activeHeadline) + m_tempHeadline = m_activeHeadline->article()->headline(); + } +} + +void NewsScroller::mouseReleaseEvent(QMouseEvent *e) +{ + if ((e->button() == QMouseEvent::LeftButton || e->button() == QMouseEvent::MidButton) + && m_activeHeadline && m_activeHeadline->article()->headline() == m_tempHeadline + && !m_mouseDrag) { + m_activeHeadline->article()->open(); + m_tempHeadline = QString::null; + } + + if (e->button() == QMouseEvent::RightButton) + emit(contextMenu()); + + if (m_mouseDrag) { + m_mouseDrag = false; + if (m_cfg->scrollingSpeed()) + m_scrollTimer->start(speedAsInterval(m_cfg->scrollingSpeed())); + } +} + +void NewsScroller::mouseMoveEvent(QMouseEvent *e) +{ + // Are we in a drag phase? + if (!m_mouseDrag) { + // If not, check whether we need to start a drag. + int dragDistance = 0; + if (m_cfg->horizontalScrolling()) + dragDistance = QABS(e->x() - m_dragPos.x()); + else + dragDistance = QABS(e->y() - m_dragPos.y()); + m_mouseDrag = (e->state() & QMouseEvent::LeftButton != 0) && + dragDistance >= KGlobal::config()->readNumEntry("StartDragDist", KApplication::startDragDistance()); + if (m_mouseDrag) // Stop the scroller if we just started a drag. + m_scrollTimer->stop(); + } else { + // If yes, move the scroller accordingly. + bool createDrag; + if (m_cfg->horizontalScrolling()) { + scroll(m_dragPos.x() - e->x(), false); + m_dragPos = e->pos(); + createDrag = e->y() < 0 || e->y() > height(); + } else { + scroll(m_dragPos.y() - e->y(), false); + m_dragPos = e->pos(); + createDrag = e->x() < 0 || e->x() > width(); + } + m_dragPos = e->pos(); + if (createDrag && m_activeHeadline) { + KURL::List url; + url.append(m_activeHeadline->article()->address()); + QDragObject *drag = new KURLDrag(url, this); + drag->setPixmap(m_activeHeadline->article()->newsSource()->icon()); + drag->drag(); + m_mouseDrag = false; + if (m_cfg->scrollingSpeed()) + m_scrollTimer->start(speedAsInterval(m_cfg->scrollingSpeed())); + } + } + + if (updateActive(e->pos())) + update(); +} + +void NewsScroller::wheelEvent(QWheelEvent *e) +{ + // ### This 11 - m_cfg->mouseWheelSpeed() could be eliminated by swapping + // the labels of the QSlider. :] + int distance = qRound(QABS(e->delta()) / (11 - m_cfg->mouseWheelSpeed())); + int direction = e->delta() > 0 ? -1 : 1; + + for (int i = 0; i < distance; i++) + scroll(direction); + + QFrame::wheelEvent(e); +} + +void NewsScroller::leaveEvent(QEvent *) +{ + if (m_cfg->slowedScrolling() && m_cfg->scrollingSpeed() > 1) + m_scrollTimer->changeInterval(speedAsInterval(m_cfg->scrollingSpeed())); + + if (m_activeHeadline) { + m_activeHeadline = 0; + update(); + } +} + +void NewsScroller::drawContents(QPainter *p) +{ + if (!scrollWidth() || // No news and no "No News Available": uninitialized + m_headlines.isEmpty()) // Happens when we're currently fetching new news + return; + + QPixmap buffer(contentsRect().width(), contentsRect().height()); + buffer.fill(m_cfg->backgroundColor()); + int pos = m_offset; + + // Paste in all the separator bitmaps (" +++ ") + if (horizontal()) { + while (pos > 0) + pos -= scrollWidth() - (m_headlines.isEmpty() ? m_separator.width() : 0); + do { + bitBlt(&buffer, pos, (contentsRect().height() - m_separator.height()) / 2, &m_separator); + pos += m_separator.width(); + } while (m_headlines.isEmpty() && pos < contentsRect().width()); + } else { + while (pos > 0) + pos -= scrollHeight() - (m_headlines.isEmpty() ? 0 : m_separator.height()); + do { + bitBlt(&buffer, (contentsRect().width() - m_separator.width()) / 2, pos, &m_separator); + pos += m_separator.height(); + } while (m_headlines.isEmpty() && pos < contentsRect().height()); + } + + // Now do the headlines themselves + do { + QPtrListIterator it(m_headlines); + for(; *it; ++it) { + if (horizontal()) { + if (pos + (*it)->width() >= 0) + bitBlt(&buffer, pos, (contentsRect().height() - (*it)->height()) / 2, (*it)->pixmap(*it == m_activeHeadline, m_cfg->underlineHighlighted())); + pos += (*it)->width(); + + if (pos + m_separator.width() >= 0) + bitBlt(&buffer, pos, (contentsRect().height() - m_separator.height()) / 2, &m_separator); + pos += m_separator.width(); + + if (pos >= contentsRect().width()) + break; + } else { + if (pos + (*it)->height() >= 0) + bitBlt(&buffer, (contentsRect().width() - (*it)->width()) / 2, pos, (*it)->pixmap(*it == m_activeHeadline, m_cfg->underlineHighlighted())); + pos += (*it)->height(); + + if (pos + m_separator.height() >= 0) + bitBlt(&buffer, (contentsRect().width() - m_separator.width()) / 2, pos, &m_separator); + pos += m_separator.height(); + + if (pos > contentsRect().height()) + break; + } + } + + /* + * Break out if we reached the bottom of the window before the end of the + * list of headlines. + */ + if (*it) + break; + } + while ((m_cfg->horizontalScrolling() && pos < contentsRect().width()) || pos < contentsRect().height()); + + p->drawPixmap(0, 0, buffer); +} + +void NewsScroller::reset(bool bSeparatorOnly) +{ + setFont(m_cfg->font()); + + m_scrollTimer->stop(); + if (m_cfg->scrollingSpeed()) + m_scrollTimer->start(speedAsInterval(m_cfg->scrollingSpeed())); + + QString sep = m_headlines.isEmpty() ? i18n(" +++ No News Available +++") : QString::fromLatin1(" +++ "); + + int w = fontMetrics().width(sep); + int h = fontMetrics().height(); + + if (rotated()) + m_separator.resize(h, w); + else + m_separator.resize(w, h); + + m_separator.fill(m_cfg->backgroundColor()); + + QPainter p(&m_separator); + p.setFont(font()); + p.setPen(m_cfg->foregroundColor()); + + if(rotated()) { + if (m_cfg->scrollingDirection() == ConfigAccess::UpRotated) { + p.rotate(90.0); + p.drawText(0, -fontMetrics().descent(),sep); + } else { + p.rotate(-90.0); + p.drawText(-w, h-fontMetrics().descent(),sep); + } + } else + p.drawText(0, m_separator.height() - fontMetrics().descent(), sep); + p.end(); + + if (!bSeparatorOnly) + for (QPtrListIterator it(m_headlines); *it; ++it) + (*it)->reset(); + + switch (m_cfg->scrollingDirection()) { + case ConfigAccess::Left: + m_offset = contentsRect().width(); + break; + case ConfigAccess::Right: + m_offset = - scrollWidth(); + break; + case ConfigAccess::Up: + case ConfigAccess::UpRotated: + m_offset = contentsRect().height(); + break; + case ConfigAccess::Down: + case ConfigAccess::DownRotated: + m_offset = - scrollHeight(); + } + + update(); +} + +int NewsScroller::scrollWidth() const +{ + int result = (m_headlines.count() + 1) * m_separator.width(); + + for (QPtrListIterator it(m_headlines); *it; ++it) + result += (*it)->width(); + + return result; +} + +int NewsScroller::scrollHeight() const +{ + int result = (m_headlines.count() + 1) * m_separator.height(); + + for (QPtrListIterator it(m_headlines); *it; ++it) + result += (*it)->height(); + + return result; +} + +bool NewsScroller::updateActive(const QPoint &pt) +{ + int pos = m_offset; + + Headline *headline = 0; + + if (!m_headlines.isEmpty()) { + while (pos > 0) + if (horizontal()) + pos -= scrollWidth() - m_separator.width(); + else + pos -= scrollHeight() - m_separator.height(); + + do { + QPtrListIterator it(m_headlines); + for (; (headline = *it); ++it) { + QRect rect; + if (horizontal()) { + pos += m_separator.width(); + rect.moveTopLeft(QPoint(pos, (contentsRect().height() - (*it)->height()) / 2)); + pos += (*it)->width(); + } else { + pos += m_separator.height(); + rect.moveTopLeft(QPoint((contentsRect().width() - (*it)->width()) / 2, pos)); + pos += (*it)->height(); + } + rect.setSize(QSize((*it)->width(), (*it)->height())); + + if (m_mouseDrag) + if (horizontal()) { + rect.setTop(0); + rect.setHeight(height()); + } else { + rect.setLeft(0); + rect.setWidth(width()); + } + + if (rect.contains(pt)) + break; + } + if (*it) + break; + } + while ((m_cfg->horizontalScrolling() && pos < contentsRect().width()) || pos < contentsRect().height()); + } + + if (m_activeHeadline == headline) + return false; + + if ((m_activeHeadline = headline)) + setCursor(KCursor::handCursor()); + else + unsetCursor(); + + return true; +} + +void NewsScroller::slotTimeout() +{ + m_totalStepping += m_stepping; + if ( m_totalStepping >= 1.0 ) { + const int distance = static_cast( m_totalStepping ); + m_totalStepping -= distance; + scroll( distance ); + } +} + +int NewsScroller::speedAsInterval( int speed ) +{ + Q_ASSERT( speed > 0 ); + + static const int MaxScreenUpdates = 25; + + if ( speed <= MaxScreenUpdates ) { + m_stepping = 1.0; + return 1000 / speed; + } + + m_stepping = speed / MaxScreenUpdates; + return 1000 / MaxScreenUpdates; +} + +#include "newsscroller.moc" diff --git a/knewsticker/newsscroller.h b/knewsticker/newsscroller.h new file mode 100644 index 00000000..efe0c3c2 --- /dev/null +++ b/knewsticker/newsscroller.h @@ -0,0 +1,102 @@ +/* + * newsscroller.h + * + * Copyright (c) 2000, 2001 Frerich Raabe + * Copyright (c) 2001 Malte Starostik + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef NEWSSCROLLER_H +#define NEWSSCROLLER_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "configaccess.h" +#include "newsengine.h" + +#include +#include +#include + +class QTimer; +class Headline; +template class QPtrList; +typedef QPtrList HeadlineList; + +class NewsScroller : public QFrame +{ + Q_OBJECT + + public: + NewsScroller(QWidget *, ConfigAccess *, const char * = 0); + + virtual QSize sizeHint() const; + virtual QSizePolicy sizePolicy() const; + + // Convenience stuff. Somehow ugly, no? + inline bool horizontal() const + { + return m_cfg->horizontal(static_cast(m_cfg->scrollingDirection())); + } + + inline bool vertical() const + { + return m_cfg->vertical(static_cast(m_cfg->scrollingDirection())); + } + + inline bool rotated() const + { + return m_cfg->rotated(static_cast(m_cfg->scrollingDirection())); + } + + public slots: + void clear(); + void addHeadline(Article::Ptr); + void reset(bool bSeparatorOnly = false); + + signals: + void contextMenu(); + + protected: + virtual void enterEvent(QEvent *); + virtual void mousePressEvent(QMouseEvent *); + virtual void mouseReleaseEvent(QMouseEvent *); + virtual void mouseMoveEvent(QMouseEvent *); + virtual void wheelEvent(QWheelEvent *); + virtual void leaveEvent(QEvent *); + virtual void drawContents(QPainter *); + virtual void dragEnterEvent( QDragEnterEvent *); + virtual void dropEvent(QDropEvent *); + + protected slots: + void scroll(int = 1, bool = true); + void slotTimeout(); + + private: + int scrollWidth() const; + int scrollHeight() const; + bool updateActive(const QPoint &); + bool isHeadline(const QString &) const; + int speedAsInterval( int speed ); + + private: + friend class Headline; + ConfigAccess *m_cfg; + QTimer *m_scrollTimer; + mutable HeadlineList m_headlines; + Headline *m_activeHeadline; + QPixmap m_separator; + int m_offset; + QPoint m_dragPos; + bool m_mouseDrag; + QString m_tempHeadline; + float m_totalStepping; + float m_stepping; +}; + +#endif // NEWSSCROLLER_H diff --git a/knewsticker/newssourcedlg.ui b/knewsticker/newssourcedlg.ui new file mode 100644 index 00000000..bc2690b2 --- /dev/null +++ b/knewsticker/newssourcedlg.ui @@ -0,0 +1,408 @@ + +NewsSourceDlg +The dialog to be used to edit another news source. +Frerich Raabe <raabe@kde.org> + + + NewsSourceDlg + + + + 0 + 0 + 438 + 201 + + + + + 5 + 4 + 0 + 0 + + + + Add News Source + + + image0 + + + + + + + + unnamed + + + 4 + + + 4 + + + + gbNewsSourceProperties + + + News Source Properties + + + + + + + + unnamed + + + 11 + + + 6 + + + + lName + + + &Name: + + + leName + + + Name of the news source + + + Here you can enter the name of the news source.<br>Note that you can also use the button at the bottom right labeled <i>Suggest</i> to let KNewsTicker fill this field automatically, after you have entered a source file below. + + + + + leName + + + Name of the news source + + + Here you can enter the name of the news source.<br>Note that you can also use the button at the bottom right labeled <i>Suggest</i> to let KNewsTicker fill this field automatically, after you have entered a source file below. + + + + + lSourceFile + + + Source &file: + + + urlSourceFile + + + The source file for this news source + + + Enter the path to the source file for the news source you want to add here. If you specified a source file here, you can use the button at the bottom right labeled <i>Suggest</i> to let KNewsTicker fill in the remaining values automatically. + + + + + lIcon + + + &Icon: + + + leIcon + + + Path to the icon for this news source + + + Here you can specify the path to an icon to be used for this news source. Icons make it easier to distinguish between multiple news sources as the headlines scroll by.<br>Note that you can also use the button at the bottom right labeled <i>Suggest</i> to let KNewsTicker fill this field automatically, after you have entered a source file above. + + + + + leIcon + + + Path to the icon for this news source + + + Here you can specify the path to an icon to be used for this news source. Icons make it easier to distinguish between multiple news sources as the headlines scroll by.<br>Note that you can also use the button at the bottom right labeled <i>Suggest</i> to let KNewsTicker fill this field automatically, after you have entered a source file above. + + + + + pixmapIcon + + + + 0 + 0 + 0 + 0 + + + + image1 + + + true + + + Icon to be used for this news source + + + This is what the currently configured icon for this news source looks like. To change this icon, use the input field at the left. + + + + + lCategory + + + Ca&tegory: + + + comboCategory + + + Into which category does this news source belong? + + + Here you can specify into which category this news source belongs. Arranging the news sources into categories makes it much easier to maintain large lists of news sources.<br>Note that you can also use the button at the bottom right labeled <i>Suggest</i> to let KNewsTicker fill this field automatically, after you have entered a source file above. + + + + + comboCategory + + + Into which category does this news source belong? + + + Here you can specify into which category this news source belongs. Arranging the news sources into categories makes it much easier to maintain large lists of news sources.<br>Note that you can also use the button at the bottom right labeled <i>Suggest</i> to let KNewsTicker fill this field automatically, after you have entered a source file above. + + + + + Spacer5 + + + Horizontal + + + Expanding + + + + + lMaxArticles + + + &Max. articles: + + + sbMaxArticles + + + Maximum number of articles + + + This option lets you define how many articles KNewsTicker should cache for this news source. This value will never be exceeded.<br>Note that you can also use the button at the bottom right labeled <i>Suggest</i> to let KNewsTicker fill this field automatically, after you have entered a source file above. + + + + + sbMaxArticles + + + 1000 + + + 10 + + + Maximum number of articles + + + This option lets you define how many articles KNewsTicker should cache for this news source. This value will never be exceeded.<br>Note that you can also use the button at the bottom right labeled <i>Suggest</i> to let KNewsTicker fill this field automatically, after you have entered a source file above. + + + + + cbProgram + + + The file is a &program + + + Is the specified source file a program? + + + Check this box to tell KNewsTicker that the file you specified in the above input field labeled <i>Source file</i> is a program and not a RDF or RSS file. KNewsTicker will then process the output (as received on <i>stdout</i>) of that program. + + + + + urlSourceFile + + + The source file for this news source + + + Enter the path to the source file for the news source you want to add here. If you specified a source file here, you can use the button at the bottom right labeled <i>Suggest</i> to let KNewsTicker fill in the remaining values automatically. + + + + + + + bCancel + + + &Cancel + + + Cancel this configuration + + + Press this button to close this dialog, discarding all entered information. + + + + + bSuggest + + + false + + + &Suggest + + + Suggest suitable values + + + Press this button to make KNewsTicker guess more or less reasonable values for some of the news properties (such as the name, icon or maximum number of articles).<br>Note that you have to supply a source file in order to use this function. + + + + + bOk + + + &OK + + + true + + + Acknowledge these values + + + Press this button to apply the values of this dialog and return to the previous configuration dialog. + + + + + spacerButtons + + + Horizontal + + + Expanding + + + + + + + 789c55d45b53db381407f0773e4586f3c6ec9cc6777b76f6a177480b85425be8ce3ec8924c4248805cb87467bffb4ae7fc6d688433f3437f1dc992e3577ba3f3e3c3d1deab9df5c66c667664a76635da73dbc5e2e9ef7ffefa7767372947e1af6e46e9ee1f3bbb279b911d1ddd2c7d04670134964f34ddc039bc14d7c1b5e44fa2933ab1899aa2d3713a4e24cfefe13c557f1637c18dd4bb82ad9a1fa2b324b55922fd703656732e6eb23ad3bc872deca2f324b3b9e6e7ea7c0c4fe01c7e2d3639d6c307bd7323fe155da443bfacb73005f6876d74990efe24366551ca789ac38d9a8f61079be82a2b1dd67fa6ae922a13bf13b75551b5e2b770a3260f3bf4c7c3a43aab0aecffd3603d9f37b0aba53e91ba4ed42ce75d87fa38af1676b8ff525d17b5ce770137f00fd8c1e7d14d5ebb26179faa9b04fe0217f0bed8065bf111dcc01f60077f8c3679e38c8e7f549b04eec4613cce7b0d3b78a63649293f0692e7a7cddbb4d5f18dd8198ff5add46da9e743dcbb75d27f0f7b352da26dd138e4efd506e74bb3c17a9e977081f3b1b0d1fda40dece195ba2d71bf696facbf80bd2dc45bb54dd5a476a1beceb756b7a9ee2fddc1dea86f7ba3fe77b53556ef7f1aed0adbefd757b54b9dce2ff59c1ffa0fd5ae74fafe59c21e4ea27d3978d1db97e26bb54fd5a4f6d6e87ed054ed52ac5f9e87ae7406e7d3c2fdfed4ea3083ba828dce4ff27bedbade3c5677a957dfc2fdfdfcecdde97abfc106be84c3475fc67d6362c32ddbe7ffc4a61976ece5eaf892a73ce32bd8f5199ef3352f78c9377ccb77bce2356fd8f236b47b7e40c687c463684f61fc15ffe2d7d2def05b7ec7ef9171a1c6237fe08fbccf073c79717de2cf439d433e0aed4bc84cc2bc4b5cc74127439daf21b11fda6918bb08ebd676c6dff860a8f33df49ef20f3e973acf99b3703f13d4d92271f1a2ce4f1e73c229eb3b2ce722f45e70195a5f27262aaeb941c6136b82087542820cd7d492edeb90a3f8f12113eba046487474f95bc6cbaec53a7d8d8ea634d30c5dd19ce67c2cdfb14e3524ae69a19970a6192de345377c40b774472b5ad386b674ff9c79f1744c38fbbdd1c3ee7f7feefc0f291dd297 + + + 789c7dcfcd6a84301007f0bb4f11f426256bd4b886a58fd0d263a1f43093c48fdd55a1dd1e4ae9bbd749a2220b8d82f9fd33c99843ca5e5f9e587a883e6f70eb35d31d7cb0d47c0dc3f7dbfbe34f148b8ad17b64227e8862ce347b9e464b739ce749e606113c8b5a02315958e5c4c6b3cc2ab7da0796c782683d655623d1044a5512a740a534511373550b2d899740e9d9110bb1f04a2c8512da10cf4499cf2739b6c40a008c2b1e56ba0b8ec41ad1dacadd7d1e09d08334e521036d10d158c4356b5a4380ced2d767fdd9ef828b5db344bb3ade5cb73a0e6ed39c6d759c239d0e83d97af801e3847c9fc130edfe6fedb9cf96debbbd18a25d0f7e9f01dc6549fbef79bfa7e80f2829874e + + + + + bSuggest + clicked() + NewsSourceDlg + slotSuggestClicked() + + + bCancel + clicked() + NewsSourceDlg + slotCancelClicked() + + + leName + textChanged(const QString&) + NewsSourceDlg + slotModified() + + + comboCategory + highlighted(const QString&) + NewsSourceDlg + slotModified() + + + sbMaxArticles + valueChanged(int) + NewsSourceDlg + slotModified() + + + leIcon + textChanged(const QString&) + NewsSourceDlg + slotModified() + + + bOk + clicked() + NewsSourceDlg + slotOkClicked() + + + urlSourceFile + textChanged(const QString &) + NewsSourceDlg + slotSourceFileChanged() + + + + leName + urlSourceFile + cbProgram + comboCategory + sbMaxArticles + leIcon + bOk + bSuggest + bCancel + + + kurlrequester.h + kcombobox.h + klineedit.h + knuminput.h + + + slotOkClicked() + slotCancelClicked() + slotModified() + slotSourceFileChanged() + slotSuggestClicked() + + + diff --git a/knewsticker/newssourcedlgimpl.cpp b/knewsticker/newssourcedlgimpl.cpp new file mode 100644 index 00000000..2cf39511 --- /dev/null +++ b/knewsticker/newssourcedlgimpl.cpp @@ -0,0 +1,240 @@ +/* + * newssourcedlgimpl.cpp + * + * Copyright (c) 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include "newssourcedlgimpl.h" +#include "xmlnewsaccess.h" +#include "configaccess.h" +#include "newsiconmgr.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +SuggestProgressDlg::SuggestProgressDlg(const KURL &url, QWidget *parent, const char *name) + : KDialogBase(parent, name, true, i18n("Downloading Data"), Cancel, Cancel), + m_gotSourceFile(false), + m_gotIcon(false) +{ + QVBox *mainWidget = makeVBoxMainWidget(); + + new QLabel(i18n("Please wait while KNewsTicker is downloading some " + "data necessary to suggest reasonable values.
    " + "
    " + "This will not take longer than one minute.
    " ), + mainWidget); + + m_progressBar = new QProgressBar(60, mainWidget); + m_progressBar->setPercentageVisible(false); + + m_timeoutTimer = new QTimer(this); + connect(m_timeoutTimer, SIGNAL(timeout()), this, SLOT(slotTimeoutTick())); + m_timeoutTimer->start(1000); + + m_xmlSrc = new XMLNewsSource; + connect(m_xmlSrc, SIGNAL(loadComplete(XMLNewsSource *, bool)), + this, SLOT(slotLoadComplete(XMLNewsSource *, bool))); + m_xmlSrc->loadFrom(url); + + connect(NewsIconMgr::self(), SIGNAL(gotIcon(const KURL &, const QPixmap &)), + this, SLOT(slotGotIcon(const KURL &, const QPixmap &))); + KURL u = url; + if (url.isLocalFile()) + u = QString::null; + else + u.setEncodedPathAndQuery(QString::fromLatin1("/favicon.ico")); + NewsIconMgr::self()->getIcon(u); +} + +SuggestProgressDlg::~SuggestProgressDlg() +{ + delete m_xmlSrc; +} + +void SuggestProgressDlg::slotTimeoutTick() +{ + if (m_progressBar->progress() == m_progressBar->totalSteps()) { + m_timeoutTimer->stop(); + KMessageBox::error(this, i18n("Could not retrieve the specified source file.")); + reject(); + return; + } + m_progressBar->setProgress(m_progressBar->progress() + 1); +} + +void SuggestProgressDlg::slotLoadComplete(XMLNewsSource *, bool succeeded) +{ + m_gotSourceFile = true; + m_succeeded = succeeded; + + if (m_gotIcon) + done(succeeded ? QDialog::Accepted : QDialog::Rejected); +} + +void SuggestProgressDlg::slotGotIcon(const KURL &url, const QPixmap &pixmap) +{ + m_gotIcon = true; + m_icon = pixmap; + m_iconURL = url; + + if (m_gotIcon) + done(m_succeeded ? QDialog::Accepted : QDialog::Rejected); +} + +NewsSourceDlgImpl::NewsSourceDlgImpl(QWidget *parent, const char *name, bool modal, WFlags fl) + : NewsSourceDlg(parent, name, modal, fl), + m_modified(false) +{ + connect(NewsIconMgr::self(), SIGNAL(gotIcon(const KURL &, const QPixmap &)), + this, SLOT(slotGotIcon(const KURL &, const QPixmap &))); + + for (unsigned int i = 0; i < DEFAULT_SUBJECTS; i++) + comboCategory->insertItem( + NewsSourceBase::subjectText(static_cast(i))); + +} + +void NewsSourceDlgImpl::slotCancelClicked() +{ + close(); +} + +void NewsSourceDlgImpl::slotOkClicked() +{ + KURL url (polishedURL(KURL(urlSourceFile->url()))); + + if (!validateURL(url)) + return; + + if (leName->text().isEmpty()) { + KMessageBox::error(this, i18n("You have to specify a name for this news" + " source to be able to use it."), i18n("No Name Specified")); + return; + } + + // This finds out which subject is selected in the 'Subject' combo box. + NewsSourceBase::Subject subject = NewsSourceBase::Computers; + for (unsigned int i = 0; i < DEFAULT_SUBJECTS; i++) { + NewsSourceBase::Subject thisSubj = static_cast(i); + if (comboCategory->currentText() == NewsSourceBase::subjectText(thisSubj)) { + subject = thisSubj; + break; + } + } + + KURL iconURL ( leIcon->text() ); + if (iconURL.protocol().isEmpty()) + if (iconURL.host().startsWith(QString::fromLatin1("ftp."))) + iconURL.setProtocol(QString::fromLatin1("ftp")); + else if (iconURL.host().startsWith(QString::fromLatin1("www."))) + iconURL.setProtocol(QString::fromLatin1("http")); + else + iconURL.setProtocol(QString::fromLatin1("file")); + + NewsSourceBase::Data nsd(leName->text(), url.url(), iconURL.url(), subject, + sbMaxArticles->value(), true, cbProgram->isChecked()); + + emit newsSource(nsd); + + close(); +} + +void NewsSourceDlgImpl::slotSourceFileChanged() +{ + bSuggest->setEnabled(!urlSourceFile->url().isEmpty()); +} + +void NewsSourceDlgImpl::slotSuggestClicked() +{ + KURL url ( polishedURL(KURL( urlSourceFile->url() )) ); + + if (!validateURL(url)) + return; + + SuggestProgressDlg dlg(url, this); + if (dlg.exec() == QDialog::Accepted) { + pixmapIcon->setPixmap(dlg.icon()); + if (NewsIconMgr::self()->isStdIcon(dlg.icon())) + leIcon->clear(); + else + leIcon->setText(dlg.iconURL().url()); + cbProgram->setChecked(false); + leName->setText(dlg.xmlSrc()->newsSourceName()); + sbMaxArticles->setValue(dlg.xmlSrc()->articles().count()); + } +} + +void NewsSourceDlgImpl::slotModified() +{ + m_modified = true; +} + +void NewsSourceDlgImpl::setup(const NewsSourceBase::Data &nsd, bool modify) +{ + leName->setText(nsd.name); + urlSourceFile->setURL(nsd.sourceFile); + cbProgram->setChecked(nsd.isProgram); + comboCategory->setCurrentItem(nsd.subject); + sbMaxArticles->setValue(nsd.maxArticles); + KURL iconURL ( nsd.icon ); + if (iconURL.protocol() == QString::fromLatin1("file")) + iconURL.setProtocol(QString::null); + leIcon->setText(iconURL.url()); + NewsIconMgr::self()->getIcon(iconURL); + if (modify == true) { + setCaption(i18n("Edit News Source")); + } +} + +KURL NewsSourceDlgImpl::polishedURL(const KURL &url) const +{ + KURL newURL = url; + + if (url.protocol().isEmpty()) + if (url.url().startsWith(QString::fromLatin1("ftp"))) + newURL = QString::fromLatin1("ftp://") + url.url(); + else + newURL = QString::fromLatin1("http://") + url.url(); + + return newURL; +} + +bool NewsSourceDlgImpl::validateURL(const KURL &url) +{ + if (url.isEmpty()) { + KMessageBox::error(this, i18n("You have to specify the source file for this" + " news source to be able to use it."), i18n("No Source File" + " Specified")); + return false; + } + + if (!url.isValid() || !url.hasPath() || url.encodedPathAndQuery() == QString::fromLatin1("/")) { + KMessageBox::error(this, i18n("KNewsTicker needs a valid RDF or RSS file to" + " suggest sensible values. The specified source file is invalid."), + i18n("Invalid Source File")); + return false; + } + + return true; +} + +void NewsSourceDlgImpl::slotGotIcon(const KURL &, const QPixmap &pixmap) +{ + pixmapIcon->setPixmap(pixmap); +} + +#include "newssourcedlgimpl.moc" diff --git a/knewsticker/newssourcedlgimpl.h b/knewsticker/newssourcedlgimpl.h new file mode 100644 index 00000000..71bb6964 --- /dev/null +++ b/knewsticker/newssourcedlgimpl.h @@ -0,0 +1,82 @@ +/* + * newssourcedlgimpl.h + * + * Copyright (c) 2001 Frerich Raabe + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef NEWSSOURCEDLGIMPL_H +#define NEWSSOURCEDLGIMPL_H +#include "newssourcedlg.h" +#include "newsengine.h" + +#include +#include + +#include + +class XMLNewsSource; +class NewsIconMgr; +class QProgressBar; +class QTimer; + +class SuggestProgressDlg : public KDialogBase +{ + Q_OBJECT + + public: + SuggestProgressDlg(const KURL &url, QWidget *parent, const char *name = 0); + virtual ~SuggestProgressDlg(); + + XMLNewsSource *xmlSrc() { return m_xmlSrc; } + QPixmap icon() const { return m_icon; } + const KURL &iconURL() const { return m_iconURL; } + + private slots: + void slotTimeoutTick(); + void slotLoadComplete(XMLNewsSource *, bool); + void slotGotIcon(const KURL &, const QPixmap &); + + private: + bool m_gotSourceFile; + bool m_gotIcon; + XMLNewsSource *m_xmlSrc; + bool m_succeeded; + QPixmap m_icon; + KURL m_iconURL; + QProgressBar *m_progressBar; + QTimer *m_timeoutTimer; +}; + +class NewsSourceDlgImpl : public NewsSourceDlg +{ + Q_OBJECT + + public: + NewsSourceDlgImpl(QWidget * = 0, const char * = 0, bool = FALSE, WFlags = 0); + + void setup(const NewsSourceBase::Data &, bool); + + signals: + void newsSource(const NewsSourceBase::Data &); + + protected slots: + void slotCancelClicked(); + void slotOkClicked(); + void slotSourceFileChanged(); + void slotSuggestClicked(); + void slotModified(); + KURL polishedURL(const KURL &) const; + bool validateURL(const KURL &); + + private slots: + void slotGotIcon(const KURL &, const QPixmap &); + + private: + bool m_modified; +}; + +#endif // NEWSSOURCEDLGIMPL_H diff --git a/kopete/AUTHORS b/kopete/AUTHORS new file mode 100644 index 00000000..b8d1a69c --- /dev/null +++ b/kopete/AUTHORS @@ -0,0 +1,66 @@ +See the Development team section of http://kopete.kde.org +for an updated list of Kopete team! + +Core Developers +=============== + +Duncan Mac-Vicar Prett (irc: duncanmv) +- Original author, main app work & design, original MSN Plugin, hacks in all plugins + +Martijn Klingens (irc: spaze) +- Kopete API and main application core developer, co-maintainer of the MSN plugin, + misc changes and fixes in the other plugins + +Matt Rogers irc: mattr) +- Kopete seperate release coordinator, OSCAR/AIM maintainer, general maintainer of stuff + +Olivier Goffart (irc: Gof) +- Kopete API and main application core developer, primary maintainer of the + MSN plugin, misc changes and fixes in the other plugins + +Will Stephenson (irc: bille) +- Core developer, KAddressBook integration, nowlistening and webpresence plugin + maintainer, Testbed testing plugin + +Other developers +================ + +Andy Goossens (irc: gandy) +- Bugfixes, cleanups and maintainer of our stable branches (backporting patches) + +Casey Allen Shobe (irc: sigthor) +- Testing, Quality Assurance and usability improvements + +Ladislav Strojil (irc: bwian) +- Bugfixes, testing and keeping Kopete running on KDE 3.1 + +Plugin Developers +================= + +Gav Wood (irc: emmCee) +- Winpopup and SMS maintainer, several contributions to the Yahoo plugin + +Grzegorz Jaskiewicz (irc: gregj) +- Gadu-Gadu maintainer + +Michel Hermier (irc: slayer) +- IRC plugin maintainer + +Till Gerken (irc: tigloo) +- Jabber maintainer + +Other contributors. Thanks! +=========================== + +This list is too long too mention. If you want to have an idea of all the +various contributions we've got, please check the CVS commit logs. If we +would attempt to list the people here other people would surely be forgotten +and feel left out. + +Let's leave it at telling you that several hundreds of people have +contributed code, helpful information, documentation, translations or +useful bug reports to Kopete, making Kopete what it is now. + +If you contributed to Kopete, you know who you are, and many thanks for +the work you've done! + diff --git a/kopete/COPYING b/kopete/COPYING new file mode 100644 index 00000000..9e4c44b1 --- /dev/null +++ b/kopete/COPYING @@ -0,0 +1,18 @@ +Kopete, The KDE Instant Messenger License +========================================= + +Kopete is licensed under the GNU General Public License version 2 +or later. The text of the GNU General Public License can be viewed at +http://www.gnu.org/licenses/gpl.html + +The Kopete library (libkopete) is licensed under the GNU Lesser +General Public License so plugin licenses are not restricted to be +free software. The text of the GNU Lesser General Public License can +be viewed at http://www.gnu.org/copyleft/lesser.html + +As a special exception, you have permission to link this program +with the following libraries and distribute executables, as long as you +follow the requirements of the GNU GPL in regard to all of the +software in the executable aside from the following libraries: +- OpenSSL (http://www.openssl.org) + diff --git a/kopete/ChangeLog b/kopete/ChangeLog new file mode 100644 index 00000000..53392915 --- /dev/null +++ b/kopete/ChangeLog @@ -0,0 +1,174 @@ +2004-09-10 11:10 Will Stephenson + + * Make it possible to suppress "XXX has left the chat" messages in + group chat. Needed for groupwise. + +2004-09-10 11:09 Will Stephenson + + * merge Novell GroupWise Messenger support into HEAD. + +2004-09-10 02:42 Olivier Goffart + + * Fix the popup menu over contacts + +2004-09-09 22:50 Matt Rogers + + * Fix the reappearing format toolbar. (#59080) + +2004-09-07 12:21 Will Stephenson + + * Fix stale chat window member lists on join/leave + +2004-09-07 06:24 Olivier Goffart + + * Don't request the picture on group chat. this might have verry + bad side effect + +2004-09-07 06:06 Olivier Goffart + + * Allow to drag and drop contact to the chatwindow to invite them + to the chat + +2004-09-06 04:58 Will Stephenson + + * Emoticon scheme for GroupWise + +2004-09-06 04:09 Olivier Goffart + + * Remove unused file Add tooltip + +2004-09-05 16:16 Olivier Goffart + + * Allow configuring the application used for the netmeeting plugin. + So you can now use Konference insteads of Gnomemeeting + +2004-09-04 15:52 Matt Rogers + + * default to enter to send messages + +2004-09-03 17:58 Ingo Klöcker + + * Fix bug 88759: (Multiple formulas in one paragraph + confuse Kopete). Approved by Olivier. + +2004-09-03 12:33 Olivier Goffart + + * Fix Bug 88751: (right click move contact context menu not + updated) + +2004-09-03 12:31 Olivier Goffart + + * Import the popup to ask the public key from KGPG (it has a quick + search line, and show more info) (#88757, #88756) + +2004-09-02 08:02 Olivier Goffart + + * Use the highlight color for highlight cells. (#88495) + +2004-09-02 04:53 Olivier Goffart + + * Do not crash if the sending movie can't be found. (88594 + +2004-09-02 04:26 Olivier Goffart + + * Let drag URL in the chatwindow to send files. (#82733) + +2004-09-02 02:26 Olivier Goffart + + * Ask for confirmation before deleting a contact. (#76224) + +2004-09-02 01:55 Olivier Goffart + + * Add Undo/Redo functions in the contactlist + -Group or metacontacts renames + -Move of metacontacts between groups + -Move of contact between metacontact + -Addition of contacts on the list. + +2004-08-30 12:53 Michel Hermier + + * Avoid to remove/destroy protocol managed temporary metacontact + while attempting to remove them of the contactlist. (#81823, #86358) + +2004-08-29 03:09 Olivier Goffart + + * Ask confirmation before overwrite a file for incomming file + transfers. + +2004-08-28 16:47 Olivier Goffart + + * Remove the edit style dialog, and run the default editor instead. + I used text/plain as mimetype because text/xml or text/x-xslt + don't have default editor by default. I added a tracker to + update the preview when the style is saved (#77649, #63825, #77650) + +2004-08-28 01:58 Olivier Goffart + + * Hide the header of the contactlist view (which only contains + "Contacts") (#87974) + +2004-08-27 14:20 Olivier Goffart + + * fix bug 73901 + +2004-08-27 13:41 Olivier Goffart + + * Add tooltips and whatsthis help + +2004-08-26 14:03 Michel Hermier + + * Removed KStringHandler::isUtf8 workaround bug, as now + KopeteMessage::decodeString should not trigger the bug anymore. + +2004-08-26 13:59 Michel Hermier + + * Make the decodeMessage function more fast for empty CString. As + a side effect it fix possible attempt to decode null string using + KStringHandler::isUtf8 wich make kopete crash with kdelibs prior + to 3.2.92. + +2004-08-23 12:54 Gustavo P. Boiko + + * Fix encoding of sent OSCAR messages + +2004-08-22 22:27 Matt Rogers + + * Fix bug 87727. + + saveOptions() was in the destructor, which meant the window state + was always hidden. + +2004-08-19 23:27 Matt Rogers + + * Rearrange the yahoo message parsing a bit so we do it all before we + create the KopeteMessage object for it. + + Workaround gaim's bugginess when sending URLs so that there + aren't parse errors. (#87190) + +2004-08-14 14:22 Matt Rogers + + * derive a new class from KActiveLabel so we can control how the + links are opened + +2004-08-14 14:11 Will Stephenson + + * Stupid crash bug, now fixed. Always keep a good MC pointer + around to call execute() on. (#87065) + +2004-08-13 16:01 Michel Hermier + + * Let's forget about deleting objects, QOject will make it for us. + Don't remove automagically added temporary irc account while + connection disconnected (was causing a crash). The previous + changes remove crashs and invalid reads while quitting. + +2004-08-13 13:55 Michel Hermier + + * Removed a possibly usage of a destroyed object, while clossing + kopete and attempting to loggin to IRC. + +2004-08-13 13:22 Michel Hermier + + * Avoided usage of a possible null pointer. (#87083, #86928) + diff --git a/kopete/INSTALL b/kopete/INSTALL new file mode 100644 index 00000000..1eabc857 --- /dev/null +++ b/kopete/INSTALL @@ -0,0 +1,239 @@ +Basic Installation +================== + + These are generic installation instructions. + + If you know how to build GNU autotools based packages using the +common 'configure/make/make install' scheme, please read on below in +the 'Finding Plugins' chapter. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. + +Finding Plugins +=============== +If you don't see any "plugins" listed in the Configure Plugins... +dialog, or the list of messaging protocols in the Add Account Wizard +is empty after installing, Kopete you may have installed it in a +directory where KDE doesn't look for additional resources like plugins. + +It's also possible that you've just been too impatient :) + +Kopete installs several .desktop files that have to be processed by a +KDE application called 'kbuildsycoca'. If you run Kopete directly +after installing it this process might still be running and Kopete +doesn't see the new plugins yet. Try running 'kbuildsycoca' from a +console and restart Kopete when it finishes. If that doesn't help, +please read on. + +KDE applications by default look in all directories listed in the +/etc/kderc file, the $KDEDIRS environment variable, in $KDEDIR if +this variable doesn't exist, and ultimately in the directory where +kdelibs is installed. Unless you take special precautions applications +will _NOT_ look in other directories, even if you install additional +software (like Kopete) there. + +The best solution is to add the following to /etc/kderc (create the file +if it doesn't exist yet): + + [Directories] + prefixes=/the/prefix/I/used/for/kopete + +If you don't have write access to /etc/kderc, or if you want to use more +parallel builds of KDE and/or Kopete you need to rely on the environment +variables instead. + +If $KDEDIRS currently points to /opt/kde3 and you installed Kopete +to /usr/local then you need to set $KDEDIRS to '/opt/kde3:/usr/local'. +You need to store this change in a file that is picked up by all shells +and for all users, like /etc/profile. The name of the preferred version +varies from system to system. + +As a last resort you can simply install Kopete in the same directory as +where your KDE resides by adding a flag to configure like + + ./configure --prefix=/opt/kde3 + +if KDE is installed in /opt/kde3. The downside is that you'll probably +end up installing Kopete into directories used by your distribution, +instead of a separate path for your own packages like the /usr/local/ +tree. Whether or not your package manager has problems with that and +whether or not you'll accept those problems should they occur is of +course up to you to decide, but we strongly recommend to not install +source-compiled packages like Kopete in directories that are managed +by a package manager and only use the KDE prefix if you compiled all +of KDE from source yourself. + diff --git a/kopete/KABC_INTEG_NOTES b/kopete/KABC_INTEG_NOTES new file mode 100644 index 00000000..f249f22c --- /dev/null +++ b/kopete/KABC_INTEG_NOTES @@ -0,0 +1,551 @@ +Address book integration notes 26/08/03 + +Premise: +KDE captures the real life relation between IM Contacts and KABC contacts; both represent people with whom we communicate electronically - so there's no need to separate them +Goals: +*) Duplicate Information may be removed. +*) This enables PIM apps like KMail to use a KDE IM service (Kopete) to display IM status or perform IM actions (chatting) +*) Or games, desktop sharing +*) Ability to put IM-delivered contact data into the KDE addressbook +*) send vcards via kopete + +What does Kopete get out of the association? +*) Ability to view KABC information on that metacontact +*) Use information in the KABC such as GPG key? +*) Can store/use KABC information in the metacontact like contact photo + + +Spaze's goals +1. Share data with the other PIM apps. Notably groups, display names, GPG keys, email addresses and other IM UIDs +2. Allow other apps to retrieve all IM-enabled contacts and their online status for e.g. presence display in kmail or invitations for desktop sharing +3. Allow other IM apps to share the same data that Kopete uses in a standardized way +4. Allow other apps to start a chat with selected persons in a standardized way _regardless of client used_ +5. in the longer run i would like to add,say, an ICQ UIN from kaddressbook and Kopete automatically picks up the change, adds it to the server side contact list and does all other kind of syncing of changes made in kaddressbook as well. for the short term one way kopete->kabc is ok, but the API should be prepared for full bidi comms. ideally kopete, libkabc and all server side contact lists are always 100% in sync as much as possible for each. + +mETz' goals +1. well, I just think Kopete should never edit my addressbook without me knowing + +Gof's goals +1. my goal is to have the kopete contactlist a kind of address book (i.e. i want to view and modify kabc entry from kopete, like if i am in the kab interface. i do not want to open KAB +2. and i want to share fields (gpg key) with other application, like kmail +3. technicaly, i think that should be transparent. +Goals (-ve): +*) don't store information that's not worth sharing in the address book + +gregj's goals +kopete should not replace server side data with kabc derived data nor upload information that wasn't present on the server before. +E.g. we know someones telephone nr. but we don't want to put this information back on server if it was not present there before. Update IMO is ok. + +brunes goals +the only points I wanted to make were +1. I think the MSN / OSCAR picture support should somehow integrate with / use the picture in kaddressbook, and +2. we need to get the KAB guys to add >1 field for IM account in KAB, and all the contacts for a MC should have their accounts there + +Syncing policies +KABC->kopete ok, what about server side metadata + +Display Name + +Priority: Goals for 3.2 +ONLY the actual linking and one-way storage from kopete in kabc and retrieval of display name and address book data +Also ability to add kabc contained contacts to kopete ( selected ones only ) + +Linking +use KMC UID to store KABC uid, QString::null if no link. +Establish the link using KMC Props dialog (mETz: Alt+return shortcut to open pls). +Policy: +One way kopete->kabc contacts +, achieve bidi later + what about the sync policy between IM contacts contained in kabc and in kopete + mETz: alt-enter doesn't work? report a bug to klistview/qlistview, that sucks :( + hyhy + Bille: implied i'd say + for example - in Addressee A I already have 3 IM id's added with another client - if i associate him with a Kopete MC + first, we need to do the link (almost done?) after, we will see + Bille: oh, that + do i want to add those contacts to the MC? + ugh.. + and the wizzard iontegration is in the link + now, it has no sense to provide a link if we don't use it at all + Bille: I think Gof is right. first we just ignore what's in libkabc and do it one-way + Bille: once that works we can do it bidi + ok + Bille: we have the luxury to be the first so no need to be compatible :) + Bille: as for bidi, that's still kde 3.2, at least partly + Bille: I think we should should a list of all these contacts and ask what to do + Bille: like + spaze: for kde 3.2, if we provide a link, we need to sync at least some stuff + (x) Add all contacts + spaze: but then, how do you remember what was chosen the next time the props dialog comes up + ( ) Add only these contacts + if not, the link is useless, and it's better no link at all + spaze: agreed + not even conditional, just always, if you say you want to use kabc + that's our first test case + once that works we can do the addressbook fields + gregj: what is your take on syncing display name to addressee name? + first store-and-retrieve, assuming nobody messes with it + hmm + third will be to detect changes made to address book fields outside kopete + yes + +*** + + spaze: ( ) add all contact is not good imo (i don't want to add irc channels) and also, it would make a lot of duplicate entities + i want kabc to provide me storage, and give back just what i stored + nothing more + gregj: what if it changes the Kopete displayname (that is not sent to the server)? + if i didn't provide email, i don't want email back + and so on + Gof: eh, we're talking different things now + maybe + Bille: hmm, this is hard to say now + Bille: i am retriving all information on connect from server + Gof: I'm talking about the fields to IMPORT from libkabc when we start kopete and kabc was changed by another app, i.e. someone was added there + Bille: as long as uid matches, this should be ok + gregj: ok + if someone changes uid in kabc, then it is his problem + Gof: but you pointed out a nice flaw in the current addressBookFields API :( + samppa Singularity spaze STiAT|off + gregj: i'm talking about adding new entries, not modifying + spaze: pls expand on that + spaze: what one? + Bille: the flaw? + spaze: oh + yup + spaze: than it is ok to me + spaze: i will have to synchronize it with server than + Bille: we now assume addressbook fields are either all stored in kabc or all stored in xml + +SEMANTICS OF KABC KEYS + ok, one question, it is already dual-key based (app, key) + so that's perfect + yeah, what's that Key about? + i wasn't sure what that's for + Bille: what kabc is "supposed" to be used like is as follows: + addCustomField( "kopete", "myCustomKey", "myValue" ) + BUT + we use kabc for data that is NOT app-specific + in our semantics, what's the All mean, all messaging apps incl kopete? :) + so we 'abuse' app as key and are stuck with a key that we don't use +<-- cdr has quit (Client Quit) + spaze: maybe there should be a category in kabc + Bille: oh... that's another 'abuse' of the sematics + Bille: that's only for contact id's + spaze: category for IM related info, emails, and so on + Bille: "messaging/msn" is the protocol + Bille: but suppose i have 2 msn accounts under a single KABC entry + so we can use category IM + spaze: oic + which is shared with other im apps + how are you going to tell that you want msn@martijn.homeip.net to be contacted by your main account + gregj: that's what the messaging/ part of the key we already use signifies + and msntest@.. with your debug account + Bille: got it :D + Bille: "All" means that all YOUR accounts can access MY account + +************************************************ +Use cases + +1) Add new metacontact + contacts using Add Contact Wizard + Decide whether to associate or not if using mandatory association. + a) Matching addressbook entry already exists + - Need to choose kabc entry in ACW? + - Check if kabc entry already contains IM information (old install of Kopete or from another client) + b) No matching entry exists + - Need to add new one + +2) Someone adds you - protocol notifies and creates (temporary?) MC. + (Martijn says this becomes the same as 1) when the MC is made permanent) + +3) All new Kopete (first run / no existing configs or contactlist) + Add accounts + Server Side Contact List (SSCL) fetched, lots of permanent contacts are created. +*) Consider if 2 accounts are added and there are contacts from different accounts such that they represent the same person. They belong in the same MC. MC merging should take place prior to kabc association. + +4) An existing Kopete config is present and we upgrade to a version of Kopete supporting kabc. + +5a) After abandoning Kopete, users start using a different KDE IM client, and would like to use the IM address details stored in the KDE address book. + +5b) The user used another KDE IM Client. But he finally discovers Kopete,which is the best, decides to use it, and he would like to use information already existing in KABC + +6) Protocols may deliver contact data that users may want to aggregate into the kabc entry. + +7) A contact adds a new IM account, either the user decides to add the new account to the MC manually, or the contact messages the user first, we get a temporary contact Kopete side, add them to the MC. + +8) The user no longer wishes to have a particular contact (or even metacontact) in Kopete, and deletes the object. + +9) A contact changes accounts (msn:foo@uncool.org -> foo@cool.org). This is the same as 7) then 8). + +Implementation notes +-------------------- +1) New MC. Need dialogs to ask if association needed. Association dialog allows to select/search kabc contacts. We need to write the kabc data sooner than closing Kopete! + +2) Same as 1) + +3) Not an issue given optional participation +*) is orthogonal and can be dealt with separately. + +4) Deferred association + +5a) We don't want any Kopete specific data in the kabc. Entries should be usable by other KDE IM apps. + +5b) The reverse case, therefore we should agree standard entry format with other apps. + We should also make using this info really easy in the Add Contact Wizard (see below). + +6) Question: how to combine all contacts in an MCs' data before saving this to kabc. + +7) First do dupe check or 'im-info-in-kabc-not-in-kopete' check in case this information already exists in KABC. Once added to a MC, if already associated, we can add the new contact information to kabc immediately, otherwise Deferred association accessed via MC properties dialog. + +8) Don't remove the kabc fields, other IM clients might be using them. So we just remove them from the Kopete contact list. Dupe contact on the MC add contact thingy will catch if the contact is added again. (and the user might still want to have other data like the phone number) * See the kabc section + +Therefore we need an Association Dialog accessed in wizard and from the MC properties dialog. Note, this name sucks, don't write any classes using it. + +Side Issues +----------- + +It's not possible to add >1 contact per protocol using the ACW. Martijn: >1 contact per MC is for power users, they can add extra contacts using the MC's context menu. Change to an iterative ACW would fix this. + + +Possible ACW order including kabc +--------------------------------- +1) Welcome :P +2) Choose Account +3) Fill in protocol details +4) Select Group +5) Select kabc contact from list | create new | create none + +6) - if the user selected to create a new entry in the KAB, show the page showed when you add normaly a new KAB entry (if possible where some fields are + - if the user selected to search in the existing KABC database, show a nice widget to search one user. + (I hope there is nice KParts in KABC) + +( 5) could come after the welcome but this might be too different for users to swallow... Will) + agreed - Olivier + yup - Matt +If we delay the KABC assoc question until after contacts are added, we cannot use any IM information +that is already present in KABC. And if different contacts are in the KABC, this might surprise the user when +the KMC appears in the contactlist with extra contacts.· + +So it would be better if we performed this check before adding any contacts, in the ACW + +Olivier: I think a step (the first?) should be to ask a displayname for the metacontact. + +wow wow wow... this make a big ACW, but a quick add feature would still be desired by people like this: +Bug 53062: Adding contacts more quickly + + +--Will's try-- +1) Welcome :P +2) Do you want to use the KDE Addressbook for this contact, or restrict it to Kopete? + ( Could have a 'remember my choice and don't ask again option' ). +( or else goto A ) +3) Choose addressee or add new addressee. + (Check that the addressee is not already associated with an MC) + (Mark the contact as 'In KABC' or can we just use valid vs invalid KABC UIDs to show relation? + (Chances are next time the user adds a KABC addressee it will coincide with an existing Kopete UID). +3.5) Select groups and ask for a displayname +4) (Check to see if IM fields are already present in KABC) +5a)"The addressbook already contained the following IM information.. +(goto C) + +A) How would you like to IM XXX? +B) Fill in contact details + (Add duplicate check after validity check) +C) Use another protocol? + +D) Finish screen + +NB) Better to move to a iterative selection of protocols (Select protocol -> fill in details -> Select protocol or done ...). +This way power users can add +1 contact per protocol without complicating the ACW for simple users. + + + +--Olivier's 2nd try-- +1) Welcome :P do you want to add this contact to KAB? ( we need to choose a good default, or using the last selected one ) + (.) Yes / search in KAB fo an existing entry => goto A + (.) Yes / and create a new KAB entry => goto B + (.) No / Do not add this contact to KAB => goto 3 +( This would require the user to remember if an appropriate KAB entry exists. + It would be better to allow the user to select/search KAB entries OR create a new one + from the same screen, if they can't find an entry - Will) +A) use a KPart from KAB to search an user an select one. if one exist => goto 3 the user still can shoose to create one => goto B +B) use a KPart from KAB to show the widget showed when adding new KAB entried => goto 3 + +3) select groups, and ask the displayName (let empty to use the serverside one) (show the KAB displayname by default) +4) select account to use (select by default account where a KAB entry exist) +5) fill the account data (if a KAB entry exist, show as default) +6) (.) another account (and select it) => goto 5 + (.) finish +7) Finish screen (is that possible to merge this screen with the previous one? + +This try to do all in as few step as possible. +I can't count very well, but AFAICS your suggestion is equal or greater: + +Step Count No IM in kabc 1 IM in kabc (new entry) + n new +Will 7 6 + 3 * n = 9 +Gof 6 5 + 2 * n = 7 + + + + KAB Widget in Kopete +========================= + + in the metacontact popup menu: +A) "Add to KAB" if the metacontact don't use KAB yet +B) "View information" if the metacontact is already in KAB + + +A) show the step 2 and A/B of my addcontactwizard (Olivier) + +B) KParts showing the KAddressBook entries information, and allow to edit it of +course + + +so kopete becomes in fact a addressbook itself + + + +Adding fields to kabc +~~~~~~~~~~~~~~~~~~~~~ +Either - all new addressee, no IM + new IM to add | existing address, no IM + new IM to add +| existing addressee, no IM + new IM to add | existing addressee, existing IM + new IM to add | existing addressee, existing IM + nothing to add + +I think we're going to need a more complex data structure to tell KCL::addMetaContact what it has to do. + + I don't understand this

    -Olivier + +Fields to put in kabc +~~~~~~~~~~~~~~~~~~~~~ +Relation between kabc entries and kopete metacontacts could be 1:1 or 1:many. 1:1 is neatest. + +In Kopete contact list: +include UID from related kabc entry + +In kabc +Something like X-MESSAGING-MSN:bob@hotmail.com for each protocol account that contact uses. + +We should keep the extra stuff in kabc to a minimum. We can store nicknames in contactlist.xml. + +We should allow plugins to save some data: +Some protocol allow to reach phone numbers, or more (see the ICQ info page) +It would be useful if KABC could remember what application writes an entry. so when ICQ try to modify an e-mail, only the "icq-email" is affected + +Or like the language or the PGP public key + +It is too complex to store the phone number according to icq as well as the kabc phone number and every other protocol's idea of phone. The fields would be of no use to any app other than the inserting program or else we would have to update the world. We *should* have a facility to insert information obtained from protocols into the common kabc fields but this is a hard problem to solve neatly. + - - I think it is not, ICQ phone number ~should~ be the same as all others one. storing it here is just a way to *centralise* all information about someone. Then, when you want to know his phone number, you look only there, and not at every place + *problem: how to trust the information? that's why kabc could handle a source of the information + + +KABC Participation modes +------------------------ + +Participation in the relation can be optional or mandatory. + +Optional +~~~~~~~~ +Scenario 1: +1) New Kopete install or account +2) Ask 'New contacts added, associate with KDE Address book?' +3) Yes: Pop up association dialog + No: Do nothing + +Scenario 2: +1) New Kopete install or account +2) Do nothing +3) Deferreed association - user performs associate when they want to + +-- For/In Favour +.) easier to code +.) easier migration +.) doesn't disturb people who don't want it +.) unobtrusive + +-- Against +.) less consistent + (can someone explain me this? -Olivier) + Yes, I need more explanation on what consistent means. - Matt +.) may confuse user? + how? + +Mandatory +~~~~~~~~~ +Scenario 1 +1) When fetching SSCL, automatically create synthetic kabc entries. +Scenario 2 +1) Fetch SSCL, ask user to create kabc entry normally. +Hybrid 1-2 +1) Fetch SSCL +2) Match SS contacts with kabc entries and associate automatically. Unmatched contacts are associated with synthetic kabc entries. + +-- For +.) Can integrate with kabc contacts without disturbing user + +-- Against +.) User has control of address book, this removes that control. +.) Synthetic entries may be duplicates of existing user added entries and user will have to reassociate MCs with correct entries and throw out the synthetic entries. +.) Hybrid approach may be too complex for user to comprehend. +.) this will be slow to add hundred of contacts when syncing the contactlist for the first time + +Conclusion +~~~~~~~~~~ +I think the Optional is the best solution. all i describe in the other part of this file are using this solution (Olivier) + + + +KDE PIM Apps' use of Kopete +-------------------------- + +Use cases + +1) Email client or contact management app displays email sender's IM status if known. + +2) Email client or contact management app wants to use Kopete as transport for something (vCard?). + +Notes + +Client would need to identify addressbook entries that are IM contactable, and obtain status information from an IM app or library. + +David Faure says it's possible to do loose lazy binding to the app responsible for providing status information as in 1). Something to do with KTrader and DCOP. We would need some DCOP to return status info to the client from the IM app. + +Showing the IM status of a contact in KMail, in KAddressBook.... +A "reply-by-IM" action in KMail + +such as things could be done. but how? + +The application could know if this user is registered to an IM by looking at messaging fields +But how to use such as information available only at runtime, like the Status. + +1) Saving the status to KABC + --the problem is that the status can not be reseted to offline if kopete crashed + +2) KMail could contact Kopete via DCop + --too bad if i want to use another IM Client? + +3) add an interface in kabc (or other) which could contact Kopete, or the IM which is actualy running. This is in effect what dfaure suggested above. + +KDE Games/Desktop sharing use of Kopete +--------------------------------------- +Use cases + +1) Program wants to use Kopete as transport for game/desktop sharing invitation. + +Notes + +As above, client must identify IM contactable addressbook entries, obtain status information and get the IM app to send the invite (inline/file transfer). Some protocols may not support file transfer though. Do we need a capabilities interface on the IM apps, or just use the lowest common denominator? + +On receipt of the invitation, IM app needs to recognize the invite and relay it to the recipient (similar lazy loose binding?). In Kopete we could do this in a plugin, quite neatly. + +Some problem: + Each protocol does that in a different way. The Game/Application need to know some protocol specific stuff (the MSN application ID for example) + + I was thinking about Atlantik, and other games that are protocol independent. "Protocol games" are protocols' problems. + + + + 'myself' integration +======================= + +Some protocols allow to send some personal information like real name, phone number, address, ... (see icq user info) +It would be cool if theses information about the users itself could be obtained from KAB. + +see also Bug 63297: "meta-contact" global(local) display nickname for all accounts + +Notes: some user might don't want to export their info the the server. + +Yes this would be cool. I think we need a general UI to control the flow of contact information between kabc and kopete. The myself issue is the same as the metacontacts issue. + + +What about the API? +===================== + + In KABC +--------- +The actual add to kabc could be performed in KopeteContactList::addMetaContact() + +ACW::accept() -> KopeteContactList::adddMetaContact() + +Here is a suggestion. I don't know if that already works like that in KABC. +AFAIK by reading the KABC API documentation, it use somme different c++ function to access/modify some stuff. +With this implementation, it is not possible to make that transparent for plugin. That mean the PGP plugin need to call directly the KABC API (with Addressee::pgpKey()) for example. + +My idea is to give for each fields a String, like for MIME-Type +so fields could be application/pgp-public-key (do you see what i mean?) +We can even add the possibility to have several entries for one field (StringList) (several emails for example) + +Theses key could be stendardized (freedesktop.org ?) + + In Kopete: +------------ + +I think we will need to change the xml format of the contactlist, and the whole pluginData thing. + +The idea is to have the same fields in Kopete than in KABC. +Gof: This was not our idea, we intend not to pollute the KABC with any more extra fields than necessary. Kopete's info may not be useful to other IM apps (Will) + +so for example, the pgp plugin will request the public key: +metacontact->data("application/pgp-public-key"); + +if the contact is in KABC, then libkopete will request info from kabc. Else, it will take the data stored internaly in the contactlist.xml (Gof) + +This is a good question. I think since we have the constraint that we have to keep kabc clean, this is the way to do it. (Will) + +for contacts same things. +Anyway? what if we have several contact in one metacontact. +for example: + +messenging/msn => xxx@msn.com ; yyy@msn.com (it's a StringList) +messenging/nickname => Mr. X ; Mr. Y + +that looks fine, but how to make sure than Mr. X is for xxx@msn.com, imagine if we have icq contact, or other? (Gof) +We should never store nicknames in kabc. The metacontact name should be taken from the kabc formatted name (Will) + +Of course every fields shouldn't be stored in KABC (for example: MSN's groups, or others) +to know what can be saved or not in kabc, i see two ways: +1) KopeteMetaContact::setData( QString key , QString data , bool saveToKAB); +2) if the key is or not recognized by KABC. example, if i save "messaging/msn-groups" KAB is not able to store it, so don't store it in KABC + + +Syncing contactlist info currently takes place when Kopete exits. other kabc apps write kabc during their runs. We will have to make changes to do this and to make sure that we don't try to write 200 kabc entries simultaneously with individual tickets after fetching the SSCL for a new account. + +I don't think that syncing the KAB on an SSCL fetch is necessary. The only information we get when we fetch the SSCL is the contact name of that person who is on our SSCL, it's not until later, perhaps when we request the user info that we update the KAB. (Matt) + + + + +|How to share data when multiple contact in a metacontact?| ++---------------------------------------------------------+ + +The problem is that there are several sources of the information. example: if i have two msn contact ion the metacontact, i have maybe two pictures. + +Currently, two solution has been mentioned: +1) use a per-contact value + a metacontact one. If there is only one contact, we automaticaly track the child contact one (exepted if the user selected himself the metacontact value). If not, we let the user choose what to use. + (like we already does for displayname) + +2) Use the account / contact priority to determine the value. + + + +KABC Association +---------------- +Association creates a 1:1 relationship between KMC and Addressee. If we assume IM apps incl Kopete put IM addresses in the KABC (Messaging/msn etc) then whenever an association is made or changed, there will have to be a sync between Kopete's idea of IM addresses and those stored in KABC. Changes to KABC data could happen while Kopete is offline so this sync check needs to take place when Kopete starts. Potential problems if another IM client changes the data whilst Kopete is running or vice versa - addressees aren't locked while an apps is running, only while writing. + +Association with existing contact: 1) No messaging information 2) Some messaging entries already exist. Possible courses of action - add (only in KABC) accounts to MC - ask (how do we remember what the user wanted the next time we read the KABC) + +What if a related kabc entry is deleted while Kopete is running? + +Deserialising contact from contactlist.xml - do we try to check the kabc entries and create any accounts that we find there but not in the contactlist.xml? + +When to update KABC +------------------- +Kopete writes its contactlist.xml at app close. This is sufficient for kopete as the data is held in memory and not shared. Data Kopete puts in KABC is shared, however, and users will expect to be able to use that data immediately. Hence, data that will be put in KABC should be put there immediately. +This should happen when the KABC-exposed data changes - when an MC's set of contacts changes +NOT when starting kopete and the MC is filled with contacts! +*) When adding a new contact (Use case: A new contact messages me, I add them to my contact list, and then I want to play a game with them, and invite them using IM, for example) + KopeteContactList::addMetaContact() +*) When linking an existing contact to KABC (so other apps gain the benefit of the new link) + handle this in KMC::setMetaContactId() - remember issues if a link is changed, do we delete the kabc fields? +*) When adding new contacts to a KMC. + KMC::addContact() +*) When moving contacts between KMCs. KC::setMetaContact()? +*) When a MC's KABC association is changed. - KsetMetaContact diff --git a/kopete/Makefile.am b/kopete/Makefile.am new file mode 100644 index 00000000..9532f16b --- /dev/null +++ b/kopete/Makefile.am @@ -0,0 +1,22 @@ +SUBDIRS = libkopete kopete protocols plugins icons sounds styles + +api: + $(mkinstalldirs) $(top_builddir)/kopete/apidocs/libkopete + if test ! -x $(top_builddir)/kopete/apidocs/common; then \ + $(LN_S) $(kde_libs_htmldir)/en/common $(top_builddir)/kopete/apidocs/common; \ + fi + doxygen $(top_srcdir)/kopete/kopete.api + + +messages: + $(EXTRACTRC) --context="Translators: The %FOO% placeholders are variables that are substituted in the code, please leave them untranslated" --tag-group=none --tag kopete-i18n styles/*.xsl > xml_doc.cpp + $(EXTRACTRC) `find . -name \*.ui -o -name \*.rc | egrep -v '(libkopete/compat|protocols/testbed)'` > rc.cpp + LIST=`find . -name \*.h -o -name \*.cpp -o -name \*.c | egrep -v '(libkopete/compat|protocols/testbed)'`; \ + if test -n "$$LIST"; then \ + $(XGETTEXT) $$LIST -o $(podir)/kopete.pot; \ + fi + -rm xml_doc.cpp + + +DOXYGEN_EMPTY = YES +include $(top_srcdir)/admin/Doxyfile.am diff --git a/kopete/README b/kopete/README new file mode 100644 index 00000000..6da033f1 --- /dev/null +++ b/kopete/README @@ -0,0 +1,29 @@ +Kopete, The KDE Messenger +Duncan Mac-Vicar Prett , and a cast of thousands. +http://kopete.kde.org +---------------------------------------------------------------------- + +Kopete is an Instant Messaging client designed to be modular and plugin based. + +It requires the KDE Desktop, version 3.3 or higher. + +Kopete ships with a lot of protocol plugins, supporting nine different messaging +systems. All plugins have seen numerous enhancements and bugfixes since the +last release and should bring your Kopete experience to a whole new level. + +Additionally a lot of work has gone into cleaning up the core Kopete API and +GUI with several important usability-, stability- and performance-improvements. + +The Kopete team consists now of several active developers, working on the +various plugins and the core API. However, Kopete wouldn't be in the shape +in which it is now without the various contributions from KDE developers +all over the world and the high-quality bug reports from active users. +Thanks to all of you for your support and your interest in Kopete! + +As always, more bug reports and patches are welcome. +Please use http://bugs.kde.org. + +The Kopete main developers can be contacted on our mailing list, +kopete-devel@kde.org, and on IRC in #kopete, on irc.freenode.net. +We always welcome new contributors. + diff --git a/kopete/TODO b/kopete/TODO new file mode 100644 index 00000000..e9a7a594 --- /dev/null +++ b/kopete/TODO @@ -0,0 +1,90 @@ +These are random snippets that should give developers a clue of what we have +to fix before the next release. + +Beware: It's totally unsorted ;) + +================================================================================ +- Once and for all fix the way displayName is used in KopeteContact. With + serverside contact lists it's now impossible to rename contacts on the server + because some people (e.g. Olivier) want to see the nickname that a user + assigned and others (e.g. Martijn) want to ALWAYS see the display name of the + meta contact. + displayName and nickName should be split and depending on the user's prefs + displayName() should return either MC->displayName() or nickName(). + +- Add a way to detect when a protocol goes offline involuntary. In that case + try a reconnect first and only leave the protocol offline if the reconnect + fails. Alternatively, retry reconnecting with increasing intervals. + -> PARTLY DONE, see KopeteAccount::disconnect() functions + +- Prevent attempting to send while offline where appropriate + +- First time wizard + +- Save the account order in the config file (IMPORTANT) + +- Interface for application invitations, and better filetransfers support + +- KAB integration + -> DONE? + + +================================================================================ + + +OSCAR ICQ/AIM TODO ITEMS + + OSCAR in general======================================================== + - somehow sync server and local list, this is not as trivial as everybody + always thinks it is because you cannot sure if local changes or + serverside-changes caused the difference (think about two clients being used + for the same account, one at home and one at work). + Update: Actually it might be possible by saving two timestamp/size values + for the account. Serverside and local values. + Also I'm much in favor of hindering the user from offline editing the + contactlist. + X save groupID in KopeteGroups + X make renaming serverside contacts possible + (should work if kopete groupname == serverside groupname) + X Base Buffer class on something like QDataStream (if possible), + I don't like all the pointer stuff in here + X get rid of AIMContactList, AIMBuddy (almost gone already) and AIMGroup. + + ICQ specific =========================================================== + X support simple icq type-2 messages so we can send/receive away + messages (yes, I even see this as a point for not releasing 0.7, away + messages ARE important or else I could take out away modes altogether) + X prevent passwords longer than 8 chars, it upsets the server + - general support for SNAC (0x15,y) + X read away reasons + X own away-reasons + - Support RTF + X honor encodings for both sides (done but incomplete) + - Option: Allow access from contacts on my contact list only + - group handling + (signals: KopeteMetaContact::movedToGroup, addedToGroup, removedToGroup) + I'm not sure what this item means, none of the other protocols listen to + these signals. + - ignore lists (needs proper group-handling) + - invisible list (needs proper group-handling) + - support sending all of your own icq userinfo to the server, + it's easy to do but a lot of of boring work + + AIM specific =========================================================== + - Nothing in here yet, I'd appreciate somebody with more extensive use + of AIM to take over just that part of the plugin. + + +================================================================================ + + + MSN TODO (for the Kopete 1.0 release) +--------------------------------------- + + - Handle the MSN PLUS! color codes + - Show internals messages in chat window when filetransfers (go with + the new interface for invitation in libkopete) + + - Search for an MSN User (not for Kopete 1.0) + +================================================================================ diff --git a/kopete/VERSION b/kopete/VERSION new file mode 100644 index 00000000..c7bfcc62 --- /dev/null +++ b/kopete/VERSION @@ -0,0 +1 @@ +Kopete v0.12.4 diff --git a/kopete/configure.in.in b/kopete/configure.in.in new file mode 100644 index 00000000..802dd84d --- /dev/null +++ b/kopete/configure.in.in @@ -0,0 +1,167 @@ +#MIN_CONFIG(3.3) +AC_HAVE_GL +KDE_INIT_DOXYGEN +AC_CACHE_CHECK(for tm_gmtoff in struct tm, ac_cv_struct_tm_gmtoff, + AC_TRY_COMPILE([ + #include + ], [ + struct tm tm; + tm.tm_gmtoff = 1; + ], ac_cv_struct_tm_gmtoff=yes, ac_cv_struct_tm_gmtoff=no)) +if test $ac_cv_struct_tm_gmtoff = yes; then + AC_DEFINE(HAVE_TM_GMTOFF, 1, [Define if you have a tm_gmtoff member in struct tm]) +fi + +KDE_CHECK_HEADERS(knotifydialog.h) + +KOPETE_INCLUDES='-I$(top_srcdir)/kopete/libkopete -I$(top_builddir)/kopete/libkopete -I$(top_srcdir)/kopete/libkopete/avdevice -I$(top_srcdir)/kopete/libkopete/ui -I$(top_builddir)/kopete/libkopete/ui' + +AC_MSG_CHECKING([for kdelibs newer than 3.3.x]) +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +kcompat_save_CXXFLAGS="$CXXFLAGS" +kcompat_safe_LIBS="$LIBS" +LIBS="$LIBS $X_EXTRA_LIBS" +CXXFLAGS="$CXXFLAGS $all_includes" + +AC_TRY_COMPILE([ +#include +#if !( KDE_IS_VERSION( 3, 3, 90 ) ) +#error Kopete compatibility with KDE 3.3 needs to be enabled +#endif +], +[ +], + AC_MSG_RESULT(yes) +, + KOPETE_COMPAT_INCLUDES='-I$(top_srcdir)/kopete/libkopete/compat' + KOPETE_INCLUDES=$KOPETE_INCLUDES' $(KOPETE_COMPAT_INCLUDES)' + LIB_KOPETECOMPAT='$(top_builddir)/kopete/libkopete/libkopete.la' + AC_MSG_RESULT(no) +) +CXXFLAGS="$kcompat_save_CXXFLAGS" +LIBS="$kcompat_safe_LIBS" +AC_LANG_RESTORE +AM_CONDITIONAL(compile_LIBKOPETE_COMPAT, test -n "$LIB_KOPETECOMPAT") + + +AC_ARG_ENABLE(smpppd, +[AC_HELP_STRING([--enable-smpppd], [enable support for the SuSE Meta PPP Daemon (smpppd) (default is NO)])], +[ + if test $enableval = yes; then + AC_DEFINE(USE_SMPPPD, 1, [enable support for the smpppd]) + COMPILESMPPPDCS=true + else + COMPILESMPPPDCS= + fi +], +[ COMPILESMPPPDCS= +]) +AM_CONDITIONAL(include_smpppdcs, test -n "$COMPILESMPPPDCS") + +KDE_FIND_PATH(xml2-config, XML_CONFIG, [${exec_prefix}/bin ${prefix}/bin], [ + AC_MSG_WARN([libxml2 not found anywhere, check ftp://xmlsoft.org/ for libxml >= 2.4.8. ]) +]) + +if test -n "$XML_CONFIG"; then + vers=`$XML_CONFIG --version 2>/dev/null | sed -e 's/libxml //' | awk 'BEGIN { FS = "."; } { printf "%d", ($1 * 1000 + $2) * 1000 + $3;}'` + if test -n "$vers" && test "$vers" -ge 2004008 + then + LIBXML_LIBS="`$XML_CONFIG --libs`" + LIBXML_RPATH= + for args in $LIBXML_LIBS; do + case $args in + -L*) + LIBXML_RPATH="$LIBXML_RPATH $args" + ;; + esac + done + LIBXML_RPATH=`echo $LIBXML_RPATH | sed -e "s/-L/-R/g"` + LIBXML_CFLAGS="`$XML_CONFIG --cflags`" + + KDE_FIND_PATH(xmllint, XMLLINT, [${prefix}/bin ${exec_prefix}/bin /usr/local/bin /opt/local/bin /usr/bin], [XMLLINT=""]) + AC_DEFINE_UNQUOTED(XMLLINT, "$XMLLINT", [Defines the executable of xmllint]) + else + AC_MSG_WARN([You need at least libxml 2.4.8]) + DO_NOT_COMPILE="$DO_NOT_COMPILE kopete" + fi +fi + + + KDE_FIND_PATH(xslt-config, XSLT_CONFIG, [${prefix}/bin ${exec_prefix}/bin /usr/local/bin /opt/local/bin /usr/bin], [ + AC_MSG_WARN([Could not find libxslt anywhere, check ftp://xmlsoft.org/ for libxslt >= 1.0.7.]) + ]) + + if test -n "$XSLT_CONFIG"; then + vers=`$XSLT_CONFIG --version 2>/dev/null | awk 'BEGIN { FS = "."; } { printf "%d", ($1 * 1000 + $2) * 1000 + $3;}'` + if test -n "$vers" && test "$vers" -ge 1000007; then + LIBXSLT_LIBS="`$XSLT_CONFIG --libs`" + LIBXSLT_RPATH= + for args in $LIBXSLT_LIBS; do + case $args in + -L*) + LIBXSLT_RPATH="$LIBXSLT_RPATH $args" + ;; + esac + done + LIBXSLT_RPATH=`echo $LIBXSLT_RPATH | sed -e "s/-L/-R/g"` + LIBXSLT_CFLAGS="`$XSLT_CONFIG --cflags`" + AC_DEFINE(HAVE_XSLT, 1, [Define if you have xslt libraries and header files]) + else + AC_WARN([You need at least libxslt 1.0.7]) + fi + fi + +if test ! "$USE_RPATH" = "yes"; then + LIBXSLT_RPATH= + LIBXML_RPATH= +fi + +if test -z "$XML_CONFIG"; then + DO_NOT_COMPILE="$DO_NOT_COMPILE kopete" +fi + +if test -z "$XSLT_CONFIG"; then + DO_NOT_COMPILE="$DO_NOT_COMPILE kopete" +fi +AC_SUBST(LIBXSLT_LIBS) +AC_SUBST(LIBXSLT_CFLAGS) +AC_SUBST(LIBXSLT_RPATH) + +AC_SUBST(LIBXML_LIBS) +AC_SUBST(LIBXML_CFLAGS) +AC_SUBST(LIBXML_RPATH) + +AC_SUBST(KOPETE_INCLUDES) +AC_SUBST(KOPETE_COMPAT_INCLUDES) +AC_SUBST(LIB_KOPETECOMPAT) + +# -- Check for installed Valgrind headers -------------------- + +AC_MSG_CHECKING([for valgrind.h]) + +AC_TRY_COMPILE([ +#define __VALGRIND_SOMESKIN_H +#include +], +[ +], + ac_have_valgrind_h=yes +, + ac_have_valgrind_h=no +) + +if test $ac_have_valgrind_h = yes; then + AC_DEFINE(HAVE_VALGRIND_H, 1, [Define if you have valgrind.h installed]) +fi + +AC_MSG_RESULT($ac_have_valgrind_h) + +# -- End valgrind ---------------------------------------------- + +# -- Determine pointer size for sqlite ------------------------- + +KDE_CHECK_TYPES +AC_DEFINE(SQLITE_PTR_SZ, SIZEOF_CHAR_P, [Determine pointer size for SQLite]) + +# -- End sqlite ------------------------------------------------ diff --git a/kopete/icons/Makefile.am b/kopete/icons/Makefile.am new file mode 100644 index 00000000..a4b97f06 --- /dev/null +++ b/kopete/icons/Makefile.am @@ -0,0 +1 @@ +KDE_ICON=AUTO diff --git a/kopete/icons/cr128-action-voicecall.png b/kopete/icons/cr128-action-voicecall.png new file mode 100644 index 00000000..7afcf712 Binary files /dev/null and b/kopete/icons/cr128-action-voicecall.png differ diff --git a/kopete/icons/cr128-action-webcamreceive.png b/kopete/icons/cr128-action-webcamreceive.png new file mode 100644 index 00000000..fe1ab8fb Binary files /dev/null and b/kopete/icons/cr128-action-webcamreceive.png differ diff --git a/kopete/icons/cr128-action-webcamsend.png b/kopete/icons/cr128-action-webcamsend.png new file mode 100644 index 00000000..7f162e68 Binary files /dev/null and b/kopete/icons/cr128-action-webcamsend.png differ diff --git a/kopete/icons/cr16-action-account_offline_overlay.png b/kopete/icons/cr16-action-account_offline_overlay.png new file mode 100644 index 00000000..b4ccc595 Binary files /dev/null and b/kopete/icons/cr16-action-account_offline_overlay.png differ diff --git a/kopete/icons/cr16-action-add_user.png b/kopete/icons/cr16-action-add_user.png new file mode 100644 index 00000000..cec9f51f Binary files /dev/null and b/kopete/icons/cr16-action-add_user.png differ diff --git a/kopete/icons/cr16-action-contact_away_overlay.png b/kopete/icons/cr16-action-contact_away_overlay.png new file mode 100644 index 00000000..5a8b8729 Binary files /dev/null and b/kopete/icons/cr16-action-contact_away_overlay.png differ diff --git a/kopete/icons/cr16-action-contact_busy_overlay.png b/kopete/icons/cr16-action-contact_busy_overlay.png new file mode 100644 index 00000000..1ddd82b6 Binary files /dev/null and b/kopete/icons/cr16-action-contact_busy_overlay.png differ diff --git a/kopete/icons/cr16-action-contact_food_overlay.png b/kopete/icons/cr16-action-contact_food_overlay.png new file mode 100644 index 00000000..c08d5bc8 Binary files /dev/null and b/kopete/icons/cr16-action-contact_food_overlay.png differ diff --git a/kopete/icons/cr16-action-contact_invisible_overlay.png b/kopete/icons/cr16-action-contact_invisible_overlay.png new file mode 100644 index 00000000..86ed1a26 Binary files /dev/null and b/kopete/icons/cr16-action-contact_invisible_overlay.png differ diff --git a/kopete/icons/cr16-action-contact_phone_overlay.png b/kopete/icons/cr16-action-contact_phone_overlay.png new file mode 100644 index 00000000..42ed83c3 Binary files /dev/null and b/kopete/icons/cr16-action-contact_phone_overlay.png differ diff --git a/kopete/icons/cr16-action-contact_xa_overlay.png b/kopete/icons/cr16-action-contact_xa_overlay.png new file mode 100644 index 00000000..43d03896 Binary files /dev/null and b/kopete/icons/cr16-action-contact_xa_overlay.png differ diff --git a/kopete/icons/cr16-action-delete_user.png b/kopete/icons/cr16-action-delete_user.png new file mode 100644 index 00000000..568b74ee Binary files /dev/null and b/kopete/icons/cr16-action-delete_user.png differ diff --git a/kopete/icons/cr16-action-edit_user.png b/kopete/icons/cr16-action-edit_user.png new file mode 100644 index 00000000..fa02d83c Binary files /dev/null and b/kopete/icons/cr16-action-edit_user.png differ diff --git a/kopete/icons/cr16-action-emoticon.png b/kopete/icons/cr16-action-emoticon.png new file mode 100644 index 00000000..3567cd09 Binary files /dev/null and b/kopete/icons/cr16-action-emoticon.png differ diff --git a/kopete/icons/cr16-action-kopeteavailable.png b/kopete/icons/cr16-action-kopeteavailable.png new file mode 100644 index 00000000..5377f424 Binary files /dev/null and b/kopete/icons/cr16-action-kopeteavailable.png differ diff --git a/kopete/icons/cr16-action-kopeteaway.png b/kopete/icons/cr16-action-kopeteaway.png new file mode 100644 index 00000000..cdaa5b29 Binary files /dev/null and b/kopete/icons/cr16-action-kopeteaway.png differ diff --git a/kopete/icons/cr16-action-kopeteeditstatusmessage.png b/kopete/icons/cr16-action-kopeteeditstatusmessage.png new file mode 100644 index 00000000..fc9884bf Binary files /dev/null and b/kopete/icons/cr16-action-kopeteeditstatusmessage.png differ diff --git a/kopete/icons/cr16-action-kopetestatusmessage.png b/kopete/icons/cr16-action-kopetestatusmessage.png new file mode 100644 index 00000000..dff59936 Binary files /dev/null and b/kopete/icons/cr16-action-kopetestatusmessage.png differ diff --git a/kopete/icons/cr16-action-metacontact_away.png b/kopete/icons/cr16-action-metacontact_away.png new file mode 100644 index 00000000..40699175 Binary files /dev/null and b/kopete/icons/cr16-action-metacontact_away.png differ diff --git a/kopete/icons/cr16-action-metacontact_offline.png b/kopete/icons/cr16-action-metacontact_offline.png new file mode 100644 index 00000000..f0e3e14e Binary files /dev/null and b/kopete/icons/cr16-action-metacontact_offline.png differ diff --git a/kopete/icons/cr16-action-metacontact_online.png b/kopete/icons/cr16-action-metacontact_online.png new file mode 100644 index 00000000..4060007a Binary files /dev/null and b/kopete/icons/cr16-action-metacontact_online.png differ diff --git a/kopete/icons/cr16-action-metacontact_unknown.png b/kopete/icons/cr16-action-metacontact_unknown.png new file mode 100644 index 00000000..290bc852 Binary files /dev/null and b/kopete/icons/cr16-action-metacontact_unknown.png differ diff --git a/kopete/icons/cr16-action-newmsg.png b/kopete/icons/cr16-action-newmsg.png new file mode 100644 index 00000000..d42bb0ae Binary files /dev/null and b/kopete/icons/cr16-action-newmsg.png differ diff --git a/kopete/icons/cr16-action-search_user.png b/kopete/icons/cr16-action-search_user.png new file mode 100644 index 00000000..d199579a Binary files /dev/null and b/kopete/icons/cr16-action-search_user.png differ diff --git a/kopete/icons/cr16-action-show_offliners.png b/kopete/icons/cr16-action-show_offliners.png new file mode 100644 index 00000000..93cefe2b Binary files /dev/null and b/kopete/icons/cr16-action-show_offliners.png differ diff --git a/kopete/icons/cr16-action-status_unknown.png b/kopete/icons/cr16-action-status_unknown.png new file mode 100644 index 00000000..fd49f31f Binary files /dev/null and b/kopete/icons/cr16-action-status_unknown.png differ diff --git a/kopete/icons/cr16-action-status_unknown_overlay.png b/kopete/icons/cr16-action-status_unknown_overlay.png new file mode 100644 index 00000000..21cff88d Binary files /dev/null and b/kopete/icons/cr16-action-status_unknown_overlay.png differ diff --git a/kopete/icons/cr16-action-voicecall.png b/kopete/icons/cr16-action-voicecall.png new file mode 100644 index 00000000..42b2f49c Binary files /dev/null and b/kopete/icons/cr16-action-voicecall.png differ diff --git a/kopete/icons/cr16-action-webcamreceive.png b/kopete/icons/cr16-action-webcamreceive.png new file mode 100644 index 00000000..0d177d9f Binary files /dev/null and b/kopete/icons/cr16-action-webcamreceive.png differ diff --git a/kopete/icons/cr16-action-webcamsend.png b/kopete/icons/cr16-action-webcamsend.png new file mode 100644 index 00000000..b1572b73 Binary files /dev/null and b/kopete/icons/cr16-action-webcamsend.png differ diff --git a/kopete/icons/cr22-action-account_offline_overlay.png b/kopete/icons/cr22-action-account_offline_overlay.png new file mode 100644 index 00000000..89d0f10f Binary files /dev/null and b/kopete/icons/cr22-action-account_offline_overlay.png differ diff --git a/kopete/icons/cr22-action-add_user.png b/kopete/icons/cr22-action-add_user.png new file mode 100644 index 00000000..539debe0 Binary files /dev/null and b/kopete/icons/cr22-action-add_user.png differ diff --git a/kopete/icons/cr22-action-delete_user.png b/kopete/icons/cr22-action-delete_user.png new file mode 100644 index 00000000..134f27f5 Binary files /dev/null and b/kopete/icons/cr22-action-delete_user.png differ diff --git a/kopete/icons/cr22-action-edit_user.png b/kopete/icons/cr22-action-edit_user.png new file mode 100644 index 00000000..9e53bfa4 Binary files /dev/null and b/kopete/icons/cr22-action-edit_user.png differ diff --git a/kopete/icons/cr22-action-kopeteavailable.png b/kopete/icons/cr22-action-kopeteavailable.png new file mode 100644 index 00000000..1449318f Binary files /dev/null and b/kopete/icons/cr22-action-kopeteavailable.png differ diff --git a/kopete/icons/cr22-action-kopeteaway.png b/kopete/icons/cr22-action-kopeteaway.png new file mode 100644 index 00000000..370144e0 Binary files /dev/null and b/kopete/icons/cr22-action-kopeteaway.png differ diff --git a/kopete/icons/cr22-action-kopeteeditstatusmessage.png b/kopete/icons/cr22-action-kopeteeditstatusmessage.png new file mode 100644 index 00000000..ce8b2267 Binary files /dev/null and b/kopete/icons/cr22-action-kopeteeditstatusmessage.png differ diff --git a/kopete/icons/cr22-action-search_user.png b/kopete/icons/cr22-action-search_user.png new file mode 100644 index 00000000..5f637d96 Binary files /dev/null and b/kopete/icons/cr22-action-search_user.png differ diff --git a/kopete/icons/cr22-action-show_offliners.png b/kopete/icons/cr22-action-show_offliners.png new file mode 100644 index 00000000..473181da Binary files /dev/null and b/kopete/icons/cr22-action-show_offliners.png differ diff --git a/kopete/icons/cr22-action-voicecall.png b/kopete/icons/cr22-action-voicecall.png new file mode 100644 index 00000000..8c5c3c52 Binary files /dev/null and b/kopete/icons/cr22-action-voicecall.png differ diff --git a/kopete/icons/cr22-action-webcamreceive.png b/kopete/icons/cr22-action-webcamreceive.png new file mode 100644 index 00000000..cb5a2a2b Binary files /dev/null and b/kopete/icons/cr22-action-webcamreceive.png differ diff --git a/kopete/icons/cr22-action-webcamsend.png b/kopete/icons/cr22-action-webcamsend.png new file mode 100644 index 00000000..55d5ef4d Binary files /dev/null and b/kopete/icons/cr22-action-webcamsend.png differ diff --git a/kopete/icons/cr32-action-account_offline_overlay.png b/kopete/icons/cr32-action-account_offline_overlay.png new file mode 100644 index 00000000..07729057 Binary files /dev/null and b/kopete/icons/cr32-action-account_offline_overlay.png differ diff --git a/kopete/icons/cr32-action-add_user.png b/kopete/icons/cr32-action-add_user.png new file mode 100644 index 00000000..6b01866e Binary files /dev/null and b/kopete/icons/cr32-action-add_user.png differ diff --git a/kopete/icons/cr32-action-delete_user.png b/kopete/icons/cr32-action-delete_user.png new file mode 100644 index 00000000..e261fa85 Binary files /dev/null and b/kopete/icons/cr32-action-delete_user.png differ diff --git a/kopete/icons/cr32-action-edit_user.png b/kopete/icons/cr32-action-edit_user.png new file mode 100644 index 00000000..d7720925 Binary files /dev/null and b/kopete/icons/cr32-action-edit_user.png differ diff --git a/kopete/icons/cr32-action-kopeteavailable.png b/kopete/icons/cr32-action-kopeteavailable.png new file mode 100644 index 00000000..24d280b9 Binary files /dev/null and b/kopete/icons/cr32-action-kopeteavailable.png differ diff --git a/kopete/icons/cr32-action-kopeteaway.png b/kopete/icons/cr32-action-kopeteaway.png new file mode 100644 index 00000000..c13f5224 Binary files /dev/null and b/kopete/icons/cr32-action-kopeteaway.png differ diff --git a/kopete/icons/cr32-action-kopeteeditstatusmessage.png b/kopete/icons/cr32-action-kopeteeditstatusmessage.png new file mode 100644 index 00000000..1d691451 Binary files /dev/null and b/kopete/icons/cr32-action-kopeteeditstatusmessage.png differ diff --git a/kopete/icons/cr32-action-metacontact_away.png b/kopete/icons/cr32-action-metacontact_away.png new file mode 100644 index 00000000..68fe27e4 Binary files /dev/null and b/kopete/icons/cr32-action-metacontact_away.png differ diff --git a/kopete/icons/cr32-action-metacontact_offline.png b/kopete/icons/cr32-action-metacontact_offline.png new file mode 100644 index 00000000..27a80af2 Binary files /dev/null and b/kopete/icons/cr32-action-metacontact_offline.png differ diff --git a/kopete/icons/cr32-action-metacontact_online.png b/kopete/icons/cr32-action-metacontact_online.png new file mode 100644 index 00000000..083a8eed Binary files /dev/null and b/kopete/icons/cr32-action-metacontact_online.png differ diff --git a/kopete/icons/cr32-action-metacontact_unknown.png b/kopete/icons/cr32-action-metacontact_unknown.png new file mode 100644 index 00000000..52ab79ed Binary files /dev/null and b/kopete/icons/cr32-action-metacontact_unknown.png differ diff --git a/kopete/icons/cr32-action-newmessage.mng b/kopete/icons/cr32-action-newmessage.mng new file mode 100644 index 00000000..a3fbc8f8 Binary files /dev/null and b/kopete/icons/cr32-action-newmessage.mng differ diff --git a/kopete/icons/cr32-action-newmsg.png b/kopete/icons/cr32-action-newmsg.png new file mode 100644 index 00000000..b18cb24e Binary files /dev/null and b/kopete/icons/cr32-action-newmsg.png differ diff --git a/kopete/icons/cr32-action-search_user.png b/kopete/icons/cr32-action-search_user.png new file mode 100644 index 00000000..48136240 Binary files /dev/null and b/kopete/icons/cr32-action-search_user.png differ diff --git a/kopete/icons/cr32-action-show_offliners.png b/kopete/icons/cr32-action-show_offliners.png new file mode 100644 index 00000000..875d19ad Binary files /dev/null and b/kopete/icons/cr32-action-show_offliners.png differ diff --git a/kopete/icons/cr32-action-voicecall.png b/kopete/icons/cr32-action-voicecall.png new file mode 100644 index 00000000..f8719dc9 Binary files /dev/null and b/kopete/icons/cr32-action-voicecall.png differ diff --git a/kopete/icons/cr32-action-webcamreceive.png b/kopete/icons/cr32-action-webcamreceive.png new file mode 100644 index 00000000..6db6c8f6 Binary files /dev/null and b/kopete/icons/cr32-action-webcamreceive.png differ diff --git a/kopete/icons/cr32-action-webcamsend.png b/kopete/icons/cr32-action-webcamsend.png new file mode 100644 index 00000000..7c0b1932 Binary files /dev/null and b/kopete/icons/cr32-action-webcamsend.png differ diff --git a/kopete/icons/cr48-action-kopeteavailable.png b/kopete/icons/cr48-action-kopeteavailable.png new file mode 100644 index 00000000..51678b32 Binary files /dev/null and b/kopete/icons/cr48-action-kopeteavailable.png differ diff --git a/kopete/icons/cr48-action-kopeteaway.png b/kopete/icons/cr48-action-kopeteaway.png new file mode 100644 index 00000000..f31bf9b7 Binary files /dev/null and b/kopete/icons/cr48-action-kopeteaway.png differ diff --git a/kopete/icons/cr48-action-metacontact_away.png b/kopete/icons/cr48-action-metacontact_away.png new file mode 100644 index 00000000..1b1e409b Binary files /dev/null and b/kopete/icons/cr48-action-metacontact_away.png differ diff --git a/kopete/icons/cr48-action-metacontact_offline.png b/kopete/icons/cr48-action-metacontact_offline.png new file mode 100644 index 00000000..5a67789d Binary files /dev/null and b/kopete/icons/cr48-action-metacontact_offline.png differ diff --git a/kopete/icons/cr48-action-metacontact_online.png b/kopete/icons/cr48-action-metacontact_online.png new file mode 100644 index 00000000..eeeebaad Binary files /dev/null and b/kopete/icons/cr48-action-metacontact_online.png differ diff --git a/kopete/icons/cr48-action-voicecall.png b/kopete/icons/cr48-action-voicecall.png new file mode 100644 index 00000000..2633f90a Binary files /dev/null and b/kopete/icons/cr48-action-voicecall.png differ diff --git a/kopete/icons/cr48-action-webcamreceive.png b/kopete/icons/cr48-action-webcamreceive.png new file mode 100644 index 00000000..e5b45d7b Binary files /dev/null and b/kopete/icons/cr48-action-webcamreceive.png differ diff --git a/kopete/icons/cr48-action-webcamsend.png b/kopete/icons/cr48-action-webcamsend.png new file mode 100644 index 00000000..d433acf8 Binary files /dev/null and b/kopete/icons/cr48-action-webcamsend.png differ diff --git a/kopete/icons/cr64-action-voicecall.png b/kopete/icons/cr64-action-voicecall.png new file mode 100644 index 00000000..5c399854 Binary files /dev/null and b/kopete/icons/cr64-action-voicecall.png differ diff --git a/kopete/icons/cr64-action-webcamreceive.png b/kopete/icons/cr64-action-webcamreceive.png new file mode 100644 index 00000000..cab7eb6b Binary files /dev/null and b/kopete/icons/cr64-action-webcamreceive.png differ diff --git a/kopete/icons/cr64-action-webcamsend.png b/kopete/icons/cr64-action-webcamsend.png new file mode 100644 index 00000000..97b5ea05 Binary files /dev/null and b/kopete/icons/cr64-action-webcamsend.png differ diff --git a/kopete/icons/crsc-action-account_offline_overlay.svgz b/kopete/icons/crsc-action-account_offline_overlay.svgz new file mode 100644 index 00000000..9356668f Binary files /dev/null and b/kopete/icons/crsc-action-account_offline_overlay.svgz differ diff --git a/kopete/icons/hi16-action-emoticon.png b/kopete/icons/hi16-action-emoticon.png new file mode 100644 index 00000000..05c8fb35 Binary files /dev/null and b/kopete/icons/hi16-action-emoticon.png differ diff --git a/kopete/icons/hi16-action-kopeteavailable.png b/kopete/icons/hi16-action-kopeteavailable.png new file mode 100644 index 00000000..25d3dc9c Binary files /dev/null and b/kopete/icons/hi16-action-kopeteavailable.png differ diff --git a/kopete/icons/hi16-action-kopeteaway.png b/kopete/icons/hi16-action-kopeteaway.png new file mode 100644 index 00000000..10227908 Binary files /dev/null and b/kopete/icons/hi16-action-kopeteaway.png differ diff --git a/kopete/icons/hi16-action-newmsg.png b/kopete/icons/hi16-action-newmsg.png new file mode 100644 index 00000000..29f597a3 Binary files /dev/null and b/kopete/icons/hi16-action-newmsg.png differ diff --git a/kopete/icons/hi16-action-status_unknown.png b/kopete/icons/hi16-action-status_unknown.png new file mode 100644 index 00000000..63e6eb17 Binary files /dev/null and b/kopete/icons/hi16-action-status_unknown.png differ diff --git a/kopete/icons/hi16-action-status_unknown_overlay.png b/kopete/icons/hi16-action-status_unknown_overlay.png new file mode 100644 index 00000000..b7e6d778 Binary files /dev/null and b/kopete/icons/hi16-action-status_unknown_overlay.png differ diff --git a/kopete/icons/hi22-action-kopeteavailable.png b/kopete/icons/hi22-action-kopeteavailable.png new file mode 100644 index 00000000..16dcc8f1 Binary files /dev/null and b/kopete/icons/hi22-action-kopeteavailable.png differ diff --git a/kopete/icons/hi22-action-kopeteaway.png b/kopete/icons/hi22-action-kopeteaway.png new file mode 100644 index 00000000..f49afcc9 Binary files /dev/null and b/kopete/icons/hi22-action-kopeteaway.png differ diff --git a/kopete/icons/hi32-action-kopeteavailable.png b/kopete/icons/hi32-action-kopeteavailable.png new file mode 100644 index 00000000..80969297 Binary files /dev/null and b/kopete/icons/hi32-action-kopeteavailable.png differ diff --git a/kopete/icons/hi32-action-kopeteaway.png b/kopete/icons/hi32-action-kopeteaway.png new file mode 100644 index 00000000..ede9cada Binary files /dev/null and b/kopete/icons/hi32-action-kopeteaway.png differ diff --git a/kopete/icons/hi32-action-newmessage.mng b/kopete/icons/hi32-action-newmessage.mng new file mode 100644 index 00000000..a3fbc8f8 Binary files /dev/null and b/kopete/icons/hi32-action-newmessage.mng differ diff --git a/kopete/icons/hi48-action-kopeteavailable.png b/kopete/icons/hi48-action-kopeteavailable.png new file mode 100644 index 00000000..52fdad8f Binary files /dev/null and b/kopete/icons/hi48-action-kopeteavailable.png differ diff --git a/kopete/icons/hi48-action-kopeteaway.png b/kopete/icons/hi48-action-kopeteaway.png new file mode 100644 index 00000000..b35a47de Binary files /dev/null and b/kopete/icons/hi48-action-kopeteaway.png differ diff --git a/kopete/kopete.api b/kopete/kopete.api new file mode 100644 index 00000000..95a196aa --- /dev/null +++ b/kopete/kopete.api @@ -0,0 +1,171 @@ +# Doxygen configuration generated by Doxywizard version 0.1 +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = Kopete +PROJECT_NUMBER = 0.10.90 +OUTPUT_DIRECTORY = apidocs +OUTPUT_LANGUAGE = English +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ALWAYS_DETAILED_SEC = YES +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = +INTERNAL_DOCS = NO +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +VERBATIM_HEADERS = YES +SHOW_INCLUDE_FILES = YES +JAVADOC_AUTOBRIEF = NO +INHERIT_DOCS = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +DISTRIBUTE_GROUP_DOC = NO +TAB_SIZE = 8 +ENABLED_SECTIONS = +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = NO +GENERATE_DEPRECATEDLIST= YES +ALIASES = +MAX_INITIALIZER_LINES = 30 +OPTIMIZE_OUTPUT_FOR_C = NO +SHOW_USED_FILES = YES +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_FORMAT = +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = libkopete libkopete/ui +FILE_PATTERNS = *.h +RECURSIVE = NO +EXCLUDE_PATTERNS = *.moc.* \ + moc* \ + *.all_cpp.* \ + *unload.* \ + */test/* \ + */tests/* +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 3 +IGNORE_PREFIX = Kopete +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_HEADER = apidocs/common/header.html +HTML_FOOTER = apidocs/common/footer.html +HTML_STYLESHEET = apidocs/common/doxygen.css +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = YES +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = kopete.tag +ALLEXTERNALS = NO +EXTERNAL_GROUPS = NO +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +HIDE_UNDOC_RELATIONS = NO +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = NO +UML_LOOK = NO +TEMPLATE_RELATIONS = YES +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +MAX_DOT_GRAPH_DEPTH = 0 +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/kopete/kopete/Makefile.am b/kopete/kopete/Makefile.am new file mode 100644 index 00000000..eb278998 --- /dev/null +++ b/kopete/kopete/Makefile.am @@ -0,0 +1,54 @@ +kopete_srcdir=$(top_srcdir)/kopete +kopete_builddir=$(top_builddir)/kopete + +INCLUDES = -I$(kopete_srcdir)/kopete + +AM_CPPFLAGS = $(KOPETE_INCLUDES) \ + -I$(kopete_srcdir)/kopete/config \ + -I$(kopete_srcdir)/kopete/addaccountwizard \ + -I$(kopete_srcdir)/kopete/addcontactwizard \ + -I$(kopete_srcdir)/kopete/config/accounts \ + -I$(kopete_builddir)/kopete/addcontactwizard \ + -I$(kopete_builddir)/libkopete/ \ + -I$(kopete_srcdir)/libkopete/ui \ + -I$(kopete_srcdir)/kopete/contactlist \ + -I$(kopete_builddir)/kopete/contactlist \ + -I$(kopete_srcdir)/kopete/chatwindow \ + -I$(kopete_srcdir)/kopete/config/plugins \ + -I$(kopete_builddir)/kopete/config \ + -I$(kopete_srcdir)/libkopete/private \ + -I$(kopete_srcdir)/libkopete/avdevice \ + -I$(kopete_srcdir) \ + $(all_includes) +KDE_ICON = AUTO +METASOURCES = AUTO + +SUBDIRS = addaccountwizard addcontactwizard contactlist chatwindow config . kconf_update + +bin_PROGRAMS = kopete +kopete_SOURCES = main.cpp kopeteapplication.cpp kopeteiface.cpp \ + kopeteiface.skel systemtray.cpp kopeteballoon.cpp kopetewindow.cpp \ + kopeteaccountstatusbaricon.cpp kimifaceimpl.cpp kimiface.skel kopeteeditglobalidentitywidget.cpp \ + groupkabcselectorwidget.ui + +kimiface_DIR = $(kde_includes) + +kopete_LDFLAGS = -no-undefined $(all_libraries) $(KDE_RPATH) -lktexteditor +kopete_LDADD = \ + addcontactwizard/libkopeteaddcontactwizard.la \ + addaccountwizard/libkopeteaddaccountwizard.la \ + contactlist/libkopetecontactlist.la \ + config/plugins/libkopetepluginconfig.la \ + ../libkopete/libkopete.la \ + ../libkopete/ui/libkopeteui.la + +mydatadir = $(kde_datadir)/kopete +mydata_DATA = kopeteui.rc eventsrc + +mimedir = $(kde_mimedir)/application +mime_DATA = x-kopete-emoticons.desktop + +xdg_apps_DATA = kopete.desktop + +# vim: set noet: +noinst_HEADERS = kimifaceimpl.h diff --git a/kopete/kopete/addaccountwizard/Makefile.am b/kopete/kopete/addaccountwizard/Makefile.am new file mode 100644 index 00000000..cb491b46 --- /dev/null +++ b/kopete/kopete/addaccountwizard/Makefile.am @@ -0,0 +1,12 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +noinst_LTLIBRARIES = libkopeteaddaccountwizard.la +noinst_HEADERS = addaccountwizard.h + +libkopeteaddaccountwizard_la_SOURCES = addaccountwizardpage1.ui addaccountwizardpage2.ui addaccountwizardpage3.ui addaccountwizard.cpp +libkopeteaddaccountwizard_la_LDFLAGS = $(all_libraries) -no-undefined +libkopeteaddaccountwizard_la_LIBADD = ../../libkopete/libkopete.la $(LIB_KDEUI) + +# vim: set noet: + diff --git a/kopete/kopete/addaccountwizard/addaccountwizard.cpp b/kopete/kopete/addaccountwizard/addaccountwizard.cpp new file mode 100644 index 00000000..5518c536 --- /dev/null +++ b/kopete/kopete/addaccountwizard/addaccountwizard.cpp @@ -0,0 +1,222 @@ +/* + addaccountwizard.cpp - Kopete Add Account Wizard + + Copyright (c) 2003-2004 by Olivier Goffart + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "addaccountwizard.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "addaccountwizardpage1.h" +#include "addaccountwizardpage2.h" +#include "editaccountwidget.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopeteprotocol.h" +#include "kopetepluginmanager.h" + +AddAccountWizard::AddAccountWizard( QWidget *parent, const char *name, bool modal, bool firstRun ) + : + KWizard(parent, name, modal, WDestructiveClose), + m_accountPage(0), + m_proto(0) +{ + // setup the select service page + m_selectService = new AddAccountWizardPage1(this); + if ( firstRun ) + m_selectService->m_header->setText( i18n( "1st message shown to users on first run of Kopete. Please keep the formatting.", "

    Welcome to Kopete

    Which messaging service do you want to connect to?

    ") ); + addPage(m_selectService, m_selectService->caption()); + setNextEnabled(m_selectService, false); + + // setup the final page + m_finish = new AddAccountWizardPage2(this); + if ( firstRun ) + m_finish->m_header->setText( i18n( "2nd message shown to users on first run of Kopete. Please keep the formatting.", "

    Congratulations

    You have finished configuring the account. You can add more accounts with Settings->Configure. Please click the \"Finish\" button.

    ") ); + addPage(m_finish, m_finish->caption()); + setFinishEnabled(m_finish, true); + + // add the available messanger services to the dialogs list + QValueList protocols = Kopete::PluginManager::self()->availablePlugins("Protocols"); + for (QValueList::Iterator it = protocols.begin(); it != protocols.end(); ++it) + { + QListViewItem *pluginItem = new QListViewItem(m_selectService->protocolListView); + pluginItem->setPixmap(0, SmallIcon((*it)->icon())); + pluginItem->setText(0, (*it)->name()); + pluginItem->setText(1, (*it)->comment()); + + m_protocolItems.insert(pluginItem, *it); + } + + // focus the ListView and select the first item + QListView &protocol_list = *m_selectService->protocolListView; + protocol_list.setFocus(); + if (protocol_list.childCount() > 0) + { + protocol_list.setSelected(protocol_list.firstChild(), true); + } + + // hook up the user input + connect(m_selectService->protocolListView, SIGNAL(clicked(QListViewItem *)), + this, SLOT(slotProtocolListClicked(QListViewItem *))); + connect(m_selectService->protocolListView, SIGNAL(selectionChanged(QListViewItem *)), + this, SLOT( slotProtocolListClicked(QListViewItem *))); + connect(m_selectService->protocolListView, SIGNAL(doubleClicked(QListViewItem *)), + this, SLOT(slotProtocolListDoubleClicked(QListViewItem *))); +} + +void AddAccountWizard::slotProtocolListClicked( QListViewItem * ) +{ + // Make sure a protocol is selected before allowing the user to continue + setNextEnabled(m_selectService, m_selectService->protocolListView->selectedItem() != 0); +} + +void AddAccountWizard::slotProtocolListDoubleClicked( QListViewItem *lvi ) +{ + // proceed to the next wizard page if we double click a protocol + next(); +} + +void AddAccountWizard::back() +{ + if (currentPage() == dynamic_cast(m_accountPage)) + { + // Deletes the accountPage, KWizard does not like deleting pages + // using different pointers, it only seems to watch its own pointer + delete currentPage(); + + m_accountPage = 0; + m_proto = 0; + + // removePage() already goes back to previous page, no back() needed + } + else + { + KWizard::back(); + } +} + +void AddAccountWizard::next() +{ + if ( currentPage() == m_selectService && + ( m_selectService->protocolListView->selectedItem() ) ) + { + QListViewItem *lvi = m_selectService->protocolListView->selectedItem(); + + m_proto = dynamic_cast(Kopete::PluginManager::self()->loadPlugin(m_protocolItems[lvi]->pluginName())); + if (!m_proto) + { + KMessageBox::queuedMessageBox(this, KMessageBox::Error, + i18n("Cannot load the %1 protocol plugin.").arg(m_protocolItems[lvi]->name()), + i18n("Error While Adding Account")); + return; + } + + m_accountPage = m_proto->createEditAccountWidget(0, this); + if (!m_accountPage) + { + KMessageBox::queuedMessageBox(this, KMessageBox::Error, + i18n("This protocol does not currently support adding accounts."), + i18n("Error While Adding Account")); + return; + } + + insertPage(dynamic_cast(m_accountPage), i18n("Step Two: Account Information"), indexOf(m_finish)); + KWizard::next(); + } + else if (currentPage() == dynamic_cast(m_accountPage)) + { + // check the data of the page is valid + if (!m_accountPage->validateData()) + { + return; + } + + QColor col = Kopete::AccountManager::self()->guessColor(m_proto); + + m_finish->mColorButton->setColor(col); + m_finish->mUseColor->setChecked(col.isValid()); + KWizard::next(); + } + else + { + kdDebug(14100) << k_funcinfo << "Next pressed on misc page" << endl; + KWizard::next(); + } + + // if it's the finish page, focus the finish button + if (currentPage() == m_finish) + { + finishButton()->setFocus(); + } +} + +void AddAccountWizard::accept() +{ + // registeredAccount shouldn't probably be called here. Anyway, if the account is already registered, + // it won't be registered twice + Kopete::AccountManager *manager = Kopete::AccountManager::self(); + Kopete::Account *account = manager->registerAccount(m_accountPage->apply()); + + // if the account wasn't created correctly then leave + if (!account) + { + return; + } + + // Make sure the protocol is correctly enabled. This is not really needed, but still good + const QString PROTO_NAME = m_proto->pluginId().remove("Protocol").lower(); + Kopete::PluginManager::self()->setPluginEnabled(PROTO_NAME , true); + + // setup the custom colour + if (m_finish->mUseColor->isChecked()) + { + account->setColor(m_finish->mColorButton->color()); + } + + // connect if neccessary + if (m_finish->mConnectNow->isChecked()) + { + account->connect(); + } + + KWizard::accept(); +} + +void AddAccountWizard::reject() +{ + // if we have a protocol plugin loaded and its not being used, unload it + if (m_proto && Kopete::AccountManager::self()->accounts(m_proto).isEmpty()) + { + const QString PROTO_NAME = m_proto->pluginId().remove("Protocol").lower(); + Kopete::PluginManager::self()->unloadPlugin(PROTO_NAME); + } + + KWizard::reject(); +} + +#include "addaccountwizard.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/addaccountwizard/addaccountwizard.h b/kopete/kopete/addaccountwizard/addaccountwizard.h new file mode 100644 index 00000000..f4d204ec --- /dev/null +++ b/kopete/kopete/addaccountwizard/addaccountwizard.h @@ -0,0 +1,70 @@ +/* + addaccountwizard.h - Kopete Add Account Wizard + + Copyright (c) 2003 by Olivier Goffart + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef ADDACCOUNTWIZARD_H +#define ADDACCOUNTWIZARD_H + +#include + +#include + +class QListViewItem; + +class KPluginInfo; + +namespace Kopete +{ +class Protocol; +} + +class AddAccountWizardPage1; +class AddAccountWizardPage2; +class KopeteEditAccountWidget; + +/** + * @author Olivier Goffart + */ +class AddAccountWizard : public KWizard +{ + Q_OBJECT + +public: + AddAccountWizard( QWidget *parent = 0, const char *name = 0 , bool modal = false, bool firstRun = false ); + +private slots: + void slotProtocolListClicked( QListViewItem *item ); + void slotProtocolListDoubleClicked( QListViewItem *lvi ); + +protected slots: + virtual void back(); + virtual void next(); + virtual void accept(); + virtual void reject(); + +private: + QMap m_protocolItems; + KopeteEditAccountWidget *m_accountPage; + AddAccountWizardPage1 *m_selectService; + AddAccountWizardPage2 *m_finish; + Kopete::Protocol *m_proto; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/addaccountwizard/addaccountwizardpage1.ui b/kopete/kopete/addaccountwizard/addaccountwizardpage1.ui new file mode 100644 index 00000000..0e4dae7d --- /dev/null +++ b/kopete/kopete/addaccountwizard/addaccountwizardpage1.ui @@ -0,0 +1,144 @@ + +AddAccountWizardPage1 + + + AddAccountWizardPage1 + + + + 0 + 0 + 652 + 464 + + + + + 0 + 0 + + + + Step One: Select Messaging Service + + + + unnamed + + + 0 + + + + layout8 + + + + unnamed + + + + PixmapLabel1 + + + image0 + + + false + + + + + spacer18 + + + Vertical + + + Expanding + + + + 21 + 70 + + + + + + + + + Name + + + true + + + true + + + + + Description + + + true + + + true + + + + protocolListView + + + + 400 + 300 + + + + true + + + true + + + + + m_header + + + + 5 + 5 + 0 + 0 + + + + <h2>Welcome to the Add Account Wizard</h2> +<p>Select the messaging service from the list below.</p> + + + WordBreak|AlignTop|AlignLeft + + + + + + + + + 789ce57d5973ea3ad7e6fdfb2b4e1dddbdd5e58f84904075f50564820009848c74f5856cc90364200364f8aaff7b2f692d19039e0067effd9e2e4a47fb3922c65e7af4ac257959feaf7fff75d7ebfef5effffad7db3b7f0f9cbf1c9fbffef56f317d7cfcfadfffe77ffdf7bffeded9dbfd6bb756faeba05cfe6bf7effff1afbfadc7bf9cbfd86e696fafc2357ed0b8b65722cc08537bffdde07dfc7b4eb84ad825ec10b6152eef84ed4e88f1f892b0f9be0831b67b882b25c2cf061f94351e11ae50fbd8606c77f5f997e1fc2afaf8fc21c4fafb421076103b55c415ba7eb663309ddf5388f1f77c83e97c3e099bf3090cc676764bb846ed1383e9ef5f426c6b7c4c5850fb7b8875bb4dc7dfdf411cbc2abcb7b357dadbd1d7ff4598ae5f5e1176c81e2cc4fa7c383718af5758888d3d9ca1c1d81edc13ae507bd960b2d75188d1de2383e97ae8fc8c3dd8bbc1d4fe1a62b4c72561638fb710633b9d1fd803db4b21d6edecc6e0aa6eb77dc215b2e7bdc1d82e0cae21f6f1787c6f6f4f9faf7742b88a583c198cf6f5f7083bd8ce760d26fb7f8658b7bb75c4606fb4ff6788f1f83d83d1be927edfd85fdc194cfd771062dd2e25e11af5cf34c4badd367f4ff6e7e6ef05f1b56230b63b7e88917fa788f777a83f1f0dc6efcbd710637f74081b3edc8758b73b2d83d1fed63e61c3976b83f1efc5b3c1d47f5f21d6c79374bdfb02b14fd773b08338e006d7f63476095710fb9a6f95dd72a954d2e7778038e403b59bfef566884d7fbaa721c6f1726d30f687f74db842df9f8518bfff6630f5e70b61eacf910831f6d70761eabfd1a3c164af3162d35fd689c1d41f03c246bfba21c6fe730893fd837188d1befb06637fb007c2f4fb2337c4687fcf60fc7ef014626ca7bf37fde5ef1a4cfd43fd61fa2b102176341e11ae51bb1f626c7f272c10fb5aff2adcf4b7b747780fb1ff45b84af831c4556d5fb467a8170e1d2fd45bfabee187d33498f830214cfdef39846ba4176d83f1fbee116181ed72cf606ce7e310633bf24d8d57d4f3bb10ebf6e0c660ea2ffafd7da3e7cc60eaff9310a31eef10267eb0af10637f3e1a8cfdcd2f09131ffca1c1d44ed77760fc5dc5606c1ff11063bbf97e05f188aef780f420f83298f8108418f9b04f5850fb7388b1fdcd608eed0dc4d51d6a9f194ced17842b887d7d7efbbb217fdc106bfef867841dc20c31d04f63e7de60f4f7de98f01e62e18518fbeb9630f92bd6430c7c42fe9409135fc42b62c30ff725c4c83ffabd7de36ff6438c7a32234cfdef5c8458dbdff3091b7fde0931eacb14b1e96ffe6630e9c34788916f5784e9f759c960f2dfe510231fde0dc6feb5ee08937e066d83a9ffc95e211f5a21c6fea6f30ffbffd060eaff2661eaffe03cc4fafb239b708dda6f438ced166141f8cd605b7f7ff489b8b683d8a969cccb4eb9a631fe1e377cf19f0813df3cfafe9eb383fcfa3298fc19da07f46857f3959f1a8c7c728788437da813267bba5706537c4ee7b36ff4283098f8d0451c8ef74fc2c6ff3c194c7c9810367ae28718bf7f48988eefdf1b4c7ad0475c257f327a0831f667c960ea1fba9e2af997119d8fe9bf911762fcfb03c2d47f41d960ecaf80ec67fa6f340db1d0ed7b842bf4fddd10ebf6d18c700db1a37fffa06cf4c07f475c29eda0de5c8418fbff9e7005dbbdb2c1d4dfb6c1783c59456cfa4b4c111f90bfb00d367c681b4cf1e06e88b5fddd5bc2a40fce8dc1143f1c11267fc0bc1063ffd51157491ff87188b1dd3198c6ef35611affa391c1d47f92b0f10774bda1bdc784c9dea30f839d8ac6743e3541ed5f0663bb83d76f97ab656d4fe788308d571bed6187fa5e415c11d85fac6930b6db6788f74dbce011a6f1e53e2036f616a784c9de828e7740facb3f0c267b8b10a3ded2df1b7b3b7b21c6f1563598ec4de75725fd0d060693fe5e1336f391a1c114df55438ce38d1126ff2b74ff56cbe53de4ab731562d4bf57c2646ffe85b852417bfb1ee2fd1db2ef04f1c10ef947df60b2678930e9993498ec252e111bfb04738cf3911bc2a45f6c1662b4cfb1c178fdd2266cf4ecce606ce7747d55ea6f7b6430b6b3d3106bfbf10ee21a5d8f13184cf6f50d26fbe2f9d865d20be7ce60b2f73e6163ffcf10239f6b84ab88bd27c49512f98f6fc4fb3bc85fff8ab0c0fef2e9f70f76f0efc5036133ff7b435c257fe13d1336f3f9ab1023dffb84c99ed6aec1682f41e75325be3b3d83c99e74fc1af1db7b08b1b61fb3099bf590db10637cd13598e61b3b06937f217bd5c89f08dd3fb5bdd0fe8706a3bded2ae13d6a7f3198fac30d31f6c70de12ab6bb32c4badd3d21ec507fbd22de2b617f786784f710bb1f8449bf3cd760d2af4fc4a6bf5999708d8ed73798f8503198e28f63c4861fee2e62d3ffde25e22af90bef2bc4d8df25c2d47fa28bd8f41f3b0fb1ee0fd1206cf4e93bc4e80fa60663ff5864cf1acdc7b817629cef550dc6bfb7fa06e3dfb337c2147f073b06131f6488912f35c47c07b1a3f5b4e698feb6f70957910ff681c1d82ead10a33e4e089bf84f1a4cdf7f0bb1febe6c198cfd278e10ef113fa56730f183ce2fe4cb28c478bc2fc255e24f8fb0f93d3aff4a09dbdd5783915ffc89708df88be3c5d9df217e71c2a48f1ef6bf63f4dca7f6ea0e5eaf4fbf5fa578c7ad194cf3893661817cf5070693fedc1a8cfc73a93f42bdb00913bfc44b88b17f9b848d1e55438cfa6d194cfc1913a6ebf35f0da6bf1f8418f9776e30febdc3428cfa45f6e634ffe3f706e3f7ed5988917f6784cd7ce6d5609a8f687de07b864f7605b1e18b7d6d30b50f081b3d7b3698f48a1b4cf15383b0e1fb95c1f87db14798f4490e0da6fe75101b3ec90961d3ff3dc2a44fbc4958903fda451cea139d8f89c7fc6fc4b51d8a37ce0d463eb99f84cd7a638b30f1c7ab1a4cfd593698fac343cc8dffec1a4cfa766c30e9c97788757f499730e919932146be1c10e6c807bb6430cd676c8369fe42f6e392fa5ff3853ba6ffec3e61d35f970693bf12848d1e3d194c7f8ffde984f39716619aafc863c2a6bf1cc282da0706a37f61743efb145f48c760ba7f42e7bf4feb6fb24998e26f7e1e626cbf3718edc5d0dece418dfa7f8ab866f46948b842f1ce09613a3f798a98937f72ef0dc6fef276091b7d9221c6f5b40f83893f1d83c99f3408738a972606633b7f3718fb9399df97a4171706633b1f21b67769beca428cf3533d3fb2f7c2febc406cfad3ee194ceddf842bd45e3798da1b846bd82e1e119bfe7477080b1aff6d83e97eca3ee203b3de7519621ccf7b88abe4dffd67c49cfacfef10e6148fdc184ce3fbd160b2974f58d2787d3098faefd0608aff8606937d6b888d7d6537c4dabebc4f789faeaf61307edf3a0cb1febe45d76773c4b6d6175b84f63d274cf6751c83a9fd8930cd4fedaec1d8cef0f8c2ccaff8b7c1f4f75f84a97fbd23c2e6f88784496ff98dc1687fd6426ce607f2c8608a37a684c93fcb3162335f9075c2343f967d83e97ee13b62be43e3fbd060d2eb6bc2d4fffe29627b97d623ba0663ff8b196163ff2661b23f1f1a8ceb05e28cb0a4f61383b13db0103bbbb4bea07f1fe4d0d8ff12b1b1bffd6830e9e13361b23f170663bb27099bfeef1a4c7a6d8718fbc71c8f7edfb6438cfe32c4a47f1f880f0c1f3a0663bbbc234cfa289f428cf39923c2f4fbf2d260f2df17884d3c26de119bf9af7b6330cdcf1961d24f796d30cdc7cddf537ce6d2f9193e78fb8425e9fd0b627b9fe2c303c29cd6333961d203f66930f6276b207676e9fed1bec1a4479f06139fda06d37a5410626c2f13dea7e30f43acdb5decbf707cc90ae2038a5f789d30f1c3b93418db99f9bee1c7a3c1e42f2f08131f1cf37db39ef510628cd7a7884d7cce5b21467def188cdfb7e9fcab153afe89c1e84fed63c234ffb4770da6fbf3738cc733bf47fec3ed1b4ceb6d4dc4c69fbb2d83295e32ed86bf658369bd86ceaf66e6c31706137fcf101bff2f0d36fca4ef1b7fecfb88ed5de21ff597cd697e30232c49affa0653bc80e31ff484be7f6b30e9cf8030f1c51911e688472f060be4974b58623bab198cedb68558ec22167a3c8a8ae92f5e266cc6f7a7c1942f7118621ccf55c2c42fdb3198e6ab2e62d33fce2d61b35e5d236cd6233f0da6f5ed53c2c407716130dddfb80e31e69770c4263e730c36f1c15388b13f7dc2d49f9c8e67ef92deb821c67850188cdf77df0893bf1715c2e43f5c0bb1e94fff9230c577eea1c1d85ffe1d618a47f8d460ec2f3e436cfacfaa13de47ecb4432c353f9e0973c41ced2f0dffb9446ce22b7b4298f445f60c26fbbd13267bda8230e9af1c194cfa4dbf67ec297608efa33d9d6fc21cafd73d3618ed257cc2640fb78bd8e82d7b21bc4ff7ff1f0ca6fef10cc6ef7bf4fb0e8d4fd6262c299f686230e9fd38c4c857e48734f6652784c9becebbc1725ff7cf056149ed2583b19de9f10d07377a3f214cf6f41862a32fde21e17dd2f706614efef9c560d2f7b6c1b4ded63298e69f6784cdef750cc6bf77760cc6efcb1a6287e2617b4c98f8eff40913bf050f31f2db264cc7171f06d37809dbb1bf64d88ee343760c46be7b25c46297d6131e0dc6bf779b06933e8e08ef93deb5438cfee5da60ca0fb93298f4e2dc603a1f61308dc72a614efeefc3606c970f21d67c90645fc30f1e188cfcf0881f7297da4b0613bf7a84f711072f21c6efebf98094862fde3b61b2bf87f680f144b84e98becff609139fdc03c482f4cfde3118edcde718fdd53161b2971f10e6b49ee7184cf9575383d15e3e9d9fa0f129a4c1680f7762305ebf7f83589af8bc6330d99b1b8cdfb71961b21ffb08b1febef74298ce875f194cf66f85587fdf6a1096647fade7eebeb11fab8718fd9d242c69fda48218f88ced6f8449dfd81561f3f7aec1c4b757c2743c16184ceb2fd42ee9f8bc6730b55b84e9f7bc73c2a4175e3bc4baffbd538371bc7b8d10ebfef6ccf1687ee50521467d0f8f4ff6168469bc7b8f06937f1b1b4cf63e33d8d5fdc1d11eaeb10f7f216cec8bdf77c3eb3f3498ae9f11a6ebb76a06637bffdd6271c5e2f05f6e6a28764c6dc337928a43b58022a98e2f3614178a5c2a22520bcbcb55e61f3fa63625c82c2328636daf07b8cec7242bcdad654ad46a11eb71b21627ab2cd7a6c8258bc55bce8d2972a1466bc8dcd68afb284b8dc822e3382badf267e9ea633993c21b27526773862f5df53267e4dabc79da8a3386379a3baad6fcc9c99b04cee0ff4be78ce14d3a67e2782397ea2867d2b893fc79a63aca9958eeccf9a35953acdea473a658bd89e3ccd346bc19476a558264fe6cac3771bc594f6f56392372712699378633997ab330c246649f4ccefc32bd59df4f6de7a3bc25cea83a30dc09fdd7cfe84d3667d6d59bcdfc94b15c3a6762b813f2278e333c436bb6e3cca67a93eca30ad29b857a62f4e71feda3d6d29b65aba1fe6ce0a39cb57cd4b67a931d136fc69915bd519c8916c39f141fc56338537c4c2cac6d63e2f57c541c6f5647dc24d49f5f1d136fab37ebf9a974ce048b9c59e6cf1fa537c5f9a88df486ea891969c89f1c7af30f8a89d3f4264d7ffed131714ebd897267ae3f5be94d968f5a7f0efec331719adee8f212d693507ffe61eb366b702658e64cb40ef9f3bbd76d7e0d6732f54671467f5eb0d0fcf43fd547e5e74d8a8f4aac0d7ff2fba83f2dae491a316bf928d29bc9327722fcc9e3a336e14c3e1f953d8f5a7bdd268133b13e6a65e4cd3f2fda3edbfba8fcbc89e3ccfa7a933d8fcae7a3623993c19f227c543ebdc9f651f9f4664b1f15c39bd072da3eff241f954f6f523963bd52fd16f2e71fb76eb3e0a3d6d09be53ac29f7ff43c2aafde28ce58eff33ac29fcde7517fe0bacda67ab364ad77e2cf7faede64f126afde443813ad893f7f928f8ae74e1667b262e26cce442df86e0af1a7889838596f36bb07bead8fda486f748de525963fff1fc4c4697ab35c883f7f4cbecd567a1359b7595b6f229c89d4ef217ffef3d66d8af251b1bc31968bf0e78fc8b749e0ccf67a93e2a39639132dda3e7f50becdc63e6a6acdac0feb730dbd49e44cb426fe6cefa3a2dcd92c26cebc9799c0992fb0ccb755b71ad6a175b4898fa2fa78993b217ffe43ef2dc0e7c43ab59a56cb3a03eb9c596dab43ff3f8e33510ba57246d7c750bf93fefce7c5c4ca325debdcba20cb60e9597d1861ab7af3665d5a03ebcabab66eac5bebcebab78656295e6f12f5e74f5c278e5bb799c0b5ee58bb5679c132a6ec59951885deb70eacaa5563161466d5ac2ae3cc660e58e96d853bef9191769cc99f641ff5ebe7e0ea0a064c3009e369d532ba3097a95f5abce24be6b3802c538bd42336660fc0a564fe94427dfebdeb36d97a73c91ed9137b06156ec55b864a0bacf7b5a8ce6cc25ee6dc59a85fc1466f60a3e3b9de2cab74027ffe989898bd5b15366533abc93e522d43857db2afd067a9f2c1be597d853b919a35d8a11e690bbc81f216f2e78f8c89991a195fec881d6bdf9d69191a6127ec14349afc127b674dd6626731dc89d66deb9a75623d7c69893fd9317151f936297a0357f5ceba309ecec163276a4da2852ed82372078ed2637d76c906ec2a9e3b218794165d136f42eeb09b707ef1c7c4c4ec169466caeed83df8ee352d43236cc84a70a44fb6c3765999ed817d2a6c9fbda673087cda81e6507484cdf9f367f8a80a8ca7aa75ce6a192a9c5e7adce28c73283677b860975c3d99ee267167ce21ee59f78a33863b0bfeeb77c6c49f30121eb90ffea9c9832d2c43858ff8983ff047fe04dc51fcd9e3cf7cc25f12f527d421fe0a3629adf0e737c6c4ca3ff13776c7dff9747bcb60e133fec13ff917ff56dca152e70d7e98c921ce8f903b544a217fb27d54be98789d75e22ff6c58fad737e02bebb10cb5069b1637eca9bc89d90432db0d0593a87789b7762f8b3998fca5eb749d41b5061f04f569f8fd849a1963123accbcfc12222c21f552e6094a5ea10eff1bed6a09229a17db6d59bbcebc49f309e2ed929937cb0950aa759e78a5ff39b2877223a74cbef527d59438db018fdf90531b18e6aaafc9e0f79e9672c43fab3c37757b813165e563be3257188efb3871cfc293abfef931ff0375ee535dbfa49cba862339bdbf62a77e6853fdb8eda9d8a9f2d73c8766dcff69542db81f264ec46dbe7677d14a8b03de2ef9bc4c29b147b6c3f2471078bfd683f71613fdb13fbc596f6abe18efd66bfdbd3397796f85374be0dc4b0d609f7ed9975082afc435ab3629d0ffbd3fe4ae68e2af6137cbeedbaddb00fed23fbd83eb14feda6dd62dff699f2ea863b7690c99f4d7d945661f04f3556cb37ef2e4a99edb6dd49e30e943dbb6b9f83752eec9eddb72fed01942bfbdabeb16fedbb2877ecfb903f9baddbc4c53523ebcb1ad843bb04e3e9e2d75986b467c7de4de70e58e7097853b6f7c03eca3a15b0ccbe7d6057ed9a632167a2232c813f1bcda3742cec3b8c9ff0d9afd19a25eb30873b7606771eed73c781d1d57384e6ceb5231d69571dd7f11c1f3983056c35e7cff67a032aec04d6a13302dffd8bb466b13863e7c1794ce78ed69e73b0ce85239c277be03c3b136d9d17e7d5795be68eb2d2127f36f1514a858fd9051b3aefbfc732589ca9fd09d14d86df02ed51ca7c01d6e93b33bbe27ca8d1e57c3a5fabdc5175447fd69f477d5a53ab032aacc653eff7594615185bdf10f5e4f15bdff69e53771ace73689d43e788bd2f2b3316e2cfda7a035a73c2eeac433e2d6245a280d175ec9c64f8ad4be7546b8ff15ba0ccce04acf302cadc8c7246d54e4bd509fcc99a474dd99373c66ace2f8af87258a76373a79ba93d0dd09e8b88324f94df02ed39c77827ca9b25fee4d59b23e7c2e9f181d32e784562cbe2f49dcbec9859fbad7a44993f803d03e7cab98e7287dd18ee382db24f1ebdf1f8817363edf1198ca7dfa8c271c5fe706e73faadfa82dfda07ebdc39f78ebadb1cc31d65a5087f92f26d3ead236b669df399f39b3c77867598b3e374d3b547fb2d1d333bf5302a3c50dae3ec72e694adcb2867d447d5217f92f5e6d3fa70f6ac323bf9d915896d0a287365d398d9d9770e9ca1530506c47027a23fab3e0a223eab0b51cd1f6c1955d46a46e67c6b296686d125292aac394361818586ca16cbdc59e10f7187ed08e6f4584d47c2bfdd02a9dc19434098bad69316330b877f0ae10c61840dadaf450f6ffb682b6d9fb9dea8f174c387ce1f6f192c420a37db6f25c4cc87c243ee280e5947a83dcb25e48f076370c73ad719127fa00ac7159b093f07774cccfc043173681d116865d6a38b46d89be2cef247d9478cc498cdacf27f0667a8b4acb278108f5931b3f25b919879606266185d4f863bba2e5b5fcbdc11cf649f0928cdefbfe27c45b1fb42bc8857f126deb3fc564acc5c9b73070bfab0b94263adc7d7efbfea9c9c817e6c8a297c66a03d1f196b3da931b3f88c70c77cfc396fc4d79c3f7fc095e7b00c9f8a6f51170de4bad31187ebc6cce2086366712c4e16b983c53a5ae48eb2d01fcf1fb00c3b1113712a9a913b8a2dd14ae74e749d7929663e748ee6aa1cfd5853ab14e58eaaff60fe34b465ce80356dd1598c529da9ddd93c6656ca1cc71fb0d02572475be7f90fe64f8f9d305774445374996b2d65d7897371b145cc7c2e7a4bcc299b621de951f5352f7f1c7f5a3ce03350e1beb8e4b578bfeaf4950532b4e7dcc4cc641db3ce7c0531730c77421ff636e7ce9fc61ff60196698b81b882729d14a5425478933b66de8bc6cce0b75ec4ad8977568bb883115631dc11aa3cff21fc6959753e10435102b5394b5dcb6e899d3563e667a33d30a3d8e59f49dc218dd63e6caed07f007f2e9c118ca7328ca8093bc9fabe52e6cc98f9313e66167bea1d24ab9c59a80fac4bc31d55ff56fef420a63903dffd2daa3967372d51cba13db131b3b424935cdae26e993391ba4c23cc78f9dfc59f16f8a74f311355d14d52e1b8628f378f99c17d0995fd2b652277ee8c0f43eefc1efd016bb4c54cbaa221caa0c76bfcad90cec9a631b3f4a46f33d5333288e14e19b9837538c27e397f603ccdd83d78a72afb5c3bafa32547e9f7475362e61739960f64e5b2e2493c77a23e4c73e897f10794b7c9eed931ccbc73a8705c918ff2493e6f1633cb0928f3391deb42be44f566ce9db094e5eb2fe44fcf1981e79e88013b8619d4a6a3f2cab975ba9be566c837f92e79e4581fc9dcd1057dd8b39c927d7eca322d71cd6bc09763b0cc1a2a1c5784bfce3af362cc2c67f26321da6c8acf44eee8624d6dff67f9c34bca73c378ea8889ce06daea78ce54e6cecd50ca3c5f490565fe22659e1faf25bf13b983458db02fe0cf4fe84f0bc693b68c74ad762177875afaeefa9a3133adc2bfc83af8f5e5637eabbb82f1b30c9aa96a1f26a7c5f2879d404c53569c010e5f17c343d99087f268d398591e8bcaea71f94c9eac72262c07609fa9755fa8fef4ac3270a62c5d5e734685642fb43828baa8ca53d9e4dfb9e6eb4bd601656e2d2af3bcc82069961af161cf85f0075418bc535569308ca742b217808743754431649ff24cad85e58a997b2b31735b76e27f4376395be10d71076b1861cfdbf247af9757e133b49ac5e490e9e85a1d114628734149cfedddbcf9cc601db110337fc98b25658efc8e8e82523448adb66ec19f169f42ff76d575e89ccced4728f010661d5d760cb61e5034d0923ddbde2c66b66f64dfc4cc71455c6affbec099c55af9b00df803ac81a8a60fcad08078af5ec45d45a5c260eb861aa1e27a3ef3b0c7f2326bbe9e18330f44c51927ffa633925749dc51d651770c37593f84c8660231cd80cf8a51618806aae21b78d8e6b3451e4a0e3173f67c3d3e666ec96b185b29bfcd3ec47e3277d087c9d74cfeb4d4cc493f5b125e91552f2887ec424ce48da82aa5d1cfc92db5cb5b79b771cc7c2fb37aaf659dab3bf08b9c41eb989a3da6f1077a53c5320d5112ddd5b3dfaa80ef8611da8719ab9b3442e5b938dc2c66b60fe450fad93dc84ee4c90a77168a95a43f2de682875596b9541f7903d751906580355559125535429373edf995ad9f15d824660665aecb9d1ce7d293bb7afe558ee38eae0f56f9a3c69318805a5e01f72fc3cfa408eb881958e652c5025923d41e3b95ac9839299f593a7131736c2f9ca879463c77b044f9037e4945ac5d79b3601962d076f36f3e030d86880054fd243b4e723ab29cf31ed76accec4996d7a7f240ee257247d7c41fd05c184f2fa00957cb960947d8e786d6e981bf9b8a2b155be78d9340992b1bc7ccfb6a9d39f7b9a9383a813bd694eca362e044cb849feafa3e0b54f8126cd3d1599eb9e324f9982b37233e663e9017eb9c27ab89cf78eee0a853f6c9b40c7eae60469edf3201a830589d4fd7cdf2e457b29795cf9c12338f613eba4e0f063288e38d29da3e79aca346d87dae5f6d011f4fc51462e18b4d7219654d5e6e9a9b211d77cde7a3d907afb92c9e3baace6f1ff894b2ee39800a77455dbcacb3a3d162713ace6dceb59e9598d9e572833527a72df75679230e84253ed9ee3af691375639f657302770084a83bb116e64196d9db1bcdd629df91ee6a36bff26441aa7216f4ca4d893576c575ef1cff5ec2356ef3fb4f4fd893ed8a60cbfb4b165b0b8362873465e5852ccec3a6e7ebfb5d0bb70fe56c89b3bc51b79a5b8a3eab5c6d725d821ca60608d74455b5c16b397867a8a4bd9609398d995721cb3d6a3f216b39faaa95bb3b965f8a7e18eaad7b30f302872471866f243765fccda0f28db85ebf29bcd6366d75b3a660f7ce8199c7533eb2e365cc137444196e14cb45e933f9730532d368b5c6b179f4ad7f5dd20cb6f25c5ccee48ceb9a3f619bd80313370c7ee835428f5f77909e69a65b63be70cd658d6b64fdf1915671de8e16b8806be45d37d749fdce7cd7233a425efdd89b95a98dbd54413bc6835736fa1160fd4bd28b5fee4be28be2c726703fd519f4e015aa3c6e6b5551665b892173e73c6e2c3de5d3766c66700417b5edd37e527606ef709b3c72acc80db199188e698d5d6592455280d71aa78b3c89d8df80351d0967eaac54ea0d7666afdc744034ec77d5f2f3743f92d8c99e59b3b05ce0cc44496c08b4e3277b500ad13d760c90158e6252c7d775688fe5ceaabdad03abc04a353dd9fb864c791fd0d5b10333f65694f52ccec7eb8c01ab04c29c7c8575a37930db00df2665ebe45ddfd5ae4ce86fcb914e50deee4b4b08f813525880616da9ce9e631b36cb9dfb2241b39d6c2416d980bda545de0cdbc5cbaf542f4e7527cf3e93a5a03f14519faec05a283989db56c2676d2f77b4a8e99417b1aee61f639b00f76a27ba7bac29b79e9bb4785e8cfa55a37ce671d359e2006e908b59e3a8bff0ec4ccf9f3992f96d799dde3ec73003fd6d6b689e78d295d8806567cd846f6b9841968960af67416bc5ac3eeeabd8913bee774642fa7df5a8d994f643fd5976a6f2fced45dfc14decccba57b3ae70e5a6843fbf4ad768a0a072a0333cca54bbd027b2c9f368e99076e8aa7e081ce763ccee4cdbc94c43e2f427fd45ad0716c1cade6aa2a37752006a0c2d9ebcc53b7953f665eca67f6dcb324ee2ab501251ee4e4cdbc348d75b6e4cfa5682ce98952e1a6ce8b1ac01c2dd7ce5ae25c2bf3a631f37eecfd5195d3a79e5428e7e4cc426133b75d88fe008364233a7280371d38a789cab9c8ebd7e4a3dbc9e9b7966266959be176978f07beb1aef3f1ab31dce80ab512decde04fd73d573eac00fe5c8941f48e0f9fb2cff5ee00296596cf39d67a626266fbc65d5c858798583dab00233b8e177dd19655f75c2a2b657108a220e40f968deda366f2f1efa0c8574099377f06d0ed45720a1b6ab62e1be15c6a810fe214ac5277cfd51d266d9d2c0deacf67aa5bf147ad050d379fa90ae9f633e75bc9f9cc46df202686592e7aa9451ea868742aea609d29cccb4ab935e845b4c527ff44fe6c181faad1d565f730dfd9346ba1256f73ecf794f00ca07ba9e329506298a7df83ea2d72e005e652a7a2ae59730a566aace9c32ed54c7563fe5cc168eee83be85bac1bca4727c7fdd1f933803a66bec67c6677a0b2bdc535c47df74b737054616d19f74129c0263ecce9b957ca32c8a135ed53d5d9a927dbad21daccbdde34661681ab72499a5a8997e64f62ea8ec1360f42adb8adc79905feb9b74bfce9e7b04c49e58c59f5ed73c678c9bdcbcc294c8c99dd57f71e2289c5981854d81d826da630aefa397c5456b9927b3caa3fd50c1dd6195e05e48cb5f840deb825772747ccdc885d6796eeee625fc3f1cedd32a8f0299ceb375cdbe6bc89c64ad3287f20124fb24e570cf578da3a739707a0a36df09e8feeeabef0396366d09e8abb1f9d6fbb47ee50dd5f02d6bc6cc999c5d2746773fd81d964699535e033cfacba8ef9b61b512d09f31ab88a32a8fa1446d7815b4d7f8e2b69af39f7c4ad191f235edd32447c256077564cbc49f916afea3979bcffce4ec01651ebbcf01a9fe9ec826db566a62276b0fe0c62018a593ccb7ef318bfc8173347f7cdf0b867abe3798e3b26cb14c9996851198077d6d4c2fcf016ccb93163ac2f5d3e60ea7d145b6a0d2f81f705bd942e44d90b19989e608c1fb27dae12bdd688993dd7f3dc23cf81a32aa5c9130b6f54d8b1546f18a979bebae3aced73a673904185f5782a4085617c5645496534affa3b8fdee5c6aebc112fe78d991dd71b83d65c16e29f92e7efc710716a76f092f7a018a4ed83ebb3257da771ab11c54e2062ab8b36af25fb3bef317c67c0abfdc627bc9e2766f69ebce782fcd38b38f5262a965bf8ffea898fb2b601f532bbb03eace29effd259511083549d110fd2eece792f0bef9b5023ed16f3c562f2992966f65edd1a68cff61cb97287de1bf4e0e582bf2b8ba17eba66416f9d9173b398bfba616968153e52b93f9a85191c04fe2cbfb3e4ca7bf7a6f2393966f6f8d67af32da6de0cd4eb546bd79c8755fd245f5c9e5b8b57a5dc8e3ffa4995ba7805ff779d3712f062de25c9cfec377be27dc43f03e87d7a5f5bf1e6d273bc199c675f5b26fcff4a6dd2f416ae8d6dcc9f16073fe27deb5daf7aebe4197a56c2bb930ebdba7d0cbe2b2e66de5477be45dd7bf31cbdc2b1700c760c114796de5eb0bb4df8036a7e0c31c891bedbb7b6a27b8de4f76fd9afdea177b418337bc79198397fd12a0c5a33d5aabed836d4194139ce951dafc51f95c5d486717c22fa10d36c382abdd3d477b8a9e8b1e9b54c3eb37716c6ccb9fd13ccc986708eaf5a85a36d2f62201bd61af9d1ec33277f5a2a02102f6a146f9e9d40fc69a7bfc34d7dbc8e3c525133684f17d8939f33dfee83f70ddebbbd145b57d588d22be46baa48267f74e69b00bb80d6548bd867d33bcf7c0f20fab40bafe7f5bde53825bee09346afde0c228cfe4a7b5574acb656c9b5cf37953f3af34da8fe38d5bbc86d6919656d6fe05db1abf93b93e26bdef3aebd1befd6bbcdc199ae5a1903ad512abc1c5b574599ddf3e9e6ef004ae00f58c6aa4b57f96e882b8b78b785da1bb4ce6b30bbbcf3eebda15762af89dc09bc1d6f379333ca326aa51958a39e6488f94e59cda4b69b65aff24765be89337105962925e55cac57a0f7d4ee6bdf6e5965027b656f0f3e156f9f5dad72871ff2b677e065e65a0898bfcb2a8cf9573d53586e1fa835f222deaab5c01ff5ac695994d5ea76ae8ca3ecd212d70c021875a7450c350f7b5ecdb7bc3ddff299cf7d5b8fb4703ee63bbef065066fd4cac629447c93d8fba1557517b7b8774745f9c33ee0e8d31c799ff98ad22e95655817dd793460339f29eb680bb9bee7fb7e80dcf147fed87f809e4fe64d573f6954f7263093efc7cfbf798d0ff27b1195ff92be5bd3227f22f3d72d0a66ddc0954e656939f7c71bf88f681d2a4ffe337be53d7fe2bff8af29fe49dd4398ba653d838afb4e55edfcc1a7b9cfbf057da6b2a69a2261078a55fe1450d4086db37b350220de88e1a1dcf1dfe6d681ffbefb53efda9ff91f89bcf906d69c026b1c1d0bc7acf5a91d63f4fe16b99faa54f75cd58c552b6cea77ffde7efe3ee79eca326ca87b2c89b93f0dffd3b742ee7c7915ffdbaffb495aa3c6d3a9acba476a3d35610e3660f7e22cb712f7f45e9403600d3e6994f93785f007e6aa308e8ef51818a62ba37f48da730423ebd83ff14ffd660c271a3aa7fe0174e67525160e79a3f677819ec8a7c40dabce3e613c9588d939af6d4bfee03e8f1d7d5fb79a23f7a7e1b7b475ce80396dbfe3c7dd7b5091f0a98ebceac93917d2950dbd2f4a9ef3bcd0f7e9d59a7b69ddb78c6ece1f959baab303fa6245859347a17f0ef6b9f07b7edfbf649fc0f126a86a0d8e33d4b967df2a075e65a4c4c4c273bd9928b5c91513f7f8148eae987da59f345afb3a37e28fcaf5aee9dd6dfa6a656c8d28ace10ffc2bffdabf991f4b2b97ea617d15ea6eaddec5e5cc6aca06c4bf35b5031e7c3aa2a3f6c163f760d53c7bc600b32927a84fccde8805ebf247ed4ba733534ba2c107ebcf6bfc5bff6e71d719b52392ce3650eb34670b4f23b500f5208e0a20a2bf863e99eabb68d9fb6a7c807f9a687def02d3b67a3e6d0dfeb4748ea1cac9ea027b36da0373b1e89c38ba0ea8d7e161e23942e435247d9f14f196d17cfc6127c0723d57068617b25389de1109679965bd9fdbd6c7d44f0b0e74945db60a7bbf5d267f7aa0050dcd1a75ff30fd59bc9c7d0cec1f90cfce99099c517accd56ad855da55ecbb9ed3f8c367e657751e7c01bfa6df64f1ad2d3329666d8007e0fbf01c3b108515fe66a524fe0057d5ea4a55ef295bc4fa4f4fbfc9a2a4229782d8df82c8eb05cf117cda0fbdeb39893f2aea83f154c8af72b54f785f74554c57d0da401d987da5720974a6c9f6c76b6966c7ec7194c49f62de1da7b338ae74943401df5c04fb7b3ad7a40f0a36b4ca45f0108ed1d46b4a03a1fe4f4efe6c5d940aeb5987de99ae9077ecf2a93e62033cf8d9f67b356816ccf45c55451767f1fbc526f167aba2d8df84b982bbceaa43aa6502b552037ddca115e5edcfb10711cba97a0e56ccc0fb25f2b060fef4d8a7ceb41aa88cb1625418e28b3e5c89ce2c2ee65d9a6abf10f7017838d3f7df53bf5b187f54e48a6b3f35f5bed442fab80e766eeba739db3abadefa3c211a3856f99c6acd285fbc5e047fd43e1c6ac549dd85835f2d648556ccf411cbecb3a87d3af55e1c53a5c26bacc26ebdfea3b97a0ebebba1df74befd2e926ae745b5f2d95723b4987d3a81d92f6a2d8972ffd73ac72df873c18e5586adbe3fb9e5130758f4ceae53508609e58816c043506138a28a72371ba11bf147cdc94edda168d0130705ccbb211ab874c730fffdcc9f69955a80d9e029eaa05e9f69fb74e6e18f93f22cf2728138b32b5ed5eab6be8e62f60a2f83ad4fa1a78bb18cda934edd199faa3c9f6dcf51d987e7dbb1a6a572e9dc23b5076631f7c9e018ea6d9dafe0bba74564a35b180d9cc211bb96daedab8073d4fc61d93b50892bef4ddd3de7997bd8e5e6e1b7bad707ec0f8ad971098ea8ee043dc846319699f3c7bf4f7bdadf6a7b8e5bd6efd8dd3a3f1afb18a28f57cf5177590ad12ee4a1ba7f58b5eacb59ba45f0c71fb2b4797a3d3d9f799dc267b2e497203e730b7a0bb67a8ef0ca9b8976e68e48f94b5ddf97bc9ef3c769f93b3fb57e8257a177f93af6dedc3244d7055946ed7beb1e79dfcbef46ddf8787a5f7d770c3c1ccc67bf9a3fbb7e990f7fc83a2d1d0d4c41bdbe8b9973ab8c3ff0dd257f0f22be42d620e1d3e35379a3f2e4f433c791366d9f8abfcf64e16f37c7dd53badec43d5fd8bf7e2bcea868c0fb76c7b26115a185fa1dbd100dbc92c2ae7c47dbe7404efd6a613ba837f4eada8518ca2afc6ab990b51f9d250ab1e314fab85bd0dbd87b6a9f195982119a922747fcd9f587d66141bee402c6d3b77b242ef58ae1d657a2328b614ea69ef06817c343f6a1ded1aba20159cde221f207ecb3efd7b6bc7ba3f7f7121db56381e6eaf67dac76bd55eff669c3cca32015d6637ee63e00b373652b137f0efc0af78335f74e5efc55aba9e73bdf85acefe9bd58d4ce912a9fb9181eea1dadca383bcabf5f6cc89f037f3fc88ea3578aca4dd5ef4c6eeb8ca342661d6a7723b5efadfbc0b7e534f150efb808dec93d8f57e174fe04dc3f08b89c3ac13a3ba8ab5fe5039d4b3715e5cd9fc758e061e08c60967705472c669db901aaeeca8628a9a784d7d89b68993ff829f39c5907309ea6faedb76dbd1359212aac72d0847ece9f7d16c343da9bed34e7fe88a9fc5123ccba0fec2cf6e97790374547e7784d0a89f85a94f9d657996f85bc958fb244715d7fbbbc9005feec060eaba5f5b1d506f677556ed75a3b4426f330d07b85bda87b5039df509d51d43b4e34b3718fbdad79f837ce2f0e34870e200adab392e201f53e1f75efa690f7c499cc37f5ae16765f0c0f693f3f9525bae13b7a178bf23d863f8150ff15cf814c99a9b60a51e1867ec3cf44cf92abebbda13af14a304b54ed19552d261a50b1b5cae954f609dc70841db0d2f20cadc8a29f3f9ce86c857e51d180f6dcea6d59a0c24544a56af60b637e204aea5e9eb68f40fee8e205b9dfeeb35ed1996f65fdb45621ecc7b717eb0ca5925af32d86873a8ffb4565546034f077549f0fd44c959d16338a22e542e7acab5c9d9762545867139575565ba30815c62c51bdcb9b3ac7e69c87217f7c2a813f64c5ad05b59c91fed5aede13a69079b79ae3d1d371c3627868d5294bf44567892eb4cff9830aad46983d2be24ac4b5d237f5abd22d26e7426789ea5c48fdf4faf63c6ce92cd117fd1459c27eb19a3f41c81d5d336fbb7539f601ecc7bd2acbc5bca11aa2d201f5703977ae7e4601152eeb2342bc94cc43e48f1e612333ca8231df34ffa2a53349750626cc2d0b7937aa7e0e563d6d3028e24de9c8431dad63de69c69c4cf327c21d5d3f780f1b446c1752bfe95254d5ce9185f4b1ba6fa3b244076ad7a6225458c71725fd1459275f9628f1c75f2cfc0de619f97f59c5200dfddcb97a4ead889c0bfdd658b5f7a57adbf0ba3917f145e7d25da92c5175273befdffdbdac3f4130825aad05e5ea2fe06a077ef55be5a6e67ca626ab8fd5bbbbe18898f956d09caca6dfb67aac76e35b8f87f1fc0106c53e1db96019a5c27d985956d533b1fa6d16db5e49cf2a03f7750666face33f979a8560cf5b387f84cf3dae788fc519c015d8ed68f696fdbd0796aa76a4f4db55f57019669a83df4e03a546cfd59c8932aa8c24db55bac38db3c4b34863fca3a7ef064f59d44cfac18cb3ed3f23ed7283dd9d0ab4965bd4b6711abf03dabad9e1056cf77e89cce2dd77f96b8438595524658ab98184467bea9a7886bf3fd11b72be25a54dd07954ba7335c0ad93f218c7b46c41d5dfbfbbc90acb904cb84fb2316b3eb2da9f0a968ab5db3741e47113c6cfebda83ff3f20c71f45df208dba298ccb72ebdf17dfb6336988a20ebea39d8edf6a58e587ba6de94256fb47d46cbdc212b8d0b7a9a24d2c7eaad6e6a7f449ded5a446eaacae9ac8bb67abaa9a82c51cdeca97455b6ebdf73fd21de44ca837356d47378fa89a9a9ba6b09aa5e486eaadea7b3a972e9d475143627d3fbd680ef39411e127f903bfe92a71f05d96f1bcb53602ca99deed43e074565608a867b24d47af3493159a22a5b192ca39e87bd8e3ebb14f2e739ae58f7dbadcbe9b7f78ed5fe884559064a984b579065544e671b8ed88d7b5774c89f450f4f16521a1dbc6cf6cba0994d60cd2545204558a62efade89385dd827753bcb5c507e52539f638cb5b57d56b913989a5faeef0f742e9de38e41858bca4dfd60f79ee339d22d883350202a7df5661095a6be455bdb07b9133bc2c4f31a79412dfd14c8147e15b3380a613f68e6a9ce2d2ee6b92f9567389337c12be5d2651c33863fc89d37f8ef73f00ef524cfbbfdf4f34d2fde4456b77b4375f4986ab764e0e1b0a03b923ae34f4c140f61de98f36f883fb1dcc1c2baa9abe02ddce942bd45a0b09db97afaf9a6a9d2cc62eec7eb3d3edac043f5ecea5ab35fe28fe14c409c79d61f55bfc1cc23e5b972f0b42acb708d8ca3f42bd16f2c7c71cb30873a2be68903bd1f5f55ede6af57aed7fcfb4cfebc07cffc327984e9f7f9169281a933df8610430ec520f26ed4ad2ca3ee78602edda6ab30c49ff788de3c2f177f9f0d0bb0406251efb3554f99aafde58b7a0a5cef54d850fb726d979f94c89ff7b0bc810679c5efdc807dac32dfd4fe88b21af76ed4f58bca03d677e4db52ed68b7755e88b6cf82dec49459302e28e7385a22fb2316b3070266898a6fb5dbe8e63b22a5f027ca9977b218d60f4c1668999ed91f917691dbda323aabadadd77c4ff57decc2ce35e44f3c6fc2c29e8af14f2af34dbfef67aa23d702ae80dee5d9573b7f16f43cc6327fde23dc795ee0ce5b588fb7cd2051fb2302fb1bee83bcd1591c5b9fbd8a05f42e4bea2d71c54403f9f8338b29cfec6ee35fd1fb23820a5f415453880aeb4c36958376259a45a870267f96f5e62de2e9c942fc6d932bd3996f2a97ee54bc14b5231268d7503464497c17140de4e14f1c67b0bc51fd60e57893f8bc28f6832e94d56e3405cdc95aea88948159d03a734efe247066b1b0a39c4735fb237e8b2bbd97671179f07581bb7d5d8a41f12a1c5f548ca9ed93cd9d99ae3fb2cf4cb3bfa332f836db1f318e87105b777007aa9f52e195d26335d9b0fa817ebf43843bc99652e5cb3a4f39aada3903f3d44a7a17b902f640d0fb23aabdee4a996f4a2faaa808a4197c07756bc7fab00631fc8972a6111cd2bfb09e24f79fdea7aa2b3a05e55c04e0b9310373f0f32a4cbfa972342f9c9ed515636b600d5897ec93c59b676d29551f89a477aeebfcfa62f28fadb2569a86dec1e8279fab9e9716abf19af5eddc08669db04765192cc49fb7087796398335161505fde499d6c173eb772cb1fb6276e6ca2e103d35f97d700ce3e9046c0356094ed03aaacee44f231c69ba4ecd5ad8e62c4ba0eb3a9f59baebbe297de3d26243f8b56736152c389d73265a883ff19c59e4ce2c684269153a53d567a9f7e92ce19b117f910aeb1ddfacbe73e35c581f518b18eeb02ff8f738853fb355eea09777f60abb8296de99ab0f9fb27e1b7861bb8aa49696b5679d0767560754f8248e332bfc49e38ce18d2987419b0f0a39cbb27ab31bee8ff88b54f88cb94c3a3df6a454389933ec8b754dbdc09f460267162dd709ba5b9d6543ef58705ae4fe88d9c55131d933cc007616fd532efec471a79950cec192cec6731fb5a7ec25cc2cf5fe88853c9b9ce73787a2ca66309e18faa765bd41aeac7227d49f65ceac72e782ac73a1718f8fd63f53710dfe692abed5eef2bf4a85c53593ec8e4dad9d79c4b75ef9dbe84f92de206716ebc3f5d682d88718ba0fb83fa2cea5fb059651d13c3b776eac8eb2cb2a6792f426567f92f5a619e1cebc3c07fddcbb9994c595da1f91de20ff0b2ca33247ac73501a6599b5b466c98a635542fe6471e61cec644a33e8d8d9abadb43f22bd41fe575806a24cd90d2e61fec496b5261a13a7e98daa559ba9ff8eea4fbcdec4a9f4733048f53b6dbd974343bdbbbba03dc6b24acb1939bde05359863d6ea63551de2cf0278d3b51decccb2cb882282bf96cdb949bfa8b6261760c4ab303fd7d1267993c9c89e34e447fd2f566d97217dacba7ed10504cee4f7669f113eb14bc135b55e1ed78b3c29f34bdc1d25ca80f83eb9fbc6f905d40d3be3d3fb889b7cc3a3e6a9933f33ae44f1a679a21672ec25a8fb0c267aab9b586d7824feb438fa69588af18de2cf0275b6f9a2bf56df0ec3dfc7aeb886beb34b8e307a957bca68f4ae24ef0181c117f92f526ca99c5a218f453bb02c670063ee7ee55706f4653713e6ab5b0afc00b5ac08121e94f5ece44cb79500a767e8d65c419ff10776abd3c8d3345e88db64c1bae6f089f5db24f928f4ae68e2a30c2d8e98fdea5534f51d5d979702725c6c13fe3a342ce8c83a3a01394c1327b689d608ff427993368893967a2f579a09eb3fa21cb381079079ff225596b368d896338330e2ac17e704056a986f5de127fb23883d63205e61981b24fd19cb9604369f3b19418ef6dc79974de04637f08ac698eac3967a275843fd97a33af4d698c5881777b5b4ec96907df3240cb64f9a8edf4465966c4218e3b58e28caaf7147754aded7391536fcc488bd61d8882b6b78c7a32ecda3a74f6e48b359dcf9f7e466f3467bca037b2b50aef2d73265a883f51ee247126ca9df9a7e3ef6cf9ec558f9db021b746ce4858959fe7cca27f5ad69b903b5876893fe97ab3cc19532b4bcd4632e59e6a96e7be7046ec42c5c230a27e94339a371e28cdb356e1610c6756b8a3ac15ea4f3667167913619073b6c9be9bacc64fd89d6010d764de65d9d6478d5c509aaf9117cb9905bd897227f45fe93e6a9533643518bfca3e87237fad7baa2d3e1b05fcdeea089827e0fce9e76262edb9d13f0de993ce19e28d29da3ee93e2a8937e16734caf986089d6b6a1d320191f0347b66b99dde90653a6a96b0648114bd59ae97f813cf9965eb2d7f9ef5dec8192aacde9c6d7d8f1c81b3cb939fd31badc295a0472a9ce9a36278135a2ec29fbc7a839cf1e85f07eaaf462c6d5740a7cd4ff83baa705ece6ca637649936cd12e239335ce24c1c777647632cc49f75f426ce5a0f811d67199571c46bc119a970b85ef31331b19e59827f1a7911adc9f6514b7ab3a4d4467f726b8de10e9cc32df491a95b815cba47d102461d5adf5657cad163be55accdd789f51c617ff444b1f09a3e2a5a1bde2cf0278533b17a13f7b9e2a10f1b3df393d124188831c5c285f9a855de0463e5b96196701b72269fdea470666ebdd138c29f4ccee8cf9c33a83d652a1d18612dab377a09582083aba00de73dcec799cd7c94f14f344b58b640b28f4ad59b58fe24f8a864bdd1add64299f9557b367a1d49f01bcffa1bcd605c14679679a3c7530b7ee16081353963e214ceeca2d5c83a7b217fd23f07099c090bb496c12aed603f3857ffd6f8363882be2e3eaea980653ae15acd0f70665e46667d3e878f4ae0cdb252df8ede420b3de719616b688d074af3acfb6853cec473678933510b25f127c64725f026a5dc065e313e0ae64fef416bf434b246d31c71cd9a3e2a863733c51d552bfb14c0190bad35e70e95aff811b6b67f6ac6f8a7ade29aa88f5ae64c127fa23171b6dea496375d0f83437fb8a9de68ff049e3bc13fad398fcaab3751ee8c3eb2f4398e37c97a13530e5647583eff04acd1fe69f4b9665c93eda3163993c21d65a9d03e0705e88de60c7067a8b953d6ff6ea93192ce9da5b8a6429ebb1c6b8135e751ebea8de24ca44ee04fb6dee4b0148eb0a61a61f9783372613c7516e64f05eb4d3667426bcd22fc498a89f372a66c38b3c41d5d431494a937face5c0bf4ef202316fe111fb5c49db1aea92cf26785376f397c54fc289b974e304ee54d25c63f153e8fcae24e9433cbfc89d79bbc3e2a8e332177cabaad12af3778672e585ddffbe198384d6f42ee7c6141fe6ce5a3e279131d71df268e8ec435153db33cd0568ce7cd363e2ab7de24f1465b2ca23f9be94d126716af1a74651c896b3ced9fca7094698205d6f351ebeb4d126722bc89e74f5c4cbc266f96b9a3cb88d3bd04b55e7ea0db16e39a9fd39bf5798316c3fa6bce9fa2f426f6d3d2eb11079199e5fa7a53584cbcc29918deacf2672b1f15c319b2da10b9b215673658b7599b3773cea8baaead3333fcc9b4c45a7ab3fcd13ab37c67ae807b0b5beb4d026756f953808fb2563913ad1378b3ddba4d413e6a9933510bfdfd7fffe7bffe1f92e4fedb + + + + protocolListView + + + + klistview.h + + diff --git a/kopete/kopete/addaccountwizard/addaccountwizardpage2.ui b/kopete/kopete/addaccountwizard/addaccountwizardpage2.ui new file mode 100644 index 00000000..a5711037 --- /dev/null +++ b/kopete/kopete/addaccountwizard/addaccountwizardpage2.ui @@ -0,0 +1,156 @@ + +AddAccountWizardPage2 + + + AddAccountWizardPage2 + + + + 0 + 0 + 600 + 356 + + + + Finished + + + + unnamed + + + 0 + + + + m_header + + + + 3 + 5 + 0 + 0 + + + + <h2>Congratulations</h2> +<p>You have finished configuring the account. Please click the "Finish" button.</p> + + + WordBreak|AlignTop + + + + + layout3 + + + + unnamed + + + + mUseColor + + + Use &custom color +for account: + + + Use a custom color for this account + + + Account are often differentiated by the protocol icon. But if you have severals accounts of the same protocol, you may apply a color filter to that icon to differentiate accounts from the same protocols. + + + + + mColorButton + + + + + + Account custom color selector + + + + + spacer5 + + + Horizontal + + + Expanding + + + + 101 + 21 + + + + + + + + PixmapLabel1_2_2_2 + + + image0 + + + false + + + + + Spacer8 + + + Vertical + + + Expanding + + + + 20 + 58 + + + + + + mConnectNow + + + Co&nnect now + + + true + + + Connect right after Finish is pressed + + + If this is checked, the account will be connected right after you clicked on <i>Finished</i>. + + + + + + + + + 789ce57d5973ea3ad7e6fdfb2b4e1dddbdd5e58f84904075f50564820009848c74f5856cc90364200364f8aaff7b2f692d19039e0067effd9e2e4a47fb3922c65e7af4ac257959feaf7fff75d7ebfef5effffad7db3b7f0f9cbf1c9fbffef56f317d7cfcfadfffe77ffdf7bffeded9dbfd6bb756faeba05cfe6bf7effff1afbfadc7bf9cbfd86e696fafc2357ed0b8b65722cc08537bffdde07dfc7b4eb84ad825ec10b6152eef84ed4e88f1f892b0f9be0831b67b882b25c2cf061f94351e11ae50fbd8606c77f5f997e1fc2afaf8fc21c4fafb421076103b55c415ba7eb663309ddf5388f1f77c83e97c3e099bf3090cc676764bb846ed1383e9ef5f426c6b7c4c5850fb7b8875bb4dc7dfdf411cbc2abcb7b357dadbd1d7ff4598ae5f5e1176c81e2cc4fa7c383718af5758888d3d9ca1c1d81edc13ae507bd960b2d75188d1de2383e97ae8fc8c3dd8bbc1d4fe1a62b4c72561638fb710633b9d1fd803db4b21d6edecc6e0aa6eb77dc215b2e7bdc1d82e0cae21f6f1787c6f6f4f9faf7742b88a583c198cf6f5f7083bd8ce760d26fb7f8658b7bb75c4606fb4ff6788f1f83d83d1be927edfd85fdc194cfd771062dd2e25e11af5cf34c4badd367f4ff6e7e6ef05f1b56230b63b7e88917fa788f777a83f1f0dc6efcbd710637f74081b3edc8758b73b2d83d1fed63e61c3976b83f1efc5b3c1d47f5f21d6c79374bdfb02b14fd773b08338e006d7f63476095710fb9a6f95dd72a954d2e7778038e403b59bfef566884d7fbaa721c6f1726d30f687f74db842df9f8518bfff6630f5e70b61eacf910831f6d70761eabfd1a3c164af3162d35fd689c1d41f03c246bfba21c6fe730893fd837188d1befb06637fb007c2f4fb2337c4687fcf60fc7ef014626ca7bf37fde5ef1a4cfd43fd61fa2b102176341e11ae51bb1f626c7f272c10fb5aff2adcf4b7b747780fb1ff45b84af831c4556d5fb467a8170e1d2fd45bfabee187d33498f830214cfdef39846ba4176d83f1fbee116181ed72cf606ce7e310633bf24d8d57d4f3bb10ebf6e0c660ea2ffafd7da3e7cc60eaff9310a31eef10267eb0af10637f3e1a8cfdcd2f09131ffca1c1d44ed77760fc5dc5606c1ff11063bbf97e05f188aef780f420f83298f8108418f9b04f5850fb7388b1fdcd608eed0dc4d51d6a9f194ced17842b887d7d7efbbb217fdc106bfef867841dc20c31d04f63e7de60f4f7de98f01e62e18518fbeb9630f92bd6430c7c42fe9409135fc42b62c30ff725c4c83ffabd7de36ff6438c7a32234cfdef5c8458dbdff3091b7fde0931eacb14b1e96ffe6630e9c34788916f5784e9f759c960f2dfe510231fde0dc6feb5ee08937e066d83a9ffc95e211f5a21c6fea6f30ffbffd060eaff2661eaffe03cc4fafb239b708dda6f438ced166141f8cd605b7f7ff489b8b683d8a969cccb4eb9a631fe1e377cf19f0813df3cfafe9eb383fcfa3298fc19da07f46857f3959f1a8c7c728788437da813267bba5706537c4ee7b36ff4283098f8d0451c8ef74fc2c6ff3c194c7c9810367ae28718bf7f48988eefdf1b4c7ad0475c257f327a0831f667c960ea1fba9e2af997119d8fe9bf911762fcfb03c2d47f41d960ecaf80ec67fa6f340db1d0ed7b842bf4fddd10ebf6d18c700db1a37fffa06cf4c07f475c29eda0de5c8418fbff9e7005dbbdb2c1d4dfb6c1783c59456cfa4b4c111f90bfb00d367c681b4cf1e06e88b5fddd5bc2a40fce8dc1143f1c11267fc0bc1063ffd51157491ff87188b1dd3198c6ef35611affa391c1d47f92b0f10774bda1bdc784c9dea30f839d8ac6743e3541ed5f0663bb83d76f97ab656d4fe788308d571bed6187fa5e415c11d85fac6930b6db6788f74dbce011a6f1e53e2036f616a784c9de828e7740facb3f0c267b8b10a3ded2df1b7b3b7b21c6f1563598ec4de75725fd0d060693fe5e1336f391a1c114df55438ce38d1126ff2b74ff56cbe53de4ab731562d4bf57c2646ffe85b852417bfb1ee2fd1db2ef04f1c10ef947df60b2678930e9993498ec252e111bfb04738cf3911bc2a45f6c1662b4cfb1c178fdd2266cf4ecce606ce7747d55ea6f7b6430b6b3d3106bfbf10ee21a5d8f13184cf6f50d26fbe2f9d865d20be7ce60b2f73e6163ffcf10239f6b84ab88bd27c49512f98f6fc4fb3bc85fff8ab0c0fef2e9f70f76f0efc5036133ff7b435c257fe13d1336f3f9ab1023dffb84c99ed6aec1682f41e75325be3b3d83c99e74fc1af1db7b08b1b61fb3099bf590db10637cd13598e61b3b06937f217bd5c89f08dd3fb5bdd0fe8706a3bded2ae13d6a7f3198fac30d31f6c70de12ab6bb32c4badd3d21ec507fbd22de2b617f786784f710bb1f8449bf3cd760d2af4fc4a6bf5999708d8ed73798f8503198e28f63c4861fee2e62d3ffde25e22af90bef2bc4d8df25c2d47fa28bd8f41f3b0fb1ee0fd1206cf4e93bc4e80fa60663ff5864cf1acdc7b817629cef550dc6bfb7fa06e3dfb337c2147f073b06131f6488912f35c47c07b1a3f5b4e698feb6f70957910ff681c1d82ead10a33e4e089bf84f1a4cdf7f0bb1febe6c198cfd278e10ef113fa56730f183ce2fe4cb28c478bc2fc255e24f8fb0f93d3aff4a09dbdd5783915ffc89708df88be3c5d9df217e71c2a48f1ef6bf63f4dca7f6ea0e5eaf4fbf5fa578c7ad194cf3893661817cf5070693fedc1a8cfc73a93f42bdb00913bfc44b88b17f9b848d1e55438cfa6d194cfc1913a6ebf35f0da6bf1f8418f9776e30febdc3428cfa45f6e634ffe3f706e3f7ed5988917f6784cd7ce6d5609a8f687de07b864f7605b1e18b7d6d30b50f081b3d7b3698f48a1b4cf15383b0e1fb95c1f87db14798f4490e0da6fe75101b3ec90961d3ff3dc2a44fbc4958903fda451cea139d8f89c7fc6fc4b51d8a37ce0d463eb99f84cd7a638b30f1c7ab1a4cfd593698fac343cc8dffec1a4cfa766c30e9c97788757f499730e919932146be1c10e6c807bb6430cd676c8369fe42f6e392fa5ff3853ba6ffec3e61d35f970693bf12848d1e3d194c7f8ffde984f39716619aafc863c2a6bf1cc282da0706a37f61743efb145f48c760ba7f42e7bf4feb6fb24998e26f7e1e626cbf3718edc5d0dece418dfa7f8ab866f46948b842f1ce09613a3f798a98937f72ef0dc6fef276091b7d9221c6f5b40f83893f1d83c99f3408738a972606633b7f3718fb9399df97a4171706633b1f21b67769beca428cf3533d3fb2f7c2febc406cfad3ee194ceddf842bd45e3798da1b846bd82e1e119bfe7477080b1aff6d83e97eca3ee203b3de7519621ccf7b88abe4dffd67c49cfacfef10e6148fdc184ce3fbd160b2974f58d2787d3098faefd0608aff8606937d6b888d7d6537c4dabebc4f789faeaf61307edf3a0cb1febe45d76773c4b6d6175b84f63d274cf6751c83a9fd8930cd4fedaec1d8cef0f8c2ccaff8b7c1f4f75f84a97fbd23c2e6f88784496ff98dc1687fd6426ce607f2c8608a37a684c93fcb3162335f9075c2343f967d83e97ee13b62be43e3fbd060d2eb6bc2d4fffe29627b97d623ba0663ff8b196163ff2661b23f1f1a8ceb05e28cb0a4f61383b13db0103bbbb4bea07f1fe4d0d8ff12b1b1bffd6830e9e13361b23f170663bb27099bfeef1a4c7a6d8718fbc71c8f7edfb6438cfe32c4a47f1f880f0c1f3a0663bbbc234cfa289f428cf39923c2f4fbf2d260f2df17884d3c26de119bf9af7b6330cdcf1961d24f796d30cdc7cddf537ce6d2f9193e78fb8425e9fd0b627b9fe2c303c29cd6333961d203f66930f6276b207676e9fed1bec1a4479f06139fda06d37a5410626c2f13dea7e30f43acdb5decbf707cc90ae2038a5f789d30f1c3b93418db99f9bee1c7a3c1e42f2f08131f1cf37db39ef510628cd7a7884d7cce5b21467def188cdfb7e9fcab153afe89c1e84fed63c234ffb4770da6fbf3738cc733bf47fec3ed1b4ceb6d4dc4c69fbb2d83295e32ed86bf658369bd86ceaf66e6c31706137fcf101bff2f0d36fca4ef1b7fecfb88ed5de21ff597cd697e30232c49affa0653bc80e31ff484be7f6b30e9cf8030f1c51911e688472f060be4974b58623bab198cedb68558ec22167a3c8a8ae92f5e266cc6f7a7c1942f7118621ccf55c2c42fdb3198e6ab2e62d33fce2d61b35e5d236cd6233f0da6f5ed53c2c407716130dddfb80e31e69770c4263e730c36f1c15388b13f7dc2d49f9c8e67ef92deb821c67850188cdf77df0893bf1715c2e43f5c0bb1e94fff9230c577eea1c1d85ffe1d618a47f8d460ec2f3e436cfacfaa13de47ecb4432c353f9e0973c41ced2f0dffb9446ce22b7b4298f445f60c26fbbd13267bda8230e9af1c194cfa4dbf67ec297608efa33d9d6fc21cafd73d3618ed257cc2640fb78bd8e82d7b21bc4ff7ff1f0ca6fef10cc6ef7bf4fb0e8d4fd6262c299f686230e9fd38c4c857e48734f6652784c9becebbc1725ff7cf056149ed2583b19de9f10d07377a3f214cf6f41862a32fde21e17dd2f706614efef9c560d2f7b6c1b4ded63298e69f6784cdef750cc6bf77760cc6efcb1a6287e2617b4c98f8eff40913bf050f31f2db264cc7171f06d37809dbb1bf64d88ee343760c46be7b25c46297d6131e0dc6bf779b06933e8e08ef93deb5438cfee5da60ca0fb93298f4e2dc603a1f61308dc72a614efeefc3606c970f21d67c90645fc30f1e188cfcf0881f7297da4b0613bf7a84f711072f21c6efebf98094862fde3b61b2bf87f680f144b84e98becff609139fdc03c482f4cfde3118edcde718fdd53161b2971f10e6b49ee7184cf9575383d15e3e9d9fa0f129a4c1680f7762305ebf7f83589af8bc6330d99b1b8cdfb71961b21ffb08b1febef74298ce875f194cf66f85587fdf6a1096647fade7eebeb11fab8718fd9d242c69fda48218f88ced6f8449dfd81561f3f7aec1c4b757c2743c16184ceb2fd42ee9f8bc6730b55b84e9f7bc73c2a4175e3bc4baffbd538371bc7b8d10ebfef6ccf1687ee50521467d0f8f4ff6168469bc7b8f06937f1b1b4cf63e33d8d5fdc1d11eaeb10f7f216cec8bdf77c3eb3f3498ae9f11a6ebb76a06637bffdd6271c5e2f05f6e6a28764c6dc337928a43b58022a98e2f3614178a5c2a22520bcbcb55e61f3fa63625c82c2328636daf07b8cec7242bcdad654ad46a11eb71b21627ab2cd7a6c8258bc55bce8d2972a1466bc8dcd68afb284b8dc822e3382badf267e9ea633993c21b27526773862f5df53267e4dabc79da8a3386379a3baad6fcc9c99b04cee0ff4be78ce14d3a67e2782397ea2867d2b893fc79a63aca9958eeccf9a35953acdea473a658bd89e3ccd346bc19476a558264fe6cac3771bc594f6f56392372712699378633997ab330c246649f4ccefc32bd59df4f6de7a3bc25cea83a30dc09fdd7cfe84d3667d6d59bcdfc94b15c3a6762b813f2278e333c436bb6e3cca67a93eca30ad29b857a62f4e71feda3d6d29b65aba1fe6ce0a39cb57cd4b67a931d136fc69915bd519c8916c39f141fc56338537c4c2cac6d63e2f57c541c6f5647dc24d49f5f1d136fab37ebf9a974ce048b9c59e6cf1fa537c5f9a88df486ea891969c89f1c7af30f8a89d3f4264d7ffed131714ebd897267ae3f5be94d968f5a7f0efec331719adee8f212d693507ffe61eb366b702658e64cb40ef9f3bbd76d7e0d6732f54671467f5eb0d0fcf43fd547e5e74d8a8f4aac0d7ff2fba83f2dae491a316bf928d29bc9327722fcc9e3a336e14c3e1f953d8f5a7bdd268133b13e6a65e4cd3f2fda3edbfba8fcbc89e3ccfa7a933d8fcae7a3623993c19f227c543ebdc9f651f9f4664b1f15c39bd072da3eff241f954f6f523963bd52fd16f2e71fb76eb3e0a3d6d09be53ac29f7ff43c2aafde28ce58eff33ac29fcde7517fe0bacda67ab364ad77e2cf7faede64f126afde443813ad893f7f928f8ae74e1667b262e26cce442df86e0af1a7889838596f36bb07bead8fda486f748de525963fff1fc4c4697ab35c883f7f4cbecd567a1359b7595b6f229c89d4ef217ffef3d66d8af251b1bc31968bf0e78fc8b749e0ccf67a93e2a39639132dda3e7f50becdc63e6a6acdac0feb730dbd49e44cb426fe6cefa3a2dcd92c26cebc9799c0992fb0ccb755b71ad6a175b4898fa2fa78993b217ffe43ef2dc0e7c43ab59a56cb3a03eb9c596dab43ff3f8e33510ba57246d7c750bf93fefce7c5c4ca325debdcba20cb60e9597d1861ab7af3665d5a03ebcabab66eac5bebcebab78656295e6f12f5e74f5c278e5bb799c0b5ee58bb5679c132a6ec59951885deb70eacaa5563161466d5ac2ae3cc660e58e96d853bef9191769cc99f641ff5ebe7e0ea0a064c3009e369d532ba3097a95f5abce24be6b3802c538bd42336660fc0a564fe94427dfebdeb36d97a73c91ed9137b06156ec55b864a0bacf7b5a8ce6cc25ee6dc59a85fc1466f60a3e3b9de2cab74027ffe989898bd5b15366533abc93e522d43857db2afd067a9f2c1be597d853b919a35d8a11e690bbc81f216f2e78f8c89991a195fec881d6bdf9d69191a6127ec14349afc127b674dd6626731dc89d66deb9a75623d7c69893fd9317151f936297a0357f5ceba309ecec163276a4da2852ed82372078ed2637d76c906ec2a9e3b218794165d136f42eeb09b707ef1c7c4c4ec169466caeed83df8ee352d43236cc84a70a44fb6c3765999ed817d2a6c9fbda673087cda81e6507484cdf9f367f8a80a8ca7aa75ce6a192a9c5e7adce28c73283677b860975c3d99ee267167ce21ee59f78a33863b0bfeeb77c6c49f30121eb90ffea9c9832d2c43858ff8983ff047fe04dc51fcd9e3cf7cc25f12f527d421fe0a3629adf0e737c6c4ca3ff13776c7dff9747bcb60e133fec13ff917ff56dca152e70d7e98c921ce8f903b544a217fb27d54be98789d75e22ff6c58fad737e02bebb10cb5069b1637eca9bc89d90432db0d0593a87789b7762f8b3998fca5eb749d41b5061f04f569f8fd849a1963123accbcfc12222c21f552e6094a5ea10eff1bed6a09229a17db6d59bbcebc49f309e2ed929937cb0950aa759e78a5ff39b2877223a74cbef527d59438db018fdf90531b18e6aaafc9e0f79e9672c43fab3c37757b813165e563be3257188efb3871cfc293abfef931ff0375ee535dbfa49cba862339bdbf62a77e6853fdb8eda9d8a9f2d73c8766dcff69542db81f264ec46dbe7677d14a8b03de2ef9bc4c29b147b6c3f2471078bfd683f71613fdb13fbc596f6abe18efd66bfdbd3397796f85374be0dc4b0d609f7ed9975082afc435ab3629d0ffbd3fe4ae68e2af6137cbeedbaddb00fed23fbd83eb14feda6dd62dff699f2ea863b7690c99f4d7d945661f04f3556cb37ef2e4a99edb6dd49e30e943dbb6b9f83752eec9eddb72fed01942bfbdabeb16fedbb2877ecfb903f9baddbc4c53523ebcb1ad843bb04e3e9e2d75986b467c7de4de70e58e7097853b6f7c03eca3a15b0ccbe7d6057ed9a632167a2232c813f1bcda3742cec3b8c9ff0d9afd19a25eb30873b7606771eed73c781d1d57384e6ceb5231d69571dd7f11c1f3983056c35e7cff67a032aec04d6a13302dffd8bb466b13863e7c1794ce78ed69e73b0ce85239c277be03c3b136d9d17e7d5795be68eb2d2127f36f1514a858fd9051b3aefbfc732589ca9fd09d14d86df02ed51ca7c01d6e93b33bbe27ca8d1e57c3a5fabdc5175447fd69f477d5a53ab032aacc653eff7594615185bdf10f5e4f15bdff69e53771ace73689d43e788bd2f2b3316e2cfda7a035a73c2eeac433e2d6245a280d175ec9c64f8ad4be7546b8ff15ba0ccce04acf302cadc8c7246d54e4bd509fcc99a474dd99373c66ace2f8af87258a76373a79ba93d0dd09e8b88324f94df02ed39c77827ca9b25fee4d59b23e7c2e9f181d32e784562cbe2f49dcbec9859fbad7a44993f803d03e7cab98e7287dd18ee382db24f1ebdf1f8817363edf1198ca7dfa8c271c5fe706e73faadfa82dfda07ebdc39f78ebadb1cc31d65a5087f92f26d3ead236b669df399f39b3c77867598b3e374d3b547fb2d1d333bf5302a3c50dae3ec72e694adcb2867d447d5217f92f5e6d3fa70f6ac323bf9d915896d0a287365d398d9d9770e9ca1530506c47027a23fab3e0a223eab0b51cd1f6c1955d46a46e67c6b296686d125292aac394361818586ca16cbdc59e10f7187ed08e6f4584d47c2bfdd02a9dc19434098bad69316330b877f0ae10c61840dadaf450f6ffb682b6d9fb9dea8f174c387ce1f6f192c420a37db6f25c4cc87c243ee280e5947a83dcb25e48f076370c73ad719127fa00ac7159b093f07774cccfc043173681d116865d6a38b46d89be2cef247d9478cc498cdacf27f0667a8b4acb278108f5931b3f25b919879606266185d4f863bba2e5b5fcbdc11cf649f0928cdefbfe27c45b1fb42bc8857f126deb3fc564acc5c9b73070bfab0b94263adc7d7efbfea9c9c817e6c8a297c66a03d1f196b3da931b3f88c70c77cfc396fc4d79c3f7fc095e7b00c9f8a6f51170de4bad31187ebc6cce2086366712c4e16b983c53a5ae48eb2d01fcf1fb00c3b1113712a9a913b8a2dd14ae74e749d7929663e748ee6aa1cfd5853ab14e58eaaff60fe34b465ce80356dd1598c529da9ddd93c6656ca1cc71fb0d02572475be7f90fe64f8f9d305774445374996b2d65d7897371b145cc7c2e7a4bcc299b621de951f5352f7f1c7f5a3ce03350e1beb8e4b578bfeaf4950532b4e7dcc4cc641db3ce7c0531730c77421ff636e7ce9fc61ff60196698b81b882729d14a5425478933b66de8bc6cce0b75ec4ad8977568bb883115631dc11aa3cff21fc6959753e10435102b5394b5dcb6e899d3563e667a33d30a3d8e59f49dc218dd63e6caed07f007f2e9c118ca7328ca8093bc9fabe52e6cc98f9313e66167bea1d24ab9c59a80fac4bc31d55ff56fef420a63903dffd2daa3967372d51cba13db131b3b424935cdae26e993391ba4c23cc78f9dfc59f16f8a74f311355d14d52e1b8628f378f99c17d0995fd2b652277ee8c0f43eefc1efd016bb4c54cbaa221caa0c76bfcad90cec9a631b3f4a46f33d5333288e14e19b9837538c27e397f603ccdd83d78a72afb5c3bafa32547e9f7475362e61739960f64e5b2e2493c77a23e4c73e897f10794b7c9eed931ccbc73a8705c918ff2493e6f1633cb0928f3391deb42be44f566ce9db094e5eb2fe44fcf1981e79e88013b8619d4a6a3f2cab975ba9be566c837f92e79e4581fc9dcd1057dd8b39c927d7eca322d71cd6bc09763b0cc1a2a1c5784bfce3af362cc2c67f26321da6c8acf44eee8624d6dff67f9c34bca73c378ea8889ce06daea78ce54e6cecd50ca3c5f490565fe22659e1faf25bf13b983458db02fe0cf4fe84f0bc693b68c74ad762177875afaeefa9a3133adc2bfc83af8f5e5637eabbb82f1b30c9aa96a1f26a7c5f2879d404c53569c010e5f17c343d99087f268d398591e8bcaea71f94c9eac72262c07609fa9755fa8fef4ac3270a62c5d5e734685642fb43828baa8ca53d9e4dfb9e6eb4bd601656e2d2af3bcc82069961af161cf85f0075418bc535569308ca742b217808743754431649ff24cad85e58a997b2b31735b76e27f4376395be10d71076b1861cfdbf247af9757e133b49ac5e490e9e85a1d114628734149cfedddbcf9cc601db110337fc98b25658efc8e8e82523448adb66ec19f169f42ff76d575e89ccced4728f010661d5d760cb61e5034d0923ddbde2c66b66f64dfc4cc71455c6affbec099c55af9b00df803ac81a8a60fcad08078af5ec45d45a5c260eb861aa1e27a3ef3b0c7f2326bbe9e18330f44c51927ffa633925749dc51d651770c37593f84c8660231cd80cf8a51618806aae21b78d8e6b3451e4a0e3173f67c3d3e666ec96b185b29bfcd3ec47e3277d087c9d74cfeb4d4cc493f5b125e91552f2887ec424ce48da82aa5d1cfc92db5cb5b79b771cc7c2fb37aaf659dab3bf08b9c41eb989a3da6f1077a53c5320d5112ddd5b3dfaa80ef8611da8719ab9b3442e5b938dc2c66b60fe450fad93dc84ee4c90a77168a95a43f2de682875596b9541f7903d751906580355559125535429373edf995ad9f15d824660665aecb9d1ce7d293bb7afe558ee38eae0f56f9a3c69318805a5e01f72fc3cfa408eb881958e652c5025923d41e3b95ac9839299f593a7131736c2f9ca879463c77b044f9037e4945ac5d79b3601962d076f36f3e030d86880054fd243b4e723ab29cf31ed76accec4996d7a7f240ee257247d7c41fd05c184f2fa00957cb960947d8e786d6e981bf9b8a2b155be78d9340992b1bc7ccfb6a9d39f7b9a9383a813bd694eca362e044cb849feafa3e0b54f8126cd3d1599eb9e324f9982b37233e663e9017eb9c27ab89cf78eee0a853f6c9b40c7eae60469edf3201a830589d4fd7cdf2e457b29795cf9c12338f613eba4e0f063288e38d29da3e79aca346d87dae5f6d011f4fc51462e18b4d7219654d5e6e9a9b211d77cde7a3d907afb92c9e3baace6f1ff894b2ee39800a77455dbcacb3a3d162713ace6dceb59e9598d9e572833527a72df75679230e84253ed9ee3af691375639f657302770084a83bb116e64196d9db1bcdd629df91ee6a36bff26441aa7216f4ca4d893576c575ef1cff5ec2356ef3fb4f4fd893ed8a60cbfb4b165b0b8362873465e5852ccec3a6e7ebfb5d0bb70fe56c89b3bc51b79a5b8a3eab5c6d725d821ca60608d74455b5c16b397867a8a4bd9609398d995721cb3d6a3f216b39faaa95bb3b965f8a7e18eaad7b30f302872471866f243765fccda0f28db85ebf29bcd6366d75b3a660f7ce8199c7533eb2e365cc137444196e14cb45e933f9730532d368b5c6b179f4ad7f5dd20cb6f25c5ccee48ceb9a3f619bd80313370c7ee835428f5f77909e69a65b63be70cd658d6b64fdf1915671de8e16b8806be45d37d749fdce7cd7233a425efdd89b95a98dbd54413bc6835736fa1160fd4bd28b5fee4be28be2c726703fd519f4e015aa3c6e6b5551665b892173e73c6e2c3de5d3766c66700417b5edd37e527606ef709b3c72acc80db199188e698d5d6592455280d71aa78b3c89d8df80351d0967eaac54ea0d7666afdc744034ec77d5f2f3743f92d8c99e59b3b05ce0cc44496c08b4e3277b500ad13d760c90158e6252c7d775688fe5ceaabdad03abc04a353dd9fb864c791fd0d5b10333f65694f52ccec7eb8c01ab04c29c7c8575a37930db00df2665ebe45ddfd5ae4ce86fcb914e50deee4b4b08f813525880616da9ce9e631b36cb9dfb2241b39d6c2416d980bda545de0cdbc5cbaf542f4e7527cf3e93a5a03f14519faec05a283989db56c2676d2f77b4a8e99417b1aee61f639b00f76a27ba7bac29b79e9bb4785e8cfa55a37ce671d359e2006e908b59e3a8bff0ec4ccf9f3992f96d799dde3ec73003fd6d6b689e78d295d8806567cd846f6b9841968960af67416bc5ac3eeeabd8913bee774642fa7df5a8d994f643fd5976a6f2fced45dfc14decccba57b3ae70e5a6843fbf4ad768a0a072a0333cca54bbd027b2c9f368e99076e8aa7e081ce763ccee4cdbc94c43e2f427fd45ad0716c1cade6aa2a37752006a0c2d9ebcc53b7953f665eca67f6dcb324ee2ab501251ee4e4cdbc348d75b6e4cfa5682ce98952e1a6ce8b1ac01c2dd7ce5ae25c2bf3a631f37eecfd5195d3a79e5428e7e4cc426133b75d88fe008364233a7280371d38a789cab9c8ebd7e4a3dbc9e9b7966266959be176978f07beb1aef3f1ab31dce80ab512decde04fd73d573eac00fe5c8941f48e0f9fb2cff5ee00296596cf39d67a626266fbc65d5c858798583dab00233b8e177dd19655f75c2a2b657108a220e40f968deda366f2f1efa0c8574099377f06d0ed45720a1b6ab62e1be15c6a810fe214ac5277cfd51d266d9d2c0deacf67aa5bf147ad050d379fa90ae9f633e75bc9f9cc46df202686592e7aa9451ea868742aea609d29cccb4ab935e845b4c527ff44fe6c181faad1d565f730dfd9346ba1256f73ecf794f00ca07ba9e329506298a7df83ea2d72e005e652a7a2ae59730a566aace9c32ed54c7563fe5cc168eee83be85bac1bca4727c7fdd1f933803a66bec67c6677a0b2bdc535c47df74b737054616d19f74129c0263ecce9b957ca32c8a135ed53d5d9a927dbad21daccbdde34661681ab72499a5a8997e64f62ea8ec1360f42adb8adc79905feb9b74bfce9e7b04c49e58c59f5ed73c678c9bdcbcc294c8c99dd57f71e2289c5981854d81d826da630aefa397c5456b9927b3caa3fd50c1dd6195e05e48cb5f840deb825772747ccdc885d6796eeee625fc3f1cedd32a8f0299ceb375cdbe6bc89c64ad3287f20124fb24e570cf578da3a739707a0a36df09e8feeeabef0396366d09e8abb1f9d6fbb47ee50dd5f02d6bc6cc999c5d2746773fd81d964699535e033cfacba8ef9b61b512d09f31ab88a32a8fa1446d7815b4d7f8e2b69af39f7c4ad191f235edd32447c256077564cbc49f916afea3979bcffce4ec01651ebbcf01a9fe9ec826db566a62276b0fe0c62018a593ccb7ef318bfc8173347f7cdf0b867abe3798e3b26cb14c9996851198077d6d4c2fcf016ccb93163ac2f5d3e60ea7d145b6a0d2f81f705bd942e44d90b19989e608c1fb27dae12bdd688993dd7f3dc23cf81a32aa5c9130b6f54d8b1546f18a979bebae3aced73a673904185f5782a4085617c5645496534affa3b8fdee5c6aebc112fe78d991dd71b83d65c16e29f92e7efc710716a76f092f7a018a4ed83ebb3257da771ab11c54e2062ab8b36af25fb3bef317c67c0abfdc627bc9e2766f69ebce782fcd38b38f5262a965bf8ffea898fb2b601f532bbb03eace29effd259511083549d110fd2eece792f0bef9b5023ed16f3c562f2992966f65edd1a68cff61cb97287de1bf4e0e582bf2b8ba17eba66416f9d9173b398bfba616968153e52b93f9a85191c04fe2cbfb3e4ca7bf7a6f2393966f6f8d67af32da6de0cd4eb546bd79c8755fd245f5c9e5b8b57a5dc8e3ffa4995ba7805ff779d3712f062de25c9cfec377be27dc43f03e87d7a5f5bf1e6d273bc199c675f5b26fcff4a6dd2f416ae8d6dcc9f16073fe27deb5daf7aebe4197a56c2bb930ebdba7d0cbe2b2e66de5477be45dd7bf31cbdc2b1700c760c114796de5eb0bb4df8036a7e0c31c891bedbb7b6a27b8de4f76fd9afdea177b418337bc79198397fd12a0c5a33d5aabed836d4194139ce951dafc51f95c5d486717c22fa10d36c382abdd3d477b8a9e8b1e9b54c3eb37716c6ccb9fd13ccc986708eaf5a85a36d2f62201bd61af9d1ec33277f5a2a02102f6a146f9e9d40fc69a7bfc34d7dbc8e3c525133684f17d8939f33dfee83f70ddebbbd145b57d588d22be46baa48267f74e69b00bb80d6548bd867d33bcf7c0f20fab40bafe7f5bde53825bee09346afde0c228cfe4a7b5574acb656c9b5cf37953f3af34da8fe38d5bbc86d6919656d6fe05db1abf93b93e26bdef3aebd1befd6bbcdc199ae5a1903ad512abc1c5b574599ddf3e9e6ef004ae00f58c6aa4b57f96e882b8b78b785da1bb4ce6b30bbbcf3eebda15762af89dc09bc1d6f379333ca326aa51958a39e6488f94e59cda4b69b65aff24765be89337105962925e55cac57a0f7d4ee6bdf6e5965027b656f0f3e156f9f5dad72871ff2b677e065e65a0898bfcb2a8cf9573d53586e1fa835f222deaab5c01ff5ac695994d5ea76ae8ca3ecd212d70c021875a7450c350f7b5ecdb7bc3ddff299cf7d5b8fb4703ee63bbef065066fd4cac629447c93d8fba1557517b7b8774745f9c33ee0e8d31c799ff98ad22e95655817dd793460339f29eb680bb9bee7fb7e80dcf147fed87f809e4fe64d573f6954f7263093efc7cfbf798d0ff27b1195ff92be5bd3227f22f3d72d0a66ddc0954e656939f7c71bf88f681d2a4ffe337be53d7fe2bff8af29fe49dd4398ba653d838afb4e55edfcc1a7b9cfbf057da6b2a69a2261078a55fe1450d4086db37b350220de88e1a1dcf1dfe6d681ffbefb53efda9ff91f89bcf906d69c026b1c1d0bc7acf5a91d63f4fe16b99faa54f75cd58c552b6cea77ffde7efe3ee79eca326ca87b2c89b93f0dffd3b742ee7c7915ffdbaffb495aa3c6d3a9acba476a3d35610e3660f7e22cb712f7f45e9403600d3e6994f93785f007e6aa308e8ef51818a62ba37f48da730423ebd83ff14ffd660c271a3aa7fe0174e67525160e79a3f677819ec8a7c40dabce3e613c9588d939af6d4bfee03e8f1d7d5fb79a23f7a7e1b7b475ce80396dbfe3c7dd7b5091f0a98ebceac93917d2950dbd2f4a9ef3bcd0f7e9d59a7b69ddb78c6ece1f959baab303fa6245859347a17f0ef6b9f07b7edfbf649fc0f126a86a0d8e33d4b967df2a075e65a4c4c4c273bd9928b5c91513f7f8148eae987da59f345afb3a37e28fcaf5aee9dd6dfa6a656c8d28ace10ffc2bffdabf991f4b2b97ea617d15ea6eaddec5e5cc6aca06c4bf35b5031e7c3aa2a3f6c163f760d53c7bc600b32927a84fccde8805ebf247ed4ba733534ba2c107ebcf6bfc5bff6e71d719b52392ce3650eb34670b4f23b500f5208e0a20a2bf863e99eabb68d9fb6a7c807f9a687def02d3b67a3e6d0dfeb4748ea1cac9ea027b36da0373b1e89c38ba0ea8d7e161e23942e435247d9f14f196d17cfc6127c0723d57068617b25389de1109679965bd9fdbd6c7d44f0b0e74945db60a7bbf5d267f7aa0050dcd1a75ff30fd59bc9c7d0cec1f90cfce99099c517accd56ad855da55ecbb9ed3f8c367e657751e7c01bfa6df64f1ad2d3329666d8007e0fbf01c3b108515fe66a524fe0057d5ea4a55ef295bc4fa4f4fbfc9a2a4229782d8df82c8eb05cf117cda0fbdeb39893f2aea83f154c8af72b54f785f74554c57d0da401d987da5720974a6c9f6c76b6966c7ec7194c49f62de1da7b338ae74943401df5c04fb7b3ad7a40f0a36b4ca45f0108ed1d46b4a03a1fe4f4efe6c5d940aeb5987de99ae9077ecf2a93e62033cf8d9f67b356816ccf45c55451767f1fbc526f167aba2d8df84b982bbceaa43aa6502b552037ddca115e5edcfb10711cba97a0e56ccc0fb25f2b060fef4d8a7ceb41aa88cb1625418e28b3e5c89ce2c2ee65d9a6abf10f7017838d3f7df53bf5b187f54e48a6b3f35f5bed442fab80e766eeba739db3abadefa3c211a3856f99c6acd285fbc5e047fd43e1c6ac549dd85835f2d648556ccf411cbecb3a87d3af55e1c53a5c26bacc26ebdfea3b97a0ebebba1df74befd2e926ae745b5f2d95723b4987d3a81d92f6a2d8972ffd73ac72df873c18e5586adbe3fb9e5130758f4ceae53508609e58816c043506138a28a72371ba11bf147cdc94edda168d0130705ccbb211ab874c730fffdcc9f69955a80d9e029eaa05e9f69fb74e6e18f93f22cf2728138b32b5ed5eab6be8e62f60a2f83ad4fa1a78bb18cda934edd199faa3c9f6dcf51d987e7dbb1a6a572e9dc23b5076631f7c9e018ea6d9dafe0bba74564a35b180d9cc211bb96daedab8073d4fc61d93b50892bef4ddd3de7997bd8e5e6e1b7bad707ec0f8ad971098ea8ee043dc846319699f3c7bf4f7bdadf6a7b8e5bd6efd8dd3a3f1afb18a28f57cf5177590ad12ee4a1ba7f58b5eacb59ba45f0c71fb2b4797a3d3d9f799dc267b2e497203e730b7a0bb67a8ef0ca9b8976e68e48f94b5ddf97bc9ef3c769f93b3fb57e8257a177f93af6dedc3244d7055946ed7beb1e79dfcbef46ddf8787a5f7d770c3c1ccc67bf9a3fbb7e990f7fc83a2d1d0d4c41bdbe8b9973ab8c3ff0dd257f0f22be42d620e1d3e35379a3f2e4f433c791366d9f8abfcf64e16f37c7dd53badec43d5fd8bf7e2bcea868c0fb76c7b26115a185fa1dbd100dbc92c2ae7c47dbe7404efd6a613ba837f4eada8518ca2afc6ab990b51f9d250ab1e314fab85bd0dbd87b6a9f195982119a922747fcd9f587d66141bee402c6d3b77b242ef58ae1d657a2328b614ea69ef06817c343f6a1ded1aba20159cde221f207ecb3efd7b6bc7ba3f7f7121db56381e6eaf67dac76bd55eff669c3cca32015d6637ee63e00b373652b137f0efc0af78335f74e5efc55aba9e73bdf85acefe9bd58d4ce912a9fb9181eea1dadca383bcabf5f6cc89f037f3fc88ea3578aca4dd5ef4c6eeb8ca342661d6a7723b5efadfbc0b7e534f150efb808dec93d8f57e174fe04dc3f08b89c3ac13a3ba8ab5fe5039d4b3715e5cd9fc758e061e08c60967705472c669db901aaeeca8628a9a784d7d89b68993ff829f39c5907309ea6faedb76dbd1359212aac72d0847ece9f7d16c343da9bed34e7fe88a9fc5123ccba0fec2cf6e97790374547e7784d0a89f85a94f9d657996f85bc958fb244715d7fbbbc9005feec060eaba5f5b1d506f677556ed75a3b4426f330d07b85bda87b5039df509d51d43b4e34b3718fbdad79f837ce2f0e34870e200adab392e201f53e1f75efa690f7c499cc37f5ae16765f0c0f693f3f9525bae13b7a178bf23d863f8150ff15cf814c99a9b60a51e1867ec3cf44cf92abebbda13af14a304b54ed19552d261a50b1b5cae954f609dc70841db0d2f20cadc8a29f3f9ce86c857e51d180f6dcea6d59a0c24544a56af60b637e204aea5e9eb68f40fee8e205b9dfeeb35ed1996f65fdb45621ecc7b717eb0ca5925af32d86873a8ffb4565546034f077549f0fd44c959d16338a22e542e7acab5c9d9762545867139575565ba30815c62c51bdcb9b3ac7e69c87217f7c2a813f64c5ad05b59c91fed5aede13a69079b79ae3d1d371c3627868d5294bf44567892eb4cff9830aad46983d2be24ac4b5d237f5abd22d26e7426789ea5c48fdf4faf63c6ce92cd117fd1459c27eb19a3f41c81d5d336fbb7539f601ecc7bd2acbc5bca11aa2d201f5703977ae7e4601152eeb2342bc94cc43e48f1e612333ca8231df34ffa2a53349750626cc2d0b7937aa7e0e563d6d3028e24de9c8431dad63de69c69c4cf327c21d5d3f780f1b446c1752bfe95254d5ce9185f4b1ba6fa3b244076ad7a6225458c71725fd1459275f9628f1c75f2cfc0de619f97f59c5200dfddcb97a4ead889c0bfdd658b5f7a57adbf0ba3917f145e7d25da92c5175273befdffdbdac3f4130825aad05e5ea2fe06a077ef55be5a6e67ca626ab8fd5bbbbe18898f956d09caca6dfb67aac76e35b8f87f1fc0106c53e1db96019a5c27d985956d533b1fa6d16db5e49cf2a03f7750666face33f979a8560cf5b387f84cf3dae788fc519c015d8ed68f696fdbd0796aa76a4f4db55f57019669a83df4e03a546cfd59c8932aa8c24db55bac38db3c4b34863fca3a7ef064f59d44cfac18cb3ed3f23ed7283dd9d0ab4965bd4b6711abf03dabad9e1056cf77e89cce2dd77f96b8438595524658ab98184467bea9a7886bf3fd11b72be25a54dd07954ba7335c0ad93f218c7b46c41d5dfbfbbc90acb904cb84fb2316b3eb2da9f0a968ab5db3741e47113c6cfebda83ff3f20c71f45df208dba298ccb72ebdf17dfb6336988a20ebea39d8edf6a58e587ba6de94256fb47d46cbdc212b8d0b7a9a24d2c7eaad6e6a7f449ded5a446eaacae9ac8bb67abaa9a82c51cdeca97455b6ebdf73fd21de44ca837356d47378fa89a9a9ba6b09aa5e486eaadea7b3a972e9d475143627d3fbd680ef39411e127f903bfe92a71f05d96f1bcb53602ca99deed43e074565608a867b24d47af3493159a22a5b192ca39e87bd8e3ebb14f2e739ae58f7dbadcbe9b7f78ed5fe884559064a984b579065544e671b8ed88d7b5774c89f450f4f16521a1dbc6cf6cba0994d60cd2545204558a62efade89385dd827753bcb5c507e52539f638cb5b57d56b913989a5faeef0f742e9de38e41858bca4dfd60f79ee339d22d883350202a7df5661095a6be455bdb07b9133bc2c4f31a79412dfd14c8147e15b3380a613f68e6a9ce2d2ee6b92f9567389337c12be5d2651c33863fc89d37f8ef73f00ef524cfbbfdf4f34d2fde4456b77b4375f4986ab764e0e1b0a03b923ae34f4c140f61de98f36f883fb1dcc1c2baa9abe02ddce942bd45a0b09db97afaf9a6a9d2cc62eec7eb3d3edac043f5ecea5ab35fe28fe14c409c79d61f55bfc1cc23e5b972f0b42acb708d8ca3f42bd16f2c7c71cb30873a2be68903bd1f5f55ede6af57aed7fcfb4cfebc07cffc327984e9f7f9169281a933df8610430ec520f26ed4ad2ca3ee78602edda6ab30c49ff788de3c2f177f9f0d0bb0406251efb3554f99aafde58b7a0a5cef54d850fb726d979f94c89ff7b0bc810679c5efdc807dac32dfd4fe88b21af76ed4f58bca03d677e4db52ed68b7755e88b6cf82dec49459302e28e7385a22fb2316b3070266898a6fb5dbe8e63b22a5f027ca9977b218d60f4c1668999ed91f917691dbda323aabadadd77c4ff57decc2ce35e44f3c6fc2c29e8af14f2af34dbfef67aa23d702ae80dee5d9573b7f16f43cc6327fde23dc795ee0ce5b588fb7cd2051fb2302fb1bee83bcd1591c5b9fbd8a05f42e4bea2d71c54403f9f8338b29cfec6ee35fd1fb23820a5f415453880aeb4c36958376259a45a870267f96f5e62de2e9c942fc6d932bd3996f2a97ee54bc14b5231268d7503464497c17140de4e14f1c67b0bc51fd60e57893f8bc28f6832e94d56e3405cdc95aea88948159d03a734efe247066b1b0a39c4735fb237e8b2bbd97671179f07581bb7d5d8a41f12a1c5f548ca9ed93cd9d99ae3fb2cf4cb3bfa332f836db1f318e87105b777007aa9f52e195d26335d9b0fa817ebf43843bc99652e5cb3a4f39aada3903f3d44a7a17b902f640d0fb23aabdee4a996f4a2faaa808a4197c07756bc7fab00631fc8972a6111cd2bfb09e24f79fdea7aa2b3a05e55c04e0b9310373f0f32a4cbfa972342f9c9ed515636b600d5897ec93c59b676d29551f89a477aeebfcfa62f28fadb2569a86dec1e8279fab9e9716abf19af5eddc08669db04765192cc49fb7087796398335161505fde499d6c173eb772cb1fb6276e6ca2e103d35f97d700ce3e9046c0356094ed03aaacee44f231c69ba4ecd5ad8e62c4ba0eb3a9f59baebbe297de3d26243f8b56736152c389d73265a883ff19c59e4ce2c684269153a53d567a9f7e92ce19b117f910aeb1ddfacbe73e35c581f518b18eeb02ff8f738853fb355eea09777f60abb8296de99ab0f9fb27e1b7861bb8aa49696b5679d0767560754f8248e332bfc49e38ce18d2987419b0f0a39cbb27ab31bee8ff88b54f88cb94c3a3df6a454389933ec8b754dbdc09f460267162dd709ba5b9d6543ef58705ae4fe88d9c55131d933cc007616fd532efec471a79950cec192cec6731fb5a7ec25cc2cf5fe88853c9b9ce73787a2ca66309e18faa765bd41aeac7227d49f65ceac72e782ac73a1718f8fd63f53710dfe692abed5eef2bf4a85c53593ec8e4dad9d79c4b75ef9dbe84f92de206716ebc3f5d682d88718ba0fb83fa2cea5fb059651d13c3b776eac8eb2cb2a6792f426567f92f5a619e1cebc3c07fddcbb9994c595da1f91de20ff0b2ca33247ac73501a6599b5b466c98a635542fe6471e61cec644a33e8d8d9abadb43f22bd41fe575806a24cd90d2e61fec496b5261a13a7e98daa559ba9ff8eea4fbcdec4a9f4733048f53b6dbd974343bdbbbba03dc6b24acb1939bde05359863d6ea63551de2cf0278d3b51decccb2cb882282bf96cdb949bfa8b6261760c4ab303fd7d1267993c9c89e34e447fd2f566d97217dacba7ed10504cee4f7669f113eb14bc135b55e1ed78b3c29f34bdc1d25ca80f83eb9fbc6f905d40d3be3d3fb889b7cc3a3e6a9933f33ae44f1a679a21672ec25a8fb0c267aab9b586d7824feb438fa69588af18de2cf0275b6f9a2bf56df0ec3dfc7aeb886beb34b8e307a957bca68f4ae24ef0181c117f92f526ca99c5a218f453bb02c670063ee7ee55706f4653713e6ab5b0afc00b5ac08121e94f5ece44cb79500a767e8d65c419ff10776abd3c8d3345e88db64c1bae6f089f5db24f928f4ae68e2a30c2d8e98fdea5534f51d5d979702725c6c13fe3a342ce8c83a3a01394c1327b689d608ff427993368893967a2f579a09eb3fa21cb381079079ff225596b368d896338330e2ac17e704056a986f5de127fb23883d63205e61981b24fd19cb9604369f3b19418ef6dc79974de04637f08ac698eac3967a275843fd97a33af4d698c5881777b5b4ec96907df3240cb64f9a8edf4465966c4218e3b58e28caaf7147754aded7391536fcc488bd61d8882b6b78c7a32ecda3a74f6e48b359dcf9f7e466f3467bca037b2b50aef2d73265a883f51ee247126ca9df9a7e3ef6cf9ec558f9db021b746ce4858959fe7cca27f5ad69b903b5876893fe97ab3cc19532b4bcd4632e59e6a96e7be7046ec42c5c230a27e94339a371e28cdb356e1610c6756b8a3ac15ea4f3667167913619073b6c9be9bacc64fd89d6010d764de65d9d6478d5c509aaf9117cb9905bd897227f45fe93e6a9533643518bfca3e87237fad7baa2d3e1b05fcdeea089827e0fce9e76262edb9d13f0de993ce19e28d29da3ee93e2a8937e16734caf986089d6b6a1d320191f0347b66b99dde90653a6a96b0648114bd59ae97f813cf9965eb2d7f9ef5dec8192aacde9c6d7d8f1c81b3cb939fd31badc295a0472a9ce9a36278135a2ec29fbc7a839cf1e85f07eaaf462c6d5740a7cd4ff83baa705ece6ca637649936cd12e239335ce24c1c777647632cc49f75f426ce5a0f811d67199571c46bc119a970b85ef31331b19e59827f1a7911adc9f6514b7ab3a4d4467f726b8de10e9cc32df491a95b815cba47d102461d5adf5657cad163be55accdd789f51c617ff444b1f09a3e2a5a1bde2cf0278533b17a13f7b9e2a10f1b3df393d124188831c5c285f9a855de0463e5b96196701b72269fdea470666ebdd138c29f4ccee8cf9c33a83d652a1d18612dab377a09582083aba00de73dcec799cd7c94f14f344b58b640b28f4ad59b58fe24f8a864bdd1add64299f9557b367a1d49f01bcffa1bcd605c14679679a3c7530b7ee16081353963e214ceeca2d5c83a7b217fd23f07099c090bb496c12aed603f3857ffd6f8363882be2e3eaea980653ae15acd0f70665e46667d3e878f4ae0cdb252df8ede420b3de719616b688d074af3acfb6853cec473678933510b25f127c64725f026a5dc065e313e0ae64fef416bf434b246d31c71cd9a3e2a863733c51d552bfb14c0190bad35e70e95aff811b6b67f6ac6f8a7ade29aa88f5ae64c127fa23171b6dea496375d0f83437fb8a9de68ff049e3bc13fad398fcaab3751ee8c3eb2f4398e37c97a13530e5647583eff04acd1fe69f4b9665c93eda3163993c21d65a9d03e0705e88de60c7067a8b953d6ff6ea93192ce9da5b8a6429ebb1c6b8135e751ebea8de24ca44ee04fb6dee4b0148eb0a61a61f9783372613c7516e64f05eb4d3667426bcd22fc498a89f372a66c38b3c41d5d431494a937face5c0bf4ef202316fe111fb5c49db1aea92cf26785376f397c54fc289b974e304ee54d25c63f153e8fcae24e9433cbfc89d79bbc3e2a8e332177cabaad12af3778672e585ddffbe198384d6f42ee7c6141fe6ce5a3e279131d71df268e8ec435153db33cd0568ce7cd363e2ab7de24f1465b2ca23f9be94d126716af1a74651c896b3ced9fca7094698205d6f351ebeb4d126722bc89e74f5c4cbc266f96b9a3cb88d3bd04b55e7ea0db16e39a9fd39bf5798316c3fa6bce9fa2f426f6d3d2eb11079199e5fa7a53584cbcc29918deacf2672b1f15c319b2da10b9b215673658b7599b3773cea8baaead3333fcc9b4c45a7ab3fcd13ab37c67ae807b0b5beb4d026756f953808fb2563913ad1378b3ddba4d413e6a9933510bfdfd7fffe7bffe1f92e4fedb + + + + + kcolorbutton.h + + diff --git a/kopete/kopete/addaccountwizard/addaccountwizardpage3.ui b/kopete/kopete/addaccountwizard/addaccountwizardpage3.ui new file mode 100644 index 00000000..8d2edc1e --- /dev/null +++ b/kopete/kopete/addaccountwizard/addaccountwizardpage3.ui @@ -0,0 +1,153 @@ + +AddAccountWizardPage3 + + + AddAccountWizardPage3 + + + + 0 + 0 + 600 + 356 + + + + Finished + + + + unnamed + + + 0 + + + + TextLabel9 + + + + 3 + 5 + 0 + 0 + + + + <h2>Congratulations</h2> +<p>You have finished configuring the account. Please click the "Finish" button.</p> + + + + + WordBreak|AlignTop + + + + + layout3 + + + + unnamed + + + + mUseColor + + + Use &custom color +for account: + + + Use a custom color for this account + + + Account are often differentiated by the protocol icon. But if you have severals accounts of the same protocol, you may apply a color filter to that icon to differentiate accounts from the same protocols. + + + + + mColorButton + + + + + + Account custom color selector + + + + + spacer5 + + + Horizontal + + + Expanding + + + + 101 + 21 + + + + + + + + PixmapLabel1_2_2_2 + + + image0 + + + false + + + + + Spacer8 + + + Vertical + + + Expanding + + + + 20 + 58 + + + + + + mConnectNow + + + Co&nnect now + + + true + + + Connect right after Finish is pressed + + + If this is checked, the account will be connected right after you clicked on <i>Finished</i>. + + + + + + + 789ce57d5973ea3ad7e6fdfb2b4e1dddbdd5e58f84904075f50564820009848c74f5856cc90364200364f8aaff7b2f692d19039e0067effd9e2e4a47fb3922c65e7af4ac257959feaf7fff75d7ebfef5effffad7db3b7f0f9cbf1c9fbffef56f317d7cfcfadfffe77ffdf7bffeded9dbfd6bb756faeba05cfe6bf7effff1afbfadc7bf9cbfd86e696fafc2357ed0b8b65722cc08537bffdde07dfc7b4eb84ad825ec10b6152eef84ed4e88f1f892b0f9be0831b67b882b25c2cf061f94351e11ae50fbd8606c77f5f997e1fc2afaf8fc21c4fafb421076103b55c415ba7eb663309ddf5388f1f77c83e97c3e099bf3090cc676764bb846ed1383e9ef5f426c6b7c4c5850fb7b8875bb4dc7dfdf411cbc2abcb7b357dadbd1d7ff4598ae5f5e1176c81e2cc4fa7c383718af5758888d3d9ca1c1d81edc13ae507bd960b2d75188d1de2383e97ae8fc8c3dd8bbc1d4fe1a62b4c72561638fb710633b9d1fd803db4b21d6edecc6e0aa6eb77dc215b2e7bdc1d82e0cae21f6f1787c6f6f4f9faf7742b88a583c198cf6f5f7083bd8ce760d26fb7f8658b7bb75c4606fb4ff6788f1f83d83d1be927edfd85fdc194cfd771062dd2e25e11af5cf34c4badd367f4ff6e7e6ef05f1b56230b63b7e88917fa788f777a83f1f0dc6efcbd710637f74081b3edc8758b73b2d83d1fed63e61c3976b83f1efc5b3c1d47f5f21d6c79374bdfb02b14fd773b08338e006d7f63476095710fb9a6f95dd72a954d2e7778038e403b59bfef566884d7fbaa721c6f1726d30f687f74db842df9f8518bfff6630f5e70b61eacf910831f6d70761eabfd1a3c164af3162d35fd689c1d41f03c246bfba21c6fe730893fd837188d1befb06637fb007c2f4fb2337c4687fcf60fc7ef014626ca7bf37fde5ef1a4cfd43fd61fa2b102176341e11ae51bb1f626c7f272c10fb5aff2adcf4b7b747780fb1ff45b84af831c4556d5fb467a8170e1d2fd45bfabee187d33498f830214cfdef39846ba4176d83f1fbee116181ed72cf606ce7e310633bf24d8d57d4f3bb10ebf6e0c660ea2ffafd7da3e7cc60eaff9310a31eef10267eb0af10637f3e1a8cfdcd2f09131ffca1c1d44ed77760fc5dc5606c1ff11063bbf97e05f188aef780f420f83298f8108418f9b04f5850fb7388b1fdcd608eed0dc4d51d6a9f194ced17842b887d7d7efbbb217fdc106bfef867841dc20c31d04f63e7de60f4f7de98f01e62e18518fbeb9630f92bd6430c7c42fe9409135fc42b62c30ff725c4c83ffabd7de36ff6438c7a32234cfdef5c8458dbdff3091b7fde0931eacb14b1e96ffe6630e9c34788916f5784e9f759c960f2dfe510231fde0dc6feb5ee08937e066d83a9ffc95e211f5a21c6fea6f30ffbffd060eaff2661eaffe03cc4fafb239b708dda6f438ced166141f8cd605b7f7ff489b8b683d8a969cccb4eb9a631fe1e377cf19f0813df3cfafe9eb383fcfa3298fc19da07f46857f3959f1a8c7c728788437da813267bba5706537c4ee7b36ff4283098f8d0451c8ef74fc2c6ff3c194c7c9810367ae28718bf7f48988eefdf1b4c7ad0475c257f327a0831f667c960ea1fba9e2af997119d8fe9bf911762fcfb03c2d47f41d960ecaf80ec67fa6f340db1d0ed7b842bf4fddd10ebf6d18c700db1a37fffa06cf4c07f475c29eda0de5c8418fbff9e7005dbbdb2c1d4dfb6c1783c59456cfa4b4c111f90bfb00d367c681b4cf1e06e88b5fddd5bc2a40fce8dc1143f1c11267fc0bc1063ffd51157491ff87188b1dd3198c6ef35611affa391c1d47f92b0f10774bda1bdc784c9dea30f839d8ac6743e3541ed5f0663bb83d76f97ab656d4fe788308d571bed6187fa5e415c11d85fac6930b6db6788f74dbce011a6f1e53e2036f616a784c9de828e7740facb3f0c267b8b10a3ded2df1b7b3b7b21c6f1563598ec4de75725fd0d060693fe5e1336f391a1c114df55438ce38d1126ff2b74ff56cbe53de4ab731562d4bf57c2646ffe85b852417bfb1ee2fd1db2ef04f1c10ef947df60b2678930e9993498ec252e111bfb04738cf3911bc2a45f6c1662b4cfb1c178fdd2266cf4ecce606ce7747d55ea6f7b6430b6b3d3106bfbf10ee21a5d8f13184cf6f50d26fbe2f9d865d20be7ce60b2f73e6163ffcf10239f6b84ab88bd27c49512f98f6fc4fb3bc85fff8ab0c0fef2e9f70f76f0efc5036133ff7b435c257fe13d1336f3f9ab1023dffb84c99ed6aec1682f41e75325be3b3d83c99e74fc1af1db7b08b1b61fb3099bf590db10637cd13598e61b3b06937f217bd5c89f08dd3fb5bdd0fe8706a3bded2ae13d6a7f3198fac30d31f6c70de12ab6bb32c4badd3d21ec507fbd22de2b617f786784f710bb1f8449bf3cd760d2af4fc4a6bf5999708d8ed73798f8503198e28f63c4861fee2e62d3ffde25e22af90bef2bc4d8df25c2d47fa28bd8f41f3b0fb1ee0fd1206cf4e93bc4e80fa60663ff5864cf1acdc7b817629cef550dc6bfb7fa06e3dfb337c2147f073b06131f6488912f35c47c07b1a3f5b4e698feb6f70957910ff681c1d82ead10a33e4e089bf84f1a4cdf7f0bb1febe6c198cfd278e10ef113fa56730f183ce2fe4cb28c478bc2fc255e24f8fb0f93d3aff4a09dbdd5783915ffc89708df88be3c5d9df217e71c2a48f1ef6bf63f4dca7f6ea0e5eaf4fbf5fa578c7ad194cf3893661817cf5070693fedc1a8cfc73a93f42bdb00913bfc44b88b17f9b848d1e55438cfa6d194cfc1913a6ebf35f0da6bf1f8418f9776e30febdc3428cfa45f6e634ffe3f706e3f7ed5988917f6784cd7ce6d5609a8f687de07b864f7605b1e18b7d6d30b50f081b3d7b3698f48a1b4cf15383b0e1fb95c1f87db14798f4490e0da6fe75101b3ec90961d3ff3dc2a44fbc4958903fda451cea139d8f89c7fc6fc4b51d8a37ce0d463eb99f84cd7a638b30f1c7ab1a4cfd593698fac343cc8dffec1a4cfa766c30e9c97788757f499730e919932146be1c10e6c807bb6430cd676c8369fe42f6e392fa5ff3853ba6ffec3e61d35f970693bf12848d1e3d194c7f8ffde984f39716619aafc863c2a6bf1cc282da0706a37f61743efb145f48c760ba7f42e7bf4feb6fb24998e26f7e1e626cbf3718edc5d0dece418dfa7f8ab866f46948b842f1ce09613a3f798a98937f72ef0dc6fef276091b7d9221c6f5b40f83893f1d83c99f3408738a972606633b7f3718fb9399df97a4171706633b1f21b67769beca428cf3533d3fb2f7c2febc406cfad3ee194ceddf842bd45e3798da1b846bd82e1e119bfe7477080b1aff6d83e97eca3ee203b3de7519621ccf7b88abe4dffd67c49cfacfef10e6148fdc184ce3fbd160b2974f58d2787d3098faefd0608aff8606937d6b888d7d6537c4dabebc4f789faeaf61307edf3a0cb1febe45d76773c4b6d6175b84f63d274cf6751c83a9fd8930cd4fedaec1d8cef0f8c2ccaff8b7c1f4f75f84a97fbd23c2e6f88784496ff98dc1687fd6426ce607f2c8608a37a684c93fcb3162335f9075c2343f967d83e97ee13b62be43e3fbd060d2eb6bc2d4fffe29627b97d623ba0663ff8b196163ff2661b23f1f1a8ceb05e28cb0a4f61383b13db0103bbbb4bea07f1fe4d0d8ff12b1b1bffd6830e9e13361b23f170663bb27099bfeef1a4c7a6d8718fbc71c8f7edfb6438cfe32c4a47f1f880f0c1f3a0663bbbc234cfa289f428cf39923c2f4fbf2d260f2df17884d3c26de119bf9af7b6330cdcf1961d24f796d30cdc7cddf537ce6d2f9193e78fb8425e9fd0b627b9fe2c303c29cd6333961d203f66930f6276b207676e9fed1bec1a4479f06139fda06d37a5410626c2f13dea7e30f43acdb5decbf707cc90ae2038a5f789d30f1c3b93418db99f9bee1c7a3c1e42f2f08131f1cf37db39ef510628cd7a7884d7cce5b21467def188cdfb7e9fcab153afe89c1e84fed63c234ffb4770da6fbf3738cc733bf47fec3ed1b4ceb6d4dc4c69fbb2d83295e32ed86bf658369bd86ceaf66e6c31706137fcf101bff2f0d36fca4ef1b7fecfb88ed5de21ff597cd697e30232c49affa0653bc80e31ff484be7f6b30e9cf8030f1c51911e688472f060be4974b58623bab198cedb68558ec22167a3c8a8ae92f5e266cc6f7a7c1942f7118621ccf55c2c42fdb3198e6ab2e62d33fce2d61b35e5d236cd6233f0da6f5ed53c2c407716130dddfb80e31e69770c4263e730c36f1c15388b13f7dc2d49f9c8e67ef92deb821c67850188cdf77df0893bf1715c2e43f5c0bb1e94fff9230c577eea1c1d85ffe1d618a47f8d460ec2f3e436cfacfaa13de47ecb4432c353f9e0973c41ced2f0dffb9446ce22b7b4298f445f60c26fbbd13267bda8230e9af1c194cfa4dbf67ec297608efa33d9d6fc21cafd73d3618ed257cc2640fb78bd8e82d7b21bc4ff7ff1f0ca6fef10cc6ef7bf4fb0e8d4fd6262c299f686230e9fd38c4c857e48734f6652784c9becebbc1725ff7cf056149ed2583b19de9f10d07377a3f214cf6f41862a32fde21e17dd2f706614efef9c560d2f7b6c1b4ded63298e69f6784cdef750cc6bf77760cc6efcb1a6287e2617b4c98f8eff40913bf050f31f2db264cc7171f06d37809dbb1bf64d88ee343760c46be7b25c46297d6131e0dc6bf779b06933e8e08ef93deb5438cfee5da60ca0fb93298f4e2dc603a1f61308dc72a614efeefc3606c970f21d67c90645fc30f1e188cfcf0881f7297da4b0613bf7a84f711072f21c6efebf98094862fde3b61b2bf87f680f144b84e98becff609139fdc03c482f4cfde3118edcde718fdd53161b2971f10e6b49ee7184cf9575383d15e3e9d9fa0f129a4c1680f7762305ebf7f83589af8bc6330d99b1b8cdfb71961b21ffb08b1febef74298ce875f194cf66f85587fdf6a1096647fade7eebeb11fab8718fd9d242c69fda48218f88ced6f8449dfd81561f3f7aec1c4b757c2743c16184ceb2fd42ee9f8bc6730b55b84e9f7bc73c2a4175e3bc4baffbd538371bc7b8d10ebfef6ccf1687ee50521467d0f8f4ff6168469bc7b8f06937f1b1b4cf63e33d8d5fdc1d11eaeb10f7f216cec8bdf77c3eb3f3498ae9f11a6ebb76a06637bffdd6271c5e2f05f6e6a28764c6dc337928a43b58022a98e2f3614178a5c2a22520bcbcb55e61f3fa63625c82c2328636daf07b8cec7242bcdad654ad46a11eb71b21627ab2cd7a6c8258bc55bce8d2972a1466bc8dcd68afb284b8dc822e3382badf267e9ea633993c21b27526773862f5df53267e4dabc79da8a3386379a3baad6fcc9c99b04cee0ff4be78ce14d3a67e2782397ea2867d2b893fc79a63aca9958eeccf9a35953acdea473a658bd89e3ccd346bc19476a558264fe6cac3771bc594f6f56392372712699378633997ab330c246649f4ccefc32bd59df4f6de7a3bc25cea83a30dc09fdd7cfe84d3667d6d59bcdfc94b15c3a6762b813f2278e333c436bb6e3cca67a93eca30ad29b857a62f4e71feda3d6d29b65aba1fe6ce0a39cb57cd4b67a931d136fc69915bd519c8916c39f141fc56338537c4c2cac6d63e2f57c541c6f5647dc24d49f5f1d136fab37ebf9a974ce048b9c59e6cf1fa537c5f9a88df486ea891969c89f1c7af30f8a89d3f4264d7ffed131714ebd897267ae3f5be94d968f5a7f0efec331719adee8f212d693507ffe61eb366b702658e64cb40ef9f3bbd76d7e0d6732f54671467f5eb0d0fcf43fd547e5e74d8a8f4aac0d7ff2fba83f2dae491a316bf928d29bc9327722fcc9e3a336e14c3e1f953d8f5a7bdd268133b13e6a65e4cd3f2fda3edbfba8fcbc89e3ccfa7a933d8fcae7a3623993c19f227c543ebdc9f651f9f4664b1f15c39bd072da3eff241f954f6f523963bd52fd16f2e71fb76eb3e0a3d6d09be53ac29f7ff43c2aafde28ce58eff33ac29fcde7517fe0bacda67ab364ad77e2cf7faede64f126afde443813ad893f7f928f8ae74e1667b262e26cce442df86e0af1a7889838596f36bb07bead8fda486f748de525963fff1fc4c4697ab35c883f7f4cbecd567a1359b7595b6f229c89d4ef217ffef3d66d8af251b1bc31968bf0e78fc8b749e0ccf67a93e2a39639132dda3e7f50becdc63e6a6acdac0feb730dbd49e44cb426fe6cefa3a2dcd92c26cebc9799c0992fb0ccb755b71ad6a175b4898fa2fa78993b217ffe43ef2dc0e7c43ab59a56cb3a03eb9c596dab43ff3f8e33510ba57246d7c750bf93fefce7c5c4ca325debdcba20cb60e9597d1861ab7af3665d5a03ebcabab66eac5bebcebab78656295e6f12f5e74f5c278e5bb799c0b5ee58bb5679c132a6ec59951885deb70eacaa5563161466d5ac2ae3cc660e58e96d853bef9191769cc99f641ff5ebe7e0ea0a064c3009e369d532ba3097a95f5abce24be6b3802c538bd42336660fc0a564fe94427dfebdeb36d97a73c91ed9137b06156ec55b864a0bacf7b5a8ce6cc25ee6dc59a85fc1466f60a3e3b9de2cab74027ffe989898bd5b15366533abc93e522d43857db2afd067a9f2c1be597d853b919a35d8a11e690bbc81f216f2e78f8c89991a195fec881d6bdf9d69191a6127ec14349afc127b674dd6626731dc89d66deb9a75623d7c69893fd9317151f936297a0357f5ceba309ecec163276a4da2852ed82372078ed2637d76c906ec2a9e3b218794165d136f42eeb09b707ef1c7c4c4ec169466caeed83df8ee352d43236cc84a70a44fb6c3765999ed817d2a6c9fbda673087cda81e6507484cdf9f367f8a80a8ca7aa75ce6a192a9c5e7adce28c73283677b860975c3d99ee267167ce21ee59f78a33863b0bfeeb77c6c49f30121eb90ffea9c9832d2c43858ff8983ff047fe04dc51fcd9e3cf7cc25f12f527d421fe0a3629adf0e737c6c4ca3ff13776c7dff9747bcb60e133fec13ff917ff56dca152e70d7e98c921ce8f903b544a217fb27d54be98789d75e22ff6c58fad737e02bebb10cb5069b1637eca9bc89d90432db0d0593a87789b7762f8b3998fca5eb749d41b5061f04f569f8fd849a1963123accbcfc12222c21f552e6094a5ea10eff1bed6a09229a17db6d59bbcebc49f309e2ed929937cb0950aa759e78a5ff39b2877223a74cbef527d59438db018fdf90531b18e6aaafc9e0f79e9672c43fab3c37757b813165e563be3257188efb3871cfc293abfef931ff0375ee535dbfa49cba862339bdbf62a77e6853fdb8eda9d8a9f2d73c8766dcff69542db81f264ec46dbe7677d14a8b03de2ef9bc4c29b147b6c3f2471078bfd683f71613fdb13fbc596f6abe18efd66bfdbd3397796f85374be0dc4b0d609f7ed9975082afc435ab3629d0ffbd3fe4ae68e2af6137cbeedbaddb00fed23fbd83eb14feda6dd62dff699f2ea863b7690c99f4d7d945661f04f3556cb37ef2e4a99edb6dd49e30e943dbb6b9f83752eec9eddb72fed01942bfbdabeb16fedbb2877ecfb903f9baddbc4c53523ebcb1ad843bb04e3e9e2d75986b467c7de4de70e58e7097853b6f7c03eca3a15b0ccbe7d6057ed9a632167a2232c813f1bcda3742cec3b8c9ff0d9afd19a25eb30873b7606771eed73c781d1d57384e6ceb5231d69571dd7f11c1f3983056c35e7cff67a032aec04d6a13302dffd8bb466b13863e7c1794ce78ed69e73b0ce85239c277be03c3b136d9d17e7d5795be68eb2d2127f36f1514a858fd9051b3aefbfc732589ca9fd09d14d86df02ed51ca7c01d6e93b33bbe27ca8d1e57c3a5fabdc5175447fd69f477d5a53ab032aacc653eff7594615185bdf10f5e4f15bdff69e53771ace73689d43e788bd2f2b3316e2cfda7a035a73c2eeac433e2d6245a280d175ec9c64f8ad4be7546b8ff15ba0ccce04acf302cadc8c7246d54e4bd509fcc99a474dd99373c66ace2f8af87258a76373a79ba93d0dd09e8b88324f94df02ed39c77827ca9b25fee4d59b23e7c2e9f181d32e784562cbe2f49dcbec9859fbad7a44993f803d03e7cab98e7287dd18ee382db24f1ebdf1f8817363edf1198ca7dfa8c271c5fe706e73faadfa82dfda07ebdc39f78ebadb1cc31d65a5087f92f26d3ead236b669df399f39b3c77867598b3e374d3b547fb2d1d333bf5302a3c50dae3ec72e694adcb2867d447d5217f92f5e6d3fa70f6ac323bf9d915896d0a287365d398d9d9770e9ca1530506c47027a23fab3e0a223eab0b51cd1f6c1955d46a46e67c6b296686d125292aac394361818586ca16cbdc59e10f7187ed08e6f4584d47c2bfdd02a9dc19434098bad69316330b877f0ae10c61840dadaf450f6ffb682b6d9fb9dea8f174c387ce1f6f192c420a37db6f25c4cc87c243ee280e5947a83dcb25e48f076370c73ad719127fa00ac7159b093f07774cccfc043173681d116865d6a38b46d89be2cef247d9478cc498cdacf27f0667a8b4acb278108f5931b3f25b919879606266185d4f863bba2e5b5fcbdc11cf649f0928cdefbfe27c45b1fb42bc8857f126deb3fc564acc5c9b73070bfab0b94263adc7d7efbfea9c9c817e6c8a297c66a03d1f196b3da931b3f88c70c77cfc396fc4d79c3f7fc095e7b00c9f8a6f51170de4bad31187ebc6cce2086366712c4e16b983c53a5ae48eb2d01fcf1fb00c3b1113712a9a913b8a2dd14ae74e749d7929663e748ee6aa1cfd5853ab14e58eaaff60fe34b465ce80356dd1598c529da9ddd93c6656ca1cc71fb0d02572475be7f90fe64f8f9d305774445374996b2d65d7897371b145cc7c2e7a4bcc299b621de951f5352f7f1c7f5a3ce03350e1beb8e4b578bfeaf4950532b4e7dcc4cc641db3ce7c0531730c77421ff636e7ce9fc61ff60196698b81b882729d14a5425478933b66de8bc6cce0b75ec4ad8977568bb883115631dc11aa3cff21fc6959753e10435102b5394b5dcb6e899d3563e667a33d30a3d8e59f49dc218dd63e6caed07f007f2e9c118ca7328ca8093bc9fabe52e6cc98f9313e66167bea1d24ab9c59a80fac4bc31d55ff56fef420a63903dffd2daa3967372d51cba13db131b3b424935cdae26e993391ba4c23cc78f9dfc59f16f8a74f311355d14d52e1b8628f378f99c17d0995fd2b652277ee8c0f43eefc1efd016bb4c54cbaa221caa0c76bfcad90cec9a631b3f4a46f33d5333288e14e19b9837538c27e397f603ccdd83d78a72afb5c3bafa32547e9f7475362e61739960f64e5b2e2493c77a23e4c73e897f10794b7c9eed931ccbc73a8705c918ff2493e6f1633cb0928f3391deb42be44f566ce9db094e5eb2fe44fcf1981e79e88013b8619d4a6a3f2cab975ba9be566c837f92e79e4581fc9dcd1057dd8b39c927d7eca322d71cd6bc09763b0cc1a2a1c5784bfce3af362cc2c67f26321da6c8acf44eee8624d6dff67f9c34bca73c378ea8889ce06daea78ce54e6cecd50ca3c5f490565fe22659e1faf25bf13b983458db02fe0cf4fe84f0bc693b68c74ad762177875afaeefa9a3133adc2bfc83af8f5e5637eabbb82f1b30c9aa96a1f26a7c5f2879d404c53569c010e5f17c343d99087f268d398591e8bcaea71f94c9eac72262c07609fa9755fa8fef4ac3270a62c5d5e734685642fb43828baa8ca53d9e4dfb9e6eb4bd601656e2d2af3bcc82069961af161cf85f0075418bc535569308ca742b217808743754431649ff24cad85e58a997b2b31735b76e27f4376395be10d71076b1861cfdbf247af9757e133b49ac5e490e9e85a1d114628734149cfedddbcf9cc601db110337fc98b25658efc8e8e82523448adb66ec19f169f42ff76d575e89ccced4728f010661d5d760cb61e5034d0923ddbde2c66b66f64dfc4cc71455c6affbec099c55af9b00df803ac81a8a60fcad08078af5ec45d45a5c260eb861aa1e27a3ef3b0c7f2326bbe9e18330f44c51927ffa633925749dc51d651770c37593f84c8660231cd80cf8a51618806aae21b78d8e6b3451e4a0e3173f67c3d3e666ec96b185b29bfcd3ec47e3277d087c9d74cfeb4d4cc493f5b125e91552f2887ec424ce48da82aa5d1cfc92db5cb5b79b771cc7c2fb37aaf659dab3bf08b9c41eb989a3da6f1077a53c5320d5112ddd5b3dfaa80ef8611da8719ab9b3442e5b938dc2c66b60fe450fad93dc84ee4c90a77168a95a43f2de682875596b9541f7903d751906580355559125535429373edf995ad9f15d824660665aecb9d1ce7d293bb7afe558ee38eae0f56f9a3c69318805a5e01f72fc3cfa408eb881958e652c5025923d41e3b95ac9839299f593a7131736c2f9ca879463c77b044f9037e4945ac5d79b3601962d076f36f3e030d86880054fd243b4e723ab29cf31ed76accec4996d7a7f240ee257247d7c41fd05c184f2fa00957cb960947d8e786d6e981bf9b8a2b155be78d9340992b1bc7ccfb6a9d39f7b9a9383a813bd694eca362e044cb849feafa3e0b54f8126cd3d1599eb9e324f9982b37233e663e9017eb9c27ab89cf78eee0a853f6c9b40c7eae60469edf3201a830589d4fd7cdf2e457b29795cf9c12338f613eba4e0f063288e38d29da3e79aca346d87dae5f6d011f4fc51462e18b4d7219654d5e6e9a9b211d77cde7a3d907afb92c9e3baace6f1ff894b2ee39800a77455dbcacb3a3d162713ace6dceb59e9598d9e572833527a72df75679230e84253ed9ee3af691375639f657302770084a83bb116e64196d9db1bcdd629df91ee6a36bff26441aa7216f4ca4d893576c575ef1cff5ec2356ef3fb4f4fd893ed8a60cbfb4b165b0b8362873465e5852ccec3a6e7ebfb5d0bb70fe56c89b3bc51b79a5b8a3eab5c6d725d821ca60608d74455b5c16b397867a8a4bd9609398d995721cb3d6a3f216b39faaa95bb3b965f8a7e18eaad7b30f302872471866f243765fccda0f28db85ebf29bcd6366d75b3a660f7ce8199c7533eb2e365cc137444196e14cb45e933f9730532d368b5c6b179f4ad7f5dd20cb6f25c5ccee48ceb9a3f619bd80313370c7ee835428f5f77909e69a65b63be70cd658d6b64fdf1915671de8e16b8806be45d37d749fdce7cd7233a425efdd89b95a98dbd54413bc6835736fa1160fd4bd28b5fee4be28be2c726703fd519f4e015aa3c6e6b5551665b892173e73c6e2c3de5d3766c66700417b5edd37e527606ef709b3c72acc80db199188e698d5d6592455280d71aa78b3c89d8df80351d0967eaac54ea0d7666afdc744034ec77d5f2f3743f92d8c99e59b3b05ce0cc44496c08b4e3277b500ad13d760c90158e6252c7d775688fe5ceaabdad03abc04a353dd9fb864c791fd0d5b10333f65694f52ccec7eb8c01ab04c29c7c8575a37930db00df2665ebe45ddfd5ae4ce86fcb914e50deee4b4b08f813525880616da9ce9e631b36cb9dfb2241b39d6c2416d980bda545de0cdbc5cbaf542f4e7527cf3e93a5a03f14519faec05a283989db56c2676d2f77b4a8e99417b1aee61f639b00f76a27ba7bac29b79e9bb4785e8cfa55a37ce671d359e2006e908b59e3a8bff0ec4ccf9f3992f96d799dde3ec73003fd6d6b689e78d295d8806567cd846f6b9841968960af67416bc5ac3eeeabd8913bee774642fa7df5a8d994f643fd5976a6f2fced45dfc14decccba57b3ae70e5a6843fbf4ad768a0a072a0333cca54bbd027b2c9f368e99076e8aa7e081ce763ccee4cdbc94c43e2f427fd45ad0716c1cade6aa2a37752006a0c2d9ebcc53b7953f665eca67f6dcb324ee2ab501251ee4e4cdbc348d75b6e4cfa5682ce98952e1a6ce8b1ac01c2dd7ce5ae25c2bf3a631f37eecfd5195d3a79e5428e7e4cc426133b75d88fe008364233a7280371d38a789cab9c8ebd7e4a3dbc9e9b7966266959be176978f07beb1aef3f1ab31dce80ab512decde04fd73d573eac00fe5c8941f48e0f9fb2cff5ee00296596cf39d67a626266fbc65d5c858798583dab00233b8e177dd19655f75c2a2b657108a220e40f968deda366f2f1efa0c8574099377f06d0ed45720a1b6ab62e1be15c6a810fe214ac5277cfd51d266d9d2c0deacf67aa5bf147ad050d379fa90ae9f633e75bc9f9cc46df202686592e7aa9451ea868742aea609d29cccb4ab935e845b4c527ff44fe6c181faad1d565f730dfd9346ba1256f73ecf794f00ca07ba9e329506298a7df83ea2d72e005e652a7a2ae59730a566aace9c32ed54c7563fe5cc168eee83be85bac1bca4727c7fdd1f933803a66bec67c6677a0b2bdc535c47df74b737054616d19f74129c0263ecce9b957ca32c8a135ed53d5d9a927dbad21daccbdde34661681ab72499a5a8997e64f62ea8ec1360f42adb8adc79905feb9b74bfce9e7b04c49e58c59f5ed73c678c9bdcbcc294c8c99dd57f71e2289c5981854d81d826da630aefa397c5456b9927b3caa3fd50c1dd6195e05e48cb5f840deb825772747ccdc885d6796eeee625fc3f1cedd32a8f0299ceb375cdbe6bc89c64ad3287f20124fb24e570cf578da3a739707a0a36df09e8feeeabef0396366d09e8abb1f9d6fbb47ee50dd5f02d6bc6cc999c5d2746773fd81d964699535e033cfacba8ef9b61b512d09f31ab88a32a8fa1446d7815b4d7f8e2b69af39f7c4ad191f235edd32447c256077564cbc49f916afea3979bcffce4ec01651ebbcf01a9fe9ec826db566a62276b0fe0c62018a593ccb7ef318bfc8173347f7cdf0b867abe3798e3b26cb14c9996851198077d6d4c2fcf016ccb93163ac2f5d3e60ea7d145b6a0d2f81f705bd942e44d90b19989e608c1fb27dae12bdd688993dd7f3dc23cf81a32aa5c9130b6f54d8b1546f18a979bebae3aced73a673904185f5782a4085617c5645496534affa3b8fdee5c6aebc112fe78d991dd71b83d65c16e29f92e7efc710716a76f092f7a018a4ed83ebb3257da771ab11c54e2062ab8b36af25fb3bef317c67c0abfdc627bc9e2766f69ebce782fcd38b38f5262a965bf8ffea898fb2b601f532bbb03eace29effd259511083549d110fd2eece792f0bef9b5023ed16f3c562f2992966f65edd1a68cff61cb97287de1bf4e0e582bf2b8ba17eba66416f9d9173b398bfba616968153e52b93f9a85191c04fe2cbfb3e4ca7bf7a6f2393966f6f8d67af32da6de0cd4eb546bd79c8755fd245f5c9e5b8b57a5dc8e3ffa4995ba7805ff779d3712f062de25c9cfec377be27dc43f03e87d7a5f5bf1e6d273bc199c675f5b26fcff4a6dd2f416ae8d6dcc9f16073fe27deb5daf7aebe4197a56c2bb930ebdba7d0cbe2b2e66de5477be45dd7bf31cbdc2b1700c760c114796de5eb0bb4df8036a7e0c31c891bedbb7b6a27b8de4f76fd9afdea177b418337bc79198397fd12a0c5a33d5aabed836d4194139ce951dafc51f95c5d486717c22fa10d36c382abdd3d477b8a9e8b1e9b54c3eb37716c6ccb9fd13ccc986708eaf5a85a36d2f62201bd61af9d1ec33277f5a2a02102f6a146f9e9d40fc69a7bfc34d7dbc8e3c525133684f17d8939f33dfee83f70ddebbbd145b57d588d22be46baa48267f74e69b00bb80d6548bd867d33bcf7c0f20fab40bafe7f5bde53825bee09346afde0c228cfe4a7b5574acb656c9b5cf37953f3af34da8fe38d5bbc86d6919656d6fe05db1abf93b93e26bdef3aebd1befd6bbcdc199ae5a1903ad512abc1c5b574599ddf3e9e6ef004ae00f58c6aa4b57f96e882b8b78b785da1bb4ce6b30bbbcf3eebda15762af89dc09bc1d6f379333ca326aa51958a39e6488f94e59cda4b69b65aff24765be89337105962925e55cac57a0f7d4ee6bdf6e5965027b656f0f3e156f9f5dad72871ff2b677e065e65a0898bfcb2a8cf9573d53586e1fa835f222deaab5c01ff5ac695994d5ea76ae8ca3ecd212d70c021875a7450c350f7b5ecdb7bc3ddff299cf7d5b8fb4703ee63bbef065066fd4cac629447c93d8fba1557517b7b8774745f9c33ee0e8d31c799ff98ad22e95655817dd793460339f29eb680bb9bee7fb7e80dcf147fed87f809e4fe64d573f6954f7263093efc7cfbf798d0ff27b1195ff92be5bd3227f22f3d72d0a66ddc0954e656939f7c71bf88f681d2a4ffe337be53d7fe2bff8af29fe49dd4398ba653d838afb4e55edfcc1a7b9cfbf057da6b2a69a2261078a55fe1450d4086db37b350220de88e1a1dcf1dfe6d681ffbefb53efda9ff91f89bcf906d69c026b1c1d0bc7acf5a91d63f4fe16b99faa54f75cd58c552b6cea77ffde7efe3ee79eca326ca87b2c89b93f0dffd3b742ee7c7915ffdbaffb495aa3c6d3a9acba476a3d35610e3660f7e22cb712f7f45e9403600d3e6994f93785f007e6aa308e8ef51818a62ba37f48da730423ebd83ff14ffd660c271a3aa7fe0174e67525160e79a3f677819ec8a7c40dabce3e613c9588d939af6d4bfee03e8f1d7d5fb79a23f7a7e1b7b475ce80396dbfe3c7dd7b5091f0a98ebceac93917d2950dbd2f4a9ef3bcd0f7e9d59a7b69ddb78c6ece1f959baab303fa6245859347a17f0ef6b9f07b7edfbf649fc0f126a86a0d8e33d4b967df2a075e65a4c4c4c273bd9928b5c91513f7f8148eae987da59f345afb3a37e28fcaf5aee9dd6dfa6a656c8d28ace10ffc2bffdabf991f4b2b97ea617d15ea6eaddec5e5cc6aca06c4bf35b5031e7c3aa2a3f6c163f760d53c7bc600b32927a84fccde8805ebf247ed4ba733534ba2c107ebcf6bfc5bff6e71d719b52392ce3650eb34670b4f23b500f5208e0a20a2bf863e99eabb68d9fb6a7c807f9a687def02d3b67a3e6d0dfeb4748ea1cac9ea027b36da0373b1e89c38ba0ea8d7e161e23942e435247d9f14f196d17cfc6127c0723d57068617b25389de1109679965bd9fdbd6c7d44f0b0e74945db60a7bbf5d267f7aa0050dcd1a75ff30fd59bc9c7d0cec1f90cfce99099c517accd56ad855da55ecbb9ed3f8c367e657751e7c01bfa6df64f1ad2d3329666d8007e0fbf01c3b108515fe66a524fe0057d5ea4a55ef295bc4fa4f4fbfc9a2a4229782d8df82c8eb05cf117cda0fbdeb39893f2aea83f154c8af72b54f785f74554c57d0da401d987da5720974a6c9f6c76b6966c7ec7194c49f62de1da7b338ae74943401df5c04fb7b3ad7a40f0a36b4ca45f0108ed1d46b4a03a1fe4f4efe6c5d940aeb5987de99ae9077ecf2a93e62033cf8d9f67b356816ccf45c55451767f1fbc526f167aba2d8df84b982bbceaa43aa6502b552037ddca115e5edcfb10711cba97a0e56ccc0fb25f2b060fef4d8a7ceb41aa88cb1625418e28b3e5c89ce2c2ee65d9a6abf10f7017838d3f7df53bf5b187f54e48a6b3f35f5bed442fab80e766eeba739db3abadefa3c211a3856f99c6acd285fbc5e047fd43e1c6ac549dd85835f2d648556ccf411cbecb3a87d3af55e1c53a5c26bacc26ebdfea3b97a0ebebba1df74befd2e926ae745b5f2d95723b4987d3a81d92f6a2d8972ffd73ac72df873c18e5586adbe3fb9e5130758f4ceae53508609e58816c043506138a28a72371ba11bf147cdc94edda168d0130705ccbb211ab874c730fffdcc9f69955a80d9e029eaa05e9f69fb74e6e18f93f22cf2728138b32b5ed5eab6be8e62f60a2f83ad4fa1a78bb18cda934edd199faa3c9f6dcf51d987e7dbb1a6a572e9dc23b5076631f7c9e018ea6d9dafe0bba74564a35b180d9cc211bb96daedab8073d4fc61d93b50892bef4ddd3de7997bd8e5e6e1b7bad707ec0f8ad971098ea8ee043dc846319699f3c7bf4f7bdadf6a7b8e5bd6efd8dd3a3f1afb18a28f57cf5177590ad12ee4a1ba7f58b5eacb59ba45f0c71fb2b4797a3d3d9f799dc267b2e497203e730b7a0bb67a8ef0ca9b8976e68e48f94b5ddf97bc9ef3c769f93b3fb57e8257a177f93af6dedc3244d7055946ed7beb1e79dfcbef46ddf8787a5f7d770c3c1ccc67bf9a3fbb7e990f7fc83a2d1d0d4c41bdbe8b9973ab8c3ff0dd257f0f22be42d620e1d3e35379a3f2e4f433c791366d9f8abfcf64e16f37c7dd53badec43d5fd8bf7e2bcea868c0fb76c7b26115a185fa1dbd100dbc92c2ae7c47dbe7404efd6a613ba837f4eada8518ca2afc6ab990b51f9d250ab1e314fab85bd0dbd87b6a9f195982119a922747fcd9f587d66141bee402c6d3b77b242ef58ae1d657a2328b614ea69ef06817c343f6a1ded1aba20159cde221f207ecb3efd7b6bc7ba3f7f7121db56381e6eaf67dac76bd55eff669c3cca32015d6637ee63e00b373652b137f0efc0af78335f74e5efc55aba9e73bdf85acefe9bd58d4ce912a9fb9181eea1dadca383bcabf5f6cc89f037f3fc88ea3578aca4dd5ef4c6eeb8ca342661d6a7723b5efadfbc0b7e534f150efb808dec93d8f57e174fe04dc3f08b89c3ac13a3ba8ab5fe5039d4b3715e5cd9fc758e061e08c60967705472c669db901aaeeca8628a9a784d7d89b68993ff829f39c5907309ea6faedb76dbd1359212aac72d0847ece9f7d16c343da9bed34e7fe88a9fc5123ccba0fec2cf6e97790374547e7784d0a89f85a94f9d657996f85bc958fb244715d7fbbbc9005feec060eaba5f5b1d506f677556ed75a3b4426f330d07b85bda87b5039df509d51d43b4e34b3718fbdad79f837ce2f0e34870e200adab392e201f53e1f75efa690f7c499cc37f5ae16765f0c0f693f3f9525bae13b7a178bf23d863f8150ff15cf814c99a9b60a51e1867ec3cf44cf92abebbda13af14a304b54ed19552d261a50b1b5cae954f609dc70841db0d2f20cadc8a29f3f9ce86c857e51d180f6dcea6d59a0c24544a56af60b637e204aea5e9eb68f40fee8e205b9dfeeb35ed1996f65fdb45621ecc7b717eb0ca5925af32d86873a8ffb4565546034f077549f0fd44c959d16338a22e542e7acab5c9d9762545867139575565ba30815c62c51bdcb9b3ac7e69c87217f7c2a813f64c5ad05b59c91fed5aede13a69079b79ae3d1d371c3627868d5294bf44567892eb4cff9830aad46983d2be24ac4b5d237f5abd22d26e7426789ea5c48fdf4faf63c6ce92cd117fd1459c27eb19a3f41c81d5d336fbb7539f601ecc7bd2acbc5bca11aa2d201f5703977ae7e4601152eeb2342bc94cc43e48f1e612333ca8231df34ffa2a53349750626cc2d0b7937aa7e0e563d6d3028e24de9c8431dad63de69c69c4cf327c21d5d3f780f1b446c1752bfe95254d5ce9185f4b1ba6fa3b244076ad7a6225458c71725fd1459275f9628f1c75f2cfc0de619f97f59c5200dfddcb97a4ead889c0bfdd658b5f7a57adbf0ba3917f145e7d25da92c5175273befdffdbdac3f4130825aad05e5ea2fe06a077ef55be5a6e67ca626ab8fd5bbbbe18898f956d09caca6dfb67aac76e35b8f87f1fc0106c53e1db96019a5c27d985956d533b1fa6d16db5e49cf2a03f7750666face33f979a8560cf5b387f84cf3dae788fc519c015d8ed68f696fdbd0796aa76a4f4db55f57019669a83df4e03a546cfd59c8932aa8c24db55bac38db3c4b34863fca3a7ef064f59d44cfac18cb3ed3f23ed7283dd9d0ab4965bd4b6711abf03dabad9e1056cf77e89cce2dd77f96b8438595524658ab98184467bea9a7886bf3fd11b72be25a54dd07954ba7335c0ad93f218c7b46c41d5dfbfbbc90acb904cb84fb2316b3eb2da9f0a968ab5db3741e47113c6cfebda83ff3f20c71f45df208dba298ccb72ebdf17dfb6336988a20ebea39d8edf6a58e587ba6de94256fb47d46cbdc212b8d0b7a9a24d2c7eaad6e6a7f449ded5a446eaacae9ac8bb67abaa9a82c51cdeca97455b6ebdf73fd21de44ca837356d47378fa89a9a9ba6b09aa5e486eaadea7b3a972e9d475143627d3fbd680ef39411e127f903bfe92a71f05d96f1bcb53602ca99deed43e074565608a867b24d47af3493159a22a5b192ca39e87bd8e3ebb14f2e739ae58f7dbadcbe9b7f78ed5fe884559064a984b579065544e671b8ed88d7b5774c89f450f4f16521a1dbc6cf6cba0994d60cd2545204558a62efade89385dd827753bcb5c507e52539f638cb5b57d56b913989a5faeef0f742e9de38e41858bca4dfd60f79ee339d22d883350202a7df5661095a6be455bdb07b9133bc2c4f31a79412dfd14c8147e15b3380a613f68e6a9ce2d2ee6b92f9567389337c12be5d2651c33863fc89d37f8ef73f00ef524cfbbfdf4f34d2fde4456b77b4375f4986ab764e0e1b0a03b923ae34f4c140f61de98f36f883fb1dcc1c2baa9abe02ddce942bd45a0b09db97afaf9a6a9d2cc62eec7eb3d3edac043f5ecea5ab35fe28fe14c409c79d61f55bfc1cc23e5b972f0b42acb708d8ca3f42bd16f2c7c71cb30873a2be68903bd1f5f55ede6af57aed7fcfb4cfebc07cffc327984e9f7f9169281a933df8610430ec520f26ed4ad2ca3ee78602edda6ab30c49ff788de3c2f177f9f0d0bb0406251efb3554f99aafde58b7a0a5cef54d850fb726d979f94c89ff7b0bc810679c5efdc807dac32dfd4fe88b21af76ed4f58bca03d677e4db52ed68b7755e88b6cf82dec49459302e28e7385a22fb2316b3070266898a6fb5dbe8e63b22a5f027ca9977b218d60f4c1668999ed91f917691dbda323aabadadd77c4ff57decc2ce35e44f3c6fc2c29e8af14f2af34dbfef67aa23d702ae80dee5d9573b7f16f43cc6327fde23dc795ee0ce5b588fb7cd2051fb2302fb1bee83bcd1591c5b9fbd8a05f42e4bea2d71c54403f9f8338b29cfec6ee35fd1fb23820a5f415453880aeb4c36958376259a45a870267f96f5e62de2e9c942fc6d932bd3996f2a97ee54bc14b5231268d7503464497c17140de4e14f1c67b0bc51fd60e57893f8bc28f6832e94d56e3405cdc95aea88948159d03a734efe247066b1b0a39c4735fb237e8b2bbd97671179f07581bb7d5d8a41f12a1c5f548ca9ed93cd9d99ae3fb2cf4cb3bfa332f836db1f318e87105b777007aa9f52e195d26335d9b0fa817ebf43843bc99652e5cb3a4f39aada3903f3d44a7a17b902f640d0fb23aabdee4a996f4a2faaa808a4197c07756bc7fab00631fc8972a6111cd2bfb09e24f79fdea7aa2b3a05e55c04e0b9310373f0f32a4cbfa972342f9c9ed515636b600d5897ec93c59b676d29551f89a477aeebfcfa62f28fadb2569a86dec1e8279fab9e9716abf19af5eddc08669db04765192cc49fb7087796398335161505fde499d6c173eb772cb1fb6276e6ca2e103d35f97d700ce3e9046c0356094ed03aaacee44f231c69ba4ecd5ad8e62c4ba0eb3a9f59baebbe297de3d26243f8b56736152c389d73265a883ff19c59e4ce2c684269153a53d567a9f7e92ce19b117f910aeb1ddfacbe73e35c581f518b18eeb02ff8f738853fb355eea09777f60abb8296de99ab0f9fb27e1b7861bb8aa49696b5679d0767560754f8248e332bfc49e38ce18d2987419b0f0a39cbb27ab31bee8ff88b54f88cb94c3a3df6a454389933ec8b754dbdc09f460267162dd709ba5b9d6543ef58705ae4fe88d9c55131d933cc007616fd532efec471a79950cec192cec6731fb5a7ec25cc2cf5fe88853c9b9ce73787a2ca66309e18faa765bd41aeac7227d49f65ceac72e782ac73a1718f8fd63f53710dfe692abed5eef2bf4a85c53593ec8e4dad9d79c4b75ef9dbe84f92de206716ebc3f5d682d88718ba0fb83fa2cea5fb059651d13c3b776eac8eb2cb2a6792f426567f92f5a619e1cebc3c07fddcbb9994c595da1f91de20ff0b2ca33247ac73501a6599b5b466c98a635542fe6471e61cec644a33e8d8d9abadb43f22bd41fe575806a24cd90d2e61fec496b5261a13a7e98daa559ba9ff8eea4fbcdec4a9f4733048f53b6dbd974343bdbbbba03dc6b24acb1939bde05359863d6ea63551de2cf0278d3b51decccb2cb882282bf96cdb949bfa8b6261760c4ab303fd7d1267993c9c89e34e447fd2f566d97217dacba7ed10504cee4f7669f113eb14bc135b55e1ed78b3c29f34bdc1d25ca80f83eb9fbc6f905d40d3be3d3fb889b7cc3a3e6a9933f33ae44f1a679a21672ec25a8fb0c267aab9b586d7824feb438fa69588af18de2cf0275b6f9a2bf56df0ec3dfc7aeb886beb34b8e307a957bca68f4ae24ef0181c117f92f526ca99c5a218f453bb02c670063ee7ee55706f4653713e6ab5b0afc00b5ac08121e94f5ece44cb79500a767e8d65c419ff10776abd3c8d3345e88db64c1bae6f089f5db24f928f4ae68e2a30c2d8e98fdea5534f51d5d979702725c6c13fe3a342ce8c83a3a01394c1327b689d608ff427993368893967a2f579a09eb3fa21cb381079079ff225596b368d896338330e2ac17e704056a986f5de127fb23883d63205e61981b24fd19cb9604369f3b19418ef6dc79974de04637f08ac698eac3967a275843fd97a33af4d698c5881777b5b4ec96907df3240cb64f9a8edf4465966c4218e3b58e28caaf7147754aded7391536fcc488bd61d8882b6b78c7a32ecda3a74f6e48b359dcf9f7e466f3467bca037b2b50aef2d73265a883f51ee247126ca9df9a7e3ef6cf9ec558f9db021b746ce4858959fe7cca27f5ad69b903b5876893fe97ab3cc19532b4bcd4632e59e6a96e7be7046ec42c5c230a27e94339a371e28cdb356e1610c6756b8a3ac15ea4f3667167913619073b6c9be9bacc64fd89d6010d764de65d9d6478d5c509aaf9117cb9905bd897227f45fe93e6a9533643518bfca3e87237fad7baa2d3e1b05fcdeea089827e0fce9e76262edb9d13f0de993ce19e28d29da3ee93e2a8937e16734caf986089d6b6a1d320191f0347b66b99dde90653a6a96b0648114bd59ae97f813cf9965eb2d7f9ef5dec8192aacde9c6d7d8f1c81b3cb939fd31badc295a0472a9ce9a36278135a2ec29fbc7a839cf1e85f07eaaf462c6d5740a7cd4ff83baa705ece6ca637649936cd12e239335ce24c1c777647632cc49f75f426ce5a0f811d67199571c46bc119a970b85ef31331b19e59827f1a7911adc9f6514b7ab3a4d4467f726b8de10e9cc32df491a95b815cba47d102461d5adf5657cad163be55accdd789f51c617ff444b1f09a3e2a5a1bde2cf0278533b17a13f7b9e2a10f1b3df393d124188831c5c285f9a855de0463e5b96196701b72269fdea470666ebdd138c29f4ccee8cf9c33a83d652a1d18612dab377a09582083aba00de73dcec799cd7c94f14f344b58b640b28f4ad59b58fe24f8a864bdd1add64299f9557b367a1d49f01bcffa1bcd605c14679679a3c7530b7ee16081353963e214ceeca2d5c83a7b217fd23f07099c090bb496c12aed603f3857ffd6f8363882be2e3eaea980653ae15acd0f70665e46667d3e878f4ae0cdb252df8ede420b3de719616b688d074af3acfb6853cec473678933510b25f127c64725f026a5dc065e313e0ae64fef416bf434b246d31c71cd9a3e2a863733c51d552bfb14c0190bad35e70e95aff811b6b67f6ac6f8a7ade29aa88f5ae64c127fa23171b6dea496375d0f83437fb8a9de68ff049e3bc13fad398fcaab3751ee8c3eb2f4398e37c97a13530e5647583eff04acd1fe69f4b9665c93eda3163993c21d65a9d03e0705e88de60c7067a8b953d6ff6ea93192ce9da5b8a6429ebb1c6b8135e751ebea8de24ca44ee04fb6dee4b0148eb0a61a61f9783372613c7516e64f05eb4d3667426bcd22fc498a89f372a66c38b3c41d5d431494a937face5c0bf4ef202316fe111fb5c49db1aea92cf26785376f397c54fc289b974e304ee54d25c63f153e8fcae24e9433cbfc89d79bbc3e2a8e332177cabaad12af3778672e585ddffbe198384d6f42ee7c6141fe6ce5a3e279131d71df268e8ec435153db33cd0568ce7cd363e2ab7de24f1465b2ca23f9be94d126716af1a74651c896b3ced9fca7094698205d6f351ebeb4d126722bc89e74f5c4cbc266f96b9a3cb88d3bd04b55e7ea0db16e39a9fd39bf5798316c3fa6bce9fa2f426f6d3d2eb11079199e5fa7a53584cbcc29918deacf2672b1f15c319b2da10b9b215673658b7599b3773cea8baaead3333fcc9b4c45a7ab3fcd13ab37c67ae807b0b5beb4d026756f953808fb2563913ad1378b3ddba4d413e6a9933510bfdfd7fffe7bffe1f92e4fedb + + + + diff --git a/kopete/kopete/addcontactwizard/Makefile.am b/kopete/kopete/addcontactwizard/Makefile.am new file mode 100644 index 00000000..9289b747 --- /dev/null +++ b/kopete/kopete/addcontactwizard/Makefile.am @@ -0,0 +1,12 @@ +METASOURCES = AUTO + +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +noinst_LTLIBRARIES = libkopeteaddcontactwizard.la +noinst_HEADERS = addcontactwizard.h + +libkopeteaddcontactwizard_la_SOURCES = addcontactwizard_base.ui addcontactwizard.cpp +libkopeteaddcontactwizard_la_LDFLAGS = $(all_libraries) -no-undefined +libkopeteaddcontactwizard_la_LIBADD = ../../libkopete/libkopete.la $(LIB_KDEUI) $(LIB_KABC) + +# vim: set noet: diff --git a/kopete/kopete/addcontactwizard/addcontactwizard.cpp b/kopete/kopete/addcontactwizard/addcontactwizard.cpp new file mode 100644 index 00000000..9b1ca28e --- /dev/null +++ b/kopete/kopete/addcontactwizard/addcontactwizard.cpp @@ -0,0 +1,344 @@ +/* + addcontactwizard.h - Kopete's Add Contact Wizard + + Copyright (c) 2004 by Olivier Goffart + Copyright (c) 2003 by Will Stephenson + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2002 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +// CONDITIONS FOR PROGRESSING: +// Welcome page +// true +// | +// V +// Select Address Book Entry +// ( Addressee is selected AND is not already associated with a contact ) +// OR Do not use address book is checked +// | +// V +// Select Display Name and Group +// true +// | +// V +// Select Account +// ( Only 1 account ) OR ( An account is selected ) +// | +// V +// (Each AddContactPage) +// ( Own conditions) +// | +// V +// Finish +// true + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +// used for its AddresseeItem class +#include +#include +#include + +#include +#include "addressbookselectorwidget.h" +#include "addcontactwizard.h" +#include "kopetecontactlist.h" +#include "kopetemetacontact.h" +#include "kopeteaccountmanager.h" +#include "kopeteaccount.h" +#include "kopetegroup.h" + +AddContactWizard::AddContactWizard( QWidget *parent, const char *name ) +: AddContactWizard_Base( parent, name ) +{ + //QVBox *kabcPageVbox = new QVBox(this->page(1)); + m_addressbookSelectorWidget = new Kopete::UI::AddressBookSelectorWidget(this->page(1)); + selectAddresseeLayout->addWidget(m_addressbookSelectorWidget); + + // Populate the groups list + Kopete::GroupList groups=Kopete::ContactList::self()->groups(); + for( Kopete::Group *it = groups.first(); it; it = groups.next() ) + { + QString groupname = it->displayName(); + if ( !groupname.isEmpty() ) + m_groupItems.insert(new QCheckListItem( groupList, groupname, QCheckListItem::CheckBox) , it ) ; + } + + protocolListView->clear(); + m_accountItems.clear(); + + // Populate the accounts list + QCheckListItem* accountLVI = 0; + QPtrList accounts = Kopete::AccountManager::self()->accounts(); + for(Kopete::Account *i=accounts.first() ; i; i=accounts.next() ) + { + accountLVI= new QCheckListItem( protocolListView, i->accountLabel(), QCheckListItem::CheckBox); + accountLVI->setText(1,i->protocol()->displayName() + QString::fromLatin1(" ") ); + //FIXME - I'm not sure the column 1 is a right place for the colored icon -Olivier + accountLVI->setPixmap( 1, i->accountIcon() ); + m_accountItems.insert(accountLVI,i); + } + protocolListView->setCurrentItem( protocolListView->firstChild() ); + groupList->setCurrentItem( groupList->firstChild() ); + + if ( accounts.count() == 1 ) + { + accountLVI->setOn( true ); + setAppropriate( selectService, false ); + } + + setNextEnabled( selectService, accounts.count() == 1 ); + setNextEnabled( selectAddressee, false ); + setFinishEnabled( finis, true ); + + // Addressee validation connections + connect( chkAddressee, SIGNAL( toggled( bool ) ), + SLOT( slotCheckAddresseeChoice( bool ) ) ); + connect( m_addressbookSelectorWidget, SIGNAL(addresseeListClicked( QListViewItem * )), SLOT(slotAddresseeListClicked( QListViewItem * )) ); + + // Group manipulation connection + connect( addGroupButton, SIGNAL(clicked()) , SLOT(slotAddGroupClicked()) ); + + // Account choice validation connections + connect( protocolListView, SIGNAL(clicked(QListViewItem *)), this, SLOT(slotProtocolListClicked(QListViewItem *))); + connect( protocolListView, SIGNAL(selectionChanged(QListViewItem *)), this, SLOT(slotProtocolListClicked(QListViewItem *))); + connect( protocolListView, SIGNAL(spacePressed(QListViewItem *)), this, SLOT(slotProtocolListClicked(QListViewItem *))); + + // read sticky settings + KConfig *config = kapp->config(); + config->setGroup("Add Contact Wizard"); + bool useKABC = config->readBoolEntry( "UseAddressBook", false ); + chkAddressee->setChecked( useKABC ); + setAppropriate( selectAddressee, useKABC ); + // load address book, if using KABC + slotCheckAddresseeChoice( useKABC ); +} + + +AddContactWizard::~AddContactWizard() +{ +} + +void AddContactWizard::slotCheckAddresseeChoice( bool on ) +{ + setAppropriate( selectAddressee, on ); +} + +void AddContactWizard::slotAddresseeListClicked( QListViewItem */*addressee*/ ) +{ + // enable next if a valid addressee is selected + bool selected = m_addressbookSelectorWidget->addresseeSelected(); + setNextEnabled( selectAddressee, selected ); + + if ( selected ) + mDisplayName->setText( m_addressbookSelectorWidget->addressee().realName() ); +} + +void AddContactWizard::slotAddGroupClicked() +{ + QString groupName = KInputDialog::getText( + i18n( "New Group" ), + i18n( "Please enter the name for the new group:" ) + ); + if ( !groupName.isNull() ) + ( new QCheckListItem( groupList, groupName, QCheckListItem::CheckBox ) )->setOn( true ); +} + +void AddContactWizard::slotProtocolListClicked( QListViewItem *) +{ + // Just makes sure a protocol is selected before allowing the user to continue + bool oneIsChecked = false; + + for (QListViewItemIterator it(protocolListView); it.current(); ++it) + { + QCheckListItem *check = dynamic_cast(it.current()); + if (check && check->isOn()) + { + oneIsChecked = true; + break; + } + } + setNextEnabled(selectService, oneIsChecked); +} + +void AddContactWizard::accept() +{ + Kopete::MetaContact *metaContact = new Kopete::MetaContact(); + + // set the display name if required + if ( !mDisplayName->text().isEmpty() ) + { + metaContact->setDisplayName( mDisplayName->text() ); + metaContact->setDisplayNameSource(Kopete::MetaContact::SourceCustom); + } + + // set the metacontact's groups + bool topLevel = true; + for ( QListViewItemIterator it( groupList ); it.current(); ++it ) + { + QCheckListItem *check = dynamic_cast( it.current() ); + if ( check && check->isOn() ) + { + if(m_groupItems.contains(check)) + metaContact->addToGroup(m_groupItems[check]); + else //it's a new group + metaContact->addToGroup( Kopete::ContactList::self()->findGroup( check->text() ) ); + topLevel = false; + } + } + if(topLevel) + metaContact->addToGroup( Kopete::Group::topLevel() ); + + bool ok = protocolPages.isEmpty(); + + // get each protocol's contact + QMap ::Iterator it; + for ( it = protocolPages.begin(); it != protocolPages.end(); ++it ) + ok = ok || it.data()->apply( it.key(), metaContact ); + + if ( ok ) + { + if ( chkAddressee->isChecked() && m_addressbookSelectorWidget->addresseeSelected() ) + { + metaContact->setMetaContactId( m_addressbookSelectorWidget->addressee().uid() ); + // if using kabc link, and the user didn't touch the mc name, set a kabc souce instead of custom + if ( chkAddressee->isChecked() && (m_addressbookSelectorWidget->addressee().realName() == mDisplayName->text())) + { + metaContact->setDisplayNameSource(Kopete::MetaContact::SourceKABC); + } + } + // add it to the contact list + Kopete::ContactList::self()->addMetaContact( metaContact ); + } + else + delete metaContact; + + // write sticky settings + KConfig *config = kapp->config(); + config->setGroup("Add Contact Wizard"); + config->writeEntry( "UseAddressBook", chkAddressee->isChecked() ); + config->sync(); + deleteLater(); +} + +void AddContactWizard::reject() +{ + QWizard::reject(); +} + +void AddContactWizard::next() +{ + // If the we're on the select account page + // follow it with the add contact pages for + // the chosen protocols + if (currentPage() == selectService || + (currentPage() == intro && !appropriate( selectService ))) + { + QStringList usedAccounts; + // We don't keep track of this pointer because it gets deleted when the wizard does (which is what we want) + for (QListViewItemIterator it(protocolListView); it.current(); ++it) + { + QCheckListItem *item = dynamic_cast(it.current()); + if (item && item->isOn()) + { + Kopete::Account *i=m_accountItems[item]; + // this shouldn't happen either, but I hate crashes + if (!i) + continue; + + usedAccounts.append( i->protocol()->pluginId() + i->accountId() ); + + if(protocolPages.contains(i)) + continue; + + AddContactPage *addPage = i->protocol()->createAddContactWidget(this, i ); + if (!addPage) + continue; + + connect(addPage, SIGNAL(dataValid( AddContactPage *, bool )), + this, SLOT( slotDataValid( AddContactPage *, bool ))); + addPage->show(); + + insertPage( addPage, i18n( "The user has to select the contact to add to the given account name", + "Choose New Contact For %1 Account %2" ).arg( i->protocol()->displayName() ).arg( item->text(0) ), indexOf( finis ) ); + protocolPages.insert( i , addPage ); + } + } + + //remove pages that were eventualy added previusely, and needs to be removed if the user pressed back. + QMap ::Iterator it; + for ( it = protocolPages.begin(); it != protocolPages.end(); ++it ) + { + Kopete::Account *i=it.key(); + if( !i || !usedAccounts.contains( i->protocol()->pluginId() + i->accountId() ) ) + { + delete it.data(); + protocolPages.remove(it); + } + } + QWizard::next(); + return; + } + + // If we're not on any account specific pages, + // we must be on an add account page, so make sure it validates + if (currentPage() != intro && + currentPage() != selectAddressee && + currentPage() != selectService && + currentPage() != selectGroup && + currentPage() != finis) + { + AddContactPage *ePage = dynamic_cast(currentPage()); + if (!ePage || !ePage->validateData()) + return; + } + + QWizard::next(); +} + +void AddContactWizard::showPage( QWidget *page ) +{ + if ( page == intro ) + setNextEnabled( page, true); // make sure the first page's Next is always enabled + + QWizard::showPage( page ); + + if ( page == finis ) + finishButton()->setFocus(); +} + +void AddContactWizard::slotDataValid(AddContactPage *onPage, bool bOn) +{ + // some plugins emit dataValid when they are not visible. + // so we need to enable the page which is signalling, not just the current page + setNextEnabled( onPage, bOn); +} + + +#include "addcontactwizard.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/addcontactwizard/addcontactwizard.h b/kopete/kopete/addcontactwizard/addcontactwizard.h new file mode 100644 index 00000000..427bb1e3 --- /dev/null +++ b/kopete/kopete/addcontactwizard/addcontactwizard.h @@ -0,0 +1,85 @@ +/* + addcontactwizard.h - Kopete's Add Contact Wizard + + Copyright (c) 2003 by Will Stephenson + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2002 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef ADDCONTACTWIZARD_H +#define ADDCONTACTWIZARD_H + +#include +#include +#include + +#include +#include +#include "addcontactwizard_base.h" + +class AddContactPage; +class QCheckListItem; + +namespace Kopete +{ +class Protocol; +class Account; +class Group; + +namespace UI +{ +class AddressBookSelectorWidget; +} + +} + +/** + * @author Duncan Mac-Vicar P. + */ +class AddContactWizard : public AddContactWizard_Base +{ + Q_OBJECT + +public: + AddContactWizard( QWidget *parent = 0, const char *name = 0 ); + ~AddContactWizard(); + virtual void showPage( QWidget *page ); + +private: + //Kopete::Protocol *currentProtocol; + //AddContactPage *currentDataWidget; + QMap protocolPages; + QMap m_accountItems; + QMap m_groupItems; + Kopete::UI::AddressBookSelectorWidget *m_addressbookSelectorWidget; + +public slots: + virtual void accept(); + virtual void reject(); + + void slotProtocolListClicked( QListViewItem * ); + + void slotAddGroupClicked(); + +protected slots: + virtual void next(); + void slotCheckAddresseeChoice( bool on ); + void slotAddresseeListClicked( QListViewItem *addressee ); + void slotDataValid( AddContactPage *onPage, bool bOn); +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/addcontactwizard/addcontactwizard_base.ui b/kopete/kopete/addcontactwizard/addcontactwizard_base.ui new file mode 100644 index 00000000..d5c31826 --- /dev/null +++ b/kopete/kopete/addcontactwizard/addcontactwizard_base.ui @@ -0,0 +1,487 @@ + +AddContactWizard_Base + + + AddContactWizard_Base + + + + 0 + 0 + 579 + 417 + + + + Contact Addition Wizard + + + + intro + + + Introduction + + + + unnamed + + + + TextLabel1 + + + + 5 + 5 + 1 + 0 + + + + <h2>Welcome to the Add Contact Wizard</h2> + +<p>This wizard will guide you through the process of adding a new contact to Kopete.</p> + + + WordBreak|AlignTop|AlignLeft + + + + + textLabel2_2 + + + <p>Kopete shares contact information with the KDE Addressbook. This gives you seamless integration between instant messaging, e-mail and other personal information management applications.</p> +<p>If you prefer not to store instant messaging information in the KDE Addressbook, uncheck the box below.</p> + + + + + textLabel1_3 + + + <p>Press the "Next" button to begin.</p> + + + + + PixmapLabel1 + + + NoFrame + + + Plain + + + image0 + + + false + + + + + spacer14_3 + + + Vertical + + + Expanding + + + + 20 + 50 + + + + + + spacer14_4 + + + Horizontal + + + Expanding + + + + 425 + 20 + + + + + + chkAddressee + + + &Use the KDE address book for this contact + + + true + + + Check this box if you do not want to integrate other KDE applications with Kopete + + + + + spacer9 + + + Vertical + + + Expanding + + + + 20 + 91 + + + + + + + + selectAddressee + + + Select Address Book Entry + + + + unnamed + + + + + + selectGroup + + + Select Display Name & Group + + + + unnamed + + + + textLabel2 + + + <qt><p><h2>Select Display Name and Group</h2></p></qt> + + + AutoText + + + WordBreak|AlignVCenter + + + + + textLabel1_4 + + + Enter the contact's displa&y name. This is how the contact will appear in Kopete: + + + mDisplayName + + + + + mDisplayName + + + Leave this blank to use any display name set by the contact + + + + + spacer14_2 + + + Vertical + + + Expanding + + + + 20 + 65 + + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + TextLabel1_3 + + + Select the contact list &group(s) that this contact should belong to : + + + WordBreak|AlignVCenter + + + groupList + + + + + + Groups + + + false + + + true + + + + groupList + + + true + + + A contact may be present in more than one group + + + + + addGroupButton + + + Create New G&roup... + + + Click here to create a new group + + + + + Spacer6 + + + Horizontal + + + Expanding + + + + 407 + 16 + + + + + + + + selectService + + + Select Instant Messaging Accounts + + + + unnamed + + + + TextLabel5 + + + <p><h2>Select Instant Messaging Accounts</h2></p> + + + WordBreak|AlignVCenter + + + + + textLabel2_3 + + + Select the &account(s) you would like to use for this contact from the list below. + + + protocolListView + + + + + textLabel3 + + + <p><i>Note</i>: If a messaging service is missing from the list, please make sure you have created an account for it in Kopete, and that it ready to add new contacts.</p> + + + + + + Account + + + true + + + true + + + + + Protocol + + + true + + + true + + + + protocolListView + + + true + + + false + + + Select the Instant Messaging systems to message the contact. If they use more than one IM system, select them all here + + + + + spacer15 + + + Vertical + + + Expanding + + + + 20 + 80 + + + + How do you want to message the contact? If they use more than one Instant Messaging system, select them all here + + + + + textLabel1_2 + + + You can always add more ways to message this contact later. + + + How do you want to message the contact? If they use more than one Instant Messaging system, select them all here + + + + + + + finis + + + Finished + + + + unnamed + + + + TextLabel9 + + + + 3 + 5 + 0 + 0 + + + + <p><h2>Congratulations</h2></p> + +<p>You have finished configuring a contact. Please click Finish and your contact will be added to your contact list.</p> + +<p><i>Note</i>: If adding this contact requires authorization from one or more of the messaging services, Kopete may prompt you for further information after this screen.</p> + + + WordBreak|AlignVCenter + + + + + Spacer4 + + + Vertical + + + Expanding + + + + 16 + 205 + + + + + + + + + 789ce4bd4793ebc6b6addb3fbf62c741efc40b5e3a1040e335e8bd2d5f376e0396de9b2279fffc4be61839ab6a1969495ada5bd2db193a479fc0023247ce9c331d12ffeb7ffef53ce8feeb7ffed77fed0ffe611afe2b9cf8bb7ffd4f745c2e2ffffbfffcbffff7bffe3b93f3fe95f50aea9fbcfbafec7fff3ffff5dfc3c3bfc27f59d9423e6bfb374e59c281669f9c23079a1de15038d41c91f3e458b32b9c08479ac7649b3cd1ec298e354fc905f24c38d13cd7ec0b2fc80e7969b890d6bcd21ce45df25a38a37943f6c85bcda1f04e58eb97da937df24138a7f9a83912ae9383425ef3bde658f859d8d6dc2387bc9e15c6f5abe644fd1ef77f21f3ba95122e688ec811792dece8bf2fde389736f7b34ee418d7adb2b0abb9494ec82dcd19e1beb0a7f9196ca7c9dade7259753fe4270d96eb19616d9f3e7f6f67c891b0b6475fdb4b2e27bc2267c93b616d8f3eca9b17ae9073e4aab0b64f5fdb73ce96fb0dc8e67a8d9c270f85b5fdfa19616d9f7e966c93b53de50af27c87cceb4162d8d1f61b8cc905b26e1f3947782baced3778233b64e4d715ae0b6b7b0e7a6497fc200cfbd0ed25e729bd75fd058f645e0f5ec91eb920acedd942f97d555e5ddf418eece3f7612cac7f1fce85b57d87077200b6507f81ba9fb657ff815c80fee199ccdf8717619dffb0430ec95d61b40fe42f54f58ffc0fc879d87b989003e677238ce7d5c8e6fe23617dff304f8e1c7dbf00f61a29bd509f0d615d5fe1956cee7727acef175964de2f1a936347eb1dc1de62e183b06e5f51899c905f8561ff68ff89edc25e8380ecc15e439b1cf3f94761dc6f4e4ef0fcf15218d7d3c2fa79910b2ea4c1f152583f3f76c81970d4be713eadaee37e9e30fe7e40e6efa7b6b06ebfb32e390b8eb5def98cba8ebf7f11c6f35ae42cae4f67c2faefe7beb0b6c779919c23a785d1de9f3467557deaf637ae81cdfdc61639c7fb6784f5df2fcae43cb962d8d5f614213f39a5afb697d9166c7ebf5c1ac6ef972db24d6e0b6b7b8c90dfbcc9cf7245e6f579895cc0dfaf86c2fafa6a24accbbb09c98e8bf8a6e351fee62ff5f535f2afdc1b7f3f12d6bfdf5e84757bdc9aeb2e38d1f6ae1e97837dc42db2cdf23b64de6f13915d3eef24acafaf47c2fafebbadb06eff3b737f0f1ce0feaab7027f17c31e1c939fe49e4cbd1655329fb7cb9279bf5d4e58fb837d8bec830f81b0aeef18fabbea79bafd1dda645e3f86e4007ce80bebf6ec6b7f9af7ec00f1788bf6e1a9e7ebfc1d6b64fefe582787e482b07efedb2339020f604f7e2609757d0cd05efd6c9adc2667c011ecdf57f6abff7e72217ba8af65871c40afe394cce71fccdff3f987acb02edfb1478ec97d61ddde873abee603951fdd9e079e30f2db321ce9fa1caec959febe6898d77be41caf4f0df37a9a9cc7f523ec2d30f93dbd9299df534a58e7f7542227e4b4b0f66703f8af503d5fe7bf6f1be6f3cbc2bafdf6f3e43c794fb6c1c3805c08b57f185585f5fd473ed9e1f5bab0be6ec560155f74fdc52e58d5b7b6a7cd1dd9833f19f3feca9eb53dbea5c92cff214766f9df16c2bafc6f4d619d9f37d45fe8a4c143d44764cad7df910be44058dbdfa84c66797acf8679bd4876519fa39661e8dddf0ae3f76f648f7a5d84f5df0fdbe420d4e5191d85d13f45fc52fd01f45723d873a4da87be7fc2f2a9f6a1fdd9b425acf51fe7c911f8d02327b0b703f48e945e5adf4b4758e7e73c15d6cf3fcf8475fedf987f27c3eb73617dfdfc2a0c3d71ffd8e8d3df08ebfa1f99eb1ef4ec5d8471fd99ece3ef7b0b615cdf9343eadd358cfb8d52e4187a0f137212c29fa37cb18a2fda5ee32399fa1e115f62a3d7db92ccf25e12615dde92e12caf8f85f5f54bc6b0a7f3773d9173e01eea3f51fa687bedcdc92c6f37470ec0bd99b0ce6f374b0ec123871cd13eb764ea31ea93e9cf530f60d59ef5fd13d85762f4d9c4641fed395e90696f47d46fa2f4d0f7bf768475fd9c3264ea53dc08ebe75fdfc8d4a39812d6cf2b56c9797057c74b3b6dcadbad912370ef4958e7af5b25c7e0de949c80476b702e437ba9909d10fdf13bb029efac474e108f573b61fdfbd33358955fdbcf394dced15eca86593e8bccf21547c2bafe4acc8f6393f3c2b00fada79d51e5811e6b61941f7f9fc9a5c981b0ee8f7457e40c78342433de8cb2e43ce38d4b2ea03d8d966427c47c548aec85da7e868fc25a9fb804561d3afdbc31f3a7f4d37c815e19634fa71d394b7b7a21e7604fe5984cfd4a2c8fe8650b6bbdca5b72815c14d6faf575bcb1b3591ffebbeb838d3e9d34394b7e15d6f6d0199073607f0656fe1df684f26455fc83ffce8195bf417fac4b66f94a1d721ef6535e906db497728fccfc5763b283f25576c2fafaa526ac9fdf819e3993dfce959c27f7c936792dacf3db9b900be0d113d9f8fb393982bd0c0be07c86f6d12067437dffe182ac86e0da5e509fb982037f74857de5943eb08f32997a5cb66453de27617dbf8b47763d5d7fd7aab07e7eed48f6c05dddffb6f3aa7e75793a2b32cbdb09c80ed917d6f5dd7e26bbe051861cd23fdf9123f617608ff9bceaf2dc788ef69757f6aff52b3ae41cda43e9956cd39f76c905da4742a63d5cefc92c5f712aacf35f9c917d700ff660ab78a1f3d781bfb24d79da5db2477e22fbe48e30fc059e67e76dd47f00fbb10b39cc0f056732e7e7a232d9c5f8f8c0eba67c9588eca13e8be63af35fcc08ebfc14b3e4003c40fb2ba8fe98f65f1dfc5e6507f1b8fd4866fedb1772403e9343f24c18e311d46f211fc09e43d87741f93f5dffd18aecd21fa2bc0555bf988fb1c9798e771ae402eafb101a66ff624276184fb7c2f08f55b20bff58ec907dd843e98d4c7d4a6d61fdfcd20b3904977d615dde36ead7c945287f7b428ec1ad0239e1f50761f44f783dcffef9d026c7d02f85f23b7684feca15fed8517ae9fc8cb3e43ce26fe98e6cb37fb211d6eda7fcce882719b20bff513a9103d853d92287d0a73c17d6cf2b9bdf47e486b07e7e1bedc955f6a1f56ed96496bf057feee6d3e42e39437d1ec83efb7363720c7f71467b705579f4f31769615dde0befa7caa7ede3f02aacede364096bfbb8b8c288977572c4fa7f12d6f9297be4185c5908ebe757dae404dc823ff354f9b59e1db42fcf94b7fd268cdf97c85972919c030f11ff3c3b0c345bfcfb429aeb317c5e2183f9e22decc353f117f3677532db5b236dd8c77ce3985c80be8d8c611fbf7f25bbbeae8fe384ec311eefc93eecab9135ec6bae74c8d4a7d215d6f659457bf7dc343912c6fc06fa6f7e2e8dfe56c730fb332df81b3f9f677bbc0a43cfa5b0fe7dd326dbe0d6825c008f601fbe9da63d266427d4e5196ec95180f97fc457bfc0f58c31e291aff444fbac915d8e17eec81eecf1cce7abf6a8ebb38afe886fca5fad9233e49a615fdb7b13fde7c0e4bff5cefaf7cd26d92137c82e78887811d861a8f3d384fe41218df25a7532d763aeb09740d913e2ad4bf6519e53911cc03eea0139823faeccc931da5b15f61098f254991f37eb6bfb6d217e84aafef4f35b7332f3dfcc913d72dd700c7d8e649f0c7b0bed3cc60f29b4cf50b5279ddfe1889c09f5efad077216eb3f43f3fb1ceabf88fe7ea8f4d0ed2f59915df8a7715d58ff7ebf26fb6c3f5b32f5a821de86a6fcb5bdb0d6b77620e7c82d617dbf26fc7594f7610f9d9e6196ff400ec85961fdbce69e1c926be4881c9013b617f8a7a89085fdf85d721ef3b743fe7da100bdaeb0ff48955fdfefb82687f01f359b4c7ba9c13f46aafc3abfb5a630f4699359feda909c27e785f5fd3bb0cf3817a37fd4c4782296f2f9e418fab466c2b8be15467bb1c809af6f84711dfdfbd8cee07e0d8fccfef5e8851ca3ff78e70b6b7f17c2fed5680bfe65f3464eb03e3464fe9c7480f9e531d9417febfa40f6399f3321078897d527b2d1ff991ca1ff59853dc7caff687bae337fae0d3deb5b61dd9eead4d72d80fb686f891adee8fa6ca3fd2679ce3f37ab64a317ec21b1d3e021ec332978816edf498eccf5c809c6a7892a1faeefc901b8f86618f65671c821fcd5d1fc7d427b833d246e0efeb6de22b33cf5b6b0d6a79e263b644f58df2fd0e34de55dccfa658f1c607d661491c350e76fb703abfe9fce5fc527bb88df6f4fe418f1e23010467d55c06e16f5d54891999fc69aec922fc2dafe76babd14322a7ee9fb9f77641ff12cbe90693f75733d417ede326015bfb4fd359a64debf31247be41761c477dd7e0a59651f98cf1c810b3ed6a787b87fd6c9a33f34ec939d00f3ef13b20ffb8f5ec821f25b8a85f5fdca4d72041e6784b5fed3023906afee8475792b1572027f7eec8055f9d11f8ec8d4bbb920b3bccd25d927af84757db7581e37407fada3db5321a7c6d3f01fc87fcec9205e8f785de983f69e22bbd0a3312127b097e119eca603f0869c0dd0dfe1fddc5ca0cbdf999299dfd6851c30bf5361f4c7b2c2e8ffe7c82119f597cf7b984f69b6c811e7eb1a60553eb49736398bf26e1232d7874735c3f8fdd52647f007e70e39e678e10dacda8b2e4f354bce717c1392d9ffaddf9169bf6de6d794b7bd2387e4b130ee7712d6f96db33c6e04bed3f1a4a0068818ff9496641beb4da30ab980787b9e905df4c74625b2077f527a26d3fe47013946fb89aee484fe310d76f318efb50e6407f1b41d915df8c35648f6a04fbb4ef661ef1dc301eca5ed9159de4e4a18fd1596d78dc91d615d7f6db4675581b477f8d382633847f6c8577288fe5c19f55d50ed13fadc0b6b3dcb2561f4075fc9b900fd990e5939705dfe67b283fe58d3fcde833e1d87ccf277cfe410ed6538264781ce5fd7229bf2778575f93be67909b8bb348cbf1f20ff8eeadfa03f88f23a36d70312f877a71071be66428e118f2efcbdc3fd0fa30199f71b9dc909f4ac410fc7e81507e40c785c12d6f5317fbf8ef58a9361c4f7da949c477ff85811d6f678dc916df0c8320cffdf32f7f7699f3139a23f7a11d67a749fc946df063981febd9130f436e5a1dec3a530e2d35158977f8078eb7869fe7dd930eb3b238cf13aecd155fd3bec2f417b719d02f68fdc05e404f531467b7595ff829e0732fdff917f6ff41a3c9013b4bf9e2d8cf54ef4375c93dfbe6598e5ad0a637d6141cee07a6f2c8cf5bfb330d63f1fc959700af1d52b14b85ed72647d89f5645fbf694bd60fd3824b3bce39530d67b46e41cfce3e1855c803f1fed0c439fb70ed9417f69e89143f4a7874fe408edbdd120c78c5f1932ed65e082957e586f5918863efd12997a0d9bc2e82f67c9d4a7ef0b6b7dfa7cbe9723e784757dc4c8bf5f08b9bf334d8ed9ff34d713b4ff0beac377b8bfa77c24c7182f1ca664ee5f18f17eae037d471bb28778d2f2c901c7074d728cf6d7ef9013e8d79c0bebbf1f404f5fe987f8d0261bfb3a90b36c6ff7c25abfc1844c3d0657721ed79bb0b740f9439dff37e819a8fe818ee767c4abc0e5fe81768e1cc29f8c56e438c0f80bfe5d8d2e02fdf7dd0999f91bf4c97cfee05118fdadbcb0b6af41966ce37a1bf61fe639df54845ea1cbf5e685e102fa2fa37b7280f83ff2c811b8572727883f23d8a7ea0dc37f0e2c32fb7b439f6c237fc388ccf8379c0863ff16ec3b2a04581ff473e410fb9b4b64155f747d8e8b60957ffdf7a711d9c3f86b649399ff16fc8beaadd35f3d90f3f4b74d61c4ff9930e2c5845c603c9f931de47fb81246fbdf915d7088fe8a1a6fc33f5d3d7201e3f1b24b667b39c1fe62551e5d7f7df41f6393dfd1a330fced3dd9c6f5e19ecce70f4fc2c83fefef79e004e549547bd7cf3b219e27ca5e301f7926733db99612d6f6b65c933dc6db1539407d748ee418eda1077b49547bd5fad5afe42cfbb7fcbd2a1ff62f317faa7cfaf9dda230eaaf26acebef8ef953f581f9ca12d9437d0eeb649fdc124679f5f3556d39181f5c2be400fde94a066cf4580764d6d7214f0e30ffd4de9223cc3ff5c760658f684faf640fed75d825333fc30139200f8575fe126dbf4e46f477c8219effb62247e86f0e5260a5b77e7e67412e303e8c8411af1fc80efd635d18fee9220cff647ecffc8df2c228df3399eb13c317617d3dd6feefb6da8bfedcf82aaccb57299303c4df36ee9f35f9ef35c8267f63619dbffe9aec827bd03ba7fc25fabb36d8c920fec5113987feee640656f9d1bf1fbf9203f000cfcfa9eac0fcb3b6f7dbec37fa1f51811ca03cf1abb0fefbfa06acec19f37f677201eda17d4f76511f2ded3f1cdb66f95b6bb28bbf0fe660551eec7743fdd86e01f13a2a92b97e37f6c911f43db6c909eca7db012bfb457be67565afbabc35d44fc1e57a7cf142e6fada744e8ed15ed66f8671ffc309acfc03c68f0db2c7fe4e91cce77507c2585fd0fe5ab5de10eb590dd8b31a2f205ed74be43cfcdbf842667e0e64553eeca72a9003dcdf87beaabf8df9fd22daa3ab9ea8f99205bb5cafe99155fe90ff3299f7eb0f8575fefa8fe4908ce77b7616feba85f6ee197dc72fe404fe76b6032bfd74fd9fb2e41cc6db0d97ccfc740c87c88fa5fb6f8e5fe0fa7217ed47f597503f47979ce3fc267fafee07ff312147c87face39713387c5faa982647189fd4e14f03551f3a5eadf97ba53ffcd58a5ce07cdf89ccfb77d1fe439bfee98cf6113a3ef65f37d09e4237c7fde468bfa197871e878eb0bedfe1996c633ea2f7464ea07f19fe2952ed15e361fc3ef2b8be10b7c939f0bc4ce6fa42eb488e59bf6fc2f0c74fe404f1bb9b16467f18fe37f2d3e02a387639bf3a467e62f3bc814fe6df77d11e1235fcd5f9f3715df5eeb15e7781bf495cce67b6f97b2f03ff943c906ddc7f5123737ebb3724737efb7411c6feaeaa30fa8fa89fc49467c0e7fb19f8e3b66eef6eda4e70bf520becc488cf170bec86f4375d72ccf99ab530e6375e8475fd2e5db0cabfe65385ec713d7b454ed03eda31d8e7fa492f21b33fdcc2df670a5c7ff54760278ffe6d5220dbe8df0665b203ffbc3993f9be56d107bb9c2f3ee07919a3e7b24be67cf1714966feeb1699f96f75c02abffa7aff899c85de5d6d4f6ed6e67c5213f9c916f2f08f5113acf4d2ed2d1a901d705c10d6f5158764cefff5f260f33c4bc77737a7ec4f97af542273fdea5225f3fdb212ee9f7339bf3ee1df7b9c6fed9fc89c4fdbb7c93ec68f291d6fddbcf247faefad363987f7e9ca33b0cbf7b9da0158d93fd66b2b64ea3d7e22733e735c20733ef37426737eb796032bfb41fb1f9039ff62c13e6d652f3affdb901cc11ece289fedf27da89ae110f12c6a804d7ea29130deb799907dfabf2399f3cdcd0cd8cfc19e7b7764b33e00fb2f287db05e32049bf2266b61bc6fe79003eaf528acafcffac2785f664e0e71fde49339ff9bc07e1dd57eb01f2922f3fdc4e23dd8cba2bd742664ce0ff6f66497fe7a4e66fee2b330e2972d8cfd680d72083eb6c8ccdf316b98f375baffefba853cd76bda64ee4f6bc17e5c558358bfeb8195bd617de981cce7c7537288f636a80aebfa4c26c2c86f931c911d61adc72223aceb7bb517c67c574c8e519e1af3eb9bf9a714d9c578bb89dfabd60b7b4c8e641bf65c46fd7bcac361fcb901abfcc27eaac25affc9901c916d615d9ed94058df6fea9063fc7e5e11c67eb0bab0cedfe1896ce6e3e05f3d539e1efca95fb0f1fb04e5f54d796a884fbe1ba0ff72d9813dbeef50cc9173dcbf9311c678827faff20fffd122c7d0639115d6cf5f98fb2528cf7223acaf1fa197ef73be6388fc06853cfccd15f617a8e110f6cbaec99cdf6fe0fe819b45fbaea03e547f0cf1759227331e1f612f81f2e7186f668531debd92b9be94a0bd86aa3c98efea09e37e0bb0cff9bb6393ccfb771ec91ed613fa3361bc3f0e7f153921e26974224728efa54de67c66e50236e54d90ff48f92fac8f38e404f57138804d7ef65961fdfcc3909c03bf75c979cc5f34c66417feb4f14ce67e85be611ff38dfd0a39000fb6c2583f41ff2156e116fb7bd7c2389fc025737ead85fa8a5dbedf39477c897d8eb7263932fbdf8b8530e26989ccf21dae64ced71c6c618c170e64cedf34995f9ff37f9d3399e5eba23d269e8ff1e700fe5cf50ff1fcf19accfd5ffb0c390f7bdf970da37e0e5d61bcaf3322333fa7aa30e6770232f3d74ec89c1fedebf278e9820f7f912a819d02cfdfa8917df4a7fdba30d6f39fc11ee7633bfcbde7a27fb1dc82fd2cfbcf01d9e437278cf709d764ced7f4ccef23ac67a6747cf56efd4bc4bb7b61fdf7d547b0b208c4bb19d8677f6790368cfc4cb2e42cfa839382618edff7641bf1f0ad4766fede72648ed77a8643d87337458ec8da5f78d902e7cf822558b567edffc22d39c1fb83714a18fbc7f2c2d84fdf01bb695cdfdc9333e83f545a608fef0b1ce760551efdbc7d5118eb65776407dcef9363e83f407de70a21e7670660f3becbf90dec71bf71694ce67ed918f9cf29bdf5ef2b6bb2b1ef4732c7c7cb1ed9e17ce90b99fbc79bda9f7979278df1537b41e6fb46419e9c70bea80d765df88b1af4c87b7c1fa37220733f7ea540f630df35c9807df6bffa5d7090c67e054bf7ef3cdbe17936c50239e0fc0a9e6f9bfa0a8be40ceabbb625c77cbf16f9b33dee5fbef6c8dc6fdccb90395f32407bb14d7ece688f05153fb0bffd851cc19f46d0aba09e8ff73f79dd35f33f68ef05557eb437b41f355ae2f88d7faffc3ffce391ccfd31ed2658e547eb97a0be1c87e7e784b00f478dc874f9abb01f251fe6f36ab02fc7e7fcee74412ec0bef66932c72f2dd8bb639ed7ed9233d87fd7417b779d3cd68bdad04775e8f8be9e47e6fedd4e811c627c7d467bbd4ddf623e1cedc555f7c77cc1919cc3f34a60cfd46f5827b3bcf50e3984fd76517ed5ff43fba8bbe0208bf2f4a764debfa1e395eade15b05e5e46f97c7547c45fd89bef73ffe83820b3bc4de8e7abfbe3fdb71999f76fbb64eeb71abc916dec570ae16f0297e7f5342cb287f7bb4e4f60951fbc9f3126733fe204cf57a313f6c74a8651fe15efef8718df1f6332e7b7fb67b0ca1fdeffd9916d7049c7472f54f686f912e427542d4cdf6f33269bf765cee408ed6b06ffaffa63ccff2b39e2fc087faf9e8ff9b57b32f5e92fc905e42785df474e8cfc5c36c288aff03f91f20f681f0532cf3f3aa07e547f8ae319febd1f217e1d6d7282fe58f702367af45fc87cdfb39f11d6e549d05e62a517f60722fec42ecf676a19e6794a9b0bb980787480feaa778ff9f743816ce6c311ffd4e80bf92bf379aafdc01e2be402ecad85f225663c1c3f90039c47d61919863df5e05f9280efaf0e74fbf2557f05e3096b4b8ee19fafafc2a80f879c60fec47f04abf263be2403f6b9ff7c1d824d79decae40cf8b417d6f96d0cc8b487b6b9eeb03e2ec298df9c935dec5f6ae27ac6f4878b657288f3d70ef8bd1a2d213e1fc760d59e313e89c93edeb74f747bf5b34e88f3e0ac2ad8e5fb00952bd8e3feb839affb3c6fe4b02427f4c713c3e87fafcfe0200b3d560761f8a71a99f969a2be722a5e627c7522337fa50539025f4760955fbcaf31246790ff728dccf9ca4503ec47882fab07b0c9cf9ecf57ed19fbf31361ecff6c92f9be56ef9d71de9ef68f7e5ee507ef0324e42cc6d7ad0639077b9ac0fef27e8cf9af03ea272fcfcf0923bee6c936b89c2617185fa18fadf4c17e148fccfd5457e4c7567ae07d941999f34fad477201ed3d0ac89ccfad406f5b7924cc573f837dce4ff56a64c6d703f455fd119effc1e707dcbf19e3790593dfe48d1c935db0d1b3532267303f61e5c83c3facc9fb79dc0f5ab923c75cff9f8255fef03e72871ce37ca913f42d049cefa8bf9279de47a7450e797ecc89ccfdba09ecdd91fcfbc2faf7494f18bfbf90135c3f7b86d19e9bd0cb71f9be5bf144e6fc72efd530fa7f41482ea03e6b688f8ec7f3b3da5932e7574ba82fc7e7fc4da94bce23feedd1fe55ef9bf38d2db2292faebb26fff19330ae17c1b7150f6d2f0f8651be725b18fb5baac218dff3f7aa078ffea045e6f949653e5ff957d47748f6108f2b1b61f45fccf598e7eff4c809ce4348605f9ec96f52368cfcd64fe40caff70de37ae34e18e3e73c390b2e9f85717edf9eecb23f9e15c6fb39cc8fc7f3aa12f873cfe7f96e07e8eb051c8fecbb6407fea33b25b3bcdd2bd99417f1c837f92fdac2f0bf437216d7939130ceef6c9273285fa3288cfee85218e3b53e39cff8da221778dea1b91ff5d89afb713f6482f6ed073c7fae3923c7688ffd5732cb17c3be0393dfb84ace81ad4418f9af838d5ead3499e7abf450ff4198c1fbe409da6b68ee17d78551ff03721e3c990a233e0564b6d7468becc03f77b686b1fe5fdc81550bc6fa109fef73ffc604fe3f0cb2787fa53e22b33cf50699efffb750fe30e4f95f29d87b64f21b5784b1df3645e67971a99630cae308ebfa6df7c91c4feed0ded5e806edf982f617f9dc4fb2477b8d0217f1b87e2133bff535d8e4b7f56a18f5ddb6c9ac9f2e9f1f66713e8305fd6293df9443e6fedb544518fb5186c23a7f8d1999f5335e905df8abf6ab61b4cf37b2c7f39f7ad02f561109f3092fe080fb71ea7c9e294f9df90b79be59a32a8cfd33077296e70dc5c2789e278cf5f3889ce3794e69619c3f8af225a6fca936d961ff6a2c8cf747ab6417dc89c81ecf735c93399f5bca0863beb8430ec85332e7bbdbb087c4e3f9581d9437f139bf53e5759feb334bd477a2e213ea0bf69d843c2fae51330cbd1a4f64ead7f184d1df782353afeeab30f4dc0b637eec48cee3fa59eba37a3f46af21997a597361cc1f05640f5ccf0a63feb6248cf2ecc901c687cd07b0d20bed7107f6799e443f21f3fc8cfd2b38e0fb92a70399f3978d37b0d1af752253af96b9cef3ea5a6d61ecb7280863ff9343a63e838330e6b342b20d2ee9781f648c3ea90e99fa34b6c2d8ef932773be3a9516c6fcdb4018ef5fcfc85c4f882a64cec7d6efc15e1aefdf5556609ffb95d61770e0603ea23e01877c7fbe5e17c6fc08ef1ff23cc3ce9c4c3dba33619cffd423538ffe4a18eb75babd296f6fca3b12c6f9ba8f64ce3f5e4ec2b0a7a530ce0b7e26737e307085b13fc7037b36f62f5433c2e86f36c0ca9febdf5f2d32e72b4f3570c0f33e9b5370c8f31a7b2c8f296fcf15c6fc57955ce0fbebbabf13e44cf952aeb0ce4ff39d31ff72470e71bde40ba3fc17b2991f5d0bebf26cae649eef57ea83957de0fc53d85bcee7fc61e99d757ed7285f4ec5376d1f0dd44fce94dfd2f124c89bfca59e8431ded9932370bd2b8cfc3e906370b814d6f99f9c85e12f7a64ae2f34af86d1df3aa3bde455f910fff9f75e06e5ad876487fbd3a047de942705fbb04d7ecfaeb0ce9fd521f33c2d6b298cfcf785b13ef2424ec003b41fdbec0f6cd8e42cecb33e2317d0ffd8e7c001f7bbd5793fd51e71de19febe609e6f1dc80938e50963ff2eec577957702320e7f0fc469eccf3349b037201fb5f3aa82f351ec5f923dd3699fb292cd4af639e67416fc73c6fd914d6f6542c91d99f285dc86c9f8d36b980f9e609dabb13301eadefc9dc5fbe41fe5cf33c6b248cf9b40999fb715b2361cc3fdc9339dfddaa1be6fb4d1d7280fd499518ac46b0985f989223beef0d7fef067c7f3c803ff4ccf3adb330c6df77649e476fad8471ff0299e755f58a649e3f5733d7b9ffb289f27abe39afec44e67cf10aedd953f6a4f3bb3a926dc4030bf7f7cdf3ad2399f3e7d64518eb431699e7dd970f86118fb6f7649e47dfda0ae37da62a99e7ad35cdf33dd47fd3fc7d82f16505edcff7793e408cf229f785f1dfb44df639ded1fd2fd59a985feb8eccfc5a2561ec1f9c9379fe7e7d4fe6f95995b330cecfb1c81ef7cbcd84d1fe90ff40d90bf6b3c29e02e571315ee0fd4c7d9c98bf90ef4f5b8847a1c98fbf310cfbb45c32cfff3f66c81efc49fd288cf7a77db28ffc8defc811e25d15fe2054ed5de7e7ba25f3fc80fd181ca6d9fe114f23f37c3f203bccdfa330c6032932cf1fabcfc926bfe6f721f62b37bbe488fbcb77649e87d9280963bf0dca1fa91e91beff60298cfd4f357280f9af3af313f2fdfc408f178358f2df13c678fc9eec826b1b618c9fcdef43f88bd64e18f3432fe408f5517e2027d0bb89fa88d5f801f1e25e18e3b72139cbfd6b863df8671ff59d98fc59b630c6974f64eef76c57c8fcbe42cd2507688fc54772087b29aec811ca7335ccf9e1b64f4ed07e4b287fe2a771de6dfd4118e31ffedee7fb02cdb130ec3520b37c3394578d9ff03d9ae91b99df173821fe2421fb03968e87ca5a595eeb5518df9b68907d5cf717c218efe68591ff323981fee72e38e0fbad9327726cebf28c5d32d7f7f73d70c8f75f2dedbfc38c79dea42f8cf9a71e99f1c41f0be3fde388ccf356da2b72c4f927943fa3ec05fa8ec839b4a7c33d38e4fce192bf0f038ce77ddd3ec2ac795ee74e58d79f7f2287cc6f288cfa75c8fc1e452314d6f5bb7904df56b4747ba991737cdfb54c8e79fe20f29f0d3368bf81ee0f8639f33c7f42e6fedda0228cf765bac2d02b4de6f72a6ab87fcee7fc5db14fce40afb64566fb6b0ec9fcde41790d0ef8be46157ae54d7efcb630d65397e498f9ab09ebfc4da7e484fd35d867decf522fde5f3d1feb351332d7d71b73c3e8efcf7360a51fe24f81ccf5b7fd859c47fbdd570c237eb5513edbe4cfbf90993f7f2b8ceb6f60a527ca9317c67eb498ccef6bd45ec93c5fa733269bef431485e1efebe0db079c6e3cb4c82ed69b931d384cdbd81fc8fb852efb3f297282f16d037a1424ffc86fc1e4dfbf0ae33cd1353903eeb7c859f4b7da5532cf3fa9f50cc35f168b641bfeb15d26f37c9fd20358f917cc871dc939ae0f41df4298c9a33e0764ae17fab00fc7e4cf2f0b633c1991b91fb36aaee7501fe7b130e60b62b287fe65bd42e67961e33999e71d35f8fb90efcffaf0b7ae795e7727acedabfb7e1de7d57b647eafc27f15d6bfbf5484f13ec59d61e8dbb58551bf4732d7df3a55b207fdafae30e6139ec93eeaa3921746ff21478eb11fa49a90f97ef1646c187af458de80e7dd4fe06fdcdb07146ebce0fd437e5f687a32ccfe95ee3faaee1bcbeff7c979f293b0ceff2e45b611af3b3b619cd732207b88b7d30999e749d52332cfa7a9d4c8fcbec404e5f5029ecf521a927318ef96b2c258dfaf901dee772c903dae0f201e78612e8ffe7389eca1bd56a1976fca1ba484d1ffb927dbecafecc93c2fa55621bb882fd31c99e72b1497c2880f1932cfab7a7384f5f3cf6b32fbc76594c70f78bed6e444e6fece6b99ccf3bfca2199fa9cd15efc308dfd8ff51599ef87047a7c1506a67cbe2d8cef873d93f9be7bd511c6fbd531d941fdd742c3885f9d3d997a7412b2cff8027f1304dc1f590ac839f8db5291ccf7d5065db20b3dd6f09f6a7c83fdbeb5ac30deff5a92f9fe4b007f1e9af2848e30f6ab3d901d963f2d8cfd7a27b2cbf2fa647eafa6531646fb37f7677bbfdc93d9ff389adff37caa9ab9cefd25b5ab30e64761cf61c0ef3bd47be41ceaffb426e7b13e7ee6df2bfdd09fb890793edbe095ccf7b57dd85b64ca1bb4c92ed917d67ace36641fede16d248cfe5d85ccfd3313f89b28e0f93ef58330f68bdf91395fbf467d44ca5e71be1efc4b6c9e1f1cc91e3916c6f70a7a641f5c3991b9be527a12c679804b32f74f6c1fc809f7ebc1fee280e7cb5c1fc93ecfefae09633cc9e7a91e04faeb2f6417eb8901f4484c7e832999f90d9b86395ff1480e70fde01bc6f5595b58d7777549e6fac8f54918f5639323724e18e367c4b32470e07faf1772c0fd8373c3d8af5a5e09e3fb23a8ff24e4f9ce2be8a17a47583f99e9f2476953be604166797a2b61ec1f2f92435c9f5f85b1bf3f12c6fce289ccef3374ee85d13f5c836f33baba7c2fc298dfed9259dff537616dafd384ece07ab1218cf1ea86eca1fdd61786b19faede17c6f70fe664ea591a90f97eda3a06877c1fa8512617e0cf8f2c9f7268989fd3fde72863f40a36c2582f3e0ac31e4f64ce8fcc53c2d8cfd822c7b097b790ccf9936e2c8cf32551bf19e5afb0dffc81ccf179272f0cbd3cb2cdf339efc8d47fda243ba89fe998ccf33bce7bb2b1cf2b99efc3f6a0a71a9fe27d8829cb6ff40af0fcac297f5012d6e50f7d72ccf9ad2b99f32d55d47ff6b602aadbc380ccf3cb6b637281e57b23b33cf52599e38d4a8a6ccedf7e328cf6587c20073c0f744fe6fb78e72299e77dd56be090e711255bb293c37a74cf30ec69c2fb2bf7a1af2f599e2887fdee81ee9f29a21e415518f3d5197202eef584111fa1574ee9a5f3f3562767684f7b618c279a64ae7fd7ce640fedad9626f33caa9a43e6794e67e8990b793ed76642e6fb45fb2999f61da0fde74dfee79630becf198055fe51de9130da735f18e799e40d23bfdd1699e59d19cef3fcde57c3e81f7413b2cdfdbf69b283f854750df37c715b18f65e27f3bc926983ccf366b62561cc074ec87cffb2c2df1bfd2acf64beeff7f6208cf5633e3fe4fb753bc339f8b3daa361bc4f541f93591fe336d9b7f5fd43e4c736fa06f7e40ceba3228cf3690fc2bafc338f9c83ffaf1cc99c8fa8b8e402daeb6c4de6f7582aaf649e7f3e9e91038ca72647618ce7127288fe57b5488ed09fbd16c83ccfa0f1404ed03f2b425fdbe8f77611c67eee2199e7778588970553fee085ccfd51d5b130e6b34786d1ff9e9fc939ce876485d1fed264f37d891499eb55d30c99fbdfaf2d618c67a66496ef027b28287f8cf30e9ec819ec5f2f1a66f94f0332cbfbe60b6b3d8b3199e76dd59fc936ece974243bb0bfc3861ce6a19f1ecf468ed12b9c0ae37b3617728efa6685715ebef97b9e0fdff584b11eec08637f6995ccef315487647ecfe66498df33b9e4c809ae4fcdf379be4f19fed2517aa07dbe0943bf3e99fabc79c2ba3e8b4d32df4f3cad84d1be10ef5d53def24818df1f7e24e7713db085e1cf5d61bc0fe7916de8d37d20733db87a26b37f5d49c8d4a37b20f37b2e950199dff72846e418fe6db717c67c7b83ccf3c08f8161b4cf3af31ff2fdd463da30dba345a63e67c37c5fb23216863dc17f78468ff044b6c99130daa3f93de727ab0f64ee2fb9acc93cefa9da22c718ffd53c7282783843ffd20bf93e70ad4acea1bd6d6332cbdb607e42f3fe6a8f6cde073d0ba37d2dc93cbf6b0f7bf3229ee7fcf64ce6fb4325f43f7c53de70466679c3b430cee31b90b97feb1209e3bc8b3599f3d5952699fb514a0e99e78177ef85619fe6f7b49f2af4f78d5eb30599e73155ea649eefb57912d6e5dd94c87c5ff62d258cfec84618fd1fe6470d5fb03fa1288cf87d2027580fdaa33df811e73f43943f30fa5c03619c275f22f37cf0b0258cf9cfa230f69b940df3bce41c99f3b5952c99e7f1770be4107acfdfc8fcbec0cc25c718ff17a1671066a0ffe5228cf3459ac2381f6b45cea27d565fc839c4939ab91fdfef7de3f38c9e9ba930decf36bfe7796f9bb230be37704f8e7338ff0cf61e447c5f68f544e6f79e42f897d0e8d9778431bff446f6707dd71486fd56c8fc7e436f4be6fb5e074b58d7c7fc85ccfd0b17d467a8ec15ebc919720ee381699accfee46c6918d78b0732cf6b5b3f0ac33fbe92795edba6228cf362101f2253bef04918fb994a641fdc8fc901cabb9809a33fdb26f37b155de81985698eb703b279bfba48e6f959b30ab900fbb81ec80ee263b540e6797b6f77649eaf761e0a6bfb583f09233ecfc93cff6d9b08e37c19f8f728cada986f447dc4a6fc615518e75d44e400bcdb08633f7e9fccef17f45d32cfcb3aa3bee23087f654dd92f93d949d4566f936cf64e6773323f33ce44b5e18ef639bdf47b8be2e09a37ca8afc4e4bf3c14c67ac42b99efe38459619c373013c6fe9f3b615dfedd89ccf3c02ad02309393fde2b9133f02f67de5fe901fbc80943cf0d398ff632ab921d8c6f6b0fc2bafcb306d9c5f5b7a330c66b65b20f7f5f0bc83c9faf6c0be37ddc1732f5dc5485f1fd63963fcae4705e06f3739b31d0bc358cf5d637ea11713fd05bde30ee17eafa88d346ef2826f3fdb6f05918edb1248cef774ec89cafdebac2daffec06649e77562d09e3fd18de3fe479dea54818f1d622f3fb25a58330e28143e6fcf5ac46e67969b3ba30f6f3bd92793edfa6268cf9c93732cfd7dbcc85313e66f9a24c16fb4d75fdc419a34f9823f3fceca82b8cf7bf9664be4fba1d0ac35f5fc121cfc73f3f93738c8fe67a9ee33373bd80fe6baf2c8cf1e480ccf3dbe62732cf579be5c93ef429bf33bec7362507e8ff55cdfd22f8bf6b931cd35ed364ea77b913c67e410f1ca539ff7d26dbd86f1141ffacd12b74c99c6f8f3686791e13ea331b727d703125db982f1bf37ea16363ff4b9bccf7094eae619e3f5520f37b25339bccf3d4aa13c3e8ff1e9ae408fdbb92e118fef42d20f3fcc72bec3b1bf17cc4ed5818ef879e84b17fb341ce64f1fdea8c615c9feec9f93cc6373adec639d16b6a187a9db6e030cdeb29c36cbf67619c4fb420f3fcfdf35118f67525b3fdee53c2da3e164532dfcfbbf8649ef75deb9303d8db7c47e6fcf67c2f0c7f6a9e1fa17f5d35bf8f698fe6f73c1ff18af698537aa27fd525f33ccaed4418eb972561e4272267b3388f29318ceb11386ff48bb7e40c389a0923bf4761cc5744e42ce3754518efe3af84f1fec02399efefee86649be3db1e99fa9eefc83c3f70179079fedc2c47e678716ef267f43f08633f1eec2b1ff17b41d50b390bfdb66561bc0fcbfc44b92cbe475f32ccf30166c278ff7c40e6f75cd6d4578d98b1be81fcd846cfe84ca67ed15218fb7777649ed756aa0ba3bd36c8dcdf536e91791efae24518e3abbd30fa3f7d32f71bec3264fa8bf996ccf3d0e64fc2d87f8efab18d3edb8a61e831de90f339d477c130aeaf9fc90ed7d3dec83c4f2cc2f30ba6fc714318d773649e67b35c9059fe7285ecf27cae26d943ff683f24fb3c3f7642e6f9763bf33c9e3738bf23f37cbbf9bdb02ecffcc1307f0f7b2f449c5fbebc08a3ff7a2473bd3f89c9b92cfa1fbe61b4ff6a8accef876d1f85f5f3dfae86711eddd9fcbd8debc7a2615cdf4ec905e8bda90be33c55b40fc7e87b6a0be37c558bccefb9473561d843208cf5821732df9f3cdbc218afe5c93c3fe67c2673ffcbe24a8ed1fe2f0d61acdfa33d3a519afb89eb649e0f792e90f3f09f3b874c3d164b722187f7979f0ce3faf14518e7df98bf776cbcdf60d8b375fe56d427e2fa5b84f2b9469fa847e6f7e24b5361e8650b6b7bde1fc86caf8b6732cf6b3c7584313f0b7fe01afb5b8cc99c7fdebe08c3ded6c238efb54ee6fadaae2f8cf5f17b7201f675691a863ed31dd9c961bc73308ceba7a9b07efe8ae5512370bcdf81e779469fe85118e7650464ae0f2d1bc2d82fba12c67ccc9eccf9e873420ed0ffbc1cc811faeb3bf823efb642acf5e0fd6f2ba69a5fc99cbfdf3ac2785e48e6f77af6cf642787f33e5386a1dfae4776a1c7a92b8cfeca92ecf1fb400d61ccdfbc90f9fdfaf598ccf7eb63f817dfe815d9c2381f794f76c1a509d9c378f43a23733fcb7c048e728c378961fad33732cf875b9c85f5f5edfb75e8ed910bf06fb50b99ef676e9fc93caff25a34ccef4fba64ea5132ec834ff7e480dfdf5b90f97efaba2c8cef2dc07f06468f534b58e7777d22f37bf771228cfa0fc8fcdec5654e8ea90fca17183d7621d99ca7581446fbdf9179dee87945f6d11e2f23c328efae2e0c7fcce79bf87a5d08a3bf32212788c79bac30f4413c0d62bedf7e417f2734e54d9664ee178a0fc2581f9b91b9deb12c9079ded4b92f8cf7c1eb64ae6fece14f43d5fe30fe5a096b7b59cec99cffbf1ae6f99f8b2732e7ff67597281fb4336c2d81f1a19467d2c1e8411bfcfc2183fdc93591fa5bd61e8bb33f9a5debba530e253400ec1db2a39c6fb676bb4ef30e6fbdc9ba561ec3f7a833f8d8cde71931c70fd6d238cf6ec9343702d26f3fbf1e72761cc67d584e1ef613fd16dc555ebf348e67ac0ee228cf30c0f649e5f5a7c1186ff2c08e37c54e64ff41808231e99e747e02df3a7ec15e379e81d197d360d32df7f2fc2be62a347fc4ca61e7b5758d7e76a40e67aca3914c6799c0b61cc173c9063f011f61bab7882f3d827e42ce3f55618fdbd7b720e5c6c91a9efc9267b19ec6731f7e3fcf6b94c0ee02f963b7288fdf0d50399e72d6cfbc2d0f3248cfb8dc8dcffb63d0aebeb6f7c7e9cc67968db1999fbe3364d61bcaf9013c67963d047791bd8db7a484ed07f3fc39e9288ef0baf36c2b87f200cfff74ce6f7538a4761d8d795ccf36d0f3561f8ab1299eb31fb1199e7a76f8ec2f83e90b96ee7707eae6d18fe681a930be82f2e4e6417fe679b16467ff7de30fa3fcb3199e3e1e5cc30dad7722a8cfa7811c67cbe2f8cfd665b7290437fc29437b631df60f2c3fadd197d12f0f6208cf91df46f9398e74fbee9f69ca4239edfbc3e92b3a88fb73d99eb8f2787ccf59bca0b99e7f7ee2a6487eb9343b20bffbcaf0b43af1ad9031fd6643f03ff71350cfbd98d0c43afa5791ed743aa0b32bfe7564bc83c5f77db23538f2dcb779be1d5f6f62c0cfb617e62ee6758e9f697645479719ec7985cc0fce639270c7ff24476781e7b4cf6f8fedb82ece37d80d2a36194e76d4b0e303f342b1bc6fbb4e7bc61d8d3b22a8cf63114c6f9554b61f8c32399df0fdafbc228bfe198fdb717619cbf6bca9ff07bc14d61d427ea3b739b51d7e5dd09e3790d61f8d71239c3fab184b1fe5115d6e5591be6f9d247ed7f926cc4f591758accf364e30cd9cf607db36a18f53b0ec94106f35343c378ff71992373fd649917c67cff011cf37ce0bd47e6f9b8bbaa30e6735e85717e4f288cfb0dc859daa3258cf63f17c67caf4dce81377d61f4c7d07e73aafe311e590be33cba1a39e47aec4118fe2b4be6f715f68161e8bb7b12c6f8ad47e67ac8fe8eccf9fb35ca978b79feeeb64b667e3729721ee3cdad611bfddd25da775e85437099ccfde5ab1e99eb0713733dc1feab63ca30cabfea0a23fe9c8575fdad96c2e86fac84d19ed0bef331cf7b5bbd92b3d86fb39b1a467bdaecc905f4d7b75bb28bf8bb805eb60afff8bef19a9ce03c8f37db30fa2f2bcf30fcf3be208cfdfb15c39c4f801e76ccf3f2778fc2a8afb930eca72a8cf1729e9cc5fb98eb3d3987fadccdc81efb173d61f427985f35a2c3fab0e120c2f804edad104569f4d7eae418fda9e9c630e637f78f8673a8cf0539c17ae36e028e4dbcdb91039c87b3ee9243d8df366f18f959f785114fcddf47c8ffda15d6d7d73d61c473d88f13290175fd1e0de7f5f5dd9d61bcafb13f8163be5fbc7f2367f3d0b345cee3fdce6d8ecc78b376c866bdbd4b0ee13fe668ef6e9430dec686f93e5f048ed3c8dfaa6318e769ac5fc936ce075a6fc98cd79b77c6f861230c7f65eec7f5bff58c1c217f2b5f18fe22278cf23585919fac30ecb126acefbfaa1a467b5b45c288ef8130e6bb0ac2b0d7b930ec2b1446fdd785315f5e11c679db4561ec676808239eadc86a44a07f6f0bebf2adcac2385fb7240cfd968663e477218cfce68591bf9c30e607509f9eb1cfb84b4ed2e85f207e7aca1e30de1c1a467f79db2467f238cfa4268ce79dc93cbf70dd21bbf0d71bc31ed6b7372e99df5759b4c95c5f5a3585a1df4618dfefe908237e3f0bc3bebbc2881f0761d8cbd830fa074b4b18fed915463c6d08a37f7b15c6fce751187a3864c6db85278ceb1961e89f12867f490b438f4458ebb13c09a3fdb485a1c79d30ecab2e0c3d2ec25a8fe59b618c57575961e87316d6fa2c9f84b1fe732f8cfa6f09a3fde48551fe9a30fccb5a18e537fa44e0e58330f48885a1c7a330ce6347fe7c65dfe8cfc586b1ff62cfeb7106f1e61419c6facfdbc030de5739150cc33fbfed84e1ef9be46c16eff72d0d63fd6d6eee97c5f87fe51ac6fe8259869cc7f9b8eb29d9c67941fb19d9c179526b931f9e07b31b93f97ef0a6208cfd1e4d61cc1f3bc2f0c753617dff794b18eb6d5d61f49f5d61ac77b785b11ed731ccfd0159615d9ec58accf71fe69e30e6a7dfafc3bf4d84d19e52c298ff5e0be37b599630fcf35518e799fbc2b09f1761f8978d30fa0b6d615dfecd3b637f534f18df57df1aa67f2b08a3fc3b61c4a3b430cabf14d6e55fecc97c1f69d11246fbb285d19ff785a187238cf767d11e82389dc57c5642cee4303fd61446fb19926dbcff3e5f19c6fee15944e6799db3b130f60bc5c2783f2911867df685311e7e13c67eacb430e687ef85b1dfe62c8cf76d4a64173c5f0a63bdf2228cfdc0af86d19e665761ece72b0a63fefa4118ef732d8411ef1c61e8f1280c3d5e84b51ef344187a3e09c3dfb584a1c7b330ea632c8cf63a17463c0c84e1ffdfcb8bf52fa317df1f9e4f0cf3fdd88630fa533361b4dfa930f42808637cf3fe7bf8c39e30e633d7c2b057cc0785713a83f3ba57e44c5adf7fba368cfeedb2248cf16b288cf1d3dc30e65b8e8fe46c0edf9b7b13c6f7113d722e8bef7108e7c06561cc4f3c90f3f8fdde36ccfebc611bebb1fbc8701ee50985b55e134f18ef4305c2d8bf9f12c67e6c5f18eff358c2181f6c84613fe6f7055c9f5e84f1bda0a130ceb37a15c67cea5518eb3f7b618cdf8ac2babcd3aa61bc7f372d0963bdba22aced695a16863e2fc278ff642a0c7d46c2d0e75918fee6280c7dee8451fe2761ccbf98fa74c0d37b61e8f1280c3dde7f8ffed64518ebd51961f81b539f0ee613661361f89b9330fc4d288cfed64818f38d2d61bcef087dd4703a8df773ebe46c1afdb94818e3f9a2e10cfa3307c3880fab36399745fdf58411cf06c2f007b130fc415e18f61d92f3e065200c7f79278cf3cbdeafe37d8a8330e2992f8cf9204b18e7c1a48411dffbc2f097b630f657ed8561cfefbf47ff6e6718fba9272d61ec4fd90aa3bf9c16c67ed29530f4580ba3fcf7c2e8df885e88cf930dd9e6ef2d61e8d514863fb585313f990843af8630ec7d208cf7b72bc2380fa72a0cbdeac2383fa8268cf929d730c78f9130d67fe5ef317f3779ff3dbe7f87fe63acec15df334884f13d811761fdf7c9b330fc474758eb3d5d09a3ffd310c6f75727c2189f85c2383f6f2c0cffe109633dea3d7f38cf3216d6fa8d2361addff8248cf1c6fbefe12f0e8633f81ed95118fe2245ce65f03ef14118fea228accb3fde0bc35fec8451fead30ceeb1f0a63ffd33ba3fc5d61947f208cf2f785d13f1b0963bf754f18fb174d7dd1df8ce5fef037934858977778f8792965a57c95822f52a8522429664a741a7f912692a69fd24ca7b94e0b49cb2fd24aa7f55769a3d236b5fb2aed753ae87454e9947a4b9d5397d4f5e728433dbe5424fca448fc838a7c4b8f5f5364f5bb14397c5004a9982aa5caa94aaa9aaafd31657ec142fe1c45be6d217f5c11a47aaa916aa65aa976aa93eafe3e659422dfd2e4ef67234611a45eaa9f1a288b19a646a9bbdfa6cc6ff2227f9e8d7cadc71f53e496ee557a108b794c3dfd9832bfc3affe3645e6bf5391cd0f28f24b7a40915b7a4ebda45e6931e9d45d2af3cbcafc0445fecc48f3b5225f469a1f5124cb944be5df2d26657f4b997f4ca4f9769bf95211930aca621c65319d949bcaa4bc8fcafc45bcc8bf5b11955440b12cff663156a0bcafb2184bf79cffd65ee44b45bed766bea748ca8a748aad2455b1c6c662ac8956e69f19697ed9468c22264d532d6b66cd53236b612dadd05a7d50e63f6f233fa2c88ffad51f55e496d6d6c6da5a3b588cb5b70ed6c43a6a65fe8991e6c714b9a5934a6fcac7e81e1e2de66c5db4327fd748f3c71531e94a8be9d2628a56492bf3cf8b34dfd7e3b322b754b62a56f593c5acac8ba547947f212ff2b322cd2f5b8851c4a4baf2be1f7dccca2a590dadccffdf6cc4a4a64e2dab6d469156c7ea2a6594c5583d2af3d78e34bf4791efeb51a61e487d6b600d3126b0f6aa87375216736735ac7badccbfa77ff6ef8b34bfae48ff437aa0c5a81e9e58cca3a5c7daffb848f3638af4ad679d5ede4791dafb6a8bb15eb5327f7d2ff2b5223fee45bea7c8b395b63256d6caa58656fe0b8bb12d7ce1f29fec45bed6038a98e4a8d152708bd76231aed5b33c3fa595f9277b91ef29a2926fa9e4bfcf3bdcc60456cd0f6e16835d957f9a8dfcbcd8fbf36cc428724b911ffbc9c751a458ccd89f8832ff8ef9c4bf828d40915b9aaa34fb38ef601dfdb9f2beb6bff0437fa995f90f449aeff8d59fa1c8f7f4f8a808d2ca5f7f1e13f81b6331fe562bf3cf89343fa2c88a69e7effdc38751a41e13f8476531277fe9bf8932ff442ff22d45761fd2996302ce3bdcc604d693b6988baf576fffb15ee47b8aecfca24ea5ef5accd62f6b65fe4eb137fd59915ff422df53a4e25755aaf9758e2243338aa4c52cfdabdf1065fe3a8afcfc566314319a2035fdd697a3486d31ed9bc5f81dadcc3f25d27cdf463e2a62523795fe7214e9f78cc5f87dadccbf3fd2fc3c4556bfda6abe4c037fa8d3c8ac2c7db098d42d5efb777ec7bfffa0cc9f3557f467b699d5afb699af15417af01fbf1e45de7a78da629efc67adcc3fd18b7c4f11935e52e9af4791b49857ff1e6f7bfc7522cdef8abddff62283ef2832f4337e56a5dcc751e46d4c408bc9df7c8c6fe3bd949f34e7fcd7b611a348d6777472bf1a451a8bf1825460e18dbe7f44a4f935459c8f290882f0f328d2584c1029efab2c06ef2afe61457ede98e6d7fb673faac73715519a242a8d8309c7042bf6f0388a7cb798607a53e60f459a6f29f2eb6de64762efef6b33bfac08d22c98bfaf457e1813188b490771b0d0cafcf3bcc8473d3e2a62d2f26631c1eaf3283258fb8d20e53fdf2c26d87c50e6af1c697e9317f9c242de15d932ed829bc58c308ae49860893141705016730c16c1492bf3d7efaffe1c45b6c19b4ee7e0f27914195cd1c3138b29067ac5ff2f30eefd1936f275abf9521193ca5f5a4c5081c5dc7a78da6236415594f93b479a1f55a416d4556a044d3326085ab751a4b118d5c37b0eda414729530af41ef23f713ef18ffbd51f8934bfac08f440eaa9d4971ede57a3486531839bf70d86c1482bf3378934bfaac8b72ce45d1193eebe9877083f594c0c8b09f458fb4f8c343f2ff6fed636f3a51ebde041a5c7e02978fe302678fd3c8a7cb798e0e58332ff0c2ff23d45905e83f437469157d5c35316731b13688bc904f738a9e5db9afc6d22cd8f2982940bf2b498ef8c098cc5e0cc993fd1467e66ffecb72bf2fa2115747202f7d328528f0974bcb619af3d58cc4765fe3691e6f728c214a66e16135a1f2d0663026331a11f06c14bc873dfffd691e6e1d7150963a6241c7f1a45869f46917a4c1054c349380dec70a695f973c7343f36cafb195ee47b8ac4e15ca745b8fce55164e807dd7075b39870ad95f9fb469a6fb799cf7a98b409b7664cf0be7a7debe1bd8f2283a1b298ddcd62c2bd56e6dfa7c89feb45beadc82d1d543a86cbef8e22dbb751242c26c886a7701dea15ffbf6da4f93145cee145a56b58fc348a4c7d358ad416738bd7378b09f558fb4f8c345f2bf2a51e7f9622672a6252592ce6f1d328f28916a3c70461c5584ca8c7da7faf48f3ab8a5cbe916a615dc604f761e3cb3141d8bcf5f0c4625a61296c6b65fe9e91e64714e9845d9d7a611f16e307d6d387b5c88f6382eead87a72c66a0da52351c8a32ff692ff25b23cd8f2932d2e92ebcffde28327cf83826081f95c5acc3a7b01dead5db3f4d913f33d2fc882226f5c2979bc57c5a59fad262f498207c0dd3ca623261166759fe4523cdd79a7ced577f5991519857c90e0bb0988ffb136931ce278bd13d3c580c4ee1fc6991e6cf8bbddfd6e3971531c9c37b913a5edb5fad2c7d18452a8b99198b89f41ef2bf5da4e9fc902279bd58e447c1d7a348f4f0be1e4586eb288ca2280e5d9c5cfc1322cdd78afc9991e64b45f2df5004691c4df44cd5eab3c57c3def8051e4ad87a72d661a6144f99f8834bf47915fb790774590e6d1e2c35ae4a77907338ad43dbcdb98e0a3c5a422bd87fc3fde83ff3d7ef59715316915adcdfec40fa3c84f6b91eff30e62319b6816e93de47f552fa26a7cf7438a7ca9c72deda343748c4eefef45727fe2e5f328f2a3c5846f51140ea3376531e748bfaffdd78c34aac4d7a8f81b6dc46872884a2a95a3ca3776b4de7d5a8bfc388a54f13aaaaab6a42c26d26f25ff876de43bad26aa478da819b57e838d184590da51e7e3ca9255f86adee1f328528f09a22e2c26ea7d50e62f1569a27e348886d128bafb610bf932dd470fbfb01679fc6a14f9c962a247adcc9f11697e6b0ffe539b899ea267a5cb2dbdfc46455e754a479928abe3f5af8c22693183dbbc4338bcf5f094c5e4a23cbe04f1978b34f3a8a05571542afc0645d291cbe4c5a90ffb13bf9877503dbcc1b74791b098d88a1e635f94f9377b915ff0abfbdbb7664cba7d18f09b5ee4db8a30c591b53716f3ddb548efeb51a4b198388e751ff8dfea457e35f6c6e378a23599c6b378aefeffe23728b2d46915af6131f1e6eb51e4e7b5c8cfa3c8781bef94f77d8cf7f1412bf39fb5914fad253edebe0614cf553ac56ff159a5cb0f2bb28caf71312ec5e5b81257e39afaa7aecf81eba43c3fb8594db0567129fd6e31ba87f7d16266710316133745997fcb98e6d7fa22712b6e6b35ce7127eec63da5cf39eeff80228378188fe2bbf83e7e881f7f213da55a29d75a299dae41574525f4f09e540fef59f5f09268692c26d633573f25d2fc2145e047e297f895baa4e34c9c8d73713eb6e342fcf25d451c95dcd84b5289f58b8a7c9512df6aa8f694fd3c8a54f13a9f04378b8132ffa131cd57912609a14b12dd4e288eb349928c93894ad32ff48022836496cce3fb64f1db34794f29d7bf0451f8a05a96adfa78296d31bd789f2ce3672af36ff322dfedafde7459256b652176b249b6375db4363badccfe831e4a11a5c9211e25c7e4f44d6b784bce2af91fd225b926d7f8092929aa54d2a96ced95f719fb0bd5dfb1838a8a5959d5a6ec9bc57ca5cc4f8f343fd85f7d8dee928a56a19ad492ba28d3d0ffad6914895f9256d24e3a4937e97d554e55d2a4afd38069988c9291f2c448d5b86a25711d4979669d923b7f6c79d6bd8aed35eb12ac540fa81758e11ea383ff8c8d7cf4abc97df2a03598248fc953f29cbca89428655e93749249b2492ec9277652489cc455e5bda591292d52e2bd9756a7f238a5bced7b7274aa30b5250d952650a6641d550f2854f17eafbcb48edadfd4e347faf0bfc98b7c3ff68eadb10f5dc6c13864a726198fc793f1743c1bcf555aa8b27e28a7b5fb5e492d75b3d4f03d59732b50515b92954fa55323a6c75b524ad8aacf6394e95acbd493faef9d2f94f9099166bc5425ffe531cde7383318afa8cb7abc196f75da8df7e3c3f8383e8ddfb432ca6f28456e25fda29caaa4a34f25edaae4ea74774bd642a73d522a836475acdb9b07cb949db2ad8352a5a7c61077aa25dde6463bd642ddcb28f3f322cdd3f8a2d2755c1c97a29764352e8f2be36a723fae7d2ff28eebe3c6b8a9526bdc1e77c65d9d3ae3deb83f1e8c87e39156e64ef9d1be3ec1412555a74f5f9454caa94baa52ca5365ecaa144a1aa934794fdaa7e8a4fbc935a38cbadbbb323fd58b8cefd92739df22cdf841fd5ff5efe3c7f1d3f879fc327e559aa5c719a547769c1be7c7f6b8a07571c6eed893231ead893f0926e1249ac437639a4c265365339d0f251d7d2ea92ae7d95a7d4845d5362e26c51b55ee5b2a31d54c52aadcd62d1fa9cc48699a51b636b2e65a999f1b69dcc9eca6083dea5749ebc034992b7b716e69b2982c27abc95aa58dfa6735d94e7693bdd2e830394e4e93b7c959b59c857e53ebd7cad9784f7ea0cadc93746fddfb47e555909e74f254b21199d4dd436573caff2a9bd1ebda7fa477f62dbf3ab94cae506152fcb622da463ea44969529e5426d5498da9aea831692a855a93f6a433e94e7a2acf13a542ef7349a59cef257dbd257f714b56e19626fd495ff55a6e29a5d349f5626e8b4cd75bf24fdf5026fd51991f54e487c6bd93c158f54426c3c9687237b99f3c4c1e274f93e7c9cbe475929e6426d9496e929fd8a24b61e24cdc89374d4d2d267f1a4cc369a4146a4ce369321d4f6febd21b3dffcf12fb3d9d4c894f925489d5c8fa635a7e4cd3a97f51e36e93defc2563f605315b7931577977adccefb4916f461a78d6e96c3abf59c674315d9a5edb2d4d57e378ba9e6ea6dbe96eba9f1ea6c7e969fa363d4f2fd3ebb4382d4dcb4c956955fd97cb343571a7b5697dda9836f5bcdcaf9733afd3f596d4c87aeddf3195832888fc8624cf7f55a9a3d2e973cc56feb7fb41993facc897639a690b9e75dabef5d8a84b47f5520ed3eeb437394cfb93f574308d946d0c9526e5e9687a37bd9f3e303d4e9fa6cfd317a5cfabb2a4f43433cd4e73d3fc7b39bf28e97b396fa9afd3138e3df6ef9182836fabf46c526005969ff66dd5fe6ecae8c8a494b9c56cd59bb1c65a99cf7afcd87ce27714419ada2a156e2d65ea4c5df6f45fa6de2ca575f127bb9935a94ec399af5479519a3ccc8259388b66b14a89fa67ac68a214ba9b4da7d5d96c369f2d66cbd96ab69e6d3e95d3fe58ced05125452aa831505b259f69101c99629da626f97de5651efdcd7bccbe45a64fcafc607ff59bf3009f15619a6d67bb9b36b3fdad45a961f3647650fd938eb697a6f2209eb296caeca854392925de66e7d9657655a9a8feb9cc4aeabfc4b393ba7a9c95952a955975569bd5678d5973d69ab53f95b3f35ed2b039bbbdb9f5316d3e254fa51252e807a79bff5511ecab989dd2bbd17eb722836f2ad2459ace66bd597f36506998d4554b8ac6fbd9687637d9cfee5514f2543b292b9b0894a5bc29251e668fb3a7d933d3d3ec45fd97d7595a6916cc32b3ec2c37cbcfec5981c999b9336f9e0a4eefe5d4251dea54d529a35217490d1146b7144e9082fbb0724baaad2d541cff2a668b323fc7464491e96cee33053765d4205e0d1a6e2d691eab1eca6a525171e7a6cbd34c757d94753ccc13d5d19dcca7f319d354d17cf6345fcc97aa759da6f7f3d57c3ddfccb7f39d4afbf941a5e3fc347f9b9fdf4b6b52b80a57c18b49e16e7e09b2ef297cbca5c00eecf9d54fa9087fffadde8c3513657e21d2fca88d881e2615e7a579795e51a93aafa9d190f2bcaa7fd298d755d4a9685d12652d8fb3e77943a9d19cb7e66d953aea9faea29efaaf6375f575de57da3ca9bf18ce07f3e17c34bf9bdfcf1f547a54e969fe3c7f09a35b496f293c498ac2687edb2bf49e06e1fa3d05f72ab67106c29f2366abd1c5132253aaa295f9d548f3b522468fef28724b699532f3ecd8d1dae4c683797e6e2bcf5b57d1a6a8bc47a0757999cfe785b93377e7de22a50645babbb248cd3da58f332f2c02d5ae2eea9781f2485515c56b8b70112de245b2187f4893c574719bb77c4f6fe15b14864f3a9574cae8548df4ffe6af2aae877e4ff5056ea3c9f719081d9944991f8934bfae48f193229216f39b328bc562098b513d945715711e6663d5521e952eb3796bb15aac179bc576b15bec55daa97fdba8ffb252563453da3caa5f8ed55f4ca7afaa47d85077392c0e8be3e2b4785b9c255d16d74571719bd3bda5e12d45711487cfe17354bda530871475c32715e3977ea87a8c1f95416f464726a3cc6ff122dfb190af14519a9455aa2caa8bdaa2ae5263d15423a29bc55495e73d29effaa0bc4b4195de5376b25db416ed4567d155a9a3feadb5e82d7c65392df58bf17ca17e7d52d1fd6635f5c97ad15f0c16c3d961315adc2dee170f8b47494f8be7c54bf4e1330551126fa3d987745ebc2acfbbf5db2a2e4199dad7313ba5bf63f7bbbd88d1e3db8abca7f422b3c82e728bfcc2563e66a87a300fcaf3be2a0fdb983bca5efc4561e12845dc85773b4254fd5f6bd15ddefeabafeca6a97e756b77ca6ad45f0ea7d1c29eec97c178304b2dc3245946aa4a93e57839594e759a2de7cb45dcb8a5e8f221d554ca47f9c56ddff8552933512389c5b762b66a4d6d6ba795f9bd5e24fd951e9f14599affad96ebe546a5ed7237f1967be56326b3371577e6cacbb655abd92e9ce561795c9e966fcbb34a17f56f57f55f7c65376be58166ca133fa8d81ea87ef1751aeaf6148cfbcbe2d4430f7b595a9697956555a5dab2be6c60bb616423c57b9dfcd88f6cd58eee55dff9aac6141335da5abc8f26974dad0c6376aaa5bf6df8b36dc468d252a9ad5367d95df696fde5603954a3a3a7e548c5ea1715991dd59236aae574952e77cbfbe5c3f251a587e593a2ebf279b15fbedcac66f9aa62d47916eaf6144c2acbf4c41ff7c6fb719cbc8c27cbcc32bbcc2df3ea1f7b59489626c5079d9a262541f0a2eca5a3c61679e565d42d10b3756492986de553c377657e38d2fc988dbc2b6292b374979e3e68d752fd9847d57fbbb5a5e9bcabe2d176d15e5aca4aee57fe2a58dd3a23d12a5e3ead92d558b5b1de6ab29aae66ba3d456a64559afacad3ac26d6b8a3fa01d16abe5aac96abdbffd6abcd6d35e47b299aadb66a149152bef7ee36c25623f2b1cc8ddf560da00c633694f91d8a7ccf42be54e49656abdd6abf3aac8e4a97d3ea6d755e5d9497b9aa5ecc6cde5e5d5524ea28fbb82c1f942ec55569555e5556d5556d555f35564de56bd6aa77335db594966335d2ac28656a93cd2435edaddaabceaaab55e9adfaabc12fa8d20b4f41ac465337653ca5cc5694e97d3103c1981debd8f49b22cd2fb5992f1519ae462addadee952e0fabc7d5d3ea79f5b27a5dddfa274515717af3cec25fa597cfabcc2aab2c2654aae456f995bd2aa8167559392a4aed1696f23405ad4cac95b16eca2cfa2b77e5ad602ca9b5f50baad8e15e8d97a6c1518d346fca34a0cc87987df739666b9b298b327fc48b7ca907145169edaf8375b87a5847eb789dac5ed6e3f5643dfda8ccea6a9459cfd6f395bd5eac97ebd52a58af57f5e575bda1cd143ed88c5266fabadeae77ebbd52e5b03e7e5f95f54945e8911a5315953283db9c83df57ca5cbf15b3adae991bbff5665203adcc6f8b34df5764f4392d9df5dbfabcbeacafebe2baa45429af2beb2a63365a93abfabb85f56679556da7b6aeadebebc6bab10ad7cd756badbc8fead7f4547fb8b59aa93126fc4c65b95f77d6dd754fa5fe7ab01e7e579766945223a4891a577a6ac43d0dfc0fca5c4cccbecd8d87d6c7de8c35b7c6d68ecafc312ff29522b497d1fa6e7dbf7e50aa3cae9fd6cf4a95cafa65fdaa6afde6812fda03b75613557667355ea795d564d659e57dabebdc3aafda5266d15ddb8b8deaed35e713354628a9d8f4b42eac9db5ab75f136a98db5f137c13775b1d4082154e3c9891a755399d0b945261db3972666ebdecc457a337a34798b4cefcafc0c1b41da849b68136f92cd7833d94c6faa6c669bf966a13a34abcd7ab35171f7490def4aff5f7b67d69cba92b4ebfbf3330ef704b6c1c671ae34829090846c03e64e12b319344f11df7f3f59559a1106afbdbbd7eafeb633ba7774b4bd8d1f65be3954a90a6afeed970089e5f3d4013de99fd650cdbc83e7742167c7c73664261162a905fd930c95cf01ea998e655b0ef116cb052ac43cab1e4f3a74027be82f1fa1e7d68c8911009995211aad4ace6e9e40740899d500dfddfcb7f8081031adc00aadc88aadc4a22cda622c16a870166f0d80cad012acd1ce3cbabb39aaf4706fb04779db42bd808496d22077bf9dc5d3faf07ada9c0450a0cfaf5794b32196284bb2480cc9966c29391764aa354933d37a199b7de81ecf1919d300321690f10db13967e36a26cac948680251257367a66926a2617bb3de5b47eba3e55b536b0654e6d6a7b5b03ad683f508549eacaed5db2dd3ee00e2e96bfdb5b59e213fb5101bc8d063f093feb175da408df7029e649db6a0befbaf2febc5ea9318b25eed76854acb26a6db06f4441cf4921cf4d8988cb93359636cea95ccb4a8e4ecca4a7f31810032f8dddb7b33cd5522a9d968edd06ff1f6d25ed96b42c5ded85b7b673dd97bfbcb3e943a4ae435d409b4155444c29df6f36104fdc00b740aba7d84fa4fb74f5f7dfb0cf5efe73eb62dc4c5b62fa838b66b7bb66f07766847848c19021934b1f934de2fc8e4393b9f4064b3f163361b47393b25f3530fa911c154623bb129f4e603b4cd34c4504685b1599bb3797b600f6d219d4220af99daa37d04ea31c76c44fb74681d3fc177644bb445e824d10c620c0ab385484abe145bc264c617645c5bc664145b05361353341f407b814caabf0f067db2cb39db38e36e32ad66b2d9787502d156577c8dcc3d31532712db1ab6371bed6137ed0f509682cad49ed973fbd35ed81d34b9c25e63eca8dddc7e40131aeb054ff4e4d30e7acb937d3a6dc1535eed473cd57b3e2ea02ba0c0bb4cfbc9ee9efb76afaa2f3532cff68b8d54c63555f099c78ccc45cecefbeccb0944cb407d7689ccaff8484604e56730fb15bd3de4b49d56998aa33b86bd704c67e9acc8b4d359ef967b7d47e3a9de064d81f7b383019eb307cd7901422f5f8283e6365b32eb445c765367ebec9cbdf3758d8c73b055e7e89c9cb36301990e7403487f4b399b64a66f73f65b91b30999fb7d04f388cb3c883910ee8eb3ebea92e396a8788eef044ee8444eec240e4526e4c7ce4e85884293e037f09be5beb35f80e7ccbe360703688039f4d717d4bc9f90bdc87c7cbadf390cea031cd6e12ec8780e4fc8d8cfc066e00c1dc1dc9b8b7aced6e769ce4e2e73369a405473f65aab93f90522b6334276941cd1919c714e45761447752687e971e768ce5bb6aa82238adb99c086c6ab4de67eedbc9f9f212fbf808f7ceea7f0cf0fd096e33e042d329c8fdde7d175a6c065b69a3b73e73323e32c52321d4c46b189ee28ce83d9c1399bc564ac8c0ce86f5acde0ccb4a8cec6b39c8d33d3603dc9c8dc1b33251e2991d41e9d27a7ebf49c67e70553e93baf6e7bd9765b8ee6eaaee19a4713afc4c95b83ac38b94b6043ed77eeca5dbb1b77ebeedcbdf38e0cbc28dcaf8edade803e690ebeb5d8e9ee17ea9150cde21edc63295fa7646cb71c67ee09b25480325356cda47df622cf4cb59c5d9d40acb835ae817f486474412435f7ec5aae8da90c5cc7759747d773fd63f77472836cf5f6805729b7eef6ec866ee4c66ee2522e8d8d71d953b40fc0b6f6c3ee037aa4f98e39baa0495098b81cfc5b7852e39e547700d55d91b365bb92b1dca1d37105c852412567f74a393bc827106f2daabc3b0f6526b437bf4ee676cc341219a526ba923b766557715577b26aad0d5773dfdc77f7c39d162bfeeecc9dbb9feec2edb80f2913da7d4cd93cb95d88b0f94e83bc4e43b51c6f5fa13654dd9efb0c645eb2fadfed1f7bee6b4ae6328f07ced16b7b2d88a6574fc7fa8bc9e83392b30d36efb3fb0d39fb05f43725738f8a5c12c97960f30ccff4964006cc5b796b6fe36dbd9db7f7bebc83775c8d578fdec93b7b9607ceefb91911cff37c2ff0422ff2622fc14679f42e81f889a122443b68b843efd8f1188f3d9d3caedc1d79bc37a852c1171ef3cec1397943c7f3046f043c86ba94e6ecf772ceae4c20b4cb9cbdc67764fc4522c84430c94dcd1b7bb2a780a9e965ef9af7e6bd7b1fde34f711da9b7973efd35b781def01ecd17bf2baf09d3db067efe56be0f5a1ee51b702643265dbf25efd369a5055a70c7ecb7dad642ad7f67d1db2930c19e0d9defa86ce1809e46625cbd9848ce1e95d2093e7eca3504c20b29cbdc693ab5f262212f34db0a5bf72257f0db6f1b7888bbff3f78482ff5510f10ffed13ff967df4a89e4e64382f15d1f9cc8f7fde060e29d68eeb6b57fdbc97ee847a7931fd7670d7e92b18128f2c05b8ea0719e4ff9b4bd857e6def33a024277d5baa6624935b09abb539d1dfb1cfe03ebb3a8140397b8d4f73ba92696e10c13c52030e2ca6828cf3797f0036f48582083019f9a22ff9e33a91948b0ca6f8aa3ff135b037ffddff00a53677f246394cfc299a82fbb3cb398c9fa00ac79f437e5291b7f89fe02d8cbd81ee64ef2ffc8effd03a80baa439db18af56f9cfaecd3da870311b27ab0669ce4ec9fc052299f98fc0e4c9effa3d7f0c549ec15eb2b8f1fbfe2b28c9058d12112568a7d60a74c426300233586ed48d7da48255b046755eb0699a51055ba8f414e42dc1cea7823df6162ef80a0ec1313805677d4372b6de311ff39fb156ee320e6c33d4b7f56a0632d360ade664ae1029a988d8c40333e17d3e700217a874032ff08320082192ac200ae22009285092661e552299d10113b0010766047c30d804bbd76018087814ae3470190562203983609c7b0b8fbcc5da0572a004aab50f262833190f4bb950a8b5b13c2ec7d07bee0d0df2b759ced9a8cf06321426735b459a890c320b34cce52d780f3e5642300d66c13cf80c16d73ce482081d744af6103c064fd8ba416ffdb97a0c9eb1cfbcd4b8c05fb87296e17219f49d17a8bdb7c12bf2168b09db612b5043ddea04fbd0084d73b6f2b29f5a1f9772b834e7660875b26650460baabd8b9cbdc23702ff246aaa4470d48085ab701d6ec2ad350977cbe9320af7e1976fdf41a4c2243ce4760c4f01179ec1acd05e3d860e2213ba152eeb6577659b13d3357aa117fa611086d6fe3cb376611428611c28d0c1b13e1d262155fea9556bd9361f431a7af1f15e02651eea6d88a6944c96b333323ff5918c0831ef31644236e4dcd7905fcacbfd7265bae1201c7eeb23cd440ea1108eb089a1148e818c1c2aa11a4ef0ba5231ef1540276cb3bf5c4287f4091dc02ad4c2377b7d9e86ef612bd4c38fe0e4d3105dac23bb43abe5f1b9ba3c653f05d5f14a7f87cacf842ec1ace56c658def16fc5522e1349ca24c13cec239ca9ce1e7b2bb847c689ee17970e122ec7ce7211522424ea46c0fe163f81476fd28ec619f792ec7c392839e7a616ea1fa17a152b1c397b01f1cc3d7308eda51cb9efa54a447465aebe89149d4c57c3267e6123aaa00aa3f05aabe4d6b0a79fb58cfd984cc4d22cf7522e5cc1b2da315e956a235f8a9689ecd1d7a8ad126dade41e492c728da457bb02fb043748c4ef997859fbbbe8cc9ef01fe1363075987810c336bd1d119790b70b1a0ab85bed6f68a3a30b2e1a9616fc6ea72809af815faa7836e416e72f1bde31d321b6fad3119fc8e7f3dd3dc4724e5e2442eea7d6d37f2223ff56edaf0a17ae846c1cd98b9e44188600b3ea3308a4ee794cb318a571ea858d373975b7ebb67c4511251fe026abd4fa7d2394474c4446c4af30168f6084dfd0be2a81f71ed696902c1a16a26e23332d763a69908d1d568100d233312505d1e8d5016344ff9e79523f10744be2e4c8ac6e021f3c83cb71198488e94254fd4d334e1ef3b60f53cb61650c96e21eb4eda6aa4469360e7c89156e9a6d0a733a237c8d1f8a9e19d45684f7d0cb5af0611a4653bea8b9c1de153fdafa908e671bd5e55a28f680a9f7c46badb688e9f2389fa583fb4fad1e72f11598075a28728afcb4e6af4045cba516f19a6f48bbfefbd1543d733850a8d5fabba04f97b509ed540d720933a307a8e5e324dd2d1fce114f5f1eeaa43b1a31ecfc6814cf41ae353e3eef79172a6895bb18eb2446ca0df1c9bf1328ddfec396af1eaa744e235d826dec6bb785fc9d05efc151fe2235209d3c0bf6501bfe5b935344ed0f1482b764daf78c337c3f854ccfcf26eea84ba297b1a9f4b9f8e78d904bd3b57acf467393bb662bb2093659a3b2af8b6f7143b31aa2ebca51c7be837c73ee282fcdb60911ab6cc3888c31f10d9108b3a7114573b4781e4da388929d3c19adbd3df40257aad37a412ad0d3ce5a4ad1a1353840a671ed368aa45a228eba670efcdc44ccc967faead19b51df5286783fe2a3117139d698c9aeb15bcd78907e89942f604bd8f87c0458847384b649f78dc9ec6622cdd4f24b5712c5f788bb2442b6c3333c0195ac7d1b041d100b9566a39507be86dd17804762a996ac5937426114446ac3932a98fe3378b8bdf89568387b0e029699f5deca827dd64fc114f635ce9fda0a70145f5adb8524dc69fa8638917502c92aa09ebdaf210771a892c1a896ce207b0c7f8a9d21355333487d81bcb34a720d59cac06ab193ac5c03c2f4b7e76b222daf688ba909984f5108671f73c8b7bf1338abed6bee59ba765db786fcdca3bea53322f713f7e2dc8dc57af26eda495e868cf0ef9cc899198ce73b24c56a9dea3272225eb64f3032260c936d925356f4933349d66681555027a1be514f2d421bbeaab696b0b3d74ad6f48be6c35392447ffd39e165de6990a5bc909a98b9e401186bf37568dcfd6a992b3d5e41cbf1232f7747924c72456620300a8b257435c65ef132771ed6de2e949eae1525bdaee12bf41579b89040934364994d4a7526b9ca1277817cc385376c8783caaccc0f355d0dc2970192ed7e1aeae4a499250445d121a7599b86fc05d66c2b4fb0647cec440dfbdec2dd12ce2904f20101936e112bcae7d67977748f86490fddb70fe8467990c13c1ea2423940741cff0674ec444bae9238408b2712227d5f902f9bc7d93ab542010a92b0b32c9b82d42fe006f5947fa16696e61b89fc57e96a897b39a6402fd94ec143b1d51bf10a1bad17804361392b3132d794bde93f712991b3d4df2914cb3d906ae42b12226336b91cc93cf2c0fae06c922e95cf06822826df79c3c0495ddaaa82b5aeecd795ac753fa33aa8f50866ee10cdd1ead9975884e90d125d0baea4f76b39f4c1e9327eb014591c566dd77f861af9d8ec3e6d12ae7fdc2d8d8b5783c81e0932ee29292b9a3a7497ac973fa4c70a6c8ea8ae405fdeea47f14da6f387feac9eb258f3a11aa8dad45e954752f198e05ca5c2ed3eeabd415a17e9864687c960a6d1cc8692899a5caf798a935b5ccd4256a51abf023fca0d6a863a036681752ace2ee37ff6ea80412501b20436d93776a47ed09995b153cf5451d2e2843f5622ca923fadd14d230ece3d499b22a1ed24484984d39f50c8db8e75d91957545ba8133b406195a5d47f84c1dd53cdf8a3fcaa5bc4c5d208a5447ceea3fca0f4669ce53f13482446bbbf5d656a9800aa93d5884c8dcaae0a9984a08e5959d52265dd99b915014fadd140d9942052e0cc536c54c850887cda6f8f2df460d2acffc816468a81a5f51355d6468cce561655f211a1453097d430d832325a0a94470b44bfbd7a85146b1dce5e1fce15022255111324ce6db0a9e1a5332ce157179c69075659402997042a9506fc1d3a426578970a9b5c034ea8d7aa73e563a7a5302bf31b14ed58b7445af69862ebabe344323d3dba5f9ff3c8d8b7d3d8ba10e809a52336a1eb593b93f2faf4a519fd422a348fa8c34e71d366daa03541ec01eab642eeb55ea89eaa615d7ae3a6300ef9bb64ed4093d13aa873e33f54cbd5cf1908c08b23ef54ab7f1bb2453884e19bd590335a30dea557445c3627642b7b20c0deaa21a56595db24f963fb172e730a675da083f7c9a36cb7d14aa00e925d103f3057e9398aa18e8415ba057d4233142a6b982a7d7f486de2255a4773892cef8796633119c2be87dd4a6bfd0a7a60ff4f15b2260f4893ed316a101f50a7aebe869355ca2f3fd8ef0dc5de48da8963612a8a5518f41d42bce4eec2ad645f2ac8beb07dac69f0c3db184d482c4d368c71bd6fb4bda75fab447e29550345b500bc077af06b44f07880a1dd2f8c6996b93003a0285f4527f77d1d3812c8a631872681fcfc226744c273485c9d0349313695589602a2ccdd13c3d203ce02fb281c611321d79536dbd6c073628a297c57c9aa115a2b990a337cb69555da0a342393a8b8be7f493e53fdb7aa38796577029fa4b5ad88bf9940655ed1de8b299b64a8f808948ac4626afce68891e0716d48aebd53a56d1bb4250690ab142cb455442a7b1a6155aa527c045a3df9a88008fccdee98f94474478ac6cf23e1fa821d8726c3e41449de1afcb3274aeb96bcd985473745a233f9a23b406e0e9788a2562bdc69facadea09aa76e86931bd226bdea8bf74c6f4acf07b44b1cda209043d07269fc430998b7a955ed01df824e97b53e54a6c6de8c32c2a51f5423f00994720f374e1219975e91ed833fdd24c03f398432d30c16f868aa6db42befd82721de1d272cd7ebdea49bb13b55803c823700c9de18bb925dfeb2759df0dea825731c99a37fd4a740c4f69466436ceb49956c685c173e0867a55aed6a501d23d01bcc603ef194225dd876ca1a14fce18b4ca98cc9259d588101ed89835b361b6c0638769840d3444f82b39c84c337386f639b7f835959d95a81fcfc185ba1c2fab1eb206d0eac2dff9b0ccb357643a9fd9540ff5dd361306a80264a0e6259e09be22910904b34744982f6225326975c61c12194feb521ec8734854a1371191ea2cdbad5356733147e6c49c19ab8908a6620714e32cc72456c056f87de1320d1abf531de2b7ac47e97be601b0097145b7ad4611e9002a6b0024ebf6d34e73d49a2df9f2f7275f64aa575ddd655cc6433352b0ec8da611e3632601314ca652c13321d525de11ab290fa4bfab729e359fda6fe499321113334913116c1443334cd93bd05be4f88d721a7b0779eb9cd0e8e0f7f097f8fdfc85f1889e69358aa8415a5515357dad7768bf18adbc8f266a14336cb64782f4dd64759712180ebc452cde5c677866905219224364aa152b2330a3320fa031bdccb3e6a4e56232222331e30622c86446615466f2ad77643c80083abd80d1f0d90d3bf8cfa41e45645db2d68107d9ac0c9d766bf46ab5ce93d967de98777b964df548df8d2a63e603783c94c84c3326d8f04caf5aaf7a0b665eaac278a8c21af22cea13a0519b329fcc82e9d488107b601e99a794c6acd13b521a50df99290f74c2c5099d7ab1aceeafbaec00ca35fd11e768b3f233c26a856b1dda5c325d328f607ae02fb8ef469531f35cde9dc7bc307d4284792586c8d4eb33b65da681abb0863c0bbf75c2b6589d3558935d9688c8c8e8777655f18e4e7a6ac512c7ca050d9335dee12fd6a00646e7a23c984eb90748e70b93ac97c5b56b5a3d9095267d5399eb09642600f1ba3034764de611a8f76537a8ef864e4a61b7ec0e7cc6d5e3a3d0dab07bf68b10c95e67c2646af52a7bfca6ea0889924285054aca4cd8137b662dd6263c189975c05cd663fdba765ca38178101aa01c13c8c13454b3017885462a3b5017bdd2b3d5d60ed09e4363470d2afe25179d9429b001d981155911eebb23d47f1fd9908df43656325db7d998adbee645c8d4aa3336011a6b448332afe5d95c49672c0529894144623ee65996e558fe6b518e95f4dc9752ac34d0403c2cc33ad9e94943e8fc211d7a92bc03b8ec35d33580694837d680d9ba77cf38b3836c1ec10e814b1442f71db659219f93baec286722b21218beadbe5e9db17241e3669e7559c59159959db01afbc6beb31fdf7947162be864a1927758068568b0537c229384cf69520ddd108d03ee1c70543475002bb6f5b68c2a5cc0bff0e4abd2fbb23334ed44bd2f3b0fdb672a6c5bfbc8603f73965d76c1760891cc08996a35c23e84fb9a77143c308dc42862857d743cf6a9c93b2ada51f68e203d732af70eb015e6814eeb12c9295e50efcff59ea9a71dc043da011c8b0e60c5eadd9aba9426d4d9da14e2c876d1b413b8f4ce94850ead63d00aaf3b48331eae04d8e7824a46a65e8db02f6cff6a9e554bda8195947de5da5c2ba771bac73b4c21f58e55ea1d3a7843460378e0d3cfdafa4c578c36f820ea00d0bd426f46a2fb2b0b750050b3b2a65e5197ea849acaf681208e9ccea239cdc4622ce63c73fae92efc23ca782b07f71967cec89870260a154ca65a8bd8e820e22b5558aa1de634f50ec7f8e436dc96db5dd18ea0ae1d97de017986d0e8211ac043c167e22dd03979c6526780259abbcd4abb8050670c7f4d2dab5726b1d99a1de9f1b83df7152816eb2fc2d0e1b3ee9b3be08c872a0ba890b82377c207f89c892132199122f37216f68e9c46539ecd94d434389b73be5a25eff83e569abc43c13c16f094d1b9810c3e4d109da3d8d56d744301e900c824ab65b4d5d6705539ef00cff52aaa0b3f6f1635e07ac2b950e37d41df54da3bc2799c8f33d802fe96572ef09d8c4a4ea6568b303217de5775101a5cc4c55c52a271a82b69138d0bef286884f8a44972fea488efed2d77008cd1ae7400f3868923995fa535e05a6bc51c652d9c6365ef88ef9c381ab344d37e91633836a5c2713cc7a7648a5a04e75e6e90c5ca3779f635cbb3dcd06638e14aac7c10250595688c15d08e33e64168c4290f743c303aa12b3da5349dd2a10e70623c56e600a59e019e5b69de8df68f20ff5af1f0618edcc896eb334fa7cf8978ffe30e693b271122d8c61c56e08c47619ccc294dde714549779cca4dae7b07a7a53c5e0befc03cbae939a49806f796f2402cdafa06feba677c50da6bb62a8c3a80faa4a6acba27bb3cef26eb82501d1f74c6dc73ef4514e1d91ede53c3a1bb0e50ffc5182c37254432c3bd76854a3c8b67dc8c9bff24cf729fdc22cbb3d79434f78e0a8ff49c5a33a5b149692cf059bfe8d4dff7960c4f1f7500ac7e5c57d7edb27967befe55ecde211364f899adde416b30ce3c7ffb299dedc5823de53af02c15b432cc3d708f3995276425324004ed0140c62eb96eee1d879277acf23c5bf10eaec73d732f24cf163caa4a5ad18e6bde4178bce37391a7e9b9d016a80574c5067739a9c13ba547d9fa14ea19741fcfbba74875db92fe661aa4e2394ff09b6264e5a09fed35e2faf02921f6b857be9d11c90c93c98914c6b7aee7597c7e6c453b789d3778b3e61ddd8a768825ed287b474a43f7090f28e7108f3e3e3bfb0b9efe1b7e9b645acbd1e5f52f87cc3bb37977eb08cdc168adb424a8131f329afc8a5f175104398ab5b7f686dfa0b53e7ecbef0a22fc9e182653a382f648f05f8d79f66aacf007fe58f28e26252dc7caeb351a60343e631c9daceea3b3d681cb565f94b311de0b5eac32d6d679408fd4353ab9ca2aed36f2f853e491284af7d4e05d12fc99b7789b77ca447897f79095c9000f9fec0348def9a0b9262d94b41a2b7cc8478486c1e6ca71192b443948b49053d41b69e0771b9169f89cfa711b7e633ec7cade3429afa6545477adb6e0ff2bed9358a38a8e8f8b28b2376188a6587cc2533c5de651189e5c9588e46498eb7916f3e856bd836721d9f179ac1434ae7b073a75fecd6c611a14a2b17eabd2803cdd6b1d212b3de0398abd72b30916f4fd613e0726bd14ae7690eaae38bd6bcef3cc9ef545137e504451b67b841ff2429d08b6118fd79bea54d01e095ebae071a1a4b977000f7e6caff82b4a4a788092e63cbef30ec223ea231aed19748e6ff8c68b695bd3033c732513ac4c759962cf24565dd1f0cb51846618a42fe2157b9abe4588f7609da940e1d50b22c0046cc24fca641091d4a270c66bb5cc125fcbb388476bc1bff1ef599e6d5252ec1d31f18e56d4e81d4744a3ce03cff625bcf231d1b7a5099654ec0547931ab487a4fd604eeb5194f545fc47114568461e9cf8293f2b7c242392199e426444a2c2f839ffd9ec1d57b443e6177c07f3e8e7ded1a81d4781f04034100f122b1734521ee9bd311b7c9f0c0bfdf5369b8fe35349f1de1abc177cb4665a43b3d81b9a4711f445e86dff57fea1785f2e8cc21678cc23ff54e781ad8b0c93892a86f748f03dfef9fb3c9b29691e2b53fe85ef976285ca32cb75efc034108f719906f20e4403f3402b422fe91d3c0cf492cff90efed2ea3a6423551f96d6bdd3284af7cb8ea162d7f9d72c8a309753a00cda83d6251164037da097c86022990d8c81598a956f9434ad3a80c760395895bca38146c53bc69558314a34308f9693d180de67806f69e2d75abb5f525d3edb03d77e818ace6b8ea2ac2f1aac49144510ca88cc6033d80e766522884761984c8908d92141877c6fb06fae49afe7d9c1d7e0804f8c2ac50aca2c0db1d2698e95dc3b088d01be3b915f4ff00d5e0abed74bc5e78164aa3b46abc8ed9e295e8f22b4ef04f4521c1cad5d700c5be031389606a7c179605d1219d8c43099828898d9c019b8df28a95f54618577b4cc8137f08b3c8b6f142a7947ab5b8e958246b377601eca20403c563374d31bbef32d02cfadaa6e6ff9d4144578071685ab40b463b6370831916370c43ea30ca2413c482e898051c8f04cafcc24dd0b30a06fe5d94c49b368413406cc806df20e68272e78201a2b96d068ad53efd052ef6080078579c4844771435eeb98ee6844aa0b9d746947702d8a4cc17c81aa94bc19160fb8b0155968d5009119f083c16038102e89808dc0703d532692ee91f81a48551ad795b4ec1d83f140a6f5ab4a9a6a47c6a3ea1d190dcc03df0978716320be25102a96744776bbbc37ad88a2b014450cf46b07b28e3950ac1d5a3508234c461d4c06dae0ad446444886486c9e47b46b23d129e3a782f556124566ee559ec1d838fc134f38ed6fe9206f18e9446a61d65ef082b3ca2ecd6447c8b20fa5e05fe3d6fa80768b9c55e9070578b22ab8822f07834233d0e661919e0321f7c029945e12115eb20c333bd7ccf48b64762f05078c7b74a5af38eb638781c3ce5796554a2c1d594f44aac60efb8a041ee504c6f131ca05df02da354e97e1b4568b71f70e90e7a2532cf8317b07e13116caf43fc4e5c79d708d90f306c55ab8e5c49b54b25cdbd235552d44e34c48a4294f46aac5ca1816f57e4d2bb7d55f03c72b320f8a2fe5d2e2251d487de1be9b4a71f86e6700915de3b64a768b81aae1199e1a64604781486c994768d602edbe1aeee1d84c6b53c4b62059e2478c7703ffc42ef34de112be90da3e98d9be4a64df473151af97d8be48ec5e25e45cd74ebb9e864e3f5975214c113dca25b4a8787f09d90191e091720736a2282ed3c246b07a55d236837c0d08a03c26368ff82922a4367e896bda3cca35052b06fbc037278ee1de091c023e28a5b17c95d8b2dd39c0f3db2bffa4a147db57c7242dcd0476b93617b180c43c205c844174ccea9c5c3382593ef1a416bdec3a4b9eef836cf96bc63480de95ab410254d2ea2a599c64b76fb26f10ef824c45f89b291fb26d14d93ae7e006fc973117e0b3bcd457837c0046a307c46bd7ee0df111978f82c10e1863cd86038ac1189731386f87eedf2ae11e0b21d8e9a3a96eb79f6524987e250baa51de80edaba761434f23b393be91d9c290d741325b98192dc3d69ec4c0823948b38ad16459bd67b71deed700c5ce4a182991053cb1e5210c13619e25ebbb26f441c6a4db17235b3444d7976f8367c076e8576dc8c9506ef9862ef20b790bad97d9c8806be8393dc49897a5e8822bc9a528a22e4eb10436ff88c647c76def003b84c732a33b0f9050f4c64f8498c90192e869de1c3f011ad770f9f521ed769dcaa3a80c6b03bec35d1b854d28658792bbc03ddc35aa7818d54e37e6b66f838171db38a0eadd6b6bac3e7f219f5c397f30ab8cc2ad66f265226337c15da424bd00543308525ba4ca2162bc9cfab8eed4ed8302f8db1c2dd1f2bc00368401fa25568a0da9cdcd4c9eb3e7498952842fb81c183b2f36e8fe88438612becaa5c84bdf0d54c4438801d851322239c054bb0054770054ff0854008854888d358892e68dcccb384869008d4b7de81efed2534b07734c44ae976da38e591dd599add553adbedca51044fc1c7eb3de9d979e854418116989abfcc0456385cf0c04432c36438cc854fc90cc0bd046124888294c64af46d4dda9059887608634186cf2a1579b6493b72ef203ccab7f556bca3e0011d25bec114dfd48aee2ccd72112b28822a4c044d7813de850f612acc84b9f0292c84ce059987ba8fd40d93c93ce611b8f8c21326d3157ac2b3f012bd5678342a69a11dd53c2bf485d77a9ebde91d54c143ff4aefb22dd1c0f7b9e25b5cc95da6f0df5f5914a10733d247c6c81c2dcbca3a5a8dd6552ea3f568738d08b2d176b4c364ecd1bef098d197805ce620f446c7d169741e59d9fde7b7f22cf10e7e5968c7c81e3975edb8a2a48dde91dee7fb9ede6b9bd190f3fb5c4df8df63d4616e77e890aee64c33f24b4c02b070148de26b44322364528f2962a98bb924236a448f98113be22ef36c76e7792956b42c560aed18f1a3c115eff00bed20de518d958c06badd17cf5cb3fb6dcdf26db6ad67a885f7a3e148b89e6946a39c88989a54e351225226031ee3943ca62083b8d0a3f1481e29237534b952934e6bb152c9b3236df4367a2f29a97f45490be528c74a4e23bde557c43cb29b6ebbbbeee863341dcd46f36bb957388c3e478b9c08b1cee8a1ee21157b1c3d8df08a3fe65252df11d2988c0ce2d21b3d8f5e46fdd1ebed3c4bee41873c9bc70a0ab72256caded11c2b290f72d333aae4d8749d2ba58157c0f0ddb7a22e1a682bee259172a61157352ea2b8bece0399b8419693a9a96fc5637ae256dc897bf14b449750ddc8b3f9dde879ac5853f174a1a4d98dd88b26ef001ad83bc4737a0332bef998dc788cd7d417fa42b444bb914845554547746b5c3cd16f224278e486dfe2b9a2be40460c818b3c7ac664223116139112e9b277647976fd764549b16f888cc896a2a54949cb3761c775ef486f445e907b908db6c889bc38f8ae1ac9c90c2f3c4610474d3e9211490d4f3bbf51dfb2c720329238166551a95561fe774a8a680cbaa23ae8dea11de87eecba77e07ba1c97dd0f886e4b93811b58a8f7c937bc5b70b32efdf13c9be5232d73c06c8881fb9c74cc51990998b9fe2a2aea479ac14da51565259ec880fc83bf2fbd21bb523a5f19ade923d4b6fc7ee917ba2c9de72f1517cba518be4ca2a76c55e4ee4597c01ebd7a3a6ca03be5ec557945e3099c70bf5ad7a4c46063c466ac1873624535a5ea949ab4a5a8a156925adebb152f28ed7c23b521abd9cc6737a53b62f6da4adb4bbee21551337d23e27f222a10b753de9708b486a474c8670f95e7d1119ec31c0e5249d254bb21b6bd27aac1c0bef901cc9bd1d2b251e9806be457b67ec6859f224ff1e22998a4881141222b94537881c0b2bc8a0581a758b5892e29afa628fc9c9241225d1a40a2bd56037f2acc4486c7aaf7c7a9b7c2d5672efc86e4e3705b28352e2245e1adc4784983494840a952f6924557834130193a4b18477bd4af245bebea2be104b848b026454692269d25ba3927673257dab7a073d93de1bbc434c6f94cfee91c7344e36de5b1a4061f9e1053789d4b2af34ad7109a5d9773e9211496d8e7de69e7c3dadc452227d4a0ba9233d488fd8432ebca39e670b25959ea46e85468d477eab7c7ac7bad4939ea597db3e52d555a92fbd56c9a057dc6f1299179691f9265fd7d5f7947a4c67ac8f8db1395e6634bed30ef305ef88c4b1325e8dd78d3482fcce792ebd695ee38cf1e63b22cd9966bc1def2a54f6e3aff1e13e22d27c7c4486c95ca82fc9d7792c5d7a0c2173820f7f1e5b637bec7c9367899292589188768cddb137f6531ab977101aa641ee9e374fe3601c8ea39f10215fd2701ca73c324bc65473cc5c1201a3c7cc98216488fa5e764b57d59778cc69cc02196ecc8f07e3e115e5688a15a0910cc7c27894d140de8169b0e609bf05b2337763f895d755a4b93a432a025cc6290f393565acde4f24334ce657d497c4d2196fd4e7c793b1367e4bf3ca85927e258db1a28ddfc71ff83d18f426dd27b7216f93a17787c6d3f10c7bcb8d0afe32f78ee7e3cf1213648b71e75b1e55220f99e129c46df51dd7d5b7e2318f604fe3eeb857ceb37525cd94a3ec1de3e7f1cbb86f3c9a267e976a6106e672fc2ab7e5d63731135cf2c87455d66543363326e8557c7925af7f4684984c7aedfbd417b8c8dbaac7c83b4246decb5ff2413ecaa766ed40348876e05871d2770da7460f5f2f699b4bfcfe5dc754654776c5cd4d1f69ac46644ff67322c40239bc4184a913492daa9169ea96e28b7c5da82f8924f018e012cb894c9595b4ae1d9846c93bc85bff8921d3e8b533d39539999707b77a9a6bd5883c9485121364f067dcef2384476198ccbddd52b3fa661e13cb127a608846d53b080de011e0b7328977903757c95bac3359711e64559edcdbd3d433afacc96f63a5c6e55dfe4879fc88486a5342e6aafaa61e734d7de5594a867099cb9ff202883c96bce392c608d308f1fbceb4f96072cba5dc911fe447f949eecabd7b7a9a6aa6919fe59712913ed8abd2565ad763e63b22f254c12fbc6205feae5b9a5ee66b1c4b258f496309c82886622acb3456080f15d1488c4b1ae8cc00f3d19c282b65ad203f11a5a3b25576ca5ef9ba8b086887729043e528bea43cb02927f95d39df5291eb4432c33ed3ec31bbeff375457d63c5526c20e328aee2d56325e5c1e1d30244069d1d3059aecc397a391a1dbe0b1c720f91352554222556128552e8ebf5ea78ab300a2b8fc6fb12134e3929bc329037573ce42e22a90d2b64eee996aea92ff1184446504666b83453ef7828bc23e3814f9c08cd3ebb504445bad455a0c328e371a2c8f28ba228aa325134e50dec5df950a6ca4c992b9fcaa26002449075940765a03cfe5522ca13314ce6325f37a96fd963aaea1b83cfd8988cab74959ef2acbc601e8f85776434f0d924ed655be92baf6afbbba8515baaae1aaa395ec8cbb12c85f24a857f161e9213e1c60b75a5aed5cd1d2a720711e549dd22cbc9e4b3aa345f5faa6fb55b4ad5f7c263baea4eddab5f198fe5b8a0b16caf6c7436887a508feae94aa6a9458d7a563e544bb555471ea9aeea65f5ad6caabe1aa821fc3f911adfa3abb788101eb9251532b7bba5c5d57c9d7b0c90a15474945c9f32318f757aca6284cf82b25556e5804143eebdd5d3c89ecaab0375a80e95b32ac8f7f8c8dd1e5210c90ce7a65fed96b25842f99aa82f2243a9235554a5328da5bd1aa2f3a1d4b12a4bc37be700f7d7ab7f27116c206e39997bba25b592af27b57ceda6b184c8a8ea44d580c8133e2f8b472789a96feabbfaf14b44fe864c738548724904db54c595de8d5955bd5b62abf93a55dfc26328f018559da973e021d3bbe554fd54176a477db82b667ea95efd9b884cb13d12c364be595bba9aafafa92ff19819d893da5df6d49efaacbea8fdbfc147fe52a6b91e353993c7aa65649af3f5c5acaa9eaf9bd4f7352533c3db890559fb1d44bee5d1e823659be813d21d5c595bfabe5b028fa9a96fee3119196362de333dfb794f73c1e3e7441a78102293253142a6497d6f754b358fa9c71261b3fa2332cd0d2298474ea44ae67abefe7656055c6aea3b4ad53723b39e6cfe8d99e6072a52f790926d892132e94ac12f774b935db3c76036fb7f83aefe828a5c25f29519f699bbbba5da74b3d22d95f27599cc6172fc1355e43a91d44e15324d2bfb37baa50bf59dd5c89c27d69f9069ee27921926f3b3b5a5a67cddade6eb0a1bfb87f5d96ff291dc1c6c6e46e657baa5efd537b3767be2ff3b33cd5f25822d9804844ca5f66dee96aecdaa0a8f698825ec33e1e4df97692e896c6f1071ea4432c3649aa79bdfa96f7c8ffae66ce23f4f452a3c2a442609b11299a6b5a57b6755573d0693a1fe3c15b9e0911301a39161327f497d27cc75f54dc9b013eecff7918c4866b8d2bbab5bba36abaa774b17a64ef8ff041f29d900594ee6ee95fd9fa82ff698e144f8bb7b9abf8548230fb011314ce6672bfbe5b5a5dbea2b4ea43f21d3dc4f6432269692f9957c7d87fa4ee489f267aa484e6454273251c1e0ab44a6b95baaabef4ff2b53ad1fe5415b9e09113c9be3099eb6b4b5756f62f3ca6b9c67b9bbcff362277c6cc2511fcf531f9c064eeccd775f5fd3e5f4fa693d9bf94c8df143575229339d8e7e4b34ae6472bfbdf764ba0bc8b3f29d3dcf611c2a3304ce68e5955cd636ea8efa43379f8b3320de67117911a999fadecdfcad7e2e49e98a913f9f7c6cc55221532558f517ed82d5d4eab9e26dd3f56456e12a990f9955955ef9afa4e7ac0e55fad2277e7de5b31730f995f5ad9bff098c9f3e4e58ff291f9cf9994c85c744bdfeec4bbb2b684b9f427af7f66a6f955328d2bfbf5b5a59bea8b8e9efa37659a7f21919acffc9595fd8c8bae19df78481391dfa0ab3f2393ab6fa55baaaf2d7dafbe938e66fec999e657c9dc58d9bfb6b69493999cb5e59f9d697e8dcc8f56f69bd696546df5b7eaea8febd57f99cffcb56e49d5d6c0e53f44577f894c91af7fb4b2af6db4ed7f13910b32bfb8b2afedb4fd9d449a75b54ee45facab3f24f3cb6b4bda9776f8cfc8347f89cc8f57f6b5a376fa4fc934bf48e6e7fba0818c76d6acff5c15b99b4cc33ee8efd796345b73fe3b8954c8fc78655f7335efbf9548854cad5bbaa5be9aaf05ff6999e697c9fc60655f0bb5e85f3b19f933ac42a6aabe8ddd92166bc97f53ccdc20d3f0de52b3faa2aba63193ff6222353277754b1aa3b1fffd442ec8dc5cd9d7388dffdf40a442e68afa96f3b536d086ffc999e697c99cbe5fd9873812feb7102993b9a5beda48137ff7e7fc6d64bed907ad49ff1bb9a464aeafecbbda58937ff767fc8d64aebe35aa299afabb3fe16f26d3d82d69134dfbdd9fef379369ec96b437edfd777fbadf4e2657dfe2ad51ed439bfeeecff6fbc968b36abed6e6daa7b6f8dd9fec771b26d3d11e0af5d51eb527adfbbb3fd7ef374466f2a9f5b467144bda8bd6d75e7ff767fa338c90997ca28b81d091b5ff784b6619997fac6eff90b966ff90b966ff90b966ff90b966ff90b966ff90b966ff90b966ff90b966fff77ffedffff9ff3e70c4ac + + + + chkAddressee + mDisplayName + groupList + addGroupButton + protocolListView + + + + klistview.h + kpushbutton.h + klistview.h + + diff --git a/kopete/kopete/addcontactwizard/fastaddcontactwizard.cpp b/kopete/kopete/addcontactwizard/fastaddcontactwizard.cpp new file mode 100644 index 00000000..43c499d6 --- /dev/null +++ b/kopete/kopete/addcontactwizard/fastaddcontactwizard.cpp @@ -0,0 +1,135 @@ +/* + fastaddcontactwizard.cpp - Kopete's FastAdd Contact Wizard + + Copyright (c) 2003 by Will Stephenson + Derived from AddContactWizard + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2002 by Duncan Mac-Vicar Prett + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#include +#include + +#include +#include + +#include "kopetecontactlist.h" +#include "kopetemetacontact.h" +#include "kopeteaccountmanager.h" +#include "kopeteaccount.h" + +#include "fastaddcontactwizard.h" + +FastAddContactWizard::FastAddContactWizard( QWidget *parent, const char *name ) +: FastAddContactWizard_Base( parent, name ) +{ + m_accountItems.clear(); + + // Populate the accounts list + QListViewItem* accountLVI = 0L; + QPtrList accounts = Kopete::AccountManager::self()->accounts(); + for(Kopete::Account *i=accounts.first() ; i; i=accounts.next() ) + { + accountLVI= new QListViewItem( protocolListView, i->accountLabel() ); + accountLVI->setText(1,i->protocol()->displayName() + QString::fromLatin1(" ") ); + accountLVI->setPixmap( 1, SmallIcon( i->protocol()->pluginIcon() ) ); + m_accountItems.insert(accountLVI,i); + } + + if ( accounts.count() == 1 ) + protocolListView->setCurrentItem( accountLVI ); + + // Account choice validation connections + connect( protocolListView, SIGNAL(clicked(QListViewItem *)), this, SLOT(slotProtocolListClicked(QListViewItem *))); + connect( protocolListView, SIGNAL(selectionChanged(QListViewItem *)), this, SLOT(slotProtocolListClicked(QListViewItem *))); + connect( protocolListView, SIGNAL(spacePressed(QListViewItem *)), this, SLOT(slotProtocolListClicked(QListViewItem *))); + + setNextEnabled( selectService, false ); + setFinishEnabled(finis, true); +} + +FastAddContactWizard::~FastAddContactWizard() +{ +} + +void FastAddContactWizard::slotProtocolListClicked( QListViewItem *account) +{ + setNextEnabled( selectService, account? account->isSelected() : false ); +} + +void FastAddContactWizard::next() +{ + // If we're on the select account page + // follow it with the add contact page for + // the chosen protocol + if ( currentPage() == selectService ) + { + QMap ::Iterator it; + for ( it = protocolPages.begin(); it != protocolPages.end(); ++it ) + { + delete it.data(); + } + protocolPages.clear(); + + QListViewItem* item = protocolListView->selectedItem(); + AddContactPage *addPage = m_accountItems[item]->protocol()->createAddContactWidget(this, m_accountItems[item] ); + if (addPage) + { + QString title = i18n( "The account name is prepended here", + "%1 contact information" ) + .arg( item->text(0) ); + addPage->show(); + insertPage( addPage, title, indexOf( finis ) ); + protocolPages.insert( m_accountItems[item] , addPage ); + } + QWizard::next(); + return; + } + + // If we're not on any account specific pages, + // we must be on an add account page, so make sure it validates + if ( currentPage() != selectService && currentPage() != finis ) + { + AddContactPage *ePage = dynamic_cast(currentPage()); + if (!ePage || !ePage->validateData()) + return; + } + + QWizard::next(); +} + +void FastAddContactWizard::accept() +{ + Kopete::MetaContact *metaContact = new Kopete::MetaContact(); + + metaContact->addToGroup( Kopete::Group::topLevel() ); + + bool ok = protocolPages.isEmpty(); + + // get each protocol's contact + QMap ::Iterator it; + for ( it = protocolPages.begin(); it != protocolPages.end(); ++it ) + ok |= it.data()->apply( it.key(), metaContact ); + + if ( ok ) + { + // add it to the contact list + Kopete::ContactList::self()->addMetaContact( metaContact ); + } + else + delete metaContact; + + deleteLater(); +} + +#include "fastaddcontactwizard.moc" diff --git a/kopete/kopete/addcontactwizard/fastaddcontactwizard.h b/kopete/kopete/addcontactwizard/fastaddcontactwizard.h new file mode 100644 index 00000000..ac8db23d --- /dev/null +++ b/kopete/kopete/addcontactwizard/fastaddcontactwizard.h @@ -0,0 +1,64 @@ +/* + fastaddcontactwizard.h - Kopete's Fast Add Contact Wizard + + Copyright (c) 2003 by Will Stephenson + + Derived from AddContactWizard + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2002 by Duncan Mac-Vicar Prett + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef FASTADDCONTACTWIZARD_H +#define FASTADDCONTACTWIZARD_H +#include +#include + +#include +#include +#include + +#include +#include + +#include "fastaddcontactwizard_base.h" + +class AddContactPage; +class QListViewItem; + +namespace Kopete +{ +class Account; +} + +/** + * This is a streamlined add contact wizard for users with simple tastes. + * @author Will Stephenson + */ + +class FastAddContactWizard : public FastAddContactWizard_Base +{ + Q_OBJECT +public: + FastAddContactWizard( QWidget *parent = 0, const char *name = 0 ); + ~FastAddContactWizard(); +private: + QMap protocolPages; + QMap m_accountItems; +public slots: + virtual void accept(); + void slotProtocolListClicked( QListViewItem * ); +protected slots: + virtual void next(); +}; + +#endif diff --git a/kopete/kopete/addcontactwizard/fastaddcontactwizard_base.ui b/kopete/kopete/addcontactwizard/fastaddcontactwizard_base.ui new file mode 100644 index 00000000..f53f487d --- /dev/null +++ b/kopete/kopete/addcontactwizard/fastaddcontactwizard_base.ui @@ -0,0 +1,219 @@ + +FastAddContactWizard_Base + + + FastAddContactWizard_Base + + + + 0 + 0 + 619 + 481 + + + + Fast Contact Addition Wizard + + + + selectService + + + Select Instant Messaging Accounts + + + + unnamed + + + + layout21 + + + + unnamed + + + + textLabel4 + + + + + + image0 + + + + + spacer18 + + + Vertical + + + Expanding + + + + 20 + 41 + + + + + + + + layout19 + + + + unnamed + + + + textLabel3 + + + <p><h2>Select IM Accounts</h2></p> + + + + + + Account + + + true + + + true + + + + + Protocol + + + true + + + true + + + + protocolListView + + + false + + + Select the Instant Messaging systems to message the contact. If they use more than one IM system, select them all here + + + + + + + + + finis + + + Done + + + + unnamed + + + + layout21_2 + + + + unnamed + + + + textLabel4_2 + + + + + + image0 + + + + + spacer18_2 + + + Vertical + + + Expanding + + + + 20 + 41 + + + + + + + + layout25 + + + + unnamed + + + + textLabel5 + + + <p><h2>Contact added.</h2></p> +<p>That was <i>fast.</i></p> + + + + + spacer21 + + + Horizontal + + + Expanding + + + + 211 + 61 + + + + + + + + + + + + + 789cedbd596f233997ae7bdfbfa2d0ba6b1ca8352b021be74292e7799e0ece45ccf3282914a18dfddf3783ef222dd9966c67655665ba5d0b1ff03da918c897e45a1cc3fffd5f7f3d5c9cfef55ffffd1fd39936f38cbf0c57cbfffa2f731e45d5fff7ffffbffffb3ffeb3dd6bfdd5517b7fb55545fdabf39fffcf7ffca7e1ff65fcd568f1ff6a9e0e24b7f9ef23e236d8b425f3fb47b8bf2deeaf0cc9fc7a3b22a6fb67f792f9fd7648dc69756b5e1e72eec8f45c117770bd277eefb67a3507d792fb354767c43df068c1b92b9e373b21a6e7a5f7c45dbc3fb920eeb506356731711f5c159c7be279d13931ddef2c897b48dfac4d3c680d797af0febecc5f97b88ff4ce9e889596c67f879e83da6a2e7ac44a4bad59ef12ab2d9d97d7856483a71f7a0cc5fd79977888f72596649e3e734cacb54c9e5fa45711e975c7c4cc6ab64a62bd65f1f2bc2336c0d989649bdf3f12dc46fe0f894d7081f4a9e2f9339758457efd8098ae0fe7c4561bf50b7a69e2fa45446ce2f7e98cd86af3f29fa5c436382e25f3f21c3738ebb2bc8e8835944f1e12ebd03f154cf767a76056e1797d30a09fc16a3ce7794c2cee8f880d94df0c7a9982239dd844f9cc89db9d362fdfe444322fcf0af5c512e99f3f11ebb8df3b22b6505ec52e675bf0bc416ca3fce65370bbd786befbc4fd36d76336271e800d8f78d8e6e91ff1f6d37ef637636213f5d3be27a6f7a5376071bfd193ccd367d2f56d053ced736ed70e8697cf1d711be59f259c3bb5f1fc78c46a9bbfcfba21d63afc7ea792ccef9ff3fad8eed6c6df7749ac77f8f35c85d8e8c09fe1f9d25fc4785e4fdcef2bc46687d7874823b63abc7dfa2ae73efb17f893063133aeff0db189f7cdc692f9eff390989e377388ed0eaf1f53e467209ee70c8807a84fb329315d9f9860e63e713f2f6f561a74fd628f7888fa9742ff21931bed197a29e2f7ac4fcc8ca717e5a3b0eb79fd9af1f6c14a877ecf6fc19d6e87d787e494b8d7e1f57989f46842ef04efd3d813f8f3529487d6e977787d59407f9d31bf7fb6473cc0ef21eaabd1d6dbbc7d4eaf890dd4c7b90dee285dc4cb25b1da4579203fa6b8deb4c01dbdcbf5b60cc95c3f17e56db13b787af231b185fa6d04e04e07e99d3f10eb5d5e3fa6bcfdb7ed968afa9d77c0e2fe29da8bdd31ba5c7f3b2036bb5cdf8afb5f167e299e2d8660f604de3e66c4b5f1fad227b6ba5cdf25f7279de7fe800e16f7a70f6071ffb447dc46fb2a63621bcf9b9d4ae6fec43b90cccb23b802b3e684f2e0f963ea88e7df10d3f317d7c41db4d788c74b569b04e37d5df63cc42f9fb8035e70ffc86a1b5d3f3589bb687fc939b8dbedf1f797787e5ffcee87c43db4f72c0277fb3df4af78fbec0cda3af5575a6071fdcc23ee53fb1e80bb3dbc6fde241ef478fa2e67e05ea7cff56c3624733d9b1a71175cf0fad2198ae73b23e24107fd8f1de221da7f13e53b14f7370dc9bc7c9a26710f3c47fe1479bf0516bf376dc9bc3c9b747daf0f5e223faaa85f05d2af8ae72536b142fe06f543edaa3d9efef49a58efa1ffd421367a789f0b16ef6b7a92797b6bfac403e24032af1f4bee9f3ad2ff4c8fc1323d3ab10aff565e82bb5a8fa7df3827367b783eea8b269edf8c8887c4b164b44fd4075d3c3fb821d63ae8af44c4cc787d43f9ea5dbbc7ef6f26e09e82e73553c1039e9f6646ac822d94afc13c0e671be9313a149f674b627aff5c23a6f7cf8e890df8f329fc8bc1d2c3d95a807b2c40f1f7e7c4f4fee654327e9f116bc473c918afa07d9bb27ce08f4c999e1931a527837f3259771e7a14c4f4bce682581fc0dfc33f58e27e77426cc25f4f47c416e24b81fa60b1e7c33f77883bc8af7d2b19fe4523ee829b2531bdbfb994ccfd45b32236c029cac716e9c9ef89293d33f82b9b3d1ff91b11d3fdcdb164ee1f9a136293784732c61bbc7fc8865b26957f442cde1710db1daeeff4115c1bd76f0966ed0fcfdf25a6e737f7882de27dc9f02f07c43678ccdb73f7393ecdc1223e460658a467a681457aa605711bf17ef60066e9e3faf83ef1a08ff71f12d3fb9b4792b93f681e83d9f092f308f997e3df2c05cbf73bc4f47ebd0766ed9fb7f77046ac903f3e018be7374f25231e9c11b789cf25a33f8ffc74c5fbbc636216d0b87e2698bd9feb1f2f8855f2e71760f1bce6257187f84a32bf7ec9fb3f5dd95fcedb60f1be7849dca5fe15dedf63efc3f3aec1e279cd1be22ef1ad64f81b3caf2f9e974f887bd49fb289fbe8afa5a8affd9e41fef70e2c9ed7bc27ee0de0bf9f24e3fa07e23e7889f40d447e4be83f10fec3c6f503999e0531a52717bf0fd07fcc90be016b1ff08fa84f03f1be664b32fc599b78009ea3fe0ca57f447b1976a9ff113489e9fdc91531bdbf407b1c327d90ff0e583cbfd9153c44fa7ac443708af42ae279f91ef110fdd5661f2cae6f0e24c3df0f8915624532af3f19ae57c5f3a625b182feef0ce955597d853f5589e9fe469358256e48c67c01da8b8cff21ca4b13cf7786c42afab7860b66e5c1eb6f4323a6e73574628dd890cccba36112ebe008ed4b17cf4fae8935f49f67d04f67f591e77f1649e6d7376c627a5ec392cccbbbe1101bc4ae645e5e11d263b03762bc84f41be2f9d327e23efcd1ac920c3dbac443b4a786474ccf6ff8c4e690a7df437998e27df908dc57707f722c99d747f38e58457b6804c4f4bc462899a7af11115be005ea8f25cad38f880d8c3f1a31585cdf482443df94d826ce24f3f209511f6df1bca0416c7679fa2d94a7cdca1fe32fba9e950fea634e4ccf6b4cc18316f14c32d7ab31276e83475c8f9e9c9fc913304b0fcf8fbb436c613ce48c89ed2e6fdf451fccf4457f674a4ce94d7d624aefac496ca0bd360ab0484f632119fa96c41df088c7af9e8ce7d11558a4677a0b66ef437f62486c52fdaec0e2798da5649efec688b84b3c91ccf33fc1fd325e3b0ab89e00e6fefb91b88df19d8ff77798fea8cf63627a5e6387b847bc2b99e767c4db4f4fce674716583cdf99107730fe9b213d5d56ff30be6d80999ea89f7bc4f4fcc63e719ff840b0c2df37e2edb127e373b52016f389787e4fbcbf710816f7378e8807c4c79279fa7770bf9c1f4f91dfbe785e5811b380c8f3ab12b3ff787e3330d317f1c920ee527d3a211e2a48dfa964f8d73362053ce1f1b227e3b38bf21888f735cec1e2fac68564fc7e49ac8227dcbff7647c4dd1be86e279ce0e71bf87f93ae83564e5c1eb677a2b99ebad9f112bd0af71454cef6b5c4b86bfbe21d6c019d2af88f7b94de201c6cb81463cc4f834d1c14c2fbcef965853509fef8875e27bc9fcfe09ef6ff4e4f87d81fcaae27dde0931bdcfdd23567af0bf317830801e46452cf2fb404cef6b3c121bc44f92319f88e769e2f9be6015f303a90b66f9e3cf374d629df46c119b785ea32d19febd436c8177a0af5c4fc8507ff55e0ff1da6d10d3fbe7a8bf3acb1fc6db3ab1785f97d852e0df7bc436715f32afff63949721df0f7f6488f779a7c45a8fdf9f65c47a0fed670016cfd3517f8c618bde37948cf938e4cf14eb7f2efc8b299e3783bf37999e582f3926b6903fdb970c3de1cf4cf1fc862a99b707ad49dc064fd01ee4fa479e82bb3ae6077db43f4ba427a3ebeb1c733d34c9fc7d0ef4b5d8f3396b0dc9dc5f6b74fdb043ac4be6fe34447cb1c5f3bc33629aef710f882dcc0778d0cb66f7237f067117cfd34cc12acf8f6611f7c0152fcfbe8ce7ae0666efe3efcf1f89e97dda3d58a67f4732de6713d3f33547322f5fcd25ee83f5b664ccbff0f2edcb781ddc82657e75629a8f0a47e07a8194b7ff0330d39bd7375d2316f9f788e97d9a4f3c200e24a3bfcefb437d19bf03bcbf23dee79d13d3fcd31ccfeb30fd79794427c494dfe48c78004eaf24e3fda164fe7e2d221e12c792110f78bc61c35113fd2df7102cd2a36560e68fb93fd212b0b85f1b132bc4a964f8ff9058c5ef13bc4fc6771fd7b3f134e6833c8398e69f74a49ff5eee1efb53b6213e5339b80993efc79fa0931bd5f13bfab2abf5f6f4ae6fe5a6f106be010f9e98bf70706710ff32f85091ef6507f931d620def8b9a92f1be5032de574ae6fe47af8875b0c7e37b7f20dea7a960a63f673d2056116ff427b0683fb6414ce5e1d0f384feeeb5649ede704aac53fa4e25237d2dc9183f39c406788cf621fb1b25ca6b28d6a30294d750e4c7cf8869be4ddb2556d15fd546c416fa5316d23364f9c17e0eba9ea59f978f51121b2ad683cf884de2b664febc05da8b22e6af2df807a54bf3cb19daabc24a803fdf3325a33c97c43afab7c12598bd9fb3d121a6f79906b1455c49e6fa8c79ffa32ffb2b15f454c5fab19683fb6d8c27039398d2138f8887980f34505e6cf489f8e10fc02c7dfc77df233691ded9a364f40752620b6c5e49c6fae235b10db69a92317ea4fb951678cec77f7d395f91a3fcb5ae82f53f0ffa6922fd7e4eacf479fab20b6215f38f560166f511fdd77d624aaf45cf17efb74e2473fdac21719b5891ccdb4fc5fb337dd97fd250de7add83e0e54fbfb3f2e0d747d7c4943e0fe5a90b7ddd01b105fddd4a32bfde86ffd0c5fb1d87b843ec0ad6103fa0271b2d53ff3624a6f99810fece10e9c9778835cc5f9a682f061b40607d78406c23de04c8af21d217f7886db42707f1ca10e971e9fd4a57e3f16284dfe5fa8789f231457af39c58457f4d83ff31d98808eb9da7c403cc8f690f92e1ff746205f3f1da2db181f91d0ff5d514f90dee8975cc17e72d6203eb6f26fcabc9f4e0cff7ba60965fcc6f414f93d51fac0f27c494df592919f12b25ee81fd13c9e80fa0bdc8fe62754c4cfb7d52f43facae067df405b86761bec9b3c0758de3cf9f12537ea6e27713f3a301ea9b25deef9f12f7c1a12999e73f7c201e80c7a8efb648af8dfcdaacfcb0ff614eac5379e27d76af85f96b83eeaf47f49cf17e9bf540509f2d624a6f720466f59fe7277c24a6f44423c9bcfec60ef110bce4ed6520fba3de35982982fd16f7e0611bfd85f0092cde373b23267d92a564fefe989e2fde173f48c6fea811b1423c96ccdbbfc6fdc3a0dd1d52fc9b112bf0e7d913b105ff9876c17589f3f2a3eb9962dc3f98485f9bf90f8c2775627a5f7a42ac129f4ae6fe69c2fde140f64fb512dca5f5b9620216e39770072cde9fd960169ff9f373a4a7a3b4e11fe23e710ffe367789499ffc4a32e6cb15624a6fe648463cd827d6880f24637f08ef3f0fe4fc957e0cae572478fd457eba6cc48af5ea5db0c84f80fc74eb0d913cfd63e236d67be37be20ee6272ca4bfcbea0f7f5e42cf53fa945f8f98d29753fa149d38908cf45c131b1ad77bc2fdf340f697b539b85e71e6ed6901ae571078fe2a6293fa33487fafd7a6fdabc85f6fd081bf4d35e23efcdf14fad6fd63febc694aaca17e4dcf884dc4571bf5ada774a9fd5c131b949f1bc998afcf24c35fe5c4267882fa21e7e734dcdfef68983fd014621a5fea3eb18df19d9580bb16f93fb4977ac0c1cb2b34884df853f708dc3768fe14faf6853e31da5f5f19201ea7743f6befd84f44ef63e947fe0e24237fe792315f7d416c81c7285f39ffa7a13c5983c27c9589f7d71580a7c78c88a93c33f89301eb01717d32e83310e90f7789bb88a731eae380a51feb6f77c4949e694b32f60bb4896d625532fc0fca7f28f47750bf86f50a13d7bf24a6f89bc0df0c7bb4bf7a8afa57f7ff797d4b91ff61ddc2f8fd8e64a47f42dc437f20cb89fbd81fb00cc1a27d2d4f8929fd334b32d6df29bd6a8b3814ac737d2668ef727fb256803b2dccff07f0ff8a787f8af6a1307f85f563f84b453c6f9613b7891792d19f3827ee80753e5e67837d0dfd3bfd112cde6fd8c41ddadf7549dca3f9eb33629a6f8ae9fa2ef58f32d42fb547fece47fe54961fc43f4732fcc70df100fdafa40166f9c57a00eabf2ad23f3b948cdfe97eb54b7c2b19fd3df81fadddc77ab40fffad89f69e237f9a68df09da8fd6a3fde7e603b141eba3681f9a486fe0120fb13e1939c40ad60b2bb44f8df52f305f48d7b3fac4d353c2df6b32fd779251de5de21e714f32bf7f097de5f8c285ffd7dbb4fe9622bfbac8ef14e9d7bb347e9e26c4b41f628ef8aa8bfe5f8efe832ef263a3bdeb8a06ff3543fbd5457ae6a664ecf7b388fb601dfece68d9d84f3347fa0db1dfd93a26a6f574af434cf9c9a1afd111f3ef287f83f528b03e9112537a83476215fdf98aee67fd715e7f6da4d760f114fd67f8174394c73c20a6f4cf33c9d02b271e103f4ac6fc07fa6f668bf66b698fc4b49f29417c35dbb43e9b138bf6664ec1bd21ce17c41ab18afa19a1be9b227f8147aca13f12ef10eb186f5a48af29f26b35887570a8121b545f0f894df4afaaa164cccfdf1393bf9fd3ef22ff738578082e0cc9e82fa2bce5f845bb26a6f914f78a98f63745e80f5a6ddaff12a13d59227fc113b181786ca27e58227f7187d8427fbb807fb05405e9290692715e202156c13aeaabdda2fd2546484cfb4f33c42fbb67e2fc43ae11d3f98d12f5c716f3a74bd4379b8d07f8ef637a3ef37fd89f0efd6df1fec591645ebe8b5b628d58958cf9059e9f7a3a0deb07da9098f617eb1e31ed0fce2c62ca4fde030f68fe3f710543df709fd8c4fa7972436c617e29ccc12c3ee27c530516fa964d624a6f9912ebc4d79231bfc5dbe350cec71b4f82b15f5b3b108cfc2e26c4e2bc481b2cf6e7678fe09e0dff13ef8245fa238fd8c6fcccf21e2ce63fc70698f5d7f9f5a30e58f8ebca22a6f45736b1415c49c67900de5e8672fca41f11b791bfa20116fb9bad2eb847e3fb1cf9673f63bf509a1153fa8316b83e40c5d31f81597bc7fc91466ca1fd4f4692d19f46f97484bfdb3188a9fd4c048bfc9e1353fe2aca9f6a12ab92b1bf8cfb83a11c6f1913e21ece17987b60168fe1cfcfc0fd3ee2ef2804b30885f91fe8db95f9457ded2a2df8ffc9297117f12cb800b30e12c6d7683ff5060e9c5fe913d3f933ed4a30ca638ef4f7c47eb3f91d58ac9fc53ab8dfc6fd11fd2ed2171e10b7b1bf643c148cf5c4b443dcc1fe88c903711feb072394af1cef2cd15efa62bfa7d92336e83c24f2db67e18ce77f82fa5eef47c0fa8a42dcc37e9cc51db185f593f888d8c67c48e48345fad29cb88bfd6013d4d7bee84fdbf0177da637fc273d4fed61bc37c6ef723ca35f10d37eede509b83b407f752e98f6cb8da1f7a047e305ab476cd1f9d09cd8867f98ccc07deabf86d7c41d94970d7fc30658787f02bd06227fd994b807fd4697c40afcbd8ffa537798797bd9417b7b5eef3823a6f39ec609b18af78de7e0ae8ef5ffc91e31ed3f9fa2bd0cfb3aea6b047f3514e9090f89fbd86f3281be4351ff1de4675877e8b95eaa645ede23bcff79fc72486ca27f33823f579803c278f48e98cefb4cae892df477e6687f8ad89f36bf25a6f9e700f5491958585f8d915f45a43fbb241e60bdc7477c52d43eea4f85f27c5e0f1910d3fe1f3d2136e05f76505f54713e7154122bf87d227ed7113f271d621beddf44fb55597dc1f9c87de201cef765c8bf3aeca03d066de23eea4f1410537ed229f110fbfd6ce8c5a20fea8f7f46dc072fa1b75c1f29c6c4b49f61722219fddb1e319dff5d2e24737dc721319d079e2c89a93f3a6a13d3f9567321197a415f8dbd9ee76f4ebf8bfdeb73a45f13fb3de737c4b43f728178a3b106cd7f1f45c4e47f22f83f4de8e3217e6bea00e53f87bf795e7f41fbd5db5df427e7884fbad8bf38bf26a6f14682fceb431a2f8547c4f4bee49658c17cc3645f30da7bda2656319f52e279723fcb1cf5cb10e7cd62dc6f88eb23ba9ee590e7c785ff30d421f2171d13ab88cf23a44fae97cce12f4db1ffb140fd34fb038c0f52b40f533c3ff18975ac973a282f5355f0be05da9becaf1b280fd6bbc5faec7497b84be75fe9fa1ed59f10e56bf535d49fbc416ca3bf96c37f58f50c1cbf1ef9b386b43f337a24a6f4b9d08ff5cf311e092d628df4803f90eb0d73a4c716fb1d97a83f763de3ccf38ff6638be7c707c426f6afd9482feb7f430ffb4432ea2b6fbfcaf379e118ccc239ce935d81c5fec7c91cccea33fc750416ef4be9779663c43b5e3f15d9ff35f1fc765fc379db7c0a1e74e19f12a4a72dee8f9e886dec978cdb60d63f4379f2f187d261fe0ee38721b14a3c27d631fed12ec19d16da6bd4010f69bd3db927a6f715bc3fa9c8fe9dbf0feed379eaf8102cae5ff2fe9bd2eb2b18df4d7c302b2fac0fe8c43db49f087af4ea03ba9cc760d546fe96d047f68f4c05dc3670fe3b6a81950efa8fa9e02ef67b8db9bf54647fc4b0c0cc9f613ef908dc1962ff67f20016f73b5db04af38573e825e3bf3d00f76dfabec32398b558e407e5356439e4fadbbcffa2287d8bbe0f81f263ff80fa54e1f932fe8d915fb53fc47cc198b75745c68b39f4d6c479f46513dc33d1bf8b911e8d3968ccf7f078a0e862fec6bc05b3df319e6a11d3fc507a494cf32913eecf94677f88fa6188fd0a09f2630c34c40ffb18acd1fc5885f27cf677286f53dc9f36c01d1aefe926b185f3390efd3ea0f9631bf935351abfcd797f4ab1c4f9f7f90e319d6f9f1f1153bc0befc01d15e7c5c707e01ecda7d948af35e8a17cc30cac0da0c798fb2be5d95fa17c6c71de3dbf0077a8bf52417f5b9cbf7334f06048f9d903b3e7a3bf714e3cc4f86dc2d3ab4a7f35df038bf3f125ef6fa8d2dfccf7c1e2f718f7b73b1638db25b6e12fb40b7057c7fe972801b3168dfde797604d81deee8d64a4ff4832be2fd02356c1f14032fa8f5c6fb523ceeb5b1a98ddcfcbdb9a4ae6ef1bf1f6a94a7f343f048bf981890aeed1fa83e582eb1516ce6760f67ed4cf67e6e90b4692d13fe9116bd03fd9938cf9bd27c9fc7d13e45faed78d63b0387fe9ed80c5f3c296643ccf27d6f1bcd4918cf86f121bf83df324f3faba407aa5bf2c513efdee10fbbf5dfa9d3d0fe59149863fe4ed55adcf6be3fb2b13f080f66b9821b185781514c436f62f6473b062603fcf88f757d4e7fde82598fd8ef42cc02cfd585f4f894de8919e135b288f11f7c7aa22ceb34f1e892dac6f2cb83f51a5ffb4513f54cdc4f8b5e4fe43d53a26c69b4b1e2f55d9dff451beacff88fc58282f83e50ff5117a1b2c43a86f285fe9cf26a84f66b74bfd371e6f54d9ff1a2dc11d0de3ab25eafff3fe8a3e7848dfeb59f0f2d0eae93e9e1eff1cac77687d98e74793ed3d2fc13d11effb6071fe293904eb7dc4b382fb4f4dce5f4dc6e0ce10e74175e2de80f6a3e1fd1d118f46787f577cafc31e82d90813f35d73b0dec67af292d7674db60f5303f754c4a331ef2f69b2fe1678de407c7f6592111b98ffb42a703d22e7f5a50766cd19e309debe3459ff623c9fc5633c2fb4c13aadefc609f110f349156fcf9a1c3f2fcfc0cc9f623d02fa283d8bc68b1d707f80f48db9ffd2647d9c707fa1c9fa3631c12c5e61fd097acaf8ba8c88e9fb43d31d705fc3f71eb413f080ceb36513b062e13ced88fb434dd6cf1cf935fb341e3671bd39a4f5325707b3888cfda407605dc17ef93025d6a9fef178a0c9fa3d7e20a6ef3399481feb2d438f92f76f3559df43bccfee2b74fd0cacd0f75122a4cfd675ec4f1bf1faabcbf8575d12d3f7acac18dc57f13c9daeef8befa33c81d90883b78f058f77ba6c3fe305318d7f2c3cbfad6bd86f1b9c11d37ec711afcfba6c3f954b4cfd93498f98c6e70beedf7519bfc626317d9f2c6c82eb1e0bd77f1fac1b180f24f4bb6e637da4e0fe4497eda9cc89c5fcc1ad64ac178cc0aa46dff3e0fd535db6b7ca23a6fbcb2931cd6f8dbbc4f4fd9691424cfe6abc434cebb1a30362fafe546680d501fc4bf6406c623f8b97818d16f6fb2ec003f1bee29098e63f165d62f1fe2b627aff62444cdffb1ae7c41ae6a39703624adf127a0fea2f44f1fa82fa311874b03ee4d3fb0c9adf9df0f6a90fc5febd49494cdfd32a5ac4f43dadc93931ad6f8c517f86a2ff3eba27a6ef098da07fbda080f512d4bf217350b89ffb1b5d11cf1fa3be2a22fd930931f5f7973631f54fc7e277ea1f8ee877b15ee1227f8a6e637ce6a460a38df299404f55f4af2787c4d41fab04d3f78dc6284f557c2f6704bd55f1fd81d802b3fe1acec3a0bda8468be6a3f1bb26fa8be30b620de3c1d1102cf6772faf88e97b1023a457ab4f10f3e7d3f3060acadf477968068dffe29898f6fb8fd15e74513ee347627aff644a4cf34315daab5e7f2188bf1fefd3bb36fa071aea072b408cbf4dc1169e37467d31c4f779c663625abf58223f8638cf3eb68987e0d11d31cd972dfbe01eed1f19ed11b7e10f7494af31a4f93613e565a81d3c2f5a100f29ffa8ef66fd4529ee2f505ea6f81ecd08fecd14f327a38c98ce5b2e7bc45d7080fa6c6a5dcc2f8fa19725eacb648798f6232ca08f557fe183ff7e414cf393e39498e62747bb6071be7379494cebf7a38164ac07d0fdf50950ae0f5d3f3029de209e586c44cbdb93790a565bc8bf8d7860193d9c27f426c4033a3f86fa618bef318cd1be6d919ff18858417fb56a13d3fce012e567f706385f39ba958cfa71414cfbc934a4cf56e83c6754805503f575c4db8bd112df679a3860f17d8ee52eb83eb1c9afdf918cf2368855b0f5085628de7a365835d1ff1af1f665b4457d19b7c0f5173878f97ac4627de89498f6434d0a626a4fcb0c2cce1b8e1262da3fb8b48875c49bd18d647cdf12e96d3387cbaf779a6036a2c3fbf8f8c7e888f6345e8245fda872622a8fc90131bd7fd427a6f72d5362da9fb5348969ffcfb22b19fb690ec11a7d9fc6a5df0d3a6fb444fabba23e151558d497c9022ccee3562d620bcfaf0e88693f9f4dd7b3fce37c06fdae52ff2aa3fb35fafe558cfc770d03fda325f4e909ff3e3e018bf6560a16f945fde9f5e97b48d513711bf30fcb4432ea23ef2f1b7d515f26015896c73eb84ff32da36bc9e88faac4b4df39467dea1b74be69c1fd913110d7578fc43dcc8f8c2692f1be3d625abfad3262da7fac3f8087d41f9922fd0385fa43156fefc650d6f763b0288f52018be7951e31ed07ab7625637cdb2156c0ba0b5687a82f25f717f5f10db4a731f450447d5ca0fc14717f3924a6fdc6e53131cdb7947c3c67a8a27d2c4e882daccf140a31b5d7c2960c7f87f25645792cdac4f4fcc519318def0a71bd81f5e2f408ac74315e1d833599be01315d5f1e49467a5d6293be07a713d37e439fae1f5a680f31fc87a629f09763b44f5dd497894d4ccfab52627a5e29aea7f5f8a204b30e2dd677f17e5d35e17f52ba5eb3108fd30bb06161bdc54904633daa407d35c4f3aa84b88df5f2b24fdcc1fe761fed9d6508f1369f81590f18f313683f8641df3389511f0c93ce538cf17c53d48f02cf33c5f38b0762dadf5bc5c43dec3735104f4ca58bfe7b521293ff9d46c4627f19fc9fa9f750bf4ade9f606ad0f3463a31ed672d0ac938cf794f3cc0fe561bf5dd627ae33c820f66fd3ff8878898cee354605bdcbf6c130fc1655732be5f342756b0ffa5e4f5cd6c092eee8855da8ffa40ac81cb8e64c4139e5fb32dce2b2c7788e9f762464ce71d162d6283d8918cf52205ac28e8cf580158a3f149c5f5323be2fa71454ce7e3ca03c9883f5362aa9fc63158d1e8fbd0485f47efa23e2f787b36bbe2fa854d6c83ab3bc9f0876d70bd838df3be64f467b83f357b033a2f56e4e0219ddf2b6e05633fcfa222a6f349654b32f6bb1e811503fd196b0facb5909fb041ac613cb5e4fed0acf7d3a0fe6792117f1ce21eb8b8918cf524e4a76f0eb0be58429f0173ef882f3bc40370914ae6e9314cb0e87f4548ffc05411df4648df7040f5793901d73b4ef8f3ae25633c312656c0e5a364cc8fd0ef2c05c81ff451ea1d11fc798964f8872b628dbe7f644986bf7e20d6c14b94872ab8dc958c7810131b184f1597c426d8c6fb556d80f2720a307b03da2f1f4f989ad07f6112d3fd8b5232daff84d802973b92515f6f896db081f4696a9fe69351ff353600427be1fec4d4d9f518df18e07a4599f34232d65b9f88db600be9d7cd3ef5df2c62fa9ea72d7ea7ef572d911eb97fa1447e0c367cc7fc5248dc21be908cefddeac45d1a4fa0fdb0f121f2e75e122be01cf931588f0bf10dedc130e9fc7709ffc2fc3ffc6d392626ff5b0492d17fb489fbf47d0fd46793f510b1be41d79b438c07538b58857fae50ff2c513e05f4b1143a0fb52888e97c68752319fe7d444cdf4730e66095f6776af0cf96d1a6fd953c9e98b6d0ab3822a6fc14e2f721f6cb1467c40a78a149e6ed693127a6ef0198a8dfb6de43ffcd41f9da269df708e977ab85f35a0bee0fad96c85fb9245631df5b85c41ab8f02423be9c12ebe0c5a3648c8fe97e8dea434c6caae89f876d620dfde992b707ab2dd2b36810d3f3966362035c5692d1de5d62135c5a9231fe8ec12aed27b4f6c19a46ebf70a988df0907feeffac8eb8bf3821a6f3f58b9964f8e79298ce532f479279fbab90feaee0c201ab2de263c9bc7d2c9ac46de2a960f4b7cc21b85ec1e1f17b424cdfc34dfa6083c6ff156f0f564fdc5f2e883bc4267117df1731e97a5dc1f8304079f7ac36dacb82fb67abcfae47fb54897be0b2908cef79d0ef1a9d073607c47d625732e2eb21f180fe5ec883647c8fe14a32f687df83cd0ee261c9fd8d3550fbd8ff5dde110fc08b7bc9989f3f251e82cbb964ec1744fd1eb0fa8cfa63112bc411b1582fed4bc67af38858437b704cc9383f722e98f64ff4c07a1ff34d3abd8f75d0d1dfe1fe9775f6e9fcad658335da2f6c39c426f18164ec6f3d94ccf36718c4349f6776c0fa00eb7be6ae60c4abcc2456b01ee5d0f516edffb3e04f148dce331b578231ff638d8869ff8cb92318e30f0bed45d15ba80fc69160cc875a77c4f4f72eac7bc958cf5f48c6f72c0ac1c88fb3473cc0efb642ac60fe399812dbb45f847e678ae3fb12a88faadea1fe725f32e6677cc9182fdd4ac6fdbbc434def122c9484f4a4c7f2fc6ce24a3bc3bc47ddaefdc928cf937f1fe3ee6e7cd8660cce7a5c8bf6ad8f0bf3e3dcfea207e5a789e269fd726a6f6663f33be3fa149e6f98b4dc1183fd81de221e5af2b19e7996782311eb005d3fa9e3d978cf9f233c198bf303dc9f8de85489f8af945f39098e627747abed9c5fe0a1bf543d7e9efdd388664ac471c48c6fec65230d6579c7d620dd7bb9e64ec47542563fdf95630dd7f484c7fefc639958cf9f863620be9378f24637e10fe4337c4f94ffc6e88fb9d6362fafb38ce0931fdfd1ad7908cfe9b4b4ce7a15c5332f6cb3625a37fbb2b98be6fd626b6e9efd30c04239e184dc9d8af86fa6918e2ef55401f83d5479ede688f7888fe880b7f688ae7bbb6647c6f10fecf345ac4c792e1cfe782313fe89e10b7b1be64a682319f63fa92b1ffe994b883f52ef74c32be9f6210ab88c70efc9f69b5b0ffdeeb130f307e75517e96b8dfbd20ee82bd5032d64b1dc9789e4bdc23f624a3ff732b19dfd3f489fbc481647cbf23211e60fec683bfb58d01d60bbc7be221f6037b0f92717d4b32be8ff248ac103f4986ff6f0ac678dd502563feb92456e93c9f2218f323c65032fcd109b181f9550df5d1362d13f35b681f36eb1162fe8bd75fbb259eef5792b19f6d4732ce2f2c8935e29164a4772c19fbff27c43a380824e3fc444e6c108792f13d9b4c32d6d3457a4cfadef3bd608c5f7d1b6cf5d11e031e5fecb661627d208825a37f974ac6f7a612625a3f0dba9279f9270bc1d037e811db882f415f32d6f70660d6fd407fe04e307d6ff35632fcc38d64d4c76b621aef06bcbced0e7b3ef64b2a60f67cb02a19f333a792b1ffee8ab88df9f6f04c322f9f28958cfa744edc21be908cf1d5a5647c6f6841dca3f330c7c4b4fe92207d5db30b7f1e6592115f72e21ee6cba3a964cc7fce88fb58bf8de692b1dfa32d19e3e12be201e243d4938cf5cd7bc1188fe416f110eb33c6a160b417e38098becf66ec4b46fc417deb5a7d3a1f3e265631ff18a17ef44c5a2f8b9bc4345e89069271fe6e2819e5a3102b585f8b54c9384f7d2919eb7b7b82d1ff8f4f88553aef772119dfc310e9d1305f1ca3fef44d8dcaf34c30e2537c2e19fbf9a792f1f7bc42c1b41f2f22d6b15e94e492513f62c9980fcf88697f6506ffd367e33194f70d31ed37773bc426e61712b4e701bb1fe72752c9484f8fd8c47c41f2446c61fd22e94ac6799b9664ec4f6a13db988f4e3a92515fa682b1bfcec825c37f237f03ab85fd9a462a19e39f03e20eed3f2e884dcc4fe5bc7f620fad16e6fbd34a32f6b32f89db882fe9ae649cbf1d49c6fed31dc9587f191377882792e18f1bc45dcc57e7489fc298eb93f625e3fb494de21e381d08c67c71aa4ac6fef821319d3f4b15c9f81e4a453cc07c6a7e2619e7e1af8987988f3274c9f0b79a64f8db06311b01f1e7233faa35c4f82e2b2523ff0b6205fe3b3f25a6ef0be773c9980f5425a37c6c62da9fa63b60e63130de447cd02c9aefcaee2423bf0ab18ef999fc84d8c0fc892eaeb74cbcbf4f6ce33cbf2e9e6f9b98afb8910cff88fac086ab26f6f7a33c74cb843fc98f25f3fa350d88e9fb7ff98098be9fa8a3beebcc63f2f4f9f03f8665d3f756503f0d9bce17ebfbc4030be723f6888716ca775732e211f4b32c9bfc29eeb7ec3ee6a7749bd8b0b1ffbb4b6cda48df9498fd0be75c32c627c89f6d69187f3b21b18efeb575416ca07f1ced0846ffd0b926a6bf4f683bc4d4bf7063c1e84fd8b664cc9fdc48463cef10dba83f6e2219cf47fcb3ed16e28d732b19e3e74bc9189f5e48863f3c908cf4dd11d3fe04df928cfe645b32e63bbb92f1fee7fb71de20978cf1c25c32bedfe01277e07f9d5c32c67733c1f0bfce5432f4be978c78a64ac6782b25eec2ff853dc9d87fde928cf3768a648c8f1f24e3bcc35432d227f4a0f5afd023a6f516e751324f8fd3928cf5a58964fcbd9327c9f81ee2a564c4ffb9649c37ec4be6e9f34d62fa7b04b1482ffd3d81e04432caf34932e6b36792118f32c988bf73c9f8fee7bd648ce746c403cc57bbb9648cd70692f1fdee9964f4c77724a33e8af40f309feb5f4946fa0dc9f02fa23ed3fca6fb2019f5712619e39747c9e86f1d4ac679d7b664a47f2919f31d223ff4fd31573c4fa1ef670e2543cf9664b49f5832e6f7762523fd4f9231feba968cef2bcf25a3bee892513f9edf8ff1d131b18af956af920c7f2df452715e31f42563fc5b48869e8a64a4a724a6efa9047b92a1df4232eaab2619e76d03c106eaeb11b16e40af4832c6aff79231de0925a33eaa92d1de1bc48681f1d6be608c9ffda6643cff4632beb7752819e521fc9d69a2fe1e48869ec21f5b16cea3913fafffabf972f66d6fd9b7369bed5b9bcdf6adcd66fbd666b37d6bb3d9beb5d96cdfda6cb66f6d36dbb7369bed5b9bcdf6adcd66fbd666b37d6bb3d9beb5d96cdfda6cb66f6d36dbb7369bed5b9bcdf6adcd66fbd666b37d6bb3d9beb5d96cdfda6cb66f6d36dbb7369bed5b9bcdf6adcd66fbd666b37d6bb3d9beb5d96cdfda6cb66f6d36dbb7369bed5b9bcdf6adcd66fbd666b37d6bb3d9beb5d96cdfda6cb66f6d36dbb7369bed5b9bcdf6adcd66fbd666b37d6bb3d9beb5d96cdfda6cb66f6d36dbb7369bed5b9bcdf6adcd66fbd666b37d6bb3d9beb5d96cdfda6cb66f6d36dbb7369bed5b9bcdf6adcd66fbd666b37d6bb3d9beb5d96cdfda6cb66f6d36dbb7369bed5b9bcdf6adcd66fbd666b37d6bb3d9beb5d96cdfda6cb66f6d36dbb7369bed5b9bcdf6adcd66fbd666b37d6bb3c99a8d6f6d5e5b5363a637f56f6d9e4d6842667c6b53b79e754d9899ccacffc9da6cd084a9c2cc6efe8ffceef5564db82a4da7b6ff49dabcd044dfa40933b7e935bdff09da704db48f6bc2ccafed6b6bf3614d9c554db805cde06b6af3f7346986cda8b6afa5cda668fc8626ee664d98c5b57d0d6dde8bc69b3c8ad4245cd5845bd24cfe6c6d3e1a8d3fa709b3b4b63f539bcf46e3cf69c22d6be67f96363f1a8d4993571ee56d4d9a53d89fa2cdcf893c5b35c98426dc66cdf9efaecdbfa009538559d12c7e576dde8dc62f5bcf8f6892bfad09d9e277d3e6d744e3154dd2f735212b7f176d7e6d347e4393e9464d982adcaa7f5b9b7f261a7f4e1361ff9636ff6c345ed1e4cdd6c36df9ac4a73d41c37c7ffbc36ff4634fe9c26cd09ec9fd3e6df8cc6ef6a52ad6ac26da7b9fbebb5f91da2f1164d46af3569eec17e9d36bf28f2fc48345e7c4e1366fbb5fd7c6d7ec768fc394d9a07cc0e9b873f4f9b9f168d3f1b797e54939d4d9a303baaedef6bf32744e38d9aecbda549f398dbc9dfd1e68f8a3c3b1b5acfaa2650e5a4790afb116dfe284d367994d79a1c0b4d989dd5f6196d3e1d8d5f7a947f2d1a7f4e93e679f3a2b68f68f38f47e3f7bceccfd2e4e46d4d985d32bbdaa6cdd789c62fbdec764d84bda5cddf8c3cbf6134dea8c9f95b9a34af99dd346f57b5f903a3f1cb9af2239a5cbed44418b4f9a322cf0f446352e57c43eb79a109b3bbe67df3bed6e60fd1e427449e7735b9ad3511c6b5f9f2d1f8739a707b683e726dbe7c345ed1e4fa8526772f34796a3e0ae3dafc7ed1f8c723cf9bd1f8739a306b316b736dbe7c34deaac9c34b4d98756ae3da7cf968fc394d987599f5b8365f3e1a6fd4e4f12d4dc8fa5c9b2f1f8d45e4f99026bde680db906bf33b46e38f479e1fd1a4bd4d13615c9b9fe1657f4493837f2a1abff228eb9af4d735692a4db5a9369a5c9bdf33f26c8ec6ef7994ed9a745f683258d7a456851b3f23f4414d5eb69e3f281abff4b2af34515635a1ff34aecdaf8dc62f3dcaaf8dc65b23cfc73469e8dcf819a17f35f2fc23d178ab26cd979a08e3da7cf968fcd2cb6ed78499d9b01afcfcd46f138d5f469e9f168ddfd4a4b14913615c9b3f3d1abf6c3d3fa289bdaa4ac369b80d8fb4f9e2d178adf5bcab8930aecd978fc6af6aca564d98f9b5496dbe74345eb1d79ab82f356116d4c6b5f9f2d1f89526d60b4dbc554d98858da8c1cf4f7dfd68fc394dc8f8f9a9af1f8d377994b7356924ccd2063f23f4f5a3f1268ff2b626c2b836ff44e4f957a3f13b9ac4eb9a34b246de9836f819a12f178d37461e661fd04418d7e6cb47e37735c9563569cc98cd1bfc8cd0d78fc61b3cca064d84716dbe7c347e4393fc8526b3674d1a0b6efc8cd09788c61f8a3c2f3dcadb9a30ab982db9363f698df4378ac6ef78d917ade785266423aecdcf8ac6ef79947f2d1a7f4e13b23169f3c5a3f1e73411c6b5f9bd23cf4f88c66f6a52bdd0642255d961b6dbd85dd1e677d3e4274663a949f9114d98edd5c6b5f9b7a3f10f8c033f178ddf6c3d1b3569ec333b681cac68f385a3f1164d765e6b42c6cf08fdbd71e09f108d5f68f2b2a6bcd0a471d438ae4d6af3a5a3f196d6b3aac9213411c6b5f9f2d1f8739a303ba98d6bf3ef47e3e8d746e38d9a1cbda58930aecdef168d5f469ebf1d8dd73439784f93c669e38cd93969f3c5a3f1e73411c6b5f9f2d1f8739a342e1a97b591365f3c1a6fd0e4846b72f652136657ccaeb9365f3e1abf53535e68c2eca6b65a9b7f2c1aff78e4f9559a5cbcd08454e1c6cf4f7dfd68bc5593ab979a70bb6bdc716dfeac68fce35ef66253eb59d784db7de35e6af3a5a3f1e73461f6c08c9f11fa42d1f8e46768c2eda9f124b5f9d2d1788b26cfaa484dc85a5c9b2f1f8d5754f98026ccdab5496dbe74347eb3f56cd4848c9f2dfbfad1f85d4d5655e930ebd6c6b5f91991e7b78ec62b5ef6039a90f173771f8e3c7f6c347eaff5ac69d26df4990d1a03aecdc7a2f16723cf6f148d3fa1095705c6b5f9b5d1f8a547f917a2f13b9af4d635690c61a4cdef188d5f469ebf118d3fa709338599cab5f9f2d1f8a597ddae89d68491369b34f9f159d9df2a1a7f4e13327e26f18f8fc62f6bcae734515e6aa2d17fa4cd178fc61faa295213fe9faef1bfe9f6f5a3f13b9a34d6356166d4c6b5f9f2d1f8039a68cf9a703335936bf3e5a3f1e734e16669fc6fba7d22f2fca1d1789347795b13664e6d5c9baf168d37469e8f69c2cdd55caecdbf1d797e79347e57137b5513665e6d5c9b2f1f8d573431a426d6264db8f91a3faff90f44e3bb7f371a7f4e132de016726d5e7bd92f168d3fa8095409b9451a3faff92f47e31f8f3c1f8ec62fbdec764db41826b5f9d2d1786beb09a52aa4899670e36712bf7e34fea02650251546dafcfa68fcd2a3fcea689c69b936d566da5c2bb485566a95b6d4464c99cd9a8c9f55e1c6ffe6c96f3d0efc6c34ded176b53d6d5f3bd00eb523ed583bd14eb533ed5cbbd02eb52bed5abbd16eb5bbed9a08236dbe4634bed71eb447a6ca93d6d2da5ae79575b59ed6d706da50533475b326b5e94d1d735b5f201ab30b75ddd00e7453b774fb0d5da4e98eee6a03ddd37d3d785b1366616d5c9bb722cf1f158df5488f992e899eead99b7ae46b36d567fa9c5d5fe88b1555a426dc4a9dff6dae3f3d1aeb4b7dc4723ad627fa8ebebb667b6bb6bf6207faa17ec4ee3ad64f5e6ac2ed54e77fbbe24f8dc6fa997eae05ac2d79fa857ea95fe9d74c9bda6ed6ec76cdeea4ddeb0f7ac2d479d49fb826a5d08459ab36aecdaf8ac6c39f3f36163d14bdad77f4aeded3fbfa401fea8aae1acdd5ad79ccd6429aa1af1a5391996118a661690786cdb45951c570605c9b7f27f2fcd0d818bd36c3353cc3671618a11119b1ae326d122325cbd62c5fb3e98acdd85d73a360ea2c5053842a46591bd7e6f78bc61bc7c64c97ca581a23ae8c6f8c8d89b1532bc372b96becadd9fe8a1dacd921d911bfefd83861ea9c3e6bc2ecac36aecd6f158db78f038d73e38274815d421996c72be39ad9cd9addaed9dd9add1b0f74e7a3f1c4ea5f4b6862b4b9616fd26f148ddf1cf3d0d8d8e81abd355d6aeb0b6d581e07c6904c593175d5cca6b4c6f39da6a61d983aa9d2313aa6511bd7e6b789c69bc781a6695aa6fd4a19df745672e89a9ee9330bccd08cccd84ccc74c5323337a7e6cc9c732b9eefabbd8eb9d00766094d9855b5716d7e381a9bcb9f1779b68d8dcd91397ead0ba93359cda5b963ee9a7be63eb703f3d03c328fb99dd0bfed9ba7e69979be7a0fbfef82d59c4b68625e99d7b5716d7e301a9b37e6edafd5a41ef39877e6fd265db8360f6b797c349fa40afb66cb6c73eb985df96f3db36f0e5e69333415a68e5a6b62d19fab206d7e20f2588dda95b27cffad68fc7ab66d756c6ce996f1564b5a35cb14f9b32ccbb61ccb7dd6c6f22c9f5bb0a2d7be15b2aba297ea586cd46125a44b6a6556c6b5f9012f6be5d6d49a5973abf8f168bc591352666195db7521752a56ee03a6cbd21a59636bb2a2c28eb5cb6d6f4d9b7d76d5c83ab00ed7b4612308ebb8d6c43a8191365b34b14e5f7b14f3c63ab3cead0bebd2bafad168bc7d0ec5bab66e3ea20bd7e6d67ce4aadc59f7d683f5b8a2c293d5b2dacc3a966c53568f69d567d7ddb13bacb576c55a953520658696c2b5d91a792cd56eda0d9671dda676639bd6996dd9b6edd8aeedfd4834de3eaf64fb7660871f55c68eecd84e585efb766a674c85b69d43057b6acfecb95d309bdb0bbbe4ff56d94ba655cbead829d3e8c11e3d6b638fb5037b62299662efd4c6b5d91a8ded5d7bcfdeb70fec43fbc83eb64fec53fbcc3eb72fec4bfbcabeb66f7e74a67ad3cc927d6bdf7d5417a6ccbdfd603fda4f7666b7ecb6ddb1bb4c879eddb707f6d0566cf5d99ca6d36009d51d836935635792428ef4570e1b5b393674711c87aff9bed743713cc777022774222776122775322777a6cecc993b85b3f8d199eab766db9cd2a93eae8bb37446ced899383bceaeb3c7f25b38fbce0153e1d039728e999dbcb0fadf8e9c5376c581b32f15ea48bff3c0b439735c6ee70e3fe7fb5e0fc5b9702e9d2be7dab9716e9d3be7de79701e9d27a7e5b49d8ed3fd7834de3e03e9f49cfec75b125366e00c1dc551dda6dba827feb82ab52627aee1b26ea06bb9acc9bb8e349bfd0b0b62aecb553ab255d7e30a755d9fb461fec60d6a559c733774f9bcdffbe3404b7523377613e7ce4d9d969bb9b93b7567eedc2ddc855bbad5fbd178b32698537297eee8e3bab86377e2ee381377d7dd73f7dd03f7d0852a2e57c4718fdd13f7d43d73cf995d703b6774cafef584fd56ab640a856a7dd0aedc4ba6cd55ad0a8c6bf3ee38d0bd766fdc5bb7ae332df7de7d701fdd27b7e5b6993e1db7ebf6dcbe3bf8b42672b6cd1dbaca273c4ce4aa5ed351bc465d63ea292ecff04ccff2ea5a526b72e65e788ee77a9ee77bc18af95efd9fe3854ca75376a5c314aaeb10d3c7e3bd1d2f86365ec22cf5f8fad4477a2896ea655eee4dddcc9b7973aff0165ee955ded21b392d6fec4dbc1d6fd7db7b4f93d7b3b2debe77e01d7e5c19efc83b7677ec4777d73bf14ebd33efdcbbf02ebd2befdabb714fbd906972cb74b8f3eebd07efd17b5ab147afc5feed8e540a9986275e9beb736cabb5cf314cedc0ebd4aac0b8361f181b7b5dafe7f59d476fe00d3dc553fda6dfa8a7df58724d37655d4fdb77bc9eeffadec734c1acaceffbc1677caf1ffa9133f61ace8e1ffb89a7fba99ff9b93ff567fedc2ffc0557e5c17bf24bbff297fec81ffb136963c63bfe2e53a9c5940b98868ebfe7effb0775fbf299363eebfdf9c7b52afe09333e5ffc91b1b17fe69ffb17fea57fe55fb37fb8f16ffd3bffde7ff01ffd27bfe5b7fd8edff57b7edf1f6cf228a4899c95f587fe275a92e1fb6ad00c1af6a3a3061a6b4b67811e1881e95d055660074ee0b266e37b8f4c95a53f0e82200ca220669690a5ecff474116e44ca5915f31851e8269300be641c1daa11d2c58ab62fe262883ca3f85716ddeeda104cb60148c9d6930097682dd602fd80f0e82c3e02838761f8293e034380bce838be0f25d4d302bdbd2ece02ab8fe8cef0d6ebc66ed7d9d9ddafb06b7ee6170e7e7c17d5d638287e031780a5a413be8305532a64237e805fd60100c032550b90d19f5d9bf76994a5110b25ab413369932f3b0c1dad749a8e96aa86b072c50325542b336aecdbb3d94d00aedd061aedb73efbd65e8874118869157847198846998857938fd802634531dcec2f9677c6f58848bb07494b0aabd6fb80c47e13830c34960853be16eb81778e17e78101e8647e17178c2f23f08d4f0343c0bcfc38bf092ec82d15978159e3295fa4ca19865e19a2973c3da97ef39e12dd3e68e6973cf5479081f6be3dabc33360e9fc256d80e3b6137ec857d771a0ec261e87b15534609ce43356a7e4413cc54478d48fb4c4b8af4c8884c7727b29ee375ed7d837bef3ab28387c889dcc88bfc2888c2288ae228094fa334bc8cb2288fa6d12c9a4b9b31ce984ae7e115ab47fda8a86b4db4f04be6a182a88c2afd285ad69a442318d7e69db171348e26d18e73635f846eb41bed45fbd14174e8cdfcabe8283a7edbcbbed484eb72129d46679fd0e53cba882efd28baaae3b51f8b781d5dfbd370c79f4737d16db81fdd45f7d143f4183d45ada81d75a22e53651ef5a27e3488869112a9cc146683b8c9fe75ce15ba881bb116cc633d3658ebdaf55ab119b311436cd79ac4acfb1fb3ee1cd7666baf2df6629f39b5308ee2384ee234cee23c9ec633ff299ec74570f1be2698bb8f17f187661c84c555bc8cccdafbaec6eb7854c7eb781c4f0237de8977233fde8bf7e383f890e9d262fa3cc647f131d3448d4fe2d3f88cd939b7334617f165a4c4cdf82a9ac5d775ad896f58eb8a9877aee2db98b5a8f8bed6247e80716db6f6dae2c7f8296ec56ddf8d3b7137eec5fd78100f63c56fc56ad24c1a2b9a9cb0dbf5979ad4b3f78991989fd125b1123bba643d3c8ad7eefe7abc4e9ce031718356e2450f899f04bcc6c4ac4d85ac6df949c874384fa2244e9224254b922cc99329d3e824529359c294498af02c18260b76cb3829b583a44a96b526c9881bff9ee8b6b171384d26c9ceba974d7693bd64df6f2707c9617224ea49729c9cb05cadaef3f0158de42c394f2e3eae8b1d2597c955721d34c468693d5e87bb75bc4e6ec283782fb94dee92fbe481d97df4c87cf121f3c9fbecd747a6c653d24ada4987accda89bf4984251d24f064c9961a2242ad4499b69433b48356892ea30aecd07c73ccf5e36355233b55236c047db49ddd413794b7d5ae5e12b1a69907e62fcc8ee8ed2385c205ea7c9cb785d7bdf340d9ed22ccdd3693a4be7519c1651ccba2fb3749a782c8e3fb178fe902ed232add2653a4ac7e984d938dd4977d38a69d44af7d2fd609e1ea487cc23cda04eca7a7de9b1d084d90933f46f3eacc9b34749cfd2f3f422bd64ba5ca5d7abbdfef486d51bbeca93dea69f98896177dea70fe96372cdfabe4afa84d1521daf3d19afa31bc4ebe83e3c8a1e93fbb4953cc487c95dda8ef7d24ec27e6575ca617d412beda6bdb49f0ed261aa301ba66ad6cc1ae938d332bdf63499119f321fcdd4092f33d328322bb38526b08caf876fd564a397cdd8d02a6e1b65f62a8f59601a59984559fc1965b2244bc3451dafe17db3cc3b5b8dd759be1eafe17d5997d78f1eb269d0626de93171d8d861c67c52ee67d92c9b6745b6202bb32a5b66a36ccc9599b03617317594a897ed64bbda41b6f7ac09b3fdec203b78d6667d6cfc91c8931d6647c65976fcd29b6427f1223bfd942e67ce28b9aae375ed7db3738c96ea789d5dacc7eb6c5ac76b78dff890c5a7fdec92a9e5c53b2c6e4de2311b735eb1b1e7058b6967d955769ddd64b7d91dfbdf7df6903d725d9eb2166b5d2da6ce34bec8da598729d35dd54458adcd673591ebc6676655b79eecf54aec27cc8eb27ebccc06cff13a1bd6def775bc4e3b75bc4eee582b7a603586795fa6d33dbc6f7493294cc36974cd5ae098e9cac65c6e93e9ace6cdbc916bb52ab5e57a6e64cd749c9bcc3367b995dbda41eee4eeaa2ab99733f796f3bfb1f4794dd6d78da3e6e76ac9aae5611ed53546c4ebfc55bcae474b88d7b5f76535267e8ed7cc2367c153e4b0feb1ed5db3b67719dcb987acefbccfbcf8ae337177bc669ee4699e3155f27c9acff27956a64a5ea4bb49275fe42c72e755be94aa704d84716d5eed58929a38db3411ebc6e655fe8939bb674beff3713ec906abf13a5c067a3d5aca77d6e3753d5a7a8ed7c91de235bc6fb89bef325f3d6163ac31f3ddcbbac638aaa3048de43adfcb27f97e7e901fe647f9717e92dde5a7f959aaa693fc3cbf60ca5ce6572f3561769ddfe4fc1b763fac498575e37a2530bffd748db9cbeff387f5789d3fd6f1baf6be88d7f913e235ab21b37ab4f456bcaebd6fde6299bbf35316eff7f3d8a9c7eb63561387c955dece3b769477f35edecf07f9305772352ba7cd69c35c64d6547b4b13615c9bd7ade7c39a8835d2a9fe195da6c6d4cc27225e3fcf6e225e4fdf8cd7d15abc8e7785f70d2cd62734bd0be6a1ce98a73a7154fb316844663698da5367eaca777a537f1ae427d3701a4d63dd9b26d37445959b559b6653d6006b6ddef028a48954a55add61b0a249136ba4d3e974f60965e6d362ba98966fc5ebdafbaec6ebec32de7f8ed7b5f7cda6d3aa8ed7d18d3fafbdaf9f7ba69f321f95f87176ce7cd6988d4e59cf7aba9c8ea2b5129b8ea793e9ce7497e9b237dddfac09b383dab836eb9ab43769b2a6cacaba717a3bfdf08ceff428b1a7cbd7f19ae5ceacbd2fe275f018ee4fabf578cd14a278cd5473987ab688d74cd533a6ee89bbcb94de89cc69395d78c7d3e3c47ae3fd27d3d3e9d9cbd6c334c99e35999e73bbe0dabc6e3d9b35499f35c1bab1164d2f3fa8cbd5f49ad5725bc46b477d8ed7182dadc7ebb4bd1aaf59cb92f13adc0d7758cb9bacc76b47f1a3e43a7d64fe3d9dde4caf364480707abb4113a872c1ed6ecabf0bf956e4795f13ac1bb3f6b47577ccb3d9f7d387bc9d5c39c33a5ed7b39b75bc0e6e990765a325e64d5766379fe375ed7da38879628ad7cc3bb378edcf44bc66de9bc5eb3441bc0e17397bc7f431d95e5aa3e9d3364da62d6efc3b6d3fa289b069e74335a63bedb17033c9f7d6e3f5f3ec66befbd1785d8f96588467358645fbb5789d0df2c9d47127d3fed6b40ca6c3a9f24a938b674dc8f8f76fde8e3caf3419ae6a52afa53beeacf99131d3ac91c6ac3417225e3fcf6eaec66bd683a3784db39bedd578cd7a7f14af315aaabd2fe275ed7dbd66ed7de3e54c9be933635b5a82eb99c9dad2564d661637fe5d81579a64ef692256d367cebbbab8338fb5fee5a6785d7bdf4c798ed7dcfbca781defafc7ebdafbb291848cd7b5f715f17ac61adad47496db52a377596bdaa4495b6842c6d77c5f479ef734c15afa2c7a57997816b0825ed4def7edd548e17d45bc16b39b225e47776c84792be27576e1996c04fa46bc9e69693c4becad299aa5b36cc5a36cd4848cf76fb67b94754da00a6c36dd961267195dccb4d578fdd66a643dbbc9624fab8ed7627613f1ba9edd5c8dd7b5f77d1dafc3451daf5909cc7cf59d729acf8a4dad675d136e8bd9826bf3494deab5f45939dbba53260f67cb992f464bdb5623939bd5789dbe19af038ad7e108f13a7d5a8dd7f93df332ef8ce866e3d9e41d4d769e5581716d94375bcf2b4d6a5566a55849df363b3e75678188d7efad46aec7eb745e7b5fc4eb3415f1ba1e2db17b6fd7e3753d5a9a3ece76fd70bad5ff3265f666fb1fd76476c0ec70c6ff56cee734c15afaec688b32d775bcaebdefc756236bef2be275721befadc7eb7ab4c4ea1af3beafe335ab31c7796776f28e32a7b3b3373cca6b4dcea189b05a9bd75e769326b52a584b9f6d5c39982588d7db572383a7c85b8bd734bb29e2f5eaec26e2b5ff2a5ebb93e066f66e9f7c7635bbfe9c26b39bd9edec6ec6fbc56f6982dd39af358185e6f3bac24b65ea78fdd9d548cc6ec67b91ff3a5ed7b39b2cb6adc6eb451daf672c2cd9eff4c967f7b30716873eaec94dad89b05a9bcf68c2ed61f6387bdaa0cc627576f3e3ab91f5ece67abcae6737eb78ed697e5c8f96a2ab3a5ea78f88d779346bcddc779469cf3aeb91e7639accbab35e6dd00691e77d4db0bf207c9cbdd92bafd7dc7e7c35f2ad782d663759dba4789db7eb1a331bcc86db7b79866f9533e50d4d0e5e6872fb5a93990aabb559d7a4deb1b44913ec2f889d79f3755ae68df5d1d2e75723c33d11af576737c38af9f3f2395ecfb5c44e2eb7f7f2586af4b9f13af2bcf428d2d634999b73ab36a14dadc9ab9af24a13da61f0307f75fe22b1ec874df17ad36a24e2f5faece67abcae6737d7e375703377e6efb425a68cc71ef5694d982a96b460cee7d2df683d6b9ac85d170fc2e62fd671ed6816fc8cd5c8d5781d6898dd44bc664d329a1d4f8ffd70feeee8769e30d1b76bd2dda849ad4a36cf6babb5f9b826d86190eaf3e97c365ffdaf982fe6e5bc9a2fe7a3f9783e99efcc77e77bf3fdf9c1fc707e343f9e9fcc4fe767f3f3f9c5fc727ec5fe7731bf9edfcc6fe7772f5723c5ec26e2f5f368a98ed7acc6dcbfd7cb63ca3cb0decc764d5eb51ea909a9c28defdb7a6e3def69226cfe346fcddb1fb6cebcbbc17af3fecbd54811af6beffb1caf67ba330a8bf4dd79b4f9607ab039f27c48937c3e64a6ccf93998579ab8eb9a3cefbac05aba97ced54f28d32e9a1bb5e9168df578bd3ebb395d66691daf0b6de6cd1aefe962f885cee2d2dfd60456f0b3ac1fd544aca5176661b11cdb8553b88557f84550844554c44552a44556e4c5b49815f3a22816455954ecca6531daa8cdb898bcb51ab91aaf8b9da959ecbea78b7758ec7d5093e085268feb9a70db2ff66b6d3eaa095f4bdfcf0e8a0396dfc3e2a8382e4e8ad3e2ac382f2e983697c555715ddc14b75c9bbbe2be78281e8ba782b53ea6e5e69ad37e7b3552c4eba2933e14dd776b4cafe86f8ec69fd384dba0e0df92dfa0c9c94b4d841543965ba55017cde264d15868c5f9425f180b7361419b85bd7084360b77e1cddb0b7f116c566711be5e8d9c6988d76c8c7dfd5e2fcff017d122de168d3fa10957a5182c9245526bb3a289be4d13ac1b2f5296d76c91336d98a364dacc1705d366b128a1cda27aa1cd723162778cb7683359ecacae468a78ed8c16bb8bbdf77a794c99fdc5c1468ff2039a303b5c1c2d8e6a6dded0e4c54e94dc7b5e1d5d1cb39c9e2c4eeb7ac3b5d1b836c6466dce16e7ec8e8bc5e51675aed66737f368eecc5a8bebfc03fbbe1637fee0bd68fcd2cbbe6c3deb9a70bb5df0bfe9f67ac7d25b9a903277a8358bfbc5036f538f759b2a2e16467129dbd4d3a2b5a24d7bd15974d95dbd2ddaf41703ac468a783d3517c3fc033b0517caecece391e7b526a4cab3265c95855a364b7e3efc4d4ddedc7591df948d79bbd44abd344ab3704aab8e53a5fd1ca74a07beb874599c7a28ca5a9bd22b7d765750869bd52923785fc4ebe8828de8dfede5197e1997c94fd5e4b6d6848c9f11fa8826a44c56e6e5b49c95f39265bb2ccbaa5c96a3725c4eca9d72b7dcab6378b95f1e88185e1e9647e531d3e6a49c32754eb76873569ecf76eb785d5e4c6fb2fea615db35652ecbabcf459e154d0ed735414d8126ccae6b8336ef6922d68dcb1b96c35b566bee58ad39468b629d98fbda13970fe563f954b6ca76795076ca6ed92bfb4c9b41392c9552ad9af376d5d8accdbc5bf1781d5d54fafb6349c3af8ccafc559a541637be76b75913b9eb82af90564ee5565ee52fee0bb50aea5a53852b3dbfabd5085e45555c2555ca3cf1b2ca983679c5627935dda2cdac9a4f6ff24ef4819d3c55512daaf21391e7539a30e3ff496db66822d68dab25cbdd68715fb7a76acc7ccd69ed87eb5a534d588b7aac76d65b14bc4db55bed55fbd54175c8ee3eda5a738ec3c2ddf8bd9215654e2ad63aabb317aa6c89c69fd384ff775ef133d0ef6902ab2eaa4b566bae0ab55c144e75cddad3bcbaa97d4d75cbfd308fdfd55d755f3dacb528af7aac9eaa56c5c756d5e67167adcebbfd5fa64cafeaf36b07d5f0435ef62d4d565579a549a5546a6d429bf51d4b6fada52f99c75836160f759d61ca346a65d09eea9e4ded6b961a6a0d8b513c7e2f75dea2f64a75692ccda5b5b4d9139c6dda2cdff5344b6fe9cbab83cf469e5735e54d4d84d5da6cd46465dd78192ea3ca5bc68b6659b211e629297351f7f9eaf684e80d5fb3526b8eeb5ab34caa8365bacc96793dee5c6ef1392cbf5bf7c62de7cb62e5dac5b2fc159aac69237771bddc9df3bc46ba64e3e9e572392a4e96e3c25b684299dad314c97252dc94adeaae3c58ee705f237ac4dc0f2f8daac56acdee726fb9cf9e72b0559b62b9717fdcf26879fce2ea934f7ad9754dcedfd6644d9b2d9a603d7079ba3c63b93ae7bacc596c3aaffd4cdd9a6a656a4fc362f74a7baa2394f035b2d65c2c2f9757bce65c6f5567c3b75d96b7d5ecd5b577cbfb9fafc99a361b3511ab81cb07a6cc23745914758d593e3165cc65abdc439d29f73133b16ca33dd5116ad9215fd3a55ad35bf69703f6a4e1566d94ea8d35dca5faf6d5a3e66722cfa6d6b3459bb7355959231db191c2482b476ce4142cf4bac6549322ae3d30eb09af285376473ac56ed69e789f8f45a8653a32ea5a333247d688f9e3913372b7a9337ab1663af2475b663846e1cfd6645d9b4d9a60ed6b14b1fcc44550de1717b52ee54e112f5b755baa7b34ac13d6e67d1aa10c8f4f2bedc91c25cbdd513aca96fd513e9ad6917cf4aa75ace5763e2a5694592c6f47f32d5797a3eabd68fc63566bf37ac7d2fa7ae088f5f946a3e513f3bcb7757da97dcc685cd798a556dcd61e18ad4928533cd5ca8c26d53e62f76867b937da1ded8df6470798411e1d8eb6f60247c7529993d1e9e86c74bef5ea8b9fabc99a361b34c11ae9e8b2588eaeca9d4559abb2b0a04bf95464c56ddd96160e1b3db95554f6ead62494a9f646d7757c1add8c8cd12d6b4f77a3fbd5f9f5d1c3b6dc32af33470f78f4c894391b3d8d5a5bb4698f3a3f5393356d765e68b2b61e38eab29cf4ca875a15e65fea7674c35bd2fee249b4a5a2a862f2c0676bcaf46b4f331a8c8623657ded61b4c1b70a1b370d7fdc98cfb832ccc6dad6abf59fa9c99a365bd68dc7c6d81c5baca65cf3bac254295b757d812e65a79857116b4b2c6abf5606917b6c8f9db75666c65bfd31cbaf37f685325c9d2dfe781c8ea35fa6cd86dd39b3bb71cc7291d49ab0110157a5ba2ba6d57dad4be9d635a67828fb4559f76716ed3a36b1d1d33e9419a7e36c9c6f5ab51a4fdfd12658558669b3d57f8fe7bf489b8debc6e362bc189745cefa755c93f2605c1533d6f7bd2be6cb76dd926aef2b6a0c1b71674b63bc1c8fc6e3f164bcf3de9ade78f71d75f6d6d5591d2fbcb4623cdeff45da6c58371eb3b8323eac1561e3a4196f434c15565beeeb7654f6c747e3e3f1c9f8747c363e1f5f8c2fc7579f59ef1c5fbfa3cdcdc85bab39b7cbbb2d57dffd126d36ac918eefc70fe347aec71d1b0b304daaa82846faf869dc1ab7c79d71f7334abca9ce96f9759edffe8b7635d87af5f09768f3e61ae998c596b1cac647f7ccab1465af8ac74793e6b8f3771579b6c9d659d27a2e70a2ad6a33d15e8fa99e6da24f8c9faecd9b6ba45539312716f327ccca7e954cec89531cfe3c5d6adbb6524ef9755ff89cadd16de2fd746dde5c239df8ac5c83f2b03cacd22a9d8493e8e7ea423527deaecdf26e7cbba68e37bed9a24d32497fae366faf1b4fb249ce468ccc4a6532fd15bad4b67da59cab53acd79cf996b1155367f693b579638d743267655a2c3bd5ee6431297f95325c9d2d2be5b0d1f19acfa95ece70ad69b39c8c7eaa366fac1b4fc69309ebc53d4e76eab5935faacdc5f671e7eb5ee0646bbf68b2f793b579b1463ad967116a343998fc64dffbb64dded186a9a3bd50675bcd39999cfe446d5ead7d4dce26e7938b7a6fc83fa2cde5b69572deaa5a6c24beaacdd5d69a73fdf3b479b9ca33b999dcbe3d3efc65ea6ce9ef52cd69bea839f75bd579f869dabc580f9c3cfe93ba706d9eded366349fb4d6b4696fed05763e3a5ffeae366bebc693eea4f74f6bc3d4e9bfa7ce64f0a2e66c9d919ffc8459ae5a9bf5b5afc9a7f6c7fe34db32af075bdeedacb5ab9dc6ce96f9ae1d7de76f8f1ffef3fffcaffff8bf491de25c + + + + + klistview.h + + diff --git a/kopete/kopete/chatwindow/Makefile.am b/kopete/kopete/chatwindow/Makefile.am new file mode 100644 index 00000000..bcec2bed --- /dev/null +++ b/kopete/kopete/chatwindow/Makefile.am @@ -0,0 +1,33 @@ +kde_module_LTLIBRARIES = libkrichtexteditpart.la kopete_chatwindow.la kopete_emailwindow.la +noinst_LTLIBRARIES = libkopetechatwindow.la +METASOURCES = AUTO + +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(KOPETE_COMPAT_INCLUDES) -I$(top_srcdir)/kopete/libkopete/private -I$(top_srcdir)/kopete/kopete $(all_includes) + +kopete_chatwindow_la_SOURCES = chatview.cpp kopetechatwindow.cpp chatmemberslistwidget.cpp +kopete_chatwindow_la_LDFLAGS = -no-undefined -module $(KDE_PLUGIN) $(all_libraries) +kopete_chatwindow_la_LIBADD = ../../libkopete/libkopete.la ./libkopetechatwindow.la $(LIB_KOPETECOMPAT) $(LIB_KDEUI) + +kopete_emailwindow_la_SOURCES = kopeteemailwindow.cpp +kopete_emailwindow_la_LDFLAGS = -no-undefined -module $(KDE_PLUGIN) $(all_libraries) +kopete_emailwindow_la_LIBADD = ../../libkopete/libkopete.la ./libkopetechatwindow.la $(LIB_KOPETECOMPAT) $(LIB_KDEUI) + +libkrichtexteditpart_la_SOURCES = krichtexteditpart.cpp +libkrichtexteditpart_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) $(KDE_RPATH) +libkrichtexteditpart_la_LIBADD = ../../libkopete/libkopete.la $(LIB_KPARTS) $(LIB_KSPELL) $(LIB_KDEPRINT) + +libkopetechatwindow_la_SOURCES = chatmessagepart.cpp emoticonselector.cpp kopeteemoticonaction.cpp \ + chattexteditpart.cpp krichtexteditpart.cpp kopetechatwindowstylemanager.cpp \ + kopetechatwindowstyle.cpp +libkopetechatwindow_la_LDFLAGS = $(all_libraries) -no-undefined +libkopetechatwindow_la_LIBADD = ../../libkopete/libkopete.la $(LIB_KOPETECOMPAT) $(LIB_KDEUI) + +partdir = $(kde_datadir)/kopeterichtexteditpart +part_DATA = kopeterichtexteditpartfull.rc + +rcdir = $(kde_datadir)/kopete +rc_DATA = kopetechatwindow.rc kopeteemailwindow.rc + +service_DATA = chatwindow.desktop emailwindow.desktop +servicedir = $(kde_servicesdir) + diff --git a/kopete/kopete/chatwindow/chatmemberslistwidget.cpp b/kopete/kopete/chatwindow/chatmemberslistwidget.cpp new file mode 100644 index 00000000..16e37991 --- /dev/null +++ b/kopete/kopete/chatwindow/chatmemberslistwidget.cpp @@ -0,0 +1,263 @@ +/* + chatmemberslistwidget.cpp - Chat Members List Widget + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "chatmemberslistwidget.h" + +#include "kopetechatsession.h" +#include "kopetecontact.h" +#include "kopeteonlinestatus.h" +#include "kopeteglobal.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopetemetacontact.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +//BEGIN ChatMembersListWidget::ToolTip + +class ChatMembersListWidget::ToolTip : public QToolTip +{ +public: + ToolTip( KListView *parent ) + : QToolTip( parent->viewport() ), m_listView ( parent ) + { + } + + virtual ~ToolTip() + { + remove( m_listView->viewport() ); + } + + void maybeTip( const QPoint &pos ) + { + if( QListViewItem *item = m_listView->itemAt( pos ) ) + { + QRect itemRect = m_listView->itemRect( item ); + if( itemRect.contains( pos ) ) + tip( itemRect, static_cast( item )->contact()->toolTip() ); + } + } + +private: + KListView *m_listView; +}; + +//END ChatMembersListWidget::ToolTip + + +//BEGIN ChatMembersListWidget::ContactItem + +ChatMembersListWidget::ContactItem::ContactItem( ChatMembersListWidget *parent, Kopete::Contact *contact ) + : KListViewItem( parent ), m_contact( contact ) +{ + QString nick = m_contact->property(Kopete::Global::Properties::self()->nickName().key()).value().toString(); + if ( nick.isEmpty() ) + nick = m_contact->contactId(); + setText( 0, nick ); + setDragEnabled(true); + + connect( m_contact, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), + this, SLOT( slotPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ) ) ; + + setStatus( parent->session()->contactOnlineStatus(m_contact) ); + reposition(); +} + +void ChatMembersListWidget::ContactItem::slotPropertyChanged( Kopete::Contact*, + const QString &key, const QVariant&, const QVariant &newValue ) +{ + if ( key == Kopete::Global::Properties::self()->nickName().key() ) + { + setText( 0, newValue.toString() ); + reposition(); + } +} + +void ChatMembersListWidget::ContactItem::setStatus( const Kopete::OnlineStatus &status ) +{ + setPixmap( 0, status.iconFor( m_contact ) ); + reposition(); +} + +void ChatMembersListWidget::ContactItem::reposition() +{ + // Qt's listview sorting is pathetic - it's impossible to reposition a single item + // when its key changes, without re-sorting the whole list. Plus, the whole list gets + // re-sorted whenever an item is added/removed. So, we do manual sorting. + // In particular, this makes adding N items O(N^2) not O(N^2 log N). + Kopete::ChatSession *session = static_cast( listView() )->session(); + int ourWeight = session->contactOnlineStatus(m_contact).weight(); + QListViewItem *after = 0; + + for ( QListViewItem *it = KListViewItem::listView()->firstChild(); it; it = it->nextSibling() ) + { + ChatMembersListWidget::ContactItem *item = static_cast(it); + int theirWeight = session->contactOnlineStatus(item->m_contact).weight(); + + if( theirWeight < ourWeight || + (theirWeight == ourWeight && item->text(0).localeAwareCompare( text(0) ) > 0 ) ) + { + break; + } + + after = it; + } + + moveItem( after ); +} + +//END ChatMembersListWidget::ContactItem + + +//BEGIN ChatMembersListWidget + +ChatMembersListWidget::ChatMembersListWidget( Kopete::ChatSession *session, QWidget *parent, const char *name ) + : KListView( parent, name ), m_session( session ) +{ + // use our own custom tooltips + setShowToolTips( false ); + m_toolTip = new ToolTip( this ); + + // set up display: no header + setAllColumnsShowFocus( true ); + addColumn( QString::null, -1 ); + header()->setStretchEnabled( true, 0 ); + header()->hide(); + + // list is sorted by us, not by Qt + setSorting( -1 ); + + // add chat members + slotContactAdded( session->myself() ); + for ( QPtrListIterator it( session->members() ); it.current(); ++it ) + slotContactAdded( *it ); + + connect( this, SIGNAL( contextMenu( KListView*, QListViewItem *, const QPoint &) ), + SLOT( slotContextMenu(KListView*, QListViewItem *, const QPoint & ) ) ); + connect( this, SIGNAL( executed( QListViewItem* ) ), + SLOT( slotExecute( QListViewItem * ) ) ); + + connect( session, SIGNAL( contactAdded(const Kopete::Contact*, bool) ), + this, SLOT( slotContactAdded(const Kopete::Contact*) ) ); + connect( session, SIGNAL( contactRemoved(const Kopete::Contact*, const QString&, Kopete::Message::MessageFormat, bool) ), + this, SLOT( slotContactRemoved(const Kopete::Contact*) ) ); + connect( session, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus & , const Kopete::OnlineStatus &) ), + this, SLOT( slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus & ) ) ); +} + +ChatMembersListWidget::~ChatMembersListWidget() +{ +} + +void ChatMembersListWidget::slotContextMenu( KListView*, QListViewItem *item, const QPoint &point ) +{ + if ( ContactItem *contactItem = dynamic_cast(item) ) + { + KPopupMenu *p = contactItem->contact()->popupMenu( session() ); + connect( p, SIGNAL( aboutToHide() ), p, SLOT( deleteLater() ) ); + p->popup( point ); + } +} + +void ChatMembersListWidget::slotContactAdded( const Kopete::Contact *contact ) +{ + if ( !m_members.contains( contact ) ) + m_members.insert( contact, new ContactItem( this, const_cast( contact ) ) ); +} + +void ChatMembersListWidget::slotContactRemoved( const Kopete::Contact *contact ) +{ + kdDebug(14000) << k_funcinfo << endl; + if ( m_members.contains( contact ) && contact != session()->myself() ) + { + delete m_members[ contact ]; + m_members.remove( contact ); + } +} + +void ChatMembersListWidget::slotContactStatusChanged( Kopete::Contact *contact, const Kopete::OnlineStatus &status ) +{ + if ( m_members.contains( contact ) ) + m_members[contact]->setStatus( status ); +} + +void ChatMembersListWidget::slotExecute( QListViewItem *item ) +{ + if ( ContactItem *contactItem = dynamic_cast(item ) ) + { + Kopete::Contact *contact=contactItem->contact(); + + if(!contact || contact == contact->account()->myself()) + return; + + contact->execute(); + } +} + +QDragObject *ChatMembersListWidget::dragObject() +{ + QListViewItem *currentLVI = currentItem(); + if( !currentLVI ) + return 0L; + + ContactItem *lvi = dynamic_cast( currentLVI ); + if( !lvi ) + return 0L; + + Kopete::Contact *c = lvi->contact(); + KMultipleDrag *drag = new KMultipleDrag( this ); + drag->addDragObject( new QStoredDrag("application/x-qlistviewitem", 0L ) ); + + QStoredDrag *d = new QStoredDrag("kopete/x-contact", 0L ); + d->setEncodedData( QString( c->protocol()->pluginId()+QChar( 0xE000 )+c->account()->accountId()+QChar( 0xE000 )+ c->contactId() ).utf8() ); + drag->addDragObject( d ); + + KABC::Addressee address = KABC::StdAddressBook::self()->findByUid(c->metaContact()->metaContactId()); + + if( !address.isEmpty() ) + { + drag->addDragObject( new QTextDrag( address.fullEmail(), 0L ) ); + KABC::VCardConverter converter; + QString vcard = converter.createVCard( address ); + if( !vcard.isNull() ) + { + QStoredDrag *vcardDrag = new QStoredDrag("text/x-vcard", 0L ); + vcardDrag->setEncodedData( vcard.utf8() ); + drag->addDragObject( vcardDrag ); + } + } + + drag->setPixmap( c->onlineStatus().iconFor(c, 12) ); + + return drag; +} + + +//END ChatMembersListWidget + +#include "chatmemberslistwidget.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/chatmemberslistwidget.h b/kopete/kopete/chatwindow/chatmemberslistwidget.h new file mode 100644 index 00000000..71084554 --- /dev/null +++ b/kopete/kopete/chatwindow/chatmemberslistwidget.h @@ -0,0 +1,121 @@ +/* + chatmemberslistwidget.h - Chat Members List Widget + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef CHATMEMBERSLISTWIDGET_H +#define CHATMEMBERSLISTWIDGET_H + +#include + +#include + +namespace Kopete +{ +class ChatSession; +class Contact; +class OnlineStatus; +} + +/** + * @author Richard Smith + */ +class ChatMembersListWidget : public KListView +{ + Q_OBJECT +public: + ChatMembersListWidget( Kopete::ChatSession *session, QWidget *parent, const char *name = 0 ); + virtual ~ChatMembersListWidget(); + + Kopete::ChatSession *session() { return m_session; } + + class ToolTip; + class ContactItem; + +protected: + + /** + * Start a drag operation + * @return a KMultipleDrag containing: + * 1) A QStoredDrag of type "application/x-qlistviewitem", + * 2) If the contact is associated with a KABC entry, + * i) a QTextDrag containing their email address, and + * ii) their vCard representation. + */ + virtual QDragObject *dragObject(); + +private slots: + /** + * Show the context menu for @p item at @p point + */ + void slotContextMenu( KListView*, QListViewItem *item, const QPoint &point ); + + /** + * Called when a contact is added to the chat session. + * Adds this contact to the contact list view. + * @param c The contact that joined the chat + */ + void slotContactAdded( const Kopete::Contact *c ); + + /** + * Called when a contact is removed from the chat session. + * Removes this contact from the contact list view. + * @param c The contact that left the chat + */ + void slotContactRemoved( const Kopete::Contact *c ); + + /** + * Called when a contact changes status. + * @param contact The contact who changed status + * @param status The new status of the contact + */ + void slotContactStatusChanged( Kopete::Contact *contact, const Kopete::OnlineStatus &status ); + + /** + * Called when a contact is clicked. + * @param item The list view item representing the clicked contact + */ + void slotExecute( QListViewItem *contact ); + +private: + Kopete::ChatSession *m_session; + QMap m_members; + ToolTip *m_toolTip; +}; + +class ChatMembersListWidget::ContactItem : public QObject, public KListViewItem +{ + Q_OBJECT +public: + ContactItem( ChatMembersListWidget *list, Kopete::Contact *contact ); + Kopete::Contact *contact() const { return m_contact; } + +private slots: + void slotPropertyChanged( Kopete::Contact *contact, const QString &key, const QVariant &oldValue, const QVariant &newValue ); + +private: + friend class ChatMembersListWidget; + + void reposition(); + void setStatus( const Kopete::OnlineStatus &status ); + + Kopete::Contact *m_contact; +}; + + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/chatmessagepart.cpp b/kopete/kopete/chatwindow/chatmessagepart.cpp new file mode 100644 index 00000000..36523dac --- /dev/null +++ b/kopete/kopete/chatwindow/chatmessagepart.cpp @@ -0,0 +1,1328 @@ +/* + chatmessagepart.cpp - Chat Message KPart + + Copyright (c) 2002-2005 by Olivier Goffart + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2004 by Richard Smith + Copyright (c) 2005-2006 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "chatmessagepart.h" + +// STYLE_TIMETEST is for time staticstic gathering. +//#define STYLE_TIMETEST + +#include + +// Qt includes +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KHTML::DOM includes +#include +#include +#include +#include +#include +#include + + +// KDE includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Kopete includes +#include "chatmemberslistwidget.h" +#include "kopetecontact.h" +#include "kopetecontactlist.h" +#include "kopetechatwindow.h" +#include "kopetechatsession.h" +#include "kopetemetacontact.h" +#include "kopetepluginmanager.h" +#include "kopeteprefs.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopeteglobal.h" +#include "kopeteemoticons.h" +#include "kopeteview.h" +#include "kopetepicture.h" + +#include "kopetechatwindowstyle.h" +#include "kopetechatwindowstylemanager.h" + +#if !(KDE_IS_VERSION(3,3,90)) +//From kdelibs/khtml/misc/htmltags.h +// used in ChatMessagePart::copy() +#define ID_BLOCKQUOTE 12 +#define ID_BR 14 +#define ID_DD 22 +#define ID_DIV 26 +#define ID_DL 27 +#define ID_DT 28 +#define ID_H1 36 +#define ID_H2 37 +#define ID_H3 38 +#define ID_H4 39 +#define ID_H5 40 +#define ID_H6 41 +#define ID_HR 43 +#define ID_IMG 48 +#define ID_LI 57 +#define ID_OL 69 +#define ID_P 72 +#define ID_PRE 75 +#define ID_TD 90 +#define ID_TH 93 +#define ID_TR 96 +#define ID_TT 97 +#define ID_UL 99 +#endif + +class ToolTip; + +class ChatMessagePart::Private +{ +public: + Private() + : tt(0L), manager(0L), scrollPressed(false), + copyAction(0L), saveAction(0L), printAction(0L), + closeAction(0L),copyURLAction(0L), currentChatStyle(0L), latestContact(0L), + latestDirection(Kopete::Message::Inbound), latestType(Kopete::Message::TypeNormal) + {} + + ~Private() + { + // Don't delete manager and latestContact, because they could be still used. + // Don't delete currentChatStyle, it is handled by ChatWindowStyleManager. + } + + bool bgOverride; + bool fgOverride; + bool rtfOverride; + + ToolTip *tt; + + Kopete::ChatSession *manager; + bool scrollPressed; + + DOM::HTMLElement activeElement; + + KAction *copyAction; + KAction *saveAction; + KAction *printAction; + KAction *closeAction; + KAction *copyURLAction; + + ChatWindowStyle *currentChatStyle; + Kopete::Contact *latestContact; + Kopete::Message::MessageDirection latestDirection; + Kopete::Message::MessageType latestType; + // Yep I know it will take memory, but I don't have choice + // to enable on-the-fly style changing. + QValueList allMessages; +}; + +class ChatMessagePart::ToolTip : public QToolTip +{ +public: + ToolTip( ChatMessagePart *c ) : QToolTip( c->view()->viewport() ) + { + m_chat = c; + } + + void maybeTip( const QPoint &/*p*/ ) + { + // FIXME: it's wrong to look for the node under the mouse - this makes too many + // assumptions about how tooltips work. but there is no nodeAtPoint. + DOM::Node node = m_chat->nodeUnderMouse(); + Kopete::Contact *contact = m_chat->contactFromNode( node ); + QString toolTipText; + + if(node.isNull()) + return; + + // this tooltip is attached to the viewport widget, so translate the node's rect + // into its coordinates. + QRect rect = node.getRect(); + rect = QRect( m_chat->view()->contentsToViewport( rect.topLeft() ), + m_chat->view()->contentsToViewport( rect.bottomRight() ) ); + + if( contact ) + { + toolTipText = contact->toolTip(); + } + else + { + m_chat->emitTooltipEvent( m_chat->textUnderMouse(), toolTipText ); + + if( toolTipText.isEmpty() ) + { + //Fall back to the title attribute + for( DOM::HTMLElement element = node; !element.isNull(); element = element.parentNode() ) + { + if( element.hasAttribute( "title" ) ) + { + toolTipText = element.getAttribute( "title" ).string(); + break; + } + } + } + } + + if( !toolTipText.isEmpty() ) + tip( rect, toolTipText ); + } + +private: + ChatMessagePart *m_chat; +}; + +ChatMessagePart::ChatMessagePart( Kopete::ChatSession *mgr, QWidget *parent, const char *name) + : KHTMLPart( parent, name ), d( new Private ) +{ + d->manager = mgr; + + KopetePrefs *kopetePrefs = KopetePrefs::prefs(); + d->currentChatStyle = ChatWindowStyleManager::self()->getStyleFromPool( kopetePrefs->stylePath() ); + + //Security settings, we don't need this stuff + setJScriptEnabled( false ) ; + setJavaEnabled( false ); + setPluginsEnabled( false ); + setMetaRefreshEnabled( false ); + setOnlyLocalReferences( true ); + + // Write the template to KHTMLPart + writeTemplate(); + + view()->setFocusPolicy( QWidget::NoFocus ); + + d->tt=new ToolTip( this ); + + // It is not possible to drag and drop on our widget + view()->setAcceptDrops(false); + + connect( KopetePrefs::prefs(), SIGNAL(messageAppearanceChanged()), + this, SLOT( slotAppearanceChanged() ) ); + connect( KopetePrefs::prefs(), SIGNAL(windowAppearanceChanged()), + this, SLOT( slotRefreshView() ) ); + connect( KopetePrefs::prefs(), SIGNAL(styleChanged(const QString &)), + this, SLOT( setStyle(const QString &) ) ); + connect( KopetePrefs::prefs(), SIGNAL(styleVariantChanged(const QString &)), + this, SLOT( setStyleVariant(const QString &) ) ); + // Refresh the style if the display name change. + connect( d->manager, SIGNAL(displayNameChanged()), this, SLOT(slotUpdateHeaderDisplayName()) ); + connect( d->manager, SIGNAL(photoChanged()), this, SLOT(slotUpdateHeaderPhoto()) ); + + connect ( browserExtension(), SIGNAL( openURLRequestDelayed( const KURL &, const KParts::URLArgs & ) ), + this, SLOT( slotOpenURLRequest( const KURL &, const KParts::URLArgs & ) ) ); + + connect( this, SIGNAL(popupMenu(const QString &, const QPoint &)), + this, SLOT(slotRightClick(const QString &, const QPoint &)) ); + connect( view(), SIGNAL(contentsMoving(int,int)), + this, SLOT(slotScrollingTo(int,int)) ); + + //initActions + d->copyAction = KStdAction::copy( this, SLOT(copy()), actionCollection() ); + d->saveAction = KStdAction::saveAs( this, SLOT(save()), actionCollection() ); + d->printAction = KStdAction::print( this, SLOT(print()),actionCollection() ); + d->closeAction = KStdAction::close( this, SLOT(slotCloseView()),actionCollection() ); + d->copyURLAction = new KAction( i18n( "Copy Link Address" ), QString::fromLatin1( "editcopy" ), 0, this, SLOT( slotCopyURL() ), actionCollection() ); + + // read formatting override flags + readOverrides(); +} + +ChatMessagePart::~ChatMessagePart() +{ + kdDebug(14000) << k_funcinfo << endl; + delete d->tt; + delete d; +} + +void ChatMessagePart::slotScrollingTo( int /*x*/, int y ) +{ + int scrolledTo = y + view()->visibleHeight(); + if ( scrolledTo >= ( view()->contentsHeight() - 10 ) ) + d->scrollPressed = false; + else + d->scrollPressed = true; +} + +void ChatMessagePart::save() +{ + KFileDialog dlg( QString::null, QString::fromLatin1( "text/html text/plain" ), view(), "fileSaveDialog", false ); + dlg.setCaption( i18n( "Save Conversation" ) ); + dlg.setOperationMode( KFileDialog::Saving ); + + if ( dlg.exec() != QDialog::Accepted ) + return; + + KURL saveURL = dlg.selectedURL(); + KTempFile tempFile; + tempFile.setAutoDelete( true ); + QFile* file = tempFile.file(); + + QTextStream stream ( file ); + stream.setEncoding(QTextStream::UnicodeUTF8); + + if ( dlg.currentFilter() == QString::fromLatin1( "text/plain" ) ) + { + QValueList::ConstIterator it, itEnd = d->allMessages.constEnd(); + for(it = d->allMessages.constBegin(); it != itEnd; ++it) + { + Kopete::Message tempMessage = *it; + stream << "[" << KGlobal::locale()->formatDateTime(tempMessage.timestamp()) << "] "; + if( tempMessage.from() && tempMessage.from()->metaContact() ) + { + stream << formatName(tempMessage.from()->metaContact()->displayName()); + } + stream << ": " << tempMessage.plainBody() << "\n"; + } + } + else + { + stream << htmlDocument().toHTML() << '\n'; + } + + tempFile.close(); + + if ( !KIO::NetAccess::move( KURL( tempFile.name() ), saveURL ) ) + { + KMessageBox::queuedMessageBox( view(), KMessageBox::Error, + i18n("Could not open %1 for writing.").arg( saveURL.prettyURL() ), // Message + i18n("Error While Saving") ); //Caption + } +} + +void ChatMessagePart::pageUp() +{ + view()->scrollBy( 0, -view()->visibleHeight() ); +} + +void ChatMessagePart::pageDown() +{ + view()->scrollBy( 0, view()->visibleHeight() ); +} + +void ChatMessagePart::slotOpenURLRequest(const KURL &url, const KParts::URLArgs &/*args*/) +{ + kdDebug(14000) << k_funcinfo << "url=" << url.url() << endl; + if ( url.protocol() == QString::fromLatin1("kopetemessage") ) + { + Kopete::Contact *contact = d->manager->account()->contacts()[ url.host() ]; + if ( contact ) + contact->execute(); + } + else + { + KRun *runner = new KRun( url, 0, false ); // false = non-local files + runner->setRunExecutables( false ); //security + //KRun autodeletes itself by default when finished. + } +} + +void ChatMessagePart::readOverrides() +{ + d->bgOverride = KopetePrefs::prefs()->bgOverride(); + d->fgOverride = KopetePrefs::prefs()->fgOverride(); + d->rtfOverride = KopetePrefs::prefs()->rtfOverride(); +} + +void ChatMessagePart::setStyle( const QString &stylePath ) +{ + // Create a new ChatWindowStyle + d->currentChatStyle = ChatWindowStyleManager::self()->getStyleFromPool(stylePath); + + // Do the actual style switch + // Wait for the event loop before switching the style + QTimer::singleShot( 0, this, SLOT(changeStyle()) ); +} + +void ChatMessagePart::setStyle( ChatWindowStyle *style ) +{ + // Change the current style + d->currentChatStyle = style; + + // Do the actual style switch + // Wait for the event loop before switching the style + QTimer::singleShot( 0, this, SLOT(changeStyle()) ); +} + +void ChatMessagePart::setStyleVariant( const QString &variantPath ) +{ + DOM::HTMLElement variantNode = document().getElementById( QString::fromUtf8("mainStyle") ); + if( !variantNode.isNull() ) + variantNode.setInnerText( QString("@import url(\"%1\");").arg(variantPath) ); +} + +void ChatMessagePart::slotAppearanceChanged() +{ + readOverrides(); + + changeStyle(); +} + +void ChatMessagePart::appendMessage( Kopete::Message &message, bool restoring ) +{ + message.setBgOverride( d->bgOverride ); + message.setFgOverride( d->fgOverride ); + message.setRtfOverride( d->rtfOverride ); + + // parse emoticons and URL now. + // Do not reparse emoticons on restoring, because it cause very intensive CPU usage on long chats. + if( !restoring ) + message.setBody( message.parsedBody() , Kopete::Message::ParsedHTML ); + +#ifdef STYLE_TIMETEST + QTime beforeMessage = QTime::currentTime(); +#endif + + QString formattedMessageHtml; + bool isConsecutiveMessage = false; + uint bufferLen = (uint)KopetePrefs::prefs()->chatViewBufferSize(); + + // Find the "Chat" div element. + // If the "Chat" div element is not found, do nothing. It's the central part of Adium format. + DOM::HTMLElement chatNode = htmlDocument().getElementById( "Chat" ); + + if( chatNode.isNull() ) + { + kdDebug(14000) << k_funcinfo << "WARNING: Chat Node was null !" << endl; + return; + } + + // Check if it's a consecutive Message + // Consecutive messages are only for normal messages, status messages do not have a
    + // We check if the from() is the latestContact, because consecutive incoming/outgoing message can come from differents peopole(in groupchat and IRC) + // Group only if the user want it. + if( KopetePrefs::prefs()->groupConsecutiveMessages() ) + { + isConsecutiveMessage = (message.direction() == d->latestDirection && d->latestContact && d->latestContact == message.from() && message.type() == d->latestType); + } + + // Don't test it in the switch to don't break consecutive messages. + if(message.type() == Kopete::Message::TypeAction) + { + // Check if chat style support Action template (Kopete extension) + if( d->currentChatStyle->hasActionTemplate() ) + { + switch(message.direction()) + { + case Kopete::Message::Inbound: + formattedMessageHtml = d->currentChatStyle->getActionIncomingHtml(); + break; + case Kopete::Message::Outbound: + formattedMessageHtml = d->currentChatStyle->getActionOutgoingHtml(); + break; + default: + break; + } + } + // Use status template if no Action template. + else + { + formattedMessageHtml = d->currentChatStyle->getStatusHtml(); + } + } + else + { + switch(message.direction()) + { + case Kopete::Message::Inbound: + { + if(isConsecutiveMessage) + { + formattedMessageHtml = d->currentChatStyle->getNextIncomingHtml(); + } + else + { + formattedMessageHtml = d->currentChatStyle->getIncomingHtml(); + } + break; + } + case Kopete::Message::Outbound: + { + if(isConsecutiveMessage) + { + formattedMessageHtml = d->currentChatStyle->getNextOutgoingHtml(); + } + else + { + formattedMessageHtml = d->currentChatStyle->getOutgoingHtml(); + } + break; + } + case Kopete::Message::Internal: + { + formattedMessageHtml = d->currentChatStyle->getStatusHtml(); + break; + } + } + } + + formattedMessageHtml = formatStyleKeywords( formattedMessageHtml, message ); + + // newMessageNode is common to both code path + // FIXME: Find a better than to create a dummy span. + DOM::HTMLElement newMessageNode = document().createElement( QString::fromUtf8("span") ); + newMessageNode.setInnerHTML( formattedMessageHtml ); + + // Find the insert Node + DOM::HTMLElement insertNode = document().getElementById( QString::fromUtf8("insert") ); + + if( isConsecutiveMessage && !insertNode.isNull() ) + { + // Replace the insert block, because it's a consecutive message. + insertNode.parentNode().replaceChild(newMessageNode, insertNode); + } + else + { + // Remove the insert block, because it's a new message. + if( !insertNode.isNull() ) + insertNode.parentNode().removeChild(insertNode); + // Append to the chat. + chatNode.appendChild(newMessageNode); + } + + // Keep the direction to see on next message + // if it's a consecutive message + // Keep also the from() contact. + d->latestDirection = message.direction(); + d->latestType = message.type(); + d->latestContact = const_cast(message.from()); + + // Add the message to the list for futher restoring if needed + if(!restoring) + d->allMessages.append(message); + + while ( bufferLen>0 && d->allMessages.count() >= bufferLen ) + { + d->allMessages.pop_front(); + + // FIXME: Find a way to make work Chat View Buffer efficiently with consecutives messages. + // Before it was calling changeStyle() but it's damn too slow. + if( !KopetePrefs::prefs()->groupConsecutiveMessages() ) + { + chatNode.removeChild( chatNode.firstChild() ); + } + } + + if ( !d->scrollPressed ) + QTimer::singleShot( 1, this, SLOT( slotScrollView() ) ); + +#ifdef STYLE_TIMETEST + kdDebug(14000) << "Message time: " << beforeMessage.msecsTo( QTime::currentTime()) << endl; +#endif +} + +void ChatMessagePart::slotRefreshView() +{ + DOM::HTMLElement kopeteNode = document().getElementById( QString::fromUtf8("KopeteStyle") ); + if( !kopeteNode.isNull() ) + kopeteNode.setInnerText( styleHTML() ); + + DOM::HTMLBodyElement bodyElement = htmlDocument().body(); + bodyElement.setBgColor( KopetePrefs::prefs()->bgColor().name() ); +} + +void ChatMessagePart::keepScrolledDown() +{ + if ( !d->scrollPressed ) + QTimer::singleShot( 1, this, SLOT( slotScrollView() ) ); +} + +const QString ChatMessagePart::styleHTML() const +{ + KopetePrefs *p = KopetePrefs::prefs(); + + int fontSize = 0; + QString fontSizeCss; + // Use correct font size unit, depending of how the QFont was build. + if( p->fontFace().pointSize() != -1 ) + { + fontSize = p->fontFace().pointSize(); + fontSizeCss = QString::fromUtf8("%1pt;").arg(fontSize); + } + else if( p->fontFace().pixelSize() != -1 ) + { + fontSize = p->fontFace().pixelSize(); + fontSizeCss = QString::fromUtf8("%1px;").arg(fontSize); + } + + QString style = QString::fromLatin1( + "body{background-color:%1;font-family:%2;font-size:%3;color:%4}" + "td{font-family:%5;font-size:%6;color:%7}" + "a{color:%8}a.visited{color:%9}" + "a.KopeteDisplayName{text-decoration:none;color:inherit;}" + "a.KopeteDisplayName:hover{text-decoration:underline;color:inherit}" + ".KopeteLink{cursor:pointer;}.KopeteLink:hover{text-decoration:underline}" + ".KopeteMessageBody > p:first-child{margin:0;padding:0;display:inline;}" /* some html messages are encapsuled into a

    */ ) + .arg( p->bgColor().name() ) + .arg( p->fontFace().family() ) + .arg( fontSizeCss ) + .arg( p->textColor().name() ) + .arg( p->fontFace().family() ) + .arg( fontSizeCss ) + .arg( p->textColor().name() ) + .arg( p->linkColor().name() ) + .arg( p->linkColor().name() ); + + return style; +} + +void ChatMessagePart::clear() +{ + // writeTemplate actually reset the HTML chat session from the beginning. + writeTemplate(); + + // Reset consecutive messages + d->latestContact = 0; + // Remove all stored messages. + d->allMessages.clear(); +} + +Kopete::Contact *ChatMessagePart::contactFromNode( const DOM::Node &n ) const +{ + DOM::Node node = n; + + if ( node.isNull() ) + return 0; + + while ( !node.isNull() && ( node.nodeType() == DOM::Node::TEXT_NODE || ((DOM::HTMLElement)node).className() != "KopeteDisplayName" ) ) + node = node.parentNode(); + + DOM::HTMLElement element = node; + if ( element.className() != "KopeteDisplayName" ) + return 0; + + if ( element.hasAttribute( "contactid" ) ) + { + QString contactId = element.getAttribute( "contactid" ).string(); + for ( QPtrListIterator it ( d->manager->members() ); it.current(); ++it ) + if ( (*it)->contactId() == contactId ) + return *it; + } + else + { + QString nick = element.innerText().string().stripWhiteSpace(); + for ( QPtrListIterator it ( d->manager->members() ); it.current(); ++it ) + if ( (*it)->property( Kopete::Global::Properties::self()->nickName().key() ).value().toString() == nick ) + return *it; + } + + return 0; +} + +void ChatMessagePart::slotRightClick( const QString &, const QPoint &point ) +{ + // look through parents until we find an Element + DOM::Node activeNode = nodeUnderMouse(); + while ( !activeNode.isNull() && activeNode.nodeType() != DOM::Node::ELEMENT_NODE ) + activeNode = activeNode.parentNode(); + + // make sure it's valid + d->activeElement = activeNode; + if ( d->activeElement.isNull() ) + return; + + KPopupMenu *chatWindowPopup = 0L; + + if ( Kopete::Contact *contact = contactFromNode( d->activeElement ) ) + { + chatWindowPopup = contact->popupMenu( d->manager ); + connect( chatWindowPopup, SIGNAL( aboutToHide() ), chatWindowPopup , SLOT( deleteLater() ) ); + } + else + { + chatWindowPopup = new KPopupMenu(); + + if ( d->activeElement.className() == "KopeteDisplayName" ) + { + chatWindowPopup->insertItem( i18n( "User Has Left" ), 1 ); + chatWindowPopup->setItemEnabled( 1, false ); + chatWindowPopup->insertSeparator(); + } + else if ( d->activeElement.tagName().lower() == QString::fromLatin1( "a" ) ) + { + d->copyURLAction->plug( chatWindowPopup ); + chatWindowPopup->insertSeparator(); + } + + d->copyAction->setEnabled( hasSelection() ); + d->copyAction->plug( chatWindowPopup ); + d->saveAction->plug( chatWindowPopup ); + d->printAction->plug( chatWindowPopup ); + chatWindowPopup->insertSeparator(); + d->closeAction->plug( chatWindowPopup ); + + connect( chatWindowPopup, SIGNAL( aboutToHide() ), chatWindowPopup, SLOT( deleteLater() ) ); + chatWindowPopup->popup( point ); + } + + //Emit for plugin hooks + emit contextMenuEvent( textUnderMouse(), chatWindowPopup ); + + chatWindowPopup->popup( point ); +} + +QString ChatMessagePart::textUnderMouse() +{ + DOM::Node activeNode = nodeUnderMouse(); + if( activeNode.nodeType() != DOM::Node::TEXT_NODE ) + return QString::null; + + DOM::Text textNode = activeNode; + QString data = textNode.data().string(); + + //Ok, we have the whole node. Now, find the text under the mouse. + int mouseLeft = view()->mapFromGlobal( QCursor::pos() ).x(), + nodeLeft = activeNode.getRect().x(), + cPos = 0, + dataLen = data.length(); + + QFontMetrics metrics( KopetePrefs::prefs()->fontFace() ); + QString buffer; + while( cPos < dataLen && nodeLeft < mouseLeft ) + { + QChar c = data[cPos++]; + if( c.isSpace() ) + buffer.truncate(0); + else + buffer += c; + + nodeLeft += metrics.width(c); + } + + if( cPos < dataLen ) + { + QChar c = data[cPos++]; + while( cPos < dataLen && !c.isSpace() ) + { + buffer += c; + c = data[cPos++]; + } + } + + return buffer; +} + +void ChatMessagePart::slotCopyURL() +{ + DOM::HTMLAnchorElement a = d->activeElement; + if ( !a.isNull() ) + { + QApplication::clipboard()->setText( a.href().string(), QClipboard::Clipboard ); + QApplication::clipboard()->setText( a.href().string(), QClipboard::Selection ); + } +} + +void ChatMessagePart::slotScrollView() +{ + // NB: view()->contentsHeight() is incorrect before the view has been shown in its window. + // Until this happens, the geometry has not been correctly calculated, so this scrollBy call + // will usually scroll to the top of the view. + view()->scrollBy( 0, view()->contentsHeight() ); +} + +void ChatMessagePart::copy(bool justselection /* default false */) +{ + /* + * The objective of this function is to keep the text of emoticons (or of latex image) when copying. + * see Bug 61676 + * This also copies the text as type text/html + * RangeImpl::toHTML was not implemented before KDE 3.4 + */ + QString text; + QString htmltext; + +#if KDE_IS_VERSION(3,3,90) + htmltext = selectedTextAsHTML(); + text = selectedText(); + //selectedText is now sufficent +// text=Kopete::Message::unescape( htmltext ).stripWhiteSpace(); + // Message::unsescape will replace image by his title attribute + // stripWhiteSpace is for removing the newline added by the and other xml things of RangeImpl::toHTML +#else + + DOM::Node startNode, endNode; + long startOffset, endOffset; + selection( startNode, startOffset, endNode, endOffset ); + + //BEGIN: copied from KHTMLPart::selectedText + + bool hasNewLine = true; + DOM::Node n = startNode; + while(!n.isNull()) + { + if(n.nodeType() == DOM::Node::TEXT_NODE /*&& n.handle()->renderer()*/) + { + QString str = n.nodeValue().string(); + hasNewLine = false; + if(n == startNode && n == endNode) + text = str.mid(startOffset, endOffset - startOffset); + else if(n == startNode) + text = str.mid(startOffset); + else if(n == endNode) + text += str.left(endOffset); + else + text += str; + } + else + { // This is our simple HTML -> ASCII transformation: + unsigned short id = n.elementId(); + switch(id) + { + case ID_IMG: //here is the main difference with KHTMLView::selectedText + { + DOM::HTMLElement e = n; + if( !e.isNull() && e.hasAttribute( "title" ) ) + text+=e.getAttribute( "title" ).string(); + break; + } + case ID_BR: + text += "\n"; + hasNewLine = true; + break; + case ID_TD: case ID_TH: case ID_HR: + case ID_OL: case ID_UL: case ID_LI: + case ID_DD: case ID_DL: case ID_DT: + case ID_PRE: case ID_BLOCKQUOTE: case ID_DIV: + if (!hasNewLine) + text += "\n"; + hasNewLine = true; + break; + case ID_P: case ID_TR: + case ID_H1: case ID_H2: case ID_H3: + case ID_H4: case ID_H5: case ID_H6: + if (!hasNewLine) + text += "\n"; + text += "\n"; + hasNewLine = true; + break; + } + } + if(n == endNode) + break; + DOM::Node next = n.firstChild(); + if(next.isNull()) + next = n.nextSibling(); + while( next.isNull() && !n.parentNode().isNull() ) + { + n = n.parentNode(); + next = n.nextSibling(); + unsigned short id = n.elementId(); + switch(id) + { + case ID_TD: case ID_TH: case ID_HR: + case ID_OL: case ID_UL: case ID_LI: + case ID_DD: case ID_DL: case ID_DT: + case ID_PRE: case ID_BLOCKQUOTE: case ID_DIV: + if (!hasNewLine) + text += "\n"; + hasNewLine = true; + break; + case ID_P: case ID_TR: + case ID_H1: case ID_H2: case ID_H3: + case ID_H4: case ID_H5: case ID_H6: + if (!hasNewLine) + text += "\n"; + text += "\n"; + hasNewLine = true; + break; + } + } + n = next; + } + + if(text.isEmpty()) + return; + + int start = 0; + int end = text.length(); + + // Strip leading LFs + while ((start < end) && (text[start] == '\n')) + start++; + + // Strip excessive trailing LFs + while ((start < (end-1)) && (text[end-1] == '\n') && (text[end-2] == '\n')) + end--; + + text=text.mid(start, end-start); + + //END: copied from KHTMLPart::selectedText +#endif + + if(text.isEmpty()) return; + + disconnect( kapp->clipboard(), SIGNAL( selectionChanged()), this, SLOT( slotClearSelection())); + +#ifndef QT_NO_MIMECLIPBOARD + if(!justselection) + { + QTextDrag *textdrag = new QTextDrag(text, 0L); + KMultipleDrag *drag = new KMultipleDrag( ); + drag->addDragObject( textdrag ); + if(!htmltext.isEmpty()) { + htmltext.replace( QChar( 0xa0 ), ' ' ); + QTextDrag *htmltextdrag = new QTextDrag(htmltext, 0L); + htmltextdrag->setSubtype("html"); + drag->addDragObject( htmltextdrag ); + } + QApplication::clipboard()->setData( drag, QClipboard::Clipboard ); + } + QApplication::clipboard()->setText( text, QClipboard::Selection ); +#else + if(!justselection) + QApplication::clipboard()->setText( text, QClipboard::Clipboard ); + QApplication::clipboard()->setText( text, QClipboard::Selection ); +#endif + connect( kapp->clipboard(), SIGNAL( selectionChanged()), SLOT( slotClearSelection())); + +} + +void ChatMessagePart::print() +{ + view()->print(); +} + +void ChatMessagePart::khtmlDrawContentsEvent( khtml::DrawContentsEvent * event) //virtual +{ + KHTMLPart::khtmlDrawContentsEvent(event); + //copy(true /*selection only*/); not needed anymore. +} +void ChatMessagePart::slotCloseView( bool force ) +{ + d->manager->view()->closeView( force ); +} + +void ChatMessagePart::emitTooltipEvent( const QString &textUnderMouse, QString &toolTip ) +{ + emit tooltipEvent( textUnderMouse, toolTip ); +} + +// Style formatting for messages(incoming, outgoing, status) +QString ChatMessagePart::formatStyleKeywords( const QString &sourceHTML, const Kopete::Message &_message ) +{ + Kopete::Message message=_message; //we will eventually need to modify it before showing it. + QString resultHTML = sourceHTML; + QString nick, contactId, service, protocolIcon, nickLink; + + if( message.from() ) + { + // Use metacontact display name if the metacontact exists and if its not the myself metacontact. + if( message.from()->metaContact() && message.from()->metaContact() != Kopete::ContactList::self()->myself() ) + { + nick = message.from()->metaContact()->displayName(); + } + // Use contact nickname for no metacontact or myself. + else + { + nick = message.from()->nickName(); + } + nick = formatName(nick); + contactId = message.from()->contactId(); + // protocol() returns NULL here in the style preview in appearance config. + // this isn't the right place to work around it, since contacts should never have + // no protocol, but it works for now. + // + // Use default if protocol() and protocol()->displayName() is NULL. + // For preview and unit tests. + QString iconName = QString::fromUtf8("kopete"); + service = QString::fromUtf8("Kopete"); + if(message.from()->protocol() && !message.from()->protocol()->displayName().isNull()) + { + service = message.from()->protocol()->displayName(); + iconName = message.from()->protocol()->pluginIcon(); + } + + protocolIcon = KGlobal::iconLoader()->iconPath( iconName, KIcon::Small ); + + nickLink=QString::fromLatin1("") + .arg( QStyleSheet::escape(message.from()->contactId()).replace('"',"""), + QStyleSheet::escape(message.from()->protocol()->pluginId()).replace('"',"""), + QStyleSheet::escape(message.from()->account()->accountId() ).replace('"',""")); + } + else + { + nickLink=""; + } + + + // Replace sender (contact nick) + resultHTML = resultHTML.replace( QString::fromUtf8("%sender%"), nickLink+nick+"" ); + // Replace time, by default display only time and display seconds(that was true means). + resultHTML = resultHTML.replace( QString::fromUtf8("%time%"), KGlobal::locale()->formatTime(message.timestamp().time(), true) ); + // Replace %screenName% (contact ID) + resultHTML = resultHTML.replace( QString::fromUtf8("%senderScreenName%"), nickLink+QStyleSheet::escape(contactId)+"" ); + // Replace service name (protocol name) + resultHTML = resultHTML.replace( QString::fromUtf8("%service%"), QStyleSheet::escape(service) ); + // Replace protocolIcon (sender statusIcon) + resultHTML = resultHTML.replace( QString::fromUtf8("%senderStatusIcon%"), QStyleSheet::escape(protocolIcon).replace('"',""") ); + + // Look for %time{X}% + QRegExp timeRegExp("%time\\{([^}]*)\\}%"); + int pos=0; + while( (pos=timeRegExp.search(resultHTML , pos) ) != -1 ) + { + QString timeKeyword = formatTime( timeRegExp.cap(1), message.timestamp() ); + resultHTML = resultHTML.replace( pos , timeRegExp.cap(0).length() , timeKeyword ); + } + + // Look for %textbackgroundcolor{X}% + // TODO: use the X value. + // Replace with user-selected highlight color if to be highlighted or + // with "inherit" otherwise to keep CSS clean + QString bgColor = QString::fromUtf8("inherit"); + if( message.importance() == Kopete::Message::Highlight && KopetePrefs::prefs()->highlightEnabled() ) + { + bgColor = KopetePrefs::prefs()->highlightBackground().name(); + } + + QRegExp textBackgroundRegExp("%textbackgroundcolor\\{([^}]*)\\}%"); + int textPos=0; + while( (textPos=textBackgroundRegExp.search(resultHTML, textPos) ) != -1 ) + { + resultHTML = resultHTML.replace( textPos , textBackgroundRegExp.cap(0).length() , bgColor ); + } + + // Replace userIconPath + if( message.from() ) + { + QString photoPath; +#if 0 + photoPath = message.from()->property(Kopete::Global::Properties::self()->photo().key()).value().toString(); + // If the photo path is empty, set the default buddy icon for the theme + if( photoPath.isEmpty() ) + { + if(message.direction() == Kopete::Message::Inbound) + photoPath = QString::fromUtf8("Incoming/buddy_icon.png"); + else if(message.direction() == Kopete::Message::Outbound) + photoPath = QString::fromUtf8("Outgoing/buddy_icon.png"); + } +#endif + if( !message.from()->metaContact()->picture().isNull() ) + { + photoPath = QString( "data:image/png;base64," ) + message.from()->metaContact()->picture().base64(); + } + else + { + if(message.direction() == Kopete::Message::Inbound) + photoPath = QString::fromUtf8("Incoming/buddy_icon.png"); + else if(message.direction() == Kopete::Message::Outbound) + photoPath = QString::fromUtf8("Outgoing/buddy_icon.png"); + } + resultHTML = resultHTML.replace(QString::fromUtf8("%userIconPath%"), photoPath); + } + + // Replace messages. + // Build the action message if the currentChatStyle do not have Action template. + if( message.type() == Kopete::Message::TypeAction && !d->currentChatStyle->hasActionTemplate() ) + { + kdDebug(14000) << k_funcinfo << "Map Action message to Status template. " << endl; + + QString boldNick = QString::fromUtf8("%1%2 ").arg(nickLink,nick); + QString newBody = boldNick + message.parsedBody(); + message.setBody(newBody, Kopete::Message::ParsedHTML ); + } + + // Set message direction("rtl"(Right-To-Left) or "ltr"(Left-to-right)) + resultHTML = resultHTML.replace( QString::fromUtf8("%messageDirection%"), message.isRightToLeft() ? "rtl" : "ltr" ); + + // These colors are used for coloring nicknames. I tried to use + // colors both visible on light and dark background. + static const char* const nameColors[] = + { + "red", "blue" , "gray", "magenta", "violet", /*"olive"*/ "#808000", "yellowgreen", + "darkred", "darkgreen", "darksalmon", "darkcyan", /*"darkyellow"*/ "#B07D2B", + "mediumpurple", "peru", "olivedrab", /*"royalred"*/ "#B01712", "darkorange", "slateblue", + "slategray", "goldenrod", "orangered", "tomato", /*"dogderblue"*/ "#1E90FF", "steelblue", + "deeppink", "saddlebrown", "coral", "royalblue" + }; + + static const int nameColorsLen = sizeof(nameColors) / sizeof(nameColors[0]) - 1; + // hash contactId to deterministically pick a color for the contact + int hash = 0; + for( uint f = 0; f < contactId.length(); ++f ) + hash += contactId[f].unicode() * f; + const QString colorName = nameColors[ hash % nameColorsLen ]; + QString lightColorName; // Do not initialize, QColor::name() is expensive! + kdDebug(14000) << k_funcinfo << "Hash " << hash << " has color " << colorName << endl; + QRegExp senderColorRegExp("%senderColor(?:\\{([^}]*)\\})?%"); + textPos=0; + while( (textPos=senderColorRegExp.search(resultHTML, textPos) ) != -1 ) + { + int light=100; + bool doLight=false; + if(senderColorRegExp.numCaptures()>=1) + { + light=senderColorRegExp.cap(1).toUInt(&doLight); + } + + // Lazily init light color + if ( doLight && lightColorName.isNull() ) + lightColorName = QColor( colorName ).light( light ).name(); + + resultHTML = resultHTML.replace( textPos , senderColorRegExp.cap(0).length(), + doLight ? lightColorName : colorName ); + } + + // Replace message at the end, maybe someone could put a Adium keyword in his message :P + resultHTML = resultHTML.replace( QString::fromUtf8("%message%"), formatMessageBody(message) ); + + // TODO: %status +// resultHTML = addNickLinks( resultHTML ); + return resultHTML; +} + +// Style formatting for header and footer. +QString ChatMessagePart::formatStyleKeywords( const QString &sourceHTML ) +{ + QString resultHTML = sourceHTML; + + Kopete::Contact *remoteContact = d->manager->members().getFirst(); + + // Verify that all contacts are not null before doing anything + if( remoteContact && d->manager->myself() ) + { + QString sourceName, destinationName; + // Use contact nickname for ourselfs, Myself metacontact display name isn't a reliable source. + sourceName = d->manager->myself()->nickName(); + if( remoteContact->metaContact() ) + destinationName = remoteContact->metaContact()->displayName(); + else + destinationName = remoteContact->nickName(); + + // Replace %chatName%, create a internal span to update it by DOM when asked. + resultHTML = resultHTML.replace( QString::fromUtf8("%chatName%"), QString("%1").arg( formatName(d->manager->displayName()) ) ); + // Replace %sourceName% + resultHTML = resultHTML.replace( QString::fromUtf8("%sourceName%"), formatName(sourceName) ); + // Replace %destinationName% + resultHTML = resultHTML.replace( QString::fromUtf8("%destinationName%"), formatName(destinationName) ); + // For %timeOpened%, display the date and time (also the seconds). + resultHTML = resultHTML.replace( QString::fromUtf8("%timeOpened%"), KGlobal::locale()->formatDateTime( QDateTime::currentDateTime(), true, true ) ); + + // Look for %timeOpened{X}% + QRegExp timeRegExp("%timeOpened\\{([^}]*)\\}%"); + int pos=0; + while( (pos=timeRegExp.search(resultHTML, pos) ) != -1 ) + { + QString timeKeyword = formatTime( timeRegExp.cap(1), QDateTime::currentDateTime() ); + resultHTML = resultHTML.replace( pos , timeRegExp.cap(0).length() , timeKeyword ); + } + // Get contact image paths +#if 0 + QString photoIncomingPath, photoOutgoingPath; + photoIncomingPath = remoteContact->property( Kopete::Global::Properties::self()->photo().key()).value().toString(); + photoOutgoingPath = d->manager->myself()->property(Kopete::Global::Properties::self()->photo().key()).value().toString(); + + if( photoIncomingPath.isEmpty() ) + photoIncomingPath = QString::fromUtf8("Incoming/buddy_icon.png"); + if( photoOutgoingPath.isEmpty() ) + photoOutgoingPath = QString::fromUtf8("Outgoing/buddy_icon.png"); + + resultHTML = resultHTML.replace( QString::fromUtf8("%incomingIconPath%"), photoIncomingPath); + resultHTML = resultHTML.replace( QString::fromUtf8("%outgoingIconPath%"), photoOutgoingPath); +#endif + QString photoIncoming, photoOutgoing; + if( remoteContact->metaContact() && !remoteContact->metaContact()->picture().isNull() ) + { + photoIncoming = QString("data:image/png;base64,%1").arg( remoteContact->metaContact()->picture().base64() ); + } + else + { + photoIncoming = QString::fromUtf8("Incoming/buddy_icon.png"); + } + + if( d->manager->myself()->metaContact() && !d->manager->myself()->metaContact()->picture().isNull() ) + { + photoOutgoing = QString("data:image/png;base64,%1").arg( d->manager->myself()->metaContact()->picture().base64() ); + } + else + { + photoOutgoing = QString::fromUtf8("Outgoing/buddy_icon.png"); + } + + + resultHTML = resultHTML.replace( QString::fromUtf8("%incomingIconPath%"), photoIncoming); + resultHTML = resultHTML.replace( QString::fromUtf8("%outgoingIconPath%"), photoOutgoing ); + } + + return resultHTML; +} + +QString ChatMessagePart::formatTime(const QString &timeFormat, const QDateTime &dateTime) +{ + char buffer[256]; + + time_t timeT; + struct tm *loctime; + // Get current time + timeT = dateTime.toTime_t(); + // Convert it to local time representation. + loctime = localtime (&timeT); + strftime (buffer, 256, timeFormat.ascii(), loctime); + + return QString(buffer); +} + +QString ChatMessagePart::formatName(const QString &sourceName) +{ + QString formattedName = sourceName; + // Escape the name. + formattedName = Kopete::Message::escape(formattedName); + + // Squeeze the nickname if the user want it + if( KopetePrefs::prefs()->truncateContactNames() ) + { + formattedName = KStringHandler::csqueeze( sourceName, KopetePrefs::prefs()->maxConactNameLength() ); + } + + return formattedName; +} + +QString ChatMessagePart::formatMessageBody(const Kopete::Message &message) +{ + QString formattedBody("%1").arg(message.parsedBody()); + + return formattedBody; +} + +void ChatMessagePart::slotUpdateHeaderDisplayName() +{ + kdDebug(14000) << k_funcinfo << endl; + DOM::HTMLElement kopeteChatNameNode = document().getElementById( QString::fromUtf8("KopeteHeaderChatNameInternal") ); + if( !kopeteChatNameNode.isNull() ) + kopeteChatNameNode.setInnerText( formatName(d->manager->displayName()) ); +} + +void ChatMessagePart::slotUpdateHeaderPhoto() +{ + // Do the actual style switch + // Wait for the event loop before switching the style + QTimer::singleShot( 0, this, SLOT(changeStyle()) ); +} + +void ChatMessagePart::changeStyle() +{ +#ifdef STYLE_TIMETEST + QTime beforeChange = QTime::currentTime(); +#endif + // Make latestContact null to reset consecutives messages. + d->latestContact = 0; + + // Rewrite the header and footer. + writeTemplate(); + + // Readd all current messages. + QValueList::ConstIterator it, itEnd = d->allMessages.constEnd(); + for(it = d->allMessages.constBegin(); it != itEnd; ++it) + { + Kopete::Message tempMessage = *it; + appendMessage(tempMessage, true); // true means that we are restoring. + } + kdDebug(14000) << k_funcinfo << "Finish changing style." << endl; +#ifdef STYLE_TIMETEST + kdDebug(14000) << "Change time: " << beforeChange.msecsTo( QTime::currentTime()) << endl; +#endif +} + +void ChatMessagePart::writeTemplate() +{ + kdDebug(14000) << k_funcinfo << endl; + +#ifdef STYLE_TIMETEST + QTime beforeHeader = QTime::currentTime(); +#endif + // Clear all the page, and begin a new page. + begin(); + + // NOTE: About styles + // Order of style tag in the template is important. + // mainStyle take over all other style definition (which is what we want). + // + // KopeteStyle: Kopete appearance configuration into a style. It loaded first because + // we don't want Kopete settings to override CSS Chat Window Style. + // baseStyle: Import the main.css from the Chat Window Style + // mainStyle: Currrent variant CSS url. + + // FIXME: Maybe this string should be load from a file, then parsed for args. + QString xhtmlBase; + xhtmlBase += QString("\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "%2\n" + "

    \n
    \n" + "%3\n" + "" + "" + ).arg( d->currentChatStyle->getStyleBaseHref() ) + .arg( formatStyleKeywords(d->currentChatStyle->getHeaderHtml()) ) + .arg( formatStyleKeywords(d->currentChatStyle->getFooterHtml()) ) + .arg( KopetePrefs::prefs()->styleVariant() ) + .arg( styleHTML() ); + write(xhtmlBase); + end(); +#ifdef STYLE_TIMETEST + kdDebug(14000) << "Header time: " << beforeHeader.msecsTo( QTime::currentTime()) << endl; +#endif +} + +#include "chatmessagepart.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/chatmessagepart.h b/kopete/kopete/chatwindow/chatmessagepart.h new file mode 100644 index 00000000..ba92b95f --- /dev/null +++ b/kopete/kopete/chatwindow/chatmessagepart.h @@ -0,0 +1,248 @@ +/* + chatmessagepart.h - Chat Message KPart + + Copyright (c) 2004 by Richard Smith + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef CHATMESSAGEPART_H +#define CHATMESSAGEPART_H + +#include +#include + +#include + +namespace Kopete +{ + class Message; + class ChatSession; + class Contact; +} +class KPopupMenu; +class ChatWindowStyle; + +/** + * @author Richard Smith + */ +class ChatMessagePart : public KHTMLPart +{ + Q_OBJECT +public: + /** + * Create a new ChatMessage Part. + */ + ChatMessagePart( Kopete::ChatSession *manager, QWidget *parent, const char *name = 0); + ~ChatMessagePart(); + + /** + * Clear the message window + */ + void clear(); + + /** + * Immediately scroll the chat to the bottom, as long as it has not been intentionally scrolled away from the bottom + * use + */ + void keepScrolledDown(); + +public slots: + /** + * Initiates a copy action + * If there is text selected in the HTML view, that text is copied + * Otherwise if @p justselection is false, the entire edit area is copied. + * + * @param justselection If this is true, then the text will be only copied to the selection buffer only. + * In this mode, if nothing is selected, then nothing is copied. + */ + void copy(bool justselection = false); + + /** + * Print out the contents of the chatwindow + */ + void print(); + + /** + * Save the contents of the chat to a file + */ + void save(); + + /** + * Scroll the view up a page + */ + void pageUp(); + + /** + * Scroll the view down a page + */ + void pageDown(); + + /** + * Appends a message to the messave view + * @param message The message to be appended + * @param restoring This flag is used to not re-append message when changing style. By default false. + */ + void appendMessage( Kopete::Message &message, bool restoring = false); + + /** + * Change the current style. + * This method override is used when preferences change. + * This method create a new ChatWindowStyle object. + * + * Need to rebuild all the XHTML content. + * + * @param stylePath absolute path to the style. + */ + void setStyle( const QString &stylePath ); + + /** + * Change the current style + * This method override is used on preview and unit tests. + * Use a already existing ChatWindowStyle object. + * + * Need to rebuild all the XHTML content. + * + * @param chatWindowStyle ChatWindowStyle object. + */ + void setStyle( ChatWindowStyle *style ); + + /** + * Change the current variant for the current style + * @param variantPath relative path to the style variant. + */ + void setStyleVariant( const QString &variantPath ); + +signals: + /** + * Emits before the context menu is about to show + */ + void contextMenuEvent( const QString &textUnderMouse, KPopupMenu *popupMenu ); + + /** + * Emits before the tooltip is about to show + */ + void tooltipEvent( const QString &textUnderMouse, QString &toolTip ); + +private slots: + void slotOpenURLRequest( const KURL &url, const KParts::URLArgs &args ); + void slotScrollView(); + void slotAppearanceChanged(); + + void slotScrollingTo( int x, int y ); + + void slotRefreshView(); + + void slotRightClick( const QString &, const QPoint &point ); + + void slotCopyURL(); + + void slotCloseView( bool force = false ); + + /** + * Do the actual style change. + */ + void changeStyle(); + + /** + * Update the display in the header template if any. + */ + void slotUpdateHeaderDisplayName(); + /** + * Upda the photo in the header. + */ + void slotUpdateHeaderPhoto(); + +protected: + virtual void khtmlDrawContentsEvent( khtml::DrawContentsEvent * ); + +private: + void readOverrides(); + + const QString styleHTML() const; + + Kopete::Contact *contactFromNode( const DOM::Node &n ) const; + + /** + * Emits before the tooltip is about to show + */ + void emitTooltipEvent( const QString &textUnderMouse, QString &toolTipString ); + + /** + * Returns the text currently under the mouse + */ + QString textUnderMouse(); + + /** + * Format(replace) style keywords for messages (incoming, outgoing, internal) + * Use formatStyleKeywords(const QString &sourceHTML) for header and footer. + * + * @param sourceHTML the source html which contains the keywords + * @param message the current Message. + * + * @return the resulting HTML with replaced keywords. + */ + QString formatStyleKeywords( const QString &sourceHTML, const Kopete::Message &message ); + /** + * Format(replace) style keywords for header and footers. + * For messages, use formatStyleKeywords(const QString &sourceHTML, Kopete::Message &message) instead. + * + * @param sourceHTML HTML source needed to be replaced. + * + * @return the resulting HTML with replaced keywords. + */ + QString formatStyleKeywords( const QString &sourceHTML ); + + /** + * Helper function to parse time in correct format. + * Use glibc strftime function. + * + * @param timeFormat the time format to parse. + * @param dateTime the QDateTime which contains the datetime to format. + * @return the formatted time string. + */ + QString formatTime(const QString &timeFormat, const QDateTime &dateTime); + + /** + * Format a nickname/displayname according to preferences. + * + * @param sourceName Source name to format. + * @return the formatted name. + */ + QString formatName( const QString &sourceName ); + + /** + * Format a message body according to the style included + * in the message. + * + * @param message Kopete::Message to format. + * @return a span tag with a style attribute. + */ + QString formatMessageBody( const Kopete::Message &message ); + + /** + * Write the template file to KHTMLPart + */ + void writeTemplate(); + + class ToolTip; + friend class ToolTip; + + class Private; + Private *d; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/chattexteditpart.cpp b/kopete/kopete/chatwindow/chattexteditpart.cpp new file mode 100644 index 00000000..bac31bcf --- /dev/null +++ b/kopete/kopete/chatwindow/chattexteditpart.cpp @@ -0,0 +1,423 @@ +/* + chattexteditpart.cpp - Chat Text Edit Part + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "chattexteditpart.h" + +#include "kopetechatsession.h" +#include "kopeteonlinestatus.h" +#include "kopeteprotocol.h" +#include "kopeteglobal.h" +#include "kopeteprefs.h" + +#include +#include +#include +#include + +#include +#include + +ChatTextEditPart::ChatTextEditPart( Kopete::ChatSession *session, QWidget *parent, const char *name ) + : KopeteRichTextEditPart( parent, name, session->protocol()->capabilities() ), m_session(session) +{ + historyPos = -1; + + toggleAutoSpellCheck(KopetePrefs::prefs()->spellCheck()); + + mComplete = new KCompletion(); + mComplete->setIgnoreCase( true ); + mComplete->setOrder( KCompletion::Weighted ); + + // set params on the edit widget + edit()->setMinimumSize( QSize( 75, 20 ) ); + edit()->setWordWrap( QTextEdit::WidgetWidth ); + edit()->setWrapPolicy( QTextEdit::AtWhiteSpace ); + edit()->setAutoFormatting( QTextEdit::AutoNone ); + + // some signals and slots connections + connect( edit(), SIGNAL( textChanged()), this, SLOT( slotTextChanged() ) ); + + // timers for typing notifications + m_typingRepeatTimer = new QTimer(this, "m_typingRepeatTimer"); + m_typingStopTimer = new QTimer(this, "m_typingStopTimer"); + + connect( m_typingRepeatTimer, SIGNAL( timeout() ), this, SLOT( slotRepeatTypingTimer() ) ); + connect( m_typingStopTimer, SIGNAL( timeout() ), this, SLOT( slotStoppedTypingTimer() ) ); + + connect( session, SIGNAL( contactAdded(const Kopete::Contact*, bool) ), + this, SLOT( slotContactAdded(const Kopete::Contact*) ) ); + connect( session, SIGNAL( contactRemoved(const Kopete::Contact*, const QString&, Kopete::Message::MessageFormat, bool) ), + this, SLOT( slotContactRemoved(const Kopete::Contact*) ) ); + connect( session, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus & , const Kopete::OnlineStatus &) ), + this, SLOT( slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + + slotContactAdded( session->myself() ); + for ( QPtrListIterator it( session->members() ); it.current(); ++it ) + slotContactAdded( *it ); +} + +ChatTextEditPart::~ChatTextEditPart() +{ + delete mComplete; +} + +KTextEdit *ChatTextEditPart::edit() +{ + return static_cast(widget()); +} + +void ChatTextEditPart::toggleAutoSpellCheck( bool enabled ) +{ + if ( richTextEnabled() ) + enabled = false; + + m_autoSpellCheckEnabled = enabled; + if ( spellHighlighter() ) + { + spellHighlighter()->setAutomatic( enabled ); + spellHighlighter()->setActive( enabled ); + } + edit()->setCheckSpellingEnabled( enabled ); +} + +bool ChatTextEditPart::autoSpellCheckEnabled() const +{ + return m_autoSpellCheckEnabled; +} + +KDictSpellingHighlighter* ChatTextEditPart::spellHighlighter() +{ + QSyntaxHighlighter *qsh = edit()->syntaxHighlighter(); + KDictSpellingHighlighter* kdsh = dynamic_cast( qsh ); + return kdsh; +} + +// NAUGHTY, BAD AND WRONG! (but needed to fix nick complete bugs) +#include +class EvilTextEdit : public KTextEdit +{ +public: + // grab the paragraph as plain text - very very evil. + QString plainText( int para ) + { + QString str = document()->paragAt( para )->string()->toString(); + // str includes an extra space on the end (from the newline character?) - remove it + return str.left( str.length() - 1 ); + } +}; + +void ChatTextEditPart::complete() +{ + int para = 1, parIdx = 1; + edit()->getCursorPosition( ¶, &parIdx); + + // FIXME: strips out all formatting + QString txt = static_cast(edit())->plainText( para ); + + if ( parIdx > 0 ) + { + int firstSpace = txt.findRev( QRegExp( QString::fromLatin1("\\s\\S+") ), parIdx - 1 ) + 1; + int lastSpace = txt.find( QRegExp( QString::fromLatin1("[\\s\\:]") ), firstSpace ); + if( lastSpace == -1 ) + lastSpace = txt.length(); + + QString word = txt.mid( firstSpace, lastSpace - firstSpace ); + QString match; + + kdDebug(14000) << k_funcinfo << word << " from '" << txt << "'" << endl; + + if ( word != m_lastMatch ) + { + match = mComplete->makeCompletion( word ); + m_lastMatch = QString::null; + parIdx -= word.length(); + } + else + { + match = mComplete->nextMatch(); + parIdx -= m_lastMatch.length(); + } + + if ( !match.isNull() && !match.isEmpty() ) + { + QString rightText = txt.right( txt.length() - lastSpace ); + + if ( para == 0 && firstSpace == 0 && rightText[0] != QChar(':') ) + { + rightText = match + QString::fromLatin1(": ") + rightText; + parIdx += 2; + } + else + rightText = match + rightText; + + // insert *before* remove. this is becase Qt adds an extra blank line + // if the rich text control becomes empty (if you remove the only para). + // disable updates while we change the contents to eliminate flicker. + edit()->setUpdatesEnabled( false ); + edit()->insertParagraph( txt.left(firstSpace) + rightText, para ); + edit()->removeParagraph( para + 1 ); + edit()->setCursorPosition( para, parIdx + match.length() ); + edit()->setUpdatesEnabled( true ); + // must call this rather than update because QTextEdit is broken :( + edit()->updateContents(); + m_lastMatch = match; + } + else + { + kdDebug(14000) << k_funcinfo << "No completions! Tried " << mComplete->items() << endl; + } + } +} + +void ChatTextEditPart::slotPropertyChanged( Kopete::Contact*, const QString &key, + const QVariant& oldValue, const QVariant &newValue ) +{ + if ( key == Kopete::Global::Properties::self()->nickName().key() ) + { + mComplete->removeItem( oldValue.toString() ); + mComplete->addItem( newValue.toString() ); + } +} + +void ChatTextEditPart::slotContactAdded( const Kopete::Contact *contact ) +{ + connect( contact, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), + this, SLOT( slotPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ) ) ; + + QString contactName = contact->property(Kopete::Global::Properties::self()->nickName()).value().toString(); + mComplete->addItem( contactName ); +} + +void ChatTextEditPart::slotContactRemoved( const Kopete::Contact *contact ) +{ + disconnect( contact, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), + this, SLOT( slotPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ) ) ; + + QString contactName = contact->property(Kopete::Global::Properties::self()->nickName()).value().toString(); + mComplete->removeItem( contactName ); +} + +bool ChatTextEditPart::canSend() +{ + if ( !m_session ) return false; + + // can't send if there's nothing *to* send... + if ( edit()->text().isEmpty() ) + return false; + + Kopete::ContactPtrList members = m_session->members(); + + // if we can't send offline, make sure we have a reachable contact... + if ( !( m_session->protocol()->capabilities() & Kopete::Protocol::CanSendOffline ) ) + { + bool reachableContactFound = false; + + //TODO: does this perform badly in large / busy IRC channels? - no, doesn't seem to + QPtrListIterator it ( members ); + for( ; it.current(); ++it ) + { + if ( (*it)->isReachable() ) + { + reachableContactFound = true; + break; + } + } + + // no online contact found and can't send offline? can't send. + if ( !reachableContactFound ) + return false; + } + + return true; +} + +void ChatTextEditPart::slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &newStatus, const Kopete::OnlineStatus &oldStatus ) +{ + //FIXME: should use signal contact->isReachableChanged, but it doesn't exist ;( + if ( ( oldStatus.status() == Kopete::OnlineStatus::Offline ) + != ( newStatus.status() == Kopete::OnlineStatus::Offline ) ) + { + emit canSendChanged( canSend() ); + } +} + +void ChatTextEditPart::sendMessage() +{ + QString txt = text( Qt::PlainText ); + // avoid sending emtpy messages or enter keys (see bug 100334) + if ( txt.isEmpty() || txt == "\n" ) + return; + + if ( m_lastMatch.isNull() && ( txt.find( QRegExp( QString::fromLatin1("^\\w+:\\s") ) ) > -1 ) ) + { //no last match and it finds something of the form of "word:" at the start of a line + QString search = txt.left( txt.find(':') ); + if( !search.isEmpty() ) + { + QString match = mComplete->makeCompletion( search ); + if( !match.isNull() ) + edit()->setText( txt.replace(0,search.length(),match) ); + } + } + + if ( !m_lastMatch.isNull() ) + { + //FIXME: what is the next line for? + mComplete->addItem( m_lastMatch ); + m_lastMatch = QString::null; + } + + slotStoppedTypingTimer(); + Kopete::Message sentMessage = contents(); + emit messageSent( sentMessage ); + historyList.prepend( edit()->text() ); + historyPos = -1; + clear(); + emit canSendChanged( false ); +} + +bool ChatTextEditPart::isTyping() +{ + QString txt = text( Qt::PlainText ); + + //Make sure the message is empty. QString::isEmpty() + //returns false if a message contains just whitespace + //which is the reason why we strip the whitespace + return !txt.stripWhiteSpace().isEmpty(); +} + +void ChatTextEditPart::slotTextChanged() +{ + if ( isTyping() ) + { + // And they were previously typing + if( !m_typingRepeatTimer->isActive() ) + { + m_typingRepeatTimer->start( 4000, false ); + slotRepeatTypingTimer(); + } + + // Reset the stop timer again, regardless of status + m_typingStopTimer->start( 4500, true ); + } + + emit canSendChanged( canSend() ); +} + +void ChatTextEditPart::historyUp() +{ + if ( historyList.empty() || historyPos == historyList.count() - 1 ) + return; + + QString text = edit()->text(); + bool empty = text.stripWhiteSpace().isEmpty(); + + // got text? save it + if ( !empty ) + { + if ( historyPos == -1 ) + { + historyList.prepend( text ); + historyPos = 0; + } + else + { + historyList[historyPos] = text; + } + } + + historyPos++; + + QString newText = historyList[historyPos]; + TextFormat format=edit()->textFormat(); + edit()->setTextFormat(AutoText); //workaround bug 115690 + edit()->setText( newText ); + edit()->setTextFormat(format); + edit()->moveCursor( QTextEdit::MoveEnd, false ); +} + +void ChatTextEditPart::historyDown() +{ + if ( historyList.empty() || historyPos == -1 ) + return; + + QString text = edit()->text(); + bool empty = text.stripWhiteSpace().isEmpty(); + + // got text? save it + if ( !empty ) + { + historyList[historyPos] = text; + } + + historyPos--; + + QString newText = ( historyPos >= 0 ? historyList[historyPos] : QString::null ); + + + TextFormat format=edit()->textFormat(); + edit()->setTextFormat(AutoText); //workaround bug 115690 + edit()->setText( newText ); + edit()->setTextFormat(format); + + + + edit()->moveCursor( QTextEdit::MoveEnd, false ); +} + +void ChatTextEditPart::addText( const QString &text ) +{ + edit()->insert( text ); +} + +void ChatTextEditPart::setContents( const Kopete::Message &message ) +{ + edit()->setText( richTextEnabled() ? message.escapedBody() : message.plainBody() ); + + setFont( message.font() ); + setFgColor( message.fg() ); + setBgColor( message.bg() ); +} + +Kopete::Message ChatTextEditPart::contents() +{ + Kopete::Message currentMsg( m_session->myself(), m_session->members(), edit()->text(), + Kopete::Message::Outbound, richTextEnabled() ? + Kopete::Message::RichText : Kopete::Message::PlainText ); + + currentMsg.setBg( bgColor() ); + currentMsg.setFg( fgColor() ); + currentMsg.setFont( font() ); + + return currentMsg; +} + +void ChatTextEditPart::slotRepeatTypingTimer() +{ + emit typing( true ); +} + +void ChatTextEditPart::slotStoppedTypingTimer() +{ + m_typingRepeatTimer->stop(); + m_typingStopTimer->stop(); + emit typing( false ); +} + +#include "chattexteditpart.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/chattexteditpart.h b/kopete/kopete/chatwindow/chattexteditpart.h new file mode 100644 index 00000000..3391b8a7 --- /dev/null +++ b/kopete/kopete/chatwindow/chattexteditpart.h @@ -0,0 +1,209 @@ +/* + chattexteditpart.h - Chat Text Edit Part + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef CHATTEXTEDITPART_H +#define CHATTEXTEDITPART_H + +#include "krichtexteditpart.h" + +#include + +class QTimer; + +class KCompletion; +class KDictSpellingHighlighter; + +namespace Kopete +{ +class Message; +class Contact; +class OnlineStatus; +class ChatSession; +} + +/** + * @brief An instant message composition part + * + * This class provides an input part suitable for the composition of instant messages. + * It provides command history, nickname completion and typing notifications. It is + * also able to determine whether the send button should be enabled. + * + * @author Richard Smith + */ +class ChatTextEditPart : public KopeteRichTextEditPart +{ + Q_OBJECT +public: + ChatTextEditPart( Kopete::ChatSession *session, QWidget *parent, const char *name = 0 ); + ~ChatTextEditPart(); + + /** + * Returns the message currently in the edit area + * @return The @ref Kopete::Message object for the message + */ + Kopete::Message contents(); + + /** + * Sets the message in the edit field + * @param message The message to display + */ + void setContents( const Kopete::Message &message ); + + /** + * Adds text into the edit area. Used when an emoticon is selected. + * @param text The text to be inserted + */ + void addText( const QString &text ); + + /** + * Can we send messages now? + */ + bool canSend(); + + /** + * Is the user typing right now? + */ + bool isTyping(); + + /** + * @return This part's main widget + */ + KTextEdit *edit(); + + /** + * Enable or Disable the automatic spell checking + * @param enabled the state that auto spell checking should beee + */ + void toggleAutoSpellCheck( bool enabled ); + + /** + * Get the state of auto spell checking + * @return true if auto spell checking is turned on, false otherwise + */ + bool autoSpellCheckEnabled() const; + +public slots: + /** + * Go up an entry in the message history. + */ + void historyUp(); + + /** + * Go down an entry in the message history. + */ + void historyDown(); + + /** + * Try to complete the word under the cursor. + */ + void complete(); + + /** + * Sends the text currently entered into the edit area. + */ + void sendMessage(); + +signals: + /** + * Emitted when a message is sent. + * @param message The message sent + */ + void messageSent( Kopete::Message &message ); + + /** + * Emitted every 4 seconds while the user is typing. + * @param typing @c true if the user is typing, @c false otherwise + */ + void typing( bool typing ); + + /** + * Our send-button-enabled flag might have changed + * @param canSend The return value of @ref canSend(). + */ + void canSendChanged( bool canSend ); + +private slots: + /** + * Called when a contact is added to the chat session. + * Adds this contact to the nickname completion list. + * @param c The contact that joined the chat + */ + void slotContactAdded( const Kopete::Contact *c ); + + /** + * Called when a contact is removed from the chat session. + * Removes this contact from the nickname completion list. + * @param c The contact left the chat + */ + void slotContactRemoved( const Kopete::Contact *c ); + + /** + * Called when a contact changes status, may emit @ref canSendChanged. + * @param contact The contact who changed status + * @param status The new status of the contact + */ + void slotContactStatusChanged( Kopete::Contact *contact, const Kopete::OnlineStatus &status, const Kopete::OnlineStatus &oldstatus ); + + /** + * Called when text is changed in the edit area + */ + void slotTextChanged(); + + /** + * User is typing, so emit a @ref typing( @c true ) signal every 4 seconds. + * This is stupid. Why not just emit it once? + */ + void slotRepeatTypingTimer(); + + /** + * Emits a @ref typing( @c false ) signal 4.5 seconds after the user stops typing. + */ + void slotStoppedTypingTimer(); + + /** + * Update completion to follow changes in users' nicknames + */ + void slotPropertyChanged( Kopete::Contact *, const QString &key, const QVariant &oldValue, const QVariant &newValue ); + +private: + KDictSpellingHighlighter* spellHighlighter(); + +private: + Kopete::ChatSession *m_session; + + /** + * The history buffer conceptually works like this: + * We have a list of messages (historyList), with indices from -1 to n. + * historyPos is our current position in this list; historyList[historyPos] + * is conceptually the message we are editing. The exception to this is that + * index -1 is treated specially; when it is modified, changes are saved to + * a new message placed at index 0. + */ + QStringList historyList; + int historyPos; + + KCompletion *mComplete; + QString m_lastMatch; + + QTimer *m_typingRepeatTimer; + QTimer *m_typingStopTimer; + bool m_autoSpellCheckEnabled; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/chatwindow/chatview.cpp b/kopete/kopete/chatwindow/chatview.cpp new file mode 100644 index 00000000..62c1206d --- /dev/null +++ b/kopete/kopete/chatwindow/chatview.cpp @@ -0,0 +1,1087 @@ +/* + chatview.cpp - Chat View + + Copyright (c) 2002-2004 by Olivier Goffart + Copyright (c) 2002-2003 by Martijn Klingens + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "chatview.h" + +#include "chatmemberslistwidget.h" +#include "chatmessagepart.h" +#include "chattexteditpart.h" +#include "kopetechatwindow.h" +#include "kopetechatsession.h" +#include "kopetemetacontact.h" +#include "kopetepluginmanager.h" +#include "kopeteprefs.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopeteglobal.h" +#include "kopetecontactlist.h" +#include "kopeteviewmanager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef KGenericFactory ChatWindowPluginFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_chatwindow, ChatWindowPluginFactory( "kopete_chatwindow" ) ) + +ChatWindowPlugin::ChatWindowPlugin(QObject *parent, const char *name, const QStringList &) : + Kopete::ViewPlugin( ChatWindowPluginFactory::instance(), parent, name ) +{} + +KopeteView* ChatWindowPlugin::createView( Kopete::ChatSession *manager ) +{ + return (KopeteView*)new ChatView(manager,this); +} + +class KopeteChatViewPrivate +{ +public: + QString captionText; + QString statusText; + bool isActive; + bool sendInProgress; + bool visibleMembers; +}; + +ChatView::ChatView( Kopete::ChatSession *mgr, ChatWindowPlugin *parent, const char *name ) + : KDockMainWindow( 0L, name, 0L ), KopeteView( mgr, parent ) +{ + d = new KopeteChatViewPrivate; + d->isActive = false; + d->visibleMembers = false; + d->sendInProgress = false; + + + m_mainWindow = 0L; + membersDock = 0L; + membersStatus = Smart; + m_tabState = Normal; + + + //FIXME: don't widgets start off hidden anyway? + hide(); + + //Create the view dock widget (KHTML Part), and set it to no docking (lock it in place) + viewDock = createDockWidget(QString::fromLatin1( "viewDock" ), QPixmap(), + 0L,QString::fromLatin1("viewDock"), QString::fromLatin1(" ")); + m_messagePart = new ChatMessagePart( mgr, viewDock, "m_messagePart" ); + + viewDock->setWidget(messagePart()->widget()); + viewDock->setDockSite(KDockWidget::DockBottom); + viewDock->setEnableDocking(KDockWidget::DockNone); + + //Create the bottom dock widget, with the edit area, statusbar and send button + editDock = createDockWidget( QString::fromLatin1( "editDock" ), QPixmap(), + 0L, QString::fromLatin1("editDock"), QString::fromLatin1(" ") ); + m_editPart = new ChatTextEditPart( mgr, editDock, "kopeterichtexteditpart" ); + + // FIXME: is this used these days? it seems totally unnecessary + connect( editPart(), SIGNAL( toggleToolbar(bool)), this, SLOT(slotToggleRtfToolbar(bool)) ); + + connect( editPart(), SIGNAL( messageSent( Kopete::Message & ) ), + this, SIGNAL( messageSent( Kopete::Message & ) ) ); + connect( editPart(), SIGNAL( canSendChanged( bool ) ), + this, SIGNAL( canSendChanged(bool) ) ); + connect( editPart(), SIGNAL( typing(bool) ), + mgr, SLOT( typing(bool) ) ); + + //Make the edit area dockable for now + editDock->setWidget( editPart()->widget() ); + editDock->setDockSite( KDockWidget::DockNone ); + editDock->setEnableDocking(KDockWidget::DockBottom); + + //Set the view as the main widget + setMainDockWidget( viewDock ); + setView(viewDock); + + //It is possible to drag and drop on this widget. + // I had to disable the acceptDrop in the khtml widget to be able to intercept theses events. + setAcceptDrops(true); + viewDock->setAcceptDrops(false); + + m_remoteTypingMap.setAutoDelete( true ); + + //Manager signals + connect( mgr, SIGNAL( displayNameChanged() ), + this, SLOT( slotChatDisplayNameChanged() ) ); + connect( mgr, SIGNAL( contactAdded(const Kopete::Contact*, bool) ), + this, SLOT( slotContactAdded(const Kopete::Contact*, bool) ) ); + connect( mgr, SIGNAL( contactRemoved(const Kopete::Contact*, const QString&, Kopete::Message::MessageFormat, bool) ), + this, SLOT( slotContactRemoved(const Kopete::Contact*, const QString&, Kopete::Message::MessageFormat, bool) ) ); + connect( mgr, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus & , const Kopete::OnlineStatus &) ), + this, SLOT( slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + connect( mgr, SIGNAL( remoteTyping( const Kopete::Contact *, bool) ), + this, SLOT( remoteTyping(const Kopete::Contact *, bool) ) ); + connect( mgr, SIGNAL( eventNotification( const QString& ) ), + this, SLOT( setStatusText( const QString& ) ) ); + + //Connections to the manager and the ViewManager that every view should have + connect( this, SIGNAL( closing( KopeteView * ) ), + KopeteViewManager::viewManager(), SLOT( slotViewDestroyed( KopeteView * ) ) ); + connect( this, SIGNAL( activated( KopeteView * ) ), + KopeteViewManager::viewManager(), SLOT( slotViewActivated( KopeteView * ) ) ); + connect( this, SIGNAL( messageSent(Kopete::Message &) ), + mgr, SLOT( sendMessage(Kopete::Message &) ) ); + connect( mgr, SIGNAL( messageSuccess() ), + this, SLOT( messageSentSuccessfully() )); + + // add contacts + slotContactAdded( mgr->myself(), true ); + for ( QPtrListIterator it( mgr->members() ); it.current(); ++it ) + slotContactAdded( *it, true ); + + setFocusProxy( editPart()->widget() ); + editPart()->widget()->setFocus(); + + // init actions + KStdAction::copy( this, SLOT(copy()), actionCollection() ); + KStdAction::close( this, SLOT(closeView()),actionCollection() ); + + setCaption( m_manager->displayName(), false ); + + // restore docking positions + readOptions(); + + // maybe show chat members + createMembersList(); +} + +ChatView::~ChatView() +{ + emit( closing( static_cast(this) ) ); + + saveOptions(); + + delete d; +} + +KTextEdit *ChatView::editWidget() +{ + return editPart()->widget(); +} + +QWidget *ChatView::mainWidget() +{ + return this; +} + +bool ChatView::canSend() +{ + return editPart()->canSend(); +} + +Kopete::Message ChatView::currentMessage() +{ + return editPart()->contents(); +} + +void ChatView::setCurrentMessage( const Kopete::Message &message ) +{ + editPart()->setContents( message ); +} + +void ChatView::cut() +{ + editPart()->edit()->cut(); +} + +void ChatView::copy() +{ + if ( messagePart()->hasSelection() ) + messagePart()->copy(); + else + editPart()->edit()->copy(); +} + +void ChatView::paste() +{ + editPart()->edit()->paste(); +} + +void ChatView::nickComplete() +{ + return editPart()->complete(); +} + +void ChatView::addText( const QString &text ) +{ + editPart()->addText( text ); +} + +void ChatView::clear() +{ + messagePart()->clear(); +} + +void ChatView::setBgColor( const QColor &newColor ) +{ + editPart()->setBgColor( newColor ); +} + +void ChatView::setFont() +{ + editPart()->setFont(); +} + +QFont ChatView::font() +{ + return editPart()->font(); +} + +void ChatView::setFont( const QFont &font ) +{ + editPart()->setFont( font ); +} + +void ChatView::setFgColor( const QColor &newColor ) +{ + editPart()->setFgColor( newColor ); +} + +void ChatView::raise( bool activate ) +{ + // this shouldn't change the focus. When the window is raised when a new message arrives + // if i am coding, or talking to someone else, i want to end my sentence before switching to + // the other chat. i just want to KNOW and SEE the other chat to switch to it later + // (except if activate==true) + + if ( !m_mainWindow || !m_mainWindow->isActiveWindow() || activate ) + makeVisible(); + + if ( !KWin::windowInfo( m_mainWindow->winId(), NET::WMDesktop ).onAllDesktops() ) + if( KopetePrefs::prefs()->trayflashNotifySetCurrentDesktopToChatView() && activate ) + KWin::setCurrentDesktop( KWin::windowInfo( m_mainWindow->winId(), NET::WMDesktop ).desktop() ); + else + KWin::setOnDesktop( m_mainWindow->winId(), KWin::currentDesktop() ); + + if(m_mainWindow->isMinimized()) + { + m_mainWindow->showNormal(); + } + + + m_mainWindow->raise(); + + /* Removed Nov 2003 + According to Zack, the user double-clicking a contact is not valid reason for a non-pager + to grab window focus. While I don't agree with this, and it runs contradictory to every other + IM out there, commenting this code out to agree with KWin policy. + + Redirect any bugs relating to the widnow now not grabbing focus on clicking a contact to KWin. + - Jason K + */ + + //Will not activate window if user was typing + if ( activate ) + KWin::activateWindow( m_mainWindow->winId() ); + +} + +void ChatView::makeVisible() +{ + if ( !m_mainWindow ) + { + m_mainWindow = KopeteChatWindow::window( m_manager ); +// if ( root ) +// root->repaint( true ); + emit windowCreated(); + } + + if ( !m_mainWindow->isVisible() ) + { + m_mainWindow->show(); + // scroll down post show and layout, otherwise the geometry is wrong to scroll to the bottom. + m_messagePart->keepScrolledDown(); + } + + + + m_mainWindow->setActiveView( this ); +} + +bool ChatView::isVisible() +{ + return ( m_mainWindow && m_mainWindow->isVisible() ); +} + +bool ChatView::visibleMembersList() +{ + return d->visibleMembers; +} + +bool ChatView::sendInProgress() +{ + return d->sendInProgress; +} + +bool ChatView::closeView( bool force ) +{ + int response = KMessageBox::Continue; + + if ( !force ) + { + if ( m_manager->members().count() > 1 ) + { + QString shortCaption = d->captionText; + shortCaption = KStringHandler::rsqueeze( shortCaption ); + + response = KMessageBox::warningContinueCancel( this, i18n("You are about to leave the group chat session %1.
    " + "You will not receive future messages from this conversation.
    ").arg( shortCaption ), i18n( "Closing Group Chat" ), + i18n( "Cl&ose Chat" ), QString::fromLatin1( "AskCloseGroupChat" ) ); + } + + if ( !unreadMessageFrom.isNull() && ( response == KMessageBox::Continue ) ) + { + response = KMessageBox::warningContinueCancel( this, i18n("You have received a message from %1 in the last " + "second. Are you sure you want to close this chat?").arg( unreadMessageFrom ), i18n( "Unread Message" ), + i18n( "Cl&ose Chat" ), QString::fromLatin1("AskCloseChatRecentMessage" ) ); + } + + if ( d->sendInProgress && ( response == KMessageBox::Continue ) ) + { + response = KMessageBox::warningContinueCancel( this, i18n( "You have a message send in progress, which will be " + "aborted if this chat is closed. Are you sure you want to close this chat?" ), i18n( "Message in Transit" ), + i18n( "Cl&ose Chat" ), QString::fromLatin1( "AskCloseChatMessageInProgress" ) ); + } + } + + if( response == KMessageBox::Continue ) + { + // Remove the widget from the window it's attached to + // and schedule it for deletion + if( m_mainWindow ) + m_mainWindow->detachChatView( this ); + deleteLater(); + + return true; + } + + return false; +} + +void ChatView::updateChatState( KopeteTabState newState ) +{ + if ( newState == Undefined ) + newState = m_tabState; + else if ( newState != Typing && ( newState != Changed || ( m_tabState != Message && m_tabState != Highlighted ) ) + && ( newState != Message || m_tabState != Highlighted ) ) + { //if the new state is not a typing state and we don't already have a message or a highlighted message + //change the tab state + m_tabState = newState; + } + + newState = m_remoteTypingMap.isEmpty() ? m_tabState : Typing ; + + emit updateChatState( this, newState ); + + if( newState != Typing ) + { + setStatusText( i18n( "One other person in the chat", + "%n other people in the chat", m_manager->members().count() ) ); + } +} + +void ChatView::setMainWindow( KopeteChatWindow* parent ) +{ + m_mainWindow = parent; +} + +void ChatView::createMembersList() +{ + if ( !membersDock ) + { + //Create the chat members list + membersDock = createDockWidget( QString::fromLatin1( "membersDock" ), QPixmap(), 0L, + QString::fromLatin1( "membersDock" ), QString::fromLatin1( " " ) ); + m_membersList = new ChatMembersListWidget( m_manager, this, "m_membersList" ); + + membersDock->setWidget( m_membersList ); + + Kopete::ContactPtrList members = m_manager->members(); + + if ( members.first() && members.first()->metaContact() != 0 ) + { + membersStatus = static_cast + ( + members.first()->metaContact()->pluginData + ( m_manager->protocol(), QString::fromLatin1( "MembersListPolicy" ) ).toInt() + ); + } + else + { + membersStatus = Smart; + } + + if( membersStatus == Smart ) + d->visibleMembers = ( m_manager->members().count() > 1 ); + else + d->visibleMembers = ( membersStatus == Visible ); + + placeMembersList( membersDockPosition ); + } +} + +void ChatView::toggleMembersVisibility() +{ + if( membersDock ) + { + d->visibleMembers = !d->visibleMembers; + membersStatus = d->visibleMembers ? Visible : Hidden; + placeMembersList( membersDockPosition ); + Kopete::ContactPtrList members = m_manager->members(); + if ( members.first()->metaContact() ) + { + members.first()->metaContact()->setPluginData( m_manager->protocol(), + QString::fromLatin1( "MembersListPolicy" ), QString::number(membersStatus) ); + } + //refreshView(); + } +} + +void ChatView::placeMembersList( KDockWidget::DockPosition dp ) +{ +// kdDebug(14000) << k_funcinfo << "Members list policy " << membersStatus << +// ", visible " << d->visibleMembers << endl; + + if ( d->visibleMembers ) + { + membersDockPosition = dp; + + // look up the dock width + int dockWidth; + KGlobal::config()->setGroup( QString::fromLatin1( "ChatViewDock" ) ); + + if( membersDockPosition == KDockWidget::DockLeft ) + { + dockWidth = KGlobal::config()->readNumEntry( + QString::fromLatin1( "membersDock,viewDock:sepPos" ), 30); + } + else + { + dockWidth = KGlobal::config()->readNumEntry( + QString::fromLatin1( "viewDock,membersDock:sepPos" ), 70); + } + + // Make sure it is shown then place it wherever + membersDock->setEnableDocking( KDockWidget::DockLeft | KDockWidget::DockRight ); + membersDock->manualDock( viewDock, membersDockPosition, dockWidth ); + membersDock->show(); + membersDock->setEnableDocking( KDockWidget::DockNone ); + } + else + { + // Dock it to the desktop then hide it + membersDock->undock(); + membersDock->hide(); + } + + if( d->isActive ) + m_mainWindow->updateMembersActions(); + + //refreshView(); +} + +void ChatView::remoteTyping( const Kopete::Contact *contact, bool isTyping ) +{ + // Make sure we (re-)add the timer at the end, because the slot will + // remove the first timer + // And yes, the const_cast is a bit ugly, but it's only used as key + // value in this dictionary (no indirections) so it's basically + // harmless. Unfortunately there's no QConstPtrDictionary in Qt... + void *key = const_cast( contact ); + m_remoteTypingMap.remove( key ); + if( isTyping ) + { + m_remoteTypingMap.insert( key, new QTimer(this) ); + connect( m_remoteTypingMap[ key ], SIGNAL( timeout() ), SLOT( slotRemoteTypingTimeout() ) ); + m_remoteTypingMap[ key ]->start( 6000, true ); + } + + // Loop through the map, constructing a string of people typing + QStringList typingList; + QPtrDictIterator it( m_remoteTypingMap ); + + for( ; it.current(); ++it ) + { + Kopete::Contact *c = static_cast( it.currentKey() ); + QString nick; + if( c->metaContact() && c->metaContact() != Kopete::ContactList::self()->myself() ) + { + nick = c->metaContact()->displayName(); + } + else + { + nick = c->nickName(); + } + typingList.append( nick ); + } + + // Update the status area + if( !typingList.isEmpty() ) + { + if ( typingList.count() == 1 ) + setStatusText( i18n( "%1 is typing a message" ).arg( typingList.first() ) ); + else + { + QString statusTyping = typingList.join( QString::fromLatin1( ", " ) ); + setStatusText( i18n( "%1 is a list of names", "%1 are typing a message" ).arg( statusTyping ) ); + } + updateChatState( Typing ); + } + else + { + updateChatState(); + } +} + +void ChatView::setStatusText( const QString &status ) +{ + d->statusText = status; + if ( d->isActive ) + m_mainWindow->setStatus( status ); +} + +const QString& ChatView::statusText() +{ + return d->statusText; +} + +void ChatView::slotChatDisplayNameChanged() +{ + // This fires whenever a contact or MC changes displayName, so only + // update the caption if it changed to avoid unneeded updates that + // could cause flickering + QString chatName = m_manager->displayName(); + if ( chatName != d->captionText ) + setCaption( chatName, true ); +} + +void ChatView::slotPropertyChanged( Kopete::Contact*, const QString &key, + const QVariant& oldValue, const QVariant &newValue ) +{ + if ( key == Kopete::Global::Properties::self()->nickName().key() ) + { + QString newName=newValue.toString(); + QString oldName=oldValue.toString(); + + if(KopetePrefs::prefs()->showEvents()) + if ( oldName != newName && !oldName.isEmpty()) + sendInternalMessage( i18n( "%1 is now known as %2" ). arg( oldName, newName ) ); + } +} + +void ChatView::slotDisplayNameChanged( const QString &oldValue, const QString &newValue ) +{ + if( KopetePrefs::prefs()->showEvents() ) + { + if( oldValue != newValue ) + sendInternalMessage( i18n( "%1 is now known as %2" ). arg( oldValue, newValue ) ); + } +} + +void ChatView::slotContactAdded(const Kopete::Contact *contact, bool suppress) +{ + QString contactName; + // Myself metacontact is not a reliable source. + if( contact->metaContact() && contact->metaContact() != Kopete::ContactList::self()->myself() ) + { + contactName = contact->metaContact()->displayName(); + } + else + { + contactName = contact->nickName(); + } + + if( contact->metaContact() && contact->metaContact() != Kopete::ContactList::self()->myself() ) + { + connect( contact->metaContact(), SIGNAL( displayNameChanged(const QString&, const QString&) ), + this, SLOT( slotDisplayNameChanged(const QString &, const QString &) ) ); + } + else + { + connect( contact, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), + this, SLOT( slotPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ) ) ; + } + + if( !suppress && m_manager->members().count() > 1 ) + sendInternalMessage( i18n("%1 has joined the chat.").arg(contactName) ); + + if( membersStatus == Smart && membersDock ) + { + bool shouldShowMembers = ( m_manager->members().count() > 1); + if( shouldShowMembers != d->visibleMembers ) + { + d->visibleMembers = shouldShowMembers; + placeMembersList( membersDockPosition ); + } + } + + updateChatState(); + emit updateStatusIcon( this ); +} + +void ChatView::slotContactRemoved( const Kopete::Contact *contact, const QString &reason, Kopete::Message::MessageFormat format, bool suppressNotification ) +{ +// kdDebug(14000) << k_funcinfo << endl; + if ( contact != m_manager->myself() ) + { + m_remoteTypingMap.remove( const_cast( contact ) ); + + QString contactName; + if( contact->metaContact() && contact->metaContact() != Kopete::ContactList::self()->myself() ) + { + contactName = contact->metaContact()->displayName(); + } + else + { + contactName = contact->nickName(); + } + + // When the last person leaves, don't disconnect the signals, since we're in a one-to-one chat + if ( m_manager->members().count() > 0 ) + { + if( contact->metaContact() ) + { + disconnect( contact->metaContact(), SIGNAL( displayNameChanged(const QString&, const QString&) ), + this, SLOT( slotDisplayNameChanged(const QString&, const QString&) ) ); + } + else + { + disconnect(contact,SIGNAL(propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & )), + this, SLOT( slotPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ) ) ; + } + } + + if ( !suppressNotification ) + { + if ( reason.isEmpty() ) + sendInternalMessage( i18n( "%1 has left the chat." ).arg( contactName ), format ) ; + else + sendInternalMessage( i18n( "%1 has left the chat (%2)." ).arg( contactName, reason ), format); + } + } + + updateChatState(); + emit updateStatusIcon( this ); +} + +QString& ChatView::caption() const +{ + return d->captionText; +} + +void ChatView::setCaption( const QString &text, bool modified ) +{ +// kdDebug(14000) << k_funcinfo << endl; + QString newCaption = text; + + //Save this caption + d->captionText = text; + + //Turncate if needed + newCaption = KStringHandler::rsqueeze( d->captionText, 20 ); + + //Call the original set caption + KDockMainWindow::setCaption( newCaption, false ); + + emit updateChatTooltip( this, QString::fromLatin1("%1").arg( d->captionText ) ); + emit updateChatLabel( this, newCaption ); + //Blink icon if modified and not active + if( !d->isActive && modified ) + updateChatState( Changed ); + else + updateChatState(); + + //Tell the parent we changed our caption + emit( captionChanged( d->isActive ) ); +} + +void ChatView::appendMessage(Kopete::Message &message) +{ + remoteTyping( message.from(), false ); + + messagePart()->appendMessage(message); + + if( !d->isActive ) + { + switch ( message.importance() ) + { + case Kopete::Message::Highlight: + updateChatState( Highlighted ); + break; + case Kopete::Message::Normal: + if ( message.direction() == Kopete::Message::Inbound ) + { + updateChatState( Message ); + break; + } // if it's an enternal message or a outgoing, fall thought + default: + updateChatState( Changed ); + } + } + + if( message.direction() == Kopete::Message::Inbound ) + { + if( message.from()->metaContact() && message.from()->metaContact() != Kopete::ContactList::self()->myself() ) + { + unreadMessageFrom = message.from()->metaContact()->displayName(); + } + else + { + unreadMessageFrom = message.from()->nickName(); + } + QTimer::singleShot( 1000, this, SLOT( slotMarkMessageRead() ) ); + } + else + unreadMessageFrom = QString::null; +} + +void ChatView::slotMarkMessageRead() +{ + unreadMessageFrom = QString::null; +} + +void ChatView::slotToggleRtfToolbar( bool enabled ) +{ + emit rtfEnabled( this, enabled ); +} + +void ChatView::slotContactStatusChanged( Kopete::Contact *contact, const Kopete::OnlineStatus &newStatus, const Kopete::OnlineStatus &oldStatus ) +{ + kdDebug(14000) << k_funcinfo << contact << endl; + bool inhibitNotification = ( newStatus.status() == Kopete::OnlineStatus::Unknown || + oldStatus.status() == Kopete::OnlineStatus::Unknown ); + if ( contact && KopetePrefs::prefs()->showEvents() && !inhibitNotification ) + { + if ( contact->account() && contact == contact->account()->myself() ) + { + // Separate notification for the 'self' contact + if ( newStatus.status() != Kopete::OnlineStatus::Connecting ) + sendInternalMessage( i18n( "You are now marked as %1." ).arg( newStatus.description() ) ); + } + else if ( !contact->account() || !contact->account()->suppressStatusNotification() ) + { + // Don't send notifications when we just connected ourselves, i.e. when suppressions are still active + if ( contact->metaContact() && contact->metaContact() != Kopete::ContactList::self()->myself() ) + { + sendInternalMessage( i18n( "%2 is now %1." ) + .arg( newStatus.description(), contact->metaContact()->displayName() ) ); + } + else + { + QString nick=contact->nickName(); + sendInternalMessage( i18n( "%2 is now %1." ) + .arg( newStatus.description(), nick ) ); + } + } + } + + // update the windows caption + slotChatDisplayNameChanged(); + emit updateStatusIcon( this ); +} + +void ChatView::sendInternalMessage(const QString &msg, Kopete::Message::MessageFormat format ) +{ + // When closing kopete, some internal message may be sent because some contact are deleted + // these contacts can already be deleted + Kopete::Message message = Kopete::Message( 0L /*m_manager->myself()*/ , 0L /*m_manager->members()*/, msg, Kopete::Message::Internal, format ); + // (in many case, this is useless to set myself as contact) + // TODO: set the contact which initiate the internal message, + // so we can later show a icon of it (for example, when he join a chat) + messagePart()->appendMessage( message ); +} + +void ChatView::sendMessage() +{ + d->sendInProgress = true; + editPart()->sendMessage(); +} + +void ChatView::messageSentSuccessfully() +{ + d->sendInProgress = false; + emit messageSuccess( this ); +} + +void ChatView::saveOptions() +{ + KConfig *config = KGlobal::config(); + + writeDockConfig ( config, QString::fromLatin1( "ChatViewDock" ) ); + config->setGroup( QString::fromLatin1( "ChatViewDock" ) ); + config->writeEntry( QString::fromLatin1( "membersDockPosition" ), membersDockPosition ); + saveChatSettings(); + config->sync(); +} + +void ChatView::saveChatSettings() +{ + Kopete::ContactPtrList contacts = msgManager()->members(); + + if ( contacts.count() == 0 ) + return; + + Kopete::MetaContact* mc = contacts.first()->metaContact(); + + if ( contacts.count() > 1 ) + return; //can't save with more than one person in chatview + + if ( !mc ) + return; + + KConfig* config = KGlobal::config(); + + QString contactListGroup = QString::fromLatin1("chatwindow_") + + mc->metaContactId(); + + config->setGroup( contactListGroup ); + config->writeEntry( "EnableRichText", editPart()->richTextEnabled() ); + config->writeEntry( "EnableAutoSpellCheck", editPart()->autoSpellCheckEnabled() ); + config->sync(); +} + +void ChatView::loadChatSettings() +{ + Kopete::ContactPtrList contacts = msgManager()->members(); + if ( contacts.count() > 1 ) + return; //can't load with more than one other person in the chat + + //read settings for metacontact + QString contactListGroup = QString::fromLatin1("chatwindow_") + + contacts.first()->metaContact()->metaContactId(); + KConfig* config = KGlobal::config(); + config->setGroup( contactListGroup ); + bool enableRichText = config->readBoolEntry( "EnableRichText", true ); + editPart()->slotSetRichTextEnabled( enableRichText ); + emit rtfEnabled( this, editPart()->richTextEnabled() ); + bool enableAutoSpell = config->readBoolEntry( "EnableAutoSpellCheck", false ); + emit autoSpellCheckEnabled( this, enableAutoSpell ); +} + +void ChatView::readOptions() +{ + KConfig *config = KGlobal::config(); + + /** THIS IS BROKEN !!! */ + //dockManager->readConfig ( config, QString::fromLatin1("ChatViewDock") ); + + //Work-around to restore dock widget positions + config->setGroup( QString::fromLatin1( "ChatViewDock" ) ); + + membersDockPosition = static_cast( + config->readNumEntry( QString::fromLatin1( "membersDockPosition" ), KDockWidget::DockRight ) ); + + QString dockKey = QString::fromLatin1( "viewDock" ); + if ( d->visibleMembers ) + { + if( membersDockPosition == KDockWidget::DockLeft ) + dockKey.prepend( QString::fromLatin1( "membersDock," ) ); + else if( membersDockPosition == KDockWidget::DockRight ) + dockKey.append( QString::fromLatin1( ",membersDock" ) ); + } + + dockKey.append( QString::fromLatin1( ",editDock:sepPos" ) ); + //kdDebug(14000) << k_funcinfo << "reading splitterpos from key: " << dockKey << endl; + int splitterPos = config->readNumEntry( dockKey, 70 ); + editDock->manualDock( viewDock, KDockWidget::DockBottom, splitterPos ); + viewDock->setDockSite( KDockWidget::DockLeft | KDockWidget::DockRight ); + editDock->setEnableDocking( KDockWidget::DockNone ); +} + +void ChatView::setActive( bool value ) +{ + d->isActive = value; + if ( d->isActive ) + { + updateChatState( Normal ); + emit( activated( static_cast(this) ) ); + } +} + +void ChatView::slotRemoteTypingTimeout() +{ + // Remove the topmost timer from the list. Why does QPtrDict use void* keys and not typed keys? *sigh* + if ( !m_remoteTypingMap.isEmpty() ) + remoteTyping( reinterpret_cast( QPtrDictIterator(m_remoteTypingMap).currentKey() ), false ); +} + +void ChatView::dragEnterEvent ( QDragEnterEvent * event ) +{ + if( event->provides( "kopete/x-contact" ) ) + { + QStringList lst=QStringList::split( QChar( 0xE000 ) , QString::fromUtf8(event->encodedData ( "kopete/x-contact" )) ); + if(m_manager->mayInvite() && m_manager->protocol()->pluginId() == lst[0] && m_manager->account()->accountId() == lst[1]) + { + QString contact=lst[2]; + + bool found =false; + QPtrList cts=m_manager->members(); + for ( QPtrListIterator it( cts ); it.current(); ++it ) + { + if(it.current()->contactId() == contact) + { + found=true; + break; + } + } + + if(!found && contact != m_manager->myself()->contactId()) + event->accept(); + } + } + else if( event->provides( "kopete/x-metacontact" ) ) + { + QString metacontactID=QString::fromUtf8(event->encodedData ( "kopete/x-metacontact" )); + Kopete::MetaContact *m=Kopete::ContactList::self()->metaContact(metacontactID); + + if( m && m_manager->mayInvite()) + { + QPtrList cts=m->contacts(); + for ( QPtrListIterator it( cts ); it.current(); ++it ) + { + Kopete::Contact *c=it.current(); + if(c && c->account() == m_manager->account()) + { + if( c != m_manager->myself() && !m_manager->members().contains(c) && c->isOnline()) + event->accept(); + } + } + } + } + // make sure it doesn't come from the current chat view - then it's an emoticon + else if ( event->provides( "text/uri-list" ) && m_manager->members().count() == 1 && + ( event->source() != (QWidget*)m_messagePart->view()->viewport() ) ) + { + Kopete::ContactPtrList members = m_manager->members(); + Kopete::Contact *contact = members.first(); + if ( contact && contact->canAcceptFiles() ) + event->accept(); + } + else + KDockMainWindow::dragEnterEvent(event); +} + +void ChatView::dropEvent ( QDropEvent * event ) +{ + if( event->provides( "kopete/x-contact" ) ) + { + QStringList lst=QStringList::split( QChar( 0xE000 ) , QString::fromUtf8(event->encodedData ( "kopete/x-contact" )) ); + if(m_manager->mayInvite() && m_manager->protocol()->pluginId() == lst[0] && m_manager->account()->accountId() == lst[1]) + { + QString contact=lst[2]; + + bool found =false; + QPtrList cts=m_manager->members(); + for ( QPtrListIterator it( cts ); it.current(); ++it ) + { + if(it.current()->contactId() == contact) + { + found=true; + break; + } + } + if(!found && contact != m_manager->myself()->contactId()) + m_manager->inviteContact(contact); + } + } + else if( event->provides( "kopete/x-metacontact" ) ) + { + QString metacontactID=QString::fromUtf8(event->encodedData ( "kopete/x-metacontact" )); + Kopete::MetaContact *m=Kopete::ContactList::self()->metaContact(metacontactID); + if(m && m_manager->mayInvite()) + { + QPtrList cts=m->contacts(); + for ( QPtrListIterator it( cts ); it.current(); ++it ) + { + Kopete::Contact *c=it.current(); + if(c && c->account() == m_manager->account() && c->isOnline()) + { + if( c != m_manager->myself() && !m_manager->members().contains(c) ) + m_manager->inviteContact(c->contactId()); + } + } + } + } + else if ( event->provides( "text/uri-list" ) && m_manager->members().count() == 1 ) + { + Kopete::ContactPtrList members = m_manager->members(); + Kopete::Contact *contact = members.first(); + + if ( !contact || !contact->canAcceptFiles() || !QUriDrag::canDecode( event ) ) + { + event->ignore(); + return; + } + + KURL::List urlList; + KURLDrag::decode( event, urlList ); + + for ( KURL::List::Iterator it = urlList.begin(); it != urlList.end(); ++it ) + { + if ( (*it).isLocalFile() ) + { //send a file + contact->sendFile( *it ); + } + else + { //this is a URL, send the URL in a message + addText( (*it).url() ); + } + } + event->acceptAction(); + return; + } + else + KDockMainWindow::dropEvent(event); + +} + +void ChatView::registerContextMenuHandler( QObject *target, const char* slot ) +{ + connect( m_messagePart, + SIGNAL( contextMenuEvent( Kopete::Message &, const QString &, KPopupMenu * ) ), + target, + slot + ); +} + +void ChatView::registerTooltipHandler( QObject *target, const char* slot ) +{ + connect( m_messagePart, + SIGNAL( tooltipEvent( Kopete::Message &, const QString &, QString & ) ), + target, + slot + ); +} + +#include "chatview.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/chatview.h b/kopete/kopete/chatwindow/chatview.h new file mode 100644 index 00000000..ef10320b --- /dev/null +++ b/kopete/kopete/chatwindow/chatview.h @@ -0,0 +1,421 @@ +/* + chatview.h - Chat View + + Copyright (c) 2002-2004 by Olivier Goffart + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef CHATVIEW_H +#define CHATVIEW_H + +#include "kopeteview.h" +#include "kopeteviewplugin.h" +#include +#include // for covariant return type of editWidget +#include + +class QTimer; + +class ChatTextEditPart; +class ChatMembersListWidget; +class ChatMessagePart; + +class KopeteChatWindow; + +class KTabWidget; + +class KopeteChatViewPrivate; +class ChatWindowPlugin; + +namespace KParts +{ + class Part; +} + +namespace Kopete +{ + class Contact; + class ChatSession; +} + +/** + * @author Olivier Goffart + */ +class ChatView : public KDockMainWindow, public KopeteView +{ + Q_OBJECT +public: + ChatView( Kopete::ChatSession *manager, ChatWindowPlugin *parent, const char *name = 0 ); + ~ChatView(); + + /** the state of our chat */ + enum KopeteTabState { Normal, Highlighted, Changed, Typing, Message, Undefined }; + + ChatMembersListWidget *membersList() const { return m_membersList; } + ChatMessagePart *messagePart() const { return m_messagePart; } + ChatTextEditPart *editPart() const { return m_editPart; } + + /** + * Adds text into the edit area. Used when you select an emoticon + * @param text The text to be inserted + */ + void addText( const QString &text ); + + /** + * Saves window settings such as splitter positions + */ + void saveOptions(); + + /** + * Tells this view it is the active view + */ + void setActive( bool value ); + + /** + * save the chat settings (rich text, auto spelling) + */ + void saveChatSettings(); + + /** + * read the chat settings (rich text, auto spelling) + */ + void loadChatSettings(); + + /** + * Clears the chat buffer + * + * Reimplemented from KopeteView + */ + virtual void clear(); + + /** + * Sets the text to be displayed on tab label and window caption + */ + void setCaption( const QString &text, bool modified ); + + /** + * Changes the pointer to the chat window. Used to re-parent the view + * @param parent The new chat window + */ + void setMainWindow( KopeteChatWindow* parent ); + + /** + * Returns the message currently in the edit area + * @return The Kopete::Message object for the message + * + * Reimplemented from KopeteView + */ + virtual Kopete::Message currentMessage(); + + /** + * Sets the current message in the chat window + * @param parent The new chat window + * + * Reimplemented from KopeteView + */ + virtual void setCurrentMessage( const Kopete::Message &newMessage ); + + /** + * Sets the placement of the chat members list. + * DockLeft, DockRight, or DockNone. + * @param dp The dock position of the list + */ + void placeMembersList( KDockWidget::DockPosition dp = KDockWidget::DockRight ); + + /** + * Shows or hides the chat members list + */ + void toggleMembersVisibility(); + + /** + * Returns the chat window this view is in + * @return The chat window + */ + KopeteChatWindow *mainWindow() const { return m_mainWindow; } + + /** + * Returns the current position of the chat member slist + * @return The position of the chat members list + */ + const KDockWidget::DockPosition membersListPosition() { return membersDockPosition; } + + /** + * Returns whether or not the chat member list is visible + * @return Is the chat member list visible? + */ + bool visibleMembersList(); + + const QString &statusText(); + + QString &caption() const; + + bool sendInProgress(); + + /** Reimplemented from KopeteView **/ + virtual void raise( bool activate=false ); + + /** Reimplemented from KopeteView **/ + virtual void makeVisible(); + + /** Reimplemented from KopeteView **/ + virtual bool isVisible(); + + /** Reimplemented from KopeteView **/ + virtual QWidget *mainWidget(); + + KTextEdit *editWidget(); + + bool canSend(); + + /** Reimplemented from KopeteView **/ + virtual void registerContextMenuHandler( QObject *target, const char* slot ); + + /** Reimplemented from KopeteView **/ + virtual void registerTooltipHandler( QObject *target, const char* slot ); + +public slots: + /** + * Initiates a cut action on the edit area of the chat view + */ + void cut(); + + /** + * Initiates a copy action + * If there is text selected in the HTML view, that text is copied + * Otherwise, the entire edit area is copied. + */ + void copy(); + + /** + * Initiates a paste action into the edit area of the chat view + */ + void paste(); + + void nickComplete(); + + /** + * Sets the foreground color of the entry area, and outgoing messages + * @param newColor The new foreground color. If this is QColor(), then + * a color chooser dialog is opened + */ + void setFgColor( const QColor &newColor = QColor() ); + + /** + * Sets the font of the edit area and outgoing messages to the specified value. + * @param newFont The new font to use. + */ + void setFont( const QFont &newFont ); + + /** + * show a Font dialog and set the font selected by the user + */ + void setFont(); + + /** + * Get the font used in the format toolbar for Rich Text formatting + */ + QFont font(); + + /** + * Sets the background color of the entry area, and outgoing messages + * @param newColor The new background color. If this is QColor(), then + * a color chooser dialog is opened + */ + void setBgColor( const QColor &newColor = QColor() ); + + /** + * Sends the text currently entered into the edit area + */ + virtual void sendMessage(); + + /** + * Called when a message is received from someone + * @param message The message received + */ + virtual void appendMessage( Kopete::Message &message ); + + /** + * Called when a typing event is received from a contact + * Updates the typing map and outputs the typing message into the status area + * @param contact The contact who is / isn't typing + * @param typing If the contact is typing now + */ + void remoteTyping( const Kopete::Contact *contact, bool typing ); + + /** + * Sets the text to be displayed on the status label + * @param text The text to be displayed + */ + void setStatusText( const QString &text ); + + /** Reimplemented from KopeteView **/ + virtual void messageSentSuccessfully(); + + virtual bool closeView( bool force = false ); + +signals: + /** + * Emitted when a message is sent + * @param message The message sent + */ + void messageSent( Kopete::Message & ); + + void messageSuccess( ChatView* ); + + /** + * Emits when the chat view is shown + */ + void shown(); + + void closing( KopeteView* ); + + void activated( KopeteView* ); + + void captionChanged( bool active ); + + void updateStatusIcon( ChatView* ); + + /** Emitted when a possible tab tooltip needs updating */ + void updateChatTooltip( ChatView*, const QString& ); + + /** Emitted when the state of the chat changes */ + void updateChatState( ChatView*, int ); + + /** Emitted when a possible tab label needs updating */ + void updateChatLabel( ChatView*, const QString& ); + + /** + * Our send-button-enabled flag has changed + */ + void canSendChanged(bool); + + /** + * Emitted when we re-parent ourselves with a new window + */ + void windowCreated(); + + /** + * Emitted when the state of RTF has changed + */ + void rtfEnabled( ChatView*, bool ); + + void autoSpellCheckEnabled( ChatView*, bool ); + +private slots: + void slotRemoteTypingTimeout(); + /** + * Show that a contact changed his nickname when a metacontact is not avaiable. + */ + void slotPropertyChanged( Kopete::Contact *contact, const QString &key, const QVariant &oldValue, const QVariant &newValue ); + + /** + * Called when a contact is added to the chat session. + * Adds this contact to the typingMap and the contact list view + * @param c The contact that joined the chat + * @param suppress mean that no notifications are showed + */ + void slotContactAdded( const Kopete::Contact *c, bool suppress ); + + /** + * Called when a contact is removed from the chat session. Updates the tab state and status icon, + * displays a notification message and performs some cleanup. + * @param c The contact left the chat + * @param reason is the reason the contact left + * @param format The format of the reason message + * @param suppressNotification mean that no notifications are showed + */ + void slotContactRemoved( const Kopete::Contact *c, const QString& reason, Kopete::Message::MessageFormat format, bool suppressNotification=false ); + + /** + * Called when a contact changes status, updates the display name, status icon and tab bar state. + * If the user isn't changing to/from an Unknown status, will also display a message in the chatwindow. + * @param contact The contact who changed status + * @param status The new status of the contact + * @param oldstatus The former status of the contact + */ + void slotContactStatusChanged( Kopete::Contact *contact, const Kopete::OnlineStatus &status, const Kopete::OnlineStatus &oldstatus ); + + /** + * Called when the chat's display name is changed + */ + void slotChatDisplayNameChanged(); + + void slotMarkMessageRead(); + + void slotToggleRtfToolbar( bool enabled ); + + /** + * Show that a (meta)contact change his display name. + */ + void slotDisplayNameChanged(const QString &oldValue, const QString &newValue); + +protected: + virtual void dragEnterEvent ( QDragEnterEvent * ); + virtual void dropEvent ( QDropEvent * ); + +private: + // widget stuff + KopeteChatWindow *m_mainWindow; + + KDockWidget *viewDock; + ChatMessagePart *m_messagePart; + + KDockWidget *membersDock; + ChatMembersListWidget *m_membersList; + + KDockWidget *editDock; + ChatTextEditPart *m_editPart; + + KopeteTabState m_tabState; + + // position and visibility of the chat member list + KDockWidget::DockPosition membersDockPosition; + enum MembersListPolicy { Smart = 0, Visible = 1, Hidden = 2 }; + MembersListPolicy membersStatus; + + // miscellany + QPtrDict m_remoteTypingMap; + QString unreadMessageFrom; + QString m_status; + + void updateChatState( KopeteTabState state = Undefined ); + + /** + * Creates the members list widget + */ + void createMembersList(); + + /** + * Read in saved options, such as splitter positions + */ + void readOptions(); + + void sendInternalMessage( const QString &msg, Kopete::Message::MessageFormat format = Kopete::Message::PlainText ); + + KopeteChatViewPrivate *d; +}; + +/** + * This is the class that makes the chatwindow a plugin + */ +class ChatWindowPlugin : public Kopete::ViewPlugin +{ + public: + ChatWindowPlugin(QObject *parent, const char *name, const QStringList &args); + KopeteView* createView( Kopete::ChatSession *manager ); +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/chatwindow.desktop b/kopete/kopete/chatwindow/chatwindow.desktop new file mode 100644 index 00000000..fa1b4f92 --- /dev/null +++ b/kopete/kopete/chatwindow/chatwindow.desktop @@ -0,0 +1,126 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kopete/Plugin +X-Kopete-Version=1000900 +X-KDE-Library=kopete_chatwindow +X-KDE-PluginInfo-Author=Kopete Developers +X-KDE-PluginInfo-Email=kopete-devel@kde.org +X-KDE-PluginInfo-Name=kopete_chatwindow +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Views +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true +Name=Kopete Chat Window +Name[ar]=ناÙذة محادثة Kopete +Name[be]=Вакно гутаркі Kopete +Name[bg]=Прозорец за разговори +Name[bn]=কপেট চà§à¦¯à¦¾à¦Ÿ উইনà§à¦¡à§‹ +Name[br]=Prenestr flapañ Kopete +Name[bs]=Kopete chat prozor +Name[ca]=Finestra de xat del Kopete +Name[cs]=Okno rozhovoru Kopete +Name[cy]=Ffenestr Sgwrs Kopete +Name[da]=Kopete Chat-vindue +Name[de]=Kopete-Chat-Fenster +Name[el]=ΠαÏάθυÏο συνομιλίας Kopete +Name[es]=Ventana de charla de Kopete +Name[et]=Kopete vestlusaken +Name[eu]=Kopete elkarrizketa leihoa +Name[fa]=پنجرۀ Ú¯Ù¾ Kopete +Name[fi]=Kopeten keskusteluikkuna +Name[fr]=Fenêtre de discussion de Kopete +Name[ga]=Fuinneog Chomhrá Kopete +Name[gl]=Fiestra de conversa de Kopete +Name[he]=חלון שיחה של Kopete +Name[hi]=के-ऑपà¥à¤Ÿà¥€ गपशप विंडो +Name[hr]=Kopeteov prozor za razgovor +Name[hu]=Kopete csevegési ablak +Name[is]=Spjallgluggi Kopete +Name[it]=Finestra di chat di Kopete +Name[ja]=Kopete ãƒãƒ£ãƒƒãƒˆã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ +Name[ka]=Kopete სáƒáƒ£áƒ‘რის ფáƒáƒœáƒ¯áƒáƒ áƒ +Name[kk]=Kopete әңгіме-дүкен терезеÑÑ– +Name[km]=បង្អួច​ជជែក​កំសាន្ហKopete +Name[lt]=Kopete pokalbių langas +Name[mk]=Прозорец за муабет од Kopete +Name[nb]=Kopete pratevindu +Name[nds]=Kopete-Klöönfinster +Name[ne]=कोपेट कà¥à¤°à¤¾à¤•à¤¾à¤¨à¥€ सञà¥à¤à¥à¤¯à¤¾à¤² +Name[nl]=Kopete gespreksvenster +Name[nn]=Kopete pratevindauge +Name[pa]=ਕੋਪੀਟੀ ਗੱਲਾਂਬਾਤਾਂ +Name[pl]=Okno rozmowy Kopete +Name[pt]=Janela de Conversão do Kopete +Name[pt_BR]=Janela de Bate-papo do Kopete +Name[ro]=Fereastră de discuÅ£ii Kopete +Name[ru]=Разговор +Name[se]=Kopete Äáttenláse +Name[sk]=Okno rozhovoru Kopete +Name[sl]=Okno za klepet v Kopete +Name[sr]=Kopete-ов прозор за ћаÑкање +Name[sr@Latn]=Kopete-ov prozor za ćaskanje +Name[sv]=Kopete-chattfönster +Name[ta]=Kopete அரடà¯à®Ÿà¯ˆ சாளரம௠+Name[tg]=Тирезаи Чати Kopete +Name[tr]=Kopete Sohbet Penceresi +Name[uk]=Вікно розмови Kopete +Name[zh_CN]=Kopete èŠå¤©çª—å£ +Name[zh_HK]=Kopete èŠå¤©è¦–窗 +Name[zh_TW]=Kopete èŠå¤©è¦–窗 +Comment=The default Kopete chat window +Comment[ar]=ناÙذة محادثة Kopete ألاÙتراضية +Comment[be]=Прадвызначанае вакно гутаркі Kopete +Comment[bg]=Стандартен прозорец за разговори +Comment[bn]=ডিফলà§à¦Ÿ কপেট চà§à¦¯à¦¾à¦Ÿ উইনà§à¦¡à§‹ +Comment[bs]=Osnovni Kopete chat prozor +Comment[ca]=La finestra de xat per omissió del Kopete +Comment[cs]=Výchozí okno Kopete pro rozhovor +Comment[cy]=Y ffenestr sgwrs Kopete rhagosod +Comment[da]=Kopete's standard-chatvindue +Comment[de]=Das übliche Chat-Fenster für Kopete +Comment[el]=Το Ï€ÏοκαθοÏισμένο παÏάθυÏο συνομιλίας του Kopete +Comment[es]=La ventana de charla predeterminada de Kopete +Comment[et]=Kopete vaikimisi vestlusaken +Comment[eu]=Kopete elkarrizketa leiho lehenetsia +Comment[fa]=پنجرۀ پیش‌Ùرض Ú¯Ù¾ Kopete +Comment[fi]=Kopeten oletuskeskusteluikkuna +Comment[fr]=La fenêtre de discussion par défaut de Kopete +Comment[ga]=Fuinneog chomhrá réamhshocruithe Kopete +Comment[gl]=A fiestra de conversa por defecto de Kopete +Comment[he]=ברירת מחדל עבור חלון השיחה של Kopete +Comment[hi]=डिफ़ॉलà¥à¤Ÿ के-ऑपà¥à¤Ÿà¥€ गपशप विंडो +Comment[hr]=UobiÄajeni Kopeteov prozor za razgovor +Comment[hu]=Az alapértelmezett Kopete csevegési ablak +Comment[is]=Sjálfgefni spjallgluggi Kopete +Comment[it]=La finestra di chat predefinita di Kopete +Comment[ja]=Kopete ã®æ¨™æº–ãƒãƒ£ãƒƒãƒˆã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ +Comment[ka]=Kopeteს ნáƒáƒ’ულისხმები სáƒáƒ£áƒ‘რის ფáƒáƒœáƒ¯áƒáƒ áƒ +Comment[kk]=Әдетті Kopete әңгіме-дүкен терезеÑÑ– +Comment[km]=បង្អួច​ជជែក​កំសាន្ážâ€‹áž›áŸ†áž“ាំ​ដើម​របស់ Kopete +Comment[lt]=Numatytas Kopete pokalbių langas +Comment[mk]=Почетниот прозорец за муабет од Kopete +Comment[nb]=Standardvinduet for Kopete nettprat +Comment[nds]=Dat Standardklöönfinster vun Kopete +Comment[ne]=पूरà¥à¤µà¤¾à¤¨à¤¿à¤°à¥à¤§à¤¾à¤°à¤¿à¤¤ कोपेट कà¥à¤°à¤¾à¤•à¤¾à¤¨à¥€ सञà¥à¤à¥à¤¯à¤¾à¤² +Comment[nl]=Het standaard Kopete gespreksvenster +Comment[nn]=Standardvindauget for nettprat i Kopete +Comment[pl]=DomyÅ›lne okno rozmowy Kopete +Comment[pt]=A janela de conversação por omissão do Kopete +Comment[pt_BR]=A janela de bate-papo padrão do Kopete +Comment[ro]=Fereastra de discuÅ£ii implicită Kopete +Comment[ru]=Разговор +Comment[se]=Standárda Kopete-Äáttenláse +Comment[sk]=Å tandardné okno rozhovoru pre Kopete +Comment[sl]=Privzeto okno za klepet v Kopete +Comment[sr]=Подразумевани Kopete-ов прозор за ћаÑкање +Comment[sr@Latn]=Podrazumevani Kopete-ov prozor za ćaskanje +Comment[sv]=Kopetes vanliga chattfönster +Comment[ta]=à®®à¯à®©à¯à®©à®¿à®°à¯à®ªà¯à®ªà¯ செயறà¯à®ªà®Ÿà¯à®Ÿà¯ˆà®ªà¯ பலகக௠கà¯à®±à¯à®¨à®¿à®°à®²à¯ +Comment[tg]=Тирезаи Чат бо нобаёнии Kopete +Comment[tr]=Varsayılan Kopete Sohbet Penceresi +Comment[uk]=Типове вікно розмови Kopete +Comment[zh_CN]=默认的 Kopete èŠå¤©çª—å£ +Comment[zh_HK]=Kopete é è¨­çš„èŠå¤©è¦–窗 +Comment[zh_TW]=é è¨­ Kopete èŠå¤©è¦–窗 diff --git a/kopete/kopete/chatwindow/emailwindow.desktop b/kopete/kopete/chatwindow/emailwindow.desktop new file mode 100644 index 00000000..ee71aea9 --- /dev/null +++ b/kopete/kopete/chatwindow/emailwindow.desktop @@ -0,0 +1,111 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kopete/Plugin +X-Kopete-Version=1000900 +X-KDE-Library=kopete_emailwindow +X-KDE-PluginInfo-Author=Kopete Developers +X-KDE-PluginInfo-Email=kopete-devel@kde.org +X-KDE-PluginInfo-Name=kopete_emailwindow +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Views +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true +Name=Kopete Email Window +Name[be]=Вакно Ñлектроннай пошты Kopete +Name[bg]=Прозорец за е-поща +Name[bn]=কপেট ই-মেইল উইনà§à¦¡à§‹ +Name[br]=Prenestr postel Kopete +Name[bs]=Kopete e-mail prozor +Name[ca]=Finestra de correu-e del Kopete +Name[cs]=Emailové okno Kopete +Name[da]=Kopete's e-mail-vindue +Name[de]=Kopete-E-Mail-Fenster +Name[el]=ΠαÏάθυÏο Email του Kopete +Name[es]=Ventana de correo de Kopete +Name[et]=Kopete e-posti aken +Name[eu]=Kopete elkarrizketa leihoa +Name[fa]=پنجرۀ رایانامۀ Kopete +Name[fi]=Kopeten sähköposti-ikkuna +Name[fr]=Fenêtre de courrier électronique Kopete +Name[ga]=Fuinneog R-Phoist Kopete +Name[gl]=Fiestra de Correo-e De Kopete +Name[he]=חלון דו×"ל של Kopete +Name[hu]=Kopete levelezési ablak +Name[is]=Kopete póstgluggi +Name[it]=Finestra dei messaggi di Kopete +Name[ja]=Kopete Eメールウィンドウ +Name[ka]=Kopeteს ელფáƒáƒ¡áƒ¢áƒ ფáƒáƒœáƒ¯áƒáƒ áƒ +Name[kk]=Kopete Ñл.пошта терезеÑÑ– +Name[km]=បង្អួច​អ៊ីមែល Kopete +Name[lt]=Kopete el. paÅ¡to langas +Name[nb]=Kopete e-postvindu +Name[nds]=Kopete-Nettpostfinster +Name[ne]=कोपेट इमेल सञà¥à¤à¥à¤¯à¤¾à¤² +Name[nl]=Kopete e-mailvenster +Name[nn]=Kopete e-postvindauge +Name[pa]=ਕੋਪੀਟੀ ਈ-ਮੇਲ à¨à¨°à©‹à¨–ਾ +Name[pl]=Okno e-mailowe Kopete +Name[pt]=Janela de E-mail do Kopete +Name[pt_BR]=Janela de Mensagens do Kopete +Name[ro]=Fereastră de e-mail Kopete +Name[ru]=Отдельные ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ +Name[sk]=Kopete okno poÅ¡ty +Name[sl]=Okno za e-poÅ¡to v Kopete +Name[sr]=Kopete-ов прозор за е-пошту +Name[sr@Latn]=Kopete-ov prozor za e-poÅ¡tu +Name[sv]=Kopete e-postfönster +Name[tr]=Kopete e-posta Penceresi +Name[uk]=Вікно ел. пошти Kopete +Name[zh_CN]=Kopete 电å­é‚®ä»¶çª—å£ +Name[zh_HK]=Kopete 電郵視窗 +Name[zh_TW]=Kopete é›»å­éƒµä»¶è¦–窗 +Comment=The Kopete email window +Comment[be]=Вакно Ñлектроннай пошты Kopete +Comment[bg]=Прозорец за изпращане на е-поща +Comment[bn]=কপেট ই-মেইল উইনà§à¦¡à§‹ +Comment[br]=Prenestr postel Kopete +Comment[bs]=Kopete e-mail prozor +Comment[ca]=La finestra de correu-e del Kopete +Comment[cs]=Emailové okno Kopete +Comment[da]=Kopete's e-mail-vindue +Comment[de]=Das E-Mail-Fenster für Kopete +Comment[el]=Το παÏάθυÏο email του Kopete +Comment[es]=La ventana de correo de Kopete +Comment[et]=Kopete e-posti aken +Comment[eu]=Kopete elkarrizketa leiho lehenetsia +Comment[fa]=پنجرۀ رایانامۀ Kopete +Comment[fi]=Kopeten sähköposti-ikkuna +Comment[fr]=La fenêtre de courrier électronique de Kopete +Comment[ga]=An fhuinneog r-phoist Kopete +Comment[gl]=A fiestra de correo-e de Kopete +Comment[he]=ברירת מחדל עבור חלון הדו×"ל של Kopete +Comment[hu]=A Kopete levelezési ablaka +Comment[is]=Póstgluggi Kopete +Comment[it]=La finestra dei messaggi di Kopete +Comment[ja]=Kopete Eメールウィンドウ +Comment[ka]=Kopeteს ელფáƒáƒ¡áƒ¢áƒ˜áƒ¡ ფáƒáƒœáƒ¯áƒáƒ áƒ +Comment[kk]=Kopete Ñл.пошта терезеÑÑ– +Comment[km]=បង្អួច​អ៊ីមែល​របស់ Kopete +Comment[lt]=Kopete el. paÅ¡to langas +Comment[nb]=Kopetes e-postvindu +Comment[nds]=Dat Kopete-Nettpostfinster +Comment[ne]=कोपेट इमेल सञà¥à¤à¥à¤¯à¤¾à¤² +Comment[nl]=Het standaard Kopete gespreksvenster +Comment[nn]=E-postvindauget i Kopete +Comment[pl]=Okno e-maila w Kopete +Comment[pt]=A janela de e-mail do Kopete +Comment[pt_BR]=A janela de mensagens do Kopete +Comment[ro]=Fereastra de e-mail Kopete +Comment[ru]=Отдельные ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ +Comment[sk]=Okno poÅ¡ty pre Kopete +Comment[sl]=Okno za e-poÅ¡to v Kopete +Comment[sr]=Подразумевани Kopete-ов прозор за е-пошту +Comment[sr@Latn]=Podrazumevani Kopete-ov prozor za e-poÅ¡tu +Comment[sv]=Kopetes e-postfönster +Comment[tr]=Kopete e-posta Penceresi +Comment[uk]=Вікно ел. пошти Kopete +Comment[zh_CN]=Kopete 电å­é‚®ä»¶çª—å£ +Comment[zh_HK]=Kopete 電郵視窗 +Comment[zh_TW]=Kopete é›»å­éƒµä»¶è¦–窗 diff --git a/kopete/kopete/chatwindow/emoticonselector.cpp b/kopete/kopete/chatwindow/emoticonselector.cpp new file mode 100644 index 00000000..e6802b45 --- /dev/null +++ b/kopete/kopete/chatwindow/emoticonselector.cpp @@ -0,0 +1,141 @@ +/* + emoticonselector.cpp + + a button that pops up a list of all emoticons and returns + the emoticon-string if one is selected in the list + + Copyright (c) 2002 by Stefan Gehn + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "emoticonselector.h" +#include "kopeteemoticons.h" + +#include + +#include +#include +#include +#include +#include + +#include + +EmoticonLabel::EmoticonLabel(const QString &emoticonText, const QString &pixmapPath, QWidget *parent, const char *name) + : QLabel(parent,name) +{ + mText = emoticonText; + setMovie( QMovie(pixmapPath) ); + setAlignment(Qt::AlignCenter); + QToolTip::add(this,emoticonText); + // Somehow QLabel doesn't tell a reasonable size when you use setMovie + // although it does it correctly for setPixmap. Therefore here is a little workaround + // to tell our minimum size. + QPixmap p(pixmapPath); + // + // Some of the custom icons are rather large + // so lets limit them to a maximum size for this display panel + // + if (p.width() > 32 || p.height() > 32) + p.resize(32, 32); + setMinimumSize(p.size()); +} + +void EmoticonLabel::mouseReleaseEvent(QMouseEvent*) +{ + emit clicked(mText); +} + +EmoticonSelector::EmoticonSelector(QWidget *parent, const char *name) + : QWidget(parent, name) +{ +// kdDebug(14000) << k_funcinfo << "called." << endl; + lay = 0L; +} + +void EmoticonSelector::prepareList(void) +{ +// kdDebug(14000) << k_funcinfo << "called." << endl; + int row = 0; + int col = 0; + QMap list = Kopete::Emoticons::self()->emoticonAndPicList(); + int emoticonsPerRow = static_cast(sqrt(list.count())); + //kdDebug(14000) << "emoticonsPerRow=" << emoticonsPerRow << endl; + + if ( lay ) + { + QObjectList *objList = queryList( "EmoticonLabel" ); + //kdDebug(14000) << k_funcinfo << "There are " << objList->count() << " EmoticonLabels to delete." << endl; + objList->setAutoDelete(true); + objList->clear(); + delete objList; + delete lay; + } + + lay = new QGridLayout(this, 0, 0, 4, 4, "emoticonLayout"); + movieList.clear(); + for (QMap::const_iterator it = list.constBegin(); it != list.constEnd(); ++it ) + { + QWidget *w = new EmoticonLabel(it.data().first(), it.key(), this); + movieList.push_back( ((QLabel*)w)->movie() ); + connect(w, SIGNAL(clicked(const QString&)), this, SLOT(emoticonClicked(const QString&))); +// kdDebug(14000) << "adding Emoticon to row=" << row << ", col=" << col << "." << endl; + lay->addWidget(w, row, col); + if ( col == emoticonsPerRow ) + { + col = 0; + row++; + } + else + col++; + } + resize(minimumSizeHint()); +} + +void EmoticonSelector::emoticonClicked(const QString &str) +{ +// kdDebug(14000) << "selected emoticon '" << str << "'" << endl; + // KDE4/Qt TODO: use qobject_cast instead. + emit ItemSelected ( str ); + if ( isVisible() && parentWidget() && + parentWidget()->inherits("QPopupMenu") ) + { + parentWidget()->close(); + } +} + +void EmoticonSelector::hideEvent( QHideEvent* ) +{ + kdDebug( 14000 ) << k_funcinfo << endl; + MovieList::iterator it; + for( it = movieList.begin(); it != movieList.end(); ++it ) + { + (*it)->pause(); + } +} + +void EmoticonSelector::showEvent( QShowEvent* ) +{ + kdDebug( 14000 ) << k_funcinfo << endl; + MovieList::iterator it; + for( it = movieList.begin(); it != movieList.end(); ++it ) + { + (*it)->unpause(); + } +} + +#include "emoticonselector.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/emoticonselector.h b/kopete/kopete/chatwindow/emoticonselector.h new file mode 100644 index 00000000..7d7b4842 --- /dev/null +++ b/kopete/kopete/chatwindow/emoticonselector.h @@ -0,0 +1,76 @@ +/* + emoticonselector.h + + a button that pops up a list of all emoticons and returns + the emoticon-string if one is selected in the list + + Copyright (c) 2002 by Stefan Gehn + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef __emoticonselector_h__ +#define __emoticonselector_h__ + +#include +#include +class QGridLayout; +class QHideEvent; +class QShowEvent; + +class EmoticonLabel : public QLabel +{ + Q_OBJECT + +public: + EmoticonLabel(const QString &emoticonText, const QString &pixmapPath, QWidget *parent=0, const char *name=0); +// ~EmoticonLabel(); + +signals: + void clicked(const QString &text); + +protected: + void mouseReleaseEvent(QMouseEvent*); + QString mText; +}; + +class EmoticonSelector : public QWidget +{ + Q_OBJECT + +public: + + EmoticonSelector ( QWidget *parent = 0, const char *name = 0 ); +// ~EmoticonSelector(); + + typedef QValueList MovieList; +signals: + /** + * gets emitted when an emoticon has been selected from the list + * the QString holds the emoticon as a string or is 0L if nothing was selected + **/ + void ItemSelected(const QString &); + +public slots: + void prepareList(); + +protected: + virtual void hideEvent( QHideEvent* ); + virtual void showEvent( QShowEvent* ); + MovieList movieList; + QGridLayout *lay; + +protected slots: + void emoticonClicked(const QString &); +}; + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/chatwindow/kopetechatwindow.cpp b/kopete/kopete/chatwindow/kopetechatwindow.cpp new file mode 100644 index 00000000..2af1426d --- /dev/null +++ b/kopete/kopete/chatwindow/kopetechatwindow.cpp @@ -0,0 +1,1280 @@ +/* + kopetechatwindow.cpp - Chat Window + + Copyright (c) 2002-2006 by Olivier Goffart + Copyright (c) 2003-2004 by Richard Smith + Copyright (C) 2002 by James Grant + Copyright (c) 2002 by Stefan Gehn + Copyright (c) 2002-2004 by Martijn Klingens + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "chatmessagepart.h" +#include "chattexteditpart.h" +#include "chatview.h" +#include "kopeteapplication.h" +#include "kopetechatwindow.h" +#include "kopeteemoticonaction.h" +#include "kopetegroup.h" +#include "kopetechatsession.h" +#include "kopetemetacontact.h" +#include "kopetepluginmanager.h" +#include "kopeteprefs.h" +#include "kopeteprotocol.h" +#include "kopetestdaction.h" +#include "kopeteviewmanager.h" + +#include +#include + +typedef QMap AccountMap; +typedef QMap GroupMap; +typedef QMap MetaContactMap; +typedef QPtrList WindowList; + +namespace +{ + AccountMap accountMap; + GroupMap groupMap; + MetaContactMap mcMap; + WindowList windows; +} + +KopeteChatWindow *KopeteChatWindow::window( Kopete::ChatSession *manager ) +{ + bool windowCreated = false; + KopeteChatWindow *myWindow; + + //Take the first and the first? What else? + Kopete::Group *group = 0L; + Kopete::ContactPtrList members = manager->members(); + Kopete::MetaContact *metaContact = members.first()->metaContact(); + + if ( metaContact ) + { + Kopete::GroupList gList = metaContact->groups(); + group = gList.first(); + } + + switch( KopetePrefs::prefs()->chatWindowPolicy() ) + { + case GROUP_BY_ACCOUNT: //Open chats from the same protocol in the same window + if( accountMap.contains( manager->account() ) ) + myWindow = accountMap[ manager->account() ]; + else + windowCreated = true; + break; + + case GROUP_BY_GROUP: //Open chats from the same group in the same window + if( group && groupMap.contains( group ) ) + myWindow = groupMap[ group ]; + else + windowCreated = true; + break; + + case GROUP_BY_METACONTACT: //Open chats from the same metacontact in the same window + if( mcMap.contains( metaContact ) ) + myWindow = mcMap[ metaContact ]; + else + windowCreated = true; + break; + + case GROUP_ALL: //Open all chats in the same window + if( windows.isEmpty() ) + windowCreated = true; + else + { + //Here we are finding the window with the most tabs and + //putting it there. Need this for the cases where config changes + //midstream + + int viewCount = -1; + for ( KopeteChatWindow *thisWindow = windows.first(); thisWindow; thisWindow = windows.next() ) + { + if( thisWindow->chatViewCount() > viewCount ) + { + myWindow = thisWindow; + viewCount = thisWindow->chatViewCount(); + } + } + } + break; + + case NEW_WINDOW: //Open every chat in a new window + default: + windowCreated = true; + break; + } + + if ( windowCreated ) + { + myWindow = new KopeteChatWindow(); + + if ( !accountMap.contains( manager->account() ) ) + accountMap.insert( manager->account(), myWindow ); + + if ( !mcMap.contains( metaContact ) ) + mcMap.insert( metaContact, myWindow ); + + if ( group && !groupMap.contains( group ) ) + groupMap.insert( group, myWindow ); + } + +// kdDebug( 14010 ) << k_funcinfo << "Open Windows: " << windows.count() << endl; + + return myWindow; +} + +KopeteChatWindow::KopeteChatWindow( QWidget *parent, const char* name ) + : KParts::MainWindow( parent, name ) +{ + m_activeView = 0L; + m_popupView = 0L; + backgroundFile = 0L; + updateBg = true; + m_tabBar = 0L; + + initActions(); + + QVBox *vBox = new QVBox( this ); + vBox->setLineWidth( 0 ); + vBox->setSpacing( 0 ); + vBox->setFrameStyle( QFrame::NoFrame ); + // set default window size. This could be removed by fixing the size hints of the contents + resize( 500, 500 ); + setCentralWidget( vBox ); + + mainArea = new QFrame( vBox ); + mainArea->setLineWidth( 0 ); + mainArea->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ) ); + mainLayout = new QVBoxLayout( mainArea ); + + if ( KopetePrefs::prefs()->chatWShowSend() ) + { + //Send Button + m_button_send = new KPushButton( i18n("Send"), statusBar() ); + m_button_send->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ) ); + m_button_send->setEnabled( false ); + m_button_send->setFont( statusBar()->font() ); + m_button_send->setFixedHeight( statusBar()->sizeHint().height() ); + connect( m_button_send, SIGNAL( clicked() ), this, SLOT( slotSendMessage() ) ); + statusBar()->addWidget( m_button_send, 0, true ); + } + else + m_button_send = 0L; + + m_status_text = new KSqueezedTextLabel( i18n("Ready."), statusBar(), "m_status_text" ); + m_status_text->setAlignment( AlignLeft | AlignVCenter ); + m_status_text->setFont( statusBar()->font() ); + m_status_text->setFixedHeight( statusBar()->sizeHint().height() ); + statusBar()->addWidget( m_status_text, 1 ); + + readOptions(); + setWFlags( Qt::WDestructiveClose ); + + windows.append( this ); + windowListChanged(); + + KGlobal::config()->setGroup( QString::fromLatin1("ChatWindowSettings") ); + m_alwaysShowTabs = KGlobal::config()->readBoolEntry( QString::fromLatin1("AlwaysShowTabs"), false ); + m_showFormatToolbar = KGlobal::config()->readBoolEntry( QString::fromLatin1("Show Format Toolbar"), true ); + adjustingFormatToolbar = false; +// kdDebug( 14010 ) << k_funcinfo << "Open Windows: " << windows.count() << endl; + kapp->ref(); +} + +KopeteChatWindow::~KopeteChatWindow() +{ + kdDebug( 14010 ) << k_funcinfo << endl; + + emit( closing( this ) ); + + for( AccountMap::Iterator it = accountMap.begin(); it != accountMap.end(); ) + { + AccountMap::Iterator mayDeleteIt = it; + ++it; + if( mayDeleteIt.data() == this ) + accountMap.remove( mayDeleteIt.key() ); + } + + for( GroupMap::Iterator it = groupMap.begin(); it != groupMap.end(); ) + { + GroupMap::Iterator mayDeleteIt = it; + ++it; + if( mayDeleteIt.data() == this ) + groupMap.remove( mayDeleteIt.key() ); + } + + for( MetaContactMap::Iterator it = mcMap.begin(); it != mcMap.end(); ) + { + MetaContactMap::Iterator mayDeleteIt = it; + ++it; + if( mayDeleteIt.data() == this ) + mcMap.remove( mayDeleteIt.key() ); + } + + windows.remove( this ); + windowListChanged(); + +// kdDebug( 14010 ) << "Open Windows: " << windows.count() << endl; + + saveOptions(); + + if( backgroundFile ) + { + backgroundFile->close(); + backgroundFile->unlink(); + delete backgroundFile; + } + + delete anim; + kapp->deref(); +} + +void KopeteChatWindow::windowListChanged() +{ + // update all windows' Move Tab to Window action + for ( QPtrListIterator it( windows ); *it; ++it ) + (*it)->checkDetachEnable(); +} + +void KopeteChatWindow::slotNickComplete() +{ + if( m_activeView ) + m_activeView->nickComplete(); +} + +void KopeteChatWindow::slotTabContextMenu( QWidget *tab, const QPoint &pos ) +{ + m_popupView = static_cast( tab ); + + KPopupMenu *popup = new KPopupMenu; + popup->insertTitle( KStringHandler::rsqueeze( m_popupView->caption() ) ); + + actionContactMenu->plug( popup ); + popup->insertSeparator(); + actionTabPlacementMenu->plug( popup ); + tabDetach->plug( popup ); + actionDetachMenu->plug( popup ); + tabClose->plug( popup ); + popup->exec( pos ); + + delete popup; + m_popupView = 0; +} + +ChatView *KopeteChatWindow::activeView() +{ + return m_activeView; +} + +void KopeteChatWindow::initActions(void) +{ + KActionCollection *coll = actionCollection(); + + createStandardStatusBarAction(); + + chatSend = new KAction( i18n( "&Send Message" ), QString::fromLatin1( "mail_send" ), QKeySequence(Key_Return) , + this, SLOT( slotSendMessage() ), coll, "chat_send" ); + chatSend->setEnabled( false ); + + KStdAction::save ( this, SLOT(slotChatSave()), coll ); + KStdAction::print ( this, SLOT(slotChatPrint()), coll ); + KAction* quitAction = KStdAction::quit ( this, SLOT(close()), coll ); + quitAction->setText( i18n("Close All Chats") ); + + tabClose = KStdAction::close ( this, SLOT(slotChatClosed()), coll, "tabs_close" ); + + tabRight=new KAction( i18n( "&Activate Next Tab" ), 0, KStdAccel::tabNext(), + this, SLOT( slotNextTab() ), coll, "tabs_right" ); + tabLeft=new KAction( i18n( "&Activate Previous Tab" ), 0, KStdAccel::tabPrev(), + this, SLOT( slotPreviousTab() ), coll, "tabs_left" ); + tabLeft->setEnabled( false ); + tabRight->setEnabled( false ); + + nickComplete = new KAction( i18n( "Nic&k Completion" ), QString::null, 0, this, SLOT( slotNickComplete() ), coll , "nick_compete"); + nickComplete->setShortcut( QKeySequence( Key_Tab ) ); + + tabDetach = new KAction( i18n( "&Detach Chat" ), QString::fromLatin1( "tab_breakoff" ), 0, + this, SLOT( slotDetachChat() ), coll, "tabs_detach" ); + tabDetach->setEnabled( false ); + + actionDetachMenu = new KActionMenu( i18n( "&Move Tab to Window" ), QString::fromLatin1( "tab_breakoff" ), coll, "tabs_detachmove" ); + actionDetachMenu->setDelayed( false ); + + connect ( actionDetachMenu->popupMenu(), SIGNAL(aboutToShow()), this, SLOT(slotPrepareDetachMenu()) ); + connect ( actionDetachMenu->popupMenu(), SIGNAL(activated(int)), this, SLOT(slotDetachChat(int)) ); + + actionTabPlacementMenu = new KActionMenu( i18n( "&Tab Placement" ), coll, "tabs_placement" ); + connect ( actionTabPlacementMenu->popupMenu(), SIGNAL(aboutToShow()), this, SLOT(slotPreparePlacementMenu()) ); + connect ( actionTabPlacementMenu->popupMenu(), SIGNAL(activated(int)), this, SLOT(slotPlaceTabs(int)) ); + + tabDetach->setShortcut( QKeySequence(CTRL + SHIFT + Key_B) ); + + KStdAction::cut( this, SLOT(slotCut()), coll); + KStdAction::copy( this, SLOT(slotCopy()), coll); + KStdAction::paste( this, SLOT(slotPaste()), coll); + + new KAction( i18n( "Set Default &Font..." ), QString::fromLatin1( "charset" ), 0, this, SLOT( slotSetFont() ), coll, "format_font" ); + new KAction( i18n( "Set Default Text &Color..." ), QString::fromLatin1( "pencil" ), 0, this, SLOT( slotSetFgColor() ), coll, "format_fgcolor" ); + new KAction( i18n( "Set &Background Color..." ), QString::fromLatin1( "fill" ), 0, this, SLOT( slotSetBgColor() ), coll, "format_bgcolor" ); + + historyUp = new KAction( i18n( "Previous History" ), QString::null, 0, + this, SLOT( slotHistoryUp() ), coll, "history_up" ); + historyUp->setShortcut( QKeySequence(CTRL + Key_Up) ); + + historyDown = new KAction( i18n( "Next History" ), QString::null, 0, + this, SLOT( slotHistoryDown() ), coll, "history_down" ); + historyDown->setShortcut( QKeySequence(CTRL + Key_Down) ); + + KStdAction::prior( this, SLOT( slotPageUp() ), coll, "scroll_up" ); + KStdAction::next( this, SLOT( slotPageDown() ), coll, "scroll_down" ); + + KStdAction::showMenubar( this, SLOT(slotViewMenuBar()), coll ); + + membersLeft = new KToggleAction( i18n( "Place to Left of Chat Area" ), QString::null, 0, + this, SLOT( slotViewMembersLeft() ), coll, "options_membersleft" ); + membersRight = new KToggleAction( i18n( "Place to Right of Chat Area" ), QString::null, 0, + this, SLOT( slotViewMembersRight() ), coll, "options_membersright" ); + toggleMembers = new KToggleAction( i18n( "Show" ), QString::null, 0, + this, SLOT( slotToggleViewMembers() ), coll, "options_togglemembers" ); + toggleMembers->setCheckedState(i18n("Hide")); + toggleAutoSpellCheck = new KToggleAction( i18n( "Automatic Spell Checking" ), QString::null, 0, + this, SLOT( toggleAutoSpellChecking() ), coll, "enable_auto_spell_check" ); + toggleAutoSpellCheck->setChecked( true ); + + actionSmileyMenu = new KopeteEmoticonAction( coll, "format_smiley" ); + actionSmileyMenu->setDelayed( false ); + connect(actionSmileyMenu, SIGNAL(activated(const QString &)), this, SLOT(slotSmileyActivated(const QString &))); + + actionContactMenu = new KActionMenu(i18n("Co&ntacts"), coll, "contacts_menu" ); + actionContactMenu->setDelayed( false ); + connect ( actionContactMenu->popupMenu(), SIGNAL(aboutToShow()), this, SLOT(slotPrepareContactMenu()) ); + + // add configure key bindings menu item + KStdAction::keyBindings( guiFactory(), SLOT( configureShortcuts() ), coll ); + + KStdAction::configureToolbars(this, SLOT(slotConfToolbar()), coll); + KopeteStdAction::preferences( coll , "settings_prefs" ); + + //The Sending movie + normalIcon = QPixmap( BarIcon( QString::fromLatin1( "kopete" ) ) ); + animIcon = KGlobal::iconLoader()->loadMovie( QString::fromLatin1( "newmessage" ), KIcon::Toolbar); + + // Pause the animation because otherwise it's running even when we're not + // showing it. This eats resources, and also triggers a pixmap leak in + // QMovie in at least Qt 3.1, Qt 3.2 and the current Qt 3.3 beta + if( !animIcon.isNull() ) //and another QT bug: it crash if we pause a null movie + animIcon.pause(); + + // we can't set the tool bar as parent, if we do, it will be deleted when we configure toolbars + anim = new QLabel( QString::null, 0L ,"kde toolbar widget" ); + anim->setMargin(5); + anim->setPixmap( normalIcon ); + + + new KWidgetAction( anim , i18n("Toolbar Animation") , 0, 0 , 0 , coll , "toolbar_animation"); + + //toolBar()->insertWidget( 99, anim->width(), anim ); + //toolBar()->alignItemRight( 99 ); + setStandardToolBarMenuEnabled( true ); + + setXMLFile( QString::fromLatin1( "kopetechatwindow.rc" ) ); + createGUI( 0L ); + + // Special handling for remembering whether the format toolbar is visible or not + connect ( toolBar("formatToolBar"), SIGNAL(visibilityChanged(bool)), this, SLOT(slotToggleFormatToolbar(bool)) ); +} + +const QString KopeteChatWindow::fileContents( const QString &path ) const +{ + QString contents; + QFile file( path ); + if ( file.open( IO_ReadOnly ) ) + { + QTextStream stream( &file ); + contents = stream.read(); + file.close(); + } + + return contents; +} + +void KopeteChatWindow::slotStopAnimation( ChatView* view ) +{ + if( view == m_activeView ) + anim->setPixmap( normalIcon ); +} + +void KopeteChatWindow::slotUpdateSendEnabled() +{ + if ( !m_activeView ) return; + + bool enabled = m_activeView->canSend(); + chatSend->setEnabled( enabled ); + if(m_button_send) + m_button_send->setEnabled( enabled ); +} + +void KopeteChatWindow::updateMembersActions() +{ + if( m_activeView ) + { + const KDockWidget::DockPosition pos = m_activeView->membersListPosition(); + bool visibleMembers = m_activeView->visibleMembersList(); + membersLeft->setChecked( pos == KDockWidget::DockLeft ); + membersLeft->setEnabled( visibleMembers ); + membersRight->setChecked( pos == KDockWidget::DockRight ); + membersRight->setEnabled( visibleMembers ); + toggleMembers->setChecked( visibleMembers ); + } +} + +void KopeteChatWindow::slotViewMembersLeft() +{ + m_activeView->placeMembersList( KDockWidget::DockLeft ); + updateMembersActions(); +} + +void KopeteChatWindow::slotViewMembersRight() +{ + m_activeView->placeMembersList( KDockWidget::DockRight ); + updateMembersActions(); +} + +void KopeteChatWindow::slotToggleViewMembers() +{ + m_activeView->toggleMembersVisibility(); + updateMembersActions(); +} + +void KopeteChatWindow::toggleAutoSpellChecking() +{ + if ( !m_activeView ) + return; + + bool currentSetting = m_activeView->editPart()->autoSpellCheckEnabled(); + m_activeView->editPart()->toggleAutoSpellCheck( !currentSetting ); + updateSpellCheckAction(); +} + +void KopeteChatWindow::updateSpellCheckAction() +{ + if ( !m_activeView ) + return; + + if ( m_activeView->editPart()->richTextEnabled() ) + { + toggleAutoSpellCheck->setEnabled( false ); + toggleAutoSpellCheck->setChecked( false ); + m_activeView->editPart()->toggleAutoSpellCheck( false ); + } + else + { + toggleAutoSpellCheck->setEnabled( true ); + if ( KopetePrefs::prefs()->spellCheck() ) + { + kdDebug(14000) << k_funcinfo << "spell check enabled" << endl; + toggleAutoSpellCheck->setChecked( true ); + m_activeView->editPart()->toggleAutoSpellCheck(true); + } + else + { + kdDebug(14000) << k_funcinfo << "spell check disabled" << endl; + toggleAutoSpellCheck->setChecked( false ); + m_activeView->editPart()->toggleAutoSpellCheck(false); + } + } +} + +void KopeteChatWindow::slotHistoryUp() +{ + if( m_activeView ) + m_activeView->editPart()->historyUp(); +} + +void KopeteChatWindow::slotHistoryDown() +{ + if( m_activeView ) + m_activeView->editPart()->historyDown(); +} + +void KopeteChatWindow::slotPageUp() +{ + if( m_activeView ) + m_activeView->messagePart()->pageUp(); +} + +void KopeteChatWindow::slotPageDown() +{ + if( m_activeView ) + m_activeView->messagePart()->pageDown(); +} + +void KopeteChatWindow::slotCut() +{ + m_activeView->cut(); +} + +void KopeteChatWindow::slotCopy() +{ + m_activeView->copy(); +} + +void KopeteChatWindow::slotPaste() +{ + m_activeView->paste(); +} + + +void KopeteChatWindow::slotSetFont() +{ + m_activeView->setFont(); +} + +void KopeteChatWindow::slotSetFgColor() +{ + m_activeView->setFgColor(); +} + +void KopeteChatWindow::slotSetBgColor() +{ + m_activeView->setBgColor(); +} + +void KopeteChatWindow::setStatus(const QString &text) +{ + m_status_text->setText(text); +} + +void KopeteChatWindow::createTabBar() +{ + if( !m_tabBar ) + { + KGlobal::config()->setGroup( QString::fromLatin1("ChatWindowSettings") ); + + m_tabBar = new KTabWidget( mainArea ); + m_tabBar->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ) ); + m_tabBar->setHoverCloseButton(KGlobal::config()->readBoolEntry( QString::fromLatin1("HoverClose"), false )); + m_tabBar->setTabReorderingEnabled(true); +#if KDE_IS_VERSION(3,4,0) + m_tabBar->setAutomaticResizeTabs(true); +#endif + connect( m_tabBar, SIGNAL( closeRequest( QWidget* )), this, SLOT( slotCloseChat( QWidget* ) ) ); + + QToolButton* m_rightWidget = new QToolButton( m_tabBar ); + connect( m_rightWidget, SIGNAL( clicked() ), this, SLOT( slotChatClosed() ) ); + m_rightWidget->setIconSet( SmallIcon( "tab_remove" ) ); + m_rightWidget->adjustSize(); + QToolTip::add( m_rightWidget, i18n("Close the current tab")); + m_tabBar->setCornerWidget( m_rightWidget, QWidget::TopRight ); + + mainLayout->addWidget( m_tabBar ); + m_tabBar->show(); + connect ( m_tabBar, SIGNAL(currentChanged(QWidget *)), this, SLOT(setActiveView(QWidget *)) ); + connect ( m_tabBar, SIGNAL(contextMenu(QWidget *, const QPoint & )), this, SLOT(slotTabContextMenu( QWidget *, const QPoint & )) ); + + for( ChatView *view = chatViewList.first(); view; view = chatViewList.next() ) + addTab( view ); + + if( m_activeView ) + m_tabBar->showPage( m_activeView ); + else + setActiveView( chatViewList.first() ); + + int tabPosition = KGlobal::config()->readNumEntry( QString::fromLatin1("Tab Placement") , 0 ); + slotPlaceTabs( tabPosition ); + } +} + +void KopeteChatWindow::slotCloseChat( QWidget *chatView ) +{ + static_cast( chatView )->closeView(); +} + +void KopeteChatWindow::addTab( ChatView *view ) +{ + QPtrList chatMembers=view->msgManager()->members(); + Kopete::Contact *c=0L; + for ( Kopete::Contact *contact = chatMembers.first(); contact; contact = chatMembers.next() ) + { + if(!c || c->onlineStatus() < contact->onlineStatus()) + c=contact; + } + QPixmap pluginIcon = c ? view->msgManager()->contactOnlineStatus( c ).iconFor( c) : SmallIcon( view->msgManager()->protocol()->pluginIcon() ); + + view->reparent( m_tabBar, 0, QPoint(), true ); + m_tabBar->addTab( view, pluginIcon, view->caption() ); + if( view == m_activeView ) + view->show(); + else + view->hide(); + connect( view, SIGNAL( captionChanged( bool ) ), this, SLOT( updateChatLabel() ) ); + connect( view, SIGNAL( updateStatusIcon( ChatView* ) ), this, SLOT( slotUpdateCaptionIcons( ChatView* ) ) ); + view->setCaption( view->caption(), false ); +} + +void KopeteChatWindow::setPrimaryChatView( ChatView *view ) +{ + //TODO figure out what else we have to save here besides the font + //reparent clears a lot of stuff out + QFont savedFont = view->font(); + view->reparent( mainArea, 0, QPoint(), true ); + view->setFont( savedFont ); + view->show(); + + mainLayout->addWidget( view ); + setActiveView( view ); +} + +void KopeteChatWindow::deleteTabBar() +{ + if( m_tabBar ) + { + disconnect ( m_tabBar, SIGNAL(currentChanged(QWidget *)), this, SLOT(setActiveView(QWidget *)) ); + disconnect ( m_tabBar, SIGNAL(contextMenu(QWidget *, const QPoint & )), this, SLOT(slotTabContextMenu( QWidget *, const QPoint & )) ); + + if( !chatViewList.isEmpty() ) + setPrimaryChatView( chatViewList.first() ); + + m_tabBar->deleteLater(); + m_tabBar = 0L; + } +} + +void KopeteChatWindow::attachChatView( ChatView* newView ) +{ + chatViewList.append( newView ); + + if ( !m_alwaysShowTabs && chatViewList.count() == 1 ) + setPrimaryChatView( newView ); + else + { + if ( !m_tabBar ) + createTabBar(); + else + addTab( newView ); + newView->setActive( false ); + } + + newView->setMainWindow( this ); + newView->editWidget()->installEventFilter( this ); + + KCursor::setAutoHideCursor( newView->editWidget(), true, true ); + connect( newView, SIGNAL(captionChanged( bool)), this, SLOT(slotSetCaption(bool)) ); + connect( newView, SIGNAL(messageSuccess( ChatView* )), this, SLOT(slotStopAnimation( ChatView* )) ); + connect( newView, SIGNAL(rtfEnabled( ChatView*, bool ) ), this, SLOT( slotRTFEnabled( ChatView*, bool ) ) ); + connect( newView, SIGNAL(updateStatusIcon( ChatView* ) ), this, SLOT(slotUpdateCaptionIcons( ChatView* ) ) ); + connect( newView, SIGNAL(updateChatState( ChatView*, int ) ), this, SLOT( updateChatState( ChatView*, int ) ) ); + + updateSpellCheckAction(); + checkDetachEnable(); + newView->loadChatSettings(); + connect( newView, SIGNAL(autoSpellCheckEnabled( ChatView*, bool ) ), + this, SLOT( slotAutoSpellCheckEnabled( ChatView*, bool ) ) ); +} + +void KopeteChatWindow::checkDetachEnable() +{ + bool haveTabs = (chatViewList.count() > 1); + tabDetach->setEnabled( haveTabs ); + tabLeft->setEnabled( haveTabs ); + tabRight->setEnabled( haveTabs ); + actionTabPlacementMenu->setEnabled( m_tabBar != 0 ); + + bool otherWindows = (windows.count() > 1); + actionDetachMenu->setEnabled( otherWindows ); +} + +void KopeteChatWindow::detachChatView( ChatView *view ) +{ + if( !chatViewList.removeRef( view ) ) + return; + + disconnect( view, SIGNAL(captionChanged( bool)), this, SLOT(slotSetCaption(bool)) ); + disconnect( view, SIGNAL( updateStatusIcon( ChatView* ) ), this, SLOT( slotUpdateCaptionIcons( ChatView* ) ) ); + disconnect( view, SIGNAL( updateChatState( ChatView*, int ) ), this, SLOT( updateChatState( ChatView*, int ) ) ); + view->editWidget()->removeEventFilter( this ); + + if( m_tabBar ) + { + int curPage = m_tabBar->currentPageIndex(); + QWidget *page = m_tabBar->page( curPage ); + + // if the current view is to be detached, switch to a different one + if( page == view ) + { + if( curPage > 0 ) + m_tabBar->setCurrentPage( curPage - 1 ); + else + m_tabBar->setCurrentPage( curPage + 1 ); + } + + m_tabBar->removePage( view ); + + if( m_tabBar->currentPage() ) + setActiveView( static_cast(m_tabBar->currentPage()) ); + } + + if( chatViewList.isEmpty() ) + close(); + else if( !m_alwaysShowTabs && chatViewList.count() == 1) + deleteTabBar(); + + checkDetachEnable(); +} + +void KopeteChatWindow::slotDetachChat( int newWindowIndex ) +{ + KopeteChatWindow *newWindow = 0L; + ChatView *detachedView; + + if( m_popupView ) + detachedView = m_popupView; + else + detachedView = m_activeView; + + if( !detachedView ) + return; + + //if we don't do this, we might crash + createGUI(0L); + guiFactory()->removeClient(detachedView->msgManager()); + + if( newWindowIndex == -1 ) + newWindow = new KopeteChatWindow(); + else + newWindow = windows.at( newWindowIndex ); + + newWindow->show(); + newWindow->raise(); + + detachChatView( detachedView ); + newWindow->attachChatView( detachedView ); +} + +void KopeteChatWindow::slotPreviousTab() +{ + int curPage = m_tabBar->currentPageIndex(); + if( curPage > 0 ) + m_tabBar->setCurrentPage( curPage - 1 ); + else + m_tabBar->setCurrentPage( m_tabBar->count() - 1 ); +} + +void KopeteChatWindow::slotNextTab() +{ + int curPage = m_tabBar->currentPageIndex(); + if( curPage == ( m_tabBar->count() - 1 ) ) + m_tabBar->setCurrentPage( 0 ); + else + m_tabBar->setCurrentPage( curPage + 1 ); +} + +void KopeteChatWindow::slotSetCaption( bool active ) +{ + if( active && m_activeView ) + { + setCaption( m_activeView->caption(), false ); + } +} + +void KopeteChatWindow::updateBackground( const QPixmap &pm ) +{ + if( updateBg ) + { + updateBg = false; + if( backgroundFile != 0L ) + { + backgroundFile->close(); + backgroundFile->unlink(); + } + + backgroundFile = new KTempFile( QString::null, QString::fromLatin1( ".bmp" ) ); + pm.save( backgroundFile->name(), "BMP" ); + QTimer::singleShot( 100, this, SLOT( slotEnableUpdateBg() ) ); + } +} + +void KopeteChatWindow::setActiveView( QWidget *widget ) +{ + ChatView *view = static_cast(widget); + + if( m_activeView == view ) + return; + + if(m_activeView) + { + disconnect( m_activeView, SIGNAL( canSendChanged(bool) ), this, SLOT( slotUpdateSendEnabled() ) ); + guiFactory()->removeClient(m_activeView->msgManager()); + m_activeView->saveChatSettings(); + } + + guiFactory()->addClient(view->msgManager()); + createGUI( view->editPart() ); + + if( m_activeView ) + m_activeView->setActive( false ); + + m_activeView = view; + + if( !chatViewList.contains( view ) ) + attachChatView( view ); + + connect( m_activeView, SIGNAL( canSendChanged(bool) ), this, SLOT( slotUpdateSendEnabled() ) ); + + //Tell it it is active + m_activeView->setActive( true ); + + //Update icons to match + slotUpdateCaptionIcons( m_activeView ); + + //Update chat members actions + updateMembersActions(); + + if ( m_activeView->sendInProgress() && !animIcon.isNull() ) + { + anim->setMovie( animIcon ); + animIcon.unpause(); + } + else + { + anim->setPixmap( normalIcon ); + if( !animIcon.isNull() ) + animIcon.pause(); + } + + if ( m_alwaysShowTabs || chatViewList.count() > 1 ) + { + if( !m_tabBar ) + createTabBar(); + + m_tabBar->showPage( m_activeView ); + } + + setCaption( m_activeView->caption() ); + setStatus( m_activeView->statusText() ); + m_activeView->setFocus(); + updateSpellCheckAction(); + slotUpdateSendEnabled(); + m_activeView->editPart()->reloadConfig(); + m_activeView->loadChatSettings(); +} + +void KopeteChatWindow::slotUpdateCaptionIcons( ChatView *view ) +{ + if ( !view ) + return; //(pas de charité) + + QPtrList chatMembers=view->msgManager()->members(); + Kopete::Contact *c=0L; + for ( Kopete::Contact *contact = chatMembers.first(); contact; contact = chatMembers.next() ) + { + if(!c || c->onlineStatus() < contact->onlineStatus()) + c=contact; + } + + if ( view == m_activeView ) + { + QPixmap icon16 = c ? view->msgManager()->contactOnlineStatus( c ).iconFor( c , 16) : + SmallIcon( view->msgManager()->protocol()->pluginIcon() ); + QPixmap icon32 = c ? view->msgManager()->contactOnlineStatus( c ).iconFor( c , 32) : + SmallIcon( view->msgManager()->protocol()->pluginIcon() ); + KWin::setIcons( winId(), icon32, icon16 ); + } + + if ( m_tabBar ) + m_tabBar->setTabIconSet( view, c ? view->msgManager()->contactOnlineStatus( c ).iconFor( c ) : + SmallIcon( view->msgManager()->protocol()->pluginIcon() ) ); +} + +void KopeteChatWindow::slotChatClosed() +{ + if( m_popupView ) + m_popupView->closeView(); + else + m_activeView->closeView(); +} + +void KopeteChatWindow::slotPrepareDetachMenu(void) +{ + QPopupMenu *detachMenu = actionDetachMenu->popupMenu(); + detachMenu->clear(); + + for ( unsigned id=0; id < windows.count(); id++ ) + { + KopeteChatWindow *win = windows.at( id ); + if( win != this ) + detachMenu->insertItem( win->caption(), id ); + } +} + +void KopeteChatWindow::slotSendMessage() +{ + if ( m_activeView && m_activeView->canSend() ) + { + if( !animIcon.isNull() ) + { + anim->setMovie( animIcon ); + animIcon.unpause(); + } + m_activeView->sendMessage(); + } +} + +void KopeteChatWindow::slotPrepareContactMenu(void) +{ + QPopupMenu *contactsMenu = actionContactMenu->popupMenu(); + contactsMenu->clear(); + + Kopete::Contact *contact; + Kopete::ContactPtrList m_them; + + if( m_popupView ) + m_them = m_popupView->msgManager()->members(); + else + m_them = m_activeView->msgManager()->members(); + + //TODO: don't display a menu with one contact in it, display that + // contact's menu instead. Will require changing text and icon of + // 'Contacts' action, or something cleverer. + uint contactCount = 0; + + for ( contact = m_them.first(); contact; contact = m_them.next() ) + { + KPopupMenu *p = contact->popupMenu(); + connect ( actionContactMenu->popupMenu(), SIGNAL(aboutToHide()), + p, SLOT(deleteLater() ) ); + + if( contact->metaContact() ) + contactsMenu->insertItem( contact->onlineStatus().iconFor( contact ) , contact->metaContact()->displayName(), p ); + else + contactsMenu->insertItem( contact->onlineStatus().iconFor( contact ) , contact->contactId(), p ); + + //FIXME: This number should be a config option + if( ++contactCount == 15 && contact != m_them.getLast() ) + { + KActionMenu *moreMenu = new KActionMenu( i18n("More..."), + QString::fromLatin1("folder_open"), contactsMenu ); + connect ( actionContactMenu->popupMenu(), SIGNAL(aboutToHide()), + moreMenu, SLOT(deleteLater() ) ); + moreMenu->plug( contactsMenu ); + contactsMenu = moreMenu->popupMenu(); + contactCount = 0; + } + } +} + +void KopeteChatWindow::slotPreparePlacementMenu() +{ + QPopupMenu *placementMenu = actionTabPlacementMenu->popupMenu(); + placementMenu->clear(); + + placementMenu->insertItem( i18n("Top"), 0 ); + placementMenu->insertItem( i18n("Bottom"), 1 ); +} + +void KopeteChatWindow::slotPlaceTabs( int placement ) +{ + if( m_tabBar ) + { + + if( placement == 0 ) + m_tabBar->setTabPosition( QTabWidget::Top ); + else + m_tabBar->setTabPosition( QTabWidget::Bottom ); + + saveOptions(); + } +} + +void KopeteChatWindow::readOptions() +{ + // load and apply config file settings affecting the appearance of the UI +// kdDebug(14010) << k_funcinfo << endl; + KConfig *config = KGlobal::config(); + applyMainWindowSettings( config, QString::fromLatin1( "KopeteChatWindow" ) ); + config->setGroup( QString::fromLatin1("ChatWindowSettings") ); + m_showFormatToolbar = config->readBoolEntry( QString::fromLatin1("Show Format Toolbar"), true ); +} + +void KopeteChatWindow::saveOptions() +{ +// kdDebug(14010) << k_funcinfo << endl; + + KConfig *config = KGlobal::config(); + + // saves menubar,toolbar and statusbar setting + saveMainWindowSettings( config, QString::fromLatin1( "KopeteChatWindow" ) ); + config->setGroup( QString::fromLatin1("ChatWindowSettings") ); + if( m_tabBar ) + config->writeEntry ( QString::fromLatin1("Tab Placement"), m_tabBar->tabPosition() ); + + config->writeEntry( QString::fromLatin1("Show Format Toolbar"), m_showFormatToolbar ); + config->sync(); +} + +void KopeteChatWindow::slotChatSave() +{ +// kdDebug(14010) << "KopeteChatWindow::slotChatSave()" << endl; + if( isActiveWindow() && m_activeView ) + m_activeView->messagePart()->save(); +} + +void KopeteChatWindow::windowActivationChange( bool ) +{ + if( isActiveWindow() && m_activeView ) + m_activeView->setActive( true ); +} + +void KopeteChatWindow::slotChatPrint() +{ + m_activeView->messagePart()->print(); +} + +void KopeteChatWindow::slotToggleStatusBar() +{ + if (statusBar()->isVisible()) + statusBar()->hide(); + else + statusBar()->show(); +} + +void KopeteChatWindow::slotToggleFormatToolbar(bool visible) +{ + if ( adjustingFormatToolbar ) + return; + m_showFormatToolbar = visible; +} + +void KopeteChatWindow::slotViewMenuBar() +{ + if( !menuBar()->isHidden() ) + menuBar()->hide(); + else + menuBar()->show(); +} + +void KopeteChatWindow::slotSmileyActivated(const QString &sm) +{ + if ( !sm.isNull() ) + m_activeView->addText( " " + sm + " " ); + //we are adding space around the emoticon becasue our parser only display emoticons not in a word. +} + +void KopeteChatWindow::slotRTFEnabled( ChatView* cv, bool enabled) +{ + if ( cv != m_activeView ) + return; + + adjustingFormatToolbar = true; + if ( enabled && m_showFormatToolbar ) + toolBar( "formatToolBar" )->show(); + else + toolBar( "formatToolBar" )->hide(); + adjustingFormatToolbar = false; + updateSpellCheckAction(); +} + +void KopeteChatWindow::slotAutoSpellCheckEnabled( ChatView* view, bool isEnabled ) +{ + if ( view != m_activeView ) + return; + + toggleAutoSpellCheck->setEnabled( isEnabled ); + toggleAutoSpellCheck->setChecked( isEnabled ); + m_activeView->editPart()->toggleAutoSpellCheck( isEnabled ); +} + +bool KopeteChatWindow::queryClose() +{ + bool canClose = true; + +// kdDebug( 14010 ) << " Windows left open:" << endl; +// for( QPtrListIterator it( chatViewList ); it; ++it) +// kdDebug( 14010 ) << " " << *it << " (" << (*it)->caption() << ")" << endl; + + for( QPtrListIterator it( chatViewList ); it; ) + { + ChatView *view = *it; + // move out of the way before view is removed + ++it; + + // FIXME: This should only check if it *can* close + // and not start closing if the close can be aborted halfway, it would + // leave us with half the chats open and half of them closed. - Martijn + + // if the view is closed, it is removed from chatViewList for us + if ( !view->closeView() ) + { + kdDebug() << k_funcinfo << "Closing view failed!" << endl; + canClose = false; + } + } + return canClose; +} + +bool KopeteChatWindow::queryExit() +{ + KopeteApplication *app = static_cast( kapp ); + if ( app->sessionSaving() + || app->isShuttingDown() /* only set if KopeteApplication::quitKopete() or + KopeteApplication::commitData() called */ + || !KopetePrefs::prefs()->showTray() /* also close if our tray icon is hidden! */ + || !isShown() ) + { + Kopete::PluginManager::self()->shutdown(); + return true; + } + else + return false; +} + +void KopeteChatWindow::closeEvent( QCloseEvent * e ) +{ + // if there's a system tray applet and we are not shutting down then just do what needs to be done if a + // window is closed. + KopeteApplication *app = static_cast( kapp ); + if ( KopetePrefs::prefs()->showTray() && !app->isShuttingDown() && !app->sessionSaving() ) { +// hide(); + // BEGIN of code borrowed from KMainWindow::closeEvent + // Save settings if auto-save is enabled, and settings have changed + if ( settingsDirty() && autoSaveSettings() ) + saveAutoSaveSettings(); + + if ( queryClose() ) { + e->accept(); + } + // END of code borrowed from KMainWindow::closeEvent + } + else + KMainWindow::closeEvent( e ); +} + +void KopeteChatWindow::slotConfKeys() +{ + KKeyDialog dlg( false, this ); + dlg.insert( actionCollection() ); + if( m_activeView ) + { + dlg.insert(m_activeView->msgManager()->actionCollection() , i18n("Plugin Actions") ); + QPtrListIterator it( *m_activeView->msgManager()->childClients() ); + KXMLGUIClient *c = 0; + while( (c = it.current()) != 0 ) + { + dlg.insert( c->actionCollection() /*, i18n("Plugin Actions")*/ ); + ++it; + } + + if( m_activeView->editPart() ) + dlg.insert( m_activeView->editPart()->actionCollection(), m_activeView->editPart()->name() ); + } + + dlg.configure(); +} + +void KopeteChatWindow::slotConfToolbar() +{ + saveMainWindowSettings(KGlobal::config(), QString::fromLatin1( "KopeteChatWindow" )); + KEditToolbar *dlg = new KEditToolbar(factory(), this ); + if (dlg->exec()) + { + if( m_activeView ) + createGUI( m_activeView->editPart() ); + else + createGUI( 0L ); + applyMainWindowSettings(KGlobal::config(), QString::fromLatin1( "KopeteChatWindow" )); + } + delete dlg; +} + +void KopeteChatWindow::updateChatState( ChatView* cv, int newState ) +{ + if ( m_tabBar ) + { + switch( newState ) + { + case ChatView::Highlighted: + m_tabBar->setTabColor( cv, Qt::blue ); + break; + case ChatView::Message: + m_tabBar->setTabColor( cv, Qt::red ); + break; + case ChatView::Changed: + m_tabBar->setTabColor( cv, Qt::darkRed ); + break; + case ChatView::Typing: + m_tabBar->setTabColor( cv, Qt::darkGreen ); + break; + case ChatView::Normal: + default: + m_tabBar->setTabColor( cv, KGlobalSettings::textColor() ); + break; + } + } +} + +void KopeteChatWindow::updateChatTooltip( ChatView* cv ) +{ + if ( m_tabBar ) + m_tabBar->setTabToolTip( cv, QString::fromLatin1("%1").arg( cv->caption() ) ); +} + +void KopeteChatWindow::updateChatLabel() +{ + const ChatView* cv = dynamic_cast( sender() ); + if ( !cv || !m_tabBar ) + return; + + ChatView* chat = const_cast( cv ); + if ( m_tabBar ) + { + m_tabBar->setTabLabel( chat, chat->caption() ); + if ( m_tabBar->count() < 2 || m_tabBar->currentPage() == static_cast(cv) ) + setCaption( chat->caption() ); + } +} + +#include "kopetechatwindow.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/kopetechatwindow.h b/kopete/kopete/chatwindow/kopetechatwindow.h new file mode 100644 index 00000000..39277d86 --- /dev/null +++ b/kopete/kopete/chatwindow/kopetechatwindow.h @@ -0,0 +1,243 @@ +/* + kopetechatwindow.h - Chat Window + + Copyright (c) 2002 by Olivier Goffart + Copyright (c) 2004 by Martijn Klingens + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETECHATWINDOW_H +#define KOPETECHATWINDOW_H + +#include +#include +#include "kopetecontact.h" +#include "kdeversion.h" + +class KAction; +class KToggleAction; +class KActionMenu; +class KTempFile; +class QPixmap; +class QTabWidget; +class KSqueezedTextLabel; +class KPushButton; +class QVBox; +class QVBoxLayout; +class QFrame; +class KTabWidget; +class QLabel; +class KopeteEmoticonAction; +class KopeteView; +class KSelectAction; +class ChatView; + +namespace Kopete +{ +class Message; +class ChatSession; +class Contact; +class Protocol; +typedef QPtrList ContactPtrList; +} + +class KopeteChatWindow : public KParts::MainWindow +{ + Q_OBJECT + + enum {NEW_WINDOW, GROUP_BY_ACCOUNT, GROUP_ALL, GROUP_BY_GROUP, GROUP_BY_METACONTACT}; + +public: + /** + * Find the appropriate window for a ChatView of the given protocol to + * dock into. If no such window exists, create one. + * @param protocol The protocol we are creating a view for + * @return A KopeteChatWindow suitable for docking a ChatView into. Guaranteed + * to be a valid pointer. + */ + static KopeteChatWindow *window( Kopete::ChatSession *manager ); + ~KopeteChatWindow(); + + /** + * Attach an unattached chatview to this window + * @param chat The chat view to attach + */ + void attachChatView( ChatView *chat ); + + /** + * Detach a chat view from this window + * @param chat The chat view to detach + */ + void detachChatView( ChatView *chat ); + + /** + * Returns the number of chat views attached to this window + */ + int chatViewCount() { return chatViewList.count(); } + + /** + * Returns the chatview in the currently active tab, or the only chat view + * if chatViewCount() == 1 + */ + ChatView *activeView(); + + void updateMembersActions(); + void setStatus( const QString & ); + + /** + * Reimplemented from KMainWindow - asks each ChatView in the window if it is ok to close the window + * @return true if no ChatView objects to closing. + */ + virtual bool queryClose(); + virtual bool queryExit(); + + KTempFile *backgroundFile; + QPtrList chatViewList; + +private: + // All KopeteChatWindows are created by the window function + KopeteChatWindow( QWidget *parent = 0, const char* name = "KopeteChatWindow" ); + + /** + * The window list has changed: + * For each chat window, update it's Move Tab to Window action + */ + static void windowListChanged(); + + void initActions(void); + void saveOptions(void); + void readOptions(void); + void checkDetachEnable(); + void createTabBar(); + void deleteTabBar(); + void addTab( ChatView* ); + void setPrimaryChatView( ChatView* ); + const QString fileContents( const QString &file ) const; + + ChatView *m_activeView; + ChatView *m_popupView; + bool m_alwaysShowTabs; + bool m_showFormatToolbar; + bool adjustingFormatToolbar; + bool updateBg; + KTabWidget *m_tabBar; + KPushButton *m_button_send; + KSqueezedTextLabel *m_status_text; + QVBoxLayout *mainLayout; + QFrame *mainArea; + QLabel *anim; + QMovie animIcon; + QPixmap normalIcon; + + KAction *chatSend; + KAction *historyUp; + KAction *historyDown; + KAction *nickComplete; + + KToggleAction *mStatusbarAction; + + KAction *tabLeft; + KAction *tabRight; + KAction *tabDetach; + KAction* tabClose; + + KToggleAction* membersLeft; + KToggleAction* membersRight; + KToggleAction* toggleMembers; + KToggleAction* toggleAutoSpellCheck; + + KopeteEmoticonAction *actionSmileyMenu; + KActionMenu *actionActionMenu; + KActionMenu *actionContactMenu; + KActionMenu *actionDetachMenu; + KActionMenu *actionTabPlacementMenu; + QString statusMsg; + +signals: + void closing( KopeteChatWindow* ); + +public slots: + void slotSmileyActivated( const QString & ); + void setActiveView( QWidget *active ); + void updateBackground( const QPixmap &pm ); + +private slots: +// void slotPrepareSmileyMenu(); + void slotPrepareContactMenu(); + void slotPrepareDetachMenu(); + void slotPreparePlacementMenu(); + void slotUpdateSendEnabled(); + + void slotCut(); + void slotCopy(); + void slotPaste(); + + void slotSetBgColor(); + void slotSetFgColor(); + void slotSetFont(); + + void slotHistoryUp(); + void slotHistoryDown(); + void slotPageUp(); + void slotPageDown(); + + void slotSendMessage(); + void slotChatSave(); + void slotChatPrint(); + + void slotPreviousTab(); + void slotNextTab(); + void slotDetachChat( int newWindowIndex = -1 ); + void slotPlaceTabs( int tabPlacement ); + + void slotViewMenuBar(); + void slotToggleStatusBar(); + void slotToggleFormatToolbar( bool ); + + void slotConfKeys(); + void slotConfToolbar(); + + void slotViewMembersLeft(); + void slotViewMembersRight(); + void slotToggleViewMembers(); + void slotEnableUpdateBg() { updateBg = true; } + + void toggleAutoSpellChecking(); + void slotRTFEnabled( ChatView*, bool ); + void slotAutoSpellCheckEnabled( ChatView*, bool ); + + void slotSetCaption( bool ); + void slotUpdateCaptionIcons( ChatView * ); + void slotChatClosed(); + void slotTabContextMenu( QWidget*, const QPoint & ); + void slotStopAnimation( ChatView* ); + void slotNickComplete(); + void slotCloseChat( QWidget* ); + + //slots for tabs from the chatview widget + void updateChatState( ChatView* cv, int state ); + void updateChatTooltip( ChatView* cv ); + void updateChatLabel(); + +private: + void updateSpellCheckAction(); + +protected: + virtual void closeEvent( QCloseEvent *e ); + virtual void windowActivationChange( bool ); +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/kopetechatwindow.rc b/kopete/kopete/chatwindow/kopetechatwindow.rc new file mode 100644 index 00000000..89f19c39 --- /dev/null +++ b/kopete/kopete/chatwindow/kopetechatwindow.rc @@ -0,0 +1,64 @@ + + + + + &Chat + + + + + + + + + + + + + &Format + + + + + &Tabs + + + + + + + + + &Settings + + + &Chat Members List + + + + + + + + + + + + + Main Toolbar + + + + + + + + + Status + + + Format Toolbar + + + + diff --git a/kopete/kopete/chatwindow/kopetechatwindowstyle.cpp b/kopete/kopete/chatwindow/kopetechatwindowstyle.cpp new file mode 100644 index 00000000..3e15281f --- /dev/null +++ b/kopete/kopete/chatwindow/kopetechatwindowstyle.cpp @@ -0,0 +1,287 @@ + /* + kopetechatwindowstyle.cpp - A Chat Window Style. + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopetechatwindowstyle.h" + +// Qt includes +#include +#include +#include +#include + +// KDE includes +#include + + +class ChatWindowStyle::Private +{ +public: + QString stylePath; + StyleVariants variantsList; + QString baseHref; + QString currentVariantPath; + + QString headerHtml; + QString footerHtml; + QString incomingHtml; + QString nextIncomingHtml; + QString outgoingHtml; + QString nextOutgoingHtml; + QString statusHtml; + QString actionIncomingHtml; + QString actionOutgoingHtml; +}; + +ChatWindowStyle::ChatWindowStyle(const QString &stylePath, int styleBuildMode) + : d(new Private) +{ + init(stylePath, styleBuildMode); +} + +ChatWindowStyle::ChatWindowStyle(const QString &stylePath, const QString &variantPath, int styleBuildMode) + : d(new Private) +{ + d->currentVariantPath = variantPath; + init(stylePath, styleBuildMode); +} + +void ChatWindowStyle::init(const QString &stylePath, int styleBuildMode) +{ + d->stylePath = stylePath; + d->baseHref = stylePath + QString::fromUtf8("/Contents/Resources/"); + readStyleFiles(); + if(styleBuildMode & StyleBuildNormal) + { + listVariants(); + } +} + +ChatWindowStyle::~ChatWindowStyle() +{ + kdDebug(14000) << k_funcinfo << endl; + delete d; +} + +ChatWindowStyle::StyleVariants ChatWindowStyle::getVariants() +{ + // If the variantList is empty, list available variants. + if( d->variantsList.isEmpty() ) + { + listVariants(); + } + return d->variantsList; +} + +QString ChatWindowStyle::getStylePath() const +{ + return d->stylePath; +} + +QString ChatWindowStyle::getStyleBaseHref() const +{ + return d->baseHref; +} + +QString ChatWindowStyle::getHeaderHtml() const +{ + return d->headerHtml; +} + +QString ChatWindowStyle::getFooterHtml() const +{ + return d->footerHtml; +} + +QString ChatWindowStyle::getIncomingHtml() const +{ + return d->incomingHtml; +} + +QString ChatWindowStyle::getNextIncomingHtml() const +{ + return d->nextIncomingHtml; +} + +QString ChatWindowStyle::getOutgoingHtml() const +{ + return d->outgoingHtml; +} + +QString ChatWindowStyle::getNextOutgoingHtml() const +{ + return d->nextOutgoingHtml; +} + +QString ChatWindowStyle::getStatusHtml() const +{ + return d->statusHtml; +} + +QString ChatWindowStyle::getActionIncomingHtml() const +{ + return d->actionIncomingHtml; +} + +QString ChatWindowStyle::getActionOutgoingHtml() const +{ + return d->actionOutgoingHtml; +} + +bool ChatWindowStyle::hasActionTemplate() const +{ + return ( !d->actionIncomingHtml.isEmpty() && !d->actionOutgoingHtml.isEmpty() ); +} + +void ChatWindowStyle::listVariants() +{ + QString variantDirPath = d->baseHref + QString::fromUtf8("Variants/"); + QDir variantDir(variantDirPath); + + QStringList variantList = variantDir.entryList("*.css"); + QStringList::ConstIterator it, itEnd = variantList.constEnd(); + for(it = variantList.constBegin(); it != itEnd; ++it) + { + QString variantName = *it, variantPath; + // Retrieve only the file name. + variantName = variantName.left(variantName.findRev(".")); + // variantPath is relative to baseHref. + variantPath = QString("Variants/%1").arg(*it); + d->variantsList.insert(variantName, variantPath); + } +} + +void ChatWindowStyle::readStyleFiles() +{ + QString headerFile = d->baseHref + QString("Header.html"); + QString footerFile = d->baseHref + QString("Footer.html"); + QString incomingFile = d->baseHref + QString("Incoming/Content.html"); + QString nextIncomingFile = d->baseHref + QString("Incoming/NextContent.html"); + QString outgoingFile = d->baseHref + QString("Outgoing/Content.html"); + QString nextOutgoingFile = d->baseHref + QString("Outgoing/NextContent.html"); + QString statusFile = d->baseHref + QString("Status.html"); + QString actionIncomingFile = d->baseHref + QString("Incoming/Action.html"); + QString actionOutgoingFile = d->baseHref + QString("Outgoing/Action.html"); + + QFile fileAccess; + // First load header file. + if( QFile::exists(headerFile) ) + { + fileAccess.setName(headerFile); + fileAccess.open(IO_ReadOnly); + QTextStream headerStream(&fileAccess); + headerStream.setEncoding(QTextStream::UnicodeUTF8); + d->headerHtml = headerStream.read(); + kdDebug(14000) << k_funcinfo << "Header HTML: " << d->headerHtml << endl; + fileAccess.close(); + } + // Load Footer file + if( QFile::exists(footerFile) ) + { + fileAccess.setName(footerFile); + fileAccess.open(IO_ReadOnly); + QTextStream headerStream(&fileAccess); + headerStream.setEncoding(QTextStream::UnicodeUTF8); + d->footerHtml = headerStream.read(); + kdDebug(14000) << k_funcinfo << "Footer HTML: " << d->footerHtml << endl; + fileAccess.close(); + } + // Load incoming file + if( QFile::exists(incomingFile) ) + { + fileAccess.setName(incomingFile); + fileAccess.open(IO_ReadOnly); + QTextStream headerStream(&fileAccess); + headerStream.setEncoding(QTextStream::UnicodeUTF8); + d->incomingHtml = headerStream.read(); + kdDebug(14000) << k_funcinfo << "Incoming HTML: " << d->incomingHtml << endl; + fileAccess.close(); + } + // Load next Incoming file + if( QFile::exists(nextIncomingFile) ) + { + fileAccess.setName(nextIncomingFile); + fileAccess.open(IO_ReadOnly); + QTextStream headerStream(&fileAccess); + headerStream.setEncoding(QTextStream::UnicodeUTF8); + d->nextIncomingHtml = headerStream.read(); + kdDebug(14000) << k_funcinfo << "NextIncoming HTML: " << d->nextIncomingHtml << endl; + fileAccess.close(); + } + // Load outgoing file + if( QFile::exists(outgoingFile) ) + { + fileAccess.setName(outgoingFile); + fileAccess.open(IO_ReadOnly); + QTextStream headerStream(&fileAccess); + headerStream.setEncoding(QTextStream::UnicodeUTF8); + d->outgoingHtml = headerStream.read(); + kdDebug(14000) << k_funcinfo << "Outgoing HTML: " << d->outgoingHtml << endl; + fileAccess.close(); + } + // Load next outgoing file + if( QFile::exists(nextOutgoingFile) ) + { + fileAccess.setName(nextOutgoingFile); + fileAccess.open(IO_ReadOnly); + QTextStream headerStream(&fileAccess); + headerStream.setEncoding(QTextStream::UnicodeUTF8); + d->nextOutgoingHtml = headerStream.read(); + kdDebug(14000) << k_funcinfo << "NextOutgoing HTML: " << d->nextOutgoingHtml << endl; + fileAccess.close(); + } + // Load status file + if( QFile::exists(statusFile) ) + { + fileAccess.setName(statusFile); + fileAccess.open(IO_ReadOnly); + QTextStream headerStream(&fileAccess); + headerStream.setEncoding(QTextStream::UnicodeUTF8); + d->statusHtml = headerStream.read(); + kdDebug(14000) << k_funcinfo << "Status HTML: " << d->statusHtml << endl; + fileAccess.close(); + } + + // Load Action Incoming file + if( QFile::exists(actionIncomingFile) ) + { + fileAccess.setName(actionIncomingFile); + fileAccess.open(IO_ReadOnly); + QTextStream headerStream(&fileAccess); + headerStream.setEncoding(QTextStream::UnicodeUTF8); + d->actionIncomingHtml = headerStream.read(); + kdDebug(14000) << k_funcinfo << "ActionIncoming HTML: " << d->actionIncomingHtml << endl; + fileAccess.close(); + } + // Load Action Outgoing file + if( QFile::exists(actionOutgoingFile) ) + { + fileAccess.setName(actionOutgoingFile); + fileAccess.open(IO_ReadOnly); + QTextStream headerStream(&fileAccess); + headerStream.setEncoding(QTextStream::UnicodeUTF8); + d->actionOutgoingHtml = headerStream.read(); + kdDebug(14000) << k_funcinfo << "ActionOutgoing HTML: " << d->actionOutgoingHtml << endl; + fileAccess.close(); + } +} + +void ChatWindowStyle::reload() +{ + d->variantsList.clear(); + readStyleFiles(); + listVariants(); +} diff --git a/kopete/kopete/chatwindow/kopetechatwindowstyle.h b/kopete/kopete/chatwindow/kopetechatwindowstyle.h new file mode 100644 index 00000000..ca5a0863 --- /dev/null +++ b/kopete/kopete/chatwindow/kopetechatwindowstyle.h @@ -0,0 +1,129 @@ + /* + kopetechatwindowstyle.h - A Chat Window Style. + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef KOPETECHATWINDOWSTYLE_H +#define KOPETECHATWINDOWSTYLE_H + +#include +#include + + +/** + * This class represent a single chat window style. + * + * @author Michaël Larouche + */ +class ChatWindowStyle +{ +public: + /** + * StyleVariants is a typedef to a QMap + * key = Variant Name + * value = Path to variant CSS file. + * Path is relative to Ressources directory. + */ + typedef QMap StyleVariants; + + /** + * This enum specifies the mode of the constructor + * - StyleBuildFast : Build the style the fatest possible + * - StyleBuildNormal : List all variants of this style. Require a async dir list. + */ + enum StyleBuildMode { StyleBuildFast, StyleBuildNormal}; + + /** + * @brief Build a single chat window style. + * + */ + ChatWindowStyle(const QString &stylePath, int styleBuildMode = StyleBuildNormal); + ChatWindowStyle(const QString &stylePath, const QString &variantPath, int styleBuildMode = StyleBuildFast); + ~ChatWindowStyle(); + + /** + * Get the list of all variants for this theme. + * If the variant aren't listed, it call the lister + * before returning the list of the Variants. + * If the variant are listed, it just return the cached + * variant list. + * @return the StyleVariants QMap. + */ + StyleVariants getVariants(); + + /** + * Get the style path. + * The style path points to the directory where the style is located. + * ex: ~/.kde/share/apps/kopete/styles/StyleName/ + * + * @return the style path based. + */ + QString getStylePath() const; + + /** + * Get the style ressource directory. + * Ressources directory is the base where all CSS, HTML and images are located. + * + * Adium(and now Kopete too) style directories are disposed like this: + * StyleName/ + * Contents/ + * Resources/ + * + * @return the path to the the ressource directory. + */ + QString getStyleBaseHref() const; + + QString getHeaderHtml() const; + QString getFooterHtml() const; + QString getIncomingHtml() const; + QString getNextIncomingHtml() const; + QString getOutgoingHtml() const; + QString getNextOutgoingHtml() const; + QString getStatusHtml() const; + + QString getActionIncomingHtml() const; + QString getActionOutgoingHtml() const; + + /** + * Check if the style has the support for Kopete Action template (Kopete extension) + * @return true if the style has Action template. + */ + bool hasActionTemplate() const; + + /** + * Reload style from disk. + */ + void reload(); +private: + /** + * Read style HTML files from disk + */ + void readStyleFiles(); + + /** + * Init this class + */ + void init(const QString &stylePath, int styleBuildMode); + + /** + * List available variants for the current style. + */ + void listVariants(); + +private: + class Private; + Private *d; +}; + +#endif diff --git a/kopete/kopete/chatwindow/kopetechatwindowstylemanager.cpp b/kopete/kopete/chatwindow/kopetechatwindowstylemanager.cpp new file mode 100644 index 00000000..71032ea3 --- /dev/null +++ b/kopete/kopete/chatwindow/kopetechatwindowstylemanager.cpp @@ -0,0 +1,390 @@ + /* + kopetechatwindowstylemanager.cpp - Manager all chat window styles + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopetechatwindowstylemanager.h" + +// Qt includes +#include + +// KDE includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopetechatwindowstyle.h" + +class ChatWindowStyleManager::Private +{ +public: + Private() + : styleDirLister(0) + {} + + ~Private() + { + if(styleDirLister) + { + styleDirLister->deleteLater(); + } + + QMap::Iterator styleIt, styleItEnd = stylePool.end(); + for(styleIt = stylePool.begin(); styleIt != styleItEnd; ++styleIt) + { + delete styleIt.data(); + } + } + + KDirLister *styleDirLister; + StyleList availableStyles; + + // key = style path, value = ChatWindowStyle instance + QMap stylePool; + + QValueStack styleDirs; +}; + +static KStaticDeleter ChatWindowStyleManagerstaticDeleter; + +ChatWindowStyleManager *ChatWindowStyleManager::s_self = 0; + +ChatWindowStyleManager *ChatWindowStyleManager::self() +{ + if( !s_self ) + { + ChatWindowStyleManagerstaticDeleter.setObject( s_self, new ChatWindowStyleManager() ); + } + + return s_self; +} + +ChatWindowStyleManager::ChatWindowStyleManager(QObject *parent, const char *name) + : QObject(parent, name), d(new Private()) +{ + kdDebug(14000) << k_funcinfo << endl; + loadStyles(); +} + +ChatWindowStyleManager::~ChatWindowStyleManager() +{ + kdDebug(14000) << k_funcinfo << endl; + delete d; +} + +void ChatWindowStyleManager::loadStyles() +{ + QStringList chatStyles = KGlobal::dirs()->findDirs( "appdata", QString::fromUtf8( "styles" ) ); + QString localStyleDir( locateLocal( "appdata", QString::fromUtf8("styles/"),true) ); + if( !chatStyles.contains(localStyleDir)) + chatStyles<styleDirs.push( KURL(*it) ); + } + + d->styleDirLister = new KDirLister(this); + d->styleDirLister->setDirOnlyMode(true); + + connect(d->styleDirLister, SIGNAL(newItems(const KFileItemList &)), this, SLOT(slotNewStyles(const KFileItemList &))); + connect(d->styleDirLister, SIGNAL(completed()), this, SLOT(slotDirectoryFinished())); + + if( !d->styleDirs.isEmpty() ) + d->styleDirLister->openURL(d->styleDirs.pop(), true); +} + +ChatWindowStyleManager::StyleList ChatWindowStyleManager::getAvailableStyles() +{ + return d->availableStyles; +} + +int ChatWindowStyleManager::installStyle(const QString &styleBundlePath) +{ + QString localStyleDir( locateLocal( "appdata", QString::fromUtf8("styles/") ) ); + + KArchiveEntry *currentEntry = 0L; + KArchiveDirectory* currentDir = 0L; + KArchive *archive = 0L; + + if( localStyleDir.isEmpty() ) + { + return StyleNoDirectoryValid; + } + + // Find mimetype for current bundle. ZIP and KTar need separate constructor + QString currentBundleMimeType = KMimeType::findByPath(styleBundlePath, 0, false)->name(); + if(currentBundleMimeType == "application/x-zip") + { + archive = new KZip(styleBundlePath); + } + else if( currentBundleMimeType == "application/x-tgz" || currentBundleMimeType == "application/x-tbz" || currentBundleMimeType == "application/x-gzip" || currentBundleMimeType == "application/x-bzip2" ) + { + archive = new KTar(styleBundlePath); + } + else + { + return StyleCannotOpen; + } + + if ( !archive->open(IO_ReadOnly) ) + { + delete archive; + + return StyleCannotOpen; + } + + const KArchiveDirectory* rootDir = archive->directory(); + + // Ok where we go to check if the archive is valid. + // Each time we found a correspondance to a theme bundle, we add a point to validResult. + // A valid style bundle must have: + // -a Contents, Contents/Resources, Co/Res/Incoming, Co/Res/Outgoing dirs + // main.css, Footer.html, Header.html, Status.html files in Contents/Ressources. + // So for a style bundle to be valid, it must have a result greather than 8, because we test for 8 required entry. + int validResult = 0; + QStringList entries = rootDir->entries(); + // Will be reused later. + QStringList::Iterator entriesIt, entriesItEnd = entries.end(); + for(entriesIt = entries.begin(); entriesIt != entries.end(); ++entriesIt) + { + currentEntry = const_cast(rootDir->entry(*entriesIt)); +// kdDebug() << k_funcinfo << "Current entry name: " << currentEntry->name() << endl; + if (currentEntry->isDirectory()) + { + currentDir = dynamic_cast( currentEntry ); + if (currentDir) + { + if( currentDir->entry(QString::fromUtf8("Contents")) ) + { +// kdDebug() << k_funcinfo << "Contents found" << endl; + validResult += 1; + } + if( currentDir->entry(QString::fromUtf8("Contents/Resources")) ) + { +// kdDebug() << k_funcinfo << "Contents/Resources found" << endl; + validResult += 1; + } + if( currentDir->entry(QString::fromUtf8("Contents/Resources/Incoming")) ) + { +// kdDebug() << k_funcinfo << "Contents/Resources/Incoming found" << endl; + validResult += 1; + } + if( currentDir->entry(QString::fromUtf8("Contents/Resources/Outgoing")) ) + { +// kdDebug() << k_funcinfo << "Contents/Resources/Outgoing found" << endl; + validResult += 1; + } + if( currentDir->entry(QString::fromUtf8("Contents/Resources/main.css")) ) + { +// kdDebug() << k_funcinfo << "Contents/Resources/main.css found" << endl; + validResult += 1; + } + if( currentDir->entry(QString::fromUtf8("Contents/Resources/Footer.html")) ) + { +// kdDebug() << k_funcinfo << "Contents/Resources/Footer.html found" << endl; + validResult += 1; + } + if( currentDir->entry(QString::fromUtf8("Contents/Resources/Status.html")) ) + { +// kdDebug() << k_funcinfo << "Contents/Resources/Status.html found" << endl; + validResult += 1; + } + if( currentDir->entry(QString::fromUtf8("Contents/Resources/Header.html")) ) + { +// kdDebug() << k_funcinfo << "Contents/Resources/Header.html found" << endl; + validResult += 1; + } + if( currentDir->entry(QString::fromUtf8("Contents/Resources/Incoming/Content.html")) ) + { +// kdDebug() << k_funcinfo << "Contents/Resources/Incoming/Content.html found" << endl; + validResult += 1; + } + if( currentDir->entry(QString::fromUtf8("Contents/Resources/Outgoing/Content.html")) ) + { +// kdDebug() << k_funcinfo << "Contents/Resources/Outgoing/Content.html found" << endl; + validResult += 1; + } + } + } + } +// kdDebug() << k_funcinfo << "Valid result: " << QString::number(validResult) << endl; + // The archive is a valid style bundle. + if(validResult >= 8) + { + bool installOk = false; + for(entriesIt = entries.begin(); entriesIt != entries.end(); ++entriesIt) + { + currentEntry = const_cast(rootDir->entry(*entriesIt)); + if(currentEntry && currentEntry->isDirectory()) + { + // Ignore this MacOS X "garbage" directory in zip. + if(currentEntry->name() == QString::fromUtf8("__MACOSX")) + { + continue; + } + else + { + currentDir = dynamic_cast(currentEntry); + if(currentDir) + { + currentDir->copyTo(localStyleDir + currentDir->name()); + installOk = true; + } + } + } + } + + archive->close(); + delete archive; + + if(installOk) + return StyleInstallOk; + else + return StyleUnknow; + } + else + { + archive->close(); + delete archive; + + return StyleNotValid; + } + + if(archive) + { + archive->close(); + delete archive; + } + + return StyleUnknow; +} + +bool ChatWindowStyleManager::removeStyle(const QString &stylePath) +{ + // Find for the current style in avaiableStyles map. + KURL urlStyle(stylePath); + QString styleName=urlStyle.fileName(); + StyleList::Iterator foundStyle = d->availableStyles.find(styleName); + // QMap iterator return end() if it found no item. + if(foundStyle != d->availableStyles.end()) + { + d->availableStyles.remove(foundStyle); + + // Remove and delete style from pool if needed. + if( d->stylePool.contains(stylePath) ) + { + ChatWindowStyle *deletedStyle = d->stylePool[stylePath]; + d->stylePool.remove(stylePath); + delete deletedStyle; + } + + // Do the actual deletion of the directory style. + return KIO::NetAccess::del( urlStyle, 0 ); + } + else + { + return false; + } +} + +ChatWindowStyle *ChatWindowStyleManager::getStyleFromPool(const QString &stylePath) +{ + if( d->stylePool.contains(stylePath) ) + { + // NOTE: This is a hidden config switch for style developers + // Check in the config if the cache is disabled. + // if the cache is disabled, reload the style everytime it's getted. + KConfig *config = KGlobal::config(); + config->setGroup("KopeteStyleDebug"); + bool disableCache = config->readBoolEntry("disableStyleCache", false); + if(disableCache) + { + d->stylePool[stylePath]->reload(); + } + + return d->stylePool[stylePath]; + } + else + { + // Build a chat window style and list its variants, then add it to the pool. + ChatWindowStyle *style = new ChatWindowStyle(stylePath, ChatWindowStyle::StyleBuildNormal); + d->stylePool.insert(stylePath, style); + + return style; + } + + return 0; +} + +void ChatWindowStyleManager::slotNewStyles(const KFileItemList &dirList) +{ + KFileItem *item; + QPtrListIterator it( dirList ); + while( (item = it.current()) != 0 ) + { + // Ignore data dir(from deprecated XSLT themes) + if( !item->url().fileName().contains(QString::fromUtf8("data")) ) + { + kdDebug(14000) << k_funcinfo << "Listing: " << item->url().fileName() << endl; + // If the style path is already in the pool, that's mean the style was updated on disk + // Reload the style + if( d->stylePool.contains(item->url().path()) ) + { + kdDebug(14000) << k_funcinfo << "Updating style: " << item->url().path() << endl; + + d->stylePool[item->url().path()]->reload(); + + // Add to avaialble if required. + if( !d->availableStyles.contains(item->url().fileName()) ) + d->availableStyles.insert(item->url().fileName(), item->url().path()); + } + else + { + // TODO: Use name from Info.plist + d->availableStyles.insert(item->url().fileName(), item->url().path()); + } + } + ++it; + } +} + +void ChatWindowStyleManager::slotDirectoryFinished() +{ + // Start another scanning if the directories stack is not empty + if( !d->styleDirs.isEmpty() ) + { + kdDebug(14000) << k_funcinfo << "Starting another directory." << endl; + d->styleDirLister->openURL(d->styleDirs.pop(), true); + } + else + { + emit loadStylesFinished(); + } +} + +#include "kopetechatwindowstylemanager.moc" diff --git a/kopete/kopete/chatwindow/kopetechatwindowstylemanager.h b/kopete/kopete/chatwindow/kopetechatwindowstylemanager.h new file mode 100644 index 00000000..4b21c79b --- /dev/null +++ b/kopete/kopete/chatwindow/kopetechatwindowstylemanager.h @@ -0,0 +1,147 @@ + /* + kopetechatwindowstylemanager.h - Manager all chat window styles + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETECHATWINDOWSTYLEMANAGER_H +#define KOPETECHATWINDOWSTYLEMANAGER_H + +#include +#include +#include +#include + +class ChatWindowStyle; +/** + * Sigleton class that handle Chat Window styles. + * It use style absolute path to avoid unexpected behavior that could happen when using style name. + * + * It can install, delete styles. The styles are managed in a pool, they are only retrieved on demand. + * + * Use getStyleFromPool to retrieve a ChatWindowStyle instance. Do not delete the returned instance, it + * is handled by this class. + * + * When called the first time, it list all the available styles in $KDEDATADIR/kopete/styles and + * KDirWatch (via KDirLister) watch for new styles. + * + * If you want to keep a trace of avaiable styles, connect to loadStylesFinished() signal. + * It is called when KDirLister finish a job(ex: on new directory). + * + * @author Michaël Larouche + */ +class KOPETE_EXPORT ChatWindowStyleManager : public QObject +{ + Q_OBJECT +public: + /** + * StyleList typedef (a QMap) + * key = Name of the style (currently the directory name) + * value = Path to the style + */ + typedef QMap StyleList; + + /** + * The StyleInstallStatus enum. It gives better return value for installStyle(). + * - StyleInstallOk : The install went fine. + * - StyleNotValid : The archive didn't contain a valid Chat Window style. + * - StyleNoDirectoryValid : It didn't find a suitable directory to install the theme. + * - StyleCannotOpen : The archive couldn't be openned. + * - StyleUnknow : Unknow error. + */ + enum StyleInstallStatus { StyleInstallOk = 0, StyleNotValid, StyleNoDirectoryValid, StyleCannotOpen, StyleUnknow }; + + /** + * Destructor. + */ + ~ChatWindowStyleManager(); + + /** + * Singleton access to this class. + * @return the single instance of this class. + */ + static ChatWindowStyleManager *self(); + + /** + * List all availables styles. + * Init KDirLister and thus KDirWatch that watch for new styles. + */ + void loadStyles(); + + /** + * Get all available styles. + */ + StyleList getAvailableStyles(); + +public slots: + /** + * Install a new style into user style directory + * Note that you must pass a path to a archive. + * + * @param styleBundlePath Path to the container file to install. + * @return A status code from StyleInstallStatus enum. + */ + int installStyle(const QString &styleBundlePath); + + /** + * Remove a style from user style directory + * + * @param stylePath the path of the style to remove. + * @return true if the deletion went without problems. + */ + bool removeStyle(const QString &stylePath); + + /** + * Get a instance of a ChatWindowStyle from the pool. + * If they are no instance for the specified style, it gets created. + * DO NOT DELETE the resulting pointer, it is handled by this class. + * + * @param stylePath Path for the specified style. Name can be ambigous. + * @return the instance of ChatWindow for the specified style. DO NOT DELETE IT. + */ + ChatWindowStyle *getStyleFromPool(const QString &stylePath); + +signals: + /** + * This signal is emitted when all styles finished to list. + * Used to inform and/or update GUI. + */ + void loadStylesFinished(); + +private slots: + /** + * KDirLister found new files. + * @param dirList new files found. + */ + void slotNewStyles(const KFileItemList &dirList); + /** + * KDirLister finished a job. + * Emit loadStylesFinished() if they are no directory left in the stack. + */ + void slotDirectoryFinished(); + +private: + /** + * Private constructor(it's a singleton class) + * Call loadStyles() to list all avaiable styles. + */ + ChatWindowStyleManager(QObject *parent = 0, const char *name = 0); + + static ChatWindowStyleManager *s_self; + + class Private; + Private *d; +}; + +#endif diff --git a/kopete/kopete/chatwindow/kopeteemailwindow.cpp b/kopete/kopete/chatwindow/kopeteemailwindow.cpp new file mode 100644 index 00000000..84b71b16 --- /dev/null +++ b/kopete/kopete/chatwindow/kopeteemailwindow.cpp @@ -0,0 +1,565 @@ +/* + kopeteemailwindow.cpp - Kopete "email window" for single-shot messages + + Copyright (c) 2002 by Daniel Stone + Copyright (c) 2003 by Jason Keirstead + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopeteemailwindow.h" + +#include "chatmessagepart.h" +#include "chattexteditpart.h" +#include "kopetecontact.h" +#include "kopetemetacontact.h" +#include "kopeteemoticonaction.h" +#include "kopetechatsession.h" +#include "kopeteplugin.h" +#include "kopetepluginmanager.h" +#include "kopeteprefs.h" +#include "kopetestdaction.h" +#include "kopeteviewmanager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +typedef KGenericFactory EmailWindowPluginFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_emailwindow, EmailWindowPluginFactory( "kopete_emailwindow" ) ) + +EmailWindowPlugin::EmailWindowPlugin(QObject *parent, const char *name, const QStringList &) : + Kopete::ViewPlugin( EmailWindowPluginFactory::instance(), parent, name ) +{} + +KopeteView* EmailWindowPlugin::createView( Kopete::ChatSession *manager ) +{ + //TODO: foreignMessage, how will we do this cleanly? + return (KopeteView*)new KopeteEmailWindow(manager,this, false); +} + +class KopeteEmailWindow::Private +{ +public: + QValueList messageQueue; + bool showingMessage; + bool sendInProgress; + bool visible; + uint queuePosition; + KPushButton *btnReplySend; + KPushButton *btnReadNext; + KPushButton *btnReadPrev; + QSplitter *split; + ChatMessagePart *messagePart; + KopeteEmailWindow::WindowMode mode; + KAction *chatSend; + QLabel *anim; + QMovie animIcon; + QPixmap normalIcon; + QString unreadMessageFrom; + ChatTextEditPart *editPart; + + KActionMenu *actionActionMenu; + KopeteEmoticonAction *actionSmileyMenu; +}; + +KopeteEmailWindow::KopeteEmailWindow( Kopete::ChatSession *manager, EmailWindowPlugin *parent, bool foreignMessage ) + : KParts::MainWindow( ), KopeteView( manager, parent ), d( new Private ) +{ + QVBox *v = new QVBox( this ); + setCentralWidget( v ); + + setMinimumSize( QSize( 75, 20 ) ); + + d->split = new QSplitter( v ); + d->split->setOrientation( QSplitter::Vertical ); + + d->messagePart = new ChatMessagePart( manager, d->split, "messagePart" ); + + // FIXME: should this be in ChatView too? maybe move to ChatMessagePart? + d->messagePart->view()->setMarginWidth( 4 ); + d->messagePart->view()->setMarginHeight( 4 ); + d->messagePart->view()->setMinimumSize( QSize( 75, 20 ) ); + + d->editPart = new ChatTextEditPart( manager, d->split, "editPart" ); + + /* + FIXME: dude, wtf? + QDomDocument doc = d->editPart->domDocument(); + QDomNode menu = doc.documentElement().firstChild(); + menu.removeChild( menu.firstChild() ); // Remove File + menu.removeChild( menu.firstChild() ); // Remove Edit + menu.removeChild( menu.firstChild() ); // Remove View + menu.removeChild( menu.lastChild() ); //Remove Help + + doc.documentElement().removeChild( doc.documentElement().childNodes().item(1) ); //Remove MainToolbar + doc.documentElement().removeChild( doc.documentElement().lastChild() ); // Remove Edit popup + */ + connect( d->editPart, SIGNAL( messageSent( Kopete::Message & ) ), + this, SIGNAL( messageSent( Kopete::Message & ) ) ); + connect( d->editPart, SIGNAL( canSendChanged( bool ) ), + this, SLOT( slotUpdateReplySend() ) ); + connect( d->editPart, SIGNAL( typing(bool) ), + manager, SIGNAL( typing(bool) ) ); + + //Connections to the manager and the ViewManager that every view should have + connect( this, SIGNAL( closing( KopeteView * ) ), + KopeteViewManager::viewManager(), SLOT( slotViewDestroyed( KopeteView * ) ) ); + connect( this, SIGNAL( activated( KopeteView * ) ), + KopeteViewManager::viewManager(), SLOT( slotViewActivated( KopeteView * ) ) ); + connect( this, SIGNAL( messageSent(Kopete::Message &) ), + manager, SLOT( sendMessage(Kopete::Message &) ) ); + connect( manager, SIGNAL( messageSuccess() ), + this, SLOT( messageSentSuccessfully() )); + + QWidget *containerWidget = new QWidget( v ); + containerWidget->setSizePolicy( QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum) ); + + QHBoxLayout *h = new QHBoxLayout( containerWidget, 4, 4 ); + h->addStretch(); + + d->btnReadPrev = new KPushButton( i18n( "<< Prev" ), containerWidget ); + connect( d->btnReadPrev, SIGNAL( pressed() ), this, SLOT( slotReadPrev() ) ); + h->addWidget( d->btnReadPrev, 0, Qt::AlignRight | Qt::AlignVCenter ); + d->btnReadPrev->setEnabled( false ); + + d->btnReadNext = new KPushButton( i18n( "(0) Next >>" ), containerWidget ); + connect( d->btnReadNext, SIGNAL( pressed() ), this, SLOT( slotReadNext() ) ); + h->addWidget( d->btnReadNext, 0, Qt::AlignRight | Qt::AlignVCenter ); + + d->btnReplySend = new KPushButton( containerWidget ); + connect( d->btnReplySend, SIGNAL( pressed() ), this, SLOT( slotReplySend() ) ); + h->addWidget( d->btnReplySend, 0, Qt::AlignRight | Qt::AlignVCenter ); + + initActions(); + setWFlags(Qt::WDestructiveClose); + + d->showingMessage = false; + + if( foreignMessage ) + toggleMode( Read ); + else + toggleMode( Send ); + + KConfig *config = KGlobal::config(); + applyMainWindowSettings( config, QString::fromLatin1( "KopeteEmailWindow" ) ); + + d->sendInProgress = false; + + toolBar()->alignItemRight( 99 ); + + d->visible = false; + d->queuePosition = 0; + + setCaption( manager->displayName() ); + + slotUpdateReplySend(); +} + +KopeteEmailWindow::~KopeteEmailWindow() +{ + emit( closing( this ) ); + + // saves menubar, toolbar and statusbar setting + KConfig *config = KGlobal::config(); + saveMainWindowSettings( config, QString::fromLatin1( "KopeteEmailWindow" ) ); + config->sync(); + + delete d; +} + +void KopeteEmailWindow::initActions(void) +{ + KActionCollection *coll = actionCollection(); + + d->chatSend = new KAction( i18n( "&Send Message" ), QString::fromLatin1( "mail_send" ), 0, + this, SLOT( slotReplySend() ), coll, "chat_send" ); + //Default to 'Return' for sending messages + d->chatSend->setShortcut( QKeySequence( Key_Return ) ); + + KStdAction::quit ( this, SLOT( slotCloseView() ), coll ); + + KStdAction::cut( d->editPart->widget(), SLOT( cut() ), coll ); + KStdAction::copy( this, SLOT(slotCopy()), coll); + KStdAction::paste( d->editPart->widget(), SLOT( paste() ), coll ); + + new KAction( i18n( "&Set Font..." ), QString::fromLatin1( "charset" ), 0, + d->editPart, SLOT( setFont() ), coll, "format_font" ); + new KAction( i18n( "Set Text &Color..." ), QString::fromLatin1( "pencil" ), 0, + d->editPart, SLOT( setFgColor() ), coll, "format_color" ); + new KAction( i18n( "Set &Background Color..." ), QString::fromLatin1( "fill" ), 0, + d->editPart, SLOT( setBgColor() ), coll, "format_bgcolor" ); + + KStdAction::showMenubar( this, SLOT( slotViewMenuBar() ), coll ); + setStandardToolBarMenuEnabled( true ); + + d->actionSmileyMenu = new KopeteEmoticonAction( coll, "format_smiley" ); + d->actionSmileyMenu->setDelayed( false ); + connect(d->actionSmileyMenu, SIGNAL(activated(const QString &)), this, SLOT(slotSmileyActivated(const QString &))); + + // add configure key bindings menu item + KStdAction::keyBindings( guiFactory(), SLOT( configureShortcuts() ), coll ); + KStdAction::configureToolbars(this, SLOT( slotConfToolbar() ), coll); + //FIXME: no longer works? + KopeteStdAction::preferences( coll , "settings_prefs" ); + + // The animated toolbarbutton + d->normalIcon = QPixmap( BarIcon( QString::fromLatin1( "kopete" ) ) ); + d->animIcon = KGlobal::iconLoader()->loadMovie( QString::fromLatin1( "newmessage" ), KIcon::Toolbar); + d->animIcon.pause(); + + d->anim = new QLabel( this, "kde toolbar widget" ); + d->anim->setMargin( 5 ); + d->anim->setPixmap( d->normalIcon ); + new KWidgetAction( d->anim, i18n("Toolbar Animation"), 0, 0, 0, coll, "toolbar_animation" ); + + setXMLFile( QString::fromLatin1( "kopeteemailwindow.rc" ) ); + createGUI( d->editPart ); + //createGUI( QString::fromLatin1( "kopeteemailwindow.rc" ) ); + guiFactory()->addClient(m_manager); +} + +void KopeteEmailWindow::closeEvent( QCloseEvent *e ) +{ + // DO NOT call base class's closeEvent - see comment in KopeteApplication constructor for reason + + // Save settings if auto-save is enabled, and settings have changed + if ( settingsDirty() && autoSaveSettings() ) + saveAutoSaveSettings(); + + e->accept(); +} + +void KopeteEmailWindow::slotViewMenuBar() +{ + if( !menuBar()->isHidden() ) + menuBar()->hide(); + else + menuBar()->show(); +} + +void KopeteEmailWindow::slotSmileyActivated(const QString &sm ) +{ + if ( !sm.isNull() ) + d->editPart->addText( sm ); +} + +void KopeteEmailWindow::slotConfToolbar() +{ + saveMainWindowSettings(KGlobal::config(), QString::fromLatin1( "KopeteEmailWindow" )); + KEditToolbar *dlg = new KEditToolbar(actionCollection(), QString::fromLatin1("kopeteemailwindow.rc") ); + if (dlg->exec()) + { + createGUI( d->editPart ); + applyMainWindowSettings(KGlobal::config(), QString::fromLatin1( "KopeteEmailWindow" )); + } + delete dlg; +} + +void KopeteEmailWindow::slotCopy() +{ +// kdDebug(14010) << k_funcinfo << endl; + + if ( d->messagePart->hasSelection() ) + d->messagePart->copy(); + else + d->editPart->widget()->copy(); +} + +void KopeteEmailWindow::appendMessage(Kopete::Message &message) +{ + if( message.from() != m_manager->myself() ) + { + if( d->mode == Send ) + toggleMode( Reply ); + + d->messageQueue.append( message ); + + if( !d->showingMessage ) + slotReadNext(); + else + { + d->btnReadNext->setPaletteForegroundColor( QColor("red") ); + updateNextButton(); + } + + d->unreadMessageFrom = message.from()->metaContact() ? + message.from()->metaContact()->displayName() : message.from()->contactId(); + QTimer::singleShot( 1000, this, SLOT(slotMarkMessageRead()) ); + } +} + +void KopeteEmailWindow::slotMarkMessageRead() +{ + d->unreadMessageFrom = QString::null; +} + +void KopeteEmailWindow::updateNextButton() +{ + if( d->queuePosition == d->messageQueue.count() ) + { + d->btnReadNext->setEnabled( false ); + + d->btnReadNext->setPaletteForegroundColor( KGlobalSettings::textColor() ); + } + else + d->btnReadNext->setEnabled( true ); + + if( d->queuePosition == 1 ) + d->btnReadPrev->setEnabled( false ); + else + d->btnReadPrev->setEnabled( true ); + + d->btnReadNext->setText( i18n( "(%1) Next >>" ).arg( d->messageQueue.count() - d->queuePosition ) ); +} + +void KopeteEmailWindow::slotUpdateReplySend() +{ + bool canSend; + if( d->mode == Read ) + canSend = true; + else + canSend = d->editPart->canSend(); + + d->btnReplySend->setEnabled( canSend ); + d->chatSend->setEnabled( canSend ); +} + +void KopeteEmailWindow::slotReadNext() +{ +// kdDebug(14010) << k_funcinfo << endl; + + d->showingMessage = true; + + d->queuePosition++; + + writeMessage( (*d->messageQueue.at( d->queuePosition - 1 )) ); + + updateNextButton(); +} + +void KopeteEmailWindow::slotReadPrev() +{ +// kdDebug(14010) << k_funcinfo << endl; + + d->showingMessage = true; + + d->queuePosition--; + + writeMessage( (*d->messageQueue.at( d->queuePosition - 1 )) ); + + updateNextButton(); +} + +void KopeteEmailWindow::writeMessage( Kopete::Message &msg ) +{ + d->messagePart->clear(); + d->messagePart->appendMessage( msg ); +} + +void KopeteEmailWindow::sendMessage() +{ + if ( !d->editPart->canSend() ) + return; + d->sendInProgress = true; + d->anim->setMovie( d->animIcon ); + d->animIcon.unpause(); + d->editPart->widget()->setEnabled( false ); + d->editPart->sendMessage(); +} + +void KopeteEmailWindow::messageSentSuccessfully() +{ + d->sendInProgress = false; + d->anim->setPixmap( d->normalIcon ); + d->animIcon.pause(); + closeView(); +} + +bool KopeteEmailWindow::closeView( bool force ) +{ + int response = KMessageBox::Continue; + + if( !force ) + { + if( m_manager->members().count() > 1 ) + { + QString shortCaption = caption(); + if( shortCaption.length() > 40 ) + shortCaption = shortCaption.left( 40 ) + QString::fromLatin1("..."); + + response = KMessageBox::warningContinueCancel(this, i18n("You are about to leave the group chat session %1.
    " + "You will not receive future messages from this conversation.
    ").arg(shortCaption), i18n("Closing Group Chat"), + i18n("Cl&ose Chat"), QString::fromLatin1("AskCloseGroupChat")); + } + + if( !d->unreadMessageFrom.isNull() && ( response == KMessageBox::Continue ) ) + { + response = KMessageBox::warningContinueCancel(this, i18n("You have received a message from %1 in the last " + "second. Are you sure you want to close this chat?").arg(d->unreadMessageFrom), i18n("Unread Message"), + i18n("Cl&ose Chat"), QString::fromLatin1("AskCloseChatRecentMessage")); + } + + if( d->sendInProgress && ( response == KMessageBox::Continue ) ) + { + response = KMessageBox::warningContinueCancel(this, i18n("You have a message send in progress, which will be " + "aborted if this chat is closed. Are you sure you want to close this chat?"), i18n("Message in Transit"), + i18n("Cl&ose Chat"), QString::fromLatin1("AskCloseChatMessageInProgress") ); + } + } + + if( response == KMessageBox::Continue ) + { + d->visible = false; + deleteLater(); + return true; + } + else + { + d->editPart->widget()->setEnabled( true ); + } + + return false; +} + +void KopeteEmailWindow::toggleMode( WindowMode newMode ) +{ + d->mode = newMode; + + switch( d->mode ) + { + case Send: + d->btnReplySend->setText( i18n( "Send" ) ); + d->editPart->widget()->show(); + d->messagePart->view()->hide(); + d->btnReadNext->hide(); + d->btnReadPrev->hide(); + break; + case Read: + d->btnReplySend->setText( i18n( "Reply" ) ); + d->editPart->widget()->hide(); + d->messagePart->view()->show(); + d->btnReadNext->show(); + d->btnReadPrev->show(); + break; + case Reply: + QValueList splitPercent; + // FIXME: should be saved and restored + splitPercent.append(50); + splitPercent.append(50); + d->btnReplySend->setText( i18n( "Send" ) ); + d->editPart->widget()->show(); + d->messagePart->view()->show(); + d->btnReadNext->show(); + d->btnReadPrev->show(); + d->split->setSizes( splitPercent ); + d->editPart->widget()->setFocus(); + break; + } + slotUpdateReplySend(); +} + +void KopeteEmailWindow::slotReplySend() +{ + if( d->mode == Read ) + toggleMode( Reply ); + else + sendMessage(); +} + +//FIXME: Activate bool no longer needed due to setActiveWindow not being allowed +void KopeteEmailWindow::raise(bool activate) +{ + makeVisible(); + + if ( !KWin::windowInfo( winId(), NET::WMDesktop ).onAllDesktops() ) + KWin::setOnDesktop( winId(), KWin::currentDesktop() ); + + KMainWindow::raise(); + + /* Removed Nov 2003 + According to Zack, the user double-clicking a contact is not valid reason for a non-pager + to grab window focus. While I don't agree with this, and it runs contradictory to every other + IM out there, commenting this code out to agree with KWin policy. + + Redirect any bugs relating to the widnow now not grabbing focus on clicking a contact to KWin. + - Jason K + */ + + //Will not activate window if user was typing + if(activate) + KWin::activateWindow( winId() ); +} + +void KopeteEmailWindow::windowActivationChange( bool ) +{ + if( isActiveWindow() ) + emit( activated( static_cast(this) ) ); +} + +void KopeteEmailWindow::makeVisible() +{ +// kdDebug(14010) << k_funcinfo << endl; + d->visible = true; + show(); +} + +bool KopeteEmailWindow::isVisible() +{ + return d->visible; +} + +Kopete::Message KopeteEmailWindow::currentMessage() +{ + return d->editPart->contents(); +} + +void KopeteEmailWindow::setCurrentMessage( const Kopete::Message &newMessage ) +{ + d->editPart->setContents( newMessage ); +} + +void KopeteEmailWindow::slotCloseView() +{ + closeView(); +} + + +#include "kopeteemailwindow.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/kopeteemailwindow.h b/kopete/kopete/chatwindow/kopeteemailwindow.h new file mode 100644 index 00000000..43908645 --- /dev/null +++ b/kopete/kopete/chatwindow/kopeteemailwindow.h @@ -0,0 +1,104 @@ +/* + kopeteemailwindow.h - Kopete "email window" for single-shot messages + + Copyright (c) 2002 by Daniel Stone + Copyright (c) 2003 by Jason Keirstead + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEEMAILWINDOW_H +#define KOPETEEMAILWINDOW_H + +#include "kopeteview.h" +#include "kopeteviewplugin.h" +#include +#include + +namespace KParts { struct URLArgs; } +class EmailWindowPlugin; + +class KopeteEmailWindow : KParts::MainWindow, public KopeteView +{ + Q_OBJECT + +public: + enum WindowMode { Send, Read, Reply }; + + KopeteEmailWindow( Kopete::ChatSession *, EmailWindowPlugin *parent, bool foreignMessage ); + ~KopeteEmailWindow(); + + virtual Kopete::Message currentMessage(); + virtual void setCurrentMessage( const Kopete::Message &newMessage ); + virtual void raise(bool activate=false); + virtual void makeVisible(); + virtual bool closeView( bool force = false ); + virtual bool isVisible(); + virtual QWidget *mainWidget() { return this; } + +public slots: + virtual void sendMessage(); + virtual void appendMessage( Kopete::Message &message ); + virtual void messageSentSuccessfully(); + +signals: + virtual void shown(); + virtual void messageSent( Kopete::Message &message ); + virtual void closing( KopeteView *view ); + virtual void activated( KopeteView *view ); + +protected: + virtual void closeEvent( QCloseEvent *e ); + virtual void windowActivationChange( bool activated ); + +private slots: + void slotReplySend(); + void slotUpdateReplySend(); + void slotReadNext(); + void slotReadPrev(); + void slotCloseView(); + + void slotSmileyActivated( const QString & ); + void slotCopy(); + + void slotViewMenuBar(); + + void slotConfToolbar(); + + void slotMarkMessageRead(); + +private: + class Private; + Private *d; + + void toggleMode( WindowMode ); + void updateNextButton(); + void initActions(); + void writeMessage( Kopete::Message & ); +}; + + +/** + * This is the class that makes the emailwindow a plugin + */ +class EmailWindowPlugin : public Kopete::ViewPlugin +{ + public: + EmailWindowPlugin(QObject *parent, const char *name, const QStringList &args); + KopeteView* createView( Kopete::ChatSession *manager ); +}; + +#endif // __KOPETEEMAILWINDOW_H__ + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/kopeteemailwindow.rc b/kopete/kopete/chatwindow/kopeteemailwindow.rc new file mode 100644 index 00000000..23bc6a7f --- /dev/null +++ b/kopete/kopete/chatwindow/kopeteemailwindow.rc @@ -0,0 +1,43 @@ + + + + + &Chat + + + + + + + + + &Format + + + + + + + + &Settings + + + + + + + Main Toolbar + + + + + + + + + + + + Status + + diff --git a/kopete/kopete/chatwindow/kopeteemoticonaction.cpp b/kopete/kopete/chatwindow/kopeteemoticonaction.cpp new file mode 100644 index 00000000..3e14e66d --- /dev/null +++ b/kopete/kopete/chatwindow/kopeteemoticonaction.cpp @@ -0,0 +1,228 @@ +/* + kopeteemoticonaction.cpp + + KAction to show the emoticon selector + + Copyright (c) 2002 by Stefan Gehn + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopeteemoticonaction.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "emoticonselector.h" +#include "kopeteemoticons.h" + +class KopeteEmoticonAction::KopeteEmoticonActionPrivate +{ +public: + KopeteEmoticonActionPrivate() + { + m_delayed = true; + m_stickyMenu = true; + m_popup = new KPopupMenu(0L,"KopeteEmoticonActionPrivate::m_popup"); + emoticonSelector = new EmoticonSelector( m_popup, "KopeteEmoticonActionPrivate::emoticonSelector"); + m_popup->insertItem( emoticonSelector ); + // TODO: Maybe connect to kopeteprefs and redo list only on config changes + connect( m_popup, SIGNAL( aboutToShow() ), emoticonSelector, SLOT( prepareList() ) ); + } + + ~KopeteEmoticonActionPrivate() + { + delete m_popup; + m_popup = 0; + } + + KPopupMenu *m_popup; + EmoticonSelector *emoticonSelector; + bool m_delayed; + bool m_stickyMenu; +}; + +KopeteEmoticonAction::KopeteEmoticonAction( QObject* parent, const char* name ) + : KAction( i18n( "Add Smiley" ), 0, parent, name ) +{ + d = new KopeteEmoticonActionPrivate; + + // Try to load the icon for our current emoticon theme, when it fails + // fall back to our own default + QString icon; + QMap emoticonsMap = Kopete::Emoticons::self()->emoticonAndPicList(); + for( QMap::const_iterator it = emoticonsMap.constBegin(); + it != emoticonsMap.constEnd(); ++it ) + { + if( ( *it ).contains( ":)" ) || ( *it ).contains( ":-)" ) ) + { + icon = it.key(); + break; + } + } + + if ( icon.isNull() ) + setIcon( "emoticon" ); + else + setIconSet( QIconSet( icon ) ); + + setShortcutConfigurable( false ); + connect( d->emoticonSelector, SIGNAL( ItemSelected( const QString & ) ), + this, SIGNAL( activated( const QString & ) ) ); +} + +KopeteEmoticonAction::~KopeteEmoticonAction() +{ + unplugAll(); +// kdDebug(14010) << "KopeteEmoticonAction::~KopeteEmoticonAction()" << endl; + delete d; + d = 0; +} + +void KopeteEmoticonAction::popup( const QPoint& global ) +{ + popupMenu()->popup( global ); +} + +KPopupMenu* KopeteEmoticonAction::popupMenu() const +{ + return d->m_popup; +} + +bool KopeteEmoticonAction::delayed() const +{ + return d->m_delayed; +} + +void KopeteEmoticonAction::setDelayed(bool _delayed) +{ + d->m_delayed = _delayed; +} + +bool KopeteEmoticonAction::stickyMenu() const +{ + return d->m_stickyMenu; +} + +void KopeteEmoticonAction::setStickyMenu(bool sticky) +{ + d->m_stickyMenu = sticky; +} + +int KopeteEmoticonAction::plug( QWidget* widget, int index ) +{ + if (kapp && !kapp->authorizeKAction(name())) + return -1; + +// kdDebug(14010) << "KopeteEmoticonAction::plug( " << widget << ", " << index << " )" << endl; + + // KDE4/Qt TODO: Use qobject_cast instead. + if ( widget->inherits("QPopupMenu") ) + { + QPopupMenu* menu = static_cast( widget ); + int id; + if ( hasIcon() ) + id = menu->insertItem( iconSet(KIcon::Small), text(), d->m_popup, -1, index ); + else + id = menu->insertItem( text(), d->m_popup, -1, index ); + + if ( !isEnabled() ) + menu->setItemEnabled( id, false ); + + addContainer( menu, id ); + connect( menu, SIGNAL( destroyed() ), this, SLOT( slotDestroyed() ) ); + + if ( m_parentCollection ) + m_parentCollection->connectHighlight( menu, this ); + + return containerCount() - 1; + } + // KDE4/Qt TODO: Use qobject_cast instead. + else if ( widget->inherits( "KToolBar" ) ) + { + KToolBar *bar = static_cast( widget ); + + int id_ = KAction::getToolButtonID(); + + if ( icon().isEmpty() && !iconSet(KIcon::Small).isNull() ) + { + bar->insertButton( + iconSet(KIcon::Small).pixmap(), id_, SIGNAL(clicked()), this, + SLOT(slotActivated()), isEnabled(), plainText(), + index ); + } + else + { + KInstance *instance; + + if ( m_parentCollection ) + instance = m_parentCollection->instance(); + else + instance = KGlobal::instance(); + + bar->insertButton( icon(), id_, SIGNAL( clicked() ), this, + SLOT( slotActivated() ), isEnabled(), plainText(), + index, instance ); + } + + addContainer( bar, id_ ); + + if (!whatsThis().isEmpty()) + QWhatsThis::add( bar->getButton(id_), whatsThis() ); + + connect( bar, SIGNAL( destroyed() ), this, SLOT( slotDestroyed() ) ); + + if (delayed()) + bar->setDelayedPopup(id_, popupMenu(), stickyMenu()); + else + bar->getButton(id_)->setPopup(popupMenu(), stickyMenu()); + + if ( m_parentCollection ) + m_parentCollection->connectHighlight(bar, this); + + return containerCount() - 1; + } + // KDE4/Qt TODO: Use qobject_cast instead. + else if ( widget->inherits( "QMenuBar" ) ) + { + QMenuBar *bar = static_cast( widget ); + + int id; + + id = bar->insertItem( text(), popupMenu(), -1, index ); + + if ( !isEnabled() ) + bar->setItemEnabled( id, false ); + + addContainer( bar, id ); + connect( bar, SIGNAL( destroyed() ), this, SLOT( slotDestroyed() ) ); + + return containerCount() - 1; + } + + return -1; +} + +#include "kopeteemoticonaction.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/kopeteemoticonaction.h b/kopete/kopete/chatwindow/kopeteemoticonaction.h new file mode 100644 index 00000000..d420518a --- /dev/null +++ b/kopete/kopete/chatwindow/kopeteemoticonaction.h @@ -0,0 +1,90 @@ +/* + kopeteemoticonaction.h + + KAction to show the emoticon selector + + Copyright (c) 2002 by Stefan Gehn + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETEEMOTICONACTION_H_ +#define _KOPETEEMOTICONACTION_H_ + +#include + +class KopeteEmoticonAction : public KAction +{ + Q_OBJECT + + Q_PROPERTY( bool delayed READ delayed WRITE setDelayed ) + Q_PROPERTY( bool stickyMenu READ stickyMenu WRITE setStickyMenu ) + +public: + KopeteEmoticonAction( QObject *parent = 0, const char *name = 0 ); + virtual ~KopeteEmoticonAction(); + + KPopupMenu * popupMenu() const; + void popup( const QPoint &global ); + + /** + * Returns true if this action creates a delayed popup menu + * when plugged in a KToolbar. + */ + bool delayed() const; + + /** + * If set to true, this action will create a delayed popup menu + * when plugged in a KToolbar. Otherwise it creates a normal popup. + * Default: delayed + * + * Remember that if the "main" action (the toolbar button itself) + * cannot be clicked, then you should call setDelayed(false). + * + * On the opposite, if the main action can be clicked, it can only happen + * in a toolbar: in a menu, the parent of a submenu can't be activated. + * To get a "normal" menu item when plugged a menu (and no submenu) + * use KToolBarPopupAction. + */ + void setDelayed( bool delayed ); + + /** + * Returns true if this action creates a sticky popup menu. + * See @ref setStickyMenu. + */ + bool stickyMenu() const; + + /** + * If set to true, this action will create a sticky popup menu + * when plugged in a KToolbar. + * "Sticky", means it's visible until a selection is made or the mouse is + * clicked elsewhere. This feature allows you to make a selection without + * having to press and hold down the mouse while making a selection. + * Default: sticky. + */ + void setStickyMenu( bool sticky ); + + virtual int plug( QWidget* widget, int index = -1 ); + +signals: + void activated( const QString &item ); + +private: + class KopeteEmoticonActionPrivate; + KopeteEmoticonActionPrivate *d; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/chatwindow/kopeterichtexteditpartfull.rc b/kopete/kopete/chatwindow/kopeterichtexteditpartfull.rc new file mode 100644 index 00000000..4031e4d7 --- /dev/null +++ b/kopete/kopete/chatwindow/kopeterichtexteditpartfull.rc @@ -0,0 +1,49 @@ + + + + F&ormat + + + + + + + &Alignment + + + + + + + + + + + + +Main Toolbar + + + + + + + +Format Toolbar + + + + + + + + + + + + + + + + + diff --git a/kopete/kopete/chatwindow/krichtexteditpart.cpp b/kopete/kopete/chatwindow/krichtexteditpart.cpp new file mode 100644 index 00000000..001096b9 --- /dev/null +++ b/kopete/kopete/chatwindow/krichtexteditpart.cpp @@ -0,0 +1,550 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "krichtexteditpart.h" +#include "krichtexteditpart.moc" +#include "kopeteprotocol.h" +#include "kopeteprefs.h" + +typedef KParts::GenericFactory KopeteRichTextEditPartFactory; +K_EXPORT_COMPONENT_FACTORY( libkopeterichtexteditpart, KopeteRichTextEditPartFactory ) + +class KopeteTextEdit : public KTextEdit +{ +public: + KopeteTextEdit( QWidget *parent ) : KTextEdit( parent ) {} + const QTextCursor * cursor() { return textCursor(); } + bool event(QEvent *event) + { + // don't allow QTextEdit to override accels + if ( event->type() == QEvent::AccelOverride ) + return QWidget::event(event); + else + return KTextEdit::event(event); + } +}; + +KopeteRichTextEditPart::KopeteRichTextEditPart( QWidget *wparent, const char *wname, QObject*, const char*, const QStringList& ) + : KParts::ReadOnlyPart( wparent, wname ? wname : "rich_text_part" ) +{ + KopeteRichTextEditPart::KopeteRichTextEditPart( wparent, wname, false ); +} + +KopeteRichTextEditPart::KopeteRichTextEditPart( QWidget *parent, const char *name, int capabilities ) + : KParts::ReadOnlyPart( parent, name ? name : "rich_text_part" ), + m_capabilities( capabilities ), + m_richTextEnabled( true ) +{ + // we need an instance + setInstance( KopeteRichTextEditPartFactory::instance() ); + + editor = new KopeteTextEdit( parent ); + editor->setReadOnly( false ); + + setWidget( editor ); + + m_richTextAvailable = ( + m_capabilities & Kopete::Protocol::RichFormatting || + m_capabilities & Kopete::Protocol::Alignment || + m_capabilities & Kopete::Protocol::RichFont || + m_capabilities & Kopete::Protocol::RichColor + ); + + createActions(); + + setXMLFile( "kopeterichtexteditpartfull.rc" ); + enableRichText->setEnabled( m_richTextAvailable ); + enableRichText->setChecked( m_richTextAvailable ); + slotSetRichTextEnabled( m_richTextAvailable ); + + //Set colors, font + readConfig(); +} + +void KopeteRichTextEditPart::slotSetRichTextEnabled( bool enable ) +{ + m_richTextEnabled = enable && m_richTextAvailable; + + if( m_richTextEnabled ) + { + editor->setTextFormat( Qt::RichText ); + } + else + { + editor->setTextFormat( Qt::PlainText ); + } + + emit toggleToolbar( buttonsEnabled() ); + + // Spellchecking disabled when using rich text because the + // text we were getting from widget was coloured HTML! + editor->setCheckSpellingEnabled( !m_richTextEnabled ); + checkSpelling->setEnabled( !m_richTextEnabled ); + + //Enable / disable buttons + updateActions(); + enableRichText->setChecked( m_richTextEnabled ); +} + +void KopeteRichTextEditPart::checkToolbarEnabled() +{ + emit toggleToolbar( buttonsEnabled() ); +} + +void KopeteRichTextEditPart::reloadConfig() +{ + readConfig(); +} + +void KopeteRichTextEditPart::createActions() +{ + createActions( actionCollection() ); +} + +KAboutData *KopeteRichTextEditPart::createAboutData() +{ + KAboutData *aboutData = new KAboutData("kopeterichtexteditpart", I18N_NOOP("KopeteRichTextEditPart"), "0.1", + I18N_NOOP("A simple rich text editor part for Kopete"), + KAboutData::License_LGPL ); + aboutData->addAuthor("Richard J. Moore", 0, "rich@kde.org", "http://xmelegance.org/" ); + aboutData->addAuthor("Jason Keirstead", 0, "jason@keirstead.org", "http://www.keirstead.org/" ); + return aboutData; +} + +void KopeteRichTextEditPart::createActions( KActionCollection *ac ) +{ + enableRichText = new KToggleAction(i18n("Enable &Rich Text"), "pencil", 0, + ac, "enableRichText" ); + enableRichText->setCheckedState(i18n("Disable &Rich Text")); + connect( enableRichText, SIGNAL( toggled(bool) ), + this, SLOT( slotSetRichTextEnabled(bool) ) ); + + checkSpelling = new KAction( i18n("Check &Spelling"), "spellcheck", 0, + editor, SLOT( checkSpelling() ), ac, "check_spelling" ); + + //Fg Color + actionFgColor = new KAction( i18n("Text &Color..."), "color_line", 0, + this, SLOT( setFgColor() ), + ac, "format_color" ); + + //BG Color + actionBgColor = new KAction( i18n("Background Co&lor..."), "color_fill", 0, + this, SLOT( setBgColor() ), + ac, "format_bgcolor" ); + + //Font Family + action_font = new KFontAction( i18n("&Font"), 0, + ac, "format_font" ); + connect( action_font, SIGNAL( activated( const QString & ) ), + this, SLOT( setFont( const QString & ) ) ); + + //Font Size + action_font_size = new KFontSizeAction( i18n("Font &Size"), 0, + ac, "format_font_size" ); + connect( action_font_size, SIGNAL( fontSizeChanged(int) ), + this, SLOT( setFontSize(int) ) ); + + //Formatting + action_bold = new KToggleAction( i18n("&Bold"), "text_bold", CTRL+Key_B, + ac, "format_bold" ); + connect( action_bold, SIGNAL( toggled(bool) ), + this, SLOT( setBold(bool) ) ); + + action_italic = new KToggleAction( i18n("&Italic"), "text_italic", CTRL+Key_I, + ac, "format_italic" ); + connect( action_italic, SIGNAL( toggled(bool) ), + this, SLOT( setItalic(bool) ) ); + + action_underline = new KToggleAction( i18n("&Underline"), "text_under", CTRL+Key_U, + ac, "format_underline" ); + connect( action_underline, SIGNAL( toggled(bool) ), + this, SLOT( setUnderline(bool) ) ); + + connect( editor, SIGNAL( currentFontChanged( const QFont & ) ), + this, SLOT( updateCharFmt() ) ); + updateCharFmt(); + + connect( editor, SIGNAL( currentFontChanged( const QFont & ) ), + this, SLOT( updateFont() ) ); + updateFont(); + + //Alignment + action_align_left = new KToggleAction( i18n("Align &Left"), "text_left", 0, + ac, "format_align_left" ); + connect( action_align_left, SIGNAL( toggled(bool) ), + this, SLOT( setAlignLeft(bool) ) ); + + action_align_center = new KToggleAction( i18n("Align &Center"), "text_center", 0, + ac, "format_align_center" ); + connect( action_align_center, SIGNAL( toggled(bool) ), + this, SLOT( setAlignCenter(bool) ) ); + + action_align_right = new KToggleAction( i18n("Align &Right"), "text_right", 0, + ac, "format_align_right" ); + connect( action_align_right, SIGNAL( toggled(bool) ), + this, SLOT( setAlignRight(bool) ) ); + + action_align_justify = new KToggleAction( i18n("&Justify"), "text_block", 0, + ac, "format_align_justify" ); + connect( action_align_justify, SIGNAL( toggled(bool) ), + this, SLOT( setAlignJustify(bool) ) ); + + action_align_left->setExclusiveGroup( "alignment" ); + action_align_center->setExclusiveGroup( "alignment" ); + action_align_right->setExclusiveGroup( "alignment" ); + action_align_justify->setExclusiveGroup( "alignment" ); + + connect( editor, SIGNAL( cursorPositionChanged( int,int ) ), + this, SLOT( updateAligment() ) ); + + updateAligment(); +} + +void KopeteRichTextEditPart::updateActions() +{ + bool buttonsEnabled = this->buttonsEnabled(); + bool enableFgColor = m_capabilities & Kopete::Protocol::BaseFgColor || m_capabilities & Kopete::Protocol::RichFgColor; + bool enableBGColor = m_capabilities & Kopete::Protocol::BaseBgColor || m_capabilities & Kopete::Protocol::RichBgColor; + bool activateAlignment = buttonsEnabled && ( m_capabilities & Kopete::Protocol::Alignment ); + bool activateFont = m_capabilities & Kopete::Protocol::BaseFont || m_capabilities & Kopete::Protocol::RichFont; + + bool activateBFormat = m_capabilities & Kopete::Protocol::BaseBFormatting || m_capabilities & Kopete::Protocol::RichBFormatting; + bool activateUFormat = m_capabilities & Kopete::Protocol::BaseUFormatting || m_capabilities & Kopete::Protocol::RichUFormatting; + bool activateIFormat = m_capabilities & Kopete::Protocol::BaseIFormatting || m_capabilities & Kopete::Protocol::RichIFormatting; + + actionFgColor->setEnabled( buttonsEnabled && enableFgColor ); + actionBgColor->setEnabled( buttonsEnabled && enableBGColor ); + + action_font->setEnabled( buttonsEnabled && activateFont ); + action_font_size->setEnabled( buttonsEnabled && activateFont ); + + action_bold->setEnabled( buttonsEnabled && activateBFormat ); + action_italic->setEnabled( buttonsEnabled && activateIFormat ); + action_underline->setEnabled( buttonsEnabled && activateUFormat ); + + action_align_left->setEnabled( activateAlignment ); + action_align_center->setEnabled( activateAlignment ); + action_align_right->setEnabled( activateAlignment ); + action_align_justify->setEnabled( activateAlignment ); +} + +void KopeteRichTextEditPart::updateCharFmt() +{ + action_bold->setChecked( editor->bold() ); + action_italic->setChecked( editor->italic() ); + action_underline->setChecked( editor->underline() ); +} + +void KopeteRichTextEditPart::clear() +{ + editor->setText( QString::null ); + setFont( mFont ); + setFgColor( mFgColor ); + + if( m_capabilities & Kopete::Protocol::BaseBFormatting || m_capabilities & Kopete::Protocol::RichBFormatting ) + { + editor->setBold( action_bold->isChecked() ); + } + if( m_capabilities & Kopete::Protocol::BaseIFormatting || m_capabilities & Kopete::Protocol::RichIFormatting ) + { + editor->setItalic( action_italic->isChecked() ); + } + if( m_capabilities & Kopete::Protocol::BaseUFormatting || m_capabilities & Kopete::Protocol::RichUFormatting ) + { + editor->setUnderline( action_underline->isChecked() ); + } +} + +void KopeteRichTextEditPart::updateAligment() +{ + int align = editor->alignment(); + + switch ( align ) + { + case AlignRight: + action_align_right->setChecked( true ); + break; + case AlignCenter: + action_align_center->setChecked( true ); + break; + case AlignLeft: + action_align_left->setChecked( true ); + break; + case AlignJustify: + action_align_justify->setChecked( true ); + break; + default: + break; + } +} + +void KopeteRichTextEditPart::updateFont() +{ + if ( editor->pointSize() > 0 ) + action_font_size->setFontSize( editor->pointSize() ); + action_font->setFont( editor->family() ); +} + +void KopeteRichTextEditPart::readConfig() +{ + // Don't update config untill we read whole config first + m_configWriteLock = true; + KConfig *config = KGlobal::config(); + config->setGroup("RichTextEditor"); + + QColor tmpColor = KGlobalSettings::textColor(); + setFgColor( config->readColorEntry("FgColor", &tmpColor ) ); + + tmpColor = KGlobalSettings::baseColor(); + setBgColor( config->readColorEntry("BgColor", &tmpColor ) ); + + QFont tmpFont = KopetePrefs::prefs()->fontFace(); + setFont( config->readFontEntry("Font", &tmpFont ) ); + + int tmp = KGlobalSettings::generalFont().pixelSize(); + setFontSize( config->readNumEntry( "FontSize", tmp ) ); + + action_bold->setChecked( config->readBoolEntry( "FontBold" ) ); + action_italic->setChecked( config->readBoolEntry( "FontItalic" ) ); + action_underline->setChecked( config->readBoolEntry( "FontUnderline" ) ); + + switch( config->readNumEntry( "EditAlignment", AlignLeft ) ) + { + case AlignLeft: + action_align_left->activate(); + break; + case AlignCenter: + action_align_center->activate(); + break; + case AlignRight: + action_align_right->activate(); + break; + case AlignJustify: + action_align_justify->activate(); + break; + } + m_configWriteLock = false; +} + +void KopeteRichTextEditPart::writeConfig() +{ + // If true we're still reading the conf write now, so don't write. + if( m_configWriteLock ) return; + + KConfig *config = KGlobal::config(); + config->setGroup("RichTextEditor"); + config->writeEntry("Font", mFont ); + config->writeEntry("FontSize", mFont.pointSize() ); + config->writeEntry("FontBold", mFont.bold() ); + config->writeEntry("FontItalic", mFont.italic() ); + config->writeEntry("FontUnderline", mFont.underline() ); + config->writeEntry("BgColor", mBgColor ); + config->writeEntry("FgColor", mFgColor ); + config->writeEntry("EditAlignment", editor->alignment() ); + config->sync(); +} + +void KopeteRichTextEditPart::setFgColor() +{ + QColor col=editor->color(); + + int s = KColorDialog::getColor( col, KGlobalSettings::textColor() , editor ); + if(!col.isValid()) + col= KGlobalSettings::textColor() ; + if ( s != QDialog::Accepted ) + return; + + setFgColor( col ); + + writeConfig(); +} + +void KopeteRichTextEditPart::setFgColor( const QColor &newColor ) +{ + mFgColor = newColor; + + if( !(m_capabilities & Kopete::Protocol::RichColor) ) + { + QPalette pal = editor->palette(); + pal.setColor(QPalette::Active, QColorGroup::Text, mFgColor ); + pal.setColor(QPalette::Inactive, QColorGroup::Text, mFgColor ); + + if ( pal == QApplication::palette( editor ) ) + editor->unsetPalette(); + else + editor->setPalette(pal); + } + + editor->setColor( mFgColor ); +} + +QColor KopeteRichTextEditPart::fgColor() +{ + if( mFgColor == KGlobalSettings::textColor()) + return QColor(); + return mFgColor; +} + +void KopeteRichTextEditPart::setBgColor() +{ + QColor col=mBgColor; + + int s = KColorDialog::getColor( col, KGlobalSettings::baseColor(), editor ); + if(!col.isValid()) + { + col=KGlobalSettings::baseColor(); + } + + if ( s != QDialog::Accepted ) + return; + + setBgColor( col ); + + writeConfig(); +} + +void KopeteRichTextEditPart::setBgColor( const QColor &newColor ) +{ + mBgColor = newColor; + + QPalette pal = editor->palette(); + pal.setColor(QPalette::Active, QColorGroup::Base, mBgColor ); + pal.setColor(QPalette::Inactive, QColorGroup::Base, mBgColor ); + pal.setColor(QPalette::Disabled, QColorGroup::Base, mBgColor ); + + if ( pal == QApplication::palette( editor ) ) + editor->unsetPalette(); + else + editor->setPalette(pal); +} + +QColor KopeteRichTextEditPart::bgColor() +{ + if( mBgColor == KGlobalSettings::baseColor()) + return QColor(); + return mBgColor; +} + +void KopeteRichTextEditPart::setFontSize( int size ) +{ + mFont.setPointSize( size ); + if( m_capabilities & Kopete::Protocol::RichFont ) + editor->setPointSize( size ); + else if( m_capabilities & Kopete::Protocol::BaseFont) + editor->setFont( mFont ); + writeConfig(); +} + +void KopeteRichTextEditPart::setFont() +{ + KFontDialog::getFont(mFont, false, editor); + setFont(mFont); + writeConfig(); +} + +void KopeteRichTextEditPart::setFont( const QFont &newFont ) +{ + mFont = newFont; + editor->setFont(mFont); + updateFont(); +} + +void KopeteRichTextEditPart::setFont( const QString &newFont ) +{ + mFont.setFamily( newFont ); + if( m_capabilities & Kopete::Protocol::RichFont) + editor->setFamily( newFont ); + else if( m_capabilities & Kopete::Protocol::BaseFont) + editor->setFont( mFont ); + updateFont(); + writeConfig(); +} + + +void KopeteRichTextEditPart::setBold( bool b ) +{ + mFont.setBold(b); + if( m_capabilities & Kopete::Protocol::RichBFormatting || m_capabilities & Kopete::Protocol::BaseBFormatting ) + { + if( m_richTextEnabled ) + editor->setBold(b); + else + editor->setFont(mFont); + } + writeConfig(); +} + +void KopeteRichTextEditPart::setItalic( bool b ) +{ + mFont.setItalic( b ); + if( m_capabilities & Kopete::Protocol::RichIFormatting || m_capabilities & Kopete::Protocol::BaseIFormatting ) + { + if(m_richTextEnabled) + editor->setItalic(b); + else + editor->setFont(mFont); + } + writeConfig(); +} + +void KopeteRichTextEditPart::setUnderline( bool b ) +{ + mFont.setUnderline( b ); + if( m_capabilities & Kopete::Protocol::RichUFormatting || m_capabilities & Kopete::Protocol::BaseUFormatting ) + { + if(m_richTextEnabled) + editor->setUnderline(b); + else + editor->setFont(mFont); + } + writeConfig(); +} + + +void KopeteRichTextEditPart::setAlignLeft( bool yes ) +{ + if ( yes ) + editor->setAlignment( AlignLeft ); + writeConfig(); +} + +void KopeteRichTextEditPart::setAlignRight( bool yes ) +{ + if ( yes ) + editor->setAlignment( AlignRight ); + writeConfig(); +} + +void KopeteRichTextEditPart::setAlignCenter( bool yes ) +{ + if ( yes ) + editor->setAlignment( AlignCenter ); + writeConfig(); +} + +void KopeteRichTextEditPart::setAlignJustify( bool yes ) +{ + if ( yes ) + editor->setAlignment( AlignJustify ); + writeConfig(); +} + +QString KopeteRichTextEditPart::text( Qt::TextFormat fmt ) const +{ + if( fmt == editor->textFormat() || fmt != Qt::PlainText ) + return editor->text(); + else + return editor->cursor()->document()->plainText(); +} + diff --git a/kopete/kopete/chatwindow/krichtexteditpart.h b/kopete/kopete/chatwindow/krichtexteditpart.h new file mode 100644 index 00000000..cd32993c --- /dev/null +++ b/kopete/kopete/chatwindow/krichtexteditpart.h @@ -0,0 +1,139 @@ +// -*- c++ -*- + +#ifndef KRICHTEXTEDITPART_H +#define KRICHTEXTEDITPART_H + +#include + +#include +#include + +class KAboutData; +class KTextEdit; +class KFontAction; +class KFontSizeAction; +class KToggleAction; +class KopeteTextEdit; + +/** + * KParts wrapper for QTextEdit. + * + * Originally by Richard Moore, rich@kde.org + * forked by Jason Keirstead + */ +class KopeteRichTextEditPart : public KParts::ReadOnlyPart +{ + Q_OBJECT + + public: + KopeteRichTextEditPart( QWidget *wparent, const char *wname, QObject*, const char*, const QStringList& ); + KopeteRichTextEditPart( QWidget *wparent, const char *wname, int capabilities ); + + /** + * Returns the current editor widget. + */ + KTextEdit *widget() const { return (KTextEdit*)editor; } + + QString text( Qt::TextFormat = Qt::AutoText ) const; + + QFont font() { return mFont; } + + QColor fgColor(); + + QColor bgColor(); + + void clear(); + + int capabilities() { return m_capabilities; } + + bool richTextEnabled() { return m_richTextAvailable && m_richTextEnabled; } + + bool buttonsEnabled() { return !m_richTextAvailable || m_richTextEnabled; } + + static KAboutData *createAboutData(); + + virtual bool openFile() { return false; }; + + public slots: + + void setFgColor(); + void setFgColor( const QColor & ); + + void setBgColor(); + void setBgColor( const QColor & ); + + void setFont(); + void setFont( const QFont & ); + void setFont( const QString & ); + + void setFontSize( int ); + + void setUnderline( bool ); + void setBold( bool ); + void setItalic( bool ); + + void setAlignLeft( bool yes ); + void setAlignRight( bool yes ); + void setAlignCenter( bool yes ); + void setAlignJustify( bool yes ); + + void checkToolbarEnabled(); + void reloadConfig(); + void slotSetRichTextEnabled( bool enable ); + + signals: + void toggleToolbar( bool enabled ); + + protected: + /** + * Creates the part's actions in the specified action collection. + */ + virtual void createActions( KActionCollection *ac ); + + protected slots: + + /** + * Creates the part's actions in the part's action collection. + */ + void createActions(); + void updateActions(); + + void updateFont(); + void updateCharFmt(); + void updateAligment(); + + private: + void readConfig(); + void writeConfig(); + + KopeteTextEdit *editor; + KAction *checkSpelling; + KToggleAction *enableRichText; + + KAction *actionFgColor; + KAction *actionBgColor; + + KToggleAction *action_bold; + KToggleAction *action_italic; + KToggleAction *action_underline; + + KFontAction *action_font; + KFontSizeAction *action_font_size; + + KToggleAction *action_align_left; + KToggleAction *action_align_right; + KToggleAction *action_align_center; + KToggleAction *action_align_justify; + + int m_capabilities; + bool m_richTextAvailable; + bool m_richTextEnabled; + + bool m_configWriteLock; + + QFont mFont; + QColor mBgColor; + QColor mFgColor; +}; + +#endif // KRICHTEXTEDITPART_H diff --git a/kopete/kopete/chatwindow/tests/Makefile.am b/kopete/kopete/chatwindow/tests/Makefile.am new file mode 100644 index 00000000..40a864d6 --- /dev/null +++ b/kopete/kopete/chatwindow/tests/Makefile.am @@ -0,0 +1,17 @@ +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_ASCII_CAST -DQT_NO_COMPAT \ + $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/kopete/chatwindow -I$(top_srcdir)/kopete/libkopete $(all_includes) -DSRCDIR=\"$(top_srcdir)/kopete/kopete/chatwindow/tests\" +METASOURCES = AUTO +check_LTLIBRARIES = kunittest_chatwindowstyle_test.la kunittest_chatwindowstylerendering_test.la + +kunittest_chatwindowstyle_test_la_SOURCES = chatwindowstyle_test.cpp +kunittest_chatwindowstyle_test_la_LIBADD = -lkunittest ../libkopetechatwindow.la +kunittest_chatwindowstyle_test_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + +kunittest_chatwindowstylerendering_test_la_SOURCES = chatwindowstylerendering_test.cpp +kunittest_chatwindowstylerendering_test_la_LIBADD = -lkunittest ../../../libkopete/libkopete.la ../libkopetechatwindow.la +kunittest_chatwindowstylerendering_test_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + +check-local: + kunittestmodrunner +guicheck: + kunittestmod $(PWD) diff --git a/kopete/kopete/chatwindow/tests/TestStyle/Contents/Info.plist b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Info.plist new file mode 100644 index 00000000..e69de29b diff --git a/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Header.html b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Header.html new file mode 100644 index 00000000..e743064c --- /dev/null +++ b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Header.html @@ -0,0 +1,7 @@ +
    %chatName%
    +
    %sourceName%
    +
    %destinationName%
    +
    %incomingIconPath%
    +
    %outgoingIconPath%
    +
    %timeOpened%
    +
    %timeOpened{%H:%M}%
    \ No newline at end of file diff --git a/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Incoming/Content.html b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Incoming/Content.html new file mode 100644 index 00000000..5a6ae0c0 --- /dev/null +++ b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Incoming/Content.html @@ -0,0 +1,9 @@ +Incoming: +
    %userIconPath%
    +
    %senderScreenName%
    +
    %sender%
    +
    %service%
    +
    %message%
    +
    %time%
    +
    %time{%H:%M}%
    +
    \ No newline at end of file diff --git a/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Incoming/NextContent.html b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Incoming/NextContent.html new file mode 100644 index 00000000..017a2cad --- /dev/null +++ b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Incoming/NextContent.html @@ -0,0 +1,3 @@ +Incoming: +
    %message%
    +
    \ No newline at end of file diff --git a/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Outgoing/Content.html b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Outgoing/Content.html new file mode 100644 index 00000000..06bbd826 --- /dev/null +++ b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Outgoing/Content.html @@ -0,0 +1,9 @@ +Outgoing: +
    %userIconPath%
    +
    %senderScreenName%
    +
    %sender%
    +
    %service%
    +
    %message%
    +
    %time%
    +
    %time{%H:%M}%
    +
    \ No newline at end of file diff --git a/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Outgoing/NextContent.html b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Outgoing/NextContent.html new file mode 100644 index 00000000..8d3fa8d0 --- /dev/null +++ b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Outgoing/NextContent.html @@ -0,0 +1,3 @@ +Outgoing: +
    %message%
    +
    \ No newline at end of file diff --git a/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Status.html b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Status.html new file mode 100644 index 00000000..71a77cd1 --- /dev/null +++ b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Status.html @@ -0,0 +1,3 @@ +
    %message%
    +
    %time%
    +
    %time{%H:%M}%
    \ No newline at end of file diff --git a/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Variants/Variant1.css b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Variants/Variant1.css new file mode 100644 index 00000000..8d1c8b69 --- /dev/null +++ b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Variants/Variant1.css @@ -0,0 +1 @@ + diff --git a/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Variants/Variant2.css b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Variants/Variant2.css new file mode 100644 index 00000000..8d1c8b69 --- /dev/null +++ b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/Variants/Variant2.css @@ -0,0 +1 @@ + diff --git a/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/main.css b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/main.css new file mode 100644 index 00000000..8d1c8b69 --- /dev/null +++ b/kopete/kopete/chatwindow/tests/TestStyle/Contents/Resources/main.css @@ -0,0 +1 @@ + diff --git a/kopete/kopete/chatwindow/tests/chatwindowstyle_test.cpp b/kopete/kopete/chatwindow/tests/chatwindowstyle_test.cpp new file mode 100644 index 00000000..26dba26b --- /dev/null +++ b/kopete/kopete/chatwindow/tests/chatwindowstyle_test.cpp @@ -0,0 +1,132 @@ +/* + ChatWindowStyle test suite + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include "chatwindowstyle_test.h" + +#include + +#include +#include + +#include + +KUNITTEST_MODULE( kunittest_chatwindowstyle_test, "KopeteChatWindowTestSuite"); +KUNITTEST_MODULE_REGISTER_TESTER( ChatWindowStyle_Test ); + +void ChatWindowStyle_Test::allTests() +{ + testStyle = new ChatWindowStyle(QString(SRCDIR)+QString("/TestStyle")); + + // change user data dir to avoid messing with user's .kde dir + setenv( "KDEHOME", QFile::encodeName( QDir::homeDirPath() + "/.kopete-unittest" ), true ); + + testPaths(); + testHtml(); + testVariants(); + testAction(); +} + +void ChatWindowStyle_Test::testPaths() +{ + QString expectedStylePath = SRCDIR + QString::fromUtf8("/TestStyle"); + QString expectedBaseHref = expectedStylePath + QString::fromUtf8("/Contents/Resources/"); + + CHECK(testStyle->getStylePath(), expectedStylePath); + CHECK(testStyle->getStyleBaseHref(), expectedBaseHref); +} + +void ChatWindowStyle_Test::testHtml() +{ + QString exceptedHeader = QString::fromUtf8( +"
    %chatName%
    \n" +"
    %sourceName%
    \n" +"
    %destinationName%
    \n" +"
    %incomingIconPath%
    \n" +"
    %outgoingIconPath%
    \n" +"
    %timeOpened%
    \n" +"
    %timeOpened{%H:%M}%
    "); + // Footer is empty on purpose, this is to test if the file doesn't exist. + QString exceptedFooter; + QString exceptedIncoming = QString::fromUtf8( +"Incoming:\n" +"
    %userIconPath%
    \n" +"
    %senderScreenName%
    \n" +"
    %sender%
    \n" +"
    %service%
    \n" +"
    %message%
    \n" +"
    %time%
    \n" +"
    %time{%H:%M}%
    \n" +"
    "); + QString exceptedNextIncoming = QString::fromUtf8( +"Incoming:\n" +"
    %message%
    \n" +"
    " +); + QString exceptedOutgoing = QString::fromUtf8( +"Outgoing:\n" +"
    %userIconPath%
    \n" +"
    %senderScreenName%
    \n" +"
    %sender%
    \n" +"
    %service%
    \n" +"
    %message%
    \n" +"
    %time%
    \n" +"
    %time{%H:%M}%
    \n" +"
    "); + QString exceptedNextOutgoing = QString::fromUtf8( +"Outgoing:\n" +"
    %message%
    \n" +"
    " +); + QString exceptedStatus = QString::fromUtf8( +"
    %message%
    \n" +"
    %time%
    \n" +"
    %time{%H:%M}%
    "); + + CHECK(testStyle->getHeaderHtml(), exceptedHeader); + CHECK(testStyle->getFooterHtml(), exceptedFooter); + CHECK(testStyle->getIncomingHtml(), exceptedIncoming); + CHECK(testStyle->getNextIncomingHtml(), exceptedNextIncoming); + CHECK(testStyle->getOutgoingHtml(), exceptedOutgoing); + CHECK(testStyle->getNextOutgoingHtml(), exceptedNextOutgoing); + CHECK(testStyle->getStatusHtml(), exceptedStatus); +} + +void ChatWindowStyle_Test::testAction() +{ + CHECK(testStyle->hasActionTemplate(), false); +} + +void ChatWindowStyle_Test::testVariants() +{ + QString expectedNameResult("Variant1;Variant2"); + QString expectedPathResult("Variants/Variant1.css;Variants/Variant2.css"); + QStringList variantNameList; + QStringList variantPathList; + ChatWindowStyle::StyleVariants variantList; + ChatWindowStyle::StyleVariants::ConstIterator it; + variantList = testStyle->getVariants(); + + for(it = variantList.constBegin(); it != variantList.constEnd(); ++it) + { + variantNameList.append(it.key()); + variantPathList.append(it.data()); + } + + CHECK(variantNameList.join(";"), expectedNameResult); + CHECK(variantPathList.join(";"), expectedPathResult); +} diff --git a/kopete/kopete/chatwindow/tests/chatwindowstyle_test.h b/kopete/kopete/chatwindow/tests/chatwindowstyle_test.h new file mode 100644 index 00000000..7cf2b912 --- /dev/null +++ b/kopete/kopete/chatwindow/tests/chatwindowstyle_test.h @@ -0,0 +1,39 @@ +/* + ChatWindowStyle test suite + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef CHATWINDOWSTYLE_TEST_H +#define CHATWINDOWSTYLE_TEST_H + +#include + +class ChatWindowStyle; + +class ChatWindowStyle_Test : public KUnitTest::Tester +{ +public: + void allTests(); + +public slots: + void testPaths(); + void testHtml(); + void testVariants(); + void testAction(); + +private: + ChatWindowStyle *testStyle; +}; + +#endif diff --git a/kopete/kopete/chatwindow/tests/chatwindowstylerendering_test.cpp b/kopete/kopete/chatwindow/tests/chatwindowstylerendering_test.cpp new file mode 100644 index 00000000..af0f6b81 --- /dev/null +++ b/kopete/kopete/chatwindow/tests/chatwindowstylerendering_test.cpp @@ -0,0 +1,326 @@ +/* + Adium(and Kopete) ChatWindowStyle format rendering test suite + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + + +#include "chatwindowstylerendering_test.h" + +#include + +// Qt includes +#include +#include +#include +#include + +// KDE includes +#include +#include +#include +#include +#include +#include + +#include + +// Libkopete includes +#include +#include +#include +#include +#include +#include + +using namespace Kopete; + +KUNITTEST_MODULE( kunittest_chatwindowstylerendering_test, "KopeteChatWindowTestSuite"); +KUNITTEST_MODULE_REGISTER_TESTER( ChatWindowStyleRendering_Test ); + +// Reimplement Kopete::Contact and its abstract method +class FakeContact : public Kopete::Contact +{ +public: + FakeContact (Kopete::Account *account, const QString &id, Kopete::MetaContact *mc ) : Kopete::Contact( account, id, mc ) {} + virtual Kopete::ChatSession *manager(Kopete::Contact::CanCreateFlags /*c*/) { return 0L; } + virtual void slotUserInfo() {}; +}; + +class FakeProtocol : public Kopete::Protocol +{ +public: +FakeProtocol( KInstance *instance, QObject *parent, const char *name ) : Kopete::Protocol(instance, parent, name) +{ + +} + +Account* createNewAccount( const QString &/*accountId*/ ) +{ + return 0L; +} + +AddContactPage* createAddContactWidget( QWidget */*parent*/, Kopete::Account */*account*/) +{ + return 0L; +} + +KopeteEditAccountWidget* createEditAccountWidget( Kopete::Account */*account*/, QWidget */*parent */) +{ + return 0L; +} + +}; + +class FakeAccount : public Kopete::Account +{ +public: +FakeAccount(Kopete::Protocol *parent, const QString &accountID, const char *name) : Kopete::Account(parent, accountID, name) +{ + +} + +~FakeAccount() +{ + +} + +bool createContact( const QString &/*contactId*/, Kopete::MetaContact */*parentContact*/ ) +{ + return true; +} + +void connect( const Kopete::OnlineStatus& /*initialStatus*/) +{ + // do nothing +} + +void disconnect() +{ + // do nothing +} + +void setOnlineStatus( const Kopete::OnlineStatus& /*status*/ , const QString &/*reason*/) +{ + // do nothing +} +}; + +class ChatWindowStyleRendering_Test::Private +{ +public: + Private() + { + protocol = new FakeProtocol( new KInstance(QCString("test-kopete-message")), 0L, "test-kopete-message"); + account = new FakeAccount(protocol, QString("testaccount"), 0); + + // Create fake meta/contacts + myselfMetaContact = new Kopete::MetaContact(); + myself = new FakeContact(account, "bob@localhost", myselfMetaContact); + myself->setNickName("Bob"); + otherMetaContact = new Kopete::MetaContact(); + other = new FakeContact(account, "audrey@localhost", otherMetaContact); + other->setNickName("Audrey"); + myselfMetaContact->setDisplayName("Bob"); + myselfMetaContact->setDisplayNameSource(Kopete::MetaContact::SourceCustom); + otherMetaContact->setDisplayName("Audrey"); + otherMetaContact->setDisplayNameSource(Kopete::MetaContact::SourceCustom); + + Kopete::ContactPtrList contactList; + contactList.append(other); + // Create fakeChatSession + fakeChatSession = Kopete::ChatSessionManager::self()->create(myself, contactList, 0); + fakeChatSession->setDisplayName("Test Session"); + + // Create testStyle + testStyle = new ChatWindowStyle(QString(SRCDIR)+QString("/TestStyle")); + } + ~Private() + { + delete myselfMetaContact; + delete otherMetaContact; + delete myself; + delete other; + delete fakeChatSession; + } + FakeProtocol *protocol; + FakeAccount *account; + Kopete::MetaContact *myselfMetaContact; + Kopete::MetaContact *otherMetaContact; + FakeContact *myself; + FakeContact *other; + Kopete::ChatSession *fakeChatSession; + ChatWindowStyle *testStyle; + + QString resultHTML; +}; + + + +ChatWindowStyleRendering_Test::ChatWindowStyleRendering_Test() +{ + d = new Private; +} + +ChatWindowStyleRendering_Test::~ChatWindowStyleRendering_Test() +{ + delete d; +} + +void ChatWindowStyleRendering_Test::allTests() +{ + // change user data dir to avoid messing with user's .kde dir + setenv( "KDEHOME", QFile::encodeName( QDir::homeDirPath() + "/.kopete-unittest" ), true ); + + KApplication::disableAutoDcopRegistration(); + //KCmdLineArgs::init(argc,argv,"testkopetemessage", 0, 0, 0, 0); + KApplication app; + + chatPart = new ChatMessagePart(d->fakeChatSession, 0, 0); + + testHeaderRendering(); + testMessageRendering(); + testStatusRendering(); + //testFullRendering(); +} + +void ChatWindowStyleRendering_Test::testHeaderRendering() +{ + QString expectedHtml = QString( +"
    Test Session
    \n" +"
    Bob
    \n" +"
    Audrey
    \n" +"
    Incoming/buddy_icon.png
    \n" +"
    Outgoing/buddy_icon.png
    \n" +"
    %1
    \n" +"
    %2
    " + ).arg(KGlobal::locale()->formatDateTime( QDateTime::currentDateTime(), true, true ) ) + .arg(QDateTime::currentDateTime().toString("hh:mm")); + + QString headerHtml = d->testStyle->getHeaderHtml(); + QString resultHtml; + + resultHtml = chatPart->formatStyleKeywords(headerHtml); + + kdDebug(14000) << "Result HTML: " << resultHtml << endl; + + CHECK(resultHtml, expectedHtml); +} + +void ChatWindowStyleRendering_Test::testMessageRendering() +{ + QString expectedIncomingHtml = QString( +"Incoming:\n" +"
    Incoming/buddy_icon.png
    \n" +"\n" +"\n" +"
    Kopete
    \n" +"
    Test
    \n" +"
    %1
    \n" +"
    %2
    \n" +"
    " + ).arg( QDateTime::currentDateTime().toString( "hh:mm:ss" ), QDateTime::currentDateTime().toString( "hh:mm" ) ); + + QString expectedOutgoingHtml = QString( +"Outgoing:\n" +"
    Outgoing/buddy_icon.png
    \n" +"\n" +"\n" +"
    Kopete
    \n" +"
    Hello there
    \n" +"
    %1
    \n" +"
    %2
    \n" +"
    " + ).arg( QDateTime::currentDateTime().toString( "hh:mm:ss" ), QDateTime::currentDateTime().toString( "hh:mm" ) ); + + + QString tempHtml; + QString resultHtml; + + Kopete::Message msgIn(d->other, d->myself, QString::fromUtf8("Test"), Kopete::Message::Inbound ); + Kopete::Message msgOut(d->myself, d->other, QString::fromUtf8("Hello there"), Kopete::Message::Outbound); + + tempHtml = d->testStyle->getIncomingHtml(); + resultHtml = chatPart->formatStyleKeywords(tempHtml, msgIn); + + kdDebug(14000) << "Message incoming HTML: " << resultHtml << endl; + + CHECK(resultHtml, expectedIncomingHtml); + + tempHtml = d->testStyle->getOutgoingHtml(); + resultHtml = chatPart->formatStyleKeywords(tempHtml, msgOut); + + kdDebug(14000) << "Message outgoing HTML: " << resultHtml << endl; + + CHECK(resultHtml, expectedOutgoingHtml); +} + +void ChatWindowStyleRendering_Test::testStatusRendering() +{ + QString expectedStatusHtml = QString( +"
    A contact went offline.
    \n" +"
    %1
    \n" +"
    %2
    " + ).arg( QDateTime::currentDateTime().toString( "hh:mm:ss" ), QDateTime::currentDateTime().toString( "hh:mm" ) ); + + QString statusHtml = d->testStyle->getStatusHtml(); + QString resultHtml; + + Kopete::Message msgStatus(0,0, QString::fromUtf8("A contact went offline."), Kopete::Message::Internal); + resultHtml = chatPart->formatStyleKeywords(statusHtml, msgStatus); + + CHECK(resultHtml, expectedStatusHtml); +} + +void ChatWindowStyleRendering_Test::testFullRendering() +{ + QString expectedFullHtml; + QString resultHtml; + + Kopete::Message msgIn1(d->myself, d->other, QString("Hello there !"), Kopete::Message::Inbound); + Kopete::Message msgIn2(d->myself, d->other, QString("How are you doing ?"), Kopete::Message::Inbound); + Kopete::Message msgOut1(d->other, d->myself, QString("Fine and you ?"), Kopete::Message::Outbound); + Kopete::Message msgStatus1(d->myself,d->other, QString("You are now marked as away."), Kopete::Message::Internal); + Kopete::Message msgStatus2(d->myself,d->other, QString("You are now marked as online."), Kopete::Message::Internal); + Kopete::Message msgIn3(d->myself, d->other, QString("Well, doing some tests."), Kopete::Message::Internal); + Kopete::Message msgOut2(d->other, d->myself, QString("All your bases are belong to us."), Kopete::Message::Outbound); + Kopete::Message msgOut3(d->other, d->myself, QString("You are on the way to destruction"), Kopete::Message::Outbound); + + // Change style on the fly in ChatMessagePart so this test would run + chatPart->setStyle(d->testStyle); + + // Simulate a consersation + chatPart->appendMessage(msgIn1); + chatPart->appendMessage(msgIn2); + chatPart->appendMessage(msgOut1); + chatPart->appendMessage(msgStatus1); + chatPart->appendMessage(msgStatus2); + chatPart->appendMessage(msgIn3); + chatPart->appendMessage(msgOut2); + chatPart->appendMessage(msgOut3); + + resultHtml = chatPart->htmlDocument().toHTML(); + + // Read the expected(sample) HTML from file. + QFile sampleHtml(QString(SRCDIR)+"sample.html"); + if(sampleHtml.open(IO_ReadOnly)) + { + QTextStream stream(&sampleHtml); + stream.setEncoding(QTextStream::UnicodeUTF8); + expectedFullHtml = stream.read(); + sampleHtml.close(); + } + + CHECK(resultHtml, expectedFullHtml); +} diff --git a/kopete/kopete/chatwindow/tests/chatwindowstylerendering_test.h b/kopete/kopete/chatwindow/tests/chatwindowstylerendering_test.h new file mode 100644 index 00000000..992d00a4 --- /dev/null +++ b/kopete/kopete/chatwindow/tests/chatwindowstylerendering_test.h @@ -0,0 +1,45 @@ +/* + Adium(and Kopete) ChatWindowStyle format rendering test suite + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef CHATWINDOWSTYLERENDERING_TEST_H +#define CHATWINDOWSTYLERENDERING_TEST_H + +#include + +// HACK: Needed to access private methods of ChatMessagePart. +#define private public +#include +#undef private + +class ChatWindowStyleRendering_Test : public KUnitTest::Tester +{ +public: + ChatWindowStyleRendering_Test(); + ~ChatWindowStyleRendering_Test(); + + void allTests(); +public slots: + void testHeaderRendering(); + void testMessageRendering(); + void testStatusRendering(); + void testFullRendering(); +private: + class Private; + Private *d; + + ChatMessagePart *chatPart; +}; +#endif diff --git a/kopete/kopete/config/Makefile.am b/kopete/kopete/config/Makefile.am new file mode 100644 index 00000000..2e866531 --- /dev/null +++ b/kopete/kopete/config/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = plugins accounts behavior appearance identity avdevice + + diff --git a/kopete/kopete/config/accounts/Makefile.am b/kopete/kopete/config/accounts/Makefile.am new file mode 100644 index 00000000..9f32e5af --- /dev/null +++ b/kopete/kopete/config/accounts/Makefile.am @@ -0,0 +1,18 @@ +kopete_srcdir=$(top_srcdir)/kopete +kopete_builddir=$(top_builddir)/kopete + +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(KOPETE_COMPAT_INCLUDES) $(all_includes) -I$(kopete_srcdir)/kopete/addaccountwizard + + +kde_module_LTLIBRARIES = kcm_kopete_accountconfig.la + +kcm_kopete_accountconfig_la_SOURCES = kopeteaccountconfigbase.ui kopeteaccountconfig.cpp +kcm_kopete_accountconfig_la_LDFLAGS = -no-undefined -module $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_accountconfig_la_LIBADD = ../../../libkopete/libkopete.la ../../addaccountwizard/libkopeteaddaccountwizard.la $(LIB_KOPETECOMPAT) $(LIB_KUTILS) + +service_DATA = kopete_accountconfig.desktop +servicedir = $(kde_servicesdir) + +# vim: set noet: + diff --git a/kopete/kopete/config/accounts/kopete_accountconfig.desktop b/kopete/kopete/config/accounts/kopete_accountconfig.desktop new file mode 100644 index 00000000..78ce4efe --- /dev/null +++ b/kopete/kopete/config/accounts/kopete_accountconfig.desktop @@ -0,0 +1,131 @@ +[Desktop Entry] +Icon=personal +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_accountconfig +X-KDE-FactoryName=KopeteAccountConfigFactory +X-KDE-ParentApp=kopete +X-KDE-ParentComponents=kopete + +Name=Accounts +Name[ar]=حسابات +Name[be]=Рахункі +Name[bg]=Сметки +Name[bn]=অà§à¦¯à¦¾à¦•à¦¾à¦‰à¦¨à§à¦Ÿ +Name[br]=Kontoù +Name[bs]=RaÄuni +Name[ca]=Comptes +Name[cs]=ÚÄty +Name[cy]=Cyfrifon +Name[da]=Konti +Name[de]=Zugänge +Name[el]=ΛογαÏιασμοί +Name[eo]=Kontoj +Name[es]=Cuentas +Name[et]=Kontod +Name[eu]=Kontuak +Name[fa]=حسابها +Name[fi]=Tilit +Name[fr]=Comptes +Name[ga]=Cuntais +Name[gl]=Contas +Name[he]=חשבונות +Name[hi]=अकाउनà¥à¤Ÿà¥à¤¸ +Name[hr]=KorisniÄki raÄuni +Name[hu]=Azonosítók +Name[is]=Notandanöfn +Name[it]=Account +Name[ja]=アカウント +Name[ka]=áƒáƒœáƒ’áƒáƒ áƒ˜áƒ¨áƒ”ბი +Name[kk]=Тіркелгілер +Name[km]=គណនី +Name[lt]=Paskyros +Name[mk]=Сметки +Name[nb]=Kontoer +Name[nds]=Kontos +Name[ne]=खाता +Name[nn]=Kontoar +Name[pa]=ਖਾਤੇ +Name[pl]=Konta +Name[pt]=Contas +Name[pt_BR]=Contas +Name[ro]=Conturi +Name[ru]=Учётные запиÑи +Name[se]=Kontut +Name[sk]=ÚÄty +Name[sl]=RaÄuni +Name[sr]=Ðалози +Name[sr@Latn]=Nalozi +Name[sv]=Konton +Name[ta]=கணகà¯à®•à¯à®•à®³à¯ +Name[tg]=ҲиÑоботҳо +Name[tr]=Hesaplar +Name[uk]=Рахунки +Name[uz]=Hisoblar +Name[uz@cyrillic]=ҲиÑоблар +Name[wa]=Contes +Name[zh_CN]=账户 +Name[zh_HK]=帳號 +Name[zh_TW]=帳號 +Comment=Here You Can Manage All Your Accounts +Comment[ar]=من هنا تستطيع إدارة جميع حساباتك +Comment[be]=Кіраванне вашымі рахункамі +Comment[bg]=ÐаÑтройване на Ñметките +Comment[bn]=আপনি à¦à¦–ানে আপনার অà§à¦¯à¦¾à¦•à¦¾à¦‰à¦¨à§à¦Ÿ পরিচালনা করতে পারেন +Comment[bs]=Ovdje možete upravljati svim svojim raÄunima +Comment[ca]=Aquí podreu gestionar tots els vostres comptes +Comment[cs]=Zde můžete spravovat veÅ¡keré své úÄty +Comment[cy]=Yma Gallwch Reoli Eich Cyfrifon i Gyd +Comment[da]=Her kan du hÃ¥ndtere alle dine konti +Comment[de]=Hier können Sie Ihre Zugänge verwalten +Comment[el]=Εδώ μποÏείτε να Ïυθμίσετε όλους τους λογαÏιασμοÏÏ‚ σας +Comment[es]=Gestiona todas sus cuentas +Comment[et]=Kõigi oma kohtode haldamine +Comment[eu]=Hemen zure kontu guztiak kudeatu ditzakezu +Comment[fa]=اینجا می‌توانید همۀ حسابهای خود را مدیریت کنید +Comment[fi]=Täältä voit hallita kaikkia tilejäsi +Comment[fr]=Vous pouvez gérer vos comptes ici +Comment[ga]=Tig leat do chuntais uile a láimhseáil anseo +Comment[gl]=Aquí podes xestionar todas as túas contas +Comment[he]=×›×ן ב×פשרותך לנהל ×ת כל חשבונותיך +Comment[hi]=यहाठआप अपने सभी अकाउनà¥à¤Ÿà¥à¤¸ का पà¥à¤°à¤¬à¤‚धन कर सकते हैं +Comment[hr]=Ovde možete upravljati svim vaÅ¡im korisniÄkim raÄunima +Comment[hu]=Itt lehet kezelni az azonosítókat +Comment[is]=Hér geturðu haldið utan um öll þín notandanöfn +Comment[it]=Qui puoi gestire tutti i tuoi account +Comment[ja]=ã“ã“ã§ã™ã¹ã¦ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’管ç†ã—ã¾ã™ +Comment[ka]=áƒáƒ¥ თქვენ შეგიძლიáƒáƒ— ყველრáƒáƒœáƒ’áƒáƒ áƒ˜áƒ¨áƒ˜áƒ¡ მáƒáƒ áƒ—ვრ+Comment[kk]=Мұнда барлық тіркелгілеріңізді баÑқара алаÑыз +Comment[km]=ទីនáŸáŸ‡ អ្នក​អាច​គ្រប់គ្រង​គណនី​ទាំងអស់​របស់​អ្នក +Comment[lt]=ÄŒia galite tvarkyti visas savo paskyras +Comment[mk]=Тука можете да управувате Ñо Ñите ваши Ñметки +Comment[nb]=Rediger kontoene du har satt opp +Comment[nds]=Hier kannst Du all Dien Kontos plegen +Comment[ne]=यहाठतपाईठतपाईà¤à¤•à¥‹ सबै खाता पà¥à¤°à¤¬à¤¨à¥à¤§ गरà¥à¤¨ सकà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤› +Comment[nl]=Hier kunt u al uw accounts beheren +Comment[nn]=Rediger kontoane du har sett opp +Comment[pl]=Tutaj można zarzÄ…dzać wszystkimi Twoimi kontami +Comment[pt]=Aqui Você Pode Gerir Todas as Suas Contas +Comment[pt_BR]=Aqui você pode gerenciar todas as suas contas +Comment[ro]=Aici vă puteÅ£i administra toate conturile +Comment[ru]=ÐаÑтройка учётных запиÑей +Comment[se]=Dáppe sáhtát gieÄ‘ahallat buot iežat kontuid +Comment[sk]=Tu môžete spravovaÅ¥ vÅ¡etky svoje úÄty +Comment[sl]=Tukaj lahko upravljate z vsemi svojimi raÄuni +Comment[sr]=Овде можете управљати Ñвим вашим налозима +Comment[sr@Latn]=Ovde možete upravljati svim vaÅ¡im nalozima +Comment[sv]=Här kan du hantera alla dina konton +Comment[ta]=இஙà¯à®•à¯ நீஙà¯à®•à®³à¯ அனைதà¯à®¤à¯ கணகà¯à®•à¯à®•à®³à¯ˆà®¯à¯à®®à¯ உளà¯à®³à®®à¯ˆà®•à¯à®• à®®à¯à®Ÿà®¿à®¯à¯à®®à¯ +Comment[tg]=Дар ин ҷо Шумо Ҳамаи ҲиÑоботҳои худро Идора карда метавонед +Comment[tr]=Bütün Hesaplarınız Buradan Yönetilebilir +Comment[uk]=Тут можна керувати вашими рахунками +Comment[uz]=Bu yerda hamma hisoblarni moslash mumkin +Comment[uz@cyrillic]=Бу ерда ҳамма ҳиÑобларни моÑлаш мумкин +Comment[zh_CN]=您å¯åœ¨æ­¤ç®¡ç†æ‚¨å…¨éƒ¨çš„账户 +Comment[zh_HK]=在此您å¯ç®¡ç†æ‚¨çš„帳號 +Comment[zh_TW]=您å¯ä»¥åœ¨æ­¤ç®¡ç†æ‚¨çš„所有帳號 +DocPath=kopete/configure-dialog.html#configuring-accounts + + diff --git a/kopete/kopete/config/accounts/kopeteaccountconfig.cpp b/kopete/kopete/config/accounts/kopeteaccountconfig.cpp new file mode 100644 index 00000000..3d86fb8d --- /dev/null +++ b/kopete/kopete/config/accounts/kopeteaccountconfig.cpp @@ -0,0 +1,285 @@ +/* + accountconfig.cpp - Kopete account config page + + Copyright (c) 2003-2004 by Olivier Goffart + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopeteaccountconfig.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "addaccountwizard.h" +#include "editaccountwidget.h" +#include "kopeteaccountconfigbase.h" +#include "kopeteaccountmanager.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" + +class KopeteAccountLVI : public KListViewItem +{ + public: + KopeteAccountLVI( Kopete::Account *a, KListView *p ) : KListViewItem( p ){ m_account = a; } + Kopete::Account *account() { return m_account; } + + private: + //need to be guarded because some accounts may be linked (that's the case of jabber transports) + QGuardedPtr m_account; +}; + +typedef KGenericFactory KopeteAccountConfigFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_accountconfig, KopeteAccountConfigFactory( "kcm_kopete_accountconfig" ) ) + +KopeteAccountConfig::KopeteAccountConfig( QWidget *parent, const char * /* name */, const QStringList &args ) +: KCModule( KopeteAccountConfigFactory::instance(), parent, args ) +{ + + ( new QVBoxLayout( this ) )->setAutoAdd( true ); + m_view = new KopeteAccountConfigBase( this, "KopeteAccountConfig::m_view" ); + + m_view->mButtonUp->setIconSet( SmallIconSet( "up" ) ); + m_view->mButtonDown->setIconSet( SmallIconSet( "down" ) ); + + connect( m_view->mButtonNew, SIGNAL( clicked() ), this, SLOT( slotAddAccount() ) ); + connect( m_view->mButtonEdit, SIGNAL( clicked() ), this, SLOT( slotEditAccount() ) ); + connect( m_view->mButtonRemove, SIGNAL( clicked() ), this, SLOT( slotRemoveAccount() ) ); + connect( m_view->mButtonUp, SIGNAL( clicked() ), this, SLOT( slotAccountUp() ) ); + connect( m_view->mButtonDown, SIGNAL( clicked() ), this, SLOT( slotAccountDown() ) ); + connect( m_view->mAccountList, SIGNAL( selectionChanged() ), this, SLOT( slotItemSelected() ) ); + connect( m_view->mAccountList, SIGNAL( doubleClicked( QListViewItem * ) ), this, SLOT( slotEditAccount() ) ); + connect( m_view->mUseColor, SIGNAL( toggled( bool ) ), this, SLOT( slotColorChanged() ) ); + connect( m_view->mColorButton, SIGNAL( changed( const QColor & ) ), this, SLOT( slotColorChanged() ) ); + + m_view->mAccountList->setSorting(-1); + + setButtons( Help ); + load(); +} + +void KopeteAccountConfig::save() +{ + uint priority = m_view->mAccountList->childCount(); + + KopeteAccountLVI *i = static_cast( m_view->mAccountList->firstChild() ); + while( i ) + { + if(!i->account()) + continue; + i->account()->setPriority( priority-- ); + i = static_cast( i->nextSibling() ); + } + + QMap::Iterator it; + for(it=m_newColors.begin() ; it != m_newColors.end() ; ++it) + it.key()->setColor(it.data()); + m_newColors.clear(); + + Kopete::AccountManager::self()->save(); + + load(); //refresh the colred accounts (in case of apply) +} + +void KopeteAccountConfig::load() +{ + KopeteAccountLVI *lvi = 0L; + + m_view->mAccountList->clear(); + + QPtrList accounts = Kopete::AccountManager::self()->accounts(); + for ( Kopete::Account *i = accounts.first() ; i; i = accounts.next() ) + { + // Insert the item after the previous one + lvi = new KopeteAccountLVI( i, m_view->mAccountList ); + lvi->setText( 0, i->protocol()->displayName() ); + lvi->setPixmap( 0, i->accountIcon() ); + lvi->setText( 1, i->accountLabel() ); + } + + m_newColors.clear(); + slotItemSelected(); +} + +void KopeteAccountConfig::slotItemSelected() +{ + m_protected=true; + KopeteAccountLVI *itemSelected = static_cast( m_view->mAccountList->selectedItem() ); + + m_view->mButtonEdit->setEnabled( itemSelected ); + m_view->mButtonRemove->setEnabled( itemSelected ); + + if ( itemSelected && itemSelected->account() ) + { + m_view->mButtonUp->setEnabled( itemSelected->itemAbove() ); + m_view->mButtonDown->setEnabled( itemSelected->itemBelow() ); + + Kopete::Account *account = itemSelected->account(); + QColor color= m_newColors.contains(account) ? m_newColors[account] : account->color(); + m_view->mUseColor->setEnabled( true ); + m_view->mUseColor->setChecked( color.isValid() ); + m_view->mColorButton->setColor( color ); + m_view->mColorButton->setEnabled( m_view->mUseColor->isChecked() ); + + } + else + { + m_view->mButtonUp->setEnabled( false ); + m_view->mButtonDown->setEnabled( false); + m_view->mUseColor->setEnabled( false ); + m_view->mColorButton->setEnabled( false ); + } + m_protected=false; +} + +void KopeteAccountConfig::slotAccountUp() +{ + KopeteAccountLVI *itemSelected = static_cast( m_view->mAccountList->selectedItem() ); + if ( !itemSelected ) + return; + + if ( itemSelected->itemAbove() ) + itemSelected->itemAbove()->moveItem( itemSelected ); + + slotItemSelected(); + emit changed( true ); +} + +void KopeteAccountConfig::slotAccountDown() +{ + KopeteAccountLVI *itemSelected = static_cast( m_view->mAccountList->selectedItem() ); + if ( !itemSelected ) + return; + + itemSelected->moveItem( itemSelected->itemBelow() ); + + slotItemSelected(); + emit changed( true ); +} + +void KopeteAccountConfig::slotAddAccount() +{ + AddAccountWizard *m_addwizard = new AddAccountWizard( this, "addAccountWizard", true ); + connect( m_addwizard, SIGNAL( destroyed( QObject * ) ), this, SLOT( slotAddWizardDone() ) ); + m_addwizard->show(); +} + +void KopeteAccountConfig::slotEditAccount() +{ + KopeteAccountLVI *lvi = static_cast( m_view->mAccountList->selectedItem() ); + if ( !lvi || !lvi->account() ) + return; + + Kopete::Account *ident = lvi->account(); + Kopete::Protocol *proto = ident->protocol(); + + KDialogBase *editDialog = new KDialogBase( this, "KopeteAccountConfig::editDialog", true, + i18n( "Edit Account" ), KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok, true ); + + KopeteEditAccountWidget *m_accountWidget = proto->createEditAccountWidget( ident, editDialog ); + if ( !m_accountWidget ) + return; + + // FIXME: Why the #### is EditAccountWidget not a QWidget?!? This sideways casting + // is braindead and error-prone. Looking at MSN the only reason I can see is + // because it allows direct subclassing of designer widgets. But what is + // wrong with embedding the designer widget in an empty QWidget instead? + // Also, if this REALLY has to be a pure class and not a widget, then the + // class should at least be renamed to EditAccountIface instead - Martijn + QWidget *w = dynamic_cast( m_accountWidget ); + if ( !w ) + return; + + editDialog->setMainWidget( w ); + if ( editDialog->exec() == QDialog::Accepted ) + { + if( m_accountWidget->validateData() ) + m_accountWidget->apply(); + } + + // FIXME: Why deleteLater? It shouldn't be in use anymore at this point - Martijn + editDialog->deleteLater(); + load(); + Kopete::AccountManager::self()->save(); +} + +void KopeteAccountConfig::slotRemoveAccount() +{ + KopeteAccountLVI *lvi = static_cast( m_view->mAccountList->selectedItem() ); + if ( !lvi || !lvi->account() ) + return; + + Kopete::Account *i = lvi->account(); + if ( KMessageBox::warningContinueCancel( this, i18n( "Are you sure you want to remove the account \"%1\"?" ).arg( i->accountLabel() ), + i18n( "Remove Account" ), KGuiItem(i18n( "Remove Account" ), "editdelete"), + "askRemoveAccount", KMessageBox::Notify | KMessageBox::Dangerous ) == KMessageBox::Continue ) + { + Kopete::AccountManager::self()->removeAccount( i ); + delete lvi; + } +} + +void KopeteAccountConfig::slotAddWizardDone() +{ + save(); + load(); +} + +void KopeteAccountConfig::slotColorChanged() +{ + if(m_protected) //this slot is called because we changed the button + return; // color because another account has been selected + + KopeteAccountLVI *lvi = static_cast( m_view->mAccountList->selectedItem() ); + if ( !lvi || !lvi->account() ) + return; + Kopete::Account *account = lvi->account(); + + if(!account->color().isValid() && !m_view->mUseColor->isChecked() ) + { //we don't use color for that account and nothing changed. + m_newColors.remove(account); + return; + } + else if(!m_view->mUseColor->isChecked()) + { //the user disabled account coloring, but it was activated before + m_newColors[account]=QColor(); + emit changed(true); + return; + } + else if(account->color() == m_view->mColorButton->color() ) + { //The color has not changed. + m_newColors.remove(account); + return; + } + else + { + m_newColors[account]=m_view->mColorButton->color(); + emit changed(true); + } +} + +#include "kopeteaccountconfig.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/config/accounts/kopeteaccountconfig.h b/kopete/kopete/config/accounts/kopeteaccountconfig.h new file mode 100644 index 00000000..9aec2a60 --- /dev/null +++ b/kopete/kopete/config/accounts/kopeteaccountconfig.h @@ -0,0 +1,61 @@ +/* + accountconfig.h - Kopete account config page + + Copyright (c) 2003-2004 by Olivier Goffart + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef __ACCOUNTCONFIG_H +#define __ACCOUNTCONFIG_H + +#include +#include +#include + +namespace Kopete +{ +class Account; +} + +class KopeteAccountConfigBase; + +/** + * @author Olivier Goffart + */ +class KopeteAccountConfig : public KCModule +{ + Q_OBJECT + +public: + KopeteAccountConfig(QWidget *parent, const char *name, const QStringList &args ); + +public slots: + virtual void save(); + virtual void load(); + +private: + KopeteAccountConfigBase *m_view; + QMap m_newColors; + bool m_protected; + +private slots: + void slotRemoveAccount(); + void slotEditAccount(); + void slotAddAccount(); + void slotAddWizardDone(); + void slotItemSelected(); + void slotAccountUp(); + void slotAccountDown(); + void slotColorChanged(); +}; +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/config/accounts/kopeteaccountconfigbase.ui b/kopete/kopete/config/accounts/kopeteaccountconfigbase.ui new file mode 100644 index 00000000..eea90c35 --- /dev/null +++ b/kopete/kopete/config/accounts/kopeteaccountconfigbase.ui @@ -0,0 +1,268 @@ + +KopeteAccountConfigBase +Olivier Goffart + + + KopeteAccountConfigBase + + + + 0 + 0 + 477 + 316 + + + + Manage Accounts + + + + unnamed + + + 0 + + + + mButtonNew + + + &New... + + + Add new account + + + + + mButtonEdit + + + &Modify... + + + Modify selected account + + + Let you edit the account's properties. + + + + + mButtonRemove + + + &Remove + + + Remove selected account + + + Remove selected account + + + + + + Protocol + + + true + + + true + + + + + Account + + + true + + + true + + + + mAccountList + + + true + + + + + mUseColor + + + Use &custom color + + + Use custom color for account + + + Allows you to set a custom color for this account + + + + + mColorButton + + + + + + Account custom color selector + + + Allows you to set a custom color for this account. +The icon of every contact of this account will be coloured with this color. Useful if you have several accounts of the same protocol + + + + + spacer3_2 + + + Horizontal + + + Minimum + + + + 16 + 16 + + + + + + layout27 + + + + unnamed + + + + mButtonUp + + + + 60 + 10 + + + + + + + Increase the priority + + + Uses these buttons to increase or decrease the priority. +The priority is used to determine which contact to use when you click on a metacontact: Kopete will use the contact of the account with the greatest priority (if all contacts have the same online status.) + + + + + spacer41 + + + Horizontal + + + Expanding + + + + 20 + 40 + + + + + + mButtonDown + + + + 60 + 10 + + + + + + + Decrease the priority + + + Uses these buttons to increase or decrease the priority. +The priority is used to determine which contact to use when you click on a metacontact: Kopete will use the contact of the account with the greatest priority (if all contacts have the same online status.) + + + + + + + spacer1 + + + Vertical + + + Expanding + + + + 70 + 50 + + + + + + spacer3 + + + Vertical + + + Fixed + + + + 70 + 20 + + + + + + + + + + mUseColor + toggled(bool) + mColorButton + setEnabled(bool) + + + + mAccountList + mButtonNew + mButtonEdit + mButtonRemove + + + + klistview.h + kpushbutton.h + kpushbutton.h + + diff --git a/kopete/kopete/config/appearance/Makefile.am b/kopete/kopete/config/appearance/Makefile.am new file mode 100644 index 00000000..7e7fc8ca --- /dev/null +++ b/kopete/kopete/config/appearance/Makefile.am @@ -0,0 +1,20 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/libkopete/private \ + -I$(top_srcdir)/kopete/kopete/chatwindow $(KOPETE_COMPAT_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kcm_kopete_appearanceconfig.la + +kcm_kopete_appearanceconfig_la_SOURCES = appearanceconfig_emoticons.ui \ + appearanceconfig_colors.ui appearanceconfig_chatwindow.ui appearanceconfig_contactlist.ui \ + appearanceconfig.cpp tooltipeditwidget.ui tooltipeditdialog.cpp + +kcm_kopete_appearanceconfig_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) \ + $(all_libraries) +kcm_kopete_appearanceconfig_la_LIBADD = ../../../libkopete/libkopete.la \ + ../../../kopete/chatwindow/libkopetechatwindow.la \ + -lktexteditor $(LIB_KOPETECOMPAT) $(LIB_KUTILS) $(LIB_KNEWSTUFF) + +service_DATA = kopete_appearanceconfig.desktop +servicedir = $(kde_servicesdir) + +# vim: set noet: diff --git a/kopete/kopete/config/appearance/appearanceconfig.cpp b/kopete/kopete/config/appearance/appearanceconfig.cpp new file mode 100644 index 00000000..e3867d41 --- /dev/null +++ b/kopete/kopete/config/appearance/appearanceconfig.cpp @@ -0,0 +1,870 @@ +/* + appearanceconfig.cpp - Kopete Look Feel Config + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + Copyright (c) 2005-2006 by Michaël Larouche + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "appearanceconfig.h" +#include "appearanceconfig_emoticons.h" +#include "appearanceconfig_chatwindow.h" +#include "appearanceconfig_colors.h" +#include "appearanceconfig_contactlist.h" + +#include "tooltipeditdialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include // for KNewStuff emoticon fetching +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // KNewStuff +#include +#include +#include + +#include // knewstuff emoticon and chatwindow fetching +#include // " +#include // " +#include // " +#include // " + +// For Kopete Chat Window Style configuration and preview. +#include +#include +#include + +// Things we fake to get the message preview to work +#include +#include +#include +#include +#include +#include +#include + +#include "kopeteprefs.h" +#include "kopeteemoticons.h" +#include "kopeteglobal.h" + +#include + +typedef KGenericFactory KopeteAppearanceConfigFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_appearanceconfig, KopeteAppearanceConfigFactory( "kcm_kopete_appearanceconfig" ) ) + +class FakeProtocol; +class FakeAccount; +class FakeContact; + +class AppearanceConfig::Private +{ +public: + Private() + : mAppearanceTabCtl(0L), preview(0L), mPrfsEmoticons(0L),mPrfsChatWindow(0L), + mPrfsColors(0L), mPrfsContactList(0L), currentStyle(0L), loading(false), + styleChanged(false) + {} + + QTabWidget *mAppearanceTabCtl; + + ChatMessagePart *preview; + AppearanceConfig_Emoticons *mPrfsEmoticons; + AppearanceConfig_ChatWindow *mPrfsChatWindow; + AppearanceConfig_Colors *mPrfsColors; + AppearanceConfig_ContactList *mPrfsContactList; + + // value is the style path + QMap styleItemMap; + ChatWindowStyle::StyleVariants currentVariantMap; + ChatWindowStyle *currentStyle; + bool loading; + bool styleChanged; + + // For style preview + FakeProtocol *previewProtocol; + FakeAccount *previewAccount; + Kopete::MetaContact *myselfMetaContact; + Kopete::MetaContact *jackMetaContact; + FakeContact *myself; + FakeContact *jack; + Kopete::ChatSession *previewChatSession; +}; + +class KopeteStyleNewStuff : public KNewStuff +{ +public: + KopeteStyleNewStuff(const QString &type, QWidget *parentWidget = 0) + : KNewStuff( type, parentWidget) + {} + + bool createUploadFile(const QString &) + { + return false; + } + + bool install(const QString &styleFilename) + { + int styleInstallReturn = 0; + styleInstallReturn = ChatWindowStyleManager::self()->installStyle( styleFilename ); + + switch(styleInstallReturn) + { + case ChatWindowStyleManager::StyleInstallOk: + { + KMessageBox::queuedMessageBox( this->parentWidget(), KMessageBox::Information, i18n("The Chat Window style was successfully installed."), i18n("Install successful") ); + return true; + } + case ChatWindowStyleManager::StyleCannotOpen: + { + KMessageBox::queuedMessageBox( this->parentWidget(), KMessageBox::Error, i18n("The specified archive cannot be opened.\nMake sure that the archive is valid ZIP or TAR archive."), i18n("Cannot open archive") ); + break; + } + case ChatWindowStyleManager::StyleNoDirectoryValid: + { + KMessageBox::queuedMessageBox( this->parentWidget(), KMessageBox::Error, i18n("Could not find a suitable place to install the Chat Window style in user directory."), i18n("Cannot find styles directory") ); + break; + } + case ChatWindowStyleManager::StyleNotValid: + { + KMessageBox::queuedMessageBox( this->parentWidget(), KMessageBox::Error, i18n("The specified archive does not contain a valid Chat Window style."), i18n("Invalid Style") ); + break; + } + + case ChatWindowStyleManager::StyleUnknow: + default: + { + KMessageBox::queuedMessageBox( this->parentWidget(), KMessageBox::Error, i18n("An unknow error occurred while trying to install the Chat Window style."), i18n("Unknow error") ); + break; + } + } + return false; + } +}; + +// TODO: Someday, this configuration dialog must(not should) use KConfigXT +AppearanceConfig::AppearanceConfig(QWidget *parent, const char* /*name*/, const QStringList &args ) +: KCModule( KopeteAppearanceConfigFactory::instance(), parent, args ) +{ + d = new Private; + + (new QVBoxLayout(this))->setAutoAdd(true); + d->mAppearanceTabCtl = new QTabWidget(this, "mAppearanceTabCtl"); + + KConfig *config = KGlobal::config(); + config->setGroup( "ChatWindowSettings" ); + + // "Emoticons" TAB ========================================================== + d->mPrfsEmoticons = new AppearanceConfig_Emoticons(d->mAppearanceTabCtl); + connect(d->mPrfsEmoticons->chkUseEmoticons, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsEmoticons->chkRequireSpaces, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsEmoticons->icon_theme_list, SIGNAL(selectionChanged()), + this, SLOT(slotSelectedEmoticonsThemeChanged())); + connect(d->mPrfsEmoticons->btnInstallTheme, SIGNAL(clicked()), + this, SLOT(installEmoticonTheme())); + + connect(d->mPrfsEmoticons->btnGetThemes, SIGNAL(clicked()), + this, SLOT(slotGetEmoticonThemes())); + connect(d->mPrfsEmoticons->btnRemoveTheme, SIGNAL(clicked()), + this, SLOT(removeSelectedEmoticonTheme())); + + d->mAppearanceTabCtl->addTab(d->mPrfsEmoticons, i18n("&Emoticons")); + + // "Chat Window" TAB ======================================================== + d->mPrfsChatWindow = new AppearanceConfig_ChatWindow(d->mAppearanceTabCtl); + + connect(d->mPrfsChatWindow->styleList, SIGNAL(selectionChanged(QListBoxItem *)), + this, SLOT(slotChatStyleSelected())); + connect(d->mPrfsChatWindow->variantList, SIGNAL(activated(const QString&)), + this, SLOT(slotChatStyleVariantSelected(const QString &))); + connect(d->mPrfsChatWindow->deleteButton, SIGNAL(clicked()), + this, SLOT(slotDeleteChatStyle())); + connect(d->mPrfsChatWindow->installButton, SIGNAL(clicked()), + this, SLOT(slotInstallChatStyle())); + connect(d->mPrfsChatWindow->btnGetStyles, SIGNAL(clicked()), + this, SLOT(slotGetChatStyles())); + connect(d->mPrfsChatWindow->groupConsecutiveMessages, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + // Show the available styles when the Manager has finish to load the styles. + connect(ChatWindowStyleManager::self(), SIGNAL(loadStylesFinished()), this, SLOT(slotLoadChatStyles())); + + d->mPrfsChatWindow->htmlFrame->setFrameStyle(QFrame::WinPanel | QFrame::Sunken); + // Create the fake Chat Session + createPreviewChatSession(); + QVBoxLayout *l = new QVBoxLayout(d->mPrfsChatWindow->htmlFrame); + d->preview = new ChatMessagePart(d->previewChatSession, d->mPrfsChatWindow->htmlFrame, "preview"); + d->preview->setJScriptEnabled(false); + d->preview->setJavaEnabled(false); + d->preview->setPluginsEnabled(false); + d->preview->setMetaRefreshEnabled(false); + KHTMLView *htmlWidget = d->preview->view(); + htmlWidget->setMarginWidth(4); + htmlWidget->setMarginHeight(4); + htmlWidget->setFocusPolicy(NoFocus); + htmlWidget->setSizePolicy( + QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); + l->addWidget(htmlWidget); + // Add the preview message to the ChatMessagePart + createPreviewMessages(); + + d->mAppearanceTabCtl->addTab( d->mPrfsChatWindow, i18n("Chat Window") ); + + // "Contact List" TAB ======================================================= + d->mPrfsContactList = new AppearanceConfig_ContactList(d->mAppearanceTabCtl); + connect(d->mPrfsContactList->mTreeContactList, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsContactList->mSortByGroup, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsContactList->mEditTooltips, SIGNAL(clicked()), + this, SLOT(slotEditTooltips())); + connect(d->mPrfsContactList->mIndentContacts, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsContactList->mDisplayMode, SIGNAL(clicked(int)), + this, SLOT(emitChanged())); + connect(d->mPrfsContactList->mIconMode, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsContactList->mAnimateChanges, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsContactList->mFadeVisibility, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsContactList->mFoldVisibility, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsContactList->mAutoHide, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsContactList->mAutoHideTimeout, SIGNAL(valueChanged(int)), + this, SLOT(emitChanged())); + + // don't enable the checkbox if XRender is not available + #ifdef HAVE_XRENDER + d->mPrfsContactList->mFadeVisibility->setEnabled(true); + #else + d->mPrfsContactList->mFadeVisibility->setEnabled(false); + #endif + + d->mAppearanceTabCtl->addTab(d->mPrfsContactList, i18n("Contact List")); + + // "Colors and Fonts" TAB =================================================== + d->mPrfsColors = new AppearanceConfig_Colors(d->mAppearanceTabCtl); + connect(d->mPrfsColors->foregroundColor, SIGNAL(changed(const QColor &)), + this, SLOT(slotHighlightChanged())); + connect(d->mPrfsColors->backgroundColor, SIGNAL(changed(const QColor &)), + this, SLOT(slotHighlightChanged())); + connect(d->mPrfsColors->fontFace, SIGNAL(fontSelected(const QFont &)), + this, SLOT(slotChangeFont())); + connect(d->mPrfsColors->textColor, SIGNAL(changed(const QColor &)), + this, SLOT(slotUpdateChatPreview())); + connect(d->mPrfsColors->bgColor, SIGNAL(changed(const QColor &)), + this, SLOT(slotUpdateChatPreview())); + connect(d->mPrfsColors->linkColor, SIGNAL(changed(const QColor &)), + this, SLOT(slotUpdateChatPreview())); + connect(d->mPrfsColors->mGreyIdleMetaContacts, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsColors->idleContactColor, SIGNAL(changed(const QColor &)), + this, SLOT(emitChanged())); + connect(d->mPrfsColors->mUseCustomFonts, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsColors->mSmallFont, SIGNAL(fontSelected(const QFont &)), + this, SLOT(emitChanged())); + connect(d->mPrfsColors->mNormalFont, SIGNAL(fontSelected(const QFont &)), + this, SLOT(emitChanged())); + connect(d->mPrfsColors->mGroupNameColor, SIGNAL(changed(const QColor &)), + this, SLOT(emitChanged())); + + connect(d->mPrfsColors->mBgOverride, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsColors->mFgOverride, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + connect(d->mPrfsColors->mRtfOverride, SIGNAL(toggled(bool)), + this, SLOT(emitChanged())); + + d->mAppearanceTabCtl->addTab(d->mPrfsColors, i18n("Colors && Fonts")); + + // ========================================================================== + + load(); +} + +AppearanceConfig::~AppearanceConfig() +{ + delete d; +} + +void AppearanceConfig::updateEmoticonsButton(bool _b) +{ + QString themeName = d->mPrfsEmoticons->icon_theme_list->currentText(); + QFileInfo fileInf(KGlobal::dirs()->findResource("emoticons", themeName+"/")); + d->mPrfsEmoticons->btnRemoveTheme->setEnabled( _b && fileInf.isWritable()); + d->mPrfsEmoticons->btnGetThemes->setEnabled( false ); +} + +void AppearanceConfig::save() +{ +// kdDebug(14000) << k_funcinfo << "called." << endl; + KopetePrefs *p = KopetePrefs::prefs(); + + // "Emoticons" TAB ========================================================== + p->setIconTheme( d->mPrfsEmoticons->icon_theme_list->currentText() ); + p->setUseEmoticons ( d->mPrfsEmoticons->chkUseEmoticons->isChecked() ); + p->setEmoticonsRequireSpaces( d->mPrfsEmoticons->chkRequireSpaces->isChecked() ); + + // "Chat Window" TAB ======================================================== + p->setGroupConsecutiveMessages( d->mPrfsChatWindow->groupConsecutiveMessages->isChecked() ); + + // Get the stylePath + if(d->currentStyle) + { + kdDebug(14000) << k_funcinfo << d->currentStyle->getStylePath() << endl; + p->setStylePath(d->currentStyle->getStylePath()); + } + // Get and save the styleVariant + if( !d->currentVariantMap.empty() ) + { + kdDebug(14000) << k_funcinfo << d->currentVariantMap[ d->mPrfsChatWindow->variantList->currentText()] << endl; + p->setStyleVariant(d->currentVariantMap[ d->mPrfsChatWindow->variantList->currentText()]); + } + + // "Contact List" TAB ======================================================= + p->setTreeView(d->mPrfsContactList->mTreeContactList->isChecked()); + p->setSortByGroup(d->mPrfsContactList->mSortByGroup->isChecked()); + p->setContactListIndentContacts(d->mPrfsContactList->mIndentContacts->isChecked()); + p->setContactListDisplayMode(KopetePrefs::ContactDisplayMode(d->mPrfsContactList->mDisplayMode->selectedId())); + p->setContactListIconMode(KopetePrefs::IconDisplayMode((d->mPrfsContactList->mIconMode->isChecked()) ? KopetePrefs::PhotoPic : KopetePrefs::IconPic)); + p->setContactListAnimation(d->mPrfsContactList->mAnimateChanges->isChecked()); + p->setContactListFading(d->mPrfsContactList->mFadeVisibility->isChecked()); + p->setContactListFolding(d->mPrfsContactList->mFoldVisibility->isChecked()); + + // "Colors & Fonts" TAB ===================================================== + p->setHighlightBackground(d->mPrfsColors->backgroundColor->color()); + p->setHighlightForeground(d->mPrfsColors->foregroundColor->color()); + p->setBgColor(d->mPrfsColors->bgColor->color()); + p->setTextColor(d->mPrfsColors->textColor->color()); + p->setLinkColor(d->mPrfsColors->linkColor->color()); + p->setFontFace(d->mPrfsColors->fontFace->font()); + p->setIdleContactColor(d->mPrfsColors->idleContactColor->color()); + p->setGreyIdleMetaContacts(d->mPrfsColors->mGreyIdleMetaContacts->isChecked()); + p->setContactListUseCustomFonts(d->mPrfsColors->mUseCustomFonts->isChecked()); + p->setContactListCustomSmallFont(d->mPrfsColors->mSmallFont->font()); + p->setContactListCustomNormalFont(d->mPrfsColors->mNormalFont->font()); + p->setContactListGroupNameColor(d->mPrfsColors->mGroupNameColor->color()); + p->setContactListAutoHide(d->mPrfsContactList->mAutoHide->isChecked()); + p->setContactListAutoHideTimeout(d->mPrfsContactList->mAutoHideTimeout->value()); + + p->setBgOverride( d->mPrfsColors->mBgOverride->isChecked() ); + p->setFgOverride( d->mPrfsColors->mFgOverride->isChecked() ); + p->setRtfOverride( d->mPrfsColors->mRtfOverride->isChecked() ); + + p->save(); + d->styleChanged = false; +} + +void AppearanceConfig::load() +{ + //we will change the state of somme controls, which will call some signals. + //so to don't refresh everything several times, we memorize we are loading. + d->loading=true; + +// kdDebug(14000) << k_funcinfo << "called" << endl; + KopetePrefs *p = KopetePrefs::prefs(); + + // "Emoticons" TAB ========================================================== + updateEmoticonlist(); + d->mPrfsEmoticons->chkUseEmoticons->setChecked( p->useEmoticons() ); + d->mPrfsEmoticons->chkRequireSpaces->setChecked( p->emoticonsRequireSpaces() ); + + // "Chat Window" TAB ======================================================== + d->mPrfsChatWindow->groupConsecutiveMessages->setChecked( p->groupConsecutiveMessages() ); + // Look for avaiable chat window styles. + slotLoadChatStyles(); + + // "Contact List" TAB ======================================================= + d->mPrfsContactList->mTreeContactList->setChecked( p->treeView() ); + d->mPrfsContactList->mSortByGroup->setChecked( p->sortByGroup() ); + d->mPrfsContactList->mIndentContacts->setChecked( p->contactListIndentContacts() ); + + // convert old single value display mode to dual display/icon modes + if (p->contactListDisplayMode() == KopetePrefs::Yagami) { + p->setContactListDisplayMode( KopetePrefs::Detailed); + p->setContactListIconMode( KopetePrefs::PhotoPic ); + } + + d->mPrfsContactList->mDisplayMode->setButton( p->contactListDisplayMode() ); + d->mPrfsContactList->mIconMode->setChecked( p->contactListIconMode() == KopetePrefs::PhotoPic); + + + d->mPrfsContactList->mAnimateChanges->setChecked( p->contactListAnimation() ); +#ifdef HAVE_XRENDER + d->mPrfsContactList->mFadeVisibility->setChecked( p->contactListFading() ); +#else + d->mPrfsContactList->mFadeVisibility->setChecked( false ); +#endif + d->mPrfsContactList->mFoldVisibility->setChecked( p->contactListFolding() ); + d->mPrfsContactList->mAutoHide->setChecked( p->contactListAutoHide() ); + d->mPrfsContactList->mAutoHideTimeout->setValue( p->contactListAutoHideTimeout() ); + + // "Colors & Fonts" TAB ===================================================== + d->mPrfsColors->foregroundColor->setColor(p->highlightForeground()); + d->mPrfsColors->backgroundColor->setColor(p->highlightBackground()); + d->mPrfsColors->textColor->setColor(p->textColor()); + d->mPrfsColors->linkColor->setColor(p->linkColor()); + d->mPrfsColors->bgColor->setColor(p->bgColor()); + d->mPrfsColors->fontFace->setFont(p->fontFace()); + d->mPrfsColors->mGreyIdleMetaContacts->setChecked(p->greyIdleMetaContacts()); + d->mPrfsColors->idleContactColor->setColor(p->idleContactColor()); + d->mPrfsColors->mUseCustomFonts->setChecked(p->contactListUseCustomFonts()); + d->mPrfsColors->mSmallFont->setFont(p->contactListCustomSmallFont()); + d->mPrfsColors->mNormalFont->setFont(p->contactListCustomNormalFont()); + d->mPrfsColors->mGroupNameColor->setColor(p->contactListGroupNameColor()); + + d->mPrfsColors->mBgOverride->setChecked( p->bgOverride() ); + d->mPrfsColors->mFgOverride->setChecked( p->fgOverride() ); + d->mPrfsColors->mRtfOverride->setChecked( p->rtfOverride() ); + + d->loading=false; + slotUpdateChatPreview(); +} + +void AppearanceConfig::slotLoadChatStyles() +{ + d->mPrfsChatWindow->styleList->clear(); + d->styleItemMap.clear(); + + ChatWindowStyleManager::StyleList availableStyles; + availableStyles = ChatWindowStyleManager::self()->getAvailableStyles(); + if( availableStyles.empty() ) + kdDebug(14000) << k_funcinfo << "Warning, available styles is empty !" << endl; + + ChatWindowStyleManager::StyleList::ConstIterator it, itEnd = availableStyles.constEnd(); + for(it = availableStyles.constBegin(); it != itEnd; ++it) + { + // Insert style name into the listbox + d->mPrfsChatWindow->styleList->insertItem( it.key(), 0 ); + // Insert the style class into the internal map for futher acces. + d->styleItemMap.insert( d->mPrfsChatWindow->styleList->firstItem(), it.data() ); + + if( it.data() == KopetePrefs::prefs()->stylePath() ) + { + kdDebug(14000) << k_funcinfo << "Restoring saved style: " << it.key() << endl; + + d->mPrfsChatWindow->styleList->setSelected( d->mPrfsChatWindow->styleList->firstItem(), true ); + } + } + + d->mPrfsChatWindow->styleList->sort(); +} + +void AppearanceConfig::updateEmoticonlist() +{ + KopetePrefs *p = KopetePrefs::prefs(); + KStandardDirs dir; + + d->mPrfsEmoticons->icon_theme_list->clear(); // Wipe out old list + // Get a list of directories in our icon theme dir + QStringList themeDirs = KGlobal::dirs()->findDirs("emoticons", ""); + // loop adding themes from all dirs into theme-list + for(unsigned int x = 0;x < themeDirs.count();x++) + { + QDir themeQDir(themeDirs[x]); + themeQDir.setFilter( QDir::Dirs ); // only scan for subdirs + themeQDir.setSorting( QDir::Name ); // I guess name is as good as any + for(unsigned int y = 0; y < themeQDir.count(); y++) + { + QStringList themes = themeQDir.entryList(QDir::Dirs, QDir::Name); + // We don't care for '.' and '..' + if ( themeQDir[y] != "." && themeQDir[y] != ".." ) + { + // Add ourselves to the list, using our directory name FIXME: use the first emoticon of the theme. + QPixmap previewPixmap = QPixmap(locate("emoticons", themeQDir[y]+"/smile.png")); + d->mPrfsEmoticons->icon_theme_list->insertItem(previewPixmap,themeQDir[y]); + } + } + } + + // Where is that theme in our big-list-o-themes? + QListBoxItem *item = d->mPrfsEmoticons->icon_theme_list->findItem( p->iconTheme() ); + + if (item) // found it... make it the currently selected theme + d->mPrfsEmoticons->icon_theme_list->setCurrentItem( item ); + else // Er, it's not there... select the current item + d->mPrfsEmoticons->icon_theme_list->setCurrentItem( 0 ); +} + +void AppearanceConfig::slotSelectedEmoticonsThemeChanged() +{ + QString themeName = d->mPrfsEmoticons->icon_theme_list->currentText(); + QFileInfo fileInf(KGlobal::dirs()->findResource("emoticons", themeName+"/")); + d->mPrfsEmoticons->btnRemoveTheme->setEnabled( fileInf.isWritable() ); + + Kopete::Emoticons emoticons( themeName ); + QStringList smileys = emoticons.emoticonAndPicList().keys(); + QString newContentText = ""; + + for(QStringList::Iterator it = smileys.begin(); it != smileys.end(); ++it ) + newContentText += QString::fromLatin1(" ").arg(*it); + + newContentText += QString::fromLatin1(""); + d->mPrfsEmoticons->icon_theme_preview->setText(newContentText); + emitChanged(); +} + +void AppearanceConfig::slotHighlightChanged() +{ +// bool value = mPrfsChatWindow->highlightEnabled->isChecked(); +// mPrfsChatWindow->foregroundColor->setEnabled ( value ); +// mPrfsChatWindow->backgroundColor->setEnabled ( value ); + slotUpdateChatPreview(); +} + +void AppearanceConfig::slotChangeFont() +{ + slotUpdateChatPreview(); + emitChanged(); +} + +void AppearanceConfig::slotChatStyleSelected() +{ + // Retrieve variant list. + QString stylePath = d->styleItemMap[d->mPrfsChatWindow->styleList->selectedItem()]; + d->currentStyle = ChatWindowStyleManager::self()->getStyleFromPool( stylePath ); + + if(d->currentStyle) + { + d->currentVariantMap = d->currentStyle->getVariants(); + kdDebug(14000) << k_funcinfo << "Loading style: " << d->currentStyle->getStylePath() << endl; + + // Update the variant list based on current style. + d->mPrfsChatWindow->variantList->clear(); + + // Add the no variant item to the list + // TODO: Use default name variant from Info.plist + // TODO: Select default variant from Info.plist + d->mPrfsChatWindow->variantList->insertItem( i18n("(No Variant)") ); + + ChatWindowStyle::StyleVariants::ConstIterator it, itEnd = d->currentVariantMap.constEnd(); + int currentIndex = 0; + for(it = d->currentVariantMap.constBegin(); it != itEnd; ++it) + { + // Insert variant name into the combobox. + d->mPrfsChatWindow->variantList->insertItem( it.key() ); + + if( it.data() == KopetePrefs::prefs()->styleVariant() ) + d->mPrfsChatWindow->variantList->setCurrentItem(currentIndex+1); + + currentIndex++; + } + + // Update the preview + slotUpdateChatPreview(); + // Get the first variant to preview + // Check if the current style has variants. + if( !d->currentVariantMap.empty() ) + d->preview->setStyleVariant(d->currentVariantMap[0]); + + emitChanged(); + } +} + +void AppearanceConfig::slotChatStyleVariantSelected(const QString &variantName) +{ +// kdDebug(14000) << k_funcinfo << variantName << endl; +// kdDebug(14000) << k_funcinfo << d->currentVariantMap[variantName] << endl; + + // Update the preview + d->preview->setStyleVariant(d->currentVariantMap[variantName]); + emitChanged(); +} + +void AppearanceConfig::slotInstallChatStyle() +{ + KURL styleToInstall = KFileDialog::getOpenURL( QString::null, QString::fromUtf8("application/x-zip application/x-tgz application/x-tbz"), this, i18n("Choose Chat Window style to install.") ); + + if( !styleToInstall.isEmpty() ) + { + QString stylePath; + if( KIO::NetAccess::download( styleToInstall, stylePath, this ) ) + { + int styleInstallReturn = 0; + styleInstallReturn = ChatWindowStyleManager::self()->installStyle( stylePath ); + switch(styleInstallReturn) + { + case ChatWindowStyleManager::StyleCannotOpen: + { + KMessageBox::queuedMessageBox( this, KMessageBox::Error, i18n("The specified archive cannot be opened.\nMake sure that the archive is valid ZIP or TAR archive."), i18n("Can't open archive") ); + break; + } + case ChatWindowStyleManager::StyleNoDirectoryValid: + { + KMessageBox::queuedMessageBox( this, KMessageBox::Error, i18n("Could not find a suitable place to install the Chat Window style in user directory."), i18n("Can't find styles directory") ); + break; + } + case ChatWindowStyleManager::StyleNotValid: + KMessageBox::queuedMessageBox( this, KMessageBox::Error, i18n("The specified archive does not contain a valid Chat Window style."), i18n("Invalid Style") ); + break; + case ChatWindowStyleManager::StyleInstallOk: + { + KMessageBox::queuedMessageBox( this, KMessageBox::Information, i18n("The Chat Window style was successfully installed."), i18n("Install successful") ); + break; + } + case ChatWindowStyleManager::StyleUnknow: + default: + { + KMessageBox::queuedMessageBox( this, KMessageBox::Error, i18n("An unknow error occurred while trying to install the Chat Window style."), i18n("Unknow error") ); + break; + } + } + + // removeTempFile check if the file is a temp file, so it's ok for local files. + KIO::NetAccess::removeTempFile( stylePath ); + } + } +} + +void AppearanceConfig::slotDeleteChatStyle() +{ + QString styleName = d->mPrfsChatWindow->styleList->selectedItem()->text(); + QString stylePathToDelete = d->styleItemMap[d->mPrfsChatWindow->styleList->selectedItem()]; + if( ChatWindowStyleManager::self()->removeStyle(stylePathToDelete) ) + { + KMessageBox::queuedMessageBox(this, KMessageBox::Information, i18n("It's the deleted style name", "The style %1 was successfully deleted.").arg(styleName)); + + // Get the first item in the stye List. + QString stylePath = (*d->styleItemMap.begin()); + d->currentStyle = ChatWindowStyleManager::self()->getStyleFromPool(stylePath); + emitChanged(); + } + else + { + KMessageBox::queuedMessageBox(this, KMessageBox::Information, i18n("It's the deleted style name", "An error occured while trying to delete %1 style.").arg(styleName)); + } +} + +void AppearanceConfig::slotGetChatStyles() +{ + // we need this because KNewStuffGeneric's install function isn't clever enough + KopeteStyleNewStuff *kopeteNewStuff = new KopeteStyleNewStuff( "kopete/chatstyle", this ); + KNS::Engine *engine = new KNS::Engine( kopeteNewStuff, "kopete/chatstyle", this ); + KNS::DownloadDialog *downloadDialog = new KNS::DownloadDialog( engine, this ); + downloadDialog->setType( "kopete/chatstyle" ); + // you have to do this by hand when providing your own Engine + KNS::ProviderLoader *provider = new KNS::ProviderLoader( this ); + QObject::connect( provider, SIGNAL( providersLoaded(Provider::List*) ), downloadDialog, SLOT( slotProviders (Provider::List *) ) ); + provider->load( "kopete/chatstyle", "http://download.kde.org/khotnewstuff/kopetestyles12-providers.xml" ); + downloadDialog->exec(); +} + +// Reimplement Kopete::Contact and its abstract method +// This is for style preview. +class FakeContact : public Kopete::Contact +{ +public: + FakeContact (Kopete::Account *account, const QString &id, Kopete::MetaContact *mc ) : Kopete::Contact( account, id, mc ) {} + virtual Kopete::ChatSession *manager(Kopete::Contact::CanCreateFlags /*c*/) { return 0L; } + virtual void slotUserInfo() {}; +}; + +// This is for style preview. +class FakeProtocol : public Kopete::Protocol +{ +public: +FakeProtocol( KInstance *instance, QObject *parent, const char *name ) : Kopete::Protocol(instance, parent, name){} +Kopete::Account* createNewAccount( const QString &/*accountId*/ ){return 0L;} +AddContactPage* createAddContactWidget( QWidget */*parent*/, Kopete::Account */*account*/){return 0L;} +KopeteEditAccountWidget* createEditAccountWidget( Kopete::Account */*account*/, QWidget */*parent */){return 0L;} +}; + +// This is for style preview. +class FakeAccount : public Kopete::Account +{ +public: +FakeAccount(Kopete::Protocol *parent, const QString &accountID, const char *name) : Kopete::Account(parent, accountID, name){} +~FakeAccount() +{} +bool createContact( const QString &/*contactId*/, Kopete::MetaContact */*parentContact*/ ){return true;} +void connect( const Kopete::OnlineStatus& /*initialStatus*/){} +void disconnect(){} +void setOnlineStatus( const Kopete::OnlineStatus& /*status*/ , const QString &/*reason*/){} +}; + +void AppearanceConfig::createPreviewChatSession() +{ + d->previewProtocol = new FakeProtocol( new KInstance(QCString("kopete-preview-chatwindowstyle")), 0L, "kopete-preview-chatwindowstyle"); + d->previewAccount = new FakeAccount(d->previewProtocol, QString("previewaccount"), 0); + + // Create fake meta/contacts + d->myselfMetaContact = new Kopete::MetaContact(); + d->myself = new FakeContact(d->previewAccount, i18n("This is the myself preview contact id", "myself@preview"), d->myselfMetaContact); + d->myself->setNickName(i18n("This is the myself preview contact nickname", "Myself")); + d->jackMetaContact = new Kopete::MetaContact(); + d->jack = new FakeContact(d->previewAccount, i18n("This is the other preview contact id", "jack@preview"), d->jackMetaContact); + d->jack->setNickName(i18n("This is the other preview contact nickname", "Jack")); + d->myselfMetaContact->setDisplayName(i18n("Myself")); + d->myselfMetaContact->setDisplayNameSource(Kopete::MetaContact::SourceCustom); + d->jackMetaContact->setDisplayName(i18n("Jack")); + d->jackMetaContact->setDisplayNameSource(Kopete::MetaContact::SourceCustom); + + Kopete::ContactPtrList contactList; + contactList.append(d->jack); + // Create fakeChatSession + d->previewChatSession = Kopete::ChatSessionManager::self()->create(d->myself, contactList, 0); + d->previewChatSession->setDisplayName("Preview Session"); +} + +void AppearanceConfig::createPreviewMessages() +{ + Kopete::Message msgIn( d->jack,d->myself, i18n( "Hello, this is an incoming message :-)" ), Kopete::Message::Inbound ); + Kopete::Message msgIn2( d->jack, d->myself, i18n( "Hello, this is an incoming consecutive message." ), Kopete::Message::Inbound ); + + Kopete::Message msgOut( d->myself, d->jack, i18n( "Ok, this is an outgoing message" ), Kopete::Message::Outbound ); + Kopete::Message msgOut2( d->myself, d->jack, i18n( "Ok, a outgoing consecutive message." ), Kopete::Message::Outbound ); + + Kopete::Message msgCol( d->jack, d->myself, i18n( "Here is an incoming colored message" ), Kopete::Message::Inbound ); + msgCol.setFg( QColor( "DodgerBlue" ) ); + msgCol.setBg( QColor( "LightSteelBlue" ) ); + Kopete::Message msgInt( d->jack, d->myself, i18n( "This is an internal message" ), Kopete::Message::Internal ); + Kopete::Message msgAct( d->jack, d->myself, i18n( "performed an action" ), Kopete::Message::Inbound, + Kopete::Message::PlainText, QString::null, Kopete::Message::TypeAction ); + Kopete::Message msgHigh( d->jack, d->myself, i18n( "This is a highlighted message" ), Kopete::Message::Inbound ); + msgHigh.setImportance( Kopete::Message::Highlight ); + // This is a UTF-8 string btw. + Kopete::Message msgRightToLeft(d->myself, d->jack, i18n("This special UTF-8 string is to test if the style support Right-to-Left language display.", "הודעות טקסט"), Kopete::Message::Outbound); + Kopete::Message msgExplanation( d->myself, d->jack, i18n( "That message was in a Right-to-Left language, which Kopete also supports." ), Kopete::Message::Outbound ); + Kopete::Message msgBye ( d->myself, d->jack, i18n( "Bye" ), Kopete::Message::Outbound ); + + // Add the messages to ChatMessagePart + d->preview->appendMessage(msgIn); + d->preview->appendMessage(msgIn2); + d->preview->appendMessage(msgOut); + d->preview->appendMessage(msgOut2); + d->preview->appendMessage(msgCol); + d->preview->appendMessage(msgInt); + d->preview->appendMessage(msgAct); + d->preview->appendMessage(msgHigh); + d->preview->appendMessage(msgRightToLeft); + d->preview->appendMessage(msgExplanation); + d->preview->appendMessage(msgBye); +} + +void AppearanceConfig::slotUpdateChatPreview() +{ + if(d->loading || !d->currentStyle) + return; + + // Update the preview + d->preview->setStyle(d->currentStyle); + + emitChanged(); +} + +void AppearanceConfig::emitChanged() +{ + emit changed( true ); +} + +void AppearanceConfig::installEmoticonTheme() +{ + KURL themeURL = KURLRequesterDlg::getURL(QString::null, this, + i18n("Drag or Type Emoticon Theme URL")); + if ( themeURL.isEmpty() ) + return; + + //TODO: support remote theme files! + if ( !themeURL.isLocalFile() ) + { + KMessageBox::queuedMessageBox( this, KMessageBox::Error, i18n("Sorry, emoticon themes must be installed from local files."), + i18n("Could Not Install Emoticon Theme") ); + return; + } + + Kopete::Global::installEmoticonTheme( themeURL.path() ); + updateEmoticonlist(); +} + +void AppearanceConfig::removeSelectedEmoticonTheme() +{ + QListBoxItem *selected = d->mPrfsEmoticons->icon_theme_list->selectedItem(); + if(selected==0) + return; + + QString themeName = selected->text(); + + QString question=i18n("Are you sure you want to remove the " + "%1 emoticon theme?
    " + "
    " + "This will delete the files installed by this theme.
    "). + arg(themeName); + + int res = KMessageBox::warningContinueCancel(this, question, i18n("Confirmation"),KStdGuiItem::del()); + if (res!=KMessageBox::Continue) + return; + + KURL themeUrl(KGlobal::dirs()->findResource("emoticons", themeName+"/")); + KIO::NetAccess::del(themeUrl, this); + + updateEmoticonlist(); +} + +void AppearanceConfig::slotGetEmoticonThemes() +{ + KConfig* config = KGlobal::config(); + config->setGroup( "KNewStuff" ); + config->writeEntry( "ProvidersUrl", + "http://download.kde.org/khotnewstuff/emoticons-providers.xml" ); + config->writeEntry( "StandardResource", "emoticons" ); + config->writeEntry( "Uncompress", "application/x-gzip" ); + config->sync(); + +#if ( KDE_IS_VERSION(3,3,90) ) + KNS::DownloadDialog::open( "emoticons", i18n( "Get New Emoticons") ); +#else + KNS::DownloadDialog::open( i18n( "Get New Emoticons" ) ); +#endif + + updateEmoticonlist(); +} + +void AppearanceConfig::slotEditTooltips() +{ + TooltipEditDialog *dlg = new TooltipEditDialog(this); + connect(dlg, SIGNAL(changed(bool)), this, SIGNAL(changed(bool))); + dlg->exec(); + delete dlg; +} + +#include "appearanceconfig.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/config/appearance/appearanceconfig.h b/kopete/kopete/config/appearance/appearanceconfig.h new file mode 100644 index 00000000..22a23024 --- /dev/null +++ b/kopete/kopete/config/appearance/appearanceconfig.h @@ -0,0 +1,70 @@ +/* + appearanceconfig.h - Kopete Look Feel Config + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef __APPEARANCE_H +#define __APPEARANCE_H + +#include "kcmodule.h" +#include +#include + +/** + * @author Duncan Mac-Vicar P. + * @author Michaël Larouche + */ +class AppearanceConfig : public KCModule +{ + Q_OBJECT + +friend class KopeteStyleNewStuff; + +public: + AppearanceConfig( QWidget *parent, const char *name, const QStringList &args ); + ~AppearanceConfig(); + + virtual void save(); + virtual void load(); + +private slots: + void slotSelectedEmoticonsThemeChanged(); + void slotUpdateChatPreview(); + void slotHighlightChanged(); + void slotChangeFont(); + void slotInstallChatStyle(); + void slotDeleteChatStyle(); + void slotChatStyleSelected(); + void slotChatStyleVariantSelected(const QString &variantName); + void slotEditTooltips(); + void emitChanged(); + void installEmoticonTheme(); + void removeSelectedEmoticonTheme(); + void slotGetEmoticonThemes(); + void slotGetChatStyles(); + void slotLoadChatStyles(); + void updateEmoticonsButton(bool); +private: + void updateEmoticonlist(); + void createPreviewChatSession(); + void createPreviewMessages(); + +private: + class Private; + Private *d; +}; +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/config/appearance/appearanceconfig_chatwindow.ui b/kopete/kopete/config/appearance/appearanceconfig_chatwindow.ui new file mode 100644 index 00000000..129abdd5 --- /dev/null +++ b/kopete/kopete/config/appearance/appearanceconfig_chatwindow.ui @@ -0,0 +1,195 @@ + +AppearanceConfig_ChatWindow + + + AppearanceConfig_ChatWindow + + + + 0 + 0 + 462 + 454 + + + + + 7 + 7 + 0 + 0 + + + + Chat Window Appearance + + + + unnamed + + + + stylesGroupBox + + + + 7 + 7 + 0 + 0 + + + + Styles + + + + unnamed + + + + splitter1 + + + + 7 + 3 + 0 + 0 + + + + Horizontal + + + + styleList + + + + 1 + 7 + 1 + 0 + + + + + + htmlFrame + + + + 3 + 7 + 4 + 0 + + + + StyledPanel + + + Raised + + + + + + layout10 + + + + unnamed + + + + btnGetStyles + + + true + + + &Get New... + + + Get new Chat Window styles over the Internet + + + + + installButton + + + &Install... + + + + + deleteButton + + + &Delete + + + + + + + textLabel1 + + + Style Variant: + + + + + variantList + + + + 7 + 1 + 0 + 0 + + + + + + + + buttonGroup2 + + + Display + + + + unnamed + + + + groupConsecutiveMessages + + + Group consecuti&ve messages + + + + + + + + styleList + installButton + deleteButton + + + + klistbox.h + kpushbutton.h + kpushbutton.h + kpushbutton.h + + diff --git a/kopete/kopete/config/appearance/appearanceconfig_colors.ui b/kopete/kopete/config/appearance/appearanceconfig_colors.ui new file mode 100644 index 00000000..6300b844 --- /dev/null +++ b/kopete/kopete/config/appearance/appearanceconfig_colors.ui @@ -0,0 +1,397 @@ + +AppearanceConfig_Colors + + + AppearanceConfig_Colors + + + + 0 + 0 + 595 + 606 + + + + Colors + + + + unnamed + + + + groupBox3 + + + GroupBoxPanel + + + Sunken + + + Chat Window + + + + unnamed + + + + textLabel3 + + + Base font: + + + + + foregroundColor + + + + + + + + textLabel2 + + + Highlight foreground: + + + + + linkColor + + + + + + + 0 + 85 + 255 + + + + + + backgroundColor + + + + + + + + textLabel1_2 + + + Base font color: + + + + + textColor + + + + + + + + bgColor + + + + + + + 255 + 255 + 255 + + + + + + textLabel1 + + + Highlight background: + + + + + textLabel1_3 + + + Link color: + + + + + textLabel2_2 + + + Background color: + + + + + fontFace + + + + + + + groupBox4 + + + Formatting Overrides + + + + unnamed + + + + mBgOverride + + + Do not show user specified &background color + + + + + mFgOverride + + + Do not show user specified &foreground color + + + + + mRtfOverride + + + Do not show user specified &rich text + + + + + + + groupBox3_2 + + + Contact List + + + + unnamed + + + + mUseCustomFonts + + + Use custom fonts for contact list items + + + + + layout5 + + + + unnamed + + + + spacer13 + + + Horizontal + + + Fixed + + + + 20 + 0 + + + + + + layout4 + + + + unnamed + + + + mSmallFontLabel + + + false + + + Small font: + + + + + mNormalFont + + + false + + + + + mNormalFontLabel + + + false + + + Normal font: + + + + + mSmallFont + + + false + + + + + + + + + layout6 + + + + unnamed + + + + mGroupNameColor + + + + + + + + idleContactColor + + + false + + + + + + + + mGreyIdleMetaContacts + + + Recolor contacts marked as idle: + + + + + textLabel1_4 + + + Group name color: + + + + + + + + + spacer2 + + + Vertical + + + Expanding + + + + 20 + 80 + + + + + + + + + + mUseCustomFonts + toggled(bool) + mNormalFontLabel + setEnabled(bool) + + + mUseCustomFonts + toggled(bool) + mNormalFont + setEnabled(bool) + + + mUseCustomFonts + toggled(bool) + mSmallFontLabel + setEnabled(bool) + + + mUseCustomFonts + toggled(bool) + mSmallFont + setEnabled(bool) + + + mGreyIdleMetaContacts + toggled(bool) + idleContactColor + setEnabled(bool) + + + + bgColor + textColor + linkColor + + + + kcolorbutton.h + kcolorbutton.h + kcolorbutton.h + kcolorbutton.h + kcolorbutton.h + kfontrequester.h + kfontrequester.h + kfontrequester.h + kcolorbutton.h + kcolorbutton.h + + diff --git a/kopete/kopete/config/appearance/appearanceconfig_contactlist.ui b/kopete/kopete/config/appearance/appearanceconfig_contactlist.ui new file mode 100644 index 00000000..4c9c7934 --- /dev/null +++ b/kopete/kopete/config/appearance/appearanceconfig_contactlist.ui @@ -0,0 +1,349 @@ + +AppearanceConfig_ContactList + + + AppearanceConfig_ContactList + + + + 0 + 0 + 707 + 445 + + + + Contact List Appearance + + + + unnamed + + + + groupBox1 + + + Layout + + + + unnamed + + + + mSortByGroup + + + Arrange metacontacts by &group + + + + + mTreeContactList + + + Show tree &branch lines + + + + + layout3 + + + + unnamed + + + + spacer3_2 + + + Horizontal + + + Fixed + + + + 16 + 16 + + + + + + mIndentContacts + + + In&dent contacts + + + + + + + + + groupBox10 + + + Contact Display Mode + + + + unnamed + + + + mDisplayMode + + + List Style + + + + unnamed + + + + radioButton8 + + + &Classic, left-aligned status icons + + + true + + + + + radioButton9 + + + &Right-aligned status icons + + + + + radioButton10 + + + Detailed &view + + + + + + + mIconMode + + + Use contact photos when available + + + + + + + groupBox3 + + + Contact List Auto-Hide + + + When enabled, the contact list will automatically be hidden a fixed amount of time after the mouse cursor leaves the window. You can set the amount of time in the 'Time until autohide' box below. + + + + unnamed + + + + mAutoHide + + + A&uto-hide contact list + + + + + layout2 + + + + unnamed + + + + spacer4 + + + Horizontal + + + Fixed + + + + 16 + 16 + + + + + + mAutoHideTimeout + + + true + + + Sec + + + 300 + + + 3 + + + 30 + + + The timeout value for both contact list and scrollbar auto-hiding. + + + + + textLabel1 + + + true + + + after the cursor left the window + + + + + spacer5 + + + Horizontal + + + Expanding + + + + 81 + 20 + + + + + + + + + + groupBox2 + + + Contact List Animations + + + + unnamed + + + + mAnimateChanges + + + &Animate changes to contact list items + + + + + mFadeVisibility + + + Fade in / out contacts as the&y appear / disappear + + + + + mFoldVisibility + + + Fo&ld in / out contacts as they appear / disappear + + + + + + + layout8 + + + + unnamed + + + + mEditTooltips + + + Change &Tooltip Contents... + + + + + spacer3 + + + Horizontal + + + Expanding + + + + 16 + 16 + + + + + + + + spacer15 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + + + mTreeContactList + toggled(bool) + mIndentContacts + setDisabled(bool) + + + + diff --git a/kopete/kopete/config/appearance/appearanceconfig_emoticons.ui b/kopete/kopete/config/appearance/appearanceconfig_emoticons.ui new file mode 100644 index 00000000..8649e4c2 --- /dev/null +++ b/kopete/kopete/config/appearance/appearanceconfig_emoticons.ui @@ -0,0 +1,214 @@ + +AppearanceConfig_Emoticons + + + AppearanceConfig_Emoticons + + + + 0 + 0 + 541 + 395 + + + + + unnamed + + + + chkUseEmoticons + + + &Use emoticons + + + true + + + If this is checked, the text representation of emoticons in messages will be replaced by an image + + + + + chkRequireSpaces + + + &Require separators (spaces) around emoticons + + + true + + + If this is checked, only emoticons that are separated from the text by spaces will be shown as images. + + + + + textLabel1 + + + NoFocus + + + Select emoticon theme: + + + + + icon_theme_list + + + + + lblPreview + + + Preview: + + + + + icon_theme_preview + + + + 7 + 0 + 0 + 0 + + + + + 32767 + 64 + + + + true + + + + + layout2 + + + + unnamed + + + + btnGetThemes + + + &Get New Themes... + + + Download emoticon theme from the Internet + + + + + btnInstallTheme + + + &Install Theme File... + + + + + btnRemoveTheme + + + + 1 + 0 + 0 + 0 + + + + Remove Theme + + + + + spacer1 + + + Horizontal + + + Expanding + + + + 31 + 20 + + + + + + + + + + + + chkUseEmoticons + toggled(bool) + chkRequireSpaces + setEnabled(bool) + + + chkUseEmoticons + toggled(bool) + lblPreview + setEnabled(bool) + + + chkUseEmoticons + toggled(bool) + icon_theme_list + setEnabled(bool) + + + chkUseEmoticons + toggled(bool) + lblPreview + setEnabled(bool) + + + chkUseEmoticons + toggled(bool) + textLabel1 + setEnabled(bool) + + + chkUseEmoticons + toggled(bool) + btnGetThemes + setEnabled(bool) + + + chkUseEmoticons + toggled(bool) + btnInstallTheme + setEnabled(bool) + + + chkUseEmoticons + toggled(bool) + btnRemoveTheme + setEnabled(bool) + + + + + klistbox.h + ktextedit.h + + diff --git a/kopete/kopete/config/appearance/kopete_appearanceconfig.desktop b/kopete/kopete/config/appearance/kopete_appearanceconfig.desktop new file mode 100644 index 00000000..e9b941b7 --- /dev/null +++ b/kopete/kopete/config/appearance/kopete_appearanceconfig.desktop @@ -0,0 +1,130 @@ +[Desktop Entry] +Icon=looknfeel +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_appearanceconfig +X-KDE-FactoryName=KopeteAppearanceConfigFactory +X-KDE-ParentApp=kopete +X-KDE-ParentComponents=kopete + +Name=Appearance +Name[ar]=المظهر +Name[be]=Вонкавы выглÑд +Name[bg]=Външен вид +Name[bn]=চেহারা +Name[br]=Neuziadur +Name[bs]=Izgled +Name[ca]=Aparença +Name[cs]=Vzhled +Name[cy]=Golwg +Name[da]=Udseende +Name[de]=Erscheinungsbild +Name[el]=Εμφάνιση +Name[eo]=Apero +Name[es]=Apariencia +Name[et]=Välimus +Name[eu]=Itxura +Name[fa]=ظاهر +Name[fi]=Ulkonäkö +Name[fr]=Apparence +Name[ga]=Cuma +Name[gl]=Apariencia +Name[he]=מר××” +Name[hi]=शकà¥à¤²-सूरत +Name[hr]=Izgled +Name[hu]=Megjelenés +Name[is]=Útlit +Name[it]=Aspetto +Name[ja]=外観 +Name[ka]=იერსáƒáƒ®áƒ” +Name[kk]=Сыртқы көрініÑÑ– +Name[km]=រូបរាង +Name[lt]=IÅ¡vaizda +Name[mk]=Изглед +Name[nb]=Utseende +Name[nds]=Utsehn +Name[ne]=दृशà¥à¤¯ +Name[nl]=Uiterlijk +Name[nn]=UtsjÃ¥nad +Name[pa]=ਦਿੱਖ +Name[pl]=WyglÄ…d +Name[pt]=Aparência +Name[pt_BR]=Aparência +Name[ro]=Aspect +Name[ru]=Внешний вид +Name[rw]=Imigaragarire +Name[se]=Fárda +Name[sk]=Vzhľad +Name[sl]=Videz +Name[sr]=Изглед +Name[sr@Latn]=Izgled +Name[sv]=Utseende +Name[tg]=Ðамуди зоҳирӣ +Name[tr]=Görünüm +Name[uk]=ВиглÑд +Name[uz]=KoÊ»rinishi +Name[uz@cyrillic]=Кўриниши +Name[wa]=Rivnance +Name[zh_CN]=外观 +Name[zh_HK]=外觀 +Name[zh_TW]=外觀 +Comment=Here You Can Alter Kopete's Look And Feel +Comment[ar]=يمكنك تغيير مظهر Kopete +Comment[be]=Вонкавы выглÑд Kopete +Comment[bg]=ÐаÑтройване Ð²ÑŠÐ½ÑˆÐ½Ð¸Ñ Ð²Ð¸Ð´ на програмата +Comment[bn]=আপনি à¦à¦–ানে কপেটের চেহারা ও কারà§à¦¯à¦•à¦¾à¦°à¦¿à¦¤à¦¾ পরিবরà§à¦¤à¦¨ করতে পারেন +Comment[bs]=Ovdje možete izmijeniti izgled Kopete-a +Comment[ca]=Aquí podreu modificar l'aparença i comportament de Kopete +Comment[cs]=Zde je možné pÅ™izpůsobit si vzhled a chování Kopete +Comment[cy]=Yma Gallwch Addasu Golwg a Theimlad Kopete +Comment[da]=Her kan du ændre Kopete's udseende +Comment[de]=Hier können Sie Kopetes Erscheinungsbild verändern +Comment[el]=Εδώ μποÏείτε να Ï„Ïοποποιήσετε την όψη και αίσθηση του Kopete +Comment[es]=Aquí­ puede modificar el aspecto de Kopete +Comment[et]=Siin saab muuta Kopete väljanägemist +Comment[eu]=Hemen Kopete-ren itxura eta izaera alda dezakezu +Comment[fa]=اینجا می‌توانید ویژگیهای ظاهری Kopete را تغییر دهید +Comment[fi]=Täältä voit muuttaa Kopeten ulkonäköä ja tuntumaa +Comment[fr]=Vous pouvez modifier l'apparence et l'ergonomie de Kopete ici +Comment[gl]=Aquí podes modificar o aspecto de Kopete +Comment[he]=×›×ן תוכל לשנות ×ת המר××” של Kopete +Comment[hi]=यहाठआप के-ऑपà¥à¤Ÿà¥€ के रूप और विनà¥à¤¯à¤¾à¤¸ को बदल सकते हैं +Comment[hr]=Ovde možete promijeniti Kopeteov izgled i naÄin rada +Comment[hu]=Itt lehet megváltoztatni a Kopete grafikai megjelenését +Comment[is]=Hér er hægt að breyta útliti og viðmóti Kopete +Comment[it]=Qui puoi modificare l'aspetto di Kopete +Comment[ja]=ã“ã“㧠Kopete ã®å¤–観をカスタマイズã—ã¾ã™ +Comment[ka]=áƒáƒ¥ თქვენ შეგიძლიáƒáƒ— Kopeteს იერსáƒáƒ®áƒ˜áƒ¡ კáƒáƒœáƒ¤áƒ˜áƒ’ურáƒáƒªáƒ˜áƒ +Comment[kk]=Мұнда Kopete-Ñ‚Ñ–Ò£ Ñырқы көрініÑін өзгерте алаÑыз +Comment[km]=ទី​នáŸáŸ‡ អ្នក​អាច​ប្ដូរ​រូបរាង និង​មុážáž„ារ​របស់ Kopete​ +Comment[lt]=ÄŒia galite keisti Kopete iÅ¡vaizdÄ… ir pojÅ«tį +Comment[mk]=Тука можете да го менувате изгледот на Kopete +Comment[nb]=Endre utseendet pÃ¥ Kopete +Comment[nds]=Hier kannst Du dat Utsehn vun Kopete ännern +Comment[ne]=यहाठतपाईठकोपेटको हेराइ र महसà¥à¤¸ अलà¥à¤Ÿà¤° गरà¥à¤¨ सकà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤› +Comment[nl]=Hier kunt u uw het uiterlijk van Kopete aanpassen +Comment[nn]=Endra utsjÃ¥naden til Kopete +Comment[pl]=Tutaj można zmienić wyglÄ…d i zachowanie Kopete +Comment[pt]=Aqui Você Pode Alterar a Aparência e Comportamento do Kopete +Comment[pt_BR]=Aqui você pode alterar a aparência do Kopete +Comment[ro]=Aici puteÅ£i modifica aspectul Kopete +Comment[ru]=ÐаÑтройка внешнего вида Kopete +Comment[se]=Dáppe sáhtát rievdadit Kopete:a fárdda +Comment[sk]=Tu môžete upraviÅ¥ vzhľad Kopete +Comment[sl]=Tukaj lahko spreminjate izgled in obÄutek za Kopete +Comment[sr]=Овде можете променити Kopete-ов изглед и оÑећај +Comment[sr@Latn]=Ovde možete promeniti Kopete-ov izgled i osećaj +Comment[sv]=Här kan du ändra Kopetes utseende och känsla +Comment[tg]=Дар Ин Ҷо Шумо Ðамуди Зоҳирии Kopete-и Худро Тағир Дода Метавонед +Comment[tr]=Kopete'nin Görünüm ve Dokusunu Buradan DeÄŸiÅŸtirebilirsiniz +Comment[uk]=Тут можна наладнати зовнішній виглÑд Kopete +Comment[uz]=Bu yerda Kopete koÊ»rinishini moslash mumkin +Comment[uz@cyrillic]=Бу ерда Kopete кўринишини моÑлаш мумкин +Comment[zh_CN]=您å¯åœ¨æ­¤æ›´æ”¹ Kopete 的观感 +Comment[zh_HK]=在此您å¯æ”¹å‹• Kopete 的外觀 +Comment[zh_TW]=您å¯ä»¥åœ¨æ­¤æ”¹è®Š Kopete 的外觀與感覺 +DocPath=kopete/configure-dialog.html#configuring-appearance + + diff --git a/kopete/kopete/config/appearance/tooltipeditdialog.cpp b/kopete/kopete/config/appearance/tooltipeditdialog.cpp new file mode 100644 index 00000000..c8ed8a5d --- /dev/null +++ b/kopete/kopete/config/appearance/tooltipeditdialog.cpp @@ -0,0 +1,226 @@ +/* + tooltipeditdialog.cpp - Kopete Tooltip Editor + + Copyright (c) 2004 by Stefan Gehn + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "tooltipeditdialog.h" +#include "tooltipeditwidget.h" + +#include "kopetecontactproperty.h" +#include "kopeteglobal.h" +#include "kopeteprefs.h" + +#include +#include +#include +#include + +#include +#include +#include + +class TooltipItem : public KListViewItem +{ + public: + TooltipItem(KListView *parent, const QString& label, const QString& propertyName) + : KListViewItem(parent, label), + mPropName(propertyName) + { + } + + TooltipItem(KListView *parent, QListViewItem *item, const QString& label, const QString& propertyName) + : KListViewItem(parent, item, label), + mPropName(propertyName) + { + } + + QString propertyName() const { return mPropName; } + private: + QString mPropName; +}; + + + +TooltipEditDialog::TooltipEditDialog(QWidget *parent, const char* name) + : KDialogBase(parent, name, true, i18n("Tooltip Editor"), Ok|Cancel, Ok, true) +{ + mMainWidget = new TooltipEditWidget(this, "TooltipEditDialog::mMainWidget"); + setMainWidget(mMainWidget); + mMainWidget->lstUsedItems->header()->hide(); + mMainWidget->lstUnusedItems->header()->hide(); + mMainWidget->lstUsedItems->setSorting( -1 ); + mMainWidget->lstUnusedItems->setSorting( 0 ); + + const Kopete::ContactPropertyTmpl::Map propmap( + Kopete::Global::Properties::self()->templateMap()); + QStringList usedKeys = KopetePrefs::prefs()->toolTipContents(); + + connect(mMainWidget->lstUnusedItems, SIGNAL(doubleClicked ( QListViewItem *, const QPoint &, int )), this, SLOT(slotAddButton())); + connect(mMainWidget->lstUsedItems, SIGNAL(doubleClicked ( QListViewItem *, const QPoint &, int )), this, SLOT(slotRemoveButton())); + + // first fill the "used" list + QStringList::Iterator usedIt=usedKeys.end(); + do + { + usedIt--; + // only add if that property key is really known + if(propmap.contains(*usedIt) && !propmap[*usedIt].isPrivate()) + { + new TooltipItem(mMainWidget->lstUsedItems, + propmap[*usedIt].label(), *usedIt); + } + } while(usedIt != usedKeys.begin()); + + // then iterate over all known properties and insert the remaining ones + // into the "unused" list + Kopete::ContactPropertyTmpl::Map::ConstIterator it; + for(it = propmap.begin(); it != propmap.end(); ++it) + { + if((usedKeys.contains(it.key())==0) && (!it.data().isPrivate())) + new TooltipItem(mMainWidget->lstUnusedItems, it.data().label(), it.key()); + } + + connect(mMainWidget->lstUnusedItems, SIGNAL(selectionChanged(QListViewItem *)), + this, SLOT(slotUnusedSelected(QListViewItem *))); + connect(mMainWidget->lstUsedItems, SIGNAL(selectionChanged(QListViewItem *)), + this, SLOT(slotUsedSelected(QListViewItem *))); + + QIconSet iconSet; + iconSet = SmallIconSet("up"); + mMainWidget->tbUp->setIconSet(iconSet); + mMainWidget->tbUp->setEnabled(false); + mMainWidget->tbUp->setAutoRepeat(true); + connect(mMainWidget->tbUp, SIGNAL(clicked()), SLOT(slotUpButton())); + + iconSet = SmallIconSet("down"); + mMainWidget->tbDown->setIconSet(iconSet); + mMainWidget->tbDown->setEnabled(false); + mMainWidget->tbDown->setAutoRepeat(true); + connect(mMainWidget->tbDown, SIGNAL(clicked()), SLOT(slotDownButton())); + + iconSet = QApplication::reverseLayout() ? SmallIconSet("back") : SmallIconSet("forward"); + mMainWidget->tbAdd->setIconSet(iconSet); + mMainWidget->tbAdd->setEnabled(false); + connect(mMainWidget->tbAdd, SIGNAL(clicked()), SLOT(slotAddButton())); + + iconSet = QApplication::reverseLayout() ? SmallIconSet("forward") : SmallIconSet("back"); + mMainWidget->tbRemove->setIconSet(iconSet); + mMainWidget->tbRemove->setEnabled(false); + connect(mMainWidget->tbRemove, SIGNAL(clicked()), SLOT(slotRemoveButton())); + + connect(this, SIGNAL(okClicked()), this, SLOT(slotOkClicked())); + + resize(QSize(450, 450)); +} + +void TooltipEditDialog::slotOkClicked() +{ + QStringList oldList = KopetePrefs::prefs()->toolTipContents(); + QStringList newList; + QListViewItemIterator it(mMainWidget->lstUsedItems); + QString keyname; + + while(it.current()) + { + keyname = static_cast(it.current())->propertyName(); + newList += keyname; + kdDebug(14000) << k_funcinfo << + "Adding key '" << keyname << "' to tooltip list" << endl; + ++it; + } + + if(oldList != newList) + { + KopetePrefs::prefs()->setToolTipContents(newList); + emit changed(true); + kdDebug(14000) << k_funcinfo << "tooltip fields changed, emitting changed()" << endl; + } +} + + +void TooltipEditDialog::slotUnusedSelected(QListViewItem *item) +{ + //mMainWidget->tbRemove->setEnabled(false); + mMainWidget->tbAdd->setEnabled(item!=0); +} + +void TooltipEditDialog::slotUsedSelected(QListViewItem *item) +{ + mMainWidget->tbRemove->setEnabled(item!=0); + //mMainWidget->tbAdd->setEnabled(false); + if (item) + { + mMainWidget->tbUp->setEnabled(item->itemAbove() != 0); + mMainWidget->tbDown->setEnabled(item->itemBelow() != 0); + } + else + { + mMainWidget->tbUp->setEnabled(false); + mMainWidget->tbDown->setEnabled(false); + } +} + +void TooltipEditDialog::slotUpButton() +{ + QListViewItem *item = mMainWidget->lstUsedItems->currentItem(); + QListViewItem *prev = item->itemAbove(); + if(prev == 0) // we are first item already + return; + + prev->moveItem(item); + slotUsedSelected(item); +} + +void TooltipEditDialog::slotDownButton() +{ + QListViewItem *item = mMainWidget->lstUsedItems->currentItem(); + QListViewItem *next = item->itemBelow(); + if(next == 0) // we are last item already + return; + + item->moveItem(next); + slotUsedSelected(item); +} + +void TooltipEditDialog::slotAddButton() +{ + TooltipItem *item = static_cast(mMainWidget->lstUnusedItems->currentItem()); + if(!item) + return; + //kdDebug(14000) << k_funcinfo << endl; + + // build a new one in the "used" list + new TooltipItem(mMainWidget->lstUsedItems, item->text(0), item->propertyName()); + + // remove the old one from "unused" list + mMainWidget->lstUnusedItems->takeItem(item); + delete item; +} + +void TooltipEditDialog::slotRemoveButton() +{ + TooltipItem *item = static_cast(mMainWidget->lstUsedItems->currentItem()); + if(!item) + return; + //kdDebug(14000) << k_funcinfo << endl; + + // build a new one in the "unused" list + new TooltipItem(mMainWidget->lstUnusedItems, item->text(0), item->propertyName()); + + // remove the old one from "used" list + mMainWidget->lstUsedItems->takeItem(item); + delete item; +} + +#include "tooltipeditdialog.moc" diff --git a/kopete/kopete/config/appearance/tooltipeditdialog.h b/kopete/kopete/config/appearance/tooltipeditdialog.h new file mode 100644 index 00000000..92d75aa9 --- /dev/null +++ b/kopete/kopete/config/appearance/tooltipeditdialog.h @@ -0,0 +1,49 @@ +/* + tooltipeditdialog.cpp - Kopete Tooltip Editor + + Copyright (c) 2004 by Stefan Gehn + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef TOOLTIPEDITDIALOG_H +#define TOOLTIPEDITDIALOG_H + +#include +#include +#include +class TooltipEditWidget; + +class TooltipEditDialog : public KDialogBase +{ + Q_OBJECT + + public: + TooltipEditDialog(QWidget *parent=0, const char* name="ToolTipEditDialog"); + + private slots: + void slotUnusedSelected(QListViewItem *); + void slotUsedSelected(QListViewItem *); + void slotUpButton(); + void slotDownButton(); + void slotAddButton(); + void slotRemoveButton(); + void slotOkClicked(); + + signals: + void changed(bool); + + private: + TooltipEditWidget *mMainWidget; +}; + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/config/appearance/tooltipeditwidget.ui b/kopete/kopete/config/appearance/tooltipeditwidget.ui new file mode 100644 index 00000000..f00b8f26 --- /dev/null +++ b/kopete/kopete/config/appearance/tooltipeditwidget.ui @@ -0,0 +1,215 @@ + +TooltipEditWidget + + + TooltipEditWidget + + + + 0 + 0 + 489 + 418 + + + + + 7 + 7 + 0 + 0 + + + + + unnamed + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + textLabel1 + + + Using the arrow buttons, put on the right the items you want to see in the contact tooltips. You can then sort them. + + + WordBreak|AlignVCenter + + + + + textLabel2 + + + <b>Here you can customize the contact tooltips</b> + + + + + layout5 + + + + unnamed + + + + + + + + false + + + false + + + + lstUnusedItems + + + true + + + This list contains elements which are currently <b>not present</b> in the contact tooltip. + + + + + layout4 + + + + unnamed + + + + spacer1 + + + Vertical + + + Expanding + + + + 20 + 60 + + + + + + layout1 + + + + unnamed + + + + tbDown + + + v + + + Use this arrow to reorder the items in the list. + + + + + tbUp + + + ^ + + + + + tbRemove + + + < + + + + + tbAdd + + + > + + + Use this arrows to add or remove items to your contact tooltips. + + + + + + + spacer2 + + + Vertical + + + Expanding + + + + 20 + 30 + + + + + + + + + + + + false + + + false + + + + lstUsedItems + + + true + + + This list contains elements which are currently <b>present</b> in the contact tooltips. + + + + + + + + + + + klistview.h + klistview.h + + diff --git a/kopete/kopete/config/avdevice/Makefile.am b/kopete/kopete/config/avdevice/Makefile.am new file mode 100644 index 00000000..f1c9303a --- /dev/null +++ b/kopete/kopete/config/avdevice/Makefile.am @@ -0,0 +1,24 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) \ + -I$(top_srcdir)/kopete/libkopete/avdevice \ + -I$(top_srcdir)/kopete/libkopete/private $(KOPETE_COMPAT_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kcm_kopete_avdeviceconfig.la + +kcm_kopete_avdeviceconfig_la_SOURCES = avdeviceconfig.cpp \ + avdeviceconfig_videoconfig.ui + +kcm_kopete_avdeviceconfig_la_LDFLAGS = -no-undefined -module $(KDE_PLUGIN) $(all_libraries) + +kcm_kopete_avdeviceconfig_la_LIBADD = ../../../libkopete/libkopete.la \ + ../../../libkopete/avdevice/libkopete_videodevice.la \ + $(LIB_KOPETECOMPAT) $(LIB_KUTILS) + +service_DATA = kopete_avdeviceconfig.desktop +servicedir = $(kde_servicesdir) + +kopeteicondir = $(kde_datadir)/kopete/icons +kopeteicon_ICON = AUTO + +# vim: set noet: +noinst_HEADERS = avdeviceconfig_videoconfig.h diff --git a/kopete/kopete/config/avdevice/avdeviceconfig.cpp b/kopete/kopete/config/avdevice/avdeviceconfig.cpp new file mode 100644 index 00000000..a2c474e0 --- /dev/null +++ b/kopete/kopete/config/avdevice/avdeviceconfig.cpp @@ -0,0 +1,229 @@ +/* + avdeviceconfig.cpp - Kopete Video Device Configuration Panel + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "avdeviceconfig.h" +#include "avdeviceconfig_videoconfig.h" +#include "videodevice.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +//#include "videodevice.h" +typedef KGenericFactory KopeteAVDeviceConfigFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_avdeviceconfig, KopeteAVDeviceConfigFactory( "kcm_kopete_avdeviceconfig" ) ) + +AVDeviceConfig::AVDeviceConfig(QWidget *parent, const char * name , const QStringList &args) + : KCModule( KopeteAVDeviceConfigFactory::instance(), parent, args ) +{ + kdDebug() << "kopete:config (avdevice): KopeteAVDeviceConfigFactory::instance() called. " << endl; + (new QVBoxLayout(this))->setAutoAdd(true); + mAVDeviceTabCtl = new QTabWidget(this, "mAVDeviceTabCtl"); + +// "Video" TAB ============================================================ + mPrfsVideoDevice = new AVDeviceConfig_VideoDevice(mAVDeviceTabCtl); + connect(mPrfsVideoDevice->mDeviceKComboBox, SIGNAL(activated(int)), this, SLOT(slotDeviceKComboBoxChanged(int))); + connect(mPrfsVideoDevice->mInputKComboBox, SIGNAL(activated(int)), this, SLOT(slotInputKComboBoxChanged(int))); + connect(mPrfsVideoDevice->mStandardKComboBox, SIGNAL(activated(int)), this, SLOT(slotStandardKComboBoxChanged(int))); + connect(mPrfsVideoDevice->mBrightnessSlider, SIGNAL(valueChanged(int)), this, SLOT(slotBrightnessSliderChanged(int))); + connect(mPrfsVideoDevice->mContrastSlider, SIGNAL(valueChanged(int)), this, SLOT(slotContrastSliderChanged(int))); + connect(mPrfsVideoDevice->mSaturationSlider, SIGNAL(valueChanged(int)), this, SLOT(slotSaturationSliderChanged(int))); + connect(mPrfsVideoDevice->mWhitenessSlider, SIGNAL(valueChanged(int)), this, SLOT(slotWhitenessSliderChanged(int))); + connect(mPrfsVideoDevice->mHueSlider, SIGNAL(valueChanged(int)), this, SLOT(slotHueSliderChanged(int))); + connect(mPrfsVideoDevice->mImageAutoBrightnessContrast, SIGNAL(toggled(bool)), this, SLOT(slotImageAutoBrightnessContrastChanged(bool))); + connect(mPrfsVideoDevice->mImageAutoColorCorrection, SIGNAL(toggled(bool)), this, SLOT(slotImageAutoColorCorrectionChanged(bool))); + connect(mPrfsVideoDevice->mImageAsMirror, SIGNAL(toggled(bool)), this, SLOT(slotImageAsMirrorChanged(bool))); + + // why is this here? + // mPrfsVideoDevice->mVideoImageLabel->setPixmap(qpixmap); + mAVDeviceTabCtl->addTab(mPrfsVideoDevice, i18n("&Video")); + mVideoDevicePool = Kopete::AV::VideoDevicePool::self(); + mVideoDevicePool->open(); + mVideoDevicePool->setSize(320, 240); + + mVideoDevicePool->fillDeviceKComboBox(mPrfsVideoDevice->mDeviceKComboBox); + mVideoDevicePool->fillInputKComboBox(mPrfsVideoDevice->mInputKComboBox); + mVideoDevicePool->fillStandardKComboBox(mPrfsVideoDevice->mStandardKComboBox); + setVideoInputParameters(); + + mVideoDevicePool->startCapturing(); + mVideoDevicePool->getFrame(); + mVideoDevicePool->getImage(&qimage); + if (qpixmap.convertFromImage(qimage,0) == true) + mPrfsVideoDevice->mVideoImageLabel->setPixmap(qpixmap); + connect(&qtimer, SIGNAL(timeout()), this, SLOT(slotUpdateImage()) ); + qtimer.start(0,FALSE); +} + + +AVDeviceConfig::~AVDeviceConfig() +{ + mVideoDevicePool->close(); +} + + + + +/*! + \fn VideoDeviceConfig::save() + */ +void AVDeviceConfig::save() +{ + /// @todo implement me + kdDebug() << "kopete:config (avdevice): save() called. " << endl; + mVideoDevicePool->saveConfig(); +} + + +/*! + \fn VideoDeviceConfig::load() + */ +void AVDeviceConfig::load() +{ + /// @todo implement me +} + +void AVDeviceConfig::slotSettingsChanged(bool){ + emit changed(true); +} + +void AVDeviceConfig::slotValueChanged(int){ + emit changed( true ); +} + +void AVDeviceConfig::setVideoInputParameters() +{ + if(mVideoDevicePool->size()) + { + mPrfsVideoDevice->mBrightnessSlider->setValue((int)(mVideoDevicePool->getBrightness()*65535)); + mPrfsVideoDevice->mContrastSlider->setValue((int)(mVideoDevicePool->getContrast()*65535)); + mPrfsVideoDevice->mSaturationSlider->setValue((int)(mVideoDevicePool->getSaturation()*65535)); + mPrfsVideoDevice->mWhitenessSlider->setValue((int)(mVideoDevicePool->getWhiteness()*65535)); + mPrfsVideoDevice->mHueSlider->setValue((int)(mVideoDevicePool->getHue()*65535)); + mPrfsVideoDevice->mImageAutoBrightnessContrast->setChecked(mVideoDevicePool->getAutoBrightnessContrast()); + mPrfsVideoDevice->mImageAutoColorCorrection->setChecked(mVideoDevicePool->getAutoColorCorrection()); + mPrfsVideoDevice->mImageAsMirror->setChecked(mVideoDevicePool->getImageAsMirror()); + } +} + +void AVDeviceConfig::slotDeviceKComboBoxChanged(int){ + kdDebug() << "kopete:config (avdevice): slotDeviceKComboBoxChanged(int) called. " << endl; + unsigned int newdevice = mPrfsVideoDevice->mDeviceKComboBox->currentItem(); + kdDebug() << "kopete:config (avdevice): slotDeviceKComboBoxChanged(int) Current device: " << mVideoDevicePool->currentDevice() << "New device: " << newdevice << endl; + if ((newdevice < mVideoDevicePool->m_videodevice.size())&&(newdevice!=mVideoDevicePool->currentDevice())) + { + kdDebug() << "kopete:config (avdevice): slotDeviceKComboBoxChanged(int) should change device. " << endl; + mVideoDevicePool->open(newdevice); + mVideoDevicePool->setSize(320, 240); + mVideoDevicePool->fillInputKComboBox(mPrfsVideoDevice->mInputKComboBox); + mVideoDevicePool->startCapturing(); + setVideoInputParameters(); + kdDebug() << "kopete:config (avdevice): slotDeviceKComboBoxChanged(int) called. " << endl; + emit changed( true ); + } + +} + +void AVDeviceConfig::slotInputKComboBoxChanged(int){ + unsigned int newinput = mPrfsVideoDevice->mInputKComboBox->currentItem(); + if((newinput < mVideoDevicePool->inputs()) && ( newinput !=mVideoDevicePool->currentInput())) + { + mVideoDevicePool->selectInput(mPrfsVideoDevice->mInputKComboBox->currentItem()); + mVideoDevicePool->fillStandardKComboBox(mPrfsVideoDevice->mStandardKComboBox); + setVideoInputParameters(); + emit changed( true ); + } +} + +// ATTENTION: The 65535.0 value must be used instead of 65535 because the trailing ".0" converts the resulting value to floating point number. +// Otherwise the resulting division operation would return 0 or 1 exclusively. + +void AVDeviceConfig::slotStandardKComboBoxChanged(int){ + emit changed( true ); +} + +void AVDeviceConfig::slotBrightnessSliderChanged(int){ + kdDebug() << "kopete:config (avdevice): slotBrightnessSliderChanged(int) called. " << mPrfsVideoDevice->mBrightnessSlider->value() / 65535.0 << endl; + mVideoDevicePool->setBrightness( mPrfsVideoDevice->mBrightnessSlider->value() / 65535.0 ); + emit changed( true ); +} + +void AVDeviceConfig::slotContrastSliderChanged(int){ + kdDebug() << "kopete:config (avdevice): slotContrastSliderChanged(int) called. " << mPrfsVideoDevice->mContrastSlider->value() / 65535.0 << endl; + mVideoDevicePool->setContrast( mPrfsVideoDevice->mContrastSlider->value() / 65535.0 ); + emit changed( true ); +} + +void AVDeviceConfig::slotSaturationSliderChanged(int){ + kdDebug() << "kopete:config (avdevice): slotSaturationSliderChanged(int) called. " << mPrfsVideoDevice->mSaturationSlider->value() / 65535.0 << endl; + mVideoDevicePool->setSaturation( mPrfsVideoDevice->mSaturationSlider->value() / 65535.0); + emit changed( true ); +} + +void AVDeviceConfig::slotWhitenessSliderChanged(int){ + kdDebug() << "kopete:config (avdevice): slotWhitenessSliderChanged(int) called. " << mPrfsVideoDevice->mWhitenessSlider->value() / 65535.0 << endl; + mVideoDevicePool->setWhiteness( mPrfsVideoDevice->mWhitenessSlider->value() / 65535.0); + emit changed( true ); +} + +void AVDeviceConfig::slotHueSliderChanged(int){ + kdDebug() << "kopete:config (avdevice): slotHueSliderChanged(int) called. " << mPrfsVideoDevice->mHueSlider->value() / 65535.0 << endl; + mVideoDevicePool->setHue( mPrfsVideoDevice->mHueSlider->value() / 65535.0 ); + emit changed( true ); +} + +void AVDeviceConfig::slotImageAutoBrightnessContrastChanged(bool){ + kdDebug() << "kopete:config (avdevice): slotImageAutoBrightnessContrastChanged(" << mPrfsVideoDevice->mImageAutoBrightnessContrast->isChecked() << ") called. " << endl; + mVideoDevicePool->setAutoBrightnessContrast(mPrfsVideoDevice->mImageAutoBrightnessContrast->isChecked()); + emit changed( true ); +} + +void AVDeviceConfig::slotImageAutoColorCorrectionChanged(bool){ + kdDebug() << "kopete:config (avdevice): slotImageAutoColorCorrectionChanged(" << mPrfsVideoDevice->mImageAutoColorCorrection->isChecked() << ") called. " << endl; + mVideoDevicePool->setAutoColorCorrection(mPrfsVideoDevice->mImageAutoColorCorrection->isChecked()); + emit changed( true ); +} + +void AVDeviceConfig::slotImageAsMirrorChanged(bool){ + kdDebug() << "kopete:config (avdevice): slotImageAsMirrorChanged(" << mPrfsVideoDevice->mImageAsMirror->isChecked() << ") called. " << endl; + mVideoDevicePool->setImageAsMirror(mPrfsVideoDevice->mImageAsMirror->isChecked()); + emit changed( true ); +} + +void AVDeviceConfig::slotUpdateImage() +{ + mVideoDevicePool->getFrame(); + mVideoDevicePool->getImage(&qimage); + bitBlt(mPrfsVideoDevice->mVideoImageLabel, 0, 0, &qimage, 0, Qt::CopyROP); +// kdDebug() << "kopete (avdeviceconfig_videoconfig): Image updated." << endl; +} diff --git a/kopete/kopete/config/avdevice/avdeviceconfig.h b/kopete/kopete/config/avdevice/avdeviceconfig.h new file mode 100644 index 00000000..d732b1a7 --- /dev/null +++ b/kopete/kopete/config/avdevice/avdeviceconfig.h @@ -0,0 +1,79 @@ +/* + avdeviceconfig.h - Kopete Video Device Configuration Panel + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef AVDEVICECONFIG_H +#define AVDEVICECONFIG_H + +#include "kcmodule.h" +#include "videodevicepool.h" +#include +#include +#include + +#ifdef HAVE_GL +# include +#endif + +class QFrame; +class QTabWidget; + +class AVDeviceConfig_VideoDevice; +class AVDeviceConfig_AudioDevice; + +/** +@author Cl�dio da Silveira Pinheiro +*/ +class AVDeviceConfig : public KCModule +{ +Q_OBJECT +public: + AVDeviceConfig(QWidget *parent, const char * name , const QStringList &args); + + ~AVDeviceConfig(); + virtual void save(); + virtual void load(); + +private slots: + void slotSettingsChanged(bool); + void slotValueChanged(int); + void slotDeviceKComboBoxChanged(int); + void slotInputKComboBoxChanged(int); + void slotStandardKComboBoxChanged(int); + void slotBrightnessSliderChanged(int); + void slotContrastSliderChanged(int); + void slotSaturationSliderChanged(int); + void slotWhitenessSliderChanged(int); + void slotHueSliderChanged(int); + void slotImageAutoBrightnessContrastChanged(bool); + void slotImageAutoColorCorrectionChanged(bool); + void slotImageAsMirrorChanged(bool); + void slotUpdateImage(); +private: + QTabWidget* mAVDeviceTabCtl; + AVDeviceConfig_VideoDevice *mPrfsVideoDevice; + AVDeviceConfig_AudioDevice *mPrfsAudioDevice; + Kopete::AV::VideoDevicePool *mVideoDevicePool ; + QImage qimage; + QPixmap qpixmap; + QTimer qtimer; + void setVideoInputParameters(); +#ifdef HAVE_GL + QGLWidget m_video_gl; +#endif +}; + +#endif diff --git a/kopete/kopete/config/avdevice/avdeviceconfig_videoconfig.ui b/kopete/kopete/config/avdevice/avdeviceconfig_videoconfig.ui new file mode 100644 index 00000000..90464a8b --- /dev/null +++ b/kopete/kopete/config/avdevice/avdeviceconfig_videoconfig.ui @@ -0,0 +1,624 @@ + +AVDeviceConfig_VideoDevice + + + AVDeviceConfig_VideoDevice + + + + 0 + 0 + 358 + 510 + + + + + 3 + 3 + 0 + 0 + + + + Video + + + + unnamed + + + + VideoTabWidget + + + + 3 + 3 + 0 + 0 + + + + + tab + + + Device + + + + unnamed + + + + buttonGroup7 + + + + 3 + 1 + 0 + 0 + + + + &Video Device Configuration + + + + unnamed + + + + videodevice_selection_layout + + + + unnamed + + + + videodevice_selection_labels + + + + unnamed + + + + deviceLabel + + + Device: + + + + + inputLabel + + + Input: + + + + + standardLabel + + + Standard: + + + + + + + videodevice_selection_combos + + + + unnamed + + + + mDeviceKComboBox + + + + 3 + 0 + 0 + 0 + + + + + + mInputKComboBox + + + + 3 + 0 + 0 + 0 + + + + + + mStandardKComboBox + + + + 3 + 0 + 0 + 0 + + + + + + + + + + + + + + tab + + + Con&trols + + + + unnamed + + + + buttonGroup12 + + + + 3 + 1 + 0 + 0 + + + + &Image Adjustment + + + + unnamed + + + + layout11 + + + + unnamed + + + + layout9 + + + + unnamed + + + + brightnessLabel + + + + 1 + 5 + 0 + 0 + + + + Brightness: + + + + + contrastLabel + + + + 1 + 5 + 0 + 0 + + + + Contrast: + + + + + saturationLabel + + + + 1 + 5 + 0 + 0 + + + + Saturation: + + + + + whitenessLabel + + + + 1 + 5 + 0 + 0 + + + + Whiteness: + + + + + hueLabel + + + + 1 + 5 + 0 + 0 + + + + Hue: + + + + + + + layout10 + + + + unnamed + + + + mBrightnessSlider + + + + 3 + 0 + 0 + 0 + + + + 65535 + + + 256 + + + 4096 + + + Horizontal + + + + + mContrastSlider + + + + 3 + 0 + 0 + 0 + + + + 65535 + + + 256 + + + 4096 + + + Horizontal + + + + + mSaturationSlider + + + + 3 + 0 + 0 + 0 + + + + 65535 + + + 256 + + + 4096 + + + Horizontal + + + + + mWhitenessSlider + + + + 3 + 0 + 0 + 0 + + + + 65535 + + + 256 + + + 4096 + + + Horizontal + + + + + mHueSlider + + + + 3 + 0 + 0 + 0 + + + + 65535 + + + 256 + + + 4096 + + + Horizontal + + + + + + + + + + + + + TabPage + + + Optio&ns + + + + unnamed + + + + layout8 + + + + unnamed + + + + ImageOptions + + + + 5 + 3 + 0 + 0 + + + + Image options + + + + unnamed + + + + mImageAutoBrightnessContrast + + + + 3 + 1 + 0 + 0 + + + + Au&tomatic brightness/contrast adjustment + + + + + mImageAutoColorCorrection + + + + 3 + 1 + 0 + 0 + + + + Automatic color correction + + + + + + + + mImageAsMirror + + + + 3 + 1 + 0 + 0 + + + + See preview mirrored + + + + + + + + + + + + + + + layout19 + + + + unnamed + + + + imageLeftSpacer + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + mVideoImageLabel + + + + 0 + 0 + 0 + 0 + + + + + 320 + 240 + + + + image0 + + + true + + + + + imageRightSpacer + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + + + 789c8597596f23470e80dfe75718c3b7c182e9eabb11ec834fc9873cbeaf601fc86ec9966df994cfc5fef794483633934d10c836fcb9582cde55fee5dbd2d9de68e9db2f5f9ee7349fb64bed153d2d7deb5e66b38fdffef3efff7ef99aa64b8baf2c5b4abffeebcb57dc5c6a9720499290640b8613e110ff22eb941b07657e7256f90be742e4a7c6a9ed076791c7a1732efbd784d3f817615a71167db0e55c0a17ce95c88f8ced3c7e74d6f306cea29fd159f583b3e8a75367d18f5bce8df0b63389be67e3ccfcd97716fd78e22cfaf1d459edefedcbedbc63e75af4cf85b33edeb8616cfef1b5b39eb7e72cf6c28b7166fb53678df7b5b3fadf388b3d503a8b3d40c6b9eaa35767d9cf37c6a5e53f73d6f3769d55bedf5f2789ac6bfcf2de1fda33ce4cdfbb716eeb6fce1acf1de3c2f2bbe7acf573605cdafe2b67f5efd359f20bb97165febe38b3c453fd2bfa7cd0a67165fea97c9db416ff1de3b1e9d77a6d12567bf8c159ed9d1ab7969fc2b8d378e2ba304579c93769bd719457fd524f2184cae221f51bd250a8fd3c35ae4cfec1b8b6fa06e3c6eaffc859d651f215b2d0c7efdab8327b969d459e9e8d7bfd57c68df587c43be4c1e20b685c1b9f0b17c1e283cfce927f96f911ca40d63fcbce7afebd31eb3a91b3fa37376ead9e64dea5759606a94f546eb23499883d9bc69932bef4acf22cf594725c1f8bfca9b3c8d3aa711e82c82bb73de3817111a4fe2171d67e1a1b97ba8e87ce5a8f12bfb48b2cfae8ceb85206894f9666135bff2e5c45d6fae894a3393a3f5be13aaeb7729eca4ff2d6ce3f5b701ef2b1c95f0aa7715dfb4de657519675d0fc8d8c9ba0f527f12faab234fd8fc695f1ab716dfa3f84ebb2089dec5f779678d381711d74beae398b7f540b3751bfd6e39ab1c9033b6bbd07e326687dcb3c2da8ecf47cfa301e9bbe37e389c94b3c0a2edba0f5f361dc194b3c8b36eed77acf9c453fab7f6d95587e6e8d83c957c25dc9668fdc5fc538b2c6e3a6e754e7adcc836252b6a9d69bccffb2ad535d67e98fb2ab538befaab3e8439947e5b84e82f6ffa17165f1bd7096fcc1ccb8b67a981b375a0f20f92d2775b078493dd54c75aaef0de9dfba75167fea2eb2f683dc5771388d753fbe396b7fca7c6b52ea2c5e57ce621fcbfdd26464f1812767ed8f63e3ced665de34c464fe9e1bb3d5b3f45bc3dca67a7fc9fba7697b46a9bf66cc13cb8fcc8b66c2960f3832b6f3988dad1e2828b799c53371d6f9766b9c5b7f4afe286983f5c3aeb3be177ace6c1ecc9cb51f6a678def8a7169f351fca710f5a93d9db3d64febacf9981aa76a2f5e3b6b7d5d1af7f6df3aebfb67e4acf93f72d6f972675cd83c7d77d67cac3bebfdfce9acf7eb87b3c66b665cdabc5feed9f44bfd51caade59395dbc4fc3f33cecc9f9b9e35bf786fdccfffc459e7ffc059eb7fe2acefcfd459df13dbceda5f4367bddffed8affdf0665c243a6f769c351f6367f51f7ad6fcefcf9dd57e72567fd959e3dd3a6bbc3b677daf34ce6affadb3dadbdb57263a5f46ce7adf8e9dd5dededfbe5e2f9cb5de5b678df7bb71a5fa59f767dccf77cd57d6f7136c185b7e79d359f3159cf53df7e8acf19e19f7f57ee5acef9b0d67f5b733b6fa844b63f38f07ceea9fcc3fca5b9bef58199b3dbce5acefa53567bd6fee8c73d54f95b3fa3b34b6fcc3b3b3f6dba1b3f6afe6a788fb6bad9f1f3f08f19b90b18ddff0f3dafefc2fe4bb284948f1b7f1e2e73fca4ff012af708ad77883b77f2f8f33bc8b9aeff1011ff1099f718e2ff88a6ff88e1ff8196da33fc92fe30aaee21aaee3060e70889bb885dbb88323dc8d7a407df941be8dd2dfa3ec5e94dac7033cc4233cc6133cc5333cc78bffb327c18029669863812556d1ef1a9ba8168080a1850ec608daaf30814bb882296ec035dc486426700b33b8837b78c0213cc2133ce367af3f7a93c01c5e700b5ee10d6fe11d093ee01396610556f104d6601d36a2d77abf0da2f41036610bb6b1841d18c12e7c873dd887033884233886133885b318297d3fc558e114cee1021208d1e01432c8a180122aa8e3c5190f2322c63bb507995aea62bc37698c9f34a14bba822d9ad235ddd02dcde80e87744f0f7fc823d1233de1363d634d737aa1577aa3ebf8047ba70ffaa4655aa1555aa375b37f0c298e6923ca0fb0a1212cd3266dd136edd08876e93bedd13e1dd0211d997e88f6031dd3099d624e67744e17f15f8b40296594cb273ef61632aa5ff34515d5d43032707c19449fd6e993db283b8217ee62fc0630fa31bf1cdf037c493bb1724ef98a162d30e56bcae185467cc3b731dfa0f935f919dff13daef1033ff2133fc77d039e73d41da55ff90d268b8afba17ea27d30a18edff9833f7999577895d7a88421aff31b6fc0e04ff5c6314acc031ef2266cc4c7ce036ff12ca66a10ff75d95eacfd2ccf3b0bfd38e651ac93f7d83b77f84e47d1e6f1222e0bf9c5ef3ff7a3f690766f4ffd0490aa85affffbf5cbef985d44a8 + + + + mHueSlider_valueChanged(int) + mHueSlider_sliderPressed() + mHueSlider_destroyed(QObject*) + + + + kcombobox.h + kcombobox.h + kcombobox.h + + diff --git a/kopete/kopete/config/avdevice/cr128-app-kopete_avdevice.png b/kopete/kopete/config/avdevice/cr128-app-kopete_avdevice.png new file mode 100644 index 00000000..a4e4fa58 Binary files /dev/null and b/kopete/kopete/config/avdevice/cr128-app-kopete_avdevice.png differ diff --git a/kopete/kopete/config/avdevice/cr22-app-kopete_avdevice.png b/kopete/kopete/config/avdevice/cr22-app-kopete_avdevice.png new file mode 100644 index 00000000..4b0eaa77 Binary files /dev/null and b/kopete/kopete/config/avdevice/cr22-app-kopete_avdevice.png differ diff --git a/kopete/kopete/config/avdevice/cr32-app-kopete_avdevice.png b/kopete/kopete/config/avdevice/cr32-app-kopete_avdevice.png new file mode 100644 index 00000000..df6476e9 Binary files /dev/null and b/kopete/kopete/config/avdevice/cr32-app-kopete_avdevice.png differ diff --git a/kopete/kopete/config/avdevice/cr64-app-kopete_avdevice.png b/kopete/kopete/config/avdevice/cr64-app-kopete_avdevice.png new file mode 100644 index 00000000..3d382289 Binary files /dev/null and b/kopete/kopete/config/avdevice/cr64-app-kopete_avdevice.png differ diff --git a/kopete/kopete/config/avdevice/kopete_avdeviceconfig.desktop b/kopete/kopete/config/avdevice/kopete_avdeviceconfig.desktop new file mode 100644 index 00000000..49fa3e94 --- /dev/null +++ b/kopete/kopete/config/avdevice/kopete_avdeviceconfig.desktop @@ -0,0 +1,118 @@ +[Desktop Entry] +Icon=kopete_avdevice +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_avdeviceconfig +X-KDE-FactoryName=KopeteAVDeviceConfigFactory +X-KDE-ParentApp=kopete +X-KDE-ParentComponents=kopete + +Name=Devices +Name[be]=Прылады +Name[bg]=УÑтройÑтва +Name[bn]=ডিভাইস +Name[br]=Trobarzhelloù +Name[bs]=UreÄ‘aji +Name[ca]=Dispositius +Name[cs]=Zařízení +Name[cy]=Dyfeisiau +Name[da]=Enheder +Name[de]=Geräte +Name[el]=Συσκευές +Name[eo]=Aparatoj +Name[es]=Dispositivos +Name[et]=Seadmed +Name[eu]=Dispositiboak +Name[fa]=دستگاهها +Name[fi]=Laitteet +Name[fr]=Périphériques +Name[ga]=Gléasanna +Name[gl]=Dispositivos +Name[he]=×”×ª×§× ×™× +Name[hu]=Eszközök +Name[id]=Divais +Name[is]=Tæki +Name[it]=Periferiche +Name[ja]=デãƒã‚¤ã‚¹ +Name[ka]=მáƒáƒ¬áƒ§áƒáƒ‘ილáƒáƒ‘ები +Name[kk]=Құрылығылар +Name[km]=ឧបករណ០+Name[lt]=Ä®renginiai +Name[lv]=IekÄrtas +Name[nb]=Enheter +Name[nds]=Reedschappen +Name[ne]=यनà¥à¤¤à¥à¤° +Name[nl]=Apparaten +Name[nn]=Einingar +Name[pa]=ਜੰਤਰ +Name[pl]=UrzÄ…dzenia +Name[pt]=Dispositivos +Name[pt_BR]=Dispositivos +Name[ru]=УÑтройÑтва +Name[rw]=Apareye +Name[sk]=Zariadenia +Name[sl]=Naprave +Name[sr]=Уређаји +Name[sr@Latn]=UreÄ‘aji +Name[sv]=Enheter +Name[th]=อุปà¸à¸£à¸“์ +Name[tr]=Aygıtlar +Name[uk]=ПриÑтрої +Name[uz]=Uskunalar +Name[uz@cyrillic]=УÑкуналар +Name[ven]=Maano +Name[wa]=Éndjins +Name[xh]=Amacebo +Name[zh_CN]=设备 +Name[zh_HK]=è£ç½® +Name[zh_TW]=è£ç½® +Name[zu]=Amacebo +Comment=Here You Can Alter Kopete's Video And Audio Devices' Settings +Comment[be]=ÐаÑтаўленні відÑа- Ñ– аўдыёпрыладаў Ð´Ð»Ñ Ð¿Ñ€Ð°Ñ†Ñ‹ з Kopete +Comment[bg]=ÐаÑтройване на видео и аудио уÑтройÑтвата на програмата +Comment[bn]=আপনি à¦à¦–ানে কপেটের ভিডিও à¦à¦¬à¦‚ অডিও ডিভাইসের মানসমূহ পরিবরà§à¦¤à¦¨ করতে পারেন +Comment[bs]=Ovdje možete izmijeniti postavke Kopete-a za video i audio ureÄ‘aje +Comment[ca]=Aquí podreu modificar els paràmetres dels dispositius d'àudio i vídeo del Kopete +Comment[cs]=Zde je možné pÅ™izpůsobit si nastavení audio a video zařízení +Comment[da]=Her kan du ændre Kopete's video- og lydenheds opsætning +Comment[de]=Hier können Sie Kopetes Einstellungen zu Audio- und Videogeräten verändern +Comment[el]=Εδώ μποÏείτε να Ï„Ïοποποιήσετε τις Ïυθμίσεις των συσκευών βίντεο και ήχου +Comment[es]=Aquí­ puede configurar los dispositivos de sonido y audio de Kopete +Comment[et]=Siin saab muuta Kopete video- ja heliseadmete seadistusi +Comment[eu]=Hemen Kopete-ren bideo eta audio ezarpenak ezar ditzakezu +Comment[fa]=اینجا می‌توانید تنظیمات دستگاههای ویدیویی Ùˆ صوتیKopete را تغییر دهید +Comment[fi]=Täältä voit muuttaa Kopeten video- ja äänilaitteiden asetuksia +Comment[fr]=Vous pouvez modifier ici la configuration des périphériques audio et vidéo de Kopete +Comment[gl]=Aquí podes modificar as opcións dos dispositivos de video e audio de Kopete +Comment[hu]=Itt lehet módosítani a Kopete video- és hangbeállításait +Comment[is]=Hér getur þú breytt vídeó- og hljóðtæki stillingum Kopete +Comment[it]=Qui puoi modificare le impostazioni audio e video di Kopete +Comment[ja]=ã“ã“㧠Kopete ã® A/V デãƒã‚¤ã‚¹è¨­å®šã‚’変更ã—ã¾ã™ +Comment[ka]=áƒáƒ¥ თქვენ შეგიძლიáƒáƒ— Kopeteს ვიდერდრáƒáƒ£áƒ“ირმáƒáƒ¬áƒ§áƒáƒ‘ილáƒáƒ‘ების პáƒáƒ áƒáƒ›áƒ”ტრების გáƒáƒ›áƒáƒ áƒ—ვრ+Comment[kk]=Мұнда Kopete-Ñ‚Ñ–Ò£ бейне мен Ð´Ñ‹Ð±Ñ‹Ñ Ò›Ò±Ñ€Ñ‹Ð»Ñ‹Ò“Ñ‹Ð»Ð°Ñ€Ð´Ñ‹Ò£ параметрлерін өзгерте алаÑыз +Comment[km]=ទីនáŸáŸ‡ អ្នក​អាច​ប្ដូរ​ការ​កំណážáŸ‹â€‹áž“ៃ​ឧបករណáŸâ€‹áž¢áž¼ážŒáž¸áž™áŸ‰áž¼ និង​វីដáŸáž¢áž¼â€‹ážšáž”ស់ Kopete +Comment[lt]=ÄŒia galite keisti Kopete video ir audio įrenginių nustatymus +Comment[nb]=Her kan du endre innstillinger for lyd og bilde i Kopete +Comment[nds]=Hier kannst Du de Instellen för de Video- un Audio-Reedschappen vun Kopete ännern +Comment[ne]=यहाठतपाईठकोपेटको भिडियो र अडियो यनà¥à¤¤à¥à¤° सेटिङ अलà¥à¤Ÿà¤° गरà¥à¤¨ सकà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤› +Comment[nl]=Hier kunt u de video- en audioapparaten voor Kopete instellen +Comment[nn]=Her kan du endra pÃ¥ innstillingane for lyd og bilete i Kopete +Comment[pl]=Tutaj można zmienić ustawienia urzÄ…dzeÅ„ audio i wideo Kopete +Comment[pt]=Aqui Você Pode Alterar a Configuração do Ãudio e Vídeo do Kopete +Comment[pt_BR]=Aqui você pode alterar as configurações dos dispositivos de audio e vídeo do Kopete +Comment[ru]=Параметры видео- и аудиоуÑтройÑтв Ð´Ð»Ñ Kopete +Comment[sk]=Tu môžete upraviÅ¥ nastavenia video a audio zariadení pre Kopete +Comment[sl]=Tukaj lahko spreminjate nastavitve video in zvoÄnih naprav za Kopete +Comment[sr]=Овде можете променити поÑтавке Kopete-ових аудио и видео уређаја +Comment[sr@Latn]=Ovde možete promeniti postavke Kopete-ovih audio i video ureÄ‘aja +Comment[sv]=Här kan du ändra Kopetes video- och ljudenhetsinställningar +Comment[tr]=Kopete'nin Video ve Ses Ayarlarını Buradan DeÄŸiÅŸtirebilirsiniz +Comment[uk]=Тут можна змінити відео Ñ– аудіо параметри Kopete +Comment[uz]=Bu yerda video va audio uskunalarini moslash mumkin +Comment[uz@cyrillic]=Бу ерда видео ва аудио уÑкуналарини моÑлаш мумкин +Comment[zh_CN]=您å¯åœ¨æ­¤æ›´æ”¹ Kopete 的影音设备设置 +Comment[zh_HK]=在此您å¯æ”¹å‹• Kopete 視åƒå’ŒéŸ³è¨Šè£ç½®çš„設定 +Comment[zh_TW]=您å¯ä»¥åœ¨æ­¤æ”¹è®Š Kopete çš„å½±åƒèˆ‡è²éŸ³è£ç½®è¨­å®š +DocPath=kopete/configure-dialog.html#configuring-avdevice diff --git a/kopete/kopete/config/behavior/Makefile.am b/kopete/kopete/config/behavior/Makefile.am new file mode 100644 index 00000000..cc7a6196 --- /dev/null +++ b/kopete/kopete/config/behavior/Makefile.am @@ -0,0 +1,18 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/libkopete/private $(KOPETE_COMPAT_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kcm_kopete_behaviorconfig.la + +kcm_kopete_behaviorconfig_la_SOURCES = \ + kopeteawayconfigbase.ui \ + behaviorconfig_chat.ui behaviorconfig_general.ui behaviorconfig_events.ui behaviorconfig.cpp + +kcm_kopete_behaviorconfig_la_LDFLAGS = -no-undefined -module $(KDE_PLUGIN) $(all_libraries) + +kcm_kopete_behaviorconfig_la_LIBADD = ../../../libkopete/libkopete.la \ + $(LIB_KOPETECOMPAT) $(LIB_KUTILS) + +service_DATA = kopete_behaviorconfig.desktop +servicedir = $(kde_servicesdir) + +# vim: set noet: diff --git a/kopete/kopete/config/behavior/behaviorconfig.cpp b/kopete/kopete/config/behavior/behaviorconfig.cpp new file mode 100644 index 00000000..379e762a --- /dev/null +++ b/kopete/kopete/config/behavior/behaviorconfig.cpp @@ -0,0 +1,305 @@ +/* + behaviorconfig.cpp - Kopete Look Feel Config + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "behaviorconfig.h" +#include "behaviorconfig_general.h" +#include "behaviorconfig_events.h" +#include "behaviorconfig_chat.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopeteprefs.h" +#include "kopeteaway.h" +#include "kopeteawayconfigbase.h" +#include "kopetepluginmanager.h" +#include "kopeteaway.h" + +#include + +typedef KGenericFactory KopeteBehaviorConfigFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_behaviorconfig, KopeteBehaviorConfigFactory( "kcm_kopete_behaviorconfig" ) ) + + +BehaviorConfig::BehaviorConfig(QWidget *parent, const char * /* name */, const QStringList &args) : + KCModule( KopeteBehaviorConfigFactory::instance(), parent, args ) +{ + (new QVBoxLayout(this))->setAutoAdd(true); + mBehaviorTabCtl = new QTabWidget(this, "mBehaviorTabCtl"); + + // "General" TAB ============================================================ + mPrfsGeneral = new BehaviorConfig_General(mBehaviorTabCtl); + mBehaviorTabCtl->addTab(mPrfsGeneral, i18n("&General")); + + // "Events" TAB ============================================================ + mPrfsEvents = new BehaviorConfig_Events(mBehaviorTabCtl); + mBehaviorTabCtl->addTab(mPrfsEvents, i18n("&Events")); + + // "Away" TAB =============================================================== + mAwayConfigUI = new KopeteAwayConfigBaseUI(mBehaviorTabCtl); + mBehaviorTabCtl->addTab(mAwayConfigUI, i18n("A&way Settings")); + + // "Chat" TAB =============================================================== + mPrfsChat = new BehaviorConfig_Chat(mBehaviorTabCtl); + mBehaviorTabCtl->addTab(mPrfsChat, i18n("Cha&t")); + + Kopete::PluginManager *pluginManager = Kopete::PluginManager::self(); + viewPlugins = pluginManager->availablePlugins("Views"); + + load(); + + + // "General" TAB ============================================================ + connect(mPrfsGeneral->mShowTrayChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsGeneral->mStartDockedChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsGeneral->mUseQueueChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsGeneral->mUseStackChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsGeneral->mQueueUnreadMessagesChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsGeneral->mAutoConnect, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + + // "Events" TAB ============================================================ + connect(mPrfsEvents->mQueueOnlyHighlightedMessagesInGroupChatsChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsEvents->mQueueOnlyMessagesOnAnotherDesktopChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsEvents->mBalloonNotifyChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsEvents->mBalloonNotifyIgnoreClosesChatViewChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsEvents->mCloseBalloonChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsEvents->mBalloonCloseDelay, SIGNAL(valueChanged(int)), + this, SLOT(slotValueChanged(int))); + connect(mPrfsEvents->mTrayflashNotifyChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsEvents->mTrayflashNotifyLeftClickOpensMessageChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsEvents->mTrayflashNotifySetCurrentDesktopToChatViewChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsEvents->mSoundIfAwayChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsEvents->mEventIfActive, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect(mPrfsEvents->mRaiseMsgWindowChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + + + // "Chat" TAB =============================================================== + connect( mPrfsChat->cb_ShowEventsChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect( mPrfsChat->highlightEnabled, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect( mPrfsChat->cb_SpellCheckChk, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect( mPrfsChat->cmbChatGroupingPolicy, SIGNAL(activated(int)), + this, SLOT(slotValueChanged(int))); + connect( mPrfsChat->mChatViewBufferSize, SIGNAL(valueChanged(int)), + this, SLOT(slotValueChanged(int))); + connect( mPrfsChat->truncateContactNameEnabled, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect( mPrfsChat->mMaxContactNameLength, SIGNAL(valueChanged(int)), + this, SLOT(slotValueChanged(int))); + connect( mPrfsChat->viewPlugin, SIGNAL(activated(int)), + this, SLOT(slotValueChanged(int))); + connect( mPrfsChat->viewPlugin, SIGNAL(activated(int)), + this, SLOT(slotUpdatePluginLabel(int))); + + // "Away" TAB =============================================================== + connect( mAwayConfigUI->rememberedMessages, SIGNAL(valueChanged(int)), + this, SLOT(slotValueChanged(int))); + connect( mAwayConfigUI->mAutoAwayTimeout, SIGNAL(valueChanged(int)), + this, SLOT(slotValueChanged(int))); + connect( mAwayConfigUI->mGoAvailable, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect( mAwayConfigUI->mUseAutoAway, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect( mAwayConfigUI->mDisplayLastAwayMessage, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect( mAwayConfigUI->mDisplayCustomAwayMessage, SIGNAL(toggled(bool)), + this, SLOT(slotSettingsChanged(bool))); + connect( mAwayConfigUI->mAutoAwayMessageEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(slotTextChanged(const QString&))); +} + +void BehaviorConfig::save() +{ +// kdDebug(14000) << k_funcinfo << "called." << endl; + + KopetePrefs *p = KopetePrefs::prefs(); + KConfig *config = KGlobal::config(); + + // "General" TAB ============================================================ + p->setShowTray(mPrfsGeneral->mShowTrayChk->isChecked()); + p->setStartDocked(mPrfsGeneral->mStartDockedChk->isChecked()); + p->setUseQueue(mPrfsGeneral->mUseQueueChk->isChecked()); + p->setUseStack(mPrfsGeneral->mUseStackChk->isChecked()); + p->setQueueUnreadMessages(mPrfsGeneral->mQueueUnreadMessagesChk->isChecked()); + p->setAutoConnect(mPrfsGeneral->mAutoConnect->isChecked()); + + // "Events" TAB ============================================================ + p->setQueueOnlyHighlightedMessagesInGroupChats(mPrfsEvents->mQueueOnlyHighlightedMessagesInGroupChatsChk->isChecked()); + p->setQueueOnlyMessagesOnAnotherDesktop(mPrfsEvents->mQueueOnlyMessagesOnAnotherDesktopChk->isChecked()); + p->setBalloonNotify(mPrfsEvents->mBalloonNotifyChk->isChecked()); + p->setBalloonNotifyIgnoreClosesChatView(mPrfsEvents->mBalloonNotifyIgnoreClosesChatViewChk->isChecked()); + p->setBalloonClose(mPrfsEvents->mCloseBalloonChk->isChecked()); + p->setBalloonDelay(mPrfsEvents->mBalloonCloseDelay->value()); + p->setTrayflashNotify(mPrfsEvents->mTrayflashNotifyChk->isChecked()); + p->setTrayflashNotifyLeftClickOpensMessage(mPrfsEvents->mTrayflashNotifyLeftClickOpensMessageChk->isChecked()); + p->setTrayflashNotifySetCurrentDesktopToChatView(mPrfsEvents->mTrayflashNotifySetCurrentDesktopToChatViewChk->isChecked()); + p->setSoundIfAway(mPrfsEvents->mSoundIfAwayChk->isChecked()); + p->setRaiseMsgWindow(mPrfsEvents->mRaiseMsgWindowChk->isChecked()); + config->setGroup("General"); + config->writeEntry("EventIfActive", mPrfsEvents->mEventIfActive->isChecked()); + + // "Away" TAB =============================================================== + p->setRememberedMessages( mAwayConfigUI->rememberedMessages->value() ); + + config->setGroup("AutoAway"); + config->writeEntry("Timeout", mAwayConfigUI->mAutoAwayTimeout->value() * 60); + config->writeEntry("GoAvailable", mAwayConfigUI->mGoAvailable->isChecked()); + config->writeEntry("UseAutoAway", mAwayConfigUI->mUseAutoAway->isChecked() ); + config->writeEntry("UseAutoAwayMessage", mAwayConfigUI->mDisplayCustomAwayMessage->isChecked() ); + config->sync(); + + // Save the auto away message, if defined + if( mAwayConfigUI->mDisplayCustomAwayMessage->isChecked() ) + { + awayInstance->setAutoAwayMessage( mAwayConfigUI->mAutoAwayMessageEdit->text() ); + } + + // "Chat" TAB =============================================================== + p->setShowEvents(mPrfsChat->cb_ShowEventsChk->isChecked()); + p->setHighlightEnabled(mPrfsChat->highlightEnabled->isChecked()); + p->setSpellCheck(mPrfsChat->cb_SpellCheckChk->isChecked()); + p->setInterfacePreference( viewPlugins[mPrfsChat->viewPlugin->currentItem()]->pluginName() ); + p->setChatWindowPolicy(mPrfsChat->cmbChatGroupingPolicy->currentItem()); + + p->setChatViewBufferSize(mPrfsChat->mChatViewBufferSize->value()); + p->setTruncateContactNames(mPrfsChat->truncateContactNameEnabled->isChecked()); + p->setMaxContactNameLength(mPrfsChat->mMaxContactNameLength->value()); + + p->save(); + emit changed(false); +} + +void BehaviorConfig::load() +{ +// kdDebug(14000) << k_funcinfo << "called" << endl; + KopetePrefs *p = KopetePrefs::prefs(); + KConfig *config = KGlobal::config(); + awayInstance = Kopete::Away::getInstance(); + + // "General" TAB ============================================================ + mPrfsGeneral->mShowTrayChk->setChecked( p->showTray() ); + mPrfsGeneral->mStartDockedChk->setChecked( p->startDocked() ); + mPrfsGeneral->mInstantMessageOpeningChk->setChecked( !p->useQueue() && !p->useStack()); + mPrfsGeneral->mUseQueueChk->setChecked( p->useQueue() ); + mPrfsGeneral->mUseStackChk->setChecked( p->useStack() ); + mPrfsGeneral->mQueueUnreadMessagesChk->setChecked ( p->queueUnreadMessages() ); + mPrfsGeneral->mAutoConnect->setChecked( p->autoConnect() ); + + // "Events" TAB ============================================================ + mPrfsEvents->mQueueOnlyHighlightedMessagesInGroupChatsChk->setChecked ( p->queueOnlyHighlightedMessagesInGroupChats() ); + mPrfsEvents->mQueueOnlyMessagesOnAnotherDesktopChk->setChecked ( p->queueOnlyMessagesOnAnotherDesktop() ); + mPrfsEvents->mBalloonNotifyChk->setChecked ( p->balloonNotify() ); + mPrfsEvents->mBalloonNotifyIgnoreClosesChatViewChk->setChecked ( p->balloonNotifyIgnoreClosesChatView() ); + mPrfsEvents->mCloseBalloonChk->setChecked( p->balloonClose() ); + mPrfsEvents->mBalloonCloseDelay->setValue( p->balloonCloseDelay() ); + mPrfsEvents->mTrayflashNotifyChk->setChecked ( p->trayflashNotify() ); + mPrfsEvents->mTrayflashNotifyLeftClickOpensMessageChk->setChecked ( p->trayflashNotifyLeftClickOpensMessage() ); + mPrfsEvents->mTrayflashNotifySetCurrentDesktopToChatViewChk->setChecked ( p->trayflashNotifySetCurrentDesktopToChatView() ); + mPrfsEvents->mSoundIfAwayChk->setChecked( p->soundIfAway() ); + mPrfsEvents->mRaiseMsgWindowChk->setChecked(p->raiseMsgWindow()); + config->setGroup("General"); + mPrfsEvents->mEventIfActive->setChecked(config->readBoolEntry("EventIfActive", true)); + + // "Away" TAB =============================================================== + config->setGroup("AutoAway"); + mAwayConfigUI->mAutoAwayTimeout->setValue(config->readNumEntry("Timeout", 600)/60); + mAwayConfigUI->mGoAvailable->setChecked(config->readBoolEntry("GoAvailable", true)); + mAwayConfigUI->mUseAutoAway->setChecked(config->readBoolEntry("UseAutoAway", true)); + mAwayConfigUI->rememberedMessages->setValue( p->rememberedMessages() ); + mAwayConfigUI->mAutoAwayMessageEdit->setText( awayInstance->autoAwayMessage() ); + + // Always display the last away message by default + mAwayConfigUI->mDisplayCustomAwayMessage->setChecked(config->readBoolEntry("UseAutoAwayMessage", false)); + + // "Chat" TAB =============================================================== + mPrfsChat->cb_ShowEventsChk->setChecked(p->showEvents()); + mPrfsChat->highlightEnabled->setChecked(p->highlightEnabled()); + mPrfsChat->cb_SpellCheckChk->setChecked(p->spellCheck()); + mPrfsChat->cmbChatGroupingPolicy->setCurrentItem(p->chatWindowPolicy()); + + mPrfsChat->mChatViewBufferSize->setValue(p->chatViewBufferSize()); + mPrfsChat->truncateContactNameEnabled->setChecked(p->truncateContactNames()); + mPrfsChat->mMaxContactNameLength->setValue(p->maxConactNameLength()); + + + mPrfsChat->viewPlugin->clear(); + int selectedIdx = 0, i = 0; + for( QValueList::iterator it = viewPlugins.begin(); it != viewPlugins.end(); ++it ) + { + if( (*it)->pluginName() == p->interfacePreference() ) + selectedIdx = i; + mPrfsChat->viewPlugin->insertItem( (*it)->name(), i++ ); + } + + mPrfsChat->viewPlugin->setCurrentItem(selectedIdx); + slotUpdatePluginLabel(selectedIdx); +} + +void BehaviorConfig::slotUpdatePluginLabel(int) +{ + mPrfsChat->viewPluginLabel->setText( viewPlugins[ mPrfsChat->viewPlugin->currentItem() ]->comment() ); +} + +void BehaviorConfig::slotSettingsChanged(bool) +{ + emit changed(true); +} + +void BehaviorConfig::slotValueChanged(int) +{ + emit changed( true ); +} + +void BehaviorConfig::slotTextChanged(const QString&) +{ + emit changed( true ); +} + +#include "behaviorconfig.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/config/behavior/behaviorconfig.h b/kopete/kopete/config/behavior/behaviorconfig.h new file mode 100644 index 00000000..5a981784 --- /dev/null +++ b/kopete/kopete/config/behavior/behaviorconfig.h @@ -0,0 +1,61 @@ +/* + behaviorconfig.h - Kopete Look Feel Config + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef __BEHAVIOR_H +#define __BEHAVIOR_H + +#include "kcmodule.h" + +namespace Kopete +{ +class Away; +} + +class QFrame; +class QTabWidget; + +class BehaviorConfig_General; +class BehaviorConfig_Events; +class BehaviorConfig_Chat; +class KopeteAwayConfigBaseUI; +class KPluginInfo; + +class BehaviorConfig : public KCModule +{ + Q_OBJECT + + public: + BehaviorConfig(QWidget *parent, const char * name , const QStringList &args) ; + + virtual void save(); + virtual void load(); + + private slots: + void slotSettingsChanged(bool); + void slotValueChanged(int); + void slotUpdatePluginLabel(int); + void slotTextChanged(const QString&); + + private: + QTabWidget* mBehaviorTabCtl; + BehaviorConfig_General *mPrfsGeneral; + BehaviorConfig_Events *mPrfsEvents; + BehaviorConfig_Chat *mPrfsChat; + KopeteAwayConfigBaseUI *mAwayConfigUI; + QValueList viewPlugins; + Kopete::Away* awayInstance; +}; +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/config/behavior/behaviorconfig_chat.ui b/kopete/kopete/config/behavior/behaviorconfig_chat.ui new file mode 100644 index 00000000..26bfae46 --- /dev/null +++ b/kopete/kopete/config/behavior/behaviorconfig_chat.ui @@ -0,0 +1,308 @@ + +BehaviorConfig_Chat + + + BehaviorConfig_Chat + + + + 0 + 0 + 564 + 537 + + + + Chat + + + + unnamed + + + + interfaceGroup + + + &Interface Preference + + + + unnamed + + + + viewPlugin + + + + + viewPluginLabel + + + + + + viewPlugin + + + + + spacer5 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + spacer2 + + + Vertical + + + Expanding + + + + 20 + 240 + + + + + + chatWindowGroup + + + Chat Window Grouping &Policy + + + + unnamed + + + + + Open All Messages in New Chat Window + + + + + Group Messages From Same Account in Same Chat Window + + + + + Group All Messages in Same Chat Window + + + + + Group Messages From Contacts in Same Group in Same Chat Window + + + + + Group Messages From Same Metacontact in Same Chat Window + + + + cmbChatGroupingPolicy + + + <dl> + <dt><tt>Open all messages in a new chat window</tt> + <dd>Every chat will have its own window. + <dt><tt>Group messages from the same account in the same chat window</tt> + <dd>All chats for one account get grouped in to one window by using tabs. + <dt><tt>Group all messages in the same chat window</tt> + <dd>All chats get grouped in to one window by using tabs. + <dt><tt>Group messages from contacts in the same group in the same chat window</tt> + <dd>All chats from one group get grouped in to one window by using tabs. + <dt><tt>Group messages from the same metacontact in the same chat window</tt> + <dd>All chats from one metacontact get grouped in to one window by using tabs. + </dl> + + + + + + + + highlightEnabled + + + + 7 + 0 + 0 + 0 + + + + High&light messages containing your nickname + + + + + cb_SpellCheckChk + + + + 7 + 0 + 0 + 0 + + + + E&nable automatic spell checking + + + + + cb_ShowEventsChk + + + &Show events in chat window + + + + + layout3 + + + + unnamed + + + + truncateContactNameEnabled + + + T&runcate contact name with more characters than: + + + + + spacer3 + + + Horizontal + + + Expanding + + + + 120 + 20 + + + + + + mMaxContactNameLength + + + false + + + 5 + + + 20 + + + + + + + layout4 + + + + unnamed + + + + txtChatViewBufferSize + + + &Maximum number of chat window lines: + + + mChatViewBufferSize + + + Limit the maximum number of lines visible in a chat window to improve speed for complex layouts. + + + + + spacer6 + + + Horizontal + + + Expanding + + + + 212 + 20 + + + + + + mChatViewBufferSize + + + 9000 + + + 2 + + + 250 + + + + + + + + + truncateContactNameEnabled + toggled(bool) + mMaxContactNameLength + setEnabled(bool) + + + + cb_ShowEventsChk + highlightEnabled + cb_SpellCheckChk + viewPlugin + cmbChatGroupingPolicy + truncateContactNameEnabled + mMaxContactNameLength + mChatViewBufferSize + + + diff --git a/kopete/kopete/config/behavior/behaviorconfig_events.ui b/kopete/kopete/config/behavior/behaviorconfig_events.ui new file mode 100644 index 00000000..ea2d1ea3 --- /dev/null +++ b/kopete/kopete/config/behavior/behaviorconfig_events.ui @@ -0,0 +1,388 @@ + +BehaviorConfig_Events + + + BehaviorConfig_Events + + + + 0 + 0 + 427 + 386 + + + + Events + + + + unnamed + + + + notifyGroupBox + + + Tray Flash && Bubble + + + + unnamed + + + + mTrayflashNotifyChk + + + Flash s&ystem tray + + + Flash the system tray icon on an incoming message + + + Flash the system tray icon whenever a message comes in. + + + + + layout11_2 + + + + unnamed + + + + spacer10_2 + + + Horizontal + + + Fixed + + + + 16 + 16 + + + + + + mTrayflashNotifyLeftClickOpensMessageChk + + + false + + + &Left mouse click opens message + + + Left mouse click on flashing system tray opens message instead of restoring/minimizing contact list + + + A left mouse click on the flashing system tray icon opens the incoming message instead of restoring/minimizing the contact list (e.g. to check who is sending messages). A middle click always opens this message. + + + + + + + mBalloonNotifyChk + + + Sho&w bubble + + + Show a bubble on an incoming message + + + Show a bubble whenever a message comes in. + + + + + layout11 + + + + unnamed + + + + spacer10 + + + Horizontal + + + Fixed + + + + 16 + 16 + + + + + + mBalloonNotifyIgnoreClosesChatViewChk + + + false + + + Button "&Ignore" closes chat + + + The "Ignore" button of the bubble closes the chat window for the sender + + + If there is already a chat window opened for the sender of the message displayed in the bubble the "Ignore" button will close this chat window. + + + + + + + layout7 + + + + unnamed + + + + spacer7 + + + Horizontal + + + Fixed + + + + 16 + 16 + + + + + + mCloseBalloonChk + + + false + + + Close &bubble automatically after + + + false + + + Automatically close bubble after fixed amount of time + + + Bubbles will automatically be closed after a fixed amount of time. A closed one will be replaced by a new one if another message is waiting. + + + + + mBalloonCloseDelay + + + false + + + Sec + + + 120 + + + 1 + + + 30 + + + + + spacer6 + + + Horizontal + + + Expanding + + + + 70 + 20 + + + + + + + + mQueueOnlyHighlightedMessagesInGroupChatsChk + + + Exclude non-highlighted messages in grou&p chats + + + Notify only highlighted messages in group chats + + + In very active group chats important messages can be singled out by excluding non-highlighted messages from notification. + + + + + mQueueOnlyMessagesOnAnotherDesktopChk + + + Exclude messages in chats on current des&ktop + + + Do not display notification for messages in chat windows on current desktop + + + This option allows you to turn off the notification of events for chat windows that are on the current desktop. If this option is turned on, then only chat windows on different desktops than the current one will notify you that an event has occured. Otherwise, all chat windows will notify you that an event has occured. + + + + + + + groupBox1 + + + Miscellaneous + + + + unnamed + + + + mSoundIfAwayChk + + + E&nable events while away + + + Enable events if your account status is "Away" + + + Enable notification events even if your account status is "Away" or less available, e.g. "Not Available" or "Do not Disturb". Note: This does not affect the flashing of the system tray icon. + + + + + mEventIfActive + + + Enable events for acti&ve chat windows + + + Enable events for incoming messages if the chat window is active + + + Enable notification events for incoming messages even if the receiving chat window is active. Note: Neither the system tray icon flashes nor the bubble is shown. + + + + + mTrayflashNotifySetCurrentDesktopToChatViewChk + + + Switch &to desktop containing chat on opening message + + + Switch to the desktop which contains the chat window for the sender when opening his/her message + + + If there is already a chat window open for the sender of the message, opening his/her message will cause a switch to the desktop which contains this chat window. + + + + + mRaiseMsgWindowChk + + + &Raise window on incoming message + + + Raise the chat window/tab on an incoming message + + + If there is already a chat window opened for the sender of an incoming message this window will be put on the current desktop and in front of all other windows. + + + + + + + spacer1 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + + + mTrayflashNotifyChk + toggled(bool) + mTrayflashNotifyLeftClickOpensMessageChk + setEnabled(bool) + + + mBalloonNotifyChk + toggled(bool) + mBalloonNotifyIgnoreClosesChatViewChk + setEnabled(bool) + + + mBalloonNotifyChk + toggled(bool) + mCloseBalloonChk + setEnabled(bool) + + + mBalloonNotifyChk + toggled(bool) + mBalloonCloseDelay + setEnabled(bool) + + + + mTrayflashNotifyChk + mBalloonNotifyChk + mQueueOnlyHighlightedMessagesInGroupChatsChk + mQueueOnlyMessagesOnAnotherDesktopChk + mSoundIfAwayChk + mEventIfActive + mTrayflashNotifySetCurrentDesktopToChatViewChk + mRaiseMsgWindowChk + + + diff --git a/kopete/kopete/config/behavior/behaviorconfig_general.ui b/kopete/kopete/config/behavior/behaviorconfig_general.ui new file mode 100644 index 00000000..d15815ad --- /dev/null +++ b/kopete/kopete/config/behavior/behaviorconfig_general.ui @@ -0,0 +1,211 @@ + +BehaviorConfig_General + + + BehaviorConfig_General + + + + 0 + 0 + 348 + 302 + + + + General + + + + unnamed + + + + buttonGroup2 + + + System Tray + + + + unnamed + + + + mShowTrayChk + + + Show system tray &icon + + + Show the icon in the system tray + + + By default, the system tray icon indicates new incoming messages by flashing and showing a bubble. A left or middle mouse click on the icon will open the message in a new chat window. Pressing the "View" button in the bubble has the same effect. + + + + + mStartDockedChk + + + false + + + Start with hidden &main window + + + Start with the main window minimized to the system tray + + + Start with the main window hidden. The only visible item is the system tray icon. + + + + + + + buttonGroup1 + + + Message Handling + + + + unnamed + + + + mInstantMessageOpeningChk + + + Open messages instantl&y + + + Instantly open incoming messages + + + If there is no existing chat window a new window will be opened when a new message comes in. If there is already a chat window opened for the sender of the message it will be displayed there instantly. + + + + + mUseQueueChk + + + Use message &queue + + + Use a message queue to store incoming messages + + + Store new incoming messages in a message queue. New messages are messages that cannot be displayed in an already open chat window. Only queued or stacked messages trigger notification via bubble, a flashing tray icon, or both.. + + + + + mUseStackChk + + + Use message stac&k + + + Use a message stack to store incoming messages + + + Store new incoming messages in a message stack. New messages are messages that cannot be displayed in an already open chat window. Only queued or stacked messages trigger notification via bubble and flashing tray. + + + + + mQueueUnreadMessagesChk + + + false + + + Queue/stack &unread messages + + + Also add unread messages to queue/stack + + + Unread messages are messages that will be displayed in an already opened but inactive chat window. Only incoming queued messages trigger notification via the bubble, the flashing tray icon, or both. With this option disabled only new incoming messages are queued, i.e. messages that cannot be displayed in an already open chat window. + + + + + + + groupBox2 + + + Miscellaneous + + + + unnamed + + + + mAutoConnect + + + Connect automatically at &startup + + + Connect all your accounts automatically when starting Kopete + + + When starting Kopete all your accounts will be connected automatically. Note: You can exclude accounts individually in their properties. + + + + + + + spacer1 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + + + mShowTrayChk + toggled(bool) + mStartDockedChk + setEnabled(bool) + + + mUseQueueChk + toggled(bool) + mQueueUnreadMessagesChk + setEnabled(bool) + + + mUseStackChk + toggled(bool) + mQueueUnreadMessagesChk + setEnabled(bool) + + + + mShowTrayChk + mStartDockedChk + mUseQueueChk + mAutoConnect + + + diff --git a/kopete/kopete/config/behavior/kopete_behaviorconfig.desktop b/kopete/kopete/config/behavior/kopete_behaviorconfig.desktop new file mode 100644 index 00000000..a9531a60 --- /dev/null +++ b/kopete/kopete/config/behavior/kopete_behaviorconfig.desktop @@ -0,0 +1,135 @@ +[Desktop Entry] +Icon=configure +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_behaviorconfig +X-KDE-FactoryName=KopeteBehaviorConfigFactory +X-KDE-ParentApp=kopete +X-KDE-ParentComponents=kopete + +Name=Behavior +Name[ar]=السلوك +Name[be]=Паводзіны +Name[bg]=Поведение +Name[bn]=আচরণ +Name[br]=Emzalc'h +Name[bs]=PonaÅ¡anje +Name[ca]=Comportament +Name[cs]=Chování +Name[cy]=Ymddygiad +Name[da]=Opførsel +Name[de]=Verhalten +Name[el]=ΣυμπεÏιφοÏά +Name[en_GB]=Behaviour +Name[eo]=Fenestrokonduto +Name[es]=Comportamiento +Name[et]=Käitumine +Name[eu]=Portaera +Name[fa]=رÙتار +Name[fi]=Käytös +Name[fr]=Comportement +Name[ga]=Oibriú +Name[gl]=Comportamento +Name[he]=×ופן פעולה +Name[hi]=बरà¥à¤¤à¤¾à¤µ +Name[hr]=PonaÅ¡anje +Name[hu]=Működés +Name[is]=Hegðun +Name[it]=Comportamento +Name[ja]=挙動 +Name[ka]=ქცევრ+Name[kk]=ҚаÑиеттер +Name[km]=ឥរិយាបហ+Name[lt]=Elgsena +Name[mk]=ОднеÑување +Name[nb]=Oppførsel +Name[nds]=Bedregen +Name[ne]=वà¥à¤¯à¤µà¤¹à¤¾à¤° +Name[nl]=Gedrag +Name[nn]=Ã…tferd +Name[pa]=ਰਵੱਈਆ +Name[pl]=Zachowanie +Name[pt]=Comportamento +Name[pt_BR]=Comportamento +Name[ro]=Comportament +Name[ru]=Поведение +Name[rw]=Imyitwarire +Name[se]=Láhtten +Name[sk]=Chovanie +Name[sl]=ObnaÅ¡anje +Name[sr]=Понашање +Name[sr@Latn]=PonaÅ¡anje +Name[sv]=Beteende +Name[tg]=Рафтор +Name[tr]=Davranış +Name[uk]=Поведінка +Name[uz]=Xususiyatlar +Name[uz@cyrillic]=ХуÑуÑиÑтлар +Name[wa]=Dujhance +Name[zh_CN]=行为 +Name[zh_HK]=行為 +Name[zh_TW]=行為 +Comment=Here You Can Personalize Kopete +Comment[ar]=هنا يمكنك تخصيص Kopete +Comment[be]=ПерÑÐ°Ð½Ð°Ð»Ñ–Ð·Ð°Ñ†Ñ‹Ñ Kopete +Comment[bg]=ÐаÑтройване поведението на програмата +Comment[bn]=আপনি à¦à¦–ানে কপেট বà§à¦¯à¦•à§à¦¤à¦¿à¦—তকরণ করতে পারেন +Comment[bs]=Ovdje možete prilagoditi sebi Kopete +Comment[ca]=Aquí podreu personalitzar el Kopete +Comment[cs]=Zde je možné pÅ™izpůsobit si Kopete +Comment[cy]=Yma Gallwch Bersonoleiddio Kopete +Comment[da]=Her kan du personliggøre Kopete +Comment[de]=Hier können Sie Kopete an Ihre persönlichen Vorstellungen anpassen +Comment[el]=Εδώ μποÏείτε να Ï€ÏοσαÏμόσετε το Kopete +Comment[en_GB]=Here You Can Personalise Kopete +Comment[es]=Aquí­ puede personalizar Kopete +Comment[et]=Siin saab muuta Kopete just selliseks, nagu sulle meeldib +Comment[eu]=Hemen Kopete pertsonalizatu dezakezu +Comment[fa]=اینجا می‌توانید Kopete را شخصی کنید +Comment[fi]=Täällä voit yksilöllistää Kopetea +Comment[fr]=Vous pouvez personnaliser Kopete ici +Comment[ga]=Tig Leat Kopete a Oiriúnú Duit Féin Anseo +Comment[gl]=Aquí podes personaliza-lo comportamento de Kopete +Comment[he]=×›×ן ניתן להת××™× ×ישית ×ת Kopete +Comment[hi]=यहाठआप के-ऑपà¥à¤Ÿà¥€ को परà¥à¤¸à¤¨à¤²à¤¾à¤‡à¤œ कर सकते हैं +Comment[hr]=Ovde možete prilagoditi Kopete +Comment[hu]=Itt lehet megváltoztatni a Kopete működési jellemzÅ‘it +Comment[is]=Hér geturðu breytt Kopete +Comment[it]=Qui puoi personalizzare Kopete +Comment[ja]=ã“ã“㧠Kopete ã®æŒ™å‹•ã‚’カスタマイズã—ã¾ã™ +Comment[ka]=თქვენ áƒáƒ¥ შეგიძლიáƒáƒ— Kopete-ს მáƒáƒ áƒ’ებრ+Comment[kk]=Мұнда Kopete-Ñ‚Ñ– ыңғайлап алуға болады +Comment[km]=ទីនáŸáŸ‡ អ្នក​អាច​ážáž˜áŸ’រូវ Kopete ážáž¶áž˜â€‹áž”ុគ្គល +Comment[lt]=ÄŒia galite Kopete pritaikyti sau +Comment[mk]=Тука можете да го прилагодите Kopete на вашите желби +Comment[nb]=Sett dine egne innstillinger i Kopete +Comment[nds]=Hier kannst Du Kopete Dien Wennsten topassen +Comment[ne]=तपाईठयहाठकोपेट निजीकरण गरà¥à¤¨ सकà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤› +Comment[nl]=Hier kunt u Kopete naar wens instellen +Comment[nn]=Vel dine eigne innstillingar i Kopete +Comment[pl]=Tutaj możesz dostosować Kopete do osobistych preferencji +Comment[pt]=Aqui você pode personalizar o Kopete +Comment[pt_BR]=Agora você pode personalizar o Kopete +Comment[ro]=Aici puteÅ£i personaliza Kopete +Comment[ru]=ПерÑÐ¾Ð½Ð°Ð»ÑŒÐ½Ð°Ñ Ð½Ð°Ñтройка Kopete +Comment[se]=Dáppe sáhtát heivehit Kopete:a iežat hápmái +Comment[sk]=Tu môžete Kopete prispôsobiÅ¥ +Comment[sl]=Tukaj lahko nastavite Kopete +Comment[sr]=Овде можете прилагодити Kopete Ñеби +Comment[sr@Latn]=Ovde možete prilagoditi Kopete sebi +Comment[sv]=Här kan du göra personlig anpassning av Kopete +Comment[tg]=Дар Ин Ҷо Шумо Kopete-и Худро ШахÑÓ£ Карда Метавонед +Comment[tr]=Burada Kopete'yi KiÅŸiselleÅŸtirebilirsiniz +Comment[uk]=Тут можна наладнати Kopete під Ñебе +Comment[uz]=Bu yerda turli xususiyatlarni moslash mumkin +Comment[uz@cyrillic]=Бу ерда турли хуÑуÑиÑтларни моÑлаш мумкин +Comment[wa]=Chal vos ploz apontyî Kopete a vosse mode +Comment[zh_CN]=您å¯åœ¨æ­¤ä¸ªæ€§åŒ– Kopete +Comment[zh_HK]=在此您å¯è¨­å®š Kopete 的個人化資料 +Comment[zh_TW]=您已經在此將 Kopete 個人化 +DocPath=kopete/configure-dialog.html#configuring-behavior + + + diff --git a/kopete/kopete/config/behavior/kopeteawayconfigbase.ui b/kopete/kopete/config/behavior/kopeteawayconfigbase.ui new file mode 100644 index 00000000..8d0b8441 --- /dev/null +++ b/kopete/kopete/config/behavior/kopeteawayconfigbase.ui @@ -0,0 +1,356 @@ + +KopeteAwayConfigBaseUI + + + KopeteAwayConfigBaseUI + + + + 0 + 0 + 471 + 266 + + + + Away Configuration + + + + unnamed + + + + groupBox2 + + + GroupBoxPanel + + + Sunken + + + General + + + + unnamed + + + + textLabel1 + + + Number of away messages to remember: + + + Kopete will remember this many away messages for use at a later date; if this limit is exceeded, the least-used message will be removed. + + + Kopete will remember this many away messages for use at a later date; if this limit is exceeded, the least-used message will be removed. + + + + + rememberedMessages + + + 1 + + + 5 + + + Kopete will remember this many away messages for use at a later date; if this limit is exceeded, the least-used message will be removed. + + + Kopete will remember this many away messages for use at a later date; if this limit is exceeded, the least-used message will be removed. + + + + + spacer6 + + + Horizontal + + + Expanding + + + + 61 + 20 + + + + + + + + groupBox3 + + + Auto Away + + + <p>If you check the <i>Use auto away</i> checkbox, Kopete will automaticaly set you globaly away when the KDE screen saver start, or after the selected minutes of user inactivity (i.e no mouse move, or key pressed)</p> +<p>Kopete will set you available again when you come back if you checked <i>Become available when detecting activity again</i></p> + + + + unnamed + + + + mUseAutoAway + + + &Use auto away + + + + + layout3 + + + + unnamed + + + + TextLabel2 + + + false + + + Become away after + + + + + mAutoAwayTimeout + + + false + + + 999 + + + 1 + + + + + TextLabel3 + + + false + + + minutes of user inactivity + + + + + spacer7 + + + Horizontal + + + Expanding + + + + 81 + 20 + + + + + + + + mGoAvailable + + + false + + + Become available when detecting activity again + + + + + + + autoAwayMessageGroup + + + false + + + Auto Away Message + + + + unnamed + + + + mDisplayLastAwayMessage + + + false + + + Display the last away message used + + + true + + + + + mDisplayCustomAwayMessage + + + false + + + Display the following away message: + + + + + layout3 + + + + unnamed + + + + spacer5_2_2 + + + Horizontal + + + Fixed + + + + 16 + 16 + + + + + + mAutoAwayMessageEdit + + + false + + + + 3 + 0 + 0 + 0 + + + + + 300 + 0 + + + + + + + + + + spacer5 + + + Vertical + + + Expanding + + + + 20 + 30 + + + + + + + + mUseAutoAway + toggled(bool) + TextLabel2 + setEnabled(bool) + + + mUseAutoAway + toggled(bool) + mAutoAwayTimeout + setEnabled(bool) + + + mUseAutoAway + toggled(bool) + TextLabel3 + setEnabled(bool) + + + mUseAutoAway + toggled(bool) + mGoAvailable + setEnabled(bool) + + + mUseAutoAway + toggled(bool) + mDisplayLastAwayMessage + setEnabled(bool) + + + mUseAutoAway + toggled(bool) + mDisplayCustomAwayMessage + setEnabled(bool) + + + mDisplayCustomAwayMessage + toggled(bool) + mAutoAwayMessageEdit + setEnabled(bool) + + + mUseAutoAway + toggled(bool) + autoAwayMessageGroup + setEnabled(bool) + + + + mUseAutoAway + mAutoAwayTimeout + mGoAvailable + + + + klineedit.h + + diff --git a/kopete/kopete/config/identity/Makefile.am b/kopete/kopete/config/identity/Makefile.am new file mode 100644 index 00000000..3f0fa409 --- /dev/null +++ b/kopete/kopete/config/identity/Makefile.am @@ -0,0 +1,15 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(KOPETE_COMPAT_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kcm_kopete_identityconfig.la + +kcm_kopete_identityconfig_la_SOURCES = kopeteidentityconfigbase.ui \ + kopeteidentityconfig.cpp globalidentitiesmanager.cpp kopeteidentityconfigpreferences.kcfgc +kcm_kopete_identityconfig_la_LDFLAGS = -no-undefined -module $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_identityconfig_la_LIBADD = -lkabc ../../../libkopete/libkopete.la $(LIB_KOPETECOMPAT) $(LIB_KUTILS) + +service_DATA = kopete_identityconfig.desktop +servicedir = $(kde_servicesdir) + +# vim: set noet: +kde_kcfg_DATA = kopeteidentityconfigpreferences.kcfg diff --git a/kopete/kopete/config/identity/globalidentitiesmanager.cpp b/kopete/kopete/config/identity/globalidentitiesmanager.cpp new file mode 100644 index 00000000..192849e4 --- /dev/null +++ b/kopete/kopete/config/identity/globalidentitiesmanager.cpp @@ -0,0 +1,260 @@ +/* + globalidentitiesmanager.h - Kopete Global identities manager. + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2003-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#include "globalidentitiesmanager.h" + +// Qt includes +#include +#include +#include + +// KDE includes +#include +#include +#include +#include +#include + +// Kopete includes +#include "kopetecontact.h" +#include "kopetemetacontact.h" +#include "kopetecontactlist.h" +#include "kopetepluginmanager.h" + +class GlobalIdentitiesManager::Private +{ +public: + QMap identitiesList; +}; + +GlobalIdentitiesManager *GlobalIdentitiesManager::s_self = 0L; +GlobalIdentitiesManager *GlobalIdentitiesManager::self() +{ + if ( !s_self ) + s_self = new GlobalIdentitiesManager; + + return s_self; +} + +GlobalIdentitiesManager::GlobalIdentitiesManager(QObject *parent, const char *name) + : QObject(parent, name) +{ + d = new Private; +} + +GlobalIdentitiesManager::~GlobalIdentitiesManager() +{ + s_self = 0L; + + delete d; +} + +void GlobalIdentitiesManager::createNewIdentity(const QString &identityName) +{ + // Create new identity metacontact based on myself to get the sub-contacts. + Kopete::MetaContact *newIdentity = createNewMetaContact(); + + // Add to internal list. + d->identitiesList.insert(identityName, newIdentity); +} + +void GlobalIdentitiesManager::copyIdentity(const QString ©IdentityName, const QString &sourceIdentity) +{ + Kopete::MetaContact *copyIdentity = createCopyMetaContact(d->identitiesList[sourceIdentity]); + + d->identitiesList.insert(copyIdentityName, copyIdentity); +} + +void GlobalIdentitiesManager::renameIdentity(const QString &oldName, const QString &newName) +{ + Kopete::MetaContact *renamedIdentity = d->identitiesList[oldName]; + d->identitiesList.remove(oldName); + d->identitiesList.insert(newName, renamedIdentity); +} + +void GlobalIdentitiesManager::removeIdentity(const QString &removedIdentity) +{ + // Clear from memory the identity metacontact. + delete d->identitiesList[removedIdentity]; + // Remove from the list. + d->identitiesList.remove(removedIdentity); +} + +void GlobalIdentitiesManager::updateIdentity(const QString &updatedIdentity, Kopete::MetaContact *sourceMetaContact) +{ + copyMetaContact(d->identitiesList[updatedIdentity], sourceMetaContact); +} + +bool GlobalIdentitiesManager::isIdentityPresent(const QString &identityName) +{ + QMapIterator it; + QMapIterator end = d->identitiesList.end(); + + for(it = d->identitiesList.begin(); it != end; ++it) + { + if(it.key() == identityName) + { + // A entry with the same name was found. + return true; + } + } + return false; +} + +Kopete::MetaContact *GlobalIdentitiesManager::getIdentity(const QString &identityName) +{ + // Check if the identity is present. + return isIdentityPresent(identityName) ? d->identitiesList[identityName] : 0; +} + +void GlobalIdentitiesManager::loadXML() +{ + kdDebug() << k_funcinfo << "Loading global identities list from XML." << endl; + + QString filename = locateLocal( "appdata", QString::fromUtf8("global-identities.xml") ); + if( filename.isEmpty() ) + { + return; + } + + QDomDocument globalIdentitiesList( QString::fromUtf8( "kopete-global-identities-list" ) ); + + QFile globalIdentitiesListFile( filename ); + globalIdentitiesListFile.open( IO_ReadOnly ); + globalIdentitiesList.setContent( &globalIdentitiesListFile ); + + QDomElement list = globalIdentitiesList.documentElement(); + QDomElement element = list.firstChild().toElement(); + while( !element.isNull() ) + { + if( element.tagName() == QString::fromUtf8("identity") ) + { + Kopete::MetaContact *metaContact = createNewMetaContact(); + QString identityName = element.attribute(QString::fromUtf8("name")); + + if(!metaContact->fromXML(element)) + { + delete metaContact; + metaContact = 0L; + } + else + { + d->identitiesList.insert(identityName, metaContact); + } + } + element = element.nextSibling().toElement(); + } + + // If no identity are loaded, create a default identity MetaContact. + if(d->identitiesList.empty()) + { + createNewIdentity(i18n("Default Identity")); + } +} + +void GlobalIdentitiesManager::saveXML() +{ + kdDebug() << k_funcinfo << "Saving global identities list to XML." << endl; + + QString globalIdentitiesListFileName = locateLocal( "appdata", QString::fromUtf8("global-identities.xml") ); + KSaveFile globalIdentitiesListFile(globalIdentitiesListFileName); + if( globalIdentitiesListFile.status() == 0 ) + { + QTextStream *stream = globalIdentitiesListFile.textStream(); + stream->setEncoding( QTextStream::UnicodeUTF8 ); + toXML().save( *stream, 4 ); + + if ( globalIdentitiesListFile.close() ) + { + return; + } + else + { + kdDebug(14000) << k_funcinfo << "Failed to write global identities list, error code is: " << globalIdentitiesListFile.status() << endl; + } + } + else + { + kdWarning(14000) << k_funcinfo << "Couldn't open global identities list file " << globalIdentitiesListFileName + << ". Global Identities list not saved." << endl; + } +} + +const QDomDocument GlobalIdentitiesManager::toXML() +{ + QDomDocument doc; + + doc.appendChild(doc.createElement(QString::fromUtf8("kopete-global-identities-list"))); + + QMapIterator it; + QMapIterator end = d->identitiesList.end(); + for(it = d->identitiesList.begin(); it != end; ++it) + { + kdDebug(14000) << k_funcinfo << "Saving " << it.key() << endl; + QDomElement identityMetaContactElement = it.data()->toXML(true); // Save minimal information. + identityMetaContactElement.setTagName(QString::fromUtf8("identity")); + identityMetaContactElement.setAttribute(QString::fromUtf8("name"), it.key()); + doc.documentElement().appendChild(doc.importNode(identityMetaContactElement, true)); + } + + return doc; +} + +Kopete::MetaContact *GlobalIdentitiesManager::createNewMetaContact() +{ + Kopete::MetaContact *newMetaContact = new Kopete::MetaContact(); + QPtrList contactList = Kopete::ContactList::self()->myself()->contacts(); + + // Copy the contacts list to the new metacontact, so Kopete::Contact for SourceContact + // will not be null. + QPtrListIterator it( contactList); + for ( ; it.current(); ++it ) + { + newMetaContact->addContact(it.current()); + } + + newMetaContact->setDisplayNameSource(Kopete::MetaContact::SourceCustom); + newMetaContact->setPhotoSource(Kopete::MetaContact::SourceCustom); + + return newMetaContact; +} + +Kopete::MetaContact *GlobalIdentitiesManager::createCopyMetaContact(Kopete::MetaContact *source) +{ + Kopete::MetaContact *copyMetaContactObject = createNewMetaContact(); + + copyMetaContact(copyMetaContactObject, source); + + return copyMetaContactObject; +} + +void GlobalIdentitiesManager::copyMetaContact(Kopete::MetaContact *destination, Kopete::MetaContact *source) +{ + destination->setDisplayName(source->customDisplayName()); + destination->setDisplayNameSource(source->displayNameSource()); + destination->setDisplayNameSourceContact(source->displayNameSourceContact()); + + destination->setPhoto(source->customPhoto()); + destination->setPhotoSource(source->photoSource()); + destination->setPhotoSourceContact(source->photoSourceContact()); +} + +QMap GlobalIdentitiesManager::getGlobalIdentitiesList() +{ + return d->identitiesList; +} + +#include "globalidentitiesmanager.moc" diff --git a/kopete/kopete/config/identity/globalidentitiesmanager.h b/kopete/kopete/config/identity/globalidentitiesmanager.h new file mode 100644 index 00000000..c188d497 --- /dev/null +++ b/kopete/kopete/config/identity/globalidentitiesmanager.h @@ -0,0 +1,143 @@ +/* + globalidentitiesmanager.h - Kopete Global identities manager. + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2003-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef GLOBALIDENTITIESMANAGER_H +#define GLOBALIDENTITIESMANAGER_H + +#include +#include + +namespace Kopete +{ + class MetaContact; +} +class QDomDocument; + +/** + * This singleton class handle the loading, saving and manipulating of all the global identities from a XML file. + * It also hold the pointer list of metacontacts. + * Use this class with GlobalIdentitiesManager::self() + * + * @author Michaël Larouche +*/ +class GlobalIdentitiesManager : public QObject +{ + Q_OBJECT +public: + /** + * @brief Return the single instance of GlobalIdentitiesManager class + * + * The global identities manager is a singleton class of which only + * a single instance will exist. If no manager exists yet, this method + * create one for you. + * + * @return The single instance of GlobalIdentitiesManager class. + * FIXME: Should I remove the singleton pattern ? + */ + static GlobalIdentitiesManager* self(); + ~GlobalIdentitiesManager(); + + /** + * @brief Create a new identity and add it to the internal list. + */ + void createNewIdentity(const QString &identityName); + + /** + * @brief Copy a identity + * + * @param copyIdentityName Name for the copy identity. + * @param sourceIdentity Name of the source identity. + */ + void copyIdentity(const QString ©IdentityName, const QString &sourceIdentity); + + /** + * @brief Rename a identity + * + * @param oldName Identity to rename. + * @param newName New identity name. + */ + void renameIdentity(const QString &oldName, const QString &newName); + + /** + * @brief Delete identity + * + * @param removedIdentity Identity name to remove. + */ + void removeIdentity(const QString &removedIdentity); + + /** + * @brief Update the specified identity using the source MetaContact + * + * @param updatedIdentity Identity to update. + * @param sourceMetaContact Source of data. + */ + void updateIdentity(const QString &updatedIdentity, Kopete::MetaContact *sourceMetaContact); + + /** + * @brief Check if the specified identityName exists. + * + * This is a helper method to avoid duplicated entries. + * @return if the identityName is in the internal list. + */ + bool isIdentityPresent(const QString &identityName); + + /** + * @brief Return the specified identity. + * + * @param identityName Identity to retrive. + * @return Identity data as Kopete::MetaContact. + */ + Kopete::MetaContact *getIdentity(const QString &identityName); + + /** + * @brief Load the XML file where global identities metacontacts are stored. + */ + void loadXML(); + + /** + * @brief Save the global identities metacontacts to XML file. + */ + void saveXML(); + + /** + * @brief Return the list of global identities metacontact. + * @return The pointer list of metacontact as QValueList + */ + QMap getGlobalIdentitiesList(); + +private: + GlobalIdentitiesManager(QObject *parent = 0, const char *name = 0); + + /** + * @brief Return a XML representation of the global identities list. + * + * @return the XML represention as QDomDocument. + */ + const QDomDocument toXML(); + + Kopete::MetaContact *createNewMetaContact(); + Kopete::MetaContact *createCopyMetaContact(Kopete::MetaContact *source); + void copyMetaContact(Kopete::MetaContact *destination, Kopete::MetaContact *source); + +private: + static GlobalIdentitiesManager *s_self; + class Private; + Private *d; + +}; + +#endif diff --git a/kopete/kopete/config/identity/kopete_identityconfig.desktop b/kopete/kopete/config/identity/kopete_identityconfig.desktop new file mode 100644 index 00000000..6b655550 --- /dev/null +++ b/kopete/kopete/config/identity/kopete_identityconfig.desktop @@ -0,0 +1,112 @@ +[Desktop Entry] +Icon=identity +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_identityconfig +X-KDE-FactoryName=KopeteIdentityConfigFactory +X-KDE-ParentApp=kopete +X-KDE-ParentComponents=kopete + +Name=Identity +Name[be]=ÐŸÑ€Ñ‹Ð²Ð°Ñ‚Ð½Ñ‹Ñ Ð·Ð²ÐµÑткі +Name[bg]=Ð˜Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ +Name[bn]=পরিচয় +Name[br]=Anvelezh +Name[bs]=Identitet +Name[ca]=Identitat +Name[cs]=Identita +Name[cy]=Dynodiad +Name[da]=Identitet +Name[de]=Identität +Name[el]=Ταυτότητα +Name[eo]=Idento +Name[es]=Identidad +Name[et]=Identiteet +Name[eu]=Identitatea +Name[fa]=هویت +Name[fi]=Identiteetti +Name[fr]=Identité +Name[ga]=Aitheantas +Name[gl]=Indentidade +Name[he]=חשבון +Name[hu]=Azonosító +Name[is]=Auðkenni +Name[it]=Identità +Name[ja]=分身 +Name[ka]=პრáƒáƒ¤áƒ˜áƒšáƒ˜ +Name[kk]=Профилі +Name[km]=អážáŸ’ážážŸáž‰áŸ’ញាណ +Name[lt]=TapatybÄ— +Name[nb]=Identitet +Name[nds]=Identiteet +Name[ne]=परिचय +Name[nl]=Identiteit +Name[nn]=Identitet +Name[pl]=Tożsamość +Name[pt]=Identidade +Name[pt_BR]=Identidade +Name[ru]=Профиль +Name[sk]=Identita +Name[sl]=Identiteta +Name[sr]=Идентитет +Name[sr@Latn]=Identitet +Name[sv]=Identitet +Name[tr]=Kimlik +Name[uk]=Профіль +Name[uz]=Shaxsiyat +Name[uz@cyrillic]=ШахÑиÑÑ‚ +Name[zh_CN]=身份 +Name[zh_HK]=識別å稱 +Name[zh_TW]=身份 +Comment=Here You Can Manage Your Global Identity +Comment[be]=Ð“Ð»Ð°Ð±Ð°Ð»ÑŒÐ½Ñ‹Ñ Ð¿Ñ€Ñ‹Ð²Ð°Ñ‚Ð½Ñ‹Ñ Ð·Ð²ÐµÑткі +Comment[bg]=ÐаÑтройване на идентификациÑта +Comment[bn]=আপনি à¦à¦–ানে আপনার বিশà§à¦¬ পরিচয় পরিচালনা করতে পারেন +Comment[bs]=Ovdje možete upravljati svojim globalnim identiteom +Comment[ca]=Aquí podreu gestionar globalment la vostra identitat +Comment[cs]=Zde můžete spravovat svou globální identitu +Comment[da]=Her kan du hÃ¥ndtere din globale identitet +Comment[de]=Hier können Sie Ihre globale Identität verwalten +Comment[el]=Εδώ μποÏείτε να χειÏιστείτε την καθολική σας ταυτότητα +Comment[es]=Aquí puede gestionar su identidad global +Comment[et]=Siin saad hallata oma globaalset identiteeti +Comment[eu]=Hemen zure identitate globala kudea dezakezu +Comment[fa]=اینجا می‌توانید هویت سراسری خود را مدیریت کنید +Comment[fi]=Täältä voit hallita globaalia identiteettiäsi +Comment[fr]=Vous pouvez gérer ici votre identité principale +Comment[ga]=Tig Leat d'Aitheantas a Láimhseáil Anseo +Comment[gl]=Aquí podes xestionar a súa identidade global +Comment[he]=×›×ן ב×פשרותך לנהל ×ת כל חשבונותיך +Comment[hu]=Itt lehet kezelni a globális azonosítókat +Comment[is]=Hér geturðu haldið utan um víðværa auðkennið þitt +Comment[it]=Qui puoi gestire la tua identità globale +Comment[ja]=ã“ã“ã§ã‚¢ã‚«ã‚¦ãƒ³ãƒˆå…±é€šã®ã‚ãªãŸã®åˆ†èº«ã‚’設定ã—ã¾ã™ +Comment[ka]=áƒáƒ¥ თქვენ თქვენი ზáƒáƒ’áƒáƒ“ი იდენტიფიკáƒáƒªáƒ˜áƒ˜áƒ¡ გáƒáƒ›áƒáƒ áƒ—ვრ+Comment[kk]=Мұнда жалпы профилін баÑқара алаÑыз +Comment[km]=ទីនáŸáŸ‡ អ្នក​អាច​គ្រប់គ្រង​អážáŸ’ážážŸáž‰áŸ’ញាណ​សកល​របស់​អ្នក +Comment[lt]=ÄŒia galite tvarkyti globaliÄ…jÄ… paskyrÄ… +Comment[nb]=Her kan du behandle din globale identitet +Comment[nds]=Hier kannst Du Dien globale Identiteet plegen +Comment[ne]=यहाठतपाईठतपाईà¤à¤•à¥‹ विशà¥à¤µà¤µà¥à¤¯à¤¾à¤ªà¥€ परिचय पà¥à¤°à¤¬à¤¨à¥à¤§ गरà¥à¤¨ सकà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤› +Comment[nl]=Hier kunt u uw globale identiteit beheren +Comment[nn]=Her kan du handtera den globale identiteten din +Comment[pl]=Tutaj można zarzÄ…dzać TwojÄ… tożsamoÅ›ciÄ… +Comment[pt]=Aqui Você Pode Gerir a Sua Identidade Global +Comment[pt_BR]=Aqui Você Pode Gerenciar Sua Identidade Global +Comment[ru]=ÐаÑтройка глобального Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ +Comment[sk]=Tu môžete spravovaÅ¥ VaÅ¡u globálnu identifikáciu +Comment[sl]=Tukaj lahko upravljate s svojo globalno identiteto +Comment[sr]=Овде можете управљати вашим општим идентитетом +Comment[sr@Latn]=Ovde možete upravljati vaÅ¡im opÅ¡tim identitetom +Comment[sv]=Här kan du hantera din generella identitet +Comment[tr]=Bütün Genel Kimliklerinizi Buradan Yönetebilirsiniz +Comment[uk]=Тут можна керувати вашим глобальним профілем +Comment[uz]=Bu yerda umumiy shaxsiyatni moslash mumkin +Comment[uz@cyrillic]=Бу ерда умумий шахÑиÑтни моÑлаш мумкин +Comment[zh_CN]=您å¯åœ¨æ­¤ç®¡ç†æ‚¨çš„全局身份 +Comment[zh_HK]=在此您å¯ç®¡ç† Kopete 的全域識別å稱 +Comment[zh_TW]=您å¯ä»¥åœ¨æ­¤ç®¡ç†æ‚¨çš„全域身份 + +DocPath=kopete/configure-dialog.html#configuring-identity diff --git a/kopete/kopete/config/identity/kopeteidentityconfig.cpp b/kopete/kopete/config/identity/kopeteidentityconfig.cpp new file mode 100644 index 00000000..26613b87 --- /dev/null +++ b/kopete/kopete/config/identity/kopeteidentityconfig.cpp @@ -0,0 +1,636 @@ +/* + kopeteidentityconfig.cpp - Kopete Identity config page + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2003-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopeteidentityconfig.h" + +// Qt includes +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE KIO includes +#include + +// KDE KABC(AddressBook) includes +#include +#include +#include + +// Kopete include +#include "kabcpersistence.h" +#include "kopeteglobal.h" +#include "kopeteaccountmanager.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopetecontact.h" +#include "kopetecontactlist.h" +#include "addressbookselectordialog.h" +#include "kopeteconfig.h" + +// Local includes +#include "kopeteidentityconfigbase.h" +#include "globalidentitiesmanager.h" +#include "kopeteidentityconfigpreferences.h" + +class KopeteIdentityConfig::Private +{ +public: + Private() : m_view(0L), myself(0L), currentIdentity(0L), selectedIdentity("") + {} + + KopeteIdentityConfigBase *m_view; + Kopete::MetaContact *myself; + Kopete::MetaContact *currentIdentity; + + QMap contactPhotoSourceList; + QString selectedIdentity; +}; + +typedef KGenericFactory KopeteIdentityConfigFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_identityconfig, KopeteIdentityConfigFactory( "kcm_kopete_identityconfig" ) ) + +KopeteIdentityConfig::KopeteIdentityConfig(QWidget *parent, const char */*name*/, const QStringList &args) : KCModule( KopeteIdentityConfigFactory::instance(), parent, args) +{ + d = new Private; + ( new QVBoxLayout( this ) )->setAutoAdd( true ); + d->m_view = new KopeteIdentityConfigBase( this, "KopeteIdentityConfig::m_view" ); + + // Setup KConfigXT link with GUI. + addConfig( Kopete::Config::self(), d->m_view ); + + // Load config + KopeteIdentityConfigPreferences::self()->readConfig(); + + // Load from XML the identities. + GlobalIdentitiesManager::self()->loadXML(); + + d->myself = Kopete::ContactList::self()->myself(); + + // Set the latest selected Identity. + d->selectedIdentity = KopeteIdentityConfigPreferences::self()->selectedIdentity(); + kdDebug() << k_funcinfo << "Latest loaded identity: " << d->selectedIdentity << endl; + + // If the latest selected Identity is not present anymore, use a fallback identity. + if( !GlobalIdentitiesManager::self()->isIdentityPresent(d->selectedIdentity) ) + { + QMapIterator it = GlobalIdentitiesManager::self()->getGlobalIdentitiesList().begin(); + d->selectedIdentity = it.key(); + } + else + { + // Update the latest identity with myself Metacontact. + GlobalIdentitiesManager::self()->updateIdentity(d->selectedIdentity, d->myself); + } + d->currentIdentity = GlobalIdentitiesManager::self()->getIdentity(d->selectedIdentity); + + // Set icon for KPushButton + d->m_view->buttonNewIdentity->setIconSet(SmallIconSet("new")); + d->m_view->buttonCopyIdentity->setIconSet(SmallIconSet("editcopy")); + d->m_view->buttonRenameIdentity->setIconSet(SmallIconSet("edit")); + d->m_view->buttonRemoveIdentity->setIconSet(SmallIconSet("delete_user")); + d->m_view->buttonClearPhoto->setIconSet( SmallIconSet( QApplication::reverseLayout() ? "locationbar_erase" : "clear_left" ) ); + + load(); // Load Configuration + + // Action signal/slots + connect(d->m_view->buttonChangeAddressee, SIGNAL(clicked()), this, SLOT(slotChangeAddressee())); + connect(d->m_view->comboSelectIdentity, SIGNAL(activated(const QString &)), this, SLOT(slotUpdateCurrentIdentity(const QString& ))); + connect(d->m_view->buttonNewIdentity, SIGNAL(clicked()), this, SLOT(slotNewIdentity())); + connect(d->m_view->buttonCopyIdentity, SIGNAL(clicked()), this, SLOT(slotCopyIdentity())); + connect(d->m_view->buttonRenameIdentity, SIGNAL(clicked()), this, SLOT(slotRenameIdentity())); + connect(d->m_view->buttonRemoveIdentity, SIGNAL(clicked()), this, SLOT(slotRemoveIdentity())); + connect(d->m_view->comboPhotoURL, SIGNAL(urlSelected(const QString& )), this, SLOT(slotChangePhoto(const QString& ))); + connect(d->m_view->buttonClearPhoto, SIGNAL(clicked()), this, SLOT(slotClearPhoto())); + + // Settings signal/slots + connect(d->m_view->radioNicknameContact, SIGNAL(toggled(bool )), this, SLOT(slotEnableAndDisableWidgets())); + connect(d->m_view->radioNicknameCustom, SIGNAL(toggled(bool )), this, SLOT(slotEnableAndDisableWidgets())); + connect(d->m_view->radioNicknameKABC, SIGNAL(toggled(bool )), this, SLOT(slotEnableAndDisableWidgets())); + + connect(d->m_view->radioPhotoContact, SIGNAL(toggled(bool )), this, SLOT(slotEnableAndDisableWidgets())); + connect(d->m_view->radioPhotoCustom, SIGNAL(toggled(bool )), this, SLOT(slotEnableAndDisableWidgets())); + connect(d->m_view->radioPhotoKABC, SIGNAL(toggled(bool )), this, SLOT(slotEnableAndDisableWidgets())); + + connect(d->m_view->checkSyncPhotoKABC, SIGNAL(toggled(bool )), this, SLOT(slotSettingsChanged())); + connect(d->m_view->lineNickname, SIGNAL(textChanged(const QString& )), this, SLOT(slotSettingsChanged())); + connect(d->m_view->comboNameContact, SIGNAL(activated(int )), this, SLOT(slotSettingsChanged())); + connect(d->m_view->comboPhotoContact, SIGNAL(activated(int )), this, SLOT(slotEnableAndDisableWidgets())); +} + +KopeteIdentityConfig::~KopeteIdentityConfig() +{ + delete d; +} + +void KopeteIdentityConfig::load() +{ + KCModule::load(); + + // Populate the select Identity combo box. + loadIdentities(); + // Populate the name contact ComboBox + slotLoadNameSources(); + // Populate the photo contact ComboBOx + slotLoadPhotoSources(); + + KABC::Addressee a = KABC::StdAddressBook::self()->whoAmI(); + // Load the address book link + if (!a.isEmpty()) + { + d->m_view->lineAddressee->setText(a.realName()); + } + + slotEnableAndDisableWidgets(); +} + +void KopeteIdentityConfig::save() +{ + KCModule::save(); + + saveCurrentIdentity(); + + // Don't save the new global identity if it's not activated. + if(d->m_view->kcfg_EnableGlobalIdentity->isChecked()) + { + // Save the myself metacontact settings. + // Nickname settings. + if(d->m_view->lineNickname->text() != d->myself->customDisplayName()) + d->myself->setDisplayName(d->m_view->lineNickname->text()); + + d->myself->setDisplayNameSource(selectedNameSource()); + d->myself->setDisplayNameSourceContact(selectedNameSourceContact()); + + // Photo settings + d->myself->setPhotoSource(selectedPhotoSource()); + d->myself->setPhotoSourceContact(selectedPhotoSourceContact()); + if(!d->m_view->comboPhotoURL->url().isEmpty()) + d->myself->setPhoto(d->m_view->comboPhotoURL->url()); + else + d->myself->setPhoto( KURL() ); + d->myself->setPhotoSyncedWithKABC(d->m_view->checkSyncPhotoKABC->isChecked()); + } + + // Save global identities list. + KopeteIdentityConfigPreferences::self()->setSelectedIdentity(d->selectedIdentity); + GlobalIdentitiesManager::self()->saveXML(); + + // (Re)made slot connections to apply Global Identity in protocols + Kopete::ContactList::self()->loadGlobalIdentity(); + + load(); +} + +void KopeteIdentityConfig::loadIdentities() +{ + d->m_view->comboSelectIdentity->clear(); + + QMap identitiesList = GlobalIdentitiesManager::self()->getGlobalIdentitiesList(); + QMapIterator it; + QMapIterator end = identitiesList.end(); + + int count=0, selectedIndex=0; + for(it = identitiesList.begin(); it != end; ++it) + { + d->m_view->comboSelectIdentity->insertItem(it.key()); + if(it.key() == d->selectedIdentity) + { + selectedIndex = count; + } + count++; + } + + d->m_view->comboSelectIdentity->setCurrentItem(selectedIndex); + d->m_view->buttonRemoveIdentity->setEnabled(count == 1 ? false : true); +} + +void KopeteIdentityConfig::saveCurrentIdentity() +{ + kdDebug() << k_funcinfo << "Saving data of current identity." << endl; + // Ignore saving when removing a identity + if(!d->currentIdentity) + return; + + if(d->m_view->lineNickname->text() != d->currentIdentity->customDisplayName()) + d->currentIdentity->setDisplayName(d->m_view->lineNickname->text()); + + d->currentIdentity->setDisplayNameSource(selectedNameSource()); + d->currentIdentity->setDisplayNameSourceContact(selectedNameSourceContact()); + + // Photo settings + d->currentIdentity->setPhotoSource(selectedPhotoSource()); + d->currentIdentity->setPhotoSourceContact(selectedPhotoSourceContact()); + if(!d->m_view->comboPhotoURL->url().isEmpty()) + d->currentIdentity->setPhoto(d->m_view->comboPhotoURL->url()); + else + d->currentIdentity->setPhoto( KURL() ); + d->currentIdentity->setPhotoSyncedWithKABC(d->m_view->checkSyncPhotoKABC->isChecked()); +} + +void KopeteIdentityConfig::slotLoadNameSources() +{ + Kopete::Contact *nameSourceContact = d->currentIdentity->displayNameSourceContact(); + + QPtrList contactList = d->myself->contacts(); // Use myself contact PtrList. Safer. + QPtrListIterator it(contactList); + + d->m_view->comboNameContact->clear(); + + for(; it.current(); ++it) + { + QString account = it.current()->property(Kopete::Global::Properties::self()->nickName()).value().toString() + " <" + it.current()->contactId() + ">"; + QPixmap accountIcon = it.current()->account()->accountIcon(); + d->m_view->comboNameContact->insertItem(accountIcon, account); + + // Select this item if it's the one we're tracking. + if(it.current() == nameSourceContact) + { + d->m_view->comboNameContact->setCurrentItem(d->m_view->comboNameContact->count() - 1); + } + } + + d->m_view->lineNickname->setText(d->currentIdentity->customDisplayName()); + + Kopete::MetaContact::PropertySource nameSource = d->currentIdentity->displayNameSource(); + + d->m_view->radioNicknameCustom->setChecked(nameSource == Kopete::MetaContact::SourceCustom); + d->m_view->radioNicknameKABC->setChecked(nameSource == Kopete::MetaContact::SourceKABC); + d->m_view->radioNicknameContact->setChecked(nameSource == Kopete::MetaContact::SourceContact); +} + +void KopeteIdentityConfig::slotLoadPhotoSources() +{ + Kopete::Contact *photoSourceContact = d->currentIdentity->photoSourceContact(); + + QPtrList contactList = d->myself->contacts(); // Use myself contact PtrList. Safer. + QPtrListIterator it(contactList); + + d->m_view->comboPhotoContact->clear(); + d->m_view->comboPhotoURL->clear(); + d->contactPhotoSourceList.clear(); + + for(; it.current(); ++it) + { + Kopete::Contact *currentContact = it.current(); + if(currentContact->hasProperty(Kopete::Global::Properties::self()->photo().key())) + { + QString account = currentContact->property(Kopete::Global::Properties::self()->nickName()).value().toString() + " <" + currentContact->contactId() + ">"; + QPixmap accountIcon = currentContact->account()->accountIcon(); + + d->m_view->comboPhotoContact->insertItem(accountIcon, account); + d->contactPhotoSourceList.insert(d->m_view->comboPhotoContact->count() - 1, currentContact); + + // Select this item if it's the one we're tracking. + if(currentContact == photoSourceContact) + { + d->m_view->comboPhotoContact->setCurrentItem(d->m_view->comboPhotoContact->count() - 1); + } + } + } + + d->m_view->comboPhotoURL->setURL(d->currentIdentity->customPhoto().pathOrURL()); + Kopete::MetaContact::PropertySource photoSource = d->currentIdentity->photoSource(); + + d->m_view->radioPhotoCustom->setChecked(photoSource == Kopete::MetaContact::SourceCustom); + d->m_view->radioPhotoContact->setChecked(photoSource == Kopete::MetaContact::SourceContact); + d->m_view->radioPhotoKABC->setChecked(photoSource == Kopete::MetaContact::SourceKABC); + + d->m_view->checkSyncPhotoKABC->setChecked(d->currentIdentity->isPhotoSyncedWithKABC()); +} + +void KopeteIdentityConfig::slotEnableAndDisableWidgets() +{ + KABC::Addressee a = KABC::StdAddressBook::self()->whoAmI(); + bool hasKABCLink = !a.isEmpty(); + + d->m_view->radioNicknameKABC->setEnabled(hasKABCLink); + d->m_view->radioPhotoKABC->setEnabled(hasKABCLink); + + // Don't sync global photo with KABC if KABC is the source + // or if they are no KABC link. (would create a break in timeline) + if( selectedPhotoSource() == Kopete::MetaContact::SourceKABC || !hasKABCLink ) + { + d->m_view->checkSyncPhotoKABC->setEnabled(false); + } + else + { + d->m_view->checkSyncPhotoKABC->setEnabled(true); + } + + d->m_view->radioNicknameContact->setEnabled(d->currentIdentity->contacts().count()); + d->m_view->radioPhotoContact->setEnabled(!d->contactPhotoSourceList.isEmpty()); + + d->m_view->comboNameContact->setEnabled(selectedNameSource() == Kopete::MetaContact::SourceContact); + d->m_view->lineNickname->setEnabled(selectedNameSource() == Kopete::MetaContact::SourceCustom); + + d->m_view->comboPhotoContact->setEnabled(selectedPhotoSource() == Kopete::MetaContact::SourceContact); + d->m_view->comboPhotoURL->setEnabled(selectedPhotoSource() == Kopete::MetaContact::SourceCustom); + + if(d->contactPhotoSourceList.isEmpty() ) + { + d->m_view->comboPhotoContact->clear(); + d->m_view->comboPhotoContact->insertItem(i18n("No Contacts with Photo Support")); + d->m_view->comboPhotoContact->setEnabled(false); + } + + QImage photo; + switch ( selectedPhotoSource() ) + { + case Kopete::MetaContact::SourceKABC: + photo = Kopete::photoFromKABC(a.uid()); + break; + case Kopete::MetaContact::SourceContact: + photo = Kopete::photoFromContact(selectedNameSourceContact()); + break; + case Kopete::MetaContact::SourceCustom: + photo = QImage(d->m_view->comboPhotoURL->url()); + break; + } + + if(!photo.isNull()) + d->m_view->labelPhoto->setPixmap(QPixmap(photo.smoothScale(64, 92, QImage::ScaleMin))); + else + d->m_view->labelPhoto->setPixmap(QPixmap()); + + emit changed(true); +} + +void KopeteIdentityConfig::slotUpdateCurrentIdentity(const QString &selectedIdentity) +{ + kdDebug() << k_funcinfo << "Updating current identity." << endl; + + // Save the current identity detail, so we don't loose information. + saveCurrentIdentity(); + + // Change the current identity reflecting the combo box. + d->selectedIdentity = selectedIdentity; + d->currentIdentity = GlobalIdentitiesManager::self()->getIdentity(d->selectedIdentity); + KopeteIdentityConfigPreferences::self()->setSelectedIdentity(d->selectedIdentity); + KopeteIdentityConfigPreferences::self()->writeConfig(); + // Save global identities list. + GlobalIdentitiesManager::self()->saveXML(); + + // Reload the details. + slotLoadNameSources(); + slotLoadPhotoSources(); +} + +void KopeteIdentityConfig::slotNewIdentity() +{ + bool ok; + QString newIdentityName = KInputDialog::getText(i18n("New Identity"), i18n("Identity name:") , QString::null , &ok); + + if(newIdentityName.isEmpty() || !ok) + return; + + + GlobalIdentitiesManager::self()->createNewIdentity(newIdentityName); + + slotUpdateCurrentIdentity(newIdentityName); + loadIdentities(); +} + +void KopeteIdentityConfig::slotCopyIdentity() +{ + bool ok; + QString copyName = KInputDialog::getText(i18n("Copy Identity"), i18n("Identity name:") , QString::null, &ok); + + if(copyName.isEmpty() || !ok) + return; + + + if(!GlobalIdentitiesManager::self()->isIdentityPresent(copyName)) + { + GlobalIdentitiesManager::self()->copyIdentity(copyName, d->selectedIdentity); + + slotUpdateCurrentIdentity(copyName); + loadIdentities(); + } + else + { + KMessageBox::error(this, i18n("An identity with the same name was found."), i18n("Identity Configuration")); + } +} + +void KopeteIdentityConfig::slotRenameIdentity() +{ + if(d->selectedIdentity.isNull()) + return; + + bool ok; + QString renamedName = KInputDialog::getText(i18n("Rename Identity"), i18n("Identity name:") , d->selectedIdentity, &ok); + + if(renamedName.isEmpty() || !ok) + return; + + + if(renamedName.isEmpty()) + return; + + if(!GlobalIdentitiesManager::self()->isIdentityPresent(renamedName)) + { + GlobalIdentitiesManager::self()->renameIdentity(d->selectedIdentity, renamedName); + + slotUpdateCurrentIdentity(renamedName); + loadIdentities(); + } + else + { + KMessageBox::error(this, i18n("An identity with the same name was found."), i18n("Identity Configuration")); + } +} + +void KopeteIdentityConfig::slotRemoveIdentity() +{ + kdDebug() << k_funcinfo << "Removing current identity." << endl; + GlobalIdentitiesManager::self()->removeIdentity(d->selectedIdentity); + // Reset the currentIdentity pointer. The currentIdentity object was deleted in GlobalIdentitiesManager. + d->currentIdentity = 0; + + // Select the entry before(or after) the removed identity. + int currentItem = d->m_view->comboSelectIdentity->currentItem(); + // Use the next item if the removed identity is the first in the comboBox. + if(currentItem - 1 < 0) + { + currentItem++; + } + else + { + currentItem--; + } + d->m_view->comboSelectIdentity->setCurrentItem(currentItem); + + slotUpdateCurrentIdentity(d->m_view->comboSelectIdentity->currentText()); + loadIdentities(); +} + + + +void KopeteIdentityConfig::slotChangeAddressee() +{ + KABC::Addressee a = Kopete::UI::AddressBookSelectorDialog::getAddressee(i18n("Addressbook Association"), i18n("Choose the person who is yourself."), d->myself->metaContactId(), this); + + if ( !a.isEmpty() ) + { + d->m_view->lineAddressee->setText(a.realName()); + KABC::StdAddressBook::self()->setWhoAmI(a); + d->myself->setMetaContactId(a.uid()); + } + + emit changed(true); +} + +void KopeteIdentityConfig::slotChangePhoto(const QString &photoUrl) +{ + QString saveLocation; + + QImage photo(photoUrl); + // use KABC photo size 100x140 + photo = KPixmapRegionSelectorDialog::getSelectedImage( QPixmap(photo), 96, 96, this ); + + if(!photo.isNull()) + { + if(photo.width() > 96 || photo.height() > 96) + { + // Scale and crop the picture. + photo = photo.smoothScale( 96, 96, QImage::ScaleMin ); + // crop image if not square + if(photo.width() < photo.height()) + photo = photo.copy((photo.width()-photo.height())/2, 0, 96, 96); + else if (photo.width() > photo.height()) + photo = photo.copy(0, (photo.height()-photo.width())/2, 96, 96); + + } + else if (photo.width() < 32 || photo.height() < 32) + { + // Scale and crop the picture. + photo = photo.smoothScale( 32, 32, QImage::ScaleMin ); + // crop image if not square + if(photo.width() < photo.height()) + photo = photo.copy((photo.width()-photo.height())/2, 0, 32, 32); + else if (photo.width() > photo.height()) + photo = photo.copy(0, (photo.height()-photo.width())/2, 32, 32); + + } + else if (photo.width() != photo.height()) + { + if(photo.width() < photo.height()) + photo = photo.copy((photo.width()-photo.height())/2, 0, photo.height(), photo.height()); + else if (photo.width() > photo.height()) + photo = photo.copy(0, (photo.height()-photo.width())/2, photo.height(), photo.height()); + } + + // Use MD5 hash to save the filename, so no problems will occur with the filename because of non-ASCII characters. + // Bug 124175: My personnal picture doesn't appear cause of l10n + QByteArray tempArray; + QBuffer tempBuffer(tempArray); + tempBuffer.open( IO_WriteOnly ); + photo.save(&tempBuffer, "PNG"); + KMD5 context(tempArray); + // Save the image to a file. + saveLocation = context.hexDigest() + ".png"; + saveLocation = locateLocal( "appdata", QString::fromUtf8("globalidentitiespictures/%1").arg( saveLocation ) ); + + if(!photo.save(saveLocation, "PNG")) + { + KMessageBox::sorry(this, + i18n("An error occurred when trying to save the custom photo for %1 identity.").arg(d->selectedIdentity), + i18n("Identity Configuration")); + } + d->m_view->comboPhotoURL->setURL(saveLocation); + slotEnableAndDisableWidgets(); + } + else + { + KMessageBox::sorry(this, + i18n("An error occurred when trying to save the custom photo for %1 identity.").arg(d->selectedIdentity), + i18n("Identity Configuration")); + } +} + +void KopeteIdentityConfig::slotClearPhoto() +{ + d->m_view->comboPhotoURL->setURL( QString::null ); + slotEnableAndDisableWidgets(); +} + +void KopeteIdentityConfig::slotSettingsChanged() +{ + emit changed(true); +} + +Kopete::MetaContact::PropertySource KopeteIdentityConfig::selectedNameSource() const +{ + if (d->m_view->radioNicknameKABC->isChecked()) + return Kopete::MetaContact::SourceKABC; + if (d->m_view->radioNicknameContact->isChecked()) + return Kopete::MetaContact::SourceContact; + if (d->m_view->radioNicknameCustom->isChecked()) + return Kopete::MetaContact::SourceCustom; + else + return Kopete::MetaContact::SourceCustom; +} + +Kopete::MetaContact::PropertySource KopeteIdentityConfig::selectedPhotoSource() const +{ + if (d->m_view->radioPhotoKABC->isChecked()) + return Kopete::MetaContact::SourceKABC; + if (d->m_view->radioPhotoContact->isChecked()) + return Kopete::MetaContact::SourceContact; + if (d->m_view->radioPhotoCustom->isChecked()) + return Kopete::MetaContact::SourceCustom; + else + return Kopete::MetaContact::SourceCustom; +} + +Kopete::Contact* KopeteIdentityConfig::selectedNameSourceContact() const +{ + Kopete::Contact *c = d->myself->contacts().at(d->m_view->comboNameContact->currentItem()); + return c ? c : 0L; +} + +Kopete::Contact* KopeteIdentityConfig::selectedPhotoSourceContact() const +{ + if (d->contactPhotoSourceList.isEmpty()) + return 0L; + + Kopete::Contact *c = d->contactPhotoSourceList[d->m_view->comboPhotoContact->currentItem()]; + return c ? c : 0L; +} + +#include "kopeteidentityconfig.moc" diff --git a/kopete/kopete/config/identity/kopeteidentityconfig.h b/kopete/kopete/config/identity/kopeteidentityconfig.h new file mode 100644 index 00000000..f18c5a8c --- /dev/null +++ b/kopete/kopete/config/identity/kopeteidentityconfig.h @@ -0,0 +1,80 @@ +/* + kopeteidentityconfig.h - Kopete identity config page + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2003-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef _IDENTITYCONFIG_H +#define _IDENTITYCONFIG_H + +#include +#include + +#include "kopetemetacontact.h" + +namespace Kopete +{ +class Contact; +} + +class KopeteIdentityConfigBase; + +/** + * @author Michaël Larouche + */ +class KopeteIdentityConfig : public KCModule +{ + Q_OBJECT +public: + KopeteIdentityConfig(QWidget *parent, const char *name, const QStringList &args ); + ~KopeteIdentityConfig(); + +public slots: + virtual void save(); + virtual void load(); + +private: + void loadIdentities(); + void saveCurrentIdentity(); + + Kopete::MetaContact::PropertySource selectedNameSource() const; + Kopete::MetaContact::PropertySource selectedPhotoSource() const; + Kopete::Contact* selectedNameSourceContact() const; + Kopete::Contact* selectedPhotoSourceContact() const; + +private slots: + void slotLoadNameSources(); + void slotLoadPhotoSources(); + void slotEnableAndDisableWidgets(); + + void slotUpdateCurrentIdentity(const QString &selectedIdentity); + void slotNewIdentity(); + void slotCopyIdentity(); + void slotRenameIdentity(); + void slotRemoveIdentity(); + + void slotChangeAddressee(); + void slotChangePhoto(const QString &photoUrl); + void slotClearPhoto(); + + void slotSettingsChanged(); + +private: + class Private; + Private *d; + +}; +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/config/identity/kopeteidentityconfigbase.ui b/kopete/kopete/config/identity/kopeteidentityconfigbase.ui new file mode 100644 index 00000000..4e31a29f --- /dev/null +++ b/kopete/kopete/config/identity/kopeteidentityconfigbase.ui @@ -0,0 +1,541 @@ + +KopeteIdentityConfigBase +Michaël Larouche + + + KopeteIdentityConfigBase + + + + 0 + 0 + 633 + 447 + + + + + unnamed + + + + kcfg_EnableGlobalIdentity + + + Enable &global identity + + + + + layout6 + + + + unnamed + + + + textLabel1 + + + Identity: + + + + + comboSelectIdentity + + + false + + + + 7 + 0 + 0 + 0 + + + + + + + + layout7 + + + + unnamed + + + + buttonNewIdentity + + + Ne&w Identity... + + + + + buttonCopyIdentity + + + Cop&y Identity... + + + + + buttonRenameIdentity + + + Rename I&dentity... + + + + + buttonRemoveIdentity + + + Remo&ve Identity + + + + + + + tabWidget2 + + + + tab + + + &Nickname + + + + unnamed + + + + nicknameButtonGroup + + + false + + + NoFrame + + + 0 + + + + + + + unnamed + + + + layout12 + + + + unnamed + + + + radioNicknameCustom + + + Cu&stom: + + + + + lineNickname + + + + + radioNicknameKABC + + + Use address boo&k name (need address book link) + + + + + radioNicknameContact + + + Use nickname from con&tact for global nickname: + + + + + comboNameContact + + + + 7 + 0 + 0 + 0 + + + + Contact to synchronize the displayname with. + + + + + + + + + spacer7_2 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + + + tab + + + P&hoto + + + + unnamed + + + + photoButtonGroup + + + false + + + 0 + + + + + + + unnamed + + + + layout7 + + + + unnamed + + + + comboPhotoURL + + + + + buttonClearPhoto + + + + 32 + 3232 + + + + + + + + + checkSyncPhotoKABC + + + S&ync address book photo with global photo + + + + + comboPhotoContact + + + + 7 + 0 + 0 + 0 + + + + + + radioPhotoCustom + + + Cus&tom: + + + + + radioPhotoContact + + + U&se photo from contact for global photo: + + + + + radioPhotoKABC + + + Use a&ddress book photo (needs address book link) + + + + + + + layout11 + + + + unnamed + + + + spacer6 + + + Vertical + + + Expanding + + + + 20 + 36 + + + + + + labelPhoto + + + + 64 + 92 + + + + + 64 + 32767 + + + + Box + + + 1 + + + <center>Photo</center> + + + RichText + + + false + + + + + spacer7 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + + + + + spacer5 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + + + TabPage + + + Address &Book Link + + + + unnamed + + + + layout7 + + + + unnamed + + + + lineAddressee + + + true + + + + + buttonChangeAddressee + + + false + + + C&hange... + + + + + + + textLabel1_2 + + + <b>Note:</b> The address book link uses KAddressBook's +current user contact. + + + + + spacer4_2 + + + Vertical + + + Expanding + + + + 20 + 100 + + + + + + + + + spacer4 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + + + kcfg_EnableGlobalIdentity + toggled(bool) + nicknameButtonGroup + setEnabled(bool) + + + kcfg_EnableGlobalIdentity + toggled(bool) + photoButtonGroup + setEnabled(bool) + + + kcfg_EnableGlobalIdentity + toggled(bool) + comboSelectIdentity + setEnabled(bool) + + + kcfg_EnableGlobalIdentity + toggled(bool) + buttonChangeAddressee + setEnabled(bool) + + + + + kcombobox.h + kpushbutton.h + kpushbutton.h + kpushbutton.h + kpushbutton.h + kurlrequester.h + kcombobox.h + kpushbutton.h + kpushbutton.h + klineedit.h + + diff --git a/kopete/kopete/config/identity/kopeteidentityconfigpreferences.kcfg b/kopete/kopete/config/identity/kopeteidentityconfigpreferences.kcfg new file mode 100644 index 00000000..3d004495 --- /dev/null +++ b/kopete/kopete/config/identity/kopeteidentityconfigpreferences.kcfg @@ -0,0 +1,15 @@ + + + + + + + + + i18n("Default Identity") + + + diff --git a/kopete/kopete/config/identity/kopeteidentityconfigpreferences.kcfgc b/kopete/kopete/config/identity/kopeteidentityconfigpreferences.kcfgc new file mode 100644 index 00000000..6f93d40f --- /dev/null +++ b/kopete/kopete/config/identity/kopeteidentityconfigpreferences.kcfgc @@ -0,0 +1,8 @@ +# Code generation options for kconfig_compiler +File=kopeteidentityconfigpreferences.kcfg +ClassName=KopeteIdentityConfigPreferences +Singleton=true +Mutators=true +MemberVariables=private +GlobalEnums=true +IncludeFiles=klocale.h diff --git a/kopete/kopete/config/plugins/Makefile.am b/kopete/kopete/config/plugins/Makefile.am new file mode 100644 index 00000000..3ed239a1 --- /dev/null +++ b/kopete/kopete/config/plugins/Makefile.am @@ -0,0 +1,11 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +noinst_LTLIBRARIES = libkopetepluginconfig.la + +libkopetepluginconfig_la_SOURCES = kopetepluginconfig.cpp +libkopetepluginconfig_la_LDFLAGS = $(all_libraries) +libkopetepluginconfig_la_LIBADD = $(LIB_KUTILS) + +# vim: set noet: + diff --git a/kopete/kopete/config/plugins/kopetepluginconfig.cpp b/kopete/kopete/config/plugins/kopetepluginconfig.cpp new file mode 100644 index 00000000..9949f70e --- /dev/null +++ b/kopete/kopete/config/plugins/kopetepluginconfig.cpp @@ -0,0 +1,120 @@ +/* + kopetepluginconfig.cpp - Configure the Kopete plugins + + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2001-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopetepluginconfig.h" + +#include +#include + +#include +#include +#include +#include + +#include "kopetepluginmanager.h" + +class KopetePluginConfigPrivate +{ +public: + KPluginSelector *pluginSelector; + bool isChanged; +}; + +KopetePluginConfig::~KopetePluginConfig() +{ + delete d; +} + +KopetePluginConfig::KopetePluginConfig( QWidget *parent, const char *name ) +: KDialogBase( Plain, i18n( "Configure Plugins" ), /*Help |*/ Cancel | Apply | Ok | User1, + Ok, parent, name, false, true, KGuiItem( i18n( "&Reset" ), "undo" ) ) +{ + d = new KopetePluginConfigPrivate; + showButton( User1, false ); + setChanged( false ); + + // FIXME: Implement this - Martijn + enableButton( KDialogBase::Help, false ); + + setInitialSize( QSize( 640, 480 ) ); + + ( new QVBoxLayout( plainPage(), 0, 0 ) )->setAutoAdd( true ); + d->pluginSelector = new KPluginSelector( plainPage() ); + setMainWidget( d->pluginSelector ); + connect( d->pluginSelector, SIGNAL( changed( bool ) ), this, SLOT( setChanged( bool ) ) ); + connect( d->pluginSelector, SIGNAL( configCommitted( const QCString & ) ), + KSettings::Dispatcher::self(), SLOT( reparseConfiguration( const QCString & ) ) ); + + d->pluginSelector->addPlugins( Kopete::PluginManager::self()->availablePlugins( "Plugins" ), i18n( "General Plugins" ), "Plugins" ); +} + +void KopetePluginConfig::setChanged( bool c ) +{ + d->isChanged = c; + enableButton( Apply, c ); +} + +void KopetePluginConfig::slotDefault() +{ + d->pluginSelector->defaults(); + setChanged( false ); +} + +void KopetePluginConfig::slotUser1() +{ + d->pluginSelector->load(); + setChanged( false ); +} + +void KopetePluginConfig::apply() +{ + if( d->isChanged ) + { + d->pluginSelector->save(); + Kopete::PluginManager::self()->loadAllPlugins(); + setChanged( false ); + } +} + +void KopetePluginConfig::slotApply() +{ + apply(); +} + +void KopetePluginConfig::slotOk() +{ + emit okClicked(); + apply(); + accept(); +} + +void KopetePluginConfig::slotHelp() +{ + kdWarning() << k_funcinfo << "FIXME: Implement!" << endl; +} + +void KopetePluginConfig::show() +{ + d->pluginSelector->load(); + + KDialogBase::show(); +} + +#include "kopetepluginconfig.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/config/plugins/kopetepluginconfig.h b/kopete/kopete/config/plugins/kopetepluginconfig.h new file mode 100644 index 00000000..e8853a1c --- /dev/null +++ b/kopete/kopete/config/plugins/kopetepluginconfig.h @@ -0,0 +1,56 @@ +/* + kopetepluginconfig.h - Configure the Kopete plugins + + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2001-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPLUGINCONFIG_H +#define KOPETEPLUGINCONFIG_H + +#include + +class KopetePluginConfigPrivate; + +/** + * Plugin selector. See KPluginSelector in kdelibs for documentation. + * + * @author Martijn Klingens + */ +class KopetePluginConfig : public KDialogBase +{ + Q_OBJECT + +public: + KopetePluginConfig( QWidget *parent, const char *name = 0L ); + ~KopetePluginConfig(); + void apply(); + +public slots: + void setChanged( bool c ); + + virtual void slotDefault(); + virtual void slotUser1(); + virtual void slotApply(); + virtual void slotOk(); + virtual void slotHelp(); + virtual void show(); + +private: + KopetePluginConfigPrivate *d; +}; + +#endif // KOPETEPLUGINCONFIG_H + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/contactlist/Makefile.am b/kopete/kopete/contactlist/Makefile.am new file mode 100644 index 00000000..d9544d5d --- /dev/null +++ b/kopete/kopete/contactlist/Makefile.am @@ -0,0 +1,28 @@ +METASOURCES = AUTO + +AM_CPPFLAGS = $(KOPETE_INCLUDES) \ + -I$(top_srcdir)/kopete/libkopete/private \ + -I$(top_srcdir)/kopete/libkopete/ui \ + -I$(top_srcdir)/kopete/kopete \ + -I$(top_srcdir)/kopete/kopete/chatwindow \ + -I$(top_srcdir)/kopete/kopete/addcontactwizard \ + -I$(top_builddir)/kopete/kopete/addcontactwizard \ + $(all_includes) + +noinst_LTLIBRARIES = libkopetecontactlist.la + +libkopetecontactlist_la_SOURCES = kopetemetacontactlvi.cpp \ + kopetestatusgroupviewitem.cpp kopetegroupviewitem.cpp kopetecontactlistview.cpp \ + kopetegvipropswidget.ui kopetemetalvipropswidget.ui kopetelviprops.cpp \ + kopeteaddrbookexport.cpp kopeteaddrbookexportui.ui customnotifications.ui \ + customnotificationprops.cpp kopetegrouplistaction.cpp kabcexport.cpp \ + kabcexport_base.ui + +libkopetecontactlist_la_LDFLAGS = $(all_libraries) +libkopetecontactlist_la_LIBADD = -lkabc ../../libkopete/libkopete.la ../addcontactwizard/libkopeteaddcontactwizard.la $(LIB_KDEUI) $(LIB_XRENDER) + +noinst_HEADERS = kopeteaddrbookexport.h customnotificationprops.h kabcexport.h + +KDE_OPTIONS = nofinal + +# vim: set noet: diff --git a/kopete/kopete/contactlist/configure.in.in b/kopete/kopete/contactlist/configure.in.in new file mode 100644 index 00000000..900224ea --- /dev/null +++ b/kopete/kopete/contactlist/configure.in.in @@ -0,0 +1,15 @@ + +dnl ----------------------------------------------------- +dnl XRender check - stolen from kdelibs/kdefx +dnl ----------------------------------------------------- +LIB_XRENDER= +if test "$kde_use_qt_emb" = "no" && test "$kde_use_qt_mac" = "no"; then + KDE_CHECK_HEADER(X11/extensions/Xrender.h, [xrender_h=yes], [xrender_h=no]) + if test "$xrender_h" = yes; then + KDE_CHECK_LIB(Xrender, XRenderComposite, [ + LIB_XRENDER=-lXrender + AC_DEFINE_UNQUOTED(HAVE_XRENDER, 1, [Defined if your system has XRender support]) + ], [], -lXext -lX11 $X_EXTRA_LIBS) + fi +fi +AC_SUBST(LIB_XRENDER) diff --git a/kopete/kopete/contactlist/customnotificationprops.cpp b/kopete/kopete/contactlist/customnotificationprops.cpp new file mode 100644 index 00000000..87833fa7 --- /dev/null +++ b/kopete/kopete/contactlist/customnotificationprops.cpp @@ -0,0 +1,168 @@ +/* + customnotificationprops.cpp + + Kopete Contactlist Custom Notifications GUI for Groups and MetaContacts + + Contains UI controller logic for managing custom notifications + + Copyright (c) 2004 Will Stephenson + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#include +#include +#include + +#include "customnotifications.h" +#include "kopeteeventpresentation.h" +#include "kopetenotifyevent.h" +#include "kopetenotifydataobject.h" + +#include "customnotificationprops.h" + +CustomNotificationProps::CustomNotificationProps( QWidget *parent, Kopete::NotifyDataObject* item, const char * name ) +: QObject( parent, name ) +{ + m_notifyWidget = new CustomNotificationWidget( parent, "notificationWidget" ); + + m_item = item; + QString path = "kopete/eventsrc"; + KConfig eventsfile( path, true, false, "data" ); + m_eventList = eventsfile.groupList(); + QStringList contactSpecificEvents; // we are only interested in events that relate to contacts + QStringList::Iterator it = m_eventList.begin(); + QStringList::Iterator end = m_eventList.end(); + for ( ; it != end; ++it ) + { + if ( !(*it).startsWith( QString::fromLatin1( "kopete_contact_" ) ) ) + continue; + contactSpecificEvents.append( *it ); + QMap entries = eventsfile.entryMap( *it ); + eventsfile.setGroup( *it ); + QString comment = eventsfile.readEntry( "Comment", QString::fromLatin1( "Found nothing!" ) ); + m_notifyWidget->cmbEvents->insertItem( comment ); + } + m_eventList = contactSpecificEvents; + slotEventsComboChanged( m_notifyWidget->cmbEvents->currentItem() ); + // we have to do this after adding items + connect( m_notifyWidget->cmbEvents, SIGNAL( activated( int ) ), this, SLOT( slotEventsComboChanged( int ) ) ); +} + +void CustomNotificationProps::slotEventsComboChanged( int itemNo ) +{ + // if the combo has changed, store the previous state of the widgets + // record the selected item so we can save it when the widget changes next + storeCurrentCustoms(); + m_event = m_eventList[ itemNo ]; + // update the widgets for the selected item + // get the corresponding Kopete::NotifyEvent + Kopete::NotifyEvent *evt = m_item->notifyEvent( m_event ); + // set the widgets accordingly + resetEventWidgets(); + if ( evt ) + { + // sound presentation + Kopete::EventPresentation *pres = evt->presentation( Kopete::EventPresentation::Sound ); + if ( pres ) + { + m_notifyWidget->chkCustomSound->setChecked( pres->enabled() ); + m_notifyWidget->customSound->setURL( pres->content() ); + m_notifyWidget->chkSoundSS->setChecked( pres->singleShot() ); + } + // message presentation + pres = evt->presentation( Kopete::EventPresentation::Message ); + if ( pres ) + { + m_notifyWidget->chkCustomMsg->setChecked( pres->enabled() ); + m_notifyWidget->customMsg->setText( pres->content() ); + m_notifyWidget->chkMsgSS->setChecked( pres->singleShot() ); + } + // chat presentation + pres = evt->presentation( Kopete::EventPresentation::Chat ); + if ( pres ) + { + m_notifyWidget->chkCustomChat->setChecked( pres->enabled() ); + m_notifyWidget->chkChatSS->setChecked( pres->singleShot() ); + } + m_notifyWidget->chkSuppressCommon->setChecked( evt->suppressCommon() ); + } + //dumpData(); +} + + +void CustomNotificationProps::dumpData() +{ + Kopete::NotifyEvent *evt = m_item->notifyEvent( m_event ); + if ( evt ) + kdDebug( 14000 ) << k_funcinfo << evt->toString() << endl; + else + kdDebug( 14000 ) << k_funcinfo << " no event exists." << endl; +} + +void CustomNotificationProps::resetEventWidgets() +{ + m_notifyWidget->chkCustomSound->setChecked( false ); + m_notifyWidget->customSound->clear(); + m_notifyWidget->chkSoundSS->setChecked( true ); + m_notifyWidget->chkCustomMsg->setChecked( false ); + m_notifyWidget->customMsg->clear(); + m_notifyWidget->chkMsgSS->setChecked( true ); + m_notifyWidget->chkCustomChat->setChecked( false ); + m_notifyWidget->chkChatSS->setChecked( true ); + m_notifyWidget->chkSuppressCommon->setChecked( false ); +} + +void CustomNotificationProps::storeCurrentCustoms() +{ + if ( !m_event.isNull() ) + { + Kopete::NotifyEvent *evt = m_item->notifyEvent( m_event ); + if ( !evt ) + { + evt = new Kopete::NotifyEvent( ); + // store the changed event + m_item->setNotifyEvent( m_event, evt ); + } + evt->setSuppressCommon( m_notifyWidget->chkSuppressCommon->isChecked() ); + // set different presentations + Kopete::EventPresentation *eventNotify = 0; + eventNotify = new Kopete::EventPresentation( Kopete::EventPresentation::Sound, + m_notifyWidget->customSound->url(), + m_notifyWidget->chkSoundSS->isChecked(), + m_notifyWidget->chkCustomSound->isChecked() ); + evt->setPresentation( Kopete::EventPresentation::Sound, eventNotify ); + // set message attributes + eventNotify = new Kopete::EventPresentation( Kopete::EventPresentation::Message, + m_notifyWidget->customMsg->text(), + m_notifyWidget->chkMsgSS->isChecked(), + m_notifyWidget->chkCustomMsg->isChecked() ); + evt->setPresentation( Kopete::EventPresentation::Message, eventNotify ); + // set chat attributes + eventNotify = new Kopete::EventPresentation( Kopete::EventPresentation::Chat, + QString::null, + m_notifyWidget->chkChatSS->isChecked(), + m_notifyWidget->chkCustomChat->isChecked() ); + evt->setPresentation( Kopete::EventPresentation::Chat, eventNotify ); + evt->setSuppressCommon( m_notifyWidget->chkSuppressCommon->isChecked() ); + } +} + +CustomNotificationWidget* CustomNotificationProps::widget() +{ + return m_notifyWidget; +} + +#include "customnotificationprops.moc" diff --git a/kopete/kopete/contactlist/customnotificationprops.h b/kopete/kopete/contactlist/customnotificationprops.h new file mode 100644 index 00000000..5d6c1dea --- /dev/null +++ b/kopete/kopete/contactlist/customnotificationprops.h @@ -0,0 +1,51 @@ +/* + customnotificationprops.h + + Kopete Contactlist Custom Notifications GUI for Groups and MetaContacts + + Copyright (c) 2004 Will Stephenson + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_CUSTOM_NOTIFICATION_PROPS_H +#define KOPETE_CUSTOM_NOTIFICATION_PROPS_H + +class CustomNotificationWidget; +class QBoxLayout; + +namespace Kopete +{ +class NotifyDataObject; +} + +class CustomNotificationProps : public QObject +{ + Q_OBJECT +public: + CustomNotificationProps( QWidget *parent, Kopete::NotifyDataObject* item, const char * name = 0 ); + ~CustomNotificationProps() {} + void dumpData(); + void resetEventWidgets(); + void storeCurrentCustoms(); + CustomNotificationWidget* widget(); + +protected slots: + void slotEventsComboChanged( int itemNo ); + +private: + CustomNotificationWidget* m_notifyWidget; + Kopete::NotifyDataObject * m_item; + QStringList m_eventList; + QString m_event; +}; + +#endif diff --git a/kopete/kopete/contactlist/customnotifications.ui b/kopete/kopete/contactlist/customnotifications.ui new file mode 100644 index 00000000..86224af2 --- /dev/null +++ b/kopete/kopete/contactlist/customnotifications.ui @@ -0,0 +1,238 @@ + +CustomNotificationWidget + + + CustomNotificationWidget + + + + 0 + 0 + 389 + 220 + + + + + unnamed + + + + layout13 + + + + unnamed + + + + textLabel1 + + + On &event: + + + cmbEvents + + + + + cmbEvents + + + Choose the event that should have a custom notification + + + + + + + layout12 + + + + unnamed + + + + customSound + + + false + + + Select the sound to play + + + + + chkCustomSound + + + &Play a sound: + + + Play a sound when this event occurs for this contact + + + + + spacer10 + + + Horizontal + + + Expanding + + + + 140 + 20 + + + + + + chkCustomChat + + + Start a cha&t + + + Open a chat window with this contact when this event occurs for this contact + + + + + chkCustomMsg + + + &Display a message: + + + Display a message on your screen when this event occurs for this contact + + + + + customMsg + + + false + + + Enter the message to display + + + + + chkMsgSS + + + false + + + D&isplay once + + + Only display a message the next time the event occurs + + + + + chkSoundSS + + + false + + + P&lay once + + + Only play a sound the next time the event occurs + + + + + chkChatSS + + + false + + + T&rigger once + + + Only start a chat the next time the event occurs + + + + + + + chkSuppressCommon + + + S&uppress standard notifications + + + false + + + Check to prevent notifications common to all contacts from happening for this contact + + + + + + + chkCustomSound + toggled(bool) + customSound + setEnabled(bool) + + + chkCustomSound + toggled(bool) + chkSoundSS + setEnabled(bool) + + + chkCustomMsg + toggled(bool) + customMsg + setEnabled(bool) + + + chkCustomMsg + toggled(bool) + chkMsgSS + setEnabled(bool) + + + chkCustomChat + toggled(bool) + chkChatSS + setEnabled(bool) + + + + cmbEvents + chkCustomSound + customSound + chkSoundSS + chkCustomMsg + customMsg + chkMsgSS + chkCustomChat + chkChatSS + chkSuppressCommon + + + + kurlrequester.h + klineedit.h + kpushbutton.h + + diff --git a/kopete/kopete/contactlist/kabcexport.cpp b/kopete/kopete/contactlist/kabcexport.cpp new file mode 100644 index 00000000..73f67344 --- /dev/null +++ b/kopete/kopete/contactlist/kabcexport.cpp @@ -0,0 +1,249 @@ +/* + kabcexport.cpp - Export Contacts to Address Book Wizard for Kopete + + Copyright (c) 2005 by Will Stephenson + Resource selector taken from KRES::SelectDialog + Copyright (c) 2002 Tobias Koenig + Copyright (c) 2002 Jan-Pascal van Best + Copyright (c) 2003 Cornelius Schumacher + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "kabcpersistence.h" + +#include "kabcexport.h" + +class ContactLVI : public QCheckListItem +{ + public: + ContactLVI ( Kopete::MetaContact * mc, QListView * parent, const QString & text, Type tt = RadioButtonController ) : QCheckListItem( parent, text, tt ), mc( mc ) + { } + Kopete::MetaContact * mc; + QString uid; +}; + +// ctor populates the resource list and contact list, and enables the next button on the first page +KabcExportWizard::KabcExportWizard( QWidget *parent, const char *name ) + : KabcExportWizard_Base( parent, name ) +{ + connect( m_addrBooks, SIGNAL( selectionChanged( QListBoxItem * ) ), SLOT( slotResourceSelectionChanged( QListBoxItem * ) ) ); + + connect( m_btnSelectAll, SIGNAL( clicked() ), SLOT( slotSelectAll() ) ); + connect( m_btnDeselectAll, SIGNAL( clicked() ), SLOT( slotDeselectAll() ) ); + + // fill resource selector + m_addressBook = Kopete::KABCPersistence::self()->addressBook(); + + QPtrList kabcResources = m_addressBook->resources(); + + QPtrListIterator resIt( kabcResources ); + KABC::Resource *resource; + + uint counter = 0; + while ( ( resource = resIt.current() ) != 0 ) + { + ++resIt; + if ( !resource->readOnly() ) + { + m_resourceMap.insert( counter, resource ); + m_addrBooks->insertItem( resource->resourceName() ); + counter++; + } + } + setNextEnabled( QWizard::page( 0 ), false ); + setFinishEnabled( QWizard::page( 1 ), true ); + // if there were no writable address books, tell the user + if ( counter == 0 ) + { + m_addrBooks->insertItem( i18n( "No writeable addressbook resource found." ) ); + m_addrBooks->insertItem( i18n( "Add or enable one using the KDE Control Center." ) ); + m_addrBooks->setEnabled( false ); + } + + if ( m_addrBooks->count() == 1 ) + m_addrBooks->setSelected( 0, true ); + + // fill contact list + QPtrList contacts = Kopete::ContactList::self()->metaContacts(); + QPtrListIterator it( contacts ); + counter = 0; + QString alreadyIn = i18n( " (already in address book)" ); + for (; it.current(); ++it) + { + m_contactMap.insert( counter, it.current() ); + QCheckListItem * lvi = new ContactLVI( it.current(), m_contactList, + it.current()->displayName(), QCheckListItem::CheckBox ); + lvi->setOn( false ); + if ( it.current()->metaContactId().contains(':') ) + { + lvi->setOn( true ); + lvi->setEnabled( true ); + } + else + lvi->setText( 0, lvi->text( 0 ) + alreadyIn ); + } +} + +KabcExportWizard::~KabcExportWizard() +{ + +} + +void KabcExportWizard::slotDeselectAll() +{ + QListViewItemIterator it( m_contactList ); + while ( it.current() ) + { + ContactLVI *item = static_cast( it.current() ); + item->setOn( false ); + ++it; + } +} + +void KabcExportWizard::slotSelectAll() +{ + QListViewItemIterator it( m_contactList ); + while ( it.current() ) + { + ContactLVI *item = static_cast( it.current() ); + ++it; + if ( !item->isEnabled() ) + continue; + item->setOn( true ); + } +} + +void KabcExportWizard::slotResourceSelectionChanged( QListBoxItem * lbi ) +{ + setNextEnabled( QWizard::page( 0 ), lbi->isSelected() ); +} + +// accept runs the export algorithm +void KabcExportWizard::accept() +{ + // first add an addressee to the selected resource + // then set the metacontactId of each MC to that of the new addressee + KABC::Resource * selectedResource = + m_resourceMap[ ( m_addrBooks->index( m_addrBooks->selectedItem() ) ) ]; + // for each item checked + { + QListViewItemIterator it( m_contactList ); + while ( it.current() ) + { + ContactLVI *item = static_cast( it.current() ); + // if it is checked and enabled + if ( item->isEnabled() && item->isOn() ) + { + KABC::Addressee addr; + addr = m_addressBook->findByUid( item->mc->metaContactId() ); + if ( addr.isEmpty() ) // unassociated contact + { + kdDebug( 14000 ) << "creating addressee " << item->mc->displayName() << " in address book " << selectedResource->resourceName() << endl; + // create a new addressee in the selected resource + addr.setResource( selectedResource ); + + // set name + QPtrList contacts = item->mc->contacts(); + if ( contacts.count() == 1 ) + { + Kopete::ContactProperty prop; + prop = contacts.first()->property( + Kopete::Global::Properties::self()->fullName() ); + if ( prop.isNull() ) + addr.setNameFromString( item->mc->displayName() ); + else + addr.setNameFromString( prop.value().toString() ); + } + else + addr.setNameFromString( item->mc->displayName() ); + + // set details + exportDetails( item->mc, addr ); + m_addressBook->insertAddressee( addr ); + // set the metacontact's id to that of the new addressee + // - this causes the addressbook to be written by libkopete + item->mc->setMetaContactId( addr.uid() ); + } + else + { + exportDetails( item->mc, addr ); + m_addressBook->insertAddressee( addr ); + } + } + ++it; + } + } + // request a write in case we only changed details on existing linked addressee + Kopete::KABCPersistence::self()->writeAddressBook( selectedResource ); + QDialog::accept(); +} + +void KabcExportWizard::exportDetails( Kopete::MetaContact * mc, KABC::Addressee & addr ) +{ + // for each contact + QPtrList contacts = mc->contacts(); + QPtrListIterator cit( contacts ); + for( ; cit.current(); ++cit ) + { + Kopete::ContactProperty prop; + prop = (*cit)->property( Kopete::Global::Properties::self()->emailAddress() ); + if ( !prop.isNull() ) + { + addr.insertEmail( prop.value().toString() ); + } + prop = (*cit)->property( Kopete::Global::Properties::self()->privatePhone() ); + if ( !prop.isNull() ) + { + addr.insertPhoneNumber( KABC::PhoneNumber( prop.value().toString(), KABC::PhoneNumber::Home ) ); + } + prop = (*cit)->property( Kopete::Global::Properties::self()->workPhone() ); + if ( !prop.isNull() ) + { + addr.insertPhoneNumber( KABC::PhoneNumber( prop.value().toString(), KABC::PhoneNumber::Work ) ); + } + prop = (*cit)->property( Kopete::Global::Properties::self()->privateMobilePhone() ); + if ( !prop.isNull() ) + { + addr.insertPhoneNumber( KABC::PhoneNumber( prop.value().toString(), KABC::PhoneNumber::Cell ) ); + } + + } + // metacontact photo + QImage photo = mc->photo(); + if ( !photo.isNull() ) + addr.setPhoto( KABC::Picture( photo ) ); +} + +#include "kabcexport.moc" diff --git a/kopete/kopete/contactlist/kabcexport.h b/kopete/kopete/contactlist/kabcexport.h new file mode 100644 index 00000000..bad1d8e6 --- /dev/null +++ b/kopete/kopete/contactlist/kabcexport.h @@ -0,0 +1,56 @@ +/* + kabcexport.h - Export Contacts to Address Book Wizard for Kopete + + Copyright (c) 2005 by Will Stephenson + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KABCEXPORTWIZARD_H +#define KABCEXPORTWIZARD_H + +#include "kabcexport_base.h" + +namespace KABC { + class AddressBook; + class Addressee; +} + +namespace Kopete { + class MetaContact; +} + +namespace KRES { + class Resource; +} + +class KabcExportWizard : public KabcExportWizard_Base +{ +Q_OBJECT + public: + KabcExportWizard( QWidget *parent = 0, const char *name = 0 ); + ~KabcExportWizard(); + public slots: + void accept(); + protected slots: + void slotDeselectAll(); + void slotSelectAll(); + void slotResourceSelectionChanged( QListBoxItem * lbi ); + protected: + void exportDetails( Kopete::MetaContact * mc, KABC::Addressee & addr ); + private: + KABC::AddressBook* m_addressBook; + QMap m_resourceMap; + QMap m_contactMap; +}; + +#endif diff --git a/kopete/kopete/contactlist/kabcexport_base.ui b/kopete/kopete/contactlist/kabcexport_base.ui new file mode 100644 index 00000000..80ace5c6 --- /dev/null +++ b/kopete/kopete/contactlist/kabcexport_base.ui @@ -0,0 +1,183 @@ + +KabcExportWizard_Base + + + KabcExportWizard_Base + + + + 0 + 0 + 423 + 398 + + + + Export Contacts + + + + page + + + Export Contacts to Address Book + + + + unnamed + + + + textLabel1 + + + + 1 + 1 + 0 + 0 + + + + This wizard helps you export instant messaging contacts to the KDE address book. + + + WordBreak|AlignVCenter + + + + + groupBox1 + + + + 3 + 3 + 0 + 0 + + + + &Select Address Book + + + + m_addrBooks + + + + 11 + 31 + 230 + 140 + + + + + 3 + 3 + 0 + 0 + + + + + + + + + WizardPage + + + Select Contacts to Export + + + + unnamed + + + + textLabel3 + + + + 1 + 1 + 0 + 0 + + + + Selected contacts will be added to the KDE address book. + + + + + + Contact + + + true + + + true + + + + m_contactList + + + true + + + AllColumns + + + + + layout3 + + + + unnamed + + + + m_btnSelectAll + + + Select &All + + + + + m_btnDeselectAll + + + &Deselect All + + + + + spacer11 + + + Horizontal + + + Expanding + + + + 51 + 20 + + + + + + + + + + diff --git a/kopete/kopete/contactlist/kopeteaddrbookexport.cpp b/kopete/kopete/contactlist/kopeteaddrbookexport.cpp new file mode 100644 index 00000000..d752f71e --- /dev/null +++ b/kopete/kopete/contactlist/kopeteaddrbookexport.cpp @@ -0,0 +1,298 @@ +/* + kopeteaddrbookexport.cpp - Kopete Online Status + + Logic for exporting data acquired from messaging systems to the + KDE address book + + Copyright (c) 2004 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#include +#include +#include +#include + +#include "kopeteaccount.h" +#include "kopeteglobal.h" +#include "kopetemetacontact.h" +#include "kopetecontact.h" + +#include "kopeteaddrbookexport.h" +#include "kopeteaddrbookexportui.h" + +KopeteAddressBookExport::KopeteAddressBookExport( QWidget *parent, Kopete::MetaContact *mc ) : QObject( parent ) +{ + // instantiate dialog and populate widgets + mParent = parent; + mAddressBook = KABC::StdAddressBook::self(); + mMetaContact = mc; +} + +KopeteAddressBookExport::~KopeteAddressBookExport() +{ + +} + +void KopeteAddressBookExport::initLabels() +{ + if ( !mAddressee.isEmpty() ) + { + mUI->mLblFirstName->setText( mAddressee.givenNameLabel() ); + mUI->mLblLastName->setText( mAddressee.familyNameLabel() ); + mUI->mLblEmail->setText( mAddressee.emailLabel() ); + mUI->mLblUrl->setText( mAddressee.urlLabel() ); + mUI->mLblHomePhone->setText( mAddressee.homePhoneLabel() ); + mUI->mLblWorkPhone->setText( mAddressee.businessPhoneLabel() ); + mUI->mLblMobilePhone->setText( mAddressee.mobilePhoneLabel() ); + } +} + +void KopeteAddressBookExport::fetchKABCData() +{ + if ( !mAddressee.isEmpty() ) + { + mAddrBookIcon = SmallIcon( "kaddressbook" ); + + // given name + QString given = mAddressee.givenName(); + if ( !given.isEmpty() ) + mUI->mFirstName->insertItem( mAddrBookIcon, given ); + else + mUI->mFirstName->insertItem( mAddrBookIcon, i18n("") ); + + // family name + QString family = mAddressee.familyName(); + if ( !family.isEmpty() ) + mUI->mLastName->insertItem( mAddrBookIcon, family ); + else + mUI->mLastName->insertItem( mAddrBookIcon, i18n("") ); + + // url + QString url = mAddressee.url().url(); + if ( !url.isEmpty() ) + mUI->mUrl->insertItem( mAddrBookIcon, url ); + else + mUI->mUrl->insertItem( mAddrBookIcon, i18n("") ); + + // emails + QStringList emails = mAddressee.emails(); + numEmails = emails.count(); + for ( QStringList::Iterator it = emails.begin(); it != emails.end(); ++it ) + mUI->mEmails->insertItem( mAddrBookIcon, *it ); + if ( numEmails == 0 ) + { + mUI->mEmails->insertItem( mAddrBookIcon, i18n("") ); + numEmails = 1; + } + + // phone numbers + fetchPhoneNumbers( mUI->mHomePhones, KABC::PhoneNumber::Home, numHomePhones ); + fetchPhoneNumbers( mUI->mWorkPhones, KABC::PhoneNumber::Work, numWorkPhones ); + fetchPhoneNumbers( mUI->mMobilePhones, KABC::PhoneNumber::Cell, numMobilePhones ); + } +} + +void KopeteAddressBookExport::fetchPhoneNumbers( KListBox * listBox, int type, uint& counter ) +{ + KABC::PhoneNumber::List phones = mAddressee.phoneNumbers( type ); + counter = phones.count(); + KABC::PhoneNumber::List::Iterator it; + for ( it = phones.begin(); it != phones.end(); ++it ) + listBox->insertItem( mAddrBookIcon, (*it).number() ); + if ( counter == 0 ) + { + listBox->insertItem( mAddrBookIcon, i18n("") ); + counter = 1; + } +} + +void KopeteAddressBookExport::fetchIMData() +{ + QPtrList contacts = mMetaContact->contacts(); + QPtrListIterator cit( contacts ); + for( ; cit.current(); ++cit ) + { + // for each contact, get the property content + Kopete::Contact* c = cit.current(); + QPixmap contactIcon = c->account()->accountIcon( 16 ); + // given name + populateIM( c, contactIcon, mUI->mFirstName, Kopete::Global::Properties::self()->firstName() ); + // family name + populateIM( c, contactIcon, mUI->mLastName, Kopete::Global::Properties::self()->lastName() ); + // url + // TODO: make URL/homepage a global template, currently only in IRC channel contact + // emails + populateIM( c, contactIcon, mUI->mEmails, Kopete::Global::Properties::self()->emailAddress() ); + // home phone + populateIM( c, contactIcon, mUI->mHomePhones, Kopete::Global::Properties::self()->privatePhone() ); + // work phone + populateIM( c, contactIcon, mUI->mWorkPhones, Kopete::Global::Properties::self()->workPhone() ); + // mobile phone + populateIM( c, contactIcon, mUI->mMobilePhones, Kopete::Global::Properties::self()->privateMobilePhone() ); + } +} + +void KopeteAddressBookExport::populateIM( const Kopete::Contact *contact, const QPixmap &icon, QComboBox *combo, const Kopete::ContactPropertyTmpl &property ) +{ + Kopete::ContactProperty prop = contact->property( property ); + if ( !prop.isNull() ) + { + combo->insertItem( icon, prop.value().toString() ); + } +} + +void KopeteAddressBookExport::populateIM( const Kopete::Contact *contact, const QPixmap &icon, KListBox *listBox, const Kopete::ContactPropertyTmpl &property ) +{ + Kopete::ContactProperty prop = contact->property( property ); + if ( !prop.isNull() ) + { + listBox->insertItem( icon, prop.value().toString() ); + } +} + +int KopeteAddressBookExport::showDialog() +{ + mAddressee = mAddressBook->findByUid( mMetaContact->metaContactId() ); + if ( !mAddressee.isEmpty() ) + { + numEmails = 0; + numHomePhones = 0; + numWorkPhones = 0; + numMobilePhones = 0; + mDialog = new KDialogBase( mParent, "addressbookexportdialog", true, i18n("Export to Address Book"), KDialogBase::Ok|KDialogBase::Cancel ); + mUI = new AddressBookExportUI( mDialog ); + mDialog->setMainWidget( mUI ); + mDialog->setButtonOK( KGuiItem( i18n( "Export" ), + QString::null, i18n( "Set address book fields using the selected data from Kopete" ) ) ); + + initLabels(); + // fetch existing data from kabc + fetchKABCData(); + // fetch data from contacts + fetchIMData(); + + return mDialog->exec(); + } + else + return QDialog::Rejected; +} + +void KopeteAddressBookExport::exportData() +{ + // write the data from the widget to KABC + // update the Addressee + // first name + bool dirty = false; + if ( newValue( mUI->mFirstName ) ) + { + dirty = true; + mAddressee.setGivenName( mUI->mFirstName->currentText() ); + } + // last name + if ( newValue( mUI->mLastName ) ) + { + dirty = true; + mAddressee.setFamilyName( mUI->mLastName->currentText() ); + } + // url + if ( newValue( mUI->mUrl ) ) + { + dirty = true; + mAddressee.setUrl( KURL( mUI->mUrl->currentText() ) ); + } + + QStringList newVals; + // email + newVals = newValues( mUI->mEmails, numEmails ); + for ( QStringList::Iterator it = newVals.begin(); it != newVals.end(); ++it ) + { + dirty = true; + mAddressee.insertEmail( *it ); + } + // home phone + newVals = newValues( mUI->mHomePhones, numHomePhones ); + for ( QStringList::Iterator it = newVals.begin(); it != newVals.end(); ++it ) + { + dirty = true; + mAddressee.insertPhoneNumber( KABC::PhoneNumber( *it, KABC::PhoneNumber::Home ) ); + } + // work phone + newVals = newValues( mUI->mWorkPhones, numWorkPhones ); + for ( QStringList::Iterator it = newVals.begin(); it != newVals.end(); ++it ) + { + dirty = true; + mAddressee.insertPhoneNumber( KABC::PhoneNumber( *it, KABC::PhoneNumber::Work ) ); + } + // mobile + newVals = newValues( mUI->mMobilePhones, numMobilePhones ); + for ( QStringList::Iterator it = newVals.begin(); it != newVals.end(); ++it ) + { + dirty = true; + mAddressee.insertPhoneNumber( KABC::PhoneNumber( *it, KABC::PhoneNumber::Cell ) ); + } + + if ( dirty ) + { + // write the changed addressbook + mAddressBook->insertAddressee( mAddressee ); + + KABC::Ticket *ticket = mAddressBook->requestSaveTicket(); + if ( !ticket ) + kdWarning( 14000 ) << k_funcinfo << "WARNING: Resource is locked by other application!" << endl; + else + { + if ( !mAddressBook->save( ticket ) ) + { + kdWarning( 14000 ) << k_funcinfo << "ERROR: Saving failed!" << endl; + mAddressBook->releaseSaveTicket( ticket ); + } + } + kdDebug( 14000 ) << k_funcinfo << "Finished writing KABC" << endl; + } +} + +bool KopeteAddressBookExport::newValue( QComboBox *combo ) +{ + // all data in position 0 is from KABC, so if position 0 is selected, + // or if the selection is the same as the data at 0, return false + return !( combo->currentItem() == 0 || + ( combo->text( combo->currentItem() ) == combo->text( 0 ) ) ); +} + +QStringList KopeteAddressBookExport::newValues( KListBox *listBox, uint counter ) +{ + QStringList newValues; + // need to iterate all items except those from KABC and check if selected and not same as the first + // counter is the number of KABC items, and hence the index of the first non KABC item + for ( uint i = counter; i < listBox->count(); ++i ) + { + if ( listBox->isSelected( i ) ) + { + // check whether it matches any KABC item + bool duplicate = false; + for ( uint j = 0; j < counter; ++j ) + { + if ( listBox->text( i ) == listBox->text( j ) ) + duplicate = true; + } + if ( !duplicate ) + newValues.append( listBox->text( i ) ); + } + } + return newValues; +} diff --git a/kopete/kopete/contactlist/kopeteaddrbookexport.h b/kopete/kopete/contactlist/kopeteaddrbookexport.h new file mode 100644 index 00000000..b4437c4e --- /dev/null +++ b/kopete/kopete/contactlist/kopeteaddrbookexport.h @@ -0,0 +1,102 @@ +/* + kopeteaddrbookexport.h - Kopete Online Status + + Logic for exporting data acquired from messaging systems to the + KDE address book + + Copyright (c) 2004 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEADDRBOOKEXPORT_H +#define KOPETEADDRBOOKEXPORT_H + +#include +#include + +#include "kopetecontactproperty.h" + +class AddressBookExportUI; +class KDialogBase; +class KListBox; +class KComboBox; + +namespace Kopete +{ +class Contact; +class MetaContact; +} + +class KopeteAddressBookExport : public QObject +{ +public: + KopeteAddressBookExport( QWidget *parent, Kopete::MetaContact *mc ); + ~KopeteAddressBookExport(); + + /** + * Display the dialog + * @return a QDialog return code + */ + int showDialog(); + /** + * Export the data to KABC if changed, omitting any duplicates + */ + void exportData(); + +protected: + /** + * Initialise the GUI labels with labels from KABC + */ + void initLabels(); + /** + * Populate the GUI with data from KABC + */ + void fetchKABCData(); + /** + * Populate a listbox with a given type of phone number + */ + void fetchPhoneNumbers( KListBox * listBox, int type, uint& counter ); + /** + * Populate the GUI with data from IM systems + */ + void fetchIMData(); + /** + * Populate a combobox with a contact's IM data + */ + void populateIM( const Kopete::Contact *contact, const QPixmap &icon, + QComboBox *combo, const Kopete::ContactPropertyTmpl &property ); + /** + * Populate a listbox with a contact's IM data + */ + void populateIM( const Kopete::Contact *contact, const QPixmap &icon, + KListBox *combo, const Kopete::ContactPropertyTmpl &property ); + + /** Check the selected item is not the first (existing KABC) item, or the same as it */ + bool newValue( QComboBox *combo ); + QStringList newValues( KListBox *listBox, uint counter ); + + // the GUI + QWidget *mParent; + KDialogBase * mDialog; + QPixmap mAddrBookIcon; + AddressBookExportUI *mUI; + Kopete::MetaContact *mMetaContact; + KABC::AddressBook *mAddressBook; + KABC::Addressee mAddressee; + + // counters tracking the number of KABC values where multiple values are possible in a single key + uint numEmails, numHomePhones, numWorkPhones, numMobilePhones; + +}; + +#endif diff --git a/kopete/kopete/contactlist/kopeteaddrbookexportui.ui b/kopete/kopete/contactlist/kopeteaddrbookexportui.ui new file mode 100644 index 00000000..9613d44f --- /dev/null +++ b/kopete/kopete/contactlist/kopeteaddrbookexportui.ui @@ -0,0 +1,149 @@ + +AddressBookExportUI + + + AddressBookExportUI + + + + 0 + 0 + 598 + 651 + + + + Merge with Address Book + + + + unnamed + + + + mLblFirstName + + + First name: + + + comboBox1 + + + + + mLblHomePhone + + + Home phone: + + + mPhones + + + + + mWorkPhones + + + Extended + + + + + mMobilePhones + + + Extended + + + + + mHomePhones + + + Extended + + + + + mLblWorkPhone + + + Work phone: + + + mPhones_2 + + + + + mLblMobilePhone + + + Mobile phone: + + + mPhones_3 + + + + + mLblUrl + + + URL: + + + comboBox4 + + + + + mUrl + + + + + mFirstName + + + + + mLastName + + + + + mLblLastName + + + Last name: + + + comboBox2 + + + + + mEmails + + + Extended + + + + + mLblEmail + + + Email: + + + mEmails + + + + + + diff --git a/kopete/kopete/contactlist/kopetecontactlistview.cpp b/kopete/kopete/contactlist/kopetecontactlistview.cpp new file mode 100644 index 00000000..b6b01a2f --- /dev/null +++ b/kopete/kopete/contactlist/kopetecontactlistview.cpp @@ -0,0 +1,2134 @@ +/* + kopetecontactlistview.cpp + + Kopete Contactlist GUI + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2002 by Stefan Gehn + Copyright (c) 2002-2005 by Olivier Goffart + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopetecontactlistview.h" +#include "kopeteuiglobal.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "addcontactwizard.h" +#include "addcontactpage.h" +#include "chatmessagepart.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetecontactlist.h" +#include "kopetemessageevent.h" +#include "kopetegroup.h" +#include "kopetegroupviewitem.h" +#include "kopetemetacontact.h" +#include "kopetemetacontactlvi.h" +#include "kopeteprefs.h" +#include "kopeteprotocol.h" +#include "kopetestatusgroupviewitem.h" +#include "kopetestdaction.h" +#include "kopetechatsessionmanager.h" +#include "kopetecontact.h" +#include "kopetechatsession.h" //needed to send the URL +#include "kopetemessage.h" //needed to send the URL +#include "kopeteglobal.h" +#include "kopetelviprops.h" +#include "kopetegrouplistaction.h" + +#include + +class ContactListViewStrategy; + +class KopeteContactListViewPrivate +{ +public: + std::auto_ptr viewStrategy; + + void updateViewStrategy( KListView *view ); +}; + +class ContactListViewStrategy +{ +public: + ContactListViewStrategy( KListView *view ) + : _listView( view ) + { + view->clear(); + } + virtual ~ContactListViewStrategy() {} + KListView *listView() { return _listView; } + void addCurrentItems() + { + // Add the already existing groups now + QPtrList grps = Kopete::ContactList::self()->groups(); + for ( QPtrListIterator groupIt( grps ); groupIt.current(); ++groupIt ) + addGroup( groupIt.current() ); + + // Add the already existing meta contacts now + QPtrList metaContacts = Kopete::ContactList::self()->metaContacts(); + for ( QPtrListIterator it( metaContacts ); it.current(); ++it ) + addMetaContact( it.current() ); + } + + virtual void addMetaContact( Kopete::MetaContact *mc ) = 0; + virtual void removeMetaContact( Kopete::MetaContact *mc ) = 0; + + virtual void addGroup( Kopete::Group * ) {} + + virtual void addMetaContactToGroup( Kopete::MetaContact *mc, Kopete::Group *gp ) = 0; + virtual void removeMetaContactFromGroup( Kopete::MetaContact *mc, Kopete::Group *gp ) = 0; + virtual void moveMetaContactBetweenGroups( Kopete::MetaContact *mc, Kopete::Group *from, Kopete::Group *to ) = 0; + + virtual void metaContactStatusChanged( Kopete::MetaContact *mc ) = 0; + +protected: + // work around QListView design stupidity. + // GroupViewItem will be QListView-derived, or QListViewItem-derived. + template + void addMetaContactToGroupInner( Kopete::MetaContact *mc, GroupViewItem *gpi ) + { + // check if the contact isn't already in the group + for( QListViewItem *item = gpi->firstChild(); item; item = item->nextSibling() ) + if ( KopeteMetaContactLVI *mci = dynamic_cast(item) ) + if ( mci->metaContact() == mc ) + return; + (void) new KopeteMetaContactLVI( mc, gpi ); + } + + template + void removeMetaContactFromGroupInner( Kopete::MetaContact *mc, GroupViewItem *gpi ) + { + KopeteMetaContactLVI* mci; + QListViewItem* item = gpi->firstChild(); + while(item) { + mci = dynamic_cast(item); + item = item->nextSibling(); + + if ( mci && mci->metaContact() == mc ) + delete mci; + } + } + +private: + KListView *_listView; +}; + +class ArrangeByGroupsViewStrategy : public ContactListViewStrategy +{ +public: + ArrangeByGroupsViewStrategy( KListView *view ) + : ContactListViewStrategy( view ) + { + addCurrentItems(); + } + + void addMetaContact( Kopete::MetaContact *mc ) + { + // create group items + Kopete::GroupList list = mc->groups(); + for ( Kopete::Group *gp = list.first(); gp; gp = list.next() ) + // will check to see if the contact is already in the group. + // this is inefficient but makes this function idempotent. + addMetaContactToGroup( mc, gp ); + } + void removeMetaContact( Kopete::MetaContact *mc ) + { + // usually, the list item will be deleted when the KMC is. however, we still + // need to make sure that the item count of the groups is correct. + // as a bonus, this allows us to remove a MC from the contact list without deleting it. + Kopete::GroupList list = mc->groups(); + for ( Kopete::Group *gp = list.first(); gp; gp = list.next() ) + removeMetaContactFromGroup( mc, gp ); + } + + void addGroup( Kopete::Group *group ) + { + (void) findOrCreateGroupItem( group ); + } + + void addMetaContactToGroup( Kopete::MetaContact *mc, Kopete::Group *gp ) + { + if ( KopeteGroupViewItem *gpi = findOrCreateGroupItem( gp ) ) + addMetaContactToGroupInner( mc, gpi ); + else + addMetaContactToGroupInner( mc, listView() ); + } + void removeMetaContactFromGroup( Kopete::MetaContact *mc, Kopete::Group *gp ) + { + if ( gp->type() == Kopete::Group::TopLevel ) + removeMetaContactFromGroupInner( mc, listView() ); + else if ( KopeteGroupViewItem *gpi = findGroupItem( gp ) ) + { + removeMetaContactFromGroupInner( mc, gpi ); + + // update the group's display of its number of children. + // TODO: make the KopeteGroupViewItem not need this, by overriding insertItem and takeItem + gpi->refreshDisplayName(); + + // remove the temporary group if it's empty + if ( gpi->childCount() == 0 ) + if ( gp->type() == Kopete::Group::Temporary ) + delete gpi; + } + } + void moveMetaContactBetweenGroups( Kopete::MetaContact *mc, Kopete::Group *from, Kopete::Group *to ) + { + // TODO: use takeItem and insertItem, and mci->movedGroup + addMetaContactToGroup( mc, to ); + removeMetaContactFromGroup( mc, from ); + } + void metaContactStatusChanged( Kopete::MetaContact * ) {} + +private: + KopeteGroupViewItem *findGroupItem( Kopete::Group *gp ) + { + if ( gp->type() == Kopete::Group::TopLevel ) return 0; + for( QListViewItem *item = listView()->firstChild(); item; item = item->nextSibling() ) + if ( KopeteGroupViewItem *gvi = dynamic_cast(item) ) + if ( gvi->group() == gp ) + return gvi; + return 0; + } + KopeteGroupViewItem *findOrCreateGroupItem( Kopete::Group *gp ) + { + if ( gp->type() == Kopete::Group::TopLevel ) return 0; + if ( KopeteGroupViewItem *item = findGroupItem(gp) ) + return item; + KopeteGroupViewItem *gpi = new KopeteGroupViewItem( gp, listView() ); + // TODO: store as plugin data the expandedness of a group + // currently this requires a 'plugin' for the main UI. + gpi->setOpen( gp->isExpanded() ); + return gpi; + } +}; + +class ArrangeByPresenceViewStrategy : public ContactListViewStrategy +{ +public: + ArrangeByPresenceViewStrategy( KListView *view ) + : ContactListViewStrategy( view ) + , m_onlineItem( new KopeteStatusGroupViewItem( Kopete::OnlineStatus::Online, listView() ) ) + , m_offlineItem( new KopeteStatusGroupViewItem( Kopete::OnlineStatus::Offline, listView() ) ) + { + m_onlineItem->setOpen( true ); + m_offlineItem->setOpen( true ); + addCurrentItems(); + } + + void removeMetaContact( Kopete::MetaContact *mc ) + { + // there's only three places we put metacontacts: online, offline and temporary. + removeMetaContactFromGroupInner( mc, m_onlineItem ); + removeMetaContactFromGroupInner( mc, m_offlineItem ); + if ( m_temporaryItem ) + removeMetaContactFromGroupInner( mc, (KopeteGroupViewItem*)m_temporaryItem ); + } + + void addMetaContact( Kopete::MetaContact *mc ) + { + updateMetaContact( mc ); + } + void addMetaContactToGroup( Kopete::MetaContact *mc, Kopete::Group * ) + { + updateMetaContact( mc ); + } + void removeMetaContactFromGroup( Kopete::MetaContact *mc, Kopete::Group * ) + { + updateMetaContact( mc ); + } + void moveMetaContactBetweenGroups( Kopete::MetaContact *mc, Kopete::Group *, Kopete::Group * ) + { + updateMetaContact( mc ); + } + void metaContactStatusChanged( Kopete::MetaContact *mc ) + { + updateMetaContact( mc ); + } +private: + void updateMetaContact( Kopete::MetaContact *mc ) + { + // split into a ...Inner function and this one to make the short-circuiting logic easier + updateMetaContactInner( mc ); + + // FIXME: these items should do this for themselves... + m_onlineItem->setText(0,i18n("Online contacts (%1)").arg(m_onlineItem->childCount())); + m_offlineItem->setText(0,i18n("Offline contacts (%1)").arg(m_offlineItem->childCount())); + } + void updateMetaContactInner( Kopete::MetaContact *mc ) + { + // this function basically *is* the arrange-by-presence strategy. + // given a metacontact, it removes it from any existing incorrect place and adds + // it to the correct place. usually it does this with takeItem and insertItem. + + // if the metacontact is temporary, it should be only in the temporary group + if ( mc->isTemporary() ) + { + removeMetaContactFromGroupInner( mc, m_onlineItem ); + removeMetaContactFromGroupInner( mc, m_offlineItem ); + + // create temporary item on demand + if ( !m_temporaryItem ) + { + m_temporaryItem = new KopeteGroupViewItem( Kopete::Group::temporary(), listView() ); + m_temporaryItem->setOpen( true ); + } + + addMetaContactToGroupInner( mc, (KopeteGroupViewItem*)m_temporaryItem ); + return; + } + + // if it's not temporary, it should not be in the temporary group + if ( m_temporaryItem ) + { + removeMetaContactFromGroupInner( mc, (KopeteGroupViewItem*)m_temporaryItem ); + + // remove temporary item if empty + if ( m_temporaryItem && m_temporaryItem->childCount() == 0 ) + { + delete (KopeteGroupViewItem*)m_temporaryItem; + } + } + + // check if the contact is already in the correct "group" + QListViewItem *currentGroup = mc->isOnline() ? m_onlineItem : m_offlineItem; + for( QListViewItem *lvi = currentGroup->firstChild(); lvi; lvi = lvi->nextSibling() ) + if ( KopeteMetaContactLVI *kc = dynamic_cast( lvi ) ) + if ( kc->metaContact() == mc ) + return; + + // item not found in the right group; look for it in the other group + QListViewItem *oppositeGroup = mc->isOnline() ? m_offlineItem : m_onlineItem; + for( QListViewItem *lvi = oppositeGroup->firstChild(); lvi; lvi = lvi->nextSibling() ) + { + if ( KopeteMetaContactLVI *kc = dynamic_cast( lvi ) ) + { + if ( kc->metaContact() == mc ) + { + // found: move it over to the right group + oppositeGroup->takeItem(kc); + currentGroup->insertItem(kc); + return; + } + } + } + + // item not found in either online neither offline groups: add it + (void) new KopeteMetaContactLVI( mc, currentGroup ); + } + + KopeteStatusGroupViewItem *m_onlineItem, *m_offlineItem; + QGuardedPtr m_temporaryItem; +}; + +void KopeteContactListViewPrivate::updateViewStrategy( KListView *view ) +{ + // this is a bit nasty, but this function needs changing if we add + // more view strategies anyway, so it should be fine. + bool bSortByGroup = (bool)dynamic_cast(viewStrategy.get()); + if ( !viewStrategy.get() || KopetePrefs::prefs()->sortByGroup() != bSortByGroup ) + { + // delete old strategy first... + viewStrategy.reset( 0 ); + // then create and store a new one + if ( KopetePrefs::prefs()->sortByGroup() ) + viewStrategy.reset( new ArrangeByGroupsViewStrategy(view) ); + else + viewStrategy.reset( new ArrangeByPresenceViewStrategy(view) ); + } +} + +// returns the next item in a depth-first descent of the list view. +// much like QLVI::itemBelow but does not depend on visibility of items, etc. +static QListViewItem *nextItem( QListViewItem *item ) +{ + if ( QListViewItem *it = item->firstChild() ) + return it; + while ( item && !item->nextSibling() ) + item = item->parent(); + if ( !item ) + return 0; + return item->nextSibling(); +} + + + +KopeteContactListView::KopeteContactListView( QWidget *parent, const char *name ) + : Kopete::UI::ListView::ListView( parent, name ) +{ + d = new KopeteContactListViewPrivate; + m_undo=0L; + m_redo=0L; + + mShowAsTree = KopetePrefs::prefs()->treeView(); + if ( mShowAsTree ) + { + setRootIsDecorated( true ); + } + else + { + setRootIsDecorated( false ); + setTreeStepSize( 0 ); + } + + d->updateViewStrategy( this ); + + setFullWidth( true ); + + connect( this, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), + SLOT( slotContextMenu( KListView *, QListViewItem *, const QPoint & ) ) ); + connect( this, SIGNAL( expanded( QListViewItem * ) ), + SLOT( slotExpanded( QListViewItem * ) ) ); + connect( this, SIGNAL( collapsed( QListViewItem * ) ), + SLOT( slotCollapsed( QListViewItem * ) ) ); + connect( this, SIGNAL( executed( QListViewItem *, const QPoint &, int ) ), + SLOT( slotExecuted( QListViewItem *, const QPoint &, int ) ) ); + connect( this, SIGNAL( selectionChanged() ), SLOT( slotViewSelectionChanged() ) ); + connect( this, SIGNAL( itemRenamed( QListViewItem * ) ), + SLOT( slotItemRenamed( QListViewItem * ) ) ); + + connect( KopetePrefs::prefs(), SIGNAL( saved() ), SLOT( slotSettingsChanged() ) ); + + connect( Kopete::ContactList::self(), SIGNAL( selectionChanged() ), + SLOT( slotListSelectionChanged() ) ); + connect( Kopete::ContactList::self(), + SIGNAL( metaContactAdded( Kopete::MetaContact * ) ), + SLOT( slotMetaContactAdded( Kopete::MetaContact * ) ) ); + connect( Kopete::ContactList::self(), + SIGNAL( metaContactRemoved( Kopete::MetaContact * ) ), + SLOT( slotMetaContactDeleted( Kopete::MetaContact * ) ) ); + connect( Kopete::ContactList::self(), SIGNAL( groupAdded( Kopete::Group * ) ), + SLOT( slotGroupAdded( Kopete::Group * ) ) ); + + connect( Kopete::ChatSessionManager::self(), SIGNAL( newEvent( Kopete::MessageEvent * ) ), + this, SLOT( slotNewMessageEvent( Kopete::MessageEvent * ) ) ); + + connect( this, SIGNAL( dropped( QDropEvent *, QListViewItem *, QListViewItem * ) ), + this, SLOT( slotDropped( QDropEvent *, QListViewItem *, QListViewItem * ) ) ); + + connect( &undoTimer, SIGNAL(timeout()) , this, SLOT (slotTimeout() ) ); + + addColumn( i18n( "Contacts" ), 0 ); //add an unique colums to add every contact + header()->hide(); // and hide the ugly header which show the single word "Contacts" + + setAutoOpen( true ); + setDragEnabled( true ); + setAcceptDrops( true ); + setItemsMovable( false ); + setDropVisualizer( false ); + setDropHighlighter( true ); + setSelectionMode( QListView::Extended ); + + // Load in the user's initial settings + slotSettingsChanged(); +} + +void KopeteContactListView::initActions( KActionCollection *ac ) +{ + actionUndo = KStdAction::undo( this , SLOT( slotUndo() ) , ac ); + actionRedo = KStdAction::redo( this , SLOT( slotRedo() ) , ac ); + actionUndo->setEnabled(false); + actionRedo->setEnabled(false); + + + new KAction( i18n( "Create New Group..." ), 0, 0, this, SLOT( addGroup() ), + ac, "AddGroup" ); + + actionSendMessage = KopeteStdAction::sendMessage( + this, SLOT( slotSendMessage() ), ac, "contactSendMessage" ); + actionStartChat = KopeteStdAction::chat( this, SLOT( slotStartChat() ), + ac, "contactStartChat" ); + + actionMove = new KopeteGroupListAction( i18n( "&Move To" ), QString::fromLatin1( "editcut" ), + 0, this, SLOT( slotMoveToGroup() ), ac, "contactMove" ); + actionCopy = new KopeteGroupListAction( i18n( "&Copy To" ), QString::fromLatin1( "editcopy" ), 0, + this, SLOT( slotCopyToGroup() ), ac, "contactCopy" ); + + actionRemove = KopeteStdAction::deleteContact( this, SLOT( slotRemove() ), + ac, "contactRemove" ); + actionSendEmail = new KAction( i18n( "Send Email..." ), QString::fromLatin1( "mail_generic" ), + 0, this, SLOT( slotSendEmail() ), ac, "contactSendEmail" ); + /* this actionRename is buggy, and useless with properties, removed in kopeteui.rc*/ + actionRename = new KAction( i18n( "Rename" ), "filesaveas", 0, + this, SLOT( slotRename() ), ac, "contactRename" ); + actionSendFile = KopeteStdAction::sendFile( this, SLOT( slotSendFile() ), + ac, "contactSendFile" ); + + actionAddContact = new KActionMenu( i18n( "&Add Contact" ), + QString::fromLatin1( "add_user" ), ac , "contactAddContact" ); + actionAddContact->popupMenu()->insertTitle( i18n("Select Account") ); + + actionAddTemporaryContact = new KAction( i18n( "Add to Your Contact List" ), "add_user", 0, + this, SLOT( slotAddTemporaryContact() ), ac, "contactAddTemporaryContact" ); + + connect( Kopete::ContactList::self(), SIGNAL( metaContactSelected( bool ) ), this, SLOT( slotMetaContactSelected( bool ) ) ); + + connect( Kopete::AccountManager::self(), SIGNAL(accountRegistered( Kopete::Account* )), SLOT(slotAddSubContactActionNewAccount(Kopete::Account*))); + connect( Kopete::AccountManager::self(), SIGNAL(accountUnregistered( const Kopete::Account* )), SLOT(slotAddSubContactActionAccountDeleted(const Kopete::Account *))); + + actionProperties = new KAction( i18n( "&Properties" ), "edit_user", Qt::Key_Alt + Qt::Key_Return, + this, SLOT( slotProperties() ), ac, "contactProperties" ); + + // Update enabled/disabled actions + slotViewSelectionChanged(); +} + +KopeteContactListView::~KopeteContactListView() +{ + delete d; +} + +void KopeteContactListView::slotAddSubContactActionNewAccount(Kopete::Account* account) +{ + KAction *action = new KAction( account->accountLabel(), account->accountIcon(), 0 , this, SLOT(slotAddContact()), account); + m_accountAddContactMap.insert( account, action); + actionAddContact->insert( action ); +} + +void KopeteContactListView::slotAddSubContactActionAccountDeleted(const Kopete::Account *account) +{ + kdDebug(14000) << k_funcinfo << endl; + if ( m_accountAddContactMap.contains( account ) ) + { + KAction *action = m_accountAddContactMap[account]; + m_accountAddContactMap.remove( account ); + actionAddContact->remove( action ); + } +} + +void KopeteContactListView::slotMetaContactAdded( Kopete::MetaContact *mc ) +{ + d->viewStrategy->addMetaContact( mc ); + + connect( mc, SIGNAL( addedToGroup( Kopete::MetaContact *, Kopete::Group * ) ), + SLOT( slotAddedToGroup( Kopete::MetaContact *, Kopete::Group * ) ) ); + connect( mc, SIGNAL( removedFromGroup( Kopete::MetaContact *, Kopete::Group * ) ), + SLOT( slotRemovedFromGroup( Kopete::MetaContact *, Kopete::Group * ) ) ); + connect( mc, SIGNAL( movedToGroup( Kopete::MetaContact *, Kopete::Group *, Kopete::Group * ) ), + SLOT( slotMovedToGroup( Kopete::MetaContact *, Kopete::Group *, Kopete::Group * ) ) ); + connect( mc, SIGNAL( onlineStatusChanged( Kopete::MetaContact *, Kopete::OnlineStatus::StatusType ) ), + SLOT( slotContactStatusChanged( Kopete::MetaContact * ) ) ); +} + +void KopeteContactListView::slotMetaContactDeleted( Kopete::MetaContact *mc ) +{ + d->viewStrategy->removeMetaContact( mc ); +} + +void KopeteContactListView::slotMetaContactSelected( bool sel ) +{ + bool set = sel; + + if( sel ) + { + Kopete::MetaContact *kmc = Kopete::ContactList::self()->selectedMetaContacts().first(); + set = sel && kmc->isReachable(); + actionAddTemporaryContact->setEnabled( sel && kmc->isTemporary() ); + } + else + { + actionAddTemporaryContact->setEnabled(false); + } + + actionSendMessage->setEnabled( set ); + actionStartChat->setEnabled( set ); + actionMove->setEnabled( sel ); // TODO: make available for several contacts + actionCopy->setEnabled( sel ); // TODO: make available for several contacts +} + +void KopeteContactListView::slotAddedToGroup( Kopete::MetaContact *mc, Kopete::Group *to ) +{ + d->viewStrategy->addMetaContactToGroup( mc, to ); +} + +void KopeteContactListView::slotRemovedFromGroup( Kopete::MetaContact *mc, Kopete::Group *from ) +{ + d->viewStrategy->removeMetaContactFromGroup( mc, from ); +} + +void KopeteContactListView::slotMovedToGroup( Kopete::MetaContact *mc, + Kopete::Group *from, Kopete::Group *to ) +{ + d->viewStrategy->moveMetaContactBetweenGroups( mc, from, to ); +} + +void KopeteContactListView::removeContact( Kopete::MetaContact *c ) +{ + d->viewStrategy->removeMetaContact( c ); +} + +void KopeteContactListView::addGroup() +{ + QString groupName = + KInputDialog::getText( i18n( "New Group" ), + i18n( "Please enter the name for the new group:" ) ); + + if ( !groupName.isEmpty() ) + addGroup( groupName ); +} + +void KopeteContactListView::addGroup( const QString &groupName ) +{ + d->viewStrategy->addGroup( Kopete::ContactList::self()->findGroup(groupName) ); +} + +void KopeteContactListView::slotGroupAdded( Kopete::Group *group ) +{ + d->viewStrategy->addGroup( group ); +} + +void KopeteContactListView::slotExpanded( QListViewItem *item ) +{ + KopeteGroupViewItem *groupLVI = dynamic_cast( item ); + if ( groupLVI ) + { + groupLVI->group()->setExpanded( true ); + groupLVI->updateIcon(); + } + + //workaround a bug in qt which make the items of a closed item not sorted. (qt 3.3.4 here) + delayedSort(); +} + +void KopeteContactListView::slotCollapsed( QListViewItem *item ) +{ + KopeteGroupViewItem *groupLVI = dynamic_cast( item ); + if ( groupLVI ) + { + groupLVI->group()->setExpanded( false ); + groupLVI->updateIcon(); + } +} + +void KopeteContactListView::slotContextMenu( KListView * /*listview*/, + QListViewItem *item, const QPoint &point ) +{ + // FIXME: this code should be moved to the various list view item classes. + KopeteMetaContactLVI *metaLVI = dynamic_cast( item ); + KopeteGroupViewItem *groupvi = dynamic_cast( item ); + + if ( item && !item->isSelected() ) + { + clearSelection(); + item->setSelected( true ); + } + + if ( !item ) + { + clearSelection(); + //Clear selection doesn't update lists of selected contact if the item is onlt heilighted (see bug 106090) + Kopete::ContactList::self()->setSelectedItems( QPtrList() , QPtrList() ); + } + + int nb = Kopete::ContactList::self()->selectedMetaContacts().count() + + Kopete::ContactList::self()->selectedGroups().count(); + + KMainWindow *window = dynamic_cast(topLevelWidget()); + if ( !window ) + { + kdError( 14000 ) << k_funcinfo << "Main window not found, unable to display context-menu; " + << "Kopete::UI::Global::mainWidget() = " << Kopete::UI::Global::mainWidget() << endl; + return; + } + + if ( metaLVI && nb == 1 ) + { + int px = mapFromGlobal( point ).x() - ( header()->sectionPos( header()->mapToIndex( 0 ) ) + + treeStepSize() * ( item->depth() + ( rootIsDecorated() ? 1 : 0 ) ) + itemMargin() ); + int py = mapFromGlobal( point ).y() - itemRect( item ).y() - (header()->isVisible() ? header()->height() : 0) ; + + //kdDebug( 14000 ) << k_funcinfo << "x: " << px << ", y: " << py << endl; + Kopete::Contact *c = metaLVI->contactForPoint( QPoint( px, py ) ) ; + if ( c ) + { + KPopupMenu *p = c->popupMenu(); + connect( p, SIGNAL( aboutToHide() ), p, SLOT( deleteLater() ) ); + p->popup( point ); + } + else + { + KPopupMenu *popup = dynamic_cast( + window->factory()->container( "contact_popup", window ) ); + if ( popup ) + { + QString title = i18n( "Translators: format: ' ()'", "%1 (%2)" ). + arg( metaLVI->metaContact()->displayName(), metaLVI->metaContact()->statusString() ); + + if ( title.length() > 43 ) + title = title.left( 40 ) + QString::fromLatin1( "..." ); + + if ( popup->title( 0 ).isNull() ) + popup->insertTitle ( title, 0, 0 ); + else + popup->changeTitle ( 0, title ); + + // Submenus for separate contact actions + bool sep = false; //FIXME: find if there is already a separator in the end - Olivier + QPtrList it = metaLVI->metaContact()->contacts(); + for( Kopete::Contact *c = it.first(); c; c = it.next() ) + { + if( sep ) + { + popup->insertSeparator(); + sep = false; + } + + KPopupMenu *contactMenu = it.current()->popupMenu(); + connect( popup, SIGNAL( aboutToHide() ), contactMenu, SLOT( deleteLater() ) ); + QString nick=c->property(Kopete::Global::Properties::self()->nickName()).value().toString(); + QString text= nick.isEmpty() ? c->contactId() : i18n( "Translators: format: ' ()'", "%2 <%1>" ). arg( c->contactId(), nick ); + text=text.replace("&","&&"); // cf BUG 115449 + + if ( text.length() > 41 ) + text = text.left( 38 ) + QString::fromLatin1( "..." ); + + popup->insertItem( c->onlineStatus().iconFor( c, 16 ), text , contactMenu ); + } + + popup->popup( point ); + } + } + } + else if ( groupvi && nb == 1 ) + { + KPopupMenu *popup = dynamic_cast( + window->factory()->container( "group_popup", window ) ); + if ( popup ) + { + QString title = groupvi->group()->displayName(); + if ( title.length() > 32 ) + title = title.left( 30 ) + QString::fromLatin1( "..." ); + + if( popup->title( 0 ).isNull() ) + popup->insertTitle( title, 0, 0 ); + else + popup->changeTitle( 0, title ); + + popup->popup( point ); + } + } + else if ( nb >= 1 ) + { + KPopupMenu *popup = dynamic_cast( + window->factory()->container( "contactlistitems_popup", window ) ); + if ( popup ) + popup->popup( point ); + } + else + { + KPopupMenu *popup = dynamic_cast( + window->factory()->container( "contactlist_popup", window ) ); + if ( popup ) + { + if ( popup->title( 0 ).isNull() ) + popup->insertTitle( i18n( "Kopete" ), 0, 0 ); + + popup->popup( point ); + } + } +} + +void KopeteContactListView::slotShowAddContactDialog() +{ + ( new AddContactWizard( Kopete::UI::Global::mainWidget() ) )->show(); +} + +void KopeteContactListView::slotSettingsChanged( void ) +{ + mShowAsTree = KopetePrefs::prefs()->treeView(); + if ( mShowAsTree ) + { + setRootIsDecorated( true ); + setTreeStepSize( 20 ); + } + else + { + setRootIsDecorated( false ); + setTreeStepSize( 0 ); + } + + // maybe setEffects should read these from KopetePrefs itself? + Kopete::UI::ListView::Item::setEffects( KopetePrefs::prefs()->contactListAnimation(), + KopetePrefs::prefs()->contactListFading(), + KopetePrefs::prefs()->contactListFolding() ); + + d->updateViewStrategy( this ); + + slotUpdateAllGroupIcons(); + update(); +} + +void KopeteContactListView::slotUpdateAllGroupIcons() +{ + // FIXME: groups can (should?) do this for themselves + // HACK: assume all groups are top-level. works for now, until the fixme above is dealt with + for ( QListViewItem *it = firstChild(); it; it = it->nextSibling() ) + if ( KopeteGroupViewItem *gpi = dynamic_cast( it ) ) + gpi->updateIcon(); +} + +void KopeteContactListView::slotExecuted( QListViewItem *item, const QPoint &p, int /* col */ ) +{ + item->setSelected( false ); + KopeteMetaContactLVI *metaContactLVI = dynamic_cast( item ); + + QPoint pos = viewport()->mapFromGlobal( p ); + Kopete::Contact *c = 0L; + if ( metaContactLVI ) + { + // Try if we are clicking a protocol icon. If so, open a direct + // connection for that protocol + QRect r = itemRect( item ); + QPoint relativePos( pos.x() - r.left() - ( treeStepSize() * + ( item->depth() + ( rootIsDecorated() ? 1 : 0 ) ) + + itemMargin() ), pos.y() - r.top() ); + c = metaContactLVI->contactForPoint( relativePos ); + if( c ) + c->execute(); + else + metaContactLVI->execute(); + } +} + +void KopeteContactListView::slotContactStatusChanged( Kopete::MetaContact *mc ) +{ + d->viewStrategy->metaContactStatusChanged( mc ); +} + +void KopeteContactListView::slotDropped(QDropEvent *e, QListViewItem *, QListViewItem *after) +{ + if(!acceptDrag(e)) + return; + + KopeteMetaContactLVI *dest_metaLVI=dynamic_cast(after); + KopeteGroupViewItem *dest_groupLVI=dynamic_cast(after); + + if( const_cast( e->source() ) == this ) + { + QPtrListIterator it( m_selectedContacts ); + + while ( it.current() ) + { + Kopete::Contact *source_contact=0L; + KopeteMetaContactLVI *source_metaLVI = it.current(); + ++it; + + if(source_metaLVI) + source_contact = source_metaLVI->contactForPoint( m_startDragPos ); + + if(source_metaLVI && dest_groupLVI) + { + if(source_metaLVI->group() == dest_groupLVI->group()) + return; + + if(source_metaLVI->metaContact()->isTemporary()) + { + addDraggedContactToGroup(source_metaLVI->metaContact(),dest_groupLVI->group()); + } + else + { + moveDraggedContactToGroup( source_metaLVI->metaContact(), + source_metaLVI->group(), dest_groupLVI->group() ); + } + } + else if(source_metaLVI && !dest_metaLVI && !dest_groupLVI) + { + if ( source_metaLVI->group()->type() == Kopete::Group::TopLevel ) + return; + + if(source_metaLVI->metaContact()->isTemporary()) + { + addDraggedContactToGroup(source_metaLVI->metaContact() , Kopete::Group::topLevel() ); + } + else + { + moveDraggedContactToGroup( source_metaLVI->metaContact(), + source_metaLVI->group(), Kopete::Group::topLevel() ); + } + } + else if(source_contact && dest_metaLVI) //we are moving a contact to another metacontact + { + if(source_metaLVI->metaContact()->isTemporary()) + { + addDraggedContactToMetaContact( source_contact,dest_metaLVI->metaContact() ); + } + else + { + UndoItem *u=new UndoItem; + u->type=UndoItem::MetaContactChange; + u->metacontact=source_metaLVI->metaContact(); + u->group=source_metaLVI->group(); + u->args << source_contact->protocol()->pluginId() << source_contact->account()->accountId() << source_contact->contactId(); + u->args << source_metaLVI->metaContact()->displayName(); + insertUndoItem(u); + + source_contact->setMetaContact(dest_metaLVI->metaContact()); + } + } + } + } + else if( e->provides("kopete/x-contact") ) + { + QString contactInfo = QString::fromUtf8( e->encodedData("kopete/x-contact") ); + QString protocolId = contactInfo.section( QChar( 0xE000 ), 0, 0 ); + QString accountId = contactInfo.section( QChar( 0xE000 ), 1, 1 ); + QString contactId = contactInfo.section( QChar( 0xE000 ), 2 ); + + addDraggedContactByInfo( protocolId, accountId, contactId, after ); + + } + else if( e->provides("text/uri-list") ) + { + if ( !QUriDrag::canDecode( e ) ) + { + e->ignore(); + return; + } + + KURL::List urlList; + KURLDrag::decode( e, urlList ); + + for ( KURL::List::Iterator it = urlList.begin(); it != urlList.end(); ++it ) + { + KURL url = (*it); + if( url.protocol() == QString::fromLatin1("kopetemessage") ) + { + //Add a contact + addDraggedContactByInfo( url.queryItem("protocolId"), + url.queryItem("accountId"), url.host(), after ); + } + else if( dest_metaLVI ) + { + QPoint p = contentsToViewport(e->pos()); + int px = p.x() - ( header()->sectionPos( header()->mapToIndex( 0 ) ) + + treeStepSize() * ( dest_metaLVI->depth() + ( rootIsDecorated() ? 1 : 0 ) ) + itemMargin() ); + int py = p.y() - itemRect( dest_metaLVI ).y(); + + Kopete::Contact *c = dest_metaLVI->contactForPoint( QPoint( px, py ) ); + + if( url.isLocalFile() ) + { + //send a file + if(c) + c->sendFile( url ); + else + dest_metaLVI->metaContact()->sendFile( url ); + } + else + { + //this is a URL, send the URL in a message + if(!c) + { + // We need to know which contact was chosen as the preferred + // in order to message it + c = dest_metaLVI->metaContact()->execute(); + } + + if (!c) + return; + + Kopete::Message msg(c->account()->myself(), c, url.url(), + Kopete::Message::Outbound); + c->manager(Kopete::Contact::CanCreate)->sendMessage(msg); + } + } + } + e->acceptAction(); + } +} + +void KopeteContactListView::moveDraggedContactToGroup( Kopete::MetaContact *contact, Kopete::Group *from, Kopete::Group *to ) +{ + contact->moveToGroup( from, to ); + + insertUndoItem( new UndoItem( UndoItem::MetaContactCopy , contact, to ) ); + UndoItem *u=new UndoItem( UndoItem::MetaContactRemove, contact, to ); + u->isStep=false; + insertUndoItem(u); +} + +void KopeteContactListView::addDraggedContactToGroup( Kopete::MetaContact *contact, Kopete::Group *group ) +{ + int r=KMessageBox::questionYesNo( Kopete::UI::Global::mainWidget(), + i18n( "Would you like to add %1 to your contact list as a member of %2?" ) + .arg( contact->displayName(), group->displayName() ), + i18n( "Kopete" ), i18n("Add"), i18n("Do Not Add"), + "addTemporaryWhenMoving" ); + + if( r == KMessageBox::Yes ) + { + contact->setTemporary( false, group ); + Kopete::ContactList::self()->addMetaContact( contact ); + insertUndoItem( new UndoItem( UndoItem::MetaContactAdd, contact, group ) ); + } +} + +void KopeteContactListView::addDraggedContactToMetaContact( Kopete::Contact *contact, Kopete::MetaContact *parent ) +{ + int r = KMessageBox::questionYesNo( Kopete::UI::Global::mainWidget(), + i18n( "Would you like to add %1 to your contact list as a child contact of %2?" ) + .arg( contact->contactId(), parent->displayName() ), + i18n( "Kopete" ), i18n("Add"), i18n("Do Not Add"), + "addTemporaryWhenMoving" ); + + if( r == KMessageBox::Yes ) + { + contact->setMetaContact(parent); + + UndoItem *u=new UndoItem; + u->type=UndoItem::ContactAdd; + u->args << contact->protocol()->pluginId() << contact->account()->accountId() << contact->contactId(); + insertUndoItem(u); + } +} + +void KopeteContactListView::addDraggedContactByInfo( const QString &protocolId, const QString &accountId, + const QString &contactId, QListViewItem *after ) +{ + kdDebug(14000) << k_funcinfo << "protocolId=" << protocolId << + ", accountId=" << accountId << ", contactId=" << contactId << endl; + + Kopete::Account *account = Kopete::AccountManager::self()->findAccount( protocolId,accountId ); + if( account ) + { + QDict contacts = account->contacts(); + Kopete::Contact *source_contact = contacts[ contactId ]; + + if( source_contact ) + { + if( source_contact->metaContact()->isTemporary() ) + { + KopeteMetaContactLVI *dest_metaLVI=dynamic_cast(after); + KopeteGroupViewItem *dest_groupLVI=dynamic_cast(after); + + if( dest_metaLVI ) + { + addDraggedContactToMetaContact( source_contact, dest_metaLVI->metaContact() ); + } + else if( dest_groupLVI ) + { + addDraggedContactToGroup( source_contact->metaContact(),dest_groupLVI->group() ); + } + else + { + addDraggedContactToGroup( source_contact->metaContact(), Kopete::Group::topLevel() ); + } + } + else + { + KMessageBox::sorry( Kopete::UI::Global::mainWidget(), + i18n("This contact is already on your contact list. It is a child contact of %1") + .arg( source_contact->metaContact()->displayName() ) + ); + } + } + } +} + +bool KopeteContactListView::acceptDrag(QDropEvent *e) const +{ + QListViewItem *source=currentItem(); + QListViewItem *parent; + QListViewItem *afterme; + // Due to a little design problem in KListView::findDrop() we can't + // call it directly from a const method until KDE 4.0, but as the + // method is in fact const we can of course get away with a + // const_cast... + const_cast( this )->findDrop( e->pos(), parent, afterme ); + + KopeteMetaContactLVI *dest_metaLVI=dynamic_cast(afterme); + + if( const_cast( e->source() ) == this ) + { + KopeteMetaContactLVI *source_metaLVI=dynamic_cast(source); + KopeteGroupViewItem *dest_groupLVI=dynamic_cast(afterme); + Kopete::Contact *source_contact=0L; + + if(source_metaLVI) + source_contact = source_metaLVI->contactForPoint( m_startDragPos ); + + if( source_metaLVI && dest_groupLVI && !source_contact) + { //we are moving a metacontact to another group + if(source_metaLVI->group() == dest_groupLVI->group()) + return false; + if ( dest_groupLVI->group()->type() == Kopete::Group::Temporary ) + return false; + // if(source_metaLVI->metaContact()->isTemporary()) + // return false; + return true; + } + else if(source_metaLVI && !dest_metaLVI && !dest_groupLVI && !source_contact) + { //we are moving a metacontact to toplevel + if ( source_metaLVI->group()->type() == Kopete::Group::TopLevel ) + return false; + // if(source_metaLVI->metaContact()->isTemporary()) + // return false; + + return true; + } + else if(source_contact && dest_metaLVI) //we are moving a contact to another metacontact + { + if(source_contact->metaContact() == dest_metaLVI->metaContact() ) + return false; + if(dest_metaLVI->metaContact()->isTemporary()) + return false; + return true; + } +/* else if(source_groupLVI && dest_groupLVI) //we are moving a group to another group + { + if(dest_groupLVI->group() == Kopete::Group::temporary) + return false; + if(source_groupLVI->group() == Kopete::Group::temporary) + return false; + if(source_groupLVI->group()->parentGroup() == dest_groupLVI->group() ) + return false; + Kopete::Group *g=dest_groupLVI->group()->parentGroup(); + while(g && g != Kopete::Group::toplevel) + { + if(g==source_groupLVI->group()) + return false; + g=g->parentGroup(); + } + return true; + } + else if(source_groupLVI && !dest_groupLVI && dest_metaLVI) //we are moving a group to toplevel + { + if(source_groupLVI->group() == Kopete::Group::temporary) + return false; + if(source_groupLVI->group()->parentGroup() == Kopete::Group::toplevel) + return false; + return true; + }*/ + } + else + { + if( e->provides( "text/uri-list" ) ) + { + //we are sending a file (or dragging from the chat view) + if( dest_metaLVI ) + { + QPoint p=contentsToViewport(e->pos()); + int px = p.x() - ( header()->sectionPos( header()->mapToIndex( 0 ) ) + + treeStepSize() * ( dest_metaLVI->depth() + ( rootIsDecorated() ? 1 : 0 ) ) + itemMargin() ); + int py = p.y() - itemRect( dest_metaLVI ).y(); + Kopete::Contact *c = dest_metaLVI->contactForPoint( QPoint( px, py ) ) ; + + if( c ? !c->isReachable() : !dest_metaLVI->metaContact()->isReachable() ) + return false; //If the pointed contact is not reachable, abort + + if( c ? c->canAcceptFiles() : dest_metaLVI->metaContact()->canAcceptFiles() ) + { + // If the pointed contact, or the metacontact if no contact are + // pointed can accept file, return true in everycase + return true; + } + } + + if ( !QUriDrag::canDecode(e) ) + return false; + + KURL::List urlList; + KURLDrag::decode( e, urlList ); + + for ( KURL::List::Iterator it = urlList.begin(); it != urlList.end(); ++it ) + { + if( (*it).protocol() != QString::fromLatin1("kopetemessage") && (*it).isLocalFile() ) + return false; //we can't send links if a locale file is in link + } + + return true; //we will send a link + } + else if( e->provides( "application/x-qlistviewitem" ) ) + { + //Coming from chat members + return true; + } + else + { + QString text; + QTextDrag::decode(e, text); + kdDebug(14000) << k_funcinfo << "drop with mimetype:" << e->format() << " data as text:" << text << endl; + } + + } + + return false; +} + +void KopeteContactListView::findDrop(const QPoint &pos, QListViewItem *&parent, + QListViewItem *&after) +{ + //Since KDE 3.1.1 , the original find Drop return 0L for afterme if the group is open. + //This woraround allow us to keep the highlight of the item, and give always a correct position + parent=0L; + QPoint p (contentsToViewport(pos)); + after=itemAt(p); +} + + +void KopeteContactListView::contentsMousePressEvent( QMouseEvent *e ) +{ + KListView::contentsMousePressEvent( e ); + if (e->button() == LeftButton ) + { + QPoint p=contentsToViewport(e->pos()); + QListViewItem *i=itemAt( p ); + if( i ) + { + //Maybe we are starting a drag? + //memorize the position to know later if the user move a small contacticon + + int px = p.x() - ( header()->sectionPos( header()->mapToIndex( 0 ) ) + + treeStepSize() * ( i->depth() + ( rootIsDecorated() ? 1 : 0 ) ) + itemMargin() ); + int py = p.y() - itemRect( i ).y(); + + m_startDragPos = QPoint( px , py ); + } + } +} + +void KopeteContactListView::slotNewMessageEvent(Kopete::MessageEvent *event) +{ + Kopete::Message msg=event->message(); + //only for single chat + if(msg.from() && msg.to().count()==1) + { + Kopete::MetaContact *m=msg.from()->metaContact(); + if(!m) + return; + + for ( QListViewItem *item = firstChild(); item; item = nextItem(item) ) + if ( KopeteMetaContactLVI *li = dynamic_cast(item) ) + if ( li->metaContact() == m ) + li->catchEvent(event); + } +} + +QDragObject *KopeteContactListView::dragObject() +{ + // Discover what the drag started on. + // If it's a MetaContactLVI, it was either on the MCLVI itself + // or on one of the child contacts + // Once we know this, + // we set the pixmap for the drag to the MC's pixmap + // or the child contact's small icon + + QListViewItem *currentLVI = currentItem(); + if( !currentLVI ) + return 0L; + + KopeteMetaContactLVI *metaLVI = dynamic_cast( currentLVI ); + if( !metaLVI ) + return 0L; + + QPixmap pm; + Kopete::Contact *c = metaLVI->contactForPoint( m_startDragPos ); + KMultipleDrag *drag = new KMultipleDrag( this ); + drag->addDragObject( new QStoredDrag("application/x-qlistviewitem", 0L ) ); + + QStoredDrag *d = new QStoredDrag("kopete/x-metacontact", 0L ); + d->setEncodedData( metaLVI->metaContact()->metaContactId().utf8() ); + drag->addDragObject( d ); + + if ( c ) // dragging a contact + { + QStoredDrag *d = new QStoredDrag("kopete/x-contact", 0L ); + d->setEncodedData( QString( c->protocol()->pluginId() +QChar( 0xE000 )+ c->account()->accountId() +QChar( 0xE000 )+ c->contactId() ).utf8() ); + drag->addDragObject( d ); + + pm = c->onlineStatus().iconFor( c, 12 ); // FIXME: fixed icon scaling + } + else // dragging a metacontact + { + // FIXME: first start at rendering the whole MC incl small icons + // into a pixmap to drag - anyone know how to complete this? + //QPainter p( pm ); + //source_metaLVI->paintCell( p, cg?, width(), 0, 0 ); + pm = SmallIcon( metaLVI->metaContact()->statusIcon() ); + } + + KABC::Addressee address = KABC::StdAddressBook::self()->findByUid( + metaLVI->metaContact()->metaContactId() + ); + + if( !address.isEmpty() ) + { + drag->addDragObject( new QTextDrag( address.fullEmail(), 0L ) ); + KABC::VCardConverter converter; + QString vcard = converter.createVCard( address ); + if( !vcard.isNull() ) + { + QStoredDrag *vcardDrag = new QStoredDrag("text/x-vcard", 0L ); + vcardDrag->setEncodedData( vcard.utf8() ); + drag->addDragObject( vcardDrag ); + } + } + + //QSize s = pm.size(); + drag->setPixmap( pm /*, QPoint( s.width() , s.height() )*/ ); + + return drag; +} + +void KopeteContactListView::slotViewSelectionChanged() +{ + QPtrList contacts; + QPtrList groups; + + m_selectedContacts.clear(); + m_selectedGroups.clear(); + + QListViewItemIterator it( this ); + while ( it.current() ) + { + QListViewItem *item = it.current(); + ++it; + + if ( item->isSelected() ) + { + KopeteMetaContactLVI *metaLVI=dynamic_cast(item); + if(metaLVI) + { + m_selectedContacts.append( metaLVI ); + if(!contacts.contains(metaLVI->metaContact())) + contacts.append( metaLVI->metaContact() ); + } + KopeteGroupViewItem *groupLVI=dynamic_cast(item); + if(groupLVI) + { + m_selectedGroups.append( groupLVI ); + if(!groups.contains(groupLVI->group())) + groups.append( groupLVI->group() ); + + } + } + } + + // will cause slotListSelectionChanged to be called to update our actions. + Kopete::ContactList::self()->setSelectedItems(contacts , groups); +} + +void KopeteContactListView::slotListSelectionChanged() +{ + QPtrList contacts = Kopete::ContactList::self()->selectedMetaContacts(); + QPtrList groups = Kopete::ContactList::self()->selectedGroups(); + + //TODO: update the list to select the items that should be selected. + // make sure slotViewSelectionChanged is *not* called. + updateActionsForSelection( contacts, groups ); +} + +void KopeteContactListView::updateActionsForSelection( + QPtrList contacts, QPtrList groups ) +{ + bool singleContactSelected = groups.isEmpty() && contacts.count() == 1; + bool inkabc=false; + if(singleContactSelected) + { + QString kabcid=contacts.first()->metaContactId(); + inkabc= !kabcid.isEmpty() && !kabcid.contains(":"); + } + + actionSendFile->setEnabled( singleContactSelected && contacts.first()->canAcceptFiles()); + actionAddContact->setEnabled( singleContactSelected && !contacts.first()->isTemporary()); + actionSendEmail->setEnabled( inkabc ); + + if( singleContactSelected ) + { + actionRename->setText(i18n("Rename Contact")); + actionRemove->setText(i18n("Remove Contact")); + actionSendMessage->setText(i18n("Send Single Message...")); + actionRename->setEnabled(true); + actionRemove->setEnabled(true); + actionAddContact->setText(i18n("&Add Subcontact")); + actionAddContact->setEnabled(!contacts.first()->isTemporary()); + } + else if( groups.count() == 1 && contacts.isEmpty() ) + { + actionRename->setText(i18n("Rename Group")); + actionRemove->setText(i18n("Remove Group")); + actionSendMessage->setText(i18n("Send Message to Group")); + actionRename->setEnabled(true); + actionRemove->setEnabled(true); + actionSendMessage->setEnabled(true); + actionAddContact->setText(i18n("&Add Contact to Group")); + actionAddContact->setEnabled(groups.first()->type()==Kopete::Group::Normal); + } + else + { + actionRename->setText(i18n("Rename")); + actionRemove->setText(i18n("Remove")); + actionRename->setEnabled(false); + actionRemove->setEnabled(contacts.count()+groups.count()); + actionAddContact->setEnabled(false); + } + + actionMove->setCurrentItem( -1 ); + actionCopy->setCurrentItem( -1 ); + + actionProperties->setEnabled( ( groups.count() + contacts.count() ) == 1 ); +} + +void KopeteContactListView::slotSendMessage() +{ + Kopete::MetaContact *m=Kopete::ContactList::self()->selectedMetaContacts().first(); + Kopete::Group *group = Kopete::ContactList::self()->selectedGroups().first(); + if(m) + m->sendMessage(); + else + if(group) + group->sendMessage(); +} + +void KopeteContactListView::slotStartChat() +{ + Kopete::MetaContact *m=Kopete::ContactList::self()->selectedMetaContacts().first(); + if(m) + m->startChat(); +} + +void KopeteContactListView::slotSendFile() +{ + Kopete::MetaContact *m=Kopete::ContactList::self()->selectedMetaContacts().first(); + if(m) + m->sendFile(KURL()); +} + + void KopeteContactListView::slotSendEmail() +{ + //I borrowed this from slotSendMessage + Kopete::MetaContact *m=Kopete::ContactList::self()->selectedMetaContacts().first(); + if ( !m->metaContactId().isEmpty( ) ) // check if in kabc + { + KABC::Addressee addressee = KABC::StdAddressBook::self()->findByUid( m->metaContactId() ); + if ( !addressee.isEmpty() ) + { + QString emailAddr = addressee.fullEmail(); + + kdDebug( 14000 ) << "Email: " << emailAddr << "!" << endl; + if ( !emailAddr.isEmpty() ) + kapp->invokeMailer( emailAddr, QString::null ); + else + KMessageBox::queuedMessageBox( this, KMessageBox::Sorry, i18n( "There is no email address set for this contact in the KDE address book." ), i18n( "No Email Address in Address Book" ) ); + } + else + KMessageBox::queuedMessageBox( this, KMessageBox::Sorry, i18n( "This contact was not found in the KDE address book. Check that a contact is selected in the properties dialog." ), i18n( "Not Found in Address Book" ) ); + } + else + KMessageBox::queuedMessageBox( this, KMessageBox::Sorry, i18n( "This contact is not associated with a KDE address book entry, where the email address is stored. Check that a contact is selected in the properties dialog." ), i18n( "Not Found in Address Book" ) ); +} + +void KopeteContactListView::slotMoveToGroup() +{ + KopeteMetaContactLVI *metaLVI=dynamic_cast(currentItem()); + if(!metaLVI) + return; + Kopete::MetaContact *m=metaLVI->metaContact(); + Kopete::Group *g=metaLVI->group(); + + //FIXME What if two groups have the same name? + Kopete::Group *to = actionMove->currentItem() ? + Kopete::ContactList::self()->findGroup( actionMove->currentText() ) : + Kopete::Group::topLevel(); + + if( !to || to->type() == Kopete::Group::Temporary ) + return; + + if(m->isTemporary()) + { + if( KMessageBox::questionYesNo( Kopete::UI::Global::mainWidget(), + i18n( "Would you like to add this contact to your contact list?" ), + i18n( "Kopete" ), i18n("Add"), i18n("Do Not Add"), + "addTemporaryWhenMoving" ) == KMessageBox::Yes ) + { + m->setTemporary(false,to); + + insertUndoItem( new UndoItem( UndoItem::MetaContactAdd , m ) ); + } + } + else if( !m->groups().contains( to ) ) + { + m->moveToGroup( g, to ); + + insertUndoItem( new UndoItem( UndoItem::MetaContactCopy , m , to ) ); + + UndoItem *u=new UndoItem( UndoItem::MetaContactRemove, m, g ); + u->isStep=false; + insertUndoItem(u); + } + + actionMove->setCurrentItem( -1 ); +} + +void KopeteContactListView::slotCopyToGroup() +{ + Kopete::MetaContact *m = + Kopete::ContactList::self()->selectedMetaContacts().first(); + + if(!m) + return; + + //FIXME! what if two groups have the same name? + Kopete::Group *to = actionCopy->currentItem() ? + Kopete::ContactList::self()->findGroup( actionCopy->currentText() ) : + Kopete::Group::topLevel(); + + if( !to || to->type() == Kopete::Group::Temporary ) + return; + + if( m->isTemporary() ) + return; + + if( !m->groups().contains( to ) ) + { + m->addToGroup( to ); + + insertUndoItem( new UndoItem( UndoItem::MetaContactCopy , m , to ) ); + } + + actionCopy->setCurrentItem( -1 ); +} + + + +void KopeteContactListView::slotRemove() +{ + QPtrList contacts = Kopete::ContactList::self()->selectedMetaContacts(); + QPtrList groups = Kopete::ContactList::self()->selectedGroups(); + + if(groups.count() + contacts.count() == 0) + return; + + QStringList items; + for( Kopete::Group *it = groups.first(); it; it = groups.next() ) + { + if(!it->displayName().isEmpty()) + items.append( it->displayName() ); + } + for( Kopete::MetaContact *it = contacts.first(); it; it = contacts.next() ) + { + if(!it->displayName().isEmpty() ) + items.append( it->displayName() ); + } + + if( items.count() <= 1 ) + { + // we are deleting an empty contact + QString msg; + if( !contacts.isEmpty() ) + { + msg = i18n( "Are you sure you want to remove the contact %1" \ + " from your contact list?" ) + .arg( contacts.first()->displayName() ) ; + } + else if( !groups.isEmpty() ) + { + msg = i18n( "Are you sure you want to remove the group %1 " \ + "and all contacts that are contained within it?" ) + .arg( groups.first()->displayName() ); + } + else + return; // this should never happen + + if( KMessageBox::warningContinueCancel( this, msg, i18n( "Remove" ), KGuiItem(i18n("Remove"),"editdelete") , + "askRemovingContactOrGroup" , KMessageBox::Notify | KMessageBox::Dangerous ) != + KMessageBox::Continue ) + { + return; + } + } + else + { + QString msg = groups.isEmpty() ? + i18n( "Are you sure you want to remove these contacts " \ + "from your contact list?" ) : + i18n( "Are you sure you want to remove these groups and " \ + "contacts from your contact list?" ); + + if( KMessageBox::warningContinueCancelList( this, msg, items, i18n("Remove"), + KGuiItem(i18n("Remove"),"editdelete"), "askRemovingContactOrGroup", + KMessageBox::Notify | KMessageBox::Dangerous ) != KMessageBox::Continue ) + { + return; + } + } + + bool undo_step=true; //only the first undo item we will add will be a step + + for( Kopete::MetaContact *mc = contacts.first(); mc; mc = contacts.next() ) + { + if(mc->groups().count()==1 || mc->isTemporary() ) + Kopete::ContactList::self()->removeMetaContact( mc ); + else + { + //try to guess from what group we are removing that contact. + QListViewItemIterator lvi_it( this ); + while ( lvi_it.current() ) + { + QListViewItem *item = lvi_it.current(); + ++lvi_it; + + if ( item->isSelected() ) + { + KopeteMetaContactLVI *metaLVI=dynamic_cast(item); + if(metaLVI && metaLVI->metaContact() == mc ) + { + if(mc->groups().count()==1) + { + Kopete::ContactList::self()->removeMetaContact( mc ); + break; + } + else + { + mc->removeFromGroup(metaLVI->group()); + insertUndoItem( new UndoItem( UndoItem::MetaContactRemove , mc , metaLVI->group() ) ); + m_undo->isStep=undo_step; //if there is several selected contacts. + undo_step=false; + } + //let's continue, it's possible this contact is selected several times + } + } + } + } + } + + for( Kopete::Group *it = groups.first(); it; it = groups.next() ) + { + QPtrList list = it->members(); + for( list.first(); list.current(); list.next() ) + { + Kopete::MetaContact *mc = list.current(); + if(mc->groups().count()==1) + Kopete::ContactList::self()->removeMetaContact(mc); + else + mc->removeFromGroup(it); + } + + if( !it->members().isEmpty() ) + { + kdDebug(14000) << "KopeteContactListView::slotRemove(): " + << "all subMetaContacts are not removed... Aborting" << endl; + continue; + } + + Kopete::ContactList::self()->removeGroup( it ); + } +} + +void KopeteContactListView::slotRename() +{ + if ( KopeteMetaContactLVI *metaLVI = dynamic_cast( currentItem() ) ) + { + metaLVI->setRenameEnabled( 0, true); + metaLVI->startRename( 0 ); + } + else if ( KopeteGroupViewItem *groupLVI = dynamic_cast( currentItem() ) ) + { + if ( !KopetePrefs::prefs()->sortByGroup() ) + return; + groupLVI->setRenameEnabled( 0, true); + groupLVI->startRename( 0 ); + } +} + +void KopeteContactListView::slotAddContact() +{ + if( !sender() ) + return; + + Kopete::MetaContact *metacontact = + Kopete::ContactList::self()->selectedMetaContacts().first(); + Kopete::Group *group = + Kopete::ContactList::self()->selectedGroups().first(); + Kopete::Account *account = dynamic_cast( sender()->parent() ); + + if ( ( metacontact && metacontact->isTemporary() ) || + (group && group->type()!=Kopete::Group::Normal ) ) + return; + + + if( account && ( metacontact || group) ) + { + KDialogBase *addDialog = new KDialogBase( this, "addDialog", true, + i18n( "Add Contact" ), KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Ok, true ); + + AddContactPage *addContactPage = + account->protocol()->createAddContactWidget( addDialog, account ); + + if (!addContactPage) + { + kdDebug(14000) << k_funcinfo << + "Error while creating addcontactpage" << endl; + } + else + { + addDialog->setMainWidget( addContactPage ); + if( addDialog->exec() == QDialog::Accepted ) + { + if( addContactPage->validateData() ) + { + if(!metacontact) + { + metacontact = new Kopete::MetaContact(); + metacontact->addToGroup( group ); + if (addContactPage->apply( account, metacontact )) + { + Kopete::ContactList::self()->addMetaContact( metacontact ); + } + else + { + delete metacontact; + } + } + else + { + addContactPage->apply( account, metacontact ); + } + } + } + } + addDialog->deleteLater(); + } +} + +void KopeteContactListView::slotAddTemporaryContact() +{ + Kopete::MetaContact *metacontact = + Kopete::ContactList::self()->selectedMetaContacts().first(); + if( metacontact ) + { +/* int r=KMessageBox::questionYesNo( Kopete::UI::Global::mainWidget(), + i18n( "Would you like to add this contact to your contact list?" ), + i18n( "Kopete" ), i18n("Add"), i18n("Do Not Add"), + "addTemporaryWhenMoving" ); + + if(r==KMessageBox::Yes)*/ + if(metacontact->isTemporary() ) + metacontact->setTemporary( false ); + } +} + +void KopeteContactListView::slotProperties() +{ +// kdDebug(14000) << k_funcinfo << "Called" << endl; + + KopeteMetaContactLVI *metaLVI = + dynamic_cast( currentItem() ); + KopeteGroupViewItem *groupLVI = + dynamic_cast( currentItem() ); + + if(metaLVI) + { + + KopeteMetaLVIProps *propsDialog = + new KopeteMetaLVIProps( metaLVI, 0L, "propsDialog" ); + + propsDialog->exec(); // modal + delete propsDialog; + + /* + if( metaLVI->group()->useCustomIcon() ) + { + metaLVI->updateCustomIcons( mShowAsTree ); + } + else + { + } + */ + metaLVI->repaint(); + } + else if(groupLVI) + { + KopeteGVIProps *propsDialog = + new KopeteGVIProps( groupLVI, 0L, "propsDialog"); + + propsDialog->exec(); // modal + delete propsDialog; + + groupLVI->updateIcon(); + } +} + +void KopeteContactListView::slotItemRenamed( QListViewItem */*item*/ ) +{ + //everithing is now done in KopeteMetaContactLVI::rename + +/* KopeteMetaContactLVI *metaLVI = dynamic_cast( item ); + Kopete::MetaContact *m= metaLVI ? metaLVI->metaContact() : 0L ; + if ( m ) + { + m->setDisplayName( metaLVI->text( 0 ) ); + } + else + { + //group are handled differently in KopeteGroupViewItem + // kdWarning( 14000 ) << k_funcinfo << "Unknown list view item '" << item + // << "' renamed, ignoring item" << endl; + } + */ +} + +void KopeteContactListView::insertUndoItem( KopeteContactListView::UndoItem *u) +{ + u->next=m_undo; + m_undo=u; + actionUndo->setEnabled(true); + while(m_redo) + { + UndoItem *i=m_redo->next; + delete m_redo; + m_redo=i; + } + actionRedo->setEnabled(false); + undoTimer.start(10*60*1000); +} + + +void KopeteContactListView::slotUndo() +{ + bool step = false; + while(m_undo && !step) + { + bool success=false; + switch (m_undo->type) + { + case UndoItem::MetaContactAdd: + { + Kopete::MetaContact *m=m_undo->metacontact; + if(m) + { + m->setTemporary(true); + success=true; + } + break; + } + case UndoItem::MetaContactCopy: + { + Kopete::MetaContact *m=m_undo->metacontact; + Kopete::Group *to=m_undo->group; + if( m && to ) + { + m->removeFromGroup( to ); + success=true; + } + break; + } + case UndoItem::MetaContactRemove: + { + Kopete::MetaContact *m=m_undo->metacontact; + Kopete::Group *g=m_undo->group; + if( m && g ) + { + m->addToGroup( g ); + success=true; + } + break; + } + case UndoItem::MetaContactRename: + { + Kopete::MetaContact *m=m_undo->metacontact; + if( m ) + { + // make a copy + QStringList undoArgs = m_undo->args; + Kopete::MetaContact::PropertySource undoSource = m_undo->nameSource; + // set undo undo + // set the source first + m_undo->nameSource = m->displayNameSource(); + if ( m->displayNameSource() == Kopete::MetaContact::SourceCustom ) + { + m_undo->args[0] = m->customDisplayName(); + } + else if ( m->displayNameSource() == Kopete::MetaContact::SourceContact ) + { + Kopete::Contact* c = m->displayNameSourceContact(); + m_undo->args[0] = c->contactId(); + m_undo->args[1] = c->protocol()->pluginId(); + m_undo->args[2] = c->account()->accountId(); + } + // source kabc requires no arguments + + // do the undo + if ( undoSource == Kopete::MetaContact::SourceContact ) + { // do undo + Kopete::Contact *c = Kopete::ContactList::self()->findContact( undoArgs[1], undoArgs[2], undoArgs[0]); + if (!c) + { + success=false; + break; + } + // do undo + m->setDisplayNameSourceContact(c); + m->setDisplayNameSource(Kopete::MetaContact::SourceContact); + } + else if ( undoSource == Kopete::MetaContact::SourceCustom ) + { + m->setDisplayName(undoArgs[0]); + m->setDisplayNameSource(Kopete::MetaContact::SourceCustom); + } + else if ( undoSource == Kopete::MetaContact::SourceKABC ) + { + m->setDisplayNameSource(Kopete::MetaContact::SourceKABC); + } + success=true; + } + break; + } + case UndoItem::GroupRename: + { + if( m_undo->group ) + { + const QString old=m_undo->group->displayName(); + m_undo->group->setDisplayName( m_undo->args[0] ); + m_undo->args[0]=old; + success=true; + } + break; + } + case UndoItem::MetaContactChange: + { + Kopete::Contact *c=Kopete::ContactList::self()->findContact(m_undo->args[0] , m_undo->args[1], m_undo->args[2] ) ; + if(c) + { + success=true; + if(m_undo->metacontact) + c->setMetaContact(m_undo->metacontact); + else + { + Kopete::MetaContact *m=new Kopete::MetaContact; + m->addToGroup(m_undo->group); + m->setDisplayName(m_undo->args[3]); + c->setMetaContact(m); + Kopete::ContactList::self()->addMetaContact(m); + } + m_undo->metacontact=c->metaContact(); //for the redo + } + break; + } + case UndoItem::ContactAdd: + { + Kopete::Contact *c=Kopete::ContactList::self()->findContact(m_undo->args[0] , m_undo->args[1], m_undo->args[2] ) ; + if(c) + { + success=true; + Kopete::MetaContact *m=new Kopete::MetaContact; + m->setTemporary(true); + c->setMetaContact(m); + Kopete::ContactList::self()->addMetaContact(m); + m_undo->metacontact=c->metaContact(); + } + break; + } + } + + if(success) //the undo item has been correctly performed + { + step=m_undo->isStep; + UndoItem *u=m_undo->next; + m_undo->next=m_redo; + m_redo=m_undo; + m_undo=u; + } + else //something has been corrupted, clear all undo items + { + while(m_undo) + { + UndoItem *u=m_undo->next; + delete m_undo; + m_undo=u; + } + } + } + actionUndo->setEnabled(m_undo); + actionRedo->setEnabled(m_redo); + undoTimer.start(10*60*1000); +} + +void KopeteContactListView::slotRedo() +{ + bool step = false; + while(m_redo && (!step || !m_redo->isStep )) + { + bool success=false; + switch (m_redo->type) + { + case UndoItem::MetaContactAdd: + { + Kopete::MetaContact *m=m_redo->metacontact; + if(m && m_redo->group) + { + m->setTemporary(false,m_redo->group); + success=true; + } + break; + } + case UndoItem::MetaContactCopy: + { + Kopete::MetaContact *m=m_redo->metacontact; + Kopete::Group *to=m_redo->group; + if( m && to ) + { + m->addToGroup( to ); + success=true; + } + break; + } + case UndoItem::MetaContactRemove: + { + Kopete::MetaContact *m=m_redo->metacontact; + Kopete::Group *g=m_redo->group; + if( m && g ) + { + m->removeFromGroup( g ); + success=true; + } + break; + } + case UndoItem::MetaContactRename: + { + /* + Kopete::MetaContact *m=m_redo->metacontact; + if( m ) + { + const QString old=m->displayName(); + if( m_redo->args[1].isEmpty() ) + { + const QString name = m_redo->args[0]; + m_redo->args[0] = m->displayNameSource()->contactId(); + m_redo->args[1] = m->displayNameSource()->protocol()->pluginId(); + m_redo->args[2] = m->displayNameSource()->account()->accountId(); + m->setDisplayName( name ); + } + else + { + const QString oldName = m->displayName(); + QPtrList< Kopete::Contact > cList = m->contacts(); + QPtrListIterator< Kopete::Contact > it (cList); + Kopete::Contact *c = Kopete::ContactList::self()->findContact( args[0], args[2], args[1]); + if ( !c) + return; + m->setNameSourceContact(c); + break; + + m_redo->args[0] = oldName; + m_redo->args[1] = ""; + m_redo->args[2] = ""; + } + success=true; + } + */ //Why is this code commented ? - Olivier 2006-04 + break; + } + case UndoItem::GroupRename: + { + if( m_redo->group ) + { + const QString old=m_redo->group->displayName(); + m_redo->group->setDisplayName( m_redo->args[0] ); + m_redo->args[0]=old; + success=true; + } + break; + } + case UndoItem::MetaContactChange: + case UndoItem::ContactAdd: + { + Kopete::Contact *c=Kopete::ContactList::self()->findContact(m_redo->args[0] , m_redo->args[1], m_redo->args[2] ) ; + if(c && m_redo->metacontact) + { + success=true; + c->setMetaContact(m_redo->metacontact); + m_redo->metacontact=c->metaContact(); + } + break; + } + } + + if(success) //the undo item has been correctly performed + { + step=true; + UndoItem *u=m_redo->next; + m_redo->next=m_undo; + m_undo=m_redo; + m_redo=u; + } + else //something has been corrupted, clear all undo items + { + while(m_redo) + { + UndoItem *u=m_redo->next; + delete m_redo; + m_redo=u; + } + } + } + actionUndo->setEnabled(m_undo); + actionRedo->setEnabled(m_redo); + undoTimer.start(10*60*1000); +} + +void KopeteContactListView::slotTimeout() +{ + undoTimer.stop(); + + //we will keep one (complete) undo action + UndoItem *Sdel=m_undo; + while(Sdel && !Sdel->isStep) + Sdel=Sdel->next; + + if(Sdel) while( Sdel->next ) + { + UndoItem *u=Sdel->next->next; + delete Sdel->next; + Sdel->next=u; + } + actionUndo->setEnabled(m_undo); + while(m_redo) + { + UndoItem *i=m_redo->next; + delete m_redo; + m_redo=i; + } + actionRedo->setEnabled(false); +} + +#include "kopetecontactlistview.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/contactlist/kopetecontactlistview.h b/kopete/kopete/contactlist/kopetecontactlistview.h new file mode 100644 index 00000000..43c60ebe --- /dev/null +++ b/kopete/kopete/contactlist/kopetecontactlistview.h @@ -0,0 +1,252 @@ +/* + kopetecontactlistview.h + + Kopete Contactlist GUI + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2002 by Stefan Gehn + Copyright (c) 2002-2005 by Olivier Goffart + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_CONTACTLISTVIEW_H +#define KOPETE_CONTACTLISTVIEW_H + +#include "kopetelistview.h" +#include "kopetemetacontact.h" + +#include +#include +#include +#include +#include +#include + +class KopeteMetaContactLVI; +class KopeteGroupViewItem; +class KopeteStatusGroupViewItem; +class KRootPixmap; +class KActionCollection; +class KAction; +class KListAction; +class KActionMenu; + +class KopeteContactListViewPrivate; + +namespace Kopete +{ +class Contact; +class MetaContact; +class Group; +class MessageEvent; +} + +/** + * @author Duncan Mac-Vicar P. + */ +class KopeteContactListView : public Kopete::UI::ListView::ListView +{ + Q_OBJECT + +public: + KopeteContactListView( QWidget *parent = 0, const char *name = 0 ); + ~KopeteContactListView(); + + /** + * Init MetaContact related actions + */ + void initActions(KActionCollection*); + + /** + * Add a given group name and return it + */ + void addGroup( const QString &groupName ); + + /** + * Are we displaying as a tree view (true), or in a flat list (false)? + * @todo make this an enum + */ + bool showAsTree() { return mShowAsTree; } + +public slots: + /** + * Remove all KopeteMetaContactLVI of a metaContact + */ + void removeContact( Kopete::MetaContact *contact ); + + /** + * Prompt the user for the group name (slot) + */ + void addGroup(); + +protected: + virtual void contentsMousePressEvent( QMouseEvent *e ); + + virtual bool acceptDrag(QDropEvent *e) const; + + /** + * Start a drag operation + * @return a KMultipleDrag containing: 1) A QStoredDrag of type "application/x-qlistviewitem", 2) If the MC is associated with a KABC entry, i) a QTextDrag containing their email address, and ii) their vCard representation. + */ + virtual QDragObject *dragObject(); + + /** + * Since KDE 3.1.1 , the original find Drop return 0L for afterme if the group is open. + * This woraround allow us to keep the highlight of the item, and give always a correct position + */ + virtual void findDrop(const QPoint &pos, QListViewItem *&parent, QListViewItem *&after); + + /** + * The selected items have changed; update our actions to show what's possible. + */ + void updateActionsForSelection( QPtrList contacts, QPtrList groups ); + +private slots: + /** + * When an account is added, so we add it to the menu action + */ + void slotAddSubContactActionNewAccount(Kopete::Account*); + /** + * When an account is destroyed, the child add subcontact action is deleted + * so we remove it from the menu action + */ + void slotAddSubContactActionAccountDeleted(const Kopete::Account *); + + void slotViewSelectionChanged(); + void slotListSelectionChanged(); + void slotContextMenu(KListView*,QListViewItem *item, const QPoint &point ); + void slotExpanded( QListViewItem *item ); + void slotCollapsed( QListViewItem *item ); + + void slotSettingsChanged( void ); + void slotUpdateAllGroupIcons(); + void slotExecuted( QListViewItem *item, const QPoint &pos, int c ); + + void slotAddedToGroup( Kopete::MetaContact *mc, Kopete::Group *to ); + void slotRemovedFromGroup( Kopete::MetaContact *mc, Kopete::Group *from ); + void slotMovedToGroup( Kopete::MetaContact *mc, Kopete::Group *from, Kopete::Group *to ); + + /** + * A meta contact was added to the contact list - update the view + */ + void slotMetaContactAdded( Kopete::MetaContact *mc ); + void slotMetaContactDeleted( Kopete::MetaContact *mc ); + void slotMetaContactSelected( bool sel ); + + void slotGroupAdded(Kopete::Group *); + + void slotContactStatusChanged( Kopete::MetaContact *mc ); + + void slotDropped(QDropEvent *e, QListViewItem *parent, QListViewItem*); + + void slotShowAddContactDialog(); + void slotNewMessageEvent(Kopete::MessageEvent *); + + /** + * Handle renamed items by renaming the meta contact + */ + void slotItemRenamed( QListViewItem *item ); + + /** Actions related slots **/ + void slotSendMessage(); + void slotStartChat(); + void slotSendFile(); + void slotSendEmail(); + void slotMoveToGroup(); + void slotCopyToGroup(); + void slotRemove(); + void slotRename(); + void slotAddContact(); + void slotAddTemporaryContact(); + void slotProperties(); + void slotUndo(); + void slotRedo(); + + void slotTimeout(); + +private: + bool mShowAsTree; + + // TODO: do we really need to store these? + QPtrList m_selectedContacts; + QPtrList m_selectedGroups; + + bool mSortByGroup; + KRootPixmap *root; + + QRect m_onItem; + + QPoint m_startDragPos; + + /* ACTIONS */ + KAction *actionSendMessage; + KAction *actionStartChat; + KAction *actionSendFile; + KAction *actionSendEmail; + KListAction *actionMove; + KListAction *actionCopy; + KAction *actionRename; + KAction *actionRemove; + KAction *actionAddTemporaryContact; + KAction *actionProperties; + KAction *actionUndo; + KAction *actionRedo; + + KopeteContactListViewPrivate *d; + + void moveDraggedContactToGroup( Kopete::MetaContact *contact, Kopete::Group *from, Kopete::Group *to ); + void addDraggedContactToGroup( Kopete::MetaContact *contact, Kopete::Group *group ); + void addDraggedContactToMetaContact( Kopete::Contact *contact, Kopete::MetaContact *parent ); + void addDraggedContactByInfo( const QString &protocolId, const QString &accountId, + const QString &contactId, QListViewItem *after ); + +public: + struct UndoItem; + UndoItem *m_undo; + UndoItem *m_redo; + void insertUndoItem(UndoItem *u); + QTimer undoTimer; + +public: + // This is public so the chatwinodw can handle sub actions + // FIXME: do we not believe in accessor functions any more? + KActionMenu *actionAddContact; + QMap m_accountAddContactMap; +}; + +struct KopeteContactListView::UndoItem +{ + enum Type { MetaContactAdd, MetaContactRemove , MetaContactCopy , MetaContactRename, MetaContactChange, ContactAdd, GroupRename } type; + QStringList args; + QGuardedPtr metacontact; + QGuardedPtr group; + UndoItem *next; + bool isStep; + Kopete::MetaContact::PropertySource nameSource; + + UndoItem() : isStep(true) {} + UndoItem(Type t, Kopete::MetaContact *m=0L ,Kopete::Group *g=0L) + { + isStep=true; + type=t; + metacontact=m; + group=g; + next=0L; + } +}; + + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/contactlist/kopetegrouplistaction.cpp b/kopete/kopete/contactlist/kopetegrouplistaction.cpp new file mode 100644 index 00000000..1556f9b6 --- /dev/null +++ b/kopete/kopete/contactlist/kopetegrouplistaction.cpp @@ -0,0 +1,66 @@ +/* + kopetegrouplistcation.cpp - the action used for Move To and copy To + + Copyright (c) 2002-2004 by Olivier Goffart + Copyright (c) 2002-2003 by Martijn Klingens + + Kopete (c) 2001-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ +/* This code was previously in libkopete/ui/kopetestdactions.cpp */ + +#include "kopetegrouplistaction.h" + +#include +#include +#include +#include +#include +#include + +#include "kopetecontactlist.h" +#include "kopetegroup.h" + +KopeteGroupListAction::KopeteGroupListAction( const QString &text, const QString &pix, const KShortcut &cut, const QObject *receiver, + const char *slot, QObject *parent, const char *name ) +: KListAction( text, pix, cut, parent, name ) +{ + connect( this, SIGNAL( activated() ), receiver, slot ); + + connect( Kopete::ContactList::self(), SIGNAL( groupAdded( Kopete::Group * ) ), this, SLOT( slotUpdateList() ) ); + connect( Kopete::ContactList::self(), SIGNAL( groupRemoved( Kopete::Group * ) ), this, SLOT( slotUpdateList() ) ); + connect( Kopete::ContactList::self(), SIGNAL( groupRenamed(Kopete::Group*, const QString& ) ), this, SLOT( slotUpdateList() ) ); + slotUpdateList(); +} + +KopeteGroupListAction::~KopeteGroupListAction() +{ +} + +void KopeteGroupListAction::slotUpdateList() +{ + QStringList groupList; + + // Add groups to our list + QPtrList groups = Kopete::ContactList::self()->groups(); + for ( Kopete::Group *it = groups.first(); it; it = groups.next() ) + { + if(it->type() == Kopete::Group::Normal) + groupList.append( it->displayName() ); + } + + groupList.sort(); + groupList.prepend(QString::null); //add a separator; + groupList.prepend( i18n("Top Level") ); //the top-level group, with the id 0 + setItems( groupList ); +} + +#include "kopetegrouplistaction.moc" diff --git a/kopete/kopete/contactlist/kopetegrouplistaction.h b/kopete/kopete/contactlist/kopetegrouplistaction.h new file mode 100644 index 00000000..3576f3ed --- /dev/null +++ b/kopete/kopete/contactlist/kopetegrouplistaction.h @@ -0,0 +1,44 @@ +/* + kopetegrouplistaction.h + + Copyright (c) 2005 Olivier Goffart + + + Kopete (c) 2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEGRLISTACT_H +#define KOPETEGRLISTACT_H + + +#include + + +/** + * Action used for Copy To and Move To + */ +class KopeteGroupListAction : public KListAction +{ + Q_OBJECT + +public: + KopeteGroupListAction( const QString &, const QString &, const KShortcut &, + const QObject *, const char *, QObject *, const char * ); + ~KopeteGroupListAction(); + +protected slots: + void slotUpdateList(); +private: + QStringList m_groupList; +}; + +#endif diff --git a/kopete/kopete/contactlist/kopetegroupviewitem.cpp b/kopete/kopete/contactlist/kopetegroupviewitem.cpp new file mode 100644 index 00000000..21b1cf79 --- /dev/null +++ b/kopete/kopete/contactlist/kopetegroupviewitem.cpp @@ -0,0 +1,286 @@ +/* + kopeteonlinestatus.cpp - Kopete Online Status + + Copyright (c) 2002-2004 by Olivier Goffart + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include + +#include +#include +#include +#include + +#include "kopetecontactlistview.h" +#include "kopetegroupviewitem.h" +#include "kopetegroup.h" +#include "kopeteonlinestatus.h" +#include "kopeteprefs.h" +#include "kopetemetacontactlvi.h" +#include "kopetemetacontact.h" + +#include + +//using namespace Kopete::UI; + +class KopeteGroupViewItem::Private +{ +public: + Kopete::UI::ListView::ImageComponent *image; + Kopete::UI::ListView::DisplayNameComponent *name; + Kopete::UI::ListView::TextComponent *count; + std::auto_ptr toolTipSource; +}; + +namespace Kopete { +namespace UI { +namespace ListView { + +class GroupToolTipSource : public ToolTipSource +{ +public: + GroupToolTipSource( KopeteGroupViewItem *gp ) + : group( gp ) + { + } + QString operator()( ComponentBase *, const QPoint &, QRect & ) + { + return group->toolTip(); + } +private: + KopeteGroupViewItem *group; +}; + +} // END namespace ListView +} // END namespace UI +} // END namespace Kopete + +KopeteGroupViewItem::KopeteGroupViewItem( Kopete::Group *group_, QListView *parent, const char *name ) +: Kopete::UI::ListView::Item( parent, group_, name ) +{ + m_group = group_; + initLVI(); +} + +KopeteGroupViewItem::KopeteGroupViewItem( Kopete::Group *group_, QListViewItem *parent, const char *name ) + : Kopete::UI::ListView::Item( parent, group_, name ) +{ + m_group = group_; + initLVI(); +} + +KopeteGroupViewItem::~KopeteGroupViewItem() +{ + delete d; +} + +void KopeteGroupViewItem::initLVI() +{ + d = new Private; + + d->toolTipSource.reset( new Kopete::UI::ListView::GroupToolTipSource( this ) ); + + using namespace Kopete::UI::ListView; + Component *hbox = new BoxComponent( this, BoxComponent::Horizontal ); + d->image = new ImageComponent( hbox ); + d->name = new DisplayNameComponent( hbox ); + d->count = new TextComponent( hbox ); + + d->image->setToolTipSource( d->toolTipSource.get() ); + d->name->setToolTipSource( d->toolTipSource.get() ); + d->count->setToolTipSource( d->toolTipSource.get() ); + + connect( m_group, SIGNAL( displayNameChanged( Kopete::Group*, const QString& ) ), + this, SLOT( refreshDisplayName() ) ); + + connect( KopetePrefs::prefs(), SIGNAL( contactListAppearanceChanged() ), + SLOT( slotConfigChanged() ) ); + connect( kapp, SIGNAL( appearanceChanged() ), SLOT( slotConfigChanged() ) ); + + connect( m_group, SIGNAL( iconAppearanceChanged() ), SLOT( updateIcon() ) ); + + refreshDisplayName(); + slotConfigChanged(); +} + +Kopete::Group* KopeteGroupViewItem::group() const +{ + return m_group; +} + +QString KopeteGroupViewItem::toolTip() const +{ + // TODO: add icon, and some more information than that which + // is already displayed in the list view item + // FIXME: post-KDE-3.3, make this better i18n-able + // currently it can't cause more problems than the contact list itself, at least. + return "" + d->name->text() + " " + d->count->text(); +} + +void KopeteGroupViewItem::slotConfigChanged() +{ + updateIcon(); + updateVisibility(); + + d->name->setColor( KopetePrefs::prefs()->contactListGroupNameColor() ); + + QFont font = listView()->font(); + if ( KopetePrefs::prefs()->contactListUseCustomFonts() ) + font = KopetePrefs::prefs()->contactListCustomNormalFont(); + d->name->setFont( font ); + + d->count->setFont( KopetePrefs::prefs()->contactListSmallFont() ); +} + +void KopeteGroupViewItem::refreshDisplayName() +{ + totalMemberCount = 0; + onlineMemberCount = 0; + + for ( QListViewItem *lvi = firstChild(); lvi; lvi = lvi->nextSibling() ) + { + if ( KopeteMetaContactLVI *kc = dynamic_cast( lvi ) ) + { + totalMemberCount++; + if ( kc->metaContact()->isOnline() ) + onlineMemberCount++; + } + } + + d->name->setText( m_group->displayName() ); + d->count->setText( i18n( "(NUMBER OF ONLINE CONTACTS/NUMBER OF CONTACTS IN GROUP)", "(%1/%2)" ) + .arg( QString::number( onlineMemberCount ), QString::number( totalMemberCount ) ) ); + + updateVisibility(); + + // Sorting in this slot is extremely expensive as it's called dozens of times and + // the sorting itself is rather slow. Therefore we call delayedSort, which tries + // to group multiple sort requests into one. + using namespace Kopete::UI::ListView; + if ( ListView::ListView *lv = dynamic_cast( listView() ) ) + lv->delayedSort(); + else + listView()->sort(); +} + +QString KopeteGroupViewItem::key( int, bool ) const +{ + //Groups are placed after topLevel contact. + //Exepted Temporary group which is the first group + if ( group()->type() != Kopete::Group::Normal ) + return "0" + d->name->text(); + return "M" + d->name->text(); +} + +void KopeteGroupViewItem::startRename( int /*col*/ ) +{ + //kdDebug(14000) << k_funcinfo << endl; + KListViewItem::startRename( 0 ); +} + +void KopeteGroupViewItem::okRename( int col ) +{ + //kdDebug(14000) << k_funcinfo << endl; + KListViewItem::okRename(col); + setRenameEnabled( 0, false ); +} + +void KopeteGroupViewItem::cancelRename( int col ) +{ + //kdDebug(14000) << k_funcinfo << endl; + KListViewItem::cancelRename(col); + setRenameEnabled( 0, false ); +} + +void KopeteGroupViewItem::updateVisibility() +{ + //FIXME: A contact can ve visible if he has a unknwon status (it's not online) + // or if he has an event (blinking icon). If such as contact is not with + // others inline contact in the group. the group will stay hidden. + int visibleUsers = onlineMemberCount; + if ( KopetePrefs::prefs()->showOffline() ) + visibleUsers = totalMemberCount; + + bool visible = KopetePrefs::prefs()->showEmptyGroups() || ( visibleUsers > 0 ); + + if ( isVisible() != visible ) + { + setTargetVisibility( visible ); + if ( visible ) + { + // When calling setVisible(true) EVERY child item will be shown, + // even if they should be hidden. + // We just re-update the visibility of all child items + for ( QListViewItem *lvi = firstChild(); lvi; lvi = lvi->nextSibling() ) + { + if ( KopeteMetaContactLVI *kmc = dynamic_cast( lvi ) ) + kmc->updateVisibility(); + } + } + } +} + +void KopeteGroupViewItem::updateIcon() +{ + // TODO: clever caching + if ( isOpen() ) + { + if ( group()->useCustomIcon() && !group()->icon( Kopete::ContactListElement::Open ).isEmpty() ) + open = SmallIcon( group()->icon( Kopete::ContactListElement::Open ) ); + else + open = SmallIcon( KOPETE_GROUP_DEFAULT_OPEN_ICON ); + + d->image->setPixmap( open ); + } + else + { + if ( group()->useCustomIcon() && !group()->icon( Kopete::ContactListElement::Closed ).isEmpty() ) + closed = SmallIcon( group()->icon( Kopete::ContactListElement::Closed ) ); + else + closed = SmallIcon( KOPETE_GROUP_DEFAULT_CLOSED_ICON ); + + d->image->setPixmap( closed ); + } +} + +QString KopeteGroupViewItem::text( int column ) const +{ + if ( column == 0 ) + return d->name->text(); + else + return KListViewItem::text( column ); +} + +void KopeteGroupViewItem::setText( int column, const QString &text ) +{ + if ( column == 0 ) + { + KopeteContactListView *lv = dynamic_cast( listView() ); + if ( lv ) + { + KopeteContactListView::UndoItem *u=new KopeteContactListView::UndoItem(KopeteContactListView::UndoItem::GroupRename, 0L, m_group); + u->args << m_group->displayName(); + lv->insertUndoItem(u); + } + group()->setDisplayName( text ); + } + else + KListViewItem::setText( column, text ); +} + +#include "kopetegroupviewitem.moc" +// vim: set noet ts=4 sts=4 sw=4: + + diff --git a/kopete/kopete/contactlist/kopetegroupviewitem.h b/kopete/kopete/contactlist/kopetegroupviewitem.h new file mode 100644 index 00000000..777ea2e8 --- /dev/null +++ b/kopete/kopete/contactlist/kopetegroupviewitem.h @@ -0,0 +1,82 @@ +/*************************************************************************** + kopetegroupviewitem.h - description + ------------------- + begin : lun oct 28 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef KOPETEGROUPVIEWITEM_H +#define KOPETEGROUPVIEWITEM_H + +#include "kopetelistviewitem.h" +#include + +#define KOPETE_GROUP_DEFAULT_OPEN_ICON "folder_open" +#define KOPETE_GROUP_DEFAULT_CLOSED_ICON "folder" + +namespace Kopete +{ +class Group; +} + +/** + * @author Olivier Goffart + */ +class KopeteGroupViewItem : public Kopete::UI::ListView::Item +{ + Q_OBJECT +public: + KopeteGroupViewItem( Kopete::Group *group , QListView *parent, const char *name = 0 ); + KopeteGroupViewItem( Kopete::Group *group , QListViewItem *parent, const char *name = 0 ); + ~KopeteGroupViewItem(); + + Kopete::Group * group() const; + + virtual void startRename( int col ); + + /** + * reimplemented from KListViewItem to take into account our alternate text storage + */ + virtual QString text( int column ) const; + virtual void setText( int column, const QString &text ); + + QString toolTip() const; + +public slots: + void refreshDisplayName(); + void updateIcon(); + void updateVisibility(); + +protected: + virtual void okRename( int col ); + virtual void cancelRename( int col ); + +private: + void initLVI(); + + Kopete::Group *m_group; + QPixmap open, closed; + + QString key( int column, bool ascending ) const; + + unsigned int onlineMemberCount; + unsigned int totalMemberCount; + + class Private; + Private *d; + +private slots: + void slotConfigChanged(); +}; + +#endif diff --git a/kopete/kopete/contactlist/kopetegvipropswidget.ui b/kopete/kopete/contactlist/kopetegvipropswidget.ui new file mode 100644 index 00000000..4dbb1599 --- /dev/null +++ b/kopete/kopete/contactlist/kopetegvipropswidget.ui @@ -0,0 +1,156 @@ + +KopeteGVIPropsWidget + + + KopeteGVIPropsWidget + + + + 0 + 0 + 220 + 223 + + + + + unnamed + + + 0 + + + + tabWidget + + + + tab + + + &General + + + + unnamed + + + + layout7 + + + + unnamed + + + + lblDisplayName + + + &Name: + + + edtDisplayName + + + + + edtDisplayName + + + + + + + grpIcons + + + Icons + + + + unnamed + + + + icnbOpen + + + + + + + + lblOpen + + + O&pen: + + + icnbOpen + + + + + lblClosed + + + C&losed: + + + icnbClosed + + + + + icnbClosed + + + + + + + + chkUseCustomIcons + + + Use custom &icons + + + + + spacerHorizontal1 + + + Horizontal + + + Expanding + + + + 16 + 16 + + + + + + + + + + + + tabWidget + edtDisplayName + chkUseCustomIcons + icnbOpen + icnbClosed + + + + kicondialog.h + kicondialog.h + + diff --git a/kopete/kopete/contactlist/kopetelviprops.cpp b/kopete/kopete/contactlist/kopetelviprops.cpp new file mode 100644 index 00000000..bf5431bf --- /dev/null +++ b/kopete/kopete/contactlist/kopetelviprops.cpp @@ -0,0 +1,570 @@ +/* + kopetelviprops.cpp + + Kopete Contactlist Properties GUI for Groups and MetaContacts + + Copyright (c) 2002-2003 by Stefan Gehn + Copyright (c) 2004 by Will Stephenson + Copyright (c) 2004-2005 by Duncan Mac-Vicar P. + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopetelviprops.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kabcpersistence.h" +#include "kopeteaddrbookexport.h" +#include "kopetecontact.h" +#include "kopetegroup.h" +#include "kopetegroupviewitem.h" +#include "kopetemetacontactlvi.h" +#include "kopeteaccount.h" +#include "kopeteprotocol.h" +#include "addressbooklinkwidget.h" +#include "addressbookselectordialog.h" + +#include "customnotificationprops.h" +#include "customnotifications.h" + +const char MC_OFF[] = "metacontact_offline"; +const char MC_ON[] = "metacontact_online"; +const char MC_AW[] = "metacontact_away"; +const char MC_UNK[] = "metacontact_unknown"; + + +KopeteGVIProps::KopeteGVIProps(KopeteGroupViewItem *gvi, QWidget *parent, const char *name) +: KDialogBase(parent, name, true, i18n("Properties of Group %1").arg(gvi->group()->displayName()), Ok|Cancel, Ok, false) +{ + mainWidget = new KopeteGVIPropsWidget(this, "mainWidget"); + mainWidget->icnbOpen->setIconSize(KIcon::SizeSmall); + mainWidget->icnbClosed->setIconSize(KIcon::SizeSmall); + + mNotificationProps = new CustomNotificationProps( this, gvi->group() ); + mainWidget->tabWidget->addTab( mNotificationProps->widget(), i18n( "Custom &Notifications" ) ); + + setMainWidget(mainWidget); + item = gvi; + m_dirty = false; + + mainWidget->edtDisplayName->setText( item->group()->displayName() ); + + mainWidget->chkUseCustomIcons->setChecked( item->group()->useCustomIcon() ); + + QString openName = item->group()->icon( Kopete::ContactListElement::Open ); + if(openName.isEmpty()) + openName = KOPETE_GROUP_DEFAULT_OPEN_ICON; + QString closeName = item->group()->icon( Kopete::ContactListElement::Closed ); + if(closeName.isEmpty()) + closeName = KOPETE_GROUP_DEFAULT_CLOSED_ICON; + mainWidget->icnbOpen->setIcon( openName ); + mainWidget->icnbClosed->setIcon( closeName ); + + connect( this, SIGNAL(okClicked()), this, SLOT( slotOkClicked() ) ); + connect( mainWidget->chkUseCustomIcons, SIGNAL( toggled( bool ) ), + this, SLOT( slotUseCustomIconsToggled( bool ) ) ); + connect( mainWidget->icnbOpen, SIGNAL( iconChanged( QString ) ), + SLOT( slotIconChanged() ) ); + connect( mainWidget->icnbClosed, SIGNAL( iconChanged( QString ) ), + SLOT( slotIconChanged() ) ); + slotUseCustomIconsToggled( mainWidget->chkUseCustomIcons->isChecked() ); +} + +KopeteGVIProps::~KopeteGVIProps() +{ +} + +void KopeteGVIProps::slotOkClicked() +{ + if( mainWidget->edtDisplayName->text() != item->group()->displayName() ) + { + item->group()->setDisplayName( mainWidget->edtDisplayName->text() ); + item->refreshDisplayName(); + } + + item->group()->setUseCustomIcon( mainWidget->chkUseCustomIcons->isChecked() ); + + // only call setIcon if the icon was changed + if( m_dirty ) + { + item->group()->setIcon( mainWidget->icnbOpen->icon(), + Kopete::ContactListElement::Open ); + + item->group()->setIcon( mainWidget->icnbClosed->icon(), + Kopete::ContactListElement::Closed ); + } + + mNotificationProps->storeCurrentCustoms(); +} + +void KopeteGVIProps::slotUseCustomIconsToggled(bool on) +{ + mainWidget->lblOpen->setEnabled( on ); + mainWidget->icnbOpen->setEnabled( on ); + mainWidget->lblClosed->setEnabled( on ); + mainWidget->icnbClosed->setEnabled( on ); +} + +void KopeteGVIProps::slotIconChanged() +{ + m_dirty = true; +} + +// ============================================================================= + + +KopeteMetaLVIProps::KopeteMetaLVIProps(KopeteMetaContactLVI *lvi, QWidget *parent, const char *name) +: KDialogBase(parent, name, true, i18n("Properties of Meta Contact %1").arg(lvi->metaContact()->displayName()), Ok|Cancel, Ok, false) +{ + m_countPhotoCapable = 0; + mainWidget = new KopeteMetaLVIPropsWidget( this, "mainWidget" ); + mainWidget->icnbOffline->setIconSize( KIcon::SizeSmall ); + mainWidget->icnbOnline->setIconSize( KIcon::SizeSmall ); + mainWidget->icnbAway->setIconSize( KIcon::SizeSmall ); + mainWidget->icnbUnknown->setIconSize( KIcon::SizeSmall ); + + mNotificationProps = new CustomNotificationProps( this, lvi->metaContact() ); + // add a button to the notification props to get the sound from KABC + // the widget's vert box layout, horiz box layout containing button, spacer, followed by a spacer + QBoxLayout * vb = static_cast( mNotificationProps->widget()->layout() ); + + QHBoxLayout* hb = new QHBoxLayout( vb, -1, "soundFromKABClayout" ); + mFromKABC = new QPushButton( i18n( "Sync KABC..." ), mNotificationProps->widget(), "getSoundFromKABC" ); + hb->addWidget( mFromKABC ); // [ [Button] <-xxxxx-> ] + hb->addStretch(); + vb->addStretch(); // vert spacer keeps the rest snug + + mainWidget->tabWidget->addTab( mNotificationProps->widget(), i18n( "Custom &Notifications" ) ); + setMainWidget( mainWidget ); + item = lvi; + + connect( mainWidget->radioNameKABC, SIGNAL(toggled(bool)), SLOT(slotEnableAndDisableWidgets())); + connect( mainWidget->radioNameContact, SIGNAL(toggled(bool)), SLOT(slotEnableAndDisableWidgets())); + connect( mainWidget->radioNameCustom, SIGNAL(toggled(bool)), SLOT(slotEnableAndDisableWidgets())); + connect( mainWidget->radioPhotoKABC, SIGNAL(toggled(bool)), SLOT(slotEnableAndDisableWidgets())); + connect( mainWidget->radioPhotoContact, SIGNAL(toggled(bool)), SLOT(slotEnableAndDisableWidgets())); + connect( mainWidget->radioPhotoCustom, SIGNAL(toggled(bool)), SLOT(slotEnableAndDisableWidgets())); + connect( mainWidget->cmbPhotoUrl, SIGNAL(urlSelected(const QString &)), SLOT(slotEnableAndDisableWidgets())); + connect( mainWidget->cmbAccountPhoto, SIGNAL(activated ( int )), SLOT(slotEnableAndDisableWidgets())); + + + mainWidget->btnClearPhoto->setIconSet( SmallIconSet( QApplication::reverseLayout() ? "locationbar_erase" : "clear_left" ) ); + connect( mainWidget->btnClearPhoto, SIGNAL( clicked() ), this, SLOT( slotClearPhotoClicked() ) ); + connect( mainWidget->widAddresseeLink, SIGNAL( addresseeChanged( const KABC::Addressee & ) ), SLOT( slotAddresseeChanged( const KABC::Addressee & ) ) ); + mainWidget->chkUseCustomIcons->setChecked( item->metaContact()->useCustomIcon() ); + + QString offlineName = item->metaContact()->icon( Kopete::ContactListElement::Offline ); + if(offlineName.isEmpty()) + offlineName = QString::fromLatin1(MC_OFF); // Default + + QString onlineName = item->metaContact()->icon( Kopete::ContactListElement::Online ); + if(onlineName.isEmpty()) + onlineName = QString::fromLatin1(MC_ON); // Default + + QString awayName = item->metaContact()->icon( Kopete::ContactListElement::Away ); + if(awayName.isEmpty()) + awayName = QString::fromLatin1(MC_AW); // Default + + QString unknownName = item->metaContact()->icon( Kopete::ContactListElement::Unknown ); + if(unknownName.isEmpty()) + unknownName = QString::fromLatin1(MC_UNK); // Default + + mainWidget->icnbOffline->setIcon( offlineName ); + mainWidget->icnbOnline->setIcon( onlineName ); + mainWidget->icnbAway->setIcon( awayName ); + mainWidget->icnbUnknown->setIcon( unknownName ); + + mainWidget->widAddresseeLink->setMetaContact( lvi->metaContact() ); + + mAddressBookUid = item->metaContact()->metaContactId(); + + mExport = 0L; + + if ( !mAddressBookUid.isEmpty() ) + { + KABC::AddressBook *ab = Kopete::KABCPersistence::self()->addressBook(); + KABC::Addressee a = ab->findByUid( mAddressBookUid ); + mainWidget->widAddresseeLink->setAddressee( a ); + + if ( !a.isEmpty() ) + { + mainWidget->btnImportKABC->setEnabled( true ); + mainWidget->btnExportKABC->setEnabled( true ); + mExport = new KopeteAddressBookExport( this, item->metaContact() ); + + mSound = a.sound(); + mFromKABC->setEnabled( !( mSound.isIntern() || mSound.url().isEmpty() ) ); + } + } + + slotLoadNameSources(); + slotLoadPhotoSources(); + + connect( this, SIGNAL(okClicked()), this, SLOT( slotOkClicked() ) ); + connect( mainWidget->chkUseCustomIcons, SIGNAL( toggled( bool ) ), + this, SLOT( slotUseCustomIconsToggled( bool ) ) ); + connect( mainWidget->btnImportKABC, SIGNAL( clicked() ), + this, SLOT( slotImportClicked() ) ); + connect( mainWidget->btnExportKABC, SIGNAL( clicked() ), + this, SLOT( slotExportClicked() ) ); + connect( mFromKABC, SIGNAL( clicked() ), + this, SLOT( slotFromKABCClicked() ) ); + connect( mNotificationProps->widget()->customSound, SIGNAL( openFileDialog( KURLRequester * )), + SLOT( slotOpenSoundDialog( KURLRequester * ))); + + slotUseCustomIconsToggled( mainWidget->chkUseCustomIcons->isChecked() ); + slotEnableAndDisableWidgets(); +} + +KopeteMetaLVIProps::~KopeteMetaLVIProps() +{ +} + + +void KopeteMetaLVIProps::slotLoadNameSources() +{ + Kopete::Contact* trackingName = item->metaContact()->displayNameSourceContact(); + QPtrList< Kopete::Contact > cList = item->metaContact()->contacts(); + QPtrListIterator it( cList ); + mainWidget->cmbAccountName->clear(); + for( ; it.current(); ++it ) + { + QString acct = it.current()->property( Kopete::Global::Properties::self()->nickName() ).value().toString() + " <" + it.current()->contactId() + ">"; + QPixmap acctIcon = it.current()->account()->accountIcon(); + mainWidget->cmbAccountName->insertItem( acctIcon, acct ); + + // Select this item if it's the one we're tracking. + if( it.current() == trackingName ) + { + mainWidget->cmbAccountName->setCurrentItem( mainWidget->cmbAccountName->count() - 1 ); + } + } + + mainWidget->edtDisplayName->setText( item->metaContact()->customDisplayName() ); + + Kopete::MetaContact::PropertySource nameSource = item->metaContact()->displayNameSource(); + + mainWidget->radioNameContact->setChecked(nameSource == Kopete::MetaContact::SourceContact); + mainWidget->radioNameKABC->setChecked(nameSource == Kopete::MetaContact::SourceKABC); + mainWidget->radioNameCustom->setChecked(nameSource == Kopete::MetaContact::SourceCustom); + +} + +void KopeteMetaLVIProps::slotLoadPhotoSources() +{ + // fill photo contact sources + QPtrList< Kopete::Contact > cList = item->metaContact()->contacts(); + m_withPhotoContacts.clear(); + Kopete::Contact* trackingPhoto = item->metaContact()->photoSourceContact(); + mainWidget->cmbAccountPhoto->clear(); + QPtrListIterator itp( cList ); + for( ; itp.current(); ++itp ) + { + Kopete::Contact *citem = itp.current(); + if ( citem->hasProperty( Kopete::Global::Properties::self()->photo().key() ) ) + { + QString acct = citem->property( Kopete::Global::Properties::self()->nickName() ).value().toString() + " <" + citem->contactId() + ">"; + QPixmap acctIcon = citem->account()->accountIcon(); + mainWidget->cmbAccountPhoto->insertItem( acctIcon, acct ); + + // Select this item if it's the one we're tracking. + if( citem == trackingPhoto ) + { + mainWidget->cmbAccountPhoto->setCurrentItem( mainWidget->cmbAccountPhoto->count() - 1 ); + } + m_withPhotoContacts.insert(mainWidget->cmbAccountPhoto->count() - 1 , citem ); + } + } +#if KDE_IS_VERSION(3,4,0) + mainWidget->cmbPhotoUrl->setKURL(item->metaContact()->customPhoto().url()); +#else + mainWidget->cmbPhotoUrl->setURL(item->metaContact()->customPhoto().url()); +#endif + + Kopete::MetaContact::PropertySource photoSource = item->metaContact()->photoSource(); + + mainWidget->radioPhotoContact->setChecked(photoSource == Kopete::MetaContact::SourceContact); + mainWidget->radioPhotoKABC->setChecked(photoSource == Kopete::MetaContact::SourceKABC); + mainWidget->radioPhotoCustom->setChecked(photoSource == Kopete::MetaContact::SourceCustom); + + mainWidget->chkSyncPhoto->setChecked(item->metaContact()->isPhotoSyncedWithKABC()); +} + +void KopeteMetaLVIProps::slotEnableAndDisableWidgets() +{ + KABC::AddressBook *ab = Kopete::KABCPersistence::self()->addressBook(); + KABC::Addressee a = ab->findByUid( mAddressBookUid ); + bool validLink = ! a.isEmpty(); + // kabc source requires a kabc link + mainWidget->radioNameKABC->setEnabled(validLink); + // kabc source requires a kabc link + mainWidget->radioPhotoKABC->setEnabled(validLink); + // sync with kabc has no sense if we use kabc as source (sync kabc with kabc? uh?) + // it has also no sense if they are no kabc link + if( selectedPhotoSource() == Kopete::MetaContact::SourceKABC || !validLink ) + { + mainWidget->chkSyncPhoto->setEnabled(false); + } + else + { + mainWidget->chkSyncPhoto->setEnabled(true); + } + + mainWidget->radioNameContact->setEnabled(item->metaContact()->contacts().count()); + mainWidget->radioPhotoContact->setEnabled(!m_withPhotoContacts.isEmpty()); + + mainWidget->cmbAccountName->setEnabled(selectedNameSource() == Kopete::MetaContact::SourceContact); + mainWidget->edtDisplayName->setEnabled(selectedNameSource() == Kopete::MetaContact::SourceCustom); + + mainWidget->cmbAccountPhoto->setEnabled(selectedPhotoSource() == Kopete::MetaContact::SourceContact); + mainWidget->cmbPhotoUrl->setEnabled(selectedPhotoSource() == Kopete::MetaContact::SourceCustom); + + if ( m_withPhotoContacts.isEmpty() ) + { + mainWidget->cmbAccountPhoto->clear(); + mainWidget->cmbAccountPhoto->insertItem(i18n("No Contacts with Photo Support")); + mainWidget->cmbAccountPhoto->setEnabled(false); + } + + QImage photo; + switch ( selectedPhotoSource() ) + { + case Kopete::MetaContact::SourceKABC: + photo = Kopete::photoFromKABC(mAddressBookUid); + break; + case Kopete::MetaContact::SourceContact: + photo = Kopete::photoFromContact(selectedPhotoSourceContact()); + break; + case Kopete::MetaContact::SourceCustom: + photo = QImage(KURL::decode_string(mainWidget->cmbPhotoUrl->url())); + break; + } + if( !photo.isNull() ) + mainWidget->photoLabel->setPixmap(QPixmap(photo.smoothScale( 64, 92, QImage::ScaleMin ))); + else + mainWidget->photoLabel->setPixmap( QPixmap() ); +} + +Kopete::MetaContact::PropertySource KopeteMetaLVIProps::selectedNameSource() const +{ + if ( mainWidget->radioNameKABC->isChecked() ) + return Kopete::MetaContact::SourceKABC; + if ( mainWidget->radioNameContact->isChecked() ) + return Kopete::MetaContact::SourceContact; + if ( mainWidget->radioNameCustom->isChecked() ) + return Kopete::MetaContact::SourceCustom; + else + return Kopete::MetaContact::SourceCustom; +} + +Kopete::MetaContact::PropertySource KopeteMetaLVIProps::selectedPhotoSource() const +{ + if ( mainWidget->radioPhotoKABC->isChecked() ) + return Kopete::MetaContact::SourceKABC; + if ( mainWidget->radioPhotoContact->isChecked() ) + return Kopete::MetaContact::SourceContact; + if ( mainWidget->radioPhotoCustom->isChecked() ) + return Kopete::MetaContact::SourceCustom; + else + return Kopete::MetaContact::SourceCustom; +} + +Kopete::Contact* KopeteMetaLVIProps::selectedNameSourceContact() const +{ + Kopete::Contact *c= item->metaContact()->contacts().at( mainWidget->cmbAccountName->currentItem() ); + return c ? c : 0L; +} + +Kopete::Contact* KopeteMetaLVIProps::selectedPhotoSourceContact() const +{ + if (m_withPhotoContacts.isEmpty()) + return 0L; + Kopete::Contact *c = m_withPhotoContacts[mainWidget->cmbAccountPhoto->currentItem() ]; + return c ? c : 0L; +} + +void KopeteMetaLVIProps::slotOkClicked() +{ + // update meta contact's UID + item->metaContact()->setMetaContactId( mAddressBookUid ); + //this has to be done first, in the case something is synced with KABC (see bug 109494) + + // set custom display name + if( mainWidget->edtDisplayName->text() != item->metaContact()->customDisplayName() ) + item->metaContact()->setDisplayName( mainWidget->edtDisplayName->text() ); + + item->metaContact()->setDisplayNameSource(selectedNameSource()); + item->metaContact()->setDisplayNameSourceContact( selectedNameSourceContact() ); + + // set photo source + item->metaContact()->setPhotoSource(selectedPhotoSource()); + item->metaContact()->setPhotoSourceContact( selectedPhotoSourceContact() ); + if ( !mainWidget->cmbPhotoUrl->url().isEmpty()) + item->metaContact()->setPhoto(KURL::fromPathOrURL((mainWidget->cmbPhotoUrl->url()))); + item->metaContact()->setPhotoSyncedWithKABC( mainWidget->chkSyncPhoto->isChecked() ); + + item->metaContact()->setUseCustomIcon( + mainWidget->chkUseCustomIcons->isChecked() ); + + // only call setIcon if any of the icons is not set to default icon + if( + mainWidget->icnbOffline->icon() != MC_OFF || + mainWidget->icnbOnline->icon() != MC_ON || + mainWidget->icnbAway->icon() != MC_AW || + mainWidget->icnbUnknown->icon() != MC_UNK ) + { + item->metaContact()->setIcon( mainWidget->icnbOffline->icon(), + Kopete::ContactListElement::Offline ); + + item->metaContact()->setIcon( mainWidget->icnbOnline->icon(), + Kopete::ContactListElement::Online ); + + item->metaContact()->setIcon( mainWidget->icnbAway->icon(), + Kopete::ContactListElement::Away ); + + item->metaContact()->setIcon( mainWidget->icnbUnknown->icon(), + Kopete::ContactListElement::Unknown ); + } + + mNotificationProps->storeCurrentCustoms(); +} + +void KopeteMetaLVIProps::slotUseCustomIconsToggled(bool on) +{ + mainWidget->lblOffline->setEnabled( on ); + mainWidget->lblOnline->setEnabled( on ); + mainWidget->lblAway->setEnabled( on ); + mainWidget->lblUnknown->setEnabled( on ); + + mainWidget->icnbOffline->setEnabled( on ); + mainWidget->icnbOnline->setEnabled( on ); + mainWidget->icnbAway->setEnabled( on ); + mainWidget->icnbUnknown->setEnabled( on ); +} + +void KopeteMetaLVIProps::slotAddresseeChanged( const KABC::Addressee & a ) +{ + if ( !a.isEmpty() ) + { + mSound = a.sound(); + mFromKABC->setEnabled( !( mSound.isIntern() || mSound.url().isEmpty() ) ); + mainWidget->btnExportKABC->setEnabled( true ); + mainWidget->btnImportKABC->setEnabled( true ); + // set/update the MC's addressee uin field + mAddressBookUid = a.uid(); + } + else + { + mainWidget->btnExportKABC->setEnabled( false ); + mainWidget->btnImportKABC->setEnabled( false ); + mAddressBookUid = QString::null; + mainWidget->radioNameContact->setChecked( true ); + mainWidget->radioPhotoContact->setChecked( true ); + } + slotEnableAndDisableWidgets(); +} + +void KopeteMetaLVIProps::slotExportClicked() +{ + item->metaContact()->setMetaContactId( mAddressBookUid ); + delete mExport; + mExport = new KopeteAddressBookExport( this, item->metaContact() ); + if ( mExport->showDialog() == QDialog::Accepted ) + mExport->exportData(); +} + +void KopeteMetaLVIProps::slotImportClicked() +{ + item->metaContact()->setMetaContactId( mAddressBookUid ); + if ( Kopete::KABCPersistence::self()->syncWithKABC( item->metaContact() ) ) + KMessageBox::queuedMessageBox( this, KMessageBox::Information, + i18n( "No contacts were imported from the address book." ), + i18n( "No Change" ) ); +} + +void KopeteMetaLVIProps::slotFromKABCClicked() +{ + mNotificationProps->widget()->customSound->setURL( mSound.url() ); +} + +void KopeteMetaLVIProps::slotOpenSoundDialog( KURLRequester *requester ) +{ + // taken from kdelibs/kio/kfile/knotifydialog.cpp + // only need to init this once + requester->disconnect( SIGNAL( openFileDialog( KURLRequester * )), + this, SLOT( slotOpenSoundDialog( KURLRequester * ))); + + KFileDialog *fileDialog = requester->fileDialog(); + //fileDialog->setCaption( i18n("Select Sound File") ); + QStringList filters; + filters << "audio/x-wav" << "audio/x-mp3" << "application/ogg" + << "audio/x-adpcm"; + fileDialog->setMimeFilter( filters ); + + // find the first "sound"-resource that contains files + QStringList soundDirs = + KGlobal::dirs()->findDirs("data", "kopete/sounds"); + soundDirs += KGlobal::dirs()->resourceDirs( "sound" ); + + if ( !soundDirs.isEmpty() ) { + KURL soundURL; + QDir dir; + dir.setFilter( QDir::Files | QDir::Readable ); + QStringList::ConstIterator it = soundDirs.begin(); + while ( it != soundDirs.end() ) { + dir = *it; + if ( dir.isReadable() && dir.count() > 2 ) { + soundURL.setPath( *it ); + fileDialog->setURL( soundURL ); + break; + } + ++it; + } + } +} + +void KopeteMetaLVIProps::slotClearPhotoClicked() +{ +#if KDE_IS_VERSION(3,4,0) + mainWidget->cmbPhotoUrl->setKURL( KURL() ); +#else + mainWidget->cmbPhotoUrl->setURL( QString::null ); +#endif + item->metaContact()->setPhoto( KURL() ); + + slotEnableAndDisableWidgets(); +} + +#include "kopetelviprops.moc" diff --git a/kopete/kopete/contactlist/kopetelviprops.h b/kopete/kopete/contactlist/kopetelviprops.h new file mode 100644 index 00000000..9a2ebf34 --- /dev/null +++ b/kopete/kopete/contactlist/kopetelviprops.h @@ -0,0 +1,102 @@ +/* + kopetelviprops.h + + Kopete Contactlist Properties GUI for Groups and MetaContacts + + Copyright (c) 2002-2003 by Stefan Gehn + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETELVIPROPS_H +#define KOPETELVIPROPS_H + +#include +#include + +#include "kopetemetacontact.h" + +#include "kopetegvipropswidget.h" +#include "kopetemetalvipropswidget.h" + +class QButtonGroup; + +class AddressBookLinkWidget; +class CustomNotificationProps; +class KPushButton; +class KopeteGroupViewItem; +class KopeteMetaContactLVI; +class KopeteAddressBookExport; +class KURLRequester; + +namespace KABC { class Addressee; } +namespace Kopete { class Contact; } + +class KopeteGVIProps: public KDialogBase +{ + Q_OBJECT + + public: + KopeteGVIProps(KopeteGroupViewItem *gvi, QWidget *parent, const char *name=0L); + ~KopeteGVIProps(); + + private: + CustomNotificationProps * mNotificationProps; + KopeteGVIPropsWidget *mainWidget; + KopeteGroupViewItem *item; + bool m_dirty; + + private slots: + void slotOkClicked(); + void slotUseCustomIconsToggled(bool on); + void slotIconChanged(); +}; + + +class KopeteMetaLVIProps: public KDialogBase +{ + Q_OBJECT + + public: + KopeteMetaLVIProps(KopeteMetaContactLVI *gvi, QWidget *parent, const char *name=0L); + ~KopeteMetaLVIProps(); + + private: + CustomNotificationProps * mNotificationProps; + QPushButton *mFromKABC; + KopeteMetaLVIPropsWidget *mainWidget; + AddressBookLinkWidget *linkWidget; + KopeteMetaContactLVI *item; + KopeteAddressBookExport *mExport; + KABC::Sound mSound; + int m_countPhotoCapable; + QMap m_withPhotoContacts; + QString mAddressBookUid; // the currently selected addressbook UID + + Kopete::MetaContact::PropertySource selectedNameSource() const; + Kopete::MetaContact::PropertySource selectedPhotoSource() const; + Kopete::Contact* selectedNameSourceContact() const; + Kopete::Contact* selectedPhotoSourceContact() const; + private slots: + void slotOkClicked(); + void slotUseCustomIconsToggled( bool on ); + void slotClearPhotoClicked(); + void slotAddresseeChanged( const KABC::Addressee & ); + void slotExportClicked(); + void slotImportClicked(); + void slotFromKABCClicked(); + void slotOpenSoundDialog( KURLRequester *requester ); + void slotLoadNameSources(); + void slotLoadPhotoSources(); + void slotEnableAndDisableWidgets(); +}; + +#endif diff --git a/kopete/kopete/contactlist/kopetemetacontactlvi.cpp b/kopete/kopete/contactlist/kopetemetacontactlvi.cpp new file mode 100644 index 00000000..86dc4b40 --- /dev/null +++ b/kopete/kopete/contactlist/kopetemetacontactlvi.cpp @@ -0,0 +1,1102 @@ +/* + kopetemetacontactlvi.cpp - Kopete Meta Contact KListViewItem + + Copyright (c) 2004 by Richard Smith + Copyright (c) 2002-2004 by Martijn Klingens + Copyright (c) 2002-2005 by Olivier Goffart + Copyright (c) 2002 by Duncan Mac-Vicar P + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include + +#include "knotification.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + + +#include "addcontactpage.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetecontactlist.h" +#include "kopetecontactlistview.h" +#include "kopeteemoticons.h" +#include "kopeteuiglobal.h" +#include "kopetegroup.h" +#include "kopetegroupviewitem.h" +#include "kopetemetacontact.h" +#include "kopetemetacontactlvi.h" +#include "kopetepluginmanager.h" +#include "kopeteprefs.h" +#include "kopetestdaction.h" +#include "systemtray.h" +#include "kopeteglobal.h" +#include "kopetecontact.h" +#include "kabcpersistence.h" + +#include + +using namespace Kopete::UI; + +namespace Kopete { +namespace UI { +namespace ListView { + +class MetaContactToolTipSource : public ToolTipSource +{ +public: + MetaContactToolTipSource( MetaContact *mc ) + : metaContact( mc ) + { + } + QString operator()( ComponentBase *, const QPoint &, QRect & ) + { + + // We begin with the meta contact display name at the top of the tooltip + QString toolTip = QString::fromLatin1(""); + + toolTip += QString::fromLatin1("
    "); + + if ( ! metaContact->photo().isNull() ) + { + QString photoName = QString::fromLatin1("kopete-metacontact-photo:%1").arg( KURL::encode_string( metaContact->metaContactId() )); + //QMimeSourceFactory::defaultFactory()->setImage( "contactimg", metaContact->photo() ); + toolTip += QString::fromLatin1("").arg( photoName ); + } + + toolTip += QString::fromLatin1(""); + + QString displayName; + Kopete::Emoticons *e = Kopete::Emoticons::self(); + QValueList t = e->tokenize( metaContact->displayName()); + QValueList::iterator it; + for( it = t.begin(); it != t.end(); ++it ) + { + if( (*it).type == Kopete::Emoticons::Image ) + { + displayName += (*it).picHTMLCode; + } else if( (*it).type == Kopete::Emoticons::Text ) + { + displayName += QStyleSheet::escape( (*it).text ); + } + } + + toolTip += QString::fromLatin1("%1

    ").arg( displayName ); + + QPtrList contacts = metaContact->contacts(); + if ( contacts.count() == 1 ) + { + return toolTip + contacts.first()->toolTip() + QString::fromLatin1("
    "); + } + + toolTip += QString::fromLatin1(""); + + // We are over a metacontact with > 1 child contacts, and not over a specific contact + // Iterate through children and display a summary tooltip + for(Contact *c = contacts.first(); c; c = contacts.next()) + { + QString iconName = QString::fromLatin1("kopete-contact-icon:%1:%2:%3") + .arg( KURL::encode_string( c->protocol()->pluginId() ), + KURL::encode_string( c->account()->accountId() ), + KURL::encode_string( c->contactId() ) + ); + + toolTip += i18n("", + "") + .arg( iconName, Kopete::Emoticons::parseEmoticons(c->property(Kopete::Global::Properties::self()->nickName()).value().toString()) , c->contactId(), c->onlineStatus().description() ); + } + + return toolTip + QString::fromLatin1("
    STATUS ICON PROTOCOL NAME (ACCOUNT NAME)STATUS DESCRIPTION
     %2 (%3)%4
    "); + } +private: + MetaContact *metaContact; +}; + +} // END namespace ListView +} // END namespace UI +} // END namespace Kopete + +class KopeteMetaContactLVI::Private +{ +public: + Private() : metaContactIcon( 0L ), nameText( 0L ), extraText( 0L ), contactIconBox( 0L ), + currentMode( -1 ), currentIconMode( -1 ) {} + ListView::ImageComponent *metaContactIcon; + ListView::DisplayNameComponent *nameText; + ListView::DisplayNameComponent *extraText; + ListView::BoxComponent *contactIconBox; + ListView::BoxComponent *spacerBox; + std::auto_ptr toolTipSource; + // metacontact icon size + int iconSize; + // protocol icon size + int contactIconSize; + int currentMode; + int currentIconMode; + + QPtrList events; +}; + +KopeteMetaContactLVI::KopeteMetaContactLVI( Kopete::MetaContact *contact, KopeteGroupViewItem *parent ) +: ListView::Item( parent, contact, "MetaContactLVI" ) +//: QObject( contact, "MetaContactLVI" ), KListViewItem( parent ) +{ + m_metaContact = contact; + m_isTopLevel = false; + m_parentGroup = parent; + m_parentView = 0L; + + initLVI(); + parent->refreshDisplayName(); +} + +KopeteMetaContactLVI::KopeteMetaContactLVI( Kopete::MetaContact *contact, QListViewItem *parent ) +: ListView::Item( parent, contact, "MetaContactLVI" ) +//: QObject( contact, "MetaContactLVI" ), KListViewItem( parent ) +{ + m_metaContact = contact; + + m_isTopLevel = true; + m_parentGroup = 0L; + m_parentView = 0L; + + initLVI(); +} + +KopeteMetaContactLVI::KopeteMetaContactLVI( Kopete::MetaContact *contact, QListView *parent ) +: ListView::Item( parent, contact, "MetaContactLVI" ) +//: QObject( contact, "MetaContactLVI" ), KListViewItem( parent ) +{ + m_metaContact = contact; + + m_isTopLevel = true; + m_parentGroup = 0L; + m_parentView = parent; + + initLVI(); +} + +void KopeteMetaContactLVI::initLVI() +{ + d = new Private; + + d->toolTipSource.reset( new ListView::MetaContactToolTipSource( m_metaContact ) ); + + m_oldStatus = m_metaContact->status(); + + connect( m_metaContact, SIGNAL( displayNameChanged( const QString &, const QString & ) ), + SLOT( slotDisplayNameChanged() ) ); + + connect( m_metaContact, SIGNAL( photoChanged() ), + SLOT( slotPhotoChanged() ) ); + + connect( m_metaContact, SIGNAL( onlineStatusChanged( Kopete::MetaContact *, Kopete::OnlineStatus::StatusType ) ), + SLOT( slotPhotoChanged() ) ); + + connect( m_metaContact, SIGNAL( onlineStatusChanged( Kopete::MetaContact *, Kopete::OnlineStatus::StatusType ) ), + this, SLOT(slotIdleStateChanged( ) ) ); + + connect( m_metaContact, SIGNAL( contactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus & ) ), + SLOT( slotContactStatusChanged( Kopete::Contact * ) ) ); + + connect( m_metaContact, SIGNAL( contactAdded( Kopete::Contact * ) ), + SLOT( slotContactAdded( Kopete::Contact * ) ) ); + + connect( m_metaContact, SIGNAL( contactRemoved( Kopete::Contact * ) ), + SLOT( slotContactRemoved( Kopete::Contact * ) ) ); + + connect( m_metaContact, SIGNAL( iconAppearanceChanged() ), + SLOT( slotUpdateMetaContact() ) ); + + connect( m_metaContact, SIGNAL( useCustomIconChanged( bool ) ), + SLOT( slotUpdateMetaContact() ) ); + + connect( m_metaContact, SIGNAL( contactIdleStateChanged( Kopete::Contact * ) ), + SLOT( slotIdleStateChanged( Kopete::Contact * ) ) ); + + connect( KopetePrefs::prefs(), SIGNAL( contactListAppearanceChanged() ), + SLOT( slotConfigChanged() ) ); + + connect( kapp, SIGNAL( appearanceChanged() ), SLOT( slotConfigChanged() ) ); + + mBlinkTimer = new QTimer( this, "mBlinkTimer" ); + connect( mBlinkTimer, SIGNAL( timeout() ), SLOT( slotBlink() ) ); + mIsBlinkIcon = false; + + //if ( !mBlinkIcon ) + // mBlinkIcon = new QPixmap( KGlobal::iconLoader()->loadIcon( QString::fromLatin1( "newmsg" ), KIcon::Small ) ); + + slotConfigChanged(); // this calls slotIdleStateChanged(), which sets up the constituent components, spacing, fonts and indirectly, the contact icon + slotDisplayNameChanged(); + updateContactIcons(); +} + +KopeteMetaContactLVI::~KopeteMetaContactLVI() +{ + delete d; + //if ( m_parentGroup ) + // m_parentGroup->refreshDisplayName(); +} + +void KopeteMetaContactLVI::movedToDifferentGroup() +{ + KopeteContactListView *lv = dynamic_cast( listView() ); + if ( !lv ) + return; + + if ( m_parentGroup ) + m_parentGroup->refreshDisplayName(); + + // create a spacer if wanted + // I assume that the safety property that allows the delete in slotConfigChanged holds here - Will + delete d->spacerBox->component( 0 ); + if ( KListViewItem::parent() && KopetePrefs::prefs()->contactListIndentContacts() && + !KopetePrefs::prefs()->treeView() ) + { + new ListView::SpacerComponent( d->spacerBox, 20, 0 ); + } + + KopeteGroupViewItem *group_item = dynamic_cast(KListViewItem::parent()); + if ( group_item ) + { + m_isTopLevel = false; + m_parentGroup = group_item; + m_parentView = 0L; + group_item->refreshDisplayName(); + } + else + { + m_isTopLevel = true; + m_parentGroup = 0L; + m_parentView = lv; + } +} + +void KopeteMetaContactLVI::rename( const QString& newName ) +{ + QString oldName = m_metaContact->displayName(); + KopeteContactListView *lv = dynamic_cast( listView() ); + if ( lv ) + { + KopeteContactListView::UndoItem *u=new KopeteContactListView::UndoItem(KopeteContactListView::UndoItem::MetaContactRename, m_metaContact); + // HACK but args are strings not ints + u->nameSource = m_metaContact->displayNameSource(); + // additional args + if ( m_metaContact->displayNameSource() == Kopete::MetaContact::SourceCustom ) + { + u->args << m_metaContact->customDisplayName(); + } + else if ( m_metaContact->displayNameSource() == Kopete::MetaContact::SourceContact ) + { + Kopete::Contact* c = m_metaContact->displayNameSourceContact(); + if(c) + u->args << c->contactId() << c->protocol()->pluginId() << c->account()->accountId(); + } + // source kabc requires no arguments + + lv->insertUndoItem(u); + } + + if ( newName.isEmpty() ) + { + // fallback to KABC + if ( !m_metaContact->metaContactId().isEmpty() ) + { + m_metaContact->setDisplayNameSource(Kopete::MetaContact::SourceKABC); + if ( ! m_metaContact->displayName().isEmpty() ) + { + slotDisplayNameChanged(); + return; + } + } + // bad luck with KABC + m_metaContact->setDisplayNameSource(Kopete::MetaContact::SourceContact); + // TODO iterate though all subcontacts to check non empty nick + m_metaContact->setDisplayNameSourceContact( m_metaContact->contacts().first() ); + slotDisplayNameChanged(); + } + else // user changed name manually, set source to custom + { + m_metaContact->setDisplayNameSource( Kopete::MetaContact::SourceCustom ); + m_metaContact->setDisplayName( newName ); + slotDisplayNameChanged(); + } + + kdDebug( 14000 ) << k_funcinfo << "newName=" << newName << endl; +} + +void KopeteMetaContactLVI::slotContactStatusChanged( Kopete::Contact *c ) +{ + updateContactIcon( c ); + + // FIXME: All this code should be in kopetemetacontact.cpp.. having it in the LVI makes it all fire + // multiple times if the user is in multiple groups - Jason + + // comparing the status of the previous and new preferred contact is the determining factor in deciding to notify + Kopete::OnlineStatus newStatus; + if ( m_metaContact->preferredContact() ) + newStatus = m_metaContact->preferredContact()->onlineStatus(); + else + { + // the last child contact has gone offline or otherwise unreachable, so take the changed contact's online status + newStatus = c->onlineStatus(); + } + + // ensure we are not suppressing notifications, because connecting or disconnected + if ( !(c->account()->suppressStatusNotification() + || ( c->account()->myself()->onlineStatus().status() == Kopete::OnlineStatus::Connecting ) + || !c->account()->isConnected() ) ) + { + if ( !c->account()->isAway() || KopetePrefs::prefs()->soundIfAway() ) + { + //int winId = KopeteSystemTray::systemTray() ? KopeteSystemTray::systemTray()->winId() : 0; + + QString text = i18n( "%1 is now %2." ) + .arg( Kopete::Emoticons::parseEmoticons( QStyleSheet::escape(m_metaContact->displayName()) ) , + QStyleSheet::escape(c->onlineStatus().description())); + + // figure out what's happened + enum ChangeType { noChange, noEvent, signedIn, changedStatus, signedOut }; + ChangeType t = noChange; + //kdDebug( 14000 ) << k_funcinfo << m_metaContact->displayName() << + //" - Old MC Status: " << m_oldStatus.status() << ", New MC Status: " << newStatus.status() << endl; + // first, exclude changes due to blocking or subscription changes at the protocol level + if ( ( m_oldStatus.status() == Kopete::OnlineStatus::Unknown + || newStatus.status() == Kopete::OnlineStatus::Unknown ) ) + t = noEvent; // This means the contact's changed from or to unknown - due to a protocol state change, not a contact state change + else // we're dealing with a genuine contact state change + { + if ( m_oldStatus.status() == Kopete::OnlineStatus::Offline ) + { + if ( newStatus.status() != Kopete::OnlineStatus::Offline ) + { + //kdDebug( 14000 ) << "signed in" << endl; + t = signedIn; // contact has gone from offline to something else, it's a sign-in + } + } + else if ( m_oldStatus.status() == Kopete::OnlineStatus::Online + || m_oldStatus.status() == Kopete::OnlineStatus::Away + || m_oldStatus.status() == Kopete::OnlineStatus::Invisible) + { + if ( newStatus.status() == Kopete::OnlineStatus::Offline ) + { + //kdDebug( 14000 ) << "signed OUT" << endl; + t = signedOut; // contact has gone from an online state to an offline state, it's a sign out + } + else if ( m_oldStatus > newStatus || m_oldStatus < newStatus ) // operator!= is useless because it's an identity operator, not an equivalence operator + { + // contact has changed online states, it's a status change, + // and the preferredContact changed status, or there is a new preferredContacat + // so it's worth notifying + //kdDebug( 14000 ) << "changed status" << endl; + t = changedStatus; + } + } + else if ( m_oldStatus != newStatus ) + { + //kdDebug( 14000 ) << "non-event" << endl; + // catch-all for any other status change we don't know about + t = noEvent; + } + // if none of the above were true, t will still be noChange + } + + // now issue the appropriate notification + switch ( t ) + { + case noEvent: + case noChange: + break; + case signedIn: + connect(KNotification::event(m_metaContact, "kopete_contact_online", text, m_metaContact->photo(), KopeteSystemTray::systemTray(), i18n( "Chat" )) , + SIGNAL(activated(unsigned int )) , this, SLOT( execute() ) ); + break; + case changedStatus: + connect(KNotification::event(m_metaContact, "kopete_contact_status_change", text, m_metaContact->photo(), KopeteSystemTray::systemTray(), i18n( "Chat" )) , + SIGNAL(activated(unsigned int )) , this, SLOT( execute() )); + break; + case signedOut: + KNotification::event(m_metaContact, "kopete_contact_offline", text, m_metaContact->photo(), KopeteSystemTray::systemTray()); + break; + } + } + //blink if the metacontact icon has changed. + if ( !mBlinkTimer->isActive() && d->metaContactIcon /*&& d->metaContactIcon->pixmap() != m_oldStatusIcon */) + { + mIsBlinkIcon = false; + m_blinkLeft = 9; + mBlinkTimer->start( 400, false ); + } + } + else + { + //the status icon probably changed, but we didn't blink. + //So the olfStatusIcon will not be set to the real after the blink. + //we set it now. + if( !mBlinkTimer->isActive() ) + m_oldStatusIcon=d->metaContactIcon ? d->metaContactIcon->pixmap() : QPixmap(); + } + + // make a note of the current status for the next time we get a status change + m_oldStatus = newStatus; + + if ( m_parentGroup ) + m_parentGroup->refreshDisplayName(); + updateVisibility(); +} + +void KopeteMetaContactLVI::slotUpdateMetaContact() +{ + slotIdleStateChanged( 0 ); + updateVisibility(); + + if ( m_parentGroup ) + m_parentGroup->refreshDisplayName(); +} + +void KopeteMetaContactLVI::execute() const +{ + if ( d->events.first() ) + d->events.first()->apply(); + else + m_metaContact->execute(); + + //The selection is removed, but the contact still hihjlihted, remove the selection in the contactlist (see bug 106090) + Kopete::ContactList::self()->setSelectedItems( QPtrList() , QPtrList() ); +} + +void KopeteMetaContactLVI::slotDisplayNameChanged() +{ + if ( d->nameText ) + { + d->nameText->setText( m_metaContact->displayName() ); + + // delay the sort if we can + if ( ListView::ListView *lv = dynamic_cast( listView() ) ) + lv->delayedSort(); + else + listView()->sort(); + } +} + +void KopeteMetaContactLVI::slotPhotoChanged() +{ + if ( d->metaContactIcon && d->currentIconMode == KopetePrefs::PhotoPic ) + { + m_oldStatusIcon= d->metaContactIcon->pixmap(); + QPixmap photoPixmap; + //QPixmap defaultIcon( KGlobal::iconLoader()->loadIcon( "vcard", KIcon::Desktop ) ); + QImage photoImg = m_metaContact->photo(); + if ( !photoImg.isNull() && (photoImg.width() > 0) && (photoImg.height() > 0) ) + { + int photoSize = d->iconSize; + + photoImg = photoImg.smoothScale( photoSize, photoSize, QImage::ScaleMin ); + + KImageEffect *effect = 0L; + switch ( m_metaContact->status() ) + { + case Kopete::OnlineStatus::Online: + break; + case Kopete::OnlineStatus::Away: + effect = new KImageEffect(); + effect->fade(photoImg, 0.5, Qt::white); + break; + case Kopete::OnlineStatus::Offline: + effect = new KImageEffect(); + effect->fade(photoImg, 0.4, Qt::white); + effect->toGray(photoImg); + break; + case Kopete::OnlineStatus::Unknown: + default: + effect = new KImageEffect(); + effect->fade(photoImg, 0.8, Qt::white); + } + delete effect; + + photoPixmap = photoImg; + } + else + { + photoPixmap=SmallIcon(m_metaContact->statusIcon(), d->iconSize); + } + d->metaContactIcon->setPixmap( photoPixmap, false); + if(mBlinkTimer->isActive()) + m_originalBlinkIcon=photoPixmap; + } +} + +/* +void KopeteMetaContactLVI::slotRemoveThisUser() +{ + kdDebug( 14000 ) << k_funcinfo << " Removing user" << endl; + //m_metaContact->removeThisUser(); + + if ( KMessageBox::warningContinueCancel( Kopete::UI::Global::mainWidget(), + i18n( "Are you sure you want to remove %1 from your contact list?" ). + arg( m_metaContact->displayName() ), i18n( "Remove Contact" ), KGuiItem(i18n("Remove"),"delete_user") ) + == KMessageBox::Continue ) + { + Kopete::ContactList::self()->removeMetaContact( m_metaContact ); + } +} + +void KopeteMetaContactLVI::slotRemoveFromGroup() +{ + if ( m_metaContact->isTemporary() ) + return; + + m_metaContact->removeFromGroup( group() ); +} +*/ + +void KopeteMetaContactLVI::startRename( int /*col*/ ) +{ + KListViewItem::startRename( 0 ); +} + +void KopeteMetaContactLVI::okRename( int col ) +{ + KListViewItem::okRename( col ); + setRenameEnabled( 0, false ); +} + +void KopeteMetaContactLVI::cancelRename( int col ) +{ + KListViewItem::cancelRename( col ); + setRenameEnabled( 0, false ); +} + +/* +void KopeteMetaContactLVI::slotMoveToGroup() +{ + if ( m_actionMove && !m_metaContact->isTemporary() ) + { + if ( m_actionMove->currentItem() == 0 ) + { + // we are moving to top-level + if ( group() != Kopete::Group::toplevel ) + m_metaContact->moveToGroup( group(), Kopete::Group::toplevel ); + } + else + { + Kopete::Group *to = Kopete::ContactList::self()->getGroup( m_actionMove->currentText() ); + if ( !m_metaContact->groups().contains( to ) ) + m_metaContact->moveToGroup( group(), to ); + } + } +} + +void KopeteMetaContactLVI::slotAddToGroup() +{ + if ( m_actionCopy ) + { + kdDebug( 14000 ) << "KopeteMetaContactLVI::slotAddToGroup " << endl; + if ( m_actionCopy->currentItem() == 0 ) + { + // we are adding to top-level + m_metaContact->addToGroup( Kopete::Group::toplevel ); + } + else + { + m_metaContact->addToGroup( Kopete::ContactList::self()->getGroup( m_actionCopy->currentText() ) ); + } + } +} +*/ + +//FIXME: this is not used... remove? +void KopeteMetaContactLVI::slotAddToNewGroup() +{ + if ( m_metaContact->isTemporary() ) + return; + + QString groupName = KInputDialog::getText( + i18n( "New Group" ), i18n( "Please enter the name for the new group:" ) ); + + if ( !groupName.isEmpty() ) + m_metaContact->addToGroup( Kopete::ContactList::self()->findGroup( groupName ) ); +} + +void KopeteMetaContactLVI::slotConfigChanged() +{ + setDisplayMode( KopetePrefs::prefs()->contactListDisplayMode(), + KopetePrefs::prefs()->contactListIconMode() ); + + // create a spacer if wanted + delete d->spacerBox->component( 0 ); + if ( KListViewItem::parent() && KopetePrefs::prefs()->contactListIndentContacts() && + !KopetePrefs::prefs()->treeView() ) + { + new ListView::SpacerComponent( d->spacerBox, 20, 0 ); + } + + if ( KopetePrefs::prefs()->contactListUseCustomFonts() ) + { + d->nameText->setFont( KopetePrefs::prefs()->contactListCustomNormalFont() ); + if ( d->extraText ) + d->extraText->setFont( KopetePrefs::prefs()->contactListSmallFont() ); + } + else + { + QFont font=listView()->font(); + d->nameText->setFont( font ); + if(d->extraText) + { + if ( font.pixelSize() != -1 ) + font.setPixelSize( (font.pixelSize() * 3) / 4 ); + else + font.setPointSizeFloat( font.pointSizeFloat() * 0.75 ); + d->extraText->setFont( font ); + } + } + + updateVisibility(); + updateContactIcons(); + slotIdleStateChanged( 0 ); + if(d->nameText) + d->nameText->redraw(); + if(d->extraText) + d->extraText->redraw(); +} + +void KopeteMetaContactLVI::setMetaContactToolTipSourceForComponent( ListView::Component *comp ) +{ + if ( comp ) + comp->setToolTipSource( d->toolTipSource.get() ); +} + +void KopeteMetaContactLVI::setDisplayMode( int mode, int iconmode ) +{ + if ( mode == d->currentMode && iconmode == d->currentIconMode ) + return; + + d->currentMode = mode; + d->currentIconMode = iconmode; + + // empty... + while ( component( 0 ) ) + delete component( 0 ); + + d->nameText = 0L; + d->extraText = 0L; + d->metaContactIcon = 0L; + d->contactIconSize = 12; + if (mode == KopetePrefs::Detailed) { + d->iconSize = iconmode == KopetePrefs::IconPic ? KIcon::SizeMedium : KIcon::SizeLarge; + } else { + d->iconSize = iconmode == KopetePrefs::IconPic ? IconSize( KIcon::Small ) : KIcon::SizeMedium; + } + disconnect( Kopete::KABCPersistence::self()->addressBook() , 0 , this , 0); + + // generate our contents + using namespace ListView; + Component *hbox = new BoxComponent( this, BoxComponent::Horizontal ); + d->spacerBox = new BoxComponent( hbox, BoxComponent::Horizontal ); + + if (iconmode == KopetePrefs::PhotoPic) { + Component *imageBox = new BoxComponent( hbox, BoxComponent::Vertical ); + new VSpacerComponent( imageBox ); + d->metaContactIcon = new ImageComponent( imageBox, d->iconSize + 2 , d->iconSize + 2 ); + new VSpacerComponent( imageBox ); + if(!metaContact()->photoSource() && !Kopete::KABCPersistence::self()->addressBook()->findByUid( metaContact()->metaContactId() ).isEmpty() ) + { //if the photo is the one of the kaddressbook, track every change in the adressbook, it might be the photo of our contact. + connect( Kopete::KABCPersistence::self()->addressBook() , SIGNAL(addressBookChanged (AddressBook *) ) , + this , SLOT(slotPhotoChanged())); + } + } else { + d->metaContactIcon = new ImageComponent( hbox ); + } + + if( mode == KopetePrefs::Detailed ) + { + d->contactIconSize = IconSize( KIcon::Small ); + Component *vbox = new BoxComponent( hbox, BoxComponent::Vertical ); + d->nameText = new DisplayNameComponent( vbox ); + d->extraText = new DisplayNameComponent( vbox ); + + Component *box = new BoxComponent( vbox, BoxComponent::Horizontal ); + d->contactIconBox = new BoxComponent( box, BoxComponent::Horizontal ); + } + else if( mode == KopetePrefs::RightAligned ) // old right-aligned contact + { + d->nameText = new DisplayNameComponent( hbox ); + new HSpacerComponent( hbox ); + d->contactIconBox = new BoxComponent( hbox, BoxComponent::Horizontal ); + } + else // older left-aligned contact + { + d->nameText = new DisplayNameComponent( hbox ); + d->contactIconBox = new BoxComponent( hbox, BoxComponent::Horizontal ); + } + + // set some components to have the metacontact tooltip + setMetaContactToolTipSourceForComponent( d->metaContactIcon ); + setMetaContactToolTipSourceForComponent( d->nameText ); + setMetaContactToolTipSourceForComponent( d->extraText ); + + // update the display name + slotDisplayNameChanged(); + slotPhotoChanged(); + slotIdleStateChanged( 0 ); + + // finally, re-add all contacts so their icons appear. remove them first for consistency. + QPtrList contacts = m_metaContact->contacts(); + for ( QPtrListIterator it( contacts ); it.current(); ++it ) + { + slotContactRemoved( *it ); + slotContactAdded( *it ); + } + m_oldStatusIcon=d->metaContactIcon ? d->metaContactIcon->pixmap() : QPixmap(); + if( mBlinkTimer->isActive() ) + m_originalBlinkIcon=m_oldStatusIcon; +} + +void KopeteMetaContactLVI::updateVisibility() +{ + if ( KopetePrefs::prefs()->showOffline() || !d->events.isEmpty() ) + setTargetVisibility( true ); + else if ( !m_metaContact->isOnline() && !mBlinkTimer->isActive() ) + setTargetVisibility( false ); + else + setTargetVisibility( true ); +} + +void KopeteMetaContactLVI::slotContactPropertyChanged( Kopete::Contact *contact, + const QString &key, const QVariant &old, const QVariant &newVal ) +{ +// if ( key == QString::fromLatin1("awayMessage") ) +// kdDebug( 14000 ) << k_funcinfo << "contact=" << contact->contactId() << ", isonline=" << contact->isOnline() << ", alloffline=" << !m_metaContact->isOnline() << ", oldvalue=" << old.toString() << ", newvalue=" << newVal.toString() << endl; + if ( key == QString::fromLatin1("awayMessage") && d->extraText && old != newVal ) + { + bool allOffline = !m_metaContact->isOnline(); + if ( newVal.toString().isEmpty() || ( !contact->isOnline() && !allOffline ) ) + { + // try to find a more suitable away message to be displayed when: + // -new away message is empty or + // -contact who set it is offline and there are contacts online in the metacontact + bool allAwayMessagesEmpty = true; + QPtrList contacts = m_metaContact->contacts(); + for ( Kopete::Contact *c = contacts.first(); c; c = contacts.next() ) + { +// kdDebug( 14000 ) << k_funcinfo << "ccontact=" << c->contactId() << ", isonline=" << c->isOnline() << ", awaymsg=" << c->property( key ).value().toString() << endl; + QString awayMessage( c->property( key ).value().toString() ); + if ( ( allOffline || c->isOnline() ) && !awayMessage.isEmpty() ) + { + // display this contact's away message when: + // -this contact's away message is not empty and + // -this contact is online or there are no contacts online at all + allAwayMessagesEmpty = false; + d->extraText->setText( awayMessage ); + break; + } + } + if ( allAwayMessagesEmpty ) + d->extraText->setText( QString::null ); + } + else + { + // just use new away message when: + // -new away message is not empty and + // -contact who set it is online or there are no contacts online at all + d->extraText->setText( newVal.toString() ); + } + } // wtf? KopeteMetaContact also connects this signals and emits photoChanged! why no connect photoChanged to slotPhotoChanged? + /*else if ( key == QString::fromLatin1("photo") && (m_metaContact->photoSourceContact() == contact) && (m_metaContact->photoSource() == Kopete::MetaContact::SourceContact)) + { + slotPhotoChanged(); + }*/ +} + +void KopeteMetaContactLVI::slotContactAdded( Kopete::Contact *c ) +{ + connect( c, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, + const QVariant &, const QVariant & ) ), + this, SLOT( slotContactPropertyChanged( Kopete::Contact *, const QString &, + const QVariant &, const QVariant & ) ) ); + connect( c->account() , SIGNAL( colorChanged(const QColor& ) ) , this, SLOT( updateContactIcons() ) ); + + updateContactIcon( c ); + + slotContactPropertyChanged( c, QString::fromLatin1("awayMessage"), + QVariant(), c->property( QString::fromLatin1("awayMessage") ).value() ); +} + +void KopeteMetaContactLVI::slotContactRemoved( Kopete::Contact *c ) +{ + disconnect( c, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, + const QVariant &, const QVariant & ) ), + this, SLOT( slotContactPropertyChanged( Kopete::Contact *, + const QString &, const QVariant &, const QVariant & ) ) ); + disconnect( c->account() , SIGNAL( colorChanged(const QColor& ) ) , this, SLOT( updateContactIcons() ) ); + + if ( ListView::Component *comp = contactComponent( c ) ) + delete comp; + + slotContactPropertyChanged( c, QString::fromLatin1("awayMessage"), + c->property( QString::fromLatin1("awayMessage") ).value(), QVariant() ); +} + +void KopeteMetaContactLVI::updateContactIcons() +{ + // show offline contacts setting may have changed + QPtrList contacts = m_metaContact->contacts(); + for ( QPtrListIterator it( contacts ); it.current(); ++it ) + updateContactIcon( *it ); +} + +void KopeteMetaContactLVI::updateContactIcon( Kopete::Contact *c ) +{ + KGlobal::config()->setGroup( QString::fromLatin1("ContactList") ); + bool bHideOffline = KGlobal::config()->readBoolEntry( + QString::fromLatin1("HideOfflineContacts"), false ); + if ( KopetePrefs::prefs()->showOffline() ) + bHideOffline = false; + + ListView::ContactComponent *comp = contactComponent( c ); + bool bShow = !bHideOffline || c->isOnline(); + if ( bShow && !comp ) + (void)new ListView::ContactComponent( d->contactIconBox, c, d->contactIconSize ); + else if ( !bShow && comp ) + delete comp; + else if ( comp ) + comp->updatePixmap(); +} + +Kopete::Contact *KopeteMetaContactLVI::contactForPoint( const QPoint &p ) const +{ + if ( ListView::ContactComponent *comp = dynamic_cast( d->contactIconBox->componentAt( p ) ) ) + return comp->contact(); + return 0L; +} + +ListView::ContactComponent *KopeteMetaContactLVI::contactComponent( const Kopete::Contact *c ) const +{ + for ( uint n = 0; n < d->contactIconBox->components(); ++n ) + { + if ( ListView::ContactComponent *comp = dynamic_cast( d->contactIconBox->component( n ) ) ) + { + if ( comp->contact() == c ) + return comp; + } + } + return 0; +} + +QRect KopeteMetaContactLVI::contactRect( const Kopete::Contact *c ) const +{ + if ( ListView::Component *comp = contactComponent( c ) ) + return comp->rect(); + return QRect(); +} + +Kopete::Group *KopeteMetaContactLVI::group() +{ + if ( m_parentGroup && m_parentGroup->group() != Kopete::Group::topLevel() ) + return m_parentGroup->group(); + else + return Kopete::Group::topLevel(); +} + +QString KopeteMetaContactLVI::key( int, bool ) const +{ + char importanceChar; + switch ( m_metaContact->status() ) + { + case Kopete::OnlineStatus::Online: + importanceChar = 'A'; + break; + case Kopete::OnlineStatus::Away: + importanceChar = 'B'; + break; + case Kopete::OnlineStatus::Offline: + importanceChar = 'C'; + break; + case Kopete::OnlineStatus::Unknown: + default: + importanceChar = 'D'; + } + + return importanceChar + d->nameText->text().lower(); +} + +bool KopeteMetaContactLVI::isTopLevel() const +{ + return m_isTopLevel; +} + +bool KopeteMetaContactLVI::isGrouped() const +{ + if ( m_parentView ) + return true; + + if ( !m_parentGroup || !m_parentGroup->group() ) + return false; + + if ( m_parentGroup->group() == Kopete::Group::temporary() && !KopetePrefs::prefs()->sortByGroup() ) + return false; + + return true; +} + +void KopeteMetaContactLVI::slotIdleStateChanged( Kopete::Contact *c ) +{ + bool doWeHaveToGrayThatContact = KopetePrefs::prefs()->greyIdleMetaContacts() && ( m_metaContact->idleTime() >= 10 * 60 ); + if ( doWeHaveToGrayThatContact ) + { + d->nameText->setColor( KopetePrefs::prefs()->idleContactColor() ); + if ( d->extraText ) + d->extraText->setColor( KopetePrefs::prefs()->idleContactColor() ); + } + else + { + d->nameText->setDefaultColor(); + if ( d->extraText ) + d->extraText->setDefaultColor(); + } + + if(d->metaContactIcon && d->currentIconMode==KopetePrefs::IconPic) + { + m_oldStatusIcon=d->metaContactIcon->pixmap(); + + QPixmap icon = SmallIcon( m_metaContact->statusIcon(), d->iconSize ); + if ( doWeHaveToGrayThatContact ) + { + // TODO: QPixmapCache this result + KIconEffect::semiTransparent( icon ); + } + + d->metaContactIcon->setPixmap( icon ); + if(mBlinkTimer->isActive()) + m_originalBlinkIcon=icon; + } + // we only need to update the contact icon if one was supplied; + // if none was supplied, we only need to update the MC appearance + if ( c ) + updateContactIcon( c ); + else + return; +} + +void KopeteMetaContactLVI::catchEvent( Kopete::MessageEvent *event ) +{ + d->events.append( event ); + + connect( event, SIGNAL( done( Kopete::MessageEvent* ) ), + this, SLOT( slotEventDone( Kopete::MessageEvent * ) ) ); + + if ( mBlinkTimer->isActive() ) + mBlinkTimer->stop(); + + m_oldStatusIcon= d->metaContactIcon ? d->metaContactIcon->pixmap() : QPixmap(); + + mBlinkTimer->start( 400, false ); + + //show the contact if it was hidden because offline. + updateVisibility(); + } + +void KopeteMetaContactLVI::slotBlink() +{ + bool haveEvent = !d->events.isEmpty(); + if ( mIsBlinkIcon ) + { + if(d->metaContactIcon) + d->metaContactIcon->setPixmap( m_originalBlinkIcon ); + if ( !haveEvent && m_blinkLeft <= 0 ) + { + mBlinkTimer->stop(); + m_oldStatusIcon=d->metaContactIcon ? d->metaContactIcon->pixmap() : QPixmap(); + updateVisibility(); + m_originalBlinkIcon=QPixmap(); //i hope this help to reduce memory consuption + } + } + else + { + if(d->metaContactIcon) + m_originalBlinkIcon=d->metaContactIcon->pixmap(); + if ( haveEvent ) + { + if(d->metaContactIcon) + d->metaContactIcon->setPixmap( SmallIcon( "newmsg", d->iconSize ) ); + } + else + { + if(d->metaContactIcon) + d->metaContactIcon->setPixmap( m_oldStatusIcon ); + m_blinkLeft--; + } + } + + mIsBlinkIcon = !mIsBlinkIcon; +} + +void KopeteMetaContactLVI::slotEventDone( Kopete::MessageEvent *event ) +{ + d->events.remove( event ); + + if ( d->events.isEmpty() ) + { + if ( mBlinkTimer->isActive() ) + { + mBlinkTimer->stop(); + //If the contact gone offline while the timer was actif, + //the visibility has not been correctly updated. so do it now + updateVisibility(); + } + + if(d->metaContactIcon) + d->metaContactIcon->setPixmap( m_originalBlinkIcon ); + m_originalBlinkIcon=QPixmap(); //i hope this help to reduce memory consuption + mIsBlinkIcon = false; + } +} + +QString KopeteMetaContactLVI::text( int column ) const +{ + if ( column == 0 ) + return d->nameText->text(); + else + return KListViewItem::text( column ); +} + +void KopeteMetaContactLVI::setText( int column, const QString &text ) +{ + if ( column == 0 ) + rename( text ); + else + KListViewItem::setText( column, text ); +} + +#include "kopetemetacontactlvi.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/contactlist/kopetemetacontactlvi.h b/kopete/kopete/contactlist/kopetemetacontactlvi.h new file mode 100644 index 00000000..767330ba --- /dev/null +++ b/kopete/kopete/contactlist/kopetemetacontactlvi.h @@ -0,0 +1,191 @@ +/* + kopetemetacontactlvi.h - Kopete Meta Contact KListViewItem + + Copyright (c) 2004 by Richard Smith + Copyright (c) 2002-2003 by Olivier Goffart + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002 by Duncan Mac-Vicar P + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef __kopetemetacontactlvi_h__ +#define __kopetemetacontactlvi_h__ + +#include "kopetelistviewitem.h" + +#include +#include +#include + +#include + +class QVariant; + +class KAction; +class KListAction; + +namespace Kopete +{ +class Account; +class MetaContact; +class Contact; +class Group; +class MessageEvent; +} + +class AddContactPage; +class KopeteGroupViewItem; + + +/** + * @author Martijn Klingens + */ +class KopeteMetaContactLVI : public Kopete::UI::ListView::Item +{ + Q_OBJECT + +public: + KopeteMetaContactLVI( Kopete::MetaContact *contact, KopeteGroupViewItem *parent ); + KopeteMetaContactLVI( Kopete::MetaContact *contact, QListViewItem *parent ); + KopeteMetaContactLVI( Kopete::MetaContact *contact, QListView *parent ); + ~KopeteMetaContactLVI(); + + /** + * metacontact this visual item represents + */ + Kopete::MetaContact *metaContact() const + { return m_metaContact; }; + + /** + * true if the item is at top level and not under a group + */ + bool isTopLevel() const; + + /** + * parent when top-level + */ + QListView *parentView() const { return m_parentView; }; + + /** + * parent when not top-level + */ + KopeteGroupViewItem *parentGroup() const { return m_parentGroup; }; + + /** + * call this when the item has been moved to a different group + */ + void movedToDifferentGroup(); + void rename( const QString& name ); + void startRename( int ); + + Kopete::Group *group(); + + /** + * Returns the Kopete::Contact of the small little icon at the point p + * @param p must be in the list view item's coordinate system. + * Returns a null pointer if p is not on a small icon. + * (This is used for e.g. the context-menu of a contact when + * right-clicking an icon, or the tooltips) + */ + Kopete::Contact *contactForPoint( const QPoint &p ) const; + + /** + * Returns the QRect small little icon used for the Kopete::Contact. + * The behavior is undefined if @param c doesn't point to a valid + * Kopete::Contact for this list view item. + * The returned QRect is using the list view item's coordinate + * system and should probably be transformed into the list view's + * coordinates before being of any practical use. + * Note that the returned Rect is always vertically stretched to fill + * the full list view item's height, only the width is relative to + * the actual icon width. + */ + QRect contactRect( const Kopete::Contact *c ) const; + + bool isGrouped() const; + + /** + * reimplemented from KListViewItem to take into account our alternate text storage + */ + virtual QString text( int column ) const; + virtual void setText( int column, const QString &text ); + +public slots: + /** + * Call the meta contact's execute as I don't want to expose m_contact + * directly. + */ + void execute() const; + + void catchEvent( Kopete::MessageEvent * ); + + void updateVisibility(); + +private slots: + void slotUpdateMetaContact(); + void slotContactStatusChanged( Kopete::Contact * ); + void slotContactPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ); + void slotContactAdded( Kopete::Contact * ); + void slotContactRemoved( Kopete::Contact * ); + + void slotDisplayNameChanged(); + void slotPhotoChanged(); + + void slotAddToNewGroup(); + void slotIdleStateChanged( Kopete::Contact * =0L); + + void slotConfigChanged(); + + void slotEventDone( Kopete::MessageEvent* ); + void slotBlink(); + + void updateContactIcons(); + +protected: + void okRename(int col); + void cancelRename(int col); + +private: + void initLVI(); + void setDisplayMode( int mode, int iconMode ); + void setMetaContactToolTipSourceForComponent( Kopete::UI::ListView::Component *comp ); + QString key( int column, bool ascending ) const; + void updateContactIcon( Kopete::Contact * ); + Kopete::UI::ListView::ContactComponent *contactComponent( const Kopete::Contact *c ) const; + + Kopete::MetaContact *m_metaContact; + KopeteGroupViewItem *m_parentGroup; + QListView *m_parentView; + bool m_isTopLevel; + + int m_pixelWide; + + Kopete::OnlineStatus m_oldStatus; + QPixmap m_oldStatusIcon; + QPixmap m_originalBlinkIcon; + + QTimer *mBlinkTimer; + + QPtrDict m_addContactActions; + + bool mIsBlinkIcon; + int m_blinkLeft; + + class Private; + Private *d; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/contactlist/kopetemetalvipropswidget.ui b/kopete/kopete/contactlist/kopetemetalvipropswidget.ui new file mode 100644 index 00000000..e191d4b9 --- /dev/null +++ b/kopete/kopete/contactlist/kopetemetalvipropswidget.ui @@ -0,0 +1,587 @@ + +KopeteMetaLVIPropsWidget + + + KopeteMetaLVIPropsWidget + + + + 0 + 0 + 530 + 457 + + + + + + + + unnamed + + + + tabWidget + + + + tab + + + &General + + + + unnamed + + + + grpAddressbook + + + + 1 + 1 + 0 + 0 + + + + Address Book Link + + + + unnamed + + + + widAddresseeLink + + + + + layout8 + + + + unnamed + + + + btnExportKABC + + + false + + + E&xport Details... + + + Export contact's details to the KDE Address Book + + + + + spacer8_2 + + + Horizontal + + + Expanding + + + + 107 + 20 + + + + + + btnImportKABC + + + false + + + &Import Contacts + + + Import contacts from the KDE Address Book + + + + + + + + + buttonGroup1 + + + Display Name Source + + + + unnamed + + + + radioNameKABC + + + Use addressbook &name (needs addressbook link) + + + + + layout11 + + + + unnamed + + + + radioNameContact + + + From contact: + + + + + spacer8 + + + Horizontal + + + Fixed + + + + 20 + 20 + + + + + + cmbAccountName + + + + 7 + 0 + 0 + 0 + + + + Contact to synchronize the displayname with. + + + + + + + layout12 + + + + unnamed + + + + radioNameCustom + + + Cus&tom: + + + + + spacer8_3 + + + Horizontal + + + Fixed + + + + 50 + 20 + + + + + + edtDisplayName + + + + + + + + + buttonGroup2 + + + Photo Source + + + + unnamed + + + + photoLabel + + + + 64 + 92 + + + + + 64 + 32767 + + + + Box + + + 1 + + + Photo + + + false + + + + + radioPhotoKABC + + + U&se addressbook photo (needs addressbook link) + + + + + layout13 + + + + unnamed + + + + radioPhotoContact + + + From contact: + + + + + spacer9 + + + Horizontal + + + Fixed + + + + 20 + 20 + + + + + + cmbAccountPhoto + + + + 7 + 0 + 0 + 0 + + + + Contact to synchronize the displayname with. + + + + + + + layout7 + + + + unnamed + + + + radioPhotoCustom + + + Custom: + + + + + spacer9_2 + + + Horizontal + + + Fixed + + + + 20 + 20 + + + + + + cmbPhotoUrl + + + + 7 + 5 + 0 + 0 + + + + + + btnClearPhoto + + + + 32 + 32 + + + + + + + + + + + + + chkSyncPhoto + + + S&ync photo to addressbook + + + + + + + TabPage + + + Ad&vanced + + + + unnamed + + + + grpIcons + + + Icons + + + + unnamed + + + + lblAway + + + Awa&y: + + + icnbAway + + + + + lblOnline + + + &Online: + + + icnbOnline + + + + + chkUseCustomIcons + + + Use custom status &icons + + + Check to set custom icons for this contact + + + + + icnbAway + + + + + + + + icnbOnline + + + + + + + + icnbOffline + + + + + + + + icnbUnknown + + + + + + + + lblOffline + + + O&ffline: + + + icnbOffline + + + + + lblUnknown + + + Un&known: + + + icnbUnknown + + + + + spacerHorizontal1 + + + Horizontal + + + Expanding + + + + 16 + 16 + + + + + + + + spacer12 + + + Vertical + + + Expanding + + + + 20 + 170 + + + + + + + + + + tabWidget + btnExportKABC + btnImportKABC + edtDisplayName + cmbAccountName + cmbAccountPhoto + chkSyncPhoto + chkUseCustomIcons + icnbOnline + icnbAway + icnbOffline + icnbUnknown + + + + Kopete::UI::AddressBookLinkWidget +
    addressbooklinkwidget.h
    +
    +
    + + + addressbooklinkwidget.h + klineedit.h + kpushbutton.h + kurlrequester.h + kcombobox.h + kpushbutton.h + kpushbutton.h + kicondialog.h + kicondialog.h + kicondialog.h + kicondialog.h + +
    diff --git a/kopete/kopete/contactlist/kopetestatusgroupviewitem.cpp b/kopete/kopete/contactlist/kopetestatusgroupviewitem.cpp new file mode 100644 index 00000000..9dc910dd --- /dev/null +++ b/kopete/kopete/contactlist/kopetestatusgroupviewitem.cpp @@ -0,0 +1,51 @@ +/* + kopetestatusgroupviewitem.cpp + + Class to show a status folder + + Copyright (c) 2001-2003 by Duncan Mac-Vicar Prett + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include "kopetestatusgroupviewitem.h" + +KopeteStatusGroupViewItem::KopeteStatusGroupViewItem( Kopete::OnlineStatus::StatusType status_ , QListView *parent, const char *name ) + : QListViewItem(parent,name) +{ + m_status = status_; +} + +KopeteStatusGroupViewItem::~KopeteStatusGroupViewItem() +{ +} + +QString KopeteStatusGroupViewItem::key( int, bool ) const +{ + switch (m_status) + { + case Kopete::OnlineStatus::Online : + return "A"; + break; + case Kopete::OnlineStatus::Away : + return "B"; + break; + case Kopete::OnlineStatus::Offline : + return "C"; + break; + case Kopete::OnlineStatus::Unknown : + default: + return "D"; + } +} + diff --git a/kopete/kopete/contactlist/kopetestatusgroupviewitem.h b/kopete/kopete/contactlist/kopetestatusgroupviewitem.h new file mode 100644 index 00000000..8b1a930f --- /dev/null +++ b/kopete/kopete/contactlist/kopetestatusgroupviewitem.h @@ -0,0 +1,43 @@ +/* + kopetestatusgroupviewitem.h + + Class to show a status folder + + Copyright (c) 2001-2003 by Duncan Mac-Vicar Prett + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETESTATUSGROUPVIEWITEM_H +#define KOPETESTATUSGROUPVIEWITEM_H + +#include +#include "kopetemetacontact.h" + +/** + *@author Duncan Mac-Vicar Prett + */ + + class KopeteStatusGroupViewItem : public QListViewItem +{ +public: + KopeteStatusGroupViewItem( Kopete::OnlineStatus::StatusType status_ , QListView *parent, const char *name=0); + ~KopeteStatusGroupViewItem(); + +private: + + Kopete::OnlineStatus::StatusType m_status; + QString key( int column, bool ascending ) const; + +}; + +#endif diff --git a/kopete/kopete/cr16-mime-kopete_emoticons.png b/kopete/kopete/cr16-mime-kopete_emoticons.png new file mode 100644 index 00000000..2f596e17 Binary files /dev/null and b/kopete/kopete/cr16-mime-kopete_emoticons.png differ diff --git a/kopete/kopete/cr22-app-kopete_all_away.png b/kopete/kopete/cr22-app-kopete_all_away.png new file mode 100644 index 00000000..b82d9abd Binary files /dev/null and b/kopete/kopete/cr22-app-kopete_all_away.png differ diff --git a/kopete/kopete/cr22-app-kopete_offline.png b/kopete/kopete/cr22-app-kopete_offline.png new file mode 100644 index 00000000..da80a3c9 Binary files /dev/null and b/kopete/kopete/cr22-app-kopete_offline.png differ diff --git a/kopete/kopete/cr22-app-kopete_some_away.png b/kopete/kopete/cr22-app-kopete_some_away.png new file mode 100644 index 00000000..d3587a19 Binary files /dev/null and b/kopete/kopete/cr22-app-kopete_some_away.png differ diff --git a/kopete/kopete/cr22-app-kopete_some_online.png b/kopete/kopete/cr22-app-kopete_some_online.png new file mode 100644 index 00000000..cb018a88 Binary files /dev/null and b/kopete/kopete/cr22-app-kopete_some_online.png differ diff --git a/kopete/kopete/cr22-mime-kopete_emoticons.png b/kopete/kopete/cr22-mime-kopete_emoticons.png new file mode 100644 index 00000000..5aa7437d Binary files /dev/null and b/kopete/kopete/cr22-mime-kopete_emoticons.png differ diff --git a/kopete/kopete/eventsrc b/kopete/kopete/eventsrc new file mode 100644 index 00000000..9d3cf2f7 --- /dev/null +++ b/kopete/kopete/eventsrc @@ -0,0 +1,1950 @@ +[!Global!] +IconName=kopete +Comment=Kopete Messenger +Comment[ar]=مرسال Kopete +Comment[be]=Праграма імгненных паведамленнÑÑž Kopete +Comment[bg]=МеÑинджър +Comment[bn]=কপেট বারà§à¦¤à¦¾à¦¬à¦¾à¦¹à¦• +Comment[ca]=Missatger Kopete +Comment[cs]=Kopete komunikátor +Comment[cy]=Negesydd Kopete +Comment[de]=Kopete-Nachrichtendienst +Comment[el]=Αποστολέας μηνυμάτων Kopete +Comment[eo]=Kopete-mesaÄilo +Comment[es]=Mensajería de Kopete +Comment[fa]=پیام‌رسان Kopete +Comment[fi]=Kopete-viestin +Comment[fr]=Messager Kopete +Comment[gl]=Mensaxería instantánea con Kopete +Comment[he]=תוכנת ×”×ž×¡×¨×™× ×”×ž×™×™×“×™× Kopete +Comment[hi]=के-ऑपà¥à¤Ÿà¥€ मैसेंजर +Comment[hu]=Kopete üzenetküldÅ‘ +Comment[is]=Kopete samtalsforrit +Comment[it]=Messaggistica Kopete +Comment[ja]=Kopete メッセンジャー +Comment[ka]=Kopete მესინჯერი +Comment[kk]=Kopete хабарлаÑу бағдарламаÑÑ‹ +Comment[km]=កម្មវិធី​ផ្ញើ​សំបុážáŸ’ážš Kopete +Comment[lt]=Kopete žinuÄių klientas +Comment[mk]=Kopete глаÑник +Comment[nb]=Kopete meldingsprogram +Comment[nds]=Kopete-Kortnarichtendeenst +Comment[ne]=कोपेट मेसेनà¥à¤œà¤° +Comment[nn]=Lynmeldingsprogrammet Kopete +Comment[pl]=Komunikator Kopete +Comment[pt]=Mensageiro Kopete +Comment[pt_BR]=Mensageiro Kopete +Comment[ro]=Mesaje instantanee Kopete +Comment[ru]=Программа обмена ÑообщениÑми Kopete +Comment[se]=Kopete-Å¡leaÄ‘gadieđáhusprográmma +Comment[sl]=SporoÄilnik Kopete +Comment[sr]=ГлаÑник Kopete +Comment[sr@Latn]=Glasnik Kopete +Comment[sv]=Kopete meddelandeklient +Comment[ta]=உடனடி தூதர௠+Comment[tg]=Пайёмбари Kopete +Comment[uk]=Програма обміну повідомленнÑми Kopete +Comment[uz]=Kopete xabar almashish vositasi +Comment[uz@cyrillic]=Kopete хабар алмашиш воÑитаÑи +Comment[ven]=Murumiwa wa Kopete +Comment[zh_CN]=Kopete 信使 + +[kopete_contact_incoming] +Name=Incoming +Name[ar]=وارد +Name[be]=Уваходнае +Name[bg]=ПриÑтигна ново Ñъобщение +Name[bn]=অনà§à¦¤à¦°à§à¦®à§à¦–ী +Name[br]=Nevez +Name[bs]=Ulazni +Name[ca]=Entrant +Name[cs]=Příchozí +Name[cy]=Cyrraedd +Name[da]=Indkommende +Name[de]=Eingang +Name[el]=ΕισεÏχόμενο +Name[eo]=Alvenantaj +Name[es]=Entrante +Name[et]=Sisenev +Name[eu]=Sarrerako +Name[fa]=واردشونده +Name[fi]=Saapuva +Name[fr]=Entrant +Name[ga]=Ag Teacht +Name[gl]=Entrante +Name[he]=נכנס +Name[hi]=आवक +Name[hr]=Dolazni +Name[hu]=BejövÅ‘ +Name[is]=à leið inn +Name[it]=In entrata +Name[ja]=å—ä¿¡ +Name[ka]=შემáƒáƒ›áƒáƒ•áƒáƒšáƒ˜ +Name[kk]=ÐšÑ–Ñ€Ñ–Ñ +Name[km]=ចូល +Name[lt]=Gaunami +Name[mk]=Дојдовни +Name[nb]=Innkommende +Name[nds]=Rinkamen +Name[ne]=आगमन +Name[nl]=Inkomend +Name[nn]=Innkomande +Name[pa]=ਆ ਰਹੇ +Name[pl]=NadchodzÄ…ce +Name[pt]=Recebido +Name[pt_BR]=Entrada +Name[ro]=Primire +Name[ru]=ВходÑщее +Name[se]=Boahtti +Name[sk]=Prichádzajúca +Name[sl]=PrihajajoÄe +Name[sr]=Долазећи +Name[sr@Latn]=Dolazeći +Name[sv]=Inkommande +Name[ta]=உளà¯à®µà®°à®µà¯ +Name[tg]=Воридшаванда +Name[tr]=Gelen +Name[uk]=Вхідна +Name[ven]=Zwine zwa khoutou da +Name[wa]=En intrêye +Name[xh]=Engenayo +Name[zh_CN]=收到 +Name[zh_HK]=å…§é€ +Name[zh_TW]=æ–°è¨Šæ¯ +Comment=An incoming message has been received +Comment[be]=Ðтрыманае ўваходнае паведамленне +Comment[bg]=ПриÑтигна ново Ñъобщение +Comment[bn]=à¦à¦•à¦Ÿà¦¿ অনà§à¦¤à¦°à§à¦®à§à¦–ী বারà§à¦¤à¦¾ গà§à¦°à¦¹à¦£ করা হয়েছে +Comment[bs]=Primljena je poruka +Comment[ca]=S'ha rebut un missatge entrant +Comment[cs]=PÅ™iÅ¡la zpráva +Comment[da]=En indkommende besked er blevet modtaget +Comment[de]=Eingehende Nachricht +Comment[el]=Ένα εισεÏχόμενο μήνυμα παÏαλήφθηκε +Comment[eo]=Alvenanta mesaÄo estis ricevita +Comment[es]=Se ha recibido un mensaje entrante +Comment[et]=Saabus sõnum +Comment[eu]=Sarrerako mezu bat jaso da +Comment[fa]=یک پیام واردشده دریاÙت شده است +Comment[fi]=Viesti on saapunut +Comment[fr]=Un message entrant a été reçu +Comment[gl]=Recibiuse unha nova mensaxe +Comment[he]=התקבל מסר נכנס +Comment[hr]=Primljena je dolazna poruka +Comment[hu]=BejövÅ‘ üzenet érkezett +Comment[is]=Skeyti móttekið +Comment[it]=Ricevuto un nuovo messaggio +Comment[ja]=å—信メッセージãŒå±Šãã¾ã—㟠+Comment[ka]=შემáƒáƒ›áƒáƒ•áƒáƒšáƒ˜ შეტყáƒáƒ‘ინებრმიღებულ იქნრ+Comment[kk]=Хабарлама келді +Comment[km]=បាន​ទទួល​សំបុážáŸ’រ​មួយ​ចូល +Comment[lt]=Gauta nauja žinutÄ— +Comment[mk]=Примена е дојдовна порака +Comment[nb]=Ny melding ankommet +Comment[nds]=En Naricht keem rin +Comment[ne]=à¤à¤‰à¤Ÿà¤¾ आगमन सनà¥à¤¦à¥‡à¤¶ पà¥à¤°à¤¾à¤ªà¥à¤¤ भयो +Comment[nl]=Inkomend bericht binnengekomen +Comment[nn]=Ny melding er komen +Comment[pl]=NadeszÅ‚a nowa wiadomość +Comment[pt]=Foi recebida uma mensagem +Comment[pt_BR]=chegou nova mensagem +Comment[ro]=A fost primit un mesaj +Comment[ru]=Получено новое Ñообщение +Comment[se]=Boahtti dieđáhus vuostáiváldon +Comment[sk]=Príchod správy +Comment[sl]=Prejeto je bilo prihajajoÄe sporoÄilo +Comment[sr]=Примљена је долазећа порука +Comment[sr@Latn]=Primljena je dolazeća poruka +Comment[sv]=Ett inkommande meddelande har anlänt +Comment[ta]=உளà¯à®µà®°à¯à®®à¯ தகவல௠பெறபà¯à®ªà®Ÿà¯à®Ÿà®¤à¯ +Comment[tg]=Пайёми воридшаванда қабул гардид +Comment[tr]=Bir gelen mesaj alındı +Comment[uk]=Отримано нове Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ +Comment[uz]=Xabar qabul qilindi +Comment[uz@cyrillic]=Хабар қабул қилинди +Comment[zh_CN]=æ”¶åˆ°äº†æ¶ˆæ¯ +Comment[zh_HK]=æ”¶åˆ°æ–°è¨Šæ¯ +Comment[zh_TW]=å·²æ”¶åˆ°æ–°è¨Šæ¯ +default_sound=Kopete_Received.ogg +default_presentation=64 + +[kopete_outgoing] +Name=Outgoing +Name[ar]=خارج +Name[be]=Зыходнае +Name[bg]=Изпратено е Ñъобщение +Name[bn]=বহিরà§à¦®à§à¦–ী +Name[bs]=Izlazni +Name[ca]=Sortint +Name[cs]=Odchozí +Name[cy]=Allfynd +Name[da]=UdgÃ¥ende +Name[de]=Ausgang +Name[el]=ΕξεÏχόμενο +Name[eo]=Elirantaj +Name[es]=Saliente +Name[et]=Väljuv +Name[eu]=Irteerakoa +Name[fa]=خروج +Name[fi]=Lähtevä +Name[fr]=Sortant +Name[ga]=Ag Imeacht +Name[gl]=Saliente +Name[he]=×™×•×¦× +Name[hi]=जावक +Name[hr]=Izlazno +Name[hu]=KimenÅ‘ +Name[is]=à leið út +Name[it]=In uscita +Name[ja]=é€ä¿¡ +Name[ka]=გáƒáƒ›áƒáƒ•áƒáƒšáƒ˜ +Name[kk]=Ð¨Ñ‹Ò“Ñ‹Ñ +Name[km]=áž…áŸáž‰ +Name[lt]=IÅ¡siunÄiamos +Name[mk]=Појдовни +Name[nb]=UtgÃ¥ende +Name[nds]=Rutgahn +Name[ne]=निरà¥à¤—मन +Name[nl]=Uitgaand +Name[nn]=UtgÃ¥ande +Name[pa]=ਜਾ ਰਹੇ +Name[pl]=WychodzÄ…ce +Name[pt]=A Enviar +Name[pt_BR]=Saída +Name[ro]=Trimitere +Name[ru]=ИÑходÑщее +Name[se]=Manni +Name[sk]=Odchádzajúca +Name[sl]=OdhajajoÄe +Name[sr]=Одлазећи +Name[sr@Latn]=Odlazeći +Name[sv]=UtgÃ¥ende +Name[ta]=வெளிசெலà¯à®²à¯à®¤à®²à¯ +Name[tg]=Хориҷшаванда +Name[tr]=Giden +Name[uk]=Вихідна +Name[ven]=Zwine zwa khou tou bva +Name[wa]=E rexhowe +Name[xh]=Ephumayo +Name[zh_CN]=å‘出 +Name[zh_HK]=外發 +Name[zh_TW]=é€å‡ºè¨Šæ¯ +Comment=An outgoing message has been sent +Comment[be]=Ðтрыманае зыходнае паведамленне +Comment[bg]=Изпратено е Ñъобщение +Comment[bn]=à¦à¦•à¦Ÿà¦¿ বহিরà§à¦®à§à¦–ী বারà§à¦¤à¦¾ পাঠান হয়েছে +Comment[bs]=Poslana je poruka +Comment[ca]=S'ha enviat un missatge sortint +Comment[cs]=Byla odeslána zpráva +Comment[da]=En udgÃ¥ende besked er blevet sendt +Comment[de]=Ausgehende Nachricht wurde versendet +Comment[el]=Ένα εξεÏχόμενο μήνυμα στάλθηκε +Comment[eo]=Eliranta mesaÄo estis sendita +Comment[es]=Se ha enviado un mensaje entrante +Comment[et]=Saadeti sõnum +Comment[eu]=Irteerako mezu bat bidali da +Comment[fa]=یک پیام خروجی ارسال شده است +Comment[fi]=Viesti on lähetetty +Comment[fr]=Un message sortant a été envoyé +Comment[gl]=Enviouse unha nova mensaxe +Comment[he]=מסר ×™×•×¦× × ×©×œ×— +Comment[hr]=Primljena je odlazna poruka +Comment[hu]=KimenÅ‘ üzenet továbbítódott +Comment[is]=Skeyti sent +Comment[it]=Inviato un messaggio +Comment[ja]=é€ä¿¡ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’é€ã‚Šã¾ã—㟠+Comment[ka]=გáƒáƒ›áƒáƒ•áƒáƒšáƒ˜ შეტყáƒáƒ‘ინებრმიღებულ იქნრ+Comment[kk]=Хабарлама жіберілді +Comment[km]=បាន​ផ្ញើ​សំបុážáŸ’រ​មួយ​ចáŸáž‰ +Comment[lt]=ŽinutÄ— iÅ¡siųsta +Comment[mk]=ИÑпратена е појдовна порака +Comment[nb]=UtgÃ¥ende melding er sendt +Comment[nds]=En Naricht wöör afschickt +Comment[ne]=à¤à¤‰à¤Ÿà¤¾ निरà¥à¤—ामन सनà¥à¤¦à¥‡à¤¶ पठाइयो +Comment[nl]=Uitgaand bericht is verzonden +Comment[nn]=UtgÃ¥ande melding er send +Comment[pl]=Wiadomość zostaÅ‚a wysÅ‚ana +Comment[pt]=Foi enviada uma mensagem +Comment[pt_BR]=mensagem enviada +Comment[ro]=A fost trimis un mesaj +Comment[ru]=ИÑходÑщее Ñообщение отправлено +Comment[se]=Manni dieđáhus sáddejuvvon +Comment[sk]=Odoslanie správy +Comment[sl]=Poslano je bilo odhajajoÄe poroÄilo +Comment[sr]=Одлазећа порука је поÑлата +Comment[sr@Latn]=Odlazeća poruka je poslata +Comment[sv]=Ett utgÃ¥ende meddelande har skickats +Comment[ta]=வெளிசà¯à®šà¯†à®²à¯à®² வேணà¯à®Ÿà®¿à®¯ தகவல௠அனà¯à®ªà¯à®ªà®Ÿà¯à®Ÿà®¤à¯ +Comment[tg]=Пайёми хориҷшаванда фириÑтода шуд +Comment[tr]=Bir giden mesaj gönderildi +Comment[uk]=ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð±ÑƒÐ»Ð¾ відіÑлано +Comment[uz]=Xabar joÊ»natildi +Comment[uz@cyrillic]=Хабар жўнатилди +Comment[zh_CN]=é€å‡ºäº†æ¶ˆæ¯ +Comment[zh_HK]=訊æ¯å·²ç™¼å‡º +Comment[zh_TW]=å·²é€å‡ºè¨Šæ¯ +default_sound=Kopete_Sent.ogg +default_presentation=0 + + +[kopete_contact_online] +Name=Online +Name[ar]=متصل +Name[be]=ЗлучÑнне з Ñеткай +Name[bg]=Включи Ñе приÑтел +Name[bn]=অনলাইন +Name[br]=Enlinenn +Name[ca]=Connectat +Name[cy]=Ar-lein +Name[el]=Σε σÏνδεση +Name[eo]=Konektata +Name[es]=Conectado +Name[et]=Võrgus +Name[fa]=برخط +Name[fi]=Yhteys auki +Name[fr]=Se connecter +Name[ga]=Ar Líne +Name[gl]=En liña +Name[he]=מקוון +Name[hi]=आनलाइन +Name[is]=Tengdur +Name[it]=In linea +Name[ja]=オンライン +Name[ka]=ხáƒáƒ–ზე +Name[kk]=Желіде +Name[km]=លើ​បណ្ážáž¶áž‰ +Name[lt]=PrisijungÄ™ +Name[mk]=Ðа линија +Name[nb]=Koble til +Name[nds]=Tokoppelt +Name[ne]=अनलाइन +Name[nl]=Online gaan +Name[nn]=Tilkopla +Name[pa]=ਆਨਲਾਇਨ +Name[pl]=DostÄ™pny +Name[pt]=Ligado +Name[pt_BR]=Ficar On-line +Name[ro]=Conectat +Name[ru]=Ð’ Ñети +Name[sk]=On-line +Name[sl]=Na zvezi +Name[sr]=Ðа вези +Name[sr@Latn]=Na vezi +Name[sv]=Uppkopplad +Name[ta]=இணையதà¯à®¤à®¿à®²à¯ +Name[tg]=Дар шабака +Name[tr]=Çevirimiçi +Name[uk]=Ð’ мережі +Name[uz]=Onlayn +Name[uz@cyrillic]=Онлайн +Name[wa]=So les fyis +Name[zh_CN]=上线 +Name[zh_HK]=上線 +Name[zh_TW]=上線 +Comment=A contact has come online +Comment[be]=Чалавек злучыўÑÑ Ð· Ñеткай +Comment[bg]=Включи Ñе приÑтел +Comment[bn]=যোগাযোগ তালিকাভà§à¦•à§à¦¤ à¦à¦•à¦œà¦¨ অনলাইন হয়েছে +Comment[bs]=Kontakt je doÅ¡ao online +Comment[ca]=Un contacte s'ha connectat +Comment[cs]=Osoba je online +Comment[da]=En kontakt er gÃ¥et pÃ¥ nettet +Comment[de]=Ein Benutzer wechselt auf Online +Comment[el]=Μια επαφή μόλις συνδέθηκε +Comment[es]=Se ha conectado un contacto +Comment[et]=Kontakt tuli võrku +Comment[eu]=Kontaktu bat on line jarri da +Comment[fa]=یک تماس برخط شد +Comment[fi]=Yhteyshenkilö on saapunut verkkoon +Comment[fr]=Un contact s'est connecté +Comment[gl]=Acaba de conectarse un contact +Comment[he]=×יש קשר התחבר +Comment[hu]=Egy partner online állapotúvá vált +Comment[is]=Notandi tengist +Comment[it]=Un utente è tornato in linea +Comment[ja]=コンタクトãŒã‚ªãƒ³ãƒ©ã‚¤ãƒ³ã«ãªã‚Šã¾ã—㟠+Comment[ka]=მეგáƒáƒ‘áƒáƒ áƒ˜ ხáƒáƒ–ზერ+Comment[kk]=ҚатынаÑушы желіге кірді +Comment[km]=ទំនាក់​ទំនង​មួយបាន​ក្លាយ​ទៅ​ជា "លើ​បណ្ដាញ" +Comment[lt]=PrisijungÄ— kontaktinis asmuo +Comment[mk]=Контактот е на линија +Comment[nb]=En bruker kobler til pÃ¥ nettet +Comment[nds]=En Kontakt hett sik tokoppelt +Comment[ne]=à¤à¤‰à¤Ÿà¤¾ समà¥à¤ªà¤°à¥à¤• अनलाइन आयो +Comment[nl]=Gebruiker gaat online +Comment[nn]=Kontakt koplar seg til nettet +Comment[pl]=Użytkownik staÅ‚ siÄ™ dostÄ™pny +Comment[pt]=Um contacto ligou-se +Comment[pt_BR]=O usuário ficou on-line +Comment[ru]=Пользователь вошёл в Ñеть +Comment[se]=Oktavuohta lea «online» +Comment[sk]=Užívateľ je on-line +Comment[sl]=Uporabnik je vzpostavil povezavo +Comment[sr]=КориÑник Ñе повезао +Comment[sr@Latn]=Korisnik se povezao +Comment[sv]=En användare har kopplat upp +Comment[ta]=பயனர௠உரையாட வநà¯à®¤à¯à®³à¯à®³à®¾à®°à¯ +Comment[tg]=Корванде ба шабака ворид шуд +Comment[tr]=Bir baÄŸlantı baÄŸlı durumda +Comment[uk]=КориÑтувач увійшов у мережу +Comment[zh_CN]=è”系人上线 +Comment[zh_HK]=有個è¯çµ¡äººå·²ä¸Šç·š +Comment[zh_TW]=è¯çµ¡äººå·²ä¸Šç·š +default_sound=Kopete_User_is_Online.ogg +default_presentation=16 + +[kopete_contact_offline] +Name=Offline +Name[ar]=غير متصل +Name[be]=Па-за Ñеткай +Name[bg]=Изключи Ñе приÑтел +Name[bn]=অফলাইন +Name[br]=N'eo ket enlinenn +Name[ca]=Desconnectat +Name[cy]=All-lein +Name[da]=GÃ¥ offline +Name[el]=ΧωÏίς σÏνδεση +Name[eo]=Nekonektata +Name[es]=Desconectar +Name[et]=Pole võrgus +Name[eu]=Deskonektatuta +Name[fa]=برون‌خط +Name[fi]=Yhteys katkaistu +Name[fr]=Se déconnecter +Name[ga]=As Líne +Name[gl]=Desconectar +Name[he]=מנותק +Name[hi]=ऑफ़लाइन +Name[hr]=Neumrežen +Name[is]=Aftengdur +Name[it]=Non in linea +Name[ja]=オフライン +Name[ka]=ხáƒáƒ–იდáƒáƒœ გáƒáƒ¡áƒ£áƒšáƒ˜ +Name[kk]=Желіден Ñ‚Ñ‹Ñ +Name[km]=ក្រៅ​បណ្ដាញ +Name[lt]=AtsijungÄ™ +Name[mk]=Ðе на линија +Name[nb]=Koble fra +Name[nds]=Afkoppelt +Name[ne]=अफलाइन +Name[nl]=Offline gaan +Name[nn]=FrÃ¥kopla +Name[pa]=ਆਫਲਾਇਨ +Name[pl]=NiedostÄ™pny +Name[pt]=Desligado +Name[pt_BR]=Desconectado +Name[ro]=Deconectat +Name[ru]=Ðвтономный режим +Name[sk]=Off-line +Name[sl]=Brez zveze +Name[sr]=Ðије на вези +Name[sr@Latn]=Nije na vezi +Name[sv]=Nerkopplad +Name[ta]=இணையதà¯à®¤à®¿à®²à¯ இலà¯à®²à¯ˆ +Name[tg]=УÑули Худмухтор +Name[tr]=BaÄŸlı deÄŸil +Name[uk]=Вимкнений +Name[uz]=Oflayn +Name[uz@cyrillic]=Офлайн +Name[zh_CN]=离线 +Name[zh_HK]=離線 +Name[zh_TW]=離線 +Comment=A contact has gone offline +Comment[be]=Чалавек адлучыўÑÑ Ð°Ð´ Ñеткі +Comment[bg]=Изключи Ñе приÑтел +Comment[bn]=যোগাযোগ তালিকাভà§à¦•à§à¦¤ à¦à¦•à¦œà¦¨ অফলাইন হয়ে গিয়েছে +Comment[bs]=Kontakt je otiÅ¡ao offline +Comment[ca]=Un contacte s'ha desconnectat +Comment[cs]=Osoba je offline +Comment[da]=En kontakt er gÃ¥et af nettet +Comment[de]=Ein Benutzer wechselt auf Offline +Comment[el]=Μια επαφή μόλις αποσυνδέθηκε +Comment[es]=Se ha desconectado un contacto +Comment[et]=Kontakt läks võrgust ära +Comment[eu]=Kontaktu bat deskonektatu da +Comment[fa]=یک تماس برون‌خط شد +Comment[fi]=Yhteyshenkilö on lähtenyt verkosta +Comment[fr]=Un contact s'est déconnecté +Comment[gl]=Acaba de desconctarse un contacto +Comment[he]=משתמש התנתק +Comment[hu]=Egy partner offline állapotúvá vált +Comment[is]=Notandi aftengist +Comment[it]=Un utente è andato non in linea +Comment[ja]=コンタクトãŒã‚ªãƒ•ãƒ©ã‚¤ãƒ³ã«ãªã‚Šã¾ã—㟠+Comment[ka]=მეგáƒáƒ‘áƒáƒ áƒ˜ ხáƒáƒ–იდáƒáƒœ გáƒáƒ•áƒ˜áƒ“რ+Comment[kk]=ҚатынаÑушы желіден шықты +Comment[km]=ទំនាក់​ទំនង​មួយបាន​ក្លាយ​ទៅ​ជា "ក្រៅ​បណ្ដាញ" +Comment[lt]=Kontaktinis asmuo atsijungÄ— +Comment[mk]=Контактот Ñе иÑклучи +Comment[nb]=En bruker kobler fra nettet +Comment[nds]=En Kontakt hett sik afkoppelt +Comment[ne]=समà¥à¤ªà¤°à¥à¤• अफलाइन भयो +Comment[nl]=Gebruiker gaat offline +Comment[nn]=Kontakt koplar seg frÃ¥ nettet +Comment[pl]=Użytkownik staÅ‚ siÄ™ niedostÄ™pny +Comment[pt]=Um contacto desligou-se +Comment[pt_BR]=O usuário se desconectou +Comment[ru]=Пользователь вышел из Ñети +Comment[se]=Oktavuohta lea «offline» +Comment[sk]=Užívateľ je off-line +Comment[sl]=Uporabnik je prekinil povezavo +Comment[sr]=КориÑник је отишао Ñа везе +Comment[sr@Latn]=Korisnik je otiÅ¡ao sa veze +Comment[sv]=En användare har kopplat ner +Comment[ta]=பயனர௠வெளியேறிவிடà¯à®Ÿà®¾à®°à¯ +Comment[tg]=Корванде аз шабака хориҷ шуд +Comment[tr]=Bir baÄŸlantı çevrim dışı +Comment[uk]=КориÑтувач вийшов з мережі +Comment[zh_CN]=è”系人离线 +Comment[zh_HK]=有個è¯çµ¡äººå·²é›¢ç·š +Comment[zh_TW]=è¯çµ¡äººå·²é›¢ç·š +default_sound=Kopete_Event.ogg +default_presentation=0 + + +[kopete_contact_status_change] +Name=Status Change +Name[ar]=تغير حالة +Name[be]=Змена Ñтану +Name[bg]=Променено ÑÑŠÑтоÑние на приÑтел +Name[bn]=অবসà§à¦¥à¦¾ পরিবরà§à¦¤à¦¨ +Name[bs]=Izmjena statusa +Name[ca]=Canvia estatus +Name[cs]=ZmÄ›na stavu +Name[cy]=Newid Cyflwr +Name[da]=Statusændring +Name[de]=Statuswechsel +Name[el]=Αλλαγή κατάστασης +Name[eo]=Status-ÅanÄo +Name[es]=Cambiar estado +Name[et]=Staatuse muutus +Name[eu]=Egoera aldaketa +Name[fa]=تغییر وضعیت +Name[fi]=Tilan muuttuminen +Name[fr]=Changement d'état +Name[ga]=Athrú Stádais +Name[gl]=Cambio de estado +Name[he]=שינוי מצב +Name[hi]=सà¥à¤¥à¤¿à¤¤à¤¿ परिवरà¥à¤¤à¤¨ +Name[hr]=Promjena statusa +Name[hu]=Ãllapotváltozás +Name[is]=Breyta stöðu +Name[it]=Cambio di stato +Name[ja]=状態ã®å¤‰åŒ– +Name[ka]=მდგáƒáƒ›áƒáƒ áƒ”áƒáƒ‘ის შეცვლრ+Name[kk]=Күй-жайы өзгерді +Name[km]=ការផ្លាស់ប្ដូរ​ស្ážáž¶áž“ភាព +Name[lt]=BÅ«senos pakitimas +Name[mk]=Промена на ÑтатуÑот +Name[nb]=Endre status +Name[nds]=Statusännern +Name[ne]=वसà¥à¤¤à¥à¤¸à¥à¤¥à¤¿à¤¤à¤¿ परिवरà¥à¤¤à¤¨ +Name[nl]=Statusverandering +Name[nn]=Statusendring +Name[pa]=ਹਾਲਤ ਬਦਲੀ +Name[pl]=Zmiana statusu +Name[pt]=Mudança de Estado +Name[pt_BR]=Mudança de Status +Name[ro]=Modificare stare +Name[ru]=Изменение ÑтатуÑа +Name[se]=Stáhtusrievdadus +Name[sk]=Zmena stavu +Name[sl]=Sprememba stanja +Name[sr]=Промена ÑтатуÑа +Name[sr@Latn]=Promena statusa +Name[sv]=Statusändring +Name[ta]=நிலை மாறà¯à®±à®®à¯ +Name[tg]=Ивази Ҳолат +Name[tr]=Durum DeÄŸiÅŸimi +Name[uk]=Зміна Ñтану +Name[uz]=Holati oÊ»zgardi +Name[uz@cyrillic]=Ҳолати ўзгарди +Name[wa]=Candjmint di statut +Name[zh_CN]=状æ€æ›´æ”¹ +Name[zh_HK]=狀態改變 +Name[zh_TW]=狀態改變 +Comment=A contact's online status has changed +Comment[be]=Сеткавы Ñтан чалавека змÑніўÑÑ +Comment[bg]=Променено ÑÑŠÑтоÑние на приÑтел +Comment[bn]=যোগাযোগ তালিকাভà§à¦•à§à¦¤ à¦à¦•à¦œà¦¨à§‡à¦° অনলাইন অবসà§à¦¥à¦¾à¦° পরিবরà§à¦¤à¦¨ হয়েছে +Comment[bs]=Kontakt je promijenio svoj online status +Comment[ca]=L'estat d'un contacte ha canviat +Comment[cs]=Osoba zmÄ›nila online stav +Comment[da]=En kontakts status pÃ¥ nettet er ændret +Comment[de]=Ein Benutzer wechselt seinen Status. +Comment[el]=Μια επαφή άλλαξε αυτή τη στιγμή κατάσταση +Comment[es]=El estado de conexión de un contacto ha cambiado +Comment[et]=Kontakt muutis oma võrgusoleku staatust +Comment[eu]=Kontaktu baten on line egoera aldatu da +Comment[fa]=وضعیت تماس برخط تغییر یاÙت +Comment[fi]=Yhteyshenkilön online-tila on muuttunut +Comment[fr]=Un contact a changé son état de connexion +Comment[gl]=O estado en liña dun contacto acaba de mudar +Comment[he]=×יש הקשר שינה ×ת מצב ההתחברות שלו +Comment[hu]=Egy partner állapota megváltozott +Comment[is]=Notandi breytir um stöðu +Comment[it]=Un utente ha modificato il suo stato in linea +Comment[ja]=コンタクトã®æŽ¥ç¶šçŠ¶æ…‹ãŒå¤‰ã‚ã‚Šã¾ã—㟠+Comment[ka]=მეგáƒáƒ‘რის ხáƒáƒ–ზე ყáƒáƒ¤áƒœáƒ˜áƒ¡ სტáƒáƒ¢áƒ£áƒ¡áƒ˜ შეიცვáƒáƒšáƒ +Comment[kk]=ҚатынаÑушының желідегі күйі өзгерді +Comment[km]=បាន​ផ្លាស់ប្ដូរ​ស្ážáž¶áž“ភាព​លើ​បណ្ដាញ​របស់​ទំនាក់​ទំនង​មួយ +Comment[lt]=Kontaktinio asmens bÅ«sena pakito +Comment[mk]=СтатуÑот на контактот на линија Ñе измени +Comment[nb]=En bruker endret tilkobling til nettet +Comment[nds]=De Tokoppel-Status vun en Kontakt hett sik ännert +Comment[ne]=समà¥à¤ªà¤°à¥à¤•à¤•à¥‹ अनलाइन वसà¥à¤¤à¥à¤¸à¥à¤¥à¤¿à¤¤à¤¿ परिवरà¥à¤¤à¤¨ भयो +Comment[nl]=Gebruiker wijzigde online status +Comment[nn]=Kontakt endrar status +Comment[pl]=Użytkownik zmieniÅ‚ swój stan +Comment[pt]=O contacto mudou o seu estado de ligação. +Comment[pt_BR]=O usuário mudou o seu status de on-line +Comment[ru]=Пользователь изменил Ñвоё ÑоÑтоÑние в Ñети +Comment[se]=Oktavuohta rievdada stáhtusa +Comment[sk]=Zmena stavu pripojenia užívateľa +Comment[sl]=Uporabnik je spremenil svoje stanje +Comment[sr]=КориÑник је променио Ñвој ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° вези +Comment[sr@Latn]=Korisnik je promenio svoj status na vezi +Comment[sv]=Uppkopplingsstatus för en användare har ändrats +Comment[ta]= இணைய உரையாடலில௠பயனரின௠நிலை மாறà¯à®±à®ªà¯à®ªà®Ÿà¯à®Ÿà®¤à¯ +Comment[tg]=Ҳолати алоқаҳои пайваÑта тағир дода шуданд +Comment[tr]=BaÄŸlantının durumu baÄŸlı olarak deÄŸiÅŸti +Comment[uk]=КориÑтувач змінив Ñвій Ñтан в мережі +Comment[zh_CN]=è”ç³»äººæ›´æ”¹äº†ä»–çš„åœ¨çº¿çŠ¶æ€ +Comment[zh_HK]=有個è¯çµ¡äººçš„上線狀態改變了 +Comment[zh_TW]=è¯çµ¡äººçš„上線狀態已改變 +default_sound=Kopete_Event.ogg +default_presentation=0 + +[kopete_contact_highlight] +Name=Highlight +Name[ar]=تمييز +Name[bg]=ОткроÑване +Name[bn]=গà§à¦°à§à¦¤à§à¦¬à¦ªà§‚রà§à¦£ +Name[br]=Splannadur +Name[bs]=Isticanje +Name[ca]=Ressaltat +Name[cs]=ZvýraznÄ›ní +Name[cy]=Amlygu +Name[da]=Fremhæv +Name[de]=Hervorhebung +Name[el]=Τονισμός +Name[eo]=Lumaĵo +Name[es]=Resaltar +Name[et]=Esiletõstmine +Name[eu]=Nabarmendu +Name[fa]=مشخص +Name[fi]=Korostus +Name[fr]=Surlignement +Name[ga]=Aibhsiú +Name[gl]=Resaltar +Name[he]=מודגש +Name[hi]=उभारें +Name[hr]=Isticanje +Name[hu]=Kiemelés +Name[is]=Merkja +Name[it]=Evidenziazione +Name[ja]=強調 +Name[ka]=მáƒáƒ áƒ™áƒ˜áƒ áƒ”ბული +Name[kk]=Ерекше +Name[km]=សំážáž¶áž“់ +Name[lt]=ParyÅ¡kinti +Name[mk]=ОÑветлување +Name[nb]=Marker +Name[nds]=Rutheven +Name[ne]=हाइलाइट +Name[nl]=Aanwijzen +Name[nn]=Marker +Name[pa]=ਉਘਾੜਨ +Name[pl]=PodÅ›wietlenie +Name[pt]=Realce +Name[pt_BR]=Destaque +Name[ro]=EvidenÅ£iat +Name[ru]=Выделение +Name[se]=Merke +Name[sk]=ZvýrazniÅ¥ +Name[sl]=Poudarjeno sporoÄilo +Name[sr]=ИÑтицање +Name[sr@Latn]=Isticanje +Name[sv]=Markera +Name[ta]=à®®à¯à®©à¯ˆà®ªà¯à®ªà¯à®±à¯à®¤à¯à®¤à®²à¯ +Name[tg]=Равшаннамоӣ +Name[tr]=Vurgu +Name[uk]=ПідÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ +Name[wa]=E sorbiyance +Name[zh_CN]=çªå‡ºæ˜¾ç¤º +Name[zh_HK]=加強顯示 +Name[zh_TW]=高亮度 +Comment=A highlighted message has been received +Comment[bg]=ПриÑтигна Ñпециално Ñъобщение +Comment[bn]=à¦à¦•à¦Ÿà¦¿ গà§à¦°à§à¦¤à§à¦¬à¦ªà§‚রà§à¦£ বারà§à¦¤à¦¾ গà§à¦°à¦¹à¦£ করা হয়েছে +Comment[bs]=Primljena je naglaÅ¡ena poruka +Comment[ca]=S'ha rebut un missatge ressaltat +Comment[cs]=Byla obdržena zvýraznÄ›ná zpráva +Comment[da]=En fremhævet besked er blevet modtaget +Comment[de]=Eine hervorgehobene Nachricht ist eingegangen +Comment[el]=Ένα τονισμένο μήνυμα μόλις παÏαλήφθηκε +Comment[es]=Se ha recibido un mensaje resaltado +Comment[et]=Saabus esiletõstetud sõnum +Comment[eu]=Nabarmendutako mezu bat jaso da +Comment[fa]=یک پیام مشخص‌شده دریاÙت شده است +Comment[fi]=Korostettu viesti on saapunut +Comment[fr]=Un message surligné a été reçu +Comment[gl]=Recibiuse unha mensaxe subliñada +Comment[he]=התקבל מסר מודגש +Comment[hr]=Osvijetljena poruka je primljena +Comment[hu]=Kiemelt üzenet érkezett +Comment[is]=Merkt skeyti móttekið +Comment[it]=Un messaggio evidenziato è stato ricevuto +Comment[ja]=強調ã•ã‚ŒãŸãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ãŒå±Šãã¾ã—㟠+Comment[ka]=მáƒáƒ áƒ™áƒ˜áƒ áƒ”ბული შეტყáƒáƒ‘ინებრმიღებულ იქნრ+Comment[kk]=Ерекше хабарлама келді +Comment[km]=បាន​ទទួល​សារ​សំážáž¶áž“់​មួយ +Comment[lt]=Gauta paryÅ¡kinta žinutÄ— +Comment[mk]=Примена е оÑветлена порака +Comment[nb]=En fremhevet melding er mottatt +Comment[nds]=En rutheevt Naricht keem rin +Comment[ne]=हाइलाइट गरिà¤à¤•à¥‹ सनà¥à¤¦à¥‡à¤¶ पà¥à¤°à¤¾à¤ªà¥à¤¤ भयो +Comment[nl]=Een geaccentueerd bericht is binnengekomen +Comment[nn]=Ei markert melding er motteken +Comment[pl]=PodÅ›wietlona wiadomość zostaÅ‚a odebrana +Comment[pt]=Foi recebida uma mensagem realçada +Comment[pt_BR]=Uma mensagem de destaque foi recebida +Comment[ru]=Получено выделенное Ñообщение +Comment[se]=Merkejuvvon dieđáhus lea vuostáiváldon +Comment[sk]=Prijatá zvýraznená správa +Comment[sl]=Prejeto je bilo poudarjeno sporoÄilo +Comment[sr]=Примљена је иÑтакнута порука +Comment[sr@Latn]=Primljena je istaknuta poruka +Comment[sv]=Ett markerat meddelande har tagits emot +Comment[ta]=தனிபà¯à®ªà®Ÿà¯à®¤à¯à®¤à®ªà¯à®ªà®Ÿà¯à®Ÿ செயà¯à®¤à®¿ பெறபà¯à®ªà®Ÿà¯à®Ÿà®¤à¯ +Comment[tg]=Пайёмҳои равшаншаванда қабул гардиданд +Comment[tr]=Vurgulanmış bir mesaj alındı +Comment[uk]=Отримано виділене Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ +Comment[zh_CN]=收到了çªå‡ºæ˜¾ç¤ºçš„æ¶ˆæ¯ +Comment[zh_HK]=æ”¶åˆ°ä¸€å€‹åŠ å¼·é¡¯ç¤ºçš„è¨Šæ¯ +Comment[zh_TW]=æŽ¥æ”¶åˆ°ä¸€å€‹é«˜äº®åº¦è¨Šæ¯ +default_sound=Kopete_Received.ogg +default_presentation=65 + + +[kopete_contact_lowpriority] +Name=Low priority messages +Name[be]=Паведамленне нізкай важнаÑці +Name[bg]=ПриÑтигна Ñъобщение Ñ Ð½Ð¸Ñък приоритет +Name[bn]=কম গà§à¦°à§à¦¤à§à¦¬à¦ªà§‚রà§à¦£ বারà§à¦¤à¦¾ +Name[bs]=Poruke niskog prioriteta +Name[ca]=Missatges de baixa prioritat +Name[cs]=Zprávy s nízkou prioritou +Name[da]=Breve med lav prioritet +Name[de]=Nachrichten mit niedriger Priorität +Name[el]=ΜηνÏματα χαμηλής Ï€ÏοτεÏαιότητας +Name[es]=Mensajes de prioridad baja +Name[et]=Madala prioriteediga sõnumid +Name[eu]=Lehentasun gutxiko mezuak +Name[fa]=پیامهای Ú©Ù… اولویت +Name[fi]=Matalan prioriteetin viestit +Name[fr]=Messages de basse priorité +Name[gl]=Mensaxes con baixa prioridade +Name[he]=הודעות בעדיפות נמוכה +Name[hu]=Alacsony prioritású üzenetek +Name[is]=Skeyti með lágum forgangi +Name[it]=Messaggi a bassa priorità +Name[ja]=優先度ã®ä½Žã„メッセージ +Name[ka]=დáƒáƒ‘áƒáƒšáƒ˜ პრიáƒáƒ áƒ˜áƒ¢áƒ”ტის შეტყáƒáƒ‘ინებრ+Name[kk]=Ðртықшылығы төмен хабарлама +Name[km]=សារ​អាទិភាព​ទាប +Name[lt]=Žemo prioriteto žinutÄ—s +Name[mk]=Пораки Ñо низок приоритет +Name[nb]=Meldinger med lav prioritet +Name[nds]=Narichten mit siete Prioriteet +Name[ne]=कम पà¥à¤°à¤¾à¤¥à¤®à¤¿à¤•à¤¤à¤¾ सनà¥à¤¦à¥‡à¤¶ +Name[nl]=Berichten met lage prioriteit +Name[nn]=Meldingar med lÃ¥g prioritet +Name[pa]=ਘੱਟ ਤਰਜੀਹ ਸà©à¨¨à©‡à¨¹à©‡ +Name[pl]=WiadomoÅ›ci o niskim priorytecie +Name[pt]=Mensagens de baixa prioridade +Name[pt_BR]=Mensagens com baixa prioridade +Name[ro]=Mesaje de prioritate mică +Name[ru]=Ð¡Ð¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ñ Ð½Ð¸Ð·ÐºÐ¸Ð¼ приоритетом +Name[se]=DieÄ‘ahusat mas lea unna ovdavuorra +Name[sk]=Správy s nízkou prioritou +Name[sl]=Manj pomembno sporoÄilo +Name[sr]=Поруке ниÑког приоритета +Name[sr@Latn]=Poruke niskog prioriteta +Name[sv]=LÃ¥gprioriterat meddelande +Name[ta]=கà¯à®±à¯ˆà®¨à¯à®¤ à®®à¯à®©à¯à®©à¯à®°à®¿à®®à¯ˆ உளà¯à®³ செயà¯à®¤à®¿à®•à®³à¯ +Name[tg]=Пайёмҳои Имтиёзашон Кам +Name[tr]=Düşük öncelikli mesajlar +Name[uk]=ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· низьким пріоритетом +Name[zh_CN]=ä½Žä¼˜å…ˆçº§æ¶ˆæ¯ +Name[zh_HK]=ä¸å¤ªé‡è¦çš„è¨Šæ¯ +Name[zh_TW]=ä½Žå„ªå…ˆæ¬Šè¨Šæ¯ +Comment=A message marked with a low priority has been received +Comment[be]=Ðтрыманае паведамленне нізкай важнаÑці +Comment[bg]=ПриÑтигна Ñъобщение Ñ Ð½Ð¸Ñък приоритет +Comment[bn]=à¦à¦•à¦Ÿà¦¿ কম গà§à¦°à§à¦¤à§à¦¬à¦ªà§‚রà§à¦£ বারà§à¦¤à¦¾ গà§à¦°à¦¹à¦£ করা হয়েছে +Comment[bs]=Primljena je poruka oznaÄena kao poruka niskog prioriteta +Comment[ca]=S'ha rebut un missatge marcat amb baixa prioritat +Comment[cs]=Byla obdržena zvýraznÄ›ná zpráva s nízkou prioritou +Comment[da]=En indkommende besked med lav prioritet er blevet modtaget +Comment[de]=Eine Nachricht mit niedriger Priorität ist eingegangen +Comment[el]=Ένα μήνυμα, σημειωμένο με χαμηλή Ï€ÏοτεÏαιότητα μόλις παÏαλήφθηκε +Comment[es]=Se ha recibido un mensaje marcado con poca prioridad +Comment[et]=Saabus madalaprioriteediline sõnum +Comment[eu]=Lehentasun gutxiko bezala markatutako mezu bat jaso da +Comment[fa]=یک پیام با نشان اولویت Ú©Ù… دریاÙت شده است +Comment[fi]=Matalaprioriteettinen viesti on saapunut +Comment[fr]=Un message marqué avec une basse priorité a été reçu +Comment[gl]=Unha mensaxe marcada coma de baixa prioridade foi recibida +Comment[he]=הודעה שמסומנת כבעלת עדיפות נמוכה התקבלה +Comment[hu]=Alacsony prioritású üzenet érkezett +Comment[is]=Skeyti með lágum forgangi móttekið +Comment[it]=Un messaggio segnato con bassa priorità è stato ricevuto +Comment[ja]=優先度ã®ä½Žã„メッセージãŒå±Šãã¾ã—㟠+Comment[ka]=დáƒáƒ‘áƒáƒšáƒ˜ პრიáƒáƒ áƒ˜áƒ¢áƒ”ტით შეტყáƒáƒ‘ინებრმიღებულ იქნრ+Comment[kk]=Ðртықшылығы төмен деп белгіленген хабарлама келді +Comment[km]=បាន​ទទួល​សារ ដែល​បាន​សម្គាល់​​ážáž¶â€‹áž‡áž¶â€‹áž¢áž¶áž‘ិភាព​ទាប +Comment[lt]=Gauta žemo prioriteto žinutÄ— +Comment[mk]=Примена е порака означена Ñо низок приоритет +Comment[nb]=En melding med lav prioritet er mottatt +Comment[nds]=En Naricht mit siete Prioriteet keem rin +Comment[ne]=कम पà¥à¤°à¤¾à¤¥à¤®à¤¿à¤•à¤¤à¤¾à¤•à¥‹ रूपमा चिनà¥à¤¹ लगाइà¤à¤•à¥‹ सनà¥à¤¦à¥‡à¤¶ पà¥à¤°à¤¾à¤ªà¥à¤¤ भयो +Comment[nl]=Er is een bericht met een lage prioriteit binnengekomen +Comment[nn]=Ei melding markert med lÃ¥g prioritet er motteken +Comment[pl]=Otrzymana zostaÅ‚a wiadomość o niskim priorytecie +Comment[pt]=Foi recebida uma mensagem marcada como de baixo prioridade +Comment[pt_BR]=Chegou uma mensagem marcada com baixa prioridade +Comment[ru]=Получены ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ñ Ð½Ð¸Ð·ÐºÐ¸Ð¼ приоритетом +Comment[se]=Dieđáhus mas lea unna ovdavuorru lea vuostáiváldon +Comment[sk]=Prijatá správa s nízkou prioritou +Comment[sl]=Prejeto je bilo manj pomembno sporoÄilo +Comment[sr]=Примљена је порука означена ниÑким приоритетом +Comment[sr@Latn]=Primljena je poruka oznaÄena niskim prioritetom +Comment[sv]=Ett inkommande meddelande har anlänt +Comment[ta]=எளிய à®®à¯à®©à¯à®©à¯à®°à®¿à®®à¯ˆ à®à®±à¯à®•à®ªà¯à®ªà®Ÿà¯à®Ÿ செயà¯à®¤à®¿ சà¯à®´à®±à¯à®šà®¿ +Comment[tg]=Пайёми бо имтиёзи паÑÑ‚ нишона карда шуда қабул гардид +Comment[tr]=ÖnceliÄŸi düşük olarak iÅŸaretlenmiÅŸ bir rmesaj alındı +Comment[uk]=Отримано Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· низьким пріоритетом +Comment[zh_CN]=æ”¶åˆ°äº†æ ‡ä¸ºä½Žä¼˜å…ˆçº§çš„æ¶ˆæ¯ +Comment[zh_HK]=收到一個標示為ä¸å¤ªé‡è¦çš„è¨Šæ¯ +Comment[zh_TW]=æŽ¥æ”¶åˆ°ä¸€å€‹æ¨™è¨˜ç‚ºä½Žå„ªå…ˆæ¬Šçš„è¨Šæ¯ +default_presentation=0 + +[kopete_authorization] +Name=Authorization +Name[bg]=ÐžÑ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ +Name[ca]=Autorització +Name[cs]=Autorizace +Name[da]=Godkendelse +Name[de]=Autorisierung +Name[el]=Πιστοποίηση +Name[eo]=Rajtigo +Name[es]=Autorización +Name[et]=Autoriseerimine +Name[fa]=اجازه +Name[fi]=Hyväksyminen +Name[fr]=Autorisation +Name[he]=הזדהות +Name[hu]=Felhasználóazonosítás +Name[is]=Auðkenning +Name[it]=Autorizzazione +Name[ja]=è¨±å¯ +Name[km]=សáŸáž…ក្ដី​អនុញ្ញាហ+Name[lt]=Autorizavimas +Name[nb]=Autorisering +Name[nds]=Identifikatschoon +Name[ne]=आधिकीकरण +Name[nl]=Autorisatie +Name[pa]=ਪਰਮਾਣਕਿਤਾ +Name[pl]=Autoryzacja +Name[pt]=Autorização +Name[pt_BR]=Autorização +Name[ru]=ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ +Name[sk]=Autorizácia +Name[sl]=Odobritev +Name[sr]=Ðуторизација +Name[sr@Latn]=Autorizacija +Name[sv]=Behörighetskontroll +Name[tr]=Yetkilendirme +Name[uk]=ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ñ–Ñ +Name[zh_CN]=èº«ä»½éªŒè¯ +Name[zh_TW]=èªè­‰ +Comment=An user has accepted/declined your authorization request +Comment[bg]=Потребител е приел/отхвърлил вашата заÑвка за Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ +Comment[ca]=Un usuari ha autoritzat/declinat la vostra petició d'autorització +Comment[cs]=Uživatel pÅ™ijmul/odmítnul váš požadavek na autorizaci +Comment[da]=En bruger har godkendt/afslÃ¥et din godkendelsesforespørgsel +Comment[de]=Ein Benutzer hat Ihre Autorisierung erlaubt/abgelehnt +Comment[el]=Ένας χÏήστης έχει πιστοποιήσει/αποÏÏίψει την αίτησή σας για πιστοποίηση +Comment[es]=Un usuario ha aceptado/declinado su petición de autorización +Comment[et]=Kasutaja autoriseeris/lükkas tagasi sinu autoriseerimissoovi +Comment[fa]=کاربری درخواست اجازۀ شما را پذیرÙته/نپذیرÙته است +Comment[fi]=Käyttäjä on hyväksynyt/hylännyt hyväksymispyyntösi +Comment[fr]=Un utilisateur a accepté ou refusé votre demande d'autorisation +Comment[he]=משתמש ×ישר/דחה ×ת בקשתך לזיהוי +Comment[hu]=Egy felhasználó elfogadta vagy elutasította az Ön felhasználóazonosítási kérését +Comment[is]=Notandi hefur heimilað eða hafnað beiðni þinni um auðkenningu auðkenningabeiðni þinni +Comment[it]=Un utente ha autorizzato/declinato la tua richiesta +Comment[ja]=ユーザã¯ã‚ãªãŸã®è¨±å¯è¦æ±‚を承諾/æ‹’å¦ã—ã¾ã—㟠+Comment[km]=អ្នក​ប្រើ​បានទទួល​យក/បដិសáŸáž’ នូវ​សំណើ​សáŸáž…ក្ដី​អនុញ្ញាážâ€‹ážšáž”ស់​អ្នក +Comment[lt]=Naudotojas priÄ—mÄ—/atmetÄ— JÅ«sų autorizacijos praÅ¡ymÄ… +Comment[nb]=En bruker har autorisert eller avvist din forespørsel om autorisering +Comment[nds]=En Bruker hett Dien Identifikatschoonanfraag tolaten/afwiest +Comment[ne]=à¤à¤‰à¤Ÿà¤¾ पà¥à¤°à¤¯à¥‹à¤—करà¥à¤¤à¤¾à¤²à¥‡ तपाईà¤à¤•à¥‹ आधिकीकरण अनà¥à¤°à¥‹à¤§ सà¥à¤µà¥€à¤•à¤¾à¤°/असà¥à¤µà¥€à¤•à¤¾à¤° गरेको छ +Comment[nl]=Een gebruiker heeft uw autorisatieverzoek geaccepteerd/afgewezen +Comment[pl]=Użytkownik zaakceptowaÅ‚/odrzuciÅ‚ Twoje żądanie autoryzacji +Comment[pt]=Um utilizador autorizou/rejeitou o seu pedido de autorização +Comment[pt_BR]=Um usuário aceitou/rejeitou seu pedido de autorização +Comment[ru]=Пользователь авторизовал Ð²Ð°Ñ Ð¸Ð»Ð¸ отклонил ваш Ð·Ð°Ð¿Ñ€Ð¾Ñ +Comment[sk]=Užívateľ prijal/odmietol vaÅ¡u požiadavku o autorizáciu +Comment[sl]=Uporabnik je spejel/zavrnil vaÅ¡ zahtevek za odobritev +Comment[sr]=КориÑник је прихватио/одбио ваш захтев за ауторизацију +Comment[sr@Latn]=Korisnik je prihvatio/odbio vaÅ¡ zahtev za autorizaciju +Comment[sv]=En användare har tillÃ¥tit eller nekat till din begäran om behörighetskontroll +Comment[tr]=Bir kullanıcı izin isteÄŸinizi onayladı/onaylamadı +Comment[uk]=КориÑтувач прийнÑв/відхилив ваш запит на авторизацію +Comment[zh_CN]=用户åŒæ„/æ‹’ç»äº†æ‚¨çš„身份验è¯è¯·æ±‚ +Comment[zh_TW]=已有一å使用者接å—/拒絕了您的èªè­‰è¦æ±‚ +default_presentation=16 + +[yahoo_mail] +Name=Yahoo Mail +Name[be]=Пошта Yahoo +Name[bg]=ПриÑтигна нова поща в Yahoo +Name[bn]=ইয়à§à¦¯à¦¾à¦¹à§ মেইল +Name[br]=Postel Yahoo +Name[bs]=Yahoo mail +Name[ca]=Correu Yahoo +Name[es]=Correo de Yahoo +Name[fa]=نامۀ یاهو +Name[fi]=Yahoo-sähköposti +Name[fr]=Courriel Yahoo +Name[he]=דו×"ל של Yahoo +Name[is]=Yahoo póstur +Name[ja]=Yahoo メール +Name[ka]=Yahoo ფáƒáƒ¡áƒ¢áƒ +Name[kk]=Yahoo поштаÑÑ‹ +Name[km]=សំបុážáŸ’រ​យ៉ាហ៊ូ +Name[lt]=Yahoo paÅ¡tas +Name[mk]=Yahoo-пошта +Name[nb]=Yahoo e-post +Name[nds]=Yahoo-Nettpost +Name[ne]=याहू मेल +Name[pa]=ਯਾਹੂ ਮੇਲ +Name[pl]=Poczta Yahoo +Name[pt]=E-mail Yahoo +Name[ru]=Почта Yahoo +Name[sk]=PoÅ¡ta Yahoo +Name[sl]=E-poÅ¡ta Yahoo +Name[sr]=Yahoo пошта +Name[sr@Latn]=Yahoo poÅ¡ta +Name[sv]=Yahoo e-post +Name[ta]=யாஹூ அஞà¯à®šà®²à¯ +Name[tg]=ПоÑти Yahoo +Name[tr]=Yahoo Posta +Name[uk]=Пошта Yahoo +Name[zh_CN]=Yahoo 邮件 +Comment=New email has arrived in your Yahoo inbox +Comment[be]=У вашай Ñлектроннай Ñкрыні Yahoo Ð½Ð¾Ð²Ð°Ñ Ð¿Ð¾ÑˆÑ‚Ð° +Comment[bg]=ПриÑтигна нова поща в Yahoo +Comment[bn]=আপনার ইয়à§à¦¯à¦¾à¦¹à§ ইনবকà§à¦¸à§‡ নতà§à¦¨ ই-মেইল উপসà§à¦¥à¦¿à¦¤ হয়েছে +Comment[br]=Deuet eo ur postel nevez d'em voest degemer Yahoo +Comment[bs]=Stigla je nova poÅ¡ta u vaÅ¡ Yahoo sanduÄić +Comment[ca]=Ha arribat un nou correu a la vostra bústia de Yahoo +Comment[cs]=PÅ™iÅ¡la nová poÅ¡ta do vaší Yahoo schránky +Comment[da]=Ny e-mail ankom til din Yahoo-indbakke +Comment[de]=Eine neue Nachricht befindet sich im Yahoo-Eingangsordner +Comment[el]=Μόλις έφτασε νέο email στα εισεÏχόμενα του Yahoo σας +Comment[eo]=Nova poÅto ricevita +Comment[es]=Tiene correo nuevo en la cuenta de Yahoo +Comment[et]=Saabus uus kiri sinu Yahoo Inboxi +Comment[eu]=E-posta berri bat jaso da zure Yahoo-ko sarrerako ontzian +Comment[fa]=یک رایانامۀ جدید در دریاÙتی یاهوی شما رسیده است +Comment[fi]=Uutta postia saapunut Yahoo-sähköpostilaatikkoon +Comment[fr]=Un nouveau message est arrivé dans votre boîte aux lettres Yahoo +Comment[gl]=Unha nova mensaxe chegou ao teu cartafol de entrada de Yahoo +Comment[he]=התקבל עבורך דו×ר חדש בתיבת הדו×"ל של Yahoo +Comment[hu]=Új levél érkezett a Yahoo postaládába +Comment[is]=Nýr póstur kominn í Yahoo innhólfið +Comment[it]=È arrivata nuova posta nella tua casella Yahoo +Comment[ja]=Yahoo ã®å—ä¿¡ç®±ã«æ–°ã—ã„メールãŒå±Šãã¾ã—㟠+Comment[ka]=áƒáƒ®áƒáƒšáƒ˜ ელფáƒáƒ¡áƒ¢áƒ მáƒáƒ•áƒ˜áƒ“რYahoo-ს სáƒáƒ¤áƒáƒ¡áƒ¢áƒ ყუთში +Comment[kk]=Yahoo пошта жәшігне хабарлама келі +Comment[km]=អ៊ីមែល​ážáŸ’មី​បាន​មក​ដល់​ក្នុង​ប្រអប់​ទទួល​យ៉ាហ៊ូ​របស់​អ្នក​ហើយ +Comment[lt]=Ä® Yahoo paÅ¡to dėžutÄ™ gautas naujas laiÅ¡kas +Comment[mk]=ПриÑтигна нова пошта во вашето Yahoo-Ñандаче +Comment[nb]=Ny e-post er ankommet i Yahoo-innboksen +Comment[nds]=Du hest niege Nettpost in Dien Yahoo-Postingang +Comment[ne]=नयाठइमेल तपाईà¤à¤•à¥‹ याहू पतà¥à¤°à¤®à¤žà¥à¤œà¥‚षामा आयो +Comment[nl]=Er is een nieuwe e-mail aangekomen in uw Yahoo-inbox +Comment[nn]=Ny e-post er komen til Yahoo-innboksen +Comment[pl]=NadeszÅ‚a nowa wiadomość do Twojej skrzynki odbiorczej w Yahoo +Comment[pt]=Chegou uma mensagem nova à sua caixa de correio do Yahoo +Comment[pt_BR]=Chegou um novo e-mail em sua caixa de entrada do Yahoo +Comment[ru]=Пришли новые пиÑьма в Yahoo +Comment[se]=OÄ‘Ä‘a e-boasta lea boahtán du Yahoo-boastaboksii +Comment[sk]=Do vaÅ¡ej schránky Yahoo priÅ¡la nová správa +Comment[sl]=Nova e-poÅ¡ta je prispela v vaÅ¡ nabiralnik Yahoo +Comment[sr]=Ðова порука је Ñтигла у ваше Yahoo Ñандуче +Comment[sr@Latn]=Nova poruka je stigla u vaÅ¡e Yahoo sanduÄe +Comment[sv]=Ett nytt brev har anlänt i din Yahoo-inkorg +Comment[ta]=உஙà¯à®•à®³à¯ மின௠அஞà¯à®šà®²à¯ பெடà¯à®Ÿà®¿à®•à¯à®•à¯à®³à¯ பà¯à®¤à®¿à®¯ மினà¯à®©à®žà¯à®šà®²à¯ ஒனà¯à®±à¯ வநà¯à®¤à¯à®³à¯à®³à®¤à¯ +Comment[tg]=Ба қутии поÑти Yahoo-и шумо пайёми Ñлектронии нав омад +Comment[tr]=Yahoo gelen kutunuza yeni bir e-posta geldi +Comment[uk]=Прийшли нові лиÑти у вашу Ñкриньку Yahoo +Comment[zh_CN]=您的 Yahoo 收件箱中有新邮件到达 +Comment[zh_HK]=您的 Yahoo 收件匣有新郵件 +Comment[zh_TW]=新郵件é€é”您的 Yahoo 收件匣 +default_presentation=16 + +[msn_alert] +Name=MSN Alert +Name[bg]=MSN Ñъобщение +Name[ca]=Alerta del MSN +Name[cs]=MSN upozornÄ›ní +Name[da]=MSN-alarm +Name[de]=MSN-Warnung +Name[el]=Ειδοποίηση του MSN +Name[es]=Alerta MSN +Name[et]=MSN teade +Name[fa]=هشدار ام‌اس‌ان +Name[fi]=MSN-varoitus +Name[fr]=Alerte MSN +Name[he]=×זהרה של MSN +Name[hu]=MSN értesítÅ‘ +Name[is]=MSN skeyti +Name[it]=Avviso MSN +Name[km]=សáŸáž…ក្ដី​ជូនដំណឹង MSN +Name[lt]=MSN: „dÄ—mesio!“ +Name[nb]=MSN-varsling +Name[nds]=MSN-Alarm +Name[ne]=à¤à¤®à¤à¤¸à¤à¤¨ सावधानी +Name[nl]=MSN-melding +Name[pa]=MSN ਚੇਤਾਵਨੀ +Name[pl]=Alarm MSN +Name[pt]=Alerta MSN +Name[pt_BR]=Alerta do MSN +Name[ru]=Предупреждение MSN +Name[sk]=MSN Upozornenie +Name[sl]=Alarm MSN +Name[sr]=MSN аларм +Name[sr@Latn]=MSN alarm +Name[sv]=MSN-larm +Name[tr]=MSN Uyarısı +Name[uk]=Сигнал MSN +Name[zh_CN]=MSN æ醒 +Name[zh_TW]=MSN 警告 +Comment=A new alert has been sent to you +Comment[bg]=Изпратено ви е ново Ñъобщение +Comment[ca]=Se us ha enviat una nova alerta +Comment[cs]=Bylo vám doruÄeno nové upozornÄ›ní +Comment[da]=En ny alarm er sendt til dig +Comment[de]=Sie haben eine neue Warnung erhalten +Comment[el]=Σας στάλθηκε μια νέα ειδοποίηση +Comment[es]=Se le ha enviado una nueva alerta +Comment[et]=Sulle saadeti uus teade +Comment[fa]=هشدار جدیدی برای شما ارسال شده است +Comment[fi]=Sinulle on lähetetty uusi varoitus +Comment[fr]=Une nouvelle alerte vous a été envoyée +Comment[he]=×זהרה חדשה נשלחה ×ליך +Comment[hu]=Új értesítÅ‘t küldtek Önnek +Comment[is]=Þér hefur verið sent nýtt skeyti +Comment[it]=Ti è stato inviato un avviso +Comment[ja]=æ–°ã—ã„アラートをå—ä¿¡ã—ã¾ã—㟠+Comment[km]=បានផ្ញើ​សáŸáž…ក្ដី​ជូន​ដំណឹង​ទៅឲ្យអ្នក +Comment[lt]=Jums pasiųstas naujas „dÄ—mesio!“ signalas +Comment[nb]=En ny varsling er sendt til deg +Comment[nds]=Een hett Di en niegen Alarm sendt +Comment[ne]=तपाईà¤à¤²à¤¾à¤ˆ à¤à¤‰à¤Ÿà¤¾ नयाठसावधानी सनà¥à¤¦à¥‡à¤¶ पठाà¤à¤•à¥‹ छ +Comment[nl]=U hebt een nieuwe melding ontvangen +Comment[pl]=Nowy alarm zostaÅ‚ wysÅ‚any do Ciebie +Comment[pt]=Foi enviada um novo alerta +Comment[pt_BR]=Um novo alerta foi enviado para você +Comment[ru]=Вам отправлено предупреждение +Comment[sk]=Bolo vám poslané nové upozornenie +Comment[sl]=Poslan vam je bil alarm +Comment[sr]=ПоÑлат вам је нови аларм +Comment[sr@Latn]=Poslat vam je novi alarm +Comment[sv]=Ett nytt larm har skickats till dig +Comment[tr]=Size yeni bir uyarı gönderildi +Comment[uk]=Вам було відіÑлано новий Ñигнал +Comment[zh_CN]=您收到了新æ醒 +Comment[zh_TW]=一個新的警告已é€é”給您 +default_presentation=16 + +[msn_mail] +Name=MSN Mail +Name[ar]=بريد MSN +Name[be]=Пошта MSN +Name[bg]=ПриÑтигна нова поща в MSN +Name[bn]=à¦à¦®à¦à¦¸à¦à¦¨ মেইল +Name[br]=Postel MSN +Name[bs]=MSN mail +Name[ca]=Correu de MSN +Name[cs]=MSN poÅ¡ta +Name[da]=MSN-Mail +Name[de]=MSN-Mail +Name[es]=Correo MSN +Name[fa]=نامۀ ام‌اس‌ان +Name[fi]=MSN-sähköposti +Name[fr]=Courriel MSN +Name[gl]=Correo MSN +Name[he]=דו×"ל של MSN +Name[hi]=à¤à¤®à¤à¤¸à¤à¤¨ मेल +Name[hr]=MSN poÅ¡ta +Name[hu]=MSN e-mail +Name[is]=MSN póstur +Name[it]=Posta MSN +Name[ja]=MSN メール +Name[ka]=MSN ფáƒáƒ¡áƒ¢áƒ +Name[kk]=MSN поштаÑÑ‹ +Name[km]=សំបុážáŸ’ážš MSN +Name[lt]=MSN PaÅ¡tas +Name[mk]=MSN-пошта +Name[nb]=MSN e-post +Name[nds]=MSN-Nettpost +Name[ne]=à¤à¤®à¤à¤¸à¤à¤¨ मेल +Name[nn]=MSN-e-post +Name[pa]=MSN ਮੇਲ +Name[pl]=Poczta MSN +Name[pt]=E-mail MSN +Name[ru]=Почта MSN +Name[sl]=E-poÅ¡ta MSN +Name[sr]=MSN пошта +Name[sr@Latn]=MSN poÅ¡ta +Name[sv]=MSN e-post +Name[ta]=MSN மினà¯à®©à®žà¯à®šà®²à¯ +Name[tg]=ПоÑти MSN +Name[tr]=MSN Posta +Name[uk]=Пошта MSN +Name[zh_CN]=MSN 邮件 +Comment=New email has arrived in your MSN inbox +Comment[be]=У вашай Ñлектроннай Ñкрыні MSN Ð½Ð¾Ð²Ð°Ñ Ð¿Ð¾ÑˆÑ‚Ð° +Comment[bg]=ПриÑтигна нова поща в MSN +Comment[bn]=আপনার à¦à¦®à¦à¦¸à¦à¦¨ ইনবকà§à¦¸à§‡ নতà§à¦¨ ই-মেইল উপসà§à¦¥à¦¿à¦¤ হয়েছে +Comment[br]=Deuet eo ur postel nevez d'em voest degemer MSN +Comment[bs]=Stigla je nova poÅ¡ta u vaÅ¡ MSN sanduÄić +Comment[ca]=Ha arribat un nou correu a la vostra bústia de MSN +Comment[cs]=PÅ™iÅ¡la nová poÅ¡ta do vaší MSN schránky +Comment[da]=Ny e-mail ankom til din MSN-indbakke +Comment[de]=Eine neue Nachricht befindet sich im MSN-Eingangsordner +Comment[el]=Μόλις έφτασε νέο e-mail στα εισεÏχόμενα του MSN σας +Comment[eo]=Nova poÅto ricevita +Comment[es]=Tiene correo nuevo en la cuenta de MSN +Comment[et]=Saabus uus kiri sinu MSN Inboxi +Comment[eu]=E-posta berri bat jaso da zure MSN-ko sarrerako ontzian +Comment[fa]=یک رایانامۀ جدید در دریاÙتی ام‌اس‌ان شما رسیده است +Comment[fi]=Uutta postia saapunut MSN-sähköpostilaatikkoon +Comment[fr]=Un nouveau message est arrivé dans votre boîte aux lettres MSN +Comment[gl]=Unha nova mensaxe chegou ao teu cartafol de entrada de MSN +Comment[he]=התקבל עבורך דו×ר חדש בתיבת הדו×"ל של MSN +Comment[hr]=Nova poÅ¡ta je stigla u vaÅ¡ MSN sanduÄić +Comment[hu]=Új levél érkezett az MSN postaládába +Comment[is]=Það er nýr póstur í MSN innhólfinu þínu +Comment[it]=È arrivata nuova posta nella tua casella MSN +Comment[ja]=MSN ã®å—ä¿¡ç®±ã«æ–°ã—ã„メールãŒå±Šãã¾ã—㟠+Comment[ka]=áƒáƒ®áƒáƒšáƒ˜ ელფáƒáƒ¡áƒ¢áƒ მáƒáƒ•áƒ˜áƒ“რMSN სáƒáƒ¤áƒáƒ¡áƒ¢áƒ ყუთში +Comment[kk]=MSN пошта жәшігне хабарлама келді +Comment[km]=អ៊ីមែល​ážáŸ’មី​បាន​មក​ដល់​ក្នុង​ប្រអប់​ទទួល MSN របស់​អ្នក​​ហើយ +Comment[lt]=Ä® MSN paÅ¡to dėžutÄ™ gautas naujas laiÅ¡kas +Comment[mk]=ПриÑтигна нова пошта во вашето MSN-Ñандаче +Comment[nb]=Ny e-post er ankommet i MSN-innboksen +Comment[nds]=Du hest niege Nettpost in Dien MSN-Postingang +Comment[ne]=तपाईà¤à¤•à¥‹ à¤à¤®à¤à¤¸à¤à¤¨ पतà¥à¤°à¤®à¤žà¥à¤œà¥‚षामा नयाठइमेल छ +Comment[nl]=Er is een nieuwe e-mail aangekomen in uw MSN-inbox +Comment[nn]=Ny e-post er komen til MSN-innboksen +Comment[pl]=NadeszÅ‚a nowa wiadomość do Twojej skrzynki odbiorczej w MSN +Comment[pt]=Chegou uma mensagem nova à sua caixa de correio do MSN +Comment[pt_BR]=chegou um novo e-mail em sua caixa de entrada MSN +Comment[ru]=Пришли новые пиÑьма в MSN +Comment[se]=OÄ‘Ä‘a e-boasta lea boahtán du MSN-boastaboksii +Comment[sk]=Do vaÅ¡ej schránky MSN priÅ¡la nová správa +Comment[sl]=Nova e-poÅ¡ta je prispela v vaÅ¡ nabiralnik MSN +Comment[sr]=Ðова порука је Ñтигла у ваше MSN Ñандуче +Comment[sr@Latn]=Nova poruka je stigla u vaÅ¡e MSN sanduÄe +Comment[sv]=Ett nytt brev har anlänt i din MSN-inkorg +Comment[ta]=உஙà¯à®•à®³à¯ எமà¯à®Žà®¸à¯à®Žà®©à¯ அஞà¯à®šà®²à¯ பெடà¯à®Ÿà®¿à®•à¯à®•à¯ பà¯à®¤à®¿à®¯ மினà¯à®©à®žà¯à®šà®²à¯ வநà¯à®¤à¯à®³à¯à®³à®¤à¯ +Comment[tg]=Ба қутии поÑти MSN-и шумо пайёми Ñлектронии нав омад +Comment[tr]=MSN gelen kutunuza yeni bir e-posta geldi +Comment[uk]=Прийшли нові лиÑти у вашу Ñкриньку MSN +Comment[zh_CN]=您的 MSN 收件箱中有新邮件到达 +Comment[zh_HK]=您的 MSN 收件匣有新郵件 +Comment[zh_TW]=新郵件é€é”您的 MSN 收件匣 +default_presentation=16 + +[icq_authorization] +Name=ICQ Authorization +Name[be]=Спраўджванне аÑобы ICQ +Name[bg]=ÐžÑ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð½Ð° ICQ +Name[bn]=আই-সি-কিউ পà§à¦°à¦¾à¦ªà§à¦¤à¦¾à¦§à¦¿à¦•à¦¾à¦° +Name[bs]=ICQ autorizacija +Name[ca]=Autorització ICQ +Name[cs]=ICQ autorizace +Name[da]=ICQ-godkendelse +Name[de]=ICQ-Autorisierung +Name[el]=Πιστοποίηση ICQ +Name[en_GB]=ICQ Authorisation +Name[eo]=ICQ-Rajtigo +Name[es]=Autorización ICQ +Name[et]=ICQ autoriseerimine +Name[eu]=ICQ baimena +Name[fa]=اجازۀ ICQ +Name[fi]=ICQ-todennus +Name[fr]=Autorisation ICQ +Name[ga]=Údárú ICQ +Name[gl]=Autorización ICQ +Name[he]=הרשמה ל-ICQ +Name[hu]=ICQ felhasználóazonosítás +Name[is]=ICQ auðkenning +Name[it]=Autorizzazione ICQ +Name[ja]=ICQ 承諾 +Name[ka]=ICQ áƒáƒ•áƒ¢áƒáƒ áƒ˜áƒ–áƒáƒªáƒ˜áƒ +Name[kk]=ICQ авторизациÑÑÑ‹ +Name[km]=សáŸáž…ក្ដី​អនុញ្ញាហICQ +Name[lt]=ICQ prieiga +Name[nb]=ICQ-autorisering +Name[nds]=ICQ-Identifikatschoon +Name[ne]=आईसीकà¥à¤¯à¥‚ आधिकीकरण +Name[nl]=ICQ-autorisatie +Name[nn]=ICQ-autorisering +Name[pa]=ICQ ਪਰਮਾਣਕਿਤਾ +Name[pl]=Autoryzacja ICQ +Name[pt]=Autorização ICQ +Name[pt_BR]=Autorização do ICQ +Name[ro]=Autorizare ICQ +Name[ru]=ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ ICQ +Name[sk]=ICQ overenie +Name[sl]=Odobritev za ICQ +Name[sr]=ICQ ауторизација +Name[sr@Latn]=ICQ autorizacija +Name[sv]=ICQ-behörighetskontroll +Name[tr]=ICQ Ä°zni +Name[uk]=ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ñ–Ñ ICQ +Name[zh_CN]=ICQ èº«ä»½éªŒè¯ +Name[zh_HK]=ICQ 授權 +Name[zh_TW]=ICQ èªè­‰ +Comment=An ICQ user has authorized/declined your authorization request +Comment[be]=КарыÑтальнік ICQ адказаў на запыт атарызацыі +Comment[bg]=Потребител на ICQ е приел/отхвърлил вашата заÑвка за Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ +Comment[bn]=à¦à¦•à¦œà¦¨ আই-সি-কিউ বà§à¦¯à¦¬à¦¹à¦¾à¦°à¦•à¦¾à¦°à§€ আপনার পà§à¦°à¦¾à¦ªà§à¦¤à¦¾à¦§à¦¿à¦•à¦¾à¦° অনà§à¦°à§‹à¦§ অনà§à¦®à§‹à¦¦à¦¨/পà§à¦°à¦¤à§à¦¯à¦¾à¦–à§à¦¯à¦¾à¦¨ করেছে +Comment[bs]=Jedan ICQ korisnik je odobrio ili odbio vaÅ¡ zahtjev za autorizaciju +Comment[ca]=Un usuari d'ICQ ha autoritzat/declinat la vostra petició d'autorització +Comment[cs]=ICQ uživatel vám poskytl/odmítnul požadavek na autorizaci +Comment[da]=En ICQ-bruger har godkendt/afslÃ¥et din godkendelsesforespørgsel +Comment[de]=Ein ICQ-Benutzer hat Ihre Autorisierung erlaubt/abgelehnt +Comment[el]=Ένας χÏήστης ICQ έχει πιστοποιήσει/αποÏÏίψει την αίτησή σας για πιστοποίηση +Comment[en_GB]=An ICQ user has authorised/declined your authorisation request +Comment[es]=Un usuario ICQ ha autorizado/declinado su petición de autorización +Comment[et]=ICQ kasutaja autoriseeris/lükkas tagasi sinu autoriseerimissoovi +Comment[eu]=ICQ erabiltzailea batek zure baimen eskaera onartu/ukatu du. +Comment[fa]=یک کاربر ICQ درخواست اجازۀ شما را پذیرÙته/نپذیرÙته است +Comment[fi]=ICQ-käyttäjä on hyväksynyt/hylännyt hyväksymispyyntösi +Comment[fr]=Un utilisateur ICQ a accepté ou décliné votre demande d'autorisation +Comment[gl]=Un usuario ICQ autorizou/declinou a súa solicitude de autorización +Comment[he]=משתמש ICQ ×ישר/דחה ×ת בקשתך להרשמה +Comment[hu]=Egy ICQ-felhasználó elfogadta vagy elutasította az Ön bejelentkezési kérését +Comment[is]=ICQ notandi hefur heimilað/hafnað auðkenningabeiðni þinni +Comment[it]=Un utente ICQ ha autorizzato/declinato la tua richiesta +Comment[ja]=ICQ ユーザãŒã‚ãªãŸã®èªå¯è¦æ±‚ã‚’èªå¯/æ‹’å¦ã—ã¾ã—㟠+Comment[ka]=ICQ მáƒáƒ›áƒ®áƒ›áƒáƒ áƒ”ბელმრმáƒáƒ’ცáƒáƒ— áƒáƒ•áƒ¢áƒáƒ áƒ˜áƒ–áƒáƒªáƒ˜áƒ +Comment[kk]=ICQ пайдаланушыÑÑ‹ Ð°Ð²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ Ñұрауыңызды құптады не құптаған жоқ +Comment[km]=អ្នក​ប្រើ ICQ ម្នាក់​បាន​អនុញ្ញាហឬ បដិសáŸáž’​សំណើសុំ​សáŸáž…ក្ដី​អនុញ្ញាážâ€‹ážšáž”ស់​អ្នក​ហើយ +Comment[lt]=ICQ naudotojas priÄ—mÄ—/atmetÄ— jÅ«sų prieigos praÅ¡ymÄ… +Comment[nb]=En ICQ-bruker har autorisert eller avvist din forespørsel om autorisering +Comment[nds]=En ICQ-Bruker hett Dien Identifikatschoonanfraag tolaten/afwiest +Comment[ne]=à¤à¤‰à¤Ÿà¤¾ आईसीकà¥à¤¯à¥‚ पà¥à¤°à¤¯à¥‹à¤—करà¥à¤¤à¤¾à¤²à¥‡ तपाईà¤à¤•à¥‹ आधिकीकरण अनà¥à¤°à¥‹à¤§à¤®à¤¾ आधिकरण गरà¥à¤¯à¥‹/घटायो +Comment[nl]=Een ICQ-gebruiker heeft uw autorisatieverzoek geaccepteerd/afgewezen +Comment[nn]=Ein ICQ-brukar har godtatt eller nekta førespurnaden din om autorisering +Comment[pl]=Użytkownik ICQ zaakceptowaÅ‚/odrzuciÅ‚ Twoje żądanie autoryzacji +Comment[pt]=Um utilizador ICQ autorizou/rejeitou o seu pedido de autorização +Comment[pt_BR]=Um usuário do ICQ aceitou/declinou seu pedido de autorização +Comment[ru]=Пользователь ICQ авторизовал Ð²Ð°Ñ Ð¸Ð»Ð¸ отклонил ваш Ð·Ð°Ð¿Ñ€Ð¾Ñ +Comment[sk]=ICQ užívateľ overil/odmietol VaÅ¡u požiadavku na overenie +Comment[sl]=Uporabnik ICQ-ja je spejel/zavrnil vaÅ¡ zahtevek za odobritev +Comment[sr]=ICQ кориÑник је ауторизовао/одбио ваш захтев за ауторизацију +Comment[sr@Latn]=ICQ korisnik je autorizovao/odbio vaÅ¡ zahtev za autorizaciju +Comment[sv]=En ICQ-användare har tillÃ¥tit eller nekat till din begäran om behörighetskontroll +Comment[tr]=ICQ kullanısı izin isteÄŸinizi onayladı/onaylamadı +Comment[uk]=КориÑтувач ICQ затвердив/відхилив ваш запит на авторизацію +Comment[zh_CN]=ICQ 用户åŒæ„/æ‹’ç»äº†æ‚¨çš„身份验è¯è¯·æ±‚ +Comment[zh_HK]=æœ‰ä½ ICQ 用戶批準/拒絕了您的授權è¦æ±‚ +Comment[zh_TW]=ICQ 使用者已èªè­‰/拒絕您的èªè­‰è¦æ±‚ +default_presentation=16 + +[irc_event] +Name=IRC Event +Name[be]=ÐŸÐ°Ð´Ð·ÐµÑ IRC +Name[bg]=Събитие в IRC +Name[bn]=আই-আর-সি ঘটনা +Name[bs]=IRC dogaÄ‘aj +Name[ca]=Esdeveniment IRC +Name[cs]=IRC událost +Name[da]=IRC-begivenhed +Name[de]=IRC-Ereignis +Name[el]=Γεγονός IRC +Name[eo]=IRC-Evento +Name[es]=Evento IRC +Name[et]=IRC sündmus +Name[eu]=IRC gertaera +Name[fa]=رویداد IRC +Name[fi]=IRC-tapahtuma +Name[fr]=Évènement IRC +Name[ga]=Teagmhas IRC +Name[gl]=Evento IRC +Name[he]=×רוע IRC +Name[hu]=IRC-esemény +Name[is]=IRC atburður +Name[it]=Evento IRC +Name[ja]=IRC イベント +Name[ka]=IRC მáƒáƒ•áƒšáƒ”ნრ+Name[kk]=IRC оқиғаÑÑ‹ +Name[km]=ព្រឹážáŸ’ážáž·áž€áž¶ážšážŽáŸ IRC +Name[lt]=IRC Ä®vykis +Name[nb]=IRC-hendelse +Name[nds]=Klöön-Begeefnis +Name[ne]=आइआरसी घटना +Name[nl]=IRC-gebeurtenis +Name[nn]=IRC-hending +Name[pa]=IRC ਘਟਨਾ +Name[pl]=Zdarzenie IRC +Name[pt]=Evento de IRC +Name[pt_BR]=Evento IRC +Name[ro]=Eveniment IRC +Name[ru]=Событие IRC +Name[sk]=IRC udalosÅ¥ +Name[sl]=Dogodek IRC +Name[sr]=IRC догађај +Name[sr@Latn]=IRC dogaÄ‘aj +Name[sv]=IRC-händelse +Name[tr]=IRC Olayı +Name[uk]=ÐŸÐ¾Ð´Ñ–Ñ IRC +Name[uz]=IRC hodisasi +Name[uz@cyrillic]=IRC ҳодиÑаÑи +Name[zh_CN]=IRC 事件 +Name[zh_HK]=IRC 事件 +Name[zh_TW]=IRC 事件 +Comment=An IRC event has occurred +Comment[be]=ÐŸÐ°Ð´Ð·ÐµÑ IRC +Comment[bg]=Събитие в клиента за IRC +Comment[bn]=à¦à¦•à¦Ÿà¦¿ আই-আর-সি ঘটনা ঘটেছে +Comment[bs]=Desio se IRC dogaÄ‘aj +Comment[ca]=Ha ocorregut un esdeveniment d'IRC +Comment[cs]=Nastala IRC událost +Comment[da]=En IRC=begivenhed er opstÃ¥et +Comment[de]=Ein IRC-Ereignis ist aufgetreten +Comment[el]=Ένα γεγονός του IRC συνέβη +Comment[eo]=Okazis IRC-evento +Comment[es]=Ocurrió un evento IRC +Comment[et]=Midagi toimus IRCus +Comment[eu]=IRC gertaera bat gertatu da +Comment[fa]=یک رویداد IRC رخ داده است +Comment[fi]=IRC-tapahtuma +Comment[fr]=Un évènement IRC s'est produit +Comment[gl]=Ocorreu un evento IRC +Comment[he]=×רוע IRC התרחש +Comment[hu]=IRC-esemény következett be +Comment[is]=IRC atburður hefur átt sér stað +Comment[it]=Evento IRC +Comment[ja]=IRC イベントãŒç™ºç”Ÿã—ã¾ã—㟠+Comment[ka]=IRC მáƒáƒ•áƒšáƒ”ნრმáƒáƒ®áƒ“რ+Comment[kk]=IRC оқиғаÑÑ‹ орын алды +Comment[km]=ព្រឹážáŸ’ážáž·áž€áž¶ážšážŽáŸ IRC មួយ​បាន​កើážáž¡áž¾áž„ +Comment[lt]=Ä®vyko IRC įvykis +Comment[nb]=Det har skjedd en IRC-hendelse +Comment[nds]=Dat hett en IRC-Begeefnis geven +Comment[ne]=à¤à¤‰à¤Ÿà¤¾ आइआरसी घटना देखापरà¥à¤¯à¥‹ +Comment[nl]=Er heeft zich een IRC-gebeurtenis voorgedaan +Comment[nn]=Det har skjedd ei IRC-hending +Comment[pl]=WystÄ…piÅ‚o zdarzenie IRC-a +Comment[pt]=Ocorreu um evento de IRC +Comment[pt_BR]=Um evento do IRC ocorreu +Comment[ru]=Произошло Ñобытие IRC +Comment[sk]=Nastala IRC udalosÅ¥ +Comment[sl]=Zgodil se je dogodek na IRC-u +Comment[sr]=Дошло јо до догађаја на IRC-у +Comment[sr@Latn]=DoÅ¡lo jo do dogaÄ‘aja na IRC-u +Comment[sv]=En IRC-händelse har inträffat +Comment[tr]=IRC olayı meydana geldi +Comment[uk]=СталаÑÑŒ Ð¿Ð¾Ð´Ñ–Ñ IRC +Comment[zh_CN]=å‘生了 IRC 事件 +Comment[zh_HK]=發生了 IRC 事件 +Comment[zh_TW]=發生 IRC 事件 +default_presentation=16 + +[connection_error] +Name=Connection Error +Name[be]=Памылка злучÑÐ½Ð½Ñ +Name[bg]=Грешка при връзка +Name[bn]=সংযোগ তà§à¦°à§à¦Ÿà¦¿ +Name[br]=Fazi ar gevreadenn +Name[bs]=GreÅ¡ka u vezi +Name[ca]=Error de connexió +Name[cs]=Chyba ve spojení +Name[da]=Forbindelsesfejl +Name[de]=Verbindungsfehler +Name[el]=Σφάλμα σÏνδεσης +Name[eo]=Konekto-eraro +Name[es]=Error en la conexión +Name[et]=Ãœhenduse viga +Name[eu]=Konexio-errorea +Name[fa]=خطای اتصال +Name[fi]=Yhteysvirhe +Name[fr]=Erreur de connexion +Name[ga]=Earráid Naisc +Name[gl]=Erro de Conexión +Name[he]=שגי××” בחיבור +Name[hu]=Csatlakozási hiba +Name[is]=Villa í tengingu +Name[it]=Errore di connessione +Name[ja]=接続エラー +Name[ka]=კáƒáƒ•áƒ¨áƒ˜áƒ áƒ˜áƒ¡ შეცდáƒáƒ›áƒ +Name[kk]=ҚоÑылым қатеÑÑ– +Name[km]=កំហុស​ការ​ážâ€‹áž—្ជាប់ +Name[lt]=RyÅ¡io klaida +Name[nb]=Koblingsfeil +Name[nds]=Verbinnen-Fehler +Name[ne]=जडान तà¥à¤°à¥à¤Ÿà¤¿ +Name[nl]=Verbindingsfout +Name[nn]=Sambandsfeil +Name[pa]=ਕà©à¨¨à©ˆà¨•à¨¸à¨¼à¨¨ ਗਲਤੀ +Name[pl]=BÅ‚Ä…d Å‚Ä…czenia +Name[pt]=Erro de Ligação +Name[pt_BR]=Erro na Conexão +Name[ru]=Редактор Ñоединений +Name[sk]=Chyba spojenia +Name[sl]=Napaka povezave +Name[sr]=Грешка везе +Name[sr@Latn]=GreÅ¡ka veze +Name[sv]=Anslutningsfel +Name[tr]=BaÄŸlantı Hatası +Name[uk]=Помилка з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ +Name[uz]=Ulanish xatosi +Name[uz@cyrillic]=Уланиш хатоÑи +Name[zh_CN]=连接错误 +Name[zh_HK]=連線錯誤 +Name[zh_TW]=連線錯誤 +Comment=An error on connection has occurred +Comment[be]=ÐдбылаÑÑ Ð¿Ð°Ð¼Ñ‹Ð»ÐºÐ° злучÑÐ½Ð½Ñ +Comment[bg]=Грешка по време на уÑтановÑване на връзка +Comment[bn]=সংযোগে à¦à¦•à¦Ÿà¦¿ তà§à¦°à§à¦Ÿà¦¿ ঘটেছে +Comment[bs]=DoÅ¡lo je do greÅ¡ke na vezi sa mrežom (Internetom) +Comment[ca]=Hi ha hagut un error al connectar +Comment[cs]=Nastala chyba ve spojení +Comment[da]=En fejl i forbindelsen er opstÃ¥et +Comment[de]=Ein Verbindungsfehler ist aufgetreten +Comment[el]=ΠαÏουσιάστηκε σφάλμα κατά τη σÏνδεση +Comment[es]=Ocurrió un error en la conexión +Comment[et]=Ãœhendusega tekkis viga +Comment[eu]=Errore bat gertatu da konexioan +Comment[fa]=هنگام اتصال خطایی رخ داده است +Comment[fi]=Yhteydessä tapahtui virhe +Comment[fr]=Une erreur de connexion est apparue +Comment[gl]=Ocorreu un erro na conexión +Comment[he]=התרחשה שגי××” בחיבור +Comment[hu]=Hiba történt csatlakozás közben +Comment[is]=Villa kom upp þegar reynt var að tengjast +Comment[it]=Si è verificato un errore di connessione +Comment[ja]=接続ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—㟠+Comment[ka]=კáƒáƒ•áƒ¨áƒ˜áƒ áƒ˜áƒ¡áƒáƒ¡ შეცდáƒáƒ›áƒ მáƒáƒ®áƒ“რ+Comment[kk]=ҚоÑылым қатеÑÑ– орын алды +Comment[km]=កំហុស​ការ​ážâ€‹áž—្ជាប់​មួយ​បាន​កើážâ€‹áž¡áž¾áž„ +Comment[lt]=Ä®vyko ryÅ¡io klaida +Comment[nb]=Det har oppstÃ¥tt en feil ved tilkobling +Comment[nds]=Dat hett en Verbinnenfehler geven +Comment[ne]=जडनामा à¤à¤‰à¤Ÿà¤¾ तà¥à¤°à¥à¤Ÿà¤¿ देखापरà¥à¤¯à¥‹ +Comment[nl]=Er deed zich een verbindingsfout voor +Comment[nn]=Det har oppstÃ¥tt eit problem med sambandet +Comment[pl]=WystÄ…piÅ‚ bÅ‚Ä…d przy Å‚Ä…czeniu +Comment[pt]=Ocorreu um erro na ligação +Comment[pt_BR]=Ocorreu um erro na conexão +Comment[ru]=Возникла ошибка ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ +Comment[sk]=Nastala chyba spojenia +Comment[sl]=PriÅ¡lo je do napake pri povezavi +Comment[sr]=Дошло јо до грешке на вези +Comment[sr@Latn]=DoÅ¡lo jo do greÅ¡ke na vezi +Comment[sv]=En fel vid anslutning har inträffat +Comment[tr]=BaÄŸlantıda hata meydana geldi +Comment[uk]=ТрапилаÑÑŒ помила під Ñ‡Ð°Ñ Ð·'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ +Comment[uz]=Aloqa oÊ»rnatishda xato roÊ»y berdi +Comment[uz@cyrillic]=Ðлоқа ўрнатишда хато рўй берди +Comment[zh_CN]=å‘生了连接错误 +Comment[zh_HK]=連線發生錯誤 +Comment[zh_TW]=發生了連線的錯誤 +default_presentation=2 + +[connection_lost] +Name=Connection Lost +Name[be]=ЗлучÑнне згубленае +Name[bg]=Връзката е прекъÑната +Name[bn]=সংযোগ বিচà§à¦›à¦¿à¦¨à§à¦¨ +Name[br]=Kollet eo ar gevreadenn +Name[bs]=Veza je pukla +Name[ca]=Connexió perduda +Name[cs]=Spojení ztraceno +Name[cy]=Collwyd Cysylltiad +Name[da]=Forbindelse tabt +Name[de]=Verbindung unterbrochen +Name[el]=Η σÏνδεση έκλεισε +Name[eo]=Konekto Perdita +Name[es]=Conexión perdida +Name[et]=Ãœhendus kadus +Name[eu]=Konexioa galdu da +Name[fa]=اتصال Ù…Ùقود شد +Name[fi]=Yhteys hävisi +Name[fr]=Connexion perdue +Name[ga]=Cailleadh an Nasc +Name[gl]=Conexión Perdida +Name[he]=חיבור נסגר +Name[hu]=A kapcsolat megszakadt +Name[is]=Tengingu tapað +Name[it]=Connessione chiusa +Name[ja]=接続切断 +Name[ka]=კáƒáƒ•áƒ¨áƒ˜áƒ áƒ˜ გáƒáƒ¬áƒ§áƒ“რ+Name[kk]=ҚоÑылым үзілді +Name[km]=បាážáŸ‹â€‹áž”ង់​ការ​ážâ€‹áž—្ជាប់ +Name[lt]=RyÅ¡ys prarastas +Name[nb]=Tilkobling mistet +Name[nds]=Verbinnen afreten +Name[ne]=जडान हरायो +Name[nl]=Verbinding verbroken +Name[nn]=Samband stengt +Name[pa]=ਕà©à¨¨à©ˆà¨•à¨¸à¨¼à¨¨ ਟà©à©±à¨Ÿà¨¿à¨† +Name[pl]=PoÅ‚Ä…czenie utracone +Name[pt]=Perdeu-se a Ligação +Name[pt_BR]=Conexão Perdida +Name[ru]=Соединение утерÑно +Name[sk]=Spojenie stratené +Name[sl]=Povezava prekinjena +Name[sr]=Веза изгубљена +Name[sr@Latn]=Veza izgubljena +Name[sv]=Anslutning förlorad +Name[tr]=BaÄŸlantı Kesildi +Name[uk]=З'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð²Ñ‚Ñ€Ð°Ñ‡ÐµÐ½Ð¾ +Name[uz]=Ulanish uzildi +Name[uz@cyrillic]=Уланиш узилди +Name[zh_CN]=连接已丢失 +Name[zh_HK]=連線已中斷 +Name[zh_TW]=連線已中斷 +Comment=The connection has been lost +Comment[be]=ЗлучÑнне згубленае +Comment[bg]=Връзката е прекъÑната +Comment[bn]=সংযোগ বিচà§à¦›à¦¿à¦¨à§à¦¨ হয়েছে +Comment[bs]=Veza na mrežu (Internet) je prekinuta +Comment[ca]=S'ha perdut la connexió +Comment[cs]=Spojení bylo ztraceno +Comment[da]=Forbindelsen er gÃ¥et tabt +Comment[de]=Die Verbindung wurde unterbrochen +Comment[el]=Η σÏνδεση έκλεισε +Comment[eo]=La konekto estis perdita +Comment[es]=Se perdió la conexión +Comment[et]=Ãœhendus kadus +Comment[eu]=Konexioa galdu da +Comment[fa]=اتصال Ù…Ùقود شده است +Comment[fi]=Yhteys on hävinnyt +Comment[fr]=La connexion a été perdue +Comment[ga]=Cailleadh an nasc +Comment[gl]=Perdeuse a conexión +Comment[he]=החיבור נסגר +Comment[hu]=A kapcsolat megszakadt +Comment[is]=Tengingin tapaðist +Comment[it]=La connessione è stata chiusa +Comment[ja]=接続ãŒåˆ‡æ–­ã•ã‚Œã¾ã—㟠+Comment[ka]=კáƒáƒ•áƒ¨áƒ˜áƒ áƒ˜ გáƒáƒ¬áƒ§áƒ“რ+Comment[kk]=ҚоÑылым үзіліÑÑ– орын алды +Comment[km]=បាន​បាážáŸ‹áž”ង់​ការ​ážáž—្ជាប់ +Comment[lt]=RyÅ¡ys prarastas +Comment[nb]=Tilkoblingen er tapt +Comment[nds]=De Verbinnen is afreten +Comment[ne]=जडान हराà¤à¤•à¥‹ छ +Comment[nl]=De verbinding is verbroken +Comment[nn]=Sambandet er stengt +Comment[pl]=PoÅ‚Ä…czenie zostaÅ‚o przerwane +Comment[pt]=A ligação foi-se abaixo +Comment[pt_BR]=A conexão foi perdida +Comment[ru]=Разрыв ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ +Comment[sk]=Spojenie sa stratilo +Comment[sl]=Povezava je bila prekinjena +Comment[sr]=Веза је изгубљена +Comment[sr@Latn]=Veza je izgubljena +Comment[sv]=Anslutningen har förlorats +Comment[tr]=BaÄŸlantı kayboldu +Comment[uk]=З'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð±ÑƒÐ»Ð¾ втрачено +Comment[uz]=OÊ»rnatilgan aloqa uzildi +Comment[uz@cyrillic]=Ўрнатилган алоқа узилди +Comment[zh_CN]=连接已丢失 +Comment[zh_HK]=連線已中斷 +Comment[zh_TW]=連線已中斷 +default_presentation=16 + +[cannot_connect] +Name=Cannot Connect +Name[be]=Ðемагчыма злучыцца +Name[bg]=Ðевъзможна връзка +Name[bn]=সংযোগ করা গেল না +Name[br]=N'hellan ket kevreañ +Name[bs]=Ne mogu se spojiti +Name[ca]=No es pot connectar +Name[cs]=Nelze se pÅ™ipojit +Name[da]=Kan ikke forbinde +Name[de]=Verbindung nicht möglich +Name[el]=ΑδÏνατη η σÏνδεση +Name[eo]=Ne eblas konekti +Name[es]=No se pudo conectar +Name[et]=Ãœhendumine ebaõnnestus +Name[eu]=Ezin da konektatu +Name[fa]=نمی‌تواند متصل شود +Name[fi]=Ei voitu yhdistää +Name[fr]=Impossible de se connecter +Name[ga]=Ní Féidir Nasc a Dhéanamh +Name[gl]=Non se pode conectar +Name[he]=×ין ×פשרות להתחבר +Name[hu]=Nem sikerült csatlakozni +Name[is]=Get ekki tengst +Name[it]=Impossibile connettersi +Name[ja]=接続ã§ãã¾ã›ã‚“ +Name[ka]=დáƒáƒ™áƒáƒ•áƒ¨áƒ˜áƒ áƒ”ბრვერ ხáƒáƒ áƒªáƒ˜áƒ”ლდებრ+Name[kk]=ҚоÑылым болмады +Name[km]=មិន​អាច​ážâ€‹áž—្ជាប់ +Name[lt]=Nepavyksta prisijungti +Name[nb]=Kan ikke koble til +Name[nds]=Tokoppeln nich mööglich +Name[ne]=जडान गरà¥à¤¨ सकिà¤à¤¦à¥ˆà¤¨ +Name[nl]=Verbinding niet mogelijk +Name[nn]=Klarar ikkje kopla til +Name[pa]=ਜà©à©œ ਨਹੀਂ ਸਕਦਾ +Name[pl]=Nie można siÄ™ poÅ‚Ä…czyć +Name[pt]=Não É Possível Ligar +Name[pt_BR]=Não foi possível efetuar a conexão +Name[ru]=Ðе удаётÑÑ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡Ð¸Ñ‚ÑŒÑÑ +Name[sk]=Nedá sa pripojiÅ¥ +Name[sl]=Povezava ni mogoÄa +Name[sr]=Повезивање немогуће +Name[sr@Latn]=Povezivanje nemoguće +Name[sv]=Kan inte ansluta +Name[tr]=BaÄŸlanılamıyor +Name[uk]=Ðе вдаєтьÑÑ Ð·'єднатиÑÑŒ +Name[uz]=Ulanib boÊ»lmadi +Name[uz@cyrillic]=Уланиб бўлмади +Name[zh_CN]=无法连接 +Name[zh_HK]=無法連接 +Name[zh_TW]=無法連線 +Comment=Kopete can't connect to the service +Comment[be]=Kopete не можа злучыцца з ÑервіÑам +Comment[bg]=УÑтановÑването на връзка е невъзможно +Comment[bn]=কপেট সারà§à¦­à¦¿à¦¸à§‡ সংযোগ করতে পারেনি +Comment[bs]=Kopete se ne može spojiti na servis +Comment[ca]=El Kopete no pot connectar al servei +Comment[cs]=Kopete se nedokáže pÅ™ipojit ke službÄ› +Comment[da]=Kopete kan ikke forbinde til tjenesten +Comment[de]=Kopete kann zu diesem Dienst keine Verbindung herstellen +Comment[el]=Το Kopete δεν μποÏεί να συνδεθεί με την υπηÏεσία +Comment[es]=Kopete no se puede conectar al servicio +Comment[et]=Kopetel ebaõnnestus ühendumine teenusega +Comment[eu]=Kopete-k ezin du zerbitzuarekin konektatu +Comment[fa]=Kopete نمی‌تواند به خدمت متصل شود +Comment[fi]=Kopete ei voi yhdistää palveluun +Comment[fr]=Kopete ne peut pas se connecter à ce service +Comment[gl]=Kopete non pode conectar co servicio +Comment[he]=Kopete ×œ× ×™×›×•×œ להתחבר לשירות +Comment[hu]=A Kopete nem tudott csatlakozni a szolgáltatóhoz +Comment[is]=Kopete gat ekki tengst þjónustunni +Comment[it]=Kopete non è in grado di connettersi al servizio +Comment[ja]=Kopete ã¯ã‚µãƒ¼ãƒ“スã«æŽ¥ç¶šã§ãã¾ã›ã‚“ +Comment[ka]=Kopeteს áƒáƒ  შეუძლირსერვერთáƒáƒœ დáƒáƒ™áƒáƒ•áƒ¨áƒ˜áƒ áƒ”ბრ+Comment[kk]=Kopete қызметке қоÑыла алмады +Comment[km]=Kopete មិន​អាច​ážâ€‹áž—្ជាប់​ទៅ​សáŸážœáž¶â€‹áž”ាន​ឡើយ +Comment[lt]=Kopete nepavyksta prisijungti prie serviso +Comment[nb]=Kopete kan ikke koble til tjenesten +Comment[nds]=Kopete kann sik nich na den Deenst tokoppeln +Comment[ne]=कोपेटले सेवामा जडान गरà¥à¤¨ सकà¥à¤¦à¥ˆà¤¨ +Comment[nl]=Kopete kan geen verbinding met de dienst maken +Comment[nn]=Kopete klarar ikkje kopla til tenesta +Comment[pl]=Kopete nie może siÄ™ poÅ‚Ä…czyć z usÅ‚ugÄ… +Comment[pt]=O Kopete não se conseguiu ligar ao serviço +Comment[pt_BR]=O Kopete não pode conectar-se ao serviço +Comment[ru]=Kopete не удаётÑÑ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡Ð¸Ñ‚ÑŒÑÑ Ðº Ñлужбе +Comment[sk]=Kopete sa nevie pripojiÅ¥ na službu +Comment[sl]=Kopete se ne more povezati s storitvijo +Comment[sr]=Kopete Ñе не може повезати Ñа ÑервиÑом +Comment[sr@Latn]=Kopete se ne može povezati sa servisom +Comment[sv]=Kopete kan inte ansluta till tjänsten +Comment[tr]=Kopete servise baÄŸlanamıyor +Comment[uk]=Kopete не може з'єднатиÑÑŒ зі Ñлужбою +Comment[uz]=Kopete xizmat bilan aloqa oÊ»rnataolmadi +Comment[uz@cyrillic]=Kopete хизмат билан алоқа ўрнатаолмади +Comment[zh_CN]=Kopete 无法连接到æœåŠ¡ +Comment[zh_HK]=Kopete 無法連接至æœå‹™ +Comment[zh_TW]=Kopete 無法連線到æœå‹™ +default_presentation=16 + +[network_problems] +Name=Network Problems +Name[be]=Ð¡ÐµÑ‚ÐºÐ°Ð²Ñ‹Ñ Ð¿Ñ€Ð°Ð±Ð»ÐµÐ¼Ñ‹ +Name[bg]=Мрежови проблеми +Name[bn]=নেটওয়ারà§à¦• সমসà§à¦¯à¦¾ +Name[br]=Kudennoù rouedad +Name[bs]=Mrežni problemi +Name[ca]=Problemes de xarxa +Name[cs]=Problémy se sítí +Name[da]=Netværksproblemer +Name[de]=Netzwerkprobleme +Name[el]=ΠÏοβλήματα δικτÏου +Name[eo]=Retproblemoj +Name[es]=Problemas de red +Name[et]=Võrguprobleemid +Name[eu]=Sare-arazoak +Name[fa]=مسئله‌های شبکه +Name[fi]=Verkko-ongelma +Name[fr]=Problèmes réseaux +Name[ga]=Fadhbanna Líonra +Name[gl]=Problemas na rede +Name[he]=בעיות רשת +Name[hu]=Hálózati hibák +Name[is]=Netvandamál +Name[it]=Problemi di rete +Name[ja]=ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã®å•é¡Œ +Name[ka]=ქსელის პრáƒáƒ‘ლემები +Name[kk]=Желідегі мәÑелелер +Name[km]=បញ្ហា​បណ្ដាញ +Name[lt]=Tinklo problemos +Name[nb]=Nettverksproblemer +Name[nds]=Nettwarkproblemen +Name[ne]=सञà¥à¤œà¤¾à¤² समसà¥à¤¯à¤¾ +Name[nl]=Netwerkproblemen +Name[nn]=Nettverksproblem +Name[pa]=ਨੈੱਟਵਰਕ ਸਮੱਸਿਆ +Name[pl]=Problemy sieci +Name[pt]=Problemas na Rede +Name[pt_BR]=Problemas na Rede +Name[ru]=Сбой Ñети +Name[sk]=SieÅ¥ové problémy +Name[sl]=Omrežne težave +Name[sr]=Проблеми мреже +Name[sr@Latn]=Problemi mreže +Name[sv]=Nätverksproblem +Name[tr]=AÄŸ Problemleri +Name[uk]=Проблеми в мережі +Name[uz]=Tarmoq muammolari +Name[uz@cyrillic]=Тармоқ муаммолари +Name[zh_CN]=网络故障 +Name[zh_HK]=網絡å•é¡Œ +Name[zh_TW]=網路å•é¡Œ +Comment=The network is experiencing problems +Comment[be]=Сетка не можа Ñправіцца з праблемамі +Comment[bg]=Мрежови проблеми +Comment[bn]=নেটওয়ারà§à¦• সমসà§à¦¯à¦¾ অনà§à¦­à¦¬ করছে +Comment[bs]=DoÅ¡lo je do problema na mreži +Comment[ca]=La xarxa està tenint problemes +Comment[cs]=Síť má problémy +Comment[da]=Netværket har for øjeblikket problemer +Comment[de]=Das Netzwerk scheint derzeit gestört zu sein +Comment[el]=ΠαÏουσιάστηκαν Ï€Ïοβλήματα στο δίκτυο +Comment[es]=La red sufre problemas +Comment[et]=Võrguga on mingeid probleeme +Comment[eu]=Sareak arazoak ditu +Comment[fa]=شبکه، مسائل را آزمایش می‌کند +Comment[fi]=Verkossa on ongelmia +Comment[fr]=Le réseau rencontre des problèmes +Comment[gl]=A rede está experimentando problemas +Comment[he]=הרשת חווה בעיות +Comment[hu]=Hiba lépett fel a hálózaton +Comment[is]=Það er vandamál með netið +Comment[it]=Ci sono dei problemi di rete +Comment[ja]=ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã«å•é¡ŒãŒç™ºç”Ÿã—ã¦ã„ã¾ã™ +Comment[ka]=ქსელს პრáƒáƒ‘ლემები áƒáƒ¥áƒ•áƒ¡ +Comment[kk]=Желіде бір мәÑелелер орын алды +Comment[km]=បណ្ដាញ​កំពុង​ជួបប្រទះ​បញ្ហា +Comment[lt]=Tinkle yra problemų +Comment[nb]=Nettverket har problemer nÃ¥ +Comment[nds]=As dat lett gifft dat Nettwarkproblemen +Comment[ne]=सञà¥à¤œà¤¾à¤²à¤²à¥‡ समसà¥à¤¯à¤¾ अनà¥à¤­à¤µ गरिरहेको छ +Comment[nl]=Het netwerk ondervindt problemen +Comment[nn]=Det er problem i nettverket +Comment[pl]=W sieci wystÄ™pujÄ… problemy +Comment[pt]=A rede está com problemas +Comment[pt_BR]=A rede está com problemas +Comment[ru]=Ð’ Ñети возникли неполадки +Comment[sk]=SieÅ¥ zakúša problémy +Comment[sl]=V omrežju se pojavljajo težave +Comment[sr]=Мрежа има проблема +Comment[sr@Latn]=Mreža ima problema +Comment[sv]=Nätverket har för närvarande problem +Comment[tr]=AÄŸ problemleri var +Comment[uk]=Ð’ мережі виникли проблеми +Comment[zh_CN]=网络é‡åˆ°é—®é¢˜ +Comment[zh_HK]=網絡發生å•é¡Œ +Comment[zh_TW]=網路有å•é¡Œ +default_presentation=16 + +[server_error] +Name=Server Internal Error +Name[be]=Ð£Ð½ÑƒÑ‚Ñ€Ð°Ð½Ð°Ñ Ð¿Ð°Ð¼Ñ‹Ð»ÐºÐ° Ñервера +Name[bg]=Вътрешна грешка на Ñървъра +Name[bn]=সারà§à¦­à¦¾à¦° অভà§à¦¯à¦¨à§à¦¤à¦°à§€à¦¨ তà§à¦°à§à¦Ÿà¦¿ +Name[bs]=Interna greÅ¡ka servera +Name[ca]=Error intern del servidor +Name[cs]=Interní chyba serveru +Name[da]=Intern fejl i serveren +Name[de]=Interner Serverfehler +Name[el]=ΕσωτεÏικό σφάλμα του εξυπηÏετητή +Name[eo]=Servil-interna eraro +Name[es]=Error interno del servidor +Name[et]=Serveri sisemine viga +Name[eu]=Zerbitzariaren barne-errorea +Name[fa]=خطای درونی کارساز +Name[fi]=Palvelimen sisäinen virhe +Name[fr]=Erreur interne du serveur +Name[gl]=Erro Interno do Servidor +Name[hu]=BelsÅ‘ kiszolgálóhiba +Name[is]=Innri villa í þjóni +Name[it]=Errore interno del server +Name[ja]=サーãƒã®å†…部エラー +Name[ka]=სერვერის შიდრშეცდáƒáƒ›áƒ +Name[kk]=Сервердегі ішкі қате +Name[km]=កំហុស​ážáž¶áž„​ក្នុង​ម៉ាស៊ីន​បម្រើ +Name[lt]=VidinÄ— serverio klaida +Name[nb]=Intern tjenerfeil +Name[nds]=Serverintern Fehler +Name[ne]=सरà¥à¤­à¤°à¤•à¥‹ आनà¥à¤¤à¤°à¥€à¤• तà¥à¤°à¥à¤Ÿà¤¿ +Name[nl]=Interne serverfout +Name[nn]=Intern tenarfeil +Name[pa]=ਸਰਵਰ ਅੰਦਰੂਨੀ ਗਲਤੀ +Name[pl]=WewnÄ™trzny bÅ‚Ä…d serwera +Name[pt]=Erro Interno do Servidor +Name[pt_BR]=Erro interno do Servidor +Name[ru]=ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ° Ñервера +Name[sk]=Interná chyba servera +Name[sl]=Notranja napaka strežnika +Name[sr]=Интерна грешка Ñервера +Name[sr@Latn]=Interna greÅ¡ka servera +Name[sv]=Internt fel i servern +Name[tr]=Sunucu İç Hatası +Name[uk]=Ð’Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° Ñервера +Name[zh_CN]=æœåŠ¡å™¨å†…部错误 +Name[zh_HK]=伺æœå™¨å…§éƒ¨éŒ¯èª¤ +Name[zh_TW]=伺æœå™¨å…§éƒ¨éŒ¯èª¤ +Comment=A service internal error has occurred +Comment[be]=ÐдбылаÑÑ ÑžÐ½ÑƒÑ‚Ñ€Ð°Ð½Ð°Ñ Ð¿Ð°Ð¼Ñ‹Ð»ÐºÐ° Ñервера +Comment[bg]=Възникна вътрешна грешка на Ñървъра +Comment[bn]=à¦à¦•à¦Ÿà¦¿ সারà§à¦­à¦¿à¦¸ অভà§à¦¯à¦¨à§à¦¤à¦°à§€à¦¨ তà§à¦°à§à¦Ÿà¦¿ ঘটেছে +Comment[bs]=Desila se interna greÅ¡ka servisa +Comment[ca]=Hi ha hagut un error intern del servei +Comment[cs]=Nastala interní chyba služby +Comment[da]=En intern fejl for tjenesten er opstÃ¥et +Comment[de]=Bei diesem Dienst ist ein interner Serverfehler aufgetreten. +Comment[el]=ΠαÏουσιάστηκε ένα εσωτεÏικό σφάλμα της υπηÏεσίας +Comment[es]=Ocurrió un error interno en el servicio +Comment[et]=Tekkis teenuse sisemine viga +Comment[eu]=Zerbitzuaren barne-errore bat gertatu da +Comment[fa]=یک خطای درونی رخ داده است +Comment[fi]=Tapahtui palvelun sisäinen virhe +Comment[fr]=Une erreur interne au service s'est produite +Comment[gl]=Ocorreu un erro interno do servicio +Comment[hu]=BelsÅ‘ hiba történt a szolgáltatásban +Comment[is]=Innri villa í þjónustu hefur átt sér stað +Comment[it]=Si è verificato un errore interno del servizio +Comment[ja]=サービス内部エラーãŒç™ºç”Ÿã—ã¾ã—㟠+Comment[ka]=სერვერის შიდრშეცდáƒáƒ›áƒ +Comment[kk]=Қызметте бір ішкі қате пайда болды +Comment[km]=កំហុស​ážáž¶áž„​ក្នុង​សáŸážœáž¶â€‹áž”ាន​កើážâ€‹áž¡áž¾áž„ +Comment[lt]=Ä®vyko vidine serverio klaida +Comment[nb]=Det har oppstÃ¥tt en intern feil ved tjenesten +Comment[nds]=Dat hett binnen den Deenst en Fehler geven +Comment[ne]=सरà¥à¤­à¤°à¤•à¥‹ आनà¥à¤¤à¤°à¥€à¤• तà¥à¤°à¥à¤Ÿà¤¿ देखापरà¥à¤¯à¥‹ +Comment[nl]=Er deed zich een interne fout op de server voor +Comment[nn]=Det har skjedd ein feil internt i tenesta +Comment[pl]=WystÄ…piÅ‚ bÅ‚Ä…d wewnÄ™trzny usÅ‚ugi +Comment[pt]=Ocorreu um erro interno do serviço +Comment[pt_BR]=Ocorreu um erro interno no serviço +Comment[ru]=ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ° Ñлужбы +Comment[sk]=Nastala interná chyba servera +Comment[sl]=PriÅ¡lo je do notranje napake pri storitvi +Comment[sr]=Дошло јо до интерне грешке ÑервиÑа +Comment[sr@Latn]=DoÅ¡lo jo do interne greÅ¡ke servisa +Comment[sv]=Ett internt fel har inträffat i tjänsten +Comment[tr]=Serviste iç hata oluÅŸtu +Comment[uk]=ТрапилаÑÑŒ Ð²Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° Ñлужби +Comment[uz]=Xizmatda ichki xato roÊ»y berdi +Comment[uz@cyrillic]=Хизматда ички хато рўй берди +Comment[zh_CN]=å‘生了æœåŠ¡å†…部错误 +Comment[zh_HK]=æœå‹™ç™¼ç”Ÿå…§éƒ¨éŒ¯èª¤ +Comment[zh_TW]=æœå‹™ç™¼ç”Ÿå…§éƒ¨éŒ¯èª¤ +default_presentation=16 + +[buzz_nudge] +Name=Buzz/Nudge +Name[bg]=Сбутване +Name[bn]=গà§à¦žà§à¦œà¦¨/গà§à¦à¦¤à§‹ +Name[ca]=Truca/Avisa +Name[cs]=Å touchanec +Name[da]=Opringning/Puf +Name[el]=Βομβητής/ειδοποίηση +Name[es]=Zumbido +Name[et]=Mõmin/müks +Name[eu]=Burrumbada/Ukondokada +Name[fr]=Vibration +Name[hu]=Figyelemfelhívó +Name[is]=Ãta við +Name[it]=Buzz/Trillo +Name[ja]=ブザー/注æ„å–šèµ· +Name[kk]=Қоңырау +Name[km]=សន្ទនា​រក ឬ កáŸáŸ‡áž€áŸ€ážœ +Name[lt]=Skambutis/KumÅ¡telÄ—jimas +Name[nds]=Anstoot +Name[ne]=बज/नज +Name[nn]=Pirk +Name[pl]=Pobudka/kuksaniec +Name[pt]=Apitar/Tocar +Name[pt_BR]=Pedir Atenção (Buzinar/Tremer) +Name[sl]=Brnenje/dregnenje +Name[sr]=Зврц/Гуркање +Name[sr@Latn]=Zvrc/Gurkanje +Name[sv]=PÃ¥ringning/knuff +Name[tr]=Sesli Uyarı/Uyarı +Name[uk]=Гудок/поштовх +Name[zh_CN]=é—ªå±æŒ¯åŠ¨ +Name[zh_HK]=響è²/æ示 +Name[zh_TW]=呼å«/來電震動 +Comment=A contact has sent you a buzz/nudge. +Comment[bg]=Контакт ви изпрати Ñбутване +Comment[bn]=যোগাযোগ তালিকাভà§à¦•à§à¦¤ à¦à¦•à¦œà¦¨ আপনাকে à¦à¦•à¦Ÿà¦¿ গà§à¦žà§à¦œà¦¨/গà§à¦à¦¤à§‹ পাঠিয়েছে। +Comment[bs]=Kontakt vam je poslao buzz/nudge. +Comment[ca]=Un contacte us ha enviat una trucada/avís. +Comment[cs]=Osoba vám poslala Å¡Å¥ouchanec :-) +Comment[da]=En kontakt har sendt dig en buzz/nudge. +Comment[de]=Ein Benutzer macht auf sich aufmerksam. +Comment[el]=Μια επαφή μόλις σας έστειλα έναν βομβητή/ειδοποίηση. +Comment[es]=Un contacto le ha mandado un zumbido +Comment[et]=Kontakt müksas sind. +Comment[eu]=Kontaktu batek burrumbada/ukondokada bat bidali dizu. +Comment[fa]=تماسی برای شما یک buzz/nudge ارسال کرده است. +Comment[fr]=Un contact vous a envoyé une vibration. +Comment[hu]=Egy partner figyelemfelhívó üzenetet küldött. +Comment[is]=Notandi hefur ýtt við þér. +Comment[it]=Un contatto ti ha inviato un buzz/trillo. +Comment[ja]=コンタクトãŒã¤ã¤ã„ã¦ã„ã¾ã™ +Comment[ka]=მეგáƒáƒ‘áƒáƒ áƒ›áƒ გáƒáƒ›áƒáƒ’იგზáƒáƒ•áƒœáƒáƒ— buzz/nudge. +Comment[kk]=Сізге қонырау жіберілді. +Comment[km]=ទំនាក់​ទំនង​មួយ​បាន​កáŸáŸ‡áž€áŸ€ážœ ឬ សន្ទនា​រក​អ្នក ។ +Comment[lt]=Kontaktas siunÄia jums skambutį/kumÅ¡telÄ—jimÄ…. +Comment[nb]=En kontakt har sendt deg en buzz/nudge +Comment[nds]=En Bruker will wat vun Di. +Comment[ne]=समà¥à¤ªà¤°à¥à¤•à¤²à¥‡ तपाईà¤à¤²à¤¾à¤ˆ बज/नज पठायो । +Comment[nl]=Een contact heeft u een buzz/nudge gestuurd. +Comment[nn]=Ein kontakt har pirka borti deg. +Comment[pl]=Użytkownik wysÅ‚aÅ‚ ci pobudkÄ™/kuksaÅ„ca. +Comment[pt]=Um contacto enviou-lhe um toque/apito. +Comment[pt_BR]=Um contato lhe enviou um pedido de atenção. +Comment[ru]=Вам отправили buzz/nudge. +Comment[sk]=Kontakt Vám poslal buzz/nudge. +Comment[sl]=Uporabnik vam je poslal brnenje/dregnenje. +Comment[sr]=Контакт вам је поÑлао зврц/гуркање. +Comment[sr@Latn]=Kontakt vam je poslao zvrc/gurkanje. +Comment[sv]=En kontakt har ringt eller knuffat dig. +Comment[tr]=Birisi size bir titreÅŸim gönderdi. +Comment[uk]=Контакт надіÑлав вам гудок/поштовх. +Comment[zh_CN]=è”系人å‘您å‘é€äº†é—ªå±æŒ¯åŠ¨ã€‚ +Comment[zh_HK]=有è¯çµ¡äººå‚³äº†éŸ¿è²æˆ–æ示給您。 +Comment[zh_TW]=è¯çµ¡äººå°æ‚¨é€å‡ºå‘¼å«/來電震動 +default_sound=Kopete_Received.ogg +default_presentation=17 diff --git a/kopete/kopete/groupkabcselectorwidget.ui b/kopete/kopete/groupkabcselectorwidget.ui new file mode 100644 index 00000000..fe0223ba --- /dev/null +++ b/kopete/kopete/groupkabcselectorwidget.ui @@ -0,0 +1,91 @@ + +GroupKABCSelectorWidget + + + GroupKABCSelectorWidget + + + + 0 + 0 + 487 + 80 + + + + + unnamed + + + 0 + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + layout13 + + + + unnamed + + + + kabcLabel + + + Addressbook entry: + + + kabcCombo + + + + + widAddresseeLink + + + + + groupLabel + + + &Group + + + groupCombo + + + + + groupCombo + + + + + + + + + + Kopete::UI::AddressBookLinkWidget +
    addressbooklinkwidget.h
    +
    +
    + + klineedit.h + kpushbutton.h + +
    diff --git a/kopete/kopete/hi128-app-kopete.png b/kopete/kopete/hi128-app-kopete.png new file mode 100644 index 00000000..c42fe9f0 Binary files /dev/null and b/kopete/kopete/hi128-app-kopete.png differ diff --git a/kopete/kopete/hi16-app-kopete.png b/kopete/kopete/hi16-app-kopete.png new file mode 100644 index 00000000..7889b585 Binary files /dev/null and b/kopete/kopete/hi16-app-kopete.png differ diff --git a/kopete/kopete/hi22-app-kopete.png b/kopete/kopete/hi22-app-kopete.png new file mode 100644 index 00000000..6bd90e32 Binary files /dev/null and b/kopete/kopete/hi22-app-kopete.png differ diff --git a/kopete/kopete/hi32-app-kopete.png b/kopete/kopete/hi32-app-kopete.png new file mode 100644 index 00000000..cfd03ba7 Binary files /dev/null and b/kopete/kopete/hi32-app-kopete.png differ diff --git a/kopete/kopete/hi48-app-kopete.png b/kopete/kopete/hi48-app-kopete.png new file mode 100644 index 00000000..0118a598 Binary files /dev/null and b/kopete/kopete/hi48-app-kopete.png differ diff --git a/kopete/kopete/hi64-app-kopete.png b/kopete/kopete/hi64-app-kopete.png new file mode 100644 index 00000000..7b49bb96 Binary files /dev/null and b/kopete/kopete/hi64-app-kopete.png differ diff --git a/kopete/kopete/hisc-app-kopete2.svgz b/kopete/kopete/hisc-app-kopete2.svgz new file mode 100644 index 00000000..7a154365 Binary files /dev/null and b/kopete/kopete/hisc-app-kopete2.svgz differ diff --git a/kopete/kopete/kconf_update/Makefile.am b/kopete/kopete/kconf_update/Makefile.am new file mode 100644 index 00000000..dc808c00 --- /dev/null +++ b/kopete/kopete/kconf_update/Makefile.am @@ -0,0 +1,32 @@ +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_COMPAT $(all_includes) + +update_DATA = kopete-pluginloader.upd kopete-account-kconf_update.upd \ + kopete-pluginloader2.upd kopete-jabberproxytype-kconf_update.upd \ + kopete-jabberpriorityaddition-kconf_update.upd kopete-nameTracking.upd +update_SCRIPTS = kopete-pluginloader.pl kopete-account-kconf_update.sh \ + kopete-pluginloader2.sh kopete-jabberproxytype-kconf_update.sh \ + kopete-jabberpriorityaddition-kconf_update.sh kopete-account-0.10.pl +updatedir = $(kde_datadir)/kconf_update + +# The Qt app cannot go into kde_datadir, that is not portable. +# install to kde_bindir/kconf_update_bin instead. +# KDE 3.2 will allow kconf_update scripts to run directly from there, +# but for us that's too late. Use the .sh script as a workaround. +kconf_PROGRAMS = kopete-account-kconf_update kopete-pluginloader2-kconf_update \ + kopete-nameTracking-kconf_update +kconfdir = $(libdir)/kconf_update_bin + +kopete_account_kconf_update_SOURCES = kopete-account-kconf_update.cpp +kopete_account_kconf_update_LDFLAGS = $(all_libraries) $(KDE_RPATH) +kopete_account_kconf_update_LDADD = $(LIB_QT) + +kopete_pluginloader2_kconf_update_SOURCES = kopete-pluginloader2.cpp +kopete_pluginloader2_kconf_update_LDFLAGS = $(all_libraries) $(KDE_RPATH) +kopete_pluginloader2_kconf_update_LDADD = $(LIB_QT) + +kopete_nameTracking_kconf_update_SOURCES = kopete-nameTracking.cpp +kopete_nameTracking_kconf_update_LDFLAGS = $(all_libraries) $(KDE_RPATH) +kopete_nameTracking_kconf_update_LDADD = $(LIB_QT) $(LIB_KDECORE) + +# vim: set noet: + diff --git a/kopete/kopete/kconf_update/kopete-account-0.10.pl b/kopete/kopete/kconf_update/kopete-account-0.10.pl new file mode 100755 index 00000000..3925a52f --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-account-0.10.pl @@ -0,0 +1,26 @@ +#!/usr/bin/perl -w +# Olivier Goffart +# License: GPL + +use strict; + +# This script rename old plugin datas key. +# It remove the PlguinData_PLUGINID_ prefix from keys. + +# read the whole config file +my $currentGroup = ""; +my %configFile; +while ( <> ) { + chomp; # eat the trailing '\n' + next if ( /^$/ ); # skip empty lines + next if ( /^\#/ ); # skip comments + if ( /^\[/ ) { # group begin + $currentGroup = $_; + next; + } elsif ( $currentGroup =~ /^\[Account_/ and /^PluginData\_.+_(.+)=(.+)$/ ) + { + print "$currentGroup\n$1=$2\n"; + my ($key,$value) = split /=/; + print "# DELETE $currentGroup$key\n"; + } +} diff --git a/kopete/kopete/kconf_update/kopete-account-kconf_update.cpp b/kopete/kopete/kconf_update/kopete-account-kconf_update.cpp new file mode 100644 index 00000000..fe1c3351 --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-account-kconf_update.cpp @@ -0,0 +1,251 @@ +/* + kconf_update app for migrating kopete 0.6.x accounts to 0.7. Code is + not up to my normal standards, but it does the job, and since it's + supposed to run exactly once on each system that's good enough for me :) + + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +static QTextStream qcin ( stdin, IO_ReadOnly ); +static QTextStream qcout( stdout, IO_WriteOnly ); +static QTextStream qcerr( stderr, IO_WriteOnly ); + +// Group cache. Yes, I know global vars are ugly :) +bool needFlush = false; +QString accountId; +QString password; +QString autoConnect; +QString protocol; +QMap pluginData; + +// Global vars to hold separate IRC vars until we have read all of them +QString ircNick; +QString ircServer; +QString ircPort; + +/* + * Function for (en/de)crypting strings for config file, taken from KMail + * Author: Stefan Taferner + */ +QString cryptStr(const QString &aStr) +{ + QString result; + for (unsigned int i = 0; i < aStr.length(); i++) + result += (aStr[i].unicode() < 0x20) ? aStr[i] : + QChar(0x1001F - aStr[i].unicode()); + return result; +} + +void parseGroup( const QString &group, const QString &rawLine ) +{ + // Groups that are converted can almost certainly be removed entirely + + if ( group == "MSN" || group == "ICQ" || group == "Oscar" || group == "Gadu" || group == "Jabber" || group == "IRC" ) + { + accountId = "EMPTY"; + autoConnect = "true"; + + if ( group == "Oscar" ) + protocol = "AIMProtocol"; + else + protocol = group + "Protocol"; + + password = QString::null; + pluginData.clear(); + + needFlush = true; + + qcout << "# DELETEGROUP [" << group << "]" << endl; + } + else + { + // Groups we don't convert. Output the raw line instead. + qcout << rawLine << endl; + } +} + +void parseKey( const QString &group, const QString &key, const QString &value, const QString &rawLine ) +{ + //qcerr << "*** group='" << group << "'" << endl; + if ( group == "MSN" ) + { + if ( key == "UserID" ) + accountId = value; + else if ( key == "Password" ) + password = value; + else if ( key == "AutoConnect" ) + autoConnect = value; + else if ( key == "Nick" ) + pluginData[ "displayName" ] = value; + + // All other keys are ignored for MSN, as these apply to stuff that's + // now in libkopete (and the main app) instead. + } + else if ( group == "ICQ" ) + { + if ( key == "UIN" ) + accountId = value; + else if ( key == "Password" ) + password = value; + else if ( key == "AutoConnect" ) + autoConnect = value; + else if ( key == "Nick" ) + pluginData[ "NickName" ] = value; + else if ( key == "Server" ) + pluginData[ key ] = value; + else if ( key == "Port" ) + pluginData[ key ] = value; + } + else if ( group == "Oscar" ) + { + if ( key == "ScreenName" ) + accountId = value; + else if ( key == "Password" ) + password = value; + else if ( key == "Server" ) + pluginData[ key ] = value; + else if ( key == "Port" ) + pluginData[ key ] = value; + } + else if ( group == "Jabber" ) + { + if ( key == "UserID" ) + accountId = value; + else if ( key == "Password" ) + password = value; + if ( key == "Server" || + key == "Port" || key == "UseSSL" || key == "Resource" ) + pluginData[ key ] = value; + } + else if ( group == "Gadu" ) + { + if ( key == "UIN" ) + accountId = value; + else if ( key == "Password" ) + password = value; + else if ( key == "Nick" ) + pluginData[ "displayName" ] = value; + } + else if ( group == "IRC" ) + { + if ( key == "Nickname" ) + ircNick = value; + if ( key == "Server" ) + ircServer = value; + if ( key == "Port" ) + ircPort = value; + if ( accountId == "EMPTY" && + !ircNick.isEmpty( ) && !ircServer.isEmpty() && + !ircPort.isEmpty() ) + { +#if QT_VERSION < 0x030200 + accountId = QString::fromLatin1( "%1@%2:%3" ).arg( ircNick ).arg( ircServer ).arg( ircPort ); +#else + accountId = QString::fromLatin1( "%1@%2:%3" ).arg( ircNick, ircServer, ircPort ); +#endif + } + } + /* + fixme: insert all other plugins here - martijn + */ + else if ( key == "Modules" ) + { + QString newValue = value; + newValue.replace ( ".plugin", ".desktop" ); + qcout << "Plugins=" << newValue; + } + else + { + // groups we don't convert. output the raw line instead. + qcout << rawLine << endl; + } +} + +void flushData( const QString &group ) +{ + + qcout << "[Account_" << protocol << "_" << accountId << "]" << endl; + qcout << "Protocol=" << protocol << endl; + + if( group == "Jabber" ) + qcout << "AccountId=" << accountId << "@" << pluginData["Server"] << endl; + else + qcout << "AccountId=" << accountId << endl; + + qcout << "Password=" << cryptStr( password ) << endl; + qcout << "AutoConnect=" << autoConnect << endl; + + QMap::ConstIterator it; + for ( it = pluginData.begin(); it != pluginData.end(); ++it ) + qcout << "PluginData_" << protocol << "_" << it.key() << "=" << it.data() << endl; + +} + +int main() +{ + qcin.setEncoding( QTextStream::UnicodeUTF8 ); + qcout.setEncoding( QTextStream::UnicodeUTF8 ); + + QString curGroup; + + QRegExp groupRegExp( "^\\[(.*)\\]" ); + QRegExp keyRegExp( "^([a-zA-Z0-9:, _-]*)\\s*=\\s*(.*)\\s*" ); + QRegExp commentRegExp( "^(#.*)?$" ); + + while ( !qcin.atEnd() ) + { + QString line = qcin.readLine(); + + if ( commentRegExp.exactMatch( line ) ) + { + // We found a comment, leave unchanged + qcout << line << endl; + } + else if ( groupRegExp.exactMatch( line ) ) + { + // We found the start of a group, parse it + if ( needFlush ) + { + // ... but we were already working on a group, so finish what + // we were doing - flush existing group first + flushData ( curGroup ); + needFlush = false; + } + + curGroup = groupRegExp.capturedTexts()[ 1 ]; + parseGroup( curGroup, line ); + } + else if ( keyRegExp.exactMatch( line ) ) + { + // We found the a key line + parseKey( curGroup, keyRegExp.capturedTexts()[ 1 ], keyRegExp.capturedTexts()[ 2 ], line ); + } + else + { + qcerr << "** Unknown input line: " << line << endl; + } + } + + if ( needFlush ) + flushData ( curGroup ); + + return 0; +} + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/kconf_update/kopete-account-kconf_update.sh b/kopete/kopete/kconf_update/kopete-account-kconf_update.sh new file mode 100644 index 00000000..4b2e086e --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-account-kconf_update.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +IFS=: +SUFF=kconf_update_bin/kopete-account-kconf_update +for path in `kde-config --path lib`; do + if test -x "$path/$SUFF"; then + exec "$path/$SUFF" + fi +done + diff --git a/kopete/kopete/kconf_update/kopete-account-kconf_update.upd b/kopete/kopete/kconf_update/kopete-account-kconf_update.upd new file mode 100644 index 00000000..ed1f3ac7 --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-account-kconf_update.upd @@ -0,0 +1,9 @@ +Id=kopete0.7/r1 +File=kopeterc +Script=kopete-account-kconf_update.sh,sh + +#remove the PluginData_ProtocolID_ prefix from config keys +Id=kopete0.10/r1 +File=kopeterc +Script=kopete-account-0.10.pl,perl + diff --git a/kopete/kopete/kconf_update/kopete-jabberpriorityaddition-kconf_update.sh b/kopete/kopete/kconf_update/kopete-jabberpriorityaddition-kconf_update.sh new file mode 100644 index 00000000..2ba47416 --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-jabberpriorityaddition-kconf_update.sh @@ -0,0 +1,2 @@ +#!/bin/sh +sed -e 's/^\(PluginData_JabberProtocol_Resource=.*\)$/\1\nPluginData_JabberProtocol_Priority=5/' diff --git a/kopete/kopete/kconf_update/kopete-jabberpriorityaddition-kconf_update.upd b/kopete/kopete/kconf_update/kopete-jabberpriorityaddition-kconf_update.upd new file mode 100644 index 00000000..8e761e6f --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-jabberpriorityaddition-kconf_update.upd @@ -0,0 +1,4 @@ +Id=kopete0.9/r1 +File=kopeterc +Script=kopete-jabberpriorityaddition-kconf_update.sh,sh + diff --git a/kopete/kopete/kconf_update/kopete-jabberproxytype-kconf_update.sh b/kopete/kopete/kconf_update/kopete-jabberproxytype-kconf_update.sh new file mode 100644 index 00000000..3cbf8a55 --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-jabberproxytype-kconf_update.sh @@ -0,0 +1,2 @@ +#!/bin/sh +sed "s/PluginData_JabberProtocol_ProxyType=SOCKS4/PluginData_JabberProtocol_ProxyType=SOCKS/" | sed "s/PluginData_JabberProtocol_ProxyType=SOCKS5/PluginData_JabberProtocol_ProxyType=SOCKS/" diff --git a/kopete/kopete/kconf_update/kopete-jabberproxytype-kconf_update.upd b/kopete/kopete/kconf_update/kopete-jabberproxytype-kconf_update.upd new file mode 100644 index 00000000..c39ce69b --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-jabberproxytype-kconf_update.upd @@ -0,0 +1,4 @@ +Id=kopete0.9/r1 +File=kopeterc +Script=kopete-jabberproxytype-kconf_update.sh,sh + diff --git a/kopete/kopete/kconf_update/kopete-nameTracking.cpp b/kopete/kopete/kconf_update/kopete-nameTracking.cpp new file mode 100644 index 00000000..391e5132 --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-nameTracking.cpp @@ -0,0 +1,135 @@ +/* + kconf_update app for updating the contactlist format ( <= 0.9.0) for MetaContacts to + track the name of a subcontact. + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#include + +static QTextStream qcerr( stderr, IO_WriteOnly ); + +int main() +{ + KInstance* inst = new KInstance( "Update script" ); + QString filename = locateLocal( "data", QString::fromLatin1( "kopete/contactlist.xml" ) ); + + // Load contact list & save backup. + QFile contactListFile( filename ); + contactListFile.open( IO_ReadOnly ); + QDomDocument contactList; + contactList.setContent( &contactListFile ); + contactListFile.close(); + QDir().rename( filename, filename + QString::fromLatin1( ".bak" ) ); + + // parse the XML file + QDomElement list = contactList.documentElement(); + QDomElement mcElement = list.firstChild().toElement(); + + while( !mcElement.isNull() ) + { + + // update all the MetaContacts + if( mcElement.tagName() == QString::fromLatin1("meta-contact") ) + { + QDomElement displayName; + QDomElement subcontact; + + QDomElement elem = mcElement.firstChild().toElement(); + while( !elem.isNull() ) + { + if( elem.tagName() == QString::fromLatin1( "display-name" ) ) + displayName = elem; + if( elem.tagName() == QString::fromLatin1( "plugin-data" ) ) + { + // check if it's a contact by checking for "protocol" substring in the tag, + // and the presence of a contactId child element. + QString pluginId = elem.attribute( QString::fromLatin1( "plugin-id" ) ); + bool isProtocol = ( pluginId.contains( "protocol", false ) > 0 ); // case-insensitive search + bool hasContactId = false; + QDomNode field = elem.firstChild(); + while( !field.isNull() ) + { + QDomElement fieldElem = field.toElement(); + + if( !fieldElem.isNull() && + fieldElem.tagName() == QString::fromLatin1( "plugin-data-field" ) && + fieldElem.attribute( QString::fromLatin1( "key" ) ) == QString::fromLatin1( "contactId" ) ) + { + hasContactId = true; + break; + } + field = field.nextSibling(); + } + + if( isProtocol && hasContactId ) + subcontact = elem; + } + + elem = elem.nextSibling().toElement(); + } // end while + + // check if we're even tracking the subcontact's name + // if displayName.isNull(), it simply won't find the attribute; no harm done + bool tracking = + ( displayName.attribute( QString::fromLatin1( "trackChildNameChanges" ), + QString::fromLatin1( "0" ) ) == QString::fromLatin1( "1" ) ); + if( !displayName.isNull() && !subcontact.isNull() && tracking ) + { + // collect info + QString nsCID; + QString nsPID; + QString nsAID; + + nsPID = subcontact.attribute( QString::fromLatin1( "plugin-id" ) ); + QDomNode field = subcontact.firstChild(); + while( !field.isNull() ) + { + QDomElement fieldElem = field.toElement(); + + if( !fieldElem.isNull() && fieldElem.tagName() == QString::fromLatin1( "plugin-data-field" ) ) + { + if( fieldElem.attribute( QString::fromLatin1( "key" ) ) == QString::fromLatin1( "contactId" ) ) + nsCID = fieldElem.text(); + if( fieldElem.attribute( QString::fromLatin1( "key" ) ) == QString::fromLatin1( "accountId" ) ) + nsAID = fieldElem.text(); + } + field = field.nextSibling(); + } + + // create the tracking info + displayName.setAttribute( QString::fromLatin1( "nameSourceContactId" ), nsCID ); + displayName.setAttribute( QString::fromLatin1( "nameSourcePluginId" ), nsPID ); + displayName.setAttribute( QString::fromLatin1( "nameSourceAccountId" ), nsAID ); + } + } + + mcElement = mcElement.nextSibling().toElement(); + } + + // Save converted contactlist + contactListFile.open( IO_WriteOnly ); + QTextStream stream( &contactListFile ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + stream << contactList.toString( 4 ); + contactListFile.flush(); + contactListFile.close(); + + return 0; +} + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/kconf_update/kopete-nameTracking.upd b/kopete/kopete/kconf_update/kopete-nameTracking.upd new file mode 100644 index 00000000..da0a0e2d --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-nameTracking.upd @@ -0,0 +1,3 @@ +Id=kopete0.9/r1 +File=kopeterc +Script=kopete-nameTracking-kconf_update diff --git a/kopete/kopete/kconf_update/kopete-pluginloader.pl b/kopete/kopete/kconf_update/kopete-pluginloader.pl new file mode 100755 index 00000000..19709d3a --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-pluginloader.pl @@ -0,0 +1,28 @@ +#!/usr/bin/perl +my $logging = "false"; +my $moduleLine; + +while( my $line = <> ) +{ + if( $line =~ /LogAll/ ) + { + $logging = "true"; + } + if( $line =~ /^Modules=/ ) + { + $moduleLine = $line; + } +} + +$moduleLine =~ s/^Modules/Plugins/; +$moduleLine =~ s/\.plugin/\.desktop/g; +$moduleLine =~ s/oscar/aim/; +if ( $logging == "true" ) +{ + chomp $moduleLine; + $moduleLine = $moduleLine . ",history.desktop\n"; +} +print $moduleLine; + +print "# DELETE Modules\n"; + diff --git a/kopete/kopete/kconf_update/kopete-pluginloader.upd b/kopete/kopete/kconf_update/kopete-pluginloader.upd new file mode 100644 index 00000000..cc739414 --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-pluginloader.upd @@ -0,0 +1,4 @@ +Id=kopete0.7/r1 +File=kopeterc +Script=kopete-pluginloader.pl,perl + diff --git a/kopete/kopete/kconf_update/kopete-pluginloader2.cpp b/kopete/kopete/kconf_update/kopete-pluginloader2.cpp new file mode 100644 index 00000000..af8daa69 --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-pluginloader2.cpp @@ -0,0 +1,89 @@ +/* + kconf_update app for migrating the list of loaded plugins in + kopete 0.7.x to the new KPluginSelector format. + + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include + +static QTextStream qcin ( stdin, IO_ReadOnly ); +static QTextStream qcout( stdout, IO_WriteOnly ); +static QTextStream qcerr( stderr, IO_WriteOnly ); + +void parseKey( const QString &group, const QString &key, const QString &value, const QString &rawLine ) +{ + //qcerr << "*** group='" << group << "'" << endl; + if ( group.isEmpty() && key == "Plugins" ) + { + QStringList plugins = QStringList::split( ',', value ); + if ( !plugins.isEmpty() ) + { + qcout << "[Plugins]" << endl; + for ( QStringList::Iterator it = plugins.begin(); it != plugins.end(); ++it ) + qcout << "kopete_" << ( *it ).remove( ".desktop" ) << "Enabled=true" << endl; + } + qcout << "# DELETE []Plugins" << endl; + } + else + { + // groups we don't convert. output the raw line instead. + qcout << rawLine << endl; + } +} + +int main() +{ + qcin.setEncoding( QTextStream::UnicodeUTF8 ); + qcout.setEncoding( QTextStream::UnicodeUTF8 ); + + QString curGroup; + + QRegExp groupRegExp( "^\\[(.*)\\]" ); + QRegExp keyRegExp( "^([a-zA-Z0-9:, _-]*)\\s*=\\s*(.*)\\s*" ); + QRegExp commentRegExp( "^(#.*)?$" ); + + while ( !qcin.atEnd() ) + { + QString line = qcin.readLine(); + + if ( commentRegExp.exactMatch( line ) ) + { + // We found a comment, leave unchanged + qcout << line << endl; + } + else if ( groupRegExp.exactMatch( line ) ) + { + // We found the start of a group, leave unchanged + qcout << line << endl; + + curGroup = groupRegExp.capturedTexts()[ 1 ]; + } + else if ( keyRegExp.exactMatch( line ) ) + { + // We found the a key line + parseKey( curGroup, keyRegExp.capturedTexts()[ 1 ], keyRegExp.capturedTexts()[ 2 ], line ); + } + else + { + qcerr << "** Unknown input line: " << line << endl; + } + } + + return 0; +} + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/kconf_update/kopete-pluginloader2.sh b/kopete/kopete/kconf_update/kopete-pluginloader2.sh new file mode 100644 index 00000000..e058dcf1 --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-pluginloader2.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +IFS=: +SUFF=kconf_update_bin/kopete-pluginloader2-kconf_update +for path in `kde-config --path lib`; do + if test -x "$path/$SUFF"; then + exec "$path/$SUFF" + fi +done + diff --git a/kopete/kopete/kconf_update/kopete-pluginloader2.upd b/kopete/kopete/kconf_update/kopete-pluginloader2.upd new file mode 100644 index 00000000..3461c7a7 --- /dev/null +++ b/kopete/kopete/kconf_update/kopete-pluginloader2.upd @@ -0,0 +1,4 @@ +Id=kopete0.8/r1 +File=kopeterc +Script=kopete-pluginloader2.sh,sh + diff --git a/kopete/kopete/kimiface.h b/kopete/kopete/kimiface.h new file mode 100644 index 00000000..3d599013 --- /dev/null +++ b/kopete/kopete/kimiface.h @@ -0,0 +1,197 @@ +/* + kimiface.h - KDE Instant Messenger DCOP Interface + + Copyright (c) 2004 Will Stephenson + + This library 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 library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef KIMIFACE_H +#define KIMIFACE_H + +#include +#include +#include +#include + +/** + * Generic DCOP interface for KDE instant messenger applications + * Note one omission of this interface is the lack of control over the range of values used for protocols' names. + * @since 3.3 + * @author Will Stephenson + */ +class KIMIface : virtual public DCOPObject +{ + K_DCOP + +k_dcop: +// ACCESSORS +// contact list + /** + * Obtain a list of IM-contactable entries in the KDE + * address book. + * @return a list of KABC uids. + */ + virtual QStringList allContacts() = 0; + /** + * Obtain a list of KDE address book entries who are + * currently reachable. + * @return a list of KABC uids who can receive a message, even if online. + */ + virtual QStringList reachableContacts() = 0; + /** + * Obtain a list of KDE address book entries who are + * currently online. + * @return a list of KABC uids who are online with unspecified presence. + */ + virtual QStringList onlineContacts() = 0; + /** + * Obtain a list of KDE address book entries who may + * receive file transfers. + * @return a list of KABC uids capable of file transfer. + */ + virtual QStringList fileTransferContacts() = 0; + +// individual + /** + * Confirm if a given KABC uid is known to KIMProxy + * @param uid the KABC uid you are interested in. + * @return whether one of the chat programs KIMProxy talks to knows of this KABC uid. + */ + virtual bool isPresent( const QString & uid ) = 0; + /** + * Obtain the IM app's idea of the contact's display name + * Useful if KABC lookups may be too slow + * @param KABC uid. + * @return The corresponding display name. + */ + virtual QString displayName( const QString & uid ) = 0; + /** + * Obtain the IM presence as a i18ned string for the specified addressee + * @param uid the KABC uid you want the presence for. + * @return the i18ned string describing presence. + */ + virtual QString presenceString( const QString & uid ) = 0; + /** + * Obtain the IM presence as a number (see KIMIface) for the specified addressee + * @param uid the KABC uid you want the presence for. + * @return a numeric representation of presence - currently one of 0 (Unknown), 1 (Offline), 2 (Connecting), 3 (Away), 4 (Online) + */ + virtual int presenceStatus( const QString & uid ) = 0; + /** + * Indicate if a given uid can receive files + * @param uid the KABC uid you are interested in. + * @return Whether the specified addressee can receive files. + */ + virtual bool canReceiveFiles( const QString & uid ) = 0; + /** + * Some media are unidirectional (eg, sending SMS via a web interface). + * @param uid the KABC uid you are interested in. + * @return Whether the specified addressee can respond. + */ + virtual bool canRespond( const QString & uid ) = 0; + /** + * Get the KABC uid corresponding to the supplied IM address + * Protocols should be + * @param contactId the protocol specific identifier for the contact, eg UIN for ICQ, screenname for AIM, nick for IRC. + * @param protocol the protocol, eg one of "AIMProtocol", "MSNProtocol", "ICQProtocol", + * @return a KABC uid or null if none found/ + */ + virtual QString locate( const QString & contactId, const QString & protocol ) = 0; +// metadata + /** + * Obtain the icon representing IM presence for the specified addressee + * @param uid the KABC uid you want the presence for. + * @return a pixmap representing the uid's presence. + */ + virtual QPixmap icon( const QString & uid ) = 0; + /** + * Get the supplied addressee's current context (home, work, or any). + * @param uid the KABC uid you want the context for. + * @return A QString describing the context, or null if not supported. + */ + virtual QString context( const QString & uid ) = 0; +// App capabilities + /** + * Discover what protocols the application supports + * @return the set of protocols that the application supports + */ + virtual QStringList protocols() = 0; + +// ACTORS + /** + * Send a single message to the specified addressee + * Any response will be handled by the IM client as a normal + * conversation. + * @param uid the KABC uid you want to chat with. + * @param message the message to send them. + */ + virtual void messageContact( const QString &uid, const QString& message ) = 0; + + /** + * Open a chat to a contact, and optionally set some initial text + */ + virtual void messageNewContact( const QString &contactId, const QString &protocol ) = 0; + + /** + * Start a chat session with the specified addressee + * @param uid the KABC uid you want to chat with. + */ + virtual void chatWithContact( const QString &uid ) = 0; + + /** + * Send the file to the contact + * @param uid the KABC uid you are sending to. + * @param sourceURL a @ref KURL to send. + * @param altFileName an alternate filename describing the file + * @param fileSize file size in bytes + */ + virtual void sendFile(const QString &uid, const KURL &sourceURL, + const QString &altFileName = QString::null, uint fileSize = 0) = 0; + +// MUTATORS +// Contact list + /** + * Add a contact to the contact list + * @param contactId the protocol specific identifier for the contact, eg UIN for ICQ, screenname for AIM, nick for IRC. + * @param protocol the protocol, eg one of "AIMProtocol", "MSNProtocol", "ICQProtocol", ... + * @return whether the add succeeded. False may signal already present, protocol not supported, or add operation not supported. + */ + virtual bool addContact( const QString &contactId, const QString &protocol ) = 0; +// SIGNALS +k_dcop_signals: + /** + * Indicates that a contact's presence has changed + * @param uid the contact whose presence changed. + * @param appId the dcop application id of the program the signal originates from. + * @param presence the new numeric presence @ref presenceStatus + */ + void contactPresenceChanged( QString uid, QCString appId, int presence ); +}; + +#endif + + + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/kimifaceimpl.cpp b/kopete/kopete/kimifaceimpl.cpp new file mode 100644 index 00000000..dd1bd962 --- /dev/null +++ b/kopete/kopete/kimifaceimpl.cpp @@ -0,0 +1,395 @@ +/* + kimifaceimpl.cpp - Kopete DCOP Interface + + Copyright (c) 2004 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetecontactlist.h" +#include "kopetechatsession.h" +#include "kopetemetacontact.h" +#include "kopeteprotocol.h" +#include "kopetepluginmanager.h" +#include "kopeteuiglobal.h" +#include "kopetecontact.h" + +#include + +#include "kimifaceimpl.h" + +KIMIfaceImpl::KIMIfaceImpl() : DCOPObject( "KIMIface" ), QObject() +{ + connect( Kopete::ContactList::self(), + SIGNAL( metaContactAdded( Kopete::MetaContact * ) ), + SLOT( slotMetaContactAdded( Kopete::MetaContact * ) ) ); +} + +KIMIfaceImpl::~KIMIfaceImpl() +{ +} + +QStringList KIMIfaceImpl::allContacts() +{ + QStringList result; + QPtrList list = Kopete::ContactList::self()->metaContacts(); + QPtrListIterator it( list ); + for( ; it.current(); ++it ) + { + if ( !it.current()->metaContactId().contains(':') ) + result.append( it.current()->metaContactId() ); + } + + return result; +} + +QStringList KIMIfaceImpl::reachableContacts() +{ + QStringList result; + QPtrList list = Kopete::ContactList::self()->metaContacts(); + QPtrListIterator it( list ); + for( ; it.current(); ++it ) + { + if ( it.current()->isReachable() && !it.current()->metaContactId().contains(':') ) + result.append( it.current()->metaContactId() ); + } + + return result; +} + +QStringList KIMIfaceImpl::onlineContacts() +{ + QStringList result; + QPtrList list = Kopete::ContactList::self()->metaContacts(); + QPtrListIterator it( list ); + for( ; it.current(); ++it ) + { + if ( it.current()->isOnline() && !it.current()->metaContactId().contains(':') ) + result.append( it.current()->metaContactId() ); + } + + return result; +} + +QStringList KIMIfaceImpl::fileTransferContacts() +{ + QStringList result; + QPtrList list = Kopete::ContactList::self()->metaContacts(); + QPtrListIterator it( list ); + for( ; it.current(); ++it ) + { + if ( it.current()->canAcceptFiles() && !it.current()->metaContactId().contains(':') ) + result.append( it.current()->metaContactId() ); + } + + return result; +} + +bool KIMIfaceImpl::isPresent( const QString & uid ) +{ + Kopete::MetaContact *mc; + mc = Kopete::ContactList::self()->metaContact( uid ); + + return ( mc != 0 ); +} + + +QString KIMIfaceImpl::displayName( const QString & uid ) +{ + Kopete::MetaContact *mc; + mc = Kopete::ContactList::self()->metaContact( uid ); + QString name; + if ( mc ) + name = mc->displayName(); + + return name; +} + +int KIMIfaceImpl::presenceStatus( const QString & uid ) +{ + //kdDebug( 14000 ) << k_funcinfo << endl; + int p = -1; + Kopete::MetaContact *m = Kopete::ContactList::self()->metaContact( uid ); + if ( m ) + { + Kopete::OnlineStatus status = m->status(); + switch ( status.status() ) + { + case Kopete::OnlineStatus::Unknown: + p = 0; + break; + case Kopete::OnlineStatus::Offline: + case Kopete::OnlineStatus::Invisible: + p = 1; + break; + case Kopete::OnlineStatus::Connecting: + p = 2; + break; + case Kopete::OnlineStatus::Away: + p = 3; + break; + case Kopete::OnlineStatus::Online: + p = 4; + break; + } + } + return p; +} + +QString KIMIfaceImpl::presenceString( const QString & uid ) +{ + //kdDebug( 14000 ) << "KIMIfaceImpl::presenceString" << endl; + QString p; + Kopete::MetaContact *m = Kopete::ContactList::self()->metaContact( uid ); + if ( m ) + { + Kopete::OnlineStatus status = m->status(); + p = status.description(); + kdDebug( 14000 ) << "Got presence for " << uid << " : " << p.ascii() << endl; + } + else + { + kdDebug( 14000 ) << "Couldn't find MC: " << uid << endl;; + p = QString(); + } + return p; +} + +bool KIMIfaceImpl::canReceiveFiles( const QString & uid ) +{ + Kopete::MetaContact *mc; + mc = Kopete::ContactList::self()->metaContact( uid ); + + if ( mc ) + return mc->canAcceptFiles(); + else + return false; +} + +bool KIMIfaceImpl::canRespond( const QString & uid ) +{ + Kopete::MetaContact *mc; + mc = Kopete::ContactList::self()->metaContact( uid ); + + if ( mc ) + { + QPtrList list = mc->contacts(); + QPtrListIterator it( list ); + Kopete::Contact *contact; + while ( ( contact = it.current() ) != 0 ) + { + ++it; + if ( contact->isOnline() && contact->protocol()->pluginId() != "SMSProtocol" ) + return true; + } + } + return false; +} + +QString KIMIfaceImpl::locate( const QString & contactId, const QString & protocolId ) +{ + Kopete::MetaContact *mc = locateProtocolContact( contactId, protocolId ); + if ( mc ) + return mc->metaContactId(); + else + return QString::null; +} + +Kopete::MetaContact * KIMIfaceImpl::locateProtocolContact( const QString & contactId, const QString & protocolId ) +{ + Kopete::MetaContact *mc = 0; + // find a matching protocol + Kopete::Protocol *protocol = dynamic_cast( Kopete::PluginManager::self()->plugin( protocolId ) ); + + if ( protocol ) + { + // find its accounts + QDict accounts = Kopete::AccountManager::self()->accounts( protocol ); + QDictIterator it( accounts ); + for( ; it.current(); ++it ) + { + Kopete::Contact *c = Kopete::ContactList::self()->findContact( protocolId, it.currentKey(), contactId ); + if (c) + { + mc=c->metaContact(); + break; + } + } + } + return mc; +} + +QPixmap KIMIfaceImpl::icon( const QString & uid ) +{ + Kopete::MetaContact *m = Kopete::ContactList::self()->metaContact( uid ); + QPixmap p; + if ( m ) + p = SmallIcon( m->statusIcon() ); + return p; +} + +QString KIMIfaceImpl::context( const QString & uid ) +{ + // TODO: support context + // shush warning + QString myUid = uid; + + return QString::null; +} + +QStringList KIMIfaceImpl::protocols() +{ + QValueList protocols = Kopete::PluginManager::self()->availablePlugins( "Protocols" ); + QStringList protocolList; + for ( QValueList::Iterator it = protocols.begin(); it != protocols.end(); ++it ) + protocolList.append( (*it)->name() ); + + return protocolList; +} + +void KIMIfaceImpl::messageContact( const QString &uid, const QString& messageText ) +{ + Kopete::MetaContact *m = Kopete::ContactList::self()->metaContact( uid ); + if ( m ) + { + Kopete::Contact * c = m->preferredContact(); + Kopete::ChatSession * manager = c->manager(Kopete::Contact::CanCreate); + c->manager( Kopete::Contact::CanCreate )->view( true ); + Kopete::Message msg = Kopete::Message( manager->myself(), manager->members(), messageText, + Kopete::Message::Outbound, Kopete::Message::PlainText); + manager->sendMessage( msg ); + } + else + unknown( uid ); +} + +void KIMIfaceImpl::messageNewContact( const QString &contactId, const QString &protocol ) +{ + Kopete::MetaContact *mc = locateProtocolContact( contactId, protocol ); + if ( mc ) + mc->sendMessage(); +} + +void KIMIfaceImpl::chatWithContact( const QString &uid ) +{ + Kopete::MetaContact *m = Kopete::ContactList::self()->metaContact( uid ); + if ( m ) + m->execute(); + else + unknown( uid ); +} + +void KIMIfaceImpl::sendFile(const QString &uid, const KURL &sourceURL, + const QString &altFileName, uint fileSize) +{ + Kopete::MetaContact *m = Kopete::ContactList::self()->metaContact( uid ); + if ( m ) + m->sendFile( sourceURL, altFileName, fileSize ); + // else, prompt to create a new MC associated with UID +} + +bool KIMIfaceImpl::addContact( const QString &contactId, const QString &protocolId ) +{ + // find a matching protocol + Kopete::Protocol *protocol = dynamic_cast( Kopete::PluginManager::self()->plugin( protocolId ) ); + + if ( protocol ) + { + // find its accounts + QDict accounts = Kopete::AccountManager::self()->accounts( protocol ); + QDictIterator it( accounts ); + Kopete::Account *ac = it.toFirst(); + if ( ac ) + { + ac->addContact( contactId ); + return true; + } + } + return false; +} + +void KIMIfaceImpl::slotMetaContactAdded( Kopete::MetaContact *mc ) +{ + connect( mc, SIGNAL( onlineStatusChanged( Kopete::MetaContact *, Kopete::OnlineStatus::StatusType ) ), + SLOT( slotContactStatusChanged( Kopete::MetaContact * ) ) ); +} + +void KIMIfaceImpl::slotContactStatusChanged( Kopete::MetaContact *mc ) +{ + if ( !mc->metaContactId().contains( ':' ) ) + { + int p = -1; + Kopete::OnlineStatus status = mc->status(); + switch ( status.status() ) + { + case Kopete::OnlineStatus::Unknown: + p = 0; + break; + case Kopete::OnlineStatus::Offline: + case Kopete::OnlineStatus::Invisible: + p = 1; + break; + case Kopete::OnlineStatus::Connecting: + p = 2; + break; + case Kopete::OnlineStatus::Away: + p = 3; + break; + case Kopete::OnlineStatus::Online: + p = 4; + break; + } + // tell anyone who's listening over DCOP + contactPresenceChanged( mc->metaContactId(), kapp->name(), p ); +/* QByteArray params; + QDataStream stream(params, IO_WriteOnly); + stream << mc->metaContactId(); + stream << kapp->name(); + stream << p; + kapp->dcopClient()->emitDCOPSignal( "contactPresenceChanged( QString, QCString, int )", params );*/ + } +} + +void KIMIfaceImpl::unknown( const QString &uid ) +{ + // warn the user that the KABC contact associated with this UID isn't known to kopete, + // either associate an existing contact with KABC or add a new one using the ACW. + KABC::AddressBook *bk = KABC::StdAddressBook::self( false ); + KABC::Addressee addr = bk->findByUid( uid ); + if ( addr.isEmpty() ) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, i18n("Another KDE application tried to use Kopete for instant messaging, but Kopete could not find the specified contact in the KDE address book."), i18n( "Not Found in Address Book" ) ); + } + else + { + QString apology = i18n( "Translators: %1 is the name of a person taken from the KDE address book, who Kopete doesn't know about. Kopete must either be told that an existing contact in Kopete is this person, or add a new contact for them", + "

    The KDE Address Book has no instant messaging information for

    %1.

    If he/she is already present in the Kopete contact list, indicate the correct addressbook entry in their properties.

    Otherwise, add a new contact using the Add Contact wizard.

    " ); + apology = apology.arg( addr.realName() ); + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Information, apology, i18n( "No Instant Messaging Address" ) ); + } +} + +#include "kimifaceimpl.moc" + diff --git a/kopete/kopete/kimifaceimpl.h b/kopete/kopete/kimifaceimpl.h new file mode 100644 index 00000000..ff8c3611 --- /dev/null +++ b/kopete/kopete/kimifaceimpl.h @@ -0,0 +1,108 @@ +/* + kimifaceimpl.cpp - Kopete DCOP Interface + + Copyright (c) 2004 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef KIMIFACEIMPL_H +#define KIMIFACEIMPL_H + +#include +#include "kimiface.h" + +namespace Kopete +{ +class MetaContact; +} + +class KIMIfaceImpl : public QObject, public KIMIface +{ + Q_OBJECT +public: + KIMIfaceImpl(); + ~KIMIfaceImpl(); + + QStringList allContacts(); + QStringList reachableContacts(); + QStringList onlineContacts(); + QStringList fileTransferContacts(); + +// individual + bool isPresent( const QString &uid ); + QString displayName( const QString &uid ); + QString presenceString( const QString &uid ); + int presenceStatus( const QString &uid ); + bool canReceiveFiles( const QString &uid ); + bool canRespond( const QString &uid ); + QString locate( const QString &contactId, const QString &protocol ); +// metadata + QPixmap icon( const QString &uid ); + QString context( const QString &uid ); +// App capabilities + QStringList protocols(); + +// ACTORS + /** + * Message a contact by their metaContactId, aka their uid in KABC. + */ + void messageContact( const QString &uid, const QString& message ); + + /** + * Open a chat to a contact, and optionally set some initial text + */ + void messageNewContact( const QString &contactId, const QString &protocolId ); + + /** + * Message a contact by their metaContactId, aka their uid in KABC. + */ + void chatWithContact( const QString &uid ); + + /** + * Send the file to the contact + */ + void sendFile(const QString &uid, const KURL &sourceURL, + const QString &altFileName = QString::null, uint fileSize = 0); + +// MUTATORS +// Contact list + bool addContact( const QString &contactId, const QString &protocolId ); +// SIGNALS + /** + * DCOP Signal used to notify + * external apps of status changes. + */ + void contactStatusChanged( const QString &uid); + +protected: + void unknown( const QString &uid ); +protected slots: + void slotMetaContactAdded( Kopete::MetaContact *mc ); + void slotContactStatusChanged( Kopete::MetaContact *mc ); + +private: + Kopete::MetaContact *locateProtocolContact( const QString & contactId, const QString & protocolId ); +}; + +#endif + + + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/kopete.desktop b/kopete/kopete/kopete.desktop new file mode 100644 index 00000000..eabdae57 --- /dev/null +++ b/kopete/kopete/kopete.desktop @@ -0,0 +1,126 @@ +[Desktop Entry] +MimeType=application/x-kopete-emoticons;application/x-icq; +Name=Kopete +Name[bn]=কপেট +Name[hi]=के-ऑपà¥à¤Ÿà¥€ +Name[ne]=कोपेट +Name[pa]=ਕੋਪੀਟੀ +GenericName=Instant Messenger +GenericName[ar]=المرسال الÙوري +GenericName[be]=Праграма імгненных паведамленнÑÑž +GenericName[bg]=Ð¡ÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² реално време +GenericName[bn]=তাতà§à¦•à§à¦·à¦£à¦¿à¦• বারà§à¦¤à¦¾à¦¬à¦¾à¦¹à¦• +GenericName[br]=Posteler a-benn-kaer +GenericName[bs]=Instant poruke +GenericName[ca]=Missatger a l'instant +GenericName[cs]=Komunikátor +GenericName[cy]=Negesydd Chwim +GenericName[el]=Στιγμιαίος αποστολέας μηνυμάτων +GenericName[eo]=RapidmesaÄilo +GenericName[es]=Mensajería instantánea +GenericName[et]=Kiirsuhtlemisrakendus +GenericName[eu]=Berehalako mezularitza +GenericName[fa]=پیام‌رسان Ùوری +GenericName[fi]=Pikaviestinohjelma +GenericName[fr]=Messagerie instantanée +GenericName[ga]=Clár teachtaireachtaí meandaracha +GenericName[gl]=Mensaxería Instantánea +GenericName[he]=תוכנת ×ž×¡×¨×™× ×ž×™×“×™×™× +GenericName[hi]=इंसटैंट मैसेंजर +GenericName[hr]=Instant poruke +GenericName[hu]=Internetes csevegÅ‘ +GenericName[is]=Spjallforrit +GenericName[it]=Messaggistica istantanea +GenericName[ja]=インスタントメッセンジャー +GenericName[kk]=Жедел хабарлаÑу +GenericName[km]=កម្មវិធី​ផ្ញើ​សារ​បន្ទាន់ +GenericName[lt]=Momentinių žinuÄių klientas +GenericName[mk]=ИнÑтант глаÑник +GenericName[nb]=Hurtigmelding +GenericName[nds]=Kortnarichtenprogramm +GenericName[ne]=ततà¥à¤•à¤¾à¤² मेसेनà¥à¤œà¤° +GenericName[nl]=Instant messenger +GenericName[nn]=Lynmeldingsprogram +GenericName[pa]=ਮੌਕਾ ਸà©à¨¨à©‡à¨¹à¨¾à¨•à¨¾à¨° +GenericName[pl]=Komunikator internetowy +GenericName[pt]=Mensageiro Instantâneo +GenericName[pt_BR]=Mensageiro Instantâneo +GenericName[ru]=Программа обмена ÑообщениÑми +GenericName[rw]=Intumwa y'Akokanya +GenericName[se]=Å leaÄ‘gadiehtoprográmma +GenericName[sl]=TakojÅ¡ni sporoÄilnik +GenericName[sr]=Брзи глаÑник +GenericName[sr@Latn]=Brzi glasnik +GenericName[sv]=Direktmeddelandeklient +GenericName[ta]=உடனடி தூதர௠+GenericName[tg]=Пайёмбари Фаврӣ +GenericName[tr]=Anında HaberleÅŸme Hizmeti +GenericName[uk]=Програма Ð´Ð»Ñ Ð¼Ð¸Ñ‚Ñ‚Ñ”Ð²Ð¾Ð³Ð¾ зв'Ñзку +GenericName[uz]=Xabar almashish vositasi +GenericName[uz@cyrillic]=Хабар алмашиш воÑитаÑи +GenericName[zh_CN]=å³æ—¶é€šè®¯å®¢æˆ·ç¨‹åº +GenericName[zh_HK]=å³æ™‚é€šè¨Šç¨‹å¼ +GenericName[zh_TW]=å³æ™‚訊æ¯å®¢æˆ¶ç«¯ç¨‹å¼ +Comment=Instant Messenger +Comment[ar]=المرسال الÙوري +Comment[be]=Праграма імгненных паведамленнÑÑž +Comment[bg]=Ð¡ÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² реално време +Comment[bn]=তাতà§à¦•à§à¦·à¦£à¦¿à¦• বারà§à¦¤à¦¾à¦¬à¦¾à¦¹à¦• +Comment[br]=Posteler a-benn-kaer +Comment[bs]=Instant poruke +Comment[ca]=Missatger a l'instant +Comment[cs]=Komunikátor +Comment[cy]=Negesydd Chwim +Comment[el]=Στιγμιαίος αποστολέας μηνυμάτων +Comment[eo]=RapidmesaÄilo +Comment[es]=Mensajería instantánea +Comment[et]=Kiirsuhtlusrakendus +Comment[eu]=Berehalako mezularitza +Comment[fa]=پیام‌رسان Ùوری +Comment[fi]=Pikaviestinohjelma +Comment[fr]=Messagerie instantanée +Comment[gl]=Mensaxería Instantánea +Comment[he]=תוכנת ×ž×¡×¨×™× ×ž×™×“×™×™× +Comment[hi]=इंसà¥à¤Ÿà¥ˆà¤‚ट मैसेंजर +Comment[hr]=Instant poruke +Comment[hu]=Azonnali üzenetküldÅ‘ +Comment[is]=Spjallforrit +Comment[it]=Messaggistica istantanea +Comment[ja]=インスタントメッセンジャー +Comment[kk]=Жедел хабарлаÑу бағдарламаÑÑ‹ +Comment[km]=កម្មវិធី​ផ្ញើ​សារ​បន្ទាន់ +Comment[lt]=Momentinių žinuÄių klientas +Comment[mk]=ИнÑтант глаÑник +Comment[nb]=hurtigmeldingssystem +Comment[nds]=Kortnarichtenprogramm +Comment[ne]=ततà¥à¤•à¤¾à¤² मेसेनà¥à¤œà¤° +Comment[nl]=Instant messenger +Comment[nn]=Lynmeldingsprogram +Comment[pl]=Komunikator +Comment[pt]=Mensageiro Instantâneo +Comment[pt_BR]=Mensageiro Instantâneo +Comment[ro]=Mesaje instantanee +Comment[ru]=Программа обмена ÑообщениÑми +Comment[se]=Instant Messenger-klienta +Comment[sl]=TakojÅ¡ni sporoÄilnik +Comment[sr]=Брзи глаÑник +Comment[sr@Latn]=Brzi glasnik +Comment[sv]=Direktmeddelandeklient +Comment[ta]=உடனடி தூதர௠+Comment[tg]=Пайёмбари Фаврӣ +Comment[tr]=Anında HaberleÅŸme Hizmeti +Comment[uk]=Програма Ð´Ð»Ñ Ð¼Ð¸Ñ‚Ñ‚Ñ”Ð²Ð¾Ð³Ð¾ зв'Ñзку +Comment[uz]=Xabar almashish vositasi +Comment[uz@cyrillic]=Хабар алмашиш воÑитаÑи +Comment[zh_CN]=å³æ—¶é€šè®¯å®¢æˆ·ç¨‹åº +Comment[zh_HK]=å³æ™‚é€šè¨Šç¨‹å¼ +Comment[zh_TW]=å³æ™‚訊æ¯å®¢æˆ¶ç«¯ç¨‹å¼ +Exec=kopete -caption "%c" %i %m %u +Type=Application +DocPath=kopete/index.html +Terminal=false +Icon=kopete +Categories=Qt;KDE;Network;InstantMessaging; +X-DCOP-ServiceType=Unique +X-DCOP-ServiceName=kopete +ServiceTypes=DCOP/InstantMessenger diff --git a/kopete/kopete/kopeteaccountstatusbaricon.cpp b/kopete/kopete/kopeteaccountstatusbaricon.cpp new file mode 100644 index 00000000..05855b63 --- /dev/null +++ b/kopete/kopete/kopeteaccountstatusbaricon.cpp @@ -0,0 +1,60 @@ +/* + kopeteaccountstatusbaricon.cpp - Kopete Account StatusBar Dock Icon + + Copyright (c) 2001-2003 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopeteaccountstatusbaricon.h" +#include "qcursor.h" + +#include + +//#include + +KopeteAccountStatusBarIcon::KopeteAccountStatusBarIcon( Kopete::Account *acc, QWidget *parent, + const char *name ) +: QLabel( parent, name ) +{ +// kdDebug(14000) << "[KopeteAccountStatusBarIcon] Setting Initial Protocol Icon" << endl; + //setMask(initialPixmap->mask()); + //setPixmap( Kopete::OnlineStatus( Kopete::OnlineStatus::Unknown, 0, proto, 0, "status_unknown", QString::null, QString::null ).protocolIcon() ); + //setPixmap( proto->status().protocolIcon() ); + + setFixedSize ( 16, 16 ); + setCursor(QCursor(Qt::PointingHandCursor)); + show(); + + m_account = acc; +} + +KopeteAccountStatusBarIcon::~KopeteAccountStatusBarIcon() +{ +} + +void KopeteAccountStatusBarIcon::mousePressEvent( QMouseEvent *me ) +{ + if( me->button() == QEvent::RightButton ) + { + emit rightClicked( m_account, QPoint( me->globalX(), me->globalY() ) ); + } + else if( me->button() == QEvent::LeftButton ) + { + emit leftClicked( m_account, QPoint( me->globalX(), me->globalY() ) ); + } +} + +#include "kopeteaccountstatusbaricon.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/kopeteaccountstatusbaricon.h b/kopete/kopete/kopeteaccountstatusbaricon.h new file mode 100644 index 00000000..7c3034c9 --- /dev/null +++ b/kopete/kopete/kopeteaccountstatusbaricon.h @@ -0,0 +1,60 @@ +/* + kopeteaccounrstatusbaricon.h - Kopete Account StatusBar Dock Icon + + Copyright (c) 2001-2003 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEACCOUNTSTATUSBARICON_H +#define KOPETEACCOUNTSTATUSBARICON_H + +#include +#include +#include + +namespace Kopete +{ +class Account; +} + +/** + * @author Duncan Mac-Vicar P. + */ +class KopeteAccountStatusBarIcon : public QLabel +{ + Q_OBJECT + +public: + /** + * Create a statusbar icon. + */ + KopeteAccountStatusBarIcon( Kopete::Account *acc, QWidget *parent, + const char *name = 0 ); + + ~KopeteAccountStatusBarIcon(); + +signals: + void rightClicked( Kopete::Account *acc, const QPoint &p ); + void leftClicked( Kopete::Account *acc, const QPoint &p ); + +protected: + virtual void mousePressEvent( QMouseEvent *me ); + +private: + Kopete::Account *m_account; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/kopeteapplication.cpp b/kopete/kopete/kopeteapplication.cpp new file mode 100644 index 00000000..3a481d3f --- /dev/null +++ b/kopete/kopete/kopeteapplication.cpp @@ -0,0 +1,338 @@ +/* + kopete.cpp + + Kopete Instant Messenger Main Class + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2003 by Martijn Klingens + + Kopete (c) 2001-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopeteapplication.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "addaccountwizard.h" +#include "kabcpersistence.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetecommandhandler.h" +#include "kopetecontactlist.h" +#include "kopeteglobal.h" +#include "kopetemimesourcefactory.h" +#include "kopetemimetypehandler.h" +#include "kopetepluginmanager.h" +#include "kopeteprotocol.h" +#include "kopetestdaction.h" +#include "kopeteuiglobal.h" +#include "kopetewindow.h" +#include "kopeteprefs.h" +#include "kopeteviewmanager.h" +#include "videodevice.h" + +#ifdef HAVE_CONFIG_H +#include +#endif + +KopeteApplication::KopeteApplication() +: KUniqueApplication( true, true, true ) +{ + m_isShuttingDown = false; + m_mainWindow = new KopeteWindow( 0, "mainWindow" ); + + Kopete::PluginManager::self(); + + Kopete::UI::Global::setMainWidget( m_mainWindow ); + + /* + * FIXME: This is a workaround for a quite odd problem: + * When starting up kopete and the msn plugin gets loaded it can bring up + * a messagebox, in case the msg configuration is missing. This messagebox + * will result in a QApplication::enter_loop() call, an event loop is + * created. At this point however the loop_level is 0, because this is all + * still inside the KopeteApplication constructor, before the exec() call from main. + * When the messagebox is finished the loop_level will drop down to zero and + * QApplication thinks the application shuts down (this is usually the case + * when the loop_level goes down to zero) . So it emits aboutToQuit(), to + * which KApplication is connected and re-emits shutdown() , to which again + * KMainWindow (a KopeteWindow instance exists already) is connected. KMainWindow's + * shuttingDown() slot calls queryExit() which results in KopeteWindow::queryExit() + * calling unloadPlugins() . This of course is wrong and just shouldn't happen. + * The workaround is to simply delay the initialization of all this to a point + * where the loop_level is already > 0 . That is why I moved all the code from + * the constructor to the initialize() method and added this single-shot-timer + * setup. (Simon) + * + * Additionally, it makes the GUI appear less 'blocking' during startup, so + * there is a secondary benefit as well here. (Martijn) + */ + QTimer::singleShot( 0, this, SLOT( slotLoadPlugins() ) ); + + m_mimeFactory = new Kopete::MimeSourceFactory; + QMimeSourceFactory::addFactory( m_mimeFactory ); + + //Create the emoticon installer + m_emoticonHandler = new Kopete::EmoticonMimeTypeHandler; +} + +KopeteApplication::~KopeteApplication() +{ + kdDebug( 14000 ) << k_funcinfo << endl; + + delete m_mainWindow; + delete m_emoticonHandler; + delete m_mimeFactory; + //kdDebug( 14000 ) << k_funcinfo << "Done" << endl; +} + +void KopeteApplication::slotLoadPlugins() +{ + // we have to load the address book early, because calling this enters the Qt event loop when there are remote resources. + // The plugin manager is written with the assumption that Kopete will not reenter the event loop during plugin load, + // otherwise lots of things break as plugins are loaded, then contacts are added to incompletely initialised MCLVIs + Kopete::KABCPersistence::self()->addressBook(); + + //Create the command handler (looks silly) + Kopete::CommandHandler::commandHandler(); + + //Create the view manager + KopeteViewManager::viewManager(); + + Kopete::AccountManager::self()->load(); + Kopete::ContactList::self()->load(); + + KConfig *config = KGlobal::config(); + + // Parse command-line arguments + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + bool showConfigDialog = false; + + config->setGroup( "Plugins" ); + + /* FIXME: This is crap, if something purged that groups but your accounts + * are still working kopete will load the necessary plugins but still show the + * stupid accounts dialog (of course empty at that time because account data + * gets loaded later on). [mETz - 29.05.2004] + */ + if ( !config->hasGroup( "Plugins" ) ) + showConfigDialog = true; + + // Listen to arguments + /* + // TODO: conflicts with emoticon installer and the general meaning + // of %U in kopete.desktop + if ( args->count() > 0 ) + { + showConfigDialog = false; + for ( int i = 0; i < args->count(); i++ ) + Kopete::PluginManager::self()->setPluginEnabled( args->arg( i ), true ); + } + */ + + // Prevent plugins from loading? (--disable=foo,bar) + QStringList disableArgs = QStringList::split( ',', args->getOption( "disable" ) ); + for ( QStringList::ConstIterator it = disableArgs.begin(); it != disableArgs.end(); ++it ) + { + showConfigDialog = false; + Kopete::PluginManager::self()->setPluginEnabled( *it, false ); + } + + // Load some plugins exclusively? (--load-plugins=foo,bar) + if ( args->isSet( "load-plugins" ) ) + { + config->deleteGroup( "Plugins", true ); + showConfigDialog = false; + QStringList plugins = QStringList::split( ',', args->getOption( "load-plugins" ) ); + for ( QStringList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it ) + Kopete::PluginManager::self()->setPluginEnabled( *it, true ); + } + + config->sync(); + + // Disable plugins altogether? (--noplugins) + if ( !args->isSet( "plugins" ) ) + { + // If anybody reenables this I'll get a sword and make a nice chop-suy out + // of your body :P [mETz - 29.05.2004] + // This screws up kopeterc because there is no way to get the Plugins group back! + //config->deleteGroup( "Plugins", true ); + + showConfigDialog = false; + // pretend all plugins were loaded :) + QTimer::singleShot(0, this, SLOT( slotAllPluginsLoaded() )); + } + else + { + Kopete::PluginManager::self()->loadAllPlugins(); + } + + connect( Kopete::PluginManager::self(), SIGNAL( allPluginsLoaded() ), + this, SLOT( slotAllPluginsLoaded() )); + + if( showConfigDialog ) + { + // No plugins specified. Show the config dialog. + // FIXME: Although it's a bit stupid it is theoretically possible that a user + // explicitly configured Kopete to not load plugins on startup. In this + // case we don't want this dialog. We need some other config setting + // like a bool hasRunKopeteBefore or so to trigger the loading of the + // wizard. Maybe using the last run version number is more useful even + // as it also allows for other features. - Martijn + // FIXME: Possibly we need to influence the showConfigDialog bool based on the + // command line arguments processed below. But how exactly? - Martijn + // NB: the command line args are completely broken atm. + // I don't want to fix them for 3.5 as plugin loading will change for KDE4. - Will + AddAccountWizard *m_addwizard = new AddAccountWizard( Kopete::UI::Global::mainWidget(), "addAccountWizard", true, true ); + m_addwizard->exec(); + Kopete::AccountManager::self()->save(); + } +} + +void KopeteApplication::slotAllPluginsLoaded() +{ + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + // --noconnect not specified? + if ( args->isSet( "connect" ) && KopetePrefs::prefs()->autoConnect() ) + Kopete::AccountManager::self()->connectAll(); + + + // Handle things like '--autoconnect foo,bar --autoconnect foobar' + QCStringList connectArgsC = args->getOptionList( "autoconnect" ); + QStringList connectArgs; + + for ( QCStringList::ConstIterator it = connectArgsC.begin(); it != connectArgsC.end(); ++it ) + { + QStringList split = QStringList::split( ',', QString::fromLatin1( *it ) ); + + for ( QStringList::ConstIterator it2 = split.begin(); it2 != split.end(); ++it2 ) + { + connectArgs.append( *it2 ); + } + } + + for ( QStringList::ConstIterator i = connectArgs.begin(); i != connectArgs.end(); ++i ) + { + QRegExp rx( QString::fromLatin1( "([^\\|]*)\\|\\|(.*)" ) ); + rx.search( *i ); + QString protocolId = rx.cap( 1 ); + QString accountId = rx.cap( 2 ); + + if ( accountId.isEmpty() ) + { + if ( protocolId.isEmpty() ) + accountId = *i; + else + continue; + } + + QPtrListIterator it( Kopete::AccountManager::self()->accounts() ); + Kopete::Account *account; + while ( ( account = it.current() ) != 0 ) + { + ++it; + + if ( ( account->accountId() == accountId ) ) + { + if ( protocolId.isEmpty() || account->protocol()->pluginId() == protocolId ) + { + account->connect(); + break; + } + } + } + } + + // Parse any passed URLs/files + handleURLArgs(); +} + +int KopeteApplication::newInstance() +{ +// kdDebug(14000) << k_funcinfo << endl; + handleURLArgs(); + + /** + * The following three lines work around a problem that + * Kopete has since it has multiple main windows so + * qapp->mainWidget() returns 0, which breaks the normal + * KUniqueApplication::newInstance() behavior that raises + * the window when we run a new instance. + * + * 1. Set the main widget to the contact list window + * 2. Call KUniqueApplication::newInstance() + * 3. Set the main widget back to 0 + * + * This little workaround fixes the problem. -Matt + */ + + setMainWidget( m_mainWindow ); + int kUniqAppReturnCode = KUniqueApplication::newInstance(); + setMainWidget( 0L ); + + return kUniqAppReturnCode; +} + +void KopeteApplication::handleURLArgs() +{ + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); +// kdDebug(14000) << k_funcinfo << "called with " << args->count() << " arguments to handle." << endl; + + if ( args->count() > 0 ) + { + for ( int i = 0; i < args->count(); i++ ) + { + KURL u( args->url( i ) ); + if ( !u.isValid() ) + continue; + + Kopete::MimeTypeHandler::dispatchURL( u ); + } // END for() + } // END args->count() > 0 +} + +void KopeteApplication::quitKopete() +{ + kdDebug( 14000 ) << k_funcinfo << endl; + + m_isShuttingDown = true; + + // close all windows + QPtrListIterator it(*KMainWindow::memberList); + for (it.toFirst(); it.current(); ++it) + { + if ( !it.current()->close() ) + { + m_isShuttingDown = false; + break; + } + } +} + + +void KopeteApplication::commitData( QSessionManager &sm ) +{ + m_isShuttingDown = true; + KUniqueApplication::commitData( sm ); +} + +#include "kopeteapplication.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/kopeteapplication.h b/kopete/kopete/kopeteapplication.h new file mode 100644 index 00000000..9634adca --- /dev/null +++ b/kopete/kopete/kopeteapplication.h @@ -0,0 +1,94 @@ +/* + kopete.h + + Kopete Instant Messenger Main Class + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEAPPLICATION_H +#define KOPETEAPPLICATION_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +class KopeteWindow; +class QSessionManager; +class QMimeSourceFactory; + +namespace Kopete +{ + class MimeTypeHandler; +} + +/** + * @author Duncan Mac-Vicar P. + */ +class KopeteApplication : public KUniqueApplication +{ + Q_OBJECT + +public: + KopeteApplication(); + ~KopeteApplication(); + + /** + * Method to return whether or not we're shutting down + * or not at this point. + */ + bool isShuttingDown() const { return m_isShuttingDown; } + + virtual int newInstance(); + +public slots: + /** + * Quit Kopete, closing all the windows, which causes application shutdown + * This method marks Kopete as 'shutting down' to avoid + * showing the message box that Kopete will be left running in the + * system tray before calling qApp->quit(). + */ + void quitKopete(); + + virtual void commitData( QSessionManager &sm ); + /** + * Load all plugins + */ + void slotLoadPlugins(); + +private slots: + /** + * auto-connect + */ + void slotAllPluginsLoaded(); +private: + // The main window might get deleted behind our back (W_DestructiveClose), + // so use a guarded pointer + QGuardedPtr m_mainWindow; + bool m_isShuttingDown; + Kopete::MimeTypeHandler *m_emoticonHandler; + QMimeSourceFactory *m_mimeFactory; + +private: + void handleURLArgs(); +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/kopeteballoon.cpp b/kopete/kopete/kopeteballoon.cpp new file mode 100644 index 00000000..cee42240 --- /dev/null +++ b/kopete/kopete/kopeteballoon.cpp @@ -0,0 +1,186 @@ +/* + kopeteballoon.cpp - Nice Balloon + + Copyright (c) 2002 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + Portions of this code based on Kim Applet code + Copyright (c) 2000-2002 by Malte Starostik + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kopeteballoon.h" +#include "systemtray.h" +#include "kopeteprefs.h" + +KopeteActiveLabel::KopeteActiveLabel( QWidget *parent, const char *name ) + : KActiveLabel( parent, name ) +{ +} + +KopeteActiveLabel::KopeteActiveLabel( const QString& text, QWidget *parent, + const char *name ) : KActiveLabel( text, parent, name ) +{ +} + +void KopeteActiveLabel::openLink( const QString& link ) +{ + KURL url( link ); + QString protocol = url.protocol(); + + if ( protocol == "mailto" ) + kapp->invokeMailer(url); + else + { + if ( KProtocolInfo::protocolClass( protocol ) == ":internet" ) // http, ftp, etc. + new KRun( url, this ); + } +} + +KopeteBalloon::KopeteBalloon(const QString &text, const QString &pix) +: QWidget(0L, "KopeteBalloon", WStyle_StaysOnTop | WStyle_Customize | + WStyle_NoBorder | WStyle_Tool | WX11BypassWM) +{ + setCaption(""); + + QVBoxLayout *BalloonLayout = new QVBoxLayout(this, 22, + KDialog::spacingHint(), "BalloonLayout"); + + // BEGIN Layout1 + QHBoxLayout *Layout1 = new QHBoxLayout(BalloonLayout, + KDialog::spacingHint(), "Layout1"); + //QLabel *mCaption = new QLabel(text, this, "mCaption"); + KopeteActiveLabel *mCaption = new KopeteActiveLabel(text, this, "mCaption"); + mCaption->setPalette(QToolTip::palette()); + mCaption->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ); + + if (!pix.isEmpty()) + { + QLabel *mImage = new QLabel(this, "mImage"); + mImage->setScaledContents(FALSE); + mImage->setPixmap(locate("data", pix)); + + Layout1->addWidget(mImage); + } + Layout1->addWidget(mCaption); + // END Layout1 + + + // BEGIN Layout2 + QHBoxLayout *Layout2 = new QHBoxLayout(BalloonLayout, + KDialog::spacingHint(), "Layout2"); + QPushButton *mViewButton = new QPushButton(i18n("to view", "View"), this, + "mViewButton"); + QPushButton *mIgnoreButton = new QPushButton(i18n("Ignore"), this, + "mIgnoreButton"); + + Layout2->addStretch(); + Layout2->addWidget(mViewButton); + Layout2->addWidget(mIgnoreButton); + Layout2->addStretch(); + // END Layout2 + + setPalette(QToolTip::palette()); + setAutoMask(TRUE); + + connect(mViewButton, SIGNAL(clicked()), + this, SIGNAL(signalButtonClicked())); + connect(mViewButton, SIGNAL(clicked()), + this, SLOT(deleteLater())); + connect(mIgnoreButton, SIGNAL(clicked()), + this, SIGNAL(signalIgnoreButtonClicked())); + connect(mIgnoreButton, SIGNAL(clicked()), + this, SLOT(deleteLater())); + connect(mCaption, SIGNAL(linkClicked(const QString &)), + this, SIGNAL(signalIgnoreButtonClicked())); + connect(mCaption, SIGNAL(linkClicked(const QString &)), + this, SLOT(deleteLater())); + + KopetePrefs *p = KopetePrefs::prefs(); + // Autoclose balloon + if (p->balloonClose()) + QTimer::singleShot( p->balloonCloseDelay() * 1000, this, SIGNAL( signalTimeout( ) ) ); +} + +void KopeteBalloon::setAnchor(const QPoint &anchor) +{ + mAnchor = anchor; + updateMask(); +} + +void KopeteBalloon::updateMask() +{ + QRegion mask(10, 10, width() - 20, height() - 20); + + QPoint corners[8] = { + QPoint(width() - 50, 10), + QPoint(10, 10), + QPoint(10, height() - 50), + QPoint(width() - 50, height() - 50), + QPoint(width() - 10, 10), + QPoint(10, 10), + QPoint(10, height() - 10), + QPoint(width() - 10, height() - 10) + }; + + for (int i = 0; i < 4; ++i) + { + QPointArray corner; + corner.makeArc(corners[i].x(), corners[i].y(), 40, 40, + i * 16 * 90, 16 * 90); + corner.resize(corner.size() + 1); + corner.setPoint(corner.size() - 1, corners[i + 4]); + mask -= corner; + } + + // get screen-geometry for screen our anchor is on + // (geometry can differ from screen to screen! + QRect deskRect = KGlobalSettings::desktopGeometry(mAnchor); + + bool bottom = (mAnchor.y() + height()) > ((deskRect.y() + deskRect.height()-48)); + bool right = (mAnchor.x() + width()) > ((deskRect.x() + deskRect.width()-48)); + + QPointArray arrow(4); + arrow.setPoint(0, QPoint(right ? width() : 0, bottom ? height() : 0)); + arrow.setPoint(1, QPoint(right ? width() - 10 : 10, + bottom ? height() - 30 : 30)); + arrow.setPoint(2, QPoint(right ? width() - 30 : 30, + bottom ? height() - 10 : 10)); + arrow.setPoint(3, arrow[0]); + mask += arrow; + setMask(mask); + + move( right ? mAnchor.x() - width() : ( mAnchor.x() < 0 ? 0 : mAnchor.x() ), + bottom ? mAnchor.y() - height() : ( mAnchor.y() < 0 ? 0 : mAnchor.y() ) ); + + //kdDebug(14000) << k_funcinfo << "finalpos: x=" << x() << ", y=" << y() << endl; +} + +#include "kopeteballoon.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/kopeteballoon.h b/kopete/kopete/kopeteballoon.h new file mode 100644 index 00000000..1c30a8e4 --- /dev/null +++ b/kopete/kopete/kopeteballoon.h @@ -0,0 +1,77 @@ +/* + kopeteballoon.h - Nice Balloon + + Copyright (c) 2002 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + Portions of this code based on Kim Applet code + Copyright (c) 2000-2002 by Malte Starostik + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEBALLOON_H +#define KOPETEBALLOON_H + +#include +#include + +/** + * A class derived from KActiveLabel so we can handle how + * links are opened. + */ +class KopeteActiveLabel : public KActiveLabel +{ + Q_OBJECT + +public: + KopeteActiveLabel( QWidget *parent = 0, const char* name = 0 ); + KopeteActiveLabel( const QString& text, QWidget *parent = 0, const char* name = 0 ); + +public slots: + virtual void openLink( const QString &link ); +}; + + + +/** + * A little balloon for notifications + * + * @author Malte Starostik + * @author Duncan Mac-Vicar Prett + */ +class KopeteBalloon : public QWidget +{ + Q_OBJECT + +public: + KopeteBalloon(const QString &text, const QString &pic); +// KopeteBalloon(); + + void setAnchor(const QPoint &anchor); + +signals: + void signalButtonClicked(); + void signalIgnoreButtonClicked(); + void signalBalloonClicked(); + void signalTimeout(); + +protected: + virtual void updateMask(); + +private: + QPoint mAnchor; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/kopeteeditglobalidentitywidget.cpp b/kopete/kopete/kopeteeditglobalidentitywidget.cpp new file mode 100644 index 00000000..63357068 --- /dev/null +++ b/kopete/kopete/kopeteeditglobalidentitywidget.cpp @@ -0,0 +1,255 @@ +/* + kopeteeditglobalidentitywidget.cpp - Kopete Edit Global Identity widget + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopeteeditglobalidentitywidget.h" + +// Qt include +#include +#include +#include +#include +#include + +// KDE include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Kopete include +#include "kopeteglobal.h" +#include "kopetecontactlist.h" +#include "kopetemetacontact.h" + + +ClickableLabel::ClickableLabel(QWidget *parent, const char *name) + : QLabel(parent, name) +{ + setCursor(QCursor(Qt::PointingHandCursor)); +} + +void ClickableLabel::mouseReleaseEvent(QMouseEvent *event) +{ + if(event->button() == Qt::LeftButton) + { + emit clicked(); + event->accept(); + } +} + +class KopeteEditGlobalIdentityWidget::Private +{ +public: + Private() : myself(0L), labelPicture(0L), lineNickname(0L), lineStatusMessage(0L), mainLayout(0L), iconSize(22), + lastNickname("") + {} + + Kopete::MetaContact *myself; + ClickableLabel *labelPicture; + KLineEdit *lineNickname; + KLineEdit *lineStatusMessage; + QHBoxLayout *mainLayout; + int iconSize; + QString lastNickname; +}; + +KopeteEditGlobalIdentityWidget::KopeteEditGlobalIdentityWidget(QWidget *parent, const char *name) + : QWidget(parent, name) +{ + d = new Private; + + d->myself = Kopete::ContactList::self()->myself(); + + createGUI(); + + // Update the GUI when a global identity key change. + connect(Kopete::ContactList::self(), SIGNAL(globalIdentityChanged(const QString&, const QVariant& )), this, SLOT(updateGUI(const QString&, const QVariant&))); +} + +KopeteEditGlobalIdentityWidget::~KopeteEditGlobalIdentityWidget() +{ + delete d; +} + +void KopeteEditGlobalIdentityWidget::setIconSize(int size) +{ + kdDebug(14000) << k_funcinfo << "Manually changing the icon size." << endl; + + // Update the picture (change the size of it) + d->iconSize = size; + d->labelPicture->setMinimumSize(QSize(d->iconSize, d->iconSize)); + d->labelPicture->setMaximumSize(QSize(d->iconSize, d->iconSize)); + if( !d->myself->photo().isNull() ) + d->labelPicture->setPixmap(QPixmap(d->myself->photo().smoothScale(d->iconSize, d->iconSize, QImage::ScaleMin))); +} + +void KopeteEditGlobalIdentityWidget::iconSizeChanged() +{ + kdDebug(14000) << k_funcinfo << "Changing icon size (i.e the picture size)" << endl; + + KToolBar *tb = (KToolBar*)sender(); + if(tb) + { + // Update the picture (change the size of it) + d->iconSize = tb->iconSize(); + d->labelPicture->setMinimumSize(QSize(d->iconSize, d->iconSize)); + d->labelPicture->setMaximumSize(QSize(d->iconSize, d->iconSize)); + if( !d->myself->photo().isNull() ) + d->labelPicture->setPixmap(QPixmap(d->myself->photo().smoothScale(d->iconSize, d->iconSize, QImage::ScaleMin))); + } +} + +void KopeteEditGlobalIdentityWidget::createGUI() +{ + d->mainLayout = new QHBoxLayout(this); + + // The picture label + d->labelPicture = new ClickableLabel(this); + d->labelPicture->setMinimumSize(QSize(d->iconSize, d->iconSize)); + d->labelPicture->setMaximumSize(QSize(d->iconSize, d->iconSize)); + d->labelPicture->setFrameShape(QFrame::Box); + d->mainLayout->addWidget(d->labelPicture); + connect(d->labelPicture, SIGNAL(clicked()), this, SLOT(photoClicked())); + + // The nickname lineEdit + d->lineNickname = new KLineEdit(this); + d->mainLayout->addWidget(d->lineNickname); + // Update the nickname when the user press return. + connect(d->lineNickname, SIGNAL(returnPressed()), this, SLOT(changeNickname())); + // Show the nickname text in red when they are change. + connect(d->lineNickname, SIGNAL(textChanged(const QString&)), this, SLOT(lineNicknameTextChanged(const QString& ))); +} + +void KopeteEditGlobalIdentityWidget::updateGUI(const QString &key, const QVariant &value) +{ + kdDebug(14000) << k_funcinfo << "Updating the GUI reflecting the global identity change." << endl; + + if(key == Kopete::Global::Properties::self()->photo().key()) + { + // Update the picture and the tooltip + if( !d->myself->photo().isNull() ) + { + d->labelPicture->setPixmap(QPixmap(d->myself->photo().smoothScale(d->iconSize, d->iconSize, QImage::ScaleMin))); + QToolTip::add(d->labelPicture, ""); + } + } + else if(key == Kopete::Global::Properties::self()->nickName().key()) + { + // Update the nickname + d->lastNickname = value.toString(); + d->lineNickname->setText(value.toString()); + } +} + +void KopeteEditGlobalIdentityWidget::photoClicked() +{ + KURL photoURL = KFileDialog::getImageOpenURL(QString::null, this, i18n("Global Photo")); + if(photoURL.isEmpty()) + return; + + // Only accept local file. + if(!photoURL.isLocalFile()) + { + KMessageBox::sorry(this, i18n("Remote photos are not allowed."), i18n("Global Photo")); + return; + } + + QString saveLocation(locateLocal("appdata", "global-photo.png")); + QImage photo(photoURL.path()); + photo = KPixmapRegionSelectorDialog::getSelectedImage( QPixmap(photo), 96, 96, this ); + + if(!photo.isNull()) + { + if(photo.width() > 96 || photo.height() > 96) + { + // Scale and crop the picture. + photo = photo.smoothScale( 96, 96, QImage::ScaleMin ); + // crop image if not square + if(photo.width() < photo.height()) + photo = photo.copy((photo.width()-photo.height())/2, 0, 96, 96); + else if (photo.width() > photo.height()) + photo = photo.copy(0, (photo.height()-photo.width())/2, 96, 96); + + } + else if (photo.width() < 32 || photo.height() < 32) + { + // Scale and crop the picture. + photo = photo.smoothScale( 32, 32, QImage::ScaleMin ); + // crop image if not square + if(photo.width() < photo.height()) + photo = photo.copy((photo.width()-photo.height())/2, 0, 32, 32); + else if (photo.width() > photo.height()) + photo = photo.copy(0, (photo.height()-photo.width())/2, 32, 32); + + } + else if (photo.width() != photo.height()) + { + if(photo.width() < photo.height()) + photo = photo.copy((photo.width()-photo.height())/2, 0, photo.height(), photo.height()); + else if (photo.width() > photo.height()) + photo = photo.copy(0, (photo.height()-photo.width())/2, photo.height(), photo.height()); + } + + if(!photo.save(saveLocation, "PNG")) + { + KMessageBox::sorry(this, + i18n("An error occurred when trying to save the global photo."), + i18n("Global Photo")); + } + } + + d->myself->setPhotoSource(Kopete::MetaContact::SourceCustom); + d->myself->setPhoto(KURL(saveLocation)); +} + +void KopeteEditGlobalIdentityWidget::lineNicknameTextChanged(const QString &text) +{ + // Display the nickname in red if they are any change. + if(text != d->lastNickname) + { + d->lineNickname->setPaletteForegroundColor(Qt::red); + } + // The nickname re-become like it was before, reset the palette. + else + { + d->lineNickname->unsetPalette(); + } +} + +void KopeteEditGlobalIdentityWidget::changeNickname() +{ + if( !d->lineNickname->text().isEmpty() && d->lineNickname->text() != d->myself->displayName() ) + { + kdDebug(14000) << k_funcinfo << "Updating global nickname..." << endl; + + // Reset the text color since the nickname is now updated. + d->lineNickname->unsetPalette(); + + // Set the new nickname and set the DisplayName source Custom. + d->lastNickname = d->lineNickname->text(); + d->myself->setDisplayName(d->lineNickname->text()); + d->myself->setDisplayNameSource(Kopete::MetaContact::SourceCustom); + } +} + +#include "kopeteeditglobalidentitywidget.moc" diff --git a/kopete/kopete/kopeteeditglobalidentitywidget.h b/kopete/kopete/kopeteeditglobalidentitywidget.h new file mode 100644 index 00000000..731a2cc5 --- /dev/null +++ b/kopete/kopete/kopeteeditglobalidentitywidget.h @@ -0,0 +1,99 @@ +/* + kopeteeditglobalidentitywidget.h - Kopete Edit Global Identity widget + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEEDITGLOBALIDENTITYWIDGET_H +#define KOPETEEDITGLOBALIDENTITYWIDGET_H + +#include +#include + +/** + * This is a simple widget added to a toolbar in KopeteWindow. + * + * It can edit the global photo and the global nickname. + * When either the photo or the nickname change, it's set the source to Custom. + * When well connected(signal/slot), it react to the toolbar icon size change. + * + * @author Michaël Larouche + */ +class KopeteEditGlobalIdentityWidget : public QWidget +{ + Q_OBJECT +public: + KopeteEditGlobalIdentityWidget(QWidget *parent = 0, const char *name = 0); + virtual ~KopeteEditGlobalIdentityWidget(); + +public slots: + /** + * This slot is called when the "parent" toolbar change its icon size. + */ + void iconSizeChanged(); + /** + * This slot is called to first set the icon size. + */ + void setIconSize(int size); + +private: + /** + * Create the internal widgets and signal/slots connections + */ + void createGUI(); + +private slots: + /** + * When a global identity key is changed, update the GUI. + */ + void updateGUI(const QString &key, const QVariant &value); + /** + * The photo label was clicked, show a ImageFileDialog. + */ + void photoClicked(); + /** + * The nickname was changed, display the text in red to display the change. + */ + void lineNicknameTextChanged(const QString &text); + /** + * User press Return/Enter in the KLineEdit, commit the new nickname. + */ + void changeNickname(); + +private: + class Private; + Private *d; +}; + +class QMouseEvent; +/** + * This is a special label that react to click. + * Also display a "hand" when hovered. + * + * @author Michaël Larouche + */ +class ClickableLabel : public QLabel +{ + Q_OBJECT +public: + ClickableLabel(QWidget *parent = 0, const char *name = 0); + +signals: + void clicked(); + +protected: + void mouseReleaseEvent(QMouseEvent *event); +}; + +#endif diff --git a/kopete/kopete/kopeteiface.cpp b/kopete/kopete/kopeteiface.cpp new file mode 100644 index 00000000..3895e271 --- /dev/null +++ b/kopete/kopete/kopeteiface.cpp @@ -0,0 +1,344 @@ +/* + kopeteiface.cpp - Kopete DCOP Interface + + Copyright (c) 2002 by Hendrik vom Lehn + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include + +#include "kopeteiface.h" +#include "kopetemetacontact.h" +#include "kopetecontactlist.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetepluginmanager.h" +#include "kopeteprotocol.h" +#include "kopeteuiglobal.h" +#include "kopeteaway.h" +#include "kopetegroup.h" +#include "kopetecontact.h" +#include "kopeteconfig.h" + +KopeteIface::KopeteIface() : DCOPObject( "KopeteIface" ) +{ + KConfig *config = KGlobal::config(); + config->setGroup("AutoAway"); + + if (config->readBoolEntry("UseAutoAway", true)) + { + connectDCOPSignal("kdesktop", "KScreensaverIface", + "KDE_start_screensaver()", "setAutoAway()", false); + } + else + { + disconnectDCOPSignal("kdesktop", "KScreensaverIface", + "KDE_start_screensaver()", "setAutoAway()"); + } + // FIXME: AFAICT, this never seems to fire. + connectDCOPSignal("kdesktop", "KScreensaverIface", + "KDE_stop_screensaver()", "setActive()", false); +} + +QStringList KopeteIface::contacts() +{ + return Kopete::ContactList::self()->contacts(); +} + +QStringList KopeteIface::reachableContacts() +{ + return Kopete::ContactList::self()->reachableContacts(); +} + +QStringList KopeteIface::onlineContacts() +{ + QStringList result; + QPtrList list = Kopete::ContactList::self()->onlineContacts(); + QPtrListIterator it( list ); + for( ; it.current(); ++it ) + result.append( it.current()->contactId() ); + + return result; +} + +QStringList KopeteIface::contactsStatus() +{ + return Kopete::ContactList::self()->contactStatuses(); +} + +QStringList KopeteIface::fileTransferContacts() +{ + return Kopete::ContactList::self()->fileTransferContacts(); +} + +QStringList KopeteIface::contactFileProtocols(const QString &displayName) +{ + return Kopete::ContactList::self()->contactFileProtocols(displayName); +} + +QString KopeteIface::messageContact( const QString &contactId, const QString &messageText ) +{ + Kopete::MetaContact *mc = Kopete::ContactList::self()->findMetaContactByContactId( contactId ); + if ( !mc ) + { + return "No such contact."; + } + + if ( mc->isReachable() ) + Kopete::ContactList::self()->messageContact( contactId, messageText ); + else + return "The contact is not reachable"; + + //Default return value + return QString::null; +} +/* +void KopeteIface::sendFile(const QString &displayName, const KURL &sourceURL, + const QString &altFileName, uint fileSize) +{ + return Kopete::ContactList::self()->sendFile(displayName, sourceURL, altFileName, fileSize); +} + +*/ + +QString KopeteIface::onlineStatus( const QString &metaContactId ) +{ + Kopete::MetaContact *m = Kopete::ContactList::self()->metaContact( metaContactId ); + if( m ) + { + Kopete::OnlineStatus status = m->status(); + return status.description(); + } + + return "Unknown Contact"; +} + +void KopeteIface::messageContactById( const QString &metaContactId ) +{ + Kopete::MetaContact *m = Kopete::ContactList::self()->metaContact( metaContactId ); + if( m ) + { + m->execute(); + } +} + +bool KopeteIface::addContact( const QString &protocolName, const QString &accountId, const QString &contactId, + const QString &displayName, const QString &groupName ) +{ + //Get the protocol instance + Kopete::Account *myAccount = Kopete::AccountManager::self()->findAccount( protocolName, accountId ); + + if( myAccount ) + { + QString contactName; + Kopete::Group *realGroup=0L; + //If the nickName isn't specified we need to display the userId in the prompt + if( displayName.isEmpty() || displayName.isNull() ) + contactName = contactId; + else + contactName = displayName; + + if ( !groupName.isEmpty() ) + realGroup=Kopete::ContactList::self()->findGroup( groupName ); + + // Confirm with the user before we add the contact + // FIXME: This is completely bogus since the user may not + // even be at the computer. We just need to add the contact --Matt + if( KMessageBox::questionYesNo( Kopete::UI::Global::mainWidget(), i18n( "An external application is attempting to add the " + " '%1' contact '%2' to your contact list. Do you want to allow this?" ) + .arg( protocolName ).arg( contactName ), i18n( "Allow Contact?" ), i18n("Allow"), i18n("Reject") ) == 3 ) // Yes == 3 + { + //User said Yes + myAccount->addContact( contactId, contactName, realGroup, Kopete::Account::DontChangeKABC); + return true; + } else { + //User said No + return false; + } + + } else { + //This protocol is not loaded + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Error, + i18n("An external application has attempted to add a contact using " + " the %1 protocol, which either does not exist or is not loaded.").arg( protocolName ), + i18n("Missing Protocol")); + + return false; + } +} + +QStringList KopeteIface::accounts() +{ + QStringList list; + QPtrList m_accounts=Kopete::AccountManager::self()->accounts(); + QPtrListIterator it( m_accounts ); + Kopete::Account *account; + while ( ( account = it.current() ) != 0 ) + { + ++it; + + list += ( account->protocol()->pluginId() +"||" + account->accountId() ); + } + + return list; + +} + +void KopeteIface::connect(const QString &protocolId, const QString &accountId ) +{ + QPtrListIterator it( Kopete::AccountManager::self()->accounts() ); + Kopete::Account *account; + while ( ( account = it.current() ) != 0 ) + { + ++it; + + if( ( account->accountId() == accountId) ) + { + if( protocolId.isEmpty() || account->protocol()->pluginId() == protocolId ) + { + account->connect(); + break; + } + } + } +} + +void KopeteIface::disconnect(const QString &protocolId, const QString &accountId ) +{ + QPtrListIterator it( Kopete::AccountManager::self()->accounts() ); + Kopete::Account *account; + while ( ( account = it.current() ) != 0 ) + { + ++it; + + if( ( account->accountId() == accountId) ) + { + if( protocolId.isEmpty() || account->protocol()->pluginId() == protocolId ) + { + account->disconnect(); + break; + } + } + } +} + +void KopeteIface::connectAll() +{ + Kopete::AccountManager::self()->connectAll(); +} + +void KopeteIface::disconnectAll() +{ + Kopete::AccountManager::self()->disconnectAll(); +} + +bool KopeteIface::loadPlugin( const QString &name ) +{ + if ( Kopete::PluginManager::self()->setPluginEnabled( name ) ) + { + QString argument = name; + if ( !argument.startsWith( "kopete_" ) ) + argument.prepend( "kopete_" ); + return Kopete::PluginManager::self()->loadPlugin( argument ); + } + else + { + return false; + } +} + +bool KopeteIface::unloadPlugin( const QString &name ) +{ + if ( Kopete::PluginManager::self()->setPluginEnabled( name, false ) ) + { + QString argument = name; + if ( !argument.startsWith( "kopete_" ) ) + argument.prepend( "kopete_" ); + return Kopete::PluginManager::self()->unloadPlugin( argument ); + } + else + { + return false; + } +} + +void KopeteIface::setAway() +{ + Kopete::AccountManager::self()->setAwayAll(); +} + +void KopeteIface::setAway(const QString &msg, bool away) +{ + Kopete::AccountManager::self()->setAwayAll(msg, away); +} + +void KopeteIface::setAvailable() +{ + Kopete::AccountManager::self()->setAvailableAll(); +} + +void KopeteIface::setAutoAway() +{ + Kopete::Away::getInstance()->setAutoAway(); +} + +void KopeteIface::setGlobalNickname( const QString &nickname ) +{ + if( Kopete::Config::enableGlobalIdentity() ) + { + Kopete::MetaContact *myselfMetaContact = Kopete::ContactList::self()->myself(); + myselfMetaContact->setDisplayNameSource( Kopete::MetaContact::SourceCustom ); + myselfMetaContact->setDisplayName( nickname ); + } +} + +void KopeteIface::setGlobalPhoto( const KURL &photoUrl ) +{ + if( Kopete::Config::enableGlobalIdentity() ) + { + Kopete::MetaContact *myselfMetaContact = Kopete::ContactList::self()->myself(); + myselfMetaContact->setPhoto( photoUrl ); + if( myselfMetaContact->photoSource() != Kopete::MetaContact::SourceCustom ) + myselfMetaContact->setPhotoSource( Kopete::MetaContact::SourceCustom ); + } +} + +QStringList KopeteIface::contactsForDisplayName( const QString & displayName ) +{ + Kopete::MetaContact * mc = Kopete::ContactList::self()->findMetaContactByDisplayName( displayName ); + QStringList contactIds; + if ( mc ) + { + QPtrList contacts = mc->contacts(); + QPtrListIterator it( contacts ); + for( ; it.current(); ++it ) + { + contactIds.append( (*it)->contactId() ); + } + } + return contactIds; +} + +QStringList KopeteIface::metacontactsForContactId( const QString & contactId ) +{ + Kopete::MetaContact * mc = Kopete::ContactList::self()->findMetaContactByContactId( contactId ); + if ( mc ) + return QStringList( mc->displayName() ); + else + return QStringList(); +} +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/kopeteiface.h b/kopete/kopete/kopeteiface.h new file mode 100644 index 00000000..4ca4c4d1 --- /dev/null +++ b/kopete/kopete/kopeteiface.h @@ -0,0 +1,184 @@ +/* + kopeteiface.h - Kopete DCOP Interface + + Copyright (c) 2002 by Hendrik vom Lehn + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KopeteIface_h +#define KopeteIface_h + +#include +#include +#include + +#include "kopeteonlinestatus.h" + +/** + * DCOP interface for kopete + */ +class KopeteIface : virtual public DCOPObject +{ + K_DCOP + +public: + KopeteIface(); + +k_dcop: + QStringList contacts(); + QStringList reachableContacts(); + QStringList onlineContacts(); + QStringList fileTransferContacts(); + QStringList contactFileProtocols(const QString &displayName); + + /*void sendFile(const QString &displayName, const KURL &sourceURL, + const QString &altFileName = QString::null, uint fileSize = 0);*/ + + // FIXME: Do we *need* this one? Sounds error prone to me, because + // nicknames can contain parentheses too. + // Better add a contactStatus( const QString id ) I'd say - Martijn + QStringList contactsStatus(); + + /** + * Open a chat to a contact, and optionally set some initial text + */ + QString messageContact( const QString &contactId, const QString &messageText = QString::null ); + + /** + * Describe the status of a contact by their metaContactId, + * aka their uid in KABC. + */ + QString onlineStatus( const QString &metaContactId ); + + /** + * Message a contact by their metaContactId, aka their uid in KABC. + */ + void messageContactById( const QString &metaContactId ); + + /** + * Adds a contact with the specified params. + * + * @param protocolName The name of the protocol this contact is for ("ICQ", etc) + * @param accountId The account ID to add the contact to + * @param contactId The unique ID for this protocol + * @param displayName The displayName of the contact (may equal userId for some protocols + * @param groupName The name of the group to add the contact to + * @return Weather or not the contact was added successfully + */ + bool addContact( const QString &protocolName, const QString &accountId, const QString &contactId, + const QString &displayName, const QString &groupName = QString::null ); + + /** + * return a list of alls accounts. + * form: XXXProtocol||AccountId + */ + QStringList accounts(); + + /** + * connect a given account in the given protocol + */ + void connect(const QString &protocolName, const QString &accountId); + /** + * disconnect a given account in the given protocol + */ + void disconnect(const QString &protocolName, const QString &accountId); + + /** + * Ask all accounts to connect + */ + void connectAll(); + + /** + * Ask all accounts to disconnect + */ + void disconnectAll(); + + /** + * load a plugin + * the name is the name of the library: example: kopete_msn + * but you can ommit the kopete_ prefix + */ + bool loadPlugin( const QString& name ); + /** + * unload a plugin + * the name is the name of the library: example: kopete_msn + * but you can ommit the kopete_ prefix + */ + bool unloadPlugin( const QString& name ); + + /** + * set all account away using the global away function + */ + void setAway(); + + /** + * set all account away using the global away function + * and set an away message + */ + void setAway( const QString &msg ) { setAway( msg, true ); } + + /** + * set all account away using the global away function + * and set an away message. + * @param away decides if the message is away/non-away + */ + void setAway( const QString &msg, bool away ); + + /** + * set Available all accountes + */ + void setAvailable(); + /** + * set all account away using the auto away funciton. + * accounts will return online if activity is detected again + */ + void setAutoAway(); + + /** + * set the global nickname if global identity is enabled. + * @param nickname the new global nickname + */ + void setGlobalNickname( const QString &nickname ); + + /** + * set the global photo if global identity is enabled. + * @param photoUrl URL to the photo + */ + void setGlobalPhoto( const KURL &photoUrl ); + + /** + * get the contactIds for a given display name + * @param displayName + */ + QStringList contactsForDisplayName( const QString & displayName ); + + /** + * get the metacontactIds that have the given contactId + * @param contactId + */ + QStringList metacontactsForContactId( const QString & contactId ); +}; + +#endif + + + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/kopeteui.rc b/kopete/kopete/kopeteui.rc new file mode 100644 index 00000000..6bdfb89c --- /dev/null +++ b/kopete/kopete/kopeteui.rc @@ -0,0 +1,107 @@ + + + + + &File + + + + + + + + + + + + &Edit + + + + + + + + + + + + + + + &Settings + + + + + + + + + + + + + + + Main Toolbar + + + + + + + + Quick Search Bar + + + + + + + + + + + + + &Other Actions + + + + + + + &Groups + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kopete/kopete/kopetewindow.cpp b/kopete/kopete/kopetewindow.cpp new file mode 100644 index 00000000..f3ec502e --- /dev/null +++ b/kopete/kopete/kopetewindow.cpp @@ -0,0 +1,1112 @@ +/* + kopetewindow.cpp - Kopete Main Window + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + Copyright (c) 2001-2002 by Stefan Gehn + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2005 by Olivier Goffart + Copyright (c) 2005-2006 by Will Stephenson + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopetewindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "addcontactpage.h" +#include "addcontactwizard.h" +#include "addressbooklinkwidget.h" +#include "groupkabcselectorwidget.h" +#include "kabcexport.h" +#include "kopeteapplication.h" +#include "kopeteaccount.h" +#include "kopeteaway.h" +#include "kopeteaccountmanager.h" +#include "kopeteaccountstatusbaricon.h" +#include "kopetecontact.h" +#include "kopetecontactlist.h" +#include "kopetecontactlistview.h" +#include "kopetegroup.h" +#include +#include "kopetelistviewsearchline.h" +#include "kopetechatsessionmanager.h" +#include "kopetepluginconfig.h" +#include "kopetepluginmanager.h" +#include "kopeteprefs.h" +#include "kopeteprotocol.h" +#include "kopetestdaction.h" +#include "kopeteawayaction.h" +#include "kopeteuiglobal.h" +#include "systemtray.h" +#include "kopeteonlinestatusmanager.h" +#include "kopeteeditglobalidentitywidget.h" + +//BEGIN GlobalStatusMessageIconLabel +GlobalStatusMessageIconLabel::GlobalStatusMessageIconLabel(QWidget *parent, const char *name) + : QLabel(parent, name) +{} + +void GlobalStatusMessageIconLabel::mouseReleaseEvent( QMouseEvent *event ) +{ + if( event->button() == Qt::LeftButton || event->button() == Qt::RightButton ) + { + emit iconClicked( event->globalPos() ); + event->accept(); + } +} +//END GlobalStatusMessageIconLabel + +/* KMainWindow is very broken from our point of view - it deref()'s the app + * when the last visible KMainWindow is destroyed. But when our main window is + * hidden when it's in the tray,closing the last chatwindow would cause the app + * to quit. - Richard + * + * Fortunately KMainWindow checks queryExit before deref()ing the Kapplication. + * KopeteWindow reimplements queryExit() and only returns true if it is shutting down + * (either because the user quit Kopete, or the session manager did). + * + * KopeteWindow and ChatWindows are closed by session management. + * App shutdown is not performed by the KopeteWindow but by KopeteApplication: + * 1) user quit - KopeteWindow::slotQuit() was called, calls KopeteApplication::quitKopete(), + * which closes all chatwindows and the KopeteWindow. The last window to close + * shuts down the PluginManager in queryExit(). When the PluginManager has completed its + * shutdown, the app is finally deref()ed, and the contactlist and accountmanager + * are saved. + * and calling KApplication::quit() + * 2) session - KopeteWindow and all chatwindows are closed by KApplication session management. + * quit Then the shutdown proceeds as above. + * + * queryClose() is honoured so group chats and chats receiving recent messages can interrupt + * (session) quit. + */ + +KopeteWindow::KopeteWindow( QWidget *parent, const char *name ) +: KMainWindow( parent, name, WType_TopLevel ) +{ + // Applications should ensure that their StatusBar exists before calling createGUI() + // so that the StatusBar is always correctly positioned when KDE is configured to use + // a MacOS-style MenuBar. + // This fixes a "statusbar drawn over the top of the toolbar" bug + // e.g. it can happen when you switch desktops on Kopete startup + + m_statusBarWidget = new QHBox(statusBar(), "m_statusBarWidget"); + m_statusBarWidget->setMargin( 2 ); + m_statusBarWidget->setSpacing( 1 ); + statusBar()->addWidget(m_statusBarWidget, 0, true ); + QHBox *statusBarMessage = new QHBox(statusBar(), "m_statusBarWidget"); + m_statusBarWidget->setMargin( 2 ); + m_statusBarWidget->setSpacing( 1 ); + + GlobalStatusMessageIconLabel *label = new GlobalStatusMessageIconLabel( statusBarMessage, "statusmsglabel" ); + label->setFixedSize( 16, 16 ); + label->setPixmap( SmallIcon( "kopetestatusmessage" ) ); + connect(label, SIGNAL(iconClicked( const QPoint& )), + this, SLOT(slotGlobalStatusMessageIconClicked( const QPoint& ))); + QToolTip::add( label, i18n( "Global status message" ) ); + m_globalStatusMessage = new KSqueezedTextLabel( statusBarMessage ); + statusBar()->addWidget(statusBarMessage, 1, false ); + + m_pluginConfig = 0L; + m_autoHideTimer = new QTimer( this ); + + // -------------------------------------------------------------------------------- + initView(); + initActions(); + contactlist->initActions(actionCollection()); + initSystray(); + // -------------------------------------------------------------------------------- + + // Trap all loaded plugins, so we can add their status bar icons accordingly , also used to add XMLGUIClient + connect( Kopete::PluginManager::self(), SIGNAL( pluginLoaded( Kopete::Plugin * ) ), + this, SLOT( slotPluginLoaded( Kopete::Plugin * ) ) ); + connect( Kopete::PluginManager::self(), SIGNAL( allPluginsLoaded() ), + this, SLOT( slotAllPluginsLoaded() )); + //Connect the appropriate account signals + /* Please note that I tried to put this in the slotAllPluginsLoaded() function + * but it seemed to break the account icons in the statusbar --Matt */ + + connect( Kopete::AccountManager::self(), SIGNAL(accountRegistered(Kopete::Account*)), + this, SLOT(slotAccountRegistered(Kopete::Account*))); + connect( Kopete::AccountManager::self(), SIGNAL(accountUnregistered(const Kopete::Account*)), + this, SLOT(slotAccountUnregistered(const Kopete::Account*))); + + connect( m_autoHideTimer, SIGNAL( timeout() ), this, SLOT( slotAutoHide() ) ); + connect( KopetePrefs::prefs(), SIGNAL( contactListAppearanceChanged() ), + this, SLOT( slotContactListAppearanceChanged() ) ); + + createGUI ( "kopeteui.rc", false ); + + // call this _after_ createGUI(), otherwise menubar is not set up correctly + loadOptions(); + + // If some plugins are already loaded, merge the GUI + Kopete::PluginList plugins = Kopete::PluginManager::self()->loadedPlugins(); + Kopete::PluginList::ConstIterator it; + for ( it = plugins.begin(); it != plugins.end(); ++it ) + slotPluginLoaded( *it ); + + // If some account alrady loaded, build the status icon + QPtrList accounts = Kopete::AccountManager::self()->accounts(); + for(Kopete::Account *a=accounts.first() ; a; a=accounts.next() ) + slotAccountRegistered(a); + + //install an event filter for the quick search toolbar so we can + //catch the hide events + toolBar( "quickSearchBar" )->installEventFilter( this ); + +} + +void KopeteWindow::initView() +{ + contactlist = new KopeteContactListView(this); + setCentralWidget(contactlist); +} + +void KopeteWindow::initActions() +{ + // this action menu contains one action per account and is updated when accounts are registered/unregistered + actionAddContact = new KActionMenu( i18n( "&Add Contact" ), "add_user", + actionCollection(), "AddContact" ); + actionAddContact->setDelayed( false ); + // this signal mapper is needed to call slotAddContact with the correct arguments + addContactMapper = new QSignalMapper( this ); + connect( addContactMapper, SIGNAL( mapped( const QString & ) ), + this, SLOT( slotAddContactDialogInternal( const QString & ) ) ); + + /* ConnectAll is now obsolete. "Go online" has replaced it. + actionConnect = new KAction( i18n( "&Connect Accounts" ), "connect_creating", + 0, Kopete::AccountManager::self(), SLOT( connectAll() ), + actionCollection(), "ConnectAll" ); + */ + + actionDisconnect = new KAction( i18n( "O&ffline" ), "connect_no", + 0, this, SLOT( slotDisconnectAll() ), + actionCollection(), "DisconnectAll" ); + + actionExportContacts = new KAction( i18n( "&Export Contacts..." ), "", 0, this, + SLOT( showExportDialog() ),actionCollection(), "ExportContacts" ); + + /* the connection menu has been replaced by the set status menu + actionConnectionMenu = new KActionMenu( i18n("Connection"),"connect_established", + actionCollection(), "Connection" ); + + actionConnectionMenu->setDelayed( false ); + actionConnectionMenu->insert(actionConnect); + actionConnectionMenu->insert(actionDisconnect); + actionConnect->setEnabled(false); + */ + actionDisconnect->setEnabled(false); + + selectAway = new KAction( i18n("&Away"), SmallIcon("kopeteaway"), 0, + this, SLOT( slotGlobalAway() ), actionCollection(), + "SetAwayAll" ); + + selectBusy = new KAction( i18n("&Busy"), SmallIcon("kopeteaway"), 0, + this, SLOT( slotGlobalBusy() ), actionCollection(), + "SetBusyAll" ); + + + actionSetInvisible = new KAction( i18n( "&Invisible" ), "kopeteavailable", 0 , + this, SLOT( slotSetInvisibleAll() ), actionCollection(), + "SetInvisibleAll" ); + + + + /*actionSetAvailable = new KAction( i18n( "&Online" ), + "kopeteavailable", 0 , Kopete::AccountManager::self(), + SLOT( setAvailableAll() ), actionCollection(), + "SetAvailableAll" );*/ + + actionSetAvailable = new KAction( i18n("&Online"), + SmallIcon("kopeteavailable"), 0, this, + SLOT( slotGlobalAvailable() ), actionCollection(), + "SetAvailableAll" ); + + actionAwayMenu = new KActionMenu( i18n("&Set Status"), "kopeteavailable", + actionCollection(), "Status" ); + actionAwayMenu->setDelayed( false ); + actionAwayMenu->insert(actionSetAvailable); + actionAwayMenu->insert(selectAway); + actionAwayMenu->insert(selectBusy); + actionAwayMenu->insert(actionSetInvisible); + actionAwayMenu->insert(actionDisconnect); + + actionPrefs = KopeteStdAction::preferences( actionCollection(), "settings_prefs" ); + + KStdAction::quit(this, SLOT(slotQuit()), actionCollection()); + + setStandardToolBarMenuEnabled(true); + menubarAction = KStdAction::showMenubar(this, SLOT(showMenubar()), actionCollection(), "settings_showmenubar" ); + statusbarAction = KStdAction::showStatusbar(this, SLOT(showStatusbar()), actionCollection(), "settings_showstatusbar"); + + KStdAction::keyBindings( guiFactory(), SLOT( configureShortcuts() ), actionCollection(), "settings_keys" ); + new KAction( i18n( "Configure Plugins..." ), "input_devices_settings", 0, this, + SLOT( slotConfigurePlugins() ), actionCollection(), "settings_plugins" ); + new KAction( i18n( "Configure &Global Shortcuts..." ), "configure_shortcuts", 0, this, + SLOT( slotConfGlobalKeys() ), actionCollection(), "settings_global" ); + + KStdAction::configureToolbars( this, SLOT(slotConfToolbar()), actionCollection() ); + KStdAction::configureNotifications(this, SLOT(slotConfNotifications()), actionCollection(), "settings_notifications" ); + + actionShowOffliners = new KToggleAction( i18n( "Show Offline &Users" ), "show_offliners", CTRL + Key_U, + this, SLOT( slotToggleShowOffliners() ), actionCollection(), "settings_show_offliners" ); + actionShowEmptyGroups = new KToggleAction( i18n( "Show Empty &Groups" ), "folder", CTRL + Key_G, + this, SLOT( slotToggleShowEmptyGroups() ), actionCollection(), "settings_show_empty_groups" ); + + actionShowOffliners->setCheckedState(i18n("Hide Offline &Users")); + actionShowEmptyGroups->setCheckedState(i18n("Hide Empty &Groups")); + + // quick search bar + QLabel *searchLabel = new QLabel( i18n("Se&arch:"), 0, "kde toolbar widget" ); + QWidget *searchBar = new Kopete::UI::ListView::SearchLine( 0, contactlist, "quicksearch_bar" ); + searchLabel->setBuddy( searchBar ); + KWidgetAction *quickSearch = new KWidgetAction( searchBar, i18n( "Quick Search Bar" ), 0, 0, 0, actionCollection(), "quicksearch_bar" ); + new KWidgetAction( searchLabel, i18n( "Search:" ), 0, 0, 0, actionCollection(), "quicksearch_label" ); + quickSearch->setAutoSized( true ); + // quick search bar - clear button + KAction *resetQuickSearch = new KAction( i18n( "Reset Quick Search" ), + QApplication::reverseLayout() ? "clear_left" : "locationbar_erase", + 0, searchBar, SLOT( clear() ), actionCollection(), "quicksearch_reset" ); + resetQuickSearch->setWhatsThis( i18n( "Reset Quick Search\n" + "Resets the quick search so that all contacts and groups are shown again." ) ); + + // Edit global identity widget/bar + editGlobalIdentityWidget = new KopeteEditGlobalIdentityWidget(this, "editglobalBar"); + editGlobalIdentityWidget->hide(); + KWidgetAction *editGlobalAction = new KWidgetAction( editGlobalIdentityWidget, i18n("Edit Global Identity Widget"), 0, 0, 0, actionCollection(), "editglobal_widget"); + editGlobalAction->setAutoSized( true ); + + // KActionMenu for selecting the global status message(kopeteonlinestatus_0) + KActionMenu * setStatusMenu = new KActionMenu( i18n( "Set Status Message" ), "kopeteeditstatusmessage", actionCollection(), "SetStatusMessage" ); + setStatusMenu->setDelayed( false ); + connect( setStatusMenu->popupMenu(), SIGNAL( aboutToShow() ), SLOT(slotBuildStatusMessageMenu() ) ); + connect( setStatusMenu->popupMenu(), SIGNAL( activated( int ) ), SLOT(slotStatusMessageSelected( int ) ) ); + + // sync actions, config and prefs-dialog + connect ( KopetePrefs::prefs(), SIGNAL(saved()), this, SLOT(slotConfigChanged()) ); + slotConfigChanged(); + + globalAccel = new KGlobalAccel( this ); + globalAccel->insert( QString::fromLatin1("Read Message"), i18n("Read Message"), i18n("Read the next pending message"), + CTRL+SHIFT+Key_I, KKey::QtWIN+CTRL+Key_I, Kopete::ChatSessionManager::self(), SLOT(slotReadMessage()) ); + + globalAccel->insert( QString::fromLatin1("Show/Hide Contact List"), i18n("Show/Hide Contact List"), i18n("Show or hide the contact list"), + CTRL+SHIFT+Key_S, KKey::QtWIN+CTRL+Key_S, this, SLOT(slotShowHide()) ); + + globalAccel->insert( QString::fromLatin1("Set Away/Back"), i18n("Set Away/Back"), i18n("Sets away from keyboard or sets back"), + CTRL+SHIFT+Key_W, KKey::QtWIN+CTRL+SHIFT+Key_W, this, SLOT(slotToggleAway()) ); + + globalAccel->readSettings(); + globalAccel->updateConnections(); +} + +void KopeteWindow::slotShowHide() +{ + if(isActiveWindow()) + { + m_autoHideTimer->stop(); //no timeouts if active + hide(); + } + else + { + show(); + //raise() and show() should normaly deIconify the window. but it doesn't do here due + // to a bug in QT or in KDE (qt3.1.x or KDE 3.1.x) then, i have to call KWin's method + if(isMinimized()) + KWin::deIconifyWindow(winId()); + + if(!KWin::windowInfo(winId(),NET::WMDesktop).onAllDesktops()) + KWin::setOnDesktop(winId(), KWin::currentDesktop()); + raise(); + setActiveWindow(); + } +} + +void KopeteWindow::slotToggleAway() +{ + Kopete::Away *mAway = Kopete::Away::getInstance(); + if ( mAway->globalAway() ) + { + Kopete::AccountManager::self()->setAvailableAll(); + } + else + { + QString awayReason = mAway->getMessage( 0 ); + slotGlobalAway(); + } +} + +void KopeteWindow::initSystray() +{ + m_tray = KopeteSystemTray::systemTray( this, "KopeteSystemTray" ); + Kopete::UI::Global::setSysTrayWId( m_tray->winId() ); + KPopupMenu *tm = m_tray->contextMenu(); + + // NOTE: This is in reverse order because we insert + // at the top of the menu, not at bottom! + actionAddContact->plug( tm, 1 ); + actionPrefs->plug( tm, 1 ); + tm->insertSeparator( 1 ); + actionAwayMenu->plug( tm, 1 ); + //actionConnectionMenu->plug ( tm, 1 ); + tm->insertSeparator( 1 ); + + QObject::connect( m_tray, SIGNAL( aboutToShowMenu( KPopupMenu * ) ), + this, SLOT( slotTrayAboutToShowMenu( KPopupMenu * ) ) ); + QObject::connect( m_tray, SIGNAL( quitSelected() ), this, SLOT( slotQuit() ) ); +} + +KopeteWindow::~KopeteWindow() +{ + delete m_pluginConfig; +} + +bool KopeteWindow::eventFilter( QObject* target, QEvent* event ) +{ + KToolBar* toolBar = dynamic_cast( target ); + KAction* resetAction = actionCollection()->action( "quicksearch_reset" ); + + if ( toolBar && resetAction && resetAction->isPlugged( toolBar ) ) + { + + if ( event->type() == QEvent::Hide ) + { + resetAction->activate(); + return true; + } + return KMainWindow::eventFilter( target, event ); + } + + return KMainWindow::eventFilter( target, event ); +} + +void KopeteWindow::loadOptions() +{ + KConfig *config = KGlobal::config(); + + toolBar("mainToolBar")->applySettings( config, "ToolBar Settings" ); + toolBar("quickSearchBar")->applySettings( config, "QuickSearchBar Settings" ); + toolBar("editGlobalIdentityBar")->applySettings( config, "EditGlobalIdentityBar Settings" ); + + // FIXME: HACK: Is there a way to do that automatic ? + editGlobalIdentityWidget->setIconSize(toolBar("editGlobalIdentityBar")->iconSize()); + connect(toolBar("editGlobalIdentityBar"), SIGNAL(modechange()), editGlobalIdentityWidget, SLOT(iconSizeChanged())); + + applyMainWindowSettings( config, "General Options" ); + config->setGroup("General Options"); + QPoint pos = config->readPointEntry("Position"); + move(pos); + + QSize size = config->readSizeEntry("Geometry"); + if(size.isEmpty()) // Default size + resize( QSize(220, 350) ); + else + resize(size); + + KopetePrefs *p = KopetePrefs::prefs(); + + m_autoHide = p->contactListAutoHide(); + m_autoHideTimeout = p->contactListAutoHideTimeout(); + + + QString tmp = config->readEntry("State", "Shown"); + if ( tmp == "Minimized" && p->showTray()) + { + showMinimized(); + } + else if ( tmp == "Hidden" && p->showTray()) + { + hide(); + } + else if ( !p->startDocked() || !p->showTray() ) + show(); + + menubarAction->setChecked( !menuBar()->isHidden() ); + statusbarAction->setChecked( !statusBar()->isHidden() ); + m_autoHide = p->contactListAutoHide(); + m_autoHideTimeout = p->contactListAutoHideTimeout(); +} + +void KopeteWindow::saveOptions() +{ + KConfig *config = KGlobal::config(); + + toolBar("mainToolBar")->saveSettings ( config, "ToolBar Settings" ); + toolBar("quickSearchBar")->saveSettings( config, "QuickSearchBar Settings" ); + toolBar("editGlobalIdentityBar")->saveSettings( config, "EditGlobalIdentityBar Settings" ); + + saveMainWindowSettings( config, "General Options" ); + + config->setGroup("General Options"); + config->writeEntry("Position", pos()); + config->writeEntry("Geometry", size()); + + if(isMinimized()) + { + config->writeEntry("State", "Minimized"); + } + else if(isHidden()) + { + config->writeEntry("State", "Hidden"); + } + else + { + config->writeEntry("State", "Shown"); + } + + config->sync(); +} + +void KopeteWindow::showMenubar() +{ + if(menubarAction->isChecked()) + menuBar()->show(); + else + menuBar()->hide(); +} + +void KopeteWindow::showStatusbar() +{ + if( statusbarAction->isChecked() ) + statusBar()->show(); + else + statusBar()->hide(); +} + +void KopeteWindow::slotToggleShowOffliners() +{ + KopetePrefs *p = KopetePrefs::prefs(); + p->setShowOffline ( actionShowOffliners->isChecked() ); + + disconnect ( KopetePrefs::prefs(), SIGNAL(saved()), this, SLOT(slotConfigChanged()) ); + p->save(); + connect ( KopetePrefs::prefs(), SIGNAL(saved()), this, SLOT(slotConfigChanged()) ); +} + +void KopeteWindow::slotToggleShowEmptyGroups() +{ + KopetePrefs *p = KopetePrefs::prefs(); + p->setShowEmptyGroups ( actionShowEmptyGroups->isChecked() ); + + disconnect ( KopetePrefs::prefs(), SIGNAL(saved()), this, SLOT(slotConfigChanged()) ); + p->save(); + connect ( KopetePrefs::prefs(), SIGNAL(saved()), this, SLOT(slotConfigChanged()) ); +} + +void KopeteWindow::slotConfigChanged() +{ + KopetePrefs *pref = KopetePrefs::prefs(); + + if( isHidden() && !pref->showTray()) // user disabled systray while kopete is hidden, show it! + show(); + + actionShowOffliners->setChecked( pref->showOffline() ); + actionShowEmptyGroups->setChecked( pref->showEmptyGroups() ); +} + +void KopeteWindow::slotContactListAppearanceChanged() +{ + KopetePrefs* p = KopetePrefs::prefs(); + m_autoHide = p->contactListAutoHide(); + m_autoHideTimeout = p->contactListAutoHideTimeout(); + + startAutoHideTimer(); +} + +void KopeteWindow::slotConfNotifications() +{ + KNotifyDialog::configure( this ); +} + +void KopeteWindow::slotConfigurePlugins() +{ + if ( !m_pluginConfig ) + m_pluginConfig = new KopetePluginConfig( this ); + m_pluginConfig->show(); + + m_pluginConfig->raise(); + + KWin::activateWindow( m_pluginConfig->winId() ); +} + +void KopeteWindow::slotConfGlobalKeys() +{ + KKeyDialog::configure( globalAccel, this ) ; +} + +void KopeteWindow::slotConfToolbar() +{ + saveMainWindowSettings(KGlobal::config(), "General Options"); + KEditToolbar *dlg = new KEditToolbar(factory()); + connect( dlg, SIGNAL(newToolbarConfig()), this, SLOT(slotUpdateToolbar()) ); + connect( dlg, SIGNAL(finished()) , dlg, SLOT(deleteLater())); + dlg->show(); +} + +void KopeteWindow::slotUpdateToolbar() +{ + applyMainWindowSettings(KGlobal::config(), "General Options"); +} + +void KopeteWindow::slotGlobalAway() +{ + Kopete::AccountManager::self()->setAwayAll( m_globalStatusMessageStored ); +} + +void KopeteWindow::slotGlobalBusy() +{ + Kopete::AccountManager::self()->setOnlineStatus( + Kopete::OnlineStatusManager::Busy, m_globalStatusMessageStored ); +} + +void KopeteWindow::slotGlobalAvailable() +{ + Kopete::AccountManager::self()->setAvailableAll( m_globalStatusMessageStored ); +} + +void KopeteWindow::slotSetInvisibleAll() +{ + Kopete::AccountManager::self()->setOnlineStatus( Kopete::OnlineStatusManager::Invisible ); +} + +void KopeteWindow::slotDisconnectAll() +{ + m_globalStatusMessage->setText( "" ); + m_globalStatusMessageStored = QString(); + Kopete::AccountManager::self()->disconnectAll(); +} + +bool KopeteWindow::queryClose() +{ + KopeteApplication *app = static_cast( kapp ); + if ( !app->sessionSaving() // if we are just closing but not shutting down + && !app->isShuttingDown() + && KopetePrefs::prefs()->showTray() + && isShown() ) + // I would make this a KMessageBox::queuedMessageBox but there doesn't seem to be don'tShowAgain support for those + KMessageBox::information( this, + i18n( "Closing the main window will keep Kopete running in the " + "system tray. Use 'Quit' from the 'File' menu to quit the application." ), + i18n( "Docking in System Tray" ), "hideOnCloseInfo" ); +// else // we are shutting down either user initiated or session management +// Kopete::PluginManager::self()->shutdown(); + + return true; +} + +bool KopeteWindow::queryExit() +{ + KopeteApplication *app = static_cast( kapp ); + if ( app->sessionSaving() + || app->isShuttingDown() /* only set if KopeteApplication::quitKopete() or + KopeteApplication::commitData() called */ + || !KopetePrefs::prefs()->showTray() /* also close if our tray icon is hidden! */ + || !isShown() ) + { + kdDebug( 14000 ) << k_funcinfo << " shutting down plugin manager" << endl; + Kopete::PluginManager::self()->shutdown(); + return true; + } + else + return false; +} + +void KopeteWindow::closeEvent( QCloseEvent *e ) +{ + // if there's a system tray applet and we are not shutting down then just do what needs to be done if a + // window is closed. + KopeteApplication *app = static_cast( kapp ); + if ( KopetePrefs::prefs()->showTray() && !app->isShuttingDown() && !app->sessionSaving() ) { + // BEGIN of code borrowed from KMainWindow::closeEvent + // Save settings if auto-save is enabled, and settings have changed + if ( settingsDirty() && autoSaveSettings() ) + saveAutoSaveSettings(); + + if ( queryClose() ) { + e->accept(); + } + // END of code borrowed from KMainWindow::closeEvent + kdDebug( 14000 ) << k_funcinfo << "just closing because we have a system tray icon" << endl; + } + else + { + kdDebug( 14000 ) << k_funcinfo << "delegating to KMainWindow::closeEvent()" << endl; + KMainWindow::closeEvent( e ); + } +} + +void KopeteWindow::slotQuit() +{ + saveOptions(); + KopeteApplication *app = static_cast( kapp ); + app->quitKopete(); +} + +void KopeteWindow::slotPluginLoaded( Kopete::Plugin * p ) +{ + guiFactory()->addClient(p); +} + +void KopeteWindow::slotAllPluginsLoaded() +{ +// actionConnect->setEnabled(true); + actionDisconnect->setEnabled(true); +} + +void KopeteWindow::slotAccountRegistered( Kopete::Account *account ) +{ +// kdDebug(14000) << k_funcinfo << "Called." << endl; + if ( !account ) + return; + + //enable the connect all toolbar button +// actionConnect->setEnabled(true); + actionDisconnect->setEnabled(true); + + connect( account->myself(), + SIGNAL(onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus &) ), + this, SLOT( slotAccountStatusIconChanged( Kopete::Contact * ) ) ); + +// connect( account, SIGNAL( iconAppearanceChanged() ), SLOT( slotAccountStatusIconChanged() ) ); + connect( account, SIGNAL( colorChanged(const QColor& ) ), SLOT( slotAccountStatusIconChanged() ) ); + + connect( account->myself(), + SIGNAL(propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), + this, SLOT( slotAccountStatusIconChanged( Kopete::Contact* ) ) ); + + KopeteAccountStatusBarIcon *sbIcon = new KopeteAccountStatusBarIcon( account, m_statusBarWidget ); + connect( sbIcon, SIGNAL( rightClicked( Kopete::Account *, const QPoint & ) ), + SLOT( slotAccountStatusIconRightClicked( Kopete::Account *, + const QPoint & ) ) ); + connect( sbIcon, SIGNAL( leftClicked( Kopete::Account *, const QPoint & ) ), + SLOT( slotAccountStatusIconRightClicked( Kopete::Account *, + const QPoint & ) ) ); + + m_accountStatusBarIcons.insert( account, sbIcon ); + slotAccountStatusIconChanged( account->myself() ); + + // add an item for this account to the add contact actionmenu + QString s = "actionAdd%1Contact"; + s.arg( account->accountId() ); + KAction *action = new KAction( account->accountLabel(), account->accountIcon(), 0 , addContactMapper, SLOT( map() ), account, s.latin1() ); + addContactMapper->setMapping( action, account->protocol()->pluginId() + QChar(0xE000) + account->accountId() ); + actionAddContact->insert( action ); +} + +void KopeteWindow::slotAccountUnregistered( const Kopete::Account *account) +{ +// kdDebug(14000) << k_funcinfo << "Called." << endl; + QPtrList accounts = Kopete::AccountManager::self()->accounts(); + if (accounts.isEmpty()) + { +// actionConnect->setEnabled(false); + actionDisconnect->setEnabled(false); + } + + // the (void*) is to remove the const. i don't know why QPtrList doesn't accept const ptr as key. + KopeteAccountStatusBarIcon *sbIcon = static_cast( m_accountStatusBarIcons[ (void*)account ] ); + + if( !sbIcon ) + return; + + m_accountStatusBarIcons.remove( (void*)account ); + delete sbIcon; + + makeTrayToolTip(); + + // update add contact actionmenu + QString s = "actionAdd%1Contact"; + s.arg( account->accountId() ); +// KAction * action = actionCollection()->action( account->accountId() ); + Kopete::Account * myAccount = const_cast< Kopete::Account * > ( account ); + KAction * action = static_cast< KAction *>( myAccount->child( s.latin1() ) ); + if ( action ) + { + kdDebug(14000) << " found KAction " << action << " with name: " << action->name() << endl; + addContactMapper->removeMappings( action ); + actionAddContact->remove( action ); + } +} + +void KopeteWindow::slotAccountStatusIconChanged() +{ + if ( const Kopete::Account *from = dynamic_cast(sender()) ) + slotAccountStatusIconChanged( from->myself() ); +} + +void KopeteWindow::slotAccountStatusIconChanged( Kopete::Contact *contact ) +{ + kdDebug( 14000 ) << k_funcinfo << contact->property( Kopete::Global::Properties::self()->awayMessage() ).value() << endl; + // update the global status label if the change doesn't +// QString newAwayMessage = contact->property( Kopete::Global::Properties::self()->awayMessage() ).value().toString(); + Kopete::OnlineStatus status = contact->onlineStatus(); +/* if ( status.status() != Kopete::OnlineStatus::Connecting ) + { + QString globalMessage = m_globalStatusMessage->text(); + if ( newAwayMessage != globalMessage ) + m_globalStatusMessage->setText( "" /* i18n("status message to show when different accounts have different status messages", "(multiple)" )*/ /*); + }*/ +// kdDebug(14000) << k_funcinfo << "Icons: '" << +// status.overlayIcons() << "'" << endl; + + if ( status != Kopete::OnlineStatus::Connecting ) + { + if(contact->hasProperty(Kopete::Global::Properties::self()->awayMessage().key())) + { + m_globalStatusMessageStored = contact->property( Kopete::Global::Properties::self()->awayMessage() ).value().toString(); + m_globalStatusMessage->setText( m_globalStatusMessageStored ); + } + else //If the account has not status message, it may be because the protocol doesn't support it (Bug 132609) + { // or because the user just set an empty status to this account. + // We will check if another account has still a status message, if yes, we will use it, if not, we will clear it. + QString statusMessageToUse; + QPtrList accounts = Kopete::AccountManager::self()->accounts(); + for(Kopete::Account *a = accounts.first(); a; a = accounts.next()) + { + Kopete::Contact *self = a->myself(); + if(self->hasProperty(Kopete::Global::Properties::self()->awayMessage().key())) + { + statusMessageToUse = self->property( Kopete::Global::Properties::self()->awayMessage() ).value().toString(); + if(statusMessageToUse == m_globalStatusMessageStored ) + break; //keep this one + } + } + m_globalStatusMessageStored = statusMessageToUse; + m_globalStatusMessage->setText( m_globalStatusMessageStored ); + } + } + + KopeteAccountStatusBarIcon *i = static_cast( m_accountStatusBarIcons[ contact->account() ] ); + if( !i ) + return; + + // Adds tooltip for each status icon, + // useful in case you have many accounts + // over one protocol + QToolTip::remove( i ); + QToolTip::add( i, contact->toolTip() ); + + // Because we want null pixmaps to detect the need for a loadMovie + // we can't use the SmallIcon() method directly + KIconLoader *loader = KGlobal::instance()->iconLoader(); + + QMovie mv = loader->loadMovie( status.overlayIcons().first(), KIcon::Small ); + + if ( mv.isNull() ) + { + // No movie found, fallback to pixmap + // Get the icon for our status + + //QPixmap pm = SmallIcon( icon ); + QPixmap pm = status.iconFor( contact->account() ); + + // No Pixmap found, fallback to Unknown + if( pm.isNull() ) + i->setPixmap( KIconLoader::unknown() ); + else + i->setPixmap( pm ); + } + else + { + //kdDebug( 14000 ) << k_funcinfo << "Using movie." << endl; + i->setMovie( mv ); + } + makeTrayToolTip(); +} + +void KopeteWindow::makeTrayToolTip() +{ + //the tool-tip of the systemtray. + if(m_tray) + { + QToolTip::remove(m_tray); + + QString tt = QString::fromLatin1(""); + QPtrList accounts = Kopete::AccountManager::self()->accounts(); + for(Kopete::Account *a = accounts.first(); a; a = accounts.next()) + { + Kopete::Contact *self = a->myself(); + tt += i18n( "Account tooltip information: ICON PROTOCOL: NAME (STATUS)
    ", + "
    %1: %2 (%5)
    " ) + .arg( a->protocol()->displayName() ).arg( a->accountLabel(), KURL::encode_string( a->protocol()->pluginId() ), + KURL::encode_string( a->accountId() ), self->onlineStatus().description() ); + } + tt += QString::fromLatin1("
    "); + QToolTip::add(m_tray, tt); + } +} + +void KopeteWindow::slotAccountStatusIconRightClicked( Kopete::Account *account, const QPoint &p ) +{ + KActionMenu *actionMenu = account->actionMenu(); + if ( !actionMenu ) + return; + + connect( actionMenu->popupMenu(), SIGNAL( aboutToHide() ), actionMenu, SLOT( deleteLater() ) ); + actionMenu->popupMenu()->popup( p ); +} + +void KopeteWindow::slotTrayAboutToShowMenu( KPopupMenu * popup ) +{ + QPtrList accounts = Kopete::AccountManager::self()->accounts(); + for(Kopete::Account *a=accounts.first() ; a; a=accounts.next() ) + { + KActionMenu *menu = a->actionMenu(); + if( menu ) + menu->plug(popup, 1 ); + + connect(popup , SIGNAL(aboutToHide()) , menu , SLOT(deleteLater())); + } + +} + + + +/*void KopeteWindow::slotProtocolStatusIconRightClicked( Kopete::Protocol *proto, const QPoint &p ) +{ + //kdDebug( 14000 ) << k_funcinfo << endl; + if ( Kopete::AccountManager::self()->accounts( proto ).count() > 0 ) + { + KActionMenu *menu = proto->protocolActions(); + + connect( menu->popupMenu(), SIGNAL( aboutToHide() ), menu, SLOT( deleteLater() ) ); + menu->popupMenu()->popup( p ); + } +}*/ + +void KopeteWindow::showExportDialog() +{ + ( new KabcExportWizard( this, "export_contact_dialog" ) )->show(); +} + +void KopeteWindow::leaveEvent( QEvent * ) +{ + startAutoHideTimer(); +} + +void KopeteWindow::showEvent( QShowEvent * ) +{ + startAutoHideTimer(); +} + +void KopeteWindow::slotAutoHide() +{ + if ( this->geometry().contains( QCursor::pos() ) == false ) + { + /* The autohide-timer doesn't need to emit + * timeouts when the window is hidden already. */ + m_autoHideTimer->stop(); + hide(); + } +} + +void KopeteWindow::startAutoHideTimer() +{ + if ( m_autoHideTimeout > 0 && m_autoHide == true && isVisible() && KopetePrefs::prefs()->showTray()) + m_autoHideTimer->start( m_autoHideTimeout * 1000 ); +} + +// Iterate each connected account, updating its status message bug keeping the +// same onlinestatus. Then update Kopete::Away and the UI. +void KopeteWindow::setStatusMessage( const QString & message ) +{ + bool changed = false; + for ( QPtrListIterator it( Kopete::AccountManager::self()->accounts() ); it.current(); ++it ) + { + Kopete::Contact *self = it.current()->myself(); + bool isInvisible = self && self->onlineStatus().status() == Kopete::OnlineStatus::Invisible; + if ( it.current()->isConnected() && !isInvisible ) + { + changed = true; + it.current()->setOnlineStatus( self->onlineStatus(), message ); + } + } + Kopete::Away::getInstance()->setGlobalAwayMessage( message ); + m_globalStatusMessageStored = message; + m_globalStatusMessage->setText( message ); +} + +void KopeteWindow::slotBuildStatusMessageMenu() +{ + QObject * senderObj = const_cast( sender() ); + m_globalStatusMessageMenu = static_cast( senderObj ); + m_globalStatusMessageMenu->clear(); +// pop up a menu containing the away messages, and a lineedit +// see kopeteaway + //messageMenu = new KPopupMenu( this ); +// messageMenu->insertTitle( i18n( "Status Message" ) ); + QHBox * newMessageBox = new QHBox( 0 ); + newMessageBox->setMargin( 1 ); + QLabel * newMessagePix = new QLabel( newMessageBox ); + newMessagePix->setPixmap( SmallIcon( "edit" ) ); +/* QLabel * newMessageLabel = new QLabel( i18n( "Add " ), newMessageBox );*/ + m_newMessageEdit = new QLineEdit( newMessageBox, "newmessage" ); + + newMessageBox->setFocusProxy( m_newMessageEdit ); + newMessageBox->setFocusPolicy( QWidget::ClickFocus ); +/* newMessageLabel->setFocusProxy( newMessageEdit ); + newMessageLabel->setBuddy( newMessageEdit ); + newMessageLabel->setFocusPolicy( QWidget::ClickFocus );*/ + newMessagePix->setFocusProxy( m_newMessageEdit ); + newMessagePix->setFocusPolicy( QWidget::ClickFocus ); + connect( m_newMessageEdit, SIGNAL( returnPressed() ), SLOT( slotNewStatusMessageEntered() ) ); + + m_globalStatusMessageMenu->insertItem( newMessageBox ); + + int i = 0; + + m_globalStatusMessageMenu->insertItem( SmallIcon( "remove" ), i18n( "No Message" ), i++ ); + m_globalStatusMessageMenu->insertSeparator(); + + QStringList awayMessages = Kopete::Away::getInstance()->getMessages(); + for( QStringList::iterator it = awayMessages.begin(); it != awayMessages.end(); ++it, ++i ) + { + m_globalStatusMessageMenu->insertItem( KStringHandler::rsqueeze( *it ), i ); + } +// connect( m_globalStatusMessageMenu, SIGNAL( activated( int ) ), SLOT( slotStatusMessageSelected( int ) ) ); +// connect( messageMenu, SIGNAL( aboutToHide() ), messageMenu, SLOT( deleteLater() ) ); + + m_newMessageEdit->setFocus(); + + //messageMenu->popup( e->globalPos(), 1 ); +} + +void KopeteWindow::slotStatusMessageSelected( int i ) +{ + Kopete::Away *away = Kopete::Away::getInstance(); + if ( 0 == i ) + setStatusMessage( "" ); + else + setStatusMessage( away->getMessage( i - 1 ) ); +} + +void KopeteWindow::slotNewStatusMessageEntered() +{ + m_globalStatusMessageMenu->close(); + QString newMessage = m_newMessageEdit->text(); + if ( !newMessage.isEmpty() ) + Kopete::Away::getInstance()->addMessage( newMessage ); + setStatusMessage( m_newMessageEdit->text() ); +} + +void KopeteWindow::slotGlobalStatusMessageIconClicked( const QPoint &position ) +{ + KPopupMenu *statusMessageIconMenu = new KPopupMenu(this, "statusMessageIconMenu"); + connect(statusMessageIconMenu, SIGNAL( aboutToShow() ), + this, SLOT(slotBuildStatusMessageMenu())); + connect( statusMessageIconMenu, SIGNAL( activated( int ) ), + SLOT( slotStatusMessageSelected( int ) ) ); + + statusMessageIconMenu->popup(position); +} + +void KopeteWindow::slotAddContactDialogInternal( const QString & accountIdentifier ) +{ + QString protocolId = accountIdentifier.section( QChar(0xE000), 0, 0 ); + QString accountId = accountIdentifier.section( QChar(0xE000), 1, 1 ); + Kopete::Account *account = Kopete::AccountManager::self()->findAccount( protocolId, accountId ); + showAddContactDialog( account ); +} + +void KopeteWindow::showAddContactDialog( Kopete::Account * account ) +{ + if ( !account ) { + kdDebug( 14000 ) << k_funcinfo << "no account given" << endl; + return; + } + + KDialogBase *addDialog = new KDialogBase( this, "addDialog", true, + i18n( "Add Contact" ), KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Ok, true ); + + QVBox * mainWid = new QVBox( addDialog ); + + AddContactPage *addContactPage = + account->protocol()->createAddContactWidget( mainWid, account ); + + GroupKABCSelectorWidget * groupKABC = new GroupKABCSelectorWidget( mainWid, "groupkabcwidget" ); + + // Populate the groups list + Kopete::GroupList groups=Kopete::ContactList::self()->groups(); + QDict groupItems; + for( Kopete::Group *it = groups.first(); it; it = groups.next() ) + { + QString groupname = it->displayName(); + if ( !groupname.isEmpty() ) + { + groupItems.insert( groupname, it ); + groupKABC->groupCombo->insertItem( groupname ); + } + } + + if (!addContactPage) + { + kdDebug(14000) << k_funcinfo << + "Error while creating addcontactpage" << endl; + } + else + { + addDialog->setMainWidget( mainWid ); + if( addDialog->exec() == QDialog::Accepted ) + { + if( addContactPage->validateData() ) + { + Kopete::MetaContact * metacontact = new Kopete::MetaContact(); + metacontact->addToGroup( groupItems[ groupKABC->groupCombo->currentText() ] ); + metacontact->setMetaContactId( groupKABC->widAddresseeLink->uid() ); + if (addContactPage->apply( account, metacontact )) + { + Kopete::ContactList::self()->addMetaContact( metacontact ); + } + else + { + delete metacontact; + } + } + } + } + addDialog->deleteLater(); +} + +#include "kopetewindow.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/kopetewindow.h b/kopete/kopete/kopetewindow.h new file mode 100644 index 00000000..cc466883 --- /dev/null +++ b/kopete/kopete/kopetewindow.h @@ -0,0 +1,280 @@ +/* + kopetewindow.h - Kopete Main Window + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + Copyright (c) 2001-2002 by Stefan Gehn + Copyright (c) 2002-2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEWINDOW_H +#define KOPETEWINDOW_H + +#include + +#include +#include + +class QHBox; +class QTimer; +class QSignalMapper; + +class QMouseEvent; +class QPoint; + +class KAction; +class KActionMenu; + +class KGlobalAccel; +class KSelectAction; +class KSqueezedTextLabel; +class KToggleAction; + +class KopeteAccountStatusBarIcon; +class KopeteContactListView; +class KopetePluginConfig; +class KopeteSystemTray; +class KopeteEditGlobalIdentityWidget; + +namespace Kopete +{ +class AwayAction; +class Account; +class Contact; +class Plugin; +class Protocol; +} + +/** + * @author Duncan Mac-Vicar P. + */ +class KopeteWindow : public KMainWindow +{ + Q_OBJECT + +public: + KopeteWindow ( QWidget *parent = 0, const char *name = 0 ); + ~KopeteWindow(); + + virtual bool eventFilter( QObject* o, QEvent* e ); + +protected: + virtual void closeEvent( QCloseEvent *ev ); + virtual void leaveEvent( QEvent* ev ); + virtual void showEvent( QShowEvent* ev ); + +private slots: + void showMenubar(); + void showStatusbar(); + void slotToggleShowOffliners(); + void slotToggleShowEmptyGroups(); + void slotConfigChanged(); + void slotConfNotifications(); + void slotConfToolbar(); + void slotUpdateToolbar(); + void slotConfigurePlugins(); + void slotConfGlobalKeys(); + void slotShowHide(); + void slotToggleAway(); + + /* show the global status message selector menu + */ + void setStatusMessage( const QString & ); + + /** + * Checks if the mousecursor is in the contact list. + * If not, the window will be hidden. + */ + void slotAutoHide(); + + /** + * This slot will apply settings that change the + * contactlist's appearance. Only autohiding is + * handled here at the moment + */ + void slotContactListAppearanceChanged(); + + /** + * This slot will set all the protocols to away + */ + void slotGlobalAway(); + void slotGlobalBusy(); + void slotGlobalAvailable(); + void slotSetInvisibleAll(); + void slotDisconnectAll(); + + void slotQuit(); + + /** + * Get a notification when a plugin is loaded, so we can merge + * XMLGUI cruft + */ + void slotPluginLoaded( Kopete::Plugin *p ); + + /** + * Get a notification when an account is created, so we can add a status bar + * icon + */ + void slotAccountRegistered( Kopete::Account *a ); + + /** + * Cleanup the status bar icon when the account is destroyed + */ + void slotAccountUnregistered( const Kopete::Account *a); + + /** + * The status icon got changed, update it. + * @param contact The account's contact that changed. + */ + void slotAccountStatusIconChanged( Kopete::Contact * contact); + + /** + * The status icon of some account changed. Must be sent by the account in question. + */ + void slotAccountStatusIconChanged(); + + /** + * Show a context menu for a protocol + */ +// void slotProtocolStatusIconRightClicked( Kopete::Protocol *proto, const QPoint &p ); + + /** + * Show a context menu for an account + */ + void slotAccountStatusIconRightClicked( Kopete::Account *a, + const QPoint &p ); + + void slotTrayAboutToShowMenu(KPopupMenu *); + + /** + * Show the Add Contact wizard + */ + void showAddContactDialog( Kopete::Account * ); + + /** + * Show the Export Contacts wizards + */ + void showExportDialog(); + + /** + * Enable the Connect All and Disconnect All buttons here + * along with connecting the accountRegistered and accountUnregistered + * signals. + */ + void slotAllPluginsLoaded(); + + /** + * Protected slot to setup the Set Global Status Message menu. + */ + void slotBuildStatusMessageMenu(); + void slotStatusMessageSelected( int i ); + void slotNewStatusMessageEntered(); + + /** + * Show the set global status message menu when clicking on the icon in the status bar. + */ + void slotGlobalStatusMessageIconClicked( const QPoint &position ); + + /** + * Extracts protocolId and accountId from the single QString argument signalled by a QSignalMapper, + * get the account, and call showAddContactDialog. + * @param accountIdentifer QString of protocolId and accountId, concatenated with QChar( 0xE000 ) + * We need both to uniquely identify an account, but QSignalMapper only emits one QString. + */ + void slotAddContactDialogInternal( const QString & accountIdentifier ); + +public: + KopeteContactListView *contactlist; + + // Some Actions + KActionMenu* actionAddContact; + + //KActionMenu* actionConnectionMenu; + //KAction* actionConnect; + KAction* actionDisconnect; + KAction* actionExportContacts; + + KActionMenu* actionAwayMenu; + KActionMenu* actionDockMenu; + KAction* selectAway; + KAction* selectBusy; + KAction* actionSetAvailable; + KAction* actionSetInvisible; + + + KAction* actionPrefs; + KAction* actionQuit; + KAction* actionSave; + KToggleAction *menubarAction; + KToggleAction *statusbarAction; + KToggleAction *actionShowOffliners; + KToggleAction *actionShowEmptyGroups; + KGlobalAccel *globalAccel; + + KopeteEditGlobalIdentityWidget *editGlobalIdentityWidget; +private: + void initView(); + void initActions(); + void initSystray(); + void loadOptions(); + void saveOptions(); + + void makeTrayToolTip(); + void startAutoHideTimer(); + + virtual bool queryClose(); + virtual bool queryExit(); +private: + int docked; + bool hidden; + int deskRight; + QPoint position; + QHBox *m_statusBarWidget; + KopeteSystemTray *m_tray; + bool m_autoHide; + unsigned int m_autoHideTimeout; + QTimer* m_autoHideTimer; + QSignalMapper* addContactMapper; + + KopetePluginConfig *m_pluginConfig; + + /** + * This is really a dictionary of KopeteAccountStatusBarIcon objects, but + * QPtrDict requires a full class definition to be known to make + * that work. And since I don't want to include that whole file here, + * use QObject instead. + */ + QPtrDict m_accountStatusBarIcons; + KSqueezedTextLabel * m_globalStatusMessage; + KPopupMenu * m_globalStatusMessageMenu; + QLineEdit * m_newMessageEdit; + QString m_globalStatusMessageStored; +}; + + +class GlobalStatusMessageIconLabel : public QLabel +{ + Q_OBJECT +public: + GlobalStatusMessageIconLabel(QWidget *parent = 0, const char *name = 0); + +protected: + void mouseReleaseEvent(QMouseEvent *event); + +signals: + void iconClicked(const QPoint &position); + +}; + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/main.cpp b/kopete/kopete/main.cpp new file mode 100644 index 00000000..d428c1bc --- /dev/null +++ b/kopete/kopete/main.cpp @@ -0,0 +1,109 @@ +/* + Kopete , The KDE Instant Messenger + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + + Viva Chile Mierda! + Started at Wed Dec 26 03:12:10 CLST 2001, Santiago de Chile + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include "kopeteapplication.h" + +#include +#include "kopeteiface.h" +#include "kimifaceimpl.h" +#include "kopeteversion.h" + +static const char description[] = + I18N_NOOP( "Kopete, the KDE Instant Messenger" ); + +static KCmdLineOptions options[] = +{ + { "noplugins", I18N_NOOP( "Do not load plugins. This option overrides all other options." ), 0 }, + { "noconnect", I18N_NOOP( "Disable auto-connection" ), 0 }, + { "autoconnect ", I18N_NOOP( "Auto-connect the specified accounts. Use a comma-separated list\n" + "to auto-connect multiple accounts." ), 0 }, + { "disable ", I18N_NOOP( "Do not load the specified plugin. Use a comma-separated list\n" + "to disable multiple plugins." ), 0 }, + { "load-plugins ", I18N_NOOP( "Load only the specified plugins. Use a comma-separated list\n" + "to load multiple plugins. This option has no effect when\n" + "--noplugins is set and overrides all other plugin related\n" + "command line options." ), 0 }, +// { "url ", I18N_NOOP( "Load the given Kopete URL" ), 0 }, +// { "!+[plugin]", I18N_NOOP( "Load specified plugins" ), 0 }, + { "!+[URL]", I18N_NOOP("URLs to pass to kopete / emoticon themes to install"), 0}, + KCmdLineLastOption +}; + +int main( int argc, char *argv[] ) +{ + KAboutData aboutData( "kopete", I18N_NOOP("Kopete"), + KOPETE_VERSION_STRING, description, KAboutData::License_GPL, + I18N_NOOP("(c) 2001-2004, Duncan Mac-Vicar Prett\n(c) 2002-2005, Kopete Development Team"), "kopete-devel@kde.org", "http://kopete.kde.org"); + + aboutData.addAuthor ( "Duncan Mac-Vicar Prett", I18N_NOOP("Developer and Project founder"), "duncan@kde.org", "http://www.mac-vicar.org/~duncan" ); + aboutData.addAuthor ( "Andre Duffeck", I18N_NOOP("Developer, Yahoo plugin maintainer"), "andre@duffeck.de" ); + aboutData.addAuthor ( "Andy Goossens", I18N_NOOP("Developer"), "andygoossens@telenet.be" ); + aboutData.addAuthor ( "Chetan Reddy", I18N_NOOP("Developer, Yahoo"), "chetan13@gmail.com" ); + aboutData.addAuthor ( "Chris Howells", I18N_NOOP("Developer, Connection status plugin author"), "howells@kde.org", "http://chrishowells.co.uk"); + aboutData.addAuthor ( "Cláudio da Silveira Pinheiro", I18N_NOOP("Developer, Video device support"), "taupter@gmail.com", "http://taupter.homelinux.org" ); + aboutData.addAuthor ( "Gregg Edghill", I18N_NOOP("Developer, MSN"), "gregg.edghill@gmail.com"); + aboutData.addAuthor ( "Grzegorz Jaskiewicz", I18N_NOOP("Developer, Gadu plugin maintainer"), "gj@pointblue.com.pl" ); + aboutData.addAuthor ( "Jason Keirstead", I18N_NOOP("Developer"), "jason@keirstead.org", "http://www.keirstead.org"); + aboutData.addAuthor ( "Matt Rogers", I18N_NOOP("Lead Developer, AIM and ICQ plugin maintainer"), "mattr@kde.org" ); + aboutData.addAuthor ( "Michel Hermier", I18N_NOOP("IRC plugin maintainer"), "michel.hermier@wanadoo.fr" ); + aboutData.addAuthor ( "Michaël Larouche", I18N_NOOP("Lead Developer"), "larouche@kde.org", "http://www.tehbisnatch.org/" ); + aboutData.addAuthor ( "Olivier Goffart", I18N_NOOP("Lead Developer, MSN plugin maintainer"), "ogoffart @ kde.org"); + aboutData.addAuthor ( "Ollivier Lapeyre Johann", I18N_NOOP("Artist / Developer, Artwork maintainer"), "johann.ollivierlapeyre@gmail.com" ); + aboutData.addAuthor ( "Richard Smith", I18N_NOOP("Developer, UI maintainer"), "kde@metafoo.co.uk" ); + aboutData.addAuthor ( "Till Gerken", I18N_NOOP("Developer, Jabber plugin maintainer"), "till@tantalo.net"); + aboutData.addAuthor ( "Will Stephenson", I18N_NOOP("Lead Developer, GroupWise maintainer"), "lists@stevello.free-online.co.uk" ); + + aboutData.addCredit ( "Vally8", I18N_NOOP("Konki style author"), "vally8@gmail.com", "http://vally8.free.fr/" ); + aboutData.addCredit ( "Tm_T", I18N_NOOP("Hacker style author"), "jussi.kekkonen@gmail.com"); + aboutData.addCredit ( "Luciash d' Being", I18N_NOOP("Kopete's icon author") ); + aboutData.addCredit ( "Steve Cable", I18N_NOOP("Sounds") ); + aboutData.addCredit ( "Jessica Hall", I18N_NOOP("Kopete Docugoddess, Bug and Patch Testing.") ); + aboutData.addCredit ( "Justin Karneges", I18N_NOOP("Iris Jabber Backend Library") ); + aboutData.addCredit ( "Tom Linsky", I18N_NOOP("OscarSocket author"), "twl6@po.cwru.edu" ); + aboutData.addCredit ( "Olaf Lueg", I18N_NOOP("Kmerlin MSN code") ); + aboutData.addCredit ( "Nick Betcher", I18N_NOOP("Former developer, project co-founder"), "nbetcher@kde.org"); + aboutData.addCredit ( "Ryan Cumming", I18N_NOOP("Former developer"), "ryan@kde.org" ); + aboutData.addCredit ( "Stefan Gehn", I18N_NOOP("Former developer"), "metz@gehn.net", "http://metz.gehn.net" ); + aboutData.addCredit ( "Martijn Klingens", I18N_NOOP("Former developer"), "klingens@kde.org" ); + aboutData.addCredit ( "Andres Krapf", I18N_NOOP("Former developer"), "dae@chez.com" ); + aboutData.addCredit ( "Carsten Pfeiffer", I18N_NOOP("Misc bugfixes and enhancements"), "pfeiffer@kde.org" ); + aboutData.addCredit ( "Zack Rusin", I18N_NOOP("Former developer, original Gadu plugin author"), "zack@kde.org" ); + aboutData.addCredit ( "Richard Stellingwerff", I18N_NOOP("Former developer"), "remenic@linuxfromscratch.org"); + aboutData.addCredit ( "Daniel Stone", I18N_NOOP("Former developer, Jabber plugin author"), "daniel@fooishbar.org", "http://fooishbar.org"); + aboutData.addCredit ( "Chris TenHarmsel", I18N_NOOP("Former developer, Oscar plugin"), "tenharmsel@users.sourceforge.net"); + aboutData.addCredit ( "Hendrik vom Lehn", I18N_NOOP("Former developer"), "hennevl@hennevl.de", "http://www.hennevl.de"); + aboutData.addCredit ( "Gav Wood", I18N_NOOP("Former developer and WinPopup maintainer"), "gav@indigoarchive.net" ); + + aboutData.setTranslator( I18N_NOOP("_: NAME OF TRANSLATORS\nYour names"), + I18N_NOOP("_: EMAIL OF TRANSLATORS\nYour emails") ); + + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineArgs::addCmdLineOptions( options ); // Add our own options. + KUniqueApplication::addCmdLineOptions(); + + KopeteApplication kopete; + new KIMIfaceImpl(); + kapp->dcopClient()->registerAs( "kopete", false ); + kapp->dcopClient()->setDefaultObject( (new KopeteIface())->objId() ); // Has to be called before exec + + kopete.exec(); +} +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/systemtray.cpp b/kopete/kopete/systemtray.cpp new file mode 100644 index 00000000..5ed018c5 --- /dev/null +++ b/kopete/kopete/systemtray.cpp @@ -0,0 +1,435 @@ +/* + systemtray.cpp - Kopete Tray Dock Icon + + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2003-2004 by Olivier Goffart + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "systemtray.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "kopeteuiglobal.h" +#include "kopetechatsessionmanager.h" +#include "kopeteballoon.h" +#include "kopeteprefs.h" +#include "kopetemetacontact.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetecontact.h" +#include "kopetewindow.h" + +KopeteSystemTray* KopeteSystemTray::s_systemTray = 0L; + +KopeteSystemTray* KopeteSystemTray::systemTray( QWidget *parent, const char* name ) +{ + if( !s_systemTray ) + s_systemTray = new KopeteSystemTray( parent, name ); + + return s_systemTray; +} + +KopeteSystemTray::KopeteSystemTray(QWidget* parent, const char* name) + : KSystemTray(parent,name) +{ +// kdDebug(14010) << "Creating KopeteSystemTray" << endl; + QToolTip::add( this, kapp->aboutData()->shortDescription() ); + + mIsBlinkIcon = false; + mIsBlinking = false; + mBlinkTimer = new QTimer(this, "mBlinkTimer"); + + mKopeteIcon = loadIcon("kopete"); + + connect(mBlinkTimer, SIGNAL(timeout()), this, SLOT(slotBlink())); + connect(Kopete::ChatSessionManager::self() , SIGNAL(newEvent(Kopete::MessageEvent*)), + this, SLOT(slotNewEvent(Kopete::MessageEvent*))); + connect(KopetePrefs::prefs(), SIGNAL(saved()), this, SLOT(slotConfigChanged())); + + connect(Kopete::AccountManager::self(), + SIGNAL(accountOnlineStatusChanged(Kopete::Account *, + const Kopete::OnlineStatus &, const Kopete::OnlineStatus &)), + this, SLOT(slotReevaluateAccountStates())); + + // the slot called by default by the quit action, KSystemTray::maybeQuit(), + // just closes the parent window, which is hard to distinguish in that window's closeEvent() + // from a click on the window's close widget + // in the quit case, we want to quit the application + // in the close widget click case, we only want to hide the parent window + // so instead, we make it call our general purpose quit slot on the window, which causes a window close and everything else we need + // KDE4 - app will have to listen for quitSelected instead + KAction *quit = actionCollection()->action( "file_quit" ); + quit->disconnect(); + KopeteWindow *myParent = static_cast( parent ); + connect( quit, SIGNAL( activated() ), myParent, SLOT( slotQuit() ) ); + + //setPixmap(mKopeteIcon); + slotReevaluateAccountStates(); + slotConfigChanged(); + + m_balloon=0l; +} + +KopeteSystemTray::~KopeteSystemTray() +{ +// kdDebug(14010) << "[KopeteSystemTray] ~KopeteSystemTray" << endl; +// delete mBlinkTimer; + Kopete::UI::Global::setSysTrayWId( 0 ); +} + +void KopeteSystemTray::mousePressEvent( QMouseEvent *me ) +{ + if ( + (me->button() == QEvent::MidButton || + (me->button() == QEvent::LeftButton && KopetePrefs::prefs()->trayflashNotifyLeftClickOpensMessage())) && + mIsBlinking ) + { + mouseDoubleClickEvent( me ); + return; + } + + KSystemTray::mousePressEvent( me ); +} + +void KopeteSystemTray::mouseDoubleClickEvent( QMouseEvent *me ) +{ + if ( !mIsBlinking ) + { + KSystemTray::mousePressEvent( me ); + } + else + { + if(!mEventList.isEmpty()) + mEventList.first()->apply(); + } +} + +void KopeteSystemTray::contextMenuAboutToShow( KPopupMenu *me ) +{ + //kdDebug(14010) << k_funcinfo << "Called." << endl; + emit aboutToShowMenu( me ); +} + +void KopeteSystemTray::startBlink( const QString &icon ) +{ + startBlink( KGlobal::iconLoader()->loadIcon( icon , KIcon::Panel ) ); +} + +void KopeteSystemTray::startBlink( const QPixmap &icon ) +{ + mBlinkIcon = icon; + if ( mBlinkTimer->isActive() == false ) + { + mIsBlinkIcon = true; + mIsBlinking = true; + mBlinkTimer->start( 1000, false ); + } + else + { + mBlinkTimer->stop(); + mIsBlinkIcon = true; + mIsBlinking = true; + mBlinkTimer->start( 1000, false ); + } +} + +void KopeteSystemTray::startBlink( const QMovie &movie ) +{ + //kdDebug( 14010 ) << k_funcinfo << "starting movie." << endl; + const_cast( movie ).unpause(); + setMovie( movie ); + mIsBlinking = true; +} + +void KopeteSystemTray::startBlink() +{ + if ( mMovie.isNull() ) + mMovie = KGlobal::iconLoader()->loadMovie( QString::fromLatin1( "newmessage" ), KIcon::Panel ); + + startBlink( mMovie ); +} + +void KopeteSystemTray::stopBlink() +{ + if ( movie() ) + kdDebug( 14010 ) << k_funcinfo << "stopping movie." << endl; + else if ( mBlinkTimer->isActive() ) + mBlinkTimer->stop(); + + if ( !mMovie.isNull() ) + mMovie.pause(); + + mIsBlinkIcon = false; + mIsBlinking = false; + //setPixmap( mKopeteIcon ); + slotReevaluateAccountStates(); +} + +void KopeteSystemTray::slotBlink() +{ + setPixmap( mIsBlinkIcon ? mKopeteIcon : mBlinkIcon ); + + mIsBlinkIcon = !mIsBlinkIcon; +} + +void KopeteSystemTray::slotNewEvent( Kopete::MessageEvent *event ) +{ + if( KopetePrefs::prefs()->useStack() ) + { + mEventList.prepend( event ); + mBalloonEventList.prepend( event ); + } + else + { + mEventList.append( event ); + mBalloonEventList.append( event ); + } + + connect(event, SIGNAL(done(Kopete::MessageEvent*)), + this, SLOT(slotEventDone(Kopete::MessageEvent*))); + + if( event->message().manager() != 0 ) + { + if( event->message().manager()->account() ) + { + if( !event->message().manager()->account()->isAway() || + KopetePrefs::prefs()->soundIfAway() ) + { + addBalloon(); + } + else + { + kdDebug(14000) << k_funcinfo << "Supressing balloon, account is away" << endl; + } + } + } + else + kdDebug(14000) << k_funcinfo << "NULL message().manager()!" << endl; + + // tray animation + if ( KopetePrefs::prefs()->trayflashNotify() ) + if( mBalloonEventList.count() == mEventList.count() ) + startBlink(); + else + stopBlink(); +} + +void KopeteSystemTray::slotEventDone(Kopete::MessageEvent *event) +{ + mEventList.remove(event); + + removeBalloonEvent(event); + + if(mEventList.isEmpty()) + stopBlink(); +} + +void KopeteSystemTray::slotRemoveBalloon() +{ + removeBalloonEvent(mBalloonEventList.first()); +} + +void KopeteSystemTray::removeBalloonEvent(Kopete::MessageEvent *event) +{ + bool current= event==mBalloonEventList.first(); + mBalloonEventList.remove(event); + + if(current && m_balloon) + { + m_balloon->deleteLater(); + m_balloon=0l; + if(!mBalloonEventList.isEmpty()) + { + //delay the addBalloon to let the time to event be deleted + //in case a contact has been deleted cf Bug 100196 + QTimer::singleShot(0, this, SLOT(addBalloon())); + } + else + { + if(KopetePrefs::prefs()->trayflashNotify() && !mEventList.isEmpty()) + startBlink(); + } + } +} + +void KopeteSystemTray::addBalloon() +{ + /*kdDebug(14010) << k_funcinfo << + m_balloon << ":" << KopetePrefs::prefs()->showTray() << + ":" << KopetePrefs::prefs()->balloonNotify() + << ":" << !mBalloonEventList.isEmpty() << endl;*/ + + if( m_balloon && KopetePrefs::prefs()->useStack() ) + { + m_balloon->deleteLater(); + m_balloon=0l; + } + + if( !m_balloon && KopetePrefs::prefs()->showTray() && KopetePrefs::prefs()->balloonNotify() && !mBalloonEventList.isEmpty() ) + { + Kopete::Message msg = mBalloonEventList.first()->message(); + + if ( msg.from() ) + { + QString msgText = squashMessage( msg ); + kdDebug(14010) << k_funcinfo << "msg text=" << msgText << endl; + + QString msgFrom; + if( msg.from()->metaContact() ) + msgFrom = msg.from()->metaContact()->displayName(); + else + msgFrom = msg.from()->contactId(); + + m_balloon = new KopeteBalloon( + i18n( "New Message from %1:
    \"%2\"" ) + .arg( QStyleSheet::escape( msgFrom ), msgText ), QString::null ); + connect(m_balloon, SIGNAL(signalBalloonClicked()), mBalloonEventList.first() , SLOT(apply())); + connect(m_balloon, SIGNAL(signalButtonClicked()), mBalloonEventList.first() , SLOT(apply())); + connect(m_balloon, SIGNAL(signalIgnoreButtonClicked()), mBalloonEventList.first() , SLOT(ignore())); + connect(m_balloon, SIGNAL(signalTimeout()), this , SLOT(slotRemoveBalloon())); + m_balloon->setAnchor(mapToGlobal(pos())); + m_balloon->show(); + KWin::setOnAllDesktops(m_balloon->winId(), true); + } + } +} + +void KopeteSystemTray::slotConfigChanged() +{ +// kdDebug(14010) << k_funcinfo << "called." << endl; + if ( KopetePrefs::prefs()->showTray() ) + show(); + else + hide(); // for users without kicker or a similar docking app +} + +void KopeteSystemTray::slotReevaluateAccountStates() +{ + // If there is a pending message, we don't need to refresh the system tray now. + // This function will even be called when the animation will stop. + if ( mIsBlinking ) + return; + + + //kdDebug(14010) << k_funcinfo << endl; + bool bOnline = false; + bool bAway = false; + bool bOffline = false; + Kopete::Contact *c = 0; + + for (QPtrListIterator it(Kopete::AccountManager::self()->accounts()); it.current(); ++it) + { + c = it.current()->myself(); + if (!c) + continue; + + if (c->onlineStatus().status() == Kopete::OnlineStatus::Online) + { + bOnline = true; // at least one contact is online + } + else if (c->onlineStatus().status() == Kopete::OnlineStatus::Away + || c->onlineStatus().status() == Kopete::OnlineStatus::Invisible) + { + bAway = true; // at least one contact is away or invisible + } + else // this account must be offline (or unknown, which I don't know how to handle) + { + bOffline = true; + } + } + + if (!bOnline && !bAway && !bOffline) // special case, no accounts defined (yet) + bOffline = true; + + if (bAway) + { + if (!bOnline && !bOffline) // none online and none offline -> all away + setPixmap(loadIcon("kopete_all_away")); + else + setPixmap(loadIcon("kopete_some_away")); + } + else if(bOnline) + { + /*if(bOffline) // at least one offline and at least one online -> some accounts online + setPixmap(loadIcon("kopete_some_online")); + else*/ // none offline and none away -> all online + setPixmap(mKopeteIcon); + } + else // none away and none online -> all offline + { + //kdDebug(14010) << k_funcinfo << "All Accounts offline!" << endl; + setPixmap(loadIcon("kopete_offline")); + } +} + + +QString KopeteSystemTray::squashMessage( const Kopete::Message& msg ) +{ + QString msgText = msg.parsedBody(); + + QRegExp rx( "(((http://)?(.+)))" ); + rx.setMinimal( true ); + if ( rx.search( msgText ) == -1 ) + { + // no URLs in text, just pick the first 30 chars of + // the parsed text if necessary. We used parsed text + // so that things like "" show correctly + // Escape it after snipping it to not snip entities + msgText =msg.plainBody() ; + if( msgText.length() > 30 ) + msgText = msgText.left( 30 ) + QString::fromLatin1( " ..." ); + msgText=Kopete::Message::escape(msgText); + } + else + { + QString plainText = msg.plainBody(); + if ( plainText.length() > 30 ) + { + QString fullUrl = rx.cap( 2 ); + QString shorterUrl; + if ( fullUrl.length() > 30 ) + { + QString urlWithoutProtocol = rx.cap( 4 ); + shorterUrl = urlWithoutProtocol.left( 27 ) + + QString::fromLatin1( "... " ); + } + else + { + shorterUrl = fullUrl.left( 27 ) + + QString::fromLatin1( "... " ); + } + // remove message text + msgText = QString::fromLatin1( "... " ) + + rx.cap( 1 ) + + QString::fromLatin1( " ..." ); + // find last occurrence of URL (the one inside the tag) + int revUrlOffset = msgText.findRev( fullUrl ); + msgText.replace( revUrlOffset, + fullUrl.length(), shorterUrl ); + } + } + return msgText; +} + +#include "systemtray.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopete/systemtray.h b/kopete/kopete/systemtray.h new file mode 100644 index 00000000..223cb173 --- /dev/null +++ b/kopete/kopete/systemtray.h @@ -0,0 +1,104 @@ +/* + systemtray.h - Kopete Tray Dock Icon + + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2003 by Olivier Goffart + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef SYSTEMTRAY_H +#define SYSTEMTRAY_H + +#include +#include + +#include + +#include "kopetemessageevent.h" + +class QTimer; +class QPoint; +class KPopupMenu; +class KActionMenu; +class KopeteBalloon; + +/** + * @author Nick Betcher + * + * NOTE: This class is for use ONLY in libkopete! It is not public API, and + * is NOT supposed to remain binary compatible in the future! + */ +class KopeteSystemTray : public KSystemTray +{ + Q_OBJECT + +public: + /** + * Retrieve the system tray instance + */ + static KopeteSystemTray* systemTray( QWidget* parent = 0, const char* name = 0 ); + + ~KopeteSystemTray(); + + // One method, multiple interfaces :-) + void startBlink( const QString &icon ); + void startBlink( const QPixmap &icon ); + void startBlink( const QMovie &movie ); + void startBlink(); + + void stopBlink(); + bool isBlinking() const { return mIsBlinking; }; + KPopupMenu *contextMenu() const { return KSystemTray::contextMenu(); }; + +protected: + virtual void mousePressEvent( QMouseEvent *e ); + virtual void mouseDoubleClickEvent( QMouseEvent *me ); + virtual void contextMenuAboutToShow( KPopupMenu * ); + +signals: + void aboutToShowMenu(KPopupMenu *am); + +private slots: + void slotBlink(); + void slotNewEvent(Kopete::MessageEvent*); + void slotEventDone(Kopete::MessageEvent *); + void slotConfigChanged(); + void slotReevaluateAccountStates(); + void slotRemoveBalloon(); + void addBalloon(); + +private: + KopeteSystemTray( QWidget* parent, const char* name ); + QString squashMessage( const Kopete::Message& msgText ); + void removeBalloonEvent(Kopete::MessageEvent *); + + QTimer *mBlinkTimer; + QPixmap mKopeteIcon; + QPixmap mBlinkIcon; + QMovie mMovie; + + bool mIsBlinkIcon; + bool mIsBlinking; + + static KopeteSystemTray* s_systemTray; + + QPtrList mEventList; + QPtrList mBalloonEventList; + KopeteBalloon *m_balloon; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/kopete/x-kopete-emoticons.desktop b/kopete/kopete/x-kopete-emoticons.desktop new file mode 100644 index 00000000..45f0ee79 --- /dev/null +++ b/kopete/kopete/x-kopete-emoticons.desktop @@ -0,0 +1,57 @@ +[Desktop Entry] +Type=MimeType +MimeType=application/x-kopete-emoticons +DefaultApp=kopete +Icon=kopete_emoticons +Patterns=*.kopete-emoticons +X-KDE-AutoEmbed=false +Comment=Kopete Emoticon Archive +Comment[be]=Ðрхіў Ñмацыйных значак Kopete +Comment[bg]=Ðрхив на икони за Kopete +Comment[bn]=কপেট অভিবà§à¦¯à¦•à§à¦¤à¦¿ পà§à¦°à¦¤à§€à¦• আরà§à¦•à¦¾à¦‡à¦­ +Comment[bs]=Kopete arhiva smajlija +Comment[ca]=Arxiu d'emoticones Kopete +Comment[cs]=Archív Kopete emotikonů +Comment[da]=Kopete emotikon-arkiv +Comment[de]=Kopete Emoticon-Archiv +Comment[el]=ΑÏχειοθήκη emoticon του Kopete +Comment[es]=Archivo de emoticonos de Kopete +Comment[et]=Kopete emotikoniarhiiv +Comment[eu]=Kopete emoticon artxiboa +Comment[fa]=بایگانی تصاویر متحرک Kopete +Comment[fi]=Kopeten hymiöarkisto +Comment[fr]=Archive d'émoticônes pour Kopete +Comment[gl]=Arquivo de Emoticonas de Kopete +Comment[he]=×רכיון ערכת רגשות של Kopete +Comment[hr]=Kopete Emoticon arhiva +Comment[hu]=Kopete emotikon-archívum +Comment[is]=Tilfynningatákn fyrir Kopete +Comment[it]=Archivio emoticon di Kopete +Comment[ja]=Kopete ã®æ„Ÿæƒ…アイコン集 +Comment[ka]=Kopeteს ემáƒáƒªáƒ˜áƒáƒ—რáƒáƒ áƒ¥áƒ˜áƒ•áƒ˜ +Comment[kk]=Kopete белгілер архиві +Comment[km]=áž”áŸážŽáŸ’ណសារ​សញ្ញា​អារម្មណ០Kopete +Comment[lt]=Kopete Å¡ypsniukų archyvas +Comment[mk]=Ðрхива Ñо емотикони за Kopete +Comment[nb]=Kopete fjesingarkiv +Comment[nds]=Kopete-Snutenarchiv +Comment[ne]=कोपेट इमोटिकन सङà¥à¤—à¥à¤°à¤¹ +Comment[nl]=Kopete Emoticon-archief +Comment[nn]=Fjesingarkiv for Kopete +Comment[pl]=Archiwum emotikon Kopete +Comment[pt]=Arquivo de 'Emoticons' do Kopete +Comment[pt_BR]=Pacote de Emoticon do Kopete +Comment[ru]=Ðрхив значков Ð´Ð»Ñ Kopete +Comment[se]=Kopete:a «emoticon»-vuorká +Comment[sk]=Archív smajlíkov Kopete +Comment[sl]=Arhiv z ikonami Äustev za Kopete +Comment[sr]=Kopete-ова архива емотикона +Comment[sr@Latn]=Kopete-ova arhiva emotikona +Comment[sv]=Kopetes smilisarkiv +Comment[ta]=Kopete எமோடிகà¯à®•à®¾à®©à¯ வளைவ௠+Comment[tg]=Бойгонии Kopete Emoticon +Comment[tr]=Kopete His Simgeleri ArÅŸivi +Comment[uk]=Ðрхів емоційок Ð´Ð»Ñ Kopete +Comment[zh_CN]=Kopete 表情存档 +Comment[zh_HK]=Kopete 表情符號集 +Comment[zh_TW]=Kopete 表情圖示檔 diff --git a/kopete/libkopete/API-TODO b/kopete/libkopete/API-TODO new file mode 100644 index 00000000..ea63082b --- /dev/null +++ b/kopete/libkopete/API-TODO @@ -0,0 +1,248 @@ +This file is a listing of (proposed) changes to be made to libkopete's API. + + + Buddy Icons: +============== + +Some support for buddy icons is needed in libkopete. Maybe just a simple contact property and a +metacontact property computed from it will do. + + + Properties: +============= + +The current properties system is a bit of a mess. With the PluginDataObjects, the ContactProperties, +KopeteContact's serializeProperties, and the various properties which are stored specially but +shouldn't be (such as KopeteGroup::expanded) it's hard to know where you are. I (Richard) would like +to replace this whole set of different property systems with a unified one. The way I see this +working is as follows: +- An object is created for each property. That object represents the property, and knows how to read + set store and manipulate that property. +- Properties on objects supporting them (KopeteContact, KopeteMetaContact, KopeteGroup, and so on) + will be accessed in a uniform manner, eg: + contact(myProperty) = 42; + metaContact(displayName) = i18n("Ford Prefect"); + The exact notation is not finalised yet. Maybe X[...] or X.property(...) would be clearer? +- New types of properties with different serialization requirements and so on can easily be created +- MetaContact properties can be computed from Contact properties (eg, for buddy icon, away message, + idle time and so on) by writing an appropriate property class. +- This system would be extended to plugin configuration, so plugin authors can easily deal with + configuration variables without having to use strings as keys. + + + KopeteMessageManager: +======================= + +KopeteMessageManager should allow any number of views to be attached, from 0 +to infinity. + +A lot of code should be moved from the KopeteViewManager to the KopeteMessageManager. +Allowing the creation of chatwindow plugin more easy + +The chat window should be restructured so each ChatView object has its own +send button. (-that's not a part of the libkopete api-) + + + KopeteMessage: +================ + +KopeteMessage should be reorganised to use QDomDocument or something similar +to store its contents - the purpose of this is so libkopete can provide a +uniform way of dealing with messages; the emoticon and link-adding filters +don't then need to grok HTML in order to be able to mangle a message + + + KopeteContactList +=================== + +Add to KopeteMetaContact a statusPixmap() function to return the pixmap +associated with its name. Take implementation from KopeteMetaContactLVI. +Use this function in the Kopete::MimeSourceFactory so we get MCs being +grayed if they're idle in tooltips too. + +KopeteContactList::removeGroup should remove the group from all +metacontacts. Move code from KopeteContactListView to KopeteContactList for +this. + +KopeteContactList::findContact and KopeteMetaContact::findContact should maybe be removed, +or at least contains ptr to accounts insteads of id. + +KopeteContact::slotDeleteContact should be renamed, maybe return a bool to cancel the deletion of the contact. + +Add an iconName() function to contacts, metacontacts and accounts returning a string that +can be put in an in any rich text to give the appropriate icon. See the +MimeSourceFactory. + +KCL::selectedMetaContacts really doesn't belong here. A contact list shouldn't +have a concept of selected items (this is action- and UI-specific knowledge). +The only place this is used is when plugins' actions need to be enabled and +disabled, or applied. Find a better way to do this UI-specific task. +KCL::selectedGroups can be removed outright. + + + KopeteEmoticon +================ + +Allow emoticons and emoticon sets to be flagged as being for only a specific protocol. +Allow the user to have more than one emoticon set enabled at once, and to set priorities. +This way, the user will be able to have a base theme, a set of MSN-specific emoticons, a +set of Gadu-Gadu-specific emoticons and so on. + +Possibly move emoticon support into a plugin? + + + KopeteOnlineStatus +==================== + +Add an Unknown status to KopeteOnlineStatus for when the status of a +contact is unknown (in the case that they've not authorised you, or the +protocol does not provide presence information) and a status for Unreachable +(in the case that your account is offline, etc). The crucial difference is +that a contact with Unknown status may be reachable (though that contact +should probably be avoided for messaging unless nothing else is available). + +More granular away settings: see Bug 57297. +The number of different global statuses (away / busy / be right back) should +be extended to a configurable list where each element contains the name of the +status, the default away message, and the KOS for every account)... though +perhaps this would be better placed in an 'advanced status' plugin? + +Add a way to register automatically KOS. The code for the right-click menu in +the Kopete account tray is duplicated all over the place; this should be +done automatically by the account tray code. + + + KopeteAccount +=============== + +KopeteAccount::password should be split in two method: KopeteAccount::password which return the +remembered password if any, but which does not try to ask it to the user. and getPassword which +acts like the acutal function. + + KopeteAccount will soon have no ::password. instead, use Kopete::PasswordedAccount, + and acct.password().request(...) or acct.password().cachedValue() + + + DCOP +====== +The DCOP interface needs to be totally re-done so as to be useful, and to hopefully not rely on +display names. Obsolete functions previously used only for DCOP should be removed from KopeteContactList +where applicable. + + + KopeteTransferManager +======================= + +The file transfer mechanisms should be available to plugins... ie, a plugin should be able to +both initiate file transfers, and intercept and possibly cancel file transfer requests, the exact +same as plugins can ( will ) be able to filter KopeteMessages ( see below ). + + + Message Processing +==================== + +Some sort of async message processing API needs to be designed and implemented +Richard's proposal: (email questions to the list or to kde@metafoo.co.uk) +- how do we order the various message filters available properly? + they give us a processing stage, and an offset within that stage. the + stages will be something like: + for an incoming message: + - Start - message was just received (History) + - ToSent - convert from received format to sent format (GPG) + - ToDesired - convert to how the user wants the message (Translator, AutoReplace) + ToDesired+Before - Highlight + - Format - decorate the message (without changing the content) (Links, Emoticons, TextEffect) + - Finished - message is now ready for display (ChatWindow / MessageQueue) + for an outgoing message: + - Start - user just hit Send + - Parse - process commands (CommandHandler, Alias, Now Listening) + Parse+After - History + - ToDesired - convert to how the user wanted to send (Translator, AutoReplace) + - Format - decorate the message (without changing the content) (TextEffect) + - ToSent - convert to the format to send in (GPG) + - Finished - message is now ready for sending (Protocols) + There should be a number of offsets defined for when to do the + processing, within a stage, such as: + - Before - before any other processing in this stage + - VeryEarly + - Early + - Normal + - Late + - VeryLate + - After - after any other processing in this stage +- how do we construct a set of message filters for a particular message + manager? + - message filters register themselves with the filter manager, with a + message direction, a stage and an offset within that stage. + - each registered message filter factory gets queried (in stage/offset + order) by the object creating the filter chain. it either returns a + new filter for the chain, or returns NULL (meaning this filter is not + needed in this chain). + - the signals in one filter are connected to the slots in the next. any + sent/received message is handed to the first filter in the appropriate + chain. +- how long does a filter chain live for? + - it's created when it's first needed (when a message is sent / received + and no chain already exists to process it, or when a chatwindow is + opened) + - it's reference counted + - the MessageQueue / ChatWindow holds a reference to its chains + - the chain knows how many messages are in it (the messages unregister + themselves when they're destroyed) + - this makes it trivial to implement 65803 - stay in chatwindows when no + window is open - just make the Kopete::Contact hold a reference to the + receive chain +- interactions with the chat manager + - the chat manager (or possibly just 'chat') is an abstraction for a + conversation between our client/user and some other computer/user. it's + a bit like the message manager we have now, but more sophisticated. + - the send and receive chains are fundamentally linked - they are owned + by the same chat manager (which has a chainFor(MessageDirection) + function) + - when a chain's reference count drops to 0, it stays alive until + all the messages in it have been processed, but calls to + chainFor(Outgoing) will create a new chain. if we want, we can + guarantee messages from the old chain get sent over the wire before + ones from the new chain, but it's probably not essential. +- interactions with a chat view + - the ChatWindow component above is actually the ChatWindowFilter. it's + owned by the filter chain, and so should not be a QWidget. + - when a chat view is closed, it drops its reference to the various + message chains. but the receive chain will still exist if there's an + incoming message that's still being processed. therefore: + - the chatwindow prompts you if you ask it to be closed and there are + messages left in its receive chain + - the chatwindow filter will *drop* messages that reach it if there's no + chatview available to send them to. but that's ok since the user will + already have been prompted about this. +- problems with this design + - when the receive chain is closed (refcount drops to 0), it's not + necessarily the case that messages in it still need to be processed. + for instance, if you don't use the History plugin, or all the messages + are already past it, it probably doesn't matter if they're dropped. we + should somehow allow the filters to prevent destruction of the part of + the chain before them, and if none of them does, destroy it. + + + + Invitation Handling Proposal: +=============================== + +Invitations is framework that allow others network applications (Games, Desktop +Sharing, Video conference, [file transfer?], ...) to initiate the communication +with kopete. (like Windows Messenger does) + +The user has two ways to initiate such as thing: + +- in the application itself, they could (with the help of KABC) select a + contactable contact; the invitaiton is transported to Kopete. +- in Kopete, in the chat window, an "tools" menu, with all possible actions. + + + + Blacklist support: +==================== + + BlackList API is added. Protocols maintainers, please check if a contact + is blocked before passing messages to MessageManager. + I will also attach the GUI to block() and unblock() in the near future. \ No newline at end of file diff --git a/kopete/libkopete/Makefile.am b/kopete/libkopete/Makefile.am new file mode 100644 index 00000000..47e6b9cf --- /dev/null +++ b/kopete/libkopete/Makefile.am @@ -0,0 +1,71 @@ +if compile_LIBKOPETE_COMPAT +COMPAT_DIR = compat +COMPAT_LIBS = compat/libkopetecompat.la +endif + + +include ../../admin/Doxyfile.am +DOXYGEN_REFERENCES = kio kdecore kdeui +DOXYGEN_EXCLUDE = compat +DOXYGEN_SET_PROJECT_NAME = libkopete + +SUBDIRS = $(COMPAT_DIR) private ui . avdevice + +METASOURCES = AUTO + +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_COMPAT -DQT_NO_CAST_ASCII -DQT_NO_ASCII_CAST \ + $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/libkopete/private \ + -I$(top_srcdir)/kopete/libkopete/ui $(all_includes) + +lib_LTLIBRARIES = libkopete.la + +libkopete_la_SOURCES = knotification.cpp connectionmanager.cpp kopeteonlinestatus.cpp kopeteonlinestatusmanager.cpp \ + kopeteprotocol.cpp kopetecontact.cpp kopetepluginmanager.cpp kopeteplugin.cpp \ + kopetemessage.cpp kopetechatsession.cpp kopetechatsessionmanager.cpp \ + kopetecontactlist.cpp kopetemetacontact.cpp kopeteawaydialog.cpp kopetetransfermanager.cpp \ + kopetegroup.cpp kcautoconfigmodule.cpp kopeteaccountmanager.cpp kopeteaccount.cpp \ + kopetecontactlistelement.cpp kopetecommandhandler.cpp kopeteaway.cpp \ + kopeteawayaction.cpp kautoconfig.cpp kopetewalletmanager.cpp kopetecontactproperty.cpp \ + kopetepassword.cpp kopeteglobal.cpp kopeteuiglobal.cpp kopetepasswordedaccount.cpp \ + kopetemimetypehandler.cpp kopetetask.cpp kopetemimesourcefactory.cpp \ + kopeteeventpresentation.cpp kopetenotifyevent.cpp kopetenotifydataobject.cpp kopeteblacklister.cpp \ + kopetemessageevent.cpp kopetemessagehandler.cpp kopetemessagehandlerchain.cpp \ + kopetesimplemessagehandler.cpp kopeteproperties.cpp kabcpersistence.cpp connectionmanager.skel \ + clientiface.stub managedconnectionaccount.cpp networkstatuscommon.h kopeteconfig.kcfgc kopeteutils.cpp \ + kopeteprefs.cpp kopetepicture.cpp webcamwidget.cpp + +libkopete_la_LDFLAGS = -no-undefined -version-info 1:0:0 $(all_libraries) +libkopete_la_LIBADD = -lkabc ui/libkopeteui.la $(COMPAT_LIBS) $(LIB_KIO) $(LIB_XSS) $(LIB_XRENDER) + +kde_kcfg_DATA = kopete.kcfg + +#AM_CXXFLAGS = -DQT_PLUGIN +#kde_widget_LTLIBRARIES = libkopetewidgets.la +#libkopetewidgets_la_LDFLAGS = $(KDE_PLUGIN) -module $(all_libraries) +#libkopetewidgets_la_LIBADD = $(LIB_KIO) libkopete.la ui/libkopeteui.la +#libkopetewidgets_la_SOURCES = ui/kopetewidgets.cpp + +kopetewidgets.cpp: $(srcdir)/kopete.widgets + $(MAKEKDEWIDGETS) -o kopetewidgets.cpp $(srcdir)/kopete.widgets + +rcdir = $(kde_datadir)/kopete +rc_DATA = kopetecommandui.rc + +servicetype_DATA = kopeteplugin.desktop kopeteprotocol.desktop kopeteui.desktop +servicetypedir = $(kde_servicetypesdir) + +kopeteincludedir = $(includedir)/kopete +kopeteinclude_HEADERS = kopeteaccount.h kopeteaccountmanager.h kopeteawayaction.h kopeteawaydialog.h kopeteaway.h \ + kopeteblacklister.h kopetecommandhandler.h kopetecontact.h kopetecontactlistelement.h kopetecontactlist.h \ + kopetecontactproperty.h kopeteeventpresentation.h kopete_export.h kopeteglobal.h kopetegroup.h \ + kopetemessageevent.h kopetemessage.h kopetemessagehandlerchain.h kopetemessagehandler.h \ + kopetechatsession.h kopetechatsessionmanager.h kopetemetacontact.h kopetemimetypehandler.h \ + kopeteonlinestatus.h kopeteonlinestatusmanager.h kopetepasswordedaccount.h \ + kopetepassword.h kopeteplugin.h kopeteprotocol.h kopetesimplemessagehandler.h kopetetask.h \ + kopetetransfermanager.h kopeteuiglobal.h kabcpersistence.h managedconnectionaccount.h \ + kopetenotifydataobject.h kopeteversion.h kopeteprefs.h kopetepicture.h webcamwidget.h \ + kopetepluginmanager.h + +# vim: set noet: + +noinst_HEADERS = kopeteblacklister.h kopeteconfig.h diff --git a/kopete/libkopete/PORTING b/kopete/libkopete/PORTING new file mode 100644 index 00000000..b1d8e8a6 --- /dev/null +++ b/kopete/libkopete/PORTING @@ -0,0 +1,93 @@ +Porting from Kopete 0.9 to Kopete 0.10 + + +*) KopetePluginManager has been renamed Kopete::PluginManager + .) QMap loadedPlugins( const QString &category = QString::null ) const; + the QMap has been replaced by a QPtrList the KPluginInfo is not interesting here. + .) addressBookFields( KopetePlugin *p ) has been removed + .) pluginName, pluginId, pluginIcon has been removed (they are acessible from pluginInfo) + .) KPluginInfo *pluginInfo( KopetePlugin* ) has been added + + +*) KopetePlugin has been renamed Kopete::Plugin + .) addressBookFields, addressBookIndexField and addAddressBookField have been removed (they were useless) + .) customChatWindowPopupActions( const KopeteMessage &, DOM::Node &node ) has been removed. + It was used to show the channel's menu when you right click on a IRC channel. Better to show the contact menu in the chatwindow for every contact. + .) KPluginInfo *pluginInfo() + + +*) KopeteProtocol has been renamed Kopete::Protocol + .) protocolAction() has been removed + .) the broken canSendOffline() has been removed, and we can uses capabilities insteads + .) (set)RichTextCapabilities has been renamed to (set)Capabilities + + +*) KopeteAccountManager has been renamed to Kopete::AccountManager + .) manager() has been renamed to self() + .) registerAccount is now public, and MUST be called manualy on account creation + .) autoConnect has been merged with connectAll which only connect accounts with the flag autoConnect + .) accountReady has been renamed again to accountRegistered + .) accountUnregistered signal takes now a const Account. + + +*) KopeteAccount has been renamed to Kopete::Account + .) Account no longer inerits from PluginDataObject. you can access directly to the config with configGroup() + The config now uses the KConfig cache dirrectly and not internal cache. + .) loaded() has been removed since properties are available dirrectly in the constructor. + .) setAccountId and accountIdChanged has been removed + .) password() setPassword() rememberPassword() has been removed. if the account has a pssword, uses PasswordedAccount + .) addContactToMetaContact has been renamed createContact. it has loose his displayName param, uses the metacontact one if you want. + .) addContact has been splitted into addMetaContact and addContact + .) connect now takes the initial status as parameter + .) autoLogin has been renamed autoConnect for consistency + + +*) KopeteContactList has been renamed to Kopete::ContactList + .) contactList() has been renamed to self() + .) getGroup has been renamed to group() or findGroup() + .) findContact now return a Contact + .) findContactByDisplayName has been renamed findMetaContactByDisplayName + .) metaContactDeleted signal has been renamed metaContactRemoved for consistency + + +*) KopeteMetaContact has been renamed to Kopete::MetaContact + .) persistentDataChanged take no more argument + .) isTopLevel has been removed + .) groupSyncMode has been removed because it is broken + + +*) KopeteContact has been renamed to Kopete::Contact + .) displayName has finaly been totaly removed + .) slotDeleteContact has been renamed to deleteContact + .) slotUserInfo has been renamed userInfo and is now pure virtual + + +*) KopeteGroup has been renamed to Kopete::Group + .) renamed() signal has been renamed into displayNameChanged() for consistancy + .) internalName has been removed + + +*) KopetePluginDataObject has been replaced Kopete::ContactListElement + + +*) KopeteOnlineStatus has been renamed Kopete::OnlineStatus + .) caption has been removed (moved to OnlineStatusManager + + +*) OnlineStatusIconCache has been replaced by OnlineStatusManager + + +*) KopeteMessage => Kopete::Message + + +*) KopeteMessageManager => Kopete::ChatSession + .) there are not anymore mmId() + .) user() has been renamed myself() + .) closed() has been renamed chatSessionDestroyed(); + .) typingMsg has been renamed userTyping + + +*) KopeteMessageManagerFactory => Kopete::ChatSessionManager + .) every function with messageManager has been renamed with ChatSession + .) addKopeteMessageManager => registerChatSession + \ No newline at end of file diff --git a/kopete/libkopete/avdevice/Makefile.am b/kopete/libkopete/avdevice/Makefile.am new file mode 100644 index 00000000..a234f797 --- /dev/null +++ b/kopete/libkopete/avdevice/Makefile.am @@ -0,0 +1,18 @@ +INCLUDES =$(GLINC) $(all_includes) +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_COMPAT -DQT_NO_CAST_ASCII -DQT_NO_ASCII_CAST \ + $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/libkopete/private \ + -I$(top_srcdir)/kopete/libkopete/ui $(all_includes) +METASOURCES = AUTO +lib_LTLIBRARIES = libkopete_videodevice.la +noinst_LTLIBRARIES = libkvideoio.la +libkopete_videodevice_la_LDFLAGS = $(KDE_RPATH) $(all_libraries) + +noinst_HEADERS = kxv.h qvideo.h qvideostream.h videocontrol.h videodevice.h \ + videodevicemodelpool.h videodevicepool.h videoinput.h \ + sonix_compress.h bayer.h +libkopete_videodevice_la_SOURCES = videocontrol.cpp videodevice.cpp \ + videodevicemodelpool.cpp videodevicepool.cpp videoinput.cpp \ + sonix_compress.cpp bayer.cpp +libkvideoio_la_LDFLAGS = -no-undefined $(all_libraries) -version-info 1:0:0 +libkvideoio_la_SOURCES = kxv.cpp qvideo.cpp qvideostream.cpp +libkvideoio_la_LIBADD = $(LIB_QT) $(LIB_KDECORE) $(GLLIB) diff --git a/kopete/libkopete/avdevice/bayer.cpp b/kopete/libkopete/avdevice/bayer.cpp new file mode 100644 index 00000000..69189bae --- /dev/null +++ b/kopete/libkopete/avdevice/bayer.cpp @@ -0,0 +1,118 @@ +/* + * BAYER2RGB24 ROUTINE TAKEN FROM: + * + * Sonix SN9C101 based webcam basic I/F routines + * Copyright (C) 2004 Takafumi Mizuno + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +void bayer2rgb24(unsigned char *dst, unsigned char *src, long int WIDTH, long int HEIGHT) +{ + long int i; + unsigned char *rawpt, *scanpt; + long int size; + + rawpt = src; + scanpt = dst; + size = WIDTH*HEIGHT; + + for ( i = 0; i < size; i++ ) + { + if ( (i/WIDTH) % 2 == 0 ) + { + if ( (i % 2) == 0 ) + { + // B + if ( (i > WIDTH) && ((i % WIDTH) > 0) ) + { + *scanpt++ = (*(rawpt-WIDTH-1)+*(rawpt-WIDTH+1)+*(rawpt+WIDTH-1)+*(rawpt+WIDTH+1))/4; // R + *scanpt++ = (*(rawpt-1)+*(rawpt+1)+*(rawpt+WIDTH)+*(rawpt-WIDTH))/4; // G + *scanpt++ = *rawpt; // B + } + else + { + // first line or left column + *scanpt++ = *(rawpt+WIDTH+1); // R + *scanpt++ = (*(rawpt+1)+*(rawpt+WIDTH))/2; // G + *scanpt++ = *rawpt; // B + } + } + else + { + // (B)G + if ( (i > WIDTH) && ((i % WIDTH) < (WIDTH-1)) ) + { + *scanpt++ = (*(rawpt+WIDTH)+*(rawpt-WIDTH))/2; // R + *scanpt++ = *rawpt; // G + *scanpt++ = (*(rawpt-1)+*(rawpt+1))/2; // B + } + else + { + // first line or right column + *scanpt++ = *(rawpt+WIDTH); // R + *scanpt++ = *rawpt; // G + *scanpt++ = *(rawpt-1); // B + } + } + } + else + { + if ( (i % 2) == 0 ) + { + // G(R) + if ( (i < (WIDTH*(HEIGHT-1))) && ((i % WIDTH) > 0) ) + { + *scanpt++ = (*(rawpt-1)+*(rawpt+1))/2; // R + *scanpt++ = *rawpt; // G + *scanpt++ = (*(rawpt+WIDTH)+*(rawpt-WIDTH))/2; // B + } + else + { + // bottom line or left column + *scanpt++ = *(rawpt+1); /* R */ + *scanpt++ = *rawpt; /* G */ + *scanpt++ = *(rawpt-WIDTH); /* B */ + } + } + else + { + // R + if ( i < (WIDTH*(HEIGHT-1)) && ((i % WIDTH) < (WIDTH-1)) ) + { + *scanpt++ = *rawpt; // R + *scanpt++ = (*(rawpt-1)+*(rawpt+1)+*(rawpt-WIDTH)+*(rawpt+WIDTH))/4; // G + *scanpt++ = (*(rawpt-WIDTH-1)+*(rawpt-WIDTH+1)+*(rawpt+WIDTH-1)+*(rawpt+WIDTH+1))/4; // B + } + else + { + // bottom line or right column + *scanpt++ = *rawpt; /* R */ + *scanpt++ = (*(rawpt-1)+*(rawpt-WIDTH))/2; /* G */ + *scanpt++ = *(rawpt-WIDTH-1); /* B */ + } + } + } + rawpt++; + } +} + diff --git a/kopete/libkopete/avdevice/bayer.h b/kopete/libkopete/avdevice/bayer.h new file mode 100644 index 00000000..af6d8baf --- /dev/null +++ b/kopete/libkopete/avdevice/bayer.h @@ -0,0 +1,30 @@ +/* + * BAYER2RGB24 ROUTINE TAKEN FROM: + * + * Sonix SN9C101 based webcam basic I/F routines + * Copyright (C) 2004 Takafumi Mizuno + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +void bayer2rgb24 (unsigned char *dst, unsigned char *src, long int WIDTH, long int HEIGHT); diff --git a/kopete/libkopete/avdevice/kxv.cpp b/kopete/libkopete/avdevice/kxv.cpp new file mode 100644 index 00000000..661bdfad --- /dev/null +++ b/kopete/libkopete/avdevice/kxv.cpp @@ -0,0 +1,711 @@ +/* + * KDE Xv interface + * + * Copyright (C) 2001 George Staikos (staikos@kde.org) + * + * This library 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 library 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 library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include +#include + +#include + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "kxv.h" + + +#include +#include +#include +#include +#ifdef HAVE_XSHM +extern "C" { +#include +#include +} +#endif + +#ifdef HAVE_LIBXV +#include +#include +#endif + +#ifdef HAVE_LIBXVMC +#include +#include +#endif + + +KXv::KXv() +{ + xv_adaptors = 0; + _devs.setAutoDelete(true); +} + + +KXv::~KXv() +{ + kdDebug() << "KXv::~KXv: Close Xv connection." << endl; + _devs.clear(); + +#ifdef HAVE_LIBXV + if (xv_adaptors > 0) + XvFreeAdaptorInfo((XvAdaptorInfo *)xv_adaptor_info); +#endif +} + + +KXvDeviceList& KXv::devices() +{ + return _devs; +} + + +bool KXv::haveXv() +{ +#ifndef HAVE_LIBXV + return false; +#else + unsigned int tmp; + if (Success != XvQueryExtension(qt_xdisplay(), + &tmp, + &tmp, + &tmp, + &tmp, + &tmp)) + return false; + + return true; +#endif +} + + +KXv* KXv::connect(Drawable d) +{ + KXv *xvptr; + + xvptr = new KXv; + if (!xvptr->init(d)) { + kdDebug() << "KXv::connect: Xv init failed." << endl; + delete xvptr; + return NULL; + } + + kdDebug() << "KXv::connect: Xv init completed." << endl; + return xvptr; +} + + +bool KXv::init(Drawable d) +{ +#ifndef HAVE_LIBXV + return false; +#else + if (Success != XvQueryExtension(qt_xdisplay(), + &xv_version, + &xv_release, + &xv_request, + &xv_event, + &xv_error)) { + kdWarning() << "KXv::init: Xv extension not available." << endl; + return false; + } + +#ifdef HAVE_LIBXVMC + // Causes crashes for some people. + // if (Success == XvMCQueryExtension(qt_xdisplay(),0,0)) { + // kdDebug() << "Found XvMC!" << endl; + // } +#endif + + if (Success != XvQueryAdaptors(qt_xdisplay(), + d, + &xv_adaptors, + (XvAdaptorInfo **)&xv_adaptor_info)) { + // Note technically fatal... what to do? + kdWarning() << "KXv::init: XvQueryAdaptors failed." << endl; + } + + XvAdaptorInfo *ai = (XvAdaptorInfo *)xv_adaptor_info; + + for (unsigned int i = 0; i < xv_adaptors; i++) { + KXvDevice *xvd = new KXvDevice; + xvd->xv_type = ai[i].type; + xvd->xv_port = ai[i].base_id; + xvd->xv_name = ai[i].name; + xvd->xv_adaptor = i; + xvd->xv_nvisualformats = ai[i].num_formats; + xvd->xv_visualformats = ai[i].formats; + if (ai[i].type & XvInputMask && + ai[i].type & XvVideoMask ) { + kdDebug() << "KXv::init: Xv VideoMask port " << ai[i].base_id << " was found." + << " Device is: " << ai[i].name << "." << endl; + } + if (ai[i].type & XvInputMask && + ai[i].type & XvImageMask ) { + kdDebug() << "KXv::init: Xv ImageMask port " << ai[i].base_id << " was found." + << " Device is: " << ai[i].name << "." << endl; + } + + if (xvd->init()) { + _devs.append(xvd); + } else { + delete xvd; + } + } + + return true; +#endif +} + +bool KXvDevice::grabStill(QImage* /*pix*/, int /*dw*/, int /*dh*/) +{ +#ifndef HAVE_LIBXV + return false; +#else + return false; +#endif +} + +int KXvDevice::displayImage(QWidget *widget, const unsigned char *const data, int w, int h, int dw, int dh) +{ + if (!widget) + return -1; + return displayImage(widget->winId(), data, w, h, 0, 0, w, h, dw, dh); +} + +int KXvDevice::displayImage(QWidget *widget, const unsigned char *const data, int w, int h, int x, int y, int sw, int sh, int dw, int dh) +{ + if (!widget) + return -1; + return displayImage(widget->winId(), data, w, h, x, y, sw, sh, dw, dh); +} + +int KXvDevice::displayImage(Window win, const unsigned char *const data, int w, int h, int dw, int dh) +{ + return displayImage(win, data, w, h, 0, 0, w, h, dw, dh); +} + +int KXvDevice::displayImage(Window win, const unsigned char *const data, int w, int h, int x, int y, int sw, int sh, int dw, int dh) +{ +#ifndef HAVE_LIBXV + return -1; +#else + Q_ASSERT(xv_port != -1); + + // Must be a video capable device! + if (!(xv_type & XvImageMask) || !(xv_type & XvInputMask)) { + kdWarning() << "KXvDevice::displayImage: This is not a video capable device." << endl; + return -1; + } + + if (xv_image_w != w || xv_image_h != h || !xv_image) + rebuildImage(w, h, _shm); + + if (!xv_image) + return -1; + + if (win != xv_last_win && xv_gc) { + XFreeGC(qt_xdisplay(), xv_gc); + xv_gc = 0; + } + + if (!xv_gc) { + xv_last_win = win; + xv_gc = XCreateGC(qt_xdisplay(), win, 0, NULL); + } + + int rc = 0; + Q_ASSERT(xv_image); + if (!_shm) { + static_cast(xv_image)->data = + (char *)const_cast(data); + rc = XvPutImage(qt_xdisplay(), xv_port, win, xv_gc, + static_cast(xv_image), x, y, sw, sh, 0, 0, dw, dh); + } else { +#ifdef HAVE_XSHM + memcpy(static_cast(xv_image)->data, data, static_cast(xv_image)->data_size); + rc = XvShmPutImage(qt_xdisplay(), xv_port, win, xv_gc, + static_cast(xv_image), x, y, sw, sh, 0, 0, dw, dh, 0); +#endif + } + + XSync(qt_xdisplay(), False); + return rc; +#endif +} + + +bool KXvDevice::startVideo(QWidget *w, int dw, int dh) +{ + if (!w) return false; + return startVideo(w->winId(), dw, dh); +} + + +bool KXvDevice::startVideo(Window w, int dw, int dh) +{ +#ifndef HAVE_LIBXV + return false; +#else + int sx = 0, sy = 0, dx = 0, dy = 0, sw = dw, sh = dh; + + // Must be a video capable device! + if (!(xv_type & XvVideoMask) || !(xv_type & XvInputMask)) { + kdWarning() << "KXvDevice::startVideo: This is not a video capable device." << endl; + return false; + } + + if (videoStarted) stopVideo(); + + if (xv_port == -1) { + kdWarning() << "KXvDevice::startVideo: No xv_port." << endl; + return false; + } + + if (w != xv_last_win && xv_gc) { + XFreeGC(qt_xdisplay(), xv_gc); + xv_gc = 0; + } + + if (!xv_gc) { + xv_last_win = w; + xv_gc = XCreateGC(qt_xdisplay(), w, 0, NULL); + } + + if (-1 != xv_encoding) { + sw = ((XvEncodingInfo *)xv_encoding_info)[xv_encoding].width; + sh = ((XvEncodingInfo *)xv_encoding_info)[xv_encoding].height; + } + + // xawtv does this here: + // ng_ratio_fixup(&dw, &dh, &dx, &dy); + + kdDebug() << "XvPutVideo: " << qt_xdisplay() + << " " << xv_port << " " << w << " " << xv_gc + << " " << sx << " " << sy << " " << sw << " " << sh + << " " << dx << " " << dy << " " << dw << " " << dh << endl; + XvPutVideo(qt_xdisplay(), xv_port, w, xv_gc, sx, sy, sw, sh, dx, dy, dw, dh); + + videoStarted = true; + videoWindow = w; + return true; +#endif +} + +bool KXvDevice::stopVideo() +{ +#ifndef HAVE_LIBXV + return false; +#else + if (!videoStarted) + return true; + if (xv_port == -1) { + kdWarning() << "KXvDevice::stopVideo: No xv_port." << endl; + return false; + } + + XvStopVideo(qt_xdisplay(), xv_port, videoWindow); + videoStarted = false; + return true; +#endif +} + + +KXvDevice::KXvDevice() +{ + xv_encoding_info = NULL; + xv_formatvalues = NULL; + xv_attr = NULL; + xv_port = -1; + xv_encoding = -1; + xv_name = QString::null; + xv_type = -1; + xv_adaptor = -1; + _shm = false; +#ifdef HAVE_LIBXV + xv_imageformat = 0x32595559; // FIXME (YUY2) +#ifdef HAVE_XSHM + if (!XShmQueryExtension(qt_xdisplay())) { + _haveShm = false; + } else { + _shm = true; + _haveShm = true; + } + xv_shminfo = new XShmSegmentInfo; +#else + xv_shminfo = 0; +#endif +#endif + xv_gc = 0; + xv_last_win = 0; + videoStarted = false; + _attrs.setAutoDelete(true); + xv_image = 0; + xv_image_w = 320; + xv_image_h = 200; +} + + +KXvDevice::~KXvDevice() +{ +#ifdef HAVE_LIBXV + _attrs.clear(); + if (videoStarted) stopVideo(); + if (xv_encoding_info) + XvFreeEncodingInfo((XvEncodingInfo *)xv_encoding_info); + XFree(xv_formatvalues); + XFree(xv_attr); +#ifdef HAVE_XSHM + delete (XShmSegmentInfo*)xv_shminfo; +#endif + destroyImage(); +#endif + if (xv_gc) + XFreeGC(qt_xdisplay(), xv_gc); + +#ifdef HAVE_LIBXV + if (xv_port != -1) + XvUngrabPort(qt_xdisplay(), xv_port, CurrentTime); +#endif +} + + +bool KXvDevice::init() +{ +#ifndef HAVE_LIBXV + return false; +#else + assert(xv_port != -1); // make sure we were prepped by KXv already. + + if (XvGrabPort(qt_xdisplay(), xv_port, CurrentTime)) { + kdWarning() << "KXvDevice::init(): Unable to grab Xv port." << endl; + return false; + } + + if (Success != XvQueryEncodings(qt_xdisplay(), + xv_port, + &xv_encodings, + (XvEncodingInfo **)&xv_encoding_info)) { + kdWarning() << "KXvDevice::init: Xv QueryEncodings failed. Dropping Xv support for this device." << endl; + return false; + } + + // Package the encodings up nicely + for (unsigned int i = 0; i < xv_encodings; i++) { + //kdDebug() << "Added encoding: " << ((XvEncodingInfo *)xv_encoding_info)[i].name << endl; + _encodingList << ((XvEncodingInfo *)xv_encoding_info)[i].name; + } + + xv_attr = XvQueryPortAttributes(qt_xdisplay(), + xv_port, + &xv_encoding_attributes); + XvAttribute *xvattr = (XvAttribute *)xv_attr; + kdDebug() << "Attributes for port " << xv_port << endl; + for (int i = 0; i < xv_encoding_attributes; i++) { + assert(xvattr); + kdDebug() << " -> " << xvattr[i].name + << ((xvattr[i].flags & XvGettable) ? " get" : "") + << ((xvattr[i].flags & XvSettable) ? " set" : "") + << " Range: " << xvattr[i].min_value + << " -> " << xvattr[i].max_value << endl; + + KXvDeviceAttribute *xvda = new KXvDeviceAttribute; + xvda->name = xvattr[i].name; + xvda->min = xvattr[i].min_value; + xvda->max = xvattr[i].max_value; + xvda->flags = xvattr[i].flags; + _attrs.append(xvda); + } + + XvImageFormatValues *fo; + fo = XvListImageFormats(qt_xdisplay(), xv_port, &xv_formats); + xv_formatvalues = (void *)fo; + kdDebug() << "Image formats for port " << xv_port << endl; + for (int i = 0; i < xv_formats; i++) { + assert(fo); + QString imout; + imout.sprintf(" 0x%x (%c%c%c%c) %s", + fo[i].id, + fo[i].id & 0xff, + (fo[i].id >> 8) & 0xff, + (fo[i].id >> 16) & 0xff, + (fo[i].id >> 24) & 0xff, + ((fo[i].format == XvPacked) ? + "Packed" : "Planar")); + kdDebug() << imout << endl; + } + + kdDebug() << "Disabling double buffering." << endl; + setAttribute("XV_DOUBLE_BUFFER", 0); + + return true; +#endif +} + + +bool KXvDevice::supportsWidget(QWidget *w) +{ +#ifndef HAVE_LIBXV + return false; +#else + for (int i = 0; i < xv_nvisualformats; i++) { + if (static_cast(xv_visualformats)[i].visual_id + == static_cast(w->x11Visual())->visualid) { + return true; + } + } + return false; +#endif +} + + +bool KXvDevice::isVideoSource() +{ +#ifndef HAVE_LIBXV + return false; +#else + if (xv_type & XvVideoMask && xv_type & XvInputMask) + return true; + return false; +#endif +} + + +bool KXvDevice::isImageBackend() +{ +#ifndef HAVE_LIBXV + return false; +#else + if (xv_type & XvImageMask && xv_type & XvInputMask) + return true; + return false; +#endif +} + + +const KXvDeviceAttributes& KXvDevice::attributes() +{ + return _attrs; +} + + +bool KXvDevice::getAttributeRange(const QString& attribute, int *min, int *max) +{ + for (KXvDeviceAttribute *at = _attrs.first(); at != NULL; at = _attrs.next()) { + if (at->name == attribute) { + if (min) *min = at->min; + if (max) *max = at->max; + return true; + } + } + return false; +} + + +bool KXvDevice::getAttribute(const QString& attribute, int *val) +{ +#ifndef HAVE_LIBXV + return false; +#else + for (KXvDeviceAttribute *at = _attrs.first(); at != NULL; at = _attrs.next()) { + if (at->name == attribute) { + if (val) + XvGetPortAttribute(qt_xdisplay(), xv_port, at->atom(), val); + return true; + } + } + return false; +#endif +} + + +bool KXvDevice::setAttribute(const QString& attribute, int val) +{ +#ifndef HAVE_LIBXV + return false; +#else + for (KXvDeviceAttribute *at = _attrs.first(); at != NULL; at = _attrs.next()) { + if (at->name == attribute) { + XvSetPortAttribute(qt_xdisplay(), xv_port, at->atom(), val); + XSync(qt_xdisplay(), False); + return true; + } + } + return false; +#endif +} + + +const QString& KXvDevice::name() const +{ + return xv_name; +} + + +int KXvDevice::port() const +{ + return xv_port; +} + +const QStringList& KXvDevice::encodings() const +{ + return _encodingList; +} + +bool KXvDevice::encoding(QString& encoding) +{ +#ifndef HAVE_LIBXV + return false; +#else + XvEncodingID enc; + + for (KXvDeviceAttribute *at = _attrs.first(); at != 0L; at = _attrs.next()) { + if (at->name == "XV_ENCODING") { + XvGetPortAttribute(qt_xdisplay(), xv_port, at->atom(), (int*)&enc); + kdDebug() << "KXvDevice: encoding: " << enc << endl; + encoding = enc; + return true; + } + } + return false; +#endif +} + +bool KXvDevice::setEncoding(const QString& e) +{ +#ifdef HAVE_LIBXV + for (unsigned int i = 0; i < xv_encodings; i++) { + if (e == ((XvEncodingInfo *)xv_encoding_info)[i].name) { + xv_encoding = i; + return setAttribute("XV_ENCODING", + ((XvEncodingInfo *)xv_encoding_info)[i].encoding_id); + } + } +#endif + return false; +} + +bool KXvDevice::videoPlaying() const +{ + return videoStarted; +} + + +bool KXvDevice::useShm(bool on) +{ +#ifndef HAVE_XSHM + if (on) { + return false; + } +#endif + if (!_haveShm) { + return false; + } + if (_shm != on) + rebuildImage(xv_image_w, xv_image_h, on); + if (_haveShm) // This can change in rebuildImage() + _shm = on; + return _shm; +} + + +bool KXvDevice::usingShm() const +{ + return _shm; +} + + +#include +void KXvDevice::rebuildImage(int w, int h, bool shm) +{ + if (xv_image) { + destroyImage(); + } +#ifdef HAVE_LIBXV + if (!shm) { + xv_image = (void*)XvCreateImage(qt_xdisplay(), xv_port, xv_imageformat, + 0, w, h); + if (!xv_image) { + kdWarning() << "KXvDevice::rebuildImage: XvCreateImage failed." << endl; + } + } else { +#ifdef HAVE_XSHM + memset(xv_shminfo, 0, sizeof(XShmSegmentInfo)); + xv_image = (void*)XvShmCreateImage(qt_xdisplay(), xv_port, xv_imageformat, + 0, w, h, static_cast(xv_shminfo)); + if (!xv_image) { + kdWarning() << "KXvDevice::rebuildImage: Error using SHM with Xv! Disabling SHM..." << endl; + _haveShm = false; + _shm = false; + xv_image = (void*)XvCreateImage(qt_xdisplay(), xv_port, xv_imageformat, + 0, w, h); + if (!xv_image) { + kdWarning() << "KXvDevice::rebuildImage: XvCreateImage failed." << endl; + } + } else { + static_cast(xv_shminfo)->shmid = + shmget(IPC_PRIVATE, + static_cast(xv_image)->data_size, + IPC_CREAT | 0600); + static_cast(xv_shminfo)->shmaddr = + (char*)shmat(static_cast(xv_shminfo)->shmid, 0, 0); + static_cast(xv_shminfo)->readOnly = True; + static_cast(xv_image)->data = + static_cast(xv_shminfo)->shmaddr; + XShmAttach(qt_xdisplay(), static_cast(xv_shminfo)); + XSync(qt_xdisplay(), False); + shmctl(static_cast(xv_shminfo)->shmid, IPC_RMID, 0); + } +#endif + } + Q_ASSERT(xv_image != 0); + xv_image_w = w; + xv_image_h = h; +#endif +} + + +void KXvDevice::destroyImage() +{ +#ifdef HAVE_LIBXV + if (!_shm) { + if (xv_image) { + static_cast(xv_image)->data = 0; + } + } else { + if (xv_image) { +#ifdef HAVE_XSHM + shmdt(static_cast(xv_shminfo)->shmaddr); +#endif + } + } + XFree(xv_image); + xv_image = 0; +#endif +} + + +Atom KXvDeviceAttribute::atom() +{ + return XInternAtom(qt_xdisplay(), name.latin1(), False); +} diff --git a/kopete/libkopete/avdevice/kxv.h b/kopete/libkopete/avdevice/kxv.h new file mode 100644 index 00000000..d386cda9 --- /dev/null +++ b/kopete/libkopete/avdevice/kxv.h @@ -0,0 +1,260 @@ +/* + * KDE Xv interface + * + * Copyright (C) 2001 George Staikos (staikos@kde.org) + * + * This library 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 library 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 library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __KXV_H +#define __KXV_H + +#include +#include +#include +#include +#include + +class QWidget; +class QImage; + +class KXvPrivate; +class KXvDevice; +class KXvDevicePrivate; + +typedef QPtrList KXvDeviceList; + + +class KXv +{ +public: + ~KXv(); + + /* + * To get access to the Xv extension, you call this method. It will return + * a KXv* object on success, or NULL if it can't connect. + * + * d is typically the Window ID + */ + static KXv *connect(Drawable d); + + /* + * True if we can connect to the Xv extension. + */ + static bool haveXv(); + + /* + * Return the list of Xv devices + */ + KXvDeviceList& devices(); + +protected: + KXv(); + bool init(Drawable d); + + /*** XV info ***/ + unsigned int xv_version, xv_release, xv_request, xv_event, xv_error; + unsigned int xv_adaptors; + void *xv_adaptor_info; + + KXvDeviceList _devs; + +private: + KXvPrivate *d; +}; + + + +class KXvDeviceAttribute +{ +public: + QString name; + int min; + int max; + int flags; + + Atom atom(); +}; + +typedef QPtrList KXvDeviceAttributes; + + +class KXvDevice +{ + friend class KXv; +public: + + KXvDevice(); + ~KXvDevice(); + + /* + * return the list of known attributes + */ + const KXvDeviceAttributes& attributes(); + + /* + * return the range for a given attribute + */ + bool getAttributeRange(const QString& attribute, int *min, int *max); + + /* + * get the current value of a given attribute + */ + bool getAttribute(const QString& attribute, int *val); + + /* + * set the current value of a given attribute + */ + bool setAttribute(const QString& attribute, int val); + + bool grabStill(QImage *pix, int dw, int dh); + + /* + * True if this device can operate on the given widget + */ + bool supportsWidget(QWidget *w); + + /* + * Display the given image with Xv. + */ + int displayImage(QWidget *widget, const unsigned char *const data, int w, int h, int dw, int dh); + int displayImage(Window win, const unsigned char *const data, int w, int h, int dw, int dh); + + /* + * Display a portion of the given image with Xv. + */ + int displayImage(QWidget *widget, const unsigned char *const data, int w, int h, int x, int y, int sw, int sh, int dw, int dh); + int displayImage(Window win, const unsigned char *const data, int w, int h, int x, int y, int sw, int sh, int dw, int dh); + + /* + * Start a video stream in widget w, width dw, height dh + */ + bool startVideo(QWidget *w, int dw, int dh); + bool startVideo(Window w, int dw, int dh); + + /* + * Is the video playing + */ + bool videoPlaying() const; + + /* + * Stop video stream + */ + bool stopVideo(); + + /* + * True if this is an image output backend (video card) + */ + bool isImageBackend(); + + /* + * True if this is a video source + */ + bool isVideoSource(); + + /* + * Name of the device + */ + const QString& name() const; + + /* + * The Xv port for this device + */ + int port() const; + + /* + * The list of encodings/norms available + */ + const QStringList& encodings() const; + + /* + * get encoding + */ + bool encoding(QString& encoding); + + /* + * Set the encoding to the given one. This should be taken from the list. + */ + bool setEncoding(const QString& e); + + /* + * Set the image format. (ex YUV) + */ + int setImageFormat(int format); + + /* + * Get the current image format + */ + int imageFormat() const; + + /* + * Use SHM for PutImage if available + */ + bool useShm(bool on); + + /* + * Is SHM being used? + */ + bool usingShm() const; + + +protected: + bool init(); + + bool _shm; + KXvDeviceAttributes _attrs; + + int xv_type, xv_adaptor; + QString xv_name; + int xv_port; + unsigned int xv_encodings; + int xv_encoding; + void *xv_encoding_info; + int xv_encoding_attributes; + void *xv_attr; + GC xv_gc; + Window xv_last_win; + + QStringList _encodingList; + + int xv_formats; + void *xv_formatvalues; + + int xv_nvisualformats; + void *xv_visualformats; // XvFormat* + + bool videoStarted; + Window videoWindow; + + long xv_imageformat; + + void *xv_shminfo; + void *xv_image; + int xv_image_w; + int xv_image_h; + bool _haveShm; + + +private: + KXvDevicePrivate *d; + + void rebuildImage(int w, int h, bool shm); + void destroyImage(); +}; + + +#endif + diff --git a/kopete/libkopete/avdevice/qvideo.cpp b/kopete/libkopete/avdevice/qvideo.cpp new file mode 100644 index 00000000..ad6fb762 --- /dev/null +++ b/kopete/libkopete/avdevice/qvideo.cpp @@ -0,0 +1,154 @@ +/*************************************************************************** + qvideo.cpp + ---------- + begin : Sat Jun 12 2004 + copyright : (C) 2004 by Dirk Ziegelmeier + (C) 2002 by George Staikos + email : dziegel@gmx.de + ***************************************************************************/ + +/* + * This library 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 library 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 library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "qvideo.h" + +#include +#include + +#include +#include + +unsigned int QVideo::bytesppForFormat(ImageFormat fmt) +{ + switch (fmt) { + case FORMAT_RGB32: + case FORMAT_RGB24: + case FORMAT_BGR32: + case FORMAT_BGR24: + return 4; + + case FORMAT_RGB15_LE: + case FORMAT_RGB16_LE: + case FORMAT_RGB15_BE: + case FORMAT_RGB16_BE: + case FORMAT_YUYV: + case FORMAT_UYVY: + case FORMAT_YUV422P: + case FORMAT_YUV420P: + return 2; + + case FORMAT_GREY: + case FORMAT_HI240: + return 1; + + default: + // unknown format + return 0; + } +} + +bool QVideo::findDisplayProperties(ImageFormat& fmt, int& depth, unsigned int& bitsperpixel, int& bytesperpixel) +{ + XVisualInfo *vi_in, vi_out; + long mask = VisualScreenMask; + int nvis = 0; + + ImageFormat p = FORMAT_NONE; + int bpp = 0; + int d = 0; + + vi_out.screen = QPaintDevice::x11AppScreen(); + vi_in = XGetVisualInfo(qt_xdisplay(), mask, &vi_out, &nvis); + + if (vi_in) { + for (int i = 0; i < nvis; i++) { + bpp = 0; + int n; + XPixmapFormatValues *pf = XListPixmapFormats(qt_xdisplay(),&n); + d = vi_in[i].depth; + for (int j = 0; j < n; j++) { + if (pf[j].depth == d) { + bpp = pf[j].bits_per_pixel; + break; + } + } + XFree(pf); + + // FIXME: Endianess detection + + p = FORMAT_NONE; + switch (bpp) { + case 32: + if (vi_in[i].red_mask == 0xff0000 && + vi_in[i].green_mask == 0x00ff00 && + vi_in[i].blue_mask == 0x0000ff) { + p = FORMAT_BGR32; + kdDebug() << "QVideo: Found BGR32 display." << endl; + } + break; + case 24: + if (vi_in[i].red_mask == 0xff0000 && + vi_in[i].green_mask == 0x00ff00 && + vi_in[i].blue_mask == 0x0000ff) { + p = FORMAT_BGR24; + kdDebug() << "QVideo: Found BGR24 display." << endl; + } + break; + case 16: + if (vi_in[i].red_mask == 0x00f800 && + vi_in[i].green_mask == 0x0007e0 && + vi_in[i].blue_mask == 0x00001f) { + p = FORMAT_RGB15_LE; + kdDebug() << "QVideo: Found RGB16_LE display." << endl; + } else + if (vi_in[i].red_mask == 0x007c00 && + vi_in[i].green_mask == 0x0003e0 && + vi_in[i].blue_mask == 0x00001f) { + p = FORMAT_RGB15_LE; + kdDebug() << "QVideo: Found RGB15_LE display." << endl; + } + break; + case 8: + default: + continue; + } + + if (p != FORMAT_NONE) + break; + } + XFree(vi_in); + } + + if (p != FORMAT_NONE) { + int bytespp = bytesppForFormat(p); + kdDebug() << "QVideo: Display properties: depth: " << d + << ", bits/pixel: " << bpp + << ", bytes/pixel: " << bytespp << endl; + fmt = p; + bitsperpixel = bpp; + bytesperpixel = bytespp; + depth = d; + return true; + } else { + kdWarning() << "QVideo: Unable to find out palette. What display do you have????" << endl; + fmt = FORMAT_NONE; + bitsperpixel = 0; + bytesperpixel = 0; + depth = 0; + return false; + } +} diff --git a/kopete/libkopete/avdevice/qvideo.h b/kopete/libkopete/avdevice/qvideo.h new file mode 100644 index 00000000..20e999fc --- /dev/null +++ b/kopete/libkopete/avdevice/qvideo.h @@ -0,0 +1,68 @@ +// -*- c++ -*- +/*************************************************************************** + qvideo.h + -------- + begin : Sat Jun 12 2004 + copyright : (C) 2004 by Dirk Ziegelmeier + email : dziegel@gmx.de + ***************************************************************************/ + +/* + * This library 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 library 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 library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef QVIDEO_H +#define QVIDEO_H + +class QVideo +{ +public: + typedef enum { + FORMAT_NONE = 0, + FORMAT_GREY = (1<<0), + FORMAT_HI240 = (1<<1), + FORMAT_RGB15_LE = (1<<2), + FORMAT_RGB15_BE = (1<<3), + FORMAT_RGB16_LE = (1<<4), + FORMAT_RGB16_BE = (1<<5), + FORMAT_RGB32 = (1<<6), + FORMAT_BGR32 = (1<<7), + FORMAT_RGB24 = (1<<8), + FORMAT_BGR24 = (1<<9), + FORMAT_YUYV = (1<<10), + FORMAT_UYVY = (1<<11), + FORMAT_YUV422P = (1<<12), + FORMAT_YUV420P = (1<<13), + FORMAT_ALL = 0x00003FFF + } ImageFormat; + + typedef enum { + METHOD_NONE = 0, + METHOD_XSHM = 1, + METHOD_XV = 2, + METHOD_XVSHM = 4, + METHOD_X11 = 8, + METHOD_DGA = 16, /* unimplemented */ + METHOD_GL = 32, + METHOD_SDL = 64 /* unimplemented */ + } VideoMethod; + + static unsigned int bytesppForFormat(ImageFormat fmt); + static bool findDisplayProperties(ImageFormat& fmt, int& depth, unsigned int& bitsperpixel, int& bytesperpixel); +}; + +#endif //QVIDEO_H + diff --git a/kopete/libkopete/avdevice/qvideostream.cpp b/kopete/libkopete/avdevice/qvideostream.cpp new file mode 100644 index 00000000..cd7aafc2 --- /dev/null +++ b/kopete/libkopete/avdevice/qvideostream.cpp @@ -0,0 +1,731 @@ +/* + * + * Copyright (C) 2002 George Staikos + * 2004 Dirk Ziegelmeier + * + * This library 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 library 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 library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "qvideostream.h" +#include +#include +#include + +#include +#include "kxv.h" + +#include +#include + +#ifdef HAVE_XSHM +extern "C" { +#include +#include +#include +#include +} +#endif + +#ifdef HAVE_GL +class QVideoStreamGLWidget : public QGLWidget +{ +public: + QVideoStreamGLWidget(QWidget* parent = 0, const char* name = 0); + virtual ~QVideoStreamGLWidget(); + + void setInputSize(const QSize& sz); + void display(const unsigned char *const img, int x, int y, int sw, int sh); + +private: + virtual void resizeGL(int w, int h); + void initializeGL(); + + virtual bool eventFilter( QObject *o, QEvent *e ); + void calc(QPoint& p, QPoint& v); + + + QSize _inputSize; + GLuint _tex; + int _tw, _th; + QWidget* _w; + int _maxGL; + QSize _sz; + bool _glfun; + QPoint _ul, _ur, _ll, _lr; + QPoint _vul, _vur, _vll, _vlr; + QTimer* _glfunTimer; +}; +#endif + +class QVideoStreamPrivate +{ +public: + QVideoStreamPrivate(); + ~QVideoStreamPrivate(); + KXv *xvHandle; + KXvDevice *xvdev; + XImage *xim; + GC gc; +#ifdef HAVE_GL + QVideoStreamGLWidget* glwidget; +#endif +#ifdef HAVE_XSHM + XShmSegmentInfo shmh; +#endif +}; + +QVideoStreamPrivate::QVideoStreamPrivate() +{ + xvHandle = 0; + xim = 0; +} + +QVideoStreamPrivate::~QVideoStreamPrivate() +{ + delete xvHandle; +} + +QVideoStream::QVideoStream(QWidget *widget, const char* name) + : QObject(widget, name), + d(new QVideoStreamPrivate), + _w(widget), + _methods(METHOD_NONE), + _method(METHOD_NONE), + _format(FORMAT_NONE), + _init(false) +{ + int dummy; + unsigned int dummy2; + findDisplayProperties(_xFormat, dummy, dummy2, dummy); + + _methods = (VideoMethod)(_methods | METHOD_X11); + +#ifdef HAVE_XSHM + if (XShmQueryExtension(_w->x11Display())) { + _methods = (VideoMethod)(_methods | METHOD_XSHM); + } +#endif + + if (KXv::haveXv()) { + _methods = (VideoMethod)(_methods | METHOD_XV); +#ifdef HAVE_XSHM + _methods = (VideoMethod)(_methods | METHOD_XVSHM); +#endif + } + +#ifdef HAVE_GL + if (QGLFormat::hasOpenGL()) { + _methods = (VideoMethod)(_methods | METHOD_GL); + } +#endif + + d->gc = XCreateGC(_w->x11Display(), _w->winId(), 0, NULL); +} + +QVideoStream::~QVideoStream() +{ + deInit(); + XFreeGC(_w->x11Display(), d->gc); + delete d; +} + +void QVideoStream::deInit() +{ + if (!_init) + return; + + _init = false; + _format = FORMAT_NONE; + + Q_ASSERT(_methods & _method); + if (!(_methods & _method)) + return; + + switch (_method) { + case METHOD_XSHM: +#ifdef HAVE_XSHM + XShmDetach(_w->x11Display(), &(d->shmh)); + XDestroyImage(d->xim); + d->xim = 0; + shmdt(d->shmh.shmaddr); +#endif + break; + case METHOD_X11: + delete[] d->xim->data; + d->xim->data = 0; + XDestroyImage(d->xim); + d->xim = 0; + break; + case METHOD_XVSHM: + case METHOD_XV: + delete d->xvHandle; + d->xvHandle = 0; + break; + case METHOD_GL: +#ifdef HAVE_GL + delete d->glwidget; +#endif + break; + default: + Q_ASSERT(0); + return; + } +} + +void QVideoStream::init() +{ + Q_ASSERT(_methods & _method); + if (!(_methods & _method)) + return; + + switch (_method) { + case METHOD_XSHM: + { +#ifdef HAVE_XSHM + if ( !_inputSize.isValid() ) { + kdWarning() << "QVideoStream::init() (XSHM): Unable to initialize due to invalid input size." << endl; + return; + } + + memset(&(d->shmh), 0, sizeof(XShmSegmentInfo)); + d->xim = XShmCreateImage(_w->x11Display(), + (Visual*)_w->x11Visual(), + _w->x11Depth(), + ZPixmap, 0, &(d->shmh), + _inputSize.width(), + _inputSize.height()); + d->shmh.shmid = shmget(IPC_PRIVATE, + d->xim->bytes_per_line*d->xim->height, + IPC_CREAT|0600); + d->shmh.shmaddr = (char *)shmat(d->shmh.shmid, 0, 0); + d->xim->data = (char*)d->shmh.shmaddr; + d->shmh.readOnly = False; + Status s = XShmAttach(_w->x11Display(), &(d->shmh)); + if (s) { + XSync(_w->x11Display(), False); + shmctl(d->shmh.shmid, IPC_RMID, 0); + _format = _xFormat; + _init = true; + } else { + kdWarning() << "XShmAttach failed!" << endl; + XDestroyImage(d->xim); + d->xim = 0; + shmdt(d->shmh.shmaddr); + } +#endif + } + break; + case METHOD_X11: + if ( !_inputSize.isValid() ) { + kdWarning() << "QVideoStream::init() (X11): Unable to initialize due to invalid input size." << endl; + return; + } + + d->xim = XCreateImage(_w->x11Display(), + (Visual*)_w->x11Visual(), + _w->x11Depth(), + ZPixmap, 0, 0, + _inputSize.width(), + _inputSize.height(), + 32, 0); + + d->xim->data = new char[d->xim->bytes_per_line*_inputSize.height()]; + _format = _xFormat; + _init = true; + break; + case METHOD_XVSHM: + case METHOD_XV: + { + if (d->xvHandle) + delete d->xvHandle; + + d->xvHandle = KXv::connect(_w->winId()); + KXvDeviceList& xvdl(d->xvHandle->devices()); + KXvDevice *xvdev = NULL; + + for (xvdev = xvdl.first(); xvdev; xvdev = xvdl.next()) { + if (xvdev->isImageBackend() && + xvdev->supportsWidget(_w)) { + d->xvdev = xvdev; + d->xvdev->useShm(_method == METHOD_XVSHM); + _format = FORMAT_YUYV; + _init = true; + break; + } + } + + if (!_init) { + delete d->xvHandle; + d->xvHandle = 0; + } + } + break; + case METHOD_GL: +#ifdef HAVE_GL + d->glwidget = new QVideoStreamGLWidget(_w, "QVideoStreamGLWidget"); + d->glwidget->resize(_w->width(), _w->height()); + d->glwidget->show(); + _format = FORMAT_BGR24; + _init = true; +#endif + break; + default: + Q_ASSERT(0); + return; + } +} + +bool QVideoStream::haveMethod(VideoMethod method) const +{ + return _methods & method; +} + +QVideo::VideoMethod QVideoStream::method() const +{ + return _method; +} + +QVideo::VideoMethod QVideoStream::setMethod(VideoMethod method) +{ + if (_methods & method) { + deInit(); + _method = method; + init(); + } + + return _method; +} + +QSize QVideoStream::maxSize() const +{ + return _size; +} + +int QVideoStream::maxWidth() const +{ + return _size.width(); +} + +int QVideoStream::maxHeight() const +{ + return _size.height(); +} + +QSize QVideoStream::size() const +{ + return _size; +} + +int QVideoStream::width() const +{ + return _size.width(); +} + +int QVideoStream::height() const +{ + return _size.height(); +} + +QSize QVideoStream::setSize(const QSize& sz) +{ + _size = sz; + return _size; +} + +int QVideoStream::setWidth(int width) +{ + if (width < 0) + width = 0; + if (width > maxWidth()) + width = maxWidth(); + _size.setWidth(width); + return _size.width(); +} + +int QVideoStream::setHeight(int height) +{ + if (height < 0) + height = 0; + if (height > maxHeight()) + height = maxHeight(); + _size.setHeight(height); + return _size.height(); +} + +QSize QVideoStream::inputSize() const +{ + return _inputSize; +} + +int QVideoStream::inputWidth() const +{ + return _inputSize.width(); +} + +int QVideoStream::inputHeight() const +{ + return _inputSize.height(); +} + +QSize QVideoStream::setInputSize(const QSize& sz) +{ + if (sz == _inputSize) + return _inputSize; + _inputSize = sz; + if (_method & (METHOD_XSHM | METHOD_X11)) { + deInit(); + init(); + } +#ifdef HAVE_GL + if (_method & METHOD_GL) { + d->glwidget->setInputSize(_inputSize); + } +#endif + return _inputSize; +} + +int QVideoStream::setInputWidth(int width) +{ + if (width == _inputSize.width()) + return _inputSize.width(); + _inputSize.setWidth(width); + if (_method & (METHOD_XSHM | METHOD_X11)) { + deInit(); + init(); + } +#ifdef HAVE_GL + if (_method & METHOD_GL) { + d->glwidget->setInputSize(_inputSize); + } +#endif + return _inputSize.width(); +} + +int QVideoStream::setInputHeight(int height) +{ + if (height == _inputSize.height()) + return _inputSize.height(); + _inputSize.setHeight(height); + if (_method & (METHOD_XSHM | METHOD_X11)) { + deInit(); + init(); + } +#ifdef HAVE_GL + if (_method & METHOD_GL) { + d->glwidget->setInputSize(_inputSize); + } +#endif + return _inputSize.height(); +} + +bool QVideoStream::supportsFormat(VideoMethod method, ImageFormat format) +{ + return (bool)(formatsForMethod(method) & format); +} + +QVideo::ImageFormat QVideoStream::formatsForMethod(VideoMethod method) +{ + switch(method) { + case METHOD_XSHM: + case METHOD_X11: + return _xFormat; + case METHOD_XV: + case METHOD_XVSHM: + return FORMAT_YUYV; + case METHOD_GL: + return FORMAT_BGR24; + default: + return FORMAT_NONE; + } +} + +QVideo::ImageFormat QVideoStream::format() const +{ + return _format; +} + +bool QVideoStream::setFormat(ImageFormat format) +{ + if(supportsFormat(_method, format)) { + _format = format; + return true; + } else { + return false; + } +} + +int QVideoStream::displayFrame(const unsigned char *const img) +{ + return displayFrame(img, 0, 0, _inputSize.width(), _inputSize.height()); +} + +int QVideoStream::displayFrame(const unsigned char *const img, int x, int y, int sw, int sh) +{ + Q_ASSERT(_init); + if (!_init) + return -1; + + Q_ASSERT(_methods & _method); + if (!(_methods & _method)) + return -1; + + switch (_method) { + case METHOD_XV: + case METHOD_XVSHM: + return d->xvdev->displayImage(_w, img, + _inputSize.width(), _inputSize.height(), x, y, sw, sh, + _size.width(), _size.height()); + break; + case METHOD_XSHM: +#ifdef HAVE_XSHM + memcpy(d->xim->data,img,d->xim->bytes_per_line*d->xim->height); + XShmPutImage(_w->x11Display(), _w->winId(), d->gc, d->xim, + x, y, + 0, 0, + sw, sh, + 0); + XSync(_w->x11Display(), False); + break; +#else + return -1; +#endif + case METHOD_X11: + memcpy(d->xim->data, img, d->xim->bytes_per_line*d->xim->height); + XPutImage(_w->x11Display(), _w->winId(), d->gc, d->xim, + x, y, + 0, 0, + sw, sh); + XSync(_w->x11Display(), False); + break; + case METHOD_GL: +#ifdef HAVE_GL + d->glwidget->display(img, x, y, sw, sh); +#endif + break; + default: + Q_ASSERT(0); + return -1; + } + + return 0; +} + +QVideoStream& QVideoStream::operator<<(const unsigned char *const img) +{ + displayFrame(img); + return *this; +} + +// --------------------------------------------------------------------------------------- +#ifdef HAVE_GL + +QVideoStreamGLWidget::QVideoStreamGLWidget(QWidget* parent, const char* name) + : QGLWidget(QGLFormat(QGL::DoubleBuffer | QGL::Rgba | QGL::DirectRendering), parent, name), + _tex(0), + _w(parent), + _glfun(false) +{ + kdDebug() << "QVideoStreamGLWidget::QVideoStreamGLWidget()" << endl; + + connect(_w, SIGNAL(resized(int, int)), + this, SLOT(resize(int, int))); + + topLevelWidget()->installEventFilter(this); + _glfunTimer = new QTimer(); +} + +QVideoStreamGLWidget::~QVideoStreamGLWidget() +{ + kdDebug() << "QVideoStreamGLWidget::~QVideoStreamGLWidget()" << endl; + delete _glfunTimer; + + makeCurrent(); + if(_tex != 0) { + glDeleteTextures(1, &_tex); + } +} + +bool QVideoStreamGLWidget::eventFilter(QObject*, QEvent* e) +{ + // For some reason, KeyPress does not work (yields 2), QEvent::KeyPress is unknown... What the f...???? + // I am too lazy to scan the header files for the reason. + if(e->type() == 6) { + QKeyEvent* ke = static_cast(e); + if(ke->key() == Qt::Key_Pause) { + _glfunTimer->start(500, true); + } else if (_glfunTimer->isActive() && (ke->key() == Qt::Key_Escape)) { + _glfun = !_glfun; + } + } + return false; +} + +void QVideoStreamGLWidget::initializeGL() +{ + kdDebug() << "QVideoStreamGLWidget::initializeGL()" << endl; + setAutoBufferSwap(false); + + QGLFormat f = format(); + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &_maxGL); + kdDebug() << "OpenGL capabilities (* = required):" << endl; + kdDebug() << " Valid context*: " << isValid() << endl; + kdDebug() << " DoubleBuffer*: " << f.doubleBuffer() << endl; + kdDebug() << " Depth: " << f.depth() << endl; + kdDebug() << " RGBA*: " << f.rgba() << endl; + kdDebug() << " Alpha: " << f.alpha() << endl; + kdDebug() << " Accum: " << f.accum() << endl; + kdDebug() << " Stencil: " << f.stencil() << endl; + kdDebug() << " Stereo: " << f.stereo() << endl; + kdDebug() << " DirectRendering*: " << f.directRendering() << endl; + kdDebug() << " Overlay: " << f.hasOverlay() << endl; + kdDebug() << " Plane: " << f.plane() << endl; + kdDebug() << " MAX_TEXTURE_SIZE: " << _maxGL << endl; + + qglClearColor(Qt::black); + glShadeModel(GL_FLAT); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + _vul = QPoint( 4, 10); + _vur = QPoint(-8, 4); + _vll = QPoint(10, -4); + _vlr = QPoint(-8, -10); +} + +void QVideoStreamGLWidget::resizeGL(int w, int h) +{ + _sz = QSize(w, h); + + glViewport(0, 0, w, h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, w, 0.0, h, -1, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + _ul = QPoint(0, 0); + _ur = QPoint(w, 0); + _ll = QPoint(0, h); + _lr = QPoint(w, h); +} + +void QVideoStreamGLWidget::setInputSize(const QSize& sz) +{ + makeCurrent(); + + _inputSize = sz; + int iw = _inputSize.width(); + int ih = _inputSize.height(); + + if ( (iw > _maxGL) || (ih > _maxGL) ) { + kdWarning() << "QVideoStreamGLWidget::setInputSize(): Texture too large! maxGL: " << _maxGL << endl; + return; + } + + // textures have power-of-two x,y dimensions + int i; + for (i = 0; iw >= (1 << i); i++) + ; + _tw = (1 << i); + for (i = 0; ih >= (1 << i); i++) + ; + _th = (1 << i); + + // Generate texture + if(_tex != 0) { + glDeleteTextures(1, &_tex); + } + glGenTextures(1, &_tex); + glBindTexture(GL_TEXTURE_2D, _tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + // Blank texture + char* dummy = new char[_tw*_th*4]; + memset(dummy, 128, _tw*_th*4); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _tw, _th, 0, + GL_RGB, GL_UNSIGNED_BYTE, dummy); + delete[] dummy; +} + +void QVideoStreamGLWidget::display(const unsigned char *const img, int x, int y, int sw, int sh) +{ + makeCurrent(); + + // FIXME: Endianess - also support GL_RGB + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _inputSize.width(), _inputSize.height(), + GL_BGR, GL_UNSIGNED_BYTE, img); + + // upper right coords + float ur_x = (float)(x + sw) / _tw; + float ur_y = (float)(y + sh) / _th; + + // lower left coords + float ll_x = (float)(x) / _tw; + float ll_y = (float)(y) / _th; + + glEnable(GL_TEXTURE_2D); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + glBindTexture(GL_TEXTURE_2D, _tex); + if (!_glfun) { + glBegin(GL_QUADS); + glTexCoord2f(ll_x, ur_y); glVertex2i(0, 0 ); + glTexCoord2f(ll_x, ll_y); glVertex2i(0, _sz.height()); + glTexCoord2f(ur_x, ll_y); glVertex2i(_sz.width(), _sz.height()); + glTexCoord2f(ur_x, ur_y); glVertex2i(_sz.width(), 0 ); + glEnd(); + } else { + calc(_ul, _vul); + calc(_ur, _vur); + calc(_ll, _vll); + calc(_lr, _vlr); + + glClear(GL_COLOR_BUFFER_BIT); + + glBegin(GL_QUADS); + glTexCoord2f(0, y); glVertex2i(_ul.x(), _ul.y()); + glTexCoord2f(0, 0); glVertex2i(_ll.x(), _ll.y()); + glTexCoord2f(x, 0); glVertex2i(_lr.x(), _lr.y()); + glTexCoord2f(x, y); glVertex2i(_ur.x(), _ur.y()); + glEnd(); + } + swapBuffers(); + glDisable(GL_TEXTURE_2D); +} + +void QVideoStreamGLWidget::calc(QPoint& p, QPoint& v) +{ + p += v; + + if(p.x() < 0) { + p.setX(-p.x()); + v.setX(-v.x()); + } + if(p.y() < 0) { + p.setY(-p.y()); + v.setY(-v.y()); + } + if(p.x() > _sz.width()) { + p.setX(_sz.width() - (p.x() - _sz.width())); + v.setX(-v.x()); + } + if(p.y() > _sz.height()) { + p.setY(_sz.height() - (p.y() - _sz.height())); + v.setY(-v.y()); + } +} +#endif + +#include "qvideostream.moc" diff --git a/kopete/libkopete/avdevice/qvideostream.h b/kopete/libkopete/avdevice/qvideostream.h new file mode 100644 index 00000000..801fa829 --- /dev/null +++ b/kopete/libkopete/avdevice/qvideostream.h @@ -0,0 +1,112 @@ +// -*- c++ -*- + +/* + * + * Copyright (C) 2002 George Staikos + * 2004 Dirk Ziegelmeier + * + * This library 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 library 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 library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _QVIDEOSTREAM_H +#define _QVIDEOSTREAM_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_GL +#include +#include +#endif + +#include +#include "qvideo.h" + +class QVideoStreamPrivate; + +/** + * QT-style video stream driver. + */ +class QVideoStream : public QObject, public QVideo +{ + Q_OBJECT + +public: + QVideoStream(QWidget *widget, const char* name = 0); + ~QVideoStream(); + + /* output method */ + bool haveMethod(VideoMethod method) const; + VideoMethod method() const; + VideoMethod setMethod(VideoMethod method); + + /* max output sizes */ + QSize maxSize() const; + int maxWidth() const; + int maxHeight() const; + + /* output sizes */ + QSize size() const; + int width() const; + int height() const; + + QSize setSize(const QSize& sz); + int setWidth(int width); + int setHeight(int height); + + /* input sizes */ + QSize inputSize() const; + int inputWidth() const; + int inputHeight() const; + + QSize setInputSize(const QSize& sz); + int setInputWidth(int width); + int setInputHeight(int height); + + /* input format */ + ImageFormat format() const; + bool setFormat(ImageFormat format); + + /* functions to find out about formats */ + ImageFormat formatsForMethod(VideoMethod method); + bool supportsFormat(VideoMethod method, ImageFormat format); + + /* Display image */ + QVideoStream& operator<<(const unsigned char *const img); + +public slots: + int displayFrame(const unsigned char *const img); + int displayFrame(const unsigned char *const img, int x, int y, int sw, int sh); + +private: + QVideoStreamPrivate* d; + + QWidget* _w; + VideoMethod _methods; // list of methods + VideoMethod _method; // the current method + ImageFormat _format; + QSize _size; + QSize _inputSize; + bool _init; + ImageFormat _xFormat; + + void deInit(); + void init(); +}; + +#endif + diff --git a/kopete/libkopete/avdevice/sonix_compress.cpp b/kopete/libkopete/avdevice/sonix_compress.cpp new file mode 100644 index 00000000..400635c4 --- /dev/null +++ b/kopete/libkopete/avdevice/sonix_compress.cpp @@ -0,0 +1,180 @@ +#include "sonix_compress.h" + +#define CLAMP(x) ((x)<0?0:((x)>255)?255:(x)) + +typedef struct { + int is_abs; + int len; + int val; + int unk; +} code_table_t; + + +/* local storage */ +static code_table_t table[256]; +static int init_done = 0; + +/* global variable */ +int sonix_unknown = 0; + +/* + sonix_decompress_init + ===================== + pre-calculates a locally stored table for efficient huffman-decoding. + + Each entry at index x in the table represents the codeword + present at the MSB of byte x. + +*/ +void sonix_decompress_init(void) +{ + int i; + int is_abs, val, len, unk; + + for (i = 0; i < 256; i++) { + is_abs = 0; + val = 0; + len = 0; + unk = 0; + if ((i & 0x80) == 0) { + /* code 0 */ + val = 0; + len = 1; + } + else if ((i & 0xE0) == 0x80) { + /* code 100 */ + val = +4; + len = 3; + } + else if ((i & 0xE0) == 0xA0) { + /* code 101 */ + val = -4; + len = 3; + } + else if ((i & 0xF0) == 0xD0) { + /* code 1101 */ + val = +11; + len = 4; + } + else if ((i & 0xF0) == 0xF0) { + /* code 1111 */ + val = -11; + len = 4; + } + else if ((i & 0xF8) == 0xC8) { + /* code 11001 */ + val = +20; + len = 5; + } + else if ((i & 0xFC) == 0xC0) { + /* code 110000 */ + val = -20; + len = 6; + } + else if ((i & 0xFC) == 0xC4) { + /* code 110001xx: unknown */ + val = 0; + len = 8; + unk = 1; + } + else if ((i & 0xF0) == 0xE0) { + /* code 1110xxxx */ + is_abs = 1; + val = (i & 0x0F) << 4; + len = 8; + } + table[i].is_abs = is_abs; + table[i].val = val; + table[i].len = len; + table[i].unk = unk; + } + + sonix_unknown = 0; + init_done = 1; +} + + +/* + sonix_decompress + ================ + decompresses an image encoded by a SN9C101 camera controller chip. + + IN width + height + inp pointer to compressed frame (with header already stripped) + OUT outp pointer to decompressed frame + + Returns 0 if the operation was successful. + Returns <0 if operation failed. + +*/ +int sonix_decompress(int width, int height, unsigned char *inp, unsigned char *outp) +{ + int row, col; + int val; + int bitpos; + unsigned char code; + unsigned char *addr; + + if (!init_done) { + /* do sonix_decompress_init first! */ + return -1; + } + + bitpos = 0; + for (row = 0; row < height; row++) { + + col = 0; + + /* first two pixels in first two rows are stored as raw 8-bit */ + if (row < 2) { + addr = inp + (bitpos >> 3); + code = (addr[0] << (bitpos & 7)) | (addr[1] >> (8 - (bitpos & 7))); + bitpos += 8; + *outp++ = code; + + addr = inp + (bitpos >> 3); + code = (addr[0] << (bitpos & 7)) | (addr[1] >> (8 - (bitpos & 7))); + bitpos += 8; + *outp++ = code; + + col += 2; + } + + while (col < width) { + /* get bitcode from bitstream */ + addr = inp + (bitpos >> 3); + code = (addr[0] << (bitpos & 7)) | (addr[1] >> (8 - (bitpos & 7))); + + /* update bit position */ + bitpos += table[code].len; + + /* update code statistics */ + sonix_unknown += table[code].unk; + + /* calculate pixel value */ + val = table[code].val; + if (!table[code].is_abs) { + /* value is relative to top and left pixel */ + if (col < 2) { + /* left column: relative to top pixel */ + val += outp[-2*width]; + } + else if (row < 2) { + /* top row: relative to left pixel */ + val += outp[-2]; + } + else { + /* main area: average of left pixel and top pixel */ + val += (outp[-2] + outp[-2*width]) / 2; + } + } + + /* store pixel */ + *outp++ = CLAMP(val); + col++; + } + } + + return 0; +} diff --git a/kopete/libkopete/avdevice/sonix_compress.h b/kopete/libkopete/avdevice/sonix_compress.h new file mode 100644 index 00000000..509bcb07 --- /dev/null +++ b/kopete/libkopete/avdevice/sonix_compress.h @@ -0,0 +1,8 @@ +// Call this function first (just once is needed), before calling sonix_decompress +void sonix_decompress_init(void); + +// decompresses data at inp until a full image of widthxheight has been written to outp +int sonix_decompress(int width, int height, unsigned char *inp, unsigned char *outp); + +// counter to detect presence of currently unknown huffman codes +extern int sonix_unknown; diff --git a/kopete/libkopete/avdevice/videocontrol.cpp b/kopete/libkopete/avdevice/videocontrol.cpp new file mode 100644 index 00000000..f4807c1c --- /dev/null +++ b/kopete/libkopete/avdevice/videocontrol.cpp @@ -0,0 +1,36 @@ +/* + videoinput.cpp - Kopete Video Input Class + + Copyright (c) 2007 by Cláudio da Silveira Pinheiro + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "videocontrol.h" + +namespace Kopete { + +namespace AV { + +VideoControl::VideoControl() +{ +} + + +VideoControl::~VideoControl() +{ +} + + +} + +} diff --git a/kopete/libkopete/avdevice/videocontrol.h b/kopete/libkopete/avdevice/videocontrol.h new file mode 100644 index 00000000..2675be6e --- /dev/null +++ b/kopete/libkopete/avdevice/videocontrol.h @@ -0,0 +1,82 @@ +/* + videoinput.cpp - Kopete Video Input Class + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#define ENABLE_AV + +#ifndef KOPETE_AVVIDEOCONTROL_H +#define KOPETE_AVVIDEOCONTROL_H + +#include +#undef __STRICT_ANSI__ +#ifndef __u64 //required by videodev.h +#define __u64 unsigned long long +#endif // __u64 + +#ifndef __s64 //required by videodev.h +#define __s64 long long +#endif // __s64 + +#include +#include +#include +#include "kopete_export.h" + +namespace Kopete { + +namespace AV { + +typedef enum +{ + CONTROLTYPE_INTEGER = 0, + CONTROLTYPE_BOOLEAN = 1, + CONTROLTYPE_MENU = 2, + CONTROLTYPE_BUTTON = 3 +} control_type; + +typedef enum +{ + CONTROLFLAG_DISABLED = (1 << 0), // This control is permanently disabled and should be ignored by the application. + CONTROLFLAG_GRABBED = (1 << 1), // This control is temporarily unchangeable, + CONTROLFLAG_READONLY = (1 << 2), // This control is permanently readable only. + CONTROLFLAG__UPDATE = (1 << 3), // Changing this control may affect the value of other controls within the same control class. + CONTROLFLAG_INACTIVE = (1 << 4), // This control is not applicable to the current configuration. + CONTROLFLAG_SLIDER = (1 << 5) // This control is best represented as a slider. +} control_flag; +/** + @author Kopete Developers +*/ +class VideoControl{ +public: + VideoControl(); + ~VideoControl(); + +protected: + __u32 m_id; + control_type m_type; + QString m_name; + __s32 m_minimum; + __s32 m_maximum; + __s32 m_step; + __s32 m_default; + __u32 m_flags; +}; + +} + +} + +#endif diff --git a/kopete/libkopete/avdevice/videodevice.cpp b/kopete/libkopete/avdevice/videodevice.cpp new file mode 100644 index 00000000..ada02ae5 --- /dev/null +++ b/kopete/libkopete/avdevice/videodevice.cpp @@ -0,0 +1,2752 @@ +/* + videodevice.cpp - Kopete Video Device Low-level Support + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#define ENABLE_AV + +#include +#include +#include + +#include + +#include "videoinput.h" +#include "videodevice.h" + +#include "bayer.h" +#include "sonix_compress.h" + +#define CLEAR(x) memset (&(x), 0, sizeof (x)) + +namespace Kopete { + +namespace AV { + +VideoDevice::VideoDevice() +{ +// kdDebug(14010) << "libkopete (avdevice): VideoDevice() called" << endl; + descriptor = -1; + m_streambuffers = 0; + m_current_input = 0; +// kdDebug(14010) << "libkopete (avdevice): VideoDevice() exited successfuly" << endl; + maxwidth = 32767; + maxheight = 32767; + minwidth = 1; + minheight = 1; +} + + +VideoDevice::~VideoDevice() +{ +} + + + + + + +#ifdef V4L2_CAP_VIDEO_CAPTURE + +void VideoDevice::enumerateMenu (void) +{ + kdDebug(14010) << k_funcinfo << " Menu items:" << endl; + + memset (&querymenu, 0, sizeof (querymenu)); + querymenu.id = queryctrl.id; + + for (querymenu.index = queryctrl.minimum; querymenu.index <= queryctrl.maximum; querymenu.index++) + { + if (0 == xioctl (VIDIOC_QUERYMENU, &querymenu)) + { + kdDebug(14010) << k_funcinfo << " " << QString::fromLocal8Bit((const char*)querymenu.name) << endl; + } + else + { + perror ("VIDIOC_QUERYMENU"); + exit (EXIT_FAILURE); + } + } +} + + +#endif + + + + + +/*! + \fn VideoDevice::xioctl(int fd, int request, void *arg) + */ +int VideoDevice::xioctl(int request, void *arg) +{ + int r; + + do r = ioctl (descriptor, request, arg); + while (-1 == r && EINTR == errno); + return r; +} + +/*! + \fn VideoDevice::errnoReturn(const char* s) + */ +int VideoDevice::errnoReturn(const char* s) +{ + /// @todo implement me + fprintf (stderr, "%s error %d, %s\n",s, errno, strerror (errno)); + return EXIT_FAILURE; +} + +/*! + \fn VideoDevice::setFileName(QString name) + */ +int VideoDevice::setFileName(QString filename) +{ + /// @todo implement me + full_filename=filename; + return EXIT_SUCCESS; +} + +/*! + \fn VideoDevice::open() + */ +int VideoDevice::open() +{ + /// @todo implement me + + kdDebug(14010) << k_funcinfo << "called" << endl; + if(-1 != descriptor) + { + kdDebug(14010) << k_funcinfo << "Device is already open" << endl; + return EXIT_SUCCESS; + } + descriptor = ::open (QFile::encodeName(full_filename), O_RDWR, 0); + if(isOpen()) + { + kdDebug(14010) << k_funcinfo << "File " << full_filename << " was opened successfuly" << endl; + if(EXIT_FAILURE==checkDevice()) + { + kdDebug(14010) << k_funcinfo << "File " << full_filename << " could not be opened" << endl; + close(); + return EXIT_FAILURE; + } + } + else + { + kdDebug(14010) << k_funcinfo << "Unable to open file " << full_filename << "Err: "<< errno << endl; + return EXIT_FAILURE; + } + + initDevice(); + selectInput(m_current_input); + kdDebug(14010) << k_funcinfo << "exited successfuly" << endl; + return EXIT_SUCCESS; +} + +bool VideoDevice::isOpen() +{ + if(-1 == descriptor) + { +// kdDebug(14010) << k_funcinfo << "VideoDevice::isOpen() File is not open" << endl; + return false; + } +// kdDebug(14010) << k_funcinfo << "VideoDevice::isOpen() File is open" << endl; + return true; +} + +int VideoDevice::checkDevice() +{ + kdDebug(14010) << k_funcinfo << "checkDevice() called." << endl; + if(isOpen()) + { + m_videocapture=false; + m_videochromakey=false; + m_videoscale=false; + m_videooverlay=false; + m_videoread=false; + m_videoasyncio=false; + m_videostream=false; + + m_driver=VIDEODEV_DRIVER_NONE; +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + +//if(!getWorkaroundBrokenDriver()) +{ + kdDebug(14010) << k_funcinfo << "checkDevice(): " << full_filename << " Trying V4L2 API." << endl; + CLEAR(V4L2_capabilities); + + if (-1 != xioctl (VIDIOC_QUERYCAP, &V4L2_capabilities)) + { + if (!(V4L2_capabilities.capabilities & V4L2_CAP_VIDEO_CAPTURE)) + { + kdDebug(14010) << k_funcinfo << "checkDevice(): " << full_filename << " is not a video capture device." << endl; + m_driver = VIDEODEV_DRIVER_NONE; + return EXIT_FAILURE; + } + m_videocapture=true; + kdDebug(14010) << k_funcinfo << "checkDevice(): " << full_filename << " is a V4L2 device." << endl; + m_driver = VIDEODEV_DRIVER_V4L2; + m_model=QString::fromLocal8Bit((const char*)V4L2_capabilities.card); + + +// Detect maximum and minimum resolution supported by the V4L2 device + CLEAR (fmt); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl (VIDIOC_G_FMT, &fmt)) + kdDebug(14010) << k_funcinfo << "VIDIOC_G_FMT failed (" << errno << ")." << endl; + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = 32767; + fmt.fmt.pix.height = 32767; + fmt.fmt.pix.field = V4L2_FIELD_ANY; + if (-1 == xioctl (VIDIOC_S_FMT, &fmt)) + { + kdDebug(14010) << k_funcinfo << "Detecting maximum size with VIDIOC_S_FMT failed (" << errno << ").Returned maxwidth: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + // Note VIDIOC_S_FMT may change width and height. + } + else + { + maxwidth = fmt.fmt.pix.width; + maxheight = fmt.fmt.pix.height; + } + if (-1 == xioctl (VIDIOC_G_FMT, &fmt)) + kdDebug(14010) << k_funcinfo << "VIDIOC_G_FMT failed (" << errno << ")." << endl; + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = 1; + fmt.fmt.pix.height = 1; + fmt.fmt.pix.field = V4L2_FIELD_ANY; + if (-1 == xioctl (VIDIOC_S_FMT, &fmt)) + { + kdDebug(14010) << k_funcinfo << "Detecting minimum size with VIDIOC_S_FMT failed (" << errno << ").Returned maxwidth: " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + // Note VIDIOC_S_FMT may change width and height. + } + else + { + minwidth = fmt.fmt.pix.width; + minheight = fmt.fmt.pix.height; + } + +// Buggy driver paranoia +/* min = fmt.fmt.pix.width * 2; + if (fmt.fmt.pix.bytesperline < min) + fmt.fmt.pix.bytesperline = min; + min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; + if (fmt.fmt.pix.sizeimage < min) + fmt.fmt.pix.sizeimage = min; + m_buffer_size=fmt.fmt.pix.sizeimage ;*/ + + int inputisok=EXIT_SUCCESS; + m_input.clear(); + for(unsigned int loop=0; inputisok==EXIT_SUCCESS; loop++) + { + struct v4l2_input videoinput; + CLEAR(videoinput); + videoinput.index = loop; + inputisok=xioctl(VIDIOC_ENUMINPUT, &videoinput); + if(inputisok==EXIT_SUCCESS) + { + VideoInput tempinput; + tempinput.name = QString::fromLocal8Bit((const char*)videoinput.name); + tempinput.hastuner = videoinput.type & V4L2_INPUT_TYPE_TUNER; + tempinput.m_standards = videoinput.std; + m_input.push_back(tempinput); + kdDebug(14010) << k_funcinfo << "Input " << loop << ": " << tempinput.name << " (tuner: " << ((videoinput.type & V4L2_INPUT_TYPE_TUNER) != 0) << ")" << endl; + if((videoinput.type & V4L2_INPUT_TYPE_TUNER) != 0) + { +// _tunerForInput[name] = desc.tuner; +// _isTuner = true; + } + else + { +// _tunerForInput[name] = -1; + } + } + } + + + + +// ----------------------------------------------------------------------------------------------------------------- +// This must turn up to be a proper method to check for controls' existence. +CLEAR (queryctrl); +// v4l2_queryctrl may zero the .id in some cases, even if the IOCTL returns EXIT_SUCCESS (tested with a bttv card, when testing for V4L2_CID_AUDIO_VOLUME). +// As of 6th Aug 2007, according to the V4L2 specification version 0.21, this behavior is undocumented, and the example 1-8 code found at +// http://www.linuxtv.org/downloads/video4linux/API/V4L2_API/spec/x519.htm fails because of this behavior with a bttv card. + +int currentid = V4L2_CID_BASE; + +kdDebug(14010) << k_funcinfo << "Checking CID controls" << endl; + +for (currentid = V4L2_CID_BASE; currentid < V4L2_CID_LASTP1; currentid++) +//for (queryctrl.id = 9963776; queryctrl.id < 9963800; queryctrl.id++) +{ + queryctrl.id = currentid; +//kdDebug(14010) << k_funcinfo << "Checking CID controls from " << V4L2_CID_BASE << " to " << V4L2_CID_LASTP1 << ". Current: " << queryctrl.id << ". IOCTL returns: " << resultado << endl; + if (0 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + continue; + +//kdDebug(14010) << k_funcinfo << " Control: " << QString::fromLocal8Bit((const char*)queryctrl.name) << endl; +kdDebug(14010) << k_funcinfo << " Control: " << QString::fromLocal8Bit((const char*)queryctrl.name) << " Values from " << queryctrl.minimum << " to " << queryctrl.maximum << " with steps of " << queryctrl.step << ". Default: " << queryctrl.default_value << endl; + +/* switch (queryctrl.type) + { + case V4L2_CTRL_TYPE_INTEGER : + }*/ + if (queryctrl.type == V4L2_CTRL_TYPE_MENU) + enumerateMenu (); + } + else + { + if (errno == EINVAL) + continue; + + perror ("VIDIOC_QUERYCTRL"); +// exit (EXIT_FAILURE); + } +} + +kdDebug(14010) << k_funcinfo << "Checking CID private controls" << endl; + +for (currentid = V4L2_CID_PRIVATE_BASE;; currentid++) +//for (queryctrl.id = 9963776; queryctrl.id < 9963800; queryctrl.id++) +{ + queryctrl.id = currentid; +//kdDebug(14010) << k_funcinfo << "Checking CID private controls from " << V4L2_CID_PRIVATE_BASE << ". Current: " << queryctrl.id << ". IOCTL returns: " << resultado << endl; + if ( 0 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + continue; + +kdDebug(14010) << k_funcinfo << " Control: " << QString::fromLocal8Bit((const char*)queryctrl.name) << " Values from " << queryctrl.minimum << " to " << queryctrl.maximum << " with steps of " << queryctrl.step << ". Default: " << queryctrl.default_value << endl; + + if (queryctrl.type == V4L2_CTRL_TYPE_MENU) + enumerateMenu (); + } + else + { + if (errno == EINVAL) + break; + + perror ("VIDIOC_QUERYCTRL"); +// exit (EXIT_FAILURE); + } +} + + + + + } + else + { +// V4L-only drivers should return an EINVAL in errno to indicate they cannot handle V4L2 calls. Not every driver is compliant, so +// it will try the V4L api even if the error code is different than expected. + kdDebug(14010) << k_funcinfo << "checkDevice(): " << full_filename << " is not a V4L2 device." << endl; + } + +} +#endif + + CLEAR(V4L_capabilities); + + if(m_driver==VIDEODEV_DRIVER_NONE) + { + kdDebug(14010) << k_funcinfo << "checkDevice(): " << full_filename << " Trying V4L API." << endl; + if (-1 == xioctl (VIDIOCGCAP, &V4L_capabilities)) + { + perror ("ioctl (VIDIOCGCAP)"); + m_driver = VIDEODEV_DRIVER_NONE; + return EXIT_FAILURE; + } + else + { + kdDebug(14010) << k_funcinfo << full_filename << " is a V4L device." << endl; + m_driver = VIDEODEV_DRIVER_V4L; + m_model=QString::fromLocal8Bit((const char*)V4L_capabilities.name); + if(V4L_capabilities.type & VID_TYPE_CAPTURE) + m_videocapture=true; + if(V4L_capabilities.type & VID_TYPE_CHROMAKEY) + m_videochromakey=true; + if(V4L_capabilities.type & VID_TYPE_SCALES) + m_videoscale=true; + if(V4L_capabilities.type & VID_TYPE_OVERLAY) + m_videooverlay=true; +// kdDebug(14010) << "libkopete (avdevice): Inputs : " << V4L_capabilities.channels << endl; +// kdDebug(14010) << "libkopete (avdevice): Audios : " << V4L_capabilities.audios << endl; + minwidth = V4L_capabilities.minwidth; + maxwidth = V4L_capabilities.maxwidth; + minheight = V4L_capabilities.minheight; + maxheight = V4L_capabilities.maxheight; + + + int inputisok=EXIT_SUCCESS; + m_input.clear(); + for(int loop=0; loop < V4L_capabilities.channels; loop++) + { + struct video_channel videoinput; + CLEAR(videoinput); + videoinput.channel = loop; + videoinput.norm = 1; + inputisok=xioctl(VIDIOCGCHAN, &videoinput); + if(inputisok==EXIT_SUCCESS) + { + VideoInput tempinput; + tempinput.name = QString::fromLocal8Bit((const char*)videoinput.name); + tempinput.hastuner=videoinput.flags & VIDEO_VC_TUNER; +// TODO: The routine to detect the appropriate video standards for V4L must be placed here + m_input.push_back(tempinput); +// kdDebug(14010) << "libkopete (avdevice): Input " << loop << ": " << tempinput.name << " (tuner: " << ((videoinput.flags & VIDEO_VC_TUNER) != 0) << ")" << endl; +/* if((input.type & V4L2_INPUT_TYPE_TUNER) != 0) + { +// _tunerForInput[name] = desc.tuner; +// _isTuner = true; + } + else + { +// _tunerForInput[name] = -1; + } +*/ } + } + + } + } +#endif + m_name=m_model; // Take care about changing the name to be different from the model itself... + + detectPixelFormats(); + +// TODO: Now we must execute the proper initialization according to the type of the driver. + kdDebug(14010) << k_funcinfo << "checkDevice() exited successfuly." << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + + +/*! + \fn VideoDevice::showDeviceCapabilities() + */ +int VideoDevice::showDeviceCapabilities() +{ + kdDebug(14010) << k_funcinfo << "showDeviceCapabilities() called." << endl; + if(isOpen()) + { +/* kdDebug(14010) << "libkopete (avdevice): Driver: " << (const char*)V4L2_capabilities.driver << " " + << ((V4L2_capabilities.version>>16) & 0xFF) << "." + << ((V4L2_capabilities.version>> 8) & 0xFF) << "." + << ((V4L2_capabilities.version ) & 0xFF) << endl; + kdDebug(14010) << "libkopete (avdevice): Card: " << name << endl; + kdDebug(14010) << "libkopete (avdevice): Capabilities:" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_VIDEO_CAPTURE) + kdDebug(14010) << "libkopete (avdevice): Video capture" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_VIDEO_OUTPUT) + kdDebug(14010) << "libkopete (avdevice): Video output" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_VIDEO_OVERLAY) + kdDebug(14010) << "libkopete (avdevice): Video overlay" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_VBI_CAPTURE) + kdDebug(14010) << "libkopete (avdevice): VBI capture" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_VBI_OUTPUT) + kdDebug(14010) << "libkopete (avdevice): VBI output" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_RDS_CAPTURE) + kdDebug(14010) << "libkopete (avdevice): RDS capture" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_TUNER) + kdDebug(14010) << "libkopete (avdevice): Tuner IO" << endl; + if(V4L2_capabilities.capabilities & V4L2_CAP_AUDIO) + kdDebug(14010) << "libkopete (avdevice): Audio IO" << endl; +;*/ + kdDebug(14010) << k_funcinfo << "Card model: " << m_model << endl; + kdDebug(14010) << k_funcinfo << "Card name : " << m_name << endl; + kdDebug(14010) << k_funcinfo << "Capabilities:" << endl; + if(canCapture()) + kdDebug(14010) << k_funcinfo << " Video capture" << endl; + if(canRead()) + kdDebug(14010) << k_funcinfo << " Read" << endl; + if(canAsyncIO()) + kdDebug(14010) << k_funcinfo << " Asynchronous input/output" << endl; + if(canStream()) + kdDebug(14010) << k_funcinfo << " Streaming" << endl; + if(canChromakey()) + kdDebug(14010) << k_funcinfo << " Video chromakey" << endl; + if(canScale()) + kdDebug(14010) << k_funcinfo << " Video scales" << endl; + if(canOverlay()) + kdDebug(14010) << k_funcinfo << " Video overlay" << endl; +// kdDebug(14010) << "libkopete (avdevice): Audios : " << V4L_capabilities.audios << endl; + kdDebug(14010) << k_funcinfo << " Max res: " << maxWidth() << " x " << maxHeight() << endl; + kdDebug(14010) << k_funcinfo << " Min res: " << minWidth() << " x " << minHeight() << endl; + kdDebug(14010) << k_funcinfo << " Inputs : " << inputs() << endl; + for (unsigned int loop=0; loop < inputs(); loop++) + kdDebug(14010) << k_funcinfo << "Input " << loop << ": " << m_input[loop].name << " (tuner: " << m_input[loop].hastuner << ")" << endl; + kdDebug(14010) << k_funcinfo << "showDeviceCapabilities() exited successfuly." << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn VideoDevicePool::initDevice() + */ +int VideoDevice::initDevice() +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "initDevice() started" << endl; + if(-1 == descriptor) + { + kdDebug(14010) << k_funcinfo << "initDevice() Device is not open" << endl; + return EXIT_FAILURE; + } + m_io_method = IO_METHOD_NONE; + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + if(V4L2_capabilities.capabilities & V4L2_CAP_READWRITE) + { + m_videoread=true; + m_io_method = IO_METHOD_READ; + kdDebug(14010) << k_funcinfo << " Read/Write interface" << endl; + } + if(V4L2_capabilities.capabilities & V4L2_CAP_ASYNCIO) + { + m_videoasyncio=true; + kdDebug(14010) << k_funcinfo << " Async IO interface" << endl; + } + if(V4L2_capabilities.capabilities & V4L2_CAP_STREAMING) + { + m_videostream=true; + m_io_method = IO_METHOD_MMAP; +// m_io_method = IO_METHOD_USERPTR; + kdDebug(14010) << k_funcinfo << " Streaming interface" << endl; + } + if(m_io_method==IO_METHOD_NONE) + { + kdDebug(14010) << k_funcinfo << "initDevice() Found no suitable input/output method for " << full_filename << endl; + return EXIT_FAILURE; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + m_videoread=true; + m_io_method=IO_METHOD_READ; + if(-1 != xioctl(VIDIOCGFBUF,&V4L_videobuffer)) + { +// m_videostream=true; +// m_io_method = IO_METHOD_MMAP; + kdDebug(14010) << k_funcinfo << " Streaming interface" << endl; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + + break; + } + +// Select video input, video standard and tune here. +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl (VIDIOC_CROPCAP, &cropcap)) + { // Errors ignored. + } + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; // reset to default + if (-1 == xioctl (VIDIOC_S_CROP, &crop)) + { + switch (errno) + { + case EINVAL: break; // Cropping not supported. + default: break; // Errors ignored. + } + } +#endif +#endif + + showDeviceCapabilities(); + kdDebug(14010) << k_funcinfo << "initDevice() exited successfuly" << endl; + return EXIT_SUCCESS; +} + +unsigned int VideoDevice::inputs() +{ + return m_input.size(); +} + + +int VideoDevice::width() +{ + return currentwidth; +} + +int VideoDevice::minWidth() +{ + return minwidth; +} + +int VideoDevice::maxWidth() +{ + return maxwidth; +} + +int VideoDevice::height() +{ + return currentheight; +} + +int VideoDevice::minHeight() +{ + return minheight; +} + +int VideoDevice::maxHeight() +{ + return maxheight; +} + +int VideoDevice::setSize( int newwidth, int newheight) +{ +kdDebug(14010) << k_funcinfo << "setSize(" << newwidth << ", " << newheight << ") called." << endl; + if(isOpen()) + { +// It should not be there. It must remain in a completely distict place, cause this method should not change the pixelformat. + kdDebug(14010) << k_funcinfo << "Trying YUY422P" << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_YUV422P)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support YUV422P format. Trying YUYV." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_YUYV)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support YUYV format. Trying UYVY." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_UYVY)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support UYVY format. Trying YUV420P." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_YUV420P)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support YUV420P format. Trying RGB24." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_RGB24)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support RGB24 format. Trying BGR24." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_BGR24)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support RGB24 format. Trying RGB32." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_RGB32)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support RGB32 format. Trying BGR32." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_BGR32)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support BGR32 format. Trying SN9C10X." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_SN9C10X)) + { + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support SN9C10X format. Trying Bayer RGB." << endl; + if(PIXELFORMAT_NONE == setPixelFormat(PIXELFORMAT_SBGGR8)) + kdDebug(14010) << k_funcinfo << "Card doesn't seem to support SBGGR8 format. Fallback from it is not yet implemented." << endl; + } + } + } + } + } + } + } + } + } + + if(newwidth > maxwidth ) newwidth = maxwidth; + if(newheight > maxheight) newheight = maxheight; + if(newwidth < minwidth ) newwidth = minwidth; + if(newheight < minheight) newheight = minheight; + + currentwidth = newwidth; + currentheight = newheight; + +//kdDebug(14010) << k_funcinfo << "width: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << width() << "x" << height() << endl; +// Change resolution for the video device + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: +// CLEAR (fmt); + if (-1 == xioctl (VIDIOC_G_FMT, &fmt)) + kdDebug(14010) << k_funcinfo << "VIDIOC_G_FMT failed (" << errno << ").Returned width: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = width(); + fmt.fmt.pix.height = height(); + fmt.fmt.pix.field = V4L2_FIELD_ANY; + if (-1 == xioctl (VIDIOC_S_FMT, &fmt)) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_S_FMT failed (" << errno << ").Returned width: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + // Note VIDIOC_S_FMT may change width and height. + } + else + { +// Buggy driver paranoia. +kdDebug(14010) << k_funcinfo << "VIDIOC_S_FMT worked (" << errno << ").Returned width: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + unsigned int min = fmt.fmt.pix.width * 2; + if (fmt.fmt.pix.bytesperline < min) + fmt.fmt.pix.bytesperline = min; + min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; + if (fmt.fmt.pix.sizeimage < min) + fmt.fmt.pix.sizeimage = min; + m_buffer_size=fmt.fmt.pix.sizeimage ; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_window V4L_videowindow; + +kdDebug(14010) << "------------- width: " << V4L_videowindow.width << " Height: " << V4L_videowindow.height << " Clipcount: " << V4L_videowindow.clipcount << " -----------------" << endl; + + if (xioctl (VIDIOCGWIN, &V4L_videowindow)== -1) + { + perror ("ioctl VIDIOCGWIN"); +// return (NULL); + } + V4L_videowindow.width = width(); + V4L_videowindow.height = height(); + V4L_videowindow.clipcount=0; + if (xioctl (VIDIOCSWIN, &V4L_videowindow)== -1) + { + perror ("ioctl VIDIOCSWIN"); +// return (NULL); + } +kdDebug(14010) << "------------- width: " << V4L_videowindow.width << " Height: " << V4L_videowindow.height << " Clipcount: " << V4L_videowindow.clipcount << " -----------------" << endl; + +// kdDebug(14010) << "libkopete (avdevice): V4L_picture.palette: " << V4L_picture.palette << " Depth: " << V4L_picture.depth << endl; + +/* if(-1 == xioctl(VIDIOCGFBUF,&V4L_videobuffer)) + kdDebug(14010) << "libkopete (avdevice): VIDIOCGFBUF failed (" << errno << "): Card cannot stream" << endl;*/ + + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + m_buffer_size = width() * height() * pixelFormatDepth(m_pixelformat) / 8; +kdDebug(14010) << "------------------------- ------- -- m_buffer_size: " << m_buffer_size << " !!! -- ------- -----------------------------------------" << endl; + + m_currentbuffer.pixelformat=m_pixelformat; + m_currentbuffer.data.resize(m_buffer_size); + + switch (m_io_method) + { + case IO_METHOD_NONE: break; + case IO_METHOD_READ: initRead (); break; + case IO_METHOD_MMAP: initMmap (); break; + case IO_METHOD_USERPTR: initUserptr (); break; + } + +kdDebug(14010) << k_funcinfo << "setSize(" << newwidth << ", " << newheight << ") exited successfuly." << endl; + return EXIT_SUCCESS; + } +kdDebug(14010) << k_funcinfo << "setSize(" << newwidth << ", " << newheight << ") Device is not open." << endl; + return EXIT_FAILURE; +} + + + + + + + + + + + + + +pixel_format VideoDevice::setPixelFormat(pixel_format newformat) +{ + pixel_format ret = PIXELFORMAT_NONE; +//kdDebug(14010) << k_funcinfo << "called." << endl; +// Change the pixel format for the video device + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: +// CLEAR (fmt); + if (-1 == xioctl (VIDIOC_G_FMT, &fmt)) + { +// return errnoReturn ("VIDIOC_S_FMT"); +// kdDebug(14010) << k_funcinfo << "VIDIOC_G_FMT failed (" << errno << ").Returned width: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + } + else + m_pixelformat = pixelFormatForPalette(fmt.fmt.pix.pixelformat); + + fmt.fmt.pix.pixelformat = pixelFormatCode(newformat); + if (-1 == xioctl (VIDIOC_S_FMT, &fmt)) + { +// kdDebug(14010) << k_funcinfo << "VIDIOC_S_FMT failed (" << errno << ").Returned width: " << pixelFormatName(fmt.fmt.pix.pixelformat) << " " << fmt.fmt.pix.width << "x" << fmt.fmt.pix.height << endl; + } + else + { + if (fmt.fmt.pix.pixelformat == pixelFormatCode(newformat)) + { + m_pixelformat = newformat; + ret = m_pixelformat; + } + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_picture V4L_picture; + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; +// kdDebug(14010) << k_funcinfo << "V4L_picture.palette: " << V4L_picture.palette << " Depth: " << V4L_picture.depth << endl; + V4L_picture.palette = pixelFormatCode(newformat); + V4L_picture.depth = pixelFormatDepth(newformat); + if(-1 == xioctl(VIDIOCSPICT,&V4L_picture)) + { +// kdDebug(14010) << k_funcinfo << "Card seems to not support " << pixelFormatName(newformat) << " format. Fallback to it is not yet implemented." << endl; + } + + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; + +// kdDebug(14010) << k_funcinfo << "V4L_picture.palette: " << V4L_picture.palette << " Depth: " << V4L_picture.depth << endl; + m_pixelformat=pixelFormatForPalette(V4L_picture.palette); + if (m_pixelformat == newformat) + ret = newformat; + + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return ret; +} + + + + + + +/*! + \fn Kopete::AV::VideoDevice::currentInput() + */ +int VideoDevice::currentInput() +{ + /// @todo implement me + if(isOpen()) + { + return m_current_input; + } + return 0; +} + +/*! + \fn Kopete::AV::VideoDevice::selectInput(int input) + */ +int VideoDevice::selectInput(int newinput) +{ + /// @todo implement me + if(m_current_input >= inputs()) + return EXIT_FAILURE; + + if(isOpen()) + { + switch (m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + if (-1 == ioctl (descriptor, VIDIOC_S_INPUT, &newinput)) + { + perror ("VIDIOC_S_INPUT"); + return EXIT_FAILURE; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + struct video_channel V4L_input; + V4L_input.channel=newinput; + V4L_input.norm=4; // Hey, it's plain wrong! It should be input's signal standard! + if (-1 == ioctl (descriptor, VIDIOCSCHAN, &V4L_input)) + { + perror ("ioctl (VIDIOCSCHAN)"); + return EXIT_FAILURE; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + kdDebug(14010) << k_funcinfo << "Selected input " << newinput << " (" << m_input[newinput].name << ")" << endl; + m_current_input = newinput; + setInputParameters(); + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn Kopete::AV::VideoDevice::setInputParameters() + */ +int VideoDevice::setInputParameters() +{ + /// @todo implement me + if( (isOpen()) && (m_current_input < inputs() ) ) + { + setBrightness( getBrightness() ); + setContrast( getContrast() ); + setSaturation( getSaturation() ); + setWhiteness( getWhiteness() ); + setHue( getHue() ); + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn VideoDevice::startCapturing() + */ +int VideoDevice::startCapturing() +{ + + kdDebug(14010) << k_funcinfo << "called." << endl; + if(isOpen()) + { + switch (m_io_method) + { + case IO_METHOD_NONE: // Card cannot capture frames + return EXIT_FAILURE; + break; + case IO_METHOD_READ: // Nothing to do + break; + case IO_METHOD_MMAP: +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + { + unsigned int loop; + for (loop = 0; loop < m_streambuffers; ++loop) + { + struct v4l2_buffer buf; + CLEAR (buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = loop; + if (-1 == xioctl (VIDIOC_QBUF, &buf)) + return errnoReturn ("VIDIOC_QBUF"); + } + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl (VIDIOC_STREAMON, &type)) + return errnoReturn ("VIDIOC_STREAMON"); + } +#endif +#endif + break; + case IO_METHOD_USERPTR: +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + { + unsigned int loop; + for (loop = 0; loop < m_streambuffers; ++loop) + { + struct v4l2_buffer buf; + CLEAR (buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + buf.m.userptr = (unsigned long) m_rawbuffers[loop].start; + buf.length = m_rawbuffers[loop].length; + if (-1 == xioctl (VIDIOC_QBUF, &buf)) + return errnoReturn ("VIDIOC_QBUF"); + } + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl (VIDIOC_STREAMON, &type)) + return errnoReturn ("VIDIOC_STREAMON"); + } +#endif +#endif + break; + } + + kdDebug(14010) << k_funcinfo << "exited successfuly." << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn VideoDevice::getFrame() + */ +int VideoDevice::getFrame() +{ + /// @todo implement me + ssize_t bytesread; + +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + struct v4l2_buffer v4l2buffer; +#endif +#endif +// kdDebug(14010) << k_funcinfo << "getFrame() called." << endl; + if(isOpen()) + { + switch (m_io_method) + { + case IO_METHOD_NONE: // Card cannot capture frames + return EXIT_FAILURE; + break; + case IO_METHOD_READ: +// kdDebug(14010) << k_funcinfo << "Using IO_METHOD_READ.File descriptor: " << descriptor << " Buffer address: " << &m_currentbuffer.data[0] << " Size: " << m_currentbuffer.data.size() << endl; + bytesread = read (descriptor, &m_currentbuffer.data[0], m_currentbuffer.data.size()); + if (-1 == bytesread) // must verify this point with ov511 driver. + { + kdDebug(14010) << k_funcinfo << "IO_METHOD_READ failed." << endl; + switch (errno) + { + case EAGAIN: + return EXIT_FAILURE; + case EIO: /* Could ignore EIO, see spec. fall through */ + default: + return errnoReturn ("read"); + } + } + if((int)m_currentbuffer.data.size() < bytesread) + { + kdDebug(14010) << k_funcinfo << "IO_METHOD_READ returned less bytes (" << bytesread << ") than it was asked for (" << m_currentbuffer.data.size() <<")." << endl; + } + break; + case IO_METHOD_MMAP: +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + CLEAR (v4l2buffer); + v4l2buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2buffer.memory = V4L2_MEMORY_MMAP; + if (-1 == xioctl (VIDIOC_DQBUF, &v4l2buffer)) + { + kdDebug(14010) << k_funcinfo << full_filename << " MMAPed getFrame failed." << endl; + switch (errno) + { + case EAGAIN: + { + kdDebug(14010) << k_funcinfo << full_filename << " MMAPed getFrame failed: EAGAIN. Pointer: " << endl; + return EXIT_FAILURE; + } + case EIO: /* Could ignore EIO, see spec. fall through */ + default: + return errnoReturn ("VIDIOC_DQBUF"); + } + } +/* if (v4l2buffer.index < m_streambuffers) + return EXIT_FAILURE;*/ //it was an assert() +//kdDebug(14010) << k_funcinfo << "m_rawbuffers[" << v4l2buffer.index << "].start: " << (void *)m_rawbuffers[v4l2buffer.index].start << " Size: " << m_currentbuffer.data.size() << endl; + + + +/*{ + unsigned long long result=0; + unsigned long long R=0, G=0, B=0, A=0; + int Rmax=0, Gmax=0, Bmax=0, Amax=0; + int Rmin=255, Gmin=255, Bmin=255, Amin=0; + + for(unsigned int loop=0;loop < m_currentbuffer.data.size();loop+=4) + { + R+=m_rawbuffers[v4l2buffer.index].start[loop]; + G+=m_rawbuffers[v4l2buffer.index].start[loop+1]; + B+=m_rawbuffers[v4l2buffer.index].start[loop+2]; +// A+=currentbuffer.data[loop+3]; + if (m_currentbuffer.data[loop] < Rmin) Rmin = m_currentbuffer.data[loop]; + if (m_currentbuffer.data[loop+1] < Gmin) Gmin = m_currentbuffer.data[loop+1]; + if (m_currentbuffer.data[loop+2] < Bmin) Bmin = m_currentbuffer.data[loop+2]; +// if (m_currentbuffer.data[loop+3] < Amin) Amin = m_currentbuffer.data[loop+3]; + if (m_currentbuffer.data[loop] > Rmax) Rmax = m_currentbuffer.data[loop]; + if (m_currentbuffer.data[loop+1] > Gmax) Gmax = m_currentbuffer.data[loop+1]; + if (m_currentbuffer.data[loop+2] > Bmax) Bmax = m_currentbuffer.data[loop+2]; +// if (m_currentbuffer.data[loop+3] > Amax) Amax = m_currentbuffer.data[loop+3]; + } + kdDebug(14010) << " R: " << R << " G: " << G << " B: " << B << " A: " << A << + " Rmin: " << Rmin << " Gmin: " << Gmin << " Bmin: " << Bmin << " Amin: " << Amin << + " Rmax: " << Rmax << " Gmax: " << Gmax << " Bmax: " << Bmax << " Amax: " << Amax << endl; +}*/ + + +memcpy(&m_currentbuffer.data[0], m_rawbuffers[v4l2buffer.index].start, m_currentbuffer.data.size()); + if (-1 == xioctl (VIDIOC_QBUF, &v4l2buffer)) + return errnoReturn ("VIDIOC_QBUF"); +#endif +#endif + break; + case IO_METHOD_USERPTR: +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + { + unsigned int i; + CLEAR (v4l2buffer); + v4l2buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2buffer.memory = V4L2_MEMORY_USERPTR; + if (-1 == xioctl (VIDIOC_DQBUF, &v4l2buffer)) + { + switch (errno) + { + case EAGAIN: + return EXIT_FAILURE; + case EIO: /* Could ignore EIO, see spec. fall through */ + default: + return errnoReturn ("VIDIOC_DQBUF"); + } + } + for (i = 0; i < m_streambuffers; ++i) + if (v4l2buffer.m.userptr == (unsigned long) m_rawbuffers[i].start && v4l2buffer.length == m_rawbuffers[i].length) + break; + if (i < m_streambuffers) + return EXIT_FAILURE; + if (-1 == xioctl (VIDIOC_QBUF, &v4l2buffer)) + return errnoReturn ("VIDIOC_QBUF"); + } +#endif +#endif + break; + } + +/* Automatic color correction. Now it just swaps R and B channels in RGB24/BGR24 modes. + if(m_input[m_current_input].getAutoColorCorrection()) + { + switch(m_currentbuffer.pixelformat) + { + case PIXELFORMAT_NONE : break; + case PIXELFORMAT_GREY : break; + case PIXELFORMAT_RGB332 : break; + case PIXELFORMAT_RGB555 : break; + case PIXELFORMAT_RGB555X: break; + case PIXELFORMAT_RGB565 : break; + case PIXELFORMAT_RGB565X: break; + case PIXELFORMAT_RGB24 : + case PIXELFORMAT_BGR24 : + { + unsigned char temp; + for(unsigned int loop=0;loop < m_currentbuffer.data.size();loop+=3) + { + temp = m_currentbuffer.data[loop]; + m_currentbuffer.data[loop] = m_currentbuffer.data[loop+2]; + m_currentbuffer.data[loop+2] = temp; + } + } + break; + case PIXELFORMAT_RGB32 : + case PIXELFORMAT_BGR32 : + { + unsigned char temp; + for(unsigned int loop=0;loop < m_currentbuffer.data.size();loop+=4) + { + temp = m_currentbuffer.data[loop]; + m_currentbuffer.data[loop] = m_currentbuffer.data[loop+2]; + m_currentbuffer.data[loop+2] = temp; + } + } + break; + case PIXELFORMAT_YUYV : break; + case PIXELFORMAT_UYVY : break; + case PIXELFORMAT_YUV420P: break; + case PIXELFORMAT_YUV422P: break; + } + }*/ +//kdDebug(14010) << k_funcinfo << "10 Using IO_METHOD_READ.File descriptor: " << descriptor << " Buffer address: " << &m_currentbuffer.data[0] << " Size: " << m_currentbuffer.data.size() << endl; + + +// put frame copy operation here +// kdDebug(14010) << k_funcinfo << "exited successfuly." << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn VideoDevice::getFrame(imagebuffer *imgbuffer) + */ +int VideoDevice::getFrame(imagebuffer *imgbuffer) +{ + if(imgbuffer) + { + getFrame(); + imgbuffer->height = m_currentbuffer.height; + imgbuffer->width = m_currentbuffer.width; + imgbuffer->pixelformat = m_currentbuffer.pixelformat; + imgbuffer->data = m_currentbuffer.data; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn Kopete::AV::VideoDevice::getImage(const QImage *qimage) + */ +int VideoDevice::getImage(QImage *qimage) +{ + /// @todo implement me + + // do NOT delete qimage here, as it is received as a parameter + if (qimage->width() != width() || qimage->height() != height()) + qimage->create(width(), height(),32, QImage::IgnoreEndian); + + uchar *bits=qimage->bits(); +// kDebug() << "Capturing in " << pixelFormatName(m_currentbuffer.pixelformat); + switch(m_currentbuffer.pixelformat) + { + case PIXELFORMAT_NONE : break; + +// Packed RGB formats + case PIXELFORMAT_RGB332 : break; + case PIXELFORMAT_RGB444 : break; + case PIXELFORMAT_RGB555 : break; + case PIXELFORMAT_RGB565 : + { + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = (m_currentbuffer.data[step]<<3)+(m_currentbuffer.data[step]<<3>>5); + bits[loop+1] = ((m_currentbuffer.data[step+1])<<5)|m_currentbuffer.data[step]>>5; + bits[loop+2] = ((m_currentbuffer.data[step+1])&248)+((m_currentbuffer.data[step+1])>>5); + bits[loop+3] = 255; + step+=2; + } + } + break; + case PIXELFORMAT_RGB555X: break; + case PIXELFORMAT_RGB565X: break; + case PIXELFORMAT_BGR24 : + { + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = m_currentbuffer.data[step+2]; + bits[loop+1] = m_currentbuffer.data[step+1]; + bits[loop+2] = m_currentbuffer.data[step]; + bits[loop+3] = 255; + step+=3; + } + } + break; + case PIXELFORMAT_RGB24 : + { + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = m_currentbuffer.data[step]; + bits[loop+1] = m_currentbuffer.data[step+1]; + bits[loop+2] = m_currentbuffer.data[step+2]; + bits[loop+3] = 255; + step+=3; + } + } + break; + case PIXELFORMAT_BGR32 : break; + case PIXELFORMAT_RGB32 : memcpy(bits,&m_currentbuffer.data[0], m_currentbuffer.data.size()); + break; + +// Bayer RGB format + case PIXELFORMAT_SBGGR8 : + { + unsigned char *d = (unsigned char *) malloc (width() * height() * 3); + bayer2rgb24(d, &m_currentbuffer.data.first(), width(), height()); + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = d[step+2]; + bits[loop+1] = d[step+1]; + bits[loop+2] = d[step]; + bits[loop+3] = 255; + step+=3; + } + free(d); + } + break; + +// YUV formats + case PIXELFORMAT_GREY : break; + case PIXELFORMAT_YUYV: + case PIXELFORMAT_UYVY: + case PIXELFORMAT_YUV420P: + case PIXELFORMAT_YUV422P: + { + uchar *yptr, *cbptr, *crptr; + bool halfheight=false; + bool packed=false; +// Adjust algorythm to specific YUV data arrangements. + if (m_currentbuffer.pixelformat == PIXELFORMAT_YUV420P) + halfheight=true; + if (m_currentbuffer.pixelformat == PIXELFORMAT_YUYV) + { + yptr = &m_currentbuffer.data[0]; + cbptr = yptr + 1; + crptr = yptr + 3; + packed=true; + } + else if (m_currentbuffer.pixelformat == PIXELFORMAT_UYVY) + { + cbptr = &m_currentbuffer.data[0]; + yptr = cbptr + 1; + crptr = cbptr + 2; + packed=true; + } + else + { + yptr = &m_currentbuffer.data[0]; + cbptr = yptr + (width()*height()); + crptr = cbptr + (width()*height()/(halfheight ? 4:2)); + } + + for(int y=0; y>1<<2])-128; + e = (crptr[x>>1<<2])-128; + } + else + { + c = (yptr[x])-16; + d = (cbptr[x>>1])-128; + e = (crptr[x>>1])-128; + } + + int r = (298 * c + 409 * e + 128)>>8; + int g = (298 * c - 100 * d - 208 * e + 128)>>8; + int b = (298 * c + 516 * d + 128)>>8; + + if (r<0) r=0; if (r>255) r=255; + if (g<0) g=0; if (g>255) g=255; + if (b<0) b=0; if (b>255) b=255; + + uint *p = (uint*)qimage->scanLine(y)+x; + *p = qRgba(r,g,b,255); + + } +// Jump to next line + if (packed) + { + yptr+=width()*2; + cbptr+=width()*2; + crptr+=width()*2; + } + else + { + yptr+=width(); + if (!halfheight || y&1) + { + cbptr+=width()/2; + crptr+=width()/2; + } + } + } + } + break; + +// Compressed formats + case PIXELFORMAT_JPEG : break; + case PIXELFORMAT_MPEG : break; + +// Reserved formats + case PIXELFORMAT_DV : break; + case PIXELFORMAT_ET61X251:break; + case PIXELFORMAT_HI240 : break; + case PIXELFORMAT_HM12 : break; + case PIXELFORMAT_MJPEG : break; + case PIXELFORMAT_PWC1 : break; + case PIXELFORMAT_PWC2 : break; + case PIXELFORMAT_SN9C10X: + { + unsigned char *s = new unsigned char [width() * height()]; + unsigned char *d = new unsigned char [width() * height() * 3]; + sonix_decompress_init(); + sonix_decompress(width(), height(), &m_currentbuffer.data.first(), s); + bayer2rgb24(d, s, width(), height()); + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = d[step+2]; + bits[loop+1] = d[step+1]; + bits[loop+2] = d[step]; + bits[loop+3] = 255; + step+=3; + } + delete[] s; + delete[] d; + } + break; + case PIXELFORMAT_WNVA : break; + case PIXELFORMAT_YYUV : break; + } + return EXIT_SUCCESS; +} + +/*! + \fn VideoDevice::stopCapturing() + */ +int VideoDevice::stopCapturing() +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "called." << endl; + if(isOpen()) + { + switch (m_io_method) + { + case IO_METHOD_NONE: // Card cannot capture frames + return EXIT_FAILURE; + break; + case IO_METHOD_READ: // Nothing to do + break; + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: +#ifdef V4L2_CAP_VIDEO_CAPTURE + { + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl (VIDIOC_STREAMOFF, &type)) + return errnoReturn ("VIDIOC_STREAMOFF"); + + if (m_io_method == IO_METHOD_MMAP) + { + unsigned int loop; + for (loop = 0; loop < m_streambuffers; ++loop) + { + if (munmap(m_rawbuffers[loop].start,m_rawbuffers[loop].length) != 0) + { + kdDebug(14010) << k_funcinfo << "unable to munmap." << endl; + } + } + } + } +#endif + break; + } + kdDebug(14010) << k_funcinfo << "exited successfuly." << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + + +/*! + \fn VideoDevice::close() + */ +int VideoDevice::close() +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << " called." << endl; + if(isOpen()) + { + kdDebug(14010) << k_funcinfo << " Device is open. Trying to properly shutdown the device." << endl; + stopCapturing(); + kdDebug(14010) << k_funcinfo << "::close() returns " << ::close(descriptor) << endl; + } + descriptor = -1; + return EXIT_SUCCESS; +} + +float VideoDevice::getBrightness() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getBrightness(); + else + return 0; +} + +float VideoDevice::setBrightness(float brightness) +{ + kdDebug(14010) << k_funcinfo << " called." << endl; + m_input[m_current_input].setBrightness(brightness); // Just to check bounds + + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + { + struct v4l2_queryctrl queryctrl; + struct v4l2_control control; + + CLEAR (queryctrl); + queryctrl.id = V4L2_CID_BRIGHTNESS; + + if (-1 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (errno != EINVAL) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_QUERYCTRL failed (" << errno << ")." << endl; + } else + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Brightness control." << endl; + } + } else + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Brightness control." << endl; + } else + { + CLEAR (control); + control.id = V4L2_CID_BRIGHTNESS; + control.value = (__s32)((queryctrl.maximum - queryctrl.minimum)*getBrightness()); + + if (-1 == xioctl (VIDIOC_S_CTRL, &control)) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_S_CTRL failed (" << errno << ")." << endl; + } + } + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_picture V4L_picture; + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; + V4L_picture.brightness = uint(65535*getBrightness()); + if(-1 == xioctl(VIDIOCSPICT,&V4L_picture)) + kdDebug(14010) << k_funcinfo << "Card seems to not support adjusting image brightness. Fallback to it is not yet implemented." << endl; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return getBrightness(); +} + +float VideoDevice::getContrast() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getContrast(); + else + return 0; +} + +float VideoDevice::setContrast(float contrast) +{ + kdDebug(14010) << k_funcinfo << " called." << endl; + m_input[m_current_input].setContrast(contrast); // Just to check bounds + + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + { + struct v4l2_queryctrl queryctrl; + struct v4l2_control control; + + CLEAR (queryctrl); + queryctrl.id = V4L2_CID_CONTRAST; + + if (-1 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (errno != EINVAL) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_QUERYCTRL failed (" << errno << ")." << endl; + } else + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Contrast control." << endl; + } + } else + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Contrast control." << endl; + } else + { + CLEAR (control); + control.id = V4L2_CID_CONTRAST; + control.value = (__s32)((queryctrl.maximum - queryctrl.minimum)*getContrast()); + + if (-1 == xioctl (VIDIOC_S_CTRL, &control)) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_S_CTRL failed (" << errno << ")." << endl; + } + } + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_picture V4L_picture; + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; + V4L_picture.contrast = uint(65535*getContrast()); + if(-1 == xioctl(VIDIOCSPICT,&V4L_picture)) + kdDebug(14010) << k_funcinfo << "Card seems to not support adjusting image contrast. Fallback to it is not yet implemented." << endl; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return getContrast(); +} + +float VideoDevice::getSaturation() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getSaturation(); + else + return 0; +} + +float VideoDevice::setSaturation(float saturation) +{ + kdDebug(14010) << k_funcinfo << " called." << endl; + m_input[m_current_input].setSaturation(saturation); // Just to check bounds + + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + { + struct v4l2_queryctrl queryctrl; + struct v4l2_control control; + + CLEAR (queryctrl); + queryctrl.id = V4L2_CID_SATURATION; + + if (-1 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (errno != EINVAL) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_QUERYCTRL failed (" << errno << ")." << endl; + } else + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Saturation control." << endl; + } + } else + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Saturation control." << endl; + } else + { + CLEAR (control); + control.id = V4L2_CID_SATURATION; + control.value = (__s32)((queryctrl.maximum - queryctrl.minimum)*getSaturation()); + + if (-1 == xioctl (VIDIOC_S_CTRL, &control)) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_S_CTRL failed (" << errno << ")." << endl; + } + } + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_picture V4L_picture; + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; + V4L_picture.colour = uint(65535*getSaturation()); + if(-1 == xioctl(VIDIOCSPICT,&V4L_picture)) + kdDebug(14010) << k_funcinfo << "Card seems to not support adjusting image saturation. Fallback to it is not yet implemented." << endl; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return getSaturation(); +} + +float VideoDevice::getWhiteness() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getWhiteness(); + else + return 0; +} + +float VideoDevice::setWhiteness(float whiteness) +{ + kdDebug(14010) << k_funcinfo << " called." << endl; + m_input[m_current_input].setWhiteness(whiteness); // Just to check bounds + + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + { + struct v4l2_queryctrl queryctrl; + struct v4l2_control control; + + CLEAR (queryctrl); + queryctrl.id = V4L2_CID_WHITENESS; + + if (-1 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (errno != EINVAL) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_QUERYCTRL failed (" << errno << ")." << endl; + } else + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Whiteness control." << endl; + } + } else + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Whiteness control." << endl; + } else + { + CLEAR (control); + control.id = V4L2_CID_WHITENESS; + control.value = (__s32)((queryctrl.maximum - queryctrl.minimum)*getWhiteness()); + + if (-1 == xioctl (VIDIOC_S_CTRL, &control)) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_S_CTRL failed (" << errno << ")." << endl; + } + } + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_picture V4L_picture; + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; + V4L_picture.whiteness = uint(65535*getWhiteness()); + if(-1 == xioctl(VIDIOCSPICT,&V4L_picture)) + kdDebug(14010) << k_funcinfo << "Card seems to not support adjusting white level. Fallback to it is not yet implemented." << endl; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return getWhiteness(); +} + +float VideoDevice::getHue() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getHue(); + else + return 0; +} + +float VideoDevice::setHue(float hue) +{ + kdDebug(14010) << k_funcinfo << " called." << endl; + m_input[m_current_input].setHue(hue); // Just to check bounds + + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + { + struct v4l2_queryctrl queryctrl; + struct v4l2_control control; + + CLEAR (queryctrl); + queryctrl.id = V4L2_CID_HUE; + + if (-1 == xioctl (VIDIOC_QUERYCTRL, &queryctrl)) + { + if (errno != EINVAL) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_QUERYCTRL failed (" << errno << ")." << endl; + } else + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Hue control." << endl; + } + } else + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) + { + kdDebug(14010) << k_funcinfo << "Device doesn't support the Hue control." << endl; + } else + { + CLEAR (control); + control.id = V4L2_CID_HUE; + control.value = (__s32)((queryctrl.maximum - queryctrl.minimum)*getHue()); + + if (-1 == xioctl (VIDIOC_S_CTRL, &control)) + { + kdDebug(14010) << k_funcinfo << "VIDIOC_S_CTRL failed (" << errno << ")." << endl; + } + } + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + { + struct video_picture V4L_picture; + if(-1 == xioctl(VIDIOCGPICT, &V4L_picture)) + kdDebug(14010) << k_funcinfo << "VIDIOCGPICT failed (" << errno << ")." << endl; + V4L_picture.hue = uint(65535*getHue()); + if(-1 == xioctl(VIDIOCSPICT,&V4L_picture)) + kdDebug(14010) << k_funcinfo << "Card seems to not support adjusting image hue. Fallback to it is not yet implemented." << endl; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return getHue(); +} + + +bool VideoDevice::getAutoBrightnessContrast() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getAutoBrightnessContrast(); + else + return false; +} + +bool VideoDevice::setAutoBrightnessContrast(bool brightnesscontrast) +{ + kdDebug(14010) << k_funcinfo << "VideoDevice::setAutoBrightnessContrast(" << brightnesscontrast << ") called." << endl; + if (m_current_input < m_input.size() ) + { + m_input[m_current_input].setAutoBrightnessContrast(brightnesscontrast); + return m_input[m_current_input].getAutoBrightnessContrast(); + } + else + return false; + +} + +bool VideoDevice::getAutoColorCorrection() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getAutoColorCorrection(); + else + return false; +} + +bool VideoDevice::setAutoColorCorrection(bool colorcorrection) +{ + kdDebug(14010) << k_funcinfo << "VideoDevice::setAutoColorCorrection(" << colorcorrection << ") called." << endl; + if (m_current_input < m_input.size() ) + { + m_input[m_current_input].setAutoColorCorrection(colorcorrection); + return m_input[m_current_input].getAutoColorCorrection(); + } + else + return false; +} + +bool VideoDevice::getImageAsMirror() +{ + if (m_current_input < m_input.size() ) + return m_input[m_current_input].getImageAsMirror(); + else + return false; +} + +bool VideoDevice::setImageAsMirror(bool imageasmirror) +{ + kdDebug(14010) << k_funcinfo << "VideoDevice::setImageAsMirror(" << imageasmirror << ") called." << endl; + if (m_current_input < m_input.size() ) + { + m_input[m_current_input].setImageAsMirror(imageasmirror); + return m_input[m_current_input].getImageAsMirror(); + } + else + return false; +} + +pixel_format VideoDevice::pixelFormatForPalette( int palette ) +{ + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + switch(palette) + { + case 0 : return PIXELFORMAT_NONE; break; + +// Packed RGB formats + case V4L2_PIX_FMT_RGB332 : return PIXELFORMAT_RGB332; break; +#if defined( V4L2_PIX_FMT_RGB444 ) + case V4L2_PIX_FMT_RGB444 : return PIXELFORMAT_RGB444; break; +#endif + case V4L2_PIX_FMT_RGB555 : return PIXELFORMAT_RGB555; break; + case V4L2_PIX_FMT_RGB565 : return PIXELFORMAT_RGB565; break; + case V4L2_PIX_FMT_RGB555X : return PIXELFORMAT_RGB555X; break; + case V4L2_PIX_FMT_RGB565X : return PIXELFORMAT_RGB565X; break; + case V4L2_PIX_FMT_BGR24 : return PIXELFORMAT_BGR24; break; + case V4L2_PIX_FMT_RGB24 : return PIXELFORMAT_RGB24; break; + case V4L2_PIX_FMT_BGR32 : return PIXELFORMAT_BGR32; break; + case V4L2_PIX_FMT_RGB32 : return PIXELFORMAT_RGB32; break; + +// Bayer RGB format + case V4L2_PIX_FMT_SBGGR8 : return PIXELFORMAT_SBGGR8; break; + +// YUV formats + case V4L2_PIX_FMT_GREY : return PIXELFORMAT_GREY; break; + case V4L2_PIX_FMT_YUYV : return PIXELFORMAT_YUYV; break; + case V4L2_PIX_FMT_UYVY : return PIXELFORMAT_UYVY; break; + case V4L2_PIX_FMT_YUV420 : return PIXELFORMAT_YUV420P; break; + case V4L2_PIX_FMT_YUV422P : return PIXELFORMAT_YUV422P; break; + +// Compressed formats + case V4L2_PIX_FMT_JPEG : return PIXELFORMAT_JPEG; break; + case V4L2_PIX_FMT_MPEG : return PIXELFORMAT_MPEG; break; + +// Reserved formats + case V4L2_PIX_FMT_DV : return PIXELFORMAT_DV; break; + case V4L2_PIX_FMT_ET61X251 : return PIXELFORMAT_ET61X251; break; + case V4L2_PIX_FMT_HI240 : return PIXELFORMAT_HI240; break; +#if defined( V4L2_PIX_FMT_HM12 ) + case V4L2_PIX_FMT_HM12 : return PIXELFORMAT_HM12; break; +#endif + case V4L2_PIX_FMT_MJPEG : return PIXELFORMAT_MJPEG; break; + case V4L2_PIX_FMT_PWC1 : return PIXELFORMAT_PWC1; break; + case V4L2_PIX_FMT_PWC2 : return PIXELFORMAT_PWC2; break; + case V4L2_PIX_FMT_SN9C10X : return PIXELFORMAT_SN9C10X; break; + case V4L2_PIX_FMT_WNVA : return PIXELFORMAT_WNVA; break; + case V4L2_PIX_FMT_YYUV : return PIXELFORMAT_YYUV; break; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + switch(palette) + { + case 0 : return PIXELFORMAT_NONE; break; + case VIDEO_PALETTE_GREY : return PIXELFORMAT_GREY; break; + case VIDEO_PALETTE_HI240 : return PIXELFORMAT_RGB332; break; + case VIDEO_PALETTE_RGB555 : return PIXELFORMAT_RGB555; break; + case VIDEO_PALETTE_RGB565 : return PIXELFORMAT_RGB565; break; + case VIDEO_PALETTE_RGB24 : return PIXELFORMAT_RGB24; break; + case VIDEO_PALETTE_RGB32 : return PIXELFORMAT_RGB32; break; + case VIDEO_PALETTE_YUYV : return PIXELFORMAT_YUYV; break; + case VIDEO_PALETTE_UYVY : return PIXELFORMAT_UYVY; break; + case VIDEO_PALETTE_YUV420 : + case VIDEO_PALETTE_YUV420P : return PIXELFORMAT_YUV420P; break; + case VIDEO_PALETTE_YUV422P : return PIXELFORMAT_YUV422P; break; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + return PIXELFORMAT_NONE; break; + } + return PIXELFORMAT_NONE; +} + +int VideoDevice::pixelFormatCode(pixel_format pixelformat) +{ + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + switch(pixelformat) + { + case PIXELFORMAT_NONE : return 0; break; + +// Packed RGB formats + case PIXELFORMAT_RGB332 : return V4L2_PIX_FMT_RGB332; break; +#if defined( V4L2_PIX_FMT_RGB444 ) + case PIXELFORMAT_RGB444 : return V4L2_PIX_FMT_RGB444; break; +#endif + case PIXELFORMAT_RGB555 : return V4L2_PIX_FMT_RGB555; break; + case PIXELFORMAT_RGB565 : return V4L2_PIX_FMT_RGB565; break; + case PIXELFORMAT_RGB555X: return V4L2_PIX_FMT_RGB555X; break; + case PIXELFORMAT_RGB565X: return V4L2_PIX_FMT_RGB565X; break; + case PIXELFORMAT_BGR24 : return V4L2_PIX_FMT_BGR24; break; + case PIXELFORMAT_RGB24 : return V4L2_PIX_FMT_RGB24; break; + case PIXELFORMAT_BGR32 : return V4L2_PIX_FMT_BGR32; break; + case PIXELFORMAT_RGB32 : return V4L2_PIX_FMT_RGB32; break; + +// Bayer RGB format + case PIXELFORMAT_SBGGR8 : return V4L2_PIX_FMT_SBGGR8; break; + +// YUV formats + case PIXELFORMAT_GREY : return V4L2_PIX_FMT_GREY; break; + case PIXELFORMAT_YUYV : return V4L2_PIX_FMT_YUYV; break; + case PIXELFORMAT_UYVY : return V4L2_PIX_FMT_UYVY; break; + case PIXELFORMAT_YUV420P: return V4L2_PIX_FMT_YUV420; break; + case PIXELFORMAT_YUV422P: return V4L2_PIX_FMT_YUV422P; break; + +// Compressed formats + case PIXELFORMAT_JPEG : return V4L2_PIX_FMT_JPEG; break; + case PIXELFORMAT_MPEG : return V4L2_PIX_FMT_MPEG; break; + +// Reserved formats + case PIXELFORMAT_DV : return V4L2_PIX_FMT_DV; break; + case PIXELFORMAT_ET61X251:return V4L2_PIX_FMT_ET61X251;break; + case PIXELFORMAT_HI240 : return V4L2_PIX_FMT_HI240; break; +#if defined( V4L2_PIX_FMT_HM12 ) + case PIXELFORMAT_HM12 : return V4L2_PIX_FMT_HM12; break; +#endif + case PIXELFORMAT_MJPEG : return V4L2_PIX_FMT_MJPEG; break; + case PIXELFORMAT_PWC1 : return V4L2_PIX_FMT_PWC1; break; + case PIXELFORMAT_PWC2 : return V4L2_PIX_FMT_PWC2; break; + case PIXELFORMAT_SN9C10X: return V4L2_PIX_FMT_SN9C10X; break; + case PIXELFORMAT_WNVA : return V4L2_PIX_FMT_WNVA; break; + case PIXELFORMAT_YYUV : return V4L2_PIX_FMT_YYUV; break; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + switch(pixelformat) + { + case PIXELFORMAT_NONE : return 0; break; + +// Packed RGB formats + case PIXELFORMAT_RGB332 : return VIDEO_PALETTE_HI240; break; + case PIXELFORMAT_RGB444 : return 0; break; + case PIXELFORMAT_RGB555 : return VIDEO_PALETTE_RGB555; break; + case PIXELFORMAT_RGB565 : return VIDEO_PALETTE_RGB565; break; + case PIXELFORMAT_RGB555X: return 0; break; + case PIXELFORMAT_RGB565X: return 0; break; + case PIXELFORMAT_BGR24 : return 0; break; + case PIXELFORMAT_RGB24 : return VIDEO_PALETTE_RGB24; break; + case PIXELFORMAT_BGR32 : return 0; break; + case PIXELFORMAT_RGB32 : return VIDEO_PALETTE_RGB32; break; + +// Bayer RGB format + case PIXELFORMAT_SBGGR8 : return 0; break; + +// YUV formats + case PIXELFORMAT_GREY : return VIDEO_PALETTE_GREY; break; + case PIXELFORMAT_YUYV : return VIDEO_PALETTE_YUYV; break; + case PIXELFORMAT_UYVY : return VIDEO_PALETTE_UYVY; break; + case PIXELFORMAT_YUV420P: return VIDEO_PALETTE_YUV420; break; + case PIXELFORMAT_YUV422P: return VIDEO_PALETTE_YUV422P; break; + +// Compressed formats + case PIXELFORMAT_JPEG : return 0; break; + case PIXELFORMAT_MPEG : return 0; break; + +// Reserved formats + case PIXELFORMAT_DV : return 0; break; + case PIXELFORMAT_ET61X251:return 0; break; + case PIXELFORMAT_HI240 : return VIDEO_PALETTE_HI240; break; + case PIXELFORMAT_HM12 : return 0; break; + case PIXELFORMAT_MJPEG : return 0; break; + case PIXELFORMAT_PWC1 : return 0; break; + case PIXELFORMAT_PWC2 : return 0; break; + case PIXELFORMAT_SN9C10X: return 0; break; + case PIXELFORMAT_WNVA : return 0; break; + case PIXELFORMAT_YYUV : return 0; break; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + return PIXELFORMAT_NONE; break; + } + return PIXELFORMAT_NONE; +} + +int VideoDevice::pixelFormatDepth(pixel_format pixelformat) +{ + switch(pixelformat) + { + case PIXELFORMAT_NONE : return 0; break; + +// Packed RGB formats + case PIXELFORMAT_RGB332 : return 8; break; + case PIXELFORMAT_RGB444 : return 16; break; + case PIXELFORMAT_RGB555 : return 16; break; + case PIXELFORMAT_RGB565 : return 16; break; + case PIXELFORMAT_RGB555X: return 16; break; + case PIXELFORMAT_RGB565X: return 16; break; + case PIXELFORMAT_BGR24 : return 24; break; + case PIXELFORMAT_RGB24 : return 24; break; + case PIXELFORMAT_BGR32 : return 32; break; + case PIXELFORMAT_RGB32 : return 32; break; + +// Bayer RGB format + case PIXELFORMAT_SBGGR8 : return 0; break; + +// YUV formats + case PIXELFORMAT_GREY : return 8; break; + case PIXELFORMAT_YUYV : return 16; break; + case PIXELFORMAT_UYVY : return 16; break; + case PIXELFORMAT_YUV420P: return 16; break; + case PIXELFORMAT_YUV422P: return 16; break; + +// Compressed formats + case PIXELFORMAT_JPEG : return 0; break; + case PIXELFORMAT_MPEG : return 0; break; + +// Reserved formats + case PIXELFORMAT_DV : return 0; break; + case PIXELFORMAT_ET61X251:return 0; break; + case PIXELFORMAT_HI240 : return 8; break; + case PIXELFORMAT_HM12 : return 0; break; + case PIXELFORMAT_MJPEG : return 0; break; + case PIXELFORMAT_PWC1 : return 0; break; + case PIXELFORMAT_PWC2 : return 0; break; + case PIXELFORMAT_SN9C10X: return 0; break; + case PIXELFORMAT_WNVA : return 0; break; + case PIXELFORMAT_YYUV : return 0; break; + } + return 0; +} + +QString VideoDevice::pixelFormatName(pixel_format pixelformat) +{ + QString returnvalue; + returnvalue = "None"; + switch(pixelformat) + { + case PIXELFORMAT_NONE : returnvalue = "None"; break; + +// Packed RGB formats + case PIXELFORMAT_RGB332 : returnvalue = "8-bit RGB332"; break; + case PIXELFORMAT_RGB444 : returnvalue = "8-bit RGB444"; break; + case PIXELFORMAT_RGB555 : returnvalue = "16-bit RGB555"; break; + case PIXELFORMAT_RGB565 : returnvalue = "16-bit RGB565"; break; + case PIXELFORMAT_RGB555X: returnvalue = "16-bit RGB555X"; break; + case PIXELFORMAT_RGB565X: returnvalue = "16-bit RGB565X"; break; + case PIXELFORMAT_BGR24 : returnvalue = "24-bit BGR24"; break; + case PIXELFORMAT_RGB24 : returnvalue = "24-bit RGB24"; break; + case PIXELFORMAT_BGR32 : returnvalue = "32-bit BGR32"; break; + case PIXELFORMAT_RGB32 : returnvalue = "32-bit RGB32"; break; + +// Bayer RGB format + case PIXELFORMAT_SBGGR8 : returnvalue = "Bayer RGB format"; break; + +// YUV formats + case PIXELFORMAT_GREY : returnvalue = "8-bit Grayscale"; break; + case PIXELFORMAT_YUYV : returnvalue = "Packed YUV 4:2:2"; break; + case PIXELFORMAT_UYVY : returnvalue = "Packed YVU 4:2:2"; break; + case PIXELFORMAT_YUV420P: returnvalue = "Planar YUV 4:2:0"; break; + case PIXELFORMAT_YUV422P: returnvalue = "Planar YUV 4:2:2"; break; + + +// Compressed formats + case PIXELFORMAT_JPEG : returnvalue = "JPEG image"; break; + case PIXELFORMAT_MPEG : returnvalue = "MPEG stream"; break; + +// Reserved formats + case PIXELFORMAT_DV : returnvalue = "DV (unknown)"; break; + case PIXELFORMAT_ET61X251:returnvalue = "ET61X251"; break; + case PIXELFORMAT_HI240 : returnvalue = "8-bit HI240 (RGB332)"; break; + case PIXELFORMAT_HM12 : returnvalue = "Packed YUV 4:2:2"; break; + case PIXELFORMAT_MJPEG : returnvalue = "8-bit Grayscale"; break; + case PIXELFORMAT_PWC1 : returnvalue = "PWC1"; break; + case PIXELFORMAT_PWC2 : returnvalue = "PWC2"; break; + case PIXELFORMAT_SN9C10X: returnvalue = "SN9C102"; break; + case PIXELFORMAT_WNVA : returnvalue = "Winnov Videum"; break; + case PIXELFORMAT_YYUV : returnvalue = "YYUV (unknown)"; break; + } + return returnvalue; +} + +QString VideoDevice::pixelFormatName(int pixelformat) +{ + QString returnvalue; + returnvalue = "None"; + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + switch(pixelformat) + { + case 0 : returnvalue = pixelFormatName(PIXELFORMAT_NONE); break; + +// Packed RGB formats + case V4L2_PIX_FMT_RGB332 : returnvalue = pixelFormatName(PIXELFORMAT_RGB332); break; +#if defined( V4L2_PIX_FMT_RGB444 ) + case V4L2_PIX_FMT_RGB444 : returnvalue = pixelFormatName(PIXELFORMAT_RGB444); break; +#endif + case V4L2_PIX_FMT_RGB555 : returnvalue = pixelFormatName(PIXELFORMAT_RGB555); break; + case V4L2_PIX_FMT_RGB565 : returnvalue = pixelFormatName(PIXELFORMAT_RGB565); break; + case V4L2_PIX_FMT_RGB555X : returnvalue = pixelFormatName(PIXELFORMAT_RGB555X); break; + case V4L2_PIX_FMT_RGB565X : returnvalue = pixelFormatName(PIXELFORMAT_RGB565X); break; + case V4L2_PIX_FMT_BGR24 : returnvalue = pixelFormatName(PIXELFORMAT_BGR24); break; + case V4L2_PIX_FMT_RGB24 : returnvalue = pixelFormatName(PIXELFORMAT_RGB24); break; + case V4L2_PIX_FMT_BGR32 : returnvalue = pixelFormatName(PIXELFORMAT_BGR32); break; + case V4L2_PIX_FMT_RGB32 : returnvalue = pixelFormatName(PIXELFORMAT_RGB32); break; + +// Bayer RGB format + case V4L2_PIX_FMT_SBGGR8 : returnvalue = pixelFormatName(PIXELFORMAT_SBGGR8); break; + +// YUV formats + case V4L2_PIX_FMT_GREY : returnvalue = pixelFormatName(PIXELFORMAT_GREY); break; + case V4L2_PIX_FMT_YUYV : returnvalue = pixelFormatName(PIXELFORMAT_YUYV); break; + case V4L2_PIX_FMT_UYVY : returnvalue = pixelFormatName(PIXELFORMAT_UYVY); break; + case V4L2_PIX_FMT_YUV420 : returnvalue = pixelFormatName(PIXELFORMAT_YUV420P); break; + case V4L2_PIX_FMT_YUV422P : returnvalue = pixelFormatName(PIXELFORMAT_YUV422P); break; + +// Compressed formats + case V4L2_PIX_FMT_JPEG : returnvalue = pixelFormatName(PIXELFORMAT_JPEG); break; + case V4L2_PIX_FMT_MPEG : returnvalue = pixelFormatName(PIXELFORMAT_MPEG); break; + +// Reserved formats + case V4L2_PIX_FMT_DV : returnvalue = pixelFormatName(PIXELFORMAT_DV); break; + case V4L2_PIX_FMT_ET61X251 : returnvalue = pixelFormatName(PIXELFORMAT_ET61X251); break; + case V4L2_PIX_FMT_HI240 : returnvalue = pixelFormatName(PIXELFORMAT_HI240); break; +#if defined( V4L2_PIX_FMT_HM12 ) + case V4L2_PIX_FMT_HM12 : returnvalue = pixelFormatName(PIXELFORMAT_HM12); break; +#endif + case V4L2_PIX_FMT_MJPEG : returnvalue = pixelFormatName(PIXELFORMAT_MJPEG); break; + case V4L2_PIX_FMT_PWC1 : returnvalue = pixelFormatName(PIXELFORMAT_PWC1); break; + case V4L2_PIX_FMT_PWC2 : returnvalue = pixelFormatName(PIXELFORMAT_PWC2); break; + case V4L2_PIX_FMT_SN9C10X : returnvalue = pixelFormatName(PIXELFORMAT_SN9C10X); break; + case V4L2_PIX_FMT_WNVA : returnvalue = pixelFormatName(PIXELFORMAT_WNVA); break; + case V4L2_PIX_FMT_YYUV : returnvalue = pixelFormatName(PIXELFORMAT_YYUV); break; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + switch(pixelformat) + { + case VIDEO_PALETTE_GREY : returnvalue = pixelFormatName(PIXELFORMAT_GREY); break; + case VIDEO_PALETTE_HI240 : returnvalue = pixelFormatName(PIXELFORMAT_RGB332); break; + case VIDEO_PALETTE_RGB555 : returnvalue = pixelFormatName(PIXELFORMAT_RGB555); break; + case VIDEO_PALETTE_RGB565 : returnvalue = pixelFormatName(PIXELFORMAT_RGB565); break; + case VIDEO_PALETTE_RGB24 : returnvalue = pixelFormatName(PIXELFORMAT_RGB24); break; + case VIDEO_PALETTE_RGB32 : returnvalue = pixelFormatName(PIXELFORMAT_RGB32); break; + case VIDEO_PALETTE_YUYV : returnvalue = pixelFormatName(PIXELFORMAT_YUYV); break; + case VIDEO_PALETTE_UYVY : returnvalue = pixelFormatName(PIXELFORMAT_UYVY); break; + case VIDEO_PALETTE_YUV420 : + case VIDEO_PALETTE_YUV420P : returnvalue = pixelFormatName(PIXELFORMAT_YUV420P); break; + case VIDEO_PALETTE_YUV422P : returnvalue = pixelFormatName(PIXELFORMAT_YUV422P); break; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return returnvalue; +} + +int VideoDevice::detectPixelFormats() +{ + int err = 0; + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + fmtdesc.index = 0; + fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + while ( err == 0 ) + { + if (-1 == xioctl (VIDIOC_ENUM_FMT, &fmtdesc)) +// if (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) < 0 ) + { + perror("VIDIOC_ENUM_FMT"); + err = errno; + } + else + { + kdDebug(14010) << k_funcinfo << fmtdesc.pixelformat << " " << pixelFormatName(fmtdesc.pixelformat) << endl; // Need a cleanup. PixelFormatForPalette is a really bad name + fmtdesc.index++; + } + } +// break; +#endif + case VIDEODEV_DRIVER_V4L: +// TODO: THis thing can be used to detec what pixel formats are supported in a API-independent way, but V4L2 has VIDIOC_ENUM_PIXFMT. +// The correct thing to do is to isolate these calls and do a proper implementation for V4L and another for V4L2 when this thing will be migrated to a plugin architecture. + +// Packed RGB formats + kdDebug(14010) << k_funcinfo << "Supported pixel formats:" << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB332)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB332) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB444)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB444) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB555)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB555) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB565)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB565) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB555X)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB555X) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB565X)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB565X) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_BGR24)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_BGR24) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB24)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB24) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_BGR32)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_BGR32) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_RGB32)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_RGB32) << endl; + +// Bayer RGB format + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_SBGGR8)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_SBGGR8) << endl; + +// YUV formats + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_GREY)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_GREY) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_YUYV)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_YUYV) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_UYVY)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_UYVY) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_YUV420P)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_YUV420P) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_YUV422P)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_YUV422P) << endl; + +// Compressed formats + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_JPEG)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_JPEG) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_MPEG)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_MPEG) << endl; + +// Reserved formats + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_DV)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_DV) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_ET61X251)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_ET61X251) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_HI240)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_HI240) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_HM12)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_HM12) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_MJPEG)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_MJPEG) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_PWC1)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_PWC1) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_PWC2)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_PWC2) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_SN9C10X)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_SN9C10X) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_WNVA)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_WNVA) << endl; + if(PIXELFORMAT_NONE != setPixelFormat(PIXELFORMAT_YYUV)) kdDebug(14010) << k_funcinfo << pixelFormatName(PIXELFORMAT_YYUV) << endl; + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + return PIXELFORMAT_NONE; break; + } + return PIXELFORMAT_NONE; +} + +__u64 VideoDevice::signalStandardCode(signal_standard standard) +{ + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + switch(standard) + { + case STANDARD_NONE : return V4L2_STD_UNKNOWN; break; + case STANDARD_PAL_B : return V4L2_STD_PAL_B; break; + case STANDARD_PAL_B1 : return V4L2_STD_PAL_B1; break; + case STANDARD_PAL_G : return V4L2_STD_PAL_G; break; + case STANDARD_PAL_H : return V4L2_STD_PAL_H; break; + case STANDARD_PAL_I : return V4L2_STD_PAL_I; break; + case STANDARD_PAL_D : return V4L2_STD_PAL_D; break; + case STANDARD_PAL_D1 : return V4L2_STD_PAL_D1; break; + case STANDARD_PAL_K : return V4L2_STD_PAL_K; break; + case STANDARD_PAL_M : return V4L2_STD_PAL_M; break; + case STANDARD_PAL_N : return V4L2_STD_PAL_N; break; + case STANDARD_PAL_Nc : return V4L2_STD_PAL_Nc; break; + case STANDARD_PAL_60 : return V4L2_STD_PAL_60; break; + case STANDARD_NTSC_M : return V4L2_STD_NTSC_M; break; + case STANDARD_NTSC_M_JP : return V4L2_STD_NTSC_M_JP; break; + case STANDARD_NTSC_443 : return V4L2_STD_NTSC; break; // Using workaround value because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case STANDARD_SECAM_B : return V4L2_STD_SECAM_B; break; + case STANDARD_SECAM_D : return V4L2_STD_SECAM_D; break; + case STANDARD_SECAM_G : return V4L2_STD_SECAM_G; break; + case STANDARD_SECAM_H : return V4L2_STD_SECAM_H; break; + case STANDARD_SECAM_K : return V4L2_STD_SECAM_K; break; + case STANDARD_SECAM_K1 : return V4L2_STD_SECAM_K1; break; + case STANDARD_SECAM_L : return V4L2_STD_SECAM_L; break; + case STANDARD_SECAM_LC : return V4L2_STD_SECAM; break; // Using workaround value because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case STANDARD_ATSC_8_VSB : return V4L2_STD_ATSC_8_VSB; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_ATSC_16_VSB : return V4L2_STD_ATSC_16_VSB; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_PAL_BG : return V4L2_STD_PAL_BG; break; + case STANDARD_PAL_DK : return V4L2_STD_PAL_DK; break; + case STANDARD_PAL : return V4L2_STD_PAL; break; + case STANDARD_NTSC : return V4L2_STD_NTSC; break; + case STANDARD_SECAM_DK : return V4L2_STD_SECAM_DK; break; + case STANDARD_SECAM : return V4L2_STD_SECAM; break; + case STANDARD_525_60 : return V4L2_STD_525_60; break; + case STANDARD_625_50 : return V4L2_STD_625_50; break; + case STANDARD_ALL : return V4L2_STD_ALL; break; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + switch(standard) + { + case STANDARD_NONE : return VIDEO_MODE_AUTO; break; + case STANDARD_PAL_B : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_B1 : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_G : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_H : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_I : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_D : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_D1 : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_K : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_M : return 5; break; // Undocumented value found to be compatible with V4L bttv driver + case STANDARD_PAL_N : return 6; break; // Undocumented value found to be compatible with V4L bttv driver + case STANDARD_PAL_Nc : return 4; break; // Undocumented value found to be compatible with V4L bttv driver + case STANDARD_PAL_60 : return VIDEO_MODE_PAL; break; + case STANDARD_NTSC_M : return VIDEO_MODE_NTSC; break; + case STANDARD_NTSC_M_JP : return 7; break; // Undocumented value found to be compatible with V4L bttv driver + case STANDARD_NTSC_443 : return VIDEO_MODE_NTSC; break; // Using workaround value because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case STANDARD_SECAM_B : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_D : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_G : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_H : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_K : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_K1 : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_L : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM_LC : return VIDEO_MODE_SECAM; break; // Using workaround value because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case STANDARD_ATSC_8_VSB : return VIDEO_MODE_AUTO; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_ATSC_16_VSB : return VIDEO_MODE_AUTO; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_PAL_BG : return VIDEO_MODE_PAL; break; + case STANDARD_PAL_DK : return VIDEO_MODE_PAL; break; + case STANDARD_PAL : return VIDEO_MODE_PAL; break; + case STANDARD_NTSC : return VIDEO_MODE_NTSC; break; + case STANDARD_SECAM_DK : return VIDEO_MODE_SECAM; break; + case STANDARD_SECAM : return VIDEO_MODE_SECAM; break; + case STANDARD_525_60 : return VIDEO_MODE_PAL; break; + case STANDARD_625_50 : return VIDEO_MODE_SECAM; break; + case STANDARD_ALL : return VIDEO_MODE_AUTO; break; + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + return STANDARD_NONE; break; + } + return STANDARD_NONE; +} + +QString VideoDevice::signalStandardName(signal_standard standard) +{ + QString returnvalue; + returnvalue = "None"; + switch(standard) + { + case STANDARD_NONE : returnvalue = "None"; break; + case STANDARD_PAL_B : returnvalue = "PAL-B"; break; + case STANDARD_PAL_B1 : returnvalue = "PAL-B1"; break; + case STANDARD_PAL_G : returnvalue = "PAL-G"; break; + case STANDARD_PAL_H : returnvalue = "PAL-H"; break; + case STANDARD_PAL_I : returnvalue = "PAL-I"; break; + case STANDARD_PAL_D : returnvalue = "PAL-D"; break; + case STANDARD_PAL_D1 : returnvalue = "PAL-D1"; break; + case STANDARD_PAL_K : returnvalue = "PAL-K"; break; + case STANDARD_PAL_M : returnvalue = "PAL-M"; break; + case STANDARD_PAL_N : returnvalue = "PAL-N"; break; + case STANDARD_PAL_Nc : returnvalue = "PAL-Nc"; break; + case STANDARD_PAL_60 : returnvalue = "PAL-60"; break; + case STANDARD_NTSC_M : returnvalue = "NTSC-M"; break; + case STANDARD_NTSC_M_JP : returnvalue = "NTSC-M(JP)"; break; + case STANDARD_NTSC_443 : returnvalue = "NTSC-443"; break; + case STANDARD_SECAM_B : returnvalue = "SECAM-B"; break; + case STANDARD_SECAM_D : returnvalue = "SECAM-D"; break; + case STANDARD_SECAM_G : returnvalue = "SECAM-G"; break; + case STANDARD_SECAM_H : returnvalue = "SECAM-H"; break; + case STANDARD_SECAM_K : returnvalue = "SECAM-K"; break; + case STANDARD_SECAM_K1 : returnvalue = "SECAM-K1"; break; + case STANDARD_SECAM_L : returnvalue = "SECAM-L"; break; + case STANDARD_SECAM_LC : returnvalue = "SECAM-LC"; break; + case STANDARD_ATSC_8_VSB : returnvalue = "ATSC-8-VSB"; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_ATSC_16_VSB : returnvalue = "ATSC-16-VSB"; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_PAL_BG : returnvalue = "PAL-BG"; break; + case STANDARD_PAL_DK : returnvalue = "PAL-DK"; break; + case STANDARD_PAL : returnvalue = "PAL"; break; + case STANDARD_NTSC : returnvalue = "NTSC"; break; + case STANDARD_SECAM_DK : returnvalue = "SECAM-DK"; break; + case STANDARD_SECAM : returnvalue = "SECAM"; break; + case STANDARD_525_60 : returnvalue = "525 lines 60Hz"; break; + case STANDARD_625_50 : returnvalue = "625 lines 50Hz"; break; + case STANDARD_ALL : returnvalue = "All"; break; + } + return returnvalue; +} + +QString VideoDevice::signalStandardName(int standard) +{ + QString returnvalue; + returnvalue = "None"; + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + switch(standard) + { + case V4L2_STD_PAL_B : returnvalue = signalStandardName(STANDARD_PAL_B); break; + case V4L2_STD_PAL_B1 : returnvalue = signalStandardName(STANDARD_PAL_B1); break; + case V4L2_STD_PAL_G : returnvalue = signalStandardName(STANDARD_PAL_G); break; + case V4L2_STD_PAL_H : returnvalue = signalStandardName(STANDARD_PAL_H); break; + case V4L2_STD_PAL_I : returnvalue = signalStandardName(STANDARD_PAL_I); break; + case V4L2_STD_PAL_D : returnvalue = signalStandardName(STANDARD_PAL_D); break; + case V4L2_STD_PAL_D1 : returnvalue = signalStandardName(STANDARD_PAL_D1); break; + case V4L2_STD_PAL_K : returnvalue = signalStandardName(STANDARD_PAL_K); break; + case V4L2_STD_PAL_M : returnvalue = signalStandardName(STANDARD_PAL_M); break; + case V4L2_STD_PAL_N : returnvalue = signalStandardName(STANDARD_PAL_N); break; + case V4L2_STD_PAL_Nc : returnvalue = signalStandardName(STANDARD_PAL_Nc); break; + case V4L2_STD_PAL_60 : returnvalue = signalStandardName(STANDARD_PAL_60); break; + case V4L2_STD_NTSC_M : returnvalue = signalStandardName(STANDARD_NTSC_M); break; + case V4L2_STD_NTSC_M_JP : returnvalue = signalStandardName(STANDARD_NTSC_M_JP); break; +// case V4L2_STD_NTSC_443 : returnvalue = signalStandardName(STANDARD_NTSC_443); break; // Commented out because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case V4L2_STD_SECAM_B : returnvalue = signalStandardName(STANDARD_SECAM_B); break; + case V4L2_STD_SECAM_D : returnvalue = signalStandardName(STANDARD_SECAM_D); break; + case V4L2_STD_SECAM_G : returnvalue = signalStandardName(STANDARD_SECAM_G); break; + case V4L2_STD_SECAM_H : returnvalue = signalStandardName(STANDARD_SECAM_H); break; + case V4L2_STD_SECAM_K : returnvalue = signalStandardName(STANDARD_SECAM_K); break; + case V4L2_STD_SECAM_K1 : returnvalue = signalStandardName(STANDARD_SECAM_K1); break; + case V4L2_STD_SECAM_L : returnvalue = signalStandardName(STANDARD_SECAM_L); break; +// case V4L2_STD_SECAM_LC : returnvalue = signalStandardName(STANDARD_SECAM_LC); break; // Commented out because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case V4L2_STD_ATSC_8_VSB : returnvalue = signalStandardName(STANDARD_ATSC_8_VSB); break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case V4L2_STD_ATSC_16_VSB : returnvalue = signalStandardName(STANDARD_ATSC_16_VSB); break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case V4L2_STD_PAL_BG : returnvalue = signalStandardName(STANDARD_PAL_BG); break; + case V4L2_STD_PAL_DK : returnvalue = signalStandardName(STANDARD_PAL_DK); break; + case V4L2_STD_PAL : returnvalue = signalStandardName(STANDARD_PAL); break; + case V4L2_STD_NTSC : returnvalue = signalStandardName(STANDARD_NTSC); break; + case V4L2_STD_SECAM_DK : returnvalue = signalStandardName(STANDARD_SECAM_DK); break; + case V4L2_STD_SECAM : returnvalue = signalStandardName(STANDARD_SECAM); break; + case V4L2_STD_525_60 : returnvalue = signalStandardName(STANDARD_525_60); break; + case V4L2_STD_625_50 : returnvalue = signalStandardName(STANDARD_625_50); break; + case V4L2_STD_ALL : returnvalue = signalStandardName(STANDARD_ALL); break; + } + break; +#endif + case VIDEODEV_DRIVER_V4L: + switch(standard) + { + case VIDEO_MODE_PAL : returnvalue = signalStandardName(STANDARD_PAL); break; + case VIDEO_MODE_NTSC : returnvalue = signalStandardName(STANDARD_NTSC); break; + case VIDEO_MODE_SECAM : returnvalue = signalStandardName(STANDARD_SECAM); break; + case VIDEO_MODE_AUTO : returnvalue = signalStandardName(STANDARD_ALL); break; // It must be disabled until I find a correct way to handle those non-standard bttv modes +// case VIDEO_MODE_PAL_Nc : returnvalue = signalStandardName(STANDARD_PAL_Nc); break; // Undocumented value found to be compatible with V4L bttv driver + case VIDEO_MODE_PAL_M : returnvalue = signalStandardName(STANDARD_PAL_M); break; // Undocumented value found to be compatible with V4L bttv driver + case VIDEO_MODE_PAL_N : returnvalue = signalStandardName(STANDARD_PAL_N); break; // Undocumented value found to be compatible with V4L bttv driver + case VIDEO_MODE_NTSC_JP : returnvalue = signalStandardName(STANDARD_NTSC_M_JP); break; // Undocumented value found to be compatible with V4L bttv driver + } + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + return returnvalue; +} + +/*! + \fn VideoDevice::detectSignalStandards() + */ +int VideoDevice::detectSignalStandards() +{ + switch(m_driver) + { +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + case VIDEODEV_DRIVER_V4L2: + break; +#endif + case VIDEODEV_DRIVER_V4L: + break; +#endif + case VIDEODEV_DRIVER_NONE: + default: + break; + } + //FIXME: return a real value + return 0; +} + +/*! + \fn VideoDevice::initRead() + */ +int VideoDevice::initRead() +{ + /// @todo implement me + + kdDebug(14010) << k_funcinfo << "called." << endl; + if(isOpen()) + { + m_rawbuffers.resize(1); + if (m_rawbuffers.size()==0) + { + fprintf (stderr, "Out of memory\n"); + return EXIT_FAILURE; + } + kdDebug(14010) << k_funcinfo << "m_buffer_size: " << m_buffer_size << endl; + +// m_rawbuffers[0].pixelformat=m_pixelformat; + m_rawbuffers[0].length = m_buffer_size; + m_rawbuffers[0].start = (uchar *)malloc (m_buffer_size); + + if (!m_rawbuffers[0].start) + { + fprintf (stderr, "Out of memory\n"); + return EXIT_FAILURE; + } + kdDebug(14010) << k_funcinfo << "exited successfuly." << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + + +/*! + \fn VideoDevice::initMmap() + */ +int VideoDevice::initMmap() +{ + /// @todo implement me +#define BUFFERS 2 + if(isOpen()) + { + kdDebug(14010) << k_funcinfo << full_filename << " Trying to MMAP" << endl; +#ifdef V4L2_CAP_VIDEO_CAPTURE + struct v4l2_requestbuffers req; + + CLEAR (req); + + req.count = BUFFERS; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl (VIDIOC_REQBUFS, &req)) + { + if (EINVAL == errno) + { + kdDebug(14010) << k_funcinfo << full_filename << " does not support memory mapping" << endl; + return EXIT_FAILURE; + } + else + { + return errnoReturn ("VIDIOC_REQBUFS"); + } + } + + if (req.count < BUFFERS) + { + kdDebug(14010) << k_funcinfo << "Insufficient buffer memory on " << full_filename << endl; + return EXIT_FAILURE; + } + + m_rawbuffers.resize(req.count); + + if (m_rawbuffers.size()==0) + { + kdDebug(14010) << k_funcinfo << "Out of memory" << endl; + return EXIT_FAILURE; + } + + for (m_streambuffers = 0; m_streambuffers < req.count; ++m_streambuffers) + { + struct v4l2_buffer v4l2buffer; + + CLEAR (v4l2buffer); + + v4l2buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2buffer.memory = V4L2_MEMORY_MMAP; + v4l2buffer.index = m_streambuffers; + + if (-1 == xioctl (VIDIOC_QUERYBUF, &v4l2buffer)) + return errnoReturn ("VIDIOC_QUERYBUF"); + + m_rawbuffers[m_streambuffers].length = v4l2buffer.length; + m_rawbuffers[m_streambuffers].start = (uchar *) mmap (NULL /* start anywhere */, v4l2buffer.length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, descriptor, v4l2buffer.m.offset); + + if (MAP_FAILED == m_rawbuffers[m_streambuffers].start) + return errnoReturn ("mmap"); + } +#endif + m_currentbuffer.data.resize(m_rawbuffers[0].length); // Makes the imagesize.data buffer size equal to the rawbuffer size + kdDebug(14010) << k_funcinfo << full_filename << " m_currentbuffer.data.size(): " << m_currentbuffer.data.size() << endl; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + + +/*! + \fn VideoDevice::initUserptr() + */ +int VideoDevice::initUserptr() +{ + /// @todo implement me + if(isOpen()) + { +#ifdef V4L2_CAP_VIDEO_CAPTURE + struct v4l2_requestbuffers req; + + CLEAR (req); + + req.count = 2; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_USERPTR; + + if (-1 == xioctl (VIDIOC_REQBUFS, &req)) + { + if (EINVAL == errno) + { + kdDebug(14010) << k_funcinfo << full_filename << " does not support memory mapping" << endl; + return EXIT_FAILURE; + } + else + { + return errnoReturn ("VIDIOC_REQBUFS"); + } + } + + m_rawbuffers.resize(4); + + if (m_rawbuffers.size()==0) + { + fprintf (stderr, "Out of memory\n"); + return EXIT_FAILURE; + } + + for (m_streambuffers = 0; m_streambuffers < 4; ++m_streambuffers) + { + m_rawbuffers[m_streambuffers].length = m_buffer_size; + m_rawbuffers[m_streambuffers].start = (uchar *) malloc (m_buffer_size); + + if (!m_rawbuffers[m_streambuffers].start) + { + kdDebug(14010) << k_funcinfo << "Out of memory" << endl; + return EXIT_FAILURE; + } + } +#endif + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +bool VideoDevice::canCapture() +{ + return m_videocapture; +} + +bool VideoDevice::canChromakey() +{ + return m_videochromakey; +} + +bool VideoDevice::canScale() +{ + return m_videoscale; +} + +bool VideoDevice::canOverlay() +{ + return m_videooverlay; +} + +bool VideoDevice::canRead() +{ + return m_videoread; +} + +bool VideoDevice::canAsyncIO() +{ + return m_videoasyncio; +} + +bool VideoDevice::canStream() +{ + return m_videostream; +} + + + +} + +} diff --git a/kopete/libkopete/avdevice/videodevice.h b/kopete/libkopete/avdevice/videodevice.h new file mode 100644 index 00000000..982ab5f3 --- /dev/null +++ b/kopete/libkopete/avdevice/videodevice.h @@ -0,0 +1,333 @@ +/* + videodevice.cpp - Kopete Video Device Low-level Support + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#define ENABLE_AV + +#ifndef KOPETE_AVVIDEODEVICELISTITEM_H +#define KOPETE_AVVIDEODEVICELISTITEM_H + +#if defined HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) && defined(ENABLE_AV) + +#include +#undef __STRICT_ANSI__ +#ifndef __u64 //required by videodev.h +#define __u64 unsigned long long +#endif // __u64 + +#ifndef __s64 //required by videodev.h +#define __s64 long long +#endif // __s64 + + +#ifndef pgoff_t +#define pgoff_t unsigned long +#endif + +#include +#include +#include +#define VIDEO_MODE_PAL_Nc 3 +#define VIDEO_MODE_PAL_M 4 +#define VIDEO_MODE_PAL_N 5 +#define VIDEO_MODE_NTSC_JP 6 +#define __STRICT_ANSI__ + +#endif // __linux__ + +#include +#include +#include +#include +#include + +#include "videoinput.h" +#include "videocontrol.h" + +namespace Kopete { + +namespace AV { + +/** +@author Kopete Developers +*/ +typedef enum +{ + VIDEODEV_DRIVER_NONE +#if defined( __linux__) && defined(ENABLE_AV) + , + VIDEODEV_DRIVER_V4L +#ifdef V4L2_CAP_VIDEO_CAPTURE + , + VIDEODEV_DRIVER_V4L2 +#endif +#endif +} videodev_driver; + +typedef enum +{ +// Packed RGB formats + PIXELFORMAT_NONE = 0, + PIXELFORMAT_GREY = (1 << 0), + PIXELFORMAT_RGB332 = (1 << 1), + PIXELFORMAT_RGB444 = (1 << 2), + PIXELFORMAT_RGB555 = (1 << 3), + PIXELFORMAT_RGB565 = (1 << 4), + PIXELFORMAT_RGB555X = (1 << 5), + PIXELFORMAT_RGB565X = (1 << 6), + PIXELFORMAT_BGR24 = (1 << 7), + PIXELFORMAT_RGB24 = (1 << 8), + PIXELFORMAT_BGR32 = (1 << 9), + PIXELFORMAT_RGB32 = (1 << 10), + +// Bayer RGB format + PIXELFORMAT_SBGGR8 = (1 << 11), + +// YUV formats + PIXELFORMAT_YUYV = (1 << 12), + PIXELFORMAT_UYVY = (1 << 13), + PIXELFORMAT_YUV420P = (1 << 14), + PIXELFORMAT_YUV422P = (1 << 15), + +// Compressed formats + PIXELFORMAT_JPEG = (1 << 16), + PIXELFORMAT_MPEG = (1 << 17), + +// Reserved formats + PIXELFORMAT_DV = (1 << 18), + PIXELFORMAT_ET61X251 = (1 << 19), + PIXELFORMAT_HI240 = (1 << 20), + PIXELFORMAT_HM12 = (1 << 21), + PIXELFORMAT_MJPEG = (1 << 22), + PIXELFORMAT_PWC1 = (1 << 23), + PIXELFORMAT_PWC2 = (1 << 24), + PIXELFORMAT_SN9C10X = (1 << 25), + PIXELFORMAT_WNVA = (1 << 26), + PIXELFORMAT_YYUV = (1 << 27) + +// PIXELFORMAT_ALL = 0x00003FFF +} pixel_format; + +typedef enum +{ + STANDARD_NONE = 0, + STANDARD_PAL_B = (1 << 0), + STANDARD_PAL_B1 = (1 << 1), + STANDARD_PAL_G = (1 << 2), + STANDARD_PAL_H = (1 << 3), + STANDARD_PAL_I = (1 << 4), + STANDARD_PAL_D = (1 << 5), + STANDARD_PAL_D1 = (1 << 6), + STANDARD_PAL_K = (1 << 7), + STANDARD_PAL_M = (1 << 8), + STANDARD_PAL_N = (1 << 9), + STANDARD_PAL_Nc = (1 << 10), + STANDARD_PAL_60 = (1 << 11), +// STANDARD_PAL_60 is a hybrid standard with 525 lines, 60 Hz refresh rate, and PAL color modulation with a 4.43 MHz color subcarrier. Some PAL video recorders can play back NTSC tapes in this mode for display on a 50/60 Hz agnostic PAL TV. + STANDARD_NTSC_M = (1 << 12), + STANDARD_NTSC_M_JP = (1 << 13), + STANDARD_NTSC_443 = (1 << 14), +// STANDARD_NTSC_443 is a hybrid standard with 525 lines, 60 Hz refresh rate, and NTSC color modulation with a 4.43 MHz color subcarrier. + STANDARD_SECAM_B = (1 << 16), + STANDARD_SECAM_D = (1 << 17), + STANDARD_SECAM_G = (1 << 18), + STANDARD_SECAM_H = (1 << 19), + STANDARD_SECAM_K = (1 << 20), + STANDARD_SECAM_K1 = (1 << 21), + STANDARD_SECAM_L = (1 << 22), + STANDARD_SECAM_LC = (1 << 23), +// ATSC/HDTV + STANDARD_ATSC_8_VSB = (1 << 24), + STANDARD_ATSC_16_VSB = (1 << 25), + + STANDARD_PAL_BG = ( STANDARD_PAL_B | STANDARD_PAL_B1 | STANDARD_PAL_G ), + STANDARD_PAL_DK = ( STANDARD_PAL_D | STANDARD_PAL_D1 | STANDARD_PAL_K ), + STANDARD_PAL = ( STANDARD_PAL_BG | STANDARD_PAL_DK | STANDARD_PAL_H | STANDARD_PAL_I ), + STANDARD_NTSC = ( STANDARD_NTSC_M | STANDARD_NTSC_M_JP ), + STANDARD_SECAM_DK = ( STANDARD_SECAM_D | STANDARD_SECAM_K | STANDARD_SECAM_K1 ), + STANDARD_SECAM = ( STANDARD_SECAM_B | STANDARD_SECAM_G | STANDARD_SECAM_H | STANDARD_SECAM_DK | STANDARD_SECAM_L), + STANDARD_525_60 = ( STANDARD_PAL_M | STANDARD_PAL_60 | STANDARD_NTSC | STANDARD_NTSC_443), + STANDARD_625_50 = ( STANDARD_PAL | STANDARD_PAL_N | STANDARD_PAL_Nc | STANDARD_SECAM), + STANDARD_ALL = ( STANDARD_525_60 | STANDARD_625_50) +} signal_standard; + + +typedef enum +{ + IO_METHOD_NONE, + IO_METHOD_READ, + IO_METHOD_MMAP, + IO_METHOD_USERPTR +} io_method; + +struct imagebuffer +{ + int height; + int width; + pixel_format pixelformat; + QValueVector data; // maybe it should be a rawbuffer instead of it? It could make us avoid a memory copy +}; +struct rawbuffer // raw buffer +{ + uchar * start; + size_t length; +}; + + +class VideoDevice{ +public: + VideoDevice(); + ~VideoDevice(); + int setFileName(QString filename); + int open(); + bool isOpen(); + int checkDevice(); + int showDeviceCapabilities(); + int initDevice(); + unsigned int inputs(); + int width(); + int minWidth(); + int maxWidth(); + int height(); + int minHeight(); + int maxHeight(); + int setSize( int newwidth, int newheight); + + pixel_format setPixelFormat(pixel_format newformat); + int pixelFormatCode(pixel_format pixelformat); + pixel_format pixelFormatForPalette( int palette ); + int pixelFormatDepth(pixel_format pixelformat); + QString pixelFormatName(pixel_format pixelformat); + QString pixelFormatName(int pixelformat); + int detectPixelFormats(); + + __u64 signalStandardCode(signal_standard standard); + QString signalStandardName(signal_standard standard); + QString signalStandardName(int standard); + int detectSignalStandards(); + + int currentInput(); + int selectInput(int input); + int setInputParameters(); + int startCapturing(); + int getFrame(); + int getFrame(imagebuffer *imgbuffer); + int getImage(QImage *qimage); + int stopCapturing(); + int close(); + + float getBrightness(); + float setBrightness(float brightness); + float getContrast(); + float setContrast(float contrast); + float getSaturation(); + float setSaturation(float saturation); + float getWhiteness(); + float setWhiteness(float whiteness); + float getHue(); + float setHue(float Hue); + + bool getAutoBrightnessContrast(); + bool setAutoBrightnessContrast(bool brightnesscontrast); + bool getAutoColorCorrection(); + bool setAutoColorCorrection(bool colorcorrection); + bool getImageAsMirror(); + bool setImageAsMirror(bool imageasmirror); + + bool canCapture(); + bool canChromakey(); + bool canScale(); + bool canOverlay(); + bool canRead(); + bool canAsyncIO(); + bool canStream(); + + QString m_model; + QString m_name; + size_t m_modelindex; // Defines what's the number of a device when more than 1 device of a given model is present; + QString full_filename; + videodev_driver m_driver; + int descriptor; + +//protected: +#if defined(__linux__) && defined(ENABLE_AV) +#ifdef V4L2_CAP_VIDEO_CAPTURE + struct v4l2_capability V4L2_capabilities; + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + struct v4l2_format fmt; + struct v4l2_fmtdesc fmtdesc; // Not sure if it must be here or inside detectPixelFormats(). Should inve +// struct v4l2_input m_input; + struct v4l2_queryctrl queryctrl; + struct v4l2_querymenu querymenu; +void enumerateMenu (void); + +#endif + struct video_capability V4L_capabilities; + struct video_buffer V4L_videobuffer; +#endif + QValueVector m_input; + QValueVector m_control; +// QFile file; +protected: + int currentwidth, minwidth, maxwidth, currentheight, minheight, maxheight; + + bool m_disablemmap; + bool m_workaroundbrokendriver; + + QValueVector m_rawbuffers; + unsigned int m_streambuffers; + imagebuffer m_currentbuffer; + int m_buffer_size; + + int m_current_input; + pixel_format m_pixelformat; + + io_method m_io_method; + bool m_videocapture; + bool m_videochromakey; + bool m_videoscale; + bool m_videooverlay; + bool m_videoread; + bool m_videoasyncio; + bool m_videostream; + + int xioctl(int request, void *arg); + int errnoReturn(const char* s); + int initRead(); + int initMmap(); + int initUserptr(); + +}; + +} + +} + +#endif diff --git a/kopete/libkopete/avdevice/videodevicemodelpool.cpp b/kopete/libkopete/avdevice/videodevicemodelpool.cpp new file mode 100644 index 00000000..c6fc533e --- /dev/null +++ b/kopete/libkopete/avdevice/videodevicemodelpool.cpp @@ -0,0 +1,68 @@ +/* + videodevicepool.h - Kopete Multiple Video Device handler Class + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "videodevicemodelpool.h" + +namespace Kopete { + +namespace AV { + +VideoDeviceModelPool::VideoDeviceModelPool() +{ +} + + +VideoDeviceModelPool::~VideoDeviceModelPool() +{ +} + +void VideoDeviceModelPool::clear() +{ + m_devicemodel.clear(); +} + +size_t VideoDeviceModelPool::size() +{ + return m_devicemodel.size(); +} + +size_t VideoDeviceModelPool::addModel( QString newmodel ) +{ + VideoDeviceModel newdevicemodel; + newdevicemodel.model=newmodel; + newdevicemodel.count=0; + + if(m_devicemodel.size()) + { + for ( size_t loop = 0 ; loop < m_devicemodel.size(); loop++) + if (newmodel == m_devicemodel[loop].model) + { + kdDebug() << k_funcinfo << "Model " << newmodel << " already exists." << endl; + m_devicemodel[loop].count++; + return m_devicemodel[loop].count; + } + } + m_devicemodel.push_back(newdevicemodel); + m_devicemodel[m_devicemodel.size()-1].model = newmodel; + m_devicemodel[m_devicemodel.size()-1].count = 0; + return 0; +} + + +} + +} diff --git a/kopete/libkopete/avdevice/videodevicemodelpool.h b/kopete/libkopete/avdevice/videodevicemodelpool.h new file mode 100644 index 00000000..54d801c4 --- /dev/null +++ b/kopete/libkopete/avdevice/videodevicemodelpool.h @@ -0,0 +1,53 @@ +/* + videodevicepool.h - Kopete Multiple Video Device handler Class + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_AVVIDEODEVICEMODELPOOL_H +#define KOPETE_AVVIDEODEVICEMODELPOOL_H + +#include +#include +#include +#include "kopete_export.h" + +namespace Kopete { + +namespace AV { + +/** + @author Kopete Developers +*/ +class VideoDeviceModelPool{ + + struct VideoDeviceModel + { + QString model; + size_t count; + }; + QValueVector m_devicemodel; +public: + VideoDeviceModelPool(); + ~VideoDeviceModelPool(); + void clear(); + size_t size(); + size_t addModel(QString newmodel); +}; + +} + +} + +#endif diff --git a/kopete/libkopete/avdevice/videodevicepool.cpp b/kopete/libkopete/avdevice/videodevicepool.cpp new file mode 100644 index 00000000..2651addb --- /dev/null +++ b/kopete/libkopete/avdevice/videodevicepool.cpp @@ -0,0 +1,889 @@ +/* + videodevice.cpp - Kopete Video Device Low-level Support + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#define ENABLE_AV + +#include +#include +#include +#include + +#include +#include +#include + +#include "videodevice.h" +#include "videodevicepool.h" + +#define CLEAR(x) memset (&(x), 0, sizeof (x)) + +namespace Kopete { + +namespace AV { + +VideoDevicePool *VideoDevicePool::s_self = NULL; +__u64 VideoDevicePool::m_clients = 0; + +VideoDevicePool* VideoDevicePool::self() +{ + kdDebug(14010) << "libkopete (avdevice): self() called" << endl; + if (s_self == NULL) + { + s_self = new VideoDevicePool; + if (s_self) + m_clients = 0; + } + kdDebug(14010) << "libkopete (avdevice): self() exited successfuly. m_clients = " << m_clients << endl; + return s_self; +} + +VideoDevicePool::VideoDevicePool() +{ +} + + +VideoDevicePool::~VideoDevicePool() +{ +} + + + + +/*! + \fn VideoDevicePool::open() + */ +int VideoDevicePool::open() +{ + /// @todo implement me + + m_ready.lock(); + if(!m_videodevice.size()) + { + kdDebug(14010) << k_funcinfo << "open(): No devices found. Must scan for available devices." << m_current_device << endl; + scanDevices(); + } + if(!m_videodevice.size()) + { + kdDebug(14010) << k_funcinfo << "open(): No devices found. bailing out." << m_current_device << endl; + m_ready.unlock(); + return EXIT_FAILURE; + } + if(m_current_device >= m_videodevice.size()) + { + kdDebug(14010) << k_funcinfo << "open(): Device out of scope (" << m_current_device << "). Defaulting to the first one." << endl; + m_current_device = 0; + } + int isopen = m_videodevice[currentDevice()].open(); + if ( isopen == EXIT_SUCCESS) + { + loadConfig(); // Temporary hack. The open() seems to clean the input parameters. Need to find a way to fix it. + + } + m_clients++; + kdDebug(14010) << k_funcinfo << "Number of clients: " << m_clients << endl; + m_ready.unlock(); + return isopen; +} + +/*! + \fn VideoDevicePool::open(int device) + */ +int VideoDevicePool::open(unsigned int device) +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "open(" << device << ") called." << endl; + if(device >= m_videodevice.size()) + { + kdDebug(14010) << k_funcinfo << "open(" << device <<"): Device does not exist." << endl; + return EXIT_FAILURE; + } + close(); + kdDebug(14010) << k_funcinfo << "open(" << device << ") Setting m_current_Device to " << device << endl; + m_current_device = device; + saveConfig(); + kdDebug(14010) << k_funcinfo << "open(" << device << ") Calling open()." << endl; + return open(); +} + +bool VideoDevicePool::isOpen() +{ + return m_videodevice[currentDevice()].isOpen(); +} + +/*! + \fn VideoDevicePool::showDeviceCapabilities(int device) + */ +int VideoDevicePool::showDeviceCapabilities(unsigned int device) +{ + return m_videodevice[device].showDeviceCapabilities(); +} + +int VideoDevicePool::width() +{ + return m_videodevice[currentDevice()].width(); +} + +int VideoDevicePool::minWidth() +{ + return m_videodevice[currentDevice()].minWidth(); +} + +int VideoDevicePool::maxWidth() +{ + return m_videodevice[currentDevice()].maxWidth(); +} + +int VideoDevicePool::height() +{ + return m_videodevice[currentDevice()].height(); +} + +int VideoDevicePool::minHeight() +{ + return m_videodevice[currentDevice()].minHeight(); +} + +int VideoDevicePool::maxHeight() +{ + return m_videodevice[currentDevice()].maxHeight(); +} + +int VideoDevicePool::setSize( int newwidth, int newheight) +{ + if(m_videodevice.size()) + return m_videodevice[currentDevice()].setSize(newwidth, newheight); + else + { + kdDebug(14010) << k_funcinfo << "VideoDevicePool::setSize() fallback for no device." << endl; + m_buffer.width=newwidth; + m_buffer.height=newheight; + m_buffer.pixelformat= PIXELFORMAT_RGB24; + m_buffer.data.resize(m_buffer.width*m_buffer.height*3); + kdDebug(14010) << k_funcinfo << "VideoDevicePool::setSize() buffer size: "<< m_buffer.data.size() << endl; + } + return EXIT_SUCCESS; +} + +/*! + \fn VideoDevicePool::close() + */ +int VideoDevicePool::close() +{ + /// @todo implement me + if(m_clients) + m_clients--; + if((currentDevice() < m_videodevice.size())&&(!m_clients)) + return m_videodevice[currentDevice()].close(); + if(m_clients) + kdDebug(14010) << k_funcinfo << "VideoDevicePool::close() The video device is still in use." << endl; + if(currentDevice() >= m_videodevice.size()) + kdDebug(14010) << k_funcinfo << "VideoDevicePool::close() Current device out of range." << endl; + return EXIT_FAILURE; +} + +/*! + \fn VideoDevicePool::startCapturing() + */ +int VideoDevicePool::startCapturing() +{ + kdDebug(14010) << k_funcinfo << "startCapturing() called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].startCapturing(); + return EXIT_FAILURE; +} + + +/*! + \fn VideoDevicePool::stopCapturing() + */ +int VideoDevicePool::stopCapturing() +{ + if(m_videodevice.size()) + return m_videodevice[currentDevice()].stopCapturing(); + return EXIT_FAILURE; +} + +// Implementation of the methods that get / set input's adjustment parameters +/*! + \fn VideoDevicePool::getBrightness() + */ +float VideoDevicePool::getBrightness() +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].getBrightness(); + else + return 0; +} + +/*! + \fn VideoDevicePool::setBrightness(float brightness) + */ +float VideoDevicePool::setBrightness(float brightness) +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].setBrightness(brightness); + else + return 0; +} + +/*! + \fn VideoDevicePool::getContrast() + */ +float VideoDevicePool::getContrast() +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].getContrast(); + else + return 0; +} + +/*! + \fn VideoDevicePool::setContrast(float contrast) + */ +float VideoDevicePool::setContrast(float contrast) +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].setContrast(contrast); + else + return 0; +} + +/*! + \fn VideoDevicePool::getSaturation() + */ +float VideoDevicePool::getSaturation() +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].getSaturation(); + else + return 0; +} + +/*! + \fn VideoDevicePool::setSaturation(float saturation) + */ +float VideoDevicePool::setSaturation(float saturation) +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].setSaturation(saturation); + else + return 0; +} + +/*! + \fn VideoDevicePool::getWhiteness() + */ +float VideoDevicePool::getWhiteness() +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].getWhiteness(); + else + return 0; +} + +/*! + \fn VideoDevicePool::setWhiteness(float whiteness) + */ +float VideoDevicePool::setWhiteness(float whiteness) +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].setWhiteness(whiteness); + else + return 0; +} + +/*! + \fn VideoDevicePool::getHue() + */ +float VideoDevicePool::getHue() +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].getHue(); + else + return 0; +} + +/*! + \fn VideoDevicePool::setHue(float hue) + */ +float VideoDevicePool::setHue(float hue) +{ + if (currentDevice() < m_videodevice.size() ) + return m_videodevice[currentDevice()].setHue(hue); + else + return 0; +} + +/*! + \fn VideoDevicePool::getAutoBrightnessContrast() + */ +bool VideoDevicePool::getAutoBrightnessContrast() +{ + if(m_videodevice.size()) + return m_videodevice[currentDevice()].getAutoBrightnessContrast(); + return false; +} + +/*! + \fn VideoDevicePool::setAutoBrightnessContrast(bool brightnesscontrast) + */ +bool VideoDevicePool::setAutoBrightnessContrast(bool brightnesscontrast) +{ + kdDebug(14010) << k_funcinfo << "VideoDevicePool::setAutoBrightnessContrast(" << brightnesscontrast << ") called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].setAutoBrightnessContrast(brightnesscontrast); + return false; +} + +/*! + \fn VideoDevicePool::getAutoColorCorrection() + */ +bool VideoDevicePool::getAutoColorCorrection() +{ + if(m_videodevice.size()) + return m_videodevice[currentDevice()].getAutoColorCorrection(); + return false; +} + +/*! + \fn VideoDevicePool::setAutoColorCorrection(bool colorcorrection) + */ +bool VideoDevicePool::setAutoColorCorrection(bool colorcorrection) +{ + kdDebug(14010) << k_funcinfo << "VideoDevicePool::setAutoColorCorrection(" << colorcorrection << ") called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].setAutoColorCorrection(colorcorrection); + return false; +} + +/*! + \fn VideoDevicePool::getIMageAsMirror() + */ +bool VideoDevicePool::getImageAsMirror() +{ + if(m_videodevice.size()) + return m_videodevice[currentDevice()].getImageAsMirror(); + return false; +} + +/*! + \fn VideoDevicePool::setImageAsMirror(bool imageasmirror) + */ +bool VideoDevicePool::setImageAsMirror(bool imageasmirror) +{ + kdDebug(14010) << k_funcinfo << "VideoDevicePool::setImageAsMirror(" << imageasmirror << ") called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].setImageAsMirror(imageasmirror); + return false; +} + +/*! + \fn VideoDevicePool::getFrame() + */ +int VideoDevicePool::getFrame() +{ +// kdDebug(14010) << k_funcinfo << "VideoDevicePool::getFrame() called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].getFrame(); + else + { + kdDebug(14010) << k_funcinfo << "VideoDevicePool::getFrame() fallback for no device." << endl; + for(unsigned int loop=0; loop < m_buffer.data.size(); loop+=3) + { + m_buffer.data[loop] = 255; + m_buffer.data[loop+1] = 0; + m_buffer.data[loop+2] = 0; + } + } +// kdDebug(14010) << k_funcinfo << "VideoDevicePool::getFrame() exited successfuly." << endl; + return EXIT_SUCCESS; +} + +/*! + \fn VideoDevicePool::getQImage(QImage *qimage) + */ +int VideoDevicePool::getImage(QImage *qimage) +{ +// kdDebug(14010) << k_funcinfo << "VideoDevicePool::getImage() called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].getImage(qimage); + else + { + kdDebug(14010) << k_funcinfo << "VideoDevicePool::getImage() fallback for no device." << endl; + qimage->create(m_buffer.width, m_buffer.height,32, QImage::IgnoreEndian); + uchar *bits=qimage->bits(); + switch(m_buffer.pixelformat) + { + case PIXELFORMAT_NONE : break; + case PIXELFORMAT_GREY : break; + case PIXELFORMAT_RGB332 : break; + case PIXELFORMAT_RGB555 : break; + case PIXELFORMAT_RGB555X: break; + case PIXELFORMAT_RGB565 : break; + case PIXELFORMAT_RGB565X: break; + case PIXELFORMAT_RGB24 : + { + kdDebug(14010) << k_funcinfo << "VideoDevicePool::getImage() fallback for no device - RGB24." << endl; + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = m_buffer.data[step]; + bits[loop+1] = m_buffer.data[step+1]; + bits[loop+2] = m_buffer.data[step+2]; + bits[loop+3] = 255; + step+=3; + } + } + break; + case PIXELFORMAT_BGR24 : break; + { + int step=0; + for(int loop=0;loop < qimage->numBytes();loop+=4) + { + bits[loop] = m_buffer.data[step+2]; + bits[loop+1] = m_buffer.data[step+1]; + bits[loop+2] = m_buffer.data[step]; + bits[loop+3] = 255; + step+=3; + } + } + break; + case PIXELFORMAT_RGB32 : memcpy(bits,&m_buffer.data[0], m_buffer.data.size()); + break; + case PIXELFORMAT_BGR32 : break; + } + } + kdDebug(14010) << k_funcinfo << "VideoDevicePool::getImage() exited successfuly." << endl; + return EXIT_SUCCESS; +} + +/*! + \fn Kopete::AV::VideoDevicePool::selectInput(int input) + */ +int VideoDevicePool::selectInput(int newinput) +{ + kdDebug(14010) << k_funcinfo << "VideoDevicePool::selectInput(" << newinput << ") called." << endl; + if(m_videodevice.size()) + return m_videodevice[currentDevice()].selectInput(newinput); + else + return 0; +} + +/*! + \fn Kopete::AV::VideoDevicePool::setInputParameters() + */ +int VideoDevicePool::setInputParameters() +{ + if(m_videodevice.size()) + return m_videodevice[currentDevice()].setInputParameters(); + else + return EXIT_FAILURE; +} + +/*! + \fn Kopete::AV::VideoDevicePool::fillDeviceKComboBox(KComboBox *combobox) + */ +int VideoDevicePool::fillDeviceKComboBox(KComboBox *combobox) +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "fillInputKComboBox: Called." << endl; + combobox->clear(); + if(m_videodevice.size()) + { + for (unsigned int loop=0; loop < m_videodevice.size(); loop++) + { + combobox->insertItem(m_videodevice[loop].m_name); + kdDebug(14010) << k_funcinfo << "DeviceKCombobox: Added device " << loop << ": " << m_videodevice[loop].m_name << endl; + } + combobox->setCurrentItem(currentDevice()); + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +/*! + \fn Kopete::AV::VideoDevicePool::fillInputKComboBox(KComboBox *combobox) + */ +int VideoDevicePool::fillInputKComboBox(KComboBox *combobox) +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "fillInputKComboBox: Called." << endl; + combobox->clear(); + if(m_videodevice.size()) + { + if(m_videodevice[currentDevice()].inputs()>0) + { + for (unsigned int loop=0; loop < m_videodevice[currentDevice()].inputs(); loop++) + { + combobox->insertItem(m_videodevice[currentDevice()].m_input[loop].name); + kdDebug(14010) << k_funcinfo << "InputKCombobox: Added input " << loop << ": " << m_videodevice[currentDevice()].m_input[loop].name << " (tuner: " << m_videodevice[currentDevice()].m_input[loop].hastuner << ")" << endl; + } + combobox->setCurrentItem(currentInput()); + return EXIT_SUCCESS; + } + } + return EXIT_FAILURE; +} + +/*! + \fn Kopete::AV::VideoDevicePool::fillStandardKComboBox(KComboBox *combobox) + */ +int VideoDevicePool::fillStandardKComboBox(KComboBox *combobox) +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "fillInputKComboBox: Called." << endl; + combobox->clear(); + if(m_videodevice.size()) + { + if(m_videodevice[currentDevice()].inputs()>0) + { + for (unsigned int loop=0; loop < 25; loop++) + { + if ( (m_videodevice[currentDevice()].m_input[currentInput()].m_standards) & (1 << loop) ) + combobox->insertItem(m_videodevice[currentDevice()].signalStandardName( 1 << loop)); +/* + case STANDARD_PAL_B1 : return V4L2_STD_PAL_B1; break; + case STANDARD_PAL_G : return V4L2_STD_PAL_G; break; + case STANDARD_PAL_H : return V4L2_STD_PAL_H; break; + case STANDARD_PAL_I : return V4L2_STD_PAL_I; break; + case STANDARD_PAL_D : return V4L2_STD_PAL_D; break; + case STANDARD_PAL_D1 : return V4L2_STD_PAL_D1; break; + case STANDARD_PAL_K : return V4L2_STD_PAL_K; break; + case STANDARD_PAL_M : return V4L2_STD_PAL_M; break; + case STANDARD_PAL_N : return V4L2_STD_PAL_N; break; + case STANDARD_PAL_Nc : return V4L2_STD_PAL_Nc; break; + case STANDARD_PAL_60 : return V4L2_STD_PAL_60; break; + case STANDARD_NTSC_M : return V4L2_STD_NTSC_M; break; + case STANDARD_NTSC_M_JP : return V4L2_STD_NTSC_M_JP; break; + case STANDARD_NTSC_443 : return V4L2_STD_NTSC; break; // Using workaround value because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case STANDARD_SECAM_B : return V4L2_STD_SECAM_B; break; + case STANDARD_SECAM_D : return V4L2_STD_SECAM_D; break; + case STANDARD_SECAM_G : return V4L2_STD_SECAM_G; break; + case STANDARD_SECAM_H : return V4L2_STD_SECAM_H; break; + case STANDARD_SECAM_K : return V4L2_STD_SECAM_K; break; + case STANDARD_SECAM_K1 : return V4L2_STD_SECAM_K1; break; + case STANDARD_SECAM_L : return V4L2_STD_SECAM_L; break; + case STANDARD_SECAM_LC : return V4L2_STD_SECAM; break; // Using workaround value because my videodev2.h header seems to not include this standard in struct __u64 v4l2_std_id + case STANDARD_ATSC_8_VSB : return V4L2_STD_ATSC_8_VSB; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_ATSC_16_VSB : return V4L2_STD_ATSC_16_VSB; break; // ATSC/HDTV Standard officially not supported by V4L2 but exists in videodev2.h + case STANDARD_PAL_BG : return V4L2_STD_PAL_BG; break; + case STANDARD_PAL_DK : return V4L2_STD_PAL_DK; break; + case STANDARD_PAL : return V4L2_STD_PAL; break; + case STANDARD_NTSC : return V4L2_STD_NTSC; break; + case STANDARD_SECAM_DK : return V4L2_STD_SECAM_DK; break; + case STANDARD_SECAM : return V4L2_STD_SECAM; break; + case STANDARD_525_60 : return V4L2_STD_525_60; break; + case STANDARD_625_50 : return V4L2_STD_625_50; break; + case STANDARD_ALL : return V4L2_STD_ALL; break; + + combobox->insertItem(m_videodevice[currentDevice()].m_input[loop].name); + kdDebug(14010) << k_funcinfo << "StandardKCombobox: Added input " << loop << ": " << m_videodevice[currentDevice()].m_input[loop].name << " (tuner: " << m_videodevice[currentDevice()].m_input[loop].hastuner << ")" << endl;*/ + } + combobox->setCurrentItem(currentInput()); + return EXIT_SUCCESS; + } + } + return EXIT_FAILURE; +} + +/*! + \fn Kopete::AV::VideoDevicePool::scanDevices() + */ +int VideoDevicePool::scanDevices() +{ + /// @todo implement me + + kdDebug(14010) << k_funcinfo << "called" << endl; +#if defined(__linux__) && defined(ENABLE_AV) + QDir videodevice_dir; + const QString videodevice_dir_path=QString::fromLocal8Bit("/dev/v4l/"); + const QString videodevice_dir_filter=QString::fromLocal8Bit("video*"); + VideoDevice videodevice; + + m_videodevice.clear(); + m_modelvector.clear(); + + videodevice_dir.setPath(videodevice_dir_path); + videodevice_dir.setNameFilter(videodevice_dir_filter); + videodevice_dir.setFilter( QDir::System | QDir::NoSymLinks | QDir::Readable | QDir::Writable ); + videodevice_dir.setSorting( QDir::Name ); + + kdDebug(14010) << k_funcinfo << "Looking for devices in " << videodevice_dir_path << endl; + const QFileInfoList *list = videodevice_dir.entryInfoList(); + + if (!list) + { + kdDebug(14010) << k_funcinfo << "Found no suitable devices in " << videodevice_dir_path << endl; + QDir videodevice_dir; + const QString videodevice_dir_path=QString::fromLocal8Bit("/dev/"); + const QString videodevice_dir_filter=QString::fromLocal8Bit("video*"); + VideoDevice videodevice; + + videodevice_dir.setPath(videodevice_dir_path); + videodevice_dir.setNameFilter(videodevice_dir_filter); + videodevice_dir.setFilter( QDir::System | QDir::NoSymLinks | QDir::Readable | QDir::Writable ); + videodevice_dir.setSorting( QDir::Name ); + + kdDebug(14010) << k_funcinfo << "Looking for devices in " << videodevice_dir_path << endl; + const QFileInfoList *list = videodevice_dir.entryInfoList(); + + if (!list) + { + kdDebug(14010) << k_funcinfo << "Found no suitable devices in " << videodevice_dir_path << endl; + return EXIT_FAILURE; + } + + QFileInfoListIterator fileiterator ( *list ); + QFileInfo *fileinfo; + + kdDebug(14010) << k_funcinfo << "scanning devices in " << videodevice_dir_path << "..." << endl; + while ( (fileinfo = fileiterator.current()) != 0 ) + { + videodevice.setFileName(fileinfo->absFilePath()); + kdDebug(14010) << k_funcinfo << "Found device " << videodevice.full_filename << endl; + videodevice.open(); // It should be opened with O_NONBLOCK (it's a FIFO) but I dunno how to do it using QFile + if(videodevice.isOpen()) + { + kdDebug(14010) << k_funcinfo << "File " << videodevice.full_filename << " was opened successfuly" << endl; + +// This must be changed to proper code to handle multiple devices of the same model. It currently simply add models without proper checking + videodevice.close(); + videodevice.m_modelindex=m_modelvector.addModel (videodevice.m_model); // Adds device to the device list and sets model number + m_videodevice.push_back(videodevice); + } + ++fileiterator; + } + + + m_current_device = 0; + loadConfig(); + kdDebug(14010) << k_funcinfo << "exited successfuly" << endl; + return EXIT_SUCCESS; + + } + QFileInfoListIterator fileiterator ( *list ); + QFileInfo *fileinfo; + + kdDebug(14010) << k_funcinfo << "scanning devices in " << videodevice_dir_path << "..." << endl; + while ( (fileinfo = fileiterator.current()) != 0 ) + { + videodevice.setFileName(fileinfo->absFilePath()); + kdDebug(14010) << k_funcinfo << "Found device " << videodevice.full_filename << endl; + videodevice.open(); // It should be opened with O_NONBLOCK (it's a FIFO) but I dunno how to do it using QFile + if(videodevice.isOpen()) + { + kdDebug(14010) << k_funcinfo << "File " << videodevice.full_filename << " was opened successfuly" << endl; + +// This must be changed to proper code to handle multiple devices of the same model. It currently simply add models without proper checking + videodevice.close(); + videodevice.m_modelindex=m_modelvector.addModel (videodevice.m_model); // Adds device to the device list and sets model number + m_videodevice.push_back(videodevice); + } + ++fileiterator; + } + m_current_device = 0; + loadConfig(); +#endif + kdDebug(14010) << k_funcinfo << "exited successfuly" << endl; + return EXIT_SUCCESS; +} + +/*! + \fn Kopete::AV::VideoDevicePool::hasDevices() + */ +bool VideoDevicePool::hasDevices() +{ + /// @todo implement me + if(m_videodevice.size()) + return true; + return false; +} + +/*! + \fn Kopete::AV::VideoDevicePool::size() + */ +size_t VideoDevicePool::size() +{ + /// @todo implement me + return m_videodevice.size(); +} + +/*! + \fn Kopete::AV::VideoDevicePool::currentDevice() + */ +unsigned int VideoDevicePool::currentDevice() +{ + /// @todo implement me + return m_current_device; +} + +/*! + \fn Kopete::AV::VideoDevicePool::currentInput() + */ +int VideoDevicePool::currentInput() +{ + /// @todo implement me + return m_videodevice[currentDevice()].currentInput(); +} + +/*! + \fn Kopete::AV::VideoDevicePool::currentInput() + */ +unsigned int VideoDevicePool::inputs() +{ + /// @todo implement me + return m_videodevice[currentDevice()].inputs(); +} + +/*! + \fn Kopete::AV::VideoDevicePool::loadConfig() + */ +void VideoDevicePool::loadConfig() +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "called" << endl; + if((hasDevices())&&(m_clients==0)) + { + KConfig *config = KGlobal::config(); + config->setGroup("Video Device Settings"); + const QString currentdevice = config->readEntry("Current Device", QString::null); + kdDebug(14010) << k_funcinfo << "Current device: " << currentdevice << endl; + +// m_current_device = 0; // Must check this thing because of the fact that multiple loadConfig in other methodas can do bad things. Watch out! + + VideoDeviceVector::iterator vditerator; + for( vditerator = m_videodevice.begin(); vditerator != m_videodevice.end(); ++vditerator ) + { + const QString modelindex = QString::fromLocal8Bit ( "Model %1 Device %2") .arg ((*vditerator).m_name ) .arg ((*vditerator).m_modelindex); + if(modelindex == currentdevice) + { + m_current_device = vditerator - m_videodevice.begin(); +// kdDebug(14010) << k_funcinfo << "This place will be used to set " << modelindex << " as the current device ( " << (vditerator - m_videodevice.begin()) << " )." << endl; + } + const QString name = config->readEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Name") .arg ((*vditerator).m_name ) .arg ((*vditerator).m_modelindex)), (*vditerator).m_model); + const int currentinput = config->readNumEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Current input") .arg ((*vditerator).m_name ) .arg ((*vditerator).m_modelindex)), 0); + kdDebug(14010) << k_funcinfo << "Device name: " << name << endl; + kdDebug(14010) << k_funcinfo << "Device current input: " << currentinput << endl; + (*vditerator).selectInput(currentinput); + + for (size_t input = 0 ; input < (*vditerator).m_input.size(); input++) + { + const float brightness = config->readDoubleNumEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Brightness").arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , 0.5 ); + const float contrast = config->readDoubleNumEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Contrast") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , 0.5 ); + const float saturation = config->readDoubleNumEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Saturation").arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , 0.5 ); + const float whiteness = config->readDoubleNumEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Whiteness") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , 0.5 ); + const float hue = config->readDoubleNumEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Hue") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , 0.5 ); + const bool autobrightnesscontrast = config->readBoolEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 AutoBrightnessContrast") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , false ); + const bool autocolorcorrection = config->readBoolEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 AutoColorCorrection") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , false ); + const bool imageasmirror = config->readBoolEntry((QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 mageAsMirror") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input)) , false ); + (*vditerator).setBrightness(brightness); + (*vditerator).setContrast(contrast); + (*vditerator).setSaturation(saturation); + (*vditerator).setHue(hue); + (*vditerator).setAutoBrightnessContrast(autobrightnesscontrast); + (*vditerator).setAutoColorCorrection(autocolorcorrection); + (*vditerator).setImageAsMirror(imageasmirror); + kdDebug(14010) << k_funcinfo << "Brightness:" << brightness << endl; + kdDebug(14010) << k_funcinfo << "Contrast :" << contrast << endl; + kdDebug(14010) << k_funcinfo << "Saturation:" << saturation << endl; + kdDebug(14010) << k_funcinfo << "Whiteness :" << whiteness << endl; + kdDebug(14010) << k_funcinfo << "Hue :" << hue << endl; + kdDebug(14010) << k_funcinfo << "AutoBrightnessContrast:" << autobrightnesscontrast << endl; + kdDebug(14010) << k_funcinfo << "AutoColorCorrection :" << autocolorcorrection << endl; + kdDebug(14010) << k_funcinfo << "ImageAsMirror :" << imageasmirror << endl; + } + } + } +} + +/*! + \fn Kopete::AV::VideoDevicePool::saveConfig() + */ +void VideoDevicePool::saveConfig() +{ + /// @todo implement me + kdDebug(14010) << k_funcinfo << "called" << endl; + if(hasDevices()) + { + KConfig *config = KGlobal::config(); + config->setGroup("Video Device Settings"); + +/* if(m_modelvector.size()) + { + VideoDeviceModelPool::m_devicemodel::iterator vmiterator; + for( vmiterator = m_modelvector.begin(); vmiterator != m_modelvector.end(); ++vmiterator ) + { + kdDebug(14010) << "Device Model: " << (*vmiterator).model << endl; + kdDebug(14010) << "Device Count: " << (*vmiterator).count << endl; + } + } +*/ +// Stores what is the current video device in use + const QString currentdevice = QString::fromLocal8Bit ( "Model %1 Device %2" ) .arg(m_videodevice[m_current_device].m_model) .arg(m_videodevice[m_current_device].m_modelindex); + config->writeEntry( "Current Device", currentdevice); + + VideoDeviceVector::iterator vditerator; + for( vditerator = m_videodevice.begin(); vditerator != m_videodevice.end(); ++vditerator ) + { + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Name:" << (*vditerator).m_name << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Current input:" << (*vditerator).currentInput() << endl; + +// Stores current input for the given video device + const QString name = QString::fromLocal8Bit ( "Model %1 Device %2 Name") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex); + const QString currentinput = QString::fromLocal8Bit ( "Model %1 Device %2 Current input") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex); + config->writeEntry( name, (*vditerator).m_name); + config->writeEntry( currentinput, (*vditerator).currentInput()); + + for (size_t input = 0 ; input < (*vditerator).m_input.size(); input++) + { + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Brightness: " << (*vditerator).m_input[input].getBrightness() << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Contrast : " << (*vditerator).m_input[input].getContrast() << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Saturation: " << (*vditerator).m_input[input].getSaturation() << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Whiteness : " << (*vditerator).m_input[input].getWhiteness() << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Hue : " << (*vditerator).m_input[input].getHue() << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Automatic brightness / contrast: " << (*vditerator).m_input[input].getAutoBrightnessContrast() << endl; + kdDebug(14010) << "Model:" << (*vditerator).m_model << ":Index:" << (*vditerator).m_modelindex << ":Input:" << input << ":Automatic color correction : " << (*vditerator).m_input[input].getAutoColorCorrection() << endl; + +// Stores configuration about each channel + const QString brightness = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Brightness") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString contrast = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Contrast") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString saturation = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Saturation") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString whiteness = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Whiteness") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString hue = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 Hue") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString autobrightnesscontrast = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 AutoBrightnessContrast") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString autocolorcorrection = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 AutoColorCorrection") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + const QString imageasmirror = QString::fromLocal8Bit ( "Model %1 Device %2 Input %3 ImageAsMirror") .arg ((*vditerator).m_model ) .arg ((*vditerator).m_modelindex) .arg (input); + config->writeEntry( brightness, (*vditerator).m_input[input].getBrightness()); + config->writeEntry( contrast, (*vditerator).m_input[input].getContrast()); + config->writeEntry( saturation, (*vditerator).m_input[input].getSaturation()); + config->writeEntry( whiteness, (*vditerator).m_input[input].getWhiteness()); + config->writeEntry( hue, (*vditerator).m_input[input].getHue()); + config->writeEntry( autobrightnesscontrast, (*vditerator).m_input[input].getAutoBrightnessContrast()); + config->writeEntry( autocolorcorrection, (*vditerator).m_input[input].getAutoColorCorrection()); + config->writeEntry( imageasmirror, (*vditerator).m_input[input].getImageAsMirror()); + } + } + config->sync(); + kdDebug(14010) << endl; + } +} + + + +} + +} diff --git a/kopete/libkopete/avdevice/videodevicepool.h b/kopete/libkopete/avdevice/videodevicepool.h new file mode 100644 index 00000000..1fbdb3e1 --- /dev/null +++ b/kopete/libkopete/avdevice/videodevicepool.h @@ -0,0 +1,127 @@ +/* + videodevicepool.h - Kopete Multiple Video Device handler Class + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_AVVIDEODEVICE_H +#define KOPETE_AVVIDEODEVICE_H + +#include +#include + + +#include "videoinput.h" +#include "videodevicemodelpool.h" +#include +#include +#include +#include +#include +#include "videodevice.h" +#include "kopete_export.h" +#include +#include +#include + +namespace Kopete { + +namespace AV { + +/** +This class allows kopete to check for the existence, open, configure, test, set parameters, grab frames from and close a given video capture card using the Video4Linux API. + +@author Cláudio da Silveira Pinheiro +*/ + +typedef QValueVector VideoDeviceVector; + +class VideoDevicePoolPrivate; + +class KOPETE_EXPORT VideoDevicePool +{ +public: + static VideoDevicePool* self(); + int open(); + int open(unsigned int device); + bool isOpen(); + int getFrame(); + int width(); + int minWidth(); + int maxWidth(); + int height(); + int minHeight(); + int maxHeight(); + int setSize( int newwidth, int newheight); + int close(); + int startCapturing(); + int stopCapturing(); + int readFrame(); + int getImage(QImage *qimage); + int selectInput(int newinput); + int setInputParameters(); + int scanDevices(); + bool hasDevices(); + size_t size(); + ~VideoDevicePool(); + VideoDeviceVector m_videodevice; // Vector to be filled with found devices + VideoDeviceModelPool m_modelvector; // Vector to be filled with unique device models + int fillDeviceKComboBox(KComboBox *combobox); + int fillInputKComboBox(KComboBox *combobox); + int fillStandardKComboBox(KComboBox *combobox); + unsigned int currentDevice(); + int currentInput(); + unsigned int inputs(); + + float getBrightness(); + float setBrightness(float brightness); + float getContrast(); + float setContrast(float contrast); + float getSaturation(); + float setSaturation(float saturation); + float getWhiteness(); + float setWhiteness(float whiteness); + float getHue(); + float setHue(float hue); + + bool getAutoBrightnessContrast(); + bool setAutoBrightnessContrast(bool brightnesscontrast); + bool getAutoColorCorrection(); + bool setAutoColorCorrection(bool colorcorrection); + bool getImageAsMirror(); + bool setImageAsMirror(bool imageasmirror); + + void loadConfig(); // Load configuration parameters; + void saveConfig(); // Save configuretion parameters; + +protected: + int xioctl(int request, void *arg); + int errnoReturn(const char* s); + int showDeviceCapabilities(unsigned int device); + void guessDriver(); + unsigned int m_current_device; + struct imagebuffer m_buffer; // only used when no devices were found + + QMutex m_ready; +private: + VideoDevicePool(); + static VideoDevicePool* s_self; + static __u64 m_clients; // Number of instances +}; + +} + +} + +#endif diff --git a/kopete/libkopete/avdevice/videoinput.cpp b/kopete/libkopete/avdevice/videoinput.cpp new file mode 100644 index 00000000..5f0f8e58 --- /dev/null +++ b/kopete/libkopete/avdevice/videoinput.cpp @@ -0,0 +1,172 @@ +/* + videoinput.cpp - Kopete Video Input Class + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "videoinput.h" + +namespace Kopete { + +namespace AV { + +VideoInput::VideoInput() +{ + kdDebug() << k_funcinfo << "Executing Video Input's constructor!!!" << endl; + m_brightness = 0.5; + m_contrast = 0.5; + m_saturation = 0.5; + m_hue = 0.5; + m_autobrightnesscontrast = false; + m_autocolorcorrection = false; +} + + +VideoInput::~VideoInput() +{ +} + +float VideoInput::getBrightness() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_brightness; +} + +float VideoInput::setBrightness(float brightness) +{ +// kdDebug() << k_funcinfo << " called." << endl; + if ( brightness > 1 ) + brightness = 1; + else + if ( brightness < 0 ) + brightness = 0; + m_brightness = brightness; + return getBrightness(); +} + +float VideoInput::getContrast() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_contrast; +} + +float VideoInput::setContrast(float contrast) +{ +// kdDebug() << k_funcinfo << " called." << endl; + if ( contrast > 1 ) + contrast = 1; + else + if ( contrast < 0 ) + contrast = 0; + m_contrast = contrast; + return getContrast(); +} + +float VideoInput::getSaturation() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_saturation; +} + +float VideoInput::setSaturation(float saturation) +{ +// kdDebug() << k_funcinfo << " called." << endl; + if ( saturation > 1 ) + saturation = 1; + else + if ( saturation < 0 ) + saturation = 0; + m_saturation = saturation; + return getSaturation(); +} + +float VideoInput::getWhiteness() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_whiteness; +} + +float VideoInput::setWhiteness(float whiteness) +{ +// kdDebug() << k_funcinfo << " called." << endl; + if ( whiteness > 1 ) + whiteness = 1; + else + if ( whiteness < 0 ) + whiteness = 0; + m_whiteness = whiteness; + return getWhiteness(); +} + +float VideoInput::getHue() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_hue; +} + +float VideoInput::setHue(float hue) +{ +// kdDebug() << k_funcinfo << " called." << endl; + if ( hue > 1 ) + hue = 1; + else + if ( hue < 0 ) + hue = 0; + m_hue = hue; + return getHue(); +} + + +bool VideoInput::getAutoBrightnessContrast() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_autobrightnesscontrast; +} + +bool VideoInput::setAutoBrightnessContrast(bool brightnesscontrast) +{ +// kdDebug() << k_funcinfo << " called." << endl; + m_autobrightnesscontrast = brightnesscontrast; + return getAutoBrightnessContrast(); +} + +bool VideoInput::getAutoColorCorrection() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_autocolorcorrection; +} + +bool VideoInput::setAutoColorCorrection(bool colorcorrection) +{ +// kdDebug() << k_funcinfo << " called." << endl; + m_autocolorcorrection = colorcorrection; + return getAutoColorCorrection(); +} + +bool VideoInput::getImageAsMirror() +{ +// kdDebug() << k_funcinfo << " called." << endl; + return m_imageasmirror; +} + +bool VideoInput::setImageAsMirror(bool imageasmirror) +{ +// kdDebug() << k_funcinfo << " called." << endl; + m_imageasmirror = imageasmirror; + return getImageAsMirror(); +} + +} + +} diff --git a/kopete/libkopete/avdevice/videoinput.h b/kopete/libkopete/avdevice/videoinput.h new file mode 100644 index 00000000..3381663e --- /dev/null +++ b/kopete/libkopete/avdevice/videoinput.h @@ -0,0 +1,89 @@ +/* + videodevice.h - Kopete Video Input Class + + Copyright (c) 2005-2006 by Cláudio da Silveira Pinheiro + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#define ENABLE_AV + +#ifndef KOPETE_AVVIDEOINPUT_H +#define KOPETE_AVVIDEOINPUT_H + +#ifdef __linux__ +#include +#undef __STRICT_ANSI__ +#endif // __linux__ +#ifndef __u64 //required by videodev.h +#define __u64 unsigned long long +#endif // __u64*/ + +#include +#include +#include +#include "kopete_export.h" + +#include "videocontrol.h" + +namespace Kopete { + +namespace AV { + +/** +@author Kopete Developers +*/ +class KOPETE_EXPORT VideoInput{ +public: + VideoInput(); + ~VideoInput(); + QString name; + int hastuner; + __u64 m_standards; + + + float getBrightness(); + float setBrightness(float brightness); + float getContrast(); + float setContrast(float contrast); + float getSaturation(); + float setSaturation(float saturation); + float getWhiteness(); + float setWhiteness(float whiteness); + float getHue(); + float setHue(float Hue); + bool getAutoBrightnessContrast(); + bool setAutoBrightnessContrast(bool brightnesscontrast); + bool getAutoColorCorrection(); + bool setAutoColorCorrection(bool colorcorrection); + bool getImageAsMirror(); + bool setImageAsMirror(bool imageasmirror); + +protected: + QValueVector m_control; + float m_brightness; + float m_contrast; + float m_saturation; + float m_whiteness; + float m_hue; + bool m_autobrightnesscontrast; + bool m_autocolorcorrection; + bool m_imageasmirror; + + +}; + +} + +} + +#endif diff --git a/kopete/libkopete/clientiface.h b/kopete/libkopete/clientiface.h new file mode 100644 index 00000000..02162189 --- /dev/null +++ b/kopete/libkopete/clientiface.h @@ -0,0 +1,56 @@ +#ifndef KDED_NETWORKSTATUS_CLIENTIFACE_H +#define KDED_NETWORKSTATUS_CLIENTIFACE_H + +#include "networkstatuscommon.h" + +#include + +class ClientIface : virtual public DCOPObject +{ +K_DCOP +k_dcop: + /** Get the set of networks that the daemon is aware of. Mostly for debug */ + virtual QStringList networks() = 0; + /** + * Get the status of the connection to the given host. + * @param host + * @return a NetworkStatus::EnumStatus representing the state of the connection to the given host + */ + virtual int status( const QString & host = QString::null ) = 0; + /** + * Request a connection to the named host, registering the application's usage of this connection + * @param host The hostname the client wants to connect to. + * @param userInitiated Indicates whether the connection is a direct result of a user action or is a background task. Used by the daemon to decide whether to create an on-demand connection. + * @return An NetworkStatus::EnumRequestResult indicating whether the request was accepted + */ + virtual int request( const QString & host, bool userInitiated ) = 0; + /** + * Indicate that a previously registered connection to the given host is no longer needed by this client + * @param host The hostname being relinquished. + */ + virtual void relinquish( const QString & host ) = 0; + /** + * Indicate that a communication failure has occured for a given host + * @param host The hostname for which the failure occurred. + * @return True indicates the caller should try again to lookup the host, as the daemon has another IP address available. + */ + virtual bool reportFailure( const QString & host ) = 0; + /** + * Utility method to check the daemon's status + */ +k_dcop_signals: + /** + * A status change occurred for the network(s) used to connect to the given host. + * @param host The host which the application has indicated it is using + * @param status The new status of the network used to reach host. + */ + void statusChange( QString host, int status ); + /** + * The network would like to shut down - any clients using this host are to finish using it immediately and call + * relinquish() when done. + * @param host The host, registered as in use by applications, which is about to be disconnected. + */ + void shutdownRequested( QString host ); +}; + +#endif diff --git a/kopete/libkopete/compat/Makefile.am b/kopete/libkopete/compat/Makefile.am new file mode 100644 index 00000000..6723bcf5 --- /dev/null +++ b/kopete/libkopete/compat/Makefile.am @@ -0,0 +1,8 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) +noinst_LTLIBRARIES = libkopetecompat.la + +libkopetecompat_la_SOURCES = kpixmapregionselectordialog.cpp kpixmapregionselectorwidget.cpp +libkopetecompat_la_LDFLAGS = -no-undefined $(all_libraries) +libkopetecompat_la_LIBADD = $(LIB_KDEUI) $(LIB_KDECORE) + diff --git a/kopete/libkopete/compat/kpixmapregionselectordialog.cpp b/kopete/libkopete/compat/kpixmapregionselectordialog.cpp new file mode 100644 index 00000000..ee9d185e --- /dev/null +++ b/kopete/libkopete/compat/kpixmapregionselectordialog.cpp @@ -0,0 +1,127 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Antonio Larrosa +#include +#include +#include +#include + +KPixmapRegionSelectorDialog::KPixmapRegionSelectorDialog(QWidget *parent, + const char *name, bool modal ) : KDialogBase(parent, name, modal, i18n("Select Region of Image"), Help|Ok|Cancel, Ok, true ) +{ + QVBox *vbox=new QVBox(this); + new QLabel(i18n("Please click and drag on the image to select the region of interest:"), vbox); + m_pixmapSelectorWidget= new KPixmapRegionSelectorWidget(vbox); + + vbox->setSpacing( KDialog::spacingHint() ); + + setMainWidget(vbox); +} + +KPixmapRegionSelectorDialog::~KPixmapRegionSelectorDialog() +{ +} + +QRect KPixmapRegionSelectorDialog::getSelectedRegion(const QPixmap &pixmap, QWidget *parent ) +{ + KPixmapRegionSelectorDialog dialog(parent); + + dialog.pixmapRegionSelectorWidget()->setPixmap(pixmap); + + QDesktopWidget desktopWidget; + QRect screen=desktopWidget.availableGeometry(); + dialog.pixmapRegionSelectorWidget()->setMaximumWidgetSize( + (int)(screen.width()*4.0/5), (int)(screen.height()*4.0/5)); + + int result = dialog.exec(); + + QRect rect; + + if ( result == QDialog::Accepted ) + rect = dialog.pixmapRegionSelectorWidget()->unzoomedSelectedRegion(); + + return rect; +} + +QRect KPixmapRegionSelectorDialog::getSelectedRegion(const QPixmap &pixmap, int aspectRatioWidth, int aspectRatioHeight, QWidget *parent ) +{ + KPixmapRegionSelectorDialog dialog(parent); + + dialog.pixmapRegionSelectorWidget()->setPixmap(pixmap); + dialog.pixmapRegionSelectorWidget()->setSelectionAspectRatio(aspectRatioWidth,aspectRatioHeight); + + QDesktopWidget desktopWidget; + QRect screen=desktopWidget.availableGeometry(); + dialog.pixmapRegionSelectorWidget()->setMaximumWidgetSize( + (int)(screen.width()*4.0/5), (int)(screen.height()*4.0/5)); + + int result = dialog.exec(); + + QRect rect; + + if ( result == QDialog::Accepted ) + rect = dialog.pixmapRegionSelectorWidget()->unzoomedSelectedRegion(); + + return rect; +} + +QImage KPixmapRegionSelectorDialog::getSelectedImage(const QPixmap &pixmap, QWidget *parent ) +{ + KPixmapRegionSelectorDialog dialog(parent); + + dialog.pixmapRegionSelectorWidget()->setPixmap(pixmap); + + QDesktopWidget desktopWidget; + QRect screen=desktopWidget.availableGeometry(); + dialog.pixmapRegionSelectorWidget()->setMaximumWidgetSize( + (int)(screen.width()*4.0/5), (int)(screen.height()*4.0/5)); + int result = dialog.exec(); + + QImage image; + + if ( result == QDialog::Accepted ) + image = dialog.pixmapRegionSelectorWidget()->selectedImage(); + + return image; +} + +QImage KPixmapRegionSelectorDialog::getSelectedImage(const QPixmap &pixmap, int aspectRatioWidth, int aspectRatioHeight, QWidget *parent ) +{ + KPixmapRegionSelectorDialog dialog(parent); + + dialog.pixmapRegionSelectorWidget()->setPixmap(pixmap); + dialog.pixmapRegionSelectorWidget()->setSelectionAspectRatio(aspectRatioWidth,aspectRatioHeight); + + QDesktopWidget desktopWidget; + QRect screen=desktopWidget.availableGeometry(); + dialog.pixmapRegionSelectorWidget()->setMaximumWidgetSize( + (int)(screen.width()*4.0/5), (int)(screen.height()*4.0/5)); + + int result = dialog.exec(); + + QImage image; + + if ( result == QDialog::Accepted ) + image = dialog.pixmapRegionSelectorWidget()->selectedImage(); + + return image; +} + diff --git a/kopete/libkopete/compat/kpixmapregionselectordialog.h b/kopete/libkopete/compat/kpixmapregionselectordialog.h new file mode 100644 index 00000000..1c15067e --- /dev/null +++ b/kopete/libkopete/compat/kpixmapregionselectordialog.h @@ -0,0 +1,107 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Antonio Larrosa + +#include +#include + +/** + * A dialog that uses a KPixmapRegionSelectorWidget to allow the user + * to select a region of an image. If you want to use special features + * like forcing the selected area to have a fixed aspect ratio, you can use + * @see pixmapRegionSelectorWidget() to get the pointer to the + * KPixmapRegionSelectorWidget object and set the desired options there. + * + * There are some convenience methods that allow to easily show a dialog + * for the user to select a region of an image, and just care about the selected + * image. + * + * @author Antonio Larrosa + * @since 3.4 + */ +class KOPETE_EXPORT KPixmapRegionSelectorDialog : public KDialogBase +{ +public: + /** + * The constructor of an empty KPixmapRegionSelectorDialog, you have to call + * later the setPixmap method of the KPixmapRegionSelectorWidget widget of + * the new object. + */ + KPixmapRegionSelectorDialog(QWidget *parent=0L, const char *name=0L, + bool modal = false ); + /** + * The destructor of the dialog + */ + ~KPixmapRegionSelectorDialog(); + + /** + * @returns the KPixmapRegionSelectorWidget widget so that additional + * parameters can be set by using it. + */ + KPixmapRegionSelectorWidget *pixmapRegionSelectorWidget() const + { return m_pixmapSelectorWidget; }; + + /** + * Creates a modal dialog, lets the user to select a region of the @p pixmap + * and returns when the dialog is closed. + * + * @returns the selected rectangle, or an invalid rectangle if the user + * pressed the Cancel button. + */ + static QRect getSelectedRegion(const QPixmap &pixmap, QWidget *parent = 0L ); + + /** + * Creates a modal dialog, lets the user to select a region of the @p pixmap + * with the same aspect ratio than @p aspectRatioWidth x @p aspectRatioHeight + * and returns when the dialog is closed. + * + * @returns the selected rectangle, or an invalid rectangle if the user + * pressed the Cancel button. + */ + static QRect getSelectedRegion(const QPixmap &pixmap, int aspectRatioWidth, int aspectRatioHeight, QWidget *parent = 0L ); + + /** + * Creates a modal dialog, lets the user to select a region of the @p pixmap + * and returns when the dialog is closed. + * + * @returns the selected image, or an invalid image if the user + * pressed the Cancel button. + */ + static QImage getSelectedImage(const QPixmap &pixmap, QWidget *parent = 0L ); + + /** + * Creates a modal dialog, lets the user to select a region of the @p pixmap + * with the same aspect ratio than @p aspectRatioWidth x @p aspectRatioHeight + * and returns when the dialog is closed. + * + * @returns the selected image, or an invalid image if the user + * pressed the Cancel button. + */ + static QImage getSelectedImage(const QPixmap &pixmap, int aspectRatioWidth, int aspectRatioHeight, QWidget *parent = 0L ); + +protected: + KPixmapRegionSelectorWidget *m_pixmapSelectorWidget; +}; + + +#endif diff --git a/kopete/libkopete/compat/kpixmapregionselectorwidget.cpp b/kopete/libkopete/compat/kpixmapregionselectorwidget.cpp new file mode 100644 index 00000000..da2be5f9 --- /dev/null +++ b/kopete/libkopete/compat/kpixmapregionselectorwidget.cpp @@ -0,0 +1,450 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Antonio Larrosa +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +KPixmapRegionSelectorWidget::KPixmapRegionSelectorWidget( QWidget *parent, + const char *name) : QWidget( parent, name) +{ + QHBoxLayout * hboxLayout=new QHBoxLayout( this ); + + hboxLayout->addStretch(); + QVBoxLayout * vboxLayout=new QVBoxLayout( hboxLayout ); + + vboxLayout->addStretch(); + m_label = new QLabel(this, "pixmapHolder"); + m_label->setBackgroundMode( Qt::NoBackground ); + m_label->installEventFilter( this ); + + vboxLayout->addWidget(m_label); + vboxLayout->addStretch(); + + hboxLayout->addStretch(); + + m_forcedAspectRatio=0; + + m_zoomFactor=1.0; +} + +KPixmapRegionSelectorWidget::~KPixmapRegionSelectorWidget() +{ +} + +void KPixmapRegionSelectorWidget::setPixmap( const QPixmap &pixmap ) +{ + Q_ASSERT(!pixmap.isNull()); //This class isn't designed to deal with null pixmaps. + m_originalPixmap = pixmap; + m_unzoomedPixmap = pixmap; + m_label->setPixmap( pixmap ); + resetSelection(); +} + +void KPixmapRegionSelectorWidget::resetSelection() +{ + m_selectedRegion = m_originalPixmap.rect(); + updatePixmap(); +} + +QRect KPixmapRegionSelectorWidget::selectedRegion() const +{ + return m_selectedRegion; +} + +void KPixmapRegionSelectorWidget::setSelectedRegion(const QRect &rect) +{ + if (!rect.isValid()) resetSelection(); + else + { + m_selectedRegion=rect; + updatePixmap(); + + QRect r=unzoomedSelectedRegion(); + } +} + +void KPixmapRegionSelectorWidget::updatePixmap() +{ + Q_ASSERT(!m_originalPixmap.isNull()); if(m_originalPixmap.isNull()) { m_label->setPixmap(m_originalPixmap); return; } + if (m_selectedRegion.width()>m_originalPixmap.width()) m_selectedRegion.setWidth( m_originalPixmap.width() ); + if (m_selectedRegion.height()>m_originalPixmap.height()) m_selectedRegion.setHeight( m_originalPixmap.height() ); + + QPainter painter; + if (m_linedPixmap.isNull()) + { + m_linedPixmap = m_originalPixmap; + + painter.begin(&m_linedPixmap); + painter.setRasterOp( Qt::XorROP ); + painter.fillRect(0,0,m_linedPixmap.width(), m_linedPixmap.height(), + QBrush( QColor(255,255,255), Qt::BDiagPattern) ); + painter.end(); + + QImage image=m_linedPixmap.convertToImage(); + image=KImageEffect::fade(image, (float)0.4, QColor(0,0,0)); + m_linedPixmap.convertFromImage(image); + } + + QPixmap pixmap = m_linedPixmap; + + painter.begin(&pixmap); + painter.drawPixmap( m_selectedRegion.topLeft(), + m_originalPixmap, m_selectedRegion ); + + painter.setPen( QColor(255,255,255) ); + painter.setRasterOp( Qt::XorROP ); + + painter.drawRect( m_selectedRegion ); + + painter.end(); + + m_label->setPixmap(pixmap); +} + + +KPopupMenu *KPixmapRegionSelectorWidget::createPopupMenu() +{ + KPopupMenu *popup=new KPopupMenu(this, "PixmapRegionSelectorPopup"); + popup->insertTitle(i18n("Image Operations")); + + KAction *action = new KAction(i18n("&Rotate Clockwise"), "rotate_cw", + 0, this, SLOT(rotateClockwise()), + popup, "rotateclockwise"); + action->plug(popup); + + action = new KAction(i18n("Rotate &Counterclockwise"), "rotate_ccw", + 0, this, SLOT(rotateCounterclockwise()), + popup, "rotatecounterclockwise"); + action->plug(popup); + +/* + I wonder if it would be appropiate to have here an "Open with..." option to + edit the image (antlarr) +*/ + return popup; +} + +void KPixmapRegionSelectorWidget::rotate(KImageEffect::RotateDirection direction) +{ + int w=m_originalPixmap.width(); + int h=m_originalPixmap.height(); + QImage img=m_unzoomedPixmap.convertToImage(); + img= KImageEffect::rotate(img, direction); + m_unzoomedPixmap.convertFromImage(img); + + img=m_originalPixmap.convertToImage(); + img= KImageEffect::rotate(img, direction); + m_originalPixmap.convertFromImage(img); + + m_linedPixmap=QPixmap(); + + if (m_forcedAspectRatio>0 && m_forcedAspectRatio!=1) + resetSelection(); + else + { + switch (direction) + { + case ( KImageEffect::Rotate90 ): + { + int x=h-m_selectedRegion.y()-m_selectedRegion.height(); + int y=m_selectedRegion.x(); + m_selectedRegion.setRect(x, y, m_selectedRegion.height(), m_selectedRegion.width() ); + updatePixmap(); + } break; + case ( KImageEffect::Rotate270 ): + { + int x=m_selectedRegion.y(); + int y=w-m_selectedRegion.x()-m_selectedRegion.width(); + m_selectedRegion.setRect(x, y, m_selectedRegion.height(), m_selectedRegion.width() ); + updatePixmap(); + } break; + default: resetSelection(); + } + } +} + +void KPixmapRegionSelectorWidget::rotateClockwise() +{ + rotate(KImageEffect::Rotate90); +} + +void KPixmapRegionSelectorWidget::rotateCounterclockwise() +{ + rotate(KImageEffect::Rotate270); +} + +bool KPixmapRegionSelectorWidget::eventFilter(QObject *obj, QEvent *ev) +{ + if ( ev->type() == QEvent::MouseButtonPress ) + { + QMouseEvent *mev= (QMouseEvent *)(ev); + //kdDebug() << QString("click at %1,%2").arg( mev->x() ).arg( mev->y() ) << endl; + + if ( mev->button() == RightButton ) + { + KPopupMenu *popup = createPopupMenu( ); + popup->exec( mev->globalPos() ); + delete popup; + return TRUE; + }; + + QCursor cursor; + + if ( m_selectedRegion.contains( mev->pos() ) + && m_selectedRegion!=m_originalPixmap.rect() ) + { + m_state=Moving; + cursor.setShape( Qt::SizeAllCursor ); + } + else + { + m_state=Resizing; + cursor.setShape( Qt::CrossCursor ); + } + QApplication::setOverrideCursor(cursor); + + m_tempFirstClick=mev->pos(); + + + return TRUE; + } + + if ( ev->type() == QEvent::MouseMove ) + { + QMouseEvent *mev= (QMouseEvent *)(ev); + + //kdDebug() << QString("move to %1,%2").arg( mev->x() ).arg( mev->y() ) << endl; + + if ( m_state == Resizing ) + { + setSelectedRegion ( + calcSelectionRectangle( m_tempFirstClick, mev->pos() ) ); + } + else if (m_state == Moving ) + { + int mevx = mev->x(); + int mevy = mev->y(); + bool mouseOutside=false; + if ( mevx < 0 ) + { + m_selectedRegion.moveBy(-m_selectedRegion.x(),0); + mouseOutside=true; + } + else if ( mevx > m_originalPixmap.width() ) + { + m_selectedRegion.moveBy(m_originalPixmap.width()-m_selectedRegion.width()-m_selectedRegion.x(),0); + mouseOutside=true; + } + if ( mevy < 0 ) + { + m_selectedRegion.moveBy(0,-m_selectedRegion.y()); + mouseOutside=true; + } + else if ( mevy > m_originalPixmap.height() ) + { + m_selectedRegion.moveBy(0,m_originalPixmap.height()-m_selectedRegion.height()-m_selectedRegion.y()); + mouseOutside=true; + } + if (mouseOutside) { updatePixmap(); return TRUE; }; + + m_selectedRegion.moveBy( mev->x()-m_tempFirstClick.x(), + mev->y()-m_tempFirstClick.y() ); + + // Check that the region has not fallen outside the image + if (m_selectedRegion.x() < 0) + m_selectedRegion.moveBy(-m_selectedRegion.x(),0); + else if (m_selectedRegion.right() > m_originalPixmap.width()) + m_selectedRegion.moveBy(-(m_selectedRegion.right()-m_originalPixmap.width()),0); + + if (m_selectedRegion.y() < 0) + m_selectedRegion.moveBy(0,-m_selectedRegion.y()); + else if (m_selectedRegion.bottom() > m_originalPixmap.height()) + m_selectedRegion.moveBy(0,-(m_selectedRegion.bottom()-m_originalPixmap.height())); + + m_tempFirstClick=mev->pos(); + updatePixmap(); + } + return TRUE; + } + + if ( ev->type() == QEvent::MouseButtonRelease ) + { + QMouseEvent *mev= (QMouseEvent *)(ev); + + if ( m_state == Resizing && mev->pos() == m_tempFirstClick) + resetSelection(); + + m_state=None; + QApplication::restoreOverrideCursor(); + + return TRUE; + } + + QWidget::eventFilter(obj, ev); + return FALSE; +} + +QRect KPixmapRegionSelectorWidget::calcSelectionRectangle( const QPoint & startPoint, const QPoint & _endPoint ) +{ + QPoint endPoint = _endPoint; + if ( endPoint.x() < 0 ) endPoint.setX(0); + else if ( endPoint.x() > m_originalPixmap.width() ) endPoint.setX(m_originalPixmap.width()); + if ( endPoint.y() < 0 ) endPoint.setY(0); + else if ( endPoint.y() > m_originalPixmap.height() ) endPoint.setY(m_originalPixmap.height()); + int w=abs(startPoint.x()-endPoint.x()); + int h=abs(startPoint.y()-endPoint.y()); + + if (m_forcedAspectRatio>0) + { + double aspectRatio=w/double(h); + + if (aspectRatio>m_forcedAspectRatio) + h=(int)(w/m_forcedAspectRatio); + else + w=(int)(h*m_forcedAspectRatio); + } + + int x,y; + if ( startPoint.x() < endPoint.x() ) + x=startPoint.x(); + else + x=startPoint.x()-w; + if ( startPoint.y() < endPoint.y() ) + y=startPoint.y(); + else + y=startPoint.y()-h; + + if (x<0) + { + w+=x; + x=0; + h=(int)(w/m_forcedAspectRatio); + + if ( startPoint.y() > endPoint.y() ) + y=startPoint.y()-h; + } + else if (x+w>m_originalPixmap.width()) + { + w=m_originalPixmap.width()-x; + h=(int)(w/m_forcedAspectRatio); + + if ( startPoint.y() > endPoint.y() ) + y=startPoint.y()-h; + } + if (y<0) + { + h+=y; + y=0; + w=(int)(h*m_forcedAspectRatio); + + if ( startPoint.x() > endPoint.x() ) + x=startPoint.x()-w; + } + else if (y+h>m_originalPixmap.height()) + { + h=m_originalPixmap.height()-y; + w=(int)(h*m_forcedAspectRatio); + + if ( startPoint.x() > endPoint.x() ) + x=startPoint.x()-w; + } + + return QRect(x,y,w,h); +} + +QRect KPixmapRegionSelectorWidget::unzoomedSelectedRegion() const +{ + return QRect((int)(m_selectedRegion.x()/m_zoomFactor), + (int)(m_selectedRegion.y()/m_zoomFactor), + (int)(m_selectedRegion.width()/m_zoomFactor), + (int)(m_selectedRegion.height()/m_zoomFactor)); +} + +QImage KPixmapRegionSelectorWidget::selectedImage() const +{ + QImage origImage=m_unzoomedPixmap.convertToImage(); + return origImage.copy(unzoomedSelectedRegion()); +} + +void KPixmapRegionSelectorWidget::setSelectionAspectRatio(int width, int height) +{ + m_forcedAspectRatio=width/double(height); +} + +void KPixmapRegionSelectorWidget::setFreeSelectionAspectRatio() +{ + m_forcedAspectRatio=0; +} + +void KPixmapRegionSelectorWidget::setMaximumWidgetSize(int width, int height) +{ + m_maxWidth=width; + m_maxHeight=height; + + m_originalPixmap=m_unzoomedPixmap; + if (m_selectedRegion == m_originalPixmap.rect()) m_selectedRegion=QRect(); + +// kdDebug() << QString(" original Pixmap :") << m_originalPixmap.rect() << endl; +// kdDebug() << QString(" unzoomed Pixmap : %1 x %2 ").arg(m_unzoomedPixmap.width()).arg(m_unzoomedPixmap.height()) << endl; + + if ( !m_originalPixmap.isNull() && + ( m_originalPixmap.width() > m_maxWidth || + m_originalPixmap.height() > m_maxHeight ) ) + { + /* We have to resize the pixmap to get it complete on the screen */ + QImage image=m_originalPixmap.convertToImage(); + m_originalPixmap.convertFromImage( image.smoothScale( width, height, QImage::ScaleMin ) ); + double oldZoomFactor = m_zoomFactor; + m_zoomFactor=m_originalPixmap.width()/(double)m_unzoomedPixmap.width(); + + if (m_selectedRegion.isValid()) + { + m_selectedRegion= + QRect((int)(m_selectedRegion.x()*m_zoomFactor/oldZoomFactor), + (int)(m_selectedRegion.y()*m_zoomFactor/oldZoomFactor), + (int)(m_selectedRegion.width()*m_zoomFactor/oldZoomFactor), + (int)(m_selectedRegion.height()*m_zoomFactor/oldZoomFactor) ); + } + } + + if (!m_selectedRegion.isValid()) m_selectedRegion = m_originalPixmap.rect(); + + m_linedPixmap=QPixmap(); + updatePixmap(); + resize(m_label->width(), m_label->height()); +} + +#include "kpixmapregionselectorwidget.moc" diff --git a/kopete/libkopete/compat/kpixmapregionselectorwidget.h b/kopete/libkopete/compat/kpixmapregionselectorwidget.h new file mode 100644 index 00000000..a4a9cfcf --- /dev/null +++ b/kopete/libkopete/compat/kpixmapregionselectorwidget.h @@ -0,0 +1,170 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Antonio Larrosa +#include +#include +#include +#include + +class KPopupMenu; + +#include "kopete_export.h" + +/** + * KPixmapRegionSelectorWidget is a widget that shows a picture and provides the + * user with a friendly way to select a rectangular subregion of the pixmap. + * + * NOTE: There are two copies of this .h and the .cpp file, with subtle differences. + * One copy is in kdelibs/kdeui, and the other copy is in kdepim/libkdepim + * This is because kdepim has to remain backwards compatible. Any changes + * to either file should be made to the other. + * + * @author Antonio Larrosa + * @since 3.4 + */ +class KOPETE_EXPORT KPixmapRegionSelectorWidget : public QWidget +{ + Q_OBJECT +public: + /** + * Constructor for a KPixmapRegionSelectorWidget. + */ + KPixmapRegionSelectorWidget( QWidget *parent = 0L, const char *name=0L); + + /** + * Destructor for a KPixmapRegionSelectorWidget + */ + ~KPixmapRegionSelectorWidget(); + + /** + * Sets the pixmap which will be shown for the user to select a region from. + * @param pixmap The pixmap. Must be non-null. + * + */ + void setPixmap( const QPixmap &pixmap ); + + /** + * @return the original whole pixmap that we're using in this widget as the + * pixmap the user is selecting a region from. + */ + QPixmap pixmap() const { return m_unzoomedPixmap; }; + + /** + * Sets the selected region to be @p rect (in zoomed pixmap coordinates) + */ + void setSelectedRegion(const QRect &rect); + + /** + * Returns the selected region ( in zoomed pixmap coordinates ) + */ + QRect selectedRegion() const; + + /** + * Returns the selected region ( in unzoomed, original pixmap coordinates ) + */ + QRect unzoomedSelectedRegion() const; + + /** + * Resets the selection to use the whole image + */ + void resetSelection(); + + /** + * @returns a QImage object with just the region the user selected from the + * image + */ + QImage selectedImage() const; + + /** + * Sets the aspect ration that the selected subimage should have. The way to + * select it, is specifying an example valid @p width and @p height. + * @see setFreeSelectionAspectRatio() + */ + void setSelectionAspectRatio(int width, int height); + + /** + * Allows the user to do a selection which has any aspect ratio. This is + * the default. + * @see setSelectionAspectRatio() + */ + void setFreeSelectionAspectRatio(); + + /** + * Sets the maximum size for the widget. If the image is larger than this + * (either horizontally or vertically), it's scaled to adjust to the maximum + * size (preserving the aspect ratio) + */ + void setMaximumWidgetSize( int width, int height ); + + /** + * Rotates the image as specified by the @p direction parameter, also tries + * to rotate the selected region so that it doesn't change, as long as the + * forced aspect ratio setting is respected, in other case, the selected region + * is resetted. + */ + void rotate(KImageEffect::RotateDirection direction); + +public slots: + /** + * Rotates the current image 90º clockwise + */ + void rotateClockwise(); + /** + * Rotates the current image 90º counterclockwise + */ + void rotateCounterclockwise(); + +protected: + /** + * Creates a KPopupMenu with the menu that appears when clicking with the right button on the label + */ + virtual KPopupMenu *createPopupMenu(); + +private: + bool eventFilter(QObject *obj, QEvent *ev); + + /** + * Recalculates the pixmap that is shown based on the current selected area, + * the original image, etc. + */ + void updatePixmap(); + + QRect calcSelectionRectangle( const QPoint &startPoint, const QPoint & endPoint ); + + enum CursorState { None=0, Resizing, Moving }; + CursorState m_state; + + QPixmap m_unzoomedPixmap; + QPixmap m_originalPixmap; + QPixmap m_linedPixmap; + QRect m_selectedRegion; + QLabel *m_label; + + QPoint m_tempFirstClick; + double m_forcedAspectRatio; + + int m_maxWidth, m_maxHeight; + double m_zoomFactor; +}; + +#endif + diff --git a/kopete/libkopete/configure.in.in b/kopete/libkopete/configure.in.in new file mode 100644 index 00000000..f9d1fb76 --- /dev/null +++ b/kopete/libkopete/configure.in.in @@ -0,0 +1,32 @@ +# -- Check for XScreenSaver ----------------------------------------- +AC_CHECK_HEADERS(tgmath.h)xss_save_ldflags="$LDFLAGS" +LDFLAGS="$X_LDFLAGS" + +LIB_XSS= + +KDE_CHECK_HEADER(X11/extensions/scrnsaver.h, + [ + AC_CHECK_LIB(Xext,XScreenSaverQueryInfo, + [ + AC_DEFINE(HAVE_XSCREENSAVER, 1, [Define if you have the XScreenSaver extension]) + LIB_XSS="-lXext" + ], + [ + ld_shared_flag= + KDE_CHECK_COMPILER_FLAG(shared, [ld_shared_flag="-shared"]) + AC_CHECK_LIB(Xss,XScreenSaverQueryInfo, + [ + AC_DEFINE(HAVE_XSCREENSAVER, 1, [Define if you have the XScreenSaver extension]) + LIB_XSS="-lXss" + ], + [], + [ $ld_shared_flag $X_PRE_LIBS -lXext -lX11 $X_EXTRA_LIBS ]) + ], + [ $X_PRE_LIBS -lX11 $X_EXTRA_LIBS ]) + ], [], + [ + #include + ] ) + +AC_SUBST(LIB_XSS) +LDFLAGS="$xss_save_ldflags" diff --git a/kopete/libkopete/connectionmanager.cpp b/kopete/libkopete/connectionmanager.cpp new file mode 100644 index 00000000..b2dd7825 --- /dev/null +++ b/kopete/libkopete/connectionmanager.cpp @@ -0,0 +1,153 @@ +#include +#include +#include +#include +#include + +#include "clientiface_stub.h" +#include "networkstatuscommon.h" + +#include "connectionmanager.h" + +// ConnectionManager's private parts +class ConnectionManagerPrivate +{ + public: + // this holds the currently active state + ConnectionManager::State m_state; + ClientIface_stub * m_stub; + bool m_userInitiatedOnly; +}; + +// Connection manager itself +ConnectionManager::ConnectionManager( QObject * parent, const char * name ) : DCOPObject( "ConnectionManager" ),QObject( parent, name ) +{ + d = new ConnectionManagerPrivate; + + d->m_stub = new ClientIface_stub( kapp->dcopClient(), "kded", "networkstatus" ); + + connectDCOPSignal( "kded", "networkstatus", "statusChange(QString,int)", "slotStatusChanged(QString,int)", false ); + d->m_userInitiatedOnly = false; + initialise(); +} + +ConnectionManager *ConnectionManager::s_self = 0L; + +ConnectionManager *ConnectionManager::self() +{ + static KStaticDeleter deleter; + if(!s_self) + deleter.setObject( s_self, new ConnectionManager( 0, "connection_manager" ) ); + return s_self; +} + +void ConnectionManager::initialise() +{ + // determine initial state and set the state object accordingly. + d->m_state = Inactive; + updateStatus(); +} + +void ConnectionManager::updateStatus() +{ + NetworkStatus::EnumStatus daemonStatus = (NetworkStatus::EnumStatus)d->m_stub->status( QString::null ); + kdDebug() << k_funcinfo << endl; + switch ( daemonStatus ) + { + case NetworkStatus::Offline: + case NetworkStatus::OfflineFailed: + case NetworkStatus::OfflineDisconnected: + case NetworkStatus::ShuttingDown: + if ( d->m_state == Online ) + { + kdDebug() << "STATE IS PENDING" << endl; + d->m_state = Pending; + } + else + { + kdDebug() << "STATE IS OFFLINE" << endl; + d->m_state = Offline; + } + break; + case NetworkStatus::Establishing: + case NetworkStatus::Online: + kdDebug() << "STATE IS ONLINE" << endl; + d->m_state = Online; + break; + case NetworkStatus::NoNetworks: + case NetworkStatus::Unreachable: + kdDebug() << "STATE IS INACTIVE" << endl; + d->m_state = Inactive; + break; + } +} + +ConnectionManager::~ConnectionManager() +{ + delete d; +} + +NetworkStatus::EnumStatus ConnectionManager::status( const QString & host ) +{ + // need also to check that the daemon hasn't died + updateStatus(); + if ( d->m_state == Pending ) + return NetworkStatus::Offline; + if ( d->m_state == Online ) + return NetworkStatus::Online; + if ( d->m_state == Offline ) + return NetworkStatus::Offline; + return NetworkStatus::NoNetworks; +} + +NetworkStatus::EnumRequestResult ConnectionManager::requestConnection( QWidget * mainWidget, const QString & host, bool userInitiated ) +{ + kdDebug() << k_funcinfo << endl; + NetworkStatus::EnumRequestResult result; + // if offline and the user has previously indicated they didn't want any new connections, suppress it + if ( d->m_state == Offline && !userInitiated && d->m_userInitiatedOnly ) + result = NetworkStatus::UserRefused; + // if offline, ask the user whether this connection should be allowed + if ( d->m_state == Offline ) + { + if ( askToConnect( mainWidget ) ) + //result = NetworkStatus::Connected; + result = (NetworkStatus::EnumRequestResult)d->m_stub->request( host, userInitiated ); + else + result = NetworkStatus::UserRefused; + } + // otherwise, just ask for the connection + else + result = (NetworkStatus::EnumRequestResult)d->m_stub->request( host, userInitiated ); + + return result; +} + +void ConnectionManager::relinquishConnection( const QString & host ) +{ + d->m_stub->relinquish( host ); +} + +void ConnectionManager::slotStatusChanged( QString host, int status ) +{ + kdDebug() << k_funcinfo << endl; + updateStatus(); + // reset user initiated only flag if we are now online + if ( d->m_state == Online ) + d->m_userInitiatedOnly = false; + + emit statusChanged( host, (NetworkStatus::EnumStatus)status ); +} + +bool ConnectionManager::askToConnect( QWidget * mainWidget ) +{ + i18n( "A network connection was disconnected. The application is now in offline mode. Do you want the application to resume network operations when the network is available again?" ); + i18n( "This application is currently in offline mode. Do you want to connect?" ); + return ( KMessageBox::questionYesNo( mainWidget, + i18n("This application is currently in offline mode. Do you want to connect in order to carry out this operation?"), + i18n("Leave Offline Mode?"), + i18n("Connect"), i18n("Stay Offline"), + QString::fromLatin1("OfflineModeAlwaysGoOnline") ) == KMessageBox::Yes ); +} + +#include "connectionmanager.moc" diff --git a/kopete/libkopete/connectionmanager.h b/kopete/libkopete/connectionmanager.h new file mode 100644 index 00000000..b78df8d4 --- /dev/null +++ b/kopete/libkopete/connectionmanager.h @@ -0,0 +1,58 @@ +/* + connectionmanager.h - Provides the client side interface to the kde networkstatus daemon + + Copyright (c) 2004 by Will Stephenson + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KDE_CONNECTION_MANAGER_H +#define KDE_CONNECTION_MANAGER_H + +#include + +#include "kopete_export.h" +#include "networkstatuscommon.h" + +class ConnectionManagerPrivate; + +class KOPETE_EXPORT ConnectionManager : public QObject, virtual public DCOPObject +{ + Q_OBJECT + K_DCOP + public: + static ConnectionManager* self(); + enum State { Inactive, Online, Offline, Pending }; + virtual ~ConnectionManager(); + NetworkStatus::EnumStatus status( const QString & host ); + // check if a hostname is available. Ask user if offline. Request host + NetworkStatus::EnumRequestResult requestConnection( QWidget* mainWidget, const QString & host, bool userInitiated ); + // method to relinquish a connection + void relinquishConnection( const QString & host ); + signals: + // signal that the network for a hostname is up/down + void statusChanged( const QString & host, NetworkStatus::EnumStatus status ); + protected: + // sets up internal state + void initialise(); + // reread the desktop status from the daemon and update internal state + void updateStatus(); + // ask if the user would like to reconnect + bool askToConnect( QWidget * mainWidget ); + k_dcop: + void slotStatusChanged( QString host, int status ); + private: + ConnectionManager( QObject *parent, const char * name ); + ConnectionManagerPrivate *d; + static ConnectionManager * s_self; +}; + +#endif + diff --git a/kopete/libkopete/kabcpersistence.cpp b/kopete/libkopete/kabcpersistence.cpp new file mode 100644 index 00000000..527a99a4 --- /dev/null +++ b/kopete/libkopete/kabcpersistence.cpp @@ -0,0 +1,452 @@ +/* + addressbooklink.cpp - Manages operations involving the KDE Address Book + + Copyright (c) 2005 Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include + +#include +#include +#include +#include + +// UI related includes used for importing from KABC +#include +#include +#include +#include "accountselector.h" +#include "kopeteuiglobal.h" + +#include + +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetecontact.h" +#include "kopetemetacontact.h" +#include "kopetepluginmanager.h" +#include "kopeteprotocol.h" + +#include "kabcpersistence.h" + +namespace Kopete +{ + +/** + * utility function to merge two QStrings containing individual elements separated by 0xE000 + */ +static QString unionContents( QString arg1, QString arg2 ) +{ + QChar separator( 0xE000 ); + QStringList outList = QStringList::split( separator, arg1 ); + QStringList arg2List = QStringList::split( separator, arg2 ); + for ( QStringList::iterator it = arg2List.begin(); it != arg2List.end(); ++it ) + if ( !outList.contains( *it ) ) + outList.append( *it ); + QString out = outList.join( separator ); + return out; +} + +KABCPersistence::KABCPersistence( QObject * parent, const char * name ) : QObject( parent, name ) +{ + s_pendingResources.setAutoDelete( false ); +} + +KABCPersistence::~KABCPersistence() +{ +} + +KABCPersistence *KABCPersistence::s_self = 0L; + +bool KABCPersistence::s_addrBookWritePending = false; + +QPtrList KABCPersistence::s_pendingResources; + +KABC::AddressBook* KABCPersistence::s_addressBook = 0; + +KABCPersistence *KABCPersistence::self() +{ + static KStaticDeleter deleter; + if(!s_self) + deleter.setObject( s_self, new KABCPersistence() ); + return s_self; +} + +KABC::AddressBook* KABCPersistence::addressBook() +{ + if ( s_addressBook == 0L ) + { + s_addressBook = KABC::StdAddressBook::self(); + KABC::StdAddressBook::setAutomaticSave( false ); + } + return s_addressBook; +} + +void KABCPersistence::write( MetaContact * mc ) +{ + // Save any changes in each contact's addressBookFields to KABC + KABC::AddressBook* ab = addressBook(); + + kdDebug( 14010 ) << k_funcinfo << "looking up Addressee for " << mc->displayName() << "..." << endl; + // Look up the address book entry + KABC::Addressee theAddressee = ab->findByUid( mc->metaContactId() ); + // Check that if addressee is not deleted or if the link is spurious + // (inherited from Kopete < 0.8, where all metacontacts had random ids) + if ( theAddressee.isEmpty() ) + { + // not found in currently enabled addressbooks - may be in a disabled resource... + return; + } + else + { + // collate the instant messaging data to be inserted into the address book + QMap addressMap; + QPtrList contacts = mc->contacts(); + QPtrListIterator cIt( contacts ); + while ( Contact * c = cIt.current() ) + { + QStringList addresses = addressMap[ c->protocol()->addressBookIndexField() ]; + addresses.append( c->contactId() ); + addressMap.insert( c->protocol()->addressBookIndexField(), addresses ); + ++cIt; + } + + // insert a custom field for each protocol + QMap::ConstIterator it = addressMap.begin(); + for ( ; it != addressMap.end(); ++it ) + { + // read existing data for this key + QString currentCustomForProtocol = theAddressee.custom( it.key(), QString::fromLatin1( "All" ) ); + // merge without duplicating + QString toWrite = unionContents( currentCustomForProtocol, it.data().join( QChar( 0xE000 ) ) ); + // Note if nothing ends up in the KABC data, this is because insertCustom does nothing if any param is empty. + kdDebug( 14010 ) << k_funcinfo << "Writing: " << it.key() << ", " << "All" << ", " << toWrite << endl; + theAddressee.insertCustom( it.key(), QString::fromLatin1( "All" ), toWrite ); + QString check = theAddressee.custom( it.key(), QString::fromLatin1( "All" ) ); + } + ab->insertAddressee( theAddressee ); + //kdDebug( 14010 ) << k_funcinfo << "dumping addressbook before write " << endl; + //dumpAB(); + writeAddressBook( theAddressee.resource() ); + //theAddressee.dump(); + } + +/* // Wipe out the existing addressBook entries + d->addressBook.clear(); + // This causes each Kopete::Protocol subclass to serialise its contacts' data into the metacontact's plugin data and address book data + emit aboutToSave(this); + + kdDebug( 14010 ) << k_funcinfo << "...FOUND ONE!" << endl; + // Store address book fields + QMap >::ConstIterator appIt = d->addressBook.begin(); + for( ; appIt != d->addressBook.end(); ++appIt ) + { + QMap::ConstIterator addrIt = appIt.data().begin(); + for( ; addrIt != appIt.data().end(); ++addrIt ) + { + // read existing data for this key + QString currentCustom = theAddressee.custom( appIt.key(), addrIt.key() ); + // merge without duplicating + QString toWrite = unionContents( currentCustom, addrIt.data() ); + // write the result + // Note if nothing ends up in the KABC data, this is because insertCustom does nothing if any param is empty. + kdDebug( 14010 ) << k_funcinfo << "Writing: " << appIt.key() << ", " << addrIt.key() << ", " << toWrite << endl; + theAddressee.insertCustom( appIt.key(), addrIt.key(), toWrite ); + } + } + ab->insertAddressee( theAddressee ); + writeAddressBook(); + }*/ +} + +void KABCPersistence::writeAddressBook( const KABC::Resource * res) +{ + if ( !s_pendingResources.containsRef( res ) ) + s_pendingResources.append( res ); + if ( !s_addrBookWritePending ) + { + s_addrBookWritePending = true; + QTimer::singleShot( 2000, this, SLOT( slotWriteAddressBook() ) ); + } +} + +void KABCPersistence::slotWriteAddressBook() +{ + //kdDebug( 14010 ) << k_funcinfo << endl; + KABC::AddressBook* ab = addressBook(); + QPtrListIterator it( s_pendingResources ); + for ( ; it.current(); ++it ) + { + //kdDebug( 14010 ) << "Writing resource " << it.current()->resourceName() << endl; + KABC::Ticket *ticket = ab->requestSaveTicket( it.current() ); + if ( !ticket ) + kdWarning( 14010 ) << "WARNING: Resource is locked by other application!" << endl; + else + { + if ( !ab->save( ticket ) ) + { + kdWarning( 14010 ) << "ERROR: Saving failed!" << endl; + ab->releaseSaveTicket( ticket ); + } + } + //kdDebug( 14010 ) << "Finished writing KABC" << endl; + } + s_pendingResources.clear(); + s_addrBookWritePending = false; +} + +void KABCPersistence::removeKABC( MetaContact *) +{ +/* // remove any data this KMC has written to the KDE address book + // Save any changes in each contact's addressBookFields to KABC + KABC::AddressBook* ab = addressBook(); + + // Wipe out the existing addressBook entries + d->addressBook.clear(); + // This causes each Kopete::Protocol subclass to serialise its contacts' data into the metacontact's plugin data and address book data + emit aboutToSave(this); + + // If the metacontact is linked to a kabc entry + if ( !d->metaContactId.isEmpty() ) + { + //kdDebug( 14010 ) << k_funcinfo << "looking up Addressee for " << displayName() << "..." << endl; + // Look up the address book entry + KABC::Addressee theAddressee = ab->findByUid( metaContactId() ); + + if ( theAddressee.isEmpty() ) + { + // remove the link + //kdDebug( 14010 ) << k_funcinfo << "...not found." << endl; + d->metaContactId=QString::null; + } + else + { + //kdDebug( 14010 ) << k_funcinfo << "...FOUND ONE!" << endl; + // Remove address book fields + QMap >::ConstIterator appIt = d->addressBook.begin(); + for( ; appIt != d->addressBook.end(); ++appIt ) + { + QMap::ConstIterator addrIt = appIt.data().begin(); + for( ; addrIt != appIt.data().end(); ++addrIt ) + { + // FIXME: This assumes Kopete is the only app writing these fields + kdDebug( 14010 ) << k_funcinfo << "Removing: " << appIt.key() << ", " << addrIt.key() << endl; + theAddressee.removeCustom( appIt.key(), addrIt.key() ); + } + } + ab->insertAddressee( theAddressee ); + + writeAddressBook(); + } + } +// kdDebug(14010) << k_funcinfo << kdBacktrace() <findByUid( mc->metaContactId() ); + + if ( !addr.isEmpty() ) // if we are associated with KABC + { +// load the set of addresses from KABC + QStringList customs = addr.customs(); + + QStringList::ConstIterator it; + for ( it = customs.begin(); it != customs.end(); ++it ) + { + QString app, name, value; + splitField( *it, app, name, value ); + kdDebug( 14010 ) << "app=" << app << " name=" << name << " value=" << value << endl; + + if ( app.startsWith( QString::fromLatin1( "messaging/" ) ) ) + { + if ( name == QString::fromLatin1( "All" ) ) + { + kdDebug( 14010 ) << " syncing \"" << app << ":" << name << " with contactlist " << endl; + // Get the protocol name from the custom field + // by chopping the 'messaging/' prefix from the custom field app name + QString protocolName = app.right( app.length() - 10 ); + // munge Jabber hack + if ( protocolName == QString::fromLatin1( "xmpp" ) ) + protocolName = QString::fromLatin1( "jabber" ); + + // Check Kopete supports it + Protocol * proto = dynamic_cast( PluginManager::self()->loadPlugin( QString::fromLatin1( "kopete_" ) + protocolName ) ); + if ( !proto ) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "\"%1\" is not supported by Kopete." ).arg( protocolName ), + i18n( "Could Not Sync with KDE Address Book" ) ); + continue; + } + + // See if we need to add each contact in this protocol + QStringList addresses = QStringList::split( QChar( 0xE000 ), value ); + QStringList::iterator end = addresses.end(); + for ( QStringList::iterator it = addresses.begin(); it != end; ++it ) + { + // check whether each one is present in Kopete + // Is it in the contact list? + // First discard anything after an 0xE120, this is used by IRC to separate nick and server group name, but + // IRC doesn't support this properly yet, so the user will have to select an appropriate account manually + int separatorPos = (*it).find( QChar( 0xE120 ) ); + if ( separatorPos != -1 ) + *it = (*it).left( separatorPos ); + + QDict accounts = Kopete::AccountManager::self()->accounts( proto ); + QDictIterator acs(accounts); + Kopete::MetaContact *otherMc = 0; + for ( acs.toFirst(); acs.current(); ++acs ) + { + Kopete::Contact *c= acs.current()->contacts()[*it]; + if(c) + { + otherMc=c->metaContact(); + break; + } + } + + if ( otherMc ) // Is it in another metacontact? + { + // Is it already in this metacontact? If so, we needn't do anything + if ( otherMc == mc ) + { + kdDebug( 14010 ) << *it << " already a child of this metacontact." << endl; + continue; + } + kdDebug( 14010 ) << *it << " already exists in OTHER metacontact, move here?" << endl; + // find the Kopete::Contact and attempt to move it to this metacontact. + otherMc->findContact( proto->pluginId(), QString::null, *it )->setMetaContact( mc ); + } + else + { + // if not, prompt to add it + kdDebug( 14010 ) << proto->pluginId() << "://" << *it << " was not found in the contact list. Prompting to add..." << endl; + if ( KMessageBox::Yes == KMessageBox::questionYesNo( Kopete::UI::Global::mainWidget(), + i18n( "An address was added to this contact by another application.
    Would you like to use it in Kopete?
    Protocol: %1
    Address: %2
    " ).arg( proto->displayName() ).arg( *it ), i18n( "Import Address From Address Book" ), i18n("Use"), i18n("Do Not Use"), QString::fromLatin1( "ImportFromKABC" ) ) ) + { + // Check the accounts for this protocol are all connected + // Most protocols do not allow you to add contacts while offline + // Would be better to have a virtual bool Kopete::Account::readyToAddContact() + bool allAccountsConnected = true; + for ( acs.toFirst(); acs.current(); ++acs ) + if ( !acs.current()->isConnected() ) + { allAccountsConnected = false; + break; + } + if ( !allAccountsConnected ) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "One or more of your accounts using %1 are offline. Most systems have to be connected to add contacts. Please connect these accounts and try again." ).arg( protocolName ), + i18n( "Not Connected" ) ); + continue; + } + + // we have got a contact to add, our accounts are connected, so add it. + // Do we need to choose an account + Kopete::Account *chosen = 0; + if ( accounts.count() > 1 ) + { // if we have >1 account in this protocol, prompt for the protocol. + KDialogBase *chooser = new KDialogBase(0, "chooser", true, + i18n("Choose Account"), KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Ok, false); + AccountSelector *accSelector = new AccountSelector(proto, chooser, + "accSelector"); + chooser->setMainWidget(accSelector); + if ( chooser->exec() == QDialog::Rejected ) + continue; + chosen = accSelector->selectedItem(); + + delete chooser; + } + else if ( accounts.isEmpty() ) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "You do not have an account configured for %1 yet. Please create an account, connect it, and try again." ).arg( protocolName ), + i18n( "No Account Found" ) ); + continue; + } + else // if we have 1 account in this protocol, choose it + { + chosen = acs.toFirst(); + } + + // add the contact to the chosen account + if ( chosen ) + { + kdDebug( 14010 ) << "Adding " << *it << " to " << chosen->accountId() << endl; + if ( chosen->addContact( *it, mc ) ) + contactAdded = true; + else + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "It was not possible to add the contact." ), + i18n( "Could Not Add Contact") ) ; + } + } + else + kdDebug( 14010 ) << " user declined to add " << *it << " to contactlist " << endl; + } + } + kdDebug( 14010 ) << " all " << addresses.count() << " contacts in " << proto->pluginId() << " checked " << endl; + } + else + kdDebug( 14010 ) << "not interested in name=" << name << endl; + + } + else + kdDebug( 14010 ) << "not interested in app=" << app << endl; + } + } + return contactAdded; + return false; +} + +// FIXME: Remove when IM address API is in KABC (KDE 4) +void KABCPersistence::splitField( const QString &str, QString &app, QString &name, QString &value ) +{ + int colon = str.find( ':' ); + if ( colon != -1 ) { + QString tmp = str.left( colon ); + value = str.mid( colon + 1 ); + + int dash = tmp.find( '-' ); + if ( dash != -1 ) { + app = tmp.left( dash ); + name = tmp.mid( dash + 1 ); + } + } +} + +void KABCPersistence::dumpAB() +{ + KABC::AddressBook * ab = addressBook(); + kdDebug( 14010 ) << k_funcinfo << " DUMPING ADDRESSBOOK" << endl; + KABC::AddressBook::ConstIterator dumpit = ab->begin(); + for ( ; dumpit != ab->end(); ++dumpit ) + { + (*dumpit).dump(); + } +} + + +} // end namespace Kopete + + // dump addressbook contents + +#include "kabcpersistence.moc" diff --git a/kopete/libkopete/kabcpersistence.h b/kopete/libkopete/kabcpersistence.h new file mode 100644 index 00000000..fa02fb64 --- /dev/null +++ b/kopete/libkopete/kabcpersistence.h @@ -0,0 +1,107 @@ +/* + addressbooklink.h - Manages operations involving the KDE Address Book + + Copyright (c) 2005 Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEADDRESSBOOKLINK_H +#define KOPETEADDRESSBOOKLINK_H + +#include "kopete_export.h" + +// Goal is to have all the address book modifying code in one place +// Currently in +// *) Add Contact Wizard +// *) KopeteMetaContact +// *) KopeteAddrBookExport +// *) KABC Export Wizard - TODO - think about sequence of events when adding addressees AND writing their IM data. - Extra save should be unnecessary because we are sharing a kabc instance +// *) Select addressbook entry + +namespace KABC +{ + class AddressBook; + class Resource; +} + +namespace Kopete +{ + + class MetaContact; + +class KOPETE_EXPORT KABCPersistence : public QObject +{ + Q_OBJECT + public: + /** + * \brief Retrieve the instance of AccountManager. + * + * The account manager is a singleton class of which only a single + * instance will exist. If no manager exists yet this function will + * create one for you. + * + * \return the instance of the AccountManager + */ + static KABCPersistence* self(); + + KABCPersistence( QObject * parent = 0, const char * name = 0 ); + ~KABCPersistence(); + /** + * @brief Access Kopete's KDE address book instance + */ + static KABC::AddressBook* addressBook(); + /** + * @brief Change the KABC data associated with this metacontact + * + * The KABC exposed data changed, so change it in KABC. + * Replaces Kopete::MetaContact::updateKABC() + */ + void write( MetaContact * mc ); + + /** + * @brief Remove any KABC data for this meta contact + */ + void removeKABC( MetaContact * mc ); + + /** + * Check for any new addresses added to this contact's KABC entry + * and prompt if they should be added in Kopete too. + * @return whether any contacts were added from KABC. + */ + bool syncWithKABC( MetaContact * mc ); + + /** + * Request an address book write, will be delayed to bundle any others happening around the same time + */ + void writeAddressBook( const KABC::Resource * res ); + protected: + + static void splitField( const QString &str, QString &app, QString &name, QString &value ); + + void dumpAB(); + protected slots: + /** + * Perform a delayed address book write + */ + void slotWriteAddressBook(); + private: + static KABCPersistence * s_self; + static KABC::AddressBook* s_addressBook; + static bool s_addrBookWritePending; + static QPtrList s_pendingResources; +}; + +} // end namespace Kopete + +#endif + diff --git a/kopete/libkopete/kautoconfig.cpp b/kopete/libkopete/kautoconfig.cpp new file mode 100644 index 00000000..497b6cd5 --- /dev/null +++ b/kopete/libkopete/kautoconfig.cpp @@ -0,0 +1,450 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 2003 Benjamin C Meyer (ben+kdelibs at meyerhome dot net) + * + * This library 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 library 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 library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "kautoconfig.h" + +#include +#include +#include +#include +#include +#include + +/** + * Macro function to warn developers when they are making calls + * that can never return anything of value + */ +#ifndef NDEBUG +#include "kdebug.h" +#define functionCallPreOrderCheck(functionName, returnValue) \ + if(!d->retrievedSettings){ \ + kdDebug(180) << "KAutoConfig::"functionName"() was called before " \ + "KAutoConfig::retrieveSettings(). This should NEVER happen because " \ + "it will do nothing. Please Fix." << endl; \ + return returnValue; \ + } + +#define functionCallPostOrderCheck(functionName, returnValue) \ + if(d->retrievedSettings){ \ + kdDebug(180) << "KAutoConfig::"functionName"() was called after " \ + "KAutoConfig::retrieveSettings(). This should NEVER happen because " \ + "it will do nothing. Please Fix." << endl; \ + return returnValue; \ + } +#else +#define functionCallPostOrderCheck(functionName, returnValue) +#define functionCallPreOrderCheck(functionName, returnValue) +#endif + +class KAutoConfig::KAutoConfigPrivate { + +public: + KAutoConfigPrivate() : changed(false) +#ifndef NDEBUG + , retrievedSettings(false) +#endif + { init(); } + + // Widgets to parse + QPtrList widgets; + // Name of the group that KConfig should be set to for each widget. + QMap groups; + + // Child widgets of widgets to ignore + QPtrList ignore; + + // Reset to false after saveSettings returns true. + bool changed; + +#ifndef NDEBUG + // Many functions require this to be true to be of any value. + bool retrievedSettings; +#endif + + // Known widgets that can be configured + QMap > autoWidgets; + // Default values for the widgets. + QMap defaultValues; + // Widgets to not get properties on (QLabel etc) + QAsciiDict ignoreTheseWidgets; + + void init(){ + ignoreTheseWidgets.insert("QLabel", new int(1)); + ignoreTheseWidgets.insert("QFrame", new int(2)); + ignoreTheseWidgets.insert("QGroupBox", new int(3)); + ignoreTheseWidgets.insert("QButtonGroup", new int(4)); + ignoreTheseWidgets.insert("QWidget", new int(5)); + ignoreTheseWidgets.setAutoDelete(true); + + static bool defaultKDEPropertyMapInstalled = false; + if ( !defaultKDEPropertyMapInstalled && kapp ) { + kapp->installKDEPropertyMap(); + defaultKDEPropertyMapInstalled = true; + } + } +}; + +KAutoConfig::KAutoConfig(KConfig *kconfig, QObject *parent, + const char *name) : QObject(parent, name), config(kconfig) { + d = new KAutoConfigPrivate(); +} + +KAutoConfig::KAutoConfig(QObject *parent, const char *name) : + QObject(parent, name), config(KGlobal::config()) { + d = new KAutoConfigPrivate(); +} + +KAutoConfig::~KAutoConfig(){ + delete d; +} + +void KAutoConfig::addWidget(QWidget *widget, const QString &group){ + functionCallPostOrderCheck("addWidget",); + d->groups.insert(widget, group); + d->widgets.append(widget); + QPtrList newAutoConfigWidget; + d->autoWidgets.insert(widget, newAutoConfigWidget ); +} + +void KAutoConfig::ignoreSubWidget(QWidget *widget){ + functionCallPostOrderCheck("ignoreSubWidget",); + d->ignore.append(widget); +} + +bool KAutoConfig::retrieveSettings(bool trackChanges){ +#ifndef NDEBUG + if(d->retrievedSettings){ + kdDebug(180) << "This should not happen. Function " + "KAutoConfig::retrieveSettings() was called more then once, returning " + "false. Please fix." << endl; + return false; + } + d->retrievedSettings = true; +#endif + + if(trackChanges){ + // QT + changedMap.insert(QString::fromLatin1("QButton"), SIGNAL(stateChanged(int))); + changedMap.insert(QString::fromLatin1("QCheckBox"), SIGNAL(stateChanged(int))); + changedMap.insert(QString::fromLatin1("QPushButton"), SIGNAL(stateChanged(int))); + changedMap.insert(QString::fromLatin1("QRadioButton"), SIGNAL(stateChanged(int))); + changedMap.insert(QString::fromLatin1("QComboBox"), SIGNAL(activated (int))); + //qsqlproperty map doesn't store the text, but the value! + //changedMap.insert(QString::fromLatin1("QComboBox"), SIGNAL(textChanged(const QString &))); + changedMap.insert(QString::fromLatin1("QDateEdit"), SIGNAL(valueChanged(const QDate &))); + changedMap.insert(QString::fromLatin1("QDateTimeEdit"), SIGNAL(valueChanged(const QDateTime &))); + changedMap.insert(QString::fromLatin1("QDial"), SIGNAL(valueChanged (int))); + changedMap.insert(QString::fromLatin1("QLineEdit"), SIGNAL(textChanged(const QString &))); + changedMap.insert(QString::fromLatin1("QSlider"), SIGNAL(valueChanged(int))); + changedMap.insert(QString::fromLatin1("QSpinBox"), SIGNAL(valueChanged(int))); + changedMap.insert(QString::fromLatin1("QTimeEdit"), SIGNAL(valueChanged(const QTime &))); + changedMap.insert(QString::fromLatin1("QTextEdit"), SIGNAL(textChanged())); + changedMap.insert(QString::fromLatin1("QTextBrowser"), SIGNAL(sourceChanged(const QString &))); + changedMap.insert(QString::fromLatin1("QMultiLineEdit"), SIGNAL(textChanged())); + changedMap.insert(QString::fromLatin1("QListBox"), SIGNAL(selectionChanged())); + changedMap.insert(QString::fromLatin1("QTabWidget"), SIGNAL(currentChanged(QWidget *))); + + // KDE + changedMap.insert( QString::fromLatin1("KComboBox"), SIGNAL(activated (int))); + changedMap.insert( QString::fromLatin1("KFontCombo"), SIGNAL(activated (int))); + changedMap.insert( QString::fromLatin1("KFontRequester"), SIGNAL(fontSelected(const QFont &))); + changedMap.insert( QString::fromLatin1("KFontChooser"), SIGNAL(fontSelected(const QFont &))); + changedMap.insert( QString::fromLatin1("KHistoryCombo"), SIGNAL(activated (int))); + + changedMap.insert( QString::fromLatin1("KColorButton"), SIGNAL(changed(const QColor &))); + changedMap.insert( QString::fromLatin1("KDatePicker"), SIGNAL(dateSelected (QDate))); + changedMap.insert( QString::fromLatin1("KEditListBox"), SIGNAL(changed())); + changedMap.insert( QString::fromLatin1("KListBox"), SIGNAL(selectionChanged())); + changedMap.insert( QString::fromLatin1("KLineEdit"), SIGNAL(textChanged(const QString &))); + changedMap.insert( QString::fromLatin1("KPasswordEdit"), SIGNAL(textChanged(const QString &))); + changedMap.insert( QString::fromLatin1("KRestrictedLine"), SIGNAL(textChanged(const QString &))); + changedMap.insert( QString::fromLatin1("KTextBrowser"), SIGNAL(sourceChanged(const QString &))); + changedMap.insert( QString::fromLatin1("KTextEdit"), SIGNAL(textChanged())); + changedMap.insert( QString::fromLatin1("KURLRequester"), SIGNAL(textChanged (const QString& ))); + changedMap.insert( QString::fromLatin1("KIntNumInput"), SIGNAL(valueChanged (int))); + changedMap.insert( QString::fromLatin1("KIntSpinBox"), SIGNAL(valueChanged (int))); + changedMap.insert( QString::fromLatin1("KDoubleNumInput"), SIGNAL(valueChanged (double))); + } + + // Go through all of the children of the widgets and find all known widgets + QPtrListIterator it( d->widgets ); + QWidget *widget; + bool usingDefaultValues = false; + while ( (widget = it.current()) != 0 ) { + ++it; + config->setGroup(d->groups[widget]); + usingDefaultValues |= parseChildren(widget, d->autoWidgets[widget], trackChanges); + } + return usingDefaultValues; +} + +bool KAutoConfig::saveSettings() { + functionCallPreOrderCheck("saveSettings", false); + + QSqlPropertyMap *propertyMap = QSqlPropertyMap::defaultMap(); + // Go through all of the widgets + QPtrListIterator it( d->widgets ); + QWidget *widget; + while ( (widget = it.current()) != 0 ) { + ++it; + config->setGroup(d->groups[widget]); + + // Go through the known autowidgets of this widget and save + QPtrListIterator it( d->autoWidgets[widget] ); + QWidget *groupWidget; + bool widgetChanged = false; + while ( (groupWidget = it.current()) != 0 ){ + ++it; + QVariant defaultValue = d->defaultValues[groupWidget]; + QVariant currentValue = propertyMap->property(groupWidget); +#if KDE_IS_VERSION( 3, 1, 90 ) + if(!config->hasDefault(QString::fromLatin1(groupWidget->name())) && currentValue == defaultValue){ + config->revertToDefault(QString::fromLatin1(groupWidget->name())); + widgetChanged = true; + } + else{ +#endif + QVariant savedValue = config->readPropertyEntry(groupWidget->name(), + defaultValue); + if(savedValue != currentValue){ + config->writeEntry(groupWidget->name(), currentValue); + widgetChanged = true; + } +#if KDE_IS_VERSION( 3, 1, 90 ) + } +#endif + } + d->changed |= widgetChanged; + if(widgetChanged) + emit( settingsChanged(widget) ); + } + + if(d->changed){ + emit( settingsChanged() ); + d->changed = false; + config->sync(); + return true; + } + return false; +} + +bool KAutoConfig::hasChanged() const { + functionCallPreOrderCheck("hasChanged", false); + + QSqlPropertyMap *propertyMap = QSqlPropertyMap::defaultMap(); + // Go through all of the widgets + QPtrListIterator it( d->widgets ); + QWidget *widget; + while ( (widget = it.current()) != 0 ) { + ++it; + config->setGroup(d->groups[widget]); + // Go through the known autowidgets of this widget and save + QPtrListIterator it( d->autoWidgets[widget] ); + QWidget *groupWidget; + while ( (groupWidget = it.current()) != 0 ){ + ++it; + QVariant defaultValue = d->defaultValues[groupWidget]; + QVariant currentValue = propertyMap->property(groupWidget); + QVariant savedValue = config->readPropertyEntry(groupWidget->name(), + defaultValue); + + // Return once just one item is found to have changed. + if((currentValue == defaultValue && savedValue != currentValue) || + (savedValue != currentValue)) + return true; + } + } + return false; +} + +bool KAutoConfig::isDefault() const { + functionCallPreOrderCheck("isDefault", false); + + QSqlPropertyMap *propertyMap = QSqlPropertyMap::defaultMap(); + // Go through all of the widgets + QPtrListIterator it( d->widgets ); + QWidget *widget; + while ( (widget = it.current()) != 0 ) { + ++it; + config->setGroup(d->groups[widget]); + // Go through the known autowidgets of this widget and save + QPtrListIterator it( d->autoWidgets[widget] ); + QWidget *groupWidget; + while ( (groupWidget = it.current()) != 0 ){ + ++it; + QVariant defaultValue = d->defaultValues[groupWidget]; + QVariant currentValue = propertyMap->property(groupWidget); + if(currentValue != defaultValue){ + //qDebug("groupWidget %s, has changed: default: %s new: %s", groupWidget->name(), defaultValue.toString().latin1(), currentValue.toString().latin1()); + return false; + } + } + } + return true; +} + +void KAutoConfig::resetSettings() const { + functionCallPreOrderCheck("resetSettings",); + + QSqlPropertyMap *propertyMap = QSqlPropertyMap::defaultMap(); + // Go through all of the widgets + QPtrListIterator it( d->widgets ); + QWidget *widget; + while ( (widget = it.current()) != 0 ) { + ++it; + config->setGroup(d->groups[widget]); + + // Go through the known autowidgets of this widget and save + QPtrListIterator it( d->autoWidgets[widget] ); + QWidget *groupWidget; + while ( (groupWidget = it.current()) != 0 ){ + ++it; + QVariant defaultValue = d->defaultValues[groupWidget]; + if(defaultValue != propertyMap->property(groupWidget)){ + propertyMap->setProperty(groupWidget, defaultValue); + d->changed = true; + } + } + } +} + +void KAutoConfig::reloadSettings() const { + functionCallPreOrderCheck("reloadSettings", ); + + QSqlPropertyMap *propertyMap = QSqlPropertyMap::defaultMap(); + // Go through all of the widgets + QPtrListIterator it( d->widgets ); + QWidget *pageWidget; + while ( (pageWidget = it.current()) != 0 ) { + ++it; + config->setGroup(d->groups[pageWidget]); + + // Go through the known widgets of this page and reload + QPtrListIterator it( d->autoWidgets[pageWidget] ); + QWidget *widget; + while ( (widget = it.current()) != 0 ){ + ++it; + QVariant defaultSetting = d->defaultValues[widget]; + QVariant setting = + config->readPropertyEntry(widget->name(), defaultSetting); + propertyMap->setProperty(widget, setting); + } + } + d->changed = false; +} + +bool KAutoConfig::parseChildren(const QWidget *widget, + QPtrList& currentGroup, bool trackChanges){ + bool valueChanged = false; + const QPtrList *listOfChildren = widget->children(); + if(!listOfChildren) + return valueChanged; + + QSqlPropertyMap *propertyMap = QSqlPropertyMap::defaultMap(); + QPtrListIterator it( *listOfChildren ); + QObject *object; + while ( (object = it.current()) != 0 ) + { + ++it; + if(!object->isWidgetType()){ + continue; + } + QWidget *childWidget = (QWidget *)object; + if(d->ignore.containsRef(childWidget)){ + continue; + } + + bool parseTheChildren = true; +#ifndef NDEBUG + if(d->ignoreTheseWidgets[childWidget->className()] == 0 && + childWidget->name(0) == NULL){ + // Without a name the widget is just skipped over. + kdDebug(180) << "KAutoConfig::retrieveSettings, widget with " + "NULL name. className: " << childWidget->className() << endl; + } +#endif + + + if( d->ignoreTheseWidgets[childWidget->className()] == 0 && + childWidget->name(0) != NULL ) + { + QVariant defaultSetting = propertyMap->property(childWidget); + if(defaultSetting.isValid()) + { + parseTheChildren = false; + // Disable the widget if it is immutable? + if(config->entryIsImmutable( QString::fromLatin1(childWidget->name()))) + childWidget->setEnabled(false); + else + { + // FOR THOSE WHO ARE LOOKING + // Here is the code were the widget is actually marked to watch. + //qDebug("KAutoConfig: Adding widget(%s)",childWidget->name()); + currentGroup.append(childWidget); + d->defaultValues.insert(childWidget, defaultSetting); + } + // Get/Set settings and connect up the changed signal + QVariant setting = + config->readPropertyEntry(childWidget->name(), defaultSetting); + if(setting != defaultSetting) + { + propertyMap->setProperty(childWidget, setting); + valueChanged = true; + } + if(trackChanges && changedMap.find(QString::fromLatin1(childWidget->className())) != + changedMap.end()) + { + connect(childWidget, changedMap[QString::fromLatin1(childWidget->className())], + this, SIGNAL(widgetModified())); + } +#ifndef NDEBUG + else if(trackChanges && + changedMap.find(QString::fromLatin1(childWidget->className())) == changedMap.end()) + { + // Without a signal kautoconfigdialog could incorectly + // enable/disable the buttons + kdDebug(180) << "KAutoConfig::retrieveSettings, Unknown changed " + "signal for widget:" << childWidget->className() << endl; + } +#endif + + } +#ifndef NDEBUG + else + { + // If kautoconfig doesn't know how to get/set the widget's value + // nothing can be done to it and it is skipped. + kdDebug(180) << "KAutoConfig::retrieveSettings, Unknown widget:" + << childWidget->className() << endl; + } +#endif + } + if(parseTheChildren) + { + // this widget is not known as something we can store. + // Maybe we can store one of its children. + valueChanged |= parseChildren(childWidget, currentGroup, trackChanges); + } + } + return valueChanged; +} + +#include "kautoconfig.moc" + diff --git a/kopete/libkopete/kautoconfig.h b/kopete/libkopete/kautoconfig.h new file mode 100644 index 00000000..de0df143 --- /dev/null +++ b/kopete/libkopete/kautoconfig.h @@ -0,0 +1,278 @@ +/* + * This file is part of the KDE libraries + * Copyright (C) 2003 Benjamin C Meyer (ben+kdelibs at meyerhome dot net) + * + * This library 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 library 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 library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef KAUTOCONFIG_H +#define KAUTOCONFIG_H + +#include +#include + +#include "kopete_export.h" + +class KConfig; +class QWidget; + +/** + * @short Provides a means of automatically retrieving, saving and resetting basic settings + * + * The KAutoConfig class provides a means of automatically retrieving, + * saving and resetting basic settings. It also can emit signals when + * settings have been changed (settings were saved) or modified (the + * user changes a checkbox from on to off). + * + * When told to retrieve settings ( retrieveSettings()) KAutoConfig + * will traverse the specified widgets building a list of all known widgets + * that have a name and havn't been marked to be ignored. + * If a setting is marked immutable the value is loaded and the widget is + * disabled. + * + * The name of the widget determines the name of the setting. The initial + * value of the widget also is the default value when the widget is reset. + * If the widget does not have a name then it is ignored. + * + * When saveSettings() or resetSettings() is called KAutoConfig + * goes through the list of known widgets and performs the operation on each + * of them. + * + * If one of the widgets needs special treatment it can be specified to be + * ignored using the ignoreSubWidget() function. + * + * KAutoConfig uses the QSqlPropertyMap class to determine if it can do + * anything to a widget. Note that KAutoConfig doesn't require a database, + * it simply uses the functionality that is built into the QSqlPropertyMap + * class. New widgets can be added to the map using + * QSqlPropertyMap::installDefaultMap(). Note that you can't just add any + * class. The class must have a matching Q_PROPERTY(...) macro defined. + * + * For example (note that KColorButton is already added and it doesn't need to + * manually added): + * + * kcolorbutton.h defines the following property: + * \code + * Q_PROPERTY( QColor color READ color WRITE setColor ) + * \endcode + * + * To add KColorButton the following code would be inserted in the main. + * + * \code + * QSqlPropertyMap *map = QSqlPropertyMap::defaultMap(); + * map.insert("KColorButton", "color"); + * QSqlPropertyMap::installDefaultMap(map); + * \endcode + * + * If you add a new widget to the QSqlPropertyMap and wish to be notified when + * it is modified you should add its signal using addWidgetChangedSignal(). + * If the Apply and Default buttons and enabled/disabled by KAutoConfigDialog + * automatically then this must be done. + * + * @see KAutoConfigDialog + * @since 3.2 + * @author Benjamin C Meyer + */ +class KOPETE_EXPORT KAutoConfig : public QObject { + +Q_OBJECT + +signals: + /** + * One or more of the settings have been saved (such as when the user + * clicks on the Apply button). This is only emitted by saveSettings() + * whenever one or more setting were changed and consequently saved. + */ + void settingsChanged(); + + /** + * One or more of the settings have been changed. + * @param widget - The widget group (pass in via addWidget()) that + * contains the one or more modified setting. + * @see settingsChanged() + */ + void settingsChanged( QWidget *widget ); + + /** + * If retrieveSettings() was told to track changes then if + * any known setting was changed this signal will be emitted. Note + * that a settings can be modified several times and might go back to the + * original saved state. hasChanged() will tell you if anything has + * actually changed from the saved values. + */ + void widgetModified(); + + +public: + /** + * Constructor. + * @param kconfig - KConfig to use when retrieving/saving the widgets + * that KAutoConfig knows about. + * @param parent - Parent object. + * @param name - Object name. + */ + KAutoConfig( KConfig *kconfig, QObject *parent=0, const char *name=0 ); + + /** + * Constructor. + * Uses KGlobal::config() when retrieving/saving the widgets that + * KAutoConfig knows about. + * @param parent - Parent object. + * @param name - Object name. + */ + KAutoConfig( QObject *parent=0, const char *name=0 ); + + /** + * Destructor. Deletes private class. + */ + ~KAutoConfig(); + + /** + * Adds a widget to the list of widgets that should be parsed for any + * children that KAutoConfig might know when retrieveSettings() is + * called. All calls to this function should be made before calling + * retrieveSettings(). + * @param widget - Pointer to the widget to add. + * @param group - Name of the group from which all of the settings for this + * widget will be located. If a child of 'widget' needs to be in a separate + * group it should be added separately and also ignored. + * @see ignoreSubWidget() + */ + void addWidget( QWidget *widget, const QString &group ); + + /** + * Ignore the specified child widget when performing an action. Doesn't + * effect widgets that were added with addWidget() only their children. All + * calls to this function should be made before calling retrieveSettings(). + * @param widget - Pointer to the widget that should be ignored. + * Note: Widgets that don't have a name are ignored automatically. + **/ + void ignoreSubWidget( QWidget *widget ); + + /** + * Traverse the specified widgets to see if anything is different then the + * current settings. retrieveSettings() must be called before this + * function to build the list of known widgets and default values. + * @return bool - True if any settings are different then the stored values. + */ + bool hasChanged() const; + + /** + * Traverse the specified widgets to see if anything is different then the + * default. retrieveSettings() must be called before this function to + * build the list of known widgets and default values. + * @return bool - True if all of the settings are their default values. + */ + bool isDefault() const; + + /** + * Adds a widget and its signal to the internal list so that when + * KAutoConfig finds widgetName in retrieveSettings() it will know + * how to connect its signal that it has changed to KAutoConfig's signal + * widgetModified(). This function should be called before + * + * Example: + * \code + * addWidgetChangedSignal( "QCheckbox", SIGNAL(stateChanged(int)) ); + * \endcode + * + * This is generally used in conjunction with the addition of a class + * to QSqlPropertyMap so KAutoConfig can get/set its values. + * + * @param widgetName - The class name of the widget (className()). + * @param signal - The signal (with "SIGNAL()" wrapper) that should be called. + */ + inline void addWidgetChangedSignal( const QString &widgetName, + const QCString &signal ){ + changedMap.insert( widgetName, signal ); + } + + /** + * Traverse the specified widgets, retrieving the settings for all known + * widgets that aren't being ignored and storing the default values. + * @param trackChanges - If any changes by the widgets should be tracked + * set true. This causes the emitting the modified() signal when + * something changes. + * @return bool - True if any setting was changed from the default. + */ + bool retrieveSettings( bool trackChanges=false ); + +public slots: + /** + * Traverse the specified widgets, saving the settings for all known + * widgets that aren't being ignored. retrieveSettings() must be called + * before this function to build the list of known widgets and default values. + * @return bool - True if any settings were changed. + * + * Example use: User clicks Ok or Apply button in a configure dialog. + */ + bool saveSettings(); + + /** + * Traverse the specified widgets, reseting the widgets to their default + * values for all known widgets that aren't being ignored. + * retrieveSettings() must be called before this function to build + * the list of known widgets and default values. + * + * Example use: User clicks Default button in a configure dialog. + */ + void resetSettings() const; + + /** + * Traverse the specified widgets, reloading the settings for all known + * widgets that aren't being ignored. + * retrieveSettings() must be called before this function to build + * the list of known widgets and default values. + * + * Example use: User clicks Reset button in a configure dialog. + */ + void reloadSettings() const; + +protected: + /** + * KConfigBase object used to get/save values. + */ + KConfig *config; + /** + * Map of the classes and the signals that they emit when changed. + */ + QMap changedMap; + + /** + * Recursive function that finds all known children. + * Goes through the children of widget and if any are known and not being + * ignored, stores them in currentGroup. Also checks if the widget + * should be disabled because it is set immutable. + * @param widget - Parent of the children to look at. + * @param currentGroup - Place to store known children of widget. + * @param trackChanges - If true then tracks any changes to the children of + * widget that are known. + * @return bool - If a widget was set to something other then its default. + * @see retrieveSettings() + */ + bool parseChildren( const QWidget *widget, + QPtrList¤tGroup, bool trackChanges ); + +private: + class KAutoConfigPrivate; + /** + * KAutoConfig Private class. + */ + KAutoConfigPrivate *d; + +}; + +#endif // KAUTOCONFIG_H + diff --git a/kopete/libkopete/kcautoconfigmodule.cpp b/kopete/libkopete/kcautoconfigmodule.cpp new file mode 100644 index 00000000..8bbe87c0 --- /dev/null +++ b/kopete/libkopete/kcautoconfigmodule.cpp @@ -0,0 +1,114 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Olivier Goffart + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + +*/ + +#include "kcautoconfigmodule.h" + +#include + +#include + +class KCAutoConfigModule::KCAutoConfigModulePrivate +{ + public: + KAutoConfig *kautoconfig; +}; + + +KCAutoConfigModule::KCAutoConfigModule( QWidget * parent, const char * name, const QStringList & args ) + : KCModule( parent, name, args ) + , d( new KCAutoConfigModulePrivate ) +{ + d->kautoconfig = new KAutoConfig( this ); + connect(d->kautoconfig, SIGNAL(widgetModified()), SLOT(slotWidgetModified())); + connect(d->kautoconfig, SIGNAL(settingsChanged()), SLOT(widgetModified())); +} + +KCAutoConfigModule::KCAutoConfigModule( KInstance * instance, QWidget * parent, const QStringList & args ) + : KCModule( instance, parent, args ) + , d( new KCAutoConfigModulePrivate ) +{ + d->kautoconfig = new KAutoConfig( this ); + connect(d->kautoconfig, SIGNAL(widgetModified()), SLOT(slotWidgetModified())); + connect(d->kautoconfig, SIGNAL(settingsChanged()), SLOT(slotWidgetModified())); +} + + + +KCAutoConfigModule::KCAutoConfigModule( KConfig *config,QWidget * parent, const char * name, const QStringList & args ) + : KCModule( parent, name, args ) , d( new KCAutoConfigModulePrivate ) +{ + d->kautoconfig = new KAutoConfig( config, this ); + connect(d->kautoconfig, SIGNAL(widgetModified()), SLOT(slotWidgetModified())); + connect(d->kautoconfig, SIGNAL(settingsChanged()), SLOT(slotWidgetModified())); +} + +KCAutoConfigModule::KCAutoConfigModule( KConfig *config , KInstance * instance, QWidget * parent, const QStringList & args ) + : KCModule( instance, parent, args ) + , d( new KCAutoConfigModulePrivate ) +{ + d->kautoconfig = new KAutoConfig( config, this ); + connect(d->kautoconfig, SIGNAL(widgetModified()), SLOT(slotWidgetModified())); + connect(d->kautoconfig, SIGNAL(settingsChanged()), SLOT(slotWidgetModified())); +} + + +KCAutoConfigModule::~KCAutoConfigModule() +{ + delete d; +} + + +void KCAutoConfigModule::load() +{ + d->kautoconfig->reloadSettings(); +} + +void KCAutoConfigModule::save() +{ + d->kautoconfig->saveSettings(); +} + +void KCAutoConfigModule::defaults() +{ + d->kautoconfig->resetSettings(); +} + +void KCAutoConfigModule::slotWidgetModified() +{ + emit changed(d->kautoconfig->hasChanged()); +} + +KAutoConfig *KCAutoConfigModule::autoConfig() +{ + return d->kautoconfig; +} + +void KCAutoConfigModule::setMainWidget(QWidget *widget, const QString& group ) +{ + QBoxLayout * l = new QVBoxLayout( this ); + l->addWidget( widget ); + + d->kautoconfig->addWidget(widget,group); + d->kautoconfig->retrieveSettings(true); +} + +#include "kcautoconfigmodule.moc" + +// vim: sw=4 sts=4 et + diff --git a/kopete/libkopete/kcautoconfigmodule.h b/kopete/libkopete/kcautoconfigmodule.h new file mode 100644 index 00000000..e3737ca5 --- /dev/null +++ b/kopete/libkopete/kcautoconfigmodule.h @@ -0,0 +1,150 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Olivier Goffart + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + +*/ + +#ifndef KCAUTOCONFIGMODULE_H +#define KCAUTOCONFIGMODULE_H + +#include + +#include "kopete_export.h" + +class KAutoConfig; +class KConfig; + + +/** + * @short Convenience KCModule for creating config page handled with KAutoConfig + * + * This class makes it very easy to create a configuration page using KAutoConfig. + * All you need to do is create a class that is derived from KCAutoConfigModule, create your + * config page with QDesigner, and add it to the module + * This can be done using the setMainWidget() method: + * \code + * typedef KGenericFactory MyPageConfigFactory; + * K_EXPORT_COMPONENT_FACTORY( kcm_mypageconfig, MyPageConfigFactory( "kcm_mypageconfig" ) ) + * + * MyPageConfig( QWidget * parent, const char *, const QStringList & args ) + * : KCAutoConfigModule( MyPageConfigFactory::instance(), parent, args ) + * { + * setMainWidget( new MyPageConfigBase(this) , "MyGroup" ); + * } + * \endcode + * + * + * @author Olivier Goffart + * @since 3.2 + */ +class KOPETE_EXPORT KCAutoConfigModule : public KCModule +{ + Q_OBJECT + public: + /** + * Standard KCModule constructor. Use KGlobal::config() + */ + KCAutoConfigModule( QWidget * parent = 0, const char * name = 0, const QStringList & args = QStringList() ); + + /** + * Standard KCModule constructor. Use KGlobal::config() + */ + KCAutoConfigModule( KInstance * instance, QWidget * parent = 0, const QStringList & args = QStringList() ); + + /** + * Constructor. + * @param config the KConfig to use + * @param instance KInstance object for this KCM + * @param parent parent widget + * @param args special arguments for this KCM + * + * @todo document what the args mean (inherited from KCModule?) + */ + KCAutoConfigModule(KConfig* config, KInstance * instance, QWidget * parent = 0, const QStringList & args = QStringList() ); + + /** + * Constructor, much like the one above, except with + * no instance and with a name. + * @param config the KConfig to use + * @param parent parent widget + * @param name name of the object + * @param args special arguments for this KCM + */ + KCAutoConfigModule(KConfig* config, QWidget * parent = 0, const char * name=0 , const QStringList & args = QStringList() ); + + + ~KCAutoConfigModule(); + + /** + * Set the main widget. @p widget will be lay out to take all available place in the module. + * @p widget must have this module as parent. + * + * This method automatically call KAutoConfig::addWidget() and KAutoConfig::retrieveSettings() + * + * @param widget the widget to place on the page and to add in the KAutoConfig + * @param group the name of the group where settings are stored in the config file + */ + void setMainWidget(QWidget *widget, const QString& group); + + /** + * @brief a reference to the KAutoConfig + * + * You can add or remove manually some widget from the KAutoWidget. + * If you choose to don't add the main widget with setMainWidget() , you need + * to call KAutoConfig::retrieveSettings(true) yourself + * + * @return a reference to the KAutoConfig + */ + KAutoConfig *autoConfig(); + + /** + * Reload the config from the configfile. + * + * You can also reimplement this method, but you should always call the parent KCModule::load() + * be sure you know what you are doing + */ + virtual void load(); + + /** + * Save the config to the configfile. + * + * You can also reimplement this method, but you should always call the parent KCModule::save() + * be sure you know what you are doing + */ + virtual void save(); + + /** + * Reload the default config + * + * You can also reimplement this method, but you should always call the parent KCModule::defaults() + * be sure you know what you are doing + */ + virtual void defaults(); + + + protected slots: + /** + * Some setting was modified, updates buttons + */ + virtual void slotWidgetModified(); + + private: + class KCAutoConfigModulePrivate; + KCAutoConfigModulePrivate * d; +}; + + +#endif diff --git a/kopete/libkopete/knotification.cpp b/kopete/libkopete/knotification.cpp new file mode 100644 index 00000000..3749c21c --- /dev/null +++ b/kopete/libkopete/knotification.cpp @@ -0,0 +1,533 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005 Olivier Goffart + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include "knotification.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include + + + +//TODO, make the KNotification aware of the systemtray. +#include "kopeteuiglobal.h" +static WId checkWinId( const QString &/*appName*/, WId senderWinId ) +{ + if(senderWinId==0) + senderWinId=Kopete::UI::Global::sysTrayWId(); + + return senderWinId; +} + + +struct KNotification::Private +{ + QWidget *widget; + QString text; + QStringList actions; + int level; +}; + +KNotification::KNotification(QObject *parent) : + QObject(parent) , d(new Private) +{ + m_linkClicked = false; +} + +KNotification::~KNotification() +{ + delete d; +} + + +void KNotification::notifyByExecute(const QString &command, const QString& event, + const QString& fromApp, const QString& text, + int winId, int eventId) +{ + if (!command.isEmpty()) + { + // kdDebug() << "executing command '" << command << "'" << endl; + QMap subst; + subst.insert( 'e', event ); + subst.insert( 'a', fromApp ); + subst.insert( 's', text ); + subst.insert( 'w', QString::number( winId )); + subst.insert( 'i', QString::number( eventId )); + QString execLine = KMacroExpander::expandMacrosShellQuote( command, subst ); + if ( execLine.isEmpty() ) + execLine = command; // fallback + + KProcess p; + p.setUseShell(true); + p << execLine; + p.start(KProcess::DontCare); +// return true; + } + //return false; +} + + +void KNotification::notifyByMessagebox() +{ + // ignore empty messages + if ( d->text.isEmpty() ) + return; + + QString action=d->actions[0]; + WId winId=d->widget ? d->widget->topLevelWidget()->winId() : 0; + + if( action.isEmpty()) + { + // display message box for specified event level + switch( d->level ) + { + default: + case KNotifyClient::Notification: + KMessageBox::informationWId( winId, d->text, i18n( "Notification" ) ); + break; + case KNotifyClient::Warning: + KMessageBox::sorryWId( winId, d->text, i18n( "Warning" ) ); + break; + case KNotifyClient::Error: + KMessageBox::errorWId( winId, d->text, i18n( "Error" ) ); + break; + case KNotifyClient::Catastrophe: + KMessageBox::errorWId( winId, d->text, i18n( "Fatal" ) ); + break; + } + } + else + { //we may show the specific action button + int result=0; + QGuardedPtr _this=this; //this can be deleted + switch( d->level ) + { + default: + case KNotifyClient::Notification: + result = KMessageBox::questionYesNo(d->widget, d->text, i18n( "Notification" ), action, KStdGuiItem::cancel(), QString::null, false ); + break; + case KNotifyClient::Warning: + result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Warning" ), action, KStdGuiItem::cancel(), QString::null, false ); + break; + case KNotifyClient::Error: + result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Error" ), action, KStdGuiItem::cancel(), QString::null, false ); + break; + case KNotifyClient::Catastrophe: + result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Fatal" ), action, KStdGuiItem::cancel(), QString::null, false ); + break; + } + if(result==KMessageBox::Yes && _this) + { + activate(0); + } + } +} + + + +void KNotification::notifyByPassivePopup(const QPixmap &pix ) +{ + QString appName = QString::fromAscii( KNotifyClient::instance()->instanceName() ); + KIconLoader iconLoader( appName ); + KConfig eventsFile( QString::fromAscii( KNotifyClient::instance()->instanceName()+"/eventsrc" ), true, false, "data"); + KConfigGroup config( &eventsFile, "!Global!" ); + QString iconName = config.readEntry( "IconName", appName ); + QPixmap icon = iconLoader.loadIcon( iconName, KIcon::Small ); + QString title = config.readEntry( "Comment", appName ); + //KPassivePopup::message(title, text, icon, senderWinId); + + WId winId=d->widget ? d->widget->topLevelWidget()->winId() : 0; + + KPassivePopup *pop = new KPassivePopup( checkWinId(appName, winId) ); + QObject::connect(this, SIGNAL(closed()), pop, SLOT(deleteLater())); + + QVBox *vb = pop->standardView( title, pix.isNull() ? d->text: QString::null , icon ); + QVBox *vb2=vb; + + if(!pix.isNull()) + { + QHBox *hb = new QHBox(vb); + hb->setSpacing(KDialog::spacingHint()); + QLabel *pil=new QLabel(hb); + pil->setPixmap(pix); + pil->setScaledContents(true); + if(pix.height() > 80 && pix.height() > pix.width() ) + { + pil->setMaximumHeight(80); + pil->setMaximumWidth(80*pix.width()/pix.height()); + } + else if(pix.width() > 80 && pix.height() <= pix.width()) + { + pil->setMaximumWidth(80); + pil->setMaximumHeight(80*pix.height()/pix.width()); + } + vb=new QVBox(hb); + QLabel *msg = new QLabel( d->text, vb, "msg_label" ); + msg->setAlignment( AlignLeft ); + } + + + if ( !d->actions.isEmpty() ) + { + QString linkCode=QString::fromLatin1("

    "); + int i=0; + for ( QStringList::ConstIterator it = d->actions.begin() ; it != d->actions.end(); ++it ) + { + i++; + linkCode+=QString::fromLatin1(" %2 ").arg( QString::number(i) , QStyleSheet::escape(*it) ); + } + linkCode+=QString::fromLatin1("

    "); + KActiveLabel *link = new KActiveLabel(linkCode , vb ); + //link->setAlignment( AlignRight ); + QObject::disconnect(link, SIGNAL(linkClicked(const QString &)), link, SLOT(openLink(const QString &))); + QObject::connect(link, SIGNAL(linkClicked(const QString &)), this, SLOT(slotPopupLinkClicked(const QString &))); + QObject::connect(link, SIGNAL(linkClicked(const QString &)), pop, SLOT(hide())); + } + + pop->setAutoDelete( true ); + //pop->setTimeout(-1); + + pop->setView( vb2 ); + pop->show(); + +} + +void KNotification::slotPopupLinkClicked(const QString &adr) +{ + m_linkClicked = true; + unsigned int action=adr.toUInt(); + if(action==0) + return; + + activate(action); + + // since we've hidden the message (KNotification::notifyByPassivePopup(const QPixmap &pix )) + // we must now schedule overselves for deletion + close(); +} + +void KNotification::activate(unsigned int action) +{ + if(action==0) + emit activated(); + + emit activated(action); + deleteLater(); +} + + +void KNotification::close() +{ + // if the user hasn't clicked the link, and if we got here, it means the dialog closed + // and we were ignored + if (!m_linkClicked) + { + emit ignored(); + } + + emit closed(); + deleteLater(); +} + + +void KNotification::raiseWidget() +{ + if(!d->widget) + return; + + raiseWidget(d->widget); +} + + +void KNotification::raiseWidget(QWidget *w) +{ + //TODO this funciton is far from finished. + if(w->isTopLevel()) + { + w->raise(); + KWin::activateWindow( w->winId() ); + } + else + { + QWidget *pw=w->parentWidget(); + raiseWidget(pw); + + if( QTabWidget *tab_widget=dynamic_cast(pw)) + { + tab_widget->showPage(w); + } + } +} + + + + + +KNotification *KNotification::event( const QString& message , const QString& text, + const QPixmap& pixmap, QWidget *widget, + const QStringList &actions, unsigned int flags) +{ + /* NOTE: this function still use the KNotifyClient, + * in the future (KDE4) all the function of the knotifyclient will be moved there. + * Some code here is derived from the old KNotify deamon + */ + + int level=KNotifyClient::Default; + QString sound; + QString file; + QString commandline; + + // get config file + KConfig eventsFile( QString::fromAscii( KNotifyClient::instance()->instanceName()+"/eventsrc" ), true, false, "data"); + eventsFile.setGroup(message); + + KConfig configFile( QString::fromAscii( KNotifyClient::instance()->instanceName()+".eventsrc" ), true, false); + configFile.setGroup(message); + + int present=KNotifyClient::getPresentation(message); + if(present==-1) + present=KNotifyClient::getDefaultPresentation(message); + if(present==-1) + present=0; + + // get sound file name + if( present & KNotifyClient::Sound ) { + QString theSound = configFile.readPathEntry( "soundfile" ); + if ( theSound.isEmpty() ) + theSound = eventsFile.readPathEntry( "default_sound" ); + if ( !theSound.isEmpty() ) + sound = theSound; + } + + // get log file name + if( present & KNotifyClient::Logfile ) { + QString theFile = configFile.readPathEntry( "logfile" ); + if ( theFile.isEmpty() ) + theFile = eventsFile.readPathEntry( "default_logfile" ); + if ( !theFile.isEmpty() ) + file = theFile; + } + + // get default event level + if( present & KNotifyClient::Messagebox ) + level = eventsFile.readNumEntry( "level", 0 ); + + // get command line + if (present & KNotifyClient::Execute ) { + commandline = configFile.readPathEntry( "commandline" ); + if ( commandline.isEmpty() ) + commandline = eventsFile.readPathEntry( "default_commandline" ); + } + + return userEvent( text, pixmap, widget, actions, present , level, sound, file, commandline, flags ); +} + +KNotification *KNotification::userEvent( const QString& text, const QPixmap& pixmap, QWidget *widget, + QStringList actions,int present, int level, const QString &sound, const QString &file, + const QString &commandline, unsigned int flags) +{ + + /* NOTE: this function still use the KNotifyClient, + * in the futur (KDE4) all the function of the knotifyclient will be moved there. + * Some code of this function fome from the old KNotify deamon + */ + + + KNotification *notify=new KNotification(widget); + notify->d->widget=widget; + notify->d->text=text; + notify->d->actions=actions; + notify->d->level=level; + WId winId=widget ? widget->topLevelWidget()->winId() : 0; + + + //we will catch some event that will not be fired by the old deamon + + + //we remove presentation that has been already be played, and we fire the event in the old way + + + KNotifyClient::userEvent(winId,text,present & ~( KNotifyClient::PassivePopup|KNotifyClient::Messagebox|KNotifyClient::Execute),level,sound,file); + + + if ( present & KNotifyClient::PassivePopup ) + { + notify->notifyByPassivePopup( pixmap ); + } + if ( present & KNotifyClient::Messagebox ) + { + QTimer::singleShot(0,notify,SLOT(notifyByMessagebox())); + } + else //not a message box (because closing the event when a message box is there is suicide) + if(flags & CloseOnTimeout) + { + QTimer::singleShot(6*1000, notify, SLOT(close())); + } + if ( present & KNotifyClient::Execute ) + { + QString appname = QString::fromAscii( KNotifyClient::instance()->instanceName() ); + notify->notifyByExecute(commandline, QString::null,appname,text, winId, 0 ); + } + + return notify; + +} + + + +/* This code is there before i find a great way to perform context-dependent notifications + * in a way independent of kopete. + * i'm in fact still using the Will's old code. + */ + + +#include "kopeteeventpresentation.h" +#include "kopetegroup.h" +#include "kopetenotifydataobject.h" +#include "kopetenotifyevent.h" +#include "kopetemetacontact.h" +#include "kopeteuiglobal.h" +#include + + +static KNotification *performCustomNotifications( QWidget *widget, Kopete::MetaContact * mc, const QString &message, bool& suppress) +{ + KNotification *n=0L; + //kdDebug( 14010 ) << k_funcinfo << endl; + if ( suppress ) + return n; + + // Anything, including the MC itself, may set suppress and prevent further notifications + + /* This is a really ugly piece of logic now. The idea is to check for notifications + * first on the metacontact, then on each of its groups, until something suppresses + * any further notifications. + * So on the first run round this loop, dataObj points to the metacontact, and on subsequent + * iterations it points to one of the contact's groups. The metacontact pointer is maintained + * so that if a group has a chat notification set for this event, we can call execute() on the MC. + */ + + bool checkingMetaContact = true; + Kopete::NotifyDataObject * dataObj = mc; + do { + QString sound; + QString text; + + if ( dataObj ) + { + Kopete::NotifyEvent *evt = dataObj->notifyEvent( message ); + if ( evt ) + { + suppress = evt->suppressCommon(); + int present = 0; + // sound + Kopete::EventPresentation *pres = evt->presentation( Kopete::EventPresentation::Sound ); + if ( pres && pres->enabled() ) + { + present = present | KNotifyClient::Sound; + sound = pres->content(); + evt->firePresentation( Kopete::EventPresentation::Sound ); + } + // message + if ( ( pres = evt->presentation( Kopete::EventPresentation::Message ) ) + && pres->enabled() ) + { + present = present | KNotifyClient::PassivePopup; + text = pres->content(); + evt->firePresentation( Kopete::EventPresentation::Message ); + } + // chat + if ( ( pres = evt->presentation( Kopete::EventPresentation::Chat ) ) + && pres->enabled() ) + { + mc->execute(); + evt->firePresentation( Kopete::EventPresentation::Chat ); + } + // fire the event + n=KNotification::userEvent( text, mc->photo(), widget, QStringList() , present, 0, sound, QString::null, QString::null , KNotification::CloseOnTimeout); + } + } + + if ( mc ) + { + if ( checkingMetaContact ) + { + // only executed on first iteration + checkingMetaContact = false; + dataObj = mc->groups().first(); + } + else + dataObj = mc->groups().next(); + } + } + while ( dataObj && !suppress ); + return n; +} + + + + +KNotification *KNotification::event( Kopete::MetaContact *mc, const QString& message , + const QString& text, const QPixmap& pixmap, QWidget *widget, + const QStringList &actions, unsigned int flags) +{ + if (message.isEmpty()) return 0; + + bool suppress = false; + KNotification *n=performCustomNotifications( widget, mc, message, suppress); + + if ( suppress ) + { + //kdDebug( 14000 ) << "suppressing common notifications" << endl; + return n; // custom notifications don't create a single unique id + } + else + { + //kdDebug( 14000 ) << "carrying out common notifications" << endl; + return event( message, text, pixmap, widget , actions, flags); + } +} + + + + + +#include "knotification.moc" + + + diff --git a/kopete/libkopete/knotification.h b/kopete/libkopete/knotification.h new file mode 100644 index 00000000..b017b7c0 --- /dev/null +++ b/kopete/libkopete/knotification.h @@ -0,0 +1,217 @@ +/* This file is part of the KDE libraries + Copyright (C) 2005 Olivier Goffart + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + + +#ifndef KNOTIFICATION_H +#define KNOTIFICATION_H + + +#include +#include +#include +#include "kopete_export.h" + +class QWidget; +namespace Kopete { class MetaContact; } + +/** + * KNotification is used to notify some event to the user. + * + * It covers severals kind of notifications + * + * @li Interface feedback events: + * For notifying the user that he/she just performed an operation, like maximizing a + * window. This allows us to play sounds when a dialog appears. + * This is an instant notification. It ends automatically after a small timeout + * + * @li complex notifications: + * Notify when one received a new message, or when something important happened + * the user has to know. This notification has a start and a end. It start when + * the event actually occurs, and finish when the message is acknowledged. + * + * + * use the static function event() to fire an event + * + * the returned KNotification pointer may be used to connect signals or slots + * + * @author Olivier Goffart + */ +class KOPETE_EXPORT KNotification : public QObject +{ + Q_OBJECT +public: + + enum NotificationFlags + { + /** + * When the notification is activated, raise the notification's widget. + * + * This will change the desktop, raise the window, and switch to the tab. + */ + RaiseWidgetOnActivation=0x01, + + /** + * The notification will be automatically closed after a timeout. + */ + CloseOnTimeout=0x02, + /** + * The notification will be automatically closed if the widget() becomes + * activated. + * + * If the widget is already activated when the notification occurs, the + * notification will be closed after a small timeout. + */ + CloseWhenWidgetActivated=0x03 + }; + + + ~KNotification(); + + /** + * @brief the widget associated to the notification + * + * If the widget is destroyed, the notification will be automatically canceled. + * If the widget is activated, the notificaiton will be automatically closed if the flags said that + * + * When the notification is activated, the widget might be raised. + * Depending of the configuration, the taskbar entry of the window containing the widget may blink. + */ + QWidget *widget(); + + signals: + /** + * Emit only when the default activation has occured + */ + void activated(); + /** + * Emit when an action has been activated. + * @param action will be 0 is the default aciton was activated, or any actiton id + */ + void activated(unsigned int action); + + /** + * Emit when the notification is closed. Both if it's activated or just ignored + */ + void closed(); + + /** + * The notification has been ignored + */ + void ignored(); + +public slots: + /** + * @brief Active the action specified action + * If the action is zero, then the default action is activated + */ + void activate(unsigned int action=0); + + /** + * close the notification without activate it. + * + * This will delete the notification + */ + void close(); + + /** + * @brief Raise the widget. + * This will change the desktop, activate the window, and the tab if needed. + */ + void raiseWidget(); + + + +private: + struct Private; + Private *d; + KNotification(QObject *parent=0L); + /** + * recursive function that raise the widget. @p w + * + * @see raiseWidget() + */ + static void raiseWidget(QWidget *w); + + bool m_linkClicked; + +private slots: + void notifyByMessagebox(); + void notifyByPassivePopup(const QPixmap &pix); + void notifyByExecute(const QString &command, const QString& event,const QString& fromApp, const QString& text, int winId, int eventId); + void slotPopupLinkClicked(const QString &); + + +public: + /** + * @brief emit an event + * + * A popup may be showed, a sound may be played, depending the config. + * + * return a KNotification . You may use that pointer to connect some signals or slot. + * the pointer is automatically deleted when the event is closed. + * + * Make sure you use one of the CloseOnTimeOut or CloseWhenWidgetActivated, if not, + * you have to close yourself the notification. + * + * @note the text is shown in a QLabel, you should make sure to escape the html is needed. + * + * @param eventId is the name of the event + * @param text is the text of the notification to show in the popup. + * @param pixmap is a picture which may be shown in the popup. + * @param widget is a widget where the notification reports to + * @param actions is a list of action texts. + * @param flags is a bitmask of NotificationsFlags + */ + static KNotification *event( const QString& eventId , const QString& text=QString::null, + const QPixmap& pixmap=QPixmap(), QWidget *widget=0L, + const QStringList &actions=QStringList(), unsigned int flags=CloseOnTimeout); + + + /** + * @brief emit a custom event + * + * @param text is the text of the notification to show in the popup. + * @param pixmap is a picture which may be shown in the popup + * @param widget is a widget where the notification raports to + * @param actions is a list of actions text. + * @param present The presentation method of the event + * @param level The error message level + * @param sound The sound to play if selected with @p present + * @param file The log file to append the message to if selected with @p parent + * @param commandLine the command line to run if selected with @p parent + * @param flags Indicates the way in which the notification should be handled + */ + static KNotification *userEvent( const QString& text, const QPixmap& pixmap, + QWidget *widget, QStringList actions,int present, int level, + const QString &sound, const QString &file, + const QString &commandLine, unsigned int flags); + + + + /** + * @todo find a proper way to do context-dependent notifications + */ + static KNotification *event( Kopete::MetaContact *mc, const QString& eventId , const QString& text=QString::null, + const QPixmap& pixmap=QPixmap(), QWidget *widget=0L, + const QStringList &actions=QStringList(),unsigned int flags=CloseOnTimeout); + +}; + + + +#endif diff --git a/kopete/libkopete/kopete.kcfg b/kopete/libkopete/kopete.kcfg new file mode 100644 index 00000000..59573689 --- /dev/null +++ b/kopete/libkopete/kopete.kcfg @@ -0,0 +1,12 @@ + + + + + + + When enabled, this allows you to set your data in a central place. All your IM accounts will use this global data. + + false + + + diff --git a/kopete/libkopete/kopete_export.h b/kopete/libkopete/kopete_export.h new file mode 100644 index 00000000..df184b57 --- /dev/null +++ b/kopete/libkopete/kopete_export.h @@ -0,0 +1,30 @@ +/* + Kopete Export macors + + Copyright (c) 2004 by Dirk Mueller + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_EXPORT_H +#define KOPETE_EXPORT_H + +#include +#include + +#if KDE_IS_VERSION(3,3,2) +#define KOPETE_EXPORT KDE_EXPORT +#else +#define KOPETE_EXPORT +#endif + +#endif diff --git a/kopete/libkopete/kopeteaccount.cpp b/kopete/libkopete/kopeteaccount.cpp new file mode 100644 index 00000000..52bb26bc --- /dev/null +++ b/kopete/libkopete/kopeteaccount.cpp @@ -0,0 +1,581 @@ +/* + kopeteaccount.cpp - Kopete Account + + Copyright (c) 2003-2005 by Olivier Goffart + Copyright (c) 2003-2004 by Martijn Klingens + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kabcpersistence.h" +#include "kopetecontactlist.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetecontact.h" +#include "kopetemetacontact.h" +#include "kopeteprotocol.h" +#include "kopetepluginmanager.h" +#include "kopetegroup.h" +#include "kopeteprefs.h" +#include "kopeteutils.h" +#include "kopeteuiglobal.h" +#include "kopeteblacklister.h" +#include "kopeteonlinestatusmanager.h" +#include "editaccountwidget.h" + +namespace Kopete +{ + + +class Account::Private +{ +public: + Private( Protocol *protocol, const QString &accountId ) + : protocol( protocol ), id( accountId ) + , excludeconnect( true ), priority( 0 ), myself( 0 ) + , suppressStatusTimer( 0 ), suppressStatusNotification( false ) + , blackList( new Kopete::BlackLister( protocol->pluginId(), accountId ) ) + , connectionTry(0) + { } + + + ~Private() { delete blackList; } + + Protocol *protocol; + QString id; + QString accountLabel; + bool excludeconnect; + uint priority; + QDict contacts; + QColor color; + Contact *myself; + QTimer suppressStatusTimer; + bool suppressStatusNotification; + Kopete::BlackLister *blackList; + KConfigGroup *configGroup; + uint connectionTry; + QString customIcon; + Kopete::OnlineStatus restoreStatus; + QString restoreMessage; +}; + +Account::Account( Protocol *parent, const QString &accountId, const char *name ) + : QObject( parent, name ), d( new Private( parent, accountId ) ) +{ + d->configGroup=new KConfigGroup(KGlobal::config(), QString::fromLatin1( "Account_%1_%2" ).arg( d->protocol->pluginId(), d->id )); + + d->excludeconnect = d->configGroup->readBoolEntry( "ExcludeConnect", false ); + d->color = d->configGroup->readColorEntry( "Color", &d->color ); + d->customIcon = d->configGroup->readEntry( "Icon", QString() ); + d->priority = d->configGroup->readNumEntry( "Priority", 0 ); + + d->restoreStatus = Kopete::OnlineStatus::Online; + d->restoreMessage = ""; + + QObject::connect( &d->suppressStatusTimer, SIGNAL( timeout() ), + this, SLOT( slotStopSuppression() ) ); +} + +Account::~Account() +{ + d->contacts.remove( d->myself->contactId() ); + + // Delete all registered child contacts first + while ( !d->contacts.isEmpty() ) + delete *QDictIterator( d->contacts ); + + kdDebug( 14010 ) << k_funcinfo << " account '" << d->id << "' about to emit accountDestroyed " << endl; + emit accountDestroyed(this); + + delete d->myself; + delete d->configGroup; + delete d; +} + +void Account::reconnect() +{ + kdDebug( 14010 ) << k_funcinfo << "account " << d->id << " restoreStatus " << d->restoreStatus.status() << " restoreMessage " << d->restoreMessage << endl; + setOnlineStatus( d->restoreStatus, d->restoreMessage ); +} + +void Account::disconnected( DisconnectReason reason ) +{ + kdDebug( 14010 ) << k_funcinfo << reason << endl; + //reconnect if needed + if(reason == BadPassword ) + { + QTimer::singleShot(0, this, SLOT(reconnect())); + } + else if ( KopetePrefs::prefs()->reconnectOnDisconnect() == true && reason > Manual ) + { + d->connectionTry++; + //use a timer to allow the plugins to clean up after return + if(d->connectionTry < 3) + QTimer::singleShot(10000, this, SLOT(reconnect())); // wait 10 seconds before reconnect + } + if(reason== OtherClient) + { + Kopete::Utils::notifyConnectionLost(this, i18n("You have been disconnected"), i18n( "You have connected from another client or computer to the account '%1'" ).arg(d->id), i18n("Most proprietary Instant Messaging services do not allow you to connect from more than one location. Check that nobody is using your account without your permission. If you need a service that supports connection from various locations at the same time, use the Jabber protocol.")); + } +} + +Protocol *Account::protocol() const +{ + return d->protocol; +} + +QString Account::accountId() const +{ + return d->id; +} + +const QColor Account::color() const +{ + return d->color; +} + +void Account::setColor( const QColor &color ) +{ + d->color = color; + if ( d->color.isValid() ) + d->configGroup->writeEntry( "Color", d->color ); + else + d->configGroup->deleteEntry( "Color" ); + emit colorChanged( color ); +} + +void Account::setPriority( uint priority ) +{ + d->priority = priority; + d->configGroup->writeEntry( "Priority", d->priority ); +} + +uint Account::priority() const +{ + return d->priority; +} + + +QPixmap Account::accountIcon(const int size) const +{ + QString icon= d->customIcon.isEmpty() ? d->protocol->pluginIcon() : d->customIcon; + + // FIXME: this code is duplicated with OnlineStatus, can we merge it somehow? + QPixmap base = KGlobal::instance()->iconLoader()->loadIcon( + icon, KIcon::Small, size ); + + if ( d->color.isValid() ) + { + KIconEffect effect; + base = effect.apply( base, KIconEffect::Colorize, 1, d->color, 0); + } + + if ( size > 0 && base.width() != size ) + { + base = QPixmap( base.convertToImage().smoothScale( size, size ) ); + } + + return base; +} + +KConfigGroup* Kopete::Account::configGroup() const +{ + return d->configGroup; +} + +void Account::setAccountLabel( const QString &label ) +{ + d->accountLabel = label; +} + +QString Account::accountLabel() const +{ + if( d->accountLabel.isNull() ) + return d->id; + return d->accountLabel; +} + +void Account::setExcludeConnect( bool b ) +{ + d->excludeconnect = b; + d->configGroup->writeEntry( "ExcludeConnect", d->excludeconnect ); +} + +bool Account::excludeConnect() const +{ + return d->excludeconnect; +} + +void Account::registerContact( Contact *c ) +{ + d->contacts.insert( c->contactId(), c ); + QObject::connect( c, SIGNAL( contactDestroyed( Kopete::Contact * ) ), + SLOT( contactDestroyed( Kopete::Contact * ) ) ); +} + +void Account::contactDestroyed( Contact *c ) +{ + d->contacts.remove( c->contactId() ); +} + + +const QDict& Account::contacts() +{ + return d->contacts; +} + + +Kopete::MetaContact* Account::addContact( const QString &contactId, const QString &displayName , Group *group, AddMode mode ) +{ + + if ( contactId == d->myself->contactId() ) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Error, + i18n("You are not allowed to add yourself to the contact list. The addition of \"%1\" to account \"%2\" will not take place.").arg(contactId,accountId()), i18n("Error Creating Contact") + ); + return false; + } + + bool isTemporary = mode == Temporary; + + Contact *c = d->contacts[ contactId ]; + + if(!group) + group=Group::topLevel(); + + if ( c && c->metaContact() ) + { + if ( c->metaContact()->isTemporary() && !isTemporary ) + { + kdDebug( 14010 ) << k_funcinfo << " You are trying to add an existing temporary contact. Just add it on the list" << endl; + + c->metaContact()->setTemporary(false, group ); + ContactList::self()->addMetaContact(c->metaContact()); + } + else + { + // should we here add the contact to the parentContact if any? + kdDebug( 14010 ) << k_funcinfo << "Contact already exists" << endl; + } + return c->metaContact(); + } + + MetaContact *parentContact = new MetaContact(); + if(!displayName.isEmpty()) + parentContact->setDisplayName( displayName ); + + //Set it as a temporary contact if requested + if ( isTemporary ) + parentContact->setTemporary( true ); + else + parentContact->addToGroup( group ); + + if ( c ) + { + c->setMetaContact( parentContact ); + if ( mode == ChangeKABC ) + { + kdDebug( 14010 ) << k_funcinfo << " changing KABC" << endl; + KABCPersistence::self()->write( parentContact ); + } + } + else + { + if ( !createContact( contactId, parentContact ) ) + { + delete parentContact; + return 0L; + } + } + + ContactList::self()->addMetaContact( parentContact ); + return parentContact; +} + +bool Account::addContact(const QString &contactId , MetaContact *parent, AddMode mode ) +{ + if ( contactId == myself()->contactId() ) + { + KMessageBox::error( Kopete::UI::Global::mainWidget(), + i18n("You are not allowed to add yourself to the contact list. The addition of \"%1\" to account \"%2\" will not take place.").arg(contactId,accountId()), i18n("Error Creating Contact") + ); + return 0L; + } + + bool isTemporary= parent->isTemporary(); + Contact *c = d->contacts[ contactId ]; + if ( c && c->metaContact() ) + { + if ( c->metaContact()->isTemporary() && !isTemporary ) + { + kdDebug( 14010 ) << + "Account::addContact: You are trying to add an existing temporary contact. Just add it on the list" << endl; + + //setMetaContact ill take care about the deletion of the old contact + c->setMetaContact(parent); + return true; + } + else + { + // should we here add the contact to the parentContact if any? + kdDebug( 14010 ) << "Account::addContact: Contact already exists" << endl; + } + return false; //(the contact is not in the correct metacontact, so false) + } + + bool success = createContact(contactId, parent); + + if ( success && mode == ChangeKABC ) + { + kdDebug( 14010 ) << k_funcinfo << " changing KABC" << endl; + KABCPersistence::self()->write( parent ); + } + + return success; +} + +KActionMenu * Account::actionMenu() +{ + //default implementation + KActionMenu *menu = new KActionMenu( accountId(), myself()->onlineStatus().iconFor( this ), this ); + QString nick = myself()->property( Kopete::Global::Properties::self()->nickName()).value().toString(); + + menu->popupMenu()->insertTitle( myself()->onlineStatus().iconFor( myself() ), + nick.isNull() ? accountLabel() : i18n( "%2 <%1>" ).arg( accountLabel(), nick ) + ); + + OnlineStatusManager::self()->createAccountStatusActions(this, menu); + menu->popupMenu()->insertSeparator(); + menu->insert( new KAction ( i18n( "Properties" ), 0, this, SLOT( editAccount() ), menu, "actionAccountProperties" ) ); + + return menu; +} + + +bool Account::isConnected() const +{ + return myself() && myself()->isOnline(); +} + +bool Account::isAway() const +{ + return d->myself && ( d->myself->onlineStatus().status() == Kopete::OnlineStatus::Away ); +} + +Contact * Account::myself() const +{ + return d->myself; +} + +void Account::setMyself( Contact *myself ) +{ + bool wasConnected = isConnected(); + + if ( d->myself ) + { + QObject::disconnect( d->myself, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotOnlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + QObject::disconnect( d->myself, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), + this, SLOT( slotContactPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ) ); + } + + d->myself = myself; + +// d->contacts.remove( myself->contactId() ); + + QObject::connect( d->myself, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotOnlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + QObject::connect( d->myself, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), + this, SLOT( slotContactPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ) ); + + if ( isConnected() != wasConnected ) + emit isConnectedChanged(); +} + +void Account::slotOnlineStatusChanged( Contact * /* contact */, + const OnlineStatus &newStatus, const OnlineStatus &oldStatus ) +{ + bool wasOffline = !oldStatus.isDefinitelyOnline(); + bool isOffline = !newStatus.isDefinitelyOnline(); + + if ( wasOffline || newStatus.status() == OnlineStatus::Offline ) + { + // Wait for five seconds until we treat status notifications for contacts + // as unrelated to our own status change. + // Five seconds may seem like a long time, but just after your own + // connection it's basically neglectible, and depending on your own + // contact list's size, the protocol you are using, your internet + // connection's speed and your computer's speed you *will* need it. + d->suppressStatusNotification = true; + d->suppressStatusTimer.start( 5000, true ); + //the timer is also used to reset the d->connectionTry + } + + if ( !isOffline ) + { + d->restoreStatus = newStatus; + d->restoreMessage = myself()->property( Kopete::Global::Properties::self()->awayMessage() ).value().toString(); +// kdDebug( 14010 ) << k_funcinfo << "account " << d->id << " restoreStatus " << d->restoreStatus.status() << " restoreMessage " << d->restoreMessage << endl; + } + +/* kdDebug(14010) << k_funcinfo << "account " << d->id << " changed status. was " + << Kopete::OnlineStatus::statusTypeToString(oldStatus.status()) << ", is " + << Kopete::OnlineStatus::statusTypeToString(newStatus.status()) << endl;*/ + if ( wasOffline != isOffline ) + emit isConnectedChanged(); +} + +void Account::setAllContactsStatus( const Kopete::OnlineStatus &status ) +{ + d->suppressStatusNotification = true; + d->suppressStatusTimer.start( 5000, true ); + + for ( QDictIterator it( d->contacts ); it.current(); ++it ) + if ( it.current() != d->myself ) + it.current()->setOnlineStatus( status ); +} + +void Account::slotContactPropertyChanged( Contact * /* contact */, + const QString &key, const QVariant &old, const QVariant &newVal ) +{ + if ( key == QString::fromLatin1("awayMessage") && old != newVal && isConnected() ) + { + d->restoreMessage = newVal.toString(); +// kdDebug( 14010 ) << k_funcinfo << "account " << d->id << " restoreMessage " << d->restoreMessage << endl; + } +} + +void Account::slotStopSuppression() +{ + d->suppressStatusNotification = false; + if(isConnected()) + d->connectionTry=0; +} + +bool Account::suppressStatusNotification() const +{ + return d->suppressStatusNotification; +} + +bool Account::removeAccount() +{ + //default implementation + return true; +} + + +BlackLister* Account::blackLister() +{ + return d->blackList; +} + +void Account::block( const QString &contactId ) +{ + d->blackList->addContact( contactId ); +} + +void Account::unblock( const QString &contactId ) +{ + d->blackList->removeContact( contactId ); +} + +bool Account::isBlocked( const QString &contactId ) +{ + return d->blackList->isBlocked( contactId ); +} + +void Account::editAccount(QWidget *parent) +{ + KDialogBase *editDialog = new KDialogBase( parent, "KopeteAccountConfig::editDialog", true, + i18n( "Edit Account" ), KDialogBase::Ok | KDialogBase::Cancel, + KDialogBase::Ok, true ); + + KopeteEditAccountWidget *m_accountWidget = protocol()->createEditAccountWidget( this, editDialog ); + if ( !m_accountWidget ) + return; + + // FIXME: Why the #### is EditAccountWidget not a QWidget?!? This sideways casting + // is braindead and error-prone. Looking at MSN the only reason I can see is + // because it allows direct subclassing of designer widgets. But what is + // wrong with embedding the designer widget in an empty QWidget instead? + // Also, if this REALLY has to be a pure class and not a widget, then the + // class should at least be renamed to EditAccountIface instead - Martijn + QWidget *w = dynamic_cast( m_accountWidget ); + if ( !w ) + return; + + editDialog->setMainWidget( w ); + if ( editDialog->exec() == QDialog::Accepted ) + { + if( m_accountWidget->validateData() ) + m_accountWidget->apply(); + } + + editDialog->deleteLater(); +} + +void Account::setPluginData( Plugin* /*plugin*/, const QString &key, const QString &value ) +{ + configGroup()->writeEntry(key,value); +} + +QString Account::pluginData( Plugin* /*plugin*/, const QString &key ) const +{ + return configGroup()->readEntry(key); +} + +void Account::setAway(bool away, const QString& reason) +{ + setOnlineStatus( OnlineStatusManager::self()->onlineStatus(protocol() , away ? OnlineStatusManager::Away : OnlineStatusManager::Online) , reason ); +} + +void Account::setCustomIcon( const QString & i) +{ + d->customIcon = i; + if(!i.isEmpty()) + d->configGroup->writeEntry( "Icon", i ); + else + d->configGroup->deleteEntry( "Icon" ); + emit colorChanged( color() ); +} + +QString Account::customIcon() const +{ + return d->customIcon; +} + +void Account::virtual_hook( uint /*id*/, void* /*data*/) +{ +} + + + +} + + //END namespace Kopete + +#include "kopeteaccount.moc" diff --git a/kopete/libkopete/kopeteaccount.h b/kopete/libkopete/kopeteaccount.h new file mode 100644 index 00000000..f3c2d338 --- /dev/null +++ b/kopete/libkopete/kopeteaccount.h @@ -0,0 +1,554 @@ +/* + kopeteaccount.h - Kopete Account + + Copyright (c) 2003-2004 by Olivier Goffart + Copyright (c) 2003-2004 by Martijn Klingens + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEACCOUNT_H +#define KOPETEACCOUNT_H + +#include "kopeteonlinestatus.h" + +#include "kopete_export.h" + +#include +#include + +class QDomNode; +class KActionMenu; +class KConfigGroup; + +namespace Kopete +{ +class Contact; +class Plugin; +class Protocol; +class MetaContact; +class Group; +class OnlineStatus; +class BlackLister; + +/** + * The Kopete::Account class handles one account. + * Each protocol should subclass this class in its own custom accounts class. + * There are few pure virtual method that the protocol must implement. Examples are: + * \li \ref connect() + * \li \ref disconnect() + * \li \ref createContact() + * + * If your account requires a password, derive from @ref PasswordedAccount instead of this class. + * + * The accountId is an @em constant unique id, which represents the login. + * The @ref myself() contact is one of the most important contacts, which represents + * the user tied to this account. You must create this contact in the contructor of your + * account and pass it to @ref setMyself(). + * + * All account data is saved to @ref KConfig. This includes the accountId, the autoconnect flag and + * the color. You can save more data using @ref configGroup() + * + * When you create a new account, you have to register it with the account manager by calling + * @ref AccountManager::registerAccount. + * + * @author Olivier Goffart + */ +class KOPETE_EXPORT Account : public QObject +{ + Q_OBJECT + + Q_ENUMS( AddMode ) + Q_PROPERTY( QString accountId READ accountId ) + Q_PROPERTY( bool excludeConnect READ excludeConnect WRITE setExcludeConnect ) + Q_PROPERTY( QColor color READ color WRITE setColor ) + Q_PROPERTY( QPixmap accountIcon READ accountIcon ) + Q_PROPERTY( bool isConnected READ isConnected ) + Q_PROPERTY( bool isAway READ isAway ) + Q_PROPERTY( bool suppressStatusNotification READ suppressStatusNotification ) + Q_PROPERTY( uint priority READ priority WRITE setPriority ) + +public: + /** + * \brief Describes how the account was disconnected + * + * Manual means that the disconnection was done by the user and no reconnection + * will take place. Any other value will reconnect the account on disconnection. + * The case where the password is wrong will be handled differently. + * @see @ref disconnected + */ + enum DisconnectReason { + OtherClient = -4, ///< connection went down because another client connected the same account + BadPassword = -3, ///< connection failed because password was incorrect + BadUserName = -2, ///< connection failed because user name was invalid / unknown + InvalidHost = -1, ///< connection failed because host is unreachable + Manual = 0, ///< the user disconnected normally + ConnectionReset = 1, ///< the connection was lost + Unknown = 99 ///< the reason for disconnection is unknown + }; + + /** + * @param parent the protocol for this account. The account is a child object of the + * protocol, so it will be automatically deleted when the protocol is. + * @param accountID the unique ID of this account. + * @param name the name of this QObject. + */ + Account(Protocol *parent, const QString &accountID, const char *name=0L); + ~Account(); + + /** + * \return the Protocol for this account + */ + Protocol *protocol() const ; + + /** + * \return the unique ID of this account used as the login + */ + QString accountId() const; + + /** + * \return The label of this account, for the GUI + */ + QString accountLabel() const; + + /** + * \brief Get the priority of this account. + * + * Used for sorting and determining the preferred account to message a contact. + */ + uint priority() const; + + /** + * \brief Set the priority of this account. + * + * @note This method is called by the UI, and should not be called elsewhere. + */ + void setPriority( uint priority ); + + /** + * \brief Set if the account should not log in automatically. + * + * This function can be used by the EditAccountPage. Kopete handles connection automatically. + * @sa @ref excludeConnect + */ + void setExcludeConnect(bool); + + /** + * \brief Get if the account should not log in. + * + * @return @c true if the account should not be connected when connectAll at startup, @c false otherwise. + */ + bool excludeConnect() const; + + /** + * \brief Get the color for this account. + * + * The color will be used to visually differentiate this account from other accounts on the + * same protocol. + * + * \return the user color for this account + */ + const QColor color() const; + + /** + * \brief Set the color for this account. + * + * This is called by Kopete's account config page; you don't have to set the color yourself. + * + * @sa @ref color() + */ + void setColor( const QColor &color); + + /** + * \brief Get the icon for this account. + * + * Generates an image of size @p size representing this account. The result is not cached. + * + * @param size the size of the icon. If the size is 0, the default size is used. + * @return the icon for this account, colored if needed + */ + QPixmap accountIcon( const int size = 0 ) const; + + /** + * \brief change the account icon. + * by default the icon of an account is the protocol one, but it may be overide it. + * Set QString::null to go back to the default (the protocol icon) + * + * this call will emit colorChanged() + */ + void setCustomIcon( const QString& ); + + /** + * \brief return the icon base + * This is the custom account icon set with setIcon. if this icon is null, then the protocol icon is used + * don't use this funciton to get the icon that need to be displayed, use accountIcon + */ + QString customIcon() const; + + + + + /** + * \brief Retrieve the 'myself' contact. + * + * \return a pointer to the Contact object for this account + * + * \see setMyself(). + */ + Contact * myself() const; + + /** + * @brief Return the menu for this account + * + * You have to reimplement this method to return the custom action menu which will + * be shown in the statusbar. It is the caller's responsibility to ensure the menu is deleted. + * + * The default implementation provides a generic menu, with actions generated from the protocol's + * registered statuses, and an action to show the account's settings dialog. + * + * You should call the default implementation from your reimplementation, and add more actions + * you require to the resulting action menu. + * + * @see OnlineStatusManager::registerOnlineStatus + */ + virtual KActionMenu* actionMenu() ; + + /** + * @brief Retrieve the list of contacts for this account + * + * The list is guaranteed to contain only contacts for this account, + * so you can safely use static_cast to your own derived contact class + * if needed. + */ + const QDict& contacts(); + + /** + * Indicates whether or not we should suppress status notifications + * for contacts belonging to this account. + * + * This is used when we just connected or disconnected, and every contact has their initial + * status set. + * + * @return @c true if notifications should not be used, @c false otherwise + */ + bool suppressStatusNotification() const; + + /** + * \brief Describes what should be done when the contact is added to a metacontact + * @sa @ref addContact() + */ + enum AddMode { + ChangeKABC = 0, ///< The KDE Address book may be updated + DontChangeKABC = 1, ///< The KDE Address book will not be changed + Temporary = 2 ///< The contact will not be added on the contactlist + }; + + /** + * \brief Create a contact (creating a new metacontact if necessary) + * + * If a contact for this account with ID @p contactId is not already on the contact list, + * a new contact with that ID is created, and added to a new metacontact. + * + * If @p mode is @c ChangeKABC, MetaContact::updateKABC will be called on the resulting metacontact. + * If @p mode is @c Temporary, MetaContact::setTemporary will be called on the resulting metacontact, + * and the metacontact will not be added to @p group. + * If @p mode is @c DontChangeKABC, no additional action is carried out. + * + * @param contactId the @ref Contact::contactId of the contact to create + * @param displayName the displayname (alias) of the new metacontact. Leave as QString::null if + * no alias is known, then by default, the nick will be taken as alias and tracked if changed. + * @param group the group to add the created metacontact to, or 0 for the top-level group. + * @param mode the mode used to add the contact. Use DontChangeKABC when deserializing. + * @return the new created metacontact or 0L if the operation failed + */ + MetaContact* addContact( const QString &contactId, const QString &displayName = QString::null, Group *group = 0, AddMode mode = DontChangeKABC ) ; + + /** + * @brief Create a new contact, adding it to an existing metacontact + * + * If a contact for this account with ID @p contactId is not already on the contact list, + * a new contact with that ID is created, and added to the metacontact @p parent. + * + * @param contactId the @ref Contact::contactId of the contact to create + * @param parent the parent metacontact (must not be 0) + * @param mode the mode used to add the contact. See addContact(const QString&,const QString&,Group*,AddMode) for details. + * + * @return @c true if creation of the contact succeeded or the contact was already in the list, + * @c false otherwise. + */ + bool addContact( const QString &contactId, MetaContact *parent, AddMode mode = DontChangeKABC ); + + /** + * @brief Indicate whether the account is connected at all. + * + * This is a convenience method that calls @ref Contact::isOnline() on @ref myself(). + * This function is safe to call if @ref setMyself() has not been called yet. + * + * @see @ref isConnectedChanged() + */ + bool isConnected() const; + + /** + * @brief Indicate whether the account is away. + * + * This is a convenience method that queries @ref Contact::onlineStatus() on @ref myself(). + * This function is safe to call if @ref setMyself() has not been called yet. + */ + bool isAway() const; + + /** + * Return the @ref KConfigGroup used to write and read special properties + * + * "Protocol", "AccountId" , "Color", "AutoConnect", "Priority", "Enabled" , "Icon" are reserved keyword + * already in use in that group. + * + * for compatibility, try to not use key that start with a uppercase + */ + KConfigGroup *configGroup() const; + + /** + * @brief Remove the account from the server. + * + * Reimplement this if your protocol supports removing the accounts from the server. + * This function is called by @ref AccountManager::removeAccount typically when you remove the + * account on the account config page. + * + * You should add a confirmation message box before removing the account. The default + * implementation does nothing. + * + * @return @c false only if the user requested for the account to be deleted, and deleting the + * account failed. Returns @c true in all other cases. + */ + virtual bool removeAccount(); + + /** + * \return a pointer to the blacklist of the account + */ + BlackLister* blackLister(); + + /** + * \return @c true if the contact with ID @p contactId is in the blacklist, @c false otherwise. + */ + virtual bool isBlocked( const QString &contactId ); + +protected: + /** + * \brief Set the 'myself' contact. + * + * This contact must be defined for every account, because it holds the online status + * of the account. You must call this function in the constructor of your account. + * + * The myself contact can't be deleted as long as the account still exists. The myself + * contact is used as a member of every ChatSession involving this account. myself's + * contactId should be the accountID. The online status of the myself contact represents + * the account's status. + * + * The myself should have the @ref ContactList::myself() as parent metacontact + * + */ + void setMyself( Contact *myself ); + + /** + * \brief Create a new contact in the specified metacontact + * + * You shouldn't ever call this method yourself. To add contacts, use @ref addContact(). + * + * This method is called by @ref addContact(). In this method, you should create the + * new custom @ref Contact, using @p parentContact as the parent. + * + * If the metacontact is not temporary and the protocol supports it, you can add the + * contact to the server. + * + * @param contactId the ID of the contact to create + * @param parentContact the metacontact to add this contact to + * @return @c true if creating the contact succeeded, @c false on failure. + */ + virtual bool createContact( const QString &contactId, MetaContact *parentContact ) =0; + + + /** + * \brief Sets the account label + * + * @param label The label to set + */ + void setAccountLabel( const QString &label ); + +protected slots: + /** + * \brief The service has been disconnected + * + * You have to call this method when you are disconnected. Depending on the value of + * @p reason, this function may attempt to reconnect to the server. + * + * - BadPassword will ask again for the password + * - OtherClient will show a message box + * + * @param reason the reason for the disconnection. + */ + virtual void disconnected( Kopete::Account::DisconnectReason reason ); + + /** + * @brief Sets the online status of all contacts in this account to the same value + * + * Some protocols do not provide status-changed events for all contacts when an account + * becomes connected or disconnected. For such protocols, this function may be useful + * to set all contacts offline. + * + * Calls @ref Kopete::Contact::setOnlineStatus on all contacts of this account (except the + * @ref myself() contact), passing @p status as the status. + * + * @param status the status to set all contacts of this account except @ref myself() to. + */ + void setAllContactsStatus( const Kopete::OnlineStatus &status ); + +signals: + /** + * The color of the account has been changed + * + * also emited when the icon change + * @todo probably rename to accountIconChanged + */ + void colorChanged( const QColor & ); + + /** + * Emitted when the account is deleted. + * @warning emitted in the Account destructor. It is not safe to call any functions on @p account. + */ + void accountDestroyed( const Kopete::Account *account ); + + /** + * Emitted whenever @ref isConnected() changes. + */ + void isConnectedChanged(); + +private: + /** + * @internal + * Reads the configuration information of the account from KConfig. + */ + void readConfig(); + +public: + /** + * @internal + * Register a new Contact with the account. This should be called @em only from the + * @ref Contact constructor, not from anywhere else (not even a derived class). + */ + void registerContact( Contact *c ); + +public slots: + /** + * @brief Go online for this service. + * + * @param initialStatus is the status to connect with. If it is an invalid status for this + * account, the default online for the account should be used. + */ + virtual void connect( const Kopete::OnlineStatus& initialStatus = OnlineStatus() ) = 0; + + /** + * @brief Go offline for this service. + * + * If the service is connecting, you should abort the connection. + * + * You should call the @ref disconnected function from this function. + */ + virtual void disconnect( ) = 0 ; + +public slots: + /** + * If @p away is @c true, set the account away with away message @p reason. Otherwise, + * set the account to not be away. + * + * @todo change ; make use of setOnlineStatus + */ + virtual void setAway( bool away, const QString &reason = QString::null ); + + /** + * Reimplement this function to set the online status + * @param status is the new status + * @param reason is the away message to set. + * @note If needed, you need to connect. if the offline status is given, you should disconnect + */ + virtual void setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason = QString::null ) = 0; + + /** + * Display the edit account widget for the account + */ + void editAccount( QWidget* parent = 0L ); + + /** + * Add a user to the blacklist. The default implementation calls + * blackList()->addContact( contactId ) + * + * @param contactId the contact to be added to the blacklist + */ + virtual void block( const QString &contactId ); + + /** + * Remove a user from the blacklist. The default implementation calls + * blackList()->removeContact( contactId ) + * + * @param contactId the contact to be removed from the blacklist + */ + virtual void unblock( const QString &contactId ); + +private slots: + /** + * Restore online status and status message on reconnect. + */ + virtual void reconnect(); + + /** + * Track the deletion of a Contact and clean up + */ + void contactDestroyed( Kopete::Contact * ); + + /** + * The @ref myself() contact's online status changed. + */ + void slotOnlineStatusChanged( Kopete::Contact *contact, const Kopete::OnlineStatus &newStatus, const Kopete::OnlineStatus &oldStatus ); + + /** + * The @ref myself() contact's property changed. + */ + void slotContactPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ); + + /** + * Stop the suppression of status notification (connected to a timer) + */ + void slotStopSuppression(); + +private: + class Private; + Private *d; + +protected: + virtual void virtual_hook( uint id, void* data); + +public: + /** + * @todo remove + * @deprecated use configGroup + */ + void setPluginData( Plugin* /*plugin*/, const QString &key, const QString &value ) KDE_DEPRECATED; + + /** + * @todo remove + * @deprecated use configGroup + */ + QString pluginData( Plugin* /*plugin*/, const QString &key ) const KDE_DEPRECATED; +}; + +} //END namespace Kopete + +#endif + diff --git a/kopete/libkopete/kopeteaccountmanager.cpp b/kopete/libkopete/kopeteaccountmanager.cpp new file mode 100644 index 00000000..b00f080e --- /dev/null +++ b/kopete/libkopete/kopeteaccountmanager.cpp @@ -0,0 +1,440 @@ +/* + kopeteaccountmanager.cpp - Kopete Account Manager + + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2003-2004 by Olivier Goffart + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteaccountmanager.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "kopeteaccount.h" +#include "kopeteaway.h" +#include "kopeteprotocol.h" +#include "kopetecontact.h" +#include "kopetecontactlist.h" +#include "kopetepluginmanager.h" +#include "kopeteonlinestatus.h" +#include "kopeteonlinestatusmanager.h" +#include "kopetemetacontact.h" +#include "kopetegroup.h" + +namespace Kopete { + +class AccountManager::Private +{ +public: + + class AccountPtrList : public QPtrList + { + protected: + int compareItems( AccountPtrList::Item a, AccountPtrList::Item b ) + { + uint priority1 = static_cast(a)->priority(); + uint priority2 = static_cast(b)->priority(); + + if( a==b ) //two account are equal only if they are equal :-) + return 0; // remember than an account can be only once on the list, but two account may have the same priority when loading + else if( priority1 > priority2 ) + return 1; + else + return -1; + } + } accounts; + +}; + +AccountManager * AccountManager::s_self = 0L; + +AccountManager * AccountManager::self() +{ + if ( !s_self ) + s_self = new AccountManager; + + return s_self; +} + + +AccountManager::AccountManager() +: QObject( qApp, "KopeteAccountManager" ) +{ + d = new Private; +} + + +AccountManager::~AccountManager() +{ + s_self = 0L; + + delete d; +} + +bool AccountManager::isAnyAccountConnected() +{ + for ( QPtrListIterator it( d->accounts ); it.current(); ++it ) + { + if(it.current()->isConnected()) + return true; + } + return false; +} + +void AccountManager::connectAll() +{ + for ( QPtrListIterator it( d->accounts ); it.current(); ++it ) + if(!it.current()->excludeConnect()) + it.current()->connect(); +} + +void AccountManager::setAvailableAll( const QString &awayReason ) +{ + Away::setGlobalAway( false ); + bool anyConnected = isAnyAccountConnected(); + + for ( QPtrListIterator it( d->accounts ); it.current(); ++it ) + { + if ( anyConnected ) + { + if ( it.current()->isConnected() ) + it.current()->setAway( false, awayReason ); + } + else + if(!it.current()->excludeConnect()) + it.current()->connect(); + } +} + +void AccountManager::disconnectAll() +{ + for ( QPtrListIterator it( d->accounts ); it.current(); ++it ) + it.current()->disconnect(); +} + +void AccountManager::setAwayAll( const QString &awayReason, bool away ) +{ + Away::setGlobalAway( true ); + bool anyConnected = isAnyAccountConnected(); + + for ( QPtrListIterator it( d->accounts ); it.current(); ++it ) + { + // FIXME: ICQ's invisible online should be set to invisible away + Contact *self = it.current()->myself(); + bool isInvisible = self && self->onlineStatus().status() == OnlineStatus::Invisible; + if ( anyConnected ) + { + if ( it.current()->isConnected() && !isInvisible ) + it.current()->setAway( away, awayReason ); + } + else + { + if ( !it.current()->excludeConnect() && !isInvisible ) + it.current()->setAway( away, awayReason ); + } + } +} + +void AccountManager::setOnlineStatus( uint category , const QString& awayMessage, uint flags ) +{ + OnlineStatusManager::Categories katgor=(OnlineStatusManager::Categories)category; + bool anyConnected = isAnyAccountConnected(); + + for ( QPtrListIterator it( d->accounts ); it.current(); ++it ) + { + Account *account = it.current(); + Kopete::OnlineStatus status = OnlineStatusManager::self()->onlineStatus(account->protocol() , katgor); + if ( anyConnected ) + { + if ( account->isConnected() || ( (flags & ConnectIfOffline) && !account->excludeConnect() ) ) + account->setOnlineStatus( status , awayMessage ); + } + else + { + if ( !account->excludeConnect() ) + account->setOnlineStatus( status , awayMessage ); + } + } +} + + +QColor AccountManager::guessColor( Protocol *protocol ) const +{ + // In a perfect wold, we should check if the color is actually not used by the account. + // Anyway, this is not really required, It would be a difficult job for about nothing more. + // -- Olivier + int protocolCount = 0; + + for ( QPtrListIterator it( d->accounts ); it.current(); ++it ) + { + if ( it.current()->protocol()->pluginId() == protocol->pluginId() ) + protocolCount++; + } + + // let's figure a color + QColor color; + switch ( protocolCount % 7 ) + { + case 0: + color = QColor(); + break; + case 1: + color = Qt::red; + break; + case 2: + color = Qt::green; + break; + case 3: + color = Qt::blue; + break; + case 4: + color = Qt::yellow; + break; + case 5: + color = Qt::magenta; + break; + case 6: + color = Qt::cyan; + break; + } + + return color; +} + +Account* AccountManager::registerAccount( Account *account ) +{ + if( !account || d->accounts.contains( account ) ) + return account; + + if( account->accountId().isEmpty() ) + { + account->deleteLater(); + return 0L; + } + + // If this account already exists, do nothing + for ( QPtrListIterator it( d->accounts ); it.current(); ++it ) + { + if ( ( account->protocol() == it.current()->protocol() ) && ( account->accountId() == it.current()->accountId() ) ) + { + account->deleteLater(); + return 0L; + } + } + + d->accounts.append( account ); + d->accounts.sort(); + + // Connect to the account's status changed signal + connect(account->myself(), SIGNAL(onlineStatusChanged(Kopete::Contact *, + const Kopete::OnlineStatus &, const Kopete::OnlineStatus &)), + this, SLOT(slotAccountOnlineStatusChanged(Kopete::Contact *, + const Kopete::OnlineStatus &, const Kopete::OnlineStatus &))); + + connect(account, SIGNAL(accountDestroyed(const Kopete::Account *)) , this, SLOT( unregisterAccount(const Kopete::Account *) )); + + emit accountRegistered( account ); + return account; +} + +void AccountManager::unregisterAccount( const Account *account ) +{ + kdDebug( 14010 ) << k_funcinfo << "Unregistering account " << account->accountId() << endl; + d->accounts.remove( account ); + emit accountUnregistered( account ); +} + +const QPtrList& AccountManager::accounts() const +{ + return d->accounts; +} + +QDict AccountManager::accounts( const Protocol *protocol ) const +{ + QDict dict; + for ( QPtrListIterator it( d->accounts ); it.current(); ++it ) + { + if ( it.current()->protocol() == protocol && !it.current()->accountId().isNull() ) + dict.insert( it.current()->accountId(), it.current() ); + } + + return dict; +} + +Account * AccountManager::findAccount( const QString &protocolId, const QString &accountId ) +{ + for ( QPtrListIterator it( d->accounts ); it.current(); ++it ) + { + if ( it.current()->protocol()->pluginId() == protocolId && it.current()->accountId() == accountId ) + return it.current(); + } + return 0L; +} + +void AccountManager::removeAccount( Account *account ) +{ + if(!account->removeAccount()) + return; + + Protocol *protocol = account->protocol(); + + + KConfigGroup *configgroup = account->configGroup(); + + // Clean up the contact list + QDictIterator it( account->contacts() ); + for ( ; it.current(); ++it ) + { + Contact* c = it.current(); + MetaContact* mc = c->metaContact(); + if ( mc == ContactList::self()->myself() ) + continue; + mc->removeContact( c ); + c->deleteLater(); + if ( mc->contacts().count() == 0 ) //we can delete the metacontact + { + //get the first group and it's members + Group* group = mc->groups().first(); + QPtrList groupMembers = group->members(); + ContactList::self()->removeMetaContact( mc ); + if ( groupMembers.count() == 1 && groupMembers.findRef( mc ) != -1 ) + ContactList::self()->removeGroup( group ); + } + } + + // Clean up the account list + d->accounts.remove( account ); + + // Clean up configuration + configgroup->deleteGroup(); + configgroup->sync(); + + delete account; + + if ( accounts( protocol ).isEmpty() ) + { + // FIXME: pluginId() should return the internal name and not the class name, so + // we can get rid of this hack - Olivier/Martijn + QString protocolName = protocol->pluginId().remove( QString::fromLatin1( "Protocol" ) ).lower(); + + PluginManager::self()->setPluginEnabled( protocolName, false ); + PluginManager::self()->unloadPlugin( protocolName ); + } +} + +void AccountManager::save() +{ + //kdDebug( 14010 ) << k_funcinfo << endl; + d->accounts.sort(); + + for ( QPtrListIterator it( d->accounts ); it.current(); ++it ) + { + KConfigBase *config = it.current()->configGroup(); + + config->writeEntry( "Protocol", it.current()->protocol()->pluginId() ); + config->writeEntry( "AccountId", it.current()->accountId() ); + } + + KGlobal::config()->sync(); +} + +void AccountManager::load() +{ + connect( PluginManager::self(), SIGNAL( pluginLoaded( Kopete::Plugin * ) ), + this, SLOT( slotPluginLoaded( Kopete::Plugin * ) ) ); + + // Iterate over all groups that start with "Account_" as those are accounts + // and load the required protocols if the account is enabled. + // Don't try to optimize duplicate calls out, the plugin queue is smart enough + // (and fast enough) to handle that without adding complexity here + KConfig *config = KGlobal::config(); + QStringList accountGroups = config->groupList().grep( QRegExp( QString::fromLatin1( "^Account_" ) ) ); + for ( QStringList::Iterator it = accountGroups.begin(); it != accountGroups.end(); ++it ) + { + config->setGroup( *it ); + + QString protocol = config->readEntry( "Protocol" ); + if ( protocol.endsWith( QString::fromLatin1( "Protocol" ) ) ) + protocol = QString::fromLatin1( "kopete_" ) + protocol.lower().remove( QString::fromLatin1( "protocol" ) ); + + if ( config->readBoolEntry( "Enabled", true ) ) + PluginManager::self()->loadPlugin( protocol, PluginManager::LoadAsync ); + } +} + +void AccountManager::slotPluginLoaded( Plugin *plugin ) +{ + Protocol* protocol = dynamic_cast( plugin ); + if ( !protocol ) + return; + + // Iterate over all groups that start with "Account_" as those are accounts + // and parse them if they are from this protocol + KConfig *config = KGlobal::config(); + QStringList accountGroups = config->groupList().grep( QRegExp( QString::fromLatin1( "^Account_" ) ) ); + for ( QStringList::Iterator it = accountGroups.begin(); it != accountGroups.end(); ++it ) + { + config->setGroup( *it ); + + if ( config->readEntry( "Protocol" ) != protocol->pluginId() ) + continue; + + // There's no GUI for this, but developers may want to disable an account. + if ( !config->readBoolEntry( "Enabled", true ) ) + continue; + + QString accountId = config->readEntry( "AccountId" ); + if ( accountId.isEmpty() ) + { + kdWarning( 14010 ) << k_funcinfo << + "Not creating account for empty accountId." << endl; + continue; + } + + kdDebug( 14010 ) << k_funcinfo << + "Creating account for '" << accountId << "'" << endl; + + Account *account = 0L; + account = registerAccount( protocol->createNewAccount( accountId ) ); + if ( !account ) + { + kdWarning( 14010 ) << k_funcinfo << + "Failed to create account for '" << accountId << "'" << endl; + continue; + } + } +} + +void AccountManager::slotAccountOnlineStatusChanged(Contact *c, + const OnlineStatus &oldStatus, const OnlineStatus &newStatus) +{ + Account *account = c->account(); + if (!account) + return; + + //kdDebug(14010) << k_funcinfo << endl; + emit accountOnlineStatusChanged(account, oldStatus, newStatus); +} + +} //END namespace Kopete + +#include "kopeteaccountmanager.moc" +// vim: set noet ts=4 sts=4 sw=4: +// kate: tab-width 4; indent-mode csands; diff --git a/kopete/libkopete/kopeteaccountmanager.h b/kopete/libkopete/kopeteaccountmanager.h new file mode 100644 index 00000000..ed0c939a --- /dev/null +++ b/kopete/libkopete/kopeteaccountmanager.h @@ -0,0 +1,233 @@ +/* + kopeteaccountmanager.h - Kopete Account Manager + + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2003-2004 by Olivier Goffart + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __kopeteaccountmanager_h__ +#define __kopeteaccountmanager_h__ + +#include +#include +#include + +#include "kopete_export.h" + + +namespace Kopete { + +class Account; +class Plugin; +class Protocol; +class Contact; +class OnlineStatus; + +/** + * AccountManager manages all defined accounts in Kopete. You can + * query them and globally set them all online or offline from here. + * + * AccountManager is a singleton, you may uses it with @ref AccountManager::self() + * + * @author Martijn Klingens + * @author Olivier Goffart + */ +class KOPETE_EXPORT AccountManager : public QObject +{ + Q_OBJECT + +public: + /** + * \brief Retrieve the instance of AccountManager. + * + * The account manager is a singleton class of which only a single + * instance will exist. If no manager exists yet this function will + * create one for you. + * + * \return the instance of the AccountManager + */ + static AccountManager* self(); + + ~AccountManager(); + + /** + * \brief Retrieve the list of accounts + * \return a list of all the accounts + */ + const QPtrList & accounts() const; + + /** + * \brief Retrieve a QDict of accounts for the given protocol + * + * The list is guaranteed to contain only accounts for the specified + * protocol + * \param p is the Protocol object you want accounts for + */ + QDict accounts( const Protocol *p ) const; + + /** + * \brief Return the account asked + * \param protocolId is the ID for the protocol + * \param accountId is the ID for the account you want + * \return the Account object found or NULL if no account was found + */ + Account* findAccount( const QString &protocolId, const QString &accountId ); + + /** + * \brief Delete the account and clean the config data + * + * This is praticaly called by the account config page when you remove the account. + */ + void removeAccount( Account *account ); + + /** + * \brief Guess the color for a new account + * + * Guesses a color for the next account of a given protocol based on the already registered colors + * \return the color guessed for the account + */ + QColor guessColor( Protocol *protocol ) const ; + + /** + * @brief Register the account. + * + * This adds the account in the manager's account list. + * It will check no accounts already exist with the same ID, if any, the account is deleted. and not added + * + * @return @p account, or 0L if the account was deleted because id collision + */ + Account *registerAccount( Account *account ); + + + /** + * Flag to be used in setOnlineStatus + * + * @c ConnectIfOffline : if set, this will connect offlines account with the status. + */ + enum SetOnlineStatusFlag { ConnectIfOffline=0x01 }; + + +public slots: + /** + * \brief Connect all accounts at once. + * + * Connect every account if the flag excludeConnect is false + * @see @ref Account::excludeConnect() + */ + void connectAll(); + + /** + * \brief Disconnect all accounts at once. + */ + void disconnectAll(); + + /** + * @brief Set all accounts a status in the specified category + * + * Account that are offline will not be connected, unless the ConnectIfOffline flag is set. + * + * @param category is one of the Kopete::OnlineStatusManager::Categories + * @param awayMessage is the new away message + * @param flags is a bitmask of SetOnlineStatusFlag + */ + void setOnlineStatus( /*Kopete::OnlineStatusManager::Categories*/ uint category, + const QString& awayMessage = QString::null, uint flags=0); + + /** + * \brief Set all accounts to away at once. + * + * All account that are connected, but not invisible will be set to away + * @see Account::setAway + * @param awayReason is the away message that will be set. + * @param away decides whether the message is away/non-away + */ + void setAwayAll( const QString &awayReason = QString::null, bool away=true ); + + /** + * \brief Connect or make available every account. + * Make all accounts Available, by setting status, and connecting if necessary. + * Accounts are connected based on their excludeConnect() setting. + * Accounts which are already connected are controlled regardless of their excludeConnect() setting. + * This is a slot, so you can connect directly to it from e.g. a KAction. + * @param awayReason is the away(status) message that will be set. + */ + void setAvailableAll( const QString &awayReason = QString::null ); + + /** + * \internal + * Save the account data to KConfig + */ + void save(); + + /** + * \internal + * Load the account data from KConfig + */ + void load(); + + + +signals: + /** + * \brief Signals when an account is ready for use + */ + void accountRegistered( Kopete::Account *account ); + + /** + * \brief Signals when an account has been unregistered + * + * At this state, we are already in the Account destructor. + */ + void accountUnregistered( const Kopete::Account *account ); + + /** + * \brief An account has changed its onlinestatus + * Technically this monitors Account::myself() onlinestatus changes + * \param account Account which changed its onlinestatus + * \param oldStatus The online status before the change + * \param newStatus The new online status + */ + void accountOnlineStatusChanged(Kopete::Account *account, + const Kopete::OnlineStatus &oldStatus, const Kopete::OnlineStatus &newStatus); + +private: + /** + * Private constructor, because we're a singleton + */ + AccountManager(); + +private slots: + void slotPluginLoaded( Kopete::Plugin *plugin ); + void slotAccountOnlineStatusChanged(Kopete::Contact *c, + const Kopete::OnlineStatus &oldStatus, const Kopete::OnlineStatus &newStatus); + + /** + * \internal + * Unregister the account. + */ + void unregisterAccount( const Kopete::Account *account ); + +private: + bool isAnyAccountConnected(); + static AccountManager *s_self; + class Private; + Private *d; +}; + +} //END namespace Kopete + + +#endif + + diff --git a/kopete/libkopete/kopeteaway.cpp b/kopete/libkopete/kopeteaway.cpp new file mode 100644 index 00000000..fa500e0c --- /dev/null +++ b/kopete/libkopete/kopeteaway.cpp @@ -0,0 +1,525 @@ +/* + kopeteaway.cpp - Kopete Away + + Copyright (c) 2002 by Hendrik vom Lehn + Copyright (c) 2003 Olivier Goffart + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "kopeteaway.h" + +#include "kopeteaccountmanager.h" +#include "kopeteaccount.h" +#include "kopeteonlinestatus.h" +#include "kopeteonlinestatusmanager.h" +#include "kopetecontact.h" +#include "kopeteprefs.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef Q_WS_X11 +#include +#include +#include +// The following include is to make --enable-final work +#include + +#ifdef HAVE_XSCREENSAVER +#define HasScreenSaver +#include +#endif +#endif // Q_WS_X11 + +// As this is an untested X extension we better leave it off +#undef HAVE_XIDLE +#undef HasXidle + + +struct KopeteAwayPrivate +{ + QString awayMessage; + QString autoAwayMessage; + bool useAutoAwayMessage; + bool globalAway; + QStringList awayMessageList; + QTime idleTime; + QTimer *timer; + bool autoaway; + bool goAvailable; + int awayTimeout; + bool useAutoAway; + QPtrList autoAwayAccounts; + + int mouse_x; + int mouse_y; + unsigned int mouse_mask; +#ifdef Q_WS_X11 + Window root; /* root window the pointer is on */ + Screen* screen; /* screen the pointer is on */ + + Time xIdleTime; +#endif + bool useXidle; + bool useMit; +}; + +Kopete::Away *Kopete::Away::instance = 0L; + +Kopete::Away::Away() : QObject( kapp , "Kopete::Away") +{ + int dummy = 0; + dummy = dummy; // shut up + + d = new KopeteAwayPrivate; + + // Set up the away messages + d->awayMessage = QString::null; + d->autoAwayMessage = QString::null; + d->useAutoAwayMessage = false; + d->globalAway = false; + d->autoaway = false; + d->useAutoAway = true; + + // Empty the list + d->awayMessageList.clear(); + + // set the XAutoLock info +#ifdef Q_WS_X11 + Display *dsp = qt_xdisplay(); +#endif + d->mouse_x = d->mouse_y=0; + d->mouse_mask = 0; +#ifdef Q_WS_X11 + d->root = DefaultRootWindow (dsp); + d->screen = ScreenOfDisplay (dsp, DefaultScreen (dsp)); +#endif + d->useXidle = false; + d->useMit = false; +#ifdef HasXidle + d->useXidle = XidleQueryExtension(qt_xdisplay(), &dummy, &dummy); +#endif +#ifdef HasScreenSaver + if(!d->useXidle) + d->useMit = XScreenSaverQueryExtension(qt_xdisplay(), &dummy, &dummy); +#endif +#ifdef Q_WS_X11 + d->xIdleTime = 0; +#endif + kdDebug(14010) << k_funcinfo << "Idle detection methods:" << endl; + kdDebug(14010) << k_funcinfo << "\tKScreensaverIface::isBlanked()" << endl; +#ifdef Q_WS_X11 + kdDebug(14010) << k_funcinfo << "\tX11 XQueryPointer()" << endl; +#endif + if (d->useXidle) + { + kdDebug(14010) << k_funcinfo << "\tX11 Xidle extension" << endl; + } + if (d->useMit) + { + kdDebug(14010) << k_funcinfo << "\tX11 MIT Screensaver extension" << endl; + } + + + load(); + KSettings::Dispatcher::self()->registerInstance( KGlobal::instance(), this, SLOT( load() ) ); + // Set up the config object + KConfig *config = KGlobal::config(); + /* Load the saved away messages */ + config->setGroup("Away Messages"); + + // Away Messages + if(config->hasKey("Messages")) + { + d->awayMessageList = config->readListEntry("Messages"); + } + else if(config->hasKey("Titles")) // Old config format + { + QStringList titles = config->readListEntry("Titles"); // Get the titles + for(QStringList::iterator i = titles.begin(); i != titles.end(); ++i) + { + d->awayMessageList.append( config->readEntry(*i) ); // And add it to the list + } + + /* Save this list to disk */ + save(); + } + else + { + d->awayMessageList.append( i18n( "Sorry, I am busy right now" ) ); + d->awayMessageList.append( i18n( "I am gone right now, but I will be back later" ) ); + + /* Save this list to disk */ + save(); + } + + // Auto away message + if(config->hasKey("AutoAwayMessage")) + { + d->autoAwayMessage = config->readEntry("AutoAwayMessage"); + } + else + { + d->autoAwayMessage = i18n( "I am gone right now, but I will be back later" ); + + // Save the default auto away message to disk + save(); + } + + // init the timer + d->timer = new QTimer(this, "AwayTimer"); + connect(d->timer, SIGNAL(timeout()), this, SLOT(slotTimerTimeout())); + d->timer->start(4000); + + //init the time and other + setActive(); +} + +Kopete::Away::~Away() +{ + if(this == instance) + instance = 0L; + delete d; +} + +QString Kopete::Away::message() +{ + return getInstance()->d->awayMessage; +} + +QString Kopete::Away::autoAwayMessage() +{ + return getInstance()->d->autoAwayMessage; +} + +void Kopete::Away::setGlobalAwayMessage(const QString &message) +{ + if( !message.isEmpty() ) + { + kdDebug(14010) << k_funcinfo << + "Setting global away message: " << message << endl; + d->awayMessage = message; + } +} + +void Kopete::Away::setAutoAwayMessage(const QString &message) +{ + if( !message.isEmpty() ) + { + kdDebug(14010) << k_funcinfo << + "Setting auto away message: " << message << endl; + d->autoAwayMessage = message; + + // Save the new auto away message to disk + save(); + } +} + +Kopete::Away *Kopete::Away::getInstance() +{ + if (!instance) + instance = new Kopete::Away; + return instance; +} + +bool Kopete::Away::globalAway() +{ + return getInstance()->d->globalAway; +} + +void Kopete::Away::setGlobalAway(bool status) +{ + getInstance()->d->globalAway = status; +} + +void Kopete::Away::save() +{ + KConfig *config = KGlobal::config(); + /* Set the away message settings in the Away Messages config group */ + config->setGroup("Away Messages"); + config->writeEntry("Messages", d->awayMessageList); + config->writeEntry("AutoAwayMessage", d->autoAwayMessage); + config->sync(); + + emit( messagesChanged() ); +} + +void Kopete::Away::load() +{ + KConfig *config = KGlobal::config(); + config->setGroup("AutoAway"); + d->awayTimeout=config->readNumEntry("Timeout", 600); + d->goAvailable=config->readBoolEntry("GoAvailable", true); + d->useAutoAway=config->readBoolEntry("UseAutoAway", true); + d->useAutoAwayMessage=config->readBoolEntry("UseAutoAwayMessage", false); +} + +QStringList Kopete::Away::getMessages() +{ + return d->awayMessageList; +} + +QString Kopete::Away::getMessage( uint messageNumber ) +{ + QStringList::iterator it = d->awayMessageList.at( messageNumber ); + if( it != d->awayMessageList.end() ) + { + QString str = *it; + d->awayMessageList.prepend( str ); + d->awayMessageList.remove( it ); + save(); + return str; + } + else + { + return QString::null; + } +} + +void Kopete::Away::addMessage(const QString &message) +{ + d->awayMessageList.prepend( message ); + if( (int)d->awayMessageList.count() > KopetePrefs::prefs()->rememberedMessages() ) + d->awayMessageList.pop_back(); + save(); +} + +long int Kopete::Away::idleTime() +{ + //FIXME: the time is reset to zero if more than 24 hours are elapsed + // we can imagine someone who leave his PC for several weeks + return (d->idleTime.elapsed() / 1000); +} + +void Kopete::Away::slotTimerTimeout() +{ + // Time to check whether we're active or autoaway. We basically have two + // bits of info to go on - KDE's screensaver status + // (KScreenSaverIface::isBlanked()) and the X11 activity detection. + // + // Note that isBlanked() is a slight of a misnomer. It returns true if we're: + // - using a non-locking screensaver, which is running, or + // - using a locking screensaver which is still locked, regardless of + // whether the user is trying to unlock it right now + // Either way, it's only worth checking for activity if the screensaver + // isn't blanked/locked, because activity while blanked is impossible and + // activity while locked never matters (if there is any, it's probably just + // the cleaner wiping the keyboard :). + + + /* we should be able to respond to KDesktop queries to avoid a deadlock, so we allow the event loop to be called */ + static bool rentrency_protection=false; + if(rentrency_protection) + return; + rentrency_protection=true; + DCOPRef screenSaver("kdesktop", "KScreensaverIface"); + DCOPReply isBlanked = screenSaver.callExt("isBlanked" , DCOPRef::UseEventLoop, 10); + rentrency_protection=false; + if(!instance) //this may have been deleted in the event loop + return; + if (!(isBlanked.isValid() && isBlanked.type == "bool" && ((bool)isBlanked))) + { + // DCOP failed, or returned something odd, or the screensaver is + // inactive, so check for activity the X11 way. It's only worth + // checking for autoaway if there's no activity, and because + // Screensaver blanking/locking implies autoAway activation (see + // KopeteIface::KopeteIface()), only worth checking autoAway when the + // screensaver isn't running. + if (isActivity()) + { + setActive(); + } + else if (!d->autoaway && d->useAutoAway && idleTime() > d->awayTimeout) + { + setAutoAway(); + } + } +} + +bool Kopete::Away::isActivity() +{ + // Copyright (c) 1999 Martin R. Jones + // + // KDE screensaver engine + // + // This module is a heavily modified xautolock. + // In fact as of KDE 2.0 this code is practically unrecognisable as xautolock. + + bool activity = false; + +#ifdef Q_WS_X11 + Display *dsp = qt_xdisplay(); + Window dummy_w; + int dummy_c; + unsigned int mask; /* modifier mask */ + int root_x; + int root_y; + + /* + * Find out whether the pointer has moved. Using XQueryPointer for this + * is gross, but it also is the only way never to mess up propagation + * of pointer events. + * + * Remark : Unlike XNextEvent(), XPending () doesn't notice if the + * connection to the server is lost. For this reason, earlier + * versions of xautolock periodically called XNoOp (). But + * why not let XQueryPointer () do the job for us, since + * we now call that periodically anyway? + */ + if (!XQueryPointer (dsp, d->root, &(d->root), &dummy_w, &root_x, &root_y, + &dummy_c, &dummy_c, &mask)) + { + /* + * Pointer has moved to another screen, so let's find out which one. + */ + for (int i = 0; i < ScreenCount(dsp); i++) + { + if (d->root == RootWindow(dsp, i)) + { + d->screen = ScreenOfDisplay (dsp, i); + break; + } + } + } + + // ================================================================================= + + Time xIdleTime = 0; // millisecs since last input event + + #ifdef HasXidle + if (d->useXidle) + { + XGetIdleTime(dsp, &xIdleTime); + } + else + #endif /* HasXIdle */ + + { + #ifdef HasScreenSaver + if(d->useMit) + { + static XScreenSaverInfo* mitInfo = 0; + if (!mitInfo) mitInfo = XScreenSaverAllocInfo(); + XScreenSaverQueryInfo (dsp, d->root, mitInfo); + xIdleTime = mitInfo->idle; + } + #endif /* HasScreenSaver */ + } + + // ================================================================================= + + // Only check idle time if we have some way of measuring it, otherwise if + // we've neither Mit nor Xidle it'll still be zero and we'll always appear active. + // FIXME: what problem does the 2000ms fudge solve? + if (root_x != d->mouse_x || root_y != d->mouse_y || mask != d->mouse_mask + || ((d->useXidle || d->useMit) && xIdleTime < d->xIdleTime + 2000)) + { + // -1 => just gone autoaway, ignore apparent activity this time round + // anything else => genuine activity + // See setAutoAway(). + if (d->mouse_x != -1) + { + activity = true; + } + d->mouse_x = root_x; + d->mouse_y = root_y; + d->mouse_mask = mask; + d->xIdleTime = xIdleTime; + } +#endif // Q_WS_X11 + // ================================================================================= + + return activity; +} + +void Kopete::Away::setActive() +{ +// kdDebug(14010) << k_funcinfo << "Found activity on desktop, resetting away timer" << endl; + d->idleTime.start(); + + if(d->autoaway) + { + d->autoaway = false; + emit activity(); + if (d->goAvailable) + { + d->autoAwayAccounts.setAutoDelete(false); + for(Kopete::Account *i=d->autoAwayAccounts.first() ; i; i=d->autoAwayAccounts.current() ) + { + if(i->isConnected() && i->isAway()) + { + i->setOnlineStatus( Kopete::OnlineStatusManager::self()->onlineStatus( i->protocol() , + Kopete::OnlineStatusManager::Online ) ); + } + + // remove() makes the next entry in the list the current one, + // that's why we use current() above + d->autoAwayAccounts.remove(); + } + } + } +} + +void Kopete::Away::setAutoAway() +{ + // A value of -1 in mouse_x indicates to checkActivity() that next time it + // fires it should ignore any apparent idle/mouse/keyboard changes. + // I think the point of this is that if you manually start the screensaver + // then there'll unavoidably be some residual mouse/keyboard activity + // that should be ignored. + d->mouse_x = -1; + +// kdDebug(14010) << k_funcinfo << "Going AutoAway!" << endl; + d->autoaway = true; + + // Set all accounts that are not away already to away. + // We remember them so later we only set the accounts to + // available that we set to away (and not the user). + QPtrList accounts = Kopete::AccountManager::self()->accounts(); + for(Kopete::Account *i=accounts.first() ; i; i=accounts.next() ) + { + if(i->myself()->onlineStatus().status() == Kopete::OnlineStatus::Online) + { + d->autoAwayAccounts.append(i); + + if(d->useAutoAwayMessage) + { + // Display a specific away message + i->setOnlineStatus( Kopete::OnlineStatusManager::self()->onlineStatus( i->protocol() , + Kopete::OnlineStatusManager::Idle ) , + getInstance()->d->autoAwayMessage); + } + else + { + // Display the last away message used + i->setOnlineStatus( Kopete::OnlineStatusManager::self()->onlineStatus( i->protocol() , + Kopete::OnlineStatusManager::Idle ) , + getInstance()->d->awayMessage); + } + } + } +} + +#include "kopeteaway.moc" +// vim: set et ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteaway.h b/kopete/libkopete/kopeteaway.h new file mode 100644 index 00000000..544dff75 --- /dev/null +++ b/kopete/libkopete/kopeteaway.h @@ -0,0 +1,217 @@ +/* + kopeteaway.h - Kopete Away + + Copyright (c) 2002 by Hendrik vom Lehn + Copyright (c) 2003 Olivier Goffart + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEAWAY_HI +#define KOPETEAWAY_HI + +#include +#include +#include + +#include "kopeteawaydialog.h" +#include "kopete_export.h" + +class QStringList; + +struct KopeteAwayPrivate; + +class KopeteGlobalAwayDialog; +class KopeteAwayDialog; + +namespace Kopete +{ + +/** + * @class Kopete::Away kopeteaway.h + * + * Kopete::Away is a singleton class that manages away messages + * for Kopete. It stores a global away message, as well as + * a list of user defined away messages. + * This class is used by KopeteAwayDialog, which gets it's + * list of user-defined away messages from this. Protocol + * plugins' individual away dialogs should also get away + * messages from this object. + * + * It also handle global Idle Time, and all auto away stuff + * + * @author Hendrik vom Lehn + * @author Chris TenHarmsel + * @author Olivier Goffart + + */ +class KOPETE_EXPORT Away : public QObject +{ +Q_OBJECT + +friend class ::KopeteAwayDialog; + +public: + + /** + * @brief Method to get the single instance of Kopete::Away + * @return Kopete::Away instance pointer + */ + static Away *getInstance(); + + /** + * @brief Gets the current global away message + * @return The global away message + */ + static QString message(); + + /** + * @brief Gets the current global auto away message + * @return The global auto away message + */ + static QString autoAwayMessage(); + + /** + * This method sets the global away message, + * it does not set you away, just sets the message. + * @brief Sets the global away message + * @param message The message you want to set + */ + void setGlobalAwayMessage(const QString &message); + + /** + * This method sets the global auto away message, + * it does not set you away, just sets the message. + * @brief Sets the global auto away message + * @param message The message you want to set + */ + void setAutoAwayMessage(const QString &message); + + /** + * @brief Sets global away for all protocols + */ + static void setGlobalAway(bool status); + + /** + * @brief Indicates global away status + * @return Bool indicating global away status + */ + static bool globalAway(); + + /** + * @brief Function to get the titles of user defined away messages + * @return List of away message titles + * + * This function can be used to retrieve a QStringList of the away message titles, + * these titles can be passed to getMessage(QString title) to retrieve the + * corresponding message. + */ + QStringList getMessages(); + + /** + * @brief Function to get an away message + * @return The away message corresponding to the title + * @param messageNumber Number of the away message to retrieve + * + * This function retrieves the away message that corresponds to the ringbuffer index + * passed in. + */ + QString getMessage( uint messageNumber ); + + /** + * @brief Adds an away message to the ringbuffer + * @param message The away message + * + * This function will add an away message to the ringbuffer of user defined + * away messages. + */ + void addMessage(const QString &message); + + /** + * time in seconds the user has been idle + */ + long int idleTime(); + +private: + Away(); + ~Away(); + + /** + * @brief Saves the away messages to disk + * + * This function will save the current list of away messages to the disk + * using KConfig. It is called automatically. + */ + void save(); + + /** + * @brief Check for activity using X11 methods + * @return true if activity was detected, otherwise false + * + * Attempt to detect activity using a variety of X11 methods. + */ + bool isActivity(); + + //Away( const Away &rhs ); + //Away &operator=( const Away &rhs ); + static Away *instance; + KopeteAwayPrivate *d; + +private slots: + void slotTimerTimeout(); + void load(); + +public slots: + /** + * @brief Mark the user active + * + * Plugins can mark the user active if they discover activity by another way than the mouse or the keyboard + * (example, the motion auto away plugin) + * this will reset the @ref idleTime to 0, and set all protocols to available (online) if the state was + * set automatically to away because of idleness, and if they was previously online + */ + void setActive(); + + /** + * Use this method if you want to go in the autoaway mode. + * This will go autoaway even if the idle time is not yet reached. (and even if the user + * did not selected to go autoaway automaticaly) + * But that will go unaway again when activity will be detected + */ + void setAutoAway(); + +signals: + /** + * @brief Activity was detected + * + * this signal is emit when activity has been discover after being autoAway. + */ + void activity(); + + /** + * @brief Default messages were changed + */ + void messagesChanged(); +}; + +} + +#endif +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopeteawayaction.cpp b/kopete/libkopete/kopeteawayaction.cpp new file mode 100644 index 00000000..84622c7e --- /dev/null +++ b/kopete/libkopete/kopeteawayaction.cpp @@ -0,0 +1,134 @@ +/* + kopeteaway.cpp - Kopete Away Action + + Copyright (c) 2003 Jason Keirstead + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include + +#include "kopeteawayaction.h" +#include "kopeteaway.h" +#include "kopeteonlinestatus.h" + + +namespace Kopete { + +class AwayAction::Private +{ +public: + Private(const OnlineStatus& s) : reasonCount(0) , status(s) {}; + int reasonCount; + OnlineStatus status; +}; + + +AwayAction::AwayAction(const QString &text, const QIconSet &pix, const KShortcut &cut, + const QObject *receiver, const char *slot, QObject *parent, const char *name ) + : KSelectAction(text, pix, cut, parent, name ) , d(new Private( OnlineStatus() ) ) +{ + QObject::connect( Kopete::Away::getInstance(), SIGNAL( messagesChanged() ), + this, SLOT( slotAwayChanged() ) ); + + QObject::connect( this, SIGNAL( awayMessageSelected( const QString & ) ), + receiver, slot ); + + QObject::connect( this, SIGNAL( activated( int ) ), + this, SLOT( slotSelectAway( int ) ) ); + + slotAwayChanged(); +} + +AwayAction::AwayAction( const OnlineStatus& status, const QString &text, const QIconSet &pix, const KShortcut &cut, + const QObject *receiver, const char *slot, QObject *parent, const char *name ) + : KSelectAction(text, pix, cut, parent, name ) , d(new Private( status ) ) +{ + QObject::connect( Kopete::Away::getInstance(), SIGNAL( messagesChanged() ), + this, SLOT( slotAwayChanged() ) ); + + QObject::connect( this, SIGNAL( awayMessageSelected( const Kopete::OnlineStatus &, const QString & ) ), + receiver, slot ); + + QObject::connect( this, SIGNAL( activated( int ) ), + this, SLOT( slotSelectAway( int ) ) ); + + slotAwayChanged(); +} + +AwayAction::~AwayAction() +{ + delete d; +} + +void AwayAction::slotAwayChanged() +{ + QStringList awayMessages = Kopete::Away::getInstance()->getMessages(); + for( QStringList::iterator it = awayMessages.begin(); it != awayMessages.end(); ++it ) + { + (*it) = KStringHandler::rsqueeze( *it ); + } + d->reasonCount = awayMessages.count(); + QStringList menu; + menu << i18n( "No Message" ); + menu << i18n( "New Message..." ); + menu << QString::null ; //separator + menu += awayMessages ; + setItems( menu ); + setCurrentItem( -1 ); +} + +void AwayAction::slotSelectAway( int index ) +{ + //remove that crappy check mark cf bug 119862 + setCurrentItem( -1 ); + + Kopete::Away *mAway = Kopete::Away::getInstance(); + QString awayReason; + + // Index == -1 means this is a result of Global Away all. + // Use the last entered message (0) + if( index == -1 ) + index = 0; + + switch(index) + { + case 0: + awayReason = QString::null; + break; + case 1: + bool ok; + awayReason = KInputDialog::getText( i18n( "New Away Message" ), i18n( "Please enter your away reason:" ) , QString::null , &ok ); + if(!ok) //the user canceled + return; + if( !awayReason.isEmpty() ) + Kopete::Away::getInstance()->addMessage( awayReason ); + break; + case 2: + //not possible case, that's a separator + break; + default: + if( index-3 < d->reasonCount ) + awayReason = mAway->getMessage( index-3 ); + } + + emit awayMessageSelected( awayReason ) ; + emit awayMessageSelected( d->status, awayReason ); +} + +} //END namespace Kopete + +#include "kopeteawayaction.moc" + diff --git a/kopete/libkopete/kopeteawayaction.h b/kopete/libkopete/kopeteawayaction.h new file mode 100644 index 00000000..f8ab9d64 --- /dev/null +++ b/kopete/libkopete/kopeteawayaction.h @@ -0,0 +1,91 @@ +/* + kopetehistorydialog.h - Kopete Away Action + + Copyright (c) 2003 Jason Keirstead + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEAWAYACTION_H +#define KOPETEAWAYACTION_H + +#include +#include +#include + +#include "kopete_export.h" + +namespace Kopete +{ + +class OnlineStatus; + +/** + * @class Kopete::AwayAction + * + * Kopete::AwayAction is a KAction that lets you select an away message + * from the list of predefined away messages, or enter a custom one. + * + * @author Jason Keirstead + */ +class KOPETE_EXPORT AwayAction : public KSelectAction +{ + Q_OBJECT + public: + /** + * Constructor + * @p text, @p pix, @p cut, @p receiver, @p slot, @p parent and + * @p name are all handled by KSelectAction. + **/ + AwayAction(const QString &text, const QIconSet &pix, + const KShortcut &cut, const QObject *receiver, const char *slot, + QObject *parent, const char *name = 0); + + /** + * Constructor + * @param status the OnlineStatus that appears in the signal + * @param slot must have the following signature: ( const OnlineStatus &, const QString & ) + * @p text, @p pix, @p cut, @p receiver, @p slot, @p parent and + * @p name are all handled by KSelectAction. + **/ + AwayAction(const OnlineStatus &status, const QString &text, const QIconSet &pix, + const KShortcut &cut, const QObject *receiver, const char *slot, + QObject *parent, const char *name = 0); + + /** + * Destructor. + */ + ~AwayAction(); + + signals: + /** + * @brief Emits when the user selects an away message + */ + void awayMessageSelected( const QString & ); + + /** + * same as above, but with the saved status + */ + void awayMessageSelected( const Kopete::OnlineStatus& , const QString & ); + + private slots: + void slotAwayChanged(); + void slotSelectAway( int index ); + + private: + class Private; + Private *d; +}; + +} + +#endif diff --git a/kopete/libkopete/kopeteawaydialog.cpp b/kopete/libkopete/kopeteawaydialog.cpp new file mode 100644 index 00000000..0dbb7023 --- /dev/null +++ b/kopete/libkopete/kopeteawaydialog.cpp @@ -0,0 +1,143 @@ +/* + kopeteawaydialog.cpp - Kopete Away Dialog + + Copyright (c) 2002 by Hendrik vom Lehn + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteawaydialog.h" + +#include +#include +#include +#include +#include + +#include "kopeteaway.h" +#include "kopeteawaydialogbase.h" + +class KopeteAwayDialogPrivate +{ +public: + KopeteAwayDialog_Base *base; +}; + +KopeteAwayDialog::KopeteAwayDialog( QWidget *parent, const char *name ) +: KDialogBase( parent, name, true, i18n( "Global Away Message" ), + KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok, true ) +{ + //kdDebug( 14010 ) << k_funcinfo << "Building KopeteAwayDialog..." << endl; + + d = new KopeteAwayDialogPrivate; + + d->base = new KopeteAwayDialog_Base( this ); + setMainWidget( d->base ); + + QObject::connect( d->base->cmbHistory, SIGNAL( activated( int ) ), this, SLOT( slotComboBoxSelection( int ) ) ); + + awayInstance = Kopete::Away::getInstance(); + mExtendedAwayType = 0; + init(); + + //kdDebug( 14010 ) << k_funcinfo << "KopeteAwayDialog created." << endl; +} + +KopeteAwayDialog::~KopeteAwayDialog() +{ + delete d; +} + +void KopeteAwayDialog::slotComboBoxSelection( int index ) +{ + // If they selected something out of the combo box + // They probably want to use it + d->base->txtOneShot->setText( awayInstance->getMessage(index) ); + d->base->txtOneShot->setCursorPosition( 0 ); +} + +void KopeteAwayDialog::show() +{ + // When this show is called, set the + // mExtendedAwayType to the empty string + mExtendedAwayType = 0; + + // Reinit the GUI + init(); + + //kdDebug( 14010 ) << k_funcinfo << "Showing Dialog with no extended away type" << endl; + + KDialogBase::show(); +} + +void KopeteAwayDialog::show( int awayType ) +{ + mExtendedAwayType = awayType; + + // Reinit the GUI to set it up correctly + init(); + + kdDebug( 14010 ) << k_funcinfo << "Showing Dialog with extended away type " << awayType << endl; + + KDialogBase::show(); +} + +void KopeteAwayDialog::cancelAway( int /* awayType */ ) +{ + /* Empty default implementation */ +} + +void KopeteAwayDialog::init() +{ + QStringList awayMessages = awayInstance->getMessages(); + for( QStringList::iterator it = awayMessages.begin(); it != awayMessages.end(); ++it ) + { + *it = KStringHandler::rsqueeze( *it ); + } + + d->base->cmbHistory->clear(); + d->base->cmbHistory->insertStringList( awayMessages ); + d->base->txtOneShot->setText( awayMessages[0] ); + + d->base->txtOneShot->setFocus(); + d->base->txtOneShot->setCursorPosition( 0 ); +} + +QString KopeteAwayDialog::getSelectedAwayMessage() +{ + mLastUserAwayMessage = d->base->txtOneShot->text(); + return mLastUserAwayMessage; +} + +void KopeteAwayDialog::slotOk() +{ + // Save the text the user typed + mLastUserTypedMessage = d->base->txtOneShot->text(); + + setAway( mExtendedAwayType ); + + KDialogBase::slotOk(); +} + +void KopeteAwayDialog::slotCancel() +{ + // Call the virtual function with the type of away + cancelAway( mExtendedAwayType ); + + KDialogBase::slotCancel(); +} + +#include "kopeteawaydialog.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopeteawaydialog.h b/kopete/libkopete/kopeteawaydialog.h new file mode 100644 index 00000000..313cafe2 --- /dev/null +++ b/kopete/libkopete/kopeteawaydialog.h @@ -0,0 +1,201 @@ +/* + kopeteawaydialog.h - Kopete Away Dialog + + Copyright (c) 2002 by Hendrik vom Lehn + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEAWAYDIALOG_H +#define KOPETEAWAYDIALOG_H + +#include +#include "kopete_export.h" + +namespace Kopete +{ +class Away; +} + +class KopeteAwayDialogPrivate; + +/** + * KopeteAwayDialog is a base class used for implementing + * Away Message selection dialogs in Kopete. It presents + * the user with a list of pre-written away messages and + * a line edit for them to type a "single shot" away message, + * one that is not saved and will be lost the next time + * they restart the application. + * + * Individual protocols should subclass this class for protocol + * specific Away Message choosers (in the case that the user + * wants to set only one protocol away). There are methods for + * getting the message that the user selected, as well as a + * virtual method that should be implemented that is called + * when the user selects "OK", and should be used to do + * protocol specific actions needed to set the user as + * "Away" (or whatever the protocol calls it). + * + * @author Hendrik vom Lehn + * @author Christopher TenHarmsel + */ + +class KOPETE_EXPORT KopeteAwayDialog : public KDialogBase +{ + Q_OBJECT + +public: + /** + * Constructor for the Away Dialog + * @param parent The object that owns this + * @param name Name for this object + */ + KopeteAwayDialog( QWidget *parent = 0, const char *name = 0 ); + + /** + * Destructor + */ + virtual ~KopeteAwayDialog(); + +protected: + /** + * Do not delete this, this instance will + * deleted when the application closes + */ + Kopete::Away *awayInstance; + + /** + * \brief Gets the last selected away message + * @return An away message + */ + QString getSelectedAwayMessage(); + + /** + * \brief Sets the user away + * + * This method is called when the user clicks + * OK in the GUI, signalling that they wish + * to set the away message that they have chosen. + * Please reimplement this method to do protocol + * specific things, and use getSelectedAwayMessage() + * to get the text of the message that the user + * selected. + * + * @param awayType This is the away type specified + * if show was called with a parameter. If show() was called + * instead, this parameter will be the empty string. You + * will need to compare it to an enum that you declare + * in your subclass. + */ + virtual void setAway( int awayType ) = 0; + + /** + * \brief Called when "Cancel" is clicked + * + * This method is called when the user clicks + * Cancel in the GUI, signalling that they + * canceled their request to mark themselves as + * away. If your implementation finds this + * information useful, implement this method + * to handle this info. By default it does nothing + * + * @param awayType This is the away type specified + * if show was called with a parameter, if show() was called + * instead, this parameter will be the empty string. + */ + virtual void cancelAway( int awayType ); + +public slots: + /** + * \brief Shows the dialog + */ + virtual void show(); + + /** + * \brief Shows the dialog + * + * Shows the away dialog, but maintains a "state" + * so you can specify if you're setting away, + * do not disturb, gone, etc for protocols that + * support this like ICQ and MSN. + * + * This string does not have any special internal + * meaning, but rather will get passed to setAway() + * when it is called so that you can decide what + * kind of "away" you really want to do. + * + * @param awayType The type of "away" you want to set. + */ + void show( int awayType ); + +protected slots: + /** + * This slot is called when the user click on "OK" + * it will call setAway(), which is pure virtual and + * should be implemented for specific needs + */ + virtual void slotOk(); + + /** + * This slot is called when the user clicks on + * "Cancel". It calls cancelAway(), which is + * pure virtual and should be implemented to + * fit your specific needs if the user selects + * "Cancel". This method will close the + * dialog, but if you require any specific actions + * please implement them in cancelAway(). + */ + virtual void slotCancel(); + +private slots: + /** + * \brief An entry was selected from the combo box + */ + void slotComboBoxSelection( int index ); + +private: + /** + * Initializes the GUI elements every time the + * dialog is show. Basically used for remembering + * the singleshot message that the user may have + * typed in. + */ + void init(); + + /** + * The last user-entered away text + * or the title of the last selected + * saved away message, whichever was + * last chosen + */ + QString mLastUserAwayMessage; + + /** + * The last message that the user typed in the + * line edit + */ + QString mLastUserTypedMessage; + + /** + * This is used to store the type of away that we're + * going to go. + */ + int mExtendedAwayType; + + KopeteAwayDialogPrivate *d; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopeteblacklister.cpp b/kopete/libkopete/kopeteblacklister.cpp new file mode 100644 index 00000000..8ec5c54b --- /dev/null +++ b/kopete/libkopete/kopeteblacklister.cpp @@ -0,0 +1,109 @@ +/* + kopeteblacklister.cpp - Kopete BlackLister + + Copyright (c) 2004 by Roie Kerstein + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteblacklister.h" + +#include "kopetecontact.h" + +#include +#include + +#include + +namespace Kopete +{ + +class BlackLister::Private +{ +public: + QStringList blacklist; + QString owner; + QString protocol; +}; + + +BlackLister::BlackLister(const QString &protocolId, const QString &accountId, QObject *parent, const char *name) + : QObject(parent, name), d( new Private ) +{ + KConfig *config = KGlobal::config(); + + d->owner = accountId; + d->protocol = protocolId; + config->setGroup("BlackLister"); + d->blacklist = config->readListEntry( d->protocol + QString::fromLatin1("_") + d->owner ); +} + +BlackLister::~BlackLister() +{ + delete d; +} + + +bool BlackLister::isBlocked(const QString &contactId) +{ + return (d->blacklist.find( contactId ) != d->blacklist.end() ); +} + +bool BlackLister::isBlocked(Contact *contact) +{ + return isBlocked(contact->contactId()); +} + +void BlackLister::addContact(const QString &contactId) +{ + if( !isBlocked(contactId) ) + { + d->blacklist += contactId; + saveToDisk(); + emit contactAdded( contactId ); + } +} + +void BlackLister::addContact(Contact *contact) +{ + QString temp = contact->contactId(); + + addContact( temp ); +} + +void BlackLister::removeContact(Contact *contact) +{ + QString temp = contact->contactId(); + + removeContact( temp ); +} + +void BlackLister::saveToDisk() +{ + KConfig *config = KGlobal::config(); + + config->setGroup("BlackLister"); + config->writeEntry( d->protocol + QString::fromLatin1("_") + d->owner, d->blacklist ); + config->sync(); +} + +void BlackLister::removeContact(const QString &contactId) +{ + if( isBlocked(contactId) ) + { + d->blacklist.remove( contactId ); + saveToDisk(); + emit contactRemoved( contactId ); + } +} + +} + +#include "kopeteblacklister.moc" diff --git a/kopete/libkopete/kopeteblacklister.h b/kopete/libkopete/kopeteblacklister.h new file mode 100644 index 00000000..ed3e5566 --- /dev/null +++ b/kopete/libkopete/kopeteblacklister.h @@ -0,0 +1,124 @@ +/* + kopeteblacklister.h - Kopete BlackLister + + Copyright (c) 2004 by Roie Kerstein + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEBLACKLISTER_H +#define KOPETEBLACKLISTER_H + +#include + +namespace Kopete +{ + +class Contact; + +/** + * @brief Manages the list of blacklisted contacts for an account + * + * This class manages the list of contacts the user wishes + * to ignore permanently. In order to use the this class, there is no need to + * create an instance. Use the @ref Kopete::Account::blackLister() instead. + * + * Keep in mind that this class does not discard messages from blocked + * users - It only manages the list. It is the up to the protocol to + * check whether a user is blocked, and act accordingly. A protocol may + * re-implement @ref Kopete::Account::block() and @ref Kopete::Account::unblock() + * and use @ref Kopete::Account::blackLister() as a persistent list manager + * only, or connect the signals @ref contactAdded() and @ref contactRemoved() + * to its slots. + * + * @sa Kopete::Account::block() Kopete::Account::unblock() + * + * @author Roie Kerstein + */ +class BlackLister : public QObject +{ + Q_OBJECT + +public: + /** + * Create an instance, and read the blacklist from disk if it exists. + * @param protocolId is the ID of the protocol owning accountId + * @param accountId is the ID of the owning Account. + * @param parent The QObject parent for this class. + * @param name The QObject name for this class. + */ + BlackLister( const QString &protocolId, const QString &accountId, QObject *parent = 0, const char *name = 0 ); + ~BlackLister(); + + /** + * \return @c true if @p contact is blocked, @c false otherwise. + */ + bool isBlocked( Contact *contact ); + + /** + * \return @c true if the contact with ID @p contactId is blocked, @c false otherwise. + */ + bool isBlocked( const QString &contactId ); + +public slots: + /** + * Add a contact to the blacklist. + * + * This function emits the @ref contactAdded() signal. + * @param contactId is the ID of the contact to be added to the list. + */ + void addContact( const QString &contactId ); + + /** + * @overload + */ + void addContact( Contact *contact ); + + /** + * \brief Remove a contact from the blacklist. + * + * Removes the contact from the blacklist. + * This function emits the @ref contactRemoved() signal. + * @param contact is the contact to be removed from the list. + */ + void removeContact( Contact *contact ); + + /** + * @overload + */ + void removeContact( const QString &contactId ); + +signals: + /** + * \brief A new contact has been added to the list + * + * Connect to this signal if you want to perform additional actions, + * and you prefer not to derive from this class. + */ + void contactAdded( const QString &contactId ); + + /** + * \brief A contact has been removed from the list + * + * Connect to this signal if you want to perform additional actions, + * and you prefer not to derive from this class. + */ + void contactRemoved( const QString &contactId ); + +private: + void saveToDisk(); + + class Private; + Private *d; +}; + +} + +#endif diff --git a/kopete/libkopete/kopetechatsession.cpp b/kopete/libkopete/kopetechatsession.cpp new file mode 100644 index 00000000..9ebf1d07 --- /dev/null +++ b/kopete/libkopete/kopetechatsession.cpp @@ -0,0 +1,515 @@ +/* + kopetechatsession.cpp - Manages all chats + + Copyright (c) 2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002 by Daniel Stone + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2004 by Olivier Goffart + Copyright (c) 2003 by Jason Keirstead + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetechatsession.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "kopeteaccount.h" +#include "kopetecommandhandler.h" +#include "kopetechatsessionmanager.h" +#include "kopetemessagehandlerchain.h" +#include "kopetemetacontact.h" +#include "knotification.h" +#include "kopeteprefs.h" +#include "kopeteuiglobal.h" +#include "kopeteglobal.h" +#include "kopeteview.h" +#include "kopetecontact.h" + +class KMMPrivate +{ +public: + Kopete::ContactPtrList mContactList; + const Kopete::Contact *mUser; + QMap contactStatus; + Kopete::Protocol *mProtocol; + bool isEmpty; + bool mCanBeDeleted; + unsigned int refcount; + bool customDisplayName; + QDateTime awayTime; + QString displayName; + KopeteView *view; + bool mayInvite; + Kopete::MessageHandlerChain::Ptr chains[3]; +}; + +Kopete::ChatSession::ChatSession( const Kopete::Contact *user, + Kopete::ContactPtrList others, Kopete::Protocol *protocol, const char *name ) +: QObject( user->account(), name ) +{ + d = new KMMPrivate; + d->mUser = user; + d->mProtocol = protocol; + d->isEmpty = others.isEmpty(); + d->mCanBeDeleted = true; + d->refcount = 0; + d->view = 0L; + d->customDisplayName = false; + d->mayInvite = false; + + for ( Kopete::Contact *c = others.first(); c; c = others.next() ) + addContact( c, true ); + + connect( user, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), this, + SLOT( slotOnlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + + if( user->metaContact() ) + connect( user->metaContact(), SIGNAL( photoChanged() ), this, SIGNAL( photoChanged() ) ); + + slotUpdateDisplayName(); +} + +Kopete::ChatSession::~ChatSession() +{ + //for ( Kopete::Contact *c = d->mContactList.first(); c; c = d->mContactList.next() ) + // c->setConversations( c->conversations() - 1 ); + + if ( !d ) + return; + d->mCanBeDeleted = false; //prevent double deletion + Kopete::ChatSessionManager::self()->removeSession( this ); + emit closing( this ); + delete d; +} + +void Kopete::ChatSession::slotOnlineStatusChanged( Kopete::Contact *c, const Kopete::OnlineStatus &status, const Kopete::OnlineStatus &oldStatus ) +{ + slotUpdateDisplayName(); + emit onlineStatusChanged((Kopete::Contact*)c, status, oldStatus); +} + +void Kopete::ChatSession::setContactOnlineStatus( const Kopete::Contact *contact, const Kopete::OnlineStatus &status ) +{ + Kopete::OnlineStatus oldStatus = d->contactStatus[ contact ]; + d->contactStatus[ contact ] = status; + disconnect( contact, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus &) ) ); + emit onlineStatusChanged( (Kopete::Contact*)contact, status, oldStatus ); +} + +const Kopete::OnlineStatus Kopete::ChatSession::contactOnlineStatus( const Kopete::Contact *contact ) const +{ + if ( d->contactStatus.contains( contact ) ) + return d->contactStatus[ contact ]; + + return contact->onlineStatus(); +} + +const QString Kopete::ChatSession::displayName() +{ + if ( d->displayName.isNull() ) + { + slotUpdateDisplayName(); + } + + return d->displayName; +} + +void Kopete::ChatSession::setDisplayName( const QString &newName ) +{ + d->displayName = newName; + d->customDisplayName = true; + emit displayNameChanged(); +} + +void Kopete::ChatSession::slotUpdateDisplayName() +{ + if( d->customDisplayName ) + return; + + Kopete::Contact *c = d->mContactList.first(); + + //If there is no member yet, don't try to update the display name + if ( !c ) + return; + + d->displayName=QString::null; + do + { + if(! d->displayName.isNull() ) + d->displayName.append( QString::fromLatin1( ", " ) ) ; + + if ( c->metaContact() ) + d->displayName.append( c->metaContact()->displayName() ); + else + { + QString nick=c->property(Kopete::Global::Properties::self()->nickName()).value().toString(); + d->displayName.append( nick.isEmpty() ? c->contactId() : nick ); + } + c=d->mContactList.next(); + } while (c); + + //If we have only 1 contact, add the status of him + if ( d->mContactList.count() == 1 ) + { + d->displayName.append( QString::fromLatin1( " (%1)" ).arg( d->mContactList.first()->onlineStatus().description() ) ); + } + + emit displayNameChanged(); +} + +const Kopete::ContactPtrList& Kopete::ChatSession::members() const +{ + return d->mContactList; +} + +const Kopete::Contact* Kopete::ChatSession::myself() const +{ + return d->mUser; +} + +Kopete::Protocol* Kopete::ChatSession::protocol() const +{ + return d->mProtocol; +} + + +#include "kopetemessagehandler.h" +#include "kopetemessageevent.h" + +// FIXME: remove this and the friend decl in KMM +class Kopete::TemporaryKMMCallbackAppendMessageHandler : public Kopete::MessageHandler +{ + Kopete::ChatSession *manager; +public: + TemporaryKMMCallbackAppendMessageHandler( Kopete::ChatSession *manager ) + : manager(manager) + { + } + void handleMessage( Kopete::MessageEvent *event ) + { + Kopete::Message message = event->message(); + emit manager->messageAppended( message, manager ); + delete event; + } +}; + +class TempFactory : public Kopete::MessageHandlerFactory +{ +public: + Kopete::MessageHandler *create( Kopete::ChatSession *manager, Kopete::Message::MessageDirection ) + { + return new Kopete::TemporaryKMMCallbackAppendMessageHandler( manager ); + } + int filterPosition( Kopete::ChatSession *, Kopete::Message::MessageDirection ) + { + // FIXME: somewhere after everyone else. + return 100000; + } +}; + +Kopete::MessageHandlerChain::Ptr Kopete::ChatSession::chainForDirection( Kopete::Message::MessageDirection dir ) +{ + if( dir < 0 || dir > 2) + kdFatal(14000) << k_funcinfo << "invalid message direction " << dir << endl; + if( !d->chains[dir] ) + { + TempFactory theTempFactory; + d->chains[dir] = Kopete::MessageHandlerChain::create( this, dir ); + } + return d->chains[dir]; +} + +void Kopete::ChatSession::sendMessage( Kopete::Message &message ) +{ + message.setManager( this ); + Kopete::Message sentMessage = message; + if ( !Kopete::CommandHandler::commandHandler()->processMessage( message, this ) ) + { + emit messageSent( sentMessage, this ); + if ( !account()->isAway() || KopetePrefs::prefs()->soundIfAway() ) + { + KNotification::event(QString::fromLatin1( "kopete_outgoing" ), i18n( "Outgoing Message Sent" ) ); + } + } + else + { + messageSucceeded(); + } +} + +void Kopete::ChatSession::messageSucceeded() +{ + emit messageSuccess(); +} + +void Kopete::ChatSession::emitNudgeNotification() +{ + KNotification::event( QString::fromLatin1("buzz_nudge"), i18n("A contact sent you a buzz/nudge.") ); +} + +void Kopete::ChatSession::appendMessage( Kopete::Message &msg ) +{ + msg.setManager( this ); + + if ( msg.direction() == Kopete::Message::Inbound ) + { + QString nick=myself()->property(Kopete::Global::Properties::self()->nickName()).value().toString(); + if ( KopetePrefs::prefs()->highlightEnabled() && !nick.isEmpty() && + msg.plainBody().contains( QRegExp( QString::fromLatin1( "\\b(%1)\\b" ).arg( nick ), false ) ) ) + { + msg.setImportance( Kopete::Message::Highlight ); + } + + emit messageReceived( msg, this ); + } + + // outbound messages here are ones the user has sent that are now + // getting reflected back to the chatwindow. they should go down + // the incoming chain. + Kopete::Message::MessageDirection chainDirection = msg.direction(); + if( chainDirection == Kopete::Message::Outbound ) + chainDirection = Kopete::Message::Inbound; + + chainForDirection( chainDirection )->processMessage( msg ); +// emit messageAppended( msg, this ); +} + +void Kopete::ChatSession::addContact( const Kopete::Contact *c, const Kopete::OnlineStatus &initialStatus, bool suppress ) +{ + if( !d->contactStatus.contains(c) ) + d->contactStatus[ c ] = initialStatus; + addContact( c, suppress ); +} + +void Kopete::ChatSession::addContact( const Kopete::Contact *c, bool suppress ) +{ + //kdDebug( 14010 ) << k_funcinfo << endl; + if ( d->mContactList.contains( c ) ) + { + kdDebug( 14010 ) << k_funcinfo << "Contact already exists" <mContactList.count() == 1 && d->isEmpty ) + { + kdDebug( 14010 ) << k_funcinfo << " FUCKER ZONE " << endl; + /* We have only 1 contact before, so the status of the + message manager was given from that contact status */ + Kopete::Contact *old = d->mContactList.first(); + d->mContactList.remove( old ); + d->mContactList.append( c ); + + disconnect( old, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotOnlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus &) ) ); + + if ( old->metaContact() ) + { + disconnect( old->metaContact(), SIGNAL( displayNameChanged( const QString &, const QString & ) ), this, SLOT( slotUpdateDisplayName() ) ); + disconnect( old->metaContact(), SIGNAL( photoChanged() ), this, SIGNAL( photoChanged() ) ); + } + else + disconnect( old, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), this, SLOT( slotUpdateDisplayName() ) ); + emit contactAdded( c, suppress ); + emit contactRemoved( old, QString::null ); + } + else + { + d->mContactList.append( c ); + emit contactAdded( c, suppress ); + } + + connect( c, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotOnlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus &) ) ); +; + if ( c->metaContact() ) + { + connect( c->metaContact(), SIGNAL( displayNameChanged( const QString &, const QString & ) ), this, SLOT( slotUpdateDisplayName() ) ); + connect( c->metaContact(), SIGNAL( photoChanged() ), this, SIGNAL( photoChanged() ) ); + } + else + connect( c, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), this, SLOT( slotUpdateDisplayName() ) ); + connect( c, SIGNAL( contactDestroyed( Kopete::Contact * ) ), this, SLOT( slotContactDestroyed( Kopete::Contact * ) ) ); + + slotUpdateDisplayName(); + } + d->isEmpty = false; +} + +void Kopete::ChatSession::removeContact( const Kopete::Contact *c, const QString& reason, Kopete::Message::MessageFormat format, bool suppressNotification ) +{ + kdDebug( 14010 ) << k_funcinfo << endl; + if ( !c || !d->mContactList.contains( c ) ) + return; + + if ( d->mContactList.count() == 1 ) + { + kdDebug( 14010 ) << k_funcinfo << "Contact not removed. Keep always one contact" << endl; + d->isEmpty = true; + } + else + { + d->mContactList.remove( c ); + + disconnect( c, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotOnlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus &) ) ); + + if ( c->metaContact() ) + { + disconnect( c->metaContact(), SIGNAL( displayNameChanged( const QString &, const QString & ) ), this, SLOT( slotUpdateDisplayName() ) ); + disconnect( c->metaContact(), SIGNAL( photoChanged() ), this, SIGNAL( photoChanged() ) ); + } + else + disconnect( c, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), this, SLOT( slotUpdateDisplayName() ) ); + disconnect( c, SIGNAL( contactDestroyed( Kopete::Contact * ) ), this, SLOT( slotContactDestroyed( Kopete::Contact * ) ) ); + + slotUpdateDisplayName(); + } + + d->contactStatus.remove( c ); + + emit contactRemoved( c, reason, format, suppressNotification ); +} + +void Kopete::ChatSession::receivedTypingMsg( const Kopete::Contact *c, bool t ) +{ + emit remoteTyping( c, t ); +} + +void Kopete::ChatSession::receivedTypingMsg( const QString &contactId, bool t ) +{ + for ( Kopete::Contact *it = d->mContactList.first(); it; it = d->mContactList.next() ) + { + if ( it->contactId() == contactId ) + { + receivedTypingMsg( it, t ); + return; + } + } +} + +void Kopete::ChatSession::typing( bool t ) +{ + emit myselfTyping( t ); +} + +void Kopete::ChatSession::receivedEventNotification( const QString& notificationText) +{ + emit eventNotification( notificationText ); +} + +void Kopete::ChatSession::setCanBeDeleted ( bool b ) +{ + d->mCanBeDeleted = b; + if (d->refcount < (b?1:0) && !d->view ) + deleteLater(); +} + +void Kopete::ChatSession::ref () +{ + d->refcount++; +} +void Kopete::ChatSession::deref () +{ + d->refcount--; + if ( d->refcount < 1 && d->mCanBeDeleted && !d->view ) + deleteLater(); +} + +KopeteView* Kopete::ChatSession::view( bool canCreate, const QString &requestedPlugin ) +{ + if ( !d->view && canCreate ) + { + d->view = Kopete::ChatSessionManager::self()->createView( this, requestedPlugin ); + if ( d->view ) + { + connect( d->view->mainWidget(), SIGNAL( closing( KopeteView * ) ), this, SLOT( slotViewDestroyed( ) ) ); + } + else + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Error, + i18n( "An error has occurred while creating a new chat window. The chat window has not been created." ), + i18n( "Error While Creating Chat Window" ) ); + } + } + return d->view; +} + +void Kopete::ChatSession::slotViewDestroyed() +{ + d->view = 0L; + if ( d->mCanBeDeleted && d->refcount < 1) + deleteLater(); +} + +Kopete::Account *Kopete::ChatSession::account() const +{ + return myself()->account(); +} + +void Kopete::ChatSession::slotContactDestroyed( Kopete::Contact *contact ) +{ + if(contact == myself()) + deleteLater(); + + if( !contact || !d->mContactList.contains( contact ) ) + return; + + //This is a workaround to prevent crash if the contact get deleted. + // in the best case, we should ask the protocol to recreate a temporary contact. + // (remember: the contact may be deleted when the users removes it from the contactlist, or when closing kopete ) + d->mContactList.remove( contact ); + emit contactRemoved( contact, QString::null ); + + if ( d->mContactList.isEmpty() ) + deleteLater(); +} + +bool Kopete::ChatSession::mayInvite() const +{ + return d->mayInvite; +} + +void Kopete::ChatSession::inviteContact(const QString& ) +{ + //default implementation do nothing +} + +void Kopete::ChatSession::setMayInvite( bool b ) +{ + d->mayInvite=b; +} + +void Kopete::ChatSession::raiseView() +{ + KopeteView *v=view(true, KopetePrefs::prefs()->interfacePreference() ); + if(v) + v->raise(true); +} + +#include "kopetechatsession.moc" + + + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetechatsession.h b/kopete/libkopete/kopetechatsession.h new file mode 100644 index 00000000..86d5fa64 --- /dev/null +++ b/kopete/libkopete/kopetechatsession.h @@ -0,0 +1,405 @@ +/* + kopetechatsession.h - Manages all chats + + Copyright (c) 2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002 by Daniel Stone + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2004 by Olivier Goffart + Copyright (c) 2003 by Jason Keirstead + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __KOPETECHATSESSION_H__ +#define __KOPETECHATSESSION_H__ + +#include +#include +#include + +#include + +#include "kopete_export.h" + +// FIXME: get rid of these includes +#include "kopetemessage.h" +#include "kopetemessagehandlerchain.h" + +class KMMPrivate; + +class KopeteView; + +namespace Kopete +{ + +class Contact; +class Message; +class Protocol; +class OnlineStatus; +class Account; +class ChatSessionManager; +class MessageHandlerChain; +class TemporaryKMMCallbackAppendMessageHandler; + +typedef QPtrList ContactPtrList; +typedef QValueList MessageList; + + +/** + * @author Duncan Mac-Vicar Prett + * @author Daniel Stone + * @author Martijn Klingens + * @author Olivier Goffart + * @author Jason Keirstead + * + * The Kopete::ChatSession manages a single chat. + * It is an interface between the protocol, and the chatwindow. + * The protocol can connect to @ref messageSent() signals to send the message, and can + * append received message with @ref messageReceived() + * + * The KMM inherits from KXMLGUIClient, this client is merged with the chatwindow's ui + * so plugins can add childClients of this client to add their own actions in the + * chatwindow. + */ +class KOPETE_EXPORT ChatSession : public QObject , public KXMLGUIClient +{ + // friend class so the object factory can access the protected constructor + friend class ChatSessionManager; + + Q_OBJECT + +public: + /** + * Delete a chat manager instance + * You shouldn't delete the KMM yourself. it will be deleted when the chatwindow is closed + * see also @ref setCanBeDeleted() , @ref deref() + */ + ~ChatSession(); + + /** + * @brief Get a list of all contacts in the session + */ + const ContactPtrList& members() const; + + /** + * @brief Get the local user in the session + * @return the local user in the session, same as account()->myself() + */ + const Contact* myself() const; + + /** + * @brief Get the protocol being used. + * @return the protocol + */ + Protocol* protocol() const; + + /** + * @brief get the account + * @return the account + */ + Account *account() const ; + + /** + * @brief The caption of the chat + * + * Used for named chats + */ + const QString displayName(); + + /** + * @brief change the displayname + * + * change the display name of the chat + */ + void setDisplayName( const QString &displayName ); + + /** + * @brief set a specified KOS for specified contact in this KMM + * + * Set a special icon for a contact in this kmm only. + * by default, all contact have their own status + */ + void setContactOnlineStatus( const Contact *contact, const OnlineStatus &newStatus ); + + /** + * @brief get the status of a contact. + * + * see @ref setContactOnlineStatus() + */ + const OnlineStatus contactOnlineStatus( const Contact *contact ) const; + + /** + * @brief the manager's view + * + * Return the view for the supplied Kopete::ChatSession. If it already + * exists, it will be returned, otherwise, 0L will be returned or a new one + * if canCreate=true + * @param canCreate create a new one if it does not exist + * @param requestedPlugin Specifies the view plugin to use if we have to create one. + */ + // FIXME: canCreate should definitely be an enum and not a bool - Martijn + KopeteView* view( bool canCreate = false, const QString &requestedPlugin = QString::null ); + + /** + * says if you may invite contact from the same account to this chat with @ref inviteContact + * @see setMayInvite + * @return true if it is possible to invite contact to this chat. + */ + bool mayInvite() const ; + + /** + * this method is called when a contact is dragged to the contactlist. + * @p contactId is the id of the contact. the contact is supposed to be of the same account as + * the @ref account() but we can't be sure the Kopete::Contact is realy on the contactlist + * + * It is possible to drag contact only if @ref mayInvite return true + * + * the default implementaiton do nothing + */ + virtual void inviteContact(const QString &contactId); + + /** + * Returns the message handler chain for the message direction @p dir. + */ + MessageHandlerChain::Ptr chainForDirection( Message::MessageDirection dir ); + +signals: + /** + * @brief the KMM will be deleted + * Used by a Kopete::ChatSession to signal that it is closing. + */ + void closing( Kopete::ChatSession *kmm ); + + /** + * a message will be soon shown in the chatwindow. + * See @ref Kopete::ChatSessionManager::aboutToDisplay() signal + */ + void messageAppended( Kopete::Message &msg, Kopete::ChatSession *kmm = 0L ); + + /** + * a message will be soon received + * See @ref Kopete::ChatSessionManager::aboutToReceive() signal + */ + void messageReceived( Kopete::Message &msg, Kopete::ChatSession *kmm = 0L ); + + /** + * @brief a message is going to be sent + * + * The message is going to be sent. + * protocols can connect to this signal to send the message ro the network. + * the protocol have also to call @ref appendMessage() and @ref messageSucceeded() + * See also @ref Kopete::ChatSessionManager::aboutToSend() signal + */ + void messageSent( Kopete::Message &msg, Kopete::ChatSession *kmm = 0L ); + + /** + * The last message has finaly successfully been sent + */ + void messageSuccess(); + + /** + * @brief a new contact is now in the chat + */ + // FIXME: What's 'suppress'? Shouldn't this be an enum? - Martijn + void contactAdded( const Kopete::Contact *contact, bool suppress ); + + /** + * @brief a contact is no longer in this chat + */ + void contactRemoved( const Kopete::Contact *contact, const QString &reason, Kopete::Message::MessageFormat format = Message::PlainText, bool contactRemoved = false ); + + /** + * @brief a contact in this chat has changed his status + */ + void onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ); + + /** + * @brief The name of the chat is changed + */ + void displayNameChanged(); + + /** + * @brief emitting a typing notification + * + * The user is typing a message, or just stopped typing + * the protocol should connect to this signal to signal to others + * that the user is typing if the protocol supports this + * @param isTyping say if the user is typing or not + */ + void myselfTyping( bool isTyping ); + + /** + * Signals that a remote user is typing a message. + * the chatwindow connects to this signal to update the statusbar + */ + void remoteTyping( const Kopete::Contact *contact, bool isTyping ); + + /** + * Signals that a an event has to be displayed in the statusbar. + * The chatwindow connects to this signal to update the statusbar. + */ + void eventNotification( const QString& notificationText); + + /** + * @brief A contact within the chat session changed his photo. + * Used to update the contacts photo in chat window. + */ + void photoChanged(); + +public slots: + /** + * @brief Got a typing notification from a user + */ + void receivedTypingMsg( const Kopete::Contact *contact , bool isTyping = true ); + + /** + * Got a typing notification from a user. This is a convenience version + * of the above method that takes a QString contactId instead of a full + * Kopete::Contact + */ + void receivedTypingMsg( const QString &contactId, bool isTyping = true ); + + /** + * @brief Got an event notification from a user. + * It will emit the signal eventNotification(). Use this slot in your protocols + * and plugins to change chatwindow statusBar text. + */ + void receivedEventNotification( const QString& notificationText ); + + /** + * Show a message to the chatwindow, or append it to the queue. + * This is the function protocols HAVE TO call for both incoming and outgoing messages + * if the message must be showed in the chatwindow + */ + void appendMessage( Kopete::Message &msg ); + + /** + * Add a contact to the session + * @param c is the contact + * @param suppress mean the there will be no automatic notifications in the chatwindow. + * (note that i don't like the param suppress at all. it is used in irc to show a different notification (with an info text) + * a QStringinfo would be more interesting, but it is also used to don't show the notification when entering in a channel) + */ + void addContact( const Kopete::Contact *c, bool suppress = false ); + + /** + * Add a contact to the session with a pre-set initial status + * @param c is the contact + * @param initialStatus The initial contactOnlineStatus of the contact + * @param suppress mean the there will be no automatic notifications in the chatwindow. + * (note that i don't like the param suppress at all. it is used in irc to show a different notification (with an info text) + * a QStringinfo would be more interesting, but it is also used to don't show the notification when entering in a channel) + * @see contactOnlineStatus + */ + void addContact( const Kopete::Contact *c, const Kopete::OnlineStatus &initialStatus, bool suppress = false ); + + /** + * Remove a contact from the session + * @param contact is the contact + * @param reason is the optional raison message showed in the chatwindow + * @param format The format of the message + * @param suppressNotification prevents a notification of the removal in the chat view. See note in @ref addContact + */ + void removeContact( const Kopete::Contact *contact, const QString& reason = QString::null, Kopete::Message::MessageFormat format = Message::PlainText, bool suppressNotification = false ); + + /** + * Set if the KMM will be deleted when the chatwindow is deleted. It is useful if you want + * to keep the KMM alive even if the chatwindow is closed. + * Warning: if you set it to false, please keep in mind that you have to reset it to true + * later to delete it. In many case, you should never delete yourself the KMM, just call this + * this method. + * default is true. + * If there are no chatwindow when setting it to true, the kmm will be deleted. + * + * @deprecated use ref and deref + */ + void setCanBeDeleted ( bool canBeDeleted ); + + /** + * reference count the chat session. + * the chat session may be deleted only if the count reach 0 + * if you ref, don't forget to deref + * @see deref() + */ + void ref(); + /** + * dereference count the chat session + * if the reference counter reach 0 and there is no chat window open, the chat session will be deleted. + */ + void deref(); + + + /** + * Send a message to the user + */ + void sendMessage( Kopete::Message &message ); + + /** + * Tell the KMM that the user is typing + * This method should be called only by a chatwindow. It emits @ref myselfTyping signal + */ + void typing( bool t ); + + /** + * Protocols have to call this method when the last message sent has been correctly sent + * This will emit @ref messageSuccess signal. and allow the email window to get closed + */ + void messageSucceeded(); + + /** + * Protcols have to call this method if they want to emit a notification when a nudge/buzz is received. + */ + void emitNudgeNotification(); + + /** + * Raise the chat window and give him the focus + * It's used when the user wanted to activated (by clicking on the "view" button of a popup) + */ + void raiseView(); + +private slots: + void slotUpdateDisplayName(); + void slotViewDestroyed(); + void slotOnlineStatusChanged( Kopete::Contact *c, const Kopete::OnlineStatus &status, const Kopete::OnlineStatus &oldStatus ); + void slotContactDestroyed( Kopete::Contact *contact ); + +protected: + /** + * Create a message manager. This constructor is private, because the + * static factory method createSession() creates the object. You may + * not create instances yourself directly! + */ + ChatSession( const Contact *user, ContactPtrList others, + Protocol *protocol, const char *name = 0 ); + + /** + * Set wether or not contact from this account may be invited in this chat. + * By default, it is set to false + * @see inviteContact() + * @see mayInvite() + */ + void setMayInvite(bool); + +private: + KMMPrivate *d; + + // FIXME: remove + friend class TemporaryKMMCallbackAppendMessageHandler; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetechatsessionmanager.cpp b/kopete/libkopete/kopetechatsessionmanager.cpp new file mode 100644 index 00000000..9b7dd489 --- /dev/null +++ b/kopete/libkopete/kopetechatsessionmanager.cpp @@ -0,0 +1,197 @@ +/* + kopetechatsessionmanager.cpp - Creates chat sessions + + Copyright (c) 2002-2003 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetechatsessionmanager.h" +#include "kopeteviewmanager.h" + +#include +#include + +#include "ui/kopeteview.h" +#include "kopetecontact.h" + +namespace Kopete { + +class ChatSessionManager::Private +{ + public: + QValueList sessions; +// UI::ChatView *activeView; +}; + +ChatSessionManager* ChatSessionManager::s_self = 0L; + +ChatSessionManager* ChatSessionManager::self() +{ + if( !s_self ) + s_self = new ChatSessionManager( kapp ); + + return s_self; +} + +ChatSessionManager::ChatSessionManager( QObject* parent, + const char* name ) + : QObject( parent, name ) +{ + d=new Private; + s_self = this; +} + +ChatSessionManager::~ChatSessionManager() +{ + s_self = 0L; + QValueListIterator it; + for ( it=d->sessions.begin() ; it!=d->sessions.end() ; ++it ) + { + kdDebug( 14010 ) << k_funcinfo << "Unloading KMM: Why this KMM isn't yet unloaded?" << endl; + (*it)->deleteLater(); + } + delete d; +} + +ChatSession* ChatSessionManager::findChatSession(const Contact *user, + ContactPtrList chatContacts, Protocol *protocol) +{ + ChatSession *result = 0L; + QValueList::Iterator it; + for ( it= d->sessions.begin(); it!=d->sessions.end() && !result ; ++it ) + { + ChatSession* cs=(*it); + if ( cs->protocol() == protocol && user == cs->myself() ) + { + QPtrList contactlist = cs->members(); + + // set this to false if chatContacts doesn't contain current cs's contactlist + bool halfMatch = true; + + Contact *tmpContact; + for (tmpContact = contactlist.first(); tmpContact && halfMatch; tmpContact = contactlist.next()) + { + if ( !chatContacts.containsRef( tmpContact ) ) + halfMatch = false; + } + + // If chatContacts contains current cs's contactlist, try the other way around + if (halfMatch) + { + bool fullMatch = true; + for (tmpContact = chatContacts.first(); tmpContact && fullMatch; tmpContact = chatContacts.next()) + { + if ( !contactlist.containsRef( tmpContact ) ) + fullMatch = false; + } + // We have a winner + if (fullMatch) + result = cs; + } + } + } + return result; +} + +ChatSession *ChatSessionManager::create( + const Contact *user, ContactPtrList chatContacts, Protocol *protocol) +{ + ChatSession *result=findChatSession( user, chatContacts, protocol); + if (!result) + { + result = new ChatSession(user, chatContacts, protocol ); + registerChatSession(result); + } + return (result); +} + +void ChatSessionManager::slotReadMessage() +{ + emit readMessage(); +} + +void ChatSessionManager::registerChatSession(ChatSession * result) +{ + d->sessions.append( result ); + + /* + * There's no need for a slot here... just add a public remove() + * method and call from KMM's destructor + */ + connect( result, SIGNAL( messageAppended( Kopete::Message &, Kopete::ChatSession * ) ), + SIGNAL( aboutToDisplay( Kopete::Message & ) ) ); + connect( result, SIGNAL( messageSent( Kopete::Message &, Kopete::ChatSession * ) ), + SIGNAL( aboutToSend(Kopete::Message & ) ) ); + connect( result, SIGNAL( messageReceived( Kopete::Message &, Kopete::ChatSession * ) ), + SIGNAL( aboutToReceive(Kopete::Message & ) ) ); + + connect( result, SIGNAL(messageAppended( Kopete::Message &, Kopete::ChatSession *) ), + SIGNAL( display( Kopete::Message &, Kopete::ChatSession *) ) ); + + emit chatSessionCreated(result); +} + + +void ChatSessionManager::removeSession( ChatSession *session) +{ + kdDebug(14010) << k_funcinfo << endl; + d->sessions.remove( session ); +} + +QValueList ChatSessionManager::sessions( ) +{ + return d->sessions; +} + +KopeteView * ChatSessionManager::createView( ChatSession *kmm , const QString &requestedPlugin ) +{ + KopeteView *newView = KopeteViewManager::viewManager()->view(kmm,requestedPlugin); + if(!newView) + { + kdDebug(14010) << k_funcinfo << "View not successfuly created" << endl; + return 0L; + } + + QObject *viewObject = dynamic_cast(newView); + if(viewObject) + { + connect(viewObject, SIGNAL(activated(KopeteView *)), + this, SIGNAL(viewActivated(KopeteView *))); + connect(viewObject, SIGNAL(closing(KopeteView *)), + this, SIGNAL(viewClosing(KopeteView *))); + } + else + { + kdWarning(14010) << "Failed to cast view to QObject *" << endl; + } + + emit viewCreated( newView ) ; + return newView; +} + +void ChatSessionManager::postNewEvent(MessageEvent *e) +{ + emit newEvent(e); +} + +KopeteView *ChatSessionManager::activeView() +{ + return KopeteViewManager::viewManager()->activeView(); +} + +} //END namespace Kopete + +#include "kopetechatsessionmanager.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetechatsessionmanager.h b/kopete/libkopete/kopetechatsessionmanager.h new file mode 100644 index 00000000..e41eb14d --- /dev/null +++ b/kopete/libkopete/kopetechatsessionmanager.h @@ -0,0 +1,192 @@ +/* + kopetechatsessionmanager.h - Creates chat sessions + + Copyright (c) 2002-2003 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEMESSAGEMANAGERFACTORY_H +#define KOPETEMESSAGEMANAGERFACTORY_H + +#include +#include +#include +#include + +#include "kopetechatsession.h" +#include "kopetemessage.h" + +#include "kopete_export.h" + +class KopeteView; + +namespace Kopete +{ + +class Contact; +class Protocol; +class MessageEvent; + +typedef QPtrList ContactPtrList; +typedef QValueList MessageList; + +/** + * @author Duncan Mac-Vicar Prett + * + * Kopete::ChatSessionManager is responsible for creating and tracking Kopete::ChatSession + * instances for each chat. + */ +class KOPETE_EXPORT ChatSessionManager : public QObject +{ + Q_OBJECT + +public: + static ChatSessionManager* self(); + + ~ChatSessionManager(); + + /** + * Create a new chat session. Provided is the initial list of contacts in + * the session. If a session with exactly these contacts already exists, + * it will be reused. Otherwise a new session is created. + * @param user The local user in the session. + * @param chatContacts The list of contacts taking part in the chat. + * @param protocol The protocol that the chat is using. + * @return A pointer to a new or reused Kopete::ChatSession. + */ + Kopete::ChatSession* create( const Kopete::Contact *user, + Kopete::ContactPtrList chatContacts, Kopete::Protocol *protocol); + + /** + * Find a chat session, if one exists, that matches the given list of contacts. + * @param user The local user in the session. + * @param chatContacts The list of contacts taking part in the chat. + * @param protocol The protocol that the chat is using. + * @return A pointer to an existing Kopete::ChatSession, or 0L if none was found. + */ + Kopete::ChatSession* findChatSession( const Kopete::Contact *user, + Kopete::ContactPtrList chatContacts, Kopete::Protocol *protocol); + + /** + * Registers a Kopete::ChatSession (or subclass thereof) with the Kopete::ChatSessionManager + */ + void registerChatSession(Kopete::ChatSession *); + + /** + * Get a list of all open sessions. + */ + QValueList sessions(); + + /** + * @internal + * called by the kmm itself when it gets deleted + */ + void removeSession( Kopete::ChatSession *session ); + + /** + * create a new view for the manager. + * only the manager should call this function + */ + KopeteView *createView( Kopete::ChatSession * , const QString &requestedPlugin = QString::null ); + + /** + * Post a new event. this will emit the @ref newEvent signal + */ + void postNewEvent(Kopete::MessageEvent*); + + /** + * Returns the current active Kopete view + */ + KopeteView *activeView(); + +signals: + /** + * This signal is emitted whenever a message + * is about to be displayed by the KopeteChatWindow. + * Please remember that both messages sent and + * messages received will emit this signal! + * Plugins may connect to this signal to change + * the message contents before it's going to be displayed. + */ + void aboutToDisplay( Kopete::Message& message ); + + /** + * Plugins may connect to this signal + * to manipulate the contents of the + * message that is being sent. + */ + void aboutToSend( Kopete::Message& message ); + + /** + * Plugins may connect to this signal + * to manipulate the contents of the + * message that is being received. + * + * This signal is emitted before @ref aboutToDisplay() + */ + void aboutToReceive( Kopete::Message& message ); + + /** + * A new view has been created + */ + void viewCreated( KopeteView * ); + + /** + * A view as been activated(manually only?). + */ + void viewActivated( KopeteView *view ); + + /* + * A view is about to close. + */ + void viewClosing( KopeteView *view ); + + /** + * a new KMM has been created + */ + void chatSessionCreated( Kopete::ChatSession *); + + /** + * the message is ready to be displayed + */ + void display( Kopete::Message& message, Kopete::ChatSession * ); + + /** + * A new event has been posted. + */ + void newEvent(Kopete::MessageEvent *); + + /** + * The global shortcut for sending message has been used + */ + void readMessage(); + +public slots: + void slotReadMessage(); + +private: + ChatSessionManager( QObject* parent = 0, const char* name = 0 ); + + class Private; + Private *d; + + static ChatSessionManager *s_self; + +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetecommandhandler.cpp b/kopete/libkopete/kopetecommandhandler.cpp new file mode 100644 index 00000000..b761ec08 --- /dev/null +++ b/kopete/libkopete/kopetecommandhandler.cpp @@ -0,0 +1,490 @@ +/* + kopetecommandhandler.cpp - Command Handler + + Copyright (c) 2003 by Jason Keirstead + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopetechatsessionmanager.h" +#include "kopeteprotocol.h" +#include "kopetepluginmanager.h" +#include "kopeteview.h" +#include "kopeteaccountmanager.h" +#include "kopeteaccount.h" +#include "kopetecommandhandler.h" +#include "kopetecontact.h" +#include "kopetecommand.h" + +using Kopete::CommandList; + +typedef QMap PluginCommandMap; +typedef QMap CommandMap; +typedef QPair ManagerPair; + +class KopeteCommandGUIClient : public QObject, public KXMLGUIClient +{ + public: + KopeteCommandGUIClient( Kopete::ChatSession *manager ) : QObject(manager), KXMLGUIClient(manager) + { + setXMLFile( QString::fromLatin1("kopetecommandui.rc") ); + + QDomDocument doc = domDocument(); + QDomNode menu = doc.documentElement().firstChild().firstChild().firstChild(); + CommandList mCommands = Kopete::CommandHandler::commandHandler()->commands( + manager->protocol() + ); + + for( QDictIterator it( mCommands ); it.current(); ++it ) + { + KAction *a = static_cast( it.current() ); + actionCollection()->insert( a ); + QDomElement newNode = doc.createElement( QString::fromLatin1("Action") ); + newNode.setAttribute( QString::fromLatin1("name"), + QString::fromLatin1( a->name() ) ); + + bool added = false; + for( QDomElement n = menu.firstChild().toElement(); + !n.isNull(); n = n.nextSibling().toElement() ) + { + if( QString::fromLatin1(a->name()) < n.attribute(QString::fromLatin1("name"))) + { + menu.insertBefore( newNode, n ); + added = true; + break; + } + } + + if( !added ) + { + menu.appendChild( newNode ); + } + } + + setDOMDocument( doc ); + } +}; + +struct CommandHandlerPrivate +{ + PluginCommandMap pluginCommands; + Kopete::CommandHandler *s_handler; + QMap processMap; + bool inCommand; + QPtrList m_commands; +}; + +CommandHandlerPrivate *Kopete::CommandHandler::p = 0L; + +Kopete::CommandHandler::CommandHandler() : QObject( qApp ) +{ + p->s_handler = this; + p->inCommand = false; + + CommandList mCommands(31, false); + mCommands.setAutoDelete( true ); + p->pluginCommands.insert( this, mCommands ); + + registerCommand( this, QString::fromLatin1("help"), SLOT( slotHelpCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /help [] - Used to list available commands, or show help for a specified command." ), 0, 1 ); + + registerCommand( this, QString::fromLatin1("close"), SLOT( slotCloseCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /close - Closes the current view." ) ); + + // FIXME: What's the difference with /close? The help doesn't explain it - Martijn + registerCommand( this, QString::fromLatin1("part"), SLOT( slotPartCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /part - Closes the current view." ) ); + + registerCommand( this, QString::fromLatin1("clear"), SLOT( slotClearCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /clear - Clears the active view's chat buffer." ) ); + + //registerCommand( this, QString::fromLatin1("me"), SLOT( slotMeCommand( const QString &, Kopete::ChatSession * ) ), + // i18n( "USAGE: /me - Formats message as in ' went to the store'." ) ); + + registerCommand( this, QString::fromLatin1("away"), SLOT( slotAwayCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /away [] - Marks you as away/back for the current account only." ) ); + + registerCommand( this, QString::fromLatin1("awayall"), SLOT( slotAwayAllCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /awayall [] - Marks you as away/back for all accounts." ) ); + + registerCommand( this, QString::fromLatin1("say"), SLOT( slotSayCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /say - Say text in this chat. This is the same as just typing a message, but is very " + "useful for scripts." ), 1 ); + + registerCommand( this, QString::fromLatin1("exec"), SLOT( slotExecCommand( const QString &, Kopete::ChatSession * ) ), + i18n( "USAGE: /exec [-o] - Executes the specified command and displays the output in the chat buffer. " + "If -o is specified, the output is sent to all members of the chat."), 1 ); + + connect( Kopete::PluginManager::self(), SIGNAL( pluginLoaded( Kopete::Plugin*) ), + this, SLOT(slotPluginLoaded(Kopete::Plugin*) ) ); + + connect( Kopete::ChatSessionManager::self(), SIGNAL( viewCreated( KopeteView * ) ), + this, SLOT( slotViewCreated( KopeteView* ) ) ); +} + +Kopete::CommandHandler::~CommandHandler() +{ + delete p; +} + +Kopete::CommandHandler *Kopete::CommandHandler::commandHandler() +{ + if( !p ) + { + p = new CommandHandlerPrivate; + p->s_handler = new Kopete::CommandHandler(); + } + + return p->s_handler; +} + +void Kopete::CommandHandler::registerCommand( QObject *parent, const QString &command, const char* handlerSlot, + const QString &help, uint minArgs, int maxArgs, const KShortcut &cut, const QString &pix ) +{ + QString lowerCommand = command.lower(); + + Kopete::Command *mCommand = new Kopete::Command( parent, lowerCommand, handlerSlot, help, + Normal, QString::null, minArgs, maxArgs, cut, pix); + p->pluginCommands[ parent ].insert( lowerCommand, mCommand ); +} + +void Kopete::CommandHandler::unregisterCommand( QObject *parent, const QString &command ) +{ + if( p->pluginCommands[ parent ].find(command) ) + p->pluginCommands[ parent ].remove( command ); +} + +void Kopete::CommandHandler::registerAlias( QObject *parent, const QString &alias, const QString &formatString, + const QString &help, CommandType type, uint minArgs, int maxArgs, const KShortcut &cut, const QString &pix ) +{ + QString lowerAlias = alias.lower(); + + Kopete::Command *mCommand = new Kopete::Command( parent, lowerAlias, 0L, help, type, + formatString, minArgs, maxArgs, cut, pix ); + p->pluginCommands[ parent ].insert( lowerAlias, mCommand ); +} + +void Kopete::CommandHandler::unregisterAlias( QObject *parent, const QString &alias ) +{ + if( p->pluginCommands[ parent ].find(alias) ) + p->pluginCommands[ parent ].remove( alias ); +} + +bool Kopete::CommandHandler::processMessage( const QString &msg, Kopete::ChatSession *manager ) +{ + if( p->inCommand ) + return false; + QRegExp splitRx( QString::fromLatin1("^/([\\S]+)(.*)") ); + QString command; + QString args; + if(splitRx.search(msg) != -1) + { + command = splitRx.cap(1); + args = splitRx.cap(2).mid(1); + } + else + return false; + + CommandList mCommands = commands( manager->protocol() ); + Kopete::Command *c = mCommands[ command ]; + if(c) + { + kdDebug(14010) << k_funcinfo << "Handled Command" << endl; + if( c->type() != SystemAlias && c->type() != UserAlias ) + p->inCommand = true; + + c->processCommand( args, manager ); + p->inCommand = false; + + return true; + } + + return false; +} + +bool Kopete::CommandHandler::processMessage( Kopete::Message &msg, Kopete::ChatSession *manager ) +{ + QString messageBody = msg.plainBody(); + + return processMessage( messageBody, manager ); +} + +void Kopete::CommandHandler::slotHelpCommand( const QString &args, Kopete::ChatSession *manager ) +{ + QString output; + if( args.isEmpty() ) + { + int commandCount = 0; + output = i18n( "Available Commands:\n" ); + + CommandList mCommands = commands( manager->myself()->protocol() ); + QDictIterator it( mCommands ); + for( ; it.current(); ++it ) + { + output.append( it.current()->command().upper() + '\t' ); + if( commandCount++ == 5 ) + { + commandCount = 0; + output.append( '\n' ); + } + } + output.append( i18n( "\nType /help for more information." ) ); + } + else + { + QString command = parseArguments( args ).front().lower(); + Kopete::Command *c = commands( manager->myself()->protocol() )[ command ]; + if( c && !c->help().isNull() ) + output = c->help(); + else + output = i18n("There is no help available for '%1'.").arg( command ); + } + + Kopete::Message msg(manager->myself(), manager->members(), output, Kopete::Message::Internal, Kopete::Message::PlainText); + manager->appendMessage(msg); +} + +void Kopete::CommandHandler::slotSayCommand( const QString &args, Kopete::ChatSession *manager ) +{ + //Just say whatever is passed + Kopete::Message msg(manager->myself(), manager->members(), args, + Kopete::Message::Outbound, Kopete::Message::PlainText); + manager->sendMessage(msg); +} + +void Kopete::CommandHandler::slotExecCommand( const QString &args, Kopete::ChatSession *manager ) +{ + if( !args.isEmpty() ) + { + KProcess *proc = 0L; + if ( kapp->authorize( QString::fromLatin1( "shell_access" ) ) ) + proc = new KProcess(manager); + + if( proc ) + { + *proc << QString::fromLatin1("sh") << QString::fromLatin1("-c"); + + QStringList argsList = parseArguments( args ); + if( argsList.front() == QString::fromLatin1("-o") ) + { + p->processMap.insert( proc, ManagerPair(manager, Kopete::Message::Outbound) ); + *proc << args.section(QRegExp(QString::fromLatin1("\\s+")), 1); + } + else + { + p->processMap.insert( proc, ManagerPair(manager, Kopete::Message::Internal) ); + *proc << args; + } + + connect(proc, SIGNAL(receivedStdout(KProcess *, char *, int)), this, SLOT(slotExecReturnedData(KProcess *, char *, int))); + connect(proc, SIGNAL(receivedStderr(KProcess *, char *, int)), this, SLOT(slotExecReturnedData(KProcess *, char *, int))); + proc->start( KProcess::NotifyOnExit, KProcess::AllOutput ); + } + else + { + Kopete::Message msg(manager->myself(), manager->members(), + i18n( "ERROR: Shell access has been restricted on your system. The /exec command will not function." ), + Kopete::Message::Internal, Kopete::Message::PlainText ); + manager->sendMessage( msg ); + } + } +} + +void Kopete::CommandHandler::slotClearCommand( const QString &, Kopete::ChatSession *manager ) +{ + if( manager->view() ) + manager->view()->clear(); +} + +void Kopete::CommandHandler::slotPartCommand( const QString &, Kopete::ChatSession *manager ) +{ + if( manager->view() ) + manager->view()->closeView(); +} + +void Kopete::CommandHandler::slotAwayCommand( const QString &args, Kopete::ChatSession *manager ) +{ + bool goAway = !manager->account()->isAway(); + + if( args.isEmpty() ) + manager->account()->setAway( goAway ); + else + manager->account()->setAway( goAway, args ); +} + +void Kopete::CommandHandler::slotAwayAllCommand( const QString &args, Kopete::ChatSession *manager ) +{ + if( manager->account()->isAway() ) + Kopete::AccountManager::self()->setAvailableAll(); + + else + { + if( args.isEmpty() ) + Kopete::AccountManager::self()->setAwayAll(); + else + Kopete::AccountManager::self()->setAwayAll( args ); + } +} + +void Kopete::CommandHandler::slotCloseCommand( const QString &, Kopete::ChatSession *manager ) +{ + if( manager->view() ) + manager->view()->closeView(); +} + +void Kopete::CommandHandler::slotExecReturnedData(KProcess *proc, char *buff, int bufflen ) +{ + kdDebug(14010) << k_funcinfo << endl; + QString buffer = QString::fromLocal8Bit( buff, bufflen ); + ManagerPair mgrPair = p->processMap[ proc ]; + Kopete::Message msg( mgrPair.first->myself(), mgrPair.first->members(), buffer, mgrPair.second, Kopete::Message::PlainText ); + if( mgrPair.second == Kopete::Message::Outbound ) + mgrPair.first->sendMessage( msg ); + else + mgrPair.first->appendMessage( msg ); +} + +void Kopete::CommandHandler::slotExecFinished(KProcess *proc) +{ + delete proc; + p->processMap.remove( proc ); +} + +QStringList Kopete::CommandHandler::parseArguments( const QString &args ) +{ + QStringList arguments; + QRegExp quotedArgs( QString::fromLatin1("\"(.*)\"") ); + quotedArgs.setMinimal( true ); + + if ( quotedArgs.search( args ) != -1 ) + { + for( int i = 0; i< quotedArgs.numCaptures(); i++ ) + arguments.append( quotedArgs.cap(i) ); + } + + QStringList otherArgs = QStringList::split( QRegExp(QString::fromLatin1("\\s+")), args.section( quotedArgs, 0 ) ); + for( QStringList::Iterator it = otherArgs.begin(); it != otherArgs.end(); ++it ) + arguments.append( *it ); + + return arguments; +} + +bool Kopete::CommandHandler::commandHandled( const QString &command ) +{ + for( PluginCommandMap::Iterator it = p->pluginCommands.begin(); it != p->pluginCommands.end(); ++it ) + { + if( it.data()[ command ] ) + return true; + } + + return false; +} + +bool Kopete::CommandHandler::commandHandledByProtocol( const QString &command, Kopete::Protocol *protocol ) +{ + // Make sure the protocol is not NULL + if(!protocol) + return false; + + // Fetch the commands for the protocol + CommandList commandList = commands( protocol ); + QDictIterator it ( commandList ); + + // Loop through commands and check if they match the supplied command + for( ; it.current(); ++it ) + { + if( it.current()->command().lower() == command ) + return true; + } + + // No commands found + return false; +} + +CommandList Kopete::CommandHandler::commands( Kopete::Protocol *protocol ) +{ + CommandList commandList(63, false); + + //Add plugin user aliases first + addCommands( p->pluginCommands[protocol], commandList, UserAlias ); + + //Add plugin system aliases next + addCommands( p->pluginCommands[protocol], commandList, SystemAlias ); + + //Add the commands for this protocol next + addCommands( p->pluginCommands[protocol], commandList ); + + //Add plugin commands + for( PluginCommandMap::Iterator it = p->pluginCommands.begin(); it != p->pluginCommands.end(); ++it ) + { + if( !it.key()->inherits("Kopete::Protocol") && it.key()->inherits("Kopete::Plugin") ) + addCommands( it.data(), commandList ); + } + + //Add global user aliases first + addCommands( p->pluginCommands[this], commandList, UserAlias ); + + //Add global system aliases next + addCommands( p->pluginCommands[this], commandList, SystemAlias ); + + //Add the internal commands *last* + addCommands( p->pluginCommands[this], commandList ); + + return commandList; +} + +void Kopete::CommandHandler::addCommands( CommandList &from, CommandList &to, CommandType type ) +{ + QDictIterator itDict( from ); + for( ; itDict.current(); ++itDict ) + { + if( !to[ itDict.currentKey() ] && + ( type == Undefined || itDict.current()->type() == type ) ) + to.insert( itDict.currentKey(), itDict.current() ); + } +} + +void Kopete::CommandHandler::slotViewCreated( KopeteView *view ) +{ + new KopeteCommandGUIClient( view->msgManager() ); +} + +void Kopete::CommandHandler::slotPluginLoaded( Kopete::Plugin *plugin ) +{ + connect( plugin, SIGNAL( destroyed( QObject * ) ), this, SLOT( slotPluginDestroyed( QObject * ) ) ); + if( !p->pluginCommands.contains( plugin ) ) + { + //Create a QDict optomized for a larger # of commands, and case insensitive + CommandList mCommands(31, false); + mCommands.setAutoDelete( true ); + p->pluginCommands.insert( plugin, mCommands ); + } +} + +void Kopete::CommandHandler::slotPluginDestroyed( QObject *plugin ) +{ + p->pluginCommands.remove( static_cast(plugin) ); +} + +#include "kopetecommandhandler.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetecommandhandler.h b/kopete/libkopete/kopetecommandhandler.h new file mode 100644 index 00000000..f763ace6 --- /dev/null +++ b/kopete/libkopete/kopetecommandhandler.h @@ -0,0 +1,219 @@ +/* + kopetecommandhandler.h - Command Handler + + Copyright (c) 2003 by Jason Keirstead + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETECOMMANDHANDLER_H_ +#define _KOPETECOMMANDHANDLER_H_ + +#include +#include +#include "kopetemessage.h" + +#include "kopete_export.h" + +class KProcess; + +struct CommandHandlerPrivate; + +class KopeteView; +class KopeteCommandGUIClient; + +namespace Kopete +{ + +class ChatSession; +class Plugin; +class Protocol; +class Command; + +typedef QDict CommandList; + +/** + * @author Jason Keirstead + * + * The Kopete::CommandHandler can handle /action like messages + */ +class KOPETE_EXPORT CommandHandler : public QObject +{ + friend class ::KopeteCommandGUIClient; + + Q_OBJECT + + public: + /** + * an enum defining the type of a command + */ + enum CommandType { Normal, SystemAlias, UserAlias, Undefined }; + + /** + * Returns a pointer to the command handler + */ + static CommandHandler *commandHandler(); + + /** + * \brief Register a command with the command handler. + * + * Command matching is case insensitive. All commands are registered, + * regardless of whether or not they are already handled by another + * handler. This is so that if the first plugin is unloaded, the next + * handler in the sequence will handle the command. However, there are + * certain commands which are reserved (internally handled by the + * Kopete::CommandHandler). These commands can also be overridden by + * registering a new duplicate command. + * + * @param parent The plugin who owns this command + * @param command The command we want to handle, not including the '/' + * @param handlerSlot The slot used to handle the command. This slot must + * accept two parameters, a QString of arguments, and a Kopete::ChatSession + * pointer to the manager under which the command was sent. + * @param help An optional help string to be shown when the user uses + * /help \ + * @param minArgs the minimum number of arguments for this command + * @param maxArgs the maximum number of arguments this command takes + * @param cut a default keyboard shortcut + * @param pix icon name, the icon will be shown in menus + */ + void registerCommand( QObject *parent, const QString &command, const char* handlerSlot, + const QString &help = QString::null, uint minArgs = 0, int maxArgs = -1, + const KShortcut &cut = 0, const QString &pix = QString::null ); + + /** + * \brief Register a command alias. + * + * @param parent The plugin who owns this alias + * @param alias The command for the alias + * @param formatString This is the string that will be transformed into another + * command. The formatString should begin with an already existing command, + * followed by any other arguments. The variables %1, %2... %9 will be substituted + * with the arguments passed into the alias. The variable %s will be substituted with + * the entire argument string + * @param help An optional help string to be shown when the user uses + * /help \ + * @param minArgs the minimum number of arguments for this command + * @param maxArgs the maximum number of arguments this command takes + * @param cut a default keyboard shortcut + * @param pix icon name, the icon will be shown in menus + */ + void registerAlias( QObject *parent, + const QString &alias, + const QString &formatString, + const QString &help = QString::null, + CommandType = SystemAlias, + uint minArgs = 0, + int maxArgs = -1, + const KShortcut &cut = 0, + const QString &pix = QString::null ); + + /** + * \brief Unregister a command. + * + * When a plugin unloads, all commands are automaticlly unregistered and deleted. + * This function should only be called in the case of a plugin which loads and + * unloads commands dynamically. + * + * @param parent The plugin who owns this command + * @param command The command to unload + */ + void unregisterCommand( QObject *parent, const QString &command ); + + /** + * \brief Unregister an alias. + * + * \see unregisterCommand( QObject *parent, const QString &command ) + * @param parent The plugin who owns this alias + * @param alias The alais to unload + */ + void unregisterAlias( QObject *parent, const QString &alias ); + + /** + * \brief Process a message to see if any commands should be handled + * + * @param msg The message to process + * @param manager The manager who owns this message + * @return True if the command was handled, false if not + */ + bool processMessage( Message &msg, ChatSession *manager ); + + /** + * \brief Process a message to see if any commands should be handled + * + * \see processMessage( Kopete::Message &msg, Kopete::ChatSession *manager) + * \param msg A QString contain the message + * \param manager the Kopete::ChatSession who will own the message + * \return true if the command was handled, false if the command was not handled. + */ + bool processMessage( const QString &msg, ChatSession *manager ); + + /** + * Parses a string of command arguments into a QStringList. Quoted + * blocks within the arguments string are treated as one argument. + */ + static QStringList parseArguments( const QString &args ); + + /** + * \brief Check if a command is already handled + * + * @param command The command to check + * @return True if the command is already being handled, False if not + */ + bool commandHandled( const QString &command ); + + /** + * \brief Check if a command is already handled by a spesific protocol + * + * @param command The command to check + * @param protocol The protocol to check + * @return True if the command is already being handled, False if not + */ + bool commandHandledByProtocol( const QString &command, Protocol *protocol); + + private slots: + void slotPluginLoaded( Kopete::Plugin * ); + void slotPluginDestroyed( QObject * ); + void slotExecReturnedData(KProcess *proc, char *buff, int bufflen ); + void slotExecFinished(KProcess *proc); + void slotViewCreated( KopeteView *view ); + + void slotHelpCommand( const QString & args, Kopete::ChatSession *manager ); + void slotClearCommand( const QString & args, Kopete::ChatSession *manager ); + void slotPartCommand( const QString & args, Kopete::ChatSession *manager ); + void slotCloseCommand( const QString & args, Kopete::ChatSession *manager ); + //void slotMeCommand( const QString & args, Kopete::ChatSession *manager ); + void slotExecCommand( const QString & args, Kopete::ChatSession *manager ); + void slotAwayCommand( const QString & args, Kopete::ChatSession *manager ); + void slotAwayAllCommand( const QString & args, Kopete::ChatSession *manager ); + void slotSayCommand( const QString & args, Kopete::ChatSession *manager ); + + private: + /** + * Helper function. Returns all the commands that can be used by a KMM of this protocol + * (all non-protocol commands, plus this protocols commands) + */ + CommandList commands( Protocol * ); + + /** + * Helper function for commands() + */ + void addCommands( CommandList &from, CommandList &to, CommandType type = Undefined ); + + CommandHandler(); + ~CommandHandler(); + + static CommandHandlerPrivate *p; +}; + +} + +#endif diff --git a/kopete/libkopete/kopetecommandui.rc b/kopete/libkopete/kopetecommandui.rc new file mode 100644 index 00000000..8cd10680 --- /dev/null +++ b/kopete/libkopete/kopetecommandui.rc @@ -0,0 +1,12 @@ + + + + + + Commands + + + + + + diff --git a/kopete/libkopete/kopeteconfig.kcfgc b/kopete/libkopete/kopeteconfig.kcfgc new file mode 100644 index 00000000..86dbae8e --- /dev/null +++ b/kopete/libkopete/kopeteconfig.kcfgc @@ -0,0 +1,13 @@ +ClassName=Config +File=kopete.kcfg +GlobalEnums=false +Inherits=KConfigSkeleton +ItemAccessors=true +MemberVariables=private +Mutators=true +NameSpace=Kopete +SetUserTexts=false +Singleton=true +Visibility=KOPETE_EXPORT +IncludeFiles=kopete_export.h + diff --git a/kopete/libkopete/kopetecontact.cpp b/kopete/libkopete/kopetecontact.cpp new file mode 100644 index 00000000..15cb27df --- /dev/null +++ b/kopete/libkopete/kopetecontact.cpp @@ -0,0 +1,863 @@ +/* + kopetecontact.cpp - Kopete Contact + + Copyright (c) 2002-2004 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2004 by Olivier Goffart + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetecontact.h" + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "kopetecontactlist.h" +#include "kopeteglobal.h" +#include "kopeteuiglobal.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopetestdaction.h" +#include "kopetechatsession.h" +#include "kopeteview.h" +#include "kopetemetacontact.h" +#include "kopeteprefs.h" +#include "metacontactselectorwidget.h" +#include "kopeteemoticons.h" + +//For the moving to another metacontact dialog +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Kopete { + +struct Contact::Private +{ +public: + bool fileCapable; + + OnlineStatus onlineStatus; + Account *account; + + MetaContact *metaContact; + + QString contactId; + QString icon; + + QTime idleTimer; + unsigned long int idleTime; + + Kopete::ContactProperty::Map properties; + +}; + +Contact::Contact( Account *account, const QString &contactId, + MetaContact *parent, const QString &icon ) + : QObject( parent ) +{ + d = new Private; + + //kdDebug( 14010 ) << k_funcinfo << "Creating contact with id " << contactId << endl; + + d->contactId = contactId; + d->metaContact = parent; + d->fileCapable = false; + d->account = account; + d->idleTime = 0; + d->icon = icon; + + // If can happend that a MetaContact may be used without a account + // (ex: for unit tests or chat window style preview) + if ( account ) + { + account->registerContact( this ); + connect( account, SIGNAL( isConnectedChanged() ), SLOT( slotAccountIsConnectedChanged() ) ); + } + + // Need to check this because myself() may have no parent + // Maybe too the metaContact doesn't have a valid protocol() + // (ex: for unit tests or chat window style preview) + if( parent && protocol() ) + { + connect( parent, SIGNAL( aboutToSave( Kopete::MetaContact * ) ), + protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact * ) ) ); + + parent->addContact( this ); + } + + +} + +Contact::~Contact() +{ + //kdDebug(14010) << k_funcinfo << endl; + emit( contactDestroyed( this ) ); + delete d; +} + + + +OnlineStatus Contact::onlineStatus() const +{ + if ( this == account()->myself() || account()->isConnected() ) + return d->onlineStatus; + else + return protocol()->accountOfflineStatus(); +} + +void Contact::setOnlineStatus( const OnlineStatus &status ) +{ + if( status == d->onlineStatus ) + return; + + OnlineStatus oldStatus = d->onlineStatus; + d->onlineStatus = status; + + Kopete::Global::Properties *globalProps = Kopete::Global::Properties::self(); + + // Contact changed from Offline to another status + if( oldStatus.status() == OnlineStatus::Offline && + status.status() != OnlineStatus::Offline ) + { + setProperty( globalProps->onlineSince(), QDateTime::currentDateTime() ); + /*kdDebug(14010) << k_funcinfo << "REMOVING lastSeen property for " << + d->displayName << endl;*/ + removeProperty( globalProps->lastSeen() ); + } + else if( oldStatus.status() != OnlineStatus::Offline && + oldStatus.status() != OnlineStatus::Unknown && + status.status() == OnlineStatus::Offline ) // Contact went back offline + { + removeProperty( globalProps->onlineSince() ); + /*kdDebug(14010) << k_funcinfo << "SETTING lastSeen property for " << + d->displayName << endl;*/ + setProperty( globalProps->lastSeen(), QDateTime::currentDateTime() ); + } + + if ( this == account()->myself() || account()->isConnected() ) + emit onlineStatusChanged( this, status, oldStatus ); +} + +void Contact::slotAccountIsConnectedChanged() +{ + if ( this == account()->myself() ) + return; + + if ( account()->isConnected() ) + emit onlineStatusChanged( this, d->onlineStatus, protocol()->accountOfflineStatus() ); + else + emit onlineStatusChanged( this, protocol()->accountOfflineStatus(), d->onlineStatus ); +} + + +void Contact::sendFile( const KURL &, const QString &, uint ) +{ + kdWarning( 14010 ) << k_funcinfo << "Plugin " + << protocol()->pluginId() << " has enabled file sending, " + << "but didn't implement it!" << endl; +} + +void Contact::slotAddContact() +{ + if( metaContact() ) + { + metaContact()->setTemporary( false ); + ContactList::self()->addMetaContact( metaContact() ); + } +} + +KPopupMenu* Contact::popupMenu( ChatSession *manager ) +{ + // Build the menu + KPopupMenu *menu = new KPopupMenu(); + + // insert title + QString titleText; + QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString(); + if( nick.isEmpty() ) + titleText = QString::fromLatin1( "%1 (%2)" ).arg( contactId(), onlineStatus().description() ); + else + titleText = QString::fromLatin1( "%1 <%2> (%3)" ).arg( nick, contactId(), onlineStatus().description() ); + menu->insertTitle( titleText ); + + if( metaContact() && metaContact()->isTemporary() && contactId() != account()->myself()->contactId() ) + { + KAction *actionAddContact = new KAction( i18n( "&Add to Your Contact List" ), QString::fromLatin1( "add_user" ), + 0, this, SLOT( slotAddContact() ), menu, "actionAddContact" ); + actionAddContact->plug( menu ); + menu->insertSeparator(); + } + + // FIXME: After KDE 3.2 we should make isReachable do the isConnected call so it can be removed here - Martijn + bool reach = account()->isConnected() && isReachable(); + bool myself = (this == account()->myself()); + + KAction *actionSendMessage = KopeteStdAction::sendMessage( this, SLOT( sendMessage() ), menu, "actionSendMessage" ); + actionSendMessage->setEnabled( reach && !myself ); + actionSendMessage->plug( menu ); + + KAction *actionChat = KopeteStdAction::chat( this, SLOT( startChat() ), menu, "actionChat" ); + actionChat->setEnabled( reach && !myself ); + actionChat->plug( menu ); + + KAction *actionSendFile = KopeteStdAction::sendFile( this, SLOT( sendFile() ), menu, "actionSendFile" ); + actionSendFile->setEnabled( reach && d->fileCapable && !myself ); + actionSendFile->plug( menu ); + + // Protocol specific options will go below this separator + // through the use of the customContextMenuActions() function + + // Get the custom actions from the protocols ( pure virtual function ) + QPtrList *customActions = customContextMenuActions( manager ); + if( customActions && !customActions->isEmpty() ) + { + menu->insertSeparator(); + + for( KAction *a = customActions->first(); a; a = customActions->next() ) + a->plug( menu ); + } + delete customActions; + + menu->insertSeparator(); + + if( metaContact() && !metaContact()->isTemporary() ) + KopeteStdAction::changeMetaContact( this, SLOT( changeMetaContact() ), menu, "actionChangeMetaContact" )->plug( menu ); + + KopeteStdAction::contactInfo( this, SLOT( slotUserInfo() ), menu, "actionUserInfo" )->plug( menu ); + +#if 0 //this is not fully implemented yet (and doesn't work). disable for now - Olivier 2005-01-11 + if ( account()->isBlocked( d->contactId ) ) + KopeteStdAction::unblockContact( this, SLOT( slotUnblock() ), menu, "actionUnblockContact" )->plug( menu ); + else + KopeteStdAction::blockContact( this, SLOT( slotBlock() ), menu, "actionBlockContact" )->plug( menu ); +#endif + + if( metaContact() && !metaContact()->isTemporary() ) + KopeteStdAction::deleteContact( this, SLOT( slotDelete() ), menu, "actionDeleteContact" )->plug( menu ); + + return menu; +} + +void Contact::changeMetaContact() +{ + KDialogBase *moveDialog = new KDialogBase( Kopete::UI::Global::mainWidget(), "moveDialog", true, i18n( "Move Contact" ), + KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok, true ); + + QVBox *w = new QVBox( moveDialog ); + w->setSpacing( KDialog::spacingHint() ); + Kopete::UI::MetaContactSelectorWidget *selector = new Kopete::UI::MetaContactSelectorWidget(w); + selector->setLabelMessage(i18n( "Select the meta contact to which you want to move this contact:" )); + // exclude this metacontact as a target metacontact for the move + selector->excludeMetaContact( metaContact() ); + QCheckBox *chkCreateNew = new QCheckBox( i18n( "Create a new metacontact for this contact" ), w ); + QWhatsThis::add( chkCreateNew , i18n( "If you select this option, a new metacontact will be created in the top-level group " + "with the name of this contact and the contact will be moved to it." ) ); + QObject::connect( chkCreateNew , SIGNAL( toggled(bool) ) , selector , SLOT ( setDisabled(bool) ) ) ; + + moveDialog->setMainWidget(w); + if( moveDialog->exec() == QDialog::Accepted ) + { + Kopete::MetaContact *mc = selector->metaContact(); + if(chkCreateNew->isChecked()) + { + mc=new Kopete::MetaContact(); + Kopete::ContactList::self()->addMetaContact(mc); + } + if( mc ) + { + setMetaContact( mc ); + } + } + + moveDialog->deleteLater(); +} + +void Contact::setMetaContact( MetaContact *m ) +{ + MetaContact *old = d->metaContact; + if(old==m) //that make no sens + return; + + if( old ) + { + int result=KMessageBox::No; + if( old->isTemporary() ) + result=KMessageBox::Yes; + else if( old->contacts().count()==1 ) + { //only one contact, including this one, that mean the contact will be empty efter the move + result = KMessageBox::questionYesNoCancel( Kopete::UI::Global::mainWidget(), i18n( "You are moving the contact `%1' to the meta contact `%2'.\n" + "`%3' will be empty afterwards. Do you want to delete this contact?" ) + .arg(contactId(), m ? m->displayName() : QString::null, old->displayName()) + , i18n( "Move Contact" ), KStdGuiItem::del(), i18n( "&Keep" ) , QString::fromLatin1("delete_old_contact_when_move") ); + if(result==KMessageBox::Cancel) + return; + } + old->removeContact( this ); + disconnect( old, SIGNAL( aboutToSave( Kopete::MetaContact * ) ), + protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact * ) ) ); + + if(result==KMessageBox::Yes) + { + //remove the old metacontact. (this delete the MC) + ContactList::self()->removeMetaContact(old); + } + else + { + d->metaContact = m; //i am forced to do that now if i want the next line works + //remove cached data for this protocol which will not be removed since we disconnected + protocol()->slotMetaContactAboutToSave( old ); + } + } + + d->metaContact = m; + + if( m ) + { + m->addContact( this ); + m->insertChild( this ); + // it is necessary to call this write here, because MetaContact::addContact() does not differentiate + // between adding completely new contacts (which should be written to kabc) and restoring upon restart + // (where no write is needed). + KABCPersistence::self()->write( m ); + connect( d->metaContact, SIGNAL( aboutToSave( Kopete::MetaContact * ) ), + protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact * ) ) ); + } + sync(); +} + +void Contact::serialize( QMap &/*serializedData*/, + QMap & /* addressBookData */ ) +{ +} + + +void Contact::serializeProperties(QMap &serializedData) +{ + + Kopete::ContactProperty::Map::ConstIterator it;// = d->properties.ConstIterator; + for (it=d->properties.begin(); it != d->properties.end(); ++it) + { + if (!it.data().tmpl().persistent()) + continue; + + QVariant val = it.data().value(); + QString key = QString::fromLatin1("prop_%1_%2").arg(QString::fromLatin1(val.typeName()), it.key()); + + serializedData[key] = val.toString(); + + } // end for() +} // end serializeProperties() + +void Contact::deserializeProperties( + QMap &serializedData ) +{ + QMap::ConstIterator it; + for ( it=serializedData.begin(); it != serializedData.end(); ++it ) + { + QString key = it.key(); + + if ( !key.startsWith( QString::fromLatin1("prop_") ) ) // avoid parsing other serialized data + continue; + + QStringList keyList = QStringList::split( QChar('_'), key, false ); + if( keyList.count() < 3 ) // invalid key, not enough parts in string "prop_X_Y" + continue; + + key = keyList[2]; // overwrite key var with the real key name this property has + QString type( keyList[1] ); // needed for QVariant casting + + QVariant variant( it.data() ); + if( !variant.cast(QVariant::nameToType(type.latin1())) ) + { + kdDebug(14010) << k_funcinfo << + "Casting QVariant to needed type FAILED" << + "key=" << key << ", type=" << type << endl; + continue; + } + + Kopete::ContactPropertyTmpl tmpl = Kopete::Global::Properties::self()->tmpl(key); + if( tmpl.isNull() ) + { + kdDebug( 14010 ) << k_funcinfo << "no ContactPropertyTmpl defined for" \ + " key " << key << ", cannot restore persistent property" << endl; + continue; + } + + setProperty(tmpl, variant); + } // end for() +} + + +bool Contact::isReachable() +{ + // The default implementation returns false when offline and true + // otherwise. Subclass if you need more control over the process. + return onlineStatus().status() != OnlineStatus::Offline; +} + + +void Contact::startChat() +{ + KopeteView *v=manager( CanCreate )->view(true, QString::fromLatin1("kopete_chatwindow") ); + if(v) + v->raise(true); +} + +void Contact::sendMessage() +{ + KopeteView *v=manager( CanCreate )->view(true, QString::fromLatin1("kopete_emailwindow") ); + if(v) + v->raise(true); +} + +void Contact::execute() +{ + // FIXME: After KDE 3.2 remove the isConnected check and move it to isReachable - Martijn + if ( account()->isConnected() && isReachable() ) + { + KopeteView *v=manager( CanCreate )->view(true, KopetePrefs::prefs()->interfacePreference() ); + if(v) + v->raise(true); + } + else + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "This user is not reachable at the moment. Please try a protocol that supports offline sending, or wait " + "until this user comes online." ), i18n( "User is Not Reachable" ) ); + } +} + +void Contact::slotDelete() +{ + if ( KMessageBox::warningContinueCancel( Kopete::UI::Global::mainWidget(), + i18n( "Are you sure you want to remove the contact '%1' from your contact list?" ). + arg( d->contactId ), i18n( "Remove Contact" ), KGuiItem(i18n("Remove"), QString::fromLatin1("delete_user") ), + QString::fromLatin1("askRemoveContact"), KMessageBox::Notify | KMessageBox::Dangerous ) + == KMessageBox::Continue ) + { + deleteContact(); + } +} + +void Contact::deleteContact() +{ + // Default implementation simply deletes the contact + deleteLater(); +} + + +MetaContact * Contact::metaContact() const +{ + return d->metaContact; +} + +QString Contact::contactId() const +{ + return d->contactId; +} + +Protocol * Contact::protocol() const +{ + return d->account ? d->account->protocol() : 0L; +} + +Account * Contact::account() const +{ + return d->account; +} + + + +void Contact::sync(unsigned int) +{ + /* Default implementation does nothing */ +} + +QString& Contact::icon() const +{ + return d->icon; +} + +void Contact::setIcon( const QString& icon ) +{ + d->icon = icon; + return; +} + +QPtrList *Contact::customContextMenuActions() +{ + return 0L; +} + +QPtrList *Contact::customContextMenuActions( ChatSession * /* manager */ ) +{ + return customContextMenuActions(); +} + + +bool Contact::isOnline() const +{ + return onlineStatus().isDefinitelyOnline(); +} + + +bool Contact::isFileCapable() const +{ + return d->fileCapable; +} + +void Contact::setFileCapable( bool filecap ) +{ + d->fileCapable = filecap; +} + + +bool Contact::canAcceptFiles() const +{ + return isOnline() && d->fileCapable; +} + +unsigned long int Contact::idleTime() const +{ + if(d->idleTime==0) + return 0; + + return d->idleTime+(d->idleTimer.elapsed()/1000); +} + +void Contact::setIdleTime( unsigned long int t ) +{ + bool idleChanged = false; + if(d->idleTime != t) + idleChanged = true; + d->idleTime=t; + if(t > 0) + d->idleTimer.start(); +//FIXME: if t == 0, idleTime() will now return garbage +// else +// d->idleTimer.stop(); + if(idleChanged) + emit idleStateChanged(this); +} + + +QStringList Contact::properties() const +{ + return d->properties.keys(); +} + +bool Contact::hasProperty(const QString &key) const +{ + return d->properties.contains(key); +} + +const ContactProperty &Contact::property(const QString &key) const +{ + if(hasProperty(key)) + return d->properties[key]; + else + return Kopete::ContactProperty::null; +} + +const Kopete::ContactProperty &Contact::property( + const Kopete::ContactPropertyTmpl &tmpl) const +{ + if(hasProperty(tmpl.key())) + return d->properties[tmpl.key()]; + else + return Kopete::ContactProperty::null; +} + + +void Contact::setProperty(const Kopete::ContactPropertyTmpl &tmpl, + const QVariant &value) +{ + if(tmpl.isNull() || tmpl.key().isEmpty()) + { + kdDebug(14000) << k_funcinfo << + "No valid template for property passed!" << endl; + return; + } + + if(value.isNull() || value.canCast(QVariant::String) && value.toString().isEmpty()) + { + removeProperty(tmpl); + } + else + { + QVariant oldValue = property(tmpl.key()).value(); + + if(oldValue != value) + { + Kopete::ContactProperty prop(tmpl, value); + d->properties.insert(tmpl.key(), prop, true); + + emit propertyChanged(this, tmpl.key(), oldValue, value); + } + } +} + +void Contact::removeProperty(const Kopete::ContactPropertyTmpl &tmpl) +{ + if(!tmpl.isNull() && !tmpl.key().isEmpty()) + { + + QVariant oldValue = property(tmpl.key()).value(); + d->properties.remove(tmpl.key()); + emit propertyChanged(this, tmpl.key(), oldValue, QVariant()); + } +} + + +QString Contact::toolTip() const +{ + Kopete::ContactProperty p; + QString tip; + QStringList shownProps = KopetePrefs::prefs()->toolTipContents(); + + // -------------------------------------------------------------------------- + // Fixed part of tooltip + + QString iconName = QString::fromLatin1("kopete-contact-icon:%1:%2:%3") + .arg( KURL::encode_string( protocol()->pluginId() ), + KURL::encode_string( account()->accountId() ), + KURL::encode_string( contactId() ) ); + + // TODO: the nickname should be a configurable properties, like others. -Olivier + QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString(); + if ( nick.isEmpty() ) + { + tip = i18n( "DISPLAY NAME
     CONTACT STATUS", + "%3
     %1" ). + arg( Kopete::Message::escape( onlineStatus().description() ), iconName, + Kopete::Message::escape( d->contactId ) ); + } + else + { + tip = i18n( "DISPLAY NAME (CONTACT ID)
     CONTACT STATUS", + "%4 (%3)
     %1" ). + arg( Kopete::Message::escape( onlineStatus().description() ), iconName, + Kopete::Message::escape( contactId() ), + Kopete::Emoticons::parseEmoticons( Kopete::Message::escape( nick ) ) ); + } + + // -------------------------------------------------------------------------- + // Configurable part of tooltip + + for(QStringList::Iterator it=shownProps.begin(); it!=shownProps.end(); ++it) + { + if((*it) == QString::fromLatin1("FormattedName")) + { + QString name = formattedName(); + if(!name.isEmpty()) + { + tip += i18n("
    Full Name: FORMATTED NAME", + "
    Full Name: %1").arg(QStyleSheet::escape(name)); + } + } + else if ((*it) == QString::fromLatin1("FormattedIdleTime")) + { + QString time = formattedIdleTime(); + if(!time.isEmpty()) + { + tip += i18n("
    Idle: FORMATTED IDLE TIME", + "
    Idle: %1").arg(time); + } + } + else if ((*it) == QString::fromLatin1("homePage")) + { + QString url = property(*it).value().toString(); + if(!url.isEmpty()) + { + tip += i18n("
    Home Page: FORMATTED URL", + "
    Home Page: %2"). + arg( KURL::encode_string( url ), Kopete::Message::escape( QStyleSheet::escape(url) ) ); + } + } + else if ((*it) == QString::fromLatin1("awayMessage")) + { + QString awaymsg = property(*it).value().toString(); + if(!awaymsg.isEmpty()) + { + tip += i18n("
    Away Message: FORMATTED AWAY MESSAGE", + "
    Away Message: %1").arg ( Kopete::Emoticons::parseEmoticons( Kopete::Message::escape(awaymsg) ) ); + } + } + else + { + p = property(*it); + if(!p.isNull()) + { + QVariant val = p.value(); + QString valueText; + + switch(val.type()) + { + case QVariant::DateTime: + valueText = KGlobal::locale()->formatDateTime(val.toDateTime()); + valueText = Kopete::Message::escape( valueText ); + break; + case QVariant::Date: + valueText = KGlobal::locale()->formatDate(val.toDate()); + valueText = Kopete::Message::escape( valueText ); + break; + case QVariant::Time: + valueText = KGlobal::locale()->formatTime(val.toTime()); + valueText = Kopete::Message::escape( valueText ); + break; + default: + if( p.isRichText() ) + { + valueText = val.toString(); + } + else + { + valueText = Kopete::Message::escape( val.toString() ); + } + } + + tip += i18n("
    PROPERTY LABEL: PROPERTY VALUE", + "
    %2: %1"). + arg( valueText, QStyleSheet::escape(p.tmpl().label()) ); + } + } + } + + return tip; +} + +QString Kopete::Contact::formattedName() const +{ + if( hasProperty(QString::fromLatin1("FormattedName")) ) + return property(QString::fromLatin1("FormattedName")).value().toString(); + + QString ret; + Kopete::ContactProperty first, last; + + first = property(QString::fromLatin1("firstName")); + last = property(QString::fromLatin1("lastName")); + if(!first.isNull()) + { + if(!last.isNull()) // contact has both first and last name + { + ret = i18n("firstName lastName", "%2 %1") + .arg(last.value().toString()) + .arg(first.value().toString()); + } + else // only first name set + { + ret = first.value().toString(); + } + } + else if(!last.isNull()) // only last name set + { + ret = last.value().toString(); + } + + return ret; +} + +QString Kopete::Contact::formattedIdleTime() const +{ + QString ret; + unsigned long int leftTime = idleTime(); + + if ( leftTime > 0 ) + { // FIXME: duplicated from code in kopetecontactlistview.cpp + unsigned long int days, hours, mins, secs; + + days = leftTime / ( 60*60*24 ); + leftTime = leftTime % ( 60*60*24 ); + hours = leftTime / ( 60*60 ); + leftTime = leftTime % ( 60*60 ); + mins = leftTime / 60; + secs = leftTime % 60; + + if ( days != 0 ) + { + ret = i18n( "d h m s", + "%4d %3h %2m %1s" ) + .arg( secs ) + .arg( mins ) + .arg( hours ) + .arg( days ); + } + else if ( hours != 0 ) + { + ret = i18n( "h m s", "%3h %2m %1s" ) + .arg( secs ) + .arg( mins ) + .arg( hours ); + } + else + { + ret = i18n( "m s", "%2m %1s" ) + .arg( secs ) + .arg( mins ); + } + } + return ret; +} + + +void Kopete::Contact::slotBlock() +{ + account()->block( d->contactId ); +} + +void Kopete::Contact::slotUnblock() +{ + account()->unblock( d->contactId ); +} + +void Kopete::Contact::setNickName( const QString &name ) +{ + setProperty( Kopete::Global::Properties::self()->nickName(), name ); +} + +QString Kopete::Contact::nickName() const +{ + QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString(); + if( !nick.isEmpty() ) + return nick; + + return contactId(); +} + +void Contact::virtual_hook( uint , void * ) +{ } + + +} //END namespace Kopete + + +#include "kopetecontact.moc" + + diff --git a/kopete/libkopete/kopetecontact.h b/kopete/libkopete/kopetecontact.h new file mode 100644 index 00000000..8f02bfc2 --- /dev/null +++ b/kopete/libkopete/kopetecontact.h @@ -0,0 +1,557 @@ +/* + kopetecontact.h - Kopete Contact + + Copyright (c) 2002-2004 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2004 by Olivier Goffart + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __KOPETECONTACT_H__ +#define __KOPETECONTACT_H__ + +#include +#include +#include +#include "kopeteglobal.h" + +#include "kopete_export.h" + +class QImage; +class KPopupMenu; +class KAction; + +namespace Kopete +{ + +class Group; +class MetaContact; +class ChatSession; +class OnlineStatus; +class Plugin; +class Protocol; +class Account; +typedef QPtrList GroupList; + +/** + * @author Duncan Mac-Vicar P. + * @author Martijn Klingens + * @author Olivier Goffart + * + * This class abstracts a generic contact + * Use it for inserting contacts in the contact list for example. + */ +class KOPETE_EXPORT Contact : public QObject +{ + Q_OBJECT + + Q_ENUMS( CanCreateFlags ) + Q_PROPERTY( QString formattedName READ formattedName ) + Q_PROPERTY( QString formattedIdleTime READ formattedIdleTime ) + Q_PROPERTY( bool isOnline READ isOnline ) + Q_PROPERTY( bool fileCapable READ isFileCapable WRITE setFileCapable ) + Q_PROPERTY( bool canAcceptFiles READ canAcceptFiles ) + //Q_PROPERTY( bool isReachable READ isReachable ) + Q_PROPERTY( QString contactId READ contactId ) + Q_PROPERTY( QString icon READ icon WRITE setIcon ) + Q_PROPERTY( QString toolTip READ toolTip ) + Q_PROPERTY( QString nickName READ nickName WRITE setNickName ) + //Q_PROPERTY( unsigned long idleTime READ idleTime WRITE setIdleTime ) + +public: + /** + * \brief Create new contact. + * + * The parent MetaContact must not be NULL + * + * \note id is required to be unique per protocol and per account. + * Across those boundaries ids may occur multiple times. + * The id is solely for comparing items safely (using pointers is + * more crash-prone). DO NOT assume anything regarding the id's + * value! Even if it may look like an ICQ UIN or an MSN passport, + * this is undefined and may change at any time! + * + * @param account is the parent account. this constructor automatically register the contact to the account + * @param id is the Contact's unique Id (mostly the user's login) + * @param parent is the parent @ref MetaContact this Contact is part of + * @param icon is an optional icon + */ + Contact( Account *account, const QString &id, MetaContact *parent, + const QString &icon = QString::null ); + + ~Contact(); + + /** + * \brief Get the metacontact for this contact + * @return The MetaContact object for this contact + */ + MetaContact *metaContact() const; + + + /** + * \brief Get the unique id that identifies a contact. + * + * \note Id is required to be unique per protocol and per account. + * Across those boundaries ids may occur multiple times. + * The id is solely for comparing items safely (using pointers is + * more crash-prone). DO NOT assume anything regarding the id's + * value! Even if it may look like an ICQ UIN or an MSN passport, + * this is undefined and may change at any time! + * + * @return The unique id of the contact + */ + QString contactId() const; + + /** + * \brief Get the protocol that the contact belongs to. + * + * simply return account()->protocol() + * + * @return the contact's protocol + */ + Protocol* protocol() const; + + /** + * \brief Get the account that this contact belongs to + * + * @return the Account object for this contact + */ + Account* account() const; + + /** + * \brief Move this contact to a new MetaContact. + * This basically reparents the contact and updates the internal + * data structures. + * If the old contact is going to be empty, a question may ask to the user if it wants to delete the old contact. + * + * @param m The new MetaContact to move this contact to + */ + void setMetaContact(MetaContact *m); + + + /** + * @brief Get whether this contact is online. + * @return @c true if the contact is online, @c false otherwise. + */ + bool isOnline() const; + + /** + * \brief Get whether this contact can receive messages + * + * Used in determining if the contact is able to + * receive messages. This function must be defined by child classes + * + * @return true if the contact can be reached + * @return false if the contact can not be reached + */ + // FIXME: After KDE 3.2 we should split this into a public, NON-virtual + // isReachable() accessor that checks for account->isConnected() + // and then calls a new virtual method that does the + // protocol-specific work, like 'doIsUnreachable' or so - Martijn + // + //FIXME: Can this be made const please? - JK + virtual bool isReachable(); + + /** + * @brief Serialize the contact for storage in the contact list. + * + * The provided serializedData contain the contact id in the field + * "contactId". If you don't like this, or don't want to + * store these fields at all, + * you are free to remove them from the list. + * + * Most plugins don't need more than these fields, so they only need + * to set the address book fields themselves. If you have nothing to + * save at all you can clear the QMap, an empty map is treated as + * 'nothing to save'. + * + * The provided addressBookFields QMap contains the index field as + * marked with @ref Plugin::addAddressBookField() with the + * contact id as value. If no index field is available the QMap is + * simply passed as an empty map. + * + * @sa Protocol::deserializeContact + */ + virtual void serialize( QMap &serializedData, QMap &addressBookData ); + + /** + * @brief Serialize the contacts persistent properties for storage in the contact list. + * + * Does the same as @ref serialize() does but for KopeteContactProperties + * set in this contact with their persistency flag turned on. + * In contrary to @ref serialize() this does not need to be reimplemented. + * + */ + void serializeProperties(QMap &serializedData); + + /** + * @brief Deserialize the contacts persistent properties + */ + void deserializeProperties(QMap &serializedData); + + /** + * @brief Get the online status of the contact + * @return the online status of the contact + */ + OnlineStatus onlineStatus() const; + + /** + * \brief Set the contact's online status + */ + void setOnlineStatus(const OnlineStatus &status); + + /** + * \brief Get the set of custom menu items for this contact + * + * Returns a set of custom menu items for the context menu + * which is displayed in showContextMenu (private). Protocols + * should use this to add protocol-specific actions to the + * popup menu. Kopete take care of the deletion of the action collection. + * Actions should have the collection as parent. + * + * @return Collection of menu items to be show on the context menu + * @todo if possible, try to use KXMLGUI + */ + virtual QPtrList *customContextMenuActions(); + + /** + * @todo What is this function for ? + */ + virtual QPtrList *customContextMenuActions( ChatSession *manager ); + + /** + * @brief Get the Context Menu for this contact + * + * This menu includes generic actions common to each protocol, and action defined in + * @ref customContextMenuActions() + */ + KPopupMenu *popupMenu( ChatSession *manager = 0L ); + + /** + * \brief Get whether or not this contact is capable of file transfers + * + * + * \see setFileCapable() + * \return true if the protocol for this contact is capable of file transfers + * \return false if the protocol for this contact is not capable of file transfers + * + * @todo have a capabilioties. or move to protocol capabilities + */ + bool isFileCapable() const; + + /** + * \brief Set the file transfer capability of this contact + * + * \param filecap The new file transfer capability setting + * @todo have a capabilioties. or move to protocol capabilities + */ + void setFileCapable( bool filecap ); + + /** + * \brief Get whether or not this contact can accept file transfers + * + * This function checks to make sure that the contact is online as well as + * capable of sending files. + * \see isReachable() + * @return true if this contact is online and is capable of receiving files + * @todo have a capabilioties. or move to protocol capabilities + */ + bool canAcceptFiles() const; + + enum CanCreateFlags { CannotCreate=false , CanCreate=true }; + + /** + * Returns the primary message manager affiliated with this contact + * Although a contact can have more than one active message manager + * (as is the case with MSN at least), only one message manager will + * ever be the contacts "primary" message manager.. aka the 1 on 1 chat. + * This function should always return that instance. + * + * @param canCreate If a new message manager can be created in addition + * to any existing managers. Currently, this is only set to true when + * a chat is initiated by the user by clicking the contact list. + */ + virtual ChatSession * manager( CanCreateFlags canCreate = CannotCreate ) =0; + + /** + * Returns the name of the icon to use for this contact + * If null, the protocol icon need to be used. + * The icon is not colored, nor has the status icon overloaded + */ + QString& icon() const; + + /** + * @brief Change the icon to use for this contact + * If you don't want to have the protocol icon as icon for this contact, you may set + * another icon. The icon doesn't need to be colored with the account icon as this operation + * will be performed later. + * + * if you want to go back to the protocol icon, set a null string. + */ + void setIcon( const QString& icon ); + + /** + * \brief Get the time (in seconds) this contact has been idle + * It will return the time set in @ref setIdleTime() with an addition of the time + * since you set this last time + * @return time this contact has been idle for, in seconds + // + // FIXME: Can we make this just 'unsigned long' ? QT Properties can't handle + // 'unsigned long int' + */ + virtual unsigned long int idleTime() const; + + /** + * \brief Set the current idle time in seconds. + * Kopete will automatically calculate the time in @ref idleTime + * except if you set 0. + // + // FIXME: Can we make this just 'unsigned long' ? QT Properties can't handle + // 'unsigned long int' + */ + void setIdleTime(unsigned long int); + + /** + * @return A QStringList containing all property keys + **/ + QStringList properties() const; + + /** + * Check for existance of a certain property stored + * using "key". + * \param key the property to check for + **/ + bool hasProperty(const QString &key) const; + + /** + * \brief Get the value of a property with key "key". + * + * If you don't know the type of the returned QVariant, you will need + * to check for it. + * \return the value of the property + **/ + const Kopete::ContactProperty &property(const QString &key) const; + const Kopete::ContactProperty &property(const Kopete::ContactPropertyTmpl &tmpl) const; + + /** + * \brief Add or Set a property for this contact. + * + * @param tmpl The template this property is based on, key, label etc. are + * taken from this one + * @param value The value to store + * + * \note Setting a NULL value or an empty QString castable value + * removes the property if it already existed. + * Don't abuse this for property-removal, instead use + * @ref removeProperty() if you want to remove on purpose. + * The Removal is done to clean up the list of properties and to purge them + * from UI. + **/ + void setProperty(const Kopete::ContactPropertyTmpl &tmpl, const QVariant &value); + + /** + * \brief Convenience method to set the nickName property to the specified value + * @param name The nickname to set + */ + void setNickName( const QString &name ); + + /** + * \brief Convenience method to retrieve the nickName property. + * + * This method will return the contactId if there has been no nickName property set + */ + QString nickName() const; + + /** + * \brief Remove a property if it exists + * + * @param tmpl the template this property is based on + **/ + void removeProperty(const Kopete::ContactPropertyTmpl &tmpl); + + /** + * \brief Get the tooltip for this contact + * Makes use of formattedName() and formattedIdleTime(). + * \return an RTF tooltip depending on KopetePrefs settings + **/ + QString toolTip() const; + + /** + * Returns a formatted string of "firstName" and/or "lastName" properties + * if present. + * Suitable for GUI display + **/ + QString formattedName() const; + + /** + * Returns a formatted string of idleTime(). + * Suitable for GUI display + **/ + QString formattedIdleTime() const; + + /** + * used in @ref sync() + */ + enum Changed{ MovedBetweenGroup = 0x01, ///< the contact has been moved between groups + DisplayNameChanged = 0x02 ///< the displayname of the contact changed + }; + + +public slots: + /** + * This should typically pop up a KopeteChatWindow + */ + void startChat(); + + /** + * Pops up an email type window + */ + void sendMessage(); + + /** + * The user clicked on the contact, do the default action + */ + void execute(); + + /** + * Changes the MetaContact that this contact is a part of. This function + * is called by the KAction changeMetaContact that is part of the context + * menu. + */ + void changeMetaContact(); + + /** + * Method to retrieve user information. Should be implemented by + * the protocols, and popup some sort of dialog box + * + * reimplement it to show the informlation + * @todo rename and make it pure virtual + */ + virtual void slotUserInfo() {}; + + /** + * @brief Syncronise the server and the metacontact. + * Protocols with server-side contact lists can implement this to + * sync the server groups with the metaContact groups. Or the server alias if any. + * + * This method is called every time the metacontact has been moved or renamed. + * + * default implementation does nothing + * + * @param changed is a bitmask of the @ref Changed enum which say why the call to this funtion is done. + */ + virtual void sync(unsigned int changed = 0xFF); + + /** + * Method to delete a contact from the contact list, + * should be implemented by protocol plugin to handle + * protocol-specific actions required to delete a contact + * (ie. messages to the server, etc) + * the default implementation simply call deleteLater() + */ + virtual void deleteContact(); + + /** + * This is the Contact level slot for sending files. It should be + * implemented by all contacts which have the setFileCapable() flag set to + * true. If the function is called through the GUI, no parameters are sent + * and they take on default values (the file is chosen with a file open dialog) + * + * @param sourceURL The actual KURL of the file you are sending + * @param fileName (Optional) An alternate name for the file - what the + * receiver will see + * @param fileSize (Optional) Size of the file being sent. Used when sending + * a nondeterminate + * file size (such as over asocket + */ + virtual void sendFile( const KURL &sourceURL = KURL(), + const QString &fileName = QString::null, uint fileSize = 0L ); + +private slots: + + /** + * This add the contact totally in the list if it was a temporary contact + */ + void slotAddContact(); + + /** + * slot called when the action "delete" is called. + */ + void slotDelete(); + + /** + * slot called when the action "block" is called. + */ + void slotBlock(); + + /** + * slot called when the action "unblock" is called. + */ + void slotUnblock(); + + /** + * The account's isConnected has changed. + */ + void slotAccountIsConnectedChanged(); + +signals: + /** + * The contact's online status changed + */ + void onlineStatusChanged( Kopete::Contact *contact, + const Kopete::OnlineStatus &status, const Kopete::OnlineStatus &oldStatus ); + + /** + * The contact is about to be destroyed. + * Called when entering the destructor. Useful for cleanup, since + * metaContact() is still accessible at this point. + * + * @warning this signal is emit in the Contact destructor, so all + * virtual method are not available + */ + void contactDestroyed( Kopete::Contact *contact ); + + /** + * The contact's idle state changed. + * You need to emit this signal to update the view. + * That mean when activity has been noticed + */ + void idleStateChanged( Kopete::Contact *contact ); + + /** + * One of the contact's properties has changed. + * @param contact this contact, useful for listening to signals from more than one contact + * @param key the key whose value has changed + * @param oldValue the value before the change, or an invalid QVariant if the property is new + * @param newValue the value after the change, or an invalid QVariant if the property was removed + */ + void propertyChanged( Kopete::Contact *contact, const QString &key, + const QVariant &oldValue, const QVariant &newValue ); + +protected: + virtual void virtual_hook(uint id, void *data); + +private: + class Private; + Private *d; + + +}; + + +} //END namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetecontactlist.cpp b/kopete/libkopete/kopetecontactlist.cpp new file mode 100644 index 00000000..9aab9f2f --- /dev/null +++ b/kopete/libkopete/kopetecontactlist.cpp @@ -0,0 +1,1112 @@ +/* + kopetecontactlist.cpp - Kopete's Contact List backend + + Copyright (c) 2005 by Michael Larouche + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2004 by Olivier Goffart + Copyright (c) 2002 by Duncan Mac-Vicar Prett + + Copyright (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetecontactlist.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "kopetemetacontact.h" +#include "kopetecontact.h" +#include "kopetechatsession.h" +//#include "kopetemessage.h" +#include "kopetepluginmanager.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetegroup.h" +#include "kopetepicture.h" + + +namespace Kopete +{ + +class ContactList::Private +{public: + /** Flag: do not save the contactlist until she is completely loaded */ + bool loaded ; + + QPtrList contacts; + QPtrList groups; + QPtrList selectedMetaContacts; + QPtrList selectedGroups; + + QTimer *saveTimer; + + MetaContact *myself; + + /** Flag: does the user uses the global identity */ + bool useGlobalIdentity; + + /** + * Current contact list version * 10 ( i.e. '10' is version '1.0' ) + */ + static const uint ContactListVersion = 10; +}; + +ContactList *ContactList::s_self = 0L; + +ContactList *ContactList::self() +{ + if( !s_self ) + s_self = new ContactList; + + return s_self; +} + +ContactList::ContactList() + : QObject( kapp, "KopeteContactList" ) +{ + d=new Private; + + //the myself metacontact can't be created now, because it will use + //ContactList::self() as parent which will call this constructor -> infinite loop + d->myself=0L; + + //no contactlist loaded yet, don't save them + d->loaded=false; + + // automatically save on changes to the list + d->saveTimer = new QTimer( this, "saveTimer" ); + connect( d->saveTimer, SIGNAL( timeout() ), SLOT ( save() ) ); + + connect( this, SIGNAL( metaContactAdded( Kopete::MetaContact * ) ), SLOT( slotSaveLater() ) ); + connect( this, SIGNAL( metaContactRemoved( Kopete::MetaContact * ) ), SLOT( slotSaveLater() ) ); + connect( this, SIGNAL( groupAdded( Kopete::Group * ) ), SLOT( slotSaveLater() ) ); + connect( this, SIGNAL( groupRemoved( Kopete::Group * ) ), SLOT( slotSaveLater() ) ); + connect( this, SIGNAL( groupRenamed( Kopete::Group *, const QString & ) ), SLOT( slotSaveLater() ) ); +} + +ContactList::~ContactList() +{ + delete d->myself; + delete d; +} + +QPtrList ContactList::metaContacts() const +{ + return d->contacts; +} + + +QPtrList ContactList::groups() const +{ + return d->groups; +} + + +MetaContact *ContactList::metaContact( const QString &metaContactId ) const +{ + QPtrListIterator it( d->contacts ); + + for( ; it.current(); ++it ) + { + if( it.current()->metaContactId() == metaContactId ) + return it.current(); + } + + return 0L; +} + + +Group * ContactList::group(unsigned int groupId) const +{ + Group *groupIterator; + for ( groupIterator = d->groups.first(); groupIterator; groupIterator = d->groups.next() ) + { + if( groupIterator->groupId()==groupId ) + return groupIterator; + } + return 0L; +} + + +Contact *ContactList::findContact( const QString &protocolId, + const QString &accountId, const QString &contactId ) const +{ + //Browsing metacontacts is too slow, better to uses the Dict of the account. + Account *i=AccountManager::self()->findAccount(protocolId,accountId); + if(!i) + { + kdDebug( 14010 ) << k_funcinfo << "Account not found" << endl; + return 0L; + } + return i->contacts()[contactId]; +} + + +MetaContact *ContactList::findMetaContactByDisplayName( const QString &displayName ) const +{ + QPtrListIterator it( d->contacts ); + for( ; it.current(); ++it ) + { +// kdDebug(14010) << "Display Name: " << it.current()->displayName() << "\n"; + if( it.current()->displayName() == displayName ) { + return it.current(); + } + } + + return 0L; +} + +MetaContact* ContactList::findMetaContactByContactId( const QString &contactId ) const +{ + QPtrList acts=AccountManager::self()->accounts(); + QPtrListIterator it( acts ); + for ( ; it.current(); ++it ) + { + Contact *c=(*it)->contacts()[contactId]; + if(c && c->metaContact()) + return c->metaContact(); + } + return 0L; +} + +Group * ContactList::findGroup(const QString& displayName, int type) +{ + if( type == Group::Temporary ) + return Group::temporary(); + + Group *groupIterator; + for ( groupIterator = d->groups.first(); groupIterator; groupIterator = d->groups.next() ) + { + if( groupIterator->type() == type && groupIterator->displayName() == displayName ) + return groupIterator; + } + + Group *newGroup = new Group( displayName, (Group::GroupType)type ); + addGroup( newGroup ); + return newGroup; +} + + +QPtrList ContactList::selectedMetaContacts() const +{ + return d->selectedMetaContacts; +} + +QPtrList ContactList::selectedGroups() const +{ + return d->selectedGroups; +} + + +void ContactList::addMetaContact( MetaContact *mc ) +{ + if ( d->contacts.contains( mc ) ) + return; + + d->contacts.append( mc ); + + emit metaContactAdded( mc ); + connect( mc, SIGNAL( persistentDataChanged( ) ), SLOT( slotSaveLater() ) ); + connect( mc, SIGNAL( addedToGroup( Kopete::MetaContact *, Kopete::Group * ) ), SIGNAL( metaContactAddedToGroup( Kopete::MetaContact *, Kopete::Group * ) ) ); + connect( mc, SIGNAL( removedFromGroup( Kopete::MetaContact *, Kopete::Group * ) ), SIGNAL( metaContactRemovedFromGroup( Kopete::MetaContact *, Kopete::Group * ) ) ); +} + + +void ContactList::removeMetaContact(MetaContact *m) +{ + if ( !d->contacts.contains(m) ) + { + kdDebug(14010) << k_funcinfo << "Trying to remove a not listed MetaContact." << endl; + return; + } + + if ( d->selectedMetaContacts.contains( m ) ) + { + d->selectedMetaContacts.remove( m ); + setSelectedItems( d->selectedMetaContacts, d->selectedGroups ); + } + + //removes subcontact from server here and now. + QPtrList cts=m->contacts(); + for( Contact *c = cts.first(); c; c = cts.next() ) + { + c->deleteContact(); + } + + d->contacts.remove( m ); + emit metaContactRemoved( m ); + m->deleteLater(); +} + + +void ContactList::addGroup( Group * g ) +{ + if(!d->groups.contains(g) ) + { + d->groups.append( g ); + emit groupAdded( g ); + connect( g , SIGNAL ( displayNameChanged(Kopete::Group* , const QString & )) , this , SIGNAL ( groupRenamed(Kopete::Group* , const QString & )) ) ; + } +} + +void ContactList::removeGroup( Group *g ) +{ + if ( d->selectedGroups.contains( g ) ) + { + d->selectedGroups.remove( g ); + setSelectedItems( d->selectedMetaContacts, d->selectedGroups ); + } + + d->groups.remove( g ); + emit groupRemoved( g ); + g->deleteLater(); +} + + +void ContactList::setSelectedItems(QPtrList metaContacts , QPtrList groups) +{ + kdDebug( 14010 ) << k_funcinfo << metaContacts.count() << " metacontacts, " << groups.count() << " groups selected" << endl; + d->selectedMetaContacts=metaContacts; + d->selectedGroups=groups; + + emit metaContactSelected( groups.isEmpty() && metaContacts.count()==1 ); + emit selectionChanged(); +} + +MetaContact* ContactList::myself() +{ + if(!d->myself) + d->myself=new MetaContact(); + return d->myself; +} + +void ContactList::loadGlobalIdentity() +{ + // Apply the global identity + if(Kopete::Config::enableGlobalIdentity()) + { + // Disconnect to make sure it will not cause duplicate calls. + disconnect(myself(), SIGNAL(displayNameChanged(const QString&, const QString&)), this, SLOT(slotDisplayNameChanged())); + disconnect(myself(), SIGNAL(photoChanged()), this, SLOT(slotPhotoChanged())); + + connect(myself(), SIGNAL(displayNameChanged(const QString&, const QString&)), this, SLOT(slotDisplayNameChanged())); + connect(myself(), SIGNAL(photoChanged()), this, SLOT(slotPhotoChanged())); + + // Ensure that the myself metaContactId is always the KABC whoAmI + KABC::Addressee a = KABC::StdAddressBook::self()->whoAmI(); + if(!a.isEmpty() && a.uid() != myself()->metaContactId()) + { + myself()->setMetaContactId(a.uid()); + } + + // Apply the global identity + // Maybe one of the myself contact from a account has a different displayName/photo at startup. + slotDisplayNameChanged(); + slotPhotoChanged(); + } + else + { + disconnect(myself(), SIGNAL(displayNameChanged(const QString&, const QString&)), this, SLOT(slotDisplayNameChanged())); + disconnect(myself(), SIGNAL(photoChanged()), this, SLOT(slotPhotoChanged())); + } +} + +void ContactList::slotDisplayNameChanged() +{ + static bool mutex=false; + if(mutex) + { + kdDebug (14010) << k_funcinfo << " mutex blocked" << endl ; + return; + } + mutex=true; + + kdDebug( 14010 ) << k_funcinfo << myself()->displayName() << endl; + + emit globalIdentityChanged(Kopete::Global::Properties::self()->nickName().key(), myself()->displayName()); + mutex=false; +} + +void ContactList::slotPhotoChanged() +{ + static bool mutex=false; + if(mutex) + { + kdDebug (14010) << k_funcinfo << " mutex blocked" << endl ; + return; + } + mutex=true; + kdDebug( 14010 ) << k_funcinfo << myself()->picture().path() << endl; + + emit globalIdentityChanged(Kopete::Global::Properties::self()->photo().key(), myself()->picture().path()); + mutex=false; + /* The mutex is usefull to don't have such as stack overflow + Kopete::ContactList::slotPhotoChanged -> Kopete::ContactList::globalIdentityChanged + MSNAccount::slotGlobalIdentityChanged -> Kopete::Contact::propertyChanged + Kopete::MetaContact::slotPropertyChanged -> Kopete::MetaContact::photoChanged -> Kopete::ContactList::slotPhotoChanged + */ +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +void ContactList::load() +{ + loadXML(); + // Apply the global identity when all the protocols plugins are loaded. + connect(PluginManager::self(), SIGNAL(allPluginsLoaded()), this, SLOT(loadGlobalIdentity())); +} + +void ContactList::loadXML() +{ + // don't save when we're in the middle of this... + d->loaded = false; + + QString filename = locateLocal( "appdata", QString::fromLatin1( "contactlist.xml" ) ); + if( filename.isEmpty() ) + { + d->loaded=true; + return ; + } + + QDomDocument contactList( QString::fromLatin1( "kopete-contact-list" ) ); + + QFile contactListFile( filename ); + contactListFile.open( IO_ReadOnly ); + contactList.setContent( &contactListFile ); + + QDomElement list = contactList.documentElement(); + + QString versionString = list.attribute( QString::fromLatin1( "version" ), QString::null ); + uint version = 0; + if( QRegExp( QString::fromLatin1( "[0-9]+\\.[0-9]" ) ).exactMatch( versionString ) ) + version = versionString.replace( QString::fromLatin1( "." ), QString::null ).toUInt(); + + if( version < Private::ContactListVersion ) + { + // The version string is invalid, or we're using an older version. + // Convert first and reparse the file afterwards + kdDebug( 14010 ) << k_funcinfo << "Contact list version " << version + << " is older than current version " << Private::ContactListVersion + << ". Converting first." << endl; + + contactListFile.close(); + + convertContactList( filename, version, Private::ContactListVersion ); + + contactList = QDomDocument ( QString::fromLatin1( "kopete-contact-list" ) ); + + contactListFile.open( IO_ReadOnly ); + contactList.setContent( &contactListFile ); + + list = contactList.documentElement(); + } + + addGroup( Kopete::Group::topLevel() ); + + QDomElement element = list.firstChild().toElement(); + while( !element.isNull() ) + { + if( element.tagName() == QString::fromLatin1("meta-contact") ) + { + //TODO: id isn't used + //QString id = element.attribute( "id", QString::null ); + Kopete::MetaContact *metaContact = new Kopete::MetaContact(); + if ( !metaContact->fromXML( element ) ) + { + delete metaContact; + metaContact = 0; + } + else + { + Kopete::ContactList::self()->addMetaContact( + metaContact ); + } + } + else if( element.tagName() == QString::fromLatin1("kopete-group") ) + { + Kopete::Group *group = new Kopete::Group(); + if( !group->fromXML( element ) ) + { + delete group; + group = 0; + } + else + { + Kopete::ContactList::self()->addGroup( group ); + } + } + // Only load myself metacontact information when Global Identity is enabled. + else if( element.tagName() == QString::fromLatin1("myself-meta-contact") && Kopete::Config::enableGlobalIdentity() ) + { + if( !myself()->fromXML( element ) ) + { + delete d->myself; + d->myself = 0; + } + } + else + { + kdWarning(14010) << "Kopete::ContactList::loadXML: " + << "Unknown element '" << element.tagName() + << "' in contact list!" << endl; + } + element = element.nextSibling().toElement(); + } + contactListFile.close(); + d->loaded=true; +} + +void ContactList::convertContactList( const QString &fileName, uint /* fromVersion */, uint /* toVersion */ ) +{ + // For now, ignore fromVersion and toVersion. These are meant for future + // changes to allow incremental (multi-pass) conversion so we don't have + // to rewrite the whole conversion code for each change. + + QDomDocument contactList( QString::fromLatin1( "messaging-contact-list" ) ); + QFile contactListFile( fileName ); + contactListFile.open( IO_ReadOnly ); + contactList.setContent( &contactListFile ); + + QDomElement oldList = contactList.documentElement(); + + QDomDocument newList( QString::fromLatin1( "kopete-contact-list" ) ); + newList.appendChild( newList.createProcessingInstruction( QString::fromLatin1( "xml" ), QString::fromLatin1( "version=\"1.0\"" ) ) ); + + QDomElement newRoot = newList.createElement( QString::fromLatin1( "kopete-contact-list" ) ); + newList.appendChild( newRoot ); + newRoot.setAttribute( QString::fromLatin1( "version" ), QString::fromLatin1( "1.0" ) ); + + QDomNode oldNode = oldList.firstChild(); + while( !oldNode.isNull() ) + { + QDomElement oldElement = oldNode.toElement(); + if( !oldElement.isNull() ) + { + if( oldElement.tagName() == QString::fromLatin1("meta-contact") ) + { + // Ignore ID, it is not used in the current list anyway + QDomElement newMetaContact = newList.createElement( QString::fromLatin1( "meta-contact" ) ); + newRoot.appendChild( newMetaContact ); + + // Plugin data is stored completely different, and requires + // some bookkeeping to convert properly + QMap pluginData; + QStringList icqData; + QStringList gaduData; + + // ICQ and Gadu can only be converted properly if the address book fields + // are already parsed. Therefore, scan for those first and add the rest + // afterwards + QDomNode oldContactNode = oldNode.firstChild(); + while( !oldContactNode.isNull() ) + { + QDomElement oldContactElement = oldContactNode.toElement(); + if( !oldContactElement.isNull() && oldContactElement.tagName() == QString::fromLatin1("address-book-field") ) + { + // Convert address book fields. + // Jabber will be called "xmpp", Aim/Toc and Aim/Oscar both will + // be called "aim". MSN, AIM, IRC, Oscar and SMS don't use address + // book fields yet; Gadu and ICQ can be converted as-is. + // As Yahoo is unfinished we won't try to convert at all. + QString id = oldContactElement.attribute( QString::fromLatin1( "id" ), QString::null ); + QString data = oldContactElement.text(); + + QString app, key, val; + QString separator = QString::fromLatin1( "," ); + if( id == QString::fromLatin1( "messaging/gadu" ) ) + separator = QString::fromLatin1( "\n" ); + else if( id == QString::fromLatin1( "messaging/icq" ) ) + separator = QString::fromLatin1( ";" ); + else if( id == QString::fromLatin1( "messaging/jabber" ) ) + id = QString::fromLatin1( "messaging/xmpp" ); + + if( id == QString::fromLatin1( "messaging/gadu" ) || id == QString::fromLatin1( "messaging/icq" ) || + id == QString::fromLatin1( "messaging/winpopup" ) || id == QString::fromLatin1( "messaging/xmpp" ) ) + { + app = id; + key = QString::fromLatin1( "All" ); + val = data.replace( separator, QChar( 0xE000 ) ); + } + + if( !app.isEmpty() ) + { + QDomElement addressBookField = newList.createElement( QString::fromLatin1( "address-book-field" ) ); + newMetaContact.appendChild( addressBookField ); + + addressBookField.setAttribute( QString::fromLatin1( "app" ), app ); + addressBookField.setAttribute( QString::fromLatin1( "key" ), key ); + + addressBookField.appendChild( newList.createTextNode( val ) ); + + // ICQ didn't store the contactId locally, only in the address + // book fields, so we need to be able to access it later + if( id == QString::fromLatin1( "messaging/icq" ) ) + icqData = QStringList::split( QChar( 0xE000 ), val ); + else if( id == QString::fromLatin1("messaging/gadu") ) + gaduData = QStringList::split( QChar( 0xE000 ), val ); + } + } + oldContactNode = oldContactNode.nextSibling(); + } + + // Now, convert the other elements + oldContactNode = oldNode.firstChild(); + while( !oldContactNode.isNull() ) + { + QDomElement oldContactElement = oldContactNode.toElement(); + if( !oldContactElement.isNull() ) + { + if( oldContactElement.tagName() == QString::fromLatin1("display-name") ) + { + QDomElement displayName = newList.createElement( QString::fromLatin1( "display-name" ) ); + displayName.appendChild( newList.createTextNode( oldContactElement.text() ) ); + newMetaContact.appendChild( displayName ); + } + else if( oldContactElement.tagName() == QString::fromLatin1("groups") ) + { + QDomElement groups = newList.createElement( QString::fromLatin1( "groups" ) ); + newMetaContact.appendChild( groups ); + + QDomNode oldGroup = oldContactElement.firstChild(); + while( !oldGroup.isNull() ) + { + QDomElement oldGroupElement = oldGroup.toElement(); + if ( oldGroupElement.tagName() == QString::fromLatin1("group") ) + { + QDomElement group = newList.createElement( QString::fromLatin1( "group" ) ); + group.appendChild( newList.createTextNode( oldGroupElement.text() ) ); + groups.appendChild( group ); + } + else if ( oldGroupElement.tagName() == QString::fromLatin1("top-level") ) + { + QDomElement group = newList.createElement( QString::fromLatin1( "top-level" ) ); + groups.appendChild( group ); + } + + oldGroup = oldGroup.nextSibling(); + } + } + else if( oldContactElement.tagName() == QString::fromLatin1( "plugin-data" ) ) + { + // Convert the plugin data + QString id = oldContactElement.attribute( QString::fromLatin1( "plugin-id" ), QString::null ); + QString data = oldContactElement.text(); + + bool convertOldAim = false; + uint fieldCount = 1; + QString addressBookLabel; + if( id == QString::fromLatin1("MSNProtocol") ) + { + fieldCount = 3; + addressBookLabel = QString::fromLatin1("msn"); + } + else if( id == QString::fromLatin1("IRCProtocol") ) + { + fieldCount = 3; + addressBookLabel = QString::fromLatin1("irc"); + } + else if( id == QString::fromLatin1("OscarProtocol") ) + { + fieldCount = 2; + addressBookLabel = QString::fromLatin1("aim"); + } + else if( id == QString::fromLatin1("AIMProtocol") ) + { + id = QString::fromLatin1("OscarProtocol"); + convertOldAim = true; + addressBookLabel = QString::fromLatin1("aim"); + } + else if( id == QString::fromLatin1("ICQProtocol") || id == QString::fromLatin1("WPProtocol") || id == QString::fromLatin1("GaduProtocol") ) + { + fieldCount = 1; + } + else if( id == QString::fromLatin1("JabberProtocol") ) + { + fieldCount = 4; + } + else if( id == QString::fromLatin1("SMSProtocol") ) + { + // SMS used a variable serializing using a dot as delimiter. + // The minimal count is three though (id, name, delimiter). + fieldCount = 2; + addressBookLabel = QString::fromLatin1("sms"); + } + + if( pluginData[ id ].isNull() ) + { + pluginData[ id ] = newList.createElement( QString::fromLatin1( "plugin-data" ) ); + pluginData[ id ].setAttribute( QString::fromLatin1( "plugin-id" ), id ); + newMetaContact.appendChild( pluginData[ id ] ); + } + + // Do the actual conversion + if( id == QString::fromLatin1( "MSNProtocol" ) || id == QString::fromLatin1( "OscarProtocol" ) || + id == QString::fromLatin1( "AIMProtocol" ) || id == QString::fromLatin1( "IRCProtocol" ) || + id == QString::fromLatin1( "ICQProtocol" ) || id == QString::fromLatin1( "JabberProtocol" ) || + id == QString::fromLatin1( "SMSProtocol" ) || id == QString::fromLatin1( "WPProtocol" ) || + id == QString::fromLatin1( "GaduProtocol" ) ) + { + QStringList strList = QStringList::split( QString::fromLatin1( "||" ), data ); + + // Unescape '||' + for( QStringList::iterator it = strList.begin(); it != strList.end(); ++it ) + { + ( *it ).replace( QString::fromLatin1( "\\|;" ), QString::fromLatin1( "|" ) ). + replace( QString::fromLatin1( "\\\\" ), QString::fromLatin1( "\\" ) ); + } + + uint idx = 0; + while( idx < strList.size() ) + { + QDomElement dataField; + + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "contactId" ) ); + if( id == QString::fromLatin1("ICQProtocol") ) + dataField.appendChild( newList.createTextNode( icqData[ idx ] ) ); + else if( id == QString::fromLatin1("GaduProtocol") ) + dataField.appendChild( newList.createTextNode( gaduData[ idx ] ) ); + else if( id == QString::fromLatin1("JabberProtocol") ) + dataField.appendChild( newList.createTextNode( strList[ idx + 1 ] ) ); + else + dataField.appendChild( newList.createTextNode( strList[ idx ] ) ); + + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "displayName" ) ); + if( convertOldAim || id == QString::fromLatin1("ICQProtocol") || id == QString::fromLatin1("WPProtocol") || id == QString::fromLatin1("GaduProtocol") ) + dataField.appendChild( newList.createTextNode( strList[ idx ] ) ); + else if( id == QString::fromLatin1("JabberProtocol") ) + dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) ); + else + dataField.appendChild( newList.createTextNode( strList[ idx + 1 ] ) ); + + if( id == QString::fromLatin1("MSNProtocol") ) + { + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "groups" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) ); + } + else if( id == QString::fromLatin1("IRCProtocol") ) + { + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "serverName" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) ); + } + else if( id == QString::fromLatin1("JabberProtocol") ) + { + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "accountId" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx ] ) ); + + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "groups" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 3 ] ) ); + } + else if( id == QString::fromLatin1( "SMSProtocol" ) && + ( idx + 2 < strList.size() ) && strList[ idx + 2 ] != QString::fromLatin1( "." ) ) + { + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "serviceName" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) ); + + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "servicePrefs" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 3 ] ) ); + + // Add extra fields + idx += 2; + } + + // MSN, AIM, IRC, Oscar and SMS didn't store address book fields up + // to now, so create one + if( id != QString::fromLatin1("ICQProtocol") && id != QString::fromLatin1("JabberProtocol") && id != QString::fromLatin1("WPProtocol") && id != QString::fromLatin1("GaduProtocol") ) + { + QDomElement addressBookField = newList.createElement( QString::fromLatin1( "address-book-field" ) ); + newMetaContact.appendChild( addressBookField ); + + addressBookField.setAttribute( QString::fromLatin1( "app" ), + QString::fromLatin1( "messaging/" ) + addressBookLabel ); + addressBookField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "All" ) ); + addressBookField.appendChild( newList.createTextNode( strList[ idx ] ) ); + } + + idx += fieldCount; + } + } + else if( id == QString::fromLatin1("ContactNotesPlugin") || id == QString::fromLatin1("CryptographyPlugin") || id == QString::fromLatin1("TranslatorPlugin") ) + { + QDomElement dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + if( id == QString::fromLatin1("ContactNotesPlugin") ) + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "notes" ) ); + else if( id == QString::fromLatin1("CryptographyPlugin") ) + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "gpgKey" ) ); + else if( id == QString::fromLatin1("TranslatorPlugin") ) + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "languageKey" ) ); + + dataField.appendChild( newList.createTextNode( data ) ); + } + } + } + oldContactNode = oldContactNode.nextSibling(); + } + } + else if( oldElement.tagName() == QString::fromLatin1("kopete-group") ) + { + QDomElement newGroup = newList.createElement( QString::fromLatin1( "kopete-group" ) ); + newRoot.appendChild( newGroup ); + + QDomNode oldGroupNode = oldNode.firstChild(); + while( !oldGroupNode.isNull() ) + { + QDomElement oldGroupElement = oldGroupNode.toElement(); + + if( oldGroupElement.tagName() == QString::fromLatin1("display-name") ) + { + QDomElement displayName = newList.createElement( QString::fromLatin1( "display-name" ) ); + displayName.appendChild( newList.createTextNode( oldGroupElement.text() ) ); + newGroup.appendChild( displayName ); + } + if( oldGroupElement.tagName() == QString::fromLatin1("type") ) + { + if( oldGroupElement.text() == QString::fromLatin1("Temporary") ) + newGroup.setAttribute( QString::fromLatin1( "type" ), QString::fromLatin1( "temporary" ) ); + else if( oldGroupElement.text() == QString::fromLatin1( "TopLevel" ) ) + newGroup.setAttribute( QString::fromLatin1( "type" ), QString::fromLatin1( "top-level" ) ); + else + newGroup.setAttribute( QString::fromLatin1( "type" ), QString::fromLatin1( "standard" ) ); + } + if( oldGroupElement.tagName() == QString::fromLatin1("view") ) + { + if( oldGroupElement.text() == QString::fromLatin1("collapsed") ) + newGroup.setAttribute( QString::fromLatin1( "view" ), QString::fromLatin1( "collapsed" ) ); + else + newGroup.setAttribute( QString::fromLatin1( "view" ), QString::fromLatin1( "expanded" ) ); + } + else if( oldGroupElement.tagName() == QString::fromLatin1("plugin-data") ) + { + // Per-group plugin data + // FIXME: This needs updating too, ideally, convert this in a later + // contactlist.xml version + QDomElement groupPluginData = newList.createElement( QString::fromLatin1( "plugin-data" ) ); + newGroup.appendChild( groupPluginData ); + + groupPluginData.setAttribute( QString::fromLatin1( "plugin-id" ), + oldGroupElement.attribute( QString::fromLatin1( "plugin-id" ), QString::null ) ); + groupPluginData.appendChild( newList.createTextNode( oldGroupElement.text() ) ); + } + + oldGroupNode = oldGroupNode.nextSibling(); + } + } + else + { + kdWarning( 14010 ) << k_funcinfo << "Unknown element '" << oldElement.tagName() + << "' in contact list!" << endl; + } + } + oldNode = oldNode.nextSibling(); + } + + // Close the file, and save the new file + contactListFile.close(); + + QDir().rename( fileName, fileName + QString::fromLatin1( ".bak" ) ); + + // kdDebug( 14010 ) << k_funcinfo << "XML output:\n" << newList.toString( 2 ) << endl; + + contactListFile.open( IO_WriteOnly ); + QTextStream stream( &contactListFile ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + stream << newList.toString( 2 ); + + contactListFile.flush(); + contactListFile.close(); +} + +void Kopete::ContactList::save() +{ + saveXML(); +} + +void Kopete::ContactList::saveXML() +{ + if(!d->loaded) + { + kdDebug(14010) << "Kopete::ContactList::saveXML: contactlist not loaded, abort saving" << endl; + return; + } + + QString contactListFileName = locateLocal( "appdata", QString::fromLatin1( "contactlist.xml" ) ); + KSaveFile contactListFile( contactListFileName ); + if( contactListFile.status() == 0 ) + { + QTextStream *stream = contactListFile.textStream(); + stream->setEncoding( QTextStream::UnicodeUTF8 ); + toXML().save( *stream, 4 ); + + if ( contactListFile.close() ) + { + // cancel any scheduled saves + d->saveTimer->stop(); + return; + } + else + { + kdDebug(14010) << "Kopete::ContactList::saveXML: failed to write contactlist, error code is: " << contactListFile.status() << endl; + } + } + else + { + kdWarning(14010) << "Kopete::ContactList::saveXML: Couldn't open contact list file " + << contactListFileName << ". Contact list not saved." << endl; + } + + // if we got here, saving the contact list failed. retry every minute until it works. + d->saveTimer->start( 60000, true /* single-shot: will get restarted by us next time if it's still failing */ ); +} + +const QDomDocument ContactList::toXML() +{ + QDomDocument doc; + doc.appendChild( doc.createElement( QString::fromLatin1("kopete-contact-list") ) ); + doc.documentElement().setAttribute( QString::fromLatin1("version"), QString::fromLatin1("1.0")); + + // Save group information. ie: Open/Closed, pehaps later icons? Who knows. + for( Kopete::Group *g = d->groups.first(); g; g = d->groups.next() ) + doc.documentElement().appendChild( doc.importNode( g->toXML(), true ) ); + + // Save metacontact information. + for( Kopete::MetaContact *m = d->contacts.first(); m; m = d->contacts.next() ) + if( !m->isTemporary() ) + doc.documentElement().appendChild( doc.importNode( m->toXML(), true ) ); + + // Save myself metacontact information + if( Kopete::Config::enableGlobalIdentity() ) + { + QDomElement myselfElement = myself()->toXML(true); // Save minimal information. + myselfElement.setTagName( QString::fromLatin1("myself-meta-contact") ); + doc.documentElement().appendChild( doc.importNode( myselfElement, true ) ); + } + + return doc; +} + +QStringList ContactList::contacts() const +{ + QStringList contacts; + QPtrListIterator it( d->contacts ); + for( ; it.current(); ++it ) + { + contacts.append( it.current()->displayName() ); + } + return contacts; +} + +QStringList ContactList::contactStatuses() const +{ + QStringList meta_contacts; + QPtrListIterator it( d->contacts ); + for( ; it.current(); ++it ) + { + meta_contacts.append( QString::fromLatin1( "%1 (%2)" ). + arg( it.current()->displayName(), it.current()->statusString() )); + } + return meta_contacts; +} + +QStringList ContactList::reachableContacts() const +{ + QStringList contacts; + QPtrListIterator it( d->contacts ); + for( ; it.current(); ++it ) + { + if ( it.current()->isReachable() ) + contacts.append( it.current()->displayName() ); + } + return contacts; +} + +QPtrList ContactList::onlineContacts() const +{ + QPtrList result; + QPtrListIterator it( d->contacts ); + for( ; it.current(); ++it ) + { + if ( it.current()->isOnline() ) + { + QPtrList contacts = it.current()->contacts(); + QPtrListIterator cit( contacts ); + for( ; cit.current(); ++cit ) + { + if ( cit.current()->isOnline() ) + result.append( cit.current() ); + } + } + } + return result; +} + +QPtrList Kopete::ContactList::onlineMetaContacts() const +{ + QPtrList result; + QPtrListIterator it( d->contacts ); + for( ; it.current(); ++it ) + { + if ( it.current()->isOnline() ) + result.append( it.current() ); + } + return result; +} + +QPtrList Kopete::ContactList::onlineMetaContacts( const QString &protocolId ) const +{ + QPtrList result; + QPtrListIterator it( d->contacts ); + for( ; it.current(); ++it ) + { + // FIXME: This loop is not very efficient :( + if ( it.current()->isOnline() ) + { + QPtrList contacts = it.current()->contacts(); + QPtrListIterator cit( contacts ); + for( ; cit.current(); ++cit ) + { + if( cit.current()->isOnline() && cit.current()->protocol()->pluginId() == protocolId ) + result.append( it.current() ); + } + } + } + return result; +} + +QPtrList Kopete::ContactList::onlineContacts( const QString &protocolId ) const +{ + QPtrList result; + QPtrListIterator it( d->contacts ); + for( ; it.current(); ++it ) + { + // FIXME: This loop is not very efficient :( + if ( it.current()->isOnline() ) + { + QPtrList contacts = it.current()->contacts(); + QPtrListIterator cit( contacts ); + for( ; cit.current(); ++cit ) + { + if( cit.current()->isOnline() && cit.current()->protocol()->pluginId() == protocolId ) + result.append( cit.current() ); + } + } + } + return result; +} + +QStringList Kopete::ContactList::fileTransferContacts() const +{ + QStringList contacts; + QPtrListIterator it( d->contacts ); + for( ; it.current(); ++it ) + { + if ( it.current()->canAcceptFiles() ) + contacts.append( it.current()->displayName() ); + } + return contacts; +} + +void Kopete::ContactList::sendFile( const QString &displayName, const KURL &sourceURL, + const QString &altFileName, const long unsigned int fileSize) +{ +// kdDebug(14010) << "Send To Display Name: " << displayName << "\n"; + + Kopete::MetaContact *c = findMetaContactByDisplayName( displayName ); + if( c ) + c->sendFile( sourceURL, altFileName, fileSize ); +} + +void Kopete::ContactList::messageContact( const QString &contactId, const QString &messageText ) +{ + Kopete::MetaContact *mc = findMetaContactByContactId( contactId ); + if (!mc) return; + + Kopete::Contact *c = mc->execute(); //We need to know which contact was chosen as the preferred in order to message it + if (!c) return; + + Kopete::Message msg(c->account()->myself(), c, messageText, Kopete::Message::Outbound); + c->manager(Contact::CanCreate)->sendMessage(msg); + +} + + +QStringList Kopete::ContactList::contactFileProtocols(const QString &displayName) +{ +// kdDebug(14010) << "Get contacts for: " << displayName << "\n"; + QStringList protocols; + + Kopete::MetaContact *c = findMetaContactByDisplayName( displayName ); + if( c ) + { + QPtrList mContacts = c->contacts(); + kdDebug(14010) << mContacts.count() << endl; + QPtrListIterator jt( mContacts ); + for ( ; jt.current(); ++jt ) + { + kdDebug(14010) << "1" << jt.current()->protocol()->pluginId() << endl; + if( jt.current()->canAcceptFiles() ) { + kdDebug(14010) << jt.current()->protocol()->pluginId() << endl; + protocols.append ( jt.current()->protocol()->pluginId() ); + } + } + return protocols; + } + return QStringList(); +} + + +void ContactList::slotSaveLater() +{ + // if we already have a save scheduled, it will be cancelled. either way, + // start a timer to save the contact list a bit later. + d->saveTimer->start( 17100 /* 17,1 seconds */, true /* single-shot */ ); +} + +void ContactList::slotKABCChanged() +{ + // TODO: react to changes in KABC, replacing this function, post 3.4 (Will) + // call syncWithKABC on each metacontact to check if its associated kabc entry has changed. +/* for ( MetaContact * mc = d->contacts.first(); mc; mc = d->contacts.next() ) + + mc->syncWithKABC();*/ +} + + +} //END namespace Kopete + +#include "kopetecontactlist.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetecontactlist.h b/kopete/libkopete/kopetecontactlist.h new file mode 100644 index 00000000..fc6dd5f9 --- /dev/null +++ b/kopete/libkopete/kopetecontactlist.h @@ -0,0 +1,405 @@ +/* + kopetecontactlist.h - Kopete's Contact List backend + + Copyright (c) 2002 by Martijn Klingens + Copyright (c) 2002-2004 by Olivier Goffart + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETECONTACTLIST_H__ +#define KOPETECONTACTLIST_H__ + +#include +#include + +#include "kopete_export.h" + +class KURL; +class QDomDocument; + + +namespace Kopete +{ + +class MetaContact; +class Group; +class Contact; + + +/** + * @brief manage contacts and metacontact + * + * The contactList is a singleton you can uses with @ref ContactList::self() + * + * it let you get a list of metacontact with metaContacts() + * Only metacontact which are on the contactlist are returned. + * + * @author Martijn Klingens + * @author Olivier Goffart + */ +class KOPETE_EXPORT ContactList : public QObject +{ + Q_OBJECT + +public: + /** + * The contact list is a singleton object. Use this method to retrieve + * the instance. + */ + static ContactList *self(); + ~ContactList(); + + /** + * @brief return a list of all metacontact of the contactlist + * Retrieve the list of all available meta contacts. + * The returned QPtrList is not the internally used variable, so changes + * to it won't propagate into the actual contact list. This can be + * useful if you need a subset of the contact list, because you can + * simply filter the result set as you wish without worrying about + * side effects. + * The contained MetaContacts are obviously _not_ duplicates, so + * changing those *will* have the expected result :-) + */ + QPtrList metaContacts() const; + + /** + * @return all groups + */ + QPtrList groups() const; + + /** + * Return the metacontact referenced by the given id. is none is found, return 0L + * @sa MetaContact::metaContactId() + */ + MetaContact *metaContact( const QString &metaContactId ) const; + + /** + * return the group with the given unique id. if none is found return 0L + */ + Group * group(unsigned int groupId) const; + + + /** + * @brief find a contact in the contactlist. + * Browse in each metacontact of the list to find the contact with the given ID. + * @param protocolId the @ref Plugin::pluginId() of the protocol ("MSNProtocol") + * @param accountId the @ref Account::accountId() + * @param contactId the @ref Contact::contactId() + * @return the contact with the parameters, or 0L if not found. + */ + Contact *findContact( const QString &protocolId, const QString &accountId, const QString &contactId ) const; + + /** + * Find a contact by display name. Returns the first match. + */ + MetaContact *findMetaContactByDisplayName( const QString &displayName ) const; + + /** + * Find a meta contact by its contact id. Returns the first match. + */ + MetaContact *findMetaContactByContactId( const QString &contactId ) const; + + /** + * @brief find a group with his displayName + * If a group already exists with the given name and the given type, the existing group will be returned. + * Otherwise, a new group will be created. + * @param displayName is the display name to search + * @param type is the Group::GroupType to search, the default value is group::Normal + * @return always a valid Group + */ + Group * findGroup( const QString &displayName, int type = 0/*Group::Normal*/ ); + + /** + * return the list of metacontact actually selected in the contactlist UI + */ + QPtrList selectedMetaContacts() const; + + /** + * return the list of groups actualy selected in the contactlist UI + */ + QPtrList selectedGroups() const ; + + /** + * return the metacontact that represent the user itself. + * This metacontact should be the parent of every Kopete::Account::myself() contacts. + * + * This metacontact is not in the contactlist. + */ + MetaContact* myself(); + + +public slots: + + /** + * Add the metacontact into the contact list + * When calling this method, the contact has to be already placed in the correct group. + * If the contact is not in a group, it will be added to the top-level group. + * It is also better if the MetaContact could also be completely created, i.e: all contacts already in it + */ + void addMetaContact( Kopete::MetaContact *c ); + + /** + * Remove a metacontact from the contactlist. + * This method delete itself the metacontact. + */ + void removeMetaContact( Kopete::MetaContact *contact ); + + /** + * Add a group + * each group must be added on the list after his creation. + */ + void addGroup(Kopete::Group *); + + /** + * Remove a group + * this method delete the group + */ + void removeGroup(Kopete::Group *); + + /** + * Set which items are selected in the ContactList GUI. + * This method has to be called by the contactlist UI side. + * it stores the selected items, and emits signals + */ + void setSelectedItems(QPtrList metaContacts , QPtrList groups); + + /** + * Apply the global identity. + */ + void loadGlobalIdentity(); + +signals: + /** + * A meta contact was added to the contact list. Interested classes + * ( like the listview widgets ) can connect to this signal to receive + * the newly added contacts. + */ + void metaContactAdded( Kopete::MetaContact *mc ); + + /** + * A metacontact has just been removed. and will be soon deleted + */ + void metaContactRemoved( Kopete::MetaContact *mc ); + + /** + * A group has just been added + */ + void groupAdded( Kopete::Group * ); + + /** + * A group has just been removed + */ + void groupRemoved( Kopete::Group * ); + + /** + * A group has just been renamed + */ + void groupRenamed(Kopete::Group *, const QString & oldname); + + /** + * A contact has been added to a group + */ + void metaContactAddedToGroup( Kopete::MetaContact *mc, Kopete::Group *to ); + /** + * A contact has been removed from a group + */ + void metaContactRemovedFromGroup( Kopete::MetaContact *mc, Kopete::Group *from ); + + /** + * This signal is emit when the selection has changed, it is emitted after the following slot + * Warning: Do not delete any contacts in slots connected to this signal. (it is the warning in the QListView::selectionChanged() doc) + */ + void selectionChanged(); + /** + * This signal is emitted each time the selection has changed. the bool is set to true if only one meta contact has been selected, + * and set to false if none, or several contacts are selected + * you can connect this signal to KAction::setEnabled if you have an action which is applied to only one contact + */ + void metaContactSelected(bool); + + /** + * This signal is emitted each time a global identity field change. + * HOWTO use: + * + * - Connect signal globalIdentityChanged(const QString &key, const QVariant + * &value) to a slot in your derivate Account class (the best + * place to put it). + * - In the slot: + * - Check the key you want to be sync with global identity. + * - Update the myself contact and/or update on server. + * + * For now, when photo is changed, it always send the photo file path. + * + * Connect signal in your Account constructor: + * @code + * connect(Kopete::ContactList::self(), SIGNAL(globalIdentityChanged(const QString&, const QVariant&)), SLOT(slotglobalIdentityChanged(const QString&, const QVariant&))); + * @endcode + * + * Example of a typical implemented slot: + * @code + * void slotGlobalIdentityChanged(const QString &key, const QVariant &value) + * { + * if(key == Kopete::Global::Properties::self()->nickName().key()) + * { + * myself()->setProperty(protocol()->propNickname, value.toString()); + * this->slotUpdateUserInfo(); + * } + * else if(key == Kopete::Global::Properties::self()->photo().key()) + * { + * myself()->setProperty(protocol()->propPhotoUrl, value.toString()); + * this->slotUpdateDisplayPicture(); + * } + * } + * @endcode + */ + void globalIdentityChanged( const QString &key, const QVariant &value ); + +private slots: + /** + * Called when the contact list changes. Flags the list dirty and schedules a save for a little while later. + */ + void slotSaveLater(); + /** + * Called on contactlist load or when KABC has changed, to check if we need to update our contactlist from there. + */ + void slotKABCChanged(); + + /** + * Called when the myself displayName changed. + */ + void slotDisplayNameChanged(); + + /** + * Called when the myself photo changed. + */ + void slotPhotoChanged(); + +private: + + /** + * Convert the contact list from an older version + */ + void convertContactList( const QString &fileName, uint fromVersion, uint toVersion ); + + + /** + * Private constructor: we are a singleton + */ + ContactList(); + + static ContactList *s_self; + class Private; + Private *d; + +public: //TODO I think all theses method should be moved to the decop interface. + /** + * Return all meta contacts + */ + QStringList contacts() const; + + /** + * Return all meta contacts that are reachable + */ + QStringList reachableContacts() const; + + /** + * Return all contacts that are online + */ + QPtrList onlineContacts() const; + + /** + * Overloaded method of @ref onlineContacts() that only returns + * the online contacts for a single protocol + */ + QPtrList onlineContacts( const QString &protocolId ) const; + + /** + * Return all meta contacts that are online + */ + QPtrList onlineMetaContacts() const; + + /** + * Overloaded method of @ref onlineMetaContacts() that only returns + * the online meta contacts for a single protocol + */ + QPtrList onlineMetaContacts( const QString &protocolId ) const; + + /** + * Returns all contacts which can accept file transfers + */ + QStringList fileTransferContacts() const; + + QStringList contactFileProtocols( const QString &displayName); + + /** + * Return all meta contacts with their current status + * + * FIXME: Do we *need* this one? Sounds error prone to me, because + * nicknames can contain parentheses too. - Martijn + */ + QStringList contactStatuses() const; + + + /** + * Exposed via DCOP in kopeteiface + * Used to send a file to a MetaContact using the highest ranked protocol + * + * FIXME: We need to change this to use a unique ID instead of the displayName + * + * @param displayName Metacontact to send file to + * @param sourceURL The file we are sending + * @param altFileName (Optional) An alternate filename for the file we are sending + * @param fileSize (Optional) The size of the file + */ + void sendFile(const QString &displayName, const KURL &sourceURL, + const QString &altFileName = QString::null, const long unsigned int fileSize = 0L); + + /** + * Open a chat to a contact, and optionally set some initial text + */ + void messageContact( const QString &displayName, const QString &messageText = QString::null ); + +public slots: + /** + * @internal + * Load the contact list + * + * FIXME: Use a better way, without exposing the XML backend, though. + */ + void load(); + + void save(); + +private: + /** + * Return a XML representation of the contact list + */ + const QDomDocument toXML(); + + /** + * Load the contact list from XML file + */ + void loadXML(); + + /** + * Save the contact list to XML file + */ + void saveXML(); +}; + +} //END namespace Kopete + + +#endif + + diff --git a/kopete/libkopete/kopetecontactlistelement.cpp b/kopete/libkopete/kopetecontactlistelement.cpp new file mode 100644 index 00000000..2474d1af --- /dev/null +++ b/kopete/libkopete/kopetecontactlistelement.cpp @@ -0,0 +1,261 @@ +/* + kopeteplugindataobject.cpp - Kopete Plugin Data Object + + Copyright (c) 2003-2004 by Olivier Goffart + Copyright (c) 2003 by Martijn Klingens + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetecontactlistelement.h" + +#include +#include +#include + +#include "kopeteplugin.h" + +namespace Kopete { + +class ContactListElement::Private +{ +public: + QMap > pluginData; + QMap icons; + bool useCustomIcon; +}; + +ContactListElement::ContactListElement( QObject *parent, const char *name ) +: QObject( parent, name ) +{ + d = new Private; + + d->useCustomIcon = false; +#if 0 //TODO + connect( Kopete::Global::onlineStatusIconCache(), SIGNAL( iconsChanged() ), SIGNAL( iconAppearanceChanged() ) ); +#endif +} + +ContactListElement::~ContactListElement() +{ + delete d; +} + +void ContactListElement::setPluginData( Plugin *plugin, const QMap &pluginData ) +{ + if ( pluginData.isEmpty() ) + { + d->pluginData.remove( plugin->pluginId() ); + return; + } + + d->pluginData[ plugin->pluginId() ] = pluginData; + + emit pluginDataChanged(); +} + +void ContactListElement::setPluginData( Plugin *p, const QString &key, const QString &value ) +{ + d->pluginData[ p->pluginId() ][ key ] = value; + + emit pluginDataChanged(); +} + +QMap ContactListElement::pluginData( Plugin *plugin ) const +{ + if ( !d->pluginData.contains( plugin->pluginId() ) ) + return QMap(); + + return d->pluginData[ plugin->pluginId() ]; +} + +QString ContactListElement::pluginData( Plugin *plugin, const QString &key ) const +{ + if ( !d->pluginData.contains( plugin->pluginId() ) || !d->pluginData[ plugin->pluginId() ].contains( key ) ) + return QString::null; + + return d->pluginData[ plugin->pluginId() ][ key ]; +} + +const QValueList ContactListElement::toXML() +{ + QDomDocument pluginData; + QValueList pluginNodes; + pluginData.appendChild( pluginData.createElement( QString::fromLatin1( "plugin-data" ) ) ); + + if ( !d->pluginData.isEmpty() ) + { + QMap >::ConstIterator pluginIt; + for ( pluginIt = d->pluginData.begin(); pluginIt != d->pluginData.end(); ++pluginIt ) + { + QDomElement pluginElement = pluginData.createElement( QString::fromLatin1( "plugin-data" ) ); + pluginElement.setAttribute( QString::fromLatin1( "plugin-id" ), pluginIt.key() ); + + QMap::ConstIterator it; + for ( it = pluginIt.data().begin(); it != pluginIt.data().end(); ++it ) + { + QDomElement pluginDataField = pluginData.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginDataField.setAttribute( QString::fromLatin1( "key" ), it.key() ); + pluginDataField.appendChild( pluginData.createTextNode( it.data() ) ); + pluginElement.appendChild( pluginDataField ); + } + + pluginData.documentElement().appendChild( pluginElement ); + pluginNodes.append( pluginElement ); + } + } + if ( !d->icons.isEmpty() ) + { + QDomElement iconsElement = pluginData.createElement( QString::fromLatin1( "custom-icons" ) ); + iconsElement.setAttribute( QString::fromLatin1( "use" ), d->useCustomIcon ? QString::fromLatin1( "1" ) : QString::fromLatin1( "0" ) ); + + for ( QMap::ConstIterator it = d->icons.begin(); it != d->icons.end(); ++it ) + { + QDomElement iconElement = pluginData.createElement( QString::fromLatin1( "icon" ) ); + QString stateStr; + switch ( it.key() ) + { + case Open: + stateStr = QString::fromLatin1( "open" ); + break; + case Closed: + stateStr = QString::fromLatin1( "closed" ); + break; + case Online: + stateStr = QString::fromLatin1( "online" ); + break; + case Away: + stateStr = QString::fromLatin1( "away" ); + break; + case Offline: + stateStr = QString::fromLatin1( "offline" ); + break; + case Unknown: + stateStr = QString::fromLatin1( "unknown" ); + break; + case None: + default: + stateStr = QString::fromLatin1( "none" ); + break; + } + iconElement.setAttribute( QString::fromLatin1( "state" ), stateStr ); + iconElement.appendChild( pluginData.createTextNode( it.data() ) ); + iconsElement.appendChild( iconElement ); + } + pluginData.documentElement().appendChild( iconsElement ); + pluginNodes.append( iconsElement ); + } + return pluginNodes; +} + +bool ContactListElement::fromXML( const QDomElement& element ) +{ + if ( element.tagName() == QString::fromLatin1( "plugin-data" ) ) + { + QMap pluginData; + QString pluginId = element.attribute( QString::fromLatin1( "plugin-id" ), QString::null ); + + //in kopete 0.6 the AIM protocol was called OSCAR + if ( pluginId == QString::fromLatin1( "OscarProtocol" ) ) + pluginId = QString::fromLatin1( "AIMProtocol" ); + + QDomNode field = element.firstChild(); + while( !field.isNull() ) + { + QDomElement fieldElement = field.toElement(); + if ( fieldElement.tagName() == QString::fromLatin1( "plugin-data-field" ) ) + { + pluginData.insert( fieldElement.attribute( QString::fromLatin1( "key" ), + QString::fromLatin1( "undefined-key" ) ), fieldElement.text() ); + } + field = field.nextSibling(); + } + d->pluginData.insert( pluginId, pluginData ); + } + else if ( element.tagName() == QString::fromLatin1( "custom-icons" ) ) + { + d->useCustomIcon= element.attribute( QString::fromLatin1( "use" ), QString::fromLatin1( "1" ) ) == QString::fromLatin1( "1" ); + QDomNode ic = element.firstChild(); + while( !ic.isNull() ) + { + QDomElement iconElement = ic.toElement(); + if ( iconElement.tagName() == QString::fromLatin1( "icon" ) ) + { + QString stateStr = iconElement.attribute( QString::fromLatin1( "state" ), QString::null ); + QString icon = iconElement.text(); + IconState state = None; + + if ( stateStr == QString::fromLatin1( "open" ) ) + state = Open; + if ( stateStr == QString::fromLatin1( "closed" ) ) + state = Closed; + if ( stateStr == QString::fromLatin1( "online" ) ) + state = Online; + if ( stateStr == QString::fromLatin1( "offline" ) ) + state = Offline; + if ( stateStr == QString::fromLatin1( "away" ) ) + state = Away; + if ( stateStr == QString::fromLatin1( "unknown" ) ) + state = Unknown; + + d->icons[ state ] = icon; + } + ic = ic.nextSibling(); + } + } + else + { + return false; + } + + return true; +} + +QString ContactListElement::icon( ContactListElement::IconState state ) const +{ + if ( d->icons.contains( state ) ) + return d->icons[state]; + + return d->icons[ None ]; +} + +void ContactListElement::setIcon( const QString& icon , ContactListElement::IconState state ) +{ + if ( icon.isNull() ) + d->icons.remove( state ); + else + d->icons[ state ] = icon; + + emit iconChanged( state, icon ); + emit iconAppearanceChanged(); +} + +bool ContactListElement::useCustomIcon() const +{ + return d->useCustomIcon; +} + +void ContactListElement::setUseCustomIcon( bool useCustomIcon ) +{ + if ( d->useCustomIcon != useCustomIcon ) + { + d->useCustomIcon = useCustomIcon; + emit useCustomIconChanged( useCustomIcon ); + } +} + +} //END namespace Kopete + +#include "kopetecontactlistelement.moc" + + + diff --git a/kopete/libkopete/kopetecontactlistelement.h b/kopete/libkopete/kopetecontactlistelement.h new file mode 100644 index 00000000..b0f2eb69 --- /dev/null +++ b/kopete/libkopete/kopetecontactlistelement.h @@ -0,0 +1,172 @@ +/* + kopeteplugindataobject.h - Kopete Plugin Data Object + + Copyright (c) 2003-2004 by Olivier Goffart + Copyright (c) 2003 by Martijn Klingens + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPLUGINDATAOBJECT_H +#define KOPETEPLUGINDATAOBJECT_H + +#include +#include + +#include "kopete_export.h" + +namespace Kopete { + +class Plugin; + + +/** + * @author Olivier Goffart + * + * This is the base class for base elements of the contactlist. + * His purpose is to share the code between @ref Group and @ref MetaContact + * + * It handle the saving and loading of plugin data from the contactlist. + * Plugins may set custom datas to metaocntacts or groups by calling @ref setPluginData + * and may retreive them with @ref pluginData + * + * It also allow to store an icon for this element. + */ +class KOPETE_EXPORT ContactListElement : public QObject /* public KopeteNotifyDataObject */ +{ + Q_OBJECT + +protected: + ContactListElement( QObject *parent = 0L, const char *name = 0L ); + ~ContactListElement(); + + +public: + + /** + * Set the plugin-specific data. + * The data in the provided QMap is a set of key/value pairs. + * Note that protocol plugins usually shouldn't use this method, but + * reimplement @ref Contact::serialize() instead. This method + * is called by @ref Protocol for those classes. + * + * WARNING: This erases all old data stored for this object! + * You may want to consider the @ref setPluginData() overload + * that takes a single field as parameter. + */ + void setPluginData( Plugin *plugin, const QMap &value ); + + /** + * Convenience method to store or change only a single field of the + * plugin data. As with the other @ref setPluginData() method, protocols + * are advised not to use this method and reimplement + * @ref Contact::serialize() instead. + * + * Note that you should save the file after adding data or it will get lost. + */ + void setPluginData( Plugin *plugin, const QString &key, const QString &value ); + + /** + * Get the settings as stored previously by calls to @ref setPluginData() + * + * Note that calling this method for protocol plugins that use the + * @ref Contact::serialize() API may yield unexpected results. + */ + QMap pluginData( Plugin *plugin ) const; + + /** + * Convenience method to retrieve only a single field from the plugin + * data. See @ref setPluginData(). + * + * Note that plugin data is accessible only after it has been loaded + * from the XML file. Don't call this method before then (e.g. in + * constructors). + */ + QString pluginData( Plugin *plugin, const QString &key ) const; + + /** + * The various icon states. Some state are reserved for Groups, + * other for metacontact. + * 'None' is the default icon. + */ + enum IconState { None, Open, Closed, Online, Away, Offline, Unknown }; + + /** + * return the icon for this object, in the given state. + * if there is no icon registered for this state, the None icon is used + * if available + */ + QString icon( IconState state = None ) const; + + /** + * Set the icon in the given state + * To clear an entry, set a QString::null + */ + void setIcon( const QString &icon, IconState = None ); + + /** + * return if yes or no the user wants to display some custom icon. + * you can use @ref icon() to know the icons to uses + */ + bool useCustomIcon() const; + + /** + * set if the user want to show custom icon he set with @ref setIcon + * this does not clear icons string if you set false + */ + void setUseCustomIcon( bool useCustomIcon ); + +signals: + /** + * The plugin data was changed (by a plugin) + */ + void pluginDataChanged(); + + /** + * The icon to use for some state has changed + */ + void iconChanged( Kopete::ContactListElement::IconState, const QString & ); + + /** + * The visual appearance of some of our icons has changed + */ + void iconAppearanceChanged(); + + /** + * The useCustomIcon property has changed + */ + void useCustomIconChanged( bool useCustomIcon ); + +protected: + /** + * Return a XML representation of plugin data + */ + const QValueList toXML(); + + /** + * Load plugin data from one Dom Element: + * It should be a element or a element. if not, nothing will happen + * @return true if something has ben loaded. false if the element was not a fine + */ + bool fromXML( const QDomElement &element ); + +private: + class Private; + Private *d; +}; + +} //END namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetecontactproperty.cpp b/kopete/libkopete/kopetecontactproperty.cpp new file mode 100644 index 00000000..87e176af --- /dev/null +++ b/kopete/libkopete/kopetecontactproperty.cpp @@ -0,0 +1,204 @@ +/* + kopetecontactproperty.cpp + + Kopete::Contact Property class + + Copyright (c) 2004 by Stefan Gehn + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetecontactproperty.h" +#include +#include "kopeteglobal.h" + +namespace Kopete +{ + +struct ContactPropertyTmplPrivate +{ + QString key; + QString label; + QString icon; + bool persistent; + bool richText; + bool privateProp; + unsigned int refCount; +}; + +ContactPropertyTmpl ContactPropertyTmpl::null; + + +ContactPropertyTmpl::ContactPropertyTmpl() +{ + d = new ContactPropertyTmplPrivate; + d->refCount = 1; + d->persistent = false; + // Don't register empty template +} + +ContactPropertyTmpl::ContactPropertyTmpl(const QString &key, + const QString &label, const QString &icon, bool persistent, bool richText, bool privateProp) +{ + ContactPropertyTmpl other = Kopete::Global::Properties::self()->tmpl(key); + if(other.isNull()) + { +// kdDebug(14000) << k_funcinfo << "Creating new template for key = '" << key << "'" << endl; + + d = new ContactPropertyTmplPrivate; + d->refCount = 1; + d->key = key; + d->label = label; + d->icon = icon; + d->persistent = persistent; + d->richText = richText; + d->privateProp = privateProp; + Kopete::Global::Properties::self()->registerTemplate(key, (*this)); + } + else + { +// kdDebug(14000) << k_funcinfo << "Using existing template for key = '" << key << "'" << endl; + d = other.d; + d->refCount++; + } +} + +ContactPropertyTmpl::ContactPropertyTmpl(const ContactPropertyTmpl &other) +{ + d = other.d; + d->refCount++; +} + +ContactPropertyTmpl &ContactPropertyTmpl::operator=( + const ContactPropertyTmpl &other) +{ + d->refCount--; + if(d->refCount == 0) + { + if (!d->key.isEmpty()) // null property + Kopete::Global::Properties::self()->unregisterTemplate(d->key); + delete d; + } + + d = other.d; + d->refCount++; + + return *this; +} + +ContactPropertyTmpl::~ContactPropertyTmpl() +{ + d->refCount--; + if(d->refCount == 0) + { + if (!d->key.isEmpty()) // null property + Kopete::Global::Properties::self()->unregisterTemplate(d->key); + delete d; + } +} + +bool ContactPropertyTmpl::operator==(const ContactPropertyTmpl &other) const +{ + return (d && other.d && + d->key == other.d->key && + d->label == other.d->label && + d->icon == other.d->key && + d->persistent == other.d->persistent); +} + +bool ContactPropertyTmpl::operator!=(const ContactPropertyTmpl &other) const +{ + return (!d || !other.d || + d->key != other.d->key || + d->label != other.d->label || + d->icon != other.d->key || + d->persistent != other.d->persistent); +} + + +const QString &ContactPropertyTmpl::key() const +{ + return d->key; +} + +const QString &ContactPropertyTmpl::label() const +{ + return d->label; +} + +const QString &ContactPropertyTmpl::icon() const +{ + return d->icon; +} + +bool ContactPropertyTmpl::persistent() const +{ + return d->persistent; +} + +bool ContactPropertyTmpl::isRichText() const +{ + return d->richText; +} + +bool ContactPropertyTmpl::isPrivate() const +{ + return d->privateProp; +} + +bool ContactPropertyTmpl::isNull() const +{ + return (!d || d->key.isNull()); +} + + +// ----------------------------------------------------------------------------- + + +ContactProperty ContactProperty::null; + +ContactProperty::ContactProperty() +{ +} + +ContactProperty::ContactProperty(const ContactPropertyTmpl &tmpl, + const QVariant &val) +{ + mTemplate = tmpl; + mValue = val; +} + +ContactProperty::~ContactProperty() +{ + //kdDebug(14000) << k_funcinfo << "this = " << (void *)this << endl; +} + +const QVariant &ContactProperty::value() const +{ + return mValue; +} + +const ContactPropertyTmpl &ContactProperty::tmpl() const +{ + return mTemplate; +} + +bool ContactProperty::isNull() const +{ + return mValue.isNull(); +} + +bool ContactProperty::isRichText() const +{ + return mTemplate.isRichText(); +} + +} // END namespace Kopete diff --git a/kopete/libkopete/kopetecontactproperty.h b/kopete/libkopete/kopetecontactproperty.h new file mode 100644 index 00000000..b5c8f060 --- /dev/null +++ b/kopete/libkopete/kopetecontactproperty.h @@ -0,0 +1,195 @@ +/* + kopetecontactproperty.h + + Kopete::Contact Property class + + Copyright (c) 2004 by Stefan Gehn + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETECONTACTPROPERTY_H_ +#define _KOPETECONTACTPROPERTY_H_ + +#include + +#include "kopete_export.h" + +namespace Kopete +{ + +struct ContactPropertyTmplPrivate; + +/** + * @author Stefan Gehn + * + * The template class for registering properties in Kopete + * You need to use this if you want to set properties for a + * Kopete::Contact + **/ +class KOPETE_EXPORT ContactPropertyTmpl +{ + public: + /** + * Constructor only used for empty ContactPropertyTmpl objects + * + * Note: Only useful for the null object + **/ + ContactPropertyTmpl(); + + /** + * Constructor + * @param key internal unique key for this template + * @param label a label to show for properties based on this template + * @param icon name of the icon to show for properties based on this template + * @param persistent if true, properties based on this template will be + * saved to the contactlist. + * @param richText Indicate that this property should be able to handle rich text + * @param privateProp if true, properties based on this template won't be + * visible to the user + **/ + ContactPropertyTmpl( const QString &key, + const QString &label, + const QString &icon = QString::null, + bool persistent = false, + bool richText = false, + bool privateProp = false ); + + /** + * Copy constructor + **/ + ContactPropertyTmpl(const ContactPropertyTmpl &other); + + /** Destructor */ + ~ContactPropertyTmpl(); + + ContactPropertyTmpl &operator=(const ContactPropertyTmpl &other); + + bool operator==(const ContactPropertyTmpl &other) const; + bool operator!=(const ContactPropertyTmpl &other) const; + + /** + * Getter for the unique key. Properties based on this template will be + * stored with this key + **/ + const QString &key() const; + + /** + * Getter for i18ned label + **/ + const QString &label() const; + + /** + * Getter for icon to show aside or instead of @p label() + **/ + const QString &icon() const; + + /** + * Returns true if properties based on this template should + * be saved across Kopete sessions, false otherwise. + **/ + bool persistent() const; + + /** + * Returns true if properties based on this template are HTML formatted + **/ + bool isRichText() const; + + /** + * Returns true if properties based on this template are invisible to the user + **/ + bool isPrivate() const; + + /** + * An empty template, check for it using isNull() + */ + static ContactPropertyTmpl null; + + /** + * Returns true if this object is an empty template + **/ + bool isNull() const; + + /** + * A Map of QString and ContactPropertyTmpl objects, based on QMap + **/ + typedef QMap Map; + + private: + ContactPropertyTmplPrivate *d; +}; + + +/** + * @author Stefan Gehn + * + * A data container for whatever information Kopete or any of its + * plugins want to store for a Kopete::Contact + **/ +class KOPETE_EXPORT ContactProperty +{ + // TODO: Add d-pointer ! + public: + /** + * Constructor only used for empty ContactProperty objects + * + * Note: you cannot set a label or value later on! + **/ + ContactProperty(); + + /** + * @param tmpl The contact property template this property is based on + * @param value The value this Property holds + **/ + ContactProperty(const ContactPropertyTmpl &tmpl, const QVariant &value); + + /** Destructor **/ + ~ContactProperty(); + + /** + * Getter for this properties template + **/ + const ContactPropertyTmpl &tmpl() const; + + /** + * Getter for this properties value + **/ + const QVariant &value() const; + + /** + * The null, i.e. empty, ContactProperty + */ + static ContactProperty null; + + /** + * Returns true if this object is an empty Property (i.e. it holds no + * value), false otherwise. + **/ + bool isNull() const; + + /** + * Returns true if this property is HTML formatted + **/ + bool isRichText() const; + + /** + * A map of key,ContactProperty items + **/ + typedef QMap Map; + + private: + QVariant mValue; + ContactPropertyTmpl mTemplate; +}; + +} // END namespace Kopete + +#endif //_KOPETECONTACTPROPERTY_H_ diff --git a/kopete/libkopete/kopeteeventpresentation.cpp b/kopete/libkopete/kopeteeventpresentation.cpp new file mode 100644 index 00000000..f90a19e5 --- /dev/null +++ b/kopete/libkopete/kopeteeventpresentation.cpp @@ -0,0 +1,90 @@ +/* + kopeteeventpresentation.cpp - Kopete Custom Notify Data Object + + Copyright (c) 2004 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteeventpresentation.h" + +Kopete::EventPresentation::EventPresentation( const PresentationType type ) +{ + m_type = type; +} + +Kopete::EventPresentation::EventPresentation( const PresentationType type, + const QString &content, const bool singleShot, const bool enabled ) +{ + m_type = type; + m_content = content; + m_singleShot = singleShot; + m_enabled = enabled; +} + +Kopete::EventPresentation::~EventPresentation() +{ +} + +Kopete::EventPresentation::PresentationType Kopete::EventPresentation::type() +{ + return m_type; +} + +QString Kopete::EventPresentation::content() +{ + return m_content; +} + +bool Kopete::EventPresentation::enabled() +{ + return m_enabled; +} + +bool Kopete::EventPresentation::singleShot() +{ + return m_singleShot; +} + +void Kopete::EventPresentation::setContent( const QString &content ) +{ + m_content = content; +} + +void Kopete::EventPresentation::setEnabled( const bool enabled ) +{ + m_enabled = enabled; +} + +void Kopete::EventPresentation::setSingleShot( const bool singleShot ) +{ + m_singleShot = singleShot; +} + +QString Kopete::EventPresentation::toString() +{ + QString type; + switch ( m_type ) + { + case Sound: + type= QString::fromLatin1("sound"); + break; + case Message: + type= QString::fromLatin1("message"); + break; + case Chat: + type= QString::fromLatin1("chat"); + break; + } + QString stringRep = QString::fromLatin1( "Presentation; type=%1; content=%2; enabled=%3; single shot=%4\n" ).arg(type).arg(m_content).arg(m_enabled).arg(m_singleShot); + return stringRep; +} diff --git a/kopete/libkopete/kopeteeventpresentation.h b/kopete/libkopete/kopeteeventpresentation.h new file mode 100644 index 00000000..ea30cb5d --- /dev/null +++ b/kopete/libkopete/kopeteeventpresentation.h @@ -0,0 +1,56 @@ +/* + kopeteeventpresentation.h - Kopete Custom Notify Data Object + + Copyright (c) 2004 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEEVENTPRESENTATION_H +#define KOPETEEVENTPRESENTATION_H + +#include + +#include "kopete_export.h" + +namespace Kopete +{ + +class KOPETE_EXPORT EventPresentation +{ +public: + enum PresentationType { Sound, Message, Chat }; + EventPresentation( const PresentationType type ); + EventPresentation( const PresentationType type, + const QString &content = QString::null, + const bool singleShot = false, const bool enabled = false ); + ~EventPresentation(); + + PresentationType type(); + QString content(); + bool enabled(); + bool singleShot(); + + void setContent( const QString &content ); + void setEnabled( const bool enabled ); + void setSingleShot( const bool singleShot ); + QString toString(); +private: + PresentationType m_type; + QString m_content; + bool m_enabled; + bool m_singleShot; +}; + +} + +#endif diff --git a/kopete/libkopete/kopeteglobal.cpp b/kopete/libkopete/kopeteglobal.cpp new file mode 100644 index 00000000..a11dafdd --- /dev/null +++ b/kopete/libkopete/kopeteglobal.cpp @@ -0,0 +1,346 @@ +/* + kopeteglobal.cpp - Kopete Globals + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteglobal.h" +#include "kopeteuiglobal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Kopete +{ + +namespace Global +{ + +class PropertiesPrivate +{ + public: + ContactPropertyTmpl::Map mTemplates; +}; + +Properties *Properties::mSelf = 0L; + +Properties *Properties::self() +{ + if(!mSelf) + { + //kdDebug(14000) << k_funcinfo << endl; + mSelf = new Properties(); + } + return mSelf; +} + +Properties::Properties() +{ + kdDebug(14000) << k_funcinfo << endl; + d = new PropertiesPrivate(); +} + +Properties::~Properties() +{ + kdDebug(14000) << k_funcinfo << endl; + delete d; +} + +const ContactPropertyTmpl &Properties::tmpl(const QString &key) const +{ + if(d->mTemplates.contains(key)) + { + /*kdDebug(14000) << k_funcinfo << + "Found template for key = '" << key << "'" << endl;*/ + return d->mTemplates[key]; + } + else + return ContactPropertyTmpl::null; +} + +bool Properties::registerTemplate(const QString &key, + const ContactPropertyTmpl &tmpl) +{ + if(d->mTemplates.contains(key)) + { + kdDebug(14000) << k_funcinfo << + "Called for EXISTING key = '" << key << "'" << endl; + return false; + } + else + { + d->mTemplates.insert(key, tmpl); + return true; + } +} + +void Properties::unregisterTemplate(const QString &key) +{ + kdDebug(14000) << k_funcinfo << "called for key: '" << key << "'" << endl; + d->mTemplates.remove(key); +} + +bool Properties::isRegistered(const QString &key) +{ + return d->mTemplates.contains(key); +} + +const ContactPropertyTmpl &Properties::fullName() const +{ + return createProp(QString::fromLatin1("FormattedName"), + i18n("Full Name")); +} + +const ContactPropertyTmpl &Properties::idleTime() const +{ + return createProp(QString::fromLatin1("idleTime"), + i18n("Idle Time")); +} + +const ContactPropertyTmpl &Properties::onlineSince() const +{ + return createProp(QString::fromLatin1("onlineSince"), + i18n("Online Since")); +} + +const ContactPropertyTmpl &Properties::lastSeen() const +{ + return createProp(QString::fromLatin1("lastSeen"), + i18n("Last Seen"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::awayMessage() const +{ + return createProp(QString::fromLatin1("awayMessage"), + i18n("Away Message")); +} + +const ContactPropertyTmpl &Properties::firstName() const +{ + return createProp(QString::fromLatin1("firstName"), + i18n("First Name"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::lastName() const +{ + return createProp(QString::fromLatin1("lastName"), + i18n("Last Name"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::privatePhone() const +{ + return createProp(QString::fromLatin1("privatePhoneNumber"), + i18n("Private Phone"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::privateMobilePhone() const +{ + return createProp(QString::fromLatin1("privateMobilePhoneNumber"), + i18n("Private Mobile Phone"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::workPhone() const +{ + return createProp(QString::fromLatin1("workPhoneNumber"), + i18n("Work Phone"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::workMobilePhone() const +{ + return createProp(QString::fromLatin1("workMobilePhoneNumber"), + i18n("Work Mobile Phone"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::emailAddress() const +{ + return createProp(QString::fromLatin1("emailAddress"), + i18n("Email Address"), QString::fromLatin1("mail_generic"), true); +} + +const ContactPropertyTmpl &Properties::nickName() const +{ + return createProp(QString::fromLatin1("nickName"), + i18n("Nick Name"), QString::null, true); +} + +const ContactPropertyTmpl &Properties::photo() const +{ + return createProp(QString::fromLatin1("photo"), + i18n("Photo"), QString::null, true); +} + + +const ContactPropertyTmpl &Properties::createProp(const QString &key, + const QString &label, const QString &icon, bool persistent) const +{ + /*kdDebug(14000) << k_funcinfo << + "key = " << key << ", label = " << label << endl;*/ + + if(!d->mTemplates.contains(key)) + { +/* kdDebug(14000) << k_funcinfo << + "CREATING NEW ContactPropertyTmpl WITH key = " << key << + ", label = " << label << ", persisten = " << persistent << endl;*/ + d->mTemplates.insert(key, ContactPropertyTmpl(key, label, icon, persistent)); + } + return tmpl(key); +} + +const ContactPropertyTmpl::Map &Properties::templateMap() const +{ + return d->mTemplates; +} + + +// ----------------------------------------------------------------------------- + + +void installEmoticonTheme(const QString &archiveName) +{ + QStringList foundThemes; + KArchiveEntry *currentEntry = 0L; + KArchiveDirectory* currentDir = 0L; + KProgressDialog *progressDlg = 0L; + KArchive *archive = 0L; + + QString localThemesDir(locateLocal("emoticons", QString::null) ); + + if(localThemesDir.isEmpty()) + { + KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget(), + KMessageBox::Error, i18n("Could not find suitable place " \ + "to install emoticon themes into.")); + return; + } + + progressDlg = new KProgressDialog(0 , "emoticonInstProgress", + i18n("Installing Emoticon Themes..."), QString::null, true); + progressDlg->progressBar()->setTotalSteps(foundThemes.count()); + progressDlg->show(); + kapp->processEvents(); + + QString currentBundleMimeType = KMimeType::findByPath(archiveName, 0, false)->name(); + if( currentBundleMimeType == QString::fromLatin1("application/x-zip") ) + archive = new KZip(archiveName); + else if( currentBundleMimeType == QString::fromLatin1("application/x-tgz") || + currentBundleMimeType == QString::fromLatin1("application/x-tbz") || + currentBundleMimeType == QString::fromLatin1("application/x-gzip") || + currentBundleMimeType == QString::fromLatin1("application/x-bzip2") ) + archive = new KTar(archiveName); + else if(archiveName.endsWith(QString::fromLatin1("jisp")) || archiveName.endsWith(QString::fromLatin1("zip")) ) + archive = new KZip(archiveName); + else + archive = new KTar(archiveName); + + if ( !archive || !archive->open(IO_ReadOnly) ) + { + KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget(), + KMessageBox::Error, + i18n("Could not open \"%1\" for unpacking.").arg(archiveName)); + delete archive; + delete progressDlg; + return; + } + + const KArchiveDirectory* rootDir = archive->directory(); + + // iterate all the dirs looking for an emoticons.xml file + QStringList entries = rootDir->entries(); + for (QStringList::Iterator it = entries.begin(); it != entries.end(); ++it) + { + currentEntry = const_cast(rootDir->entry(*it)); + if (currentEntry->isDirectory()) + { + currentDir = dynamic_cast( currentEntry ); + if (currentDir && ( currentDir->entry(QString::fromLatin1("emoticons.xml")) != NULL || + currentDir->entry(QString::fromLatin1("icondef.xml")) != NULL ) ) + foundThemes.append(currentDir->name()); + } + } + + if (foundThemes.isEmpty()) + { + KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget(), + KMessageBox::Error, i18n("The file \"%1\" is not a valid" \ + " emoticon theme archive.").arg(archiveName)); + archive->close(); + delete archive; + delete progressDlg; + return; + } + + for (QStringList::ConstIterator it = foundThemes.begin(); it != foundThemes.end(); ++it) + { + progressDlg->setLabel( + i18n("Installing %1 emoticon theme") + .arg(*it)); + progressDlg->resize(progressDlg->sizeHint()); + kapp->processEvents(); + + if (progressDlg->wasCancelled()) + break; + + currentEntry = const_cast(rootDir->entry(*it)); + if (currentEntry == 0) + { + kdDebug(14010) << k_funcinfo << "couldn't get next archive entry" << endl; + continue; + } + + if(currentEntry->isDirectory()) + { + currentDir = dynamic_cast(currentEntry); + if (currentDir == 0) + { + kdDebug(14010) << k_funcinfo << + "couldn't cast archive entry to KArchiveDirectory" << endl; + continue; + } + currentDir->copyTo(localThemesDir + *it); + progressDlg->progressBar()->advance(1); + } + } + + archive->close(); + delete archive; + + // check if all steps were done, if there are skipped ones then we didn't + // succeed copying all dirs from the tarball + if (progressDlg->progressBar()->totalSteps() > progressDlg->progressBar()->progress()) + { + KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget(), + KMessageBox::Error, + i18n("A problem occurred during the installation process. " + "However, some of the emoticon themes in the archive may have been " + "installed.")); + } + + delete progressDlg; +} + +} // END namespace Global + +} // END namespace Kopete + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteglobal.h b/kopete/libkopete/kopeteglobal.h new file mode 100644 index 00000000..aa6456f4 --- /dev/null +++ b/kopete/libkopete/kopeteglobal.h @@ -0,0 +1,176 @@ +/* + kopeteglobal.h - Kopete Globals + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEGLOBAL_H +#define KOPETEGLOBAL_H + +#include "kopetecontactproperty.h" + +#include "kopete_export.h" + +/** + * This namespace contains all of Kopete's core classes and functions. + */ +namespace Kopete +{ + +/** + * This namespace contains Kopete's global settings and functions + */ +namespace Global +{ + class PropertiesPrivate; + + /** + * \brief Installs one or more kopete emoticon themes from a tarball + * (either .kopete-emoticons or .tar.gz or .tar.bz2) + * + * @p localPath Full path to a local emoticon archive, use KIO to download + * files in case their are non-local. + * + * @return true in case install was successful, false otherwise. Errors are + * displayed by either KIO or by using KMessagebox directly. + * + * TODO: If possible, port it to KIO instead of using ugly blocking KTar + **/ + KOPETE_EXPORT void installEmoticonTheme(const QString &localPath); + + /** + * \brief Global facility to query/store templates that are needed by KopeteContactProperty + * + * Basically all a plugin author needs to worry about is creating ContactPropertyTmpl + * objects for all the properties he wants to set for a Kopete::Contact, + * everything else is handled behind the scenes. + **/ + class KOPETE_EXPORT Properties + { + friend class Kopete::ContactPropertyTmpl; + public: + /** + * \brief Singleton accessor for this class. + * + * Use it to access the global list of property-templates or to get + * a reference to one of the common ContactPropertyTmpl objects + */ + static Properties *self(); + + /** + * Return a template with defined by @p key, if no such template has + * been registered ContactPropertyTmpl::null will be returned + */ + const ContactPropertyTmpl &tmpl(const QString &key) const; + + /** + * @return a ready-to-use template for a contact's full name. + * + * This is actually no real property, it makes use of + * firstName() and lastName() to assemble an name that consists of + * both name parts + */ + const ContactPropertyTmpl &fullName() const; + + /** + * Return default template for a contact's idle-time + */ + const ContactPropertyTmpl &idleTime() const; + /** + * Return default template for a contact's online-since time + * (i.e. time since he went from offline to online) + */ + const ContactPropertyTmpl &onlineSince() const; + /** + * @return default template for a contact's last-seen time + */ + const ContactPropertyTmpl &lastSeen() const; + /** + * @return default template for a contact's away-message + */ + const ContactPropertyTmpl &awayMessage() const; + /** + * @return default template for a contact's first name + */ + const ContactPropertyTmpl &firstName() const; + /** + * @return default template for a contact's last name + */ + const ContactPropertyTmpl &lastName() const; + /** + * @return default template for a contact's email-address + */ + const ContactPropertyTmpl &emailAddress() const; + /** + * @return default template for a contact's private phone number + */ + const ContactPropertyTmpl &privatePhone() const; + /** + * @return default template for a contact's private mobile number + */ + const ContactPropertyTmpl &privateMobilePhone() const; + /** + * @return default template for a contact's work phone number + */ + const ContactPropertyTmpl &workPhone() const; + /** + * @return default template for a contact's work mobile number + */ + const ContactPropertyTmpl &workMobilePhone() const; + /** + * @return default template for a contact's nickname (set by the contact) + */ + const ContactPropertyTmpl &nickName() const; + /** + * default template for a contact's photo. + * + * It could be either a QString or a QImage. + * If it's a QString, it should points to the path the image is stored. + */ + const ContactPropertyTmpl &photo() const; + + /** + * @return a map of all registered ContactPropertyTmpl object + */ + const ContactPropertyTmpl::Map &templateMap() const; + + /** + * return true if a template with key @p key is already registered, + * false otherwise + */ + bool isRegistered(const QString &key); + + private: + Properties(); + ~Properties(); + + bool registerTemplate(const QString &key, + const ContactPropertyTmpl &tmpl); + void unregisterTemplate(const QString &key); + + const ContactPropertyTmpl &createProp(const QString &key, + const QString &label, const QString &icon=QString::null, + bool persistent = false) const; + + private: + static Properties *mSelf; + PropertiesPrivate *d; + }; // end class Properties + +} // Global + +} // Kopete + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetegroup.cpp b/kopete/libkopete/kopetegroup.cpp new file mode 100644 index 00000000..f50eb08b --- /dev/null +++ b/kopete/libkopete/kopetegroup.cpp @@ -0,0 +1,335 @@ +/* + kopetegroup.cpp - Kopete (Meta)Contact Group + + Copyright (c) 2002-2004 by Olivier Goffart + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetegroup.h" + +#include "kopetecontactlist.h" +#include "kopetemetacontact.h" +#include "kopetecontact.h" +#include "kopetechatsession.h" + +#include + +namespace Kopete { + +class Group::Private +{ +public: + QString displayName; + Group::GroupType type; + bool expanded; + uint groupId; + + //Unique contact id per metacontact + static uint uniqueGroupId; +}; + +Group *Group::s_topLevel = 0L; +Group *Group::s_temporary = 0L; +Group * Group::topLevel() +{ + if ( !s_topLevel ) + s_topLevel = new Group( i18n( "Top Level" ), Group::TopLevel ); + + return s_topLevel; +} + +Group * Group::temporary() +{ + if ( !s_temporary ) + s_temporary = new Group( i18n( "Not in your contact list" ), Group::Temporary ); + + return s_temporary; +} + +uint Group::Private::uniqueGroupId = 0; + +Group::Group( const QString &_name, GroupType _type ) + : ContactListElement( ContactList::self() ) +{ + d = new Private; + d->displayName = _name; + d->type = _type; + d->expanded = true; + d->groupId = 0; +} + +Group::Group() + : ContactListElement( ContactList::self() ) +{ + d = new Private; + d->expanded = true; + d->type = Normal; + d->groupId = 0; +} + +Group::~Group() +{ + if(d->type == TopLevel) + s_topLevel=0L; + if(d->type == Temporary) + s_temporary=0L; + delete d; +} + +QPtrList Group::members() const +{ + QPtrList members = ContactList::self()->metaContacts(); + // members is a *copy* of the meta contacts, so using first(), next() and remove() is fine. + for( members.first(); members.current(); ) + { + if ( members.current()->groups().contains( this ) ) + members.next(); + else + members.remove(); + } + return members; +} + +const QDomElement Group::toXML() +{ + QDomDocument group; + group.appendChild( group.createElement( QString::fromLatin1( "kopete-group" ) ) ); + group.documentElement().setAttribute( QString::fromLatin1( "groupId" ), QString::number( groupId() ) ); + + QString type; + switch ( d->type ) + { + case Temporary: + type = QString::fromLatin1( "temporary" ); + break; + case TopLevel: + type = QString::fromLatin1( "top-level" ); + break; + default: + type = QString::fromLatin1( "standard" ); // == Normal + break; + } + + group.documentElement().setAttribute( QString::fromLatin1( "type" ), type ); + group.documentElement().setAttribute( QString::fromLatin1( "view" ), QString::fromLatin1( d->expanded ? "expanded" : "collapsed" ) ); + + QDomElement displayName = group.createElement( QString::fromLatin1( "display-name" ) ); + displayName.appendChild( group.createTextNode( d->displayName ) ); + group.documentElement().appendChild( displayName ); + + // Store other plugin data + QValueList pluginData = ContactListElement::toXML(); + for ( QValueList::Iterator it = pluginData.begin(); it != pluginData.end(); ++it ) + group.documentElement().appendChild( group.importNode( *it, true ) ); + + // Store custom notification data + QDomElement notifyData = Kopete::NotifyDataObject::notifyDataToXML(); + if ( notifyData.hasChildNodes() ) + group.documentElement().appendChild( group.importNode( notifyData, true ) ); + + return group.documentElement(); +} + +bool Group::fromXML( const QDomElement &data ) +{ + QString strGroupId = data.attribute( QString::fromLatin1( "groupId" ) ); + if ( !strGroupId.isEmpty() ) + { + d->groupId = strGroupId.toUInt(); + if ( d->groupId > d->uniqueGroupId ) + d->uniqueGroupId = d->groupId; + } + + // Don't overwrite type for Temporary and TopLevel groups + if ( d->type != Temporary && d->type != TopLevel ) + { + QString type = data.attribute( QString::fromLatin1( "type" ), QString::fromLatin1( "standard" ) ); + if ( type == QString::fromLatin1( "temporary" ) ) + { + if ( d->type != Temporary ) + { + s_temporary->fromXML( data ); + return false; + } + } + else if ( type == QString::fromLatin1( "top-level" ) ) + { + if ( d->type != TopLevel ) + { + s_topLevel->fromXML( data ); + return false; + } + } + else + { + d->type = Normal; + } + } + + QString view = data.attribute( QString::fromLatin1( "view" ), QString::fromLatin1( "expanded" ) ); + d->expanded = ( view != QString::fromLatin1( "collapsed" ) ); + + QDomNode groupData = data.firstChild(); + while ( !groupData.isNull() ) + { + QDomElement groupElement = groupData.toElement(); + if ( groupElement.tagName() == QString::fromLatin1( "display-name" ) ) + { + // Don't set display name for temporary or top-level items + if ( d->type == Normal ) + d->displayName = groupElement.text(); + } + else if( groupElement.tagName() == QString::fromLatin1( "custom-notifications" ) ) + { + Kopete::NotifyDataObject::notifyDataFromXML( groupElement ); + } + else + { + Kopete::ContactListElement::fromXML( groupElement ); + } + + groupData = groupData.nextSibling(); + } + + // Sanity checks. We must not have groups without a displayname. + if ( d->displayName.isEmpty() ) + { + switch ( d->type ) + { + case Temporary: + d->displayName = QString::fromLatin1( "Temporary" ); + break; + case TopLevel: + d->displayName = QString::fromLatin1( "Top-Level" ); + break; + default: + d->displayName = i18n( "(Unnamed Group)" ); + break; + } + } + + //this allows to save data for the top-level group in the top-level group + return ( d->type == Normal ); +} + +void Group::setDisplayName( const QString &s ) +{ + if ( d->displayName != s ) + { + QString oldname = d->displayName; + d->displayName = s; + emit displayNameChanged( this, oldname ); + } +} + +QString Group::displayName() const +{ + return d->displayName; +} + +Group::GroupType Group::type() const +{ + return d->type; +} + +void Group::setType( GroupType t ) +{ + d->type = t; +} + +void Group::setExpanded( bool isExpanded ) +{ + d->expanded = isExpanded; +} + +bool Group::isExpanded() const +{ + return d->expanded; +} + +uint Group::groupId() const +{ + if ( d->groupId == 0 ) + d->groupId = ++d->uniqueGroupId; + + return d->groupId; +} + + +void Group::sendMessage() +{ + QPtrList list = onlineMembers(); + Kopete::MetaContact *mc = list.first(); + Kopete::Contact *c; + + if(!mc) + return; + c = mc->preferredContact(); + c->sendMessage(); + if( c->manager( Contact::CanCreate ) ) + { + connect( c->manager(), SIGNAL( messageSent( Kopete::Message&, Kopete::ChatSession* ) ), this, SLOT( sendMessage( Kopete::Message& ) )); + } +} + +void Group::sendMessage( Message& msg ) +{ + QPtrList list = onlineMembers(); + Kopete::MetaContact *mc = list.first(); + ChatSession *cs=msg.manager(); + if( cs ) + { + disconnect( cs, SIGNAL( messageSent( Kopete::Message&, Kopete::ChatSession* ) ), this, SLOT( sendMessage( Kopete::Message& ) ) ); + } + else + return; + + if(!mc) + return; + list.remove( msg.to().first()->metaContact() ); + for( mc = list.first(); mc; mc = list.next() ) + { + if(mc->isReachable()) + { + Contact *kcontact=mc->preferredContact(); + if( kcontact->manager( Contact::CanCreate ) ) + { + //This is hack and stupid. send message to group should never exist anyway - Olivier 2005-09-11 + // changing the "to" is require, because jabber use it to send the messgae. Cf BUG 111514 + Message msg2(cs->myself() , kcontact , msg.plainBody() , msg.direction() , Message::PlainText , msg.requestedPlugin() ); + kcontact->manager( Contact::CanCreate )->sendMessage( msg2 ); + } + } + } +} + +QPtrList Group::onlineMembers() const +{ + QPtrList list = members(); + + for( list.first(); list.current(); ) + if( list.current()->isReachable() && list.current()->isOnline() ) + list.next(); + else + list.remove(); + return list; +} + +} //END namespace Kopete + + +#include "kopetegroup.moc" + + + diff --git a/kopete/libkopete/kopetegroup.h b/kopete/libkopete/kopetegroup.h new file mode 100644 index 00000000..37b8572d --- /dev/null +++ b/kopete/libkopete/kopetegroup.h @@ -0,0 +1,187 @@ +/* + kopetegroup.h - Kopete (Meta)Contact Group + + Copyright (c) 2002-2004 by Olivier Goffart + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEGROUP_H +#define KOPETEGROUP_H + +#include "kopetenotifydataobject.h" +#include "kopetecontactlistelement.h" + +#include "kopete_export.h" + +#include + +class QDomElement; + + +namespace Kopete { + + +class MetaContact; +class Message; + +/** + * Class which represents the Group. + * + * A Group is a ConstacListElement which means plugin can save datas. + * + * some static group are availavle from this class: topLevel and temporary + * + * @author Olivier Goffart + */ +class KOPETE_EXPORT Group : public ContactListElement, public NotifyDataObject +{ + Q_PROPERTY( QString displayName READ displayName WRITE setDisplayName ) + Q_PROPERTY( uint groupId READ groupId ) + Q_PROPERTY( bool expanded READ isExpanded WRITE setExpanded ) + + Q_OBJECT + +public: + /** Kinds of groups. */ + enum GroupType { Normal=0, Temporary, TopLevel }; + + /** + * \brief Create an empty group + * + * Note that the constructor will not add the group automatically to the contact list. + * Use @ref ContactList::addGroup() to add it + */ + Group(); + + /** + * \brief Create a group of the specified type + * + * Overloaded constructor to create a group with a display name of the specified type. + */ + Group( const QString &displayName, GroupType type = Normal ); + + ~Group(); + + /** + * \brief Return the group's display name + * + * \return the display name of the group + */ + QString displayName() const; + + /** + * \brief Rename the group + */ + void setDisplayName( const QString &newName ); + + /** + * \return the group type + */ + GroupType type() const; + + /** + * \brief Set the group type + */ + void setType( GroupType newType ); + + /** + * \return the unique id for this group + */ + uint groupId() const; + + /** + * @brief child metacontact + * This function is not very efficient - it searches through all the metacontacts in the contact list + * \return the members of this group + */ + QPtrList members() const; + + /** + * \brief Set if the group is expanded. + * + * This is saved to the xml contactlist file + */ + void setExpanded( bool expanded ); + + /** + * + * \return true if the group is expanded. + * \return false otherwise + */ + bool isExpanded() const; + + /** + * \return a Group pointer to the toplevel group + */ + static Group *topLevel(); + + /** + * \return a Group pointer to the temporary group + */ + static Group *temporary(); + + + + /** + * @internal + * Outputs the group data in XML + */ + const QDomElement toXML(); + + + /** + * @internal + * Loads the group data from XML + */ + bool fromXML( const QDomElement &data ); + + + + +public slots: + /** + * Send a message to all contacts in the group + */ + void sendMessage(); + + +signals: + /** + * \brief Emitted when the group has been renamed + */ + void displayNameChanged( Kopete::Group *group , const QString &oldName ); + + +private slots: + void sendMessage( Kopete::Message& ); + +private: + static Group *s_topLevel; + static Group *s_temporary; + + class Private; + Private *d; + + + /** + * @internal used to get reachabe contact to send message to thom. + */ + QPtrList onlineMembers() const; +}; + +} //END namespace Kopete + +#endif + + diff --git a/kopete/libkopete/kopetemessage.cpp b/kopete/libkopete/kopetemessage.cpp new file mode 100644 index 00000000..54761799 --- /dev/null +++ b/kopete/libkopete/kopetemessage.cpp @@ -0,0 +1,641 @@ +/* + kopetemessage.cpp - Base class for Kopete messages + + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2006 by Olivier Goffart + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopetemessage.h" +#include "kopetemetacontact.h" +#include "kopeteprotocol.h" +#include "kopetechatsession.h" +#include "kopeteprefs.h" +#include "kopetecontact.h" +#include "kopeteemoticons.h" + + +using namespace Kopete; + +class Message::Private + : public KShared +{ +public: + Private( const QDateTime &timeStamp, const Contact *from, const ContactPtrList &to, + const QString &subject, MessageDirection direction, + const QString &requestedPlugin, MessageType type ); + + QGuardedPtr from; + ContactPtrList to; + ChatSession *manager; + + MessageDirection direction; + MessageFormat format; + MessageType type; + QString requestedPlugin; + MessageImportance importance; + bool bgOverride; + bool fgOverride; + bool rtfOverride; + bool isRightToLeft; + QDateTime timeStamp; + QFont font; + + QColor fgColor; + QColor bgColor; + QString body; + QString subject; +}; + +Message::Private::Private( const QDateTime &timeStamp, const Contact *from, + const ContactPtrList &to, const QString &subject, + MessageDirection direction, const QString &requestedPlugin, MessageType type ) +: from( from ), to( to ), manager( 0 ), direction( direction ), format( PlainText ), type( type ), + requestedPlugin( requestedPlugin ), importance( (to.count() <= 1) ? Normal : Low ), + bgOverride( false ), fgOverride( false ), rtfOverride( false ), isRightToLeft( false ), + timeStamp( timeStamp ), body( QString::null ), subject( subject ) +{ +} + +Message::Message() +: d( new Private( QDateTime::currentDateTime(), 0L, QPtrList(), QString::null, Internal, + QString::null, TypeNormal ) ) +{ +} + +Message::Message( const Contact *fromKC, const QPtrList &toKC, const QString &body, + MessageDirection direction, MessageFormat f, const QString &requestedPlugin, MessageType type ) +: d( new Private( QDateTime::currentDateTime(), fromKC, toKC, QString::null, direction, requestedPlugin, type ) ) +{ + doSetBody( body, f ); +} + +Message::Message( const Contact *fromKC, const Contact *toKC, const QString &body, + MessageDirection direction, MessageFormat f, const QString &requestedPlugin, MessageType type ) +{ + QPtrList to; + to.append(toKC); + d = new Private( QDateTime::currentDateTime(), fromKC, to, QString::null, direction, requestedPlugin, type ); + doSetBody( body, f ); +} + +Message::Message( const Contact *fromKC, const QPtrList &toKC, const QString &body, + const QString &subject, MessageDirection direction, MessageFormat f, const QString &requestedPlugin, MessageType type ) + : d( new Private( QDateTime::currentDateTime(), fromKC, toKC, subject, direction, requestedPlugin, type ) ) +{ + doSetBody( body, f ); +} + +Message::Message( const QDateTime &timeStamp, const Contact *fromKC, const QPtrList &toKC, + const QString &body, MessageDirection direction, MessageFormat f, const QString &requestedPlugin, MessageType type ) + : d( new Private( timeStamp, fromKC, toKC, QString::null, direction, requestedPlugin, type ) ) +{ + doSetBody( body, f ); +} + + +Message::Message( const QDateTime &timeStamp, const Contact *fromKC, const QPtrList &toKC, + const QString &body, const QString &subject, MessageDirection direction, MessageFormat f, const QString &requestedPlugin, MessageType type ) + : d( new Private( timeStamp, fromKC, toKC, subject, direction, requestedPlugin, type ) ) +{ + doSetBody( body, f ); +} + +Kopete::Message::Message( const Message &other ) + : d(other.d) +{ +} + + + +Message& Message::operator=( const Message &other ) +{ + d = other.d; + return *this; +} + +Message::~Message() +{ +} + +void Message::detach() +{ + // there is no detach in KSharedPtr :( + if( d.count() == 1 ) + return; + + // Warning: this only works as long as the private object doesn't contain pointers to allocated objects. + // The from contact for example is fine, but it's a shallow copy this way. + d = new Private(*d); +} + +void Message::setBgOverride( bool enabled ) +{ + detach(); + d->bgOverride = enabled; +} + +void Message::setFgOverride( bool enabled ) +{ + detach(); + d->fgOverride = enabled; +} + +void Message::setRtfOverride( bool enabled ) +{ + detach(); + d->rtfOverride = enabled; +} + +void Message::setFg( const QColor &color ) +{ + detach(); + d->fgColor=color; +} + +void Message::setBg( const QColor &color ) +{ + detach(); + d->bgColor=color; +} + +void Message::setFont( const QFont &font ) +{ + detach(); + d->font = font; +} + +void Message::doSetBody( const QString &_body, Message::MessageFormat f ) +{ + QString body = _body; + + //TODO: move that in ChatTextEditPart::contents + if( f == RichText ) + { + //This is coming from the RichTextEditor component. + //Strip off the containing HTML document + body.replace( QRegExp( QString::fromLatin1(".*]*>(.*).*") ), QString::fromLatin1("\\1") ); + + //Strip

    tags + body.replace( QString::fromLatin1("

    "), QString::null ); + + //Replace

    with a
    + body.replace( QString::fromLatin1("

    "), QString::fromLatin1("
    ") ); + + //Remove trailing
    + if ( body.endsWith( QString::fromLatin1("
    ") ) ) + body.truncate( body.length() - 5 ); + + body.remove( QString::fromLatin1("\n") ); + body.replace( QRegExp( QString::fromLatin1( "\\s\\s" ) ), QString::fromLatin1( "  " ) ); + } + /* + else if( f == ParsedHTML ) + { + kdWarning( 14000 ) << k_funcinfo << "using ParsedHTML which is internal! Message: '" << + body << "', Backtrace: " << kdBacktrace() << endl; + } + */ + + d->body = body; + d->format = f; + + // unescaping is very expensive, do it only once and cache the result + d->isRightToLeft = ( f & RichText ? unescape( d->body ).isRightToLeft() : d->body.isRightToLeft() ); +} + +void Message::setBody( const QString &body, MessageFormat f ) +{ + detach(); + + doSetBody( body, f ); +} + +bool Message::isRightToLeft() const +{ + return d->isRightToLeft; +} + +void Message::setImportance(Message::MessageImportance i) +{ + detach(); + d->importance = i; +} + +QString Message::unescape( const QString &xml ) +{ + QString data = xml; + + // Remove linebreak and multiple spaces. First return nbsp's to normal spaces :) + data.simplifyWhiteSpace(); + + int pos; + while ( ( pos = data.find( '<' ) ) != -1 ) + { + int endPos = data.find( '>', pos + 1 ); + if( endPos == -1 ) + break; // No more complete elements left + + // Take the part between < and >, and extract the element name from that + int matchWidth = endPos - pos + 1; + QString match = data.mid( pos + 1, matchWidth - 2 ).simplifyWhiteSpace(); + int elemEndPos = match.find( ' ' ); + QString elem = ( elemEndPos == -1 ? match.lower() : match.left( elemEndPos ).lower() ); + if ( elem == QString::fromLatin1( "img" ) ) + { + // Replace smileys with their original text' + const QString attrTitle = QString::fromLatin1( "title=\"" ); + int titlePos = match.find( attrTitle, elemEndPos ); + int titleEndPos = match.find( '"', titlePos + attrTitle.length() ); + if( titlePos == -1 || titleEndPos == -1 ) + { + // Not a smiley but a normal + // Don't update pos, we restart at this position :) + data.remove( pos, matchWidth ); + } + else + { + QString orig = match.mid( titlePos + attrTitle.length(), + titleEndPos - titlePos - attrTitle.length() ); + data.replace( pos, matchWidth, orig ); + pos += orig.length(); + } + } + else if ( elem == QString::fromLatin1( "/p" ) || elem == QString::fromLatin1( "/div" ) || + elem == QString::fromLatin1( "br" ) ) + { + // Replace paragraph, div and line breaks with a newline + data.replace( pos, matchWidth, '\n' ); + pos++; + } + else + { + // Remove all other elements entirely + // Don't update pos, we restart at this position :) + data.remove( pos, matchWidth ); + } + } + + // Replace stuff starting with '&' + data.replace( QString::fromLatin1( ">" ), QString::fromLatin1( ">" ) ); + data.replace( QString::fromLatin1( "<" ), QString::fromLatin1( "<" ) ); + data.replace( QString::fromLatin1( """ ), QString::fromLatin1( "\"" ) ); + data.replace( QString::fromLatin1( " " ), QString::fromLatin1( " " ) ); + data.replace( QString::fromLatin1( "&" ), QString::fromLatin1( "&" ) ); + data.replace( QString::fromLatin1( " " ), QString::fromLatin1( " " ) ); //this one is used in jabber: note, we should escape all &#xx; + + return data; +} + +QString Message::escape( const QString &text ) +{ + QString html = QStyleSheet::escape( text ); + //Replace carriage returns inside the text + html.replace( QString::fromLatin1( "\n" ), QString::fromLatin1( "
    " ) ); + //Replace a tab with 4 spaces + html.replace( QString::fromLatin1( "\t" ), QString::fromLatin1( "    " ) ); + + //Replace multiple spaces with   + //do not replace every space so we break the linebreak + html.replace( QRegExp( QString::fromLatin1( "\\s\\s" ) ), QString::fromLatin1( "  " ) ); + + return html; +} + + + +QString Message::plainBody() const +{ + QString body=d->body; + if( d->format & RichText ) + { + body = unescape( body ); + } + return body; +} + +QString Message::escapedBody() const +{ + QString escapedBody=d->body; +// kdDebug(14000) << k_funcinfo << escapedBody << " " << d->rtfOverride << endl; + + if( d->format & PlainText ) + { + escapedBody=escape( escapedBody ); + } + else if( d->format & RichText && d->rtfOverride) + { + //remove the rich text + escapedBody = escape (unescape( escapedBody ) ); + } + + return escapedBody; +} + +QString Message::parsedBody() const +{ + //kdDebug(14000) << k_funcinfo << "messageformat: " << d->format << endl; + + if( d->format == ParsedHTML ) + { + return d->body; + } + else + { + return Kopete::Emoticons::parseEmoticons(parseLinks(escapedBody(), RichText)); + } +} + +static QString makeRegExp( const char *pattern ) +{ + const QString urlChar = QString::fromLatin1( "\\+\\-\\w\\./#@&;:=\\?~%_,\\!\\$\\*\\(\\)" ); + const QString boundaryStart = QString::fromLatin1( "(^|[^%1])(" ).arg( urlChar ); + const QString boundaryEnd = QString::fromLatin1( ")([^%1]|$)" ).arg( urlChar ); + + return boundaryStart + QString::fromLatin1(pattern) + boundaryEnd; +} + +QString Message::parseLinks( const QString &message, MessageFormat format ) +{ + if ( format == ParsedHTML ) + return message; + + if ( format & RichText ) + { + // < in HTML *always* means start-of-tag + QStringList entries = QStringList::split( QChar('<'), message, true ); + + QStringList::Iterator it = entries.begin(); + + // first one is different: it doesn't start with an HTML tag. + if ( it != entries.end() ) + { + *it = parseLinks( *it, PlainText ); + ++it; + } + + for ( ; it != entries.end(); ++it ) + { + QString curr = *it; + // > in HTML means start-of-tag if and only if it's the first one after a < + int tagclose = curr.find( QChar('>') ); + // no >: the HTML is broken, but we can cope + if ( tagclose == -1 ) + continue; + QString tag = curr.left( tagclose + 1 ); + QString body = curr.mid( tagclose + 1 ); + *it = tag + parseLinks( body, PlainText ); + } + return entries.join(QString::fromLatin1("<")); + } + + QString result = message; + + // common subpatterns - may not contain matching parens! + const QString name = QString::fromLatin1( "[\\w\\+\\-=_\\.]+" ); + const QString userAndPassword = QString::fromLatin1( "(?:%1(?::%1)?\\@)" ).arg( name ); + const QString urlChar = QString::fromLatin1( "\\+\\-\\w\\./#@&;:=\\?~%_,\\!\\$\\*\\(\\)" ); + const QString urlSection = QString::fromLatin1( "[%1]+" ).arg( urlChar ); + const QString domain = QString::fromLatin1( "[\\-\\w_]+(?:\\.[\\-\\w_]+)+" ); + + //Replace http/https/ftp links: + // Replace (stuff)://[user:password@](linkstuff) with a link + result.replace( + QRegExp( makeRegExp("\\w+://%1?\\w%2").arg( userAndPassword, urlSection ) ), + QString::fromLatin1("\\1\\2\\3" ) ); + + // Replace www.X.Y(linkstuff) with a http: link + result.replace( + QRegExp( makeRegExp("%1?www\\.%2%3").arg( userAndPassword, domain, urlSection ) ), + QString::fromLatin1("\\1\\2\\3" ) ); + + //Replace Email Links + // Replace user@domain with a mailto: link + result.replace( + QRegExp( makeRegExp("%1@%2").arg( name, domain ) ), + QString::fromLatin1("\\1\\2\\3") ); + + //Workaround for Bug 85061: Highlighted URLs adds a ' ' after the URL itself + // the trailing   is included in the url. + result.replace( QRegExp( QString::fromLatin1("(timeStamp; +} + +const Contact *Message::from() const +{ + return d->from; +} + +QPtrList Message::to() const +{ + return d->to; +} + +Message::MessageType Message::type() const +{ + return d->type; +} + +QString Message::requestedPlugin() const +{ + return d->requestedPlugin; +} + +QColor Message::fg() const +{ + return d->fgColor; +} + +QColor Message::bg() const +{ + return d->bgColor; +} + +QFont Message::font() const +{ + //QDomElement bodyNode = d->xmlDoc.elementsByTagName( QString::fromLatin1("body") ).item(0).toElement(); + //return QFont( bodyNode.attribute( QString::fromLatin1("font") ), bodyNode.attribute( QString::fromLatin1("fontsize") ).toInt() ); + return d->font; +} + +QString Message::subject() const +{ + return d->subject; +} + +Message::MessageFormat Message::format() const +{ + return d->format; +} + +Message::MessageDirection Message::direction() const +{ + return d->direction; +} + +Message::MessageImportance Message::importance() const +{ + return d->importance; +} + +ChatSession *Message::manager() const +{ + return d->manager; +} + +void Message::setManager(ChatSession *kmm) +{ + detach(); + d->manager=kmm; +} + +QString Message::getHtmlStyleAttribute() const +{ + QString styleAttribute; + + styleAttribute = QString::fromUtf8("style=\""); + + // Affect foreground(color) and background color to message. + if( !d->fgOverride && d->fgColor.isValid() ) + { + styleAttribute += QString::fromUtf8("color: %1; ").arg(d->fgColor.name()); + } + if( !d->bgOverride && d->bgColor.isValid() ) + { + styleAttribute += QString::fromUtf8("background-color: %1; ").arg(d->bgColor.name()); + } + + // Affect font parameters. + if( !d->rtfOverride && d->font!=QFont() ) + { + QString fontstr; + if(!d->font.family().isNull()) + fontstr+=QString::fromLatin1("font-family: ")+d->font.family()+QString::fromLatin1("; "); + if(d->font.italic()) + fontstr+=QString::fromLatin1("font-style: italic; "); + if(d->font.strikeOut()) + fontstr+=QString::fromLatin1("text-decoration: line-through; "); + if(d->font.underline()) + fontstr+=QString::fromLatin1("text-decoration: underline; "); + if(d->font.bold()) + fontstr+=QString::fromLatin1("font-weight: bold;"); + + styleAttribute += fontstr; + } + + styleAttribute += QString::fromUtf8("\""); + + return styleAttribute; +} + +// KDE4: Move that to a utils class/namespace +QString Message::decodeString( const QCString &message, const QTextCodec *providedCodec, bool *success ) +{ + /* + Note to everyone. This function is not the most efficient, that is for sure. + However, it *is* the only way we can be guarenteed that a given string is + decoded properly. + */ + + if( success ) + *success = true; + + // Avoid heavy codec tests on empty message. + if( message.isEmpty() ) + return QString::fromAscii( message ); + + //Check first 128 chars + int charsToCheck = message.length(); + charsToCheck = 128 > charsToCheck ? charsToCheck : 128; + + //They are providing a possible codec. Check if it is valid + if( providedCodec && providedCodec->heuristicContentMatch( message, charsToCheck ) >= 0 ) + { + //All chars decodable. + return providedCodec->toUnicode( message ); + } + + //Check if it is UTF + if( KStringHandler::isUtf8(message) ) + { + //We have a UTF string almost for sure. At least we know it will be decoded. + return QString::fromUtf8( message ); + } + + //Try codecForContent - exact match + QTextCodec *testCodec = QTextCodec::codecForContent(message, charsToCheck); + if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= 0 ) + { + //All chars decodable. + return testCodec->toUnicode( message ); + } + + kdWarning(14000) << k_funcinfo << "Unable to decode string using provided codec(s), taking best guesses!" << endl; + if( success ) + *success = false; + + //We don't have any clues here. + + //Try local codec + testCodec = QTextCodec::codecForLocale(); + if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= 0 ) + { + //All chars decodable. + kdDebug(14000) << k_funcinfo << "Using locale's codec" << endl; + return testCodec->toUnicode( message ); + } + + //Try latin1 codec + testCodec = QTextCodec::codecForMib(4); + if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= 0 ) + { + //All chars decodable. + kdDebug(14000) << k_funcinfo << "Using latin1" << endl; + return testCodec->toUnicode( message ); + } + + kdDebug(14000) << k_funcinfo << "Using latin1 and cleaning string" << endl; + //No codec decoded. Just decode latin1, and clean out any junk. + QString result = QString::fromLatin1( message ); + const uint length = message.length(); + for( uint i = 0; i < length; ++i ) + { + if( !result[i].isPrint() ) + result[i] = '?'; + } + + return result; +} diff --git a/kopete/libkopete/kopetemessage.h b/kopete/libkopete/kopetemessage.h new file mode 100644 index 00000000..0737d2ae --- /dev/null +++ b/kopete/libkopete/kopetemessage.h @@ -0,0 +1,424 @@ +/* + kopetemessage.h - Base class for Kopete messages + + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2004 by Olivier Goffart + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __KOPETE_MESSAGE_H__ +#define __KOPETE_MESSAGE_H__ + +#include "kopetecontact.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kopete_export.h" + + +class QDateTime; + +namespace Kopete { + + + class ChatSession; +class Contact; + + +/** + * @author Martijn Klingens + * @author Olivier Goffart + * + * Message represents any kind of messages shown on a chat view. + * + * The message may be a simple plaintext string, or a Richtext HTML like message, + * this is indicated by the @ref format() flag. + * PlainText message can however have a color, or specific fonts with the flag + * @ref bg(), @ref fg(), @ref font() + * It is recommended to use these flags, even for RichText messages, so the user can disable + * custom colors in the chat window style. + */ +class KOPETE_EXPORT Message +{ +public: + /** + * Direction of a message. + * - Inbound: Message is from the chat partner + * - Outbound: Message sent by the user. + * - Internal: Messages which are not sent via the network. This is just a notification a plugin can show in a chat view + * - Action: For the /me command , like on irc + */ + enum MessageDirection { Inbound = 0, Outbound = 1, Internal= 2 }; + + /** + * Format of body + * - PlainText: Just a simple text, without any formatting. If it contains HTML tags then they will be simply shown in the chatview. + * - RichText: Text already HTML escaped and which can contains some tags. the string + * should be a valid (X)HTML string. + * Any HTML specific characters (\<, \>, \&, ...) are escaped to the equivalent HTML + * entity (\>, \<, ...) newlines are \
    and any other HTML tags will be interpreted. + * - ParsedHTML: only used by the chatview, this text is parsed and ready to + * show into the chatview, with Emoticons, and URLs + * - Crypted is used only by Jabber and the Cryptography plugin + */ + enum MessageFormat{ PlainText = 0x01 , RichText =0x02 , ParsedHTML = 0x04|RichText , Crypted = 0x08|PlainText}; + + /** + * Specifies the type of the message. + * Currently supported types are: + * - Normal: a message + * - Action: an IRC-style DESCRIBE action. + */ + enum MessageType { TypeNormal, TypeAction }; + + /** + * Specifies the type of notification that will be sent with this message + * - Low: almost no notifications. automatically used in groupChat + * - Normal: Default notification, for normal message + * - Highlight: Highlight notification, for most important messages, which require particular attentions. + */ + enum MessageImportance { Low = 0, Normal = 1, Highlight = 2 }; + + /** + * Constructs a new empty message + */ + Message(); + + /** + * Deref and clean private object if refcount == 0 + */ + ~Message(); + + /** + * Constructs a new message. See @ref setBody() to more information about the format + * @param fromKC The Contact that the message is coming from + * @param toKC List of Contacts the message is going to + * @param body Message body + * @param direction The direction of the message, Message::Inbound, Message::Outbound, Message::Internal + * @param format Format of the message + * @param requestedPlugin Requested view plugin for the message + * @param type Type of the message, see @ref MessageType + */ + Message( const Contact *fromKC, const QPtrList &toKC, const QString &body, + MessageDirection direction, MessageFormat format = PlainText, + const QString &requestedPlugin = QString::null, MessageType type = TypeNormal ); + + /** + * Constructs a new message. See @ref setBody() to more information about the format + * @param fromKC The Contact that the message is coming from + * @param toKC List of Contacts the message is going to + * @param body Message body + * @param direction The direction of the message, Message::Inbound, Message::Outbound, Message::Internal + * @param format Format of the message + * @param requestedPlugin Requested view plugin for the message + * @param type Type of the message, see @ref MessageType + */ + Message( const Contact *fromKC, const Contact *toKC, const QString &body, + MessageDirection direction, MessageFormat format = PlainText, + const QString &requestedPlugin = QString::null, MessageType type = TypeNormal ); + + /** + * Constructs a new message. See @ref setBody() to more information about the format + * @param fromKC The Contact that the message is coming from + * @param toKC List of Contacts the message is going to + * @param body Message body + * @param subject The subject of the message + * @param direction The direction of the message, Message::Inbound, Message::Outbound, Message::Internal + * @param format Format of the message + * @param requestedPlugin Requested view plugin for the message + * @param type Type of the message, see @ref MessageType + */ + Message( const Contact *fromKC, const QPtrList &toKC, const QString &body, + const QString &subject, MessageDirection direction, MessageFormat format = PlainText, + const QString &requestedPlugin = QString::null, MessageType type = TypeNormal ); + + /** + * Constructs a new message. See @ref setBody() to more information about the format + * @param timeStamp Timestamp for the message + * @param fromKC The Contact that the message is coming from + * @param toKC List of Contacts the message is going to + * @param body Message body + * @param direction The direction of the message, Message::Inbound, Message::Outbound, Message::Internal + * @param format Format of the message + * @param requestedPlugin Requested view plugin for the message + * @param type Type of the message, see @ref MessageType + */ + Message( const QDateTime &timeStamp, const Contact *fromKC, const QPtrList &toKC, + const QString &body, MessageDirection direction, MessageFormat format = PlainText, + const QString &requestedPlugin = QString::null, MessageType type = TypeNormal ); + + /** + * Constructs a new message. See @ref setBody() to more information about the format + * @param timeStamp Timestamp for the message + * @param fromKC The Contact that the message is coming from + * @param toKC List of Contacts the message is going to + * @param body Message body + * @param subject The subject of the message + * @param direction The direction of the message, Message::Inbound, Message::Outbound, Message::Internal + * @param format Format of the message + * @param requestedPlugin Requested view plugin for the message + * @param type Type of the message, see @ref MessageType + */ + Message( const QDateTime &timeStamp, const Contact *fromKC, const QPtrList &toKC, + const QString &body, const QString &subject, MessageDirection direction, + MessageFormat format = PlainText, const QString &requestedPlugin = QString::null, + MessageType type = TypeNormal ); + + /** + * Copy constructor. + * Just adds a reference, doesn't actually copy. + */ + Message( const Message &other ); + + /** + * Assignment operator + * Just like the copy constructor it just refs and doesn't copy. + */ + Message & operator=( const Message &other ); + + /** + * Accessor method for the timestamp of the message + * @return The message's timestamp + */ + QDateTime timestamp() const; + + /** + * Accessor method for the Contact that sent this message + * @return The Contact who sent this message + */ + const Contact * from() const; + + /** + * Accessor method for the Contacts that this message was sent to + * @return Pointer list of the Contacts this message was sent to + */ + QPtrList to() const; + + /** + * @return the @ref MessageType of this message + */ + MessageType type() const; + + /** + * @return the view plugin you would prefer to use to read this message. If + * null, Kopete will use the user's preferred plugin. + */ + QString requestedPlugin() const; + + /** + * Accessor method for the foreground color + * @return The message's foreground color + */ + QColor fg() const; + + /** + * Accessor method for the background color of the message + * @return The message's background color + */ + QColor bg() const; + + /** + * Accessor method for the font of the message + * @return The message's font + */ + QFont font() const; + + /** + * Accessor method for the subject of the message + * @return The message subject + */ + QString subject() const; + + /** + * Accessor method for the format of the message + * @return The message format + */ + MessageFormat format() const; + + /** + * Accessor method for the direction of the message + * @return The message direction + */ + MessageDirection direction() const; + + /** + * @brief Accessor method for the importance + * @return The message importance (low/normal/highlight) + */ + MessageImportance importance() const; + + /** + * @brief Set the importance. + * @see importance + * @param importance The message importance to set + */ + void setImportance(MessageImportance importance); + + /** + * Sets the foreground color for the message + * @param color The color + */ + void setFg( const QColor &color ); + + /** + * Sets the background color for the message + * @param color The color + */ + void setBg( const QColor &color ); + + /** + * Sets the font for the message + * @param font The font + */ + void setFont( const QFont &font ); + + /** + * @brief Sets the body of the message + * + * @param body The body + * @param format The format of the message, @see MessageFormat + */ + void setBody( const QString &body, MessageFormat format = PlainText ); + + /** + * Get the message body back as plain text + * @return The message body as plain text + */ + QString plainBody() const; + + /** + * Get the message body as escaped (X)HTML format. + * That means every HTML special char (\>, \<, \&, ...) is escaped to the HTML entity (\<, \>, ...) + * and newlines (\\n) are converted to \
    + * @return The message body as escaped text + */ + QString escapedBody() const; + + /** + * Get the message body as parsed HTML with Emoticons, and URL parsed + * this should be ready to be shown in the chatwindow. + * @return The HTML and Emoticon parsed message body + */ + QString parsedBody() const; + + /** + * Get the related message manager. + * If it is not set, returns 0L. + * + * The @ref ChatSession is only set if the message is already passed by the manager. + * We should trust this only in aboutToSend/aboutToReceive signals + */ + ChatSession *manager() const ; + + /** + * set the messagemanager for this message. + * should be only used by the manager itself + */ + void setManager(ChatSession *); + + /** + * Enables the use of a background for a message + * @param enable A flag to indicate if the background should be enabled or disabled. + */ + void setBgOverride( bool enable ); + + /** + * Enables the use of a foreground for a message + * @param enable A flag to indicate if the foreground should be enabled or disabled. + */ + void setFgOverride( bool enable ); + + /** + * Enables the use of a RTF formatting for a message + * @param enable A flag to indicate if the RTF formatting should be enabled or disabled. + */ + void setRtfOverride( bool enable ); + + /** + * Return HTML style attribute for this message. + * @return A string formatted like this: "style=attr" + */ + QString getHtmlStyleAttribute() const; + +public: /* static helpers */ + + /** + * Unescapes a string, removing XML entity references and returns a plain text. + * + * Note that this method is *VERY* expensive when called on rich text bodies, + * use with care! + * + * @param xml The string you want to unescape + */ + static QString unescape( const QString &xml ); + + /** + * Indicate whether the string is right-to-left (Arabic or Hebrew are bidi locales) + * or "normal" left-to-right. Calculating RTL on rich text is expensive, and + * isRightToLeft() therefore uses a cached value. + */ + bool isRightToLeft() const; + + /** + * @brief Transform a pleintext message to an html. + * it escape main entity like > < add some <br /> or &nbsp; + */ + static QString escape( const QString & ); + + + /** + * Helper function to decode a string. Whatever returned here is *nearly guarenteed* to + * be parseable by the XML engine. + * + * @param message The string you are trying to decode + * @param providedCodec A codec you want to try to decode with + * @param success Optional pointer to a bool you want updated on success. "Success" + * is defined as a successfull decoding using either UTF8 or the codec you + * provided. If a guess has to be taken, success will be false. + */ + static QString decodeString( const QCString &message, + const QTextCodec *providedCodec = 0L, bool *success = 0L ); + +private: + /** + * Message is implicitly shared. + * Detach the instance when modifying data. + */ + void detach(); + + /** + * Called internally by @ref setBody() and the constructor + * Basically @ref setBody() without detach + */ + void doSetBody( const QString &body, MessageFormat format = PlainText ); + + class Private; + KSharedPtr d; + + static QString parseLinks( const QString &message, MessageFormat format ); +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetemessageevent.cpp b/kopete/libkopete/kopetemessageevent.cpp new file mode 100644 index 00000000..fb129837 --- /dev/null +++ b/kopete/libkopete/kopetemessageevent.cpp @@ -0,0 +1,105 @@ +/* + kopetemessageevent.cpp - Kopete Message Event + + Copyright (c) 2003 by Olivier Goffart + Copyright (c) 2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002 by Hendrik vom Lehn + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include "kopetemessageevent.h" +#include "kopetemetacontact.h" +#include "kopetecontactlist.h" +#include "kopetecontact.h" +#include "kopeteprefs.h" + +namespace Kopete +{ + +class MessageEvent::Private +{ +public: + Kopete::Message message; + EventState state; +}; + +MessageEvent::MessageEvent( const Message& m, QObject *parent, const char *name ) + : QObject(parent,name), d( new Private ) +{ + d->message = m; + d->state = Nothing; + const Contact *c=m.from(); + if(c) + connect(c,SIGNAL(contactDestroyed( Kopete::Contact* )),this,SLOT(discard())); +} + +MessageEvent::~MessageEvent() +{ + kdDebug(14010) << k_funcinfo << endl; + emit done(this); + delete d; +} + +Kopete::Message MessageEvent::message() +{ + return d->message; +} + +void MessageEvent::setMessage( const Kopete::Message &message ) +{ + d->message = message; +} + +MessageEvent::EventState MessageEvent::state() +{ + return d->state; +} + +void MessageEvent::apply() +{ + kdDebug(14010) << k_funcinfo << endl; + d->state = Applied; + deleteLater(); +} + +void MessageEvent::ignore() +{ + // FIXME: this should be done by the contact list for itself. + if( d->message.from()->metaContact() && d->message.from()->metaContact()->isTemporary() && + KopetePrefs::prefs()->balloonNotifyIgnoreClosesChatView() ) + ContactList::self()->removeMetaContact( d->message.from()->metaContact() ); + d->state = Ignored; + deleteLater(); +} + +void MessageEvent::accept() +{ + emit accepted(this); +} + +void MessageEvent::discard() +{ + emit discarded(this); + delete this; +} + +} + +#include "kopetemessageevent.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetemessageevent.h b/kopete/libkopete/kopetemessageevent.h new file mode 100644 index 00000000..7beb1aa2 --- /dev/null +++ b/kopete/libkopete/kopetemessageevent.h @@ -0,0 +1,129 @@ +/* + kopetemessageevent.h - Kopete Message Event + + Copyright (c) 2003 by Olivier Goffart + Copyright (c) 2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002 by Hendrik vom Lehn + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEMESSAGEEVENT_H +#define KOPETEMESSAGEEVENT_H + +#include + +#include "kopetemessage.h" + +#include "kopete_export.h" + +namespace Kopete +{ + +/** + * @author Olivier Goffart + * @author Richard Smith + * + * Kopete::MessageEvent is used when a new messages arrives, it is + * caught by the UI. It contains just informations about + * the message, and a signal when it is terminated (i.e. + * the message is read + **/ +class KOPETE_EXPORT MessageEvent : public QObject +{ + Q_OBJECT + +public: + MessageEvent(const Kopete::Message& , QObject* parent=0L, const char *name=0L); + ~MessageEvent(); + + /** + * @return A copy of the message + */ + Kopete::Message message(); + + /** + * Sets the message contained in this event. + * @param message The new value for the message + */ + void setMessage( const Kopete::Message &message ); + + /** + * The state of the event. + * - @c Nothing means that the event has not been accepted or ignored + * - @c Applied if the event has been applied + * - @c Ignored if the event has been ignored + */ + enum EventState { Nothing , Applied , Ignored }; + + EventState state(); + +public slots: + /** + * @deprecated Use accept() instead to continue the processing of this event once the caller has moved to using MessageHandlers + * + * execute the event + */ + void apply(); + + /** + * @deprecated Use discard() instead to destroy this event once the caller has moved to using MessageHandlers + * + * ignore the event + */ + void ignore(); + + /** + * @brief Passes the event to the next handler + * + * Call this when you've finished processing this event + */ + void accept(); + + /** + * @brief Discards the event + * + * If this event should not be processed any further, this function + * should be called to discard it. + */ + void discard(); + +signals: + /** + * The event has been processed + */ + void done(Kopete::MessageEvent *); + + /** + * The event has been discarded. + * @param event The event sending the signal. + */ + void discarded(Kopete::MessageEvent *event); + + /** + * The event has been accepted by its current handler. + * @param event The event sending the signal. + */ + void accepted(Kopete::MessageEvent *event); + +private: + class Private; + Private *d; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetemessagehandler.cpp b/kopete/libkopete/kopetemessagehandler.cpp new file mode 100644 index 00000000..89628d4f --- /dev/null +++ b/kopete/libkopete/kopetemessagehandler.cpp @@ -0,0 +1,111 @@ +/* + kopetemessagefilter.cpp - Kopete Message Filtering + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetemessagehandler.h" +#include "kopetemessageevent.h" + +#include + +namespace Kopete +{ + +class MessageHandler::Private +{ +public: + Private() : next(0) {} + MessageHandler *next; +}; + +MessageHandler::MessageHandler() + : QObject( 0 ), d( new Private ) +{ +} + +MessageHandler::~MessageHandler() +{ + delete d; +} + +MessageHandler *MessageHandler::next() +{ + return d->next; +} + +void MessageHandler::setNext( MessageHandler *next ) +{ + d->next = next; +} + +int MessageHandler::capabilities() +{ + return d->next->capabilities(); +} + +void MessageHandler::handleMessageInternal( MessageEvent *event ) +{ + connect( event, SIGNAL( accepted(Kopete::MessageEvent*) ), this, SLOT( messageAccepted(Kopete::MessageEvent*) ) ); + handleMessage( event ); +} + +void MessageHandler::handleMessage( MessageEvent *event ) +{ + messageAccepted( event ); +} + +void MessageHandler::messageAccepted( MessageEvent *event ) +{ + disconnect( event, SIGNAL( accepted(Kopete::MessageEvent*) ), this, SLOT( messageAccepted(Kopete::MessageEvent*) ) ); + d->next->handleMessageInternal( event ); +} + + +class MessageHandlerFactory::Private +{ +public: + static FactoryList &factories(); +}; + +MessageHandlerFactory::FactoryList &MessageHandlerFactory::Private::factories() +{ + static KStaticDeleter deleter; + static FactoryList *list = 0; + if( !list ) + deleter.setObject( list, new FactoryList ); + return *list; +} + +MessageHandlerFactory::MessageHandlerFactory() + : d( new Private ) +{ + Private::factories().append(this); +} + +MessageHandlerFactory::~MessageHandlerFactory() +{ + Private::factories().remove( this ); + delete d; +} + +MessageHandlerFactory::FactoryList MessageHandlerFactory::messageHandlerFactories() +{ + return Private::factories(); +} + +} + +#include "kopetemessagehandler.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemessagehandler.h b/kopete/libkopete/kopetemessagehandler.h new file mode 100644 index 00000000..ba16184c --- /dev/null +++ b/kopete/libkopete/kopetemessagehandler.h @@ -0,0 +1,225 @@ +/* + kopetemessagehandler.h - Kopete Message Filtering + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEMESSAGEHANDLER_H +#define KOPETEMESSAGEHANDLER_H + +#include +//#include +#include "kopete_export.h" + +//FIXME: Message::MessageDirection could be moved into namespace Kopete +// to avoid this being included everywhere +#include "kopetemessage.h" + +#include + +namespace Kopete +{ + +class MessageEvent; +class ChatSession; + +/** + * @author Richard Smith + * + * An object which sits between the protocol and the chat window which + * intercepts and processes messages on their way through. + * + * This class implements Handler role in the Chain of Responsibility pattern. + * The Client role will be filled by the Kopete::MessageHandlerChain class. + */ +class KOPETE_EXPORT MessageHandler : public QObject +{ + Q_OBJECT +public: + MessageHandler(); + virtual ~MessageHandler() = 0; + + /** + * @return the next handler in the chain + */ + MessageHandler *next(); + // FIXME: remove? + void setNext( MessageHandler *next ); + + /** + * @brief Gets the rich-text capabilities of this message handling object + * + * The default implementation returns next()->capabilities(). + */ + virtual int capabilities(); + + /** + * @brief Performs any processing necessary on the message + * + * @param event The message event to process. Should not be null. + * + * Overriders of this handler @em must cause (possibly asynchronously) + * one of the following to happen: + * - @p event->discard() to be called + * - @p event->continue() to be called + * - this base class implementation to be called (equivalent to event->continue() but faster) + * + * The base class implementation passes the event on to the next + * handler in the chain. + * + * @note If you store @p event, be aware that it could be deleted at any time, and either + * connect to the its discarded(Kopete::MessageEvent*) signal or store it in a QGuardedPtr. + */ + virtual void handleMessage( MessageEvent *event ); + + /** @internal */ + void handleMessageInternal( MessageEvent *event ); +private slots: + /** + * @internal The message has been accepted. Pass it on to the next handler. + */ + void messageAccepted( Kopete::MessageEvent *event ); + +private: + class Private; + Private *d; +}; + +/** + * @author Richard Smith + * + * A factory for creating MessageHandlers. Instantiate a class derived from MessageHandlerFactory + * in order to make your MessageHandler be automatically added to the list of handlers used + * when constructing handler chains. + * + * @note If you construct a handler for an Inbound chain, it may still be asked to process Outbound + * messages. This is because when a message is being sent it first passes through the Outbound + * chain to the protocol, then (when it has been delivered) it passes back through the Inbound + * chain to the chat window to be displayed. + */ +class KOPETE_EXPORT MessageHandlerFactory +{ +public: + /** + * Constructs a MessageHandlerFactory, and adds it to the list of factories considered when + * creating a MessageHandlerChain for a ChatSession. + * + * @note Since the factory is added to the list of possible factories before the object is + * finished being constructed, it is not safe to call any function from a derived class's + * constructor which may cause a MessageHandlerChain to be created. + */ + MessageHandlerFactory(); + /** + * Destroys the MessageHandlerFactory and removes it from the list of factories. + */ + virtual ~MessageHandlerFactory(); + + typedef QValueList FactoryList; + /** + * @return the list of registered message handler factories + */ + static FactoryList messageHandlerFactories(); + + /** + * @brief Creates a message handler for a given manager in a given direction. + * @param manager The manager whose message handler chain the message handler is for + * @param direction The direction of the chain that is being created. + * @return the @ref MessageHandler object to put in the chain, or 0 if none is needed. + */ + virtual MessageHandler *create( ChatSession *manager, Message::MessageDirection direction ) = 0; + + /** + * Special stages usable with any message direction + */ + enum SpecialStage + { + StageDoNotCreate = -10000, ///< do not create a filter for this stage + StageStart = 0, ///< start of processing + StageEnd = 10000 ///< end of processing + }; + + /** + * Processing stages for handlers in inbound message handler chains + */ + enum InboundStage + { + InStageStart = 0, ///< message was just received + InStageToSent = 2000, ///< convert from received format to sent format + InStageToDesired = 5000, ///< convert to how the user wants the message + InStageFormat = 7000, ///< decorate the message without changing the content + InStageEnd = 10000 ///< message ready for display + }; + + /** + * Processing stages for handlers in outbound message handler chains + */ + enum OutboundStage + { + OutStageStart = 0, ///< user just hit Send + OutStageParse = 2000, ///< process commands + OutStageToDesired = 4000, ///< convert to how the user wanted to send + OutStageFormat = 6000, ///< decorate the message without changing the content + OutStageToSent = 8000, ///< convert to the format to send in + OutStageEnd = 10000 ///< message ready for sending + }; + + /** + * Processing stages for handlers in internal message handler chains + */ + enum InternalStage + { + IntStageStart = 0, ///< some component just created the message + IntStageEnd = 10000 ///< message ready for display + }; + + /** + * Offsets within a processing stage. Using these values allows finer + * control over where in a chain a message handler will be added. Add + * one of these values to values from the various Stage enumerations + * to form a filter position. + */ + enum Offset + { + OffsetBefore = -90, + OffsetVeryEarly = -60, + OffsetEarly = -30, + OffsetNormal = 0, + OffsetLate = 30, + OffsetVeryLate = 60, + OffsetAfter = 90 + }; + + /** + * @brief Returns the position in the message handler chain to put this factory's handlers + * @param manager The manager whose message handler chain the message handler is for + * @param direction The direction of the chain that is being created. + * @return a member of the InboundStage, OutboundStage or InternalStage enumeration, as + * appropriate, optionally combined with a member of the Offset enumeration. + * @retval StageDoNotCreate No filter should be created for this chain. + */ + virtual int filterPosition( ChatSession *manager, Message::MessageDirection direction ) = 0; + +private: + // noncopyable + MessageHandlerFactory(const MessageHandlerFactory &); + void operator=(const MessageHandlerFactory &); + + class Private; + Private *d; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemessagehandlerchain.cpp b/kopete/libkopete/kopetemessagehandlerchain.cpp new file mode 100644 index 00000000..fe1e96ab --- /dev/null +++ b/kopete/libkopete/kopetemessagehandlerchain.cpp @@ -0,0 +1,186 @@ +/* + kopetemessagehandlerchain.h - Kopete Message Handler Chain + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetemessagehandlerchain.h" +#include "kopetemessagehandler.h" +#include "kopetemessageevent.h" +#include "kopetechatsession.h" + +#include + +#include +#include +#include + +namespace Kopete +{ + +class MessageHandlerChainTerminator : public MessageHandler +{ +public: + void handleMessage( MessageEvent *event ) + { + kdError( 14010 ) << k_funcinfo << "message got to end of chain!" << endl; + event->discard(); + } + int capabilities() + { + kdError( 14010 ) << k_funcinfo << "request got to end of chain!" << endl; + return 0; + } +}; + +// BEGIN MessageHandlerChain + +class MessageHandlerChain::Private +{ +public: + Private() : first(0) {} + MessageHandler *first; +}; + +MessageHandlerChain::Ptr MessageHandlerChain::create( ChatSession *manager, Message::MessageDirection direction ) +{ + // create the handler chain + MessageHandlerChain *chain = new MessageHandlerChain; + + // grab the list of handler factories + typedef MessageHandlerFactory::FactoryList FactoryList; + FactoryList factories = MessageHandlerFactory::messageHandlerFactories(); + + // create a sorted list of handlers + typedef QValueList HandlerList; + typedef QMap HandlerMap; + HandlerMap handlers; + uint count = 0; + for( FactoryList::Iterator it = factories.begin(); it != factories.end(); ++it ) + { + int position = (*it)->filterPosition( manager, direction ); + if ( position == MessageHandlerFactory::StageDoNotCreate ) + continue; + MessageHandler *handler = (*it)->create( manager, direction ); + if ( handler ) + { + ++count; + handlers[ position ].append( handler ); + } + } + + kdDebug(14010) << k_funcinfo << "got " << count << " handlers for chain" << endl; + + // add the handlers to the chain + MessageHandler *curr = 0; + for( HandlerMap::Iterator it = handlers.begin(); it != handlers.end(); ++it ) + { + for ( HandlerList::Iterator handlerIt = (*it).begin(); handlerIt != (*it).end(); ++handlerIt ) + { + if ( curr ) + curr->setNext( *handlerIt ); + else + chain->d->first = *handlerIt; + curr = *handlerIt; + } + } + + // add a terminator to avoid crashes if the message somehow manages to get to the + // end of the chain. maybe we should use a MessageHandlerFactory for this too? + MessageHandler *terminator = new MessageHandlerChainTerminator; + if ( curr ) + curr->setNext( terminator ); + else // empty chain: might happen for dir == Internal + chain->d->first = terminator; + + return chain; +} + +MessageHandlerChain::MessageHandlerChain() + : QObject( 0 ), d( new Private ) +{ +} + +MessageHandlerChain::~MessageHandlerChain() +{ + kdDebug(14010) << k_funcinfo << endl; + MessageHandler *handler = d->first; + while( handler ) + { + MessageHandler *next = handler->next(); + delete handler; + handler = next; + } + delete d; +} + + +ProcessMessageTask *MessageHandlerChain::processMessage( const Message &message ) +{ + MessageEvent *event = new MessageEvent( message ); + return new ProcessMessageTask( this, event ); +} + +int MessageHandlerChain::capabilities() +{ + return d->first->capabilities(); +} + +// END MessageHandlerChain + +// BEGIN ProcessMessageTask + +class ProcessMessageTask::Private +{ +public: + Private( MessageHandlerChain::Ptr chain, MessageEvent *event ) : chain(chain), event(event) {} + MessageHandlerChain::Ptr chain; + MessageEvent *event; +}; + +ProcessMessageTask::ProcessMessageTask( MessageHandlerChain::Ptr chain, MessageEvent *event ) + : d( new Private(chain, event) ) +{ + QTimer::singleShot( 0, this, SLOT( slotStart() ) ); + connect( event, SIGNAL( done( Kopete::MessageEvent* ) ), this, SLOT( slotDone() ) ); + event->message().manager()->ref(); +} + +ProcessMessageTask::~ProcessMessageTask() +{ + delete d; +} + +void ProcessMessageTask::slotStart() +{ + d->chain->d->first->handleMessageInternal( d->event ); +} + +void ProcessMessageTask::slotDone() +{ + d->event->message().manager()->deref(); + emitResult(); +} + +MessageEvent *ProcessMessageTask::event() +{ + return d->event; +} + +//END ProcessMessageTask + +} + +#include "kopetemessagehandlerchain.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemessagehandlerchain.h b/kopete/libkopete/kopetemessagehandlerchain.h new file mode 100644 index 00000000..5852c2da --- /dev/null +++ b/kopete/libkopete/kopetemessagehandlerchain.h @@ -0,0 +1,96 @@ +/* + kopetefilterchain.h - Kopete Message Filter Chain + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEFILTERCHAIN_H +#define KOPETEFILTERCHAIN_H + +#include +#include +#include +#include "kopetemessage.h" +#include "kopetetask.h" + +namespace Kopete +{ + +class MessageEvent; +class MessageHandler; +class ProcessMessageTask; + +/** + * @brief A chain of message handlers; the processing layer between protocol and chat view + * + * This class represents a chain of connected message handlers. + * + * This class is the client of the chain of responsibility formed by the + * MessageHandlers, and acts as a facade for that chain, presenting a + * more convenient interface. + * + * @author Richard Smith + */ +class MessageHandlerChain : public QObject, private KShared +{ + Q_OBJECT +public: + friend class KSharedPtr; + typedef KSharedPtr Ptr; + + /** + * Create a new MessageHandlerChain object with the appropriate handlers for + * processing messages entering @p manager in direction @p direction. + */ + static Ptr create( ChatSession *manager, Message::MessageDirection direction ); + + ProcessMessageTask *processMessage( const Message &message ); + int capabilities(); + +private: + MessageHandlerChain(); + ~MessageHandlerChain(); + + friend class ProcessMessageTask; + class Private; + Private *d; +}; + +/** + * @brief A task for processing a message + * @author Richard Smith + */ +class ProcessMessageTask : public Task +{ + Q_OBJECT +public: + MessageEvent *event(); + +private slots: + void slotStart(); + void slotDone(); + +private: + ProcessMessageTask(MessageHandlerChain::Ptr, MessageEvent *event); + ~ProcessMessageTask(); + + friend class MessageHandlerChain; + class Private; + Private *d; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemessagemanager.h b/kopete/libkopete/kopetemessagemanager.h new file mode 100644 index 00000000..a07fb6f9 --- /dev/null +++ b/kopete/libkopete/kopetemessagemanager.h @@ -0,0 +1,3 @@ +#warning kopetemessagemanager.h has been renamed to kopetechatsession.h +#include "kopetechatsession.h" + diff --git a/kopete/libkopete/kopetemessagemanagerfactory.h b/kopete/libkopete/kopetemessagemanagerfactory.h new file mode 100644 index 00000000..9ebdaa98 --- /dev/null +++ b/kopete/libkopete/kopetemessagemanagerfactory.h @@ -0,0 +1,3 @@ +#warning kopetemessagemanagerfactory.h has been renamed to kopetechatsessionmanager.h +#include "kopetechatsessionmanager.h" + diff --git a/kopete/libkopete/kopetemetacontact.cpp b/kopete/libkopete/kopetemetacontact.cpp new file mode 100644 index 00000000..e181f52e --- /dev/null +++ b/kopete/libkopete/kopetemetacontact.cpp @@ -0,0 +1,1442 @@ +/* + kopetemetacontact.cpp - Kopete Meta Contact + + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2005 by Olivier Goffart + Copyright (c) 2002-2004 by Duncan Mac-Vicar Prett + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetemetacontact.h" + +#include + +#include +#include + +#include +#include +#include +#include + +#include "kabcpersistence.h" +#include "kopetecontactlist.h" +#include "kopetecontact.h" +#include "kopeteaccountmanager.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopetepluginmanager.h" +#include "kopetegroup.h" +#include "kopeteglobal.h" +#include "kopeteprefs.h" +#include "kopeteuiglobal.h" +#include "kopetepicture.h" + +namespace Kopete { + +// this is just to save typing +const QString NSCID_ELEM = QString::fromUtf8("nameSourceContactId" ); +const QString NSPID_ELEM = QString::fromUtf8( "nameSourcePluginId" ); +const QString NSAID_ELEM = QString::fromUtf8( "nameSourceAccountId" ); +const QString PSCID_ELEM = QString::fromUtf8( "photoSourceContactId" ); +const QString PSPID_ELEM = QString::fromUtf8( "photoSourcePluginId" ); +const QString PSAID_ELEM = QString::fromUtf8( "photoSourceAccountId" ); + +class MetaContact::Private +{ public: + Private() : + photoSource(MetaContact::SourceCustom), displayNameSource(MetaContact::SourceCustom), + displayNameSourceContact(0L), photoSourceContact(0L), temporary(false), + onlineStatus(Kopete::OnlineStatus::Offline), photoSyncedWithKABC(false) + {} + + ~Private() + {} + + QPtrList contacts; + + // property sources + PropertySource photoSource; + PropertySource displayNameSource; + + // when source is contact + Contact *displayNameSourceContact; + Contact *photoSourceContact; + + // used when source is kabc + QString metaContactId; + + // used when source is custom + QString displayName; + KURL photoUrl; + + QPtrList groups; + QMap > addressBook; + bool temporary; + + OnlineStatus::StatusType onlineStatus; + bool photoSyncedWithKABC; + + // Used to set contact source at load. + QString nameSourcePID, nameSourceAID, nameSourceCID; + QString photoSourcePID, photoSourceAID, photoSourceCID; + + // The photo cache. Reduce disk access and CPU usage. + Picture customPicture, contactPicture, kabcPicture; +}; + +MetaContact::MetaContact() + : ContactListElement( ContactList::self() ) +{ + d = new Private; + + connect( this, SIGNAL( pluginDataChanged() ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( iconChanged( Kopete::ContactListElement::IconState, const QString & ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( useCustomIconChanged( bool ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( displayNameChanged( const QString &, const QString & ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( movedToGroup( Kopete::MetaContact *, Kopete::Group *, Kopete::Group * ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( removedFromGroup( Kopete::MetaContact *, Kopete::Group * ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( addedToGroup( Kopete::MetaContact *, Kopete::Group * ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( contactAdded( Kopete::Contact * ) ), SIGNAL( persistentDataChanged() ) ); + connect( this, SIGNAL( contactRemoved( Kopete::Contact * ) ), SIGNAL( persistentDataChanged() ) ); + + // Update the KABC picture when the KDE Address book change. + connect(KABCPersistence::self()->addressBook(), SIGNAL(addressBookChanged(AddressBook *)), this, SLOT(slotUpdateAddressBookPicture())); + + // make sure MetaContact is at least in one group + addToGroup( Group::topLevel() ); + //i'm not sure this is correct -Olivier + // we probably should do the check in groups() instead +} + +MetaContact::~MetaContact() +{ + delete d; +} + +void MetaContact::addContact( Contact *c ) +{ + if( d->contacts.contains( c ) ) + { + kdWarning(14010) << "Ignoring attempt to add duplicate contact " << c->contactId() << "!" << endl; + } + else + { + d->contacts.append( c ); + + connect( c, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + SLOT( slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + + connect( c, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), + this, SLOT( slotPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ) ) ; + + connect( c, SIGNAL( contactDestroyed( Kopete::Contact * ) ), + this, SLOT( slotContactDestroyed( Kopete::Contact * ) ) ); + + connect( c, SIGNAL( idleStateChanged( Kopete::Contact * ) ), + this, SIGNAL( contactIdleStateChanged( Kopete::Contact * ) ) ); + + emit contactAdded(c); + + updateOnlineStatus(); + + // if this is the first contact, probbaly was created by a protocol + // so it has empty custom properties, then set sources to the contact + if ( d->contacts.count() == 1 ) + { + if ( displayName().isEmpty() ) + { + setDisplayNameSourceContact(c); + setDisplayNameSource(SourceContact); + } + if ( photo().isNull() ) + { + setPhotoSourceContact(c); + setPhotoSource(SourceContact); + } + } + } +} + +void MetaContact::updateOnlineStatus() +{ + Kopete::OnlineStatus::StatusType newStatus = Kopete::OnlineStatus::Unknown; + Kopete::OnlineStatus mostSignificantStatus; + + for ( QPtrListIterator it( d->contacts ); it.current(); ++it ) + { + // find most significant status + if ( it.current()->onlineStatus() > mostSignificantStatus ) + mostSignificantStatus = it.current()->onlineStatus(); + } + + newStatus = mostSignificantStatus.status(); + + if( newStatus != d->onlineStatus ) + { + d->onlineStatus = newStatus; + emit onlineStatusChanged( this, d->onlineStatus ); + } +} + + +void MetaContact::removeContact(Contact *c, bool deleted) +{ + if( !d->contacts.contains( c ) ) + { + kdDebug(14010) << k_funcinfo << " Contact is not in this metaContact " << endl; + } + else + { + // must check before removing, or will always be false + bool wasTrackingName = ( !displayNameSourceContact() && (displayNameSource() == SourceContact) ); + bool wasTrackingPhoto = ( !photoSourceContact() && (photoSource() == SourceContact) ); + // save for later use + QString currDisplayName = displayName(); + + d->contacts.remove( c ); + + // if the contact was a source of property data, clean + if (displayNameSourceContact() == c) + setDisplayNameSourceContact(0L); + if (photoSourceContact() == c) + setPhotoSourceContact(0L); + + + if ( wasTrackingName ) + { + // Oh! this contact was the source for the metacontact's name + // lets do something + // is this the only contact? + if ( d->contacts.isEmpty() ) + { + // fallback to a custom name as we don't have + // more contacts to chose as source. + setDisplayNameSource(SourceCustom); + // perhaps the custom display name was empty + // no problems baby, I saved the old one. + setDisplayName(currDisplayName); + } + else + { + // we didn't fallback to SourceCustom above so lets use the next + // contact as source + setDisplayNameSourceContact( d->contacts.first() ); + } + } + + if ( wasTrackingPhoto ) + { + // Oh! this contact was the source for the metacontact's photo + // lets do something + // is this the only contact? + if ( d->contacts.isEmpty() ) + { + // fallback to a custom photo as we don't have + // more contacts to chose as source. + setPhotoSource(SourceCustom); + // FIXME set the custom photo + } + else + { + // we didn't fallback to SourceCustom above so lets use the next + // contact as source + setPhotoSourceContact( d->contacts.first() ); + } + } + + if(!deleted) + { //If this function is tell by slotContactRemoved, c is maybe just a QObject + disconnect( c, SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + disconnect( c, SIGNAL( propertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ), + this, SLOT( slotPropertyChanged( Kopete::Contact *, const QString &, const QVariant &, const QVariant & ) ) ) ; + disconnect( c, SIGNAL( contactDestroyed( Kopete::Contact * ) ), + this, SLOT( slotContactDestroyed( Kopete::Contact * ) ) ); + disconnect( c, SIGNAL( idleStateChanged( Kopete::Contact * ) ), + this, SIGNAL( contactIdleStateChanged( Kopete::Contact *) ) ); + + kdDebug( 14010 ) << k_funcinfo << "Contact disconnected" << endl; + + KABCPersistence::self()->write( this ); + } + + // Reparent the contact + removeChild( c ); + + emit contactRemoved( c ); + } + updateOnlineStatus(); +} + +Contact *MetaContact::findContact( const QString &protocolId, const QString &accountId, const QString &contactId ) +{ + //kdDebug( 14010 ) << k_funcinfo << "Num contacts: " << d->contacts.count() << endl; + QPtrListIterator it( d->contacts ); + for( ; it.current(); ++it ) + { + //kdDebug( 14010 ) << k_funcinfo << "Trying " << it.current()->contactId() << ", proto " + //<< it.current()->protocol()->pluginId() << ", account " << it.current()->accountId() << endl; + if( ( it.current()->contactId() == contactId ) && ( it.current()->protocol()->pluginId() == protocolId || protocolId.isNull() ) ) + { + if ( accountId.isNull() ) + return it.current(); + + if(it.current()->account()) + { + if(it.current()->account()->accountId() == accountId) + return it.current(); + } + } + } + + // Contact not found + return 0L; +} + +void MetaContact::setDisplayNameSource(PropertySource source) +{ + QString oldName = displayName(); + d->displayNameSource = source; + QString newName = displayName(); + if ( oldName != newName) + emit displayNameChanged( oldName, newName ); +} + +MetaContact::PropertySource MetaContact::displayNameSource() const +{ + return d->displayNameSource; +} + +void MetaContact::setPhotoSource(PropertySource source) +{ + PropertySource oldSource = photoSource(); + d->photoSource = source; + if ( source != oldSource ) + { + emit photoChanged(); + } +} + +MetaContact::PropertySource MetaContact::photoSource() const +{ + return d->photoSource; +} + + +Contact *MetaContact::sendMessage() +{ + Contact *c = preferredContact(); + + if( !c ) + { + KMessageBox::queuedMessageBox( UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "This user is not reachable at the moment. Please make sure you are connected and using a protocol that supports offline sending, or wait " + "until this user comes online." ), i18n( "User is Not Reachable" ) ); + } + else + { + c->sendMessage(); + return c; + } + return 0L; +} + +Contact *MetaContact::startChat() +{ + Contact *c = preferredContact(); + + if( !c ) + { + KMessageBox::queuedMessageBox( UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "This user is not reachable at the moment. Please make sure you are connected and using a protocol that supports offline sending, or wait " + "until this user comes online." ), i18n( "User is Not Reachable" ) ); + } + else + { + c->startChat(); + return c; + } + return 0L; +} + +Contact *MetaContact::preferredContact() +{ + /* + This function will determine what contact will be used to reach the contact. + + The prefered contact is choose with the following criterias: (in that order) + 1) If a contact was an open chatwindow already, we will use that one. + 2) The contact with the better online status is used. But if that + contact is not reachable, we prefer return no contact. + 3) If all the criterias aboxe still gives ex-eaquo, we use the preffered + account as selected in the account preferances (with the arrows) + */ + + Contact *contact = 0; + bool hasOpenView=false; //has the selected contact already an open chatwindow + for ( QPtrListIterator it( d->contacts ); it.current(); ++it ) + { + Contact *c=it.current(); + + //Does the contact an open chatwindow? + if( c->manager( Contact::CannotCreate ) ) + { //no need to check the view. having a manager is enough + if( !hasOpenView ) + { + contact=c; + hasOpenView=true; + if( c->isReachable() ) + continue; + } //else, several contact might have an open view, uses following criterias + } + else if( hasOpenView && contact->isReachable() ) + continue; //This contact has not open view, but the selected contact has, and is reachable + + // FIXME: The isConnected call should be handled in Contact::isReachable + // after KDE 3.2 - Martijn + if ( !c->account() || !c->account()->isConnected() || !c->isReachable() ) + continue; //if this contact is not reachable, we ignore it. + + if ( !contact ) + { //this is the first contact. + contact= c; + continue; + } + + if( c->onlineStatus().status() > contact->onlineStatus().status() ) + contact=c; //this contact has a better status + else if ( c->onlineStatus().status() == contact->onlineStatus().status() ) + { + if( c->account()->priority() > contact->account()->priority() ) + contact=c; + else if( c->account()->priority() == contact->account()->priority() + && c->onlineStatus().weight() > contact->onlineStatus().weight() ) + contact = c; //the weight is not supposed to follow the same scale for each protocol + } + } + return contact; +} + +Contact *MetaContact::execute() +{ + Contact *c = preferredContact(); + + if( !c ) + { + KMessageBox::queuedMessageBox( UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "This user is not reachable at the moment. Please make sure you are connected and using a protocol that supports offline sending, or wait " + "until this user comes online." ), i18n( "User is Not Reachable" ) ); + } + else + { + c->execute(); + return c; + } + + return 0L; +} + +unsigned long int MetaContact::idleTime() const +{ + unsigned long int time = 0; + QPtrListIterator it( d->contacts ); + for( ; it.current(); ++it ) + { + unsigned long int i = it.current()->idleTime(); + if( it.current()->isOnline() && i < time || time == 0 ) + { + time = i; + } + } + return time; +} + +QString MetaContact::statusIcon() const +{ + switch( status() ) + { + case OnlineStatus::Online: + if( useCustomIcon() ) + return icon( ContactListElement::Online ); + else + return QString::fromUtf8( "metacontact_online" ); + case OnlineStatus::Away: + if( useCustomIcon() ) + return icon( ContactListElement::Away ); + else + return QString::fromUtf8( "metacontact_away" ); + + case OnlineStatus::Unknown: + if( useCustomIcon() ) + return icon( ContactListElement::Unknown ); + if ( d->contacts.isEmpty() ) + return QString::fromUtf8( "metacontact_unknown" ); + else + return QString::fromUtf8( "metacontact_offline" ); + + case OnlineStatus::Offline: + default: + if( useCustomIcon() ) + return icon( ContactListElement::Offline ); + else + return QString::fromUtf8( "metacontact_offline" ); + } +} + +QString MetaContact::statusString() const +{ + switch( status() ) + { + case OnlineStatus::Online: + return i18n( "Online" ); + case OnlineStatus::Away: + return i18n( "Away" ); + case OnlineStatus::Offline: + return i18n( "Offline" ); + case OnlineStatus::Unknown: + default: + return i18n( "Status not available" ); + } +} + +OnlineStatus::StatusType MetaContact::status() const +{ + return d->onlineStatus; +} + +bool MetaContact::isOnline() const +{ + QPtrListIterator it( d->contacts ); + for( ; it.current(); ++it ) + { + if( it.current()->isOnline() ) + return true; + } + return false; +} + +bool MetaContact::isReachable() const +{ + if ( isOnline() ) + return true; + + for ( QPtrListIterator it( d->contacts ); it.current(); ++it ) + { + if ( it.current()->account()->isConnected() && it.current()->isReachable() ) + return true; + } + return false; +} + +//Determine if we are capable of accepting file transfers +bool MetaContact::canAcceptFiles() const +{ + if( !isOnline() ) + return false; + + QPtrListIterator it( d->contacts ); + for( ; it.current(); ++it ) + { + if( it.current()->canAcceptFiles() ) + return true; + } + return false; +} + +//Slot for sending files +void MetaContact::sendFile( const KURL &sourceURL, const QString &altFileName, unsigned long fileSize ) +{ + //If we can't send any files then exit + if( d->contacts.isEmpty() || !canAcceptFiles() ) + return; + + //Find the highest ranked protocol that can accept files + Contact *contact = d->contacts.first(); + for( QPtrListIterator it( d->contacts ) ; it.current(); ++it ) + { + if( ( *it )->onlineStatus() > contact->onlineStatus() && ( *it )->canAcceptFiles() ) + contact = *it; + } + + //Call the sendFile slot of this protocol + contact->sendFile( sourceURL, altFileName, fileSize ); +} + + +void MetaContact::slotContactStatusChanged( Contact * c, const OnlineStatus &status, const OnlineStatus &/*oldstatus*/ ) +{ + updateOnlineStatus(); + emit contactStatusChanged( c, status ); +} + +void MetaContact::setDisplayName( const QString &name ) +{ + /*kdDebug( 14010 ) << k_funcinfo << "Change displayName from " << d->displayName << + " to " << name << ", d->trackChildNameChanges=" << d->trackChildNameChanges << endl; + kdDebug(14010) << kdBacktrace(6) << endl;*/ + + if( name == d->displayName ) + return; + + const QString old = d->displayName; + d->displayName = name; + + emit displayNameChanged( old , name ); + + for( QPtrListIterator it( d->contacts ) ; it.current(); ++it ) + ( *it )->sync(Contact::DisplayNameChanged); + +} + +QString MetaContact::customDisplayName() const +{ + return d->displayName; +} + +QString MetaContact::displayName() const +{ + PropertySource source = displayNameSource(); + if ( source == SourceKABC ) + { + // kabc source, try to get from addressbook + // if the metacontact has a kabc association + if ( !metaContactId().isEmpty() ) + return nameFromKABC(metaContactId()); + } + else if ( source == SourceContact ) + { + if ( d->displayNameSourceContact==0 ) + { + if( d->contacts.count() >= 1 ) + {// don't call setDisplayNameSource , or there will probably be an infinite loop + d->displayNameSourceContact=d->contacts.first(); +// kdDebug( 14010 ) << k_funcinfo << " setting displayname source for " << metaContactId() << endl; + } + } + if ( displayNameSourceContact() != 0L ) + { + return nameFromContact(displayNameSourceContact()); + } + else + { +// kdDebug( 14010 ) << k_funcinfo << " source == SourceContact , but there is no displayNameSourceContact for contact " << metaContactId() << endl; + } + } + return d->displayName; +} + +QString nameFromKABC( const QString &id ) /*const*/ +{ + KABC::AddressBook* ab = KABCPersistence::self()->addressBook(); + if ( ! id.isEmpty() && !id.contains(':') ) + { + KABC::Addressee theAddressee = ab->findByUid(id); + if ( theAddressee.isEmpty() ) + { + kdDebug( 14010 ) << k_funcinfo << "no KABC::Addressee found for ( " << id << " ) " << " in current address book" << endl; + } + else + { + return theAddressee.formattedName(); + } + } + // no kabc association, return null image + return QString::null; +} + +QString nameFromContact( Kopete::Contact *c) /*const*/ +{ + if ( !c ) + return QString::null; + + QString contactName; + if ( c->hasProperty( Kopete::Global::Properties::self()->nickName().key() ) ) + contactName = c->property( Global::Properties::self()->nickName()).value().toString(); + + //the replace is there to workaround the Bug 95444 + return contactName.isEmpty() ? c->contactId() : contactName.replace('\n',QString::fromUtf8("")); +} + +KURL MetaContact::customPhoto() const +{ + return d->photoUrl; +} + +void MetaContact::setPhoto( const KURL &url ) +{ + d->photoUrl = url; + d->customPicture.setPicture(url.path()); + + if ( photoSource() == SourceCustom ) + { + emit photoChanged(); + } +} + +QImage MetaContact::photo() const +{ + if( picture().image().width() > 96 && picture().image().height() > 96 ) + { + kdDebug( 14010 ) << k_funcinfo << "Resizing image from " << picture().image().width() << " x " << picture().image().height() << endl; + return picture().image().smoothScale(96,96,QImage::ScaleMin); + } + else + return picture().image(); +} + +Picture &MetaContact::picture() const +{ + if ( photoSource() == SourceKABC ) + { + return d->kabcPicture; + } + else if ( photoSource() == SourceContact ) + { + return d->contactPicture; + } + + return d->customPicture; +} + +QImage MetaContact::photoFromCustom() const +{ + return d->customPicture.image(); +} + +QImage photoFromContact( Kopete::Contact *contact) /*const*/ +{ + if ( contact == 0L ) + return QImage(); + + QVariant photoProp; + if ( contact->hasProperty( Kopete::Global::Properties::self()->photo().key() ) ) + photoProp = contact->property( Kopete::Global::Properties::self()->photo().key() ).value(); + + QImage img; + if(photoProp.canCast( QVariant::Image )) + img=photoProp.toImage(); + else if(photoProp.canCast( QVariant::Pixmap )) + img=photoProp.toPixmap().convertToImage(); + else if(!photoProp.asString().isEmpty()) + { + img=QPixmap( photoProp.toString() ).convertToImage(); + } + return img; +} + +QImage photoFromKABC( const QString &id ) /*const*/ +{ + KABC::AddressBook* ab = KABCPersistence::self()->addressBook(); + if ( ! id.isEmpty() && !id.contains(':') ) + { + KABC::Addressee theAddressee = ab->findByUid(id); + if ( theAddressee.isEmpty() ) + { + kdDebug( 14010 ) << k_funcinfo << "no KABC::Addressee found for ( " << id << " ) " << " in current address book" << endl; + } + else + { + KABC::Picture pic = theAddressee.photo(); + if ( pic.data().isNull() && pic.url().isEmpty() ) + pic = theAddressee.logo(); + + if ( pic.isIntern()) + { + return pic.data(); + } + else + { + return QPixmap( pic.url() ).convertToImage(); + } + } + } + // no kabc association, return null image + return QImage(); +} + +Contact *MetaContact::displayNameSourceContact() const +{ + return d->displayNameSourceContact; +} + +Contact *MetaContact::photoSourceContact() const +{ + return d->photoSourceContact; +} + +void MetaContact::setDisplayNameSourceContact( Contact *contact ) +{ + Contact *old = d->displayNameSourceContact; + d->displayNameSourceContact = contact; + if ( displayNameSource() == SourceContact ) + { + emit displayNameChanged( nameFromContact(old), nameFromContact(contact)); + } +} + +void MetaContact::setPhotoSourceContact( Contact *contact ) +{ + d->photoSourceContact = contact; + + // Create a cache for the contact photo. + if(d->photoSourceContact != 0L) + { + QVariant photoProp; + if ( contact->hasProperty( Kopete::Global::Properties::self()->photo().key() ) ) + photoProp = contact->property( Kopete::Global::Properties::self()->photo().key() ).value(); + + if(photoProp.canCast( QVariant::Image )) + d->contactPicture.setPicture(photoProp.toImage()); + else if(photoProp.canCast( QVariant::Pixmap )) + d->contactPicture.setPicture(photoProp.toPixmap().convertToImage()); + else if(!photoProp.asString().isEmpty()) + { + d->contactPicture.setPicture(photoProp.toString()); + } + } + + if ( photoSource() == SourceContact ) + { + emit photoChanged(); + } +} + +void MetaContact::slotPropertyChanged( Contact* subcontact, const QString &key, + const QVariant &oldValue, const QVariant &newValue ) +{ + if ( displayNameSource() == SourceContact ) + { + if( key == Global::Properties::self()->nickName().key() ) + { + if (displayNameSourceContact() == subcontact) + { + emit displayNameChanged( oldValue.toString(), newValue.toString()); + } + else + { + // HACK the displayName that changed is not from the contact we are tracking, but + // as the current one is null, lets use this new one + if (displayName().isEmpty()) + setDisplayNameSourceContact(subcontact); + } + } + } + + if (photoSource() == SourceContact) + { + if ( key == Global::Properties::self()->photo().key() ) + { + if (photoSourceContact() != subcontact) + { + // HACK the displayName that changed is not from the contact we are tracking, but + // as the current one is null, lets use this new one + if (photo().isNull()) + setPhotoSourceContact(subcontact); + + } + else if(photoSourceContact() == subcontact) + { + if(d->photoSyncedWithKABC) + setPhotoSyncedWithKABC(true); + + setPhotoSourceContact(subcontact); + } + } + } +} + +void MetaContact::moveToGroup( Group *from, Group *to ) +{ + if ( !from || !groups().contains( from ) ) + { + // We're adding, not moving, because 'from' is illegal + addToGroup( to ); + return; + } + + if ( !to || groups().contains( to ) ) + { + // We're removing, not moving, because 'to' is illegal + removeFromGroup( from ); + return; + } + + if ( isTemporary() && to->type() != Group::Temporary ) + return; + + + //kdDebug( 14010 ) << k_funcinfo << from->displayName() << " => " << to->displayName() << endl; + + d->groups.remove( from ); + d->groups.append( to ); + + for( Contact *c = d->contacts.first(); c ; c = d->contacts.next() ) + c->sync(Contact::MovedBetweenGroup); + + emit movedToGroup( this, from, to ); +} + +void MetaContact::removeFromGroup( Group *group ) +{ + if ( !group || !groups().contains( group ) || ( isTemporary() && group->type() == Group::Temporary ) ) + { + return; + } + + d->groups.remove( group ); + + // make sure MetaContact is at least in one group + if ( d->groups.isEmpty() ) + { + d->groups.append( Group::topLevel() ); + emit addedToGroup( this, Group::topLevel() ); + } + + for( Contact *c = d->contacts.first(); c ; c = d->contacts.next() ) + c->sync(Contact::MovedBetweenGroup); + + emit removedFromGroup( this, group ); +} + +void MetaContact::addToGroup( Group *to ) +{ + if ( !to || groups().contains( to ) ) + return; + + if ( d->temporary && to->type() != Group::Temporary ) + return; + + if ( d->groups.contains( Group::topLevel() ) ) + { + d->groups.remove( Group::topLevel() ); + emit removedFromGroup( this, Group::topLevel() ); + } + + d->groups.append( to ); + + for( Contact *c = d->contacts.first(); c ; c = d->contacts.next() ) + c->sync(Contact::MovedBetweenGroup); + + emit addedToGroup( this, to ); +} + +QPtrList MetaContact::groups() const +{ + return d->groups; +} + +void MetaContact::slotContactDestroyed( Contact *contact ) +{ + removeContact(contact,true); +} + +const QDomElement MetaContact::toXML(bool minimal) +{ + // This causes each Kopete::Protocol subclass to serialise its contacts' data into the metacontact's plugin data and address book data + emit aboutToSave(this); + + QDomDocument metaContact; + metaContact.appendChild( metaContact.createElement( QString::fromUtf8( "meta-contact" ) ) ); + metaContact.documentElement().setAttribute( QString::fromUtf8( "contactId" ), metaContactId() ); + + // the custom display name, used for the custom name source + QDomElement displayName = metaContact.createElement( QString::fromUtf8("display-name" ) ); + displayName.appendChild( metaContact.createTextNode( d->displayName ) ); + metaContact.documentElement().appendChild( displayName ); + QDomElement photo = metaContact.createElement( QString::fromUtf8("photo" ) ); + KURL photoUrl = d->photoUrl; + photo.appendChild( metaContact.createTextNode( photoUrl.url() ) ); + metaContact.documentElement().appendChild( photo ); + + // Property sources + QDomElement propertySources = metaContact.createElement( QString::fromUtf8("property-sources" ) ); + QDomElement _nameSource = metaContact.createElement( QString::fromUtf8("name") ); + QDomElement _photoSource = metaContact.createElement( QString::fromUtf8("photo") ); + + // set the contact source for display name + _nameSource.setAttribute(QString::fromUtf8("source"), sourceToString(displayNameSource())); + + // set contact source metadata + if (displayNameSourceContact()) + { + QDomElement contactNameSource = metaContact.createElement( QString::fromUtf8("contact-source") ); + contactNameSource.setAttribute( NSCID_ELEM, displayNameSourceContact()->contactId() ); + contactNameSource.setAttribute( NSPID_ELEM, displayNameSourceContact()->protocol()->pluginId() ); + contactNameSource.setAttribute( NSAID_ELEM, displayNameSourceContact()->account()->accountId() ); + _nameSource.appendChild( contactNameSource ); + } + + // set the contact source for photo + _photoSource.setAttribute(QString::fromUtf8("source"), sourceToString(photoSource())); + + if( !d->metaContactId.isEmpty() ) + photo.setAttribute( QString::fromUtf8("syncWithKABC") , QString::fromUtf8( d->photoSyncedWithKABC ? "true" : "false" ) ); + + if (photoSourceContact()) + { + //kdDebug(14010) << k_funcinfo << "serializing photo source " << nameFromContact(photoSourceContact()) << endl; + // set contact source metadata for photo + QDomElement contactPhotoSource = metaContact.createElement( QString::fromUtf8("contact-source") ); + contactPhotoSource.setAttribute( NSCID_ELEM, photoSourceContact()->contactId() ); + contactPhotoSource.setAttribute( NSPID_ELEM, photoSourceContact()->protocol()->pluginId() ); + contactPhotoSource.setAttribute( NSAID_ELEM, photoSourceContact()->account()->accountId() ); + _photoSource.appendChild( contactPhotoSource ); + } + // apend name and photo sources to property sources + propertySources.appendChild(_nameSource); + propertySources.appendChild(_photoSource); + + metaContact.documentElement().appendChild(propertySources); + + // Don't store these information in minimal mode. + if(!minimal) + { + // Store groups + if ( !d->groups.isEmpty() ) + { + QDomElement groups = metaContact.createElement( QString::fromUtf8("groups") ); + Group *g; + for ( g = d->groups.first(); g; g = d->groups.next() ) + { + QDomElement group = metaContact.createElement( QString::fromUtf8("group") ); + group.setAttribute( QString::fromUtf8("id"), g->groupId() ); + groups.appendChild( group ); + } + metaContact.documentElement().appendChild( groups ); + } + + // Store other plugin data + QValueList pluginData = Kopete::ContactListElement::toXML(); + for( QValueList::Iterator it = pluginData.begin(); it != pluginData.end(); ++it ) + metaContact.documentElement().appendChild( metaContact.importNode( *it, true ) ); + + // Store custom notification data + QDomElement notifyData = NotifyDataObject::notifyDataToXML(); + if ( notifyData.hasChildNodes() ) + metaContact.documentElement().appendChild( metaContact.importNode( notifyData, true ) ); + } + return metaContact.documentElement(); +} + +bool MetaContact::fromXML( const QDomElement& element ) +{ + if( !element.hasChildNodes() ) + return false; + + bool oldPhotoTracking = false; + bool oldNameTracking = false; + + QString strContactId = element.attribute( QString::fromUtf8("contactId") ); + if( !strContactId.isEmpty() ) + { + d->metaContactId = strContactId; + // Set the KABC Picture + slotUpdateAddressBookPicture(); + } + + QDomElement contactElement = element.firstChild().toElement(); + while( !contactElement.isNull() ) + { + + if( contactElement.tagName() == QString::fromUtf8( "display-name" ) ) + { // custom display name, used for the custom name source + + // WTF, why were we not loading the metacontact if nickname was empty. + //if ( contactElement.text().isEmpty() ) + // return false; + + //the replace is there to workaround the Bug 95444 + d->displayName = contactElement.text().replace('\n',QString::fromUtf8("")); + + if ( contactElement.hasAttribute(NSCID_ELEM) && contactElement.hasAttribute(NSPID_ELEM) && contactElement.hasAttribute(NSAID_ELEM)) + { + oldNameTracking = true; + //kdDebug(14010) << k_funcinfo << "old name tracking" << endl; + // retrieve deprecated data (now stored in property-sources) + // save temporarely, we will find a Contact* with this later + d->nameSourceCID = contactElement.attribute( NSCID_ELEM ); + d->nameSourcePID = contactElement.attribute( NSPID_ELEM ); + d->nameSourceAID = contactElement.attribute( NSAID_ELEM ); + } +// else +// kdDebug(14010) << k_funcinfo << "no old name tracking" << endl; + } + else if( contactElement.tagName() == QString::fromUtf8( "photo" ) ) + { + // custom photo, used for custom photo source + setPhoto( KURL(contactElement.text()) ); + + d->photoSyncedWithKABC = (contactElement.attribute(QString::fromUtf8("syncWithKABC")) == QString::fromUtf8("1")) || (contactElement.attribute(QString::fromUtf8("syncWithKABC")) == QString::fromUtf8("true")); + + // retrieve deprecated data (now stored in property-sources) + // save temporarely, we will find a Contact* with this later + if ( contactElement.hasAttribute(PSCID_ELEM) && contactElement.hasAttribute(PSPID_ELEM) && contactElement.hasAttribute(PSAID_ELEM)) + { + oldPhotoTracking = true; +// kdDebug(14010) << k_funcinfo << "old photo tracking" << endl; + d->photoSourceCID = contactElement.attribute( PSCID_ELEM ); + d->photoSourcePID = contactElement.attribute( PSPID_ELEM ); + d->photoSourceAID = contactElement.attribute( PSAID_ELEM ); + } +// else +// kdDebug(14010) << k_funcinfo << "no old photo tracking" << endl; + } + else if( contactElement.tagName() == QString::fromUtf8( "property-sources" ) ) + { + QDomNode property = contactElement.firstChild(); + while( !property.isNull() ) + { + QDomElement propertyElement = property.toElement(); + + if( propertyElement.tagName() == QString::fromUtf8( "name" ) ) + { + QString source = propertyElement.attribute( QString::fromUtf8("source") ); + setDisplayNameSource(stringToSource(source)); + // find contact sources now. + QDomNode propertyParam = propertyElement.firstChild(); + while( !propertyParam.isNull() ) + { + QDomElement propertyParamElement = propertyParam.toElement(); + if( propertyParamElement.tagName() == QString::fromUtf8( "contact-source" ) ) + { + d->nameSourceCID = propertyParamElement.attribute( NSCID_ELEM ); + d->nameSourcePID = propertyParamElement.attribute( NSPID_ELEM ); + d->nameSourceAID = propertyParamElement.attribute( NSAID_ELEM ); + } + propertyParam = propertyParam.nextSibling(); + } + } + if( propertyElement.tagName() == QString::fromUtf8( "photo" ) ) + { + QString source = propertyElement.attribute( QString::fromUtf8("source") ); + setPhotoSource(stringToSource(source)); + // find contact sources now. + QDomNode propertyParam = propertyElement.firstChild(); + while( !propertyParam.isNull() ) + { + QDomElement propertyParamElement = propertyParam.toElement(); + if( propertyParamElement.tagName() == QString::fromUtf8( "contact-source" ) ) + { + d->photoSourceCID = propertyParamElement.attribute( NSCID_ELEM ); + d->photoSourcePID = propertyParamElement.attribute( NSPID_ELEM ); + d->photoSourceAID = propertyParamElement.attribute( NSAID_ELEM ); + } + propertyParam = propertyParam.nextSibling(); + } + } + property = property.nextSibling(); + } + } + else if( contactElement.tagName() == QString::fromUtf8( "groups" ) ) + { + QDomNode group = contactElement.firstChild(); + while( !group.isNull() ) + { + QDomElement groupElement = group.toElement(); + + if( groupElement.tagName() == QString::fromUtf8( "group" ) ) + { + QString strGroupId = groupElement.attribute( QString::fromUtf8("id") ); + if( !strGroupId.isEmpty() ) + addToGroup( Kopete::ContactList::self()->group( strGroupId.toUInt() ) ); + else //kopete 0.6 contactlist + addToGroup( Kopete::ContactList::self()->findGroup( groupElement.text() ) ); + } + else if( groupElement.tagName() == QString::fromUtf8( "top-level" ) ) //kopete 0.6 contactlist + addToGroup( Kopete::Group::topLevel() ); + + group = group.nextSibling(); + } + } + else if( contactElement.tagName() == QString::fromUtf8( "address-book-field" ) ) + { + QString app = contactElement.attribute( QString::fromUtf8( "app" ), QString::null ); + QString key = contactElement.attribute( QString::fromUtf8( "key" ), QString::null ); + QString val = contactElement.text(); + d->addressBook[ app ][ key ] = val; + } + else if( contactElement.tagName() == QString::fromUtf8( "custom-notifications" ) ) + { + Kopete::NotifyDataObject::notifyDataFromXML( contactElement ); + } + else //if( groupElement.tagName() == QString::fromUtf8( "plugin-data" ) || groupElement.tagName() == QString::fromUtf8("custom-icons" )) + { + Kopete::ContactListElement::fromXML(contactElement); + } + contactElement = contactElement.nextSibling().toElement(); + } + + if( oldNameTracking ) + { + /* if (displayNameSourceContact() ) <- doesn't work because the contact is only set up when all plugin are loaded (BUG 111956) */ + if ( !d->nameSourceCID.isEmpty() ) + { +// kdDebug(14010) << k_funcinfo << "Converting old name source" << endl; + // even if the old tracking attributes exists, they could have been null, that means custom + setDisplayNameSource(SourceContact); + } + else + { + // lets do the best conversion for the old name tracking + // if the custom display name is the same as kabc name, set the source to kabc + if ( !d->metaContactId.isEmpty() && ( d->displayName == nameFromKABC(d->metaContactId)) ) + setDisplayNameSource(SourceKABC); + else + setDisplayNameSource(SourceCustom); + } + } + + if ( oldPhotoTracking ) + { +// kdDebug(14010) << k_funcinfo << "Converting old photo source" << endl; + if ( !d->photoSourceCID.isEmpty() ) + { + setPhotoSource(SourceContact); + } + else + { + if ( !d->metaContactId.isEmpty() && !photoFromKABC(d->metaContactId).isNull()) + setPhotoSource(SourceKABC); + else + setPhotoSource(SourceCustom); + } + } + + // If a plugin is loaded, load data cached + connect( Kopete::PluginManager::self(), SIGNAL( pluginLoaded(Kopete::Plugin*) ), + this, SLOT( slotPluginLoaded(Kopete::Plugin*) ) ); + + // All plugins are already loaded, call manually the contact setting slot. + if( Kopete::PluginManager::self()->isAllPluginsLoaded() ) + slotAllPluginsLoaded(); + else + // When all plugins are loaded, set the source contact. + connect( Kopete::PluginManager::self(), SIGNAL( allPluginsLoaded() ), + this, SLOT( slotAllPluginsLoaded() ) ); + + // track changes only works if ONE Contact is inside the MetaContact +// if (d->contacts.count() > 1) // Does NOT work as intended +// d->trackChildNameChanges=false; + +// kdDebug(14010) << k_funcinfo << "END" << endl; + return true; +} + +QString MetaContact::sourceToString(PropertySource source) const +{ + if ( source == SourceCustom ) + return QString::fromUtf8("custom"); + else if ( source == SourceKABC ) + return QString::fromUtf8("addressbook"); + else if ( source == SourceContact ) + return QString::fromUtf8("contact"); + else // recovery + return sourceToString(SourceCustom); +} + +MetaContact::PropertySource MetaContact::stringToSource(const QString &name) const +{ + if ( name == QString::fromUtf8("custom") ) + return SourceCustom; + else if ( name == QString::fromUtf8("addressbook") ) + return SourceKABC; + else if ( name == QString::fromUtf8("contact") ) + return SourceContact; + else // recovery + return SourceCustom; +} + +QString MetaContact::addressBookField( Kopete::Plugin * /* p */, const QString &app, const QString & key ) const +{ + return d->addressBook[ app ][ key ]; +} + +void Kopete::MetaContact::setAddressBookField( Kopete::Plugin * /* p */, const QString &app, const QString &key, const QString &value ) +{ + d->addressBook[ app ][ key ] = value; +} + +void MetaContact::slotPluginLoaded( Plugin *p ) +{ + if( !p ) + return; + + QMap map= pluginData( p ); + if(!map.isEmpty()) + { + p->deserialize(this,map); + } +} + +void MetaContact::slotAllPluginsLoaded() +{ + // Now that the plugins and subcontacts are loaded, set the source contact. + setDisplayNameSourceContact( findContact( d->nameSourcePID, d->nameSourceAID, d->nameSourceCID) ); + setPhotoSourceContact( findContact( d->photoSourcePID, d->photoSourceAID, d->photoSourceCID) ); +} + +void MetaContact::slotUpdateAddressBookPicture() +{ + KABC::AddressBook* ab = KABCPersistence::self()->addressBook(); + QString id = metaContactId(); + if ( !id.isEmpty() && !id.contains(':') ) + { + KABC::Addressee theAddressee = ab->findByUid(id); + if ( theAddressee.isEmpty() ) + { + kdDebug( 14010 ) << k_funcinfo << "no KABC::Addressee found for ( " << id << " ) " << " in current address book" << endl; + } + else + { + KABC::Picture pic = theAddressee.photo(); + if ( pic.data().isNull() && pic.url().isEmpty() ) + pic = theAddressee.logo(); + + d->kabcPicture.setPicture(pic); + } + } +} + +bool MetaContact::isTemporary() const +{ + return d->temporary; +} + +void MetaContact::setTemporary( bool isTemporary, Group *group ) +{ + d->temporary = isTemporary; + Group *temporaryGroup = Group::temporary(); + if ( d->temporary ) + { + addToGroup (temporaryGroup); + Group *g; + for( g = d->groups.first(); g; g = d->groups.next() ) + { + if(g != temporaryGroup) + removeFromGroup(g); + } + } + else + moveToGroup(temporaryGroup, group ? group : Group::topLevel()); +} + +QString MetaContact::metaContactId() const +{ + if(d->metaContactId.isEmpty()) + { + Contact *c=d->contacts.first(); + if(!c) + return QString::null; + return c->protocol()->pluginId()+QString::fromUtf8(":")+c->account()->accountId()+QString::fromUtf8(":") + c->contactId() ; + } + return d->metaContactId; +} + +void MetaContact::setMetaContactId( const QString& newMetaContactId ) +{ + if(newMetaContactId == d->metaContactId) + return; + + // 1) Check the Id is not already used by another contact + // 2) cause a kabc write ( only in response to metacontactLVIProps calling this, or will + // write be called twice when creating a brand new MC? ) + // 3) What about changing from one valid kabc to another, are kabc fields removed? + // 4) May be called with Null to remove an invalid kabc uid by KMC::toKABC() + // 5) Is called when reading the saved contact list + + // Don't remove IM addresses from kabc if we are changing contacts; + // other programs may have written that data and depend on it + d->metaContactId = newMetaContactId; + KABCPersistence::self()->write( this ); + emit onlineStatusChanged( this, d->onlineStatus ); + emit persistentDataChanged(); +} + +bool MetaContact::isPhotoSyncedWithKABC() const +{ + return d->photoSyncedWithKABC; +} + +void MetaContact::setPhotoSyncedWithKABC(bool b) +{ + d->photoSyncedWithKABC=b; + if(b) + { + QVariant newValue; + + switch( photoSource() ) + { + case SourceContact: + { + Contact *source = photoSourceContact(); + if(source != 0L) + newValue = source->property( Kopete::Global::Properties::self()->photo() ).value(); + break; + } + case SourceCustom: + { + if( !d->customPicture.isNull() ) + newValue = d->customPicture.path(); + break; + } + // Don't sync the photo with KABC if the source is KABC ! + default: + return; + } + + if ( !d->metaContactId.isEmpty() && !newValue.isNull()) + { + KABC::Addressee theAddressee = KABCPersistence::self()->addressBook()->findByUid( metaContactId() ); + + if ( !theAddressee.isEmpty() ) + { + QImage img; + if(newValue.canCast( QVariant::Image )) + img=newValue.toImage(); + else if(newValue.canCast( QVariant::Pixmap )) + img=newValue.toPixmap().convertToImage(); + + if(img.isNull()) + { + // Some protocols like MSN save the photo as a url in + // contact properties, we should not use this url + // to sync with kabc but try first to embed the + // photo data in the kabc addressee, because it could + // be remote resource and the local url makes no sense + QImage fallBackImage = QImage(newValue.toString()); + if(fallBackImage.isNull()) + theAddressee.setPhoto(newValue.toString()); + else + theAddressee.setPhoto(fallBackImage); + } + else + theAddressee.setPhoto(img); + + KABCPersistence::self()->addressBook()->insertAddressee(theAddressee); + KABCPersistence::self()->writeAddressBook( theAddressee.resource() ); + } + } + } +} + +QPtrList MetaContact::contacts() const +{ + return d->contacts; +} +} //END namespace Kopete + +#include "kopetemetacontact.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemetacontact.h b/kopete/libkopete/kopetemetacontact.h new file mode 100644 index 00000000..3bdaa33a --- /dev/null +++ b/kopete/libkopete/kopetemetacontact.h @@ -0,0 +1,615 @@ +/* + kopetemetacontact.h - Kopete Meta Contact + + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2005 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2005 by Olivier Goffart + Copyright (c) 2003 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef kopetemetacontact_h__ +#define kopetemetacontact_h__ + +#include "kopetecontactlistelement.h" +#include +#include + +#include +#include "kopete_export.h" + +#include "kopetenotifydataobject.h" +#include "kopetecontactlistelement.h" +#include "kopeteonlinestatus.h" + +class QDomNode; + +class KURL; + +namespace Kopete { + + +class Plugin; +class Group; +class Picture; + +/** + * @author Will Stephenson + * @author Martijn Klingens + * @author Duncan Mac-Vicar Prett + * @author Olivier Goffart + * + * A metacontact represent a person. This is a kind of entry to + * the contactlist. All information of a contact is contained in + * the metacontact. Plugins can store data in it with all + * @ref ContactListElement methods + */ +class KOPETE_EXPORT MetaContact : public ContactListElement, public NotifyDataObject +{ + Q_OBJECT + + Q_PROPERTY( QString displayName READ displayName WRITE setDisplayName ) + Q_PROPERTY( QString statusString READ statusString ) + Q_PROPERTY( QString statusIcon READ statusIcon ) + Q_PROPERTY( bool isOnline READ isOnline ) + Q_PROPERTY( bool isReachable READ isReachable ) + Q_PROPERTY( bool isTemporary READ isTemporary ) + Q_PROPERTY( bool canAcceptFiles READ canAcceptFiles ) + //Q_PROPERTY( ulong idleTime READ idleTime ) + Q_PROPERTY( QString metaContactId READ metaContactId WRITE setMetaContactId ) + Q_PROPERTY( bool photoSyncedWithKABC READ isPhotoSyncedWithKABC WRITE setPhotoSyncedWithKABC ) + +public: + /** + * Enumeration of possible sources for a property (which may be + * photos, see setPhotoSource() for instance). + */ + enum PropertySource { + SourceContact /**< Data comes from the contact itself. */, + SourceKABC /**< Data comes from KABC (addressbook). */, + SourceCustom /**< Data comes from somewhere else. */ + }; + + /** + * constructor + */ + MetaContact(); + /** + * destructor + */ + ~MetaContact(); + + /** + * @brief Returns this metacontact's ID. + * + * Every metacontact has a unique id, set by when creating the contact, or reading the contactlist + * TODO: make it real + */ + QString metaContactId() const; + + /** + * @brief Add or change the link to a KDE addressbook (KABC) Addressee. + * FIXME: Use with care. You could create 1 to many relationships with the current implementation + */ + void setMetaContactId( const QString& newMetaContactId ); + + /** + * @brief Retrieve the list of contacts that are part of the meta contact + */ + QPtrList contacts() const; + + /** + * @brief The groups the contact is stored in + */ + QPtrList groups() const; + + /** + * Find the Contact to a given contact. If contact + * is not found, a null pointer is returned. + * if @p protocolId or @p accountId are null, it is searched over all protocols/accounts + */ + Contact *findContact( const QString &protocolId, const QString &accountId, const QString &contactId ); + + /** + * @brief Set the source of metacontact displayName + * + * This method selects the display name source for one + * of the sources defined in @ref PropertySource + * + * @see PropertySource + */ + void setDisplayNameSource(PropertySource source); + + /** + * @brief get the source of metacontact display name + * + * This method obtains the current name source for one + * of the sources defined in @ref PropertySource + * + * @see PropertySource + */ + PropertySource displayNameSource() const; + + /** + * @brief Set the source of metacontact photo + * + * This method selects the photo source for one + * of the sources defined in @ref PropertySource + * + * @see PropertySource + */ + void setPhotoSource(PropertySource source); + + /** + * @brief get the source of metacontact photo + * + * This method obtains the current photo source for one + * of the sources defined in @ref PropertySource + * + * @see PropertySource + */ + PropertySource photoSource() const; + + /** + * @brief the display name showed in the contactlist window + * + * The displayname is the name which should be shown almost everywere to + * represent the metacontact. (in the contactlist, in the chatwindow, ....) + * + * This is a kind of alias, set by the kopete user, as opposed to a nickname + * set by the contact itself. + * + * If the protocol support alias serverside, the metacontact displayname + * should probably be syncronized with the alias on the server. + * + * This displayName is obtained from the source set with @ref setDisplayNameSource + */ + QString displayName() const; + + /** + * @brief the photo showed in the contactlist window + * + * Returns a image for the metacontact. If the metacontact photo source is + * the KDE addressbook. it will return the picture stored in the addressbook + * It can also use a subcontact as the photo source. + * + * This photo is obtained from the source set with @ref setPhotoSource + */ + QImage photo() const; + + /** + * Return the correct Kopete::Picture object depending of the metacontact photo source. + * + * This photo is obtained from the source set with @ref setPhotoSource + * + * KDE4 TODO: Rename this to photo() and use the new object. + */ + Picture &picture() const; + + /** + * @brief Set the custom displayName. + * + * This display name is used when name source is Custom + * this metohd may emit @ref displayNameChanged signal. + * And will call @ref Kopete::Contact::sync + * + * @see displayName() + * @see displayNameSource() + */ + void setDisplayName( const QString &name ); + + /** + * @brief Returns the custom display name + * + * @see displayName() + * @see displayNameSource() + */ + QString customDisplayName() const; + + /** + * @brief Returns the custom display photo + * + * @see photo() + * @see photoSource() + */ + KURL customPhoto() const; + + + /** + * @brief Set the custom photo. + * + * This photo is used when photo source is set toCustom + * this metohd may emit @ref photoChanged signal. + * + * @see photo() + * @see photoSource() + */ + void setPhoto( const KURL &url ); + + /** + * @brief get the subcontact being tracked for its displayname (null if not set) + * + * The MetaContact will adjust its displayName() every time the + * "nameSource" changes its nickname property. + */ + Contact *displayNameSourceContact() const; + + /** + * @brief set the subcontact whose name is to be tracked (set to null to disable tracking) + * @see nameSource + */ + void setDisplayNameSourceContact( Contact* contact ); + + /** + * @brief get the subcontact being tracked for its photo + */ + Contact *photoSourceContact() const; + + /** + * @brief set the subcontact to use for SourceContact source + */ + void setPhotoSourceContact( Contact* contact ); + + /** + * @return true if when a subcontact change his photo, the photo will be set to the kabc contact. + */ + bool isPhotoSyncedWithKABC() const; + + /** + * Set if the photo should be synced with the adressbook when the photosource change his photo + * + * If \p b is true, the photo will be synced immediatly if possible + */ + void setPhotoSyncedWithKABC(bool b); + + + /** + * Temporary contacts will not be serialized. + * If they are added to the contactlist, they appears in a special "Not in your contactlist" group. + * (the @ref Group::temporary group) + */ + bool isTemporary() const; + + /** + * @brief Add a contact which has just been deserialised to the meta contact + * @param c The Contact being added + */ + void addContact( Contact *c ); + + /** + * @brief remove the contact from this metacontact + * + * set 'deleted' to true if the Contact is already deleted + * + * @param c is the contact to remove + * @param deleted : if it is false, it will disconnect the old contact, and call some method. + */ + void removeContact( Contact *c , bool deleted = false ); + + /** + * @return the preferred child Contact for communication, or 0 if none is suitable (all unreachable). + */ + Contact *preferredContact(); + + /** + * @brief The name of the icon associated with the contact's status + * @todo improve with OnlineStatus + */ + QString statusIcon() const; + + /** + * @brief The status string of the contact + * + * @see @ref status() + * @todo improve with OnlineStatus + */ + QString statusString() const; + + /** + * Returns whether this contact can be reached online for at least one + * FIXME: Make that an enum, because status can be unknown for certain + * protocols + */ + bool isOnline() const; + + /** + * Returns whether this contact can accept files + * @return True if the user is online with a file capable protocol, false otherwise + */ + bool canAcceptFiles() const; + + /** + * Return a more fine-grained status. + * Online means at least one sub-contact is online, away means at least + * one is away, but nobody is online and offline speaks for itself + */ + OnlineStatus::StatusType status() const; + + /** + * Like isOnline, but returns true even if the contact is not online, but + * can be reached trough offline-messages. + * it it return false, you are unable to open a chatwindow + * @todo : Here too, use preference order, not append order! + * @todo : Here too an enum. + */ + bool isReachable() const; + + /** + * return the time in second the contact is idle. + */ + unsigned long int idleTime() const; + + /** + * Return a XML representation of the metacontact + * @internal + * @param minimal When true, it doesn't save the + * plugins, groups and notification data. False by default. + */ + const QDomElement toXML(bool minimal = false); + + /** + * Creates a metacontact from XML + * Return value of false indicated that + * creation failed and this contact should be + * discarded. + * @internal + */ + bool fromXML( const QDomElement& cnode ); + + /** + * Get or set a field for the KDE address book backend. Fields not + * registered during the call to Plugin::addressBookFields() + * cannot be altered! + * + * @param p The Plugin by which uses this field + * @param app refers to the application id in the libkabc database. + * This should be a standardized format to make sense in the address + * book in the first place - if you could use "" as application + * then probably you should use the plugin data API instead of the + * address book fields. + * @param key The name of the address book field to get or set + * + * @todo: In the code the requirement that fields are registered first + * is already lifted, but the API needs some review before we + * can remove it here too. + * Probably it requires once more some rewrites to get it working + * properly :( - Martijn + */ + QString addressBookField( Plugin *p, const QString &app, const QString &key ) const; + + /** + * @brief set an address book field + * + * @see also @ref addressBookField() + * @param p The Plugin by which uses this field + * @param app The application ID in the KABC database + * @param key The name of the address book field to set + * @param value The value of the address book field to set + */ + void setAddressBookField( Plugin *p, const QString &app, const QString &key, const QString &value ); + +public slots: + + /** + * @brief Send a file to this metacontact + * + * This is the MetaContact level slot for sending files. It may be called through the + * "Send File" entry in the GUI, or over DCOP. If the function is called through the GUI, + * no parameters are sent and they assume default values. This slot calls the slotSendFile + * with identical params of the highest ranked contact capable of sending files (if any) + * + * @param sourceURL The actual KURL of the file you are sending + * @param altFileName (Optional) An alternate name for the file - what the receiver will see + * @param fileSize (Optional) Size of the file being sent. Used when sending a nondeterminate + * file size (such as over a socket) + * + */ + void sendFile( const KURL &sourceURL, const QString &altFileName = QString::null, + unsigned long fileSize = 0L ); +signals: + /** + * This metaContact is going to be saved to the contactlist. Plugins should + * connect to this signal to update data with setPluginData() + */ + void aboutToSave( Kopete::MetaContact *metaContact ); + + /** + * One of the subcontacts' idle status has changed. As with online status, + * this can occur without the metacontact changing idle state + */ + void contactIdleStateChanged( Kopete::Contact *contact ); + + +public slots: + + /** + * @brief Move a contact from one group to another. + */ + void moveToGroup( Kopete::Group *from, Kopete::Group *to ); + + /** + * @brief Remove a contact from one group + */ + void removeFromGroup( Kopete::Group *from ); + + /** + * @brief Add a contact to another group. + */ + void addToGroup( Kopete::Group *to ); + + /** + * @brief Set if this is a temporary contact. (see @ref isTemporary) + * + * @param b if the contact is or not temporary + * @param group if the contact was temporary and b is false, then the contact will be moved to this group. + * if group is null, it will be moved to top-level + */ + void setTemporary( bool b = true, Kopete::Group *group = 0L ); + + /** + * @brief Contact another user. + * + * Depending on the config settings, call sendMessage() or + * startChat() + * + * returns the Contact that was chosen as the preferred + */ + Contact *execute(); + + /** + * @brief Send a single message, classic ICQ style. + * + * The actual sending is done by the Contact, but the meta contact + * does the GUI side of things. + * This is a slot to allow being called easily from e.g. a GUI. + * + * returns the Contact that was chosen as the preferred + */ + Contact *sendMessage(); + + /** + * @brief Start a chat in a persistent chat window + * + * Like sendMessage, but this time a full-blown chat will be opened. + * Most protocols can't distinguish between the two and are either + * completely session based like MSN or completely message based like + * ICQ the only true difference is the GUI shown to the user. + * + * returns the Contact that was chosen as the preferred + */ + Contact *startChat(); + +signals: + /** + * @brief The MetaContact online status changed + */ + void onlineStatusChanged( Kopete::MetaContact *contact, Kopete::OnlineStatus::StatusType status ); + + /** + * @brief A contact's online status changed + * + * this signal differs from @ref onlineStatusChanged because a contact can + * change his status without changing MetaContact status. It is mainly used to update the small icons + * in the contactlist + */ + void contactStatusChanged( Kopete::Contact *contact, const Kopete::OnlineStatus &status ); + + /** + * @brief The meta contact's display name changed + */ + void displayNameChanged( const QString &oldName, const QString &newName ); + + /** + * @brief The meta contact's photo changed + */ + void photoChanged(); + + /** + * @brief The contact was moved + */ + void movedToGroup( Kopete::MetaContact *contact, Kopete::Group *from, Kopete::Group *to ); + + /** + * @brief The contact was removed from group + */ + void removedFromGroup( Kopete::MetaContact *contact, Kopete::Group *group ); + + /** + * @brief The contact was added to another group + */ + void addedToGroup( Kopete::MetaContact *contact, Kopete::Group *to ); + + /** + * @brief a contact has been added into this metacontact + * + * This signal is emitted when a contact is added to this metacontact + */ + void contactAdded( Kopete::Contact *c ); + + /** + * @brief a contact has been removed from this metacontact + * + * This signal is emitted when a contact is removed from this metacontact + */ + void contactRemoved( Kopete::Contact *c ); + + /** + * Some part of this object's persistent data (as returned by toXML) has changed. + */ + void persistentDataChanged( ); + +private slots: + /** + * Update the contact's online status and emit onlineStatusChanged + * when appropriate + */ + void updateOnlineStatus(); + + /** + * One of the child contact's online status changed + */ + void slotContactStatusChanged( Kopete::Contact *c, const Kopete::OnlineStatus &status, const Kopete::OnlineStatus &oldStatus ); + + /** + * One of the child contact's property changed + */ + void slotPropertyChanged( Kopete::Contact *contact, const QString &key, const QVariant &oldValue, const QVariant &newValue ); + + /** + * A child contact was deleted, remove it from the list, if it's still + * there + */ + void slotContactDestroyed( Kopete::Contact* ); + + /** + * If a plugin is loaded, maybe data about this plugin are already cached in the metacontact + */ + void slotPluginLoaded( Kopete::Plugin *plugin ); + + /** + * When all the plugins are loaded, set the Contact Source. + */ + void slotAllPluginsLoaded(); + + /** + * Update the KABC Picture when the addressbook is changed. + */ + void slotUpdateAddressBookPicture(); + +protected: + //QImage photoFromContact( Kopete::Contact *c) const; + //QImage photoFromKABC( const QString &id ) const; + QImage photoFromCustom() const; + //QString nameFromContact( Kopete::Contact *c) const; + //QString nameFromKABC( const QString &id ) const; + + QString sourceToString(PropertySource source) const; + PropertySource stringToSource(const QString &name) const; +private: + class Private; + Private *d; +}; + +// util functions shared with metacontact property dialog +KOPETE_EXPORT QImage photoFromContact( Kopete::Contact *c) /*const*/; +KOPETE_EXPORT QImage photoFromKABC( const QString &id ) /*const*/; +KOPETE_EXPORT QString nameFromContact( Kopete::Contact *c) /*const*/; +KOPETE_EXPORT QString nameFromKABC( const QString &id ) /*const*/; + +} //END namespace Kopete + + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetemimesourcefactory.cpp b/kopete/libkopete/kopetemimesourcefactory.cpp new file mode 100644 index 00000000..a34d8aee --- /dev/null +++ b/kopete/libkopete/kopetemimesourcefactory.cpp @@ -0,0 +1,175 @@ +/* + kopetemimesourcefactory.cpp - Kopete mime source factory + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetemimesourcefactory.h" + +#include "kopeteaccountmanager.h" +#include "kopetecontactlist.h" +#include "kopeteaccount.h" +#include "kopetecontact.h" +#include "kopetemetacontact.h" + +#include +#include + +#include +#include +#include + +namespace Kopete +{ + +class MimeSourceFactory::Private +{ +public: + Private() : lastMimeSource( 0 ) {} + ~Private() { delete lastMimeSource; } + mutable QMimeSource *lastMimeSource; +}; + +MimeSourceFactory::MimeSourceFactory() + : d( new Private ) +{ +} + +MimeSourceFactory::~MimeSourceFactory() +{ + delete d; +} + +const QMimeSource *MimeSourceFactory::data( const QString &abs_name ) const +{ + // flag used to signal something went wrong when creating a mimesource + bool completed = false; + // extract and decode arguments + QStringList parts = QStringList::split( QChar(':'), abs_name ); + for ( QStringList::Iterator it = parts.begin(); it != parts.end(); ++it ) + *it = KURL::decode_string( *it ); + + QPixmap img; + if ( parts[0] == QString::fromLatin1("kopete-contact-icon") ) + { + if ( parts.size() >= 4 ) + { + Account *account = AccountManager::self()->findAccount( parts[1], parts[2] ); + if ( account ) + { + Contact *contact = account->contacts()[ parts[3] ]; + if ( contact ) + { + img = contact->onlineStatus().iconFor( contact ); + completed = true; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-contact-icon: contact not found" << endl; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-contact-icon: account not found" << endl; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-contact-icon: insufficient information in abs_name: " << parts << endl; + } + + if ( parts[0] == QString::fromLatin1("kopete-account-icon") ) + { + if ( parts.size() >= 3 ) + { + Account *account = AccountManager::self()->findAccount( parts[1], parts[2] ); + if ( account ) + { + img = account->myself()->onlineStatus().iconFor( account->myself() ); + completed = true; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-account-icon: account not found" << endl; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-account-icon: insufficient information in abs_name: " << parts << endl; + } + + if ( parts[0] == QString::fromLatin1("kopete-metacontact-icon") ) + { + if ( parts.size() >= 2 ) + { + MetaContact *mc = ContactList::self()->metaContact( parts[1] ); + if ( mc ) + { + img = SmallIcon( mc->statusIcon() ); + completed = true; + } + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-metacontact-icon: insufficient information in abs_name: " << parts << endl; + } + + if ( parts[0] == QString::fromLatin1("kopete-metacontact-photo") ) + { + if ( parts.size() >= 2 ) + { + MetaContact *mc = ContactList::self()->metaContact( parts[1] ); + if ( mc ) + { + QImage photo = mc->photo(); + delete d->lastMimeSource; + d->lastMimeSource = new QImageDrag( photo ); + return d->lastMimeSource; + } + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-metacontact-photo: insufficient information in abs_name: " << parts << endl; + } + + if ( parts[0] == QString::fromLatin1("kopete-onlinestatus-icon") ) + { + if ( parts.size() >= 2 ) + { + /* + * We are using a dirty trick here: this mime source is supposed to return the + * icon for an arbitrary KOS instance. To do this, the caller needs to ask + * the KOS for the mime source key first, which also ensures the icon is + * currently in the cache. The cache is global, so we just need to find any + * existing KOS instance to return us the rendered icon from the cache. + * To find a valid KOS, we ask Kopete's account manager to locate an existing + * account. We'll use the myself() instance of that account to reference its + * current KOS object, which in turn has access to the global KOS icon cache. + * Note that if the cache has been invalidated in the meantime, we'll just + * get an empty pixmap back. + */ + Account *account = AccountManager::self()->accounts().getFirst(); + if ( account ) + { + img = account->myself()->onlineStatus().iconFor( parts[1] ); + completed = true; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-onlinestatus-icon: no active account found" << endl; + } + else + kdDebug( 14010 ) << k_funcinfo << "kopete-onlinestatus-icon: insufficient information in abs_name: " << parts << endl; + } + + delete d->lastMimeSource; + if ( completed ) + d->lastMimeSource = new QImageDrag( img.convertToImage() ); + else + d->lastMimeSource = 0; + return d->lastMimeSource; +} + +} // END namespace Kopete + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemimesourcefactory.h b/kopete/libkopete/kopetemimesourcefactory.h new file mode 100644 index 00000000..76c2f188 --- /dev/null +++ b/kopete/libkopete/kopetemimesourcefactory.h @@ -0,0 +1,55 @@ +/* + kopetemimesourcefactory.h - Kopete mime source factory + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEMIMESOURCEFACTORY_H +#define KOPETEMIMESOURCEFACTORY_H + +#include + +#include "kopete_export.h" + +namespace Kopete +{ + +/** + * @brief A mime source factory for providing kopete's various icons for labels and tooltips + * + * The following 'protocols' are supported, and provide appropriate icons for + * various situations: + * kopete-contact-icon:\:\:\ + * kopete-account-icon:\:\ + * kopete-metacontact-icon:\ + * Note that the various id strings should be URL-encoded (with, for instance, + * KURL::encode_string) if they might contain colons. + */ +class KOPETE_EXPORT MimeSourceFactory : public QMimeSourceFactory +{ +public: + MimeSourceFactory(); + ~MimeSourceFactory(); + + const QMimeSource *data( const QString &abs_name ) const; + +private: + class Private; + Private *d; +}; + +} // Kopete + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemimetypehandler.cpp b/kopete/libkopete/kopetemimetypehandler.cpp new file mode 100644 index 00000000..04c4939b --- /dev/null +++ b/kopete/libkopete/kopetemimetypehandler.cpp @@ -0,0 +1,215 @@ +/* + kopetemimetypehandler.cpp - Kopete mime type handlers + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetemimetypehandler.h" +#include "kopeteglobal.h" +#include "kopeteuiglobal.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Kopete +{ + +namespace +{ + static QDict g_mimeHandlers; + static QDict g_protocolHandlers; +} + +class MimeTypeHandler::Private +{ +public: + Private( bool carf ) : canAcceptRemoteFiles( carf ) {} + bool canAcceptRemoteFiles; + QStringList mimeTypes; + QStringList protocols; +}; + +MimeTypeHandler::MimeTypeHandler( bool canAcceptRemoteFiles ) + : d( new Private( canAcceptRemoteFiles ) ) +{ +} + +MimeTypeHandler::~MimeTypeHandler() +{ + for( QStringList::iterator it = d->mimeTypes.begin(); it != d->mimeTypes.end(); ++it ) + g_mimeHandlers.remove( *it ); + + for( QStringList::iterator it = d->protocols.begin(); it != d->protocols.end(); ++it ) + g_protocolHandlers.remove( *it ); + + delete d; +} + +bool MimeTypeHandler::registerAsMimeHandler( const QString &mimeType ) +{ + if( g_mimeHandlers[ mimeType ] ) + { + kdWarning(14010) << k_funcinfo << "Warning: Two mime type handlers attempting" + " to handle " << mimeType << endl; + return false; + } + + g_mimeHandlers.insert( mimeType, this ); + d->mimeTypes.append( mimeType ); +// kdDebug(14010) << k_funcinfo << "Mime type " << mimeType << " registered" << endl; + return true; +} + +bool MimeTypeHandler::registerAsProtocolHandler( const QString &protocol ) +{ + if( g_protocolHandlers[ protocol ] ) + { + kdWarning(14010) << k_funcinfo << "Warning: Two protocol handlers attempting" + " to handle " << protocol << endl; + return false; + } + + g_protocolHandlers.insert( protocol, this ); + d->protocols.append( protocol ); + kdDebug(14010) << k_funcinfo << "Mime type " << protocol << " registered" << endl; + return true; +} + +const QStringList MimeTypeHandler::mimeTypes() const +{ + return d->mimeTypes; +} + +const QStringList MimeTypeHandler::protocols() const +{ + return d->protocols; +} + +bool MimeTypeHandler::canAcceptRemoteFiles() const +{ + return d->canAcceptRemoteFiles; +} + +bool MimeTypeHandler::dispatchURL( const KURL &url ) +{ + if( url.isEmpty() ) + return false; + + QString type = KMimeType::findByURL( url )->name(); + + MimeTypeHandler *mimeHandler = g_mimeHandlers[ type ]; + + if( mimeHandler ) + { + return dispatchToHandler( url, type, mimeHandler ); + } + else + { + mimeHandler = g_protocolHandlers[ url.protocol() ]; + + if( mimeHandler ) + { + mimeHandler->handleURL( url ); + return true; + } + else + { + kdDebug(14010) << "No mime type handler can handle this URL: " << url.prettyURL() << endl; + return false; + } + } +} + +bool MimeTypeHandler::dispatchToHandler( const KURL &url, const QString &mimeType, MimeTypeHandler *handler ) +{ + if( !handler->canAcceptRemoteFiles() ) + { + QString file; + if( !KIO::NetAccess::download( url, file, Kopete::UI::Global::mainWidget() ) ) + { + QString sorryText; + if ( url.isLocalFile() ) + { + sorryText = i18n( "Unable to find the file %1." ); + } + else + { + sorryText = i18n( "Unable to download the requested file;
    " + "please check that address %1 is correct.
    " ); + } + + KMessageBox::sorry( Kopete::UI::Global::mainWidget(), + sorryText.arg( url.prettyURL() ) ); + return false; + } + + KURL dest; + dest.setPath( file ); + + if( !mimeType.isNull() ) + handler->handleURL( mimeType, dest ); + else + handler->handleURL( dest ); + + // for now, local-only handlers have to be synchronous + KIO::NetAccess::removeTempFile( file ); + } + else + { + if( !mimeType.isNull() ) + handler->handleURL( mimeType, url ); + else + handler->handleURL( url ); + } + + return true; +} + +void MimeTypeHandler::handleURL( const KURL &url ) const +{ + Q_UNUSED( url ); +} + +void MimeTypeHandler::handleURL( const QString &mimeType, const KURL &url ) const +{ + Q_UNUSED( mimeType ); + Q_UNUSED( url ); +} + + +EmoticonMimeTypeHandler::EmoticonMimeTypeHandler() + : MimeTypeHandler( false ) +{ + registerAsMimeHandler( QString::fromLatin1("application/x-kopete-emoticons") ); + registerAsMimeHandler( QString::fromLatin1("application/x-tgz") ); + registerAsMimeHandler( QString::fromLatin1("application/x-tbz") ); +} + +void EmoticonMimeTypeHandler::handleURL( const QString &, const KURL &url ) const +{ + Global::installEmoticonTheme( url.path() ); +} + +} // END namespace Kopete + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetemimetypehandler.h b/kopete/libkopete/kopetemimetypehandler.h new file mode 100644 index 00000000..8f3235f0 --- /dev/null +++ b/kopete/libkopete/kopetemimetypehandler.h @@ -0,0 +1,133 @@ +/* + kopetemimetypehandler.h - Kopete Mime-type Handlers + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEMIMETYPEHANDLER_H +#define KOPETEMIMETYPEHANDLER_H + +class KURL; +class QString; +class QStringList; + +#include "kopete_export.h" + +namespace Kopete +{ + +/** + * @brief A handler for some set of mime-types + * A mime type handler is responsible for handling requests to open files of + * certain mime types presented to the main application. + */ +class KOPETE_EXPORT MimeTypeHandler +{ +protected: + MimeTypeHandler( bool canAcceptRemoteFiles = false ); +public: + virtual ~MimeTypeHandler(); + + /** + * Finds a MimeTypeHandler for a given URL, and tells that handler to handle it + * + * @param url the url to dispatch + * + * @return true if a handler was registered for the mime type, false otherwise + */ + static bool dispatchURL( const KURL &url ); + + /** + * Returns a list of mime types this object is registered to handle + */ + const QStringList mimeTypes() const; + + /** + * Returns a list of protocols this object is registered to handle + */ + const QStringList protocols() const; + + /** + * Returns true if this handler can accept remote files direcltly; + * If false, remote files are downloaded via KIO::NetAccess before + * being passed to handleURL + */ + bool canAcceptRemoteFiles() const; + + /** + * Handles the URL @p url + * + * @param url The url to handle + */ + virtual void handleURL( const KURL &url ) const; + + /** + * Handles the URL @p url, which has the mime type @p mimeType + * + * @param mimeType The mime type of the URL + * @param url The url to handle + */ + virtual void handleURL( const QString &mimeType, const KURL &url ) const; + +protected: + /** + * Register this object as the handler of type @p mimeType. + * @param mimeType the mime type to handle + * @return true if registration succeeded, false if another handler is + * already set for this mime type. + */ + bool registerAsMimeHandler( const QString &mimeType ); + + /** + * Register this object as the handler of type @p protocol. + * @param protocol the protocol to handle + * @return true if registration succeeded, false if another handler is + * already set for this protocol. + */ + bool registerAsProtocolHandler( const QString &protocol ); + +private: + /** + * Helper function. + * Attempts to dispatch a given URL to a given handler + * + * @param url The url to dispatch + * @param mimeType The mime type of the url + * @param handler The handler to attempt + * + * @return true if a handler was able to process the URL, false otherwise + */ + static bool dispatchToHandler( const KURL &url, const QString &mimeType, MimeTypeHandler *handler ); + + class Private; + Private *d; +}; + +/** + * Mime-type handler class for Kopete emoticon files + */ +class KOPETE_EXPORT EmoticonMimeTypeHandler : public MimeTypeHandler +{ +public: + EmoticonMimeTypeHandler(); + + const QStringList mimeTypes() const; + + void handleURL( const QString &mimeType, const KURL &url ) const; +}; + +} // Kopete + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetenotifydataobject.cpp b/kopete/libkopete/kopetenotifydataobject.cpp new file mode 100644 index 00000000..9a0de544 --- /dev/null +++ b/kopete/libkopete/kopetenotifydataobject.cpp @@ -0,0 +1,152 @@ +/* + kopetenotifydataobject.cpp - Container for notification events + + Copyright (c) 2004 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include +#include "kopetenotifydataobject.h" +#include "kopetenotifyevent.h" + +class Kopete::NotifyDataObject::Private +{ +public: + QDict events; +}; + +Kopete::NotifyDataObject::NotifyDataObject() +{ + d = new Private(); + d->events.setAutoDelete( true ); +} + +Kopete::NotifyDataObject::~NotifyDataObject() +{ + delete d; +} + +Kopete::NotifyEvent * Kopete::NotifyDataObject::notifyEvent( const QString &event ) const +{ + Kopete::NotifyEvent *evt = d->events.find( event ); + return evt; +} + +void Kopete::NotifyDataObject::setNotifyEvent( const QString& event, Kopete::NotifyEvent *notifyEvent ) +{ + d->events.replace( event, notifyEvent ); +} + +bool Kopete::NotifyDataObject::removeNotifyEvent( const QString &event ) +{ + return d->events.remove( event ); +} + +QDomElement Kopete::NotifyDataObject::notifyDataToXML() +{ + QDomDocument notify; + QDomElement notifications; + if ( !d->events.isEmpty() ) + { + // + notifications = notify.createElement( QString::fromLatin1( "custom-notifications" ) ); + QDictIterator it( d->events ); + for ( ; it.current(); ++it ) + { + // + QDomElement event = notify.createElement( QString::fromLatin1( "event" ) ); + event.setAttribute( QString::fromLatin1( "name" ), it.currentKey() ); + event.setAttribute( QString::fromLatin1( "suppress-common" ), QString::fromLatin1( it.current()->suppressCommon() ? "true" : "false" ) ); + QValueList presentations = it.current()->toXML(); + // + for ( QValueList::Iterator it = presentations.begin(); it != presentations.end(); ++it ) + event.appendChild( notify.importNode( *it, true ) ); + notifications.appendChild( event ); + } + } + return notifications; +} + +bool Kopete::NotifyDataObject::notifyDataFromXML( const QDomElement& element ) +{ + if ( element.tagName() == QString::fromLatin1( "custom-notifications" ) ) + { + QDomNode field = element.firstChild(); + while( !field.isNull() ) + { + //read an event + QDomElement fieldElement = field.toElement(); + if ( fieldElement.tagName() == QString::fromLatin1( "event" ) ) + { + // get its attributes + QString name = fieldElement.attribute( QString::fromLatin1( "name" ), QString::null ); + QString suppress = fieldElement.attribute( QString::fromLatin1( "suppress-common" ), QString::null ); + Kopete::NotifyEvent *evt = new Kopete::NotifyEvent( suppress == QString::fromLatin1( "true" ) ); + + // get its children + QDomNode child = fieldElement.firstChild(); + while( !child.isNull() ) + { + QDomElement childElement = child.toElement(); + if ( childElement.tagName() == QString::fromLatin1( "sound-presentation" ) ) + { +// kdDebug(14010) << k_funcinfo << "read: sound" << endl; + QString src = childElement.attribute( QString::fromLatin1( "src" ) ); + QString enabled = childElement.attribute( QString::fromLatin1( "enabled" ) ); + QString singleShot = childElement.attribute( QString::fromLatin1( "single-shot" ) ); + Kopete::EventPresentation *pres = new Kopete::EventPresentation( Kopete::EventPresentation::Sound, src, + ( singleShot == QString::fromLatin1( "true" ) ), + ( enabled == QString::fromLatin1( "true" ) ) ); + evt->setPresentation( Kopete::EventPresentation::Sound, pres ); +// kdDebug(14010) << k_funcinfo << "after sound: " << evt->toString() << endl; + } + if ( childElement.tagName() == QString::fromLatin1( "message-presentation" ) ) + { +// kdDebug(14010) << k_funcinfo << "read: msg" << endl; + QString src = childElement.attribute( QString::fromLatin1( "src" ) ); + QString enabled = childElement.attribute( QString::fromLatin1( "enabled" ) ); + QString singleShot = childElement.attribute( QString::fromLatin1( "single-shot" ) ); + Kopete::EventPresentation *pres = new Kopete::EventPresentation( Kopete::EventPresentation::Message, src, + ( singleShot == QString::fromLatin1( "true" ) ), + ( enabled == QString::fromLatin1( "true" ) ) ); + evt->setPresentation( Kopete::EventPresentation::Message, pres ); +// kdDebug(14010) << k_funcinfo << "after message: " << evt->toString() << endl; + } + if ( childElement.tagName() == QString::fromLatin1( "chat-presentation" ) ) + { +// kdDebug(14010) << k_funcinfo << "read: chat" << endl; + QString enabled = childElement.attribute( QString::fromLatin1( "enabled" ) ); + QString singleShot = childElement.attribute( QString::fromLatin1( "single-shot" ) ); + Kopete::EventPresentation *pres = new Kopete::EventPresentation( Kopete::EventPresentation::Chat, QString::null, + ( singleShot == QString::fromLatin1( "true" ) ), + ( enabled == QString::fromLatin1( "true" ) ) ); + evt->setPresentation( Kopete::EventPresentation::Chat, pres ); +// kdDebug(14010) << k_funcinfo << "after chat: " << evt->toString() << endl; + } + child = child.nextSibling(); + } +// kdDebug(14010) << k_funcinfo << "read: " << evt->toString() << endl; + setNotifyEvent( name, evt ); + } + field = field.nextSibling(); + } + return true; + } + else + { + kdDebug( 14010 ) << "element wasn't custom-notifications" << endl; + return false; + } +} + diff --git a/kopete/libkopete/kopetenotifydataobject.h b/kopete/libkopete/kopetenotifydataobject.h new file mode 100644 index 00000000..db253c60 --- /dev/null +++ b/kopete/libkopete/kopetenotifydataobject.h @@ -0,0 +1,58 @@ +/* + kopetenotifydataobject.h - Kopete Custom Notify Data Object + + Copyright (c) 2004 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ +#ifndef KOPETENOTIFYDATAOBJECT_H +#define KOPETENOTIFYDATAOBJECT_H + +#include +#include +#include + +#include "kopete_export.h" + +class QDomElement; + +namespace Kopete +{ + +class NotifyEvent; + +/** + * Contains custom notification control and storage functionality + */ + +class KOPETE_EXPORT NotifyDataObject +{ + public: + NotifyDataObject(); + ~NotifyDataObject(); + // Notify events + NotifyEvent *notifyEvent( const QString &event ) const; + void setNotifyEvent( const QString &event, + NotifyEvent *notifyEvent ); + bool removeNotifyEvent( const QString &event ); + // Serialization + protected: + QDomElement notifyDataToXML(); + bool notifyDataFromXML( const QDomElement& element ); + private: + class Private; + NotifyDataObject::Private* d; +}; + +} + +#endif diff --git a/kopete/libkopete/kopetenotifyevent.cpp b/kopete/libkopete/kopetenotifyevent.cpp new file mode 100644 index 00000000..28c4ab15 --- /dev/null +++ b/kopete/libkopete/kopetenotifyevent.cpp @@ -0,0 +1,181 @@ +/* + kopetenotifyevent.h - Kopete Notifications for a given event + + Copyright (c) 2004 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include +#include "kopetenotifyevent.h" +#include "kopeteeventpresentation.h" + +Kopete::NotifyEvent::NotifyEvent( const bool suppressCommon ) +{ + m_suppressCommon = suppressCommon; + m_message = 0; + m_chat = 0; + m_sound = 0; +} + +Kopete::NotifyEvent::~NotifyEvent() +{ + delete m_sound; + delete m_message; + delete m_chat; +} + +bool Kopete::NotifyEvent::suppressCommon() const +{ + return m_suppressCommon; +} + +Kopete::EventPresentation *Kopete::NotifyEvent::presentation( const Kopete::EventPresentation::PresentationType type ) const +{ + switch ( type ) + { + case Kopete::EventPresentation::Sound: + return m_sound; + case Kopete::EventPresentation::Message: + return m_message; + case Kopete::EventPresentation::Chat: + return m_chat; + default: + return 0; + } +} + +void Kopete::NotifyEvent::removePresentation( const Kopete::EventPresentation::PresentationType type ) +{ + Kopete::EventPresentation **presToChange; + switch ( type ) + { + case Kopete::EventPresentation::Sound: + presToChange = &m_sound; + break; + case Kopete::EventPresentation::Message: + presToChange = &m_message; + break; + case Kopete::EventPresentation::Chat: + presToChange = &m_chat; + break; + default: + kdDebug( 14010 ) << k_funcinfo << " Someone tried to set an unrecognised type of presentation!" << endl; + return; + } + if ( *presToChange ) + { + delete *presToChange; + *presToChange = 0; + } +} + +void Kopete::NotifyEvent::setPresentation( const Kopete::EventPresentation::PresentationType type, Kopete::EventPresentation * notification ) +{ + Kopete::EventPresentation **presToChange; + switch ( type ) + { + case Kopete::EventPresentation::Sound: + presToChange = &m_sound; + break; + case Kopete::EventPresentation::Message: + presToChange = &m_message; + break; + case Kopete::EventPresentation::Chat: + presToChange = &m_chat; + break; + default: + kdDebug( 14010 ) << k_funcinfo << " Someone tried to set an unrecognised type of presentation!" << endl; + return; + } + if ( *presToChange ) + delete *presToChange; + *presToChange = notification; +} + +bool Kopete::NotifyEvent::firePresentation( const Kopete::EventPresentation::PresentationType type ) +{ + kdDebug( 14010 ) << k_funcinfo << endl; + Kopete::EventPresentation **presToChange; + switch ( type ) + { + case Kopete::EventPresentation::Sound: + presToChange = &m_sound; + break; + case Kopete::EventPresentation::Message: + presToChange = &m_message; + break; + case Kopete::EventPresentation::Chat: + presToChange = &m_chat; + break; + default: + return false; + } + kdDebug( 14010 ) << toString() << endl; + if ( *presToChange && (*presToChange)->singleShot() ) + { + kdDebug( 14010 ) << " removing singleshot!" << endl; + delete *presToChange; + *presToChange = 0; + kdDebug( 14010 ) << toString() << endl; + return true; + } + return false; +} + +void Kopete::NotifyEvent::setSuppressCommon( const bool suppress ) +{ + m_suppressCommon = suppress; +} + +const QValueList Kopete::NotifyEvent::toXML() const +{ + QDomDocument eventData; + QValueList eventNodes; + if ( m_sound && !m_sound->content().isEmpty() ) + { + QDomElement soundElmt = eventData.createElement( QString::fromLatin1( "sound-presentation" ) ); + soundElmt.setAttribute( QString::fromLatin1( "enabled" ), QString::fromLatin1( m_sound->enabled() ? "true" : "false" ) ); + soundElmt.setAttribute( QString::fromLatin1( "single-shot" ), QString::fromLatin1( m_sound->singleShot() ? "true" : "false" ) ); + soundElmt.setAttribute( QString::fromLatin1( "src" ), m_sound->content() ); + eventNodes.append( soundElmt ); + } + if ( m_message && !m_message->content().isEmpty() ) + { + QDomElement msgElmt = eventData.createElement( QString::fromLatin1( "message-presentation" ) ); + msgElmt.setAttribute( QString::fromLatin1( "enabled" ), QString::fromLatin1( m_message->enabled() ? "true" : "false" ) ); + msgElmt.setAttribute( QString::fromLatin1( "single-shot" ), QString::fromLatin1( m_message->singleShot() ? "true" : "false" ) ); + msgElmt.setAttribute( QString::fromLatin1( "src" ), m_message->content() ); + eventNodes.append( msgElmt ); + } + if ( m_chat && m_chat->enabled() ) + { + QDomElement chatElmt = eventData.createElement( QString::fromLatin1( "chat-presentation" ) ); + chatElmt.setAttribute( QString::fromLatin1( "enabled" ), QString::fromLatin1( "true" ) ); + chatElmt.setAttribute( QString::fromLatin1( "single-shot" ), QString::fromLatin1( m_chat->singleShot() ? "true" : "false" ) ); + eventNodes.append( chatElmt ); + } + return eventNodes; +} + +QString Kopete::NotifyEvent::toString() +{ + QString stringRep = QString::fromLatin1("Event; Suppress common=%1").arg( QString::fromLatin1( suppressCommon() ? "true" : "false" ) ); + if ( m_sound) + stringRep += m_sound->toString(); + if ( m_message) + stringRep += m_message->toString(); + if ( m_chat) + stringRep += m_chat->toString(); + return stringRep; +} diff --git a/kopete/libkopete/kopetenotifyevent.h b/kopete/libkopete/kopetenotifyevent.h new file mode 100644 index 00000000..b7acd3c1 --- /dev/null +++ b/kopete/libkopete/kopetenotifyevent.h @@ -0,0 +1,60 @@ +/* + kopetenotifyevent.h - Container for presentations of an event + + Copyright (c) 2004 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETENOTIFYEVENT_H +#define KOPETENOTIFYEVENT_H + +#include +#include +#include "kopeteeventpresentation.h" + +#include "kopete_export.h" + +class QDomElement; + +namespace Kopete +{ + +class KOPETE_EXPORT NotifyEvent +{ +public: + NotifyEvent( const bool suppressCommon = false ); + ~NotifyEvent(); + + bool suppressCommon() const; + EventPresentation *presentation( const EventPresentation::PresentationType type ) const; + void setPresentation( const EventPresentation::PresentationType type, EventPresentation * ); + void removePresentation( const EventPresentation::PresentationType type ); + /** + * @return true if the presentation was single shot + */ + bool firePresentation( const EventPresentation::PresentationType type ); + + void setSuppressCommon( bool suppress ); + const QValueList toXML() const; + QString toString(); +private: + QString m_event; + EventPresentation *m_sound; + EventPresentation *m_message; + EventPresentation *m_chat; + bool m_suppressCommon; +}; + +} + +#endif diff --git a/kopete/libkopete/kopeteonlinestatus.cpp b/kopete/libkopete/kopeteonlinestatus.cpp new file mode 100644 index 00000000..8872f28b --- /dev/null +++ b/kopete/libkopete/kopeteonlinestatus.cpp @@ -0,0 +1,302 @@ +/* + kopeteonlinestatus.cpp - Kopete Online Status + + Copyright (c) 2003 by Martijn Klingens + Copyright (c) 2003 by Duncan Mac-Vicar Prett + Copyright (c) 2003 by Will Stephenson + Copyright (c) 2004 by Olivier Goffart + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteonlinestatus.h" +#include "kopeteonlinestatusmanager.h" + +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopetecontact.h" +#include +#include +#include +#include +#include +#include + + + +using namespace Kopete; + +class OnlineStatus::Private + : public KShared +{ +public: + StatusType status; + unsigned weight; + Protocol *protocol; + unsigned internalStatus; + QStringList overlayIcons; + QString description; + unsigned refCount; + + QString protocolIcon() const + { + return protocol ? protocol->pluginIcon() : QString::fromLatin1( "unknown" ); + } + +}; + +/** + * This is required by some plugins, when a status need to be stored on + * the disk, to avoid problems. + */ +static struct +{ + OnlineStatus::StatusType status; + const char *name; +} statusNames[] = { + { OnlineStatus::Unknown, "Unknown" }, + { OnlineStatus::Offline, "Offline" }, + { OnlineStatus::Connecting, "Connecting" }, + { OnlineStatus::Invisible, "Invisible" }, + { OnlineStatus::Online, "Online"}, + { OnlineStatus::Away, "Away" } }; + +OnlineStatus::OnlineStatus( StatusType status, unsigned weight, Protocol *protocol, + unsigned internalStatus, const QStringList &overlayIcons, const QString &description ) + : d( new Private ) +{ + d->status = status; + d->internalStatus = internalStatus; + d->weight = weight; + d->overlayIcons = overlayIcons; + d->protocol = protocol; + d->description = description; +} + +OnlineStatus::OnlineStatus( StatusType status, unsigned weight, Protocol *protocol, unsigned internalStatus, + const QStringList &overlayIcons, const QString &description, const QString &caption, unsigned int categories , unsigned int options ) + : d( new Private ) +{ + d->status = status; + d->internalStatus = internalStatus; + d->weight = weight; + d->overlayIcons = overlayIcons; + d->protocol = protocol; + d->description = description; + + OnlineStatusManager::self()->registerOnlineStatus(*this, caption, categories, options ); +} + +OnlineStatus::OnlineStatus( StatusType status ) + : d( new Private ) +{ + d->status = status; + d->internalStatus = 0; + d->weight = 0; + d->protocol = 0L; + + switch( status ) + { + case Online: + d->description = i18n( "Online" ); + break; + case Away: + d->description = i18n( "Away" ); + break; + case Connecting: + d->description = i18n( "Connecting" ); + break; + case Invisible: + d->description = i18n( "Invisible" ); + break; + case Offline: + d->description = i18n( "Offline" ); + break; + case Unknown: + default: + d->description = i18n( "Unknown" ); + d->overlayIcons = QString::fromLatin1("status_unknown"); + break; + + } +} + +OnlineStatus::OnlineStatus() + : d( new Private ) +{ + d->status = Unknown; + d->internalStatus = 0; + d->weight = 0; + d->protocol = 0L; + d->overlayIcons = QString::fromLatin1( "status_unknown" ); +} + +OnlineStatus::OnlineStatus( const OnlineStatus &other ) + : d( other.d ) +{ +} + +bool OnlineStatus::operator==( const OnlineStatus &other ) const +{ + if ( d->internalStatus == other.d->internalStatus && d->protocol == other.d->protocol && + d->weight == other.d->weight && d->overlayIcons == other.d->overlayIcons && + d->description == other.d->description ) + { + return true; + } + + return false; +} + +bool OnlineStatus::operator!=( const OnlineStatus &other ) const +{ + return !(*this == other); +} + + +bool OnlineStatus::operator>( const OnlineStatus &other ) const +{ + if( d->status == other.d->status ) + return d->weight > other.d->weight; + else + return d->status > other.d->status; +} + +bool OnlineStatus::operator<( const OnlineStatus &other ) const +{ + if( d->status == other.d->status ) + return d->weight < other.d->weight; + else + return d->status < other.d->status; +} + +OnlineStatus & OnlineStatus::operator=( const OnlineStatus &other ) +{ + d = other.d; + return *this; +} + +OnlineStatus::~OnlineStatus() +{ +} + +OnlineStatus::StatusType OnlineStatus::status() const +{ + return d->status; +} + +unsigned OnlineStatus::internalStatus() const +{ + return d->internalStatus; +} + +unsigned OnlineStatus::weight() const +{ + return d->weight; +} + +QStringList OnlineStatus::overlayIcons() const +{ + return d->overlayIcons; +} + +QString OnlineStatus::description() const +{ + return d->description; +} + +Protocol* OnlineStatus::protocol() const +{ + return d->protocol; +} + +bool OnlineStatus::isDefinitelyOnline() const +{ + if ( status() == Offline || status() == Connecting || status() == Unknown ) + return false; + return true; +} + +QPixmap OnlineStatus::iconFor( const Contact *contact, int size ) const +{ + return OnlineStatusManager::self()->cacheLookupByMimeSource( mimeSourceFor( contact, size ) ); +} + + +QString OnlineStatus::mimeSourceFor( const Contact *contact, int size ) const +{ + // figure out what icon we should use for this contact + QString iconName = contact->icon(); + if ( iconName.isNull() ) + iconName = contact->account()->customIcon(); + if ( iconName.isNull() ) + iconName = d->protocolIcon(); + + + return mimeSource( iconName, size, contact->account()->color(),contact->idleTime() >= 10*60 ); +} + +QPixmap OnlineStatus::iconFor( const Account *account, int size ) const +{ + return OnlineStatusManager::self()->cacheLookupByMimeSource( mimeSourceFor( account, size ) ); +} + +QString OnlineStatus::mimeSourceFor( const Account *account, int size ) const +{ + QString iconName = account->customIcon(); + if ( iconName.isNull() ) + iconName = d->protocolIcon(); + + return mimeSource( iconName, size, account->color(), false ); +} + +QPixmap OnlineStatus::iconFor( const QString &mimeSource ) const +{ + return OnlineStatusManager::self()->cacheLookupByMimeSource( mimeSource ); +} + +QPixmap OnlineStatus::protocolIcon() const +{ + return OnlineStatusManager::self()->cacheLookupByObject( *this, d->protocolIcon() , 16, QColor() ); +} + +QString OnlineStatus::mimeSource( const QString& icon, int size, QColor color, bool idle) const +{ + // make sure the item is in the cache + OnlineStatusManager::self()->cacheLookupByObject( *this, icon, size, color, idle ); + // now return the fingerprint instead + return OnlineStatusManager::self()->fingerprint( *this, icon, size, color, idle ); +} + +QString OnlineStatus::statusTypeToString(OnlineStatus::StatusType statusType) +{ + const int size = sizeof(statusNames) / sizeof(statusNames[0]); + + for (int i=0; i< size; i++) + if (statusNames[i].status == statusType) + return QString::fromLatin1(statusNames[i].name); + + return QString::fromLatin1(statusNames[0].name); // Unknown +} + +OnlineStatus::StatusType OnlineStatus::statusStringToType(QString& string) +{ + int size = sizeof(statusNames) / sizeof(statusNames[0]); + + for (int i=0; i< size; i++) + if (QString::fromLatin1(statusNames[i].name) == string) + return statusNames[i].status; + + return OnlineStatus::Unknown; +} + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteonlinestatus.h b/kopete/libkopete/kopeteonlinestatus.h new file mode 100644 index 00000000..2eed5164 --- /dev/null +++ b/kopete/libkopete/kopeteonlinestatus.h @@ -0,0 +1,415 @@ +/* + kopeteonlinestatus.h - Kopete Online Status + + Copyright (c) 2003 by Martijn Klingens + Copyright (c) 2003 by Duncan Mac-Vicar Prett + Copyright (c) 2003 by Will Stephenson + Copyright (c) 2004 by Olivier Goffart + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef kopeteonlinestatus_h +#define kopeteonlinestatus_h + +#include "kopete_export.h" + +#include +#include + +#include + +class QString; +class QPixmap; +class QColor; + +namespace Kopete +{ + + class OnlineStatusManager; + class Protocol; + class Account; + class Contact; + +/** + * @author Martijn Klingens + * @author Will Stephenson (icon generating code) + * + * OnlineStatus is a class that encapsulates all information about the + * various online states that a protocol can be in in a single class. The + * online status consists of both a 'global' status as it's known to libkopete + * and used for going online or away, which non-protocol plugins can use, + * and the 'private' status, which is simply an unsigned int and is only + * useful for the actual protocol plugin that uses the status. + * + * This class is passed around by value, but is refcounted to cut down on the + * amount of overhead. All in all it should be more than fast enough for + * general use. + * + * Note that ONLY the constructor can set the data, the object is considered + * to be const after creation as there really shouldn't be a need to change + * a status' characteristics during runtime! + */ +class KOPETE_EXPORT OnlineStatus +{ +public: + /** + * The available global states. It is possible that multiple internal + * states map to the same global states. For example ICQ's 'Do not disturb' + * is handled just like 'Away' by libkopete. Only ICQ itself makes (and + * should make) a distinction. + * The order is important and is used in the < or > operator + */ + enum StatusType + { + /** + * Refers to protocols where state cannot be determined. This + * applies to SMS contacts (text messages via mobile phones), + * since there's no presence information over SMS, but also + * to e.g. MSN contacts that are not on your contact list, + * since MSN only allows a user to query online state for + * users that are formally on the contact list. Lastly, libkopete + * itself uses the Unknown state in @ref MetaContact for + * meta contacts that have no child contacts at all. + */ + Unknown=0, + /** + * State where you really cannot be contacted. Although + * Kopete doesn't oppose any technical limitations it really + * doesn't make sense to have more than one status per protocol + * that maps to 'Offline', since you're supposed to be + * disconnected from the network in this state. + */ + Offline=10, + /** + * State where the user is not available on the network yet + * but trying to get onto. Most useful to yourself contact, because + * this state means not visible but with network access + */ + Connecting=20, + /** + * State where you are online but none of your contacts can + * see that you're online. Useful for all the protocols that support + * being invisible. + */ + Invisible=30, + /** + * Refers to a state where you can be technically reached, but + * for one reason or another it is often not useful to do so. + * This can be because you really aren't behind the computer + * ('Away' or 'Idle') or because you have other things to do + * and don't want to get involved in messaging ('Busy' or 'Do + * not Disturb' for example). + */ + Away=40, + /** + * Refers to a true online state, i.e. you can be contacted by + * others both technically and practically. This also applies + * to e.g. ICQ's 'Free for Chat' status. + */ + Online=50 + }; + // note than Unknown is first, because the metacontact algorithm to detect + // the metacontact status from the contact status starts from Unknown, and + // takes a contact only if its status is greater + + /** + * Reserved internal status values + * + * Any internal status value > 0x80000000 is reserved for internal + * libkopete use. This enumeration lists the currently known values. + */ + enum ReservedInternalStatus + { + /** + * The account this contact belongs to is offline. Used with + * the Unknown StatusType. + */ + AccountOffline = 0x80000001 + }; + + + /** + * Constructor. + * + * Creates an empty OnlineStatus object. Since you cannot change + * OnlineStatus objects that are already created other than by their + * assignment operator, this constructor is only a convenience method + * for use in e.g. class members and local variables. + */ + OnlineStatus(); + + + /** + * Constructor. + * + * Creates a new OnlineStatus object. All fields are mandatory; there + * are no default values. Also, you cannot change the object after creation. + * + * @param status is the global online status as used by libkopete + * @param weight is the 'weight' of this status. The contact list is + * sorted by status, and by weight within a status. It's not possible to + * 'promote' an Away item to a level above Online, since the status field + * always takes precedence. Weight is used when the same status is used + * more than once. Weight is also used for picking the most important + * 'Away' status for a protocol when going Away. + * @param protocol is a pointer to the protocol used. This is used when + * comparing two online status objects. + * @param internalStatus is the status as used internally by the protocol. + * This status is usually a lot more fine-grained than the status as used + * by libkopete and should be unique per protocol. + * @param overlayIcons is a list of QStrings which are the name of status + * icons to be used by the KDE icon loader. (Statuses which don't have icons + * to overlay like Online and Offline should use QString::null as icon + * name ). NOTE if the string is a movie ( *.mng ) it must be the first string in the list. + * TODO: KDE4 sort out movies and overlay icons. + * @param description is a description in e.g. tooltips. + */ + OnlineStatus( StatusType status, unsigned weight, Protocol *protocol, + unsigned internalStatus, const QStringList &overlayIcons, const QString &description ); + + /** + * Constructor. + * + * @p Creates a new OnlineStatus object and registers it with the @ref Kopete::OnlineStatusManager. + * Registration allows you to generate a KActionMenu filled with KActions for changing to this OnlineStatus, + * using Kopete::Account::accountMenu(). + * + * @p Note that weight has an additional significance for registered protocols when used for menu generation. + * + * All fields are mandatory; there + * are no default values. Also, you cannot change the object after creation. + * + * @param status is the global online status as used by libkopete + * @param weight is the 'weight' of this status. The contact list is + * sorted by status, and by weight within a status. It's not possible to + * 'promote' an Away item to a level above Online, since the status field + * always takes precedence. Weight is used when the same status is used + * more than once. Weight is also used for picking the most important + * 'Away' status for a protocol when going Away. Additionally, Weight determinesis also + * @param protocol is a pointer to the protocol used. This is used when + * comparing two online status objects. + * @param internalStatus is the status as used internally by the protocol. + * This status is usually a lot more fine-grained than the status as used + * by libkopete and should be unique per protocol. + * @param overlayIcon is a string returning the name of the status icon to be + * used by the KDE icon loader. (Status whiwh doesn't have icon to overlay like + * Online and Offline should use QString::null as icon string) + * @param description is a description in e.g. tooltips. + * @param caption is the text of the action in the menu + * @param categories the categories this online status is in + * @param options the options of this online status + * @see Kopete::OnlineStatusManager::registerOnlineStatus for more info about the categories and options parameters + */ + OnlineStatus( StatusType status, unsigned weight, Protocol *protocol, unsigned internalStatus, const QStringList &overlayIcon, + const QString &description, const QString& caption, unsigned int categories=0x0 , unsigned int options=0x0 ); + + + /** + * Constructor. + * + * Creates a libkopete builtin status object. Weight, protocol and internal + * status are set to zero, the strings and icons are set to the meta contact + * strings. + */ + OnlineStatus( StatusType status ); + + /** + * Copy constructor. + * + * Just adds a reference to the refcount. Used to copy around the status + * objects with very little overhead. + */ + OnlineStatus( const OnlineStatus &other ); + + /** + * Destructor. + */ + ~OnlineStatus(); + + /** + * \brief Return the status + */ + StatusType status() const; + + /** + * \brief Return the internal status + */ + unsigned internalStatus() const; + + /** + * \brief Return the weight + */ + unsigned weight() const; + + /** + * \brief Return the list of overlay icons + */ + QStringList overlayIcons() const; + + /** + * \brief Return the description + */ + QString description() const; + + /** + * \brief Return the protocol this applies to + */ + Protocol* protocol() const; + + /** + * @return @c true if this a contact with this status is definitely online, + * @c false if the contact is Offline, Connecting or Unknown. + */ + bool isDefinitelyOnline() const; + + + /** + * \brief Return a status icon generated for the given Contact + * + * This will draw an overlay representing the online status + * of the contact the OnlineStatus applies to + * over the base icon. + * A cache is employed to reduce CPU and memory usage. + * @param contact is the contact the icon should apply to. + * @param size is the size we the icon should be scaled to - 16 is default and so costs nothing + */ + QPixmap iconFor( const Contact *contact, int size = 16 ) const; + + /** + * \brief Return the mime source for a status icon generated for the given Contact + * + * This behaves essentially like the method above, except for that + * it returns a mime source string that can be used to render the + * image in richtext components and the like. The returned key + * is only valid until the cache is cleared for the next time, + * so no assumptions should be made about long-time availability + * of the referenced data. + * @param contact is the contact the icon should apply to. + * @param size is the size we the icon should be scaled to - 16 is default and so costs nothing + */ + QString mimeSourceFor( const Contact *contact, int size = 16 ) const; + + /** + * \brief Return a status icon generated for the given Account + * + * This will draw an overlay representing the online status + * of the account the OnlineStatus applies to + * over the base icon. + * A cache is employed to reduce CPU and memory usage. + * @param account is the account the icon should apply to. + * The account's color causes tinting, if it's plain QColor(), no tinting takes place. + * @param size is the size we the icon should be scaled to - 16 is default and so costs nothing + */ + QPixmap iconFor( const Account *account, int size = 16 ) const; + + /** + * \brief Return the mime source for a status icon generated for the given Account + * + * This behaves essentially like the method above, except for that + * it returns a mime source string that can be used to render the + * image in richtext components and the like. The returned key + * is only valid until the cache is cleared for the next time, + * so no assumptions should be made about long-time availability + * of the referenced data. + * @param account is the account the icon should apply to. + * The account's color causes tinting, if it's plain QColor(), no tinting takes place. + * @param size is the size we the icon should be scaled to - 16 is default and so costs nothing + */ + QString mimeSourceFor( const Account *account, int size = 16 ) const; + + /** + * \brief Return a previously rendered status icon for a mime source key + * + * You can access icons with this method that have previously been rendered + * using mimeSourceFor(). Note that only a cache lookup will be done, so + * if the cache has been invalidated due to a change of icon sets between + * requesting the key (thus rendering the icon) and trying to access the + * icon by key, an invalid pixmap will be returned. + */ + QPixmap iconFor( const QString &mimeSource ) const; + + /** + * \brief Returns the status icon for the protocol. + * + * A cache is employed to reduce CPU and memory usage. + */ + QPixmap protocolIcon() const; + + /** + * Assignment operator + */ + OnlineStatus & operator=( const OnlineStatus &other ); + + /** + * Comparison operator + * + * Returns true if both the protocol and the internal status are + * identical. + */ + bool operator==( const OnlineStatus &other ) const; + + /** + * Comparison operator + * + * This operator works exactly opposite of @ref operator==() + */ + bool operator!=( const OnlineStatus &other ) const; + + /** + * Comparison operator + * + * Returns true if the status() of this contact is of higher value than the other + * contact or if both statuses are equal and weight() is higher for this contact. + */ + bool operator>( const OnlineStatus &other ) const; + + /** + * Comparison operator + * + * This operator works exactly opposite of @ref operator>() + */ + bool operator<( const OnlineStatus &other ) const; + + /** + * \brief returns a QString from a StatusType + * + * Static method to convert a Kopete::OnlineStatus::StatusType to a string to avoid + * many issues when saving StatusType to disk + */ + static QString statusTypeToString(OnlineStatus::StatusType status); + + /** + * \brief returns a StatusType from a QString + * + * Static method to convert a QString representing a StatusType to a StatusType to avoid + * many issues when saving StatusType to disk + */ + static OnlineStatus::StatusType statusStringToType(QString& string); + + + +private: + + class Private; + KSharedPtr d; + + QString mimeSource( const QString& icon, int size, QColor color, bool idle) const; + + +}; + +} //END namespace Kopete + +#endif + + diff --git a/kopete/libkopete/kopeteonlinestatusmanager.cpp b/kopete/libkopete/kopeteonlinestatusmanager.cpp new file mode 100644 index 00000000..61c41b83 --- /dev/null +++ b/kopete/libkopete/kopeteonlinestatusmanager.cpp @@ -0,0 +1,436 @@ +/* + kopeteonlinestatusmanager.cpp + + Copyright (c) 2004 by Olivier Goffart + Copyright (c) 2003 by Will Stephenson + + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteonlinestatusmanager.h" + +#include "kopeteawayaction.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopetecontact.h" + +#include +#include +#include +#include +#include +#include +#include // for WORDS_BIGENDIAN + +#include // for min + +namespace Kopete { + + +class OnlineStatusManager::Private +{public: + + struct RegisteredStatusStruct + { + QString caption; + unsigned int categories; + unsigned int options; + }; + + typedef QMap< OnlineStatus , RegisteredStatusStruct > ProtocolMap ; + + QPixmap *nullPixmap; + QMap registeredStatus; + QDict< QPixmap > iconCache; +}; + +OnlineStatusManager *OnlineStatusManager::s_self=0L; + +OnlineStatusManager *OnlineStatusManager::self() +{ + static KStaticDeleter deleter; + if(!s_self) + deleter.setObject( s_self, new OnlineStatusManager() ); + return s_self; +} + +OnlineStatusManager::OnlineStatusManager() + : d( new Private ) +{ + d->iconCache.setAutoDelete( true ); + d->nullPixmap = new QPixmap; + connect( kapp, SIGNAL( iconChanged(int) ), this, SLOT( slotIconsChanged() ) ); +} + +OnlineStatusManager::~OnlineStatusManager() +{ + delete d->nullPixmap; + delete d; +} + +void OnlineStatusManager::slotIconsChanged() +{ + d->iconCache.clear(); + emit iconsChanged(); +} + +void OnlineStatusManager::registerOnlineStatus( const OnlineStatus &status, const QString & caption, unsigned int categories, unsigned int options) +{ + Private::RegisteredStatusStruct s; + s.caption=caption; + s.categories=categories; + s.options=options; + d->registeredStatus[status.protocol()].insert(status, s ); +} + +OnlineStatus OnlineStatusManager::onlineStatus(Protocol * protocol, Categories category) const +{ + /* Each category has a number which is a power of two, so it is possible to have several categories per online status + * the logaritm in base two if this number, which represent the bit which is equal to 1 in the number is chosen to be in a tree + * 1 (0 is reserved for Offline) + * / \ + * 2 3 + * / \ / \ + * 4 5 6 7 + * /\ / \ / \ / \ + * 8 9 10 11 12 13 14 15 + * To get the parent of a key, one just divide per two the number + */ + + Private::ProtocolMap protocolMap=d->registeredStatus[protocol]; + + int categ_nb=-1; //the logaritm of category + uint category_=category; + while(category_) + { + category_ >>= 1; + categ_nb++; + } //that code will give the log +1 + + do + { + Private::ProtocolMap::Iterator it; + for ( it = protocolMap.begin(); it != protocolMap.end(); it++ ) + { + unsigned int catgs=it.data().categories; + if(catgs & (1<<(categ_nb))) + return it.key(); + } + //no status found in this category, try the previous one. + categ_nb=(int)(categ_nb/2); + } while (categ_nb > 0); + + kdWarning() << "No status in the category " << category << " for the protocol " << protocol->displayName() <iconCache.find( fp ); + if ( !theIcon ) + { + // cache miss +// kdDebug(14010) << k_funcinfo << "Missed " << fingerprint << " in icon cache!" << endl; + theIcon = renderIcon( statusFor, icon, size, color, idle); + d->iconCache.insert( fp, theIcon ); + } + return *theIcon; +} + +QPixmap OnlineStatusManager::cacheLookupByMimeSource( const QString &mimeSource ) +{ + // look it up in the cache + const QPixmap *theIcon= d->iconCache.find( mimeSource ); + if ( !theIcon ) + { + // need to return an invalid pixmap + theIcon = d->nullPixmap; + } + return *theIcon; +} + +// This code was forked from the broken KImageEffect::blendOnLower, but it's +// been so heavily fixed and rearranged it's hard to recognise that now. +static void blendOnLower( const QImage &upper_, QImage &lower, const QPoint &offset ) +{ + if ( upper_.width() <= 0 || upper_.height() <= 0 ) + return; + if ( lower.width() <= 0 || lower.height() <= 0 ) + return; + if ( offset.x() < 0 || offset.x() >= lower.width() ) + return; + if ( offset.y() < 0 || offset.y() >= lower.height() ) + return; + + QImage upper = upper_; + if ( upper.depth() != 32 ) + upper = upper.convertDepth( 32 ); + if ( lower.depth() != 32 ) + lower = lower.convertDepth( 32 ); + + const int cx = offset.x(); + const int cy = offset.y(); + const int cw = std::min( upper.width() + cx, lower.width() ); + const int ch = std::min( upper.height() + cy, lower.height() ); + const int m = 255; + + for ( int j = cy; j < ch; ++j ) + { + QRgb *u = (QRgb*)upper.scanLine(j - cy); + QRgb *l = (QRgb*)lower.scanLine(j) + cx; + + for( int k = cx; k < cw; ++u, ++l, ++k ) + { + int ua = qAlpha(*u); + if ( !ua ) + continue; + + int la = qAlpha(*l); + + int d = ua * m + la * (m - ua); + uchar r = uchar( ( qRed(*u) * ua * m + qRed(*l) * la * (m - ua) ) / d ); + uchar g = uchar( ( qGreen(*u) * ua * m + qGreen(*l) * la * (m - ua) ) / d ); + uchar b = uchar( ( qBlue(*u) * ua * m + qBlue(*l) * la * (m - ua) ) / d ); + uchar a = uchar( ( ua * ua * m + la * la * (m - ua) ) / d ); + *l = qRgba( r, g, b, a ); + } + } +} + +// Get bounding box of image via alpha channel +static QRect getBoundingBox( const QImage& image ) +{ + const int width = image.width(); + const int height = image.height(); + if ( width <= 0 || height <= 0 ) + return QRect(); + + // scan image from left to right and top to bottom + // to get upper left corner of bounding box + int x1 = width - 1; + int y1 = height - 1; + for ( int j = 0; j < height; ++j ) + { + QRgb *i = (QRgb*)image.scanLine(j); + + for( int k = 0; k < width; ++i, ++k ) + { + if ( qAlpha(*i) ) + { + x1 = std::min( x1, k ); + y1 = std::min( y1, j ); + break; + } + } + } + + // scan image from right to left and bottom to top + // to get lower right corner of bounding box + int x2 = 0; + int y2 = 0; + for ( int j = height-1; j >= 0; --j ) + { + QRgb *i = (QRgb*)image.scanLine(j) + width-1; + + for( int k = width-1; k >= 0; --i, --k ) + { + if ( qAlpha(*i) ) + { + x2 = std::max( x2, k ); + y2 = std::max( y2, j ); + break; + } + } + } + return QRect( x1, y1, std::max( 0, x2-x1+1 ), std::max( 0, y2-y1+1 ) ); +} + +// Get offset for upperImage to blend it in the i%4-th corner of lowerImage: +// bottom right, bottom left, top left, top right +static QPoint getOffsetForCorner( const QImage& upperImage, const QImage& lowerImage, const int i ) +{ + const int dX = lowerImage.width() - upperImage.width(); + const int dY = lowerImage.height() - upperImage.height(); + const int corner = i % 4; + QPoint offset; + switch( corner ) { + case 0: + // bottom right + offset = QPoint( dX, dY ); + break; + case 1: + // bottom left + offset = QPoint( 0, dY ); + break; + case 2: + // top left + offset = QPoint( 0, 0 ); + break; + case 3: + // top right + offset = QPoint( dX, 0 ); + break; + } + return offset; +} + +QPixmap* OnlineStatusManager::renderIcon( const OnlineStatus &statusFor, const QString& baseIcon, int size, QColor color, bool idle) const +{ + // create an icon suiting the status from the base icon + // use reasonable defaults if not provided or protocol not set + + if ( baseIcon == statusFor.overlayIcons().first() ) + kdWarning( 14010 ) << "Base and overlay icons are the same - icon effects will not be visible." << endl; + + QPixmap* basis = new QPixmap( SmallIcon( baseIcon ) ); + + // Colorize + if ( color.isValid() ) + *basis = KIconEffect().apply( *basis, KIconEffect::Colorize, 1, color, 0); + + // Note that we do this before compositing the overlay, since we want + // that to be colored in this case. + if ( statusFor.internalStatus() == Kopete::OnlineStatus::AccountOffline || statusFor.status() == Kopete::OnlineStatus::Offline ) + { + *basis = KIconEffect().apply( *basis, KIconEffect::ToGray , 0.85, QColor() , false ); + } + + //composite the iconOverlay for this status and the supplied baseIcon + QStringList overlays = statusFor.overlayIcons(); + if ( !( overlays.isEmpty() ) ) // otherwise leave the basis as-is + { + KIconLoader *loader = KGlobal::instance()->iconLoader(); + + int i = 0; + for( QStringList::iterator it = overlays.begin(), end = overlays.end(); it != end; ++it ) + { + QPixmap overlay = loader->loadIcon(*it, KIcon::Small, 0 , + KIcon::DefaultState, 0L, /*canReturnNull=*/ true ); + + if ( !overlay.isNull() ) + { + // we want to preserve the alpha channels of both basis and overlay. + // there's no way to do this in Qt. In fact, there's no way to do this + // in KDE since KImageEffect is so badly broken. + QImage basisImage = basis->convertToImage(); + QImage overlayImage = overlay.convertToImage(); + QPoint offset; + if ( (*it).endsWith( QString::fromLatin1( "_overlay" ) ) ) + { + // it is possible to have more than one overlay icon + // to avoid overlapping we place them in different corners + overlayImage = overlayImage.copy( getBoundingBox( overlayImage ) ); + offset = getOffsetForCorner( overlayImage, basisImage, i ); + ++i; + } + blendOnLower( overlayImage, basisImage, offset ); + basis->convertFromImage( basisImage ); + } + } + } + + // no need to scale if the icon is already of the required size (assuming height == width!) + if ( basis->width() != size ) + { + QImage scaledImg = basis->convertToImage().smoothScale( size, size ); + *basis = QPixmap( scaledImg ); + } + + // if idle, apply effects + if ( idle ) + KIconEffect::semiTransparent( *basis ); + + return basis; +} + +void OnlineStatusManager::createAccountStatusActions( Account *account , KActionMenu *parent) +{ + Private::ProtocolMap protocolMap=d->registeredStatus[account->protocol()]; + Private::ProtocolMap::Iterator it; + for ( it = --protocolMap.end(); it != protocolMap.end(); --it ) + { + unsigned int options=it.data().options; + if(options & OnlineStatusManager::HideFromMenu) + continue; + + OnlineStatus status=it.key(); + QString caption=it.data().caption; + KAction *action; + + // Any existing actions owned by the account are reused by recovering them + // from the parent's child list. + // The description of the onlinestatus is used as the qobject name + // This is safe as long as OnlineStatus are immutable + QCString actionName = status.description().ascii(); + if ( !( action = static_cast( account->child( actionName ) ) ) ) + { + if(options & OnlineStatusManager::HasAwayMessage) + { + action = new AwayAction( status, caption, status.iconFor(account), 0, account, + SLOT( setOnlineStatus( const Kopete::OnlineStatus&, const QString& ) ), + account, actionName ); + } + else + { + action=new OnlineStatusAction( status, caption, status.iconFor(account) , account, actionName ); + connect(action,SIGNAL(activated(const Kopete::OnlineStatus&)) , + account, SLOT(setOnlineStatus(const Kopete::OnlineStatus&))); + } + } + +#if 0 + //disabled because since action are reused, they are not enabled back if the account is online. + if(options & OnlineStatusManager::DisabledIfOffline && !account->isConnected()) + action->setEnabled(false); +#endif + + if(parent) + parent->insert(action); + + } +} + + +OnlineStatusAction::OnlineStatusAction( const OnlineStatus& status, const QString &text, const QIconSet &pix, QObject *parent, const char *name) + : KAction( text, pix, KShortcut() , parent, name) , m_status(status) +{ + connect(this,SIGNAL(activated()),this,SLOT(slotActivated())); +} + +void OnlineStatusAction::slotActivated() +{ + emit activated(m_status); +} + + +} //END namespace Kopete + +#include "kopeteonlinestatusmanager.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopeteonlinestatusmanager.h b/kopete/libkopete/kopeteonlinestatusmanager.h new file mode 100644 index 00000000..d3369403 --- /dev/null +++ b/kopete/libkopete/kopeteonlinestatusmanager.h @@ -0,0 +1,169 @@ +/* + kopeteonlinestatusmanager.h + + Copyright (c) 2004-2005 by Olivier Goffart + + Kopete (c) 2004-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef kopeteonlinestatusmanager_h__ +#define kopeteonlinestatusmanager_h__ + +#include + +#include "kopeteonlinestatus.h" +#include "kaction.h" + +class QString; +class QPixmap; +class QColor; +class KActionMenu; + +namespace Kopete +{ + class OnlineStatus; + class Account; + + +/** + * OnlineStatusManager is a singleton which manage OnlineStatus + * + * @author Olivier Goffart + */ +class KOPETE_EXPORT OnlineStatusManager : public QObject +{ + Q_OBJECT +public: + static OnlineStatusManager* self(); + ~OnlineStatusManager(); + + /** + * Kopete will uses categories to have a more general system than siply globaly away. + * + * Idealy, in each protocol, there should be one status per categories (status may be in several or in none categories + * + * Idle is the status used for auto-away + * + * Status number are organised so that make a tree. + */ + //please be carrefull when modifying values of status. read comment in onlineStatus() + enum Categories + { + Idle=1<<8, ExtendedAway=1<<9 , Invisible=1<<10, + // \ / __________/ + /*1<<4*/ Busy=1<<5, FreeForChat=1<<6, /* 1<<7*/ + // \ / / + Away=1<<2, /* 1<<3 */ + // \ / + Online=1<<1, + Offline=1 + }; + + + /** + * @see registerOnlineStatus + */ + enum Options + { + /// The user may set away messages for this online status + HasAwayMessage = 0x01, + /// The action of the status will be disabled if the account is offline. + /// use it if your protocol doesn't support connecting with the status as initial status. + /// You praticaly shouldn't abuse of that, and automaticaly set status after connecting if possible + DisabledIfOffline = 0x02, + /// The status will not appears in the action menu. Used if you want to register the status for e.g. autoaway, + /// without letting the user set itself that status + HideFromMenu = 0x04 + }; + + /** + * You need to register each status an account can be. + * Registered statuses will appear in the account menu. + * + * The Protocol constructor is a good place to call this function. + * But if you want, you may use a special OnlineStatus constructor that call this function automaticaly + * + * You can set the status to be in the predefined categories. + * Ideally, each category should own one status. + * A status may be in several categories, or in none. + * There shouldn't be more than one status per protocol per categories. + * + * @param status The status to register + * @param caption The caption that will appear in menus (e.g. "Set &Away") + * @param categories A bitflag of @ref Categories + * @param options is a bitflag of @ref Options + */ + void registerOnlineStatus(const OnlineStatus& status, const QString &caption, unsigned int categories=0x00 , unsigned int options=0x0); + + /** + * insert "setStatus" actions from the given account to the specified actionMenu. + * (actions have that menu as parent QObject) + * they are connected to the Account::setOnlineStatus signal + * + * Items are stored by status height. + * + * @param account the account + * @param parent the ActionMenu where action are inserted + */ + void createAccountStatusActions( Account *account , KActionMenu *parent); + + /** + * return the status of the @p protocol which is in the category @p category + * + * If no status has been registered in this category, return the one in the category which is the most similair + */ + OnlineStatus onlineStatus(Protocol *protocol, Categories category) const; + +private: + friend class OnlineStatus; + QPixmap cacheLookupByObject( const OnlineStatus &statusFor, const QString& icon, int size, QColor color, bool idle = false); + QPixmap cacheLookupByMimeSource( const QString &mimeSource ); + QString fingerprint( const OnlineStatus &statusFor, const QString& icon, int size, QColor color, bool idle = false); + QPixmap* renderIcon( const OnlineStatus &statusFor, const QString& baseicon, int size, QColor color, bool idle = false) const; + +signals: + void iconsChanged(); + +private slots: + void slotIconsChanged(); + +private: + + static OnlineStatusManager *s_self; + OnlineStatusManager(); + class Private; + Private *d; +}; + + +/** + * @internal + */ +class OnlineStatusAction : public KAction +{ + Q_OBJECT + public: + OnlineStatusAction ( const OnlineStatus& status, const QString &text, const QIconSet &pix, QObject *parent=0, const char *name=0); + signals: + void activated( const Kopete::OnlineStatus& status ); + private slots: + void slotActivated(); + private: + OnlineStatus m_status; +}; + +} //END namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetepassword.cpp b/kopete/libkopete/kopetepassword.cpp new file mode 100644 index 00000000..f0b788a9 --- /dev/null +++ b/kopete/libkopete/kopetepassword.cpp @@ -0,0 +1,502 @@ +/* + kopetepassword.cpp - Kopete Password + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteuiglobal.h" +#include "kopetepassword.h" +#include "kopetepassworddialog.h" +#include "kopetewalletmanager.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Kopete::Password::Private +{ +public: + Private( const QString &group, uint maxLen, bool blanksAllowed ) + : refCount( 1 ), configGroup( group ), remembered( false ), maximumLength( maxLen ), + isWrong( false ), allowBlankPassword( blanksAllowed ) + { + } + Private *incRef() + { + ++refCount; + return this; + } + void decRef() + { + if( --refCount == 0 ) + delete this; + } + /** Reference count */ + int refCount; + /** Group to use for KConfig and KWallet */ + const QString configGroup; + /** Is the password being remembered? */ + bool remembered; + /** The current password in the KConfig file, or QString::null if no password there */ + QString passwordFromKConfig; + /** The maximum length allowed for this password, or -1 if there is no limit */ + uint maximumLength; + /** Is the current password known to be wrong? */ + bool isWrong; + /** Are we allowed to have blank passwords? */ + bool allowBlankPassword; + /** The cached password */ + QString cachedValue; +}; + +/** + * Implementation detail of Kopete::Password: manages a single password request + * @internal + * @author Richard Smith + */ +class KopetePasswordRequest : public KopetePasswordRequestBase +{ +public: + KopetePasswordRequest( QObject *owner, Kopete::Password &pass ) + : QObject( owner ), mPassword( pass ), mWallet( 0 ) + { + } + + /** + * Start the request - ask for the wallet + */ + void begin() + { + kdDebug( 14010 ) << k_funcinfo << endl; + Kopete::WalletManager::self()->openWallet( this, SLOT( walletReceived( KWallet::Wallet* ) ) ); + } + + void walletReceived( KWallet::Wallet *wallet ) + { + kdDebug( 14010 ) << k_funcinfo << endl; + mWallet = wallet; + processRequest(); + } + + /** + * Got wallet; now carry out whatever action this request represents + */ + virtual void processRequest() = 0; + + void slotOkPressed() {} + void slotCancelPressed() {} + +protected: + Kopete::Password mPassword; + KWallet::Wallet *mWallet; +}; + +/** + * Implementation detail of Kopete::Password: manages a single password retrieval request + * @internal + * @author Richard Smith + */ +class KopetePasswordGetRequest : public KopetePasswordRequest +{ +public: + KopetePasswordGetRequest( QObject *owner, Kopete::Password &pass ) + : KopetePasswordRequest( owner, pass ) + { + } + + QString grabPassword() + { + // Before trying to read from the wallet, check if the config file holds a password. + // If so, remove it from the config and set it through KWallet instead. + QString pwd; + if ( mPassword.d->remembered && !mPassword.d->passwordFromKConfig.isNull() ) + { + pwd = mPassword.d->passwordFromKConfig; + mPassword.set( pwd ); + return pwd; + } + + if ( mWallet && mWallet->readPassword( mPassword.d->configGroup, pwd ) == 0 && !pwd.isNull() ) + return pwd; + + if ( mPassword.d->remembered && !mPassword.d->passwordFromKConfig.isNull() ) + return mPassword.d->passwordFromKConfig; + + return QString::null; + } + + void finished( const QString &result ) + { + mPassword.d->cachedValue = result; + emit requestFinished( result ); + delete this; + } +}; + +class KopetePasswordGetRequestPrompt : public KopetePasswordGetRequest +{ +public: + KopetePasswordGetRequestPrompt( QObject *owner, Kopete::Password &pass, const QPixmap &image, const QString &prompt, Kopete::Password::PasswordSource source ) + : KopetePasswordGetRequest( owner, pass ), mImage( image ), mPrompt( prompt ), mSource( source ), mView( 0 ) + { + } + + void processRequest() + { + QString result = grabPassword(); + if ( mSource == Kopete::Password::FromUser || result.isNull() ) + doPasswordDialog(); + else + finished( result ); + } + + void doPasswordDialog() + { + kdDebug( 14010 ) << k_funcinfo << endl; + + KDialogBase *passwdDialog = new KDialogBase( Kopete::UI::Global::mainWidget(), "passwdDialog", true, i18n( "Password Required" ), + KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok, true ); + + mView = new KopetePasswordDialog( passwdDialog ); + passwdDialog->setMainWidget( mView ); + + mView->m_text->setText( mPrompt ); + mView->m_image->setPixmap( mImage ); + /* Do not put the default password, or it will confuse those which doesn't echo anything for the password + mView->m_password->insert( password ); + */ + int maxLength = mPassword.maximumLength(); + if ( maxLength != 0 ) + mView->m_password->setMaxLength( maxLength ); + mView->m_password->setFocus(); + + // FIXME: either document what these are for or remove them - lilac + mView->adjustSize(); + passwdDialog->adjustSize(); + + connect( passwdDialog, SIGNAL( okClicked() ), SLOT( slotOkPressed() ) ); + connect( passwdDialog, SIGNAL( cancelClicked() ), SLOT( slotCancelPressed() ) ); + connect( this, SIGNAL( destroyed() ), passwdDialog, SLOT( deleteLater() ) ); + passwdDialog->show(); + } + + void slotOkPressed() + { + QString result = QString::fromLocal8Bit( mView->m_password->password() ); + if ( mView->m_save_passwd->isChecked() ) + mPassword.set( result ); + + finished( result ); + } + + void slotCancelPressed() + { + finished( QString::null ); + } + +private: + QPixmap mImage; + QString mPrompt; + Kopete::Password::PasswordSource mSource; + unsigned int mMaxLength; + KopetePasswordDialog *mView; +}; + +class KopetePasswordGetRequestNoPrompt : public KopetePasswordGetRequest +{ +public: + KopetePasswordGetRequestNoPrompt( QObject *owner, Kopete::Password &pass ) + : KopetePasswordGetRequest( owner, pass ) + { + } + + void processRequest() + { + finished( grabPassword() ); + } +}; + +/** + * Implementation detail of Kopete::Password: manages a single password change request + * @internal + * @author Richard Smith + */ +class KopetePasswordSetRequest : public KopetePasswordRequest +{ +public: + KopetePasswordSetRequest( Kopete::Password &pass, const QString &newPass ) + : KopetePasswordRequest( 0, pass ), mNewPass( newPass ) + { + if ( KApplication *app = KApplication::kApplication() ) + app->ref(); + } + ~KopetePasswordSetRequest() + { + if ( KApplication *app = KApplication::kApplication() ) + app->deref(); + kdDebug( 14010 ) << k_funcinfo << "job complete" << endl; + } + void processRequest() + { + if ( setPassword() ) + { + mPassword.setWrong( false ); + mPassword.d->cachedValue = mNewPass; + } + delete this; + } + bool setPassword() + { + kdDebug( 14010 ) << k_funcinfo << " setting password for " << mPassword.d->configGroup << endl; + + if ( mWallet && mWallet->writePassword( mPassword.d->configGroup, mNewPass ) == 0 ) + { + mPassword.d->remembered = true; + mPassword.d->passwordFromKConfig = QString::null; + mPassword.writeConfig(); + return true; + } + + if ( KWallet::Wallet::isEnabled() ) + { + // If we end up here, the wallet is enabled, but failed somehow. + // Ask the user what to do now. + + //NOTE: This will start a nested event loop. However, this is fine; the only code we + // call after this point is in Kopete::Password, so as long as we've not been deleted + // everything should work out OK. We have no parent QObject, so we should survive. + if ( KMessageBox::warningContinueCancel( Kopete::UI::Global::mainWidget(), + i18n( "Kopete is unable to save your password securely in your wallet;
    " + "do you want to save the password in the unsafe configuration file instead?
    " ), + i18n( "Unable to Store Secure Password" ), + KGuiItem( i18n( "Store &Unsafe" ), QString::fromLatin1( "unlock" ) ), + QString::fromLatin1( "KWalletFallbackToKConfig" ) ) != KMessageBox::Continue ) + { + return false; + } + } + mPassword.d->remembered = true; + mPassword.d->passwordFromKConfig = mNewPass; + mPassword.writeConfig(); + return true; + } + +private: + QString mNewPass; +}; + +class KopetePasswordClearRequest : public KopetePasswordRequest +{ +public: + KopetePasswordClearRequest( Kopete::Password &pass ) + : KopetePasswordRequest( 0, pass ) + { + if ( KApplication *app = KApplication::kApplication() ) + app->ref(); + } + ~KopetePasswordClearRequest() + { + if ( KApplication *app = KApplication::kApplication() ) + app->deref(); + kdDebug( 14010 ) << k_funcinfo << "job complete" << endl; + } + void processRequest() + { + if ( clearPassword() ) + { + mPassword.setWrong( true ); + mPassword.d->cachedValue = QString::null; + } + + delete this; + } + bool clearPassword() + { + kdDebug( 14010 ) << k_funcinfo << " clearing password" << endl; + + mPassword.d->remembered = false; + mPassword.d->passwordFromKConfig = QString::null; + mPassword.writeConfig(); + if ( mWallet ) + mWallet->removeEntry( mPassword.d->configGroup ); + return true; + } +}; + +Kopete::Password::Password( const QString &configGroup, uint maximumLength, const char *name ) + : QObject( 0, name ), d( new Private( configGroup, maximumLength, false ) ) +{ + readConfig(); +} + +Kopete::Password::Password( const QString &configGroup, uint maximumLength, + bool allowBlankPassword, const char *name ) + : QObject( 0, name ), d( new Private( configGroup, maximumLength, allowBlankPassword ) ) +{ + readConfig(); +} + +Kopete::Password::Password( Password &other, const char *name ) + : QObject( 0, name ), d( other.d->incRef() ) +{ +} + +Kopete::Password::~Password() +{ + d->decRef(); +} + +Kopete::Password &Kopete::Password::operator=( Password &other ) +{ + if ( d == other.d ) return *this; + d->decRef(); + d = other.d->incRef(); + return *this; +} + +void Kopete::Password::readConfig() +{ + KConfig *config = KGlobal::config(); + config->setGroup( d->configGroup ); + + QString passwordCrypted = config->readEntry( "Password" ); + if ( passwordCrypted.isNull() ) + d->passwordFromKConfig = QString::null; + else + d->passwordFromKConfig = KStringHandler::obscure( passwordCrypted ); + + d->remembered = config->readBoolEntry( "RememberPassword", false ); + d->isWrong = config->readBoolEntry( "PasswordIsWrong", false ); +} + +void Kopete::Password::writeConfig() +{ + KConfig *config = KGlobal::config(); + if(!config->hasGroup(d->configGroup)) + { + //### (KOPETE) + // if the kopete account has been removed, we have no way to know it. + // but we don't want in any case to recreate the group. + // see Bug 106460 + // (the problem is that when we remove the account, we remove the password + // also, which cause a call to this function ) + return; + } + + config->setGroup( d->configGroup ); + + if ( d->remembered && !d->passwordFromKConfig.isNull() ) + config->writeEntry( "Password", KStringHandler::obscure( d->passwordFromKConfig ) ); + else + config->deleteEntry( "Password" ); + + config->writeEntry( "RememberPassword", d->remembered ); + config->writeEntry( "PasswordIsWrong", d->isWrong ); +} + +int Kopete::Password::preferredImageSize() +{ + return IconSize(KIcon::Toolbar); +} + +bool Kopete::Password::allowBlankPassword() +{ + return d->allowBlankPassword; +} + +uint Kopete::Password::maximumLength() +{ + return d->maximumLength; +} + +void Kopete::Password::setMaximumLength( uint max ) +{ + d->maximumLength = max; +} + +bool Kopete::Password::isWrong() +{ + return d->isWrong; +} + +void Kopete::Password::setWrong( bool bWrong ) +{ + d->isWrong = bWrong; + writeConfig(); + + if ( bWrong ) d->cachedValue = QString::null; +} + +void Kopete::Password::requestWithoutPrompt( QObject *returnObj, const char *slot ) +{ + KopetePasswordRequest *request = new KopetePasswordGetRequestNoPrompt( returnObj, *this ); + // call connect on returnObj so we can still connect if 'slot' is protected/private + returnObj->connect( request, SIGNAL( requestFinished( const QString & ) ), slot ); + request->begin(); +} + +void Kopete::Password::request( QObject *returnObj, const char *slot, const QPixmap &image, const QString &prompt, Kopete::Password::PasswordSource source ) +{ + KopetePasswordRequest *request = new KopetePasswordGetRequestPrompt( returnObj, *this, image, prompt, source ); + returnObj->connect( request, SIGNAL( requestFinished( const QString & ) ), slot ); + request->begin(); +} + +QString Kopete::Password::cachedValue() +{ + return d->cachedValue; +} + +void Kopete::Password::set( const QString &pass ) +{ + // if we're being told to forget the password, and we aren't remembering one, + // don't try to open the wallet. fixes bug #71804. + if( pass.isNull() && !d->allowBlankPassword ) + { + if( remembered() ) + clear(); + return; + } + + KopetePasswordRequest *request = new KopetePasswordSetRequest( *this, pass ); + request->begin(); +} + +void Kopete::Password::clear() +{ + KopetePasswordClearRequest *request = new KopetePasswordClearRequest( *this ); + request->begin(); +} + +bool Kopete::Password::remembered() +{ + return d->remembered; +} + +#include "kopetepassword.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetepassword.h b/kopete/libkopete/kopetepassword.h new file mode 100644 index 00000000..149db6f6 --- /dev/null +++ b/kopete/libkopete/kopetepassword.h @@ -0,0 +1,220 @@ +/* + kopetepassword.h - Kopete Password + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPASSWORD_H +#define KOPETEPASSWORD_H + +#include +#include "kopete_export.h" + +namespace KWallet { class Wallet; } + +class QPixmap; + +/** @internal */ +class KopetePasswordGetRequest; +/** @internal */ +class KopetePasswordSetRequest; +/** @internal */ +class KopetePasswordClearRequest; + +namespace Kopete +{ + +/** + * @author Richard Smith + * + * The Kopete::Password object is responsible for storing and retrieving a + * password for a plugin or account object. + * + * If the KWallet is active, passwords will be stored in it, otherwise, they + * will be stored in the KConfig, in a slightly mangled form. + */ +class KOPETE_EXPORT Password : public QObject +{ + Q_OBJECT + +public: + /** + * Create a new Kopete::Password object. + * + * @param configGroup The configuration group to save passwords in. + * @param maxLength The maximum length of the password, or 0 if no maximum exists. + * @param name The name for this object + * + * @deprecated Use the constructor that specifies if a blank password is allowed + */ + explicit Password( const QString &configGroup, uint maxLength = 0, const char *name = 0 ); + + /** + * Create a new Kopete::Password object. + * + * @param configGroup The configuration group to save passwords in. + * @param maxLength The maximum length of the password, or 0 if no maximum exists. + * @param allowBlankPassword If this password is allowed to be blank + * @param name The name for this object + */ + explicit Password( const QString &configGroup, uint maxLength = 0, + bool allowBlankPassword = false, const char *name = 0 ); + + /** + * Create a shallow copy of this object + */ + Password( Password &other, const char *name = 0 ); + ~Password(); + + /** + * Assignment operator for passwords: make this object represent a different password + */ + Password &operator=( Password &other ); + + /** + * Returns the preferred size for images passed to the retrieve and request functions. + */ + static int preferredImageSize(); + + /** + * @brief Returns the maximum allowed length of the password, or 0 if there is no maximum. + */ + uint maximumLength(); + /** + * Sets the maximum allowed length of the password. + * @param max The new maximum allowed length, or 0 if there is no maximum. + */ + void setMaximumLength( uint max ); + + /** + * @brief Returns whether the password currently stored by this object is known to be incorrect. + * This flag gets reset whenever the user enters a new password, and is + * expected to be set by the user of this class if it is detected that the + * password the user entered is wrong. + */ + bool isWrong(); + /** + * Flag the password as being incorrect. + * @see isWrong + */ + void setWrong( bool bWrong = true ); + + /** + * Type of password request to perform: + * FromConfigOrUser : get the password from the config file, or from the user + * if no password in config. + * FromUser : always ask the user for a password (ie, if last password was + * wrong or you know the password has changed). + */ + enum PasswordSource { FromConfigOrUser, FromUser }; + + /** + * @brief Start an asynchronous call to get the password. + * Causes a password entry dialog to appear if the password is not set. Triggers + * a provided slot when done, but not until after this function has returned (you + * don't need to worry about reentrancy or nested event loops). + * + * @param receiver The object to notify when the password request finishes + * @param slot The slot on receiver to call at the end of the request. The signature + * of this function should be slot( const QString &password ). password will + * be the password if successful, or QString::null if failed. + * @param image The icon to display in the dialog when asking for the password + * @param prompt The message to display to the user, asking for a + * password. Can be any Qt RichText string. + * @param source The source the password is taken from if a wrong or + * invalid password is entered or the password could not be found in the wallet + */ + void request( QObject *receiver, const char *slot, const QPixmap &image, + const QString &prompt, PasswordSource source = FromConfigOrUser ); + + /** + * @brief Start an asynchronous password request without a prompt + * + * Starts an asynchronous password request. Does not pop up a password entry dialog + * if there is no password. + * @see request(QObject*,const char*,const QPixmap&,const QString&,bool,unsigned int) + * The password given to the provided slot will be NULL if no password could be retrieved for + * some reason, such as the user declining to open the wallet, or no password being found. + */ + void requestWithoutPrompt( QObject *receiver, const char *slot ); + + /** + * @return true if the password is remembered, false otherwise. + * + * If it returns false, calling @ref request() will + * pop up an Enter Password window. + */ + bool remembered(); + + /** + * @return true if you are allowed to have a blank password + */ + bool allowBlankPassword(); + + /** + * When a password request succeeds, the password is cached. This function + * returns the cached password, if there is one, or QString::null if there + * is not. + */ + QString cachedValue(); + +public slots: + /** + * Set the password for this account. + * @param pass If set to QString::null, the password is forgotten unless you + * specified to allow blank passwords. Otherwise, sets the password to + * this value. + * + * Note: this function is asynchronous; changes will not be instant. + */ + void set( const QString &pass = QString::null ); + + /** + * Unconditionally clears the stored password + */ + void clear(); + +private: + void readConfig(); + void writeConfig(); + + class Private; + Private *d; + + //TODO: can we rearrange things so these aren't friends? + friend class ::KopetePasswordGetRequest; + friend class ::KopetePasswordSetRequest; + friend class ::KopetePasswordClearRequest; +}; + +} + +/** + * This class is an implementation detail of KopetePassword. + * @internal + * @see KopetePassword + */ +class KopetePasswordRequestBase : public virtual QObject +{ + Q_OBJECT +signals: + void requestFinished( const QString &password ); +public slots: + virtual void walletReceived( KWallet::Wallet *wallet ) = 0; + virtual void slotOkPressed() = 0; + virtual void slotCancelPressed() = 0; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetepasswordedaccount.cpp b/kopete/libkopete/kopetepasswordedaccount.cpp new file mode 100644 index 00000000..9fea5c66 --- /dev/null +++ b/kopete/libkopete/kopetepasswordedaccount.cpp @@ -0,0 +1,111 @@ +/* + kopetepasswordedaccount.cpp - Kopete Account with a password + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetepasswordedaccount.h" +#include "kopetepassword.h" +#include "kopeteprotocol.h" +#include "kopeteonlinestatus.h" + +#include + +#include + +struct Kopete::PasswordedAccount::Private +{ + Private( const QString &group, uint maxLen, bool allowBlankPassword ) : + password( group, maxLen, allowBlankPassword, "mPassword" ) {} + Kopete::Password password; + Kopete::OnlineStatus initialStatus; +}; + +Kopete::PasswordedAccount::PasswordedAccount( Kopete::Protocol *parent, const QString &acctId, uint maxLen, const char *name ) + : Kopete::Account( parent, acctId, name ), d( new Private( QString::fromLatin1("Account_")+ parent->pluginId() + QString::fromLatin1("_") + acctId , maxLen, false ) ) +{ +} + +Kopete::PasswordedAccount::PasswordedAccount( Kopete::Protocol *parent, const QString &acctId, uint maxLen, + bool allowBlankPassword, const char *name ) + : Kopete::Account( parent, acctId, name ), d( new Private( QString::fromLatin1("Account_")+ parent->pluginId() + QString::fromLatin1("_") + acctId , maxLen, allowBlankPassword ) ) +{ +} + +Kopete::PasswordedAccount::~PasswordedAccount() +{ + delete d; +} + +Kopete::Password &Kopete::PasswordedAccount::password() +{ + return d->password; +} + +void Kopete::PasswordedAccount::connect( ) +{ + Kopete::OnlineStatus s(Kopete::OnlineStatus::Online); + connect( s ); +} + +void Kopete::PasswordedAccount::connect( const Kopete::OnlineStatus& initialStatus ) +{ + // check that the networkstatus is up + + // warn user somewhere + d->initialStatus = initialStatus; + QString cached = password().cachedValue(); + if( !cached.isNull() || d->password.allowBlankPassword() ) + { + connectWithPassword( cached ); + return; + } + + QString prompt = passwordPrompt(); + Kopete::Password::PasswordSource src = password().isWrong() ? Kopete::Password::FromUser : Kopete::Password::FromConfigOrUser; + + password().request( this, SLOT( connectWithPassword( const QString & ) ), accountIcon( Kopete::Password::preferredImageSize() ), prompt, src ); +} + +QString Kopete::PasswordedAccount::passwordPrompt() +{ + if ( password().isWrong() ) + return i18n( "The password was wrong; please re-enter your password for %1 account %2" ).arg( protocol()->displayName(), accountId() ); + else + return i18n( "Please enter your password for %1 account %2" ).arg( protocol()->displayName(), accountId() ); +} + +Kopete::OnlineStatus Kopete::PasswordedAccount::initialStatus() +{ + return d->initialStatus; +} + +bool Kopete::PasswordedAccount::removeAccount() +{ + password().set(QString::null); + return Kopete::Account::removeAccount(); +} + +void Kopete::PasswordedAccount::disconnected( Kopete::Account::DisconnectReason reason ) +{ + if(reason==Kopete::Account::BadPassword || reason==Kopete::Account::BadUserName) + { + password().setWrong(true); + } + Kopete::Account::disconnected(reason); +} + + +#include "kopetepasswordedaccount.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetepasswordedaccount.h b/kopete/libkopete/kopetepasswordedaccount.h new file mode 100644 index 00000000..1534025d --- /dev/null +++ b/kopete/libkopete/kopetepasswordedaccount.h @@ -0,0 +1,132 @@ +/* + kopetepasswordedaccount.h - Kopete Account with a password + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPASSWORDEDACCOUNT_H +#define KOPETEPASSWORDEDACCOUNT_H + +#include "kopeteaccount.h" + +#include "kopete_export.h" + +class Kopete::OnlineStatus; + +namespace Kopete +{ + +class Password; + +/** + * An account requiring a password to connect. Instead of reimplementing connect() + * in your subclass, reimplement connectWithPassword. + * + * @author Richard Smith + */ +class KOPETE_EXPORT PasswordedAccount : public Account +{ + Q_OBJECT + +public: + + /** + * KopetePasswordedAccount constructor + * @param parent The protocol this account connects via + * @param acctId The ID of this account - should be unique within this protocol + * @param maxPasswordLength The maximum length for passwords for this account, or 0 for no limit + * @param name The name for this QObject + * + * @deprecated Use the constructor that specifies if a blank password is allowed + */ + PasswordedAccount( Protocol *parent, const QString &acctId, uint maxPasswordLength = 0, const char *name = 0 ); + + /** + * KopetePasswordedAccount constructor + * @param parent The protocol this account connects via + * @param acctId The ID of this account - should be unique within this protocol + * @param maxPasswordLength The maximum length for passwords for this account, or 0 for no limit + * @param allowBlankPassword If this protocol allows blank passwords. Note that this will mean that + * + * @param name The name for this QObject + */ + PasswordedAccount( Protocol *parent, const QString &acctId, uint maxPasswordLength = 0, + bool allowBlankPassword = false, const char *name = 0 ); + + virtual ~PasswordedAccount(); + + /** + * Returns a reference to the password object stored in this account. + */ + Password &password(); + + void connect(); + + /** + * @brief Go online for this service. + * + * @param initialStatus is the status to connect with. If it is an invalid status for this + * account, the default online for the account should be used. + */ + void connect( const OnlineStatus& initialStatus ); + + /** + * \brief Get the initial status + */ + OnlineStatus initialStatus(); + + /** + * @brief Remove the account from the server. + * + * Reimplementation of Account::removeAccount() to remove the password from the wallet. + * if your protocol reimplements this function, this function should still be called. + * + * @return Always true + */ + virtual bool removeAccount(); + + +public slots: + /** + * Called when your account should attempt to connect. + * @param password The password to connect with, or QString::null + * if the user wished to cancel the connection attempt. + */ + virtual void connectWithPassword( const QString &password ) = 0; + +protected: + /** + * Returns the prompt shown to the user when requesting their password. + * The default implementation should be adequate in most cases; override + * if you have a custom message to show the user. + */ + virtual QString passwordPrompt(); + +protected slots: + /** + * @internal + * Reimplemented to set the password wrong if the reason is BadPassword + */ + virtual void disconnected( Kopete::Account::DisconnectReason reason ); + + +private: + class Private; + Private *d; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetepicture.cpp b/kopete/libkopete/kopetepicture.cpp new file mode 100644 index 00000000..1c586b40 --- /dev/null +++ b/kopete/libkopete/kopetepicture.cpp @@ -0,0 +1,197 @@ +/* + kopetepicture.cpp - Kopete Picture + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ +#include "kopetepicture.h" + +#include + +#include + +#include +#include +#include + +namespace Kopete +{ + +class Picture::Private : public KShared +{ +public: + Private() + {} + + QString pictureBase64; + QImage pictureImage; + QString picturePath; +}; + +Picture::Picture() + : d(new Private) +{ +} + +Picture::Picture(const QString &path) + : d(new Private) +{ + setPicture(path); +} + +Picture::Picture(const QImage &image) + : d(new Private) +{ + setPicture(image); +} + +Picture::Picture(const KABC::Picture &picture) + : d(new Private) +{ + setPicture(picture); +} + +Picture::Picture(const Picture &other) + : d(other.d) +{} + +Picture::~Picture() +{} + +Picture &Picture::operator=(const Picture &other) +{ + d = other.d; + return *this; +} + +QImage Picture::image() +{ + // Do the conversion if only needed. + // If the image is null, the path is not empty then. + if( d->pictureImage.isNull() ) + { + d->pictureImage = QImage(d->picturePath); + } + + return d->pictureImage; +} + +QString Picture::base64() +{ + if( d->pictureBase64.isEmpty() ) + { + // Generate base64 cache for the picture. + QByteArray tempArray; + QBuffer tempBuffer( tempArray ); + tempBuffer.open( IO_WriteOnly ); + // Make sure it create a image cache. + if( image().save( &tempBuffer, "PNG" ) ) + { + d->pictureBase64 = KCodecs::base64Encode(tempArray); + } + } + + return d->pictureBase64; +} + +QString Picture::path() +{ + if( d->picturePath.isEmpty() ) + { + // For a image source, finding a filename is tricky. + // I decided to use MD5 Hash as the filename. + QString localPhotoPath; + + // Generate MD5 Hash for the image. + QByteArray tempArray; + QBuffer tempBuffer(tempArray); + tempBuffer.open( IO_WriteOnly ); + image().save(&tempBuffer, "PNG"); + KMD5 context(tempArray); + // Save the image to a file. + localPhotoPath = context.hexDigest() + ".png"; + localPhotoPath = locateLocal( "appdata", QString::fromUtf8("metacontactpicturecache/%1").arg( localPhotoPath) ); + if( image().save(localPhotoPath, "PNG") ) + { + d->picturePath = localPhotoPath; + } + } + + return d->picturePath; +} + +bool Picture::isNull() +{ + if( d->pictureBase64.isEmpty() && d->picturePath.isEmpty() && d->pictureImage.isNull() ) + { + return true; + } + else + { + return false; + } +} + +void Picture::clear() +{ + detach(); + d->pictureBase64 = QString::null; + d->picturePath = QString::null; + d->pictureImage = QImage(); +} + +void Picture::setPicture(const QImage &image) +{ + detach(); + + d->pictureImage = image; + + // Clear the path and base64, it will call the update of then when "getted" + d->picturePath= QString::null; + d->pictureBase64 = QString::null; +} + +void Picture::setPicture(const QString &path) +{ + detach(); + d->picturePath = path; + + // Clear the image and base64, it will call the update of then when "getted" + d->pictureImage = QImage(); + d->pictureBase64 = QString::null; +} + +void Picture::setPicture(const KABC::Picture &picture) +{ + // No need to call detach() here because setPicture will do it. + if ( picture.isIntern()) + { + setPicture( picture.data() ); + } + else + { + setPicture( picture.url() ); + } +} + +void Picture::detach() +{ + // there is no detach in KSharedPtr. + if( d.count() == 1 ) + return; + + // Warning: this only works as long as the private object doesn't contain pointers to allocated objects. + d = new Private(*d); +} + +} // END namespace Kopete diff --git a/kopete/libkopete/kopetepicture.h b/kopete/libkopete/kopetepicture.h new file mode 100644 index 00000000..5631afc1 --- /dev/null +++ b/kopete/libkopete/kopetepicture.h @@ -0,0 +1,149 @@ +/* + kopetepicture.h - Kopete Picture + + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ +#ifndef KOPETEPICTURE_H +#define KOPETEPICTURE_H + +#include +#include +#include "kopete_export.h" + +#include + +namespace KABC +{ + class Picture; +} + +namespace Kopete +{ +/** + * @brief Represent a picture in Kopete context + * + * It kept a cache of a QImage object, a base64 string and + * a path to a image file. It ensure that all source are synced. + * Interally, the image is stored in PNG format when possible. + * It can happen that the image path do not return a PNG file. + * + * You can only use an QImage and a image path to create/update + * the picture. + * If the picture doesn't exist as a file, it generate a local + * copy into ~/.kde/share/apps/kopete/metacontactpicturecache + * + * This class is implicitly shared, so don't use it as a pointer. + * + * How to use this class: + * @code + * Kopete::Picture picture; + * picture.setPicture(QImage()); + * picture.setPicture(QString("/tmp/image.png")); + * + * QString base64 = picture.base64(); + * QString path = picture.path(); + * QImage image = picture.image(); + * @endcode + * + * @author Michaël Larouche + */ +class KOPETE_EXPORT Picture +{ +public: + /** + * Create a empty Kopete::Picture + */ + Picture(); + /** + * Create a picture from a local path. + */ + Picture(const QString &path); + /** + * Create a picture from a QImage. + */ + Picture(const QImage &image); + /** + * Create a picture from a KABC::Picture. + */ + Picture(const KABC::Picture &picture); + /** + * Copy a picture. It doesn't create a full copy, it just make a reference. + */ + Picture(const Picture &other); + /** + * Delete the Kopete::Picture + */ + ~Picture(); + /** + * Assignment operator. + * Like the copy constructor, it just make a reference. + */ + Picture &operator=(const Picture &other); + + /** + * Return the current picture as QImage. + * QImage can used to draw the image on a context. + * + * @return the QImage cache of current picture. + */ + QImage image(); + /** + * Return the current picture as a base64 string. + * The base64 is used to include the picture into a XML/XHTML context. + */ + QString base64(); + /** + * Return the local path of the current picture. + */ + QString path(); + + /** + * Check if the picture is null. + */ + bool isNull(); + /** + * Reset the picture. + */ + void clear(); + + /** + * Set the picture content. + * @param image the picture as a QImage. + */ + void setPicture(const QImage &image); + /** + * Set the picture content. + * @param path the path to the picture. + */ + void setPicture(const QString &path); + /** + * Set the picture content. + * @param picture a KABC Picture. + */ + void setPicture(const KABC::Picture &picture); + +private: + /** + * Kopete::Picture is implicitly shared. + * Detach the instance when modifying data. + */ + void detach(); + + class Private; + KSharedPtr d; +}; + +}//END namespace Kopete + +#endif diff --git a/kopete/libkopete/kopeteplugin.cpp b/kopete/libkopete/kopeteplugin.cpp new file mode 100644 index 00000000..cec99179 --- /dev/null +++ b/kopete/libkopete/kopeteplugin.cpp @@ -0,0 +1,110 @@ +/* + kopeteplugin.cpp - Kopete Plugin API + + Copyright (c) 2001-2002 by Duncan Mac-Vicar P. + Copyright (c) 2002-2004 by Olivier Goffart + + Copyright (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteplugin.h" +#include "kopetepluginmanager.h" + +#include +#include +#include + +namespace Kopete { + +class Plugin::Private +{ +public: + QStringList addressBookFields; + QString indexField; +}; + +Plugin::Plugin( KInstance *instance, QObject *parent, const char *name ) +: QObject( parent, name ), KXMLGUIClient(), d(new Private) +{ + setInstance( instance ); + KSettings::Dispatcher::self()->registerInstance( instance, this, SIGNAL( settingsChanged() ) ); +} + +Plugin::~Plugin() +{ + delete d; +} + +QString Plugin::pluginId() const +{ + return QString::fromLatin1( className() ); +} + + +QString Plugin::displayName() const +{ + return pluginInfo() ? pluginInfo()->name() : QString::null; +} + +QString Plugin::pluginIcon() const +{ + return pluginInfo() ? pluginInfo()->icon() : QString::null; +} + + +KPluginInfo *Plugin::pluginInfo() const +{ + return PluginManager::self()->pluginInfo( this ); +} + +void Plugin::aboutToUnload() +{ + // Just make the unload synchronous by default + emit readyForUnload(); +} + + +void Plugin::deserialize( MetaContact * /* metaContact */, + const QMap & /* stream */ ) +{ + // Do nothing in default implementation +} + + + +void Kopete::Plugin::addAddressBookField( const QString &field, AddressBookFieldAddMode mode ) +{ + d->addressBookFields.append( field ); + if( mode == MakeIndexField ) + d->indexField = field; +} + +QStringList Kopete::Plugin::addressBookFields() const +{ + return d->addressBookFields; +} + +QString Kopete::Plugin::addressBookIndexField() const +{ + return d->indexField; + +} + + +void Plugin::virtual_hook( uint, void * ) { } + +} //END namespace Kopete + + +#include "kopeteplugin.moc" + + diff --git a/kopete/libkopete/kopeteplugin.desktop b/kopete/libkopete/kopeteplugin.desktop new file mode 100644 index 00000000..f5c29fd4 --- /dev/null +++ b/kopete/libkopete/kopeteplugin.desktop @@ -0,0 +1,69 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Kopete/Plugin +X-KDE-Derived=KPluginInfo +Comment=Kopete Plugin +Comment[ar]=توصيلة Kopete +Comment[be]=Модуль Kopete +Comment[bg]=ПриÑтавки на Kopete +Comment[bn]=কপেট পà§à¦²à¦¾à¦—িন +Comment[br]=Lugent Kopete +Comment[bs]=Kopete dodatak +Comment[ca]=Connector de Kopete +Comment[cs]=Modul aplikace Kopete +Comment[cy]=Ategyn Kopete +Comment[da]=Kopete-plugin +Comment[de]=Kopete-Modul +Comment[el]=ΠÏόσθετο Kopete +Comment[eo]=Kopete-kromaĵo +Comment[es]=Complemento de Kopete +Comment[et]=Kopete plugin +Comment[eu]=Kopete plugin-a +Comment[fa]=Kopete وصلۀ +Comment[fi]=Kopete-liitännäinen +Comment[fr]=Module de Kopete +Comment[ga]=Breiseán Kopete +Comment[gl]=Plugin de Kopete +Comment[he]=תוסף Kopete +Comment[hi]=के-ऑपà¥à¤Ÿà¥€ पà¥à¤²à¤—इन +Comment[hr]=Umetak za Kopete +Comment[hu]=Kopete bÅ‘vítÅ‘modul +Comment[is]=Kopete íforrit +Comment[it]=Plugin di Kopete +Comment[ja]=Kopete プラグイン +Comment[ka]=Kopeteს მáƒáƒ“ული +Comment[kk]=Kopete плагин модулі +Comment[km]=កម្មវិធី​ជំនួយ Kopete +Comment[lt]=Kopete įskiepis +Comment[mk]=Приклучок за Kopete +Comment[nb]=Programtillegg for Kopete +Comment[nds]=Kopete-Moduul +Comment[ne]=कोपेट पà¥à¤²à¤—इन +Comment[nl]=Kopete-plugin +Comment[nn]=Kopete-programtillegg +Comment[pl]=Wtyczka Kopete +Comment[pt]='Plugin' do Kopete +Comment[pt_BR]=Plug-in do Kopete +Comment[ro]=Modul Kopete +Comment[ru]=Модуль Kopete +Comment[se]=Kopete lassemoduvla +Comment[sk]=Modul Kopete +Comment[sl]=Vstavek za Kopete +Comment[sr]=Прикључак за Kopete +Comment[sr@Latn]=PrikljuÄak za Kopete +Comment[sv]=Insticksprogram för Kopete +Comment[ta]=Kopete செரà¯à®•à®²à¯ +Comment[tg]=Модули Kopete +Comment[tr]=Kopete Eklentisi +Comment[uk]=Втулок Kopete +Comment[uz]=Kopete plagini +Comment[uz@cyrillic]=Kopete плагини +Comment[wa]=Tchôke-divins po Kopete +Comment[zh_CN]=Kopete æ’件 +Comment[zh_HK]=Kopete æ’件 +Comment[zh_TW]=Kopete å¤–æŽ›ç¨‹å¼ + +# The Kopete version for which the plugin is written +[PropertyDef::X-Kopete-Version] +Type=int + diff --git a/kopete/libkopete/kopeteplugin.h b/kopete/libkopete/kopeteplugin.h new file mode 100644 index 00000000..43a80849 --- /dev/null +++ b/kopete/libkopete/kopeteplugin.h @@ -0,0 +1,217 @@ +/* + kopeteplugin.h - Kopete Plugin API + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2004 by Olivier Goffart + + Copyright (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPLUGIN_H +#define KOPETEPLUGIN_H + +#include +#include +#include + +#include "kopete_export.h" + +#include //TODO: remove +namespace DOM { class Node; } //TODO: remove +class KAction; //TODO: remove + + +class KPluginInfo; + + +namespace Kopete +{ + +class MetaContact; + +/** + * @brief Base class for all plugins or protocols. + * + * To create a plugin, you need to create a .desktop file which looks like that: + * \verbatim +[Desktop Entry] +Encoding=UTF-8 +Type=Service +X-Kopete-Version=1000900 +Icon=icon +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_myplugin +X-KDE-PluginInfo-Author=Your Name +X-KDE-PluginInfo-Email=your@mail.com +X-KDE-PluginInfo-Name=kopete_myplugin +X-KDE-PluginInfo-Version=0.0.1 +X-KDE-PluginInfo-Website=http://yoursite.com +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=MyPlugin +Comment=Plugin that do some nice stuff + \endverbatim + * + * The constructor of your plugin should looks like this: + * + * \code + typedef KGenericFactory MyPluginFactory; + static const KAboutData aboutdata("kopete_myplugin", I18N_NOOP("MyPlugin") , "1.0" ); + K_EXPORT_COMPONENT_FACTORY( kopete_myplugin, MyPluginFactory( &aboutdata ) ) + + MyPlugin::MyPlugin( QObject *parent, const char *name, const QStringList & args ) + : Kopete::Plugin( MyPluginFactory::instance(), parent, name ) + { + //... + } + \endcode + * + * Kopete::Plugin inherits from KXMLGUIClient. That client is added + * to the Kopete's mainwindow KXMLGUIFactory. So you may add actions + * on the main window (for hinstance in the meta contact popup menu). + * Please note the the client is added right after the plugin is created. + * so you have to create every actions in the constructor + * + * @author Duncan Mac-Vicar P. + * @author Olivier Goffart + */ +class KOPETE_EXPORT Plugin : public QObject, public KXMLGUIClient +{ + Q_OBJECT + +public: + Plugin( KInstance *instance, QObject *parent, const char *name ); + virtual ~Plugin(); + + /** + * Returns the KPluginInfo object associated with this plugin + */ + KPluginInfo *pluginInfo() const; + + /** + * Get the name of the icon for this plugin. The icon name is taken from the + * .desktop file. + * + * May return an empty string if the .desktop file for this plugin specifies + * no icon name to use. + * + * This is a convenience method that simply calls @ref pluginInfo()->icon(). + */ + QString pluginIcon() const; + + /** + * Returns the display name of this plugin. + * + * This is a convenience method that simply calls @ref pluginInfo()->name(). + */ + QString displayName() const; + + /** + * @brief Get the plugin id + * @return the plugin's id which is gotten by calling QObject::className(). + */ + QString pluginId() const; + + /** + * Return the list of all keys from the address book in which the plugin + * is interested. Those keys are monitored for changes upon load and + * during runtime. When the key actually changes, the plugin's + * addressBookKeyChanged( Kopete::MetaContact *mc, const QString &key ) + * is called. + * You can add fields to the list using @ref addAddressBookField() + */ + QStringList addressBookFields() const; + + /** + * Return the index field as set by @ref addAddressBookField() + */ + QString addressBookIndexField() const; + + /** + * Mode for an address book field as used by @ref addAddressBookField() + */ + enum AddressBookFieldAddMode { AddOnly, MakeIndexField }; + + /** + * Add a field to the list of address book fields. See also @ref addressBookFields() + * for a description of the fields. + * + * Set mode to MakeIndexField to make this the index field. Index fields + * are currently used by Kopete::Contact::serialize to autoset the index + * when possible. + * + * Only one field can be index field. Calling this method multiple times + * as index field will reset the value of index field! + */ + void addAddressBookField( const QString &field, AddressBookFieldAddMode mode = AddOnly ); + + /** + * @brief Prepare for unloading a plugin + * + * When unloading a plugin the plugin manager first calls aboutToUnload() + * to indicate the pending unload. Some plugins need time to shutdown + * asynchronously and thus can't be simply deleted in the destructor. + * + * The default implementation immediately emits the @ref readyForUnload() signal, + * which basically makes the shutdown immediate and synchronous. If you need + * more time you can reimplement this method and fire the signal whenever + * you're ready. (you have 3 seconds) + * + * @ref Kopete::Protocol reimplement it. + */ + virtual void aboutToUnload(); + +signals: + /** + * Notify that the settings of a plugin were changed. + * These changes are passed on from the new KCDialog code in kdelibs/kutils. + */ + void settingsChanged(); + + /** + * Indicate when we're ready for unload. + * @see aboutToUnload() + */ + void readyForUnload(); + +public slots: + + /** + * deserialize() and tell the plugin + * to apply the previously stored data again. + * This method is also responsible for retrieving the settings from the + * address book. Settings that were registered can be retrieved with + * @ref Kopete::MetaContact::addressBookField(). + * + * The default implementation does nothing. + * + * @todo we probably should think to another way to save the contacltist. + */ + virtual void deserialize( MetaContact *metaContact, const QMap &data ); + + +protected: + virtual void virtual_hook( uint id, void *data ); + +private: + class Private; + Private *d; +}; + + +} //END namespace Kopete + + +#endif diff --git a/kopete/libkopete/kopetepluginmanager.cpp b/kopete/libkopete/kopetepluginmanager.cpp new file mode 100644 index 00000000..8f613a86 --- /dev/null +++ b/kopete/libkopete/kopetepluginmanager.cpp @@ -0,0 +1,534 @@ +/* + kopetepluginmanager.cpp - Kopete Plugin Loader + + Copyright (c) 2002-2003 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2004 by Olivier Goffart + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "config.h" + +#include "kopetepluginmanager.h" + +#if defined(HAVE_VALGRIND_H) && !defined(NDEBUG) && defined(__i386__) +// We don't want the per-skin includes, so pretend we have a skin header already +#define __VALGRIND_SOMESKIN_H +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopeteplugin.h" +#include "kopetecontactlist.h" +#include "kopeteaccountmanager.h" + +namespace Kopete +{ + + +class PluginManager::Private +{ +public: + Private() : shutdownMode( StartingUp ), isAllPluginsLoaded(false) {} + + // All available plugins, regardless of category, and loaded or not + QValueList plugins; + + // Dict of all currently loaded plugins, mapping the KPluginInfo to + // a plugin + typedef QMap InfoToPluginMap; + InfoToPluginMap loadedPlugins; + + // The plugin manager's mode. The mode is StartingUp until loadAllPlugins() + // has finished loading the plugins, after which it is set to Running. + // ShuttingDown and DoneShutdown are used during Kopete shutdown by the + // async unloading of plugins. + enum ShutdownMode { StartingUp, Running, ShuttingDown, DoneShutdown }; + ShutdownMode shutdownMode; + + // Plugins pending for loading + QValueStack pluginsToLoad; + + static KStaticDeleter deleter; + + bool isAllPluginsLoaded; +}; + +KStaticDeleter PluginManager::Private::deleter; +PluginManager* PluginManager::s_self = 0L; + +PluginManager* PluginManager::self() +{ + if ( !s_self ) + Private::deleter.setObject( s_self, new PluginManager() ); + + return s_self; +} + +PluginManager::PluginManager() : QObject( qApp ), d( new Private ) +{ + d->plugins = KPluginInfo::fromServices( KTrader::self()->query( QString::fromLatin1( "Kopete/Plugin" ), + QString::fromLatin1( "[X-Kopete-Version] == 1000900" ) ) ); + + // We want to add a reference to the application's event loop so we + // can remain in control when all windows are removed. + // This way we can unload plugins asynchronously, which is more + // robust if they are still doing processing. + kapp->ref(); +} + +PluginManager::~PluginManager() +{ + if ( d->shutdownMode != Private::DoneShutdown ) + kdWarning( 14010 ) << k_funcinfo << "Destructing plugin manager without going through the shutdown process! Backtrace is: " << endl << kdBacktrace() << endl; + + // Quick cleanup of the remaining plugins, hope it helps + // Note that deleting it.data() causes slotPluginDestroyed to be called, which + // removes the plugin from the list of loaded plugins. + while ( !d->loadedPlugins.empty() ) + { + Private::InfoToPluginMap::ConstIterator it = d->loadedPlugins.begin(); + kdWarning( 14010 ) << k_funcinfo << "Deleting stale plugin '" << it.data()->name() << "'" << endl; + delete it.data(); + } + + delete d; +} + +QValueList PluginManager::availablePlugins( const QString &category ) const +{ + if ( category.isEmpty() ) + return d->plugins; + + QValueList result; + QValueList::ConstIterator it; + for ( it = d->plugins.begin(); it != d->plugins.end(); ++it ) + { + if ( ( *it )->category() == category ) + result.append( *it ); + } + + return result; +} + +PluginList PluginManager::loadedPlugins( const QString &category ) const +{ + PluginList result; + + for ( Private::InfoToPluginMap::ConstIterator it = d->loadedPlugins.begin(); + it != d->loadedPlugins.end(); ++it ) + { + if ( category.isEmpty() || it.key()->category() == category ) + result.append( it.data() ); + } + + return result; +} + + +KPluginInfo *PluginManager::pluginInfo( const Plugin *plugin ) const +{ + for ( Private::InfoToPluginMap::ConstIterator it = d->loadedPlugins.begin(); + it != d->loadedPlugins.end(); ++it ) + { + if ( it.data() == plugin ) + return it.key(); + } + return 0; +} + +void PluginManager::shutdown() +{ + if(d->shutdownMode != Private::Running) + { + kdDebug( 14010 ) << k_funcinfo << "called when not running. / state = " << d->shutdownMode << endl; + return; + } + + d->shutdownMode = Private::ShuttingDown; + + + /* save the contact list now, just in case a change was made very recently + and it hasn't autosaved yet + from a OO point of view, theses lines should not be there, but i don't + see better place -Olivier + */ + Kopete::ContactList::self()->save(); + Kopete::AccountManager::self()->save(); + + // Remove any pending plugins to load, we're shutting down now :) + d->pluginsToLoad.clear(); + + // Ask all plugins to unload + for ( Private::InfoToPluginMap::ConstIterator it = d->loadedPlugins.begin(); + it != d->loadedPlugins.end(); /* EMPTY */ ) + { + // Plugins could emit their ready for unload signal directly in response to this, + // which would invalidate the current iterator. Therefore, we copy the iterator + // and increment it beforehand. + Private::InfoToPluginMap::ConstIterator current( it ); + ++it; + // FIXME: a much cleaner approach would be to just delete the plugin now. if it needs + // to do some async processing, it can grab a reference to the app itself and create + // another object to do it. + current.data()->aboutToUnload(); + } + + // When running under valgrind, don't enable the timer because it will almost + // certainly fire due to valgrind's much slower processing +#if defined(HAVE_VALGRIND_H) && !defined(NDEBUG) && defined(__i386__) + if ( RUNNING_ON_VALGRIND ) + kdDebug(14010) << k_funcinfo << "Running under valgrind, disabling plugin unload timeout guard" << endl; + else +#endif + QTimer::singleShot( 3000, this, SLOT( slotShutdownTimeout() ) ); +} + +void PluginManager::slotPluginReadyForUnload() +{ + // Using QObject::sender() is on purpose here, because otherwise all + // plugins would have to pass 'this' as parameter, which makes the API + // less clean for plugin authors + // FIXME: I don't buy the above argument. Add a Kopete::Plugin::emitReadyForUnload(void), + // and make readyForUnload be passed a plugin. - Richard + Plugin *plugin = dynamic_cast( const_cast( sender() ) ); + kdDebug( 14010 ) << k_funcinfo << plugin->pluginId() << "ready for unload" << endl; + if ( !plugin ) + { + kdWarning( 14010 ) << k_funcinfo << "Calling object is not a plugin!" << endl; + return; + } + + plugin->deleteLater(); +} + + +void PluginManager::slotShutdownTimeout() +{ + // When we were already done the timer might still fire. + // Do nothing in that case. + if ( d->shutdownMode == Private::DoneShutdown ) + return; + + QStringList remaining; + for ( Private::InfoToPluginMap::ConstIterator it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it ) + remaining.append( it.data()->pluginId() ); + + kdWarning( 14010 ) << k_funcinfo << "Some plugins didn't shutdown in time!" << endl + << "Remaining plugins: " << remaining.join( QString::fromLatin1( ", " ) ) << endl + << "Forcing Kopete shutdown now." << endl; + + slotShutdownDone(); +} + +void PluginManager::slotShutdownDone() +{ + kdDebug( 14010 ) << k_funcinfo << endl; + + d->shutdownMode = Private::DoneShutdown; + + kapp->deref(); +} + +void PluginManager::loadAllPlugins() +{ + // FIXME: We need session management here - Martijn + + KConfig *config = KGlobal::config(); + if ( config->hasGroup( QString::fromLatin1( "Plugins" ) ) ) + { + QMap pluginsMap; + + QMap entries = config->entryMap( QString::fromLatin1( "Plugins" ) ); + QMap::Iterator it; + for ( it = entries.begin(); it != entries.end(); ++it ) + { + QString key = it.key(); + if ( key.endsWith( QString::fromLatin1( "Enabled" ) ) ) + pluginsMap.insert( key.left( key.length() - 7 ), (it.data() == QString::fromLatin1( "true" )) ); + } + + QValueList plugins = availablePlugins( QString::null ); + QValueList::ConstIterator it2 = plugins.begin(); + QValueList::ConstIterator end = plugins.end(); + for ( ; it2 != end; ++it2 ) + { + // Protocols are loaded automatically so they aren't always in Plugins group. (fixes bug 167113) + if ( (*it2)->category() == QString::fromLatin1( "Protocols" ) ) + continue; + + QString pluginName = (*it2)->pluginName(); + bool inMap = pluginsMap.contains( pluginName ); + if ( (inMap && pluginsMap[pluginName]) || (!inMap && (*it2)->isPluginEnabledByDefault()) ) + { + if ( !plugin( pluginName ) ) + d->pluginsToLoad.push( pluginName ); + } + else + { + //This happens if the user unloaded plugins with the config plugin page. + // No real need to be assync because the user usualy unload few plugins + // compared tto the number of plugin to load in a cold start. - Olivier + if ( plugin( pluginName ) ) + unloadPlugin( pluginName ); + } + } + } + else + { + // we had no config, so we load any plugins that should be loaded by default. + QValueList plugins = availablePlugins( QString::null ); + QValueList::ConstIterator it = plugins.begin(); + QValueList::ConstIterator end = plugins.end(); + for ( ; it != end; ++it ) + { + if ( (*it)->isPluginEnabledByDefault() ) + d->pluginsToLoad.push( (*it)->pluginName() ); + } + } + // Schedule the plugins to load + QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) ); +} + +void PluginManager::slotLoadNextPlugin() +{ + if ( d->pluginsToLoad.isEmpty() ) + { + if ( d->shutdownMode == Private::StartingUp ) + { + d->shutdownMode = Private::Running; + d->isAllPluginsLoaded = true; + emit allPluginsLoaded(); + } + return; + } + + QString key = d->pluginsToLoad.pop(); + loadPluginInternal( key ); + + // Schedule the next run unconditionally to avoid code duplication on the + // allPluginsLoaded() signal's handling. This has the added benefit that + // the signal is delayed one event loop, so the accounts are more likely + // to be instantiated. + QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) ); +} + +Plugin * PluginManager::loadPlugin( const QString &_pluginId, PluginLoadMode mode /* = LoadSync */ ) +{ + QString pluginId = _pluginId; + + // Try to find legacy code + // FIXME: Find any cases causing this, remove them, and remove this too - Richard + if ( pluginId.endsWith( QString::fromLatin1( ".desktop" ) ) ) + { + kdWarning( 14010 ) << "Trying to use old-style API!" << endl << kdBacktrace() << endl; + pluginId = pluginId.remove( QRegExp( QString::fromLatin1( ".desktop$" ) ) ); + } + + if ( mode == LoadSync ) + { + return loadPluginInternal( pluginId ); + } + else + { + d->pluginsToLoad.push( pluginId ); + QTimer::singleShot( 0, this, SLOT( slotLoadNextPlugin() ) ); + return 0L; + } +} + +Plugin *PluginManager::loadPluginInternal( const QString &pluginId ) +{ + //kdDebug( 14010 ) << k_funcinfo << pluginId << endl; + + KPluginInfo *info = infoForPluginId( pluginId ); + if ( !info ) + { + kdWarning( 14010 ) << k_funcinfo << "Unable to find a plugin named '" << pluginId << "'!" << endl; + return 0L; + } + + if ( d->loadedPlugins.contains( info ) ) + return d->loadedPlugins[ info ]; + + int error = 0; + Plugin *plugin = KParts::ComponentFactory::createInstanceFromQuery( QString::fromLatin1( "Kopete/Plugin" ), + QString::fromLatin1( "[X-KDE-PluginInfo-Name]=='%1'" ).arg( pluginId ), this, 0, QStringList(), &error ); + + if ( plugin ) + { + d->loadedPlugins.insert( info, plugin ); + info->setPluginEnabled( true ); + + connect( plugin, SIGNAL( destroyed( QObject * ) ), this, SLOT( slotPluginDestroyed( QObject * ) ) ); + connect( plugin, SIGNAL( readyForUnload() ), this, SLOT( slotPluginReadyForUnload() ) ); + + kdDebug( 14010 ) << k_funcinfo << "Successfully loaded plugin '" << pluginId << "'" << endl; + + emit pluginLoaded( plugin ); + } + else + { + switch( error ) + { + case KParts::ComponentFactory::ErrNoServiceFound: + kdDebug( 14010 ) << k_funcinfo << "No service implementing the given mimetype " + << "and fullfilling the given constraint expression can be found." << endl; + break; + + case KParts::ComponentFactory::ErrServiceProvidesNoLibrary: + kdDebug( 14010 ) << "the specified service provides no shared library." << endl; + break; + + case KParts::ComponentFactory::ErrNoLibrary: + kdDebug( 14010 ) << "the specified library could not be loaded." << endl; + break; + + case KParts::ComponentFactory::ErrNoFactory: + kdDebug( 14010 ) << "the library does not export a factory for creating components." << endl; + break; + + case KParts::ComponentFactory::ErrNoComponent: + kdDebug( 14010 ) << "the factory does not support creating components of the specified type." << endl; + break; + } + + kdDebug( 14010 ) << k_funcinfo << "Loading plugin '" << pluginId << "' failed, KLibLoader reported error: '" << endl << + KLibLoader::self()->lastErrorMessage() << "'" << endl; + } + + return plugin; +} + +bool PluginManager::unloadPlugin( const QString &spec ) +{ + //kdDebug(14010) << k_funcinfo << spec << endl; + if( Plugin *thePlugin = plugin( spec ) ) + { + thePlugin->aboutToUnload(); + return true; + } + else + return false; +} + + + +void PluginManager::slotPluginDestroyed( QObject *plugin ) +{ + for ( Private::InfoToPluginMap::Iterator it = d->loadedPlugins.begin(); + it != d->loadedPlugins.end(); ++it ) + { + if ( it.data() == plugin ) + { + d->loadedPlugins.erase( it ); + break; + } + } + + if ( d->shutdownMode == Private::ShuttingDown && d->loadedPlugins.isEmpty() ) + { + // Use a timer to make sure any pending deleteLater() calls have + // been handled first + QTimer::singleShot( 0, this, SLOT( slotShutdownDone() ) ); + } +} + + + + +Plugin* PluginManager::plugin( const QString &_pluginId ) const +{ + // Hack for compatibility with Plugin::pluginId(), which returns + // classname() instead of the internal name. Changing that is not easy + // as it invalidates the config file, the contact list, and most likely + // other code as well. + // For now, just transform FooProtocol to kopete_foo. + // FIXME: In the future we'll need to change this nevertheless to unify + // the handling - Martijn + QString pluginId = _pluginId; + if ( pluginId.endsWith( QString::fromLatin1( "Protocol" ) ) ) + pluginId = QString::fromLatin1( "kopete_" ) + _pluginId.lower().remove( QString::fromLatin1( "protocol" ) ); + // End hack + + KPluginInfo *info = infoForPluginId( pluginId ); + if ( !info ) + return 0L; + + if ( d->loadedPlugins.contains( info ) ) + return d->loadedPlugins[ info ]; + else + return 0L; +} + +KPluginInfo * PluginManager::infoForPluginId( const QString &pluginId ) const +{ + QValueList::ConstIterator it; + for ( it = d->plugins.begin(); it != d->plugins.end(); ++it ) + { + if ( ( *it )->pluginName() == pluginId ) + return *it; + } + + return 0L; +} + + +bool PluginManager::setPluginEnabled( const QString &_pluginId, bool enabled /* = true */ ) +{ + QString pluginId = _pluginId; + + KConfig *config = KGlobal::config(); + config->setGroup( "Plugins" ); + + // FIXME: What is this for? This sort of thing is kconf_update's job - Richard + if ( !pluginId.startsWith( QString::fromLatin1( "kopete_" ) ) ) + pluginId.prepend( QString::fromLatin1( "kopete_" ) ); + + if ( !infoForPluginId( pluginId ) ) + return false; + + config->writeEntry( pluginId + QString::fromLatin1( "Enabled" ), enabled ); + config->sync(); + + return true; +} + +bool PluginManager::isAllPluginsLoaded() const +{ + return d->isAllPluginsLoaded; +} + +} //END namespace Kopete + + +#include "kopetepluginmanager.moc" + + + + + diff --git a/kopete/libkopete/kopetepluginmanager.h b/kopete/libkopete/kopetepluginmanager.h new file mode 100644 index 00000000..815cf422 --- /dev/null +++ b/kopete/libkopete/kopetepluginmanager.h @@ -0,0 +1,246 @@ +/* + kopetepluginmanager.h - Kopete Plugin Loader + + Copyright (c) 2002-2003 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPLUGINMANAGER_H +#define KOPETEPLUGINMANAGER_H + +#include +#include +#include +#include +#include + +#include "kopete_export.h" + +class KPluginInfo; + +namespace Kopete +{ + +class Plugin; + +typedef QValueList PluginList; + +/** + * @author Duncan Mac-Vicar Prett + * @author Martijn Klingens + */ +class KOPETE_EXPORT PluginManager : public QObject +{ + Q_OBJECT + Q_ENUMS( PluginLoadMode ) + +public: + /** + * Retrieve the plugin loader instance. + */ + static PluginManager* self(); + + ~PluginManager(); + + /** + * Returns a list of all available plugins for the given category. + * Currently there are two categories, "Plugins" and "Protocols", but + * you can add your own categories if you want. + * + * If you pass an empty string you get the complete list of ALL plugins. + * + * You can query all information on the plugins through the KPluginInfo + * interface. + */ + QValueList availablePlugins( const QString &category = QString::null ) const; + + /** + * Returns a list of all plugins that are actually loaded. + * If you omit the category you get all, otherwise it's a filtered list. + * See also @ref availablePlugins(). + */ + PluginList loadedPlugins( const QString &category = QString::null ) const; + + /** + * @brief Search by plugin name. This is the key used as X-KDE-PluginInfo-Name in + * the .desktop file, e.g. "kopete_jabber" + * + * @return The @ref Kopete::Plugin object found by the search, or a null + * pointer if the plugin is not loaded. + * + * If you want to also load the plugin you can better use @ref loadPlugin, which returns + * the pointer to the plugin if it's already loaded. + */ + Plugin *plugin( const QString &pluginName ) const; + + /** + * @return the KPluginInfo for the specified plugin + */ + KPluginInfo *pluginInfo( const Kopete::Plugin *plugin ) const; + + + /** + * Shuts down the plugin manager on Kopete shutdown, but first + * unloads all plugins asynchronously. + * + * After 3 seconds all plugins should be removed; what's still left + * by then is unloaded through a hard delete instead. + * + * Note that this call also derefs the plugin manager from the event + * loop, so do NOT call this method when not terminating Kopete! + */ + void shutdown(); + + /** + * Enable a plugin. + * + * This marks a plugin as enabled in the config file, so loadAll() + * can pick it up later. + * + * This method does not actually load a plugin, it only edits the + * config file. + * + * @param name is the name of the plugin as it is listed in the .desktop + * file in the X-KDE-Library field. + * @param enabled sets whether or not the plugin is enabled + * + * Returns false when no appropriate plugin can be found. + */ + bool setPluginEnabled( const QString &name, bool enabled = true ); + + /** + * This method check if all the plugins are loaded. + * @return true if all the plugins are loaded. + */ + bool isAllPluginsLoaded() const; + + /** + * Plugin loading mode. Used by @ref loadPlugin(). Code that doesn't want to block + * the GUI and/or lot a lot of plugins at once should use asynchronous loading (@c LoadAsync). + * The default is synchronous loading (@c LoadSync). + */ + enum PluginLoadMode { LoadSync, LoadAsync }; + +public slots: + /** + * @brief Load a single plugin by plugin name. Returns an existing plugin + * if one is already loaded in memory. + * + * If mode is set to Async, the plugin will be queued and loaded in + * the background. This method will return a null pointer. To get + * the loaded plugin you can track the @ref pluginLoaded() signal. + * + * See also @ref plugin(). + */ + Plugin *loadPlugin( const QString &pluginId, PluginLoadMode mode = LoadSync ); + + /** + * @brief Unload the plugin specified by @p pluginName + */ + bool unloadPlugin( const QString &pluginName ); + + /** + * @brief Loads all the enabled plugins. Also used to reread the + * config file when the configuration has changed. + */ + void loadAllPlugins(); + +signals: + /** + * @brief Signals a new plugin has just been loaded. + */ + void pluginLoaded( Kopete::Plugin *plugin ); + + /** + * @brief All plugins have been loaded by the plugin manager. + * + * This signal is emitted exactly ONCE, when the plugin manager has emptied + * its plugin queue for the first time. This means that if you call an async + * loadPlugin() before loadAllPlugins() this signal is probably emitted after + * the initial call completes, unless you are quick enough to fill the queue + * before it completes, which is a dangerous race you shouldn't count upon :) + * + * The signal is delayed one event loop iteration through a singleShot timer, + * but that is not guaranteed to be enough for account instantiation. You may + * need an additional timer for it in the code if you want to programmatically + * act on it. + * + * If you use the signal for enabling/disabling GUI objects there is little + * chance a user is able to activate them in the short while that's remaining, + * the slow part of the code is over now and the remaining processing time + * is neglectable for the user. + */ + void allPluginsLoaded(); + +private slots: + /** + * @brief Cleans up some references if the plugin is destroyed + */ + void slotPluginDestroyed( QObject *plugin ); + + /** + * shutdown() starts a timer, when it fires we force all plugins + * to be unloaded here by deref()-ing the event loop to trigger the plugin + * manager's destruction + */ + void slotShutdownTimeout(); + + /** + * Common entry point to deref() the KApplication. Used both by the clean + * shutdown and the timeout condition of slotShutdownTimeout() + */ + void slotShutdownDone(); + + /** + * Emitted by a Kopete::Plugin when it's ready for unload + */ + void slotPluginReadyForUnload(); + + /** + * Load a plugin from our queue. Does nothing if the queue is empty. + * Schedules itself again if more plugins are pending. + */ + void slotLoadNextPlugin(); + +private: + /** + * @internal + * + * The internal method for loading plugins. + * Called by @ref loadPlugin directly or through the queue for async plugin + * loading. + */ + Plugin * loadPluginInternal( const QString &pluginId ); + + /** + * @internal + * + * Find the KPluginInfo structure by key. Reduces some code duplication. + * + * Returns a null pointer when no plugin info is found. + */ + KPluginInfo * infoForPluginId( const QString &pluginId ) const; + + PluginManager(); + + class Private; + Private *d; + static PluginManager *s_self; +}; + +} + +#endif // KOPETEPLUGINMANAGER_H + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteprefs.cpp b/kopete/libkopete/kopeteprefs.cpp new file mode 100644 index 00000000..e1148260 --- /dev/null +++ b/kopete/libkopete/kopeteprefs.cpp @@ -0,0 +1,672 @@ +/* + kopeteprefs.cpp - Kopete Preferences Container-Class + + Copyright (c) 2002 by Stefan Gehn + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteprefs.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#define KOPETE_DEFAULT_CHATSTYLE "Kopete" + +KopetePrefs *KopetePrefs::s_prefs = 0L; + +KopetePrefs *KopetePrefs::prefs() +{ + if( !s_prefs ) + s_prefs = new KopetePrefs; + return s_prefs; +} + +KopetePrefs::KopetePrefs() : QObject( kapp, "KopetePrefs" ) +{ + config = KGlobal::config(); + load(); +} + +void KopetePrefs::load() +{ +// kdDebug( 14010 ) << k_funcinfo << endl; + config->setGroup("Appearance"); + + mIconTheme = config->readEntry("EmoticonTheme", defaultTheme()); + mUseEmoticons = config->readBoolEntry("Use Emoticons", true); + mEmoticonsRequireSpaces = config->readBoolEntry("EmoticonsRequireSpaces" , true ); + mShowOffline = config->readBoolEntry("ShowOfflineUsers", true); + mShowEmptyGroups = config->readBoolEntry("ShowEmptyGroups", true); + mGreyIdle = config->readBoolEntry("GreyIdleMetaContacts", true); + mSortByGroup = config->readBoolEntry("SortByGroup" , true); + mTreeView = config->readBoolEntry("TreeView", true); + mStartDocked = config->readBoolEntry("StartDocked", false); + mUseQueue = config->readBoolEntry("Use Queue", true); + mUseStack = config->readBoolEntry("Use Stack", false); + mRaiseMsgWindow = config->readBoolEntry("Raise Msg Window", false); + mShowEvents = config->readBoolEntry("Show Events in Chat Window", true); + mSpellCheck = config->readBoolEntry("SpellCheck", true); + mQueueUnreadMessages = config->readBoolEntry("Queue Unread Messages", false); + mQueueOnlyHighlightedMessagesInGroupChats = config->readBoolEntry("Queue Only Highlighted Messages In Group Chats", false); + mQueueOnlyMessagesOnAnotherDesktop = config->readBoolEntry("Queue Only Messages On Another Desktop", false); + mBalloonNotify = config->readBoolEntry("Balloon Notification", true); + mBalloonNotifyIgnoreClosesChatView = config->readBoolEntry("Balloon Notification Ignore Closes Chat View", false); + mBalloonCloseDelay = config->readNumEntry("Balloon Autoclose Delay", 30); + mBalloonClose = config->readBoolEntry("Balloon Autoclose", false); + mTrayflashNotify = config->readBoolEntry("Trayflash Notification", true); + mTrayflashNotifyLeftClickOpensMessage = config->readBoolEntry("Trayflash Notification Left Click Opens Message", true); + mTrayflashNotifySetCurrentDesktopToChatView = config->readBoolEntry("Trayflash Notification Set Current Desktop To Chat View", false); + mSoundIfAway = config->readBoolEntry("Sound Notification If Away", true); + mChatWindowPolicy = config->readNumEntry("Chatwindow Policy", 0); + mRichText = config->readBoolEntry("RichText editor", false); + mChatWShowSend = config->readBoolEntry("Show Chatwindow Send Button", true); + mRememberedMessages = config->readNumEntry("Remembered Messages", 5); + mTruncateContactNames = config->readBoolEntry("TruncateContactNames", false); + mMaxContactNameLength = config->readNumEntry("MaxContactNameLength", 20); + + mChatViewBufferSize = config->readNumEntry("ChatView BufferSize", 250); + + QColor tmpColor = KGlobalSettings::highlightColor(); + mHighlightBackground = config->readColorEntry("Highlight Background Color", &tmpColor); + tmpColor = KGlobalSettings::highlightedTextColor(); + mHighlightForeground = config->readColorEntry("Highlight Foreground Color", &tmpColor); + mHighlightEnabled = config->readBoolEntry("Highlighting Enabled", true); + mBgOverride = config->readBoolEntry("ChatView Override Background", false); + mFgOverride = config->readBoolEntry("ChatView Override Foreground", false); + mRtfOverride = config->readBoolEntry("ChatView Override RTF", false); + mInterfacePreference = config->readEntry("View Plugin", QString::fromLatin1("kopete_chatwindow") ); + tmpColor = KGlobalSettings::textColor(); + mTextColor = config->readColorEntry("Text Color", &tmpColor ); + tmpColor = KGlobalSettings::baseColor(); + mBgColor = config->readColorEntry("Bg Color", &tmpColor ); + tmpColor = KGlobalSettings::linkColor(); + mLinkColor = config->readColorEntry("Link Color", &tmpColor ); + mFontFace = config->readFontEntry("Font Face"); + tmpColor = darkGray; + mIdleContactColor = config->readColorEntry("Idle Contact Color", &tmpColor); + + mShowTray = config->readBoolEntry("Show Systemtray", true); + + _setStylePath(config->readEntry("StylePath")); + mStyleVariant = config->readEntry("StyleVariant"); + // Read Chat Window Style display + mGroupConsecutiveMessages = config->readBoolEntry("GroupConsecutiveMessages", true); + + mToolTipContents = config->readListEntry("ToolTipContents"); + if(mToolTipContents.empty()) + { + mToolTipContents + << QString::fromLatin1("FormattedName") + << QString::fromLatin1("userInfo") + << QString::fromLatin1("server") + << QString::fromLatin1("channels") + << QString::fromLatin1("FormattedIdleTime") + << QString::fromLatin1("channelMembers") + << QString::fromLatin1("channelTopic") + << QString::fromLatin1("emailAddress") + << QString::fromLatin1("homePage") + << QString::fromLatin1("onlineSince") + << QString::fromLatin1("lastOnline") + << QString::fromLatin1("awayMessage"); + } + + config->setGroup("ContactList"); + int n = metaObject()->findProperty( "contactListDisplayMode" ); + QString value = config->readEntry("DisplayMode",QString::fromLatin1("Default")); + mContactListDisplayMode = (ContactDisplayMode)metaObject()->property( n )->keyToValue( value.latin1() ); + n = metaObject()->findProperty( "contactListIconMode" ); + value = config->readEntry("IconMode", + QString::fromLatin1("IconDefault")); + mContactListIconMode = (IconDisplayMode) metaObject()->property( n )->keyToValue( value.latin1() ); + mContactListIndentContacts = config->readBoolEntry("IndentContacts", false); + mContactListUseCustomFonts = config->readBoolEntry("UseCustomFonts", false); + QFont font = KGlobalSettings::generalFont(); + mContactListNormalFont = config->readFontEntry("NormalFont", &font); + if ( font.pixelSize() != -1 ) + font.setPixelSize( (font.pixelSize() * 3) / 4 ); + else + font.setPointSizeFloat( font.pointSizeFloat() * 0.75 ); + mContactListSmallFont = config->readFontEntry("SmallFont", &font); + mContactListGroupNameColor = config->readColorEntry("GroupNameColor", &darkRed); + mContactListAnimation = config->readBoolEntry("AnimateChanges", true); + mContactListFading = config->readBoolEntry("FadeItems", true); + mContactListFolding = config->readBoolEntry("FoldItems", true); + mContactListAutoHide = config->readBoolEntry("AutoHide", false); + mContactListAutoHideTimeout = config->readUnsignedNumEntry("AutoHideTimeout", 30); + + // Load the reconnection setting + config->setGroup("General"); + mReconnectOnDisconnect = config->readBoolEntry("ReconnectOnDisconnect", true); + mAutoConnect = config->readBoolEntry("AutoConnect", false); + + // Nothing has changed yet + mWindowAppearanceChanged = false; + mContactListAppearanceChanged = false; + mMessageAppearanceChanged = false; + mStylePathChanged = false; + mStyleVariantChanged = false; +} + +void KopetePrefs::save() +{ +// kdDebug(14010) << "KopetePrefs::save()" << endl; + config->setGroup("Appearance"); + + config->writeEntry("EmoticonTheme", mIconTheme); + config->writeEntry("Use Emoticons", mUseEmoticons); + config->writeEntry("EmoticonsRequireSpaces", mEmoticonsRequireSpaces); + config->writeEntry("ShowOfflineUsers", mShowOffline); + config->writeEntry("ShowEmptyGroups", mShowEmptyGroups); + config->writeEntry("GreyIdleMetaContacts", mGreyIdle); + config->writeEntry("TreeView", mTreeView); + config->writeEntry("SortByGroup", mSortByGroup); + config->writeEntry("StartDocked", mStartDocked); + config->writeEntry("Use Queue", mUseQueue); + config->writeEntry("Use Stack", mUseStack); + config->writeEntry("Raise Msg Window", mRaiseMsgWindow); + config->writeEntry("Show Events in Chat Window", mShowEvents); + config->writeEntry("SpellCheck", mSpellCheck); + config->writeEntry("Queue Unread Messages", mQueueUnreadMessages); + config->writeEntry("Queue Only Highlighted Messages In Group Chats", mQueueOnlyHighlightedMessagesInGroupChats); + config->writeEntry("Queue Only Messages On Another Desktop", mQueueOnlyMessagesOnAnotherDesktop); + config->writeEntry("Balloon Notification", mBalloonNotify); + config->writeEntry("Balloon Notification Ignore Closes Chat View", mBalloonNotifyIgnoreClosesChatView); + config->writeEntry("Balloon Autoclose Delay", mBalloonCloseDelay); + config->writeEntry("Balloon Autoclose", mBalloonClose); + config->writeEntry("Trayflash Notification", mTrayflashNotify); + config->writeEntry("Trayflash Notification Left Click Opens Message", mTrayflashNotifyLeftClickOpensMessage); + config->writeEntry("Trayflash Notification Set Current Desktop To Chat View", mTrayflashNotifySetCurrentDesktopToChatView); + config->writeEntry("Sound Notification If Away", mSoundIfAway); + config->writeEntry("Chatwindow Policy", mChatWindowPolicy); + config->writeEntry("ChatView Override Background", mBgOverride); + config->writeEntry("ChatView Override Foreground", mFgOverride); + config->writeEntry("ChatView Override RTF", mRtfOverride); + config->writeEntry("ChatView BufferSize", mChatViewBufferSize); + config->writeEntry("Highlight Background Color", mHighlightBackground); + config->writeEntry("Highlight Foreground Color", mHighlightForeground); + config->writeEntry("Highlighting Enabled", mHighlightEnabled ); + config->writeEntry("Font Face", mFontFace); + config->writeEntry("Text Color",mTextColor); + config->writeEntry("Remembered Messages",mRememberedMessages); + config->writeEntry("Bg Color", mBgColor); + config->writeEntry("Link Color", mLinkColor); + config->writeEntry("Idle Contact Color", mIdleContactColor); + config->writeEntry("RichText editor", mRichText); + config->writeEntry("Show Chatwindow Send Button", mChatWShowSend); + config->writeEntry("TruncateContactNames", mTruncateContactNames); + config->writeEntry("MaxContactNameLength", mMaxContactNameLength); + + config->writeEntry("View Plugin", mInterfacePreference); + + config->writeEntry("Show Systemtray", mShowTray); + + //Style + //for xhtml+css + config->writeEntry("StylePath", mStylePath); + config->writeEntry("StyleVariant", mStyleVariant); + // Chat Window Display + config->writeEntry("GroupConsecutiveMessages", mGroupConsecutiveMessages); + + config->writeEntry("ToolTipContents", mToolTipContents); + + config->setGroup("ContactList"); + int n = metaObject()->findProperty( "contactListDisplayMode" ); + config->writeEntry("DisplayMode", metaObject()->property( n )->valueToKey( mContactListDisplayMode )); + n = metaObject()->findProperty( "contactListIconMode" ); + config->writeEntry("IconMode", metaObject()->property( n )->valueToKey( mContactListIconMode )); + config->writeEntry("IndentContacts", mContactListIndentContacts); + config->writeEntry("UseCustomFonts", mContactListUseCustomFonts); + config->writeEntry("NormalFont", mContactListNormalFont); + config->writeEntry("SmallFont", mContactListSmallFont); + config->writeEntry("GroupNameColor", mContactListGroupNameColor); + config->writeEntry("AnimateChanges", mContactListAnimation); + config->writeEntry("FadeItems", mContactListFading); + config->writeEntry("FoldItems", mContactListFolding); + config->writeEntry("AutoHide", mContactListAutoHide); + config->writeEntry("AutoHideTimeout", mContactListAutoHideTimeout); + + //Save the reconnection setting + config->setGroup("General"); + config->writeEntry("ReconnectOnDisconnect", mReconnectOnDisconnect); + config->writeEntry("AutoConnect", mAutoConnect); + + config->sync(); + emit saved(); + + if(mWindowAppearanceChanged) + emit windowAppearanceChanged(); + + if(mContactListAppearanceChanged) + emit contactListAppearanceChanged(); + + if(mMessageAppearanceChanged) + emit messageAppearanceChanged(); + + if(mStylePathChanged) + emit styleChanged(mStylePath); + + if(mStyleVariantChanged) + emit styleVariantChanged(mStyleVariant); + + // Clear all *Changed flags. This will cause breakage if someone makes some + // changes but doesn't save them in a slot connected to a *Changed signal. + mWindowAppearanceChanged = false; + mContactListAppearanceChanged = false; + mMessageAppearanceChanged = false; + mStylePathChanged = false; + mStyleVariantChanged = false; +} + +void KopetePrefs::setIconTheme(const QString &value) +{ + if( mIconTheme != value ) + { + mMessageAppearanceChanged = true; + mContactListAppearanceChanged = true; + } + mIconTheme = value; +} + +void KopetePrefs::setUseEmoticons(bool value) +{ + if( mUseEmoticons != value ) + { + mMessageAppearanceChanged = true; + mContactListAppearanceChanged = true; + } + mUseEmoticons = value; +} + +void KopetePrefs::setShowOffline(bool value) +{ + if( value != mShowOffline ) mContactListAppearanceChanged = true; + mShowOffline = value; +} + +void KopetePrefs::setShowEmptyGroups(bool value) +{ + if( value != mShowEmptyGroups ) mContactListAppearanceChanged = true; + mShowEmptyGroups = value; +} + +void KopetePrefs::setTreeView(bool value) +{ + if( value != mTreeView ) mContactListAppearanceChanged = true; + mTreeView = value; +} + +void KopetePrefs::setSortByGroup(bool value) +{ + if( value != mSortByGroup ) mContactListAppearanceChanged = true; + mSortByGroup = value; +} + +void KopetePrefs::setGreyIdleMetaContacts(bool value) +{ + if( value != mGreyIdle ) mContactListAppearanceChanged = true; + mGreyIdle = value; +} + +void KopetePrefs::setStartDocked(bool value) +{ + mStartDocked = value; +} + +void KopetePrefs::setUseQueue(bool value) +{ + mUseQueue = value; +} + +void KopetePrefs::setUseStack(bool value) +{ + mUseStack = value; +} + + +void KopetePrefs::setRaiseMsgWindow(bool value) +{ + mRaiseMsgWindow = value; +} + +void KopetePrefs::setRememberedMessages(int value) +{ + mRememberedMessages = value; +} + +void KopetePrefs::setShowEvents(bool value) +{ + mShowEvents = value; +} + +void KopetePrefs::setTrayflashNotify(bool value) +{ + mTrayflashNotify = value; +} + +void KopetePrefs::setSpellCheck(bool value) +{ + mSpellCheck = value; +} + +void KopetePrefs::setQueueUnreadMessages(bool value) +{ + mQueueUnreadMessages = value; +} + +void KopetePrefs::setQueueOnlyHighlightedMessagesInGroupChats(bool value) +{ + mQueueOnlyHighlightedMessagesInGroupChats = value; +} + +void KopetePrefs::setQueueOnlyMessagesOnAnotherDesktop(bool value) +{ + mQueueOnlyMessagesOnAnotherDesktop = value; +} + +void KopetePrefs::setTrayflashNotifyLeftClickOpensMessage(bool value) +{ + mTrayflashNotifyLeftClickOpensMessage = value; +} + +void KopetePrefs::setTrayflashNotifySetCurrentDesktopToChatView(bool value) +{ + mTrayflashNotifySetCurrentDesktopToChatView = value; +} + +void KopetePrefs::setBalloonNotify(bool value) +{ + mBalloonNotify = value; +} + +void KopetePrefs::setBalloonNotifyIgnoreClosesChatView(bool value) +{ + mBalloonNotifyIgnoreClosesChatView = value; +} + +void KopetePrefs::setBalloonClose( bool value ) +{ + mBalloonClose = value; +} + +void KopetePrefs::setBalloonDelay( int value ) +{ + mBalloonCloseDelay = value; +} + +void KopetePrefs::setSoundIfAway(bool value) +{ + mSoundIfAway = value; +} + +void KopetePrefs::setStylePath(const QString &stylePath) +{ + if(mStylePath != stylePath) mStylePathChanged = true; + _setStylePath(stylePath); +} + +void KopetePrefs::_setStylePath(const QString &stylePath) +{ + mStylePath = stylePath; + + // Fallback to default style if the directory doesn't exist + // or the value is empty. + if( !QFile::exists(stylePath) || stylePath.isEmpty() ) + { + QString fallback; + fallback = QString(QString::fromLatin1("styles/%1/")).arg(QString::fromLatin1(KOPETE_DEFAULT_CHATSTYLE)); + mStylePath = locate("appdata", fallback); + } +} + +void KopetePrefs::setStyleVariant(const QString &variantPath) +{ + if(mStyleVariant != variantPath) mStyleVariantChanged = true; + mStyleVariant = variantPath; +} + +void KopetePrefs::setFontFace( const QFont &value ) +{ + if( value != mFontFace ) mWindowAppearanceChanged = true; + mFontFace = value; +} + +void KopetePrefs::setTextColor( const QColor &value ) +{ + if( value != mTextColor ) mWindowAppearanceChanged = true; + mTextColor = value; +} + +void KopetePrefs::setBgColor( const QColor &value ) +{ + if( value != mBgColor ) mWindowAppearanceChanged = true; + mBgColor = value; +} + +void KopetePrefs::setLinkColor( const QColor &value ) +{ + if( value != mLinkColor ) mWindowAppearanceChanged = true; + mLinkColor = value; +} + +void KopetePrefs::setChatWindowPolicy(int value) +{ + mChatWindowPolicy = value; +} + +void KopetePrefs::setTruncateContactNames( bool value ) +{ + mTruncateContactNames = value; +} + +void KopetePrefs::setMaxContactNameLength( int value ) +{ + mMaxContactNameLength = value; +} + +void KopetePrefs::setInterfacePreference(const QString &value) +{ + mInterfacePreference = value; +} + +void KopetePrefs::setChatViewBufferSize( int value ) +{ + mChatViewBufferSize = value; +} + +void KopetePrefs::setHighlightBackground(const QColor &value) +{ + if( value != mHighlightBackground ) mWindowAppearanceChanged = true; + mHighlightBackground = value; +} + +void KopetePrefs::setHighlightForeground(const QColor &value) +{ + if( value != mHighlightForeground ) mWindowAppearanceChanged = true; + mHighlightForeground = value; +} + +void KopetePrefs::setHighlightEnabled(bool value) +{ + if( value != mHighlightEnabled ) mWindowAppearanceChanged = true; + mHighlightEnabled = value; +} + +void KopetePrefs::setBgOverride(bool value) +{ + if( value != mBgOverride ) mMessageAppearanceChanged = true; + mBgOverride = value; +} + +void KopetePrefs::setFgOverride(bool value) +{ + if( value != mFgOverride ) mMessageAppearanceChanged = true; + mFgOverride = value; +} + +void KopetePrefs::setRtfOverride(bool value) +{ + if( value != mRtfOverride ) mMessageAppearanceChanged = true; + mRtfOverride = value; +} + +void KopetePrefs::setShowTray(bool value) +{ + mShowTray = value; +} + + +QString KopetePrefs::fileContents(const QString &path) +{ + QString contents; + QFile file( path ); + if ( file.open( IO_ReadOnly ) ) + { + QTextStream stream( &file ); + contents = stream.read(); + file.close(); + } + return contents; +} + +void KopetePrefs::setIdleContactColor(const QColor &value) +{ + if( value != mIdleContactColor ) mContactListAppearanceChanged = true; + mIdleContactColor = value; +} + +void KopetePrefs::setRichText(bool value) +{ + mRichText=value; +} + +void KopetePrefs::setToolTipContents(const QStringList &value) +{ + mToolTipContents=value; +} + +void KopetePrefs::setContactListIndentContacts( bool v ) +{ + if( v != mContactListIndentContacts ) mContactListAppearanceChanged = true; + mContactListIndentContacts = v; +} + +void KopetePrefs::setContactListDisplayMode( ContactDisplayMode v ) +{ + if( v != mContactListDisplayMode ) mContactListAppearanceChanged = true; + mContactListDisplayMode = v; +} + +void KopetePrefs::setContactListIconMode( IconDisplayMode v ) +{ + if( v != mContactListIconMode ) mContactListAppearanceChanged = true; + mContactListIconMode = v; +} + +void KopetePrefs::setContactListUseCustomFonts( bool v ) +{ + if( v != mContactListUseCustomFonts ) mContactListAppearanceChanged = true; + mContactListUseCustomFonts = v; +} + +void KopetePrefs::setContactListCustomNormalFont( const QFont & v ) +{ + if( v != mContactListNormalFont ) mContactListAppearanceChanged = true; + mContactListNormalFont = v; +} + +void KopetePrefs::setContactListCustomSmallFont( const QFont & v ) +{ + if( v != mContactListSmallFont ) mContactListAppearanceChanged = true; + mContactListSmallFont = v; +} + +QFont KopetePrefs::contactListSmallFont() const +{ + if ( mContactListUseCustomFonts ) + return contactListCustomSmallFont(); + QFont smallFont = KGlobalSettings::generalFont(); + if ( smallFont.pixelSize() != -1 ) + smallFont.setPixelSize( (smallFont.pixelSize() * 3) / 4 ); + else + smallFont.setPointSizeFloat( smallFont.pointSizeFloat() * 0.75 ); + return smallFont; +} + +void KopetePrefs::setContactListGroupNameColor( const QColor & v ) +{ + if( v != mContactListGroupNameColor ) mContactListAppearanceChanged = true; + mContactListGroupNameColor = v; +} + +void KopetePrefs::setContactListAnimation( bool n ) +{ + if( n != mContactListAnimation ) mContactListAppearanceChanged = true; + mContactListAnimation = n; +} + +void KopetePrefs::setContactListFading( bool n ) +{ + if( n != mContactListFading ) mContactListAppearanceChanged = true; + mContactListFading = n; +} + +void KopetePrefs::setContactListFolding( bool n ) +{ + if( n != mContactListFolding ) mContactListAppearanceChanged = true; + mContactListFolding = n; +} + +void KopetePrefs::setContactListAutoHide( bool n ) +{ + if( n != mContactListAutoHide ) mContactListAppearanceChanged = true; + mContactListAutoHide = n; +} + +void KopetePrefs::setContactListAutoHideTimeout( unsigned int n ) +{ + if( n != mContactListAutoHideTimeout ) mContactListAppearanceChanged = true; + mContactListAutoHideTimeout = n; +} + +void KopetePrefs::setReconnectOnDisconnect( bool newSetting ) +{ + mReconnectOnDisconnect = newSetting; +} + +void KopetePrefs::setAutoConnect(bool b) +{ + mAutoConnect=b; +} + +void KopetePrefs::setEmoticonsRequireSpaces( bool b ) +{ + if( mEmoticonsRequireSpaces != b ) + { + mMessageAppearanceChanged = true; + mContactListAppearanceChanged = true; + } + mEmoticonsRequireSpaces=b; +} + +void KopetePrefs::setGroupConsecutiveMessages( bool value ) +{ + mGroupConsecutiveMessages = value; +} +#include "kopeteprefs.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteprefs.h b/kopete/libkopete/kopeteprefs.h new file mode 100644 index 00000000..4a5162ff --- /dev/null +++ b/kopete/libkopete/kopeteprefs.h @@ -0,0 +1,318 @@ +/* + kopeteprefs.cpp - Kopete Preferences Container-Class + + Copyright (c) 2002 by Stefan Gehn + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __KOPETEPREFS_H__ +#define __KOPETEPREFS_H__ + +#include +#include +#include +#include + +#include "kopete_export.h" + +class KConfig; + +class KOPETE_EXPORT KopetePrefs : public QObject +{ + Q_OBJECT + // here so we can use Qt to translate enums<-->strings + Q_PROPERTY( ContactDisplayMode contactListDisplayMode READ contactListDisplayMode WRITE setContactListDisplayMode ) + Q_PROPERTY( IconDisplayMode contactListIconMode READ contactListIconMode WRITE setContactListIconMode ) + Q_ENUMS( ContactDisplayMode IconDisplayMode ) + +public: + /** + * The prefs container-class is a singleton object. Use this method to retrieve + * the instance. + */ + static KopetePrefs *prefs(); + + /** + * Reads all pref-variables from KConfig + * usually you don't need this as KopetePrefs loads settings + * when an instance is created + */ + void load(); + + /** + * Stores all pref-variables into KConfig + */ + void save(); + + QString iconTheme() const { return mIconTheme; } + bool useEmoticons() const { return mUseEmoticons; } + bool showOffline() const { return mShowOffline; } + bool showEmptyGroups() const { return mShowEmptyGroups; } + bool treeView() const { return mTreeView; } + bool sortByGroup() const { return mSortByGroup; } + bool greyIdleMetaContacts() const { return mGreyIdle; } + bool startDocked() const { return mStartDocked; } + bool useQueue() const { return mUseQueue; } + bool useStack() const { return mUseStack; } + bool raiseMsgWindow() const{ return mRaiseMsgWindow; } + bool showEvents() const{ return mShowEvents; } + bool trayflashNotify() const { return mTrayflashNotify; } + bool spellCheck() const { return mSpellCheck; } + bool queueUnreadMessages() const { return mQueueUnreadMessages; } + bool queueOnlyHighlightedMessagesInGroupChats() const { return mQueueOnlyHighlightedMessagesInGroupChats; } + bool queueOnlyMessagesOnAnotherDesktop() const { return mQueueOnlyMessagesOnAnotherDesktop; } + bool trayflashNotifyLeftClickOpensMessage() const { return mTrayflashNotifyLeftClickOpensMessage; } + bool trayflashNotifySetCurrentDesktopToChatView() const { return mTrayflashNotifySetCurrentDesktopToChatView; } + bool balloonNotify() const { return mBalloonNotify; } + bool balloonNotifyIgnoreClosesChatView() const { return mBalloonNotifyIgnoreClosesChatView; } + bool balloonClose() const { return mBalloonClose; } + int balloonCloseDelay() const { return mBalloonCloseDelay; } + bool soundIfAway() const { return mSoundIfAway; } + int chatViewBufferSize() const { return mChatViewBufferSize; } + int rememberedMessages() const { return mRememberedMessages; } + const QColor &highlightBackground() const { return mHighlightBackground; } + const QColor &highlightForeground() const { return mHighlightForeground; } + const QColor &textColor() const { return mTextColor; } + const QColor &bgColor() const { return mBgColor; } + const QColor &linkColor() const { return mLinkColor; } + const QFont &fontFace() const { return mFontFace; } + const QColor &idleContactColor() const { return mIdleContactColor; } + bool highlightEnabled() const { return mHighlightEnabled; } + bool bgOverride() const { return mBgOverride; } + bool fgOverride() const { return mFgOverride; } + bool rtfOverride() const { return mRtfOverride; } + + QString interfacePreference() const { return mInterfacePreference; } + bool showTray() const { return mShowTray; } + bool richText() const { return mRichText; } + bool chatWShowSend() const { return mChatWShowSend; } + bool autoConnect() const { return mAutoConnect; } + + int chatWindowPolicy() const { return mChatWindowPolicy; } + + //Styles + QString defaultTheme() const { return QString::fromLatin1("Default"); } + //for Adium (xhtml+css) + QString stylePath() const { return mStylePath; } + QString styleVariant() const { return mStyleVariant; } + + QStringList toolTipContents() const { return mToolTipContents; } + + /// + enum ContactDisplayMode { Classic, RightAligned, Detailed, Yagami, Default = Classic }; + /// + enum IconDisplayMode { IconPic, PhotoPic, IconDefault = IconPic }; + bool contactListIndentContacts() const { return mContactListIndentContacts; } + ContactDisplayMode contactListDisplayMode() const { return mContactListDisplayMode; } + IconDisplayMode contactListIconMode() const { return mContactListIconMode; } + bool contactListUseCustomFonts() const { return mContactListUseCustomFonts; } + QFont contactListCustomNormalFont() const { return mContactListNormalFont; } + QFont contactListCustomSmallFont() const { return mContactListSmallFont; } + QFont contactListSmallFont() const; + QColor contactListGroupNameColor() const { return mContactListGroupNameColor; } + bool contactListAnimation() const { return mContactListAnimation; } + bool contactListFading() const { return mContactListFading; } + bool contactListFolding() const { return mContactListFolding; } + bool contactListAutoHide() const { return mContactListAutoHide; } + unsigned int contactListAutoHideTimeout() const { return mContactListAutoHideTimeout; } + + bool reconnectOnDisconnect() const { return mReconnectOnDisconnect; } + + bool truncateContactNames() const { return mTruncateContactNames; } + int maxConactNameLength() const { return mMaxContactNameLength; } + bool emoticonsRequireSpaces() const { return mEmoticonsRequireSpaces; } + bool groupConsecutiveMessages() const { return mGroupConsecutiveMessages; } + + void setIconTheme(const QString &value); + void setUseEmoticons(bool value); + void setShowOffline(bool value); + void setShowEmptyGroups(bool value); + void setTreeView(bool); + void setSortByGroup(bool); + void setGreyIdleMetaContacts(bool); + void setStartDocked(bool); + void setUseQueue(bool); + void setUseStack(bool); + void setRaiseMsgWindow(bool); + void setShowEvents(bool); + void setTrayflashNotify(bool); + void setSpellCheck(bool); + void setQueueUnreadMessages(bool); + void setQueueOnlyHighlightedMessagesInGroupChats(bool); + void setQueueOnlyMessagesOnAnotherDesktop(bool); + void setTrayflashNotifyLeftClickOpensMessage(bool); + void setTrayflashNotifySetCurrentDesktopToChatView(bool); + void setBalloonNotify(bool); + void setBalloonNotifyIgnoreClosesChatView(bool); + void setSoundIfAway(bool); + void setBeepNotify(bool); + void setChatWindowPolicy(int); + void setStylePath(const QString &); + void setStyleVariant(const QString &); + void setChatViewBufferSize(int); + void setHighlightBackground(const QColor &); + void setHighlightForeground(const QColor &); + void setHighlightEnabled(bool); + void setBgOverride(bool); + void setFgOverride(bool); + void setRtfOverride(bool); + void setInterfacePreference(const QString &viewPlugin); + void setTextColor(const QColor &); + void setBgColor(const QColor &); + void setLinkColor(const QColor &); + void setFontFace(const QFont &); + void setIdleContactColor(const QColor &); + void setShowTray(bool); + void setRichText(bool); + void setRememberedMessages(int); + void setToolTipContents(const QStringList &); + void setContactListIndentContacts( bool v ); + void setContactListDisplayMode( ContactDisplayMode v ); + void setContactListIconMode( IconDisplayMode v ); + void setContactListUseCustomFonts( bool v ); + void setContactListCustomNormalFont( const QFont & v ); + void setContactListCustomSmallFont( const QFont & v ); + void setContactListGroupNameColor( const QColor & v ); + void setContactListAnimation( bool ); + void setContactListFading( bool ); + void setContactListFolding( bool ); + void setContactListAutoHide( bool ); + void setContactListAutoHideTimeout( unsigned int ); + void setReconnectOnDisconnect( bool newSetting ); + void setTruncateContactNames( bool ); + void setMaxContactNameLength( int ); + void setAutoConnect( bool ); + void setEmoticonsRequireSpaces( bool ); + void setBalloonClose( bool ); + void setBalloonDelay( int ); + void setGroupConsecutiveMessages( bool ); + +signals: + /** + * Emitted when config gets saved by save() + */ + void saved(); + /** + * Emitted when config gets saved by save() and a certain + * setting has changed. + * Naming scheme is the same as with the config vars. + */ + void windowAppearanceChanged(); + void messageAppearanceChanged(); + void contactListAppearanceChanged(); + /** + * Emitted when chat Window Style changed. + * @param stylePath New stylePath + */ + void styleChanged(const QString &stylePath); + /** + * Emitted when ChatWindowStyle variant changed. + * @param variantPath New variant Path. + */ + void styleVariantChanged(const QString &variantPath); + +private: + /** + * Private constructor: we are a singleton + */ + KopetePrefs(); + + /** + * Our instance + */ + static KopetePrefs *s_prefs; + + KConfig *config; + + QString mIconTheme; + bool mUseEmoticons; + bool mShowOffline; + bool mShowEmptyGroups; + bool mGreyIdle; + bool mTreeView; + bool mSortByGroup; + bool mStartDocked; + bool mUseQueue; + bool mUseStack; + bool mRaiseMsgWindow; + bool mShowEvents; + bool mTrayflashNotify; + bool mSpellCheck; + bool mQueueUnreadMessages; + bool mQueueOnlyHighlightedMessagesInGroupChats; + bool mQueueOnlyMessagesOnAnotherDesktop; + bool mTrayflashNotifyLeftClickOpensMessage; + bool mTrayflashNotifySetCurrentDesktopToChatView; + bool mBalloonNotify; + bool mBalloonNotifyIgnoreClosesChatView; + bool mBalloonClose; + int mBalloonCloseDelay; + bool mSoundIfAway; + int mRememberedMessages; + QString mInterfacePreference; + int mChatViewBufferSize; + QColor mHighlightBackground; + QColor mHighlightForeground; + QColor mTextColor; + QColor mBgColor; + QColor mLinkColor; + QFont mFontFace; + QColor mIdleContactColor; + bool mHighlightEnabled; + bool mBgOverride; + bool mFgOverride; + bool mRtfOverride; + bool mShowTray; + bool mWindowAppearanceChanged; + bool mMessageAppearanceChanged; + bool mContactListAppearanceChanged; + bool mChatWShowSend; + bool mAutoConnect; + + int mChatWindowPolicy; + + bool mTruncateContactNames; + int mMaxContactNameLength; + + bool mRichText; + + // xhtml+css + //for Adium (xhtml+css) + QString mStylePath; + QString mStyleVariant; + bool mStylePathChanged; + bool mStyleVariantChanged; + + QStringList mToolTipContents; + + bool mContactListIndentContacts; + ContactDisplayMode mContactListDisplayMode; + IconDisplayMode mContactListIconMode; + bool mContactListUseCustomFonts; + QFont mContactListNormalFont; + QFont mContactListSmallFont; + QColor mContactListGroupNameColor; + bool mContactListAnimation; + bool mContactListFading; + bool mContactListFolding; + bool mContactListAutoHide; + unsigned int mContactListAutoHideTimeout; + + bool mReconnectOnDisconnect; + bool mEmoticonsRequireSpaces; + bool mGroupConsecutiveMessages; + + QString fileContents(const QString &path); + void _setStylePath (const QString &); +}; +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteproperties.cpp b/kopete/libkopete/kopeteproperties.cpp new file mode 100644 index 00000000..9009cd07 --- /dev/null +++ b/kopete/libkopete/kopeteproperties.cpp @@ -0,0 +1,50 @@ +/* + kopetetproperties.cpp - Kopete Properties + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteproperties.h" + +#include + +#include +#include +#include + +namespace Kopete { +namespace Properties { + +// Keep as much type-independent code out of the templated stuff as we can +// FIXME: shouldn't be inline +void customPropertyDataIncorrectType( const char *name, const std::type_info &found, const std::type_info &expected ) +{ + kdWarning(14010) << "data time mismatch for property data name " << name + << ". found: " << found.name() << ", expected: " << expected.name() << endl; +} + +template<> +int variantTo(QVariant) { return 0; } +//... + +QVariant variantFromXML(const QDomElement&) +{ + return QVariant(); +} + +void variantToXML(QVariant v, QDomElement &) +{ +} + +} // namespace Properties +} // namespace Kopete diff --git a/kopete/libkopete/kopeteproperties.h b/kopete/libkopete/kopeteproperties.h new file mode 100644 index 00000000..bfeaedea --- /dev/null +++ b/kopete/libkopete/kopeteproperties.h @@ -0,0 +1,350 @@ +/* + kopeteproperties.h - Kopete Properties + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPROPERTIES_H +#define KOPETEPROPERTIES_H + +#include + +#include + +class QString; +class QVariant; +class QDomElement; + +namespace Kopete +{ + +/** + * Contains the classes forming Kopete's Properties system. + * + * @todo Explain more, give examples. + * + * @author Richard Smith + */ +namespace Properties +{ + +//BEGIN core functionality + +/** + * @brief Property-type-independent base class for properties + * + * The base class for all properties of any type which can be set or got for @p Parent + * objects. It is rare to need to use this class directly. Usually you will want to use + * the @ref Property derived class, or dynamic_cast the PropertyBase object to another interface. + * + * @see Property UserVisible XMLSerializable StringSerializable + * + * @author Richard Smith + */ +template +class PropertyBase +{ +public: + /** + * Returns the name of the property. This name should uniquely identify this property + * within the type Parent, and will be used for persistently identifying this property. + * + * For core properties, the chosen name should not contain any slash characters. For + * properties defined in plugins kept in Kopete's CVS, the name should be of the form + * pluginName/propertyName. For third-party plugins, please use a URL with a host which + * you own, such as "http://my-host.com/kopete/properties/groupId". + * + * @return the name of this property. + */ + virtual const char *name() const = 0; +}; + +/** + * @brief Property-type-dependent base class for properties + * + * This class represents a property of type @p Type applicable to @p Parent objects. Usage + * of this class is usually as simple as: + * + * \code + * SomeParent *propertyContainer = ... + * Property &myProperty = ... + * QString value = propertyContainer->property(myProperty); + * propertyContainer->setProperty(myProperty, "hello"); + * \endcode + * + * You should never need to call functions in this class directly. + */ +template +class Property : public PropertyBase +{ +public: + /** + * Returns the value of this property in the object @p parent. + */ + virtual Type get( const Parent *parent ) const = 0; + /** + * Sets the value of this property in the object @p parent. + */ + virtual void set( Parent *, const Type & ) const = 0; +}; + +/** + * @brief Base class for property data objects + * + * Some property objects want to store property-specific data in their parent objects. + * To support that, subclasses of this class are permitted to be stored. Once passed + * to the @ref PropertyStorage object via @ref PropertyStorage::setCustomPropertyData, + * the @ref PropertyStorage object owns the PropertyData, and will delete it when it + * is no longer needed. + */ +struct PropertyData +{ + virtual ~PropertyData() {} +}; + +/** + * @brief Storage object for PropertyData objects + * + * This class is responsible for storing PropertyData-derived data objects for properties. + * This is the non-templated part of the @ref WithProperties class, split out into its own + * class to eliminate the template bloat. + */ +class PropertyStorage +{ + typedef QAsciiDict PropertyDict; + // setCustomPropertyData can be called on a const object, allowing the + // guarantee that DataProperty::data() never returns 0. + mutable PropertyDict _storage; + +public: + PropertyStorage() { _storage.setAutoDelete( true ); } + + /** + * Sets the stored property data with name @p name to be @p data. + * + * @note The @p name argument should usually be the name of the property which the data + * is being stored for. However, if properties wish to share data, they may choose to + * name their custom data differently. Names are bound by the same rules as are laid out + * for naming properties in PropertyBase::name. + */ + void setCustomPropertyData( const char *name, PropertyData *data ) const { _storage.replace( name, data ); } + + /** + * Gets the stored property data with name @p name. Returns a null + * pointer if no data has been stored for that property. + */ + PropertyData *getCustomPropertyData( const char *name ) const { return _storage[name]; } +}; + +/** + * @brief Base class for classes to which properties can be applied + * + * This class provides support for properties to another class. If you want your class + * to support properties, derive from this passing your class as the Parent parameter: + * + * \code + * class YourClass : public WithProperties { ... }; + * \endcode + * + * You will also need to explicitly specialise the propertyCreated() member function to + * load property data upon creation of a new property object. + */ +template +class WithProperties : public PropertyStorage +{ +public: + /** + * Get the value of property @p prop in this object. + * @param prop the Property object representing the property to get + */ + template + T property( Property const &prop ) { return prop.get( static_cast(this) ); } + /** + * Set the value of property @p prop in this object. + * @param prop the Property object representing the property to get + * @param value the value to set the property to + */ + template + void setProperty( Property const &prop, const T &value ) { prop.set( static_cast(this), value ); } + + /** + * Called when a property is created; loads the Parent object's data into the property. + * + * @note Derived classes must explicitly specialize this to load the property's data into + * every object of this type. + */ + static void propertyCreated( const PropertyBase &property ); +}; + +//END core functionality + +//BEGIN interfaces + +/** + * @brief An interface for user-visible properties + * @todo document + */ +template +struct UserVisible +{ + virtual QString userText( Parent * ) = 0; + virtual QString label() = 0; + virtual QString icon() = 0; +}; + +/** + * @brief An interface for properties which can be serialized as XML + * @todo document + */ +template +struct XMLSerializable +{ + virtual void fromXML( Parent *, const QDomElement & ) = 0; + virtual void toXML( const Parent *, QDomElement & ) = 0; +}; + +/** + * @brief An interface for properties which can be serialized as strings + * @todo document + */ +template +struct StringSerializable +{ + virtual void fromString( Parent *, const QString & ) = 0; + virtual QString toString( const Parent * ) = 0; +}; + +//END interfaces + +//BEGIN convenience classes + +/** + * @internal Display a warning message when the wrong type of property data is found + */ +void customPropertyDataIncorrectType( const char *name, const std::type_info &found, const std::type_info &expected ); + +/** + * @brief Convenience implementation of a Property that stores PropertyData + * + * A property for objects of type @p Parent, that stores data in the class @p Data. + * @p Data must be derived from @ref PropertyBase, or your code will not compile. + */ +template +class DataProperty : public Property +{ +public: + Data *data( const Parent *c ) const + { + PropertyData *pd = c->getCustomPropertyData( this->name() ); + Data *data = dynamic_cast(pd); + if ( !data ) + { + if ( pd ) + customPropertyDataIncorrectType( this->name(), typeid(*pd), typeid(Data) ); + data = new Data; + c->setCustomPropertyData( this->name(), data ); + } + return data; + } +}; + +/** + * @brief Convenience implementation of a PropertyData subclass which stores a single datum + * + * If a @ref Property needs to store only a single value in an object, using this + * class is simpler than deriving from @ref PropertyData yourself. The value will + * be default-constructed (which means for numeric types and pointers it will be + * set to 0). + */ +template +struct SimplePropertyData : public PropertyData +{ + SimplePropertyData() : value() {} + T value; +}; + +/** + * @brief Convenience implementation of a Property which stores a single datum as PropertyData + * + * This convenience class implements the @ref Property interface by simply storing and + * retrieving the datum from PropertyData. This class does not provide any serialization + * of the data. + * + * @note You will need to derive from this class to use it; the @ref name function is + * still pure virtual. + */ +template +class SimpleDataProperty : public DataProperty > +{ +public: + Type get( const Parent *p ) const { return data(p)->value; } + void set( Parent *p, const Type &v ) const { data(p)->value = v; } +}; + +/** + * Move somewhere else + * @{ + */ + +/** + * Explicitly specialised for all types QVariant supports + */ +template T variantTo(QVariant); + +QVariant variantFromXML(const QDomElement&); +void variantToXML(QVariant v, QDomElement &); + +/** + * @} + */ + +/** + * @brief Convenience implementation of XMLSerializable in terms of QVariants + * + * This class provides XML serialization for data that can be stored in a QVariant. You + * will need to multiply-inherit from this class and (usually indirectly) from @ref Property. + * + * You can combine this class with other convenience classes such as SimpleDataProperty + * like this: + * + * \code + * class ContactNickNameProperty + * : public SimpleDataProperty + * , XMLProperty + * { + * public: + * const char *name() const { return "nickName"; } + * }; + * \endcode + */ +template +class XMLProperty : public XMLSerializable +{ +public: + void fromXML( Parent *t, const QDomElement &e ) + { + static_cast(this)->set(t, variantTo(variantFromXML(e))); + } + void toXML( const Parent *t, QDomElement &e ) + { + variantToXML(QVariant(static_cast(this)->get(t)),e); + } +}; + +//END convenience classes + +} // namespace Properties + +} // namespace Kopete + +#endif diff --git a/kopete/libkopete/kopeteprotocol.cpp b/kopete/libkopete/kopeteprotocol.cpp new file mode 100644 index 00000000..7854a1a3 --- /dev/null +++ b/kopete/libkopete/kopeteprotocol.cpp @@ -0,0 +1,340 @@ +/* + kopeteprotocol.cpp - Kopete Protocol + + Copyright (c) 2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2004 by Olivier Goffart + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteprotocol.h" + +#include +#include +#include + +#include + +#include "kopeteaccountmanager.h" +#include "kopeteaccount.h" +#include "kopetecontact.h" +#include "kopeteglobal.h" +#include "kopetecontactproperty.h" +#include "kopetemetacontact.h" + +namespace Kopete +{ + +class Protocol::Private +{ +public: + bool unloading; + int capabilities; + /* + * Make sure we always have a lastSeen and a fullname property as long as + * a protocol is loaded + */ + ContactPropertyTmpl mStickLastSeen; + ContactPropertyTmpl mStickFullName; + + Kopete::OnlineStatus accountNotConnectedStatus; +}; + +Protocol::Protocol( KInstance *instance, QObject *parent, const char *name ) +: Plugin( instance, parent, name ) +{ + d = new Private; + d->mStickLastSeen = Global::Properties::self()->lastSeen(); + d->mStickFullName = Global::Properties::self()->fullName(); + d->unloading = false; + d->capabilities = 0; + d->accountNotConnectedStatus = Kopete::OnlineStatus( Kopete::OnlineStatus::Unknown, 0, this, Kopete::OnlineStatus::AccountOffline, QString::fromLatin1( "account_offline_overlay" ), i18n( "Account Offline" ) ); +} + +Protocol::~Protocol() +{ + // Remove all active accounts + QDict accounts = AccountManager::self()->accounts( this ); + if ( !accounts.isEmpty() ) + { + kdWarning( 14010 ) << k_funcinfo << "Deleting protocol with existing accounts! Did the account unloading go wrong?" << endl; + + for( QDictIterator it( accounts ); it.current() ; ++it ) + delete *it; + } + + delete d; +} + +unsigned int Protocol::capabilities() const +{ + return d->capabilities; +} + +void Protocol::setCapabilities( unsigned int capabilities ) +{ + d->capabilities = capabilities; +} + + +Kopete::OnlineStatus Protocol::accountOfflineStatus() const +{ + return d->accountNotConnectedStatus; +} + +void Protocol::slotAccountOnlineStatusChanged( Contact *self ) +{//slot connected in aboutToUnload + if ( !self || !self->account() || self->account()->isConnected()) + return; + // some protocols change status several times during shutdown. We should only call deleteLater() once + disconnect( self, 0, this, 0 ); + + connect( self->account(), SIGNAL(accountDestroyed(const Kopete::Account* )), + this, SLOT( slotAccountDestroyed( ) ) ); + + self->account()->deleteLater(); +} + +void Protocol::slotAccountDestroyed( ) +{ + QDict dict = AccountManager::self()->accounts( this ); + if ( dict.isEmpty() ) + { + // While at this point we are still in a stack trace from the destroyed + // account it's safe to emit readyForUnload already, because it uses a + // deleteLater rather than a delete for exactly this reason, to keep the + // API managable + emit( readyForUnload() ); + } +} + +void Protocol::aboutToUnload() +{ + + d->unloading = true; + + // Disconnect all accounts + QDict accounts = AccountManager::self()->accounts( this ); + + if ( accounts.isEmpty() ) + emit readyForUnload(); + else for ( QDictIterator it( accounts ); it.current() ; ++it ) + { + if ( it.current()->myself() && it.current()->myself()->isOnline() ) + { + kdDebug( 14010 ) << k_funcinfo << it.current()->accountId() << + " is still connected, disconnecting..." << endl; + + QObject::connect( it.current()->myself(), + SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT( slotAccountOnlineStatusChanged( Kopete::Contact * ) ) ); + it.current()->disconnect(); + } + else + { + // Remove account, it's already disconnected + kdDebug( 14010 ) << k_funcinfo << it.current()->accountId() << + " is already disconnected, deleting..." << endl; + + QObject::connect( it.current(), SIGNAL( accountDestroyed( const Kopete::Account* ) ), + this, SLOT( slotAccountDestroyed( ) ) ); + it.current()->deleteLater(); + } + } +} + + + +void Protocol::slotMetaContactAboutToSave( MetaContact *metaContact ) +{ + QMap serializedData, sd; + QMap addressBookData, ad; + QMap::Iterator it; + + //kdDebug( 14010 ) << "Protocol::metaContactAboutToSave: protocol " << pluginId() << ": serializing " << metaContact->displayName() << endl; + + QPtrList contacts=metaContact->contacts(); + for (Contact *c=contacts.first() ; c ; c=contacts.next() ) + { + if( c->protocol()->pluginId() != pluginId() ) + continue; + + sd.clear(); + ad.clear(); + + // Preset the contactId and displayName, if the plugin doesn't want to save + // them, or use its own format, it can call clear() on the provided list + sd[ QString::fromLatin1( "contactId" ) ] = c->contactId(); + //TODO(nick) remove + sd[ QString::fromLatin1( "displayName" ) ] = c->property(Global::Properties::self()->nickName()).value().toString(); + if(c->account()) + sd[ QString::fromLatin1( "accountId" ) ] = c->account()->accountId(); + + // If there's an index field preset it too + QString index = c->protocol()->addressBookIndexField(); + if( !index.isEmpty() ) + ad[ index ] = c->contactId(); + + c->serializeProperties( sd ); + c->serialize( sd, ad ); + + // Merge the returned fields with what we already (may) have + for( it = sd.begin(); it != sd.end(); ++it ) + { + // The Unicode chars E000-F800 are non-printable and reserved for + // private use in applications. For more details, see also + // http://www.unicode.org/charts/PDF/UE000.pdf. + // Inside libkabc the use of QChar( 0xE000 ) has been standardized + // as separator for the string lists, use this also for the 'normal' + // serialized data. + if( serializedData.contains( it.key() ) ) + serializedData[ it.key() ] = serializedData[ it.key() ] + QChar( 0xE000 ) + it.data(); + else + serializedData[ it.key() ] = it.data(); + } + + for( it = ad.begin(); it != ad.end(); ++it ) + { + if( addressBookData.contains( it.key() ) ) + addressBookData[ it.key() ] = addressBookData[ it.key() ] + QChar( 0xE000 ) + it.data(); + else + addressBookData[ it.key() ] = it.data(); + } + } + + // Pass all returned fields to the contact list + //if( !serializedData.isEmpty() ) //even if we are empty, that mean there are no contact, so remove old value + metaContact->setPluginData( this, serializedData ); + + for( it = addressBookData.begin(); it != addressBookData.end(); ++it ) + { + //kdDebug( 14010 ) << "Protocol::metaContactAboutToSave: addressBookData: key: " << it.key() << ", data: " << it.data() << endl; + // FIXME: This is a terrible hack to check the key name for the phrase "messaging/" + // to indicate what app name to use, but for now it's by far the easiest + // way to get this working. + // Once all this is in CVS and the actual storage in libkabc is working + // we can devise a better API, but with the constantly changing + // requirements every time I learn more about kabc I'd better no touch + // the API yet - Martijn + if( it.key().startsWith( QString::fromLatin1( "messaging/" ) ) ) + { + metaContact->setAddressBookField( this, it.key(), QString::fromLatin1( "All" ), it.data() ); +// kdDebug(14010) << k_funcinfo << "metaContact->setAddressBookField( " << this << ", " << it.key() << ", \"All\", " << it.data() << " );" << endl; + } + else + metaContact->setAddressBookField( this, QString::fromLatin1( "kopete" ), it.key(), it.data() ); + } +} + +void Protocol::deserialize( MetaContact *metaContact, const QMap &data ) +{ + /*kdDebug( 14010 ) << "Protocol::deserialize: protocol " << + pluginId() << ": deserializing " << metaContact->displayName() << endl;*/ + + QMap serializedData; + QMap serializedDataIterators; + QMap::ConstIterator it; + for( it = data.begin(); it != data.end(); ++it ) + { + serializedData[ it.key() ] = QStringList::split( QChar( 0xE000 ), it.data(), true ); + serializedDataIterators[ it.key() ] = serializedData[ it.key() ].begin(); + } + + uint count = serializedData[QString::fromLatin1("contactId")].count(); + + // Prepare the independent entries to pass to the plugin's implementation + for( uint i = 0; i < count ; i++ ) + { + QMap sd; + QMap::Iterator serializedDataIt; + for( serializedDataIt = serializedDataIterators.begin(); serializedDataIt != serializedDataIterators.end(); ++serializedDataIt ) + { + sd[ serializedDataIt.key() ] = *( serializedDataIt.data() ); + ++( serializedDataIt.data() ); + } + + const QString& accountId=sd[ QString::fromLatin1( "accountId" ) ]; + // myself was allowed in the contactlist in old version of kopete. + // But if one keep it on the contactlist now, it may conflict witht he myself metacontact. + // So ignore it + if(accountId == sd[ QString::fromLatin1( "contactId" ) ] ) + { + kdDebug( 14010 ) << k_funcinfo << "Myself contact was on the contactlist.xml for account " << accountId << ". Ignore it" << endl; + continue; + } + + // FIXME: This code almost certainly breaks when having more than + // one contact in a meta contact. There are solutions, but + // they are all hacky and the API needs revision anyway (see + // FIXME a few lines below :), so I'm not going to add that + // for now. + // Note that even though it breaks, the current code will + // never notice, since none of the plugins use the address + // book data in the deserializer yet, only when serializing. + // - Martijn + QMap ad; + QStringList kabcFields = addressBookFields(); + for( QStringList::Iterator fieldIt = kabcFields.begin(); fieldIt != kabcFields.end(); ++fieldIt ) + { + // FIXME: This hack is even more ugly, and has the same reasons as the similar + // hack in the serialize code. + // Once this code is actually capable of talking to kabc this hack + // should be removed ASAP! - Martijn + if( ( *fieldIt ).startsWith( QString::fromLatin1( "messaging/" ) ) ) + ad[ *fieldIt ] = metaContact->addressBookField( this, *fieldIt, QString::fromLatin1( "All" ) ); + else + ad[ *fieldIt ] = metaContact->addressBookField( this, QString::fromLatin1( "kopete" ), *fieldIt ); + } + + // Check if we have an account id. If not we're deserializing a Kopete 0.6 contact + // (our our config is corrupted). Pick the first available account there. This + // might not be what you want for corrupted accounts, but it's correct for people + // who migrate from 0.6, as there's only one account in that case + if( accountId.isNull() ) + { + QDict accounts = AccountManager::self()->accounts( this ); + if ( accounts.count() > 0 ) + { + sd[ QString::fromLatin1( "accountId" ) ] = QDictIterator( accounts ).currentKey(); + } + else + { + kdWarning( 14010 ) << k_funcinfo << + "No account available and account not set in " \ + "contactlist.xml either!" << endl + << "Not deserializing this contact." << endl; + return; + } + } + + + Contact *c = deserializeContact( metaContact, sd, ad ); + if (c) // should never be null but I do not like crashes + c->deserializeProperties( sd ); + } +} + +Contact *Protocol::deserializeContact( + MetaContact */*metaContact */, + const QMap & /* serializedData */, + const QMap & /* addressBookData */ ) +{ + /* Default implementation does nothing */ + return 0; +} + + +} //END namespace Kopete + +#include "kopeteprotocol.moc" + diff --git a/kopete/libkopete/kopeteprotocol.desktop b/kopete/libkopete/kopeteprotocol.desktop new file mode 100644 index 00000000..3b3762a4 --- /dev/null +++ b/kopete/libkopete/kopeteprotocol.desktop @@ -0,0 +1,66 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Kopete/Protocol +X-KDE-Derived=Kopete/Plugin +Comment=Kopete Protocol Plugin +Comment[ar]=توصيلة بروتوكول KDE +Comment[be]=Пратакол Kopete +Comment[bg]=ПриÑтавка за протоколите на Kopete +Comment[bn]=কপেট পà§à¦°à§‹à¦Ÿà§‹à¦•à¦² পà§à¦²à¦¾à¦—িন +Comment[br]=Lugent komenad Kopete +Comment[bs]=Kopete dodatak za protokol +Comment[ca]=Connector de protocol per a Kopete +Comment[cs]=Modul protokolu aplikace Kopete +Comment[cy]=Ategyn Protocol Kopete +Comment[da]=Kopete-protokol-plugin +Comment[de]=Kopete Protokoll-Modul +Comment[el]=ΠÏόσθετο Ï€Ïωτοκόλλου Kopete +Comment[eo]=Kopete-Protokolkromaĵo +Comment[es]=Complemento de protocolo de Kopete +Comment[et]=Kopete protokolliplugin +Comment[eu]=Kopete protocolo plugin-a +Comment[fa]=وصلۀ قرارداد Kopete +Comment[fi]=Kopeten yhteyskäytäntöliitännäinen +Comment[fr]=Module de protocole pour Kopete +Comment[ga]=Breiseán Phrótacal Kopete +Comment[gl]=Plugin de protocolo para Kopete +Comment[he]=תוסף פרוטוקול של Kopete +Comment[hi]=के-ऑपà¥à¤Ÿà¥€ पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¥‰à¤² पà¥à¤²à¤—इन +Comment[hr]=Umetak za Kopete protokol +Comment[hu]=Kopete protokollkezelÅ‘ bÅ‘vítÅ‘modul +Comment[is]=Ãforrit fyrir Kopete samskiptamátann +Comment[it]=Plugin del protocollo di Kopete +Comment[ja]=Kopete プロトコルプラグイン +Comment[ka]=Kopete áƒáƒ¥áƒ›áƒ˜áƒ¡ მáƒáƒ“ული +Comment[kk]=Kopete протоколының плагин модулі +Comment[km]=កម្មវិធី​ជំនួយ​ពិធីការ Kopete +Comment[lt]=Kopete protokolo įskiepis +Comment[mk]=Приклучок за протокол во Kopete +Comment[nb]=Programtillegg for Kopete-protokoll +Comment[nds]=Kopete-Protokollmoduul +Comment[ne]=कोपेट पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¤² पà¥à¤²à¤—इन +Comment[nl]=Kopete protocol-plugin +Comment[nn]=Kopete-programtillegg for protokoll +Comment[pl]=Wtyczka protokoÅ‚u Kopete +Comment[pt]='Plugin' de Protocolo do Kopete +Comment[pt_BR]=Plugin do Protocolo do Kopete +Comment[ro]=Modul de protocol Kopete +Comment[ru]=Модуль протокола Kopete +Comment[se]=Kopete protokollalassemoduvla +Comment[sk]=Modul protokolu Kopete +Comment[sl]=Vstavek za protokol za Kopete +Comment[sr]=Kopete-ов прикључак за протокол +Comment[sr@Latn]=Kopete-ov prikljuÄak za protokol +Comment[sv]=Protokollinsticksprogram för Kopete +Comment[ta]=Kopete விதிமà¯à®±à¯ˆ செரà¯à®•à®²à¯ +Comment[tg]=Модули Қарордоди Kopete +Comment[tr]=Kopete Ä°letiÅŸim Kuralı Eklentisi +Comment[uk]=Втулок протоколу Ð´Ð»Ñ Kopete +Comment[wa]=Tchôke-divins di protocole po Kopete +Comment[zh_CN]=Kopete åè®®æ’件 +Comment[zh_HK]=Kopete 通訊å”定æ’件 +Comment[zh_TW]=Kopete å”å®šå¤–æŽ›ç¨‹å¼ + +[PropertyDef::X-Kopete-Messaging-Protocol] +Type=QString + diff --git a/kopete/libkopete/kopeteprotocol.h b/kopete/libkopete/kopeteprotocol.h new file mode 100644 index 00000000..805e00c2 --- /dev/null +++ b/kopete/libkopete/kopeteprotocol.h @@ -0,0 +1,269 @@ +/* + kopeteprotocol.h - Kopete Protocol + + Copyright (c) 2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2004 by Olivier Goffart + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPROTOCOL_H +#define KOPETEPROTOCOL_H + +#include "kopeteplugin.h" +#include "kopeteonlinestatus.h" + +class KopeteEditAccountWidget; +class AddContactPage; + +#include "kopete_export.h" + +namespace Kopete +{ + +class Contact; +class MetaContact; +class Account; + +/*namespace UI +{ + class EditAccountWidget; + class AddContactPage; +}*/ + + +/** + * @brief base class of every protocols. + * + * A protocol is just a particular case of Plugin + * + * Protocol is an abstract class, you need to reimplement createNewAccount, + * createAddContactPage, createEditAccountWidget + * + * + * @author Duncan Mac-Vicar Prett + * @author Martijn Klingens + * @author Olivier Goffart + */ +class KOPETE_EXPORT Protocol : public Plugin +{ + Q_OBJECT + +public: + + /** + * @todo Ideally, the destructor should be protected. but we need it public to allow QPtrList + */ + virtual ~Protocol(); + + /** + * @brief Create an empty Account + * + * This method is called during the loading of the config file. + * @param accountId - the account ID to create the account with. This is usually + * the login name of the account + * + * you don't need to register the account to the AccountManager in this function. + * But if you want to use this function don't forget to call @ref AccountManager::registerAccount + * + * @return The new @ref Account object created by this function + */ + virtual Account *createNewAccount( const QString &accountId ) = 0; + + /** + * @brief Create a new AddContactPage widget to be shown in the Add Contact Wizard. + * + * @return A new AddContactPage to be shown in the Add Contact Wizard + */ + virtual AddContactPage *createAddContactWidget( QWidget *parent, Account *account ) = 0; + + /** + * @brief Create a new KopeteEditAccountWidget + * + * @return A new KopeteEditAccountWidget to be shown in the account part of the configurations. + * + * @param account is the KopeteAccount to edit. If it's 0L, then we create a new account + * @param parent The parent of the 'to be returned' widget + */ + virtual KopeteEditAccountWidget * createEditAccountWidget( Account *account, QWidget *parent ) = 0; + + + /** + * @brief a bitmask of the capabilities of this protocol + * @sa @ref setCapabilities + */ + unsigned int capabilities() const ; + + + /** + * @brief Available capabilities + * + * @ref capabilities() returns an ORed list of these, which + * the edit widget interperts to determine what buttons to show + */ + enum Capabilities + { + BaseFgColor = 0x1, ///< Setting the bg color of the whole edit widget / message + BaseBgColor = 0x2, ///< Setting the fg color of the whole edit widget / message + RichFgColor = 0x4, ///< Setting the fg/bg color of text portions individually + RichBgColor = 0x8, ///< Setting the fg/bg color of text portions individually + + BaseFont = 0x10, ///< Setting the font of the whole edit widget / message + RichFont = 0x20, ///< Setting the font of text portions individually + + /// Setting the formatting of the whole edit widget / message + BaseUFormatting = 0x40, + BaseIFormatting = 0x80, + BaseBFormatting = 0x100, + + /// Setting the formatting of text portions individually + RichUFormatting = 0x200, + RichIFormatting = 0x400, + RichBFormatting = 0x800, + + Alignment = 0x1000, ///< Setting the alignment of text portions + + /// Setting the formatting of the whole edit widget / message + BaseFormatting = BaseIFormatting | BaseUFormatting | BaseBFormatting, + + /// Setting the formatting of text portions individually + RichFormatting = RichIFormatting | RichUFormatting | RichBFormatting, + + RichColor = RichBgColor | RichFgColor, + BaseColor = BaseBgColor | BaseFgColor, + + //Shortcut for All of the above - full HTML + FullRTF = RichFormatting | Alignment | RichFont | RichFgColor | RichBgColor , + + + CanSendOffline = 0x10000 ///< If it's possible to send offline messages + }; + + /** + * @brief Returns the status used for contacts when accounts of this protocol are offline + */ + Kopete::OnlineStatus accountOfflineStatus() const; + + +protected: + /** + * @brief Constructor for Protocol + * + * @param instance The protocol's instance, every plugin needs to have a KInstance of its own + * @param parent The protocol's parent object + * @param name The protocol's name + */ + Protocol( KInstance *instance, QObject *parent, const char *name ); + + /** + * @brief Sets the capabilities of this protcol. + * + * The subclass contructor is a good place for calling it. + * @sa @ref capabilities() + */ + void setCapabilities( unsigned int ); + +public: + + /** + * Reimplemented from Kopete::Plugin. + * + * This method disconnects all accounts and deletes them, after which it + * will emit readyForUnload. + * + * Note that this is an asynchronous operation that may take some time + * with active chats. It's no longer immediate as it used to be in + * Kopete 0.7.x and before. This also means that you can do a clean + * shutdown. + * @note The method is not private to allow subclasses to reimplement + * it even more, but if you need to do this please explain why + * on the list first. It might make more sense to add another + * virtual for protocols that's called instead, but for now I + * actually think protocols don't need their own implementation + * at all, so I left out the necessary hooks on purpose. + * - Martijn + */ + virtual void aboutToUnload(); + +private slots: + /** + * @internal + * The account changed online status. Used while unloading the protocol. + */ + void slotAccountOnlineStatusChanged( Kopete::Contact *self ); + + /** + * @internal + * The account is destroyed. When it's the last account we emit the + * readyForUnload signal. Used while unloading the protocol. + */ + void slotAccountDestroyed( ); + + +public: + + /** + * @brief Deserialize the plugin data for a meta contact. + * + * This method splits up the data into the independent Kopete::Contact objects + * and calls @ref deserializeContact() for each contact. + * + * Note that you can still reimplement this method if you prefer, but you are + * strongly recommended to use this version of the method instead, unless you + * want to do _VERY_ special things with the data... + * + * @todo we probably should think to another way to save the contacltist. + */ + virtual void deserialize( MetaContact *metaContact, const QMap &serializedData ); + + /** + * @brief Deserialize a single contact. + * + * This method is called by @ref deserialize() for each separate contact, + * so you don't need to add your own hooks for multiple contacts in a single + * meta contact yourself. @p serializedData and @p addressBookData will be + * the data the contact provided in Kopete::Contact::serialize. + * + * The default implementation does nothing. + * + * @return The contact created from the data + * @sa Contact::serialize + * + * @todo we probably should think to another way to save the contacltist. + */ + virtual Contact *deserializeContact( MetaContact *metaContact, + const QMap &serializedData, + const QMap &addressBookData ); + + + +public slots: + /** + * A meta contact is about to save. + * Call serialize() for all contained contacts for this protocol. + * @internal + * it's public because for example, Contact::setMetaContact uses it. + * @todo we probably should think to another way to save the contacltist. + */ + void slotMetaContactAboutToSave( Kopete::MetaContact *metaContact ); + + +private: + class Private; + Private *d; +}; + +} //END namespace kopete + +#endif + diff --git a/kopete/libkopete/kopetesimplemessagehandler.cpp b/kopete/libkopete/kopetesimplemessagehandler.cpp new file mode 100644 index 00000000..3e44520c --- /dev/null +++ b/kopete/libkopete/kopetesimplemessagehandler.cpp @@ -0,0 +1,101 @@ +/* + kopetemessagefilter.cpp - Kopete Message Filtering + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetesimplemessagehandler.h" +#include "kopetemessageevent.h" + +#include + +#include + +namespace Kopete +{ + +//BEGIN SimpleMessageHandlerFactory + +class SimpleMessageHandlerFactory::Private +{ +public: + Message::MessageDirection direction; + int position; + QGuardedPtr target; + const char *slot; +}; + +SimpleMessageHandlerFactory::SimpleMessageHandlerFactory( Message::MessageDirection direction, + int position, QObject *target, const char *slot ) + : d( new Private ) +{ + d->direction = direction; + d->position = position; + d->target = target; + d->slot = slot; +} + +SimpleMessageHandlerFactory::~SimpleMessageHandlerFactory() +{ + delete d; +} + +MessageHandler *SimpleMessageHandlerFactory::create( ChatSession */*manager*/, Message::MessageDirection direction ) +{ + if ( direction != d->direction ) + return 0; + MessageHandler *handler = new SimpleMessageHandler; + QObject::connect( handler, SIGNAL( handle( Kopete::Message & ) ), d->target, d->slot ); + return handler; +} + +int SimpleMessageHandlerFactory::filterPosition( ChatSession */*manager*/, Message::MessageDirection direction ) +{ + if ( direction != d->direction ) + return StageDoNotCreate; + return d->position; +} + +//END SimpleMessageHandlerFactory + +//BEGIN SimpleMessageHandler + +class SimpleMessageHandler::Private +{ +}; + +SimpleMessageHandler::SimpleMessageHandler() + : d(0) +{ +} + +SimpleMessageHandler::~SimpleMessageHandler() +{ + delete d; +} + +void SimpleMessageHandler::handleMessage( MessageEvent *event ) +{ + Message message = event->message(); + emit handle( message ); + event->setMessage( message ); + MessageHandler::handleMessage( event ); +} + +//END SimpleMessageHandler + +} + +#include "kopetesimplemessagehandler.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetesimplemessagehandler.h b/kopete/libkopete/kopetesimplemessagehandler.h new file mode 100644 index 00000000..af6de4ab --- /dev/null +++ b/kopete/libkopete/kopetesimplemessagehandler.h @@ -0,0 +1,90 @@ +/* + kopetesimplemessagehandler.h - Kopete Message Filtering - simple interface + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETESIMPLEMESSAGEHANDLER_H +#define KOPETESIMPLEMESSAGEHANDLER_H + +#include "kopete_export.h" +#include "kopetemessagehandler.h" + +namespace Kopete +{ + +/** + * @brief A MessageHandlerFactory that creates synchronous MessageHandlers that just call a slot + * + * A concrete MessageHandlerFactory. This class is intended to make writing MessageHandlers simpler; + * all that is required is to implement a message processing function and place an instance of this + * class in your Plugin-derived class. + * + * Whenever a message passes through a handler created by this factory, the slot passed to the + * constructor will be called. The slot should take a single argument of type (non-@p const) + * Message &. + */ +class KOPETE_EXPORT SimpleMessageHandlerFactory : public MessageHandlerFactory +{ +public: + /** + * @param direction The direction this factory should create message handlers for + * @param position Where in the chain the handler should be installed + * @param target The object to call back to when handling a message + * @param slot The slot on @p target to call when handling a message + * @see Kopete::MessageHandlerFactory::filterPosition + */ + SimpleMessageHandlerFactory( Message::MessageDirection direction, int position, + QObject *target, const char *slot ); + ~SimpleMessageHandlerFactory(); + + /** + * Creates and returns a SimpleMessageHandler object. + */ + MessageHandler *create( ChatSession *manager, Message::MessageDirection direction ); + /** + * Returns the filter position passed to the constructor if @p direction matches the + * direction passed to the constructor, otherwise returns @c StageDoNotCreate. + */ + int filterPosition( ChatSession *manager, Message::MessageDirection direction ); + +private: + class Private; + Private *d; +}; + +/** + * @internal This class is used to implement SimpleMessageHandlerFactory. + */ +class SimpleMessageHandler : public MessageHandler +{ + Q_OBJECT +public: + SimpleMessageHandler(); + ~SimpleMessageHandler(); + + void handleMessage( MessageEvent *event ); + +signals: + void handle( Kopete::Message &message ); + +private: + class Private; + Private *d; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetetask.cpp b/kopete/libkopete/kopetetask.cpp new file mode 100644 index 00000000..b7484116 --- /dev/null +++ b/kopete/libkopete/kopetetask.cpp @@ -0,0 +1,108 @@ +/* + kopetetask.cpp - Kopete Task + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetetask.h" + +#include + +#include + +namespace Kopete +{ + +class Task::Private +{ +public: + Private() + : result( ResultFailed ) + { + errorMessage = i18n( "The operation has not finished yet" ); + } + + Task::Result result; + QString errorMessage; + QPtrList subtasks; +}; + +Task::Task() + : d( new Private ) +{ +} + +Task::~Task() +{ + delete d; +} + +bool Task::succeeded() const +{ + return d->result == ResultSucceeded; +} + +const QString &Task::errorString() const +{ + return d->errorMessage; +} + +void Task::abort( int flags ) +{ + int childFlags = flags & ~AbortEmitResult; + for ( Task *task = d->subtasks.first(); task; task = d->subtasks.next() ) + task->abort( childFlags ); + + if ( flags & AbortEmitResult ) + emitResult( ResultFailed, i18n( "Aborted" ) ); + else + delete this; +} + +void Task::addSubtask( Task *task ) +{ + d->subtasks.append( task ); + connect( task, SIGNAL( result( Kopete::Task* ) ), + this, SLOT( slotResult( Kopete::Task* ) ) ); + connect( task, SIGNAL( statusMessage( Kopete::Task*, const QString & ) ), + this, SIGNAL( statusMessage( Kopete::Task*, const QString & ) ) ); +} + +void Task::removeSubtask( Task *task, RemoveSubtaskIfLast actionIfLast ) +{ + disconnect( task, SIGNAL( result( Kopete::Task* ) ), + this, SLOT( slotResult( Kopete::Task* ) ) ); + disconnect( task, SIGNAL( statusMessage( Kopete::Task*, const QString & ) ), + this, SIGNAL( statusMessage( Kopete::Task*, const QString & ) ) ); + d->subtasks.remove( task ); + if ( d->subtasks.isEmpty() && actionIfLast == IfLastEmitResult ) + emitResult( task->succeeded() ? ResultSucceeded : ResultFailed, task->errorString() ); +} + +void Task::emitResult( Result res, const QString &errorMessage ) +{ + d->result = res; + d->errorMessage = errorMessage; + emit result( this ); + delete this; +} + +void Task::slotResult( Kopete::Task *task ) +{ + removeSubtask( task ); +} + +} + +#include "kopetetask.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetetask.h b/kopete/libkopete/kopetetask.h new file mode 100644 index 00000000..115e1ebe --- /dev/null +++ b/kopete/libkopete/kopetetask.h @@ -0,0 +1,162 @@ +/* + kopetetask.h - Kopete Task + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETETASK_H +#define KOPETETASK_H + +#include +#include + +namespace Kopete +{ + +/** + * The base class for all tasks. + * For most tasks created in Kopete, the code looks like + * + * \code + * Kopete::Task *task = someobject->someoperation( some parameters ); + * connect( task, SIGNAL( result( Kopete::Task * ) ), + * this, SLOT( slotResult( Kopete::Task * ) ) ); + * \endcode + * (other connects, specific to the job) + * + * And slotResult is usually at least: + * + * \code + * if ( !task->succeeded() ) + * Kopete::UI::Global::showTaskError( task ); + * \endcode + * + * Much of the ideas (and some of the documentation and function names) for this + * class come from KIO::Job. + * @author Richard Smith + */ +class Task : public QObject +{ + Q_OBJECT + +protected: + Task(); +public: + ~Task(); + + /** + * Returns whether the task completed successfully. + * Only call this method from the slot connected to result(). + * @return if the task succeeded, returns true, otherwise returns false. + */ + bool succeeded() const; + /** + * Converts an error code and a non-i18n error message into an + * error message in the current language. The low level (non-i18n) + * error message (usually a url) is put into the translated error + * message using %%1. + * + * Use this to display the error yourself, but for a dialog box + * use Kopete::UI::Global::showTaskError. Do not call it if succeeded() + * returns true. + * @return the error message and if there is no error, a message + * telling the user that the app is broken, so check with + * succeeded() whether there is an error. + */ + const QString &errorString() const; + + /** Flags for the abort() function */ + enum AbortFlags { AbortNormal = 0, AbortEmitResult = 1 }; +public slots: + /** + * Abort this task. + * This aborts all subtasks and deletes the task. + * + * @param flags a combination of flags from AbortFlags. If AbortEmitResult is + * set, Job will emit the result signal. AbortEmitResult is removed + * from the flags passed to the abort function of subtasks. + */ + virtual void abort( int flags = AbortNormal ); + +signals: + /** + * Emitted when the task is finished, in any case (completed, canceled, + * failed...). Use error() to find the result. + * @param task the task that emitted this signal + */ + void result( Kopete::Task *task ); + /** + * Emitted to display status information about this task. + * Examples of messages are: + * "Removing ICQ contact Joe from server-side list", + * "Loading account plugin", etc. + * @param task the task that emitted this signal + * @param message the info message + */ + void statusMessage( Kopete::Task *task, const QString &message ); + +protected: + /** + * Add a task that has to be completed before a result is emitted. This + * obviously should not be called after the finish signal is emitted by + * the subtask. + * + * @param task the subtask to add + */ + virtual void addSubtask( Task *task ); + + enum RemoveSubtaskIfLast { IfLastDoNothing, IfLastEmitResult }; + /** + * Mark a sub job as being done. If it's the last to + * wait on the job will emit a result - jobs with + * two steps might want to override slotResult + * in order to avoid calling this method. + * + * @param task the subjob to add + * @param actionIfLast the action to take if this is the last subtask. + * If set to IfLastEmitResult, the error information from @p task + * will be copied to this object, and emitResult() will be called. + */ + virtual void removeSubtask( Task *task, RemoveSubtaskIfLast actionIfLast = IfLastEmitResult ); + + enum Result { ResultFailed = 0, ResultSucceeded = 1 }; + /** + * Utility function to emit the result signal, and suicide this job. + * Sets the stored result and error message to @p result and @p errorMessage. + * You should call this instead of emitting the result() signal yourself. + */ + void emitResult( Result result = ResultSucceeded, const QString &errorMessage = QString::null ); + +protected slots: + /** + * Called whenever a subtask finishes. + * The default implementation checks for errors and propagates + * them to this task, then calls removeSubtask(). + * Override if you want to provide a different @p actionIfLast to + * removeSubtask, or want to perform some other processing in response + * to a subtask finishing + * @param task the subtask that finished + * @see result() + */ + virtual void slotResult( Kopete::Task *task ); + +private: + class Private; + Private *d; +}; + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopetetransfermanager.cpp b/kopete/libkopete/kopetetransfermanager.cpp new file mode 100644 index 00000000..1131cd90 --- /dev/null +++ b/kopete/libkopete/kopetetransfermanager.cpp @@ -0,0 +1,271 @@ +/* + kopetetransfermanager.cpp + + Copyright (c) 2002-2003 by Nick Betcher + Copyright (c) 2002-2003 by Richard Smith + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include +#include + +#include "kopetemetacontact.h" +#include "kopetecontact.h" +#include "kopeteuiglobal.h" + +#include "kopetetransfermanager.h" +#include "kopetefileconfirmdialog.h" + +/*************************** + * Kopete::FileTransferInfo * + ***************************/ + +Kopete::FileTransferInfo::FileTransferInfo( Kopete::Contact *contact, const QString& file, const unsigned long size, const QString &recipient, KopeteTransferDirection di, const unsigned int id, QString internalId) +{ + mContact = contact; + mFile = file; + mId = id; + mSize = size; + mRecipient = recipient; + m_intId= internalId; + mDirection= di; +} + +/*************************** + * Kopete::Transfer * + ***************************/ + + +Kopete::Transfer::Transfer( const Kopete::FileTransferInfo &kfti, const QString &localFile, bool showProgressInfo) + : KIO::Job(showProgressInfo), mInfo(kfti) +{ + KURL targ; targ.setPath( localFile ); + init( targ, showProgressInfo ); +} + +Kopete::Transfer::Transfer( const Kopete::FileTransferInfo &kfti, const Kopete::Contact *contact, bool showProgressInfo) + : KIO::Job(showProgressInfo), mInfo(kfti) +{ + // TODO: use mInfo.url().fileName() after move to protocol-aware filetransfers + KURL targ; targ.setPath( mInfo.file() ); + init( displayURL( contact, targ.fileName() ), showProgressInfo ); +} + +void Kopete::Transfer::init( const KURL &target, bool showProgressInfo ) +{ + mTarget = target; + + if( showProgressInfo ) + Observer::self()->slotCopying( this, sourceURL(), destinationURL() ); + + connect( this, SIGNAL( result( KIO::Job* ) ), SLOT( slotResultEmitted() ) ); + + setAutoErrorHandlingEnabled( true, 0 ); +} + +Kopete::Transfer::~Transfer() +{ +} + +KURL Kopete::Transfer::displayURL( const Kopete::Contact *contact, const QString &file ) +{ + KURL url; + url.setProtocol( QString::fromLatin1("kopete") ); + + QString host; + if( !contact ) + host = QString::fromLatin1("unknown origin"); + else if( contact->metaContact() ) + host = contact->metaContact()->displayName(); + else + host = contact->contactId(); + url.setHost(host); + + // url.setPath( contact->protocol()->displayName() ); + + url.setFileName( file ); + return url; +} + +// TODO: add possibility of network file transfers; +// call mInfo->url() not file() +KURL Kopete::Transfer::sourceURL() +{ + if( mInfo.direction() == Kopete::FileTransferInfo::Incoming ) + return displayURL( mInfo.contact(), mInfo.file() ); + else + { + KURL url; url.setPath( mInfo.file() ); + return url; + } +} + +KURL Kopete::Transfer::destinationURL() +{ + return mTarget; +} + +void Kopete::Transfer::slotProcessed(unsigned int bytes) +{ + emitPercent( bytes, mInfo.size() ); +} + +void Kopete::Transfer::slotComplete() +{ + emitResult(); +} + +void Kopete::Transfer::slotError( int error, const QString &errorText ) +{ + m_error = error; + m_errorText = errorText; + + emitResult(); +} + +void Kopete::Transfer::slotResultEmitted() +{ + if( error() == KIO::ERR_USER_CANCELED ) + emit transferCanceled(); +} + +/*************************** + * Kopete::TransferManager * + ***************************/ + +static KStaticDeleter deleteManager; +Kopete::TransferManager *Kopete::TransferManager::s_transferManager = 0; + +Kopete::TransferManager* Kopete::TransferManager::transferManager() +{ + if(!s_transferManager) + deleteManager.setObject(s_transferManager, new Kopete::TransferManager(0)); + + return s_transferManager; +} + +Kopete::TransferManager::TransferManager( QObject *parent ) : QObject( parent ) +{ + nextID = 0; +} + +Kopete::Transfer* Kopete::TransferManager::addTransfer( Kopete::Contact *contact, const QString& file, const unsigned long size, const QString &recipient , Kopete::FileTransferInfo::KopeteTransferDirection di) +{ +// if (nextID != 0) + nextID++; + Kopete::FileTransferInfo info(contact, file, size, recipient,di, nextID); + Kopete::Transfer *trans = new Kopete::Transfer(info, contact); + connect(trans, SIGNAL(result(KIO::Job *)), this, SLOT(slotComplete(KIO::Job *))); + mTransfersMap.insert(nextID, trans); + return trans; +} + +void Kopete::TransferManager::slotAccepted(const Kopete::FileTransferInfo& info, const QString& filename) +{ + Kopete::Transfer *trans = new Kopete::Transfer(info, filename); + connect(trans, SIGNAL(result(KIO::Job *)), this, SLOT(slotComplete(KIO::Job *))); + mTransfersMap.insert(info.transferId(), trans); + emit accepted(trans,filename); +} + +int Kopete::TransferManager::askIncomingTransfer( Kopete::Contact *contact, const QString& file, const unsigned long size, const QString& description, QString internalId) +{ +// if (nextID != 0) + nextID++; + + QString dn= contact ? (contact->metaContact() ? contact->metaContact()->displayName() : contact->contactId()) : i18n(""); + + Kopete::FileTransferInfo info(contact, file, size, dn, Kopete::FileTransferInfo::Incoming , nextID , internalId); + + //FIXME!!! this will not be deleted if it's still open when kopete exits + KopeteFileConfirmDialog *diag= new KopeteFileConfirmDialog(info, description , 0 ) ; + + connect( diag, SIGNAL( accepted(const Kopete::FileTransferInfo&, const QString&)) , this, SLOT( slotAccepted(const Kopete::FileTransferInfo&, const QString&) ) ); + connect( diag, SIGNAL( refused(const Kopete::FileTransferInfo&)) , this, SIGNAL( refused(const Kopete::FileTransferInfo&) ) ); + diag->show(); + return nextID; +} + +void Kopete::TransferManager::removeTransfer( unsigned int id ) +{ + mTransfersMap.remove(id); + //we don't need to delete the job, the job get deleted itself +} + +void Kopete::TransferManager::slotComplete(KIO::Job *job) +{ + Kopete::Transfer *transfer=dynamic_cast(job); + if(!transfer) + return; + + emit done(transfer); + + for( QMap::Iterator it = mTransfersMap.begin(); + it != mTransfersMap.end(); ++it ) + { + if( it.data() == transfer ) + { + removeTransfer(it.key()); + break; + } + } +} + +void Kopete::TransferManager::sendFile( const KURL &file, const QString &fname, unsigned long sz, + bool mustBeLocal, QObject *sendTo, const char *slot ) +{ + KURL url(file); + QString filename; + unsigned int size = 0; + + //If the file location is null, then get it from a file open dialog + if( !url.isValid() ) + url = KFileDialog::getOpenURL( QString::null, QString::fromLatin1("*"), 0l, i18n( "Kopete File Transfer" )); + else + { + filename = fname; + size = sz; + } + + if( filename.isEmpty() ) + filename = url.fileName(); + + if( size == 0 ) + { + KFileItem finfo(KFileItem::Unknown, KFileItem::Unknown, url); + size = (unsigned long)finfo.size(); + } + + if( !url.isEmpty() ) + { + if( mustBeLocal && !url.isLocalFile() ) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "Sorry, sending files which are not stored locally is not yet supported by this protocol.\n" + "Please copy this file to your computer and try again." ) ); + } + else + { + connect( this, SIGNAL(sendFile(const KURL&, const QString&, unsigned int)), sendTo, slot ); + emit sendFile( url, filename, size ); + disconnect( this, SIGNAL(sendFile(const KURL&, const QString&, unsigned int)), sendTo, slot ); + } + } +} + +#include "kopetetransfermanager.moc" + diff --git a/kopete/libkopete/kopetetransfermanager.h b/kopete/libkopete/kopetetransfermanager.h new file mode 100644 index 00000000..f4e7416f --- /dev/null +++ b/kopete/libkopete/kopetetransfermanager.h @@ -0,0 +1,212 @@ +/* + kopetetransfermanager.h + + Copyright (c) 2002-2003 by Nick Betcher + Copyright (c) 2002-2003 by Richard Smith + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEFILETRANSFER_H +#define KOPETEFILETRANSFER_H + +#include +#include +#include +#include "kopete_export.h" + +#include + +namespace Kopete +{ + +class Transfer; +class Contact; + +/** + * @author Nick Betcher. + */ +class KOPETE_EXPORT FileTransferInfo +{ +public: + enum KopeteTransferDirection { Incoming, Outgoing }; + + FileTransferInfo( Contact *, const QString&, const unsigned long size, const QString &, KopeteTransferDirection di, const unsigned int id, QString internalId=QString::null); + ~FileTransferInfo() {} + unsigned int transferId() const { return mId; } + const Contact* contact() const { return mContact; } + QString file() const { return mFile; } + QString recipient() const { return mRecipient; } + unsigned long size() const { return mSize; } + QString internalId() const { return m_intId; } + KopeteTransferDirection direction() const { return mDirection; } + +private: + unsigned long mSize; + QString mRecipient; + unsigned int mId; + Contact *mContact; + QString mFile; + QString m_intId; + KopeteTransferDirection mDirection; +}; + +/** + * Creates and manages kopete file transfers + */ +class KOPETE_EXPORT TransferManager : public QObject +{ + Q_OBJECT + +public: + /** + * Retrieve the transfer manager instance + */ + static TransferManager* transferManager(); + virtual ~TransferManager() {}; + + /** + * @brief Adds a file transfer to the Kopete::TransferManager + */ + Transfer *addTransfer( Contact *contact, const QString& file, const unsigned long size, const QString &recipient , FileTransferInfo::KopeteTransferDirection di); + int askIncomingTransfer( Contact *contact, const QString& file, const unsigned long size, const QString& description=QString::null, QString internalId=QString::null); + void removeTransfer( unsigned int id ); + + /** + * @brief Ask the user which file to send when they click Send File. + * + * Possibly ask the user which file to send when they click Send File. Sends a signal indicating KURL to + * send when the local user accepts the transfer. + * @param file If valid, the user will not be prompted for a URL, and this one will be used instead. + * If it refers to a remote file and mustBeLocal is true, the file will be transferred to the local + * filesystem. + * @param localFile file name to display if file is a valid URL + * @param fileSize file size to send if file is a valid URL + * @param mustBeLocal If the protocol can only send files on the local filesystem, this flag + * allows you to ensure the filename will be local. + * @param sendTo The object to send the signal to + * @param slot The slot to send the signal to. Signature: sendFile(const KURL &file) + */ + void sendFile( const KURL &file, const QString &localFile, unsigned long fileSize, + bool mustBeLocal, QObject *sendTo, const char *slot ); + +signals: + /** @brief Signals the transfer is done. */ + void done( Kopete::Transfer* ); + + /** @brief Signals the transfer has been canceled. */ + void canceled( Kopete::Transfer* ); + + /** @brief Signals the transfer has been accepted */ + void accepted(Kopete::Transfer*, const QString &fileName); + + /** @brief Signals the transfer has been rejected */ + void refused(const Kopete::FileTransferInfo& ); + + /** @brief Send a file */ + void sendFile(const KURL &file, const QString &localFile, unsigned int fileSize); + +private slots: + void slotAccepted(const Kopete::FileTransferInfo&, const QString&); + void slotComplete(KIO::Job*); + +private: + TransferManager( QObject *parent ); + static TransferManager *s_transferManager; + + int nextID; + QMap mTransfersMap; +}; + +/** + * A KIO job for a kopete file transfer. + * @author Richard Smith + */ +class KOPETE_EXPORT Transfer : public KIO::Job +{ + Q_OBJECT + +public: + /** + * Constructor + */ + Transfer( const FileTransferInfo &, const QString &localFile, bool showProgressInfo = true); + + /** + * Constructor + */ + Transfer( const FileTransferInfo &, const Contact *toUser, bool showProgressInfo = true); + + /** + * Destructor + */ + ~Transfer(); + + /** @brief Get the info for this file transfer */ + const FileTransferInfo &info() const { return mInfo; } + + /** + * Retrieve a URL indicating where the file is being copied from. + * For display purposes only! There's no guarantee that this URL + * refers to a real file being transferred. + */ + KURL sourceURL(); + + /** + * Retrieve a URL indicating where the file is being copied to. + * See @ref sourceURL + */ + KURL destinationURL(); + +public slots: + + /** + * @brief Set the file size processed so far + */ + void slotProcessed(unsigned int); + + /** + * @brief Indicate that the transfer is complete + */ + void slotComplete(); + + /** + * @brief Inform the job that an error has occurred while transferring the file. + * + * @param error A member of the KIO::Error enumeration indicating what error occurred. + * @param errorText A string to aid understanding of the error, often the offending URL. + */ + void slotError( int error, const QString &errorText ); + +signals: + /** + * @deprecated Use result() and check error() for ERR_USER_CANCELED + */ + void transferCanceled(); + +private: + void init( const KURL &, bool ); + + static KURL displayURL( const Contact *contact, const QString &file ); + + FileTransferInfo mInfo; + KURL mTarget; + int mPercent; + +private slots: + void slotResultEmitted(); +}; + +} + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/kopeteui.desktop b/kopete/libkopete/kopeteui.desktop new file mode 100644 index 00000000..6818fc35 --- /dev/null +++ b/kopete/libkopete/kopeteui.desktop @@ -0,0 +1,60 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Kopete/UI +X-KDE-Derived=Kopete/Plugin +Comment=A Kopete UI Plugin +Comment[ar]=توصيلة واجهة استخدام Kopete +Comment[be]=Модуль інтÑрфÑйÑу Kopete +Comment[bg]=ПриÑтавка за Ð³Ñ€Ð°Ñ„Ð¸Ñ‡Ð½Ð¸Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð½Ð° Kopete +Comment[bn]=à¦à¦•à¦Ÿà¦¿ কপেট ইউ-আই পà§à¦²à¦¾à¦—িন +Comment[bs]=Kopete dodatak za UI +Comment[ca]=Un connector de IU per a Kopete +Comment[cs]=Modul rozhraní aplikace Kopete +Comment[cy]=Ategyn UI Kopete +Comment[da]=En Kopete UI-plugin +Comment[de]=Ein Kopete Benutzeroberflächenmodul +Comment[el]=Ένα Ï€Ïόσθετο γÏÎ±Ï†Î¹ÎºÎ¿Ï Ï€ÎµÏιβάλλοντος του Kopete +Comment[es]=Complemento de UI de Kopete +Comment[et]=Kopete kasutajaliidese plugin +Comment[eu]=Kopete UI plugin bat +Comment[fa]=یک وصلۀ شناسۀ کاربر Kopete +Comment[fi]=Kopeten käyttöliittymäliitännäinen +Comment[fr]=Un module d'interface utilisateur pour Kopete +Comment[ga]=Breiseán Chomhéadan Úsáideora Kopete +Comment[gl]=Un protocolo de interfaz gráfica para Kopete +Comment[he]=תוסף ממשק משתמש של Kopete +Comment[hi]=à¤à¤• के-ऑपà¥à¤Ÿà¥€ यूआई पà¥à¤²à¤—इन +Comment[hr]=Umetak za Kopeteovo korisniÄko suÄelje +Comment[hu]=Kopete bÅ‘vítÅ‘modul a grafikus felülethez +Comment[is]=Viðmótsíforrit fyrir Kopete +Comment[it]=Plugin per UI di Kopete +Comment[ja]=Kopete UI プラグイン +Comment[ka]=Kopete UI მáƒáƒ“ული +Comment[kk]=Kopete интерфейÑінің плагин модулі +Comment[km]=កម្មវិធី​ជំនួយ​ចំណុច​ប្រទាក់​ក្រាហ្វិក​របស់ Kopete +Comment[lt]=Kopete sÄ…sajos įskiepis +Comment[mk]=UI-приклучок за Kopete +Comment[nb]=Et programtillegg for Kopete brukergrensesnitt +Comment[nds]=Kopete-Böversietmoduul +Comment[ne]=कोपेट यू आई पà¥à¤²à¤—इन +Comment[nl]=Een Kopete gebruikersinterface-plugin +Comment[nn]=Kopete-programtillegg for brukargrensesnitt +Comment[pl]=Wtyczka interfejsu użytkownika Kopete +Comment[pt]=Um 'Plugin' de Interface do Kopete +Comment[pt_BR]=Um plug-in de UI do Kopete +Comment[ro]=Un modul interfaţă grafică Kopete +Comment[ru]=Модуль интерфейÑа Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Kopete +Comment[se]=Kopete geavaheaddjelaktalassemoduvla +Comment[sk]=Modul rozhrania Kopete +Comment[sl]=Vstavek za uporabniÅ¡ki vmesnik za Kopete +Comment[sr]=Прикључак за Kopete-ов кориÑнички Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÑ˜Ñ +Comment[sr@Latn]=PrikljuÄak za Kopete-ov korisniÄki interfejs +Comment[sv]=Gränssnittsinsticksprogram för Kopete +Comment[ta]=ஒர௠Kopete UI செரà¯à®•à®²à¯ +Comment[tg]=Модули ИнтерфейÑи Корвандии Kopete +Comment[tr]=Bir Kopete UI Eklentisi +Comment[uk]=Втулок інтерфейÑу Ð´Ð»Ñ Kopete +Comment[wa]=On tchôke-divins d' eterface grafike po Kopete +Comment[zh_CN]=Kopete ç•Œé¢æ’件 +Comment[zh_HK]=Kopete 用戶界é¢æ’件 +Comment[zh_TW]=Kopete 使用者介é¢å¤–æŽ›ç¨‹å¼ diff --git a/kopete/libkopete/kopeteuiglobal.cpp b/kopete/libkopete/kopeteuiglobal.cpp new file mode 100644 index 00000000..06c0dfa3 --- /dev/null +++ b/kopete/libkopete/kopeteuiglobal.cpp @@ -0,0 +1,60 @@ +/* + kopeteuiglobal.cpp - Kopete UI Globals + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteuiglobal.h" + +#include + + +namespace Kopete +{ + + +namespace +{ + QGuardedPtr g_mainWidget; + int g_sysTrayWId; +} + +void UI::Global::setMainWidget( QWidget *widget ) +{ + g_mainWidget = widget; +} + +QWidget *UI::Global::mainWidget() +{ + return g_mainWidget; +} + +void UI::Global::setSysTrayWId( int newWinId ) +{ + g_sysTrayWId = newWinId; +} + +int UI::Global::sysTrayWId() +{ + if ( g_sysTrayWId == 0 ) + return g_mainWidget->winId(); + else + return g_sysTrayWId; +} + + +} + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopeteuiglobal.h b/kopete/libkopete/kopeteuiglobal.h new file mode 100644 index 00000000..4a79eb87 --- /dev/null +++ b/kopete/libkopete/kopeteuiglobal.h @@ -0,0 +1,72 @@ +/* + kopeteuiglobal.h - Kopete UI Globals + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEUIGLOBAL_H +#define KOPETEUIGLOBAL_H + +#include + +#include "kopete_export.h" + +namespace Kopete +{ + +namespace UI +{ + +/** + * This namespace contains the Kopete user interface's global settings + */ +namespace Global +{ + /** + * Set the main widget to widget + */ + KOPETE_EXPORT void setMainWidget( QWidget *widget ); + /** + * Returns the main widget - this is the widget that message boxes + * and KNotify stuff should use as a parent. + */ + KOPETE_EXPORT QWidget *mainWidget(); + + /** + * \brief Returns the WId of the system tray. + * + * Allows developers easy access to the WId of the system tray so + * that it can be used for passive popups in the protocols + * \return the WId of the system tray. Returns the WId of the main + * widget if there's no system tray. + */ + KOPETE_EXPORT int sysTrayWId(); + + /** + * \brief Set the WId of the system tray. + * + * Called by the KopeteSystemTray constructor and destructor to + * set the WId for the system tray appropriately + */ + KOPETE_EXPORT void setSysTrayWId( int newWinId ); +} //Global::UI + +} //UI + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopeteutils.cpp b/kopete/libkopete/kopeteutils.cpp new file mode 100644 index 00000000..d7d8eb0f --- /dev/null +++ b/kopete/libkopete/kopeteutils.cpp @@ -0,0 +1,124 @@ +/* + Kopete Utils. + Copyright (c) 2005 Duncan Mac-Vicar Prett + + isHostReachable function code derived from KDE's HTTP kioslave + Copyright (c) 2005 Waldo Bastian + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "kopeteaccount.h" +#include "knotification.h" +#include "kopeteutils_private.h" +#include "kopeteutils.h" +#include "kopeteuiglobal.h" + +static const QString notifyConnectionLost_DefaultMessage = i18n("You have been disconnected."); +static const QString notifyConnectionLost_DefaultCaption = i18n("Connection Lost."); +static const QString notifyConnectionLost_DefaultExplanation = i18n("Kopete lost the channel used to talk to the instant messaging system.\nThis can be because either your internet access went down, the service is experiencing problems, or the service disconnected you because you tried to connect with the same account from another location. Try connecting again later."); + +static const QString notifyCannotConnect_DefaultMessage = i18n("Can't connect with the instant messaging server or peers."); +static const QString notifyCannotConnect_DefaultCaption = i18n("Can't connect."); +static const QString notifyCannotConnect_DefaultExplanation = i18n("This means Kopete can't reach the instant messaging server or peers.\nThis can be because either your internet access is down or the server is experiencing problems. Try connecting again later."); + +namespace Kopete +{ +namespace Utils +{ + +void notify( QPixmap pic, const QString &eventid, const QString &caption, const QString &message, const QString explanation, const QString debugInfo) +{ + QString action; + if ( !explanation.isEmpty() ) + action = i18n( "More Information..." ); + kdDebug( 14010 ) << k_funcinfo << endl; + KNotification *n = KNotification::event( eventid, message, pic , 0L , action ); + ErrorNotificationInfo info; + info.explanation = explanation; + info.debugInfo = debugInfo; + + NotifyHelper::self()->registerNotification(n, info); + QObject::connect( n, SIGNAL(activated(unsigned int )) , NotifyHelper::self() , SLOT( slotEventActivated(unsigned int) ) ); + QObject::connect( n, SIGNAL(closed()) , NotifyHelper::self() , SLOT( slotEventClosed() ) ); +} + +void notifyConnectionLost( const Account *account, const QString &caption, const QString &message, const QString &explanation, const QString &debugInfo ) +{ + if (!account) + return; + + notify( account->accountIcon(32), QString::fromLatin1("connection_lost"), caption.isEmpty() ? notifyConnectionLost_DefaultCaption : caption, message.isEmpty() ? notifyConnectionLost_DefaultMessage : message, explanation.isEmpty() ? notifyConnectionLost_DefaultExplanation : explanation, debugInfo); +} + +bool isHostReachable(const QString &host) +{ + const int NetWorkStatusUnknown = 1; + const int NetWorkStatusOnline = 8; + QCString replyType; + QByteArray params; + QByteArray reply; + + QDataStream stream(params, IO_WriteOnly); + stream << host; + + if ( KApplication::kApplication()->dcopClient()->call( "kded", "networkstatus", "status(QString)", params, replyType, reply ) && (replyType == "int") ) + { + int result; + QDataStream stream2( reply, IO_ReadOnly ); + stream2 >> result; + return (result != NetWorkStatusUnknown) && (result != NetWorkStatusOnline); + } + return false; // On error, assume we are online +} + +void notifyCannotConnect( const Account *account, const QString &explanation, const QString &debugInfo) +{ + if (!account) + return; + + notify( account->accountIcon(), QString::fromLatin1("cannot_connect"), notifyCannotConnect_DefaultCaption, notifyCannotConnect_DefaultMessage, notifyCannotConnect_DefaultExplanation, debugInfo); +} + +void notifyConnectionError( const Account *account, const QString &caption, const QString &message, const QString &explanation, const QString &debugInfo ) +{ + if (!account) + return; + + // TODO: Display a specific default connection error message, I don't want to introducte too many new strings + notify( account->accountIcon(32), QString::fromLatin1("connection_error"), caption, message, explanation, debugInfo); +} + +void notifyServerError( const Account *account, const QString &caption, const QString &message, const QString &explanation, const QString &debugInfo ) +{ + if (!account) + return; + + // TODO: Display a specific default server error message, I don't want to introducte too many new strings + notify( account->accountIcon(32), QString::fromLatin1("server_error"), caption, message, explanation, debugInfo); +} + +} // end ns ErrorNotifier +} // end ns Kopete + diff --git a/kopete/libkopete/kopeteutils.h b/kopete/libkopete/kopeteutils.h new file mode 100644 index 00000000..1cbcb4c3 --- /dev/null +++ b/kopete/libkopete/kopeteutils.h @@ -0,0 +1,114 @@ +/* + Kopete Utils. + + Copyright (c) 2005 Duncan Mac-Vicar Prett + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_UTILS_H +#define KOPETE_UTILS_H + +#include "qobject.h" +#include "qstring.h" +#include "qpixmap.h" +#include "kopete_export.h" + +class KNotification; + +namespace Kopete +{ + +class Account; + +namespace Utils +{ + +/** + * Checks if host is accesible. Useful for plugins to check for disconnected events. + * + * @param host The host to be cheked + */ +bool isHostReachable( const QString &host ); + +/** + * Notifies the user connection has been lost without coupling plugins with GUI code. + * + * @param account The account that lost the connection and wants to notify the user. + * @param caption A brief subject line, used where possible if the presentation allows it. + * @param message A short description of the error. + * @param explanation A long description on how the error occured and what the user can do about it. + * @param debugInfo Debug info that can be sent to the developers or to the network service owners. + * + * You can not provide debugInfo without an user explanation. If you don't provide a caption, message, or + * explanation, Kopete will use a default explanation. + */ +void KOPETE_EXPORT notifyConnectionLost( const Account *account, + const QString &caption = QString::null, + const QString &message = QString::null, + const QString &explanation = QString::null, + const QString &debugInfo = QString::null ); + + +/** + * Notifies the user the server is not reachable without coupling plugins with GUI code. + * + * @param account The account that cannot establish a connection and want to notify the user about that. + * @param explanation A long description on how the error occured and what the user can do about it. + * @param debugInfo Debug info that can be sent to the developers or to the network service owners. + * + * You can not provide debugInfo without an user explanation. If you don't provide a caption, message, or + * explanation, Kopete will use a default explanation. + */ +void KOPETE_EXPORT notifyCannotConnect( const Account *account, + const QString &explanation = QString::null, + const QString &debugInfo = QString::null); + +/** + * Notifies the user that an error on a connection occcured without coupling plugins with GUI code. + * + * @param account The account where the connection error occured and wants to notify the user. + * @param caption A brief subject line, used where possible if the presentation allows it. + * @param message A short description of the error. + * @param explanation A long description on how the error occured and what the user can do about it. + * @param debugInfo Debug info that can be sent to the developers or to the network service owners. + * + * You can not provide debugInfo without an user explanation. If you don't provide a caption, message, or + * explanation, Kopete will use a default explanation. + */ +void KOPETE_EXPORT notifyConnectionError( const Account *account, + const QString &caption = QString::null, + const QString &message = QString::null, + const QString &explanation = QString::null, + const QString &debugInfo = QString::null ); + +/** + * Notifies the user that an error on the server occcured without coupling plugins with GUI code. + * + * @param account The account where the server error occured and wants to notify the user. + * @param caption A brief subject line, used where possible if the presentation allows it. + * @param message A short description of the error. + * @param explanation A long description on how the error occured and what the user can do about it. + * @param debugInfo Debug info that can be sent to the developers or to the network service owners. + * + * You can not provide debugInfo without an user explanation. If you don't provide a caption, message, or + * explanation, Kopete will use a default explanation. + */ +void KOPETE_EXPORT notifyServerError( const Account *account, + const QString &caption = QString::null, + const QString &message = QString::null, + const QString &explanation = QString::null, + const QString &debugInfo = QString::null ); +} // end ns Utils +} // end ns Kopete + +#endif diff --git a/kopete/libkopete/kopeteversion.h b/kopete/libkopete/kopeteversion.h new file mode 100644 index 00000000..9775347c --- /dev/null +++ b/kopete/libkopete/kopeteversion.h @@ -0,0 +1,29 @@ +/* + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETE_VERSION_H_ +#define _KOPETE_VERSION_H_ + +#define KOPETE_VERSION_STRING "0.12.7" +#define KOPETE_VERSION_MAJOR 0 +#define KOPETE_VERSION_MINOR 12 +#define KOPETE_VERSION_RELEASE 7 +#define KOPETE_MAKE_VERSION( a,b,c ) (((a) << 16) | ((b) << 8) | (c)) + +#define KOPETE_VERSION \ + KOPETE_MAKE_VERSION(KOPETE_VERSION_MAJOR,KOPETE_VERSION_MINOR,KOPETE_VERSION_RELEASE) + +#define KOPETE_IS_VERSION(a,b,c) ( KOPETE_VERSION >= KOPETE_MAKE_VERSION(a,b,c) ) + + +#endif // _KOPETE_VERSION_H_ diff --git a/kopete/libkopete/kopetewalletmanager.cpp b/kopete/libkopete/kopetewalletmanager.cpp new file mode 100644 index 00000000..e1d198fc --- /dev/null +++ b/kopete/libkopete/kopetewalletmanager.cpp @@ -0,0 +1,190 @@ +/* + kopetewalletmanager.cpp - Kopete Wallet Manager + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetewalletmanager.h" + +#include "kopeteuiglobal.h" + +#include +#include +#include + +#include +#include +#include + +static WId mainWindowID() +{ + if ( QWidget *w = Kopete::UI::Global::mainWidget() ) + return w->winId(); + return 0; +} + +class Kopete::WalletManager::Private +{ +public: + Private() : wallet(0), signal(0) {} + ~Private() { delete wallet; delete signal; } + + KWallet::Wallet *wallet; + + // we can't just connect every slot that wants the wallet to the + // walletOpened signal - since we disconnect all the slots immediately + // after emitting the signal, this would result in everyone who asked + // for the wallet again in response to a walletOpened signal to fail + // to receive it. + // instead, we store a KopeteWalletSignal which we connect to, and create + // a new one for each set of requests. + KopeteWalletSignal *signal; +}; + +Kopete::WalletManager::WalletManager() + : d( new Private ) +{ +} + +Kopete::WalletManager::~WalletManager() +{ + closeWallet(); + delete d; +} + +Kopete::WalletManager *Kopete::WalletManager::self() +{ + static KStaticDeleter s_deleter; + static Kopete::WalletManager *s_self = 0; + + if ( !s_self ) + s_deleter.setObject( s_self, new Kopete::WalletManager() ); + return s_self; +} + +void Kopete::WalletManager::openWallet( QObject *object, const char *slot ) +{ + if ( !d->signal ) + d->signal = new KopeteWalletSignal; + // allow connecting to protected slots by calling object->connect + connect( d->signal, SIGNAL( walletOpened( KWallet::Wallet* ) ), object, slot ); + //object->connect( d->signal, SIGNAL( walletOpened( KWallet::Wallet* ) ), slot ); + openWalletInner(); +} + +void Kopete::WalletManager::openWalletInner() +{ + // do we already have a wallet? + if ( d->wallet ) + { + // if the wallet isn't open yet, we're pending a slotWalletChangedStatus + // anyway, so we don't set up a single shot. + if ( d->wallet->isOpen() ) + { + kdDebug(14010) << k_funcinfo << " wallet already open" << endl; + QTimer::singleShot( 0, this, SLOT( slotGiveExistingWallet() ) ); + } + else + { + kdDebug(14010) << k_funcinfo << " still waiting for earlier request" << endl; + } + return; + } + + kdDebug(14010) << k_funcinfo << " about to open wallet async" << endl; + + // we have no wallet: ask for one. + d->wallet = KWallet::Wallet::openWallet( KWallet::Wallet::NetworkWallet(), + mainWindowID(), KWallet::Wallet::Asynchronous ); + + connect( d->wallet, SIGNAL( walletOpened(bool) ), SLOT( slotWalletChangedStatus() ) ); +} + +void Kopete::WalletManager::slotWalletChangedStatus() +{ + kdDebug(14010) << k_funcinfo << " isOpen: " << d->wallet->isOpen() << endl; + + if( d->wallet->isOpen() ) + { + if ( !d->wallet->hasFolder( QString::fromLatin1( "Kopete" ) ) ) + d->wallet->createFolder( QString::fromLatin1( "Kopete" ) ); + + if ( d->wallet->setFolder( QString::fromLatin1( "Kopete" ) ) ) + { + // success! + QObject::connect( d->wallet, SIGNAL( walletClosed() ), this, SLOT( closeWallet() ) ); + } + else + { + // opened OK, but we can't use it + delete d->wallet; + d->wallet = 0; + } + } + else + { + // failed to open + delete d->wallet; + d->wallet = 0; + } + + emitWalletOpened( d->wallet ); +} + +void Kopete::WalletManager::slotGiveExistingWallet() +{ + kdDebug(14010) << k_funcinfo << " with d->wallet " << d->wallet << endl; + + if ( d->wallet ) + { + // the wallet was already open + if ( d->wallet->isOpen() ) + emitWalletOpened( d->wallet ); + // if the wallet was not open, but d->wallet is not 0, + // then we're waiting for it to open, and will be told + // when it's done: do nothing. + else + kdDebug(14010) << k_funcinfo << " wallet gone, waiting for another wallet" << endl; + } + else + { + // the wallet was lost between us trying to open it and + // getting called back. try to reopen it. + openWalletInner(); + } +} + +void Kopete::WalletManager::closeWallet() +{ + if ( !d->wallet ) return; + + delete d->wallet; + d->wallet = 0L; + + emit walletLost(); +} + +void Kopete::WalletManager::emitWalletOpened( KWallet::Wallet *wallet ) +{ + KopeteWalletSignal *signal = d->signal; + d->signal = 0; + if ( signal ) + emit signal->walletOpened( wallet ); + delete signal; +} + + +#include "kopetewalletmanager.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/kopetewalletmanager.h b/kopete/libkopete/kopetewalletmanager.h new file mode 100644 index 00000000..fdd3a154 --- /dev/null +++ b/kopete/libkopete/kopetewalletmanager.h @@ -0,0 +1,116 @@ +/* + kopetewalletmanager.h - Kopete Wallet Manager + + Copyright (c) 2004 by Richard Smith + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEWALLETMANAGER_H +#define KOPETEWALLETMANAGER_H + +#include + +#include + +#include "kopete_export.h" + +namespace KWallet { class Wallet; } + +namespace Kopete +{ + +/** + * @author Richard Smith + * + * The Kopete::WalletManager class is a singleton, which looks after Kopete's + * KWallet connection. + */ +class KOPETE_EXPORT WalletManager : public QObject +{ + Q_OBJECT + +public: + /** + * Retrieve the wallet manager instance + */ + static WalletManager *self(); + ~WalletManager(); + + /** + * @brief Attempt to open the KWallet asyncronously, then signal an + * object to indicate the task is complete. + * + * @param object The object to call back to + * @param slot The slot on object to call; must have signature slot( KWallet::Wallet* ) + * The parameter to the slot will be the wallet that was opened if the call + * succeeded, or NULL if the wallet failed to open or the Kopete folder was + * inaccessible. + * + * For simplicity of client code, it is guaranteed that your slot + * will not be called during a call to this function. + */ + void openWallet( QObject *object, const char *slot ); + +public slots: + /** + * Close the connection to the wallet. Will cause walletLost() to be emitted. + */ + void closeWallet(); + +signals: + /** + * Emitted when the connection to the wallet is lost. + */ + void walletLost(); + +private slots: + /** + * Called by the stored wallet pointer when it is successfully opened or + * when it fails. + * + * Causes walletOpened to be emitted. + */ + void slotWalletChangedStatus(); + + /** + * Called by a singleShot timer in the event that we are asked for a + * wallet when we already have one open and ready. + */ + void slotGiveExistingWallet(); + +private: + void openWalletInner(); + void emitWalletOpened( KWallet::Wallet *wallet ); + + class Private; + Private *d; + + WalletManager(); +}; + +} + +/** + * @internal + */ +class KopeteWalletSignal : public QObject +{ + Q_OBJECT + friend class Kopete::WalletManager; +signals: + void walletOpened( KWallet::Wallet *wallet ); +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/managedconnectionaccount.cpp b/kopete/libkopete/managedconnectionaccount.cpp new file mode 100644 index 00000000..0f1625b2 --- /dev/null +++ b/kopete/libkopete/managedconnectionaccount.cpp @@ -0,0 +1,73 @@ +/* + managedconnectionaccount.h - Kopete Account that uses a manager to + control its connection and respond to connection events + + Copyright (c) 2005 by Will Stephenson + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "connectionmanager.h" +#include "kopeteuiglobal.h" + +#include "managedconnectionaccount.h" + + +namespace Kopete +{ + +ManagedConnectionAccount::ManagedConnectionAccount( Protocol *parent, const QString &acctId, uint maxPasswordLength, const char *name ) + : PasswordedAccount( parent, acctId, maxPasswordLength, name ), m_waitingForConnection( false ) +{ + QObject::connect( ConnectionManager::self(), SIGNAL(statusChanged(const QString&, NetworkStatus::EnumStatus ) ), + SLOT(slotConnectionStatusChanged(const QString&, NetworkStatus::EnumStatus ) ) ); +} + +void ManagedConnectionAccount::connectWithPassword( const QString &password ) +{ + m_password = password; + NetworkStatus::EnumStatus status = ConnectionManager::self()->status( QString::null ); + if ( status == NetworkStatus::NoNetworks ) + performConnectWithPassword( password ); + else + { + m_waitingForConnection = true; + // need to adapt libkopete so we know the hostname in this class and whether the connection was user initiated + // for now, these are the default parameters to always bring up a connection to "the internet". + NetworkStatus::EnumRequestResult response = ConnectionManager::self()->requestConnection( Kopete::UI::Global::mainWidget(), QString::null, true ); + if ( response == NetworkStatus::Connected ) + { + m_waitingForConnection = false; + performConnectWithPassword( password ); + } + else if ( response == NetworkStatus::UserRefused || response == NetworkStatus::Unavailable ) + disconnect(); + } +} + +void ManagedConnectionAccount::slotConnectionStatusChanged( const QString & host, NetworkStatus::EnumStatus status ) +{ + Q_UNUSED(host); // as above, we didn't register a hostname, so treat any connection as our own. + + if ( m_waitingForConnection && ( status == NetworkStatus::Online || status == NetworkStatus::NoNetworks ) ) + { + m_waitingForConnection = false; + performConnectWithPassword( m_password ); + } + else if ( isConnected() && ( status == NetworkStatus::Offline + || status == NetworkStatus::ShuttingDown + || status == NetworkStatus::OfflineDisconnected + || status == NetworkStatus::OfflineFailed ) ) + disconnect(); +} + +} // end namespace Kopete +#include "managedconnectionaccount.moc" diff --git a/kopete/libkopete/managedconnectionaccount.h b/kopete/libkopete/managedconnectionaccount.h new file mode 100644 index 00000000..ad29feed --- /dev/null +++ b/kopete/libkopete/managedconnectionaccount.h @@ -0,0 +1,79 @@ +/* + managedconnectionaccount.h - Kopete Account that uses a manager to + control its connection and respond to connection events + + Copyright (c) 2005 by Will Stephenson + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef MANAGEDCONNECTIONACCOUNT_H +#define MANAGEDCONNECTIONACCOUNT_H + +#include "networkstatuscommon.h" + +#include "kopetepasswordedaccount.h" + +namespace Kopete +{ +class Protocol; + +/** + * A ManagedConnectionAccount queries the NetworkStatus KDED Module before trying to connect using + * connectwithPassword, starting a network connection if needed. If the network is not available, + * it delays calling performConnectWithPassword until it receives notification from the daemon + * that the network is up. The account receiveds notifications from the daemon of network failures + * and calls disconnect to set the account offline in a timely manner. + */ +class KOPETE_EXPORT ManagedConnectionAccount : public PasswordedAccount +{ + Q_OBJECT + public: + /** + * @brief ManagedConnectionAccount constructor. + * @param parent The protocol this account connects via + * @param acctId The ID of this account - should be unique within this protocol + * @param maxPasswordLength The maximum length for passwords for this account, or 0 for no limit + * @param name The name for this QObject + */ + ManagedConnectionAccount( Protocol *parent, const QString &acctId, uint maxPasswordLength = 0, const char *name = 0 ); + public slots: + /** + * @brief Begin the connection process, by checking if the connection is available with the ConnectionManager. + * This method is called by PasswordedAccount::connect() + * @param password the password to connect with. + */ + void connectWithPassword( const QString &password ); + protected: + /** + * @brief Connect to the server, once the network is available. + * This method is called by the ManagedConnectionAccount once the network is available. In this method you should set up your + * network connection and connect to the server. + */ + virtual void performConnectWithPassword( const QString & password ) = 0; + protected slots: + /** + * @brief Handle a change in the network connection + * Called by the ConnectionManager when the network comes up or fails. + * The default implementation calls performConnectWithPassword when the network goes online and connectWithPassword() was + * previously called, and calls disconnect() when the connection goes down. + * @param host For future expansion. + * @param status the new status of the network + */ + virtual void slotConnectionStatusChanged( const QString & host, NetworkStatus::EnumStatus status ); + private: + QString m_password; + bool m_waitingForConnection; +}; + +} + +#endif diff --git a/kopete/libkopete/networkstatuscommon.cpp b/kopete/libkopete/networkstatuscommon.cpp new file mode 100644 index 00000000..216752bd --- /dev/null +++ b/kopete/libkopete/networkstatuscommon.cpp @@ -0,0 +1,32 @@ +#include "networkstatuscommon.h" +#include + +QDataStream & operator<< ( QDataStream & s, const NetworkStatus::Properties p ) +{ + kdDebug() << k_funcinfo << "status is: " << (int)p.status << endl; + s << (int)p.status; + s << (int)p.onDemandPolicy; + s << p.service; + s << ( p.internet ? 1 : 0 ); + s << p.netmasks; + return s; +} + +QDataStream & operator>> ( QDataStream & s, NetworkStatus::Properties &p ) +{ + int status, onDemandPolicy, internet; + s >> status; + kdDebug() << k_funcinfo << "status is: " << status << endl; + p.status = ( NetworkStatus::EnumStatus )status; + s >> onDemandPolicy; + p.onDemandPolicy = ( NetworkStatus::EnumOnDemandPolicy )onDemandPolicy; + s >> p.service; + s >> internet; + if ( internet ) + p.internet = true; + else + p.internet = false; + s >> p.netmasks; + kdDebug() << k_funcinfo << "enum converted status is: " << p.status << endl; + return s; +} diff --git a/kopete/libkopete/networkstatuscommon.h b/kopete/libkopete/networkstatuscommon.h new file mode 100644 index 00000000..e6906445 --- /dev/null +++ b/kopete/libkopete/networkstatuscommon.h @@ -0,0 +1,33 @@ +#ifndef NETWORKSTATUS_COMMON_H +#define NETWORKSTATUS_COMMON_H + +#include + +namespace NetworkStatus +{ + enum EnumStatus { NoNetworks = 1, Unreachable, OfflineDisconnected, OfflineFailed, ShuttingDown, Offline, Establishing, Online }; + enum EnumRequestResult { RequestAccepted = 1, Connected, UserRefused, Unavailable }; + enum EnumOnDemandPolicy { All, User, None, Permanent }; + struct Properties + { + QString name; + // status of the network + EnumStatus status; + // policy for on-demand usage as defined by the service + EnumOnDemandPolicy onDemandPolicy; + // identifier for the service + QCString service; + // indicate that the connection is to 'the internet' - similar to default gateway in routing + bool internet; + // list of netmasks that the network connects to - overridden by above internet + QStringList netmasks; + // for future expansion consider + // EnumChargingModel - FlatRate, TimeCharge, VolumeCharged + // EnumLinkStatus - for WLANs - VPOOR, POOR, AVERAGE, GOOD, EXCELLENT + }; +} + +QDataStream & operator>> ( QDataStream & s, NetworkStatus::Properties &p ); +QDataStream & operator<< ( QDataStream & s, const NetworkStatus::Properties p ); + +#endif diff --git a/kopete/libkopete/private/Makefile.am b/kopete/libkopete/private/Makefile.am new file mode 100644 index 00000000..15e930df --- /dev/null +++ b/kopete/libkopete/private/Makefile.am @@ -0,0 +1,13 @@ +METASOURCES = AUTO + +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_COMPAT -DQT_NO_CAST_ASCII -DQT_NO_ASCII_CAST \ + $(KOPETE_INCLUDES) $(all_includes) + +noinst_LTLIBRARIES = libkopeteprivate.la + +libkopeteprivate_la_SOURCES = kopeteemoticons.cpp \ + kopetecommand.cpp kopeteviewmanager.cpp kopeteutils_private.cpp +libkopeteprivate_la_LDFLAGS = $(all_libraries) +libkopeteprivate_la_LIBADD = $(LIB_KDEUI) +# vim: set noet: + diff --git a/kopete/libkopete/private/kopetecommand.cpp b/kopete/libkopete/private/kopetecommand.cpp new file mode 100644 index 00000000..52588f2e --- /dev/null +++ b/kopete/libkopete/private/kopetecommand.cpp @@ -0,0 +1,142 @@ +/* + kopetecommand.cpp - Command + + Copyright (c) 2003 by Jason Keirstead + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include +#include + +#include "kopetechatsessionmanager.h" +#include "kopeteview.h" +#include "kopetecommand.h" +#include "kopeteuiglobal.h" + +Kopete::Command::Command( QObject *parent, const QString &command, const char* handlerSlot, + const QString &help, Kopete::CommandHandler::CommandType type, const QString &formatString, + uint minArgs, int maxArgs, const KShortcut &cut, const QString &pix ) + : KAction( command[0].upper() + command.right( command.length() - 1).lower(), pix, cut, parent, + ( command.lower() + QString::fromLatin1("_command") ).latin1() ) +{ + init( command, handlerSlot, help, type, formatString, minArgs, maxArgs ); +} + +void Kopete::Command::init( const QString &command, const char* slot, const QString &help, + Kopete::CommandHandler::CommandType type, const QString &formatString, uint minArgs, int maxArgs ) +{ + m_command = command; + m_help = help; + m_type = type; + m_formatString = formatString; + m_minArgs = minArgs; + m_maxArgs = maxArgs; + m_processing = false; + + if( m_type == Kopete::CommandHandler::Normal ) + { + QObject::connect( this, SIGNAL( handleCommand( const QString &, Kopete::ChatSession *) ), + parent(), slot ); + } + + QObject::connect( this, SIGNAL( activated() ), this, SLOT( slotAction() ) ); +} + +void Kopete::Command::slotAction() +{ + Kopete::ChatSession *manager = Kopete::ChatSessionManager::self()->activeView()->msgManager(); + + QString args; + if( m_minArgs > 0 ) + { + args = KInputDialog::getText( i18n("Enter Arguments"), i18n("Enter the arguments to %1:").arg(m_command) ); + if( args.isNull() ) + return; + } + + processCommand( args, manager, true ); +} + +void Kopete::Command::processCommand( const QString &args, Kopete::ChatSession *manager, bool gui ) +{ + QStringList mArgs = Kopete::CommandHandler::parseArguments( args ); + if( m_processing ) + { + printError( i18n("Alias \"%1\" expands to itself.").arg( text() ), manager, gui ); + } + else if( mArgs.count() < m_minArgs ) + { + printError( i18n("\"%1\" requires at least %n argument.", + "\"%1\" requires at least %n arguments.", m_minArgs) + .arg( text() ), manager, gui ); + } + else if( m_maxArgs > -1 && (int)mArgs.count() > m_maxArgs ) + { + printError( i18n("\"%1\" has a maximum of %n argument.", + "\"%1\" has a maximum of %n arguments.", m_minArgs) + .arg( text() ), manager, gui ); + } + else if( !KApplication::kApplication()->authorizeKAction( name() ) ) + { + printError( i18n("You are not authorized to perform the command \"%1\".").arg(text()), manager, gui ); + } + else + { + m_processing = true; + if( m_type == Kopete::CommandHandler::UserAlias || + m_type == Kopete::CommandHandler::SystemAlias ) + { + QString formatString = m_formatString; + + // Translate %s to the whole string and %n to current nickname + + formatString.replace( QString::fromLatin1("%n"), manager->myself()->nickName() ); + formatString.replace( QString::fromLatin1("%s"), args ); + + // Translate %1..%N to word1..wordN + + while( mArgs.count() > 0 ) + { + formatString = formatString.arg( mArgs.front() ); + mArgs.pop_front(); + } + + kdDebug(14010) << "New Command after processing alias: " << formatString << endl; + + Kopete::CommandHandler::commandHandler()->processMessage( QString::fromLatin1("/") + formatString, manager ); + } + else + { + emit( handleCommand( args, manager ) ); + } + m_processing = false; + } +} + +void Kopete::Command::printError( const QString &error, Kopete::ChatSession *manager, bool gui ) const +{ + if( gui ) + { + KMessageBox::error( Kopete::UI::Global::mainWidget(), error, i18n("Command Error") ); + } + else + { + Kopete::Message msg( manager->myself(), manager->members(), error, + Kopete::Message::Internal, Kopete::Message::PlainText ); + manager->appendMessage( msg ); + } +} + +#include "kopetecommand.moc" diff --git a/kopete/libkopete/private/kopetecommand.h b/kopete/libkopete/private/kopetecommand.h new file mode 100644 index 00000000..298872db --- /dev/null +++ b/kopete/libkopete/private/kopetecommand.h @@ -0,0 +1,109 @@ + +/* + kopetecommand.h - Command + + Copyright (c) 2003 by Jason Keirstead + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __KOPETECOMMAND_H__ +#define __KOPETECOMMAND_H__ + +#include +#include +#include "kopetecommandhandler.h" + +namespace Kopete +{ + +class ChatSession; + +class Command : public KAction +{ + Q_OBJECT + + public: + /** + * Creates a Kopete::Command object + * + * @param parent The plugin who owns this command + * @param command The command we want to handle, not including the '/' + * @param handlerSlot The slot used to handle the command. This slot must + * accept two parameters, a QString of arguments, and a Kopete::ChatSession + * pointer to the Manager under which the command was sent. + * @param help An optional help string to be shown when the user uses + * /help command + * @param type If this command is an alias, and what type + * @param formatString The formatString of the alias if any + * @param minArgs Minimum number of arguments + * @param maxArgs Maximum number of arguments + * @param cut The shortcut for the command + * @param pix The icon to use for the command + */ + Command( QObject *parent, const QString &command, const char* handlerSlot, + const QString &help = QString::null, CommandHandler::CommandType type = CommandHandler::Normal, const QString &formatString = QString::null, + uint minArgs = 0, int maxArgs = -1, const KShortcut &cut = 0, + const QString &pix = QString::null ); + + /** + * Process this command + */ + void processCommand( const QString &args, ChatSession *manager, bool gui = false ); + + /** + * Returns the command this object handles + */ + const QString &command() const { return m_command; }; + + /** + * Returns the help string for this command + */ + const QString &help() const { return m_help; }; + + /** + * Returns the type of the command + */ + const CommandHandler::CommandType type() const { return m_type; }; + + signals: + /** + * Emitted whenever a command is handled by this object. When a command + * has been handled, all processing on it stops by the command handler + * (a command cannot be handled twice) + */ + void handleCommand( const QString &args, Kopete::ChatSession *manager ); + + private slots: + /** + * Connected to our activated() signal + */ + void slotAction(); + + private: + void init( const QString &command, const char* slot, const QString &help, + CommandHandler::CommandType type, const QString &formatString, + uint minArgs, int maxArgs ); + + void printError( const QString &error, ChatSession *manager, bool gui = false ) const; + + QString m_command; + QString m_help; + QString m_formatString; + uint m_minArgs; + int m_maxArgs; + bool m_processing; + CommandHandler::CommandType m_type; +}; + +} + +#endif diff --git a/kopete/libkopete/private/kopeteemoticons.cpp b/kopete/libkopete/private/kopeteemoticons.cpp new file mode 100644 index 00000000..87da4cf7 --- /dev/null +++ b/kopete/libkopete/private/kopeteemoticons.cpp @@ -0,0 +1,559 @@ +/* + kopeteemoticons.cpp - Kopete Preferences Container-Class + + Copyright (c) 2002 by Stefan Gehn + Copyright (c) 2002-2006 by Olivier Goffart + Copyright (c) 2005 by Engin AYDOGAN + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteemoticons.h" + +#include "kopeteprefs.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + + +/* + * Testcases can be found in the kopeteemoticontest app in the tests/ directory. + */ + + +namespace Kopete { + + +struct Emoticons::Emoticon +{ + Emoticon(){} + /* sort by longest to shortest matchText */ + bool operator< (const Emoticon &e){ return matchText.length() > e.matchText.length(); } + QString matchText; + QString matchTextEscaped; + QString picPath; + QString picHTMLCode; +}; + +/* This is the object we will store each emoticon match in */ +struct Emoticons::EmoticonNode { + const Emoticon emoticon; + int pos; + EmoticonNode() : emoticon(), pos( -1 ) {} + EmoticonNode( const Emoticon e, int p ) : emoticon( e ), pos( p ) {} +}; + +class Emoticons::Private +{ +public: + QMap > emoticonMap; + QMap emoticonAndPicList; + + /** + * The current icon theme from KopetePrefs + */ + QString theme; + + +}; + + +Emoticons *Emoticons::s_self = 0L; + +Emoticons *Emoticons::self() +{ + if( !s_self ) + s_self = new Emoticons; + return s_self; +} + + +QString Emoticons::parseEmoticons(const QString& message, ParseMode mode ) //static +{ + return self()->parse( message, mode ); +} + +QValueList Emoticons::tokenizeEmoticons( const QString& message, ParseMode mode ) // static +{ + return self()->tokenize( message, mode ); +} + +QValueList Emoticons::tokenize( const QString& message, uint mode ) +{ + QValueList result; + if ( !KopetePrefs::prefs()->useEmoticons() ) + { + result.append( Token( Text, message ) ); + return result; + } + + if( ! ( mode & (StrictParse|RelaxedParse) ) ) + { + //if none of theses two mode are selected, use the mode from the config + mode |= KopetePrefs::prefs()->emoticonsRequireSpaces() ? StrictParse : RelaxedParse ; + } + + /* previous char, in the firs iteration assume that it is space since we want + * to let emoticons at the beginning, the very first previous QChar must be a space. */ + QChar p = ' '; + QChar c; /* current char */ + QChar n; /* next character after a match candidate, if strict this should be QChar::null or space */ + + /* This is the EmoticonNode container, it will represent each matched emoticon */ + QValueList foundEmoticons; + QValueList::const_iterator found; + /* First-pass, store the matched emoticon locations in foundEmoticons */ + QValueList emoticonList; + QValueList::const_iterator it; + size_t pos; + + bool inHTMLTag = false; + bool inHTMLLink = false; + bool inHTMLEntity = false; + QString needle; // search for this + for ( pos = 0; pos < message.length(); pos++ ) + { + c = message[ pos ]; + + if ( mode & SkipHTML ) // Shall we skip HTML ? + { + if ( !inHTMLTag ) // Are we already in an HTML tag ? + { + if ( c == '<' ) { // If not check if are going into one + inHTMLTag = true; // If we are, change the state to inHTML + p = c; + continue; + } + } + else // We are already in a HTML tag + { + if ( c == '>' ) { // Check if it ends + inHTMLTag = false; // If so, change the state + if ( p == 'a' ) + { + inHTMLLink = false; + } + } + else if ( c == 'a' && p == '<' ) // check if we just entered an achor tag + { + inHTMLLink = true; // don't put smileys in urls + } + p = c; + continue; + } + + if( !inHTMLEntity ) + { // are we + if( c == '&' ) + { + inHTMLEntity = true; + } + } + } + + if ( inHTMLLink ) // i can't think of any situation where a link adress might need emoticons + { + p = c; + continue; + } + + if ( (mode & StrictParse) && !p.isSpace() && p != '>') + { // '>' may mark the end of an html tag + p = c; + continue; + } /* strict requires space before the emoticon */ + if ( d->emoticonMap.contains( c ) ) + { + emoticonList = d->emoticonMap[ c ]; + bool found = false; + for ( it = emoticonList.begin(); it != emoticonList.end(); ++it ) + { + // If this is an HTML, then search for the HTML form of the emoticon. + // For instance >o) + needle = ( mode & SkipHTML ) ? (*it).matchTextEscaped : (*it).matchText; + if ( ( pos == (size_t)message.find( needle, pos ) ) ) + { + if( mode & StrictParse ) + { + /* check if the character after this match is space or end of string*/ + n = message[ pos + needle.length() ]; + //
    marks the end of a line + if( n != '<' && !n.isSpace() && !n.isNull() && n!= '&') + break; + } + /* Perfect match */ + foundEmoticons.append( EmoticonNode( (*it), pos ) ); + found = true; + /* Skip the matched emoticon's matchText */ + pos += needle.length() - 1; + break; + } + } + if( !found ) + { + if( inHTMLEntity ){ + // If we are in an HTML entitiy such as > + int htmlEnd = message.find( ';', pos ); + // Search for where it ends + if( htmlEnd == -1 ) + { + // Apparently this HTML entity isn't ended, something is wrong, try skip the '&' + // and continue + kdDebug( 14000 ) << k_funcinfo << "Broken HTML entity, trying to recover." << endl; + inHTMLEntity = false; + pos++; + } + else + { + pos = htmlEnd; + inHTMLEntity = false; + } + } + } + } /* else no emoticons begin with this character, so don't do anything */ + p = c; + } + + /* if no emoticons found just return the text */ + if ( foundEmoticons.isEmpty() ) + { + result.append( Token( Text, message ) ); + return result; + } + + /* Second-pass, generate tokens based on the matches */ + + pos = 0; + int length; + + for ( found = foundEmoticons.begin(); found != foundEmoticons.end(); ++found ) + { + needle = ( mode & SkipHTML ) ? (*found).emoticon.matchTextEscaped : (*found).emoticon.matchText; + if ( ( length = ( (*found).pos - pos ) ) ) + { + result.append( Token( Text, message.mid( pos, length ) ) ); + result.append( Token( Image, (*found).emoticon.matchTextEscaped, (*found).emoticon.picPath, (*found).emoticon.picHTMLCode ) ); + pos += length + needle.length(); + } + else + { + result.append( Token( Image, (*found).emoticon.matchTextEscaped, (*found).emoticon.picPath, (*found).emoticon.picHTMLCode ) ); + pos += needle.length(); + } + } + + if ( message.length() - pos ) // if there is remaining regular text + { + result.append( Token( Text, message.mid( pos ) ) ); + } + + return result; +} + +Emoticons::Emoticons( const QString &theme ) : QObject( kapp, "KopeteEmoticons" ) +{ +// kdDebug(14010) << "KopeteEmoticons::KopeteEmoticons" << endl; + d=new Private; + if(theme.isNull()) + { + initEmoticons(); + connect( KopetePrefs::prefs(), SIGNAL(saved()), this, SLOT(initEmoticons()) ); + } + else + { + initEmoticons( theme ); + } +} + + +Emoticons::~Emoticons( ) +{ + delete d; +} + + + +void Emoticons::addIfPossible( const QString& filenameNoExt, const QStringList &emoticons ) +{ + KStandardDirs *dir = KGlobal::dirs(); + QString pic; + + //maybe an extension was given, so try to find the exact file + pic = dir->findResource( "emoticons", d->theme + QString::fromLatin1( "/" ) + filenameNoExt ); + + if( pic.isNull() ) + pic = dir->findResource( "emoticons", d->theme + QString::fromLatin1( "/" ) + filenameNoExt + QString::fromLatin1( ".mng" ) ); + if ( pic.isNull() ) + pic = dir->findResource( "emoticons", d->theme + QString::fromLatin1( "/" ) + filenameNoExt + QString::fromLatin1( ".png" ) ); + if ( pic.isNull() ) + pic = dir->findResource( "emoticons", d->theme + QString::fromLatin1( "/" ) + filenameNoExt + QString::fromLatin1( ".gif" ) ); + + if( !pic.isNull() ) // only add if we found one file + { + QPixmap p; + QString result; + + d->emoticonAndPicList.insert( pic, emoticons ); + + for ( QStringList::const_iterator it = emoticons.constBegin(), end = emoticons.constEnd(); + it != end; ++it ) + { + QString matchEscaped=QStyleSheet::escape(*it); + + Emoticon e; + e.picPath = pic; + + // We need to include size (width, height attributes) hints in the emoticon HTML code + // Unless we do so, ChatMessagePart::slotScrollView does not work properly and causing + // HTMLPart not to be scrolled to the very last message. + p.load( e.picPath ); + result = QString::fromLatin1( "" ); + + e.picHTMLCode = result; + e.matchTextEscaped = matchEscaped; + e.matchText = *it; + d->emoticonMap[ matchEscaped[0] ].append( e ); + d->emoticonMap[ (*it)[0] ].append( e ); + } + } +} + +void Emoticons::initEmoticons( const QString &theme ) +{ + if(theme.isNull()) + { + if ( d->theme == KopetePrefs::prefs()->iconTheme() ) + return; + + d->theme = KopetePrefs::prefs()->iconTheme(); + } + else + { + d->theme = theme; + } + +// kdDebug(14010) << k_funcinfo << "Called" << endl; + d->emoticonAndPicList.clear(); + d->emoticonMap.clear(); + + QString filename= KGlobal::dirs()->findResource( "emoticons", d->theme + QString::fromLatin1( "/emoticons.xml" ) ); + if(!filename.isEmpty()) + return initEmoticon_emoticonsxml( filename ); + filename= KGlobal::dirs()->findResource( "emoticons", d->theme + QString::fromLatin1( "/icondef.xml" ) ); + if(!filename.isEmpty()) + return initEmoticon_JEP0038( filename ); + kdWarning(14010) << k_funcinfo << "emotiucon XML theme description not found" < keys = d->emoticonMap.keys(); + for ( QValueList::const_iterator it = keys.begin(); it != keys.end(); ++it ) + { + QChar key = (*it); + QValueList keyValues = d->emoticonMap[key]; + qHeapSort(keyValues.begin(), keyValues.end()); + d->emoticonMap[key] = keyValues; + } +} + + + + +QMap Emoticons::emoticonAndPicList() +{ + return d->emoticonAndPicList; +} + + +QString Emoticons::parse( const QString &message, ParseMode mode ) +{ + if ( !KopetePrefs::prefs()->useEmoticons() ) + return message; + + QValueList tokens = tokenize( message, mode ); + QValueList::const_iterator token; + QString result; + QPixmap p; + for ( token = tokens.begin(); token != tokens.end(); ++token ) + { + switch ( (*token).type ) + { + case Text: + result += (*token).text; + break; + case Image: + result += (*token).picHTMLCode; + kdDebug( 14010 ) << k_funcinfo << "Emoticon html code: " << result << endl; + break; + default: + kdDebug( 14010 ) << k_funcinfo << "Unknown token type. Something's broken." << endl; + } + } + return result; +} + +} //END namesapce Kopete + +#include "kopeteemoticons.moc" + + + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/private/kopeteemoticons.h b/kopete/libkopete/private/kopeteemoticons.h new file mode 100644 index 00000000..848185e6 --- /dev/null +++ b/kopete/libkopete/private/kopeteemoticons.h @@ -0,0 +1,184 @@ +/* + kopeteemoticons.cpp - Kopete Preferences Container-Class + + Copyright (c) 2002-2003 by Stefan Gehn + Kopete (c) 2002-2004 by the Kopete developers + Copyright (c) 2005 by Engin AYDOGAN + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef kopeteemoticons_h__ +#define kopeteemoticons_h__ + +#include +#include +#include + +#include "kopete_export.h" + +namespace Kopete { + +class KOPETE_EXPORT Emoticons : public QObject +{ + Q_OBJECT +public: + /** + * Constructor: DON'T use it if you want to use the emoticon theme + * chosen by the user. + * Instead, use @ref Kopete::Emoticons::self() + **/ + Emoticons( const QString &theme = QString::null ); + + ~Emoticons( ); + + /** + * The emoticons container-class by default is a singleton object. + * Use this method to retrieve the instance. + */ + static Emoticons *self(); + + /** + * The possible parse modes + */ + enum ParseMode { DefaultParseMode = 0x0 , /** Use strict or relaxed according the config */ + StrictParse = 0x1, /** Strict parsing requires a space between each emoticon */ + RelaxedParse = 0x4, /** Parse mode where all possible emoticon matches are allowed */ + SkipHTML = 0x2 /** Skip emoticons within HTML */ + }; + + /** + * Use it to parse emoticons in a text. + * You don't need to use this for chat windows, + * There is a special class that abstract a chat view + * and uses emoticons parser. + * This function will use the selected emoticon theme. + * If nicks is provided, they will not be parsed if they + * exist in message. + */ + static QString parseEmoticons( const QString &message, ParseMode = SkipHTML ) ; + + + QString parse( const QString &message, ParseMode = SkipHTML ); + + /** + * TokenType, a token might be an image ( emoticon ) or text. + */ + enum TokenType { Undefined, /** Undefined, for completeness only */ + Image, /** Token contains a path to an image */ + Text /** Token contains test */ + }; + + /** + * A token consists of a QString text which is either a regular text + * or a path to image depending on the type. + * If type is Image the text refers to an image path. + * If type is Text the text refers to a regular text. + */ + struct Token { + Token() : type( Undefined ) {} + Token( TokenType t, const QString &m ) : type( t ), text(m) {} + Token( TokenType t, const QString &m, const QString &p, const QString &html ) + : type( t ), text( m ), picPath( p ), picHTMLCode( html ) {} + TokenType type; + QString text; + QString picPath; + QString picHTMLCode; + }; + + + /** + * Static function which will call tokenize + * @see tokenize( const QString& ) + */ + static QValueList tokenizeEmoticons( const QString &message, ParseMode mode = DefaultParseMode ); + + /** + * Tokenizes an message. + * For example; + * Assume :], (H), :-x are three emoticons. + * A text "(H)(H) foo bar john :] :-x" would be tokenized as follows (not strict): + * 1- /path/to/shades.png + * 2- /path/to/shades.png + * 3- " foo bar john " + * 4- /path/to/bat.png + * 5- " " + * 6- /path/to/kiss.png + * + * Strict tokenization (require spaces around emoticons): + * 1- "(H)(H) foo bar john " + * 2- /path/to/bat.png + * 3- " " + * 4- /path/to/kiss.png + * Note: quotation marks are used to emphasize white spaces. + * @param message is the message to tokenize + * @param mode is a bitmask of ParseMode enum + * @return a QValueList which consiste of ordered tokens of the text. + * @author Engin AYDOGAN < engin@bzzzt.biz > + * @since 23-03-05 + */ + QValueList tokenize( const QString &message, uint mode = DefaultParseMode ); + + /** + * Return all emoticons and the corresponding icon. + * (only one emoticon per image) + */ + QMap emoticonAndPicList(); + + +private: + /** + * Our instance + **/ + static Emoticons *s_self; + + /** + * add an emoticon to our mapping if + * an animation/pixmap has been found for it + **/ + void addIfPossible( const QString& filenameNoExt, const QStringList &emoticons ); + + /** + * uses the kopete's emoticons.xml for the theme + * @see initEmoticons + */ + void initEmoticon_emoticonsxml( const QString & filename); + + /** + * uses the JEP-0038 xml description for the theme + * @see initEmoticons + */ + void initEmoticon_JEP0038( const QString & filename); + + /** + * sorts emoticons for convenient parsing, which yields greedy matching on + * matchText + */ + void sortEmoticons(); + + + struct Emoticon; + struct EmoticonNode; + class Private; + Private *d; +private slots: + + /** + * Fills the map with paths and emoticons + * This needs to be done on every emoticon-theme change + **/ + void initEmoticons ( const QString &theme = QString::null ); +}; + + +} //END namespace Kopete + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/private/kopeteutils_private.cpp b/kopete/libkopete/private/kopeteutils_private.cpp new file mode 100644 index 00000000..3746bcd3 --- /dev/null +++ b/kopete/libkopete/private/kopeteutils_private.cpp @@ -0,0 +1,85 @@ +/* + Kopete Utils. + Copyright (c) 2005 Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include + +#include + +#include "knotification.h" +#include "kopeteutils_private.h" +#include "kopeteuiglobal.h" + +namespace Kopete +{ +namespace Utils +{ + +NotifyHelper* NotifyHelper::s_self = 0L; + +NotifyHelper::NotifyHelper() +{ +} + +NotifyHelper::~NotifyHelper() +{ +} + +NotifyHelper* NotifyHelper::self() +{ + if (!s_self) + s_self = new NotifyHelper(); + + return s_self; +} + +void NotifyHelper::slotEventActivated(unsigned int action) +{ + const KNotification *n = dynamic_cast(QObject::sender()); + if (n) + { + ErrorNotificationInfo info = m_events[n]; + if ( info.debugInfo.isEmpty() ) + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Information, info.explanation, info.caption); + else + KMessageBox::queuedDetailedError( Kopete::UI::Global::mainWidget(), info.explanation, info.debugInfo, info.caption); + + unregisterNotification(n); + } +} + +void NotifyHelper::slotEventClosed() +{ + const KNotification *n = dynamic_cast(QObject::sender()); + if (n) + unregisterNotification(n); +} + +void NotifyHelper::registerNotification(const KNotification* event, ErrorNotificationInfo error) +{ + m_events.insert( event, error); +} + +void NotifyHelper::unregisterNotification(const KNotification* event) +{ + m_events.remove(event); +} + +} // end ns ErrorNotifier +} // end ns Kopete + +#include "kopeteutils_private.moc" diff --git a/kopete/libkopete/private/kopeteutils_private.h b/kopete/libkopete/private/kopeteutils_private.h new file mode 100644 index 00000000..a684c965 --- /dev/null +++ b/kopete/libkopete/private/kopeteutils_private.h @@ -0,0 +1,60 @@ +/* + Kopete Utils. + + Copyright (c) 2005 Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_UTILS_PRIVATE_H +#define KOPETE_UTILS_PRIVATE_H + +#include "qobject.h" +#include "qstring.h" +#include "qpixmap.h" + +class KNotification; + +namespace Kopete +{ + +namespace Utils +{ + +typedef struct +{ + QString caption; + QString explanation; + QString debugInfo; +} ErrorNotificationInfo; + +class NotifyHelper : public QObject +{ +Q_OBJECT +public: + static NotifyHelper* self(); + void registerNotification(const KNotification* event, ErrorNotificationInfo error); + void unregisterNotification(const KNotification* event); +public slots: + void slotEventActivated(unsigned int action); + void slotEventClosed(); +private: + NotifyHelper(); + ~NotifyHelper(); + QMap m_events; + static NotifyHelper *s_self; +}; + +} // end ns Utils +} // end ns Kopete + +#endif diff --git a/kopete/libkopete/private/kopeteviewmanager.cpp b/kopete/libkopete/private/kopeteviewmanager.cpp new file mode 100644 index 00000000..c6d295fd --- /dev/null +++ b/kopete/libkopete/private/kopeteviewmanager.cpp @@ -0,0 +1,364 @@ +/* + kopeteviewmanager.cpp - View Manager + + Copyright (c) 2003 by Jason Keirstead + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopeteprefs.h" +#include "kopeteaccount.h" +#include "kopetepluginmanager.h" +#include "kopeteviewplugin.h" +#include "kopetechatsessionmanager.h" +#include "kopetemetacontact.h" +#include "kopetenotifyevent.h" +#include "kopetemessageevent.h" +#include "kopeteview.h" +//#include "systemtray.h" + +#include "kopeteviewmanager.h" + +typedef QMap ManagerMap; +typedef QPtrList EventList; + +struct KopeteViewManagerPrivate +{ + ManagerMap managerMap; + EventList eventList; + KopeteView *activeView; + + bool useQueueOrStack; + bool raiseWindow; + bool queueUnreadMessages; + bool queueOnlyHighlightedMessagesInGroupChats; + bool queueOnlyMessagesOnAnotherDesktop; + bool balloonNotifyIgnoreClosesChatView; + bool foreignMessage; +}; + +KopeteViewManager *KopeteViewManager::s_viewManager = 0L; + +KopeteViewManager *KopeteViewManager::viewManager() +{ + if( !s_viewManager ) + s_viewManager = new KopeteViewManager(); + return s_viewManager; +} + +KopeteViewManager::KopeteViewManager() +{ + s_viewManager=this; + d = new KopeteViewManagerPrivate; + d->activeView = 0L; + d->foreignMessage=false; + + connect( KopetePrefs::prefs(), SIGNAL( saved() ), this, SLOT( slotPrefsChanged() ) ); + + connect( Kopete::ChatSessionManager::self() , SIGNAL( display( Kopete::Message &, Kopete::ChatSession *) ), + this, SLOT ( messageAppended( Kopete::Message &, Kopete::ChatSession *) ) ); + + connect( Kopete::ChatSessionManager::self() , SIGNAL( readMessage() ), + this, SLOT ( nextEvent() ) ); + + slotPrefsChanged(); +} + +KopeteViewManager::~KopeteViewManager() +{ +// kdDebug(14000) << k_funcinfo << endl; + + //delete all open chatwindow. + ManagerMap::Iterator it; + for ( it = d->managerMap.begin(); it != d->managerMap.end(); ++it ) + it.data()->closeView( true ); //this does not clean the map, but we don't care + + delete d; +} + +void KopeteViewManager::slotPrefsChanged() +{ + d->useQueueOrStack = KopetePrefs::prefs()->useQueue() || KopetePrefs::prefs()->useStack(); + d->raiseWindow = KopetePrefs::prefs()->raiseMsgWindow(); + d->queueUnreadMessages = KopetePrefs::prefs()->queueUnreadMessages(); + d->queueOnlyHighlightedMessagesInGroupChats = KopetePrefs::prefs()->queueOnlyHighlightedMessagesInGroupChats(); + d->queueOnlyMessagesOnAnotherDesktop = KopetePrefs::prefs()->queueOnlyMessagesOnAnotherDesktop(); + d->balloonNotifyIgnoreClosesChatView = KopetePrefs::prefs()->balloonNotifyIgnoreClosesChatView(); +} + +KopeteView *KopeteViewManager::view( Kopete::ChatSession* session, const QString &requestedPlugin ) +{ +// kdDebug(14000) << k_funcinfo << endl; + + if( d->managerMap.contains( session ) && d->managerMap[ session ] ) + { + return d->managerMap[ session ]; + } + else + { + Kopete::PluginManager *pluginManager = Kopete::PluginManager::self(); + Kopete::ViewPlugin *viewPlugin = 0L; + + QString pluginName = requestedPlugin.isEmpty() ? KopetePrefs::prefs()->interfacePreference() : requestedPlugin; + if( !pluginName.isEmpty() ) + { + viewPlugin = (Kopete::ViewPlugin*)pluginManager->loadPlugin( pluginName ); + + if( !viewPlugin ) + { + kdWarning(14000) << "Requested view plugin, " << pluginName << + ", was not found. Falling back to chat window plugin" << endl; + } + } + + if( !viewPlugin ) + viewPlugin = (Kopete::ViewPlugin*)pluginManager->loadPlugin( QString::fromLatin1("kopete_chatwindow") ); + + if( viewPlugin ) + { + KopeteView *newView = viewPlugin->createView(session); + + d->foreignMessage = false; + d->managerMap.insert( session, newView ); + + connect( session, SIGNAL( closing(Kopete::ChatSession *) ), + this, SLOT(slotChatSessionDestroyed(Kopete::ChatSession*)) ); + + return newView; + } + else + { + kdError(14000) << "Could not create a view, no plugins available!" << endl; + return 0L; + } + } +} + + +void KopeteViewManager::messageAppended( Kopete::Message &msg, Kopete::ChatSession *manager) +{ +// kdDebug(14000) << k_funcinfo << endl; + + bool outgoingMessage = ( msg.direction() == Kopete::Message::Outbound ); + + if( !outgoingMessage || d->managerMap.contains( manager ) ) + { + d->foreignMessage=!outgoingMessage; //let know for the view we are about to create + manager->view(true,msg.requestedPlugin())->appendMessage( msg ); + d->foreignMessage=false; //the view is created, reset the flag + + bool appendMessageEvent = d->useQueueOrStack; + + QWidget *w; + if( d->queueUnreadMessages && ( w = dynamic_cast(view( manager )) ) ) + { + // append msg event to queue if chat window is active but not the chat view in it... + appendMessageEvent = appendMessageEvent && !(w->isActiveWindow() && manager->view() == d->activeView); + // ...and chat window is on another desktop + appendMessageEvent = appendMessageEvent && (!d->queueOnlyMessagesOnAnotherDesktop || !KWin::windowInfo( w->topLevelWidget()->winId(), NET::WMDesktop ).isOnCurrentDesktop()); + } + else + { + // append if no chat window exists already + appendMessageEvent = appendMessageEvent && !view( manager )->isVisible(); + } + + // in group chats always append highlighted messages to queue + appendMessageEvent = appendMessageEvent && (!d->queueOnlyHighlightedMessagesInGroupChats || manager->members().count() == 1 || msg.importance() == Kopete::Message::Highlight); + + if( appendMessageEvent ) + { + if ( !outgoingMessage ) + { + Kopete::MessageEvent *event=new Kopete::MessageEvent(msg,manager); + d->eventList.append( event ); + connect(event, SIGNAL(done(Kopete::MessageEvent *)), this, SLOT(slotEventDeleted(Kopete::MessageEvent *))); + Kopete::ChatSessionManager::self()->postNewEvent(event); + } + } + else if( d->eventList.isEmpty() ) + { + readMessages( manager, outgoingMessage ); + } + + if ( !outgoingMessage && ( !manager->account()->isAway() || KopetePrefs::prefs()->soundIfAway() ) + && msg.direction() != Kopete::Message::Internal ) + { + QWidget *w=dynamic_cast(manager->view(false)); + KConfig *config = KGlobal::config(); + config->setGroup("General"); + if( (!manager->view(false) || !w || manager->view() != d->activeView || + config->readBoolEntry("EventIfActive", true) || !w->isActiveWindow()) + && msg.from()) + { + QString msgFrom = QString::null; + if( msg.from()->metaContact() ) + msgFrom = msg.from()->metaContact()->displayName(); + else + msgFrom = msg.from()->contactId(); + + QString msgText = msg.plainBody(); + if( msgText.length() > 90 ) + msgText = msgText.left(88) + QString::fromLatin1("..."); + + QString event; + QString body =i18n( "Incoming message from %1
    \"%2\"
    " ); + + switch( msg.importance() ) + { + case Kopete::Message::Low: + event = QString::fromLatin1( "kopete_contact_lowpriority" ); + break; + case Kopete::Message::Highlight: + event = QString::fromLatin1( "kopete_contact_highlight" ); + body = i18n( "A highlighted message arrived from %1
    \"%2\"
    " ); + break; + default: + event = QString::fromLatin1( "kopete_contact_incoming" ); + } + KNotification *notify=KNotification::event(msg.from()->metaContact() , event, body.arg( QStyleSheet::escape(msgFrom), QStyleSheet::escape(msgText) ), 0, /*msg.from()->metaContact(),*/ + w , i18n("View") ); + + connect(notify,SIGNAL(activated(unsigned int )), manager , SLOT(raiseView()) ); + } + } + } +} + +void KopeteViewManager::readMessages( Kopete::ChatSession *manager, bool outgoingMessage, bool activate ) +{ +// kdDebug( 14000 ) << k_funcinfo << endl; + d->foreignMessage=!outgoingMessage; //let know for the view we are about to create + KopeteView *thisView = manager->view( true ); + d->foreignMessage=false; //the view is created, reset the flag + if( ( outgoingMessage && !thisView->isVisible() ) || d->raiseWindow || activate ) + thisView->raise( activate ); + else if( !thisView->isVisible() ) + thisView->makeVisible(); + + QPtrListIterator it( d->eventList ); + Kopete::MessageEvent* event; + while ( ( event = it.current() ) != 0 ) + { + ++it; + if ( event->message().manager() == manager ) + { + event->apply(); + d->eventList.remove( event ); + } + } +} + +void KopeteViewManager::slotEventDeleted( Kopete::MessageEvent *event ) +{ +// kdDebug(14000) << k_funcinfo << endl; + Kopete::ChatSession *kmm=event->message().manager(); + if(!kmm) + return; + + d->eventList.remove( event ); + + if ( event->state() == Kopete::MessageEvent::Applied ) + { + readMessages( kmm, false, true ); + } + else if ( event->state() == Kopete::MessageEvent::Ignored && d->balloonNotifyIgnoreClosesChatView ) + { + bool bAnotherWithThisManager = false; + for( QPtrListIterator it( d->eventList ); it; ++it ) + { + Kopete::MessageEvent *event = it.current(); + if ( event->message().manager() == kmm ) + bAnotherWithThisManager = true; + } + if ( !bAnotherWithThisManager && kmm->view( false ) ) + kmm->view()->closeView( true ); + } +} + +void KopeteViewManager::nextEvent() +{ +// kdDebug( 14000 ) << k_funcinfo << endl; + + if( d->eventList.isEmpty() ) + return; + + Kopete::MessageEvent* event = d->eventList.first(); + + if ( event ) + event->apply(); +} + +void KopeteViewManager::slotViewActivated( KopeteView *view ) +{ +// kdDebug( 14000 ) << k_funcinfo << endl; + d->activeView = view; + + QPtrListIterator it ( d->eventList ); + Kopete::MessageEvent* event; + while ( ( event = it.current() ) != 0 ) + { + ++it; + if ( event->message().manager() == view->msgManager() ) + event->deleteLater(); + } + +} + +void KopeteViewManager::slotViewDestroyed( KopeteView *closingView ) +{ +// kdDebug( 14000 ) << k_funcinfo << endl; + + if( d->managerMap.contains( closingView->msgManager() ) ) + { + d->managerMap.remove( closingView->msgManager() ); +// closingView->msgManager()->setCanBeDeleted( true ); + } + + if( closingView == d->activeView ) + d->activeView = 0L; +} + +void KopeteViewManager::slotChatSessionDestroyed( Kopete::ChatSession *manager ) +{ +// kdDebug( 14000 ) << k_funcinfo << endl; + + if( d->managerMap.contains( manager ) ) + { + KopeteView *v=d->managerMap[ manager ]; + v->closeView( true ); + delete v; //closeView call deleteLater, but in this case this is not enough, because some signal are called that case crash + d->managerMap.remove( manager ); + } +} + +KopeteView* KopeteViewManager::activeView() const +{ + return d->activeView; +} + + +#include "kopeteviewmanager.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/private/kopeteviewmanager.h b/kopete/libkopete/private/kopeteviewmanager.h new file mode 100644 index 00000000..b1706906 --- /dev/null +++ b/kopete/libkopete/private/kopeteviewmanager.h @@ -0,0 +1,103 @@ +/* + kopeteviewmanager.h - View Manager + + Copyright (c) 2003 by Jason Keirstead + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEVIEWMANAGER_H +#define KOPETEVIEWMANAGER_H + +#include "kopetemessage.h" +#include "kopete_export.h" + +namespace Kopete +{ + class ChatSession; + class Protocol; + class Contact; + class MessageEvent; +} + +class KopeteView; +class QTextEdit; + +struct KopeteViewManagerPrivate; + +/** + * Relates an actual chat to the means used to view it. + */ +class KOPETE_EXPORT KopeteViewManager : public QObject +{ + Q_OBJECT + public: + /** This is a singleton class. Call this method to get a pointer to + * a KopeteViewManager. + */ + static KopeteViewManager *viewManager(); + + KopeteViewManager(); + ~KopeteViewManager(); + + /** + * Return a view for the supplied Kopete::ChatSession. If one already + * exists, it will be returned, otherwise, a new view is created. + * @param session The Kopete::ChatSession we are viewing. + * @param requestedPlugin Specifies the view plugin to use. + */ + KopeteView *view( Kopete::ChatSession *session, const QString &requestedPlugin = QString::null ); + + /** + * Provide access to the list of KopeteChatWindow the class maintains. + */ + KopeteView *activeView() const; + + private: + + + KopeteViewManagerPrivate *d; + static KopeteViewManager *s_viewManager; + + public slots: + /** + * Make a view visible and on top. + * @param manager The originating Kopete::ChatSession. + * @param outgoingMessage Whether the message is inbound or outbound. + * @param activate Indicate whether the view should be activated + * @todo Document @p activate + */ + void readMessages( Kopete::ChatSession* manager, bool outgoingMessage, bool activate = false ); + + /** + * Called when a new message has been appended to the given + * Kopete::ChatSession. Procures a view for the message, and generates any notification events or displays messages, as appropriate. + * @param msg The new message + * @param manager The originating Kopete::ChatSession + */ + void messageAppended( Kopete::Message &msg, Kopete::ChatSession *manager); + + void nextEvent(); + + private slots: + void slotViewDestroyed( KopeteView *); + void slotChatSessionDestroyed( Kopete::ChatSession * ); + + /** + * An event has been deleted. + */ + void slotEventDeleted( Kopete::MessageEvent * ); + + void slotPrefsChanged(); + void slotViewActivated( KopeteView * ); +}; + +#endif diff --git a/kopete/libkopete/tests/Makefile.am b/kopete/libkopete/tests/Makefile.am new file mode 100644 index 00000000..417ce2a8 --- /dev/null +++ b/kopete/libkopete/tests/Makefile.am @@ -0,0 +1,41 @@ +SUBDIRS = mock . +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_ASCII_CAST -DQT_NO_COMPAT \ + $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/libkopete/private -I$(top_srcdir)/kopete/libkopete/private -I$(top_srcdir)/kopete/libkopete/tests/mock $(all_includes) -DSRCDIR=\"$(top_srcdir)/kopete/libkopete/tests\" +METASOURCES = AUTO + +check_LTLIBRARIES = kunittest_kopetemessage_test.la kunittest_kopetepropertiestest.la kunittest_kopetecontactlist_test.la +noinst_LTLIBRARIES = kunittest_kopeteemoticontest.la + +check_PROGRAMS = kopetewallettest_program kopetepasswordtest_program + +kunittest_kopetepropertiestest_la_SOURCES = kopetepropertiestest.cpp ../kopeteproperties.cpp +kunittest_kopetepropertiestest_la_LIBADD = -lkunittest ../libkopete.la +kunittest_kopetepropertiestest_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + +kunittest_kopeteemoticontest_la_SOURCES = kopeteemoticontest.cpp +kunittest_kopeteemoticontest_la_LIBADD = -lkunittest ../libkopete.la +kunittest_kopeteemoticontest_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + +kunittest_kopetemessage_test_la_SOURCES = kopetemessage_test.cpp +kunittest_kopetemessage_test_la_LIBADD = -lkunittest mock/libkopete_mock.la +kunittest_kopetemessage_test_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + +kopetewallettest_program_SOURCES = kopetewallettest_program.cpp +kopetewallettest_program_LDFLAGS = -no-undefined $(all_libraries) $(KDE_RPATH) +kopetewallettest_program_LDADD = ../libkopete.la + +kopetepasswordtest_program_SOURCES = kopetepasswordtest_program.cpp +kopetepasswordtest_program_LDFLAGS = -no-undefined $(all_libraries) $(KDE_RPATH) +kopetepasswordtest_program_LDADD = ../libkopete.la + +kunittest_kopetecontactlist_test_la_SOURCES = kopetecontactlist_test.cpp +kunittest_kopetecontactlist_test_la_LIBADD = -lkunittest mock/libkopete_mock.la +kunittest_kopetecontactlist_test_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + +noinst_HEADERS = kopetepropertiestest.h kopeteemoticontest.h + +check-local: + kunittestmodrunner +guicheck: + kunittestmod $(PWD) + diff --git a/kopete/libkopete/tests/README b/kopete/libkopete/tests/README new file mode 100644 index 00000000..ead9fbdc --- /dev/null +++ b/kopete/libkopete/tests/README @@ -0,0 +1,47 @@ +LibKopete Unit Tests +==================== + +KopeteSuite: +-------------- +Emoticon Test +Link Test +Property Test + +Test Programs: +-------------- +Password Test Program +Wallet Test Program + + +HOWTO Run +========= + +You can use the console or the GUI version: + + $ make guicheck + $ make check + +The 'silent' switch in make is useful to reduce output: + + $ make check -s + + +Tricks +====== + +Accessing private data?, you should not. We will kill you. +If it is really required, do something like: + + #define private public + #include "kopetemessage.h" + #undef private + +Add a new test quickly: + + $ ./create_test.rb Kopete::ContactList + Creating test for class Kopete::ContactList + kopetecontactlist_test.h and kopetecontactlist_test.cpp writen. + Please add the following to Makefile.am: + kunittest_kopetecontactlist_test_la_SOURCES = kopetecontactlist_test.cpp + kunittest_kopetecontactlist_test_la_LIBADD = -lkunittest ../mock/libkopete_mock.la + kunittest_kopetecontactlist_test_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) diff --git a/kopete/libkopete/tests/create_test.rb b/kopete/libkopete/tests/create_test.rb new file mode 100755 index 00000000..7951bf35 --- /dev/null +++ b/kopete/libkopete/tests/create_test.rb @@ -0,0 +1,56 @@ +#!/usr/bin/ruby +# +# Copyright (c) 2005 by Duncan Mac-Vicar +# +# Kopete (c) 2002-2005 by the Kopete developers +# +# ************************************************************************* +# * * +# * 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. * +# * * +# ************************************************************************* + +className = ARGV[0] + +if className.nil? + puts "Need a class name" + exit +end + +puts "Creating test for class #{className}" + +hBase = "template_test.h" +cppBase = "template_test.cpp" + +fileH = File.new(hBase).read +fileCpp = File.new(cppBase).read + +fileH.gsub!(/TEMPLATE/, className.upcase.gsub(/::/,"")) +fileH.gsub!(/Template/, className.gsub(/::/,"")) +fileH.gsub!(/some requirement/, className + " class.") + +fileCpp.gsub!(/TEMPLATE/, className.upcase.gsub(/::/,"")) +fileCpp.gsub!(/template/, className.downcase.gsub(/::/,"")) +fileCpp.gsub!(/Template/, className.gsub(/::/,"")) +fileCpp.gsub!(/some requirement/, className + " class.") + +makefileAm = "kunittest_template_test_la_SOURCES = template_test.cpp\nkunittest_template_test_la_LIBADD = -lkunittest ../mock/libkopete_mock.la\nkunittest_template_test_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries)\n" +makefileAm.gsub!(/template/, className.downcase.gsub(/::/,"")) + +hNew = hBase.gsub(/template/, className.downcase.gsub(/::/,"")) +cppNew = cppBase.gsub(/template/, className.downcase.gsub(/::/,"")) + +hOut = File.new(hNew, "w") +cppOut = File.new(cppNew, "w") + +hOut.write(fileH) +cppOut.write(fileCpp) + +puts "#{hNew} and #{cppNew} writen." + +puts "Please add the following to Makefile.am:" +puts makefileAm + diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-1.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-1.input new file mode 100644 index 00000000..795d3c7b --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-1.input @@ -0,0 +1 @@ +:)) \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-1.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-1.output new file mode 100644 index 00000000..795d3c7b --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-1.output @@ -0,0 +1 @@ +:)) \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-10.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-10.input new file mode 100644 index 00000000..6ddd0c7f --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-10.input @@ -0,0 +1 @@ +:Ptesting:P \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-10.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-10.output new file mode 100644 index 00000000..6ddd0c7f --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-10.output @@ -0,0 +1 @@ +:Ptesting:P \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-2.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-2.input new file mode 100644 index 00000000..2571b163 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-2.input @@ -0,0 +1 @@ +In a sentence:practical example \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-2.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-2.output new file mode 100644 index 00000000..2571b163 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-2.output @@ -0,0 +1 @@ +In a sentence:practical example \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-3.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-3.input new file mode 100644 index 00000000..2319ced9 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-3.input @@ -0,0 +1 @@ +Bla ( ) \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-3.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-3.output new file mode 100644 index 00000000..2319ced9 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-3.output @@ -0,0 +1 @@ +Bla ( ) \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-4.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-4.input new file mode 100644 index 00000000..f5d88878 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-4.input @@ -0,0 +1 @@ +:D and :-D are not the same as :d and :-d \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-4.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-4.output new file mode 100644 index 00000000..0d94eb97 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-4.output @@ -0,0 +1 @@ + and are not the same as :d and :-d \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-5.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-5.input new file mode 100644 index 00000000..5b39691b --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-5.input @@ -0,0 +1 @@ +4d:D>:)F:/>:-(:Pu:d9 \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-5.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-5.output new file mode 100644 index 00000000..5b39691b --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-5.output @@ -0,0 +1 @@ +4d:D>:)F:/>:-(:Pu:d9 \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-6.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-6.input new file mode 100644 index 00000000..379e01a1 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-6.input @@ -0,0 +1 @@ +<::pvar:: test=1> \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-6.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-6.output new file mode 100644 index 00000000..379e01a1 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-6.output @@ -0,0 +1 @@ +<::pvar:: test=1> \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-7.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-7.input new file mode 100644 index 00000000..d6e7e6c0 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-7.input @@ -0,0 +1 @@ +a non-breaking space ( ) character \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-7.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-7.output new file mode 100644 index 00000000..d6e7e6c0 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-7.output @@ -0,0 +1 @@ +a non-breaking space ( ) character \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-8.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-8.input new file mode 100644 index 00000000..a3734027 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-8.input @@ -0,0 +1 @@ +-+-[-:-(-:-)-:-]-+- \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-8.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-8.output new file mode 100644 index 00000000..a3734027 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-8.output @@ -0,0 +1 @@ +-+-[-:-(-:-)-:-]-+- \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-9.input b/kopete/libkopete/tests/emoticon-parser-testcases/broken-9.input new file mode 100644 index 00000000..538c5b0b --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-9.input @@ -0,0 +1 @@ +::shrugs:: \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/broken-9.output b/kopete/libkopete/tests/emoticon-parser-testcases/broken-9.output new file mode 100644 index 00000000..538c5b0b --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/broken-9.output @@ -0,0 +1 @@ +::shrugs:: \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-1.input b/kopete/libkopete/tests/emoticon-parser-testcases/working-1.input new file mode 100644 index 00000000..a5440d64 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-1.input @@ -0,0 +1 @@ +:):) \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-1.output b/kopete/libkopete/tests/emoticon-parser-testcases/working-1.output new file mode 100644 index 00000000..a7c018d4 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-1.output @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-2.input b/kopete/libkopete/tests/emoticon-parser-testcases/working-2.input new file mode 100644 index 00000000..223ce5be --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-2.input @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-2.output b/kopete/libkopete/tests/emoticon-parser-testcases/working-2.output new file mode 100644 index 00000000..223ce5be --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-2.output @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-3.input b/kopete/libkopete/tests/emoticon-parser-testcases/working-3.input new file mode 100644 index 00000000..d685c091 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-3.input @@ -0,0 +1 @@ +End of sentence:p \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-3.output b/kopete/libkopete/tests/emoticon-parser-testcases/working-3.output new file mode 100644 index 00000000..013515be --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-3.output @@ -0,0 +1 @@ +End of sentence \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-4.input b/kopete/libkopete/tests/emoticon-parser-testcases/working-4.input new file mode 100644 index 00000000..093690c4 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-4.input @@ -0,0 +1 @@ +http://www.kde.org \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-4.output b/kopete/libkopete/tests/emoticon-parser-testcases/working-4.output new file mode 100644 index 00000000..093690c4 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-4.output @@ -0,0 +1 @@ +http://www.kde.org \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-5.input b/kopete/libkopete/tests/emoticon-parser-testcases/working-5.input new file mode 100644 index 00000000..1e3caf28 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-5.input @@ -0,0 +1 @@ +>:-) \ No newline at end of file diff --git a/kopete/libkopete/tests/emoticon-parser-testcases/working-5.output b/kopete/libkopete/tests/emoticon-parser-testcases/working-5.output new file mode 100644 index 00000000..3b1d4c31 --- /dev/null +++ b/kopete/libkopete/tests/emoticon-parser-testcases/working-5.output @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kopete/libkopete/tests/kopetecontactlist_test.cpp b/kopete/libkopete/tests/kopetecontactlist_test.cpp new file mode 100644 index 00000000..001f3f0d --- /dev/null +++ b/kopete/libkopete/tests/kopetecontactlist_test.cpp @@ -0,0 +1,55 @@ +/* + Tests for Kopete::ContactList class. + + Copyright (c) 2005 by Duncan Mac-Vicar + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include "kopetecontactlist_test.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE( kunittest_kopetecontactlist_test, "KopeteSuite"); +KUNITTEST_MODULE_REGISTER_TESTER( KopeteContactList_Test ); + +void KopeteContactList_Test::allTests() +{ + testSomething(); +} + +void KopeteContactList_Test::testSomething() +{ + // change user data dir to avoid messing with user's .kde dir + setenv( "KDEHOME", QFile::encodeName( QDir::homeDirPath() + "/.kopete-unittest" ), true ); + + QString filename = locateLocal( "appdata", QString::fromLatin1( "contactlist.xml" ) ); + if( ! filename.isEmpty() ) + { + // previous test run, delete the previous contact list + bool removed = QFile::remove(filename); + // if we cant remove the file, abort test + if (!removed) + return; + } + + int result = 1; + int expected = 1; + // result should be the expected one + CHECK(result, expected); +} + + diff --git a/kopete/libkopete/tests/kopetecontactlist_test.h b/kopete/libkopete/tests/kopetecontactlist_test.h new file mode 100644 index 00000000..faab1e48 --- /dev/null +++ b/kopete/libkopete/tests/kopetecontactlist_test.h @@ -0,0 +1,35 @@ +/* + Tests for Kopete::ContactList class. + + Copyright (c) 2005 by Duncan Mac-Vicar + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETECONTACTLIST_TEST_H +#define KOPETECONTACTLIST_TEST_H + +#include + +// change to SlotTester when it works +class KopeteContactList_Test : public KUnitTest::Tester +{ +public: + void allTests(); +public slots: + void testSomething(); +private: + +}; + +#endif + diff --git a/kopete/libkopete/tests/kopeteemoticontest.cpp b/kopete/libkopete/tests/kopeteemoticontest.cpp new file mode 100644 index 00000000..e9a81c1d --- /dev/null +++ b/kopete/libkopete/tests/kopeteemoticontest.cpp @@ -0,0 +1,132 @@ +/* + Tests for Kopete::Message::parseEmoticons + + Copyright (c) 2004 by Richard Smith + Copyright (c) 2005 by Duncan Mac-Vicar + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "kopeteemoticontest.h" +#include "kopetemessage.h" +#include "kopeteemoticons.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE( kunittest_kopeteemoticontest, "KopeteSuite"); +KUNITTEST_MODULE_REGISTER_TESTER( KopeteEmoticonTest ); + +/* + There are three sets of tests, the Kopete 0.7 baseline with tests that were + working properly in Kopete 0.7.x. When these fail it's a real regression. + + The second set are those known to work in the current codebase. + The last set is the set with tests that are known to fail right now. + + the name convention is working|broken-number.input|output +*/ + + +void KopeteEmoticonTest::allTests() +{ + // change user data dir to avoid messing with user's .kde dir + setenv( "KDEHOME", QFile::encodeName( QDir::homeDirPath() + "/.kopete-unittest" ), true ); + + //KApplication::disableAutoDcopRegistration(); + //KApplication app; + + testEmoticonParser(); +} + +void KopeteEmoticonTest::testEmoticonParser() +{ + Kopete::Emoticons emo("Default"); + QString basePath = QString::fromLatin1( SRCDIR ) + QString::fromLatin1("/emoticon-parser-testcases"); + QDir testCasesDir(basePath); + + QStringList inputFileNames = testCasesDir.entryList("*.input"); + for ( QStringList::ConstIterator it = inputFileNames.begin(); it != inputFileNames.end(); ++it) + { + QString fileName = *it; + kdDebug() << "testcase: " << fileName << endl; + QString outputFileName = fileName; + outputFileName.replace("input","output"); + // open the input file + QFile inputFile(basePath + QString::fromLatin1("/") + fileName); + QFile expectedFile(basePath + QString::fromLatin1("/") + outputFileName); + // check if the expected output file exists + // if it doesn't, skip the testcase + if ( ! expectedFile.exists() ) + { + SKIP("Warning! expected output for testcase "+ *it + " not found. Skiping testcase"); + continue; + } + if ( inputFile.open( IO_ReadOnly ) && expectedFile.open( IO_ReadOnly )) + { + QTextStream inputStream(&inputFile); + QTextStream expectedStream(&expectedFile); + QString inputData; + QString expectedData; + inputData = inputStream.read(); + expectedData = expectedStream.read(); + + inputFile.close(); + expectedFile.close(); + + QString path = KGlobal::dirs()->findResource( "emoticons", "Default/smile.png" ).replace( "smile.png", QString::null ); + + Kopete::Emoticons::self(); + QString result = emo.parse( inputData ).replace( path, QString::null ); + + // HACK to know the test case we applied, concatenate testcase name to both + // input and expected string. WIll remove when I can add some sort of metadata + // to a CHECK so debug its origin testcase + result = fileName + QString::fromLatin1(": ") + result; + expectedData = fileName + QString::fromLatin1(": ") + expectedData; + // if the test case begins with broken, we expect it to fail, then use XFAIL + // otherwise use CHECK + if ( fileName.section("-", 0, 0) == QString::fromLatin1("broken") ) + { + kdDebug() << "checking known-broken testcase: " << fileName << endl; + XFAIL(result, expectedData); + } + else + { + kdDebug() << "checking known-working testcase: " << fileName << endl; + CHECK(result, expectedData); + } + } + else + { + SKIP("Warning! can't open testcase files for "+ *it + ". Skiping testcase"); + continue; + } + } + +} + + + + + \ No newline at end of file diff --git a/kopete/libkopete/tests/kopeteemoticontest.h b/kopete/libkopete/tests/kopeteemoticontest.h new file mode 100644 index 00000000..a885b2c4 --- /dev/null +++ b/kopete/libkopete/tests/kopeteemoticontest.h @@ -0,0 +1,39 @@ +/* + Tests for Kopete::Message::parseEmoticons + + Copyright (c) 2004 by Richard Smith + Copyright (c) 2005 by Duncan Mac-Vicar + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_EMOTICON_TEST_H +#define KOPETE_EMOTICON_TEST_H + +#include + +// change to SlotTester when it works +class KopeteEmoticonTest : public KUnitTest::Tester +{ +public: + //KopeteLinkTest(); + //~KopeteLinkTest(); + void allTests(); +public slots: + void testEmoticonParser(); +private: + +}; + +#endif + + diff --git a/kopete/libkopete/tests/kopetemessage.xsd b/kopete/libkopete/tests/kopetemessage.xsd new file mode 100644 index 00000000..69f99d20 --- /dev/null +++ b/kopete/libkopete/tests/kopetemessage.xsd @@ -0,0 +1,180 @@ + + + + + + + + ... it will instruct the Kopete XSL engine that you want the entire contents + of the chat window to be re-drawn each time a new message is appended. This + is not the normal procedure, and is only required for special situations + (see the Adium style for an example). + + TransformAllMessages is the only flag currently defined. + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kopete/libkopete/tests/kopetemessage_test.cpp b/kopete/libkopete/tests/kopetemessage_test.cpp new file mode 100644 index 00000000..1ca57123 --- /dev/null +++ b/kopete/libkopete/tests/kopetemessage_test.cpp @@ -0,0 +1,324 @@ +/* + Tests for Kopete::Message + + Copyright (c) 2005 by Tommi Rantala + Copyright (c) 2005 by Duncan Mac-Vicar Prett + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopetemessage_test.h" +#include "kopeteaccount_mock.h" +#include "kopeteprotocol_mock.h" +#include "kopetecontact_mock.h" +#include "kopetemetacontact_mock.h" +#include "kopeteaccount_mock.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE( kunittest_kopetemessage_test, "KopeteSuite"); +KUNITTEST_MODULE_REGISTER_TESTER( KopeteMessage_Test ); + +/* + There are four sets of tests: for each of plain text and html, we have those + known to work in the current codebase, and those known to fail right now. + + the name convention is working|broken-plaintext|html-number.input|output +*/ + +KopeteMessage_Test::KopeteMessage_Test() +{ + // change user data dir to avoid messing with user's .kde dir + setenv( "KDEHOME", QFile::encodeName( QDir::homeDirPath() + "/.kopete-unittest" ), true ); + + // create fake objects needed to build a reasonable testeable message + m_protocol = new Kopete::Test::Mock::Protocol( new KInstance(QCString("test-kopete-message")), 0L, "test-kopete-message"); + m_account = new Kopete::Test::Mock::Account(m_protocol, "testaccount"); + m_metaContactMyself = new Kopete::Test::Mock::MetaContact(); + m_metaContactOther = new Kopete::Test::Mock::MetaContact(); + m_contactFrom = new Kopete::Test::Mock::Contact(m_account, QString::fromLatin1("test-myself"), m_metaContactMyself, QString::null); + m_contactTo = new Kopete::Test::Mock::Contact(m_account, QString::fromLatin1("test-dest"), m_metaContactOther, QString::null); + m_message = new Kopete::Message( m_contactFrom, m_contactTo, QString::null, Kopete::Message::Outbound, Kopete::Message::PlainText); +} + +void KopeteMessage_Test::allTests() +{ + KApplication::disableAutoDcopRegistration(); + //KCmdLineArgs::init(argc,argv,"testkopetemessage", 0, 0, 0, 0); + + // At least Kopete::Message::asXML() seems to require that a QApplication + // is created. Running the console version doesn't create it, but the GUI + // version does. + + if (!kapp) + new KApplication(); + + testPrimitives(); + testLinkParser(); +} + +void KopeteMessage_Test::testPrimitives() +{ + /********************************************** + * from(), to() + *********************************************/ + + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foobar", Kopete::Message::Inbound, Kopete::Message::PlainText); + Q_ASSERT(msg.from()); + Q_ASSERT(!msg.to().isEmpty()); + } + + /********************************************** + * Direction + *********************************************/ + + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foobar", Kopete::Message::Inbound, Kopete::Message::PlainText); + CHECK(Kopete::Message::Inbound, msg.direction()); + } + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foobar", Kopete::Message::Outbound, Kopete::Message::RichText); + CHECK(Kopete::Message::Outbound, msg.direction()); + } + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foobar", Kopete::Message::Internal, Kopete::Message::RichText); + CHECK(Kopete::Message::Internal, msg.direction()); + } + + /********************************************** + * Message Format + *********************************************/ + + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foobar", Kopete::Message::Inbound, Kopete::Message::PlainText); + CHECK(Kopete::Message::PlainText, msg.format()); + } + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foobar", Kopete::Message::Inbound, Kopete::Message::RichText); + CHECK(Kopete::Message::RichText, msg.format()); + } + { + QString m = "foobar"; + Kopete::Message msg( m_contactFrom, m_contactTo, m, Kopete::Message::Inbound, Kopete::Message::RichText); + + msg.setBody(m, Kopete::Message::PlainText); + CHECK(Kopete::Message::PlainText, msg.format()); + + msg.setBody(m, Kopete::Message::RichText); + CHECK(Kopete::Message::RichText, msg.format()); + + msg.setBody(m, Kopete::Message::ParsedHTML); + CHECK(Kopete::Message::ParsedHTML, msg.format()); + + msg.setBody(m, Kopete::Message::Crypted); + CHECK(Kopete::Message::Crypted, msg.format()); + } + + + /********************************************** + * setBody() + *********************************************/ + + { + QString m = "foobar"; + Kopete::Message msg( m_contactFrom, m_contactTo, m, Kopete::Message::Inbound, Kopete::Message::RichText); + + msg.setBody("NEW", Kopete::Message::PlainText); + CHECK(QString("NEW"), msg.plainBody()); + + msg.setBody("NEW_NEW", Kopete::Message::RichText); + CHECK(QString("NEW_NEW"), msg.plainBody()); + } + { + QString m = "foobar"; + Kopete::Message msg( m_contactFrom, m_contactTo, m, Kopete::Message::Inbound, Kopete::Message::PlainText); + + msg.setBody("NEW", Kopete::Message::PlainText); + CHECK(QString("NEW"), msg.plainBody()); + + msg.setBody("NEW_NEW", Kopete::Message::RichText); + CHECK(QString("NEW_NEW"), msg.plainBody()); + } + { + QString m = " HELLO WORLD "; + Kopete::Message msg( m_contactFrom, m_contactTo, m, Kopete::Message::Inbound, Kopete::Message::PlainText); + CHECK(m, msg.plainBody()); + + msg.setBody(" SIMPLE", Kopete::Message::PlainText); + CHECK(msg.plainBody(), QString(" SIMPLE") ); + CHECK(msg.escapedBody(), QString("<simple> SIMPLE") ); + + msg.setBody("SIMPLE", Kopete::Message::RichText); + CHECK(msg.plainBody(), QString("SIMPLE") ); + CHECK(msg.escapedBody(), QString("SIMPLE") ); + + CHECK(Kopete::Message::unescape( QString( "SIMPLE" ) ), QString("SIMPLE") ); + CHECK(Kopete::Message::unescape( QString( "Foo " ) ), QString("Foo ") ); + CHECK(Kopete::Message::unescape( QString( "Foo " ) ), QString("Foo Bar") ); + + msg.setBody(m, Kopete::Message::RichText); + + // FIXME: Should setBody() also strip extra white space? + //CHECK(msg.plainBody(), QString("HELLO WORLD")); + //CHECK(msg.escapedBody(), QString("HELLO WORLD")); + + CHECK(msg.escapedBody(), QString("   HELLO WORLD   ")); + CHECK(msg.plainBody(), QString(" HELLO WORLD ")); + CHECK(msg.plainBody().stripWhiteSpace(), QString("HELLO WORLD")); + CHECK(msg.escapedBody().stripWhiteSpace(), QString("  HELLO WORLD  ")); + } + { + Kopete::Message msg( m_contactFrom, m_contactTo, "foo", Kopete::Message::Inbound, Kopete::Message::PlainText); + + msg.setBody("

    foo", Kopete::Message::RichText); + CHECK(msg.escapedBody(), QString("foo")); + + msg.setBody("

    foo

    ", Kopete::Message::RichText); + CHECK(msg.escapedBody(), QString("foo")); + + msg.setBody("\n

    foo

    \n
    ", Kopete::Message::RichText); + CHECK(msg.escapedBody(), QString("foo
    ")); + } + + /********************************************** + * Copy constructor + *********************************************/ + + { + Kopete::Message msg1(m_contactFrom, m_contactTo, "foo", Kopete::Message::Inbound, Kopete::Message::RichText); + Kopete::Message msg2(msg1); + + CHECK(msg1.plainBody(), msg2.plainBody()); + CHECK(msg1.escapedBody(), msg2.escapedBody()); + + msg1.setBody("NEW", Kopete::Message::PlainText); + CHECK(msg1.plainBody(), QString("NEW")); + CHECK(msg2.plainBody(), QString("foo")); + } + + /********************************************** + * operator= + *********************************************/ + + { + Kopete::Message msg1(m_contactFrom, m_contactTo, "foo", Kopete::Message::Inbound, Kopete::Message::RichText); + { + Kopete::Message msg2; + + CHECK(msg2.plainBody(), QString::null); + + msg2 = msg1; + + CHECK(msg1.plainBody(), msg2.plainBody()); + CHECK(msg1.escapedBody(), msg2.escapedBody()); + + msg1.setBody("NEW", Kopete::Message::PlainText); + CHECK(msg1.plainBody(), QString("NEW")); + CHECK(msg2.plainBody(), QString("foo")); + } + CHECK(msg1.plainBody(), QString("NEW")); + + msg1 = msg1; + CHECK(msg1.plainBody(), QString("NEW")); + } +} + +void KopeteMessage_Test::setup() +{ +} + +void KopeteMessage_Test::testLinkParser() +{ + QString basePath = QString::fromLatin1( SRCDIR ) + QString::fromLatin1("/link-parser-testcases"); + QDir testCasesDir(basePath); + + QStringList inputFileNames = testCasesDir.entryList("*.input"); + for ( QStringList::ConstIterator it = inputFileNames.begin(); it != inputFileNames.end(); ++it) + { + QString fileName = *it; + QString outputFileName = fileName; + outputFileName.replace("input","output"); + // open the input file + QFile inputFile(basePath + QString::fromLatin1("/") + fileName); + QFile expectedFile(basePath + QString::fromLatin1("/") + outputFileName); + // check if the expected output file exists + // if it doesn't, skip the testcase + if ( ! expectedFile.exists() ) + { + SKIP("Warning! expected output for testcase "+ *it + " not found. Skiping testcase"); + continue; + } + if ( inputFile.open( IO_ReadOnly ) && expectedFile.open( IO_ReadOnly )) + { + QTextStream inputStream(&inputFile); + QTextStream expectedStream(&expectedFile); + QString inputData; + QString expectedData; + inputData = inputStream.read(); + expectedData = expectedStream.read(); + + inputFile.close(); + expectedFile.close(); + + // use a concrete url + inputData.replace( "$URL","http://www.kde.org" ); + expectedData.replace( "$URL","http://www.kde.org" ); + + // set message format for parsing according to textcase filename convention + Kopete::Message::MessageFormat format; + if ( fileName.section("-", 1, 1) == QString::fromLatin1("plaintext") ) + format = Kopete::Message::PlainText; + else + format = Kopete::Message::RichText; + + QString result = Kopete::Message::parseLinks( inputData, format ); + + // HACK to know the test case we applied, concatenate testcase name to both + // input and expected string. WIll remove when I can add some sort of metadata + // to a CHECK so debug its origin testcase + result = fileName + QString::fromLatin1(": ") + result; + expectedData = fileName + QString::fromLatin1(": ") + expectedData; + // if the test case begins with broken, we expect it to fail, then use XFAIL + // otherwise use CHECK + if ( fileName.section("-", 0, 0) == QString::fromLatin1("broken") ) + { + //kdDebug() << "checking known-broken testcase: " << fileName << endl; + XFAIL(result, expectedData); + } + else + { + //kdDebug() << "checking known-working testcase: " << fileName << endl; + CHECK(result, expectedData); + } + } + else + { + SKIP("Warning! can't open testcase files for "+ *it + ". Skiping testcase"); + continue; + } + } +} + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/tests/kopetemessage_test.h b/kopete/libkopete/tests/kopetemessage_test.h new file mode 100644 index 00000000..52d09fb8 --- /dev/null +++ b/kopete/libkopete/tests/kopetemessage_test.h @@ -0,0 +1,56 @@ +/* + Tests for Kopete::Message + + Copyright (c) 2005 by Duncan Mac-Vicar + Copyright (c) 2005 by Tommi Rantala + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEMESSAGE_TEST_H +#define KOPETEMESSAGE_TEST_H + +#include + +#define private public +#include "kopetemessage.h" +#undef private + +class Kopete::Protocol; +class Kopete::Account; +class Kopete::MetaContact; +class Kopete::Contact; + +// change to SlotTester when it works +class KopeteMessage_Test : public KUnitTest::Tester +{ +public: + KopeteMessage_Test(); + void allTests(); + +public slots: + void testPrimitives(); + void testLinkParser(); + +private: + void setup(); + Kopete::Message *m_message; + Kopete::Protocol *m_protocol; + Kopete::Account *m_account; + Kopete::MetaContact *m_metaContactMyself; + Kopete::MetaContact *m_metaContactOther; + Kopete::Contact *m_contactFrom; + Kopete::Contact *m_contactTo; +}; + +#endif + diff --git a/kopete/libkopete/tests/kopetepasswordtest_program.cpp b/kopete/libkopete/tests/kopetepasswordtest_program.cpp new file mode 100644 index 00000000..a1f3a50e --- /dev/null +++ b/kopete/libkopete/tests/kopetepasswordtest_program.cpp @@ -0,0 +1,132 @@ +/* + Tests for the Kopete::Password class + + Copyright (c) 2003 by Richard Smith + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopetepasswordtest_program.h" +#include "kopetepassword.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static QTextStream _out( stdout, IO_WriteOnly ); + +static KCmdLineOptions opts[] = +{ + { "id ", I18N_NOOP("Config group to store password in"), "TestAccount" }, + { "set ", I18N_NOOP("Set password to new"), 0 }, + { "error", I18N_NOOP("Claim password was erroneous"), 0 }, + { "prompt ", I18N_NOOP("Password prompt"), "Enter a password" }, + { "image ", I18N_NOOP("Image to display in password dialog"), 0 }, + KCmdLineLastOption +}; + +using namespace Kopete; + +QString retrieve( Password &pwd, const QPixmap &image, const QString &prompt ) +{ + PasswordRetriever r; + pwd.request( &r, SLOT( gotPassword( const QString & ) ), image, prompt ); + QTimer tmr; + r.connect( &tmr, SIGNAL( timeout() ), SLOT( timer() ) ); + tmr.start( 1000 ); + qApp->exec(); + return r.password; +} + +void PasswordRetriever::gotPassword( const QString &pass ) +{ + password = pass; + qApp->quit(); +} + +void PasswordRetriever::timer() +{ + _out << "." << flush; +} + +int main( int argc, char *argv[] ) +{ + KAboutData aboutData( "kopetepasswordtest", "kopetepasswordtest", "version" ); + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineArgs::addCmdLineOptions( opts ); + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + KApplication app( "kopetepasswordtest" ); + + bool setPassword = args->isSet("set"); + QString newPwd = args->getOption("set"); + QString passwordId = args->getOption("id"); + bool error = args->isSet("error"); + QString prompt = args->getOption("prompt"); + QPixmap image = QString(args->getOption("image")); + + _out << (image.isNull() ? "image is null" : "image is valid") << endl; + + Password pwd( passwordId, 0, false ); + pwd.setWrong( error ); + + _out << "Cached value is null: " << pwd.cachedValue().isNull() << endl; + + QString pass = retrieve( pwd, image, prompt ); + + if ( !pass.isNull() ) + _out << "Read password: " << pass << endl; + else + _out << "Could not read a password" << endl; + + _out << "Cached value: " << (pwd.cachedValue().isNull() ? "null" : pwd.cachedValue()) << endl; + + if ( setPassword ) + { + if ( newPwd.isEmpty() ) + { + _out << "Clearing password" << endl; + newPwd = QString::null; + } + else + { + _out << "Setting password to " << newPwd << endl; + } + pwd.set( newPwd ); + } + + // without this, setting passwords will fail since they're + // set asynchronously. + QTimer::singleShot( 0, &app, SLOT( deref() ) ); + app.exec(); + + if ( setPassword ) + { + pass = retrieve( pwd, image, i18n("Hopefully this popped up because you set the password to the empty string.") ); + if( pass == newPwd ) + _out << "Password successfully set." << endl; + else + _out << "Failed: password ended up as " << pass << endl; + } + + return 0; +} + +#include "kopetepasswordtest_program.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/tests/kopetepasswordtest_program.h b/kopete/libkopete/tests/kopetepasswordtest_program.h new file mode 100644 index 00000000..507da2a1 --- /dev/null +++ b/kopete/libkopete/tests/kopetepasswordtest_program.h @@ -0,0 +1,16 @@ +#ifndef KOPETEPASSWORDTEST_H +#define KOPETEPASSWORDTEST_H + +#include + +class PasswordRetriever : public QObject +{ + Q_OBJECT +public: + QString password; +public slots: + void timer(); + void gotPassword( const QString & ); +}; + +#endif diff --git a/kopete/libkopete/tests/kopetepropertiestest.cpp b/kopete/libkopete/tests/kopetepropertiestest.cpp new file mode 100644 index 00000000..1e60c77c --- /dev/null +++ b/kopete/libkopete/tests/kopetepropertiestest.cpp @@ -0,0 +1,59 @@ +/* + Tests for Kopete Properties + + Copyright (c) 2004 by Richard Smith + Copyright (c) 2005 by Duncan Mac-Vicar + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include + +#include "kopeteproperties.h" + +#include +#include + +#include +#include +#include +#include + +#include "kopetepropertiestest.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE( kunittest_kopetepropertiestest, "KopeteSuite"); +KUNITTEST_MODULE_REGISTER_TESTER( KopetePropertiesTest ); + +using namespace Kopete::Properties; + +static QTextStream _out( stdout, IO_WriteOnly ); + +class PropertyHost : public WithProperties {}; + +class FooProperty : public SimpleDataProperty +{ +public: + const char *name() const { return "foo"; } +} fooProperty; + +void KopetePropertiesTest::allTests() +{ + PropertyHost myPropertyHost; + CHECK( myPropertyHost.property(fooProperty).isNull(), true); + myPropertyHost.setProperty( fooProperty, QString::fromLatin1("Foo!") ); + CHECK( myPropertyHost.property(fooProperty), QString::fromLatin1("Foo!") ); +} + + + \ No newline at end of file diff --git a/kopete/libkopete/tests/kopetepropertiestest.h b/kopete/libkopete/tests/kopetepropertiestest.h new file mode 100644 index 00000000..c997dd80 --- /dev/null +++ b/kopete/libkopete/tests/kopetepropertiestest.h @@ -0,0 +1,36 @@ +/* + Tests for Kopete Properties + + Copyright (c) 2004 by Richard Smith + Copyright (c) 2005 by Duncan Mac-Vicar + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_PROPERTIES_TEST_H +#define KOPETE_PROPERTIES_TEST_H + +#include + +// change to SlotTester when it works +class KopetePropertiesTest : public KUnitTest::Tester +{ +public: + void allTests(); +public slots: +private: + +}; + +#endif + + diff --git a/kopete/libkopete/tests/kopetewallettest_program.cpp b/kopete/libkopete/tests/kopetewallettest_program.cpp new file mode 100644 index 00000000..29de1edc --- /dev/null +++ b/kopete/libkopete/tests/kopetewallettest_program.cpp @@ -0,0 +1,98 @@ +/* + Tests for the wallet manager + + Copyright (c) 2003 by Richard Smith + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopetewalletmanager.h" +#include "kopetewallettest_program.h" + +static QTextStream _out( stdout, IO_WriteOnly ); + +void closeWallet() +{ + Kopete::WalletManager::self()->closeWallet(); +} + +void delay() +{ + QTimer::singleShot( 3000, qApp, SLOT( quit() ) ); + qApp->exec(); +} + +void openWalletAsync() +{ + WalletReciever *r = new WalletReciever; + _out << "[ASYNC] About to open wallet, receiver: " << r << endl; + Kopete::WalletManager::self()->openWallet( r, SLOT( gotWallet( KWallet::Wallet* ) ) ); +} + +void WalletReciever::gotWallet( KWallet::Wallet *w ) +{ + _out << "[ASYNC] Received wallet pointer: " << w << " for receiver: " << this << endl; +} + +void WalletReciever::timer() +{ + _out << "Timer..." << endl; +} + +int main( int argc, char *argv[] ) +{ + KAboutData aboutData( "kopetewallettest", "kopetewallettest", "version" ); + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineOptions opts[] = { {"+action",0,0}, KCmdLineLastOption }; + KCmdLineArgs::addCmdLineOptions( opts ); + KApplication app( "kopetewallettest" ); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + // must register with DCOP or async callbacks will fail + _out << "DCOP registration returned " << app.dcopClient()->registerAs(app.name()) << endl; + + for( int i = 0; i < args->count(); ++i ) + { + QString arg = args->arg( i ); + _out << "Processing " << arg << endl; + if( arg == QString::fromLatin1( "open" ) ) openWalletAsync(); + if( arg == QString::fromLatin1( "close" ) ) closeWallet(); + if( arg == QString::fromLatin1( "delay" ) ) delay(); + _out << "Done." << endl; + } + + WalletReciever *r = new WalletReciever; + + QTimer timer; + r->connect( &timer, SIGNAL( timeout() ), SLOT( timer() ) ); + timer.start( 1000 ); + + _out << "About to start 30 second event loop" << endl; + QTimer::singleShot( 30000, qApp, SLOT( quit() ) ); + return qApp->exec(); +} + +#include "kopetewallettest_program.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/tests/kopetewallettest_program.h b/kopete/libkopete/tests/kopetewallettest_program.h new file mode 100644 index 00000000..58bdbb6e --- /dev/null +++ b/kopete/libkopete/tests/kopetewallettest_program.h @@ -0,0 +1,17 @@ +#ifndef KOPETEWALLETTEST_H +#define KOPETEWALLETTEST_H + +#include + +namespace KWallet { class Wallet; } + +class WalletReciever : public QObject +{ + Q_OBJECT +public slots: + void timer(); +private slots: + void gotWallet( KWallet::Wallet *w ); +}; + +#endif diff --git a/kopete/libkopete/tests/link-parser-testcases/broken-html-1.input b/kopete/libkopete/tests/link-parser-testcases/broken-html-1.input new file mode 100644 index 00000000..ecaf4b7e --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/broken-html-1.input @@ -0,0 +1 @@ +
    $URL \ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/broken-html-1.output b/kopete/libkopete/tests/link-parser-testcases/broken-html-1.output new file mode 100644 index 00000000..5bf3f88a --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/broken-html-1.output @@ -0,0 +1 @@ +$URL diff --git a/kopete/libkopete/tests/link-parser-testcases/working-html-1.input b/kopete/libkopete/tests/link-parser-testcases/working-html-1.input new file mode 100644 index 00000000..306ab458 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-html-1.input @@ -0,0 +1 @@ +$URL \ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-html-1.output b/kopete/libkopete/tests/link-parser-testcases/working-html-1.output new file mode 100644 index 00000000..ecaf4b7e --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-html-1.output @@ -0,0 +1 @@ +$URL \ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-html-2.input b/kopete/libkopete/tests/link-parser-testcases/working-html-2.input new file mode 100644 index 00000000..4480dee7 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-html-2.input @@ -0,0 +1 @@ +KDE \ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-html-2.output b/kopete/libkopete/tests/link-parser-testcases/working-html-2.output new file mode 100644 index 00000000..4480dee7 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-html-2.output @@ -0,0 +1 @@ +KDE \ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-plaintext-1.input b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-1.input new file mode 100644 index 00000000..306ab458 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-1.input @@ -0,0 +1 @@ +$URL \ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-plaintext-1.output b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-1.output new file mode 100644 index 00000000..ecaf4b7e --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-1.output @@ -0,0 +1 @@ +$URL \ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-plaintext-2.input b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-2.input new file mode 100644 index 00000000..14e0e606 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-2.input @@ -0,0 +1 @@ +$URL/ \ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-plaintext-2.output b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-2.output new file mode 100644 index 00000000..109c616b --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-2.output @@ -0,0 +1 @@ +$URL/ \ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-plaintext-3.input b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-3.input new file mode 100644 index 00000000..828cd483 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-3.input @@ -0,0 +1 @@ +www.kde.org/ \ No newline at end of file diff --git a/kopete/libkopete/tests/link-parser-testcases/working-plaintext-3.output b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-3.output new file mode 100644 index 00000000..9e898eb0 --- /dev/null +++ b/kopete/libkopete/tests/link-parser-testcases/working-plaintext-3.output @@ -0,0 +1 @@ +www.kde.org/ \ No newline at end of file diff --git a/kopete/libkopete/tests/mock/Makefile.am b/kopete/libkopete/tests/mock/Makefile.am new file mode 100644 index 00000000..b132a2a5 --- /dev/null +++ b/kopete/libkopete/tests/mock/Makefile.am @@ -0,0 +1,14 @@ +METASOURCES = AUTO +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_COMPAT -DQT_NO_CAST_ASCII -DQT_NO_ASCII_CAST \ + $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/libkopete/private $(all_includes) + +noinst_LTLIBRARIES = libkopete_mock.la + +libkopete_mock_la_SOURCES = kopetemessage_mock.cpp kopeteaccount_mock.cpp kopetecontact_mock.cpp kopetemetacontact_mock.cpp kopeteprotocol_mock.cpp + +libkopete_mock_la_LDFLAGS = $(all_libraries) -lkabc +libkopete_mock_la_LIBADD = ../../libkopete.la ../../private/libkopeteprivate.la $(LIB_KHTML) + +noinst_HEADERS = kopetemessage_mock.h kopetecontact_mock.h kopetemetacontact_mock.h kopeteaccount_mock.h kopeteprotocol_mock.h + + diff --git a/kopete/libkopete/tests/mock/kopeteaccount_mock.cpp b/kopete/libkopete/tests/mock/kopeteaccount_mock.cpp new file mode 100644 index 00000000..8a8425bc --- /dev/null +++ b/kopete/libkopete/tests/mock/kopeteaccount_mock.cpp @@ -0,0 +1,61 @@ +/* + Account mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopeteaccount_mock.h" +#include "kopetemetacontact.h" +#include "kopeteaccount_mock.h" + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +Account::Account(Kopete::Protocol *parent, const QString &accountID, const char *name) : Kopete::Account(parent, accountID, name) +{ + +} + +Account::~Account() +{ + +} + +bool Account::createContact( const QString &contactId, Kopete::MetaContact *parentContact ) +{ + return true; +} + +void Account::connect( const Kopete::OnlineStatus& initialStatus) +{ + // do nothing +} + +void Account::disconnect() +{ + // do nothing +} + +void Account::setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason) +{ + // do nothing +} + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete diff --git a/kopete/libkopete/tests/mock/kopeteaccount_mock.h b/kopete/libkopete/tests/mock/kopeteaccount_mock.h new file mode 100644 index 00000000..55ba15cc --- /dev/null +++ b/kopete/libkopete/tests/mock/kopeteaccount_mock.h @@ -0,0 +1,54 @@ +/* + Account mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETEACCOUNT_MOCK_H_ +#define _KOPETEACCOUNT_MOCK_H_ + +#include "kopeteaccount.h" + +class Kopete::Protocol; +class Kopete::OnlineStatus; +class Kopete::MetaContact; + +class QString; + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +class Account : public Kopete::Account +{ +public: + Account(Kopete::Protocol *parent, const QString &accountID, const char *name=0L); + ~Account(); + // pure virtual functions implementation + virtual bool createContact( const QString &contactId, MetaContact *parentContact ); + virtual void connect( const Kopete::OnlineStatus& initialStatus = OnlineStatus() ); + virtual void disconnect(); + virtual void setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason = QString::null ); +}; + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete + + +#endif + diff --git a/kopete/libkopete/tests/mock/kopetecontact_mock.cpp b/kopete/libkopete/tests/mock/kopetecontact_mock.cpp new file mode 100644 index 00000000..19cfa7b0 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopetecontact_mock.cpp @@ -0,0 +1,44 @@ +/* + Contact mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopetecontact_mock.h" + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +Contact::Contact( Kopete::Account *account, const QString &id, Kopete::MetaContact *parent, const QString &icon) : Kopete::Contact( account, id, parent, icon) +{ + +} + +Contact::~Contact() +{ + +} + +Kopete::ChatSession* Contact::manager( CanCreateFlags canCreate) +{ + return 0L; +} + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete \ No newline at end of file diff --git a/kopete/libkopete/tests/mock/kopetecontact_mock.h b/kopete/libkopete/tests/mock/kopetecontact_mock.h new file mode 100644 index 00000000..e445a571 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopetecontact_mock.h @@ -0,0 +1,49 @@ +/* + Contact mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETECONTACT_MOCK_H_ +#define _KOPETECONTACT_MOCK_H_ + +#include "kopetecontact.h" + +class Kopete::MetaContact; +class Kopete::Account; +class Kopete::ChatSession; +class QString; + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +class Contact : public Kopete::Contact +{ +public: + Contact( Kopete::Account *account, const QString &id, Kopete::MetaContact *parent, const QString &icon = QString::null ); + ~Contact(); + virtual Kopete::ChatSession* manager( CanCreateFlags canCreate = CannotCreate ); +}; + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete + + +#endif + diff --git a/kopete/libkopete/tests/mock/kopetemessage_mock.cpp b/kopete/libkopete/tests/mock/kopetemessage_mock.cpp new file mode 100644 index 00000000..a3e543e3 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopetemessage_mock.cpp @@ -0,0 +1,20 @@ +/* + Message mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopetemessage_mock.h" + + diff --git a/kopete/libkopete/tests/mock/kopetemessage_mock.h b/kopete/libkopete/tests/mock/kopetemessage_mock.h new file mode 100644 index 00000000..13c92574 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopetemessage_mock.h @@ -0,0 +1,39 @@ +/* + Message mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETEMESSAGE_MOCK_H_ +#define _KOPETEMESSAGE_MOCK_H_ + +#include "kopetemessage.h" + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +class Message : public Kopete::Message +{ + +}; + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete + +#endif \ No newline at end of file diff --git a/kopete/libkopete/tests/mock/kopetemetacontact_mock.cpp b/kopete/libkopete/tests/mock/kopetemetacontact_mock.cpp new file mode 100644 index 00000000..32f0fe1c --- /dev/null +++ b/kopete/libkopete/tests/mock/kopetemetacontact_mock.cpp @@ -0,0 +1,20 @@ +/* + MetaContact Mock Object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopetemetacontact_mock.h" + + diff --git a/kopete/libkopete/tests/mock/kopetemetacontact_mock.h b/kopete/libkopete/tests/mock/kopetemetacontact_mock.h new file mode 100644 index 00000000..f3311713 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopetemetacontact_mock.h @@ -0,0 +1,41 @@ +/* + MetaContact Mock Object + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETEMETACONTACT_MOCK_H_ +#define _KOPETEMETACONTACT_MOCK_H_ + +#include "kopetemetacontact.h" + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +class MetaContact : public Kopete::MetaContact +{ + +}; + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete + + +#endif + diff --git a/kopete/libkopete/tests/mock/kopeteprotocol_mock.cpp b/kopete/libkopete/tests/mock/kopeteprotocol_mock.cpp new file mode 100644 index 00000000..d3bbd0e2 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopeteprotocol_mock.cpp @@ -0,0 +1,49 @@ +/* + Protocol mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopeteprotocol_mock.h" + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +Protocol::Protocol( KInstance *instance, QObject *parent, const char *name ) : Kopete::Protocol(instance, parent, name) +{ + +} + +Account* Protocol::createNewAccount( const QString &accountId ) +{ + return 0L; +} + +AddContactPage* Protocol::createAddContactWidget( QWidget *parent, Kopete::Account *account ) +{ + return 0L; +} + +KopeteEditAccountWidget* Protocol::createEditAccountWidget( Kopete::Account *account, QWidget *parent ) +{ + return 0L; +} + +} // end ns mock +} // end ns test +} // end ns kopete diff --git a/kopete/libkopete/tests/mock/kopeteprotocol_mock.h b/kopete/libkopete/tests/mock/kopeteprotocol_mock.h new file mode 100644 index 00000000..189f7d79 --- /dev/null +++ b/kopete/libkopete/tests/mock/kopeteprotocol_mock.h @@ -0,0 +1,53 @@ +/* + Protocol mock object class + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef _KOPETEPROTOCOL_MOCK_H_ +#define _KOPETEPROTOCOL_MOCK_H_ + +#include "kopeteprotocol.h" + +class KInstance; +class QObject; + +class KopeteEditAccountWidget; +class AddContactPage; +class KopeteEditAccountWidget; + +namespace Kopete +{ +namespace Test +{ +namespace Mock +{ + +class Protocol : public Kopete::Protocol +{ +public: + Protocol( KInstance *instance, QObject *parent, const char *name ); + // pure virtual functions implemented + virtual Account *createNewAccount( const QString &accountId ); + virtual AddContactPage *createAddContactWidget( QWidget *parent, Kopete::Account *account ); + virtual KopeteEditAccountWidget * createEditAccountWidget( Kopete::Account *account, QWidget *parent ); +}; + +} // end ns Kopete::Test::Mock +} // end ns Kopete::Test +} // end ns Kopete + + +#endif + diff --git a/kopete/libkopete/tests/template_test.cpp b/kopete/libkopete/tests/template_test.cpp new file mode 100644 index 00000000..8598f79f --- /dev/null +++ b/kopete/libkopete/tests/template_test.cpp @@ -0,0 +1,37 @@ +/* + Tests for some requirement + + Copyright (c) 2005 by Duncan Mac-Vicar + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include "template_test.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE( kunittest_template_test, "KopeteSuite"); +KUNITTEST_MODULE_REGISTER_TESTER( Template_Test ); + +void Template_Test::allTests() +{ + testSomething(); +} + +void Template_Test::testSomething() +{ + int result = 1; + int expected = 1; + // result should be the expected one + CHECK(result, expected); +} diff --git a/kopete/libkopete/tests/template_test.h b/kopete/libkopete/tests/template_test.h new file mode 100644 index 00000000..4d0f1617 --- /dev/null +++ b/kopete/libkopete/tests/template_test.h @@ -0,0 +1,35 @@ +/* + Tests for some requirement + + Copyright (c) 2005 by Duncan Mac-Vicar + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef TEMPLATE_TEST_H +#define TEMPLATE_TEST_H + +#include + +// change to SlotTester when it works +class Template_Test : public KUnitTest::Tester +{ +public: + void allTests(); +public slots: + void testSomething(); +private: + +}; + +#endif + diff --git a/kopete/libkopete/ui/Makefile.am b/kopete/libkopete/ui/Makefile.am new file mode 100644 index 00000000..211e0b48 --- /dev/null +++ b/kopete/libkopete/ui/Makefile.am @@ -0,0 +1,31 @@ +METASOURCES = AUTO +AM_CPPFLAGS = -DKDE_NO_COMPAT -DQT_NO_COMPAT -DQT_NO_CAST_ASCII -DQT_NO_ASCII_CAST \ + $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/libkopete/private $(all_includes) + +noinst_LTLIBRARIES = libkopeteui.la + +libkopeteui_la_SOURCES = kopetecontactaction.cpp addcontactpage.cpp \ + editaccountwidget.cpp kopetepassworddialog.ui kopetestdaction.cpp kopeteawaydialogbase.ui \ + kopetefileconfirmdialog.cpp fileconfirmbase.ui userinfodialog.cpp kopeteview.cpp \ + kopetepasswordwidgetbase.ui kopetepasswordwidget.cpp accountselector.cpp kopeteviewplugin.cpp \ + addresseeitem.cpp addressbookselectorwidget_base.ui addressbookselectordialog.cpp \ + addressbookselectorwidget.cpp metacontactselectorwidget_base.ui metacontactselectorwidget.cpp \ + kopetelistview.cpp kopetelistviewitem.cpp kopetelistviewsearchline.cpp \ + contactaddednotifywidget.ui contactaddednotifydialog.cpp addressbooklinkwidget_base.ui \ + addressbooklinkwidget.cpp + +libkopeteui_la_LDFLAGS = $(all_libraries) -lkabc +libkopeteui_la_LIBADD = ../private/libkopeteprivate.la $(LIB_KHTML) + +kopeteincludedir = $(includedir)/kopete/ui +kopeteinclude_HEADERS = accountselector.h fileconfirmbase.h \ + kopetefileconfirmdialog.h kopetepasswordwidget.h kopeteview.h addcontactpage.h \ + kopeteawaydialogbase.h kopetepasswordwidgetbase.h kopeteviewplugin.h editaccountwidget.h \ + kopetecontactaction.h kopetepassworddialog.h kopetestdaction.h userinfodialog.h \ + addressbookselectordialog.h addressbookselectorwidget.h kopetelistview.h kopetelistviewitem.h \ + kopetelistviewsearchline.h addressbooklinkwidget.h + +noinst_HEADERS = addresseeitem.h contactaddednotifywidget.h + +# vim: set noet: + diff --git a/kopete/libkopete/ui/accountselector.cpp b/kopete/libkopete/ui/accountselector.cpp new file mode 100644 index 00000000..2ea8e719 --- /dev/null +++ b/kopete/libkopete/ui/accountselector.cpp @@ -0,0 +1,186 @@ +/* + accountselector.cpp - An Accountselector + + Copyright (c) 2004 by Stefan Gehn + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "accountselector.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" + +#include +#include +#include + +#include +#include + +class AccountListViewItem : public KListViewItem +{ + private: + Kopete::Account *mAccount; + + public: + AccountListViewItem(QListView *parent, Kopete::Account *acc) + : KListViewItem(parent) + { + if (acc==0) + return; + + /*kdDebug(14010) << k_funcinfo << + "account name = " << acc->accountId() << endl;*/ + mAccount = acc; + setText(0, mAccount->accountId()); + setPixmap(0, mAccount->accountIcon()); + } + + Kopete::Account *account() + { + return mAccount; + } +}; + + +// ---------------------------------------------------------------------------- + +class AccountSelectorPrivate +{ + public: + KListView *lv; + Kopete::Protocol *proto; +}; + + +AccountSelector::AccountSelector(QWidget *parent, const char *name) + : QWidget(parent, name) +{ + //kdDebug(14010) << k_funcinfo << "for no special protocol" << endl; + d = new AccountSelectorPrivate; + d->proto = 0; + initUI(); +} + + +AccountSelector::AccountSelector(Kopete::Protocol *proto, QWidget *parent, + const char *name) : QWidget(parent, name) +{ + //kdDebug(14010) << k_funcinfo << " for protocol " << proto->pluginId() << endl; + d = new AccountSelectorPrivate; + d->proto = proto; + initUI(); +} + + +AccountSelector::~AccountSelector() +{ + kdDebug(14010) << k_funcinfo << endl; + delete d; +} + + +void AccountSelector::initUI() +{ + kdDebug(14010) << k_funcinfo << endl; + (new QVBoxLayout(this))->setAutoAdd(true); + d->lv = new KListView(this); + d->lv->setFullWidth(true); + d->lv->addColumn(QString::fromLatin1("")); + d->lv->header()->hide(); + + if(d->proto != 0) + { + kdDebug(14010) << k_funcinfo << "creating list for a certain protocol" << endl; + QDict accounts = Kopete::AccountManager::self()->accounts(d->proto); + QDictIterator it(accounts); + for(; Kopete::Account *account = it.current(); ++it) + { + new AccountListViewItem(d->lv, account); + } + } + else + { + kdDebug(14010) << k_funcinfo << "creating list of all accounts" << endl; + QPtrList accounts = Kopete::AccountManager::self()->accounts(); + Kopete::Account *account = 0; + for(account = accounts.first(); account; account = accounts.next()) + { + new AccountListViewItem(d->lv, account); + } + } + + connect(d->lv, SIGNAL(selectionChanged(QListViewItem *)), + this, SLOT(slotSelectionChanged(QListViewItem *))); +} + + +void AccountSelector::setSelected(Kopete::Account *account) +{ + if (account==0) + return; + + QListViewItemIterator it(d->lv); + while (it.current()) + { + if(static_cast(it.current())->account() == account) + { + it.current()->setSelected(true); + return; + } + } +} + + +bool AccountSelector::isSelected(Kopete::Account *account) +{ + if (account==0) + return false; + + QListViewItemIterator it(d->lv); + while (it.current()) + { + if(static_cast(it.current())->account() == account) + return true; + } + return false; +} + + +Kopete::Account *AccountSelector::selectedItem() +{ + //kdDebug(14010) << k_funcinfo << endl; + + if (d->lv->selectedItem() != 0) + return static_cast(d->lv->selectedItem())->account(); + return 0; +} + + +void AccountSelector::slotSelectionChanged(QListViewItem *item) +{ + //kdDebug(14010) << k_funcinfo << endl; + if (item != 0) + { + Kopete::Account *account = static_cast(item)->account(); + if (account != 0) + { + emit selectionChanged(account); + return; + } + } + + emit selectionChanged(0); +} + +#include "accountselector.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/accountselector.h b/kopete/libkopete/ui/accountselector.h new file mode 100644 index 00000000..4f5d50ac --- /dev/null +++ b/kopete/libkopete/ui/accountselector.h @@ -0,0 +1,92 @@ +/* + accountselector.cpp - An Accountselector + + Copyright (c) 2004 by Stefan Gehn + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef ACCOUNTSELECTOR_H +#define ACCOUNTSELECTOR_H + +#include +#include +#include "kopete_export.h" + +class AccountSelectorPrivate; +class QListViewItem; +/** + * \brief widget to select an account, based on KListView + * @author Stefan Gehn + */ +class KOPETE_EXPORT AccountSelector : public QWidget +{ +Q_OBJECT + + public: + /** + * Constructor. + * + * The parameters @p parent and @p name are handled by + * KListView. + */ + AccountSelector(QWidget *parent=0, const char *name=0); + + /** + * Constructor for a list of accounts for one protocol only + * + * The parameters @p parent and @p name are handled by + * KListView. @p proto defines the protocol whose accounts are + * shown in the list + */ + AccountSelector(Kopete::Protocol *proto, QWidget *parent=0, const char *name=0); + + /** + * Destructor. + */ + ~AccountSelector(); + + /** + * Select @p account in the list, in case it's part of the list + */ + void setSelected(Kopete::Account *account); + + /** + * Returns true in case @p account is in the list and + * the currently selected item, false otherwise + */ + bool isSelected(Kopete::Account *account); + + /** + * @return the currently selected account. + */ + Kopete::Account *selectedItem(); + + signals: + /** + * Emitted whenever the selection changed, @p acc is a pointer to the + * newly selected account + */ + void selectionChanged(Kopete::Account *acc); + + private slots: + void slotSelectionChanged(QListViewItem *item); + + private: + void initUI(); + + private: + AccountSelectorPrivate *d; +}; + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/addcontactpage.cpp b/kopete/libkopete/ui/addcontactpage.cpp new file mode 100644 index 00000000..f308a7d4 --- /dev/null +++ b/kopete/libkopete/ui/addcontactpage.cpp @@ -0,0 +1,29 @@ +/* + addcontactpage.cpp - Kopete's Add Contact GUI + + Copyright (c) 2002 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "addcontactpage.h" + +AddContactPage::AddContactPage(QWidget *parent, const char *name ) : QWidget(parent,name) +{ +} + +AddContactPage::~AddContactPage() +{ +} + +#include "addcontactpage.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/addcontactpage.h b/kopete/libkopete/ui/addcontactpage.h new file mode 100644 index 00000000..506c5bcc --- /dev/null +++ b/kopete/libkopete/ui/addcontactpage.h @@ -0,0 +1,65 @@ +/* + addcontactpage.h - Kopete's Add Contact GUI + + Copyright (c) 2002 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef ADDCONTACTPAGE_H +#define ADDCONTACTPAGE_H + +#include +#include + +#include "kopete_export.h" + +/** + * @author Duncan Mac-Vicar P. + * @todo i want to be able to have a assync apply. + * (in the case of jabber, i need to translate the legacy id to a JID) + * this could also be usefull in the case of MLSN to check if no error (and also jabber) + */ +class KOPETE_EXPORT AddContactPage : public QWidget +{ +Q_OBJECT + +public: + AddContactPage(QWidget *parent=0, const char *name=0); + virtual ~AddContactPage(); + //Kopete::Protocol *protocol; + + /** + * Plugin should reimplement this methode. + * return true if the content of the page are valid + * + * This method is called in the add account wizzard when the user press the next button + * and this page is showed. when it return false, it does not go to the nextpage. + * You should popup a dialog to explain WHY the page has not been validate + */ + virtual bool validateData()=0; + + /** + * add the contact the the specified meta contact, with the given account + * return false if the contact has not been added + */ + virtual bool apply(Kopete::Account * , Kopete::MetaContact *) = 0; + +signals: + /** + * New incarnation of validateData, emit it everytime you think the current data is valid/invalid + */ + void dataValid( AddContactPage *, bool); +}; + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/addressbooklinkwidget.cpp b/kopete/libkopete/ui/addressbooklinkwidget.cpp new file mode 100644 index 00000000..a6aff32b --- /dev/null +++ b/kopete/libkopete/ui/addressbooklinkwidget.cpp @@ -0,0 +1,99 @@ +/* + AddressBookLinkWidget + + A compact widget for showing and changing which address book item a + particular Kopete::MetaContact is related to. + + Comprises a label showing the contact's name, a Clear button, and a Change + button that usually invokes the AddressBookSelectorWidget. + + Copyright (c) 2006 by Will Stephenson + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include + +#include + +#include + +#include "addressbooklinkwidget.h" +#include "addressbookselectordialog.h" +#include "addressbookselectorwidget.h" + +namespace Kopete { +namespace UI { + + +AddressBookLinkWidget::AddressBookLinkWidget( QWidget * parent, const char * name ) : AddressBookLinkWidgetBase( parent, name ), mMetaContact( 0 ) +{ + btnClear->setIconSet( SmallIconSet( QApplication::reverseLayout() ? QString::fromLatin1( "locationbar_erase" ) : QString::fromLatin1( "clear_left") ) ); + connect( btnClear, SIGNAL( clicked() ), this, SLOT( slotClearAddressee() ) ); + connect( btnSelectAddressee, SIGNAL( clicked() ), SLOT( slotSelectAddressee() ) ); +} + +void AddressBookLinkWidget::setAddressee( const KABC::Addressee& addr ) +{ + edtAddressee->setText( addr.realName() ); + btnClear->setEnabled( !addr.isEmpty() ); +} + +void AddressBookLinkWidget::setMetaContact( const Kopete::MetaContact * mc ) +{ + mMetaContact = mc; +} + +QString AddressBookLinkWidget::uid() const +{ + return mSelectedUid; +} + +void AddressBookLinkWidget::slotClearAddressee() +{ + edtAddressee->clear(); + btnClear->setEnabled( false ); + KABC::Addressee mrEmpty; + mSelectedUid = QString::null; + emit addresseeChanged( mrEmpty ); +} + +void AddressBookLinkWidget::slotSelectAddressee() +{ + QString message; + if ( mMetaContact ) + message = i18n("Choose the corresponding entry for '%1'" ).arg( mMetaContact->displayName() ); + else + message = i18n("Choose the corresponding entry in the address book" ); + + Kopete::UI::AddressBookSelectorDialog dialog( i18n("Addressbook Association"), message, ( mMetaContact ? mMetaContact->metaContactId() : QString::null ), this ); + int result = dialog.exec(); + + KABC::Addressee addr; + if ( result == QDialog::Accepted ) + { + addr = dialog.addressBookSelectorWidget()->addressee(); + + edtAddressee->setText( addr.realName() ); + btnClear->setEnabled( !addr.isEmpty() ); + mSelectedUid = ( addr.isEmpty() ? QString::null : addr.uid() ); + emit addresseeChanged( addr ); + } +} + +} // end namespace UI +} // end namespace Kopete + +#include "addressbooklinkwidget.moc" diff --git a/kopete/libkopete/ui/addressbooklinkwidget.h b/kopete/libkopete/ui/addressbooklinkwidget.h new file mode 100644 index 00000000..dff23c58 --- /dev/null +++ b/kopete/libkopete/ui/addressbooklinkwidget.h @@ -0,0 +1,81 @@ +/* + AddressBookLinkWidget + + A compact widget for showing and changing which address book item a + particular Kopete::MetaContact is related to. + + Comprises a label showing the contact's name, a Clear button, and a Change + button that usually invokes the AddressBookSelectorWidget. + + Copyright (c) 2006 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef ADDRESSBOOKLINKWIDGET_H +#define ADDRESSBOOKLINKWIDGET_H + +#include + +#include "addressbooklinkwidget_base.h" + +namespace Kopete { +class MetaContact; + +namespace UI { + +/** + * A compact widget for showing and changing which address book item a + * particular Kopete::MetaContact is related to. + * + * Comprises a label showing the contact's name, a Clear button, and a Change + * button that usually invokes the AddressBookSelectorWidget. + */ +class AddressBookLinkWidget : public AddressBookLinkWidgetBase +{ +Q_OBJECT +public: + AddressBookLinkWidget( QWidget * parent, const char * name ); + ~AddressBookLinkWidget() {} + /** + * Set the currently selected addressee + */ + void setAddressee( const KABC::Addressee& addr ); + /** + * Set the current metacontact so that the selector dialog may be preselected + */ + void setMetaContact( const Kopete::MetaContact * ); + /** + * Return the selected addressbook UID. + */ + QString uid() const; +signals: + /** + * Emitted when the selected addressee changed. addr is the KABC::Addressee that was selected. If addr.isEmpty() is empty, the clear button was clicked. + */ + void addresseeChanged( const KABC::Addressee& addr ); + + /** + * Provided so you can perform your own actions instead of opening the AddressBookSelectorWidget. + * To do so, QObject::disconnect() btnSelectAddressee and connect your own slot to this signal + */ + void selectAddresseeClicked(); +protected slots: + void slotClearAddressee(); + void slotSelectAddressee(); +private: + const Kopete::MetaContact * mMetaContact; + QString mSelectedUid; +}; +} // end namespace UI +} // end namespace Kopete +#endif diff --git a/kopete/libkopete/ui/addressbooklinkwidget_base.ui b/kopete/libkopete/ui/addressbooklinkwidget_base.ui new file mode 100644 index 00000000..4656459c --- /dev/null +++ b/kopete/libkopete/ui/addressbooklinkwidget_base.ui @@ -0,0 +1,78 @@ + +AddressBookLinkWidgetBase + + + AddressBookLinkWidgetBase + + + + 0 + 0 + 350 + 31 + + + + + unnamed + + + 0 + + + + layout9 + + + + unnamed + + + + edtAddressee + + + + + + true + + + The KDE Address Book entry associated with this Kopete Contact + + + + + btnClear + + + + + + Clear + + + + + btnSelectAddressee + + + true + + + C&hange... + + + Select an address book entry + + + + + + + + + klineedit.h + kpushbutton.h + + diff --git a/kopete/libkopete/ui/addressbookselectordialog.cpp b/kopete/libkopete/ui/addressbookselectordialog.cpp new file mode 100644 index 00000000..7d2e17ff --- /dev/null +++ b/kopete/libkopete/ui/addressbookselectordialog.cpp @@ -0,0 +1,89 @@ +/* + AddressBookSelectorDialog + Nice Dialog to select a KDE AddressBook contact + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "addressbookselectordialog.h" +#include "addressbookselectorwidget.h" +#include +#include +#include +#include +#include +#include + +namespace Kopete +{ +namespace UI +{ + +AddressBookSelectorDialog::AddressBookSelectorDialog(const QString &title, const QString &message, const QString &preSelectUid, QWidget *parent, const char *name, bool modal ) : KDialogBase(parent, name, modal, title, Help|Ok|Cancel, Ok, true ) +{ + QVBox *vbox=new QVBox(this); + m_addressBookSelectorWidget= new AddressBookSelectorWidget(vbox); + m_addressBookSelectorWidget->setLabelMessage(message); + + vbox->setSpacing( KDialog::spacingHint() ); + + setMainWidget(vbox); + enableButtonOK(false); + //setHelp("linkaddressbook"); + + connect(m_addressBookSelectorWidget, SIGNAL(addresseeListClicked( QListViewItem * )), SLOT(slotWidgetAddresseeListClicked( QListViewItem * ))); + + if ( !preSelectUid.isEmpty() ) + m_addressBookSelectorWidget->selectAddressee(preSelectUid); +} + +AddressBookSelectorDialog::~AddressBookSelectorDialog() +{ +} + +KABC::Addressee AddressBookSelectorDialog::getAddressee( const QString &title, const QString &message, const QString &preSelectUid, QWidget *parent) +{ + AddressBookSelectorDialog dialog(title, message, preSelectUid, parent); + int result = dialog.exec(); + + KABC::Addressee adr; + if ( result == QDialog::Accepted ) + adr = dialog.addressBookSelectorWidget()->addressee(); + + return adr; +} + +void AddressBookSelectorDialog::slotWidgetAddresseeListClicked( QListViewItem *addressee ) +{ + // enable ok if a valid addressee is selected + enableButtonOK( addressee ? addressee->isSelected() : false); +} + +void AddressBookSelectorDialog::accept() +{ + QDialog::accept(); +} + +void AddressBookSelectorDialog::reject() +{ + QDialog::reject(); +} + +} // namespace UI +} // namespace Kopete + +#include "addressbookselectordialog.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/addressbookselectordialog.h b/kopete/libkopete/ui/addressbookselectordialog.h new file mode 100644 index 00000000..f391aa3a --- /dev/null +++ b/kopete/libkopete/ui/addressbookselectordialog.h @@ -0,0 +1,90 @@ +/* + AddressBookSelectorDialog + Nice Dialog to select a KDE AddressBook contact + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef ADDRESSBOOKSELECTORDIALOG_H +#define ADDRESSBOOKSELECTORDIALOG_H + +#include +#include "kopete_export.h" +#include + +namespace KABC +{ + class AddressBook; + class Addressee; +} + +namespace Kopete +{ +namespace UI +{ + +class AddressBookSelectorWidget; + +/** + * A dialog that uses AddressBookSelectorWidget to allow the user + * to select a KDE addressbook contact. If you want to use special features + * you can use @see addressBookSelectorWidget() to get the pointer to the + * AddressBookSelectorWidget object and set the desired options there. + * + * @author Duncan Mac-Vicar Prett + */ +class KOPETE_EXPORT AddressBookSelectorDialog : public KDialogBase +{ + Q_OBJECT +public: + /** + * The constructor of an empty AddressBookSelectorWidget + */ + AddressBookSelectorDialog( const QString &title, const QString &message, const QString &preSelectUid, QWidget *parent=0L, const char *name=0L, bool modal = false ); + /** + * The destructor of the dialog + */ + ~AddressBookSelectorDialog(); + + /** + * @returns the AddressBookSelectorWidget widget so that additional + * parameters can be set by using it. + */ + AddressBookSelectorWidget *addressBookSelectorWidget() const + { return m_addressBookSelectorWidget; }; + + /** + * Creates a modal dialog, lets the user to select a addressbook contact + * and returns when the dialog is closed. + * + * @returns the selected contact, or a null addressee if the user + * pressed the Cancel button. Optionally + */ + static KABC::Addressee getAddressee( const QString &title, const QString &message, const QString &preSelectUid, QWidget *parent = 0L ); + +protected slots: + virtual void accept(); + virtual void reject(); + void slotWidgetAddresseeListClicked( QListViewItem *addressee ); +protected: + AddressBookSelectorWidget *m_addressBookSelectorWidget; +}; + +} // namespace UI +} // namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/addressbookselectorwidget.cpp b/kopete/libkopete/ui/addressbookselectorwidget.cpp new file mode 100644 index 00000000..50c4a885 --- /dev/null +++ b/kopete/libkopete/ui/addressbookselectorwidget.cpp @@ -0,0 +1,171 @@ +/* + AddressBookSelectorWidget + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Based on LinkAddressBookUI whose code was shamelessly stolen from + kopete's add new contact wizard, used in Konversation, and then + reappropriated by Kopete. + + LinkAddressBookUI: + Copyright (c) 2004 by John Tapsell + Copyright (c) 2003-2005 by Will Stephenson + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2002 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "addressbookselectorwidget.h" +#include +#include "kabcpersistence.h" + +using namespace Kopete::UI; + +namespace Kopete +{ +namespace UI +{ + +AddressBookSelectorWidget::AddressBookSelectorWidget( QWidget *parent, const char *name ) + : AddressBookSelectorWidget_Base( parent, name ) +{ + m_addressBook = Kopete::KABCPersistence::self()->addressBook(); + + // Addressee validation connections + connect( addAddresseeButton, SIGNAL( clicked() ), SLOT( slotAddAddresseeClicked() ) ); + connect( addAddresseeButton, SIGNAL( clicked() ), SIGNAL( addAddresseeClicked() ) ); + + connect( addresseeListView, SIGNAL( clicked(QListViewItem * ) ), + SIGNAL( addresseeListClicked( QListViewItem * ) ) ); + connect( addresseeListView, SIGNAL( selectionChanged( QListViewItem * ) ), + SIGNAL( addresseeListClicked( QListViewItem * ) ) ); + connect( addresseeListView, SIGNAL( spacePressed( QListViewItem * ) ), + SIGNAL( addresseeListClicked( QListViewItem * ) ) ); + + connect( m_addressBook, SIGNAL( addressBookChanged( AddressBook * ) ), this, SLOT( slotLoadAddressees() ) ); + + //We should add a clear KAction here. But we can't really do that with a designer file :\ this sucks + + addresseeListView->setColumnText(2, SmallIconSet(QString::fromLatin1("email")), i18n("Email")); + + kListViewSearchLine->setListView(addresseeListView); + slotLoadAddressees(); + + addresseeListView->setColumnWidthMode(0, QListView::Manual); + addresseeListView->setColumnWidth(0, 63); //Photo is 60, and it's nice to have a small gap, imho +} + + +AddressBookSelectorWidget::~AddressBookSelectorWidget() +{ + disconnect( m_addressBook, SIGNAL( addressBookChanged( AddressBook * ) ), this, SLOT( slotLoadAddressees() ) ); +} + + +KABC::Addressee AddressBookSelectorWidget::addressee() +{ + AddresseeItem *item = 0L; + item = static_cast( addresseeListView->selectedItem() ); + + if ( item ) + m_addressee = item->addressee(); + + return m_addressee; +} + +void AddressBookSelectorWidget::selectAddressee( const QString &uid ) +{ + // iterate trough list view + QListViewItemIterator it( addresseeListView ); + while( it.current() ) + { + AddresseeItem *addrItem = (AddresseeItem *) it.current(); + if ( addrItem->addressee().uid() == uid ) + { + // select the contact item + addresseeListView->setSelected( addrItem, true ); + addresseeListView->ensureItemVisible( addrItem ); + } + ++it; + } +} + +bool AddressBookSelectorWidget::addresseeSelected() +{ + return addresseeListView->selectedItem() ? true : false; +} + +/** Read in contacts from addressbook, and select the contact that is for our nick. */ +void AddressBookSelectorWidget::slotLoadAddressees() +{ + addresseeListView->clear(); + KABC::AddressBook::Iterator it; + AddresseeItem *addr; + for( it = m_addressBook->begin(); it != m_addressBook->end(); ++it ) + { + addr = new AddresseeItem( addresseeListView, (*it)); + } + +} + +void AddressBookSelectorWidget::setLabelMessage( const QString &msg ) +{ + lblHeader->setText(msg); +} + +void AddressBookSelectorWidget::slotAddAddresseeClicked() +{ + // Pop up add addressee dialog + QString addresseeName = KInputDialog::getText( i18n( "New Address Book Entry" ), i18n( "Name the new entry:" ), QString::null, 0, this ); + + if ( !addresseeName.isEmpty() ) + { + KABC::Addressee addr; + addr.setNameFromString( addresseeName ); + m_addressBook->insertAddressee(addr); + Kopete::KABCPersistence::self()->writeAddressBook( 0 ); + slotLoadAddressees(); + // select the addressee we just added + QListViewItem * added = addresseeListView->findItem( addresseeName, 1 ); + kListViewSearchLine->clear(); + kListViewSearchLine->updateSearch(); + addresseeListView->setSelected( added, true ); + addresseeListView->ensureItemVisible( added ); + } +} + +} // namespace UI +} // namespace Kopete + +#include "addressbookselectorwidget.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/addressbookselectorwidget.h b/kopete/libkopete/ui/addressbookselectorwidget.h new file mode 100644 index 00000000..3141f726 --- /dev/null +++ b/kopete/libkopete/ui/addressbookselectorwidget.h @@ -0,0 +1,90 @@ +/* + AddressBookSelectorWidget + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Based on LinkAddressBookUI whose code was shamelessly stolen from + kopete's add new contact wizard, used in Konversation, and then + reappropriated by Kopete. + + LinkAddressBookUI: + Copyright (c) 2004 by John Tapsell + Copyright (c) 2003-2005 by Will Stephenson + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2002 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef AddressBookSelectorWidget_H +#define AddressBookSelectorWidget_H + +#include +#include + +#include +#include "kopete_export.h" + +#include "addressbookselectorwidget_base.h" + +namespace KABC { + class AddressBook; + class Addressee; +} + +namespace Kopete +{ +namespace UI +{ + +class KOPETE_EXPORT AddressBookSelectorWidget : public AddressBookSelectorWidget_Base +{ + Q_OBJECT +public: + AddressBookSelectorWidget( QWidget *parent = 0, const char *name = 0 ); + ~AddressBookSelectorWidget(); + KABC::Addressee addressee(); + /** + * sets the widget label message + * example: Please select a contact + * or, Choose a contact to delete + */ + void setLabelMessage( const QString &msg ); + /** + * pre-selects a contact + */ + void selectAddressee( const QString &uid ); + /** + * @return true if there is a contact selected + */ + bool addresseeSelected(); + +private: + KABC::AddressBook * m_addressBook; + KABC::Addressee m_addressee; + +protected slots: + void slotAddAddresseeClicked(); + /** + * Utility function, populates the addressee list + */ + void slotLoadAddressees(); +signals: + void addresseeListClicked( QListViewItem *addressee ); + void addAddresseeClicked(); +}; + +} // namespace UI +} // namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/addressbookselectorwidget_base.ui b/kopete/libkopete/ui/addressbookselectorwidget_base.ui new file mode 100644 index 00000000..d5e2e6f2 --- /dev/null +++ b/kopete/libkopete/ui/addressbookselectorwidget_base.ui @@ -0,0 +1,175 @@ + +AddressBookSelectorWidget_Base + + + AddressBookSelectorWidget_Base + + + + 0 + 0 + 596 + 572 + + + + + 5 + 1 + 0 + 0 + + + + Select Contact + + + + unnamed + + + 0 + + + + spacer11 + + + Horizontal + + + Expanding + + + + 405 + 20 + + + + + + addAddresseeButton + + + Create New Entr&y... + + + Create a new entry in your address book + + + + + lblHeader + + + + 7 + 4 + 0 + 0 + + + + + + + Photo + + + false + + + true + + + + + Name + + + true + + + true + + + + + Email + + + true + + + true + + + + addresseeListView + + + + 7 + 7 + 0 + 10 + + + + true + + + LastColumn + + + Select the contact you want to communicate with via Instant Messaging + + + + + layout1 + + + + unnamed + + + + lblSearch + + + + 1 + 5 + 0 + 0 + + + + S&earch: + + + kListViewSearchLine + + + + + kListViewSearchLine + + + + + + + + klistviewsearchline.h + + + + + kactivelabel.h + klistview.h + klistviewsearchline.h + + diff --git a/kopete/libkopete/ui/addresseeitem.cpp b/kopete/libkopete/ui/addresseeitem.cpp new file mode 100644 index 00000000..3888ee27 --- /dev/null +++ b/kopete/libkopete/ui/addresseeitem.cpp @@ -0,0 +1,63 @@ +/* + Copyright (c) 2001 Cornelius Schumacher + + This library 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 library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include +#include + +#include +#include + +#include "addresseeitem.h" + +AddresseeItem::AddresseeItem( QListView *parent, const KABC::Addressee &addressee) : + KListViewItem( parent ), + mAddressee( addressee ) +{ + //We can't save showphoto because we don't have a d pointer + KABC::Picture pic = mAddressee.photo(); + if(!pic.isIntern()) + pic = mAddressee.logo(); + if(pic.isIntern()) + { + QPixmap qpixmap( pic.data().scaleWidth(60) ); //60 pixels seems okay.. kmail uses 60 btw + setPixmap( Photo,qpixmap ); + } + + setText( Name, addressee.realName() ); + setText( Email, addressee.preferredEmail() ); +} + +QString AddresseeItem::key( int column, bool ) const +{ + if (column == Email) { + QString value = text(Email); + QRegExp emailRe(QString::fromLatin1("<\\S*>")); + int match = emailRe.search(value); + if (match > -1) + value = value.mid(match + 1, emailRe.matchedLength() - 2); + + return value.lower(); + } + + return text(column).lower(); +} + + diff --git a/kopete/libkopete/ui/addresseeitem.h b/kopete/libkopete/ui/addresseeitem.h new file mode 100644 index 00000000..b190fea6 --- /dev/null +++ b/kopete/libkopete/ui/addresseeitem.h @@ -0,0 +1,68 @@ +/* + This file is part of libkabc. + Copyright (c) 2001 Cornelius Schumacher + + This library 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 library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef KABC_ADDRESSEEDIALOG_H +#define KABC_ADDRESSEEDIALOG_H + +#include + +#include +#include +#include + +#include + +/** + @short Special ListViewItem +*/ +class AddresseeItem : public KListViewItem +{ + public: + + /** + Type of column + @li @p Name - Name in Addressee + @li @p Email - Email in Addressee + */ + enum columns { Photo =0, Name = 1, Email = 2 }; + + /** + Constructor. + + @param parent The parent listview. + @param addressee The associated addressee. + */ + AddresseeItem( QListView *parent, const KABC::Addressee &addressee ); + + /** + Returns the addressee. + */ + KABC::Addressee addressee() const { return mAddressee; } + + /** + Method used by QListView to sort the items. + */ + virtual QString key( int column, bool ascending ) const; + + private: + KABC::Addressee mAddressee; +}; + +#endif diff --git a/kopete/libkopete/ui/contactaddednotifydialog.cpp b/kopete/libkopete/ui/contactaddednotifydialog.cpp new file mode 100644 index 00000000..abcd4c7e --- /dev/null +++ b/kopete/libkopete/ui/contactaddednotifydialog.cpp @@ -0,0 +1,178 @@ +/* + Copyright (c) 2005 Olivier Goffart + + Kopete (c) 2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "contactaddednotifydialog.h" + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "kopetegroup.h" +#include "kopeteaccount.h" +#include "kopeteuiglobal.h" +#include "kopeteprotocol.h" +#include "kopetecontactlist.h" +#include "kopetemetacontact.h" +#include "addressbooklinkwidget.h" +#include "addressbookselectordialog.h" + + +#include "contactaddednotifywidget.h" + +namespace Kopete { + +namespace UI { + +struct ContactAddedNotifyDialog::Private +{ + ContactAddedNotifyWidget *widget; + Account *account; + QString contactId; + QString addressbookId; +}; + + +ContactAddedNotifyDialog::ContactAddedNotifyDialog(const QString& contactId, + const QString& contactNick, Kopete::Account *account, uint hide) + : KDialogBase( Global::mainWidget(), "ContactAddedNotify", /*modal=*/false, + i18n("Someone Has Added You"), Ok|Cancel ) +{ + + setWFlags(WDestructiveClose | getWFlags() ); + + d=new Private; + d->widget=new ContactAddedNotifyWidget(this); + setMainWidget(d->widget); + + d->account=account; + d->contactId=contactId; + d->widget->m_label->setText(i18n(" The contact %2 has added you to his/her contactlist. (Account %3)") + .arg( KURL::encode_string( account->protocol()->pluginId() ) + QString::fromLatin1(":") + + KURL::encode_string( account->accountId() ) , + contactNick.isEmpty() ? contactId : contactNick + QString::fromLatin1(" < ") + contactId + QString::fromLatin1(" >") , + account->accountLabel() ) ); + if( hide & InfoButton) + d->widget->m_infoButton->hide() ; + if( hide & AuthorizeCheckBox ) + { + d->widget->m_authorizeCb->hide(); + d->widget->m_authorizeCb->setChecked(false); + } + if( hide & AddCheckBox ) + { + d->widget->m_addCb->hide(); + d->widget->m_addCb->setChecked(false); + } + if( hide & AddGroupBox ) + d->widget->m_contactInfoBox->hide(); + + // Populate the groups list + Kopete::GroupList groups=Kopete::ContactList::self()->groups(); + for( Kopete::Group *it = groups.first(); it; it = groups.next() ) + { + QString groupname = it->displayName(); + if ( it->type() == Group::Normal && !groupname.isEmpty() ) + { + d->widget->m_groupList->insertItem(groupname); + } + } + d->widget->m_groupList->setCurrentText(QString::null); //default to top-level + + connect( d->widget->widAddresseeLink, SIGNAL( addresseeChanged( const KABC::Addressee& ) ), this, SLOT( slotAddresseeSelected( const KABC::Addressee& ) ) ); + connect( d->widget->m_infoButton, SIGNAL( clicked() ), this, SLOT( slotInfoClicked() ) ); + + connect( this, SIGNAL(okClicked()) , this , SLOT(slotFinished())); + +} + + +ContactAddedNotifyDialog::~ContactAddedNotifyDialog() +{ + delete d; +} + +bool ContactAddedNotifyDialog::added() const +{ + return d->widget->m_addCb->isChecked(); +} + +bool ContactAddedNotifyDialog::authorized() const +{ + return d->widget->m_authorizeCb->isChecked(); +} + +QString ContactAddedNotifyDialog::displayName() const +{ + return d->widget->m_displayNameEdit->text(); +} + +Group *ContactAddedNotifyDialog::group() const +{ + QString grpName=d->widget->m_groupList->currentText(); + if(grpName.isEmpty()) + return Group::topLevel(); + + return ContactList::self()->findGroup( grpName ); +} + +MetaContact *ContactAddedNotifyDialog::addContact() const +{ + if(!added() || !d->account) + return 0L; + + MetaContact *metacontact=d->account->addContact(d->contactId, displayName(), group()); + if(!metacontact) + return 0L; + + metacontact->setMetaContactId(d->addressbookId); + + return metacontact; +} + +void ContactAddedNotifyDialog::slotAddresseeSelected( const KABC::Addressee & addr ) +{ + if ( !addr.isEmpty() ) + { + d->addressbookId = addr.uid(); + } +} + +void ContactAddedNotifyDialog::slotInfoClicked() +{ + emit infoClicked(d->contactId); +} + +void ContactAddedNotifyDialog::slotFinished() +{ + emit applyClicked(d->contactId); +} + + + +} // namespace UI +} // namespace Kopete +#include "contactaddednotifydialog.moc" diff --git a/kopete/libkopete/ui/contactaddednotifydialog.h b/kopete/libkopete/ui/contactaddednotifydialog.h new file mode 100644 index 00000000..96f8844c --- /dev/null +++ b/kopete/libkopete/ui/contactaddednotifydialog.h @@ -0,0 +1,175 @@ +/* + Copyright (c) 2005 Olivier Goffart + + Kopete (c) 2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + + +#ifndef KOPETE_UICONTACTADDEDNOTIFYDIALOG_H +#define KOPETE_UICONTACTADDEDNOTIFYDIALOG_H + +#include +#include "kopete_export.h" + +namespace KABC { + class Addressee; +} + +namespace Kopete { + +class Group; +class Account; +class MetaContact; + +namespace UI { + +/** + * @brief Dialog which is shown when a contact added you in the contactlist. + * + * This dialog asks the user to give authorization for the addition to the + * person who added the user and also asks the user if the contact who you've + * received the notification for should be added to the user's contact list + * + * example of usage + * @code + + Kopete::UI::ContactAddedNotifyDialog *dialog = + new ContactAddedNotifyDialog(contactId, QString::null,account); + QObject::connect(dialog,SIGNAL(applyClicked(const QString&)),this,SLOT(contactAddedDialogApplied())); + QObject::connect(dialog,SIGNAL(infoClicked(const QString&)),this,SLOT(contactAddedDialogInfo())); + dialog->show(); + + * @endcode + * + * and in your contactAddedDialogApplied slot + * @code + const Kopete::UI::ContactAddedNotifyDialog *dialog = + dynamic_cast(sender()); + if(!dialog) + return; + if(dialog->authorized()) + socket->authorize(contactId); + if(dialog->added()) + dialog->addContact(); + * @endcode + * + * Note that you can also use exec() but this is not recommended + * + * @author Olivier Goffart + * @since 0.11 + */ +class KOPETE_EXPORT ContactAddedNotifyDialog : public KDialogBase +{ +Q_OBJECT +public: + /** + * All widget in the dialog that may be hidden. + */ + enum HideWidget + { + InfoButton = 0x01, /**< the button which ask for more info about the contact */ + AuthorizeCheckBox = 0x02, /**< the checkbox which ask for authorize the contact */ + AddCheckBox = 0x04, /**< the checkbox which ask if the contact should be added */ + AddGroupBox = 0x08 /**< all the widget about metacontact properties */ + }; + + /** + * @brief Constructor + * + * The dialog is by default not modal, and will delete itself when closed + * + * @param contactId the contactId of the contact which just added the user + * @param contactNick the nickname of the contact if available. + * @param account is used to display the account icon and informaiton about the account + * @param hide a bitmask of HideWidget used to hide some widget. By default, everything is shown. + * + */ + ContactAddedNotifyDialog(const QString& contactId, const QString& contactNick=QString::null, + Kopete::Account *account=0L, uint hide=0x00); + + /** + * @brief Destructor + */ + ~ContactAddedNotifyDialog(); + + /** + * @brief return if the user has checked the "authorize" checkbox + * @return true if the authorize checkbox is checked, false otherwise + */ + bool authorized() const; + + /** + * @brief return if the user has checked the "add" checkbox + * @return true if the add checkbox is checked, false otherwise + */ + bool added() const; + + /** + * @brief return the display name the user has entered + */ + QString displayName() const; + + /** + * @brief return the group the user has selected + * + * If the user has entered a group which doesn't exist yet, it will be created now + */ + Group* group() const; + +public slots: + + /** + * @brief create a metacontact. + * + * This function only works if the add checkbox is checked, otherwise, + * it will return 0L. + * + * it uses the Account::addContact function to add the contact + * + * @return the new metacontact created, or 0L if the operation failed. + */ + MetaContact *addContact() const; + +signals: + /** + * @brief the dialog has been applied + * @param contactId is the id of the contact passed in the constructor. + */ + void applyClicked(const QString &contactId); + + /** + * @brief the button "info" has been pressed + * If you haven't hidden the more info button, you should connect this + * signal to a slot which show a dialog with more info about the + * contact. + * + * hint: you can use sender() as parent of the new dialog + * @param contactId is the id of the contact passed in the constructor. + */ + void infoClicked(const QString &contactId); + + +private slots: + void slotAddresseeSelected( const KABC::Addressee &); + void slotInfoClicked(); + void slotFinished(); + +private: + struct Private; + Private *d; +}; + + + +} // namespace UI +} // namespace Kopete +#endif diff --git a/kopete/libkopete/ui/contactaddednotifywidget.ui b/kopete/libkopete/ui/contactaddednotifywidget.ui new file mode 100644 index 00000000..47d3f070 --- /dev/null +++ b/kopete/libkopete/ui/contactaddednotifywidget.ui @@ -0,0 +1,260 @@ + +ContactAddedNotifyWidget +Olivier Goffart + + + Form2 + + + + 0 + 0 + 466 + 342 + + + + + unnamed + + + + m_label + + + The contact XXX added you in his contactlist + + + WordBreak|AlignVCenter + + + + + layout2 + + + + unnamed + + + + spacer1 + + + Horizontal + + + Expanding + + + + 151 + 20 + + + + + + m_infoButton + + + Read More Info About This Contact + + + + + + + m_authorizeCb + + + Authorize this contact to see my status + + + true + + + + + m_addCb + + + Add this contact in my contactlist + + + true + + + + + m_contactInfoBox + + + + + + + unnamed + + + + layout19 + + + + unnamed + + + + textLabel7 + + + Display name: + + + The display name of the contact. Leave it empty to use the contact nickname + + + Enter the contact display name. This is how the contact will appears in the contactlist. +Leave it empty if you want to see the contact nickname as display name. + + + + + m_displayNameEdit + + + The display name of the contact. Leave it empty to use the contact nickname + + + Enter the contact display name. This is how the contact will appears in the contactlist. +Leave it empty if you want to see the contact nickname as display name. + + + + + + + layout6 + + + + unnamed + + + + textLabel5 + + + In the group: + + + Enter the group where the contact should be added. Leave it empty to add it in the top level group. + + + + + m_groupList + + + + 3 + 0 + 0 + 0 + + + + true + + + Enter the group where the contact should be added. Leave it empty to add it in the top level group. + + + + + + + textLabel6 + + + Addressbook link: + + + + + layout11 + + + + unnamed + + + + spacer11 + + + Horizontal + + + Expanding + + + + 51 + 20 + + + + + + widAddresseeLink + + + + + + + + + spacer21 + + + Vertical + + + Expanding + + + + 21 + 40 + + + + + + + + m_addCb + toggled(bool) + m_contactInfoBox + setEnabled(bool) + + + + + Kopete::UI::AddressBookLinkWidget +
    addressbooklinkwidget.h
    +
    +
    + + + kpushbutton.h + klineedit.h + kcombobox.h + klineedit.h + klineedit.h + kpushbutton.h + +
    diff --git a/kopete/libkopete/ui/editaccountwidget.cpp b/kopete/libkopete/ui/editaccountwidget.cpp new file mode 100644 index 00000000..7428a8ad --- /dev/null +++ b/kopete/libkopete/ui/editaccountwidget.cpp @@ -0,0 +1,49 @@ +/* + editaccountwidget.cpp - Kopete Account Widget + + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2003 by Olivier Goffart + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "editaccountwidget.h" + +class KopeteEditAccountWidgetPrivate +{ +public: + Kopete::Account *account; +}; + +KopeteEditAccountWidget::KopeteEditAccountWidget( Kopete::Account *account ) +{ + d = new KopeteEditAccountWidgetPrivate; + d->account = account; +} + +KopeteEditAccountWidget::~KopeteEditAccountWidget() +{ + delete d; +} + +Kopete::Account * KopeteEditAccountWidget::account() const +{ + return d->account; +} + +void KopeteEditAccountWidget::setAccount( Kopete::Account *account ) +{ + d->account = account; +} + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/editaccountwidget.h b/kopete/libkopete/ui/editaccountwidget.h new file mode 100644 index 00000000..533c90ff --- /dev/null +++ b/kopete/libkopete/ui/editaccountwidget.h @@ -0,0 +1,104 @@ +/* + editaccountwidget.h - Kopete Account Widget + + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2003 by Olivier Goffart + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef EDITACCOUNTWIDGET_H +#define EDITACCOUNTWIDGET_H + +#include "kopete_export.h" + +namespace Kopete +{ +class Account; +} + +class KopeteEditAccountWidgetPrivate; + +/** + * @author Olivier Goffart + * + * This class is used by the protocol plugins to add specific protocol fields in the add account wizard, + * or in the account preferences. If the given account is 0L, then you will have to create a new account + * in @ref apply(). + * + * Each protocol has to subclass this class, and the protocol's edit account page MUST inherits from + * QWidget too. + * + * We suggest to put at least these fields in the page: + * + * - The User login, or the accountId. you can retrieve it from @ref Kopete::Account::accountId(). This + * field has to be marked as ReadOnly or shown as a label if the account already exists. Remember + * that accountId should be constant after account creation! + * + * - The password, and the remember password checkboxes. + * + * - The auto connect checkbox: use @ref Kopete::Account::excludeConnect() and + * @ref Kopete::Account::setExcludeConnect() to get/set this flag. + * + * You may add other custom fields, e.g. the nickname. To save or retrieve these settings use + * @ref Kopete::ContactListElement::pluginData() with your protocol as plugin. + */ +class KOPETE_EXPORT KopeteEditAccountWidget +{ +public: + /** + * Constructor. + * + * If 'account' is 0L we are in the 'add account wizard', otherwise + * we are editing an existing account. + */ + KopeteEditAccountWidget( Kopete::Account *account ); + + /** + * Destructor + */ + virtual ~KopeteEditAccountWidget(); + + /** + * This method must be reimplemented. + * It does the same as @ref AddContactPage::validateData() + */ + virtual bool validateData() = 0; + + /** + * Create a new account if we are in the 'add account wizard', + * otherwise update the existing account. + */ + virtual Kopete::Account *apply() = 0; + +protected: + /** + * Get a pointer to the Kopete::Account passed to the constructor. + * You can modify it any way you like, just don't delete the object. + */ + Kopete::Account * account() const; + + /** + * Set the account + */ + // FIXME: Is it possible to make the API not require this? A const account + // in this widget seems a lot cleaner to me - Martijn + void setAccount( Kopete::Account *account ); + +private: + KopeteEditAccountWidgetPrivate *d; +}; + +// vim: set noet ts=4 sts=4 sw=4: + +#endif + diff --git a/kopete/libkopete/ui/fileconfirmbase.ui b/kopete/libkopete/ui/fileconfirmbase.ui new file mode 100644 index 00000000..3d697b0f --- /dev/null +++ b/kopete/libkopete/ui/fileconfirmbase.ui @@ -0,0 +1,151 @@ + +FileConfirmBase + + + FileConfirmBase + + + + 0 + 0 + 410 + 307 + + + + A User Would Like to Send You a File + + + + unnamed + + + 3 + + + 6 + + + + TextLabel1 + + + A user is trying to send you a file. The file will only be downloaded if you accept this dialog. If you do not wish to receive it, please click 'Refuse'. This file will never be executed by Kopete at any point during or after the transfer. + + + WordBreak|AlignVCenter + + + + + TextLabel1_2 + + + From: + + + + + TextLabel7 + + + File name: + + + + + m_saveto + + + + + cmdBrowse + + + &Browse... + + + + + TextLabel11 + + + Size: + + + + + TextLabel8 + + + Description: + + + + + m_description + + + true + + + + + Spacer6 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + TextLabel13 + + + Save to: + + + + + m_from + + + true + + + + + m_filename + + + true + + + + + m_size + + + true + + + + + + + klineedit.h + kpushbutton.h + klineedit.h + klineedit.h + klineedit.h + + diff --git a/kopete/libkopete/ui/kopete.widgets b/kopete/libkopete/ui/kopete.widgets new file mode 100644 index 00000000..7c441d0f --- /dev/null +++ b/kopete/libkopete/ui/kopete.widgets @@ -0,0 +1,24 @@ +[Global] +PluginName=KopeteWidgets +Includes=kinstance.h +Init=new KInstance("kopetewidgets"); + +[Kopete::UI::ListView::ListView] +ToolTip=List View (Kopete) +WhatsThis=A component capable list view widget. +IncludeFile=kopetelistview.h +Group=Views (Kopete) + +[Kopete::UI::ListView::SearchLine] +ToolTip=List View Search Line (Kopete) +WhatsThis=Search line able to use Kopete custom list View. +IncludeFile=kopetelistviewsearchline.h +ConstructorArgs=(parent, 0, name) +Group=Input (Kopete) + +[Kopete::UI::AddressBookLinkWidget] +ToolTip=Address Book Link Widget (Kopete) +WhatsThis=KABC::Addressee display/selector +IncludeFile=addressbooklinkwidget.h +ConstructorArgs=(parent, name) +Group=Input (Kopete) diff --git a/kopete/libkopete/ui/kopeteawaydialogbase.ui b/kopete/libkopete/ui/kopeteawaydialogbase.ui new file mode 100644 index 00000000..783fe4da --- /dev/null +++ b/kopete/libkopete/ui/kopeteawaydialogbase.ui @@ -0,0 +1,85 @@ + +KopeteAwayDialog_Base + + + KopeteAwayDialog_Base + + + + 0 + 0 + 322 + 192 + + + + + unnamed + + + 0 + + + + TextLabel1 + + + Please specify an away message, or choose a predefined one. + + + WordBreak|AlignVCenter|AlignLeft + + + + + + + txtOneShot + + + + 3 + 0 + 0 + 0 + + + + + 300 + 0 + + + + + + cmbHistory + + + false + + + AtCurrent + + + + + spacer2 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + + diff --git a/kopete/libkopete/ui/kopetecontactaction.cpp b/kopete/libkopete/ui/kopetecontactaction.cpp new file mode 100644 index 00000000..d02c2ff2 --- /dev/null +++ b/kopete/libkopete/ui/kopetecontactaction.cpp @@ -0,0 +1,54 @@ +/* + kopetecontactaction.cpp - KAction for selecting a Kopete::Contact + + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetecontactaction.h" + +#include "kopetemetacontact.h" +#include "kopetecontact.h" +#include "kopeteonlinestatus.h" + +KopeteContactAction::KopeteContactAction( Kopete::Contact *contact, const QObject *receiver, + const char *slot, KAction *parent ) +: KAction( contact->metaContact()->displayName(), QIconSet( contact->onlineStatus().iconFor( contact ) ), KShortcut(), + parent, contact->contactId().latin1() ) +{ + m_contact = contact; + + connect( this, SIGNAL( activated() ), SLOT( slotContactActionActivated() ) ); + connect( this, SIGNAL( activated( Kopete::Contact * ) ), receiver, slot ); +} + +KopeteContactAction::~KopeteContactAction() +{ +} + +void KopeteContactAction::slotContactActionActivated() +{ + emit activated( m_contact ); +} + +Kopete::Contact * KopeteContactAction::contact() const +{ + return m_contact; +} + + +#include "kopetecontactaction.moc" + +// vim: set noet ts=4 sts=4 sw=4: + + diff --git a/kopete/libkopete/ui/kopetecontactaction.h b/kopete/libkopete/ui/kopetecontactaction.h new file mode 100644 index 00000000..bb9d9f76 --- /dev/null +++ b/kopete/libkopete/ui/kopetecontactaction.h @@ -0,0 +1,61 @@ +/* + kopetecontactaction.cpp - KAction for selecting a Kopete::Contact + + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef __kopetecontactaction_h__ +#define __kopetecontactaction_h__ + +#include +#include "kopete_export.h" + +namespace Kopete +{ +class Contact; +} + +/** + * @author Martijn Klingens + */ +class KOPETE_EXPORT KopeteContactAction : public KAction +{ + Q_OBJECT + +public: + /** + * Create a new KopeteContactAction + */ + KopeteContactAction( Kopete::Contact *contact, const QObject* receiver, const char* slot, KAction* parent ); + ~KopeteContactAction(); + + Kopete::Contact * contact() const; + +signals: + /** + * Overloaded signal to get the selected contact + */ + void activated( Kopete::Contact *action ); + +private slots: + void slotContactActionActivated(); + +private: + Kopete::Contact *m_contact; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/kopetefileconfirmdialog.cpp b/kopete/libkopete/ui/kopetefileconfirmdialog.cpp new file mode 100644 index 00000000..01036a05 --- /dev/null +++ b/kopete/libkopete/ui/kopetefileconfirmdialog.cpp @@ -0,0 +1,117 @@ +/* + kopetefileconfirmdialog.cpp + + Copyright (c) 2003-2004 by Olivier Goffart + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include +#include +#include +#include +#include +#include + +//#include "kopetetransfermanager.h" +#include "fileconfirmbase.h" +#include "kopetefileconfirmdialog.h" + +#include "kopetemetacontact.h" +#include "kopetecontact.h" + +KopeteFileConfirmDialog::KopeteFileConfirmDialog(const Kopete::FileTransferInfo &info,const QString& description,QWidget *parent, const char *name ) +: KDialogBase( parent, name, false, i18n( "A User Would Like to Send You a File" ), + KDialogBase::User1 | KDialogBase::User2, KDialogBase::User1, true, i18n( "&Refuse" ), i18n( "&Accept" ) ), + m_info( info ) +{ + setWFlags( WDestructiveClose ); + m_emited=false; + + m_view=new FileConfirmBase(this, "FileConfirmView"); + m_view->m_from->setText( info.contact()->metaContact()->displayName() + QString::fromLatin1( " <" ) + + info.contact()->contactId() + QString::fromLatin1( "> " ) ); + m_view->m_size->setText( KGlobal::locale()->formatNumber( long( info.size() ), 0 ) ); + m_view->m_description->setText( description ); + m_view->m_filename->setText( info.file() ); + + KGlobal::config()->setGroup("File Transfer"); + const QString defaultPath=KGlobal::config()->readEntry("defaultPath" , QDir::homeDirPath() ); + m_view->m_saveto->setText(defaultPath + QString::fromLatin1( "/" ) + info.file() ); + + setMainWidget(m_view); + + connect(m_view->cmdBrowse, SIGNAL(clicked()), this, SLOT(slotBrowsePressed())); +} + +KopeteFileConfirmDialog::~KopeteFileConfirmDialog() +{ +} + +void KopeteFileConfirmDialog::slotBrowsePressed() +{ + QString saveFileName = KFileDialog::getSaveFileName( m_view->m_saveto->text(), QString::fromLatin1( "*" ), 0L , i18n( "File Transfer" ) ); + if ( !saveFileName.isNull()) + { + m_view->m_saveto->setText(saveFileName); + } +} + +void KopeteFileConfirmDialog::slotUser2() +{ + m_emited=true; + KURL url(m_view->m_saveto->text()); + if(url.isValid() && url.isLocalFile() ) + { + const QString directory=url.directory(); + if(!directory.isEmpty()) + { + KGlobal::config()->setGroup("File Transfer"); + KGlobal::config()->writeEntry("defaultPath" , directory ); + } + + if(QFile(m_view->m_saveto->text()).exists()) + { + int ret=KMessageBox::warningContinueCancel(this, i18n("The file '%1' already exists.\nDo you want to overwrite it ?").arg(m_view->m_saveto->text()) , + i18n("Overwrite File") , KStdGuiItem::save()); + if(ret==KMessageBox::Cancel) + return; + } + + emit accepted(m_info,m_view->m_saveto->text()); + close(); + } + else + KMessageBox::queuedMessageBox (this, KMessageBox::Sorry, i18n("You must provide a valid local filename") ); +} + +void KopeteFileConfirmDialog::slotUser1() +{ + m_emited=true; + emit refused(m_info); + close(); +} + +void KopeteFileConfirmDialog::closeEvent( QCloseEvent *e) +{ + if(!m_emited) + { + m_emited=true; + emit refused(m_info); + } + KDialogBase::closeEvent(e); +} + +#include "kopetefileconfirmdialog.moc" + diff --git a/kopete/libkopete/ui/kopetefileconfirmdialog.h b/kopete/libkopete/ui/kopetefileconfirmdialog.h new file mode 100644 index 00000000..20d58d51 --- /dev/null +++ b/kopete/libkopete/ui/kopetefileconfirmdialog.h @@ -0,0 +1,57 @@ +/* + kopetefileconfirmdialog.h + + Copyright (c) 2003 by Olivier Goffart + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEFILECONFIRMDIALOG_H +#define KOPETEFILECONFIRMDIALOG_H + +#include +#include +#include "kopetetransfermanager.h" + +class FileConfirmBase; + +/** + *@author Olivier Goffart + */ + +class KopeteFileConfirmDialog : public KDialogBase +{ +Q_OBJECT + +public: + KopeteFileConfirmDialog(const Kopete::FileTransferInfo &info,const QString& description=QString::null, QWidget *parent=0, const char* name=0); + ~KopeteFileConfirmDialog(); + +private: + FileConfirmBase* m_view; + Kopete::FileTransferInfo m_info; + bool m_emited; + +public slots: + void slotBrowsePressed(); + +protected slots: + virtual void slotUser2(); + virtual void slotUser1(); + virtual void closeEvent( QCloseEvent *e); + +signals: + void accepted(const Kopete::FileTransferInfo &info, const QString &filename); + void refused(const Kopete::FileTransferInfo &info); +}; + +#endif diff --git a/kopete/libkopete/ui/kopetelistview.cpp b/kopete/libkopete/ui/kopetelistview.cpp new file mode 100644 index 00000000..594f0920 --- /dev/null +++ b/kopete/libkopete/ui/kopetelistview.cpp @@ -0,0 +1,215 @@ +/* + kopetelistview.cpp - List View providing support for ListView::Items + + Copyright (c) 2004 by Engin AYDOGAN + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopetelistview.h" +#include "kopetelistviewitem.h" +#include "kopeteuiglobal.h" +#include "kopeteglobal.h" +#include "kopeteprefs.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace Kopete { +namespace UI { +namespace ListView { + +/* + Custom QToolTip for the list view. + The decision whether or not to show tooltips is taken in + maybeTip(). See also the QListView sources from Qt itself. + Delegates to the list view items. +*/ +class ToolTip : public QToolTip +{ +public: + ToolTip( QWidget *parent, ListView *lv ); + virtual ~ToolTip(); + + void maybeTip( const QPoint &pos ); + +private: + ListView *m_listView; +}; + +ToolTip::ToolTip( QWidget *parent, ListView *lv ) + : QToolTip( parent ) +{ + m_listView = lv; +} + +ToolTip::~ToolTip() +{ +} + +void ToolTip::maybeTip( const QPoint &pos ) +{ + if( !parentWidget() || !m_listView ) + return; + + if( Item *item = dynamic_cast( m_listView->itemAt( pos ) ) ) + { + QRect itemRect = m_listView->itemRect( item ); + + uint leftMargin = m_listView->treeStepSize() * + ( item->depth() + ( m_listView->rootIsDecorated() ? 1 : 0 ) ) + + m_listView->itemMargin(); + + uint xAdjust = itemRect.left() + leftMargin; + uint yAdjust = itemRect.top(); + QPoint relativePos( pos.x() - xAdjust, pos.y() - yAdjust ); + + std::pair toolTip = item->toolTip( relativePos ); + if ( toolTip.first.isEmpty() ) + return; + + toolTip.second.moveBy( xAdjust, yAdjust ); +// kdDebug( 14000 ) << k_funcinfo << "Adding tooltip: itemRect: " +// << toolTip.second << ", tooltip: " << toolTip.first << endl; + tip( toolTip.second, toolTip.first ); + } +} + +struct ListView::Private +{ + QTimer sortTimer; + std::auto_ptr toolTip; + //! C-tor + Private() {} +}; + +ListView::ListView( QWidget *parent, const char *name ) + : KListView( parent, name ), d( new Private ) +{ + connect( &d->sortTimer, SIGNAL( timeout() ), this, SLOT( slotSort() ) ); + + // We have our own tooltips, don't use the default QListView ones + setShowToolTips( false ); + d->toolTip.reset( new ToolTip( viewport(), this ) ); + + connect( this, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), + SLOT( slotContextMenu( KListView *, QListViewItem *, const QPoint & ) ) ); + connect( this, SIGNAL( doubleClicked( QListViewItem * ) ), + SLOT( slotDoubleClicked( QListViewItem * ) ) ); + + // set up flags for nicer painting + clearWFlags( WStaticContents ); + setWFlags( WNoAutoErase ); + + // clear the appropriate flags from the viewport - qt docs say we have to mask + // these flags out of the QListView to make weirdly painted list items work, but + // that doesn't do the job. masking them out of the viewport does. +// class MyWidget : public QWidget { public: using QWidget::clearWFlags; }; +// static_cast( viewport() )->clearWFlags( WStaticContents ); +// static_cast( viewport() )->setWFlags( WNoAutoErase ); + + // The above causes compiler errors with the (broken) native TRU64 and IRIX compilers. + // This should make it compile for both platforms and still seems to work. + // This is, of course, a nasty hack, but it works, so... + static_cast(viewport())->clearWFlags( WStaticContents ); + static_cast(viewport())->setWFlags( WNoAutoErase ); +} + +ListView::~ListView() +{ + delete d; +} + +void ListView::slotDoubleClicked( QListViewItem *item ) +{ + kdDebug( 14000 ) << k_funcinfo << endl; + + if ( item ) + setOpen( item, !isOpen( item ) ); +} + +void ListView::slotContextMenu( KListView * /*listview*/, + QListViewItem *item, const QPoint &/*point*/ ) +{ + if ( item && !item->isSelected() ) + { + clearSelection(); + item->setSelected( true ); + } + if ( !item ) + clearSelection(); + +// if( Item *myItem = dynamic_cast( item ) ) + ;// TODO: myItem->contextMenu( point ); +} + +void ListView::setShowTreeLines( bool bShowAsTree ) +{ + if ( bShowAsTree ) + { + setRootIsDecorated( true ); + setTreeStepSize( 20 ); + } + else + { + setRootIsDecorated( false ); + setTreeStepSize( 0 ); + } + // TODO: relayout all items. their width may have changed, but they won't know about it. +} + +/* This is a small hack ensuring that only F2 triggers inline + * renaming. Won't win a beauty award, but whoever wrote it thinks + * relying on the fact that QListView intercepts and processes the + * F2 event through this event filter is sorta safe. + * + * Also use enter to execute the item since executed is not usually + * called when enter is pressed. + */ +void ListView::keyPressEvent( QKeyEvent *e ) +{ + QListViewItem *item = currentItem(); + if ( (e->key() == Qt::Key_F2) && item && item->isVisible() ) + rename( item, 0 ); + else if ( (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) && item && item->isVisible() ) + { + // must provide a point within the item; emitExecute checks for this + QPoint p = viewport()->mapToGlobal(itemRect(item).center()); + emitExecute( currentItem(), p, 0 ); + } + else + KListView::keyPressEvent(e); +} + +void ListView::delayedSort() +{ + if ( !d->sortTimer.isActive() ) + d->sortTimer.start( 500, true ); +} + +} // END namespace ListView +} // END namespace UI +} // END namespace Kopete + +#include "kopetelistview.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/kopetelistview.h b/kopete/libkopete/ui/kopetelistview.h new file mode 100644 index 00000000..8b2c579b --- /dev/null +++ b/kopete/libkopete/ui/kopetelistview.h @@ -0,0 +1,74 @@ +/* + kopetelistview.h - List View providing extra support for ListView::Items + + Copyright (c) 2005 by Engin AYDOGAN + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_LISTVIEW_H +#define KOPETE_LISTVIEW_H + +#include + +namespace Kopete { +namespace UI { +namespace ListView { + +/** + * @author Engin AYDOGAN + * @author Richard Smith + */ +class ListView : public KListView +{ + Q_OBJECT + +public: + ListView( QWidget *parent = 0, const char *name = 0 ); + ~ListView(); + + /** + * Schedule a delayed sort operation. Sorts will be withheld for at most + * half a second, after which they will be performed. This way multiple + * sort calls can be safely bundled without writing complex code to avoid + * the sorts entirely. + */ + void delayedSort(); + + /** + * Set whether to show the lines and +/- boxes in the tree + */ + void setShowTreeLines( bool bShowAsTree ); + +public slots: + /** + * Calls QListView::sort() + */ + void slotSort() { sort(); } +protected: + virtual void keyPressEvent( QKeyEvent *e ); +private slots: + void slotContextMenu(KListView*,QListViewItem *item, const QPoint &point ); + void slotDoubleClicked( QListViewItem *item ); +private: + struct Private; + Private *d; +}; + +} // END namespace ListView +} // END namespace UI +} // END namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/kopetelistviewitem.cpp b/kopete/libkopete/ui/kopetelistviewitem.cpp new file mode 100644 index 00000000..fda2ff4c --- /dev/null +++ b/kopete/libkopete/ui/kopetelistviewitem.cpp @@ -0,0 +1,1314 @@ +/* + kopetelistviewitem.cpp - Kopete's modular QListViewItems + + Copyright (c) 2005 by Engin AYDOGAN + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "kopetecontact.h" +#include "kopetelistviewitem.h" +#include "kopeteemoticons.h" +#include "kopeteonlinestatus.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_XRENDER +# include +# include +#endif + +#include + +namespace Kopete { +namespace UI { +namespace ListView { + +// ComponentBase -------- + +class ComponentBase::Private +{ +public: + QPtrList components; +}; + +ComponentBase::ComponentBase() + : d( new Private ) +{ +} + +ComponentBase::~ComponentBase() +{ + d->components.setAutoDelete( true ); + delete d; +} + +uint ComponentBase::components() { return d->components.count(); } +Component *ComponentBase::component( uint n ) { return d->components.at( n ); } + +Component *ComponentBase::componentAt( const QPoint &pt ) +{ + for ( uint n = 0; n < components(); ++n ) + { + if ( component( n )->rect().contains( pt ) ) + { + if ( Component *comp = component( n )->componentAt( pt ) ) + return comp; + return component( n ); + } + } + return 0; +} + +void ComponentBase::componentAdded( Component *component ) +{ + d->components.append( component ); +} + +void ComponentBase::componentRemoved( Component *component ) +{ + //TODO: make sure the component is in d->components once and only once. + // if not, the situation is best referred to as 'very very broken indeed'. + d->components.remove( component ); +} + +void ComponentBase::clear() +{ + /* I'm switching setAutoDelete back and forth instead of turning it + * on permenantly, because original author of this class set it to + * auto delete in the dtor, that might have a reason that I can't + * imagine right now */ + bool tmp = d->components.autoDelete(); + d->components.setAutoDelete( true ); + d->components.clear(); + d->components.setAutoDelete( tmp ); +} + +void ComponentBase::componentResized( Component * ) +{ +} + +std::pair ComponentBase::toolTip( const QPoint &relativePos ) +{ + for ( uint n = 0; n < components(); ++n ) + if ( component( n )->rect().contains( relativePos ) ) + return component( n )->toolTip( relativePos ); + + return std::make_pair( QString::null, QRect() ); +} + +void ComponentBase::updateAnimationPosition( int p, int s ) +{ + for ( uint n = 0; n < components(); ++n ) + { + Component *comp = component( n ); + QRect start = comp->startRect(); + QRect target = comp->targetRect(); + QRect rc( start.left() + ((target.left() - start.left()) * p) / s, + start.top() + ((target.top() - start.top()) * p) / s, + start.width() + ((target.width() - start.width()) * p) / s, + start.height() + ((target.height() - start.height()) * p) / s ); + comp->setRect( rc ); + comp->updateAnimationPosition( p, s ); + } +} + +// Component -------- + +class Component::Private +{ +public: + Private( ComponentBase *parent ) + : parent( parent ), minWidth( 0 ), minHeight( 0 ) + , growHoriz( false ), growVert( false ) + , tipSource( 0 ) + { + } + ComponentBase *parent; + QRect rect; + QRect startRect, targetRect; + int minWidth, minHeight; + bool growHoriz, growVert; + bool show; /** @since 23-03-2005 */ + ToolTipSource *tipSource; +}; + +Component::Component( ComponentBase *parent ) + : d( new Private( parent ) ) +{ + d->parent->componentAdded( this ); + d->show = true; +} + +int Component::RTTI = Rtti_Component; + +Component::~Component() +{ + d->parent->componentRemoved( this ); + delete d; +} + + +void Component::hide() +{ + d->show = false; +} + +void Component::show() +{ + d->show = true; +} + +bool Component::isShown() +{ + return d->show; +} + +bool Component::isHidden() +{ + return !d->show; +} + +void Component::setToolTipSource( ToolTipSource *source ) +{ + d->tipSource = source; +} + +std::pair Component::toolTip( const QPoint &relativePos ) +{ + if ( !d->tipSource ) + return ComponentBase::toolTip( relativePos ); + + QRect rc = rect(); + QString result = (*d->tipSource)( this, relativePos, rc ); + return std::make_pair(result, rc); +} + +QRect Component::rect() { return d->rect; } +QRect Component::startRect() { return d->startRect; } +QRect Component::targetRect() { return d->targetRect; } + +int Component::minWidth() { return d->minWidth; } +int Component::minHeight() { return d->minHeight; } +int Component::widthForHeight( int ) { return minWidth(); } +int Component::heightForWidth( int ) { return minHeight(); } + +bool Component::setMinWidth( int width ) +{ + if ( d->minWidth == width ) return false; + d->minWidth = width; + + d->parent->componentResized( this ); + return true; +} +bool Component::setMinHeight( int height ) +{ + if ( d->minHeight == height ) return false; + d->minHeight = height; + + d->parent->componentResized( this ); + return true; +} + +void Component::layout( const QRect &newRect ) +{ + if ( rect().isNull() ) + d->startRect = QRect( newRect.topLeft(), newRect.topLeft() ); + else + d->startRect = rect(); + d->targetRect = newRect; + //kdDebug(14000) << k_funcinfo << "At " << rect << endl; +} + +void Component::setRect( const QRect &rect ) +{ + d->rect = rect; +} + +void Component::paint( QPainter *painter, const QColorGroup &cg ) +{ + /*painter->setPen( Qt::red ); + painter->drawRect( rect() );*/ + for ( uint n = 0; n < components(); ++n ) + { + if( component( n )->isShown() ) + component( n )->paint( painter, cg ); + } +} + +void Component::repaint() +{ + d->parent->repaint(); +} + +void Component::relayout() +{ + d->parent->relayout(); +} + +void Component::componentAdded( Component *component ) +{ + ComponentBase::componentAdded( component ); + //update( Relayout ); +} + +void Component::componentRemoved( Component *component ) +{ + ComponentBase::componentRemoved( component ); + //update( Relayout ); +} + +// BoxComponent -------- + +class BoxComponent::Private +{ +public: + Private( BoxComponent::Direction dir ) : direction( dir ) {} + BoxComponent::Direction direction; + + static const int padding = 2; +}; + +BoxComponent::BoxComponent( ComponentBase *parent, Direction dir ) + : Component( parent ), d( new Private( dir ) ) +{ +} + +int BoxComponent::RTTI = Rtti_BoxComponent; + +BoxComponent::~BoxComponent() +{ + delete d; +} + +int BoxComponent::widthForHeight( int height ) +{ + if ( d->direction != Horizontal ) + { + int width = 0; + for ( uint n = 0; n < components(); ++n ) + width = QMAX( width, component( n )->widthForHeight( height ) ); + return width; + } + else + { + int width = (components() - 1) * Private::padding; + for ( uint n = 0; n < components(); ++n ) + width += component( n )->widthForHeight( height ); + return width; + } +} + +int BoxComponent::heightForWidth( int width ) +{ + if ( d->direction == Horizontal ) + { + int height = 0; + for ( uint n = 0; n < components(); ++n ) + height = QMAX( height, component( n )->heightForWidth( width ) ); + return height; + } + else + { + int height = (components() - 1) * Private::padding; + for ( uint n = 0; n < components(); ++n ) + height += component( n )->heightForWidth( width ); + return height; + } +} + +void BoxComponent::calcMinSize() +{ + int sum = (components() - 1) * Private::padding, max = 0; + for ( uint n = 0; n < components(); ++n ) + { + Component *comp = component( n ); + if ( d->direction == Horizontal ) + { + max = QMAX( max, comp->minHeight() ); + sum += comp->minWidth(); + } + else + { + max = QMAX( max, comp->minWidth() ); + sum += comp->minHeight(); + } + } + + bool sizeChanged = false; + if ( d->direction == Horizontal ) + { + if ( setMinWidth( sum ) ) sizeChanged = true; + if ( setMinHeight( max ) ) sizeChanged = true; + } + else + { + if ( setMinWidth( max ) ) sizeChanged = true; + if ( setMinHeight( sum ) ) sizeChanged = true; + } + + if ( sizeChanged ) + repaint(); + else + relayout(); +} + +void BoxComponent::layout( const QRect &rect ) +{ + Component::layout( rect ); + + bool horiz = (d->direction == Horizontal); + int fixedSize = 0; + for ( uint n = 0; n < components(); ++n ) + { + Component *comp = component( n ); + if ( horiz ) + fixedSize += comp->minWidth(); + else + fixedSize += comp->minHeight(); + } + + // remaining space after all fixed items have been allocated + int padding = Private::padding; + + // ensure total is at least minXXX. the only time the rect + // will be smaller than that is when we don't fit, and in + // that cases we should pretend that we're wide/high enough. + int total; + if ( horiz ) + total = QMAX( rect.width(), minWidth() ); + else + total = QMAX( rect.height(), minHeight() ); + + int remaining = total - fixedSize - padding * (components() - 1); + + // finally, lay everything out + int pos = 0; + for ( uint n = 0; n < components(); ++n ) + { + Component *comp = component( n ); + + QRect rc; + if ( horiz ) + { + rc.setLeft( rect.left() + pos ); + rc.setTop( rect.top() ); + rc.setHeight( rect.height() ); + int minWidth = comp->minWidth(); + int desiredWidth = comp->widthForHeight( rect.height() ); + rc.setWidth( QMIN( remaining + minWidth, desiredWidth ) ); + pos += rc.width(); + remaining -= rc.width() - minWidth; + } + else + { + rc.setLeft( rect.left() ); + rc.setTop( rect.top() + pos ); + rc.setWidth( rect.width() ); + int minHeight = comp->minHeight(); + int desiredHeight = comp->heightForWidth( rect.width() ); + rc.setHeight( QMIN( remaining + minHeight, desiredHeight ) ); + pos += rc.height(); + remaining -= rc.height() - minHeight; + } + comp->layout( rc & rect ); + pos += padding; + } +} + +void BoxComponent::componentAdded( Component *component ) +{ + Component::componentAdded( component ); + calcMinSize(); +} + +void BoxComponent::componentRemoved( Component *component ) +{ + Component::componentRemoved( component ); + calcMinSize(); +} + +void BoxComponent::componentResized( Component *component ) +{ + Component::componentResized( component ); + calcMinSize(); +} + +// ImageComponent -------- + +class ImageComponent::Private +{ +public: + QPixmap image; +}; + +ImageComponent::ImageComponent( ComponentBase *parent ) + : Component( parent ), d( new Private ) +{ +} + +int ImageComponent::RTTI = Rtti_ImageComponent; + +ImageComponent::ImageComponent( ComponentBase *parent, int minW, int minH ) + : Component( parent ), d( new Private ) +{ + setMinWidth( minW ); + setMinHeight( minH ); + repaint(); +} + +ImageComponent::~ImageComponent() +{ + delete d; +} + +QPixmap ImageComponent::pixmap() +{ + return d->image; +} + +void ImageComponent::setPixmap( const QPixmap &img, bool adjustSize) +{ + d->image = img; + if ( adjustSize ) + { + setMinWidth( img.width() ); + setMinHeight( img.height() ); + } + repaint(); +} + +static QPoint operator+( const QPoint &pt, const QSize &sz ) +{ + return QPoint( pt.x() + sz.width(), pt.y() + sz.height() ); +} + +/*static QPoint operator+( const QSize &sz, const QPoint &pt ) +{ + return pt + sz; +}*/ + +void ImageComponent::paint( QPainter *painter, const QColorGroup & ) +{ + QRect ourRc = rect(); + QRect rc = d->image.rect(); + // center rc within our rect + rc.moveTopLeft( ourRc.topLeft() + (ourRc.size() - rc.size()) / 2 ); + // paint, shrunk to be within our rect + painter->drawPixmap( rc & ourRc, d->image ); +} + +void ImageComponent::scale( int w, int h, QImage::ScaleMode mode ) +{ + QImage im = d->image.convertToImage(); + setPixmap( QPixmap( im.smoothScale( w, h, mode ) ) ); +} +// TextComponent + +class TextComponent::Private +{ +public: + Private() : customColor( false ) {} + QString text; + bool customColor; + QColor color; + QFont font; +}; + +TextComponent::TextComponent( ComponentBase *parent, const QFont &font, const QString &text ) + : Component( parent ), d( new Private ) +{ + setFont( font ); + setText( text ); +} + +int TextComponent::RTTI = Rtti_TextComponent; + +TextComponent::~TextComponent() +{ + delete d; +} + +QString TextComponent::text() +{ + return d->text; +} + +void TextComponent::setText( const QString &text ) +{ + if ( text == d->text ) return; + d->text = text; + relayout(); + calcMinSize(); +} + +QFont TextComponent::font() +{ + return d->font; +} + +void TextComponent::setFont( const QFont &font ) +{ + if ( font == d->font ) return; + d->font = font; + calcMinSize(); +} + +void TextComponent::calcMinSize() +{ + setMinWidth( 0 ); + + if ( !d->text.isEmpty() ) + setMinHeight( QFontMetrics( font() ).height() ); + else + setMinHeight( 0 ); + + repaint(); +} + +int TextComponent::widthForHeight( int ) +{ + // add 2 to place an extra gap between the text and things to its right. + // allegedly if this is not done the protocol icons overlap the text. + // i however have never seen this problem (which would almost certainly + // be a bug somewhere else). + return QFontMetrics( font() ).width( d->text ) + 2; +} + +QColor TextComponent::color() +{ + return d->customColor ? d->color : QColor(); +} + +void TextComponent::setColor( const QColor &color ) +{ + d->color = color; + d->customColor = true; + repaint(); +} + +void TextComponent::setDefaultColor() +{ + d->customColor = false; + repaint(); +} + +void TextComponent::paint( QPainter *painter, const QColorGroup &cg ) +{ + if ( d->customColor ) + painter->setPen( d->color ); + else + painter->setPen( cg.text() ); + QString dispStr = KStringHandler::rPixelSqueeze( d->text, QFontMetrics( font() ), rect().width() ); + painter->setFont( font() ); + painter->drawText( rect(), Qt::SingleLine, dispStr ); +} + +// DisplayNameComponent + +class DisplayNameComponent::Private +{ +public: + QString text; + QFont font; +}; + +DisplayNameComponent::DisplayNameComponent( ComponentBase *parent ) + : BoxComponent( parent ), d( new Private ) +{ +} + +int DisplayNameComponent::RTTI = Rtti_DisplayNameComponent; + +DisplayNameComponent::~DisplayNameComponent() +{ + delete d; +} + +void DisplayNameComponent::layout( const QRect &rect ) +{ + Component::layout( rect ); + + // finally, lay everything out + QRect rc; + int totalWidth = rect.width(); + int usedWidth = 0; + bool exceeded = false; + for ( uint n = 0; n < components(); ++n ) + { + Component *comp = component( n ); + if ( !exceeded ) + { + if ( ( usedWidth + comp->widthForHeight( rect.height() ) ) > totalWidth ) + { + exceeded = true; + // TextComponents can squeeze themselves + if ( comp->rtti() == Rtti_TextComponent ) + { + comp->show(); + comp->layout( QRect( usedWidth+ rect.left(), rect.top(), + totalWidth - usedWidth, + comp->heightForWidth( totalWidth - usedWidth ) ) ); + } else { + comp->hide(); + } + } + else + { + comp->show(); + comp->layout( QRect( usedWidth+ rect.left(), rect.top(), + comp->widthForHeight( rect.height() ), + comp->heightForWidth( rect.width() ) ) ); + } + usedWidth+= comp->widthForHeight( rect.height() ); + } + else + { + // Shall we implement a hide()/show() in Component class ? + comp->hide(); + } + } +} + +void DisplayNameComponent::setText( const QString& text ) +{ + if ( d->text == text ) + return; + d->text = text; + + redraw(); +} + +void DisplayNameComponent::redraw() +{ + QColor color; + for ( uint n = 0; n < components(); ++n ) + if( component( n )->rtti() == Rtti_TextComponent ) + { + ((TextComponent*)component(n))->color(); + } + + QValueList tokens; + QValueList::const_iterator token; + + clear(); // clear childs + + tokens = Kopete::Emoticons::tokenizeEmoticons( d->text ); + ImageComponent *ic; + TextComponent *t; + + QFontMetrics fontMetrics( d->font ); + int fontHeight = fontMetrics.height(); + for ( token = tokens.begin(); token != tokens.end(); ++token ) + { + switch ( (*token).type ) + { + case Kopete::Emoticons::Text: + t = new TextComponent( this, d->font, (*token).text ); + break; + case Kopete::Emoticons::Image: + ic = new ImageComponent( this ); + ic->setPixmap( QPixmap( (*token).picPath ) ); + ic->scale( INT_MAX, fontHeight, QImage::ScaleMin ); + break; + default: + kdDebug( 14010 ) << k_funcinfo << "This should have not happened!" << endl; + } + } + + if(color.isValid()) + setColor( color ); +} + +void DisplayNameComponent::setFont( const QFont& font ) +{ + for ( uint n = 0; n < components(); ++n ) + if( component( n )->rtti() == Rtti_TextComponent ) + ((TextComponent*)component(n))->setFont( font ); + d->font = font; +} + +void DisplayNameComponent::setColor( const QColor& color ) +{ + for ( uint n = 0; n < components(); ++n ) + if( component( n )->rtti() == Rtti_TextComponent ) + ((TextComponent*)component(n))->setColor( color ); +} + +void DisplayNameComponent::setDefaultColor() +{ + for ( uint n = 0; n < components(); ++n ) + if( component( n )->rtti() == Rtti_TextComponent ) + ((TextComponent*)component(n))->setDefaultColor(); +} + +QString DisplayNameComponent::text() +{ + return d->text; +} + +// HSpacerComponent -------- + +HSpacerComponent::HSpacerComponent( ComponentBase *parent ) + : Component( parent ) +{ + setMinWidth( 0 ); + setMinHeight( 0 ); +} + +int HSpacerComponent::RTTI = Rtti_HSpacerComponent; + +int HSpacerComponent::widthForHeight( int ) +{ + return INT_MAX; +} + +// VSpacerComponent -------- + +VSpacerComponent::VSpacerComponent( ComponentBase *parent ) + : Component( parent ) +{ + setMinWidth( 0 ); + setMinHeight( 0 ); +} + +int VSpacerComponent::RTTI = Rtti_VSpacerComponent; + +int VSpacerComponent::heightForWidth( int ) +{ + return INT_MAX; +} + +////////////////// ContactComponent ///////////////////////// + +class ContactComponent::Private +{ +public: + Kopete::Contact *contact; + int iconSize; +}; + +ContactComponent::ContactComponent( ComponentBase *parent, Kopete::Contact *contact, int iconSize) : ImageComponent( parent ) , d( new Private ) +{ + d->contact = contact; + d->iconSize = iconSize; + updatePixmap(); +} + +ContactComponent::~ContactComponent() +{ + delete d; +} + +void ContactComponent::updatePixmap() +{ + setPixmap( contact()->onlineStatus().iconFor( contact(), d->iconSize ) ); +} +Kopete::Contact *ContactComponent::contact() +{ + return d->contact; +} + +// we don't need to use a tooltip source here - this way is simpler +std::pair ContactComponent::toolTip( const QPoint &/*relativePos*/ ) +{ + return std::make_pair(d->contact->toolTip(),rect()); +} + +////////////////// SpacerComponent ///////////////////////// + +SpacerComponent::SpacerComponent( ComponentBase *parent, int w, int h ) : Component( parent ) +{ + setMinWidth(w); + setMinHeight(h); +} + +// Item -------- + +/** + * A periodic timer intended to be shared amongst multiple objects. Will run only + * if an object is attached to it. + */ +class SharedTimer : private QTimer +{ + int period; + int users; +public: + SharedTimer( int period ) : period(period), users(0) {} + void attach( QObject *target, const char *slot ) + { + connect( this, SIGNAL(timeout()), target, slot ); + if( users++ == 0 ) + start( period ); + //kdDebug(14000) << "SharedTimer::attach: users is now " << users << "\n"; + } + void detach( QObject *target, const char *slot ) + { + disconnect( this, SIGNAL(timeout()), target, slot ); + if( --users == 0 ) + stop(); + //kdDebug(14000) << "SharedTimer::detach: users is now " << users << "\n"; + } +}; + +class SharedTimerRef +{ + SharedTimer &timer; + QObject * const object; + const char * const slot; + bool attached; +public: + SharedTimerRef( SharedTimer &timer, QObject *obj, const char *slot ) + : timer(timer), object(obj), slot(slot), attached(false) + { + } + void start() + { + if( attached ) return; + timer.attach( object, slot ); + attached = true; + } + void stop() + { + if( !attached ) return; + timer.detach( object, slot ); + attached = false; + } + bool isActive() + { + return attached; + } +}; + +class Item::Private +{ +public: + Private( Item *item ) + : layoutAnimateTimer( theLayoutAnimateTimer(), item, SLOT( slotLayoutAnimateItems() ) ) + , animateLayout( true ), opacity( 1.0 ) + , visibilityTimer( theVisibilityTimer(), item, SLOT( slotUpdateVisibility() ) ) + , visibilityLevel( 0 ), visibilityTarget( false ), searchMatch( true ) + { + } + + QTimer layoutTimer; + + //QTimer layoutAnimateTimer; + SharedTimerRef layoutAnimateTimer; + SharedTimer &theLayoutAnimateTimer() + { + static SharedTimer timer( 10 ); + return timer; + } + + bool animateLayout; + int layoutAnimateSteps; + static const int layoutAnimateStepsTotal = 10; + + float opacity; + + //QTimer visibilityTimer; + SharedTimerRef visibilityTimer; + SharedTimer &theVisibilityTimer() + { + static SharedTimer timer( 40 ); + return timer; + } + + int visibilityLevel; + bool visibilityTarget; + static const int visibilityFoldSteps = 7; +#ifdef HAVE_XRENDER + static const int visibilityFadeSteps = 7; +#else + static const int visibilityFadeSteps = 0; +#endif + static const int visibilityStepsTotal = visibilityFoldSteps + visibilityFadeSteps; + + bool searchMatch; + + static bool animateChanges; + static bool fadeVisibility; + static bool foldVisibility; +}; + +bool Item::Private::animateChanges = true; +bool Item::Private::fadeVisibility = true; +bool Item::Private::foldVisibility = true; + +Item::Item( QListViewItem *parent, QObject *owner, const char *name ) + : QObject( owner, name ), KListViewItem( parent ), d( new Private(this) ) +{ + initLVI(); +} + +Item::Item( QListView *parent, QObject *owner, const char *name ) + : QObject( owner, name ), KListViewItem( parent ), d( new Private(this) ) +{ + initLVI(); +} + +Item::~Item() +{ + delete d; +} + +void Item::setEffects( bool animation, bool fading, bool folding ) +{ + Private::animateChanges = animation; + Private::fadeVisibility = fading; + Private::foldVisibility = folding; +} + +void Item::initLVI() +{ + connect( listView()->header(), SIGNAL( sizeChange( int, int, int ) ), SLOT( slotColumnResized() ) ); + connect( &d->layoutTimer, SIGNAL( timeout() ), SLOT( slotLayoutItems() ) ); + //connect( &d->layoutAnimateTimer, SIGNAL( timeout() ), SLOT( slotLayoutAnimateItems() ) ); + //connect( &d->visibilityTimer, SIGNAL( timeout() ), SLOT( slotUpdateVisibility() ) ); + setVisible( false ); + setTargetVisibility( true ); +} + +void Item::slotColumnResized() +{ + scheduleLayout(); + // if we've been resized, don't animate the layout + d->animateLayout = false; +} + +void Item::scheduleLayout() +{ + // perform a delayed layout in order to speed it all up + if ( ! d->layoutTimer.isActive() ) + d->layoutTimer.start( 30, true ); +} + +void Item::slotLayoutItems() +{ + d->layoutTimer.stop(); + + for ( uint n = 0; n < components(); ++n ) + { + int width = listView()->columnWidth(n); + if ( n == 0 ) + { + int d = depth() + (listView()->rootIsDecorated() ? 1 : 0); + width -= d * listView()->treeStepSize(); + } + + int height = component( n )->heightForWidth( width ); + component( n )->layout( QRect( 0, 0, width, height ) ); + //kdDebug(14000) << k_funcinfo << "Component " << n << " is " << width << " x " << height << endl; + } + + if ( Private::animateChanges && d->animateLayout && !d->visibilityTimer.isActive() ) + { + d->layoutAnimateTimer.start(); + //if ( !d->layoutAnimateTimer.isActive() ) + // d->layoutAnimateTimer.start( 10 ); + d->layoutAnimateSteps = 0; + } + else + { + d->layoutAnimateSteps = Private::layoutAnimateStepsTotal; + d->animateLayout = true; + } + slotLayoutAnimateItems(); +} + +void Item::slotLayoutAnimateItems() +{ + if ( ++d->layoutAnimateSteps >= Private::layoutAnimateStepsTotal ) + d->layoutAnimateTimer.stop(); + + const int s = Private::layoutAnimateStepsTotal; + const int p = QMIN( d->layoutAnimateSteps, s ); + + updateAnimationPosition( p, s ); + setHeight(0); + repaint(); +} + +float Item::opacity() +{ + return d->opacity; +} + +void Item::setOpacity( float opacity ) +{ + if ( d->opacity == opacity ) return; + d->opacity = opacity; + repaint(); +} + +void Item::setSearchMatch( bool match ) +{ + d->searchMatch = match; + + if ( !match ) + setVisible( false ); + else + { + kdDebug(14000) << k_funcinfo << " match: " << match << ", vis timer active: " << d->visibilityTimer.isActive() + << ", target visibility: " << targetVisibility() << endl; + if ( d->visibilityTimer.isActive() ) + setVisible( true ); + else + setVisible( targetVisibility() ); + } +} + +bool Item::targetVisibility() +{ + return d->visibilityTarget; +} + +void Item::setTargetVisibility( bool vis ) +{ + if ( d->visibilityTarget == vis ) + { + // in case we're getting called because our parent was shown and + // we need to be rehidden + if ( !d->visibilityTimer.isActive() ) + setVisible( vis && d->searchMatch ); + return; + } + d->visibilityTarget = vis; + d->visibilityTimer.start(); + //d->visibilityTimer.start( 40 ); + if ( targetVisibility() ) + setVisible( d->searchMatch ); + slotUpdateVisibility(); +} + +void Item::slotUpdateVisibility() +{ + if ( targetVisibility() ) + ++d->visibilityLevel; + else + --d->visibilityLevel; + + if ( !Private::foldVisibility && !Private::fadeVisibility ) + d->visibilityLevel = targetVisibility() ? Private::visibilityStepsTotal : 0; + else if ( !Private::fadeVisibility && d->visibilityLevel >= Private::visibilityFoldSteps ) + d->visibilityLevel = targetVisibility() ? Private::visibilityStepsTotal : Private::visibilityFoldSteps - 1; + else if ( !Private::foldVisibility && d->visibilityLevel <= Private::visibilityFoldSteps ) + d->visibilityLevel = targetVisibility() ? Private::visibilityFoldSteps + 1 : 0; + + if ( d->visibilityLevel >= Private::visibilityStepsTotal ) + { + d->visibilityLevel = Private::visibilityStepsTotal; + d->visibilityTimer.stop(); + } + else if ( d->visibilityLevel <= 0 ) + { + d->visibilityLevel = 0; + d->visibilityTimer.stop(); + setVisible( false ); + } + setHeight( 0 ); + repaint(); +} + +void Item::repaint() +{ + // if we're about to relayout, don't bother painting yet. + if ( d->layoutTimer.isActive() ) + return; + listView()->repaintItem( this ); +} + +void Item::relayout() +{ + scheduleLayout(); +} + +void Item::setup() +{ + KListViewItem::setup(); + slotLayoutItems(); +} + +void Item::setHeight( int ) +{ + int minHeight = 0; + for ( uint n = 0; n < components(); ++n ) + minHeight = QMAX( minHeight, component( n )->rect().height() ); + //kdDebug(14000) << k_funcinfo << "Height is " << minHeight << endl; + if ( Private::foldVisibility && d->visibilityTimer.isActive() ) + { + int vis = QMIN( d->visibilityLevel, Private::visibilityFoldSteps ); + minHeight = (minHeight * vis) / Private::visibilityFoldSteps; + } + KListViewItem::setHeight( minHeight ); +} + +int Item::width( const QFontMetrics &, const QListView *lv, int c ) const +{ + // Qt computes the itemRect from this. we want the whole item to be + // clickable, so we return the widest we could possibly be. + return lv->header()->sectionSize( c ); +} + +void Item::paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ) +{ + QPixmap back( width, height() ); + QPainter paint( &back ); + //KListViewItem::paintCell( &paint, cg, column, width, align ); + // PASTED FROM KLISTVIEWITEM: + // set the alternate cell background colour if necessary + QColorGroup _cg = cg; + if (isAlternate()) + if (listView()->viewport()->backgroundMode()==Qt::FixedColor) + _cg.setColor(QColorGroup::Background, static_cast< KListView* >(listView())->alternateBackground()); + else + _cg.setColor(QColorGroup::Base, static_cast< KListView* >(listView())->alternateBackground()); + // PASTED FROM QLISTVIEWITEM + { + QPainter *p = &paint; + + QListView *lv = listView(); + if ( !lv ) + return; + QFontMetrics fm( p->fontMetrics() ); + + // any text we render is done by the Components, not by this class, so make sure we've nothing to write + QString t; + + // removed text truncating code from Qt - we do that differently, further on + + int marg = lv->itemMargin(); + int r = marg; + // const QPixmap * icon = pixmap( column ); + + const BackgroundMode bgmode = lv->viewport()->backgroundMode(); + const QColorGroup::ColorRole crole = QPalette::backgroundRoleFromMode( bgmode ); + + if ( _cg.brush( crole ) != lv->colorGroup().brush( crole ) ) + p->fillRect( 0, 0, width, height(), _cg.brush( crole ) ); + else + { + // all copied from QListView::paintEmptyArea + + //lv->paintEmptyArea( p, QRect( 0, 0, width, height() ) ); + QStyleOption opt( lv->sortColumn(), 0 ); // ### hack; in 3.1, add a property in QListView and QHeader + QStyle::SFlags how = QStyle::Style_Default; + if ( lv->isEnabled() ) + how |= QStyle::Style_Enabled; + + lv->style().drawComplexControl( QStyle::CC_ListView, + p, lv, QRect( 0, 0, width, height() ), lv->colorGroup(), + how, QStyle::SC_ListView, QStyle::SC_None, + opt ); + } + + + + if ( isSelected() && + (column == 0 || lv->allColumnsShowFocus()) ) { + p->fillRect( r - marg, 0, width - r + marg, height(), + _cg.brush( QColorGroup::Highlight ) ); + // removed text pen setting code from Qt + } + + // removed icon drawing code from Qt + + // draw the tree gubbins + if ( multiLinesEnabled() && column == 0 && isOpen() && childCount() ) { + int textheight = fm.size( align, t ).height() + 2 * lv->itemMargin(); + textheight = QMAX( textheight, QApplication::globalStrut().height() ); + if ( textheight % 2 > 0 ) + textheight++; + if ( textheight < height() ) { + int w = lv->treeStepSize() / 2; + lv->style().drawComplexControl( QStyle::CC_ListView, p, lv, + QRect( 0, textheight, w + 1, height() - textheight + 1 ), _cg, + lv->isEnabled() ? QStyle::Style_Enabled : QStyle::Style_Default, + QStyle::SC_ListViewExpand, + (uint)QStyle::SC_All, QStyleOption( this ) ); + } + } + } + // END OF PASTE + + + //do you see a better way to tell the TextComponent we are selected ? - Olivier 2004-09-02 + if ( isSelected() ) + _cg.setColor(QColorGroup::Text , _cg.highlightedText() ); + + if ( Component *comp = component( column ) ) + comp->paint( &paint, _cg ); + paint.end(); + +#ifdef HAVE_XRENDER + QColor rgb = cg.base();//backgroundColor(); + float opac = 1.0; + if ( d->visibilityTimer.isActive() && Private::fadeVisibility ) + { + int vis = QMAX( d->visibilityLevel - Private::visibilityFoldSteps, 0 ); + opac = float(vis) / Private::visibilityFadeSteps; + } + opac *= opacity(); + const int alpha = 257 - int(opac * 257); + if ( alpha != 0 ) + { + XRenderColor clr = { alpha * rgb.red(), alpha * rgb.green(), alpha * rgb.blue(), alpha * 0xff }; + XRenderFillRectangle( back.x11Display(), PictOpOver, back.x11RenderHandle(), + &clr, 0, 0, width, height() ); + } +#endif + + p->drawPixmap( 0, 0, back ); +} + +void Item::componentAdded( Component *component ) +{ + ComponentBase::componentAdded( component ); + scheduleLayout(); +} + +void Item::componentRemoved( Component *component ) +{ + ComponentBase::componentRemoved( component ); + scheduleLayout(); +} + +void Item::componentResized( Component *component ) +{ + ComponentBase::componentResized( component ); + scheduleLayout(); +} + +} // END namespace ListView +} // END namespace UI +} // END namespace Kopete + +#include "kopetelistviewitem.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/kopetelistviewitem.h b/kopete/libkopete/ui/kopetelistviewitem.h new file mode 100644 index 00000000..5952c569 --- /dev/null +++ b/kopete/libkopete/ui/kopetelistviewitem.h @@ -0,0 +1,462 @@ +/* + kopetelistviewitem.h - Kopete's modular QListViewItems + + Copyright (c) 2005 by Engin AYDOGAN + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETE_LISTVIEWITEM_H +#define KOPETE_LISTVIEWITEM_H + +#include +#include + +#include +#include + +class QPixmap; + +namespace Kopete { +namespace UI { +namespace ListView { + +class Component; + +class ComponentBase +{ +public: + ComponentBase(); + virtual ~ComponentBase() = 0; + + uint components(); + Component *component( uint n ); + Component *componentAt( const QPoint &pt ); + + /** Repaint this item */ + virtual void repaint() = 0; + /** Relayout this item */ + virtual void relayout() = 0; + + /** + * Get the tool tip string and rectangle for a tip request at position + * relativePos relative to this item. Queries the appropriate child component. + * + * @return a pair where the first element is the tooltip, and the second is + * the rectangle within the item for which the tip should be displayed. + */ + virtual std::pair toolTip( const QPoint &relativePos ); + +protected: + /** A child item has been added to this item */ + virtual void componentAdded( Component *component ); + /** A child item has been removed from this item */ + virtual void componentRemoved( Component *component ); + /** A child item has been resized */ + virtual void componentResized( Component *component ); + /** Remove all children */ + virtual void clear(); + + /** @internal animate items */ + void updateAnimationPosition( int p, int s ); +private: + class Private; + Private *d; + + // calls componentAdded and componentRemoved + friend class Component; +}; + +/** + * @author Richard Smith + */ +class ToolTipSource +{ +public: + /** + * Get the tooltip string and rect for a component + * + * @param component The component to get a tip for + * @param pt The point (relative to the list item) the mouse is at + * @param rect The tip will be removed when the mouse leaves this rect. + * Will initially be set to \p component's rect(). + */ + virtual QString operator() ( ComponentBase *component, const QPoint &pt, QRect &rect ) = 0; +}; + +/** + * This class represents a rectangular subsection of a ListItem. + * + * @author Richard Smith + */ +class Component : public ComponentBase +{ +protected: + Component( ComponentBase *parent ); +public: + virtual ~Component() = 0; + + /** + * Set the size and position of this item relative to the list view item. Should + * only be called by the containing item. + * @param rect the new rectangle this component will paint in, relative to the painter + * passed to the paint() function by the parent item. + */ + virtual void layout( const QRect &rect ); + + /** + * Paint this item, inside the rectangle returned by rect(). + * The default implementation calls paint on all children. + */ + virtual void paint( QPainter *painter, const QColorGroup &cg ); + + void repaint(); + void relayout(); + + /** + * @return the rect this component was allocated last time it was laid out + */ + QRect rect(); + /** + * Prevents this component to be drawn + */ + void hide(); + + /** + * Makes this component to be drawn + */ + void show(); + + bool isShown(); + bool isHidden(); + + /** + * Returns the smallest this component can become horizontally while still + * being useful. + */ + int minWidth(); + /** + * Returns the smallest this component can become vertically while still + * being useful. + */ + int minHeight(); + + /** + * Returns the width this component desires for a given @a height. By default + * this function returns minWidth(). + */ + virtual int widthForHeight( int height ); + /** + * Returns the height this component desires for a given @a width. By default + * this function returns minHeight(). + */ + virtual int heightForWidth( int width ); + + /** + * Set a tool tip source for this item. The tool tip source object is + * still owned by the caller, and must live for at least as long as + * this component. + */ + void setToolTipSource( ToolTipSource *source = 0 ); + + /** + * Get the tool tip string and rectangle for a tip request at position + * relativePos relative to this item. If a tooltip source is set, it will + * be used. Otherwise calls the base class. + * + * @return a pair where the first element is the tooltip, and the second is + * the rectangle within the item for which the tip should be displayed. + */ + std::pair toolTip( const QPoint &relativePos ); + + /** + * RTTI: Runtime Type Information + * Exactly the same as Qt's approach to identify types of + * QCanvasItems. + */ + + enum RttiValues { + Rtti_Component, Rtti_BoxComponent, Rtti_TextComponent, + Rtti_ImageComponent, Rtti_DisplayNameComponent, + Rtti_HSpacerComponent, Rtti_VSpacerComponent + }; + + static int RTTI; + virtual int rtti() const { return RTTI; } +protected: + /** + * Change the minimum width, in pixels, this component requires in order + * to be at all useful. Note: do not call this from your layout() function. + * @param width the minimum width + * @return true if the size has actually changed, false if it's been set to + * the existing values. if it returns true, you do not need to relayout, + * since the parent component will do that for you. + */ + bool setMinWidth( int width ); + /** + * Change the minimum height, in pixels, this component requires in order + * to be at all useful. Note: do not call this from your layout() function. + * @param height the minimum height + * @return true if the size has actually changed, false if it's been set to + * the existing values. If it returns true, you do not need to relayout, + * since the parent component will do that for you. + */ + bool setMinHeight( int height ); + + void componentAdded( Component *component ); + void componentRemoved( Component *component ); + +private: + // calls the three functions below + friend void ComponentBase::updateAnimationPosition( int p, int s ); + + // used for animation + void setRect( const QRect &rect ); + QRect startRect(); + QRect targetRect(); + + class Private; + Private *d; +}; + +class BoxComponent : public Component +{ +public: + enum Direction { Horizontal, Vertical }; + BoxComponent( ComponentBase *parent, Direction dir = Horizontal ); + ~BoxComponent(); + + void layout( const QRect &rect ); + + virtual int widthForHeight( int height ); + virtual int heightForWidth( int width ); + + static int RTTI; + virtual int rtti() const { return RTTI; } + +protected: + void componentAdded( Component *component ); + void componentRemoved( Component *component ); + void componentResized( Component *component ); + +private: + void calcMinSize(); + + class Private; + Private *d; +}; + +class TextComponent : public Component +{ +public: + TextComponent( ComponentBase *parent, const QFont &font = QFont(), const QString &text = QString::null ); + ~TextComponent(); + + QString text(); + void setText( const QString &text ); + + QFont font(); + void setFont( const QFont &font ); + + QColor color(); + void setColor( const QColor &color ); + void setDefaultColor(); + + int widthForHeight( int ); + + void paint( QPainter *painter, const QColorGroup &cg ); + + static int RTTI; + virtual int rtti() const { return RTTI; } + +private: + void calcMinSize(); + + class Private; + Private *d; +}; + +class ImageComponent : public Component +{ +public: + ImageComponent( ComponentBase *parent ); + ImageComponent( ComponentBase *parent, int minW, int minH ); + ~ImageComponent(); + + void setPixmap( const QPixmap &img, bool adjustSize = true); + QPixmap pixmap( void ); + + void paint( QPainter *painter, const QColorGroup &cg ); + + void scale( int w, int h, QImage::ScaleMode ); + static int RTTI; + virtual int rtti() const { return RTTI; } +private: + class Private; + Private *d; +}; + +/** + * ContactComponent + */ +class ContactComponent : public ImageComponent +{ +public: + ContactComponent( ComponentBase *parent, Kopete::Contact *contact, int iconSize); + ~ContactComponent(); + void updatePixmap(); + Kopete::Contact *contact(); + std::pair toolTip( const QPoint &relativePos ); +protected: + class Private; + Private *d; +}; + +/** + * SpacerComponent + */ +class SpacerComponent : public Component +{ +public: + SpacerComponent( ComponentBase *parent, int w, int h ); +}; + +/** + * DisplayNameComponent + */ + +class DisplayNameComponent : public BoxComponent +{ +public: + /** + * Constructor + */ + DisplayNameComponent( ComponentBase *parent ); + + /** + * Dtor + */ + ~DisplayNameComponent(); + void layout( const QRect& rect ); + + QString text(); + void setText( const QString& text ); + void setFont( const QFont& font ); + void setColor( const QColor& color ); + void setDefaultColor(); + static int RTTI; + virtual int rtti() const { return RTTI; } + /** + * reparse again for emoticon (call this when emoticon theme change) + */ + void redraw(); + +private: + class Private; + Private *d; +}; + +class HSpacerComponent : public Component +{ +public: + HSpacerComponent( ComponentBase *parent ); + int widthForHeight( int ); + + static int RTTI; + virtual int rtti() const { return RTTI; } +}; + +class VSpacerComponent : public Component +{ +public: + VSpacerComponent( ComponentBase *parent ); + int heightForWidth( int ); + + static int RTTI; + virtual int rtti() const { return RTTI; } +}; + +/** + * List-view item composed of Component items. Supports height-for-width, tooltips and + * some animation effects. + * + * @author Richard Smith + */ +class Item : public QObject, public KListViewItem, public ComponentBase +{ + Q_OBJECT +public: + Item( QListView *parent, QObject *owner = 0, const char *name = 0 ); + Item( QListViewItem *parent, QObject *owner = 0, const char *name = 0 ); + ~Item(); + + void repaint(); + void relayout(); + + void setup(); + virtual void paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ); + //TODO: startRename(...) + + float opacity(); + void setOpacity( float alpha ); + + bool targetVisibility(); + void setTargetVisibility( bool vis ); + + /** + * Turn on and off certain visual effects for all Items. + * @param animation whether changes to items should be animated. + * @param fading whether requests to setTargetVisibility should cause fading of items. + * @param folding whether requests to setTargetVisibility should cause folding of items. + */ + static void setEffects( bool animation, bool fading, bool folding ); + + int width( const QFontMetrics & fm, const QListView * lv, int c ) const; + + /** + * Show or hide this item in a clean way depending on whether it matches + * the current quick search + * @param match If true, show or hide the item as normal. If false, hide the item immediately. + */ + virtual void setSearchMatch( bool match ); + +protected: + void componentAdded( Component *component ); + void componentRemoved( Component *component ); + void componentResized( Component *component ); + + void setHeight( int ); + +private: + void initLVI(); + void recalcHeight(); + void scheduleLayout(); + +private slots: + void slotColumnResized(); + void slotLayoutItems(); + void slotLayoutAnimateItems(); + void slotUpdateVisibility(); + +private: + class Private; + Private *d; +}; + +} // END namespace ListView +} // END namespace UI +} // END namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/kopetelistviewsearchline.cpp b/kopete/libkopete/ui/kopetelistviewsearchline.cpp new file mode 100644 index 00000000..a71d86c0 --- /dev/null +++ b/kopete/libkopete/ui/kopetelistviewsearchline.cpp @@ -0,0 +1,138 @@ +/* + kopetelistviewsearchline.cpp - a widget for performing quick searches on Kopete::ListViews + Based on code from KMail, copyright (c) 2004 Till Adam + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kopetelistviewsearchline.h" +#include "kopetelistviewitem.h" +#include "kopetelistview.h" + +namespace Kopete { +namespace UI { +namespace ListView { + +SearchLine::SearchLine( QWidget *parent, ListView *listView, const char *name ) + : KListViewSearchLine( parent, listView, name ) +{ +} + +SearchLine::SearchLine(QWidget *parent, const char *name) + : KListViewSearchLine( parent, 0, name ) +{ +} + +SearchLine::~SearchLine() +{ +} + + +void SearchLine::updateSearch( const QString &s ) +{ + // we copy a huge chunk of code here simply in order to override + // the way items are shown/hidden. KSearchLine rudely + // calls setVisible() on items with no way to customise this behaviour. + + //BEGIN code from KSearchLine::updateSearch + if( !listView() ) + return; + + search = s.isNull() ? text() : s; + + // If there's a selected item that is visible, make sure that it's visible + // when the search changes too (assuming that it still matches). + + QListViewItem *currentItem = 0; + + switch( listView()->selectionMode() ) + { + case KListView::NoSelection: + break; + case KListView::Single: + currentItem = listView()->selectedItem(); + break; + default: + for( QListViewItemIterator it(listView(), QListViewItemIterator::Selected | QListViewItemIterator::Visible); + it.current() && !currentItem; ++it ) + { + if( listView()->itemRect( it.current() ).isValid() ) + currentItem = it.current(); + } + } + + if( keepParentsVisible() ) + checkItemParentsVisible( listView()->firstChild() ); + else + checkItemParentsNotVisible(); + + if( currentItem ) + listView()->ensureItemVisible( currentItem ); + //END code from KSearchLine::updateSearch +} + +void SearchLine::checkItemParentsNotVisible() +{ + //BEGIN code from KSearchLine::checkItemParentsNotVisible + QListViewItemIterator it( listView() ); + for( ; it.current(); ++it ) + { + QListViewItem *item = it.current(); + if( itemMatches( item, search ) ) + setItemVisible( item, true ); + else + setItemVisible( item, false ); + } + //END code from KSearchLine::checkItemParentsNotVisible +} + +bool SearchLine::checkItemParentsVisible( QListViewItem *item ) +{ + //BEGIN code from KSearchLine::checkItemParentsVisible + bool visible = false; + for( ; item; item = item->nextSibling() ) { + if( ( item->firstChild() && checkItemParentsVisible( item->firstChild() ) ) || + itemMatches( item, search ) ) + { + setItemVisible( item, true ); + // OUCH! this operation just became exponential-time. + // however, setting an item visible sets all its descendents + // visible too, which we definitely don't want. + // plus, in Kopete the nesting is never more than 2 deep, + // so this really just doubles the runtime, if that. + // this still can be done in O(n) time by a mark-set process, + // but that's overkill in our case. + checkItemParentsVisible( item->firstChild() ); + visible = true; + } + else + setItemVisible( item, false ); + } + return visible; + //END code from KSearchLine::checkItemParentsVisible +} + +void SearchLine::setItemVisible( QListViewItem *it, bool b ) +{ + if( Item *item = dynamic_cast( it ) ) + item->setSearchMatch( b ); + else + it->setVisible( b ); +} + +} // namespace ListView +} // namespace UI +} // namespace Kopete + +#include "kopetelistviewsearchline.moc" diff --git a/kopete/libkopete/ui/kopetelistviewsearchline.h b/kopete/libkopete/ui/kopetelistviewsearchline.h new file mode 100644 index 00000000..a453b844 --- /dev/null +++ b/kopete/libkopete/ui/kopetelistviewsearchline.h @@ -0,0 +1,66 @@ +/* + kopetelistviewsearchline.h - a widget for performing quick searches of Kopete::ListViews + + Copyright (c) 2004 by Richard Smith + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KOPETELISTVIEWSEARCHLINE_H +#define KOPETELISTVIEWSEARCHLINE_H + +#include + +namespace Kopete { +namespace UI { +namespace ListView { + +class ListView; + +class SearchLine : public KListViewSearchLine +{ + Q_OBJECT +public: + /** + * Constructs a SearchLine with \a listView being the + * ListView to be filtered. + * + * If \a listView is null then the widget will be disabled until a listview + * is set with setListView(). + */ + SearchLine( QWidget *parent, ListView *listView = 0, const char *name = 0 ); + /** + * Constructs a SearchLine without any ListView to filter. The + * KListView object has to be set later with setListView(). + */ + SearchLine(QWidget *parent, const char *name); + /** + * Destroys the SearchLine. + */ + ~SearchLine(); + + void updateSearch( const QString &s ); + +protected: + virtual void checkItemParentsNotVisible(); + virtual bool checkItemParentsVisible( QListViewItem *it ); + virtual void setItemVisible( QListViewItem *it, bool visible ); + +private: + QString search; +}; + +} // end namespace ListView +} // end namespace UI +} // end namespace Kopete + +#endif diff --git a/kopete/libkopete/ui/kopetepassworddialog.ui b/kopete/libkopete/ui/kopetepassworddialog.ui new file mode 100644 index 00000000..7ba4dff9 --- /dev/null +++ b/kopete/libkopete/ui/kopetepassworddialog.ui @@ -0,0 +1,109 @@ + +KopetePasswordDialog +Olivier Goffart + + + KopetePasswordDialog + + + + 0 + 0 + 472 + 117 + + + + + unnamed + + + + layout3 + + + + unnamed + + + + m_image + + + + + m_text + + + + 5 + 5 + 1 + 0 + + + + RichText + + + + + + + layout2 + + + + unnamed + + + + textLabel3 + + + &Password: + + + m_password + + + + + m_password + + + + + + + m_save_passwd + + + &Remember password + + + + + spacer1 + + + Vertical + + + MinimumExpanding + + + + 20 + 0 + + + + + + + m_password + m_save_passwd + + + diff --git a/kopete/libkopete/ui/kopetepasswordwidget.cpp b/kopete/libkopete/ui/kopetepasswordwidget.cpp new file mode 100644 index 00000000..2345f103 --- /dev/null +++ b/kopete/libkopete/ui/kopetepasswordwidget.cpp @@ -0,0 +1,132 @@ +/* + kopetepasswordwidget.cpp - widget for modifying a Kopete::Password + + Copyright (c) 2003 by Richard Smith + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetepasswordwidget.h" +#include "kopetepassword.h" + +#include + +#include + +class Kopete::UI::PasswordWidget::Private +{ +public: + uint maxLength; +}; + +Kopete::UI::PasswordWidget::PasswordWidget( QWidget *parent, const char *name, Kopete::Password *from ) + : KopetePasswordWidgetBase( parent, name ), d( new Private ) +{ + load( from ); +} + +Kopete::UI::PasswordWidget::~PasswordWidget() +{ + delete d; +} + +void Kopete::UI::PasswordWidget::load( Kopete::Password *source ) +{ + disconnect( mRemembered, SIGNAL( stateChanged( int ) ), this, SLOT( slotRememberChanged() ) ); + disconnect( mPassword, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( changed() ) ); + disconnect( mRemembered, SIGNAL( stateChanged( int ) ), this, SIGNAL( changed() ) ); + + if ( source && source->remembered() ) + { + mRemembered->setTristate(); + mRemembered->setNoChange(); + source->requestWithoutPrompt( this, SLOT( receivePassword( const QString & ) ) ); + } + else + { + mRemembered->setTristate( false ); + mRemembered->setChecked( false ); + } + + if ( source ) + d->maxLength = source->maximumLength(); + else + d->maxLength = 0; + + mPassword->setEnabled( false ); + + connect( mRemembered, SIGNAL( stateChanged( int ) ), this, SLOT( slotRememberChanged() ) ); + connect( mPassword, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( changed() ) ); + connect( mRemembered, SIGNAL( stateChanged( int ) ), this, SIGNAL( changed() ) ); + + emit changed(); +} + +void Kopete::UI::PasswordWidget::slotRememberChanged() +{ + mRemembered->setTristate( false ); + mPassword->setEnabled( mRemembered->isChecked() ); +} + +void Kopete::UI::PasswordWidget::receivePassword( const QString &pwd ) +{ + // pwd == null could mean user declined to open wallet + // don't uncheck the remembered field in this case. + if ( !pwd.isNull() && mRemembered->state() == QButton::NoChange ) + { + mRemembered->setChecked( true ); + setPassword( pwd ); + } +} + +void Kopete::UI::PasswordWidget::save( Kopete::Password *target ) +{ + if ( !target || mRemembered->state() == QButton::NoChange ) + return; + + if ( mRemembered->isChecked() ) + target->set( password() ); + else + target->set(); +} + +bool Kopete::UI::PasswordWidget::validate() +{ + if ( !mRemembered->isChecked() ) return true; + if ( d->maxLength == 0 ) return true; + return password().length() <= d->maxLength; +} + +QString Kopete::UI::PasswordWidget::password() const +{ + return QString::fromLocal8Bit( mPassword->password() ); +} + +bool Kopete::UI::PasswordWidget::remember() const +{ + return mRemembered->state() == QButton::On; +} + +void Kopete::UI::PasswordWidget::setPassword( const QString &pass ) +{ + // switch out of 'waiting for wallet' mode if we're in it + mRemembered->setTristate( false ); + + // fill in the password text + mPassword->erase(); + mPassword->insert( pass ); + mPassword->setEnabled( remember() ); +} + +#include "kopetepasswordwidget.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/kopetepasswordwidget.h b/kopete/libkopete/ui/kopetepasswordwidget.h new file mode 100644 index 00000000..b1c51a39 --- /dev/null +++ b/kopete/libkopete/ui/kopetepasswordwidget.h @@ -0,0 +1,109 @@ +/* + kopetepasswordwidget.cpp - widget for editing a Kopete::Password + + Copyright (c) 2003 by Richard Smith + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEPASSWORDWIDGET_H +#define KOPETEPASSWORDWIDGET_H + +#include "kopetepasswordwidgetbase.h" +#include "kopete_export.h" + +namespace Kopete +{ + +class Password; + +namespace UI +{ + +/** + * @author Richard Smith + * This widget displays an editable password, including the Remember password checkbox. + * @todo This is NOT BC yet : it derives from a uic-generated class + */ +class KOPETE_EXPORT PasswordWidget : public KopetePasswordWidgetBase +{ + Q_OBJECT + +public: + /** + * Creates a Kopete::PasswordWidget. + * @param parent The widget to nest this one inside + * @param name The name of this QObject + * @param from The password to load the data for this widget from, or 0 if none + */ + PasswordWidget( QWidget *parent, const char *name = 0, Kopete::Password *from = 0 ); + ~PasswordWidget(); + + /** + * Loads the information stored in source into the widget + */ + void load( Kopete::Password *source ); + /** + * Saves the information in the widget into target + */ + void save( Kopete::Password *target ); + + /** + * Returns true if the information in the widget is valid, false if it is not. + * Currently the only way this can fail is if the password is too long. + * @todo this should return an enum of failures. + */ + bool validate(); + + /** + * Returns the string currently in the input box in the widget + */ + QString password() const; + /** + * Returns a boolean indicating whether the Remember Password checkbox is checked. + * Result is undefined if the Remember Password field is in the 'no change' state + * because the user has not (yet) opened the wallet. + */ + bool remember() const; + + /** + * Set the password stored in the widget. + * @param pass The text to place in the password field. + */ + void setPassword( const QString &pass ); + +signals: + /** + * Emitted when the information stored in this widget changes + */ + void changed(); + +public slots: + /** @internal */ + void receivePassword( const QString & ); + +private slots: + void slotRememberChanged(); + +private: + class Private; + Private *d; +}; + +} + +} + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/kopetepasswordwidgetbase.ui b/kopete/libkopete/ui/kopetepasswordwidgetbase.ui new file mode 100644 index 00000000..5f6b665a --- /dev/null +++ b/kopete/libkopete/ui/kopetepasswordwidgetbase.ui @@ -0,0 +1,98 @@ + +KopetePasswordWidgetBase +Richard Smith <kde@metafoo.co.uk> + + + KopetePasswordWidgetBase + + + + 0 + 0 + 497 + 50 + + + + + 1 + 0 + 0 + 0 + + + + + unnamed + + + 0 + + + + mRemembered + + + Remember password + + + Check this and enter your password below if you would like your password to be stored in your wallet, so Kopete does not have to ask you for it each time it is needed. + + + + + spacer1 + + + Horizontal + + + Fixed + + + + 16 + 20 + + + + + + textLabel1 + + + Password: + + + + + mPassword + + + false + + + + 7 + 0 + 0 + 0 + + + + Enter your password here. + + + Enter your password here. If you would rather not save your password, uncheck the Remember password checkbox above; you will then be prompted for your password whenever it is needed. + + + + + + mRemembered + + + + kpassdlg.h + + diff --git a/kopete/libkopete/ui/kopetestdaction.cpp b/kopete/libkopete/ui/kopetestdaction.cpp new file mode 100644 index 00000000..e6731485 --- /dev/null +++ b/kopete/libkopete/ui/kopetestdaction.cpp @@ -0,0 +1,129 @@ +/* + kopetestdaction.cpp - Kopete Standard Actionds + + Copyright (c) 2001-2002 by Ryan Cumming + Copyright (c) 2002-2003 by Martijn Klingens + + Kopete (c) 2001-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopetestdaction.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopetecontactlist.h" +#include "kopetegroup.h" +#include "kopeteuiglobal.h" + +KSettings::Dialog *KopetePreferencesAction::s_settingsDialog = 0L; + +KopetePreferencesAction::KopetePreferencesAction( KActionCollection *parent, const char *name ) +#if KDE_IS_VERSION( 3, 3, 90 ) +: KAction( KStdGuiItem::configure(), 0, 0, 0, parent, name ) +#else +: KAction( KGuiItem( i18n( "&Configure Kopete..." ), + QString::fromLatin1( "configure" ) ), 0, 0, 0, parent, name ) +#endif +{ + connect( this, SIGNAL( activated() ), this, SLOT( slotShowPreferences() ) ); +} + +KopetePreferencesAction::~KopetePreferencesAction() +{ +} + +void KopetePreferencesAction::slotShowPreferences() +{ + // FIXME: Use static deleter - Martijn + if ( !s_settingsDialog ) + s_settingsDialog = new KSettings::Dialog( KSettings::Dialog::Static, Kopete::UI::Global::mainWidget() ); + s_settingsDialog->show(); + + s_settingsDialog->dialog()->raise(); + + KWin::activateWindow( s_settingsDialog->dialog()->winId() ); +} + +KAction * KopeteStdAction::preferences( KActionCollection *parent, const char *name ) +{ + return new KopetePreferencesAction( parent, name ); +} + +KAction * KopeteStdAction::chat( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "Start &Chat..." ), QString::fromLatin1( "mail_generic" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::sendMessage( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "&Send Single Message..." ), QString::fromLatin1( "mail_generic" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::contactInfo( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "User &Info" ), QString::fromLatin1( "messagebox_info" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::sendFile( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "Send &File..." ), QString::fromLatin1( "attach" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::viewHistory( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "View &History..." ), QString::fromLatin1( "history" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::addGroup( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "&Create Group..." ), QString::fromLatin1( "folder" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::changeMetaContact( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "Cha&nge Meta Contact..." ), QString::fromLatin1( "move" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::deleteContact( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "&Delete Contact" ), QString::fromLatin1( "delete_user" ), Qt::Key_Delete, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::changeAlias( const QObject *recvr, const char *slot, QObject *parent, const char *name ) +{ + return new KAction( i18n( "Change A&lias..." ), QString::fromLatin1( "signature" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::blockContact( const QObject *recvr, const char *slot, QObject* parent, const char *name ) +{ + return new KAction( i18n( "&Block Contact" ), QString::fromLatin1( "player_pause" ), 0, recvr, slot, parent, name ); +} + +KAction * KopeteStdAction::unblockContact( const QObject *recvr, const char *slot, QObject* parent, const char *name ) +{ + return new KAction( i18n( "Un&block Contact" ), QString::fromLatin1( "player_play" ), 0, recvr, slot, parent, name ); +} + +#include "kopetestdaction.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/kopetestdaction.h b/kopete/libkopete/ui/kopetestdaction.h new file mode 100644 index 00000000..8f06d296 --- /dev/null +++ b/kopete/libkopete/ui/kopetestdaction.h @@ -0,0 +1,119 @@ +/* + kopetestdaction.h - Kopete Standard Actionds + + Copyright (c) 2001-2002 by Ryan Cumming + Copyright (c) 2002-2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETESTDACTION_H +#define KOPETESTDACTION_H + +#undef KDE_NO_COMPAT +#include +#include + +#include "kopete_export.h" + +/** + * @author Ryan Cumming + */ +class KOPETE_EXPORT KopeteStdAction +{ +public: + /** + * Standard action to start a chat + */ + static KAction *chat( const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0 ); + /** + * Standard action to send a single message + */ + static KAction *sendMessage(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to open a user info dialog + */ + static KAction *contactInfo(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to open a history dialog or something similar + */ + static KAction *viewHistory(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to initiate sending a file to a contact + */ + static KAction *sendFile(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to change a contacts @ref Kopete::MetaContact + */ + static KAction *changeMetaContact(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to add a group + */ + static KAction *addGroup(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to delete a contact + */ + static KAction *deleteContact(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to change a contact alias/nickname in your contactlist + */ + static KAction *changeAlias(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to block a contact + */ + static KAction *blockContact(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + /** + * Standard action to unblock a contact + */ + static KAction *unblockContact(const QObject *recvr, const char *slot, + QObject* parent, const char *name = 0); + + /** + * Return an action to change the Kopete preferences. + * + * The object has no signal/slot, the prefs are automatically shown + */ + static KAction *preferences(KActionCollection *parent, const char *name = 0); +}; + + +namespace KSettings +{ + class Dialog; +} + +class KOPETE_EXPORT KopetePreferencesAction : public KAction +{ + Q_OBJECT + + public: + KopetePreferencesAction( KActionCollection *parent, const char *name = 0 ); + ~KopetePreferencesAction(); + + protected slots: + void slotShowPreferences(); + private: + static KSettings::Dialog *s_settingsDialog; +}; + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/libkopete/ui/kopeteview.cpp b/kopete/libkopete/ui/kopeteview.cpp new file mode 100644 index 00000000..91f1fa9c --- /dev/null +++ b/kopete/libkopete/ui/kopeteview.cpp @@ -0,0 +1,52 @@ +/* + kopeteview.cpp - View Abstract Class + + Copyright (c) 2003 by Jason Keirstead + Copyright (c) 2003 by Olivier Goffart + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteview.h" + +KopeteView::KopeteView( Kopete::ChatSession *manager, Kopete::ViewPlugin *plugin ) + : m_manager(manager), m_plugin(plugin) +{ +} + +Kopete::ChatSession *KopeteView::msgManager() const +{ + return m_manager; +} + +void KopeteView::clear() +{ + //Do nothing +} + +void KopeteView::appendMessages(QValueList msgs) +{ + QValueList::iterator it; + for ( it = msgs.begin(); it != msgs.end(); ++it ) + { + appendMessage(*it); + } + +} + +Kopete::ViewPlugin *KopeteView::plugin() +{ + return m_plugin; +} + +KopeteView::~ KopeteView( ) +{ +} diff --git a/kopete/libkopete/ui/kopeteview.h b/kopete/libkopete/ui/kopeteview.h new file mode 100644 index 00000000..47320546 --- /dev/null +++ b/kopete/libkopete/ui/kopeteview.h @@ -0,0 +1,176 @@ +/* + kopeteview.h - View Manager + + Copyright (c) 2003 by Jason Keirstead + Copyright (c) 2004 by Matt Rogers + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + + +#ifndef KOPETEVIEW_H +#define KOPETEVIEW_H + +#include "kopetemessage.h" +#include +#include "kopete_export.h" + +namespace Kopete +{ + class ViewPlugin; +} + +/** + * @author Jason Keirstead + * + * Abstract parent class for all types of views used for messaging.These view objects + * are provided by a @ref Kopete::ViewPlugin + * + * @see Kopete::ViewPlugin + */ +class KOPETE_EXPORT KopeteView +{ + public: + /** + * constructor + */ + KopeteView( Kopete::ChatSession *manager, Kopete::ViewPlugin *parent ); + virtual ~KopeteView(); + + /** + * @brief Returns the message currently in the edit area + * @return The Kopete::Message object containing the message + */ + virtual Kopete::Message currentMessage() = 0; + /** + * Set the message that the view is currently editing. + * @param newMessage The Kopete::Message object containing the message to be edited. + */ + virtual void setCurrentMessage( const Kopete::Message &newMessage ) = 0; + + /** + * @brief Get the message manager + * @return The Kopete::ChatSession that the view is in communication with. + */ + Kopete::ChatSession *msgManager() const; + + /** + * @brief add a message to the view + * + * The message gets added at the end of the view and is automatically + * displayed. Classes that inherit from KopeteView should make this a slot. + */ + virtual void appendMessage( Kopete::Message & ) = 0; + + /** + * @brief append multiple messages to the view + * + * This function does the same thing as the above function but + * can be reimplemented if it is faster to apend several messages + * in the same time. + * + * The default implementation just call @ref appendMessage() X times + */ + virtual void appendMessages( QValueList ); + + /** + * @brief Raises the view above other windows + * @param activate change the focus to the window + */ + virtual void raise(bool activate = false) = 0; + + /** + * @brief Clear the buffer + */ + virtual void clear(); + + /** + * @brief Make the view visible + * + * Makes the view visible if it is currently hidden. + */ + virtual void makeVisible() = 0; + + /** + * @brief Close this view + */ + virtual bool closeView( bool force = false ) = 0; + + /** + * @brief Get the current visibility of the view + * @return Whether the view is visible or not. + */ + virtual bool isVisible() = 0; + + /** + * @brief Get the view widget + * + * Can be reimplemented to return this if derived object is a widget + */ + virtual QWidget *mainWidget() = 0; + + /** + * @brief Inform the view the message was sent successfully + * + * This should be reimplemented as a SLOT in any derived objects + */ + virtual void messageSentSuccessfully() = 0; + + /** + * @brief Register a handler for the context menu + * + * Plugins should call this slot at view creation to register + * themselves as handlers for the context menu of this view. Plugins + * can attach to the viewCreated signal of KopeteMessageManagerFactory + * to know when views are created. + * + * A view does not need to implement this method unless they have context + * menus that can be extended + * + * @param target A target QObject for the contextMenuEvent signal of the view + * @param slot A slot that matches the signature ( QString&, KPopupMenu *) + */ + virtual void registerContextMenuHandler( QObject *target, const char*slot ){ Q_UNUSED(target); Q_UNUSED(slot); }; + + /** + * @brief Register a handler for the tooltip + * + * Plugins should call this slot at view creation to register + * themselves as handlers for the tooltip of this view. Plugins + * can attach to the viewCreated signal of KopeteMessageManagerFactory + * to know when views are created. + * + * A view does not need to impliment this method unless it has the ability + * to show tooltips + * + * @param target A target QObject for the contextMenuEvent signal of the view + * @param slot A slot that matches the signature ( QString&, KPopupMenu *) + */ + virtual void registerTooltipHandler( QObject *target, const char*slot ){ Q_UNUSED(target); Q_UNUSED(slot); }; + + /** + * @brief Returns the Kopete::ViewPlugin responsible for this view + * + * KopeteView objects are created by plugins. This returns a pointer to the plugin + * that created this view. You can use this to infer other information on this view + * and it's capabilities. + */ + Kopete::ViewPlugin *plugin(); + + protected: + /** + * a pointer to the Kopete::ChatSession given in the constructor + */ + Kopete::ChatSession *m_manager; + Kopete::ViewPlugin *m_plugin; +}; + +#endif diff --git a/kopete/libkopete/ui/kopeteviewplugin.cpp b/kopete/libkopete/ui/kopeteviewplugin.cpp new file mode 100644 index 00000000..b358e547 --- /dev/null +++ b/kopete/libkopete/ui/kopeteviewplugin.cpp @@ -0,0 +1,28 @@ +/* + kopeteviewplugin.cpp - View Manager + + Copyright (c) 2005 by Jason Keirstead + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "kopeteviewplugin.h" + +Kopete::ViewPlugin::ViewPlugin( KInstance *instance, QObject *parent, const char *name ) : + Kopete::Plugin( instance, parent, name ) +{ + +} + +void Kopete::ViewPlugin::aboutToUnload() +{ + emit readyForUnload(); +} diff --git a/kopete/libkopete/ui/kopeteviewplugin.h b/kopete/libkopete/ui/kopeteviewplugin.h new file mode 100644 index 00000000..e7797d56 --- /dev/null +++ b/kopete/libkopete/ui/kopeteviewplugin.h @@ -0,0 +1,59 @@ +/* + kopeteviewplugin.h - View Manager + + Copyright (c) 2005 by Jason Keirstead + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KOPETEVIEWPLUGIN_H +#define KOPETEVIEWPLUGIN_H + +#include "kopeteplugin.h" + +class KopeteView; + +namespace Kopete +{ + +class ChatSession; + +/** + * @author Jason Keirstead + * + * @brief Factory plugin for creating KopeteView objects. + * + * Kopete ships with two of these currently, a Chat Window view plugin, and + * an Email Window view plugin. + * + */ +class KOPETE_EXPORT ViewPlugin : public Plugin +{ + public: + /** + * @brief Create and initialize the plugin + */ + ViewPlugin( KInstance *instance, QObject *parent = 0L, const char *name = 0L ); + + /** + * @brief Creates a view to be associated with the passed in session + */ + virtual KopeteView *createView( ChatSession * /*session*/ ){ return 0L; }; + + /** + * @brief Reimplemented from Kopete::Plugin + */ + virtual void aboutToUnload(); +}; + +} + +#endif diff --git a/kopete/libkopete/ui/kopetewidgets.cpp b/kopete/libkopete/ui/kopetewidgets.cpp new file mode 100644 index 00000000..093ee48e --- /dev/null +++ b/kopete/libkopete/ui/kopetewidgets.cpp @@ -0,0 +1,131 @@ +/** +* This file was autogenerated by makekdewidgets. Any changes will be lost! +* The generated code in this file is licensed under the same license that the +* input file. +*/ +#include + +#include +#include +#include +#include +#ifndef EMBED_IMAGES +#include +#endif + +class KopeteWidgets : public QWidgetPlugin +{ +public: + KopeteWidgets(); + + virtual ~KopeteWidgets(); + + virtual QStringList keys() const + { + QStringList result; + for (WidgetInfos::ConstIterator it = m_widgets.begin(); it != m_widgets.end(); ++it) + result << it.key(); + return result; + } + + virtual QWidget *create(const QString &key, QWidget *parent = 0, const char *name = 0); + + virtual QIconSet iconSet(const QString &key) const + { +#ifdef EMBED_IMAGES + QPixmap pix(m_widgets[key].iconSet); +#else + QPixmap pix(locate( "data", + QString::fromLatin1("kopetewidgets/pics/") + m_widgets[key].iconSet)); +#endif + return QIconSet(pix); + } + + virtual bool isContainer(const QString &key) const { return m_widgets[key].isContainer; } + + virtual QString group(const QString &key) const { return m_widgets[key].group; } + + virtual QString includeFile(const QString &key) const { return m_widgets[key].includeFile; } + + virtual QString toolTip(const QString &key) const { return m_widgets[key].toolTip; } + + virtual QString whatsThis(const QString &key) const { return m_widgets[key].whatsThis; } +private: + struct WidgetInfo + { + QString group; +#ifdef EMBED_IMAGES + QPixmap iconSet; +#else + QString iconSet; +#endif + QString includeFile; + QString toolTip; + QString whatsThis; + bool isContainer; + }; + typedef QMap WidgetInfos; + WidgetInfos m_widgets; +}; +KopeteWidgets::KopeteWidgets() +{ + WidgetInfo widget; + + widget.group = QString::fromLatin1("Input (Kopete)"); +#ifdef EMBED_IMAGES + widget.iconSet = QPixmap(kopete__ui__addressbooklinkwidget_xpm); +#else + widget.iconSet = QString::fromLatin1("kopete__ui__addressbooklinkwidget.png"); +#endif + widget.includeFile = QString::fromLatin1("addressbooklinkwidget.h"); + widget.toolTip = QString::fromLatin1("Address Book Link Widget (Kopete)"); + widget.whatsThis = QString::fromLatin1("KABC::Addressee display/selector"); + widget.isContainer = false; + m_widgets.insert(QString::fromLatin1("Kopete::UI::AddressBookLinkWidget"), widget); + + widget.group = QString::fromLatin1("Views (Kopete)"); +#ifdef EMBED_IMAGES + widget.iconSet = QPixmap(kopete__ui__listview__listview_xpm); +#else + widget.iconSet = QString::fromLatin1("kopete__ui__listview__listview.png"); +#endif + widget.includeFile = QString::fromLatin1("kopetelistview.h"); + widget.toolTip = QString::fromLatin1("List View (Kopete)"); + widget.whatsThis = QString::fromLatin1("A component capable list view widget."); + widget.isContainer = false; + m_widgets.insert(QString::fromLatin1("Kopete::UI::ListView::ListView"), widget); + + widget.group = QString::fromLatin1("Input (Kopete)"); +#ifdef EMBED_IMAGES + widget.iconSet = QPixmap(kopete__ui__listview__searchline_xpm); +#else + widget.iconSet = QString::fromLatin1("kopete__ui__listview__searchline.png"); +#endif + widget.includeFile = QString::fromLatin1("kopetelistviewsearchline.h"); + widget.toolTip = QString::fromLatin1("List View Search Line (Kopete)"); + widget.whatsThis = QString::fromLatin1("Search line able to use Kopete custom list View."); + widget.isContainer = false; + m_widgets.insert(QString::fromLatin1("Kopete::UI::ListView::SearchLine"), widget); + + new KInstance("kopetewidgets"); +} +KopeteWidgets::~KopeteWidgets() +{ + +} +QWidget *KopeteWidgets::create(const QString &key, QWidget *parent, const char *name) +{ + + if (key == QString::fromLatin1("Kopete::UI::AddressBookLinkWidget")) + return new Kopete::UI::AddressBookLinkWidget(parent, name); + + if (key == QString::fromLatin1("Kopete::UI::ListView::ListView")) + return new Kopete::UI::ListView::ListView(parent, name); + + if (key == QString::fromLatin1("Kopete::UI::ListView::SearchLine")) + return new Kopete::UI::ListView::SearchLine(parent, 0, name); + + return 0; +} +KDE_Q_EXPORT_PLUGIN(KopeteWidgets) + diff --git a/kopete/libkopete/ui/metacontactselectorwidget.cpp b/kopete/libkopete/ui/metacontactselectorwidget.cpp new file mode 100644 index 00000000..d9c75308 --- /dev/null +++ b/kopete/libkopete/ui/metacontactselectorwidget.cpp @@ -0,0 +1,287 @@ +/* + MetaContactSelectorWidget + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kopetelistview.h" +#include "kopetelistviewsearchline.h" +#include "kopetecontactlist.h" +#include "kopetemetacontact.h" +#include "metacontactselectorwidget_base.h" +#include "metacontactselectorwidget.h" + +using namespace Kopete::UI::ListView; + +namespace Kopete +{ +namespace UI +{ + +class MetaContactSelectorWidgetLVI::Private +{ +public: + Kopete::MetaContact *metaContact; + ImageComponent *metaContactPhoto; + ImageComponent *metaContactIcon; + DisplayNameComponent *nameText; + TextComponent *extraText; + BoxComponent *contactIconBox; + BoxComponent *spacerBox; + int photoSize; + int contactIconSize; +}; + + +MetaContactSelectorWidgetLVI::MetaContactSelectorWidgetLVI(Kopete::MetaContact *mc, QListView *parent, QObject *owner, const char *name) : Kopete::UI::ListView::Item(parent, owner, name) , d( new Private() ) +{ + d->metaContact = mc; + d->photoSize = 60; + + connect( d->metaContact, SIGNAL( photoChanged() ), + SLOT( slotPhotoChanged() ) ); + connect( d->metaContact, SIGNAL( displayNameChanged(const QString&, const QString&) ), + SLOT( slotDisplayNameChanged() ) ); + buildVisualComponents(); +} + +Kopete::MetaContact* MetaContactSelectorWidgetLVI::metaContact() +{ + return d->metaContact; +} + +void MetaContactSelectorWidgetLVI::slotDisplayNameChanged() +{ + if ( d->nameText ) + { + d->nameText->setText( d->metaContact->displayName() ); + + // delay the sort if we can + if ( ListView::ListView *lv = dynamic_cast( listView() ) ) + lv->delayedSort(); + else + listView()->sort(); + } +} + +QString MetaContactSelectorWidgetLVI::text ( int /* column */ ) const +{ + return d->metaContact->displayName(); +} + +void MetaContactSelectorWidgetLVI::slotPhotoChanged() +{ + QPixmap photoPixmap; + QImage photoImg = d->metaContact->photo(); + if ( !photoImg.isNull() && (photoImg.width() > 0) && (photoImg.height() > 0) ) + { + int photoSize = d->photoSize; + + photoImg = photoImg.smoothScale( photoSize, photoSize, QImage::ScaleMin ) ; + + // draw a 1 pixel black border + photoPixmap = photoImg; + QPainter p(&photoPixmap); + p.setPen(Qt::black); + p.drawLine(0, 0, photoPixmap.width()-1, 0); + p.drawLine(0, photoPixmap.height()-1, photoPixmap.width()-1, photoPixmap.height()-1); + p.drawLine(0, 0, 0, photoPixmap.height()-1); + p.drawLine(photoPixmap.width()-1, 0, photoPixmap.width()-1, photoPixmap.height()-1); + } + else + { + // if no photo use the smilie icon + photoPixmap=SmallIcon(d->metaContact->statusIcon(), d->photoSize); + } + d->metaContactPhoto->setPixmap( photoPixmap, false); +} + +void MetaContactSelectorWidgetLVI::buildVisualComponents() +{ + // empty... + while ( component( 0 ) ) + delete component( 0 ); + + d->nameText = 0L; + d->metaContactPhoto = 0L; + d->extraText = 0L; + d->contactIconSize = 16; + d->photoSize = 48; + + Component *hbox = new BoxComponent( this, BoxComponent::Horizontal ); + d->spacerBox = new BoxComponent( hbox, BoxComponent::Horizontal ); + + d->contactIconSize = IconSize( KIcon::Small ); + Component *imageBox = new BoxComponent( hbox, BoxComponent::Vertical ); + new VSpacerComponent( imageBox ); + // include borders in size + d->metaContactPhoto = new ImageComponent( imageBox, d->photoSize + 2 , d->photoSize + 2 ); + new VSpacerComponent( imageBox ); + Component *vbox = new BoxComponent( hbox, BoxComponent::Vertical ); + d->nameText = new DisplayNameComponent( vbox ); + d->extraText = new TextComponent( vbox ); + + Component *box = new BoxComponent( vbox, BoxComponent::Horizontal ); + d->contactIconBox = new BoxComponent( box, BoxComponent::Horizontal ); + + slotUpdateContactBox(); + slotDisplayNameChanged(); + slotPhotoChanged(); +} + +void MetaContactSelectorWidgetLVI::slotUpdateContactBox() +{ + QPtrList contacts = d->metaContact->contacts(); + for(Kopete::Contact *c = contacts.first(); c; c = contacts.next()) + { + new ContactComponent(d->contactIconBox, c, IconSize( KIcon::Small )); + } +} + +class MetaContactSelectorWidget::Private +{ +public: + MetaContactSelectorWidget_Base *widget; + QValueList excludedMetaContacts; +}; + + +MetaContactSelectorWidget::MetaContactSelectorWidget( QWidget *parent, const char *name ) + : QWidget( parent, name ), d( new Private() ) +{ + QBoxLayout *l = new QVBoxLayout(this); + d->widget = new MetaContactSelectorWidget_Base(this); + l->addWidget(d->widget); + + connect( d->widget->metaContactListView, SIGNAL( clicked(QListViewItem * ) ), + SIGNAL( metaContactListClicked( QListViewItem * ) ) ); + connect( d->widget->metaContactListView, SIGNAL( selectionChanged( QListViewItem * ) ), + SIGNAL( metaContactListClicked( QListViewItem * ) ) ); + connect( d->widget->metaContactListView, SIGNAL( spacePressed( QListViewItem * ) ), + SIGNAL( metaContactListClicked( QListViewItem * ) ) ); + + connect( Kopete::ContactList::self(), SIGNAL( metaContactAdded( Kopete::MetaContact * ) ), this, SLOT( slotLoadMetaContacts() ) ); + + d->widget->kListViewSearchLine->setListView(d->widget->metaContactListView); + d->widget->metaContactListView->setFullWidth( true ); + d->widget->metaContactListView->header()->hide(); + d->widget->metaContactListView->setColumnWidthMode(0, QListView::Maximum); + slotLoadMetaContacts(); +} + + +MetaContactSelectorWidget::~MetaContactSelectorWidget() +{ + disconnect( Kopete::ContactList::self(), SIGNAL( metaContactAdded( Kopete::MetaContact * ) ), this, SLOT( slotLoadMetaContacts() ) ); +} + + +Kopete::MetaContact* MetaContactSelectorWidget::metaContact() +{ + MetaContactSelectorWidgetLVI *item = 0L; + item = static_cast( d->widget->metaContactListView->selectedItem() ); + + if ( item ) + return item->metaContact(); + + return 0L; +} + +void MetaContactSelectorWidget::selectMetaContact( Kopete::MetaContact *mc ) +{ + // iterate trough list view + QListViewItemIterator it( d->widget->metaContactListView ); + while( it.current() ) + { + MetaContactSelectorWidgetLVI *item = (MetaContactSelectorWidgetLVI *) it.current(); + if (!item) + continue; + + if ( mc == item->metaContact() ) + { + // select the contact item + d->widget->metaContactListView->setSelected( item, true ); + d->widget->metaContactListView->ensureItemVisible( item ); + } + ++it; + } +} + +void MetaContactSelectorWidget::excludeMetaContact( Kopete::MetaContact *mc ) +{ + if( d->excludedMetaContacts.findIndex(mc) == -1 ) + { + d->excludedMetaContacts.append(mc); + } + slotLoadMetaContacts(); +} + +bool MetaContactSelectorWidget::metaContactSelected() +{ + return d->widget->metaContactListView->selectedItem() ? true : false; +} + +/** Read in metacontacts from contactlist */ +void MetaContactSelectorWidget::slotLoadMetaContacts() +{ + d->widget->metaContactListView->clear(); + + QPtrList metaContacts = Kopete::ContactList::self()->metaContacts(); + for( Kopete::MetaContact *mc = metaContacts.first(); mc ; mc = metaContacts.next() ) + { + if( !mc->isTemporary() && (d->excludedMetaContacts.findIndex(mc) == -1) ) + { + new MetaContactSelectorWidgetLVI(mc, d->widget->metaContactListView); + } + } + + d->widget->metaContactListView->sort(); +} + +void MetaContactSelectorWidget::setLabelMessage( const QString &msg ) +{ + d->widget->lblHeader->setText(msg); +} + +} // namespace UI +} // namespace Kopete + +#include "metacontactselectorwidget.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/metacontactselectorwidget.h b/kopete/libkopete/ui/metacontactselectorwidget.h new file mode 100644 index 00000000..1c0a21ff --- /dev/null +++ b/kopete/libkopete/ui/metacontactselectorwidget.h @@ -0,0 +1,103 @@ +/* + MetaContactSelectorWidget + + Copyright (c) 2005 by Duncan Mac-Vicar Prett + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MetaContactSelectorWidget_H +#define MetaContactSelectorWidget_H + +#include +#include +#include "kopetelistviewitem.h" +#include "kopete_export.h" + +class Kopete::MetaContact; + +namespace Kopete +{ +namespace UI +{ + +/** + * @author Duncan Mac-Vicar Prett + * This class provides a widget which allows easy selection + * of available Kopete metacontacts. + */ +class KOPETE_EXPORT MetaContactSelectorWidget : public QWidget +{ + Q_OBJECT +public: + MetaContactSelectorWidget( QWidget *parent = 0, const char *name = 0 ); + ~MetaContactSelectorWidget(); + Kopete::MetaContact* metaContact(); + /** + * sets the widget label message + * example: Please select a contact + * or, Choose a contact to delete + */ + void setLabelMessage( const QString &msg ); + /** + * pre-selects a contact + */ + void selectMetaContact( Kopete::MetaContact *mc ); + /** + * excludes a metacontact from being shown in the list + * if the metacontact is already excluded, do nothing + */ + void excludeMetaContact( Kopete::MetaContact *mc ); + /** + * @return true if there is a contact selected + */ + bool metaContactSelected(); +protected slots: + /** + * Utility function, populates the metacontact list + */ + void slotLoadMetaContacts(); +signals: + void metaContactListClicked( QListViewItem *mc ); +private: + class Private; + Private *d; +}; + +/** + * @author Duncan Mac-Vicar Prett + */ + +class MetaContactSelectorWidgetLVI : public Kopete::UI::ListView::Item +{ + Q_OBJECT +public: + MetaContactSelectorWidgetLVI(Kopete::MetaContact *mc, QListView *parent, QObject *owner = 0, const char *name = 0 ); + Kopete::MetaContact* metaContact(); + virtual QString text ( int column ) const; +protected slots: + void slotPhotoChanged(); + void slotDisplayNameChanged(); + void buildVisualComponents(); + void slotUpdateContactBox(); +private: + class Private; + Private *d; +}; + +} // namespace UI +} // namespace Kopete + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/metacontactselectorwidget_base.ui b/kopete/libkopete/ui/metacontactselectorwidget_base.ui new file mode 100644 index 00000000..bc1a38eb --- /dev/null +++ b/kopete/libkopete/ui/metacontactselectorwidget_base.ui @@ -0,0 +1,107 @@ + +MetaContactSelectorWidget_Base + + + MetaContactSelectorWidget_Base + + + + 0 + 0 + 427 + 306 + + + + + 5 + 1 + 0 + 0 + + + + Select Contact + + + + unnamed + + + + lblHeader + + + + 7 + 4 + 0 + 0 + + + + + + layout2 + + + + unnamed + + + + lblSearch + + + + 1 + 5 + 0 + 0 + + + + S&earch: + + + kListViewSearchLine + + + + + kListViewSearchLine + + + + + + + + Meta Contact + + + true + + + true + + + + metaContactListView + + + + + + kopetelistviewsearchline.h + kopetelistview.h + qheader.h + + + + + kactivelabel.h + kopetelistviewsearchline.h + kopetelistview.h + + diff --git a/kopete/libkopete/ui/userinfodialog.cpp b/kopete/libkopete/ui/userinfodialog.cpp new file mode 100644 index 00000000..a25454a9 --- /dev/null +++ b/kopete/libkopete/ui/userinfodialog.cpp @@ -0,0 +1,277 @@ +/* + userinfodialog.h + + Copyright (c) 2003 by Zack Rusin + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "userinfodialog.h" +#include "kopeteuiglobal.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Kopete { + +struct UserInfoDialog::UserInfoDialogPrivate { + QString name; + QString id; + QString awayMessage; + QString status; + QString warningLevel; + QString onlineSince; + QString info; + QString address; + QString phone; + QMap customFields; + QVBoxLayout *topLayout; + QWidget *page; + DialogStyle style; + KHTMLPart *htmlPart; + + KLineEdit *nameEdit; + KLineEdit *idEdit; + KLineEdit *statusEdit; + KLineEdit *warningEdit; + KLineEdit *onlineEdit; + KLineEdit *addressEdit; + KLineEdit *phoneEdit; + KTextBrowser *awayBrowser; + KTextBrowser *infoBrowser; +}; + +UserInfoDialog::UserInfoDialog( const QString& descr ) +: KDialogBase( Kopete::UI::Global::mainWidget(), "userinfodialog", true, i18n( "User Info for %1" ).arg( descr ), KDialogBase::Ok ) +{ + d = new UserInfoDialogPrivate; + d->page = new QWidget( this ); + setMainWidget( d->page ); + d->topLayout = new QVBoxLayout( d->page, 0, spacingHint() ); + d->style = Widget; +} + +UserInfoDialog::~UserInfoDialog() +{ + delete d; d=0; +} + +void UserInfoDialog::setStyle( DialogStyle style ) +{ + d->style = style; +} + +void UserInfoDialog::setName( const QString& name ) +{ + d->name = name; +} + +void UserInfoDialog::setId( const QString& id ) +{ + d->id = id; +} + +void UserInfoDialog::setAwayMessage( const QString& msg ) +{ + d->awayMessage = msg; +} + +void UserInfoDialog::setStatus( const QString& status ) +{ + d->status = status; +} + +void UserInfoDialog::setWarningLevel(const QString& level ) +{ + d->warningLevel = level; +} + +void UserInfoDialog::setOnlineSince( const QString& since ) +{ + d->onlineSince = since; +} + +void UserInfoDialog::setInfo( const QString& info ) +{ + d->info = info; +} + +void UserInfoDialog::setAddress( const QString& addr ) +{ + d->address = addr; +} + +void UserInfoDialog::setPhone( const QString& phone ) +{ + d->phone = phone; +} + +void UserInfoDialog::addCustomField( const QString& /*name*/, const QString& /*txt*/ ) +{ + +} + +void UserInfoDialog::addHTMLText( const QString& /*str*/ ) +{ + +} + +QHBox* UserInfoDialog::addLabelEdit( const QString& label, const QString& text, KLineEdit*& edit ) +{ + QHBox *box = new QHBox( d->page ); + new QLabel( label, box ); + edit = new KLineEdit( box ); + edit->setAlignment( Qt::AlignHCenter ); + edit->setText( text ); + edit->setReadOnly( true ); + return box; +} + +void UserInfoDialog::fillHTML() +{ + d->htmlPart = new KHTMLPart( this ); + + QString text; + /* + if ( d->name.isEmpty() ) { + text.append( QString("
    ") + i18n("Name : ") + + QString("") ); + text.append( d->name + QString("

    ") ); + } + + if ( d->id.isEmpty() ) { + text.append( "
    " + i18n("Id : ") + "" ); + text.append( d->id + "

    " ); + } + + if ( d->warningLevel.isEmpty() ) { + text.append( "
    " + i18n("Warning Level : ") + "" ); + text.append( d->warningLevel + "

    " ); + } + + if ( d->onlineSince.isEmpty() ) { + text.append( "
    " + i18n("Online Since : ") + "" ); + text.append( d->onlineSince + "

    " ); + } + + if ( d->address.isEmpty() ) { + text.append( "
    " + i18n("Address : ") + "" ); + text.append( d->address + "

    " ); + } + + if ( d->phone.isEmpty() ) { + text.append( "
    " + i18n("Phone : ") + "" ); + text.append( d->phone + "

    " ); + } + + if ( d->status.isEmpty() ) { + text.append( "
    " + i18n("Status : ") + "" ); + text.append( d->status + "

    " ); + } + + if ( d->awayMessage.isEmpty() ) { + text.append( "
    " + i18n("Away Message : ") + "" ); + text.append( d->awayMessage + "

    " ); + } + + if ( d->info.isEmpty() ) { + text.append( "
    " + i18n("Info : ") + "" ); + text.append( d->info + "

    " ); + } +*/ + d->htmlPart->setOnlyLocalReferences( true ); + d->htmlPart->begin(); + d->htmlPart->write( text ); + d->htmlPart->end(); +} + +void UserInfoDialog::fillWidgets() +{ + kdDebug(14010)<<"Creating widgets"<name.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Name:"), d->name, d->nameEdit ) ); + } + + if ( !d->id.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Contact ID:"), d->id, d->idEdit ) ); + } + + if ( !d->status.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Status:"), d->status, d->statusEdit ) ); + } + + if ( !d->warningLevel.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Warning level:"), d->warningLevel, d->warningEdit ) ); + } + + if ( !d->onlineSince.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Online since:"), d->onlineSince, d->onlineEdit ) ); + } + + if ( !d->address.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Address:"), d->address, d->addressEdit ) ); + } + + if ( !d->phone.isEmpty() ) { + d->topLayout->addWidget( addLabelEdit( i18n("Phone:"), d->phone, d->phoneEdit ) ); + } + + if ( !d->awayMessage.isEmpty() ) { + QVBox *awayBox = new QVBox( d->page ); + new QLabel( i18n("Away message:"), awayBox ); + d->awayBrowser = new KTextBrowser( awayBox ); + d->awayBrowser->setText( d->awayMessage ); + d->topLayout->addWidget( awayBox ); + } + + if ( !d->info.isEmpty() ) { + QVBox *infoBox = new QVBox( d->page ); + new QLabel( i18n("User info:"), infoBox ); + d->infoBrowser = new KTextBrowser( infoBox ); + d->infoBrowser->setText( d->info ); + d->topLayout->addWidget( infoBox ); + } +} + +void UserInfoDialog::setStyleSheet( const QString& /*css*/ ) +{ +} + +void UserInfoDialog::create() +{ + if ( d->style == HTML ) { + fillHTML(); + } else { + fillWidgets(); + } +} + +void UserInfoDialog::show() +{ + create(); + KDialogBase::show(); +} + +} + +#include "userinfodialog.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/libkopete/ui/userinfodialog.h b/kopete/libkopete/ui/userinfodialog.h new file mode 100644 index 00000000..7df19f4f --- /dev/null +++ b/kopete/libkopete/ui/userinfodialog.h @@ -0,0 +1,91 @@ +/* + userinfodialog.h + + Copyright (c) 2003 by Zack Rusin + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef USERINFODIALOG_H +#define USERINFODIALOG_H + +#include +#include + +#include "kopete_export.h" + +class KLineEdit; + +namespace Kopete { + + class KOPETE_EXPORT UserInfoDialog : public KDialogBase + { + Q_OBJECT + public: + UserInfoDialog( const QString& descr ); + virtual ~UserInfoDialog(); + + + /** + * Specifies the look of this dialog. If set to HTML only + * KHTMLPart will be in the dialog and it's look can be customized + * through setStyleSheet + * @see setStyleSheet + */ + enum DialogStyle { HTML, Widget }; + void setStyle( DialogStyle style ); + + // The functions below set elements as specified in the name. + // If an element is not set it won't be displayed. + void setName( const QString& name ); + void setId( const QString& id ); + void setAwayMessage( const QString& msg ); + void setStatus( const QString& status ); + void setWarningLevel(const QString& level ); + void setOnlineSince( const QString& since ); + void setInfo( const QString& info ); + void setAddress( const QString& addr ); + void setPhone( const QString& phone ); + + void addCustomField( const QString& name, const QString& txt ); + void addHTMLText( const QString& str ); + + ///Shows the dialog + virtual void show(); + protected: + /** + * This function has to be called after setting all the fields. + * It builds the GUI for the dialog. By default show() calls it. + */ + virtual void create(); + //Fills the dialog HTML if DialogStyle is HTML + virtual void fillHTML(); + //Fills the dialog with widgets if DialogStyle is Widget + virtual void fillWidgets(); + + /** + * If the DialogStyle is set to HTML one can customize the look of this + * dialog by setting the right stylesheet. The CSS id elements that can be + * customized include : "name", "id", "warningLevel", "onlineSince", + * "address", "phone", "status", "awayMessage" and "info". + */ + void setStyleSheet( const QString& css ); + + QHBox* addLabelEdit( const QString& label, const QString& text, KLineEdit*& edit ); + + private: + struct UserInfoDialogPrivate; + UserInfoDialogPrivate *d; + }; + +} +#endif diff --git a/kopete/libkopete/ui/widgets.cw b/kopete/libkopete/ui/widgets.cw new file mode 100644 index 00000000..7f3b38dc --- /dev/null +++ b/kopete/libkopete/ui/widgets.cw @@ -0,0 +1,21 @@ + + + + Kopete::UI::PasswordWidget +
    kopetepasswordwidget.h
    + + 50 + 50 + + 0 + + 1 + 0 + + + 789c9d97c76e24490e86effd1442f3d65870d2451a0ce6206f5adeb4cc620f8c34f2553225b5a4c1befb46927fe6a1d4c0ccac4287fa8a0c26834193f5dbb785b3fd9d856fbf7d799ec9ecba5ea8afe469e15bf3727ffffeeffffcf1e797af49b2d0ffc7d142f2f55f5fbe1ecc16ea85dde9a4ed81290045faa77ca49cf4ab67ba1e3953969173651ab9d4fdf1c8a27c3872ad7c3c72d3b32c2a67c3f3444636fbef23eb7e590267e68fcc4656395d8dacf6d938ef97eaef289781cddebdb2846fccff44b989ba58e3411f3dc75158e6df1d3889539517ca49bf54fe43398d1dec4f46567f685fd9c539f43fc025f80c1c3ce8d93f28e77185f8bf0c6cfae4c0b5f9c3c6e5c0542a4b5876fe13708df39d2bd7716372aa8c93c4e4723bb2f977aadc26ceec4bdb7310e6b0bfab9c2445ecd49f1370694cebca65d260ffa6711a41aef14d24e94cee6be3348e0ae5e9c8765f07ca3e8db17f0f9c825794eb3489351fe9bb7217e4969f29388bd51ee9fda5715a19730696b852fea95c6471647ca95cf64bcf43e0cef4657b647bfe6acf213d6bb32f9a9f5992b5a64f9a8f990b6cf9ecc15d6cf9acf6b2da55a8a75cb973dee4b2d1b38b5c05de02434e87e01aacfb351df5bebddea74b5c67f9c795711ea15e357e2ecde358fb87efc0a867df8cac728a074e62e527706afb59f3cf6583be5c28bb3c35f693812d1fbdc6dbe57966fec932d899ffa4f7e38adca17e34beaeca4b9c271a18f1d5fc739257b0d70d9c68be7bed7fcee7827ab91cd8e4740cf6b19d4ffb87ab07b9bf516e72d4a3ac821b9cef6e60d84f47367ded2fae1d9f7704f6163f79000ffde27660f387ed3c5dbf54dfeebb0bf6acbe6b706bfa5ef32f8f8b18f5bf0f463f20edb77956a4e68fbc80b3c4facd163847be6bfde72ec86dbeac82715fbc0286be683cf3bc081d44f7df80d344fb0b6b7de64581fb916765297263d6fccd9b7ee97e566e8b06cfdb1f59cf2bda2f8bbc2c11df0c5c41aefdb328ca22b1feb0012eedbce24736f91b18fb796d64eb87cb60817dcdafb07d906bfd14d22f659d8785ef97b2f6d7b2df6ef1d6fb2babaac6f92e8c25b27ecc1f239b7f3a5fca5a8678ea7c2d9bc0e6ef1238b3fca2c7814d5f4cbf9334b5f833384bed3c7a5f5514f4adbe2ec07962e7590457f06f6f649b977afe2a16f42f5e070bfc591e18f7a5f1a812a9e0df39d827da4fbd8c6ccfd7785569bf945f953371a9d5a3de67950bfa2d75c63eb2fb15bdcfaaf011e6c7263846bf8f4636f91618f3c7d3c0f047fb69550efab2074e12bd6fd27957553ec33c5b043bcc7f9d0795f818fdb501a3df8ae673e507ff69021eea37063bc45ffb73550736ff36c0a84f3e0317a86f8b5f139e6ffe1f821de6d9127898ff3be00a7c3ab2cd03e3d697560ff40016e47b3bb2e96bbd559d8f32bbff4b7065f193042c98873acf240af6adbf3c83c5f2954bb0c7bcd77c107d81d2fdebc63e33ff640aaecc9e1c8151df5c803dfaadc64b52dfa03ed64636ffb4ff4b2867f453ede7227586fcd3f9264ded91fffafe236d5da37fe83c91ceb7b9e5b7f6731ff92ed7f747d6fbf7e185cf58347e3e6932e47b3430fa893edf87d795c2fc3f003b67f5f902cec17abfde0dfa3c053b3c5ffb832fc2eb8fddcf233887fc195c801f46b6f3cdc02558fba72f7d2d1a7f7e321ee58fe0cad86bbff24dd3e2fe74fef836b0c6eb60d62fa6bf5e07b3419f853dd7dc70fb8bd5f1255fd90ed30f9f3c5ff30ddff21ddff384a7fcc08ffcc4cf61cdf8855ff9e79c7e1db4dff89d3f7891977899577895d7789d377893b7f83b6fcfe937bc13b477798ff7f9800ff928ac633ee11f7cca6761d7f99c7e1b3cb908da11c7413be1943376e153cc39175c72f549ff9e17c31744429e6a6aa8a58e2ee98aaee9866e7f617fc24b7417a4f734a1293dd0233dd133cd8285177aa5f9f3b63ca5377aa78f607b919668995682e62aadd17ab0b1419b9ff41f682b48bed336edd02eed05ed7d5ea3033a0cdf1ed1f127fd273ae123fa41a774a6b685cee982228a837e42e927fd47caf8985c38651eb40b2ac38e4a5842658a97fab33fd248cb87d2c9a55cc9b5dcc82d1fc99ddccb44a6f230af2f8ff224cf417f262ff22a3fe54ddee543166549966545567f617f4dd66523dc6b2c9bb225df655b7682f692ecca9eeccfe97772c09b722847722c27c1f3eb70f66bf9116c9fca999ccbc59cfe25bf4a143a5ef8992561324a78bd92522acf9ebc78efe7cf7b153276db37bef59dbff457fedadff85b7f27abfede4ffcd4cf9ff76faeff4fffefeff8c7f5fedfdfbffc0fa355c495 + + changed() +
    +
    +
    diff --git a/kopete/libkopete/webcamwidget.cpp b/kopete/libkopete/webcamwidget.cpp new file mode 100644 index 00000000..ae617ad5 --- /dev/null +++ b/kopete/libkopete/webcamwidget.cpp @@ -0,0 +1,107 @@ +/* + webcamwidget.h - A simple widget for displaying webcam frames + + Copyright (c) 2006 by Gustavo Pichorim Boiko + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "webcamwidget.h" + +#include +#include + +#include +namespace Kopete +{ + +WebcamWidget::WebcamWidget( QWidget* parent, const char* name ) +: QWidget( parent, name ) +{ + clear(); +} + +WebcamWidget::~WebcamWidget() +{ + // don't do anything either +} + +void WebcamWidget::updatePixmap(const QPixmap& pixmap) +{ + mPixmap = pixmap; + mText = ""; + + QPaintEvent *ev = new QPaintEvent( rect(), true ); + paintEvent( ev ); + delete ev; +} + +void WebcamWidget::clear() +{ + mText = ""; + if (!mPixmap.isNull()) + mPixmap.resize(0,0); + + QPaintEvent *ev = new QPaintEvent( rect(), true ); + paintEvent( ev ); + delete ev; +} + +void WebcamWidget::setText(const QString& text) +{ + mText = text; + + // for now redraw everything + QPaintEvent *ev = new QPaintEvent( rect(), true ); + paintEvent( ev ); + delete ev; +} + +void WebcamWidget::paintEvent( QPaintEvent* event ) +{ + QMemArray rects = event->region().rects(); + + if (!mPixmap.isNull()) + { + for (unsigned int i = 0; i < rects.count(); ++i) + { + bitBlt(this, rects[i].topLeft(), &mPixmap, rects[i], Qt::CopyROP, true); + } + } + else + { + for (unsigned int i = 0; i < rects.count(); ++i) + { + QColor bgColor = paletteBackgroundColor(); + QPainter p(this); + p.fillRect(rects[i], bgColor); + } + } + + // TODO: draw the text + QPainter p(this); + QRect r = p.boundingRect(rect(), Qt::AlignCenter | Qt::WordBreak, mText ); + if ( !mText.isEmpty() && event->rect().intersects(r)) + { + p.setPen(Qt::black); + QRect rec = rect(); + rec.moveTopLeft(QPoint(1,1)); + p.drawText(rec, Qt::AlignCenter | Qt::WordBreak, mText, -1); + + rec.moveTopLeft(QPoint(-1,-1)); + p.setPen(Qt::white); + p.drawText(rec, Qt::AlignCenter | Qt::WordBreak, mText, -1); + } +} + +} // end namespace Kopete + +#include "webcamwidget.moc" diff --git a/kopete/libkopete/webcamwidget.h b/kopete/libkopete/webcamwidget.h new file mode 100644 index 00000000..6458f60a --- /dev/null +++ b/kopete/libkopete/webcamwidget.h @@ -0,0 +1,66 @@ +/* + webcamwidget.h - A simple widget for displaying webcam frames + + Copyright (c) 2006 by Gustavo Pichorim Boiko + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef WEBCAMWIDGET_H +#define WEBCAMWIDGET_H + +#include +#include +#include + +#include "kopete_export.h" + +namespace Kopete +{ +/** + * A simple widget to display webcam frames. + */ +class KOPETE_EXPORT WebcamWidget : public QWidget +{ +Q_OBJECT +public: + /** + * @brief WebcamWidget constructor. + * @param parent The parent widget of this widget + * @param name The name for this QObject + */ + WebcamWidget( QWidget* parent = 0, const char* name = 0 ); + ~WebcamWidget(); + + /** + * @brief Updates the frame being displayed in the widget + * @param pixmap The frame to be displayed + */ + void updatePixmap(const QPixmap& pixmap); + + /** + * @brief Clear the widget + */ + void clear(); + + /** + * @brief Set a text to be displayed in the widget + * @param text The text to be displayed + */ + void setText(const QString& text); +protected slots: + void paintEvent( QPaintEvent* event ); + QPixmap mPixmap; + QString mText; +}; + +} // end namespace Kopete +#endif diff --git a/kopete/plugins/Makefile.am b/kopete/plugins/Makefile.am new file mode 100644 index 00000000..8b817465 --- /dev/null +++ b/kopete/plugins/Makefile.am @@ -0,0 +1,12 @@ +if include_motionautoaway +MOTIONAUTOAWAY_SUBDIR=motionautoaway +endif + +if include_smpppdcs +SMPPPDCS_SUBDIR=smpppdcs +endif + +SUBDIRS = latex autoreplace history contactnotes cryptography\ + connectionstatus translator nowlistening webpresence texteffect\ + highlight alias $(MOTIONAUTOAWAY_SUBDIR) netmeeting addbookmarks\ + statistics $(SMPPPDCS_SUBDIR) diff --git a/kopete/plugins/addbookmarks/Makefile.am b/kopete/plugins/addbookmarks/Makefile.am new file mode 100644 index 00000000..696ddfb9 --- /dev/null +++ b/kopete/plugins/addbookmarks/Makefile.am @@ -0,0 +1,22 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +noinst_HEADERS = addbookmarksplugin.h addbookmarkspreferences.h \ + addbookmarksprefssettings.h addbookmarksprefsui.h + +kde_module_LTLIBRARIES = kopete_addbookmarks.la kcm_kopete_addbookmarks.la + +kopete_addbookmarks_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kopete_addbookmarks_la_LIBADD = ../../libkopete/libkopete.la +kopete_addbookmarks_la_SOURCES = addbookmarksplugin.cpp addbookmarksprefssettings.cpp + +kcm_kopete_addbookmarks_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_addbookmarks_la_LIBADD = ../../libkopete/libkopete.la $(LIB_KUTILS) +kcm_kopete_addbookmarks_la_SOURCES = addbookmarkspreferences.cpp addbookmarksprefsui.ui \ + addbookmarksprefssettings.cpp + +service_DATA = kopete_addbookmarks.desktop +servicedir = $(kde_servicesdir) + +kcm_DATA = kopete_addbookmarks_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog diff --git a/kopete/plugins/addbookmarks/addbookmarksplugin.cpp b/kopete/plugins/addbookmarks/addbookmarksplugin.cpp new file mode 100644 index 00000000..fae164f1 --- /dev/null +++ b/kopete/plugins/addbookmarks/addbookmarksplugin.cpp @@ -0,0 +1,194 @@ +// +// C++ Implementation: %{MODULE} +// +// Description: +// +// +// Author: Roie Kerstein , (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// + +#include +#include +#include +#include +#include + +#include "addbookmarksplugin.moc" +#include "addbookmarksplugin.h" +#include "kopetecontact.h" +#include "kopetechatsessionmanager.h" +#include "kopeteglobal.h" +#include "kopetemetacontact.h" + + +K_EXPORT_COMPONENT_FACTORY( kopete_addbookmarks, BookmarksPluginFactory( "kopete_addbookmarks" ) ) + + +static bool isURLInGroup(const KURL& url, const KBookmarkGroup& group) +{ + KBookmark bookmark = group.first(); + + for( ; !bookmark.isNull() ; bookmark = group.next(bookmark) ){ + if( !bookmark.isGroup() && !bookmark.isSeparator() ) + if( url == bookmark.url() ) + return true; + } + return false; +} + +BookmarksPlugin::BookmarksPlugin(QObject *parent, const char *name, const QStringList &/*args*/) + : Kopete::Plugin(BookmarksPluginFactory::instance(), parent, name) +{ + //kdDebug(14501) << "plugin loading" << endl; + connect( Kopete::ChatSessionManager::self(), SIGNAL( aboutToDisplay( Kopete::Message & ) ), this, SLOT( slotBookmarkURLsInMessage( Kopete::Message & ) ) ); +} + +/*! + \fn BookmarksPlugin::slotBookmarkURLsInMessage(KopeteMessage & msg) + */ +void BookmarksPlugin::slotBookmarkURLsInMessage(Kopete::Message & msg) +{ + //kdDebug(14501) << "recieved message:" << endl << msg.parsedBody() << endl; + if(msg.direction() != Kopete::Message::Inbound) + return; + KURL::List *URLsList; + KURL::List::iterator it; + URLsList = extractURLsFromString( msg.parsedBody() ); + if (!URLsList->empty()) { + for( it = URLsList->begin() ; it != URLsList->end() ; ++it){ + if ( m_settings.addBookmarksFromUnknownContacts() || !msg.from()->metaContact()->isTemporary() ) + { + if ( msg.from()->metaContact() ) { + addKopeteBookmark(*it, msg.from()->metaContact()->displayName() ); + //kdDebug (14501) << "name:" << msg.from()->metaContact()->displayName() << endl; + } + else { + addKopeteBookmark(*it, msg.from()->property(Kopete::Global::Properties::self()->nickName()).value().toString() ); + //kdDebug (14501) << "name:" << msg.from()->property(Kopete::Global::Properties::self()->nickName()).value().toString() << endl; + } + } + } + } + delete URLsList; +} + +void BookmarksPlugin::slotAddKopeteBookmark( KIO::Job *transfer, const QByteArray &data ) +{ + QTextCodec *codec = getPageEncoding( data ); + QString htmlpage = codec->toUnicode( data ); + QRegExp rx("([^<]*){1,96}"); + rx.setCaseSensitive(false); + int pos = rx.search( htmlpage ); + KBookmarkManager *mgr = KBookmarkManager::userBookmarksManager(); + KBookmarkGroup group = getKopeteFolder(); + QString sender = m_map[(KIO::TransferJob*)transfer].sender; + + if ( m_settings.useSubfolderForContact( sender ) ) + group = getFolder( group, sender ); + + if( pos == -1 ){ + group.addBookmark( mgr, m_map[(KIO::TransferJob*)transfer].url.prettyURL(), m_map[(KIO::TransferJob*)transfer].url.url() ); + kdDebug( 14501 ) << "failed to extract title from first data chunk" << endl; + }else { + group.addBookmark( mgr, rx.cap( 1 ).simplifyWhiteSpace(), + m_map[(KIO::TransferJob*)transfer].url.url() ); + } + mgr->save(); + mgr->emitChanged( group ); + m_map.remove( (KIO::TransferJob*)transfer ); + transfer->kill(); +} + +KURL::List* BookmarksPlugin::extractURLsFromString( const QString& text ) +{ + KURL::List *list = new KURL::List; + QRegExp rx("append(url); + } + return list; +} + +void BookmarksPlugin::addKopeteBookmark( const KURL& url, const QString& sender ) +{ + KBookmarkGroup group = getKopeteFolder(); + + if ( m_settings.useSubfolderForContact( sender ) ) { + group = getFolder( group, sender ); + } + // either restrict to http(s) or to KProtocolInfo::protocolClass() == :internet + if( !isURLInGroup( url, group ) + && url.isValid() && url.protocol().startsWith("http") ) { + KIO::TransferJob *transfer; + // make asynchronous transfer to avoid GUI freezing due to overloaded web servers + transfer = KIO::get(url, false, false); + transfer->setInteractive(false); + connect ( transfer, SIGNAL ( data( KIO::Job *, const QByteArray & ) ), + this, SLOT ( slotAddKopeteBookmark( KIO::Job *, const QByteArray & ) ) ); + m_map[transfer].url = url; + m_map[transfer].sender = sender; + } +} + +KBookmarkGroup BookmarksPlugin::getKopeteFolder() +{ + KBookmarkManager *mgr = KBookmarkManager::userBookmarksManager(); + + return getFolder( mgr->root(), QString::fromLatin1("kopete") ); +} + +KBookmarkGroup BookmarksPlugin::getFolder( KBookmarkGroup group, const QString& folder ) +{ + KBookmark bookmark; + + + for( bookmark=group.first(); !bookmark.isNull() && !(bookmark.isGroup() && !bookmark.fullText().compare( folder )); bookmark = group.next(bookmark)); + if( bookmark.isNull() ){ + KBookmarkManager *mgr = KBookmarkManager::userBookmarksManager(); + //kdDebug (14501) << "GetFolder:" << folder << endl; + group = group.createNewFolder( mgr, folder, true); + }else { + group = bookmark.toGroup(); + } + return group; +} + +QTextCodec* BookmarksPlugin::getPageEncoding( const QByteArray& data ) +{ + QString temp = QString::fromLatin1(data); + QRegExp rx("]*(charset|CHARSET)\\s*=\\s*[^>]*>"); + int pos = rx.search( temp ); + QTextCodec *codec; + + if( pos == -1 ){ + kdDebug( 14501 ) << "charset not found in first data chunk" << endl; + return QTextCodec::codecForName("iso8859-1"); + } + //kdDebug(14501) << temp.mid(pos, rx.matchedLength()) << endl; + temp = temp.mid(pos, rx.matchedLength()-1); + temp = temp.mid( temp.find("charset", 0, false)+7); + temp = temp.remove('=').simplifyWhiteSpace(); + for( pos = 0 ; temp[pos].isLetterOrNumber() || temp[pos] == '-' ; pos++ ); + temp = temp.left( pos ); + //kdDebug(14501) << "encoding: " << temp << endl; + codec = QTextCodec::codecForName( temp.latin1() ); + if( !codec ){ + return QTextCodec::codecForName("iso8859-1"); + } + return codec; +} + +void BookmarksPlugin::slotReloadSettings() +{ + m_settings.load(); +} diff --git a/kopete/plugins/addbookmarks/addbookmarksplugin.h b/kopete/plugins/addbookmarks/addbookmarksplugin.h new file mode 100644 index 00000000..4c425b9f --- /dev/null +++ b/kopete/plugins/addbookmarks/addbookmarksplugin.h @@ -0,0 +1,56 @@ +// +// C++ Interface: %{MODULE} +// +// Description: +// +// +// Author: Roie Kerstein , (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#ifndef ADDBOOKMARKSPLUGIN_H +#define ADDBOOKMARKSPLUGIN_H + +#include "addbookmarksprefssettings.h" +#include +#include +#include +#include +#include +#include + +/** +@author Roie Kerstein +*/ + +class BookmarksPlugin : public Kopete::Plugin +{ +Q_OBJECT +public: + BookmarksPlugin(QObject *parent, const char *name, const QStringList &args); + +private: + typedef struct S_URLANDNAME{ + KURL url; + QString sender; + } URLandName; + typedef QMap JobsToURLsMap; + JobsToURLsMap m_map; + BookmarksPrefsSettings m_settings; + void addKopeteBookmark( const KURL& url, const QString& sender ); + KURL::List* extractURLsFromString( const QString& text ); + KBookmarkGroup getKopeteFolder(); + KBookmarkGroup getFolder( KBookmarkGroup group, const QString& folder ); + QTextCodec* getPageEncoding( const QByteArray& data ); +public slots: + void slotBookmarkURLsInMessage(Kopete::Message & msg); + void slotReloadSettings(); + +private slots: + void slotAddKopeteBookmark( KIO::Job *transfer, const QByteArray &data ); +}; + +typedef KGenericFactory BookmarksPluginFactory; + +#endif diff --git a/kopete/plugins/addbookmarks/addbookmarkspreferences.cpp b/kopete/plugins/addbookmarks/addbookmarkspreferences.cpp new file mode 100644 index 00000000..12ebd877 --- /dev/null +++ b/kopete/plugins/addbookmarks/addbookmarkspreferences.cpp @@ -0,0 +1,114 @@ +// +// C++ Implementation: %{MODULE} +// +// Description: +// +// +// Author: Roie Kerstein , (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#include "addbookmarkspreferences.h" +#include "addbookmarksprefsui.h" +#include "addbookmarksplugin.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +typedef KGenericFactory BookmarksPreferencesFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_addbookmarks, BookmarksPreferencesFactory("kcm_kopete_addbookmarks") ) + +BookmarksPreferences::BookmarksPreferences(QWidget *parent, const char *name, const QStringList &args) + : KCModule(BookmarksPreferencesFactory::instance(), parent, args) +{ + Q_UNUSED( name ); + ( new QVBoxLayout (this) )->setAutoAdd( true ); + p_dialog = new BookmarksPrefsUI( this ); + load(); + connect( p_dialog->yesButton, SIGNAL( toggled(bool) ), this, SLOT( slotSetStatusChanged() )); + connect( p_dialog->noButton, SIGNAL( toggled(bool) ), this, SLOT( slotSetStatusChanged() )); + connect( p_dialog->onlySelectedButton, SIGNAL( toggled(bool) ), this, SLOT( slotSetStatusChanged() )); + connect( p_dialog->onlyNotSelectedButton, SIGNAL( toggled(bool) ), this, SLOT( slotSetStatusChanged() )); + connect( p_dialog->contactList, SIGNAL( selectionChanged() ), this, SLOT( slotSetStatusChanged() )); + if(Kopete::PluginManager::self()->plugin("kopete_addbookmarks") ) + connect( this, SIGNAL(PreferencesChanged()), Kopete::PluginManager::self()->plugin("kopete_addbookmarks") , SLOT(slotReloadSettings())); + connect( p_dialog->m_addUntrusted, SIGNAL( toggled(bool) ), this, SLOT( slotAddUntrustedChanged() ) ); +} + + +BookmarksPreferences::~BookmarksPreferences() +{ +} + +void BookmarksPreferences::save() +{ + QStringList list; + QStringList::iterator it; + + + m_settings.setFolderForEachContact( (BookmarksPrefsSettings::UseSubfolders)p_dialog->buttonGroup1->selectedId() ); + if ( m_settings.isFolderForEachContact() == BookmarksPrefsSettings::SelectedContacts || + m_settings.isFolderForEachContact() == BookmarksPrefsSettings::UnselectedContacts ) { + for( uint i = 0; i < p_dialog->contactList->count() ; ++i ){ + if( p_dialog->contactList->isSelected( i ) ){ + list += p_dialog->contactList->text( i ); + } + } + m_settings.setContactsList( list ); + } + m_settings.setAddBookmarksFromUnknownContacts( p_dialog->m_addUntrusted->isChecked() ); + m_settings.save(); + emit PreferencesChanged(); + emit KCModule::changed(false); +} + +void BookmarksPreferences::slotSetStatusChanged() +{ + if ( p_dialog->buttonGroup1->selectedId() == 1 || p_dialog->buttonGroup1->selectedId() == 0) + p_dialog->contactList->setEnabled(false); + else + p_dialog->contactList->setEnabled(true); + + emit KCModule::changed(true); +} + +void BookmarksPreferences::slotAddUntrustedChanged() +{ + emit KCModule::changed(true); +} + +void BookmarksPreferences::load() +{ + QStringList list; + QStringList::iterator it; + QListBoxItem* item; + + m_settings.load(); + p_dialog->buttonGroup1->setButton(m_settings.isFolderForEachContact()); + p_dialog->m_addUntrusted->setChecked( m_settings.addBookmarksFromUnknownContacts() ); + if( p_dialog->contactList->count() == 0 ){ + QStringList contacts = Kopete::ContactList::self()->contacts(); + contacts.sort(); + p_dialog->contactList->insertStringList( contacts ); + } + p_dialog->contactList->clearSelection(); + p_dialog->contactList->setEnabled( m_settings.isFolderForEachContact() == BookmarksPrefsSettings::SelectedContacts || + m_settings.isFolderForEachContact() == BookmarksPrefsSettings::UnselectedContacts ); + list = m_settings.getContactsList(); + for( it = list.begin() ; it != list.end() ; ++it){ + if ( ( item = p_dialog->contactList->findItem(*it, Qt::ExactMatch ) ) ){ + p_dialog->contactList->setSelected( item, true ); + } + } + emit KCModule::changed(false); +} + +#include "addbookmarkspreferences.moc" diff --git a/kopete/plugins/addbookmarks/addbookmarkspreferences.h b/kopete/plugins/addbookmarks/addbookmarkspreferences.h new file mode 100644 index 00000000..7a9d5bff --- /dev/null +++ b/kopete/plugins/addbookmarks/addbookmarkspreferences.h @@ -0,0 +1,45 @@ +// +// C++ Interface: %{MODULE} +// +// Description: +// +// +// Author: Roie Kerstein , (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#ifndef ADDBOOKMARKSPREFERENCES_H +#define ADDBOOKMARKSPREFERENCES_H + +#include +#include "addbookmarksprefssettings.h" +#include "addbookmarksprefsui.h" + +/** +@author Roie Kerstein +*/ +class BookmarksPreferences : public KCModule +{ +Q_OBJECT +public: + BookmarksPreferences(QWidget *parent = 0, const char *name = 0, const QStringList &args = QStringList()); + + ~BookmarksPreferences(); + + virtual void load(); + virtual void save(); + +signals: + void PreferencesChanged(); + +private: + BookmarksPrefsUI *p_dialog; + BookmarksPrefsSettings m_settings; + +private slots: + void slotSetStatusChanged(); + void slotAddUntrustedChanged(); +}; + +#endif diff --git a/kopete/plugins/addbookmarks/addbookmarksprefssettings.cpp b/kopete/plugins/addbookmarks/addbookmarksprefssettings.cpp new file mode 100644 index 00000000..045ce801 --- /dev/null +++ b/kopete/plugins/addbookmarks/addbookmarksprefssettings.cpp @@ -0,0 +1,87 @@ +// +// C++ Implementation: %{MODULE} +// +// Description: +// +// +// Author: Roie Kerstein , (C) 2004 +// +// License: GPL v2 +// +// +#include +#include +#include + +#include "addbookmarksprefssettings.h" + +BookmarksPrefsSettings::BookmarksPrefsSettings(QObject *parent, const char *name) + : QObject(parent, name) +{ + load(); +} + + +BookmarksPrefsSettings::~BookmarksPrefsSettings() +{ +} + +void BookmarksPrefsSettings::load() +{ + KConfig * configfile = KGlobal::config(); + m_isfolderforeachcontact = Always; + m_contactslist.clear(); + m_addbookmarksfromunknowns = false; + if( configfile->getConfigState() == KConfigBase::NoAccess ){ + kdDebug( 14501 ) << "load: failed to open config file for reading" << endl; + return; + } + if( !configfile->hasGroup("Bookmarks Plugin") ){ + kdDebug( 14501 ) << "load: no config found in file" << endl; + return; + } + configfile->setGroup("Bookmarks Plugin"); + m_isfolderforeachcontact = (UseSubfolders)configfile->readNumEntry( "UseSubfolderForEachContact", 0 ); + m_contactslist = configfile->readListEntry( "ContactsList" ); + m_addbookmarksfromunknowns = configfile->readBoolEntry( "AddBookmarksFromUnknownContacts" ); +} + +void BookmarksPrefsSettings::save() +{ + KConfig * configfile = KGlobal::config(); + + if( configfile->getConfigState() != KConfigBase::ReadWrite ){ + kdDebug( 14501 ) << "save: failed to open config file for writing" << endl; + return; + } + configfile->setGroup( "Bookmarks Plugin" ); + configfile->writeEntry( "UseSubfolderForEachContact", (int)m_isfolderforeachcontact ); + configfile->writeEntry( "ContactsList", m_contactslist ); + configfile->writeEntry( "AddBookmarksFromUnknownContacts", m_addbookmarksfromunknowns ); + configfile->sync(); +} + +bool BookmarksPrefsSettings::useSubfolderForContact( QString nickname ) +{ + if ( !nickname.isEmpty() ) + { + switch( m_isfolderforeachcontact ){ + case Never: + return false; + case Always: + return true; + case SelectedContacts: + return ( m_contactslist.find( nickname ) != m_contactslist.end() ); + case UnselectedContacts: + return ( m_contactslist.find( nickname ) == m_contactslist.end() ); + } + } + return false; +} + +void BookmarksPrefsSettings::setAddBookmarksFromUnknownContacts( bool addUntrusted ) +{ + m_addbookmarksfromunknowns = addUntrusted; +} + +#include "addbookmarksprefssettings.moc" diff --git a/kopete/plugins/addbookmarks/addbookmarksprefssettings.h b/kopete/plugins/addbookmarks/addbookmarksprefssettings.h new file mode 100644 index 00000000..2d82e7c4 --- /dev/null +++ b/kopete/plugins/addbookmarks/addbookmarksprefssettings.h @@ -0,0 +1,49 @@ +// +// C++ Interface: %{MODULE} +// +// Description: +// +// +// Author: Roie Kerstein , (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#ifndef ADDBOOKMARKSPREFSSETTINGS_H +#define ADDBOOKMARKSPREFSSETTINGS_H + +#include +#include + +/** +@author Roie Kerstein +*/ +class BookmarksPrefsSettings : public QObject +{ +Q_OBJECT +public: + enum UseSubfolders { Always=0, Never=1, SelectedContacts=2, UnselectedContacts=3 }; + + BookmarksPrefsSettings(QObject *parent = 0, const char *name = 0); + + ~BookmarksPrefsSettings(); + + void load(); + void save(); + UseSubfolders isFolderForEachContact() {return m_isfolderforeachcontact;} + void setFolderForEachContact(UseSubfolders val) {m_isfolderforeachcontact = val;} + bool useSubfolderForContact( QString nickname ); + QStringList getContactsList() {return m_contactslist;} + void setContactsList(QStringList list) {m_contactslist = list;} + bool addBookmarksFromUnknownContacts() { return m_addbookmarksfromunknowns; }; + void setAddBookmarksFromUnknownContacts( bool ); + +private: + bool m_folderPerContact; + bool m_addbookmarksfromunknowns; + UseSubfolders m_isfolderforeachcontact; + QStringList m_contactslist; + +}; + +#endif diff --git a/kopete/plugins/addbookmarks/addbookmarksprefsui.ui b/kopete/plugins/addbookmarks/addbookmarksprefsui.ui new file mode 100644 index 00000000..67be2b9e --- /dev/null +++ b/kopete/plugins/addbookmarks/addbookmarksprefsui.ui @@ -0,0 +1,104 @@ + +BookmarksPrefsUI + + + BookmarksPrefsUI + + + + 0 + 0 + 396 + 421 + + + + + unnamed + + + + buttonGroup1 + + + Use Subfolder for Each Contact + + + + unnamed + + + + yesButton + + + true + + + Always + + + true + + + + + noButton + + + true + + + Never + + + 1 + + + + + onlySelectedButton + + + true + + + Onl&y the selected contacts + + + false + + + 2 + + + + + onlyNotSelectedButton + + + true + + + Not the selected contacts + + + + + + + contactList + + + + + m_addUntrusted + + + Add Bookmarks from Contacts Not In Your Contact List + + + + + + diff --git a/kopete/plugins/addbookmarks/kopete_addbookmarks.desktop b/kopete/plugins/addbookmarks/kopete_addbookmarks.desktop new file mode 100644 index 00000000..c3c65428 --- /dev/null +++ b/kopete/plugins/addbookmarks/kopete_addbookmarks.desktop @@ -0,0 +1,126 @@ +[Desktop Entry] +Type=Service +Name=Bookmarks +Name[be]=Закладкі +Name[bg]=Отметки +Name[bn]=বà§à¦•à¦®à¦¾à¦°à§à¦• +Name[br]=Sinedoù +Name[bs]=ZabiljeÅ¡ke +Name[ca]=Punts +Name[cs]=Záložky +Name[cy]=Nodau Tudalen +Name[da]=Bogmærker +Name[de]=Lesezeichen +Name[el]=Σελιδοδείκτες +Name[eo]=Legosignoj +Name[es]=Marcadores +Name[et]=Järjehoidjad +Name[eu]=Lastermarkak +Name[fa]=چوب الÙها +Name[fi]=Kirjanmerkit +Name[fr]=Signets +Name[ga]=Leabharmharcanna +Name[gl]=Marcadores +Name[he]=סימניות +Name[hu]=KönyvjelzÅ‘k +Name[id]=Bookmark +Name[is]=Bókamerki +Name[it]=Segnalibri +Name[ja]=ブックマーク +Name[ka]=სáƒáƒœáƒ˜áƒ¨áƒœáƒ”ები +Name[kk]=Бетбелгілер +Name[km]=ចំណាំ +Name[lt]=ŽymelÄ—s +Name[lv]=GrÄmatzÄ«mes +Name[mk]=Обележувачи +Name[mt]=Favoriti +Name[nb]=Bokmerker +Name[nds]=Leestekens +Name[ne]=पà¥à¤¸à¥à¤¤à¤•à¤šà¤¿à¤¨à¥‹ +Name[nl]=Bladwijzers +Name[nn]=Bokmerke +Name[pa]=ਬà©à©±à¨•à¨®à¨¾à¨°à¨• +Name[pl]=ZakÅ‚adki +Name[pt]=Favoritos +Name[pt_BR]=Favoritos +Name[ro]=Semne de carte +Name[ru]=Закладки +Name[rw]=Utumenyetso +Name[sk]=Záložky +Name[sl]=Zaznamki +Name[sr]=Маркери +Name[sr@Latn]=Markeri +Name[sv]=Bokmärken +Name[ta]=பà¯à®¤à¯à®¤à®•à®•à¯à®•à¯à®±à®¿à®•à®³à¯ +Name[th]=ที่คั่นหนังสือ +Name[tr]=Yer imleri +Name[uk]=Закладки +Name[uz]=XatchoÊ»plar +Name[uz@cyrillic]=Хатчўплар +Name[ven]=Dzitswayo dza bugu +Name[wa]=RimÃ¥kes +Name[xh]=Amanqaku encwadi +Name[zh_CN]=书签 +Name[zh_HK]=書籤 +Name[zh_TW]=書籤 +Name[zu]=Amamaki encwadi +Comment=Automatically bookmark links in incoming messages +Comment[be]=Ðўтаматычна Ñтвараць закладкі Ð´Ð»Ñ ÑпаÑылак, перададзеных вам праз Ñ–Ð¼Ð³Ð½ÐµÐ½Ð½Ñ‹Ñ Ð¿Ð°Ð²ÐµÐ´Ð°Ð¼Ð»ÐµÐ½Ð½Ñ– +Comment[bg]=Ðвтоматично добавÑне на връзките във входÑщите ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÐºÑŠÐ¼ отметките +Comment[bn]=অনà§à¦¤à¦°à§à¦®à§à¦–ী বারà§à¦¤à¦¾à¦®à¦§à§à¦¯à¦¸à§à¦¥ লিঙà§à¦•à¦—à§à¦²à§‹ সà§à¦¬à§Ÿà¦‚কà§à¦°à§€à§Ÿà¦­à¦¾à¦¬à§‡ বà§à¦•à¦®à¦¾à¦°à§à¦• করে +Comment[bs]=Automatski zabilježi linkove u dolaznim porukama +Comment[ca]=Apunta automàticament els enllaços en els missatges entrants +Comment[cs]=Automaticky pÅ™idat do záložek odkazy z příchozích zpráv +Comment[da]=Sæt link til indkommende breve automatisk +Comment[de]=Verknüpfungen in eingehenden Nachrichten automatisch als Lesezeichen ablegen +Comment[el]=Αυτόματη τοποθέτηση σελιδοδεικτών στα εισεÏχόμενα μηνÏματα +Comment[es]=Anota automáticamente los enlaces de los mensajes entrantes +Comment[et]=Sissetulevates sõnumites olevate viitade automaatne lisamine järjehoidjatesse +Comment[eu]=Automatikoki gorde lastermarketan sarrerako mezuetako loturak. +Comment[fa]=چوب ال٠به طور خودکار به پیامهای واردشده پیوند می‌خورد +Comment[fi]=Lisää saapuvien viestien sisältämät linkit automaattisesti kirjanmerkkeihin +Comment[fr]=Ajouter automatiquement un signet pour les liens présents dans les messages rentrants +Comment[gl]=Marcar automáticamente as ligazóns nas mensaxes entrantes +Comment[he]=שמור ×§×™×©×•×¨×™× ×ž×”×ž×¡×¨×™× ×”× ×›× ×¡×™× ×‘×¨×©×™×ž×ª הסימניות ב×ופן ×וטומטי +Comment[hu]=KönyvjelzÅ‘ létrehozása a bejövÅ‘ üzenetekben található linkekrÅ‘l +Comment[is]=Setja sjálfkrafa bókamerki á tengla í skilaboðum +Comment[it]=Aggiungi automaticamente al segnalibro i collegamenti nei messaggi in entrata +Comment[ja]=å—信メッセージ中ã®ãƒªãƒ³ã‚¯ã‚’自動的ã«ãƒ–ックマークã«è¿½åŠ  +Comment[ka]=შემáƒáƒ›áƒáƒ•áƒáƒš შეტყáƒáƒ‘ინებებში ბმულების áƒáƒ•áƒ áƒáƒ›áƒáƒ¢áƒ£áƒ áƒáƒ“ ჩáƒáƒœáƒ˜áƒ¨áƒ•áƒœáƒ +Comment[kk]=ÐšÑ–Ñ€Ñ–Ñ Ñ…Ð°Ð±Ð°Ñ€Ð»Ð°Ð¼Ð°Ð»Ð°Ñ€Ð´Ð°Ò“Ñ‹ Ñілтемелерді автоматты түрде бетбелгілеу +Comment[km]=ចំណាំ​ážáŸ†ážŽâ€‹áž“ៅ​ក្នុង​សារ​ចូល ដោយ​ស្វáŸáž™áž”្រវážáŸ’ážáž· +Comment[lt]=Gautose žinutÄ—se esanÄias nuorodas automatiÅ¡kai įtraukti į žymeles +Comment[mk]=Ги обележува автоматÑки врÑките во дојдовните пораки +Comment[nb]=Sett automatisk bokmerke for lenker i innkommende meldinger +Comment[nds]=Links in rinkamen Narichten automaatsch de Leestekens tofögen +Comment[ne]=आगमन सनà¥à¤¦à¥‡à¤¶à¤®à¤¾ सà¥à¤µà¤šà¤¾à¤²à¤¿à¤¤ पà¥à¤¸à¥à¤¤à¤•à¤šà¤¿à¤¨à¥‹ लिङà¥à¤• +Comment[nl]=Koppelingen in inkomende berichten automatisch als bladwijzer opslaan +Comment[nn]=Lag automatisk bokmerke til lenkjer i innkomande meldingar +Comment[pl]=Automatycznie dodawaj zakÅ‚adkÄ™ dla odnoÅ›ników w nadchodzÄ…cych komunikatach +Comment[pt]=Adicionar automaticamente ao favoritos ligações em mensagens recebidas +Comment[pt_BR]=Adiciona automaticamente aos favoritos os links em mensagens recebidas +Comment[ru]=ÐвтоматичеÑки делать закладки ÑÑылок из входных Ñообщений +Comment[sk]=Automaticky vytvorí záložky odkazov v prichádzajúcich správach +Comment[sl]=Samodejno doda povezave v prihajajoÄih sporoÄilih med zaznamke +Comment[sr]=ÐутоматÑки маркирај везе у долазећим порукама +Comment[sr@Latn]=Automatski markiraj veze u dolazećim porukama +Comment[sv]=Bokmärk automatiskt länkar i inkommande meddelande. +Comment[ta]=உளà¯à®µà®°à¯à®®à¯ செயà¯à®¤à®¿à®•à®³à®¿à®²à¯ தானாகவே பà¯à®¤à¯à®¤à®•à®•à¯à®•à¯à®±à®¿ இணைபà¯à®ªà¯à®•à®³à¯ +Comment[tr]=Otomatik olarak gelen mesajları yer imine baÄŸlar +Comment[uk]=Ðвтоматично робити закладки поÑилань з вхідних повідомлень +Comment[zh_CN]=自动将收到消æ¯ä¸­çš„链接加入书签 +Comment[zh_HK]=自動將收到的訊æ¯å…§çš„連çµåŠ åˆ°æ›¸ç±¤ +Comment[zh_TW]=自動將接收訊æ¯ä¸­çš„連çµåŠ å…¥æ›¸ç±¤ +Icon=konqueror +ServiceTypes=Kopete/Plugin +X-Kopete-Version=1000900 +X-KDE-Library=kopete_addbookmarks +X-KDE-PluginInfo-Author=Roie Kerstein +X-KDE-PluginInfo-Email=sf_kersteinroie@bezeqint.net +X-KDE-PluginInfo-Name=kopete_addbookmarks +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false diff --git a/kopete/plugins/addbookmarks/kopete_addbookmarks_config.desktop b/kopete/plugins/addbookmarks/kopete_addbookmarks_config.desktop new file mode 100644 index 00000000..933a4ee1 --- /dev/null +++ b/kopete/plugins/addbookmarks/kopete_addbookmarks_config.desktop @@ -0,0 +1,121 @@ +[Desktop Entry] +Type=Service +Name=Bookmarks +Name[be]=Закладкі +Name[bg]=Отметки +Name[bn]=বà§à¦•à¦®à¦¾à¦°à§à¦• +Name[br]=Sinedoù +Name[bs]=ZabiljeÅ¡ke +Name[ca]=Punts +Name[cs]=Záložky +Name[cy]=Nodau Tudalen +Name[da]=Bogmærker +Name[de]=Lesezeichen +Name[el]=Σελιδοδείκτες +Name[eo]=Legosignoj +Name[es]=Marcadores +Name[et]=Järjehoidjad +Name[eu]=Lastermarkak +Name[fa]=چوب الÙها +Name[fi]=Kirjanmerkit +Name[fr]=Signets +Name[ga]=Leabharmharcanna +Name[gl]=Marcadores +Name[he]=סימניות +Name[hu]=KönyvjelzÅ‘k +Name[id]=Bookmark +Name[is]=Bókamerki +Name[it]=Segnalibri +Name[ja]=ブックマーク +Name[ka]=სáƒáƒœáƒ˜áƒ¨áƒœáƒ”ები +Name[kk]=Бетбелгілер +Name[km]=ចំណាំ +Name[lt]=ŽymelÄ—s +Name[lv]=GrÄmatzÄ«mes +Name[mk]=Обележувачи +Name[mt]=Favoriti +Name[nb]=Bokmerker +Name[nds]=Leestekens +Name[ne]=पà¥à¤¸à¥à¤¤à¤•à¤šà¤¿à¤¨à¥‹ +Name[nl]=Bladwijzers +Name[nn]=Bokmerke +Name[pa]=ਬà©à©±à¨•à¨®à¨¾à¨°à¨• +Name[pl]=ZakÅ‚adki +Name[pt]=Favoritos +Name[pt_BR]=Favoritos +Name[ro]=Semne de carte +Name[ru]=Закладки +Name[rw]=Utumenyetso +Name[sk]=Záložky +Name[sl]=Zaznamki +Name[sr]=Маркери +Name[sr@Latn]=Markeri +Name[sv]=Bokmärken +Name[ta]=பà¯à®¤à¯à®¤à®•à®•à¯à®•à¯à®±à®¿à®•à®³à¯ +Name[th]=ที่คั่นหนังสือ +Name[tr]=Yer imleri +Name[uk]=Закладки +Name[uz]=XatchoÊ»plar +Name[uz@cyrillic]=Хатчўплар +Name[ven]=Dzitswayo dza bugu +Name[wa]=RimÃ¥kes +Name[xh]=Amanqaku encwadi +Name[zh_CN]=书签 +Name[zh_HK]=書籤 +Name[zh_TW]=書籤 +Name[zu]=Amamaki encwadi +Comment=Automatically bookmark links in incoming messages +Comment[be]=Ðўтаматычна Ñтвараць закладкі Ð´Ð»Ñ ÑпаÑылак, перададзеных вам праз Ñ–Ð¼Ð³Ð½ÐµÐ½Ð½Ñ‹Ñ Ð¿Ð°Ð²ÐµÐ´Ð°Ð¼Ð»ÐµÐ½Ð½Ñ– +Comment[bg]=Ðвтоматично добавÑне на връзките във входÑщите ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÐºÑŠÐ¼ отметките +Comment[bn]=অনà§à¦¤à¦°à§à¦®à§à¦–ী বারà§à¦¤à¦¾à¦®à¦§à§à¦¯à¦¸à§à¦¥ লিঙà§à¦•à¦—à§à¦²à§‹ সà§à¦¬à§Ÿà¦‚কà§à¦°à§€à§Ÿà¦­à¦¾à¦¬à§‡ বà§à¦•à¦®à¦¾à¦°à§à¦• করে +Comment[bs]=Automatski zabilježi linkove u dolaznim porukama +Comment[ca]=Apunta automàticament els enllaços en els missatges entrants +Comment[cs]=Automaticky pÅ™idat do záložek odkazy z příchozích zpráv +Comment[da]=Sæt link til indkommende breve automatisk +Comment[de]=Verknüpfungen in eingehenden Nachrichten automatisch als Lesezeichen ablegen +Comment[el]=Αυτόματη τοποθέτηση σελιδοδεικτών στα εισεÏχόμενα μηνÏματα +Comment[es]=Anota automáticamente los enlaces de los mensajes entrantes +Comment[et]=Sissetulevates sõnumites olevate viitade automaatne lisamine järjehoidjatesse +Comment[eu]=Automatikoki gorde lastermarketan sarrerako mezuetako loturak. +Comment[fa]=چوب ال٠به طور خودکار به پیامهای واردشده پیوند می‌خورد +Comment[fi]=Lisää saapuvien viestien sisältämät linkit automaattisesti kirjanmerkkeihin +Comment[fr]=Ajouter automatiquement un signet pour les liens présents dans les messages rentrants +Comment[gl]=Marcar automáticamente as ligazóns nas mensaxes entrantes +Comment[he]=שמור ×§×™×©×•×¨×™× ×ž×”×ž×¡×¨×™× ×”× ×›× ×¡×™× ×‘×¨×©×™×ž×ª הסימניות ב×ופן ×וטומטי +Comment[hu]=KönyvjelzÅ‘ létrehozása a bejövÅ‘ üzenetekben található linkekrÅ‘l +Comment[is]=Setja sjálfkrafa bókamerki á tengla í skilaboðum +Comment[it]=Aggiungi automaticamente al segnalibro i collegamenti nei messaggi in entrata +Comment[ja]=å—信メッセージ中ã®ãƒªãƒ³ã‚¯ã‚’自動的ã«ãƒ–ックマークã«è¿½åŠ  +Comment[ka]=შემáƒáƒ›áƒáƒ•áƒáƒš შეტყáƒáƒ‘ინებებში ბმულების áƒáƒ•áƒ áƒáƒ›áƒáƒ¢áƒ£áƒ áƒáƒ“ ჩáƒáƒœáƒ˜áƒ¨áƒ•áƒœáƒ +Comment[kk]=ÐšÑ–Ñ€Ñ–Ñ Ñ…Ð°Ð±Ð°Ñ€Ð»Ð°Ð¼Ð°Ð»Ð°Ñ€Ð´Ð°Ò“Ñ‹ Ñілтемелерді автоматты түрде бетбелгілеу +Comment[km]=ចំណាំ​ážáŸ†ážŽâ€‹áž“ៅ​ក្នុង​សារ​ចូល ដោយ​ស្វáŸáž™áž”្រវážáŸ’ážáž· +Comment[lt]=Gautose žinutÄ—se esanÄias nuorodas automatiÅ¡kai įtraukti į žymeles +Comment[mk]=Ги обележува автоматÑки врÑките во дојдовните пораки +Comment[nb]=Sett automatisk bokmerke for lenker i innkommende meldinger +Comment[nds]=Links in rinkamen Narichten automaatsch de Leestekens tofögen +Comment[ne]=आगमन सनà¥à¤¦à¥‡à¤¶à¤®à¤¾ सà¥à¤µà¤šà¤¾à¤²à¤¿à¤¤ पà¥à¤¸à¥à¤¤à¤•à¤šà¤¿à¤¨à¥‹ लिङà¥à¤• +Comment[nl]=Koppelingen in inkomende berichten automatisch als bladwijzer opslaan +Comment[nn]=Lag automatisk bokmerke til lenkjer i innkomande meldingar +Comment[pl]=Automatycznie dodawaj zakÅ‚adkÄ™ dla odnoÅ›ników w nadchodzÄ…cych komunikatach +Comment[pt]=Adicionar automaticamente ao favoritos ligações em mensagens recebidas +Comment[pt_BR]=Adiciona automaticamente aos favoritos os links em mensagens recebidas +Comment[ru]=ÐвтоматичеÑки делать закладки ÑÑылок из входных Ñообщений +Comment[sk]=Automaticky vytvorí záložky odkazov v prichádzajúcich správach +Comment[sl]=Samodejno doda povezave v prihajajoÄih sporoÄilih med zaznamke +Comment[sr]=ÐутоматÑки маркирај везе у долазећим порукама +Comment[sr@Latn]=Automatski markiraj veze u dolazećim porukama +Comment[sv]=Bokmärk automatiskt länkar i inkommande meddelande. +Comment[ta]=உளà¯à®µà®°à¯à®®à¯ செயà¯à®¤à®¿à®•à®³à®¿à®²à¯ தானாகவே பà¯à®¤à¯à®¤à®•à®•à¯à®•à¯à®±à®¿ இணைபà¯à®ªà¯à®•à®³à¯ +Comment[tr]=Otomatik olarak gelen mesajları yer imine baÄŸlar +Comment[uk]=Ðвтоматично робити закладки поÑилань з вхідних повідомлень +Comment[zh_CN]=自动将收到消æ¯ä¸­çš„链接加入书签 +Comment[zh_HK]=自動將收到的訊æ¯å…§çš„連çµåŠ åˆ°æ›¸ç±¤ +Comment[zh_TW]=自動將接收訊æ¯ä¸­çš„連çµåŠ å…¥æ›¸ç±¤ +Icon=konqueror +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_addbookmarks +X-KDE-FactoryName=BookmarksConfigFactory +X-KDE-ParentApp=kopete_addbookmarks +X-KDE-ParentComponents=kopete_addbookmarks diff --git a/kopete/plugins/alias/Makefile.am b/kopete/plugins/alias/Makefile.am new file mode 100644 index 00000000..d295e0cc --- /dev/null +++ b/kopete/plugins/alias/Makefile.am @@ -0,0 +1,19 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kopete_alias.la kcm_kopete_alias.la + +kopete_alias_la_SOURCES = aliasplugin.cpp +kopete_alias_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kopete_alias_la_LIBADD = ../../libkopete/libkopete.la + +kcm_kopete_alias_la_SOURCES = aliaspreferences.cpp aliasdialogbase.ui aliasdialog.ui editaliasdialog.cpp +kcm_kopete_alias_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_alias_la_LIBADD = $(LIB_KOPETECOMPAT) $(LIB_KUTILS) ../../libkopete/libkopete.la + +service_DATA = kopete_alias.desktop +servicedir = $(kde_servicesdir) + +kcm_DATA = kopete_alias_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog + diff --git a/kopete/plugins/alias/aliasdialog.ui b/kopete/plugins/alias/aliasdialog.ui new file mode 100644 index 00000000..1d980d52 --- /dev/null +++ b/kopete/plugins/alias/aliasdialog.ui @@ -0,0 +1,193 @@ + +AliasDialog + + + AliasDialog + + + + 0 + 0 + 361 + 268 + + + + Add New Alias + + + + unnamed + + + + textLabel2 + + + Command: + + + command + + + + + command + + + This is the command that you want to run when you execute this alias. + + + <qt>This is the command that you want to run when you execute this alias. + +You can use the variables <b>%1, %2 ... %9</b> in your command, and they will be replaced with the arguments of the alias. The variable <b>%s</b> will be replaced with all arguments. <b>%n</b> expands to your nickname. + +Do not include the '/' in the command (if you do it will be stripped off anyway).</qt> + + + + + textLabel1 + + + Alias: + + + alias + + + + + alias + + + This is the alias you are adding (what you will type after the command identifier, '/'). + + + This is the alias you are adding (what you will type after the command identifier, '/'). Do not include the '/' (it will be stripped off if you do anyway). + + + + + addButton + + + &Save + + + false + + + + + kPushButton3 + + + &Cancel + + + + + + Protocols + + + true + + + true + + + + protocolList + + + true + + + Multi + + + 0 + + + AllColumns + + + true + + + false + + + If you want this alias to only be active for certain protocols, select those protocols here. + + + If you want this alias to only be active for certain protocols, select those protocols here. + + + + + textLabel4 + + + true + + + For protocols: + + + AlignTop + + + kListView2 + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + + + + + kPushButton3 + clicked() + AliasDialog + reject() + + + addButton + clicked() + AliasDialog + accept() + + + + alias + command + protocolList + addButton + kPushButton3 + + + + klineedit.h + klineedit.h + kpushbutton.h + kpushbutton.h + klistview.h + + diff --git a/kopete/plugins/alias/aliasdialogbase.ui b/kopete/plugins/alias/aliasdialogbase.ui new file mode 100644 index 00000000..f70cc5bf --- /dev/null +++ b/kopete/plugins/alias/aliasdialogbase.ui @@ -0,0 +1,107 @@ + +AliasDialogBase + + + AliasDialogBase + + + + 0 + 0 + 602 + 424 + + + + + unnamed + + + + + Alias + + + true + + + true + + + + + Command + + + true + + + true + + + + + Protocols + + + true + + + true + + + + aliasList + + + Extended + + + true + + + false + + + false + + + This is the list of custom aliases and the commands that you have already added + + + + + addButton + + + &Add New Alias... + + + + + deleteButton + + + &Delete Selected + + + + + editButton + + + Edit Alias... + + + + + + + + + klistview.h + kpushbutton.h + kpushbutton.h + kpushbutton.h + + diff --git a/kopete/plugins/alias/aliasplugin.cpp b/kopete/plugins/alias/aliasplugin.cpp new file mode 100644 index 00000000..1594a836 --- /dev/null +++ b/kopete/plugins/alias/aliasplugin.cpp @@ -0,0 +1,38 @@ +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include + +#include "kopetemessagemanagerfactory.h" + +#include "aliasplugin.h" + +typedef KGenericFactory AliasPluginFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_alias, AliasPluginFactory( "kopete_alias" ) ) +AliasPlugin * AliasPlugin::pluginStatic_ = 0L; + +AliasPlugin::AliasPlugin( QObject *parent, const char * name, const QStringList & ) + : Kopete::Plugin( AliasPluginFactory::instance(), parent, name ) +{ + if( !pluginStatic_ ) + pluginStatic_ = this; + +} + +AliasPlugin::~AliasPlugin() +{ + pluginStatic_ = 0L; +} + +AliasPlugin * AliasPlugin::plugin() +{ + return pluginStatic_ ; +} + +#include "aliasplugin.moc" diff --git a/kopete/plugins/alias/aliasplugin.h b/kopete/plugins/alias/aliasplugin.h new file mode 100644 index 00000000..8e55dc20 --- /dev/null +++ b/kopete/plugins/alias/aliasplugin.h @@ -0,0 +1,31 @@ +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef AliasPLUGIN_H +#define AliasPLUGIN_H + +#include "kopeteplugin.h" + +class AliasPlugin : public Kopete::Plugin +{ + Q_OBJECT + + public: + static AliasPlugin *plugin(); + + AliasPlugin( QObject *parent, const char *name, const QStringList &args ); + ~AliasPlugin(); + + private: + static AliasPlugin * pluginStatic_; +}; + +#endif + + diff --git a/kopete/plugins/alias/aliaspreferences.cpp b/kopete/plugins/alias/aliaspreferences.cpp new file mode 100644 index 00000000..65342ddf --- /dev/null +++ b/kopete/plugins/alias/aliaspreferences.cpp @@ -0,0 +1,502 @@ +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopetecommandhandler.h" +#include "kopetepluginmanager.h" +#include "kopeteaccount.h" +#include "kopeteprotocol.h" + +#include "aliasdialogbase.h" +#include "editaliasdialog.h" +#include "aliaspreferences.h" + +typedef KGenericFactory AliasPreferencesFactory; + +class AliasItem : public QListViewItem +{ + public: + AliasItem( QListView *parent, + uint number, + const QString &alias, + const QString &command, const ProtocolList &p ) : + QListViewItem( parent, alias, command ) + { + protocolList = p; + id = number; + } + + ProtocolList protocolList; + uint id; + + protected: + void paintCell( QPainter *p, const QColorGroup &cg, + int column, int width, int align ) + { + if ( column == 2 ) + { + int cellWidth = width - ( protocolList.count() * 16 ) - 4; + if ( cellWidth < 0 ) + cellWidth = 0; + + QListViewItem::paintCell( p, cg, column, cellWidth, align ); + + // Draw the rest of the background + QListView *lv = listView(); + if ( !lv ) + return; + + int marg = lv->itemMargin(); + int r = marg; + const BackgroundMode bgmode = lv->viewport()->backgroundMode(); + const QColorGroup::ColorRole crole = + QPalette::backgroundRoleFromMode( bgmode ); + p->fillRect( cellWidth, 0, width - cellWidth, height(), + cg.brush( crole ) ); + + if ( isSelected() && ( column == 0 || listView()->allColumnsShowFocus() ) ) + { + p->fillRect( QMAX( cellWidth, r - marg ), 0, + width - cellWidth - r + marg, height(), + cg.brush( QColorGroup::Highlight ) ); + if ( isEnabled() || !lv ) + p->setPen( cg.highlightedText() ); + else if ( !isEnabled() && lv ) + p->setPen( lv->palette().disabled().highlightedText() ); + } + + // And last, draw the online status icons + int mc_x = 0; + + for ( ProtocolList::Iterator it = protocolList.begin(); + it != protocolList.end(); ++it ) + { + QPixmap icon = SmallIcon( (*it)->pluginIcon() ); + p->drawPixmap( mc_x + 4, height() - 16, + icon ); + mc_x += 16; + } + } + else + { + // Use Qt's own drawing + QListViewItem::paintCell( p, cg, column, width, align ); + } + } +}; + +class ProtocolItem : public QListViewItem +{ + public: + ProtocolItem( QListView *parent, KPluginInfo *p ) : + QListViewItem( parent, p->name() ) + { + this->setPixmap( 0, SmallIcon( p->icon() ) ); + id = p->pluginName(); + } + + QString id; +}; + +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_alias, AliasPreferencesFactory( "kcm_kopete_alias" ) ) + +AliasPreferences::AliasPreferences( QWidget *parent, const char *, const QStringList &args ) + : KCModule( AliasPreferencesFactory::instance(), parent, args ) +{ + ( new QVBoxLayout( this ) )->setAutoAdd( true ); + preferencesDialog = new AliasDialogBase( this ); + + connect( preferencesDialog->addButton, SIGNAL(clicked()), this, SLOT( slotAddAlias() ) ); + connect( preferencesDialog->editButton, SIGNAL(clicked()), this, SLOT( slotEditAlias() ) ); + connect( preferencesDialog->deleteButton, SIGNAL(clicked()), this, SLOT( slotDeleteAliases() ) ); + connect( Kopete::PluginManager::self(), SIGNAL( pluginLoaded( Kopete::Plugin * ) ), + this, SLOT( slotPluginLoaded( Kopete::Plugin * ) ) ); + + connect( preferencesDialog->aliasList, SIGNAL(selectionChanged()), + this, SLOT( slotCheckAliasSelected() ) ); + + load(); +} + +AliasPreferences::~AliasPreferences() +{ + QListViewItem *myChild = preferencesDialog->aliasList->firstChild(); + while( myChild ) + { + ProtocolList protocols = static_cast( myChild )->protocolList; + for( ProtocolList::Iterator it = protocols.begin(); it != protocols.end(); ++it ) + { + Kopete::CommandHandler::commandHandler()->unregisterAlias( + *it, + myChild->text(0) + ); + } + + myChild = myChild->nextSibling(); + } +} + +// reload configuration reading it from kopeterc +void AliasPreferences::load() +{ + KConfig *config = KGlobal::config(); + if( config->hasGroup( "AliasPlugin" ) ) + { + config->setGroup("AliasPlugin"); + QStringList aliases = config->readListEntry("AliasNames"); + for( QStringList::Iterator it = aliases.begin(); it != aliases.end(); ++it ) + { + uint aliasNumber = config->readUnsignedNumEntry( (*it) + "_id" ); + QString aliasCommand = config->readEntry( (*it) + "_command" ); + QStringList protocols = config->readListEntry( (*it) + "_protocols" ); + + ProtocolList protocolList; + for( QStringList::Iterator it2 = protocols.begin(); it2 != protocols.end(); ++it2 ) + { + Kopete::Plugin *p = Kopete::PluginManager::self()->plugin( *it2 ); + protocolList.append( (Kopete::Protocol*)p ); + } + + addAlias( *it, aliasCommand, protocolList, aliasNumber ); + } + + } + + slotCheckAliasSelected(); +} + +void AliasPreferences::slotPluginLoaded( Kopete::Plugin *plugin ) +{ + Kopete::Protocol *protocol = static_cast( plugin ); + if( protocol ) + { + KConfig *config = KGlobal::config(); + if( config->hasGroup( "AliasPlugin" ) ) + { + config->setGroup("AliasPlugin"); + QStringList aliases = config->readListEntry("AliasNames"); + for( QStringList::Iterator it = aliases.begin(); it != aliases.end(); ++it ) + { + uint aliasNumber = config->readUnsignedNumEntry( (*it) + "_id" ); + QString aliasCommand = config->readEntry( (*it) + "_command" ); + QStringList protocols = config->readListEntry( (*it) + "_protocols" ); + + for( QStringList::iterator it2 = protocols.begin(); it2 != protocols.end(); ++it2 ) + { + if( *it2 == protocol->pluginId() ) + { + QPair pr( protocol, *it ); + if( protocolMap.find( pr ) == protocolMap.end() ) + { + Kopete::CommandHandler::commandHandler()->registerAlias( + protocol, + *it, + aliasCommand, + QString::fromLatin1("Custom alias for %1").arg(aliasCommand), + Kopete::CommandHandler::UserAlias + ); + + protocolMap.insert( pr, true ); + + AliasItem *item = aliasMap[ *it ]; + if( item ) + { + item->protocolList.append( protocol ); + item->repaint(); + } + else + { + ProtocolList pList; + pList.append( protocol ); + aliasMap.insert( *it, new AliasItem( preferencesDialog->aliasList, aliasNumber, *it, aliasCommand, pList ) ); + } + } + } + } + } + } + } +} + +// save list to kopeterc and creates map out of it +void AliasPreferences::save() +{ + KConfig *config = KGlobal::config(); + config->deleteGroup( QString::fromLatin1("AliasPlugin") ); + config->setGroup( QString::fromLatin1("AliasPlugin") ); + + QStringList aliases; + AliasItem *item = (AliasItem*)preferencesDialog->aliasList->firstChild(); + while( item ) + { + QStringList protocols; + for( ProtocolList::Iterator it = item->protocolList.begin(); + it != item->protocolList.end(); ++it ) + { + protocols += (*it)->pluginId(); + } + + aliases += item->text(0); + + config->writeEntry( item->text(0) + "_id", item->id ); + config->writeEntry( item->text(0) + "_command", item->text(1) ); + config->writeEntry( item->text(0) + "_protocols", protocols ); + + item = (AliasItem*)item->nextSibling(); + } + + config->writeEntry( "AliasNames", aliases ); + config->sync(); + emit KCModule::changed(false); +} + +void AliasPreferences::addAlias( QString &alias, QString &command, const ProtocolList &p, uint id ) +{ + QRegExp spaces( QString::fromLatin1("\\s+") ); + + if( alias.startsWith( QString::fromLatin1("/") ) ) + alias = alias.section( '/', 1 ); + if( command.startsWith( QString::fromLatin1("/") ) ) + command = command.section( '/', 1 ); + + if( id == 0 ) + { + if( preferencesDialog->aliasList->lastItem() ) + id = static_cast( preferencesDialog->aliasList->lastItem() )->id + 1; + else + id = 1; + } + + QString newAlias = command.section( spaces, 0, 0 ); + + aliasMap.insert( alias, new AliasItem( preferencesDialog->aliasList, id, alias, command, p ) ); + + // count the number of arguments present in 'command' + QRegExp rx( "(%\\d+)" ); + QStringList list; + int pos = 0; + while ( pos >= 0 ) { + pos = rx.search( command, pos ); + if ( pos > -1 ) { + list += rx.cap( 1 ); + pos += rx.matchedLength(); + } + } + int argc = list.count(); + + for( ProtocolList::ConstIterator it = p.begin(); it != p.end(); ++it ) + { + Kopete::CommandHandler::commandHandler()->registerAlias( + *it, + alias, + command, + QString::fromLatin1("Custom alias for %1").arg(command), + Kopete::CommandHandler::UserAlias, + 0, + argc + ); + + protocolMap.insert( QPair( *it, alias ), true ); + } +} + +void AliasPreferences::slotAddAlias() +{ + EditAliasDialog addDialog; + loadProtocols( &addDialog ); + addDialog.addButton->setText( i18n("&Add") ); + + if( addDialog.exec() == QDialog::Accepted ) + { + QString alias = addDialog.alias->text(); + if( alias.startsWith( QString::fromLatin1("/") ) ) + alias = alias.section( '/', 1 ); + + if( alias.contains( QRegExp("[_=]") ) ) + { + KMessageBox::error( this, i18n("Could not add alias %1. An" + " alias name cannot contain the characters \"_\" or \"=\"." + "").arg(alias),i18n("Invalid Alias Name") ); + } + else + { + QString command = addDialog.command->text(); + ProtocolList protocols = selectedProtocols( &addDialog ); + + // Loop through selected protocols + + for( ProtocolList::Iterator it = protocols.begin(); it != protocols.end(); ++it ) + { + + // And check if they already have the command enabled + + if( Kopete::CommandHandler::commandHandler()->commandHandledByProtocol( alias, *it ) ) + { + KMessageBox::error( this, i18n("Could not add alias %1. This " + "command is already being handled by either another alias or " + "Kopete itself.").arg(alias), i18n("Could Not Add Alias") ); + return; + } + } + addAlias( alias, command, protocols ); + emit KCModule::changed(true); + + } + } +} + +const ProtocolList AliasPreferences::selectedProtocols( EditAliasDialog *dialog ) +{ + ProtocolList protocolList; + QListViewItem *item = dialog->protocolList->firstChild(); + + while( item ) + { + if( item->isSelected() ) + { + + // If you dont have the selected protocol enabled, Kopete::PluginManager::self()->plugin + // will return NULL, check for that + + if(Kopete::PluginManager::self()->plugin( static_cast(item)->id) ) + protocolList.append( (Kopete::Protocol*) + Kopete::PluginManager::self()->plugin( static_cast(item)->id ) + ); + } + item = item->nextSibling(); + } + + return protocolList; +} + +void AliasPreferences::loadProtocols( EditAliasDialog *dialog ) +{ + QValueList plugins = Kopete::PluginManager::self()->availablePlugins("Protocols"); + for( QValueList::Iterator it = plugins.begin(); it != plugins.end(); ++it ) + { + ProtocolItem *item = new ProtocolItem( dialog->protocolList, *it ); + itemMap[ (Kopete::Protocol*)Kopete::PluginManager::self()->plugin( (*it)->pluginName() ) ] = item; + } +} + +void AliasPreferences::slotEditAlias() +{ + EditAliasDialog editDialog; + loadProtocols( &editDialog ); + + QListViewItem *item = preferencesDialog->aliasList->selectedItems().first(); + if( item ) + { + QString oldAlias = item->text(0); + editDialog.alias->setText( oldAlias ); + editDialog.command->setText( item->text(1) ); + ProtocolList protocols = static_cast( item )->protocolList; + for( ProtocolList::Iterator it = protocols.begin(); it != protocols.end(); ++it ) + { + itemMap[ *it ]->setSelected( true ); + } + + if( editDialog.exec() == QDialog::Accepted ) + { + QString alias = editDialog.alias->text(); + if( alias.startsWith( QString::fromLatin1("/") ) ) + alias = alias.section( '/', 1 ); + if( alias.contains( QRegExp("[_=]") ) ) + { + KMessageBox::error( this, i18n("Could not add alias %1. An" + " alias name cannot contain the characters \"_\" or \"=\"." + "").arg(alias),i18n("Invalid Alias Name") ); + } + else + { + QString command = editDialog.command->text(); + + if( alias == oldAlias ) + { + for( ProtocolList::Iterator it = protocols.begin(); it != protocols.end(); ++it ) + { + Kopete::CommandHandler::commandHandler()->unregisterAlias( + *it, + oldAlias + ); + } + + + ProtocolList selProtocols = selectedProtocols( &editDialog ); + + for( ProtocolList::Iterator it = selProtocols.begin(); it != selProtocols.end(); ++it ) + { + if( Kopete::CommandHandler::commandHandler()->commandHandledByProtocol( alias, *it ) ) + { + KMessageBox::error( this, i18n("Could not add alias %1. This " + "command is already being handled by either another alias or " + "Kopete itself.").arg(alias), i18n("Could Not Add Alias") ); + return; + } + } + + delete item; + + addAlias( alias, command, selProtocols ); + emit KCModule::changed(true); + } + } + } + } +} + +void AliasPreferences::slotDeleteAliases() +{ + if( KMessageBox::warningContinueCancel(this, i18n("Are you sure you want to delete the selected aliases?"), i18n("Delete Aliases"), KGuiItem(i18n("Delete"), "editdelete") ) == KMessageBox::Continue ) + { + QPtrList< QListViewItem > items = preferencesDialog->aliasList->selectedItems(); + for( QListViewItem *i = items.first(); i; i = items.next() ) + { + ProtocolList protocols = static_cast( i )->protocolList; + for( ProtocolList::Iterator it = protocols.begin(); it != protocols.end(); ++it ) + { + Kopete::CommandHandler::commandHandler()->unregisterAlias( + *it, + i->text(0) + ); + + protocolMap.erase( QPair( *it, i->text(0) ) ); + } + + aliasMap.erase( i->text(0) ); + delete i; + emit KCModule::changed(true); + } + + save(); + } +} + +void AliasPreferences::slotCheckAliasSelected() +{ + int numItems = preferencesDialog->aliasList->selectedItems().count(); + preferencesDialog->deleteButton->setEnabled( numItems > 0 ); + preferencesDialog->editButton->setEnabled( numItems == 1 ); +} + +#include "aliaspreferences.moc" + diff --git a/kopete/plugins/alias/aliaspreferences.h b/kopete/plugins/alias/aliaspreferences.h new file mode 100644 index 00000000..330553a3 --- /dev/null +++ b/kopete/plugins/alias/aliaspreferences.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef AliasPREFERENCES_H +#define AliasPREFERENCES_H + +#include "kcmodule.h" + +typedef QValueList ProtocolList; + +class AliasDialogBase; +namespace Kopete { class Protocol; } +class ProtocolItem; +class AliasItem; +class AliasDialog; +namespace Kopete { class Plugin; } + +class AliasPreferences : public KCModule +{ + Q_OBJECT + + public: + AliasPreferences( QWidget *parent = 0, const char *name = 0, + const QStringList &args = QStringList() ); + ~AliasPreferences(); + + virtual void save(); + virtual void load(); + + private slots: + void slotAddAlias(); + void slotEditAlias(); + void slotDeleteAliases(); + void slotCheckAliasSelected(); + void slotPluginLoaded( Kopete::Plugin * ); + + private: + AliasDialogBase * preferencesDialog; + void addAlias( QString &alias, QString &command, const ProtocolList &p, uint id = 0 ); + void loadProtocols( EditAliasDialog *dialog ); + const ProtocolList selectedProtocols( EditAliasDialog *dialog ); + QMap itemMap; + QMap, bool> protocolMap; + QMap aliasMap; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/alias/editaliasdialog.cpp b/kopete/plugins/alias/editaliasdialog.cpp new file mode 100644 index 00000000..42eb2f4b --- /dev/null +++ b/kopete/plugins/alias/editaliasdialog.cpp @@ -0,0 +1,51 @@ +/* + Kopete Alias Plugin + + Copyright (c) 2005 by Matt Rogers + Kopete Copyright (c) 2002-2005 by the Kopete Developers + + *************************************************************************** + * * + * 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. * + * * + *************************************************************************** + +*/ + +#include "editaliasdialog.h" +#include +#include +#include +#include +#include +#include + + +EditAliasDialog::EditAliasDialog( QWidget* parent, const char* name ) +: AliasDialog( parent, name ) +{ + QObject::connect( alias, SIGNAL( textChanged( const QString& ) ), this, SLOT( checkButtonsEnabled() ) ); + QObject::connect( command, SIGNAL( textChanged( const QString& ) ), this, SLOT( checkButtonsEnabled() ) ); + QObject::connect( protocolList, SIGNAL( selectionChanged() ), this, SLOT( checkButtonsEnabled() ) ); + + checkButtonsEnabled(); +} + +EditAliasDialog::~EditAliasDialog() +{ +} + +void EditAliasDialog::checkButtonsEnabled() +{ + if ( !alias->text().isEmpty() && !command->text().isEmpty() && !protocolList->selectedItems().isEmpty() ) + addButton->setEnabled( true ); + else + addButton->setEnabled( false ) ; +} + +#include "editaliasdialog.moc" + +// kate: space-indent off; replace-tabs off; tab-width 4; indent-mode csands; diff --git a/kopete/plugins/alias/editaliasdialog.h b/kopete/plugins/alias/editaliasdialog.h new file mode 100644 index 00000000..869e8903 --- /dev/null +++ b/kopete/plugins/alias/editaliasdialog.h @@ -0,0 +1,38 @@ +/* + Kopete Alias Plugin + + Copyright (c) 2005 by Matt Rogers + Kopete Copyright (c) 2002-2005 by the Kopete Developers + + *************************************************************************** + * * + * 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. * + * * + *************************************************************************** + +*/ + +#ifndef _EDITALIASDIALOG_H_ +#define _EDITALIASDIALOG_H_ + +#include "aliasdialog.h" + +class QWidget; + +class EditAliasDialog : public AliasDialog +{ + Q_OBJECT +public: + EditAliasDialog( QWidget* parent = 0, const char* name = 0 ); + virtual ~EditAliasDialog(); + +public slots: + void checkButtonsEnabled(); +}; + +#endif + +// kate: space-indent off; replace-tabs off; tab-width 4; indent-mode csands; diff --git a/kopete/plugins/alias/kopete_alias.desktop b/kopete/plugins/alias/kopete_alias.desktop new file mode 100644 index 00000000..4a23fcd3 --- /dev/null +++ b/kopete/plugins/alias/kopete_alias.desktop @@ -0,0 +1,108 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=alias +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_alias +X-KDE-PluginInfo-Author=Jason Keirstead +X-KDE-PluginInfo-Email=jason@keirstead.org +X-KDE-PluginInfo-Name=kopete_alias +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Alias +Name[ar]=اسم مستعار +Name[be]=ПÑеўданім +Name[bg]=Запазени думи +Name[bn]=ছদà§à¦®à¦¨à¦¾à¦® +Name[br]=Lesanv +Name[ca]=Àlies +Name[cy]=Ffugenw +Name[el]=Αντιστοιχία +Name[eo]=Alinomo +Name[eu]=Aliasa +Name[fa]=نام‌گردان +Name[fi]=Uusi nimi +Name[ga]=Ailias +Name[he]=כינוי +Name[hi]=अलाà¤à¤¸ +Name[hu]=Alias nevek +Name[is]=Samheiti +Name[ja]=エイリアス +Name[ka]=ფსევდáƒáƒœáƒ˜áƒ›áƒ˜ +Name[kk]=Бүркеншік атаулар +Name[km]=ឈ្មោះ​ក្លែងក្លាយ +Name[lt]=Kitas vardas +Name[mk]=Други имиња +Name[ne]=उपनाम +Name[pa]=ਉਪ-ਨਾਂ +Name[pt]=Nome Alternativo +Name[pt_BR]=Apelidos +Name[ru]=ПÑевдоним +Name[sl]=Drugo ime +Name[sr]=ÐÐ»Ð¸Ñ˜Ð°Ñ +Name[sr@Latn]=Alijas +Name[ta]=மாறà¯à®±à¯à®ªà¯à®ªà¯†à®¯à®°à¯ +Name[tg]=Ð¢Ð°Ñ…Ð°Ð»Ð»ÑƒÑ +Name[tr]=Takma Ä°sim +Name[uk]=ПÑевдонім +Name[zh_CN]=别å +Name[zh_HK]=別å +Name[zh_TW]=別å +Comment=Adds custom aliases for commands +Comment[ar]=يضي٠أسماء مستعارة للأوامر +Comment[be]=Дадаць адмыÑÐ»Ð¾Ð²Ñ‹Ñ Ð¿Ñеўданімы Ð´Ð»Ñ Ð·Ð°Ð³Ð°Ð´Ð°Ñž +Comment[bg]=ПриÑтавка за добавÑне на запазени думи, при въвеждането на които ще Ñе изпълнÑват зададени команди +Comment[bn]=কমà§à¦¯à¦¾à¦¨à§à¦¡à§‡à¦° জনà§à¦¯ সà§à¦¬à¦¨à¦¿à¦°à§à¦¬à¦¾à¦šà¦¿à¦¤ ছদà§à¦®à¦¨à¦¾à¦® যোগ করে +Comment[bs]=Dodajte vlastite aliase naredbama +Comment[ca]=Afegeix àlies personalitzats als vostres comandaments +Comment[cs]=PÅ™idává vlastní pÅ™ezdívky pro příkazy +Comment[cy]=Ychwanegu ffugenwau addasiedig ar gyfer gorchmynion +Comment[da]=Tilføj personligt alias for kommandoer +Comment[de]=Fügt benutzerdefinierte Aliase für Befehle hinzu +Comment[el]=ΠÏοσθέτει Ï€ÏοσαÏμοσμένες αντιστοιχίες για εντολές +Comment[es]=Añade alias personales a las órdenes +Comment[et]=Lisab käskudele kohandatud aliase +Comment[eu]=Alias pertsonalizatuak gehitzen ditu aginduentzat +Comment[fa]=نام‌گردانهای سÙارشی را برای Ùرمانها می‌اÙزاید +Comment[fi]=Lisää uusia nimiä komennoille +Comment[fr]=Ajoute des alias personnalisés à des commandes +Comment[gl]=Engadir alias persoáis para comandos +Comment[he]=הוספת ×›×™× ×•×™× ×ž×•×ª××ž×™× ×ישית עבור פקודות +Comment[hi]=कमांडà¥à¤¸ के लिठमनपसंद अलाà¤à¤¸ जोड़े +Comment[hr]=Dodaje posebne aliase za naredbe +Comment[hu]=Egyéni másodlagos (alias) nevek megadása parancsokhoz +Comment[is]=Bætir við samheitum á skipanir +Comment[it]=Aggiungi alias personalizzati per i comandi +Comment[ja]=コマンドã®ã‚«ã‚¹ã‚¿ãƒ ã‚¨ã‚¤ãƒªã‚¢ã‚¹ã‚’追加 +Comment[ka]=ბრძáƒáƒœáƒ”ბებს áƒáƒœáƒ˜áƒ­áƒ”ბს სხვáƒáƒ“áƒáƒ¡áƒ®áƒ•áƒ ფსევდáƒáƒœáƒ˜áƒ›áƒ”ბს +Comment[kk]=Командаларға бүркеншік атауларды беру +Comment[km]=បន្ážáŸ‚ម​ឈ្មោះ​ក្លែងក្លាយ​ផ្ទាល់​ážáŸ’លួន​សម្រាប់​ពាក្យ​បញ្ជា +Comment[lt]=Komandoms suteikiami papildomi vardai +Comment[mk]=Додава ÑопÑтвени алтернативни имиња на командите +Comment[nb]=Legg til egne alias for kommandoer +Comment[nds]=Föögt egen Aliases för Befehlen to +Comment[ne]=आदेशका लागि अनà¥à¤•à¥‚ल उपनामहरू थपà¥à¤¦à¤› +Comment[nl]=Toevoegen van eigen aliassen voor commando's +Comment[nn]=Legg til eigne alias for kommandoar +Comment[pl]=Dodawanie wÅ‚asnych aliasów dla poleceÅ„ +Comment[pt]=Adiciona novos nomes para os comandos +Comment[pt_BR]=Adiciona notas pessoais em seus contatos +Comment[ru]=ДобавлÑет пÑевдонимы Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´ +Comment[se]=Lasit iežat aliasaid gohÄÄumiid várás +Comment[sk]=Pridá vlastné aliasy pre príkazy +Comment[sl]=Dodajanje drugih imen za ukaze +Comment[sr]=Додаје поÑебне алијаÑе за наредбе +Comment[sr@Latn]=Dodaje posebne alijase za naredbe +Comment[sv]=Lägger till egna alias för kommandon +Comment[ta]=கடà¯à®Ÿà®³à¯ˆà®•à®³à¯ தன௠விரà¯à®ªà¯à®ªà®ªà¯ பெயரை சேரà¯à®•à¯à®•à¯à®®à¯ +Comment[tg]=Барои фармонҳо тахаллуÑҳои дигарро илова мекунад +Comment[tr]=Komutlar için özel takma isimler ekler +Comment[uk]=Додає пÑевдоніми Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´ +Comment[zh_CN]=添加命令的自定义别å +Comment[zh_HK]=為命令加上自訂別å +Comment[zh_TW]=新增命令的別å + diff --git a/kopete/plugins/alias/kopete_alias_config.desktop b/kopete/plugins/alias/kopete_alias_config.desktop new file mode 100644 index 00000000..01fadd24 --- /dev/null +++ b/kopete/plugins/alias/kopete_alias_config.desktop @@ -0,0 +1,104 @@ +[Desktop Entry] +Icon=color +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_alias +X-KDE-FactoryName=AliasConfigFactory +X-KDE-ParentApp=kopete_alias +X-KDE-ParentComponents=kopete_alias + +Name=Alias +Name[ar]=اسم مستعار +Name[be]=ПÑеўданім +Name[bg]=Запазени думи +Name[bn]=ছদà§à¦®à¦¨à¦¾à¦® +Name[br]=Lesanv +Name[ca]=Àlies +Name[cy]=Ffugenw +Name[el]=Αντιστοιχία +Name[eo]=Alinomo +Name[eu]=Aliasa +Name[fa]=نام‌گردان +Name[fi]=Uusi nimi +Name[ga]=Ailias +Name[he]=כינוי +Name[hi]=अलाà¤à¤¸ +Name[hu]=Alias nevek +Name[is]=Samheiti +Name[ja]=エイリアス +Name[ka]=ფსევდáƒáƒœáƒ˜áƒ›áƒ˜ +Name[kk]=Бүркеншік атаулар +Name[km]=ឈ្មោះ​ក្លែងក្លាយ +Name[lt]=Kitas vardas +Name[mk]=Други имиња +Name[ne]=उपनाम +Name[pa]=ਉਪ-ਨਾਂ +Name[pt]=Nome Alternativo +Name[pt_BR]=Apelidos +Name[ru]=ПÑевдоним +Name[sl]=Drugo ime +Name[sr]=ÐÐ»Ð¸Ñ˜Ð°Ñ +Name[sr@Latn]=Alijas +Name[ta]=மாறà¯à®±à¯à®ªà¯à®ªà¯†à®¯à®°à¯ +Name[tg]=Ð¢Ð°Ñ…Ð°Ð»Ð»ÑƒÑ +Name[tr]=Takma Ä°sim +Name[uk]=ПÑевдонім +Name[zh_CN]=别å +Name[zh_HK]=別å +Name[zh_TW]=別å +Comment=Adds custom aliases for commands +Comment[ar]=يضي٠أسماء مستعارة للأوامر +Comment[be]=Дадаць адмыÑÐ»Ð¾Ð²Ñ‹Ñ Ð¿Ñеўданімы Ð´Ð»Ñ Ð·Ð°Ð³Ð°Ð´Ð°Ñž +Comment[bg]=ПриÑтавка за добавÑне на запазени думи, при въвеждането на които ще Ñе изпълнÑват зададени команди +Comment[bn]=কমà§à¦¯à¦¾à¦¨à§à¦¡à§‡à¦° জনà§à¦¯ সà§à¦¬à¦¨à¦¿à¦°à§à¦¬à¦¾à¦šà¦¿à¦¤ ছদà§à¦®à¦¨à¦¾à¦® যোগ করে +Comment[bs]=Dodajte vlastite aliase naredbama +Comment[ca]=Afegeix àlies personalitzats als vostres comandaments +Comment[cs]=PÅ™idává vlastní pÅ™ezdívky pro příkazy +Comment[cy]=Ychwanegu ffugenwau addasiedig ar gyfer gorchmynion +Comment[da]=Tilføj personligt alias for kommandoer +Comment[de]=Fügt benutzerdefinierte Aliase für Befehle hinzu +Comment[el]=ΠÏοσθέτει Ï€ÏοσαÏμοσμένες αντιστοιχίες για εντολές +Comment[es]=Añade alias personales a las órdenes +Comment[et]=Lisab käskudele kohandatud aliase +Comment[eu]=Alias pertsonalizatuak gehitzen ditu aginduentzat +Comment[fa]=نام‌گردانهای سÙارشی را برای Ùرمانها می‌اÙزاید +Comment[fi]=Lisää uusia nimiä komennoille +Comment[fr]=Ajoute des alias personnalisés à des commandes +Comment[gl]=Engadir alias persoáis para comandos +Comment[he]=הוספת ×›×™× ×•×™× ×ž×•×ª××ž×™× ×ישית עבור פקודות +Comment[hi]=कमांडà¥à¤¸ के लिठमनपसंद अलाà¤à¤¸ जोड़े +Comment[hr]=Dodaje posebne aliase za naredbe +Comment[hu]=Egyéni másodlagos (alias) nevek megadása parancsokhoz +Comment[is]=Bætir við samheitum á skipanir +Comment[it]=Aggiungi alias personalizzati per i comandi +Comment[ja]=コマンドã®ã‚«ã‚¹ã‚¿ãƒ ã‚¨ã‚¤ãƒªã‚¢ã‚¹ã‚’追加 +Comment[ka]=ბრძáƒáƒœáƒ”ბებს áƒáƒœáƒ˜áƒ­áƒ”ბს სხვáƒáƒ“áƒáƒ¡áƒ®áƒ•áƒ ფსევდáƒáƒœáƒ˜áƒ›áƒ”ბს +Comment[kk]=Командаларға бүркеншік атауларды беру +Comment[km]=បន្ážáŸ‚ម​ឈ្មោះ​ក្លែងក្លាយ​ផ្ទាល់​ážáŸ’លួន​សម្រាប់​ពាក្យ​បញ្ជា +Comment[lt]=Komandoms suteikiami papildomi vardai +Comment[mk]=Додава ÑопÑтвени алтернативни имиња на командите +Comment[nb]=Legg til egne alias for kommandoer +Comment[nds]=Föögt egen Aliases för Befehlen to +Comment[ne]=आदेशका लागि अनà¥à¤•à¥‚ल उपनामहरू थपà¥à¤¦à¤› +Comment[nl]=Toevoegen van eigen aliassen voor commando's +Comment[nn]=Legg til eigne alias for kommandoar +Comment[pl]=Dodawanie wÅ‚asnych aliasów dla poleceÅ„ +Comment[pt]=Adiciona novos nomes para os comandos +Comment[pt_BR]=Adiciona notas pessoais em seus contatos +Comment[ru]=ДобавлÑет пÑевдонимы Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´ +Comment[se]=Lasit iežat aliasaid gohÄÄumiid várás +Comment[sk]=Pridá vlastné aliasy pre príkazy +Comment[sl]=Dodajanje drugih imen za ukaze +Comment[sr]=Додаје поÑебне алијаÑе за наредбе +Comment[sr@Latn]=Dodaje posebne alijase za naredbe +Comment[sv]=Lägger till egna alias för kommandon +Comment[ta]=கடà¯à®Ÿà®³à¯ˆà®•à®³à¯ தன௠விரà¯à®ªà¯à®ªà®ªà¯ பெயரை சேரà¯à®•à¯à®•à¯à®®à¯ +Comment[tg]=Барои фармонҳо тахаллуÑҳои дигарро илова мекунад +Comment[tr]=Komutlar için özel takma isimler ekler +Comment[uk]=Додає пÑевдоніми Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´ +Comment[zh_CN]=添加命令的自定义别å +Comment[zh_HK]=為命令加上自訂別å +Comment[zh_TW]=新增命令的別å + diff --git a/kopete/plugins/autoreplace/Makefile.am b/kopete/plugins/autoreplace/Makefile.am new file mode 100644 index 00000000..511da48d --- /dev/null +++ b/kopete/plugins/autoreplace/Makefile.am @@ -0,0 +1,21 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +SUBDIRS = icons + +kde_module_LTLIBRARIES = kopete_autoreplace.la kcm_kopete_autoreplace.la + +kopete_autoreplace_la_SOURCES = autoreplaceplugin.cpp autoreplaceconfig.cpp +kopete_autoreplace_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kopete_autoreplace_la_LIBADD = ../../libkopete/libkopete.la + +kcm_kopete_autoreplace_la_SOURCES = autoreplacepreferences.cpp autoreplaceconfig.cpp autoreplaceprefs.ui +kcm_kopete_autoreplace_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_autoreplace_la_LIBADD = ../../libkopete/libkopete.la $(LIB_KUTILS) + +service_DATA = kopete_autoreplace.desktop +servicedir = $(kde_servicesdir) + +kcm_DATA = kopete_autoreplace_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog + diff --git a/kopete/plugins/autoreplace/autoreplaceconfig.cpp b/kopete/plugins/autoreplace/autoreplaceconfig.cpp new file mode 100644 index 00000000..0407990a --- /dev/null +++ b/kopete/plugins/autoreplace/autoreplaceconfig.cpp @@ -0,0 +1,133 @@ +/* + autoreplaceconfig.cpp + + Copyright (c) 2003 by Roberto Pariset + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "autoreplaceconfig.h" + +#include +#include +#include + +AutoReplaceConfig::AutoReplaceConfig() +{ + load(); +} + +// reload configuration reading it from kopeterc +void AutoReplaceConfig::load() +{ + KConfig *config = KGlobal::config(); + config->setGroup( "AutoReplace Plugin" ); + + QStringList wordsList = config->readListEntry( "WordsToReplace" ); + if( wordsList.isEmpty() ) + { + // basic list, key/value + // a list based on i18n should be provided, i.e. for italian + // "qsa,qualcosa,qno,qualcuno" remember UTF-8 accents + wordsList = defaultAutoReplaceList(); + } + + // we may be reloading after removing an entry from the list + m_map.clear(); + QString k, v; + for ( QStringList::Iterator it = wordsList.begin(); it != wordsList.end(); ++it ) + { + k = *it; + ++it; + if( it == wordsList.end() ) + break; + v = *it; + m_map.insert( k, v ); + } + + m_autoreplaceIncoming = config->readBoolEntry( "AutoReplaceIncoming" , false ); + m_autoreplaceOutgoing = config->readBoolEntry( "AutoReplaceOutgoing" , true ); + m_addDot = config->readBoolEntry( "DotEndSentence" , false ); + m_upper = config->readBoolEntry( "CapitalizeBeginningSentence" , false ); +} + +QStringList AutoReplaceConfig::defaultAutoReplaceList() +{ + return QStringList::split( ",", i18n( "list_of_words_to_replace", + "ur,your,r,are,u,you,theres,there is,arent,are not,dont,do not" ) ); +} + +void AutoReplaceConfig::loadDefaultAutoReplaceList() +{ + QStringList wordsList = defaultAutoReplaceList(); + m_map.clear(); + QString k, v; + for ( QStringList::Iterator it = wordsList.begin(); it != wordsList.end(); ++it ) + { + k = *it; + v = *( ++it ); + m_map.insert( k, v ); + } +} + + +bool AutoReplaceConfig::autoReplaceIncoming() const +{ + return m_autoreplaceIncoming; +} + +bool AutoReplaceConfig::autoReplaceOutgoing() const +{ + return m_autoreplaceOutgoing; +} + +bool AutoReplaceConfig::dotEndSentence() const +{ + return m_addDot; +} + +bool AutoReplaceConfig::capitalizeBeginningSentence() const +{ + return m_upper; +} + +void AutoReplaceConfig::setMap( const WordsToReplace &w ) +{ + m_map = w; +} + +AutoReplaceConfig::WordsToReplace AutoReplaceConfig::map() const +{ + return m_map; +} + +void AutoReplaceConfig::save() +{ + KConfig * config = KGlobal::config(); + config->setGroup( "AutoReplace Plugin" ); + + QStringList newWords; + WordsToReplace::Iterator it; + for ( it = m_map.begin(); it != m_map.end(); ++it ) + { + newWords.append( it.key() ); + newWords.append( it.data() ); + } + + config->writeEntry( "WordsToReplace", newWords ); + + config->sync(); +} + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/autoreplace/autoreplaceconfig.h b/kopete/plugins/autoreplace/autoreplaceconfig.h new file mode 100644 index 00000000..62b11fbf --- /dev/null +++ b/kopete/plugins/autoreplace/autoreplaceconfig.h @@ -0,0 +1,58 @@ +/* + autoreplaceconfig.h + + Copyright (c) 2003 by Roberto Pariset + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#ifndef AutoReplaceConfig_H +#define AutoReplaceConfig_H + +class AutoReplaceConfig +{ +public: + AutoReplaceConfig(); + + void save(); + void load(); + + typedef QMap WordsToReplace; + + WordsToReplace map() const; + bool autoReplaceIncoming() const; + bool autoReplaceOutgoing() const; + bool dotEndSentence() const; + bool capitalizeBeginningSentence() const; + + void setMap( const WordsToReplace &w ); + QStringList defaultAutoReplaceList(); + void loadDefaultAutoReplaceList(); + +private: + WordsToReplace m_map; + + bool m_autoreplaceIncoming; + bool m_autoreplaceOutgoing; + bool m_addDot; + bool m_upper; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/autoreplace/autoreplaceplugin.cpp b/kopete/plugins/autoreplace/autoreplaceplugin.cpp new file mode 100644 index 00000000..c06bc0bd --- /dev/null +++ b/kopete/plugins/autoreplace/autoreplaceplugin.cpp @@ -0,0 +1,128 @@ +/*************************************************************************** + autoreplaceplugin.cpp - description + ------------------- + begin : 20030425 + copyright : (C) 2003 by Roberto Pariset + email : victorheremita@fastwebnet.it + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include + +#include + +#include "kopetechatsessionmanager.h" +#include "kopetesimplemessagehandler.h" + +#include "autoreplaceplugin.h" +#include "autoreplaceconfig.h" + +typedef KGenericFactory AutoReplacePluginFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_autoreplace, AutoReplacePluginFactory( "kopete_autoreplace" ) ) +AutoReplacePlugin * AutoReplacePlugin::pluginStatic_ = 0L; + +AutoReplacePlugin::AutoReplacePlugin( QObject *parent, const char * name, const QStringList & ) +: Kopete::Plugin( AutoReplacePluginFactory::instance(), parent, name ) +{ + if( !pluginStatic_ ) + pluginStatic_ = this; + + m_prefs = new AutoReplaceConfig; + + connect( Kopete::ChatSessionManager::self(), SIGNAL( aboutToSend( Kopete::Message & ) ), + this, SLOT( slotAboutToSend( Kopete::Message & ) ) ); + + // nb this connection causes the slot to be called on in- and outbound + // messages which suggests something is broken in the message handler + // system! + m_inboundHandler = new Kopete::SimpleMessageHandlerFactory( Kopete::Message::Inbound, + Kopete::MessageHandlerFactory::InStageToSent, this, SLOT( slotAboutToSend( Kopete::Message& ) ) ); + + connect( this, SIGNAL( settingsChanged() ), this, SLOT( slotSettingsChanged() ) ); +} + +AutoReplacePlugin::~AutoReplacePlugin() +{ + pluginStatic_ = 0L; + delete m_inboundHandler; + delete m_prefs; +} + +AutoReplacePlugin * AutoReplacePlugin::plugin() +{ + return pluginStatic_ ; +} + +void AutoReplacePlugin::slotSettingsChanged() +{ + m_prefs->load(); +} + +void AutoReplacePlugin::slotAboutToSend( Kopete::Message &msg ) +{ + if ( ( msg.direction() == Kopete::Message::Outbound && m_prefs->autoReplaceOutgoing() ) || + ( msg.direction() == Kopete::Message::Inbound && m_prefs->autoReplaceIncoming() ) ) + { + QString replaced_message = msg.plainBody(); + AutoReplaceConfig::WordsToReplace map = m_prefs->map(); + + // replaces all matched words --> try to find a more 'economic' way + // "\\b(%1)\\b" doesn't work when substituting /me. + QString match = "(^|\\s|\\.|\\;|\\,|\\:)(%1)(\\b)"; + AutoReplaceConfig::WordsToReplace::Iterator it; + bool isReplaced=false; + for ( it = map.begin(); it != map.end(); ++it ) + { + QRegExp re( match.arg( QRegExp::escape( it.key() ) ) ); + if( re.search( replaced_message ) != -1 ) + { + QString before = re.cap(1); + QString after = re.cap(3); + replaced_message.replace( re, before + map.find( it.key() ).data() + after ); + isReplaced=true; + } + } + + // the message is now the one with replaced words + if(isReplaced) + msg.setBody( replaced_message, Kopete::Message::PlainText ); + + if( msg.direction() == Kopete::Message::Outbound ) + { + if ( m_prefs->dotEndSentence() ) + { + QString replaced_message = msg.plainBody(); + // eventually add . at the end of the lines, sent lines only + replaced_message.replace( QRegExp( "([a-z])$" ), "\\1." ); + // replaced_message.replace(QRegExp( "([\\w])$" ), "\\1." ); + + // the message is now the one with replaced words + msg.setBody( replaced_message, Kopete::Message::PlainText ); + } + + if( m_prefs->capitalizeBeginningSentence() ) + { + QString replaced_message = msg.plainBody(); + // eventually start each sent line with capital letter + // TODO ". " "? " "! " + replaced_message[ 0 ] = replaced_message.at( 0 ).upper(); + + // the message is now the one with replaced words + msg.setBody( replaced_message, Kopete::Message::PlainText ); + } + } + } +} + +#include "autoreplaceplugin.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/autoreplace/autoreplaceplugin.h b/kopete/plugins/autoreplace/autoreplaceplugin.h new file mode 100644 index 00000000..750f0614 --- /dev/null +++ b/kopete/plugins/autoreplace/autoreplaceplugin.h @@ -0,0 +1,63 @@ +/*************************************************************************** + autoreplaceplugin.h - description + ------------------- + begin : 20030425 + copyright : (C) 2003 by Roberto Pariset + email : victorheremita@fastwebnet.it + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef AutoReplacePLUGIN_H +#define AutoReplacePLUGIN_H + +#include +#include +#include +#include + +#include "kopetemessage.h" +#include "kopeteplugin.h" + +namespace Kopete { + class Message; + class MetaContact; + class ChatSession; + class SimpleMessageHandlerFactory; +} + +class AutoReplaceConfig; + +class AutoReplacePlugin : public Kopete::Plugin +{ + Q_OBJECT + +public: + static AutoReplacePlugin *plugin(); + + AutoReplacePlugin( QObject *parent, const char *name, const QStringList &args ); + ~AutoReplacePlugin(); + +private slots: + void slotAboutToSend( Kopete::Message &msg ); + + void slotSettingsChanged(); + +private: + static AutoReplacePlugin * pluginStatic_; + Kopete::SimpleMessageHandlerFactory *m_inboundHandler; + + AutoReplaceConfig *m_prefs; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/autoreplace/autoreplacepreferences.cpp b/kopete/plugins/autoreplace/autoreplacepreferences.cpp new file mode 100644 index 00000000..0a2a6b0f --- /dev/null +++ b/kopete/plugins/autoreplace/autoreplacepreferences.cpp @@ -0,0 +1,215 @@ +/*************************************************************************** + autoreplacepreferences.cpp - description + ------------------- + begin : 20030426 + copyright : (C) 2003 by Roberto Pariset + email : victorheremita@fastwebnet.it + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "autoreplaceprefs.h" +#include "autoreplacepreferences.h" +#include "autoreplaceconfig.h" + +typedef KGenericFactory AutoReplacePreferencesFactory; + +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_autoreplace, AutoReplacePreferencesFactory( "kcm_kopete_autoreplace" ) ) + +AutoReplacePreferences::AutoReplacePreferences( QWidget *parent, const char * /* name */, const QStringList &args ) +: KCAutoConfigModule( AutoReplacePreferencesFactory::instance(), parent, args ) +{ + ( new QVBoxLayout( this ) )->setAutoAdd( true ); + preferencesDialog = new AutoReplacePrefsUI( this ); + + // creates table columns (avoids new columns every time) + preferencesDialog->m_list->addColumn( i18n("Text" ) ); + preferencesDialog->m_list->addColumn( i18n("Replacement" ) ); + preferencesDialog->m_list->header()->setStretchEnabled( true , 1 ); + + // connect SIGNALS/SLOTS + connect( preferencesDialog->m_add, SIGNAL(pressed()), + SLOT( slotAddCouple()) ); + connect( preferencesDialog->m_edit, SIGNAL(pressed()), + SLOT( slotEditCouple()) ); + connect( preferencesDialog->m_remove, SIGNAL(pressed()), + SLOT(slotRemoveCouple()) ); + connect( preferencesDialog->m_list, SIGNAL(selectionChanged()), + SLOT(slotSelectionChanged()) ); + connect( preferencesDialog->m_key, SIGNAL(textChanged ( const QString & )), + SLOT( slotEnableAddEdit( const QString & )) ); + + m_wordListChanged = false; + + // Sentence options and which messages to apply autoreplace to + // are managed by KCMAutoConfigModule. The list of replacements + // itself is manually read/written as KCMAutoConfigModule doesn't support it. + autoConfig()->ignoreSubWidget( preferencesDialog->replacementsGroup ); + setMainWidget( preferencesDialog, "AutoReplace Plugin" ); + + m_config = new AutoReplaceConfig; + load(); +} + +AutoReplacePreferences::~AutoReplacePreferences() +{ + delete m_config; +} + +// reload configuration reading it from kopeterc +void AutoReplacePreferences::load() +{ + m_config->load(); + + // Removes and deletes all the items in this list view and triggers an update + preferencesDialog->m_list->clear(); + + // show keys/values on gui + AutoReplaceConfig::WordsToReplace::Iterator it; + AutoReplaceConfig::WordsToReplace map = m_config->map(); + for ( it = map.begin(); it != map.end(); ++it ) + { + // notice: insertItem is called automatically by the constructor + new QListViewItem( preferencesDialog->m_list, it.key(), it.data() ); + } + + m_wordListChanged = false; + KCAutoConfigModule::load(); +} + +// save list to kopeterc and creates map out of it +void AutoReplacePreferences::save() +{ + // make a list reading all values from gui + AutoReplaceConfig::WordsToReplace newWords; + for ( QListViewItem * i = preferencesDialog->m_list->firstChild(); i != 0; i = i->nextSibling() ) + newWords[ i->text( 0 ) ] = i->text( 1 ); + + // save the words list + m_config->setMap( newWords ); + m_config->save(); + + m_wordListChanged = false; + KCAutoConfigModule::save(); +} + +// read m_key m_value, create a QListViewItem +void AutoReplacePreferences::slotAddCouple() +{ + QString k = preferencesDialog->m_key->text(); + QString v = preferencesDialog->m_value->text(); + if ( !k.isEmpty() && !k.isNull() && !v.isEmpty() && !v.isNull() ) + { + QListViewItem * lvi; + QListViewItem * oldLvi = 0; + // see if we are replacing an existing entry + if ( ( oldLvi = preferencesDialog->m_list->findItem( k, 0 ) ) ) + delete oldLvi; + lvi = new QListViewItem( preferencesDialog->m_list, k, v ); + // Triggers a size, geometry and content update + // during the next iteration of the event loop + preferencesDialog->m_list->triggerUpdate(); + // select last added + preferencesDialog->m_list->setSelected( lvi, true ); + } + + m_wordListChanged = true; + slotWidgetModified(); +} + +// edit the selected item +void AutoReplacePreferences::slotEditCouple() +{ + QString k = preferencesDialog->m_key->text(); + QString v = preferencesDialog->m_value->text(); + QListViewItem * lvi; + if ( ( lvi = preferencesDialog->m_list->selectedItem() ) && !k.isEmpty() && !k.isNull() && !v.isEmpty() && !v.isNull() ) + { + lvi->setText( 0, k ); + lvi->setText( 1, v ); + preferencesDialog->m_list->triggerUpdate(); + m_wordListChanged = true; + slotWidgetModified(); + } +} + +// Returns a pointer to the selected item if the list view is in +// Single selection mode and an item is selected +void AutoReplacePreferences::slotRemoveCouple() +{ + delete preferencesDialog->m_list->selectedItem(); + + m_wordListChanged = true; + slotWidgetModified(); +} + +void AutoReplacePreferences::slotEnableAddEdit( const QString & keyText ) +{ + preferencesDialog->m_add->setEnabled( !keyText.isEmpty() ); + preferencesDialog->m_edit->setEnabled( !keyText.isEmpty() && preferencesDialog->m_list->selectedItem() ); +} + +void AutoReplacePreferences::slotSelectionChanged() +{ + QListViewItem *selection = 0; + if ( ( selection = preferencesDialog->m_list->selectedItem() ) ) + { + // enable the remove button + preferencesDialog->m_remove->setEnabled( true ); + // put the selection contents into the text entry widgets so they can be edited + preferencesDialog->m_key->setText( selection->text( 0 ) ); + preferencesDialog->m_value->setText( selection->text( 1 ) ); + } + else + { + preferencesDialog->m_remove->setEnabled( false ); + preferencesDialog->m_key->clear(); + preferencesDialog->m_value->clear(); + } +} + +void AutoReplacePreferences::slotWidgetModified() +{ + emit KCModule::changed( m_wordListChanged || autoConfig()->hasChanged() ); +} + +void AutoReplacePreferences::defaults() +{ + KCAutoConfigModule::defaults(); + preferencesDialog->m_list->clear(); + m_config->loadDefaultAutoReplaceList(); + AutoReplaceConfig::WordsToReplace::Iterator it; + AutoReplaceConfig::WordsToReplace map = m_config->map(); + for ( it = map.begin(); it != map.end(); ++it ) + { + // notice: insertItem is called automatically by the constructor + new QListViewItem( preferencesDialog->m_list, it.key(), it.data() ); + } + m_wordListChanged = true; + slotWidgetModified(); +} + +#include "autoreplacepreferences.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/autoreplace/autoreplacepreferences.h b/kopete/plugins/autoreplace/autoreplacepreferences.h new file mode 100644 index 00000000..a08b2ba2 --- /dev/null +++ b/kopete/plugins/autoreplace/autoreplacepreferences.h @@ -0,0 +1,64 @@ +/*************************************************************************** + autoreplacepreferences.h - description + ------------------- + begin : 20030426 + copyright : (C) 2003 by Roberto Pariset + email : victorheremita@fastwebnet.it + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef AutoReplacePREFERENCES_H +#define AutoReplacePREFERENCES_H + +#include "kcautoconfigmodule.h" + +class AutoReplacePrefsUI; +class AutoReplaceConfig; + + // TODO + // add button enabled only when k and v are present + // remove button enabled only when a QListViewItem is selected + // signal/slot when map changes (needed?) + // capital letter not just at the beginning but always after ". ", "! "... + +class AutoReplacePreferences : public KCAutoConfigModule +{ + Q_OBJECT + +public: + AutoReplacePreferences( QWidget *parent = 0, const char *name = 0, const QStringList &args = QStringList() ); + ~AutoReplacePreferences(); + + virtual void save(); + virtual void load(); + virtual void defaults(); + +private slots: + //void slotSettingsDirty(); + void slotAddCouple(); + void slotEditCouple(); + void slotRemoveCouple(); + void slotEnableAddEdit( const QString & ); + void slotSelectionChanged(); + +protected slots: + virtual void slotWidgetModified(); +private: + AutoReplacePrefsUI * preferencesDialog; + AutoReplaceConfig *m_config; + + bool m_wordListChanged; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/autoreplace/autoreplaceprefs.ui b/kopete/plugins/autoreplace/autoreplaceprefs.ui new file mode 100644 index 00000000..09db8e9d --- /dev/null +++ b/kopete/plugins/autoreplace/autoreplaceprefs.ui @@ -0,0 +1,219 @@ + +AutoReplacePrefsUI +Roberto Pariset + + + AutoReplacePrefsUI + + + + 0 + 0 + 458 + 378 + + + + + unnamed + + + + gb_sentences + + + Sentence Options + + + + unnamed + + + + DotEndSentence + + + Add a dot at the end of each sent line + + + + + CapitalizeBeginningSentence + + + Start each sent line with a capital letter + + + + + + + gb_options + + + Replacement Options + + + + unnamed + + + + AutoReplaceIncoming + + + Auto replace on incoming messages + + + + + AutoReplaceOutgoing + + + Auto replace on outgoing messages + + + true + + + + + + + replacementsGroup + + + Replacements List + + + + unnamed + + + + layout5 + + + + unnamed + + + + m_add + + + true + + + &Add + + + false + + + + + m_edit + + + false + + + &Edit + + + false + + + + + spacer1 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + m_remove + + + false + + + &Remove + + + + + + + layout1 + + + + unnamed + + + + textLabel1 + + + Te&xt: + + + m_key + + + + + m_key + + + + + textLabel2 + + + Re&placement: + + + m_value + + + + + m_value + + + + + + + m_list + + + Single + + + true + + + true + + + + + + + + diff --git a/kopete/plugins/autoreplace/icons/Makefile.am b/kopete/plugins/autoreplace/icons/Makefile.am new file mode 100644 index 00000000..224eb420 --- /dev/null +++ b/kopete/plugins/autoreplace/icons/Makefile.am @@ -0,0 +1,3 @@ +kopeteicondir = $(kde_datadir)/kopete/icons +kopeteicon_ICON = AUTO + diff --git a/kopete/plugins/autoreplace/icons/cr32-app-autoreplace.png b/kopete/plugins/autoreplace/icons/cr32-app-autoreplace.png new file mode 100644 index 00000000..71f1fa32 Binary files /dev/null and b/kopete/plugins/autoreplace/icons/cr32-app-autoreplace.png differ diff --git a/kopete/plugins/autoreplace/kopete_autoreplace.desktop b/kopete/plugins/autoreplace/kopete_autoreplace.desktop new file mode 100644 index 00000000..7189fdc2 --- /dev/null +++ b/kopete/plugins/autoreplace/kopete_autoreplace.desktop @@ -0,0 +1,128 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=autoreplace +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_autoreplace +X-KDE-PluginInfo-Author=Roberto Pariset +X-KDE-PluginInfo-Email=victorheremita@fastwebnet.it +X-KDE-PluginInfo-Name=kopete_autoreplace +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Auto Replace +Name[ar]=الاستبدال التلقائي +Name[be]=ÐÑžÑ‚Ð°Ð¼Ð°Ñ‚Ñ‹Ñ‡Ð½Ð°Ñ Ð·Ð°Ð¼ÐµÐ½Ð° +Name[bg]=Ðвтоматична замÑна +Name[bn]=সà§à¦¬à§Ÿà¦‚কà§à¦°à§€à§Ÿ পà§à¦°à¦¤à¦¿à¦¸à§à¦¥à¦¾à¦ªà¦¨ +Name[bs]=Auto zamjena +Name[ca]=Auto-substitució +Name[cs]=Automatické nahrazení +Name[cy]=Hunan-Amnewid +Name[da]=Auto-erstat +Name[de]=Automatische Ersetzung +Name[el]=Αυτόματη αντικατάσταση +Name[eo]=AÅ­tomata anstataÅ­igo +Name[es]=Auto reemplazar +Name[et]=Automaatne asendamine +Name[eu]=Auto ordezkatu +Name[fa]=جایگزینی خودکار +Name[fi]=Automaattinen korvaus +Name[fr]=Remplacement automatique +Name[gl]=Auto-reemplazo +Name[he]=החלפה ×וטומטית +Name[hi]=सà¥à¤µà¤šà¤²à¤¿à¤¤ बदलें +Name[hr]=Automatska zamijena +Name[hu]=Automatikus szövegcsere +Name[is]=Skipta sjálfkrafa út +Name[it]=Sostituisci automaticamente +Name[ja]=è‡ªå‹•ç½®æ› +Name[ka]=áƒáƒ•áƒ¢áƒ ჩáƒáƒœáƒáƒªáƒ•áƒšáƒ”ბრ+Name[kk]=Ðвто алмаÑтыру +Name[km]=ជំនួស​ស្វáŸáž™áž”្រវážáŸ’ážáž· +Name[lt]=Automatinis keitimas +Name[mk]=ÐвтоматÑка замена +Name[nb]=Automatisk utskifting +Name[nds]=Automaatsch utwesseln +Name[ne]=सà¥à¤µà¤¤: पà¥à¤°à¤¤à¤¿à¤¸à¥à¤¥à¤¾à¤ªà¤¨ +Name[nl]=Automatisch vervangen +Name[nn]=Automatisk utbyting +Name[pa]=ਆਟੋ ਤਬਦੀਲ +Name[pl]=Automatyczne zastÄ™powanie +Name[pt]=Substituição Automática +Name[pt_BR]=Substituição Automática +Name[ro]=ÃŽnlocuire automată +Name[ru]=Ðвтозамена +Name[se]=Auto-buhtte +Name[sk]=Automatické náhrady +Name[sl]=Samo-zamenjava +Name[sr]=ÐутоматÑка замена +Name[sr@Latn]=Automatska zamena +Name[sv]=Ersätt automatiskt +Name[ta]=தனà¯à®©à®¿à®¯à®•à¯à®• மாறà¯à®±à®¿ +Name[tg]=Ҷойивазкунии Худкор +Name[tr]=Otomatik DeÄŸiÅŸtir +Name[uk]=Ðвтоматична заміна +Name[uz]=Avto-almashtirish +Name[uz@cyrillic]=Ðвто-алмаштириш +Name[wa]=Replaecî otomaticmint +Name[zh_CN]=è‡ªåŠ¨æ›¿æ¢ +Name[zh_HK]=自動å–代 +Name[zh_TW]=自動å–代 +Comment=Auto replaces some text you can choose +Comment[ar]=يقوم بتغيير تلÙائي للنصوص التي يمكن اختيارها +Comment[be]=ÐÑžÑ‚Ð°Ð¼Ð°Ñ‚Ñ‹Ñ‡Ð½Ð°Ñ Ð·Ð°Ð¼ÐµÐ½Ð° Ñ‚ÑкÑту +Comment[bg]=ПриÑтавка за автоматична замÑна на текÑÑ‚ в ÑъобщениÑта +Comment[bn]=সà§à¦¬à§Ÿà¦‚কà§à¦°à§€à§Ÿà¦­à¦¾à¦¬à§‡ পà§à¦°à¦¤à¦¿à¦¸à§à¦¥à¦¾à¦ªà¦¨ করে কিছৠটেকà§à¦¸à¦Ÿ যা আপনি বেছে নিতে পারেন +Comment[bs]=Automatski zamjenjuje neki tekst koji izaberete +Comment[ca]=Auto-substitueix algun text que podreu escollir +Comment[cs]=Automaticky nahrazuje zvolený text +Comment[cy]=Hunan-amnewid testun y gallwch ei ddewis +Comment[da]=Autoerstatter noget tekst du kan vælge +Comment[de]=Ersetzt wählbare Texte automatisch +Comment[el]=Αντικαθιστά αυτόματα κάποιο κείμενο που επιλέγετε +Comment[es]=Autoreemplaza texto que puede elegir +Comment[et]=Asendab automaatselt sinu valitud teksti +Comment[eu]=Hautatu dezakezun testua auto ordezten du +Comment[fa]=بعضی از متنها را Ú©Ù‡ می‌توانید انتخاب کنید، به طور خودکار جایگزین می‌کند +Comment[fi]=Korvaa valitsemasi tekstit automaattisesti +Comment[fr]=Remplace automatiquement du texte que vous pouvez choisir +Comment[gl]=Reemplaza automáticamente algún texto que tí podes escoller +Comment[he]=מחליף ×וטומטית טקסט לבחירתך +Comment[hi]=कà¥à¤› पाठ जिनà¥à¤¹à¥‡à¤‚ आप चà¥à¤¨ सकते हैं, सà¥à¤µà¤šà¤²à¤¿à¤¤ बदलें +Comment[hr]=Automatska zamjena teksta kojeg odaberete +Comment[hu]=A beállított szövegeket automatikusan lecseréli +Comment[is]=Skiptir sjálfkrafa út þeim texta sem þú velur +Comment[it]=Sostituisci automaticamente del testo a scelta +Comment[ja]=é¸æŠžã—ãŸãƒ†ã‚­ã‚¹ãƒˆã‚’è‡ªå‹•ç½®æ› +Comment[ka]=áƒáƒ áƒ©áƒ”ული ტექსტის áƒáƒ•áƒ¢áƒ ჩáƒáƒœáƒáƒªáƒ•áƒšáƒ”ბრ+Comment[kk]=Таңдаған мәтінді автоматты түрде алмаÑтыру +Comment[km]=ជំនួស​អážáŸ’ážáž”ទ​ដែល​អ្នក​អាច​ជ្រើស ដោយ​ស្វáŸáž™áž”្រវážáŸ’ážáž· +Comment[lt]=AutomatiÅ¡kai keiÄia parinktÄ… tekstÄ… +Comment[mk]=ÐвтоматÑка замена на некој текÑÑ‚ што го избирате +Comment[nb]=Skift ut en valgt tekst automatisk +Comment[nds]=Wesselt automaatsch wat instellbor Text ut +Comment[ne]=सà¥à¤µà¤¤: पà¥à¤°à¤¤à¤¿à¤¸à¥à¤¥à¤¾à¤ªà¤¨ हà¥à¤¨à¥‡ केही पाठ तपाईà¤à¤²à¥‡ रोजà¥à¤¨ सकà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤› +Comment[nl]=Vervangt automatisch tekst die u kunt instellen +Comment[nn]=Skift ut ein vald tekst automatisk +Comment[pl]=Automatycznie zastÄ™puje okreÅ›lony tekst +Comment[pt]=Substitui automaticamente algum texto seleccionado por si +Comment[pt_BR]=Substitui automaticamente o texto que você escolher +Comment[ru]=ÐвтоматичеÑки заменÑет текÑÑ‚, который вы вводите +Comment[se]=AutomáhtalaÄÄat buhtte teavstta maid válljet +Comment[sk]=Automaticky nahradzuje text, ktorý si vyberiete +Comment[sl]=Samodejno zamenja besedilo, ki ga lahko izberete +Comment[sr]=ÐутоматÑка замена неког текÑта који одаберете +Comment[sr@Latn]=Automatska zamena nekog teksta koji odaberete +Comment[sv]=Ersätt automatiskt text du kan markera +Comment[ta]=நீஙà¯à®•à®³à¯ தேரà¯à®µà¯ செயà¯à®¤ உரையை தானாக மாறà¯à®±à®µà¯à®®à¯ +Comment[tg]=Матни интихобкардаи шуморо ба таври худкор иваз мекунад +Comment[tr]=Seçilen bazı metinler otomatik deÄŸiÅŸtirilir +Comment[uk]=Ðвтоматично замінÑÑ” текÑÑ‚, Ñкий ви вкажете +Comment[zh_CN]=自动替æ¢æ‚¨å¯é€‰æ‹©çš„æŸäº›æ–‡å­— +Comment[zh_HK]=自動å–代å¯é¸æ“‡çš„文字 +Comment[zh_TW]=自動å–代您所é¸æ“‡çš„文字 + diff --git a/kopete/plugins/autoreplace/kopete_autoreplace_config.desktop b/kopete/plugins/autoreplace/kopete_autoreplace_config.desktop new file mode 100644 index 00000000..4efc1abd --- /dev/null +++ b/kopete/plugins/autoreplace/kopete_autoreplace_config.desktop @@ -0,0 +1,124 @@ +[Desktop Entry] +Icon=color +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_autoreplace +X-KDE-FactoryName=AutoReplaceConfigFactory +X-KDE-ParentApp=kopete_autoreplace +X-KDE-ParentComponents=kopete_autoreplace + +Name=Auto Replace +Name[ar]=الاستبدال التلقائي +Name[be]=ÐÑžÑ‚Ð°Ð¼Ð°Ñ‚Ñ‹Ñ‡Ð½Ð°Ñ Ð·Ð°Ð¼ÐµÐ½Ð° +Name[bg]=Ðвтоматична замÑна +Name[bn]=সà§à¦¬à§Ÿà¦‚কà§à¦°à§€à§Ÿ পà§à¦°à¦¤à¦¿à¦¸à§à¦¥à¦¾à¦ªà¦¨ +Name[bs]=Auto zamjena +Name[ca]=Auto-substitució +Name[cs]=Automatické nahrazení +Name[cy]=Hunan-Amnewid +Name[da]=Auto-erstat +Name[de]=Automatische Ersetzung +Name[el]=Αυτόματη αντικατάσταση +Name[eo]=AÅ­tomata anstataÅ­igo +Name[es]=Auto reemplazar +Name[et]=Automaatne asendamine +Name[eu]=Auto ordezkatu +Name[fa]=جایگزینی خودکار +Name[fi]=Automaattinen korvaus +Name[fr]=Remplacement automatique +Name[gl]=Auto-reemplazo +Name[he]=החלפה ×וטומטית +Name[hi]=सà¥à¤µà¤šà¤²à¤¿à¤¤ बदलें +Name[hr]=Automatska zamijena +Name[hu]=Automatikus szövegcsere +Name[is]=Skipta sjálfkrafa út +Name[it]=Sostituisci automaticamente +Name[ja]=è‡ªå‹•ç½®æ› +Name[ka]=áƒáƒ•áƒ¢áƒ ჩáƒáƒœáƒáƒªáƒ•áƒšáƒ”ბრ+Name[kk]=Ðвто алмаÑтыру +Name[km]=ជំនួស​ស្វáŸáž™áž”្រវážáŸ’ážáž· +Name[lt]=Automatinis keitimas +Name[mk]=ÐвтоматÑка замена +Name[nb]=Automatisk utskifting +Name[nds]=Automaatsch utwesseln +Name[ne]=सà¥à¤µà¤¤: पà¥à¤°à¤¤à¤¿à¤¸à¥à¤¥à¤¾à¤ªà¤¨ +Name[nl]=Automatisch vervangen +Name[nn]=Automatisk utbyting +Name[pa]=ਆਟੋ ਤਬਦੀਲ +Name[pl]=Automatyczne zastÄ™powanie +Name[pt]=Substituição Automática +Name[pt_BR]=Substituição Automática +Name[ro]=ÃŽnlocuire automată +Name[ru]=Ðвтозамена +Name[se]=Auto-buhtte +Name[sk]=Automatické náhrady +Name[sl]=Samo-zamenjava +Name[sr]=ÐутоматÑка замена +Name[sr@Latn]=Automatska zamena +Name[sv]=Ersätt automatiskt +Name[ta]=தனà¯à®©à®¿à®¯à®•à¯à®• மாறà¯à®±à®¿ +Name[tg]=Ҷойивазкунии Худкор +Name[tr]=Otomatik DeÄŸiÅŸtir +Name[uk]=Ðвтоматична заміна +Name[uz]=Avto-almashtirish +Name[uz@cyrillic]=Ðвто-алмаштириш +Name[wa]=Replaecî otomaticmint +Name[zh_CN]=è‡ªåŠ¨æ›¿æ¢ +Name[zh_HK]=自動å–代 +Name[zh_TW]=自動å–代 +Comment=Autoreplaces some text you can choose +Comment[ar]=تقوم بتغييرتلقائي لبعض النصوص التي يمكنك الاختيار منها +Comment[be]=ÐÑžÑ‚Ð°Ð¼Ð°Ñ‚Ñ‹Ñ‡Ð½Ð°Ñ Ð·Ð°Ð¼ÐµÐ½Ð° Ñ‚ÑкÑту +Comment[bg]=ПриÑтавка за автоматична замÑна на текÑÑ‚ в ÑъобщениÑта +Comment[bn]=সà§à¦¬à§Ÿà¦‚কà§à¦°à§€à§Ÿà¦­à¦¾à¦¬à§‡ পà§à¦°à¦¤à¦¿à¦¸à§à¦¥à¦¾à¦ªà¦¨ করে কিছৠটেকà§à¦¸à¦Ÿ যা আপনি বেছে নিতে পারেন +Comment[bs]=Automatski zamjenjuje neki tekst koji izaberete +Comment[ca]=Auto-substitueix algun text que podreu escollir +Comment[cs]=Automaticky nahrazuje zvolený text +Comment[cy]=Hunan-amnewid testun y gallwch ei ddewis +Comment[da]=Autoerstatter noget tekst du kan vælge +Comment[de]=Ersetzt wählbare Texte automatisch +Comment[el]=Αντικαθιστά αυτόματα κάποιο κείμενο που επιλέγετε +Comment[es]=Autoreemplaza texto que puede elegir +Comment[et]=Asendab automaatselt sinu valitud teksti +Comment[eu]=Hautatu dezakezun testua auto ordezten du +Comment[fa]=بعضی از متنها را Ú©Ù‡ می‌توانید انتخاب کنید، به طور خودکار جایگزین می‌کند +Comment[fi]=Korvaa valitsemasi tekstit automaattisesti +Comment[fr]=Remplace automatiquement du texte que vous pouvez choisir +Comment[gl]=Reemplaza algún texto que podes escoller +Comment[he]=מחליף ×וטומטית טקסט לבחירתך +Comment[hi]=कà¥à¤› पाठ जिनà¥à¤¹à¥‡à¤‚ आप चà¥à¤¨ सकते हैं, सà¥à¤µà¤šà¤²à¤¿à¤¤ बदलें +Comment[hr]=Automatska zamjena teksta kojeg odaberete +Comment[hu]=A kiválasztott szövegeket automatikusan lecseréli +Comment[is]=Skiptir sjálfkrafa út þeim texta sem þú velur +Comment[it]=Sostituisci automaticamente del testo a scelta +Comment[ja]=é¸æŠžã—ãŸãƒ†ã‚­ã‚¹ãƒˆã‚’è‡ªå‹•ç½®æ› +Comment[ka]=áƒáƒ áƒ©áƒ”ული ტექსტის áƒáƒ•áƒ¢áƒ ჩáƒáƒœáƒáƒªáƒ•áƒšáƒ”ბრ+Comment[kk]=Таңдаған мәтінді автоматты түрде алмаÑтыру +Comment[km]=ជំនួស​អážáŸ’ážáž”ទ​ដែល​អ្នក​អាច​ជ្រើស ដោយ​ស្វáŸáž™áž”្រវážáŸ’ážáž· +Comment[lt]=AutomatiÅ¡kai keiÄia parinktÄ… tekstÄ… +Comment[mk]=ÐвтоматÑка замена на некој текÑÑ‚ што го избирате +Comment[nb]=Skift ut en valgt tekst automatisk +Comment[nds]=Wesselt automaatsch wat instellbor Text ut +Comment[ne]=सà¥à¤µà¤¤: पà¥à¤°à¤¤à¤¿à¤¸à¥à¤¥à¤¾à¤ªà¤¨ हà¥à¤¨à¥‡ केही पाठ तपाईà¤à¤²à¥‡ रोजà¥à¤¨ सकà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤› +Comment[nl]=Vervangt automatisch tekst die u kunt instellen +Comment[nn]=Byter ut ein vald tekst automatisk +Comment[pl]=Automatycznie zastÄ™puje okreÅ›lony tekst +Comment[pt]=Substitui automaticamente algum texto seleccionado por si +Comment[pt_BR]=Substitui automaticamente o texto que você escolher +Comment[ru]=ÐвтоматичеÑки заменÑет выбранный вами текÑÑ‚ +Comment[se]=Buhtte válljejuvvon teavstta automáhtalaÅ¡ +Comment[sk]=Automaticky nahradzuje text, ktorý si vyberiete +Comment[sl]=Samodejno zamenja besedilo, ki ga lahko izberete +Comment[sr]=ÐутоматÑка замена неког текÑта који одаберете +Comment[sr@Latn]=Automatska zamena nekog teksta koji odaberete +Comment[sv]=Ersätt automatiskt text du kan markera +Comment[ta]=நீஙà¯à®•à®³à¯ தேரà¯à®µà¯ செயà¯à®¤ உரையை தானாக மாறà¯à®±à®µà¯à®®à¯ +Comment[tg]=Матни интихобкардаи шуморо ба таври худкор иваз мекунад +Comment[tr]=Seçilen bazı metinler otomatik deÄŸiÅŸtirilir +Comment[uk]=Ðвтоматично замінÑÑ” текÑÑ‚, Ñкий ви вкажете +Comment[zh_CN]=自动替æ¢æ‚¨å¯é€‰æ‹©çš„æŸäº›æ–‡å­— +Comment[zh_HK]=自動å–代å¯é¸æ“‡çš„文字 +Comment[zh_TW]=自動å–代您所é¸æ“‡çš„文字 + diff --git a/kopete/plugins/connectionstatus/Makefile.am b/kopete/plugins/connectionstatus/Makefile.am new file mode 100644 index 00000000..7fcb3ed8 --- /dev/null +++ b/kopete/plugins/connectionstatus/Makefile.am @@ -0,0 +1,12 @@ +METASOURCES = AUTO + +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kopete_connectionstatus.la + +kopete_connectionstatus_la_SOURCES = connectionstatusplugin.cpp +kopete_connectionstatus_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) +kopete_connectionstatus_la_LIBADD = ../../libkopete/libkopete.la + +service_DATA = kopete_connectionstatus.desktop +servicedir = $(kde_servicesdir) diff --git a/kopete/plugins/connectionstatus/connectionstatusplugin.cpp b/kopete/plugins/connectionstatus/connectionstatusplugin.cpp new file mode 100644 index 00000000..8840c893 --- /dev/null +++ b/kopete/plugins/connectionstatus/connectionstatusplugin.cpp @@ -0,0 +1,137 @@ +/* + connectionstatusplugin.cpp + + Copyright (c) 2002-2003 by Chris Howells + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include "connectionstatusplugin.h" + +#include + +#include +#include +#include + +#include "kopeteaccountmanager.h" + +typedef KGenericFactory ConnectionStatusPluginFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_connectionstatus, ConnectionStatusPluginFactory( "kopete_connectionstatus" ) ) + +ConnectionStatusPlugin::ConnectionStatusPlugin( QObject *parent, const char *name, const QStringList& /* args */ ) +: Kopete::Plugin( ConnectionStatusPluginFactory::instance(), parent, name ) +{ + kdDebug( 14301 ) << k_funcinfo << endl; + + m_process = 0L; + + m_timer = new QTimer(); + connect( m_timer, SIGNAL( timeout() ), this, SLOT( slotCheckStatus() ) ); + m_timer->start( 60000 ); + + m_pluginConnected = false; +} + +ConnectionStatusPlugin::~ConnectionStatusPlugin() +{ + kdDebug( 14301 ) << k_funcinfo << endl; + delete m_timer; + delete m_process; +} + +void ConnectionStatusPlugin::slotCheckStatus() +{ + kdDebug( 14301 ) << k_funcinfo << endl; + + if ( m_process ) + { + kdWarning( 14301 ) << k_funcinfo << "Previous netstat process is still running!" << endl + << "Not starting new netstat. Perhaps your system is under heavy load?" << endl; + + return; + } + + m_buffer = QString::null; + + // Use KProcess to run netstat -rn. We'll then parse the output of + // netstat -rn in slotProcessStdout() to see if it mentions the + // default gateway. If so, we're connected, if not, we're offline + m_process = new KProcess; + *m_process << "netstat" << "-r"; + + connect( m_process, SIGNAL( receivedStdout( KProcess *, char *, int ) ), this, SLOT( slotProcessStdout( KProcess *, char *, int ) ) ); + connect( m_process, SIGNAL( processExited( KProcess * ) ), this, SLOT( slotProcessExited( KProcess * ) ) ); + + if ( !m_process->start( KProcess::NotifyOnExit, KProcess::Stdout ) ) + { + kdWarning( 14301 ) << k_funcinfo << "Unable to start netstat process!" << endl; + + delete m_process; + m_process = 0L; + } +} + +void ConnectionStatusPlugin::slotProcessExited( KProcess *process ) +{ + kdDebug( 14301 ) << m_buffer << endl; + + if ( process == m_process ) + { + setConnectedStatus( m_buffer.contains( "default" ) ); + m_buffer = QString::null; + delete m_process; + m_process = 0L; + } +} + +void ConnectionStatusPlugin::slotProcessStdout( KProcess *, char *buffer, int buflen ) +{ + // Look for a default gateway + //kdDebug( 14301 ) << k_funcinfo << endl; + m_buffer += QString::fromLatin1( buffer, buflen ); + //kdDebug( 14301 ) << qsBuffer << endl; +} + +void ConnectionStatusPlugin::setConnectedStatus( bool connected ) +{ + //kdDebug( 14301 ) << k_funcinfo << endl; + + // We have to handle a few cases here. First is the machine is connected, and the plugin thinks + // we're connected. Then we don't do anything. Next, we can have machine connected, but plugin thinks + // we're disconnected. Also, machine disconnected, plugin disconnected -- we + // don't do anything. Finally, we can have the machine disconnected, and the plugin thinks we're + // connected. This mechanism is required so that we don't keep calling the connect/disconnect functions + // constantly. + + if ( connected && !m_pluginConnected ) + { + // The machine is connected and plugin thinks we're disconnected + kdDebug( 14301 ) << k_funcinfo << "Setting m_pluginConnected to true" << endl; + m_pluginConnected = true; + Kopete::AccountManager::self()->connectAll(); + kdDebug( 14301 ) << k_funcinfo << "We're connected" << endl; + } + else if ( !connected && m_pluginConnected ) + { + // The machine isn't connected and plugin thinks we're connected + kdDebug( 14301 ) << k_funcinfo << "Setting m_pluginConnected to false" << endl; + m_pluginConnected = false; + Kopete::AccountManager::self()->disconnectAll(); + kdDebug( 14301 ) << k_funcinfo << "We're offline" << endl; + } +} + +#include "connectionstatusplugin.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/connectionstatus/connectionstatusplugin.h b/kopete/plugins/connectionstatus/connectionstatusplugin.h new file mode 100644 index 00000000..5f0a53bf --- /dev/null +++ b/kopete/plugins/connectionstatus/connectionstatusplugin.h @@ -0,0 +1,58 @@ +/* + connectionstatusplugin.h + + Copyright (c) 2002-2003 by Chris Howells + Copyright (c) 2003 by Martijn Klingens + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef CONNECTIONSTATUSPLUGIN_H +#define CONNECTIONSTATUSPLUGIN_H + +#include "kopeteplugin.h" + +class QTimer; +class KProcess; + +/** + * @author Chris Howells + */ +class ConnectionStatusPlugin : public Kopete::Plugin +{ + Q_OBJECT + +public: + ConnectionStatusPlugin( QObject *parent, const char *name, const QStringList &args ); + ~ConnectionStatusPlugin(); + +private slots: + void slotCheckStatus(); + void slotProcessStdout( KProcess *process, char *buffer, int len ); + + /** + * Notify when the netstat process has exited + */ + void slotProcessExited( KProcess *process ); + +private: + void setConnectedStatus( bool newStatus ); + + bool m_pluginConnected; + KProcess *m_process; + QTimer *m_timer; + QString m_buffer; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/connectionstatus/kopete_connectionstatus.desktop b/kopete/plugins/connectionstatus/kopete_connectionstatus.desktop new file mode 100644 index 00000000..bbad7456 --- /dev/null +++ b/kopete/plugins/connectionstatus/kopete_connectionstatus.desktop @@ -0,0 +1,125 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_connectionstatus +X-KDE-PluginInfo-Author=Chris Howells +X-KDE-PluginInfo-Email=howells@kde.org +X-KDE-PluginInfo-Name=kopete_connectionstatus +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Connection Status +Name[ar]=وضع الاتصال +Name[be]=Стан злучÑÐ½Ð½Ñ +Name[bg]=СъÑтоÑние на връзката +Name[bn]=সংযোগ অবসà§à¦¥à¦¾ +Name[bs]=Status veze +Name[ca]=Estatus de la connexió +Name[cs]=Stav spojení +Name[cy]=Cyflwr Cysylltiad +Name[da]=Forbindelsesstatus +Name[de]=Verbindungsstatus +Name[el]=Κατάσταση σÏνδεσης +Name[eo]=Konektostato +Name[es]=Estado de conexión +Name[et]=Ãœhenduse staatus +Name[eu]=Konexioaren egoera +Name[fa]=وضعیت اتصال +Name[fi]=Yhteyden tila +Name[fr]=État de la connexion +Name[ga]=Stádas Ceangail +Name[gl]=Estado da conexión1 +Name[he]=מצב החיבור +Name[hi]=कनेकà¥à¤¶à¤¨ सà¥à¤¥à¤¿à¤¤à¤¿ +Name[hr]=Status veze +Name[hu]=A kapcsolat állapota +Name[is]=Staða tengingar +Name[it]=Stato della connessione +Name[ja]=æŽ¥ç¶šçŠ¶æ³ +Name[ka]=კáƒáƒ•áƒ¨áƒ˜áƒ áƒ˜áƒ¡ სტáƒáƒ¢áƒ£áƒ¡áƒ˜ +Name[kk]=ҚоÑылымның күй-жайы +Name[km]=ស្ážáž¶áž“ភាព​ážáž€áž¶ážšâ€‹â€‹áž—្ជាប់ +Name[lt]=RyÅ¡io bÅ«sena +Name[mk]=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð½Ð° поврзувањето +Name[nb]=Tilstand for forbindelsen +Name[nds]=Verbinnen-Status +Name[ne]=जडान वसà¥à¤¤à¥à¤¸à¥à¤¥à¤¿à¤¤à¤¿ +Name[nl]=Verbindingsstatus +Name[nn]=Tilstand for sambandet +Name[pa]=ਕà©à¨¨à©ˆà¨•à¨¸à¨¼à¨¨ ਹਾਲਤ +Name[pl]=Status poÅ‚Ä…czenia +Name[pt]=Estado da Ligação +Name[pt_BR]=Status da Conexão +Name[ro]=Stare conexiune +Name[ru]=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ +Name[se]=Oktavuohtástáhtus +Name[sk]=Stav spojenia +Name[sl]=Stanje povezave +Name[sr]=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð²ÐµÐ·Ðµ +Name[sr@Latn]=Status veze +Name[sv]=Anslutningsstatus +Name[ta]=இணைபà¯à®ªà¯ நிலை +Name[tg]=Ҳолати ПайваÑтшавӣ +Name[tr]=BaÄŸlantı Durumu +Name[uk]=Стан з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ +Name[uz]=Aloqaning holati +Name[uz@cyrillic]=Ðлоқанинг ҳолати +Name[wa]=Estat do raloyaedje +Name[zh_CN]=è¿žæŽ¥çŠ¶æ€ +Name[zh_HK]=連線狀態 +Name[zh_TW]=連線狀態 +Comment=Connects/disconnects Kopete automatically depending on availability of Internet connection +Comment[ar]=يقوم بوصل Ùˆ Ùصل Kopete تلقائيا استنادا إلى وضع الاتصال بالشبكة +Comment[be]=Злучае/адлучае Kopete ад ÑервіÑаў у залежнаÑці ад наÑўнаÑці інтÑрфÑйÑаў з ІнтÑрнÑтам +Comment[bg]=Ðвтоматично уÑтановÑване и прекъÑване на връзката в завиÑимоÑÑ‚ от ÑÑŠÑтоÑнието на връзката Ñ Ð˜Ð½Ñ‚ÐµÑ€Ð½ÐµÑ‚ +Comment[bn]=ইনà§à¦Ÿà¦¾à¦°à¦¨à§‡à¦Ÿ সংযোগের সà§à¦¬à¦¿à¦§à¦¾ অনà§à¦¯à¦¾à§Ÿà§€ কপেট সà§à¦¬à§Ÿà¦‚কà§à¦°à§€à§Ÿà¦­à¦¾à¦¬à§‡ সংযোগ/সংযোগবিচà§à¦›à¦¿à¦¨à§à¦¨à¦•à¦°à§‡ +Comment[bs]=Automatski spaja Kopete i prekida vezu ovisno o dostupnosti Internet konekcije +Comment[ca]=Connecta/desconnecta automàticament depenent de la disponibilitat de la connexió a Internet +Comment[cs]=Automaticky pÅ™ipojí nebo odpojí vzhledem k dostupnosti pÅ™ipojení na Internet +Comment[cy]=Cysylltu/datgyslltu Kopete yn ymysgogol, yn dibynnu ar argaeledd cysylltiad Rhyngrwyd +Comment[da]=Forbinder/afbryder Kopete automatisk afhængig af om der er en internet-forbindelse +Comment[de]=Verbindet/trennt Kopete automatisch abhängig von der Verfügbarkeit einer Internetverbindung +Comment[el]=Συνδέει/αποσυνδέει το Kopete αυτόματα ανάλογα με την κατάσταση της σÏνδεσης με το διαδίκτυο +Comment[es]=Conecta/desconecta Kopete automáticamente según la disponibilidad de la conexión a internet +Comment[et]=Kopete automaatne ühendamine/ühenduse katkestamine vastavalt internetiühenduse olemasolule +Comment[eu]=Kopete automatikoki konektatu/deskonektatzen du internet-eko konexioaren eskuragarritasunaren arabera +Comment[fa]=بسته به قابلیت دسترسی اتصال اینترنت، Kopete به طور خودکار وصل/قطع ارتباط می‌کند +Comment[fi]=Yhdistää/katkaisee yhteyden automaattisesti riippuen onko Internet-yhteys päällä +Comment[fr]=Connecte / déconnecte automatiquement Kopete en fonction de la disponibilité de la connexion Internet +Comment[gl]=Conecta/desconecta a Kopete automáticamente dependendo da disponibilidade de conexión a Internet +Comment[he]=חיבור\ניתוק Kopete ב×ופן ×וטומטי בתלות בזמינות של החיבור ל×ינטרנט +Comment[hi]=इंटरनेट कनेकà¥à¤¶à¤¨ की उपलबà¥à¤§à¤¤à¤¾ की निरà¥à¤­à¤°à¤¤à¤¾ के आधार पर के-ऑपà¥à¤Ÿà¥€ को सà¥à¤µà¤šà¤²à¤¿à¤¤ कनेकà¥à¤Ÿ/डिसà¥à¤•à¤¨à¥‡à¤•à¥à¤Ÿ करता है +Comment[hu]=A Kopete automatikus csatlakoztatása és bontása az internetkapcsolat elérhetÅ‘ségétÅ‘l függÅ‘en +Comment[is]=(Af)tengir Kopete sjálfkrafa miðað við stöðu Internettengingar +Comment[it]=Connetti/disconnetti automaticamente Kopete a seconda della disponibilità della connessione ad internet +Comment[ja]=インターãƒãƒƒãƒˆæŽ¥ç¶šã®æœ‰ç„¡ã«ã‚ˆã‚Šè‡ªå‹•çš„ã« Kopete を接続/切断ã—ã¾ã™ +Comment[ka]=ინტერნეტ კáƒáƒ•áƒ¨áƒ˜áƒ áƒ˜áƒ¡ áƒáƒ áƒ¡áƒ”ბáƒáƒ‘ისáƒáƒ¡ áƒáƒ•áƒ¢áƒáƒ›áƒáƒ¢áƒ£áƒ áƒáƒ“ áƒáƒ™áƒáƒ•áƒ¨áƒ˜áƒ áƒ”ბს Kopete-ს +Comment[kk]=Интернетке қоÑылымның бар=жоғына қарай Kopete-Ñ‚Ñ– автоматты түрде қоÑады және ажыратады +Comment[km]=ភ្ជាប់ ​ ​ផ្ដាច​ Kopeteដោយ​ស្វáŸáž™áž”្រវážáŸ’ážáž·â€‹ážŠáŸ„យ​ផ្អែក​លើ​ភាព​មាន​នៃការណážâ€‹áž—្ជាប់​អ៊ីនធឺណិហ+Comment[lt]=AutomatiÅ¡kai prijungia/atjungia Kopete, priklausomai nuo interneto ryÅ¡io buvimo +Comment[mk]=ÐвтоматÑки го поврзува/иÑклучува Kopete во завиÑноÑÑ‚ на доÑтапноÑта на поврзувањето на Интернет +Comment[nb]=Kobler Kopete automatisk til/fra avhengig av om internettforbindelse er tilgjengelig +Comment[nds]=Koppelt Kopete automaatsch to- oder af, afhangen vun de Verföögborkeit vun de Internetverbinnen +Comment[ne]=इनà¥à¤Ÿà¤°à¤¨à¥‡à¤Ÿ जडानको उपलबà¥à¤§à¤¤à¤¾à¤®à¤¾ आधारित कोपेट सà¥à¤µà¤šà¤¾à¤²à¤¿à¤¤ रूपमा जडान गरà¥à¤¦à¤›/विचà¥à¤›à¥‡à¤¦à¤¨ गरà¥à¤¦à¤› +Comment[nl]=Bouwt automatisch verbindingen op voor Kopete of verbreekt ze, afhankelijk van de beschikbaarheid van de internetverbinding +Comment[nn]=Koplar Kopete automatisk til eller frÃ¥ avhengig av om Internett-sambandet er tilgjengeleg. +Comment[pl]=Automatycznie podÅ‚Ä…cza/odÅ‚Ä…cza Kopete zależnie od stanu poÅ‚Ä…czenia z Internetem +Comment[pt]=Liga/desliga o Kopete automaticamente, dependendo da disponibilidade de uma ligação à Internet +Comment[pt_BR]=Conecta/desconecta o Kopete automaticamente dependendo da disponibilidade da conexão com a Internet +Comment[ru]=ÐвтоматичеÑки входит в Ñеть или выходит из неё в завиÑимоÑти от Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ñ ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ñ Ð˜Ð½Ñ‚ÐµÑ€Ð½ÐµÑ‚ +Comment[sk]=Pripojí/odpojí Kopete v závislosti Äi existuje pripojenie na Internet +Comment[sl]=Samodejno vzpostavi/prekine povezavo Kopete glede na dostopnost internetne povezave +Comment[sr]=ÐутоматÑки уÑпоÑтавља или прекида везу у Kopete-у у завиÑноÑти од доÑтупноÑти интернет везе +Comment[sr@Latn]=Automatski uspostavlja ili prekida vezu u Kopete-u u zavisnosti od dostupnosti internet veze +Comment[sv]=Ansluter eller kopplar ner Kopete automatiskt beroende pÃ¥ Internetförbindelsens tillgänglighet +Comment[ta]=இணைய இணைபà¯à®ªà¯ˆ பொரà¯à®¤à¯à®¤à¯ Kopete யà¯à®Ÿà®©à¯à®¤à®¾à®©à®¾à®• இணையà¯à®®à¯/நீகà¯à®•à¯à®®à¯ +Comment[tg]=ВобаÑта ба имкониÑтҳои алоқаи Интернет Kopete-ро ба таври худкор пайваÑÑ‚/канда мекунад +Comment[tr]=Ä°nternet baÄŸlantısının kullanılabilir olabilirliÄŸine göre Kopete'yi otomatik baÄŸlar/baÄŸlamaz +Comment[uk]=Ðвтоматично входить в мережу чи виходить з неї в залежноÑÑ‚Ñ– від наÑвноÑÑ‚Ñ– з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· Інтернет +Comment[zh_CN]=æ ¹æ® Internet 连接是å¦å¯ç”¨è‡ªåŠ¨è¿žæŽ¥/æ–­å¼€ Kopete +Comment[zh_HK]=自動根據互è¯ç¶²é€£æŽ¥æƒ…æ³é€£æŽ¥æˆ–中斷 Kopete 連線 +Comment[zh_TW]=自動ä¾æ“šç¶²è·¯ç‹€æ³ä¾†é€£ç·š/中斷連線 Kopete diff --git a/kopete/plugins/contactnotes/Makefile.am b/kopete/plugins/contactnotes/Makefile.am new file mode 100644 index 00000000..94ff86ce --- /dev/null +++ b/kopete/plugins/contactnotes/Makefile.am @@ -0,0 +1,15 @@ +METASOURCES = AUTO + +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kopete_contactnotes.la + +kopete_contactnotes_la_SOURCES = contactnotesplugin.cpp contactnotesedit.cpp +kopete_contactnotes_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kopete_contactnotes_la_LIBADD = ../../libkopete/libkopete.la -lkio + +service_DATA = kopete_contactnotes.desktop +servicedir = $(kde_servicesdir) + +mydatadir = $(kde_datadir)/kopete_contactnotes +mydata_DATA = contactnotesui.rc diff --git a/kopete/plugins/contactnotes/contactnotesedit.cpp b/kopete/plugins/contactnotes/contactnotesedit.cpp new file mode 100644 index 00000000..f7333c7b --- /dev/null +++ b/kopete/plugins/contactnotes/contactnotesedit.cpp @@ -0,0 +1,55 @@ +/*************************************************************************** + contactnotesedit.cpp - description + ------------------- + begin : lun sep 16 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#include +#include +#include + +#include + +#include "kopetemetacontact.h" + +#include "contactnotesplugin.h" +#include "contactnotesedit.h" + +ContactNotesEdit::ContactNotesEdit(Kopete::MetaContact *m,ContactNotesPlugin *p,const char *name) : KDialogBase(0L, name , false, i18n("Contact Notes") , KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok) +{ + m_plugin=p; + m_metaContact=m; + + QVBox *w=new QVBox(this); + w->setSpacing(KDialog::spacingHint()); + m_label = new QLabel(i18n("Notes about %1:").arg(m->displayName()) , w , "m_label"); + m_linesEdit= new QTextEdit ( w , "m_linesEdit"); + + m_linesEdit->setText(p->notes(m)); + + enableButtonSeparator(true); + setMainWidget(w); +} + +ContactNotesEdit::~ContactNotesEdit() +{ +} + +void ContactNotesEdit::slotOk() +{ + emit notesChanged(m_linesEdit->text(),m_metaContact) ; + KDialogBase::slotOk(); +} + +#include "contactnotesedit.moc" diff --git a/kopete/plugins/contactnotes/contactnotesedit.h b/kopete/plugins/contactnotes/contactnotesedit.h new file mode 100644 index 00000000..20fe7d10 --- /dev/null +++ b/kopete/plugins/contactnotes/contactnotesedit.h @@ -0,0 +1,53 @@ +/*************************************************************************** + contactnotesedit.h - description + ------------------- + begin : lun sep 16 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef CONTACTNOTESEDIT_H +#define CONTACTNOTESEDIT_H + +#include +#include +#include + +class QLabel; +class QTextEdit; +namespace Kopete { class MetaContact; } +class ContactNotesPlugin; + +/** + *@author Olivier Goffart + */ + +class ContactNotesEdit : public KDialogBase { + Q_OBJECT +public: + ContactNotesEdit(Kopete::MetaContact *m,ContactNotesPlugin *p=0 ,const char *name=0); + ~ContactNotesEdit(); + +private: + ContactNotesPlugin *m_plugin; + Kopete::MetaContact *m_metaContact; + + QLabel *m_label; + QTextEdit *m_linesEdit; + +protected slots: // Protected slots + virtual void slotOk(); +signals: // Signals + void notesChanged(const QString, Kopete::MetaContact*); +}; + +#endif diff --git a/kopete/plugins/contactnotes/contactnotesplugin.cpp b/kopete/plugins/contactnotes/contactnotesplugin.cpp new file mode 100644 index 00000000..5982200f --- /dev/null +++ b/kopete/plugins/contactnotes/contactnotesplugin.cpp @@ -0,0 +1,83 @@ +/*************************************************************************** + contactnotes.cpp - description + ------------------- + begin : lun sep 16 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#include +#include +#include + +#include "kopetemetacontact.h" +#include "kopetecontactlist.h" + +#include "contactnotesedit.h" + +#include "contactnotesplugin.h" + +typedef KGenericFactory ContactNotesPluginFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_contactnotes, ContactNotesPluginFactory( "kopete_contactnotes" ) ) + +ContactNotesPlugin::ContactNotesPlugin( QObject *parent, const char *name, const QStringList & /* args */ ) +: Kopete::Plugin( ContactNotesPluginFactory::instance(), parent, name ) +{ + if ( pluginStatic_ ) + kdDebug(14302)<<"ContactNotesPlugin::ContactNotesPlugin : plugin already initialized"<setEnabled(Kopete::ContactList::self()->selectedMetaContacts().count()==1 ); + + setXMLFile("contactnotesui.rc"); +} + +ContactNotesPlugin::~ContactNotesPlugin() +{ + pluginStatic_ = 0L; +} + +ContactNotesPlugin* ContactNotesPlugin::plugin() +{ + return pluginStatic_ ; +} + +ContactNotesPlugin* ContactNotesPlugin::pluginStatic_ = 0L; + + +void ContactNotesPlugin::slotEditInfo() +{ + Kopete::MetaContact *m=Kopete::ContactList::self()->selectedMetaContacts().first(); + if(!m) + return; + ContactNotesEdit *e=new ContactNotesEdit(m,this); + connect( e, SIGNAL( notesChanged( const QString, Kopete::MetaContact*) ),this, + SLOT( setNotes( const QString, Kopete::MetaContact * ) ) ); + e->show(); +} + + +QString ContactNotesPlugin::notes(Kopete::MetaContact *m) +{ + return m->pluginData( this, "notes" ); +} + +void ContactNotesPlugin::setNotes( const QString n, Kopete::MetaContact *m ) +{ + m->setPluginData( this, "notes", n ); +} + +#include "contactnotesplugin.moc" + diff --git a/kopete/plugins/contactnotes/contactnotesplugin.h b/kopete/plugins/contactnotes/contactnotesplugin.h new file mode 100644 index 00000000..c4fb8224 --- /dev/null +++ b/kopete/plugins/contactnotes/contactnotesplugin.h @@ -0,0 +1,66 @@ +/*************************************************************************** + contactnotesplugin.h - description + ------------------- + begin : lun sep 16 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef BABELFISHPLUGIN_H +#define BABELFISHPLUGIN_H + +#include +#include +#include + +#include "kopeteplugin.h" + +class QString; +class KAction; +class KActionCollection; + +namespace Kopete { class MetaContact; } + +/** + * @author Olivier Goffart + * + * Kopete Contact Notes Plugin + * + */ + +class ContactNotesPlugin : public Kopete::Plugin +{ + Q_OBJECT + +public: + static ContactNotesPlugin *plugin(); + + ContactNotesPlugin( QObject *parent, const char *name, const QStringList &args ); + ~ContactNotesPlugin(); + + QString notes(Kopete::MetaContact *m); + + +public slots: + void setNotes(const QString n, Kopete::MetaContact *m); + +private: + static ContactNotesPlugin* pluginStatic_; + +private slots: // Private slots + /** No descriptions */ + void slotEditInfo(); +}; + +#endif + + diff --git a/kopete/plugins/contactnotes/contactnotesui.rc b/kopete/plugins/contactnotes/contactnotesui.rc new file mode 100644 index 00000000..9e7a5748 --- /dev/null +++ b/kopete/plugins/contactnotes/contactnotesui.rc @@ -0,0 +1,12 @@ + + + + + &Edit + + + + + + + diff --git a/kopete/plugins/contactnotes/kopete_contactnotes.desktop b/kopete/plugins/contactnotes/kopete_contactnotes.desktop new file mode 100644 index 00000000..455f299d --- /dev/null +++ b/kopete/plugins/contactnotes/kopete_contactnotes.desktop @@ -0,0 +1,124 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=identity +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_contactnotes +X-KDE-PluginInfo-Author=Olivier Goffart +X-KDE-PluginInfo-Email=ogoffart@tiscalinet.be +X-KDE-PluginInfo-Name=kopete_contactnotes +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Contact Notes +Name[ar]=ملاحظات الاتصال +Name[be]=Ðататкі +Name[bg]=Бележки за контактите +Name[bn]=যোগাযোগ নোট +Name[br]=Notennoù an darempred +Name[bs]=Kontakt informacije +Name[ca]=Notes del contacte +Name[cs]=Poznámky ke kontaktu +Name[cy]=Nodiadau Cysylltiad +Name[da]=Kontaktnoter +Name[de]=Kontakt-Notizen +Name[el]=Σημειώσεις επαφής +Name[eo]=Kontaktinformoj +Name[es]=Notas de contacto +Name[et]=Kontakti märkmed +Name[eu]=Kontaktuaren oharrak +Name[fa]=یادداشتهای تماس +Name[fi]=Yhteystiedot +Name[fr]=Notes sur les contacts +Name[gl]=Notas de contacto +Name[he]=הערות על ×יש-קשר +Name[hi]=समà¥à¤ªà¤°à¥à¤• नोटà¥à¤¸ +Name[hr]=ZabiljeÅ¡ke kontakata +Name[hu]=Megjegyzés a partnerekhez +Name[is]=Skráð um notanda +Name[it]=Note contatto +Name[ja]=コンタクトメモ +Name[ka]=მეგáƒáƒ‘რის შენიშვნები +Name[kk]=Контакт еÑкертпелері +Name[km]=​ចំណាំ​ក្នុង​ទំនាក់​ទំនង +Name[lt]=Kontaktų pastabos +Name[mk]=Забелешки за контакт +Name[nb]=Notater om kontakter +Name[nds]=Kontakt-Notizen +Name[ne]=समà¥à¤ªà¤°à¥à¤• दà¥à¤°à¤·à¥à¤Ÿà¤¬à¥à¤¯ +Name[nl]=Notities bij contacten +Name[nn]=Notat om kontaktar +Name[pa]=ਸੰਪਰਕ ਨੋਟਿਸ +Name[pl]=Notatki kontaktu +Name[pt]=Notas do Contacto +Name[pt_BR]=Notas do Contato +Name[ru]=ÐŸÑ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ðº контакту +Name[sk]=Poznámky kontaktu +Name[sl]=Zapiski za stike +Name[sr]=Белешке о контакту +Name[sr@Latn]=BeleÅ¡ke o kontaktu +Name[sv]=Kontaktanteckningar +Name[ta]=தொடரà¯à®ªà¯ கà¯à®±à®¿à®ªà¯à®ªà¯ +Name[tg]=Эзоҳи ПайваÑтшавӣ +Name[tr]=BaÄŸlantı Notları +Name[uk]=Примітки до контакту +Name[zh_CN]=è”系人备忘 +Name[zh_HK]=è¯çµ¡äººå‚™è¨» +Name[zh_TW]=è¯çµ¡äººå‚™å¿˜éŒ„ +Comment=Add personal notes on your contacts +Comment[ar]=أض٠ملاحظاتك الشخصية إلى بيانات اﻹتصال +Comment[be]=Дадаць Ð¿Ñ€Ñ‹Ð²Ð°Ñ‚Ð½Ñ‹Ñ Ð½Ð°Ñ‚Ð°Ñ‚ÐºÑ– Ð´Ð»Ñ Ð»ÑŽÐ´Ð·ÐµÐ¹ у ÑпіÑе +Comment[bg]=ДобавÑне и редактиране на бележки и коментари към контактите +Comment[bn]=আপনার যোগাযোগে বà§à¦¯à¦•à§à¦¤à¦¿à¦—ত নোট যোগ করà§à¦¨ +Comment[bs]=Dodajte liÄne biljeÅ¡ke vaÅ¡im kontaktima +Comment[ca]=Afegeix notes personals als vostres contactes +Comment[cs]=Osobní poznámky ke kontaktům +Comment[cy]=Ychwanegu nodiadau personol ynglyn â'ch cysylltiadau +Comment[da]=Tilføj personlige noter om dine kontakter +Comment[de]=Ermöglicht das Hinzufügen persönlicher Notizen zu Kontakten +Comment[el]=ΠÏοσθέστε Ï€Ïοσωπικές σημειώσεις στις επαφές σας +Comment[eo]=Aldoni personajn notojn al viaj kontaktoj +Comment[es]=Añade notas personales a tus contactos +Comment[et]=Isiklike märkmete lisamine kontaktide kohta +Comment[eu]=Gehitu ohar pertsonalak zure kontaktuei +Comment[fa]=اÙزودن پیامهای شخصی به تماسهای شما +Comment[fi]=Lisää omia muistiinpanoja yhteystietoihin +Comment[fr]=Ajoutez des notes personnelles à vos contacts +Comment[gl]=Engadir notas persoáis ós teus contactos +Comment[he]=הוספת הערות ×ישיות לגבי ×נשי-הקשר שלך +Comment[hi]=आपके समà¥à¤ªà¤°à¥à¤•à¥‹à¤‚ पर निजी टीप जोड़े +Comment[hr]=Dodaje osobne zabiljeÅ¡ke o vaÅ¡im kontaktima +Comment[hu]=Személyes megjegyzések fűzése a partnerek adataihoz +Comment[is]=Bæta við upplýsingum um notanda +Comment[it]=Aggiungi note personali ai tuoi contatti +Comment[ja]=コンタクトã«å€‹äººçš„ãªãƒ¡ãƒ¢ã‚’加ãˆã¾ã™ +Comment[ka]=მეგáƒáƒ‘რებისთვის პერსáƒáƒœáƒáƒšáƒ£áƒ áƒ˜ შენიშვნების დáƒáƒ›áƒáƒ¢áƒ”ბრ+Comment[kk]=Контакттарға жеке еÑкертпелерді қоÑу +Comment[km]=បន្ážáŸ‚ម​ចំណាំ​ផ្ទាល់​ážáŸ’លួន​លើ​ទំនាក់​ទំនង​របស់​អ្នក +Comment[lt]=Papildykite kontaktus asmeniniais užraÅ¡ais +Comment[mk]=Додадете лични забелешки на вашите контакти +Comment[nb]=Legg til egne notater i kontaktlista +Comment[nds]=Dien Kontaken persöönliche Notizen tofögen +Comment[ne]=तपाईà¤à¤•à¥‹ समà¥à¤ªà¤°à¥à¤•à¤®à¤¾ वà¥à¤¯à¤•à¥à¤¤à¤¿à¤—त दà¥à¤°à¤·à¥à¤Ÿà¤¬à¥à¤¯ थपà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Voeg persoonlijke notities toe aan contacten +Comment[nn]=Legg til eigne notat i kontaktlista +Comment[pl]=Dodaje osobiste notatki do kontaktu +Comment[pt]=Adiciona notas pessoais aos seus contactos +Comment[pt_BR]=Adiciona notas pessoais em seus contatos +Comment[ru]=Добавить личные Ð¿Ñ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ðº вашим контактам +Comment[sk]=Pridá osobné poznámky ku kontaktom +Comment[sl]=Dodajte osebne zapiske k vaÅ¡im stikom +Comment[sr]=Додаје личне забелешке о вашим контактима +Comment[sr@Latn]=Dodaje liÄne zabeleÅ¡ke o vaÅ¡im kontaktima +Comment[sv]=Lägg till personliga anteckningar om kontakter +Comment[ta]=உஙà¯à®•à®³à¯ உரையில௠சிறபà¯à®ªà¯ விளைவà¯à®•à®³à¯ˆ சேரà¯à®•à¯à®•à¯à®®à¯ +Comment[tg]=Ба пайваÑтагии худ Ñзоҳҳои шахÑиро илова кунед +Comment[tr]=BaÄŸlantıların üzerine kiÅŸisel notlar ekle +Comment[uk]=Додати оÑобиÑÑ‚Ñ– примітки до влаÑних контактів +Comment[wa]=Radjouter des notes da vosse Ã¥ calpin d' adresses +Comment[zh_CN]=在您的è”系人上添加ç§äººå¤‡å¿˜ +Comment[zh_HK]=為您的è¯çµ¡äººæ–°å¢žå€‹äººå‚™è¨» +Comment[zh_TW]=新增è¯çµ¡äººçš„備忘錄 diff --git a/kopete/plugins/cryptography/Makefile.am b/kopete/plugins/cryptography/Makefile.am new file mode 100644 index 00000000..bbcaca2c --- /dev/null +++ b/kopete/plugins/cryptography/Makefile.am @@ -0,0 +1,25 @@ +METASOURCES = AUTO + +SUBDIRS=icons + +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kopete_cryptography.la kcm_kopete_cryptography.la + +kopete_cryptography_la_SOURCES = cryptographyplugin.cpp kgpginterface.cpp cryptographyguiclient.cpp cryptographyselectuserkey.cpp cryptographyuserkey_ui.ui popuppublic.cpp kgpgselkey.cpp +kopete_cryptography_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kopete_cryptography_la_LIBADD = ../../libkopete/libkopete.la + +kcm_kopete_cryptography_la_SOURCES = cryptographypreferences.cpp cryptographyprefsbase.ui kgpgselkey.cpp +kcm_kopete_cryptography_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_cryptography_la_LIBADD = ../../libkopete/libkopete.la $(LIB_KUTILS) + +service_DATA = kopete_cryptography.desktop +servicedir = $(kde_servicesdir) + +kcm_DATA = kopete_cryptography_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog + +mydatadir = $(kde_datadir)/kopete_cryptography +mydata_DATA = cryptographyui.rc cryptographychatui.rc + diff --git a/kopete/plugins/cryptography/cryptographychatui.rc b/kopete/plugins/cryptography/cryptographychatui.rc new file mode 100644 index 00000000..3d8835a6 --- /dev/null +++ b/kopete/plugins/cryptography/cryptographychatui.rc @@ -0,0 +1,9 @@ + + + + + &Tools + + + + diff --git a/kopete/plugins/cryptography/cryptographyguiclient.cpp b/kopete/plugins/cryptography/cryptographyguiclient.cpp new file mode 100644 index 00000000..0c53eee0 --- /dev/null +++ b/kopete/plugins/cryptography/cryptographyguiclient.cpp @@ -0,0 +1,75 @@ +/* + cryptographyguiclient.cpp + + Copyright (c) 2004 by Olivier Goffart + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#include "cryptographyguiclient.h" +#include "cryptographyplugin.h" + + +#include "kopetemetacontact.h" +#include "kopetecontact.h" +#include "kopetechatsession.h" + +#include +#include +#include +#include + +class CryptographyPlugin; + +CryptographyGUIClient::CryptographyGUIClient(Kopete::ChatSession *parent ) + : QObject(parent) , KXMLGUIClient(parent) +{ + if(!parent || parent->members().isEmpty()) + { + deleteLater(); //we refuse to build this client, it is based on wrong parametters + return; + } + + QPtrList mb=parent->members(); + Kopete::MetaContact *first=mb.first()->metaContact(); + + if(!first) + { + deleteLater(); //we refuse to build this client, it is based on wrong parametters + return; + } + + setInstance( KGenericFactory::instance() ); + + + m_action=new KToggleAction( i18n("Encrypt Messages" ), QString::fromLatin1( "encrypted" ), 0, this, SLOT(slotToggled()), actionCollection() , "cryptographyToggle" ); + m_action->setChecked( first->pluginData( CryptographyPlugin::plugin() , "encrypt_messages") != QString::fromLatin1("off") ) ; + + setXMLFile("cryptographychatui.rc"); +} + + +CryptographyGUIClient::~CryptographyGUIClient() +{} + +void CryptographyGUIClient::slotToggled() +{ + QPtrList mb=static_cast(parent())->members(); + Kopete::MetaContact *first=mb.first()->metaContact(); + + if(!first) + return; + + first->setPluginData(CryptographyPlugin::plugin() , "encrypt_messages" , + m_action->isChecked() ? "on" : "off" ); +} + + +#include "cryptographyguiclient.moc" + diff --git a/kopete/plugins/cryptography/cryptographyguiclient.h b/kopete/plugins/cryptography/cryptographyguiclient.h new file mode 100644 index 00000000..5a1aee2c --- /dev/null +++ b/kopete/plugins/cryptography/cryptographyguiclient.h @@ -0,0 +1,41 @@ +/* + cryptographyguiclient.h + + Copyright (c) 2004 by Olivier Goffart + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef CRYPTOGUICLIENT_H +#define CRYPTOGUICLIENT_H + +#include +#include + +namespace Kopete { class ChatSession; } +class KToggleAction; + +/** + *@author Olivier Goffart + */ +class CryptographyGUIClient : public QObject, public KXMLGUIClient +{ +Q_OBJECT +public: + CryptographyGUIClient(Kopete::ChatSession *parent = 0); + ~CryptographyGUIClient(); + +private: + KToggleAction *m_action; + +private slots: + void slotToggled(); +}; + +#endif diff --git a/kopete/plugins/cryptography/cryptographyplugin.cpp b/kopete/plugins/cryptography/cryptographyplugin.cpp new file mode 100644 index 00000000..701ad8bd --- /dev/null +++ b/kopete/plugins/cryptography/cryptographyplugin.cpp @@ -0,0 +1,326 @@ +/*************************************************************************** + cryptographyplugin.cpp - description + ------------------- + begin : jeu nov 14 2002 + copyright : (C) 2002-2004 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "kopetemetacontact.h" +#include "kopetecontactlist.h" +#include "kopetechatsessionmanager.h" +#include "kopetesimplemessagehandler.h" +#include "kopeteuiglobal.h" +#include "kopetecontact.h" + +#include "cryptographyplugin.h" +#include "cryptographyselectuserkey.h" +#include "cryptographyguiclient.h" + +#include "kgpginterface.h" + +//This regexp try to match an HTML text, but only some authorized tags. +// used in slotIncomingMessage +//There are not rules to know if the test should be sent in html or not. +//In Jabber, the JEP says it's not. so we don't use richtext in our message, but some client did. +//We limit the html to some basis tag to limit security problem (bad links) +// - Olivier +const QRegExp CryptographyPlugin::isHTML( QString::fromLatin1( "^[^<>]*(|[\\s/][^><]*>)[^><]*)+$" ) , false ); + +typedef KGenericFactory CryptographyPluginFactory; +static const KAboutData aboutdata("kopete_cryptography", I18N_NOOP("Cryptography") , "1.0" ); +K_EXPORT_COMPONENT_FACTORY( kopete_cryptography, CryptographyPluginFactory( &aboutdata ) ) + +CryptographyPlugin::CryptographyPlugin( QObject *parent, const char *name, const QStringList & /* args */ ) +: Kopete::Plugin( CryptographyPluginFactory::instance(), parent, name ), + m_cachedPass() +{ + if( !pluginStatic_ ) + pluginStatic_=this; + + m_inboundHandler = new Kopete::SimpleMessageHandlerFactory( Kopete::Message::Inbound, + Kopete::MessageHandlerFactory::InStageToSent, this, SLOT( slotIncomingMessage( Kopete::Message& ) ) ); + connect( Kopete::ChatSessionManager::self(), + SIGNAL( aboutToSend( Kopete::Message & ) ), + SLOT( slotOutgoingMessage( Kopete::Message & ) ) ); + + m_cachedPass_timer = new QTimer(this, "m_cachedPass_timer" ); + QObject::connect(m_cachedPass_timer, SIGNAL(timeout()), this, SLOT(slotForgetCachedPass() )); + + + KAction *action=new KAction( i18n("&Select Cryptography Public Key..."), "encrypted", 0, this, SLOT (slotSelectContactKey()), actionCollection() , "contactSelectKey"); + connect ( Kopete::ContactList::self() , SIGNAL( metaContactSelected(bool)) , action , SLOT(setEnabled(bool))); + action->setEnabled(Kopete::ContactList::self()->selectedMetaContacts().count()==1 ); + + setXMLFile("cryptographyui.rc"); + loadSettings(); + connect(this, SIGNAL(settingsChanged()), this, SLOT( loadSettings() ) ); + + connect( Kopete::ChatSessionManager::self(), SIGNAL( chatSessionCreated( Kopete::ChatSession * )) , SLOT( slotNewKMM( Kopete::ChatSession * ) ) ); + //Add GUI action to all already existing kmm (if the plugin is launched when kopete already rining) + QValueList sessions = Kopete::ChatSessionManager::self()->sessions(); + for (QValueListIterator it= sessions.begin(); it!=sessions.end() ; ++it) + { + slotNewKMM(*it); + } + +} + +CryptographyPlugin::~CryptographyPlugin() +{ + delete m_inboundHandler; + pluginStatic_ = 0L; +} + +void CryptographyPlugin::loadSettings() +{ + KConfig *config = KGlobal::config(); + config->setGroup("Cryptography Plugin"); + + mPrivateKeyID = config->readEntry("PGP_private_key"); + mAlsoMyKey = config->readBoolEntry("Also_my_key", false); + + if(config->readBoolEntry("Cache_Till_App_Close", false)) + mCachePassPhrase = Keep; + if(config->readBoolEntry("Cache_Till_Time", false)) + mCachePassPhrase = Time; + if(config->readBoolEntry("Cache_Never", false)) + mCachePassPhrase = Never; + mCacheTime = config->readNumEntry("Cache_Time", 15); + mAskPassPhrase = config->readBoolEntry("No_Passphrase_Handling", false); +} + +CryptographyPlugin* CryptographyPlugin::plugin() +{ + return pluginStatic_ ; +} + +CryptographyPlugin* CryptographyPlugin::pluginStatic_ = 0L; + +QCString CryptographyPlugin::cachedPass() +{ + return pluginStatic_->m_cachedPass; +} + +void CryptographyPlugin::setCachedPass(const QCString& p) +{ + if(pluginStatic_->mCacheMode==Never) + return; + if(pluginStatic_->mCacheMode==Time) + pluginStatic_->m_cachedPass_timer->start(pluginStatic_->mCacheTime * 60000, false); + + pluginStatic_->m_cachedPass=p; +} + +bool CryptographyPlugin::passphraseHandling() +{ + return !pluginStatic_->mAskPassPhrase; +} + + +/*KActionCollection *CryptographyPlugin::customChatActions(Kopete::ChatSession *KMM) +{ + delete m_actionCollection; + + m_actionCollection = new KActionCollection(this); + KAction *actionTranslate = new KAction( i18n ("Translate"), 0, + this, SLOT( slotTranslateChat() ), m_actionCollection, "actionTranslate" ); + m_actionCollection->insert( actionTranslate ); + + m_currentChatSession=KMM; + return m_actionCollection; +}*/ + +void CryptographyPlugin::slotIncomingMessage( Kopete::Message& msg ) +{ + QString body = msg.plainBody(); + if( !body.startsWith( QString::fromLatin1("-----BEGIN PGP MESSAGE----") ) + || !body.contains( QString::fromLatin1("-----END PGP MESSAGE----") ) ) + return; + + if( msg.direction() != Kopete::Message::Inbound ) + { + QString plainBody; + if ( m_cachedMessages.contains( body ) ) + { + plainBody = m_cachedMessages[ body ]; + m_cachedMessages.remove( body ); + } + else + { + plainBody = KgpgInterface::KgpgDecryptText( body, mPrivateKeyID ); + } + + if( !plainBody.isEmpty() ) + { + //Check if this is a RTF message before escaping it + if( !isHTML.exactMatch( plainBody ) ) + { + plainBody = QStyleSheet::escape( plainBody ); + + //this is the same algoritm as in Kopete::Message::escapedBody(); + plainBody.replace( QString::fromLatin1( "\n" ), QString::fromLatin1( "
    " ) ) + .replace( QString::fromLatin1( "\t" ), QString::fromLatin1( "    " ) ) + .replace( QRegExp( QString::fromLatin1( "\\s\\s" ) ), QString::fromLatin1( "  " ) ); + } + + msg.setBody( QString::fromLatin1("
    ") + + i18n("Outgoing Encrypted Message: ") + + QString::fromLatin1("
    ") + + plainBody + + QString::fromLatin1("
    ") + , Kopete::Message::RichText ); + } + + //if there are too messages in cache, clear the cache + if(m_cachedMessages.count() > 5) + m_cachedMessages.clear(); + + return; + } + + + //the Message::unescape is there because client like fire replace linebreak by
    to work even if the protocol doesn't allow newlines (IRC) + // cf http://fire.sourceforge.net/forums/viewtopic.php?t=174 and Bug #96052 + if(body.contains("<")) + body= Kopete::Message::unescape(body); + + body = KgpgInterface::KgpgDecryptText( body, mPrivateKeyID ); + + if( !body.isEmpty() ) + { + //Check if this is a RTF message before escaping it + if( !isHTML.exactMatch( body ) ) + { + body = Kopete::Message::escape( body ); + } + + msg.setBody( QString::fromLatin1("
    ") + + i18n("Incoming Encrypted Message: ") + + QString::fromLatin1("
    ") + + body + + QString::fromLatin1("
    ") + , Kopete::Message::RichText ); + } + +} + +void CryptographyPlugin::slotOutgoingMessage( Kopete::Message& msg ) +{ + if(msg.direction() != Kopete::Message::Outbound) + return; + + QStringList keys; + QPtrList contactlist = msg.to(); + for( Kopete::Contact *c = contactlist.first(); c; c = contactlist.next() ) + { + QString tmpKey; + if( c->metaContact() ) + { + if(c->metaContact()->pluginData( this, "encrypt_messages" ) == "off" ) + return; + tmpKey = c->metaContact()->pluginData( this, "gpgKey" ); + } + if( tmpKey.isEmpty() ) + { + // kdDebug( 14303 ) << "CryptographyPlugin::slotOutgoingMessage: no key selected for one contact" <selectedMetaContacts().first(); + if(!m) + return; + QString key = m->pluginData( this, "gpgKey" ); + CryptographySelectUserKey *opts = new CryptographySelectUserKey( key, m ); + opts->exec(); + if( opts->result() ) + { + key = opts->publicKey(); + m->setPluginData( this, "gpgKey", key ); + } + delete opts; +} + +void CryptographyPlugin::slotForgetCachedPass() +{ + m_cachedPass=QCString(); + m_cachedPass_timer->stop(); +} + +void CryptographyPlugin::slotNewKMM(Kopete::ChatSession *KMM) +{ + connect(this , SIGNAL( destroyed(QObject*)) , + new CryptographyGUIClient(KMM) , SLOT(deleteLater())); +} + + + +#include "cryptographyplugin.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/cryptography/cryptographyplugin.h b/kopete/plugins/cryptography/cryptographyplugin.h new file mode 100644 index 00000000..506617cc --- /dev/null +++ b/kopete/plugins/cryptography/cryptographyplugin.h @@ -0,0 +1,102 @@ +/*************************************************************************** + cryptographyplugin.h - description + ------------------- + begin : jeu nov 14 2002 + copyright : (C) 2002-2004 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef CryptographyPLUGIN_H +#define CryptographyPLUGIN_H + + +#include "kopeteplugin.h" + +class QStringList; +class QString; +class QTimer; + +namespace Kopete +{ + class Message; + class MetaContact; + class ChatSession; + class SimpleMessageHandlerFactory; +} + +/** + * @author Olivier Goffart + */ + +class CryptographyPlugin : public Kopete::Plugin +{ + Q_OBJECT + +public: + enum CacheMode + { + Keep = 0, + Time = 1, + Never = 2 + }; + + static CryptographyPlugin *plugin(); + static QCString cachedPass(); + static void setCachedPass(const QCString &pass); + static bool passphraseHandling(); + static const QRegExp isHTML; + + CryptographyPlugin( QObject *parent, const char *name, const QStringList &args ); + ~CryptographyPlugin(); + +public slots: + + void slotIncomingMessage( Kopete::Message& msg ); + void slotOutgoingMessage( Kopete::Message& msg ); + +private slots: + + void slotSelectContactKey(); + void slotForgetCachedPass(); + void loadSettings(); + + void slotNewKMM(Kopete::ChatSession *); + +private: + static CryptographyPlugin* pluginStatic_; + Kopete::SimpleMessageHandlerFactory *m_inboundHandler; + QCString m_cachedPass; + QTimer *m_cachedPass_timer; + + //cache messages for showing + QMap m_cachedMessages; + + //Settings + QString mPrivateKeyID; + int mCacheMode; + unsigned int mCacheTime; + bool mAlsoMyKey; + bool mAskPassPhrase; + bool mCachePassPhrase; +}; + +#endif + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/cryptography/cryptographypreferences.cpp b/kopete/plugins/cryptography/cryptographypreferences.cpp new file mode 100644 index 00000000..1039aac8 --- /dev/null +++ b/kopete/plugins/cryptography/cryptographypreferences.cpp @@ -0,0 +1,49 @@ +/*************************************************************************** + cryptographypreferences.cpp - description + ------------------- + begin : jeu nov 14 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#include + +#include +#include + +#include "cryptographyprefsbase.h" +#include "cryptographypreferences.h" +#include "kgpgselkey.h" + +typedef KGenericFactory CryptographyPreferencesFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_cryptography, CryptographyPreferencesFactory("kcm_kopete_cryptography")) + +CryptographyPreferences::CryptographyPreferences(QWidget *parent, const char* /*name*/, const QStringList &args) + : KCAutoConfigModule(CryptographyPreferencesFactory::instance(), parent, args) +{ + // Add actuall widget generated from ui file. + preferencesDialog = new CryptographyPrefsUI(this); + connect (preferencesDialog->m_selectOwnKey , SIGNAL(pressed()) , this , SLOT(slotSelectPressed())); + setMainWidget( preferencesDialog ,"Cryptography Plugin"); +} + +void CryptographyPreferences::slotSelectPressed() +{ + KgpgSelKey opts(this,0,false); + opts.exec(); + if (opts.result()==QDialog::Accepted) + preferencesDialog->PGP_private_key->setText(opts.getkeyID()); +} + +#include "cryptographypreferences.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/cryptography/cryptographypreferences.h b/kopete/plugins/cryptography/cryptographypreferences.h new file mode 100644 index 00000000..057eacf1 --- /dev/null +++ b/kopete/plugins/cryptography/cryptographypreferences.h @@ -0,0 +1,42 @@ +/*************************************************************************** + cryptographypreferences.h + ------------------- + begin : jeu nov 14 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef CryptographyPREFERENCES_H +#define CryptographyPREFERENCES_H + +#include "kcautoconfigmodule.h" + +class CryptographyPrefsUI; +class KAutoConfig; + +/** + * Preference widget for the Cryptography plugin + * @author Olivier Goffart + */ +class CryptographyPreferences : public KCAutoConfigModule { + Q_OBJECT +public: + CryptographyPreferences(QWidget *parent = 0, const char *name = 0, const QStringList &args = QStringList()); +private: + CryptographyPrefsUI *preferencesDialog; +private slots: // Public slots + void slotSelectPressed(); +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/cryptography/cryptographyprefsbase.ui b/kopete/plugins/cryptography/cryptographyprefsbase.ui new file mode 100644 index 00000000..ecec3507 --- /dev/null +++ b/kopete/plugins/cryptography/cryptographyprefsbase.ui @@ -0,0 +1,196 @@ + +CryptographyPrefsUI +Olivier Goffart + + + CryptographyPrefsUI + + + + 0 + 0 + 403 + 287 + + + + + unnamed + + + + TextLabel1 + + + Your private PGP key: + + + + + m_selectOwnKey + + + Select... + + + + + PGP_private_key + + + true + + + + + Also_my_key + + + Encrypt outgoing messages with this key + + + <qt>Check this box if you want to encrypt outgoing messages with this key, so that you will be able to decrypt them yourself later.<br> +<b>Warning:</b> This can increase the size of messages, and some protocols will refuse to send your messages because they are too large. + + + + + spacer3 + + + Vertical + + + Expanding + + + + 21 + 50 + + + + + + m_cache + + + Cache Passphrase + + + + unnamed + + + + Cache_Till_App_Close + + + Until Kopete closes + + + true + + + + + Cache_Time + + + + 7 + 0 + 0 + 0 + + + + 999 + + + 1 + + + 15 + + + + + textLabel1 + + + + 7 + 0 + 0 + 0 + + + + minutes + + + + + Cache_Till_Time + + + + 1 + 0 + 0 + 0 + + + + For + + + + + Cache_Never + + + Never + + + + + + + No_Passphrase_Handling + + + Do not ask for the passphrase + + + + + + + No_Passphrase_Handling + toggled(bool) + m_cache + setDisabled(bool) + + + + PGP_private_key + m_selectOwnKey + Also_my_key + No_Passphrase_Handling + Cache_Till_App_Close + Cache_Till_Time + Cache_Time + Cache_Never + + + m_selectOwnKey_clicked() + m_selectOwnKey_toggled(bool) + m_selectOwnKey_stateChanged(int) + + + + klineedit.h + + diff --git a/kopete/plugins/cryptography/cryptographyselectuserkey.cpp b/kopete/plugins/cryptography/cryptographyselectuserkey.cpp new file mode 100644 index 00000000..4f1cc35e --- /dev/null +++ b/kopete/plugins/cryptography/cryptographyselectuserkey.cpp @@ -0,0 +1,71 @@ +/*************************************************************************** + cryptographyselectuserkey.cpp - description + ------------------- + begin : dim nov 17 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#include +#include +#include +#include + +#include "cryptographyuserkey_ui.h" +#include "kopetemetacontact.h" +#include "popuppublic.h" + +#include "cryptographyselectuserkey.h" + +CryptographySelectUserKey::CryptographySelectUserKey(const QString& key ,Kopete::MetaContact *mc) : KDialogBase( 0l, "CryptographySelectUserKey", /*modal = */true, i18n("Select Contact's Public Key") , KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok ) +{ + m_metaContact=mc; + view = new CryptographyUserKey_ui(this,"CryptographyUserKey_ui"); + setMainWidget(view); + + connect (view->m_selectKey , SIGNAL(clicked()) , this , SLOT(slotSelectPressed())); + connect (view->m_removeButton , SIGNAL(clicked()) , this , SLOT(slotRemovePressed())); + + view->m_titleLabel->setText(i18n("Select public key for %1").arg(mc->displayName())); + view->m_editKey->setText(key); +} +CryptographySelectUserKey::~CryptographySelectUserKey() +{ +} + +void CryptographySelectUserKey::slotSelectPressed() +{ + popupPublic *dialog=new popupPublic(this, "public_keys", 0,false); + connect(dialog,SIGNAL(selectedKey(QString &,QString,bool,bool)),this,SLOT(keySelected(QString &))); + dialog->show(); +} + + +void CryptographySelectUserKey::keySelected(QString &key) +{ + view->m_editKey->setText(key); +} + +void CryptographySelectUserKey::slotRemovePressed() +{ + view->m_editKey->setText(""); +} + +QString CryptographySelectUserKey::publicKey() const +{ + return view->m_editKey->text(); +} + + + +#include "cryptographyselectuserkey.moc" + diff --git a/kopete/plugins/cryptography/cryptographyselectuserkey.h b/kopete/plugins/cryptography/cryptographyselectuserkey.h new file mode 100644 index 00000000..1a8828cf --- /dev/null +++ b/kopete/plugins/cryptography/cryptographyselectuserkey.h @@ -0,0 +1,51 @@ +/*************************************************************************** + cryptographyselectuserkey.h - description + ------------------- + begin : dim nov 17 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef CRYPTOGRAPHYSELECTUSERKEY_H +#define CRYPTOGRAPHYSELECTUSERKEY_H + +#include + +namespace Kopete { class MetaContact; } +class CryptographyUserKey_ui; + +/** + *@author OlivierGoffart + */ + +class CryptographySelectUserKey : public KDialogBase { + Q_OBJECT +public: + CryptographySelectUserKey(const QString &key, Kopete::MetaContact *mc); + ~CryptographySelectUserKey(); + + + QString publicKey() const; + +private slots: + void keySelected(QString &); + void slotSelectPressed(); + /** No descriptions */ + void slotRemovePressed(); + +private: + CryptographyUserKey_ui *view; + Kopete::MetaContact *m_metaContact; + +}; + +#endif diff --git a/kopete/plugins/cryptography/cryptographyui.rc b/kopete/plugins/cryptography/cryptographyui.rc new file mode 100644 index 00000000..542cb597 --- /dev/null +++ b/kopete/plugins/cryptography/cryptographyui.rc @@ -0,0 +1,12 @@ + + + + + &Edit + + + + + + + diff --git a/kopete/plugins/cryptography/cryptographyuserkey_ui.ui b/kopete/plugins/cryptography/cryptographyuserkey_ui.ui new file mode 100644 index 00000000..d84f2fec --- /dev/null +++ b/kopete/plugins/cryptography/cryptographyuserkey_ui.ui @@ -0,0 +1,79 @@ + +CryptographyUserKey_ui + + + CryptographyUserKey_ui + + + + 0 + 0 + 442 + 232 + + + + + unnamed + + + + TextLabel3 + + + PGP key: + + + + + m_editKey + + + true + + + + + m_selectKey + + + Select... + + + + + Spacer3 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + m_removeButton + + + Remove + + + + + m_titleLabel + + + TextLabel2 + + + + + + diff --git a/kopete/plugins/cryptography/icons/Makefile.am b/kopete/plugins/cryptography/icons/Makefile.am new file mode 100644 index 00000000..db2335c3 --- /dev/null +++ b/kopete/plugins/cryptography/icons/Makefile.am @@ -0,0 +1,2 @@ +kgpgiconsdir = $(kde_datadir)/kopete/icons +kgpgicons_ICON = AUTO diff --git a/kopete/plugins/cryptography/icons/cr16-action-kgpg_key1.png b/kopete/plugins/cryptography/icons/cr16-action-kgpg_key1.png new file mode 100644 index 00000000..ee3b21c7 Binary files /dev/null and b/kopete/plugins/cryptography/icons/cr16-action-kgpg_key1.png differ diff --git a/kopete/plugins/cryptography/icons/cr16-action-kgpg_key2.png b/kopete/plugins/cryptography/icons/cr16-action-kgpg_key2.png new file mode 100644 index 00000000..ad4e016f Binary files /dev/null and b/kopete/plugins/cryptography/icons/cr16-action-kgpg_key2.png differ diff --git a/kopete/plugins/cryptography/icons/cr16-action-kgpg_key3.png b/kopete/plugins/cryptography/icons/cr16-action-kgpg_key3.png new file mode 100644 index 00000000..ae788cab Binary files /dev/null and b/kopete/plugins/cryptography/icons/cr16-action-kgpg_key3.png differ diff --git a/kopete/plugins/cryptography/kgpginterface.cpp b/kopete/plugins/cryptography/kgpginterface.cpp new file mode 100644 index 00000000..51b35a63 --- /dev/null +++ b/kopete/plugins/cryptography/kgpginterface.cpp @@ -0,0 +1,176 @@ +#include "cryptographyplugin.h" //(for the cached passphrase) +//Code from KGPG + +/*************************************************************************** + kgpginterface.cpp - description + ------------------- + begin : Mon Jul 8 2002 + copyright : (C) 2002 by y0k0 + email : bj@altern.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. * + * * + ***************************************************************************/ + + +#include +#include + +#include +#include +#include +#include +#include + +#include + +//#include "kdetailedconsole.h" + +#include "kgpginterface.h" + +KgpgInterface::KgpgInterface() +{} + +KgpgInterface::~KgpgInterface() +{} + +QString KgpgInterface::KgpgEncryptText(QString text,QString userIDs, QString Options) +{ + FILE *fp; + QString dests,encResult; + char buffer[200]; + + userIDs=userIDs.stripWhiteSpace(); + userIDs=userIDs.simplifyWhiteSpace(); + Options=Options.stripWhiteSpace(); + + int ct=userIDs.find(" "); + while (ct!=-1) // if multiple keys... + { + dests+=" --recipient "+userIDs.section(' ',0,0); + userIDs.remove(0,ct+1); + ct=userIDs.find(" "); + } + dests+=" --recipient "+userIDs; + + QCString gpgcmd = "echo -n "; + gpgcmd += KShellProcess::quote( text ).utf8(); + gpgcmd += " | gpg --no-secmem-warning --no-tty "; + gpgcmd += Options.local8Bit(); + gpgcmd += " -e "; + gpgcmd += dests.local8Bit(); + + ////////// encode with untrusted keys or armor if checked by user + fp = popen( gpgcmd, "r"); + while ( fgets( buffer, sizeof(buffer), fp)) + encResult+=buffer; + pclose(fp); + + if( !encResult.isEmpty() ) + return encResult; + else + return QString::null; +} + +QString KgpgInterface::KgpgDecryptText(QString text,QString userID) +{ + FILE *fp,*pass; + QString encResult; + + char buffer[200]; + int counter=0,ppass[2]; + QCString password = CryptographyPlugin::cachedPass(); + bool passphraseHandling=CryptographyPlugin::passphraseHandling(); + + while ((counter<3) && (encResult.isEmpty())) + { + counter++; + if(passphraseHandling && password.isNull()) + { + /// pipe for passphrase + //userID=QString::fromUtf8(userID); + userID.replace('<',"<"); + QString passdlg=i18n("Enter passphrase for %1:").arg(userID); + if (counter>1) + passdlg.prepend(i18n("Bad passphrase
    You have %1 tries left.
    ").arg(QString::number(4-counter))); + + /// pipe for passphrase + int code=KPasswordDialog::getPassword(password,passdlg); + if (code!=QDialog::Accepted) + return QString::null; + CryptographyPlugin::setCachedPass(password); + } + + if(passphraseHandling) + { + pipe(ppass); + pass = fdopen(ppass[1], "w"); + fwrite(password, sizeof(char), strlen(password), pass); + // fwrite("\n", sizeof(char), 1, pass); + fclose(pass); + } + + QCString gpgcmd="echo "; + gpgcmd += KShellProcess::quote(text).utf8(); + gpgcmd += " | gpg --no-secmem-warning --no-tty "; + if(passphraseHandling) + gpgcmd += "--passphrase-fd " + QString::number(ppass[0]).local8Bit(); + gpgcmd += " -d "; + + ////////// encode with untrusted keys or armor if checked by user + fp = popen(gpgcmd, "r"); + while ( fgets( buffer, sizeof(buffer), fp)) + encResult += QString::fromUtf8(buffer); + + pclose(fp); + password = QCString(); + } + + if( !encResult.isEmpty() ) + return encResult; + else + return QString::null; +} + +QString KgpgInterface::checkForUtf8(QString txt) +{ + + // code borrowed from gpa + const char *s; + + /* Make sure the encoding is UTF-8. + * Test structure suggested by Werner Koch */ + if (txt.isEmpty()) + return QString::null; + + for (s = txt.ascii(); *s && !(*s & 0x80); s++) + ; + if (*s && !strchr (txt.ascii(), 0xc3) && (txt.find("\\x")==-1)) + return txt; + + /* The string is not in UTF-8 */ + //if (strchr (txt.ascii(), 0xc3)) return (txt+" +++"); + if (txt.find("\\x")==-1) + return QString::fromUtf8(txt.ascii()); + // if (!strchr (txt.ascii(), 0xc3) || (txt.find("\\x")!=-1)) { + for ( int idx = 0 ; (idx = txt.find( "\\x", idx )) >= 0 ; ++idx ) { + char str[2] = "x"; + str[0] = (char) QString( txt.mid( idx + 2, 2 ) ).toShort( 0, 16 ); + txt.replace( idx, 4, str ); + } + if (!strchr (txt.ascii(), 0xc3)) + return QString::fromUtf8(txt.ascii()); + else + return QString::fromUtf8(QString::fromUtf8(txt.ascii()).ascii()); // perform Utf8 twice, or some keys display badly +} + + + + +#include "kgpginterface.moc" diff --git a/kopete/plugins/cryptography/kgpginterface.h b/kopete/plugins/cryptography/kgpginterface.h new file mode 100644 index 00000000..b70bc68a --- /dev/null +++ b/kopete/plugins/cryptography/kgpginterface.h @@ -0,0 +1,91 @@ +//Code from KGPG + +/*************************************************************************** + kgpginterface.h - description + ------------------- + begin : Sat Jun 29 2002 + copyright : (C) 2002 by + email : + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KGPGINTERFACE_H +#define KGPGINTERFACE_H + + +#include + +/** + * Encrypt a file using gpg. + */ +//class KgpgEncryptFile : public QObject { +class KgpgInterface : public QObject { + + Q_OBJECT + + public: + /** + * Initialize the class + */ + KgpgInterface(); + + + /**Encrypt text function + * @param text QString text to be encrypted. + * @param userIDs the recipients key id's. + * @param Options String with the wanted gpg options. ex: "--armor" + * returns the encrypted text or empty string if encyption failed + */ + static QString KgpgEncryptText(QString text,QString userIDs, QString Options=""); + + /**Decrypt text function + * @param text QString text to be decrypted. + * @param userID QString the name of the decryption key (only used to prompt user for passphrase) + */ + static QString KgpgDecryptText(QString text,QString userID); +// static QString KgpgDecryptFileToText(KURL srcUrl,QString userID); + + /* + * Destructor for the class. + */ + ~KgpgInterface(); + + static QString checkForUtf8(QString txt); + + + private slots: + +signals: + + private: + /** + * @internal structure for communication + */ + QString message,tempKeyFile,userIDs,txtprocess,output; + QCString passphrase; + bool deleteSuccess,konsLocal,anonymous,txtsent,decfinished,decok,badmdc; + int signSuccess; + int step,signb,sigsearch; + QString konsSignKey, konsKeyID; + + + /** + * @internal structure for the file information + */ + KURL file; + /** + * @internal structure to send signal only once on error. + */ + bool encError; +}; + + +#endif diff --git a/kopete/plugins/cryptography/kgpgselkey.cpp b/kopete/plugins/cryptography/kgpgselkey.cpp new file mode 100644 index 00000000..70f76598 --- /dev/null +++ b/kopete/plugins/cryptography/kgpgselkey.cpp @@ -0,0 +1,245 @@ +//Code from KGPG + +/*************************************************************************** + listkeys.cpp - description + ------------------- + begin : Thu Jul 4 2002 + copyright : (C) 2002 by y0k0 + email : bj@altern.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. * + * * + ***************************************************************************/ + +////////////////////////////////////////////////////// code for the key management + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "kgpgselkey.h" + + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////// Secret key selection dialog, used when user wants to sign a key +KgpgSelKey::KgpgSelKey(QWidget *parent, const char *name,bool showlocal):KDialogBase( parent, name, true,i18n("Private Key List"),Ok | Cancel) +{ + QString keyname; + QWidget *page = new QWidget(this); + QLabel *labeltxt; + KIconLoader *loader = KGlobal::iconLoader(); + + keyPair=loader->loadIcon("kgpg_key2",KIcon::Small,20); + + setMinimumSize(300,200); + keysListpr = new KListView( page ); + keysListpr->setRootIsDecorated(true); + keysListpr->addColumn( i18n( "Name" ) ); + keysListpr->setShowSortIndicator(true); + keysListpr->setFullWidth(true); + + labeltxt=new QLabel(i18n("Choose secret key:"),page); + QVBoxLayout *vbox=new QVBoxLayout(page,3); + + vbox->addWidget(labeltxt); + vbox->addWidget(keysListpr); + if (showlocal==true) + { + local = new QCheckBox(i18n("Local signature (cannot be exported)"),page); + vbox->addWidget(local); + } + + FILE *fp,*fp2; + QString tst,tst2; + char line[130]; + + // FIXME: Why use popen instead of KProcess, QProcess or KProcIO?!? + // Are we interested in having buffer overflows now? - Martijn + fp = popen( "gpg --no-tty --with-colon --list-secret-keys", "r" ); + while ( fgets( line, sizeof(line), fp)) + { + tst=line; + if (tst.startsWith("sec")) + { + const QString trust=tst.section(':',1,1); + QString val=tst.section(':',6,6); + QString id=QString("0x"+tst.section(':',4,4).right(8)); + if (val.isEmpty()) + val=i18n("Unlimited"); + QString tr; + switch( trust[0] ) + { + case 'o': + tr= i18n("Unknown"); + break; + case 'i': + tr= i18n("Invalid"); + break; + case 'd': + tr=i18n("Disabled"); + break; + case 'r': + tr=i18n("Revoked"); + break; + case 'e': + tr=i18n("Expired"); + break; + case 'q': + tr=i18n("Undefined"); + break; + case 'n': + tr=i18n("None"); + break; + case 'm': + tr=i18n("Marginal"); + break; + case 'f': + tr=i18n("Full"); + break; + case 'u': + tr=i18n("Ultimate"); + break; + default: + tr=i18n("?"); + break; + } + tst=tst.section(":",9,9); + + // FIXME: Same here: don't use popen! - Martijn + fp2 = popen( QString( "gpg --no-tty --with-colon --list-key %1" ).arg( KShellProcess::quote( id ) ).latin1(), "r" ); + bool dead=true; + while ( fgets( line, sizeof(line), fp2)) + { + tst2=line; + if (tst2.startsWith("pub")) + { + const QString trust2=tst2.section(':',1,1); + switch( trust2[0] ) + { + case 'f': + dead=false; + break; + case 'u': + dead=false; + break; + default: + break; + } + } + } + pclose(fp2); + if (!tst.isEmpty() && (!dead)) + { + KListViewItem *item=new KListViewItem(keysListpr,extractKeyName(tst)); + KListViewItem *sub= new KListViewItem(item,i18n("ID: %1, trust: %2, expiration: %3").arg(id).arg(tr).arg(val)); + sub->setSelectable(false); + item->setPixmap(0,keyPair); + } + } + } + pclose(fp); + + + QObject::connect(keysListpr,SIGNAL(doubleClicked(QListViewItem *,const QPoint &,int)),this,SLOT(slotpreOk())); + QObject::connect(keysListpr,SIGNAL(clicked(QListViewItem *)),this,SLOT(slotSelect(QListViewItem *))); + + + keysListpr->setSelected(keysListpr->firstChild(),true); + + page->show(); + resize(this->minimumSize()); + setMainWidget(page); +} + +QString KgpgSelKey::extractKeyName(QString fullName) +{ + QString kMail; + if (fullName.find("<")!=-1) + { + kMail=fullName.section('<',-1,-1); + kMail.truncate(kMail.length()-1); + } + QString kName=fullName.section('<',0,0); + if (kName.find("(")!=-1) kName=kName.section('(',0,0); + return QString(kMail+" ("+kName+")").stripWhiteSpace(); +} + +void KgpgSelKey::slotpreOk() +{ + if (keysListpr->currentItem()->depth()!=0) + return; + else + slotOk(); +} + +void KgpgSelKey::slotOk() +{ + if (keysListpr->currentItem()==NULL) + reject(); + else + accept(); +} + +void KgpgSelKey::slotSelect(QListViewItem *item) +{ + if (item==NULL) return; + if (item->depth()!=0) + { + keysListpr->setSelected(item->parent(),true); + keysListpr->setCurrentItem(item->parent()); + } +} + + +QString KgpgSelKey::getkeyID() +{ + QString userid; + ///// emit selected key + if (keysListpr->currentItem()==NULL) return(""); + else + { + userid=keysListpr->currentItem()->firstChild()->text(0); + userid=userid.section(',',0,0); + userid=userid.section(':',1,1); + userid=userid.stripWhiteSpace(); + return(userid); + } +} + +QString KgpgSelKey::getkeyMail() +{ + QString username; + ///// emit selected key + if (keysListpr->currentItem()==NULL) return(""); + else + { + username=keysListpr->currentItem()->text(0); + //username=username.section(' ',0,0); + username=username.stripWhiteSpace(); + return(username); + } +} + +bool KgpgSelKey::getlocal() +{ + ///// emit exportation choice + return(local->isChecked()); +} + +#include "kgpgselkey.moc" diff --git a/kopete/plugins/cryptography/kgpgselkey.h b/kopete/plugins/cryptography/kgpgselkey.h new file mode 100644 index 00000000..11bcc498 --- /dev/null +++ b/kopete/plugins/cryptography/kgpgselkey.h @@ -0,0 +1,64 @@ +//Code from KGPG + +/*************************************************************************** + listkeys.h - description + ------------------- + begin : Thu Jul 4 2002 + copyright : (C) 2002 by y0k0 + email : bj@altern.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. * + * * + ***************************************************************************/ + +#ifndef LISTKEYS_H +#define LISTKEYS_H + + +#include + +class KListView; +class QCheckBox; + +typedef struct gpgKey{ + QString gpgkeymail; + QString gpgkeyname; + QString gpgkeyid; + QString gpgkeytrust; + QString gpgkeyvalidity; + QString gpgkeysize; + QString gpgkeycreation; + QString gpgkeyexpiration; + QString gpgkeyalgo; +}; + +class KgpgSelKey : public KDialogBase +{ + Q_OBJECT + +public: + KgpgSelKey( QWidget *parent = 0, const char *name = 0,bool showlocal=true); + KListView *keysListpr; +QPixmap keyPair; +QCheckBox *local; +private slots: +void slotOk(); +void slotpreOk(); +void slotSelect(QListViewItem *item); +QString extractKeyName(QString fullName); + +public: + QString getkeyID(); + QString getkeyMail(); + bool getlocal(); +}; + + + +#endif diff --git a/kopete/plugins/cryptography/kopete_cryptography.desktop b/kopete/plugins/cryptography/kopete_cryptography.desktop new file mode 100644 index 00000000..bbc3b591 --- /dev/null +++ b/kopete/plugins/cryptography/kopete_cryptography.desktop @@ -0,0 +1,129 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=encrypted +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_cryptography +X-KDE-PluginInfo-Author=Olivier Goffart +X-KDE-PluginInfo-Email=ogoffart@tiscalinet.be +X-KDE-PluginInfo-Name=kopete_cryptography +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Cryptography +Name[ar]=التشÙير +Name[be]=ÐšÑ€Ñ‹Ð¿Ñ‚Ð°Ð³Ñ€Ð°Ñ„Ñ–Ñ +Name[bg]=Шифроване +Name[bn]=কà§à¦°à¦¿à¦ªà§à¦Ÿà§‹à¦—à§à¦°à¦¾à¦«à¦¿ +Name[bs]=Kriptografija +Name[ca]=Xifrat +Name[cs]=Å ifrování +Name[cy]=Cêl-ysgrifennaeth +Name[da]=Kryptografi +Name[de]=Kryptographie +Name[el]=ΚÏυπτογÏαφία +Name[eo]=Ĉifrado +Name[es]=Criptografí­a +Name[et]=Krüpto +Name[eu]=Kriptografia +Name[fa]=رمزنگاری +Name[fi]=Salaus +Name[fr]=Cryptographie +Name[ga]=Criptiúchán +Name[gl]=Criptografía +Name[he]=קריפטוגרפיה +Name[hi]=कà¥à¤°à¤¿à¤ªà¥à¤Ÿà¥‹à¤—à¥à¤°à¤¾à¤«à¥€ +Name[hr]=Kriptografija +Name[hu]=Titkosítás +Name[is]=Dulritun +Name[it]=Crittografia +Name[ja]=æš—å·åŒ– +Name[ka]=კრიპტáƒáƒ’რáƒáƒ¤áƒ˜áƒ +Name[kk]=Шифрлау +Name[km]=ការ​គ្រីប +Name[lt]=Å ifravimas +Name[mk]=Криптографија +Name[nb]=Kryptografi +Name[nds]=Verslöteln +Name[ne]=कà¥à¤°à¤¿à¤ªà¥à¤Ÿà¥‹à¤—à¥à¤°à¤¾à¤«à¥€ +Name[nl]=Cryptografie +Name[nn]=Kryptografi +Name[pl]=Szyfrowanie +Name[pt]=Encriptação +Name[pt_BR]=Criptografia +Name[ro]=Criptografie +Name[ru]=Шифрование +Name[se]=Kryptográfiija +Name[sk]=Å ifrovanie +Name[sl]=Å ifriranje +Name[sr]=Криптографија +Name[sr@Latn]=Kriptografija +Name[sv]=Kryptering +Name[ta]=மறைகà¯à®•à¯à®±à®¿à®¯à®¿à®¯à®²à¯ +Name[tg]=Рамзгузорӣ +Name[tr]=Åžifreleme +Name[uk]=ÐšÑ€Ð¸Ð¿Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ñ–Ñ +Name[uz]=Kriptografiya +Name[uz@cyrillic]=ÐšÑ€Ð¸Ð¿Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ +Name[wa]=Criptografeye +Name[zh_CN]=加密 +Name[zh_HK]=密碼學 +Name[zh_TW]=加密 +Comment=Encrypt and decrypt messages with GPG +Comment[ar]=يقوم بتشÙير ÙˆÙÙ„ التشÙير الرسائل عن طريق GPG +Comment[be]=Шыфраваць Ñ– раÑшыфроўваць паведамленні Ñž GPG +Comment[bg]=Шифроване на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ñ GPG +Comment[bn]=জিপিজি বà§à¦¯à¦¬à¦¹à¦¾à¦° করে বারà§à¦¤à¦¾ à¦à¦¨à¦•à§à¦°à¦¿à¦ªà§à¦Ÿ à¦à¦¬à¦‚ ডিকà§à¦°à¦¿à¦ªà§à¦Ÿ করà§à¦¨ +Comment[bs]=Å ifrujte poruke koristeći GPG +Comment[ca]=Xifra i desxifra missatges amb GPG +Comment[cs]=Å ifrování a deÅ¡ifrování zpráv pomocí GPG +Comment[cy]=Celu a datgelu negeseuon efo GPG +Comment[da]=Kryptér og dekryptér beskeder med GPG +Comment[de]=Verschlüsselt und entschlüsselt Nachrichten mit GPG +Comment[el]=ΚÏυπτογÏαφήστε και αποκÏυπτογÏαφήστε μηνÏματα με το GPG +Comment[es]=Cifra y descifra mensajes con GPG +Comment[et]=Sõnumite krüptimine ja lahtikrüptimine GPG abil +Comment[eu]=Enkriptatu eta desenkriptatu mezuak GPG-rekin +Comment[fa]=رمزبندی Ùˆ سرگشایی پیامها با GPG +Comment[fi]=Salaa ja pura salaus viesteistä GPG-ohjelmalla +Comment[fr]=Chiffrer et déchiffrer les messages avec GPG +Comment[gl]=Encriptar e desencriptar mensaxes con GPG +Comment[he]=הצפנה ופענוח של הודעות בעזרת GPG +Comment[hi]=जीपीजी के दà¥à¤µà¤¾à¤°à¤¾ संदेश à¤à¤¨à¤•à¥à¤°à¤¿à¤ªà¥à¤Ÿ तथा डिकà¥à¤°à¤¿à¤ªà¥à¤Ÿ करे +Comment[hr]=Kriptiranje i dekriptiranej poruka GPG-om +Comment[hu]=Titkosítás/dekódolás GPG-vel +Comment[is]=Dulrita og afkóða skeyti með GPG +Comment[it]=Cifra e decifra i messaggi con GPG +Comment[ja]=GPG を使ã£ã¦ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’æš—å·åŒ–/å¾©å· +Comment[ka]=GPG შეტყáƒáƒ‘ინებების დáƒáƒ¨áƒ˜áƒ¤áƒ•áƒ áƒ დრგáƒáƒ¨áƒ˜áƒ¤áƒ•áƒ áƒ•áƒ +Comment[kk]=GPG көмегімен хабарларды шифрлау не шифрын шешу +Comment[km]=អ៊ិនគ្រីប និង​ឌិគ្រីប​សារ​ដោយ​ប្រើ GPG +Comment[lt]=Užšifruoti ir atÅ¡ifruoti žinutes GPG bÅ«du +Comment[mk]=Криптирајте и декриптирајте пораки Ñо GPG +Comment[nb]=Krypter / dekrypter meldinger med GPG +Comment[nds]=Narichten mit GPG ver- un opslöteln +Comment[ne]=जीपीजी सà¤à¤— सनà¥à¤¦à¥‡à¤¶à¤¹à¤°à¥‚ गà¥à¤ªà¥à¤¤à¤¿à¤•à¤°à¤£ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥/गà¥à¤ªà¥à¤¤à¤²à¥‡à¤–न उलà¥à¤Ÿà¤¾à¤‰à¤¨à¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Versleutel en ontcijfer berichten met GPG +Comment[nn]=Krypter og dekrypter meldingar med GPG +Comment[pl]=Szyfruje i odszyfrowuje wiadomoÅ›ci za pomocÄ… GPG +Comment[pt]=Cifra e decifra as mensagens com o GPG +Comment[pt_BR]=Criptografa e descriptografa mensagens com o GPG +Comment[ro]=Criptează ÅŸi decriptează mesajele cu GPG +Comment[ru]=Шифрование Ñообщений при помощи GPG +Comment[se]=Kryptere ja dekryptere dieđáhusaid GPG:ain +Comment[sk]=Å ifruje a deÅ¡ifruje správy pomocou GPG +Comment[sl]=Å ifriranje in deÅ¡ifriranje sporoÄil z GPG +Comment[sr]=Шифровање и дешифровање порука помоћу GPG-а +Comment[sr@Latn]=Å ifrovanje i deÅ¡ifrovanje poruka pomoću GPG-a +Comment[sv]=Kryptera och avkoda meddelanden med GPG +Comment[ta]=GPG உடன௠மறையாகà¯à®•à®®à¯ மறà¯à®±à¯à®®à¯ மறைவிலகà¯à®•à®®à¯ +Comment[tg]=Рамзгузорӣ ва рамзкушоии пайёмҳо ба воÑитаи GPG +Comment[tr]=GPG ile mesajları ÅŸifrele ve çöz +Comment[uk]=Ð¨Ð¸Ñ„Ñ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚Ð° Ñ€Ð¾Ð·ÑˆÐ¸Ñ„Ñ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ з GPG +Comment[wa]=Ecripter eyet discripter les messaedjes avou GPG +Comment[zh_CN]=用 GPG åŠ å¯†å’Œè§£å¯†æ¶ˆæ¯ +Comment[zh_HK]=以 GPG åŠ å¯†æˆ–è§£å¯†è¨Šæ¯ +Comment[zh_TW]=用 GPG åŠ å¯†æˆ–è§£å¯†æ‚¨çš„è¨Šæ¯ diff --git a/kopete/plugins/cryptography/kopete_cryptography_config.desktop b/kopete/plugins/cryptography/kopete_cryptography_config.desktop new file mode 100644 index 00000000..ed2d4cdb --- /dev/null +++ b/kopete/plugins/cryptography/kopete_cryptography_config.desktop @@ -0,0 +1,126 @@ +[Desktop Entry] +Icon=encrypted +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_cryptography +X-KDE-FactoryName=CryptographyConfigFactory +X-KDE-ParentApp=kopete_cryptography +X-KDE-ParentComponents=kopete_cryptography + +Name=Cryptography +Name[ar]=التشÙير +Name[be]=ÐšÑ€Ñ‹Ð¿Ñ‚Ð°Ð³Ñ€Ð°Ñ„Ñ–Ñ +Name[bg]=Шифроване +Name[bn]=কà§à¦°à¦¿à¦ªà§à¦Ÿà§‹à¦—à§à¦°à¦¾à¦«à¦¿ +Name[bs]=Kriptografija +Name[ca]=Xifrat +Name[cs]=Å ifrování +Name[cy]=Cêl-ysgrifennaeth +Name[da]=Kryptografi +Name[de]=Kryptographie +Name[el]=ΚÏυπτογÏαφία +Name[eo]=Ĉifrado +Name[es]=Criptografí­a +Name[et]=Krüpto +Name[eu]=Kriptografia +Name[fa]=رمزنگاری +Name[fi]=Salaus +Name[fr]=Cryptographie +Name[ga]=Criptiúchán +Name[gl]=Criptografía +Name[he]=קריפטוגרפיה +Name[hi]=कà¥à¤°à¤¿à¤ªà¥à¤Ÿà¥‹à¤—à¥à¤°à¤¾à¤«à¥€ +Name[hr]=Kriptografija +Name[hu]=Titkosítás +Name[is]=Dulritun +Name[it]=Crittografia +Name[ja]=æš—å·åŒ– +Name[ka]=კრიპტáƒáƒ’რáƒáƒ¤áƒ˜áƒ +Name[kk]=Шифрлау +Name[km]=ការ​គ្រីប +Name[lt]=Å ifravimas +Name[mk]=Криптографија +Name[nb]=Kryptografi +Name[nds]=Verslöteln +Name[ne]=कà¥à¤°à¤¿à¤ªà¥à¤Ÿà¥‹à¤—à¥à¤°à¤¾à¤«à¥€ +Name[nl]=Cryptografie +Name[nn]=Kryptografi +Name[pl]=Szyfrowanie +Name[pt]=Encriptação +Name[pt_BR]=Criptografia +Name[ro]=Criptografie +Name[ru]=Шифрование +Name[se]=Kryptográfiija +Name[sk]=Å ifrovanie +Name[sl]=Å ifriranje +Name[sr]=Криптографија +Name[sr@Latn]=Kriptografija +Name[sv]=Kryptering +Name[ta]=மறைகà¯à®•à¯à®±à®¿à®¯à®¿à®¯à®²à¯ +Name[tg]=Рамзгузорӣ +Name[tr]=Åžifreleme +Name[uk]=ÐšÑ€Ð¸Ð¿Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ñ–Ñ +Name[uz]=Kriptografiya +Name[uz@cyrillic]=ÐšÑ€Ð¸Ð¿Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ +Name[wa]=Criptografeye +Name[zh_CN]=加密 +Name[zh_HK]=密碼學 +Name[zh_TW]=加密 +Comment=Encrypts messages using PGP +Comment[ar]=يشÙر الرسائل باستخدام PGP +Comment[be]=Шыфруе паведамленні Ñž PGP +Comment[bg]=Шифроване на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ñ GPG +Comment[bn]=পিজিপি বà§à¦¯à¦¬à¦¹à¦¾à¦° করে বারà§à¦¤à¦¾ à¦à¦¨à¦•à§à¦°à¦¿à¦ªà§à¦Ÿ করে +Comment[bs]=Å ifruje poruke koristeći GPG +Comment[ca]=Xifra missatges emprant PGP +Comment[cs]=Å ifrování zpráv pomocí GPG +Comment[cy]=Celu negeseuon gan ddefnyddio PGP +Comment[da]=Krypterer beskeder ved brug af PGP +Comment[de]=Verschlüsselt Nachrichten mit PGP +Comment[el]=ΚÏυπτογÏαφεί τα μηνÏματα χÏησιμοποιώντας το PGP +Comment[es]=Cifra mensajes usando PGP +Comment[et]=Sõnumite krüptimine PGP abil +Comment[eu]=Enkriptatu mezuak PGP-rekin +Comment[fa]=پیامها را با استÙاده از GPG رمزبندی می‌کند +Comment[fi]=Salaa viestit käyttäen PGP-ohjelmaa +Comment[fr]=Chiffrer les messages avec PGP +Comment[gl]=Encripta mensaxes con PGP +Comment[he]=מצפין הודעות בעזרת PGP +Comment[hi]=जीपीजी के दà¥à¤µà¤¾à¤°à¤¾ संदेश à¤à¤¨à¤•à¥à¤°à¤¿à¤ªà¥à¤Ÿ करे +Comment[hr]=Kriptiranje i dekriptiranej poruka PGP-om +Comment[hu]=Ãœzenetek titkosítása PGP-vel +Comment[is]=Dulritar skeyti með PGP +Comment[it]=Cifra i messaggi con PGP +Comment[ja]=PGP を使ã£ã¦ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’æš—å·åŒ– +Comment[ka]=PGP შეტყáƒáƒ‘ინებების დáƒáƒ¨áƒ˜áƒ¤áƒ•áƒ áƒ•áƒ +Comment[kk]=PGP көмегімен хабарларды шифрлау +Comment[km]=អ៊ិនគ្រីប​សារ​ដោយ​ប្រើ PGP +Comment[lt]=Užšifruoti žinutes PGP bÅ«du +Comment[mk]=Криптирајте пораки употребувајќи PGP +Comment[nb]=Krypterer meldinger med PGP +Comment[nds]=Verslötelt Narichten mit PGP +Comment[ne]=पीजीपी पà¥à¤°à¤¯à¥‹à¤— गरेर सनà¥à¤¦à¥‡à¤¶ गà¥à¤ªà¥à¤¤à¤¿à¤•à¤°à¤£ गरà¥à¤¦à¤› +Comment[nl]=Versleutelt berichten met PGP +Comment[nn]=Krypterer meldingar med PGP +Comment[pl]=Szyfruje wiadomoÅ›ci za pomocÄ… PGP +Comment[pt]=Cifra as mensagens usando o PGP +Comment[pt_BR]=Criptografa mensagens usando o PGP +Comment[ro]=Criptează mesajele cu PGP +Comment[ru]=Шифрует Ñообщение Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ PGP +Comment[se]=Kryptere dieđáhusaid PGP:ain +Comment[sk]=Å ifruje správy pomocou PGP +Comment[sl]=Å ifriranje sporoÄil s PGP +Comment[sr]=Шифровање и дешифровање порука помоћу PGP-а +Comment[sr@Latn]=Å ifrovanje i deÅ¡ifrovanje poruka pomoću PGP-a +Comment[sv]=Krypterar meddelanden med PGP +Comment[ta]=PGP யை பயனà¯à®ªà®Ÿà¯à®¤à¯à®¤à®¿ செயà¯à®¤à®¿à®•à®³à¯ˆ சஙà¯à®•à¯‡à®¤à®™à¯à®•à®³à®¾à®•à¯à®•à¯ +Comment[tg]=Рамзгузории пайёмҳо ба воÑитаи PGP +Comment[tr]=PGP kullanan mesajları ÅŸifreler +Comment[uk]=Шифрує Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð·Ð° допомогою PGP +Comment[wa]=Ecripter les messaedjes avou PGP +Comment[zh_CN]=用 PGP åŠ å¯†æ¶ˆæ¯ +Comment[zh_HK]=將訊æ¯ä»¥ PGP 加密 +Comment[zh_TW]=用 PGP åŠ å¯†è¨Šæ¯ + diff --git a/kopete/plugins/cryptography/popuppublic.cpp b/kopete/plugins/cryptography/popuppublic.cpp new file mode 100644 index 00000000..36008bcf --- /dev/null +++ b/kopete/plugins/cryptography/popuppublic.cpp @@ -0,0 +1,514 @@ +//File Imported from KGPG ( 2004 - 09 - 03 ) + +/*************************************************************************** + popuppublic.cpp - description + ------------------- + begin : Sat Jun 29 2002 + copyright : (C) 2002 by Jean-Baptiste Mardelle + email : bj@altern.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. * + * * + ***************************************************************************/ + +//////////////////////////////////////////////////////// code for choosing a public key from a list for encryption +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "popuppublic.h" +//#include "kgpgsettings.h" +//#include "kgpgview.h" +//#include "kgpg.h" +#include "kgpginterface.h" + +///////////////// klistviewitem special + +class UpdateViewItem2 : public KListViewItem +{ +public: + UpdateViewItem2(QListView *parent, QString name,QString mail,QString id,bool isDefault); + virtual void paintCell(QPainter *p, const QColorGroup &cg,int col, int width, int align); + virtual QString key(int c,bool ) const; + bool def; +}; + +UpdateViewItem2::UpdateViewItem2(QListView *parent, QString name,QString mail,QString id,bool isDefault) + : KListViewItem(parent) +{ +def=isDefault; + setText(0,name); + setText(1,mail); + setText(2,id); +} + + +void UpdateViewItem2::paintCell(QPainter *p, const QColorGroup &cg,int column, int width, int alignment) +{ + if ((def) && (column<2)) { + QFont font(p->font()); + font.setBold(true); + p->setFont(font); + } + KListViewItem::paintCell(p, cg, column, width, alignment); +} + +QString UpdateViewItem2 :: key(int c,bool ) const +{ + return text(c).lower(); +} + +/////////////// main view + +popupPublic::popupPublic(QWidget *parent, const char *name,QString sfile,bool filemode,KShortcut goDefaultKey): +KDialogBase( Plain, i18n("Select Public Key"), Details | Ok | Cancel, Ok, parent, name,true) +{ + QWidget *page = plainPage(); + QVBoxLayout *vbox=new QVBoxLayout(page,0,spacingHint()); + vbox->setAutoAdd(true); + + setButtonText(KDialogBase::Details,i18n("Options")); + +/* if (KGpgSettings::allowCustomEncryptionOptions()) + customOptions=KGpgSettings::customEncryptionOptions();*/ + + KIconLoader *loader = KGlobal::iconLoader(); + + keyPair=loader->loadIcon("kgpg_key2",KIcon::Small,20); + keySingle=loader->loadIcon("kgpg_key1",KIcon::Small,20); + keyGroup=loader->loadIcon("kgpg_key3",KIcon::Small,20); + + if (filemode) setCaption(i18n("Select Public Key for %1").arg(sfile)); + fmode=filemode; + + QHButtonGroup *hBar=new QHButtonGroup(page); + //hBar->setFrameStyle(QFrame::NoFrame); + hBar->setMargin(0); + + QToolButton *clearSearch = new QToolButton(hBar); + clearSearch->setTextLabel(i18n("Clear Search"), true); + clearSearch->setIconSet(SmallIconSet(QApplication::reverseLayout() ? "clear_left" + : "locationbar_erase")); + (void) new QLabel(i18n("Search: "),hBar); + KListViewSearchLine* listViewSearch = new KListViewSearchLine(hBar); + connect(clearSearch, SIGNAL(pressed()), listViewSearch, SLOT(clear())); + + keysList = new KListView( page ); + keysList->addColumn(i18n("Name")); + keysList->addColumn(i18n("Email")); + keysList->addColumn(i18n("ID")); + + listViewSearch->setListView(keysList); + + keysList->setRootIsDecorated(false); + page->setMinimumSize(540,200); + keysList->setShowSortIndicator(true); + keysList->setFullWidth(true); + keysList->setAllColumnsShowFocus(true); + keysList->setSelectionModeExt(KListView::Extended); + keysList->setColumnWidthMode(0,QListView::Manual); + keysList->setColumnWidthMode(1,QListView::Manual); + keysList->setColumnWidth(0,210); + keysList->setColumnWidth(1,210); + + boutonboxoptions=new QButtonGroup(5,Qt::Vertical ,page,0); + + KActionCollection *actcol=new KActionCollection(this); + (void) new KAction(i18n("&Go to Default Key"),goDefaultKey, this, SLOT(slotGotoDefaultKey()),actcol,"go_default_key"); + + + CBarmor=new QCheckBox(i18n("ASCII armored encryption"),boutonboxoptions); + CBuntrusted=new QCheckBox(i18n("Allow encryption with untrusted keys"),boutonboxoptions); + CBhideid=new QCheckBox(i18n("Hide user id"),boutonboxoptions); + setDetailsWidget(boutonboxoptions); + QWhatsThis::add + (keysList,i18n("Public keys list: select the key that will be used for encryption.")); + QWhatsThis::add + (CBarmor,i18n("ASCII encryption: makes it possible to open the encrypted file/message in a text editor")); + QWhatsThis::add + (CBhideid,i18n("Hide user ID: Do not put the keyid into encrypted packets. This option hides the receiver " + "of the message and is a countermeasure against traffic analysis. It may slow down the decryption process because " + "all available secret keys are tried.")); + QWhatsThis::add + (CBuntrusted,i18n("Allow encryption with untrusted keys: when you import a public key, it is usually " + "marked as untrusted and you cannot use it unless you sign it in order to make it 'trusted'. Checking this " + "box enables you to use any key, even if it has not be signed.")); + + if (filemode) { + QWidget *parentBox=new QWidget(boutonboxoptions); + QHBoxLayout *shredBox=new QHBoxLayout(parentBox,0); + //shredBox->setFrameStyle(QFrame::NoFrame); + //shredBox->setMargin(0); + CBshred=new QCheckBox(i18n("Shred source file"),parentBox); + QWhatsThis::add + (CBshred,i18n("Shred source file: permanently remove source file. No recovery will be possible")); + + QString shredWhatsThis = i18n( "Shred source file:

    Checking this option will shred (overwrite several times before erasing) the files you have encrypted. This way, it is almost impossible that the source file is recovered.

    But you must be aware that this is not secure on all file systems, and that parts of the file may have been saved in a temporary file or in the spooler of your printer if you previously opened it in an editor or tried to print it. Only works on files (not on folders).

    "); + KActiveLabel *warn= new KActiveLabel( i18n("
    Read this before using shredding").arg(shredWhatsThis),parentBox ); + shredBox->addWidget(CBshred); + shredBox->addWidget(warn); + } + + CBsymmetric=new QCheckBox(i18n("Symmetrical encryption"),boutonboxoptions); + QWhatsThis::add + (CBsymmetric,i18n("Symmetrical encryption: encryption does not use keys. You just need to give a password " + "to encrypt/decrypt the file")); + QObject::connect(CBsymmetric,SIGNAL(toggled(bool)),this,SLOT(isSymetric(bool))); + +//BEGIN modified for Kopete + + setWFlags( getWFlags() | Qt::WDestructiveClose ); + + + /*CBarmor->setChecked( KGpgSettings::asciiArmor() ); + CBuntrusted->setChecked( KGpgSettings::allowUntrustedKeys() ); + CBhideid->setChecked( KGpgSettings::hideUserID() ); + if (filemode) CBshred->setChecked( KGpgSettings::shredSource() );*/ + KConfig *config = KGlobal::config(); + config->setGroup("Cryptography Plugin"); + + CBarmor->hide(); + CBuntrusted->setChecked(config->readBoolEntry("UntrustedKeys", true)); + CBhideid->hide(); + if (filemode) CBshred->hide(); + CBsymmetric->hide(); + +//END modified for Kopete + + /*if (KGpgSettings::allowCustomEncryptionOptions()) { + QHButtonGroup *bGroup = new QHButtonGroup(page); + //bGroup->setFrameStyle(QFrame::NoFrame); + (void) new QLabel(i18n("Custom option:"),bGroup); + KLineEdit *optiontxt=new KLineEdit(bGroup); + optiontxt->setText(customOptions); + QWhatsThis::add + (optiontxt,i18n("Custom option: for experienced users only, allows you to enter a gpg command line option, like: '--armor'")); + QObject::connect(optiontxt,SIGNAL(textChanged ( const QString & )),this,SLOT(customOpts(const QString & ))); + }*/ + QObject::connect(keysList,SIGNAL(doubleClicked(QListViewItem *,const QPoint &,int)),this,SLOT(slotOk())); +// QObject::connect(this,SIGNAL(okClicked()),this,SLOT(crypte())); + QObject::connect(CBuntrusted,SIGNAL(toggled(bool)),this,SLOT(refresh(bool))); + + char line[200]="\0"; + FILE *fp2; + seclist=QString::null; + + fp2 = popen("gpg --no-secmem-warning --no-tty --with-colon --list-secret-keys ", "r"); + while ( fgets( line, sizeof(line), fp2)) + { + QString readLine=line; + if (readLine.startsWith("sec")) seclist+=", 0x"+readLine.section(":",4,4).right(8); + } + pclose(fp2); + + trusted=CBuntrusted->isChecked(); + + refreshkeys(); + setMinimumSize(550,200); + updateGeometry(); + keysList->setFocus(); + show(); +} + +popupPublic::~popupPublic() +{} + + +void popupPublic::slotAccept() +{ +accept(); +} + +void popupPublic::enable() +{ + QListViewItem *current = keysList->firstChild(); + if (current==NULL) + return; + current->setVisible(true); + while ( current->nextSibling() ) { + current = current->nextSibling(); + current->setVisible(true); + } + keysList->ensureItemVisible(keysList->currentItem()); +} + +void popupPublic::sort() +{ + bool reselect=false; + QListViewItem *current = keysList->firstChild(); + if (current==NULL) + return; + + if ((untrustedList.find(current->text(2))!=untrustedList.end()) && (!current->text(2).isEmpty())){ + if (current->isSelected()) { + current->setSelected(false); + reselect=true; + } + current->setVisible(false); + } + + while ( current->nextSibling() ) { + current = current->nextSibling(); + if ((untrustedList.find(current->text(2))!=untrustedList.end()) && (!current->text(2).isEmpty())) { + if (current->isSelected()) { + current->setSelected(false); + reselect=true; + } + current->setVisible(false); + } + } + + if (reselect) { + QListViewItem *firstvisible; + firstvisible=keysList->firstChild(); + while (firstvisible->isVisible()!=true) { + firstvisible=firstvisible->nextSibling(); + if (firstvisible==NULL) + return; + } + keysList->setSelected(firstvisible,true); + keysList->setCurrentItem(firstvisible); + keysList->ensureItemVisible(firstvisible); + } +} + +void popupPublic::isSymetric(bool state) +{ + keysList->setEnabled(!state); + CBuntrusted->setEnabled(!state); + CBhideid->setEnabled(!state); +} + + +void popupPublic::customOpts(const QString &str) +{ + customOptions=str; +} + +void popupPublic::slotGotoDefaultKey() +{ + /*QListViewItem *myDefaulKey = keysList->findItem(KGpgSettings::defaultKey(),2); + keysList->clearSelection(); + keysList->setCurrentItem(myDefaulKey); + keysList->setSelected(myDefaulKey,true); + keysList->ensureItemVisible(myDefaulKey);*/ +} + +void popupPublic::refresh(bool state) +{ + if (state) + enable(); + else + sort(); +} + +void popupPublic::refreshkeys() +{ + keysList->clear(); + /*QStringList groups= QStringList::split(",", KGpgSettings::groups()); + if (!groups.isEmpty()) + { + for ( QStringList::Iterator it = groups.begin(); it != groups.end(); ++it ) + { + if (!QString(*it).isEmpty()) + { + UpdateViewItem2 *item=new UpdateViewItem2(keysList,QString(*it),QString::null,QString::null,false); + item->setPixmap(0,keyGroup); + } + } + }*/ + KProcIO *encid=new KProcIO(); + *encid << "gpg"<<"--no-secmem-warning"<<"--no-tty"<<"--with-colon"<<"--list-keys"; + ///////// when process ends, update dialog infos + QObject::connect(encid, SIGNAL(processExited(KProcess *)),this, SLOT(slotpreselect())); + QObject::connect(encid, SIGNAL(readReady(KProcIO *)),this, SLOT(slotprocread(KProcIO *))); + encid->start(KProcess::NotifyOnExit,true); +} + +void popupPublic::slotpreselect() +{ +QListViewItem *it; + //if (fmode) it=keysList->findItem(KGpgSettings::defaultKey(),2); + //else { + it=keysList->firstChild(); + if (it==NULL) + return; + while (!it->isVisible()) { + it=it->nextSibling(); + if (it==NULL) + return; + } + //} +if (!trusted) + sort(); + keysList->setSelected(it,true); + keysList->setCurrentItem(it); + keysList->ensureItemVisible(it); +emit keyListFilled(); +} + +void popupPublic::slotSetVisible() +{ + keysList->ensureItemVisible(keysList->currentItem()); +} + +void popupPublic::slotprocread(KProcIO *p) +{ + ///////////////////////////////////////////////////////////////// extract encryption keys + bool dead; + QString tst,keyname,keymail; + + QString defaultKey ;// = KGpgSettings::defaultKey().right(8); + + while (p->readln(tst)!=-1) { + if (tst.startsWith("pub")) { + QStringList keyString=QStringList::split(":",tst,true); + dead=false; + const QString trust=keyString[1]; + QString val=keyString[6]; + QString id=QString("0x"+keyString[4].right(8)); + if (val.isEmpty()) + val=i18n("Unlimited"); + QString tr; + switch( trust[0] ) { + case 'o': + untrustedList<setSelectable(false); + if (seclist.find(tst,0,FALSE)!=-1) + item->setPixmap(0,keyPair); + else + item->setPixmap(0,keySingle); + } + } + } +} + + +void popupPublic::slotOk() +{ +//BEGIN modified for Kopete + KConfig *config = KGlobal::config(); + config->setGroup("Cryptography Plugin"); + + config->writeEntry("UntrustedKeys", CBuntrusted->isChecked()); + config->writeEntry("HideID", CBhideid->isChecked()); + +//END modified for Kopete + + + + + ////// emit selected data +kdDebug(2100)<<"Ok pressed"< list=keysList->selectedItems(); + + for ( uint i = 0; i < list.count(); ++i ) + if ( list.at(i) ) { + if (!list.at(i)->text(2).isEmpty()) selectedKeys<text(2); + else selectedKeys<text(0); + } + if (selectedKeys.isEmpty() && !CBsymmetric->isChecked()) + return; +kdDebug(2100)<<"Selected Key:"<isChecked()) + returnOptions<<"--always-trust"; + if (CBarmor->isChecked()) + returnOptions<<"--armor"; + if (CBhideid->isChecked()) + returnOptions<<"--throw-keyid"; + /*if ((KGpgSettings::allowCustomEncryptionOptions()) && (!customOptions.stripWhiteSpace().isEmpty())) + returnOptions.operator+ (QStringList::split(QString(" "),customOptions.simplifyWhiteSpace()));*/ + //hide(); + +//MODIFIED for kopete + if (fmode) + emit selectedKey(selectedKeys.first(),QString(),CBshred->isChecked(),CBsymmetric->isChecked()); + else + emit selectedKey(selectedKeys.first(),QString(),false,CBsymmetric->isChecked()); + accept(); +} + +#include "popuppublic.moc" diff --git a/kopete/plugins/cryptography/popuppublic.h b/kopete/plugins/cryptography/popuppublic.h new file mode 100644 index 00000000..7e147385 --- /dev/null +++ b/kopete/plugins/cryptography/popuppublic.h @@ -0,0 +1,78 @@ +//File Imported from KGPG ( 2004 - 09 - 03 ) + +/*************************************************************************** + popuppublic.h - description + ------------------- + begin : Sat Jun 29 2002 + copyright : (C) 2002 by Jean-Baptiste Mardelle + email : bj@altern.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. * + * * + ***************************************************************************/ +#ifndef POPUPPUBLIC_H +#define POPUPPUBLIC_H + +#include + +//#include +#include + + +class QPushButton; +class QCheckBox; +class KListView; +class QButtonGroup; +class KProcIO; + +class popupPublic : public KDialogBase //QDialog +{ + Q_OBJECT +public: + + popupPublic(QWidget *parent=0, const char *name=0,QString sfile="",bool filemode=false,KShortcut goDefaultKey=QKeySequence(CTRL+Qt::Key_Home)); + ~popupPublic(); + KListView *keysList; + QCheckBox *CBarmor,*CBuntrusted,*CBshred,*CBsymmetric,*CBhideid; + bool fmode,trusted; + QPixmap keyPair,keySingle,keyGroup; + QString seclist; + QStringList untrustedList; + +private: + KConfig *config; + QButtonGroup *boutonboxoptions; + QString customOptions; + +private slots: + void customOpts(const QString &); + void slotprocread(KProcIO *); + void slotpreselect(); + void refreshkeys(); + void refresh(bool state); + void isSymetric(bool state); + void sort(); + void enable(); + void slotGotoDefaultKey(); + +public slots: +void slotAccept(); +void slotSetVisible(); + +protected slots: +virtual void slotOk(); + +signals: + void selectedKey(QString & ,QString,bool,bool); + void keyListFilled(); + +}; + +#endif // POPUPPUBLIC_H + diff --git a/kopete/plugins/highlight/Makefile.am b/kopete/plugins/highlight/Makefile.am new file mode 100644 index 00000000..d74a3886 --- /dev/null +++ b/kopete/plugins/highlight/Makefile.am @@ -0,0 +1,21 @@ +METASOURCES = AUTO + +SUBDIRS = icons + +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kopete_highlight.la kcm_kopete_highlight.la + +kopete_highlight_la_SOURCES = highlightplugin.cpp highlightconfig.cpp filter.cpp +kopete_highlight_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kopete_highlight_la_LIBADD = ../../libkopete/libkopete.la + +kcm_kopete_highlight_la_SOURCES = highlightprefsbase.ui highlightpreferences.cpp filter.cpp highlightconfig.cpp +kcm_kopete_highlight_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_highlight_la_LIBADD = $(LIB_KOPETECOMPAT) $(LIB_KUTILS) + +service_DATA = kopete_highlight.desktop +servicedir = $(kde_servicesdir) + +kcm_DATA = kopete_highlight_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog diff --git a/kopete/plugins/highlight/filter.cpp b/kopete/plugins/highlight/filter.cpp new file mode 100644 index 00000000..814bd678 --- /dev/null +++ b/kopete/plugins/highlight/filter.cpp @@ -0,0 +1,32 @@ +/*************************************************************************** + filter.cpp - filter for the highlight plugin + ------------------- + begin : mar 14 2003 + copyright : (C) 2003 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#include "filter.h" + +Filter::Filter() +{ +} + +Filter::~Filter() +{ +} + +/* + * But is this file useful? :-D + */ + + diff --git a/kopete/plugins/highlight/filter.h b/kopete/plugins/highlight/filter.h new file mode 100644 index 00000000..b2ac0794 --- /dev/null +++ b/kopete/plugins/highlight/filter.h @@ -0,0 +1,54 @@ +/*************************************************************************** + filter.h - filter for the highlight plugin + ------------------- + begin : mar 14 2003 + copyright : (C) 2003 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef FILTER_H +#define FILTER_H + + +#include +#include + +/** + * @author Olivier Goffart + **/ +class Filter +{ +public: + Filter(); + ~Filter(); + + QString displayName; + QString search; + bool caseSensitive; + bool isRegExp; + + bool setImportance; + unsigned int importance; + + bool setFG; + QColor FG; + + bool setBG; + QColor BG; + + bool playSound; + QString soundFN; + + bool raiseView; +}; + +#endif diff --git a/kopete/plugins/highlight/highlightconfig.cpp b/kopete/plugins/highlight/highlightconfig.cpp new file mode 100644 index 00000000..ba97e6a8 --- /dev/null +++ b/kopete/plugins/highlight/highlightconfig.cpp @@ -0,0 +1,206 @@ +/* + highlightconfig.cpp + + Copyright (c) 2003 by Olivier Goffart + Copyright (c) 2003 by Matt Rogers + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "filter.h" +#include "highlightconfig.h" + + +HighlightConfig::HighlightConfig() +{ + load(); + m_filters.setAutoDelete(true); +} + +HighlightConfig::~HighlightConfig() +{ + m_filters.clear(); +} + +void HighlightConfig::removeFilter(Filter *f) +{ + //m_filters is "autodelete (true) so when we use remove(...) it deleted f + //so don't use (delete (f) after otherwise ot crash + m_filters.remove(f); +} + +void HighlightConfig::appendFilter(Filter *f) +{ + m_filters.append(f); +} + +QPtrList HighlightConfig::filters() const +{ + return m_filters; +} + +Filter* HighlightConfig::newFilter() +{ + Filter *filtre=new Filter(); + filtre->caseSensitive=false; + filtre->isRegExp=false; + filtre->setImportance=false; + filtre->importance=1; + filtre->setBG=false; + filtre->setFG=false; + filtre->playSound=false; + filtre->raiseView=false; + filtre->displayName=i18n("-New filter-"); + m_filters.append(filtre); + return filtre; +} + +void HighlightConfig::load() +{ + m_filters.clear(); //clear filters + + QString filename = locateLocal( "appdata", QString::fromLatin1( "highlight.xml" ) ); + if( filename.isEmpty() ) + return ; + + QDomDocument filterList( QString::fromLatin1( "highlight-plugin" ) ); + + QFile filterListFile( filename ); + filterListFile.open( IO_ReadOnly ); + filterList.setContent( &filterListFile ); + + QDomElement list = filterList.documentElement(); + + QDomNode node = list.firstChild(); + while( !node.isNull() ) + { + QDomElement element = node.toElement(); + if( !element.isNull() ) + { +// if( element.tagName() == QString::fromLatin1("filter") +// { + Filter *filtre=newFilter(); + QDomNode filterNode = node.firstChild(); + + while( !filterNode.isNull() ) + { + QDomElement filterElement = filterNode.toElement(); + if( !filterElement.isNull() ) + { + if( filterElement.tagName() == QString::fromLatin1( "display-name" ) ) + { + filtre->displayName = filterElement.text(); + } + else if( filterElement.tagName() == QString::fromLatin1( "search" ) ) + { + filtre->search = filterElement.text(); + + filtre->caseSensitive= ( filterElement.attribute( QString::fromLatin1( "caseSensitive" ), QString::fromLatin1( "1" ) ) == QString::fromLatin1( "1" ) ); + filtre->isRegExp= ( filterElement.attribute( QString::fromLatin1( "regExp" ), QString::fromLatin1( "0" ) ) == QString::fromLatin1( "1" ) ); + } + else if( filterElement.tagName() == QString::fromLatin1( "FG" ) ) + { + filtre->FG = filterElement.text(); + filtre->setFG= ( filterElement.attribute( QString::fromLatin1( "set" ), QString::fromLatin1( "0" ) ) == QString::fromLatin1( "1" ) ); + } + else if( filterElement.tagName() == QString::fromLatin1( "BG" ) ) + { + filtre->BG = filterElement.text(); + filtre->setBG= ( filterElement.attribute( QString::fromLatin1( "set" ), QString::fromLatin1( "0" ) ) == QString::fromLatin1( "1" ) ); + } + else if( filterElement.tagName() == QString::fromLatin1( "importance" ) ) + { + filtre->importance = filterElement.text().toUInt(); + filtre->setImportance= ( filterElement.attribute( QString::fromLatin1( "set" ), QString::fromLatin1( "0" ) ) == QString::fromLatin1( "1" ) ); + } + else if( filterElement.tagName() == QString::fromLatin1( "sound" ) ) + { + filtre->soundFN = filterElement.text(); + filtre->playSound = ( filterElement.attribute( QString::fromLatin1( "set" ), QString::fromLatin1( "0" ) ) == QString::fromLatin1( "1" ) ); + } + else if( filterElement.tagName() == QString::fromLatin1( "raise" ) ) + { + filtre->raiseView = ( filterElement.attribute( QString::fromLatin1( "set" ), QString::fromLatin1( "0" ) ) == QString::fromLatin1( "1" ) ); + } + } + filterNode = filterNode.nextSibling(); + } +// } + } + node = node.nextSibling(); + } + filterListFile.close(); +} + +void HighlightConfig::save() +{ + + QString fileName = locateLocal( "appdata", QString::fromLatin1( "highlight.xml" ) ); + + KSaveFile file( fileName ); + if( file.status() == 0 ) + { + QTextStream *stream = file.textStream(); + stream->setEncoding( QTextStream::UnicodeUTF8 ); + + QString xml = QString::fromLatin1( + "\n" + "\n" + "\n" ); + + // Save metafilter information. + QPtrListIterator filtreIt( m_filters ); + for( ; filtreIt.current(); ++filtreIt ) + { + Filter *filtre = *filtreIt; + xml += QString::fromLatin1( " \n " ) + + QStyleSheet::escape(filtre->displayName) + + QString::fromLatin1( "\n" ); + + xml += QString::fromLatin1(" ( filtre->caseSensitive ) ) + + QString::fromLatin1("\" regExp=\"") + QString::number( static_cast( filtre->isRegExp ) ) + + QString::fromLatin1( "\">" ) + QStyleSheet::escape( filtre->search ) + QString::fromLatin1( "\n" ); + + xml += QString::fromLatin1(" ( filtre->setBG ) ) + + QString::fromLatin1( "\">" ) + QStyleSheet::escape( filtre->BG.name() ) + QString::fromLatin1( "\n" ); + xml += QString::fromLatin1(" ( filtre->setFG ) ) + + QString::fromLatin1( "\">" ) + QStyleSheet::escape( filtre->FG.name() ) + QString::fromLatin1( "\n" ); + + xml += QString::fromLatin1(" ( filtre->setImportance ) ) + + QString::fromLatin1( "\">" ) + QString::number( filtre->importance ) + QString::fromLatin1( "\n" ); + + xml += QString::fromLatin1(" ( filtre->playSound ) ) + + QString::fromLatin1( "\">" ) + QStyleSheet::escape( filtre->soundFN ) + QString::fromLatin1( "\n" ); + + xml += QString::fromLatin1(" ( filtre->raiseView ) ) + + QString::fromLatin1( "\">\n" ); + + xml += QString::fromLatin1( " \n" ); + } + + xml += QString::fromLatin1( "\n" ); + + *stream << xml; + } +} + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/highlight/highlightconfig.h b/kopete/plugins/highlight/highlightconfig.h new file mode 100644 index 00000000..35813403 --- /dev/null +++ b/kopete/plugins/highlight/highlightconfig.h @@ -0,0 +1,46 @@ +/* + highlightconfig.h + + Copyright (c) 2003 by Olivier Goffart + Copyright (c) 2003 by Matt Rogers + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef HIGHLIGHTCONFIG_H +#define HIGHLIGHTCONFIG_H + +#include +#include + +class Filter; + +class HighlightConfig +{ +public: + HighlightConfig(); + ~HighlightConfig(); + + void load(); + void save(); + + QPtrList filters() const; + void removeFilter (Filter *f); + void appendFilter (Filter *f); + Filter* newFilter(); + +private: + QPtrList m_filters; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/highlight/highlightplugin.cpp b/kopete/plugins/highlight/highlightplugin.cpp new file mode 100644 index 00000000..2f1cbb43 --- /dev/null +++ b/kopete/plugins/highlight/highlightplugin.cpp @@ -0,0 +1,106 @@ +/*************************************************************************** + highlightplugin.cpp - description + ------------------- + begin : mar 14 2003 + copyright : (C) 2003 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#include +#include +#include + +#include "kopetechatsessionmanager.h" +#include "kopeteview.h" + +#include "filter.h" +#include "highlightplugin.h" +#include "highlightconfig.h" + +typedef KGenericFactory HighlightPluginFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_highlight, HighlightPluginFactory( "kopete_highlight" ) ) + +HighlightPlugin::HighlightPlugin( QObject *parent, const char *name, const QStringList &/*args*/ ) +: Kopete::Plugin( HighlightPluginFactory::instance(), parent, name ) +{ + if( !pluginStatic_ ) + pluginStatic_=this; + + connect( Kopete::ChatSessionManager::self(), SIGNAL( aboutToDisplay( Kopete::Message & ) ), SLOT( slotIncomingMessage( Kopete::Message & ) ) ); + connect ( this , SIGNAL( settingsChanged() ) , this , SLOT( slotSettingsChanged() ) ); + + m_config = new HighlightConfig; + + m_config->load(); +} + +HighlightPlugin::~HighlightPlugin() +{ + pluginStatic_ = 0L; + delete m_config; +} + +HighlightPlugin* HighlightPlugin::plugin() +{ + return pluginStatic_ ; +} + +HighlightPlugin* HighlightPlugin::pluginStatic_ = 0L; + + +void HighlightPlugin::slotIncomingMessage( Kopete::Message& msg ) +{ + if(msg.direction() != Kopete::Message::Inbound) + return; // FIXME: highlighted internal/actions messages are not showed correctly in the chat window (bad style) + // but they should maybe be highlinghted if needed + + QPtrList filters=m_config->filters(); + QPtrListIterator it( filters ); + Filter *f; + while ((f = it.current()) != 0 ) + { + ++it; + if(f->isRegExp ? + msg.plainBody().contains(QRegExp(f->search , f->caseSensitive)) : + msg.plainBody().contains(f->search , f->caseSensitive) ) + { + if(f->setBG) + msg.setBg(f->BG); + if(f->setFG) + msg.setFg(f->FG); + if(f->setImportance) + msg.setImportance((Kopete::Message::MessageImportance)f->importance); + if(f->playSound) + KNotifyClient::userEvent (QString::null, KNotifyClient::Sound, KNotifyClient::Default, f->soundFN ); + + if (f->raiseView && + msg.manager() && msg.manager()->view()) { + KopeteView *theview = msg.manager()->view(); + theview->raise(); + } + + break; //uh? + } + } +} + +void HighlightPlugin::slotSettingsChanged() +{ + m_config->load(); +} + + + +#include "highlightplugin.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/highlight/highlightplugin.h b/kopete/plugins/highlight/highlightplugin.h new file mode 100644 index 00000000..0a421f55 --- /dev/null +++ b/kopete/plugins/highlight/highlightplugin.h @@ -0,0 +1,63 @@ +/*************************************************************************** + highlightplugin.h - description + ------------------- + begin : mar 14 2003 + copyright : (C) 2003 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef HighlightPLUGIN_H +#define HighlightPLUGIN_H + +#include +#include +#include + +#include "kopetemessage.h" +#include "kopeteplugin.h" + +class QStringList; +class QString; +class QTimer; + +namespace Kopete { class Message; } +namespace Kopete { class MetaContact; } +namespace Kopete { class ChatSession; } + +class HighlightConfig; +class Filter; + +/** + * @author Olivier Goffart + */ + +class HighlightPlugin : public Kopete::Plugin +{ + Q_OBJECT + +public: + static HighlightPlugin *plugin(); + + HighlightPlugin( QObject *parent, const char *name, const QStringList &args ); + ~HighlightPlugin(); + +public slots: + void slotIncomingMessage( Kopete::Message& msg ); + void slotSettingsChanged(); + + +private: + static HighlightPlugin* pluginStatic_; + HighlightConfig *m_config; +}; + +#endif diff --git a/kopete/plugins/highlight/highlightpreferences.cpp b/kopete/plugins/highlight/highlightpreferences.cpp new file mode 100644 index 00000000..9641d034 --- /dev/null +++ b/kopete/plugins/highlight/highlightpreferences.cpp @@ -0,0 +1,269 @@ +/*************************************************************************** + highlightpreferences.cpp - description + ------------------- + begin : mar 14 2003 + copyright : (C) 2003 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "filter.h" +#include "highlightplugin.h" +#include "highlightconfig.h" +#include "highlightprefsbase.h" +#include "highlightpreferences.h" + +typedef KGenericFactory HighlightPreferencesFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_highlight, HighlightPreferencesFactory( "kcm_kopete_highlight" ) ) + +HighlightPreferences::HighlightPreferences(QWidget *parent, const char* /*name*/, const QStringList &args) + : KCModule(HighlightPreferencesFactory::instance(), parent, args) +{ + donttouch=true; + ( new QVBoxLayout( this ) )->setAutoAdd( true ); + preferencesDialog = new HighlightPrefsUI(this); + m_config = new HighlightConfig; + + connect(preferencesDialog->m_list , SIGNAL(selectionChanged()) , this , SLOT(slotCurrentFilterChanged())); + connect(preferencesDialog->m_list , SIGNAL(doubleClicked ( QListViewItem *, const QPoint &, int )) , this , SLOT(slotRenameFilter())); + connect(preferencesDialog->m_add , SIGNAL(pressed()) , this , SLOT(slotAddFilter())); + connect(preferencesDialog->m_remove , SIGNAL(pressed()) , this , SLOT(slotRemoveFilter())); + connect(preferencesDialog->m_rename , SIGNAL(pressed()) , this , SLOT(slotRenameFilter())); + connect(preferencesDialog->m_editregexp , SIGNAL(pressed()) , this , SLOT(slotEditRegExp())); + + //Maybe here i should use a slot per widget, but i am too lazy + connect(preferencesDialog->m_case , SIGNAL(stateChanged(int)) , this , SLOT(slotSomethingHasChanged())); + connect(preferencesDialog->m_regexp , SIGNAL(stateChanged(int)) , this , SLOT(slotSomethingHasChanged())); + connect(preferencesDialog->m_setImportance , SIGNAL(stateChanged(int)) , this , SLOT(slotSomethingHasChanged())); + connect(preferencesDialog->m_setBG , SIGNAL(stateChanged(int)) , this , SLOT(slotSomethingHasChanged())); + connect(preferencesDialog->m_setFG , SIGNAL(stateChanged(int)) , this , SLOT(slotSomethingHasChanged())); + connect(preferencesDialog->m_search , SIGNAL(textChanged(const QString&)) , this , SLOT(slotSomethingHasChanged())); + connect(preferencesDialog->m_sound , SIGNAL(stateChanged(int)) , this , SLOT(slotSomethingHasChanged())); + connect(preferencesDialog->m_soundFN , SIGNAL(textChanged(const QString&)) , this , SLOT(slotSomethingHasChanged())); + connect(preferencesDialog->m_raise , SIGNAL(stateChanged(int)) , this , SLOT(slotSomethingHasChanged())); + connect(preferencesDialog->m_search , SIGNAL(textChanged(const QString&)) , this , SLOT(slotSomethingHasChanged())); + connect(preferencesDialog->m_importance , SIGNAL(activated(int)) , this , SLOT(slotSomethingHasChanged())); + connect(preferencesDialog->m_FG , SIGNAL(changed(const QColor&)) , this , SLOT(slotSomethingHasChanged())); + connect(preferencesDialog->m_BG , SIGNAL(changed(const QColor&)) , this , SLOT(slotSomethingHasChanged())); + + load(); + donttouch=false; +} + +HighlightPreferences::~HighlightPreferences() +{ + delete m_config; +} + +void HighlightPreferences::load() +{ + m_config->load(); + donttouch=true; + preferencesDialog->m_list->clear(); + m_filterItems.clear(); + + QPtrList filters=m_config->filters(); + QPtrListIterator it( filters ); + Filter *f; + bool first=true; + while ( (f=it.current()) != 0 ) + { + ++it; + QListViewItem* lvi= new QListViewItem(preferencesDialog->m_list); + lvi->setText(0,f->displayName ); + m_filterItems.insert(lvi,f); + if(first) + preferencesDialog->m_list->setSelected(lvi, true); + first=false; + } + donttouch=false; + emit KCModule::changed(false); +} + +void HighlightPreferences::save() +{ + m_config->save(); + emit KCModule::changed(false); +} + + +void HighlightPreferences::slotCurrentFilterChanged() +{ + donttouch=true; + Filter *current; + if(!preferencesDialog->m_list->selectedItem() || !(current=m_filterItems[preferencesDialog->m_list->selectedItem()])) + { + preferencesDialog->m_search->setEnabled(false); + preferencesDialog->m_case->setEnabled(false); + preferencesDialog->m_regexp->setEnabled(false); + preferencesDialog->m_importance->setEnabled(false); + preferencesDialog->m_setImportance->setEnabled(false); + preferencesDialog->m_BG->setEnabled(false); + preferencesDialog->m_setBG->setEnabled(false); + preferencesDialog->m_FG->setEnabled(false); + preferencesDialog->m_setFG->setEnabled(false); + preferencesDialog->m_soundFN->setEnabled(false); + preferencesDialog->m_sound->setEnabled(false); + preferencesDialog->m_raise->setEnabled(false); + preferencesDialog->m_editregexp->setEnabled(false); + preferencesDialog->m_rename->setEnabled(false); + preferencesDialog->m_remove->setEnabled(false); + donttouch=false; + return; + } + + preferencesDialog->m_rename->setEnabled(true); + preferencesDialog->m_remove->setEnabled(true); + + preferencesDialog->m_search->setEnabled(true); + preferencesDialog->m_case->setEnabled(true); + preferencesDialog->m_regexp->setEnabled(true); + preferencesDialog->m_setImportance->setEnabled(true); + preferencesDialog->m_setBG->setEnabled(true); + preferencesDialog->m_setFG->setEnabled(true); + preferencesDialog->m_sound->setEnabled(true); + preferencesDialog->m_raise->setEnabled(true); + + + preferencesDialog->m_search->setText(current->search); + preferencesDialog->m_case->setChecked(current->caseSensitive); + preferencesDialog->m_regexp->setChecked(current->isRegExp); + preferencesDialog->m_editregexp->setEnabled(current->isRegExp); + preferencesDialog->m_importance->setCurrentItem(current->importance); + preferencesDialog->m_setImportance->setChecked(current->setImportance); + preferencesDialog->m_importance->setEnabled(current->setImportance); + preferencesDialog->m_BG->setColor(current->BG); + preferencesDialog->m_setBG->setChecked(current->setBG); + preferencesDialog->m_BG->setEnabled(current->setBG); + preferencesDialog->m_FG->setColor(current->FG); + preferencesDialog->m_setFG->setChecked(current->setFG); + preferencesDialog->m_FG->setEnabled(current->setFG); + preferencesDialog->m_soundFN->setURL(current->soundFN); + preferencesDialog->m_sound->setChecked(current->playSound); + preferencesDialog->m_raise->setChecked(current->raiseView); + preferencesDialog->m_soundFN->setEnabled(current->playSound); + + donttouch=false; +} + +void HighlightPreferences::slotAddFilter() +{ + Filter *filtre=m_config->newFilter(); + QListViewItem* lvi= new QListViewItem(preferencesDialog->m_list); + lvi->setText(0,filtre->displayName ); + m_filterItems.insert(lvi,filtre); + preferencesDialog->m_list->setSelected(lvi, true); +} + +void HighlightPreferences::slotRemoveFilter() +{ + QListViewItem *lvi=preferencesDialog->m_list->selectedItem(); + if(!lvi) + return; + Filter *current=m_filterItems[lvi]; + if(!current) + return; + + m_filterItems.remove(lvi); + delete lvi; + m_config->removeFilter(current); + emit KCModule::changed(true); +} + +void HighlightPreferences::slotRenameFilter() +{ + QListViewItem *lvi=preferencesDialog->m_list->selectedItem(); + if(!lvi) + return; + Filter *current=m_filterItems[lvi]; + if(!current) + return; + + bool ok; + QString newname = KInputDialog::getText( + i18n( "Rename Filter" ), i18n( "Please enter the new name for the filter:" ), current->displayName, &ok ); + if( !ok ) + return; + current->displayName=newname; + lvi->setText(0,newname); + emit KCModule::changed(true); +} + + +void HighlightPreferences::slotSomethingHasChanged() +{ + Filter *current; + if(donttouch || !preferencesDialog->m_list->selectedItem() || !(current=m_filterItems[preferencesDialog->m_list->selectedItem()])) + return; + + current->search=preferencesDialog->m_search->text(); + current->caseSensitive=preferencesDialog->m_case->isChecked(); + current->isRegExp=preferencesDialog->m_regexp->isChecked(); + preferencesDialog->m_editregexp->setEnabled(current->isRegExp); + current->importance=preferencesDialog->m_importance->currentItem(); + current->setImportance=preferencesDialog->m_setImportance->isChecked(); + preferencesDialog->m_importance->setEnabled(current->setImportance); + current->BG=preferencesDialog->m_BG->color(); + current->setBG=preferencesDialog->m_setBG->isChecked(); + preferencesDialog->m_BG->setEnabled(current->setBG); + current->FG=preferencesDialog->m_FG->color(); + current->setFG=preferencesDialog->m_setFG->isChecked(); + preferencesDialog->m_FG->setEnabled(current->setFG); + current->soundFN=preferencesDialog->m_soundFN->url(); + current->playSound=preferencesDialog->m_sound->isChecked(); + preferencesDialog->m_soundFN->setEnabled(current->playSound); + current->raiseView=preferencesDialog->m_raise->isChecked(); + + emit KCModule::changed(true); +} + +void HighlightPreferences::slotEditRegExp() +{ + QDialog *editorDialog = KParts::ComponentFactory::createInstanceFromQuery( "KRegExpEditor/KRegExpEditor" ); + if ( editorDialog ) + { + // kdeutils was installed, so the dialog was found fetch the editor interface + KRegExpEditorInterface *editor = static_cast( editorDialog->qt_cast( "KRegExpEditorInterface" ) ); + Q_ASSERT( editor ); // This should not fail! + // now use the editor. + editor->setRegExp(preferencesDialog->m_search->text()); + + // Finally exec the dialog + if(editorDialog->exec() == QDialog::Accepted ) + { + preferencesDialog->m_search->setText(editor->regExp()); + } + + } + else + { + // Don't offer the dialog. + } +} + +#include "highlightpreferences.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/highlight/highlightpreferences.h b/kopete/plugins/highlight/highlightpreferences.h new file mode 100644 index 00000000..a2c7e31b --- /dev/null +++ b/kopete/plugins/highlight/highlightpreferences.h @@ -0,0 +1,58 @@ +/*************************************************************************** + highlightpreferences.h - description + ------------------- + begin : mar 14 2003 + copyright : (C) 2003 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef HighlightPREFERENCES_H +#define HighlightPREFERENCES_H + +#include +#include + +class HighlightPrefsUI; +class Filter; +class QListViewItem; + +/** + *@author Olivier Goffart + */ + +class HighlightPreferences : public KCModule { + Q_OBJECT +public: + + HighlightPreferences(QWidget *parent = 0, const char* name = 0, const QStringList &args = QStringList()); + ~HighlightPreferences(); + + virtual void save(); + virtual void load(); + +private: + HighlightPrefsUI *preferencesDialog; + HighlightConfig *m_config; + QMap m_filterItems; + + bool donttouch; + +private slots: + void slotCurrentFilterChanged(); + void slotAddFilter(); + void slotRemoveFilter(); + void slotRenameFilter(); + void slotSomethingHasChanged(); + void slotEditRegExp(); +}; + +#endif diff --git a/kopete/plugins/highlight/highlightprefsbase.ui b/kopete/plugins/highlight/highlightprefsbase.ui new file mode 100644 index 00000000..b8150c56 --- /dev/null +++ b/kopete/plugins/highlight/highlightprefsbase.ui @@ -0,0 +1,466 @@ + +HighlightPrefsUI +Olivier Goffart + + + HighlighPrefsUI + + + + 0 + 0 + 513 + 504 + + + + HighlighPrefsUI + + + + unnamed + + + + groupBox1 + + + + 1 + 3 + 0 + 0 + + + + Available Filters + + + + unnamed + + + + m_add + + + Add + + + + + m_remove + + + false + + + Remove + + + + + m_rename + + + false + + + Rename... + + + + + + Filters + + + true + + + true + + + + m_list + + + + 7 + 7 + 0 + 0 + + + + + + + + groupBox2 + + + + 7 + 5 + 0 + 0 + + + + Criteria + + + + unnamed + + + + textLabel1 + + + + 1 + 0 + 0 + 0 + + + + If the message contains: + + + + + m_search + + + + + layout3 + + + + unnamed + + + + m_regexp + + + Regular expression + + + + + m_editregexp + + + + 5 + 0 + 0 + 0 + + + + Edit... + + + + + spacer3 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + + + m_case + + + Case sensitive + + + + + + + buttonGroup2 + + + + 7 + 5 + 0 + 0 + + + + Action + + + + unnamed + + + + layout4 + + + + unnamed + + + + m_setImportance + + + + 1 + 0 + 0 + 0 + + + + Set the message importance to: + + + + + + Low + + + + + Normal + + + + + Highlight + + + + m_importance + + + + 3 + 0 + 0 + 0 + + + + + + spacer4 + + + Horizontal + + + Expanding + + + + 31 + 21 + + + + + + + + layout5 + + + + unnamed + + + + m_setBG + + + Change the background color to: + + + + + m_BG + + + + 5 + 0 + 0 + 0 + + + + + + + + + spacer5 + + + Horizontal + + + Expanding + + + + 31 + 21 + + + + + + + + layout6 + + + + unnamed + + + + m_setFG + + + Change the foreground color to: + + + + + m_FG + + + + 5 + 0 + 0 + 0 + + + + + + + + + spacer6 + + + Horizontal + + + Expanding + + + + 41 + 21 + + + + + + + + layout7 + + + + unnamed + + + + m_sound + + + Play a sound: + + + + + m_soundFN + + + + + + + layout8 + + + + unnamed + + + + m_raise + + + Raise window + + + + + + + + + + m_list + m_add + m_remove + m_rename + m_search + m_regexp + m_editregexp + m_case + m_setImportance + m_importance + m_setBG + m_BG + m_setFG + m_FG + m_sound + m_soundFN + m_raise + + + + klistview.h + klineedit.h + kcombobox.h + kcolorbutton.h + kcolorbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + + diff --git a/kopete/plugins/highlight/icons/Makefile.am b/kopete/plugins/highlight/icons/Makefile.am new file mode 100644 index 00000000..224eb420 --- /dev/null +++ b/kopete/plugins/highlight/icons/Makefile.am @@ -0,0 +1,3 @@ +kopeteicondir = $(kde_datadir)/kopete/icons +kopeteicon_ICON = AUTO + diff --git a/kopete/plugins/highlight/icons/cr32-app-highlight.png b/kopete/plugins/highlight/icons/cr32-app-highlight.png new file mode 100644 index 00000000..54f6736d Binary files /dev/null and b/kopete/plugins/highlight/icons/cr32-app-highlight.png differ diff --git a/kopete/plugins/highlight/kopete_highlight.desktop b/kopete/plugins/highlight/kopete_highlight.desktop new file mode 100644 index 00000000..0d43c122 --- /dev/null +++ b/kopete/plugins/highlight/kopete_highlight.desktop @@ -0,0 +1,129 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=highlight +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_highlight +X-KDE-PluginInfo-Author=Olivier Goffart +X-KDE-PluginInfo-Email=ogoffart@tiscalinet.be +X-KDE-PluginInfo-Name=kopete_highlight +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Highlight +Name[ar]=تمييز +Name[bg]=ОткроÑване +Name[bn]=গà§à¦°à§à¦¤à§à¦¬à¦ªà§‚রà§à¦£ +Name[br]=Splannadur +Name[bs]=Isticanje +Name[ca]=Ressaltat +Name[cs]=ZvýraznÄ›ní +Name[cy]=Amlygu +Name[da]=Fremhæv +Name[de]=Hervorhebung +Name[el]=Τονισμός +Name[eo]=Lumaĵo +Name[es]=Resaltar +Name[et]=Esiletõstmine +Name[eu]=Nabarmendu +Name[fa]=مشخص +Name[fi]=Korostus +Name[fr]=Surlignement +Name[ga]=Aibhsiú +Name[gl]=Resaltar +Name[he]=מודגש +Name[hi]=उभारें +Name[hr]=Isticanje +Name[hu]=Kiemelés +Name[is]=Merkja +Name[it]=Evidenziazione +Name[ja]=強調 +Name[ka]=მáƒáƒ áƒ™áƒ˜áƒ áƒ”ბული +Name[kk]=Ерекше +Name[km]=សំážáž¶áž“់ +Name[lt]=ParyÅ¡kinti +Name[mk]=ОÑветлување +Name[nb]=Marker +Name[nds]=Rutheven +Name[ne]=हाइलाइट +Name[nl]=Aanwijzen +Name[nn]=Marker +Name[pa]=ਉਘਾੜਨ +Name[pl]=PodÅ›wietlenie +Name[pt]=Realce +Name[pt_BR]=Destaque +Name[ro]=EvidenÅ£iat +Name[ru]=Выделение +Name[se]=Merke +Name[sk]=ZvýrazniÅ¥ +Name[sl]=Poudarjeno sporoÄilo +Name[sr]=ИÑтицање +Name[sr@Latn]=Isticanje +Name[sv]=Markera +Name[ta]=à®®à¯à®©à¯ˆà®ªà¯à®ªà¯à®±à¯à®¤à¯à®¤à®²à¯ +Name[tg]=Равшаннамоӣ +Name[tr]=Vurgu +Name[uk]=ПідÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ +Name[wa]=E sorbiyance +Name[zh_CN]=çªå‡ºæ˜¾ç¤º +Name[zh_HK]=加強顯示 +Name[zh_TW]=高亮度 +Comment=Highlight messages +Comment[ar]=الرسائل المميزة +Comment[bg]=ОткроÑване на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ +Comment[bn]=বারà§à¦¤à¦¾ পà§à¦°à¦œà§à¦œà§à¦¬à¦²à¦¨ +Comment[bs]=Istakni poruke +Comment[ca]=Missatges a ressaltar +Comment[cs]=Zvýraznit zprávy +Comment[cy]=Amlygu negeseuon +Comment[da]=Fremhæv beskeder +Comment[de]=Hervorhebung von Nachrichten +Comment[el]=Τονισμός μηνυμάτων +Comment[eo]=Elstarigi mesaÄojn +Comment[es]=Resaltar mensajes +Comment[et]=Sõnumite esiletõstmine +Comment[eu]=Nabarmendu mezuak +Comment[fa]=پیامهای مشخص +Comment[fi]=Korosta viestit +Comment[fr]=Surligner les messages +Comment[ga]=Aibhsigh teachtaireachtaí +Comment[gl]=Resaltar mensaxes +Comment[he]=מדגיש הודעות +Comment[hi]=संदेशों को उभारें +Comment[hr]=Isticanje poruka +Comment[hu]=Szövegkiemelés +Comment[is]=Merkja skeyti +Comment[it]=Evidenzia i messaggi +Comment[ja]=メッセージを強調 +Comment[ka]=მáƒáƒ áƒ™áƒ˜áƒ áƒ”ბული შეტყáƒáƒ‘ინებრ+Comment[kk]=Хабарларды боÑулау +Comment[km]=សារ​សំážáž¶áž“់ +Comment[lt]=ParyÅ¡kinti žinutes +Comment[mk]=Обележете пораки +Comment[nb]=Marker meldinger +Comment[nds]=Narichten rutheven +Comment[ne]=सनà¥à¤¦à¥‡à¤¶ हाइलाइट गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Laat berichten oplichten +Comment[nn]=Marker meldingar +Comment[pl]=PodÅ›wietlanie wiadomoÅ›ci +Comment[pt]=Realçar as mensagens +Comment[pt_BR]=Destaca mensagens +Comment[ro]=EvidenÅ£iază mesajele +Comment[ru]=ПодÑвечивание Ñообщений +Comment[se]=Merke dieÄ‘uid +Comment[sk]=Zvýraznenie správ +Comment[sl]=Poudarjanje sporoÄil +Comment[sr]=ИÑтицање порука +Comment[sr@Latn]=Isticanje poruka +Comment[sv]=Markera meddelanden +Comment[ta]=தகவல௠தனிபà¯à®ªà®Ÿà¯à®¤à¯à®¤à®²à¯ +Comment[tg]=РавшанÑозии пайёмҳо +Comment[tr]=Vurgulanmış mesajlar +Comment[uk]=ПідÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ +Comment[wa]=Mete les messaedjes e sorbriyance +Comment[zh_CN]=çªå‡ºæ˜¾ç¤ºæ¶ˆæ¯ +Comment[zh_HK]=將訊æ¯åŠ å¼·é¡¯ç¤º +Comment[zh_TW]=é«˜äº®åº¦è¨Šæ¯ diff --git a/kopete/plugins/highlight/kopete_highlight_config.desktop b/kopete/plugins/highlight/kopete_highlight_config.desktop new file mode 100644 index 00000000..ae29a289 --- /dev/null +++ b/kopete/plugins/highlight/kopete_highlight_config.desktop @@ -0,0 +1,125 @@ +[Desktop Entry] +Icon=highlight +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_highlight +X-KDE-FactoryName=HighlightConfigFactory +X-KDE-ParentApp=kopete_highlight +X-KDE-ParentComponents=kopete_highlight + +Name=Highlight +Name[ar]=تمييز +Name[bg]=ОткроÑване +Name[bn]=গà§à¦°à§à¦¤à§à¦¬à¦ªà§‚রà§à¦£ +Name[br]=Splannadur +Name[bs]=Isticanje +Name[ca]=Ressaltat +Name[cs]=ZvýraznÄ›ní +Name[cy]=Amlygu +Name[da]=Fremhæv +Name[de]=Hervorhebung +Name[el]=Τονισμός +Name[eo]=Lumaĵo +Name[es]=Resaltar +Name[et]=Esiletõstmine +Name[eu]=Nabarmendu +Name[fa]=مشخص +Name[fi]=Korostus +Name[fr]=Surlignement +Name[ga]=Aibhsiú +Name[gl]=Resaltar +Name[he]=מודגש +Name[hi]=उभारें +Name[hr]=Isticanje +Name[hu]=Kiemelés +Name[is]=Merkja +Name[it]=Evidenziazione +Name[ja]=強調 +Name[ka]=მáƒáƒ áƒ™áƒ˜áƒ áƒ”ბული +Name[kk]=Ерекше +Name[km]=សំážáž¶áž“់ +Name[lt]=ParyÅ¡kinti +Name[mk]=ОÑветлување +Name[nb]=Marker +Name[nds]=Rutheven +Name[ne]=हाइलाइट +Name[nl]=Aanwijzen +Name[nn]=Marker +Name[pa]=ਉਘਾੜਨ +Name[pl]=PodÅ›wietlenie +Name[pt]=Realce +Name[pt_BR]=Destaque +Name[ro]=EvidenÅ£iat +Name[ru]=Выделение +Name[se]=Merke +Name[sk]=ZvýrazniÅ¥ +Name[sl]=Poudarjeno sporoÄilo +Name[sr]=ИÑтицање +Name[sr@Latn]=Isticanje +Name[sv]=Markera +Name[ta]=à®®à¯à®©à¯ˆà®ªà¯à®ªà¯à®±à¯à®¤à¯à®¤à®²à¯ +Name[tg]=Равшаннамоӣ +Name[tr]=Vurgu +Name[uk]=ПідÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ +Name[wa]=E sorbiyance +Name[zh_CN]=çªå‡ºæ˜¾ç¤º +Name[zh_HK]=加強顯示 +Name[zh_TW]=高亮度 +Comment=Highlights text based on filters +Comment[ar]=تميز النصوص بناءا على التصÙية +Comment[be]=ПадÑвечвае Ñ‚ÑкÑÑ‚ згодна з фільтрамі +Comment[bg]=ОткроÑване на текÑÑ‚ в ÑъобщениÑта на базата на ключови думи +Comment[bn]=ফিলà§à¦Ÿà¦¾à¦°à§‡à¦° ওপর ভিতà§à¦¤à¦¿ করে টেকà§à¦¸à¦Ÿ পà§à¦°à¦œà§à¦œà§à¦¬à¦² করে +Comment[bs]=IstiÄe poruke na osnovu filtera +Comment[ca]=Ressalta el text basant-se en filtres +Comment[cs]=Zvýraznit text podle filtrů +Comment[cy]=Amlygu testun ar sail hidlau +Comment[da]=Fremhæv tekst baseret pÃ¥ filtre +Comment[de]=Texthervorhebung mit Hilfe von Filtern +Comment[el]=Τονίζει το κείμενο βασισμένο σε φίλτÏα +Comment[es]=Realza el texto basándose en filtros +Comment[et]=Teksti esiletõstmine filtrite põhjal +Comment[eu]=Iragazkietan oinarritutako testua nabarmentzen du +Comment[fa]=متن را بر اساس پالایه‌ها مشخص می‌کند +Comment[fi]=Korostaa tekstin suotimien perusteella +Comment[fr]=Surligne les messages qui répondent à certains critères +Comment[ga]=Aibhsíonn seo téacs de réir scagairí +Comment[gl]=Resalta texto usando filtros +Comment[he]=מדגיש ×ž×™×œ×™× ×¢×œ פי ×ž×¡× × ×™× +Comment[hi]=फ़िलà¥à¤Ÿà¤° आधारित पाठ उभारें +Comment[hr]=Isticanje poruka na osnovu filtera +Comment[hu]=Szövegkiemelés üzenetekben megadott szempontok szerint +Comment[is]=Merkir texta með tilliti til síunar +Comment[it]=Evidenzia i testi attraverso dei filtri +Comment[ja]=æ¡ä»¶ã«å¾“ã£ã¦ãƒ†ã‚­ã‚¹ãƒˆã‚’強調 +Comment[ka]=ტექსტის მáƒáƒ áƒ™áƒ˜áƒ áƒ”ბáƒáƒ¡ áƒáƒ™áƒ”თებს ფილტრების მეშვეáƒáƒ‘ით +Comment[kk]=Мәтінді Ñүзгілеп боÑулау +Comment[km]=សារ​សំážáž¶áž“់​ដែល​ផ្អែក​លើ​ážáž˜áŸ’ážšáž„ +Comment[lt]=ParyÅ¡kinamas tekstas, atsižvelgiant į filtrus +Comment[mk]=Го обележува текÑтот базирано на филтри +Comment[nb]=Marker meldinger ved bruk av filtere +Comment[nds]=Filterbaseert Rutheven vun Text +Comment[ne]=फिलà¥à¤Ÿà¤°à¤®à¤¾ आधारित पाठ हाइलाइट गरà¥à¤¦à¤› +Comment[nl]=Laat tekst oplichten gebaseerd op filters +Comment[nn]=Marker meldingar ved bruk av filter +Comment[pl]=PodÅ›wietla tekst na podstawie ustawionych filtrów +Comment[pt]=Realça o texto com base em filtros +Comment[pt_BR]=Destaca o texto baseado em filtros +Comment[ro]=EvidenÅ£iază mesajele pe baza unor filtre +Comment[ru]=ПодÑвечивание текÑта оÑновываетÑÑ Ð½Ð° фильтрах +Comment[se]=Merke teavstta silliid bokte +Comment[sk]=Zvýrazňuje text pomocou filtrov +Comment[sl]=Poudarjanje besedila glede na filtre +Comment[sr]=ИÑтицање порука на оÑнову филтера +Comment[sr@Latn]=Isticanje poruka na osnovu filtera +Comment[sv]=Markerar text baserat pÃ¥ filter +Comment[ta]=வடிகடà¯à®Ÿà®¿à®¯à¯ˆ பொரà¯à®¤à¯à®¤à¯ உரையை தனிபà¯à®ªà®Ÿà¯à®¤à¯à®¤à¯ +Comment[tg]=РавшанÑозии матн дар аÑоÑи полоÑгарҳо +Comment[tr]=Süzgeçlerde metin temelli vurgular +Comment[uk]=ПідÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚ÐµÐºÑту базуєтьÑÑ Ð½Ð° фільтрах +Comment[wa]=Mete li tecse e sorbriyance d' après les passetes +Comment[zh_CN]=æ ¹æ®è¿‡æ»¤å™¨çªå‡ºæ˜¾ç¤ºæ–‡å­— +Comment[zh_HK]=根據éŽæ¿¾å™¨å°‡è¨Šæ¯åŠ å¼·é¡¯ç¤º +Comment[zh_TW]=基於éŽæ¿¾å™¨çš„é«˜äº®åº¦è¨Šæ¯ diff --git a/kopete/plugins/history/Makefile.am b/kopete/plugins/history/Makefile.am new file mode 100644 index 00000000..765e5197 --- /dev/null +++ b/kopete/plugins/history/Makefile.am @@ -0,0 +1,26 @@ +METASOURCES = AUTO + +INCLUDES = $(KOPETE_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kopete_history.la kcm_kopete_history.la + +kopete_history_la_SOURCES = historyplugin.cpp historydialog.cpp historyviewer.ui\ + historylogger.cpp converter.cpp historyguiclient.cpp historyconfig.kcfgc + +kopete_history_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kopete_history_la_LIBADD = ../../libkopete/libkopete.la + +kcm_kopete_history_la_SOURCES = historyprefsui.ui historypreferences.cpp historyconfig.kcfgc +kcm_kopete_history_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_history_la_LIBADD = ../../libkopete/libkopete.la $(LIB_KUTILS) + +service_DATA = kopete_history.desktop +servicedir = $(kde_servicesdir) + +mydatadir = $(kde_datadir)/kopete_history +mydata_DATA = historyui.rc historychatui.rc + +kcm_DATA = kopete_history_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog + +kde_kcfg_DATA = historyconfig.kcfg diff --git a/kopete/plugins/history/converter.cpp b/kopete/plugins/history/converter.cpp new file mode 100644 index 00000000..22f662bc --- /dev/null +++ b/kopete/plugins/history/converter.cpp @@ -0,0 +1,341 @@ +//Olivier Goffart +// 2003 06 26 + +#include "historyplugin.h" //just needed because we are a member of this class + // we don't use any history function here + +/**----------------------------------------------------------- + * CONVERTER from the old kopete history. + * it port history from kopete 0.6, 0.5 and above the actual + * this should be placed in a perl script handled by KConf_update + * but i need to acess to some info i don't have with perl, like + * the accountId, to know each protocol id, and more + *-----------------------------------------------------------*/ + +#include "kopetepluginmanager.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetecontact.h" +#include "kopetemessage.h" +#include "kopeteprotocol.h" +#include "kopeteuiglobal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CBUFLENGTH 512 // buffer length for fgets() + +void HistoryPlugin::convertOldHistory() +{ + bool deleteFiles= KMessageBox::questionYesNo( Kopete::UI::Global::mainWidget(), + i18n( "Would you like to remove old history files?" ) , i18n( "History Converter" ), KStdGuiItem::del(), i18n("Keep") ) == KMessageBox::Yes; + + KProgressDialog *progressDlg=new KProgressDialog(Kopete::UI::Global::mainWidget() , "history_progress_dlg" , i18n( "History converter" ) , + QString::null , true); //modal to make sure the user will not doing stupid things (we have a kapp->processEvents()) + progressDlg->setAllowCancel(false); //because i am too lazy to allow to cancel + + + QString kopetedir=locateLocal( "data", QString::fromLatin1( "kopete")); + QDir d( kopetedir ); //d should point to ~/.kde/share/apps/kopete/ + + d.setFilter( QDir::Dirs ); + + const QFileInfoList *list = d.entryInfoList(); + QFileInfoListIterator it( *list ); + QFileInfo *fi; + while ( (fi = it.current()) != 0 ) + { + QString protocolId; + QString accountId; + + if( Kopete::Protocol *p = dynamic_cast( Kopete::PluginManager::self()->plugin( fi->fileName() ) ) ) + { + protocolId=p->pluginId(); + QDictIterator it(Kopete::AccountManager::self()->accounts(p)); + Kopete::Account *a = it.current(); + if(a) + accountId=a->accountId(); + } + + if(accountId.isNull() || protocolId.isNull()) + { + if(fi->fileName() == "MSNProtocol" || fi->fileName() == "msn_logs" ) + { + protocolId="MSNProtocol"; + KGlobal::config()->setGroup("MSN"); + accountId=KGlobal::config()->readEntry( "UserID" ); + } + else if(fi->fileName() == "ICQProtocol" || fi->fileName() == "icq_logs" ) + { + protocolId="ICQProtocol"; + KGlobal::config()->setGroup("ICQ"); + accountId=KGlobal::config()->readEntry( "UIN" ); + } + else if(fi->fileName() == "AIMProtocol" || fi->fileName() == "aim_logs" ) + { + protocolId="AIMProtocol"; + KGlobal::config()->setGroup("AIM"); + accountId=KGlobal::config()->readEntry( "UserID" ); + } + else if(fi->fileName() == "OscarProtocol" ) + { + protocolId="AIMProtocol"; + KGlobal::config()->setGroup("OSCAR"); + accountId=KGlobal::config()->readEntry( "UserID" ); + } + else if(fi->fileName() == "JabberProtocol" || fi->fileName() == "jabber_logs") + { + protocolId="JabberProtocol"; + KGlobal::config()->setGroup("Jabber"); + accountId=KGlobal::config()->readEntry( "UserID" ); + } + //TODO: gadu, wp + } + + if(!protocolId.isEmpty() || !accountId.isEmpty()) + { + QDir d2( fi->absFilePath() ); + d2.setFilter( QDir::Files ); + d2.setNameFilter("*.log"); + const QFileInfoList *list = d2.entryInfoList(); + QFileInfoListIterator it2( *list ); + QFileInfo *fi2; + + progressDlg->progressBar()->reset(); + progressDlg->progressBar()->setTotalSteps(d2.count()); + progressDlg->setLabel(i18n("Parsing old history in %1").arg(fi->fileName())); + progressDlg->show(); //if it was not already showed... + + while ( (fi2 = it2.current()) != 0 ) + { + //we assume that all "-" are dots. (like in hotmail.com) + QString contactId=fi2->fileName().replace(".log" , QString::null).replace("-" , "."); + + if(!contactId.isEmpty() ) + { + progressDlg->setLabel(i18n("Parsing old history in %1:\n%2").arg(fi->fileName()).arg(contactId)); + kapp->processEvents(0); //make sure the text is updated in the progressDlg + + int month=0; + int year=0; + QDomDocument doc; + QDomElement docElem; + + QDomElement msgelement; + QDomNode node; + QDomDocument xmllist; + Kopete::Message::MessageDirection dir; + QString body, date, nick; + QString buffer, msgBlock; + char cbuf[CBUFLENGTH]; // buffer for the log file + + QString logFileName = fi2->absFilePath(); + + // open the file + FILE *f = fopen(QFile::encodeName(logFileName), "r"); + + // create a new block + while ( ! feof( f ) ) + { + fgets(cbuf, CBUFLENGTH, f); + buffer = QString::fromUtf8(cbuf); + + while ( strchr(cbuf, '\n') == NULL && !feof(f) ) + { + fgets( cbuf, CBUFLENGTH, f ); + buffer += QString::fromUtf8(cbuf); + } + + if( buffer.startsWith( QString::fromLatin1( "\n" ) /*strcmp("\n", cbuf )*/ ) + { + fgets(cbuf, CBUFLENGTH, f); + buffer = QString::fromUtf8(cbuf); + + while ( strchr(cbuf, '\n') == NULL && !feof(f) ) + { + fgets( cbuf, CBUFLENGTH, f ); + buffer += QString::fromUtf8(cbuf); + } + msgBlock.append(buffer); + } + + // now let's work on this new block + xmllist.setContent(msgBlock, false); + msgelement = xmllist.documentElement(); + node = msgelement.firstChild(); + + if( msgelement.attribute( QString::fromLatin1( "direction" ) ) == QString::fromLatin1( "inbound" ) ) + dir = Kopete::Message::Inbound; + else + dir = Kopete::Message::Outbound; + + // Read all the elements. + QString tagname; + QDomElement element; + + while ( ! node.isNull() ) + { + if ( node.isElement() ) + { + element = node.toElement(); + tagname = element.tagName(); + + if( tagname == QString::fromLatin1( "srcnick" ) ) + nick = element.text(); + + else if( tagname == QString::fromLatin1( "date" ) ) + date = element.text(); + else if( tagname == QString::fromLatin1( "body" ) ) + body = element.text().stripWhiteSpace(); + } + + node = node.nextSibling(); + } + //FIXME!! The date in logs writed with kopete running with QT 3.0 is Localised. + // so QT can't parse it correctly. + QDateTime dt=QDateTime::fromString(date); + if(dt.date().month() != month || dt.date().year() != year) + { + if(!docElem.isNull()) + { + QDate date(year,month,1); + QString name = protocolId.replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) + + QString::fromLatin1( "/" ) + + contactId.replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) + + date.toString(".yyyyMM"); + KSaveFile file( locateLocal( "data", QString::fromLatin1( "kopete/logs/" ) + name+ QString::fromLatin1( ".xml" ) ) ); + if( file.status() == 0 ) + { + QTextStream *stream = file.textStream(); + //stream->setEncoding( QTextStream::UnicodeUTF8 ); //???? oui ou non? + doc.save( *stream , 1 ); + file.close(); + } + } + + + month=dt.date().month(); + year=dt.date().year(); + docElem=QDomElement(); + } + + if(docElem.isNull()) + { + doc=QDomDocument("Kopete-History"); + docElem= doc.createElement( "kopete-history" ); + docElem.setAttribute ( "version" , "0.7" ); + doc.appendChild( docElem ); + QDomElement headElem = doc.createElement( "head" ); + docElem.appendChild( headElem ); + QDomElement dateElem = doc.createElement( "date" ); + dateElem.setAttribute( "year", QString::number(year) ); + dateElem.setAttribute( "month", QString::number(month) ); + headElem.appendChild(dateElem); + QDomElement myselfElem = doc.createElement( "contact" ); + myselfElem.setAttribute( "type", "myself" ); + myselfElem.setAttribute( "contactId", accountId ); + headElem.appendChild(myselfElem); + QDomElement contactElem = doc.createElement( "contact" ); + contactElem.setAttribute( "contactId", contactId ); + headElem.appendChild(contactElem); + QDomElement importElem = doc.createElement( "imported" ); + importElem.setAttribute( "from", fi->fileName() ); + importElem.setAttribute( "date", QDateTime::currentDateTime().toString() ); + headElem.appendChild(importElem); + } + QDomElement msgElem = doc.createElement( "msg" ); + msgElem.setAttribute( "in", dir==Kopete::Message::Outbound ? "0" : "1" ); + msgElem.setAttribute( "from", dir==Kopete::Message::Outbound ? accountId : contactId ); + msgElem.setAttribute( "nick", nick ); //do we have to set this? + msgElem.setAttribute( "time", QString::number(dt.date().day()) + " " + QString::number(dt.time().hour()) + ":" + QString::number(dt.time().minute()) ); + QDomText msgNode = doc.createTextNode( body.stripWhiteSpace() ); + docElem.appendChild( msgElem ); + msgElem.appendChild( msgNode ); + } + } + + fclose( f ); + if(deleteFiles) + d2.remove(fi2->fileName() , false); + + if(!docElem.isNull()) + { + QDate date(year,month,1); + QString name = protocolId.replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) + + QString::fromLatin1( "/" ) + + contactId.replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) + + date.toString(".yyyyMM"); + KSaveFile file( locateLocal( "data", QString::fromLatin1( "kopete/logs/" ) + name+ QString::fromLatin1( ".xml" ) ) ); + if( file.status() == 0 ) + { + QTextStream *stream = file.textStream(); + //stream->setEncoding( QTextStream::UnicodeUTF8 ); //???? oui ou non? + doc.save( *stream ,1 ); + file.close(); + } + } + + } + progressDlg->progressBar()->setProgress(progressDlg->progressBar()->progress()+1); + ++it2; + } + } + ++it; + } + delete progressDlg; + +} + + +bool HistoryPlugin::detectOldHistory() +{ + KGlobal::config()->setGroup("History Plugin"); + QString version=KGlobal::config()->readEntry( "Version" ,"0.6" ); + + if(version != "0.6") + return false; + + + QDir d( locateLocal( "data", QString::fromLatin1( "kopete/logs")) ); + d.setFilter( QDir::Dirs ); + if(d.count() >= 3) // '.' and '..' are included + return false; //the new history already exists + + QDir d2( locateLocal( "data", QString::fromLatin1( "kopete")) ); + d2.setFilter( QDir::Dirs ); + const QFileInfoList *list = d2.entryInfoList(); + QFileInfoListIterator it( *list ); + QFileInfo *fi; + while ( (fi = it.current()) != 0 ) + { + if( dynamic_cast( Kopete::PluginManager::self()->plugin( fi->fileName() ) ) ) + return true; + + if(fi->fileName() == "MSNProtocol" || fi->fileName() == "msn_logs" ) + return true; + else if(fi->fileName() == "ICQProtocol" || fi->fileName() == "icq_logs" ) + return true; + else if(fi->fileName() == "AIMProtocol" || fi->fileName() == "aim_logs" ) + return true; + else if(fi->fileName() == "OscarProtocol" ) + return true; + else if(fi->fileName() == "JabberProtocol" || fi->fileName() == "jabber_logs") + return true; + ++it; + } + return false; +} diff --git a/kopete/plugins/history/historychatui.rc b/kopete/plugins/history/historychatui.rc new file mode 100644 index 00000000..2f49392f --- /dev/null +++ b/kopete/plugins/history/historychatui.rc @@ -0,0 +1,17 @@ + + + + + &Tools + + + + + + + + + + + + diff --git a/kopete/plugins/history/historyconfig.kcfg b/kopete/plugins/history/historyconfig.kcfg new file mode 100644 index 00000000..58e6c9d2 --- /dev/null +++ b/kopete/plugins/history/historyconfig.kcfg @@ -0,0 +1,35 @@ + + + + + + + + + false + + + + + 7 + + + + + 20 + + + + + 170, 170, 127 + + + + + + + + diff --git a/kopete/plugins/history/historyconfig.kcfgc b/kopete/plugins/history/historyconfig.kcfgc new file mode 100644 index 00000000..1e985622 --- /dev/null +++ b/kopete/plugins/history/historyconfig.kcfgc @@ -0,0 +1,7 @@ +# Code generation options for kconfig_compiler +File=historyconfig.kcfg +ClassName=HistoryConfig +Singleton=true +Mutators=true +MemberVariables=private +GlobalEnums=true diff --git a/kopete/plugins/history/historydialog.cpp b/kopete/plugins/history/historydialog.cpp new file mode 100644 index 00000000..4dd98fee --- /dev/null +++ b/kopete/plugins/history/historydialog.cpp @@ -0,0 +1,613 @@ +/* + kopetehistorydialog.cpp - Kopete History Dialog + + Copyright (c) 2002 by Richard Stellingwerff + Copyright (c) 2004 by Stefan Gehn + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "historydialog.h" +#include "historylogger.h" +#include "historyviewer.h" +#include "kopetemetacontact.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopetecontactlist.h" +#include "kopeteprefs.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class KListViewDateItem : public KListViewItem +{ +public: + KListViewDateItem(KListView* parent, QDate date, Kopete::MetaContact *mc); + QDate date() { return mDate; } + Kopete::MetaContact *metaContact() { return mMetaContact; } + +public: + int compare(QListViewItem *i, int col, bool ascending) const; +private: + QDate mDate; + Kopete::MetaContact *mMetaContact; +}; + + + +KListViewDateItem::KListViewDateItem(KListView* parent, QDate date, Kopete::MetaContact *mc) + : KListViewItem(parent, date.toString(Qt::ISODate), mc->displayName()) +{ + mDate = date; + mMetaContact = mc; +} + +int KListViewDateItem::compare(QListViewItem *i, int col, bool ascending) const +{ + if (col) + return QListViewItem::compare(i, col, ascending); + + //compare dates - do NOT use ascending var here + KListViewDateItem* item = static_cast(i); + if ( mDate < item->date() ) + return -1; + return ( mDate > item->date() ); +} + + +HistoryDialog::HistoryDialog(Kopete::MetaContact *mc, QWidget* parent, + const char* name) : KDialogBase(parent, name, false, + i18n("History for %1").arg(mc->displayName()), 0), mSearching(false) +{ + QString fontSize; + QString htmlCode; + QString fontStyle; + + kdDebug(14310) << k_funcinfo << "called." << endl; + setWFlags(Qt::WDestructiveClose); // send SIGNAL(closing()) on quit + + // FIXME: Allow to show this dialog for only one contact + mMetaContact = mc; + + + + // Widgets initializations + mMainWidget = new HistoryViewer(this, "HistoryDialog::mMainWidget"); + mMainWidget->searchLine->setFocus(); + mMainWidget->searchLine->setTrapReturnKey (true); + mMainWidget->searchLine->setTrapReturnKey(true); + mMainWidget->searchErase->setPixmap(BarIcon("locationbar_erase")); + + mMainWidget->contactComboBox->insertItem(i18n("All")); + mMetaContactList = Kopete::ContactList::self()->metaContacts(); + QPtrListIterator it(mMetaContactList); + for(; it.current(); ++it) + { + mMainWidget->contactComboBox->insertItem((*it)->displayName()); + } + + if (mMetaContact) + mMainWidget->contactComboBox->setCurrentItem(mMetaContactList.find(mMetaContact)+1); + + mMainWidget->dateSearchLine->setListView(mMainWidget->dateListView); + mMainWidget->dateListView->setSorting(0, 0); //newest-first + + setMainWidget(mMainWidget); + + // Initializing HTML Part + mMainWidget->htmlFrame->setFrameStyle(QFrame::WinPanel | QFrame::Sunken); + QVBoxLayout *l = new QVBoxLayout(mMainWidget->htmlFrame); + mHtmlPart = new KHTMLPart(mMainWidget->htmlFrame, "htmlHistoryView"); + + //Security settings, we don't need this stuff + mHtmlPart->setJScriptEnabled(false); + mHtmlPart->setJavaEnabled(false); + mHtmlPart->setPluginsEnabled(false); + mHtmlPart->setMetaRefreshEnabled(false); + mHtmlPart->setOnlyLocalReferences(true); + + mHtmlView = mHtmlPart->view(); + mHtmlView->setMarginWidth(4); + mHtmlView->setMarginHeight(4); + mHtmlView->setFocusPolicy(NoFocus); + mHtmlView->setSizePolicy( + QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); + l->addWidget(mHtmlView); + + QTextOStream( &fontSize ) << KopetePrefs::prefs()->fontFace().pointSize(); + fontStyle = ""; + + mHtmlPart->begin(); + htmlCode = "" + fontStyle + ""; + mHtmlPart->write( QString::fromLatin1( htmlCode.latin1() ) ); + mHtmlPart->end(); + + + connect(mHtmlPart->browserExtension(), SIGNAL(openURLRequestDelayed(const KURL &, const KParts::URLArgs &)), + this, SLOT(slotOpenURLRequest(const KURL &, const KParts::URLArgs &))); + connect(mMainWidget->dateListView, SIGNAL(clicked(QListViewItem*)), this, SLOT(dateSelected(QListViewItem*))); + connect(mMainWidget->searchButton, SIGNAL(clicked()), this, SLOT(slotSearch())); + connect(mMainWidget->searchLine, SIGNAL(returnPressed()), this, SLOT(slotSearch())); + connect(mMainWidget->searchLine, SIGNAL(textChanged(const QString&)), this, SLOT(slotSearchTextChanged(const QString&))); + connect(mMainWidget->searchErase, SIGNAL(clicked()), this, SLOT(slotSearchErase())); + connect(mMainWidget->contactComboBox, SIGNAL(activated(int)), this, SLOT(slotContactChanged(int))); + connect(mMainWidget->messageFilterBox, SIGNAL(activated(int)), this, SLOT(slotFilterChanged(int ))); + connect(mHtmlPart, SIGNAL(popupMenu(const QString &, const QPoint &)), this, SLOT(slotRightClick(const QString &, const QPoint &))); + + //initActions + KActionCollection* ac = new KActionCollection(this); + mCopyAct = KStdAction::copy( this, SLOT(slotCopy()), ac ); + mCopyURLAct = new KAction( i18n( "Copy Link Address" ), QString::fromLatin1( "editcopy" ), 0, this, SLOT( slotCopyURL() ), ac ); + + resize(650, 700); + centerOnScreen(this); + + // show the dialog before people get impatient + show(); + + // Load history dates in the listview + init(); +} + +HistoryDialog::~HistoryDialog() +{ + mSearching = false; +} + +void HistoryDialog::init() +{ + if(mMetaContact) + { + HistoryLogger logger(mMetaContact, this); + init(mMetaContact); + } + else + { + QPtrListIterator it(mMetaContactList); + for(; it.current(); ++it) + { + HistoryLogger logger(*it, this); + init(*it); + } + + } + + initProgressBar(i18n("Loading..."),mInit.dateMCList.count()); + QTimer::singleShot(0,this,SLOT(slotLoadDays())); +} + +void HistoryDialog::slotLoadDays() +{ + if(mInit.dateMCList.isEmpty()) + { + if (!mMainWidget->searchLine->text().isEmpty()) + QTimer::singleShot(0, this, SLOT(slotSearch())); + doneProgressBar(); + return; + } + + DMPair pair(mInit.dateMCList.first()); + mInit.dateMCList.pop_front(); + HistoryLogger logger(pair.metaContact(), this); + QValueList dayList = logger.getDaysForMonth(pair.date()); + for (unsigned int i=0; idateListView, c2Date, pair.metaContact()); + } + mMainWidget->searchProgress->advance(1); + QTimer::singleShot(0,this,SLOT(slotLoadDays())); + + +} + +void HistoryDialog::init(Kopete::MetaContact *mc) +{ + QPtrList contacts=mc->contacts(); + QPtrListIterator it( contacts ); + + for( ; it.current(); ++it ) + { + init(*it); + } +} + +void HistoryDialog::init(Kopete::Contact *c) +{ + // Get year and month list + QRegExp rx( "\\.(\\d\\d\\d\\d)(\\d\\d)" ); + const QString contact_in_filename=c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ); + QFileInfo *fi; + + + // BEGIN check if there are Kopete 0.7.x + QDir d1(locateLocal("data",QString("kopete/logs/")+ + c->protocol()->pluginId().replace( QRegExp(QString::fromLatin1("[./~?*]")),QString::fromLatin1("-")) + )); + d1.setFilter( QDir::Files | QDir::NoSymLinks ); + d1.setSorting( QDir::Name ); + + const QFileInfoList *list1 = d1.entryInfoList(); + if ( list1 != 0 ) + { + QFileInfoListIterator it1( *list1 ); + while ( (fi = it1.current()) != 0 ) + { + if(fi->fileName().contains(contact_in_filename)) + { + rx.search(fi->fileName()); + + QDate cDate = QDate(rx.cap(1).toInt(), rx.cap(2).toInt(), 1); + + DMPair pair(cDate, c->metaContact()); + mInit.dateMCList.append(pair); + + } + ++it1; + } + } + // END of kopete 0.7.x check + + QString logDir = locateLocal("data",QString("kopete/logs/")+ + c->protocol()->pluginId().replace( QRegExp(QString::fromLatin1("[./~?*]")),QString::fromLatin1("-")) + + QString::fromLatin1( "/" ) + + c->account()->accountId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) + ); + QDir d(logDir); + d.setFilter( QDir::Files | QDir::NoSymLinks ); + d.setSorting( QDir::Name ); + const QFileInfoList *list = d.entryInfoList(); + if ( list != 0 ) + { + QFileInfoListIterator it( *list ); + while ( (fi = it.current()) != 0 ) + { + if(fi->fileName().contains(contact_in_filename)) + { + + rx.search(fi->fileName()); + + // We search for an item in the list view with the same year. If then we add the month + QDate cDate = QDate(rx.cap(1).toInt(), rx.cap(2).toInt(), 1); + + DMPair pair(cDate, c->metaContact()); + mInit.dateMCList.append(pair); + } + ++it; + } + } +} + +void HistoryDialog::dateSelected(QListViewItem* it) +{ + KListViewDateItem *item = static_cast(it); + + if (!item) return; + + QDate chosenDate = item->date(); + + HistoryLogger logger(item->metaContact(), this); + QValueList msgs=logger.readMessages(chosenDate); + + setMessages(msgs); +} + +void HistoryDialog::setMessages(QValueList msgs) +{ + // Clear View + DOM::HTMLElement htmlBody = mHtmlPart->htmlDocument().body(); + while(htmlBody.hasChildNodes()) + htmlBody.removeChild(htmlBody.childNodes().item(htmlBody.childNodes().length() - 1)); + // ---- + + QString dir = (QApplication::reverseLayout() ? QString::fromLatin1("rtl") : + QString::fromLatin1("ltr")); + + QValueList::iterator it = msgs.begin(); + + + QString accountLabel; + QString resultHTML = "" + (*it).timestamp().date().toString() + "
    "; + DOM::HTMLElement newNode = mHtmlPart->document().createElement(QString::fromLatin1("span")); + newNode.setAttribute(QString::fromLatin1("dir"), dir); + newNode.setInnerHTML(resultHTML); + mHtmlPart->htmlDocument().body().appendChild(newNode); + + // Populating HTML Part with messages + for ( it = msgs.begin(); it != msgs.end(); ++it ) + { + if ( mMainWidget->messageFilterBox->currentItem() == 0 + || ( mMainWidget->messageFilterBox->currentItem() == 1 && (*it).direction() == Kopete::Message::Inbound ) + || ( mMainWidget->messageFilterBox->currentItem() == 2 && (*it).direction() == Kopete::Message::Outbound ) ) + { + resultHTML = ""; + + if (accountLabel.isEmpty() || accountLabel != (*it).from()->account()->accountLabel()) + // If the message's account is new, just specify it to the user + { + if (!accountLabel.isEmpty()) + resultHTML += "


    "; + resultHTML += "" + (*it).from()->account()->accountLabel() + "
    "; + } + accountLabel = (*it).from()->account()->accountLabel(); + + QString body = (*it).parsedBody(); + + if (!mMainWidget->searchLine->text().isEmpty()) + // If there is a search, then we hightlight the keywords + { + body = body.replace(mMainWidget->searchLine->text(), "" + mMainWidget->searchLine->text() + "", false); + } + + resultHTML += "(" + (*it).timestamp().time().toString() + ") " + + ((*it).direction() == Kopete::Message::Outbound ? + "textColor().dark().name() + "\">> " + : "textColor().light(200).name() + "\">< ") + + body + "
    "; + + newNode = mHtmlPart->document().createElement(QString::fromLatin1("span")); + newNode.setAttribute(QString::fromLatin1("dir"), dir); + newNode.setInnerHTML(resultHTML); + + mHtmlPart->htmlDocument().body().appendChild(newNode); + } + } +} + +void HistoryDialog::slotFilterChanged(int /* index */) +{ + dateSelected(mMainWidget->dateListView->currentItem()); +} + +void HistoryDialog::slotOpenURLRequest(const KURL &url, const KParts::URLArgs &/*args*/) +{ + kdDebug(14310) << k_funcinfo << "url=" << url.url() << endl; + new KRun(url, 0, false); // false = non-local files +} + +// Disable search button if there is no search text +void HistoryDialog::slotSearchTextChanged(const QString& searchText) +{ + if (searchText.isEmpty()) + { + mMainWidget->searchButton->setEnabled(false); + slotSearchErase(); + } + else + { + mMainWidget->searchButton->setEnabled(true); + } +} + +void HistoryDialog::listViewShowElements(bool s) +{ + KListViewDateItem* item = static_cast(mMainWidget->dateListView->firstChild()); + while (item != 0) + { + item->setVisible(s); + item = static_cast(item->nextSibling()); + } +} + +// Erase the search line, show all date/metacontacts items in the list (accordint to the +// metacontact selected in the combobox) +void HistoryDialog::slotSearchErase() +{ + mMainWidget->searchLine->clear(); + listViewShowElements(true); +} + +/* +* How does the search work +* ------------------------ +* We do the search respecting the current metacontact filter item. To do this, we iterate over the +* elements in the KListView (KListViewDateItems) and, for each one, we iterate over its subcontacts, +* manually searching the log files of each one. To avoid searching files twice, the months that have +* been searched already are stored in searchedMonths. The matches are placed in the matches QMap. +* Finally, the current date item is checked in the matches QMap, and if it is present, it is shown. +* +* Keyword highlighting is done in setMessages() : if the search field isn't empty, we highlight the +* search keyword. +* +* The search is _not_ case sensitive +*/ +void HistoryDialog::slotSearch() +{ + if (mMainWidget->dateListView->childCount() == 0) return; + + QRegExp rx("^ ([^<]*)<"); + QMap > monthsSearched; + QMap > matches; + + // cancel button pressed + if (mSearching) + { + listViewShowElements(true); + goto searchFinished; + } + + listViewShowElements(false); + + initProgressBar(i18n("Searching..."), mMainWidget->dateListView->childCount()); + mMainWidget->searchButton->setText(i18n("&Cancel")); + mSearching = true; + + // iterate over items in the date list widget + for(KListViewDateItem *curItem = static_cast(mMainWidget->dateListView->firstChild()); + curItem != 0; + curItem = static_cast(curItem->nextSibling()) + ) + { + qApp->processEvents(); + if (!mSearching) return; + + QDate month(curItem->date().year(),curItem->date().month(),1); + // if we haven't searched the relevant history logs, search them now + if (!monthsSearched[month].contains(curItem->metaContact())) + { + monthsSearched[month].push_back(curItem->metaContact()); + QPtrList contacts = curItem->metaContact()->contacts(); + for(QPtrListIterator it( contacts ); it.current(); ++it) + { + // get filename and open file + QString filename(HistoryLogger::getFileName(*it, curItem->date())); + if (!QFile::exists(filename)) continue; + QFile file(filename); + file.open(IO_ReadOnly); + if (!file.isOpen()) + { + kdWarning(14310) << k_funcinfo << "Error opening " << + file.name() << ": " << file.errorString() << endl; + continue; + } + + QTextStream stream(&file); + QString textLine; + while(!stream.atEnd()) + { + textLine = stream.readLine(); + if (textLine.contains(mMainWidget->searchLine->text(), false)) + { + if(rx.search(textLine) != -1) + { + // only match message body + if (rx.cap(2).contains(mMainWidget->searchLine->text())) + matches[QDate(curItem->date().year(),curItem->date().month(),rx.cap(1).toInt())].push_back(curItem->metaContact()); + } + // this will happen when multiline messages are searched, properly + // parsing the files would fix this + else { } + } + qApp->processEvents(); + if (!mSearching) return; + } + file.close(); + } + } + + // relevant logfiles have been searched now, check if current date matches + if (matches[curItem->date()].contains(curItem->metaContact())) + curItem->setVisible(true); + + // Next date item + mMainWidget->searchProgress->advance(1); + } + +searchFinished: + mMainWidget->searchButton->setText(i18n("Se&arch")); + mSearching = false; + doneProgressBar(); +} + + + +// When a contact is selected in the combobox. Item 0 is All contacts. +void HistoryDialog::slotContactChanged(int index) +{ + mMainWidget->dateListView->clear(); + if (index == 0) + { + setCaption(i18n("History for All Contacts")); + mMetaContact = 0; + init(); + } + else + { + mMetaContact = mMetaContactList.at(index-1); + setCaption(i18n("History for %1").arg(mMetaContact->displayName())); + init(); + } +} + +void HistoryDialog::initProgressBar(const QString& text, int nbSteps) +{ + mMainWidget->searchProgress->setTotalSteps(nbSteps); + mMainWidget->searchProgress->setProgress(0); + mMainWidget->searchProgress->show(); + mMainWidget->statusLabel->setText(text); +} + +void HistoryDialog::doneProgressBar() +{ + mMainWidget->searchProgress->hide(); + mMainWidget->statusLabel->setText(i18n("Ready")); +} + +void HistoryDialog::slotRightClick(const QString &url, const QPoint &point) +{ + KPopupMenu *chatWindowPopup = 0L; + chatWindowPopup = new KPopupMenu(); + + if ( !url.isEmpty() ) + { + mURL = url; + mCopyURLAct->plug( chatWindowPopup ); + chatWindowPopup->insertSeparator(); + } + mCopyAct->setEnabled( mHtmlPart->hasSelection() ); + mCopyAct->plug( chatWindowPopup ); + + connect( chatWindowPopup, SIGNAL( aboutToHide() ), chatWindowPopup, SLOT( deleteLater() ) ); + chatWindowPopup->popup(point); +} + +void HistoryDialog::slotCopy() +{ + QString qsSelection; + qsSelection = mHtmlPart->selectedText(); + if ( qsSelection.isEmpty() ) return; + + disconnect( kapp->clipboard(), SIGNAL( selectionChanged()), mHtmlPart, SLOT(slotClearSelection())); + QApplication::clipboard()->setText(qsSelection, QClipboard::Clipboard); + QApplication::clipboard()->setText(qsSelection, QClipboard::Selection); + connect( kapp->clipboard(), SIGNAL( selectionChanged()), mHtmlPart, SLOT(slotClearSelection())); +} + +void HistoryDialog::slotCopyURL() +{ + disconnect( kapp->clipboard(), SIGNAL( selectionChanged()), mHtmlPart, SLOT(slotClearSelection())); + QApplication::clipboard()->setText( mURL, QClipboard::Clipboard); + QApplication::clipboard()->setText( mURL, QClipboard::Selection); + connect( kapp->clipboard(), SIGNAL( selectionChanged()), mHtmlPart, SLOT(slotClearSelection())); +} + +#include "historydialog.moc" diff --git a/kopete/plugins/history/historydialog.h b/kopete/plugins/history/historydialog.h new file mode 100644 index 00000000..cf26037d --- /dev/null +++ b/kopete/plugins/history/historydialog.h @@ -0,0 +1,146 @@ +/* + kopetehistorydialog.h - Kopete History Dialog + + Copyright (c) 2002 by Richard Stellingwerff + Copyright (c) 2004 by Stefan Gehn + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef _HISTORYDIALOG_H +#define _HISTORYDIALOG_H + +#include +#include + +#include +#include + +#include "kopetemessage.h" + +class HistoryViewer; + +//class HistoryWidget; +namespace Kopete { class MetaContact; } +namespace Kopete { class XSLT; } +class HistoryLogger; +class KHTMLView; +class KHTMLPart; + +class KURL; +namespace KParts { struct URLArgs; class Part; } + + +class KListViewDateItem; + +class DMPair +{ + public: + DMPair() {md = QDate(0, 0, 0); mc = 0; } + DMPair(QDate d, Kopete::MetaContact *c) { md = d; mc =c; } + QDate date() const { return md; } + Kopete::MetaContact* metaContact() const { return mc; } + bool operator==(const DMPair p1) const { return p1.date() == this->date() && p1.metaContact() == this->metaContact(); } + private: + QDate md; + Kopete::MetaContact *mc; +}; + +/** + * @author Richard Stellingwerff + * @author Stefan Gehn + */ +class HistoryDialog : public KDialogBase +{ + Q_OBJECT + + public: + HistoryDialog(Kopete::MetaContact *mc, QWidget* parent=0, + const char* name="HistoryDialog"); + ~HistoryDialog(); + + /** + * Calls init(Kopete::Contact *c) for each subcontact of the metacontact + */ + + + signals: + void closing(); + + private slots: + void slotOpenURLRequest(const KURL &url, const KParts::URLArgs &/*args*/); + + // Called when a date is selected in the treeview + void dateSelected(QListViewItem *); + + void slotSearch(); + + // Reinitialise search + void slotSearchErase(); + void slotSearchTextChanged(const QString& txt); // To enable/disable search button + void slotContactChanged(int index); + void slotFilterChanged(int index); + + void init(); + void slotLoadDays(); + + void slotRightClick(const QString &url, const QPoint &point); + void slotCopy(); + void slotCopyURL(); + + private: + enum Disabled { Prev=1, Next=2 }; + void refreshEnabled( /*Disabled*/ uint disabled ); + + void initProgressBar(const QString& text, int nbSteps); + void doneProgressBar(); + void init(Kopete::MetaContact *mc); + void init(Kopete::Contact *c); + + /** + * Show the messages in the HTML View + */ + void setMessages(QValueList m); + + void listViewShowElements(bool s); + + /** + * Search if @param item already has @param text child + */ + bool hasChild(KListViewItem* item, int month); + + /** + * We show history dialog to look at the log for a metacontact. Here is this metacontact. + */ + Kopete::MetaContact *mMetaContact; + + QPtrList mMetaContactList; + + // History View + KHTMLView *mHtmlView; + KHTMLPart *mHtmlPart; + HistoryViewer *mMainWidget; + Kopete::XSLT *mXsltParser; + + struct Init + { + QValueList dateMCList; // mc for MetaContact + } mInit; + + bool mSearching; + + KAction *mCopyAct; + KAction *mCopyURLAct; + QString mURL; +}; + +#endif diff --git a/kopete/plugins/history/historyguiclient.cpp b/kopete/plugins/history/historyguiclient.cpp new file mode 100644 index 00000000..133e50a3 --- /dev/null +++ b/kopete/plugins/history/historyguiclient.cpp @@ -0,0 +1,115 @@ +/* + historyguiclient.cpp + + Copyright (c) 2003-2004 by Olivier Goffart + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#include "historyguiclient.h" +#include "historylogger.h" +#include "historyconfig.h" + +#include "kopetechatsession.h" +#include "kopetecontact.h" +#include "kopeteview.h" + +#include +#include +#include + +class HistoryPlugin; + +HistoryGUIClient::HistoryGUIClient(Kopete::ChatSession *parent, const char *name) + : QObject(parent, name), KXMLGUIClient(parent) +{ + setInstance(KGenericFactory::instance()); + + m_manager = parent; + + // Refuse to build this client, it is based on wrong parameters + if(!m_manager || m_manager->members().isEmpty()) + deleteLater(); + + QPtrList mb=m_manager->members(); + m_logger=new HistoryLogger( mb.first() , this ); + + actionLast=new KAction( i18n("History Last" ), QString::fromLatin1( "finish" ), 0, this, SLOT(slotLast()), actionCollection() , "historyLast" ); + actionPrev = KStdAction::back( this, SLOT(slotPrevious()), actionCollection() , "historyPrevious" ); + actionNext = KStdAction::forward( this, SLOT(slotNext()), actionCollection() , "historyNext" ); + + // we are generally at last when begining + actionPrev->setEnabled(true); + actionNext->setEnabled(false); + actionLast->setEnabled(false); + + setXMLFile("historychatui.rc"); +} + + +HistoryGUIClient::~HistoryGUIClient() +{ +} + + +void HistoryGUIClient::slotPrevious() +{ + KopeteView *m_currentView = m_manager->view(true); + m_currentView->clear(); + + QPtrList mb = m_manager->members(); + QValueList msgs = m_logger->readMessages( + HistoryConfig::number_ChatWindow(), /*mb.first()*/ 0L, + HistoryLogger::AntiChronological, true); + + actionPrev->setEnabled(msgs.count() == HistoryConfig::number_ChatWindow()); + actionNext->setEnabled(true); + actionLast->setEnabled(true); + + m_currentView->appendMessages(msgs); +} + +void HistoryGUIClient::slotLast() +{ + KopeteView *m_currentView = m_manager->view(true); + m_currentView->clear(); + + QPtrList mb = m_manager->members(); + m_logger->setPositionToLast(); + QValueList msgs = m_logger->readMessages( + HistoryConfig::number_ChatWindow(), /*mb.first()*/ 0L, + HistoryLogger::AntiChronological, true); + + actionPrev->setEnabled(true); + actionNext->setEnabled(false); + actionLast->setEnabled(false); + + m_currentView->appendMessages(msgs); +} + + +void HistoryGUIClient::slotNext() +{ + KopeteView *m_currentView = m_manager->view(true); + m_currentView->clear(); + + QPtrList mb = m_manager->members(); + QValueList msgs = m_logger->readMessages( + HistoryConfig::number_ChatWindow(), /*mb.first()*/ 0L, + HistoryLogger::Chronological, false); + + actionPrev->setEnabled(true); + actionNext->setEnabled(msgs.count() == HistoryConfig::number_ChatWindow()); + actionLast->setEnabled(msgs.count() == HistoryConfig::number_ChatWindow()); + + m_currentView->appendMessages(msgs); +} + +#include "historyguiclient.moc" diff --git a/kopete/plugins/history/historyguiclient.h b/kopete/plugins/history/historyguiclient.h new file mode 100644 index 00000000..420795e0 --- /dev/null +++ b/kopete/plugins/history/historyguiclient.h @@ -0,0 +1,55 @@ +/* + historyguiclient.h + + Copyright (c) 2003 by Olivier Goffart + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef HISTORYGUICLIENT_H +#define HISTORYGUICLIENT_H + +#include +#include + +namespace Kopete { class ChatSession; } +class HistoryLogger; +class KAction; + +/** + *@author Olivier Goffart + */ +class HistoryGUIClient : public QObject , public KXMLGUIClient +{ +Q_OBJECT +public: + HistoryGUIClient(Kopete::ChatSession *parent = 0, const char *name = 0); + ~HistoryGUIClient(); + + HistoryLogger *logger() const { return m_logger; } + +private slots: + void slotPrevious(); + void slotLast(); + void slotNext(); + +private: + HistoryLogger *m_logger; + Kopete::ChatSession *m_manager; + //bool m_autoChatWindow; + //int m_nbAutoChatWindow; + //unsigned int m_nbChatWindow; + + KAction *actionPrev; + KAction *actionNext; + KAction *actionLast; +}; + +#endif diff --git a/kopete/plugins/history/historylogger.cpp b/kopete/plugins/history/historylogger.cpp new file mode 100644 index 00000000..7848136f --- /dev/null +++ b/kopete/plugins/history/historylogger.cpp @@ -0,0 +1,851 @@ +/* + historylogger.cpp + + Copyright (c) 2003-2004 by Olivier Goffart + + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "historylogger.h" +#include "historyconfig.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "kopeteglobal.h" +#include "kopetecontact.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopetemetacontact.h" +#include "kopetechatsession.h" + +// ----------------------------------------------------------------------------- +HistoryLogger::HistoryLogger( Kopete::MetaContact *m, QObject *parent, const char *name ) + : QObject(parent, name) +{ + m_saveTimer=0L; + m_saveTimerTime=0; + m_metaContact=m; + m_hideOutgoing=false; + m_cachedMonth=-1; + m_realMonth=QDate::currentDate().month(); + m_oldSens=Default; + + //the contact may be destroyed, for example, if the contact changes its metacontact + connect(m_metaContact , SIGNAL(destroyed(QObject *)) , this , SLOT(slotMCDeleted())); + + setPositionToLast(); +} + + +HistoryLogger::HistoryLogger( Kopete::Contact *c, QObject *parent, const char *name ) + : QObject(parent, name) +{ + m_saveTimer=0L; + m_saveTimerTime=0; + m_cachedMonth=-1; + m_metaContact=c->metaContact(); + m_hideOutgoing=false; + m_realMonth=QDate::currentDate().month(); + m_oldSens=Default; + + //the contact may be destroyed, for example, if the contact changes its metacontact + connect(m_metaContact , SIGNAL(destroyed(QObject *)) , this , SLOT(slotMCDeleted())); + + setPositionToLast(); +} + + +HistoryLogger::~HistoryLogger() +{ + if(m_saveTimer && m_saveTimer->isActive()) + saveToDisk(); +} + + +void HistoryLogger::setPositionToLast() +{ + setCurrentMonth(0); + m_oldSens = AntiChronological; + m_oldMonth=0; + m_oldElements.clear(); +} + + +void HistoryLogger::setPositionToFirst() +{ + setCurrentMonth( getFirstMonth() ); + m_oldSens = Chronological; + m_oldMonth=m_currentMonth; + m_oldElements.clear(); +} + + +void HistoryLogger::setCurrentMonth(int month) +{ + m_currentMonth = month; + m_currentElements.clear(); +} + + +QDomDocument HistoryLogger::getDocument(const Kopete::Contact *c, unsigned int month , bool canLoad , bool* contain) +{ + if(m_realMonth!=QDate::currentDate().month()) + { //We changed month, our indice are not correct anymore, clean memory. + // or we will see what i called "the 31 midnight bug"(TM) :-) -Olivier + m_documents.clear(); + m_cachedMonth=-1; + m_currentMonth++; //Not usre it's ok, but should work; + m_oldMonth++; // idem + m_realMonth=QDate::currentDate().month(); + } + + if(!m_metaContact) + { //this may happen if the contact has been moved, and the MC deleted + if(c && c->metaContact()) + m_metaContact=c->metaContact(); + else + return QDomDocument(); + } + + if(!m_metaContact->contacts().contains(c)) + { + if(contain) + *contain=false; + return QDomDocument(); + } + + QMap documents = m_documents[c]; + if (documents.contains(month)) + return documents[month]; + + + QDomDocument doc = getDocument(c, QDate::currentDate().addMonths(0-month), canLoad, contain); + + documents.insert(month, doc); + m_documents[c]=documents; + + return doc; + +} + +QDomDocument HistoryLogger::getDocument(const Kopete::Contact *c, const QDate date , bool canLoad , bool* contain) +{ + if(!m_metaContact) + { //this may happen if the contact has been moved, and the MC deleted + if(c && c->metaContact()) + m_metaContact=c->metaContact(); + else + return QDomDocument(); + } + + if(!m_metaContact->contacts().contains(c)) + { + if(contain) + *contain=false; + return QDomDocument(); + } + + if(!canLoad) + { + if(contain) + *contain=false; + return QDomDocument(); + } + + QString FileName = getFileName(c, date); + + QDomDocument doc( "Kopete-History" ); + + QFile file( FileName ); + if ( !file.open( IO_ReadOnly ) ) + { + if(contain) + *contain=false; + return doc; + } + if ( !doc.setContent( &file ) ) + { + file.close(); + if(contain) + *contain=false; + return doc; + } + file.close(); + + if(contain) + *contain=true; + + return doc; +} + + +void HistoryLogger::appendMessage( const Kopete::Message &msg , const Kopete::Contact *ct ) +{ + if(!msg.from()) + return; + + // If no contact are given: If the manager is availiable, use the manager's + // first contact (the channel on irc, or the other contact for others protocols + const Kopete::Contact *c = ct; + if(!c && msg.manager() ) + { + QPtrList mb=msg.manager()->members() ; + c = mb.first(); + } + if(!c) //If the contact is still not initialized, use the message author. + c = msg.direction()==Kopete::Message::Outbound ? msg.to().first() : msg.from() ; + + + if(!m_metaContact) + { //this may happen if the contact has been moved, and the MC deleted + if(c && c->metaContact()) + m_metaContact=c->metaContact(); + else + return; + } + + + if(!c || !m_metaContact->contacts().contains(c) ) + { + /*QPtrList contacts= m_metaContact->contacts(); + QPtrListIterator it( contacts ); + for( ; it.current(); ++it ) + { + if( (*it)->protocol()->pluginId() == msg.from()->protocol()->pluginId() ) + { + c=*it; + break; + } + }*/ + //if(!c) + + kdWarning(14310) << k_funcinfo << "No contact found in this metacontact to" << + " append in the history" << endl; + return; + } + + QDomDocument doc=getDocument(c,0); + QDomElement docElem = doc.documentElement(); + + if(docElem.isNull()) + { + docElem= doc.createElement( "kopete-history" ); + docElem.setAttribute ( "version" , "0.9" ); + doc.appendChild( docElem ); + QDomElement headElem = doc.createElement( "head" ); + docElem.appendChild( headElem ); + QDomElement dateElem = doc.createElement( "date" ); + dateElem.setAttribute( "year", QString::number(QDate::currentDate().year()) ); + dateElem.setAttribute( "month", QString::number(QDate::currentDate().month()) ); + headElem.appendChild(dateElem); + QDomElement myselfElem = doc.createElement( "contact" ); + myselfElem.setAttribute( "type", "myself" ); + myselfElem.setAttribute( "contactId", c->account()->myself()->contactId() ); + headElem.appendChild(myselfElem); + QDomElement contactElem = doc.createElement( "contact" ); + contactElem.setAttribute( "contactId", c->contactId() ); + headElem.appendChild(contactElem); + } + + QDomElement msgElem = doc.createElement( "msg" ); + msgElem.setAttribute( "in", msg.direction()==Kopete::Message::Outbound ? "0" : "1" ); + msgElem.setAttribute( "from", msg.from()->contactId() ); + msgElem.setAttribute( "nick", msg.from()->property( Kopete::Global::Properties::self()->nickName() ).value().toString() ); //do we have to set this? + msgElem.setAttribute( "time", msg.timestamp().toString("d h:m:s") ); + + QDomText msgNode = doc.createTextNode( msg.plainBody() ); + docElem.appendChild( msgElem ); + msgElem.appendChild( msgNode ); + + + // I'm temporizing the save. + // On hight-traffic channel, saving can take lots of CPU. (because the file is big) + // So i wait a time proportional to the time needed to save.. + + const QString filename=getFileName(c,QDate::currentDate()); + if(!m_toSaveFileName.isEmpty() && m_toSaveFileName != filename) + { //that mean the contact or the month has changed, save it now. + saveToDisk(); + } + + m_toSaveFileName=filename; + m_toSaveDocument=doc; + + if(!m_saveTimer) + { + m_saveTimer=new QTimer(this); + connect( m_saveTimer, SIGNAL( timeout() ) , this, SLOT(saveToDisk()) ); + } + if(!m_saveTimer->isActive()) + m_saveTimer->start( m_saveTimerTime, true /*singleshot*/ ); +} + +void HistoryLogger::saveToDisk() +{ + if(m_saveTimer) + m_saveTimer->stop(); + if(m_toSaveFileName.isEmpty() || m_toSaveDocument.isNull()) + return; + + QTime t; + t.start(); //mesure the time needed to save. + + KSaveFile file( m_toSaveFileName ); + if( file.status() == 0 ) + { + QTextStream *stream = file.textStream(); + //stream->setEncoding( QTextStream::UnicodeUTF8 ); //???? oui ou non? + m_toSaveDocument.save( *stream, 1 ); + file.close(); + + m_saveTimerTime=QMIN(t.elapsed()*1000, 300000); + //a time 1000 times supperior to the time needed to save. but with a upper limit of 5 minutes + //on a my machine, (2.4Ghz, but old HD) it should take about 10 ms to save the file. + // So that would mean save every 10 seconds, which seems to be ok. + // But it may take 500 ms if the file to save becomes too big (1Mb). + kdDebug(14310) << k_funcinfo << m_toSaveFileName << " saved in " << t.elapsed() << " ms " < HistoryLogger::readMessages(QDate date) +{ + QRegExp rxTime("(\\d+) (\\d+):(\\d+)($|:)(\\d*)"); //(with a 0.7.x compatibility) + QValueList messages; + + + QPtrList ct=m_metaContact->contacts(); + QPtrListIterator it( ct ); + + for( ; it.current(); ++it ) + { + QDomDocument doc=getDocument(*it,date, true, 0L); + QDomElement docElem = doc.documentElement(); + QDomNode n = docElem.firstChild(); + + while(!n.isNull()) + { + QDomElement msgElem2 = n.toElement(); + if( !msgElem2.isNull() && msgElem2.tagName()=="msg") + { + rxTime.search(msgElem2.attribute("time")); + QDateTime dt( QDate(date.year() , date.month() , rxTime.cap(1).toUInt()), QTime( rxTime.cap(2).toUInt() , rxTime.cap(3).toUInt(), rxTime.cap(5).toUInt() ) ); + + if (dt.date() != date) + { + n = n.nextSibling(); + continue; + } + + Kopete::Message::MessageDirection dir = (msgElem2.attribute("in") == "1") ? + Kopete::Message::Inbound : Kopete::Message::Outbound; + + if(!m_hideOutgoing || dir != Kopete::Message::Outbound) + { //parse only if we don't hide it + + QString f=msgElem2.attribute("from" ); + const Kopete::Contact *from=f.isNull()? 0L : (*it)->account()->contacts()[f]; + + if(!from) + from= dir==Kopete::Message::Inbound ? (*it) : (*it)->account()->myself(); + + Kopete::ContactPtrList to; + to.append( dir==Kopete::Message::Inbound ? (*it)->account()->myself() : *it ); + + Kopete::Message msg(dt, from, to, msgElem2.text(), dir); + msg.setBody( QString::fromLatin1("%2") + .arg( dt.toString(Qt::LocalDate), msg.escapedBody() ), + Kopete::Message::RichText); + + + // We insert it at the good place, given its date + QValueListIterator msgIt; + + for (msgIt = messages.begin(); msgIt != messages.end(); ++msgIt) + { + if ((*msgIt).timestamp() > msg.timestamp()) + break; + } + messages.insert(msgIt, msg); + } + } + + n = n.nextSibling(); + } // end while on messages + + } + return messages; +} + +QValueList HistoryLogger::readMessages(unsigned int lines, + const Kopete::Contact *c, Sens sens, bool reverseOrder, bool colorize) +{ + //QDate dd = QDate::currentDate().addMonths(0-m_currentMonth); + + QValueList messages; + + // A regexp useful for this function + QRegExp rxTime("(\\d+) (\\d+):(\\d+)($|:)(\\d*)"); //(with a 0.7.x compatibility) + + if(!m_metaContact) + { //this may happen if the contact has been moved, and the MC deleted + if(c && c->metaContact()) + m_metaContact=c->metaContact(); + else + return messages; + } + + if(c && !m_metaContact->contacts().contains(c) ) + return messages; + + if(sens ==0 ) //if no sens are selected, just continue in the previous sens + sens = m_oldSens ; + if( m_oldSens != 0 && sens != m_oldSens ) + { //we changed our sens! so retrieve the old position to fly in the other way + m_currentElements= m_oldElements; + m_currentMonth=m_oldMonth; + } + else + { + m_oldElements=m_currentElements; + m_oldMonth=m_currentMonth; + } + m_oldSens=sens; + + //getting the color for messages: + QColor fgColor = HistoryConfig::history_color(); + + //Hello guest! + + //there are two algoritms: + // - if a contact is given, or the metacontact contain only one contact, just read the history. + // - else, merge the history + + //the merging algoritm is the following: + // we see what contact we have to read first, and we look at the firt date before another contact + // has a message with a bigger date. + + QDateTime timeLimit; + const Kopete::Contact *currentContact=c; + if(!c && m_metaContact->contacts().count()==1) + currentContact=m_metaContact->contacts().first(); + else if(!c && m_metaContact->contacts().count()== 0) + { + return messages; + } + + while(messages.count() < lines) + { + timeLimit=QDateTime(); + QDomElement msgElem; //here is the message element + QDateTime timestamp; //and the timestamp of this message + + if(!c && m_metaContact->contacts().count()>1) + { //we have to merge the differents subcontact history + QPtrList ct=m_metaContact->contacts(); + QPtrListIterator it( ct ); + for( ; it.current(); ++it ) + { //we loop over each contact. we are searching the contact with the next message with the smallest date, + // it will becomes our current contact, and the contact with the mext message with the second smallest + // date, this date will bocomes the limit. + + QDomNode n; + if(m_currentElements.contains(*it)) + n=m_currentElements[*it]; + else //there is not yet "next message" register, so we will take the first (for the current month) + { + QDomDocument doc=getDocument(*it,m_currentMonth); + QDomElement docElem = doc.documentElement(); + n= (sens==Chronological)?docElem.firstChild() : docElem.lastChild(); + + //i can't drop the root element + workaround.append(docElem); + } + while(!n.isNull()) + { + QDomElement msgElem2 = n.toElement(); + if( !msgElem2.isNull() && msgElem2.tagName()=="msg") + { + rxTime.search(msgElem2.attribute("time")); + QDate d=QDate::currentDate().addMonths(0-m_currentMonth); + QDateTime dt( QDate(d.year() , d.month() , rxTime.cap(1).toUInt()), QTime( rxTime.cap(2).toUInt() , rxTime.cap(3).toUInt(), rxTime.cap(5).toUInt() ) ); + if(!timestamp.isValid() || ((sens==Chronological )? dt < timestamp : dt > timestamp) ) + { + timeLimit=timestamp; + timestamp=dt; + msgElem=msgElem2; + currentContact=*it; + + } + else if(!timeLimit.isValid() || ((sens==Chronological) ? timeLimit > dt : timeLimit < dt) ) + { + timeLimit=dt; + } + break; + } + n=(sens==Chronological)? n.nextSibling() : n.previousSibling(); + } + } + } + else //we don't have to merge the history. just take the next item in the contact + { + if(m_currentElements.contains(currentContact)) + msgElem=m_currentElements[currentContact]; + else + { + QDomDocument doc=getDocument(currentContact,m_currentMonth); + QDomElement docElem = doc.documentElement(); + QDomNode n= (sens==Chronological)?docElem.firstChild() : docElem.lastChild(); + msgElem=QDomElement(); + while(!n.isNull()) //continue until we get a msg + { + msgElem=n.toElement(); + if( !msgElem.isNull() && msgElem.tagName()=="msg") + { + m_currentElements[currentContact]=msgElem; + break; + } + n=(sens==Chronological)? n.nextSibling() : n.previousSibling(); + } + + //i can't drop the root element + workaround.append(docElem); + } + } + + + if(msgElem.isNull()) //we don't find ANY messages in any contact for this month. so we change the month + { + if(sens==Chronological) + { + if(m_currentMonth <= 0) + break; //there are no other messages to show. break even if we don't have nb messages + setCurrentMonth(m_currentMonth-1); + } + else + { + if(m_currentMonth >= getFirstMonth(c)) + break; //we don't have any other messages to show + setCurrentMonth(m_currentMonth+1); + } + continue; //begin the loop from the bottom, and find currentContact and timeLimit again + } + + while( + (messages.count() < lines) && + !msgElem.isNull() && + (!timestamp.isValid() || !timeLimit.isValid() || + ((sens==Chronological) ? timestamp <= timeLimit : timestamp >= timeLimit) + )) + { + // break this loop, if we have reached the correct number of messages, + // if there are no more messages for this contact, or if we reached + // the timeLimit msgElem is the next message, still not parsed, so + // we parse it now + + Kopete::Message::MessageDirection dir = (msgElem.attribute("in") == "1") ? + Kopete::Message::Inbound : Kopete::Message::Outbound; + + if(!m_hideOutgoing || dir != Kopete::Message::Outbound) + { //parse only if we don't hide it + + if( m_filter.isNull() || ( m_filterRegExp? msgElem.text().contains(QRegExp(m_filter,m_filterCaseSensitive)) : msgElem.text().contains(m_filter,m_filterCaseSensitive) )) + { + QString f=msgElem.attribute("from" ); + const Kopete::Contact *from=(f.isNull() || !currentContact) ? 0L : currentContact->account()->contacts()[f]; + + if(!from) + from= dir==Kopete::Message::Inbound ? currentContact : currentContact->account()->myself(); + + Kopete::ContactPtrList to; + to.append( dir==Kopete::Message::Inbound ? currentContact->account()->myself() : currentContact ); + + if(!timestamp.isValid()) + { + //parse timestamp only if it was not already parsed + rxTime.search(msgElem.attribute("time")); + QDate d=QDate::currentDate().addMonths(0-m_currentMonth); + timestamp=QDateTime( QDate(d.year() , d.month() , rxTime.cap(1).toUInt()), QTime( rxTime.cap(2).toUInt() , rxTime.cap(3).toUInt() , rxTime.cap(5).toUInt() ) ); + } + + Kopete::Message msg(timestamp, from, to, msgElem.text(), dir); + if (colorize) + { + msg.setBody( QString::fromLatin1("%3") + .arg( fgColor.name(), timestamp.toString(Qt::LocalDate), msg.escapedBody() ), + Kopete::Message::RichText + ); + msg.setFg( fgColor ); + } + else + { + msg.setBody( QString::fromLatin1("%2") + .arg( timestamp.toString(Qt::LocalDate), msg.escapedBody() ), + Kopete::Message::RichText + ); + } + + if(reverseOrder) + messages.prepend(msg); + else + messages.append(msg); + } + } + + //here is the point of workaround. If i drop the root element, this crashes + //get the next message + QDomNode node = ( (sens==Chronological) ? msgElem.nextSibling() : + msgElem.previousSibling() ); + + msgElem = QDomElement(); //n.toElement(); + while (!node.isNull() && msgElem.isNull()) + { + msgElem = node.toElement(); + if (!msgElem.isNull()) + { + if (msgElem.tagName() == "msg") + { + if (!c && (m_metaContact->contacts().count() > 1)) + { + // In case of hideoutgoing messages, it is faster to do + // this, so we don't parse the date if it is not needed + QRegExp rx("(\\d+) (\\d+):(\\d+):(\\d+)"); + rx.search(msgElem.attribute("time")); + + QDate d = QDate::currentDate().addMonths(0-m_currentMonth); + timestamp = QDateTime( + QDate(d.year(), d.month(), rx.cap(1).toUInt()), + QTime( rx.cap(2).toUInt(), rx.cap(3).toUInt() ) ); + } + else + timestamp = QDateTime(); //invalid + } + else + msgElem = QDomElement(); + } + + node = (sens == Chronological) ? node.nextSibling() : + node.previousSibling(); + } + m_currentElements[currentContact]=msgElem; //this is the next message + } + } + + if(messages.count() < lines) + m_currentElements.clear(); //current elements are null this can't be allowed + + return messages; +} + +QString HistoryLogger::getFileName(const Kopete::Contact* c, QDate date) +{ + + QString name = c->protocol()->pluginId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) + + QString::fromLatin1( "/" ) + + c->account()->accountId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) + + QString::fromLatin1( "/" ) + + c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) + + date.toString(".yyyyMM"); + + QString filename=locateLocal( "data", QString::fromLatin1( "kopete/logs/" ) + name+ QString::fromLatin1( ".xml" ) ) ; + + //Check if there is a kopete 0.7.x file + QFileInfo fi(filename); + if(!fi.exists()) + { + name = c->protocol()->pluginId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) + + QString::fromLatin1( "/" ) + + c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) + + date.toString(".yyyyMM"); + + QString filename2=locateLocal( "data", QString::fromLatin1( "kopete/logs/" ) + name+ QString::fromLatin1( ".xml" ) ) ; + + QFileInfo fi2(filename2); + if(fi2.exists()) + return filename2; + } + + return filename; + +} + +unsigned int HistoryLogger::getFirstMonth(const Kopete::Contact *c) +{ + if(!c) + return getFirstMonth(); + + QRegExp rx( "\\.(\\d\\d\\d\\d)(\\d\\d)" ); + QFileInfo *fi; + + // BEGIN check if there are Kopete 0.7.x + QDir d1(locateLocal("data",QString("kopete/logs/")+ + c->protocol()->pluginId().replace( QRegExp(QString::fromLatin1("[./~?*]")),QString::fromLatin1("-")) + )); + d1.setFilter( QDir::Files | QDir::NoSymLinks ); + d1.setSorting( QDir::Name ); + + const QFileInfoList *list1 = d1.entryInfoList(); + QFileInfoListIterator it1( *list1 ); + + while ( (fi = it1.current()) != 0 ) + { + if(fi->fileName().contains(c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ))) + { + rx.search(fi->fileName()); + int result = 12*(QDate::currentDate().year() - rx.cap(1).toUInt()) +QDate::currentDate().month() - rx.cap(2).toUInt(); + + if(result < 0) + { + kdWarning(14310) << k_funcinfo << "Kopete only found log file from Kopete 0.7.x made in the future. Check your date!" << endl; + break; + } + return result; + } + ++it1; + } + // END of kopete 0.7.x check + + + QDir d(locateLocal("data",QString("kopete/logs/")+ + c->protocol()->pluginId().replace( QRegExp(QString::fromLatin1("[./~?*]")),QString::fromLatin1("-")) + + QString::fromLatin1( "/" ) + + c->account()->accountId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) + )); + + d.setFilter( QDir::Files | QDir::NoSymLinks ); + d.setSorting( QDir::Name ); + + const QFileInfoList *list = d.entryInfoList(); + QFileInfoListIterator it( *list ); + while ( (fi = it.current()) != 0 ) + { + if(fi->fileName().contains(c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ))) + { + rx.search(fi->fileName()); + int result = 12*(QDate::currentDate().year() - rx.cap(1).toUInt()) +QDate::currentDate().month() - rx.cap(2).toUInt(); + if(result < 0) + { + kdWarning(14310) << k_funcinfo << "Kopete only found log file made in the future. Check your date!" << endl; + break; + } + return result; + } + ++it; + } + return 0; +} + +unsigned int HistoryLogger::getFirstMonth() +{ + if(m_cachedMonth!=-1) + return m_cachedMonth; + + if(!m_metaContact) + return 0; + + int m=0; + QPtrList contacts=m_metaContact->contacts(); + QPtrListIterator it( contacts ); + for( ; it.current(); ++it ) + { + int m2=getFirstMonth(*it); + if(m2>m) m=m2; + } + m_cachedMonth=m; + return m; +} + +void HistoryLogger::setHideOutgoing(bool b) +{ + m_hideOutgoing = b; +} + +void HistoryLogger::slotMCDeleted() +{ + m_metaContact = 0; +} + +void HistoryLogger::setFilter(const QString& filter, bool caseSensitive , bool isRegExp) +{ + m_filter=filter; + m_filterCaseSensitive=caseSensitive; + m_filterRegExp=isRegExp; +} + +QString HistoryLogger::filter() const +{ + return m_filter; +} + +bool HistoryLogger::filterCaseSensitive() const +{ + return m_filterCaseSensitive; +} + +bool HistoryLogger::filterRegExp() const +{ + return m_filterRegExp; +} + +QValueList HistoryLogger::getDaysForMonth(QDate date) +{ + QRegExp rxTime("time=\"(\\d+) \\d+:\\d+(:\\d+)?\""); //(with a 0.7.x compatibility) + + QValueList dayList; + + QPtrList contacts = m_metaContact->contacts(); + QPtrListIterator it(contacts); + + int lastDay=0; + for(; it.current(); ++it) + { +// kdDebug() << getFileName(*it, date) << endl; + QFile file(getFileName(*it, date)); + if(!file.open(IO_ReadOnly)) + { + continue; + } + QTextStream stream(&file); + QString fullText = stream.read(); + file.close(); + + int pos = 0; + while( (pos = rxTime.search(fullText, pos)) != -1) + { + pos += rxTime.matchedLength(); + int day=rxTime.capturedTexts()[1].toInt(); + + if ( day !=lastDay && dayList.find(day) == dayList.end()) // avoid duplicates + { + dayList.append(rxTime.capturedTexts()[1].toInt()); + lastDay=day; + } + } + } + return dayList; +} + +#include "historylogger.moc" diff --git a/kopete/plugins/history/historylogger.h b/kopete/plugins/history/historylogger.h new file mode 100644 index 00000000..85cdbdd7 --- /dev/null +++ b/kopete/plugins/history/historylogger.h @@ -0,0 +1,217 @@ +/* + historylogger.cpp + + Copyright (c) 2003-2004 by Olivier Goffart + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef HISTORYLOGGER_H +#define HISTORYLOGGER_H + +#include +#include "kopetemessage.h" //TODO: REMOVE + +namespace Kopete { class Contact; } +namespace Kopete { class MetaContact; } +class QFile; +class QDomDocument; +class QTimer; + +/** + * One hinstance of this class is opened for every Kopete::ChatSession, + * or for the history dialog + * + * @author Olivier Goffart + */ +class HistoryLogger : public QObject +{ +Q_OBJECT +public: + + /** + * - Chronological: messages are read from the first to the last, in the time order + * - AntiChronological: messages are read from the last to the first, in the time reversed order + */ + enum Sens { Default , Chronological , AntiChronological }; + + /** + * Constructor, takes the contact, and the color of messages + */ + HistoryLogger( Kopete::MetaContact *m , QObject *parent = 0, const char *name = 0); + HistoryLogger( Kopete::Contact *c , QObject *parent = 0, const char *name = 0); + + + ~HistoryLogger(); + + /** + * return or setif yes or no outgoing message are hidden (and not parsed) + */ + bool hideOutgoing() const { return m_hideOutgoing; } + void setHideOutgoing(bool); + + /** + * set a searching filter + * @param filter is the string to search + * @param caseSensitive say if the case is important + * @param isRegExp say if the filter is a QRegExp, or a simle string + */ + void setFilter(const QString& filter, bool caseSensitive=false , bool isRegExp=false); + QString filter() const; + bool filterCaseSensitive() const ; + bool filterRegExp() const; + + + + //---------------------------------- + + /** + * log a message + * @param c add a presision to the contact to use, if null, autodetect. + */ + void appendMessage( const Kopete::Message &msg , const Kopete::Contact *c=0L ); + + /** + * read @param lines message from the current position + * from Kopete::Contact @param c in the given @param sens + */ + QValueList readMessages(unsigned int lines, + const Kopete::Contact *c=0, Sens sens=Default, + bool reverseOrder=false, bool colorize=true); + + /** + * Same as the following, but for one date. I did'nt reuse the above function + * because its structure is really different. + * Read all the messages for the given @param date + */ + QValueList readMessages(QDate date); + + + /** + * The pausition is set to the last message + */ + void setPositionToLast(); + + /** + * The position is set to the first message + */ + void setPositionToFirst(); + + /** + * Set the current month (in number of month since the actual month) + */ + void setCurrentMonth(int month); + + /** + * @return The list of the days for which there is a log for m_metaContact for month of * @param date (don't care of the day) + */ + QValueList getDaysForMonth(QDate date); + + /** + * Get the filename of the xml file which contains the history from the + * contact in the specified @param date. Specify @param date in order to get the filename for + * the given date.year() date.month(). + */ + static QString getFileName(const Kopete::Contact* , QDate date); + +private: + bool m_hideOutgoing; + bool m_filterCaseSensitive; + bool m_filterRegExp; + QString m_filter; + + + /* + *contais all QDomDocument, for a KC, for a specified Month + */ + QMap > m_documents; + + /** + * Contains the current message. + * in fact, this is the next, still not showed + */ + QMap m_currentElements; + + /** + * Get the document, open it is @param canload is true, contain is set to false if the document + * is not already contained + */ + QDomDocument getDocument(const Kopete::Contact *c, unsigned int month , bool canLoad=true , bool* contain=0L); + + QDomDocument getDocument(const Kopete::Contact *c, const QDate date, bool canLoad=true, bool* contain=0L); + + /** + * look over files to get the last month for this contact + */ + unsigned int getFirstMonth(const Kopete::Contact *c); + unsigned int getFirstMonth(); + + + /* + * the current month + */ + unsigned int m_currentMonth; + + /* + * the cached getFirstMonth + */ + int m_cachedMonth; + + + + /* + * the metacontact we are using + */ + Kopete::MetaContact *m_metaContact; + + /* + * keep the old position in memory, so if we change the sens, we can begin here + */ + QMap m_oldElements; + unsigned int m_oldMonth; + Sens m_oldSens; + + /** + * the timer used to save the file + */ + QTimer *m_saveTimer; + QDomDocument m_toSaveDocument; + QString m_toSaveFileName; + unsigned int m_saveTimerTime; //time in ms between each save + + /** + * workaround for the 31 midnight bug. + * it contains the number of the current month. + */ + int m_realMonth; + + /* + * FIXME: + * WORKAROUND + * due to a bug in QT, i have to keep the document element in the memory to + * prevent crashes + */ + QValueList workaround; + +private slots: + /** + * the metacontact has been deleted + */ + void slotMCDeleted(); + + /** + * save the current month's document on the disk. + * connected to the m_saveTimer signal + */ + void saveToDisk(); +}; + +#endif diff --git a/kopete/plugins/history/historyplugin.cpp b/kopete/plugins/history/historyplugin.cpp new file mode 100644 index 00000000..bf8d70b4 --- /dev/null +++ b/kopete/plugins/history/historyplugin.cpp @@ -0,0 +1,194 @@ +/* + historyplugin.cpp + + Copyright (c) 2003-2004 by Olivier Goffart + (c) 2003 by Stefan Gehn + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +//#include +#include +#include + +#include "kopetechatsessionmanager.h" +#include "kopetemetacontact.h" +#include "kopeteview.h" +#include "kopetecontactlist.h" +#include "kopeteuiglobal.h" +#include "kopetemessageevent.h" +#include "kopeteviewplugin.h" + +#include "historydialog.h" +#include "historyplugin.h" +#include "historylogger.h" +#include "historyguiclient.h" +#include "historyconfig.h" + +typedef KGenericFactory HistoryPluginFactory; +static const KAboutData aboutdata("kopete_history", I18N_NOOP("History") , "1.0" ); +K_EXPORT_COMPONENT_FACTORY( kopete_history, HistoryPluginFactory( &aboutdata ) ) + +HistoryPlugin::HistoryPlugin( QObject *parent, const char *name, const QStringList & /* args */ ) +: Kopete::Plugin( HistoryPluginFactory::instance(), parent, name ), m_loggerFactory( this ) +{ + KAction *viewMetaContactHistory = new KAction( i18n("View &History" ), + QString::fromLatin1( "history" ), 0, this, SLOT(slotViewHistory()), + actionCollection(), "viewMetaContactHistory" ); + viewMetaContactHistory->setEnabled( + Kopete::ContactList::self()->selectedMetaContacts().count() == 1 ); + + connect(Kopete::ContactList::self(), SIGNAL(metaContactSelected(bool)), + viewMetaContactHistory, SLOT(setEnabled(bool))); + + connect(Kopete::ChatSessionManager::self(), SIGNAL(viewCreated(KopeteView*)), + this, SLOT(slotViewCreated(KopeteView*))); + + connect(this, SIGNAL(settingsChanged()), this, SLOT(slotSettingsChanged())); + + setXMLFile("historyui.rc"); + if(detectOldHistory()) + { + if( + KMessageBox::questionYesNo(Kopete::UI::Global::mainWidget(), + i18n( "Old history files from Kopete 0.6.x or older has been detected.\n" + "Do you want to import and convert it to the new history format?" ), + i18n( "History Plugin" ), i18n("Import && Convert"), i18n("Do Not Import") ) == KMessageBox::Yes ) + { + convertOldHistory(); + } + } + + // Add GUI action to all existing kmm objects + // (Needed if the plugin is enabled while kopete is already running) + QValueList sessions = Kopete::ChatSessionManager::self()->sessions(); + for (QValueListIterator it= sessions.begin(); it!=sessions.end() ; ++it) + { + if(!m_loggers.contains(*it)) + { + m_loggers.insert(*it, new HistoryGUIClient( *it ) ); + connect( *it, SIGNAL(closing(Kopete::ChatSession*)), + this, SLOT(slotKMMClosed(Kopete::ChatSession*))); + } + } +} + + +HistoryPlugin::~HistoryPlugin() +{ +} + + +void HistoryMessageLogger::handleMessage( Kopete::MessageEvent *event ) +{ + history->messageDisplayed( event->message() ); + MessageHandler::handleMessage( event ); +} + +void HistoryPlugin::messageDisplayed(const Kopete::Message &m) +{ + if(m.direction()==Kopete::Message::Internal || !m.manager()) + return; + + if(!m_loggers.contains(m.manager())) + { + m_loggers.insert(m.manager() , new HistoryGUIClient( m.manager() ) ); + connect(m.manager(), SIGNAL(closing(Kopete::ChatSession*)), + this, SLOT(slotKMMClosed(Kopete::ChatSession*))); + } + + HistoryLogger *l=m_loggers[m.manager()]->logger(); + if(l) + { + QPtrList mb=m.manager()->members(); + l->appendMessage(m,mb.first()); + } + + m_lastmessage=m; +} + + +void HistoryPlugin::slotViewHistory() +{ + Kopete::MetaContact *m=Kopete::ContactList::self()->selectedMetaContacts().first(); + if(m) + { + int lines = HistoryConfig::number_ChatWindow(); + + // TODO: Keep track of open dialogs and raise instead of + // opening a new (duplicated) one + new HistoryDialog(m); + } +} + + +void HistoryPlugin::slotViewCreated( KopeteView* v ) +{ + if(v->plugin()->pluginInfo()->pluginName() != QString::fromLatin1("kopete_chatwindow") ) + return; //Email chat windows are not supported. + + bool autoChatWindow = HistoryConfig::auto_chatwindow(); + int nbAutoChatWindow = HistoryConfig::number_Auto_chatwindow(); + + KopeteView *m_currentView = v; + Kopete::ChatSession *m_currentChatSession = v->msgManager(); + QPtrList mb = m_currentChatSession->members(); + + if(!m_currentChatSession) + return; //i am sorry + + if(!m_loggers.contains(m_currentChatSession)) + { + m_loggers.insert(m_currentChatSession , new HistoryGUIClient( m_currentChatSession ) ); + connect( m_currentChatSession, SIGNAL(closing(Kopete::ChatSession*)), + this , SLOT(slotKMMClosed(Kopete::ChatSession*))); + } + + if(!autoChatWindow || nbAutoChatWindow == 0) + return; + + HistoryLogger *logger = m_loggers[m_currentChatSession]->logger(); + + logger->setPositionToLast(); + + QValueList msgs = logger->readMessages(nbAutoChatWindow, + /*mb.first()*/ 0L, HistoryLogger::AntiChronological, true, true); + + // make sure the last message is not the one which will be appened right + // after the view is created (and which has just been logged in) + if( + (msgs.last().plainBody() == m_lastmessage.plainBody()) && + (m_lastmessage.manager() == m_currentChatSession)) + { + msgs.remove(msgs.fromLast()); + } + + m_currentView->appendMessages( msgs ); +} + + +void HistoryPlugin::slotKMMClosed( Kopete::ChatSession* kmm) +{ + m_loggers[kmm]->deleteLater(); + m_loggers.remove(kmm); +} + +void HistoryPlugin::slotSettingsChanged() +{ + kdDebug(14310) << k_funcinfo << "RELOADING CONFIG" << endl; + HistoryConfig::self()->readConfig(); +} + +#include "historyplugin.moc" diff --git a/kopete/plugins/history/historyplugin.h b/kopete/plugins/history/historyplugin.h new file mode 100644 index 00000000..63e2c87b --- /dev/null +++ b/kopete/plugins/history/historyplugin.h @@ -0,0 +1,106 @@ +/* + historyplugin.h + + Copyright (c) 2003-2005 by Olivier Goffart + (c) 2003 by Stefan Gehn + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef HISTORYPLUGIN_H +#define HISTORYPLUGIN_H + +#include +#include +#include + +#include "kopeteplugin.h" + +#include "kopetemessage.h" +#include "kopetemessagehandler.h" + +class KopeteView; +class KActionCollection; + +namespace Kopete +{ +class MetaContact; +class ChatSession; +} + +class HistoryPreferences; +class HistoryGUIClient; +class HistoryPlugin; + +/** + * @author Richard Smith + */ +class HistoryMessageLogger : public Kopete::MessageHandler +{ + HistoryPlugin *history; +public: + HistoryMessageLogger( HistoryPlugin *history ) : history(history) {} + void handleMessage( Kopete::MessageEvent *event ); +}; + +class HistoryMessageLoggerFactory : public Kopete::MessageHandlerFactory +{ + HistoryPlugin *history; +public: + HistoryMessageLoggerFactory( HistoryPlugin *history ) : history(history) {} + Kopete::MessageHandler *create( Kopete::ChatSession * /*manager*/, Kopete::Message::MessageDirection direction ) + { + if( direction != Kopete::Message::Inbound ) + return 0; + return new HistoryMessageLogger(history); + } + int filterPosition( Kopete::ChatSession *, Kopete::Message::MessageDirection ) + { + return Kopete::MessageHandlerFactory::InStageToSent+5; + } +}; + +/** + * @author Olivier Goffart + */ +class HistoryPlugin : public Kopete::Plugin +{ + Q_OBJECT + public: + HistoryPlugin( QObject *parent, const char *name, const QStringList &args ); + ~HistoryPlugin(); + + /** + * convert the Kopete 0.6 / 0.5 history to the new format + */ + static void convertOldHistory(); + /** + * return true if an old history has been detected, and no new ones + */ + static bool detectOldHistory(); + + void messageDisplayed(const Kopete::Message &msg); + + private slots: + void slotViewCreated( KopeteView* ); + void slotViewHistory(); + void slotKMMClosed( Kopete::ChatSession* ); + void slotSettingsChanged(); + + private: + HistoryMessageLoggerFactory m_loggerFactory; + QMap m_loggers; + Kopete::Message m_lastmessage; +}; + +#endif + + diff --git a/kopete/plugins/history/historypreferences.cpp b/kopete/plugins/history/historypreferences.cpp new file mode 100644 index 00000000..61fce469 --- /dev/null +++ b/kopete/plugins/history/historypreferences.cpp @@ -0,0 +1,88 @@ +/* + historypreferences.cpp + + Copyright (c) 2003 by Olivier Goffart + (c) 2003 by Stefan Gehn + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "historypreferences.h" +#include "historyconfig.h" +#include "historyprefsui.h" + +#include +#include +#include +#include +#include +#include + +typedef KGenericFactory HistoryConfigFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_history, HistoryConfigFactory( "kcm_kopete_history" ) ) + +HistoryPreferences::HistoryPreferences(QWidget *parent, const char*/*name*/, const QStringList &args) + : KCModule(HistoryConfigFactory::instance(), parent, args) +{ + kdDebug(14310) << k_funcinfo << "called." << endl; + (new QVBoxLayout(this))->setAutoAdd(true); + p = new HistoryPrefsUI(this); + + connect(p->chkShowPrevious, SIGNAL(toggled(bool)), this, SLOT(slotShowPreviousChanged(bool))); + connect(p->Number_Auto_chatwindow, SIGNAL(valueChanged(int)), + this, SLOT(slotModified())); + connect(p->Number_ChatWindow, SIGNAL(valueChanged(int)), + this, SLOT(slotModified())); + connect(p->History_color, SIGNAL(changed(const QColor&)), + this, SLOT(slotModified())); + load(); +} + +HistoryPreferences::~HistoryPreferences() +{ + kdDebug(14310) << k_funcinfo << "called." << endl; +} + +void HistoryPreferences::load() +{ + kdDebug(14310) << k_funcinfo << "called." << endl; + HistoryConfig::self()->readConfig(); + p->chkShowPrevious->setChecked(HistoryConfig::auto_chatwindow()); + slotShowPreviousChanged(p->chkShowPrevious->isChecked()); + p->Number_Auto_chatwindow->setValue(HistoryConfig::number_Auto_chatwindow()); + p->Number_ChatWindow->setValue(HistoryConfig::number_ChatWindow()); + p->History_color->setColor(HistoryConfig::history_color()); + //p-> HistoryConfig::browserStyle(); + emit KCModule::changed(false); +} + +void HistoryPreferences::save() +{ + kdDebug(14310) << k_funcinfo << "called." << endl; + HistoryConfig::setAuto_chatwindow(p->chkShowPrevious->isChecked()); + HistoryConfig::setNumber_Auto_chatwindow(p->Number_Auto_chatwindow->value()); + HistoryConfig::setNumber_ChatWindow(p->Number_ChatWindow->value()); + HistoryConfig::setHistory_color(p->History_color->color()); + HistoryConfig::self()->writeConfig(); + emit KCModule::changed(false); +} + +void HistoryPreferences::slotModified() +{ + emit KCModule::changed(true); +} + +void HistoryPreferences::slotShowPreviousChanged(bool on) +{ + emit KCModule::changed(true); +} + +#include "historypreferences.moc" diff --git a/kopete/plugins/history/historypreferences.h b/kopete/plugins/history/historypreferences.h new file mode 100644 index 00000000..247e2bc8 --- /dev/null +++ b/kopete/plugins/history/historypreferences.h @@ -0,0 +1,48 @@ +/* + historypreferences.h + + Copyright (c) 2003 by Olivier Goffart + (c) 2003 by Stefan Gehn + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef HISTORYPREFERENCES_H +#define HISTORYPREFERENCES_H + +#include +#include + +class HistoryPrefsUI; + +/** + * @author Stefan Gehn + */ +class HistoryPreferences : public KCModule +{ + Q_OBJECT + public: + HistoryPreferences(QWidget *parent=0, const char* name=0, + const QStringList &args = QStringList()); + ~HistoryPreferences(); + + virtual void save(); + virtual void load(); + + private slots: + void slotModified(); + void slotShowPreviousChanged(bool); + + private: + HistoryPrefsUI *p; +}; + +#endif diff --git a/kopete/plugins/history/historyprefsui.ui b/kopete/plugins/history/historyprefsui.ui new file mode 100644 index 00000000..5942a07a --- /dev/null +++ b/kopete/plugins/history/historyprefsui.ui @@ -0,0 +1,187 @@ + +HistoryPrefsUI +Olivier Goffart + + + HistoryPrefsWidget + + + + 0 + 0 + 363 + 212 + + + + HistoryPrefsWidget + + + + unnamed + + + + grpChatHistory + + + Chat History + + + + unnamed + + + + lblNoLinesPerPage + + + Number of messages per page: + + + The number of messages that are shown when browsing history in the chat window + + + + + Number_ChatWindow + + + 32768 + + + 1 + + + 10 + + + The number of message that are shown when borwsing history in the chat window + + + + + colorLabel + + + Color of messages: + + + History_color + + + Color of history messages in the chat window + + + + + History_color + + + + + + + 170 + 170 + 127 + + + + Color of history messages in the chat window + + + + + Number_Auto_chatwindow + + + 32768 + + + 1 + + + 7 + + + This is the number of messages that will be added automatically in the chat window when opening a new chat. + + + + + numberLabel + + + Number of messages to show: + + + Number_Auto_chatwindow + + + This is the number of messages that will be added automatically in the chat window when opening a new chat. + + + + + chkShowPrevious + + + Show chat history in new chats + + + true + + + When a new chat is opened, automatically add the last few messages between you and that contact. + + + + + + + spacer2 + + + Vertical + + + Expanding + + + + 31 + 90 + + + + + + + + + + chkShowPrevious + toggled(bool) + numberLabel + setEnabled(bool) + + + chkShowPrevious + toggled(bool) + Number_Auto_chatwindow + setEnabled(bool) + + + + chkShowPrevious + Number_Auto_chatwindow + History_color + + + + knuminput.h + kcolorbutton.h + knuminput.h + + diff --git a/kopete/plugins/history/historyui.rc b/kopete/plugins/history/historyui.rc new file mode 100644 index 00000000..5f72b22c --- /dev/null +++ b/kopete/plugins/history/historyui.rc @@ -0,0 +1,12 @@ + + + + + &Edit + + + + + + + diff --git a/kopete/plugins/history/historyviewer.ui b/kopete/plugins/history/historyviewer.ui new file mode 100644 index 00000000..4cef647e --- /dev/null +++ b/kopete/plugins/history/historyviewer.ui @@ -0,0 +1,347 @@ + +HistoryViewer + + + HistoryViewer + + + + 0 + 0 + 682 + 634 + + + + + 5 + 5 + 0 + 0 + + + + + 300 + 200 + + + + + unnamed + + + 0 + + + + layout3 + + + + unnamed + + + + statusLabel + + + + 32767 + 20 + + + + Ready + + + + + searchProgress + + + + + + + layout8 + + + + unnamed + + + + searchErase + + + + + + + + + + + textLabel2 + + + Search: + + + + + searchLine + + + + + searchButton + + + false + + + + 1 + 0 + 0 + 0 + + + + + 70 + 0 + + + + + 150 + 32767 + + + + Se&arch + + + + + + + splitter2 + + + Horizontal + + + + layout5 + + + + unnamed + + + 0 + + + + dateSearchLine + + + true + + + + 5 + 0 + 0 + 0 + + + + + 140 + 0 + + + + + 32767 + 32767 + + + + + + + Date + + + true + + + true + + + + + Contact + + + true + + + true + + + + dateListView + + + + 5 + 7 + 0 + 0 + + + + + 0 + 0 + + + + + 32767 + 32767 + + + + false + + + + + + + htmlFrame + + + + 5 + 5 + 10 + 0 + + + + + 0 + 0 + + + + WinPanel + + + Sunken + + + + + + layout11 + + + + unnamed + + + + textLabel1 + + + + 1 + 5 + 0 + 0 + + + + Contact: + + + + + contactComboBox + + + + 7 + 0 + 0 + 0 + + + + + + textLabel1_2 + + + Message Filter: + + + + + + All messages + + + + + Only incoming + + + + + Only outgoing + + + + messageFilterBox + + + + 0 + 0 + 0 + 0 + + + + + 200 + 0 + + + + + + + + + + + + kprogress.h + klineedit.h + klistviewsearchline.h + klistview.h + kcombobox.h + + diff --git a/kopete/plugins/history/kopete_history.desktop b/kopete/plugins/history/kopete_history.desktop new file mode 100644 index 00000000..5f14aee0 --- /dev/null +++ b/kopete/plugins/history/kopete_history.desktop @@ -0,0 +1,139 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=history +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_history +X-KDE-PluginInfo-Author=Olivier Goffart +X-KDE-PluginInfo-Email=ogoffart@tiscalinet.be +X-KDE-PluginInfo-Name=kopete_history +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true +Name=History +Name[ar]=محÙوظات +Name[az]=KeçmiÅŸ +Name[be]=ГіÑÑ‚Ð¾Ñ€Ñ‹Ñ +Name[bg]=ИÑÑ‚Ð¾Ñ€Ð¸Ñ +Name[bn]=ইতিহাস +Name[br]=Istor +Name[bs]=Historija +Name[ca]=Historial +Name[cs]=Historie +Name[cy]=Hanes +Name[da]=Historik +Name[de]=Verlauf +Name[el]=ΙστοÏικό +Name[eo]=Historio +Name[es]=Historia +Name[et]=Ajalugu +Name[eu]=Historia +Name[fa]=تاریخچه +Name[fi]=Historia +Name[fr]=Historique +Name[ga]=Stair +Name[gl]=Historial +Name[he]=היסטוריה +Name[hi]=इतिहास +Name[hr]=Povijest +Name[hu]=Ãœzenetnapló +Name[id]=Sejarah +Name[is]=Ferill +Name[it]=Cronologia +Name[ja]=履歴 +Name[ka]=ისტáƒáƒ áƒ˜áƒ +Name[kk]=Журнал +Name[km]=ប្រវážáŸ’ážáž· +Name[lt]=Istorija +Name[lv]=VÄ“sture +Name[mk]=ИÑторија +Name[mt]=KronoloÄ¡ija +Name[nb]=Historie +Name[nds]=Vörgeschicht +Name[ne]=इतिहास +Name[nl]=Geschiedenis +Name[nn]=Historie +Name[pa]=ਅਤੀਤ +Name[pl]=Historia +Name[pt]=Histórico +Name[pt_BR]=História +Name[ro]=Istoric +Name[ru]=Журнал разговоров +Name[rw]=Amateka +Name[se]=Historihkka +Name[sk]=História +Name[sl]=Zgodovina +Name[sr]=ИÑторија +Name[sr@Latn]=Istorija +Name[sv]=Historik +Name[ta]=வரலாற௠+Name[tg]=ÐомнавиÑи Ñӯҳбатҳо +Name[th]=ประวัติà¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™ +Name[tr]=GeçmiÅŸ +Name[uk]=ІÑÑ‚Ð¾Ñ€Ñ–Ñ +Name[uz]=Tarix +Name[uz@cyrillic]=Тарих +Name[ven]=Divhazwakale +Name[wa]=Istwere +Name[xh]=Imbali +Name[zh_CN]=åŽ†å² +Name[zh_HK]=歷程紀錄 +Name[zh_TW]=æ­·å² +Name[zu]=Umlando +Comment=Log all messages to keep track of your conversations +Comment[ar]=سجل جميع الرسائل للمحاÙظة على محادثاتك +Comment[be]=ЗапіÑваць уÑе паведамленні Ð´Ð»Ñ ÑтварÑÐ½Ð½Ñ Ð´Ð·Ñ‘Ð½Ð½Ñ–ÐºÐ°Ñž гутарак +Comment[bg]=Ð—Ð°Ð¿Ð¸Ñ Ð½Ð° вÑички ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ñ Ñ†ÐµÐ» преглед и Ñ‚ÑŠÑ€Ñене в Ñ‚ÑÑ… в бъдеще +Comment[bn]=আপনার কথোপকথনের খতিয়ান রাখতে সব বারà§à¦¤à¦¾ কারà§à¦¯à¦¬à¦¿à¦¬à¦°à¦£à§€à¦¤à§‡ লিখে রাখে +Comment[bs]=ZapiÅ¡i sve poruke u historiju +Comment[ca]=Registra tots els missatges per seguir les vostres converses +Comment[cs]=Záznam konverzace +Comment[cy]=Cofnodi pob neges er mwyn cadw trefn ar eich sgwrsiau +Comment[da]=Log alle beskeder for at holde styr pÃ¥ dine konversationer +Comment[de]=Protokolliert alle Nachrichten der eigenen Gespräche +Comment[el]=ΚαταγÏάψτε όλα τα μηνÏματά σας για να διατηÏήσετε αÏχείο με τις συζητήσεις σας +Comment[es]=Registra todos los mensajes para guardar sus conversaciones +Comment[et]=Kõigi sõnumite logimine, et neil ka hiljem silm peal hoida +Comment[eu]=Gorde mezu guztiak zure elkarrizketak jarrai ahal ditzazun +Comment[fa]=برای ردگیری مکالمات خود همۀ پیامها را ثبت کنید +Comment[fi]=Laita kaikki viestisi lokiin +Comment[fr]=Enregistrer tous les messages pour conserver une trace de vos discussions +Comment[gl]=Rexitra tódolas mensajex para gardar conversacións +Comment[he]=שומר תיעוד מסודר שלך כל שיחותיך +Comment[hi]=आपके वारà¥à¤¤à¤¾à¤²à¤¾à¤ª की जानकारी बनाठरखने के लिठसभी संदेशों को लॉग करें +Comment[hr]=Upisuje u dnevnik sve poruke kako biste vodili evidenciju o svojim razgovorima +Comment[hu]=Az üzenetek archiválása +Comment[is]=Halda til haga samskiptaannál +Comment[it]=Effettua il log di tutti i messaggi in modo da avere traccia delle tue conversazioni +Comment[ja]=会話を残ã™ãŸã‚ã«ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã®ãƒ­ã‚°ã‚’å–ã‚‹ +Comment[ka]=ყველრშეტყáƒáƒ‘ინების ჟურნáƒáƒšáƒ˜áƒ áƒ”ბრთქვენი სáƒáƒ£áƒ‘რების ჩáƒáƒ¡áƒáƒ¬áƒ”რáƒáƒ“ +Comment[kk]=ХабарлаÑу барыÑын журналға жазып отыру +Comment[km]=ចុះ​កំណážáŸ‹â€‹áž áŸážáž»â€‹ážŸáž¶ážšâ€‹áž‘ាំងអស់ ដើម្បី​ážáž¶áž˜ážŠáž¶áž“​ការ​សន្ទនា​របស់​អ្នក +Comment[lt]=Ä®raÅ¡inÄ—ti visas žinutes ir vesti pokalbių žurnalÄ… +Comment[mk]=Ги зачувува Ñите пораки за да ги Ñледите вашите разговори +Comment[nb]=Logg alle meldinger for Ã¥ ta vare pÃ¥ samtalene dine +Comment[nds]=All Narichten för't Nakieken na't Logbook schrieven +Comment[ne]=तपाईà¤à¤•à¥‹ वारà¥à¤¤à¤¾à¤²à¤¾à¤ªà¤•à¥‹ टà¥à¤°à¤¯à¤¾à¤• राखà¥à¤¨ सबै सनà¥à¤¦à¥‡à¤¶ लग गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Bewaar alle berichten in een logboek om uw conversaties later opnieuw te kunnen bekijken +Comment[nn]=Logg alle meldingar for Ã¥ ta vare pÃ¥ samtalane dine +Comment[pl]=Zapisuje wszystkie wiadomoÅ›ci, aby trzymać historiÄ™ Twoich rozmów +Comment[pt]=Regista todas as mensagens para manter um registo da sua conversa +Comment[pt_BR]=Registra todas as mensagens para manter o histórico de suas conversações +Comment[ru]=Делать запиÑи ваших разговоров в журнале +Comment[se]=Vurke buot dieđáhusaid vai oaidnit du ságastallamiid +Comment[sk]=Záznam vÅ¡etkých správ, aby ste mohli sledovaÅ¥ vaÅ¡e rozhovory +Comment[sl]=Beleži vsa sporoÄila za hranjenje vaÅ¡ih pogovorov +Comment[sr]=УпиÑује у дневник Ñве поруке да би Ñте водили евиденцију о Ñвојим разговорима +Comment[sr@Latn]=Upisuje u dnevnik sve poruke da bi ste vodili evidenciju o svojim razgovorima +Comment[sv]=Logga alla meddelanden för att hÃ¥lla ordning pÃ¥ samtalen +Comment[ta]=உஙà¯à®•à®³à¯ உரையாடலை கவனிகà¯à®• அனைதà¯à®¤à¯ செயà¯à®¤à®¿à®•à®³à¯ˆà®¯à¯à®®à¯ பà¯à®•à¯à®ªà®¤à®¿ +Comment[tg]=Сабти ҳамаи пайёмҳо барои пайгардии ҳамаи Ñӯҳбатҳои шумо +Comment[tr]=KonuÅŸmalarınızın kaydedildiÄŸi bütün günlük mesajları +Comment[uk]=Робити запиÑи в журналі Ð´Ð»Ñ ÑÐ»Ñ–Ð´ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð° вашими розмовами +Comment[wa]=WÃ¥rder on djournÃ¥ di tos vos messaedjes, po vos poleur rivey li conversÃ¥cion +Comment[zh_CN]=记录您对è¯çš„å…¨éƒ¨æ¶ˆæ¯ +Comment[zh_HK]=記錄所有訊æ¯ï¼Œè®“您能追查您的å°è©±ç´€éŒ„ +Comment[zh_TW]=紀錄所有å°è©±è¨Šæ¯ diff --git a/kopete/plugins/history/kopete_history_config.desktop b/kopete/plugins/history/kopete_history_config.desktop new file mode 100644 index 00000000..5ee2d6b2 --- /dev/null +++ b/kopete/plugins/history/kopete_history_config.desktop @@ -0,0 +1,141 @@ +[Desktop Entry] +Icon=history +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_history +X-KDE-FactoryName=HistoryConfigFactory +X-KDE-ParentApp=kopete_history +X-KDE-ParentComponents=kopete_history + +Name=History +Name[ar]=محÙوظات +Name[az]=KeçmiÅŸ +Name[be]=ГіÑÑ‚Ð¾Ñ€Ñ‹Ñ +Name[bg]=ИÑÑ‚Ð¾Ñ€Ð¸Ñ +Name[bn]=ইতিহাস +Name[br]=Istor +Name[bs]=Historija +Name[ca]=Historial +Name[cs]=Historie +Name[cy]=Hanes +Name[da]=Historik +Name[de]=Verlauf +Name[el]=ΙστοÏικό +Name[eo]=Historio +Name[es]=Historia +Name[et]=Ajalugu +Name[eu]=Historia +Name[fa]=تاریخچه +Name[fi]=Historia +Name[fr]=Historique +Name[ga]=Stair +Name[gl]=Historial +Name[he]=היסטוריה +Name[hi]=इतिहास +Name[hr]=Povijest +Name[hu]=Ãœzenetnapló +Name[id]=Sejarah +Name[is]=Ferill +Name[it]=Cronologia +Name[ja]=履歴 +Name[ka]=ისტáƒáƒ áƒ˜áƒ +Name[kk]=Журнал +Name[km]=ប្រវážáŸ’ážáž· +Name[lt]=Istorija +Name[lv]=VÄ“sture +Name[mk]=ИÑторија +Name[mt]=KronoloÄ¡ija +Name[nb]=Historie +Name[nds]=Vörgeschicht +Name[ne]=इतिहास +Name[nl]=Geschiedenis +Name[nn]=Historie +Name[pa]=ਅਤੀਤ +Name[pl]=Historia +Name[pt]=Histórico +Name[pt_BR]=História +Name[ro]=Istoric +Name[ru]=Журнал разговоров +Name[rw]=Amateka +Name[se]=Historihkka +Name[sk]=História +Name[sl]=Zgodovina +Name[sr]=ИÑторија +Name[sr@Latn]=Istorija +Name[sv]=Historik +Name[ta]=வரலாற௠+Name[tg]=ÐомнавиÑи Ñӯҳбатҳо +Name[th]=ประวัติà¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™ +Name[tr]=GeçmiÅŸ +Name[uk]=ІÑÑ‚Ð¾Ñ€Ñ–Ñ +Name[uz]=Tarix +Name[uz@cyrillic]=Тарих +Name[ven]=Divhazwakale +Name[wa]=Istwere +Name[xh]=Imbali +Name[zh_CN]=åŽ†å² +Name[zh_HK]=歷程紀錄 +Name[zh_TW]=æ­·å² +Name[zu]=Umlando +Comment=History Plugin +Comment[ar]=توصيلة المحÙوظات +Comment[be]=Модуль гіÑторыі +Comment[bg]=ПриÑтавка за иÑториÑта +Comment[bn]=ইতিহাস পà§à¦²à¦¾à¦—িন +Comment[br]=Lugent an istorig +Comment[bs]=Dodatak za historiju +Comment[ca]=Connector de l'historial +Comment[cs]=Modul historie +Comment[cy]=Ategyn Hanes +Comment[da]=Historik-plugin +Comment[de]=Verlaufsmodul +Comment[el]=ΠÏόσθετο ιστοÏÎ¹ÎºÎ¿Ï +Comment[eo]=Historio-kromaĵo +Comment[es]=Complemento de Historial +Comment[et]=Ajalooplugin +Comment[eu]=Historia plugin-a +Comment[fa]=وصلۀ تاریخچه +Comment[fi]=Historia-liitännäinen +Comment[fr]=Module d'historique +Comment[ga]=Breiseán Staire +Comment[gl]=Plugin de historial +Comment[he]=תוסף ההיסטוריה +Comment[hi]=इतिहास पà¥à¤²à¤—इन +Comment[hr]=Umetak za povijest +Comment[hu]=ElÅ‘zmények bÅ‘vítÅ‘modul +Comment[is]=Ferilsíforrit +Comment[it]=Plugin cronologia +Comment[ja]=履歴プラグイン +Comment[ka]=ისტáƒáƒ áƒ˜áƒ˜áƒ¡ მáƒáƒ“ული +Comment[kk]=Журнал плагин модулі +Comment[km]=កម្មវិធី​ជំនួយ​ប្រវážáŸ’ážáž· +Comment[lt]=Istorijos įskiepis +Comment[mk]=Приклучок за иÑторија +Comment[nb]=Programtillegg for historie +Comment[nds]=Vörgeschichtmoduul +Comment[ne]=इतिहास पà¥à¤²à¤—इन +Comment[nl]=Geschiedenis-plugin +Comment[nn]=Programtillegg for historie +Comment[pl]=Wtyczka historii +Comment[pt]='Plugin' de Historial +Comment[pt_BR]=Plugin de Histórico +Comment[ro]=Modul istoric +Comment[ru]=Модуль Ð¶ÑƒÑ€Ð½Ð°Ð»Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ +Comment[se]=Historihkkalassemoduvla +Comment[sk]=Modul histórie +Comment[sl]=Vstavek Zgodovina +Comment[sr]=Прикључак за иÑторијат +Comment[sr@Latn]=PrikljuÄak za istorijat +Comment[sv]=Historikinsticksprogram +Comment[ta]=வரலாறà¯à®±à¯ செரà¯à®•à®²à¯ +Comment[tg]=Модули ÐомнавиÑи Ñӯҳбатҳо +Comment[tr]=GeçmiÅŸ Eklentisi +Comment[uk]=Втулок Ñ–Ñторії +Comment[uz]=Tarix plagini +Comment[uz@cyrillic]=Тарих плагини +Comment[wa]=Tchôke-divins del istwere +Comment[zh_CN]=历å²æ’件 +Comment[zh_HK]=歷程紀錄æ’件 +Comment[zh_TW]=æ­·å²å¤–æŽ›ç¨‹å¼ diff --git a/kopete/plugins/latex/Makefile.am b/kopete/plugins/latex/Makefile.am new file mode 100644 index 00000000..924da747 --- /dev/null +++ b/kopete/plugins/latex/Makefile.am @@ -0,0 +1,27 @@ +METASOURCES = AUTO + +SUBDIRS = icons + +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kopete_latex.la kcm_kopete_latex.la + +kopete_latex_la_SOURCES = latexplugin.cpp latexconfig.kcfgc latexguiclient.cpp +kopete_latex_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kopete_latex_la_LIBADD = ../../libkopete/libkopete.la + +kcm_kopete_latex_la_SOURCES = latexprefsbase.ui latexpreferences.cpp latexconfig.kcfgc +kcm_kopete_latex_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_latex_la_LIBADD = $(LIB_KOPETECOMPAT) $(LIB_KUTILS) + +service_DATA = kopete_latex.desktop +servicedir = $(kde_servicesdir) + +kcm_DATA = kopete_latex_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog + +bin_SCRIPTS = kopete_latexconvert.sh +kde_kcfg_DATA = latexconfig.kcfg + +mydatadir = $(kde_datadir)/kopete_latex +mydata_DATA = latexchatui.rc \ No newline at end of file diff --git a/kopete/plugins/latex/icons/Makefile.am b/kopete/plugins/latex/icons/Makefile.am new file mode 100644 index 00000000..224eb420 --- /dev/null +++ b/kopete/plugins/latex/icons/Makefile.am @@ -0,0 +1,3 @@ +kopeteicondir = $(kde_datadir)/kopete/icons +kopeteicon_ICON = AUTO + diff --git a/kopete/plugins/latex/icons/cr32-app-latex.png b/kopete/plugins/latex/icons/cr32-app-latex.png new file mode 100644 index 00000000..69df3f61 Binary files /dev/null and b/kopete/plugins/latex/icons/cr32-app-latex.png differ diff --git a/kopete/plugins/latex/kopete_latex.desktop b/kopete/plugins/latex/kopete_latex.desktop new file mode 100644 index 00000000..3d957701 --- /dev/null +++ b/kopete/plugins/latex/kopete_latex.desktop @@ -0,0 +1,71 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=latex +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_latex +X-KDE-PluginInfo-Author=Duncan Mac-Vicar +X-KDE-PluginInfo-Email=duncan@kde.org +X-KDE-PluginInfo-Name=kopete_latex +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=KopeTeX +Name[bn]=কপেটেক +Name[ja]=KopeTex +Name[ne]=कोपेटेकà¥à¤¸ +Name[sv]=Kopetex +Comment=Render Latex formulas in the chatwindow +Comment[be]=Паказваць формулы Latex у вакне гутаркі +Comment[bg]=Показване на формули на Latex в прозореца за чат +Comment[bn]=চà§à¦¯à¦¾à¦Ÿ উইনà§à¦¡à§‹à¦¤à§‡ লেটেক ফরà§à¦®à§‚লা পà§à¦°à¦¦à¦°à§à¦¶à¦¨ করে +Comment[bs]=Iscrtava Latex formule u chat prozoru +Comment[ca]=Representa fórmules Latex a la finestra de xat +Comment[cs]=Vykresluje vzorce LaTeXu v oknÄ› rozhovoru +Comment[da]=Viser Latex-formler i chat-vinduet +Comment[de]=Latex-Formeln in einem Chat-Fenster anzeigen +Comment[el]=Εξισώσεις Latex στο παÏάθυÏο συνομιλίας +Comment[es]=Muestra fórmulas LaTeX en la ventana de charla +Comment[et]=LaTeXi valemite renderdamine vestlusaknas +Comment[eu]=Elkarrizketa leihoaetan Latex formulak marrazten ditu +Comment[fa]=نمایش Ùرمولهای Latex در پنجرۀ Ú¯Ùتگو +Comment[fi]=Renderöi Latex-kaavoja keskusteluikkunaan +Comment[fr]=Affichage de formules LaTeX dans la fenêtre de discussion +Comment[gl]=Debuxar as fórmulas de Laxtex na fiestra de conversa +Comment[he]=מציג נוסח×ות של Latex בחלון השיחה +Comment[hu]=Latex-es képletek megjelenítése a csevegési ablakban +Comment[is]=Teikna Latex formúlur í spjallglugganum +Comment[it]=Visualizza formule latex nella finestra di chat +Comment[ja]=ãƒãƒ£ãƒƒãƒˆã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ã§ LaTeX ã®æ•°å¼ã‚’表示 +Comment[ka]=Latex ფáƒáƒ áƒ›áƒ£áƒšáƒ”ბის სáƒáƒ£áƒ‘რის ფáƒáƒœáƒ¯áƒáƒ áƒáƒ¨áƒ˜ რენდერი +Comment[kk]=Latex формулаларын әңгіме терезеÑінде келтіру +Comment[km]=បង្ហាញ​រូបមន្ហLatex នៅ​ក្នុង​បង្អួច​ជជែក​កំសាន្ដ +Comment[lt]=Vykdyti Latex formules pokalbių lange +Comment[mk]=ИÑцртува Latex формули во прозорецот за разговори +Comment[nb]=Tegn LaTeX-formler i pratevinduet +Comment[nds]=Latex-Formeln binnen dat Klöönfinster wiesen +Comment[ne]=कà¥à¤°à¤¾à¤•à¤¾à¤¨à¥€ सञà¥à¤à¥à¤¯à¤¾à¤²à¤®à¤¾ लà¥à¤¯à¤¾à¤Ÿà¥‡à¤•à¥à¤¸ सूतà¥à¤° रेनà¥à¤¡à¤° गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Latex-formules renderen in het gespreksvenster +Comment[nn]=Vis Latex-formlar i pratevindauget +Comment[pl]=WyÅ›wietla wyrażenia Latexa w oknie rozmowy +Comment[pt]=Mostrar formulas de Latex na janela de conversação +Comment[pt_BR]=Renderiza fórmulas do Latex em uma janela de bate-papo +Comment[ro]=Randează formule LaTeX în fereastra de discuÅ£ii +Comment[ru]=Ð’Ñтавка и вывод формул Latex в окнах разговоров Kopete +Comment[se]=Sárggo LaTeX-hámuid Äáttenláses +Comment[sk]=Zobrazovanie výrazov Latex v okne rozhovoru +Comment[sl]=Izris formul LaTeX v oknu za klepet +Comment[sr]=Приказ Latex формула у прозору за ћаÑкање +Comment[sr@Latn]=Prikaz Latex formula u prozoru za ćaskanje +Comment[sv]=Visa Latex-formler i chattfönstret +Comment[ta]= Render Latex formulas in the chatwindow +Comment[tg]=Формулаҳои Render Latex дар тирезаи чат +Comment[tr]=Sohbet penceresindeki Latex formüllerini verir +Comment[uk]=Ð’Ñ–Ð´Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ„Ð¾Ñ€Ð¼ÑƒÐ» Latex у вікнах розмов Kopete +Comment[zh_CN]=在èŠå¤©çª—å£ä¸­æ¸²æŸ“ Latex å…¬å¼ +Comment[zh_HK]=在èŠå¤©è¦–窗內顯示 Latex æ–¹ç¨‹å¼ +Comment[zh_TW]=在èŠå¤©è¦–窗中加入 Latex å…¬å¼ + diff --git a/kopete/plugins/latex/kopete_latex_config.desktop b/kopete/plugins/latex/kopete_latex_config.desktop new file mode 100644 index 00000000..a5e67a2a --- /dev/null +++ b/kopete/plugins/latex/kopete_latex_config.desktop @@ -0,0 +1,69 @@ +[Desktop Entry] +Icon=latex +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_latex +X-KDE-FactoryName=LatexConfigFactory +X-KDE-ParentApp=kopete_latex +X-KDE-ParentComponents=kopete_latex + +Name=Highlight +Name[ar]=تمييز +Name[bg]=ОткроÑване +Name[bn]=গà§à¦°à§à¦¤à§à¦¬à¦ªà§‚রà§à¦£ +Name[br]=Splannadur +Name[bs]=Isticanje +Name[ca]=Ressaltat +Name[cs]=ZvýraznÄ›ní +Name[cy]=Amlygu +Name[da]=Fremhæv +Name[de]=Hervorhebung +Name[el]=Τονισμός +Name[eo]=Lumaĵo +Name[es]=Resaltar +Name[et]=Esiletõstmine +Name[eu]=Nabarmendu +Name[fa]=مشخص +Name[fi]=Korostus +Name[fr]=Surlignement +Name[ga]=Aibhsiú +Name[gl]=Resaltar +Name[he]=מודגש +Name[hi]=उभारें +Name[hr]=Isticanje +Name[hu]=Kiemelés +Name[is]=Merkja +Name[it]=Evidenziazione +Name[ja]=強調 +Name[ka]=მáƒáƒ áƒ™áƒ˜áƒ áƒ”ბული +Name[kk]=Ерекше +Name[km]=សំážáž¶áž“់ +Name[lt]=ParyÅ¡kinti +Name[mk]=ОÑветлување +Name[nb]=Marker +Name[nds]=Rutheven +Name[ne]=हाइलाइट +Name[nl]=Aanwijzen +Name[nn]=Marker +Name[pa]=ਉਘਾੜਨ +Name[pl]=PodÅ›wietlenie +Name[pt]=Realce +Name[pt_BR]=Destaque +Name[ro]=EvidenÅ£iat +Name[ru]=Выделение +Name[se]=Merke +Name[sk]=ZvýrazniÅ¥ +Name[sl]=Poudarjeno sporoÄilo +Name[sr]=ИÑтицање +Name[sr@Latn]=Isticanje +Name[sv]=Markera +Name[ta]=à®®à¯à®©à¯ˆà®ªà¯à®ªà¯à®±à¯à®¤à¯à®¤à®²à¯ +Name[tg]=Равшаннамоӣ +Name[tr]=Vurgu +Name[uk]=ПідÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ +Name[wa]=E sorbiyance +Name[zh_CN]=çªå‡ºæ˜¾ç¤º +Name[zh_HK]=加強顯示 +Name[zh_TW]=高亮度 diff --git a/kopete/plugins/latex/kopete_latexconvert.sh b/kopete/plugins/latex/kopete_latexconvert.sh new file mode 100755 index 00000000..b7f92263 --- /dev/null +++ b/kopete/plugins/latex/kopete_latexconvert.sh @@ -0,0 +1,234 @@ +#!/bin/sh +############################################################# +# TEX2IM: Converts LaTeX formulas to pixel graphics which # +# can be easily included in Text-Processors like # +# M$ or Staroffice. # +# # +# Required software: latex, convert (image magic) # +# to get color, the latex color package is required # +############################################################# +# Version 1.8 (http://www.nought.de/tex2im.html) # +# published under the GNU public licence (GPL) # +# (c) 14. May 2004 by Andreas Reigber # +# Email: anderl@nought.de # +############################################################# + +# +# Default values +# + +resolution="150x150" +format="png" +color1="white" +color2="black" +trans=1 +noformula=0 +aa=1 +extra_header="$HOME/.tex2im_header" + +if [ -f ~/.tex2imrc ]; then + source ~/.tex2imrc +fi + +OPTERR=0 + +if [ $# -lt 1 ]; then + echo "Usage: `basename $0` [options] file.tex, for help give option -h" 1>&2 + exit 1 +fi + +while getopts hanzb:t:f:o:r:vx: Optionen; do + case $Optionen in + h) echo "tex2im [options] latex_expression + +The content of input file should be _plain_ latex mathmode code! +Alternatively, a string containing the latex code can be specified. + +Options: +-v show version +-h show help +-a change status of antialiasing + default is on for normal mode and + off for transparent mode +-o file specifies output filename, + default is inputfile with new extension +-f expr specifies output format, + possible examples: gif, jpg, tif...... + all formates supported by 'convert' should work, + default: png +-r expr specifies desired resolution in dpi, + possible examples: 100x100, 300x300, 200x150, + default is 150x150 +-b expr specifies the background color + default: white +-t expr specifies the text color + default: black +-n no-formula mode (do not wrap in eqnarray* environment) + default: off +-z transparent background + default: off +-x file file containing extra header lines. + default: ~/.tex2im_header" + exit 0 ;; + v) echo "TEX2IM Version 1.8" + exit 0 ;; + r) resolution=$OPTARG;; + o) outfile=$OPTARG;; + z) trans=1 + aa=0;; + a) if [ $aa -eq 0 ]; then + aa=1 + else + aa=0 + fi;; + n) noformula=1;; + b) color1=$OPTARG;; + t) color2=$OPTARG;; + f) format=$OPTARG;; + x) extra_header=$OPTARG;; + esac +done + +# +# Generate temporary directory +# + +if test -n "`type -p mktemp`" ; then + tmpdir="`mktemp /tmp/tex2imXXXXXX`" + rm $tmpdir + mkdir $tmpdir +else + tmpdir=/tmp/tex2im$$ + if [ -e $tmpdir ] ; then + echo "$0: Temporary directory $tmpdir already exists." 1>&2 + exit 1 + fi + mkdir $tmpdir +fi +homedir="`pwd`" || exit 1 + +# +# Names for input and output files +# + +while [ $OPTIND -le $# ] +do + +eval infile=\$${OPTIND} + +if [ -z $outfile ]; then + if [ -e "$infile" ]; then + base=`basename ${infile} .tex` ; + outfile=${base}.$format + else + outfile=out.$format + fi +fi + +# +# Here we go +# + +( +cat << ENDHEADER1 +\documentclass[12pt]{article} +\usepackage{color} +\usepackage[dvips]{graphicx} +\pagestyle{empty} +ENDHEADER1 +) > $tmpdir/out.tex + +# +# Do we have a file containing extra files to include into the header? +# + +if [ -f $extra_header ]; then + ( + cat $extra_header + ) >> $tmpdir/out.tex +fi + +if [ $noformula -eq 1 ]; then +( +cat << ENDHEADER2 +\pagecolor{$color1} +\begin{document} +{\color{$color2} +ENDHEADER2 +) >> $tmpdir/out.tex +else +( +cat << ENDHEADER2 +\pagecolor{$color1} +\begin{document} +{\color{$color2} +\begin{eqnarray*} +ENDHEADER2 +) >> $tmpdir/out.tex +fi + +# Kopete does not need to parse the content of a file. +#if [ -e "$infile" ]; then +# cat $infile >> $tmpdir/out.tex +#else + echo "$infile" >> $tmpdir/out.tex +#fi + +if [ $noformula -eq 1 ]; then +( +cat << ENDFOOTER +}\end{document} +ENDFOOTER +) >> $tmpdir/out.tex +else +( +cat << ENDFOOTER +\end{eqnarray*}} +\end{document} +ENDFOOTER +) >> $tmpdir/out.tex +fi + +cd $tmpdir +for f in $homedir/*.eps; do + test -f ${f##*/} || ln -s $f . # multi-processing! +done +latex -interaction=batchmode out.tex > /dev/null +cd "$homedir" +dvips -o $tmpdir/out.eps -E $tmpdir/out.dvi 2> /dev/null + +# +# Transparent background +# + +if [ $trans -eq 1 ]; then + if [ $aa -eq 1 ]; then + convert +adjoin -antialias -transparent $color1 -density $resolution $tmpdir/out.eps $tmpdir/out.$format + else + convert +adjoin +antialias -transparent $color1 -density $resolution $tmpdir/out.eps $tmpdir/out.$format + fi +else + if [ $aa -eq 1 ]; then + convert +adjoin -antialias -density $resolution $tmpdir/out.eps $tmpdir/out.$format + else + convert +adjoin +antialias -density $resolution $tmpdir/out.eps $tmpdir/out.$format + fi +fi + + +if [ -e $tmpdir/out.$format ]; then + mv $tmpdir/out.$format $outfile +else + mv $tmpdir/out.$format.0 $outfile +fi + +let OPTIND=$OPTIND+1 +outfile="" +done + +# +# Cleanup +# + +rm -rf $tmpdir +exit 0 diff --git a/kopete/plugins/latex/latexchatui.rc b/kopete/plugins/latex/latexchatui.rc new file mode 100644 index 00000000..06e8c03a --- /dev/null +++ b/kopete/plugins/latex/latexchatui.rc @@ -0,0 +1,9 @@ + + + + + &Tools + + + + diff --git a/kopete/plugins/latex/latexconfig.kcfg b/kopete/plugins/latex/latexconfig.kcfg new file mode 100644 index 00000000..f6d0b335 --- /dev/null +++ b/kopete/plugins/latex/latexconfig.kcfg @@ -0,0 +1,19 @@ + + + + + + + + + 150 + + + + 150 + + + diff --git a/kopete/plugins/latex/latexconfig.kcfgc b/kopete/plugins/latex/latexconfig.kcfgc new file mode 100644 index 00000000..9e4e4fec --- /dev/null +++ b/kopete/plugins/latex/latexconfig.kcfgc @@ -0,0 +1,7 @@ +# Code generation options for kconfig_compiler +File=latexconfig.kcfg +ClassName=LatexConfig +Singleton=true +Mutators=true +MemberVariables=private +GlobalEnums=true diff --git a/kopete/plugins/latex/latexguiclient.cpp b/kopete/plugins/latex/latexguiclient.cpp new file mode 100644 index 00000000..8d7cbf3e --- /dev/null +++ b/kopete/plugins/latex/latexguiclient.cpp @@ -0,0 +1,76 @@ +/* + latexguiclient.cpp + + Kopete Latex plugin + + Copyright (c) 2003-2005 by Olivier Goffart + + Kopete (c) 2003-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include + +#include +#include +#include +#include +#include +#include + +#include "kopetechatsession.h" +#include "kopeteview.h" +#include "kopetemessage.h" + +#include "latexplugin.h" +#include "latexguiclient.h" + +LatexGUIClient::LatexGUIClient( Kopete::ChatSession *parent, const char *name ) +: QObject( parent, name ), KXMLGUIClient( parent ) +{ + setInstance( LatexPlugin::plugin()->instance() ); + connect( LatexPlugin::plugin(), SIGNAL( destroyed( QObject * ) ), this, SLOT( deleteLater() ) ); + + m_manager = parent; + + new KAction( i18n( "Preview Latex Images" ), "latex", CTRL + Key_L, this, SLOT( slotPreview() ), actionCollection(), "latexPreview" ); + + setXMLFile( "latexchatui.rc" ); +} + +LatexGUIClient::~LatexGUIClient() +{ +} + +void LatexGUIClient::slotPreview() +{ + if ( !m_manager->view() ) + return; + + Kopete::Message msg = m_manager->view()->currentMessage(); + QString messageText = msg.plainBody(); + if(!messageText.contains("$$")) //we haven't found any latex strings + { + KMessageBox::sorry(reinterpret_cast(m_manager->view()) , i18n("There are no latex in the message you are typing. The latex formula must be included between $$ and $$ "), i18n("No Latex Formula") ); + return; + } + + msg=Kopete::Message( msg.from() , msg.to() , + i18n("Preview of the latex message :
    %1").arg(msg.plainBody()), + Kopete::Message::Internal , Kopete::Message::RichText); + m_manager->appendMessage(msg) ; +} + + +#include "latexguiclient.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/latex/latexguiclient.h b/kopete/plugins/latex/latexguiclient.h new file mode 100644 index 00000000..c8ca9e99 --- /dev/null +++ b/kopete/plugins/latex/latexguiclient.h @@ -0,0 +1,53 @@ +/* + latexguiclient.h + + Kopete Latex Plugin + + Copyright (c) 2005 by Olivier Goffart + + Kopete (c) 2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef TRANSLATORGUICLIENT_H +#define TRANSLATORGUICLIENT_H + +#include +#include + +#include + +#include "kopetemessage.h" +#include "kopeteplugin.h" + +namespace Kopete { class ChatSession; } + +/** + * @author Olivier Goffart + */ + +class LatexGUIClient : public QObject , public KXMLGUIClient +{ + Q_OBJECT + +public: + LatexGUIClient( Kopete::ChatSession *parent, const char *name=0L); + ~LatexGUIClient(); + +private slots: + void slotPreview(); + +private: + Kopete::ChatSession *m_manager; +}; + +#endif + diff --git a/kopete/plugins/latex/latexplugin.cpp b/kopete/plugins/latex/latexplugin.cpp new file mode 100644 index 00000000..7ceab209 --- /dev/null +++ b/kopete/plugins/latex/latexplugin.cpp @@ -0,0 +1,259 @@ +/* + Kopete Latex Plugin + + Copyright (c) 2004 by Duncan Mac-Vicar Prett + Copyright (c) 2004-2005 by Olivier Goffart + + Kopete (c) 2001-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopetechatsessionmanager.h" +#include "kopeteuiglobal.h" + +#include "latexplugin.h" +#include "latexconfig.h" +#include "latexguiclient.h" + +#define ENCODED_IMAGE_MODE 0 + +typedef KGenericFactory LatexPluginFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_latex, LatexPluginFactory( "kopete_latex" ) ) + +LatexPlugin::LatexPlugin( QObject *parent, const char *name, const QStringList &/*args*/ ) +: Kopete::Plugin( LatexPluginFactory::instance(), parent, name ) +{ +// kdDebug() << k_funcinfo << endl; + if( !s_pluginStatic ) + s_pluginStatic = this; + + mMagickNotFoundShown = false; + connect( Kopete::ChatSessionManager::self(), SIGNAL( aboutToDisplay( Kopete::Message & ) ), SLOT( slotMessageAboutToShow( Kopete::Message & ) ) ); + connect( Kopete::ChatSessionManager::self(), SIGNAL( aboutToSend(Kopete::Message& ) ), this, SLOT(slotMessageAboutToSend(Kopete::Message& ) ) ); + connect ( this , SIGNAL( settingsChanged() ) , this , SLOT( slotSettingsChanged() ) ); + connect( Kopete::ChatSessionManager::self(), SIGNAL( chatSessionCreated( Kopete::ChatSession * ) ), + this, SLOT( slotNewChatSession( Kopete::ChatSession * ) ) ); + + m_convScript = KStandardDirs::findExe("kopete_latexconvert.sh"); + slotSettingsChanged(); + + //Add GUI action to all already existing kmm (if the plugin is launched when kopete already rining) + QValueList sessions = Kopete::ChatSessionManager::self()->sessions(); + for (QValueListIterator it= sessions.begin(); it!=sessions.end() ; ++it) + slotNewChatSession( *it ); +} + +LatexPlugin::~LatexPlugin() +{ + s_pluginStatic = 0L; +} + +LatexPlugin* LatexPlugin::plugin() +{ + return s_pluginStatic ; +} + +LatexPlugin* LatexPlugin::s_pluginStatic = 0L; + +void LatexPlugin::slotNewChatSession( Kopete::ChatSession *KMM ) +{ + new LatexGUIClient( KMM ); +} + + +void LatexPlugin::slotMessageAboutToShow( Kopete::Message& msg ) +{ + QString mMagick = KStandardDirs::findExe("convert"); + if ( mMagick.isEmpty() ) + { + // show just once + if ( !mMagickNotFoundShown ) + { + KMessageBox::queuedMessageBox( + Kopete::UI::Global::mainWidget(), + KMessageBox::Error, i18n("I cannot find the Magick convert program.\nconvert is required to render the Latex formulas.\nPlease go to www.imagemagick.org or to your distribution site and get the right package.") + ); + mMagickNotFoundShown = true; + } + // dont try to parse if convert is not installed + return; + } + + QString messageText = msg.plainBody(); + if( !messageText.contains("$$")) + return; + + //kdDebug() << k_funcinfo << " Using converter: " << m_convScript << endl; + + // /\[([^]]).*?\[/$1\]/ + // \$\$.+?\$\$ + + // this searches for $$formula$$ + QRegExp rg("\\$\\$.+\\$\\$"); + rg.setMinimal(true); + // this searches for [latex]formula[/latex] + //QRegExp rg("\\[([^]\]).*?\\[/$1\\]"); + + int pos = 0; + + QMap replaceMap; + while (pos >= 0 && (unsigned int)pos < messageText.length()) + { +// kdDebug() << k_funcinfo << " searching pos: " << pos << endl; + pos = rg.search(messageText, pos); + + if (pos >= 0 ) + { + QString match = rg.cap(0); + pos += rg.matchedLength(); + + QString formul=match; + if(!securityCheck(formul)) + continue; + + QString fileName=handleLatex(formul.replace("$$","")); + + // get the image and encode it with base64 + #if ENCODED_IMAGE_MODE + QImage renderedImage( fileName ); + imagePxWidth = renderedImage.width(); + imagePxHeight = renderedImage.height(); + if ( !renderedImage.isNull() ) + { + QByteArray ba; + QBuffer buffer( ba ); + buffer.open( IO_WriteOnly ); + renderedImage.save( &buffer, "PNG" ); + QString imageURL = QString::fromLatin1("data:image/png;base64,%1").arg( KCodecs::base64Encode( ba ) ); + replaceMap[match] = imageURL; + } + #else + replaceMap[match] = fileName; + #endif + } + } + + if(replaceMap.isEmpty()) //we haven't found any latex strings + return; + + messageText= msg.escapedBody(); + + int imagePxWidth,imagePxHeight; + for (QMap::ConstIterator it = replaceMap.begin(); it != replaceMap.end(); ++it) + { + QImage theImage(*it); + if(theImage.isNull()) + continue; + imagePxWidth = theImage.width(); + imagePxHeight = theImage.height(); + QString escapedLATEX=QStyleSheet::escape(it.key()).replace("\"","""); //we need the escape quotes because that string will be in a title="" argument, but not the \n + messageText.replace(Kopete::Message::escape(it.key()), " \"" "); + } + + msg.setBody( messageText, Kopete::Message::RichText ); +} + + +void LatexPlugin::slotMessageAboutToSend( Kopete::Message& msg) +{ + Q_UNUSED(msg) + //disabled because to work correctly, we need to find what special has the gif we can send over MSN +#if 0 + KConfig *config = KGlobal::config(); + config->setGroup("Latex Plugin"); + + if(!config->readBoolEntry("ParseOutgoing", false)) + return; + + QString messageText = msg.plainBody(); + if( !messageText.contains("$$")) + return; +/* if( msg.from()->protocol()->pluginId()!="MSNProtocol" ) + return;*/ + + // this searches for $$formula$$ + QRegExp rg("^\\s*\\$\\$([^$]+)\\$\\$\\s*$"); + + if( rg.search(messageText) != -1 ) + { + QString latexFormula = rg.cap(1); + if(!securityCheck( latexFormula )) + return; + + QString url = handleLatex(latexFormula); + + + if(!url.isNull()) + { + QString escapedLATEX= QStyleSheet::escape(messageText).replace("\"","""); + QString messageText="\"""; + msg.setBody( messageText, Kopete::Message::RichText ); + } + } +#endif +} + +QString LatexPlugin::handleLatex(const QString &latexFormula) +{ + KTempFile *tempFile=new KTempFile( locateLocal( "tmp", "kopetelatex-" ), ".png" ); + tempFile->setAutoDelete(true); + m_tempFiles.append(tempFile); + m_tempFiles.setAutoDelete(true); + QString fileName = tempFile->name(); + + KProcess p; + + QString argumentRes = "-r %1x%2"; + QString argumentOut = "-o %1"; + //QString argumentFormat = "-fgif"; //we uses gif format because MSN only handle gif + int hDPI, vDPI; + hDPI = LatexConfig::self()->horizontalDPI(); + vDPI = LatexConfig::self()->verticalDPI(); + p << m_convScript << argumentRes.arg(QString::number(hDPI), QString::number(vDPI)) << argumentOut.arg(fileName) /*<< argumentFormat*/ << latexFormula ; + + kdDebug() << k_funcinfo << " Rendering " << m_convScript << " " << argumentRes.arg(QString::number(hDPI), QString::number(vDPI)) << " " << argumentOut.arg(fileName) << endl; + + // FIXME our sucky sync filter API limitations :-) + p.start(KProcess::Block); + return fileName; +} + +bool LatexPlugin::securityCheck(const QString &latexFormula) +{ + return !latexFormula.contains(QRegExp("\\\\(def|let|futurelet|newcommand|renewcomment|else|fi|write|input|include" + "|chardef|catcode|makeatletter|noexpand|toksdef|every|errhelp|errorstopmode|scrollmode|nonstopmode|batchmode" + "|read|csname|newhelp|relax|afterground|afterassignment|expandafter|noexpand|special|command|loop|repeat|toks" + "|output|line|mathcode|name|item|section|mbox|DeclareRobustCommand)[^a-zA-Z]")); + +} + +void LatexPlugin::slotSettingsChanged() +{ + LatexConfig::self()->readConfig(); +} + +#include "latexplugin.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/latex/latexplugin.h b/kopete/plugins/latex/latexplugin.h new file mode 100644 index 00000000..a0fcd7fd --- /dev/null +++ b/kopete/plugins/latex/latexplugin.h @@ -0,0 +1,77 @@ +/* + latexplugin.h + + Kopete Latex Plugin + + Copyright (c) 2004 by Duncan Mac-Vicar Prett + Copyright (c) 2004-2005 by Olivier Goffart + + Kopete (c) 2001-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef LATEXPLUGIN_H +#define LATEXPLUGIN_H + +#include +#include + +#include + +#include "kopetemessage.h" +#include "kopeteplugin.h" + +class QStringList; +class QString; + + +namespace Kopete { class Message; class ChatSession; } + +/** + * @author Duncan Mac-Vicar Prett + */ + +class LatexPlugin : public Kopete::Plugin +{ + Q_OBJECT + +public: + static LatexPlugin *plugin(); + + LatexPlugin( QObject *parent, const char *name, const QStringList &args ); + ~LatexPlugin(); + +public slots: + void slotSettingsChanged(); + void slotMessageAboutToShow( Kopete::Message& msg ); + void slotMessageAboutToSend( Kopete::Message& msg ); + void slotNewChatSession( Kopete::ChatSession *KMM); + +public: + /** + * gives a latex formula, and return the filename of the file where the latex is stored. + */ + QString handleLatex(const QString &latex); + + /** + * return false if the latex formula may contains malicious commands + */ + bool securityCheck(const QString & formula); + + +private: + static LatexPlugin* s_pluginStatic; + QString m_convScript; + bool mMagickNotFoundShown; + QPtrList m_tempFiles; +}; + +#endif diff --git a/kopete/plugins/latex/latexpreferences.cpp b/kopete/plugins/latex/latexpreferences.cpp new file mode 100644 index 00000000..1727ae49 --- /dev/null +++ b/kopete/plugins/latex/latexpreferences.cpp @@ -0,0 +1,76 @@ +/* + Kopete Latex Plugin + + Copyright (c) 2004 by Duncan Mac-Vicar Prett + + Kopete (c) 2001-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include +#include + +#include "latexplugin.h" +#include "latexconfig.h" +#include "latexprefsbase.h" +#include "latexpreferences.h" + +typedef KGenericFactory LatexPreferencesFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_latex, LatexPreferencesFactory( "kcm_kopete_latex" ) ) + +LatexPreferences::LatexPreferences(QWidget *parent, const char* /*name*/, const QStringList &args) + : KCModule(LatexPreferencesFactory::instance(), parent, args) +{ + ( new QVBoxLayout( this ) )->setAutoAdd( true ); + m_preferencesDialog = new LatexPrefsUI(this); + // connect widget signals here + m_preferencesDialog->horizontalDPI->setMinValue(1); + m_preferencesDialog->verticalDPI->setMinValue(1); + + connect(m_preferencesDialog->horizontalDPI, SIGNAL(valueChanged(int)), this, SLOT(slotModified())); + connect(m_preferencesDialog->verticalDPI, SIGNAL(valueChanged(int)), this, SLOT(slotModified())); + + load(); +} + +LatexPreferences::~LatexPreferences() +{ +} + +void LatexPreferences::load() +{ + LatexConfig::self()->readConfig(); + // load widgets here + m_preferencesDialog->horizontalDPI->setValue(LatexConfig::self()->horizontalDPI()); + m_preferencesDialog->verticalDPI->setValue(LatexConfig::self()->verticalDPI()); + emit KCModule::changed(false); +} + +void LatexPreferences::slotModified() +{ + emit KCModule::changed(true); +} + +void LatexPreferences::save() +{ + LatexConfig::self()->setHorizontalDPI(m_preferencesDialog->horizontalDPI->value()); + LatexConfig::self()->setVerticalDPI(m_preferencesDialog->verticalDPI->value()); + LatexConfig::self()->writeConfig(); + emit KCModule::changed(false); +} + +#include "latexpreferences.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/latex/latexpreferences.h b/kopete/plugins/latex/latexpreferences.h new file mode 100644 index 00000000..c08b35b5 --- /dev/null +++ b/kopete/plugins/latex/latexpreferences.h @@ -0,0 +1,48 @@ +/* + Kopete Latex Plugin + + Copyright (c) 2004 by Duncan Mac-Vicar Prett + + Kopete (c) 2001-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef LatexPREFERENCES_H +#define LatexPREFERENCES_H + +#include +#include + +class LatexPrefsUI; +class QListViewItem; + +/** + *@author Duncan Mac-Vicar Prett + */ + +class LatexPreferences : public KCModule +{ + Q_OBJECT +public: + + LatexPreferences(QWidget *parent = 0, const char* name = 0, const QStringList &args = QStringList()); + ~LatexPreferences(); + + virtual void save(); + virtual void load(); + +private: + LatexPrefsUI *m_preferencesDialog; +private slots: + void slotModified(); +}; + +#endif diff --git a/kopete/plugins/latex/latexprefsbase.ui b/kopete/plugins/latex/latexprefsbase.ui new file mode 100644 index 00000000..fbb11a21 --- /dev/null +++ b/kopete/plugins/latex/latexprefsbase.ui @@ -0,0 +1,168 @@ + +LatexPrefsUI +Duncan Mac-Vicar + + + LatexPrefsUI + + + + 0 + 0 + 513 + 232 + + + + + unnamed + + + + textLabel1 + + + Box + + + <p>The <font size="+1">KopeTeX</font> plugin allows <font size="+1">Kopet</font>e to render Latex formulas in the chat window. The sender must enclose the formula between two $ signs. ie: $$formula$$</p> +<p>This plugin requires ImageMagick convert program installed in order to work.</p> + + + + + groupBox1 + + + Options + + + + unnamed + + + + spacer5 + + + Vertical + + + Expanding + + + + 20 + 30 + + + + + + layout1 + + + + unnamed + + + + textLabel2 + + + Rendering resolution (DPI): + + + + + spacer3 + + + Horizontal + + + Expanding + + + + 280 + 20 + + + + + + + + layout2 + + + + unnamed + + + + horizontalDPI + + + + + textLabel3 + + + x + + + + + verticalDPI + + + + + spacer4 + + + Horizontal + + + Expanding + + + + 220 + 20 + + + + + + + + + + spacer6 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + + + knuminput.h + knuminput.h + knuminput.h + knuminput.h + + diff --git a/kopete/plugins/motionautoaway/COPYING.motion b/kopete/plugins/motionautoaway/COPYING.motion new file mode 100644 index 00000000..96bdc086 --- /dev/null +++ b/kopete/plugins/motionautoaway/COPYING.motion @@ -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. + + + Copyright (C) 19yy + + 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. + + , 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/kopete/plugins/motionautoaway/Makefile.am b/kopete/plugins/motionautoaway/Makefile.am new file mode 100644 index 00000000..ff2c5bd8 --- /dev/null +++ b/kopete/plugins/motionautoaway/Makefile.am @@ -0,0 +1,24 @@ +METASOURCES = AUTO + +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kopete_motionaway.la kcm_kopete_motionaway.la + +kopete_motionaway_la_SOURCES = motionawayplugin.cpp motionawayconfig.kcfgc +kopete_motionaway_la_LDFLAGS = -module $(KDE_PLUGIN) +kopete_motionaway_la_LIBADD = ../../libkopete/libkopete.la + +kcm_kopete_motionaway_la_SOURCES = motionawayprefs.ui motionawaypreferences.cpp motionawayconfig.kcfgc +kcm_kopete_motionaway_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_motionaway_la_LIBADD = $(LIB_KOPETECOMPAT) $(LIB_KUTILS) + + + +service_DATA = kopete_motionaway.desktop +servicedir = $(kde_servicesdir) + +kcm_DATA = kopete_motionaway_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog +kde_kcfg_DATA = motionawayconfig.kcfg + + diff --git a/kopete/plugins/motionautoaway/configure.in.in b/kopete/plugins/motionautoaway/configure.in.in new file mode 100644 index 00000000..5533607f --- /dev/null +++ b/kopete/plugins/motionautoaway/configure.in.in @@ -0,0 +1,18 @@ +dnl Only compile motionautoaway on Linux (needs video4linux) + +dnl Disabled for now. It breaks with patched Linux 2.4 kernels and +dnl vanilla Linux 2.5 and 2.6 kernels + +#AC_MSG_CHECKING([if motionautoaway plugin should be compiled]) + +#if test "x`uname`" = "xLinux"; then +# COMPILEMOTION=true +# AC_SUBST(COMPILEMOTION) +# AC_MSG_RESULT([yes]) +#else + COMPILEMOTION= +# AC_SUBST(COMPILEMOTION) +# AC_MSG_RESULT([no]) +#fi + +AM_CONDITIONAL(include_motionautoaway, test -n "$COMPILEMOTION") diff --git a/kopete/plugins/motionautoaway/kopete_motionaway.desktop b/kopete/plugins/motionautoaway/kopete_motionaway.desktop new file mode 100644 index 00000000..93c8d617 --- /dev/null +++ b/kopete/plugins/motionautoaway/kopete_motionaway.desktop @@ -0,0 +1,115 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_motionaway +X-KDE-PluginInfo-Author=Duncan Mac-Vicar Prett +X-KDE-PluginInfo-Email=duncan@kde.org +X-KDE-PluginInfo-Name=kopete_motionaway +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Motion Auto-Away +Name[ar]=حركة التبعيد التلقائية +Name[bg]=ПромÑна на ÑÑŠÑтоÑнието +Name[bn]=চলাচল সà§à¦¬à§Ÿà¦‚কà§à¦°à§€à§Ÿ-অনà§à¦ªà¦¸à§à¦¥à¦¿à¦¤à¦¿ +Name[bs]=Auto-odsutnost na osnovu kretanja +Name[ca]=Auto-absent per moviment +Name[cs]=Nepřítomnost podle pohybu +Name[cy]=Dim Yma (Dim Symudiad) +Name[da]=Bevægelses-auto-borte +Name[de]=Auto-Abwesenheit +Name[el]=Αυτόματη μετάβαση σε απουσία +Name[es]=Ausencia automática por movimiento +Name[et]=Automaatne äraolek +Name[eu]=Auto-urrunduta +Name[fa]=حرکت خودکار دور +Name[fi]=Liikeen perusteella poistuminen +Name[fr]=Détection de mouvement +Name[gl]=Auto-alonxamento +Name[he]=מסמן "×œ× × ×ž×¦×" בהת×× ×œ×ª× ×•×¢×” +Name[hi]=मोशन आटो अवे +Name[hr]=Automatska odsutnost u nedostatku pokreta +Name[hu]=A távollét érzékelése mozgásdetektálással +Name[it]=Automaticamente "assente" se inutilizzato +Name[ja]=自動ä¸åœ¨æ™‚ã®å‹•ä½œ +Name[ka]=áƒáƒ•áƒ¢áƒ-გáƒáƒ¡áƒ•áƒšáƒ +Name[kk]=Орында жоқ күйін автоорнату +Name[km]=ចលនា​ស្វáŸáž™áž”្រវážáŸ’ážáž· áž–áŸáž›â€‹áž˜áž·áž“​នៅ +Name[lt]=JudÄ—jimas „PasitraukÄ™s“ +Name[mk]=Ðвто-отÑутен за движење +Name[nb]=Bli borte automatisk +Name[nds]=Auto-Wegwesen +Name[ne]=चाल सà¥à¤µà¤¤: बाहिर +Name[nl]=Automatisch afwezig +Name[nn]=Bli vekke automatisk +Name[pl]=Wykrywanie nieaktywnoÅ›ci za pomocÄ… czujnika ruchu +Name[pt]=Ausência Automática por Movimento +Name[pt_BR]=Ausente Automático +Name[ru]=Ðвтоопределение отÑутÑÑ‚Ð²Ð¸Ñ +Name[sk]=Automatická prítomnosÅ¥ podľa pohybu +Name[sl]=Samo-odsotnost (premikanje) +Name[sr]=ÐутоматÑка одÑутноÑÑ‚ без покретâ +Name[sr@Latn]=Automatska odsutnost bez pokretâ +Name[sv]=Automatisk rörelsefrÃ¥nvaro +Name[ta]=அகராதி +Name[tg]=Ҳаракати Ðқибгардии Худкор +Name[tr]=Otomatik Uzakta Hareketi +Name[uk]=ÐвтовиÑÐ²Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ÑутноÑÑ‚Ñ– +Name[zh_CN]=自动离开 +Name[zh_HK]=動作感應器 +Name[zh_TW]=動作åµæ¸¬è‡ªå‹•é›¢é–‹ +Comment=Sets away status when not detecting movement near the computer +Comment[ar]=يحول وضع الاتصال إلى ÙÙŠ الخارج عندما لا يتم تحديد تعاملات مع الكومبيوتر +Comment[bg]=ПромÑна на ÑÑŠÑтоÑнието, когато нÑма активноÑÑ‚ от Ñтрана на Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ +Comment[bn]=যখন কমà§à¦ªà¦¿à¦‰à¦Ÿà¦¾à¦°à§‡à¦° কাছে কোনও চলাচল অনà§à¦­à§‚ত হয়না তখন অনà§à¦ªà¦¸à§à¦¥à¦¿à¦¤ অবসà§à¦¥à¦¾ নিযà§à¦•à§à¦¤ করে +Comment[bs]=Postavlja status odsutnosti ako nije detektovano kretanje u blizini raÄunara +Comment[ca]=Estableix l'estatus d'absent en no detectar moviment a l'ordinador +Comment[cs]=Nastaví automaticky stav "nepřítomen" pÅ™i absenci pohybu +Comment[cy]=Gosod cyflwr i "i fwrdd" os na chanfyddir symudiad wrth ymyl y cyfrifiadur +Comment[da]=Sætter borte-status nÃ¥r ingen bevægelse detekteres nær computeren +Comment[de]=Setzt automatisch den Status auf abwesend, wenn keine Aktivität am Rechner feststellbar ist +Comment[el]=ΟÏίζει την κατάσταση σε απουσία όταν δεν ανιχνεÏει κίνηση κοντά στον υπολογιστή +Comment[es]=Se pone en estado Ausente cuando no se detecte movimiento cerca de su equipo +Comment[et]=Määrab äraoleku staatuse, kui arvuti juures mingit elutegevust ei täheldata +Comment[eu]=Ezarri urrunduta egoera konputagailuan mugimendurik ez dagoenean +Comment[fa]=اگر هیچ حرکتی نزدیک رایانۀ شما آشکار نشود، وضعیت را کنار می‌گذارد +Comment[fi]=Asettaa poissaolevaksi, jos koneen lähistöllä ei havaita liikettä +Comment[fr]=Active l'état absent lorsque aucune activité n'est détectée près de l'ordinateur +Comment[gl]=Pónse en estado alonxado cando non se detectan movementos preto do seu ordenador +Comment[he]=מגדיר מצב "×œ× × ×ž×¦×" בעת חוסר פעילות על המחשב +Comment[hi]=जब कमà¥à¤ªà¥à¤¯à¥‚टर के आसपास गतिविधि पता नहीं लगता है तो सà¥à¤¥à¤¿à¤¤à¤¿ को दूर नियत करता है +Comment[hr]=Postavlja status „odsutan“ kada ne detektira pomicanje u blizini raÄunala +Comment[hu]=A távolléti állapot automatikus beállítása, ha nincs mozgás a számítógép körül +Comment[is]=Breytir stöðu þinni ef tölvan er ekki notkun +Comment[it]=Imposta lo stato ad assente quando non viene rilevato nessun movimento +Comment[ja]=コンピュータã®è¿‘ãã«ã„ãªã„å ´åˆã€ä¸åœ¨çŠ¶æ…‹ã«ã‚»ãƒƒãƒˆã—ã¾ã™ +Comment[ka]=áƒáƒ§áƒ”ნებს გáƒáƒ¡áƒ•áƒšáƒ˜áƒ¡ სტáƒáƒ¢áƒ£áƒ¡áƒ¡ რáƒáƒ“ესáƒáƒª კáƒáƒ›áƒžáƒ˜áƒ£áƒ¢áƒ”რთáƒáƒœ áƒáƒ¥áƒ¢áƒ˜áƒ•áƒáƒ‘რáƒáƒ  შეიმჩნევრ+Comment[kk]=Компьютерде қимыл жоқты байқап орында жоқ деген күйді орнатады +Comment[km]=កំណážáŸ‹â€‹ážŸáŸ’ážáž¶áž“ភាព​មិន​នៅ នៅ​ពáŸáž›â€‹áž€áž»áŸ†áž–្យូទáŸážš និង​អ្វីៗ​នៅ​ជិážâ€‹ážœáž¶ មិន​កម្រើក +Comment[lt]=BÅ«klÄ— nustatoma „PasitraukÄ™s“, jei Å¡alia kompiuterio nÄ—ra jokio judÄ—jimo +Comment[mk]=Го поÑтавува ÑтатуÑот отÑутен кога нема движење на компјутерот +Comment[nb]=Sett som borte nÃ¥r det ikke er noen bevegelse pÃ¥ mus eller tastatur +Comment[nds]=Stellt den Status automaatsch op "Nich dor", wenn sik an'n Reekner nix deit +Comment[ne]=कमà¥à¤ªà¥à¤¯à¥à¤Ÿà¤° नजिक चाल पतà¥à¤¤à¤¾ नलागà¥à¤¦à¤¾ वसà¥à¤¤à¥à¤¸à¥à¤¥à¤¿à¤¤à¤¿ बाहिर सेट गरà¥à¤¦à¤› +Comment[nl]=Stelt automatisch afwezigheid in als er geen beweging wordt gedetecteerd +Comment[nn]=Set som vekke nÃ¥r det ikkje er noka rørsle pÃ¥ mus eller tastatur +Comment[pl]=Ustawia status "Zaraz wracam", gdy nie wykrywa żadnego ruchu w pobliżu komputera +Comment[pt]=Configura o estado de ausência ao não detectar movimento perto do computador +Comment[pt_BR]=Configura o status de ausente quando não detectar movimento próximo ao computador +Comment[ru]=УÑтанавливает ÑоÑтоÑние "отÑутÑтвует", еÑли работа за компьютером не региÑтрируетÑÑ Ð² течение долгого времени +Comment[sk]=Nastaví stav prítomnosti podľa toho, Äi bol zaznamenaný pohyb pri poÄítaÄi +Comment[sl]=Nastavi stanje odsotnosti, ko ni premikanja v bližini raÄunalnika +Comment[sr]=ПоÑтавља ÑÑ‚Ð°Ñ‚ÑƒÑ â€žÐ¾Ð´Ñутан“ када не детектује померање у близини рачунара +Comment[sr@Latn]=Postavlja status „odsutan“ kada ne detektuje pomeranje u blizini raÄunara +Comment[sv]=Ställer automatisk in frÃ¥nvarostatus när ingen rörelse märks nära datorn +Comment[ta]=கணிபà¯à®ªà¯Šà®±à®¿à®¯à®¿à®©à¯ à®…à®°à¯à®•à¯‡ நடகà¯à®•à¯à®®à¯ இயகà¯à®•à®¤à¯à®¤à¯ˆ கணà¯à®Ÿà¯à®ªà®¿à®Ÿà®¿à®•à¯à®•à®¾à®¤ போத௠அமைகà¯à®•à¯à®®à¯ +Comment[tg]=Ҳангоми муайÑн накардани ҳаракат дар назди компютер ҳолатро дур меÑозад +Comment[tr]=Bilgisayarın başında olunmadığı algılanırsa uzakta olarak belirler +Comment[uk]=Ð’Ñтановлює Ñтан у Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ "відÑутній", Ñкщо не реєÑтруєтьÑÑ Ñ€Ð¾Ð±Ð¾Ñ‚Ð° за комп'ютером +Comment[zh_CN]=æ ¹æ®è®¡ç®—机æ—的状æ€è®¾ç½®ç¦»å¼€çŠ¶æ€ +Comment[zh_HK]=åµæ¸¬ä¸åˆ°é›»è…¦é™„近有動作時將狀態設為「離開〠+Comment[zh_TW]=當åµæ¸¬ä¸åˆ°å‹•ä½œæ™‚自動設為離開 diff --git a/kopete/plugins/motionautoaway/kopete_motionaway_config.desktop b/kopete/plugins/motionautoaway/kopete_motionaway_config.desktop new file mode 100644 index 00000000..ffe5775b --- /dev/null +++ b/kopete/plugins/motionautoaway/kopete_motionaway_config.desktop @@ -0,0 +1,111 @@ +[Desktop Entry] +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_motionaway +X-KDE-FactoryName=MotionAwayConfigFactory +X-KDE-ParentApp=kopete_motionaway +X-KDE-ParentComponents=kopete_motionaway + +Name=Motion Auto-Away +Name[ar]=حركة التبعيد التلقائية +Name[bg]=ПромÑна на ÑÑŠÑтоÑнието +Name[bn]=চলাচল সà§à¦¬à§Ÿà¦‚কà§à¦°à§€à§Ÿ-অনà§à¦ªà¦¸à§à¦¥à¦¿à¦¤à¦¿ +Name[bs]=Auto-odsutnost na osnovu kretanja +Name[ca]=Auto-absent per moviment +Name[cs]=Nepřítomnost podle pohybu +Name[cy]=Dim Yma (Dim Symudiad) +Name[da]=Bevægelses-auto-borte +Name[de]=Auto-Abwesenheit +Name[el]=Αυτόματη μετάβαση σε απουσία +Name[es]=Ausencia automática por movimiento +Name[et]=Automaatne äraolek +Name[eu]=Auto-urrunduta +Name[fa]=حرکت خودکار دور +Name[fi]=Liikeen perusteella poistuminen +Name[fr]=Détection de mouvement +Name[gl]=Auto-alonxamento +Name[he]=מסמן "×œ× × ×ž×¦×" בהת×× ×œ×ª× ×•×¢×” +Name[hi]=मोशन आटो अवे +Name[hr]=Automatska odsutnost u nedostatku pokreta +Name[hu]=A távollét érzékelése mozgásdetektálással +Name[it]=Automaticamente "assente" se inutilizzato +Name[ja]=自動ä¸åœ¨æ™‚ã®å‹•ä½œ +Name[ka]=áƒáƒ•áƒ¢áƒ-გáƒáƒ¡áƒ•áƒšáƒ +Name[kk]=Орында жоқ күйін автоорнату +Name[km]=ចលនា​ស្វáŸáž™áž”្រវážáŸ’ážáž· áž–áŸáž›â€‹áž˜áž·áž“​នៅ +Name[lt]=JudÄ—jimas „PasitraukÄ™s“ +Name[mk]=Ðвто-отÑутен за движење +Name[nb]=Bli borte automatisk +Name[nds]=Auto-Wegwesen +Name[ne]=चाल सà¥à¤µà¤¤: बाहिर +Name[nl]=Automatisch afwezig +Name[nn]=Bli vekke automatisk +Name[pl]=Wykrywanie nieaktywnoÅ›ci za pomocÄ… czujnika ruchu +Name[pt]=Ausência Automática por Movimento +Name[pt_BR]=Ausente Automático +Name[ru]=Ðвтоопределение отÑутÑÑ‚Ð²Ð¸Ñ +Name[sk]=Automatická prítomnosÅ¥ podľa pohybu +Name[sl]=Samo-odsotnost (premikanje) +Name[sr]=ÐутоматÑка одÑутноÑÑ‚ без покретâ +Name[sr@Latn]=Automatska odsutnost bez pokretâ +Name[sv]=Automatisk rörelsefrÃ¥nvaro +Name[ta]=அகராதி +Name[tg]=Ҳаракати Ðқибгардии Худкор +Name[tr]=Otomatik Uzakta Hareketi +Name[uk]=ÐвтовиÑÐ²Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ÑутноÑÑ‚Ñ– +Name[zh_CN]=自动离开 +Name[zh_HK]=動作感應器 +Name[zh_TW]=動作åµæ¸¬è‡ªå‹•é›¢é–‹ +Comment=Sets away status when not detecting movement near the computer +Comment[ar]=يحول وضع الاتصال إلى ÙÙŠ الخارج عندما لا يتم تحديد تعاملات مع الكومبيوتر +Comment[bg]=ПромÑна на ÑÑŠÑтоÑнието, когато нÑма активноÑÑ‚ от Ñтрана на Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ +Comment[bn]=যখন কমà§à¦ªà¦¿à¦‰à¦Ÿà¦¾à¦°à§‡à¦° কাছে কোনও চলাচল অনà§à¦­à§‚ত হয়না তখন অনà§à¦ªà¦¸à§à¦¥à¦¿à¦¤ অবসà§à¦¥à¦¾ নিযà§à¦•à§à¦¤ করে +Comment[bs]=Postavlja status odsutnosti ako nije detektovano kretanje u blizini raÄunara +Comment[ca]=Estableix l'estatus d'absent en no detectar moviment a l'ordinador +Comment[cs]=Nastaví automaticky stav "nepřítomen" pÅ™i absenci pohybu +Comment[cy]=Gosod cyflwr i "i fwrdd" os na chanfyddir symudiad wrth ymyl y cyfrifiadur +Comment[da]=Sætter borte-status nÃ¥r ingen bevægelse detekteres nær computeren +Comment[de]=Setzt automatisch den Status auf abwesend, wenn keine Aktivität am Rechner feststellbar ist +Comment[el]=ΟÏίζει την κατάσταση σε απουσία όταν δεν ανιχνεÏει κίνηση κοντά στον υπολογιστή +Comment[es]=Se pone en estado Ausente cuando no se detecte movimiento cerca de su equipo +Comment[et]=Määrab äraoleku staatuse, kui arvuti juures mingit elutegevust ei täheldata +Comment[eu]=Ezarri urrunduta egoera konputagailuan mugimendurik ez dagoenean +Comment[fa]=اگر هیچ حرکتی نزدیک رایانۀ شما آشکار نشود، وضعیت را کنار می‌گذارد +Comment[fi]=Asettaa poissaolevaksi, jos koneen lähistöllä ei havaita liikettä +Comment[fr]=Active l'état absent lorsque aucune activité n'est détectée près de l'ordinateur +Comment[gl]=Pónse en estado alonxado cando non se detectan movementos preto do seu ordenador +Comment[he]=מגדיר מצב "×œ× × ×ž×¦×" בעת חוסר פעילות על המחשב +Comment[hi]=जब कमà¥à¤ªà¥à¤¯à¥‚टर के आसपास गतिविधि पता नहीं लगता है तो सà¥à¤¥à¤¿à¤¤à¤¿ को दूर नियत करता है +Comment[hr]=Postavlja status „odsutan“ kada ne detektira pomicanje u blizini raÄunala +Comment[hu]=A távolléti állapot automatikus beállítása, ha nincs mozgás a számítógép körül +Comment[is]=Breytir stöðu þinni ef tölvan er ekki notkun +Comment[it]=Imposta lo stato ad assente quando non viene rilevato nessun movimento +Comment[ja]=コンピュータã®è¿‘ãã«ã„ãªã„å ´åˆã€ä¸åœ¨çŠ¶æ…‹ã«ã‚»ãƒƒãƒˆã—ã¾ã™ +Comment[ka]=áƒáƒ§áƒ”ნებს გáƒáƒ¡áƒ•áƒšáƒ˜áƒ¡ სტáƒáƒ¢áƒ£áƒ¡áƒ¡ რáƒáƒ“ესáƒáƒª კáƒáƒ›áƒžáƒ˜áƒ£áƒ¢áƒ”რთáƒáƒœ áƒáƒ¥áƒ¢áƒ˜áƒ•áƒáƒ‘რáƒáƒ  შეიმჩნევრ+Comment[kk]=Компьютерде қимыл жоқты байқап орында жоқ деген күйді орнатады +Comment[km]=កំណážáŸ‹â€‹ážŸáŸ’ážáž¶áž“ភាព​មិន​នៅ នៅ​ពáŸáž›â€‹áž€áž»áŸ†áž–្យូទáŸážš និង​អ្វីៗ​នៅ​ជិážâ€‹ážœáž¶ មិន​កម្រើក +Comment[lt]=BÅ«klÄ— nustatoma „PasitraukÄ™s“, jei Å¡alia kompiuterio nÄ—ra jokio judÄ—jimo +Comment[mk]=Го поÑтавува ÑтатуÑот отÑутен кога нема движење на компјутерот +Comment[nb]=Sett som borte nÃ¥r det ikke er noen bevegelse pÃ¥ mus eller tastatur +Comment[nds]=Stellt den Status automaatsch op "Nich dor", wenn sik an'n Reekner nix deit +Comment[ne]=कमà¥à¤ªà¥à¤¯à¥à¤Ÿà¤° नजिक चाल पतà¥à¤¤à¤¾ नलागà¥à¤¦à¤¾ वसà¥à¤¤à¥à¤¸à¥à¤¥à¤¿à¤¤à¤¿ बाहिर सेट गरà¥à¤¦à¤› +Comment[nl]=Stelt automatisch afwezigheid in als er geen beweging wordt gedetecteerd +Comment[nn]=Set som vekke nÃ¥r det ikkje er noka rørsle pÃ¥ mus eller tastatur +Comment[pl]=Ustawia status "Zaraz wracam", gdy nie wykrywa żadnego ruchu w pobliżu komputera +Comment[pt]=Configura o estado de ausência ao não detectar movimento perto do computador +Comment[pt_BR]=Configura o status de ausente quando não detectar movimento próximo ao computador +Comment[ru]=УÑтанавливает ÑоÑтоÑние "отÑутÑтвует", еÑли работа за компьютером не региÑтрируетÑÑ Ð² течение долгого времени +Comment[sk]=Nastaví stav prítomnosti podľa toho, Äi bol zaznamenaný pohyb pri poÄítaÄi +Comment[sl]=Nastavi stanje odsotnosti, ko ni premikanja v bližini raÄunalnika +Comment[sr]=ПоÑтавља ÑÑ‚Ð°Ñ‚ÑƒÑ â€žÐ¾Ð´Ñутан“ када не детектује померање у близини рачунара +Comment[sr@Latn]=Postavlja status „odsutan“ kada ne detektuje pomeranje u blizini raÄunara +Comment[sv]=Ställer automatisk in frÃ¥nvarostatus när ingen rörelse märks nära datorn +Comment[ta]=கணிபà¯à®ªà¯Šà®±à®¿à®¯à®¿à®©à¯ à®…à®°à¯à®•à¯‡ நடகà¯à®•à¯à®®à¯ இயகà¯à®•à®¤à¯à®¤à¯ˆ கணà¯à®Ÿà¯à®ªà®¿à®Ÿà®¿à®•à¯à®•à®¾à®¤ போத௠அமைகà¯à®•à¯à®®à¯ +Comment[tg]=Ҳангоми муайÑн накардани ҳаракат дар назди компютер ҳолатро дур меÑозад +Comment[tr]=Bilgisayarın başında olunmadığı algılanırsa uzakta olarak belirler +Comment[uk]=Ð’Ñтановлює Ñтан у Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ "відÑутній", Ñкщо не реєÑтруєтьÑÑ Ñ€Ð¾Ð±Ð¾Ñ‚Ð° за комп'ютером +Comment[zh_CN]=æ ¹æ®è®¡ç®—机æ—的状æ€è®¾ç½®ç¦»å¼€çŠ¶æ€ +Comment[zh_HK]=åµæ¸¬ä¸åˆ°é›»è…¦é™„近有動作時將狀態設為「離開〠+Comment[zh_TW]=當åµæ¸¬ä¸åˆ°å‹•ä½œæ™‚自動設為離開 diff --git a/kopete/plugins/motionautoaway/motionawayconfig.kcfg b/kopete/plugins/motionautoaway/motionawayconfig.kcfg new file mode 100644 index 00000000..9bad717c --- /dev/null +++ b/kopete/plugins/motionautoaway/motionawayconfig.kcfg @@ -0,0 +1,25 @@ + + + + + + + If this option is set, the plugin will put you in status available if you are away and it detects motion again. + true + + + + This is the Video4Linux path of the camera or device you want to use to detect motion. In most systems the first video device is /dev/v4l/video0. + /dev/v4l/video0 + + + + This setting affects how fast the plugin switches to away status. Once the plugin detects no motion, it will wait this amount of minutes before switching to away status. + 1 + 0 + + + diff --git a/kopete/plugins/motionautoaway/motionawayconfig.kcfgc b/kopete/plugins/motionautoaway/motionawayconfig.kcfgc new file mode 100644 index 00000000..52f9d6ca --- /dev/null +++ b/kopete/plugins/motionautoaway/motionawayconfig.kcfgc @@ -0,0 +1,8 @@ +ClassName=MotionAwayConfig +File=motionawayconfig.kcfg +GlobalEnums=true +ItemAccessors=true +MemberVariables=private +Mutators=true +SetUserTexts=false +Singleton=true diff --git a/kopete/plugins/motionautoaway/motionawayplugin.cpp b/kopete/plugins/motionautoaway/motionawayplugin.cpp new file mode 100644 index 00000000..d534a186 --- /dev/null +++ b/kopete/plugins/motionautoaway/motionawayplugin.cpp @@ -0,0 +1,308 @@ +/* + motionawayplugin.cpp + + Kopete Motion Detector Auto-Away plugin + + Copyright (c) 2002 by Duncan Mac-Vicar Prett + + Contains code from motion.c ( Detect changes in a video stream ) + Copyright 2000 by Jeroen Vreeken (pe1rxq@amsat.org) + Distributed under the GNU public license version 2 + See also the file 'COPYING.motion' + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "config.h" + +#include "motionawayplugin.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "kopeteaccountmanager.h" +#include "kopeteaway.h" +/* The following is a hack: + * e.g. Mandrake 9.x ships with a patched + * kernel which doesn't define this 64 bit types (we need GNU C lib + * because we use long long and warning - gcc extensions.) + * + * This is caused by the !defined(__STRICT_ANSI__) check in + * /usr/include/asm/types.h + */ +#if !defined(__u64) && defined(__GNUC__) +#if SIZEOF_UNSIGNED_LONG >= 8 +typedef unsigned long __u64; +#else +typedef unsigned long long __u64; +#endif +#endif + +#if !defined(__s64) && defined(__GNUC__) +#if SIZEOF_LONG >= 8 +typedef signed long __s64; +#else +typedef __signed__ long long __s64; +#endif +#endif +/* + * End hack + */ + +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,50) +#define _LINUX_TIME_H +#endif +#include + +#define DEF_WIDTH 352 +#define DEF_HEIGHT 288 +#define DEF_QUALITY 50 +#define DEF_CHANGES 5000 + +#define DEF_POLL_INTERVAL 1500 + +#define DEF_GAP 60*5 /* 5 minutes */ + +#define NORM_DEFAULT 0 +#define IN_DEFAULT 8 + +typedef KGenericFactory MotionAwayPluginFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_motionaway, MotionAwayPluginFactory( "kopete_motionaway" ) ) + +MotionAwayPlugin::MotionAwayPlugin( QObject *parent, const char *name, const QStringList & /* args */ ) +: Kopete::Plugin( MotionAwayPluginFactory::instance(), parent, name ) +{ + kdDebug(14305) << k_funcinfo << "Called." << endl; + /* This should be read from config someday may be */ + m_width = DEF_WIDTH; + m_height = DEF_HEIGHT; + m_quality = DEF_QUALITY; + m_maxChanges = DEF_CHANGES; + m_gap = DEF_GAP; + + /* We haven't took the first picture yet */ + m_tookFirst = false; + + m_captureTimer = new QTimer(this); + m_awayTimer = new QTimer(this); + + connect( m_captureTimer, SIGNAL(timeout()), this, SLOT(slotCapture()) ); + connect( m_awayTimer, SIGNAL(timeout()), this, SLOT(slotTimeout()) ); + + signal(SIGCHLD, SIG_IGN); + + m_imageRef.resize( m_width * m_height * 3); + m_imageNew.resize( m_width * m_height * 3); + m_imageOld.resize( m_width * m_height * 3); + m_imageOut.resize( m_width * m_height * 3); + + + kdDebug(14305) << k_funcinfo << "Opening Video4Linux Device" << endl; + + m_deviceHandler = open( videoDevice.latin1() , O_RDWR); + + if (m_deviceHandler < 0) + { + kdDebug(14305) << k_funcinfo << "Can't open Video4Linux Device" << endl; + } + else + { + kdDebug(14305) << k_funcinfo << "Worked! Setting Capture timers!" << endl; + /* Capture first image, or we will get a alarm on start */ + getImage (m_deviceHandler, m_imageRef, DEF_WIDTH, DEF_HEIGHT, IN_DEFAULT, NORM_DEFAULT, + VIDEO_PALETTE_RGB24); + + /* We have the first image now */ + m_tookFirst = true; + m_wentAway = false; + + m_captureTimer->start( DEF_POLL_INTERVAL ); + m_awayTimer->start( awayTimeout * 60 * 1000 ); + } + loadSettings(); + connect(this, SIGNAL(settingsChanged()), this, SLOT( loadSettings() ) ); +} + +MotionAwayPlugin::~MotionAwayPlugin() +{ + kdDebug(14305) << k_funcinfo << "Closing Video4Linux Device" << endl; + close (m_deviceHandler); + kdDebug(14305) << k_funcinfo << "Freeing memory" << endl; +} + +void MotionAwayPlugin::loadSettings(){ + KConfig *kconfig = KGlobal::config(); + kconfig->setGroup("MotionAway Plugin"); + + awayTimeout = kconfig->readNumEntry("AwayTimeout", 1); + becomeAvailableWithActivity = kconfig->readBoolEntry("BecomeAvailableWithActivity", true); + videoDevice = kconfig->readEntry("VideoDevice", "/dev/video0"); + m_awayTimer->changeInterval(awayTimeout * 60 * 1000); +} + +int MotionAwayPlugin::getImage(int _dev, QByteArray &_image, int _width, int _height, int _input, int _norm, int _fmt) +{ + struct video_capability vid_caps; + struct video_channel vid_chnl; + struct video_window vid_win; + struct pollfd video_fd; + + // Just to avoid a warning + _fmt = 0; + + if (ioctl (_dev, VIDIOCGCAP, &vid_caps) == -1) + { + perror ("ioctl (VIDIOCGCAP)"); + return (-1); + } + /* Set channels and norms, NOT TESTED my philips cam doesn't have a + * tuner. */ + if (_input != IN_DEFAULT) + { + vid_chnl.channel = -1; + if (ioctl (_dev, VIDIOCGCHAN, &vid_chnl) == -1) + { + perror ("ioctl (VIDIOCGCHAN)"); + } + else + { + vid_chnl.channel = _input; + vid_chnl.norm = _norm; + + if (ioctl (_dev, VIDIOCSCHAN, &vid_chnl) == -1) + { + perror ("ioctl (VIDIOCSCHAN)"); + return (-1); + } + } + } + /* Set image size */ + if (ioctl (_dev, VIDIOCGWIN, &vid_win) == -1) + return (-1); + + vid_win.width=_width; + vid_win.height=_height; + + if (ioctl (_dev, VIDIOCSWIN, &vid_win) == -1) + return (-1); + + /* Check if data available on the video device */ + video_fd.fd = _dev; + video_fd.events = 0; + video_fd.events |= POLLIN; + video_fd.revents = 0; + + poll(&video_fd, 1, 0); + + if (video_fd.revents & POLLIN) { + /* Read an image */ + return read (_dev, _image.data() , _width * _height * 3); + } else { + return (-1); + } +} + +void MotionAwayPlugin::slotCapture() +{ + /* Should go on forever... emphasis on 'should' */ + if ( getImage ( m_deviceHandler, m_imageNew, m_width, m_height, IN_DEFAULT, NORM_DEFAULT, + VIDEO_PALETTE_RGB24) == m_width * m_height *3 ) + { + int diffs = 0; + if ( m_tookFirst ) + { + /* Make a differences picture in image_out */ + for (int i=0; i< m_width * m_height * 3 ; i++) + { + m_imageOut[i]= m_imageOld[i]- m_imageNew[i]; + if ((signed char)m_imageOut[i] > 32 || (signed char)m_imageOut[i] < -32) + { + m_imageOld[i] = m_imageNew[i]; + diffs++; + } + else + { + m_imageOut[i] = 0; + } + } + } + else + { + /* First picture: new image is now the old */ + for (int i=0; i< m_width * m_height * 3; i++) + m_imageOld[i] = m_imageNew[i]; + } + + /* The cat just walked in :) */ + if (diffs > m_maxChanges) + { + kdDebug(14305) << k_funcinfo << "Motion Detected. [" << diffs << "] Reseting Timeout" << endl; + + /* If we were away, now we are available again */ + if ( becomeAvailableWithActivity && !Kopete::Away::globalAway() && m_wentAway) + { + slotActivity(); + } + + /* We reset the away timer */ + m_awayTimer->stop(); + m_awayTimer->start( awayTimeout * 60 * 1000 ); + } + + /* Old image slowly decays, this will make it even harder on + slow moving object to stay undetected */ + /* + for (i=0; istop(); + } +} + +void MotionAwayPlugin::slotActivity() +{ + kdDebug(14305) << k_funcinfo << "User activity!, going available" << endl; + m_wentAway = false; + Kopete::AccountManager::self()->setAvailableAll(); +} + +void MotionAwayPlugin::slotTimeout() +{ + if(!Kopete::Away::globalAway() && !m_wentAway) + { + kdDebug(14305) << k_funcinfo << "Timeout and no user activity, going away" << endl; + m_wentAway = true; + Kopete::AccountManager::self()->setAwayAll(); + } +} + +#include "motionawayplugin.moc" +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/motionautoaway/motionawayplugin.h b/kopete/plugins/motionautoaway/motionawayplugin.h new file mode 100644 index 00000000..98e7e343 --- /dev/null +++ b/kopete/plugins/motionautoaway/motionawayplugin.h @@ -0,0 +1,93 @@ +/* + motionawayplugin.h + + Kopete Motion Detector Auto-Away plugin + + Copyright (c) 2002 by Duncan Mac-Vicar Prett + + Contains code from motion.c ( Detect changes in a video stream ) + Copyright 2000 by Jeroen Vreeken (pe1rxq@amsat.org) + Distributed under the GNU public license version 2 + See also the file 'COPYING.motion' + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MOTIONAWAYPLUGIN_H +#define MOTIONAWAYPLUGIN_H + +#include "kopeteplugin.h" + +class QTimer; + +/** + * @author Duncan Mac-Vicar Prett + */ + +class MotionAwayPlugin : public Kopete::Plugin +{ + Q_OBJECT + +public: + MotionAwayPlugin( QObject *parent, const char *name, const QStringList &args ); + ~MotionAwayPlugin(); + +public slots: + void loadSettings(); + void slotTimeout(); + void slotCapture(); + void slotActivity(); + +private: + int awayTimeout; + bool becomeAvailableWithActivity; + QString videoDevice; + + QTimer *m_captureTimer; + QTimer *m_awayTimer; + + int getImage(int, QByteArray& ,int ,int ,int ,int ,int ); + + bool m_tookFirst; + bool m_wentAway; + + int m_width; + int m_height; + + int m_quality; + int m_maxChanges; + + int m_deviceHandler; + int shots; + int m_gap; + + QByteArray m_imageRef; + QByteArray m_imageNew; + QByteArray m_imageOld; + QByteArray m_imageOut; + + /* + time_t currenttimep; + time_t lasttime; + struct tm *currenttime; + + char file[255]; + char filepath[255]; + char c; + + */ +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/motionautoaway/motionawaypreferences.cpp b/kopete/plugins/motionautoaway/motionawaypreferences.cpp new file mode 100644 index 00000000..a4962c5c --- /dev/null +++ b/kopete/plugins/motionautoaway/motionawaypreferences.cpp @@ -0,0 +1,70 @@ +/* + Kopete Motion Detector Auto-Away plugin + + Copyright (c) 2002-2004 by Duncan Mac-Vicar Prett + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#include +#include +#include + +#include "motionawayprefs.h" +#include "motionawaypreferences.h" +#include "motionawayconfig.h" + +typedef KGenericFactory MotionAwayPreferencesFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_motionaway, MotionAwayPreferencesFactory("kcm_kopete_motionaway")) + +MotionAwayPreferences::MotionAwayPreferences(QWidget *parent, const char* /*name*/, const QStringList &args) + : KCModule(MotionAwayPreferencesFactory::instance(), parent, args) +{ + // Add actuall widget generated from ui file. + ( new QVBoxLayout( this ) )->setAutoAdd( true ); + preferencesDialog = new motionawayPrefsUI(this); + connect(preferencesDialog->BecomeAvailableWithActivity, SIGNAL(toggled(bool)), this, SLOT(slotWidgetModified())); + connect(preferencesDialog->AwayTimeout, SIGNAL(valueChanged(int)), this, SLOT(slotWidgetModified())); + connect(preferencesDialog->VideoDevice, SIGNAL(textChanged(const QString &)), this, SLOT(slotWidgetModified())); + load(); +} + +void MotionAwayPreferences::load() +{ + MotionAwayConfig::self()->readConfig(); + preferencesDialog->AwayTimeout->setValue(MotionAwayConfig::self()->awayTimeout()); + preferencesDialog->BecomeAvailableWithActivity->setChecked(MotionAwayConfig::self()->becomeAvailableWithActivity()); + preferencesDialog->VideoDevice->setText(MotionAwayConfig::self()->videoDevice()); + emit KCModule::changed(false); +} + +void MotionAwayPreferences::slotWidgetModified() +{ + emit KCModule::changed(true); +} + +void MotionAwayPreferences::save() +{ + MotionAwayConfig::self()->setAwayTimeout(preferencesDialog->AwayTimeout->value()); + MotionAwayConfig::self()->setBecomeAvailableWithActivity(preferencesDialog->BecomeAvailableWithActivity->isChecked()); + MotionAwayConfig::self()->setVideoDevice(preferencesDialog->VideoDevice->text()); + MotionAwayConfig::self()->writeConfig(); + emit KCModule::changed(false); +} + +#include "motionawaypreferences.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/motionautoaway/motionawaypreferences.h b/kopete/plugins/motionautoaway/motionawaypreferences.h new file mode 100644 index 00000000..43b33411 --- /dev/null +++ b/kopete/plugins/motionautoaway/motionawaypreferences.h @@ -0,0 +1,45 @@ +/* + Kopete Motion Detector Auto-Away plugin + + Copyright (c) 2002-2004 by Duncan Mac-Vicar Prett + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MOTIONAWAYPREFERENCES_H +#define MOTIONAWAYPREFERENCES_H + +#include "kcmodule.h" + +class motionawayPrefsUI; + +/** + * Preference widget for the Motion Away plugin + * @author Duncan Mac-Vicar P. + */ +class MotionAwayPreferences : public KCModule +{ + Q_OBJECT +public: + MotionAwayPreferences(QWidget *parent = 0, const char *name = 0, const QStringList &args = QStringList()); + virtual void save(); + virtual void load(); + +private: + motionawayPrefsUI *preferencesDialog; +private slots: + void slotWidgetModified(); +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/motionautoaway/motionawayprefs.ui b/kopete/plugins/motionautoaway/motionawayprefs.ui new file mode 100644 index 00000000..134f939a --- /dev/null +++ b/kopete/plugins/motionautoaway/motionawayprefs.ui @@ -0,0 +1,297 @@ + +motionawayPrefsUI +Duncan Mac-Vicar P. + + + motionawayPrefsUI + + + + 0 + 0 + 411 + 406 + + + + + 1 + 1 + 0 + 0 + + + + + unnamed + + + + textLabel1 + + + <p>Motion Auto-Away can set you to be away automatically when it does not detect motion from your webcam or any video4linux device.</p> <p>It will put you online again when it detects you moving in front of the camera.</p> + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + groupBox2 + + + Video Settings + + + + unnamed + + + + layout21 + + + + unnamed + + + + TextLabel2_2 + + + &Video4Linux device: + + + VideoDevice + + + + + spacer2 + + + Horizontal + + + Expanding + + + + 95 + 20 + + + + + + VideoDevice + + + /dev/video0 + + + + + + + spacer21 + + + Vertical + + + Fixed + + + + 20 + 20 + + + + + + + + groupBox1 + + + Away Settings + + + + unnamed + + + + layout5 + + + + unnamed + + + + BecomeAvailableWithActivity + + + Become available when &detecting activity again + + + true + + + + + spacer5 + + + Horizontal + + + Expanding + + + + 30 + 20 + + + + + + + + layout6 + + + + unnamed + + + + TextLabel2 + + + &Become away after this many minutes of inactivity: + + + AwayTimeout + + + + + spacer4 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + + + layout16 + + + + unnamed + + + + AwayTimeout + + + + + textLabel2 + + + minutes + + + + + spacer3 + + + Horizontal + + + Expanding + + + + 180 + 20 + + + + + + + + spacer22 + + + Vertical + + + Fixed + + + + 20 + 20 + + + + + + + + Spacer2 + + + Vertical + + + Fixed + + + + 20 + 20 + + + + + + + + klineedit.h + knuminput.h + knuminput.h + + diff --git a/kopete/plugins/netmeeting/Makefile.am b/kopete/plugins/netmeeting/Makefile.am new file mode 100644 index 00000000..2b3560be --- /dev/null +++ b/kopete/plugins/netmeeting/Makefile.am @@ -0,0 +1,23 @@ +METASOURCES = AUTO + +AM_CPPFLAGS = $(KOPETE_INCLUDES) -I$(top_srcdir)/kopete/protocols/msn $(all_includes) + +kde_module_LTLIBRARIES = kopete_netmeeting.la kcm_kopete_netmeeting.la + +kopete_netmeeting_la_SOURCES = netmeetingplugin.cpp netmeetinginvitation.cpp netmeetingguiclient.cpp +kopete_netmeeting_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kopete_netmeeting_la_LIBADD = $(top_builddir)/kopete/libkopete/libkopete.la $(top_builddir)/kopete/protocols/msn/libkopete_msn_shared.la + +service_DATA = kopete_netmeeting.desktop +servicedir = $(kde_servicesdir) + +mydatadir = $(kde_datadir)/kopete_netmeeting +mydata_DATA = netmeetingchatui.rc + +kcm_kopete_netmeeting_la_SOURCES = netmeetingprefs_ui.ui netmeetingpreferences.cpp +kcm_kopete_netmeeting_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_netmeeting_la_LIBADD = $(LIB_KOPETECOMPAT) $(LIB_KUTILS) + + +kcm_DATA = kopete_netmeeting_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog diff --git a/kopete/plugins/netmeeting/kopete_netmeeting.desktop b/kopete/plugins/netmeeting/kopete_netmeeting.desktop new file mode 100644 index 00000000..3636f6ce --- /dev/null +++ b/kopete/plugins/netmeeting/kopete_netmeeting.desktop @@ -0,0 +1,81 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=phone +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_netmeeting +X-KDE-PluginInfo-Author=Olivier Goffart +X-KDE-PluginInfo-Email=ogoffart@tiscalinet.be +X-KDE-PluginInfo-Name=kopete_netmeeting +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends=kopete_msn +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Netmeeting +Name[ar]=الاجتماع على الشبكة +Name[bg]=Видео чат +Name[bn]=নেট মিটিং +Name[da]=Netmøde +Name[eo]=Reta renkontiÄo +Name[fa]=نت میتینگ +Name[fr]=Vidéo-conférence +Name[hi]=नेटमीटिंग +Name[ja]=ãƒãƒƒãƒˆãƒŸãƒ¼ãƒ†ã‚£ãƒ³ã‚° +Name[km]=ប្រជុំ​លើ​បណ្ដាញ +Name[lt]=Bendravimas tinkle +Name[nds]=Nettmööt +Name[ne]=नेट मिटिङ +Name[nl]=NetMeeting +Name[pa]=ਨੈੱਟ-ਮੀਟਿੰਗ +Name[sv]=Nätverksmöte +Name[ta]=இணைய சநà¯à®¤à®¿à®ªà¯à®ªà¯ +Name[tg]=Вохӯриҳои шабакавӣ +Comment=Voice and Video with MSN Messenger +Comment[be]=Гук Ñ– відÑа праз MSN Messenger +Comment[bg]=ПриÑтавка за разговор Ñ Ð³Ð»Ð°Ñ Ð¸ видео Ñ MSN Messenger +Comment[bn]=à¦à¦®à¦à¦¸à¦à¦¨ বারà§à¦¤à¦¾à¦¬à¦¾à¦¹à¦•à§‡à¦° সঙà§à¦—ে সà§à¦¬à¦° à¦à¦¬à¦‚ ভিডিও +Comment[bs]=Glas i video sa MSN Messengerom +Comment[ca]=Veu i vídeo amb MSN Messenger +Comment[cs]=Hlas a video pomocí MSN Messenger +Comment[da]=Stemme og video med MSN Messenger +Comment[de]=Sprache und Video mit dem MSN-Messenger verwenden +Comment[el]=Βίντεο και εικόνα με το MSN Messenger +Comment[es]=Voz y vídeo con MSN Messenger +Comment[et]=Audio ja video kasutamine MSN Messengeriga +Comment[eu]=Ahotsa eta bideoa MSN Messenger-ekin +Comment[fa]=ویدیو Ùˆ صدا با پیام‌رسان ام‌اس‌ان +Comment[fi]=Ääni ja videokuva MSN Messengerin kanssa +Comment[fr]=Voix et vidéo avec MSN Messenger +Comment[gl]=Voz e video con MSN Messenger +Comment[he]=חוזי ושמע ×¢× MSN Messenger +Comment[hu]=Hang és videó az MSN Messengerrel +Comment[is]=Hljóð og vídeó með MSN Messenger +Comment[it]=Voce e video con MSN Messenger +Comment[ja]=MSN メッセンジャーã¨ãƒœã‚¤ã‚¹/ビデオãƒãƒ£ãƒƒãƒˆ +Comment[ka]=ხმრდრვიდერMSN მესინჯერთáƒáƒœ +Comment[kk]=MSN Messenger Ð´Ñ‹Ð±Ñ‹Ñ Ð¿ÐµÐ½ бейнемен +Comment[km]=សំឡáŸáž„ និង​វីដáŸáž¢áž¼â€‹ážŠáŸ„យ​ប្រើ​កម្មវិធី​ផ្ញើ​សារ MSN +Comment[lt]=Bendravimas balsu ir vaizdu per MSN Messenger +Comment[mk]=Ð“Ð»Ð°Ñ Ð¸ видео Ñо ГлаÑникот на MSN +Comment[nb]=Lyd og bilde med MSN Messenger +Comment[nds]=Spraak un Video mit dat MSN-Kortnarichtenprogramm +Comment[ne]=à¤à¤®à¤à¤¸à¤à¤¨ मेसेनà¥à¤œà¤°à¤¸à¤à¤— आवाज र भिडियो +Comment[nl]=Beeld en geluid met MSN Messenger +Comment[nn]=Lyd og bilete med MSN Messenger +Comment[pl]=GÅ‚os i wideo za pomocÄ… MSN Messenger +Comment[pt]=Voz e Vídeo com o MSN Messenger +Comment[pt_BR]=Voz e Vídeo com o MSN Messenger +Comment[ru]=Ðудио и видео Ñ MSN Messenger +Comment[sk]=Hlas a video pomocou MSN Messenger +Comment[sl]=Glas in video z MSN Messenger +Comment[sr]=Ð“Ð»Ð°Ñ Ð¸ видео Ñа MSN Messenger-ом +Comment[sr@Latn]=Glas i video sa MSN Messenger-om +Comment[sv]=Ljud och video med MSN Messenger +Comment[ta]=எமà¯à®Žà®¸à¯à®Žà®©à¯ செயà¯à®¤à®¿à®¯à®¿à®²à¯ கà¯à®°à®²à¯ மறà¯à®±à¯à®®à¯ படகà¯à®•à®¾à®Ÿà¯à®šà®¿ +Comment[tr]=MSN Messenger ile Video ve Ses +Comment[uk]=Ðудіо Ñ– відео з MSN Messenger +Comment[zh_CN]=与 MSN Messenger 一起使用影音 +Comment[zh_HK]=å’Œ MSN Messenger ä¸€èµ·ä½¿ç”¨èªžéŸ³å’Œè¦–åƒ +Comment[zh_TW]=MSN Messenger å½±åƒèˆ‡è²éŸ³ diff --git a/kopete/plugins/netmeeting/kopete_netmeeting_config.desktop b/kopete/plugins/netmeeting/kopete_netmeeting_config.desktop new file mode 100644 index 00000000..16f24f6c --- /dev/null +++ b/kopete/plugins/netmeeting/kopete_netmeeting_config.desktop @@ -0,0 +1,77 @@ +[Desktop Entry] +Icon=highlight +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_netmeeting +X-KDE-FactoryName=NetmeetingConfigFactory +X-KDE-ParentApp=kopete_netmeeting +X-KDE-ParentComponents=kopete_netmeeting + +Name=Netmeeting +Name[ar]=الاجتماع على الشبكة +Name[bg]=Видео чат +Name[bn]=নেট মিটিং +Name[da]=Netmøde +Name[eo]=Reta renkontiÄo +Name[fa]=نت میتینگ +Name[fr]=Vidéo-conférence +Name[hi]=नेटमीटिंग +Name[ja]=ãƒãƒƒãƒˆãƒŸãƒ¼ãƒ†ã‚£ãƒ³ã‚° +Name[km]=ប្រជុំ​លើ​បណ្ដាញ +Name[lt]=Bendravimas tinkle +Name[nds]=Nettmööt +Name[ne]=नेट मिटिङ +Name[nl]=NetMeeting +Name[pa]=ਨੈੱਟ-ਮੀਟਿੰਗ +Name[sv]=Nätverksmöte +Name[ta]=இணைய சநà¯à®¤à®¿à®ªà¯à®ªà¯ +Name[tg]=Вохӯриҳои шабакавӣ +Comment=Voice and Video with MSN Messenger +Comment[be]=Гук Ñ– відÑа праз MSN Messenger +Comment[bg]=ПриÑтавка за разговор Ñ Ð³Ð»Ð°Ñ Ð¸ видео Ñ MSN Messenger +Comment[bn]=à¦à¦®à¦à¦¸à¦à¦¨ বারà§à¦¤à¦¾à¦¬à¦¾à¦¹à¦•à§‡à¦° সঙà§à¦—ে সà§à¦¬à¦° à¦à¦¬à¦‚ ভিডিও +Comment[bs]=Glas i video sa MSN Messengerom +Comment[ca]=Veu i vídeo amb MSN Messenger +Comment[cs]=Hlas a video pomocí MSN Messenger +Comment[da]=Stemme og video med MSN Messenger +Comment[de]=Sprache und Video mit dem MSN-Messenger verwenden +Comment[el]=Βίντεο και εικόνα με το MSN Messenger +Comment[es]=Voz y vídeo con MSN Messenger +Comment[et]=Audio ja video kasutamine MSN Messengeriga +Comment[eu]=Ahotsa eta bideoa MSN Messenger-ekin +Comment[fa]=ویدیو Ùˆ صدا با پیام‌رسان ام‌اس‌ان +Comment[fi]=Ääni ja videokuva MSN Messengerin kanssa +Comment[fr]=Voix et vidéo avec MSN Messenger +Comment[gl]=Voz e video con MSN Messenger +Comment[he]=חוזי ושמע ×¢× MSN Messenger +Comment[hu]=Hang és videó az MSN Messengerrel +Comment[is]=Hljóð og vídeó með MSN Messenger +Comment[it]=Voce e video con MSN Messenger +Comment[ja]=MSN メッセンジャーã¨ãƒœã‚¤ã‚¹/ビデオãƒãƒ£ãƒƒãƒˆ +Comment[ka]=ხმრდრვიდერMSN მესინჯერთáƒáƒœ +Comment[kk]=MSN Messenger Ð´Ñ‹Ð±Ñ‹Ñ Ð¿ÐµÐ½ бейнемен +Comment[km]=សំឡáŸáž„ និង​វីដáŸáž¢áž¼â€‹ážŠáŸ„យ​ប្រើ​កម្មវិធី​ផ្ញើ​សារ MSN +Comment[lt]=Bendravimas balsu ir vaizdu per MSN Messenger +Comment[mk]=Ð“Ð»Ð°Ñ Ð¸ видео Ñо ГлаÑникот на MSN +Comment[nb]=Lyd og bilde med MSN Messenger +Comment[nds]=Spraak un Video mit dat MSN-Kortnarichtenprogramm +Comment[ne]=à¤à¤®à¤à¤¸à¤à¤¨ मेसेनà¥à¤œà¤°à¤¸à¤à¤— आवाज र भिडियो +Comment[nl]=Beeld en geluid met MSN Messenger +Comment[nn]=Lyd og bilete med MSN Messenger +Comment[pl]=GÅ‚os i wideo za pomocÄ… MSN Messenger +Comment[pt]=Voz e Vídeo com o MSN Messenger +Comment[pt_BR]=Voz e Vídeo com o MSN Messenger +Comment[ru]=Ðудио и видео Ñ MSN Messenger +Comment[sk]=Hlas a video pomocou MSN Messenger +Comment[sl]=Glas in video z MSN Messenger +Comment[sr]=Ð“Ð»Ð°Ñ Ð¸ видео Ñа MSN Messenger-ом +Comment[sr@Latn]=Glas i video sa MSN Messenger-om +Comment[sv]=Ljud och video med MSN Messenger +Comment[ta]=எமà¯à®Žà®¸à¯à®Žà®©à¯ செயà¯à®¤à®¿à®¯à®¿à®²à¯ கà¯à®°à®²à¯ மறà¯à®±à¯à®®à¯ படகà¯à®•à®¾à®Ÿà¯à®šà®¿ +Comment[tr]=MSN Messenger ile Video ve Ses +Comment[uk]=Ðудіо Ñ– відео з MSN Messenger +Comment[zh_CN]=与 MSN Messenger 一起使用影音 +Comment[zh_HK]=å’Œ MSN Messenger ä¸€èµ·ä½¿ç”¨èªžéŸ³å’Œè¦–åƒ +Comment[zh_TW]=MSN Messenger å½±åƒèˆ‡è²éŸ³ diff --git a/kopete/plugins/netmeeting/netmeetingchatui.rc b/kopete/plugins/netmeeting/netmeetingchatui.rc new file mode 100644 index 00000000..b0d139ae --- /dev/null +++ b/kopete/plugins/netmeeting/netmeetingchatui.rc @@ -0,0 +1,9 @@ + + + + + &Tools + + + + diff --git a/kopete/plugins/netmeeting/netmeetingguiclient.cpp b/kopete/plugins/netmeeting/netmeetingguiclient.cpp new file mode 100644 index 00000000..e024a872 --- /dev/null +++ b/kopete/plugins/netmeeting/netmeetingguiclient.cpp @@ -0,0 +1,61 @@ +/* + netmeetingguiclient.cpp + + Kopete NetMeeting plugin + + Copyright (c) 2003-2004 by Olivier Goffart + + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include + +#include +#include +#include +#include + +#include "msnchatsession.h" +#include "msncontact.h" + +#include "netmeetingguiclient.h" +#include "netmeetinginvitation.h" + +class NetMeetingPlugin; + +NetMeetingGUIClient::NetMeetingGUIClient( MSNChatSession *parent, const char *name ) +: QObject( parent, name ) , KXMLGUIClient(parent) +{ + setInstance(KGenericFactory::instance()); + m_manager=parent; + + new KAction( i18n( "Invite to Use NetMeeting" ), 0, this, SLOT( slotStartInvitation() ), actionCollection() , "netmeeting" ) ; + + setXMLFile("netmeetingchatui.rc"); +} + +NetMeetingGUIClient::~NetMeetingGUIClient() +{ + +} + +void NetMeetingGUIClient::slotStartInvitation() +{ + QPtrList c=m_manager->members(); + NetMeetingInvitation *i=new NetMeetingInvitation(false, static_cast(c.first()),m_manager); + m_manager->initInvitation(i); +} + +#include "netmeetingguiclient.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/netmeeting/netmeetingguiclient.h b/kopete/plugins/netmeeting/netmeetingguiclient.h new file mode 100644 index 00000000..fa84b694 --- /dev/null +++ b/kopete/plugins/netmeeting/netmeetingguiclient.h @@ -0,0 +1,60 @@ +/* + netmeetingguiclient.h + + Kopete NetMeeting Plugin + + Copyright (c) 2003 by Olivier Goffart + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef TRANSLATORGUICLIENT_H +#define TRANSLATORGUICLIENT_H + +#include +#include + +namespace Kopete { class ChatSession; } +class MSNChatSession; +class NetMeetingPlugin; + +/** + * @author Olivier Goffart + */ + +class NetMeetingGUIClient : public QObject , public KXMLGUIClient +{ + Q_OBJECT + +public: + NetMeetingGUIClient( MSNChatSession *parent, const char *name=0L); + ~NetMeetingGUIClient(); + +private slots: + void slotStartInvitation(); + +private: + MSNChatSession *m_manager; + NetMeetingPlugin *m_plugin; +}; + +#endif + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/netmeeting/netmeetinginvitation.cpp b/kopete/plugins/netmeeting/netmeetinginvitation.cpp new file mode 100644 index 00000000..191bc140 --- /dev/null +++ b/kopete/plugins/netmeeting/netmeetinginvitation.cpp @@ -0,0 +1,183 @@ +/* + msninvitation.cpp + + Copyright (c) 2003 by Olivier Goffart + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "netmeetinginvitation.h" + +#include "kopeteuiglobal.h" + +#include "msnchatsession.h" +#include "msnswitchboardsocket.h" +#include "msncontact.h" +#include "kopetemetacontact.h" + +#include +#include +#include +#include +#include + + +#include +#include + +NetMeetingInvitation::NetMeetingInvitation(bool incoming, MSNContact *c, QObject *parent) + : QObject(parent) , MSNInvitation( incoming, NetMeetingInvitation::applicationID() , i18n("NetMeeting") ) +{ + m_contact=c; + oki=false; +} + + +NetMeetingInvitation::~NetMeetingInvitation() +{ +} + + +QString NetMeetingInvitation::invitationHead() +{ + QTimer::singleShot( 10*60000, this, SLOT( slotTimeout() ) ); //send TIMEOUT in 10 minute if the invitation has not been accepted/refused + return QString( MSNInvitation::invitationHead()+ + "Session-Protocol: SM1\r\n" + "Session-ID: {6672F94C-45BF-11D7-B4AE-00010A1008DF}\r\n" //FIXME i don't know what is the session id + "\r\n").utf8(); +} + +void NetMeetingInvitation::parseInvitation(const QString& msg) +{ + QRegExp rx("Invitation-Command: ([A-Z]*)"); + rx.search(msg); + QString command=rx.cap(1); + if( msg.contains("Invitation-Command: INVITE") ) + { + MSNInvitation::parseInvitation(msg); //for the cookie + + unsigned int result = KMessageBox::questionYesNo( Kopete::UI::Global::mainWidget(), + i18n("%1 wants to start a chat with NetMeeting; do you want to accept it? " ).arg(m_contact->metaContact()->displayName()), + i18n("MSN Plugin") , i18n("Accept"),i18n("Refuse")); + + MSNChatSession* manager=dynamic_cast(m_contact->manager()); + + if(manager && manager->service()) + { + if(result==3) // Yes == 3 + { + QCString message=QString( + "MIME-Version: 1.0\r\n" + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" + "\r\n" + "Invitation-Command: ACCEPT\r\n" + "Invitation-Cookie: " + QString::number(cookie()) + "\r\n" + "Session-ID: {6672F94C-45BF-11D7-B4AE-00010A1008DF}\r\n" //FIXME + "Session-Protocol: SM1\r\n" + "Launch-Application: TRUE\r\n" + "Request-Data: IP-Address:\r\n" + "IP-Address: " + manager->service()->getLocalIP()+ "\r\n" + "\r\n" ).utf8(); + + + manager->service()->sendCommand( "MSG" , "N", true, message ); + oki=false; + QTimer::singleShot( 10* 60000, this, SLOT( slotTimeout() ) ); //TIMOUT afte 10 min + } + else //No + { + manager->service()->sendCommand( "MSG" , "N", true, rejectMessage() ); + emit done(this); + } + } + } + else if( msg.contains("Invitation-Command: ACCEPT") ) + { + if( ! incoming() ) + { + MSNChatSession* manager=dynamic_cast(m_contact->manager()); + if(manager && manager->service()) + { + QCString message=QString( + "MIME-Version: 1.0\r\n" + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" + "\r\n" + "Invitation-Command: ACCEPT\r\n" + "Invitation-Cookie: " + QString::number(cookie()) + "\r\n" + "Session-ID: {6672F94C-45BF-11D7-B4AE-00010A1008DF}\r\n" //FIXME: what is session id? + "Session-Protocol: SM1\r\n" + "Launch-Application: TRUE\r\n" + "Request-Data: IP-Address:\r\n" + "IP-Address: " + manager->service()->getLocalIP() + "\r\n" + "\r\n" ).utf8(); + manager->service()->sendCommand( "MSG" , "N", true, message ); + } + rx=QRegExp("IP-Address: ([0-9\\:\\.]*)"); + rx.search(msg); + QString ip_address = rx.cap(1); + startMeeting(ip_address); + kdDebug() << k_funcinfo << ip_address << endl; + } + else + { + rx=QRegExp("IP-Address: ([0-9\\:\\.]*)"); + rx.search(msg); + QString ip_address = rx.cap(1); + + startMeeting(ip_address); + } + } + else //CANCEL + { + emit done(this); + } +} + +void NetMeetingInvitation::slotTimeout() +{ + if(oki) + return; + + MSNChatSession* manager=dynamic_cast(m_contact->manager()); + + if(manager && manager->service()) + { + manager->service()->sendCommand( "MSG" , "N", true, rejectMessage("TIMEOUT") ); + } + emit done(this); + +} + + +void NetMeetingInvitation::startMeeting(const QString & ip_address) +{ + //TODO: use KProcess + + KConfig *config=KGlobal::config(); + config->setGroup("Netmeeting Plugin"); + QString app=config->readEntry("NetmeetingApplication","ekiga -c callto://%1").arg(ip_address); + + kdDebug() << k_funcinfo << app << endl ; + + QStringList args=QStringList::split(" ", app); + + KProcess p; + for(QStringList::Iterator it=args.begin() ; it != args.end() ; ++it) + { + p << *it; + } + p.start(); +} + +#include "netmeetinginvitation.moc" + + + + diff --git a/kopete/plugins/netmeeting/netmeetinginvitation.h b/kopete/plugins/netmeeting/netmeetinginvitation.h new file mode 100644 index 00000000..0fbaf318 --- /dev/null +++ b/kopete/plugins/netmeeting/netmeetinginvitation.h @@ -0,0 +1,56 @@ +/* + netmeetinginvitation.cpp + + Copyright (c) 2003 by Olivier Goffart + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef MSNVOICEINVITATION_H +#define MSNVOICEINVITATION_H + +#include +#include "msninvitation.h" + +class MSNContact; + +/** + *@author Olivier Goffart + */ +class NetMeetingInvitation : public QObject , public MSNInvitation +{ +Q_OBJECT +public: + NetMeetingInvitation(bool incoming ,MSNContact*, QObject *parent = 0); + ~NetMeetingInvitation(); + + static QString applicationID() { return "44BBA842-CC51-11CF-AAFA-00AA00B6015C"; } + QString invitationHead(); + + virtual void parseInvitation(const QString& invitation); + + virtual QObject* object() { return this; } + +signals: + void done( MSNInvitation * ); + +private slots: + void slotTimeout(); + +private: + MSNContact *m_contact; + bool oki; + void startMeeting(const QString & ip_address); + +}; + + +#endif diff --git a/kopete/plugins/netmeeting/netmeetingplugin.cpp b/kopete/plugins/netmeeting/netmeetingplugin.cpp new file mode 100644 index 00000000..d2ea501c --- /dev/null +++ b/kopete/plugins/netmeeting/netmeetingplugin.cpp @@ -0,0 +1,91 @@ +/* + netmeetingplugin.cpp + + Copyright (c) 2003-2004 by Olivier Goffart + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "netmeetingplugin.h" + +#include +#include +#include +#include +#include + +#include "kopetepluginmanager.h" +#include "kopetechatsessionmanager.h" + +#include "msnchatsession.h" +#include "msnprotocol.h" +#include "msncontact.h" + +#include "netmeetinginvitation.h" +#include "netmeetingguiclient.h" + + +static const KAboutData aboutdata("kopete_netmeeting", I18N_NOOP("NetMeeting") , "1.0" ); +K_EXPORT_COMPONENT_FACTORY( kopete_netmeeting, KGenericFactory( &aboutdata ) ) + +NetMeetingPlugin::NetMeetingPlugin( QObject *parent, const char *name, const QStringList &/*args*/ ) +: Kopete::Plugin( KGlobal::instance(), parent, name ) +{ + if(MSNProtocol::protocol()) + slotPluginLoaded(MSNProtocol::protocol()); + else + connect(Kopete::PluginManager::self() , SIGNAL(pluginLoaded(Kopete::Plugin*) ), this, SLOT(slotPluginLoaded(Kopete::Plugin*))); + + + connect( Kopete::ChatSessionManager::self(), SIGNAL( chatSessionCreated( Kopete::ChatSession * )) , SLOT( slotNewKMM( Kopete::ChatSession * ) ) ); + //Add GUI action to all already existing kmm (if the plugin is launched when kopete already rining) + QValueList sessions = Kopete::ChatSessionManager::self()->sessions(); + for (QValueListIterator it= sessions.begin(); it!=sessions.end() ; ++it) + { + slotNewKMM(*it); + } +} + +NetMeetingPlugin::~NetMeetingPlugin() +{ + +} + +void NetMeetingPlugin::slotPluginLoaded(Kopete::Plugin *p) +{ + if(p->pluginId()=="MSNProtocol") + { + connect( p , SIGNAL(invitation(MSNInvitation*& , const QString & , long unsigned int , MSNChatSession* , MSNContact* )) , + this, SLOT( slotInvitation(MSNInvitation*& , const QString & , long unsigned int , MSNChatSession* , MSNContact* ))); + } +} + +void NetMeetingPlugin::slotNewKMM(Kopete::ChatSession *KMM) +{ + MSNChatSession *msnMM=dynamic_cast(KMM); + if(msnMM) + { + connect(this , SIGNAL( destroyed(QObject*)) , + new NetMeetingGUIClient(msnMM) + , SLOT(deleteLater())); + } +} + + +void NetMeetingPlugin::slotInvitation(MSNInvitation*& invitation, const QString &bodyMSG , long unsigned int /*cookie*/ , MSNChatSession* msnMM , MSNContact* c ) +{ + if(!invitation && bodyMSG.contains(NetMeetingInvitation::applicationID())) + { + invitation=new NetMeetingInvitation(true,c,msnMM); + invitation->parseInvitation(bodyMSG); + } +} + +#include "netmeetingplugin.moc" diff --git a/kopete/plugins/netmeeting/netmeetingplugin.h b/kopete/plugins/netmeeting/netmeetingplugin.h new file mode 100644 index 00000000..7427bbf8 --- /dev/null +++ b/kopete/plugins/netmeeting/netmeetingplugin.h @@ -0,0 +1,46 @@ +/* + netmeetingplugin.h + + Copyright (c) 2003 by Olivier Goffart + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + + + +#ifndef NetMeetingPLUGIN_H +#define NetMeetingPLUGIN_H + +#include "kopeteplugin.h" + +namespace Kopete { class ChatSession; } +class MSNChatSession; +class MSNContact; +class MSNInvitation; + + +class NetMeetingPlugin : public Kopete::Plugin +{ + Q_OBJECT + +public: + NetMeetingPlugin( QObject *parent, const char *name, const QStringList &args ); + ~NetMeetingPlugin(); + +private slots: + void slotNewKMM(Kopete::ChatSession *); + void slotPluginLoaded(Kopete::Plugin*); + void slotInvitation(MSNInvitation*& invitation, const QString &bodyMSG , long unsigned int cookie , MSNChatSession* msnMM , MSNContact* c ); + + +}; + +#endif + diff --git a/kopete/plugins/netmeeting/netmeetingpreferences.cpp b/kopete/plugins/netmeeting/netmeetingpreferences.cpp new file mode 100644 index 00000000..b28dfe09 --- /dev/null +++ b/kopete/plugins/netmeeting/netmeetingpreferences.cpp @@ -0,0 +1,81 @@ +/*************************************************************************** + Netmeetingpreferences.cpp - description + ------------------- + copyright : (C) 2004 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "netmeetingplugin.h" +#include "netmeetingprefs_ui.h" +#include "netmeetingpreferences.h" + +typedef KGenericFactory NetmeetingPreferencesFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_netmeeting, NetmeetingPreferencesFactory( "kcm_kopete_netmeeting" ) ) + +NetmeetingPreferences::NetmeetingPreferences(QWidget *parent, const char* /*name*/, const QStringList &args) + : KCModule(NetmeetingPreferencesFactory::instance(), parent, args) +{ + ( new QVBoxLayout( this ) )->setAutoAdd( true ); + preferencesDialog = new NetmeetingPrefsUI(this); + + connect(preferencesDialog->m_app , SIGNAL(textChanged(const QString &)) , this , SLOT(slotChanged())); + + load(); +} + +NetmeetingPreferences::~NetmeetingPreferences() +{ +} + +void NetmeetingPreferences::load() +{ + KConfig *config=KGlobal::config(); + config->setGroup("Netmeeting Plugin"); + preferencesDialog->m_app->setCurrentText(config->readEntry("NetmeetingApplication","ekiga -c callto://%1")); + emit KCModule::changed(false); +} + +void NetmeetingPreferences::save() +{ + KConfig *config=KGlobal::config(); + config->setGroup("Netmeeting Plugin"); + config->writeEntry("NetmeetingApplication",preferencesDialog->m_app->currentText()); + emit KCModule::changed(false); +} + + +void NetmeetingPreferences::slotChanged() +{ + emit KCModule::changed(true); +} + +#include "netmeetingpreferences.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/netmeeting/netmeetingpreferences.h b/kopete/plugins/netmeeting/netmeetingpreferences.h new file mode 100644 index 00000000..94a7031e --- /dev/null +++ b/kopete/plugins/netmeeting/netmeetingpreferences.h @@ -0,0 +1,46 @@ +/*************************************************************************** + netmeetingpreferences.h - description + ------------------- + copyright : (C) 2004 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef NetmeetingPREFERENCES_H +#define NetmeetingPREFERENCES_H + +#include +#include + +class NetmeetingPrefsUI; + +/** + *@author Olivier Goffart + */ + +class NetmeetingPreferences : public KCModule { + Q_OBJECT +public: + + NetmeetingPreferences(QWidget *parent = 0, const char* name = 0, const QStringList &args = QStringList()); + ~NetmeetingPreferences(); + + virtual void save(); + virtual void load(); + +private: + NetmeetingPrefsUI *preferencesDialog; + +private slots: + void slotChanged(); +}; + +#endif diff --git a/kopete/plugins/netmeeting/netmeetingprefs_ui.ui b/kopete/plugins/netmeeting/netmeetingprefs_ui.ui new file mode 100644 index 00000000..ed84eb6b --- /dev/null +++ b/kopete/plugins/netmeeting/netmeetingprefs_ui.ui @@ -0,0 +1,148 @@ + +NetmeetingPrefsUI +Olivier Goffart + + + Form1 + + + + 0 + 0 + 424 + 297 + + + + + unnamed + + + + textLabel1 + + + The NetMeeting Plugin allows you to start a video or voice chat with your MSN Messenger contacts. + +This is not the same as webcam chat you can find in the newer Windows Messenger®, but uses the older NetMeeting chat you can find in old versions. + + + WordBreak|AlignVCenter + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + layout1 + + + + unnamed + + + + textLabel2 + + + Application to launch: + + + + + + ekiga -c callto://%1 + + + + + konference callto://%1 + + + + m_app + + + + 3 + 0 + 0 + 0 + + + + true + + + true + + + + + + + textLabel3 + + + <b>%1</b> will be replaced by the ip to call + + + WordBreak|AlignVCenter + + + + + spacer1 + + + Vertical + + + Expanding + + + + 21 + 60 + + + + + + kActiveLabel1 + + + + 5 + 5 + 0 + 0 + + + + You can download Konference here: <a href="http://www.kde-apps.org/content/show.php?content=10395">http://www.kde-apps.org/content/show.php?content=10395</a> + + + + + + + + + kcombobox.h + klineedit.h + kactivelabel.h + + diff --git a/kopete/plugins/nowlistening/DESIGN b/kopete/plugins/nowlistening/DESIGN new file mode 100644 index 00000000..d2be5d5f --- /dev/null +++ b/kopete/plugins/nowlistening/DESIGN @@ -0,0 +1,52 @@ +Now Listening Plugin + +What It Does + +This plugin tells your chat buddies what media (music, ...) you are currently enjoying. If you turn on "Now Listening" for a contact, when a new track begins, it automatically sends a user defined message. + +How It Does It +Looks for running media applications (Noatun, Kscd, Xmms) using DCOP and asks them what track they are currently playing. The current track is discovered on a periodic basis, and if it has changed, it is advertised in live chatwindows to contacts that the user has indicated should be notified. + +Fair Points +Do we want to tell ALL chat partners what we're listening to? +- The operation of the plugin is configurable for each contact, using a context menu item. + +IRC chats probably DON'T want extraneous bumf. +- Yes, by using KMM::protocol() we can discover which protocol a chat uses and squelch messages to it. Would need something like a view of QCheckListItems to change the user's per protocol preferences in the Config. + +Should we only advertise if the other party to the chat is online? +- Yes + +Is it possible to tell whether media players are actually playing? +- Not for KsCD, but we can and do for Noatun and xmms. + +Alternative methods +Is there a way to be notified when tracks change? +- Not yet, but xmms and noatun seem to provide an interface to find out how long a track will be playing for. Could try and predict when a track will change. +Of course, if ppl change tracks before the track has ended then this prediction is no good. To do this we would need to add a per mediaplayer timer, or at least a pointer to a timer in the plugin (ugly!). + +Discovering all live chatwindows +KopeteMessageManagerFactory::factory()->sessions(), or Kopete::sessionFactory() until that is implemented? +- Done + +OOOH - HOW ABOUT SEEING IF XMMS-KDE HAS DCOP IN IT? +NO IT DOESN'T + +How contact specific plugin data works +Each metacontact has a method pluginData(KopetePlugin *). This takes a pointer to a plugin, and returns a QStringList containing the metacontact's data for that plugin. A corresponding setPluginData() method changes this. Who is responsible for making sure this data persists? + +What about custom actions? +KopetePlugin::custom[Chat|ContextMenuActions] both return a set of KActions that the plugin wants to have added to the UI. Looking at contactnotes, it seems that this set is recreated every time thses methods are called and they change the state of the plugin (currentContact). Is this so that the context menu is generated individually for each MC, so that the method that is called when the men item is clicked know which MC it applies to? + +Choosing whether to advertise to all contacts +We can either advertise periodically to (some) contacts, or we could just add an Action to advertise what we're currently listening to. +DONE + +Maybe need a per-contact toggle, and a per-chat button to advertise. +DONE + +Customising the advert message +DONE +Maybe provide substitution as in "Now listening to %track by %artist (on %album)". Here () indicates substitute whole clause only if enclosed variable is set. + +Change so action inserts text in current message? diff --git a/kopete/plugins/nowlistening/Makefile.am b/kopete/plugins/nowlistening/Makefile.am new file mode 100644 index 00000000..a9357d5f --- /dev/null +++ b/kopete/plugins/nowlistening/Makefile.am @@ -0,0 +1,25 @@ +METASOURCES = AUTO + +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(XMMS_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kopete_nowlistening.la kcm_kopete_nowlistening.la + +kopete_nowlistening_la_SOURCES = nowlisteningconfig.kcfgc nowlisteningplugin.cpp nlkscd.cpp nlnoatun.cpp nlxmms.cpp nowlisteningguiclient.cpp nljuk.cpp nlamarok.cpp nlkaffeine.cpp +kopete_nowlistening_la_LDFLAGS = -module $(KDE_PLUGIN) $(XMMS_LDFLAGS) $(all_libraries) +kopete_nowlistening_la_LIBADD = ../../libkopete/libkopete.la $(XMMS_LIBS) + +kcm_kopete_nowlistening_la_SOURCES = nowlisteningprefs.ui nowlisteningpreferences.cpp nowlisteningconfig.kcfgc +kcm_kopete_nowlistening_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_nowlistening_la_LIBADD = $(LIB_KOPETECOMPAT) $(LIB_KUTILS) + +service_DATA = kopete_nowlistening.desktop +servicedir = $(kde_servicesdir) + +kcm_DATA = kopete_nowlistening_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog + +kde_kcfg_DATA = nowlisteningconfig.kcfg + +mydatadir = $(kde_datadir)/kopete +mydata_DATA = nowlisteningui.rc nowlisteningchatui.rc +noinst_HEADERS = nlkaffeine.h diff --git a/kopete/plugins/nowlistening/README b/kopete/plugins/nowlistening/README new file mode 100644 index 00000000..83bdd6d1 --- /dev/null +++ b/kopete/plugins/nowlistening/README @@ -0,0 +1,41 @@ +README for Now Listening Plugin + +AUTHOR Will Stephenson + +This plugin tells chat partners what you're currently listening to. On demand, it sends them a configurable message. + +Caveat: It currently only works with KsCD, Xmms, Noatun, JuK and amaroK. Other media players will be supported shortly. + +Caveat 2: It relies on DCOP - I doubt much will happen if you're not running the rest of KDE. + +Caveat 3: There's no escaping of the substituted strings, so you might get it stuck in a loop... + +REQUIREMENTS +Requires XMMS remote control header xmmsctrl.h +(can be found in: +* SuSE's base xmms rpm +* Mandrake's libxmms1-devel rpm +* RedHat, TurboLinux, Conectiva: xmms-devel ) +Which in turn requires glib-devel and gtk-devel to build. + +IF CONFIGURE GIVES AN ERROR, OR IT DOESN'T WORK WITH XMMS, CHECK THESE ARE INSTALLED! + +Configure test doesn't tell us that this is why it bails. + +Rerun make -f Makefile.cvs && ./configure - or any subset of that which will +reconfigure the new part of the tree. + +make + +USE + +Enable the plugin in Settings->Configure Plugins. You may wish to change the default message. + +You can force a notification by typing the string "/media" at the start of a new message, which will be substituted, or by using the Chat->Actions->Send Media Info menu item. + +BUGS + +Please report to bugs.kde.org. If you need help contact me at lists@stevello.free-online.co.uk. + +TODO +More media players! diff --git a/kopete/plugins/nowlistening/configure.in.in b/kopete/plugins/nowlistening/configure.in.in new file mode 100644 index 00000000..08309761 --- /dev/null +++ b/kopete/plugins/nowlistening/configure.in.in @@ -0,0 +1,59 @@ +dnl AM_PATH_XMMS([1.0.0]) +dnl AM_INIT_AUTOMAKE(mediacontrol, 0.1) +dnl AC_PATH_PROG(XMMS_CONFIG, xmms-config, no) +dnl AM_PATH_XMMS(1.0.0,,AC_MSG_ERROR([*** XMMS >= 1.0.0 not installed - please install first ***])) + +AC_DEFUN([AC_CHECK_XMMS], +[ + AC_MSG_CHECKING([for libxmms]) + AC_CACHE_VAL(ac_cv_have_xmms, + [ + ac_save_libs="$LIBS" + LIBS="`xmms-config --libs`" + ac_CPPFLAGS_save="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $all_includes `xmms-config --cflags`" + ac_LDFLAGS_save="$LDFLAGS" + LDFLAGS="$LDFLAGS $all_libraries" + AC_TRY_LINK( + [#include ], + [xmms_remote_stop(0);], + [ac_cv_have_xmms="yes"], + [ac_cv_have_xmms="no"] + ) + LIBS="$ac_save_libs" + LDFLAGS="$ac_LDFLAGS_save" + CPPFLAGS="$ac_CPPFLAGS_save" + ]) + AC_MSG_RESULT($ac_cv_have_xmms) + if test "$ac_cv_have_xmms" = "yes"; then + XMMS_INCLUDES="`xmms-config --cflags`" + + for arg in `xmms-config --libs`; do + case $arg in + -[[lL]]*) + XMMS_LIBS="$XMMS_LIBS $arg" + ;; + *) + XMMS_LDFLAGS="$XMMS_LDFLAGS $arg" + esac + done + AC_DEFINE(HAVE_XMMS, 1, [Define if you have xmms libraries and header files.]) + fi +]) + +AC_ARG_WITH(xmms, + [AC_HELP_STRING(--with-xmms, + [enable support for XMMS @<:@default=check@:>@])], + [], with_xmms=check) + +if test "x$with_xmms" != xno; then + AC_CHECK_XMMS + + if test "x$with_xmms" != xcheck && test "x$ac_cv_have_xmms" = xno; then + AC_MSG_ERROR([--with-xmms was given, but test for XMMS failed]) + fi +fi + +AC_SUBST(XMMS_LIBS) +AC_SUBST(XMMS_LDFLAGS) +AC_SUBST(XMMS_INCLUDES) diff --git a/kopete/plugins/nowlistening/kopete_nowlistening.desktop b/kopete/plugins/nowlistening/kopete_nowlistening.desktop new file mode 100644 index 00000000..871e7574 --- /dev/null +++ b/kopete/plugins/nowlistening/kopete_nowlistening.desktop @@ -0,0 +1,125 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=kaboodle +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_nowlistening +X-KDE-PluginInfo-Author=Will Stephenson +X-KDE-PluginInfo-Email=will@stevello.free-online.co.uk +X-KDE-PluginInfo-Name=kopete_nowlistening +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Now Listening +Name[ar]=الاستماع جار +Name[be]=Слухаю зараз +Name[bg]=Инфо за пеÑен +Name[bn]=à¦à¦–ন শà§à¦¨à¦›à§‡à¦¨ +Name[bs]=Sada sluÅ¡am +Name[ca]=Ara s'escolta +Name[cs]=Nyní poslouchám +Name[cy]=Rwan yn Gwrando ar +Name[da]=Lytter nu +Name[de]=Im Hintergrund läuft +Name[el]=ΤώÏα ακοÏω +Name[eo]=Nun aÅ­skultanta +Name[es]=Escuchando +Name[et]=Praegu kuulan +Name[eu]=Orain entzuten +Name[fa]=الان گوش می‌کند +Name[fi]=Nyt soi +Name[fr]=En écoute +Name[ga]=Ag Éisteacht Anois +Name[gl]=Escoitando +Name[he]=מ×זין עכשיו +Name[hi]=नाऊ लिसनिंग +Name[hr]=Sada sluÅ¡am +Name[hu]=Most figyelek +Name[is]=Hlustandi á +Name[it]=Adesso sto ascoltando +Name[ja]=今è´ã„ã¦ã„ã‚‹ã‚‚ã® +Name[ka]=áƒáƒ®áƒšáƒ ისმინებრ+Name[kk]=Тыңдауда +Name[km]=កំពុង​ស្ដាប់ +Name[lt]=Dabar klausau +Name[mk]=Сега Ñлуша +Name[nb]=Hører nÃ¥ +Name[nds]=In'n Achtergrund löppt +Name[ne]=अहिले सà¥à¤¨à¤¿à¤°à¤¹à¥‡à¤•à¥‹ छ +Name[nl]=Luistert naar +Name[nn]=Høyrer pÃ¥ +Name[pa]=ਹà©à¨£ ਸà©à¨£à¨¦à¨¾ +Name[pl]=Czego sÅ‚ucham +Name[pt]=Agora a Ouvir +Name[pt_BR]=Ouvindo agora +Name[ru]=Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð·Ð²ÑƒÑ‡Ð¸Ñ‚ +Name[se]=Dál guldaleamen +Name[sk]=Teraz poÄúvam +Name[sl]=Zdaj posluÅ¡am +Name[sr]=Сада Ñлушам +Name[sr@Latn]=Sada sluÅ¡am +Name[sv]=Lyssnar nu +Name[ta]=தறà¯à®ªà¯‹à®¤à¯ கேடà¯à®Ÿà¯à®•à¯à®•à¯Šà®£à¯à®Ÿà¯à®³à¯à®³à®¾à®°à¯ +Name[tg]=Ҳоло гӯшкунӣ рафта иÑтодааÑÑ‚ +Name[tr]=Åžimdi Dinleniyor +Name[uk]=Зараз Ñлухаю +Name[zh_CN]=çŽ°åœ¨æ”¶å¬ +Name[zh_HK]=ç¾æ­£è†è½ +Name[zh_TW]=æ­£åœ¨æ”¶è½ +Comment=Tells your buddies what you're listening to +Comment[ar]=تخبر أصدقائك بما تستمع إليه +Comment[be]=ПаведамлÑе ÑÑбрам, што вы зараз Ñлухаеце +Comment[bg]=ПриÑтавка за информиране на приÑтелите какво Ñлушате +Comment[bn]=আপনার বনà§à¦§à§à¦¦à§‡à¦° বলে আপনি কি শà§à¦¨à¦›à§‡à¦¨ +Comment[bs]=ObavjeÅ¡tava vaÅ¡e prijatelje o muzici koju sluÅ¡ate +Comment[ca]=Li diu als vostres companys què esteu escoltant +Comment[cs]=SdÄ›lí vaÅ¡im kamarádům, co právÄ› posloucháte +Comment[cy]=Dweud wrth eich cyfeillion be y gwrandwch arnodd +Comment[da]=Fortæller dine venner hvad du lytter til +Comment[de]=Teilt Ihren Freunden mit, welche Musik Sie gerade hören +Comment[el]=ΕνημεÏώνει τους φίλους σας σχετικά με το τι ακοÏτε +Comment[es]=Le cuenta a sus contactos lo que está escuchando +Comment[et]=Semudele teatamine, mida parajasti kuulad +Comment[eu]=Zure lagunei zer entzuten ari zaren esaten die +Comment[fa]=به دوستان شما می‌گوید Ú©Ù‡ به Ú†Ù‡ چیز گوش می‌دهید +Comment[fi]=Kertoo kavereillesi mitä kuuntelet tällä hetkellä +Comment[fr]=Indique à vos contacts ce que vous êtes en train d'écouter +Comment[gl]=Dílle ós teus amigos que estás a escoitar +Comment[he]=מספר לחבריך ל×יזו מוסיקה ×תה מ×זין כרגע +Comment[hi]=आपके बडà¥à¤¡à¥€à¤¸ को बताता है कि आप कà¥à¤¯à¤¾ सà¥à¤¨ रहे हैं +Comment[hr]=Govori vaÅ¡im prijateljima Å¡to trenutno sluÅ¡ate +Comment[hu]=A partnerek értesítése arról, hogy Ön mit hallgat +Comment[is]=Segir vinum þínum á hvað þú ert að hlusta +Comment[it]=Di' ai tuoi amici cosa stai ascoltando +Comment[ja]=何をè´ã„ã¦ã„ã‚‹ã‹ã‚’仲間ã«ä¼ãˆã‚‹ +Comment[ka]=áƒáƒ¢áƒ§áƒáƒ‘ინებს თქვენს მეგáƒáƒ‘რებს, თუ რáƒáƒ¡ უსმენთ +Comment[kk]=ДоÑтарыңызға тыңдап тұрғаныңыз туралы хабарлайды +Comment[km]=ប្រាប់​សម្លាញ់​អ្នក​ážáž¶ អ្នក​កំពុង​ស្ដាប់​អ្វី +Comment[lt]=PraneÅ¡kite biÄiuliams kÄ… dabar klausotÄ—s +Comment[mk]=Им кажува на вашите пријатели што Ñлушате +Comment[nb]=Forteller venner hva du hører pÃ¥ +Comment[nds]=Vertellt Dien Frünnen, welke Musik Du jüst höörst +Comment[ne]=तपाईà¤à¤²à¥‡ सà¥à¤¨à¤¿à¤°à¤¹à¤¨à¥à¤­à¤à¤•à¥‹ तपाईà¤à¤•à¥‹ साथीलाई भनà¥à¤¦à¤› +Comment[nl]=Vertelt je vrienden naar welke muziek je luistert +Comment[nn]=Fortel venner kva du høyrer pÃ¥ +Comment[pl]=Przekazuje znajomym, czego obecnie sÅ‚uchasz +Comment[pt]=Indica aos seus amigos o que você está a ouvir +Comment[pt_BR]=Diz a seus colegas o que você está escutando +Comment[ru]=Сообщает вашим ÑобеÑедникам что вы ÑÐµÐ¹Ñ‡Ð°Ñ Ñлушаете +Comment[se]=Muitala olbmáide maid dál leat guldaleamen +Comment[sk]=Oznámi vaÅ¡im priateľom, koho práve poÄúvate +Comment[sl]=SporoÄí vaÅ¡im prijateljem, kaj posluÅ¡ate +Comment[sr]=Говори вашим другарима шта тренутно Ñлушате +Comment[sr@Latn]=Govori vaÅ¡im drugarima Å¡ta trenutno sluÅ¡ate +Comment[sv]=Talar om för kompisar vad du lyssnar pÃ¥ +Comment[ta]=நீஙà¯à®•à®³à¯ கேடà¯à®Ÿà¯à®•à¯ கொணà¯à®Ÿà®¿à®°à¯à®ªà¯à®ªà®¤à¯ˆ உஙà¯à®•à®³à¯ எதிராலிகà¯à®•à¯ சொலà¯à®²à¯à®®à¯ +Comment[tg]=Ба дӯÑтонатон чи гӯш карда иÑтодаатонро мегӯÑд +Comment[tr]=DinlediÄŸiniz arkadaÅŸların listesini söyler +Comment[uk]=Сповіщає ваших друзів про, те що ви зараз Ñлухаєте +Comment[zh_CN]=告诉您的好å‹æ‚¨æ­£åœ¨æ”¶å¬çš„内容 +Comment[zh_HK]=告訴您的伙伴您正在è†è½ç”šéº¼ +Comment[zh_TW]=告訴您的好å‹æ‚¨æ­£åœ¨æ”¶è½ä»€éº¼ + diff --git a/kopete/plugins/nowlistening/kopete_nowlistening_config.desktop b/kopete/plugins/nowlistening/kopete_nowlistening_config.desktop new file mode 100644 index 00000000..8b01b2bd --- /dev/null +++ b/kopete/plugins/nowlistening/kopete_nowlistening_config.desktop @@ -0,0 +1,121 @@ +[Desktop Entry] +Icon=kaboodle +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_nowlistening +X-KDE-FactoryName=NowListeningConfigFactory +X-KDE-ParentApp=kopete_nowlistening +X-KDE-ParentComponents=kopete_nowlistening + +Name=Now Listening +Name[ar]=الاستماع جار +Name[be]=Слухаю зараз +Name[bg]=Инфо за пеÑен +Name[bn]=à¦à¦–ন শà§à¦¨à¦›à§‡à¦¨ +Name[bs]=Sada sluÅ¡am +Name[ca]=Ara s'escolta +Name[cs]=Nyní poslouchám +Name[cy]=Rwan yn Gwrando ar +Name[da]=Lytter nu +Name[de]=Im Hintergrund läuft +Name[el]=ΤώÏα ακοÏω +Name[eo]=Nun aÅ­skultanta +Name[es]=Escuchando +Name[et]=Praegu kuulan +Name[eu]=Orain entzuten +Name[fa]=الان گوش می‌کند +Name[fi]=Nyt soi +Name[fr]=En écoute +Name[ga]=Ag Éisteacht Anois +Name[gl]=Escoitando +Name[he]=מ×זין עכשיו +Name[hi]=नाऊ लिसनिंग +Name[hr]=Sada sluÅ¡am +Name[hu]=Most figyelek +Name[is]=Hlustandi á +Name[it]=Adesso sto ascoltando +Name[ja]=今è´ã„ã¦ã„ã‚‹ã‚‚ã® +Name[ka]=áƒáƒ®áƒšáƒ ისმინებრ+Name[kk]=Тыңдауда +Name[km]=កំពុង​ស្ដាប់ +Name[lt]=Dabar klausau +Name[mk]=Сега Ñлуша +Name[nb]=Hører nÃ¥ +Name[nds]=In'n Achtergrund löppt +Name[ne]=अहिले सà¥à¤¨à¤¿à¤°à¤¹à¥‡à¤•à¥‹ छ +Name[nl]=Luistert naar +Name[nn]=Høyrer pÃ¥ +Name[pa]=ਹà©à¨£ ਸà©à¨£à¨¦à¨¾ +Name[pl]=Czego sÅ‚ucham +Name[pt]=Agora a Ouvir +Name[pt_BR]=Ouvindo agora +Name[ru]=Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð·Ð²ÑƒÑ‡Ð¸Ñ‚ +Name[se]=Dál guldaleamen +Name[sk]=Teraz poÄúvam +Name[sl]=Zdaj posluÅ¡am +Name[sr]=Сада Ñлушам +Name[sr@Latn]=Sada sluÅ¡am +Name[sv]=Lyssnar nu +Name[ta]=தறà¯à®ªà¯‹à®¤à¯ கேடà¯à®Ÿà¯à®•à¯à®•à¯Šà®£à¯à®Ÿà¯à®³à¯à®³à®¾à®°à¯ +Name[tg]=Ҳоло гӯшкунӣ рафта иÑтодааÑÑ‚ +Name[tr]=Åžimdi Dinleniyor +Name[uk]=Зараз Ñлухаю +Name[zh_CN]=çŽ°åœ¨æ”¶å¬ +Name[zh_HK]=ç¾æ­£è†è½ +Name[zh_TW]=æ­£åœ¨æ”¶è½ +Comment=Tells your buddies what you're listening to +Comment[ar]=تخبر أصدقائك بما تستمع إليه +Comment[be]=ПаведамлÑе ÑÑбрам, што вы зараз Ñлухаеце +Comment[bg]=ПриÑтавка за информиране на приÑтелите какво Ñлушате +Comment[bn]=আপনার বনà§à¦§à§à¦¦à§‡à¦° বলে আপনি কি শà§à¦¨à¦›à§‡à¦¨ +Comment[bs]=ObavjeÅ¡tava vaÅ¡e prijatelje o muzici koju sluÅ¡ate +Comment[ca]=Li diu als vostres companys què esteu escoltant +Comment[cs]=SdÄ›lí vaÅ¡im kamarádům, co právÄ› posloucháte +Comment[cy]=Dweud wrth eich cyfeillion be y gwrandwch arnodd +Comment[da]=Fortæller dine venner hvad du lytter til +Comment[de]=Teilt Ihren Freunden mit, welche Musik Sie gerade hören +Comment[el]=ΕνημεÏώνει τους φίλους σας σχετικά με το τι ακοÏτε +Comment[es]=Le cuenta a sus contactos lo que está escuchando +Comment[et]=Semudele teatamine, mida parajasti kuulad +Comment[eu]=Zure lagunei zer entzuten ari zaren esaten die +Comment[fa]=به دوستان شما می‌گوید Ú©Ù‡ به Ú†Ù‡ چیز گوش می‌دهید +Comment[fi]=Kertoo kavereillesi mitä kuuntelet tällä hetkellä +Comment[fr]=Indique à vos contacts ce que vous êtes en train d'écouter +Comment[gl]=Dílle ós teus amigos que estás a escoitar +Comment[he]=מספר לחבריך ל×יזו מוסיקה ×תה מ×זין כרגע +Comment[hi]=आपके बडà¥à¤¡à¥€à¤¸ को बताता है कि आप कà¥à¤¯à¤¾ सà¥à¤¨ रहे हैं +Comment[hr]=Govori vaÅ¡im prijateljima Å¡to trenutno sluÅ¡ate +Comment[hu]=A partnerek értesítése arról, hogy Ön mit hallgat +Comment[is]=Segir vinum þínum á hvað þú ert að hlusta +Comment[it]=Di' ai tuoi amici cosa stai ascoltando +Comment[ja]=何をè´ã„ã¦ã„ã‚‹ã‹ã‚’仲間ã«ä¼ãˆã‚‹ +Comment[ka]=áƒáƒ¢áƒ§áƒáƒ‘ინებს თქვენს მეგáƒáƒ‘რებს, თუ რáƒáƒ¡ უსმენთ +Comment[kk]=ДоÑтарыңызға тыңдап тұрғаныңыз туралы хабарлайды +Comment[km]=ប្រាប់​សម្លាញ់​អ្នក​ážáž¶ អ្នក​កំពុង​ស្ដាប់​អ្វី +Comment[lt]=PraneÅ¡kite biÄiuliams kÄ… dabar klausotÄ—s +Comment[mk]=Им кажува на вашите пријатели што Ñлушате +Comment[nb]=Forteller venner hva du hører pÃ¥ +Comment[nds]=Vertellt Dien Frünnen, welke Musik Du jüst höörst +Comment[ne]=तपाईà¤à¤²à¥‡ सà¥à¤¨à¤¿à¤°à¤¹à¤¨à¥à¤­à¤à¤•à¥‹ तपाईà¤à¤•à¥‹ साथीलाई भनà¥à¤¦à¤› +Comment[nl]=Vertelt je vrienden naar welke muziek je luistert +Comment[nn]=Fortel venner kva du høyrer pÃ¥ +Comment[pl]=Przekazuje znajomym, czego obecnie sÅ‚uchasz +Comment[pt]=Indica aos seus amigos o que você está a ouvir +Comment[pt_BR]=Diz a seus colegas o que você está escutando +Comment[ru]=Сообщает вашим ÑобеÑедникам что вы ÑÐµÐ¹Ñ‡Ð°Ñ Ñлушаете +Comment[se]=Muitala olbmáide maid dál leat guldaleamen +Comment[sk]=Oznámi vaÅ¡im priateľom, koho práve poÄúvate +Comment[sl]=SporoÄí vaÅ¡im prijateljem, kaj posluÅ¡ate +Comment[sr]=Говори вашим другарима шта тренутно Ñлушате +Comment[sr@Latn]=Govori vaÅ¡im drugarima Å¡ta trenutno sluÅ¡ate +Comment[sv]=Talar om för kompisar vad du lyssnar pÃ¥ +Comment[ta]=நீஙà¯à®•à®³à¯ கேடà¯à®Ÿà¯à®•à¯ கொணà¯à®Ÿà®¿à®°à¯à®ªà¯à®ªà®¤à¯ˆ உஙà¯à®•à®³à¯ எதிராலிகà¯à®•à¯ சொலà¯à®²à¯à®®à¯ +Comment[tg]=Ба дӯÑтонатон чи гӯш карда иÑтодаатонро мегӯÑд +Comment[tr]=DinlediÄŸiniz arkadaÅŸların listesini söyler +Comment[uk]=Сповіщає ваших друзів про, те що ви зараз Ñлухаєте +Comment[zh_CN]=告诉您的好å‹æ‚¨æ­£åœ¨æ”¶å¬çš„内容 +Comment[zh_HK]=告訴您的伙伴您正在è†è½ç”šéº¼ +Comment[zh_TW]=告訴您的好å‹æ‚¨æ­£åœ¨æ”¶è½ä»€éº¼ + diff --git a/kopete/plugins/nowlistening/nlamarok.cpp b/kopete/plugins/nowlistening/nlamarok.cpp new file mode 100644 index 00000000..15d19411 --- /dev/null +++ b/kopete/plugins/nowlistening/nlamarok.cpp @@ -0,0 +1,125 @@ +/* + nlamarok.cpp + + Kopete Now Listening To plugin + + Copyright (c) 2002,2003,2004 by Will Stephenson + Kopete + Copyright (c) 2002,2003,2004 by the Kopete developers + + Purpose: + This class abstracts the interface to amaroK by + implementing NLMediaPlayer + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include + +#include "nlmediaplayer.h" +#include "nlamarok.h" + +NLamaroK::NLamaroK( DCOPClient *client ) : NLMediaPlayer() +{ + m_client = client; + m_type = Audio; + m_name = "Amarok"; +} + +void NLamaroK::update() +{ + m_playing = false; + m_newTrack = false; + QString newTrack; + QByteArray data, replyData; + QCString replyType; + QString result; + + // see if amaroK is registered with DCOP + if ( !m_client->isApplicationRegistered( "amarok" ) ) + { + kdDebug ( 14307 ) << "AmaroK is not running!\n" << endl; + return; + } + + // see if it's playing + // use status() call first, if not supported (amaroK 1.0 or earlier), use isPlaying + + if ( !m_client->call( "amarok", "player", "status()", data, + replyType, replyData ) ) + { + kdDebug( 14307 ) << k_funcinfo << " DCOP status() returned error, falling back to isPlaying()." << endl; + if ( !m_client->call( "amarok", "player", "isPlaying()", data, + replyType, replyData ) ) + { + kdDebug( 14307 ) << k_funcinfo << " DCOP error on Amarok." << endl; + } + else + { + QDataStream reply( replyData, IO_ReadOnly ); + if ( replyType == "bool" ) { + reply >> m_playing; + } + } + } + else + { + int status = 0; + + QDataStream reply( replyData, IO_ReadOnly ); + if ( replyType == "int" ) { + reply >> status; + kdDebug( 14307 ) << k_funcinfo << "Amarok status()=" << status << endl; + } + + if ( status ) + { + m_playing = true; + } + } + + if ( m_client->call( "amarok", "player", "title()", data, + replyType, replyData ) ) + { + QDataStream reply( replyData, IO_ReadOnly ); + + if ( replyType == "QString" ) { + reply >> newTrack; + } + } + + if ( newTrack != m_track ) + { + m_newTrack = true; + m_track = newTrack; + } + + if ( m_client->call( "amarok", "player", "album()", data, + replyType, replyData ) ) + { + QDataStream reply( replyData, IO_ReadOnly ); + + if ( replyType == "QString" ) { + reply >> m_album; + } + } + + if ( m_client->call( "amarok", "player", "artist()", data, + replyType, replyData ) ) + { + QDataStream reply( replyData, IO_ReadOnly ); + + if ( replyType == "QString" ) { + reply >> m_artist; + } + } +} + diff --git a/kopete/plugins/nowlistening/nlamarok.h b/kopete/plugins/nowlistening/nlamarok.h new file mode 100644 index 00000000..b79900c2 --- /dev/null +++ b/kopete/plugins/nowlistening/nlamarok.h @@ -0,0 +1,40 @@ +/* + nlamarok.h + + Kopete Now Listening To plugin + + + Copyright (c) 2002,2003,2004 by Will Stephenson + + Kopete (c) 2002,2003,2004 by the Kopete developers + + Purpose: + This class abstracts the interface to amaroK by + implementing NLMediaPlayer + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef NLAMAROK_H +#define NLAMAROK_H + +#include + +class NLamaroK : public NLMediaPlayer +{ + public: + NLamaroK( DCOPClient *client ); + virtual void update(); + protected: + DCOPClient *m_client; +}; + +#endif + diff --git a/kopete/plugins/nowlistening/nljuk.cpp b/kopete/plugins/nowlistening/nljuk.cpp new file mode 100644 index 00000000..41de23bc --- /dev/null +++ b/kopete/plugins/nowlistening/nljuk.cpp @@ -0,0 +1,112 @@ +/* + nljuk.cpp + + Kopete Now Listening To plugin + + Copyright (c) 2002,2003,2004 by Will Stephenson + Copyright (c) 2003 by Ismail Donmez + Copyright (c) 2002,2003 by the Kopete developers + + Purpose: + This class abstracts the interface to JuK by + implementing NLMediaPlayer + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include + +#include "nlmediaplayer.h" +#include "nljuk.h" + +NLJuk::NLJuk( DCOPClient *client ) : NLMediaPlayer() +{ + m_client = client; + m_type = Audio; + m_name = "JuK"; +} + +void NLJuk::update() +{ + m_playing = false; + QString newTrack; + + // see if JuK is registered with DCOP + if ( m_client->isApplicationRegistered( "juk" ) ) + { + // see if it's playing + QByteArray data, replyData; + QCString replyType; + QString result; + + if ( m_client->call( "juk", "Player", "playing()", data, + replyType, replyData ) ) + { + QDataStream reply( replyData, IO_ReadOnly ); + if ( replyType == "bool" ) { + reply >> m_playing; + } + } + + { + QDataStream arg( data, IO_WriteOnly ); + arg << QString::fromLatin1("Album"); + if ( m_client->call( "juk", "Player", "trackProperty(QString)", data, + replyType, replyData ) ) + { + QDataStream reply( replyData, IO_ReadOnly ); + + if ( replyType == "QString" ) { + reply >> m_album; + } + } + } + + { + QDataStream arg( data, IO_WriteOnly ); + arg << QString::fromLatin1("Artist"); + if ( m_client->call( "juk", "Player", "trackProperty(QString)", data, + replyType, replyData ) ) + { + QDataStream reply( replyData, IO_ReadOnly ); + + if ( replyType == "QString" ) { + reply >> m_artist; + } + } + } + + { + QDataStream arg( data, IO_WriteOnly ); + arg << QString::fromLatin1("Title"); + if ( m_client->call( "juk", "Player", "trackProperty(QString)", data, + replyType, replyData ) ) + { + QDataStream reply( replyData, IO_ReadOnly ); + + if ( replyType == "QString" ) { + reply >> newTrack; + } + } + } + + if ( newTrack != m_track ) + { + m_newTrack = true; + m_track = newTrack; + } + else + m_newTrack = false; + } + else + kdDebug( 14307 ) << "Juk is not running!\n" << endl; +} + diff --git a/kopete/plugins/nowlistening/nljuk.h b/kopete/plugins/nowlistening/nljuk.h new file mode 100644 index 00000000..40794c59 --- /dev/null +++ b/kopete/plugins/nowlistening/nljuk.h @@ -0,0 +1,41 @@ +/* + nljuk.h + + Kopete Now Listening To plugin + + + Copyright (c) 2002,2003,2004 by Will Stephenson + Copyright (c) 2003 by Ismail Donmez + + Kopete (c) 2002,2003 by the Kopete developers + + Purpose: + This class abstracts the interface to JuK by + implementing NLMediaPlayer + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef NLJUK_H +#define NLJUK_H + +#include + +class NLJuk : public NLMediaPlayer +{ + public: + NLJuk( DCOPClient *client ); + virtual void update(); + protected: + DCOPClient *m_client; +}; + +#endif + diff --git a/kopete/plugins/nowlistening/nlkaffeine.cpp b/kopete/plugins/nowlistening/nlkaffeine.cpp new file mode 100644 index 00000000..fe02077f --- /dev/null +++ b/kopete/plugins/nowlistening/nlkaffeine.cpp @@ -0,0 +1,101 @@ +/* + nlkaffeine.cpp + + Kopete Now Listening To plugin + + Copyright (c) 2004 by Will Stephenson + Kopete + Copyright (c) 2002,2003,2004 by the Kopete developers + + Purpose: + This class abstracts the interface to Kaffeine by + implementing NLMediaPlayer + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include + +#include "nlmediaplayer.h" +#include "nlkaffeine.h" + +NLKaffeine::NLKaffeine( DCOPClient *client ) : NLMediaPlayer() +{ + m_client = client; + m_type = Video; + m_name = "Kaffeine"; +} + +void NLKaffeine::update() +{ + m_playing = false; + m_newTrack = false; + QString newTrack; + bool error = true; // Asume we have a error first. + QCString kaffeineIface("Kaffeine"), kaffeineGetTrack("getTitle()"); + + // see if kaffeine is registered with DCOP + if ( m_client->isApplicationRegistered( "kaffeine" ) ) + { + // see if it's playing + QByteArray data, replyData; + QCString replyType; + QString result; + if ( !m_client->call( "kaffeine", kaffeineIface, "isPlaying()", data, + replyType, replyData ) ) + { + kdDebug ( 14307 ) << k_funcinfo << " Trying DCOP interface of Kaffeine >= 0.5" << endl; + // Trying with the new Kaffeine DCOP interface (>=0.5) + kaffeineIface = "KaffeineIface"; + kaffeineGetTrack = "title()"; + if( !m_client->call( "kaffeine", kaffeineIface, "isPlaying()", data, replyType, replyData ) ) + { + kdDebug( 14307 ) << k_funcinfo << " DCOP error on Kaffeine." << endl; + } + else + { + error = false; + } + } + else + { + error = false; + } + + // If we didn't get any DCOP error, check if Kaffeine is playing. + if(!error) + { + QDataStream reply( replyData, IO_ReadOnly ); + if ( replyType == "bool" ) { + reply >> m_playing; + kdDebug( 14307 ) << "checked if Kaffeine is playing!" << endl; + } + } + + if ( m_client->call( "kaffeine", kaffeineIface, kaffeineGetTrack, data, + replyType, replyData ) ) + { + QDataStream reply( replyData, IO_ReadOnly ); + + if ( replyType == "QString" ) { + reply >> newTrack; + } + } + if( newTrack != m_track ) + { + m_newTrack = true; + m_track = newTrack; + } + } + else + kdDebug ( 14307 ) << "Kaffeine is not running!\n" << endl; +} + diff --git a/kopete/plugins/nowlistening/nlkaffeine.h b/kopete/plugins/nowlistening/nlkaffeine.h new file mode 100644 index 00000000..7f868e79 --- /dev/null +++ b/kopete/plugins/nowlistening/nlkaffeine.h @@ -0,0 +1,40 @@ +/* + nlkaffeine.h + + Kopete Now Listening To plugin + + + Copyright (c) 2002,2003,2004 by Will Stephenson + + Kopete (c) 2002,2003,2004 by the Kopete developers + + Purpose: + This class abstracts the interface to Kaffeine by + implementing NLMediaPlayer + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef NLKAFFEINE_H +#define NLKAFFEINE_H + +#include + +class NLKaffeine : public NLMediaPlayer +{ + public: + NLKaffeine( DCOPClient *client ); + virtual void update(); + protected: + DCOPClient *m_client; +}; + +#endif + diff --git a/kopete/plugins/nowlistening/nlkscd.cpp b/kopete/plugins/nowlistening/nlkscd.cpp new file mode 100644 index 00000000..a20c809b --- /dev/null +++ b/kopete/plugins/nowlistening/nlkscd.cpp @@ -0,0 +1,121 @@ +/* + nlkscd.cpp + + Kopete Now Listening To plugin + + Copyright (c) 2002,2003,2004 by Will Stephenson + + Kopete (c) 2002,2003,2004 by the Kopete developers + + Purpose: + This class abstracts the interface to KsCD by + implementing NLMediaPlayer + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include + +#include "nlmediaplayer.h" + +#include "nlkscd.h" + +NLKscd::NLKscd( DCOPClient *client ) : NLMediaPlayer() +{ + m_client = client; + m_type = Audio; + m_name = "KsCD"; +} + +void NLKscd::update() +{ + m_playing = false; + QString newTrack; + // see if it's registered with DCOP + if ( m_client->isApplicationRegistered( "kscd" ) ) + { + // see if it's playing + QByteArray data, replyData; + QCString replyType; + if ( !m_client->call( "kscd", "CDPlayer", "playing()", data, + replyType, replyData ) ) + { + // we're talking to a KsCD without the playing() method + m_playing = true; +// kdDebug( 14307 ) << "NLKscd::update() - KsCD without playing()" +// << endl; + } + else + { + QDataStream reply( replyData, IO_ReadOnly ); + if ( replyType == "bool" ) { + reply >> m_playing; +// kdDebug( 14307 ) << "NLKscd::update() - KsCD is " << +// ( m_playing ? "" : "not " ) << "playing!" << endl; + } + } + // poll it for its current artist + if ( !m_client->call( "kscd", "CDPlayer", + "currentArtist()", data, replyType, replyData ) ) + kdDebug( 14307 ) << "NLKscd::update() DCOP error" + << endl; + else { + QDataStream reply( replyData, IO_ReadOnly ); + if ( replyType == "QString" ) + reply >> m_artist; + else + kdDebug( 14307 ) << "NLKscd::update() trackList returned unexpected reply type!" << endl; + } + + //album + if ( !m_client->call( "kscd", "CDPlayer", + "currentAlbum()", data, replyType, replyData ) ) + kdDebug( 14307 ) << "NLKscd::update() DCOP error" + << endl; + else { + QDataStream reply( replyData, IO_ReadOnly ); + if ( replyType == "QString" ) + reply >> m_album; + else + kdDebug( 14307 ) << "NLKscd::update() trackList returned unexpected reply type!" << endl; + } + + // Get the current track title + if ( !m_client->call( "kscd", "CDPlayer", + "currentTrackTitle()", data, replyType, replyData ) ) + kdDebug( 14307 ) << "NLKscd::update() - there was some error using DCOP." << endl; + else { + QDataStream reply( replyData, IO_ReadOnly ); + if ( replyType == "QString" ) { + reply >> newTrack; + //kdDebug( 14307 ) << "the result is: " << newTrack.latin1() + // << endl; + } else + kdDebug( 14307 ) << "NLKscd::update()- currentTrackTitle " + << "returned unexpected reply type!" << endl; + } + // if the current track title has changed + if ( newTrack != m_track ) + { + m_newTrack = true; + m_track = newTrack; + } + else + m_newTrack = false; +// kdDebug( 14307 ) << "NLKscd::update() - found kscd - " +// << m_track << endl; + + } +// else +// kdDebug( 14307 ) << "NLKscd::update() - kscd not found" << endl; +} + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/nowlistening/nlkscd.h b/kopete/plugins/nowlistening/nlkscd.h new file mode 100644 index 00000000..e245ec76 --- /dev/null +++ b/kopete/plugins/nowlistening/nlkscd.h @@ -0,0 +1,41 @@ +/* + nlkscd.h + + Kopete Now Listening To plugin + + Copyright (c) 2002,2003,2004 by Will Stephenson + + Kopete (c) 2002,2003,2004 by the Kopete developers + + Purpose: + This class abstracts the interface to Kscd by + implementing NLMediaPlayer + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef NLKSCD_H +#define NLKSCD_H + +#include + +class NLKscd : public NLMediaPlayer +{ + public: + NLKscd( DCOPClient *client ); + virtual void update(); + protected: + DCOPClient *m_client; + +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/nowlistening/nlmediaplayer.h b/kopete/plugins/nowlistening/nlmediaplayer.h new file mode 100644 index 00000000..5c619150 --- /dev/null +++ b/kopete/plugins/nowlistening/nlmediaplayer.h @@ -0,0 +1,58 @@ +/* + nlmediaplayer.h + + Kopete Now Listening To plugin + + Copyright (c) 2002,2003,2004 by Will Stephenson + + Kopete (c) 2002,2003,2004 by the Kopete developers + + Purpose: + Represents a generic media player + and abstracts real media players' actual interfaces (DCOP for KDE apps, + otherwise anything goes! + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef NLMEDIAPLAYER_H +#define NLMEDIAPLAYER_H + +class NLMediaPlayer +{ + public: + enum NLMediaType { Audio, Video }; + NLMediaPlayer() { m_playing = false; m_artist = ""; m_album = ""; m_track = ""; m_newTrack = false; } + virtual ~NLMediaPlayer() {} + /** + * This communicates with the actual mediaplayer and updates + * the model of its state in this class + */ + virtual void update() = 0; + bool playing() const { return m_playing; } + bool newTrack() const { return m_newTrack; } + QString artist() const { return m_artist; } + QString album() const { return m_album; } + QString track() const { return m_track; } + QString name() const{ return m_name; } + NLMediaType mediaType() const { return m_type; } + protected: + // The name of the application + QString m_name; + bool m_playing; + bool m_newTrack; + QString m_artist; + QString m_album; + QString m_track; + NLMediaType m_type; +}; + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/nowlistening/nlnoatun.cpp b/kopete/plugins/nowlistening/nlnoatun.cpp new file mode 100644 index 00000000..62bdc8ba --- /dev/null +++ b/kopete/plugins/nowlistening/nlnoatun.cpp @@ -0,0 +1,147 @@ +/* + nlnoatun.cpp + + Kopete Now Listening To plugin + + Copyright (c) 2002 by Will Stephenson + + Kopete (c) 2002 by the Kopete developers + + Purpose: + This class abstracts the interface to Noatun by + implementing NLMediaPlayer + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include "nlmediaplayer.h" +#include "nlnoatun.h" + +NLNoatun::NLNoatun( DCOPClient *client ) : NLMediaPlayer() +{ + m_client = client; + m_name = "noatun"; + // FIXME - detect current media type in update() + m_type = Audio; +} + +void NLNoatun::update() +{ + // Thanks mETz for telling me about Noatun's currentProperty() + m_playing = false; + QString newTrack; + // see if it's registered with DCOP + QCString appname = find(); + if ( !appname.isEmpty() ) + { + // see if it's playing + QByteArray data, replyData; + QCString replyType; + if ( !m_client->call( appname, "Noatun", "state()", data, + replyType, replyData ) ) + { + kdDebug( 14307 ) << "NLNoatun::update() DCOP error on " << appname << endl; + } + else + { + QDataStream reply( replyData, IO_ReadOnly ); + if ( replyType == "int" ) { + int state = 0; + reply >> state; + m_playing = ( state == 2 ); + //kdDebug( 14307 ) << "checked if Noatun is playing!" << endl; + } + } + // poll it for its current songtitle, artist and album + // Using properties + m_artist = currentProperty( appname, "author" ); + m_album = currentProperty( appname, "album" ); + QString title = currentProperty( appname, "title" ); + // if properties not set ( no id3 tags... ) fallback to filename + if ( !title.isEmpty() ) + newTrack = title; + else + // Using the title() method + if ( !m_client->call( appname, "Noatun", + "title()", data, replyType, replyData ) ) + kdDebug( 14307 ) << "NLNoatun::update() DCOP error on " << appname + << endl; + else { + QDataStream reply( replyData, IO_ReadOnly ); + if ( replyType == "QString" ) { + reply >> newTrack; + } else + kdDebug( 14307 ) << "NLNoatun::update(), title() returned unexpected reply type!" << endl; + } + // if the current track title has changed + if ( newTrack != m_track ) + { + m_newTrack = true; + m_track = newTrack; + } + else + m_newTrack = false; + kdDebug( 14307 ) << "NLNoatun::update() - found "<< appname << " - " + << m_track << endl; + + } + else + kdDebug( 14307 ) << "NLNoatun::update() - noatun not found" << endl; +} + +QCString NLNoatun::find() const +{ + QCString app = "noatun"; + if ( !m_client->isApplicationRegistered( app ) ) + { + // looking for a registered app prefixed with 'app' + QCStringList allApps = m_client->registeredApplications(); + QCStringList::iterator it; + for ( it = allApps.begin(); it != allApps.end(); it++ ) + { + //kdDebug( 14307 ) << ( *it ) << endl; + if ( ( *it ).left( 6 ) == app ) + { + app = ( *it ); + break; + } + } + // not found, set app to "" + if ( it == allApps.end() ) + app = ""; + } + return app; +} + +QString NLNoatun::currentProperty( QCString appname, QString property ) const +{ + QByteArray data, replyData; + QCString replyType; + QDataStream arg( data, IO_WriteOnly ); + QString result = ""; + arg << property; + if ( !m_client->call( appname, "Noatun", + "currentProperty(QString)", data, replyType, replyData ) ) + { + kdDebug( 14307 ) << "NLNoatun::currentProperty() DCOP error on " + << appname << endl; + } + else + { + QDataStream reply( replyData, IO_ReadOnly ); + if ( replyType == "QString" ) + { + reply >> result; + } + } + return result; +} +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/nowlistening/nlnoatun.h b/kopete/plugins/nowlistening/nlnoatun.h new file mode 100644 index 00000000..88441754 --- /dev/null +++ b/kopete/plugins/nowlistening/nlnoatun.h @@ -0,0 +1,41 @@ +/* + nlnoatun.h + + Kopete Now Listening To plugin + + Copyright (c) 2002,2003,2004 by Will Stephenson + + Kopete (c) 2002,2003,2004 by the Kopete developers + + Purpose: + This class abstracts the interface to Noatun by + implementing NLMediaPlayer + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef NLNOATUN_H +#define NLNOATUN_H + +#include + +class NLNoatun : public NLMediaPlayer +{ + public: + NLNoatun( DCOPClient *client ); + virtual void update(); + protected: + QCString find() const; + QString currentProperty( QCString appname, QString property ) const; + DCOPClient *m_client; +}; + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/nowlistening/nlxmms.cpp b/kopete/plugins/nowlistening/nlxmms.cpp new file mode 100644 index 00000000..f0a9f47a --- /dev/null +++ b/kopete/plugins/nowlistening/nlxmms.cpp @@ -0,0 +1,73 @@ +/* + nlxmms.cpp + + Kopete Now Listening To plugin + + Copyright (c) 2002 by Will Stephenson + + Kopete (c) 2002 by the Kopete developers + + Purpose: + This class abstracts the interface to the X Multimedia System (xmms) by + implementing NLMediaPlayer + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#include "config.h" + +#ifdef HAVE_XMMS + +#include +#include // need to fix Makefile.am for this? +#include "nlmediaplayer.h" +#include "nlxmms.h" + +NLXmms::NLXmms() : NLMediaPlayer() +{ + m_name = "Xmms"; +} + + +void NLXmms::update() +{ + //look for running xmms + if ( xmms_remote_get_version( 0 ) ) + { + QString newTrack; + // see if it's playing + if ( xmms_remote_is_playing( 0 ) && !xmms_remote_is_paused( 0 ) ) + { + m_playing = true; + + // get the artist and album title + // get the song title + newTrack = xmms_remote_get_playlist_title( 0, xmms_remote_get_playlist_pos( 0 ) ); + //kdDebug( 14307 ) << "NLXmms::update() - track is: " << m_track << endl; + m_artist = newTrack.section( " - ", 0, 0 ); + newTrack = newTrack.section( " - ", -1, -1 ); + } + else + m_playing = false; + // check if it's a new song + if ( newTrack != m_track ) + { + m_newTrack = true; + m_track = newTrack; + } + else + m_newTrack = false; + kdDebug( 14307 ) << k_funcinfo << " - found xmms - " << m_track << endl; + } + else + kdDebug( 14307 ) << k_funcinfo << " - xmms not found" << endl; +} + +#endif +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/nowlistening/nlxmms.h b/kopete/plugins/nowlistening/nlxmms.h new file mode 100644 index 00000000..14c74ea8 --- /dev/null +++ b/kopete/plugins/nowlistening/nlxmms.h @@ -0,0 +1,41 @@ +/* + nlxmms.h + + Kopete Now Listening To plugin + + Copyright (c) 2002 by Will Stephenson + + Kopete (c) 2002 by the Kopete developers + + Purpose: + This class abstracts the interface to the X Multimedia System (xmms) by + implementing NLMediaPlayer + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#include "config.h" + +#ifndef NLXMMS_H +#define NLXMMS_H + +#ifdef HAVE_XMMS + +class NLXmms : public NLMediaPlayer +{ + public: + NLXmms(); + virtual void update(); +}; + +#endif + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/nowlistening/nowlisteningchatui.rc b/kopete/plugins/nowlistening/nowlisteningchatui.rc new file mode 100644 index 00000000..9ab501f7 --- /dev/null +++ b/kopete/plugins/nowlistening/nowlisteningchatui.rc @@ -0,0 +1,9 @@ + + + + + &Tools + + + + diff --git a/kopete/plugins/nowlistening/nowlisteningconfig.kcfg b/kopete/plugins/nowlistening/nowlisteningconfig.kcfg new file mode 100644 index 00000000..8233b737 --- /dev/null +++ b/kopete/plugins/nowlistening/nowlisteningconfig.kcfg @@ -0,0 +1,54 @@ + + + + + + + + + i18n("Now listening to:") + + + + + i18n("%track( by %artist)( on %album)") + + + + + i18n(", and ") + + + + + true + + + + + false + + + + + false + + + + + false + + + + + false + + + + + + + diff --git a/kopete/plugins/nowlistening/nowlisteningconfig.kcfgc b/kopete/plugins/nowlistening/nowlisteningconfig.kcfgc new file mode 100644 index 00000000..87c53b77 --- /dev/null +++ b/kopete/plugins/nowlistening/nowlisteningconfig.kcfgc @@ -0,0 +1,8 @@ +# Code generation options for kconfig_compiler +File=nowlisteningconfig.kcfg +ClassName=NowListeningConfig +Singleton=true +Mutators=true +MemberVariables=private +GlobalEnums=true +IncludeFiles=klocale.h diff --git a/kopete/plugins/nowlistening/nowlisteningguiclient.cpp b/kopete/plugins/nowlistening/nowlisteningguiclient.cpp new file mode 100644 index 00000000..8e7b1908 --- /dev/null +++ b/kopete/plugins/nowlistening/nowlisteningguiclient.cpp @@ -0,0 +1,79 @@ +/* + nowlisteningguiclient.cpp + + Kopete Now Listening To plugin + + Copyright (c) 2005 by Tommi Rantala + Copyright (c) 2002,2003,2004 by Will Stephenson + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "nowlisteningguiclient.h" +#include "nowlisteningplugin.h" + +#include "kopetechatsessionmanager.h" +#include "kopeteview.h" + +#include +#include +#include +#include + +NowListeningGUIClient::NowListeningGUIClient( Kopete::ChatSession *parent, NowListeningPlugin *plugin ) + : QObject(parent) , KXMLGUIClient(parent) +{ + connect(plugin, SIGNAL(readyForUnload()), SLOT(slotPluginUnloaded())); + m_msgManager = parent; + m_action = new KAction( i18n( "Send Media Info" ), 0, this, + SLOT( slotAdvertToCurrentChat() ), actionCollection(), "actionSendAdvert" ); + setXMLFile("nowlisteningchatui.rc"); +} + +void NowListeningGUIClient::slotAdvertToCurrentChat() +{ + kdDebug( 14307 ) << k_funcinfo << endl; + + // Sanity check - don't crash if the plugin is unloaded and we get called. + if (!NowListeningPlugin::plugin()) + return; + + QString message = NowListeningPlugin::plugin()->mediaPlayerAdvert(); + + // We warn in a mode appropriate to the mode the user invoked the + // plugin - GUI on menu action, in message if they typed '/media' + if ( message.isEmpty() ) + { + QWidget * origin = 0L; + if ( m_msgManager && m_msgManager->view() ) + origin = m_msgManager->view()->mainWidget(); + KMessageBox::queuedMessageBox( origin, KMessageBox::Sorry, + i18n( "None of the supported media players (KsCD, JuK, amaroK, Noatun or Kaffeine) are playing anything." ), + i18n( "Nothing to Send" ) ); + } + else + { + //advertise to a single chat + if ( m_msgManager ) + NowListeningPlugin::plugin()->advertiseToChat( m_msgManager, message ); + } +} + +// The plugin itself is being unloaded - so remove the GUI entry. +void NowListeningGUIClient::slotPluginUnloaded() +{ + m_action->unplugAll(); +} + +#include "nowlisteningguiclient.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/nowlistening/nowlisteningguiclient.h b/kopete/plugins/nowlistening/nowlisteningguiclient.h new file mode 100644 index 00000000..9f89d351 --- /dev/null +++ b/kopete/plugins/nowlistening/nowlisteningguiclient.h @@ -0,0 +1,53 @@ +/* + nowlisteningguiclient.h + + Kopete Now Listening To plugin + + Copyright (c) 2005 by Tommi Rantala + Copyright (c) 2002,2003,2004 by Will Stephenson + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef NOWLISTENINGGUICLIENT_H +#define NOWLISTENINGGUICLIENT_H + +#include +#include + +class KAction; +class NowListeningPlugin; + +namespace Kopete { + class ChatSession; +} + +class NowListeningGUIClient : public QObject, public KXMLGUIClient +{ + Q_OBJECT + +public: + NowListeningGUIClient( Kopete::ChatSession* parent, NowListeningPlugin* plugin ); + virtual ~NowListeningGUIClient() {} + +protected slots: + void slotAdvertToCurrentChat(); + void slotPluginUnloaded(); + +private: + Kopete::ChatSession* m_msgManager; + KAction* m_action; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/nowlistening/nowlisteningplugin.cpp b/kopete/plugins/nowlistening/nowlisteningplugin.cpp new file mode 100644 index 00000000..e28316be --- /dev/null +++ b/kopete/plugins/nowlistening/nowlisteningplugin.cpp @@ -0,0 +1,554 @@ +/* + nowlisteningplugin.cpp + + Kopete Now Listening To plugin + + Copyright (c) 2002,2003,2004 by Will Stephenson + Copyright (c) 2005-2006 by Michaël Larouche + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "config.h" +#include "kopetechatsessionmanager.h" +#include "kopetemetacontact.h" +#include "kopetecontact.h" +#include "kopetecommandhandler.h" +#include "kopeteaccount.h" +#include "kopeteprotocol.h" +#include "kopeteaccountmanager.h" + +#include "nowlisteningconfig.h" +#include "nowlisteningplugin.h" +#include "nlmediaplayer.h" +#include "nlkscd.h" +#include "nlnoatun.h" +#include "nljuk.h" +#include "nlamarok.h" +#include "nlkaffeine.h" +#include "nowlisteningguiclient.h" + +#if defined Q_WS_X11 && !defined K_WS_QTONLY && defined HAVE_XMMS +#include "nlxmms.h" +#endif + +class NowListeningPlugin::Private +{ +public: + Private() : m_currentMediaPlayer(0L), m_client(0L), m_currentChatSession(0L), m_currentMetaContact(0L), + advertTimer(0L) + {} + + // abstracted media player interfaces + QPtrList m_mediaPlayerList; + NLMediaPlayer *m_currentMediaPlayer; + + // Needed for DCOP interprocess communication + DCOPClient *m_client; + Kopete::ChatSession *m_currentChatSession; + Kopete::MetaContact *m_currentMetaContact; + + // Used when using automatic advertising to know who has already gotten + // the music information + QStringList m_musicSentTo; + + // Used when advertising to status message. + QTimer *advertTimer; +}; + +typedef KGenericFactory NowListeningPluginFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_nowlistening, NowListeningPluginFactory( "kopete_nowlistening" ) ) + +NowListeningPlugin::NowListeningPlugin( QObject *parent, const char* name, const QStringList& /*args*/ ) +: Kopete::Plugin( NowListeningPluginFactory::instance(), parent, name ) +{ + if ( pluginStatic_ ) + kdDebug( 14307 )<<"####"<<"Now Listening already initialized"< sessions = Kopete::ChatSessionManager::self()->sessions(); + for (QValueListIterator it= sessions.begin(); it!=sessions.end() ; ++it) + slotNewKMM( *it ); + + // get a pointer to the dcop client + d->m_client = kapp->dcopClient(); //new DCOPClient(); + + // set up known media players + d->m_mediaPlayerList.setAutoDelete( true ); + d->m_mediaPlayerList.append( new NLKscd( d->m_client ) ); + d->m_mediaPlayerList.append( new NLNoatun( d->m_client ) ); + d->m_mediaPlayerList.append( new NLJuk( d->m_client ) ); + d->m_mediaPlayerList.append( new NLamaroK( d->m_client ) ); + d->m_mediaPlayerList.append( new NLKaffeine( d->m_client ) ); + +#if defined Q_WS_X11 && !defined K_WS_QTONLY && HAVE_XMMS + d->m_mediaPlayerList.append( new NLXmms() ); +#endif + + // User has selected a specific mediaPlayer so update the currentMediaPlayer pointer. + if( NowListeningConfig::self()->useSpecifiedMediaPlayer() ) + { + updateCurrentMediaPlayer(); + } + + // watch for '/media' getting typed + Kopete::CommandHandler::commandHandler()->registerCommand( + this, + "media", + SLOT( slotMediaCommand( const QString &, Kopete::ChatSession * ) ), + i18n("USAGE: /media - Displays information on current song"), + 0 + ); + + connect ( this , SIGNAL( settingsChanged() ) , this , SLOT( slotSettingsChanged() ) ); + + // Advert the accounts with the current listened track. + d->advertTimer = new QTimer(this); + connect(d->advertTimer, SIGNAL( timeout() ), this, SLOT( slotAdvertCurrentMusic() ) ); + d->advertTimer->start(5000); // Update every 5 seconds +} + +NowListeningPlugin::~NowListeningPlugin() +{ + //kdDebug( 14307 ) << k_funcinfo << endl; + + delete d; + + pluginStatic_ = 0L; +} + +void NowListeningPlugin::slotNewKMM(Kopete::ChatSession *KMM) +{ + new NowListeningGUIClient( KMM, this ); +} + +NowListeningPlugin* NowListeningPlugin::plugin() +{ + return pluginStatic_ ; +} + +void NowListeningPlugin::slotMediaCommand( const QString &args, Kopete::ChatSession *theChat ) +{ + QString advert = mediaPlayerAdvert(); + if ( advert.isEmpty() ) + { + // Catch no players/no track playing message case: + // Since we can't stop a message send in a plugin, add some message text to + // prevent us sending an empty message + advert = i18n("Message from Kopete user to another user; used when sending media information even though there are no songs playing or no media players running", "Now Listening for Kopete - it would tell you what I am listening to, if I was listening to something on a supported media player."); + } + + Kopete::Message msg( theChat->myself(), + theChat->members(), + advert + " " + args, + Kopete::Message::Outbound, + Kopete::Message::RichText + ); + + theChat->sendMessage( msg ); +} + +void NowListeningPlugin::slotOutgoingMessage(Kopete::Message& msg) +{ + // Only do stuff if autoadvertising is on + if(!NowListeningConfig::self()->chatAdvertising()) + return; + + QString originalBody = msg.plainBody(); + + // If it is a /media message, don't process it + if(originalBody.startsWith(NowListeningConfig::self()->header())) + return; + + // What will be sent + QString newBody; + + // Getting the list of contacts the message will be sent to to determine if at least + // one of them has never gotten the current music information. + Kopete::ContactPtrList dest = msg.to(); + bool mustSendAnyway = false; + for( Kopete::Contact *c = dest.first() ; c ; c = dest.next() ) + { + const QString& cId = c->contactId(); + if( 0 == d->m_musicSentTo.contains( cId ) ) + { + mustSendAnyway = true; + + // The contact will get the music information so we put it in the list. + d->m_musicSentTo.push_back( cId ); + } + } + + bool newTrack = newTrackPlaying(); + + // We must send the music information if someone has never gotten it or the track(s) + // has changed since it was last sent. + if ( mustSendAnyway || newTrack ) + { + QString advert = mediaPlayerAdvert(false); // false since newTrackPlaying() did the update + if( !advert.isEmpty() ) + newBody = originalBody + "
    " + advert; + + // If we send because the information has changed since it was last sent, we must + // rebuild the list of contacts the latest information was sent to. + if( newTrack ) + { + d->m_musicSentTo.clear(); + for( Kopete::Contact *c = dest.first() ; c ; c = dest.next() ) + { + d->m_musicSentTo.push_back( c->contactId() ); + } + } + } + + // If the body has been modified, change the message + if( !newBody.isEmpty() ) + { + msg.setBody( newBody, Kopete::Message::RichText ); + } +} + +void NowListeningPlugin::slotAdvertCurrentMusic() +{ + // Do anything when statusAdvertising is off. + if( !NowListeningConfig::self()->statusAdvertising() && !NowListeningConfig::self()->appendStatusAdvertising() ) + return; + + // This slot is called every 5 seconds, so we check if we have a new track playing. + if( newTrackPlaying() ) + { + QString advert; + + QPtrList accountsList = Kopete::AccountManager::self()->accounts(); + for( Kopete::Account* a = accountsList.first(); a; a = accountsList.next() ) + { + /* + NOTE: + MSN status message(personal message) use a special tag to advert the current music playing. + So, we don't send the all formatted string, send a special string seperated by ";". + + Also, do not use MSN hack in appending mode. + */ + if( a->protocol()->pluginId() == "MSNProtocol" && !NowListeningConfig::self()->appendStatusAdvertising() ) + { + QString track, artist, album, mediaList; + bool isPlaying=false; + + if( NowListeningConfig::self()->useSpecifiedMediaPlayer() && d->m_currentMediaPlayer ) + { + if( d->m_currentMediaPlayer->playing() ) + { + track = d->m_currentMediaPlayer->track(); + artist = d->m_currentMediaPlayer->artist(); + album = d->m_currentMediaPlayer->album(); + mediaList = track + ";" + artist + ";" + album; + isPlaying = true; + } + } + else + { + for ( NLMediaPlayer* i = d->m_mediaPlayerList.first(); i; i = d->m_mediaPlayerList.next() ) + { + if( i->playing() ) + { + track = i->track(); + artist = i->artist(); + album = i->album(); + mediaList = track + ";" + artist + ";" + album; + isPlaying = true; + } + } + } + + // KDE4 TODO: Use the new status message framework, and remove this "hack". + if( isPlaying ) + { + advert = QString("[Music]%1").arg(mediaList); + } + + } + else + { + if( NowListeningConfig::self()->appendStatusAdvertising() ) + { + // Check for the now listening message in parenthesis, + // include the header to not override other messages in parenthesis. + QRegExp statusSong( QString(" \\(%1.*\\)$").arg( NowListeningConfig::header()) ); + + // HACK: Don't keep appending the now listened song. Replace it in the status message. + advert = a->myself()->property( Kopete::Global::Properties::self()->awayMessage() ).value().toString(); + // Remove the braces when they are no listened song. + QString mediaAdvert = mediaPlayerAdvert(false); + if(!mediaAdvert.isEmpty()) + { + if(statusSong.search(advert) != -1) + { + advert = advert.replace(statusSong, QString(" (%1)").arg(mediaPlayerAdvert(false)) ); + } + else + { + advert += QString(" (%1)").arg( mediaPlayerAdvert(false) ); + } + } + else + { + advert = advert.replace(statusSong, ""); + } + } + else + { + advert = mediaPlayerAdvert(false); // newTrackPlaying has done the update. + } + } + + a->setOnlineStatus(a->myself()->onlineStatus(), advert); + } + } +} + +QString NowListeningPlugin::mediaPlayerAdvert(bool update) +{ + // generate message for all players + QString message; + + if( NowListeningConfig::self()->useSpecifiedMediaPlayer() && d->m_currentMediaPlayer != 0L ) + { + buildTrackMessage(message, d->m_currentMediaPlayer, update); + } + else + { + for ( NLMediaPlayer* i = d->m_mediaPlayerList.first(); i; i = d->m_mediaPlayerList.next() ) + { + buildTrackMessage(message, i, update); + } + } + + kdDebug( 14307 ) << k_funcinfo << message << endl; + + return message; +} + +void NowListeningPlugin::buildTrackMessage(QString &message, NLMediaPlayer *player, bool update) +{ + QString perTrack = NowListeningConfig::self()->perTrack(); + + if(update) + player->update(); + if ( player->playing() ) + { + kdDebug( 14307 ) << k_funcinfo << player->name() << " is playing" << endl; + if ( message.isEmpty() ) + message = NowListeningConfig::self()->header(); + + if ( message != NowListeningConfig::self()->header() ) // > 1 track playing! + message = message + NowListeningConfig::self()->conjunction(); + message = message + substDepthFirst( player, perTrack, false ); + } +} + +bool NowListeningPlugin::newTrackPlaying(void) const +{ + if( NowListeningConfig::self()->useSpecifiedMediaPlayer() && d->m_currentMediaPlayer != 0L ) + { + d->m_currentMediaPlayer->update(); + if( d->m_currentMediaPlayer->newTrack() ) + return true; + } + else + { + for ( NLMediaPlayer* i = d->m_mediaPlayerList.first(); i; i = d->m_mediaPlayerList.next() ) + { + i->update(); + if( i->newTrack() ) + return true; + } + } + return false; +} + +QString NowListeningPlugin::substDepthFirst( NLMediaPlayer *player, + QString in, bool inBrackets ) const +{ + QString track = player->track(); + QString artist = player->artist(); + QString album = player->album(); + QString playerName = player->name(); + + for ( unsigned int i = 0; i < in.length(); i++ ) + { + QChar c = in.at( i ); + //kdDebug(14307) << "Now working on:" << in << " char is: " << c << endl; + if ( c == '(' ) + { + // find matching bracket + int depth = 0; + //kdDebug(14307) << "Looking for ')'" << endl; + for ( unsigned int j = i + 1; j < in.length(); j++ ) + { + QChar d = in.at( j ); + //kdDebug(14307) << "Got " << d << endl; + if ( d == '(' ) + depth++; + if ( d == ')' ) + { + // have we found the match? + if ( depth == 0 ) + { + // recursively replace contents of matching () + QString substitution = substDepthFirst( player, + in.mid( i + 1, j - i - 1), true ) ; + in.replace ( i, j - i + 1, substitution ); + // perform substitution and return the result + i = i + substitution.length() - 1; + break; + } + else + depth--; + } + } + } + } + // no () found, perform substitution! + // get each string (to) to substitute for (from) + bool done = false; + if ( in.contains ( "%track" ) ) + { + if ( track.isEmpty() ) + track = i18n("Unknown track"); + + in.replace( "%track", track ); + done = true; + } + + if ( in.contains ( "%artist" ) && !artist.isEmpty() ) + { + if ( artist.isEmpty() ) + artist = i18n("Unknown artist"); + in.replace( "%artist", artist ); + done = true; + } + if ( in.contains ( "%album" ) && !album.isEmpty() ) + { + if ( album.isEmpty() ) + album = i18n("Unknown album"); + in.replace( "%album", album ); + done = true; + } + if ( in.contains ( "%player" ) && !playerName.isEmpty() ) + { + if ( playerName.isEmpty() ) + playerName = i18n("Unknown player"); + in.replace( "%player", playerName ); + done = true; + } + // make whether we return anything dependent on whether we + // were in brackets and if we were, if a substitution was made. + if ( inBrackets && !done ) + return ""; + + return in; +} + +void NowListeningPlugin::advertiseToChat( Kopete::ChatSession *theChat, QString message ) +{ + Kopete::ContactPtrList pl = theChat->members(); + + // get on with it + kdDebug(14307) << k_funcinfo << + ( pl.isEmpty() ? "has no " : "has " ) << "interested recipients: " << endl; +/* for ( pl.first(); pl.current(); pl.next() ) + kdDebug(14307) << "NowListeningPlugin::advertiseNewTracks() " << pl.current()->displayName() << endl; */ + // if no-one in this KMM wants to be advertised to, don't send + // any message + if ( pl.isEmpty() ) + return; + Kopete::Message msg( theChat->myself(), + pl, + message, + Kopete::Message::Outbound, + Kopete::Message::RichText ); + theChat->sendMessage( msg ); +} + +void NowListeningPlugin::updateCurrentMediaPlayer() +{ + kdDebug(14307) << k_funcinfo << "Update current media player (single mode)" << endl; + + d->m_currentMediaPlayer = d->m_mediaPlayerList.at( NowListeningConfig::self()->selectedMediaPlayer() ); +} + +void NowListeningPlugin::slotSettingsChanged() +{ + // Force reading config + NowListeningConfig::self()->readConfig(); + + // Update the currentMediaPlayer, because config has changed. + if( NowListeningConfig::useSpecifiedMediaPlayer() ) + updateCurrentMediaPlayer(); + + disconnect(Kopete::ChatSessionManager::self(), + SIGNAL(aboutToSend(Kopete::Message&)), + this, + SLOT(slotOutgoingMessage(Kopete::Message&))); + + d->advertTimer->stop(); + disconnect(d->advertTimer, SIGNAL(timeout()), this, SLOT(slotAdvertCurrentMusic())); + + if( NowListeningConfig::self()->chatAdvertising() ) + { + kdDebug(14307) << k_funcinfo << "Now using chat window advertising." << endl; + + connect(Kopete::ChatSessionManager::self(), + SIGNAL(aboutToSend(Kopete::Message&)), + this, + SLOT(slotOutgoingMessage(Kopete::Message&))); + } + else if( NowListeningConfig::self()->statusAdvertising() || NowListeningConfig::self()->appendStatusAdvertising() ) + { + kdDebug(14307) << k_funcinfo << "Now using status message advertising." << endl; + + connect(d->advertTimer, SIGNAL(timeout()), this, SLOT(slotAdvertCurrentMusic())); + d->advertTimer->start(5000); + } +} + +NowListeningPlugin* NowListeningPlugin::pluginStatic_ = 0L; + +#include "nowlisteningplugin.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/nowlistening/nowlisteningplugin.h b/kopete/plugins/nowlistening/nowlisteningplugin.h new file mode 100644 index 00000000..7a608fd2 --- /dev/null +++ b/kopete/plugins/nowlistening/nowlisteningplugin.h @@ -0,0 +1,111 @@ +/* + nowlisteningplugin.h + + Kopete Now Listening To plugin + + Copyright (c) 2002,2003,2004 by Will Stephenson + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef NOWLISTENINGPLUGIN_H +#define NOWLISTENINGPLUGIN_H + + +#include "kopeteplugin.h" +#include + +namespace Kopete { class ChatSession; class Message; } + +class NLMediaPlayer; +class QStringList; + +/** + * @author Will Stephenson + * @author Michaël Larouche + */ +class NowListeningPlugin : public Kopete::Plugin +{ + Q_OBJECT + +friend class NowListeningGUIClient; + + public: + NowListeningPlugin( QObject *parent, const char *name, const QStringList &args ); + virtual ~NowListeningPlugin(); + static NowListeningPlugin* plugin(); + + public slots: + void slotMediaCommand( const QString &, Kopete::ChatSession *theChat ); + void slotOutgoingMessage(Kopete::Message&); + void slotAdvertCurrentMusic(); + + protected: + /** + * Constructs a string containing the track information. + * @param update Whether the players must update their data. It can be + * useful to set it to false if one already has called + * update somewhere else, for instance in newTrackPlaying(). + */ + QString mediaPlayerAdvert(bool update = true); + /** + * @internal Build the message for @ref mediaPlayerAdvert + * @param message Reference to the messsage, because return QString cause data loss. + * @param player Pointer to the current Media Player. + * Used to get the information about the current track playing. + * @param update Whether the players must update their data. It can be + * useful to set it to false if one already has called + * update somewhere else, for instance in newTrackPlaying(). + */ + void buildTrackMessage(QString &message, NLMediaPlayer *player, bool update); + /** + * @return true if one of the players has changed track since the last message. + */ + bool newTrackPlaying(void) const; + /** + * Creates the string for a single player + * @p player - the media player we're using + * @p in - the source format string + * @p bool - is this call within a set of brackets for conditional expansion? + */ + QString substDepthFirst( NLMediaPlayer *player, QString in, bool inBrackets) const; + /** + * Sends a message to a single chat + */ + void advertiseToChat( Kopete::ChatSession* theChat, QString message ); + /** + * Update the currentMedia pointer on config change. + */ + void updateCurrentMediaPlayer(); + + protected slots: + /** + * Reacts to a new chat starting and adds actions to its GUI + */ + void slotNewKMM( Kopete::ChatSession* ); + + /** + * Reacts to the plugin's settings changed signal, originating from the KCModule dispatcher + */ + void slotSettingsChanged(); + + private: + class Private; + Private *d; + + static NowListeningPlugin* pluginStatic_; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/nowlistening/nowlisteningpreferences.cpp b/kopete/plugins/nowlistening/nowlisteningpreferences.cpp new file mode 100644 index 00000000..179ce3a5 --- /dev/null +++ b/kopete/plugins/nowlistening/nowlisteningpreferences.cpp @@ -0,0 +1,95 @@ +/* + nowlisteningpreferences.cpp + + Kopete Now Listening To plugin + + Copyright (c) 2002,2003,2004 by Will Stephenson + Copyright (c) 2005 by Michaël Larouche + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#include +#include +#include +#include + +#include +#include +#include + +#include "config.h" // for HAVE_XMMS +#include "nowlisteningprefs.h" +#include "nowlisteningconfig.h" +#include "nowlisteningpreferences.h" +#include "nowlisteningpreferences.moc" + +typedef KGenericFactory NowListeningPreferencesFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_nowlistening, NowListeningPreferencesFactory( "kcm_kopete_nowlistening" ) ) + + +NowListeningPreferences::NowListeningPreferences(QWidget *parent, const char* /*name*/, const QStringList &args) + : KCModule( NowListeningPreferencesFactory::instance(), parent, args ) +{ + ( new QVBoxLayout( this ) )->setAutoAdd( true ); + preferencesDialog = new NowListeningPrefsUI( this ); + + addConfig( NowListeningConfig::self(), preferencesDialog ); + + // Fill the media player listbox. + preferencesDialog->kcfg_SelectedMediaPlayer->insertItem(QString::fromUtf8("Kscd")); + preferencesDialog->kcfg_SelectedMediaPlayer->insertItem(QString::fromUtf8("Noatun")); + preferencesDialog->kcfg_SelectedMediaPlayer->insertItem(QString::fromUtf8("Juk")); + preferencesDialog->kcfg_SelectedMediaPlayer->insertItem(QString::fromUtf8("amaroK")); + preferencesDialog->kcfg_SelectedMediaPlayer->insertItem(QString::fromUtf8("Kaffeine")); +#if defined Q_WS_X11 && !defined K_WS_QTONLY && defined HAVE_XMMS + preferencesDialog->kcfg_SelectedMediaPlayer->insertItem(QString::fromUtf8("XMMS")); +#endif + load(); +} + +NowListeningPreferences::~NowListeningPreferences( ) +{ + delete preferencesDialog; +} + +void NowListeningPreferences::save() +{ + KCModule::save(); +} + +void NowListeningPreferences::load() +{ + KCModule::load(); +} + +void NowListeningPreferences::slotSettingsChanged() +{ + emit changed( true ); +} + +void NowListeningPreferences::defaults() +{ + /*preferencesDialog->m_header->setText( i18n("Now Listening To: ")); + preferencesDialog->m_perTrack->setText(i18n("%track( by %artist)( on %album)")); + preferencesDialog->m_conjunction->setText( i18n(", and ")); + preferencesDialog->m_autoAdvertising->setChecked( false );*/ +} + +/* +* Local variables: +* c-indentation-style: k&r +* c-basic-offset: 8 +* indent-tabs-mode: t +* End: +*/ +// vim: set noet ts=4 sts=4 sw=4: +// diff --git a/kopete/plugins/nowlistening/nowlisteningpreferences.h b/kopete/plugins/nowlistening/nowlisteningpreferences.h new file mode 100644 index 00000000..14d9ceea --- /dev/null +++ b/kopete/plugins/nowlistening/nowlisteningpreferences.h @@ -0,0 +1,59 @@ +/* + nowlisteningpreferences.h + + Kopete Now Listening To plugin + + Copyright (c) 2002,2003,2004 by Will Stephenson + + Kopete (c) 2002,2003,2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef NOWLISTENINGPREFERENCES_H +#define NOWLISTENINGPREFERENCES_H + +#include + +class NowListeningPrefsUI; +class NowListeningConfig; + +/** + *@author Will Stephenson + */ + +class NowListeningPreferences : public KCModule +{ +Q_OBJECT +public: + NowListeningPreferences(QWidget *parent = 0, const char *name = 0, const QStringList &args = QStringList()); + virtual ~NowListeningPreferences(); + virtual void save(); + virtual void load(); + virtual void defaults(); + +private slots: + void slotSettingsChanged(); + +private: + NowListeningPrefsUI *preferencesDialog; + +}; + +#endif +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/nowlistening/nowlisteningprefs.ui b/kopete/plugins/nowlistening/nowlisteningprefs.ui new file mode 100644 index 00000000..08dd72b9 --- /dev/null +++ b/kopete/plugins/nowlistening/nowlisteningprefs.ui @@ -0,0 +1,376 @@ + +NowListeningPrefsUI + + + NowListeningPrefsUI + + + + 0 + 0 + 391 + 370 + + + + Now Listening + + + + unnamed + + + 11 + + + 6 + + + + advertiseNewMediaToBuddiesLabel + + + <b>Share Your Musical Taste</b> + + + AlignVCenter + + + + + advertiseNewMediaToBuddiesHLine + + + HLine + + + Sunken + + + + + tabWidget2 + + + + TabPage + + + Messa&ge + + + + unnamed + + + + layout4 + + + + unnamed + + + + useThisMessageLabel + + + Use this message when advertising: + + + + + helperLabel + + + %track, %artist, %album, %player will be substituted if known. +Expressions in brackets depend on a substitution being made. + + + WordBreak|AlignVCenter + + + + + layout2 + + + + unnamed + + + + m_headerLabel + + + Start with: + + + AlignVCenter|AlignLeft + + + m_header + + + + + kcfg_Header + + + Now Listening To: + + + + + m_perTrackLabel + + + For each track: + + + AlignVCenter|AlignLeft + + + m_perTrack + + + + + kcfg_PerTrack + + + %track (by %artist)(on %album) + + + + + m_conjunctionLabel + + + Conjunction (if >1 track): + + + AlignVCenter|AlignLeft + + + m_conjunction + + + + + kcfg_Conjunction + + + , and + + + + + + + + + spacer4 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + + + tab + + + A&dvertising Mode + + + + unnamed + + + + buttonGroup2 + + + NoFrame + + + + + + + unnamed + + + + kcfg_ExplicitAdvertising + + + Explicit &via "Tools->Send Media Info", +or by typing "/media" in the chat +window edit area. + + + + + kcfg_ChatAdvertising + + + &Show in chat window (automatic) + + + + + kcfg_StatusAdvertising + + + Show &the music you are listening to +in place of your status message. + + + + + kcfg_AppendStatusAdvertising + + + Appe&nd to your status message + + + + + + + spacer3 + + + Vertical + + + Expanding + + + + 20 + 80 + + + + + + + + tab + + + Media Pla&yer + + + + unnamed + + + + layout2_2 + + + + unnamed + + + + kcfg_UseSpecifiedMediaPlayer + + + Use &specified media player + + + + + kcfg_SelectedMediaPlayer + + + false + + + + 5 + 5 + 0 + 0 + + + + FixedNumber + + + Variable + + + false + + + + + + + spacer5 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + + + + spacer6 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + + + kcfg_UseSpecifiedMediaPlayer + toggled(bool) + kcfg_SelectedMediaPlayer + setEnabled(bool) + + + + + klistbox.h + + diff --git a/kopete/plugins/nowlistening/nowlisteningui.rc b/kopete/plugins/nowlistening/nowlisteningui.rc new file mode 100644 index 00000000..149b3a69 --- /dev/null +++ b/kopete/plugins/nowlistening/nowlisteningui.rc @@ -0,0 +1,6 @@ + + + + + + diff --git a/kopete/plugins/smpppdcs/Changelog.smpppdcs b/kopete/plugins/smpppdcs/Changelog.smpppdcs new file mode 100644 index 00000000..80854a86 --- /dev/null +++ b/kopete/plugins/smpppdcs/Changelog.smpppdcs @@ -0,0 +1,67 @@ +Changelog/README SMPPPDCS +========================= + +The smpppdcs-plugin for Kopete provides a internet connection +detection based on SuSE's kinternet/smpppd or on the netstat +program. + +The smpppd is a controller to the internet interfaces. The plugin +is inquiring this interface frequently and checks if it reports a +connection to the internet and then activates all Kopete accounts. + +The netstat is checking if a default gateway is existing and then +activates all Kopete accounts, too. + +Changelog +========= + +0.79 (2006/01/25) +* using KConfigXT for configuration +* using dcopidl2cpp stub generated from kinternetiface.h (from kinternet package), + no more own implementation +* experimental implementation of the the KDED-NetworkStatus (not active, yet) +* significantly speeded up automatic detection of a SMPPPD +* BUGFIX: reloading the plugin in a already running Kopete will no more + result in an inactive plugin +* refactoring to allow easy implementation of new detection methods +* even more speed improvements + +0.75 (2006/01/01) +* use of KSocketStream instead of deprecated KExtendedSocket +* progressbar while searching for an smpppd on the local network +* automatically found smpppd server is resolved via DNS +* Fixed Bug 111369: better detection of a SMPPPD and no more freeze of Kopete + +0.74 (2005/12/27) +* minor bugfixes +* disable netstat in config if the binary cannot be found + +0.72 (2005/09/07) +* internal refactoring to provide online status + +0.7 (2004/11/20) + +* list to ignore accounts integrated. + Accounts can be excluded from the plugin connect/disconnect + mechanism +* connection detection enhanced: first kinternet is asked via + DCOP for a running connection, if this fails the smpppd is asked +* improved startup detection, compatible with recent CVS changes +* some API chages in the config module + +0.6 (2004/10/18) + +* adapting to KDE 3.3.1 + +0.4 (2004/10/05) + +* toggling between netstat and smpppdcs works without restart + of kopete + +0.3 (2004/10/03) + +* accounts get activated after they are loaded and initialized + +0.1 (2004/09/04) + +* first version of the plugin diff --git a/kopete/plugins/smpppdcs/Makefile.am b/kopete/plugins/smpppdcs/Makefile.am new file mode 100644 index 00000000..11173ac6 --- /dev/null +++ b/kopete/plugins/smpppdcs/Makefile.am @@ -0,0 +1,35 @@ +METASOURCES = AUTO + +SUBDIRS = icons libsmpppdclient unittest + +EXTRA_DIST = Changelog.smpppdcs + +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) -Ilibsmpppdclient + +kde_module_LTLIBRARIES = kopete_smpppdcs.la kcm_kopete_smpppdcs.la + +kopete_smpppdcs_la_SOURCES = kinternetiface.stub smpppdcsplugin.cpp \ + onlineinquiry.cpp smpppdcsiface.skel detectordcop.cpp detectorsmpppd.cpp \ + detectornetstat.cpp detectornetworkstatus.cpp smpppdcsconfig.kcfgc +kopete_smpppdcs_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) +kopete_smpppdcs_la_LIBADD = \ + libsmpppdclient/libsmpppdclient.la ../../libkopete/libkopete.la + +kcm_kopete_smpppdcs_la_SOURCES = smpppdcsprefs.ui smpppdcspreferences.cpp \ + smpppdsearcher.cpp smpppdcsprefsimpl.cpp smpppdlocationui.ui smpppdlocationwidget.cpp \ + smpppdcsconfig.kcfgc +kcm_kopete_smpppdcs_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_smpppdcs_la_LIBADD = libsmpppdclient/libsmpppdclient.la \ + ../../libkopete/libkopete.la $(LIB_KUTILS) + +service_DATA = kopete_smpppdcs.desktop +servicedir = $(kde_servicesdir) + +kcm_DATA = kopete_smpppdcs_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog + +kde_kcfg_DATA = smpppdcs.kcfg + +noinst_HEADERS = smpppdcsiface.h detectordcop.h detectorsmpppd.h \ + detectornetstat.h kinternetiface.h detectornetworkstatus.h \ + smpppdsearcher.h smpppdcsprefsimpl.h smpppdlocationwidget.h diff --git a/kopete/plugins/smpppdcs/detector.h b/kopete/plugins/smpppdcs/detector.h new file mode 100644 index 00000000..094de9e5 --- /dev/null +++ b/kopete/plugins/smpppdcs/detector.h @@ -0,0 +1,59 @@ +/* + detector.h + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef DETECTOR_H +#define DETECTOR_H + +class IConnector; + +/** + * @brief Detector interface to find out if there is a connection to the internet. + * + * Subclasses should implement the specific ways to check for an internet + * connection + * + * @author Heiko Schäfer + * + */ + +class Detector { + + Detector(const Detector&); + Detector& operator=(const Detector&); + +public: + /** + * @brief Creates an Detector instance. + * + * @param connector A connector to send feedback to the calling object + */ + Detector(IConnector * connector) : m_connector(connector) {} + + /** + * @brief Destroys an Detector instance. + * + */ + virtual ~Detector() {} + + virtual void checkStatus() const = 0; + + virtual void smpppdServerChange() {} + +protected: + IConnector * m_connector; +}; + +#endif diff --git a/kopete/plugins/smpppdcs/detectordcop.cpp b/kopete/plugins/smpppdcs/detectordcop.cpp new file mode 100644 index 00000000..2536674d --- /dev/null +++ b/kopete/plugins/smpppdcs/detectordcop.cpp @@ -0,0 +1,77 @@ +/* + detectordcop.cpp + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#include "kinternetiface_stub.h" + +#include "detectordcop.h" +#include "iconnector.h" + +QCString DetectorDCOP::m_kinternetApp = ""; + +DetectorDCOP::DetectorDCOP(IConnector * connector) + : Detector(connector) {} + +DetectorDCOP::~DetectorDCOP() {} + +/*! + \fn DetectorDCOP::getKInternetDCOP() + */ +QCString DetectorDCOP::getKInternetDCOP() const { + DCOPClient * client = kapp->dcopClient(); + if(m_kinternetApp.isEmpty() && client && client->isAttached()) { + // get all registered dcop apps and search for kinternet + QCStringList apps = client->registeredApplications(); + QCStringList::iterator iter; + for(iter = apps.begin(); iter != apps.end(); ++iter) { + if((*iter).left(9) == "kinternet") { + return *iter; + } + } + } + + return m_kinternetApp; +} + +/*! + \fn DetectorDCOP::getConnectionStatusDCOP() + */ +DetectorDCOP::KInternetDCOPState DetectorDCOP::getConnectionStatusDCOP() const { + kdDebug(14312) << k_funcinfo << "Start inquiring " << m_kinternetApp << " via DCOP" << endl; + + + KInternetIface_stub stub = KInternetIface_stub(kapp->dcopClient(), m_kinternetApp, "KInternetIface"); + + bool status = stub.isOnline(); + + if(stub.ok()) { + if(status) { + kdDebug(14312) << k_funcinfo << "isOnline() returned true" << endl; + return CONNECTED; + } else { + kdDebug(14312) << k_funcinfo << "isOnline() returned false" << endl; + return DISCONNECTED; + } + } else { + kdWarning(14312) << k_funcinfo << "DCOP call to " << m_kinternetApp << " failed!"; + } + + return ERROR; +} + diff --git a/kopete/plugins/smpppdcs/detectordcop.h b/kopete/plugins/smpppdcs/detectordcop.h new file mode 100644 index 00000000..5306998b --- /dev/null +++ b/kopete/plugins/smpppdcs/detectordcop.h @@ -0,0 +1,51 @@ +/* + detectordcop.h + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef DETECTORDCOP_H +#define DETECTORDCOP_H + +#include "detector.h" + +class IConnector; + +/** + @author Heiko Schäfer +*/ +class DetectorDCOP : public Detector { + + DetectorDCOP(const DetectorDCOP&); + DetectorDCOP& operator=(const DetectorDCOP&); + +public: + DetectorDCOP(IConnector * connector); + virtual ~DetectorDCOP(); + +protected: + + enum KInternetDCOPState { + CONNECTED, + DISCONNECTED, + ERROR + }; + + QCString getKInternetDCOP() const; + KInternetDCOPState getConnectionStatusDCOP() const; + +protected: + static QCString m_kinternetApp; +}; + +#endif diff --git a/kopete/plugins/smpppdcs/detectornetstat.cpp b/kopete/plugins/smpppdcs/detectornetstat.cpp new file mode 100644 index 00000000..60dff658 --- /dev/null +++ b/kopete/plugins/smpppdcs/detectornetstat.cpp @@ -0,0 +1,76 @@ +/* + detectornetstat.cpp + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include +#include + +#include "iconnector.h" +#include "detectornetstat.h" + +DetectorNetstat::DetectorNetstat(IConnector* connector) + : Detector(connector), m_buffer(QString::null), m_process(NULL) {} + +DetectorNetstat::~DetectorNetstat() { + delete m_process; +} + +void DetectorNetstat::checkStatus() const { + kdDebug(14312) << k_funcinfo << endl; + + if(m_process) { + kdWarning(14312) << k_funcinfo << "Previous netstat process is still running!" << endl + << "Not starting new netstat. Perhaps your system is under heavy load?" << endl; + + return; + } + + m_buffer = QString::null; + + // Use KProcess to run netstat -r. We'll then parse the output of + // netstat -r in slotProcessStdout() to see if it mentions the + // default gateway. If so, we're connected, if not, we're offline + m_process = new KProcess; + *m_process << "netstat" << "-r"; + + connect(m_process, SIGNAL(receivedStdout(KProcess *, char *, int)), this, SLOT(slotProcessStdout( KProcess *, char *, int))); + connect(m_process, SIGNAL(processExited(KProcess *)), this, SLOT(slotProcessExited(KProcess *))); + + if(!m_process->start(KProcess::NotifyOnExit, KProcess::Stdout)) { + kdWarning(14312) << k_funcinfo << "Unable to start netstat process!" << endl; + + delete m_process; + m_process = 0L; + } +} + +void DetectorNetstat::slotProcessStdout(KProcess *, char *buffer, int buflen) { + // Look for a default gateway + kdDebug(14312) << k_funcinfo << endl; + m_buffer += QString::fromLatin1(buffer, buflen); + kdDebug(14312) << m_buffer << endl; +} + +void DetectorNetstat::slotProcessExited(KProcess *process) { + kdDebug(14312) << k_funcinfo << m_buffer << endl; + if(process == m_process) { + m_connector->setConnectedStatus(m_buffer.contains("default")); + m_buffer = QString::null; + delete m_process; + m_process = 0L; + } +} + +#include "detectornetstat.moc" diff --git a/kopete/plugins/smpppdcs/detectornetstat.h b/kopete/plugins/smpppdcs/detectornetstat.h new file mode 100644 index 00000000..d51a6d97 --- /dev/null +++ b/kopete/plugins/smpppdcs/detectornetstat.h @@ -0,0 +1,56 @@ +/* + detectornetstat.h + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef DETECTORNETSTAT_H +#define DETECTORNETSTAT_H + +#include + +#include "detector.h" + +class KProcess; +class IConnector; + +/** + @author Heiko Schäfer +*/ +class DetectorNetstat : protected QObject, public Detector { + Q_OBJECT + + DetectorNetstat(const DetectorNetstat&); + DetectorNetstat& operator=(const DetectorNetstat&); + +public: + DetectorNetstat(IConnector* connector); + virtual ~DetectorNetstat(); + + virtual void checkStatus() const; + +private slots: + // Original cs-plugin code + void slotProcessStdout(KProcess * process, char * buffer, int len); + + /** + * Notify when the netstat process has exited + */ + void slotProcessExited(KProcess *process); + +private: + mutable QString m_buffer; + mutable KProcess * m_process; +}; + +#endif diff --git a/kopete/plugins/smpppdcs/detectornetworkstatus.cpp b/kopete/plugins/smpppdcs/detectornetworkstatus.cpp new file mode 100644 index 00000000..921718b7 --- /dev/null +++ b/kopete/plugins/smpppdcs/detectornetworkstatus.cpp @@ -0,0 +1,68 @@ +/* + detectornetworkstatus.cpp + + Copyright (c) 2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include + +#include "kopeteuiglobal.h" +#include "connectionmanager.h" + +#include "iconnector.h" +#include "detectornetworkstatus.h" + +DetectorNetworkStatus::DetectorNetworkStatus(IConnector* connector) + : Detector(connector), m_connManager(NULL) { + + m_connManager = ConnectionManager::self(); + connect(m_connManager, SIGNAL(statusChanged(const QString&, NetworkStatus::EnumStatus)), + this, SLOT(statusChanged(const QString&, NetworkStatus::EnumStatus))); +} + +DetectorNetworkStatus::~DetectorNetworkStatus() {} + +void DetectorNetworkStatus::checkStatus() const { + // needs to do nothing +} + +void DetectorNetworkStatus::statusChanged(const QString& host, NetworkStatus::EnumStatus status) { + switch(status) { + case NetworkStatus::Offline: + kdDebug(14312) << k_funcinfo << host << ": NetworkStatus::Offline" << endl; + break; + case NetworkStatus::OfflineFailed: + kdDebug(14312) << k_funcinfo << host << ": NetworkStatus::OfflineFailed" << endl; + break; + case NetworkStatus::OfflineDisconnected: + kdDebug(14312) << k_funcinfo << host << ": NetworkStatus::OfflineDisconnected" << endl; + break; + case NetworkStatus::ShuttingDown: + kdDebug(14312) << k_funcinfo << host << ": NetworkStatus::ShuttingDown" << endl; + break; + case NetworkStatus::Establishing: + kdDebug(14312) << k_funcinfo << host << ": NetworkStatus::Establishing" << endl; + break; + case NetworkStatus::Online: + kdDebug(14312) << k_funcinfo << host << ": NetworkStatus::Online" << endl; + break; + case NetworkStatus::NoNetworks: + kdDebug(14312) << k_funcinfo << host << ": NetworkStatus::NoNetworks" << endl; + break; + case NetworkStatus::Unreachable: + kdDebug(14312) << k_funcinfo << host << ": NetworkStatus::Unreachable" << endl; + break; + } +} + +#include "detectornetworkstatus.moc" diff --git a/kopete/plugins/smpppdcs/detectornetworkstatus.h b/kopete/plugins/smpppdcs/detectornetworkstatus.h new file mode 100644 index 00000000..20315902 --- /dev/null +++ b/kopete/plugins/smpppdcs/detectornetworkstatus.h @@ -0,0 +1,50 @@ +/* + detectornetworkstatus.h + + Copyright (c) 2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef DETECTORNETWORKSTATUS_H +#define DETECTORNETWORKSTATUS_H + +#include + +#include "detector.h" + +class IConnector; +class ConnectionManager; + +/** + @author Heiko Schäfer +*/ +class DetectorNetworkStatus : protected QObject, public Detector +{ + Q_OBJECT + + DetectorNetworkStatus(const DetectorNetworkStatus&); + DetectorNetworkStatus& operator=(const DetectorNetworkStatus&); + +public: + DetectorNetworkStatus(IConnector* connector); + virtual ~DetectorNetworkStatus(); + + virtual void checkStatus() const; + +protected slots: + void statusChanged(const QString& host, NetworkStatus::EnumStatus status); + +private: + ConnectionManager * m_connManager; +}; + +#endif diff --git a/kopete/plugins/smpppdcs/detectorsmpppd.cpp b/kopete/plugins/smpppdcs/detectorsmpppd.cpp new file mode 100644 index 00000000..35ed1e05 --- /dev/null +++ b/kopete/plugins/smpppdcs/detectorsmpppd.cpp @@ -0,0 +1,71 @@ +/* + detectorsmpppd.cpp + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include + +#include "iconnector.h" +#include "detectorsmpppd.h" +#include "smpppdcsconfig.h" + +#include "smpppdclient.h" + +DetectorSMPPPD::DetectorSMPPPD(IConnector * connector) + : DetectorDCOP(connector) {} + +DetectorSMPPPD::~DetectorSMPPPD() {} + +/*! + \fn DetectorSMPPPD::checkStatus() + */ +void DetectorSMPPPD::checkStatus() const { + kdDebug(14312) << k_funcinfo << "Checking for online status..." << endl; + +#ifndef NOKINTERNETDCOP + m_kinternetApp = getKInternetDCOP(); + if(kapp->dcopClient() && m_kinternetApp != "") { + switch(getConnectionStatusDCOP()) { + case CONNECTED: + m_connector->setConnectedStatus(true); + return; + case DISCONNECTED: + m_connector->setConnectedStatus(false); + return; + default: + break; + } + } +#else +#warning DCOP inquiry disabled + kdDebug(14312) << k_funcinfo << "DCOP inquiry disabled" << endl; +#endif + + SMPPPD::Client c; + + unsigned int port = SMPPPDCSConfig::self()->port(); + QString server = SMPPPDCSConfig::self()->server(); + + c.setPassword(SMPPPDCSConfig::self()->password().utf8()); + + if(c.connect(server, port)) { + m_connector->setConnectedStatus(c.isOnline()); + } else { + kdDebug(14312) << k_funcinfo << "not connected to smpppd => I'll try again later" << endl; + m_connector->setConnectedStatus(false); + } +} diff --git a/kopete/plugins/smpppdcs/detectorsmpppd.h b/kopete/plugins/smpppdcs/detectorsmpppd.h new file mode 100644 index 00000000..0f72d46d --- /dev/null +++ b/kopete/plugins/smpppdcs/detectorsmpppd.h @@ -0,0 +1,46 @@ +/* + detectorsmpppd.h + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef DETECTORSMPPPD_H +#define DETECTORSMPPPD_H + +#include + +#include "detectordcop.h" + +namespace KNetwork { +class KStreamSocket; +}; + +class IConnector; + +/** + @author Heiko Schäfer +*/ +class DetectorSMPPPD : public DetectorDCOP { + + DetectorSMPPPD(const DetectorSMPPPD&); + DetectorSMPPPD& operator=(const DetectorSMPPPD&); + +public: + DetectorSMPPPD(IConnector* connector); + virtual ~DetectorSMPPPD(); + + virtual void checkStatus() const; + +}; + +#endif diff --git a/kopete/plugins/smpppdcs/iconnector.h b/kopete/plugins/smpppdcs/iconnector.h new file mode 100644 index 00000000..c4846862 --- /dev/null +++ b/kopete/plugins/smpppdcs/iconnector.h @@ -0,0 +1,45 @@ +/* + iconnector.h + + Copyright (c) 2005-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef ICONNECTOR_H +#define ICONNECTOR_H + +/** + * @brief Interface to an object setting a connection status. + * + * @author Heiko Schäfer + */ +class IConnector { + IConnector(const IConnector&); + IConnector& operator=(const IConnector&); + +public: + IConnector() {} + + virtual ~IConnector() {} + + /** + * @brief Set the connection status. + * + * This method needs to get reimplemented at classes which implement + * this interface. + * + * @param newStatus the status of the internet connection, TRUE if there is a connection, otherwise FALSE + */ + virtual void setConnectedStatus(bool newStatus) = 0; +}; + +#endif diff --git a/kopete/plugins/smpppdcs/icons/Makefile.am b/kopete/plugins/smpppdcs/icons/Makefile.am new file mode 100644 index 00000000..9143c6b4 --- /dev/null +++ b/kopete/plugins/smpppdcs/icons/Makefile.am @@ -0,0 +1,2 @@ +kopeteicondir = $(kde_datadir)/kopete/icons +kopeteicon_ICON = AUTO diff --git a/kopete/plugins/smpppdcs/icons/cr32-app-smpppdcs.png b/kopete/plugins/smpppdcs/icons/cr32-app-smpppdcs.png new file mode 100644 index 00000000..d895298b Binary files /dev/null and b/kopete/plugins/smpppdcs/icons/cr32-app-smpppdcs.png differ diff --git a/kopete/plugins/smpppdcs/kinternetiface.h b/kopete/plugins/smpppdcs/kinternetiface.h new file mode 100644 index 00000000..b0ac8aa7 --- /dev/null +++ b/kopete/plugins/smpppdcs/kinternetiface.h @@ -0,0 +1,47 @@ +// -*- c++ -*- +/*************************************************************************** + * * + * Copyright: SuSE Linux AG, Nuernberg * + * * + * Author: Arvin Schnell * + * * + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + + +#ifndef KINTERNETIFACE_H +#define KINTERNETIFACE_H + + +#include + +class KInternetIface : public DCOPObject +{ + K_DCOP + +public: + + KInternetIface (const QCString& name) : DCOPObject (name) { } + +k_dcop: + + // query function for susewatcher + bool isOnline () { +#ifndef NDEBUG + fprintf (stderr, "%s\n", __PRETTY_FUNCTION__); +#endif + return kinternet && kinternet->get_status () == KInternet::CONNECTED; + } + +}; + + +#endif diff --git a/kopete/plugins/smpppdcs/kopete_smpppdcs.desktop b/kopete/plugins/smpppdcs/kopete_smpppdcs.desktop new file mode 100644 index 00000000..09653c75 --- /dev/null +++ b/kopete/plugins/smpppdcs/kopete_smpppdcs.desktop @@ -0,0 +1,108 @@ +[Desktop Entry] +Type=Service +Icon=smpppdcs +X-Kopete-Version=1000900 +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_smpppdcs +X-KDE-PluginInfo-Author=Heiko Schäfer +X-KDE-PluginInfo-Email=heiko@rangun.de +X-KDE-PluginInfo-Name=kopete_smpppdcs +X-KDE-PluginInfo-Version=0.79 +X-KDE-PluginInfo-Website=http://www.rangun.de +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true +Name=SUSE smpppd-enabled Connection Status (SMPPPD) +Name[bn]=SUSE smpppd-enabled সংযোগ অবসà§à¦¥à¦¾ (SMPPPD) +Name[bs]=Status veze koji koristi SUSE-ov smpppd servis +Name[ca]=Estat actiu de la connexió SUSE smpppd (SMPPPD) +Name[cs]=Stav spojení SUSE smpppd-enabled (SMPPPD) +Name[da]=SUSE smpppd-aktiveret Forbindelsesstatus (SMPPPD) +Name[de]=Verbindungsstatus mit Unterstützung für SuSE SMPPPD +Name[el]=SuSE smpppd-ενεÏγοποίηση κατάσταση σÏνδεσης (SMPPPD) +Name[es]=Estado de conexión con SUSE smpppd habilitado (SMPPPD) +Name[et]=SUSE smpppd-võimalusega ühenduse staatus (SMPPPD) +Name[eu]=SUSE smpppd-gaitutat konexioaren egoera (SMPPPD) +Name[fa]=وضعیت اتصال Ùعال -SUSE smpppd (SMPPPD) +Name[fi]=SUSE smpppd-enabled -yhteyden tila (SMPPPD) +Name[fr]=État de la connexion pour « smpppd-enabled » de SUSE (SMPPPD) +Name[he]=SUSE smpppd-מ×פשר מצב חיבור (SMPPPD) +Name[hu]=SUSE smpppd-alapú állapotjellemzÅ‘ (SMPPPD) +Name[is]=SUSE smpppd-virkt staða tengingar (SMPPPD) +Name[it]=Stato della connessione di SUSE smpppd +Name[ja]=SUSE smpppd を使ã£ãŸæŽ¥ç¶šçŠ¶æ…‹ (SMPPPD) +Name[ka]=SUSE smpppd-ჩáƒáƒ áƒ—ული კáƒáƒ•áƒ¨áƒ˜áƒ áƒ˜áƒ¡ სტáƒáƒ¢áƒ£áƒ¡áƒ˜ (SMPPPD) +Name[kk]=SUSE smpppd (SMPPPD) қоÑылымының күйі +Name[km]=ស្ážáž¶áž“ភាព​ážáž—្ជាប់ SUSE ដែល​អនុញ្ញាហsmpppd (SMPPPD) +Name[lt]=SUSE smpppd ryÅ¡io bÅ«klÄ— (SMPPPD) +Name[nb]=Tilstand for SMPPPD – SUSE smpppd-forbindelse +Name[nds]=Verbinnenstatus mit Ãœnnerstütten för SuSE-SMPPPD +Name[ne]=SUSE smpppd ले जडान वसà¥à¤¤à¥à¤¸à¥à¤¥à¤¿à¤¤à¤¿ (SMPPPD) सकà¥à¤·à¤® पारà¥à¤¯à¥‹ +Name[nl]=SUSE smpppd-geactiveerde verbindingsstatus (SMPPPD) +Name[nn]=Tilstand for SMPPPD – SUSE smpppd-forbindelse +Name[pl]=Status poÅ‚Ä…czenia SUSE SMPPPD +Name[pt]=Estado da Ligação activado pelo 'smpppd' da SUSE (SMPPPD) +Name[pt_BR]=Status da Conexão compatível com SUSE smpppd +Name[ru]=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ñ SUSE smpppd (SMPPPD) +Name[sk]=Stav spojenia SUSE smpppd-enabled (SMPPPD) +Name[sl]=Stanje povezave z uporabo SuSE SMPPPD +Name[sr]=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ SUSE-ове smpppd-везе (SMPPPD) +Name[sr@Latn]=Status SUSE-ove smpppd-veze (SMPPPD) +Name[sv]=SUSE SMPPPD-aktiverad anslutningsstatus (SMPPPD) +Name[tr]=SUSE smpppd BaÄŸlantı Durumu (SMPPPD) +Name[uk]=SUSE smpppd-уможливлений Стан з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ (SMPPPD) +Name[zh_CN]=SUSE smppp 连接状æ€(SMPPPD) +Name[zh_HK]=連接 SUSE smpppd 的連線狀態 (SMPPPD) +Name[zh_TW]=SUSE smpppd 連線狀態 +Comment=Connects/disconnects Kopete automatically depending on availability of Internet connection +Comment[ar]=يقوم بوصل Ùˆ Ùصل Kopete تلقائيا استنادا إلى وضع الاتصال بالشبكة +Comment[be]=Злучае/адлучае Kopete ад ÑервіÑаў у залежнаÑці ад наÑўнаÑці інтÑрфÑйÑаў з ІнтÑрнÑтам +Comment[bg]=Ðвтоматично уÑтановÑване и прекъÑване на връзката в завиÑимоÑÑ‚ от ÑÑŠÑтоÑнието на връзката Ñ Ð˜Ð½Ñ‚ÐµÑ€Ð½ÐµÑ‚ +Comment[bn]=ইনà§à¦Ÿà¦¾à¦°à¦¨à§‡à¦Ÿ সংযোগের সà§à¦¬à¦¿à¦§à¦¾ অনà§à¦¯à¦¾à§Ÿà§€ কপেট সà§à¦¬à§Ÿà¦‚কà§à¦°à§€à§Ÿà¦­à¦¾à¦¬à§‡ সংযোগ/সংযোগবিচà§à¦›à¦¿à¦¨à§à¦¨à¦•à¦°à§‡ +Comment[bs]=Automatski spaja Kopete i prekida vezu ovisno o dostupnosti Internet konekcije +Comment[ca]=Connecta/desconnecta automàticament depenent de la disponibilitat de la connexió a Internet +Comment[cs]=Automaticky pÅ™ipojí nebo odpojí vzhledem k dostupnosti pÅ™ipojení na Internet +Comment[cy]=Cysylltu/datgyslltu Kopete yn ymysgogol, yn dibynnu ar argaeledd cysylltiad Rhyngrwyd +Comment[da]=Forbinder/afbryder Kopete automatisk afhængig af om der er en internet-forbindelse +Comment[de]=Verbindet/trennt Kopete automatisch abhängig von der Verfügbarkeit einer Internetverbindung +Comment[el]=Συνδέει/αποσυνδέει το Kopete αυτόματα ανάλογα με την κατάσταση της σÏνδεσης με το διαδίκτυο +Comment[es]=Conecta/desconecta Kopete automáticamente según la disponibilidad de la conexión a internet +Comment[et]=Kopete automaatne ühendamine/ühenduse katkestamine vastavalt internetiühenduse olemasolule +Comment[eu]=Kopete automatikoki konektatu/deskonektatzen du internet-eko konexioaren eskuragarritasunaren arabera +Comment[fa]=بسته به قابلیت دسترسی اتصال اینترنت، Kopete به طور خودکار وصل/قطع ارتباط می‌کند +Comment[fi]=Yhdistää/katkaisee yhteyden automaattisesti riippuen onko Internet-yhteys päällä +Comment[fr]=Connecte / déconnecte automatiquement Kopete en fonction de la disponibilité de la connexion Internet +Comment[gl]=Conecta/desconecta a Kopete automáticamente dependendo da disponibilidade de conexión a Internet +Comment[he]=חיבור\ניתוק Kopete ב×ופן ×וטומטי בתלות בזמינות של החיבור ל×ינטרנט +Comment[hi]=इंटरनेट कनेकà¥à¤¶à¤¨ की उपलबà¥à¤§à¤¤à¤¾ की निरà¥à¤­à¤°à¤¤à¤¾ के आधार पर के-ऑपà¥à¤Ÿà¥€ को सà¥à¤µà¤šà¤²à¤¿à¤¤ कनेकà¥à¤Ÿ/डिसà¥à¤•à¤¨à¥‡à¤•à¥à¤Ÿ करता है +Comment[hu]=A Kopete automatikus csatlakoztatása és bontása az internetkapcsolat elérhetÅ‘ségétÅ‘l függÅ‘en +Comment[is]=(Af)tengir Kopete sjálfkrafa miðað við stöðu Internettengingar +Comment[it]=Connetti/disconnetti automaticamente Kopete a seconda della disponibilità della connessione ad internet +Comment[ja]=インターãƒãƒƒãƒˆæŽ¥ç¶šã®æœ‰ç„¡ã«ã‚ˆã‚Šè‡ªå‹•çš„ã« Kopete を接続/切断ã—ã¾ã™ +Comment[ka]=ინტერნეტ კáƒáƒ•áƒ¨áƒ˜áƒ áƒ˜áƒ¡ áƒáƒ áƒ¡áƒ”ბáƒáƒ‘ისáƒáƒ¡ áƒáƒ•áƒ¢áƒáƒ›áƒáƒ¢áƒ£áƒ áƒáƒ“ áƒáƒ™áƒáƒ•áƒ¨áƒ˜áƒ áƒ”ბს Kopete-ს +Comment[kk]=Интернетке қоÑылымның бар=жоғына қарай Kopete-Ñ‚Ñ– автоматты түрде қоÑады және ажыратады +Comment[km]=ភ្ជាប់ ​ ​ផ្ដាច​ Kopeteដោយ​ស្វáŸáž™áž”្រវážáŸ’ážáž·â€‹ážŠáŸ„យ​ផ្អែក​លើ​ភាព​មាន​នៃការណážâ€‹áž—្ជាប់​អ៊ីនធឺណិហ+Comment[lt]=AutomatiÅ¡kai prijungia/atjungia Kopete, priklausomai nuo interneto ryÅ¡io buvimo +Comment[mk]=ÐвтоматÑки го поврзува/иÑклучува Kopete во завиÑноÑÑ‚ на доÑтапноÑта на поврзувањето на Интернет +Comment[nb]=Kobler Kopete automatisk til/fra avhengig av om internettforbindelse er tilgjengelig +Comment[nds]=Koppelt Kopete automaatsch to- oder af, afhangen vun de Verföögborkeit vun de Internetverbinnen +Comment[ne]=इनà¥à¤Ÿà¤°à¤¨à¥‡à¤Ÿ जडानको उपलबà¥à¤§à¤¤à¤¾à¤®à¤¾ आधारित कोपेट सà¥à¤µà¤šà¤¾à¤²à¤¿à¤¤ रूपमा जडान गरà¥à¤¦à¤›/विचà¥à¤›à¥‡à¤¦à¤¨ गरà¥à¤¦à¤› +Comment[nl]=Bouwt automatisch verbindingen op voor Kopete of verbreekt ze, afhankelijk van de beschikbaarheid van de internetverbinding +Comment[nn]=Koplar Kopete automatisk til eller frÃ¥ avhengig av om Internett-sambandet er tilgjengeleg. +Comment[pl]=Automatycznie podÅ‚Ä…cza/odÅ‚Ä…cza Kopete zależnie od stanu poÅ‚Ä…czenia z Internetem +Comment[pt]=Liga/desliga o Kopete automaticamente, dependendo da disponibilidade de uma ligação à Internet +Comment[pt_BR]=Conecta/desconecta o Kopete automaticamente dependendo da disponibilidade da conexão com a Internet +Comment[ru]=ÐвтоматичеÑки входит в Ñеть или выходит из неё в завиÑимоÑти от Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ñ ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ñ Ð˜Ð½Ñ‚ÐµÑ€Ð½ÐµÑ‚ +Comment[sk]=Pripojí/odpojí Kopete v závislosti Äi existuje pripojenie na Internet +Comment[sl]=Samodejno vzpostavi/prekine povezavo Kopete glede na dostopnost internetne povezave +Comment[sr]=ÐутоматÑки уÑпоÑтавља или прекида везу у Kopete-у у завиÑноÑти од доÑтупноÑти интернет везе +Comment[sr@Latn]=Automatski uspostavlja ili prekida vezu u Kopete-u u zavisnosti od dostupnosti internet veze +Comment[sv]=Ansluter eller kopplar ner Kopete automatiskt beroende pÃ¥ Internetförbindelsens tillgänglighet +Comment[ta]=இணைய இணைபà¯à®ªà¯ˆ பொரà¯à®¤à¯à®¤à¯ Kopete யà¯à®Ÿà®©à¯à®¤à®¾à®©à®¾à®• இணையà¯à®®à¯/நீகà¯à®•à¯à®®à¯ +Comment[tg]=ВобаÑта ба имкониÑтҳои алоқаи Интернет Kopete-ро ба таври худкор пайваÑÑ‚/канда мекунад +Comment[tr]=Ä°nternet baÄŸlantısının kullanılabilir olabilirliÄŸine göre Kopete'yi otomatik baÄŸlar/baÄŸlamaz +Comment[uk]=Ðвтоматично входить в мережу чи виходить з неї в залежноÑÑ‚Ñ– від наÑвноÑÑ‚Ñ– з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· Інтернет +Comment[zh_CN]=æ ¹æ® Internet 连接是å¦å¯ç”¨è‡ªåŠ¨è¿žæŽ¥/æ–­å¼€ Kopete +Comment[zh_HK]=自動根據互è¯ç¶²é€£æŽ¥æƒ…æ³é€£æŽ¥æˆ–中斷 Kopete 連線 +Comment[zh_TW]=自動ä¾æ“šç¶²è·¯ç‹€æ³ä¾†é€£ç·š/中斷連線 Kopete diff --git a/kopete/plugins/smpppdcs/kopete_smpppdcs_config.desktop b/kopete/plugins/smpppdcs/kopete_smpppdcs_config.desktop new file mode 100644 index 00000000..3a2553cf --- /dev/null +++ b/kopete/plugins/smpppdcs/kopete_smpppdcs_config.desktop @@ -0,0 +1,108 @@ +[Desktop Entry] +Icon=smpppdcs +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_smpppdcs +X-KDE-FactoryName=SMPPPDCSConfigFactory +X-KDE-ParentApp=kopete_smpppdcs +X-KDE-ParentComponents=kopete_smpppdcs + +X-Kopete-Version=1000900 + +Name=SUSE SMPPPD Connection Status +Name[be]=Стан злучÑÐ½Ð½Ñ SUSE SMPPPD +Name[bn]=SUSE SMPPPD সংযোগ অবসà§à¦¥à¦¾ +Name[bs]=SUSE SMPPPD status veze +Name[ca]=Estatus de la connexió SUSE SMPPPD +Name[cs]=Stav spojení SUSE SMPPPD +Name[da]=SUSE SMPPD Forbindelsesstatus +Name[de]=SuSE SMPPPD-Verbindungsstatus +Name[el]=Κατάσταση σÏνδεσης του SuSE SMPPPD +Name[es]=Estado de conexión de SUSE SMPPPD +Name[et]=SUSE SMPPPD ühenduse staatus +Name[eu]=SUSE SMPPPD konexioaren egoera +Name[fa]=وضعیت اتصال SUSE SMPPPD +Name[fi]=SUSE SMPPPD -yhteyden tila +Name[fr]=État de la connexion SUSE SMPPPD +Name[ga]=Stádas Ceangail SUSE SMPPPD +Name[gl]=Estado da conexión de SUSE SMPPPD +Name[he]=מצב החיבור של SUSE SMPPPD +Name[hu]=SUSE SMPPPD kapcsolati állapot +Name[is]=SUSE SMPPPD tengingarstaða +Name[it]=Stato della connessione di SUSE SMPPPD +Name[ja]=SUSE SMPPPD 接続状態 +Name[ka]=SUSE SMPPPD კáƒáƒ•áƒ¨áƒ˜áƒ áƒ˜áƒ¡ სტáƒáƒ¢áƒ£áƒ¡áƒ˜ +Name[kk]=SUSE SMPPPD Ð±Ð°Ð¹Ð»Ð°Ð½Ñ‹Ñ ÐºÒ¯Ð¹Ñ– +Name[km]=ស្ážáž¶áž“ភាព​ការ​ážáž—្ជាប់ SUSE SMPPPD +Name[lt]=SUSE SMPPPD ryÅ¡io bÅ«klÄ— +Name[nb]=Tilstand for SUSE-SMPPPD-forbindelsen +Name[nds]=SUSE SMPPPD-Verbinnenstatus +Name[ne]=SUSE SMPPPD जडान वसà¥à¤¤à¥à¤¸à¥à¤¥à¤¿à¤¤à¤¿ +Name[nl]=SUSE SMPPPD-verbindingsstatus +Name[nn]=Tilstand for SUSE-SMPPPD-sambandet +Name[pl]=Status poÅ‚Ä…czenia SUSE SMPPPD +Name[pt]=Estado da Ligação do SMPPPD para a SUSE +Name[pt_BR]=Status da Conexão SUSE SMPPPD +Name[ru]=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ SUSE SMPPPD +Name[sk]=Stav spojenia SUSE SMPPPD +Name[sl]=Stanje povezave z uporabo SuSE SMPPPD +Name[sr]=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ SUSE-ове SMPPPD везе +Name[sr@Latn]=Status SUSE-ove SMPPPD veze +Name[sv]=SUSE SMPPPD anslutningsstatus +Name[tr]=SUSE SMPPPD baÄŸlantı durumu +Name[uk]=Стан з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ SUSE SMPPPD +Name[zh_CN]=SUSE SMPPPD è¿žæŽ¥çŠ¶æ€ +Name[zh_HK]=SUSE SMPPPD 連線狀態 +Name[zh_TW]=SUSE SMPPPD 連線狀態 +Comment=SMPPPDCS Plugin +Comment[be]=Модуль SMPPPDCS +Comment[bn]=SMPPPDCS পà§à¦²à¦¾à¦—িন +Comment[br]=Lugant SMPPPDCS +Comment[bs]=SMPPPDCS dodatak +Comment[ca]=Connector SMPPPDCS +Comment[cs]=SMPPPDCS modul +Comment[da]=SMPPPDCS-Plugin +Comment[de]=SMPPPDCS-Modul +Comment[el]=ΠÏόσθετο SMPPPDCS +Comment[eo]=SMPPPDCS-kromaĵo +Comment[es]=Extensión SMPPPDCS +Comment[et]=SMPPPDCS plugin +Comment[eu]=SMPPPDCS plugin-a +Comment[fa]=SUSE SMPPPD وصلۀ +Comment[fi]=SMPPPDCS-liitännäinen +Comment[fr]=Module SMPPPDCS +Comment[ga]=Breiseán SMPPPDCS +Comment[gl]=Plugin SMPPPDCS +Comment[he]=תוסף SMPPPDCS +Comment[hu]=SMPPPDCS bÅ‘vítÅ‘modul +Comment[is]=SMPPPDCS íforrit +Comment[it]=Plugin SMPPPDCS +Comment[ja]=SMPPPDCS プラグイン +Comment[ka]=SMPPPDCS მáƒáƒ“ული +Comment[kk]=SMPPPDCS плагин модулі +Comment[km]=កម្មវិធី​ជំនួយ SMPPPDCS +Comment[lt]=SMPPPDCS įskiepis +Comment[nb]=Programtillegg for SMPPPDCS +Comment[nds]=SMPPPDCS-Moduul +Comment[ne]=SMPPPDCS पà¥à¤²à¤—इन +Comment[nl]=SMPPPDCS-plugin +Comment[nn]=Programtillegg for SMPPPDCS +Comment[pl]=Wtyczka SMPPPDCS +Comment[pt]='Plugin' do SMPPPDCS +Comment[pt_BR]=Plugin SMPPPDCS +Comment[ro]=Modul SMPPPDCS +Comment[ru]=Модуль SMPPPDCS +Comment[sk]=SMPPPDCS modul +Comment[sl]=Vstavek SMPPPDCS +Comment[sr]=Прикључак SMPPPDCS +Comment[sr@Latn]=PrikljuÄak SMPPPDCS +Comment[sv]=SMPPPDCS-insticksprogram +Comment[tr]=SMPPPDCS Eklentisi +Comment[uk]=Втулок SMPPPDCS +Comment[uz]=SMPPPDCS plagini +Comment[uz@cyrillic]=SMPPPDCS плагини +Comment[zh_CN]=SMPPPDCS æ’件 +Comment[zh_HK]=SMPPPDCS æ’件 +Comment[zh_TW]=SMPPPDCS å¤–æŽ›ç¨‹å¼ diff --git a/kopete/plugins/smpppdcs/libsmpppdclient/Makefile.am b/kopete/plugins/smpppdcs/libsmpppdclient/Makefile.am new file mode 100644 index 00000000..9fc9258c --- /dev/null +++ b/kopete/plugins/smpppdcs/libsmpppdclient/Makefile.am @@ -0,0 +1,10 @@ +AM_CPPFLAGS = $(all_includes) + +noinst_LTLIBRARIES = libsmpppdclient.la +libsmpppdclient_la_LDFLAGS = -avoid-version $(all_libraries) + +noinst_HEADERS = smpppdclient.h smpppdstate.h smpppdready.h smpppdunsettled.h +libsmpppdclient_la_SOURCES = smpppdclient.cpp smpppdstate.cpp smpppdready.cpp \ + smpppdunsettled.cpp + +libsmpppdclient_la_LIBADD = -lcrypto diff --git a/kopete/plugins/smpppdcs/libsmpppdclient/smpppdclient.cpp b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdclient.cpp new file mode 100644 index 00000000..d386b669 --- /dev/null +++ b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdclient.cpp @@ -0,0 +1,104 @@ +/* + smpppdclient.cpp + + Copyright (c) 2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include + +#include "smpppdunsettled.h" +#include "smpppdclient.h" + +using namespace SMPPPD; + +Client::Client() + : m_state(NULL), m_sock(NULL), m_serverID(QString::null), m_serverVer(QString::null), m_password(QString::null) { + changeState(Unsettled::instance()); +} + +Client::~Client() { + disconnect(); +} + +bool Client::connect(const QString& server, uint port) { + return m_state->connect(this, server, port); +} + +void Client::disconnect() { + m_state->disconnect(this); +} + +QStringList Client::getInterfaceConfigurations() { + return m_state->getInterfaceConfigurations(this); +} + +bool Client::statusInterface(const QString& ifcfg) { + return m_state->statusInterface(this, ifcfg); +} + +QString Client::serverID() const { + return m_serverID; +} + +QString Client::serverVersion() const { + return m_serverVer; +} + +QStringList Client::read() const { + QStringList qsl; + + if(isReady()) { + QDataStream stream(m_sock); + char s[1024]; + + stream.readRawBytes(s, 1023); + char *sp = s; + + for(int i = 0; i < 1024; i++) { + if(s[i] == '\n') { + s[i] = 0; + qsl.push_back(sp); + sp = &(s[i+1]); + } + } + } + + return qsl; +} + +void Client::write(const char * cmd) { + if(isReady()) { + QDataStream stream(m_sock); + stream.writeRawBytes(cmd, strlen(cmd)); + stream.writeRawBytes("\n", strlen("\n")); + m_sock->flush(); + } +} + +bool Client::isReady() const { + return m_sock && m_sock->state() == KNetwork::KStreamSocket::Connected; +} + +bool Client::isOnline() { + + if(isReady()) { + QStringList ifcfgs = getInterfaceConfigurations(); + for(uint i = 0; i < ifcfgs.count(); i++) { + if(statusInterface(ifcfgs[i])) { + return true; + } + } + } + + return false; +} diff --git a/kopete/plugins/smpppdcs/libsmpppdclient/smpppdclient.h b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdclient.h new file mode 100644 index 00000000..a123cd4c --- /dev/null +++ b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdclient.h @@ -0,0 +1,80 @@ +/* + smpppdclient.h + + Copyright (c) 2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef SMPPPDCLIENT_H +#define SMPPPDCLIENT_H + +#include + +namespace KNetwork { +class KStreamSocket; +}; + +namespace SMPPPD { + +class State; + +/** + @author Heiko Schaefer +*/ +class Client { + Client(const Client&); + Client& operator=(const Client&); + +public: + Client(); + ~Client(); + + bool isReady() const; + + bool connect(const QString& server, uint port = 3185); + void disconnect(); + + QStringList getInterfaceConfigurations(); + bool statusInterface(const QString& ifcfg); + + bool isOnline(); + QString serverID() const; + QString serverVersion() const; + + void setPassword(const QString& password); + +private: + friend class State; + + void changeState(State * newState); + QStringList read() const; + void write(const char * cmd); + +private: + State * m_state; + KNetwork::KStreamSocket * m_sock; + QString m_serverID; + QString m_serverVer; + QString m_password; +}; + +inline void Client::changeState(State * newState) { + m_state = newState; +} + +inline void Client::setPassword(const QString& password) { + m_password = password; +} + +}; + +#endif diff --git a/kopete/plugins/smpppdcs/libsmpppdclient/smpppdready.cpp b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdready.cpp new file mode 100644 index 00000000..421914bb --- /dev/null +++ b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdready.cpp @@ -0,0 +1,104 @@ +/* + smpppdready.cpp + + Copyright (c) 2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include + +#include +#include + +#include "smpppdunsettled.h" +#include "smpppdclient.h" +#include "smpppdready.h" + +using namespace SMPPPD; + +Ready * Ready::m_instance = NULL; + +Ready::Ready() {} + +Ready::~Ready() {} + +Ready * Ready::instance() { + if(!m_instance) { + m_instance = new Ready; + } + + return m_instance; +} + +void Ready::disconnect(Client * client) { + kdDebug(14312) << k_funcinfo << endl; + if(socket(client)) { + socket(client)->flush(); + socket(client)->close(); + + delete socket(client); + setSocket(client, NULL); + + setServerID(client, QString::null); + setServerVersion(client, QString::null); + } + + changeState(client, Unsettled::instance()); +} + +QStringList Ready::getInterfaceConfigurations(Client * client) { + + QStringList ifcfgs; + + // we want all ifcfgs + kdDebug(14312) << k_funcinfo << "smpppd req: list-ifcfgs" << endl; + write(client, "list-ifcfgs"); + QStringList stream = read(client); + kdDebug(14312) << k_funcinfo << "smpppd ack: " << stream[0] << endl; + if(stream[0].startsWith("ok")) { + // we have now a QStringList with all ifcfgs + // we extract them and put them in the global ifcfgs-list + // stream[1] tells us how many ifcfgs are coming next + QRegExp numIfcfgsRex("^BEGIN IFCFGS ([0-9]+).*"); + if(numIfcfgsRex.exactMatch(stream[1])) { + int count_ifcfgs = numIfcfgsRex.cap(1).toInt(); + kdDebug(14312) << k_funcinfo << "ifcfgs: " << count_ifcfgs << endl; + + for(int i = 0; i < count_ifcfgs; i++) { + QRegExp ifcfgRex("^i \"(ifcfg-[a-zA-Z]+[0-9]+)\".*"); + if(ifcfgRex.exactMatch(stream[i+2])) { + ifcfgs.push_back(ifcfgRex.cap(1)); + } + } + } + } + + return ifcfgs; +} + +bool Ready::statusInterface(Client * client, const QString& ifcfg) { + + QString cmd = "list-status " + ifcfg; + + write(client, cmd.latin1()); + socket(client)->waitForMore(0); + + QStringList stream = read(client); + + if(stream[0].startsWith("ok")) { + if(stream[2].startsWith("status connected")) { + return true; + } + } + + return false; +} diff --git a/kopete/plugins/smpppdcs/libsmpppdclient/smpppdready.h b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdready.h new file mode 100644 index 00000000..9ec3ab93 --- /dev/null +++ b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdready.h @@ -0,0 +1,49 @@ +/* + smpppdready.h + + Copyright (c) 2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef SMPPPDREADY_H +#define SMPPPDREADY_H + +#include "smpppdstate.h" + +namespace SMPPPD { + +/** + @author Heiko Schaefer +*/ +class Ready : public State +{ + Ready(const Ready&); + Ready& operator=(const Ready&); + + Ready(); + +public: + virtual ~Ready(); + + static Ready * instance(); + + virtual void disconnect(Client * client); + virtual QStringList getInterfaceConfigurations(Client * client); + virtual bool statusInterface(Client * client, const QString& ifcfg); + +private: + static Ready * m_instance; +}; + +}; + +#endif diff --git a/kopete/plugins/smpppdcs/libsmpppdclient/smpppdstate.cpp b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdstate.cpp new file mode 100644 index 00000000..9e4bd508 --- /dev/null +++ b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdstate.cpp @@ -0,0 +1,76 @@ +/* + smpppdstate.cpp + + Copyright (c) 2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include + +#include "smpppdclient.h" +#include "smpppdstate.h" + +using namespace SMPPPD; + +State::State() {} + +State::~State() {} + +QStringList State::read(Client * client) const { + return client->read(); +} + +void State::write(Client * client, const char * cmd) { + client->write(cmd); +} + +void State::changeState(Client * client, State * state) { + client->changeState(state); +} + +KNetwork::KStreamSocket * State::socket(Client * client) const { + return client->m_sock; +} + +QString State::password(Client * client) const { + return client->m_password; +} + +void State::setPassword(Client * client, const QString& pass) { + client->m_password = pass; +} + +void State::setServerID(Client * client, const QString& id) { + client->m_serverID = id; +} + +void State::setServerVersion(Client * client, const QString& ver) { + client->m_serverVer = ver; +} + +void State::setSocket(Client * client, KNetwork::KStreamSocket * sock) { + client->m_sock = sock; +} + +bool State::connect(Client * /* client */, const QString& /* server */, uint /* port */) { + return false; +} + +void State::disconnect(Client * /* client */) {} + +QStringList State::getInterfaceConfigurations(Client * /* client */) { + return QStringList(); +} + +bool State::statusInterface(Client * /* client */, const QString& /* ifcfg */) { + return false; +} diff --git a/kopete/plugins/smpppdcs/libsmpppdclient/smpppdstate.h b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdstate.h new file mode 100644 index 00000000..0e7d393b --- /dev/null +++ b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdstate.h @@ -0,0 +1,58 @@ +/* + smpppdstate.h + + Copyright (c) 2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef SMPPPDSTATE_H +#define SMPPPDSTATE_H + +#include + +namespace SMPPPD { + +class Client; + +/** + @author Heiko Schaefer +*/ +class State { + State(const State&); + State& operator=(const State&); + +public: + State(); + virtual ~State(); + + virtual bool connect(Client * client, const QString& server, uint port = 3185); + virtual void disconnect(Client * client); + + virtual QStringList getInterfaceConfigurations(Client * client); + virtual bool statusInterface(Client * client, const QString& ifcfg); + +protected: + QStringList read(Client * client) const; + void write(Client * client, const char * cmd); + void changeState(Client * client, State * state); + KNetwork::KStreamSocket * socket(Client * client) const; + void setSocket(Client * client, KNetwork::KStreamSocket * sock); + QString password(Client * client) const; + void setPassword(Client * client, const QString& pass); + void setServerID(Client * client, const QString& id); + void setServerVersion(Client * client, const QString& ver); + +}; + +}; + +#endif diff --git a/kopete/plugins/smpppdcs/libsmpppdclient/smpppdunsettled.cpp b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdunsettled.cpp new file mode 100644 index 00000000..7ed5f516 --- /dev/null +++ b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdunsettled.cpp @@ -0,0 +1,153 @@ +/* + smpppdunsettled.cpp + + Copyright (c) 2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include +#include + +#include + +#include +#include + +#include "smpppdready.h" +#include "smpppdunsettled.h" + +using namespace SMPPPD; + +Unsettled * Unsettled::m_instance = NULL; + +Unsettled::Unsettled() {} + +Unsettled::~Unsettled() {} + +Unsettled * Unsettled::instance() { + if(!m_instance) { + m_instance = new Unsettled(); + } + + return m_instance; +} + +bool Unsettled::connect(Client * client, const QString& server, uint port) { + if(!socket(client) || + socket(client)->state() != KNetwork::KStreamSocket::Connected || + socket(client)->state() != KNetwork::KStreamSocket::Connecting) { + + QString resolvedServer = server; + + changeState(client, Ready::instance()); + disconnect(client); + + // since a lookup on a non-existant host can take a lot of time we + // try to get the IP of server before and we do the lookup ourself + KNetwork::KResolver resolver(server); + resolver.start(); + if(resolver.wait(500)) { + KNetwork::KResolverResults results = resolver.results(); + if(!results.empty()) { + QString ip = results[0].address().asInet().ipAddress().toString(); + kdDebug(14312) << k_funcinfo << "Found IP-Address for " << server << ": " << ip << endl; + resolvedServer = ip; + } else { + kdWarning(14312) << k_funcinfo << "No IP-Address found for " << server << endl; + return false; + } + } else { + kdWarning(14312) << k_funcinfo << "Looking up hostname timed out, consider to use IP or correct host" << endl; + return false; + } + + setSocket(client, new KNetwork::KStreamSocket(resolvedServer, QString::number(port))); + socket(client)->setBlocking(TRUE); + + if(!socket(client)->connect()) { + kdDebug(14312) << k_funcinfo << "Socket Error: " << KNetwork::KStreamSocket::errorString(socket(client)->error()) << endl; + } else { + kdDebug(14312) << k_funcinfo << "Successfully connected to smpppd \"" << server << ":" << port << "\"" << endl; + + static QString verRex = "^SuSE Meta pppd \\(smpppd\\), Version (.*)$"; + static QString clgRex = "^challenge = (.*)$"; + + QRegExp ver(verRex); + QRegExp clg(clgRex); + + QString response = read(client)[0]; + + if(response != QString::null && + ver.exactMatch(response)) { + setServerID(client, response); + setServerVersion(client, ver.cap(1)); + changeState(client, Ready::instance()); + return true; + } else if(response != QString::null && + clg.exactMatch(response)) { + if(password(client) != QString::null) { + // we are challenged, ok, respond + write(client, QString("response = %1\n").arg(make_response(clg.cap(1).stripWhiteSpace(), password(client))).latin1()); + response = read(client)[0]; + if(ver.exactMatch(response)) { + setServerID(client, response); + setServerVersion(client, ver.cap(1)); + return true; + } else { + kdWarning(14312) << k_funcinfo << "SMPPPD responded: " << response << endl; + changeState(client, Ready::instance()); + disconnect(client); + } + } else { + kdWarning(14312) << k_funcinfo << "SMPPPD requested a challenge, but no password was supplied!" << endl; + changeState(client, Ready::instance()); + disconnect(client); + } + } + } + } + + return false; +} + +QString Unsettled::make_response(const QString& chex, const QString& password) const { + + int size = chex.length (); + if (size & 1) + return "error"; + size >>= 1; + + // convert challenge from hex to bin + QString cbin; + for (int i = 0; i < size; i++) { + QString tmp = chex.mid (2 * i, 2); + cbin.append ((char) strtol (tmp.ascii (), 0, 16)); + } + + // calculate response + unsigned char rbin[MD5_DIGEST_LENGTH]; + MD5state_st md5; + MD5_Init (&md5); + MD5_Update (&md5, cbin.ascii (), size); + MD5_Update (&md5, password.ascii(), password.length ()); + MD5_Final (rbin, &md5); + + // convert response from bin to hex + QString rhex; + for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { + char buffer[3]; + snprintf (buffer, 3, "%02x", rbin[i]); + rhex.append (buffer); + } + + return rhex; +} diff --git a/kopete/plugins/smpppdcs/libsmpppdclient/smpppdunsettled.h b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdunsettled.h new file mode 100644 index 00000000..57a83752 --- /dev/null +++ b/kopete/plugins/smpppdcs/libsmpppdclient/smpppdunsettled.h @@ -0,0 +1,49 @@ +/* + smpppdunsettled.h + + Copyright (c) 2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef SMPPPDSMPPPDUNSETTLED_H +#define SMPPPDSMPPPDUNSETTLED_H + +#include "smpppdstate.h" + +namespace SMPPPD { + +/** + @author Heiko Schaefer +*/ +class Unsettled : public State +{ + Unsettled(const Unsettled&); + Unsettled& operator=(const Unsettled&); + + Unsettled(); +public: + virtual ~Unsettled(); + + static Unsettled * instance(); + + virtual bool connect(Client * client, const QString& server, uint port = 3185); + +private: + QString make_response(const QString& chex, const QString& password) const; + +private: + static Unsettled * m_instance; +}; + +} + +#endif diff --git a/kopete/plugins/smpppdcs/onlineinquiry.cpp b/kopete/plugins/smpppdcs/onlineinquiry.cpp new file mode 100644 index 00000000..4cab45d7 --- /dev/null +++ b/kopete/plugins/smpppdcs/onlineinquiry.cpp @@ -0,0 +1,45 @@ +/* + onlineinquiry.cpp + + Copyright (c) 2005-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include "detectornetstat.h" +#include "detectorsmpppd.h" +#include "onlineinquiry.h" + +OnlineInquiry::OnlineInquiry() + : m_detector(NULL), m_online(FALSE) {} + +OnlineInquiry::~OnlineInquiry() { + delete m_detector; +} + +bool OnlineInquiry::isOnline(bool useSMPPPD) { + + delete m_detector; + + if(useSMPPPD) { + m_detector = new DetectorSMPPPD(this); + } else { + m_detector = new DetectorNetstat(this); + } + + m_detector->checkStatus(); + + return m_online; +} + +void OnlineInquiry::setConnectedStatus(bool newStatus) { + m_online = newStatus; +} diff --git a/kopete/plugins/smpppdcs/onlineinquiry.h b/kopete/plugins/smpppdcs/onlineinquiry.h new file mode 100644 index 00000000..c9b5221a --- /dev/null +++ b/kopete/plugins/smpppdcs/onlineinquiry.h @@ -0,0 +1,45 @@ +/* + onlineinquiry.h + + Copyright (c) 2005-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef ONLINEINQUIRY_H +#define ONLINEINQUIRY_H + +#include "iconnector.h" + +class Detector; + +/** + * @author Heiko Schäfer + */ + +class OnlineInquiry : public IConnector { + OnlineInquiry(const OnlineInquiry&); + OnlineInquiry& operator=(const OnlineInquiry&); + +public: + OnlineInquiry(); + virtual ~OnlineInquiry(); + + bool isOnline(bool useSMPPPD); + + virtual void setConnectedStatus(bool newStatus); + +private: + Detector * m_detector; + bool m_online; +}; + +#endif diff --git a/kopete/plugins/smpppdcs/smpppdcs.kcfg b/kopete/plugins/smpppdcs/smpppdcs.kcfg new file mode 100644 index 00000000..2ca65f54 --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdcs.kcfg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + localhost + + + + 3185 + + + + true + + + + false + + + \ No newline at end of file diff --git a/kopete/plugins/smpppdcs/smpppdcsconfig.kcfgc b/kopete/plugins/smpppdcs/smpppdcsconfig.kcfgc new file mode 100644 index 00000000..2e955708 --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdcsconfig.kcfgc @@ -0,0 +1,6 @@ +File=smpppdcs.kcfg +ClassName=SMPPPDCSConfig +Singleton=true +Mutators=true +MemberVariables=private +GlobalEnums=true \ No newline at end of file diff --git a/kopete/plugins/smpppdcs/smpppdcsiface.h b/kopete/plugins/smpppdcs/smpppdcsiface.h new file mode 100644 index 00000000..face60ad --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdcsiface.h @@ -0,0 +1,36 @@ +/* + smpppdcsiface.h + + Copyright (c) 2005-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef SMPPPDCSIFACE_H +#define SMPPPDCSIFACE_H + +#include + +/** + * @author Heiko Schäfer + */ + +class SMPPPDCSIFace : virtual public DCOPObject +{ + K_DCOP + + k_dcop: + + virtual QString detectionMethod() const = 0; + virtual bool isOnline() const = 0; +}; + +#endif diff --git a/kopete/plugins/smpppdcs/smpppdcsplugin.cpp b/kopete/plugins/smpppdcs/smpppdcsplugin.cpp new file mode 100644 index 00000000..2ed8455c --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdcsplugin.cpp @@ -0,0 +1,221 @@ +/* + smpppdcsplugin.cpp + + Copyright (c) 2002-2003 by Chris Howells + Copyright (c) 2003 by Martijn Klingens + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include "onlineinquiry.h" +#include "smpppdcsplugin.h" + +#include + +#include +#include + +#include "kopeteprotocol.h" +#include "networkstatuscommon.h" +#include "kopetepluginmanager.h" +#include "kopeteaccountmanager.h" + +#include "detectornetworkstatus.h" +#include "detectornetstat.h" +#include "detectorsmpppd.h" +#include "smpppdcsconfig.h" + +typedef KGenericFactory SMPPPDCSPluginFactory; +K_EXPORT_COMPONENT_FACTORY(kopete_smpppdcs, SMPPPDCSPluginFactory("kopete_smpppdcs")) + +SMPPPDCSPlugin::SMPPPDCSPlugin(QObject *parent, const char * name, const QStringList& /* args */) + : DCOPObject("SMPPPDCSIface"), Kopete::Plugin(SMPPPDCSPluginFactory::instance(), parent, name), + m_detectorSMPPPD(NULL), m_detectorNetstat(NULL), m_detectorNetworkStatus(NULL), m_timer(NULL), +m_onlineInquiry(NULL) { + + kdDebug(14312) << k_funcinfo << endl; + + m_pluginConnected = false; + + m_onlineInquiry = new OnlineInquiry(); + m_detectorSMPPPD = new DetectorSMPPPD(this); + m_detectorNetstat = new DetectorNetstat(this); + + // experimental, not used yet + m_detectorNetworkStatus = new DetectorNetworkStatus(this); + + // we wait for the allPluginsLoaded signal, to connect + // as early as possible after startup, but not before + // all accounts are ready + connect(Kopete::PluginManager::self(), SIGNAL(allPluginsLoaded()), + this, SLOT(allPluginsLoaded())); + + // if kopete was already running and the plugin + // was loaded later, we check once after 15 secs + // if all other plugins have been loaded + QTimer::singleShot(15000, this, SLOT(allPluginsLoaded())); +} + +SMPPPDCSPlugin::~SMPPPDCSPlugin() { + + kdDebug(14312) << k_funcinfo << endl; + + delete m_timer; + delete m_detectorSMPPPD; + delete m_detectorNetstat; + delete m_detectorNetworkStatus; + delete m_onlineInquiry; +} + +void SMPPPDCSPlugin::allPluginsLoaded() { + + if(Kopete::PluginManager::self()->isAllPluginsLoaded()) { + m_timer = new QTimer(); + connect(m_timer, SIGNAL(timeout()), this, SLOT(slotCheckStatus())); + + if(SMPPPDCSConfig::self()->useSmpppd()) { + m_timer->start(30000); + } else { + // we use 1 min interval, because it reflects + // the old connectionstatus plugin behaviour + m_timer->start(60000); + } + + slotCheckStatus(); + } +} + +bool SMPPPDCSPlugin::isOnline() const { + return m_onlineInquiry->isOnline(SMPPPDCSConfig::self()->useSmpppd()); +} + +void SMPPPDCSPlugin::slotCheckStatus() { + + // reread config to get changes + SMPPPDCSConfig::self()->readConfig(); + + if(SMPPPDCSConfig::self()->useSmpppd()) { + m_detectorSMPPPD->checkStatus(); + } else { + m_detectorNetstat->checkStatus(); + } +} + +void SMPPPDCSPlugin::setConnectedStatus( bool connected ) { + kdDebug(14312) << k_funcinfo << connected << endl; + + // We have to handle a few cases here. First is the machine is connected, and the plugin thinks + // we're connected. Then we don't do anything. Next, we can have machine connected, but plugin thinks + // we're disconnected. Also, machine disconnected, plugin disconnected -- we + // don't do anything. Finally, we can have the machine disconnected, and the plugin thinks we're + // connected. This mechanism is required so that we don't keep calling the connect/disconnect functions + // constantly. + + if ( connected && !m_pluginConnected ) { + // The machine is connected and plugin thinks we're disconnected + kdDebug(14312) << k_funcinfo << "Setting m_pluginConnected to true" << endl; + m_pluginConnected = true; + connectAllowed(); + kdDebug(14312) << k_funcinfo << "We're connected" << endl; + } else if ( !connected && m_pluginConnected ) { + // The machine isn't connected and plugin thinks we're connected + kdDebug(14312) << k_funcinfo << "Setting m_pluginConnected to false" << endl; + m_pluginConnected = false; + disconnectAllowed(); + kdDebug(14312) << k_funcinfo << "We're offline" << endl; + } +} + +void SMPPPDCSPlugin::connectAllowed() { + + QStringList list = SMPPPDCSConfig::self()->ignoredAccounts(); + + Kopete::AccountManager * m = Kopete::AccountManager::self(); + for(QPtrListIterator it(m->accounts()) + ; + it.current(); + ++it) { + +#ifndef NDEBUG + if(it.current()->inherits("Kopete::ManagedConnectionAccount")) { + kdDebug(14312) << k_funcinfo << "Account " << it.current()->protocol()->pluginId() + "_" + it.current()->accountId() << " is an managed account!" << endl; + } else { + kdDebug(14312) << k_funcinfo << "Account " << it.current()->protocol()->pluginId() + "_" + it.current()->accountId() << " is an unmanaged account!" << endl; + } +#endif + + if(!list.contains(it.current()->protocol()->pluginId() + "_" + it.current()-> + accountId())) { + it.current()->connect(); + } + } +} + +void SMPPPDCSPlugin::disconnectAllowed() { + + QStringList list = SMPPPDCSConfig::self()->ignoredAccounts(); + + Kopete::AccountManager * m = Kopete::AccountManager::self(); + for(QPtrListIterator it(m->accounts()) + ; + it.current(); + ++it) { + +#ifndef NDEBUG + if(it.current()->inherits("Kopete::ManagedConnectionAccount")) { + kdDebug(14312) << k_funcinfo << "Account " << it.current()->protocol()->pluginId() + "_" + it.current()->accountId() << " is an managed account!" << endl; + } else { + kdDebug(14312) << k_funcinfo << "Account " << it.current()->protocol()->pluginId() + "_" + it.current()->accountId() << " is an unmanaged account!" << endl; + } +#endif + + if(!list.contains(it.current()->protocol()->pluginId() + "_" + it.current()->accountId())) { + it.current()->disconnect(); + } + } +} + +QString SMPPPDCSPlugin::detectionMethod() const { + if(SMPPPDCSConfig::self()->useSmpppd()) { + return "smpppd"; + } else { + return "netstat"; + } +} + +/*! + \fn SMPPPDCSPlugin::smpppdServerChanged(const QString& server) + */ +void SMPPPDCSPlugin::smpppdServerChanged(const QString& server) { + + QString oldServer = SMPPPDCSConfig::self()->server().utf8(); + + if(oldServer != server) { + kdDebug(14312) << k_funcinfo << "Detected a server change" << endl; + m_detectorSMPPPD->smpppdServerChange(); + } +} + +void SMPPPDCSPlugin::aboutToUnload() { + + kdDebug(14312) << k_funcinfo << endl; + + if(m_timer) { + m_timer->stop(); + } + + emit readyForUnload(); +} + +#include "smpppdcsplugin.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/smpppdcs/smpppdcsplugin.h b/kopete/plugins/smpppdcs/smpppdcsplugin.h new file mode 100644 index 00000000..789d9c41 --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdcsplugin.h @@ -0,0 +1,109 @@ +/* + smpppdcsplugin.h + + Copyright (c) 2002-2003 by Chris Howells + Copyright (c) 2003 by Martijn Klingens + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef SMPPPDCSPLUGIN_H +#define SMPPPDCSPLUGIN_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "detector.h" +#include "iconnector.h" +#include "smpppdcsiface.h" + +#include "kopeteplugin.h" +#include "kopeteaccount.h" + +class QTimer; +class Detector; +class OnlineInquiry; + +/** + * @brief Plugin for the detection of an internet connection + * + * This plugin inquires either the smpppd or netstat + * for an existing internet connection and depending + * on that connects or disconnects all accounts. + * + * Therefore it should be enabled on dial up network + * connections. + * + * @author Chris Howells , Heiko Schäfer + */ +class SMPPPDCSPlugin : public Kopete::Plugin, public IConnector, virtual public SMPPPDCSIFace { + Q_OBJECT + SMPPPDCSPlugin(const SMPPPDCSPlugin&); + SMPPPDCSPlugin& operator=(const SMPPPDCSPlugin&); + +public: + /** + * @brief Creates an SMPPPDCSPlugin instance + */ + SMPPPDCSPlugin( QObject *parent, const char *name, const QStringList &args ); + + /** + * @brief Destroys an SMPPPDCSPlugin instance + */ + virtual ~SMPPPDCSPlugin(); + + // Implementation of DCOP iface + /** + * @brief Checks if we are online. + * @note This method is reserved for future use. Do not use at the moment! + * @return TRUE if online, otherwise FALSE + */ + virtual bool isOnline() const; + + /** + * @brief Sets the status in all allowed accounts. + * Allowed accounts are set in the config dialog of the plugin. + * + * @see SMPPPDCSPrefs + */ + virtual void setConnectedStatus( bool newStatus ); + + virtual QString detectionMethod() const; + + virtual void aboutToUnload(); + +public slots: + void smpppdServerChanged(const QString& server); + +private slots: + void slotCheckStatus(); + void allPluginsLoaded(); + +private: + + void connectAllowed(); + void disconnectAllowed(); + +private: + + Detector * m_detectorSMPPPD; + Detector * m_detectorNetstat; + Detector * m_detectorNetworkStatus; + bool m_pluginConnected; + QTimer * m_timer; + OnlineInquiry * m_onlineInquiry; +}; + +#endif /* SMPPPDCSPLUGIN_H */ + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/smpppdcs/smpppdcspreferences.cpp b/kopete/plugins/smpppdcs/smpppdcspreferences.cpp new file mode 100644 index 00000000..ddce3572 --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdcspreferences.cpp @@ -0,0 +1,187 @@ +/* + smpppdcspreferences.cpp + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#include +#include +#include +#include + +#include "kopeteaccount.h" +#include "kopeteprotocol.h" +#include "kopeteaccountmanager.h" + +#include "smpppdlocationwidget.h" +#include "smpppdcspreferences.h" +#include "smpppdcsprefsimpl.h" +#include "smpppdcsconfig.h" + +typedef KGenericFactory SMPPPDCSPreferencesFactory; +K_EXPORT_COMPONENT_FACTORY(kcm_kopete_smpppdcs, SMPPPDCSPreferencesFactory("kcm_kopete_smpppdcs")) + +SMPPPDCSPreferences::SMPPPDCSPreferences(QWidget * parent, const char * /* name */, const QStringList& args) + : KCModule(SMPPPDCSPreferencesFactory::instance(), parent, args), m_ui(NULL) { + + Kopete::AccountManager * manager = Kopete::AccountManager::self(); + (new QVBoxLayout(this))->setAutoAdd(true); + m_ui = new SMPPPDCSPrefs(this); + + for(QPtrListIterator it(manager->accounts()); it.current(); ++it) + { + QString protoName; + QRegExp rex("(.*)Protocol"); + + if(rex.search((*it)->protocol()->pluginId()) > -1) { + protoName = rex.cap(1); + } else { + protoName = (*it)->protocol()->pluginId(); + } + + if(it.current()->inherits("Kopete::ManagedConnectionAccount")) { + protoName += QString(", %1").arg(i18n("connection status is managed by Kopete")); + } + + QCheckListItem * cli = new QCheckListItem(m_ui->accountList, + (*it)->accountId() + " (" + protoName + ")", QCheckListItem::CheckBox); + cli->setPixmap(0, (*it)->accountIcon()); + + m_accountMapOld[cli->text(0)] = AccountPrivMap(FALSE, (*it)->protocol()->pluginId() + "_" + (*it)->accountId()); + m_accountMapCur[cli->text(0)] = AccountPrivMap(FALSE, (*it)->protocol()->pluginId() + "_" + (*it)->accountId());; + m_ui->accountList->insertItem(cli); + } + + connect(m_ui->accountList, SIGNAL(clicked(QListViewItem *)), this, SLOT(listClicked(QListViewItem *))); + + // connect for modified + connect(m_ui->useNetstat, SIGNAL(clicked()), this, SLOT(slotModified())); + connect(m_ui->useSmpppd, SIGNAL(clicked()), this, SLOT(slotModified())); + + connect(m_ui->SMPPPDLocation->server, SIGNAL(textChanged(const QString&)), this, SLOT(slotModified())); + connect(m_ui->SMPPPDLocation->port, SIGNAL(valueChanged(int)), this, SLOT(slotModified())); + connect(m_ui->SMPPPDLocation->Password, SIGNAL(textChanged(const QString&)), this, SLOT(slotModified())); + + load(); +} + +SMPPPDCSPreferences::~SMPPPDCSPreferences() { + delete m_ui; +} + +void SMPPPDCSPreferences::listClicked(QListViewItem * item) +{ + QCheckListItem * cli = dynamic_cast(item); + + if(cli->isOn() != m_accountMapCur[cli->text(0)].m_on) { + AccountMap::iterator itOld = m_accountMapOld.begin(); + AccountMap::iterator itCur; + bool change = FALSE; + + for(itCur = m_accountMapCur.begin(); itCur != m_accountMapCur.end(); ++itCur, ++itOld) { + if((*itCur).m_on != (*itOld).m_on){ + change = TRUE; + break; + } + } + emit KCModule::changed(change); + } + m_accountMapCur[cli->text(0)].m_on = cli->isOn(); +} + +void SMPPPDCSPreferences::defaults() +{ + QListViewItemIterator it(m_ui->accountList); + while(it.current()) { + QCheckListItem * cli = dynamic_cast(it.current()); + cli->setOn(FALSE); + ++it; + } + + SMPPPDCSConfig::self()->setDefaults(); + + m_ui->useNetstat->setChecked(SMPPPDCSConfig::self()->useNetstat()); + m_ui->useSmpppd->setChecked(SMPPPDCSConfig::self()->useSmpppd()); + + m_ui->SMPPPDLocation->server->setText(SMPPPDCSConfig::self()->server()); + m_ui->SMPPPDLocation->port->setValue(SMPPPDCSConfig::self()->port()); + m_ui->SMPPPDLocation->Password->setText(SMPPPDCSConfig::self()->password()); +} + +void SMPPPDCSPreferences::load() +{ + + SMPPPDCSConfig::self()->readConfig(); + + static QString rexStr = "^(.*) \\((.*)\\)"; + QRegExp rex(rexStr); + QStringList list = SMPPPDCSConfig::self()->ignoredAccounts(); + QListViewItemIterator it(m_ui->accountList); + while(it.current()) { + QCheckListItem * cli = dynamic_cast(it.current()); + if(rex.search(cli->text(0)) > -1) { + bool isOn = list.contains(rex.cap(2) + "Protocol_" + rex.cap(1)); + // m_accountMapOld[cli->text(0)].m_on = isOn; + m_accountMapCur[cli->text(0)].m_on = isOn; + cli->setOn(isOn); + } + ++it; + } + + m_ui->useNetstat->setChecked(SMPPPDCSConfig::self()->useNetstat()); + m_ui->useSmpppd->setChecked(SMPPPDCSConfig::self()->useSmpppd()); + + m_ui->SMPPPDLocation->server->setText(SMPPPDCSConfig::self()->server()); + m_ui->SMPPPDLocation->port->setValue(SMPPPDCSConfig::self()->port()); + m_ui->SMPPPDLocation->Password->setText(SMPPPDCSConfig::self()->password()); + + emit KCModule::changed(false); +} + +void SMPPPDCSPreferences::save() +{ + QStringList list; + QListViewItemIterator it(m_ui->accountList); + while(it.current()) { + + QCheckListItem * cli = dynamic_cast(it.current()); + if(cli->isOn()) { + list.append(m_accountMapCur[cli->text(0)].m_id); + } + + ++it; + } + + SMPPPDCSConfig::self()->setIgnoredAccounts(list); + + SMPPPDCSConfig::self()->setUseNetstat(m_ui->useNetstat->isChecked()); + SMPPPDCSConfig::self()->setUseSmpppd(m_ui->useSmpppd->isChecked()); + + SMPPPDCSConfig::self()->setServer(m_ui->SMPPPDLocation->server->text()); + SMPPPDCSConfig::self()->setPort(m_ui->SMPPPDLocation->port->value()); + SMPPPDCSConfig::self()->setPassword(m_ui->SMPPPDLocation->Password->text()); + + SMPPPDCSConfig::self()->writeConfig(); + + emit KCModule::changed(false); +} + +void SMPPPDCSPreferences::slotModified() { + emit KCModule::changed(true); +} + +#include "smpppdcspreferences.moc" diff --git a/kopete/plugins/smpppdcs/smpppdcspreferences.h b/kopete/plugins/smpppdcs/smpppdcspreferences.h new file mode 100644 index 00000000..8bbeff69 --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdcspreferences.h @@ -0,0 +1,77 @@ +/* + smpppdcspreferences.h + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef SMPPPDCSPREFERENCES_H +#define SMPPPDCSPREFERENCES_H + +#include + +class QListViewItem; + +class SMPPPDCSPrefs; + +class AccountPrivMap { +public: + AccountPrivMap(bool isOn = FALSE, const QString& id = QString::null) + : m_on(isOn), m_id(id) {} + bool m_on; + QString m_id; +}; + +/** + * @brief Module for the configuration of the smpppdcs-plugin + * + * @author Heiko Schäfer + */ +class SMPPPDCSPreferences : public KCModule { + Q_OBJECT + + SMPPPDCSPreferences(const SMPPPDCSPreferences&); + SMPPPDCSPreferences& operator=(const SMPPPDCSPreferences&); + +public: + typedef QMap AccountMap; + + /** + * @brief Creates an SMPPPDCSPreferences instance + */ + SMPPPDCSPreferences(QWidget * parent = 0, const char * name = 0, const QStringList &args = QStringList()); + + /** + * @brief Destroys an SMPPPDCSPreferences instance + */ + virtual ~SMPPPDCSPreferences(); + + virtual void load(); + virtual void save(); + virtual void defaults(); + +protected slots: + void listClicked(QListViewItem * item); + +private slots: + void slotModified(); + +protected: + + /// The UI class generated by the QT-designer + SMPPPDCSPrefs * m_ui; + + AccountMap m_accountMapOld; + AccountMap m_accountMapCur; +}; + +#endif diff --git a/kopete/plugins/smpppdcs/smpppdcsprefs.ui b/kopete/plugins/smpppdcs/smpppdcsprefs.ui new file mode 100644 index 00000000..067c55a3 --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdcsprefs.ui @@ -0,0 +1,284 @@ + +SMPPPDCSPrefsBase +Heiko Schaefer + + + SMPPPDCSPrefsBase + + + + 0 + 0 + 476 + 225 + + + + SMPPPDCS Preferences + + + + unnamed + + + + tabWidget + + + + tab + + + &Connection + + + + unnamed + + + + layout4 + + + + unnamed + + + 6 + + + 6 + + + + csMethod + + + Method of Connection Status Detection + + + + unnamed + + + 6 + + + 6 + + + + useNetstat + + + &netstat - Standard method of connection status detection + + + true + + + Uses the netstat command to find a gateway; suitable on dial-up computers + + + + + useSmpppd + + + smpppd - Ad&vanced method of connection status detection + + + Uses the smpppd on a gateway; suitable for a computer in a private network + + + + + autoCSLayout + + + + unnamed + + + + autoCSTest + + + &Try to Detect Automatically + + + Tries to find an appropriate connection method + + + + + spacer4 + + + Horizontal + + + Expanding + + + + 341 + 20 + + + + + + + + + + smpppdPrefs + + + false + + + Location of the SMPPPD + + + + unnamed + + + 6 + + + 6 + + + + SMPPPDLocation + + + + + + + spacer18 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + + + + + tab + + + Acco&unts + + + + unnamed + + + 6 + + + + spacer4_2 + + + Vertical + + + Fixed + + + + 20 + 6 + + + + + + toIgnoreLabel + + + Choose the accounts to ignore: + + + + + + Account + + + true + + + false + + + + accountList + + + true + + + LastColumn + + + + + + + + + + SMPPPDLocationWidget +
    smpppdlocationwidget.h
    + + 16 + 16 + + 1 + + 5 + 5 + 0 + 0 + + image0 +
    +
    + + + 89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b0000042c49444154388db5954f6c14551cc73fefcd7476b65bdaae4bb78bb5502a14d404e4801c88182d1c4c2c693da847400f9c24c68b878684238660e2b1e01f12c19493012ef2478c814412d354a46017a8a564bb6da5bbedccee767776e63d0ffb073751d483bfe49799974c3eeffb7ebf37df9fd05a530b2184040cc0042420aaf9a4d0d554800f045a6b256ae0e1e1e1d6bebebe838ee31c48a7d39b5cd7fd075e251cc7617272f2ded8d8d819cff33e0316819259537aead4a9839d5dd6d1784f91f55b0a94830242088404d304292bef68a89f520802a598fecddaa04f1a876f5c250c7c0a64cdeac686e33807e23d45e6b297c8b877f1831542614550b6599835c83c2a81b6786a75134faf2f1169f12997350881d9021d0903e06de0745d3160a6d3e94dbd5b0a64dcbb94b5831d0e3375ab892b1772dcf9790528543f8dd0d367b36768153b5e31503a0f1aecb004580b44ffac58baae8b1714f0833c7638cc8dab303a320f4822ab4c7a37c69196203de3319d5ce1c4d13c733331dedc67a129a154fd128401ab0616d55a130ac3d42d93d1913940d13fd0c9ee0183685c60da01c5421bd72f7a8c8efccef9afd374267ad93d642365be0636a0d28ec7600941d9e6f23917f0e97f23ce5bef35d19ec863da0ed9059b2be70bec196c66dfa10ec0e49b338f7017258651bf95021035c595429bb0903248fe52a2b5b595dd7b4d945cc2340cdca536be389ee3f67886c5798f773fe8e0dac508c989659277a2180da4ca4ff07821058b8b251445d63d6b13ed1098a6417e39cac85197dbe31962ab9bd9f1f22a226d45366f6d0620fdb08c900d281af6110284b20085b414861d905d88f2e52739ee8cbb8022143259d3dd84691730aa2d52da441a8de0c6958068870022a41e9629ad3473fd3b8fdbe319dadb9b4924da994d2d716c7896fbe35152f78b48245d6b2da4507faf582be8eaf159b721cc837b05ae7debb1f79d08cb8b515edad942a22bc4b1c33eb3d34b1c797f06af90a72d16e2f96d9a74aa11dca8586b222d01af0fb60070f6c402d72f15d97f28c6f6d7027a5f5ce6c3233dc4e2ede496b278be4fff608cee8d3e1add806aeca51094cbb06397c1ecc328e746537c7e3ccdb5cb1136bf60635882d4d41c6ec6836ab37efa214f72208ed9f4d7cdd38ee310280542e38b1c43fb6de26b3672e1ec3cc99bcb246f66a938a3241ab3e91f7c861fbf77710b1e5e49915bae974203ba0e9e9c9cbc373d6d6d305a040a89c2a77f50b27d5782bbbf7acccf28349235dd16cf6dd374f7295e1de8a45c02d37499182b01cc0201a085d61a2144d8b2ac8fb6ed340e77240c4261890e04c250185262546d534a032154b59e0ad394e41c98182bf268ce6721ed9f064e0253356f6da2e24c1f030f783c15fe6da680af8021602bd051532ca9b8521488559f61aa86c29343578fbf0264a94c906c7d3409214c20043457a116ff6de6795578012889ff6b98fe016ea0ce1c6a2573410000000049454e44ae426082 + + + + tabWidget + useNetstat + autoCSTest + useSmpppd + accountList + + + + kpushbutton.h + smpppdlocationwidget.h + klistview.h + +
    diff --git a/kopete/plugins/smpppdcs/smpppdcsprefsimpl.cpp b/kopete/plugins/smpppdcs/smpppdcsprefsimpl.cpp new file mode 100644 index 00000000..5a834c97 --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdcsprefsimpl.cpp @@ -0,0 +1,165 @@ +/* + smpppdcsprefsimpl.cpp + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kopetepluginmanager.h" + +#include "../smpppdcsplugin.h" + +#include "smpppdlocationwidget.h" +#include "smpppdcsprefsimpl.h" +#include "smpppdsearcher.h" + +SMPPPDCSPrefs::SMPPPDCSPrefs(QWidget* parent, const char* name, WFlags fl) + : SMPPPDCSPrefsBase(parent, name, fl), m_plugin(NULL), m_scanProgressDlg(NULL), m_curSearcher(NULL) { + + // search for our main-plugin instance + Kopete::Plugin * p = Kopete::PluginManager::self()->plugin("kopete_smpppdcs"); + if(p) { + m_plugin = static_cast(p); + } + + // signals and slots connections + connect(useNetstat, SIGNAL(toggled(bool)), this, SLOT(disableSMPPPDSettings())); + connect(useSmpppd, SIGNAL(toggled(bool)), this, SLOT(enableSMPPPDSettings())); + connect(autoCSTest, SIGNAL(clicked()), this, SLOT(determineCSType())); + + if(m_plugin) { + connect((QObject *)SMPPPDLocation->server, SIGNAL(textChanged(const QString&)), + m_plugin, SLOT(smpppdServerChanged(const QString&))); + } + + // if netstat is NOT available, disable the option and set to SMPPPD + if(KStandardDirs::findExe("netstat") == QString::null) { + autoCSTest->setEnabled(FALSE); + useNetstat->setEnabled(FALSE); + useNetstat->setChecked(FALSE); + useSmpppd->setChecked(TRUE); + } +} + +SMPPPDCSPrefs::~SMPPPDCSPrefs() { + delete m_scanProgressDlg; +} + +void SMPPPDCSPrefs::determineCSType() { + + // while we search, we'll disable the button + autoCSTest->setEnabled(false); + //kapp->processEvents(); + + /* broadcast network for a smpppd. + If one is available set to smpppd method */ + + SMPPPDSearcher searcher; + m_curSearcher = &searcher; + + connect(&searcher, SIGNAL(smpppdFound(const QString&)), this, SLOT(smpppdFound(const QString&))); + connect(&searcher, SIGNAL(smpppdNotFound()), this, SLOT(smpppdNotFound())); + connect(&searcher, SIGNAL(scanStarted(uint)), this, SLOT(scanStarted(uint))); + connect(&searcher, SIGNAL(scanProgress(uint)), this, SLOT(scanProgress(uint))); + connect(&searcher, SIGNAL(scanFinished()), this, SLOT(scanFinished())); + + searcher.searchNetwork(); + m_curSearcher = NULL; +} + +void SMPPPDCSPrefs::scanStarted(uint total) { + kdDebug(14312) << k_funcinfo << "Scanning for a SMPPPD started. Will scan " << total << " IPs" << endl; + + // setup the scanProgress Dialog + if(!m_scanProgressDlg) { + m_scanProgressDlg = new KProgressDialog(this, 0, i18n("Searching"), i18n("Searching for a SMPPPD on the local network..."), TRUE); + m_scanProgressDlg->setAutoClose(TRUE); + m_scanProgressDlg->setAllowCancel(TRUE); + m_scanProgressDlg->setMinimumDuration(2000); + + connect(m_scanProgressDlg, SIGNAL(cancelClicked()), this, SLOT(cancelScanning())); + } + m_scanProgressDlg->progressBar()->setTotalSteps(total); + m_scanProgressDlg->progressBar()->setProgress(0); + m_scanProgressDlg->show(); +} + +void SMPPPDCSPrefs::scanProgress(uint cur) { + m_scanProgressDlg->progressBar()->setProgress(cur); + kapp->processEvents(); +} + +void SMPPPDCSPrefs::cancelScanning() { + kdDebug(14312) << k_funcinfo << endl; + Q_ASSERT(m_curSearcher); + m_curSearcher->cancelSearch(); +} + +void SMPPPDCSPrefs::smpppdFound(const QString& host) { + kdDebug(14312) << k_funcinfo << endl; + + QString myHost = host; + + // try to get the domain name + struct in_addr addr; + if(inet_aton(host.ascii(), &addr)) { + struct hostent * hostEnt = gethostbyaddr(&addr.s_addr, sizeof(addr.s_addr), AF_INET); + if(hostEnt) { + myHost = hostEnt->h_name; + } else { +#ifndef NDEBUG + switch(h_errno) { + case HOST_NOT_FOUND: + kdDebug(14312) << k_funcinfo << "No such host is known in the database." << endl; + break; + case TRY_AGAIN: + kdDebug(14312) << k_funcinfo << "Couldn't contact DNS server." << endl; + break; + case NO_RECOVERY: + kdDebug(14312) << k_funcinfo << "A non-recoverable error occurred." << endl; + break; + case NO_ADDRESS: + kdDebug(14312) << k_funcinfo << "The host database contains an entry for the name, but it doesn't have an associated Internet address." << endl; + break; + } +#endif + + } + } + + SMPPPDLocation->setServer(myHost); + useNetstat->setChecked(false); + useSmpppd->setChecked(true); + autoCSTest->setEnabled(true); +} + +void SMPPPDCSPrefs::smpppdNotFound() { + kdDebug(14312) << k_funcinfo << endl; + useNetstat->setChecked(true); + useSmpppd->setChecked(false); + autoCSTest->setEnabled(true); +} + +#include "smpppdcsprefsimpl.moc" diff --git a/kopete/plugins/smpppdcs/smpppdcsprefsimpl.h b/kopete/plugins/smpppdcs/smpppdcsprefsimpl.h new file mode 100644 index 00000000..181ba9fa --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdcsprefsimpl.h @@ -0,0 +1,76 @@ +/* + smpppdcsprefsimpl.h + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef SMPPPDCSPREFSIMPL_H +#define SMPPPDCSPREFSIMPL_H + +#include + +#include + +#include "smpppdcsprefs.h" + +class SMPPPDCSPlugin; +class SMPPPDSearcher; + +/** +@author Heiko Schäfer +*/ +class SMPPPDCSPrefs : public SMPPPDCSPrefsBase +{ + Q_OBJECT + + SMPPPDCSPrefs(const SMPPPDCSPrefs&); + SMPPPDCSPrefs& operator=(const SMPPPDCSPrefs&); + +public: + + SMPPPDCSPrefs(QWidget* parent, const char* name = 0, WFlags fl = 0); + ~SMPPPDCSPrefs(); + +signals: + void foundSMPPPD(bool found); + +protected slots: + void enableSMPPPDSettings(); + void disableSMPPPDSettings(); + void determineCSType(); + void smpppdFound(const QString & host); + void smpppdNotFound(); + void scanStarted(uint total); + void scanProgress(uint cur); + void scanFinished(); + void cancelScanning(); + +private: + SMPPPDCSPlugin * m_plugin; + KProgressDialog * m_scanProgressDlg; + SMPPPDSearcher * m_curSearcher; +}; + +inline void SMPPPDCSPrefs::enableSMPPPDSettings() { + smpppdPrefs->setEnabled(true); +} + +inline void SMPPPDCSPrefs::disableSMPPPDSettings() { + smpppdPrefs->setEnabled(false); +} + +inline void SMPPPDCSPrefs::scanFinished() { + m_scanProgressDlg->hide(); +} + +#endif diff --git a/kopete/plugins/smpppdcs/smpppdlocationui.ui b/kopete/plugins/smpppdcs/smpppdlocationui.ui new file mode 100644 index 00000000..0424f6f6 --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdlocationui.ui @@ -0,0 +1,149 @@ + +SMPPPDLocationWidgetBase + + + SMPPPDLocationWidgetBase + + + + 0 + 0 + 365 + 167 + + + + SMPPPDLocation + + + + unnamed + + + + textLabel1 + + + Ser&ver: + + + server + + + + + server + + + 4 + + + localhost + + + 256 + + + The server on which the SMPPPD is running + + + + + textLabel2 + + + P&ort: + + + port + + + + + layout14 + + + + unnamed + + + + port + + + 4 + + + 3185 + + + 0 + + + The port on which the SMPPPD is running on + + + + + textLabel3 + + + Default: 3185 + + + + + spacer15 + + + Horizontal + + + Expanding + + + + 130 + 20 + + + + + + + + m_passwordLabel + + + Pass&word: + + + Password + + + + + Password + + + 4 + + + Password + + + The password to authenticate with the smpppd + + + + + + + + + klineedit.h + knuminput.h + knuminput.h + klineedit.h + + diff --git a/kopete/plugins/smpppdcs/smpppdlocationwidget.cpp b/kopete/plugins/smpppdcs/smpppdlocationwidget.cpp new file mode 100644 index 00000000..b20509d9 --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdlocationwidget.cpp @@ -0,0 +1,30 @@ +/* + smpppdlocationwidget.cpp + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include + +#include "smpppdlocationwidget.h" + +SMPPPDLocationWidget::SMPPPDLocationWidget(QWidget* parent, const char* name, WFlags fl) + : SMPPPDLocationWidgetBase(parent, name, fl) {} + +SMPPPDLocationWidget::~SMPPPDLocationWidget() {} + +void SMPPPDLocationWidget::setServer(const QString& serv) { + server->setText(serv); +} + +#include "smpppdlocationwidget.moc" diff --git a/kopete/plugins/smpppdcs/smpppdlocationwidget.h b/kopete/plugins/smpppdcs/smpppdlocationwidget.h new file mode 100644 index 00000000..00fa9157 --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdlocationwidget.h @@ -0,0 +1,39 @@ +/* + smpppdlocationwidget.h + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef SMPPPDLOCATIONWIDGET_H +#define SMPPPDLOCATIONWIDGET_H + +#include "smpppdlocationui.h" + +/** + @author Heiko Schäfer +*/ +class SMPPPDLocationWidget : public SMPPPDLocationWidgetBase +{ + Q_OBJECT + + SMPPPDLocationWidget(const SMPPPDLocationWidget&); + SMPPPDLocationWidget& operator=(const SMPPPDLocationWidget&); + +public: + SMPPPDLocationWidget(QWidget* parent = 0, const char* name = 0, WFlags fl = 0); + ~SMPPPDLocationWidget(); + + void setServer(const QString& serv); +}; + +#endif diff --git a/kopete/plugins/smpppdcs/smpppdsearcher.cpp b/kopete/plugins/smpppdcs/smpppdsearcher.cpp new file mode 100644 index 00000000..6ee9c878 --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdsearcher.cpp @@ -0,0 +1,189 @@ +/* + smpppdsearcher.h + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include +#include + +#include +#include + +#include "smpppdclient.h" +#include "smpppdsearcher.h" + +SMPPPDSearcher::SMPPPDSearcher() + : m_cancelSearchNow(FALSE), + m_procIfconfig(NULL), +m_procNetstat(NULL) {} + +SMPPPDSearcher::~SMPPPDSearcher() { + delete m_procIfconfig; + delete m_procNetstat; +} + +/*! + \fn SMPPPDSearcher::searchNetwork() const + */ +void SMPPPDSearcher::searchNetwork() { + kdDebug(14312) << k_funcinfo << endl; + + // the first point to search is localhost + if(!scan("127.0.0.1", "255.0.0.0")) { + + m_procNetstat = new KProcess; + m_procNetstat->setEnvironment("LANG", "C"); // we want to force english output + + *m_procNetstat << "/bin/netstat" << "-rn"; + connect(m_procNetstat, SIGNAL(receivedStdout(KProcess *,char *,int)), this, SLOT(slotStdoutReceivedNetstat(KProcess *,char *,int))); + if(!m_procNetstat->start(KProcess::Block, KProcess::Stdout)) { + kdDebug(14312) << k_funcinfo << "Couldn't execute /sbin/netstat -rn" << endl << "Perhaps the package net-tools isn't installed." << endl; + + emit smpppdNotFound(); + } + + delete m_procNetstat; + m_procNetstat = NULL; + } +} + +/*! + \fn SMPPPDSearcher::slotStdoutReceived(KProcess * proc, char * buf, int len) + */ +void SMPPPDSearcher::slotStdoutReceivedIfconfig(KProcess * /* proc */, char * buf, int len) { + kdDebug(14312) << k_funcinfo << endl; + + QString myBuf = QString::fromLatin1(buf,len); + QRegExp rex("^[ ]{10}.*inet addr:([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}).*Mask:([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})"); + // tokenize the string into lines + QStringList toks = QStringList::split("\n", myBuf); + for(QStringList::size_type i = 0; i < toks.count(); i++) { + if(rex.exactMatch(toks[i])) { + if(scan(rex.cap(1), rex.cap(2))) { + return; + } + } + } + + emit smpppdNotFound(); +} +void SMPPPDSearcher::slotStdoutReceivedNetstat(KProcess * /* proc */, char * buf, int len) { + kdDebug(14312) << k_funcinfo << endl; + + QRegExp rexGW(".*\\n0.0.0.0[ ]*([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}).*"); + QString myBuf = QString::fromLatin1(buf,len); + + if(!(rexGW.exactMatch(myBuf) && scan(rexGW.cap(1), "255.255.255.255"))) { + // if netstat -r found no gateway we search the network + m_procIfconfig = new KProcess; + m_procIfconfig->setEnvironment("LANG", "C"); // we want to force english output + + *m_procIfconfig << "/sbin/ifconfig"; + connect(m_procIfconfig, SIGNAL(receivedStdout(KProcess *,char *,int)), this, SLOT(slotStdoutReceivedIfconfig(KProcess *,char *,int))); + if(!m_procIfconfig->start(KProcess::Block, KProcess::Stdout)) { + kdDebug(14312) << k_funcinfo << "Couldn't execute /sbin/ifconfig" << endl << "Perhaps the package net-tools isn't installed." << endl; + + emit smpppdNotFound(); + } + + delete m_procIfconfig; + m_procIfconfig = NULL; + } +} + +/*! + \fn SMPPPDSearcher::scan() const + */ +bool SMPPPDSearcher::scan(const QString& ip, const QString& mask) { + kdDebug(14312) << k_funcinfo << "Scanning " << ip << "/" << mask << "..." << endl; + + SMPPPD::Client client; + + if(ip == "127.0.0.1") { // if localhost, we only scan this one host + if(client.connect(ip, 3185)) { + client.disconnect(); + emit smpppdFound(ip); + return true; + } + + return false; + } + + uint min_range = 0; + uint max_range = 255; + + // calculate ip range (only last mask entry) + QRegExp lastRex("([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})"); + if(lastRex.exactMatch(ip)) { + + uint lastWordIP = lastRex.cap(4).toUInt(); + + QStringList ipToks; + for(int i = 1; i < 5; i++) { + ipToks.push_back(lastRex.cap(i)); + } + + if(lastRex.exactMatch(mask)) { + uint lastWordMask = lastRex.cap(4).toUInt(); + + if(lastWordMask == 0) { + kdDebug(14312) << k_funcinfo << "IP-Range: " << ipToks[0] << "." << ipToks[1] << "." << ipToks[2] << ".0 - " << ipToks[0] << "." << ipToks[1] << "." << ipToks[2] << ".255" << endl; + max_range = 255; + } else if(lastWordMask == 255) { + min_range = max_range = lastWordIP; + } else { + kdDebug(14312) << k_funcinfo << "IP-Range: " << ipToks[0] << "." << ipToks[1] << "." << ipToks[2] << ".0 - " << ipToks[0] << "." << ipToks[1] << "." << ipToks[2] << "." << lastWordMask << endl; + max_range = lastWordMask; + } + } + + uint range = max_range - min_range; + m_cancelSearchNow = FALSE; + if(range > 1) { + emit scanStarted(max_range); + } + for(uint i = min_range; i <= max_range; i++) { + if(m_cancelSearchNow) { + if(range > 1) { + emit scanFinished(); + } + break; + } + if(range > 1) { + emit scanProgress(i); + } + + if(client.connect(QString(ipToks[0] + "." + ipToks[1] + "." + ipToks[2] + "." + QString::number(i)), 3185)) { + client.disconnect(); + emit smpppdFound(ip); + if(range > 1) { + emit scanFinished(); + } + return true; + } +#ifndef NDEBUG + else { + kdDebug(14312) << k_funcinfo << "No smpppd found at " << QString(ipToks[0] + "." + ipToks[1] + "." + ipToks[2] + "." + QString::number(i)) << endl; + } +#endif + } + if(range > 1) { + emit scanFinished(); + } + } + + return false; +} + +#include "smpppdsearcher.moc" diff --git a/kopete/plugins/smpppdcs/smpppdsearcher.h b/kopete/plugins/smpppdcs/smpppdsearcher.h new file mode 100644 index 00000000..af36637d --- /dev/null +++ b/kopete/plugins/smpppdcs/smpppdsearcher.h @@ -0,0 +1,102 @@ +/* + smpppdsearcher.h + + Copyright (c) 2004-2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + + +#ifndef SMPPPDSEARCHER_H +#define SMPPPDSEARCHER_H + +#include + +class KProcess; + +/** + * @brief Searches a network for a smpppd + * + * @todo Use of the SLP to find the smpppd + * @author Heiko Schäfer + */ +class SMPPPDSearcher : public QObject { + Q_OBJECT + + SMPPPDSearcher(const SMPPPDSearcher&); + SMPPPDSearcher& operator=(const SMPPPDSearcher&); + +public: + /** + * @brief Creates an SMPPPDSearcher instance + */ + SMPPPDSearcher(); + + /** + * @brief Destroys an SMPPPDSearcher instance + */ + ~SMPPPDSearcher(); + + /** + * @brief Triggers a network scan to find a smpppd + * @see smpppdFound + * @see smpppdNotFound + */ + void searchNetwork(); + + void cancelSearch(); + +protected: + /** + * @brief Scans a network for a smpppd + * + * Scans a network for a smpppd described by + * ip and mask. + * + * @param ip the ntwork ip + * @param mask the network mask + * @return TRUE if an smpppd was found + */ + bool scan(const QString& ip, const QString& mask); + +signals: + /** + * @brief A smppd was found + * + * @param host the host there the smpppd was found + */ + void smpppdFound(const QString& host); + + /** + * @brief No smpppd was found + */ + void smpppdNotFound(); + + void scanStarted(uint total); + void scanProgress(uint cur); + void scanFinished(); + +protected slots: + void slotStdoutReceivedIfconfig(KProcess * proc, char * buf, int len); + void slotStdoutReceivedNetstat (KProcess * proc, char * buf, int len); + +private: + bool m_cancelSearchNow; + KProcess * m_procIfconfig; + KProcess * m_procNetstat; +}; + +inline void SMPPPDSearcher::cancelSearch() { + m_cancelSearchNow = TRUE; +} + +#endif + diff --git a/kopete/plugins/smpppdcs/unittest/Makefile.am b/kopete/plugins/smpppdcs/unittest/Makefile.am new file mode 100644 index 00000000..9694bff9 --- /dev/null +++ b/kopete/plugins/smpppdcs/unittest/Makefile.am @@ -0,0 +1,15 @@ +INCLUDES = -I$(top_srcdir)/src $(all_includes) -I../libsmpppdclient +METASOURCES = AUTO + + +check_PROGRAMS = smpppdcstests + +smpppdcstests_SOURCES = main.cpp clienttest.cpp +smpppdcstests_LDFLAGS = $(KDE_RPATH) $(all_libraries) +smpppdcstests_LDADD = ../libsmpppdclient/libsmpppdclient.la -lkunittestgui + +noinst_HEADERS = clienttest.h + +check: + kunittest ./smpppdcstests ClientTest + diff --git a/kopete/plugins/smpppdcs/unittest/clienttest.cpp b/kopete/plugins/smpppdcs/unittest/clienttest.cpp new file mode 100644 index 00000000..5affd83c --- /dev/null +++ b/kopete/plugins/smpppdcs/unittest/clienttest.cpp @@ -0,0 +1,121 @@ +/* + clienttest.cpp + + Copyright (c) 2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include "smpppdclient.h" + +#include "clienttest.h" + +ClientTest::ClientTest(const char * name) + : KUnitTest::SlotTester(name) {} + +ClientTest::~ClientTest() {} + +void ClientTest::testInitIsReady() { + SMPPPD::Client c; + CHECK(c.isReady(), false); +} + +void ClientTest::testAfterConnectIsReady() { + SMPPPD::Client c; + if(c.connect("warwar", 3185)) { + CHECK(c.isReady(), true); + } else { + SKIP("Test skipped because no smpppd at warwar:3185"); + } +} + +void ClientTest::testConnect() { + SMPPPD::Client c; + CHECK(c.connect("warwar", 3185), true); + CHECK(c.connect("localhost", 3185), false); +} + +void ClientTest::testCommunicationBeforeConnect() { + SMPPPD::Client c; + QStringList l = c.getInterfaceConfigurations(); + + CHECK(l.count() == 0, true); + CHECK(c.statusInterface("ifcfg0"), false); +} + +void ClientTest::testServerIDBeforeConnect() { + SMPPPD::Client c; + CHECK(c.serverID(), QString::null); +} + +void ClientTest::testServerVersionBeforeConnect() { + SMPPPD::Client c; + CHECK(c.serverVersion(), QString::null); +} + +void ClientTest::testCommunicationAfterConnect() { + SMPPPD::Client c; + if(c.connect("warwar", 3185)) { + CHECK(c.getInterfaceConfigurations().count() > 0, true); + } else { + SKIP("Test skipped because no smpppd at warwar:3185"); + } +} + +void ClientTest::testServerIDAfterConnect() { + SMPPPD::Client c; + if(c.connect("warwar", 3185)) { + CHECK(c.serverID().isEmpty(), false); + } else { + SKIP("Test skipped because no smpppd at warwar:3185"); + } +} + +void ClientTest::testServerVersionAfterConnect() { + SMPPPD::Client c; + if(c.connect("warwar", 3185)) { + CHECK(c.serverVersion().isEmpty(), false); + } else { + SKIP("Test skipped because no smpppd at warwar:3185"); + } +} + +void ClientTest::testCommunicationAfterDisconnect() { + SMPPPD::Client c; + if(c.connect("warwar", 3185)) { + c.disconnect(); + CHECK(c.getInterfaceConfigurations().count() == 0, true); + } else { + SKIP("Test skipped because no smpppd at warwar:3185"); + } +} + +void ClientTest::testServerIDAfterDisconnect() { + SMPPPD::Client c; + if(c.connect("warwar", 3185)) { + c.disconnect(); + CHECK(c.serverID(), QString::null); + } else { + SKIP("Test skipped because no smpppd at warwar:3185"); + } +} + +void ClientTest::testServerVersionAfterDisconnect() { + SMPPPD::Client c; + if(c.connect("warwar", 3185)) { + c.disconnect(); + CHECK(c.serverVersion(), QString::null); + } else { + SKIP("Test skipped because no smpppd at warwar:3185"); + } +} + +#include "clienttest.moc" diff --git a/kopete/plugins/smpppdcs/unittest/clienttest.h b/kopete/plugins/smpppdcs/unittest/clienttest.h new file mode 100644 index 00000000..5db7ef7b --- /dev/null +++ b/kopete/plugins/smpppdcs/unittest/clienttest.h @@ -0,0 +1,50 @@ +/* + clienttest.h + + Copyright (c) 2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef CLIENTTEST_H +#define CLIENTTEST_H + +#include + +/** + @author Heiko Schäfer +*/ +class ClientTest : public KUnitTest::SlotTester { + Q_OBJECT + + ClientTest(const ClientTest&); + ClientTest& operator=(const ClientTest&); + +public: + ClientTest(const char * name = 0); + virtual ~ClientTest(); + +private slots: + void testInitIsReady(); + void testAfterConnectIsReady(); + void testConnect(); + void testCommunicationBeforeConnect(); + void testServerIDBeforeConnect(); + void testServerVersionBeforeConnect(); + void testCommunicationAfterConnect(); + void testServerIDAfterConnect(); + void testServerVersionAfterConnect(); + void testCommunicationAfterDisconnect(); + void testServerIDAfterDisconnect(); + void testServerVersionAfterDisconnect(); +}; + +#endif diff --git a/kopete/plugins/smpppdcs/unittest/main.cpp b/kopete/plugins/smpppdcs/unittest/main.cpp new file mode 100644 index 00000000..ec14489b --- /dev/null +++ b/kopete/plugins/smpppdcs/unittest/main.cpp @@ -0,0 +1,45 @@ +/* + main.cpp + + Copyright (c) 2006 by Heiko Schaefer + + Kopete (c) 2002-2006 by the Kopete developers + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include +#include + +#include "clienttest.h" + +static const char description[] = I18N_NOOP("SMPPPDClientTests"); +static const char version[] = "0.1"; +static KCmdLineOptions options[] = { KCmdLineLastOption }; + +int main( int argc, char** argv ) { + KAboutData about("SMPPPDClientTests", I18N_NOOP("SMPPPDClientTests"), version, description, + KAboutData::License_BSD, "(C) 2006 Heiko Schäfer", 0, 0, "heiko@rangun.de"); + + KCmdLineArgs::init(argc, argv, &about); + KCmdLineArgs::addCmdLineOptions(options); + KApplication app; + + KUnitTest::Runner::registerTester("ClientTest", new ClientTest); + + KUnitTest::RunnerGUI runner(0); + runner.show(); + app.setMainWidget(&runner); + + return app.exec(); +} diff --git a/kopete/plugins/statistics/Makefile.am b/kopete/plugins/statistics/Makefile.am new file mode 100644 index 00000000..b6aa7812 --- /dev/null +++ b/kopete/plugins/statistics/Makefile.am @@ -0,0 +1,21 @@ +METASOURCES = AUTO + +INCLUDES = $(KOPETE_INCLUDES) $(all_includes) + +SUBDIRS = sqlite + +kde_module_LTLIBRARIES = kopete_statistics.la + +kopete_statistics_la_SOURCES = statisticsplugin.cpp statisticsdb.cpp statisticsdialog.cpp statisticswidget.ui statisticscontact.cpp statisticsdcopiface.skel + +kopete_statistics_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kopete_statistics_la_LIBADD = ../../libkopete/libkopete.la sqlite/libsqlite.la + +service_DATA = kopete_statistics.desktop +servicedir = $(kde_servicesdir) + +mydatadir = $(kde_datadir)/kopete_statistics +mydata_DATA = statisticsui.rc + +mydatadirimagesdir = $(kde_datadir)/kopete/pics/statistics +mydatadirimages_DATA = images/blue.png images/navy.png images/black.png images/gray.png diff --git a/kopete/plugins/statistics/TODO b/kopete/plugins/statistics/TODO new file mode 100644 index 00000000..dba76062 --- /dev/null +++ b/kopete/plugins/statistics/TODO @@ -0,0 +1,3 @@ +* A database cleaner. + - If theyre are two Online status following themselves with small times betweens them (less than 1 minute ?), merge them. + \ No newline at end of file diff --git a/kopete/plugins/statistics/images/black.png b/kopete/plugins/statistics/images/black.png new file mode 100644 index 00000000..b8344f01 Binary files /dev/null and b/kopete/plugins/statistics/images/black.png differ diff --git a/kopete/plugins/statistics/images/blue.png b/kopete/plugins/statistics/images/blue.png new file mode 100644 index 00000000..e58e283d Binary files /dev/null and b/kopete/plugins/statistics/images/blue.png differ diff --git a/kopete/plugins/statistics/images/gray.png b/kopete/plugins/statistics/images/gray.png new file mode 100644 index 00000000..49ba3af4 Binary files /dev/null and b/kopete/plugins/statistics/images/gray.png differ diff --git a/kopete/plugins/statistics/images/navy.png b/kopete/plugins/statistics/images/navy.png new file mode 100644 index 00000000..0038d5e1 Binary files /dev/null and b/kopete/plugins/statistics/images/navy.png differ diff --git a/kopete/plugins/statistics/kopete_statistics.desktop b/kopete/plugins/statistics/kopete_statistics.desktop new file mode 100644 index 00000000..239e5320 --- /dev/null +++ b/kopete/plugins/statistics/kopete_statistics.desktop @@ -0,0 +1,111 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=statistics +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_statistics +X-KDE-PluginInfo-Author=Marc Cramdal +X-KDE-PluginInfo-Email=marc.cramdal@yahoo.fr +X-KDE-PluginInfo-Name=kopete_statistics +X-KDE-PluginInfo-Version=0.1 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Statistics +Name[be]=СтатыÑтыка +Name[bg]=СтатиÑтика +Name[bn]=পরিসংখà§à¦¯à¦¾à¦¨ +Name[br]=Stadegoù +Name[bs]=Statistike +Name[ca]=Estadístiques +Name[cs]=Statistika +Name[cy]=Ystadegau +Name[da]=Statistik +Name[de]=Statistiken +Name[el]=Στατιστικά +Name[eo]=Statistikoj +Name[es]=Estadísticas +Name[et]=Statistika +Name[eu]=Estatistikak +Name[fa]=آمار +Name[fi]=Tilastot +Name[fr]=Statistiques +Name[ga]=Staitistic +Name[gl]=Estatísticas +Name[he]=סטטיסטיקה +Name[hu]=Statisztika +Name[is]=Tölfræði +Name[it]=Statistiche +Name[ja]=統計 +Name[ka]=სტáƒáƒ¢áƒ˜áƒ¡áƒ¢áƒ˜áƒ™áƒ +Name[kk]=СтатиÑтика +Name[km]=ស្ážáž·ážáž· +Name[lt]=Statistika +Name[nb]=Statistikk +Name[nds]=Statistik +Name[ne]=तशà¥à¤¯à¤¾à¤™à¥à¤• +Name[nl]=Statistieken +Name[nn]=Statistikk +Name[pa]=ਅੰਕੜੇ +Name[pl]=Statystyki +Name[pt]=Estatísticas +Name[pt_BR]=Estatísticas +Name[ro]=Statistici +Name[ru]=СтатиÑтика +Name[sk]=Å tatistiky +Name[sl]=Statistika +Name[sr]=СтатиÑтика +Name[sr@Latn]=Statistika +Name[sv]=Statistik +Name[tr]=Ä°statistlikler +Name[uk]=СтатиÑтика +Name[uz]=Statistika +Name[uz@cyrillic]=СтатиÑтика +Name[zh_CN]=统计 +Name[zh_HK]=統計 +Name[zh_TW]=統計 +Comment=Gather some meaningful statistics +Comment[bg]=ПриÑтавка за обобщаваща ÑтатиÑтика +Comment[bn]=কিছৠঅরà§à¦¥à¦ªà§‚রà§à¦£ পরিসংখà§à¦¯à¦¾à¦¨ সমবেত করে +Comment[bs]=Prikuplja neke interesantne statistike +Comment[ca]=Obté estadístiques +Comment[cs]=ShromažÄuje užiteÄné statistiky +Comment[da]=Indsamling af noget meningsfuld statistik +Comment[de]=Einige aussagekräftige Statistiken sammeln +Comment[el]=Συλλογή μεÏικών σημαντικών στατιστικών +Comment[es]=Recoge algunas estadí­sticas significativas +Comment[et]=Veidi statistikat, millest võib kasu olla +Comment[eu]=Estatistika esanguratsuak bildu +Comment[fa]=جمع‌آوری بعضی از آمارهای با معنی +Comment[fi]=Kerää hyödyllisiä tilastoja +Comment[fr]=Récupération de quelques statistiques utiles +Comment[he]=מלקט סטטיסטיקה בעלת משמעות +Comment[hu]=Statisztikai adatok gyűjtése +Comment[is]=Safna saman nokkrum gagnlegum tölfræði upplýsingum +Comment[it]=Raccoglie delle statistiche significative +Comment[ja]=有æ„義ãªçµ±è¨ˆãƒ‡ãƒ¼ã‚¿ã‚’åŽé›†ã—ã¾ã™ +Comment[ka]=სტáƒáƒ¢áƒ˜áƒ¡áƒ¢áƒ˜áƒ™áƒ˜áƒ¡ შეგრáƒáƒ•áƒ”ბრ+Comment[kk]=Кейбір маңызды ÑтатиÑтиканы жинақтау +Comment[km]=ប្រមែប្រមូល​ស្ážáž·ážáž·â€‹ážŸáŸ†ážáž¶áž“់ៗ​មួយ​ចំនួន +Comment[lt]=Rinkti prasmingÄ… statistikÄ… +Comment[nb]=Samle noen opplysninger med mening +Comment[nds]=En poor sinnvulle Statistiken sammeln +Comment[ne]=केही अरà¥à¤¥à¤ªà¥‚रà¥à¤£ तशà¥à¤¯à¤¾à¤™à¥à¤• भेला गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Verzamel wat betekenisvolle statistieken +Comment[nn]=Samla nokre opplysningar med meining +Comment[pl]=Zbieranie niektórych znaczÄ…cych statystyk +Comment[pt]=Recolher algumas estatísticas relevantes +Comment[pt_BR]=Coleta estatísticas úteis +Comment[ru]=Собрать некоторые ÑтатиÑтичеÑкие данные +Comment[sk]=Zozbiera niektoré významné Å¡tatistiky +Comment[sl]=Zbiranje uporabne statistike +Comment[sr]=Сакупи нешто кориÑне ÑтатиÑтике +Comment[sr@Latn]=Sakupi neÅ¡to korisne statistike +Comment[sv]=Samla in en del användbar statistik +Comment[tr]=Anlamlı istatistlikler topla +Comment[uk]=Зібрати деÑкі ÑтатиÑтичні дані +Comment[zh_CN]=收å–更有æ„义的统计 +Comment[zh_HK]=收集一些有用的統計 +Comment[zh_TW]=收集一些有用的統計 diff --git a/kopete/plugins/statistics/kopetestatistics.kateproject b/kopete/plugins/statistics/kopetestatistics.kateproject new file mode 100644 index 00000000..e06ea21b --- /dev/null +++ b/kopete/plugins/statistics/kopetestatistics.kateproject @@ -0,0 +1,7 @@ +[Project Dir] +Dirs= +Files=statisticscontact.cpp/statisticscontact.h/statisticsdb.cpp/statisticsdb.h/statisticsdialog.cpp/statisticsdialog.h/statisticsplugin.cpp/statisticsplugin.h/statisticsui.rc/statisticswidget.cpp/statisticswidget.h/statisticswidget.ui/TODO/Makefile.am/kopete_statistics.template.html/kopete_statistics.css/kopete_statistics.desktop/statisticsdcopiface.h + +[Project File] +Name=KopeteStatistics +Type=Default diff --git a/kopete/plugins/statistics/sqlite/Makefile.am b/kopete/plugins/statistics/sqlite/Makefile.am new file mode 100644 index 00000000..f647c6d5 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/Makefile.am @@ -0,0 +1,51 @@ +noinst_LTLIBRARIES = \ + libsqlite.la + +KDE_CFLAGS = \ + -w + +libsqlite_la_CFLAGS = \ + $(all_includes) \ + -DTHREADSAFE=1 + +libsqlite_la_LDFLAGS = \ + $(LIBPTHREAD) + +libsqlite_la_SOURCES = \ + attach.c \ + auth.c \ + btree.c \ + build.c \ + date.c \ + delete.c \ + encode.c \ + expr.c \ + func.c \ + hash.c \ + insert.c \ + legacy.c \ + main.c \ + opcodes.c \ + os_mac.c \ + os_unix.c \ + os_win.c \ + pager.c \ + parse.c \ + pragma.c \ + printf.c \ + random.c \ + select.c \ + shell.c \ + table.c \ + tokenize.c \ + trigger.c \ + update.c \ + utf.c \ + util.c \ + vacuum.c \ + vdbe.c \ + vdbeapi.c \ + vdbeaux.c \ + vdbemem.c \ + where.c + diff --git a/kopete/plugins/statistics/sqlite/attach.c b/kopete/plugins/statistics/sqlite/attach.c new file mode 100644 index 00000000..2f089986 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/attach.c @@ -0,0 +1,329 @@ +/* +** 2003 April 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used to implement the ATTACH and DETACH commands. +** +** $Id$ +*/ +#include "sqliteInt.h" + +/* +** This routine is called by the parser to process an ATTACH statement: +** +** ATTACH DATABASE filename AS dbname +** +** The pFilename and pDbname arguments are the tokens that define the +** filename and dbname in the ATTACH statement. +*/ +void sqlite3Attach( + Parse *pParse, /* The parser context */ + Token *pFilename, /* Name of database file */ + Token *pDbname, /* Name of the database to use internally */ + int keyType, /* 0: no key. 1: TEXT, 2: BLOB */ + Token *pKey /* Text of the key for keytype 1 and 2 */ +){ + Db *aNew; + int rc, i; + char *zFile, *zName; + sqlite3 *db; + Vdbe *v; + + v = sqlite3GetVdbe(pParse); + if( !v ) return; + sqlite3VdbeAddOp(v, OP_Halt, 0, 0); + if( pParse->explain ) return; + db = pParse->db; + if( db->nDb>=MAX_ATTACHED+2 ){ + sqlite3ErrorMsg(pParse, "too many attached databases - max %d", + MAX_ATTACHED); + pParse->rc = SQLITE_ERROR; + return; + } + + if( !db->autoCommit ){ + sqlite3ErrorMsg(pParse, "cannot ATTACH database within transaction"); + pParse->rc = SQLITE_ERROR; + return; + } + + zFile = sqlite3NameFromToken(pFilename);; + if( zFile==0 ) return; +#ifndef SQLITE_OMIT_AUTHORIZATION + if( sqlite3AuthCheck(pParse, SQLITE_ATTACH, zFile, 0, 0)!=SQLITE_OK ){ + sqliteFree(zFile); + return; + } +#endif /* SQLITE_OMIT_AUTHORIZATION */ + + zName = sqlite3NameFromToken(pDbname); + if( zName==0 ) return; + for(i=0; inDb; i++){ + char *z = db->aDb[i].zName; + if( z && sqlite3StrICmp(z, zName)==0 ){ + sqlite3ErrorMsg(pParse, "database %z is already in use", zName); + pParse->rc = SQLITE_ERROR; + sqliteFree(zFile); + return; + } + } + + if( db->aDb==db->aDbStatic ){ + aNew = sqliteMalloc( sizeof(db->aDb[0])*3 ); + if( aNew==0 ) return; + memcpy(aNew, db->aDb, sizeof(db->aDb[0])*2); + }else{ + aNew = sqliteRealloc(db->aDb, sizeof(db->aDb[0])*(db->nDb+1) ); + if( aNew==0 ) return; + } + db->aDb = aNew; + aNew = &db->aDb[db->nDb++]; + memset(aNew, 0, sizeof(*aNew)); + sqlite3HashInit(&aNew->tblHash, SQLITE_HASH_STRING, 0); + sqlite3HashInit(&aNew->idxHash, SQLITE_HASH_STRING, 0); + sqlite3HashInit(&aNew->trigHash, SQLITE_HASH_STRING, 0); + sqlite3HashInit(&aNew->aFKey, SQLITE_HASH_STRING, 1); + aNew->zName = zName; + aNew->safety_level = 3; + rc = sqlite3BtreeFactory(db, zFile, 0, MAX_PAGES, &aNew->pBt); + if( rc ){ + sqlite3ErrorMsg(pParse, "unable to open database: %s", zFile); + } +#if SQLITE_HAS_CODEC + { + extern int sqlite3CodecAttach(sqlite3*, int, void*, int); + char *zKey; + int nKey; + if( keyType==0 ){ + /* No key specified. Use the key from the main database */ + extern void sqlite3CodecGetKey(sqlite3*, int, void**, int*); + sqlite3CodecGetKey(db, 0, (void**)&zKey, &nKey); + }else if( keyType==1 ){ + /* Key specified as text */ + zKey = sqlite3NameFromToken(pKey); + nKey = strlen(zKey); + }else{ + /* Key specified as a BLOB */ + char *zTemp; + assert( keyType==2 ); + pKey->z++; + pKey->n--; + zTemp = sqlite3NameFromToken(pKey); + zKey = sqlite3HexToBlob(zTemp); + sqliteFree(zTemp); + } + sqlite3CodecAttach(db, db->nDb-1, zKey, nKey); + if( keyType ){ + sqliteFree(zKey); + } + } +#endif + sqliteFree(zFile); + db->flags &= ~SQLITE_Initialized; + if( pParse->nErr==0 && rc==SQLITE_OK ){ + rc = sqlite3ReadSchema(pParse); + } + if( rc ){ + int i = db->nDb - 1; + assert( i>=2 ); + if( db->aDb[i].pBt ){ + sqlite3BtreeClose(db->aDb[i].pBt); + db->aDb[i].pBt = 0; + } + sqlite3ResetInternalSchema(db, 0); + if( 0==pParse->nErr ){ + pParse->nErr++; + pParse->rc = SQLITE_ERROR; + } + } +} + +/* +** This routine is called by the parser to process a DETACH statement: +** +** DETACH DATABASE dbname +** +** The pDbname argument is the name of the database in the DETACH statement. +*/ +void sqlite3Detach(Parse *pParse, Token *pDbname){ + int i; + sqlite3 *db; + Vdbe *v; + Db *pDb = 0; + + v = sqlite3GetVdbe(pParse); + if( !v ) return; + sqlite3VdbeAddOp(v, OP_Halt, 0, 0); + if( pParse->explain ) return; + db = pParse->db; + for(i=0; inDb; i++){ + pDb = &db->aDb[i]; + if( pDb->pBt==0 || pDb->zName==0 ) continue; + if( strlen(pDb->zName)!=pDbname->n ) continue; + if( sqlite3StrNICmp(pDb->zName, pDbname->z, pDbname->n)==0 ) break; + } + if( i>=db->nDb ){ + sqlite3ErrorMsg(pParse, "no such database: %T", pDbname); + return; + } + if( i<2 ){ + sqlite3ErrorMsg(pParse, "cannot detach database %T", pDbname); + return; + } + if( !db->autoCommit ){ + sqlite3ErrorMsg(pParse, "cannot DETACH database within transaction"); + pParse->rc = SQLITE_ERROR; + return; + } +#ifndef SQLITE_OMIT_AUTHORIZATION + if( sqlite3AuthCheck(pParse,SQLITE_DETACH,db->aDb[i].zName,0,0)!=SQLITE_OK ){ + return; + } +#endif /* SQLITE_OMIT_AUTHORIZATION */ + sqlite3BtreeClose(pDb->pBt); + pDb->pBt = 0; + sqlite3ResetInternalSchema(db, 0); +} + +/* +** Initialize a DbFixer structure. This routine must be called prior +** to passing the structure to one of the sqliteFixAAAA() routines below. +** +** The return value indicates whether or not fixation is required. TRUE +** means we do need to fix the database references, FALSE means we do not. +*/ +int sqlite3FixInit( + DbFixer *pFix, /* The fixer to be initialized */ + Parse *pParse, /* Error messages will be written here */ + int iDb, /* This is the database that must be used */ + const char *zType, /* "view", "trigger", or "index" */ + const Token *pName /* Name of the view, trigger, or index */ +){ + sqlite3 *db; + + if( iDb<0 || iDb==1 ) return 0; + db = pParse->db; + assert( db->nDb>iDb ); + pFix->pParse = pParse; + pFix->zDb = db->aDb[iDb].zName; + pFix->zType = zType; + pFix->pName = pName; + return 1; +} + +/* +** The following set of routines walk through the parse tree and assign +** a specific database to all table references where the database name +** was left unspecified in the original SQL statement. The pFix structure +** must have been initialized by a prior call to sqlite3FixInit(). +** +** These routines are used to make sure that an index, trigger, or +** view in one database does not refer to objects in a different database. +** (Exception: indices, triggers, and views in the TEMP database are +** allowed to refer to anything.) If a reference is explicitly made +** to an object in a different database, an error message is added to +** pParse->zErrMsg and these routines return non-zero. If everything +** checks out, these routines return 0. +*/ +int sqlite3FixSrcList( + DbFixer *pFix, /* Context of the fixation */ + SrcList *pList /* The Source list to check and modify */ +){ + int i; + const char *zDb; + struct SrcList_item *pItem; + + if( pList==0 ) return 0; + zDb = pFix->zDb; + for(i=0, pItem=pList->a; inSrc; i++, pItem++){ + if( pItem->zDatabase==0 ){ + pItem->zDatabase = sqliteStrDup(zDb); + }else if( sqlite3StrICmp(pItem->zDatabase,zDb)!=0 ){ + sqlite3ErrorMsg(pFix->pParse, + "%s %T cannot reference objects in database %s", + pFix->zType, pFix->pName, pItem->zDatabase); + return 1; + } + if( sqlite3FixSelect(pFix, pItem->pSelect) ) return 1; + if( sqlite3FixExpr(pFix, pItem->pOn) ) return 1; + } + return 0; +} +int sqlite3FixSelect( + DbFixer *pFix, /* Context of the fixation */ + Select *pSelect /* The SELECT statement to be fixed to one database */ +){ + while( pSelect ){ + if( sqlite3FixExprList(pFix, pSelect->pEList) ){ + return 1; + } + if( sqlite3FixSrcList(pFix, pSelect->pSrc) ){ + return 1; + } + if( sqlite3FixExpr(pFix, pSelect->pWhere) ){ + return 1; + } + if( sqlite3FixExpr(pFix, pSelect->pHaving) ){ + return 1; + } + pSelect = pSelect->pPrior; + } + return 0; +} +int sqlite3FixExpr( + DbFixer *pFix, /* Context of the fixation */ + Expr *pExpr /* The expression to be fixed to one database */ +){ + while( pExpr ){ + if( sqlite3FixSelect(pFix, pExpr->pSelect) ){ + return 1; + } + if( sqlite3FixExprList(pFix, pExpr->pList) ){ + return 1; + } + if( sqlite3FixExpr(pFix, pExpr->pRight) ){ + return 1; + } + pExpr = pExpr->pLeft; + } + return 0; +} +int sqlite3FixExprList( + DbFixer *pFix, /* Context of the fixation */ + ExprList *pList /* The expression to be fixed to one database */ +){ + int i; + struct ExprList_item *pItem; + if( pList==0 ) return 0; + for(i=0, pItem=pList->a; inExpr; i++, pItem++){ + if( sqlite3FixExpr(pFix, pItem->pExpr) ){ + return 1; + } + } + return 0; +} +int sqlite3FixTriggerStep( + DbFixer *pFix, /* Context of the fixation */ + TriggerStep *pStep /* The trigger step be fixed to one database */ +){ + while( pStep ){ + if( sqlite3FixSelect(pFix, pStep->pSelect) ){ + return 1; + } + if( sqlite3FixExpr(pFix, pStep->pWhere) ){ + return 1; + } + if( sqlite3FixExprList(pFix, pStep->pExprList) ){ + return 1; + } + pStep = pStep->pNext; + } + return 0; +} diff --git a/kopete/plugins/statistics/sqlite/auth.c b/kopete/plugins/statistics/sqlite/auth.c new file mode 100644 index 00000000..b251eacf --- /dev/null +++ b/kopete/plugins/statistics/sqlite/auth.c @@ -0,0 +1,223 @@ +/* +** 2003 January 11 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used to implement the sqlite3_set_authorizer() +** API. This facility is an optional feature of the library. Embedded +** systems that do not need this facility may omit it by recompiling +** the library with -DSQLITE_OMIT_AUTHORIZATION=1 +** +** $Id$ +*/ +#include "sqliteInt.h" + +/* +** All of the code in this file may be omitted by defining a single +** macro. +*/ +#ifndef SQLITE_OMIT_AUTHORIZATION + +/* +** Set or clear the access authorization function. +** +** The access authorization function is be called during the compilation +** phase to verify that the user has read and/or write access permission on +** various fields of the database. The first argument to the auth function +** is a copy of the 3rd argument to this routine. The second argument +** to the auth function is one of these constants: +** +** SQLITE_CREATE_INDEX +** SQLITE_CREATE_TABLE +** SQLITE_CREATE_TEMP_INDEX +** SQLITE_CREATE_TEMP_TABLE +** SQLITE_CREATE_TEMP_TRIGGER +** SQLITE_CREATE_TEMP_VIEW +** SQLITE_CREATE_TRIGGER +** SQLITE_CREATE_VIEW +** SQLITE_DELETE +** SQLITE_DROP_INDEX +** SQLITE_DROP_TABLE +** SQLITE_DROP_TEMP_INDEX +** SQLITE_DROP_TEMP_TABLE +** SQLITE_DROP_TEMP_TRIGGER +** SQLITE_DROP_TEMP_VIEW +** SQLITE_DROP_TRIGGER +** SQLITE_DROP_VIEW +** SQLITE_INSERT +** SQLITE_PRAGMA +** SQLITE_READ +** SQLITE_SELECT +** SQLITE_TRANSACTION +** SQLITE_UPDATE +** +** The third and fourth arguments to the auth function are the name of +** the table and the column that are being accessed. The auth function +** should return either SQLITE_OK, SQLITE_DENY, or SQLITE_IGNORE. If +** SQLITE_OK is returned, it means that access is allowed. SQLITE_DENY +** means that the SQL statement will never-run - the sqlite3_exec() call +** will return with an error. SQLITE_IGNORE means that the SQL statement +** should run but attempts to read the specified column will return NULL +** and attempts to write the column will be ignored. +** +** Setting the auth function to NULL disables this hook. The default +** setting of the auth function is NULL. +*/ +int sqlite3_set_authorizer( + sqlite3 *db, + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), + void *pArg +){ + db->xAuth = xAuth; + db->pAuthArg = pArg; + return SQLITE_OK; +} + +/* +** Write an error message into pParse->zErrMsg that explains that the +** user-supplied authorization function returned an illegal value. +*/ +static void sqliteAuthBadReturnCode(Parse *pParse, int rc){ + sqlite3ErrorMsg(pParse, "illegal return value (%d) from the " + "authorization function - should be SQLITE_OK, SQLITE_IGNORE, " + "or SQLITE_DENY", rc); + pParse->rc = SQLITE_ERROR; +} + +/* +** The pExpr should be a TK_COLUMN expression. The table referred to +** is in pTabList or else it is the NEW or OLD table of a trigger. +** Check to see if it is OK to read this particular column. +** +** If the auth function returns SQLITE_IGNORE, change the TK_COLUMN +** instruction into a TK_NULL. If the auth function returns SQLITE_DENY, +** then generate an error. +*/ +void sqlite3AuthRead( + Parse *pParse, /* The parser context */ + Expr *pExpr, /* The expression to check authorization on */ + SrcList *pTabList /* All table that pExpr might refer to */ +){ + sqlite3 *db = pParse->db; + int rc; + Table *pTab; /* The table being read */ + const char *zCol; /* Name of the column of the table */ + int iSrc; /* Index in pTabList->a[] of table being read */ + const char *zDBase; /* Name of database being accessed */ + TriggerStack *pStack; /* The stack of current triggers */ + + if( db->xAuth==0 ) return; + assert( pExpr->op==TK_COLUMN ); + for(iSrc=0; iSrcnSrc; iSrc++){ + if( pExpr->iTable==pTabList->a[iSrc].iCursor ) break; + } + if( iSrc>=0 && iSrcnSrc ){ + pTab = pTabList->a[iSrc].pTab; + }else if( (pStack = pParse->trigStack)!=0 ){ + /* This must be an attempt to read the NEW or OLD pseudo-tables + ** of a trigger. + */ + assert( pExpr->iTable==pStack->newIdx || pExpr->iTable==pStack->oldIdx ); + pTab = pStack->pTab; + }else{ + return; + } + if( pTab==0 ) return; + if( pExpr->iColumn>=0 ){ + assert( pExpr->iColumnnCol ); + zCol = pTab->aCol[pExpr->iColumn].zName; + }else if( pTab->iPKey>=0 ){ + assert( pTab->iPKeynCol ); + zCol = pTab->aCol[pTab->iPKey].zName; + }else{ + zCol = "ROWID"; + } + assert( pExpr->iDbnDb ); + zDBase = db->aDb[pExpr->iDb].zName; + rc = db->xAuth(db->pAuthArg, SQLITE_READ, pTab->zName, zCol, zDBase, + pParse->zAuthContext); + if( rc==SQLITE_IGNORE ){ + pExpr->op = TK_NULL; + }else if( rc==SQLITE_DENY ){ + if( db->nDb>2 || pExpr->iDb!=0 ){ + sqlite3ErrorMsg(pParse, "access to %s.%s.%s is prohibited", + zDBase, pTab->zName, zCol); + }else{ + sqlite3ErrorMsg(pParse, "access to %s.%s is prohibited",pTab->zName,zCol); + } + pParse->rc = SQLITE_AUTH; + }else if( rc!=SQLITE_OK ){ + sqliteAuthBadReturnCode(pParse, rc); + } +} + +/* +** Do an authorization check using the code and arguments given. Return +** either SQLITE_OK (zero) or SQLITE_IGNORE or SQLITE_DENY. If SQLITE_DENY +** is returned, then the error count and error message in pParse are +** modified appropriately. +*/ +int sqlite3AuthCheck( + Parse *pParse, + int code, + const char *zArg1, + const char *zArg2, + const char *zArg3 +){ + sqlite3 *db = pParse->db; + int rc; + + /* Don't do any authorization checks if the database is initialising. */ + if( db->init.busy ){ + return SQLITE_OK; + } + + if( db->xAuth==0 ){ + return SQLITE_OK; + } + rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext); + if( rc==SQLITE_DENY ){ + sqlite3ErrorMsg(pParse, "not authorized"); + pParse->rc = SQLITE_AUTH; + }else if( rc!=SQLITE_OK && rc!=SQLITE_IGNORE ){ + rc = SQLITE_DENY; + sqliteAuthBadReturnCode(pParse, rc); + } + return rc; +} + +/* +** Push an authorization context. After this routine is called, the +** zArg3 argument to authorization callbacks will be zContext until +** popped. Or if pParse==0, this routine is a no-op. +*/ +void sqlite3AuthContextPush( + Parse *pParse, + AuthContext *pContext, + const char *zContext +){ + pContext->pParse = pParse; + if( pParse ){ + pContext->zAuthContext = pParse->zAuthContext; + pParse->zAuthContext = zContext; + } +} + +/* +** Pop an authorization context that was previously pushed +** by sqlite3AuthContextPush +*/ +void sqlite3AuthContextPop(AuthContext *pContext){ + if( pContext->pParse ){ + pContext->pParse->zAuthContext = pContext->zAuthContext; + pContext->pParse = 0; + } +} + +#endif /* SQLITE_OMIT_AUTHORIZATION */ diff --git a/kopete/plugins/statistics/sqlite/btree.c b/kopete/plugins/statistics/sqlite/btree.c new file mode 100644 index 00000000..fe8754e0 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/btree.c @@ -0,0 +1,4462 @@ +/* +** 2004 April 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** $Id$ +** +** This file implements a external (disk-based) database using BTrees. +** For a detailed discussion of BTrees, refer to +** +** Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3: +** "Sorting And Searching", pages 473-480. Addison-Wesley +** Publishing Company, Reading, Massachusetts. +** +** The basic idea is that each page of the file contains N database +** entries and N+1 pointers to subpages. +** +** ---------------------------------------------------------------- +** | Ptr(0) | Key(0) | Ptr(1) | Key(1) | ... | Key(N) | Ptr(N+1) | +** ---------------------------------------------------------------- +** +** All of the keys on the page that Ptr(0) points to have values less +** than Key(0). All of the keys on page Ptr(1) and its subpages have +** values greater than Key(0) and less than Key(1). All of the keys +** on Ptr(N+1) and its subpages have values greater than Key(N). And +** so forth. +** +** Finding a particular key requires reading O(log(M)) pages from the +** disk where M is the number of entries in the tree. +** +** In this implementation, a single file can hold one or more separate +** BTrees. Each BTree is identified by the index of its root page. The +** key and data for any entry are combined to form the "payload". A +** fixed amount of payload can be carried directly on the database +** page. If the payload is larger than the preset amount then surplus +** bytes are stored on overflow pages. The payload for an entry +** and the preceding pointer are combined to form a "Cell". Each +** page has a small header which contains the Ptr(N+1) pointer and other +** information such as the size of key and data. +** +** FORMAT DETAILS +** +** The file is divided into pages. The first page is called page 1, +** the second is page 2, and so forth. A page number of zero indicates +** "no such page". The page size can be anything between 512 and 65536. +** Each page can be either a btree page, a freelist page or an overflow +** page. +** +** The first page is always a btree page. The first 100 bytes of the first +** page contain a special header (the "file header") that describes the file. +** The format of the file header is as follows: +** +** OFFSET SIZE DESCRIPTION +** 0 16 Header string: "SQLite format 3\000" +** 16 2 Page size in bytes. +** 18 1 File format write version +** 19 1 File format read version +** 20 1 Bytes of unused space at the end of each page +** 21 1 Max embedded payload fraction +** 22 1 Min embedded payload fraction +** 23 1 Min leaf payload fraction +** 24 4 File change counter +** 28 4 Reserved for future use +** 32 4 First freelist page +** 36 4 Number of freelist pages in the file +** 40 60 15 4-byte meta values passed to higher layers +** +** All of the integer values are big-endian (most significant byte first). +** +** The file change counter is incremented when the database is changed more +** than once within the same second. This counter, together with the +** modification time of the file, allows other processes to know +** when the file has changed and thus when they need to flush their +** cache. +** +** The max embedded payload fraction is the amount of the total usable +** space in a page that can be consumed by a single cell for standard +** B-tree (non-LEAFDATA) tables. A value of 255 means 100%. The default +** is to limit the maximum cell size so that at least 4 cells will fit +** on one page. Thus the default max embedded payload fraction is 64. +** +** If the payload for a cell is larger than the max payload, then extra +** payload is spilled to overflow pages. Once an overflow page is allocated, +** as many bytes as possible are moved into the overflow pages without letting +** the cell size drop below the min embedded payload fraction. +** +** The min leaf payload fraction is like the min embedded payload fraction +** except that it applies to leaf nodes in a LEAFDATA tree. The maximum +** payload fraction for a LEAFDATA tree is always 100% (or 255) and it +** not specified in the header. +** +** Each btree pages is divided into three sections: The header, the +** cell pointer array, and the cell area area. Page 1 also has a 100-byte +** file header that occurs before the page header. +** +** |----------------| +** | file header | 100 bytes. Page 1 only. +** |----------------| +** | page header | 8 bytes for leaves. 12 bytes for interior nodes +** |----------------| +** | cell pointer | | 2 bytes per cell. Sorted order. +** | array | | Grows downward +** | | v +** |----------------| +** | unallocated | +** | space | +** |----------------| ^ Grows upwards +** | cell content | | Arbitrary order interspersed with freeblocks. +** | area | | and free space fragments. +** |----------------| +** +** The page headers looks like this: +** +** OFFSET SIZE DESCRIPTION +** 0 1 Flags. 1: intkey, 2: zerodata, 4: leafdata, 8: leaf +** 1 2 byte offset to the first freeblock +** 3 2 number of cells on this page +** 5 2 first byte of the cell content area +** 7 1 number of fragmented free bytes +** 8 4 Right child (the Ptr(N+1) value). Omitted on leaves. +** +** The flags define the format of this btree page. The leaf flag means that +** this page has no children. The zerodata flag means that this page carries +** only keys and no data. The intkey flag means that the key is a integer +** which is stored in the key size entry of the cell header rather than in +** the payload area. +** +** The cell pointer array begins on the first byte after the page header. +** The cell pointer array contains zero or more 2-byte numbers which are +** offsets from the beginning of the page to the cell content in the cell +** content area. The cell pointers occur in sorted order. The system strives +** to keep free space after the last cell pointer so that new cells can +** be easily added without having to defragment the page. +** +** Cell content is stored at the very end of the page and grows toward the +** beginning of the page. +** +** Unused space within the cell content area is collected into a linked list of +** freeblocks. Each freeblock is at least 4 bytes in size. The byte offset +** to the first freeblock is given in the header. Freeblocks occur in +** increasing order. Because a freeblock must be at least 4 bytes in size, +** any group of 3 or fewer unused bytes in the cell content area cannot +** exist on the freeblock chain. A group of 3 or fewer free bytes is called +** a fragment. The total number of bytes in all fragments is recorded. +** in the page header at offset 7. +** +** SIZE DESCRIPTION +** 2 Byte offset of the next freeblock +** 2 Bytes in this freeblock +** +** Cells are of variable length. Cells are stored in the cell content area at +** the end of the page. Pointers to the cells are in the cell pointer array +** that immediately follows the page header. Cells is not necessarily +** contiguous or in order, but cell pointers are contiguous and in order. +** +** Cell content makes use of variable length integers. A variable +** length integer is 1 to 9 bytes where the lower 7 bits of each +** byte are used. The integer consists of all bytes that have bit 8 set and +** the first byte with bit 8 clear. The most significant byte of the integer +** appears first. A variable-length integer may not be more than 9 bytes long. +** As a special case, all 8 bytes of the 9th byte are used as data. This +** allows a 64-bit integer to be encoded in 9 bytes. +** +** 0x00 becomes 0x00000000 +** 0x7f becomes 0x0000007f +** 0x81 0x00 becomes 0x00000080 +** 0x82 0x00 becomes 0x00000100 +** 0x80 0x7f becomes 0x0000007f +** 0x8a 0x91 0xd1 0xac 0x78 becomes 0x12345678 +** 0x81 0x81 0x81 0x81 0x01 becomes 0x10204081 +** +** Variable length integers are used for rowids and to hold the number of +** bytes of key and data in a btree cell. +** +** The content of a cell looks like this: +** +** SIZE DESCRIPTION +** 4 Page number of the left child. Omitted if leaf flag is set. +** var Number of bytes of data. Omitted if the zerodata flag is set. +** var Number of bytes of key. Or the key itself if intkey flag is set. +** * Payload +** 4 First page of the overflow chain. Omitted if no overflow +** +** Overflow pages form a linked list. Each page except the last is completely +** filled with data (pagesize - 4 bytes). The last page can have as little +** as 1 byte of data. +** +** SIZE DESCRIPTION +** 4 Page number of next overflow page +** * Data +** +** Freelist pages come in two subtypes: trunk pages and leaf pages. The +** file header points to first in a linked list of trunk page. Each trunk +** page points to multiple leaf pages. The content of a leaf page is +** unspecified. A trunk page looks like this: +** +** SIZE DESCRIPTION +** 4 Page number of next trunk page +** 4 Number of leaf pointers on this page +** * zero or more pages numbers of leaves +*/ +#include "sqliteInt.h" +#include "pager.h" +#include "btree.h" +#include "os.h" +#include + + +/* The following value is the maximum cell size assuming a maximum page +** size give above. +*/ +#define MX_CELL_SIZE(pBt) (pBt->pageSize-8) + +/* The maximum number of cells on a single page of the database. This +** assumes a minimum cell size of 3 bytes. Such small cells will be +** exceedingly rare, but they are possible. +*/ +#define MX_CELL(pBt) ((pBt->pageSize-8)/3) + +/* Forward declarations */ +typedef struct MemPage MemPage; + +/* +** This is a magic string that appears at the beginning of every +** SQLite database in order to identify the file as a real database. +** 123456789 123456 */ +static const char zMagicHeader[] = "SQLite format 3"; + +/* +** Page type flags. An ORed combination of these flags appear as the +** first byte of every BTree page. +*/ +#define PTF_INTKEY 0x01 +#define PTF_ZERODATA 0x02 +#define PTF_LEAFDATA 0x04 +#define PTF_LEAF 0x08 + +/* +** As each page of the file is loaded into memory, an instance of the following +** structure is appended and initialized to zero. This structure stores +** information about the page that is decoded from the raw file page. +** +** The pParent field points back to the parent page. This allows us to +** walk up the BTree from any leaf to the root. Care must be taken to +** unref() the parent page pointer when this page is no longer referenced. +** The pageDestructor() routine handles that chore. +*/ +struct MemPage { + u8 isInit; /* True if previously initialized. MUST BE FIRST! */ + u8 idxShift; /* True if Cell indices have changed */ + u8 nOverflow; /* Number of overflow cell bodies in aCell[] */ + u8 intKey; /* True if intkey flag is set */ + u8 leaf; /* True if leaf flag is set */ + u8 zeroData; /* True if table stores keys only */ + u8 leafData; /* True if tables stores data on leaves only */ + u8 hasData; /* True if this page stores data */ + u8 hdrOffset; /* 100 for page 1. 0 otherwise */ + u8 childPtrSize; /* 0 if leaf==1. 4 if leaf==0 */ + u16 maxLocal; /* Copy of Btree.maxLocal or Btree.maxLeaf */ + u16 minLocal; /* Copy of Btree.minLocal or Btree.minLeaf */ + u16 cellOffset; /* Index in aData of first cell pointer */ + u16 idxParent; /* Index in parent of this node */ + u16 nFree; /* Number of free bytes on the page */ + u16 nCell; /* Number of cells on this page, local and ovfl */ + struct _OvflCell { /* Cells that will not fit on aData[] */ + u8 *pCell; /* Pointers to the body of the overflow cell */ + u16 idx; /* Insert this cell before idx-th non-overflow cell */ + } aOvfl[5]; + struct Btree *pBt; /* Pointer back to BTree structure */ + u8 *aData; /* Pointer back to the start of the page */ + Pgno pgno; /* Page number for this page */ + MemPage *pParent; /* The parent of this page. NULL for root */ +}; + +/* +** The in-memory image of a disk page has the auxiliary information appended +** to the end. EXTRA_SIZE is the number of bytes of space needed to hold +** that extra information. +*/ +#define EXTRA_SIZE sizeof(MemPage) + +/* +** Everything we need to know about an open database +*/ +struct Btree { + Pager *pPager; /* The page cache */ + BtCursor *pCursor; /* A list of all open cursors */ + MemPage *pPage1; /* First page of the database */ + u8 inTrans; /* True if a transaction is in progress */ + u8 inStmt; /* True if we are in a statement subtransaction */ + u8 readOnly; /* True if the underlying file is readonly */ + u8 maxEmbedFrac; /* Maximum payload as % of total page size */ + u8 minEmbedFrac; /* Minimum payload as % of total page size */ + u8 minLeafFrac; /* Minimum leaf payload as % of total page size */ + u8 pageSizeFixed; /* True if the page size can no longer be changed */ + u16 pageSize; /* Total number of bytes on a page */ + u16 usableSize; /* Number of usable bytes on each page */ + int maxLocal; /* Maximum local payload in non-LEAFDATA tables */ + int minLocal; /* Minimum local payload in non-LEAFDATA tables */ + int maxLeaf; /* Maximum local payload in a LEAFDATA table */ + int minLeaf; /* Minimum local payload in a LEAFDATA table */ +}; +typedef Btree Bt; + +/* +** Btree.inTrans may take one of the following values. +*/ +#define TRANS_NONE 0 +#define TRANS_READ 1 +#define TRANS_WRITE 2 + +/* +** An instance of the following structure is used to hold information +** about a cell. The parseCellPtr() function fills in this structure +** based on information extract from the raw disk page. +*/ +typedef struct CellInfo CellInfo; +struct CellInfo { + u8 *pCell; /* Pointer to the start of cell content */ + i64 nKey; /* The key for INTKEY tables, or number of bytes in key */ + u32 nData; /* Number of bytes of data */ + u16 nHeader; /* Size of the cell content header in bytes */ + u16 nLocal; /* Amount of payload held locally */ + u16 iOverflow; /* Offset to overflow page number. Zero if no overflow */ + u16 nSize; /* Size of the cell content on the main b-tree page */ +}; + +/* +** A cursor is a pointer to a particular entry in the BTree. +** The entry is identified by its MemPage and the index in +** MemPage.aCell[] of the entry. +*/ +struct BtCursor { + Btree *pBt; /* The Btree to which this cursor belongs */ + BtCursor *pNext, *pPrev; /* Forms a linked list of all cursors */ + int (*xCompare)(void*,int,const void*,int,const void*); /* Key comp func */ + void *pArg; /* First arg to xCompare() */ + Pgno pgnoRoot; /* The root page of this tree */ + MemPage *pPage; /* Page that contains the entry */ + int idx; /* Index of the entry in pPage->aCell[] */ + CellInfo info; /* A parse of the cell we are pointing at */ + u8 wrFlag; /* True if writable */ + u8 isValid; /* TRUE if points to a valid entry */ + u8 status; /* Set to SQLITE_ABORT if cursors is invalidated */ +}; + +/* +** Forward declaration +*/ +static int checkReadLocks(Btree*,Pgno,BtCursor*); + + +/* +** Read or write a two- and four-byte big-endian integer values. +*/ +static u32 get2byte(unsigned char *p){ + return (p[0]<<8) | p[1]; +} +static u32 get4byte(unsigned char *p){ + return (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3]; +} +static void put2byte(unsigned char *p, u32 v){ + p[0] = v>>8; + p[1] = v; +} +static void put4byte(unsigned char *p, u32 v){ + p[0] = v>>24; + p[1] = v>>16; + p[2] = v>>8; + p[3] = v; +} + +/* +** Routines to read and write variable-length integers. These used to +** be defined locally, but now we use the varint routines in the util.c +** file. +*/ +#define getVarint sqlite3GetVarint +#define getVarint32 sqlite3GetVarint32 +#define putVarint sqlite3PutVarint + +/* +** Given a btree page and a cell index (0 means the first cell on +** the page, 1 means the second cell, and so forth) return a pointer +** to the cell content. +** +** This routine works only for pages that do not contain overflow cells. +*/ +static u8 *findCell(MemPage *pPage, int iCell){ + u8 *data = pPage->aData; + assert( iCell>=0 ); + assert( iCellhdrOffset+3]) ); + return data + get2byte(&data[pPage->cellOffset+2*iCell]); +} + +/* +** This a more complex version of findCell() that works for +** pages that do contain overflow cells. See insert +*/ +static u8 *findOverflowCell(MemPage *pPage, int iCell){ + int i; + for(i=pPage->nOverflow-1; i>=0; i--){ + int k; + struct _OvflCell *pOvfl; + pOvfl = &pPage->aOvfl[i]; + k = pOvfl->idx; + if( k<=iCell ){ + if( k==iCell ){ + return pOvfl->pCell; + } + iCell--; + } + } + return findCell(pPage, iCell); +} + +/* +** Parse a cell content block and fill in the CellInfo structure. There +** are two versions of this function. parseCell() takes a cell index +** as the second argument and parseCellPtr() takes a pointer to the +** body of the cell as its second argument. +*/ +static void parseCellPtr( + MemPage *pPage, /* Page containing the cell */ + u8 *pCell, /* Pointer to the cell text. */ + CellInfo *pInfo /* Fill in this structure */ +){ + int n; /* Number bytes in cell content header */ + u32 nPayload; /* Number of bytes of cell payload */ + + pInfo->pCell = pCell; + assert( pPage->leaf==0 || pPage->leaf==1 ); + n = pPage->childPtrSize; + assert( n==4-4*pPage->leaf ); + if( pPage->hasData ){ + n += getVarint32(&pCell[n], &nPayload); + }else{ + nPayload = 0; + } + n += getVarint(&pCell[n], (u64 *)&pInfo->nKey); + pInfo->nHeader = n; + pInfo->nData = nPayload; + if( !pPage->intKey ){ + nPayload += pInfo->nKey; + } + if( nPayload<=pPage->maxLocal ){ + /* This is the (easy) common case where the entire payload fits + ** on the local page. No overflow is required. + */ + int nSize; /* Total size of cell content in bytes */ + pInfo->nLocal = nPayload; + pInfo->iOverflow = 0; + nSize = nPayload + n; + if( nSize<4 ){ + nSize = 4; /* Minimum cell size is 4 */ + } + pInfo->nSize = nSize; + }else{ + /* If the payload will not fit completely on the local page, we have + ** to decide how much to store locally and how much to spill onto + ** overflow pages. The strategy is to minimize the amount of unused + ** space on overflow pages while keeping the amount of local storage + ** in between minLocal and maxLocal. + ** + ** Warning: changing the way overflow payload is distributed in any + ** way will result in an incompatible file format. + */ + int minLocal; /* Minimum amount of payload held locally */ + int maxLocal; /* Maximum amount of payload held locally */ + int surplus; /* Overflow payload available for local storage */ + + minLocal = pPage->minLocal; + maxLocal = pPage->maxLocal; + surplus = minLocal + (nPayload - minLocal)%(pPage->pBt->usableSize - 4); + if( surplus <= maxLocal ){ + pInfo->nLocal = surplus; + }else{ + pInfo->nLocal = minLocal; + } + pInfo->iOverflow = pInfo->nLocal + n; + pInfo->nSize = pInfo->iOverflow + 4; + } +} +static void parseCell( + MemPage *pPage, /* Page containing the cell */ + int iCell, /* The cell index. First cell is 0 */ + CellInfo *pInfo /* Fill in this structure */ +){ + parseCellPtr(pPage, findCell(pPage, iCell), pInfo); +} + +/* +** Compute the total number of bytes that a Cell needs in the cell +** data area of the btree-page. The return number includes the cell +** data header and the local payload, but not any overflow page or +** the space used by the cell pointer. +*/ +#ifndef NDEBUG +static int cellSize(MemPage *pPage, int iCell){ + CellInfo info; + parseCell(pPage, iCell, &info); + return info.nSize; +} +#endif +static int cellSizePtr(MemPage *pPage, u8 *pCell){ + CellInfo info; + parseCellPtr(pPage, pCell, &info); + return info.nSize; +} + +/* +** Do sanity checking on a page. Throw an exception if anything is +** not right. +** +** This routine is used for internal error checking only. It is omitted +** from most builds. +*/ +#if defined(BTREE_DEBUG) && !defined(NDEBUG) && 0 +static void _pageIntegrity(MemPage *pPage){ + int usableSize; + u8 *data; + int i, j, idx, c, pc, hdr, nFree; + int cellOffset; + int nCell, cellLimit; + u8 *used; + + used = sqliteMallocRaw( pPage->pBt->pageSize ); + if( used==0 ) return; + usableSize = pPage->pBt->usableSize; + assert( pPage->aData==&((unsigned char*)pPage)[-pPage->pBt->pageSize] ); + hdr = pPage->hdrOffset; + assert( hdr==(pPage->pgno==1 ? 100 : 0) ); + assert( pPage->pgno==sqlite3pager_pagenumber(pPage->aData) ); + c = pPage->aData[hdr]; + if( pPage->isInit ){ + assert( pPage->leaf == ((c & PTF_LEAF)!=0) ); + assert( pPage->zeroData == ((c & PTF_ZERODATA)!=0) ); + assert( pPage->leafData == ((c & PTF_LEAFDATA)!=0) ); + assert( pPage->intKey == ((c & (PTF_INTKEY|PTF_LEAFDATA))!=0) ); + assert( pPage->hasData == + !(pPage->zeroData || (!pPage->leaf && pPage->leafData)) ); + assert( pPage->cellOffset==pPage->hdrOffset+12-4*pPage->leaf ); + assert( pPage->nCell = get2byte(&pPage->aData[hdr+3]) ); + } + data = pPage->aData; + memset(used, 0, usableSize); + for(i=0; ileaf*4; i++) used[i] = 1; + nFree = 0; + pc = get2byte(&data[hdr+1]); + while( pc ){ + int size; + assert( pc>0 && pcisInit==0 + || pPage->nFree==nFree+data[hdr+7]+cellLimit-(cellOffset+2*nCell) ); + cellOffset = pPage->cellOffset; + for(i=0; i0 && pcaData) ); + assert( pPage->pBt!=0 ); + assert( pPage->pBt->usableSize <= SQLITE_MAX_PAGE_SIZE ); + assert( pPage->nOverflow==0 ); + temp = sqliteMalloc( pPage->pBt->pageSize ); + if( temp==0 ) return SQLITE_NOMEM; + data = pPage->aData; + hdr = pPage->hdrOffset; + cellOffset = pPage->cellOffset; + nCell = pPage->nCell; + assert( nCell==get2byte(&data[hdr+3]) ); + usableSize = pPage->pBt->usableSize; + brk = get2byte(&data[hdr+5]); + memcpy(&temp[brk], &data[brk], usableSize - brk); + brk = usableSize; + for(i=0; ipBt->usableSize ); + size = cellSizePtr(pPage, &temp[pc]); + brk -= size; + memcpy(&data[brk], &temp[pc], size); + put2byte(pAddr, brk); + } + assert( brk>=cellOffset+2*nCell ); + put2byte(&data[hdr+5], brk); + data[hdr+1] = 0; + data[hdr+2] = 0; + data[hdr+7] = 0; + addr = cellOffset+2*nCell; + memset(&data[addr], 0, brk-addr); + sqliteFree(temp); + return SQLITE_OK; +} + +/* +** Allocate nByte bytes of space on a page. +** +** Return the index into pPage->aData[] of the first byte of +** the new allocation. Or return 0 if there is not enough free +** space on the page to satisfy the allocation request. +** +** If the page contains nBytes of free space but does not contain +** nBytes of contiguous free space, then this routine automatically +** calls defragementPage() to consolidate all free space before +** allocating the new chunk. +*/ +static int allocateSpace(MemPage *pPage, int nByte){ + int addr, pc, hdr; + int size; + int nFrag; + int top; + int nCell; + int cellOffset; + unsigned char *data; + + data = pPage->aData; + assert( sqlite3pager_iswriteable(data) ); + assert( pPage->pBt ); + if( nByte<4 ) nByte = 4; + if( pPage->nFreenOverflow>0 ) return 0; + pPage->nFree -= nByte; + hdr = pPage->hdrOffset; + + nFrag = data[hdr+7]; + if( nFrag<60 ){ + /* Search the freelist looking for a slot big enough to satisfy the + ** space request. */ + addr = hdr+1; + while( (pc = get2byte(&data[addr]))>0 ){ + size = get2byte(&data[pc+2]); + if( size>=nByte ){ + if( sizecellOffset; + if( nFrag>=60 || cellOffset + 2*nCell > top - nByte ){ + if( defragmentPage(pPage) ) return 0; + top = get2byte(&data[hdr+5]); + } + top -= nByte; + assert( cellOffset + 2*nCell <= top ); + put2byte(&data[hdr+5], top); + return top; +} + +/* +** Return a section of the pPage->aData to the freelist. +** The first byte of the new free block is pPage->aDisk[start] +** and the size of the block is "size" bytes. +** +** Most of the effort here is involved in coalesing adjacent +** free blocks into a single big free block. +*/ +static void freeSpace(MemPage *pPage, int start, int size){ + int addr, pbegin, hdr; + unsigned char *data = pPage->aData; + + assert( pPage->pBt!=0 ); + assert( sqlite3pager_iswriteable(data) ); + assert( start>=pPage->hdrOffset+6+(pPage->leaf?0:4) ); + assert( (start + size)<=pPage->pBt->usableSize ); + if( size<4 ) size = 4; + + /* Add the space back into the linked list of freeblocks */ + hdr = pPage->hdrOffset; + addr = hdr + 1; + while( (pbegin = get2byte(&data[addr]))0 ){ + assert( pbegin<=pPage->pBt->usableSize-4 ); + assert( pbegin>addr ); + addr = pbegin; + } + assert( pbegin<=pPage->pBt->usableSize-4 ); + assert( pbegin>addr || pbegin==0 ); + put2byte(&data[addr], start); + put2byte(&data[start], pbegin); + put2byte(&data[start+2], size); + pPage->nFree += size; + + /* Coalesce adjacent free blocks */ + addr = pPage->hdrOffset + 1; + while( (pbegin = get2byte(&data[addr]))>0 ){ + int pnext, psize; + assert( pbegin>addr ); + assert( pbegin<=pPage->pBt->usableSize-4 ); + pnext = get2byte(&data[pbegin]); + psize = get2byte(&data[pbegin+2]); + if( pbegin + psize + 3 >= pnext && pnext>0 ){ + int frag = pnext - (pbegin+psize); + assert( frag<=data[pPage->hdrOffset+7] ); + data[pPage->hdrOffset+7] -= frag; + put2byte(&data[pbegin], get2byte(&data[pnext])); + put2byte(&data[pbegin+2], pnext+get2byte(&data[pnext+2])-pbegin); + }else{ + addr = pbegin; + } + } + + /* If the cell content area begins with a freeblock, remove it. */ + if( data[hdr+1]==data[hdr+5] && data[hdr+2]==data[hdr+6] ){ + int top; + pbegin = get2byte(&data[hdr+1]); + memcpy(&data[hdr+1], &data[pbegin], 2); + top = get2byte(&data[hdr+5]); + put2byte(&data[hdr+5], top + get2byte(&data[pbegin+2])); + } +} + +/* +** Decode the flags byte (the first byte of the header) for a page +** and initialize fields of the MemPage structure accordingly. +*/ +static void decodeFlags(MemPage *pPage, int flagByte){ + Btree *pBt; /* A copy of pPage->pBt */ + + assert( pPage->hdrOffset==(pPage->pgno==1 ? 100 : 0) ); + pPage->intKey = (flagByte & (PTF_INTKEY|PTF_LEAFDATA))!=0; + pPage->zeroData = (flagByte & PTF_ZERODATA)!=0; + pPage->leaf = (flagByte & PTF_LEAF)!=0; + pPage->childPtrSize = 4*(pPage->leaf==0); + pBt = pPage->pBt; + if( flagByte & PTF_LEAFDATA ){ + pPage->leafData = 1; + pPage->maxLocal = pBt->maxLeaf; + pPage->minLocal = pBt->minLeaf; + }else{ + pPage->leafData = 0; + pPage->maxLocal = pBt->maxLocal; + pPage->minLocal = pBt->minLocal; + } + pPage->hasData = !(pPage->zeroData || (!pPage->leaf && pPage->leafData)); +} + +/* +** Initialize the auxiliary information for a disk block. +** +** The pParent parameter must be a pointer to the MemPage which +** is the parent of the page being initialized. The root of a +** BTree has no parent and so for that page, pParent==NULL. +** +** Return SQLITE_OK on success. If we see that the page does +** not contain a well-formed database page, then return +** SQLITE_CORRUPT. Note that a return of SQLITE_OK does not +** guarantee that the page is well-formed. It only shows that +** we failed to detect any corruption. +*/ +static int initPage( + MemPage *pPage, /* The page to be initialized */ + MemPage *pParent /* The parent. Might be NULL */ +){ + int pc; /* Address of a freeblock within pPage->aData[] */ + int i; /* Loop counter */ + int hdr; /* Offset to beginning of page header */ + u8 *data; /* Equal to pPage->aData */ + Btree *pBt; /* The main btree structure */ + int usableSize; /* Amount of usable space on each page */ + int cellOffset; /* Offset from start of page to first cell pointer */ + int nFree; /* Number of unused bytes on the page */ + int top; /* First byte of the cell content area */ + + pBt = pPage->pBt; + assert( pBt!=0 ); + assert( pParent==0 || pParent->pBt==pBt ); + assert( pPage->pgno==sqlite3pager_pagenumber(pPage->aData) ); + assert( pPage->aData == &((unsigned char*)pPage)[-pBt->pageSize] ); + if( pPage->pParent!=pParent && (pPage->pParent!=0 || pPage->isInit) ){ + /* The parent page should never change unless the file is corrupt */ + return SQLITE_CORRUPT; /* bkpt-CORRUPT */ + } + if( pPage->isInit ) return SQLITE_OK; + if( pPage->pParent==0 && pParent!=0 ){ + pPage->pParent = pParent; + sqlite3pager_ref(pParent->aData); + } + hdr = pPage->hdrOffset; + data = pPage->aData; + decodeFlags(pPage, data[hdr]); + pPage->nOverflow = 0; + pPage->idxShift = 0; + usableSize = pBt->usableSize; + pPage->cellOffset = cellOffset = hdr + 12 - 4*pPage->leaf; + top = get2byte(&data[hdr+5]); + pPage->nCell = get2byte(&data[hdr+3]); + if( pPage->nCell>MX_CELL(pBt) ){ + /* To many cells for a single page. The page must be corrupt */ + return SQLITE_CORRUPT; /* bkpt-CORRUPT */ + } + if( pPage->nCell==0 && pParent!=0 && pParent->pgno!=1 ){ + /* All pages must have at least one cell, except for root pages */ + return SQLITE_CORRUPT; /* bkpt-CORRUPT */ + } + + /* Compute the total free space on the page */ + pc = get2byte(&data[hdr+1]); + nFree = data[hdr+7] + top - (cellOffset + 2*pPage->nCell); + i = 0; + while( pc>0 ){ + int next, size; + if( pc>usableSize-4 ){ + /* Free block is off the page */ + return SQLITE_CORRUPT; /* bkpt-CORRUPT */ + } + if( i++>SQLITE_MAX_PAGE_SIZE/4 ){ + /* The free block list forms an infinite loop */ + return SQLITE_CORRUPT; /* bkpt-CORRUPT */ + } + next = get2byte(&data[pc]); + size = get2byte(&data[pc+2]); + if( next>0 && next<=pc+size+3 ){ + /* Free blocks must be in accending order */ + return SQLITE_CORRUPT; /* bkpt-CORRUPT */ + } + nFree += size; + pc = next; + } + pPage->nFree = nFree; + if( nFree>=usableSize ){ + /* Free space cannot exceed total page size */ + return SQLITE_CORRUPT; /* bkpt-CORRUPT */ + } + + pPage->isInit = 1; + pageIntegrity(pPage); + return SQLITE_OK; +} + +/* +** Set up a raw page so that it looks like a database page holding +** no entries. +*/ +static void zeroPage(MemPage *pPage, int flags){ + unsigned char *data = pPage->aData; + Btree *pBt = pPage->pBt; + int hdr = pPage->hdrOffset; + int first; + + assert( sqlite3pager_pagenumber(data)==pPage->pgno ); + assert( &data[pBt->pageSize] == (unsigned char*)pPage ); + assert( sqlite3pager_iswriteable(data) ); + memset(&data[hdr], 0, pBt->usableSize - hdr); + data[hdr] = flags; + first = hdr + 8 + 4*((flags&PTF_LEAF)==0); + memset(&data[hdr+1], 0, 4); + data[hdr+7] = 0; + put2byte(&data[hdr+5], pBt->usableSize); + pPage->nFree = pBt->usableSize - first; + decodeFlags(pPage, flags); + pPage->hdrOffset = hdr; + pPage->cellOffset = first; + pPage->nOverflow = 0; + pPage->idxShift = 0; + pPage->nCell = 0; + pPage->isInit = 1; + pageIntegrity(pPage); +} + +/* +** Get a page from the pager. Initialize the MemPage.pBt and +** MemPage.aData elements if needed. +*/ +static int getPage(Btree *pBt, Pgno pgno, MemPage **ppPage){ + int rc; + unsigned char *aData; + MemPage *pPage; + rc = sqlite3pager_get(pBt->pPager, pgno, (void**)&aData); + if( rc ) return rc; + pPage = (MemPage*)&aData[pBt->pageSize]; + pPage->aData = aData; + pPage->pBt = pBt; + pPage->pgno = pgno; + pPage->hdrOffset = pPage->pgno==1 ? 100 : 0; + *ppPage = pPage; + return SQLITE_OK; +} + +/* +** Get a page from the pager and initialize it. This routine +** is just a convenience wrapper around separate calls to +** getPage() and initPage(). +*/ +static int getAndInitPage( + Btree *pBt, /* The database file */ + Pgno pgno, /* Number of the page to get */ + MemPage **ppPage, /* Write the page pointer here */ + MemPage *pParent /* Parent of the page */ +){ + int rc; + if( pgno==0 ){ + return SQLITE_CORRUPT; /* bkpt-CORRUPT */ + } + rc = getPage(pBt, pgno, ppPage); + if( rc==SQLITE_OK && (*ppPage)->isInit==0 ){ + rc = initPage(*ppPage, pParent); + } + return rc; +} + +/* +** Release a MemPage. This should be called once for each prior +** call to getPage. +*/ +static void releasePage(MemPage *pPage){ + if( pPage ){ + assert( pPage->aData ); + assert( pPage->pBt ); + assert( &pPage->aData[pPage->pBt->pageSize]==(unsigned char*)pPage ); + sqlite3pager_unref(pPage->aData); + } +} + +/* +** This routine is called when the reference count for a page +** reaches zero. We need to unref the pParent pointer when that +** happens. +*/ +static void pageDestructor(void *pData, int pageSize){ + MemPage *pPage = (MemPage*)&((char*)pData)[pageSize]; + if( pPage->pParent ){ + MemPage *pParent = pPage->pParent; + pPage->pParent = 0; + releasePage(pParent); + } + pPage->isInit = 0; +} + +/* +** During a rollback, when the pager reloads information into the cache +** so that the cache is restored to its original state at the start of +** the transaction, for each page restored this routine is called. +** +** This routine needs to reset the extra data section at the end of the +** page to agree with the restored data. +*/ +static void pageReinit(void *pData, int pageSize){ + MemPage *pPage = (MemPage*)&((char*)pData)[pageSize]; + if( pPage->isInit ){ + pPage->isInit = 0; + initPage(pPage, pPage->pParent); + } +} + +/* +** Open a database file. +** +** zFilename is the name of the database file. If zFilename is NULL +** a new database with a random name is created. This randomly named +** database file will be deleted when sqlite3BtreeClose() is called. +*/ +int sqlite3BtreeOpen( + const char *zFilename, /* Name of the file containing the BTree database */ + Btree **ppBtree, /* Pointer to new Btree object written here */ + int flags /* Options */ +){ + Btree *pBt; + int rc; + int nReserve; + unsigned char zDbHeader[100]; + + /* + ** The following asserts make sure that structures used by the btree are + ** the right size. This is to guard against size changes that result + ** when compiling on a different architecture. + */ + assert( sizeof(i64)==8 ); + assert( sizeof(u64)==8 ); + assert( sizeof(u32)==4 ); + assert( sizeof(u16)==2 ); + assert( sizeof(Pgno)==4 ); + assert( sizeof(ptr)==sizeof(char*) ); + assert( sizeof(uptr)==sizeof(ptr) ); + + pBt = sqliteMalloc( sizeof(*pBt) ); + if( pBt==0 ){ + *ppBtree = 0; + return SQLITE_NOMEM; + } + rc = sqlite3pager_open(&pBt->pPager, zFilename, EXTRA_SIZE, + (flags & BTREE_OMIT_JOURNAL)==0); + if( rc!=SQLITE_OK ){ + if( pBt->pPager ) sqlite3pager_close(pBt->pPager); + sqliteFree(pBt); + *ppBtree = 0; + return rc; + } + sqlite3pager_set_destructor(pBt->pPager, pageDestructor); + sqlite3pager_set_reiniter(pBt->pPager, pageReinit); + pBt->pCursor = 0; + pBt->pPage1 = 0; + pBt->readOnly = sqlite3pager_isreadonly(pBt->pPager); + sqlite3pager_read_fileheader(pBt->pPager, sizeof(zDbHeader), zDbHeader); + pBt->pageSize = get2byte(&zDbHeader[16]); + if( pBt->pageSize<512 || pBt->pageSize>SQLITE_MAX_PAGE_SIZE ){ + pBt->pageSize = SQLITE_DEFAULT_PAGE_SIZE; + pBt->maxEmbedFrac = 64; /* 25% */ + pBt->minEmbedFrac = 32; /* 12.5% */ + pBt->minLeafFrac = 32; /* 12.5% */ + nReserve = 0; + }else{ + nReserve = zDbHeader[20]; + pBt->maxEmbedFrac = zDbHeader[21]; + pBt->minEmbedFrac = zDbHeader[22]; + pBt->minLeafFrac = zDbHeader[23]; + pBt->pageSizeFixed = 1; + } + pBt->usableSize = pBt->pageSize - nReserve; + sqlite3pager_set_pagesize(pBt->pPager, pBt->pageSize); + *ppBtree = pBt; + return SQLITE_OK; +} + +/* +** Close an open database and invalidate all cursors. +*/ +int sqlite3BtreeClose(Btree *pBt){ + while( pBt->pCursor ){ + sqlite3BtreeCloseCursor(pBt->pCursor); + } + sqlite3pager_close(pBt->pPager); + sqliteFree(pBt); + return SQLITE_OK; +} + +/* +** Change the busy handler callback function. +*/ +int sqlite3BtreeSetBusyHandler(Btree *pBt, BusyHandler *pHandler){ + sqlite3pager_set_busyhandler(pBt->pPager, pHandler); + return SQLITE_OK; +} + +/* +** Change the limit on the number of pages allowed in the cache. +** +** The maximum number of cache pages is set to the absolute +** value of mxPage. If mxPage is negative, the pager will +** operate asynchronously - it will not stop to do fsync()s +** to insure data is written to the disk surface before +** continuing. Transactions still work if synchronous is off, +** and the database cannot be corrupted if this program +** crashes. But if the operating system crashes or there is +** an abrupt power failure when synchronous is off, the database +** could be left in an inconsistent and unrecoverable state. +** Synchronous is on by default so database corruption is not +** normally a worry. +*/ +int sqlite3BtreeSetCacheSize(Btree *pBt, int mxPage){ + sqlite3pager_set_cachesize(pBt->pPager, mxPage); + return SQLITE_OK; +} + +/* +** Change the way data is synced to disk in order to increase or decrease +** how well the database resists damage due to OS crashes and power +** failures. Level 1 is the same as asynchronous (no syncs() occur and +** there is a high probability of damage) Level 2 is the default. There +** is a very low but non-zero probability of damage. Level 3 reduces the +** probability of damage to near zero but with a write performance reduction. +*/ +int sqlite3BtreeSetSafetyLevel(Btree *pBt, int level){ + sqlite3pager_set_safety_level(pBt->pPager, level); + return SQLITE_OK; +} + +/* +** Change the default pages size and the number of reserved bytes per page. +*/ +int sqlite3BtreeSetPageSize(Btree *pBt, int pageSize, int nReserve){ + if( pBt->pageSizeFixed ){ + return SQLITE_READONLY; + } + if( nReserve<0 ){ + nReserve = pBt->pageSize - pBt->usableSize; + } + if( pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE ){ + pBt->pageSize = pageSize; + sqlite3pager_set_pagesize(pBt->pPager, pageSize); + } + pBt->usableSize = pBt->pageSize - nReserve; + return SQLITE_OK; +} + +/* +** Return the currently defined page size +*/ +int sqlite3BtreeGetPageSize(Btree *pBt){ + return pBt->pageSize; +} +int sqlite3BtreeGetReserve(Btree *pBt){ + return pBt->pageSize - pBt->usableSize; +} + +/* +** Get a reference to pPage1 of the database file. This will +** also acquire a readlock on that file. +** +** SQLITE_OK is returned on success. If the file is not a +** well-formed database file, then SQLITE_CORRUPT is returned. +** SQLITE_BUSY is returned if the database is locked. SQLITE_NOMEM +** is returned if we run out of memory. SQLITE_PROTOCOL is returned +** if there is a locking protocol violation. +*/ +static int lockBtree(Btree *pBt){ + int rc; + MemPage *pPage1; + if( pBt->pPage1 ) return SQLITE_OK; + rc = getPage(pBt, 1, &pPage1); + if( rc!=SQLITE_OK ) return rc; + + + /* Do some checking to help insure the file we opened really is + ** a valid database file. + */ + rc = SQLITE_NOTADB; + if( sqlite3pager_pagecount(pBt->pPager)>0 ){ + u8 *page1 = pPage1->aData; + if( memcmp(page1, zMagicHeader, 16)!=0 ){ + goto page1_init_failed; + } + if( page1[18]>1 || page1[19]>1 ){ + goto page1_init_failed; + } + pBt->pageSize = get2byte(&page1[16]); + pBt->usableSize = pBt->pageSize - page1[20]; + if( pBt->usableSize<500 ){ + goto page1_init_failed; + } + pBt->maxEmbedFrac = page1[21]; + pBt->minEmbedFrac = page1[22]; + pBt->minLeafFrac = page1[23]; + } + + /* maxLocal is the maximum amount of payload to store locally for + ** a cell. Make sure it is small enough so that at least minFanout + ** cells can will fit on one page. We assume a 10-byte page header. + ** Besides the payload, the cell must store: + ** 2-byte pointer to the cell + ** 4-byte child pointer + ** 9-byte nKey value + ** 4-byte nData value + ** 4-byte overflow page pointer + ** So a cell consists of a 2-byte poiner, a header which is as much as + ** 17 bytes long, 0 to N bytes of payload, and an optional 4 byte overflow + ** page pointer. + */ + pBt->maxLocal = (pBt->usableSize-12)*pBt->maxEmbedFrac/255 - 23; + pBt->minLocal = (pBt->usableSize-12)*pBt->minEmbedFrac/255 - 23; + pBt->maxLeaf = pBt->usableSize - 35; + pBt->minLeaf = (pBt->usableSize-12)*pBt->minLeafFrac/255 - 23; + if( pBt->minLocal>pBt->maxLocal || pBt->maxLocal<0 ){ + goto page1_init_failed; + } + assert( pBt->maxLeaf + 23 <= MX_CELL_SIZE(pBt) ); + pBt->pPage1 = pPage1; + return SQLITE_OK; + +page1_init_failed: + releasePage(pPage1); + pBt->pPage1 = 0; + return rc; +} + +/* +** If there are no outstanding cursors and we are not in the middle +** of a transaction but there is a read lock on the database, then +** this routine unrefs the first page of the database file which +** has the effect of releasing the read lock. +** +** If there are any outstanding cursors, this routine is a no-op. +** +** If there is a transaction in progress, this routine is a no-op. +*/ +static void unlockBtreeIfUnused(Btree *pBt){ + if( pBt->inTrans==TRANS_NONE && pBt->pCursor==0 && pBt->pPage1!=0 ){ + if( pBt->pPage1->aData==0 ){ + MemPage *pPage = pBt->pPage1; + pPage->aData = &((char*)pPage)[-pBt->pageSize]; + pPage->pBt = pBt; + pPage->pgno = 1; + } + releasePage(pBt->pPage1); + pBt->pPage1 = 0; + pBt->inStmt = 0; + } +} + +/* +** Create a new database by initializing the first page of the +** file. +*/ +static int newDatabase(Btree *pBt){ + MemPage *pP1; + unsigned char *data; + int rc; + if( sqlite3pager_pagecount(pBt->pPager)>0 ) return SQLITE_OK; + pP1 = pBt->pPage1; + assert( pP1!=0 ); + data = pP1->aData; + rc = sqlite3pager_write(data); + if( rc ) return rc; + memcpy(data, zMagicHeader, sizeof(zMagicHeader)); + assert( sizeof(zMagicHeader)==16 ); + put2byte(&data[16], pBt->pageSize); + data[18] = 1; + data[19] = 1; + data[20] = pBt->pageSize - pBt->usableSize; + data[21] = pBt->maxEmbedFrac; + data[22] = pBt->minEmbedFrac; + data[23] = pBt->minLeafFrac; + memset(&data[24], 0, 100-24); + zeroPage(pP1, PTF_INTKEY|PTF_LEAF|PTF_LEAFDATA ); + pBt->pageSizeFixed = 1; + return SQLITE_OK; +} + +/* +** Attempt to start a new transaction. A write-transaction +** is started if the second argument is nonzero, otherwise a read- +** transaction. If the second argument is 2 or more and exclusive +** transaction is started, meaning that no other process is allowed +** to access the database. A preexisting transaction may not be +** upgrade to exclusive by calling this routine a second time - the +** exclusivity flag only works for a new transaction. +** +** A write-transaction must be started before attempting any +** changes to the database. None of the following routines +** will work unless a transaction is started first: +** +** sqlite3BtreeCreateTable() +** sqlite3BtreeCreateIndex() +** sqlite3BtreeClearTable() +** sqlite3BtreeDropTable() +** sqlite3BtreeInsert() +** sqlite3BtreeDelete() +** sqlite3BtreeUpdateMeta() +** +** If wrflag is true, then nMaster specifies the maximum length of +** a master journal file name supplied later via sqlite3BtreeSync(). +** This is so that appropriate space can be allocated in the journal file +** when it is created.. +*/ +int sqlite3BtreeBeginTrans(Btree *pBt, int wrflag){ + int rc = SQLITE_OK; + + /* If the btree is already in a write-transaction, or it + ** is already in a read-transaction and a read-transaction + ** is requested, this is a no-op. + */ + if( pBt->inTrans==TRANS_WRITE || + (pBt->inTrans==TRANS_READ && !wrflag) ){ + return SQLITE_OK; + } + if( pBt->readOnly && wrflag ){ + return SQLITE_READONLY; + } + + if( pBt->pPage1==0 ){ + rc = lockBtree(pBt); + } + + if( rc==SQLITE_OK && wrflag ){ + rc = sqlite3pager_begin(pBt->pPage1->aData, wrflag>1); + if( rc==SQLITE_OK ){ + rc = newDatabase(pBt); + } + } + + if( rc==SQLITE_OK ){ + pBt->inTrans = (wrflag?TRANS_WRITE:TRANS_READ); + if( wrflag ) pBt->inStmt = 0; + }else{ + unlockBtreeIfUnused(pBt); + } + return rc; +} + +/* +** Commit the transaction currently in progress. +** +** This will release the write lock on the database file. If there +** are no active cursors, it also releases the read lock. +*/ +int sqlite3BtreeCommit(Btree *pBt){ + int rc = SQLITE_OK; + if( pBt->inTrans==TRANS_WRITE ){ + rc = sqlite3pager_commit(pBt->pPager); + } + pBt->inTrans = TRANS_NONE; + pBt->inStmt = 0; + unlockBtreeIfUnused(pBt); + return rc; +} + +#ifndef NDEBUG +/* +** Return the number of write-cursors open on this handle. This is for use +** in assert() expressions, so it is only compiled if NDEBUG is not +** defined. +*/ +static int countWriteCursors(Btree *pBt){ + BtCursor *pCur; + int r = 0; + for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ + if( pCur->wrFlag ) r++; + } + return r; +} +#endif + +#if 0 +/* +** Invalidate all cursors +*/ +static void invalidateCursors(Btree *pBt){ + BtCursor *pCur; + for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ + MemPage *pPage = pCur->pPage; + if( pPage /* && !pPage->isInit */ ){ + pageIntegrity(pPage); + releasePage(pPage); + pCur->pPage = 0; + pCur->isValid = 0; + pCur->status = SQLITE_ABORT; + } + } +} +#endif + +#ifdef SQLITE_TEST +/* +** Print debugging information about all cursors to standard output. +*/ +void sqlite3BtreeCursorList(Btree *pBt){ + BtCursor *pCur; + for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ + MemPage *pPage = pCur->pPage; + char *zMode = pCur->wrFlag ? "rw" : "ro"; + sqlite3DebugPrintf("CURSOR %p rooted at %4d(%s) currently at %d.%d%s\n", + pCur, pCur->pgnoRoot, zMode, + pPage ? pPage->pgno : 0, pCur->idx, + pCur->isValid ? "" : " eof" + ); + } +} +#endif + +/* +** Rollback the transaction in progress. All cursors will be +** invalided by this operation. Any attempt to use a cursor +** that was open at the beginning of this operation will result +** in an error. +** +** This will release the write lock on the database file. If there +** are no active cursors, it also releases the read lock. +*/ +int sqlite3BtreeRollback(Btree *pBt){ + int rc = SQLITE_OK; + MemPage *pPage1; + if( pBt->inTrans==TRANS_WRITE ){ + rc = sqlite3pager_rollback(pBt->pPager); + /* The rollback may have destroyed the pPage1->aData value. So + ** call getPage() on page 1 again to make sure pPage1->aData is + ** set correctly. */ + if( getPage(pBt, 1, &pPage1)==SQLITE_OK ){ + releasePage(pPage1); + } + assert( countWriteCursors(pBt)==0 ); + } + pBt->inTrans = TRANS_NONE; + pBt->inStmt = 0; + unlockBtreeIfUnused(pBt); + return rc; +} + +/* +** Start a statement subtransaction. The subtransaction can +** can be rolled back independently of the main transaction. +** You must start a transaction before starting a subtransaction. +** The subtransaction is ended automatically if the main transaction +** commits or rolls back. +** +** Only one subtransaction may be active at a time. It is an error to try +** to start a new subtransaction if another subtransaction is already active. +** +** Statement subtransactions are used around individual SQL statements +** that are contained within a BEGIN...COMMIT block. If a constraint +** error occurs within the statement, the effect of that one statement +** can be rolled back without having to rollback the entire transaction. +*/ +int sqlite3BtreeBeginStmt(Btree *pBt){ + int rc; + if( (pBt->inTrans!=TRANS_WRITE) || pBt->inStmt ){ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + rc = pBt->readOnly ? SQLITE_OK : sqlite3pager_stmt_begin(pBt->pPager); + pBt->inStmt = 1; + return rc; +} + + +/* +** Commit the statment subtransaction currently in progress. If no +** subtransaction is active, this is a no-op. +*/ +int sqlite3BtreeCommitStmt(Btree *pBt){ + int rc; + if( pBt->inStmt && !pBt->readOnly ){ + rc = sqlite3pager_stmt_commit(pBt->pPager); + }else{ + rc = SQLITE_OK; + } + pBt->inStmt = 0; + return rc; +} + +/* +** Rollback the active statement subtransaction. If no subtransaction +** is active this routine is a no-op. +** +** All cursors will be invalidated by this operation. Any attempt +** to use a cursor that was open at the beginning of this operation +** will result in an error. +*/ +int sqlite3BtreeRollbackStmt(Btree *pBt){ + int rc; + if( pBt->inStmt==0 || pBt->readOnly ) return SQLITE_OK; + rc = sqlite3pager_stmt_rollback(pBt->pPager); + assert( countWriteCursors(pBt)==0 ); + pBt->inStmt = 0; + return rc; +} + +/* +** Default key comparison function to be used if no comparison function +** is specified on the sqlite3BtreeCursor() call. +*/ +static int dfltCompare( + void *NotUsed, /* User data is not used */ + int n1, const void *p1, /* First key to compare */ + int n2, const void *p2 /* Second key to compare */ +){ + int c; + c = memcmp(p1, p2, n1readOnly ){ + return SQLITE_READONLY; + } + if( checkReadLocks(pBt, iTable, 0) ){ + return SQLITE_LOCKED; + } + } + if( pBt->pPage1==0 ){ + rc = lockBtree(pBt); + if( rc!=SQLITE_OK ){ + return rc; + } + } + pCur = sqliteMallocRaw( sizeof(*pCur) ); + if( pCur==0 ){ + rc = SQLITE_NOMEM; + goto create_cursor_exception; + } + pCur->pgnoRoot = (Pgno)iTable; + if( iTable==1 && sqlite3pager_pagecount(pBt->pPager)==0 ){ + rc = SQLITE_EMPTY; + pCur->pPage = 0; + goto create_cursor_exception; + } + pCur->pPage = 0; /* For exit-handler, in case getAndInitPage() fails. */ + rc = getAndInitPage(pBt, pCur->pgnoRoot, &pCur->pPage, 0); + if( rc!=SQLITE_OK ){ + goto create_cursor_exception; + } + pCur->xCompare = xCmp ? xCmp : dfltCompare; + pCur->pArg = pArg; + pCur->pBt = pBt; + pCur->wrFlag = wrFlag; + pCur->idx = 0; + memset(&pCur->info, 0, sizeof(pCur->info)); + pCur->pNext = pBt->pCursor; + if( pCur->pNext ){ + pCur->pNext->pPrev = pCur; + } + pCur->pPrev = 0; + pBt->pCursor = pCur; + pCur->isValid = 0; + pCur->status = SQLITE_OK; + *ppCur = pCur; + return SQLITE_OK; + +create_cursor_exception: + if( pCur ){ + releasePage(pCur->pPage); + sqliteFree(pCur); + } + unlockBtreeIfUnused(pBt); + return rc; +} + +#if 0 /* Not Used */ +/* +** Change the value of the comparison function used by a cursor. +*/ +void sqlite3BtreeSetCompare( + BtCursor *pCur, /* The cursor to whose comparison function is changed */ + int(*xCmp)(void*,int,const void*,int,const void*), /* New comparison func */ + void *pArg /* First argument to xCmp() */ +){ + pCur->xCompare = xCmp ? xCmp : dfltCompare; + pCur->pArg = pArg; +} +#endif + +/* +** Close a cursor. The read lock on the database file is released +** when the last cursor is closed. +*/ +int sqlite3BtreeCloseCursor(BtCursor *pCur){ + Btree *pBt = pCur->pBt; + if( pCur->pPrev ){ + pCur->pPrev->pNext = pCur->pNext; + }else{ + pBt->pCursor = pCur->pNext; + } + if( pCur->pNext ){ + pCur->pNext->pPrev = pCur->pPrev; + } + releasePage(pCur->pPage); + unlockBtreeIfUnused(pBt); + sqliteFree(pCur); + return SQLITE_OK; +} + +/* +** Make a temporary cursor by filling in the fields of pTempCur. +** The temporary cursor is not on the cursor list for the Btree. +*/ +static void getTempCursor(BtCursor *pCur, BtCursor *pTempCur){ + memcpy(pTempCur, pCur, sizeof(*pCur)); + pTempCur->pNext = 0; + pTempCur->pPrev = 0; + if( pTempCur->pPage ){ + sqlite3pager_ref(pTempCur->pPage->aData); + } +} + +/* +** Delete a temporary cursor such as was made by the CreateTemporaryCursor() +** function above. +*/ +static void releaseTempCursor(BtCursor *pCur){ + if( pCur->pPage ){ + sqlite3pager_unref(pCur->pPage->aData); + } +} + +/* +** Make sure the BtCursor.info field of the given cursor is valid. +** If it is not already valid, call parseCell() to fill it in. +** +** BtCursor.info is a cache of the information in the current cell. +** Using this cache reduces the number of calls to parseCell(). +*/ +static void getCellInfo(BtCursor *pCur){ + if( pCur->info.nSize==0 ){ + parseCell(pCur->pPage, pCur->idx, &pCur->info); + }else{ +#ifndef NDEBUG + CellInfo info; + memset(&info, 0, sizeof(info)); + parseCell(pCur->pPage, pCur->idx, &info); + assert( memcmp(&info, &pCur->info, sizeof(info))==0 ); +#endif + } +} + +/* +** Set *pSize to the size of the buffer needed to hold the value of +** the key for the current entry. If the cursor is not pointing +** to a valid entry, *pSize is set to 0. +** +** For a table with the INTKEY flag set, this routine returns the key +** itself, not the number of bytes in the key. +*/ +int sqlite3BtreeKeySize(BtCursor *pCur, i64 *pSize){ + if( !pCur->isValid ){ + *pSize = 0; + }else{ + getCellInfo(pCur); + *pSize = pCur->info.nKey; + } + return SQLITE_OK; +} + +/* +** Set *pSize to the number of bytes of data in the entry the +** cursor currently points to. Always return SQLITE_OK. +** Failure is not possible. If the cursor is not currently +** pointing to an entry (which can happen, for example, if +** the database is empty) then *pSize is set to 0. +*/ +int sqlite3BtreeDataSize(BtCursor *pCur, u32 *pSize){ + if( !pCur->isValid ){ + /* Not pointing at a valid entry - set *pSize to 0. */ + *pSize = 0; + }else{ + getCellInfo(pCur); + *pSize = pCur->info.nData; + } + return SQLITE_OK; +} + +/* +** Read payload information from the entry that the pCur cursor is +** pointing to. Begin reading the payload at "offset" and read +** a total of "amt" bytes. Put the result in zBuf. +** +** This routine does not make a distinction between key and data. +** It just reads bytes from the payload area. Data might appear +** on the main page or be scattered out on multiple overflow pages. +*/ +static int getPayload( + BtCursor *pCur, /* Cursor pointing to entry to read from */ + int offset, /* Begin reading this far into payload */ + int amt, /* Read this many bytes */ + unsigned char *pBuf, /* Write the bytes into this buffer */ + int skipKey /* offset begins at data if this is true */ +){ + unsigned char *aPayload; + Pgno nextPage; + int rc; + MemPage *pPage; + Btree *pBt; + int ovflSize; + u32 nKey; + + assert( pCur!=0 && pCur->pPage!=0 ); + assert( pCur->isValid ); + pBt = pCur->pBt; + pPage = pCur->pPage; + pageIntegrity(pPage); + assert( pCur->idx>=0 && pCur->idxnCell ); + getCellInfo(pCur); + aPayload = pCur->info.pCell; + aPayload += pCur->info.nHeader; + if( pPage->intKey ){ + nKey = 0; + }else{ + nKey = pCur->info.nKey; + } + assert( offset>=0 ); + if( skipKey ){ + offset += nKey; + } + if( offset+amt > nKey+pCur->info.nData ){ + return SQLITE_ERROR; + } + if( offsetinfo.nLocal ){ + int a = amt; + if( a+offset>pCur->info.nLocal ){ + a = pCur->info.nLocal - offset; + } + memcpy(pBuf, &aPayload[offset], a); + if( a==amt ){ + return SQLITE_OK; + } + offset = 0; + pBuf += a; + amt -= a; + }else{ + offset -= pCur->info.nLocal; + } + ovflSize = pBt->usableSize - 4; + if( amt>0 ){ + nextPage = get4byte(&aPayload[pCur->info.nLocal]); + while( amt>0 && nextPage ){ + rc = sqlite3pager_get(pBt->pPager, nextPage, (void**)&aPayload); + if( rc!=0 ){ + return rc; + } + nextPage = get4byte(aPayload); + if( offset ovflSize ){ + a = ovflSize - offset; + } + memcpy(pBuf, &aPayload[offset+4], a); + offset = 0; + amt -= a; + pBuf += a; + }else{ + offset -= ovflSize; + } + sqlite3pager_unref(aPayload); + } + } + + if( amt>0 ){ + return SQLITE_CORRUPT; /* bkpt-CORRUPT */ + } + return SQLITE_OK; +} + +/* +** Read part of the key associated with cursor pCur. Exactly +** "amt" bytes will be transfered into pBuf[]. The transfer +** begins at "offset". +** +** Return SQLITE_OK on success or an error code if anything goes +** wrong. An error is returned if "offset+amt" is larger than +** the available payload. +*/ +int sqlite3BtreeKey(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ + if( pCur->isValid==0 ){ + return pCur->status; + } + assert( pCur->pPage!=0 ); + assert( pCur->pPage->intKey==0 ); + assert( pCur->idx>=0 && pCur->idxpPage->nCell ); + return getPayload(pCur, offset, amt, (unsigned char*)pBuf, 0); +} + +/* +** Read part of the data associated with cursor pCur. Exactly +** "amt" bytes will be transfered into pBuf[]. The transfer +** begins at "offset". +** +** Return SQLITE_OK on success or an error code if anything goes +** wrong. An error is returned if "offset+amt" is larger than +** the available payload. +*/ +int sqlite3BtreeData(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ + if( !pCur->isValid ){ + return pCur->status ? pCur->status : SQLITE_INTERNAL; + } + assert( pCur->pPage!=0 ); + assert( pCur->idx>=0 && pCur->idxpPage->nCell ); + return getPayload(pCur, offset, amt, pBuf, 1); +} + +/* +** Return a pointer to payload information from the entry that the +** pCur cursor is pointing to. The pointer is to the beginning of +** the key if skipKey==0 and it points to the beginning of data if +** skipKey==1. The number of bytes of available key/data is written +** into *pAmt. If *pAmt==0, then the value returned will not be +** a valid pointer. +** +** This routine is an optimization. It is common for the entire key +** and data to fit on the local page and for there to be no overflow +** pages. When that is so, this routine can be used to access the +** key and data without making a copy. If the key and/or data spills +** onto overflow pages, then getPayload() must be used to reassembly +** the key/data and copy it into a preallocated buffer. +** +** The pointer returned by this routine looks directly into the cached +** page of the database. The data might change or move the next time +** any btree routine is called. +*/ +static const unsigned char *fetchPayload( + BtCursor *pCur, /* Cursor pointing to entry to read from */ + int *pAmt, /* Write the number of available bytes here */ + int skipKey /* read beginning at data if this is true */ +){ + unsigned char *aPayload; + MemPage *pPage; + Btree *pBt; + u32 nKey; + int nLocal; + + assert( pCur!=0 && pCur->pPage!=0 ); + assert( pCur->isValid ); + pBt = pCur->pBt; + pPage = pCur->pPage; + pageIntegrity(pPage); + assert( pCur->idx>=0 && pCur->idxnCell ); + getCellInfo(pCur); + aPayload = pCur->info.pCell; + aPayload += pCur->info.nHeader; + if( pPage->intKey ){ + nKey = 0; + }else{ + nKey = pCur->info.nKey; + } + if( skipKey ){ + aPayload += nKey; + nLocal = pCur->info.nLocal - nKey; + }else{ + nLocal = pCur->info.nLocal; + if( nLocal>nKey ){ + nLocal = nKey; + } + } + *pAmt = nLocal; + return aPayload; +} + + +/* +** For the entry that cursor pCur is point to, return as +** many bytes of the key or data as are available on the local +** b-tree page. Write the number of available bytes into *pAmt. +** +** The pointer returned is ephemeral. The key/data may move +** or be destroyed on the next call to any Btree routine. +** +** These routines is used to get quick access to key and data +** in the common case where no overflow pages are used. +*/ +const void *sqlite3BtreeKeyFetch(BtCursor *pCur, int *pAmt){ + return (const void*)fetchPayload(pCur, pAmt, 0); +} +const void *sqlite3BtreeDataFetch(BtCursor *pCur, int *pAmt){ + return (const void*)fetchPayload(pCur, pAmt, 1); +} + + +/* +** Move the cursor down to a new child page. The newPgno argument is the +** page number of the child page to move to. +*/ +static int moveToChild(BtCursor *pCur, u32 newPgno){ + int rc; + MemPage *pNewPage; + MemPage *pOldPage; + Btree *pBt = pCur->pBt; + + assert( pCur->isValid ); + rc = getAndInitPage(pBt, newPgno, &pNewPage, pCur->pPage); + if( rc ) return rc; + pageIntegrity(pNewPage); + pNewPage->idxParent = pCur->idx; + pOldPage = pCur->pPage; + pOldPage->idxShift = 0; + releasePage(pOldPage); + pCur->pPage = pNewPage; + pCur->idx = 0; + pCur->info.nSize = 0; + if( pNewPage->nCell<1 ){ + return SQLITE_CORRUPT; /* bkpt-CORRUPT */ + } + return SQLITE_OK; +} + +/* +** Return true if the page is the virtual root of its table. +** +** The virtual root page is the root page for most tables. But +** for the table rooted on page 1, sometime the real root page +** is empty except for the right-pointer. In such cases the +** virtual root page is the page that the right-pointer of page +** 1 is pointing to. +*/ +static int isRootPage(MemPage *pPage){ + MemPage *pParent = pPage->pParent; + if( pParent==0 ) return 1; + if( pParent->pgno>1 ) return 0; + if( get2byte(&pParent->aData[pParent->hdrOffset+3])==0 ) return 1; + return 0; +} + +/* +** Move the cursor up to the parent page. +** +** pCur->idx is set to the cell index that contains the pointer +** to the page we are coming from. If we are coming from the +** right-most child page then pCur->idx is set to one more than +** the largest cell index. +*/ +static void moveToParent(BtCursor *pCur){ + Pgno oldPgno; + MemPage *pParent; + MemPage *pPage; + int idxParent; + + assert( pCur->isValid ); + pPage = pCur->pPage; + assert( pPage!=0 ); + assert( !isRootPage(pPage) ); + pageIntegrity(pPage); + pParent = pPage->pParent; + assert( pParent!=0 ); + pageIntegrity(pParent); + idxParent = pPage->idxParent; + sqlite3pager_ref(pParent->aData); + oldPgno = pPage->pgno; + releasePage(pPage); + pCur->pPage = pParent; + pCur->info.nSize = 0; + assert( pParent->idxShift==0 ); + pCur->idx = idxParent; +} + +/* +** Move the cursor to the root page +*/ +static int moveToRoot(BtCursor *pCur){ + MemPage *pRoot; + int rc; + Btree *pBt = pCur->pBt; + + rc = getAndInitPage(pBt, pCur->pgnoRoot, &pRoot, 0); + if( rc ){ + pCur->isValid = 0; + return rc; + } + releasePage(pCur->pPage); + pageIntegrity(pRoot); + pCur->pPage = pRoot; + pCur->idx = 0; + pCur->info.nSize = 0; + if( pRoot->nCell==0 && !pRoot->leaf ){ + Pgno subpage; + assert( pRoot->pgno==1 ); + subpage = get4byte(&pRoot->aData[pRoot->hdrOffset+8]); + assert( subpage>0 ); + pCur->isValid = 1; + rc = moveToChild(pCur, subpage); + } + pCur->isValid = pCur->pPage->nCell>0; + return rc; +} + +/* +** Move the cursor down to the left-most leaf entry beneath the +** entry to which it is currently pointing. +*/ +static int moveToLeftmost(BtCursor *pCur){ + Pgno pgno; + int rc; + MemPage *pPage; + + assert( pCur->isValid ); + while( !(pPage = pCur->pPage)->leaf ){ + assert( pCur->idx>=0 && pCur->idxnCell ); + pgno = get4byte(findCell(pPage, pCur->idx)); + rc = moveToChild(pCur, pgno); + if( rc ) return rc; + } + return SQLITE_OK; +} + +/* +** Move the cursor down to the right-most leaf entry beneath the +** page to which it is currently pointing. Notice the difference +** between moveToLeftmost() and moveToRightmost(). moveToLeftmost() +** finds the left-most entry beneath the *entry* whereas moveToRightmost() +** finds the right-most entry beneath the *page*. +*/ +static int moveToRightmost(BtCursor *pCur){ + Pgno pgno; + int rc; + MemPage *pPage; + + assert( pCur->isValid ); + while( !(pPage = pCur->pPage)->leaf ){ + pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]); + pCur->idx = pPage->nCell; + rc = moveToChild(pCur, pgno); + if( rc ) return rc; + } + pCur->idx = pPage->nCell - 1; + pCur->info.nSize = 0; + return SQLITE_OK; +} + +/* Move the cursor to the first entry in the table. Return SQLITE_OK +** on success. Set *pRes to 0 if the cursor actually points to something +** or set *pRes to 1 if the table is empty. +*/ +int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){ + int rc; + if( pCur->status ){ + return pCur->status; + } + rc = moveToRoot(pCur); + if( rc ) return rc; + if( pCur->isValid==0 ){ + assert( pCur->pPage->nCell==0 ); + *pRes = 1; + return SQLITE_OK; + } + assert( pCur->pPage->nCell>0 ); + *pRes = 0; + rc = moveToLeftmost(pCur); + return rc; +} + +/* Move the cursor to the last entry in the table. Return SQLITE_OK +** on success. Set *pRes to 0 if the cursor actually points to something +** or set *pRes to 1 if the table is empty. +*/ +int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ + int rc; + if( pCur->status ){ + return pCur->status; + } + rc = moveToRoot(pCur); + if( rc ) return rc; + if( pCur->isValid==0 ){ + assert( pCur->pPage->nCell==0 ); + *pRes = 1; + return SQLITE_OK; + } + assert( pCur->isValid ); + *pRes = 0; + rc = moveToRightmost(pCur); + return rc; +} + +/* Move the cursor so that it points to an entry near pKey/nKey. +** Return a success code. +** +** For INTKEY tables, only the nKey parameter is used. pKey is +** ignored. For other tables, nKey is the number of bytes of data +** in nKey. The comparison function specified when the cursor was +** created is used to compare keys. +** +** If an exact match is not found, then the cursor is always +** left pointing at a leaf page which would hold the entry if it +** were present. The cursor might point to an entry that comes +** before or after the key. +** +** The result of comparing the key with the entry to which the +** cursor is written to *pRes if pRes!=NULL. The meaning of +** this value is as follows: +** +** *pRes<0 The cursor is left pointing at an entry that +** is smaller than pKey or if the table is empty +** and the cursor is therefore left point to nothing. +** +** *pRes==0 The cursor is left pointing at an entry that +** exactly matches pKey. +** +** *pRes>0 The cursor is left pointing at an entry that +** is larger than pKey. +*/ +int sqlite3BtreeMoveto(BtCursor *pCur, const void *pKey, i64 nKey, int *pRes){ + int rc; + + if( pCur->status ){ + return pCur->status; + } + rc = moveToRoot(pCur); + if( rc ) return rc; + assert( pCur->pPage ); + assert( pCur->pPage->isInit ); + if( pCur->isValid==0 ){ + *pRes = -1; + assert( pCur->pPage->nCell==0 ); + return SQLITE_OK; + } + for(;;){ + int lwr, upr; + Pgno chldPg; + MemPage *pPage = pCur->pPage; + int c = -1; /* pRes return if table is empty must be -1 */ + lwr = 0; + upr = pPage->nCell-1; + pageIntegrity(pPage); + while( lwr<=upr ){ + void *pCellKey; + i64 nCellKey; + pCur->idx = (lwr+upr)/2; + pCur->info.nSize = 0; + sqlite3BtreeKeySize(pCur, &nCellKey); + if( pPage->intKey ){ + if( nCellKeynKey ){ + c = +1; + }else{ + c = 0; + } + }else{ + int available; + pCellKey = (void *)fetchPayload(pCur, &available, 0); + if( available>=nCellKey ){ + c = pCur->xCompare(pCur->pArg, nCellKey, pCellKey, nKey, pKey); + }else{ + pCellKey = sqliteMallocRaw( nCellKey ); + if( pCellKey==0 ) return SQLITE_NOMEM; + rc = sqlite3BtreeKey(pCur, 0, nCellKey, (void *)pCellKey); + c = pCur->xCompare(pCur->pArg, nCellKey, pCellKey, nKey, pKey); + sqliteFree(pCellKey); + if( rc ) return rc; + } + } + if( c==0 ){ + if( pPage->leafData && !pPage->leaf ){ + lwr = pCur->idx; + upr = lwr - 1; + break; + }else{ + if( pRes ) *pRes = 0; + return SQLITE_OK; + } + } + if( c<0 ){ + lwr = pCur->idx+1; + }else{ + upr = pCur->idx-1; + } + } + assert( lwr==upr+1 ); + assert( pPage->isInit ); + if( pPage->leaf ){ + chldPg = 0; + }else if( lwr>=pPage->nCell ){ + chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); + }else{ + chldPg = get4byte(findCell(pPage, lwr)); + } + if( chldPg==0 ){ + assert( pCur->idx>=0 && pCur->idxpPage->nCell ); + if( pRes ) *pRes = c; + return SQLITE_OK; + } + pCur->idx = lwr; + pCur->info.nSize = 0; + rc = moveToChild(pCur, chldPg); + if( rc ){ + return rc; + } + } + /* NOT REACHED */ +} + +/* +** Return TRUE if the cursor is not pointing at an entry of the table. +** +** TRUE will be returned after a call to sqlite3BtreeNext() moves +** past the last entry in the table or sqlite3BtreePrev() moves past +** the first entry. TRUE is also returned if the table is empty. +*/ +int sqlite3BtreeEof(BtCursor *pCur){ + return pCur->isValid==0; +} + +/* +** Advance the cursor to the next entry in the database. If +** successful then set *pRes=0. If the cursor +** was already pointing to the last entry in the database before +** this routine was called, then set *pRes=1. +*/ +int sqlite3BtreeNext(BtCursor *pCur, int *pRes){ + int rc; + MemPage *pPage = pCur->pPage; + + assert( pRes!=0 ); + if( pCur->isValid==0 ){ + *pRes = 1; + return SQLITE_OK; + } + assert( pPage->isInit ); + assert( pCur->idxnCell ); + pCur->idx++; + pCur->info.nSize = 0; + if( pCur->idx>=pPage->nCell ){ + if( !pPage->leaf ){ + rc = moveToChild(pCur, get4byte(&pPage->aData[pPage->hdrOffset+8])); + if( rc ) return rc; + rc = moveToLeftmost(pCur); + *pRes = 0; + return rc; + } + do{ + if( isRootPage(pPage) ){ + *pRes = 1; + pCur->isValid = 0; + return SQLITE_OK; + } + moveToParent(pCur); + pPage = pCur->pPage; + }while( pCur->idx>=pPage->nCell ); + *pRes = 0; + if( pPage->leafData ){ + rc = sqlite3BtreeNext(pCur, pRes); + }else{ + rc = SQLITE_OK; + } + return rc; + } + *pRes = 0; + if( pPage->leaf ){ + return SQLITE_OK; + } + rc = moveToLeftmost(pCur); + return rc; +} + +/* +** Step the cursor to the back to the previous entry in the database. If +** successful then set *pRes=0. If the cursor +** was already pointing to the first entry in the database before +** this routine was called, then set *pRes=1. +*/ +int sqlite3BtreePrevious(BtCursor *pCur, int *pRes){ + int rc; + Pgno pgno; + MemPage *pPage; + if( pCur->isValid==0 ){ + *pRes = 1; + return SQLITE_OK; + } + pPage = pCur->pPage; + assert( pPage->isInit ); + assert( pCur->idx>=0 ); + if( !pPage->leaf ){ + pgno = get4byte( findCell(pPage, pCur->idx) ); + rc = moveToChild(pCur, pgno); + if( rc ) return rc; + rc = moveToRightmost(pCur); + }else{ + while( pCur->idx==0 ){ + if( isRootPage(pPage) ){ + pCur->isValid = 0; + *pRes = 1; + return SQLITE_OK; + } + moveToParent(pCur); + pPage = pCur->pPage; + } + pCur->idx--; + pCur->info.nSize = 0; + if( pPage->leafData ){ + rc = sqlite3BtreePrevious(pCur, pRes); + }else{ + rc = SQLITE_OK; + } + } + *pRes = 0; + return rc; +} + +/* +** The TRACE macro will print high-level status information about the +** btree operation when the global variable sqlite3_btree_trace is +** enabled. +*/ +#if SQLITE_TEST +# define TRACE(X) if( sqlite3_btree_trace )\ + { sqlite3DebugPrintf X; fflush(stdout); } +#else +# define TRACE(X) +#endif +int sqlite3_btree_trace=0; /* True to enable tracing */ + +/* +** Allocate a new page from the database file. +** +** The new page is marked as dirty. (In other words, sqlite3pager_write() +** has already been called on the new page.) The new page has also +** been referenced and the calling routine is responsible for calling +** sqlite3pager_unref() on the new page when it is done. +** +** SQLITE_OK is returned on success. Any other return value indicates +** an error. *ppPage and *pPgno are undefined in the event of an error. +** Do not invoke sqlite3pager_unref() on *ppPage if an error is returned. +** +** If the "nearby" parameter is not 0, then a (feeble) effort is made to +** locate a page close to the page number "nearby". This can be used in an +** attempt to keep related pages close to each other in the database file, +** which in turn can make database access faster. +*/ +static int allocatePage(Btree *pBt, MemPage **ppPage, Pgno *pPgno, Pgno nearby){ + MemPage *pPage1; + int rc; + int n; /* Number of pages on the freelist */ + int k; /* Number of leaves on the trunk of the freelist */ + + pPage1 = pBt->pPage1; + n = get4byte(&pPage1->aData[36]); + if( n>0 ){ + /* There are pages on the freelist. Reuse one of those pages. */ + MemPage *pTrunk; + rc = sqlite3pager_write(pPage1->aData); + if( rc ) return rc; + put4byte(&pPage1->aData[36], n-1); + rc = getPage(pBt, get4byte(&pPage1->aData[32]), &pTrunk); + if( rc ) return rc; + rc = sqlite3pager_write(pTrunk->aData); + if( rc ){ + releasePage(pTrunk); + return rc; + } + k = get4byte(&pTrunk->aData[4]); + if( k==0 ){ + /* The trunk has no leaves. So extract the trunk page itself and + ** use it as the newly allocated page */ + *pPgno = get4byte(&pPage1->aData[32]); + memcpy(&pPage1->aData[32], &pTrunk->aData[0], 4); + *ppPage = pTrunk; + TRACE(("ALLOCATE: %d trunk - %d free pages left\n", *pPgno, n-1)); + }else if( k>pBt->usableSize/4 - 8 ){ + /* Value of k is out of range. Database corruption */ + return SQLITE_CORRUPT; /* bkpt-CORRUPT */ + }else{ + /* Extract a leaf from the trunk */ + int closest; + unsigned char *aData = pTrunk->aData; + if( nearby>0 ){ + int i, dist; + closest = 0; + dist = get4byte(&aData[8]) - nearby; + if( dist<0 ) dist = -dist; + for(i=1; isqlite3pager_pagecount(pBt->pPager) ){ + /* Free page off the end of the file */ + return SQLITE_CORRUPT; /* bkpt-CORRUPT */ + } + TRACE(("ALLOCATE: %d was leaf %d of %d on trunk %d: %d more free pages\n", + *pPgno, closest+1, k, pTrunk->pgno, n-1)); + if( closestaData); + rc = sqlite3pager_write((*ppPage)->aData); + } + } + }else{ + /* There are no pages on the freelist, so create a new page at the + ** end of the file */ + *pPgno = sqlite3pager_pagecount(pBt->pPager) + 1; + rc = getPage(pBt, *pPgno, ppPage); + if( rc ) return rc; + rc = sqlite3pager_write((*ppPage)->aData); + TRACE(("ALLOCATE: %d from end of file\n", *pPgno)); + } + return rc; +} + +/* +** Add a page of the database file to the freelist. +** +** sqlite3pager_unref() is NOT called for pPage. +*/ +static int freePage(MemPage *pPage){ + Btree *pBt = pPage->pBt; + MemPage *pPage1 = pBt->pPage1; + int rc, n, k; + + /* Prepare the page for freeing */ + assert( pPage->pgno>1 ); + pPage->isInit = 0; + releasePage(pPage->pParent); + pPage->pParent = 0; + + /* Increment the free page count on pPage1 */ + rc = sqlite3pager_write(pPage1->aData); + if( rc ) return rc; + n = get4byte(&pPage1->aData[36]); + put4byte(&pPage1->aData[36], n+1); + + if( n==0 ){ + /* This is the first free page */ + rc = sqlite3pager_write(pPage->aData); + if( rc ) return rc; + memset(pPage->aData, 0, 8); + put4byte(&pPage1->aData[32], pPage->pgno); + TRACE(("FREE-PAGE: %d first\n", pPage->pgno)); + }else{ + /* Other free pages already exist. Retrive the first trunk page + ** of the freelist and find out how many leaves it has. */ + MemPage *pTrunk; + rc = getPage(pBt, get4byte(&pPage1->aData[32]), &pTrunk); + if( rc ) return rc; + k = get4byte(&pTrunk->aData[4]); + if( k>=pBt->usableSize/4 - 8 ){ + /* The trunk is full. Turn the page being freed into a new + ** trunk page with no leaves. */ + rc = sqlite3pager_write(pPage->aData); + if( rc ) return rc; + put4byte(pPage->aData, pTrunk->pgno); + put4byte(&pPage->aData[4], 0); + put4byte(&pPage1->aData[32], pPage->pgno); + TRACE(("FREE-PAGE: %d new trunk page replacing %d\n", + pPage->pgno, pTrunk->pgno)); + }else{ + /* Add the newly freed page as a leaf on the current trunk */ + rc = sqlite3pager_write(pTrunk->aData); + if( rc ) return rc; + put4byte(&pTrunk->aData[4], k+1); + put4byte(&pTrunk->aData[8+k*4], pPage->pgno); + sqlite3pager_dont_write(pBt->pPager, pPage->pgno); + TRACE(("FREE-PAGE: %d leaf on trunk page %d\n",pPage->pgno,pTrunk->pgno)); + } + releasePage(pTrunk); + } + return rc; +} + +/* +** Free any overflow pages associated with the given Cell. +*/ +static int clearCell(MemPage *pPage, unsigned char *pCell){ + Btree *pBt = pPage->pBt; + CellInfo info; + Pgno ovflPgno; + int rc; + + parseCellPtr(pPage, pCell, &info); + if( info.iOverflow==0 ){ + return SQLITE_OK; /* No overflow pages. Return without doing anything */ + } + ovflPgno = get4byte(&pCell[info.iOverflow]); + while( ovflPgno!=0 ){ + MemPage *pOvfl; + rc = getPage(pBt, ovflPgno, &pOvfl); + if( rc ) return rc; + ovflPgno = get4byte(pOvfl->aData); + rc = freePage(pOvfl); + if( rc ) return rc; + sqlite3pager_unref(pOvfl->aData); + } + return SQLITE_OK; +} + +/* +** Create the byte sequence used to represent a cell on page pPage +** and write that byte sequence into pCell[]. Overflow pages are +** allocated and filled in as necessary. The calling procedure +** is responsible for making sure sufficient space has been allocated +** for pCell[]. +** +** Note that pCell does not necessary need to point to the pPage->aData +** area. pCell might point to some temporary storage. The cell will +** be constructed in this temporary area then copied into pPage->aData +** later. +*/ +static int fillInCell( + MemPage *pPage, /* The page that contains the cell */ + unsigned char *pCell, /* Complete text of the cell */ + const void *pKey, i64 nKey, /* The key */ + const void *pData,int nData, /* The data */ + int *pnSize /* Write cell size here */ +){ + int nPayload; + const u8 *pSrc; + int nSrc, n, rc; + int spaceLeft; + MemPage *pOvfl = 0; + MemPage *pToRelease = 0; + unsigned char *pPrior; + unsigned char *pPayload; + Btree *pBt = pPage->pBt; + Pgno pgnoOvfl = 0; + int nHeader; + CellInfo info; + + /* Fill in the header. */ + nHeader = 0; + if( !pPage->leaf ){ + nHeader += 4; + } + if( pPage->hasData ){ + nHeader += putVarint(&pCell[nHeader], nData); + }else{ + nData = 0; + } + nHeader += putVarint(&pCell[nHeader], *(u64*)&nKey); + parseCellPtr(pPage, pCell, &info); + assert( info.nHeader==nHeader ); + assert( info.nKey==nKey ); + assert( info.nData==nData ); + + /* Fill in the payload */ + nPayload = nData; + if( pPage->intKey ){ + pSrc = pData; + nSrc = nData; + nData = 0; + }else{ + nPayload += nKey; + pSrc = pKey; + nSrc = nKey; + } + *pnSize = info.nSize; + spaceLeft = info.nLocal; + pPayload = &pCell[nHeader]; + pPrior = &pCell[info.iOverflow]; + + while( nPayload>0 ){ + if( spaceLeft==0 ){ + rc = allocatePage(pBt, &pOvfl, &pgnoOvfl, pgnoOvfl); + if( rc ){ + releasePage(pToRelease); + clearCell(pPage, pCell); + return rc; + } + put4byte(pPrior, pgnoOvfl); + releasePage(pToRelease); + pToRelease = pOvfl; + pPrior = pOvfl->aData; + put4byte(pPrior, 0); + pPayload = &pOvfl->aData[4]; + spaceLeft = pBt->usableSize - 4; + } + n = nPayload; + if( n>spaceLeft ) n = spaceLeft; + if( n>nSrc ) n = nSrc; + memcpy(pPayload, pSrc, n); + nPayload -= n; + pPayload += n; + pSrc += n; + nSrc -= n; + spaceLeft -= n; + if( nSrc==0 ){ + nSrc = nData; + pSrc = pData; + } + } + releasePage(pToRelease); + return SQLITE_OK; +} + +/* +** Change the MemPage.pParent pointer on the page whose number is +** given in the second argument so that MemPage.pParent holds the +** pointer in the third argument. +*/ +static void reparentPage(Btree *pBt, Pgno pgno, MemPage *pNewParent, int idx){ + MemPage *pThis; + unsigned char *aData; + + if( pgno==0 ) return; + assert( pBt->pPager!=0 ); + aData = sqlite3pager_lookup(pBt->pPager, pgno); + if( aData ){ + pThis = (MemPage*)&aData[pBt->pageSize]; + assert( pThis->aData==aData ); + if( pThis->isInit ){ + if( pThis->pParent!=pNewParent ){ + if( pThis->pParent ) sqlite3pager_unref(pThis->pParent->aData); + pThis->pParent = pNewParent; + if( pNewParent ) sqlite3pager_ref(pNewParent->aData); + } + pThis->idxParent = idx; + } + sqlite3pager_unref(aData); + } +} + +/* +** Change the pParent pointer of all children of pPage to point back +** to pPage. +** +** In other words, for every child of pPage, invoke reparentPage() +** to make sure that each child knows that pPage is its parent. +** +** This routine gets called after you memcpy() one page into +** another. +*/ +static void reparentChildPages(MemPage *pPage){ + int i; + Btree *pBt; + + if( pPage->leaf ) return; + pBt = pPage->pBt; + for(i=0; inCell; i++){ + reparentPage(pBt, get4byte(findCell(pPage,i)), pPage, i); + } + reparentPage(pBt, get4byte(&pPage->aData[pPage->hdrOffset+8]), pPage, i); + pPage->idxShift = 0; +} + +/* +** Remove the i-th cell from pPage. This routine effects pPage only. +** The cell content is not freed or deallocated. It is assumed that +** the cell content has been copied someplace else. This routine just +** removes the reference to the cell from pPage. +** +** "sz" must be the number of bytes in the cell. +*/ +static void dropCell(MemPage *pPage, int idx, int sz){ + int i; /* Loop counter */ + int pc; /* Offset to cell content of cell being deleted */ + u8 *data; /* pPage->aData */ + u8 *ptr; /* Used to move bytes around within data[] */ + + assert( idx>=0 && idxnCell ); + assert( sz==cellSize(pPage, idx) ); + assert( sqlite3pager_iswriteable(pPage->aData) ); + data = pPage->aData; + ptr = &data[pPage->cellOffset + 2*idx]; + pc = get2byte(ptr); + assert( pc>10 && pc+sz<=pPage->pBt->usableSize ); + freeSpace(pPage, pc, sz); + for(i=idx+1; inCell; i++, ptr+=2){ + ptr[0] = ptr[2]; + ptr[1] = ptr[3]; + } + pPage->nCell--; + put2byte(&data[pPage->hdrOffset+3], pPage->nCell); + pPage->nFree += 2; + pPage->idxShift = 1; +} + +/* +** Insert a new cell on pPage at cell index "i". pCell points to the +** content of the cell. +** +** If the cell content will fit on the page, then put it there. If it +** will not fit, then make a copy of the cell content into pTemp if +** pTemp is not null. Regardless of pTemp, allocate a new entry +** in pPage->aOvfl[] and make it point to the cell content (either +** in pTemp or the original pCell) and also record its index. +** Allocating a new entry in pPage->aCell[] implies that +** pPage->nOverflow is incremented. +*/ +static void insertCell( + MemPage *pPage, /* Page into which we are copying */ + int i, /* New cell becomes the i-th cell of the page */ + u8 *pCell, /* Content of the new cell */ + int sz, /* Bytes of content in pCell */ + u8 *pTemp /* Temp storage space for pCell, if needed */ +){ + int idx; /* Where to write new cell content in data[] */ + int j; /* Loop counter */ + int top; /* First byte of content for any cell in data[] */ + int end; /* First byte past the last cell pointer in data[] */ + int ins; /* Index in data[] where new cell pointer is inserted */ + int hdr; /* Offset into data[] of the page header */ + int cellOffset; /* Address of first cell pointer in data[] */ + u8 *data; /* The content of the whole page */ + u8 *ptr; /* Used for moving information around in data[] */ + + assert( i>=0 && i<=pPage->nCell+pPage->nOverflow ); + assert( sz==cellSizePtr(pPage, pCell) ); + assert( sqlite3pager_iswriteable(pPage->aData) ); + if( pPage->nOverflow || sz+2>pPage->nFree ){ + if( pTemp ){ + memcpy(pTemp, pCell, sz); + pCell = pTemp; + } + j = pPage->nOverflow++; + assert( jaOvfl)/sizeof(pPage->aOvfl[0]) ); + pPage->aOvfl[j].pCell = pCell; + pPage->aOvfl[j].idx = i; + pPage->nFree = 0; + }else{ + data = pPage->aData; + hdr = pPage->hdrOffset; + top = get2byte(&data[hdr+5]); + cellOffset = pPage->cellOffset; + end = cellOffset + 2*pPage->nCell + 2; + ins = cellOffset + 2*i; + if( end > top - sz ){ + defragmentPage(pPage); + top = get2byte(&data[hdr+5]); + assert( end + sz <= top ); + } + idx = allocateSpace(pPage, sz); + assert( idx>0 ); + assert( end <= get2byte(&data[hdr+5]) ); + pPage->nCell++; + pPage->nFree -= 2; + memcpy(&data[idx], pCell, sz); + for(j=end-2, ptr=&data[j]; j>ins; j-=2, ptr-=2){ + ptr[0] = ptr[-2]; + ptr[1] = ptr[-1]; + } + put2byte(&data[ins], idx); + put2byte(&data[hdr+3], pPage->nCell); + pPage->idxShift = 1; + pageIntegrity(pPage); + } +} + +/* +** Add a list of cells to a page. The page should be initially empty. +** The cells are guaranteed to fit on the page. +*/ +static void assemblePage( + MemPage *pPage, /* The page to be assemblied */ + int nCell, /* The number of cells to add to this page */ + u8 **apCell, /* Pointers to cell bodies */ + int *aSize /* Sizes of the cells */ +){ + int i; /* Loop counter */ + int totalSize; /* Total size of all cells */ + int hdr; /* Index of page header */ + int cellptr; /* Address of next cell pointer */ + int cellbody; /* Address of next cell body */ + u8 *data; /* Data for the page */ + + assert( pPage->nOverflow==0 ); + totalSize = 0; + for(i=0; inFree ); + assert( pPage->nCell==0 ); + cellptr = pPage->cellOffset; + data = pPage->aData; + hdr = pPage->hdrOffset; + put2byte(&data[hdr+3], nCell); + cellbody = allocateSpace(pPage, totalSize); + assert( cellbody>0 ); + assert( pPage->nFree >= 2*nCell ); + pPage->nFree -= 2*nCell; + for(i=0; ipBt->usableSize ); + pPage->nCell = nCell; +} + +/* +** GCC does not define the offsetof() macro so we'll have to do it +** ourselves. +*/ +#ifndef offsetof +#define offsetof(STRUCTURE,FIELD) ((int)((char*)&((STRUCTURE*)0)->FIELD)) +#endif + +/* +** The following parameters determine how many adjacent pages get involved +** in a balancing operation. NN is the number of neighbors on either side +** of the page that participate in the balancing operation. NB is the +** total number of pages that participate, including the target page and +** NN neighbors on either side. +** +** The minimum value of NN is 1 (of course). Increasing NN above 1 +** (to 2 or 3) gives a modest improvement in SELECT and DELETE performance +** in exchange for a larger degradation in INSERT and UPDATE performance. +** The value of NN appears to give the best results overall. +*/ +#define NN 1 /* Number of neighbors on either side of pPage */ +#define NB (NN*2+1) /* Total pages involved in the balance */ + +/* Forward reference */ +static int balance(MemPage*); + +/* +** This routine redistributes Cells on pPage and up to NN*2 siblings +** of pPage so that all pages have about the same amount of free space. +** Usually NN siblings on either side of pPage is used in the balancing, +** though more siblings might come from one side if pPage is the first +** or last child of its parent. If pPage has fewer than 2*NN siblings +** (something which can only happen if pPage is the root page or a +** child of root) then all available siblings participate in the balancing. +** +** The number of siblings of pPage might be increased or decreased by one or +** two in an effort to keep pages nearly full but not over full. The root page +** is special and is allowed to be nearly empty. If pPage is +** the root page, then the depth of the tree might be increased +** or decreased by one, as necessary, to keep the root page from being +** overfull or completely empty. +** +** Note that when this routine is called, some of the Cells on pPage +** might not actually be stored in pPage->aData[]. This can happen +** if the page is overfull. Part of the job of this routine is to +** make sure all Cells for pPage once again fit in pPage->aData[]. +** +** In the course of balancing the siblings of pPage, the parent of pPage +** might become overfull or underfull. If that happens, then this routine +** is called recursively on the parent. +** +** If this routine fails for any reason, it might leave the database +** in a corrupted state. So if this routine fails, the database should +** be rolled back. +*/ +static int balance_nonroot(MemPage *pPage){ + MemPage *pParent; /* The parent of pPage */ + Btree *pBt; /* The whole database */ + int nCell = 0; /* Number of cells in aCell[] */ + int nOld; /* Number of pages in apOld[] */ + int nNew; /* Number of pages in apNew[] */ + int nDiv; /* Number of cells in apDiv[] */ + int i, j, k; /* Loop counters */ + int idx; /* Index of pPage in pParent->aCell[] */ + int nxDiv; /* Next divider slot in pParent->aCell[] */ + int rc; /* The return code */ + int leafCorrection; /* 4 if pPage is a leaf. 0 if not */ + int leafData; /* True if pPage is a leaf of a LEAFDATA tree */ + int usableSpace; /* Bytes in pPage beyond the header */ + int pageFlags; /* Value of pPage->aData[0] */ + int subtotal; /* Subtotal of bytes in cells on one page */ + int iSpace = 0; /* First unused byte of aSpace[] */ + int mxCellPerPage; /* Maximum number of cells in one page */ + MemPage *apOld[NB]; /* pPage and up to two siblings */ + Pgno pgnoOld[NB]; /* Page numbers for each page in apOld[] */ + MemPage *apCopy[NB]; /* Private copies of apOld[] pages */ + MemPage *apNew[NB+2]; /* pPage and up to NB siblings after balancing */ + Pgno pgnoNew[NB+2]; /* Page numbers for each page in apNew[] */ + int idxDiv[NB]; /* Indices of divider cells in pParent */ + u8 *apDiv[NB]; /* Divider cells in pParent */ + int cntNew[NB+2]; /* Index in aCell[] of cell after i-th page */ + int szNew[NB+2]; /* Combined size of cells place on i-th page */ + u8 **apCell; /* All cells begin balanced */ + int *szCell; /* Local size of all cells in apCell[] */ + u8 *aCopy[NB]; /* Space for holding data of apCopy[] */ + u8 *aSpace; /* Space to hold copies of dividers cells */ + + /* + ** Find the parent page. + */ + assert( pPage->isInit ); + assert( sqlite3pager_iswriteable(pPage->aData) ); + pBt = pPage->pBt; + pParent = pPage->pParent; + sqlite3pager_write(pParent->aData); + assert( pParent ); + TRACE(("BALANCE: begin page %d child of %d\n", pPage->pgno, pParent->pgno)); + + /* + ** Allocate space for memory structures + */ + mxCellPerPage = MX_CELL(pBt); + apCell = sqliteMallocRaw( + (mxCellPerPage+2)*NB*(sizeof(u8*)+sizeof(int)) + + sizeof(MemPage)*NB + + pBt->pageSize*(5+NB) + ); + if( apCell==0 ){ + return SQLITE_NOMEM; + } + szCell = (int*)&apCell[(mxCellPerPage+2)*NB]; + aCopy[0] = (u8*)&szCell[(mxCellPerPage+2)*NB]; + for(i=1; ipageSize+sizeof(MemPage)]; + } + aSpace = &aCopy[NB-1][pBt->pageSize+sizeof(MemPage)]; + + /* + ** Find the cell in the parent page whose left child points back + ** to pPage. The "idx" variable is the index of that cell. If pPage + ** is the rightmost child of pParent then set idx to pParent->nCell + */ + if( pParent->idxShift ){ + Pgno pgno; + pgno = pPage->pgno; + assert( pgno==sqlite3pager_pagenumber(pPage->aData) ); + for(idx=0; idxnCell; idx++){ + if( get4byte(findCell(pParent, idx))==pgno ){ + break; + } + } + assert( idxnCell + || get4byte(&pParent->aData[pParent->hdrOffset+8])==pgno ); + }else{ + idx = pPage->idxParent; + } + + /* + ** Initialize variables so that it will be safe to jump + ** directly to balance_cleanup at any moment. + */ + nOld = nNew = 0; + sqlite3pager_ref(pParent->aData); + + /* + ** Find sibling pages to pPage and the cells in pParent that divide + ** the siblings. An attempt is made to find NN siblings on either + ** side of pPage. More siblings are taken from one side, however, if + ** pPage there are fewer than NN siblings on the other side. If pParent + ** has NB or fewer children then all children of pParent are taken. + */ + nxDiv = idx - NN; + if( nxDiv + NB > pParent->nCell ){ + nxDiv = pParent->nCell - NB + 1; + } + if( nxDiv<0 ){ + nxDiv = 0; + } + nDiv = 0; + for(i=0, k=nxDiv; inCell ){ + idxDiv[i] = k; + apDiv[i] = findCell(pParent, k); + nDiv++; + assert( !pParent->leaf ); + pgnoOld[i] = get4byte(apDiv[i]); + }else if( k==pParent->nCell ){ + pgnoOld[i] = get4byte(&pParent->aData[pParent->hdrOffset+8]); + }else{ + break; + } + rc = getAndInitPage(pBt, pgnoOld[i], &apOld[i], pParent); + if( rc ) goto balance_cleanup; + apOld[i]->idxParent = k; + apCopy[i] = 0; + assert( i==nOld ); + nOld++; + } + + /* + ** Make copies of the content of pPage and its siblings into aOld[]. + ** The rest of this function will use data from the copies rather + ** that the original pages since the original pages will be in the + ** process of being overwritten. + */ + for(i=0; ipageSize]; + p->aData = &((u8*)p)[-pBt->pageSize]; + memcpy(p->aData, apOld[i]->aData, pBt->pageSize + sizeof(MemPage)); + p->aData = &((u8*)p)[-pBt->pageSize]; + } + + /* + ** Load pointers to all cells on sibling pages and the divider cells + ** into the local apCell[] array. Make copies of the divider cells + ** into space obtained form aSpace[] and remove the the divider Cells + ** from pParent. + ** + ** If the siblings are on leaf pages, then the child pointers of the + ** divider cells are stripped from the cells before they are copied + ** into aSpace[]. In this way, all cells in apCell[] are without + ** child pointers. If siblings are not leaves, then all cell in + ** apCell[] include child pointers. Either way, all cells in apCell[] + ** are alike. + ** + ** leafCorrection: 4 if pPage is a leaf. 0 if pPage is not a leaf. + ** leafData: 1 if pPage holds key+data and pParent holds only keys. + */ + nCell = 0; + leafCorrection = pPage->leaf*4; + leafData = pPage->leafData && pPage->leaf; + for(i=0; inCell+pOld->nOverflow; + for(j=0; jpageSize*5 ); + memcpy(pTemp, apDiv[i], sz); + apCell[nCell] = pTemp+leafCorrection; + dropCell(pParent, nxDiv, sz); + szCell[nCell] -= leafCorrection; + assert( get4byte(pTemp)==pgnoOld[i] ); + if( !pOld->leaf ){ + assert( leafCorrection==0 ); + /* The right pointer of the child page pOld becomes the left + ** pointer of the divider cell */ + memcpy(apCell[nCell], &pOld->aData[pOld->hdrOffset+8], 4); + }else{ + assert( leafCorrection==4 ); + } + nCell++; + } + } + } + + /* + ** Figure out the number of pages needed to hold all nCell cells. + ** Store this number in "k". Also compute szNew[] which is the total + ** size of all cells on the i-th page and cntNew[] which is the index + ** in apCell[] of the cell that divides page i from page i+1. + ** cntNew[k] should equal nCell. + ** + ** Values computed by this block: + ** + ** k: The total number of sibling pages + ** szNew[i]: Spaced used on the i-th sibling page. + ** cntNew[i]: Index in apCell[] and szCell[] for the first cell to + ** the right of the i-th sibling page. + ** usableSpace: Number of bytes of space available on each sibling. + ** + */ + usableSpace = pBt->usableSize - 12 + leafCorrection; + for(subtotal=k=i=0; i usableSpace ){ + szNew[k] = subtotal - szCell[i]; + cntNew[k] = i; + if( leafData ){ i--; } + subtotal = 0; + k++; + } + } + szNew[k] = subtotal; + cntNew[k] = nCell; + k++; + + /* + ** The packing computed by the previous block is biased toward the siblings + ** on the left side. The left siblings are always nearly full, while the + ** right-most sibling might be nearly empty. This block of code attempts + ** to adjust the packing of siblings to get a better balance. + ** + ** This adjustment is more than an optimization. The packing above might + ** be so out of balance as to be illegal. For example, the right-most + ** sibling might be completely empty. This adjustment is not optional. + */ + for(i=k-1; i>0; i--){ + int szRight = szNew[i]; /* Size of sibling on the right */ + int szLeft = szNew[i-1]; /* Size of sibling on the left */ + int r; /* Index of right-most cell in left sibling */ + int d; /* Index of first cell to the left of right sibling */ + + r = cntNew[i-1] - 1; + d = r + 1 - leafData; + while( szRight==0 || szRight+szCell[d]+2<=szLeft-(szCell[r]+2) ){ + szRight += szCell[d] + 2; + szLeft -= szCell[r] + 2; + cntNew[i-1]--; + r = cntNew[i-1] - 1; + d = r + 1 - leafData; + } + szNew[i] = szRight; + szNew[i-1] = szLeft; + } + assert( cntNew[0]>0 ); + + /* + ** Allocate k new pages. Reuse old pages where possible. + */ + assert( pPage->pgno>1 ); + pageFlags = pPage->aData[0]; + for(i=0; iaData); + }else{ + rc = allocatePage(pBt, &pNew, &pgnoNew[i], pgnoNew[i-1]); + if( rc ) goto balance_cleanup; + apNew[i] = pNew; + } + nNew++; + zeroPage(pNew, pageFlags); + } + + /* Free any old pages that were not reused as new pages. + */ + while( ii ){ + int t; + MemPage *pT; + t = pgnoNew[i]; + pT = apNew[i]; + pgnoNew[i] = pgnoNew[minI]; + apNew[i] = apNew[minI]; + pgnoNew[minI] = t; + apNew[minI] = pT; + } + } + TRACE(("BALANCE: old: %d %d %d new: %d(%d) %d(%d) %d(%d) %d(%d) %d(%d)\n", + pgnoOld[0], + nOld>=2 ? pgnoOld[1] : 0, + nOld>=3 ? pgnoOld[2] : 0, + pgnoNew[0], szNew[0], + nNew>=2 ? pgnoNew[1] : 0, nNew>=2 ? szNew[1] : 0, + nNew>=3 ? pgnoNew[2] : 0, nNew>=3 ? szNew[2] : 0, + nNew>=4 ? pgnoNew[3] : 0, nNew>=4 ? szNew[3] : 0, + nNew>=5 ? pgnoNew[4] : 0, nNew>=5 ? szNew[4] : 0)); + + + /* + ** Evenly distribute the data in apCell[] across the new pages. + ** Insert divider cells into pParent as necessary. + */ + j = 0; + for(i=0; ipgno==pgnoNew[i] ); + assemblePage(pNew, cntNew[i]-j, &apCell[j], &szCell[j]); + j = cntNew[i]; + assert( pNew->nCell>0 ); + assert( pNew->nOverflow==0 ); + if( ileaf ){ + memcpy(&pNew->aData[8], pCell, 4); + pTemp = 0; + }else if( leafData ){ + CellInfo info; + j--; + parseCellPtr(pNew, apCell[j], &info); + pCell = &aSpace[iSpace]; + fillInCell(pParent, pCell, 0, info.nKey, 0, 0, &sz); + iSpace += sz; + assert( iSpace<=pBt->pageSize*5 ); + pTemp = 0; + }else{ + pCell -= 4; + pTemp = &aSpace[iSpace]; + iSpace += sz; + assert( iSpace<=pBt->pageSize*5 ); + } + insertCell(pParent, nxDiv, pCell, sz, pTemp); + put4byte(findOverflowCell(pParent,nxDiv), pNew->pgno); + j++; + nxDiv++; + } + } + assert( j==nCell ); + if( (pageFlags & PTF_LEAF)==0 ){ + memcpy(&apNew[nNew-1]->aData[8], &apCopy[nOld-1]->aData[8], 4); + } + if( nxDiv==pParent->nCell+pParent->nOverflow ){ + /* Right-most sibling is the right-most child of pParent */ + put4byte(&pParent->aData[pParent->hdrOffset+8], pgnoNew[nNew-1]); + }else{ + /* Right-most sibling is the left child of the first entry in pParent + ** past the right-most divider entry */ + put4byte(findOverflowCell(pParent, nxDiv), pgnoNew[nNew-1]); + } + + /* + ** Reparent children of all cells. + */ + for(i=0; iisInit ); + /* assert( pPage->isInit ); // No! pPage might have been added to freelist */ + /* pageIntegrity(pPage); // No! pPage might have been added to freelist */ + rc = balance(pParent); + + /* + ** Cleanup before returning. + */ +balance_cleanup: + sqliteFree(apCell); + for(i=0; ipgno, nOld, nNew, nCell)); + return rc; +} + +/* +** This routine is called for the root page of a btree when the root +** page contains no cells. This is an opportunity to make the tree +** shallower by one level. +*/ +static int balance_shallower(MemPage *pPage){ + MemPage *pChild; /* The only child page of pPage */ + Pgno pgnoChild; /* Page number for pChild */ + int rc = SQLITE_OK; /* Return code from subprocedures */ + Btree *pBt; /* The main BTree structure */ + int mxCellPerPage; /* Maximum number of cells per page */ + u8 **apCell; /* All cells from pages being balanced */ + int *szCell; /* Local size of all cells */ + + assert( pPage->pParent==0 ); + assert( pPage->nCell==0 ); + pBt = pPage->pBt; + mxCellPerPage = MX_CELL(pBt); + apCell = sqliteMallocRaw( mxCellPerPage*(sizeof(u8*)+sizeof(int)) ); + if( apCell==0 ) return SQLITE_NOMEM; + szCell = (int*)&apCell[mxCellPerPage]; + if( pPage->leaf ){ + /* The table is completely empty */ + TRACE(("BALANCE: empty table %d\n", pPage->pgno)); + }else{ + /* The root page is empty but has one child. Transfer the + ** information from that one child into the root page if it + ** will fit. This reduces the depth of the tree by one. + ** + ** If the root page is page 1, it has less space available than + ** its child (due to the 100 byte header that occurs at the beginning + ** of the database fle), so it might not be able to hold all of the + ** information currently contained in the child. If this is the + ** case, then do not do the transfer. Leave page 1 empty except + ** for the right-pointer to the child page. The child page becomes + ** the virtual root of the tree. + */ + pgnoChild = get4byte(&pPage->aData[pPage->hdrOffset+8]); + assert( pgnoChild>0 ); + assert( pgnoChild<=sqlite3pager_pagecount(pPage->pBt->pPager) ); + rc = getPage(pPage->pBt, pgnoChild, &pChild); + if( rc ) goto end_shallow_balance; + if( pPage->pgno==1 ){ + rc = initPage(pChild, pPage); + if( rc ) goto end_shallow_balance; + assert( pChild->nOverflow==0 ); + if( pChild->nFree>=100 ){ + /* The child information will fit on the root page, so do the + ** copy */ + int i; + zeroPage(pPage, pChild->aData[0]); + for(i=0; inCell; i++){ + apCell[i] = findCell(pChild,i); + szCell[i] = cellSizePtr(pChild, apCell[i]); + } + assemblePage(pPage, pChild->nCell, apCell, szCell); + freePage(pChild); + TRACE(("BALANCE: child %d transfer to page 1\n", pChild->pgno)); + }else{ + /* The child has more information that will fit on the root. + ** The tree is already balanced. Do nothing. */ + TRACE(("BALANCE: child %d will not fit on page 1\n", pChild->pgno)); + } + }else{ + memcpy(pPage->aData, pChild->aData, pPage->pBt->usableSize); + pPage->isInit = 0; + pPage->pParent = 0; + rc = initPage(pPage, 0); + assert( rc==SQLITE_OK ); + freePage(pChild); + TRACE(("BALANCE: transfer child %d into root %d\n", + pChild->pgno, pPage->pgno)); + } + reparentChildPages(pPage); + releasePage(pChild); + } +end_shallow_balance: + sqliteFree(apCell); + return rc; +} + + +/* +** The root page is overfull +** +** When this happens, Create a new child page and copy the +** contents of the root into the child. Then make the root +** page an empty page with rightChild pointing to the new +** child. Finally, call balance_internal() on the new child +** to cause it to split. +*/ +static int balance_deeper(MemPage *pPage){ + int rc; /* Return value from subprocedures */ + MemPage *pChild; /* Pointer to a new child page */ + Pgno pgnoChild; /* Page number of the new child page */ + Btree *pBt; /* The BTree */ + int usableSize; /* Total usable size of a page */ + u8 *data; /* Content of the parent page */ + u8 *cdata; /* Content of the child page */ + int hdr; /* Offset to page header in parent */ + int brk; /* Offset to content of first cell in parent */ + + assert( pPage->pParent==0 ); + assert( pPage->nOverflow>0 ); + pBt = pPage->pBt; + rc = allocatePage(pBt, &pChild, &pgnoChild, pPage->pgno); + if( rc ) return rc; + assert( sqlite3pager_iswriteable(pChild->aData) ); + usableSize = pBt->usableSize; + data = pPage->aData; + hdr = pPage->hdrOffset; + brk = get2byte(&data[hdr+5]); + cdata = pChild->aData; + memcpy(cdata, &data[hdr], pPage->cellOffset+2*pPage->nCell-hdr); + memcpy(&cdata[brk], &data[brk], usableSize-brk); + rc = initPage(pChild, pPage); + if( rc ) return rc; + memcpy(pChild->aOvfl, pPage->aOvfl, pPage->nOverflow*sizeof(pPage->aOvfl[0])); + pChild->nOverflow = pPage->nOverflow; + if( pChild->nOverflow ){ + pChild->nFree = 0; + } + assert( pChild->nCell==pPage->nCell ); + zeroPage(pPage, pChild->aData[0] & ~PTF_LEAF); + put4byte(&pPage->aData[pPage->hdrOffset+8], pgnoChild); + TRACE(("BALANCE: copy root %d into %d\n", pPage->pgno, pChild->pgno)); + rc = balance_nonroot(pChild); + releasePage(pChild); + return rc; +} + +/* +** Decide if the page pPage needs to be balanced. If balancing is +** required, call the appropriate balancing routine. +*/ +static int balance(MemPage *pPage){ + int rc = SQLITE_OK; + if( pPage->pParent==0 ){ + if( pPage->nOverflow>0 ){ + rc = balance_deeper(pPage); + } + if( pPage->nCell==0 ){ + rc = balance_shallower(pPage); + } + }else{ + if( pPage->nOverflow>0 || pPage->nFree>pPage->pBt->usableSize*2/3 ){ + rc = balance_nonroot(pPage); + } + } + return rc; +} + +/* +** This routine checks all cursors that point to table pgnoRoot. +** If any of those cursors other than pExclude were opened with +** wrFlag==0 then this routine returns SQLITE_LOCKED. If all +** cursors that point to pgnoRoot were opened with wrFlag==1 +** then this routine returns SQLITE_OK. +** +** In addition to checking for read-locks (where a read-lock +** means a cursor opened with wrFlag==0) this routine also moves +** all cursors other than pExclude so that they are pointing to the +** first Cell on root page. This is necessary because an insert +** or delete might change the number of cells on a page or delete +** a page entirely and we do not want to leave any cursors +** pointing to non-existant pages or cells. +*/ +static int checkReadLocks(Btree *pBt, Pgno pgnoRoot, BtCursor *pExclude){ + BtCursor *p; + for(p=pBt->pCursor; p; p=p->pNext){ + if( p->pgnoRoot!=pgnoRoot || p==pExclude ) continue; + if( p->wrFlag==0 ) return SQLITE_LOCKED; + if( p->pPage->pgno!=p->pgnoRoot ){ + moveToRoot(p); + } + } + return SQLITE_OK; +} + +/* +** Insert a new record into the BTree. The key is given by (pKey,nKey) +** and the data is given by (pData,nData). The cursor is used only to +** define what table the record should be inserted into. The cursor +** is left pointing at a random location. +** +** For an INTKEY table, only the nKey value of the key is used. pKey is +** ignored. For a ZERODATA table, the pData and nData are both ignored. +*/ +int sqlite3BtreeInsert( + BtCursor *pCur, /* Insert data into the table of this cursor */ + const void *pKey, i64 nKey, /* The key of the new record */ + const void *pData, int nData /* The data of the new record */ +){ + int rc; + int loc; + int szNew; + MemPage *pPage; + Btree *pBt = pCur->pBt; + unsigned char *oldCell; + unsigned char *newCell = 0; + + if( pCur->status ){ + return pCur->status; /* A rollback destroyed this cursor */ + } + if( pBt->inTrans!=TRANS_WRITE ){ + /* Must start a transaction before doing an insert */ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + assert( !pBt->readOnly ); + if( !pCur->wrFlag ){ + return SQLITE_PERM; /* Cursor not open for writing */ + } + if( checkReadLocks(pBt, pCur->pgnoRoot, pCur) ){ + return SQLITE_LOCKED; /* The table pCur points to has a read lock */ + } + rc = sqlite3BtreeMoveto(pCur, pKey, nKey, &loc); + if( rc ) return rc; + pPage = pCur->pPage; + assert( pPage->intKey || nKey>=0 ); + assert( pPage->leaf || !pPage->leafData ); + TRACE(("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n", + pCur->pgnoRoot, nKey, nData, pPage->pgno, + loc==0 ? "overwrite" : "new entry")); + assert( pPage->isInit ); + rc = sqlite3pager_write(pPage->aData); + if( rc ) return rc; + newCell = sqliteMallocRaw( MX_CELL_SIZE(pBt) ); + if( newCell==0 ) return SQLITE_NOMEM; + rc = fillInCell(pPage, newCell, pKey, nKey, pData, nData, &szNew); + if( rc ) goto end_insert; + assert( szNew==cellSizePtr(pPage, newCell) ); + assert( szNew<=MX_CELL_SIZE(pBt) ); + if( loc==0 && pCur->isValid ){ + int szOld; + assert( pCur->idx>=0 && pCur->idxnCell ); + oldCell = findCell(pPage, pCur->idx); + if( !pPage->leaf ){ + memcpy(newCell, oldCell, 4); + } + szOld = cellSizePtr(pPage, oldCell); + rc = clearCell(pPage, oldCell); + if( rc ) goto end_insert; + dropCell(pPage, pCur->idx, szOld); + }else if( loc<0 && pPage->nCell>0 ){ + assert( pPage->leaf ); + pCur->idx++; + pCur->info.nSize = 0; + }else{ + assert( pPage->leaf ); + } + insertCell(pPage, pCur->idx, newCell, szNew, 0); + rc = balance(pPage); + /* sqlite3BtreePageDump(pCur->pBt, pCur->pgnoRoot, 1); */ + /* fflush(stdout); */ + moveToRoot(pCur); +end_insert: + sqliteFree(newCell); + return rc; +} + +/* +** Delete the entry that the cursor is pointing to. The cursor +** is left pointing at a random location. +*/ +int sqlite3BtreeDelete(BtCursor *pCur){ + MemPage *pPage = pCur->pPage; + unsigned char *pCell; + int rc; + Pgno pgnoChild = 0; + Btree *pBt = pCur->pBt; + + assert( pPage->isInit ); + if( pCur->status ){ + return pCur->status; /* A rollback destroyed this cursor */ + } + if( pBt->inTrans!=TRANS_WRITE ){ + /* Must start a transaction before doing a delete */ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + assert( !pBt->readOnly ); + if( pCur->idx >= pPage->nCell ){ + return SQLITE_ERROR; /* The cursor is not pointing to anything */ + } + if( !pCur->wrFlag ){ + return SQLITE_PERM; /* Did not open this cursor for writing */ + } + if( checkReadLocks(pBt, pCur->pgnoRoot, pCur) ){ + return SQLITE_LOCKED; /* The table pCur points to has a read lock */ + } + rc = sqlite3pager_write(pPage->aData); + if( rc ) return rc; + pCell = findCell(pPage, pCur->idx); + if( !pPage->leaf ){ + pgnoChild = get4byte(pCell); + } + clearCell(pPage, pCell); + if( !pPage->leaf ){ + /* + ** The entry we are about to delete is not a leaf so if we do not + ** do something we will leave a hole on an internal page. + ** We have to fill the hole by moving in a cell from a leaf. The + ** next Cell after the one to be deleted is guaranteed to exist and + ** to be a leaf so we can use it. + */ + BtCursor leafCur; + unsigned char *pNext; + int szNext; + int notUsed; + unsigned char *tempCell; + assert( !pPage->leafData ); + getTempCursor(pCur, &leafCur); + rc = sqlite3BtreeNext(&leafCur, ¬Used); + if( rc!=SQLITE_OK ){ + if( rc!=SQLITE_NOMEM ){ + rc = SQLITE_CORRUPT; /* bkpt-CORRUPT */ + } + return rc; + } + rc = sqlite3pager_write(leafCur.pPage->aData); + if( rc ) return rc; + TRACE(("DELETE: table=%d delete internal from %d replace from leaf %d\n", + pCur->pgnoRoot, pPage->pgno, leafCur.pPage->pgno)); + dropCell(pPage, pCur->idx, cellSizePtr(pPage, pCell)); + pNext = findCell(leafCur.pPage, leafCur.idx); + szNext = cellSizePtr(leafCur.pPage, pNext); + assert( MX_CELL_SIZE(pBt)>=szNext+4 ); + tempCell = sqliteMallocRaw( MX_CELL_SIZE(pBt) ); + if( tempCell==0 ) return SQLITE_NOMEM; + insertCell(pPage, pCur->idx, pNext-4, szNext+4, tempCell); + put4byte(findOverflowCell(pPage, pCur->idx), pgnoChild); + rc = balance(pPage); + sqliteFree(tempCell); + if( rc ) return rc; + dropCell(leafCur.pPage, leafCur.idx, szNext); + rc = balance(leafCur.pPage); + releaseTempCursor(&leafCur); + }else{ + TRACE(("DELETE: table=%d delete from leaf %d\n", + pCur->pgnoRoot, pPage->pgno)); + dropCell(pPage, pCur->idx, cellSizePtr(pPage, pCell)); + rc = balance(pPage); + } + moveToRoot(pCur); + return rc; +} + +/* +** Create a new BTree table. Write into *piTable the page +** number for the root page of the new table. +** +** The type of type is determined by the flags parameter. Only the +** following values of flags are currently in use. Other values for +** flags might not work: +** +** BTREE_INTKEY|BTREE_LEAFDATA Used for SQL tables with rowid keys +** BTREE_ZERODATA Used for SQL indices +*/ +int sqlite3BtreeCreateTable(Btree *pBt, int *piTable, int flags){ + MemPage *pRoot; + Pgno pgnoRoot; + int rc; + if( pBt->inTrans!=TRANS_WRITE ){ + /* Must start a transaction first */ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + if( pBt->readOnly ){ + return SQLITE_READONLY; + } + rc = allocatePage(pBt, &pRoot, &pgnoRoot, 1); + if( rc ) return rc; + assert( sqlite3pager_iswriteable(pRoot->aData) ); + zeroPage(pRoot, flags | PTF_LEAF); + sqlite3pager_unref(pRoot->aData); + *piTable = (int)pgnoRoot; + return SQLITE_OK; +} + +/* +** Erase the given database page and all its children. Return +** the page to the freelist. +*/ +static int clearDatabasePage( + Btree *pBt, /* The BTree that contains the table */ + Pgno pgno, /* Page number to clear */ + MemPage *pParent, /* Parent page. NULL for the root */ + int freePageFlag /* Deallocate page if true */ +){ + MemPage *pPage; + int rc; + unsigned char *pCell; + int i; + + rc = getAndInitPage(pBt, pgno, &pPage, pParent); + if( rc ) return rc; + rc = sqlite3pager_write(pPage->aData); + if( rc ) return rc; + for(i=0; inCell; i++){ + pCell = findCell(pPage, i); + if( !pPage->leaf ){ + rc = clearDatabasePage(pBt, get4byte(pCell), pPage->pParent, 1); + if( rc ) return rc; + } + rc = clearCell(pPage, pCell); + if( rc ) return rc; + } + if( !pPage->leaf ){ + rc = clearDatabasePage(pBt, get4byte(&pPage->aData[8]), pPage->pParent, 1); + if( rc ) return rc; + } + if( freePageFlag ){ + rc = freePage(pPage); + }else{ + zeroPage(pPage, pPage->aData[0] | PTF_LEAF); + } + releasePage(pPage); + return rc; +} + +/* +** Delete all information from a single table in the database. iTable is +** the page number of the root of the table. After this routine returns, +** the root page is empty, but still exists. +** +** This routine will fail with SQLITE_LOCKED if there are any open +** read cursors on the table. Open write cursors are moved to the +** root of the table. +*/ +int sqlite3BtreeClearTable(Btree *pBt, int iTable){ + int rc; + BtCursor *pCur; + if( pBt->inTrans!=TRANS_WRITE ){ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ + if( pCur->pgnoRoot==(Pgno)iTable ){ + if( pCur->wrFlag==0 ) return SQLITE_LOCKED; + moveToRoot(pCur); + } + } + rc = clearDatabasePage(pBt, (Pgno)iTable, 0, 0); + if( rc ){ + sqlite3BtreeRollback(pBt); + } + return rc; +} + +/* +** Erase all information in a table and add the root of the table to +** the freelist. Except, the root of the principle table (the one on +** page 1) is never added to the freelist. +** +** This routine will fail with SQLITE_LOCKED if there are any open +** cursors on the table. +*/ +int sqlite3BtreeDropTable(Btree *pBt, int iTable){ + int rc; + MemPage *pPage; + BtCursor *pCur; + if( pBt->inTrans!=TRANS_WRITE ){ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ + if( pCur->pgnoRoot==(Pgno)iTable ){ + return SQLITE_LOCKED; /* Cannot drop a table that has a cursor */ + } + } + rc = getPage(pBt, (Pgno)iTable, &pPage); + if( rc ) return rc; + rc = sqlite3BtreeClearTable(pBt, iTable); + if( rc ) return rc; + if( iTable>1 ){ + rc = freePage(pPage); + }else{ + zeroPage(pPage, PTF_INTKEY|PTF_LEAF ); + } + releasePage(pPage); + return rc; +} + + +/* +** Read the meta-information out of a database file. Meta[0] +** is the number of free pages currently in the database. Meta[1] +** through meta[15] are available for use by higher layers. Meta[0] +** is read-only, the others are read/write. +** +** The schema layer numbers meta values differently. At the schema +** layer (and the SetCookie and ReadCookie opcodes) the number of +** free pages is not visible. So Cookie[0] is the same as Meta[1]. +*/ +int sqlite3BtreeGetMeta(Btree *pBt, int idx, u32 *pMeta){ + int rc; + unsigned char *pP1; + + assert( idx>=0 && idx<=15 ); + rc = sqlite3pager_get(pBt->pPager, 1, (void**)&pP1); + if( rc ) return rc; + *pMeta = get4byte(&pP1[36 + idx*4]); + sqlite3pager_unref(pP1); + + /* The current implementation is unable to handle writes to an autovacuumed + ** database. So make such a database readonly. */ + if( idx==4 && *pMeta>0 ) pBt->readOnly = 1; + + return SQLITE_OK; +} + +/* +** Write meta-information back into the database. Meta[0] is +** read-only and may not be written. +*/ +int sqlite3BtreeUpdateMeta(Btree *pBt, int idx, u32 iMeta){ + unsigned char *pP1; + int rc; + assert( idx>=1 && idx<=15 ); + if( pBt->inTrans!=TRANS_WRITE ){ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + assert( pBt->pPage1!=0 ); + pP1 = pBt->pPage1->aData; + rc = sqlite3pager_write(pP1); + if( rc ) return rc; + put4byte(&pP1[36 + idx*4], iMeta); + return SQLITE_OK; +} + +/* +** Return the flag byte at the beginning of the page that the cursor +** is currently pointing to. +*/ +int sqlite3BtreeFlags(BtCursor *pCur){ + MemPage *pPage = pCur->pPage; + return pPage ? pPage->aData[pPage->hdrOffset] : 0; +} + +/* +** Print a disassembly of the given page on standard output. This routine +** is used for debugging and testing only. +*/ +#ifdef SQLITE_TEST +int sqlite3BtreePageDump(Btree *pBt, int pgno, int recursive){ + int rc; + MemPage *pPage; + int i, j, c; + int nFree; + u16 idx; + int hdr; + int nCell; + int isInit; + unsigned char *data; + char range[20]; + unsigned char payload[20]; + + rc = getPage(pBt, (Pgno)pgno, &pPage); + isInit = pPage->isInit; + if( pPage->isInit==0 ){ + initPage(pPage, 0); + } + if( rc ){ + return rc; + } + hdr = pPage->hdrOffset; + data = pPage->aData; + c = data[hdr]; + pPage->intKey = (c & (PTF_INTKEY|PTF_LEAFDATA))!=0; + pPage->zeroData = (c & PTF_ZERODATA)!=0; + pPage->leafData = (c & PTF_LEAFDATA)!=0; + pPage->leaf = (c & PTF_LEAF)!=0; + pPage->hasData = !(pPage->zeroData || (!pPage->leaf && pPage->leafData)); + nCell = get2byte(&data[hdr+3]); + sqlite3DebugPrintf("PAGE %d: flags=0x%02x frag=%d parent=%d\n", pgno, + data[hdr], data[hdr+7], + (pPage->isInit && pPage->pParent) ? pPage->pParent->pgno : 0); + assert( hdr == (pgno==1 ? 100 : 0) ); + idx = hdr + 12 - pPage->leaf*4; + for(i=0; ileaf ){ + child = 0; + }else{ + child = get4byte(pCell); + } + sz = info.nData; + if( !pPage->intKey ) sz += info.nKey; + if( sz>sizeof(payload)-1 ) sz = sizeof(payload)-1; + memcpy(payload, &pCell[info.nHeader], sz); + for(j=0; j0x7f ) payload[j] = '.'; + } + payload[sz] = 0; + sqlite3DebugPrintf( + "cell %2d: i=%-10s chld=%-4d nk=%-4lld nd=%-4d payload=%s\n", + i, range, child, info.nKey, info.nData, payload + ); + } + if( !pPage->leaf ){ + sqlite3DebugPrintf("right_child: %d\n", get4byte(&data[hdr+8])); + } + nFree = 0; + i = 0; + idx = get2byte(&data[hdr+1]); + while( idx>0 && idxpBt->usableSize ){ + int sz = get2byte(&data[idx+2]); + sprintf(range,"%d..%d", idx, idx+sz-1); + nFree += sz; + sqlite3DebugPrintf("freeblock %2d: i=%-10s size=%-4d total=%d\n", + i, range, sz, nFree); + idx = get2byte(&data[idx]); + i++; + } + if( idx!=0 ){ + sqlite3DebugPrintf("ERROR: next freeblock index out of range: %d\n", idx); + } + if( recursive && !pPage->leaf ){ + for(i=0; iisInit = isInit; + sqlite3pager_unref(data); + fflush(stdout); + return SQLITE_OK; +} +#endif + +#ifdef SQLITE_TEST +/* +** Fill aResult[] with information about the entry and page that the +** cursor is pointing to. +** +** aResult[0] = The page number +** aResult[1] = The entry number +** aResult[2] = Total number of entries on this page +** aResult[3] = Cell size (local payload + header) +** aResult[4] = Number of free bytes on this page +** aResult[5] = Number of free blocks on the page +** aResult[6] = Total payload size (local + overflow) +** aResult[7] = Header size in bytes +** aResult[8] = Local payload size +** aResult[9] = Parent page number +** +** This routine is used for testing and debugging only. +*/ +int sqlite3BtreeCursorInfo(BtCursor *pCur, int *aResult, int upCnt){ + int cnt, idx; + MemPage *pPage = pCur->pPage; + BtCursor tmpCur; + + pageIntegrity(pPage); + assert( pPage->isInit ); + getTempCursor(pCur, &tmpCur); + while( upCnt-- ){ + moveToParent(&tmpCur); + } + pPage = tmpCur.pPage; + pageIntegrity(pPage); + aResult[0] = sqlite3pager_pagenumber(pPage->aData); + assert( aResult[0]==pPage->pgno ); + aResult[1] = tmpCur.idx; + aResult[2] = pPage->nCell; + if( tmpCur.idx>=0 && tmpCur.idxnCell ){ + getCellInfo(&tmpCur); + aResult[3] = tmpCur.info.nSize; + aResult[6] = tmpCur.info.nData; + aResult[7] = tmpCur.info.nHeader; + aResult[8] = tmpCur.info.nLocal; + }else{ + aResult[3] = 0; + aResult[6] = 0; + aResult[7] = 0; + aResult[8] = 0; + } + aResult[4] = pPage->nFree; + cnt = 0; + idx = get2byte(&pPage->aData[pPage->hdrOffset+1]); + while( idx>0 && idxpBt->usableSize ){ + cnt++; + idx = get2byte(&pPage->aData[idx]); + } + aResult[5] = cnt; + if( pPage->pParent==0 || isRootPage(pPage) ){ + aResult[9] = 0; + }else{ + aResult[9] = pPage->pParent->pgno; + } + releaseTempCursor(&tmpCur); + return SQLITE_OK; +} +#endif + +/* +** Return the pager associated with a BTree. This routine is used for +** testing and debugging only. +*/ +Pager *sqlite3BtreePager(Btree *pBt){ + return pBt->pPager; +} + +/* +** This structure is passed around through all the sanity checking routines +** in order to keep track of some global state information. +*/ +typedef struct IntegrityCk IntegrityCk; +struct IntegrityCk { + Btree *pBt; /* The tree being checked out */ + Pager *pPager; /* The associated pager. Also accessible by pBt->pPager */ + int nPage; /* Number of pages in the database */ + int *anRef; /* Number of times each page is referenced */ + char *zErrMsg; /* An error message. NULL of no errors seen. */ +}; + +/* +** Append a message to the error message string. +*/ +static void checkAppendMsg( + IntegrityCk *pCheck, + char *zMsg1, + const char *zFormat, + ... +){ + va_list ap; + char *zMsg2; + va_start(ap, zFormat); + zMsg2 = sqlite3VMPrintf(zFormat, ap); + va_end(ap); + if( zMsg1==0 ) zMsg1 = ""; + if( pCheck->zErrMsg ){ + char *zOld = pCheck->zErrMsg; + pCheck->zErrMsg = 0; + sqlite3SetString(&pCheck->zErrMsg, zOld, "\n", zMsg1, zMsg2, (char*)0); + sqliteFree(zOld); + }else{ + sqlite3SetString(&pCheck->zErrMsg, zMsg1, zMsg2, (char*)0); + } + sqliteFree(zMsg2); +} + +/* +** Add 1 to the reference count for page iPage. If this is the second +** reference to the page, add an error message to pCheck->zErrMsg. +** Return 1 if there are 2 ore more references to the page and 0 if +** if this is the first reference to the page. +** +** Also check that the page number is in bounds. +*/ +static int checkRef(IntegrityCk *pCheck, int iPage, char *zContext){ + if( iPage==0 ) return 1; + if( iPage>pCheck->nPage || iPage<0 ){ + checkAppendMsg(pCheck, zContext, "invalid page number %d", iPage); + return 1; + } + if( pCheck->anRef[iPage]==1 ){ + checkAppendMsg(pCheck, zContext, "2nd reference to page %d", iPage); + return 1; + } + return (pCheck->anRef[iPage]++)>1; +} + +/* +** Check the integrity of the freelist or of an overflow page list. +** Verify that the number of pages on the list is N. +*/ +static void checkList( + IntegrityCk *pCheck, /* Integrity checking context */ + int isFreeList, /* True for a freelist. False for overflow page list */ + int iPage, /* Page number for first page in the list */ + int N, /* Expected number of pages in the list */ + char *zContext /* Context for error messages */ +){ + int i; + int expected = N; + int iFirst = iPage; + while( N-- > 0 ){ + unsigned char *pOvfl; + if( iPage<1 ){ + checkAppendMsg(pCheck, zContext, + "%d of %d pages missing from overflow list starting at %d", + N+1, expected, iFirst); + break; + } + if( checkRef(pCheck, iPage, zContext) ) break; + if( sqlite3pager_get(pCheck->pPager, (Pgno)iPage, (void**)&pOvfl) ){ + checkAppendMsg(pCheck, zContext, "failed to get page %d", iPage); + break; + } + if( isFreeList ){ + int n = get4byte(&pOvfl[4]); + if( n>pCheck->pBt->usableSize/4-8 ){ + checkAppendMsg(pCheck, zContext, + "freelist leaf count too big on page %d", iPage); + N--; + }else{ + for(i=0; ipBt; + usableSize = pBt->usableSize; + if( iPage==0 ) return 0; + if( checkRef(pCheck, iPage, zParentContext) ) return 0; + if( (rc = getPage(pBt, (Pgno)iPage, &pPage))!=0 ){ + checkAppendMsg(pCheck, zContext, + "unable to get the page. error code=%d", rc); + return 0; + } + maxLocal = pPage->leafData ? pBt->maxLeaf : pBt->maxLocal; + if( (rc = initPage(pPage, pParent))!=0 ){ + checkAppendMsg(pCheck, zContext, "initPage() returns error code %d", rc); + releasePage(pPage); + return 0; + } + + /* Check out all the cells. + */ + depth = 0; + cur.pPage = pPage; + for(i=0; inCell; i++){ + u8 *pCell; + int sz; + CellInfo info; + + /* Check payload overflow pages + */ + sprintf(zContext, "On tree page %d cell %d: ", iPage, i); + pCell = findCell(pPage,i); + parseCellPtr(pPage, pCell, &info); + sz = info.nData; + if( !pPage->intKey ) sz += info.nKey; + if( sz>info.nLocal ){ + int nPage = (sz - info.nLocal + usableSize - 5)/(usableSize - 4); + checkList(pCheck, 0, get4byte(&pCell[info.iOverflow]),nPage,zContext); + } + + /* Check sanity of left child page. + */ + if( !pPage->leaf ){ + pgno = get4byte(pCell); + d2 = checkTreePage(pCheck,pgno,pPage,zContext,0,0,0,0); + if( i>0 && d2!=depth ){ + checkAppendMsg(pCheck, zContext, "Child page depth differs"); + } + depth = d2; + } + } + if( !pPage->leaf ){ + pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]); + sprintf(zContext, "On page %d at right child: ", iPage); + checkTreePage(pCheck, pgno, pPage, zContext,0,0,0,0); + } + + /* Check for complete coverage of the page + */ + data = pPage->aData; + hdr = pPage->hdrOffset; + hit = sqliteMalloc( usableSize ); + if( hit ){ + memset(hit, 1, get2byte(&data[hdr+5])); + nCell = get2byte(&data[hdr+3]); + cellStart = hdr + 12 - 4*pPage->leaf; + for(i=0; i=pc; j--) hit[j]++; + } + for(cnt=0, i=get2byte(&data[hdr+1]); i>0 && i=i; j--) hit[j]++; + i = get2byte(&data[i]); + } + for(i=cnt=0; i1 ){ + checkAppendMsg(pCheck, 0, + "Multiple uses for byte %d of page %d", i, iPage); + break; + } + } + if( cnt!=data[hdr+7] ){ + checkAppendMsg(pCheck, 0, + "Fragmented space is %d byte reported as %d on page %d", + cnt, data[hdr+7], iPage); + } + } + sqliteFree(hit); + + releasePage(pPage); + return depth+1; +} + +/* +** This routine does a complete check of the given BTree file. aRoot[] is +** an array of pages numbers were each page number is the root page of +** a table. nRoot is the number of entries in aRoot. +** +** If everything checks out, this routine returns NULL. If something is +** amiss, an error message is written into memory obtained from malloc() +** and a pointer to that error message is returned. The calling function +** is responsible for freeing the error message when it is done. +*/ +char *sqlite3BtreeIntegrityCheck(Btree *pBt, int *aRoot, int nRoot){ + int i; + int nRef; + IntegrityCk sCheck; + + nRef = *sqlite3pager_stats(pBt->pPager); + if( lockBtree(pBt)!=SQLITE_OK ){ + return sqliteStrDup("Unable to acquire a read lock on the database"); + } + sCheck.pBt = pBt; + sCheck.pPager = pBt->pPager; + sCheck.nPage = sqlite3pager_pagecount(sCheck.pPager); + if( sCheck.nPage==0 ){ + unlockBtreeIfUnused(pBt); + return 0; + } + sCheck.anRef = sqliteMallocRaw( (sCheck.nPage+1)*sizeof(sCheck.anRef[0]) ); + for(i=0; i<=sCheck.nPage; i++){ sCheck.anRef[i] = 0; } + i = PENDING_BYTE/pBt->pageSize + 1; + if( i<=sCheck.nPage ){ + sCheck.anRef[i] = 1; + } + sCheck.zErrMsg = 0; + + /* Check the integrity of the freelist + */ + checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]), + get4byte(&pBt->pPage1->aData[36]), "Main freelist: "); + + /* Check all the tables. + */ + for(i=0; ipPager) ){ + checkAppendMsg(&sCheck, 0, + "Outstanding page count goes from %d to %d during this analysis", + nRef, *sqlite3pager_stats(pBt->pPager) + ); + } + + /* Clean up and report errors. + */ + sqliteFree(sCheck.anRef); + return sCheck.zErrMsg; +} + +/* +** Return the full pathname of the underlying database file. +*/ +const char *sqlite3BtreeGetFilename(Btree *pBt){ + assert( pBt->pPager!=0 ); + return sqlite3pager_filename(pBt->pPager); +} + +/* +** Return the pathname of the directory that contains the database file. +*/ +const char *sqlite3BtreeGetDirname(Btree *pBt){ + assert( pBt->pPager!=0 ); + return sqlite3pager_dirname(pBt->pPager); +} + +/* +** Return the pathname of the journal file for this database. The return +** value of this routine is the same regardless of whether the journal file +** has been created or not. +*/ +const char *sqlite3BtreeGetJournalname(Btree *pBt){ + assert( pBt->pPager!=0 ); + return sqlite3pager_journalname(pBt->pPager); +} + +/* +** Copy the complete content of pBtFrom into pBtTo. A transaction +** must be active for both files. +** +** The size of file pBtFrom may be reduced by this operation. +** If anything goes wrong, the transaction on pBtFrom is rolled back. +*/ +int sqlite3BtreeCopyFile(Btree *pBtTo, Btree *pBtFrom){ + int rc = SQLITE_OK; + Pgno i, nPage, nToPage; + + if( pBtTo->inTrans!=TRANS_WRITE || pBtFrom->inTrans!=TRANS_WRITE ){ + return SQLITE_ERROR; + } + if( pBtTo->pCursor ) return SQLITE_BUSY; + nToPage = sqlite3pager_pagecount(pBtTo->pPager); + nPage = sqlite3pager_pagecount(pBtFrom->pPager); + for(i=1; rc==SQLITE_OK && i<=nPage; i++){ + void *pPage; + rc = sqlite3pager_get(pBtFrom->pPager, i, &pPage); + if( rc ) break; + rc = sqlite3pager_overwrite(pBtTo->pPager, i, pPage); + if( rc ) break; + sqlite3pager_unref(pPage); + } + for(i=nPage+1; rc==SQLITE_OK && i<=nToPage; i++){ + void *pPage; + rc = sqlite3pager_get(pBtTo->pPager, i, &pPage); + if( rc ) break; + rc = sqlite3pager_write(pPage); + sqlite3pager_unref(pPage); + sqlite3pager_dont_write(pBtTo->pPager, i); + } + if( !rc && nPagepPager, nPage); + } + if( rc ){ + sqlite3BtreeRollback(pBtTo); + } + return rc; +} + +/* +** Return non-zero if a transaction is active. +*/ +int sqlite3BtreeIsInTrans(Btree *pBt){ + return (pBt && (pBt->inTrans==TRANS_WRITE)); +} + +/* +** Return non-zero if a statement transaction is active. +*/ +int sqlite3BtreeIsInStmt(Btree *pBt){ + return (pBt && pBt->inStmt); +} + +/* +** This call is a no-op if no write-transaction is currently active on pBt. +** +** Otherwise, sync the database file for the btree pBt. zMaster points to +** the name of a master journal file that should be written into the +** individual journal file, or is NULL, indicating no master journal file +** (single database transaction). +** +** When this is called, the master journal should already have been +** created, populated with this journal pointer and synced to disk. +** +** Once this is routine has returned, the only thing required to commit +** the write-transaction for this database file is to delete the journal. +*/ +int sqlite3BtreeSync(Btree *pBt, const char *zMaster){ + if( pBt->inTrans==TRANS_WRITE ){ + return sqlite3pager_sync(pBt->pPager, zMaster); + } + return SQLITE_OK; +} diff --git a/kopete/plugins/statistics/sqlite/btree.h b/kopete/plugins/statistics/sqlite/btree.h new file mode 100644 index 00000000..48524aef --- /dev/null +++ b/kopete/plugins/statistics/sqlite/btree.h @@ -0,0 +1,124 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the sqlite B-Tree file +** subsystem. See comments in the source code for a detailed description +** of what each interface routine does. +** +** @(#) $Id$ +*/ +#ifndef _BTREE_H_ +#define _BTREE_H_ + +/* TODO: This definition is just included so other modules compile. It +** needs to be revisited. +*/ +#define SQLITE_N_BTREE_META 10 + +/* +** Forward declarations of structure +*/ +typedef struct Btree Btree; +typedef struct BtCursor BtCursor; + + +int sqlite3BtreeOpen( + const char *zFilename, /* Name of database file to open */ + Btree **, /* Return open Btree* here */ + int flags /* Flags */ +); + +/* The flags parameter to sqlite3BtreeOpen can be the bitwise or of the +** following values. +*/ +#define BTREE_OMIT_JOURNAL 1 /* Do not use journal. No argument */ +#define BTREE_MEMORY 2 /* In-memory DB. No argument */ + +int sqlite3BtreeClose(Btree*); +int sqlite3BtreeSetBusyHandler(Btree*,BusyHandler*); +int sqlite3BtreeSetCacheSize(Btree*,int); +int sqlite3BtreeSetSafetyLevel(Btree*,int); +int sqlite3BtreeSetPageSize(Btree*,int,int); +int sqlite3BtreeGetPageSize(Btree*); +int sqlite3BtreeGetReserve(Btree*); +int sqlite3BtreeBeginTrans(Btree*,int); +int sqlite3BtreeCommit(Btree*); +int sqlite3BtreeRollback(Btree*); +int sqlite3BtreeBeginStmt(Btree*); +int sqlite3BtreeCommitStmt(Btree*); +int sqlite3BtreeRollbackStmt(Btree*); +int sqlite3BtreeCreateTable(Btree*, int*, int flags); +int sqlite3BtreeIsInTrans(Btree*); +int sqlite3BtreeIsInStmt(Btree*); +int sqlite3BtreeSync(Btree*, const char *zMaster); + +const char *sqlite3BtreeGetFilename(Btree *); +const char *sqlite3BtreeGetDirname(Btree *); +const char *sqlite3BtreeGetJournalname(Btree *); +int sqlite3BtreeCopyFile(Btree *, Btree *); + +/* The flags parameter to sqlite3BtreeCreateTable can be the bitwise OR +** of the following flags: +*/ +#define BTREE_INTKEY 1 /* Table has only 64-bit signed integer keys */ +#define BTREE_ZERODATA 2 /* Table has keys only - no data */ +#define BTREE_LEAFDATA 4 /* Data stored in leaves only. Implies INTKEY */ + +int sqlite3BtreeDropTable(Btree*, int); +int sqlite3BtreeClearTable(Btree*, int); +int sqlite3BtreeGetMeta(Btree*, int idx, u32 *pValue); +int sqlite3BtreeUpdateMeta(Btree*, int idx, u32 value); + +int sqlite3BtreeCursor( + Btree*, /* BTree containing table to open */ + int iTable, /* Index of root page */ + int wrFlag, /* 1 for writing. 0 for read-only */ + int(*)(void*,int,const void*,int,const void*), /* Key comparison function */ + void*, /* First argument to compare function */ + BtCursor **ppCursor /* Returned cursor */ +); + +void sqlite3BtreeSetCompare( + BtCursor *, + int(*)(void*,int,const void*,int,const void*), + void* +); + +int sqlite3BtreeCloseCursor(BtCursor*); +int sqlite3BtreeMoveto(BtCursor*, const void *pKey, i64 nKey, int *pRes); +int sqlite3BtreeDelete(BtCursor*); +int sqlite3BtreeInsert(BtCursor*, const void *pKey, i64 nKey, + const void *pData, int nData); +int sqlite3BtreeFirst(BtCursor*, int *pRes); +int sqlite3BtreeLast(BtCursor*, int *pRes); +int sqlite3BtreeNext(BtCursor*, int *pRes); +int sqlite3BtreeEof(BtCursor*); +int sqlite3BtreeFlags(BtCursor*); +int sqlite3BtreePrevious(BtCursor*, int *pRes); +int sqlite3BtreeKeySize(BtCursor*, i64 *pSize); +int sqlite3BtreeKey(BtCursor*, u32 offset, u32 amt, void*); +const void *sqlite3BtreeKeyFetch(BtCursor*, int *pAmt); +const void *sqlite3BtreeDataFetch(BtCursor*, int *pAmt); +int sqlite3BtreeDataSize(BtCursor*, u32 *pSize); +int sqlite3BtreeData(BtCursor*, u32 offset, u32 amt, void*); + +char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot); +struct Pager *sqlite3BtreePager(Btree*); + + +#ifdef SQLITE_TEST +int sqlite3BtreeCursorInfo(BtCursor*, int*, int); +void sqlite3BtreeCursorList(Btree*); +int sqlite3BtreePageDump(Btree*, int, int recursive); +#endif + + +#endif /* _BTREE_H_ */ diff --git a/kopete/plugins/statistics/sqlite/build.c b/kopete/plugins/statistics/sqlite/build.c new file mode 100644 index 00000000..3e5e08a5 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/build.c @@ -0,0 +1,2564 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains C code routines that are called by the SQLite parser +** when syntax rules are reduced. The routines in this file handle the +** following kinds of SQL syntax: +** +** CREATE TABLE +** DROP TABLE +** CREATE INDEX +** DROP INDEX +** creating ID lists +** BEGIN TRANSACTION +** COMMIT +** ROLLBACK +** PRAGMA +** +** $Id$ +*/ +#include "sqliteInt.h" +#include + +/* +** This routine is called when a new SQL statement is beginning to +** be parsed. Check to see if the schema for the database needs +** to be read from the SQLITE_MASTER and SQLITE_TEMP_MASTER tables. +** If it does, then read it. +*/ +void sqlite3BeginParse(Parse *pParse, int explainFlag){ + pParse->explain = explainFlag; + pParse->nVar = 0; +} + +/* +** This routine is called after a single SQL statement has been +** parsed and a VDBE program to execute that statement has been +** prepared. This routine puts the finishing touches on the +** VDBE program and resets the pParse structure for the next +** parse. +** +** Note that if an error occurred, it might be the case that +** no VDBE code was generated. +*/ +void sqlite3FinishCoding(Parse *pParse){ + sqlite3 *db; + Vdbe *v; + + if( sqlite3_malloc_failed ) return; + + /* Begin by generating some termination code at the end of the + ** vdbe program + */ + db = pParse->db; + v = sqlite3GetVdbe(pParse); + if( v ){ + sqlite3VdbeAddOp(v, OP_Halt, 0, 0); + + /* The cookie mask contains one bit for each database file open. + ** (Bit 0 is for main, bit 1 is for temp, and so forth.) Bits are + ** set for each database that is used. Generate code to start a + ** transaction on each used database and to verify the schema cookie + ** on each used database. + */ + if( pParse->cookieGoto>0 ){ + u32 mask; + int iDb; + sqlite3VdbeChangeP2(v, pParse->cookieGoto-1, sqlite3VdbeCurrentAddr(v)); + for(iDb=0, mask=1; iDbnDb; mask<<=1, iDb++){ + if( (mask & pParse->cookieMask)==0 ) continue; + sqlite3VdbeAddOp(v, OP_Transaction, iDb, (mask & pParse->writeMask)!=0); + sqlite3VdbeAddOp(v, OP_VerifyCookie, iDb, pParse->cookieValue[iDb]); + } + sqlite3VdbeAddOp(v, OP_Goto, 0, pParse->cookieGoto); + } + + /* Add a No-op that contains the complete text of the compiled SQL + ** statement as its P3 argument. This does not change the functionality + ** of the program. + ** + ** This is used to implement sqlite3_trace() functionality. + */ + sqlite3VdbeOp3(v, OP_Noop, 0, 0, pParse->zSql, pParse->zTail-pParse->zSql); + } + + + /* Get the VDBE program ready for execution + */ + if( v && pParse->nErr==0 ){ + FILE *trace = (db->flags & SQLITE_VdbeTrace)!=0 ? stdout : 0; + sqlite3VdbeTrace(v, trace); + sqlite3VdbeMakeReady(v, pParse->nVar, pParse->nMem+3, + pParse->nTab+3, pParse->explain); + pParse->rc = pParse->nErr ? SQLITE_ERROR : SQLITE_DONE; + pParse->colNamesSet = 0; + }else if( pParse->rc==SQLITE_OK ){ + pParse->rc = SQLITE_ERROR; + } + pParse->nTab = 0; + pParse->nMem = 0; + pParse->nSet = 0; + pParse->nAgg = 0; + pParse->nVar = 0; + pParse->cookieMask = 0; + pParse->cookieGoto = 0; +} + +/* +** Locate the in-memory structure that describes a particular database +** table given the name of that table and (optionally) the name of the +** database containing the table. Return NULL if not found. +** +** If zDatabase is 0, all databases are searched for the table and the +** first matching table is returned. (No checking for duplicate table +** names is done.) The search order is TEMP first, then MAIN, then any +** auxiliary databases added using the ATTACH command. +** +** See also sqlite3LocateTable(). +*/ +Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){ + Table *p = 0; + int i; + assert( zName!=0 ); + assert( (db->flags & SQLITE_Initialized) || db->init.busy ); + for(i=0; inDb; i++){ + int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ + if( zDatabase!=0 && sqlite3StrICmp(zDatabase, db->aDb[j].zName) ) continue; + p = sqlite3HashFind(&db->aDb[j].tblHash, zName, strlen(zName)+1); + if( p ) break; + } + return p; +} + +/* +** Locate the in-memory structure that describes a particular database +** table given the name of that table and (optionally) the name of the +** database containing the table. Return NULL if not found. Also leave an +** error message in pParse->zErrMsg. +** +** The difference between this routine and sqlite3FindTable() is that this +** routine leaves an error message in pParse->zErrMsg where +** sqlite3FindTable() does not. +*/ +Table *sqlite3LocateTable(Parse *pParse, const char *zName, const char *zDbase){ + Table *p; + + /* Read the database schema. If an error occurs, leave an error message + ** and code in pParse and return NULL. */ + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ + return 0; + } + + p = sqlite3FindTable(pParse->db, zName, zDbase); + if( p==0 ){ + if( zDbase ){ + sqlite3ErrorMsg(pParse, "no such table: %s.%s", zDbase, zName); + }else if( sqlite3FindTable(pParse->db, zName, 0)!=0 ){ + sqlite3ErrorMsg(pParse, "table \"%s\" is not in database \"%s\"", + zName, zDbase); + }else{ + sqlite3ErrorMsg(pParse, "no such table: %s", zName); + } + pParse->checkSchema = 1; + } + return p; +} + +/* +** Locate the in-memory structure that describes +** a particular index given the name of that index +** and the name of the database that contains the index. +** Return NULL if not found. +** +** If zDatabase is 0, all databases are searched for the +** table and the first matching index is returned. (No checking +** for duplicate index names is done.) The search order is +** TEMP first, then MAIN, then any auxiliary databases added +** using the ATTACH command. +*/ +Index *sqlite3FindIndex(sqlite3 *db, const char *zName, const char *zDb){ + Index *p = 0; + int i; + assert( (db->flags & SQLITE_Initialized) || db->init.busy ); + for(i=0; inDb; i++){ + int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ + if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zName) ) continue; + p = sqlite3HashFind(&db->aDb[j].idxHash, zName, strlen(zName)+1); + if( p ) break; + } + return p; +} + +/* +** Reclaim the memory used by an index +*/ +static void freeIndex(Index *p){ + sqliteFree(p->zColAff); + sqliteFree(p); +} + +/* +** Remove the given index from the index hash table, and free +** its memory structures. +** +** The index is removed from the database hash tables but +** it is not unlinked from the Table that it indexes. +** Unlinking from the Table must be done by the calling function. +*/ +static void sqliteDeleteIndex(sqlite3 *db, Index *p){ + Index *pOld; + + assert( db!=0 && p->zName!=0 ); + pOld = sqlite3HashInsert(&db->aDb[p->iDb].idxHash, p->zName, + strlen(p->zName)+1, 0); + if( pOld!=0 && pOld!=p ){ + sqlite3HashInsert(&db->aDb[p->iDb].idxHash, pOld->zName, + strlen(pOld->zName)+1, pOld); + } + freeIndex(p); +} + +/* +** Unlink the given index from its table, then remove +** the index from the index hash table and free its memory +** structures. +*/ +void sqlite3UnlinkAndDeleteIndex(sqlite3 *db, int iDb, const char *zIdxName){ + Index *pIndex; + int len; + + len = strlen(zIdxName); + pIndex = sqlite3HashInsert(&db->aDb[iDb].idxHash, zIdxName, len+1, 0); + if( pIndex ){ + if( pIndex->pTable->pIndex==pIndex ){ + pIndex->pTable->pIndex = pIndex->pNext; + }else{ + Index *p; + for(p=pIndex->pTable->pIndex; p && p->pNext!=pIndex; p=p->pNext){} + if( p && p->pNext==pIndex ){ + p->pNext = pIndex->pNext; + } + } + freeIndex(pIndex); + } + db->flags |= SQLITE_InternChanges; +} + +/* +** Erase all schema information from the in-memory hash tables of +** a single database. This routine is called to reclaim memory +** before the database closes. It is also called during a rollback +** if there were schema changes during the transaction or if a +** schema-cookie mismatch occurs. +** +** If iDb<=0 then reset the internal schema tables for all database +** files. If iDb>=2 then reset the internal schema for only the +** single file indicated. +*/ +void sqlite3ResetInternalSchema(sqlite3 *db, int iDb){ + HashElem *pElem; + Hash temp1; + Hash temp2; + int i, j; + + assert( iDb>=0 && iDbnDb ); + db->flags &= ~SQLITE_Initialized; + for(i=iDb; inDb; i++){ + Db *pDb = &db->aDb[i]; + temp1 = pDb->tblHash; + temp2 = pDb->trigHash; + sqlite3HashInit(&pDb->trigHash, SQLITE_HASH_STRING, 0); + sqlite3HashClear(&pDb->aFKey); + sqlite3HashClear(&pDb->idxHash); + for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){ + Trigger *pTrigger = sqliteHashData(pElem); + sqlite3DeleteTrigger(pTrigger); + } + sqlite3HashClear(&temp2); + sqlite3HashInit(&pDb->tblHash, SQLITE_HASH_STRING, 0); + for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){ + Table *pTab = sqliteHashData(pElem); + sqlite3DeleteTable(db, pTab); + } + sqlite3HashClear(&temp1); + DbClearProperty(db, i, DB_SchemaLoaded); + if( iDb>0 ) return; + } + assert( iDb==0 ); + db->flags &= ~SQLITE_InternChanges; + + /* If one or more of the auxiliary database files has been closed, + ** then remove then from the auxiliary database list. We take the + ** opportunity to do this here since we have just deleted all of the + ** schema hash tables and therefore do not have to make any changes + ** to any of those tables. + */ + for(i=0; inDb; i++){ + struct Db *pDb = &db->aDb[i]; + if( pDb->pBt==0 ){ + if( pDb->pAux && pDb->xFreeAux ) pDb->xFreeAux(pDb->pAux); + pDb->pAux = 0; + } + } + for(i=j=2; inDb; i++){ + struct Db *pDb = &db->aDb[i]; + if( pDb->pBt==0 ){ + sqliteFree(pDb->zName); + pDb->zName = 0; + continue; + } + if( jaDb[j] = db->aDb[i]; + } + j++; + } + memset(&db->aDb[j], 0, (db->nDb-j)*sizeof(db->aDb[j])); + db->nDb = j; + if( db->nDb<=2 && db->aDb!=db->aDbStatic ){ + memcpy(db->aDbStatic, db->aDb, 2*sizeof(db->aDb[0])); + sqliteFree(db->aDb); + db->aDb = db->aDbStatic; + } +} + +/* +** This routine is called whenever a rollback occurs. If there were +** schema changes during the transaction, then we have to reset the +** internal hash tables and reload them from disk. +*/ +void sqlite3RollbackInternalChanges(sqlite3 *db){ + if( db->flags & SQLITE_InternChanges ){ + sqlite3ResetInternalSchema(db, 0); + } +} + +/* +** This routine is called when a commit occurs. +*/ +void sqlite3CommitInternalChanges(sqlite3 *db){ + db->flags &= ~SQLITE_InternChanges; +} + +/* +** Clear the column names from a table or view. +*/ +static void sqliteResetColumnNames(Table *pTable){ + int i; + Column *pCol; + assert( pTable!=0 ); + for(i=0, pCol=pTable->aCol; inCol; i++, pCol++){ + sqliteFree(pCol->zName); + sqliteFree(pCol->zDflt); + sqliteFree(pCol->zType); + } + sqliteFree(pTable->aCol); + pTable->aCol = 0; + pTable->nCol = 0; +} + +/* +** Remove the memory data structures associated with the given +** Table. No changes are made to disk by this routine. +** +** This routine just deletes the data structure. It does not unlink +** the table data structure from the hash table. Nor does it remove +** foreign keys from the sqlite.aFKey hash table. But it does destroy +** memory structures of the indices and foreign keys associated with +** the table. +** +** Indices associated with the table are unlinked from the "db" +** data structure if db!=NULL. If db==NULL, indices attached to +** the table are deleted, but it is assumed they have already been +** unlinked. +*/ +void sqlite3DeleteTable(sqlite3 *db, Table *pTable){ + Index *pIndex, *pNext; + FKey *pFKey, *pNextFKey; + + if( pTable==0 ) return; + + /* Delete all indices associated with this table + */ + for(pIndex = pTable->pIndex; pIndex; pIndex=pNext){ + pNext = pIndex->pNext; + assert( pIndex->iDb==pTable->iDb || (pTable->iDb==0 && pIndex->iDb==1) ); + sqliteDeleteIndex(db, pIndex); + } + + /* Delete all foreign keys associated with this table. The keys + ** should have already been unlinked from the db->aFKey hash table + */ + for(pFKey=pTable->pFKey; pFKey; pFKey=pNextFKey){ + pNextFKey = pFKey->pNextFrom; + assert( pTable->iDbnDb ); + assert( sqlite3HashFind(&db->aDb[pTable->iDb].aFKey, + pFKey->zTo, strlen(pFKey->zTo)+1)!=pFKey ); + sqliteFree(pFKey); + } + + /* Delete the Table structure itself. + */ + sqliteResetColumnNames(pTable); + sqliteFree(pTable->zName); + sqliteFree(pTable->zColAff); + sqlite3SelectDelete(pTable->pSelect); + sqliteFree(pTable); +} + +/* +** Unlink the given table from the hash tables and the delete the +** table structure with all its indices and foreign keys. +*/ +void sqlite3UnlinkAndDeleteTable(sqlite3 *db, int iDb, const char *zTabName){ + Table *p; + FKey *pF1, *pF2; + Db *pDb; + + assert( db!=0 ); + assert( iDb>=0 && iDbnDb ); + assert( zTabName && zTabName[0] ); + pDb = &db->aDb[iDb]; + p = sqlite3HashInsert(&pDb->tblHash, zTabName, strlen(zTabName)+1, 0); + if( p ){ + for(pF1=p->pFKey; pF1; pF1=pF1->pNextFrom){ + int nTo = strlen(pF1->zTo) + 1; + pF2 = sqlite3HashFind(&pDb->aFKey, pF1->zTo, nTo); + if( pF2==pF1 ){ + sqlite3HashInsert(&pDb->aFKey, pF1->zTo, nTo, pF1->pNextTo); + }else{ + while( pF2 && pF2->pNextTo!=pF1 ){ pF2=pF2->pNextTo; } + if( pF2 ){ + pF2->pNextTo = pF1->pNextTo; + } + } + } + sqlite3DeleteTable(db, p); + } + db->flags |= SQLITE_InternChanges; +} + +/* +** Given a token, return a string that consists of the text of that +** token with any quotations removed. Space to hold the returned string +** is obtained from sqliteMalloc() and must be freed by the calling +** function. +** +** Tokens are really just pointers into the original SQL text and so +** are not \000 terminated and are not persistent. The returned string +** is \000 terminated and is persistent. +*/ +char *sqlite3NameFromToken(Token *pName){ + char *zName; + if( pName ){ + zName = sqliteStrNDup(pName->z, pName->n); + sqlite3Dequote(zName); + }else{ + zName = 0; + } + return zName; +} + +/* +** Open the sqlite_master table stored in database number iDb for +** writing. The table is opened using cursor 0. +*/ +void sqlite3OpenMasterTable(Vdbe *v, int iDb){ + sqlite3VdbeAddOp(v, OP_Integer, iDb, 0); + sqlite3VdbeAddOp(v, OP_OpenWrite, 0, MASTER_ROOT); + sqlite3VdbeAddOp(v, OP_SetNumColumns, 0, 5); /* sqlite_master has 5 columns */ +} + +/* +** The token *pName contains the name of a database (either "main" or +** "temp" or the name of an attached db). This routine returns the +** index of the named database in db->aDb[], or -1 if the named db +** does not exist. +*/ +int findDb(sqlite3 *db, Token *pName){ + int i; + Db *pDb; + for(pDb=db->aDb, i=0; inDb; i++, pDb++){ + if( pName->n==strlen(pDb->zName) && + 0==sqlite3StrNICmp(pDb->zName, pName->z, pName->n) ){ + return i; + } + } + return -1; +} + +/* The table or view or trigger name is passed to this routine via tokens +** pName1 and pName2. If the table name was fully qualified, for example: +** +** CREATE TABLE xxx.yyy (...); +** +** Then pName1 is set to "xxx" and pName2 "yyy". On the other hand if +** the table name is not fully qualified, i.e.: +** +** CREATE TABLE yyy(...); +** +** Then pName1 is set to "yyy" and pName2 is "". +** +** This routine sets the *ppUnqual pointer to point at the token (pName1 or +** pName2) that stores the unqualified table name. The index of the +** database "xxx" is returned. +*/ +int sqlite3TwoPartName( + Parse *pParse, /* Parsing and code generating context */ + Token *pName1, /* The "xxx" in the name "xxx.yyy" or "xxx" */ + Token *pName2, /* The "yyy" in the name "xxx.yyy" */ + Token **pUnqual /* Write the unqualified object name here */ +){ + int iDb; /* Database holding the object */ + sqlite3 *db = pParse->db; + + if( pName2 && pName2->n>0 ){ + assert( !db->init.busy ); + *pUnqual = pName2; + iDb = findDb(db, pName1); + if( iDb<0 ){ + sqlite3ErrorMsg(pParse, "unknown database %T", pName1); + pParse->nErr++; + return -1; + } + }else{ + assert( db->init.iDb==0 || db->init.busy ); + iDb = db->init.iDb; + *pUnqual = pName1; + } + return iDb; +} + +/* +** This routine is used to check if the UTF-8 string zName is a legal +** unqualified name for a new schema object (table, index, view or +** trigger). All names are legal except those that begin with the string +** "sqlite_" (in upper, lower or mixed case). This portion of the namespace +** is reserved for internal use. +*/ +int sqlite3CheckObjectName(Parse *pParse, const char *zName){ + if( !pParse->db->init.busy && 0==sqlite3StrNICmp(zName, "sqlite_", 7) ){ + sqlite3ErrorMsg(pParse, "object name reserved for internal use: %s", zName); + return SQLITE_ERROR; + } + return SQLITE_OK; +} + +/* +** Begin constructing a new table representation in memory. This is +** the first of several action routines that get called in response +** to a CREATE TABLE statement. In particular, this routine is called +** after seeing tokens "CREATE" and "TABLE" and the table name. The +** pStart token is the CREATE and pName is the table name. The isTemp +** flag is true if the table should be stored in the auxiliary database +** file instead of in the main database file. This is normally the case +** when the "TEMP" or "TEMPORARY" keyword occurs in between +** CREATE and TABLE. +** +** The new table record is initialized and put in pParse->pNewTable. +** As more of the CREATE TABLE statement is parsed, additional action +** routines will be called to add more information to this record. +** At the end of the CREATE TABLE statement, the sqlite3EndTable() routine +** is called to complete the construction of the new table record. +*/ +void sqlite3StartTable( + Parse *pParse, /* Parser context */ + Token *pStart, /* The "CREATE" token */ + Token *pName1, /* First part of the name of the table or view */ + Token *pName2, /* Second part of the name of the table or view */ + int isTemp, /* True if this is a TEMP table */ + int isView /* True if this is a VIEW */ +){ + Table *pTable; + Index *pIdx; + char *zName; + sqlite3 *db = pParse->db; + Vdbe *v; + int iDb; /* Database number to create the table in */ + Token *pName; /* Unqualified name of the table to create */ + + /* The table or view name to create is passed to this routine via tokens + ** pName1 and pName2. If the table name was fully qualified, for example: + ** + ** CREATE TABLE xxx.yyy (...); + ** + ** Then pName1 is set to "xxx" and pName2 "yyy". On the other hand if + ** the table name is not fully qualified, i.e.: + ** + ** CREATE TABLE yyy(...); + ** + ** Then pName1 is set to "yyy" and pName2 is "". + ** + ** The call below sets the pName pointer to point at the token (pName1 or + ** pName2) that stores the unqualified table name. The variable iDb is + ** set to the index of the database that the table or view is to be + ** created in. + */ + iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName); + if( iDb<0 ) return; + if( isTemp && iDb>1 ){ + /* If creating a temp table, the name may not be qualified */ + sqlite3ErrorMsg(pParse, "temporary table name must be unqualified"); + pParse->nErr++; + return; + } + if( isTemp ) iDb = 1; + + pParse->sNameToken = *pName; + zName = sqlite3NameFromToken(pName); + if( zName==0 ) return; + if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ + sqliteFree(zName); + return; + } + if( db->init.iDb==1 ) isTemp = 1; +#ifndef SQLITE_OMIT_AUTHORIZATION + assert( (isTemp & 1)==isTemp ); + { + int code; + char *zDb = db->aDb[iDb].zName; + if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0, zDb) ){ + sqliteFree(zName); + return; + } + if( isView ){ + if( isTemp ){ + code = SQLITE_CREATE_TEMP_VIEW; + }else{ + code = SQLITE_CREATE_VIEW; + } + }else{ + if( isTemp ){ + code = SQLITE_CREATE_TEMP_TABLE; + }else{ + code = SQLITE_CREATE_TABLE; + } + } + if( sqlite3AuthCheck(pParse, code, zName, 0, zDb) ){ + sqliteFree(zName); + return; + } + } +#endif + + /* Make sure the new table name does not collide with an existing + ** index or table name in the same database. Issue an error message if + ** it does. + */ + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ) return; + pTable = sqlite3FindTable(db, zName, db->aDb[iDb].zName); + if( pTable ){ + sqlite3ErrorMsg(pParse, "table %T already exists", pName); + sqliteFree(zName); + return; + } + if( (pIdx = sqlite3FindIndex(db, zName, 0))!=0 && + ( iDb==0 || !db->init.busy) ){ + sqlite3ErrorMsg(pParse, "there is already an index named %s", zName); + sqliteFree(zName); + return; + } + pTable = sqliteMalloc( sizeof(Table) ); + if( pTable==0 ){ + pParse->rc = SQLITE_NOMEM; + pParse->nErr++; + sqliteFree(zName); + return; + } + pTable->zName = zName; + pTable->nCol = 0; + pTable->aCol = 0; + pTable->iPKey = -1; + pTable->pIndex = 0; + pTable->iDb = iDb; + if( pParse->pNewTable ) sqlite3DeleteTable(db, pParse->pNewTable); + pParse->pNewTable = pTable; + + /* Begin generating the code that will insert the table record into + ** the SQLITE_MASTER table. Note in particular that we must go ahead + ** and allocate the record number for the table entry now. Before any + ** PRIMARY KEY or UNIQUE keywords are parsed. Those keywords will cause + ** indices to be created and the table record must come before the + ** indices. Hence, the record number for the table must be allocated + ** now. + */ + if( !db->init.busy && (v = sqlite3GetVdbe(pParse))!=0 ){ + sqlite3BeginWriteOperation(pParse, 0, iDb); + /* Every time a new table is created the file-format + ** and encoding meta-values are set in the database, in + ** case this is the first table created. + */ + sqlite3VdbeAddOp(v, OP_Integer, db->file_format, 0); + sqlite3VdbeAddOp(v, OP_SetCookie, iDb, 1); + sqlite3VdbeAddOp(v, OP_Integer, db->enc, 0); + sqlite3VdbeAddOp(v, OP_SetCookie, iDb, 4); + + sqlite3OpenMasterTable(v, iDb); + sqlite3VdbeAddOp(v, OP_NewRecno, 0, 0); + sqlite3VdbeAddOp(v, OP_Dup, 0, 0); + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + sqlite3VdbeAddOp(v, OP_PutIntKey, 0, 0); + } +} + +/* +** Add a new column to the table currently being constructed. +** +** The parser calls this routine once for each column declaration +** in a CREATE TABLE statement. sqlite3StartTable() gets called +** first to get things going. Then this routine is called for each +** column. +*/ +void sqlite3AddColumn(Parse *pParse, Token *pName){ + Table *p; + int i; + char *z; + Column *pCol; + if( (p = pParse->pNewTable)==0 ) return; + z = sqlite3NameFromToken(pName); + if( z==0 ) return; + for(i=0; inCol; i++){ + if( sqlite3StrICmp(z, p->aCol[i].zName)==0 ){ + sqlite3ErrorMsg(pParse, "duplicate column name: %s", z); + sqliteFree(z); + return; + } + } + if( (p->nCol & 0x7)==0 ){ + Column *aNew; + aNew = sqliteRealloc( p->aCol, (p->nCol+8)*sizeof(p->aCol[0])); + if( aNew==0 ) return; + p->aCol = aNew; + } + pCol = &p->aCol[p->nCol]; + memset(pCol, 0, sizeof(p->aCol[0])); + pCol->zName = z; + + /* If there is no type specified, columns have the default affinity + ** 'NONE'. If there is a type specified, then sqlite3AddColumnType() will + ** be called next to set pCol->affinity correctly. + */ + pCol->affinity = SQLITE_AFF_NONE; + pCol->pColl = pParse->db->pDfltColl; + p->nCol++; +} + +/* +** This routine is called by the parser while in the middle of +** parsing a CREATE TABLE statement. A "NOT NULL" constraint has +** been seen on a column. This routine sets the notNull flag on +** the column currently under construction. +*/ +void sqlite3AddNotNull(Parse *pParse, int onError){ + Table *p; + int i; + if( (p = pParse->pNewTable)==0 ) return; + i = p->nCol-1; + if( i>=0 ) p->aCol[i].notNull = onError; +} + +/* +** This routine is called by the parser while in the middle of +** parsing a CREATE TABLE statement. The pFirst token is the first +** token in the sequence of tokens that describe the type of the +** column currently under construction. pLast is the last token +** in the sequence. Use this information to construct a string +** that contains the typename of the column and store that string +** in zType. +*/ +void sqlite3AddColumnType(Parse *pParse, Token *pFirst, Token *pLast){ + Table *p; + int i, j; + int n; + char *z, **pz; + Column *pCol; + if( (p = pParse->pNewTable)==0 ) return; + i = p->nCol-1; + if( i<0 ) return; + pCol = &p->aCol[i]; + pz = &pCol->zType; + n = pLast->n + (pLast->z - pFirst->z); + assert( pCol->zType==0 ); + z = pCol->zType = sqlite3MPrintf("%.*s", n, pFirst->z); + if( z==0 ) return; + for(i=j=0; z[i]; i++){ + int c = z[i]; + if( isspace(c) ) continue; + z[j++] = c; + } + z[j] = 0; + pCol->affinity = sqlite3AffinityType(z, n); +} + +/* +** The given token is the default value for the last column added to +** the table currently under construction. If "minusFlag" is true, it +** means the value token was preceded by a minus sign. +** +** This routine is called by the parser while in the middle of +** parsing a CREATE TABLE statement. +*/ +void sqlite3AddDefaultValue(Parse *pParse, Token *pVal, int minusFlag){ + Table *p; + int i; + char *z; + if( (p = pParse->pNewTable)==0 ) return; + i = p->nCol-1; + if( i<0 ) return; + assert( p->aCol[i].zDflt==0 ); + z = p->aCol[i].zDflt = sqlite3MPrintf("%s%T", minusFlag ? "-" : "", pVal); + sqlite3Dequote(z); +} + +/* +** Designate the PRIMARY KEY for the table. pList is a list of names +** of columns that form the primary key. If pList is NULL, then the +** most recently added column of the table is the primary key. +** +** A table can have at most one primary key. If the table already has +** a primary key (and this is the second primary key) then create an +** error. +** +** If the PRIMARY KEY is on a single column whose datatype is INTEGER, +** then we will try to use that column as the row id. (Exception: +** For backwards compatibility with older databases, do not do this +** if the file format version number is less than 1.) Set the Table.iPKey +** field of the table under construction to be the index of the +** INTEGER PRIMARY KEY column. Table.iPKey is set to -1 if there is +** no INTEGER PRIMARY KEY. +** +** If the key is not an INTEGER PRIMARY KEY, then create a unique +** index for the key. No index is created for INTEGER PRIMARY KEYs. +*/ +void sqlite3AddPrimaryKey(Parse *pParse, ExprList *pList, int onError){ + Table *pTab = pParse->pNewTable; + char *zType = 0; + int iCol = -1, i; + if( pTab==0 ) goto primary_key_exit; + if( pTab->hasPrimKey ){ + sqlite3ErrorMsg(pParse, + "table \"%s\" has more than one primary key", pTab->zName); + goto primary_key_exit; + } + pTab->hasPrimKey = 1; + if( pList==0 ){ + iCol = pTab->nCol - 1; + pTab->aCol[iCol].isPrimKey = 1; + }else{ + for(i=0; inExpr; i++){ + for(iCol=0; iColnCol; iCol++){ + if( sqlite3StrICmp(pList->a[i].zName, pTab->aCol[iCol].zName)==0 ){ + break; + } + } + if( iColnCol ) pTab->aCol[iCol].isPrimKey = 1; + } + if( pList->nExpr>1 ) iCol = -1; + } + if( iCol>=0 && iColnCol ){ + zType = pTab->aCol[iCol].zType; + } + if( zType && sqlite3StrICmp(zType, "INTEGER")==0 ){ + pTab->iPKey = iCol; + pTab->keyConf = onError; + }else{ + sqlite3CreateIndex(pParse, 0, 0, 0, pList, onError, 0, 0); + pList = 0; + } + +primary_key_exit: + sqlite3ExprListDelete(pList); + return; +} + +/* +** Set the collation function of the most recently parsed table column +** to the CollSeq given. +*/ +void sqlite3AddCollateType(Parse *pParse, const char *zType, int nType){ + Table *p; + Index *pIdx; + CollSeq *pColl; + int i; + + if( (p = pParse->pNewTable)==0 ) return; + i = p->nCol-1; + + pColl = sqlite3LocateCollSeq(pParse, zType, nType); + p->aCol[i].pColl = pColl; + + /* If the column is declared as " PRIMARY KEY COLLATE ", + ** then an index may have been created on this column before the + ** collation type was added. Correct this if it is the case. + */ + for(pIdx = p->pIndex; pIdx; pIdx=pIdx->pNext){ + assert( pIdx->nColumn==1 ); + if( pIdx->aiColumn[0]==i ) pIdx->keyInfo.aColl[0] = pColl; + } +} + +/* +** Locate and return an entry from the db.aCollSeq hash table. If the entry +** specified by zName and nName is not found and parameter 'create' is +** true, then create a new entry. Otherwise return NULL. +** +** Each pointer stored in the sqlite3.aCollSeq hash table contains an +** array of three CollSeq structures. The first is the collation sequence +** prefferred for UTF-8, the second UTF-16le, and the third UTF-16be. +** +** Stored immediately after the three collation sequences is a copy of +** the collation sequence name. A pointer to this string is stored in +** each collation sequence structure. +*/ +static CollSeq * findCollSeqEntry( + sqlite3 *db, + const char *zName, + int nName, + int create +){ + CollSeq *pColl; + if( nName<0 ) nName = strlen(zName); + pColl = sqlite3HashFind(&db->aCollSeq, zName, nName); + + if( 0==pColl && create ){ + pColl = sqliteMalloc( 3*sizeof(*pColl) + nName + 1 ); + if( pColl ){ + pColl[0].zName = (char*)&pColl[3]; + pColl[0].enc = SQLITE_UTF8; + pColl[1].zName = (char*)&pColl[3]; + pColl[1].enc = SQLITE_UTF16LE; + pColl[2].zName = (char*)&pColl[3]; + pColl[2].enc = SQLITE_UTF16BE; + memcpy(pColl[0].zName, zName, nName); + pColl[0].zName[nName] = 0; + sqlite3HashInsert(&db->aCollSeq, pColl[0].zName, nName, pColl); + } + } + return pColl; +} + +/* +** Parameter zName points to a UTF-8 encoded string nName bytes long. +** Return the CollSeq* pointer for the collation sequence named zName +** for the encoding 'enc' from the database 'db'. +** +** If the entry specified is not found and 'create' is true, then create a +** new entry. Otherwise return NULL. +*/ +CollSeq *sqlite3FindCollSeq( + sqlite3 *db, + u8 enc, + const char *zName, + int nName, + int create +){ + CollSeq *pColl = findCollSeqEntry(db, zName, nName, create); + assert( SQLITE_UTF8==1 && SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); + assert( enc>=SQLITE_UTF8 && enc<=SQLITE_UTF16BE ); + if( pColl ) pColl += enc-1; + return pColl; +} + +/* +** Invoke the 'collation needed' callback to request a collation sequence +** in the database text encoding of name zName, length nName. +** If the collation sequence +*/ +static void callCollNeeded(sqlite3 *db, const char *zName, int nName){ + assert( !db->xCollNeeded || !db->xCollNeeded16 ); + if( nName<0 ) nName = strlen(zName); + if( db->xCollNeeded ){ + char *zExternal = sqliteStrNDup(zName, nName); + if( !zExternal ) return; + db->xCollNeeded(db->pCollNeededArg, db, (int)db->enc, zExternal); + sqliteFree(zExternal); + } + if( db->xCollNeeded16 ){ + char const *zExternal; + sqlite3_value *pTmp = sqlite3GetTransientValue(db); + sqlite3ValueSetStr(pTmp, -1, zName, SQLITE_UTF8, SQLITE_STATIC); + zExternal = sqlite3ValueText(pTmp, SQLITE_UTF16NATIVE); + if( !zExternal ) return; + db->xCollNeeded16(db->pCollNeededArg, db, (int)db->enc, zExternal); + } +} + +/* +** This routine is called if the collation factory fails to deliver a +** collation function in the best encoding but there may be other versions +** of this collation function (for other text encodings) available. Use one +** of these instead if they exist. Avoid a UTF-8 <-> UTF-16 conversion if +** possible. +*/ +static int synthCollSeq(Parse *pParse, CollSeq *pColl){ + CollSeq *pColl2; + char *z = pColl->zName; + int n = strlen(z); + sqlite3 *db = pParse->db; + int i; + static const u8 aEnc[] = { SQLITE_UTF16BE, SQLITE_UTF16LE, SQLITE_UTF8 }; + for(i=0; i<3; i++){ + pColl2 = sqlite3FindCollSeq(db, aEnc[i], z, n, 0); + if( pColl2->xCmp!=0 ){ + memcpy(pColl, pColl2, sizeof(CollSeq)); + return SQLITE_OK; + } + } + if( pParse->nErr==0 ){ + sqlite3ErrorMsg(pParse, "no such collation sequence: %.*s", n, z); + } + pParse->nErr++; + return SQLITE_ERROR; +} + +/* +** This routine is called on a collation sequence before it is used to +** check that it is defined. An undefined collation sequence exists when +** a database is loaded that contains references to collation sequences +** that have not been defined by sqlite3_create_collation() etc. +** +** If required, this routine calls the 'collation needed' callback to +** request a definition of the collating sequence. If this doesn't work, +** an equivalent collating sequence that uses a text encoding different +** from the main database is substituted, if one is available. +*/ +int sqlite3CheckCollSeq(Parse *pParse, CollSeq *pColl){ + if( pColl && !pColl->xCmp ){ + /* No collation sequence of this type for this encoding is registered. + ** Call the collation factory to see if it can supply us with one. + */ + callCollNeeded(pParse->db, pColl->zName, strlen(pColl->zName)); + if( !pColl->xCmp && synthCollSeq(pParse, pColl) ){ + return SQLITE_ERROR; + } + } + return SQLITE_OK; +} + +/* +** Call sqlite3CheckCollSeq() for all collating sequences in an index, +** in order to verify that all the necessary collating sequences are +** loaded. +*/ +int sqlite3CheckIndexCollSeq(Parse *pParse, Index *pIdx){ + if( pIdx ){ + int i; + for(i=0; inColumn; i++){ + if( sqlite3CheckCollSeq(pParse, pIdx->keyInfo.aColl[i]) ){ + return SQLITE_ERROR; + } + } + } + return SQLITE_OK; +} + +/* +** This function returns the collation sequence for database native text +** encoding identified by the string zName, length nName. +** +** If the requested collation sequence is not available, or not available +** in the database native encoding, the collation factory is invoked to +** request it. If the collation factory does not supply such a sequence, +** and the sequence is available in another text encoding, then that is +** returned instead. +** +** If no versions of the requested collations sequence are available, or +** another error occurs, NULL is returned and an error message written into +** pParse. +*/ +CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName, int nName){ + u8 enc = pParse->db->enc; + u8 initbusy = pParse->db->init.busy; + CollSeq *pColl = sqlite3FindCollSeq(pParse->db, enc, zName, nName, initbusy); + if( nName<0 ) nName = strlen(zName); + if( !initbusy && (!pColl || !pColl->xCmp) ){ + /* No collation sequence of this type for this encoding is registered. + ** Call the collation factory to see if it can supply us with one. + */ + callCollNeeded(pParse->db, zName, nName); + pColl = sqlite3FindCollSeq(pParse->db, enc, zName, nName, 0); + if( pColl && !pColl->xCmp ){ + /* There may be a version of the collation sequence that requires + ** translation between encodings. Search for it with synthCollSeq(). + */ + if( synthCollSeq(pParse, pColl) ){ + return 0; + } + } + } + + /* If nothing has been found, write the error message into pParse */ + if( !initbusy && (!pColl || !pColl->xCmp) ){ + if( pParse->nErr==0 ){ + sqlite3ErrorMsg(pParse, "no such collation sequence: %.*s", nName, zName); + } + pColl = 0; + } + return pColl; +} + + + +/* +** Scan the column type name zType (length nType) and return the +** associated affinity type. +*/ +char sqlite3AffinityType(const char *zType, int nType){ + int n, i; + static const struct { + const char *zSub; /* Keywords substring to search for */ + char nSub; /* length of zSub */ + char affinity; /* Affinity to return if it matches */ + } substrings[] = { + {"INT", 3, SQLITE_AFF_INTEGER}, + {"CHAR", 4, SQLITE_AFF_TEXT}, + {"CLOB", 4, SQLITE_AFF_TEXT}, + {"TEXT", 4, SQLITE_AFF_TEXT}, + {"BLOB", 4, SQLITE_AFF_NONE}, + }; + + if( nType==0 ){ + return SQLITE_AFF_NONE; + } + for(i=0; iaDb[iDb].schema_cookie+1, 0); + sqlite3VdbeAddOp(v, OP_SetCookie, iDb, 0); +} + +/* +** Measure the number of characters needed to output the given +** identifier. The number returned includes any quotes used +** but does not include the null terminator. +** +** The estimate is conservative. It might be larger that what is +** really needed. +*/ +static int identLength(const char *z){ + int n; + for(n=0; *z; n++, z++){ + if( *z=='"' ){ n++; } + } + return n + 2; +} + +/* +** Write an identifier onto the end of the given string. Add +** quote characters as needed. +*/ +static void identPut(char *z, int *pIdx, char *zSignedIdent){ + unsigned char *zIdent = (unsigned char*)zSignedIdent; + int i, j, needQuote; + i = *pIdx; + for(j=0; zIdent[j]; j++){ + if( !isalnum(zIdent[j]) && zIdent[j]!='_' ) break; + } + needQuote = zIdent[j]!=0 || isdigit(zIdent[0]) + || sqlite3KeywordCode(zIdent, j)!=TK_ID; + if( needQuote ) z[i++] = '"'; + for(j=0; zIdent[j]; j++){ + z[i++] = zIdent[j]; + if( zIdent[j]=='"' ) z[i++] = '"'; + } + if( needQuote ) z[i++] = '"'; + z[i] = 0; + *pIdx = i; +} + +/* +** Generate a CREATE TABLE statement appropriate for the given +** table. Memory to hold the text of the statement is obtained +** from sqliteMalloc() and must be freed by the calling function. +*/ +static char *createTableStmt(Table *p){ + int i, k, n; + char *zStmt; + char *zSep, *zSep2, *zEnd, *z; + Column *pCol; + n = 0; + for(pCol = p->aCol, i=0; inCol; i++, pCol++){ + n += identLength(pCol->zName); + z = pCol->zType; + if( z ){ + n += (strlen(z) + 1); + } + } + n += identLength(p->zName); + if( n<50 ){ + zSep = ""; + zSep2 = ","; + zEnd = ")"; + }else{ + zSep = "\n "; + zSep2 = ",\n "; + zEnd = "\n)"; + } + n += 35 + 6*p->nCol; + zStmt = sqliteMallocRaw( n ); + if( zStmt==0 ) return 0; + strcpy(zStmt, p->iDb==1 ? "CREATE TEMP TABLE " : "CREATE TABLE "); + k = strlen(zStmt); + identPut(zStmt, &k, p->zName); + zStmt[k++] = '('; + for(pCol=p->aCol, i=0; inCol; i++, pCol++){ + strcpy(&zStmt[k], zSep); + k += strlen(&zStmt[k]); + zSep = zSep2; + identPut(zStmt, &k, pCol->zName); + if( (z = pCol->zType)!=0 ){ + zStmt[k++] = ' '; + strcpy(&zStmt[k], z); + k += strlen(z); + } + } + strcpy(&zStmt[k], zEnd); + return zStmt; +} + +/* +** This routine is called to report the final ")" that terminates +** a CREATE TABLE statement. +** +** The table structure that other action routines have been building +** is added to the internal hash tables, assuming no errors have +** occurred. +** +** An entry for the table is made in the master table on disk, unless +** this is a temporary table or db->init.busy==1. When db->init.busy==1 +** it means we are reading the sqlite_master table because we just +** connected to the database or because the sqlite_master table has +** recently changes, so the entry for this table already exists in +** the sqlite_master table. We do not want to create it again. +** +** If the pSelect argument is not NULL, it means that this routine +** was called to create a table generated from a +** "CREATE TABLE ... AS SELECT ..." statement. The column names of +** the new table will match the result set of the SELECT. +*/ +void sqlite3EndTable(Parse *pParse, Token *pEnd, Select *pSelect){ + Table *p; + sqlite3 *db = pParse->db; + + if( (pEnd==0 && pSelect==0) || pParse->nErr || sqlite3_malloc_failed ) return; + p = pParse->pNewTable; + if( p==0 ) return; + + assert( !db->init.busy || !pSelect ); + + /* If the db->init.busy is 1 it means we are reading the SQL off the + ** "sqlite_master" or "sqlite_temp_master" table on the disk. + ** So do not write to the disk again. Extract the root page number + ** for the table from the db->init.newTnum field. (The page number + ** should have been put there by the sqliteOpenCb routine.) + */ + if( db->init.busy ){ + p->tnum = db->init.newTnum; + } + + /* If not initializing, then create a record for the new table + ** in the SQLITE_MASTER table of the database. The record number + ** for the new table entry should already be on the stack. + ** + ** If this is a TEMPORARY table, write the entry into the auxiliary + ** file instead of into the main database file. + */ + if( !db->init.busy ){ + int n; + Vdbe *v; + + v = sqlite3GetVdbe(pParse); + if( v==0 ) return; + + if( p->pSelect==0 ){ + /* A regular table */ + sqlite3VdbeAddOp(v, OP_CreateTable, p->iDb, 0); + }else{ + /* A view */ + sqlite3VdbeAddOp(v, OP_Integer, 0, 0); + } + + sqlite3VdbeAddOp(v, OP_Close, 0, 0); + + /* If this is a CREATE TABLE xx AS SELECT ..., execute the SELECT + ** statement to populate the new table. The root-page number for the + ** new table is on the top of the vdbe stack. + ** + ** Once the SELECT has been coded by sqlite3Select(), it is in a + ** suitable state to query for the column names and types to be used + ** by the new table. + */ + if( pSelect ){ + Table *pSelTab; + sqlite3VdbeAddOp(v, OP_Dup, 0, 0); + sqlite3VdbeAddOp(v, OP_Integer, p->iDb, 0); + sqlite3VdbeAddOp(v, OP_OpenWrite, 1, 0); + pParse->nTab = 2; + sqlite3Select(pParse, pSelect, SRT_Table, 1, 0, 0, 0, 0); + sqlite3VdbeAddOp(v, OP_Close, 1, 0); + if( pParse->nErr==0 ){ + pSelTab = sqlite3ResultSetOfSelect(pParse, 0, pSelect); + if( pSelTab==0 ) return; + assert( p->aCol==0 ); + p->nCol = pSelTab->nCol; + p->aCol = pSelTab->aCol; + pSelTab->nCol = 0; + pSelTab->aCol = 0; + sqlite3DeleteTable(0, pSelTab); + } + } + + sqlite3OpenMasterTable(v, p->iDb); + + sqlite3VdbeOp3(v, OP_String8, 0, 0, p->pSelect==0?"table":"view",P3_STATIC); + sqlite3VdbeOp3(v, OP_String8, 0, 0, p->zName, 0); + sqlite3VdbeOp3(v, OP_String8, 0, 0, p->zName, 0); + sqlite3VdbeAddOp(v, OP_Pull, 3, 0); + + if( pSelect ){ + char *z = createTableStmt(p); + n = z ? strlen(z) : 0; + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + sqlite3VdbeChangeP3(v, -1, z, n); + sqliteFree(z); + }else{ + if( p->pSelect ){ + sqlite3VdbeOp3(v, OP_String8, 0, 0, "CREATE VIEW ", P3_STATIC); + }else{ + sqlite3VdbeOp3(v, OP_String8, 0, 0, "CREATE TABLE ", P3_STATIC); + } + assert( pEnd!=0 ); + n = Addr(pEnd->z) - Addr(pParse->sNameToken.z) + 1; + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + sqlite3VdbeChangeP3(v, -1, pParse->sNameToken.z, n); + sqlite3VdbeAddOp(v, OP_Concat, 0, 0); + } + sqlite3VdbeOp3(v, OP_MakeRecord, 5, 0, "tttit", P3_STATIC); + sqlite3VdbeAddOp(v, OP_PutIntKey, 0, 0); + sqlite3ChangeCookie(db, v, p->iDb); + sqlite3VdbeAddOp(v, OP_Close, 0, 0); + sqlite3VdbeOp3(v, OP_ParseSchema, p->iDb, 0, + sqlite3MPrintf("tbl_name='%q'",p->zName), P3_DYNAMIC); + } + + /* Add the table to the in-memory representation of the database. + */ + if( db->init.busy && pParse->nErr==0 ){ + Table *pOld; + FKey *pFKey; + Db *pDb = &db->aDb[p->iDb]; + pOld = sqlite3HashInsert(&pDb->tblHash, p->zName, strlen(p->zName)+1, p); + if( pOld ){ + assert( p==pOld ); /* Malloc must have failed inside HashInsert() */ + return; + } + for(pFKey=p->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + int nTo = strlen(pFKey->zTo) + 1; + pFKey->pNextTo = sqlite3HashFind(&pDb->aFKey, pFKey->zTo, nTo); + sqlite3HashInsert(&pDb->aFKey, pFKey->zTo, nTo, pFKey); + } + pParse->pNewTable = 0; + db->nTable++; + db->flags |= SQLITE_InternChanges; + } +} + +/* +** The parser calls this routine in order to create a new VIEW +*/ +void sqlite3CreateView( + Parse *pParse, /* The parsing context */ + Token *pBegin, /* The CREATE token that begins the statement */ + Token *pName1, /* The token that holds the name of the view */ + Token *pName2, /* The token that holds the name of the view */ + Select *pSelect, /* A SELECT statement that will become the new view */ + int isTemp /* TRUE for a TEMPORARY view */ +){ + Table *p; + int n; + const unsigned char *z; + Token sEnd; + DbFixer sFix; + Token *pName; + + sqlite3StartTable(pParse, pBegin, pName1, pName2, isTemp, 1); + p = pParse->pNewTable; + if( p==0 || pParse->nErr ){ + sqlite3SelectDelete(pSelect); + return; + } + sqlite3TwoPartName(pParse, pName1, pName2, &pName); + if( sqlite3FixInit(&sFix, pParse, p->iDb, "view", pName) + && sqlite3FixSelect(&sFix, pSelect) + ){ + sqlite3SelectDelete(pSelect); + return; + } + + /* Make a copy of the entire SELECT statement that defines the view. + ** This will force all the Expr.token.z values to be dynamically + ** allocated rather than point to the input string - which means that + ** they will persist after the current sqlite3_exec() call returns. + */ + p->pSelect = sqlite3SelectDup(pSelect); + sqlite3SelectDelete(pSelect); + if( !pParse->db->init.busy ){ + sqlite3ViewGetColumnNames(pParse, p); + } + + /* Locate the end of the CREATE VIEW statement. Make sEnd point to + ** the end. + */ + sEnd = pParse->sLastToken; + if( sEnd.z[0]!=0 && sEnd.z[0]!=';' ){ + sEnd.z += sEnd.n; + } + sEnd.n = 0; + n = sEnd.z - pBegin->z; + z = (const unsigned char*)pBegin->z; + while( n>0 && (z[n-1]==';' || isspace(z[n-1])) ){ n--; } + sEnd.z = &z[n-1]; + sEnd.n = 1; + + /* Use sqlite3EndTable() to add the view to the SQLITE_MASTER table */ + sqlite3EndTable(pParse, &sEnd, 0); + return; +} + +/* +** The Table structure pTable is really a VIEW. Fill in the names of +** the columns of the view in the pTable structure. Return the number +** of errors. If an error is seen leave an error message in pParse->zErrMsg. +*/ +int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ + ExprList *pEList; + Select *pSel; + Table *pSelTab; + int nErr = 0; + + assert( pTable ); + + /* A positive nCol means the columns names for this view are + ** already known. + */ + if( pTable->nCol>0 ) return 0; + + /* A negative nCol is a special marker meaning that we are currently + ** trying to compute the column names. If we enter this routine with + ** a negative nCol, it means two or more views form a loop, like this: + ** + ** CREATE VIEW one AS SELECT * FROM two; + ** CREATE VIEW two AS SELECT * FROM one; + ** + ** Actually, this error is caught previously and so the following test + ** should always fail. But we will leave it in place just to be safe. + */ + if( pTable->nCol<0 ){ + sqlite3ErrorMsg(pParse, "view %s is circularly defined", pTable->zName); + return 1; + } + + /* If we get this far, it means we need to compute the table names. + */ + assert( pTable->pSelect ); /* If nCol==0, then pTable must be a VIEW */ + pSel = pTable->pSelect; + + /* Note that the call to sqlite3ResultSetOfSelect() will expand any + ** "*" elements in this list. But we will need to restore the list + ** back to its original configuration afterwards, so we save a copy of + ** the original in pEList. + */ + pEList = pSel->pEList; + pSel->pEList = sqlite3ExprListDup(pEList); + if( pSel->pEList==0 ){ + pSel->pEList = pEList; + return 1; /* Malloc failed */ + } + pTable->nCol = -1; + pSelTab = sqlite3ResultSetOfSelect(pParse, 0, pSel); + if( pSelTab ){ + assert( pTable->aCol==0 ); + pTable->nCol = pSelTab->nCol; + pTable->aCol = pSelTab->aCol; + pSelTab->nCol = 0; + pSelTab->aCol = 0; + sqlite3DeleteTable(0, pSelTab); + DbSetProperty(pParse->db, pTable->iDb, DB_UnresetViews); + }else{ + pTable->nCol = 0; + nErr++; + } + sqlite3SelectUnbind(pSel); + sqlite3ExprListDelete(pSel->pEList); + pSel->pEList = pEList; + return nErr; +} + +/* +** Clear the column names from every VIEW in database idx. +*/ +static void sqliteViewResetAll(sqlite3 *db, int idx){ + HashElem *i; + if( !DbHasProperty(db, idx, DB_UnresetViews) ) return; + for(i=sqliteHashFirst(&db->aDb[idx].tblHash); i; i=sqliteHashNext(i)){ + Table *pTab = sqliteHashData(i); + if( pTab->pSelect ){ + sqliteResetColumnNames(pTab); + } + } + DbClearProperty(db, idx, DB_UnresetViews); +} + +/* +** This routine is called to do the work of a DROP TABLE statement. +** pName is the name of the table to be dropped. +*/ +void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView){ + Table *pTab; + Vdbe *v; + int base; + sqlite3 *db = pParse->db; + int iDb; + + if( pParse->nErr || sqlite3_malloc_failed ) goto exit_drop_table; + assert( pName->nSrc==1 ); + pTab = sqlite3LocateTable(pParse, pName->a[0].zName, pName->a[0].zDatabase); + + if( pTab==0 ) goto exit_drop_table; + iDb = pTab->iDb; + assert( iDb>=0 && iDbnDb ); +#ifndef SQLITE_OMIT_AUTHORIZATION + { + int code; + const char *zTab = SCHEMA_TABLE(pTab->iDb); + const char *zDb = db->aDb[pTab->iDb].zName; + if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb)){ + goto exit_drop_table; + } + if( isView ){ + if( iDb==1 ){ + code = SQLITE_DROP_TEMP_VIEW; + }else{ + code = SQLITE_DROP_VIEW; + } + }else{ + if( iDb==1 ){ + code = SQLITE_DROP_TEMP_TABLE; + }else{ + code = SQLITE_DROP_TABLE; + } + } + if( sqlite3AuthCheck(pParse, code, pTab->zName, 0, zDb) ){ + goto exit_drop_table; + } + if( sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb) ){ + goto exit_drop_table; + } + } +#endif + if( pTab->readOnly ){ + sqlite3ErrorMsg(pParse, "table %s may not be dropped", pTab->zName); + pParse->nErr++; + goto exit_drop_table; + } + if( isView && pTab->pSelect==0 ){ + sqlite3ErrorMsg(pParse, "use DROP TABLE to delete table %s", pTab->zName); + goto exit_drop_table; + } + if( !isView && pTab->pSelect ){ + sqlite3ErrorMsg(pParse, "use DROP VIEW to delete view %s", pTab->zName); + goto exit_drop_table; + } + + /* Generate code to remove the table from the master table + ** on disk. + */ + v = sqlite3GetVdbe(pParse); + if( v ){ + static const VdbeOpList dropTable[] = { + { OP_Rewind, 0, ADDR(13), 0}, + { OP_String8, 0, 0, 0}, /* 1 */ + { OP_MemStore, 1, 1, 0}, + { OP_MemLoad, 1, 0, 0}, /* 3 */ + { OP_Column, 0, 2, 0}, /* sqlite_master.tbl_name */ + { OP_Ne, 0, ADDR(12), 0}, + { OP_String8, 0, 0, "trigger"}, + { OP_Column, 0, 2, 0}, /* sqlite_master.type */ + { OP_Eq, 0, ADDR(12), 0}, + { OP_Delete, 0, 0, 0}, + { OP_Rewind, 0, ADDR(13), 0}, + { OP_Goto, 0, ADDR(3), 0}, + { OP_Next, 0, ADDR(3), 0}, /* 12 */ + }; + Index *pIdx; + Trigger *pTrigger; + sqlite3BeginWriteOperation(pParse, 0, pTab->iDb); + + /* Drop all triggers associated with the table being dropped. Code + ** is generated to remove entries from sqlite_master and/or + ** sqlite_temp_master if required. + */ + pTrigger = pTab->pTrigger; + while( pTrigger ){ + assert( pTrigger->iDb==pTab->iDb || pTrigger->iDb==1 ); + sqlite3DropTriggerPtr(pParse, pTrigger, 1); + pTrigger = pTrigger->pNext; + } + + /* Drop all SQLITE_MASTER table and index entries that refer to the + ** table. The program name loops through the master table and deletes + ** every row that refers to a table of the same name as the one being + ** dropped. Triggers are handled seperately because a trigger can be + ** created in the temp database that refers to a table in another + ** database. + */ + sqlite3OpenMasterTable(v, pTab->iDb); + base = sqlite3VdbeAddOpList(v, ArraySize(dropTable), dropTable); + sqlite3VdbeChangeP3(v, base+1, pTab->zName, 0); + sqlite3ChangeCookie(db, v, pTab->iDb); + sqlite3VdbeAddOp(v, OP_Close, 0, 0); + if( !isView ){ + sqlite3VdbeAddOp(v, OP_Destroy, pTab->tnum, pTab->iDb); + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + sqlite3VdbeAddOp(v, OP_Destroy, pIdx->tnum, pIdx->iDb); + } + } + sqlite3VdbeOp3(v, OP_DropTable, pTab->iDb, 0, pTab->zName, 0); + } + sqliteViewResetAll(db, iDb); + +exit_drop_table: + sqlite3SrcListDelete(pName); +} + +/* +** This routine is called to create a new foreign key on the table +** currently under construction. pFromCol determines which columns +** in the current table point to the foreign key. If pFromCol==0 then +** connect the key to the last column inserted. pTo is the name of +** the table referred to. pToCol is a list of tables in the other +** pTo table that the foreign key points to. flags contains all +** information about the conflict resolution algorithms specified +** in the ON DELETE, ON UPDATE and ON INSERT clauses. +** +** An FKey structure is created and added to the table currently +** under construction in the pParse->pNewTable field. The new FKey +** is not linked into db->aFKey at this point - that does not happen +** until sqlite3EndTable(). +** +** The foreign key is set for IMMEDIATE processing. A subsequent call +** to sqlite3DeferForeignKey() might change this to DEFERRED. +*/ +void sqlite3CreateForeignKey( + Parse *pParse, /* Parsing context */ + ExprList *pFromCol, /* Columns in this table that point to other table */ + Token *pTo, /* Name of the other table */ + ExprList *pToCol, /* Columns in the other table */ + int flags /* Conflict resolution algorithms. */ +){ + Table *p = pParse->pNewTable; + int nByte; + int i; + int nCol; + char *z; + FKey *pFKey = 0; + + assert( pTo!=0 ); + if( p==0 || pParse->nErr ) goto fk_end; + if( pFromCol==0 ){ + int iCol = p->nCol-1; + if( iCol<0 ) goto fk_end; + if( pToCol && pToCol->nExpr!=1 ){ + sqlite3ErrorMsg(pParse, "foreign key on %s" + " should reference only one column of table %T", + p->aCol[iCol].zName, pTo); + goto fk_end; + } + nCol = 1; + }else if( pToCol && pToCol->nExpr!=pFromCol->nExpr ){ + sqlite3ErrorMsg(pParse, + "number of columns in foreign key does not match the number of " + "columns in the referenced table"); + goto fk_end; + }else{ + nCol = pFromCol->nExpr; + } + nByte = sizeof(*pFKey) + nCol*sizeof(pFKey->aCol[0]) + pTo->n + 1; + if( pToCol ){ + for(i=0; inExpr; i++){ + nByte += strlen(pToCol->a[i].zName) + 1; + } + } + pFKey = sqliteMalloc( nByte ); + if( pFKey==0 ) goto fk_end; + pFKey->pFrom = p; + pFKey->pNextFrom = p->pFKey; + z = (char*)&pFKey[1]; + pFKey->aCol = (struct sColMap*)z; + z += sizeof(struct sColMap)*nCol; + pFKey->zTo = z; + memcpy(z, pTo->z, pTo->n); + z[pTo->n] = 0; + z += pTo->n+1; + pFKey->pNextTo = 0; + pFKey->nCol = nCol; + if( pFromCol==0 ){ + pFKey->aCol[0].iFrom = p->nCol-1; + }else{ + for(i=0; inCol; j++){ + if( sqlite3StrICmp(p->aCol[j].zName, pFromCol->a[i].zName)==0 ){ + pFKey->aCol[i].iFrom = j; + break; + } + } + if( j>=p->nCol ){ + sqlite3ErrorMsg(pParse, + "unknown column \"%s\" in foreign key definition", + pFromCol->a[i].zName); + goto fk_end; + } + } + } + if( pToCol ){ + for(i=0; ia[i].zName); + pFKey->aCol[i].zCol = z; + memcpy(z, pToCol->a[i].zName, n); + z[n] = 0; + z += n+1; + } + } + pFKey->isDeferred = 0; + pFKey->deleteConf = flags & 0xff; + pFKey->updateConf = (flags >> 8 ) & 0xff; + pFKey->insertConf = (flags >> 16 ) & 0xff; + + /* Link the foreign key to the table as the last step. + */ + p->pFKey = pFKey; + pFKey = 0; + +fk_end: + sqliteFree(pFKey); + sqlite3ExprListDelete(pFromCol); + sqlite3ExprListDelete(pToCol); +} + +/* +** This routine is called when an INITIALLY IMMEDIATE or INITIALLY DEFERRED +** clause is seen as part of a foreign key definition. The isDeferred +** parameter is 1 for INITIALLY DEFERRED and 0 for INITIALLY IMMEDIATE. +** The behavior of the most recently created foreign key is adjusted +** accordingly. +*/ +void sqlite3DeferForeignKey(Parse *pParse, int isDeferred){ + Table *pTab; + FKey *pFKey; + if( (pTab = pParse->pNewTable)==0 || (pFKey = pTab->pFKey)==0 ) return; + pFKey->isDeferred = isDeferred; +} + +/* +** Create a new index for an SQL table. pIndex is the name of the index +** and pTable is the name of the table that is to be indexed. Both will +** be NULL for a primary key or an index that is created to satisfy a +** UNIQUE constraint. If pTable and pIndex are NULL, use pParse->pNewTable +** as the table to be indexed. pParse->pNewTable is a table that is +** currently being constructed by a CREATE TABLE statement. +** +** pList is a list of columns to be indexed. pList will be NULL if this +** is a primary key or unique-constraint on the most recent column added +** to the table currently under construction. +*/ +void sqlite3CreateIndex( + Parse *pParse, /* All information about this parse */ + Token *pName1, /* First part of index name. May be NULL */ + Token *pName2, /* Second part of index name. May be NULL */ + SrcList *pTblName, /* Table to index. Use pParse->pNewTable if 0 */ + ExprList *pList, /* A list of columns to be indexed */ + int onError, /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ + Token *pStart, /* The CREATE token that begins a CREATE TABLE statement */ + Token *pEnd /* The ")" that closes the CREATE INDEX statement */ +){ + Table *pTab = 0; /* Table to be indexed */ + Index *pIndex = 0; /* The index to be created */ + char *zName = 0; + int i, j; + Token nullId; /* Fake token for an empty ID list */ + DbFixer sFix; /* For assigning database names to pTable */ + int isTemp; /* True for a temporary index */ + sqlite3 *db = pParse->db; + + int iDb; /* Index of the database that is being written */ + Token *pName = 0; /* Unqualified name of the index to create */ + + if( pParse->nErr || sqlite3_malloc_failed ) goto exit_create_index; + + /* + ** Find the table that is to be indexed. Return early if not found. + */ + if( pTblName!=0 ){ + + /* Use the two-part index name to determine the database + ** to search for the table. 'Fix' the table name to this db + ** before looking up the table. + */ + assert( pName1 && pName2 ); + iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName); + if( iDb<0 ) goto exit_create_index; + + /* If the index name was unqualified, check if the the table + ** is a temp table. If so, set the database to 1. + */ + pTab = sqlite3SrcListLookup(pParse, pTblName); + if( pName2 && pName2->n==0 && pTab && pTab->iDb==1 ){ + iDb = 1; + } + + if( sqlite3FixInit(&sFix, pParse, iDb, "index", pName) && + sqlite3FixSrcList(&sFix, pTblName) + ){ + goto exit_create_index; + } + pTab = sqlite3LocateTable(pParse, pTblName->a[0].zName, + pTblName->a[0].zDatabase); + if( !pTab ) goto exit_create_index; + assert( iDb==pTab->iDb ); + }else{ + assert( pName==0 ); + pTab = pParse->pNewTable; + iDb = pTab->iDb; + } + + if( pTab==0 || pParse->nErr ) goto exit_create_index; + if( pTab->readOnly ){ + sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName); + goto exit_create_index; + } + if( pTab->pSelect ){ + sqlite3ErrorMsg(pParse, "views may not be indexed"); + goto exit_create_index; + } + isTemp = pTab->iDb==1; + + /* + ** Find the name of the index. Make sure there is not already another + ** index or table with the same name. + ** + ** Exception: If we are reading the names of permanent indices from the + ** sqlite_master table (because some other process changed the schema) and + ** one of the index names collides with the name of a temporary table or + ** index, then we will continue to process this index. + ** + ** If pName==0 it means that we are + ** dealing with a primary key or UNIQUE constraint. We have to invent our + ** own name. + */ + if( pName ){ + zName = sqlite3NameFromToken(pName); + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ) goto exit_create_index; + if( zName==0 ) goto exit_create_index; + if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ + goto exit_create_index; + } + if( !db->init.busy ){ + Index *pISameName; /* Another index with the same name */ + Table *pTSameName; /* A table with same name as the index */ + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ) goto exit_create_index; + if( (pISameName = sqlite3FindIndex(db, zName, db->aDb[iDb].zName))!=0 ){ + sqlite3ErrorMsg(pParse, "index %s already exists", zName); + goto exit_create_index; + } + if( (pTSameName = sqlite3FindTable(db, zName, 0))!=0 ){ + sqlite3ErrorMsg(pParse, "there is already a table named %s", zName); + goto exit_create_index; + } + } + }else if( pName==0 ){ + char zBuf[30]; + int n; + Index *pLoop; + for(pLoop=pTab->pIndex, n=1; pLoop; pLoop=pLoop->pNext, n++){} + sprintf(zBuf,"_%d",n); + zName = 0; + sqlite3SetString(&zName, "sqlite_autoindex_", pTab->zName, zBuf, (char*)0); + if( zName==0 ) goto exit_create_index; + } + + /* Check for authorization to create an index. + */ +#ifndef SQLITE_OMIT_AUTHORIZATION + { + const char *zDb = db->aDb[pTab->iDb].zName; + if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0, zDb) ){ + goto exit_create_index; + } + i = SQLITE_CREATE_INDEX; + if( isTemp ) i = SQLITE_CREATE_TEMP_INDEX; + if( sqlite3AuthCheck(pParse, i, zName, pTab->zName, zDb) ){ + goto exit_create_index; + } + } +#endif + + /* If pList==0, it means this routine was called to make a primary + ** key out of the last column added to the table under construction. + ** So create a fake list to simulate this. + */ + if( pList==0 ){ + nullId.z = pTab->aCol[pTab->nCol-1].zName; + nullId.n = strlen(nullId.z); + pList = sqlite3ExprListAppend(0, 0, &nullId); + if( pList==0 ) goto exit_create_index; + } + + /* + ** Allocate the index structure. + */ + pIndex = sqliteMalloc( sizeof(Index) + strlen(zName) + 1 + + (sizeof(int) + sizeof(CollSeq*))*pList->nExpr ); + if( pIndex==0 ) goto exit_create_index; + pIndex->aiColumn = (int*)&pIndex->keyInfo.aColl[pList->nExpr]; + pIndex->zName = (char*)&pIndex->aiColumn[pList->nExpr]; + strcpy(pIndex->zName, zName); + pIndex->pTable = pTab; + pIndex->nColumn = pList->nExpr; + pIndex->onError = onError; + pIndex->autoIndex = pName==0; + pIndex->iDb = iDb; + + /* Scan the names of the columns of the table to be indexed and + ** load the column indices into the Index structure. Report an error + ** if any column is not found. + */ + for(i=0; inExpr; i++){ + for(j=0; jnCol; j++){ + if( sqlite3StrICmp(pList->a[i].zName, pTab->aCol[j].zName)==0 ) break; + } + if( j>=pTab->nCol ){ + sqlite3ErrorMsg(pParse, "table %s has no column named %s", + pTab->zName, pList->a[i].zName); + goto exit_create_index; + } + pIndex->aiColumn[i] = j; + if( pList->a[i].pExpr ){ + assert( pList->a[i].pExpr->pColl ); + pIndex->keyInfo.aColl[i] = pList->a[i].pExpr->pColl; + }else{ + pIndex->keyInfo.aColl[i] = pTab->aCol[j].pColl; + } + assert( pIndex->keyInfo.aColl[i] ); + if( !db->init.busy && + sqlite3CheckCollSeq(pParse, pIndex->keyInfo.aColl[i]) + ){ + goto exit_create_index; + } + } + pIndex->keyInfo.nField = pList->nExpr; + + if( pTab==pParse->pNewTable ){ + /* This routine has been called to create an automatic index as a + ** result of a PRIMARY KEY or UNIQUE clause on a column definition, or + ** a PRIMARY KEY or UNIQUE clause following the column definitions. + ** i.e. one of: + ** + ** CREATE TABLE t(x PRIMARY KEY, y); + ** CREATE TABLE t(x, y, UNIQUE(x, y)); + ** + ** Either way, check to see if the table already has such an index. If + ** so, don't bother creating this one. This only applies to + ** automatically created indices. Users can do as they wish with + ** explicit indices. + */ + Index *pIdx; + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + int k; + assert( pIdx->onError!=OE_None ); + assert( pIdx->autoIndex ); + assert( pIndex->onError!=OE_None ); + + if( pIdx->nColumn!=pIndex->nColumn ) continue; + for(k=0; knColumn; k++){ + if( pIdx->aiColumn[k]!=pIndex->aiColumn[k] ) break; + if( pIdx->keyInfo.aColl[k]!=pIndex->keyInfo.aColl[k] ) break; + } + if( k==pIdx->nColumn ){ + if( pIdx->onError!=pIndex->onError ){ + /* This constraint creates the same index as a previous + ** constraint specified somewhere in the CREATE TABLE statement. + ** However the ON CONFLICT clauses are different. If both this + ** constraint and the previous equivalent constraint have explicit + ** ON CONFLICT clauses this is an error. Otherwise, use the + ** explicitly specified behaviour for the index. + */ + if( !(pIdx->onError==OE_Default || pIndex->onError==OE_Default) ){ + sqlite3ErrorMsg(pParse, + "conflicting ON CONFLICT clauses specified", 0); + } + if( pIdx->onError==OE_Default ){ + pIdx->onError = pIndex->onError; + } + } + goto exit_create_index; + } + } + } + + /* Link the new Index structure to its table and to the other + ** in-memory database structures. + */ + if( db->init.busy ){ + Index *p; + p = sqlite3HashInsert(&db->aDb[pIndex->iDb].idxHash, + pIndex->zName, strlen(pIndex->zName)+1, pIndex); + if( p ){ + assert( p==pIndex ); /* Malloc must have failed */ + goto exit_create_index; + } + db->flags |= SQLITE_InternChanges; + if( pTblName!=0 ){ + pIndex->tnum = db->init.newTnum; + } + } + + /* If the db->init.busy is 0 then create the index on disk. This + ** involves writing the index into the master table and filling in the + ** index with the current table contents. + ** + ** The db->init.busy is 0 when the user first enters a CREATE INDEX + ** command. db->init.busy is 1 when a database is opened and + ** CREATE INDEX statements are read out of the master table. In + ** the latter case the index already exists on disk, which is why + ** we don't want to recreate it. + ** + ** If pTblName==0 it means this index is generated as a primary key + ** or UNIQUE constraint of a CREATE TABLE statement. Since the table + ** has just been created, it contains no data and the index initialization + ** step can be skipped. + */ + else if( db->init.busy==0 ){ + int n; + Vdbe *v; + int lbl1, lbl2; + + v = sqlite3GetVdbe(pParse); + if( v==0 ) goto exit_create_index; + if( pTblName!=0 ){ + sqlite3BeginWriteOperation(pParse, 0, iDb); + sqlite3OpenMasterTable(v, iDb); + } + sqlite3VdbeAddOp(v, OP_NewRecno, 0, 0); + sqlite3VdbeOp3(v, OP_String8, 0, 0, "index", P3_STATIC); + sqlite3VdbeOp3(v, OP_String8, 0, 0, pIndex->zName, 0); + sqlite3VdbeOp3(v, OP_String8, 0, 0, pTab->zName, 0); + sqlite3VdbeAddOp(v, OP_CreateIndex, iDb, 0); + if( pTblName ){ + sqlite3VdbeAddOp(v, OP_Dup, 0, 0); + sqlite3VdbeAddOp(v, OP_Integer, iDb, 0); + sqlite3VdbeOp3(v, OP_OpenWrite, 1, 0, + (char*)&pIndex->keyInfo, P3_KEYINFO); + } + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + if( pStart && pEnd ){ + if( onError==OE_None ){ + sqlite3VdbeChangeP3(v, -1, "CREATE INDEX ", P3_STATIC); + }else{ + sqlite3VdbeChangeP3(v, -1, "CREATE UNIQUE INDEX ", P3_STATIC); + } + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + n = Addr(pEnd->z) - Addr(pName->z) + 1; + sqlite3VdbeChangeP3(v, -1, pName->z, n); + sqlite3VdbeAddOp(v, OP_Concat, 0, 0); + } + sqlite3VdbeOp3(v, OP_MakeRecord, 5, 0, "tttit", P3_STATIC); + sqlite3VdbeAddOp(v, OP_PutIntKey, 0, 0); + if( pTblName ){ + sqlite3VdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqlite3VdbeAddOp(v, OP_OpenRead, 2, pTab->tnum); + /* VdbeComment((v, "%s", pTab->zName)); */ + sqlite3VdbeAddOp(v, OP_SetNumColumns, 2, pTab->nCol); + lbl2 = sqlite3VdbeMakeLabel(v); + sqlite3VdbeAddOp(v, OP_Rewind, 2, lbl2); + lbl1 = sqlite3VdbeCurrentAddr(v); + sqlite3GenerateIndexKey(v, pIndex, 2); + sqlite3VdbeOp3(v, OP_IdxPut, 1, pIndex->onError!=OE_None, + "indexed columns are not unique", P3_STATIC); + sqlite3VdbeAddOp(v, OP_Next, 2, lbl1); + sqlite3VdbeResolveLabel(v, lbl2); + sqlite3VdbeAddOp(v, OP_Close, 2, 0); + sqlite3VdbeAddOp(v, OP_Close, 1, 0); + sqlite3ChangeCookie(db, v, iDb); + sqlite3VdbeAddOp(v, OP_Close, 0, 0); + sqlite3VdbeOp3(v, OP_ParseSchema, iDb, 0, + sqlite3MPrintf("name='%q'", pIndex->zName), P3_DYNAMIC); + } + } + + /* When adding an index to the list of indices for a table, make + ** sure all indices labeled OE_Replace come after all those labeled + ** OE_Ignore. This is necessary for the correct operation of UPDATE + ** and INSERT. + */ + if( db->init.busy || pTblName==0 ){ + if( onError!=OE_Replace || pTab->pIndex==0 + || pTab->pIndex->onError==OE_Replace){ + pIndex->pNext = pTab->pIndex; + pTab->pIndex = pIndex; + }else{ + Index *pOther = pTab->pIndex; + while( pOther->pNext && pOther->pNext->onError!=OE_Replace ){ + pOther = pOther->pNext; + } + pIndex->pNext = pOther->pNext; + pOther->pNext = pIndex; + } + pIndex = 0; + } + + /* Clean up before exiting */ +exit_create_index: + if( pIndex ){ + freeIndex(pIndex); + } + sqlite3ExprListDelete(pList); + sqlite3SrcListDelete(pTblName); + sqliteFree(zName); + return; +} + +/* +** This routine will drop an existing named index. This routine +** implements the DROP INDEX statement. +*/ +void sqlite3DropIndex(Parse *pParse, SrcList *pName){ + Index *pIndex; + Vdbe *v; + sqlite3 *db = pParse->db; + + if( pParse->nErr || sqlite3_malloc_failed ) return; + assert( pName->nSrc==1 ); + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ) return; + pIndex = sqlite3FindIndex(db, pName->a[0].zName, pName->a[0].zDatabase); + if( pIndex==0 ){ + sqlite3ErrorMsg(pParse, "no such index: %S", pName, 0); + pParse->checkSchema = 1; + goto exit_drop_index; + } + if( pIndex->autoIndex ){ + sqlite3ErrorMsg(pParse, "index associated with UNIQUE " + "or PRIMARY KEY constraint cannot be dropped", 0); + goto exit_drop_index; + } +#ifndef SQLITE_OMIT_AUTHORIZATION + { + int code = SQLITE_DROP_INDEX; + Table *pTab = pIndex->pTable; + const char *zDb = db->aDb[pIndex->iDb].zName; + const char *zTab = SCHEMA_TABLE(pIndex->iDb); + if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){ + goto exit_drop_index; + } + if( pIndex->iDb ) code = SQLITE_DROP_TEMP_INDEX; + if( sqlite3AuthCheck(pParse, code, pIndex->zName, pTab->zName, zDb) ){ + goto exit_drop_index; + } + } +#endif + + /* Generate code to remove the index and from the master table */ + v = sqlite3GetVdbe(pParse); + if( v ){ + static const VdbeOpList dropIndex[] = { + { OP_Rewind, 0, ADDR(9), 0}, + { OP_String8, 0, 0, 0}, /* 1 */ + { OP_MemStore, 1, 1, 0}, + { OP_MemLoad, 1, 0, 0}, /* 3 */ + { OP_Column, 0, 1, 0}, + { OP_Eq, 0, ADDR(8), 0}, + { OP_Next, 0, ADDR(3), 0}, + { OP_Goto, 0, ADDR(9), 0}, + { OP_Delete, 0, 0, 0}, /* 8 */ + }; + int base; + + sqlite3BeginWriteOperation(pParse, 0, pIndex->iDb); + sqlite3OpenMasterTable(v, pIndex->iDb); + base = sqlite3VdbeAddOpList(v, ArraySize(dropIndex), dropIndex); + sqlite3VdbeChangeP3(v, base+1, pIndex->zName, 0); + sqlite3ChangeCookie(db, v, pIndex->iDb); + sqlite3VdbeAddOp(v, OP_Close, 0, 0); + sqlite3VdbeAddOp(v, OP_Destroy, pIndex->tnum, pIndex->iDb); + sqlite3VdbeOp3(v, OP_DropIndex, pIndex->iDb, 0, pIndex->zName, 0); + } + +exit_drop_index: + sqlite3SrcListDelete(pName); +} + +/* +** Append a new element to the given IdList. Create a new IdList if +** need be. +** +** A new IdList is returned, or NULL if malloc() fails. +*/ +IdList *sqlite3IdListAppend(IdList *pList, Token *pToken){ + if( pList==0 ){ + pList = sqliteMalloc( sizeof(IdList) ); + if( pList==0 ) return 0; + pList->nAlloc = 0; + } + if( pList->nId>=pList->nAlloc ){ + struct IdList_item *a; + pList->nAlloc = pList->nAlloc*2 + 5; + a = sqliteRealloc(pList->a, pList->nAlloc*sizeof(pList->a[0]) ); + if( a==0 ){ + sqlite3IdListDelete(pList); + return 0; + } + pList->a = a; + } + memset(&pList->a[pList->nId], 0, sizeof(pList->a[0])); + pList->a[pList->nId].zName = sqlite3NameFromToken(pToken); + pList->nId++; + return pList; +} + +/* +** Append a new table name to the given SrcList. Create a new SrcList if +** need be. A new entry is created in the SrcList even if pToken is NULL. +** +** A new SrcList is returned, or NULL if malloc() fails. +** +** If pDatabase is not null, it means that the table has an optional +** database name prefix. Like this: "database.table". The pDatabase +** points to the table name and the pTable points to the database name. +** The SrcList.a[].zName field is filled with the table name which might +** come from pTable (if pDatabase is NULL) or from pDatabase. +** SrcList.a[].zDatabase is filled with the database name from pTable, +** or with NULL if no database is specified. +** +** In other words, if call like this: +** +** sqlite3SrcListAppend(A,B,0); +** +** Then B is a table name and the database name is unspecified. If called +** like this: +** +** sqlite3SrcListAppend(A,B,C); +** +** Then C is the table name and B is the database name. +*/ +SrcList *sqlite3SrcListAppend(SrcList *pList, Token *pTable, Token *pDatabase){ + struct SrcList_item *pItem; + if( pList==0 ){ + pList = sqliteMalloc( sizeof(SrcList) ); + if( pList==0 ) return 0; + pList->nAlloc = 1; + } + if( pList->nSrc>=pList->nAlloc ){ + SrcList *pNew; + pList->nAlloc *= 2; + pNew = sqliteRealloc(pList, + sizeof(*pList) + (pList->nAlloc-1)*sizeof(pList->a[0]) ); + if( pNew==0 ){ + sqlite3SrcListDelete(pList); + return 0; + } + pList = pNew; + } + pItem = &pList->a[pList->nSrc]; + memset(pItem, 0, sizeof(pList->a[0])); + if( pDatabase && pDatabase->z==0 ){ + pDatabase = 0; + } + if( pDatabase && pTable ){ + Token *pTemp = pDatabase; + pDatabase = pTable; + pTable = pTemp; + } + pItem->zName = sqlite3NameFromToken(pTable); + pItem->zDatabase = sqlite3NameFromToken(pDatabase); + pItem->iCursor = -1; + pList->nSrc++; + return pList; +} + +/* +** Assign cursors to all tables in a SrcList +*/ +void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){ + int i; + for(i=0; inSrc; i++){ + if( pList->a[i].iCursor<0 ){ + pList->a[i].iCursor = pParse->nTab++; + } + } +} + +/* +** Add an alias to the last identifier on the given identifier list. +*/ +void sqlite3SrcListAddAlias(SrcList *pList, Token *pToken){ + if( pList && pList->nSrc>0 ){ + pList->a[pList->nSrc-1].zAlias = sqlite3NameFromToken(pToken); + } +} + +/* +** Delete an IdList. +*/ +void sqlite3IdListDelete(IdList *pList){ + int i; + if( pList==0 ) return; + for(i=0; inId; i++){ + sqliteFree(pList->a[i].zName); + } + sqliteFree(pList->a); + sqliteFree(pList); +} + +/* +** Return the index in pList of the identifier named zId. Return -1 +** if not found. +*/ +int sqlite3IdListIndex(IdList *pList, const char *zName){ + int i; + if( pList==0 ) return -1; + for(i=0; inId; i++){ + if( sqlite3StrICmp(pList->a[i].zName, zName)==0 ) return i; + } + return -1; +} + +/* +** Delete an entire SrcList including all its substructure. +*/ +void sqlite3SrcListDelete(SrcList *pList){ + int i; + struct SrcList_item *pItem; + if( pList==0 ) return; + for(pItem=pList->a, i=0; inSrc; i++, pItem++){ + sqliteFree(pItem->zDatabase); + sqliteFree(pItem->zName); + sqliteFree(pItem->zAlias); + if( pItem->pTab && pItem->pTab->isTransient ){ + sqlite3DeleteTable(0, pItem->pTab); + } + sqlite3SelectDelete(pItem->pSelect); + sqlite3ExprDelete(pItem->pOn); + sqlite3IdListDelete(pItem->pUsing); + } + sqliteFree(pList); +} + +/* +** Begin a transaction +*/ +void sqlite3BeginTransaction(Parse *pParse, int type){ + sqlite3 *db; + Vdbe *v; + int i; + + if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return; + if( pParse->nErr || sqlite3_malloc_failed ) return; + if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "BEGIN", 0, 0) ) return; + + v = sqlite3GetVdbe(pParse); + if( !v ) return; + if( type!=TK_DEFERRED ){ + for(i=0; inDb; i++){ + sqlite3VdbeAddOp(v, OP_Transaction, i, (type==TK_EXCLUSIVE)+1); + } + } + sqlite3VdbeAddOp(v, OP_AutoCommit, 0, 0); +} + +/* +** Commit a transaction +*/ +void sqlite3CommitTransaction(Parse *pParse){ + sqlite3 *db; + Vdbe *v; + + if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return; + if( pParse->nErr || sqlite3_malloc_failed ) return; + if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "COMMIT", 0, 0) ) return; + + v = sqlite3GetVdbe(pParse); + if( v ){ + sqlite3VdbeAddOp(v, OP_AutoCommit, 1, 0); + } +} + +/* +** Rollback a transaction +*/ +void sqlite3RollbackTransaction(Parse *pParse){ + sqlite3 *db; + Vdbe *v; + + if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return; + if( pParse->nErr || sqlite3_malloc_failed ) return; + if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "ROLLBACK", 0, 0) ) return; + + v = sqlite3GetVdbe(pParse); + if( v ){ + sqlite3VdbeAddOp(v, OP_AutoCommit, 1, 1); + } +} + +/* +** Make sure the TEMP database is open and available for use. Return +** the number of errors. Leave any error messages in the pParse structure. +*/ +static int sqlite3OpenTempDatabase(Parse *pParse){ + sqlite3 *db = pParse->db; + if( db->aDb[1].pBt==0 && !pParse->explain ){ + int rc = sqlite3BtreeFactory(db, 0, 0, MAX_PAGES, &db->aDb[1].pBt); + if( rc!=SQLITE_OK ){ + sqlite3ErrorMsg(pParse, "unable to open a temporary database " + "file for storing temporary tables"); + pParse->rc = rc; + return 1; + } + if( db->flags & !db->autoCommit ){ + rc = sqlite3BtreeBeginTrans(db->aDb[1].pBt, 1); + if( rc!=SQLITE_OK ){ + sqlite3ErrorMsg(pParse, "unable to get a write lock on " + "the temporary database file"); + pParse->rc = rc; + return 1; + } + } + } + return 0; +} + +/* +** Generate VDBE code that will verify the schema cookie and start +** a read-transaction for all named database files. +** +** It is important that all schema cookies be verified and all +** read transactions be started before anything else happens in +** the VDBE program. But this routine can be called after much other +** code has been generated. So here is what we do: +** +** The first time this routine is called, we code an OP_Goto that +** will jump to a subroutine at the end of the program. Then we +** record every database that needs its schema verified in the +** pParse->cookieMask field. Later, after all other code has been +** generated, the subroutine that does the cookie verifications and +** starts the transactions will be coded and the OP_Goto P2 value +** will be made to point to that subroutine. The generation of the +** cookie verification subroutine code happens in sqlite3FinishCoding(). +** +** If iDb<0 then code the OP_Goto only - don't set flag to verify the +** schema on any databases. This can be used to position the OP_Goto +** early in the code, before we know if any database tables will be used. +*/ +void sqlite3CodeVerifySchema(Parse *pParse, int iDb){ + sqlite3 *db; + Vdbe *v; + int mask; + + v = sqlite3GetVdbe(pParse); + if( v==0 ) return; /* This only happens if there was a prior error */ + db = pParse->db; + if( pParse->cookieGoto==0 ){ + pParse->cookieGoto = sqlite3VdbeAddOp(v, OP_Goto, 0, 0)+1; + } + if( iDb>=0 ){ + assert( iDbnDb ); + assert( db->aDb[iDb].pBt!=0 || iDb==1 ); + assert( iDb<32 ); + mask = 1<cookieMask & mask)==0 ){ + pParse->cookieMask |= mask; + pParse->cookieValue[iDb] = db->aDb[iDb].schema_cookie; + if( iDb==1 ){ + sqlite3OpenTempDatabase(pParse); + } + } + } +} + +/* +** Generate VDBE code that prepares for doing an operation that +** might change the database. +** +** This routine starts a new transaction if we are not already within +** a transaction. If we are already within a transaction, then a checkpoint +** is set if the setStatement parameter is true. A checkpoint should +** be set for operations that might fail (due to a constraint) part of +** the way through and which will need to undo some writes without having to +** rollback the whole transaction. For operations where all constraints +** can be checked before any changes are made to the database, it is never +** necessary to undo a write and the checkpoint should not be set. +** +** Only database iDb and the temp database are made writable by this call. +** If iDb==0, then the main and temp databases are made writable. If +** iDb==1 then only the temp database is made writable. If iDb>1 then the +** specified auxiliary database and the temp database are made writable. +*/ +void sqlite3BeginWriteOperation(Parse *pParse, int setStatement, int iDb){ + Vdbe *v = sqlite3GetVdbe(pParse); + if( v==0 ) return; + sqlite3CodeVerifySchema(pParse, iDb); + pParse->writeMask |= 1<db->aDb[1].pBt!=0 ){ + sqlite3BeginWriteOperation(pParse, setStatement, 1); + } +} + +/* +** Return the transient sqlite3_value object used for encoding conversions +** during SQL compilation. +*/ +sqlite3_value *sqlite3GetTransientValue(sqlite3 *db){ + if( !db->pValue ){ + db->pValue = sqlite3ValueNew(); + } + return db->pValue; +} diff --git a/kopete/plugins/statistics/sqlite/date.c b/kopete/plugins/statistics/sqlite/date.c new file mode 100644 index 00000000..634e81d5 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/date.c @@ -0,0 +1,893 @@ +/* +** 2003 October 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the C functions that implement date and time +** functions for SQLite. +** +** There is only one exported symbol in this file - the function +** sqlite3RegisterDateTimeFunctions() found at the bottom of the file. +** All other code has file scope. +** +** $Id$ +** +** NOTES: +** +** SQLite processes all times and dates as Julian Day numbers. The +** dates and times are stored as the number of days since noon +** in Greenwich on November 24, 4714 B.C. according to the Gregorian +** calendar system. +** +** 1970-01-01 00:00:00 is JD 2440587.5 +** 2000-01-01 00:00:00 is JD 2451544.5 +** +** This implemention requires years to be expressed as a 4-digit number +** which means that only dates between 0000-01-01 and 9999-12-31 can +** be represented, even though julian day numbers allow a much wider +** range of dates. +** +** The Gregorian calendar system is used for all dates and times, +** even those that predate the Gregorian calendar. Historians usually +** use the Julian calendar for dates prior to 1582-10-15 and for some +** dates afterwards, depending on locale. Beware of this difference. +** +** The conversion algorithms are implemented based on descriptions +** in the following text: +** +** Jean Meeus +** Astronomical Algorithms, 2nd Edition, 1998 +** ISBM 0-943396-61-1 +** Willmann-Bell, Inc +** Richmond, Virginia (USA) +*/ +#include "sqliteInt.h" +#include "os.h" +#include +#include +#include +#include + +#ifndef SQLITE_OMIT_DATETIME_FUNCS + +/* +** A structure for holding a single date and time. +*/ +typedef struct DateTime DateTime; +struct DateTime { + double rJD; /* The julian day number */ + int Y, M, D; /* Year, month, and day */ + int h, m; /* Hour and minutes */ + int tz; /* Timezone offset in minutes */ + double s; /* Seconds */ + char validYMD; /* True if Y,M,D are valid */ + char validHMS; /* True if h,m,s are valid */ + char validJD; /* True if rJD is valid */ + char validTZ; /* True if tz is valid */ +}; + + +/* +** Convert zDate into one or more integers. Additional arguments +** come in groups of 5 as follows: +** +** N number of digits in the integer +** min minimum allowed value of the integer +** max maximum allowed value of the integer +** nextC first character after the integer +** pVal where to write the integers value. +** +** Conversions continue until one with nextC==0 is encountered. +** The function returns the number of successful conversions. +*/ +static int getDigits(const char *zDate, ...){ + va_list ap; + int val; + int N; + int min; + int max; + int nextC; + int *pVal; + int cnt = 0; + va_start(ap, zDate); + do{ + N = va_arg(ap, int); + min = va_arg(ap, int); + max = va_arg(ap, int); + nextC = va_arg(ap, int); + pVal = va_arg(ap, int*); + val = 0; + while( N-- ){ + if( !isdigit(*(u8*)zDate) ){ + return cnt; + } + val = val*10 + *zDate - '0'; + zDate++; + } + if( valmax || (nextC!=0 && nextC!=*zDate) ){ + return cnt; + } + *pVal = val; + zDate++; + cnt++; + }while( nextC ); + return cnt; +} + +/* +** Read text from z[] and convert into a floating point number. Return +** the number of digits converted. +*/ +static int getValue(const char *z, double *pR){ + const char *zEnd; + *pR = sqlite3AtoF(z, &zEnd); + return zEnd - z; +} + +/* +** Parse a timezone extension on the end of a date-time. +** The extension is of the form: +** +** (+/-)HH:MM +** +** If the parse is successful, write the number of minutes +** of change in *pnMin and return 0. If a parser error occurs, +** return 0. +** +** A missing specifier is not considered an error. +*/ +static int parseTimezone(const char *zDate, DateTime *p){ + int sgn = 0; + int nHr, nMn; + while( isspace(*(u8*)zDate) ){ zDate++; } + p->tz = 0; + if( *zDate=='-' ){ + sgn = -1; + }else if( *zDate=='+' ){ + sgn = +1; + }else{ + return *zDate!=0; + } + zDate++; + if( getDigits(zDate, 2, 0, 14, ':', &nHr, 2, 0, 59, 0, &nMn)!=2 ){ + return 1; + } + zDate += 5; + p->tz = sgn*(nMn + nHr*60); + while( isspace(*(u8*)zDate) ){ zDate++; } + return *zDate!=0; +} + +/* +** Parse times of the form HH:MM or HH:MM:SS or HH:MM:SS.FFFF. +** The HH, MM, and SS must each be exactly 2 digits. The +** fractional seconds FFFF can be one or more digits. +** +** Return 1 if there is a parsing error and 0 on success. +*/ +static int parseHhMmSs(const char *zDate, DateTime *p){ + int h, m, s; + double ms = 0.0; + if( getDigits(zDate, 2, 0, 24, ':', &h, 2, 0, 59, 0, &m)!=2 ){ + return 1; + } + zDate += 5; + if( *zDate==':' ){ + zDate++; + if( getDigits(zDate, 2, 0, 59, 0, &s)!=1 ){ + return 1; + } + zDate += 2; + if( *zDate=='.' && isdigit((u8)zDate[1]) ){ + double rScale = 1.0; + zDate++; + while( isdigit(*(u8*)zDate) ){ + ms = ms*10.0 + *zDate - '0'; + rScale *= 10.0; + zDate++; + } + ms /= rScale; + } + }else{ + s = 0; + } + p->validJD = 0; + p->validHMS = 1; + p->h = h; + p->m = m; + p->s = s + ms; + if( parseTimezone(zDate, p) ) return 1; + p->validTZ = p->tz!=0; + return 0; +} + +/* +** Convert from YYYY-MM-DD HH:MM:SS to julian day. We always assume +** that the YYYY-MM-DD is according to the Gregorian calendar. +** +** Reference: Meeus page 61 +*/ +static void computeJD(DateTime *p){ + int Y, M, D, A, B, X1, X2; + + if( p->validJD ) return; + if( p->validYMD ){ + Y = p->Y; + M = p->M; + D = p->D; + }else{ + Y = 2000; /* If no YMD specified, assume 2000-Jan-01 */ + M = 1; + D = 1; + } + if( M<=2 ){ + Y--; + M += 12; + } + A = Y/100; + B = 2 - A + (A/4); + X1 = 365.25*(Y+4716); + X2 = 30.6001*(M+1); + p->rJD = X1 + X2 + D + B - 1524.5; + p->validJD = 1; + p->validYMD = 0; + if( p->validHMS ){ + p->rJD += (p->h*3600.0 + p->m*60.0 + p->s)/86400.0; + if( p->validTZ ){ + p->rJD += p->tz*60/86400.0; + p->validHMS = 0; + p->validTZ = 0; + } + } +} + +/* +** Parse dates of the form +** +** YYYY-MM-DD HH:MM:SS.FFF +** YYYY-MM-DD HH:MM:SS +** YYYY-MM-DD HH:MM +** YYYY-MM-DD +** +** Write the result into the DateTime structure and return 0 +** on success and 1 if the input string is not a well-formed +** date. +*/ +static int parseYyyyMmDd(const char *zDate, DateTime *p){ + int Y, M, D, neg; + + if( zDate[0]=='-' ){ + zDate++; + neg = 1; + }else{ + neg = 0; + } + if( getDigits(zDate,4,0,9999,'-',&Y,2,1,12,'-',&M,2,1,31,0,&D)!=3 ){ + return 1; + } + zDate += 10; + while( isspace(*(u8*)zDate) ){ zDate++; } + if( parseHhMmSs(zDate, p)==0 ){ + /* We got the time */ + }else if( *zDate==0 ){ + p->validHMS = 0; + }else{ + return 1; + } + p->validJD = 0; + p->validYMD = 1; + p->Y = neg ? -Y : Y; + p->M = M; + p->D = D; + if( p->validTZ ){ + computeJD(p); + } + return 0; +} + +/* +** Attempt to parse the given string into a Julian Day Number. Return +** the number of errors. +** +** The following are acceptable forms for the input string: +** +** YYYY-MM-DD HH:MM:SS.FFF +/-HH:MM +** DDDD.DD +** now +** +** In the first form, the +/-HH:MM is always optional. The fractional +** seconds extension (the ".FFF") is optional. The seconds portion +** (":SS.FFF") is option. The year and date can be omitted as long +** as there is a time string. The time string can be omitted as long +** as there is a year and date. +*/ +static int parseDateOrTime(const char *zDate, DateTime *p){ + memset(p, 0, sizeof(*p)); + if( parseYyyyMmDd(zDate,p)==0 ){ + return 0; + }else if( parseHhMmSs(zDate, p)==0 ){ + return 0; + }else if( sqlite3StrICmp(zDate,"now")==0){ + double r; + if( sqlite3OsCurrentTime(&r)==0 ){ + p->rJD = r; + p->validJD = 1; + return 0; + } + return 1; + }else if( sqlite3IsNumber(zDate, 0, SQLITE_UTF8) ){ + p->rJD = sqlite3AtoF(zDate, 0); + p->validJD = 1; + return 0; + } + return 1; +} + +/* +** Compute the Year, Month, and Day from the julian day number. +*/ +static void computeYMD(DateTime *p){ + int Z, A, B, C, D, E, X1; + if( p->validYMD ) return; + if( !p->validJD ){ + p->Y = 2000; + p->M = 1; + p->D = 1; + }else{ + Z = p->rJD + 0.5; + A = (Z - 1867216.25)/36524.25; + A = Z + 1 + A - (A/4); + B = A + 1524; + C = (B - 122.1)/365.25; + D = 365.25*C; + E = (B-D)/30.6001; + X1 = 30.6001*E; + p->D = B - D - X1; + p->M = E<14 ? E-1 : E-13; + p->Y = p->M>2 ? C - 4716 : C - 4715; + } + p->validYMD = 1; +} + +/* +** Compute the Hour, Minute, and Seconds from the julian day number. +*/ +static void computeHMS(DateTime *p){ + int Z, s; + if( p->validHMS ) return; + Z = p->rJD + 0.5; + s = (p->rJD + 0.5 - Z)*86400000.0 + 0.5; + p->s = 0.001*s; + s = p->s; + p->s -= s; + p->h = s/3600; + s -= p->h*3600; + p->m = s/60; + p->s += s - p->m*60; + p->validHMS = 1; +} + +/* +** Compute both YMD and HMS +*/ +static void computeYMD_HMS(DateTime *p){ + computeYMD(p); + computeHMS(p); +} + +/* +** Clear the YMD and HMS and the TZ +*/ +static void clearYMD_HMS_TZ(DateTime *p){ + p->validYMD = 0; + p->validHMS = 0; + p->validTZ = 0; +} + +/* +** Compute the difference (in days) between localtime and UTC (a.k.a. GMT) +** for the time value p where p is in UTC. +*/ +static double localtimeOffset(DateTime *p){ + DateTime x, y; + time_t t; + struct tm *pTm; + x = *p; + computeYMD_HMS(&x); + if( x.Y<1971 || x.Y>=2038 ){ + x.Y = 2000; + x.M = 1; + x.D = 1; + x.h = 0; + x.m = 0; + x.s = 0.0; + } else { + int s = x.s + 0.5; + x.s = s; + } + x.tz = 0; + x.validJD = 0; + computeJD(&x); + t = (x.rJD-2440587.5)*86400.0 + 0.5; + sqlite3OsEnterMutex(); + pTm = localtime(&t); + y.Y = pTm->tm_year + 1900; + y.M = pTm->tm_mon + 1; + y.D = pTm->tm_mday; + y.h = pTm->tm_hour; + y.m = pTm->tm_min; + y.s = pTm->tm_sec; + sqlite3OsLeaveMutex(); + y.validYMD = 1; + y.validHMS = 1; + y.validJD = 0; + y.validTZ = 0; + computeJD(&y); + return y.rJD - x.rJD; +} + +/* +** Process a modifier to a date-time stamp. The modifiers are +** as follows: +** +** NNN days +** NNN hours +** NNN minutes +** NNN.NNNN seconds +** NNN months +** NNN years +** start of month +** start of year +** start of week +** start of day +** weekday N +** unixepoch +** localtime +** utc +** +** Return 0 on success and 1 if there is any kind of error. +*/ +static int parseModifier(const char *zMod, DateTime *p){ + int rc = 1; + int n; + double r; + char *z, zBuf[30]; + z = zBuf; + for(n=0; nrJD += localtimeOffset(p); + clearYMD_HMS_TZ(p); + rc = 0; + } + break; + } + case 'u': { + /* + ** unixepoch + ** + ** Treat the current value of p->rJD as the number of + ** seconds since 1970. Convert to a real julian day number. + */ + if( strcmp(z, "unixepoch")==0 && p->validJD ){ + p->rJD = p->rJD/86400.0 + 2440587.5; + clearYMD_HMS_TZ(p); + rc = 0; + }else if( strcmp(z, "utc")==0 ){ + double c1; + computeJD(p); + c1 = localtimeOffset(p); + p->rJD -= c1; + clearYMD_HMS_TZ(p); + p->rJD += c1 - localtimeOffset(p); + rc = 0; + } + break; + } + case 'w': { + /* + ** weekday N + ** + ** Move the date to the same time on the next occurrence of + ** weekday N where 0==Sunday, 1==Monday, and so forth. If the + ** date is already on the appropriate weekday, this is a no-op. + */ + if( strncmp(z, "weekday ", 8)==0 && getValue(&z[8],&r)>0 + && (n=r)==r && n>=0 && r<7 ){ + int Z; + computeYMD_HMS(p); + p->validTZ = 0; + p->validJD = 0; + computeJD(p); + Z = p->rJD + 1.5; + Z %= 7; + if( Z>n ) Z -= 7; + p->rJD += n - Z; + clearYMD_HMS_TZ(p); + rc = 0; + } + break; + } + case 's': { + /* + ** start of TTTTT + ** + ** Move the date backwards to the beginning of the current day, + ** or month or year. + */ + if( strncmp(z, "start of ", 9)!=0 ) break; + z += 9; + computeYMD(p); + p->validHMS = 1; + p->h = p->m = 0; + p->s = 0.0; + p->validTZ = 0; + p->validJD = 0; + if( strcmp(z,"month")==0 ){ + p->D = 1; + rc = 0; + }else if( strcmp(z,"year")==0 ){ + computeYMD(p); + p->M = 1; + p->D = 1; + rc = 0; + }else if( strcmp(z,"day")==0 ){ + rc = 0; + } + break; + } + case '+': + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + n = getValue(z, &r); + if( n<=0 ) break; + if( z[n]==':' ){ + /* A modifier of the form (+|-)HH:MM:SS.FFF adds (or subtracts) the + ** specified number of hours, minutes, seconds, and fractional seconds + ** to the time. The ".FFF" may be omitted. The ":SS.FFF" may be + ** omitted. + */ + const char *z2 = z; + DateTime tx; + int day; + if( !isdigit(*(u8*)z2) ) z2++; + memset(&tx, 0, sizeof(tx)); + if( parseHhMmSs(z2, &tx) ) break; + computeJD(&tx); + tx.rJD -= 0.5; + day = (int)tx.rJD; + tx.rJD -= day; + if( z[0]=='-' ) tx.rJD = -tx.rJD; + computeJD(p); + clearYMD_HMS_TZ(p); + p->rJD += tx.rJD; + rc = 0; + break; + } + z += n; + while( isspace(*(u8*)z) ) z++; + n = strlen(z); + if( n>10 || n<3 ) break; + if( z[n-1]=='s' ){ z[n-1] = 0; n--; } + computeJD(p); + rc = 0; + if( n==3 && strcmp(z,"day")==0 ){ + p->rJD += r; + }else if( n==4 && strcmp(z,"hour")==0 ){ + p->rJD += r/24.0; + }else if( n==6 && strcmp(z,"minute")==0 ){ + p->rJD += r/(24.0*60.0); + }else if( n==6 && strcmp(z,"second")==0 ){ + p->rJD += r/(24.0*60.0*60.0); + }else if( n==5 && strcmp(z,"month")==0 ){ + int x, y; + computeYMD_HMS(p); + p->M += r; + x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; + p->Y += x; + p->M -= x*12; + p->validJD = 0; + computeJD(p); + y = r; + if( y!=r ){ + p->rJD += (r - y)*30.0; + } + }else if( n==4 && strcmp(z,"year")==0 ){ + computeYMD_HMS(p); + p->Y += r; + p->validJD = 0; + computeJD(p); + }else{ + rc = 1; + } + clearYMD_HMS_TZ(p); + break; + } + default: { + break; + } + } + return rc; +} + +/* +** Process time function arguments. argv[0] is a date-time stamp. +** argv[1] and following are modifiers. Parse them all and write +** the resulting time into the DateTime structure p. Return 0 +** on success and 1 if there are any errors. +*/ +static int isDate(int argc, sqlite3_value **argv, DateTime *p){ + int i; + if( argc==0 ) return 1; + if( SQLITE_NULL==sqlite3_value_type(argv[0]) || + parseDateOrTime(sqlite3_value_text(argv[0]), p) ) return 1; + for(i=1; izErrMsg and return NULL. If all tables +** are found, return a pointer to the last table. +*/ +Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){ + Table *pTab = 0; + int i; + struct SrcList_item *pItem; + for(i=0, pItem=pSrc->a; inSrc; i++, pItem++){ + pTab = sqlite3LocateTable(pParse, pItem->zName, pItem->zDatabase); + pItem->pTab = pTab; + } + return pTab; +} + +/* +** Check to make sure the given table is writable. If it is not +** writable, generate an error message and return 1. If it is +** writable return 0; +*/ +int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){ + if( pTab->readOnly ){ + sqlite3ErrorMsg(pParse, "table %s may not be modified", pTab->zName); + return 1; + } + if( !viewOk && pTab->pSelect ){ + sqlite3ErrorMsg(pParse,"cannot modify %s because it is a view",pTab->zName); + return 1; + } + return 0; +} + +/* +** Generate code that will open a table for reading. +*/ +void sqlite3OpenTableForReading( + Vdbe *v, /* Generate code into this VDBE */ + int iCur, /* The cursor number of the table */ + Table *pTab /* The table to be opened */ +){ + sqlite3VdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqlite3VdbeAddOp(v, OP_OpenRead, iCur, pTab->tnum); + VdbeComment((v, "# %s", pTab->zName)); + sqlite3VdbeAddOp(v, OP_SetNumColumns, iCur, pTab->nCol); +} + + +/* +** Process a DELETE FROM statement. +*/ +void sqlite3DeleteFrom( + Parse *pParse, /* The parser context */ + SrcList *pTabList, /* The table from which we should delete things */ + Expr *pWhere /* The WHERE clause. May be null */ +){ + Vdbe *v; /* The virtual database engine */ + Table *pTab; /* The table from which records will be deleted */ + const char *zDb; /* Name of database holding pTab */ + int end, addr = 0; /* A couple addresses of generated code */ + int i; /* Loop counter */ + WhereInfo *pWInfo; /* Information about the WHERE clause */ + Index *pIdx; /* For looping over indices of the table */ + int iCur; /* VDBE Cursor number for pTab */ + sqlite3 *db; /* Main database structure */ + int isView; /* True if attempting to delete from a view */ + AuthContext sContext; /* Authorization context */ + + int row_triggers_exist = 0; /* True if any triggers exist */ + int before_triggers; /* True if there are BEFORE triggers */ + int after_triggers; /* True if there are AFTER triggers */ + int oldIdx = -1; /* Cursor for the OLD table of AFTER triggers */ + + sContext.pParse = 0; + if( pParse->nErr || sqlite3_malloc_failed ){ + pTabList = 0; + goto delete_from_cleanup; + } + db = pParse->db; + assert( pTabList->nSrc==1 ); + + /* Locate the table which we want to delete. This table has to be + ** put in an SrcList structure because some of the subroutines we + ** will be calling are designed to work with multiple tables and expect + ** an SrcList* parameter instead of just a Table* parameter. + */ + pTab = sqlite3SrcListLookup(pParse, pTabList); + if( pTab==0 ) goto delete_from_cleanup; + before_triggers = sqlite3TriggersExist(pParse, pTab->pTrigger, + TK_DELETE, TK_BEFORE, TK_ROW, 0); + after_triggers = sqlite3TriggersExist(pParse, pTab->pTrigger, + TK_DELETE, TK_AFTER, TK_ROW, 0); + row_triggers_exist = before_triggers || after_triggers; + isView = pTab->pSelect!=0; + if( sqlite3IsReadOnly(pParse, pTab, before_triggers) ){ + goto delete_from_cleanup; + } + assert( pTab->iDbnDb ); + zDb = db->aDb[pTab->iDb].zName; + if( sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb) ){ + goto delete_from_cleanup; + } + + /* If pTab is really a view, make sure it has been initialized. + */ + if( isView && sqlite3ViewGetColumnNames(pParse, pTab) ){ + goto delete_from_cleanup; + } + + /* Allocate a cursor used to store the old.* data for a trigger. + */ + if( row_triggers_exist ){ + oldIdx = pParse->nTab++; + } + + /* Resolve the column names in all the expressions. + */ + assert( pTabList->nSrc==1 ); + iCur = pTabList->a[0].iCursor = pParse->nTab++; + if( sqlite3ExprResolveAndCheck(pParse, pTabList, 0, pWhere, 0, 0) ){ + goto delete_from_cleanup; + } + + /* Start the view context + */ + if( isView ){ + sqlite3AuthContextPush(pParse, &sContext, pTab->zName); + } + + /* Begin generating code. + */ + v = sqlite3GetVdbe(pParse); + if( v==0 ){ + goto delete_from_cleanup; + } + sqlite3VdbeCountChanges(v); + sqlite3BeginWriteOperation(pParse, row_triggers_exist, pTab->iDb); + + /* If we are trying to delete from a view, construct that view into + ** a temporary table. + */ + if( isView ){ + Select *pView = sqlite3SelectDup(pTab->pSelect); + sqlite3Select(pParse, pView, SRT_TempTable, iCur, 0, 0, 0, 0); + sqlite3SelectDelete(pView); + } + + /* Initialize the counter of the number of rows deleted, if + ** we are counting rows. + */ + if( db->flags & SQLITE_CountRows ){ + sqlite3VdbeAddOp(v, OP_Integer, 0, 0); + } + + /* Special case: A DELETE without a WHERE clause deletes everything. + ** It is easier just to erase the whole table. Note, however, that + ** this means that the row change count will be incorrect. + */ + if( pWhere==0 && !row_triggers_exist ){ + if( db->flags & SQLITE_CountRows ){ + /* If counting rows deleted, just count the total number of + ** entries in the table. */ + int endOfLoop = sqlite3VdbeMakeLabel(v); + int addr; + if( !isView ){ + sqlite3OpenTableForReading(v, iCur, pTab); + } + sqlite3VdbeAddOp(v, OP_Rewind, iCur, sqlite3VdbeCurrentAddr(v)+2); + addr = sqlite3VdbeAddOp(v, OP_AddImm, 1, 0); + sqlite3VdbeAddOp(v, OP_Next, iCur, addr); + sqlite3VdbeResolveLabel(v, endOfLoop); + sqlite3VdbeAddOp(v, OP_Close, iCur, 0); + } + if( !isView ){ + sqlite3VdbeAddOp(v, OP_Clear, pTab->tnum, pTab->iDb); + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + sqlite3VdbeAddOp(v, OP_Clear, pIdx->tnum, pIdx->iDb); + } + } + } + + /* The usual case: There is a WHERE clause so we have to scan through + ** the table and pick which records to delete. + */ + else{ + /* Ensure all required collation sequences are available. */ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( sqlite3CheckIndexCollSeq(pParse, pIdx) ){ + goto delete_from_cleanup; + } + } + + /* Begin the database scan + */ + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 1, 0); + if( pWInfo==0 ) goto delete_from_cleanup; + + /* Remember the key of every item to be deleted. + */ + sqlite3VdbeAddOp(v, OP_ListWrite, 0, 0); + if( db->flags & SQLITE_CountRows ){ + sqlite3VdbeAddOp(v, OP_AddImm, 1, 0); + } + + /* End the database scan loop. + */ + sqlite3WhereEnd(pWInfo); + + /* Open the pseudo-table used to store OLD if there are triggers. + */ + if( row_triggers_exist ){ + sqlite3VdbeAddOp(v, OP_OpenPseudo, oldIdx, 0); + sqlite3VdbeAddOp(v, OP_SetNumColumns, oldIdx, pTab->nCol); + } + + /* Delete every item whose key was written to the list during the + ** database scan. We have to delete items after the scan is complete + ** because deleting an item can change the scan order. + */ + sqlite3VdbeAddOp(v, OP_ListRewind, 0, 0); + end = sqlite3VdbeMakeLabel(v); + + /* This is the beginning of the delete loop when there are + ** row triggers. + */ + if( row_triggers_exist ){ + addr = sqlite3VdbeAddOp(v, OP_ListRead, 0, end); + sqlite3VdbeAddOp(v, OP_Dup, 0, 0); + if( !isView ){ + sqlite3OpenTableForReading(v, iCur, pTab); + } + sqlite3VdbeAddOp(v, OP_MoveGe, iCur, 0); + sqlite3VdbeAddOp(v, OP_Recno, iCur, 0); + sqlite3VdbeAddOp(v, OP_RowData, iCur, 0); + sqlite3VdbeAddOp(v, OP_PutIntKey, oldIdx, 0); + if( !isView ){ + sqlite3VdbeAddOp(v, OP_Close, iCur, 0); + } + + sqlite3CodeRowTrigger(pParse, TK_DELETE, 0, TK_BEFORE, pTab, -1, + oldIdx, (pParse->trigStack)?pParse->trigStack->orconf:OE_Default, + addr); + } + + if( !isView ){ + /* Open cursors for the table we are deleting from and all its + ** indices. If there are row triggers, this happens inside the + ** OP_ListRead loop because the cursor have to all be closed + ** before the trigger fires. If there are no row triggers, the + ** cursors are opened only once on the outside the loop. + */ + sqlite3OpenTableAndIndices(pParse, pTab, iCur, OP_OpenWrite); + + /* This is the beginning of the delete loop when there are no + ** row triggers */ + if( !row_triggers_exist ){ + addr = sqlite3VdbeAddOp(v, OP_ListRead, 0, end); + } + + /* Delete the row */ + sqlite3GenerateRowDelete(db, v, pTab, iCur, 1); + } + + /* If there are row triggers, close all cursors then invoke + ** the AFTER triggers + */ + if( row_triggers_exist ){ + if( !isView ){ + for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + sqlite3VdbeAddOp(v, OP_Close, iCur + i, pIdx->tnum); + } + sqlite3VdbeAddOp(v, OP_Close, iCur, 0); + } + sqlite3CodeRowTrigger(pParse, TK_DELETE, 0, TK_AFTER, pTab, -1, + oldIdx, (pParse->trigStack)?pParse->trigStack->orconf:OE_Default, + addr); + } + + /* End of the delete loop */ + sqlite3VdbeAddOp(v, OP_Goto, 0, addr); + sqlite3VdbeResolveLabel(v, end); + sqlite3VdbeAddOp(v, OP_ListReset, 0, 0); + + /* Close the cursors after the loop if there are no row triggers */ + if( !row_triggers_exist ){ + for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + sqlite3VdbeAddOp(v, OP_Close, iCur + i, pIdx->tnum); + } + sqlite3VdbeAddOp(v, OP_Close, iCur, 0); + } + } + + /* + ** Return the number of rows that were deleted. + */ + if( db->flags & SQLITE_CountRows ){ + sqlite3VdbeAddOp(v, OP_Callback, 1, 0); + sqlite3VdbeSetNumCols(v, 1); + sqlite3VdbeSetColName(v, 0, "rows deleted", P3_STATIC); + } + +delete_from_cleanup: + sqlite3AuthContextPop(&sContext); + sqlite3SrcListDelete(pTabList); + sqlite3ExprDelete(pWhere); + return; +} + +/* +** This routine generates VDBE code that causes a single row of a +** single table to be deleted. +** +** The VDBE must be in a particular state when this routine is called. +** These are the requirements: +** +** 1. A read/write cursor pointing to pTab, the table containing the row +** to be deleted, must be opened as cursor number "base". +** +** 2. Read/write cursors for all indices of pTab must be open as +** cursor number base+i for the i-th index. +** +** 3. The record number of the row to be deleted must be on the top +** of the stack. +** +** This routine pops the top of the stack to remove the record number +** and then generates code to remove both the table record and all index +** entries that point to that record. +*/ +void sqlite3GenerateRowDelete( + sqlite3 *db, /* The database containing the index */ + Vdbe *v, /* Generate code into this VDBE */ + Table *pTab, /* Table containing the row to be deleted */ + int iCur, /* Cursor number for the table */ + int count /* Increment the row change counter */ +){ + int addr; + addr = sqlite3VdbeAddOp(v, OP_NotExists, iCur, 0); + sqlite3GenerateRowIndexDelete(db, v, pTab, iCur, 0); + sqlite3VdbeAddOp(v, OP_Delete, iCur, (count?OPFLAG_NCHANGE:0)); + sqlite3VdbeChangeP2(v, addr, sqlite3VdbeCurrentAddr(v)); +} + +/* +** This routine generates VDBE code that causes the deletion of all +** index entries associated with a single row of a single table. +** +** The VDBE must be in a particular state when this routine is called. +** These are the requirements: +** +** 1. A read/write cursor pointing to pTab, the table containing the row +** to be deleted, must be opened as cursor number "iCur". +** +** 2. Read/write cursors for all indices of pTab must be open as +** cursor number iCur+i for the i-th index. +** +** 3. The "iCur" cursor must be pointing to the row that is to be +** deleted. +*/ +void sqlite3GenerateRowIndexDelete( + sqlite3 *db, /* The database containing the index */ + Vdbe *v, /* Generate code into this VDBE */ + Table *pTab, /* Table containing the row to be deleted */ + int iCur, /* Cursor number for the table */ + char *aIdxUsed /* Only delete if aIdxUsed!=0 && aIdxUsed[i]!=0 */ +){ + int i; + Index *pIdx; + + for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + if( aIdxUsed!=0 && aIdxUsed[i-1]==0 ) continue; + sqlite3GenerateIndexKey(v, pIdx, iCur); + sqlite3VdbeAddOp(v, OP_IdxDelete, iCur+i, 0); + } +} + +/* +** Generate code that will assemble an index key and put it on the top +** of the tack. The key with be for index pIdx which is an index on pTab. +** iCur is the index of a cursor open on the pTab table and pointing to +** the entry that needs indexing. +*/ +void sqlite3GenerateIndexKey( + Vdbe *v, /* Generate code into this VDBE */ + Index *pIdx, /* The index for which to generate a key */ + int iCur /* Cursor number for the pIdx->pTable table */ +){ + int j; + Table *pTab = pIdx->pTable; + + sqlite3VdbeAddOp(v, OP_Recno, iCur, 0); + for(j=0; jnColumn; j++){ + int idx = pIdx->aiColumn[j]; + if( idx==pTab->iPKey ){ + sqlite3VdbeAddOp(v, OP_Dup, j, 0); + }else{ + sqlite3VdbeAddOp(v, OP_Column, iCur, idx); + } + } + sqlite3VdbeAddOp(v, OP_MakeRecord, pIdx->nColumn, (1<<24)); + sqlite3IndexAffinityStr(v, pIdx); +} diff --git a/kopete/plugins/statistics/sqlite/encode.c b/kopete/plugins/statistics/sqlite/encode.c new file mode 100644 index 00000000..b10c96b3 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/encode.c @@ -0,0 +1,257 @@ +/* +** 2002 April 25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains helper routines used to translate binary data into +** a null-terminated string (suitable for use in SQLite) and back again. +** These are convenience routines for use by people who want to store binary +** data in an SQLite database. The code in this file is not used by any other +** part of the SQLite library. +** +** $Id$ +*/ +#include +#include + +/* +** How This Encoder Works +** +** The output is allowed to contain any character except 0x27 (') and +** 0x00. This is accomplished by using an escape character to encode +** 0x27 and 0x00 as a two-byte sequence. The escape character is always +** 0x01. An 0x00 is encoded as the two byte sequence 0x01 0x01. The +** 0x27 character is encoded as the two byte sequence 0x01 0x28. Finally, +** the escape character itself is encoded as the two-character sequence +** 0x01 0x02. +** +** To summarize, the encoder works by using an escape sequences as follows: +** +** 0x00 -> 0x01 0x01 +** 0x01 -> 0x01 0x02 +** 0x27 -> 0x01 0x28 +** +** If that were all the encoder did, it would work, but in certain cases +** it could double the size of the encoded string. For example, to +** encode a string of 100 0x27 characters would require 100 instances of +** the 0x01 0x03 escape sequence resulting in a 200-character output. +** We would prefer to keep the size of the encoded string smaller than +** this. +** +** To minimize the encoding size, we first add a fixed offset value to each +** byte in the sequence. The addition is modulo 256. (That is to say, if +** the sum of the original character value and the offset exceeds 256, then +** the higher order bits are truncated.) The offset is chosen to minimize +** the number of characters in the string that need to be escaped. For +** example, in the case above where the string was composed of 100 0x27 +** characters, the offset might be 0x01. Each of the 0x27 characters would +** then be converted into an 0x28 character which would not need to be +** escaped at all and so the 100 character input string would be converted +** into just 100 characters of output. Actually 101 characters of output - +** we have to record the offset used as the first byte in the sequence so +** that the string can be decoded. Since the offset value is stored as +** part of the output string and the output string is not allowed to contain +** characters 0x00 or 0x27, the offset cannot be 0x00 or 0x27. +** +** Here, then, are the encoding steps: +** +** (1) Choose an offset value and make it the first character of +** output. +** +** (2) Copy each input character into the output buffer, one by +** one, adding the offset value as you copy. +** +** (3) If the value of an input character plus offset is 0x00, replace +** that one character by the two-character sequence 0x01 0x01. +** If the sum is 0x01, replace it with 0x01 0x02. If the sum +** is 0x27, replace it with 0x01 0x03. +** +** (4) Put a 0x00 terminator at the end of the output. +** +** Decoding is obvious: +** +** (5) Copy encoded characters except the first into the decode +** buffer. Set the first encoded character aside for use as +** the offset in step 7 below. +** +** (6) Convert each 0x01 0x01 sequence into a single character 0x00. +** Convert 0x01 0x02 into 0x01. Convert 0x01 0x28 into 0x27. +** +** (7) Subtract the offset value that was the first character of +** the encoded buffer from all characters in the output buffer. +** +** The only tricky part is step (1) - how to compute an offset value to +** minimize the size of the output buffer. This is accomplished by testing +** all offset values and picking the one that results in the fewest number +** of escapes. To do that, we first scan the entire input and count the +** number of occurances of each character value in the input. Suppose +** the number of 0x00 characters is N(0), the number of occurances of 0x01 +** is N(1), and so forth up to the number of occurances of 0xff is N(255). +** An offset of 0 is not allowed so we don't have to test it. The number +** of escapes required for an offset of 1 is N(1)+N(2)+N(40). The number +** of escapes required for an offset of 2 is N(2)+N(3)+N(41). And so forth. +** In this way we find the offset that gives the minimum number of escapes, +** and thus minimizes the length of the output string. +*/ + +/* +** Encode a binary buffer "in" of size n bytes so that it contains +** no instances of characters '\'' or '\000'. The output is +** null-terminated and can be used as a string value in an INSERT +** or UPDATE statement. Use sqlite_decode_binary() to convert the +** string back into its original binary. +** +** The result is written into a preallocated output buffer "out". +** "out" must be able to hold at least 2 +(257*n)/254 bytes. +** In other words, the output will be expanded by as much as 3 +** bytes for every 254 bytes of input plus 2 bytes of fixed overhead. +** (This is approximately 2 + 1.0118*n or about a 1.2% size increase.) +** +** The return value is the number of characters in the encoded +** string, excluding the "\000" terminator. +** +** If out==NULL then no output is generated but the routine still returns +** the number of characters that would have been generated if out had +** not been NULL. +*/ +int sqlite_encode_binary(const unsigned char *in, int n, unsigned char *out){ + int i, j, e, m; + unsigned char x; + int cnt[256]; + if( n<=0 ){ + if( out ){ + out[0] = 'x'; + out[1] = 0; + } + return 1; + } + memset(cnt, 0, sizeof(cnt)); + for(i=n-1; i>=0; i--){ cnt[in[i]]++; } + m = n; + for(i=1; i<256; i++){ + int sum; + if( i=='\'' ) continue; + sum = cnt[i] + cnt[(i+1)&0xff] + cnt[(i+'\'')&0xff]; + if( sum +/* +** The subroutines above are not tested by the usual test suite. To test +** these routines, compile just this one file with a -DENCODER_TEST=1 option +** and run the result. +*/ +int main(int argc, char **argv){ + int i, j, n, m, nOut, nByteIn, nByteOut; + unsigned char in[30000]; + unsigned char out[33000]; + + nByteIn = nByteOut = 0; + for(i=0; i%d (max %d)", n, strlen(out)+1, m); + if( strlen(out)+1>m ){ + printf(" ERROR output too big\n"); + exit(1); + } + for(j=0; out[j]; j++){ + if( out[j]=='\'' ){ + printf(" ERROR contains (')\n"); + exit(1); + } + } + j = sqlite_decode_binary(out, out); + if( j!=n ){ + printf(" ERROR decode size %d\n", j); + exit(1); + } + if( memcmp(in, out, n)!=0 ){ + printf(" ERROR decode mismatch\n"); + exit(1); + } + printf(" OK\n"); + } + fprintf(stderr,"Finished. Total encoding: %d->%d bytes\n", + nByteIn, nByteOut); + fprintf(stderr,"Avg size increase: %.3f%%\n", + (nByteOut-nByteIn)*100.0/(double)nByteIn); +} +#endif /* ENCODER_TEST */ + + + diff --git a/kopete/plugins/statistics/sqlite/expr.c b/kopete/plugins/statistics/sqlite/expr.c new file mode 100644 index 00000000..2da3645b --- /dev/null +++ b/kopete/plugins/statistics/sqlite/expr.c @@ -0,0 +1,1927 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains routines used for analyzing expressions and +** for generating VDBE code that evaluates expressions in SQLite. +** +** $Id$ +*/ +#include "sqliteInt.h" +#include + +/* +** Return the 'affinity' of the expression pExpr if any. +** +** If pExpr is a column, a reference to a column via an 'AS' alias, +** or a sub-select with a column as the return value, then the +** affinity of that column is returned. Otherwise, 0x00 is returned, +** indicating no affinity for the expression. +** +** i.e. the WHERE clause expresssions in the following statements all +** have an affinity: +** +** CREATE TABLE t1(a); +** SELECT * FROM t1 WHERE a; +** SELECT a AS b FROM t1 WHERE b; +** SELECT * FROM t1 WHERE (select a from t1); +*/ +char sqlite3ExprAffinity(Expr *pExpr){ + if( pExpr->op==TK_AS ){ + return sqlite3ExprAffinity(pExpr->pLeft); + } + if( pExpr->op==TK_SELECT ){ + return sqlite3ExprAffinity(pExpr->pSelect->pEList->a[0].pExpr); + } + return pExpr->affinity; +} + +/* +** Return the default collation sequence for the expression pExpr. If +** there is no default collation type, return 0. +*/ +CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){ + CollSeq *pColl = 0; + if( pExpr ){ + pColl = pExpr->pColl; + if( pExpr->op==TK_AS && !pColl ){ + return sqlite3ExprCollSeq(pParse, pExpr->pLeft); + } + } + if( sqlite3CheckCollSeq(pParse, pColl) ){ + pColl = 0; + } + return pColl; +} + +/* +** pExpr is the left operand of a comparison operator. aff2 is the +** type affinity of the right operand. This routine returns the +** type affinity that should be used for the comparison operator. +*/ +char sqlite3CompareAffinity(Expr *pExpr, char aff2){ + char aff1 = sqlite3ExprAffinity(pExpr); + if( aff1 && aff2 ){ + /* Both sides of the comparison are columns. If one has numeric or + ** integer affinity, use that. Otherwise use no affinity. + */ + if( aff1==SQLITE_AFF_INTEGER || aff2==SQLITE_AFF_INTEGER ){ + return SQLITE_AFF_INTEGER; + }else if( aff1==SQLITE_AFF_NUMERIC || aff2==SQLITE_AFF_NUMERIC ){ + return SQLITE_AFF_NUMERIC; + }else{ + return SQLITE_AFF_NONE; + } + }else if( !aff1 && !aff2 ){ + /* Neither side of the comparison is a column. Compare the + ** results directly. + */ + /* return SQLITE_AFF_NUMERIC; // Ticket #805 */ + return SQLITE_AFF_NONE; + }else{ + /* One side is a column, the other is not. Use the columns affinity. */ + return (aff1 + aff2); + } +} + +/* +** pExpr is a comparison operator. Return the type affinity that should +** be applied to both operands prior to doing the comparison. +*/ +static char comparisonAffinity(Expr *pExpr){ + char aff; + assert( pExpr->op==TK_EQ || pExpr->op==TK_IN || pExpr->op==TK_LT || + pExpr->op==TK_GT || pExpr->op==TK_GE || pExpr->op==TK_LE || + pExpr->op==TK_NE ); + assert( pExpr->pLeft ); + aff = sqlite3ExprAffinity(pExpr->pLeft); + if( pExpr->pRight ){ + aff = sqlite3CompareAffinity(pExpr->pRight, aff); + } + else if( pExpr->pSelect ){ + aff = sqlite3CompareAffinity(pExpr->pSelect->pEList->a[0].pExpr, aff); + } + else if( !aff ){ + aff = SQLITE_AFF_NUMERIC; + } + return aff; +} + +/* +** pExpr is a comparison expression, eg. '=', '<', IN(...) etc. +** idx_affinity is the affinity of an indexed column. Return true +** if the index with affinity idx_affinity may be used to implement +** the comparison in pExpr. +*/ +int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity){ + char aff = comparisonAffinity(pExpr); + return + (aff==SQLITE_AFF_NONE) || + (aff==SQLITE_AFF_NUMERIC && idx_affinity==SQLITE_AFF_INTEGER) || + (aff==SQLITE_AFF_INTEGER && idx_affinity==SQLITE_AFF_NUMERIC) || + (aff==idx_affinity); +} + +/* +** Return the P1 value that should be used for a binary comparison +** opcode (OP_Eq, OP_Ge etc.) used to compare pExpr1 and pExpr2. +** If jumpIfNull is true, then set the low byte of the returned +** P1 value to tell the opcode to jump if either expression +** evaluates to NULL. +*/ +static int binaryCompareP1(Expr *pExpr1, Expr *pExpr2, int jumpIfNull){ + char aff = sqlite3ExprAffinity(pExpr2); + return (((int)sqlite3CompareAffinity(pExpr1, aff))<<8)+(jumpIfNull?1:0); +} + +/* +** Return a pointer to the collation sequence that should be used by +** a binary comparison operator comparing pLeft and pRight. +** +** If the left hand expression has a collating sequence type, then it is +** used. Otherwise the collation sequence for the right hand expression +** is used, or the default (BINARY) if neither expression has a collating +** type. +*/ +static CollSeq* binaryCompareCollSeq(Parse *pParse, Expr *pLeft, Expr *pRight){ + CollSeq *pColl = sqlite3ExprCollSeq(pParse, pLeft); + if( !pColl ){ + pColl = sqlite3ExprCollSeq(pParse, pRight); + } + return pColl; +} + +/* +** Generate code for a comparison operator. +*/ +static int codeCompare( + Parse *pParse, /* The parsing (and code generating) context */ + Expr *pLeft, /* The left operand */ + Expr *pRight, /* The right operand */ + int opcode, /* The comparison opcode */ + int dest, /* Jump here if true. */ + int jumpIfNull /* If true, jump if either operand is NULL */ +){ + int p1 = binaryCompareP1(pLeft, pRight, jumpIfNull); + CollSeq *p3 = binaryCompareCollSeq(pParse, pLeft, pRight); + return sqlite3VdbeOp3(pParse->pVdbe, opcode, p1, dest, (void*)p3, P3_COLLSEQ); +} + +/* +** Construct a new expression node and return a pointer to it. Memory +** for this node is obtained from sqliteMalloc(). The calling function +** is responsible for making sure the node eventually gets freed. +*/ +Expr *sqlite3Expr(int op, Expr *pLeft, Expr *pRight, Token *pToken){ + Expr *pNew; + pNew = sqliteMalloc( sizeof(Expr) ); + if( pNew==0 ){ + /* When malloc fails, we leak memory from pLeft and pRight */ + return 0; + } + pNew->op = op; + pNew->pLeft = pLeft; + pNew->pRight = pRight; + if( pToken ){ + assert( pToken->dyn==0 ); + pNew->span = pNew->token = *pToken; + }else if( pLeft && pRight ){ + sqlite3ExprSpan(pNew, &pLeft->span, &pRight->span); + } + return pNew; +} + +/* +** Join two expressions using an AND operator. If either expression is +** NULL, then just return the other expression. +*/ +Expr *sqlite3ExprAnd(Expr *pLeft, Expr *pRight){ + if( pLeft==0 ){ + return pRight; + }else if( pRight==0 ){ + return pLeft; + }else{ + return sqlite3Expr(TK_AND, pLeft, pRight, 0); + } +} + +/* +** Set the Expr.span field of the given expression to span all +** text between the two given tokens. +*/ +void sqlite3ExprSpan(Expr *pExpr, Token *pLeft, Token *pRight){ + assert( pRight!=0 ); + assert( pLeft!=0 ); + if( !sqlite3_malloc_failed && pRight->z && pLeft->z ){ + assert( pLeft->dyn==0 || pLeft->z[pLeft->n]==0 ); + if( pLeft->dyn==0 && pRight->dyn==0 ){ + pExpr->span.z = pLeft->z; + pExpr->span.n = pRight->n + Addr(pRight->z) - Addr(pLeft->z); + }else{ + pExpr->span.z = 0; + } + } +} + +/* +** Construct a new expression node for a function with multiple +** arguments. +*/ +Expr *sqlite3ExprFunction(ExprList *pList, Token *pToken){ + Expr *pNew; + pNew = sqliteMalloc( sizeof(Expr) ); + if( pNew==0 ){ + /* sqlite3ExprListDelete(pList); // Leak pList when malloc fails */ + return 0; + } + pNew->op = TK_FUNCTION; + pNew->pList = pList; + if( pToken ){ + assert( pToken->dyn==0 ); + pNew->token = *pToken; + }else{ + pNew->token.z = 0; + } + pNew->span = pNew->token; + return pNew; +} + +/* +** Assign a variable number to an expression that encodes a wildcard +** in the original SQL statement. +** +** Wildcards consisting of a single "?" are assigned the next sequential +** variable number. +** +** Wildcards of the form "?nnn" are assigned the number "nnn". We make +** sure "nnn" is not too be to avoid a denial of service attack when +** the SQL statement comes from an external source. +** +** Wildcards of the form ":aaa" or "$aaa" are assigned the same number +** as the previous instance of the same wildcard. Or if this is the first +** instance of the wildcard, the next sequenial variable number is +** assigned. +*/ +void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){ + Token *pToken; + if( pExpr==0 ) return; + pToken = &pExpr->token; + assert( pToken->n>=1 ); + assert( pToken->z!=0 ); + assert( pToken->z[0]!=0 ); + if( pToken->n==1 ){ + /* Wildcard of the form "?". Assign the next variable number */ + pExpr->iTable = ++pParse->nVar; + }else if( pToken->z[0]=='?' ){ + /* Wildcard of the form "?nnn". Convert "nnn" to an integer and + ** use it as the variable number */ + int i; + pExpr->iTable = i = atoi(&pToken->z[1]); + if( i<1 || i>SQLITE_MAX_VARIABLE_NUMBER ){ + sqlite3ErrorMsg(pParse, "variable number must be between ?1 and ?%d", + SQLITE_MAX_VARIABLE_NUMBER); + } + if( i>pParse->nVar ){ + pParse->nVar = i; + } + }else{ + /* Wildcards of the form ":aaa" or "$aaa". Reuse the same variable + ** number as the prior appearance of the same name, or if the name + ** has never appeared before, reuse the same variable number + */ + int i, n; + n = pToken->n; + for(i=0; inVarExpr; i++){ + Expr *pE; + if( (pE = pParse->apVarExpr[i])!=0 + && pE->token.n==n + && memcmp(pE->token.z, pToken->z, n)==0 ){ + pExpr->iTable = pE->iTable; + break; + } + } + if( i>=pParse->nVarExpr ){ + pExpr->iTable = ++pParse->nVar; + if( pParse->nVarExpr>=pParse->nVarExprAlloc-1 ){ + pParse->nVarExprAlloc += pParse->nVarExprAlloc + 10; + pParse->apVarExpr = sqliteRealloc(pParse->apVarExpr, + pParse->nVarExprAlloc*sizeof(pParse->apVarExpr[0]) ); + } + if( !sqlite3_malloc_failed ){ + assert( pParse->apVarExpr!=0 ); + pParse->apVarExpr[pParse->nVarExpr++] = pExpr; + } + } + } +} + +/* +** Recursively delete an expression tree. +*/ +void sqlite3ExprDelete(Expr *p){ + if( p==0 ) return; + if( p->span.dyn ) sqliteFree((char*)p->span.z); + if( p->token.dyn ) sqliteFree((char*)p->token.z); + sqlite3ExprDelete(p->pLeft); + sqlite3ExprDelete(p->pRight); + sqlite3ExprListDelete(p->pList); + sqlite3SelectDelete(p->pSelect); + sqliteFree(p); +} + + +/* +** The following group of routines make deep copies of expressions, +** expression lists, ID lists, and select statements. The copies can +** be deleted (by being passed to their respective ...Delete() routines) +** without effecting the originals. +** +** The expression list, ID, and source lists return by sqlite3ExprListDup(), +** sqlite3IdListDup(), and sqlite3SrcListDup() can not be further expanded +** by subsequent calls to sqlite*ListAppend() routines. +** +** Any tables that the SrcList might point to are not duplicated. +*/ +Expr *sqlite3ExprDup(Expr *p){ + Expr *pNew; + if( p==0 ) return 0; + pNew = sqliteMallocRaw( sizeof(*p) ); + if( pNew==0 ) return 0; + memcpy(pNew, p, sizeof(*pNew)); + if( p->token.z!=0 ){ + pNew->token.z = sqliteStrDup(p->token.z); + pNew->token.dyn = 1; + }else{ + assert( pNew->token.z==0 ); + } + pNew->span.z = 0; + pNew->pLeft = sqlite3ExprDup(p->pLeft); + pNew->pRight = sqlite3ExprDup(p->pRight); + pNew->pList = sqlite3ExprListDup(p->pList); + pNew->pSelect = sqlite3SelectDup(p->pSelect); + return pNew; +} +void sqlite3TokenCopy(Token *pTo, Token *pFrom){ + if( pTo->dyn ) sqliteFree((char*)pTo->z); + if( pFrom->z ){ + pTo->n = pFrom->n; + pTo->z = sqliteStrNDup(pFrom->z, pFrom->n); + pTo->dyn = 1; + }else{ + pTo->z = 0; + } +} +ExprList *sqlite3ExprListDup(ExprList *p){ + ExprList *pNew; + struct ExprList_item *pItem, *pOldItem; + int i; + if( p==0 ) return 0; + pNew = sqliteMalloc( sizeof(*pNew) ); + if( pNew==0 ) return 0; + pNew->nExpr = pNew->nAlloc = p->nExpr; + pNew->a = pItem = sqliteMalloc( p->nExpr*sizeof(p->a[0]) ); + if( pItem==0 ){ + sqliteFree(pNew); + return 0; + } + pOldItem = p->a; + for(i=0; inExpr; i++, pItem++, pOldItem++){ + Expr *pNewExpr, *pOldExpr; + pItem->pExpr = pNewExpr = sqlite3ExprDup(pOldExpr = pOldItem->pExpr); + if( pOldExpr->span.z!=0 && pNewExpr ){ + /* Always make a copy of the span for top-level expressions in the + ** expression list. The logic in SELECT processing that determines + ** the names of columns in the result set needs this information */ + sqlite3TokenCopy(&pNewExpr->span, &pOldExpr->span); + } + assert( pNewExpr==0 || pNewExpr->span.z!=0 + || pOldExpr->span.z==0 || sqlite3_malloc_failed ); + pItem->zName = sqliteStrDup(pOldItem->zName); + pItem->sortOrder = pOldItem->sortOrder; + pItem->isAgg = pOldItem->isAgg; + pItem->done = 0; + } + return pNew; +} +SrcList *sqlite3SrcListDup(SrcList *p){ + SrcList *pNew; + int i; + int nByte; + if( p==0 ) return 0; + nByte = sizeof(*p) + (p->nSrc>0 ? sizeof(p->a[0]) * (p->nSrc-1) : 0); + pNew = sqliteMallocRaw( nByte ); + if( pNew==0 ) return 0; + pNew->nSrc = pNew->nAlloc = p->nSrc; + for(i=0; inSrc; i++){ + struct SrcList_item *pNewItem = &pNew->a[i]; + struct SrcList_item *pOldItem = &p->a[i]; + pNewItem->zDatabase = sqliteStrDup(pOldItem->zDatabase); + pNewItem->zName = sqliteStrDup(pOldItem->zName); + pNewItem->zAlias = sqliteStrDup(pOldItem->zAlias); + pNewItem->jointype = pOldItem->jointype; + pNewItem->iCursor = pOldItem->iCursor; + pNewItem->pTab = 0; + pNewItem->pSelect = sqlite3SelectDup(pOldItem->pSelect); + pNewItem->pOn = sqlite3ExprDup(pOldItem->pOn); + pNewItem->pUsing = sqlite3IdListDup(pOldItem->pUsing); + } + return pNew; +} +IdList *sqlite3IdListDup(IdList *p){ + IdList *pNew; + int i; + if( p==0 ) return 0; + pNew = sqliteMallocRaw( sizeof(*pNew) ); + if( pNew==0 ) return 0; + pNew->nId = pNew->nAlloc = p->nId; + pNew->a = sqliteMallocRaw( p->nId*sizeof(p->a[0]) ); + if( pNew->a==0 ) return 0; + for(i=0; inId; i++){ + struct IdList_item *pNewItem = &pNew->a[i]; + struct IdList_item *pOldItem = &p->a[i]; + pNewItem->zName = sqliteStrDup(pOldItem->zName); + pNewItem->idx = pOldItem->idx; + } + return pNew; +} +Select *sqlite3SelectDup(Select *p){ + Select *pNew; + if( p==0 ) return 0; + pNew = sqliteMallocRaw( sizeof(*p) ); + if( pNew==0 ) return 0; + pNew->isDistinct = p->isDistinct; + pNew->pEList = sqlite3ExprListDup(p->pEList); + pNew->pSrc = sqlite3SrcListDup(p->pSrc); + pNew->pWhere = sqlite3ExprDup(p->pWhere); + pNew->pGroupBy = sqlite3ExprListDup(p->pGroupBy); + pNew->pHaving = sqlite3ExprDup(p->pHaving); + pNew->pOrderBy = sqlite3ExprListDup(p->pOrderBy); + pNew->op = p->op; + pNew->pPrior = sqlite3SelectDup(p->pPrior); + pNew->nLimit = p->nLimit; + pNew->nOffset = p->nOffset; + pNew->zSelect = 0; + pNew->iLimit = -1; + pNew->iOffset = -1; + pNew->ppOpenTemp = 0; + return pNew; +} + + +/* +** Add a new element to the end of an expression list. If pList is +** initially NULL, then create a new expression list. +*/ +ExprList *sqlite3ExprListAppend(ExprList *pList, Expr *pExpr, Token *pName){ + if( pList==0 ){ + pList = sqliteMalloc( sizeof(ExprList) ); + if( pList==0 ){ + /* sqlite3ExprDelete(pExpr); // Leak memory if malloc fails */ + return 0; + } + assert( pList->nAlloc==0 ); + } + if( pList->nAlloc<=pList->nExpr ){ + pList->nAlloc = pList->nAlloc*2 + 4; + pList->a = sqliteRealloc(pList->a, pList->nAlloc*sizeof(pList->a[0])); + if( pList->a==0 ){ + /* sqlite3ExprDelete(pExpr); // Leak memory if malloc fails */ + pList->nExpr = pList->nAlloc = 0; + return pList; + } + } + assert( pList->a!=0 ); + if( pExpr || pName ){ + struct ExprList_item *pItem = &pList->a[pList->nExpr++]; + memset(pItem, 0, sizeof(*pItem)); + pItem->pExpr = pExpr; + pItem->zName = sqlite3NameFromToken(pName); + } + return pList; +} + +/* +** Delete an entire expression list. +*/ +void sqlite3ExprListDelete(ExprList *pList){ + int i; + struct ExprList_item *pItem; + if( pList==0 ) return; + assert( pList->a!=0 || (pList->nExpr==0 && pList->nAlloc==0) ); + assert( pList->nExpr<=pList->nAlloc ); + for(pItem=pList->a, i=0; inExpr; i++, pItem++){ + sqlite3ExprDelete(pItem->pExpr); + sqliteFree(pItem->zName); + } + sqliteFree(pList->a); + sqliteFree(pList); +} + +/* +** Walk an expression tree. Return 1 if the expression is constant +** and 0 if it involves variables. +** +** For the purposes of this function, a double-quoted string (ex: "abc") +** is considered a variable but a single-quoted string (ex: 'abc') is +** a constant. +*/ +int sqlite3ExprIsConstant(Expr *p){ + switch( p->op ){ + case TK_ID: + case TK_COLUMN: + case TK_DOT: + case TK_FUNCTION: + return 0; + case TK_NULL: + case TK_STRING: + case TK_BLOB: + case TK_INTEGER: + case TK_FLOAT: + case TK_VARIABLE: + return 1; + default: { + if( p->pLeft && !sqlite3ExprIsConstant(p->pLeft) ) return 0; + if( p->pRight && !sqlite3ExprIsConstant(p->pRight) ) return 0; + if( p->pList ){ + int i; + for(i=0; ipList->nExpr; i++){ + if( !sqlite3ExprIsConstant(p->pList->a[i].pExpr) ) return 0; + } + } + return p->pLeft!=0 || p->pRight!=0 || (p->pList && p->pList->nExpr>0); + } + } + return 0; +} + +/* +** If the given expression codes a constant integer that is small enough +** to fit in a 32-bit integer, return 1 and put the value of the integer +** in *pValue. If the expression is not an integer or if it is too big +** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged. +*/ +int sqlite3ExprIsInteger(Expr *p, int *pValue){ + switch( p->op ){ + case TK_INTEGER: { + if( sqlite3GetInt32(p->token.z, pValue) ){ + return 1; + } + break; + } + case TK_STRING: { + const u8 *z = (u8*)p->token.z; + int n = p->token.n; + if( n>0 && z[0]=='-' ){ z++; n--; } + while( n>0 && *z && isdigit(*z) ){ z++; n--; } + if( n==0 && sqlite3GetInt32(p->token.z, pValue) ){ + return 1; + } + break; + } + case TK_UPLUS: { + return sqlite3ExprIsInteger(p->pLeft, pValue); + } + case TK_UMINUS: { + int v; + if( sqlite3ExprIsInteger(p->pLeft, &v) ){ + *pValue = -v; + return 1; + } + break; + } + default: break; + } + return 0; +} + +/* +** Return TRUE if the given string is a row-id column name. +*/ +int sqlite3IsRowid(const char *z){ + if( sqlite3StrICmp(z, "_ROWID_")==0 ) return 1; + if( sqlite3StrICmp(z, "ROWID")==0 ) return 1; + if( sqlite3StrICmp(z, "OID")==0 ) return 1; + return 0; +} + +/* +** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up +** that name in the set of source tables in pSrcList and make the pExpr +** expression node refer back to that source column. The following changes +** are made to pExpr: +** +** pExpr->iDb Set the index in db->aDb[] of the database holding +** the table. +** pExpr->iTable Set to the cursor number for the table obtained +** from pSrcList. +** pExpr->iColumn Set to the column number within the table. +** pExpr->op Set to TK_COLUMN. +** pExpr->pLeft Any expression this points to is deleted +** pExpr->pRight Any expression this points to is deleted. +** +** The pDbToken is the name of the database (the "X"). This value may be +** NULL meaning that name is of the form Y.Z or Z. Any available database +** can be used. The pTableToken is the name of the table (the "Y"). This +** value can be NULL if pDbToken is also NULL. If pTableToken is NULL it +** means that the form of the name is Z and that columns from any table +** can be used. +** +** If the name cannot be resolved unambiguously, leave an error message +** in pParse and return non-zero. Return zero on success. +*/ +static int lookupName( + Parse *pParse, /* The parsing context */ + Token *pDbToken, /* Name of the database containing table, or NULL */ + Token *pTableToken, /* Name of table containing column, or NULL */ + Token *pColumnToken, /* Name of the column. */ + SrcList *pSrcList, /* List of tables used to resolve column names */ + ExprList *pEList, /* List of expressions used to resolve "AS" */ + Expr *pExpr /* Make this EXPR node point to the selected column */ +){ + char *zDb = 0; /* Name of the database. The "X" in X.Y.Z */ + char *zTab = 0; /* Name of the table. The "Y" in X.Y.Z or Y.Z */ + char *zCol = 0; /* Name of the column. The "Z" */ + int i, j; /* Loop counters */ + int cnt = 0; /* Number of matching column names */ + int cntTab = 0; /* Number of matching table names */ + sqlite3 *db = pParse->db; /* The database */ + + assert( pColumnToken && pColumnToken->z ); /* The Z in X.Y.Z cannot be NULL */ + zDb = sqlite3NameFromToken(pDbToken); + zTab = sqlite3NameFromToken(pTableToken); + zCol = sqlite3NameFromToken(pColumnToken); + if( sqlite3_malloc_failed ){ + return 1; /* Leak memory (zDb and zTab) if malloc fails */ + } + assert( zTab==0 || pEList==0 ); + + pExpr->iTable = -1; + for(i=0; inSrc; i++){ + struct SrcList_item *pItem = &pSrcList->a[i]; + Table *pTab = pItem->pTab; + Column *pCol; + + if( pTab==0 ) continue; + assert( pTab->nCol>0 ); + if( zTab ){ + if( pItem->zAlias ){ + char *zTabName = pItem->zAlias; + if( sqlite3StrICmp(zTabName, zTab)!=0 ) continue; + }else{ + char *zTabName = pTab->zName; + if( zTabName==0 || sqlite3StrICmp(zTabName, zTab)!=0 ) continue; + if( zDb!=0 && sqlite3StrICmp(db->aDb[pTab->iDb].zName, zDb)!=0 ){ + continue; + } + } + } + if( 0==(cntTab++) ){ + pExpr->iTable = pItem->iCursor; + pExpr->iDb = pTab->iDb; + } + for(j=0, pCol=pTab->aCol; jnCol; j++, pCol++){ + if( sqlite3StrICmp(pCol->zName, zCol)==0 ){ + cnt++; + pExpr->iTable = pItem->iCursor; + pExpr->iDb = pTab->iDb; + /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */ + pExpr->iColumn = j==pTab->iPKey ? -1 : j; + pExpr->affinity = pTab->aCol[j].affinity; + pExpr->pColl = pTab->aCol[j].pColl; + break; + } + } + } + + /* If we have not already resolved the name, then maybe + ** it is a new.* or old.* trigger argument reference + */ + if( zDb==0 && zTab!=0 && cnt==0 && pParse->trigStack!=0 ){ + TriggerStack *pTriggerStack = pParse->trigStack; + Table *pTab = 0; + if( pTriggerStack->newIdx != -1 && sqlite3StrICmp("new", zTab) == 0 ){ + pExpr->iTable = pTriggerStack->newIdx; + assert( pTriggerStack->pTab ); + pTab = pTriggerStack->pTab; + }else if( pTriggerStack->oldIdx != -1 && sqlite3StrICmp("old", zTab) == 0 ){ + pExpr->iTable = pTriggerStack->oldIdx; + assert( pTriggerStack->pTab ); + pTab = pTriggerStack->pTab; + } + + if( pTab ){ + int j; + Column *pCol = pTab->aCol; + + pExpr->iDb = pTab->iDb; + cntTab++; + for(j=0; j < pTab->nCol; j++, pCol++) { + if( sqlite3StrICmp(pCol->zName, zCol)==0 ){ + cnt++; + pExpr->iColumn = j==pTab->iPKey ? -1 : j; + pExpr->affinity = pTab->aCol[j].affinity; + pExpr->pColl = pTab->aCol[j].pColl; + break; + } + } + } + } + + /* + ** Perhaps the name is a reference to the ROWID + */ + if( cnt==0 && cntTab==1 && sqlite3IsRowid(zCol) ){ + cnt = 1; + pExpr->iColumn = -1; + pExpr->affinity = SQLITE_AFF_INTEGER; + } + + /* + ** If the input is of the form Z (not Y.Z or X.Y.Z) then the name Z + ** might refer to an result-set alias. This happens, for example, when + ** we are resolving names in the WHERE clause of the following command: + ** + ** SELECT a+b AS x FROM table WHERE x<10; + ** + ** In cases like this, replace pExpr with a copy of the expression that + ** forms the result set entry ("a+b" in the example) and return immediately. + ** Note that the expression in the result set should have already been + ** resolved by the time the WHERE clause is resolved. + */ + if( cnt==0 && pEList!=0 ){ + for(j=0; jnExpr; j++){ + char *zAs = pEList->a[j].zName; + if( zAs!=0 && sqlite3StrICmp(zAs, zCol)==0 ){ + assert( pExpr->pLeft==0 && pExpr->pRight==0 ); + pExpr->op = TK_AS; + pExpr->iColumn = j; + pExpr->pLeft = sqlite3ExprDup(pEList->a[j].pExpr); + sqliteFree(zCol); + assert( zTab==0 && zDb==0 ); + return 0; + } + } + } + + /* + ** If X and Y are NULL (in other words if only the column name Z is + ** supplied) and the value of Z is enclosed in double-quotes, then + ** Z is a string literal if it doesn't match any column names. In that + ** case, we need to return right away and not make any changes to + ** pExpr. + */ + if( cnt==0 && zTab==0 && pColumnToken->z[0]=='"' ){ + sqliteFree(zCol); + return 0; + } + + /* + ** cnt==0 means there was not match. cnt>1 means there were two or + ** more matches. Either way, we have an error. + */ + if( cnt!=1 ){ + char *z = 0; + char *zErr; + zErr = cnt==0 ? "no such column: %s" : "ambiguous column name: %s"; + if( zDb ){ + sqlite3SetString(&z, zDb, ".", zTab, ".", zCol, 0); + }else if( zTab ){ + sqlite3SetString(&z, zTab, ".", zCol, 0); + }else{ + z = sqliteStrDup(zCol); + } + sqlite3ErrorMsg(pParse, zErr, z); + sqliteFree(z); + } + + /* Clean up and return + */ + sqliteFree(zDb); + sqliteFree(zTab); + sqliteFree(zCol); + sqlite3ExprDelete(pExpr->pLeft); + pExpr->pLeft = 0; + sqlite3ExprDelete(pExpr->pRight); + pExpr->pRight = 0; + pExpr->op = TK_COLUMN; + sqlite3AuthRead(pParse, pExpr, pSrcList); + return cnt!=1; +} + +/* +** This routine walks an expression tree and resolves references to +** table columns. Nodes of the form ID.ID or ID resolve into an +** index to the table in the table list and a column offset. The +** Expr.opcode for such nodes is changed to TK_COLUMN. The Expr.iTable +** value is changed to the index of the referenced table in pTabList +** plus the "base" value. The base value will ultimately become the +** VDBE cursor number for a cursor that is pointing into the referenced +** table. The Expr.iColumn value is changed to the index of the column +** of the referenced table. The Expr.iColumn value for the special +** ROWID column is -1. Any INTEGER PRIMARY KEY column is tried as an +** alias for ROWID. +** +** We also check for instances of the IN operator. IN comes in two +** forms: +** +** expr IN (exprlist) +** and +** expr IN (SELECT ...) +** +** The first form is handled by creating a set holding the list +** of allowed values. The second form causes the SELECT to generate +** a temporary table. +** +** This routine also looks for scalar SELECTs that are part of an expression. +** If it finds any, it generates code to write the value of that select +** into a memory cell. +** +** Unknown columns or tables provoke an error. The function returns +** the number of errors seen and leaves an error message on pParse->zErrMsg. +*/ +int sqlite3ExprResolveIds( + Parse *pParse, /* The parser context */ + SrcList *pSrcList, /* List of tables used to resolve column names */ + ExprList *pEList, /* List of expressions used to resolve "AS" */ + Expr *pExpr /* The expression to be analyzed. */ +){ + int i; + + if( pExpr==0 || pSrcList==0 ) return 0; + for(i=0; inSrc; i++){ + assert( pSrcList->a[i].iCursor>=0 && pSrcList->a[i].iCursornTab ); + } + switch( pExpr->op ){ + /* Double-quoted strings (ex: "abc") are used as identifiers if + ** possible. Otherwise they remain as strings. Single-quoted + ** strings (ex: 'abc') are always string literals. + */ + case TK_STRING: { + if( pExpr->token.z[0]=='\'' ) break; + /* Fall thru into the TK_ID case if this is a double-quoted string */ + } + /* A lone identifier is the name of a columnd. + */ + case TK_ID: { + if( lookupName(pParse, 0, 0, &pExpr->token, pSrcList, pEList, pExpr) ){ + return 1; + } + break; + } + + /* A table name and column name: ID.ID + ** Or a database, table and column: ID.ID.ID + */ + case TK_DOT: { + Token *pColumn; + Token *pTable; + Token *pDb; + Expr *pRight; + + pRight = pExpr->pRight; + if( pRight->op==TK_ID ){ + pDb = 0; + pTable = &pExpr->pLeft->token; + pColumn = &pRight->token; + }else{ + assert( pRight->op==TK_DOT ); + pDb = &pExpr->pLeft->token; + pTable = &pRight->pLeft->token; + pColumn = &pRight->pRight->token; + } + if( lookupName(pParse, pDb, pTable, pColumn, pSrcList, 0, pExpr) ){ + return 1; + } + break; + } + + case TK_IN: { + char affinity; + Vdbe *v = sqlite3GetVdbe(pParse); + KeyInfo keyInfo; + int addr; /* Address of OP_OpenTemp instruction */ + + if( v==0 ) return 1; + if( sqlite3ExprResolveIds(pParse, pSrcList, pEList, pExpr->pLeft) ){ + return 1; + } + affinity = sqlite3ExprAffinity(pExpr->pLeft); + + /* Whether this is an 'x IN(SELECT...)' or an 'x IN()' + ** expression it is handled the same way. A temporary table is + ** filled with single-field index keys representing the results + ** from the SELECT or the . + ** + ** If the 'x' expression is a column value, or the SELECT... + ** statement returns a column value, then the affinity of that + ** column is used to build the index keys. If both 'x' and the + ** SELECT... statement are columns, then numeric affinity is used + ** if either column has NUMERIC or INTEGER affinity. If neither + ** 'x' nor the SELECT... statement are columns, then numeric affinity + ** is used. + */ + pExpr->iTable = pParse->nTab++; + addr = sqlite3VdbeAddOp(v, OP_OpenTemp, pExpr->iTable, 0); + memset(&keyInfo, 0, sizeof(keyInfo)); + keyInfo.nField = 1; + sqlite3VdbeAddOp(v, OP_SetNumColumns, pExpr->iTable, 1); + + if( pExpr->pSelect ){ + /* Case 1: expr IN (SELECT ...) + ** + ** Generate code to write the results of the select into the temporary + ** table allocated and opened above. + */ + int iParm = pExpr->iTable + (((int)affinity)<<16); + ExprList *pEList; + assert( (pExpr->iTable&0x0000FFFF)==pExpr->iTable ); + sqlite3Select(pParse, pExpr->pSelect, SRT_Set, iParm, 0, 0, 0, 0); + pEList = pExpr->pSelect->pEList; + if( pEList && pEList->nExpr>0 ){ + keyInfo.aColl[0] = binaryCompareCollSeq(pParse, pExpr->pLeft, + pEList->a[0].pExpr); + } + }else if( pExpr->pList ){ + /* Case 2: expr IN (exprlist) + ** + ** For each expression, build an index key from the evaluation and + ** store it in the temporary table. If is a column, then use + ** that columns affinity when building index keys. If is not + ** a column, use numeric affinity. + */ + int i; + if( !affinity ){ + affinity = SQLITE_AFF_NUMERIC; + } + keyInfo.aColl[0] = pExpr->pLeft->pColl; + + /* Loop through each expression in . */ + for(i=0; ipList->nExpr; i++){ + Expr *pE2 = pExpr->pList->a[i].pExpr; + + /* Check that the expression is constant and valid. */ + if( !sqlite3ExprIsConstant(pE2) ){ + sqlite3ErrorMsg(pParse, + "right-hand side of IN operator must be constant"); + return 1; + } + if( sqlite3ExprCheck(pParse, pE2, 0, 0) ){ + return 1; + } + + /* Evaluate the expression and insert it into the temp table */ + sqlite3ExprCode(pParse, pE2); + sqlite3VdbeOp3(v, OP_MakeRecord, 1, 0, &affinity, 1); + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + sqlite3VdbeAddOp(v, OP_PutStrKey, pExpr->iTable, 0); + } + } + sqlite3VdbeChangeP3(v, addr, (void *)&keyInfo, P3_KEYINFO); + + break; + } + + case TK_SELECT: { + /* This has to be a scalar SELECT. Generate code to put the + ** value of this select in a memory cell and record the number + ** of the memory cell in iColumn. + */ + pExpr->iColumn = pParse->nMem++; + if(sqlite3Select(pParse, pExpr->pSelect, SRT_Mem,pExpr->iColumn,0,0,0,0)){ + return 1; + } + break; + } + + /* For all else, just recursively walk the tree */ + default: { + if( pExpr->pLeft + && sqlite3ExprResolveIds(pParse, pSrcList, pEList, pExpr->pLeft) ){ + return 1; + } + if( pExpr->pRight + && sqlite3ExprResolveIds(pParse, pSrcList, pEList, pExpr->pRight) ){ + return 1; + } + if( pExpr->pList ){ + int i; + ExprList *pList = pExpr->pList; + for(i=0; inExpr; i++){ + Expr *pArg = pList->a[i].pExpr; + if( sqlite3ExprResolveIds(pParse, pSrcList, pEList, pArg) ){ + return 1; + } + } + } + } + } + return 0; +} + +/* +** pExpr is a node that defines a function of some kind. It might +** be a syntactic function like "count(x)" or it might be a function +** that implements an operator, like "a LIKE b". +** +** This routine makes *pzName point to the name of the function and +** *pnName hold the number of characters in the function name. +*/ +static void getFunctionName(Expr *pExpr, const char **pzName, int *pnName){ + switch( pExpr->op ){ + case TK_FUNCTION: { + *pzName = pExpr->token.z; + *pnName = pExpr->token.n; + break; + } + case TK_LIKE: { + *pzName = "like"; + *pnName = 4; + break; + } + case TK_GLOB: { + *pzName = "glob"; + *pnName = 4; + break; + } + default: { + *pzName = "can't happen"; + *pnName = 12; + break; + } + } +} + +/* +** Error check the functions in an expression. Make sure all +** function names are recognized and all functions have the correct +** number of arguments. Leave an error message in pParse->zErrMsg +** if anything is amiss. Return the number of errors. +** +** if pIsAgg is not null and this expression is an aggregate function +** (like count(*) or max(value)) then write a 1 into *pIsAgg. +*/ +int sqlite3ExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){ + int nErr = 0; + if( pExpr==0 ) return 0; + switch( pExpr->op ){ + case TK_GLOB: + case TK_LIKE: + case TK_FUNCTION: { + int n = pExpr->pList ? pExpr->pList->nExpr : 0; /* Number of arguments */ + int no_such_func = 0; /* True if no such function exists */ + int wrong_num_args = 0; /* True if wrong number of arguments */ + int is_agg = 0; /* True if is an aggregate function */ + int i; + int nId; /* Number of characters in function name */ + const char *zId; /* The function name. */ + FuncDef *pDef; + int enc = pParse->db->enc; + + getFunctionName(pExpr, &zId, &nId); + pDef = sqlite3FindFunction(pParse->db, zId, nId, n, enc, 0); + if( pDef==0 ){ + pDef = sqlite3FindFunction(pParse->db, zId, nId, -1, enc, 0); + if( pDef==0 ){ + no_such_func = 1; + }else{ + wrong_num_args = 1; + } + }else{ + is_agg = pDef->xFunc==0; + } + if( is_agg && !allowAgg ){ + sqlite3ErrorMsg(pParse, "misuse of aggregate function %.*s()", nId, zId); + nErr++; + is_agg = 0; + }else if( no_such_func ){ + sqlite3ErrorMsg(pParse, "no such function: %.*s", nId, zId); + nErr++; + }else if( wrong_num_args ){ + sqlite3ErrorMsg(pParse,"wrong number of arguments to function %.*s()", + nId, zId); + nErr++; + } + if( is_agg ){ + pExpr->op = TK_AGG_FUNCTION; + if( pIsAgg ) *pIsAgg = 1; + } + for(i=0; nErr==0 && ipList->a[i].pExpr, + allowAgg && !is_agg, pIsAgg); + } + /* FIX ME: Compute pExpr->affinity based on the expected return + ** type of the function + */ + } + default: { + if( pExpr->pLeft ){ + nErr = sqlite3ExprCheck(pParse, pExpr->pLeft, allowAgg, pIsAgg); + } + if( nErr==0 && pExpr->pRight ){ + nErr = sqlite3ExprCheck(pParse, pExpr->pRight, allowAgg, pIsAgg); + } + if( nErr==0 && pExpr->pList ){ + int n = pExpr->pList->nExpr; + int i; + for(i=0; nErr==0 && ipList->a[i].pExpr; + nErr = sqlite3ExprCheck(pParse, pE2, allowAgg, pIsAgg); + } + } + break; + } + } + return nErr; +} + +/* +** Call sqlite3ExprResolveIds() followed by sqlite3ExprCheck(). +** +** This routine is provided as a convenience since it is very common +** to call ResolveIds() and Check() back to back. +*/ +int sqlite3ExprResolveAndCheck( + Parse *pParse, /* The parser context */ + SrcList *pSrcList, /* List of tables used to resolve column names */ + ExprList *pEList, /* List of expressions used to resolve "AS" */ + Expr *pExpr, /* The expression to be analyzed. */ + int allowAgg, /* True to allow aggregate expressions */ + int *pIsAgg /* Set to TRUE if aggregates are found */ +){ + if( pExpr==0 ) return 0; + if( sqlite3ExprResolveIds(pParse,pSrcList,pEList,pExpr) ){ + return 1; + } + return sqlite3ExprCheck(pParse, pExpr, allowAgg, pIsAgg); +} + +/* +** Generate an instruction that will put the integer describe by +** text z[0..n-1] on the stack. +*/ +static void codeInteger(Vdbe *v, const char *z, int n){ + int i; + if( sqlite3GetInt32(z, &i) ){ + sqlite3VdbeAddOp(v, OP_Integer, i, 0); + }else if( sqlite3FitsIn64Bits(z) ){ + sqlite3VdbeOp3(v, OP_Integer, 0, 0, z, n); + }else{ + sqlite3VdbeOp3(v, OP_Real, 0, 0, z, n); + } +} + +/* +** Generate code into the current Vdbe to evaluate the given +** expression and leave the result on the top of stack. +** +** This code depends on the fact that certain token values (ex: TK_EQ) +** are the same as opcode values (ex: OP_Eq) that implement the corresponding +** operation. Special comments in vdbe.c and the mkopcodeh.awk script in +** the make process cause these values to align. Assert()s in the code +** below verify that the numbers are aligned correctly. +*/ +void sqlite3ExprCode(Parse *pParse, Expr *pExpr){ + Vdbe *v = pParse->pVdbe; + int op; + if( v==0 || pExpr==0 ) return; + op = pExpr->op; + switch( op ){ + case TK_COLUMN: { + if( pParse->useAgg ){ + sqlite3VdbeAddOp(v, OP_AggGet, 0, pExpr->iAgg); + }else if( pExpr->iColumn>=0 ){ + sqlite3VdbeAddOp(v, OP_Column, pExpr->iTable, pExpr->iColumn); +#ifndef NDEBUG + if( pExpr->span.z && pExpr->span.n>0 && pExpr->span.n<100 ){ + VdbeComment((v, "# %T", &pExpr->span)); + } +#endif + }else{ + sqlite3VdbeAddOp(v, OP_Recno, pExpr->iTable, 0); + } + break; + } + case TK_INTEGER: { + codeInteger(v, pExpr->token.z, pExpr->token.n); + break; + } + case TK_FLOAT: + case TK_STRING: { + assert( TK_FLOAT==OP_Real ); + assert( TK_STRING==OP_String8 ); + sqlite3VdbeOp3(v, op, 0, 0, pExpr->token.z, pExpr->token.n); + sqlite3VdbeDequoteP3(v, -1); + break; + } + case TK_BLOB: { + assert( TK_BLOB==OP_HexBlob ); + sqlite3VdbeOp3(v, op, 0, 0, pExpr->token.z+1, pExpr->token.n-1); + sqlite3VdbeDequoteP3(v, -1); + break; + } + case TK_NULL: { + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + break; + } + case TK_VARIABLE: { + sqlite3VdbeAddOp(v, OP_Variable, pExpr->iTable, 0); + if( pExpr->token.n>1 ){ + sqlite3VdbeChangeP3(v, -1, pExpr->token.z, pExpr->token.n); + } + break; + } + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_NE: + case TK_EQ: { + assert( TK_LT==OP_Lt ); + assert( TK_LE==OP_Le ); + assert( TK_GT==OP_Gt ); + assert( TK_GE==OP_Ge ); + assert( TK_EQ==OP_Eq ); + assert( TK_NE==OP_Ne ); + sqlite3ExprCode(pParse, pExpr->pLeft); + sqlite3ExprCode(pParse, pExpr->pRight); + codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, 0, 0); + break; + } + case TK_AND: + case TK_OR: + case TK_PLUS: + case TK_STAR: + case TK_MINUS: + case TK_REM: + case TK_BITAND: + case TK_BITOR: + case TK_SLASH: + case TK_LSHIFT: + case TK_RSHIFT: + case TK_CONCAT: { + assert( TK_AND==OP_And ); + assert( TK_OR==OP_Or ); + assert( TK_PLUS==OP_Add ); + assert( TK_MINUS==OP_Subtract ); + assert( TK_REM==OP_Remainder ); + assert( TK_BITAND==OP_BitAnd ); + assert( TK_BITOR==OP_BitOr ); + assert( TK_SLASH==OP_Divide ); + assert( TK_LSHIFT==OP_ShiftLeft ); + assert( TK_RSHIFT==OP_ShiftRight ); + assert( TK_CONCAT==OP_Concat ); + sqlite3ExprCode(pParse, pExpr->pLeft); + sqlite3ExprCode(pParse, pExpr->pRight); + sqlite3VdbeAddOp(v, op, 0, 0); + break; + } + case TK_UMINUS: { + Expr *pLeft = pExpr->pLeft; + assert( pLeft ); + if( pLeft->op==TK_FLOAT || pLeft->op==TK_INTEGER ){ + Token *p = &pLeft->token; + char *z = sqliteMalloc( p->n + 2 ); + sprintf(z, "-%.*s", p->n, p->z); + if( pLeft->op==TK_FLOAT ){ + sqlite3VdbeOp3(v, OP_Real, 0, 0, z, p->n+1); + }else{ + codeInteger(v, z, p->n+1); + } + sqliteFree(z); + break; + } + /* Fall through into TK_NOT */ + } + case TK_BITNOT: + case TK_NOT: { + assert( TK_BITNOT==OP_BitNot ); + assert( TK_NOT==OP_Not ); + sqlite3ExprCode(pParse, pExpr->pLeft); + sqlite3VdbeAddOp(v, op, 0, 0); + break; + } + case TK_ISNULL: + case TK_NOTNULL: { + int dest; + assert( TK_ISNULL==OP_IsNull ); + assert( TK_NOTNULL==OP_NotNull ); + sqlite3VdbeAddOp(v, OP_Integer, 1, 0); + sqlite3ExprCode(pParse, pExpr->pLeft); + dest = sqlite3VdbeCurrentAddr(v) + 2; + sqlite3VdbeAddOp(v, op, 1, dest); + sqlite3VdbeAddOp(v, OP_AddImm, -1, 0); + break; + } + case TK_AGG_FUNCTION: { + sqlite3VdbeAddOp(v, OP_AggGet, 0, pExpr->iAgg); + break; + } + case TK_GLOB: + case TK_LIKE: + case TK_FUNCTION: { + ExprList *pList = pExpr->pList; + int nExpr = pList ? pList->nExpr : 0; + FuncDef *pDef; + int nId; + const char *zId; + int p2 = 0; + int i; + u8 enc = pParse->db->enc; + CollSeq *pColl = 0; + getFunctionName(pExpr, &zId, &nId); + pDef = sqlite3FindFunction(pParse->db, zId, nId, nExpr, enc, 0); + assert( pDef!=0 ); + nExpr = sqlite3ExprCodeExprList(pParse, pList); + for(i=0; ia[i].pExpr) ){ + p2 |= (1<needCollSeq && !pColl ){ + pColl = sqlite3ExprCollSeq(pParse, pList->a[i].pExpr); + } + } + if( pDef->needCollSeq ){ + if( !pColl ) pColl = pParse->db->pDfltColl; + sqlite3VdbeOp3(v, OP_CollSeq, 0, 0, (char *)pColl, P3_COLLSEQ); + } + sqlite3VdbeOp3(v, OP_Function, nExpr, p2, (char*)pDef, P3_FUNCDEF); + break; + } + case TK_SELECT: { + sqlite3VdbeAddOp(v, OP_MemLoad, pExpr->iColumn, 0); + VdbeComment((v, "# load subquery result")); + break; + } + case TK_IN: { + int addr; + char affinity; + + /* Figure out the affinity to use to create a key from the results + ** of the expression. affinityStr stores a static string suitable for + ** P3 of OP_MakeRecord. + */ + affinity = comparisonAffinity(pExpr); + + sqlite3VdbeAddOp(v, OP_Integer, 1, 0); + + /* Code the from " IN (...)". The temporary table + ** pExpr->iTable contains the values that make up the (...) set. + */ + sqlite3ExprCode(pParse, pExpr->pLeft); + addr = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp(v, OP_NotNull, -1, addr+4); /* addr + 0 */ + sqlite3VdbeAddOp(v, OP_Pop, 2, 0); + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + sqlite3VdbeAddOp(v, OP_Goto, 0, addr+7); + sqlite3VdbeOp3(v, OP_MakeRecord, 1, 0, &affinity, 1); /* addr + 4 */ + sqlite3VdbeAddOp(v, OP_Found, pExpr->iTable, addr+7); + sqlite3VdbeAddOp(v, OP_AddImm, -1, 0); /* addr + 6 */ + + break; + } + case TK_BETWEEN: { + Expr *pLeft = pExpr->pLeft; + struct ExprList_item *pLItem = pExpr->pList->a; + Expr *pRight = pLItem->pExpr; + sqlite3ExprCode(pParse, pLeft); + sqlite3VdbeAddOp(v, OP_Dup, 0, 0); + sqlite3ExprCode(pParse, pRight); + codeCompare(pParse, pLeft, pRight, OP_Ge, 0, 0); + sqlite3VdbeAddOp(v, OP_Pull, 1, 0); + pLItem++; + pRight = pLItem->pExpr; + sqlite3ExprCode(pParse, pRight); + codeCompare(pParse, pLeft, pRight, OP_Le, 0, 0); + sqlite3VdbeAddOp(v, OP_And, 0, 0); + break; + } + case TK_UPLUS: + case TK_AS: { + sqlite3ExprCode(pParse, pExpr->pLeft); + break; + } + case TK_CASE: { + int expr_end_label; + int jumpInst; + int addr; + int nExpr; + int i; + ExprList *pEList; + struct ExprList_item *aListelem; + + assert(pExpr->pList); + assert((pExpr->pList->nExpr % 2) == 0); + assert(pExpr->pList->nExpr > 0); + pEList = pExpr->pList; + aListelem = pEList->a; + nExpr = pEList->nExpr; + expr_end_label = sqlite3VdbeMakeLabel(v); + if( pExpr->pLeft ){ + sqlite3ExprCode(pParse, pExpr->pLeft); + } + for(i=0; ipLeft ){ + sqlite3VdbeAddOp(v, OP_Dup, 1, 1); + jumpInst = codeCompare(pParse, pExpr->pLeft, aListelem[i].pExpr, + OP_Ne, 0, 1); + sqlite3VdbeAddOp(v, OP_Pop, 1, 0); + }else{ + jumpInst = sqlite3VdbeAddOp(v, OP_IfNot, 1, 0); + } + sqlite3ExprCode(pParse, aListelem[i+1].pExpr); + sqlite3VdbeAddOp(v, OP_Goto, 0, expr_end_label); + addr = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeChangeP2(v, jumpInst, addr); + } + if( pExpr->pLeft ){ + sqlite3VdbeAddOp(v, OP_Pop, 1, 0); + } + if( pExpr->pRight ){ + sqlite3ExprCode(pParse, pExpr->pRight); + }else{ + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + } + sqlite3VdbeResolveLabel(v, expr_end_label); + break; + } + case TK_RAISE: { + if( !pParse->trigStack ){ + sqlite3ErrorMsg(pParse, + "RAISE() may only be used within a trigger-program"); + return; + } + if( pExpr->iColumn!=OE_Ignore ){ + assert( pExpr->iColumn==OE_Rollback || + pExpr->iColumn == OE_Abort || + pExpr->iColumn == OE_Fail ); + sqlite3VdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, pExpr->iColumn, + pExpr->token.z, pExpr->token.n); + sqlite3VdbeDequoteP3(v, -1); + } else { + assert( pExpr->iColumn == OE_Ignore ); + sqlite3VdbeAddOp(v, OP_ContextPop, 0, 0); + sqlite3VdbeAddOp(v, OP_Goto, 0, pParse->trigStack->ignoreJump); + VdbeComment((v, "# raise(IGNORE)")); + } + } + break; + } +} + +/* +** Generate code that pushes the value of every element of the given +** expression list onto the stack. +** +** Return the number of elements pushed onto the stack. +*/ +int sqlite3ExprCodeExprList( + Parse *pParse, /* Parsing context */ + ExprList *pList /* The expression list to be coded */ +){ + struct ExprList_item *pItem; + int i, n; + Vdbe *v; + if( pList==0 ) return 0; + v = sqlite3GetVdbe(pParse); + n = pList->nExpr; + for(pItem=pList->a, i=0; ipExpr); + } + return n; +} + +/* +** Generate code for a boolean expression such that a jump is made +** to the label "dest" if the expression is true but execution +** continues straight thru if the expression is false. +** +** If the expression evaluates to NULL (neither true nor false), then +** take the jump if the jumpIfNull flag is true. +** +** This code depends on the fact that certain token values (ex: TK_EQ) +** are the same as opcode values (ex: OP_Eq) that implement the corresponding +** operation. Special comments in vdbe.c and the mkopcodeh.awk script in +** the make process cause these values to align. Assert()s in the code +** below verify that the numbers are aligned correctly. +*/ +void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ + Vdbe *v = pParse->pVdbe; + int op = 0; + if( v==0 || pExpr==0 ) return; + op = pExpr->op; + switch( op ){ + case TK_AND: { + int d2 = sqlite3VdbeMakeLabel(v); + sqlite3ExprIfFalse(pParse, pExpr->pLeft, d2, !jumpIfNull); + sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull); + sqlite3VdbeResolveLabel(v, d2); + break; + } + case TK_OR: { + sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull); + sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull); + break; + } + case TK_NOT: { + sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull); + break; + } + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_NE: + case TK_EQ: { + assert( TK_LT==OP_Lt ); + assert( TK_LE==OP_Le ); + assert( TK_GT==OP_Gt ); + assert( TK_GE==OP_Ge ); + assert( TK_EQ==OP_Eq ); + assert( TK_NE==OP_Ne ); + sqlite3ExprCode(pParse, pExpr->pLeft); + sqlite3ExprCode(pParse, pExpr->pRight); + codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, dest, jumpIfNull); + break; + } + case TK_ISNULL: + case TK_NOTNULL: { + assert( TK_ISNULL==OP_IsNull ); + assert( TK_NOTNULL==OP_NotNull ); + sqlite3ExprCode(pParse, pExpr->pLeft); + sqlite3VdbeAddOp(v, op, 1, dest); + break; + } + case TK_BETWEEN: { + /* The expression "x BETWEEN y AND z" is implemented as: + ** + ** 1 IF (x < y) GOTO 3 + ** 2 IF (x <= z) GOTO + ** 3 ... + */ + int addr; + Expr *pLeft = pExpr->pLeft; + Expr *pRight = pExpr->pList->a[0].pExpr; + sqlite3ExprCode(pParse, pLeft); + sqlite3VdbeAddOp(v, OP_Dup, 0, 0); + sqlite3ExprCode(pParse, pRight); + addr = codeCompare(pParse, pLeft, pRight, OP_Lt, 0, !jumpIfNull); + + pRight = pExpr->pList->a[1].pExpr; + sqlite3ExprCode(pParse, pRight); + codeCompare(pParse, pLeft, pRight, OP_Le, dest, jumpIfNull); + + sqlite3VdbeAddOp(v, OP_Integer, 0, 0); + sqlite3VdbeChangeP2(v, addr, sqlite3VdbeCurrentAddr(v)); + sqlite3VdbeAddOp(v, OP_Pop, 1, 0); + break; + } + default: { + sqlite3ExprCode(pParse, pExpr); + sqlite3VdbeAddOp(v, OP_If, jumpIfNull, dest); + break; + } + } +} + +/* +** Generate code for a boolean expression such that a jump is made +** to the label "dest" if the expression is false but execution +** continues straight thru if the expression is true. +** +** If the expression evaluates to NULL (neither true nor false) then +** jump if jumpIfNull is true or fall through if jumpIfNull is false. +*/ +void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ + Vdbe *v = pParse->pVdbe; + int op = 0; + if( v==0 || pExpr==0 ) return; + + /* The value of pExpr->op and op are related as follows: + ** + ** pExpr->op op + ** --------- ---------- + ** TK_ISNULL OP_NotNull + ** TK_NOTNULL OP_IsNull + ** TK_NE OP_Eq + ** TK_EQ OP_Ne + ** TK_GT OP_Le + ** TK_LE OP_Gt + ** TK_GE OP_Lt + ** TK_LT OP_Ge + ** + ** For other values of pExpr->op, op is undefined and unused. + ** The value of TK_ and OP_ constants are arranged such that we + ** can compute the mapping above using the following expression. + ** Assert()s verify that the computation is correct. + */ + op = ((pExpr->op+(TK_ISNULL&1))^1)-(TK_ISNULL&1); + + /* Verify correct alignment of TK_ and OP_ constants + */ + assert( pExpr->op!=TK_ISNULL || op==OP_NotNull ); + assert( pExpr->op!=TK_NOTNULL || op==OP_IsNull ); + assert( pExpr->op!=TK_NE || op==OP_Eq ); + assert( pExpr->op!=TK_EQ || op==OP_Ne ); + assert( pExpr->op!=TK_LT || op==OP_Ge ); + assert( pExpr->op!=TK_LE || op==OP_Gt ); + assert( pExpr->op!=TK_GT || op==OP_Le ); + assert( pExpr->op!=TK_GE || op==OP_Lt ); + + switch( pExpr->op ){ + case TK_AND: { + sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull); + sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull); + break; + } + case TK_OR: { + int d2 = sqlite3VdbeMakeLabel(v); + sqlite3ExprIfTrue(pParse, pExpr->pLeft, d2, !jumpIfNull); + sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull); + sqlite3VdbeResolveLabel(v, d2); + break; + } + case TK_NOT: { + sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull); + break; + } + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_NE: + case TK_EQ: { + sqlite3ExprCode(pParse, pExpr->pLeft); + sqlite3ExprCode(pParse, pExpr->pRight); + codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, dest, jumpIfNull); + break; + } + case TK_ISNULL: + case TK_NOTNULL: { + sqlite3ExprCode(pParse, pExpr->pLeft); + sqlite3VdbeAddOp(v, op, 1, dest); + break; + } + case TK_BETWEEN: { + /* The expression is "x BETWEEN y AND z". It is implemented as: + ** + ** 1 IF (x >= y) GOTO 3 + ** 2 GOTO + ** 3 IF (x > z) GOTO + */ + int addr; + Expr *pLeft = pExpr->pLeft; + Expr *pRight = pExpr->pList->a[0].pExpr; + sqlite3ExprCode(pParse, pLeft); + sqlite3VdbeAddOp(v, OP_Dup, 0, 0); + sqlite3ExprCode(pParse, pRight); + addr = sqlite3VdbeCurrentAddr(v); + codeCompare(pParse, pLeft, pRight, OP_Ge, addr+3, !jumpIfNull); + + sqlite3VdbeAddOp(v, OP_Pop, 1, 0); + sqlite3VdbeAddOp(v, OP_Goto, 0, dest); + pRight = pExpr->pList->a[1].pExpr; + sqlite3ExprCode(pParse, pRight); + codeCompare(pParse, pLeft, pRight, OP_Gt, dest, jumpIfNull); + break; + } + default: { + sqlite3ExprCode(pParse, pExpr); + sqlite3VdbeAddOp(v, OP_IfNot, jumpIfNull, dest); + break; + } + } +} + +/* +** Do a deep comparison of two expression trees. Return TRUE (non-zero) +** if they are identical and return FALSE if they differ in any way. +*/ +int sqlite3ExprCompare(Expr *pA, Expr *pB){ + int i; + if( pA==0 ){ + return pB==0; + }else if( pB==0 ){ + return 0; + } + if( pA->op!=pB->op ) return 0; + if( !sqlite3ExprCompare(pA->pLeft, pB->pLeft) ) return 0; + if( !sqlite3ExprCompare(pA->pRight, pB->pRight) ) return 0; + if( pA->pList ){ + if( pB->pList==0 ) return 0; + if( pA->pList->nExpr!=pB->pList->nExpr ) return 0; + for(i=0; ipList->nExpr; i++){ + if( !sqlite3ExprCompare(pA->pList->a[i].pExpr, pB->pList->a[i].pExpr) ){ + return 0; + } + } + }else if( pB->pList ){ + return 0; + } + if( pA->pSelect || pB->pSelect ) return 0; + if( pA->iTable!=pB->iTable || pA->iColumn!=pB->iColumn ) return 0; + if( pA->token.z ){ + if( pB->token.z==0 ) return 0; + if( pB->token.n!=pA->token.n ) return 0; + if( sqlite3StrNICmp(pA->token.z, pB->token.z, pB->token.n)!=0 ) return 0; + } + return 1; +} + +/* +** Add a new element to the pParse->aAgg[] array and return its index. +*/ +static int appendAggInfo(Parse *pParse){ + if( (pParse->nAgg & 0x7)==0 ){ + int amt = pParse->nAgg + 8; + AggExpr *aAgg = sqliteRealloc(pParse->aAgg, amt*sizeof(pParse->aAgg[0])); + if( aAgg==0 ){ + return -1; + } + pParse->aAgg = aAgg; + } + memset(&pParse->aAgg[pParse->nAgg], 0, sizeof(pParse->aAgg[0])); + return pParse->nAgg++; +} + +/* +** Analyze the given expression looking for aggregate functions and +** for variables that need to be added to the pParse->aAgg[] array. +** Make additional entries to the pParse->aAgg[] array as necessary. +** +** This routine should only be called after the expression has been +** analyzed by sqlite3ExprResolveIds() and sqlite3ExprCheck(). +** +** If errors are seen, leave an error message in zErrMsg and return +** the number of errors. +*/ +int sqlite3ExprAnalyzeAggregates(Parse *pParse, Expr *pExpr){ + int i; + AggExpr *aAgg; + int nErr = 0; + + if( pExpr==0 ) return 0; + switch( pExpr->op ){ + case TK_COLUMN: { + aAgg = pParse->aAgg; + for(i=0; inAgg; i++){ + if( aAgg[i].isAgg ) continue; + if( aAgg[i].pExpr->iTable==pExpr->iTable + && aAgg[i].pExpr->iColumn==pExpr->iColumn ){ + break; + } + } + if( i>=pParse->nAgg ){ + i = appendAggInfo(pParse); + if( i<0 ) return 1; + pParse->aAgg[i].isAgg = 0; + pParse->aAgg[i].pExpr = pExpr; + } + pExpr->iAgg = i; + break; + } + case TK_AGG_FUNCTION: { + aAgg = pParse->aAgg; + for(i=0; inAgg; i++){ + if( !aAgg[i].isAgg ) continue; + if( sqlite3ExprCompare(aAgg[i].pExpr, pExpr) ){ + break; + } + } + if( i>=pParse->nAgg ){ + u8 enc = pParse->db->enc; + i = appendAggInfo(pParse); + if( i<0 ) return 1; + pParse->aAgg[i].isAgg = 1; + pParse->aAgg[i].pExpr = pExpr; + pParse->aAgg[i].pFunc = sqlite3FindFunction(pParse->db, + pExpr->token.z, pExpr->token.n, + pExpr->pList ? pExpr->pList->nExpr : 0, enc, 0); + } + pExpr->iAgg = i; + break; + } + default: { + if( pExpr->pLeft ){ + nErr = sqlite3ExprAnalyzeAggregates(pParse, pExpr->pLeft); + } + if( nErr==0 && pExpr->pRight ){ + nErr = sqlite3ExprAnalyzeAggregates(pParse, pExpr->pRight); + } + if( nErr==0 && pExpr->pList ){ + int n = pExpr->pList->nExpr; + int i; + for(i=0; nErr==0 && ipList->a[i].pExpr); + } + } + break; + } + } + return nErr; +} + +/* +** Locate a user function given a name, a number of arguments and a flag +** indicating whether the function prefers UTF-16 over UTF-8. Return a +** pointer to the FuncDef structure that defines that function, or return +** NULL if the function does not exist. +** +** If the createFlag argument is true, then a new (blank) FuncDef +** structure is created and liked into the "db" structure if a +** no matching function previously existed. When createFlag is true +** and the nArg parameter is -1, then only a function that accepts +** any number of arguments will be returned. +** +** If createFlag is false and nArg is -1, then the first valid +** function found is returned. A function is valid if either xFunc +** or xStep is non-zero. +** +** If createFlag is false, then a function with the required name and +** number of arguments may be returned even if the eTextRep flag does not +** match that requested. +*/ +FuncDef *sqlite3FindFunction( + sqlite3 *db, /* An open database */ + const char *zName, /* Name of the function. Not null-terminated */ + int nName, /* Number of characters in the name */ + int nArg, /* Number of arguments. -1 means any number */ + u8 enc, /* Preferred text encoding */ + int createFlag /* Create new entry if true and does not otherwise exist */ +){ + FuncDef *p; /* Iterator variable */ + FuncDef *pFirst; /* First function with this name */ + FuncDef *pBest = 0; /* Best match found so far */ + int bestmatch = 0; + + + assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); + if( nArg<-1 ) nArg = -1; + + pFirst = (FuncDef*)sqlite3HashFind(&db->aFunc, zName, nName); + for(p=pFirst; p; p=p->pNext){ + /* During the search for the best function definition, bestmatch is set + ** as follows to indicate the quality of the match with the definition + ** pointed to by pBest: + ** + ** 0: pBest is NULL. No match has been found. + ** 1: A variable arguments function that prefers UTF-8 when a UTF-16 + ** encoding is requested, or vice versa. + ** 2: A variable arguments function that uses UTF-16BE when UTF-16LE is + ** requested, or vice versa. + ** 3: A variable arguments function using the same text encoding. + ** 4: A function with the exact number of arguments requested that + ** prefers UTF-8 when a UTF-16 encoding is requested, or vice versa. + ** 5: A function with the exact number of arguments requested that + ** prefers UTF-16LE when UTF-16BE is requested, or vice versa. + ** 6: An exact match. + ** + ** A larger value of 'matchqual' indicates a more desirable match. + */ + if( p->nArg==-1 || p->nArg==nArg || nArg==-1 ){ + int match = 1; /* Quality of this match */ + if( p->nArg==nArg || nArg==-1 ){ + match = 4; + } + if( enc==p->iPrefEnc ){ + match += 2; + } + else if( (enc==SQLITE_UTF16LE && p->iPrefEnc==SQLITE_UTF16BE) || + (enc==SQLITE_UTF16BE && p->iPrefEnc==SQLITE_UTF16LE) ){ + match += 1; + } + + if( match>bestmatch ){ + pBest = p; + bestmatch = match; + } + } + } + + /* If the createFlag parameter is true, and the seach did not reveal an + ** exact match for the name, number of arguments and encoding, then add a + ** new entry to the hash table and return it. + */ + if( createFlag && bestmatch<6 && + (pBest = sqliteMalloc(sizeof(*pBest)+nName+1)) ){ + pBest->nArg = nArg; + pBest->pNext = pFirst; + pBest->zName = (char*)&pBest[1]; + pBest->iPrefEnc = enc; + memcpy(pBest->zName, zName, nName); + pBest->zName[nName] = 0; + sqlite3HashInsert(&db->aFunc, pBest->zName, nName, (void*)pBest); + } + + if( pBest && (pBest->xStep || pBest->xFunc || createFlag) ){ + return pBest; + } + return 0; +} diff --git a/kopete/plugins/statistics/sqlite/func.c b/kopete/plugins/statistics/sqlite/func.c new file mode 100644 index 00000000..f61bdae3 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/func.c @@ -0,0 +1,1018 @@ +/* +** 2002 February 23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the C functions that implement various SQL +** functions of SQLite. +** +** There is only one exported symbol in this file - the function +** sqliteRegisterBuildinFunctions() found at the bottom of the file. +** All other code has file scope. +** +** $Id$ +*/ +#include +#include +#include +#include +#include "sqliteInt.h" +#include "vdbeInt.h" +#include "os.h" + +static CollSeq *sqlite3GetFuncCollSeq(sqlite3_context *context){ + return context->pColl; +} + +/* +** Implementation of the non-aggregate min() and max() functions +*/ +static void minmaxFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int i; + int mask; /* 0 for min() or 0xffffffff for max() */ + int iBest; + CollSeq *pColl; + + if( argc==0 ) return; + mask = sqlite3_user_data(context)==0 ? 0 : -1; + pColl = sqlite3GetFuncCollSeq(context); + assert( pColl ); + assert( mask==-1 || mask==0 ); + iBest = 0; + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + for(i=1; i=0 ){ + iBest = i; + } + } + sqlite3_result_value(context, argv[iBest]); +} + +/* +** Return the type of the argument. +*/ +static void typeofFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *z = 0; + switch( sqlite3_value_type(argv[0]) ){ + case SQLITE_NULL: z = "null"; break; + case SQLITE_INTEGER: z = "integer"; break; + case SQLITE_TEXT: z = "text"; break; + case SQLITE_FLOAT: z = "real"; break; + case SQLITE_BLOB: z = "blob"; break; + } + sqlite3_result_text(context, z, -1, SQLITE_STATIC); +} + +/* +** Implementation of the length() function +*/ +static void lengthFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int len; + + assert( argc==1 ); + switch( sqlite3_value_type(argv[0]) ){ + case SQLITE_BLOB: + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + sqlite3_result_int(context, sqlite3_value_bytes(argv[0])); + break; + } + case SQLITE_TEXT: { + const char *z = sqlite3_value_text(argv[0]); + for(len=0; *z; z++){ if( (0xc0&*z)!=0x80 ) len++; } + sqlite3_result_int(context, len); + break; + } + default: { + sqlite3_result_null(context); + break; + } + } +} + +/* +** Implementation of the abs() function +*/ +static void absFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ + assert( argc==1 ); + switch( sqlite3_value_type(argv[0]) ){ + case SQLITE_INTEGER: { + i64 iVal = sqlite3_value_int64(argv[0]); + if( iVal<0 ) iVal = iVal * -1; + sqlite3_result_int64(context, iVal); + break; + } + case SQLITE_NULL: { + sqlite3_result_null(context); + break; + } + default: { + double rVal = sqlite3_value_double(argv[0]); + if( rVal<0 ) rVal = rVal * -1.0; + sqlite3_result_double(context, rVal); + break; + } + } +} + +/* +** Implementation of the substr() function +*/ +static void substrFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *z; + const char *z2; + int i; + int p1, p2, len; + + assert( argc==3 ); + z = sqlite3_value_text(argv[0]); + if( z==0 ) return; + p1 = sqlite3_value_int(argv[1]); + p2 = sqlite3_value_int(argv[2]); + for(len=0, z2=z; *z2; z2++){ if( (0xc0&*z2)!=0x80 ) len++; } + if( p1<0 ){ + p1 += len; + if( p1<0 ){ + p2 += p1; + p1 = 0; + } + }else if( p1>0 ){ + p1--; + } + if( p1+p2>len ){ + p2 = len-p1; + } + for(i=0; i30 ) n = 30; + if( n<0 ) n = 0; + } + if( SQLITE_NULL==sqlite3_value_type(argv[0]) ) return; + r = sqlite3_value_double(argv[0]); + sprintf(zBuf,"%.*f",n,r); + sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); +} + +/* +** Implementation of the upper() and lower() SQL functions. +*/ +static void upperFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ + unsigned char *z; + int i; + if( argc<1 || SQLITE_NULL==sqlite3_value_type(argv[0]) ) return; + z = sqliteMalloc(sqlite3_value_bytes(argv[0])+1); + if( z==0 ) return; + strcpy(z, sqlite3_value_text(argv[0])); + for(i=0; z[i]; i++){ + z[i] = toupper(z[i]); + } + sqlite3_result_text(context, z, -1, SQLITE_TRANSIENT); + sqliteFree(z); +} +static void lowerFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ + unsigned char *z; + int i; + if( argc<1 || SQLITE_NULL==sqlite3_value_type(argv[0]) ) return; + z = sqliteMalloc(sqlite3_value_bytes(argv[0])+1); + if( z==0 ) return; + strcpy(z, sqlite3_value_text(argv[0])); + for(i=0; z[i]; i++){ + z[i] = tolower(z[i]); + } + sqlite3_result_text(context, z, -1, SQLITE_TRANSIENT); + sqliteFree(z); +} + +/* +** Implementation of the IFNULL(), NVL(), and COALESCE() functions. +** All three do the same thing. They return the first non-NULL +** argument. +*/ +static void ifnullFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int i; + for(i=0; imatchOne; + u8 matchAll = pInfo->matchAll; + u8 matchSet = pInfo->matchSet; + u8 noCase = pInfo->noCase; + + while( (c = *zPattern)!=0 ){ + if( c==matchAll ){ + while( (c=zPattern[1]) == matchAll || c == matchOne ){ + if( c==matchOne ){ + if( *zString==0 ) return 0; + sqliteNextChar(zString); + } + zPattern++; + } + if( c==0 ) return 1; + if( c==matchSet ){ + while( *zString && patternCompare(&zPattern[1],zString,pInfo)==0 ){ + sqliteNextChar(zString); + } + return *zString!=0; + }else{ + while( (c2 = *zString)!=0 ){ + if( noCase ){ + c2 = sqlite3UpperToLower[c2]; + c = sqlite3UpperToLower[c]; + while( c2 != 0 && c2 != c ){ c2 = sqlite3UpperToLower[*++zString]; } + }else{ + while( c2 != 0 && c2 != c ){ c2 = *++zString; } + } + if( c2==0 ) return 0; + if( patternCompare(&zPattern[1],zString,pInfo) ) return 1; + sqliteNextChar(zString); + } + return 0; + } + }else if( c==matchOne ){ + if( *zString==0 ) return 0; + sqliteNextChar(zString); + zPattern++; + }else if( c==matchSet ){ + int prior_c = 0; + seen = 0; + invert = 0; + c = sqliteCharVal(zString); + if( c==0 ) return 0; + c2 = *++zPattern; + if( c2=='^' ){ invert = 1; c2 = *++zPattern; } + if( c2==']' ){ + if( c==']' ) seen = 1; + c2 = *++zPattern; + } + while( (c2 = sqliteCharVal(zPattern))!=0 && c2!=']' ){ + if( c2=='-' && zPattern[1]!=']' && zPattern[1]!=0 && prior_c>0 ){ + zPattern++; + c2 = sqliteCharVal(zPattern); + if( c>=prior_c && c<=c2 ) seen = 1; + prior_c = 0; + }else if( c==c2 ){ + seen = 1; + prior_c = c2; + }else{ + prior_c = c2; + } + sqliteNextChar(zPattern); + } + if( c2==0 || (seen ^ invert)==0 ) return 0; + sqliteNextChar(zString); + zPattern++; + }else{ + if( noCase ){ + if( sqlite3UpperToLower[c] != sqlite3UpperToLower[*zString] ) return 0; + }else{ + if( c != *zString ) return 0; + } + zPattern++; + zString++; + } + } + return *zString==0; +} + + +/* +** Implementation of the like() SQL function. This function implements +** the build-in LIKE operator. The first argument to the function is the +** pattern and the second argument is the string. So, the SQL statements: +** +** A LIKE B +** +** is implemented as like(B,A). +** +** If the pointer retrieved by via a call to sqlite3_user_data() is +** not NULL, then this function uses UTF-16. Otherwise UTF-8. +*/ +static void likeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *zA = sqlite3_value_text(argv[0]); + const unsigned char *zB = sqlite3_value_text(argv[1]); + if( zA && zB ){ + sqlite3_result_int(context, patternCompare(zA, zB, &likeInfo)); + } +} + +/* +** Implementation of the glob() SQL function. This function implements +** the build-in GLOB operator. The first argument to the function is the +** string and the second argument is the pattern. So, the SQL statements: +** +** A GLOB B +** +** is implemented as glob(A,B). +*/ +static void globFunc(sqlite3_context *context, int arg, sqlite3_value **argv){ + const unsigned char *zA = sqlite3_value_text(argv[0]); + const unsigned char *zB = sqlite3_value_text(argv[1]); + if( zA && zB ){ + sqlite3_result_int(context, patternCompare(zA, zB, &globInfo)); + } +} + +/* +** Implementation of the NULLIF(x,y) function. The result is the first +** argument if the arguments are different. The result is NULL if the +** arguments are equal to each other. +*/ +static void nullifFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + CollSeq *pColl = sqlite3GetFuncCollSeq(context); + if( sqlite3MemCompare(argv[0], argv[1], pColl)!=0 ){ + sqlite3_result_value(context, argv[0]); + } +} + +/* +** Implementation of the VERSION(*) function. The result is the version +** of the SQLite library that is running. +*/ +static void versionFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3_result_text(context, sqlite3_version, -1, SQLITE_STATIC); +} + +/* +** EXPERIMENTAL - This is not an official function. The interface may +** change. This function may disappear. Do not write code that depends +** on this function. +** +** Implementation of the QUOTE() function. This function takes a single +** argument. If the argument is numeric, the return value is the same as +** the argument. If the argument is NULL, the return value is the string +** "NULL". Otherwise, the argument is enclosed in single quotes with +** single-quote escapes. +*/ +static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ + if( argc<1 ) return; + switch( sqlite3_value_type(argv[0]) ){ + case SQLITE_NULL: { + sqlite3_result_text(context, "NULL", 4, SQLITE_STATIC); + break; + } + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + sqlite3_result_value(context, argv[0]); + break; + } + case SQLITE_BLOB: { + static const char hexdigits[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + char *zText = 0; + int nBlob = sqlite3_value_bytes(argv[0]); + char const *zBlob = sqlite3_value_blob(argv[0]); + + zText = (char *)sqliteMalloc((2*nBlob)+4); + if( !zText ){ + sqlite3_result_error(context, "out of memory", -1); + }else{ + int i; + for(i=0; i>4)&0x0F]; + zText[(i*2)+3] = hexdigits[(zBlob[i])&0x0F]; + } + zText[(nBlob*2)+2] = '\''; + zText[(nBlob*2)+3] = '\0'; + zText[0] = 'X'; + zText[1] = '\''; + sqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT); + sqliteFree(zText); + } + break; + } + case SQLITE_TEXT: { + int i,j,n; + const char *zArg = sqlite3_value_text(argv[0]); + char *z; + + for(i=n=0; zArg[i]; i++){ if( zArg[i]=='\'' ) n++; } + z = sqliteMalloc( i+n+3 ); + if( z==0 ) return; + z[0] = '\''; + for(i=0, j=1; zArg[i]; i++){ + z[j++] = zArg[i]; + if( zArg[i]=='\'' ){ + z[j++] = '\''; + } + } + z[j++] = '\''; + z[j] = 0; + sqlite3_result_text(context, z, j, SQLITE_TRANSIENT); + sqliteFree(z); + } + } +} + +#ifdef SQLITE_SOUNDEX +/* +** Compute the soundex encoding of a word. +*/ +static void soundexFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ + char zResult[8]; + const u8 *zIn; + int i, j; + static const unsigned char iCode[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, + 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, + 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, + }; + assert( argc==1 ); + zIn = (u8*)sqlite3_value_text(argv[0]); + for(i=0; zIn[i] && !isalpha(zIn[i]); i++){} + if( zIn[i] ){ + zResult[0] = toupper(zIn[i]); + for(j=1; j<4 && zIn[i]; i++){ + int code = iCode[zIn[i]&0x7f]; + if( code>0 ){ + zResult[j++] = code + '0'; + } + } + while( j<4 ){ + zResult[j++] = '0'; + } + zResult[j] = 0; + sqlite3_result_text(context, zResult, 4, SQLITE_TRANSIENT); + }else{ + sqlite3_result_text(context, "?000", 4, SQLITE_STATIC); + } +} +#endif + +#ifdef SQLITE_TEST +/* +** This function generates a string of random characters. Used for +** generating test data. +*/ +static void randStr(sqlite3_context *context, int argc, sqlite3_value **argv){ + static const unsigned char zSrc[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + ".-!,:*^+=_|?/<> "; + int iMin, iMax, n, r, i; + unsigned char zBuf[1000]; + if( argc>=1 ){ + iMin = sqlite3_value_int(argv[0]); + if( iMin<0 ) iMin = 0; + if( iMin>=sizeof(zBuf) ) iMin = sizeof(zBuf)-1; + }else{ + iMin = 1; + } + if( argc>=2 ){ + iMax = sqlite3_value_int(argv[1]); + if( iMax=sizeof(zBuf) ) iMax = sizeof(zBuf)-1; + }else{ + iMax = 50; + } + n = iMin; + if( iMax>iMin ){ + sqlite3Randomness(sizeof(r), &r); + r &= 0x7fffffff; + n += r%(iMax + 1 - iMin); + } + assert( nenc); + zVal = sqliteMalloc(len+3); + zVal[len] = 0; + zVal[len-1] = 0; + assert( zVal ); + zVal++; + memcpy(zVal, sqlite3ValueText(argv[0], db->enc), len); + if( db->enc==SQLITE_UTF8 ){ + sqlite3_result_text(pCtx, zVal, -1, destructor); + }else if( db->enc==SQLITE_UTF16LE ){ + sqlite3_result_text16le(pCtx, zVal, -1, destructor); + }else{ + sqlite3_result_text16be(pCtx, zVal, -1, destructor); + } +} +static void test_destructor_count( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **argv +){ + sqlite3_result_int(pCtx, test_destructor_count_var); +} +#endif /* SQLITE_TEST */ + +#ifdef SQLITE_TEST +/* +** Routines for testing the sqlite3_get_auxdata() and sqlite3_set_auxdata() +** interface. +** +** The test_auxdata() SQL function attempts to register each of its arguments +** as auxiliary data. If there are no prior registrations of aux data for +** that argument (meaning the argument is not a constant or this is its first +** call) then the result for that argument is 0. If there is a prior +** registration, the result for that argument is 1. The overall result +** is the individual argument results separated by spaces. +*/ +static void free_test_auxdata(void *p) {sqliteFree(p);} +static void test_auxdata( + sqlite3_context *pCtx, + int nArg, + sqlite3_value **argv +){ + int i; + char *zRet = sqliteMalloc(nArg*2); + if( !zRet ) return; + for(i=0; isum += sqlite3_value_double(argv[0]); + p->cnt++; + } +} +static void sumFinalize(sqlite3_context *context){ + SumCtx *p; + p = sqlite3_aggregate_context(context, sizeof(*p)); + sqlite3_result_double(context, p ? p->sum : 0.0); +} +static void avgFinalize(sqlite3_context *context){ + SumCtx *p; + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( p && p->cnt>0 ){ + sqlite3_result_double(context, p->sum/(double)p->cnt); + } +} + +/* +** An instance of the following structure holds the context of a +** variance or standard deviation computation. +*/ +typedef struct StdDevCtx StdDevCtx; +struct StdDevCtx { + double sum; /* Sum of terms */ + double sum2; /* Sum of the squares of terms */ + int cnt; /* Number of terms counted */ +}; + +#if 0 /* Omit because math library is required */ +/* +** Routines used to compute the standard deviation as an aggregate. +*/ +static void stdDevStep(sqlite3_context *context, int argc, const char **argv){ + StdDevCtx *p; + double x; + if( argc<1 ) return; + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( p && argv[0] ){ + x = sqlite3AtoF(argv[0], 0); + p->sum += x; + p->sum2 += x*x; + p->cnt++; + } +} +static void stdDevFinalize(sqlite3_context *context){ + double rN = sqlite3_aggregate_count(context); + StdDevCtx *p = sqlite3_aggregate_context(context, sizeof(*p)); + if( p && p->cnt>1 ){ + double rCnt = cnt; + sqlite3_set_result_double(context, + sqrt((p->sum2 - p->sum*p->sum/rCnt)/(rCnt-1.0))); + } +} +#endif + +/* +** The following structure keeps track of state information for the +** count() aggregate function. +*/ +typedef struct CountCtx CountCtx; +struct CountCtx { + int n; +}; + +/* +** Routines to implement the count() aggregate function. +*/ +static void countStep(sqlite3_context *context, int argc, sqlite3_value **argv){ + CountCtx *p; + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( (argc==0 || SQLITE_NULL!=sqlite3_value_type(argv[0])) && p ){ + p->n++; + } +} +static void countFinalize(sqlite3_context *context){ + CountCtx *p; + p = sqlite3_aggregate_context(context, sizeof(*p)); + sqlite3_result_int(context, p ? p->n : 0); +} + +/* +** This function tracks state information for the min() and max() +** aggregate functions. +*/ +typedef struct MinMaxCtx MinMaxCtx; +struct MinMaxCtx { + char *z; /* The best so far */ + char zBuf[28]; /* Space that can be used for storage */ +}; + +/* +** Routines to implement min() and max() aggregate functions. +*/ +static void minmaxStep(sqlite3_context *context, int argc, sqlite3_value **argv){ + Mem *pArg = (Mem *)argv[0]; + Mem *pBest; + + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + pBest = (Mem *)sqlite3_aggregate_context(context, sizeof(*pBest)); + if( !pBest ) return; + + if( pBest->flags ){ + int max; + int cmp; + CollSeq *pColl = sqlite3GetFuncCollSeq(context); + /* This step function is used for both the min() and max() aggregates, + ** the only difference between the two being that the sense of the + ** comparison is inverted. For the max() aggregate, the + ** sqlite3_user_data() function returns (void *)-1. For min() it + ** returns (void *)db, where db is the sqlite3* database pointer. + ** Therefore the next statement sets variable 'max' to 1 for the max() + ** aggregate, or 0 for min(). + */ + max = ((sqlite3_user_data(context)==(void *)-1)?1:0); + cmp = sqlite3MemCompare(pBest, pArg, pColl); + if( (max && cmp<0) || (!max && cmp>0) ){ + sqlite3VdbeMemCopy(pBest, pArg); + } + }else{ + sqlite3VdbeMemCopy(pBest, pArg); + } +} +static void minMaxFinalize(sqlite3_context *context){ + sqlite3_value *pRes; + pRes = (sqlite3_value *)sqlite3_aggregate_context(context, sizeof(Mem)); + if( pRes->flags ){ + sqlite3_result_value(context, pRes); + } + sqlite3VdbeMemRelease(pRes); +} + + +/* +** This function registered all of the above C functions as SQL +** functions. This should be the only routine in this file with +** external linkage. +*/ +void sqlite3RegisterBuiltinFunctions(sqlite3 *db){ + static const struct { + char *zName; + signed char nArg; + u8 argType; /* 0: none. 1: db 2: (-1) */ + u8 eTextRep; /* 1: UTF-16. 0: UTF-8 */ + u8 needCollSeq; + void (*xFunc)(sqlite3_context*,int,sqlite3_value **); + } aFuncs[] = { + { "min", -1, 0, SQLITE_UTF8, 1, minmaxFunc }, + { "min", 0, 0, SQLITE_UTF8, 1, 0 }, + { "max", -1, 2, SQLITE_UTF8, 1, minmaxFunc }, + { "max", 0, 2, SQLITE_UTF8, 1, 0 }, + { "typeof", 1, 0, SQLITE_UTF8, 0, typeofFunc }, + { "length", 1, 0, SQLITE_UTF8, 0, lengthFunc }, + { "substr", 3, 0, SQLITE_UTF8, 0, substrFunc }, + { "substr", 3, 0, SQLITE_UTF16LE, 0, sqlite3utf16Substr }, + { "abs", 1, 0, SQLITE_UTF8, 0, absFunc }, + { "round", 1, 0, SQLITE_UTF8, 0, roundFunc }, + { "round", 2, 0, SQLITE_UTF8, 0, roundFunc }, + { "upper", 1, 0, SQLITE_UTF8, 0, upperFunc }, + { "lower", 1, 0, SQLITE_UTF8, 0, lowerFunc }, + { "coalesce", -1, 0, SQLITE_UTF8, 0, ifnullFunc }, + { "coalesce", 0, 0, SQLITE_UTF8, 0, 0 }, + { "coalesce", 1, 0, SQLITE_UTF8, 0, 0 }, + { "ifnull", 2, 0, SQLITE_UTF8, 1, ifnullFunc }, + { "random", -1, 0, SQLITE_UTF8, 0, randomFunc }, + { "like", 2, 0, SQLITE_UTF8, 0, likeFunc }, + { "glob", 2, 0, SQLITE_UTF8, 0, globFunc }, + { "nullif", 2, 0, SQLITE_UTF8, 1, nullifFunc }, + { "sqlite_version", 0, 0, SQLITE_UTF8, 0, versionFunc}, + { "quote", 1, 0, SQLITE_UTF8, 0, quoteFunc }, + { "last_insert_rowid", 0, 1, SQLITE_UTF8, 0, last_insert_rowid }, + { "changes", 0, 1, SQLITE_UTF8, 0, changes }, + { "total_changes", 0, 1, SQLITE_UTF8, 0, total_changes }, +#ifdef SQLITE_SOUNDEX + { "soundex", 1, 0, SQLITE_UTF8, 0, soundexFunc}, +#endif +#ifdef SQLITE_TEST + { "randstr", 2, 0, SQLITE_UTF8, 0, randStr }, + { "test_destructor", 1, 1, SQLITE_UTF8, 0, test_destructor}, + { "test_destructor_count", 0, 0, SQLITE_UTF8, 0, test_destructor_count}, + { "test_auxdata", -1, 0, SQLITE_UTF8, 0, test_auxdata}, +#endif + }; + static const struct { + char *zName; + signed char nArg; + u8 argType; + u8 needCollSeq; + void (*xStep)(sqlite3_context*,int,sqlite3_value**); + void (*xFinalize)(sqlite3_context*); + } aAggs[] = { + { "min", 1, 0, 1, minmaxStep, minMaxFinalize }, + { "max", 1, 2, 1, minmaxStep, minMaxFinalize }, + { "sum", 1, 0, 0, sumStep, sumFinalize }, + { "avg", 1, 0, 0, sumStep, avgFinalize }, + { "count", 0, 0, 0, countStep, countFinalize }, + { "count", 1, 0, 0, countStep, countFinalize }, +#if 0 + { "stddev", 1, 0, stdDevStep, stdDevFinalize }, +#endif + }; + int i; + + for(i=0; ineedCollSeq = 1; + } + } + } + for(i=0; ineedCollSeq = 1; + } + } + } + sqlite3RegisterDateTimeFunctions(db); +} diff --git a/kopete/plugins/statistics/sqlite/hash.c b/kopete/plugins/statistics/sqlite/hash.c new file mode 100644 index 00000000..23e2e197 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/hash.c @@ -0,0 +1,380 @@ +/* +** 2001 September 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the implementation of generic hash-tables +** used in SQLite. +** +** $Id$ +*/ +#include "sqliteInt.h" +#include + +/* Turn bulk memory into a hash table object by initializing the +** fields of the Hash structure. +** +** "pNew" is a pointer to the hash table that is to be initialized. +** keyClass is one of the constants SQLITE_HASH_INT, SQLITE_HASH_POINTER, +** SQLITE_HASH_BINARY, or SQLITE_HASH_STRING. The value of keyClass +** determines what kind of key the hash table will use. "copyKey" is +** true if the hash table should make its own private copy of keys and +** false if it should just use the supplied pointer. CopyKey only makes +** sense for SQLITE_HASH_STRING and SQLITE_HASH_BINARY and is ignored +** for other key classes. +*/ +void sqlite3HashInit(Hash *pNew, int keyClass, int copyKey){ + assert( pNew!=0 ); + assert( keyClass>=SQLITE_HASH_STRING && keyClass<=SQLITE_HASH_BINARY ); + pNew->keyClass = keyClass; +#if 0 + if( keyClass==SQLITE_HASH_POINTER || keyClass==SQLITE_HASH_INT ) copyKey = 0; +#endif + pNew->copyKey = copyKey; + pNew->first = 0; + pNew->count = 0; + pNew->htsize = 0; + pNew->ht = 0; +} + +/* Remove all entries from a hash table. Reclaim all memory. +** Call this routine to delete a hash table or to reset a hash table +** to the empty state. +*/ +void sqlite3HashClear(Hash *pH){ + HashElem *elem; /* For looping over all elements of the table */ + + assert( pH!=0 ); + elem = pH->first; + pH->first = 0; + if( pH->ht ) sqliteFree(pH->ht); + pH->ht = 0; + pH->htsize = 0; + while( elem ){ + HashElem *next_elem = elem->next; + if( pH->copyKey && elem->pKey ){ + sqliteFree(elem->pKey); + } + sqliteFree(elem); + elem = next_elem; + } + pH->count = 0; +} + +#if 0 /* NOT USED */ +/* +** Hash and comparison functions when the mode is SQLITE_HASH_INT +*/ +static int intHash(const void *pKey, int nKey){ + return nKey ^ (nKey<<8) ^ (nKey>>8); +} +static int intCompare(const void *pKey1, int n1, const void *pKey2, int n2){ + return n2 - n1; +} +#endif + +#if 0 /* NOT USED */ +/* +** Hash and comparison functions when the mode is SQLITE_HASH_POINTER +*/ +static int ptrHash(const void *pKey, int nKey){ + uptr x = Addr(pKey); + return x ^ (x<<8) ^ (x>>8); +} +static int ptrCompare(const void *pKey1, int n1, const void *pKey2, int n2){ + if( pKey1==pKey2 ) return 0; + if( pKey1 0 ){ + h = (h<<3) ^ h ^ *(z++); + } + return h & 0x7fffffff; +} +static int binCompare(const void *pKey1, int n1, const void *pKey2, int n2){ + if( n1!=n2 ) return 1; + return memcmp(pKey1,pKey2,n1); +} + +/* +** Return a pointer to the appropriate hash function given the key class. +** +** The C syntax in this function definition may be unfamilar to some +** programmers, so we provide the following additional explanation: +** +** The name of the function is "hashFunction". The function takes a +** single parameter "keyClass". The return value of hashFunction() +** is a pointer to another function. Specifically, the return value +** of hashFunction() is a pointer to a function that takes two parameters +** with types "const void*" and "int" and returns an "int". +*/ +static int (*hashFunction(int keyClass))(const void*,int){ +#if 0 /* HASH_INT and HASH_POINTER are never used */ + switch( keyClass ){ + case SQLITE_HASH_INT: return &intHash; + case SQLITE_HASH_POINTER: return &ptrHash; + case SQLITE_HASH_STRING: return &strHash; + case SQLITE_HASH_BINARY: return &binHash;; + default: break; + } + return 0; +#else + if( keyClass==SQLITE_HASH_STRING ){ + return &strHash; + }else{ + assert( keyClass==SQLITE_HASH_BINARY ); + return &binHash; + } +#endif +} + +/* +** Return a pointer to the appropriate hash function given the key class. +** +** For help in interpreted the obscure C code in the function definition, +** see the header comment on the previous function. +*/ +static int (*compareFunction(int keyClass))(const void*,int,const void*,int){ +#if 0 /* HASH_INT and HASH_POINTER are never used */ + switch( keyClass ){ + case SQLITE_HASH_INT: return &intCompare; + case SQLITE_HASH_POINTER: return &ptrCompare; + case SQLITE_HASH_STRING: return &strCompare; + case SQLITE_HASH_BINARY: return &binCompare; + default: break; + } + return 0; +#else + if( keyClass==SQLITE_HASH_STRING ){ + return &strCompare; + }else{ + assert( keyClass==SQLITE_HASH_BINARY ); + return &binCompare; + } +#endif +} + +/* Link an element into the hash table +*/ +static void insertElement( + Hash *pH, /* The complete hash table */ + struct _ht *pEntry, /* The entry into which pNew is inserted */ + HashElem *pNew /* The element to be inserted */ +){ + HashElem *pHead; /* First element already in pEntry */ + pHead = pEntry->chain; + if( pHead ){ + pNew->next = pHead; + pNew->prev = pHead->prev; + if( pHead->prev ){ pHead->prev->next = pNew; } + else { pH->first = pNew; } + pHead->prev = pNew; + }else{ + pNew->next = pH->first; + if( pH->first ){ pH->first->prev = pNew; } + pNew->prev = 0; + pH->first = pNew; + } + pEntry->count++; + pEntry->chain = pNew; +} + + +/* Resize the hash table so that it cantains "new_size" buckets. +** "new_size" must be a power of 2. The hash table might fail +** to resize if sqliteMalloc() fails. +*/ +static void rehash(Hash *pH, int new_size){ + struct _ht *new_ht; /* The new hash table */ + HashElem *elem, *next_elem; /* For looping over existing elements */ + int (*xHash)(const void*,int); /* The hash function */ + + assert( (new_size & (new_size-1))==0 ); + new_ht = (struct _ht *)sqliteMalloc( new_size*sizeof(struct _ht) ); + if( new_ht==0 ) return; + if( pH->ht ) sqliteFree(pH->ht); + pH->ht = new_ht; + pH->htsize = new_size; + xHash = hashFunction(pH->keyClass); + for(elem=pH->first, pH->first=0; elem; elem = next_elem){ + int h = (*xHash)(elem->pKey, elem->nKey) & (new_size-1); + next_elem = elem->next; + insertElement(pH, &new_ht[h], elem); + } +} + +/* This function (for internal use only) locates an element in an +** hash table that matches the given key. The hash for this key has +** already been computed and is passed as the 4th parameter. +*/ +static HashElem *findElementGivenHash( + const Hash *pH, /* The pH to be searched */ + const void *pKey, /* The key we are searching for */ + int nKey, + int h /* The hash for this key. */ +){ + HashElem *elem; /* Used to loop thru the element list */ + int count; /* Number of elements left to test */ + int (*xCompare)(const void*,int,const void*,int); /* comparison function */ + + if( pH->ht ){ + struct _ht *pEntry = &pH->ht[h]; + elem = pEntry->chain; + count = pEntry->count; + xCompare = compareFunction(pH->keyClass); + while( count-- && elem ){ + if( (*xCompare)(elem->pKey,elem->nKey,pKey,nKey)==0 ){ + return elem; + } + elem = elem->next; + } + } + return 0; +} + +/* Remove a single entry from the hash table given a pointer to that +** element and a hash on the element's key. +*/ +static void removeElementGivenHash( + Hash *pH, /* The pH containing "elem" */ + HashElem* elem, /* The element to be removed from the pH */ + int h /* Hash value for the element */ +){ + struct _ht *pEntry; + if( elem->prev ){ + elem->prev->next = elem->next; + }else{ + pH->first = elem->next; + } + if( elem->next ){ + elem->next->prev = elem->prev; + } + pEntry = &pH->ht[h]; + if( pEntry->chain==elem ){ + pEntry->chain = elem->next; + } + pEntry->count--; + if( pEntry->count<=0 ){ + pEntry->chain = 0; + } + if( pH->copyKey && elem->pKey ){ + sqliteFree(elem->pKey); + } + sqliteFree( elem ); + pH->count--; +} + +/* Attempt to locate an element of the hash table pH with a key +** that matches pKey,nKey. Return the data for this element if it is +** found, or NULL if there is no match. +*/ +void *sqlite3HashFind(const Hash *pH, const void *pKey, int nKey){ + int h; /* A hash on key */ + HashElem *elem; /* The element that matches key */ + int (*xHash)(const void*,int); /* The hash function */ + + if( pH==0 || pH->ht==0 ) return 0; + xHash = hashFunction(pH->keyClass); + assert( xHash!=0 ); + h = (*xHash)(pKey,nKey); + assert( (pH->htsize & (pH->htsize-1))==0 ); + elem = findElementGivenHash(pH,pKey,nKey, h & (pH->htsize-1)); + return elem ? elem->data : 0; +} + +/* Insert an element into the hash table pH. The key is pKey,nKey +** and the data is "data". +** +** If no element exists with a matching key, then a new +** element is created. A copy of the key is made if the copyKey +** flag is set. NULL is returned. +** +** If another element already exists with the same key, then the +** new data replaces the old data and the old data is returned. +** The key is not copied in this instance. If a malloc fails, then +** the new data is returned and the hash table is unchanged. +** +** If the "data" parameter to this function is NULL, then the +** element corresponding to "key" is removed from the hash table. +*/ +void *sqlite3HashInsert(Hash *pH, const void *pKey, int nKey, void *data){ + int hraw; /* Raw hash value of the key */ + int h; /* the hash of the key modulo hash table size */ + HashElem *elem; /* Used to loop thru the element list */ + HashElem *new_elem; /* New element added to the pH */ + int (*xHash)(const void*,int); /* The hash function */ + + assert( pH!=0 ); + xHash = hashFunction(pH->keyClass); + assert( xHash!=0 ); + hraw = (*xHash)(pKey, nKey); + assert( (pH->htsize & (pH->htsize-1))==0 ); + h = hraw & (pH->htsize-1); + elem = findElementGivenHash(pH,pKey,nKey,h); + if( elem ){ + void *old_data = elem->data; + if( data==0 ){ + removeElementGivenHash(pH,elem,h); + }else{ + elem->data = data; + } + return old_data; + } + if( data==0 ) return 0; + new_elem = (HashElem*)sqliteMalloc( sizeof(HashElem) ); + if( new_elem==0 ) return data; + if( pH->copyKey && pKey!=0 ){ + new_elem->pKey = sqliteMallocRaw( nKey ); + if( new_elem->pKey==0 ){ + sqliteFree(new_elem); + return data; + } + memcpy((void*)new_elem->pKey, pKey, nKey); + }else{ + new_elem->pKey = (void*)pKey; + } + new_elem->nKey = nKey; + pH->count++; + if( pH->htsize==0 ){ + rehash(pH,8); + if( pH->htsize==0 ){ + pH->count = 0; + sqliteFree(new_elem); + return data; + } + } + if( pH->count > pH->htsize ){ + rehash(pH,pH->htsize*2); + } + assert( pH->htsize>0 ); + assert( (pH->htsize & (pH->htsize-1))==0 ); + h = hraw & (pH->htsize-1); + insertElement(pH, &pH->ht[h], new_elem); + new_elem->data = data; + return 0; +} diff --git a/kopete/plugins/statistics/sqlite/hash.h b/kopete/plugins/statistics/sqlite/hash.h new file mode 100644 index 00000000..cf004ddc --- /dev/null +++ b/kopete/plugins/statistics/sqlite/hash.h @@ -0,0 +1,109 @@ +/* +** 2001 September 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the header file for the generic hash-table implemenation +** used in SQLite. +** +** $Id$ +*/ +#ifndef _SQLITE_HASH_H_ +#define _SQLITE_HASH_H_ + +/* Forward declarations of structures. */ +typedef struct Hash Hash; +typedef struct HashElem HashElem; + +/* A complete hash table is an instance of the following structure. +** The internals of this structure are intended to be opaque -- client +** code should not attempt to access or modify the fields of this structure +** directly. Change this structure only by using the routines below. +** However, many of the "procedures" and "functions" for modifying and +** accessing this structure are really macros, so we can't really make +** this structure opaque. +*/ +struct Hash { + char keyClass; /* SQLITE_HASH_INT, _POINTER, _STRING, _BINARY */ + char copyKey; /* True if copy of key made on insert */ + int count; /* Number of entries in this table */ + HashElem *first; /* The first element of the array */ + int htsize; /* Number of buckets in the hash table */ + struct _ht { /* the hash table */ + int count; /* Number of entries with this hash */ + HashElem *chain; /* Pointer to first entry with this hash */ + } *ht; +}; + +/* Each element in the hash table is an instance of the following +** structure. All elements are stored on a single doubly-linked list. +** +** Again, this structure is intended to be opaque, but it can't really +** be opaque because it is used by macros. +*/ +struct HashElem { + HashElem *next, *prev; /* Next and previous elements in the table */ + void *data; /* Data associated with this element */ + void *pKey; int nKey; /* Key associated with this element */ +}; + +/* +** There are 4 different modes of operation for a hash table: +** +** SQLITE_HASH_INT nKey is used as the key and pKey is ignored. +** +** SQLITE_HASH_POINTER pKey is used as the key and nKey is ignored. +** +** SQLITE_HASH_STRING pKey points to a string that is nKey bytes long +** (including the null-terminator, if any). Case +** is ignored in comparisons. +** +** SQLITE_HASH_BINARY pKey points to binary data nKey bytes long. +** memcmp() is used to compare keys. +** +** A copy of the key is made for SQLITE_HASH_STRING and SQLITE_HASH_BINARY +** if the copyKey parameter to HashInit is 1. +*/ +/* #define SQLITE_HASH_INT 1 // NOT USED */ +/* #define SQLITE_HASH_POINTER 2 // NOT USED */ +#define SQLITE_HASH_STRING 3 +#define SQLITE_HASH_BINARY 4 + +/* +** Access routines. To delete, insert a NULL pointer. +*/ +void sqlite3HashInit(Hash*, int keytype, int copyKey); +void *sqlite3HashInsert(Hash*, const void *pKey, int nKey, void *pData); +void *sqlite3HashFind(const Hash*, const void *pKey, int nKey); +void sqlite3HashClear(Hash*); + +/* +** Macros for looping over all elements of a hash table. The idiom is +** like this: +** +** Hash h; +** HashElem *p; +** ... +** for(p=sqliteHashFirst(&h); p; p=sqliteHashNext(p)){ +** SomeStructure *pData = sqliteHashData(p); +** // do something with pData +** } +*/ +#define sqliteHashFirst(H) ((H)->first) +#define sqliteHashNext(E) ((E)->next) +#define sqliteHashData(E) ((E)->data) +#define sqliteHashKey(E) ((E)->pKey) +#define sqliteHashKeysize(E) ((E)->nKey) + +/* +** Number of entries in a hash table +*/ +#define sqliteHashCount(H) ((H)->count) + +#endif /* _SQLITE_HASH_H_ */ diff --git a/kopete/plugins/statistics/sqlite/insert.c b/kopete/plugins/statistics/sqlite/insert.c new file mode 100644 index 00000000..65cbdc8f --- /dev/null +++ b/kopete/plugins/statistics/sqlite/insert.c @@ -0,0 +1,1018 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains C code routines that are called by the parser +** to handle INSERT statements in SQLite. +** +** $Id$ +*/ +#include "sqliteInt.h" + +/* +** Set P3 of the most recently inserted opcode to a column affinity +** string for index pIdx. A column affinity string has one character +** for each column in the table, according to the affinity of the column: +** +** Character Column affinity +** ------------------------------ +** 'n' NUMERIC +** 'i' INTEGER +** 't' TEXT +** 'o' NONE +*/ +void sqlite3IndexAffinityStr(Vdbe *v, Index *pIdx){ + if( !pIdx->zColAff ){ + /* The first time a column affinity string for a particular index is + ** required, it is allocated and populated here. It is then stored as + ** a member of the Index structure for subsequent use. + ** + ** The column affinity string will eventually be deleted by + ** sqliteDeleteIndex() when the Index structure itself is cleaned + ** up. + */ + int n; + Table *pTab = pIdx->pTable; + pIdx->zColAff = (char *)sqliteMalloc(pIdx->nColumn+1); + if( !pIdx->zColAff ){ + return; + } + for(n=0; nnColumn; n++){ + pIdx->zColAff[n] = pTab->aCol[pIdx->aiColumn[n]].affinity; + } + pIdx->zColAff[pIdx->nColumn] = '\0'; + } + + sqlite3VdbeChangeP3(v, -1, pIdx->zColAff, 0); +} + +/* +** Set P3 of the most recently inserted opcode to a column affinity +** string for table pTab. A column affinity string has one character +** for each column indexed by the index, according to the affinity of the +** column: +** +** Character Column affinity +** ------------------------------ +** 'n' NUMERIC +** 'i' INTEGER +** 't' TEXT +** 'o' NONE +*/ +void sqlite3TableAffinityStr(Vdbe *v, Table *pTab){ + /* The first time a column affinity string for a particular table + ** is required, it is allocated and populated here. It is then + ** stored as a member of the Table structure for subsequent use. + ** + ** The column affinity string will eventually be deleted by + ** sqlite3DeleteTable() when the Table structure itself is cleaned up. + */ + if( !pTab->zColAff ){ + char *zColAff; + int i; + + zColAff = (char *)sqliteMalloc(pTab->nCol+1); + if( !zColAff ){ + return; + } + + for(i=0; inCol; i++){ + zColAff[i] = pTab->aCol[i].affinity; + } + zColAff[pTab->nCol] = '\0'; + + pTab->zColAff = zColAff; + } + + sqlite3VdbeChangeP3(v, -1, pTab->zColAff, 0); +} + + +/* +** This routine is call to handle SQL of the following forms: +** +** insert into TABLE (IDLIST) values(EXPRLIST) +** insert into TABLE (IDLIST) select +** +** The IDLIST following the table name is always optional. If omitted, +** then a list of all columns for the table is substituted. The IDLIST +** appears in the pColumn parameter. pColumn is NULL if IDLIST is omitted. +** +** The pList parameter holds EXPRLIST in the first form of the INSERT +** statement above, and pSelect is NULL. For the second form, pList is +** NULL and pSelect is a pointer to the select statement used to generate +** data for the insert. +** +** The code generated follows one of three templates. For a simple +** select with data coming from a VALUES clause, the code executes +** once straight down through. The template looks like this: +** +** open write cursor to and its indices +** puts VALUES clause expressions onto the stack +** write the resulting record into
    +** cleanup +** +** If the statement is of the form +** +** INSERT INTO
    SELECT ... +** +** And the SELECT clause does not read from
    at any time, then +** the generated code follows this template: +** +** goto B +** A: setup for the SELECT +** loop over the tables in the SELECT +** gosub C +** end loop +** cleanup after the SELECT +** goto D +** B: open write cursor to
    and its indices +** goto A +** C: insert the select result into
    +** return +** D: cleanup +** +** The third template is used if the insert statement takes its +** values from a SELECT but the data is being inserted into a table +** that is also read as part of the SELECT. In the third form, +** we have to use a intermediate table to store the results of +** the select. The template is like this: +** +** goto B +** A: setup for the SELECT +** loop over the tables in the SELECT +** gosub C +** end loop +** cleanup after the SELECT +** goto D +** C: insert the select result into the intermediate table +** return +** B: open a cursor to an intermediate table +** goto A +** D: open write cursor to
    and its indices +** loop over the intermediate table +** transfer values form intermediate table into
    +** end the loop +** cleanup +*/ +void sqlite3Insert( + Parse *pParse, /* Parser context */ + SrcList *pTabList, /* Name of table into which we are inserting */ + ExprList *pList, /* List of values to be inserted */ + Select *pSelect, /* A SELECT statement to use as the data source */ + IdList *pColumn, /* Column names corresponding to IDLIST. */ + int onError /* How to handle constraint errors */ +){ + Table *pTab; /* The table to insert into */ + char *zTab; /* Name of the table into which we are inserting */ + const char *zDb; /* Name of the database holding this table */ + int i, j, idx; /* Loop counters */ + Vdbe *v; /* Generate code into this virtual machine */ + Index *pIdx; /* For looping over indices of the table */ + int nColumn; /* Number of columns in the data */ + int base = 0; /* VDBE Cursor number for pTab */ + int iCont=0,iBreak=0; /* Beginning and end of the loop over srcTab */ + sqlite3 *db; /* The main database structure */ + int keyColumn = -1; /* Column that is the INTEGER PRIMARY KEY */ + int endOfLoop; /* Label for the end of the insertion loop */ + int useTempTable; /* Store SELECT results in intermediate table */ + int srcTab = 0; /* Data comes from this temporary cursor if >=0 */ + int iSelectLoop = 0; /* Address of code that implements the SELECT */ + int iCleanup = 0; /* Address of the cleanup code */ + int iInsertBlock = 0; /* Address of the subroutine used to insert data */ + int iCntMem = 0; /* Memory cell used for the row counter */ + int isView; /* True if attempting to insert into a view */ + + int row_triggers_exist = 0; /* True if there are FOR EACH ROW triggers */ + int before_triggers; /* True if there are BEFORE triggers */ + int after_triggers; /* True if there are AFTER triggers */ + int newIdx = -1; /* Cursor for the NEW table */ + + if( pParse->nErr || sqlite3_malloc_failed ) goto insert_cleanup; + db = pParse->db; + + /* Locate the table into which we will be inserting new information. + */ + assert( pTabList->nSrc==1 ); + zTab = pTabList->a[0].zName; + if( zTab==0 ) goto insert_cleanup; + pTab = sqlite3SrcListLookup(pParse, pTabList); + if( pTab==0 ){ + goto insert_cleanup; + } + assert( pTab->iDbnDb ); + zDb = db->aDb[pTab->iDb].zName; + if( sqlite3AuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, zDb) ){ + goto insert_cleanup; + } + + /* Ensure that: + * (a) the table is not read-only, + * (b) that if it is a view then ON INSERT triggers exist + */ + before_triggers = sqlite3TriggersExist(pParse, pTab->pTrigger, TK_INSERT, + TK_BEFORE, TK_ROW, 0); + after_triggers = sqlite3TriggersExist(pParse, pTab->pTrigger, TK_INSERT, + TK_AFTER, TK_ROW, 0); + row_triggers_exist = before_triggers || after_triggers; + isView = pTab->pSelect!=0; + if( sqlite3IsReadOnly(pParse, pTab, before_triggers) ){ + goto insert_cleanup; + } + if( pTab==0 ) goto insert_cleanup; + + /* If pTab is really a view, make sure it has been initialized. + */ + if( isView && sqlite3ViewGetColumnNames(pParse, pTab) ){ + goto insert_cleanup; + } + + /* Ensure all required collation sequences are available. */ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( sqlite3CheckIndexCollSeq(pParse, pIdx) ){ + goto insert_cleanup; + } + } + + /* Allocate a VDBE + */ + v = sqlite3GetVdbe(pParse); + if( v==0 ) goto insert_cleanup; + sqlite3VdbeCountChanges(v); + sqlite3BeginWriteOperation(pParse, pSelect || row_triggers_exist, pTab->iDb); + + /* if there are row triggers, allocate a temp table for new.* references. */ + if( row_triggers_exist ){ + newIdx = pParse->nTab++; + } + + /* Figure out how many columns of data are supplied. If the data + ** is coming from a SELECT statement, then this step also generates + ** all the code to implement the SELECT statement and invoke a subroutine + ** to process each row of the result. (Template 2.) If the SELECT + ** statement uses the the table that is being inserted into, then the + ** subroutine is also coded here. That subroutine stores the SELECT + ** results in a temporary table. (Template 3.) + */ + if( pSelect ){ + /* Data is coming from a SELECT. Generate code to implement that SELECT + */ + int rc, iInitCode; + iInitCode = sqlite3VdbeAddOp(v, OP_Goto, 0, 0); + iSelectLoop = sqlite3VdbeCurrentAddr(v); + iInsertBlock = sqlite3VdbeMakeLabel(v); + rc = sqlite3Select(pParse, pSelect, SRT_Subroutine, iInsertBlock, 0,0,0,0); + if( rc || pParse->nErr || sqlite3_malloc_failed ) goto insert_cleanup; + iCleanup = sqlite3VdbeMakeLabel(v); + sqlite3VdbeAddOp(v, OP_Goto, 0, iCleanup); + assert( pSelect->pEList ); + nColumn = pSelect->pEList->nExpr; + + /* Set useTempTable to TRUE if the result of the SELECT statement + ** should be written into a temporary table. Set to FALSE if each + ** row of the SELECT can be written directly into the result table. + ** + ** A temp table must be used if the table being updated is also one + ** of the tables being read by the SELECT statement. Also use a + ** temp table in the case of row triggers. + */ + if( row_triggers_exist ){ + useTempTable = 1; + }else{ + int addr = 0; + useTempTable = 0; + while( useTempTable==0 ){ + VdbeOp *pOp; + addr = sqlite3VdbeFindOp(v, addr, OP_OpenRead, pTab->tnum); + if( addr==0 ) break; + pOp = sqlite3VdbeGetOp(v, addr-2); + if( pOp->opcode==OP_Integer && pOp->p1==pTab->iDb ){ + useTempTable = 1; + } + } + } + + if( useTempTable ){ + /* Generate the subroutine that SELECT calls to process each row of + ** the result. Store the result in a temporary table + */ + srcTab = pParse->nTab++; + sqlite3VdbeResolveLabel(v, iInsertBlock); + sqlite3VdbeAddOp(v, OP_MakeRecord, nColumn, 0); + sqlite3TableAffinityStr(v, pTab); + sqlite3VdbeAddOp(v, OP_NewRecno, srcTab, 0); + sqlite3VdbeAddOp(v, OP_Pull, 1, 0); + sqlite3VdbeAddOp(v, OP_PutIntKey, srcTab, 0); + sqlite3VdbeAddOp(v, OP_Return, 0, 0); + + /* The following code runs first because the GOTO at the very top + ** of the program jumps to it. Create the temporary table, then jump + ** back up and execute the SELECT code above. + */ + sqlite3VdbeChangeP2(v, iInitCode, sqlite3VdbeCurrentAddr(v)); + sqlite3VdbeAddOp(v, OP_OpenTemp, srcTab, 0); + sqlite3VdbeAddOp(v, OP_SetNumColumns, srcTab, nColumn); + sqlite3VdbeAddOp(v, OP_Goto, 0, iSelectLoop); + sqlite3VdbeResolveLabel(v, iCleanup); + }else{ + sqlite3VdbeChangeP2(v, iInitCode, sqlite3VdbeCurrentAddr(v)); + } + }else{ + /* This is the case if the data for the INSERT is coming from a VALUES + ** clause + */ + SrcList dummy; + assert( pList!=0 ); + srcTab = -1; + useTempTable = 0; + assert( pList ); + nColumn = pList->nExpr; + dummy.nSrc = 0; + for(i=0; ia[i].pExpr,0,0) ){ + goto insert_cleanup; + } + } + } + + /* Make sure the number of columns in the source data matches the number + ** of columns to be inserted into the table. + */ + if( pColumn==0 && nColumn!=pTab->nCol ){ + sqlite3ErrorMsg(pParse, + "table %S has %d columns but %d values were supplied", + pTabList, 0, pTab->nCol, nColumn); + goto insert_cleanup; + } + if( pColumn!=0 && nColumn!=pColumn->nId ){ + sqlite3ErrorMsg(pParse, "%d values for %d columns", nColumn, pColumn->nId); + goto insert_cleanup; + } + + /* If the INSERT statement included an IDLIST term, then make sure + ** all elements of the IDLIST really are columns of the table and + ** remember the column indices. + ** + ** If the table has an INTEGER PRIMARY KEY column and that column + ** is named in the IDLIST, then record in the keyColumn variable + ** the index into IDLIST of the primary key column. keyColumn is + ** the index of the primary key as it appears in IDLIST, not as + ** is appears in the original table. (The index of the primary + ** key in the original table is pTab->iPKey.) + */ + if( pColumn ){ + for(i=0; inId; i++){ + pColumn->a[i].idx = -1; + } + for(i=0; inId; i++){ + for(j=0; jnCol; j++){ + if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zName)==0 ){ + pColumn->a[i].idx = j; + if( j==pTab->iPKey ){ + keyColumn = i; + } + break; + } + } + if( j>=pTab->nCol ){ + if( sqlite3IsRowid(pColumn->a[i].zName) ){ + keyColumn = i; + }else{ + sqlite3ErrorMsg(pParse, "table %S has no column named %s", + pTabList, 0, pColumn->a[i].zName); + pParse->nErr++; + goto insert_cleanup; + } + } + } + } + + /* If there is no IDLIST term but the table has an integer primary + ** key, the set the keyColumn variable to the primary key column index + ** in the original table definition. + */ + if( pColumn==0 ){ + keyColumn = pTab->iPKey; + } + + /* Open the temp table for FOR EACH ROW triggers + */ + if( row_triggers_exist ){ + sqlite3VdbeAddOp(v, OP_OpenPseudo, newIdx, 0); + sqlite3VdbeAddOp(v, OP_SetNumColumns, newIdx, pTab->nCol); + } + + /* Initialize the count of rows to be inserted + */ + if( db->flags & SQLITE_CountRows ){ + iCntMem = pParse->nMem++; + sqlite3VdbeAddOp(v, OP_Integer, 0, 0); + sqlite3VdbeAddOp(v, OP_MemStore, iCntMem, 1); + } + + /* Open tables and indices if there are no row triggers */ + if( !row_triggers_exist ){ + base = pParse->nTab; + sqlite3OpenTableAndIndices(pParse, pTab, base, OP_OpenWrite); + } + + /* If the data source is a temporary table, then we have to create + ** a loop because there might be multiple rows of data. If the data + ** source is a subroutine call from the SELECT statement, then we need + ** to launch the SELECT statement processing. + */ + if( useTempTable ){ + iBreak = sqlite3VdbeMakeLabel(v); + sqlite3VdbeAddOp(v, OP_Rewind, srcTab, iBreak); + iCont = sqlite3VdbeCurrentAddr(v); + }else if( pSelect ){ + sqlite3VdbeAddOp(v, OP_Goto, 0, iSelectLoop); + sqlite3VdbeResolveLabel(v, iInsertBlock); + } + + /* Run the BEFORE and INSTEAD OF triggers, if there are any + */ + endOfLoop = sqlite3VdbeMakeLabel(v); + if( before_triggers ){ + + /* build the NEW.* reference row. Note that if there is an INTEGER + ** PRIMARY KEY into which a NULL is being inserted, that NULL will be + ** translated into a unique ID for the row. But on a BEFORE trigger, + ** we do not know what the unique ID will be (because the insert has + ** not happened yet) so we substitute a rowid of -1 + */ + if( keyColumn<0 ){ + sqlite3VdbeAddOp(v, OP_Integer, -1, 0); + }else if( useTempTable ){ + sqlite3VdbeAddOp(v, OP_Column, srcTab, keyColumn); + }else if( pSelect ){ + sqlite3VdbeAddOp(v, OP_Dup, nColumn - keyColumn - 1, 1); + }else{ + sqlite3ExprCode(pParse, pList->a[keyColumn].pExpr); + sqlite3VdbeAddOp(v, OP_NotNull, -1, sqlite3VdbeCurrentAddr(v)+3); + sqlite3VdbeAddOp(v, OP_Pop, 1, 0); + sqlite3VdbeAddOp(v, OP_Integer, -1, 0); + sqlite3VdbeAddOp(v, OP_MustBeInt, 0, 0); + } + + /* Create the new column data + */ + for(i=0; inCol; i++){ + if( pColumn==0 ){ + j = i; + }else{ + for(j=0; jnId; j++){ + if( pColumn->a[j].idx==i ) break; + } + } + if( pColumn && j>=pColumn->nId ){ + sqlite3VdbeOp3(v, OP_String8, 0, 0, pTab->aCol[i].zDflt, P3_STATIC); + }else if( useTempTable ){ + sqlite3VdbeAddOp(v, OP_Column, srcTab, j); + }else if( pSelect ){ + sqlite3VdbeAddOp(v, OP_Dup, nColumn-j-1, 1); + }else{ + sqlite3ExprCode(pParse, pList->a[j].pExpr); + } + } + sqlite3VdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); + + /* If this is an INSERT on a view with an INSTEAD OF INSERT trigger, + ** do not attempt any conversions before assembling the record. + ** If this is a real table, attempt conversions as required by the + ** table column affinities. + */ + if( !isView ){ + sqlite3TableAffinityStr(v, pTab); + } + sqlite3VdbeAddOp(v, OP_PutIntKey, newIdx, 0); + + /* Fire BEFORE or INSTEAD OF triggers */ + if( sqlite3CodeRowTrigger(pParse, TK_INSERT, 0, TK_BEFORE, pTab, + newIdx, -1, onError, endOfLoop) ){ + goto insert_cleanup; + } + } + + /* If any triggers exists, the opening of tables and indices is deferred + ** until now. + */ + if( row_triggers_exist && !isView ){ + base = pParse->nTab; + sqlite3OpenTableAndIndices(pParse, pTab, base, OP_OpenWrite); + } + + /* Push the record number for the new entry onto the stack. The + ** record number is a randomly generate integer created by NewRecno + ** except when the table has an INTEGER PRIMARY KEY column, in which + ** case the record number is the same as that column. + */ + if( !isView ){ + if( keyColumn>=0 ){ + if( useTempTable ){ + sqlite3VdbeAddOp(v, OP_Column, srcTab, keyColumn); + }else if( pSelect ){ + sqlite3VdbeAddOp(v, OP_Dup, nColumn - keyColumn - 1, 1); + }else{ + sqlite3ExprCode(pParse, pList->a[keyColumn].pExpr); + } + /* If the PRIMARY KEY expression is NULL, then use OP_NewRecno + ** to generate a unique primary key value. + */ + sqlite3VdbeAddOp(v, OP_NotNull, -1, sqlite3VdbeCurrentAddr(v)+3); + sqlite3VdbeAddOp(v, OP_Pop, 1, 0); + sqlite3VdbeAddOp(v, OP_NewRecno, base, 0); + sqlite3VdbeAddOp(v, OP_MustBeInt, 0, 0); + }else{ + sqlite3VdbeAddOp(v, OP_NewRecno, base, 0); + } + + /* Push onto the stack, data for all columns of the new entry, beginning + ** with the first column. + */ + for(i=0; inCol; i++){ + if( i==pTab->iPKey ){ + /* The value of the INTEGER PRIMARY KEY column is always a NULL. + ** Whenever this column is read, the record number will be substituted + ** in its place. So will fill this column with a NULL to avoid + ** taking up data space with information that will never be used. */ + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + continue; + } + if( pColumn==0 ){ + j = i; + }else{ + for(j=0; jnId; j++){ + if( pColumn->a[j].idx==i ) break; + } + } + if( pColumn && j>=pColumn->nId ){ + sqlite3VdbeOp3(v, OP_String8, 0, 0, pTab->aCol[i].zDflt, P3_STATIC); + }else if( useTempTable ){ + sqlite3VdbeAddOp(v, OP_Column, srcTab, j); + }else if( pSelect ){ + sqlite3VdbeAddOp(v, OP_Dup, i+nColumn-j, 1); + }else{ + sqlite3ExprCode(pParse, pList->a[j].pExpr); + } + } + + /* Generate code to check constraints and generate index keys and + ** do the insertion. + */ + sqlite3GenerateConstraintChecks(pParse, pTab, base, 0, keyColumn>=0, + 0, onError, endOfLoop); + sqlite3CompleteInsertion(pParse, pTab, base, 0,0,0, + after_triggers ? newIdx : -1); + } + + /* Update the count of rows that are inserted + */ + if( (db->flags & SQLITE_CountRows)!=0 ){ + sqlite3VdbeAddOp(v, OP_MemIncr, iCntMem, 0); + } + + if( row_triggers_exist ){ + /* Close all tables opened */ + if( !isView ){ + sqlite3VdbeAddOp(v, OP_Close, base, 0); + for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){ + sqlite3VdbeAddOp(v, OP_Close, idx+base, 0); + } + } + + /* Code AFTER triggers */ + if( sqlite3CodeRowTrigger(pParse, TK_INSERT, 0, TK_AFTER, pTab, newIdx, -1, + onError, endOfLoop) ){ + goto insert_cleanup; + } + } + + /* The bottom of the loop, if the data source is a SELECT statement + */ + sqlite3VdbeResolveLabel(v, endOfLoop); + if( useTempTable ){ + sqlite3VdbeAddOp(v, OP_Next, srcTab, iCont); + sqlite3VdbeResolveLabel(v, iBreak); + sqlite3VdbeAddOp(v, OP_Close, srcTab, 0); + }else if( pSelect ){ + sqlite3VdbeAddOp(v, OP_Pop, nColumn, 0); + sqlite3VdbeAddOp(v, OP_Return, 0, 0); + sqlite3VdbeResolveLabel(v, iCleanup); + } + + if( !row_triggers_exist ){ + /* Close all tables opened */ + sqlite3VdbeAddOp(v, OP_Close, base, 0); + for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){ + sqlite3VdbeAddOp(v, OP_Close, idx+base, 0); + } + } + + /* + ** Return the number of rows inserted. + */ + if( db->flags & SQLITE_CountRows ){ + sqlite3VdbeAddOp(v, OP_MemLoad, iCntMem, 0); + sqlite3VdbeAddOp(v, OP_Callback, 1, 0); + sqlite3VdbeSetNumCols(v, 1); + sqlite3VdbeSetColName(v, 0, "rows inserted", P3_STATIC); + } + +insert_cleanup: + sqlite3SrcListDelete(pTabList); + if( pList ) sqlite3ExprListDelete(pList); + if( pSelect ) sqlite3SelectDelete(pSelect); + sqlite3IdListDelete(pColumn); +} + +/* +** Generate code to do a constraint check prior to an INSERT or an UPDATE. +** +** When this routine is called, the stack contains (from bottom to top) +** the following values: +** +** 1. The recno of the row to be updated before the update. This +** value is omitted unless we are doing an UPDATE that involves a +** change to the record number. +** +** 2. The recno of the row after the update. +** +** 3. The data in the first column of the entry after the update. +** +** i. Data from middle columns... +** +** N. The data in the last column of the entry after the update. +** +** The old recno shown as entry (1) above is omitted unless both isUpdate +** and recnoChng are 1. isUpdate is true for UPDATEs and false for +** INSERTs and recnoChng is true if the record number is being changed. +** +** The code generated by this routine pushes additional entries onto +** the stack which are the keys for new index entries for the new record. +** The order of index keys is the same as the order of the indices on +** the pTable->pIndex list. A key is only created for index i if +** aIdxUsed!=0 and aIdxUsed[i]!=0. +** +** This routine also generates code to check constraints. NOT NULL, +** CHECK, and UNIQUE constraints are all checked. If a constraint fails, +** then the appropriate action is performed. There are five possible +** actions: ROLLBACK, ABORT, FAIL, REPLACE, and IGNORE. +** +** Constraint type Action What Happens +** --------------- ---------- ---------------------------------------- +** any ROLLBACK The current transaction is rolled back and +** sqlite3_exec() returns immediately with a +** return code of SQLITE_CONSTRAINT. +** +** any ABORT Back out changes from the current command +** only (do not do a complete rollback) then +** cause sqlite3_exec() to return immediately +** with SQLITE_CONSTRAINT. +** +** any FAIL Sqlite_exec() returns immediately with a +** return code of SQLITE_CONSTRAINT. The +** transaction is not rolled back and any +** prior changes are retained. +** +** any IGNORE The record number and data is popped from +** the stack and there is an immediate jump +** to label ignoreDest. +** +** NOT NULL REPLACE The NULL value is replace by the default +** value for that column. If the default value +** is NULL, the action is the same as ABORT. +** +** UNIQUE REPLACE The other row that conflicts with the row +** being inserted is removed. +** +** CHECK REPLACE Illegal. The results in an exception. +** +** Which action to take is determined by the overrideError parameter. +** Or if overrideError==OE_Default, then the pParse->onError parameter +** is used. Or if pParse->onError==OE_Default then the onError value +** for the constraint is used. +** +** The calling routine must open a read/write cursor for pTab with +** cursor number "base". All indices of pTab must also have open +** read/write cursors with cursor number base+i for the i-th cursor. +** Except, if there is no possibility of a REPLACE action then +** cursors do not need to be open for indices where aIdxUsed[i]==0. +** +** If the isUpdate flag is true, it means that the "base" cursor is +** initially pointing to an entry that is being updated. The isUpdate +** flag causes extra code to be generated so that the "base" cursor +** is still pointing at the same entry after the routine returns. +** Without the isUpdate flag, the "base" cursor might be moved. +*/ +void sqlite3GenerateConstraintChecks( + Parse *pParse, /* The parser context */ + Table *pTab, /* the table into which we are inserting */ + int base, /* Index of a read/write cursor pointing at pTab */ + char *aIdxUsed, /* Which indices are used. NULL means all are used */ + int recnoChng, /* True if the record number will change */ + int isUpdate, /* True for UPDATE, False for INSERT */ + int overrideError, /* Override onError to this if not OE_Default */ + int ignoreDest /* Jump to this label on an OE_Ignore resolution */ +){ + int i; + Vdbe *v; + int nCol; + int onError; + int addr; + int extra; + int iCur; + Index *pIdx; + int seenReplace = 0; + int jumpInst1=0, jumpInst2; + int contAddr; + int hasTwoRecnos = (isUpdate && recnoChng); + + v = sqlite3GetVdbe(pParse); + assert( v!=0 ); + assert( pTab->pSelect==0 ); /* This table is not a VIEW */ + nCol = pTab->nCol; + + /* Test all NOT NULL constraints. + */ + for(i=0; iiPKey ){ + continue; + } + onError = pTab->aCol[i].notNull; + if( onError==OE_None ) continue; + if( overrideError!=OE_Default ){ + onError = overrideError; + }else if( onError==OE_Default ){ + onError = OE_Abort; + } + if( onError==OE_Replace && pTab->aCol[i].zDflt==0 ){ + onError = OE_Abort; + } + sqlite3VdbeAddOp(v, OP_Dup, nCol-1-i, 1); + addr = sqlite3VdbeAddOp(v, OP_NotNull, 1, 0); + switch( onError ){ + case OE_Rollback: + case OE_Abort: + case OE_Fail: { + char *zMsg = 0; + sqlite3VdbeAddOp(v, OP_Halt, SQLITE_CONSTRAINT, onError); + sqlite3SetString(&zMsg, pTab->zName, ".", pTab->aCol[i].zName, + " may not be NULL", (char*)0); + sqlite3VdbeChangeP3(v, -1, zMsg, P3_DYNAMIC); + break; + } + case OE_Ignore: { + sqlite3VdbeAddOp(v, OP_Pop, nCol+1+hasTwoRecnos, 0); + sqlite3VdbeAddOp(v, OP_Goto, 0, ignoreDest); + break; + } + case OE_Replace: { + sqlite3VdbeOp3(v, OP_String8, 0, 0, pTab->aCol[i].zDflt, P3_STATIC); + sqlite3VdbeAddOp(v, OP_Push, nCol-i, 0); + break; + } + default: assert(0); + } + sqlite3VdbeChangeP2(v, addr, sqlite3VdbeCurrentAddr(v)); + } + + /* Test all CHECK constraints + */ + /**** TBD ****/ + + /* If we have an INTEGER PRIMARY KEY, make sure the primary key + ** of the new record does not previously exist. Except, if this + ** is an UPDATE and the primary key is not changing, that is OK. + */ + if( recnoChng ){ + onError = pTab->keyConf; + if( overrideError!=OE_Default ){ + onError = overrideError; + }else if( onError==OE_Default ){ + onError = OE_Abort; + } + + if( isUpdate ){ + sqlite3VdbeAddOp(v, OP_Dup, nCol+1, 1); + sqlite3VdbeAddOp(v, OP_Dup, nCol+1, 1); + jumpInst1 = sqlite3VdbeAddOp(v, OP_Eq, 0, 0); + } + sqlite3VdbeAddOp(v, OP_Dup, nCol, 1); + jumpInst2 = sqlite3VdbeAddOp(v, OP_NotExists, base, 0); + switch( onError ){ + default: { + onError = OE_Abort; + /* Fall thru into the next case */ + } + case OE_Rollback: + case OE_Abort: + case OE_Fail: { + sqlite3VdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, onError, + "PRIMARY KEY must be unique", P3_STATIC); + break; + } + case OE_Replace: { + sqlite3GenerateRowIndexDelete(pParse->db, v, pTab, base, 0); + if( isUpdate ){ + sqlite3VdbeAddOp(v, OP_Dup, nCol+hasTwoRecnos, 1); + sqlite3VdbeAddOp(v, OP_MoveGe, base, 0); + } + seenReplace = 1; + break; + } + case OE_Ignore: { + assert( seenReplace==0 ); + sqlite3VdbeAddOp(v, OP_Pop, nCol+1+hasTwoRecnos, 0); + sqlite3VdbeAddOp(v, OP_Goto, 0, ignoreDest); + break; + } + } + contAddr = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeChangeP2(v, jumpInst2, contAddr); + if( isUpdate ){ + sqlite3VdbeChangeP2(v, jumpInst1, contAddr); + sqlite3VdbeAddOp(v, OP_Dup, nCol+1, 1); + sqlite3VdbeAddOp(v, OP_MoveGe, base, 0); + } + } + + /* Test all UNIQUE constraints by creating entries for each UNIQUE + ** index and making sure that duplicate entries do not already exist. + ** Add the new records to the indices as we go. + */ + extra = -1; + for(iCur=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, iCur++){ + if( aIdxUsed && aIdxUsed[iCur]==0 ) continue; /* Skip unused indices */ + extra++; + + /* Create a key for accessing the index entry */ + sqlite3VdbeAddOp(v, OP_Dup, nCol+extra, 1); + for(i=0; inColumn; i++){ + int idx = pIdx->aiColumn[i]; + if( idx==pTab->iPKey ){ + sqlite3VdbeAddOp(v, OP_Dup, i+extra+nCol+1, 1); + }else{ + sqlite3VdbeAddOp(v, OP_Dup, i+extra+nCol-idx, 1); + } + } + jumpInst1 = sqlite3VdbeAddOp(v, OP_MakeRecord, pIdx->nColumn, (1<<24)); + sqlite3IndexAffinityStr(v, pIdx); + + /* Find out what action to take in case there is an indexing conflict */ + onError = pIdx->onError; + if( onError==OE_None ) continue; /* pIdx is not a UNIQUE index */ + if( overrideError!=OE_Default ){ + onError = overrideError; + }else if( onError==OE_Default ){ + onError = OE_Abort; + } + if( seenReplace ){ + if( onError==OE_Ignore ) onError = OE_Replace; + else if( onError==OE_Fail ) onError = OE_Abort; + } + + + /* Check to see if the new index entry will be unique */ + sqlite3VdbeAddOp(v, OP_Dup, extra+nCol+1+hasTwoRecnos, 1); + jumpInst2 = sqlite3VdbeAddOp(v, OP_IsUnique, base+iCur+1, 0); + + /* Generate code that executes if the new index entry is not unique */ + switch( onError ){ + case OE_Rollback: + case OE_Abort: + case OE_Fail: { + int j, n1, n2; + char zErrMsg[200]; + strcpy(zErrMsg, pIdx->nColumn>1 ? "columns " : "column "); + n1 = strlen(zErrMsg); + for(j=0; jnColumn && n1aCol[pIdx->aiColumn[j]].zName; + n2 = strlen(zCol); + if( j>0 ){ + strcpy(&zErrMsg[n1], ", "); + n1 += 2; + } + if( n1+n2>sizeof(zErrMsg)-30 ){ + strcpy(&zErrMsg[n1], "..."); + n1 += 3; + break; + }else{ + strcpy(&zErrMsg[n1], zCol); + n1 += n2; + } + } + strcpy(&zErrMsg[n1], + pIdx->nColumn>1 ? " are not unique" : " is not unique"); + sqlite3VdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, onError, zErrMsg, 0); + break; + } + case OE_Ignore: { + assert( seenReplace==0 ); + sqlite3VdbeAddOp(v, OP_Pop, nCol+extra+3+hasTwoRecnos, 0); + sqlite3VdbeAddOp(v, OP_Goto, 0, ignoreDest); + break; + } + case OE_Replace: { + sqlite3GenerateRowDelete(pParse->db, v, pTab, base, 0); + if( isUpdate ){ + sqlite3VdbeAddOp(v, OP_Dup, nCol+extra+1+hasTwoRecnos, 1); + sqlite3VdbeAddOp(v, OP_MoveGe, base, 0); + } + seenReplace = 1; + break; + } + default: assert(0); + } + contAddr = sqlite3VdbeCurrentAddr(v); + assert( contAddr<(1<<24) ); +#if NULL_DISTINCT_FOR_UNIQUE + sqlite3VdbeChangeP2(v, jumpInst1, contAddr | (1<<24)); +#endif + sqlite3VdbeChangeP2(v, jumpInst2, contAddr); + } +} + +/* +** This routine generates code to finish the INSERT or UPDATE operation +** that was started by a prior call to sqlite3GenerateConstraintChecks. +** The stack must contain keys for all active indices followed by data +** and the recno for the new entry. This routine creates the new +** entries in all indices and in the main table. +** +** The arguments to this routine should be the same as the first six +** arguments to sqlite3GenerateConstraintChecks. +*/ +void sqlite3CompleteInsertion( + Parse *pParse, /* The parser context */ + Table *pTab, /* the table into which we are inserting */ + int base, /* Index of a read/write cursor pointing at pTab */ + char *aIdxUsed, /* Which indices are used. NULL means all are used */ + int recnoChng, /* True if the record number will change */ + int isUpdate, /* True for UPDATE, False for INSERT */ + int newIdx /* Index of NEW table for triggers. -1 if none */ +){ + int i; + Vdbe *v; + int nIdx; + Index *pIdx; + int pik_flags; + + v = sqlite3GetVdbe(pParse); + assert( v!=0 ); + assert( pTab->pSelect==0 ); /* This table is not a VIEW */ + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){} + for(i=nIdx-1; i>=0; i--){ + if( aIdxUsed && aIdxUsed[i]==0 ) continue; + sqlite3VdbeAddOp(v, OP_IdxPut, base+i+1, 0); + } + sqlite3VdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); + sqlite3TableAffinityStr(v, pTab); + if( newIdx>=0 ){ + sqlite3VdbeAddOp(v, OP_Dup, 1, 0); + sqlite3VdbeAddOp(v, OP_Dup, 1, 0); + sqlite3VdbeAddOp(v, OP_PutIntKey, newIdx, 0); + } + pik_flags = (OPFLAG_NCHANGE|(isUpdate?0:OPFLAG_LASTROWID)); + sqlite3VdbeAddOp(v, OP_PutIntKey, base, pik_flags); + + if( isUpdate && recnoChng ){ + sqlite3VdbeAddOp(v, OP_Pop, 1, 0); + } +} + +/* +** Generate code that will open cursors for a table and for all +** indices of that table. The "base" parameter is the cursor number used +** for the table. Indices are opened on subsequent cursors. +*/ +void sqlite3OpenTableAndIndices( + Parse *pParse, /* Parsing context */ + Table *pTab, /* Table to be opened */ + int base, /* Cursor number assigned to the table */ + int op /* OP_OpenRead or OP_OpenWrite */ +){ + int i; + Index *pIdx; + Vdbe *v = sqlite3GetVdbe(pParse); + assert( v!=0 ); + sqlite3VdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqlite3VdbeAddOp(v, op, base, pTab->tnum); + VdbeComment((v, "# %s", pTab->zName)); + sqlite3VdbeAddOp(v, OP_SetNumColumns, base, pTab->nCol); + for(i=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + sqlite3VdbeAddOp(v, OP_Integer, pIdx->iDb, 0); + sqlite3VdbeOp3(v, op, i+base, pIdx->tnum, + (char*)&pIdx->keyInfo, P3_KEYINFO); + } + if( pParse->nTab<=base+i ){ + pParse->nTab = base+i; + } +} diff --git a/kopete/plugins/statistics/sqlite/legacy.c b/kopete/plugins/statistics/sqlite/legacy.c new file mode 100644 index 00000000..f575f1f0 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/legacy.c @@ -0,0 +1,138 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Main file for the SQLite library. The routines in this file +** implement the programmer interface to the library. Routines in +** other files are for internal use by SQLite and should not be +** accessed by users of the library. +** +** $Id$ +*/ + +#include "sqliteInt.h" +#include "os.h" +#include + +/* +** Execute SQL code. Return one of the SQLITE_ success/failure +** codes. Also write an error message into memory obtained from +** malloc() and make *pzErrMsg point to that message. +** +** If the SQL is a query, then for each row in the query result +** the xCallback() function is called. pArg becomes the first +** argument to xCallback(). If xCallback=NULL then no callback +** is invoked, even for queries. +*/ +int sqlite3_exec( + sqlite3 *db, /* The database on which the SQL executes */ + const char *zSql, /* The SQL to be executed */ + sqlite3_callback xCallback, /* Invoke this callback routine */ + void *pArg, /* First argument to xCallback() */ + char **pzErrMsg /* Write error messages here */ +){ + int rc = SQLITE_OK; + const char *zLeftover; + sqlite3_stmt *pStmt = 0; + char **azCols = 0; + + int nRetry = 0; + int nChange = 0; + int nCallback; + + if( zSql==0 ) return SQLITE_OK; + while( (rc==SQLITE_OK || (rc==SQLITE_SCHEMA && (++nRetry)<2)) && zSql[0] ){ + int nCol; + char **azVals = 0; + + pStmt = 0; + rc = sqlite3_prepare(db, zSql, -1, &pStmt, &zLeftover); + if( rc!=SQLITE_OK ){ + if( pStmt ) sqlite3_finalize(pStmt); + continue; + } + if( !pStmt ){ + /* this happens for a comment or white-space */ + zSql = zLeftover; + continue; + } + + db->nChange += nChange; + nCallback = 0; + + nCol = sqlite3_column_count(pStmt); + azCols = sqliteMalloc(2*nCol*sizeof(const char *)); + if( nCol && !azCols ){ + rc = SQLITE_NOMEM; + goto exec_out; + } + + while( 1 ){ + int i; + rc = sqlite3_step(pStmt); + + /* Invoke the callback function if required */ + if( xCallback && (SQLITE_ROW==rc || + (SQLITE_DONE==rc && !nCallback && db->flags&SQLITE_NullCallback)) ){ + if( 0==nCallback ){ + for(i=0; ipVdbe==0 ){ + nChange = db->nChange; + } + if( rc!=SQLITE_SCHEMA ){ + nRetry = 0; + zSql = zLeftover; + while( isspace((unsigned char)zSql[0]) ) zSql++; + } + break; + } + } + + sqliteFree(azCols); + azCols = 0; + } + +exec_out: + if( pStmt ) sqlite3_finalize(pStmt); + if( azCols ) sqliteFree(azCols); + + if( sqlite3_malloc_failed ){ + rc = SQLITE_NOMEM; + } + if( rc!=SQLITE_OK && rc==sqlite3_errcode(db) && pzErrMsg ){ + *pzErrMsg = malloc(1+strlen(sqlite3_errmsg(db))); + if( *pzErrMsg ){ + strcpy(*pzErrMsg, sqlite3_errmsg(db)); + } + }else if( pzErrMsg ){ + *pzErrMsg = 0; + } + + return rc; +} diff --git a/kopete/plugins/statistics/sqlite/lempar.c b/kopete/plugins/statistics/sqlite/lempar.c new file mode 100644 index 00000000..ee1edbfa --- /dev/null +++ b/kopete/plugins/statistics/sqlite/lempar.c @@ -0,0 +1,687 @@ +/* Driver template for the LEMON parser generator. +** The author disclaims copyright to this source code. +*/ +/* First off, code is include which follows the "include" declaration +** in the input file. */ +#include +%% +/* Next is all token values, in a form suitable for use by makeheaders. +** This section will be null unless lemon is run with the -m switch. +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ +%% +/* Make sure the INTERFACE macro is defined. +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** YYCODETYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 terminals +** and nonterminals. "int" is used otherwise. +** YYNOCODE is a number of type YYCODETYPE which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** YYACTIONTYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 rules and +** states combined. "int" is used otherwise. +** ParseTOKENTYPE is the data type used for minor tokens given +** directly to the parser from the tokenizer. +** YYMINORTYPE is the data type used for all minor tokens. +** This is typically a union of many types, one of +** which is ParseTOKENTYPE. The entry in the union +** for base tokens is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. +** ParseARG_SDECL A static variable declaration for the %extra_argument +** ParseARG_PDECL A parameter declaration for the %extra_argument +** ParseARG_STORE Code to store %extra_argument into yypParser +** ParseARG_FETCH Code to extract %extra_argument from yypParser +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ +%% +#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) +#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) +#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) + +/* Next are that tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < YYNSTATE Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. +** +** N == YYNSTATE+YYNRULE A syntax error has occurred. +** +** N == YYNSTATE+YYNRULE+1 The parser accepts its input. +** +** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as +** +** yy_action[ yy_shift_ofst[S] + X ] +** +** If the index value yy_shift_ofst[S]+X is out of range or if the value +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] +** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table +** and that yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of +** YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +*/ +%% +#define YY_SZ_ACTTAB (sizeof(yy_action)/sizeof(yy_action[0])) + +/* The next table maps tokens into fallback tokens. If a construct +** like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammer, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { +%% +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +*/ +struct yyStackEntry { + int stateno; /* The state-number */ + int major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + int yyidx; /* Index of top element in stack */ + int yyerrcnt; /* Shifts left before out of the error */ + ParseARG_SDECL /* A place to hold %extra_argument */ + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +**
      +**
    • A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +**
    • A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +**
    +** +** Outputs: +** None. +*/ +void ParseTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *yyTokenName[] = { +%% +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *yyRuleName[] = { +%% +}; +#endif /* NDEBUG */ + +/* +** This function returns the symbolic name associated with a token +** value. +*/ +const char *ParseTokenName(int tokenType){ +#ifndef NDEBUG + if( tokenType>0 && tokenType<(sizeof(yyTokenName)/sizeof(yyTokenName[0])) ){ + return yyTokenName[tokenType]; + }else{ + return "Unknown"; + } +#else + return ""; +#endif +} + +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to Parse and ParseFree. +*/ +void *ParseAlloc(void *(*mallocProc)(size_t)){ + yyParser *pParser; + pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); + if( pParser ){ + pParser->yyidx = -1; + } + return pParser; +} + +/* The following function deletes the value associated with a +** symbol. The symbol can be either a terminal or nonterminal. +** "yymajor" is the symbol code, and "yypminor" is a pointer to +** the value. +*/ +static void yy_destructor(YYCODETYPE yymajor, YYMINORTYPE *yypminor){ + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ +%% + default: break; /* If no destructor action specified: do nothing */ + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +** +** Return the major token number for the symbol popped. +*/ +static int yy_pop_parser_stack(yyParser *pParser){ + YYCODETYPE yymajor; + yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; + + if( pParser->yyidx<0 ) return 0; +#ifndef NDEBUG + if( yyTraceFILE && pParser->yyidx>=0 ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yymajor = yytos->major; + yy_destructor( yymajor, &yytos->minor); + pParser->yyidx--; + return yymajor; +} + +/* +** Deallocate and destroy a parser. Destructors are all called for +** all stack elements before shutting the parser down. +** +** Inputs: +**
      +**
    • A pointer to the parser. This should be a pointer +** obtained from ParseAlloc. +**
    • A pointer to a function used to reclaim memory obtained +** from malloc. +**
    +*/ +void ParseFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ + yyParser *pParser = (yyParser*)p; + if( pParser==0 ) return; + while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); + (*freeProc)((void*)pParser); +} + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_shift_action( + yyParser *pParser, /* The parser */ + int iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + + /* if( pParser->yyidx<0 ) return YY_NO_ACTION; */ + i = yy_shift_ofst[stateno]; + if( i==YY_SHIFT_USE_DFLT ){ + return yy_default[stateno]; + } + if( iLookAhead==YYNOCODE ){ + return YY_NO_ACTION; + } + i += iLookAhead; + if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){ +#ifdef YYFALLBACK + int iFallback; /* Fallback token */ + if( iLookAhead %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + return yy_find_shift_action(pParser, iFallback); + } +#endif + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_reduce_action( + yyParser *pParser, /* The parser */ + int iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + + i = yy_reduce_ofst[stateno]; + if( i==YY_REDUCE_USE_DFLT ){ + return yy_default[stateno]; + } + if( iLookAhead==YYNOCODE ){ + return YY_NO_ACTION; + } + i += iLookAhead; + if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + int yyNewState, /* The new state to shift in */ + int yyMajor, /* The major token to shift in */ + YYMINORTYPE *yypMinor /* Pointer ot the minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yyidx++; + if( yypParser->yyidx>=YYSTACKDEPTH ){ + ParseARG_FETCH; + yypParser->yyidx--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument var */ + return; + } + yytos = &yypParser->yystack[yypParser->yyidx]; + yytos->stateno = yyNewState; + yytos->major = yyMajor; + yytos->minor = *yypMinor; +#ifndef NDEBUG + if( yyTraceFILE && yypParser->yyidx>0 ){ + int i; + fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); + fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"\n"); + } +#endif +} + +/* The following table contains information about every rule that +** is used during the reduce. +*/ +static struct { + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + unsigned char nrhs; /* Number of right-hand side symbols in the rule */ +} yyRuleInfo[] = { +%% +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +*/ +static void yy_reduce( + yyParser *yypParser, /* The parser */ + int yyruleno /* Number of the rule by which to reduce */ +){ + int yygoto; /* The next state */ + int yyact; /* The next action */ + YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + ParseARG_FETCH; + yymsp = &yypParser->yystack[yypParser->yyidx]; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno>=0 + && yyruleno + ** { ... } // User supplied code + ** #line + ** break; + */ +%% + }; + yygoto = yyRuleInfo[yyruleno].lhs; + yysize = yyRuleInfo[yyruleno].nrhs; + yypParser->yyidx -= yysize; + yyact = yy_find_reduce_action(yypParser,yygoto); + if( yyact < YYNSTATE ){ + yy_shift(yypParser,yyact,yygoto,&yygotominor); + }else if( yyact == YYNSTATE + YYNRULE + 1 ){ + yy_accept(yypParser); + } +} + +/* +** The following code executes when the parse fails +*/ +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + YYMINORTYPE yyminor /* The minor type of the error token */ +){ + ParseARG_FETCH; +#define TOKEN (yyminor.yy0) +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "ParseAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +**
      +**
    • A pointer to the parser (an opaque structure.) +**
    • The major token number. +**
    • The minor token number. +**
    • An option argument of a grammar-specified type. +**
    +** +** Outputs: +** None. +*/ +void Parse( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + ParseTOKENTYPE yyminor /* The value for the token */ + ParseARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + int yyact; /* The parser action. */ + int yyendofinput; /* True if we are at the end of input */ + int yyerrorhit = 0; /* True if yymajor has invoked an error */ + yyParser *yypParser; /* The parser */ + + /* (re)initialize the parser, if necessary */ + yypParser = (yyParser*)yyp; + if( yypParser->yyidx<0 ){ + if( yymajor==0 ) return; + yypParser->yyidx = 0; + yypParser->yyerrcnt = -1; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; + } + yyminorunion.yy0 = yyminor; + yyendofinput = (yymajor==0); + ParseARG_STORE; + +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]); + } +#endif + + do{ + yyact = yy_find_shift_action(yypParser,yymajor); + if( yyactyyerrcnt--; + if( yyendofinput && yypParser->yyidx>=0 ){ + yymajor = 0; + }else{ + yymajor = YYNOCODE; + } + }else if( yyact < YYNSTATE + YYNRULE ){ + yy_reduce(yypParser,yyact-YYNSTATE); + }else if( yyact == YY_ERROR_ACTION ){ + int yymx; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yymx = yypParser->yystack[yypParser->yyidx].major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yymajor,&yyminorunion); + yymajor = YYNOCODE; + }else{ + while( + yypParser->yyidx >= 0 && + yymx != YYERRORSYMBOL && + (yyact = yy_find_shift_action(yypParser,YYERRORSYMBOL)) >= YYNSTATE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yyidx < 0 || yymajor==0 ){ + yy_destructor(yymajor,&yyminorunion); + yy_parse_failed(yypParser); + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + YYMINORTYPE u2; + u2.YYERRSYMDT = 0; + yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); + } + } + yypParser->yyerrcnt = 3; + yyerrorhit = 1; +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yypParser->yyerrcnt = 3; + yy_destructor(yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); + } + yymajor = YYNOCODE; +#endif + }else{ + yy_accept(yypParser); + yymajor = YYNOCODE; + } + }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); + return; +} diff --git a/kopete/plugins/statistics/sqlite/main.c b/kopete/plugins/statistics/sqlite/main.c new file mode 100644 index 00000000..0ae7e1b2 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/main.c @@ -0,0 +1,1346 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Main file for the SQLite library. The routines in this file +** implement the programmer interface to the library. Routines in +** other files are for internal use by SQLite and should not be +** accessed by users of the library. +** +** $Id$ +*/ +#include "sqliteInt.h" +#include "os.h" +#include + +/* +** The following constant value is used by the SQLITE_BIGENDIAN and +** SQLITE_LITTLEENDIAN macros. +*/ +const int sqlite3one = 1; + +/* +** Fill the InitData structure with an error message that indicates +** that the database is corrupt. +*/ +static void corruptSchema(InitData *pData, const char *zExtra){ + if( !sqlite3_malloc_failed ){ + sqlite3SetString(pData->pzErrMsg, "malformed database schema", + zExtra!=0 && zExtra[0]!=0 ? " - " : (char*)0, zExtra, (char*)0); + } +} + +/* +** This is the callback routine for the code that initializes the +** database. See sqlite3Init() below for additional information. +** This routine is also called from the OP_ParseSchema opcode of the VDBE. +** +** Each callback contains the following information: +** +** argv[0] = name of thing being created +** argv[1] = root page number for table or index. NULL for trigger or view. +** argv[2] = SQL text for the CREATE statement. +** argv[3] = "1" for temporary files, "0" for main database, "2" or more +** for auxiliary database files. +** +*/ +int sqlite3InitCallback(void *pInit, int argc, char **argv, char **azColName){ + InitData *pData = (InitData*)pInit; + sqlite3 *db = pData->db; + int iDb; + + assert( argc==4 ); + if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */ + if( argv[1]==0 || argv[3]==0 ){ + corruptSchema(pData, 0); + return 1; + } + iDb = atoi(argv[3]); + assert( iDb>=0 && iDbnDb ); + if( argv[2] && argv[2][0] ){ + /* Call the parser to process a CREATE TABLE, INDEX or VIEW. + ** But because db->init.busy is set to 1, no VDBE code is generated + ** or executed. All the parser does is build the internal data + ** structures that describe the table, index, or view. + */ + char *zErr; + int rc; + assert( db->init.busy ); + db->init.iDb = iDb; + db->init.newTnum = atoi(argv[1]); + rc = sqlite3_exec(db, argv[2], 0, 0, &zErr); + db->init.iDb = 0; + if( SQLITE_OK!=rc ){ + corruptSchema(pData, zErr); + sqlite3_free(zErr); + return rc; + } + }else{ + /* If the SQL column is blank it means this is an index that + ** was created to be the PRIMARY KEY or to fulfill a UNIQUE + ** constraint for a CREATE TABLE. The index should have already + ** been created when we processed the CREATE TABLE. All we have + ** to do here is record the root page number for that index. + */ + Index *pIndex; + pIndex = sqlite3FindIndex(db, argv[0], db->aDb[iDb].zName); + if( pIndex==0 || pIndex->tnum!=0 ){ + /* This can occur if there exists an index on a TEMP table which + ** has the same name as another index on a permanent index. Since + ** the permanent table is hidden by the TEMP table, we can also + ** safely ignore the index on the permanent table. + */ + /* Do Nothing */; + }else{ + pIndex->tnum = atoi(argv[1]); + } + } + return 0; +} + +/* +** Attempt to read the database schema and initialize internal +** data structures for a single database file. The index of the +** database file is given by iDb. iDb==0 is used for the main +** database. iDb==1 should never be used. iDb>=2 is used for +** auxiliary databases. Return one of the SQLITE_ error codes to +** indicate success or failure. +*/ +static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){ + int rc; + BtCursor *curMain; + int size; + Table *pTab; + char const *azArg[5]; + char zDbNum[30]; + int meta[10]; + InitData initData; + char const *zMasterSchema; + char const *zMasterName; + + /* + ** The master database table has a structure like this + */ + static const char master_schema[] = + "CREATE TABLE sqlite_master(\n" + " type text,\n" + " name text,\n" + " tbl_name text,\n" + " rootpage integer,\n" + " sql text\n" + ")" + ; + static const char temp_master_schema[] = + "CREATE TEMP TABLE sqlite_temp_master(\n" + " type text,\n" + " name text,\n" + " tbl_name text,\n" + " rootpage integer,\n" + " sql text\n" + ")" + ; + + assert( iDb>=0 && iDbnDb ); + + /* zMasterSchema and zInitScript are set to point at the master schema + ** and initialisation script appropriate for the database being + ** initialised. zMasterName is the name of the master table. + */ + if( iDb==1 ){ + zMasterSchema = temp_master_schema; + zMasterName = TEMP_MASTER_NAME; + }else{ + zMasterSchema = master_schema; + zMasterName = MASTER_NAME; + } + + /* Construct the schema tables. */ + sqlite3SafetyOff(db); + azArg[0] = zMasterName; + azArg[1] = "1"; + azArg[2] = zMasterSchema; + sprintf(zDbNum, "%d", iDb); + azArg[3] = zDbNum; + azArg[4] = 0; + initData.db = db; + initData.pzErrMsg = pzErrMsg; + rc = sqlite3InitCallback(&initData, 4, (char **)azArg, 0); + if( rc!=SQLITE_OK ){ + sqlite3SafetyOn(db); + return rc; + } + pTab = sqlite3FindTable(db, zMasterName, db->aDb[iDb].zName); + if( pTab ){ + pTab->readOnly = 1; + } + sqlite3SafetyOn(db); + + /* Create a cursor to hold the database open + */ + if( db->aDb[iDb].pBt==0 ){ + if( iDb==1 ) DbSetProperty(db, 1, DB_SchemaLoaded); + return SQLITE_OK; + } + rc = sqlite3BtreeCursor(db->aDb[iDb].pBt, MASTER_ROOT, 0, 0, 0, &curMain); + if( rc!=SQLITE_OK && rc!=SQLITE_EMPTY ){ + sqlite3SetString(pzErrMsg, sqlite3ErrStr(rc), (char*)0); + return rc; + } + + /* Get the database meta information. + ** + ** Meta values are as follows: + ** meta[0] Schema cookie. Changes with each schema change. + ** meta[1] File format of schema layer. + ** meta[2] Size of the page cache. + ** meta[3] Use freelist if 0. Autovacuum if greater than zero. + ** meta[4] Db text encoding. 1:UTF-8 3:UTF-16 LE 4:UTF-16 BE + ** meta[5] + ** meta[6] + ** meta[7] + ** meta[8] + ** meta[9] + ** + ** Note: The hash defined SQLITE_UTF* symbols in sqliteInt.h correspond to + ** the possible values of meta[4]. + */ + if( rc==SQLITE_OK ){ + int i; + for(i=0; rc==SQLITE_OK && iaDb[iDb].pBt, i+1, (u32 *)&meta[i]); + } + if( rc ){ + sqlite3SetString(pzErrMsg, sqlite3ErrStr(rc), (char*)0); + sqlite3BtreeCloseCursor(curMain); + return rc; + } + }else{ + memset(meta, 0, sizeof(meta)); + } + db->aDb[iDb].schema_cookie = meta[0]; + + /* If opening a non-empty database, check the text encoding. For the + ** main database, set sqlite3.enc to the encoding of the main database. + ** For an attached db, it is an error if the encoding is not the same + ** as sqlite3.enc. + */ + if( meta[4] ){ /* text encoding */ + if( iDb==0 ){ + /* If opening the main database, set db->enc. */ + db->enc = (u8)meta[4]; + db->pDfltColl = sqlite3FindCollSeq(db, db->enc, "BINARY", 6, 0); + }else{ + /* If opening an attached database, the encoding much match db->enc */ + if( meta[4]!=db->enc ){ + sqlite3BtreeCloseCursor(curMain); + sqlite3SetString(pzErrMsg, "attached databases must use the same" + " text encoding as main database", (char*)0); + return SQLITE_ERROR; + } + } + } + + size = meta[2]; + if( size==0 ){ size = MAX_PAGES; } + db->aDb[iDb].cache_size = size; + + if( iDb==0 ){ + db->file_format = meta[1]; + if( db->file_format==0 ){ + /* This happens if the database was initially empty */ + db->file_format = 1; + } + } + + /* + ** file_format==1 Version 3.0.0. + */ + if( meta[1]>1 ){ + sqlite3BtreeCloseCursor(curMain); + sqlite3SetString(pzErrMsg, "unsupported file format", (char*)0); + return SQLITE_ERROR; + } + + sqlite3BtreeSetCacheSize(db->aDb[iDb].pBt, db->aDb[iDb].cache_size); + + /* Read the schema information out of the schema tables + */ + assert( db->init.busy ); + if( rc==SQLITE_EMPTY ){ + /* For an empty database, there is nothing to read */ + rc = SQLITE_OK; + }else{ + char *zSql; + zSql = sqlite3MPrintf( + "SELECT name, rootpage, sql, %s FROM '%q'.%s", + zDbNum, db->aDb[iDb].zName, zMasterName); + sqlite3SafetyOff(db); + rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0); + sqlite3SafetyOn(db); + sqliteFree(zSql); + sqlite3BtreeCloseCursor(curMain); + } + if( sqlite3_malloc_failed ){ + sqlite3SetString(pzErrMsg, "out of memory", (char*)0); + rc = SQLITE_NOMEM; + sqlite3ResetInternalSchema(db, 0); + } + if( rc==SQLITE_OK ){ + DbSetProperty(db, iDb, DB_SchemaLoaded); + }else{ + sqlite3ResetInternalSchema(db, iDb); + } + return rc; +} + +/* +** Initialize all database files - the main database file, the file +** used to store temporary tables, and any additional database files +** created using ATTACH statements. Return a success code. If an +** error occurs, write an error message into *pzErrMsg. +** +** After the database is initialized, the SQLITE_Initialized +** bit is set in the flags field of the sqlite structure. +*/ +int sqlite3Init(sqlite3 *db, char **pzErrMsg){ + int i, rc; + + if( db->init.busy ) return SQLITE_OK; + assert( (db->flags & SQLITE_Initialized)==0 ); + rc = SQLITE_OK; + db->init.busy = 1; + for(i=0; rc==SQLITE_OK && inDb; i++){ + if( DbHasProperty(db, i, DB_SchemaLoaded) || i==1 ) continue; + rc = sqlite3InitOne(db, i, pzErrMsg); + if( rc ){ + sqlite3ResetInternalSchema(db, i); + } + } + + /* Once all the other databases have been initialised, load the schema + ** for the TEMP database. This is loaded last, as the TEMP database + ** schema may contain references to objects in other databases. + */ + if( rc==SQLITE_OK && db->nDb>1 && !DbHasProperty(db, 1, DB_SchemaLoaded) ){ + rc = sqlite3InitOne(db, 1, pzErrMsg); + if( rc ){ + sqlite3ResetInternalSchema(db, 1); + } + } + + db->init.busy = 0; + if( rc==SQLITE_OK ){ + db->flags |= SQLITE_Initialized; + sqlite3CommitInternalChanges(db); + } + + if( rc!=SQLITE_OK ){ + db->flags &= ~SQLITE_Initialized; + } + return rc; +} + +/* +** This routine is a no-op if the database schema is already initialised. +** Otherwise, the schema is loaded. An error code is returned. +*/ +int sqlite3ReadSchema(Parse *pParse){ + int rc = SQLITE_OK; + sqlite3 *db = pParse->db; + if( !db->init.busy ){ + if( (db->flags & SQLITE_Initialized)==0 ){ + rc = sqlite3Init(db, &pParse->zErrMsg); + } + } + assert( rc!=SQLITE_OK || (db->flags & SQLITE_Initialized)||db->init.busy ); + if( rc!=SQLITE_OK ){ + pParse->rc = rc; + pParse->nErr++; + } + return rc; +} + +/* +** The version of the library +*/ +const char rcsid3[] = "@(#) \044Id: SQLite version " SQLITE_VERSION " $"; +const char sqlite3_version[] = SQLITE_VERSION; +const char *sqlite3_libversion(void){ return sqlite3_version; } + +/* +** This is the default collating function named "BINARY" which is always +** available. +*/ +static int binaryCollatingFunc( + void *NotUsed, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + int rc, n; + n = nKey1lastRowid; +} + +/* +** Return the number of changes in the most recent call to sqlite3_exec(). +*/ +int sqlite3_changes(sqlite3 *db){ + return db->nChange; +} + +/* +** Return the number of changes since the database handle was opened. +*/ +int sqlite3_total_changes(sqlite3 *db){ + return db->nTotalChange; +} + +/* +** Close an existing SQLite database +*/ +int sqlite3_close(sqlite3 *db){ + HashElem *i; + int j; + + if( !db ){ + return SQLITE_OK; + } + if( sqlite3SafetyCheck(db) ){ + return SQLITE_MISUSE; + } + + /* If there are any outstanding VMs, return SQLITE_BUSY. */ + if( db->pVdbe ){ + sqlite3Error(db, SQLITE_BUSY, + "Unable to close due to unfinalised statements"); + return SQLITE_BUSY; + } + assert( !sqlite3SafetyCheck(db) ); + + /* FIX ME: db->magic may be set to SQLITE_MAGIC_CLOSED if the database + ** cannot be opened for some reason. So this routine needs to run in + ** that case. But maybe there should be an extra magic value for the + ** "failed to open" state. + */ + if( db->magic!=SQLITE_MAGIC_CLOSED && sqlite3SafetyOn(db) ){ + /* printf("DID NOT CLOSE\n"); fflush(stdout); */ + return SQLITE_ERROR; + } + + for(j=0; jnDb; j++){ + struct Db *pDb = &db->aDb[j]; + if( pDb->pBt ){ + sqlite3BtreeClose(pDb->pBt); + pDb->pBt = 0; + } + } + sqlite3ResetInternalSchema(db, 0); + assert( db->nDb<=2 ); + assert( db->aDb==db->aDbStatic ); + for(i=sqliteHashFirst(&db->aFunc); i; i=sqliteHashNext(i)){ + FuncDef *pFunc, *pNext; + for(pFunc = (FuncDef*)sqliteHashData(i); pFunc; pFunc=pNext){ + pNext = pFunc->pNext; + sqliteFree(pFunc); + } + } + + for(i=sqliteHashFirst(&db->aCollSeq); i; i=sqliteHashNext(i)){ + CollSeq *pColl = (CollSeq *)sqliteHashData(i); + sqliteFree(pColl); + } + sqlite3HashClear(&db->aCollSeq); + + sqlite3HashClear(&db->aFunc); + sqlite3Error(db, SQLITE_OK, 0); /* Deallocates any cached error strings. */ + if( db->pValue ){ + sqlite3ValueFree(db->pValue); + } + if( db->pErr ){ + sqlite3ValueFree(db->pErr); + } + + db->magic = SQLITE_MAGIC_ERROR; + sqliteFree(db); + return SQLITE_OK; +} + +/* +** Rollback all database files. +*/ +void sqlite3RollbackAll(sqlite3 *db){ + int i; + for(i=0; inDb; i++){ + if( db->aDb[i].pBt ){ + sqlite3BtreeRollback(db->aDb[i].pBt); + db->aDb[i].inTrans = 0; + } + } + sqlite3ResetInternalSchema(db, 0); +} + +/* +** Return a static string that describes the kind of error specified in the +** argument. +*/ +const char *sqlite3ErrStr(int rc){ + const char *z; + switch( rc ){ + case SQLITE_ROW: + case SQLITE_DONE: + case SQLITE_OK: z = "not an error"; break; + case SQLITE_ERROR: z = "SQL logic error or missing database"; break; + case SQLITE_INTERNAL: z = "internal SQLite implementation flaw"; break; + case SQLITE_PERM: z = "access permission denied"; break; + case SQLITE_ABORT: z = "callback requested query abort"; break; + case SQLITE_BUSY: z = "database is locked"; break; + case SQLITE_LOCKED: z = "database table is locked"; break; + case SQLITE_NOMEM: z = "out of memory"; break; + case SQLITE_READONLY: z = "attempt to write a readonly database"; break; + case SQLITE_INTERRUPT: z = "interrupted"; break; + case SQLITE_IOERR: z = "disk I/O error"; break; + case SQLITE_CORRUPT: z = "database disk image is malformed"; break; + case SQLITE_NOTFOUND: z = "table or record not found"; break; + case SQLITE_FULL: z = "database is full"; break; + case SQLITE_CANTOPEN: z = "unable to open database file"; break; + case SQLITE_PROTOCOL: z = "database locking protocol failure"; break; + case SQLITE_EMPTY: z = "table contains no data"; break; + case SQLITE_SCHEMA: z = "database schema has changed"; break; + case SQLITE_TOOBIG: z = "too much data for one table row"; break; + case SQLITE_CONSTRAINT: z = "constraint failed"; break; + case SQLITE_MISMATCH: z = "datatype mismatch"; break; + case SQLITE_MISUSE: z = "library routine called out of sequence";break; + case SQLITE_NOLFS: z = "kernel lacks large file support"; break; + case SQLITE_AUTH: z = "authorization denied"; break; + case SQLITE_FORMAT: z = "auxiliary database format error"; break; + case SQLITE_RANGE: z = "bind index out of range"; break; + case SQLITE_NOTADB: z = "file is encrypted or is not a database";break; + default: z = "unknown error"; break; + } + return z; +} + +/* +** This routine implements a busy callback that sleeps and tries +** again until a timeout value is reached. The timeout value is +** an integer number of milliseconds passed in as the first +** argument. +*/ +static int sqliteDefaultBusyCallback( + void *Timeout, /* Maximum amount of time to wait */ + int count /* Number of times table has been busy */ +){ +#if SQLITE_MIN_SLEEP_MS==1 + static const char delays[] = + { 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 50, 100}; + static const short int totals[] = + { 0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228, 287}; +# define NDELAY (sizeof(delays)/sizeof(delays[0])) + ptr timeout = (ptr)Timeout; + ptr delay, prior; + + if( count <= NDELAY ){ + delay = delays[count-1]; + prior = totals[count-1]; + }else{ + delay = delays[NDELAY-1]; + prior = totals[NDELAY-1] + delay*(count-NDELAY-1); + } + if( prior + delay > timeout ){ + delay = timeout - prior; + if( delay<=0 ) return 0; + } + sqlite3OsSleep(delay); + return 1; +#else + int timeout = (int)Timeout; + if( (count+1)*1000 > timeout ){ + return 0; + } + sqlite3OsSleep(1000); + return 1; +#endif +} + +/* +** This routine sets the busy callback for an Sqlite database to the +** given callback function with the given argument. +*/ +int sqlite3_busy_handler( + sqlite3 *db, + int (*xBusy)(void*,int), + void *pArg +){ + if( sqlite3SafetyCheck(db) ){ + return SQLITE_MISUSE; + } + db->busyHandler.xFunc = xBusy; + db->busyHandler.pArg = pArg; + return SQLITE_OK; +} + +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK +/* +** This routine sets the progress callback for an Sqlite database to the +** given callback function with the given argument. The progress callback will +** be invoked every nOps opcodes. +*/ +void sqlite3_progress_handler( + sqlite3 *db, + int nOps, + int (*xProgress)(void*), + void *pArg +){ + if( !sqlite3SafetyCheck(db) ){ + if( nOps>0 ){ + db->xProgress = xProgress; + db->nProgressOps = nOps; + db->pProgressArg = pArg; + }else{ + db->xProgress = 0; + db->nProgressOps = 0; + db->pProgressArg = 0; + } + } +} +#endif + + +/* +** This routine installs a default busy handler that waits for the +** specified number of milliseconds before returning 0. +*/ +int sqlite3_busy_timeout(sqlite3 *db, int ms){ + if( ms>0 ){ + sqlite3_busy_handler(db, sqliteDefaultBusyCallback, (void*)(ptr)ms); + }else{ + sqlite3_busy_handler(db, 0, 0); + } + return SQLITE_OK; +} + +/* +** Cause any pending operation to stop at its earliest opportunity. +*/ +void sqlite3_interrupt(sqlite3 *db){ + if( !sqlite3SafetyCheck(db) ){ + db->flags |= SQLITE_Interrupt; + } +} + +/* +** Windows systems should call this routine to free memory that +** is returned in the in the errmsg parameter of sqlite3_open() when +** SQLite is a DLL. For some reason, it does not work to call free() +** directly. +** +** Note that we need to call free() not sqliteFree() here. +*/ +void sqlite3_free(char *p){ free(p); } + +/* +** Create new user functions. +*/ +int sqlite3_create_function( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int enc, + void *pUserData, + void (*xFunc)(sqlite3_context*,int,sqlite3_value **), + void (*xStep)(sqlite3_context*,int,sqlite3_value **), + void (*xFinal)(sqlite3_context*) +){ + FuncDef *p; + int nName; + + if( sqlite3SafetyCheck(db) ){ + return SQLITE_MISUSE; + } + if( zFunctionName==0 || + (xFunc && (xFinal || xStep)) || + (!xFunc && (xFinal && !xStep)) || + (!xFunc && (!xFinal && xStep)) || + (nArg<-1 || nArg>127) || + (255<(nName = strlen(zFunctionName))) ){ + return SQLITE_ERROR; + } + + /* If SQLITE_UTF16 is specified as the encoding type, transform this + ** to one of SQLITE_UTF16LE or SQLITE_UTF16BE using the + ** SQLITE_UTF16NATIVE macro. SQLITE_UTF16 is not used internally. + ** + ** If SQLITE_ANY is specified, add three versions of the function + ** to the hash table. + */ + if( enc==SQLITE_UTF16 ){ + enc = SQLITE_UTF16NATIVE; + }else if( enc==SQLITE_ANY ){ + int rc; + rc = sqlite3_create_function(db, zFunctionName, nArg, SQLITE_UTF8, + pUserData, xFunc, xStep, xFinal); + if( rc!=SQLITE_OK ) return rc; + rc = sqlite3_create_function(db, zFunctionName, nArg, SQLITE_UTF16LE, + pUserData, xFunc, xStep, xFinal); + if( rc!=SQLITE_OK ) return rc; + enc = SQLITE_UTF16BE; + } + + p = sqlite3FindFunction(db, zFunctionName, nName, nArg, enc, 1); + if( p==0 ) return SQLITE_NOMEM; + p->xFunc = xFunc; + p->xStep = xStep; + p->xFinalize = xFinal; + p->pUserData = pUserData; + return SQLITE_OK; +} +int sqlite3_create_function16( + sqlite3 *db, + const void *zFunctionName, + int nArg, + int eTextRep, + void *pUserData, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +){ + int rc; + char const *zFunc8; + sqlite3_value *pTmp; + + if( sqlite3SafetyCheck(db) ){ + return SQLITE_MISUSE; + } + pTmp = sqlite3GetTransientValue(db); + sqlite3ValueSetStr(pTmp, -1, zFunctionName, SQLITE_UTF16NATIVE,SQLITE_STATIC); + zFunc8 = sqlite3ValueText(pTmp, SQLITE_UTF8); + + if( !zFunc8 ){ + return SQLITE_NOMEM; + } + rc = sqlite3_create_function(db, zFunc8, nArg, eTextRep, + pUserData, xFunc, xStep, xFinal); + return rc; +} + +/* +** Register a trace function. The pArg from the previously registered trace +** is returned. +** +** A NULL trace function means that no tracing is executes. A non-NULL +** trace is a pointer to a function that is invoked at the start of each +** sqlite3_exec(). +*/ +void *sqlite3_trace(sqlite3 *db, void (*xTrace)(void*,const char*), void *pArg){ + void *pOld = db->pTraceArg; + db->xTrace = xTrace; + db->pTraceArg = pArg; + return pOld; +} + +/*** EXPERIMENTAL *** +** +** Register a function to be invoked when a transaction comments. +** If either function returns non-zero, then the commit becomes a +** rollback. +*/ +void *sqlite3_commit_hook( + sqlite3 *db, /* Attach the hook to this database */ + int (*xCallback)(void*), /* Function to invoke on each commit */ + void *pArg /* Argument to the function */ +){ + void *pOld = db->pCommitArg; + db->xCommitCallback = xCallback; + db->pCommitArg = pArg; + return pOld; +} + + +/* +** This routine is called to create a connection to a database BTree +** driver. If zFilename is the name of a file, then that file is +** opened and used. If zFilename is the magic name ":memory:" then +** the database is stored in memory (and is thus forgotten as soon as +** the connection is closed.) If zFilename is NULL then the database +** is for temporary use only and is deleted as soon as the connection +** is closed. +** +** A temporary database can be either a disk file (that is automatically +** deleted when the file is closed) or a set of red-black trees held in memory, +** depending on the values of the TEMP_STORE compile-time macro and the +** db->temp_store variable, according to the following chart: +** +** TEMP_STORE db->temp_store Location of temporary database +** ---------- -------------- ------------------------------ +** 0 any file +** 1 1 file +** 1 2 memory +** 1 0 file +** 2 1 file +** 2 2 memory +** 2 0 memory +** 3 any memory +*/ +int sqlite3BtreeFactory( + const sqlite3 *db, /* Main database when opening aux otherwise 0 */ + const char *zFilename, /* Name of the file containing the BTree database */ + int omitJournal, /* if TRUE then do not journal this file */ + int nCache, /* How many pages in the page cache */ + Btree **ppBtree /* Pointer to new Btree object written here */ +){ + int btree_flags = 0; + int rc; + + assert( ppBtree != 0); + if( omitJournal ){ + btree_flags |= BTREE_OMIT_JOURNAL; + } + if( zFilename==0 ){ +#ifndef TEMP_STORE +# define TEMP_STORE 1 +#endif +#if TEMP_STORE==0 + /* Do nothing */ +#endif +#if TEMP_STORE==1 + if( db->temp_store==2 ) zFilename = ":memory:"; +#endif +#if TEMP_STORE==2 + if( db->temp_store!=1 ) zFilename = ":memory:"; +#endif +#if TEMP_STORE==3 + zFilename = ":memory:"; +#endif + } + + rc = sqlite3BtreeOpen(zFilename, ppBtree, btree_flags); + if( rc==SQLITE_OK ){ + sqlite3BtreeSetBusyHandler(*ppBtree, (void*)&db->busyHandler); + sqlite3BtreeSetCacheSize(*ppBtree, nCache); + } + return rc; +} + +/* +** Return UTF-8 encoded English language explanation of the most recent +** error. +*/ +const char *sqlite3_errmsg(sqlite3 *db){ + const char *z; + if( sqlite3_malloc_failed ){ + return sqlite3ErrStr(SQLITE_NOMEM); + } + if( sqlite3SafetyCheck(db) || db->errCode==SQLITE_MISUSE ){ + return sqlite3ErrStr(SQLITE_MISUSE); + } + z = sqlite3_value_text(db->pErr); + if( z==0 ){ + z = sqlite3ErrStr(db->errCode); + } + return z; +} + +/* +** Return UTF-16 encoded English language explanation of the most recent +** error. +*/ +const void *sqlite3_errmsg16(sqlite3 *db){ + /* Because all the characters in the string are in the unicode + ** range 0x00-0xFF, if we pad the big-endian string with a + ** zero byte, we can obtain the little-endian string with + ** &big_endian[1]. + */ + static const char outOfMemBe[] = { + 0, 'o', 0, 'u', 0, 't', 0, ' ', + 0, 'o', 0, 'f', 0, ' ', + 0, 'm', 0, 'e', 0, 'm', 0, 'o', 0, 'r', 0, 'y', 0, 0, 0 + }; + static const char misuseBe [] = { + 0, 'l', 0, 'i', 0, 'b', 0, 'r', 0, 'a', 0, 'r', 0, 'y', 0, ' ', + 0, 'r', 0, 'o', 0, 'u', 0, 't', 0, 'i', 0, 'n', 0, 'e', 0, ' ', + 0, 'c', 0, 'a', 0, 'l', 0, 'l', 0, 'e', 0, 'd', 0, ' ', + 0, 'o', 0, 'u', 0, 't', 0, ' ', + 0, 'o', 0, 'f', 0, ' ', + 0, 's', 0, 'e', 0, 'q', 0, 'u', 0, 'e', 0, 'n', 0, 'c', 0, 'e', 0, 0, 0 + }; + + const void *z; + if( sqlite3_malloc_failed ){ + return (void *)(&outOfMemBe[SQLITE_UTF16NATIVE==SQLITE_UTF16LE?1:0]); + } + if( sqlite3SafetyCheck(db) || db->errCode==SQLITE_MISUSE ){ + return (void *)(&misuseBe[SQLITE_UTF16NATIVE==SQLITE_UTF16LE?1:0]); + } + z = sqlite3_value_text16(db->pErr); + if( z==0 ){ + sqlite3ValueSetStr(db->pErr, -1, sqlite3ErrStr(db->errCode), + SQLITE_UTF8, SQLITE_STATIC); + z = sqlite3_value_text16(db->pErr); + } + return z; +} + +/* +** Return the most recent error code generated by an SQLite routine. +*/ +int sqlite3_errcode(sqlite3 *db){ + if( sqlite3_malloc_failed ){ + return SQLITE_NOMEM; + } + if( sqlite3SafetyCheck(db) ){ + return SQLITE_MISUSE; + } + return db->errCode; +} + +/* +** Check schema cookies in all databases. If any cookie is out +** of date, return 0. If all schema cookies are current, return 1. +*/ +static int schemaIsValid(sqlite3 *db){ + int iDb; + int rc; + BtCursor *curTemp; + int cookie; + int allOk = 1; + + for(iDb=0; allOk && iDbnDb; iDb++){ + Btree *pBt; + pBt = db->aDb[iDb].pBt; + if( pBt==0 ) continue; + rc = sqlite3BtreeCursor(pBt, MASTER_ROOT, 0, 0, 0, &curTemp); + if( rc==SQLITE_OK ){ + rc = sqlite3BtreeGetMeta(pBt, 1, (u32 *)&cookie); + if( rc==SQLITE_OK && cookie!=db->aDb[iDb].schema_cookie ){ + allOk = 0; + } + sqlite3BtreeCloseCursor(curTemp); + } + } + return allOk; +} + +/* +** Compile the UTF-8 encoded SQL statement zSql into a statement handle. +*/ +int sqlite3_prepare( + sqlite3 *db, /* Database handle. */ + const char *zSql, /* UTF-8 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const char** pzTail /* OUT: End of parsed string */ +){ + Parse sParse; + char *zErrMsg = 0; + int rc = SQLITE_OK; + + if( sqlite3_malloc_failed ){ + return SQLITE_NOMEM; + } + + assert( ppStmt ); + *ppStmt = 0; + if( sqlite3SafetyOn(db) ){ + return SQLITE_MISUSE; + } + + memset(&sParse, 0, sizeof(sParse)); + sParse.db = db; + sqlite3RunParser(&sParse, zSql, &zErrMsg); + + if( sqlite3_malloc_failed ){ + rc = SQLITE_NOMEM; + sqlite3RollbackAll(db); + sqlite3ResetInternalSchema(db, 0); + db->flags &= ~SQLITE_InTrans; + goto prepare_out; + } + if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK; + if( sParse.rc!=SQLITE_OK && sParse.checkSchema && !schemaIsValid(db) ){ + sParse.rc = SQLITE_SCHEMA; + } + if( sParse.rc==SQLITE_SCHEMA ){ + sqlite3ResetInternalSchema(db, 0); + } + if( pzTail ) *pzTail = sParse.zTail; + rc = sParse.rc; + + if( rc==SQLITE_OK && sParse.pVdbe && sParse.explain ){ + sqlite3VdbeSetNumCols(sParse.pVdbe, 5); + sqlite3VdbeSetColName(sParse.pVdbe, 0, "addr", P3_STATIC); + sqlite3VdbeSetColName(sParse.pVdbe, 1, "opcode", P3_STATIC); + sqlite3VdbeSetColName(sParse.pVdbe, 2, "p1", P3_STATIC); + sqlite3VdbeSetColName(sParse.pVdbe, 3, "p2", P3_STATIC); + sqlite3VdbeSetColName(sParse.pVdbe, 4, "p3", P3_STATIC); + } + +prepare_out: + if( sqlite3SafetyOff(db) ){ + rc = SQLITE_MISUSE; + } + if( rc==SQLITE_OK ){ + *ppStmt = (sqlite3_stmt*)sParse.pVdbe; + }else if( sParse.pVdbe ){ + sqlite3_finalize((sqlite3_stmt*)sParse.pVdbe); + } + + if( zErrMsg ){ + sqlite3Error(db, rc, "%s", zErrMsg); + sqliteFree(zErrMsg); + }else{ + sqlite3Error(db, rc, 0); + } + return rc; +} + +/* +** Compile the UTF-16 encoded SQL statement zSql into a statement handle. +*/ +int sqlite3_prepare16( + sqlite3 *db, /* Database handle. */ + const void *zSql, /* UTF-8 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const void **pzTail /* OUT: End of parsed string */ +){ + /* This function currently works by first transforming the UTF-16 + ** encoded string to UTF-8, then invoking sqlite3_prepare(). The + ** tricky bit is figuring out the pointer to return in *pzTail. + */ + char const *zSql8 = 0; + char const *zTail8 = 0; + int rc; + sqlite3_value *pTmp; + + if( sqlite3SafetyCheck(db) ){ + return SQLITE_MISUSE; + } + pTmp = sqlite3GetTransientValue(db); + sqlite3ValueSetStr(pTmp, -1, zSql, SQLITE_UTF16NATIVE, SQLITE_STATIC); + zSql8 = sqlite3ValueText(pTmp, SQLITE_UTF8); + if( !zSql8 ){ + sqlite3Error(db, SQLITE_NOMEM, 0); + return SQLITE_NOMEM; + } + rc = sqlite3_prepare(db, zSql8, -1, ppStmt, &zTail8); + + if( zTail8 && pzTail ){ + /* If sqlite3_prepare returns a tail pointer, we calculate the + ** equivalent pointer into the UTF-16 string by counting the unicode + ** characters between zSql8 and zTail8, and then returning a pointer + ** the same number of characters into the UTF-16 string. + */ + int chars_parsed = sqlite3utf8CharLen(zSql8, zTail8-zSql8); + *pzTail = (u8 *)zSql + sqlite3utf16ByteLen(zSql, chars_parsed); + } + + return rc; +} + +/* +** This routine does the work of opening a database on behalf of +** sqlite3_open() and sqlite3_open16(). The database filename "zFilename" +** is UTF-8 encoded. The fourth argument, "def_enc" is one of the TEXT_* +** macros from sqliteInt.h. If we end up creating a new database file +** (not opening an existing one), the text encoding of the database +** will be set to this value. +*/ +static int openDatabase( + const char *zFilename, /* Database filename UTF-8 encoded */ + sqlite3 **ppDb /* OUT: Returned database handle */ +){ + sqlite3 *db; + int rc, i; + char *zErrMsg = 0; + + /* Allocate the sqlite data structure */ + db = sqliteMalloc( sizeof(sqlite3) ); + if( db==0 ) goto opendb_out; + db->priorNewRowid = 0; + db->magic = SQLITE_MAGIC_BUSY; + db->nDb = 2; + db->aDb = db->aDbStatic; + db->enc = SQLITE_UTF8; + db->autoCommit = 1; + /* db->flags |= SQLITE_ShortColNames; */ + sqlite3HashInit(&db->aFunc, SQLITE_HASH_STRING, 0); + sqlite3HashInit(&db->aCollSeq, SQLITE_HASH_STRING, 0); + for(i=0; inDb; i++){ + sqlite3HashInit(&db->aDb[i].tblHash, SQLITE_HASH_STRING, 0); + sqlite3HashInit(&db->aDb[i].idxHash, SQLITE_HASH_STRING, 0); + sqlite3HashInit(&db->aDb[i].trigHash, SQLITE_HASH_STRING, 0); + sqlite3HashInit(&db->aDb[i].aFKey, SQLITE_HASH_STRING, 1); + } + + /* Add the default collation sequence BINARY. BINARY works for both UTF-8 + ** and UTF-16, so add a version for each to avoid any unnecessary + ** conversions. The only error that can occur here is a malloc() failure. + */ + sqlite3_create_collation(db, "BINARY", SQLITE_UTF8, 0,binaryCollatingFunc); + sqlite3_create_collation(db, "BINARY", SQLITE_UTF16LE, 0,binaryCollatingFunc); + sqlite3_create_collation(db, "BINARY", SQLITE_UTF16BE, 0,binaryCollatingFunc); + db->pDfltColl = sqlite3FindCollSeq(db, db->enc, "BINARY", 6, 0); + if( !db->pDfltColl ){ + rc = db->errCode; + assert( rc!=SQLITE_OK ); + db->magic = SQLITE_MAGIC_CLOSED; + goto opendb_out; + } + + /* Also add a UTF-8 case-insensitive collation sequence. */ + sqlite3_create_collation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc); + + /* Open the backend database driver */ + rc = sqlite3BtreeFactory(db, zFilename, 0, MAX_PAGES, &db->aDb[0].pBt); + if( rc!=SQLITE_OK ){ + sqlite3Error(db, rc, 0); + db->magic = SQLITE_MAGIC_CLOSED; + goto opendb_out; + } + db->aDb[0].zName = "main"; + db->aDb[1].zName = "temp"; + + /* The default safety_level for the main database is 'full' for the temp + ** database it is 'NONE'. This matches the pager layer defaults. */ + db->aDb[0].safety_level = 3; + db->aDb[1].safety_level = 1; + + /* Register all built-in functions, but do not attempt to read the + ** database schema yet. This is delayed until the first time the database + ** is accessed. + */ + sqlite3RegisterBuiltinFunctions(db); + if( rc==SQLITE_OK ){ + sqlite3Error(db, SQLITE_OK, 0); + db->magic = SQLITE_MAGIC_OPEN; + }else{ + sqlite3Error(db, rc, "%s", zErrMsg, 0); + if( zErrMsg ) sqliteFree(zErrMsg); + db->magic = SQLITE_MAGIC_CLOSED; + } + +opendb_out: + if( sqlite3_errcode(db)==SQLITE_OK && sqlite3_malloc_failed ){ + sqlite3Error(db, SQLITE_NOMEM, 0); + } + *ppDb = db; + return sqlite3_errcode(db); +} + +/* +** Open a new database handle. +*/ +int sqlite3_open( + const char *zFilename, + sqlite3 **ppDb +){ + return openDatabase(zFilename, ppDb); +} + +/* +** Open a new database handle. +*/ +int sqlite3_open16( + const void *zFilename, + sqlite3 **ppDb +){ + char const *zFilename8; /* zFilename encoded in UTF-8 instead of UTF-16 */ + int rc = SQLITE_NOMEM; + sqlite3_value *pVal; + + assert( ppDb ); + *ppDb = 0; + pVal = sqlite3ValueNew(); + sqlite3ValueSetStr(pVal, -1, zFilename, SQLITE_UTF16NATIVE, SQLITE_STATIC); + zFilename8 = sqlite3ValueText(pVal, SQLITE_UTF8); + if( zFilename8 ){ + rc = openDatabase(zFilename8, ppDb); + if( rc==SQLITE_OK && *ppDb ){ + sqlite3_exec(*ppDb, "PRAGMA encoding = 'UTF-16'", 0, 0, 0); + } + } + if( pVal ){ + sqlite3ValueFree(pVal); + } + + return rc; +} + +/* +** The following routine destroys a virtual machine that is created by +** the sqlite3_compile() routine. The integer returned is an SQLITE_ +** success/failure code that describes the result of executing the virtual +** machine. +** +** This routine sets the error code and string returned by +** sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16(). +*/ +int sqlite3_finalize(sqlite3_stmt *pStmt){ + int rc; + if( pStmt==0 ){ + rc = SQLITE_OK; + }else{ + rc = sqlite3VdbeFinalize((Vdbe*)pStmt); + } + return rc; +} + +/* +** Terminate the current execution of an SQL statement and reset it +** back to its starting state so that it can be reused. A success code from +** the prior execution is returned. +** +** This routine sets the error code and string returned by +** sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16(). +*/ +int sqlite3_reset(sqlite3_stmt *pStmt){ + int rc; + if( pStmt==0 ){ + rc = SQLITE_OK; + }else{ + rc = sqlite3VdbeReset((Vdbe*)pStmt); + sqlite3VdbeMakeReady((Vdbe*)pStmt, -1, 0, 0, 0); + } + return rc; +} + +/* +** Register a new collation sequence with the database handle db. +*/ +int sqlite3_create_collation( + sqlite3* db, + const char *zName, + int enc, + void* pCtx, + int(*xCompare)(void*,int,const void*,int,const void*) +){ + CollSeq *pColl; + int rc = SQLITE_OK; + + if( sqlite3SafetyCheck(db) ){ + return SQLITE_MISUSE; + } + + /* If SQLITE_UTF16 is specified as the encoding type, transform this + ** to one of SQLITE_UTF16LE or SQLITE_UTF16BE using the + ** SQLITE_UTF16NATIVE macro. SQLITE_UTF16 is not used internally. + */ + if( enc==SQLITE_UTF16 ){ + enc = SQLITE_UTF16NATIVE; + } + + if( enc!=SQLITE_UTF8 && enc!=SQLITE_UTF16LE && enc!=SQLITE_UTF16BE ){ + sqlite3Error(db, SQLITE_ERROR, + "Param 3 to sqlite3_create_collation() must be one of " + "SQLITE_UTF8, SQLITE_UTF16, SQLITE_UTF16LE or SQLITE_UTF16BE" + ); + return SQLITE_ERROR; + } + pColl = sqlite3FindCollSeq(db, (u8)enc, zName, strlen(zName), 1); + if( 0==pColl ){ + rc = SQLITE_NOMEM; + }else{ + pColl->xCmp = xCompare; + pColl->pUser = pCtx; + pColl->enc = enc; + } + sqlite3Error(db, rc, 0); + return rc; +} + +/* +** Register a new collation sequence with the database handle db. +*/ +int sqlite3_create_collation16( + sqlite3* db, + const char *zName, + int enc, + void* pCtx, + int(*xCompare)(void*,int,const void*,int,const void*) +){ + char const *zName8; + sqlite3_value *pTmp; + if( sqlite3SafetyCheck(db) ){ + return SQLITE_MISUSE; + } + pTmp = sqlite3GetTransientValue(db); + sqlite3ValueSetStr(pTmp, -1, zName, SQLITE_UTF16NATIVE, SQLITE_STATIC); + zName8 = sqlite3ValueText(pTmp, SQLITE_UTF8); + return sqlite3_create_collation(db, zName8, enc, pCtx, xCompare); +} + +/* +** Register a collation sequence factory callback with the database handle +** db. Replace any previously installed collation sequence factory. +*/ +int sqlite3_collation_needed( + sqlite3 *db, + void *pCollNeededArg, + void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*) +){ + if( sqlite3SafetyCheck(db) ){ + return SQLITE_MISUSE; + } + db->xCollNeeded = xCollNeeded; + db->xCollNeeded16 = 0; + db->pCollNeededArg = pCollNeededArg; + return SQLITE_OK; +} + +/* +** Register a collation sequence factory callback with the database handle +** db. Replace any previously installed collation sequence factory. +*/ +int sqlite3_collation_needed16( + sqlite3 *db, + void *pCollNeededArg, + void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*) +){ + if( sqlite3SafetyCheck(db) ){ + return SQLITE_MISUSE; + } + db->xCollNeeded = 0; + db->xCollNeeded16 = xCollNeeded16; + db->pCollNeededArg = pCollNeededArg; + return SQLITE_OK; +} diff --git a/kopete/plugins/statistics/sqlite/opcodes.c b/kopete/plugins/statistics/sqlite/opcodes.c new file mode 100644 index 00000000..b6f01219 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/opcodes.c @@ -0,0 +1,128 @@ +/* Automatically generated. Do not edit */ +/* See the mkopcodec.h script for details. */ +const char *const sqlite3OpcodeNames[] = { "?", + "ContextPop", + "IntegrityCk", + "DropTrigger", + "DropIndex", + "Recno", + "KeyAsData", + "Delete", + "MoveGt", + "VerifyCookie", + "Push", + "Dup", + "Blob", + "IdxGT", + "IdxRecno", + "RowKey", + "PutStrKey", + "IsUnique", + "SetNumColumns", + "IdxIsNull", + "NullRow", + "OpenPseudo", + "OpenWrite", + "OpenRead", + "Transaction", + "AutoCommit", + "Pop", + "Halt", + "Vacuum", + "ListRead", + "RowData", + "NotExists", + "MoveLe", + "SetCookie", + "Variable", + "AggNext", + "AggReset", + "Sort", + "IdxDelete", + "ResetCount", + "OpenTemp", + "IdxColumn", + "Integer", + "AggSet", + "CreateIndex", + "IdxPut", + "MoveLt", + "Return", + "MemLoad", + "SortNext", + "IdxLT", + "Rewind", + "AddImm", + "AggFunc", + "AggInit", + "MemIncr", + "ListReset", + "Clear", + "Or", + "And", + "Not", + "PutIntKey", + "If", + "Callback", + "IsNull", + "NotNull", + "Ne", + "Eq", + "Gt", + "Le", + "Lt", + "Ge", + "BitAnd", + "BitOr", + "ShiftLeft", + "ShiftRight", + "Add", + "Subtract", + "Multiply", + "Divide", + "Remainder", + "Concat", + "Negative", + "SortReset", + "BitNot", + "String8", + "SortPut", + "Last", + "NotFound", + "MakeRecord", + "String", + "Goto", + "AggFocus", + "DropTable", + "Column", + "Noop", + "AggGet", + "CreateTable", + "NewRecno", + "Found", + "Distinct", + "Close", + "Statement", + "IfNot", + "Pull", + "MemStore", + "Next", + "Prev", + "MoveGe", + "MustBeInt", + "ForceInt", + "CollSeq", + "Gosub", + "ContextPush", + "ListRewind", + "ListWrite", + "ParseSchema", + "Destroy", + "IdxGE", + "FullKey", + "ReadCookie", + "AbsValue", + "Real", + "HexBlob", + "Function", +}; diff --git a/kopete/plugins/statistics/sqlite/opcodes.h b/kopete/plugins/statistics/sqlite/opcodes.h new file mode 100644 index 00000000..7b792c5a --- /dev/null +++ b/kopete/plugins/statistics/sqlite/opcodes.h @@ -0,0 +1,126 @@ +/* Automatically generated. Do not edit */ +/* See the mkopcodeh.awk script for details */ +#define OP_ContextPop 1 +#define OP_IntegrityCk 2 +#define OP_DropTrigger 3 +#define OP_DropIndex 4 +#define OP_Recno 5 +#define OP_KeyAsData 6 +#define OP_Delete 7 +#define OP_MoveGt 8 +#define OP_VerifyCookie 9 +#define OP_Push 10 +#define OP_Dup 11 +#define OP_Blob 12 +#define OP_IdxGT 13 +#define OP_IdxRecno 14 +#define OP_RowKey 15 +#define OP_PutStrKey 16 +#define OP_IsUnique 17 +#define OP_SetNumColumns 18 +#define OP_Eq 67 +#define OP_IdxIsNull 19 +#define OP_NullRow 20 +#define OP_OpenPseudo 21 +#define OP_OpenWrite 22 +#define OP_OpenRead 23 +#define OP_Transaction 24 +#define OP_AutoCommit 25 +#define OP_Negative 82 +#define OP_Pop 26 +#define OP_Halt 27 +#define OP_Vacuum 28 +#define OP_ListRead 29 +#define OP_RowData 30 +#define OP_NotExists 31 +#define OP_MoveLe 32 +#define OP_SetCookie 33 +#define OP_Variable 34 +#define OP_AggNext 35 +#define OP_AggReset 36 +#define OP_Sort 37 +#define OP_IdxDelete 38 +#define OP_ResetCount 39 +#define OP_OpenTemp 40 +#define OP_IdxColumn 41 +#define OP_NotNull 65 +#define OP_Ge 71 +#define OP_Remainder 80 +#define OP_Divide 79 +#define OP_Integer 42 +#define OP_AggSet 43 +#define OP_CreateIndex 44 +#define OP_IdxPut 45 +#define OP_MoveLt 46 +#define OP_And 59 +#define OP_ShiftLeft 74 +#define OP_Real 122 +#define OP_Return 47 +#define OP_MemLoad 48 +#define OP_SortNext 49 +#define OP_IdxLT 50 +#define OP_Rewind 51 +#define OP_Gt 68 +#define OP_AddImm 52 +#define OP_Subtract 77 +#define OP_AggFunc 53 +#define OP_AggInit 54 +#define OP_MemIncr 55 +#define OP_ListReset 56 +#define OP_Clear 57 +#define OP_PutIntKey 61 +#define OP_IsNull 64 +#define OP_If 62 +#define OP_Callback 63 +#define OP_SortReset 83 +#define OP_SortPut 86 +#define OP_Last 87 +#define OP_NotFound 88 +#define OP_MakeRecord 89 +#define OP_BitAnd 72 +#define OP_Add 76 +#define OP_HexBlob 123 +#define OP_String 90 +#define OP_Goto 91 +#define OP_AggFocus 92 +#define OP_DropTable 93 +#define OP_Column 94 +#define OP_Noop 95 +#define OP_Not 60 +#define OP_Le 69 +#define OP_BitOr 73 +#define OP_Multiply 78 +#define OP_String8 85 +#define OP_AggGet 96 +#define OP_CreateTable 97 +#define OP_NewRecno 98 +#define OP_Found 99 +#define OP_Distinct 100 +#define OP_Close 101 +#define OP_Statement 102 +#define OP_IfNot 103 +#define OP_Pull 104 +#define OP_MemStore 105 +#define OP_Next 106 +#define OP_Prev 107 +#define OP_MoveGe 108 +#define OP_Lt 70 +#define OP_Ne 66 +#define OP_MustBeInt 109 +#define OP_ForceInt 110 +#define OP_ShiftRight 75 +#define OP_CollSeq 111 +#define OP_Gosub 112 +#define OP_ContextPush 113 +#define OP_ListRewind 114 +#define OP_ListWrite 115 +#define OP_ParseSchema 116 +#define OP_Destroy 117 +#define OP_IdxGE 118 +#define OP_FullKey 119 +#define OP_ReadCookie 120 +#define OP_BitNot 84 +#define OP_AbsValue 121 +#define OP_Or 58 +#define OP_Function 124 +#define OP_Concat 81 diff --git a/kopete/plugins/statistics/sqlite/os.h b/kopete/plugins/statistics/sqlite/os.h new file mode 100644 index 00000000..fc478baa --- /dev/null +++ b/kopete/plugins/statistics/sqlite/os.h @@ -0,0 +1,197 @@ +/* +** 2001 September 16 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This header file (together with is companion C source-code file +** "os.c") attempt to abstract the underlying operating system so that +** the SQLite library will work on both POSIX and windows systems. +*/ +#ifndef _SQLITE_OS_H_ +#define _SQLITE_OS_H_ + +/* +** Figure out if we are dealing with Unix, Windows or MacOS. +** +** N.B. MacOS means Mac Classic (or Carbon). Treat Darwin (OS X) as Unix. +** The MacOS build is designed to use CodeWarrior (tested with v8) +*/ +#if !defined(OS_UNIX) && !defined(OS_TEST) +# ifndef OS_WIN +# ifndef OS_MAC +# if defined(__MACOS__) +# define OS_MAC 1 +# define OS_WIN 0 +# define OS_UNIX 0 +# elif defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) || defined(__BORLANDC__) +# define OS_MAC 0 +# define OS_WIN 1 +# define OS_UNIX 0 +# else +# define OS_MAC 0 +# define OS_WIN 0 +# define OS_UNIX 1 +# endif +# else +# define OS_WIN 0 +# define OS_UNIX 0 +# endif +# else +# define OS_MAC 0 +# define OS_UNIX 0 +# endif +#else +# define OS_MAC 0 +# ifndef OS_WIN +# define OS_WIN 0 +# endif +#endif + +/* +** Invoke the appropriate operating-system specific header file. +*/ +#if OS_TEST +# include "os_test.h" +#endif +#if OS_UNIX +# include "os_unix.h" +#endif +#if OS_WIN +# include "os_win.h" +#endif +#if OS_MAC +# include "os_mac.h" +#endif + +/* +** Temporary files are named starting with this prefix followed by 16 random +** alphanumeric characters, and no file extension. They are stored in the +** OS's standard temporary file directory, and are deleted prior to exit. +** If sqlite is being embedded in another program, you may wish to change the +** prefix to reflect your program's name, so that if your program exits +** prematurely, old temporary files can be easily identified. This can be done +** using -DTEMP_FILE_PREFIX=myprefix_ on the compiler command line. +*/ +#ifndef TEMP_FILE_PREFIX +# define TEMP_FILE_PREFIX "sqlite_" +#endif + +/* +** The following values may be passed as the second argument to +** sqlite3OsLock(). The various locks exhibit the following semantics: +** +** SHARED: Any number of processes may hold a SHARED lock simultaneously. +** RESERVED: A single process may hold a RESERVED lock on a file at +** any time. Other processes may hold and obtain new SHARED locks. +** PENDING: A single process may hold a PENDING lock on a file at +** any one time. Existing SHARED locks may persist, but no new +** SHARED locks may be obtained by other processes. +** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks. +** +** PENDING_LOCK may not be passed directly to sqlite3OsLock(). Instead, a +** process that requests an EXCLUSIVE lock may actually obtain a PENDING +** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to +** sqlite3OsLock(). +*/ +#define NO_LOCK 0 +#define SHARED_LOCK 1 +#define RESERVED_LOCK 2 +#define PENDING_LOCK 3 +#define EXCLUSIVE_LOCK 4 + +/* +** File Locking Notes: (Mostly about windows but also some info for Unix) +** +** We cannot use LockFileEx() or UnlockFileEx() on Win95/98/ME because +** those functions are not available. So we use only LockFile() and +** UnlockFile(). +** +** LockFile() prevents not just writing but also reading by other processes. +** A SHARED_LOCK is obtained by locking a single randomly-chosen +** byte out of a specific range of bytes. The lock byte is obtained at +** random so two separate readers can probably access the file at the +** same time, unless they are unlucky and choose the same lock byte. +** An EXCLUSIVE_LOCK is obtained by locking all bytes in the range. +** There can only be one writer. A RESERVED_LOCK is obtained by locking +** a single byte of the file that is designated as the reserved lock byte. +** A PENDING_LOCK is obtained by locking a designated byte different from +** the RESERVED_LOCK byte. +** +** On WinNT/2K/XP systems, LockFileEx() and UnlockFileEx() are available, +** which means we can use reader/writer locks. When reader/writer locks +** are used, the lock is placed on the same range of bytes that is used +** for probabilistic locking in Win95/98/ME. Hence, the locking scheme +** will support two or more Win95 readers or two or more WinNT readers. +** But a single Win95 reader will lock out all WinNT readers and a single +** WinNT reader will lock out all other Win95 readers. +** +** The following #defines specify the range of bytes used for locking. +** SHARED_SIZE is the number of bytes available in the pool from which +** a random byte is selected for a shared lock. The pool of bytes for +** shared locks begins at SHARED_FIRST. +** +** These #defines are available in os.h so that Unix can use the same +** byte ranges for locking. This leaves open the possiblity of having +** clients on win95, winNT, and unix all talking to the same shared file +** and all locking correctly. To do so would require that samba (or whatever +** tool is being used for file sharing) implements locks correctly between +** windows and unix. I'm guessing that isn't likely to happen, but by +** using the same locking range we are at least open to the possibility. +** +** Locking in windows is manditory. For this reason, we cannot store +** actual data in the bytes used for locking. The pager never allocates +** the pages involved in locking therefore. SHARED_SIZE is selected so +** that all locks will fit on a single page even at the minimum page size. +** PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE +** is set high so that we don't have to allocate an unused page except +** for very large databases. But one should test the page skipping logic +** by setting PENDING_BYTE low and running the entire regression suite. +** +** Changing the value of PENDING_BYTE results in a subtly incompatible +** file format. Depending on how it is changed, you might not notice +** the incompatibility right away, even running a full regression test. +** The default location of PENDING_BYTE is the first byte past the +** 1GB boundary. +** +*/ +#define PENDING_BYTE 0x40000000 /* First byte past the 1GB boundary */ +/* #define PENDING_BYTE 0x5400 // Page 20 - for testing */ +#define RESERVED_BYTE (PENDING_BYTE+1) +#define SHARED_FIRST (PENDING_BYTE+2) +#define SHARED_SIZE 510 + + +int sqlite3OsDelete(const char*); +int sqlite3OsFileExists(const char*); +int sqlite3OsOpenReadWrite(const char*, OsFile*, int*); +int sqlite3OsOpenExclusive(const char*, OsFile*, int); +int sqlite3OsOpenReadOnly(const char*, OsFile*); +int sqlite3OsOpenDirectory(const char*, OsFile*); +int sqlite3OsSyncDirectory(const char*); +int sqlite3OsTempFileName(char*); +int sqlite3OsClose(OsFile*); +int sqlite3OsRead(OsFile*, void*, int amt); +int sqlite3OsWrite(OsFile*, const void*, int amt); +int sqlite3OsSeek(OsFile*, i64 offset); +int sqlite3OsSync(OsFile*); +int sqlite3OsTruncate(OsFile*, i64 size); +int sqlite3OsFileSize(OsFile*, i64 *pSize); +int sqlite3OsRandomSeed(char*); +int sqlite3OsSleep(int ms); +int sqlite3OsCurrentTime(double*); +int sqlite3OsFileModTime(OsFile*, double*); +void sqlite3OsEnterMutex(void); +void sqlite3OsLeaveMutex(void); +char *sqlite3OsFullPathname(const char*); +int sqlite3OsLock(OsFile*, int); +int sqlite3OsUnlock(OsFile*, int); +int sqlite3OsCheckReservedLock(OsFile *id); + +#endif /* _SQLITE_OS_H_ */ diff --git a/kopete/plugins/statistics/sqlite/os_common.h b/kopete/plugins/statistics/sqlite/os_common.h new file mode 100644 index 00000000..94311b96 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/os_common.h @@ -0,0 +1,107 @@ +/* +** 2004 May 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains macros and a little bit of code that is common to +** all of the platform-specific files (os_*.c) and is #included into those +** files. +** +** This file should be #included by the os_*.c files only. It is not a +** general purpose header file. +*/ + +/* +** At least two bugs have slipped in because we changed the MEMORY_DEBUG +** macro to SQLITE_DEBUG and some older makefiles have not yet made the +** switch. The following code should catch this problem at compile-time. +*/ +#ifdef MEMORY_DEBUG +# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead." +#endif + + +int sqlite3_os_trace = 0; +#ifdef SQLITE_DEBUG +static int last_page = 0; +#define SEEK(X) last_page=(X) +#define TRACE1(X) if( sqlite3_os_trace ) sqlite3DebugPrintf(X) +#define TRACE2(X,Y) if( sqlite3_os_trace ) sqlite3DebugPrintf(X,Y) +#define TRACE3(X,Y,Z) if( sqlite3_os_trace ) sqlite3DebugPrintf(X,Y,Z) +#define TRACE4(X,Y,Z,A) if( sqlite3_os_trace ) sqlite3DebugPrintf(X,Y,Z,A) +#define TRACE5(X,Y,Z,A,B) if( sqlite3_os_trace ) sqlite3DebugPrintf(X,Y,Z,A,B) +#define TRACE6(X,Y,Z,A,B,C) if(sqlite3_os_trace) sqlite3DebugPrintf(X,Y,Z,A,B,C) +#define TRACE7(X,Y,Z,A,B,C,D) \ + if(sqlite3_os_trace) sqlite3DebugPrintf(X,Y,Z,A,B,C,D) +#else +#define SEEK(X) +#define TRACE1(X) +#define TRACE2(X,Y) +#define TRACE3(X,Y,Z) +#define TRACE4(X,Y,Z,A) +#define TRACE5(X,Y,Z,A,B) +#define TRACE6(X,Y,Z,A,B,C) +#define TRACE7(X,Y,Z,A,B,C,D) +#endif + +/* +** Macros for performance tracing. Normally turned off. Only works +** on i486 hardware. +*/ +#ifdef SQLITE_PERFORMANCE_TRACE +__inline__ unsigned long long int hwtime(void){ + unsigned long long int x; + __asm__("rdtsc\n\t" + "mov %%edx, %%ecx\n\t" + :"=A" (x)); + return x; +} +static unsigned long long int g_start; +static unsigned int elapse; +#define TIMER_START g_start=hwtime() +#define TIMER_END elapse=hwtime()-g_start +#define TIMER_ELAPSED elapse +#else +#define TIMER_START +#define TIMER_END +#define TIMER_ELAPSED 0 +#endif + +/* +** If we compile with the SQLITE_TEST macro set, then the following block +** of code will give us the ability to simulate a disk I/O error. This +** is used for testing the I/O recovery logic. +*/ +#ifdef SQLITE_TEST +int sqlite3_io_error_pending = 0; +int sqlite3_diskfull_pending = 0; +#define SimulateIOError(A) \ + if( sqlite3_io_error_pending ) \ + if( sqlite3_io_error_pending-- == 1 ){ local_ioerr(); return A; } +static void local_ioerr(){ + sqlite3_io_error_pending = 0; /* Really just a place to set a breakpoint */ +} +#define SimulateDiskfullError \ + if( sqlite3_diskfull_pending ) \ + if( sqlite3_diskfull_pending-- == 1 ){ local_ioerr(); return SQLITE_FULL; } +#else +#define SimulateIOError(A) +#define SimulateDiskfullError +#endif + +/* +** When testing, keep a count of the number of open files. +*/ +#ifdef SQLITE_TEST +int sqlite3_open_file_count = 0; +#define OpenCounter(X) sqlite3_open_file_count+=(X) +#else +#define OpenCounter(X) +#endif diff --git a/kopete/plugins/statistics/sqlite/os_mac.c b/kopete/plugins/statistics/sqlite/os_mac.c new file mode 100644 index 00000000..f84c168d --- /dev/null +++ b/kopete/plugins/statistics/sqlite/os_mac.c @@ -0,0 +1,738 @@ +/* +** 2004 May 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code that is specific classic mac. Mac OS X +** uses the os_unix.c file, not this one. +*/ +#include "sqliteInt.h" +#include "os.h" +#if OS_MAC /* This file used on classic mac only */ + +#include +#include +#include +#include +#include +#include +#include + +/* +** Macros used to determine whether or not to use threads. +*/ +#if defined(THREADSAFE) && THREADSAFE +# include +# define SQLITE_MACOS_MULTITASKING 1 +#endif + +/* +** Include code that is common to all os_*.c files +*/ +#include "os_common.h" + +/* +** Delete the named file +*/ +int sqlite3OsDelete(const char *zFilename){ + unlink(zFilename); + return SQLITE_OK; +} + +/* +** Return TRUE if the named file exists. +*/ +int sqlite3OsFileExists(const char *zFilename){ + return access(zFilename, 0)==0; +} + +/* +** Attempt to open a file for both reading and writing. If that +** fails, try opening it read-only. If the file does not exist, +** try to create it. +** +** On success, a handle for the open file is written to *id +** and *pReadonly is set to 0 if the file was opened for reading and +** writing or 1 if the file was opened read-only. The function returns +** SQLITE_OK. +** +** On failure, the function returns SQLITE_CANTOPEN and leaves +** *id and *pReadonly unchanged. +*/ +int sqlite3OsOpenReadWrite( + const char *zFilename, + OsFile *id, + int *pReadonly +){ + FSSpec fsSpec; +# ifdef _LARGE_FILE + HFSUniStr255 dfName; + FSRef fsRef; + if( __path2fss(zFilename, &fsSpec) != noErr ){ + if( HCreate(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, 'SQLI', cDocumentFile) != noErr ) + return SQLITE_CANTOPEN; + } + if( FSpMakeFSRef(&fsSpec, &fsRef) != noErr ) + return SQLITE_CANTOPEN; + FSGetDataForkName(&dfName); + if( FSOpenFork(&fsRef, dfName.length, dfName.unicode, + fsRdWrShPerm, &(id->refNum)) != noErr ){ + if( FSOpenFork(&fsRef, dfName.length, dfName.unicode, + fsRdWrPerm, &(id->refNum)) != noErr ){ + if (FSOpenFork(&fsRef, dfName.length, dfName.unicode, + fsRdPerm, &(id->refNum)) != noErr ) + return SQLITE_CANTOPEN; + else + *pReadonly = 1; + } else + *pReadonly = 0; + } else + *pReadonly = 0; +# else + __path2fss(zFilename, &fsSpec); + if( !sqlite3OsFileExists(zFilename) ){ + if( HCreate(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, 'SQLI', cDocumentFile) != noErr ) + return SQLITE_CANTOPEN; + } + if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrShPerm, &(id->refNum)) != noErr ){ + if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrPerm, &(id->refNum)) != noErr ){ + if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdPerm, &(id->refNum)) != noErr ) + return SQLITE_CANTOPEN; + else + *pReadonly = 1; + } else + *pReadonly = 0; + } else + *pReadonly = 0; +# endif + if( HOpenRF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrShPerm, &(id->refNumRF)) != noErr){ + id->refNumRF = -1; + } + id->locked = 0; + id->delOnClose = 0; + OpenCounter(+1); + return SQLITE_OK; +} + + +/* +** Attempt to open a new file for exclusive access by this process. +** The file will be opened for both reading and writing. To avoid +** a potential security problem, we do not allow the file to have +** previously existed. Nor do we allow the file to be a symbolic +** link. +** +** If delFlag is true, then make arrangements to automatically delete +** the file when it is closed. +** +** On success, write the file handle into *id and return SQLITE_OK. +** +** On failure, return SQLITE_CANTOPEN. +*/ +int sqlite3OsOpenExclusive(const char *zFilename, OsFile *id, int delFlag){ + FSSpec fsSpec; +# ifdef _LARGE_FILE + HFSUniStr255 dfName; + FSRef fsRef; + __path2fss(zFilename, &fsSpec); + if( HCreate(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, 'SQLI', cDocumentFile) != noErr ) + return SQLITE_CANTOPEN; + if( FSpMakeFSRef(&fsSpec, &fsRef) != noErr ) + return SQLITE_CANTOPEN; + FSGetDataForkName(&dfName); + if( FSOpenFork(&fsRef, dfName.length, dfName.unicode, + fsRdWrPerm, &(id->refNum)) != noErr ) + return SQLITE_CANTOPEN; +# else + __path2fss(zFilename, &fsSpec); + if( HCreate(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, 'SQLI', cDocumentFile) != noErr ) + return SQLITE_CANTOPEN; + if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrPerm, &(id->refNum)) != noErr ) + return SQLITE_CANTOPEN; +# endif + id->refNumRF = -1; + id->locked = 0; + id->delOnClose = delFlag; + if (delFlag) + id->pathToDel = sqlite3OsFullPathname(zFilename); + OpenCounter(+1); + return SQLITE_OK; +} + +/* +** Attempt to open a new file for read-only access. +** +** On success, write the file handle into *id and return SQLITE_OK. +** +** On failure, return SQLITE_CANTOPEN. +*/ +int sqlite3OsOpenReadOnly(const char *zFilename, OsFile *id){ + FSSpec fsSpec; +# ifdef _LARGE_FILE + HFSUniStr255 dfName; + FSRef fsRef; + if( __path2fss(zFilename, &fsSpec) != noErr ) + return SQLITE_CANTOPEN; + if( FSpMakeFSRef(&fsSpec, &fsRef) != noErr ) + return SQLITE_CANTOPEN; + FSGetDataForkName(&dfName); + if( FSOpenFork(&fsRef, dfName.length, dfName.unicode, + fsRdPerm, &(id->refNum)) != noErr ) + return SQLITE_CANTOPEN; +# else + __path2fss(zFilename, &fsSpec); + if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdPerm, &(id->refNum)) != noErr ) + return SQLITE_CANTOPEN; +# endif + if( HOpenRF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrShPerm, &(id->refNumRF)) != noErr){ + id->refNumRF = -1; + } + id->locked = 0; + id->delOnClose = 0; + OpenCounter(+1); + return SQLITE_OK; +} + +/* +** Attempt to open a file descriptor for the directory that contains a +** file. This file descriptor can be used to fsync() the directory +** in order to make sure the creation of a new file is actually written +** to disk. +** +** This routine is only meaningful for Unix. It is a no-op under +** windows since windows does not support hard links. +** +** On success, a handle for a previously open file is at *id is +** updated with the new directory file descriptor and SQLITE_OK is +** returned. +** +** On failure, the function returns SQLITE_CANTOPEN and leaves +** *id unchanged. +*/ +int sqlite3OsOpenDirectory( + const char *zDirname, + OsFile *id +){ + return SQLITE_OK; +} + +/* +** Create a temporary file name in zBuf. zBuf must be big enough to +** hold at least SQLITE_TEMPNAME_SIZE characters. +*/ +int sqlite3OsTempFileName(char *zBuf){ + static char zChars[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; + int i, j; + char zTempPath[SQLITE_TEMPNAME_SIZE]; + char zdirName[32]; + CInfoPBRec infoRec; + Str31 dirName; + memset(&infoRec, 0, sizeof(infoRec)); + memset(zTempPath, 0, SQLITE_TEMPNAME_SIZE); + if( FindFolder(kOnSystemDisk, kTemporaryFolderType, kCreateFolder, + &(infoRec.dirInfo.ioVRefNum), &(infoRec.dirInfo.ioDrParID)) == noErr ){ + infoRec.dirInfo.ioNamePtr = dirName; + do{ + infoRec.dirInfo.ioFDirIndex = -1; + infoRec.dirInfo.ioDrDirID = infoRec.dirInfo.ioDrParID; + if( PBGetCatInfoSync(&infoRec) == noErr ){ + CopyPascalStringToC(dirName, zdirName); + i = strlen(zdirName); + memmove(&(zTempPath[i+1]), zTempPath, strlen(zTempPath)); + strcpy(zTempPath, zdirName); + zTempPath[i] = ':'; + }else{ + *zTempPath = 0; + break; + } + } while( infoRec.dirInfo.ioDrDirID != fsRtDirID ); + } + if( *zTempPath == 0 ) + getcwd(zTempPath, SQLITE_TEMPNAME_SIZE-24); + for(;;){ + sprintf(zBuf, "%s"TEMP_FILE_PREFIX, zTempPath); + j = strlen(zBuf); + sqlite3Randomness(15, &zBuf[j]); + for(i=0; i<15; i++, j++){ + zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; + } + zBuf[j] = 0; + if( !sqlite3OsFileExists(zBuf) ) break; + } + return SQLITE_OK; +} + +/* +** Close a file. +*/ +int sqlite3OsClose(OsFile *id){ + if( id->refNumRF!=-1 ) + FSClose(id->refNumRF); +# ifdef _LARGE_FILE + FSCloseFork(id->refNum); +# else + FSClose(id->refNum); +# endif + if( id->delOnClose ){ + unlink(id->pathToDel); + sqliteFree(id->pathToDel); + } + OpenCounter(-1); + return SQLITE_OK; +} + +/* +** Read data from a file into a buffer. Return SQLITE_OK if all +** bytes were read successfully and SQLITE_IOERR if anything goes +** wrong. +*/ +int sqlite3OsRead(OsFile *id, void *pBuf, int amt){ + int got; + SimulateIOError(SQLITE_IOERR); + TRACE2("READ %d\n", last_page); +# ifdef _LARGE_FILE + FSReadFork(id->refNum, fsAtMark, 0, (ByteCount)amt, pBuf, (ByteCount*)&got); +# else + got = amt; + FSRead(id->refNum, &got, pBuf); +# endif + if( got==amt ){ + return SQLITE_OK; + }else{ + return SQLITE_IOERR; + } +} + +/* +** Write data from a buffer into a file. Return SQLITE_OK on success +** or some other error code on failure. +*/ +int sqlite3OsWrite(OsFile *id, const void *pBuf, int amt){ + OSErr oserr; + int wrote = 0; + SimulateIOError(SQLITE_IOERR); + TRACE2("WRITE %d\n", last_page); + while( amt>0 ){ +# ifdef _LARGE_FILE + oserr = FSWriteFork(id->refNum, fsAtMark, 0, + (ByteCount)amt, pBuf, (ByteCount*)&wrote); +# else + wrote = amt; + oserr = FSWrite(id->refNum, &wrote, pBuf); +# endif + if( wrote == 0 || oserr != noErr) + break; + amt -= wrote; + pBuf = &((char*)pBuf)[wrote]; + } + if( oserr != noErr || amt>wrote ){ + return SQLITE_FULL; + } + return SQLITE_OK; +} + +/* +** Move the read/write pointer in a file. +*/ +int sqlite3OsSeek(OsFile *id, off_t offset){ + off_t curSize; + SEEK(offset/1024 + 1); + if( sqlite3OsFileSize(id, &curSize) != SQLITE_OK ){ + return SQLITE_IOERR; + } + if( offset >= curSize ){ + if( sqlite3OsTruncate(id, offset+1) != SQLITE_OK ){ + return SQLITE_IOERR; + } + } +# ifdef _LARGE_FILE + if( FSSetForkPosition(id->refNum, fsFromStart, offset) != noErr ){ +# else + if( SetFPos(id->refNum, fsFromStart, offset) != noErr ){ +# endif + return SQLITE_IOERR; + }else{ + return SQLITE_OK; + } +} + +/* +** Make sure all writes to a particular file are committed to disk. +** +** Under Unix, also make sure that the directory entry for the file +** has been created by fsync-ing the directory that contains the file. +** If we do not do this and we encounter a power failure, the directory +** entry for the journal might not exist after we reboot. The next +** SQLite to access the file will not know that the journal exists (because +** the directory entry for the journal was never created) and the transaction +** will not roll back - possibly leading to database corruption. +*/ +int sqlite3OsSync(OsFile *id){ +# ifdef _LARGE_FILE + if( FSFlushFork(id->refNum) != noErr ){ +# else + ParamBlockRec params; + memset(¶ms, 0, sizeof(ParamBlockRec)); + params.ioParam.ioRefNum = id->refNum; + if( PBFlushFileSync(¶ms) != noErr ){ +# endif + return SQLITE_IOERR; + }else{ + return SQLITE_OK; + } +} + +/* +** Sync the directory zDirname. This is a no-op on operating systems other +** than UNIX. +*/ +int sqlite3OsSyncDirectory(const char *zDirname){ + SimulateIOError(SQLITE_IOERR); + return SQLITE_OK; +} + +/* +** Truncate an open file to a specified size +*/ +int sqlite3OsTruncate(OsFile *id, off_t nByte){ + SimulateIOError(SQLITE_IOERR); +# ifdef _LARGE_FILE + if( FSSetForkSize(id->refNum, fsFromStart, nByte) != noErr){ +# else + if( SetEOF(id->refNum, nByte) != noErr ){ +# endif + return SQLITE_IOERR; + }else{ + return SQLITE_OK; + } +} + +/* +** Determine the current size of a file in bytes +*/ +int sqlite3OsFileSize(OsFile *id, off_t *pSize){ +# ifdef _LARGE_FILE + if( FSGetForkSize(id->refNum, pSize) != noErr){ +# else + if( GetEOF(id->refNum, pSize) != noErr ){ +# endif + return SQLITE_IOERR; + }else{ + return SQLITE_OK; + } +} + +/* +** Windows file locking notes: [similar issues apply to MacOS] +** +** We cannot use LockFileEx() or UnlockFileEx() on Win95/98/ME because +** those functions are not available. So we use only LockFile() and +** UnlockFile(). +** +** LockFile() prevents not just writing but also reading by other processes. +** (This is a design error on the part of Windows, but there is nothing +** we can do about that.) So the region used for locking is at the +** end of the file where it is unlikely to ever interfere with an +** actual read attempt. +** +** A database read lock is obtained by locking a single randomly-chosen +** byte out of a specific range of bytes. The lock byte is obtained at +** random so two separate readers can probably access the file at the +** same time, unless they are unlucky and choose the same lock byte. +** A database write lock is obtained by locking all bytes in the range. +** There can only be one writer. +** +** A lock is obtained on the first byte of the lock range before acquiring +** either a read lock or a write lock. This prevents two processes from +** attempting to get a lock at a same time. The semantics of +** sqlite3OsReadLock() require that if there is already a write lock, that +** lock is converted into a read lock atomically. The lock on the first +** byte allows us to drop the old write lock and get the read lock without +** another process jumping into the middle and messing us up. The same +** argument applies to sqlite3OsWriteLock(). +** +** On WinNT/2K/XP systems, LockFileEx() and UnlockFileEx() are available, +** which means we can use reader/writer locks. When reader writer locks +** are used, the lock is placed on the same range of bytes that is used +** for probabilistic locking in Win95/98/ME. Hence, the locking scheme +** will support two or more Win95 readers or two or more WinNT readers. +** But a single Win95 reader will lock out all WinNT readers and a single +** WinNT reader will lock out all other Win95 readers. +** +** Note: On MacOS we use the resource fork for locking. +** +** The following #defines specify the range of bytes used for locking. +** N_LOCKBYTE is the number of bytes available for doing the locking. +** The first byte used to hold the lock while the lock is changing does +** not count toward this number. FIRST_LOCKBYTE is the address of +** the first byte in the range of bytes used for locking. +*/ +#define N_LOCKBYTE 10239 +#define FIRST_LOCKBYTE (0x000fffff - N_LOCKBYTE) + +/* +** Change the status of the lock on the file "id" to be a readlock. +** If the file was write locked, then this reduces the lock to a read. +** If the file was read locked, then this acquires a new read lock. +** +** Return SQLITE_OK on success and SQLITE_BUSY on failure. If this +** library was compiled with large file support (LFS) but LFS is not +** available on the host, then an SQLITE_NOLFS is returned. +*/ +int sqlite3OsReadLock(OsFile *id){ + int rc; + if( id->locked>0 || id->refNumRF == -1 ){ + rc = SQLITE_OK; + }else{ + int lk; + OSErr res; + int cnt = 5; + ParamBlockRec params; + sqlite3Randomness(sizeof(lk), &lk); + lk = (lk & 0x7fffffff)%N_LOCKBYTE + 1; + memset(¶ms, 0, sizeof(params)); + params.ioParam.ioRefNum = id->refNumRF; + params.ioParam.ioPosMode = fsFromStart; + params.ioParam.ioPosOffset = FIRST_LOCKBYTE; + params.ioParam.ioReqCount = 1; + while( cnt-->0 && (res = PBLockRangeSync(¶ms))!=noErr ){ + UInt32 finalTicks; + Delay(1, &finalTicks); /* 1/60 sec */ + } + if( res == noErr ){ + params.ioParam.ioPosOffset = FIRST_LOCKBYTE+1; + params.ioParam.ioReqCount = N_LOCKBYTE; + PBUnlockRangeSync(¶ms); + params.ioParam.ioPosOffset = FIRST_LOCKBYTE+lk; + params.ioParam.ioReqCount = 1; + res = PBLockRangeSync(¶ms); + params.ioParam.ioPosOffset = FIRST_LOCKBYTE; + params.ioParam.ioReqCount = 1; + PBUnlockRangeSync(¶ms); + } + if( res == noErr ){ + id->locked = lk; + rc = SQLITE_OK; + }else{ + rc = SQLITE_BUSY; + } + } + return rc; +} + +/* +** Change the lock status to be an exclusive or write lock. Return +** SQLITE_OK on success and SQLITE_BUSY on a failure. If this +** library was compiled with large file support (LFS) but LFS is not +** available on the host, then an SQLITE_NOLFS is returned. +*/ +int sqlite3OsWriteLock(OsFile *id){ + int rc; + if( id->locked<0 || id->refNumRF == -1 ){ + rc = SQLITE_OK; + }else{ + OSErr res; + int cnt = 5; + ParamBlockRec params; + memset(¶ms, 0, sizeof(params)); + params.ioParam.ioRefNum = id->refNumRF; + params.ioParam.ioPosMode = fsFromStart; + params.ioParam.ioPosOffset = FIRST_LOCKBYTE; + params.ioParam.ioReqCount = 1; + while( cnt-->0 && (res = PBLockRangeSync(¶ms))!=noErr ){ + UInt32 finalTicks; + Delay(1, &finalTicks); /* 1/60 sec */ + } + if( res == noErr ){ + params.ioParam.ioPosOffset = FIRST_LOCKBYTE + id->locked; + params.ioParam.ioReqCount = 1; + if( id->locked==0 + || PBUnlockRangeSync(¶ms)==noErr ){ + params.ioParam.ioPosOffset = FIRST_LOCKBYTE+1; + params.ioParam.ioReqCount = N_LOCKBYTE; + res = PBLockRangeSync(¶ms); + }else{ + res = afpRangeNotLocked; + } + params.ioParam.ioPosOffset = FIRST_LOCKBYTE; + params.ioParam.ioReqCount = 1; + PBUnlockRangeSync(¶ms); + } + if( res == noErr ){ + id->locked = -1; + rc = SQLITE_OK; + }else{ + rc = SQLITE_BUSY; + } + } + return rc; +} + +/* +** Unlock the given file descriptor. If the file descriptor was +** not previously locked, then this routine is a no-op. If this +** library was compiled with large file support (LFS) but LFS is not +** available on the host, then an SQLITE_NOLFS is returned. +*/ +int sqlite3OsUnlock(OsFile *id){ + int rc; + ParamBlockRec params; + memset(¶ms, 0, sizeof(params)); + params.ioParam.ioRefNum = id->refNumRF; + params.ioParam.ioPosMode = fsFromStart; + if( id->locked==0 || id->refNumRF == -1 ){ + rc = SQLITE_OK; + }else if( id->locked<0 ){ + params.ioParam.ioPosOffset = FIRST_LOCKBYTE+1; + params.ioParam.ioReqCount = N_LOCKBYTE; + PBUnlockRangeSync(¶ms); + rc = SQLITE_OK; + id->locked = 0; + }else{ + params.ioParam.ioPosOffset = FIRST_LOCKBYTE+id->locked; + params.ioParam.ioReqCount = 1; + PBUnlockRangeSync(¶ms); + rc = SQLITE_OK; + id->locked = 0; + } + return rc; +} + +/* +** Get information to seed the random number generator. The seed +** is written into the buffer zBuf[256]. The calling function must +** supply a sufficiently large buffer. +*/ +int sqlite3OsRandomSeed(char *zBuf){ + /* We have to initialize zBuf to prevent valgrind from reporting + ** errors. The reports issued by valgrind are incorrect - we would + ** prefer that the randomness be increased by making use of the + ** uninitialized space in zBuf - but valgrind errors tend to worry + ** some users. Rather than argue, it seems easier just to initialize + ** the whole array and silence valgrind, even if that means less randomness + ** in the random seed. + ** + ** When testing, initializing zBuf[] to zero is all we do. That means + ** that we always use the same random number sequence.* This makes the + ** tests repeatable. + */ + memset(zBuf, 0, 256); +#if !defined(SQLITE_TEST) + { + int pid; + Microseconds((UnsignedWide*)zBuf); + pid = getpid(); + memcpy(&zBuf[sizeof(UnsignedWide)], &pid, sizeof(pid)); + } +#endif + return SQLITE_OK; +} + +/* +** Sleep for a little while. Return the amount of time slept. +*/ +int sqlite3OsSleep(int ms){ + UInt32 finalTicks; + UInt32 ticks = (((UInt32)ms+16)*3)/50; /* 1/60 sec per tick */ + Delay(ticks, &finalTicks); + return (int)((ticks*50)/3); +} + +/* +** Static variables used for thread synchronization +*/ +static int inMutex = 0; +#ifdef SQLITE_MACOS_MULTITASKING + static MPCriticalRegionID criticalRegion; +#endif + +/* +** The following pair of routine implement mutual exclusion for +** multi-threaded processes. Only a single thread is allowed to +** executed code that is surrounded by EnterMutex() and LeaveMutex(). +** +** SQLite uses only a single Mutex. There is not much critical +** code and what little there is executes quickly and without blocking. +*/ +void sqlite3OsEnterMutex(){ +#ifdef SQLITE_MACOS_MULTITASKING + static volatile int notInit = 1; + if( notInit ){ + if( notInit == 2 ) /* as close as you can get to thread safe init */ + MPYield(); + else{ + notInit = 2; + MPCreateCriticalRegion(&criticalRegion); + notInit = 0; + } + } + MPEnterCriticalRegion(criticalRegion, kDurationForever); +#endif + assert( !inMutex ); + inMutex = 1; +} +void sqlite3OsLeaveMutex(){ + assert( inMutex ); + inMutex = 0; +#ifdef SQLITE_MACOS_MULTITASKING + MPExitCriticalRegion(criticalRegion); +#endif +} + +/* +** Turn a relative pathname into a full pathname. Return a pointer +** to the full pathname stored in space obtained from sqliteMalloc(). +** The calling function is responsible for freeing this space once it +** is no longer needed. +*/ +char *sqlite3OsFullPathname(const char *zRelative){ + char *zFull = 0; + if( zRelative[0]==':' ){ + char zBuf[_MAX_PATH+1]; + sqlite3SetString(&zFull, getcwd(zBuf, sizeof(zBuf)), &(zRelative[1]), + (char*)0); + }else{ + if( strchr(zRelative, ':') ){ + sqlite3SetString(&zFull, zRelative, (char*)0); + }else{ + char zBuf[_MAX_PATH+1]; + sqlite3SetString(&zFull, getcwd(zBuf, sizeof(zBuf)), zRelative, (char*)0); + } + } + return zFull; +} + +/* +** The following variable, if set to a non-zero value, becomes the result +** returned from sqlite3OsCurrentTime(). This is used for testing. +*/ +#ifdef SQLITE_TEST +int sqlite3_current_time = 0; +#endif + +/* +** Find the current time (in Universal Coordinated Time). Write the +** current time and date as a Julian Day number into *prNow and +** return 0. Return 1 if the time and date cannot be found. +*/ +int sqlite3OsCurrentTime(double *prNow){ + *prNow = 0.0; /**** FIX ME *****/ +#ifdef SQLITE_TEST + if( sqlite3_current_time ){ + *prNow = sqlite3_current_time/86400.0 + 2440587.5; + } +#endif + return 0; +} + +#endif /* OS_MAC */ diff --git a/kopete/plugins/statistics/sqlite/os_mac.h b/kopete/plugins/statistics/sqlite/os_mac.h new file mode 100644 index 00000000..5b60f818 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/os_mac.h @@ -0,0 +1,41 @@ +/* +** 2004 May 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This header file defines OS-specific features of classic Mac. +** OS X uses the os_unix.h file, not this one. +*/ +#ifndef _SQLITE_OS_MAC_H_ +#define _SQLITE_OS_MAC_H_ + + +#include +#include +#define SQLITE_TEMPNAME_SIZE _MAX_PATH +#define SQLITE_MIN_SLEEP_MS 17 + +/* +** The OsFile structure is a operating-system independing representation +** of an open file handle. It is defined differently for each architecture. +** +** This is the definition for class Mac. +*/ +typedef struct OsFile OsFile; +struct OsFile { + SInt16 refNum; /* Data fork/file reference number */ + SInt16 refNumRF; /* Resource fork reference number (for locking) */ + int locked; /* 0: unlocked, <0: write lock, >0: read lock */ + int delOnClose; /* True if file is to be deleted on close */ + char *pathToDel; /* Name of file to delete on close */ +}; + + +#endif /* _SQLITE_OS_MAC_H_ */ diff --git a/kopete/plugins/statistics/sqlite/os_unix.c b/kopete/plugins/statistics/sqlite/os_unix.c new file mode 100644 index 00000000..94fca701 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/os_unix.c @@ -0,0 +1,1276 @@ +/* +** 2004 May 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code that is specific to Unix systems. +*/ +#include "sqliteInt.h" +#include "os.h" +#if OS_UNIX /* This file is used on unix only */ + + +#include +#include +#include +#ifndef O_LARGEFILE +# define O_LARGEFILE 0 +#endif +#ifdef SQLITE_DISABLE_LFS +# undef O_LARGEFILE +# define O_LARGEFILE 0 +#endif +#ifndef O_NOFOLLOW +# define O_NOFOLLOW 0 +#endif +#ifndef O_BINARY +# define O_BINARY 0 +#endif + + +/* +** The DJGPP compiler environment looks mostly like Unix, but it +** lacks the fcntl() system call. So redefine fcntl() to be something +** that always succeeds. This means that locking does not occur under +** DJGPP. But its DOS - what did you expect? +*/ +#ifdef __DJGPP__ +# define fcntl(A,B,C) 0 +#endif + +/* +** Macros used to determine whether or not to use threads. The +** SQLITE_UNIX_THREADS macro is defined if we are synchronizing for +** Posix threads and SQLITE_W32_THREADS is defined if we are +** synchronizing using Win32 threads. +*/ +#if defined(THREADSAFE) && THREADSAFE +# include +# define SQLITE_UNIX_THREADS 1 +#endif + + +/* +** Include code that is common to all os_*.c files +*/ +#include "os_common.h" + +#if defined(THREADSAFE) && THREADSAFE && defined(__linux__) +#define getpid pthread_self +#endif + +/* +** Here is the dirt on POSIX advisory locks: ANSI STD 1003.1 (1996) +** section 6.5.2.2 lines 483 through 490 specify that when a process +** sets or clears a lock, that operation overrides any prior locks set +** by the same process. It does not explicitly say so, but this implies +** that it overrides locks set by the same process using a different +** file descriptor. Consider this test case: +** +** int fd1 = open("./file1", O_RDWR|O_CREAT, 0644); +** int fd2 = open("./file2", O_RDWR|O_CREAT, 0644); +** +** Suppose ./file1 and ./file2 are really the same file (because +** one is a hard or symbolic link to the other) then if you set +** an exclusive lock on fd1, then try to get an exclusive lock +** on fd2, it works. I would have expected the second lock to +** fail since there was already a lock on the file due to fd1. +** But not so. Since both locks came from the same process, the +** second overrides the first, even though they were on different +** file descriptors opened on different file names. +** +** Bummer. If you ask me, this is broken. Badly broken. It means +** that we cannot use POSIX locks to synchronize file access among +** competing threads of the same process. POSIX locks will work fine +** to synchronize access for threads in separate processes, but not +** threads within the same process. +** +** To work around the problem, SQLite has to manage file locks internally +** on its own. Whenever a new database is opened, we have to find the +** specific inode of the database file (the inode is determined by the +** st_dev and st_ino fields of the stat structure that fstat() fills in) +** and check for locks already existing on that inode. When locks are +** created or removed, we have to look at our own internal record of the +** locks to see if another thread has previously set a lock on that same +** inode. +** +** The OsFile structure for POSIX is no longer just an integer file +** descriptor. It is now a structure that holds the integer file +** descriptor and a pointer to a structure that describes the internal +** locks on the corresponding inode. There is one locking structure +** per inode, so if the same inode is opened twice, both OsFile structures +** point to the same locking structure. The locking structure keeps +** a reference count (so we will know when to delete it) and a "cnt" +** field that tells us its internal lock status. cnt==0 means the +** file is unlocked. cnt==-1 means the file has an exclusive lock. +** cnt>0 means there are cnt shared locks on the file. +** +** Any attempt to lock or unlock a file first checks the locking +** structure. The fcntl() system call is only invoked to set a +** POSIX lock if the internal lock structure transitions between +** a locked and an unlocked state. +** +** 2004-Jan-11: +** More recent discoveries about POSIX advisory locks. (The more +** I discover, the more I realize the a POSIX advisory locks are +** an abomination.) +** +** If you close a file descriptor that points to a file that has locks, +** all locks on that file that are owned by the current process are +** released. To work around this problem, each OsFile structure contains +** a pointer to an openCnt structure. There is one openCnt structure +** per open inode, which means that multiple OsFiles can point to a single +** openCnt. When an attempt is made to close an OsFile, if there are +** other OsFiles open on the same inode that are holding locks, the call +** to close() the file descriptor is deferred until all of the locks clear. +** The openCnt structure keeps a list of file descriptors that need to +** be closed and that list is walked (and cleared) when the last lock +** clears. +** +** First, under Linux threads, because each thread has a separate +** process ID, lock operations in one thread do not override locks +** to the same file in other threads. Linux threads behave like +** separate processes in this respect. But, if you close a file +** descriptor in linux threads, all locks are cleared, even locks +** on other threads and even though the other threads have different +** process IDs. Linux threads is inconsistent in this respect. +** (I'm beginning to think that linux threads is an abomination too.) +** The consequence of this all is that the hash table for the lockInfo +** structure has to include the process id as part of its key because +** locks in different threads are treated as distinct. But the +** openCnt structure should not include the process id in its +** key because close() clears lock on all threads, not just the current +** thread. Were it not for this goofiness in linux threads, we could +** combine the lockInfo and openCnt structures into a single structure. +** +** 2004-Jun-28: +** On some versions of linux, threads can override each others locks. +** On others not. Sometimes you can change the behavior on the same +** system by setting the LD_ASSUME_KERNEL environment variable. The +** POSIX standard is silent as to which behavior is correct, as far +** as I can tell, so other versions of unix might show the same +** inconsistency. There is no little doubt in my mind that posix +** advisory locks and linux threads are profoundly broken. +** +** To work around the inconsistencies, we have to test at runtime +** whether or not threads can override each others locks. This test +** is run once, the first time any lock is attempted. A static +** variable is set to record the results of this test for future +** use. +*/ + +/* +** An instance of the following structure serves as the key used +** to locate a particular lockInfo structure given its inode. +** +** If threads cannot override each others locks, then we set the +** lockKey.tid field to the thread ID. If threads can override +** each others locks then tid is always set to zero. tid is also +** set to zero if we compile without threading support. +*/ +struct lockKey { + dev_t dev; /* Device number */ + ino_t ino; /* Inode number */ +#ifdef SQLITE_UNIX_THREADS + pthread_t tid; /* Thread ID or zero if threads cannot override each other */ +#endif +}; + +/* +** An instance of the following structure is allocated for each open +** inode on each thread with a different process ID. (Threads have +** different process IDs on linux, but not on most other unixes.) +** +** A single inode can have multiple file descriptors, so each OsFile +** structure contains a pointer to an instance of this object and this +** object keeps a count of the number of OsFiles pointing to it. +*/ +struct lockInfo { + struct lockKey key; /* The lookup key */ + int cnt; /* Number of SHARED locks held */ + int locktype; /* One of SHARED_LOCK, RESERVED_LOCK etc. */ + int nRef; /* Number of pointers to this structure */ +}; + +/* +** An instance of the following structure serves as the key used +** to locate a particular openCnt structure given its inode. This +** is the same as the lockKey except that the thread ID is omitted. +*/ +struct openKey { + dev_t dev; /* Device number */ + ino_t ino; /* Inode number */ +}; + +/* +** An instance of the following structure is allocated for each open +** inode. This structure keeps track of the number of locks on that +** inode. If a close is attempted against an inode that is holding +** locks, the close is deferred until all locks clear by adding the +** file descriptor to be closed to the pending list. +*/ +struct openCnt { + struct openKey key; /* The lookup key */ + int nRef; /* Number of pointers to this structure */ + int nLock; /* Number of outstanding locks */ + int nPending; /* Number of pending close() operations */ + int *aPending; /* Malloced space holding fd's awaiting a close() */ +}; + +/* +** These hash table maps inodes and process IDs into lockInfo and openCnt +** structures. Access to these hash tables must be protected by a mutex. +*/ +static Hash lockHash = { SQLITE_HASH_BINARY, 0, 0, 0, 0, 0 }; +static Hash openHash = { SQLITE_HASH_BINARY, 0, 0, 0, 0, 0 }; + + +#ifdef SQLITE_UNIX_THREADS +/* +** This variable records whether or not threads can override each others +** locks. +** +** 0: No. Threads cannot override each others locks. +** 1: Yes. Threads can override each others locks. +** -1: We don't know yet. +*/ +static int threadsOverrideEachOthersLocks = -1; + +/* +** This structure holds information passed into individual test +** threads by the testThreadLockingBehavior() routine. +*/ +struct threadTestData { + int fd; /* File to be locked */ + struct flock lock; /* The locking operation */ + int result; /* Result of the locking operation */ +}; + +/* +** The testThreadLockingBehavior() routine launches two separate +** threads on this routine. This routine attempts to lock a file +** descriptor then returns. The success or failure of that attempt +** allows the testThreadLockingBehavior() procedure to determine +** whether or not threads can override each others locks. +*/ +static void *threadLockingTest(void *pArg){ + struct threadTestData *pData = (struct threadTestData*)pArg; + pData->result = fcntl(pData->fd, F_SETLK, &pData->lock); + return pArg; +} + +/* +** This procedure attempts to determine whether or not threads +** can override each others locks then sets the +** threadsOverrideEachOthersLocks variable appropriately. +*/ +static void testThreadLockingBehavior(fd_orig){ + int fd; + struct threadTestData d[2]; + pthread_t t[2]; + + fd = dup(fd_orig); + if( fd<0 ) return; + memset(d, 0, sizeof(d)); + d[0].fd = fd; + d[0].lock.l_type = F_RDLCK; + d[0].lock.l_len = 1; + d[0].lock.l_start = 0; + d[0].lock.l_whence = SEEK_SET; + d[1] = d[0]; + d[1].lock.l_type = F_WRLCK; + pthread_create(&t[0], 0, threadLockingTest, &d[0]); + pthread_create(&t[1], 0, threadLockingTest, &d[1]); + pthread_join(t[0], 0); + pthread_join(t[1], 0); + close(fd); + threadsOverrideEachOthersLocks = d[0].result==0 && d[1].result==0; +} +#endif /* SQLITE_UNIX_THREADS */ + +/* +** Release a lockInfo structure previously allocated by findLockInfo(). +*/ +static void releaseLockInfo(struct lockInfo *pLock){ + pLock->nRef--; + if( pLock->nRef==0 ){ + sqlite3HashInsert(&lockHash, &pLock->key, sizeof(pLock->key), 0); + sqliteFree(pLock); + } +} + +/* +** Release a openCnt structure previously allocated by findLockInfo(). +*/ +static void releaseOpenCnt(struct openCnt *pOpen){ + pOpen->nRef--; + if( pOpen->nRef==0 ){ + sqlite3HashInsert(&openHash, &pOpen->key, sizeof(pOpen->key), 0); + sqliteFree(pOpen->aPending); + sqliteFree(pOpen); + } +} + +/* +** Given a file descriptor, locate lockInfo and openCnt structures that +** describes that file descriptor. Create a new ones if necessary. The +** return values might be unset if an error occurs. +** +** Return the number of errors. +*/ +static int findLockInfo( + int fd, /* The file descriptor used in the key */ + struct lockInfo **ppLock, /* Return the lockInfo structure here */ + struct openCnt **ppOpen /* Return the openCnt structure here */ +){ + int rc; + struct lockKey key1; + struct openKey key2; + struct stat statbuf; + struct lockInfo *pLock; + struct openCnt *pOpen; + rc = fstat(fd, &statbuf); + if( rc!=0 ) return 1; + memset(&key1, 0, sizeof(key1)); + key1.dev = statbuf.st_dev; + key1.ino = statbuf.st_ino; +#ifdef SQLITE_UNIX_THREADS + if( threadsOverrideEachOthersLocks<0 ){ + testThreadLockingBehavior(fd); + } + key1.tid = threadsOverrideEachOthersLocks ? 0 : pthread_self(); +#endif + memset(&key2, 0, sizeof(key2)); + key2.dev = statbuf.st_dev; + key2.ino = statbuf.st_ino; + pLock = (struct lockInfo*)sqlite3HashFind(&lockHash, &key1, sizeof(key1)); + if( pLock==0 ){ + struct lockInfo *pOld; + pLock = sqliteMallocRaw( sizeof(*pLock) ); + if( pLock==0 ) return 1; + pLock->key = key1; + pLock->nRef = 1; + pLock->cnt = 0; + pLock->locktype = 0; + pOld = sqlite3HashInsert(&lockHash, &pLock->key, sizeof(key1), pLock); + if( pOld!=0 ){ + assert( pOld==pLock ); + sqliteFree(pLock); + return 1; + } + }else{ + pLock->nRef++; + } + *ppLock = pLock; + pOpen = (struct openCnt*)sqlite3HashFind(&openHash, &key2, sizeof(key2)); + if( pOpen==0 ){ + struct openCnt *pOld; + pOpen = sqliteMallocRaw( sizeof(*pOpen) ); + if( pOpen==0 ){ + releaseLockInfo(pLock); + return 1; + } + pOpen->key = key2; + pOpen->nRef = 1; + pOpen->nLock = 0; + pOpen->nPending = 0; + pOpen->aPending = 0; + pOld = sqlite3HashInsert(&openHash, &pOpen->key, sizeof(key2), pOpen); + if( pOld!=0 ){ + assert( pOld==pOpen ); + sqliteFree(pOpen); + releaseLockInfo(pLock); + return 1; + } + }else{ + pOpen->nRef++; + } + *ppOpen = pOpen; + return 0; +} + +/* +** Delete the named file +*/ +int sqlite3OsDelete(const char *zFilename){ + unlink(zFilename); + return SQLITE_OK; +} + +/* +** Return TRUE if the named file exists. +*/ +int sqlite3OsFileExists(const char *zFilename){ + return access(zFilename, 0)==0; +} + +/* +** Attempt to open a file for both reading and writing. If that +** fails, try opening it read-only. If the file does not exist, +** try to create it. +** +** On success, a handle for the open file is written to *id +** and *pReadonly is set to 0 if the file was opened for reading and +** writing or 1 if the file was opened read-only. The function returns +** SQLITE_OK. +** +** On failure, the function returns SQLITE_CANTOPEN and leaves +** *id and *pReadonly unchanged. +*/ +int sqlite3OsOpenReadWrite( + const char *zFilename, + OsFile *id, + int *pReadonly +){ + int rc; + assert( !id->isOpen ); + id->dirfd = -1; + id->h = open(zFilename, O_RDWR|O_CREAT|O_LARGEFILE|O_BINARY, 0644); + if( id->h<0 ){ +#ifdef EISDIR + if( errno==EISDIR ){ + return SQLITE_CANTOPEN; + } +#endif + id->h = open(zFilename, O_RDONLY|O_LARGEFILE|O_BINARY); + if( id->h<0 ){ + return SQLITE_CANTOPEN; + } + *pReadonly = 1; + }else{ + *pReadonly = 0; + } + sqlite3OsEnterMutex(); + rc = findLockInfo(id->h, &id->pLock, &id->pOpen); + sqlite3OsLeaveMutex(); + if( rc ){ + close(id->h); + return SQLITE_NOMEM; + } + id->locktype = 0; + id->isOpen = 1; + TRACE3("OPEN %-3d %s\n", id->h, zFilename); + OpenCounter(+1); + return SQLITE_OK; +} + + +/* +** Attempt to open a new file for exclusive access by this process. +** The file will be opened for both reading and writing. To avoid +** a potential security problem, we do not allow the file to have +** previously existed. Nor do we allow the file to be a symbolic +** link. +** +** If delFlag is true, then make arrangements to automatically delete +** the file when it is closed. +** +** On success, write the file handle into *id and return SQLITE_OK. +** +** On failure, return SQLITE_CANTOPEN. +*/ +int sqlite3OsOpenExclusive(const char *zFilename, OsFile *id, int delFlag){ + int rc; + assert( !id->isOpen ); + if( access(zFilename, 0)==0 ){ + return SQLITE_CANTOPEN; + } + id->dirfd = -1; + id->h = open(zFilename, + O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW|O_LARGEFILE|O_BINARY, 0600); + if( id->h<0 ){ + return SQLITE_CANTOPEN; + } + sqlite3OsEnterMutex(); + rc = findLockInfo(id->h, &id->pLock, &id->pOpen); + sqlite3OsLeaveMutex(); + if( rc ){ + close(id->h); + unlink(zFilename); + return SQLITE_NOMEM; + } + id->locktype = 0; + id->isOpen = 1; + if( delFlag ){ + unlink(zFilename); + } + TRACE3("OPEN-EX %-3d %s\n", id->h, zFilename); + OpenCounter(+1); + return SQLITE_OK; +} + +/* +** Attempt to open a new file for read-only access. +** +** On success, write the file handle into *id and return SQLITE_OK. +** +** On failure, return SQLITE_CANTOPEN. +*/ +int sqlite3OsOpenReadOnly(const char *zFilename, OsFile *id){ + int rc; + assert( !id->isOpen ); + id->dirfd = -1; + id->h = open(zFilename, O_RDONLY|O_LARGEFILE|O_BINARY); + if( id->h<0 ){ + return SQLITE_CANTOPEN; + } + sqlite3OsEnterMutex(); + rc = findLockInfo(id->h, &id->pLock, &id->pOpen); + sqlite3OsLeaveMutex(); + if( rc ){ + close(id->h); + return SQLITE_NOMEM; + } + id->locktype = 0; + id->isOpen = 1; + TRACE3("OPEN-RO %-3d %s\n", id->h, zFilename); + OpenCounter(+1); + return SQLITE_OK; +} + +/* +** Attempt to open a file descriptor for the directory that contains a +** file. This file descriptor can be used to fsync() the directory +** in order to make sure the creation of a new file is actually written +** to disk. +** +** This routine is only meaningful for Unix. It is a no-op under +** windows since windows does not support hard links. +** +** On success, a handle for a previously open file is at *id is +** updated with the new directory file descriptor and SQLITE_OK is +** returned. +** +** On failure, the function returns SQLITE_CANTOPEN and leaves +** *id unchanged. +*/ +int sqlite3OsOpenDirectory( + const char *zDirname, + OsFile *id +){ + if( !id->isOpen ){ + /* Do not open the directory if the corresponding file is not already + ** open. */ + return SQLITE_CANTOPEN; + } + assert( id->dirfd<0 ); + id->dirfd = open(zDirname, O_RDONLY|O_BINARY, 0644); + if( id->dirfd<0 ){ + return SQLITE_CANTOPEN; + } + TRACE3("OPENDIR %-3d %s\n", id->dirfd, zDirname); + return SQLITE_OK; +} + +/* +** If the following global variable points to a string which is the +** name of a directory, then that directory will be used to store +** temporary files. +*/ +const char *sqlite3_temp_directory = 0; + +/* +** Create a temporary file name in zBuf. zBuf must be big enough to +** hold at least SQLITE_TEMPNAME_SIZE characters. +*/ +int sqlite3OsTempFileName(char *zBuf){ + static const char *azDirs[] = { + 0, + "/var/tmp", + "/usr/tmp", + "/tmp", + ".", + }; + static const unsigned char zChars[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; + int i, j; + struct stat buf; + const char *zDir = "."; + azDirs[0] = sqlite3_temp_directory; + for(i=0; iisOpen ); + SimulateIOError(SQLITE_IOERR); + TIMER_START; + got = read(id->h, pBuf, amt); + TIMER_END; + TRACE4("READ %-3d %7d %d\n", id->h, last_page, TIMER_ELAPSED); + SEEK(0); + /* if( got<0 ) got = 0; */ + if( got==amt ){ + return SQLITE_OK; + }else{ + return SQLITE_IOERR; + } +} + +/* +** Write data from a buffer into a file. Return SQLITE_OK on success +** or some other error code on failure. +*/ +int sqlite3OsWrite(OsFile *id, const void *pBuf, int amt){ + int wrote = 0; + assert( id->isOpen ); + SimulateIOError(SQLITE_IOERR); + SimulateDiskfullError; + TIMER_START; + while( amt>0 && (wrote = write(id->h, pBuf, amt))>0 ){ + amt -= wrote; + pBuf = &((char*)pBuf)[wrote]; + } + TIMER_END; + TRACE4("WRITE %-3d %7d %d\n", id->h, last_page, TIMER_ELAPSED); + SEEK(0); + if( amt>0 ){ + return SQLITE_FULL; + } + return SQLITE_OK; +} + +/* +** Move the read/write pointer in a file. +*/ +int sqlite3OsSeek(OsFile *id, i64 offset){ + assert( id->isOpen ); + SEEK(offset/1024 + 1); + lseek(id->h, offset, SEEK_SET); + return SQLITE_OK; +} + +/* +** The fsync() system call does not work as advertised on many +** unix systems. The following procedure is an attempt to make +** it work better. +*/ +static int full_fsync(int fd){ + int rc; +#ifdef F_FULLFSYNC + rc = fcntl(fd, F_FULLFSYNC, 0); + if( rc ) rc = fsync(fd); +#else + rc = fsync(fd); +#endif + return rc; +} + +/* +** Make sure all writes to a particular file are committed to disk. +** +** Under Unix, also make sure that the directory entry for the file +** has been created by fsync-ing the directory that contains the file. +** If we do not do this and we encounter a power failure, the directory +** entry for the journal might not exist after we reboot. The next +** SQLite to access the file will not know that the journal exists (because +** the directory entry for the journal was never created) and the transaction +** will not roll back - possibly leading to database corruption. +*/ +int sqlite3OsSync(OsFile *id){ + assert( id->isOpen ); + SimulateIOError(SQLITE_IOERR); + TRACE2("SYNC %-3d\n", id->h); + if( full_fsync(id->h) ){ + return SQLITE_IOERR; + } + if( id->dirfd>=0 ){ + TRACE2("DIRSYNC %-3d\n", id->dirfd); + full_fsync(id->dirfd); + close(id->dirfd); /* Only need to sync once, so close the directory */ + id->dirfd = -1; /* when we are done. */ + } + return SQLITE_OK; +} + +/* +** Sync the directory zDirname. This is a no-op on operating systems other +** than UNIX. +*/ +int sqlite3OsSyncDirectory(const char *zDirname){ + int fd; + int r; + SimulateIOError(SQLITE_IOERR); + fd = open(zDirname, O_RDONLY|O_BINARY, 0644); + TRACE3("DIRSYNC %-3d (%s)\n", fd, zDirname); + if( fd<0 ){ + return SQLITE_CANTOPEN; + } + r = fsync(fd); + close(fd); + return ((r==0)?SQLITE_OK:SQLITE_IOERR); +} + +/* +** Truncate an open file to a specified size +*/ +int sqlite3OsTruncate(OsFile *id, i64 nByte){ + assert( id->isOpen ); + SimulateIOError(SQLITE_IOERR); + return ftruncate(id->h, nByte)==0 ? SQLITE_OK : SQLITE_IOERR; +} + +/* +** Determine the current size of a file in bytes +*/ +int sqlite3OsFileSize(OsFile *id, i64 *pSize){ + struct stat buf; + assert( id->isOpen ); + SimulateIOError(SQLITE_IOERR); + if( fstat(id->h, &buf)!=0 ){ + return SQLITE_IOERR; + } + *pSize = buf.st_size; + return SQLITE_OK; +} + +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, return +** non-zero. If the file is unlocked or holds only SHARED locks, then +** return zero. +*/ +int sqlite3OsCheckReservedLock(OsFile *id){ + int r = 0; + + assert( id->isOpen ); + sqlite3OsEnterMutex(); /* Needed because id->pLock is shared across threads */ + + /* Check if a thread in this process holds such a lock */ + if( id->pLock->locktype>SHARED_LOCK ){ + r = 1; + } + + /* Otherwise see if some other process holds it. + */ + if( !r ){ + struct flock lock; + lock.l_whence = SEEK_SET; + lock.l_start = RESERVED_BYTE; + lock.l_len = 1; + lock.l_type = F_WRLCK; + fcntl(id->h, F_GETLK, &lock); + if( lock.l_type!=F_UNLCK ){ + r = 1; + } + } + + sqlite3OsLeaveMutex(); + TRACE3("TEST WR-LOCK %d %d\n", id->h, r); + + return r; +} + +#ifdef SQLITE_DEBUG +/* +** Helper function for printing out trace information from debugging +** binaries. This returns the string represetation of the supplied +** integer lock-type. +*/ +static const char * locktypeName(int locktype){ + switch( locktype ){ + case NO_LOCK: return "NONE"; + case SHARED_LOCK: return "SHARED"; + case RESERVED_LOCK: return "RESERVED"; + case PENDING_LOCK: return "PENDING"; + case EXCLUSIVE_LOCK: return "EXCLUSIVE"; + } + return "ERROR"; +} +#endif + +/* +** Lock the file with the lock specified by parameter locktype - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. +*/ +int sqlite3OsLock(OsFile *id, int locktype){ + /* The following describes the implementation of the various locks and + ** lock transitions in terms of the POSIX advisory shared and exclusive + ** lock primitives (called read-locks and write-locks below, to avoid + ** confusion with SQLite lock names). The algorithms are complicated + ** slightly in order to be compatible with windows systems simultaneously + ** accessing the same database file, in case that is ever required. + ** + ** Symbols defined in os.h indentify the 'pending byte' and the 'reserved + ** byte', each single bytes at well known offsets, and the 'shared byte + ** range', a range of 510 bytes at a well known offset. + ** + ** To obtain a SHARED lock, a read-lock is obtained on the 'pending + ** byte'. If this is successful, a random byte from the 'shared byte + ** range' is read-locked and the lock on the 'pending byte' released. + ** + ** A process may only obtain a RESERVED lock after it has a SHARED lock. + ** A RESERVED lock is implemented by grabbing a write-lock on the + ** 'reserved byte'. + ** + ** A process may only obtain a PENDING lock after it has obtained a + ** SHARED lock. A PENDING lock is implemented by obtaining a write-lock + ** on the 'pending byte'. This ensures that no new SHARED locks can be + ** obtained, but existing SHARED locks are allowed to persist. A process + ** does not have to obtain a RESERVED lock on the way to a PENDING lock. + ** This property is used by the algorithm for rolling back a journal file + ** after a crash. + ** + ** An EXCLUSIVE lock, obtained after a PENDING lock is held, is + ** implemented by obtaining a write-lock on the entire 'shared byte + ** range'. Since all other locks require a read-lock on one of the bytes + ** within this range, this ensures that no other locks are held on the + ** database. + ** + ** The reason a single byte cannot be used instead of the 'shared byte + ** range' is that some versions of windows do not support read-locks. By + ** locking a random byte from a range, concurrent SHARED locks may exist + ** even if the locking primitive used is always a write-lock. + */ + int rc = SQLITE_OK; + struct lockInfo *pLock = id->pLock; + struct flock lock; + int s; + + assert( id->isOpen ); + TRACE7("LOCK %d %s was %s(%s,%d) pid=%d\n", id->h, locktypeName(locktype), + locktypeName(id->locktype), locktypeName(pLock->locktype), pLock->cnt + ,getpid() ); + + /* If there is already a lock of this type or more restrictive on the + ** OsFile, do nothing. Don't use the end_lock: exit path, as + ** sqlite3OsEnterMutex() hasn't been called yet. + */ + if( id->locktype>=locktype ){ + TRACE3("LOCK %d %s ok (already held)\n", id->h, locktypeName(locktype)); + return SQLITE_OK; + } + + /* Make sure the locking sequence is correct + */ + assert( id->locktype!=NO_LOCK || locktype==SHARED_LOCK ); + assert( locktype!=PENDING_LOCK ); + assert( locktype!=RESERVED_LOCK || id->locktype==SHARED_LOCK ); + + /* This mutex is needed because id->pLock is shared across threads + */ + sqlite3OsEnterMutex(); + + /* If some thread using this PID has a lock via a different OsFile* + ** handle that precludes the requested lock, return BUSY. + */ + if( (id->locktype!=pLock->locktype && + (pLock->locktype>=PENDING_LOCK || locktype>SHARED_LOCK)) + ){ + rc = SQLITE_BUSY; + goto end_lock; + } + + /* If a SHARED lock is requested, and some thread using this PID already + ** has a SHARED or RESERVED lock, then increment reference counts and + ** return SQLITE_OK. + */ + if( locktype==SHARED_LOCK && + (pLock->locktype==SHARED_LOCK || pLock->locktype==RESERVED_LOCK) ){ + assert( locktype==SHARED_LOCK ); + assert( id->locktype==0 ); + assert( pLock->cnt>0 ); + id->locktype = SHARED_LOCK; + pLock->cnt++; + id->pOpen->nLock++; + goto end_lock; + } + + lock.l_len = 1L; + lock.l_whence = SEEK_SET; + + /* A PENDING lock is needed before acquiring a SHARED lock and before + ** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will + ** be released. + */ + if( locktype==SHARED_LOCK + || (locktype==EXCLUSIVE_LOCK && id->locktypeh, F_SETLK, &lock); + if( s ){ + rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY; + goto end_lock; + } + } + + + /* If control gets to this point, then actually go ahead and make + ** operating system calls for the specified lock. + */ + if( locktype==SHARED_LOCK ){ + assert( pLock->cnt==0 ); + assert( pLock->locktype==0 ); + + /* Now get the read-lock */ + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; + s = fcntl(id->h, F_SETLK, &lock); + + /* Drop the temporary PENDING lock */ + lock.l_start = PENDING_BYTE; + lock.l_len = 1L; + lock.l_type = F_UNLCK; + fcntl(id->h, F_SETLK, &lock); + if( s ){ + rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY; + }else{ + id->locktype = SHARED_LOCK; + id->pOpen->nLock++; + pLock->cnt = 1; + } + }else if( locktype==EXCLUSIVE_LOCK && pLock->cnt>1 ){ + /* We are trying for an exclusive lock but another thread in this + ** same process is still holding a shared lock. */ + rc = SQLITE_BUSY; + }else{ + /* The request was for a RESERVED or EXCLUSIVE lock. It is + ** assumed that there is a SHARED or greater lock on the file + ** already. + */ + assert( 0!=id->locktype ); + lock.l_type = F_WRLCK; + switch( locktype ){ + case RESERVED_LOCK: + lock.l_start = RESERVED_BYTE; + break; + case EXCLUSIVE_LOCK: + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; + break; + default: + assert(0); + } + s = fcntl(id->h, F_SETLK, &lock); + if( s ){ + rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY; + } + } + + if( rc==SQLITE_OK ){ + id->locktype = locktype; + pLock->locktype = locktype; + }else if( locktype==EXCLUSIVE_LOCK ){ + id->locktype = PENDING_LOCK; + pLock->locktype = PENDING_LOCK; + } + +end_lock: + sqlite3OsLeaveMutex(); + TRACE4("LOCK %d %s %s\n", id->h, locktypeName(locktype), + rc==SQLITE_OK ? "ok" : "failed"); + return rc; +} + +/* +** Lower the locking level on file descriptor id to locktype. locktype +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +** +** It is not possible for this routine to fail if the second argument +** is NO_LOCK. If the second argument is SHARED_LOCK, this routine +** might return SQLITE_IOERR instead of SQLITE_OK. +*/ +int sqlite3OsUnlock(OsFile *id, int locktype){ + struct lockInfo *pLock; + struct flock lock; + int rc = SQLITE_OK; + + assert( id->isOpen ); + TRACE7("UNLOCK %d %d was %d(%d,%d) pid=%d\n", id->h, locktype, id->locktype, + id->pLock->locktype, id->pLock->cnt, getpid()); + + assert( locktype<=SHARED_LOCK ); + if( id->locktype<=locktype ){ + return SQLITE_OK; + } + sqlite3OsEnterMutex(); + pLock = id->pLock; + assert( pLock->cnt!=0 ); + if( id->locktype>SHARED_LOCK ){ + assert( pLock->locktype==id->locktype ); + if( locktype==SHARED_LOCK ){ + lock.l_type = F_RDLCK; + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; + if( fcntl(id->h, F_SETLK, &lock)!=0 ){ + /* This should never happen */ + rc = SQLITE_IOERR; + } + } + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = PENDING_BYTE; + lock.l_len = 2L; assert( PENDING_BYTE+1==RESERVED_BYTE ); + fcntl(id->h, F_SETLK, &lock); + pLock->locktype = SHARED_LOCK; + } + if( locktype==NO_LOCK ){ + struct openCnt *pOpen; + + /* Decrement the shared lock counter. Release the lock using an + ** OS call only when all threads in this same process have released + ** the lock. + */ + pLock->cnt--; + if( pLock->cnt==0 ){ + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = lock.l_len = 0L; + fcntl(id->h, F_SETLK, &lock); + pLock->locktype = NO_LOCK; + } + + /* Decrement the count of locks against this same file. When the + ** count reaches zero, close any other file descriptors whose close + ** was deferred because of outstanding locks. + */ + pOpen = id->pOpen; + pOpen->nLock--; + assert( pOpen->nLock>=0 ); + if( pOpen->nLock==0 && pOpen->nPending>0 ){ + int i; + for(i=0; inPending; i++){ + close(pOpen->aPending[i]); + } + sqliteFree(pOpen->aPending); + pOpen->nPending = 0; + pOpen->aPending = 0; + } + } + sqlite3OsLeaveMutex(); + id->locktype = locktype; + return rc; +} + +/* +** Close a file. +*/ +int sqlite3OsClose(OsFile *id){ + if( !id->isOpen ) return SQLITE_OK; + sqlite3OsUnlock(id, NO_LOCK); + if( id->dirfd>=0 ) close(id->dirfd); + id->dirfd = -1; + sqlite3OsEnterMutex(); + if( id->pOpen->nLock ){ + /* If there are outstanding locks, do not actually close the file just + ** yet because that would clear those locks. Instead, add the file + ** descriptor to pOpen->aPending. It will be automatically closed when + ** the last lock is cleared. + */ + int *aNew; + struct openCnt *pOpen = id->pOpen; + pOpen->nPending++; + aNew = sqliteRealloc( pOpen->aPending, pOpen->nPending*sizeof(int) ); + if( aNew==0 ){ + /* If a malloc fails, just leak the file descriptor */ + }else{ + pOpen->aPending = aNew; + pOpen->aPending[pOpen->nPending-1] = id->h; + } + }else{ + /* There are no outstanding locks so we can close the file immediately */ + close(id->h); + } + releaseLockInfo(id->pLock); + releaseOpenCnt(id->pOpen); + sqlite3OsLeaveMutex(); + id->isOpen = 0; + TRACE2("CLOSE %-3d\n", id->h); + OpenCounter(-1); + return SQLITE_OK; +} + +/* +** Get information to seed the random number generator. The seed +** is written into the buffer zBuf[256]. The calling function must +** supply a sufficiently large buffer. +*/ +int sqlite3OsRandomSeed(char *zBuf){ + /* We have to initialize zBuf to prevent valgrind from reporting + ** errors. The reports issued by valgrind are incorrect - we would + ** prefer that the randomness be increased by making use of the + ** uninitialized space in zBuf - but valgrind errors tend to worry + ** some users. Rather than argue, it seems easier just to initialize + ** the whole array and silence valgrind, even if that means less randomness + ** in the random seed. + ** + ** When testing, initializing zBuf[] to zero is all we do. That means + ** that we always use the same random number sequence.* This makes the + ** tests repeatable. + */ + memset(zBuf, 0, 256); +#if !defined(SQLITE_TEST) + { + int pid; + time((time_t*)zBuf); + pid = getpid(); + memcpy(&zBuf[sizeof(time_t)], &pid, sizeof(pid)); + } +#endif + return SQLITE_OK; +} + +/* +** Sleep for a little while. Return the amount of time slept. +*/ +int sqlite3OsSleep(int ms){ +#if defined(HAVE_USLEEP) && HAVE_USLEEP + usleep(ms*1000); + return ms; +#else + sleep((ms+999)/1000); + return 1000*((ms+999)/1000); +#endif +} + +/* +** Static variables used for thread synchronization +*/ +static int inMutex = 0; +#ifdef SQLITE_UNIX_THREADS +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +/* +** The following pair of routine implement mutual exclusion for +** multi-threaded processes. Only a single thread is allowed to +** executed code that is surrounded by EnterMutex() and LeaveMutex(). +** +** SQLite uses only a single Mutex. There is not much critical +** code and what little there is executes quickly and without blocking. +*/ +void sqlite3OsEnterMutex(){ +#ifdef SQLITE_UNIX_THREADS + pthread_mutex_lock(&mutex); +#endif + assert( !inMutex ); + inMutex = 1; +} +void sqlite3OsLeaveMutex(){ + assert( inMutex ); + inMutex = 0; +#ifdef SQLITE_UNIX_THREADS + pthread_mutex_unlock(&mutex); +#endif +} + +/* +** Turn a relative pathname into a full pathname. Return a pointer +** to the full pathname stored in space obtained from sqliteMalloc(). +** The calling function is responsible for freeing this space once it +** is no longer needed. +*/ +char *sqlite3OsFullPathname(const char *zRelative){ + char *zFull = 0; + if( zRelative[0]=='/' ){ + sqlite3SetString(&zFull, zRelative, (char*)0); + }else{ + char zBuf[5000]; + sqlite3SetString(&zFull, getcwd(zBuf, sizeof(zBuf)), "/", zRelative, + (char*)0); + } + return zFull; +} + +/* +** The following variable, if set to a non-zero value, becomes the result +** returned from sqlite3OsCurrentTime(). This is used for testing. +*/ +#ifdef SQLITE_TEST +int sqlite3_current_time = 0; +#endif + +/* +** Find the current time (in Universal Coordinated Time). Write the +** current time and date as a Julian Day number into *prNow and +** return 0. Return 1 if the time and date cannot be found. +*/ +int sqlite3OsCurrentTime(double *prNow){ + time_t t; + time(&t); + *prNow = t/86400.0 + 2440587.5; +#ifdef SQLITE_TEST + if( sqlite3_current_time ){ + *prNow = sqlite3_current_time/86400.0 + 2440587.5; + } +#endif + return 0; +} + +#if 0 /* NOT USED */ +/* +** Find the time that the file was last modified. Write the +** modification time and date as a Julian Day number into *prNow and +** return SQLITE_OK. Return SQLITE_ERROR if the modification +** time cannot be found. +*/ +int sqlite3OsFileModTime(OsFile *id, double *prNow){ + int rc; + struct stat statbuf; + if( fstat(id->h, &statbuf)==0 ){ + *prNow = statbuf.st_mtime/86400.0 + 2440587.5; + rc = SQLITE_OK; + }else{ + rc = SQLITE_ERROR; + } + return rc; +} +#endif /* NOT USED */ + +#endif /* OS_UNIX */ diff --git a/kopete/plugins/statistics/sqlite/os_unix.h b/kopete/plugins/statistics/sqlite/os_unix.h new file mode 100644 index 00000000..72f818be --- /dev/null +++ b/kopete/plugins/statistics/sqlite/os_unix.h @@ -0,0 +1,89 @@ +/* +** 2004 May 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This header file defined OS-specific features for Unix. +*/ +#ifndef _SQLITE_OS_UNIX_H_ +#define _SQLITE_OS_UNIX_H_ + +/* +** Helpful hint: To get this to compile on HP/UX, add -D_INCLUDE_POSIX_SOURCE +** to the compiler command line. +*/ + +/* +** These #defines should enable >2GB file support on Posix if the +** underlying operating system supports it. If the OS lacks +** large file support, or if the OS is windows, these should be no-ops. +** +** Large file support can be disabled using the -DSQLITE_DISABLE_LFS switch +** on the compiler command line. This is necessary if you are compiling +** on a recent machine (ex: RedHat 7.2) but you want your code to work +** on an older machine (ex: RedHat 6.0). If you compile on RedHat 7.2 +** without this option, LFS is enable. But LFS does not exist in the kernel +** in RedHat 6.0, so the code won't work. Hence, for maximum binary +** portability you should omit LFS. +** +** Similar is true for MacOS. LFS is only supported on MacOS 9 and later. +*/ +#ifndef SQLITE_DISABLE_LFS +# define _LARGE_FILE 1 +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# define _LARGEFILE_SOURCE 1 +#endif + +/* +** standard include files. +*/ +#include +#include +#include +#include + +/* +** The OsFile structure is a operating-system independing representation +** of an open file handle. It is defined differently for each architecture. +** +** This is the definition for Unix. +** +** OsFile.locktype takes one of the values SHARED_LOCK, RESERVED_LOCK, +** PENDING_LOCK or EXCLUSIVE_LOCK. +*/ +typedef struct OsFile OsFile; +struct OsFile { + struct Pager *pPager; /* The pager that owns this OsFile. Might be 0 */ + struct openCnt *pOpen; /* Info about all open fd's on this inode */ + struct lockInfo *pLock; /* Info about locks on this inode */ + int h; /* The file descriptor */ + unsigned char locktype; /* The type of lock held on this fd */ + unsigned char isOpen; /* True if needs to be closed */ + int dirfd; /* File descriptor for the directory */ +}; + +/* +** Maximum number of characters in a temporary file name +*/ +#define SQLITE_TEMPNAME_SIZE 200 + +/* +** Minimum interval supported by sqlite3OsSleep(). +*/ +#if defined(HAVE_USLEEP) && HAVE_USLEEP +# define SQLITE_MIN_SLEEP_MS 1 +#else +# define SQLITE_MIN_SLEEP_MS 1000 +#endif + + +#endif /* _SQLITE_OS_UNIX_H_ */ diff --git a/kopete/plugins/statistics/sqlite/os_win.c b/kopete/plugins/statistics/sqlite/os_win.c new file mode 100644 index 00000000..f6e3e3ea --- /dev/null +++ b/kopete/plugins/statistics/sqlite/os_win.c @@ -0,0 +1,747 @@ +/* +** 2004 May 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code that is specific to windows. +*/ +#include "sqliteInt.h" +#include "os.h" +#if OS_WIN /* This file is used for windows only */ + +#include + +/* +** Macros used to determine whether or not to use threads. +*/ +#if defined(THREADSAFE) && THREADSAFE +# define SQLITE_W32_THREADS 1 +#endif + +/* +** Include code that is common to all os_*.c files +*/ +#include "os_common.h" + +/* +** Delete the named file +*/ +int sqlite3OsDelete(const char *zFilename){ + DeleteFileA(zFilename); + TRACE2("DELETE \"%s\"\n", zFilename); + return SQLITE_OK; +} + +/* +** Return TRUE if the named file exists. +*/ +int sqlite3OsFileExists(const char *zFilename){ + return GetFileAttributesA(zFilename) != 0xffffffff; +} + +/* +** Attempt to open a file for both reading and writing. If that +** fails, try opening it read-only. If the file does not exist, +** try to create it. +** +** On success, a handle for the open file is written to *id +** and *pReadonly is set to 0 if the file was opened for reading and +** writing or 1 if the file was opened read-only. The function returns +** SQLITE_OK. +** +** On failure, the function returns SQLITE_CANTOPEN and leaves +** *id and *pReadonly unchanged. +*/ +int sqlite3OsOpenReadWrite( + const char *zFilename, + OsFile *id, + int *pReadonly +){ + HANDLE h; + assert( !id->isOpen ); + h = CreateFileA(zFilename, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, + NULL + ); + if( h==INVALID_HANDLE_VALUE ){ + h = CreateFileA(zFilename, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, + NULL + ); + if( h==INVALID_HANDLE_VALUE ){ + return SQLITE_CANTOPEN; + } + *pReadonly = 1; + }else{ + *pReadonly = 0; + } + id->h = h; + id->locktype = NO_LOCK; + id->sharedLockByte = 0; + id->isOpen = 1; + OpenCounter(+1); + TRACE3("OPEN R/W %d \"%s\"\n", h, zFilename); + return SQLITE_OK; +} + + +/* +** Attempt to open a new file for exclusive access by this process. +** The file will be opened for both reading and writing. To avoid +** a potential security problem, we do not allow the file to have +** previously existed. Nor do we allow the file to be a symbolic +** link. +** +** If delFlag is true, then make arrangements to automatically delete +** the file when it is closed. +** +** On success, write the file handle into *id and return SQLITE_OK. +** +** On failure, return SQLITE_CANTOPEN. +*/ +int sqlite3OsOpenExclusive(const char *zFilename, OsFile *id, int delFlag){ + HANDLE h; + int fileflags; + assert( !id->isOpen ); + if( delFlag ){ + fileflags = FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_RANDOM_ACCESS + | FILE_FLAG_DELETE_ON_CLOSE; + }else{ + fileflags = FILE_FLAG_RANDOM_ACCESS; + } + h = CreateFileA(zFilename, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + fileflags, + NULL + ); + if( h==INVALID_HANDLE_VALUE ){ + return SQLITE_CANTOPEN; + } + id->h = h; + id->locktype = NO_LOCK; + id->sharedLockByte = 0; + id->isOpen = 1; + OpenCounter(+1); + TRACE3("OPEN EX %d \"%s\"\n", h, zFilename); + return SQLITE_OK; +} + +/* +** Attempt to open a new file for read-only access. +** +** On success, write the file handle into *id and return SQLITE_OK. +** +** On failure, return SQLITE_CANTOPEN. +*/ +int sqlite3OsOpenReadOnly(const char *zFilename, OsFile *id){ + HANDLE h; + assert( !id->isOpen ); + h = CreateFileA(zFilename, + GENERIC_READ, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, + NULL + ); + if( h==INVALID_HANDLE_VALUE ){ + return SQLITE_CANTOPEN; + } + id->h = h; + id->locktype = NO_LOCK; + id->sharedLockByte = 0; + id->isOpen = 1; + OpenCounter(+1); + TRACE3("OPEN RO %d \"%s\"\n", h, zFilename); + return SQLITE_OK; +} + +/* +** Attempt to open a file descriptor for the directory that contains a +** file. This file descriptor can be used to fsync() the directory +** in order to make sure the creation of a new file is actually written +** to disk. +** +** This routine is only meaningful for Unix. It is a no-op under +** windows since windows does not support hard links. +** +** On success, a handle for a previously open file is at *id is +** updated with the new directory file descriptor and SQLITE_OK is +** returned. +** +** On failure, the function returns SQLITE_CANTOPEN and leaves +** *id unchanged. +*/ +int sqlite3OsOpenDirectory( + const char *zDirname, + OsFile *id +){ + return SQLITE_OK; +} + +/* +** If the following global variable points to a string which is the +** name of a directory, then that directory will be used to store +** temporary files. +*/ +const char *sqlite3_temp_directory = 0; + +/* +** Create a temporary file name in zBuf. zBuf must be big enough to +** hold at least SQLITE_TEMPNAME_SIZE characters. +*/ +int sqlite3OsTempFileName(char *zBuf){ + static char zChars[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; + int i, j; + char zTempPath[SQLITE_TEMPNAME_SIZE]; + if( sqlite3_temp_directory ){ + strncpy(zTempPath, sqlite3_temp_directory, SQLITE_TEMPNAME_SIZE-30); + zTempPath[SQLITE_TEMPNAME_SIZE-30] = 0; + }else{ + GetTempPathA(SQLITE_TEMPNAME_SIZE-30, zTempPath); + } + for(i=strlen(zTempPath); i>0 && zTempPath[i-1]=='\\'; i--){} + zTempPath[i] = 0; + for(;;){ + sprintf(zBuf, "%s\\"TEMP_FILE_PREFIX, zTempPath); + j = strlen(zBuf); + sqlite3Randomness(15, &zBuf[j]); + for(i=0; i<15; i++, j++){ + zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; + } + zBuf[j] = 0; + if( !sqlite3OsFileExists(zBuf) ) break; + } + TRACE2("TEMP FILENAME: %s\n", zBuf); + return SQLITE_OK; +} + +/* +** Close a file. +*/ +int sqlite3OsClose(OsFile *id){ + if( id->isOpen ){ + TRACE2("CLOSE %d\n", id->h); + CloseHandle(id->h); + OpenCounter(-1); + id->isOpen = 0; + } + return SQLITE_OK; +} + +/* +** Read data from a file into a buffer. Return SQLITE_OK if all +** bytes were read successfully and SQLITE_IOERR if anything goes +** wrong. +*/ +int sqlite3OsRead(OsFile *id, void *pBuf, int amt){ + DWORD got; + assert( id->isOpen ); + SimulateIOError(SQLITE_IOERR); + TRACE3("READ %d lock=%d\n", id->h, id->locktype); + if( !ReadFile(id->h, pBuf, amt, &got, 0) ){ + got = 0; + } + if( got==(DWORD)amt ){ + return SQLITE_OK; + }else{ + return SQLITE_IOERR; + } +} + +/* +** Write data from a buffer into a file. Return SQLITE_OK on success +** or some other error code on failure. +*/ +int sqlite3OsWrite(OsFile *id, const void *pBuf, int amt){ + int rc; + DWORD wrote; + assert( id->isOpen ); + SimulateIOError(SQLITE_IOERR); + SimulateDiskfullError; + TRACE3("WRITE %d lock=%d\n", id->h, id->locktype); + while( amt>0 && (rc = WriteFile(id->h, pBuf, amt, &wrote, 0))!=0 && wrote>0 ){ + amt -= wrote; + pBuf = &((char*)pBuf)[wrote]; + } + if( !rc || amt>(int)wrote ){ + return SQLITE_FULL; + } + return SQLITE_OK; +} + +/* +** Move the read/write pointer in a file. +*/ +int sqlite3OsSeek(OsFile *id, i64 offset){ + LONG upperBits = offset>>32; + LONG lowerBits = offset & 0xffffffff; + DWORD rc; + assert( id->isOpen ); + SEEK(offset/1024 + 1); + rc = SetFilePointer(id->h, lowerBits, &upperBits, FILE_BEGIN); + TRACE3("SEEK %d %lld\n", id->h, offset); + return SQLITE_OK; +} + +/* +** Make sure all writes to a particular file are committed to disk. +*/ +int sqlite3OsSync(OsFile *id){ + assert( id->isOpen ); + TRACE3("SYNC %d lock=%d\n", id->h, id->locktype); + if( FlushFileBuffers(id->h) ){ + return SQLITE_OK; + }else{ + return SQLITE_IOERR; + } +} + +/* +** Sync the directory zDirname. This is a no-op on operating systems other +** than UNIX. +*/ +int sqlite3OsSyncDirectory(const char *zDirname){ + SimulateIOError(SQLITE_IOERR); + return SQLITE_OK; +} + +/* +** Truncate an open file to a specified size +*/ +int sqlite3OsTruncate(OsFile *id, i64 nByte){ + LONG upperBits = nByte>>32; + assert( id->isOpen ); + TRACE3("TRUNCATE %d %lld\n", id->h, nByte); + SimulateIOError(SQLITE_IOERR); + SetFilePointer(id->h, nByte, &upperBits, FILE_BEGIN); + SetEndOfFile(id->h); + return SQLITE_OK; +} + +/* +** Determine the current size of a file in bytes +*/ +int sqlite3OsFileSize(OsFile *id, i64 *pSize){ + DWORD upperBits, lowerBits; + assert( id->isOpen ); + SimulateIOError(SQLITE_IOERR); + lowerBits = GetFileSize(id->h, &upperBits); + *pSize = (((i64)upperBits)<<32) + lowerBits; + return SQLITE_OK; +} + +/* +** Return true (non-zero) if we are running under WinNT, Win2K or WinXP. +** Return false (zero) for Win95, Win98, or WinME. +** +** Here is an interesting observation: Win95, Win98, and WinME lack +** the LockFileEx() API. But we can still statically link against that +** API as long as we don't call it win running Win95/98/ME. A call to +** this routine is used to determine if the host is Win95/98/ME or +** WinNT/2K/XP so that we will know whether or not we can safely call +** the LockFileEx() API. +*/ +static int isNT(void){ + static int osType = 0; /* 0=unknown 1=win95 2=winNT */ + if( osType==0 ){ + OSVERSIONINFO sInfo; + sInfo.dwOSVersionInfoSize = sizeof(sInfo); + GetVersionEx(&sInfo); + osType = sInfo.dwPlatformId==VER_PLATFORM_WIN32_NT ? 2 : 1; + } + return osType==2; +} + +/* +** Acquire a reader lock. +** Different API routines are called depending on whether or not this +** is Win95 or WinNT. +*/ +static int getReadLock(OsFile *id){ + int res; + if( isNT() ){ + OVERLAPPED ovlp; + ovlp.Offset = SHARED_FIRST; + ovlp.OffsetHigh = 0; + ovlp.hEvent = 0; + res = LockFileEx(id->h, LOCKFILE_FAIL_IMMEDIATELY, 0, SHARED_SIZE,0,&ovlp); + }else{ + int lk; + sqlite3Randomness(sizeof(lk), &lk); + id->sharedLockByte = (lk & 0x7fffffff)%(SHARED_SIZE - 1); + res = LockFile(id->h, SHARED_FIRST+id->sharedLockByte, 0, 1, 0); + } + return res; +} + +/* +** Undo a readlock +*/ +static int unlockReadLock(OsFile *id){ + int res; + if( isNT() ){ + res = UnlockFile(id->h, SHARED_FIRST, 0, SHARED_SIZE, 0); + }else{ + res = UnlockFile(id->h, SHARED_FIRST + id->sharedLockByte, 0, 1, 0); + } + return res; +} + +/* +** Lock the file with the lock specified by parameter locktype - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** This routine will only increase a lock. The sqlite3OsUnlock() routine +** erases all locks at once and returns us immediately to locking level 0. +** It is not possible to lower the locking level one step at a time. You +** must go straight to locking level 0. +*/ +int sqlite3OsLock(OsFile *id, int locktype){ + int rc = SQLITE_OK; /* Return code from subroutines */ + int res = 1; /* Result of a windows lock call */ + int newLocktype; /* Set id->locktype to this value before exiting */ + int gotPendingLock = 0;/* True if we acquired a PENDING lock this time */ + + assert( id->isOpen ); + TRACE5("LOCK %d %d was %d(%d)\n", + id->h, locktype, id->locktype, id->sharedLockByte); + + /* If there is already a lock of this type or more restrictive on the + ** OsFile, do nothing. Don't use the end_lock: exit path, as + ** sqlite3OsEnterMutex() hasn't been called yet. + */ + if( id->locktype>=locktype ){ + return SQLITE_OK; + } + + /* Make sure the locking sequence is correct + */ + assert( id->locktype!=NO_LOCK || locktype==SHARED_LOCK ); + assert( locktype!=PENDING_LOCK ); + assert( locktype!=RESERVED_LOCK || id->locktype==SHARED_LOCK ); + + /* Lock the PENDING_LOCK byte if we need to acquire a PENDING lock or + ** a SHARED lock. If we are acquiring a SHARED lock, the acquisition of + ** the PENDING_LOCK byte is temporary. + */ + newLocktype = id->locktype; + if( id->locktype==NO_LOCK + || (locktype==EXCLUSIVE_LOCK && id->locktype==RESERVED_LOCK) + ){ + int cnt = 3; + while( cnt-->0 && (res = LockFile(id->h, PENDING_BYTE, 0, 1, 0))==0 ){ + /* Try 3 times to get the pending lock. The pending lock might be + ** held by another reader process who will release it momentarily. + */ + TRACE2("could not get a PENDING lock. cnt=%d\n", cnt); + Sleep(1); + } + gotPendingLock = res; + } + + /* Acquire a shared lock + */ + if( locktype==SHARED_LOCK && res ){ + assert( id->locktype==NO_LOCK ); + res = getReadLock(id); + if( res ){ + newLocktype = SHARED_LOCK; + } + } + + /* Acquire a RESERVED lock + */ + if( locktype==RESERVED_LOCK && res ){ + assert( id->locktype==SHARED_LOCK ); + res = LockFile(id->h, RESERVED_BYTE, 0, 1, 0); + if( res ){ + newLocktype = RESERVED_LOCK; + } + } + + /* Acquire a PENDING lock + */ + if( locktype==EXCLUSIVE_LOCK && res ){ + newLocktype = PENDING_LOCK; + gotPendingLock = 0; + } + + /* Acquire an EXCLUSIVE lock + */ + if( locktype==EXCLUSIVE_LOCK && res ){ + assert( id->locktype>=SHARED_LOCK ); + res = unlockReadLock(id); + TRACE2("unreadlock = %d\n", res); + res = LockFile(id->h, SHARED_FIRST, 0, SHARED_SIZE, 0); + if( res ){ + newLocktype = EXCLUSIVE_LOCK; + }else{ + TRACE2("error-code = %d\n", GetLastError()); + } + } + + /* If we are holding a PENDING lock that ought to be released, then + ** release it now. + */ + if( gotPendingLock && locktype==SHARED_LOCK ){ + UnlockFile(id->h, PENDING_BYTE, 0, 1, 0); + } + + /* Update the state of the lock has held in the file descriptor then + ** return the appropriate result code. + */ + if( res ){ + rc = SQLITE_OK; + }else{ + TRACE4("LOCK FAILED %d trying for %d but got %d\n", id->h, + locktype, newLocktype); + rc = SQLITE_BUSY; + } + id->locktype = newLocktype; + return rc; +} + +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, return +** non-zero, otherwise zero. +*/ +int sqlite3OsCheckReservedLock(OsFile *id){ + int rc; + assert( id->isOpen ); + if( id->locktype>=RESERVED_LOCK ){ + rc = 1; + TRACE3("TEST WR-LOCK %d %d (local)\n", id->h, rc); + }else{ + rc = LockFile(id->h, RESERVED_BYTE, 0, 1, 0); + if( rc ){ + UnlockFile(id->h, RESERVED_BYTE, 0, 1, 0); + } + rc = !rc; + TRACE3("TEST WR-LOCK %d %d (remote)\n", id->h, rc); + } + return rc; +} + +/* +** Lower the locking level on file descriptor id to locktype. locktype +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +** +** It is not possible for this routine to fail if the second argument +** is NO_LOCK. If the second argument is SHARED_LOCK then this routine +** might return SQLITE_IOERR; +*/ +int sqlite3OsUnlock(OsFile *id, int locktype){ + int type; + int rc = SQLITE_OK; + assert( id->isOpen ); + assert( locktype<=SHARED_LOCK ); + TRACE5("UNLOCK %d to %d was %d(%d)\n", id->h, locktype, + id->locktype, id->sharedLockByte); + type = id->locktype; + if( type>=EXCLUSIVE_LOCK ){ + UnlockFile(id->h, SHARED_FIRST, 0, SHARED_SIZE, 0); + if( locktype==SHARED_LOCK && !getReadLock(id) ){ + /* This should never happen. We should always be able to + ** reacquire the read lock */ + rc = SQLITE_IOERR; + } + } + if( type>=RESERVED_LOCK ){ + UnlockFile(id->h, RESERVED_BYTE, 0, 1, 0); + } + if( locktype==NO_LOCK && type>=SHARED_LOCK ){ + unlockReadLock(id); + } + if( type>=PENDING_LOCK ){ + UnlockFile(id->h, PENDING_BYTE, 0, 1, 0); + } + id->locktype = locktype; + return rc; +} + +/* +** Get information to seed the random number generator. The seed +** is written into the buffer zBuf[256]. The calling function must +** supply a sufficiently large buffer. +*/ +int sqlite3OsRandomSeed(char *zBuf){ + /* We have to initialize zBuf to prevent valgrind from reporting + ** errors. The reports issued by valgrind are incorrect - we would + ** prefer that the randomness be increased by making use of the + ** uninitialized space in zBuf - but valgrind errors tend to worry + ** some users. Rather than argue, it seems easier just to initialize + ** the whole array and silence valgrind, even if that means less randomness + ** in the random seed. + ** + ** When testing, initializing zBuf[] to zero is all we do. That means + ** that we always use the same random number sequence.* This makes the + ** tests repeatable. + */ + memset(zBuf, 0, 256); + GetSystemTime((LPSYSTEMTIME)zBuf); + return SQLITE_OK; +} + +/* +** Sleep for a little while. Return the amount of time slept. +*/ +int sqlite3OsSleep(int ms){ + Sleep(ms); + return ms; +} + +/* +** Static variables used for thread synchronization +*/ +static int inMutex = 0; +#ifdef SQLITE_W32_THREADS + static CRITICAL_SECTION cs; +#endif + +/* +** The following pair of routine implement mutual exclusion for +** multi-threaded processes. Only a single thread is allowed to +** executed code that is surrounded by EnterMutex() and LeaveMutex(). +** +** SQLite uses only a single Mutex. There is not much critical +** code and what little there is executes quickly and without blocking. +*/ +void sqlite3OsEnterMutex(){ +#ifdef SQLITE_W32_THREADS + static int isInit = 0; + while( !isInit ){ + static long lock = 0; + if( InterlockedIncrement(&lock)==1 ){ + InitializeCriticalSection(&cs); + isInit = 1; + }else{ + Sleep(1); + } + } + EnterCriticalSection(&cs); +#endif + assert( !inMutex ); + inMutex = 1; +} +void sqlite3OsLeaveMutex(){ + assert( inMutex ); + inMutex = 0; +#ifdef SQLITE_W32_THREADS + LeaveCriticalSection(&cs); +#endif +} + +/* +** Turn a relative pathname into a full pathname. Return a pointer +** to the full pathname stored in space obtained from sqliteMalloc(). +** The calling function is responsible for freeing this space once it +** is no longer needed. +*/ +char *sqlite3OsFullPathname(const char *zRelative){ + char *zNotUsed; + char *zFull; + int nByte; + nByte = GetFullPathNameA(zRelative, 0, 0, &zNotUsed) + 1; + zFull = sqliteMalloc( nByte ); + if( zFull==0 ) return 0; + GetFullPathNameA(zRelative, nByte, zFull, &zNotUsed); + return zFull; +} + +/* +** The following variable, if set to a non-zero value, becomes the result +** returned from sqlite3OsCurrentTime(). This is used for testing. +*/ +#ifdef SQLITE_TEST +int sqlite3_current_time = 0; +#endif + +/* +** Find the current time (in Universal Coordinated Time). Write the +** current time and date as a Julian Day number into *prNow and +** return 0. Return 1 if the time and date cannot be found. +*/ +int sqlite3OsCurrentTime(double *prNow){ + FILETIME ft; + /* FILETIME structure is a 64-bit value representing the number of + 100-nanosecond intervals since January 1, 1601 (= JD 2305813.5). + */ + double now; + GetSystemTimeAsFileTime( &ft ); + now = ((double)ft.dwHighDateTime) * 4294967296.0; + *prNow = (now + ft.dwLowDateTime)/864000000000.0 + 2305813.5; +#ifdef SQLITE_TEST + if( sqlite3_current_time ){ + *prNow = sqlite3_current_time/86400.0 + 2440587.5; + } +#endif + return 0; +} + +/* +** Find the time that the file was last modified. Write the +** modification time and date as a Julian Day number into *prNow and +** return SQLITE_OK. Return SQLITE_ERROR if the modification +** time cannot be found. +*/ +int sqlite3OsFileModTime(OsFile *id, double *prMTime){ + int rc; + FILETIME ft; + /* FILETIME structure is a 64-bit value representing the number of + ** 100-nanosecond intervals since January 1, 1601 (= JD 2305813.5). + */ + if( GetFileTime(id->h, 0, 0, &ft) ){ + double t; + t = ((double)ft.dwHighDateTime) * 4294967296.0; + *prMTime = (t + ft.dwLowDateTime)/864000000000.0 + 2305813.5; + rc = SQLITE_OK; + }else{ + rc = SQLITE_ERROR; + } + return rc; +} + +#endif /* OS_WIN */ diff --git a/kopete/plugins/statistics/sqlite/os_win.h b/kopete/plugins/statistics/sqlite/os_win.h new file mode 100644 index 00000000..baf937b2 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/os_win.h @@ -0,0 +1,40 @@ +/* +** 2004 May 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This header file defines OS-specific features for Win32 +*/ +#ifndef _SQLITE_OS_WIN_H_ +#define _SQLITE_OS_WIN_H_ + +#include +#include + +/* +** The OsFile structure is a operating-system independing representation +** of an open file handle. It is defined differently for each architecture. +** +** This is the definition for Win32. +*/ +typedef struct OsFile OsFile; +struct OsFile { + HANDLE h; /* Handle for accessing the file */ + unsigned char locktype; /* Type of lock currently held on this file */ + unsigned char isOpen; /* True if needs to be closed */ + short sharedLockByte; /* Randomly chosen byte used as a shared lock */ +}; + + +#define SQLITE_TEMPNAME_SIZE (MAX_PATH+50) +#define SQLITE_MIN_SLEEP_MS 1 + + +#endif /* _SQLITE_OS_WIN_H_ */ diff --git a/kopete/plugins/statistics/sqlite/pager.c b/kopete/plugins/statistics/sqlite/pager.c new file mode 100644 index 00000000..a374562b --- /dev/null +++ b/kopete/plugins/statistics/sqlite/pager.c @@ -0,0 +1,3205 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the implementation of the page cache subsystem or "pager". +** +** The pager is used to access a database disk file. It implements +** atomic commit and rollback through the use of a journal file that +** is separate from the database file. The pager also implements file +** locking to prevent two processes from writing the same database +** file simultaneously, or one process from reading the database while +** another is writing. +** +** @(#) $Id$ +*/ +#include "sqliteInt.h" +#include "os.h" +#include "pager.h" +#include +#include + +/* +** Macros for troubleshooting. Normally turned off +*/ +#if 0 +#define TRACE1(X) sqlite3DebugPrintf(X) +#define TRACE2(X,Y) sqlite3DebugPrintf(X,Y) +#define TRACE3(X,Y,Z) sqlite3DebugPrintf(X,Y,Z) +#define TRACE4(X,Y,Z,W) sqlite3DebugPrintf(X,Y,Z,W) +#else +#define TRACE1(X) +#define TRACE2(X,Y) +#define TRACE3(X,Y,Z) +#define TRACE4(X,Y,Z,W) +#endif + + +/* +** The page cache as a whole is always in one of the following +** states: +** +** PAGER_UNLOCK The page cache is not currently reading or +** writing the database file. There is no +** data held in memory. This is the initial +** state. +** +** PAGER_SHARED The page cache is reading the database. +** Writing is not permitted. There can be +** multiple readers accessing the same database +** file at the same time. +** +** PAGER_RESERVED This process has reserved the database for writing +** but has not yet made any changes. Only one process +** at a time can reserve the database. The original +** database file has not been modified so other +** processes may still be reading the on-disk +** database file. +** +** PAGER_EXCLUSIVE The page cache is writing the database. +** Access is exclusive. No other processes or +** threads can be reading or writing while one +** process is writing. +** +** PAGER_SYNCED The pager moves to this state from PAGER_EXCLUSIVE +** after all dirty pages have been written to the +** database file and the file has been synced to +** disk. All that remains to do is to remove the +** journal file and the transaction will be +** committed. +** +** The page cache comes up in PAGER_UNLOCK. The first time a +** sqlite3pager_get() occurs, the state transitions to PAGER_SHARED. +** After all pages have been released using sqlite_page_unref(), +** the state transitions back to PAGER_UNLOCK. The first time +** that sqlite3pager_write() is called, the state transitions to +** PAGER_RESERVED. (Note that sqlite_page_write() can only be +** called on an outstanding page which means that the pager must +** be in PAGER_SHARED before it transitions to PAGER_RESERVED.) +** The transition to PAGER_EXCLUSIVE occurs when before any changes +** are made to the database file. After an sqlite3pager_rollback() +** or sqlite_pager_commit(), the state goes back to PAGER_SHARED. +*/ +#define PAGER_UNLOCK 0 +#define PAGER_SHARED 1 /* same as SHARED_LOCK */ +#define PAGER_RESERVED 2 /* same as RESERVED_LOCK */ +#define PAGER_EXCLUSIVE 4 /* same as EXCLUSIVE_LOCK */ +#define PAGER_SYNCED 5 + +/* +** If the SQLITE_BUSY_RESERVED_LOCK macro is set to true at compile-time, +** then failed attempts to get a reserved lock will invoke the busy callback. +** This is off by default. To see why, consider the following scenario: +** +** Suppose thread A already has a shared lock and wants a reserved lock. +** Thread B already has a reserved lock and wants an exclusive lock. If +** both threads are using their busy callbacks, it might be a long time +** be for one of the threads give up and allows the other to proceed. +** But if the thread trying to get the reserved lock gives up quickly +** (if it never invokes its busy callback) then the contention will be +** resolved quickly. +*/ +#ifndef SQLITE_BUSY_RESERVED_LOCK +# define SQLITE_BUSY_RESERVED_LOCK 0 +#endif + +/* +** Each in-memory image of a page begins with the following header. +** This header is only visible to this pager module. The client +** code that calls pager sees only the data that follows the header. +** +** Client code should call sqlite3pager_write() on a page prior to making +** any modifications to that page. The first time sqlite3pager_write() +** is called, the original page contents are written into the rollback +** journal and PgHdr.inJournal and PgHdr.needSync are set. Later, once +** the journal page has made it onto the disk surface, PgHdr.needSync +** is cleared. The modified page cannot be written back into the original +** database file until the journal pages has been synced to disk and the +** PgHdr.needSync has been cleared. +** +** The PgHdr.dirty flag is set when sqlite3pager_write() is called and +** is cleared again when the page content is written back to the original +** database file. +*/ +typedef struct PgHdr PgHdr; +struct PgHdr { + Pager *pPager; /* The pager to which this page belongs */ + Pgno pgno; /* The page number for this page */ + PgHdr *pNextHash, *pPrevHash; /* Hash collision chain for PgHdr.pgno */ + PgHdr *pNextFree, *pPrevFree; /* Freelist of pages where nRef==0 */ + PgHdr *pNextAll; /* A list of all pages */ + PgHdr *pNextStmt, *pPrevStmt; /* List of pages in the statement journal */ + u8 inJournal; /* TRUE if has been written to journal */ + u8 inStmt; /* TRUE if in the statement subjournal */ + u8 dirty; /* TRUE if we need to write back changes */ + u8 needSync; /* Sync journal before writing this page */ + u8 alwaysRollback; /* Disable dont_rollback() for this page */ + short int nRef; /* Number of users of this page */ + PgHdr *pDirty; /* Dirty pages sorted by PgHdr.pgno */ + /* pPager->pageSize bytes of page data follow this header */ + /* Pager.nExtra bytes of local data follow the page data */ +}; + +/* +** For an in-memory only database, some extra information is recorded about +** each page so that changes can be rolled back. (Journal files are not +** used for in-memory databases.) The following information is added to +** the end of every EXTRA block for in-memory databases. +** +** This information could have been added directly to the PgHdr structure. +** But then it would take up an extra 8 bytes of storage on every PgHdr +** even for disk-based databases. Splitting it out saves 8 bytes. This +** is only a savings of 0.8% but those percentages add up. +*/ +typedef struct PgHistory PgHistory; +struct PgHistory { + u8 *pOrig; /* Original page text. Restore to this on a full rollback */ + u8 *pStmt; /* Text as it was at the beginning of the current statement */ +}; + +/* +** A macro used for invoking the codec if there is one +*/ +#ifdef SQLITE_HAS_CODEC +# define CODEC(P,D,N,X) if( P->xCodec ){ P->xCodec(P->pCodecArg,D,N,X); } +#else +# define CODEC(P,D,N,X) +#endif + +/* +** Convert a pointer to a PgHdr into a pointer to its data +** and back again. +*/ +#define PGHDR_TO_DATA(P) ((void*)(&(P)[1])) +#define DATA_TO_PGHDR(D) (&((PgHdr*)(D))[-1]) +#define PGHDR_TO_EXTRA(G,P) ((void*)&((char*)(&(G)[1]))[(P)->pageSize]) +#define PGHDR_TO_HIST(P,PGR) \ + ((PgHistory*)&((char*)(&(P)[1]))[(PGR)->pageSize+(PGR)->nExtra]) + +/* +** How big to make the hash table used for locating in-memory pages +** by page number. +*/ +#define N_PG_HASH 2048 + +/* +** Hash a page number +*/ +#define pager_hash(PN) ((PN)&(N_PG_HASH-1)) + +/* +** A open page cache is an instance of the following structure. +*/ +struct Pager { + char *zFilename; /* Name of the database file */ + char *zJournal; /* Name of the journal file */ + char *zDirectory; /* Directory hold database and journal files */ + OsFile fd, jfd; /* File descriptors for database and journal */ + OsFile stfd; /* File descriptor for the statement subjournal*/ + int dbSize; /* Number of pages in the file */ + int origDbSize; /* dbSize before the current change */ + int stmtSize; /* Size of database (in pages) at stmt_begin() */ + i64 stmtJSize; /* Size of journal at stmt_begin() */ + int nRec; /* Number of pages written to the journal */ + u32 cksumInit; /* Quasi-random value added to every checksum */ + int stmtNRec; /* Number of records in stmt subjournal */ + int nExtra; /* Add this many bytes to each in-memory page */ + void (*xDestructor)(void*,int); /* Call this routine when freeing pages */ + void (*xReiniter)(void*,int); /* Call this routine when reloading pages */ + int pageSize; /* Number of bytes in a page */ + int nPage; /* Total number of in-memory pages */ + int nRef; /* Number of in-memory pages with PgHdr.nRef>0 */ + int mxPage; /* Maximum number of pages to hold in cache */ + int nHit, nMiss, nOvfl; /* Cache hits, missing, and LRU overflows */ + void (*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */ + void *pCodecArg; /* First argument to xCodec() */ + u8 journalOpen; /* True if journal file descriptors is valid */ + u8 journalStarted; /* True if header of journal is synced */ + u8 useJournal; /* Use a rollback journal on this file */ + u8 stmtOpen; /* True if the statement subjournal is open */ + u8 stmtInUse; /* True we are in a statement subtransaction */ + u8 stmtAutoopen; /* Open stmt journal when main journal is opened*/ + u8 noSync; /* Do not sync the journal if true */ + u8 fullSync; /* Do extra syncs of the journal for robustness */ + u8 state; /* PAGER_UNLOCK, _SHARED, _RESERVED, etc. */ + u8 errMask; /* One of several kinds of errors */ + u8 tempFile; /* zFilename is a temporary file */ + u8 readOnly; /* True for a read-only database */ + u8 needSync; /* True if an fsync() is needed on the journal */ + u8 dirtyCache; /* True if cached pages have changed */ + u8 alwaysRollback; /* Disable dont_rollback() for all pages */ + u8 memDb; /* True to inhibit all file I/O */ + u8 *aInJournal; /* One bit for each page in the database file */ + u8 *aInStmt; /* One bit for each page in the database */ + u8 setMaster; /* True if a m-j name has been written to jrnl */ + BusyHandler *pBusyHandler; /* Pointer to sqlite.busyHandler */ + PgHdr *pFirst, *pLast; /* List of free pages */ + PgHdr *pFirstSynced; /* First free page with PgHdr.needSync==0 */ + PgHdr *pAll; /* List of all pages */ + PgHdr *pStmt; /* List of pages in the statement subjournal */ + i64 journalOff; /* Current byte offset in the journal file */ + i64 journalHdr; /* Byte offset to previous journal header */ + i64 stmtHdrOff; /* First journal header written this statement */ + i64 stmtCksum; /* cksumInit when statement was started */ + int sectorSize; /* Assumed sector size during rollback */ + PgHdr *aHash[N_PG_HASH]; /* Hash table to map page number to PgHdr */ +}; + +/* +** These are bits that can be set in Pager.errMask. +*/ +#define PAGER_ERR_FULL 0x01 /* a write() failed */ +#define PAGER_ERR_MEM 0x02 /* malloc() failed */ +#define PAGER_ERR_LOCK 0x04 /* error in the locking protocol */ +#define PAGER_ERR_CORRUPT 0x08 /* database or journal corruption */ +#define PAGER_ERR_DISK 0x10 /* general disk I/O error - bad hard drive? */ + +/* +** Journal files begin with the following magic string. The data +** was obtained from /dev/random. It is used only as a sanity check. +** +** Since version 2.8.0, the journal format contains additional sanity +** checking information. If the power fails while the journal is begin +** written, semi-random garbage data might appear in the journal +** file after power is restored. If an attempt is then made +** to roll the journal back, the database could be corrupted. The additional +** sanity checking data is an attempt to discover the garbage in the +** journal and ignore it. +** +** The sanity checking information for the new journal format consists +** of a 32-bit checksum on each page of data. The checksum covers both +** the page number and the pPager->pageSize bytes of data for the page. +** This cksum is initialized to a 32-bit random value that appears in the +** journal file right after the header. The random initializer is important, +** because garbage data that appears at the end of a journal is likely +** data that was once in other files that have now been deleted. If the +** garbage data came from an obsolete journal file, the checksums might +** be correct. But by initializing the checksum to random value which +** is different for every journal, we minimize that risk. +*/ +static const unsigned char aJournalMagic[] = { + 0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd7, +}; + +/* +** The size of the header and of each page in the journal is determined +** by the following macros. +*/ +#define JOURNAL_PG_SZ(pPager) ((pPager->pageSize) + 8) + +/* +** The journal header size for this pager. In the future, this could be +** set to some value read from the disk controller. The important +** characteristic is that it is the same size as a disk sector. +*/ +#define JOURNAL_HDR_SZ(pPager) (pPager->sectorSize) + +#define PAGER_SECTOR_SIZE 512 + +/* +** Page number PAGER_MJ_PGNO is never used in an SQLite database (it is +** reserved for working around a windows/posix incompatibility). It is +** used in the journal to signify that the remainder of the journal file +** is devoted to storing a master journal name - there are no more pages to +** roll back. See comments for function writeMasterJournal() for details. +*/ +#define PAGER_MJ_PGNO(x) (PENDING_BYTE/((x)->pageSize)) + +/* +** Enable reference count tracking (for debugging) here: +*/ +#ifdef SQLITE_TEST + int pager3_refinfo_enable = 0; + static void pager_refinfo(PgHdr *p){ + static int cnt = 0; + if( !pager3_refinfo_enable ) return; + sqlite3DebugPrintf( + "REFCNT: %4d addr=%p nRef=%d\n", + p->pgno, PGHDR_TO_DATA(p), p->nRef + ); + cnt++; /* Something to set a breakpoint on */ + } +# define REFINFO(X) pager_refinfo(X) +#else +# define REFINFO(X) +#endif + +/* +** Read a 32-bit integer from the given file descriptor. Store the integer +** that is read in *pRes. Return SQLITE_OK if everything worked, or an +** error code is something goes wrong. +** +** All values are stored on disk as big-endian. +*/ +static int read32bits(OsFile *fd, u32 *pRes){ + u32 res; + int rc; + rc = sqlite3OsRead(fd, &res, sizeof(res)); + if( rc==SQLITE_OK ){ + unsigned char ac[4]; + memcpy(ac, &res, 4); + res = (ac[0]<<24) | (ac[1]<<16) | (ac[2]<<8) | ac[3]; + } + *pRes = res; + return rc; +} + +/* +** Write a 32-bit integer into the given file descriptor. Return SQLITE_OK +** on success or an error code is something goes wrong. +*/ +static int write32bits(OsFile *fd, u32 val){ + unsigned char ac[4]; + ac[0] = (val>>24) & 0xff; + ac[1] = (val>>16) & 0xff; + ac[2] = (val>>8) & 0xff; + ac[3] = val & 0xff; + return sqlite3OsWrite(fd, ac, 4); +} + +/* +** Write the 32-bit integer 'val' into the page identified by page header +** 'p' at offset 'offset'. +*/ +static void store32bits(u32 val, PgHdr *p, int offset){ + unsigned char *ac; + ac = &((unsigned char*)PGHDR_TO_DATA(p))[offset]; + ac[0] = (val>>24) & 0xff; + ac[1] = (val>>16) & 0xff; + ac[2] = (val>>8) & 0xff; + ac[3] = val & 0xff; +} + +/* +** Read a 32-bit integer at offset 'offset' from the page identified by +** page header 'p'. +*/ +static u32 retrieve32bits(PgHdr *p, int offset){ + unsigned char *ac; + ac = &((unsigned char*)PGHDR_TO_DATA(p))[offset]; + return (ac[0]<<24) | (ac[1]<<16) | (ac[2]<<8) | ac[3]; +} + + +/* +** Convert the bits in the pPager->errMask into an approprate +** return code. +*/ +static int pager_errcode(Pager *pPager){ + int rc = SQLITE_OK; + if( pPager->errMask & PAGER_ERR_LOCK ) rc = SQLITE_PROTOCOL; + if( pPager->errMask & PAGER_ERR_DISK ) rc = SQLITE_IOERR; + if( pPager->errMask & PAGER_ERR_FULL ) rc = SQLITE_FULL; + if( pPager->errMask & PAGER_ERR_MEM ) rc = SQLITE_NOMEM; + if( pPager->errMask & PAGER_ERR_CORRUPT ) rc = SQLITE_CORRUPT; + return rc; +} + +/* +** When this is called the journal file for pager pPager must be open. +** The master journal file name is read from the end of the file and +** written into memory obtained from sqliteMalloc(). *pzMaster is +** set to point at the memory and SQLITE_OK returned. The caller must +** sqliteFree() *pzMaster. +** +** If no master journal file name is present *pzMaster is set to 0 and +** SQLITE_OK returned. +*/ +static int readMasterJournal(OsFile *pJrnl, char **pzMaster){ + int rc; + u32 len; + i64 szJ; + u32 cksum; + int i; + unsigned char aMagic[8]; /* A buffer to hold the magic header */ + + *pzMaster = 0; + + rc = sqlite3OsFileSize(pJrnl, &szJ); + if( rc!=SQLITE_OK || szJ<16 ) return rc; + + rc = sqlite3OsSeek(pJrnl, szJ-16); + if( rc!=SQLITE_OK ) return rc; + + rc = read32bits(pJrnl, &len); + if( rc!=SQLITE_OK ) return rc; + + rc = read32bits(pJrnl, &cksum); + if( rc!=SQLITE_OK ) return rc; + + rc = sqlite3OsRead(pJrnl, aMagic, 8); + if( rc!=SQLITE_OK || memcmp(aMagic, aJournalMagic, 8) ) return rc; + + rc = sqlite3OsSeek(pJrnl, szJ-16-len); + if( rc!=SQLITE_OK ) return rc; + + *pzMaster = (char *)sqliteMalloc(len+1); + if( !*pzMaster ){ + return SQLITE_NOMEM; + } + rc = sqlite3OsRead(pJrnl, *pzMaster, len); + if( rc!=SQLITE_OK ){ + sqliteFree(*pzMaster); + *pzMaster = 0; + return rc; + } + + /* See if the checksum matches the master journal name */ + for(i=0; ijournalOff; + if( c ){ + offset = ((c-1)/JOURNAL_HDR_SZ(pPager) + 1) * JOURNAL_HDR_SZ(pPager); + } + assert( offset%JOURNAL_HDR_SZ(pPager)==0 ); + assert( offset>=c ); + assert( (offset-c)journalOff = offset; + return sqlite3OsSeek(&pPager->jfd, pPager->journalOff); +} + +/* +** The journal file must be open when this routine is called. A journal +** header (JOURNAL_HDR_SZ bytes) is written into the journal file at the +** current location. +** +** The format for the journal header is as follows: +** - 8 bytes: Magic identifying journal format. +** - 4 bytes: Number of records in journal, or -1 no-sync mode is on. +** - 4 bytes: Random number used for page hash. +** - 4 bytes: Initial database page count. +** - 4 bytes: Sector size used by the process that wrote this journal. +** +** Followed by (JOURNAL_HDR_SZ - 24) bytes of unused space. +*/ +static int writeJournalHdr(Pager *pPager){ + + int rc = seekJournalHdr(pPager); + if( rc ) return rc; + + pPager->journalHdr = pPager->journalOff; + if( pPager->stmtHdrOff==0 ){ + pPager->stmtHdrOff = pPager->journalHdr; + } + pPager->journalOff += JOURNAL_HDR_SZ(pPager); + + /* FIX ME: + ** + ** Possibly for a pager not in no-sync mode, the journal magic should not + ** be written until nRec is filled in as part of next syncJournal(). + ** + ** Actually maybe the whole journal header should be delayed until that + ** point. Think about this. + */ + rc = sqlite3OsWrite(&pPager->jfd, aJournalMagic, sizeof(aJournalMagic)); + + if( rc==SQLITE_OK ){ + /* The nRec Field. 0xFFFFFFFF for no-sync journals. */ + rc = write32bits(&pPager->jfd, pPager->noSync ? 0xffffffff : 0); + } + if( rc==SQLITE_OK ){ + /* The random check-hash initialiser */ + sqlite3Randomness(sizeof(pPager->cksumInit), &pPager->cksumInit); + rc = write32bits(&pPager->jfd, pPager->cksumInit); + } + if( rc==SQLITE_OK ){ + /* The initial database size */ + rc = write32bits(&pPager->jfd, pPager->dbSize); + } + if( rc==SQLITE_OK ){ + /* The assumed sector size for this process */ + rc = write32bits(&pPager->jfd, pPager->sectorSize); + } + + /* The journal header has been written successfully. Seek the journal + ** file descriptor to the end of the journal header sector. + */ + if( rc==SQLITE_OK ){ + sqlite3OsSeek(&pPager->jfd, pPager->journalOff-1); + rc = sqlite3OsWrite(&pPager->jfd, "\000", 1); + } + return rc; +} + +/* +** The journal file must be open when this is called. A journal header file +** (JOURNAL_HDR_SZ bytes) is read from the current location in the journal +** file. See comments above function writeJournalHdr() for a description of +** the journal header format. +** +** If the header is read successfully, *nRec is set to the number of +** page records following this header and *dbSize is set to the size of the +** database before the transaction began, in pages. Also, pPager->cksumInit +** is set to the value read from the journal header. SQLITE_OK is returned +** in this case. +** +** If the journal header file appears to be corrupted, SQLITE_DONE is +** returned and *nRec and *dbSize are not set. If JOURNAL_HDR_SZ bytes +** cannot be read from the journal file an error code is returned. +*/ +static int readJournalHdr( + Pager *pPager, + i64 journalSize, + u32 *pNRec, + u32 *pDbSize +){ + int rc; + unsigned char aMagic[8]; /* A buffer to hold the magic header */ + + rc = seekJournalHdr(pPager); + if( rc ) return rc; + + if( pPager->journalOff+JOURNAL_HDR_SZ(pPager) > journalSize ){ + return SQLITE_DONE; + } + + rc = sqlite3OsRead(&pPager->jfd, aMagic, sizeof(aMagic)); + if( rc ) return rc; + + if( memcmp(aMagic, aJournalMagic, sizeof(aMagic))!=0 ){ + return SQLITE_DONE; + } + + rc = read32bits(&pPager->jfd, pNRec); + if( rc ) return rc; + + rc = read32bits(&pPager->jfd, &pPager->cksumInit); + if( rc ) return rc; + + rc = read32bits(&pPager->jfd, pDbSize); + if( rc ) return rc; + + /* Update the assumed sector-size to match the value used by + ** the process that created this journal. If this journal was + ** created by a process other than this one, then this routine + ** is being called from within pager_playback(). The local value + ** of Pager.sectorSize is restored at the end of that routine. + */ + rc = read32bits(&pPager->jfd, (u32 *)&pPager->sectorSize); + if( rc ) return rc; + + pPager->journalOff += JOURNAL_HDR_SZ(pPager); + rc = sqlite3OsSeek(&pPager->jfd, pPager->journalOff); + return rc; +} + + +/* +** Write the supplied master journal name into the journal file for pager +** pPager at the current location. The master journal name must be the last +** thing written to a journal file. If the pager is in full-sync mode, the +** journal file descriptor is advanced to the next sector boundary before +** anything is written. The format is: +** +** + 4 bytes: PAGER_MJ_PGNO. +** + N bytes: length of master journal name. +** + 4 bytes: N +** + 4 bytes: Master journal name checksum. +** + 8 bytes: aJournalMagic[]. +** +** The master journal page checksum is the sum of the bytes in the master +** journal name. +*/ +static int writeMasterJournal(Pager *pPager, const char *zMaster){ + int rc; + int len; + int i; + u32 cksum = 0; + + if( !zMaster || pPager->setMaster) return SQLITE_OK; + pPager->setMaster = 1; + + len = strlen(zMaster); + for(i=0; ifullSync ){ + rc = seekJournalHdr(pPager); + if( rc!=SQLITE_OK ) return rc; + } + pPager->journalOff += (len+20); + + rc = write32bits(&pPager->jfd, PAGER_MJ_PGNO(pPager)); + if( rc!=SQLITE_OK ) return rc; + + rc = sqlite3OsWrite(&pPager->jfd, zMaster, len); + if( rc!=SQLITE_OK ) return rc; + + rc = write32bits(&pPager->jfd, len); + if( rc!=SQLITE_OK ) return rc; + + rc = write32bits(&pPager->jfd, cksum); + if( rc!=SQLITE_OK ) return rc; + + rc = sqlite3OsWrite(&pPager->jfd, aJournalMagic, sizeof(aJournalMagic)); + pPager->needSync = 1; + return rc; +} + +/* +** Add or remove a page from the list of all pages that are in the +** statement journal. +** +** The Pager keeps a separate list of pages that are currently in +** the statement journal. This helps the sqlite3pager_stmt_commit() +** routine run MUCH faster for the common case where there are many +** pages in memory but only a few are in the statement journal. +*/ +static void page_add_to_stmt_list(PgHdr *pPg){ + Pager *pPager = pPg->pPager; + if( pPg->inStmt ) return; + assert( pPg->pPrevStmt==0 && pPg->pNextStmt==0 ); + pPg->pPrevStmt = 0; + if( pPager->pStmt ){ + pPager->pStmt->pPrevStmt = pPg; + } + pPg->pNextStmt = pPager->pStmt; + pPager->pStmt = pPg; + pPg->inStmt = 1; +} +static void page_remove_from_stmt_list(PgHdr *pPg){ + if( !pPg->inStmt ) return; + if( pPg->pPrevStmt ){ + assert( pPg->pPrevStmt->pNextStmt==pPg ); + pPg->pPrevStmt->pNextStmt = pPg->pNextStmt; + }else{ + assert( pPg->pPager->pStmt==pPg ); + pPg->pPager->pStmt = pPg->pNextStmt; + } + if( pPg->pNextStmt ){ + assert( pPg->pNextStmt->pPrevStmt==pPg ); + pPg->pNextStmt->pPrevStmt = pPg->pPrevStmt; + } + pPg->pNextStmt = 0; + pPg->pPrevStmt = 0; + pPg->inStmt = 0; +} + +/* +** Find a page in the hash table given its page number. Return +** a pointer to the page or NULL if not found. +*/ +static PgHdr *pager_lookup(Pager *pPager, Pgno pgno){ + PgHdr *p = pPager->aHash[pager_hash(pgno)]; + while( p && p->pgno!=pgno ){ + p = p->pNextHash; + } + return p; +} + +/* +** Unlock the database and clear the in-memory cache. This routine +** sets the state of the pager back to what it was when it was first +** opened. Any outstanding pages are invalidated and subsequent attempts +** to access those pages will likely result in a coredump. +*/ +static void pager_reset(Pager *pPager){ + PgHdr *pPg, *pNext; + for(pPg=pPager->pAll; pPg; pPg=pNext){ + pNext = pPg->pNextAll; + sqliteFree(pPg); + } + pPager->pFirst = 0; + pPager->pFirstSynced = 0; + pPager->pLast = 0; + pPager->pAll = 0; + memset(pPager->aHash, 0, sizeof(pPager->aHash)); + pPager->nPage = 0; + if( pPager->state>=PAGER_RESERVED ){ + sqlite3pager_rollback(pPager); + } + sqlite3OsUnlock(&pPager->fd, NO_LOCK); + pPager->state = PAGER_UNLOCK; + pPager->dbSize = -1; + pPager->nRef = 0; + assert( pPager->journalOpen==0 ); +} + +/* +** When this routine is called, the pager has the journal file open and +** a RESERVED or EXCLUSIVE lock on the database. This routine releases +** the database lock and acquires a SHARED lock in its place. The journal +** file is deleted and closed. +** +** TODO: Consider keeping the journal file open for temporary databases. +** This might give a performance improvement on windows where opening +** a file is an expensive operation. +*/ +static int pager_unwritelock(Pager *pPager){ + PgHdr *pPg; + int rc; + assert( !pPager->memDb ); + if( pPager->statestmtOpen ){ + sqlite3OsClose(&pPager->stfd); + pPager->stmtOpen = 0; + } + if( pPager->journalOpen ){ + sqlite3OsClose(&pPager->jfd); + pPager->journalOpen = 0; + sqlite3OsDelete(pPager->zJournal); + sqliteFree( pPager->aInJournal ); + pPager->aInJournal = 0; + for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ + pPg->inJournal = 0; + pPg->dirty = 0; + pPg->needSync = 0; + } + pPager->dirtyCache = 0; + pPager->nRec = 0; + }else{ + assert( pPager->dirtyCache==0 || pPager->useJournal==0 ); + } + rc = sqlite3OsUnlock(&pPager->fd, SHARED_LOCK); + pPager->state = PAGER_SHARED; + pPager->origDbSize = 0; + pPager->setMaster = 0; + return rc; +} + +/* +** Compute and return a checksum for the page of data. +** +** This is not a real checksum. It is really just the sum of the +** random initial value and the page number. We experimented with +** a checksum of the entire data, but that was found to be too slow. +** +** Note that the page number is stored at the beginning of data and +** the checksum is stored at the end. This is important. If journal +** corruption occurs due to a power failure, the most likely scenario +** is that one end or the other of the record will be changed. It is +** much less likely that the two ends of the journal record will be +** correct and the middle be corrupt. Thus, this "checksum" scheme, +** though fast and simple, catches the mostly likely kind of corruption. +** +** FIX ME: Consider adding every 200th (or so) byte of the data to the +** checksum. That way if a single page spans 3 or more disk sectors and +** only the middle sector is corrupt, we will still have a reasonable +** chance of failing the checksum and thus detecting the problem. +*/ +static u32 pager_cksum(Pager *pPager, Pgno pgno, const char *aData){ + u32 cksum = pPager->cksumInit; + int i = pPager->pageSize-200; + while( i>0 ){ + cksum += aData[i]; + i -= 200; + } + return cksum; +} + +/* +** Read a single page from the journal file opened on file descriptor +** jfd. Playback this one page. +** +** If useCksum==0 it means this journal does not use checksums. Checksums +** are not used in statement journals because statement journals do not +** need to survive power failures. +*/ +static int pager_playback_one_page(Pager *pPager, OsFile *jfd, int useCksum){ + int rc; + PgHdr *pPg; /* An existing page in the cache */ + Pgno pgno; /* The page number of a page in journal */ + u32 cksum; /* Checksum used for sanity checking */ + u8 aData[SQLITE_MAX_PAGE_SIZE]; /* Temp storage for a page */ + + rc = read32bits(jfd, &pgno); + if( rc!=SQLITE_OK ) return rc; + rc = sqlite3OsRead(jfd, &aData, pPager->pageSize); + if( rc!=SQLITE_OK ) return rc; + pPager->journalOff += pPager->pageSize + 4; + + /* Sanity checking on the page. This is more important that I originally + ** thought. If a power failure occurs while the journal is being written, + ** it could cause invalid data to be written into the journal. We need to + ** detect this invalid data (with high probability) and ignore it. + */ + if( pgno==0 || pgno==PAGER_MJ_PGNO(pPager) ){ + return SQLITE_DONE; + } + if( pgno>(unsigned)pPager->dbSize ){ + return SQLITE_OK; + } + if( useCksum ){ + rc = read32bits(jfd, &cksum); + if( rc ) return rc; + pPager->journalOff += 4; + if( pager_cksum(pPager, pgno, aData)!=cksum ){ + return SQLITE_DONE; + } + } + + assert( pPager->state==PAGER_RESERVED || pPager->state>=PAGER_EXCLUSIVE ); + + /* If the pager is in RESERVED state, then there must be a copy of this + ** page in the pager cache. In this case just update the pager cache, + ** not the database file. The page is left marked dirty in this case. + ** + ** If in EXCLUSIVE state, then we update the pager cache if it exists + ** and the main file. The page is then marked not dirty. + */ + pPg = pager_lookup(pPager, pgno); + assert( pPager->state>=PAGER_EXCLUSIVE || pPg ); + TRACE3("PLAYBACK %d page %d\n", pPager->fd.h, pgno); + if( pPager->state>=PAGER_EXCLUSIVE ){ + sqlite3OsSeek(&pPager->fd, (pgno-1)*(i64)pPager->pageSize); + rc = sqlite3OsWrite(&pPager->fd, aData, pPager->pageSize); + } + if( pPg ){ + /* No page should ever be rolled back that is in use, except for page + ** 1 which is held in use in order to keep the lock on the database + ** active. + */ + void *pData; + assert( pPg->nRef==0 || pPg->pgno==1 ); + pData = PGHDR_TO_DATA(pPg); + memcpy(pData, aData, pPager->pageSize); + if( pPager->xDestructor ){ /*** FIX ME: Should this be xReinit? ***/ + pPager->xDestructor(pData, pPager->pageSize); + } + if( pPager->state>=PAGER_EXCLUSIVE ){ + pPg->dirty = 0; + pPg->needSync = 0; + } + CODEC(pPager, pData, pPg->pgno, 3); + } + return rc; +} + +/* +** Parameter zMaster is the name of a master journal file. A single journal +** file that referred to the master journal file has just been rolled back. +** This routine checks if it is possible to delete the master journal file, +** and does so if it is. +** +** The master journal file contains the names of all child journals. +** To tell if a master journal can be deleted, check to each of the +** children. If all children are either missing or do not refer to +** a different master journal, then this master journal can be deleted. +*/ +static int pager_delmaster(const char *zMaster){ + int rc; + int master_open = 0; + OsFile master; + char *zMasterJournal = 0; /* Contents of master journal file */ + i64 nMasterJournal; /* Size of master journal file */ + + /* Open the master journal file exclusively in case some other process + ** is running this routine also. Not that it makes too much difference. + */ + memset(&master, 0, sizeof(master)); + rc = sqlite3OsOpenReadOnly(zMaster, &master); + if( rc!=SQLITE_OK ) goto delmaster_out; + master_open = 1; + rc = sqlite3OsFileSize(&master, &nMasterJournal); + if( rc!=SQLITE_OK ) goto delmaster_out; + + if( nMasterJournal>0 ){ + char *zJournal; + char *zMasterPtr = 0; + + /* Load the entire master journal file into space obtained from + ** sqliteMalloc() and pointed to by zMasterJournal. + */ + zMasterJournal = (char *)sqliteMalloc(nMasterJournal); + if( !zMasterJournal ){ + rc = SQLITE_NOMEM; + goto delmaster_out; + } + rc = sqlite3OsRead(&master, zMasterJournal, nMasterJournal); + if( rc!=SQLITE_OK ) goto delmaster_out; + + zJournal = zMasterJournal; + while( (zJournal-zMasterJournal)pAll; pPg; pPg=pPg->pNextAll){ + char zBuf[SQLITE_MAX_PAGE_SIZE]; + if( !pPg->dirty ) continue; + if( (int)pPg->pgno <= pPager->origDbSize ){ + sqlite3OsSeek(&pPager->fd, pPager->pageSize*(i64)(pPg->pgno-1)); + rc = sqlite3OsRead(&pPager->fd, zBuf, pPager->pageSize); + TRACE3("REFETCH %d page %d\n", pPager->fd.h, pPg->pgno); + if( rc ) break; + CODEC(pPager, zBuf, pPg->pgno, 2); + }else{ + memset(zBuf, 0, pPager->pageSize); + } + if( pPg->nRef==0 || memcmp(zBuf, PGHDR_TO_DATA(pPg), pPager->pageSize) ){ + memcpy(PGHDR_TO_DATA(pPg), zBuf, pPager->pageSize); + if( pPager->xReiniter ){ + pPager->xReiniter(PGHDR_TO_DATA(pPg), pPager->pageSize); + }else{ + memset(PGHDR_TO_EXTRA(pPg, pPager), 0, pPager->nExtra); + } + } + pPg->needSync = 0; + pPg->dirty = 0; + } + return rc; +} + +/* +** Truncate the main file of the given pager to the number of pages +** indicated. +*/ +static int pager_truncate(Pager *pPager, int nPage){ + return sqlite3OsTruncate(&pPager->fd, pPager->pageSize*(i64)nPage); +} + +/* +** Playback the journal and thus restore the database file to +** the state it was in before we started making changes. +** +** The journal file format is as follows: +** +** (1) 8 byte prefix. A copy of aJournalMagic[]. +** (2) 4 byte big-endian integer which is the number of valid page records +** in the journal. If this value is 0xffffffff, then compute the +** number of page records from the journal size. +** (3) 4 byte big-endian integer which is the initial value for the +** sanity checksum. +** (4) 4 byte integer which is the number of pages to truncate the +** database to during a rollback. +** (5) 4 byte integer which is the number of bytes in the master journal +** name. The value may be zero (indicate that there is no master +** journal.) +** (6) N bytes of the master journal name. The name will be nul-terminated +** and might be shorter than the value read from (5). If the first byte +** of the name is \000 then there is no master journal. The master +** journal name is stored in UTF-8. +** (7) Zero or more pages instances, each as follows: +** + 4 byte page number. +** + pPager->pageSize bytes of data. +** + 4 byte checksum +** +** When we speak of the journal header, we mean the first 6 items above. +** Each entry in the journal is an instance of the 7th item. +** +** Call the value from the second bullet "nRec". nRec is the number of +** valid page entries in the journal. In most cases, you can compute the +** value of nRec from the size of the journal file. But if a power +** failure occurred while the journal was being written, it could be the +** case that the size of the journal file had already been increased but +** the extra entries had not yet made it safely to disk. In such a case, +** the value of nRec computed from the file size would be too large. For +** that reason, we always use the nRec value in the header. +** +** If the nRec value is 0xffffffff it means that nRec should be computed +** from the file size. This value is used when the user selects the +** no-sync option for the journal. A power failure could lead to corruption +** in this case. But for things like temporary table (which will be +** deleted when the power is restored) we don't care. +** +** If the file opened as the journal file is not a well-formed +** journal file then all pages up to the first corrupted page are rolled +** back (or no pages if the journal header is corrupted). The journal file +** is then deleted and SQLITE_OK returned, just as if no corruption had +** been encountered. +** +** If an I/O or malloc() error occurs, the journal-file is not deleted +** and an error code is returned. +*/ +static int pager_playback(Pager *pPager){ + i64 szJ; /* Size of the journal file in bytes */ + u32 nRec; /* Number of Records in the journal */ + int i; /* Loop counter */ + Pgno mxPg = 0; /* Size of the original file in pages */ + int rc; /* Result code of a subroutine */ + char *zMaster = 0; /* Name of master journal file if any */ + + /* Figure out how many records are in the journal. Abort early if + ** the journal is empty. + */ + assert( pPager->journalOpen ); + rc = sqlite3OsFileSize(&pPager->jfd, &szJ); + if( rc!=SQLITE_OK ){ + goto end_playback; + } + + /* Read the master journal name from the journal, if it is present. + ** If a master journal file name is specified, but the file is not + ** present on disk, then the journal is not hot and does not need to be + ** played back. + */ + rc = readMasterJournal(&pPager->jfd, &zMaster); + assert( rc!=SQLITE_DONE ); + if( rc!=SQLITE_OK || (zMaster && !sqlite3OsFileExists(zMaster)) ){ + sqliteFree(zMaster); + zMaster = 0; + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + goto end_playback; + } + sqlite3OsSeek(&pPager->jfd, 0); + pPager->journalOff = 0; + + /* This loop terminates either when the readJournalHdr() call returns + ** SQLITE_DONE or an IO error occurs. */ + while( 1 ){ + + /* Read the next journal header from the journal file. If there are + ** not enough bytes left in the journal file for a complete header, or + ** it is corrupted, then a process must of failed while writing it. + ** This indicates nothing more needs to be rolled back. + */ + rc = readJournalHdr(pPager, szJ, &nRec, &mxPg); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + } + goto end_playback; + } + + /* If nRec is 0xffffffff, then this journal was created by a process + ** working in no-sync mode. This means that the rest of the journal + ** file consists of pages, there are no more journal headers. Compute + ** the value of nRec based on this assumption. + */ + if( nRec==0xffffffff ){ + assert( pPager->journalOff==JOURNAL_HDR_SZ(pPager) ); + nRec = (szJ - JOURNAL_HDR_SZ(pPager))/JOURNAL_PG_SZ(pPager); + } + + /* If this is the first header read from the journal, truncate the + ** database file back to it's original size. + */ + if( pPager->journalOff==JOURNAL_HDR_SZ(pPager) ){ + assert( pPager->origDbSize==0 || pPager->origDbSize==mxPg ); + rc = pager_truncate(pPager, mxPg); + if( rc!=SQLITE_OK ){ + goto end_playback; + } + pPager->dbSize = mxPg; + } + + /* rc = sqlite3OsSeek(&pPager->jfd, JOURNAL_HDR_SZ(pPager)); */ + if( rc!=SQLITE_OK ) goto end_playback; + + /* Copy original pages out of the journal and back into the database file. + */ + for(i=0; ijfd, 1); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + pPager->journalOff = szJ; + break; + }else{ + goto end_playback; + } + } + } + } + + /* Pages that have been written to the journal but never synced + ** where not restored by the loop above. We have to restore those + ** pages by reading them back from the original database. + */ + assert( rc==SQLITE_OK ); + pager_reload_cache(pPager); + +end_playback: + if( rc==SQLITE_OK ){ + rc = pager_unwritelock(pPager); + } + if( zMaster ){ + /* If there was a master journal and this routine will return true, + ** see if it is possible to delete the master journal. If errors + ** occur during this process, ignore them. + */ + if( rc==SQLITE_OK ){ + pager_delmaster(zMaster); + } + sqliteFree(zMaster); + } + + /* The Pager.sectorSize variable may have been updated while rolling + ** back a journal created by a process with a different PAGER_SECTOR_SIZE + ** value. Reset it to the correct value for this process. + */ + pPager->sectorSize = PAGER_SECTOR_SIZE; + return rc; +} + +/* +** Playback the statement journal. +** +** This is similar to playing back the transaction journal but with +** a few extra twists. +** +** (1) The number of pages in the database file at the start of +** the statement is stored in pPager->stmtSize, not in the +** journal file itself. +** +** (2) In addition to playing back the statement journal, also +** playback all pages of the transaction journal beginning +** at offset pPager->stmtJSize. +*/ +static int pager_stmt_playback(Pager *pPager){ + i64 szJ; /* Size of the full journal */ + i64 hdrOff; + int nRec; /* Number of Records */ + int i; /* Loop counter */ + int rc; + + szJ = pPager->journalOff; +#ifndef NDEBUG + { + i64 os_szJ; + rc = sqlite3OsFileSize(&pPager->jfd, &os_szJ); + if( rc!=SQLITE_OK ) return rc; + assert( szJ==os_szJ ); + } +#endif + + /* Set hdrOff to be the offset to the first journal header written + ** this statement transaction, or the end of the file if no journal + ** header was written. + */ + hdrOff = pPager->stmtHdrOff; + assert( pPager->fullSync || !hdrOff ); + if( !hdrOff ){ + hdrOff = szJ; + } + + + /* Truncate the database back to its original size. + */ + rc = pager_truncate(pPager, pPager->stmtSize); + pPager->dbSize = pPager->stmtSize; + + /* Figure out how many records are in the statement journal. + */ + assert( pPager->stmtInUse && pPager->journalOpen ); + sqlite3OsSeek(&pPager->stfd, 0); + nRec = pPager->stmtNRec; + + /* Copy original pages out of the statement journal and back into the + ** database file. Note that the statement journal omits checksums from + ** each record since power-failure recovery is not important to statement + ** journals. + */ + for(i=nRec-1; i>=0; i--){ + rc = pager_playback_one_page(pPager, &pPager->stfd, 0); + assert( rc!=SQLITE_DONE ); + if( rc!=SQLITE_OK ) goto end_stmt_playback; + } + + /* Now roll some pages back from the transaction journal. Pager.stmtJSize + ** was the size of the journal file when this statement was started, so + ** everything after that needs to be rolled back, either into the + ** database, the memory cache, or both. + ** + ** If it is not zero, then Pager.stmtHdrOff is the offset to the start + ** of the first journal header written during this statement transaction. + */ + rc = sqlite3OsSeek(&pPager->jfd, pPager->stmtJSize); + if( rc!=SQLITE_OK ){ + goto end_stmt_playback; + } + pPager->journalOff = pPager->stmtJSize; + pPager->cksumInit = pPager->stmtCksum; + assert( JOURNAL_HDR_SZ(pPager)<(pPager->pageSize+8) ); + while( pPager->journalOff <= (hdrOff-(pPager->pageSize+8)) ){ + rc = pager_playback_one_page(pPager, &pPager->jfd, 1); + assert( rc!=SQLITE_DONE ); + if( rc!=SQLITE_OK ) goto end_stmt_playback; + } + + while( pPager->journalOff < szJ ){ + u32 nRec; + u32 dummy; + rc = readJournalHdr(pPager, szJ, &nRec, &dummy); + if( rc!=SQLITE_OK ){ + assert( rc!=SQLITE_DONE ); + goto end_stmt_playback; + } + if( nRec==0 ){ + nRec = (szJ - pPager->journalOff) / (pPager->pageSize+8); + } + for(i=nRec-1; i>=0 && pPager->journalOff < szJ; i--){ + rc = pager_playback_one_page(pPager, &pPager->jfd, 1); + assert( rc!=SQLITE_DONE ); + if( rc!=SQLITE_OK ) goto end_stmt_playback; + } + } + + pPager->journalOff = szJ; + +end_stmt_playback: + if( rc!=SQLITE_OK ){ + pPager->errMask |= PAGER_ERR_CORRUPT; + rc = SQLITE_CORRUPT; /* bkpt-CORRUPT */ + }else{ + pPager->journalOff = szJ; + /* pager_reload_cache(pPager); */ + } + return rc; +} + +/* +** Change the maximum number of in-memory pages that are allowed. +** +** The maximum number is the absolute value of the mxPage parameter. +** If mxPage is negative, the noSync flag is also set. noSync bypasses +** calls to sqlite3OsSync(). The pager runs much faster with noSync on, +** but if the operating system crashes or there is an abrupt power +** failure, the database file might be left in an inconsistent and +** unrepairable state. +*/ +void sqlite3pager_set_cachesize(Pager *pPager, int mxPage){ + if( mxPage>=0 ){ + pPager->noSync = pPager->tempFile; + if( pPager->noSync ) pPager->needSync = 0; + }else{ + pPager->noSync = 1; + mxPage = -mxPage; + } + if( mxPage>10 ){ + pPager->mxPage = mxPage; + }else{ + pPager->mxPage = 10; + } +} + +/* +** Adjust the robustness of the database to damage due to OS crashes +** or power failures by changing the number of syncs()s when writing +** the rollback journal. There are three levels: +** +** OFF sqlite3OsSync() is never called. This is the default +** for temporary and transient files. +** +** NORMAL The journal is synced once before writes begin on the +** database. This is normally adequate protection, but +** it is theoretically possible, though very unlikely, +** that an inopertune power failure could leave the journal +** in a state which would cause damage to the database +** when it is rolled back. +** +** FULL The journal is synced twice before writes begin on the +** database (with some additional information - the nRec field +** of the journal header - being written in between the two +** syncs). If we assume that writing a +** single disk sector is atomic, then this mode provides +** assurance that the journal will not be corrupted to the +** point of causing damage to the database during rollback. +** +** Numeric values associated with these states are OFF==1, NORMAL=2, +** and FULL=3. +*/ +void sqlite3pager_set_safety_level(Pager *pPager, int level){ + pPager->noSync = level==1 || pPager->tempFile; + pPager->fullSync = level==3 && !pPager->tempFile; + if( pPager->noSync ) pPager->needSync = 0; +} + +/* +** Open a temporary file. Write the name of the file into zName +** (zName must be at least SQLITE_TEMPNAME_SIZE bytes long.) Write +** the file descriptor into *fd. Return SQLITE_OK on success or some +** other error code if we fail. +** +** The OS will automatically delete the temporary file when it is +** closed. +*/ +static int sqlite3pager_opentemp(char *zFile, OsFile *fd){ + int cnt = 8; + int rc; + do{ + cnt--; + sqlite3OsTempFileName(zFile); + rc = sqlite3OsOpenExclusive(zFile, fd, 1); + }while( cnt>0 && rc!=SQLITE_OK && rc!=SQLITE_NOMEM ); + return rc; +} + +/* +** Create a new page cache and put a pointer to the page cache in *ppPager. +** The file to be cached need not exist. The file is not locked until +** the first call to sqlite3pager_get() and is only held open until the +** last page is released using sqlite3pager_unref(). +** +** If zFilename is NULL then a randomly-named temporary file is created +** and used as the file to be cached. The file will be deleted +** automatically when it is closed. +** +** If zFilename is ":memory:" then all information is held in cache. +** It is never written to disk. This can be used to implement an +** in-memory database. +*/ +int sqlite3pager_open( + Pager **ppPager, /* Return the Pager structure here */ + const char *zFilename, /* Name of the database file to open */ + int nExtra, /* Extra bytes append to each in-memory page */ + int useJournal /* TRUE to use a rollback journal on this file */ +){ + Pager *pPager; + char *zFullPathname = 0; + int nameLen; + OsFile fd; + int rc = SQLITE_OK; + int i; + int tempFile = 0; + int memDb = 0; + int readOnly = 0; + char zTemp[SQLITE_TEMPNAME_SIZE]; + + *ppPager = 0; + memset(&fd, 0, sizeof(fd)); + if( sqlite3_malloc_failed ){ + return SQLITE_NOMEM; + } + if( zFilename && zFilename[0] ){ + if( strcmp(zFilename,":memory:")==0 ){ + memDb = 1; + zFullPathname = sqliteStrDup(""); + rc = SQLITE_OK; + }else{ + zFullPathname = sqlite3OsFullPathname(zFilename); + if( zFullPathname ){ + rc = sqlite3OsOpenReadWrite(zFullPathname, &fd, &readOnly); + } + } + }else{ + rc = sqlite3pager_opentemp(zTemp, &fd); + zFilename = zTemp; + zFullPathname = sqlite3OsFullPathname(zFilename); + if( rc==SQLITE_OK ){ + tempFile = 1; + } + } + if( !zFullPathname ){ + sqlite3OsClose(&fd); + return SQLITE_NOMEM; + } + if( rc!=SQLITE_OK ){ + sqlite3OsClose(&fd); + sqliteFree(zFullPathname); + return rc; + } + nameLen = strlen(zFullPathname); + pPager = sqliteMalloc( sizeof(*pPager) + nameLen*3 + 30 ); + if( pPager==0 ){ + sqlite3OsClose(&fd); + sqliteFree(zFullPathname); + return SQLITE_NOMEM; + } + TRACE3("OPEN %d %s\n", fd.h, zFullPathname); + pPager->zFilename = (char*)&pPager[1]; + pPager->zDirectory = &pPager->zFilename[nameLen+1]; + pPager->zJournal = &pPager->zDirectory[nameLen+1]; + strcpy(pPager->zFilename, zFullPathname); + strcpy(pPager->zDirectory, zFullPathname); + for(i=nameLen; i>0 && pPager->zDirectory[i-1]!='/'; i--){} + if( i>0 ) pPager->zDirectory[i-1] = 0; + strcpy(pPager->zJournal, zFullPathname); + sqliteFree(zFullPathname); + strcpy(&pPager->zJournal[nameLen], "-journal"); + pPager->fd = fd; +#if OS_UNIX + pPager->fd.pPager = pPager; +#endif + pPager->journalOpen = 0; + pPager->useJournal = useJournal && !memDb; + pPager->stmtOpen = 0; + pPager->stmtInUse = 0; + pPager->nRef = 0; + pPager->dbSize = memDb-1; + pPager->pageSize = SQLITE_DEFAULT_PAGE_SIZE; + pPager->stmtSize = 0; + pPager->stmtJSize = 0; + pPager->nPage = 0; + pPager->mxPage = 100; + pPager->state = PAGER_UNLOCK; + pPager->errMask = 0; + pPager->tempFile = tempFile; + pPager->memDb = memDb; + pPager->readOnly = readOnly; + pPager->needSync = 0; + pPager->noSync = pPager->tempFile || !useJournal; + pPager->fullSync = (pPager->noSync?0:1); + pPager->pFirst = 0; + pPager->pFirstSynced = 0; + pPager->pLast = 0; + pPager->nExtra = nExtra; + pPager->sectorSize = PAGER_SECTOR_SIZE; + pPager->pBusyHandler = 0; + memset(pPager->aHash, 0, sizeof(pPager->aHash)); + *ppPager = pPager; + return SQLITE_OK; +} + +/* +** Set the busy handler function. +*/ +void sqlite3pager_set_busyhandler(Pager *pPager, BusyHandler *pBusyHandler){ + pPager->pBusyHandler = pBusyHandler; +} + +/* +** Set the destructor for this pager. If not NULL, the destructor is called +** when the reference count on each page reaches zero. The destructor can +** be used to clean up information in the extra segment appended to each page. +** +** The destructor is not called as a result sqlite3pager_close(). +** Destructors are only called by sqlite3pager_unref(). +*/ +void sqlite3pager_set_destructor(Pager *pPager, void (*xDesc)(void*,int)){ + pPager->xDestructor = xDesc; +} + +/* +** Set the reinitializer for this pager. If not NULL, the reinitializer +** is called when the content of a page in cache is restored to its original +** value as a result of a rollback. The callback gives higher-level code +** an opportunity to restore the EXTRA section to agree with the restored +** page data. +*/ +void sqlite3pager_set_reiniter(Pager *pPager, void (*xReinit)(void*,int)){ + pPager->xReiniter = xReinit; +} + +/* +** Set the page size. +** +** The page size must only be changed when the cache is empty. +*/ +void sqlite3pager_set_pagesize(Pager *pPager, int pageSize){ + assert( pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE ); + pPager->pageSize = pageSize; +} + +/* +** Read the first N bytes from the beginning of the file into memory +** that pDest points to. No error checking is done. +*/ +void sqlite3pager_read_fileheader(Pager *pPager, int N, unsigned char *pDest){ + memset(pDest, 0, N); + if( pPager->memDb==0 ){ + sqlite3OsSeek(&pPager->fd, 0); + sqlite3OsRead(&pPager->fd, pDest, N); + } +} + +/* +** Return the total number of pages in the disk file associated with +** pPager. +*/ +int sqlite3pager_pagecount(Pager *pPager){ + i64 n; + assert( pPager!=0 ); + if( pPager->dbSize>=0 ){ + return pPager->dbSize; + } + if( sqlite3OsFileSize(&pPager->fd, &n)!=SQLITE_OK ){ + pPager->errMask |= PAGER_ERR_DISK; + return 0; + } + n /= pPager->pageSize; + if( !pPager->memDb && n==PENDING_BYTE/pPager->pageSize ){ + n++; + } + if( pPager->state!=PAGER_UNLOCK ){ + pPager->dbSize = n; + } + return n; +} + +/* +** Forward declaration +*/ +static int syncJournal(Pager*); + + +/* +** Unlink a page from the free list (the list of all pages where nRef==0) +** and from its hash collision chain. +*/ +static void unlinkPage(PgHdr *pPg){ + Pager *pPager = pPg->pPager; + + /* Keep the pFirstSynced pointer pointing at the first synchronized page */ + if( pPg==pPager->pFirstSynced ){ + PgHdr *p = pPg->pNextFree; + while( p && p->needSync ){ p = p->pNextFree; } + pPager->pFirstSynced = p; + } + + /* Unlink from the freelist */ + if( pPg->pPrevFree ){ + pPg->pPrevFree->pNextFree = pPg->pNextFree; + }else{ + assert( pPager->pFirst==pPg ); + pPager->pFirst = pPg->pNextFree; + } + if( pPg->pNextFree ){ + pPg->pNextFree->pPrevFree = pPg->pPrevFree; + }else{ + assert( pPager->pLast==pPg ); + pPager->pLast = pPg->pPrevFree; + } + pPg->pNextFree = pPg->pPrevFree = 0; + + /* Unlink from the pgno hash table */ + if( pPg->pNextHash ){ + pPg->pNextHash->pPrevHash = pPg->pPrevHash; + } + if( pPg->pPrevHash ){ + pPg->pPrevHash->pNextHash = pPg->pNextHash; + }else{ + int h = pager_hash(pPg->pgno); + assert( pPager->aHash[h]==pPg ); + pPager->aHash[h] = pPg->pNextHash; + } + pPg->pNextHash = pPg->pPrevHash = 0; +} + +/* +** This routine is used to truncate an in-memory database. Delete +** all pages whose pgno is larger than pPager->dbSize and is unreferenced. +** Referenced pages larger than pPager->dbSize are zeroed. +*/ +static void memoryTruncate(Pager *pPager){ + PgHdr *pPg; + PgHdr **ppPg; + int dbSize = pPager->dbSize; + + ppPg = &pPager->pAll; + while( (pPg = *ppPg)!=0 ){ + if( pPg->pgno<=dbSize ){ + ppPg = &pPg->pNextAll; + }else if( pPg->nRef>0 ){ + memset(PGHDR_TO_DATA(pPg), 0, pPager->pageSize); + ppPg = &pPg->pNextAll; + }else{ + *ppPg = pPg->pNextAll; + unlinkPage(pPg); + sqliteFree(pPg); + pPager->nPage--; + } + } +} + +/* +** Truncate the file to the number of pages specified. +*/ +int sqlite3pager_truncate(Pager *pPager, Pgno nPage){ + int rc; + sqlite3pager_pagecount(pPager); + if( pPager->errMask!=0 ){ + rc = pager_errcode(pPager); + return rc; + } + if( nPage>=(unsigned)pPager->dbSize ){ + return SQLITE_OK; + } + if( pPager->memDb ){ + pPager->dbSize = nPage; + memoryTruncate(pPager); + return SQLITE_OK; + } + rc = syncJournal(pPager); + if( rc!=SQLITE_OK ){ + return rc; + } + rc = pager_truncate(pPager, nPage); + if( rc==SQLITE_OK ){ + pPager->dbSize = nPage; + } + return rc; +} + +/* +** Shutdown the page cache. Free all memory and close all files. +** +** If a transaction was in progress when this routine is called, that +** transaction is rolled back. All outstanding pages are invalidated +** and their memory is freed. Any attempt to use a page associated +** with this page cache after this function returns will likely +** result in a coredump. +*/ +int sqlite3pager_close(Pager *pPager){ + PgHdr *pPg, *pNext; + switch( pPager->state ){ + case PAGER_RESERVED: + case PAGER_SYNCED: + case PAGER_EXCLUSIVE: { + sqlite3pager_rollback(pPager); + if( !pPager->memDb ){ + sqlite3OsUnlock(&pPager->fd, NO_LOCK); + } + assert( pPager->journalOpen==0 ); + break; + } + case PAGER_SHARED: { + if( !pPager->memDb ){ + sqlite3OsUnlock(&pPager->fd, NO_LOCK); + } + break; + } + default: { + /* Do nothing */ + break; + } + } + for(pPg=pPager->pAll; pPg; pPg=pNext){ +#ifndef NDEBUG + if( pPager->memDb ){ + PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager); + assert( !pPg->alwaysRollback ); + assert( !pHist->pOrig ); + assert( !pHist->pStmt ); + } +#endif + pNext = pPg->pNextAll; + sqliteFree(pPg); + } + TRACE2("CLOSE %d\n", pPager->fd.h); + sqlite3OsClose(&pPager->fd); + assert( pPager->journalOpen==0 ); + /* Temp files are automatically deleted by the OS + ** if( pPager->tempFile ){ + ** sqlite3OsDelete(pPager->zFilename); + ** } + */ + if( pPager->zFilename!=(char*)&pPager[1] ){ + assert( 0 ); /* Cannot happen */ + sqliteFree(pPager->zFilename); + sqliteFree(pPager->zJournal); + sqliteFree(pPager->zDirectory); + } + sqliteFree(pPager); + return SQLITE_OK; +} + +/* +** Return the page number for the given page data. +*/ +Pgno sqlite3pager_pagenumber(void *pData){ + PgHdr *p = DATA_TO_PGHDR(pData); + return p->pgno; +} + +/* +** The page_ref() function increments the reference count for a page. +** If the page is currently on the freelist (the reference count is zero) then +** remove it from the freelist. +** +** For non-test systems, page_ref() is a macro that calls _page_ref() +** online of the reference count is zero. For test systems, page_ref() +** is a real function so that we can set breakpoints and trace it. +*/ +static void _page_ref(PgHdr *pPg){ + if( pPg->nRef==0 ){ + /* The page is currently on the freelist. Remove it. */ + if( pPg==pPg->pPager->pFirstSynced ){ + PgHdr *p = pPg->pNextFree; + while( p && p->needSync ){ p = p->pNextFree; } + pPg->pPager->pFirstSynced = p; + } + if( pPg->pPrevFree ){ + pPg->pPrevFree->pNextFree = pPg->pNextFree; + }else{ + pPg->pPager->pFirst = pPg->pNextFree; + } + if( pPg->pNextFree ){ + pPg->pNextFree->pPrevFree = pPg->pPrevFree; + }else{ + pPg->pPager->pLast = pPg->pPrevFree; + } + pPg->pPager->nRef++; + } + pPg->nRef++; + REFINFO(pPg); +} +#ifdef SQLITE_TEST + static void page_ref(PgHdr *pPg){ + if( pPg->nRef==0 ){ + _page_ref(pPg); + }else{ + pPg->nRef++; + REFINFO(pPg); + } + } +#else +# define page_ref(P) ((P)->nRef==0?_page_ref(P):(void)(P)->nRef++) +#endif + +/* +** Increment the reference count for a page. The input pointer is +** a reference to the page data. +*/ +int sqlite3pager_ref(void *pData){ + PgHdr *pPg = DATA_TO_PGHDR(pData); + page_ref(pPg); + return SQLITE_OK; +} + +/* +** Sync the journal. In other words, make sure all the pages that have +** been written to the journal have actually reached the surface of the +** disk. It is not safe to modify the original database file until after +** the journal has been synced. If the original database is modified before +** the journal is synced and a power failure occurs, the unsynced journal +** data would be lost and we would be unable to completely rollback the +** database changes. Database corruption would occur. +** +** This routine also updates the nRec field in the header of the journal. +** (See comments on the pager_playback() routine for additional information.) +** If the sync mode is FULL, two syncs will occur. First the whole journal +** is synced, then the nRec field is updated, then a second sync occurs. +** +** For temporary databases, we do not care if we are able to rollback +** after a power failure, so sync occurs. +** +** This routine clears the needSync field of every page current held in +** memory. +*/ +static int syncJournal(Pager *pPager){ + PgHdr *pPg; + int rc = SQLITE_OK; + + /* Sync the journal before modifying the main database + ** (assuming there is a journal and it needs to be synced.) + */ + if( pPager->needSync ){ + if( !pPager->tempFile ){ + assert( pPager->journalOpen ); + /* assert( !pPager->noSync ); // noSync might be set if synchronous + ** was turned off after the transaction was started. Ticket #615 */ +#ifndef NDEBUG + { + /* Make sure the pPager->nRec counter we are keeping agrees + ** with the nRec computed from the size of the journal file. + */ + i64 jSz; + rc = sqlite3OsFileSize(&pPager->jfd, &jSz); + if( rc!=0 ) return rc; + assert( pPager->journalOff==jSz ); + } +#endif + { + /* Write the nRec value into the journal file header. If in + ** full-synchronous mode, sync the journal first. This ensures that + ** all data has really hit the disk before nRec is updated to mark + ** it as a candidate for rollback. + */ + if( pPager->fullSync ){ + TRACE2("SYNC journal of %d\n", pPager->fd.h); + rc = sqlite3OsSync(&pPager->jfd); + if( rc!=0 ) return rc; + } + sqlite3OsSeek(&pPager->jfd, pPager->journalHdr + sizeof(aJournalMagic)); + rc = write32bits(&pPager->jfd, pPager->nRec); + if( rc ) return rc; + + sqlite3OsSeek(&pPager->jfd, pPager->journalOff); + } + TRACE2("SYNC journal of %d\n", pPager->fd.h); + rc = sqlite3OsSync(&pPager->jfd); + if( rc!=0 ) return rc; + pPager->journalStarted = 1; + } + pPager->needSync = 0; + + /* Erase the needSync flag from every page. + */ + for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ + pPg->needSync = 0; + } + pPager->pFirstSynced = pPager->pFirst; + } + +#ifndef NDEBUG + /* If the Pager.needSync flag is clear then the PgHdr.needSync + ** flag must also be clear for all pages. Verify that this + ** invariant is true. + */ + else{ + for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ + assert( pPg->needSync==0 ); + } + assert( pPager->pFirstSynced==pPager->pFirst ); + } +#endif + + return rc; +} + +/* +** Try to obtain a lock on a file. Invoke the busy callback if the lock +** is currently not available. Repeate until the busy callback returns +** false or until the lock succeeds. +** +** Return SQLITE_OK on success and an error code if we cannot obtain +** the lock. +*/ +static int pager_wait_on_lock(Pager *pPager, int locktype){ + int rc; + assert( PAGER_SHARED==SHARED_LOCK ); + assert( PAGER_RESERVED==RESERVED_LOCK ); + assert( PAGER_EXCLUSIVE==EXCLUSIVE_LOCK ); + if( pPager->state>=locktype ){ + rc = SQLITE_OK; + }else{ + int busy = 1; + do { + rc = sqlite3OsLock(&pPager->fd, locktype); + }while( rc==SQLITE_BUSY && + pPager->pBusyHandler && + pPager->pBusyHandler->xFunc && + pPager->pBusyHandler->xFunc(pPager->pBusyHandler->pArg, busy++) + ); + if( rc==SQLITE_OK ){ + pPager->state = locktype; + } + } + return rc; +} + +/* +** Given a list of pages (connected by the PgHdr.pDirty pointer) write +** every one of those pages out to the database file and mark them all +** as clean. +*/ +static int pager_write_pagelist(PgHdr *pList){ + Pager *pPager; + int rc; + + if( pList==0 ) return SQLITE_OK; + pPager = pList->pPager; + + /* At this point there may be either a RESERVED or EXCLUSIVE lock on the + ** database file. If there is already an EXCLUSIVE lock, the following + ** calls to sqlite3OsLock() are no-ops. + ** + ** Moving the lock from RESERVED to EXCLUSIVE actually involves going + ** through an intermediate state PENDING. A PENDING lock prevents new + ** readers from attaching to the database but is unsufficient for us to + ** write. The idea of a PENDING lock is to prevent new readers from + ** coming in while we wait for existing readers to clear. + ** + ** While the pager is in the RESERVED state, the original database file + ** is unchanged and we can rollback without having to playback the + ** journal into the original database file. Once we transition to + ** EXCLUSIVE, it means the database file has been changed and any rollback + ** will require a journal playback. + */ + rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK); + if( rc!=SQLITE_OK ){ + return rc; + } + + while( pList ){ + assert( pList->dirty ); + sqlite3OsSeek(&pPager->fd, (pList->pgno-1)*(i64)pPager->pageSize); + CODEC(pPager, PGHDR_TO_DATA(pList), pList->pgno, 6); + TRACE3("STORE %d page %d\n", pPager->fd.h, pList->pgno); + rc = sqlite3OsWrite(&pPager->fd, PGHDR_TO_DATA(pList), pPager->pageSize); + CODEC(pPager, PGHDR_TO_DATA(pList), pList->pgno, 0); + if( rc ) return rc; + pList->dirty = 0; + pList = pList->pDirty; + } + return SQLITE_OK; +} + +/* +** Collect every dirty page into a dirty list and +** return a pointer to the head of that list. All pages are +** collected even if they are still in use. +*/ +static PgHdr *pager_get_all_dirty_pages(Pager *pPager){ + PgHdr *p, *pList; + pList = 0; + for(p=pPager->pAll; p; p=p->pNextAll){ + if( p->dirty ){ + p->pDirty = pList; + pList = p; + } + } + return pList; +} + +/* +** Acquire a page. +** +** A read lock on the disk file is obtained when the first page is acquired. +** This read lock is dropped when the last page is released. +** +** A _get works for any page number greater than 0. If the database +** file is smaller than the requested page, then no actual disk +** read occurs and the memory image of the page is initialized to +** all zeros. The extra data appended to a page is always initialized +** to zeros the first time a page is loaded into memory. +** +** The acquisition might fail for several reasons. In all cases, +** an appropriate error code is returned and *ppPage is set to NULL. +** +** See also sqlite3pager_lookup(). Both this routine and _lookup() attempt +** to find a page in the in-memory cache first. If the page is not already +** in memory, this routine goes to disk to read it in whereas _lookup() +** just returns 0. This routine acquires a read-lock the first time it +** has to go to disk, and could also playback an old journal if necessary. +** Since _lookup() never goes to disk, it never has to deal with locks +** or journal files. +*/ +int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){ + PgHdr *pPg; + int rc; + + /* Make sure we have not hit any critical errors. + */ + assert( pPager!=0 ); + assert( pgno!=0 ); + *ppPage = 0; + if( pPager->errMask & ~(PAGER_ERR_FULL) ){ + return pager_errcode(pPager); + } + + /* If this is the first page accessed, then get a SHARED lock + ** on the database file. + */ + if( pPager->nRef==0 && !pPager->memDb ){ + rc = pager_wait_on_lock(pPager, SHARED_LOCK); + if( rc!=SQLITE_OK ){ + return rc; + } + + /* If a journal file exists, and there is no RESERVED lock on the + ** database file, then it either needs to be played back or deleted. + */ + if( pPager->useJournal && + sqlite3OsFileExists(pPager->zJournal) && + !sqlite3OsCheckReservedLock(&pPager->fd) + ){ + int rc; + + /* Get an EXCLUSIVE lock on the database file. At this point it is + ** important that a RESERVED lock is not obtained on the way to the + ** EXCLUSIVE lock. If it were, another process might open the + ** database file, detect the RESERVED lock, and conclude that the + ** database is safe to read while this process is still rolling it + ** back. + ** + ** Because the intermediate RESERVED lock is not requested, the + ** second process will get to this point in the code and fail to + ** obtain it's own EXCLUSIVE lock on the database file. + */ + rc = sqlite3OsLock(&pPager->fd, EXCLUSIVE_LOCK); + if( rc!=SQLITE_OK ){ + sqlite3OsUnlock(&pPager->fd, NO_LOCK); + pPager->state = PAGER_UNLOCK; + return rc; + } + pPager->state = PAGER_EXCLUSIVE; + + /* Open the journal for reading only. Return SQLITE_BUSY if + ** we are unable to open the journal file. + ** + ** The journal file does not need to be locked itself. The + ** journal file is never open unless the main database file holds + ** a write lock, so there is never any chance of two or more + ** processes opening the journal at the same time. + */ + rc = sqlite3OsOpenReadOnly(pPager->zJournal, &pPager->jfd); + if( rc!=SQLITE_OK ){ + sqlite3OsUnlock(&pPager->fd, NO_LOCK); + pPager->state = PAGER_UNLOCK; + return SQLITE_BUSY; + } + pPager->journalOpen = 1; + pPager->journalStarted = 0; + pPager->journalOff = 0; + pPager->setMaster = 0; + pPager->journalHdr = 0; + + /* Playback and delete the journal. Drop the database write + ** lock and reacquire the read lock. + */ + rc = pager_playback(pPager); + if( rc!=SQLITE_OK ){ + return rc; + } + } + pPg = 0; + }else{ + /* Search for page in cache */ + pPg = pager_lookup(pPager, pgno); + if( pPager->memDb && pPager->state==PAGER_UNLOCK ){ + pPager->state = PAGER_SHARED; + } + } + if( pPg==0 ){ + /* The requested page is not in the page cache. */ + int h; + pPager->nMiss++; + if( pPager->nPagemxPage || pPager->pFirst==0 || pPager->memDb ){ + /* Create a new page */ + pPg = sqliteMallocRaw( sizeof(*pPg) + pPager->pageSize + + sizeof(u32) + pPager->nExtra + + pPager->memDb*sizeof(PgHistory) ); + if( pPg==0 ){ + if( !pPager->memDb ){ + pager_unwritelock(pPager); + } + pPager->errMask |= PAGER_ERR_MEM; + return SQLITE_NOMEM; + } + memset(pPg, 0, sizeof(*pPg)); + if( pPager->memDb ){ + memset(PGHDR_TO_HIST(pPg, pPager), 0, sizeof(PgHistory)); + } + pPg->pPager = pPager; + pPg->pNextAll = pPager->pAll; + pPager->pAll = pPg; + pPager->nPage++; + }else{ + /* Find a page to recycle. Try to locate a page that does not + ** require us to do an fsync() on the journal. + */ + pPg = pPager->pFirstSynced; + + /* If we could not find a page that does not require an fsync() + ** on the journal file then fsync the journal file. This is a + ** very slow operation, so we work hard to avoid it. But sometimes + ** it can't be helped. + */ + if( pPg==0 ){ + int rc = syncJournal(pPager); + if( rc!=0 ){ + sqlite3pager_rollback(pPager); + return SQLITE_IOERR; + } + if( pPager->fullSync ){ + /* If in full-sync mode, write a new journal header into the + ** journal file. This is done to avoid ever modifying a journal + ** header that is involved in the rollback of pages that have + ** already been written to the database (in case the header is + ** trashed when the nRec field is updated). + */ + pPager->nRec = 0; + assert( pPager->journalOff > 0 ); + rc = writeJournalHdr(pPager); + if( rc!=0 ){ + sqlite3pager_rollback(pPager); + return SQLITE_IOERR; + } + } + pPg = pPager->pFirst; + } + assert( pPg->nRef==0 ); + + /* Write the page to the database file if it is dirty. + */ + if( pPg->dirty ){ + assert( pPg->needSync==0 ); + pPg->pDirty = 0; + rc = pager_write_pagelist( pPg ); + if( rc!=SQLITE_OK ){ + sqlite3pager_rollback(pPager); + return SQLITE_IOERR; + } + } + assert( pPg->dirty==0 ); + + /* If the page we are recycling is marked as alwaysRollback, then + ** set the global alwaysRollback flag, thus disabling the + ** sqlite_dont_rollback() optimization for the rest of this transaction. + ** It is necessary to do this because the page marked alwaysRollback + ** might be reloaded at a later time but at that point we won't remember + ** that is was marked alwaysRollback. This means that all pages must + ** be marked as alwaysRollback from here on out. + */ + if( pPg->alwaysRollback ){ + pPager->alwaysRollback = 1; + } + + /* Unlink the old page from the free list and the hash table + */ + unlinkPage(pPg); + pPager->nOvfl++; + } + pPg->pgno = pgno; + if( pPager->aInJournal && (int)pgno<=pPager->origDbSize ){ + sqlite3CheckMemory(pPager->aInJournal, pgno/8); + assert( pPager->journalOpen ); + pPg->inJournal = (pPager->aInJournal[pgno/8] & (1<<(pgno&7)))!=0; + pPg->needSync = 0; + }else{ + pPg->inJournal = 0; + pPg->needSync = 0; + } + if( pPager->aInStmt && (int)pgno<=pPager->stmtSize + && (pPager->aInStmt[pgno/8] & (1<<(pgno&7)))!=0 ){ + page_add_to_stmt_list(pPg); + }else{ + page_remove_from_stmt_list(pPg); + } + pPg->dirty = 0; + pPg->nRef = 1; + REFINFO(pPg); + pPager->nRef++; + h = pager_hash(pgno); + pPg->pNextHash = pPager->aHash[h]; + pPager->aHash[h] = pPg; + if( pPg->pNextHash ){ + assert( pPg->pNextHash->pPrevHash==0 ); + pPg->pNextHash->pPrevHash = pPg; + } + if( pPager->nExtra>0 ){ + memset(PGHDR_TO_EXTRA(pPg, pPager), 0, pPager->nExtra); + } + sqlite3pager_pagecount(pPager); + if( pPager->errMask!=0 ){ + sqlite3pager_unref(PGHDR_TO_DATA(pPg)); + rc = pager_errcode(pPager); + return rc; + } + if( pPager->dbSize<(int)pgno ){ + memset(PGHDR_TO_DATA(pPg), 0, pPager->pageSize); + }else{ + int rc; + assert( pPager->memDb==0 ); + sqlite3OsSeek(&pPager->fd, (pgno-1)*(i64)pPager->pageSize); + rc = sqlite3OsRead(&pPager->fd, PGHDR_TO_DATA(pPg), pPager->pageSize); + TRACE3("FETCH %d page %d\n", pPager->fd.h, pPg->pgno); + CODEC(pPager, PGHDR_TO_DATA(pPg), pPg->pgno, 3); + if( rc!=SQLITE_OK ){ + i64 fileSize; + if( sqlite3OsFileSize(&pPager->fd,&fileSize)!=SQLITE_OK + || fileSize>=pgno*pPager->pageSize ){ + sqlite3pager_unref(PGHDR_TO_DATA(pPg)); + return rc; + }else{ + memset(PGHDR_TO_DATA(pPg), 0, pPager->pageSize); + } + } + } + }else{ + /* The requested page is in the page cache. */ + pPager->nHit++; + page_ref(pPg); + } + *ppPage = PGHDR_TO_DATA(pPg); + return SQLITE_OK; +} + +/* +** Acquire a page if it is already in the in-memory cache. Do +** not read the page from disk. Return a pointer to the page, +** or 0 if the page is not in cache. +** +** See also sqlite3pager_get(). The difference between this routine +** and sqlite3pager_get() is that _get() will go to the disk and read +** in the page if the page is not already in cache. This routine +** returns NULL if the page is not in cache or if a disk I/O error +** has ever happened. +*/ +void *sqlite3pager_lookup(Pager *pPager, Pgno pgno){ + PgHdr *pPg; + + assert( pPager!=0 ); + assert( pgno!=0 ); + if( pPager->errMask & ~(PAGER_ERR_FULL) ){ + return 0; + } + pPg = pager_lookup(pPager, pgno); + if( pPg==0 ) return 0; + page_ref(pPg); + return PGHDR_TO_DATA(pPg); +} + +/* +** Release a page. +** +** If the number of references to the page drop to zero, then the +** page is added to the LRU list. When all references to all pages +** are released, a rollback occurs and the lock on the database is +** removed. +*/ +int sqlite3pager_unref(void *pData){ + PgHdr *pPg; + + /* Decrement the reference count for this page + */ + pPg = DATA_TO_PGHDR(pData); + assert( pPg->nRef>0 ); + pPg->nRef--; + REFINFO(pPg); + + /* When the number of references to a page reach 0, call the + ** destructor and add the page to the freelist. + */ + if( pPg->nRef==0 ){ + Pager *pPager; + pPager = pPg->pPager; + pPg->pNextFree = 0; + pPg->pPrevFree = pPager->pLast; + pPager->pLast = pPg; + if( pPg->pPrevFree ){ + pPg->pPrevFree->pNextFree = pPg; + }else{ + pPager->pFirst = pPg; + } + if( pPg->needSync==0 && pPager->pFirstSynced==0 ){ + pPager->pFirstSynced = pPg; + } + if( pPager->xDestructor ){ + pPager->xDestructor(pData, pPager->pageSize); + } + + /* When all pages reach the freelist, drop the read lock from + ** the database file. + */ + pPager->nRef--; + assert( pPager->nRef>=0 ); + if( pPager->nRef==0 && !pPager->memDb ){ + pager_reset(pPager); + } + } + return SQLITE_OK; +} + +/* +** Create a journal file for pPager. There should already be a RESERVED +** or EXCLUSIVE lock on the database file when this routine is called. +** +** Return SQLITE_OK if everything. Return an error code and release the +** write lock if anything goes wrong. +*/ +static int pager_open_journal(Pager *pPager){ + int rc; + assert( !pPager->memDb ); + assert( pPager->state>=PAGER_RESERVED ); + assert( pPager->journalOpen==0 ); + assert( pPager->useJournal ); + sqlite3pager_pagecount(pPager); + pPager->aInJournal = sqliteMalloc( pPager->dbSize/8 + 1 ); + if( pPager->aInJournal==0 ){ + rc = SQLITE_NOMEM; + goto failed_to_open_journal; + } + rc = sqlite3OsOpenExclusive(pPager->zJournal, &pPager->jfd,pPager->tempFile); + pPager->journalOff = 0; + pPager->setMaster = 0; + pPager->journalHdr = 0; + if( rc!=SQLITE_OK ){ + goto failed_to_open_journal; + } + sqlite3OsOpenDirectory(pPager->zDirectory, &pPager->jfd); + pPager->journalOpen = 1; + pPager->journalStarted = 0; + pPager->needSync = 0; + pPager->alwaysRollback = 0; + pPager->nRec = 0; + if( pPager->errMask!=0 ){ + rc = pager_errcode(pPager); + return rc; + } + pPager->origDbSize = pPager->dbSize; + + rc = writeJournalHdr(pPager); + + if( pPager->stmtAutoopen && rc==SQLITE_OK ){ + rc = sqlite3pager_stmt_begin(pPager); + } + if( rc!=SQLITE_OK ){ + rc = pager_unwritelock(pPager); + if( rc==SQLITE_OK ){ + rc = SQLITE_FULL; + } + } + return rc; + +failed_to_open_journal: + sqliteFree(pPager->aInJournal); + pPager->aInJournal = 0; + sqlite3OsUnlock(&pPager->fd, NO_LOCK); + pPager->state = PAGER_UNLOCK; + return rc; +} + +/* +** Acquire a write-lock on the database. The lock is removed when +** the any of the following happen: +** +** * sqlite3pager_commit() is called. +** * sqlite3pager_rollback() is called. +** * sqlite3pager_close() is called. +** * sqlite3pager_unref() is called to on every outstanding page. +** +** The first parameter to this routine is a pointer to any open page of the +** database file. Nothing changes about the page - it is used merely to +** acquire a pointer to the Pager structure and as proof that there is +** already a read-lock on the database. +** +** The second parameter indicates how much space in bytes to reserve for a +** master journal file-name at the start of the journal when it is created. +** +** A journal file is opened if this is not a temporary file. For temporary +** files, the opening of the journal file is deferred until there is an +** actual need to write to the journal. +** +** If the database is already reserved for writing, this routine is a no-op. +** +** If exFlag is true, go ahead and get an EXCLUSIVE lock on the file +** immediately instead of waiting until we try to flush the cache. The +** exFlag is ignored if a transaction is already active. +*/ +int sqlite3pager_begin(void *pData, int exFlag){ + PgHdr *pPg = DATA_TO_PGHDR(pData); + Pager *pPager = pPg->pPager; + int rc = SQLITE_OK; + assert( pPg->nRef>0 ); + assert( pPager->state!=PAGER_UNLOCK ); + if( pPager->state==PAGER_SHARED ){ + assert( pPager->aInJournal==0 ); + if( pPager->memDb ){ + pPager->state = PAGER_EXCLUSIVE; + pPager->origDbSize = pPager->dbSize; + }else{ + if( SQLITE_BUSY_RESERVED_LOCK || exFlag ){ + rc = pager_wait_on_lock(pPager, RESERVED_LOCK); + }else{ + rc = sqlite3OsLock(&pPager->fd, RESERVED_LOCK); + } + if( rc==SQLITE_OK ){ + pPager->state = PAGER_RESERVED; + if( exFlag ){ + rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK); + } + } + if( rc!=SQLITE_OK ){ + return rc; + } + pPager->dirtyCache = 0; + TRACE2("TRANSACTION %d\n", pPager->fd.h); + if( pPager->useJournal && !pPager->tempFile ){ + rc = pager_open_journal(pPager); + } + } + } + return rc; +} + +/* +** Mark a data page as writeable. The page is written into the journal +** if it is not there already. This routine must be called before making +** changes to a page. +** +** The first time this routine is called, the pager creates a new +** journal and acquires a RESERVED lock on the database. If the RESERVED +** lock could not be acquired, this routine returns SQLITE_BUSY. The +** calling routine must check for that return value and be careful not to +** change any page data until this routine returns SQLITE_OK. +** +** If the journal file could not be written because the disk is full, +** then this routine returns SQLITE_FULL and does an immediate rollback. +** All subsequent write attempts also return SQLITE_FULL until there +** is a call to sqlite3pager_commit() or sqlite3pager_rollback() to +** reset. +*/ +int sqlite3pager_write(void *pData){ + PgHdr *pPg = DATA_TO_PGHDR(pData); + Pager *pPager = pPg->pPager; + int rc = SQLITE_OK; + + /* Check for errors + */ + if( pPager->errMask ){ + return pager_errcode(pPager); + } + if( pPager->readOnly ){ + return SQLITE_PERM; + } + + assert( !pPager->setMaster ); + + /* Mark the page as dirty. If the page has already been written + ** to the journal then we can return right away. + */ + pPg->dirty = 1; + if( pPg->inJournal && (pPg->inStmt || pPager->stmtInUse==0) ){ + pPager->dirtyCache = 1; + return SQLITE_OK; + } + + /* If we get this far, it means that the page needs to be + ** written to the transaction journal or the ckeckpoint journal + ** or both. + ** + ** First check to see that the transaction journal exists and + ** create it if it does not. + */ + assert( pPager->state!=PAGER_UNLOCK ); + rc = sqlite3pager_begin(pData, 0); + if( rc!=SQLITE_OK ){ + return rc; + } + assert( pPager->state>=PAGER_RESERVED ); + if( !pPager->journalOpen && pPager->useJournal ){ + rc = pager_open_journal(pPager); + if( rc!=SQLITE_OK ) return rc; + } + assert( pPager->journalOpen || !pPager->useJournal ); + pPager->dirtyCache = 1; + + /* The transaction journal now exists and we have a RESERVED or an + ** EXCLUSIVE lock on the main database file. Write the current page to + ** the transaction journal if it is not there already. + */ + if( !pPg->inJournal && (pPager->useJournal || pPager->memDb) ){ + if( (int)pPg->pgno <= pPager->origDbSize ){ + int szPg; + u32 saved; + if( pPager->memDb ){ + PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager); + TRACE3("JOURNAL %d page %d\n", pPager->fd.h, pPg->pgno); + assert( pHist->pOrig==0 ); + pHist->pOrig = sqliteMallocRaw( pPager->pageSize ); + if( pHist->pOrig ){ + memcpy(pHist->pOrig, PGHDR_TO_DATA(pPg), pPager->pageSize); + } + }else{ + u32 cksum; + CODEC(pPager, pData, pPg->pgno, 7); + cksum = pager_cksum(pPager, pPg->pgno, pData); + saved = *(u32*)PGHDR_TO_EXTRA(pPg, pPager); + store32bits(cksum, pPg, pPager->pageSize); + szPg = pPager->pageSize+8; + store32bits(pPg->pgno, pPg, -4); + rc = sqlite3OsWrite(&pPager->jfd, &((char*)pData)[-4], szPg); + pPager->journalOff += szPg; + TRACE4("JOURNAL %d page %d needSync=%d\n", + pPager->fd.h, pPg->pgno, pPg->needSync); + CODEC(pPager, pData, pPg->pgno, 0); + *(u32*)PGHDR_TO_EXTRA(pPg, pPager) = saved; + if( rc!=SQLITE_OK ){ + sqlite3pager_rollback(pPager); + pPager->errMask |= PAGER_ERR_FULL; + return rc; + } + pPager->nRec++; + assert( pPager->aInJournal!=0 ); + pPager->aInJournal[pPg->pgno/8] |= 1<<(pPg->pgno&7); + pPg->needSync = !pPager->noSync; + if( pPager->stmtInUse ){ + pPager->aInStmt[pPg->pgno/8] |= 1<<(pPg->pgno&7); + page_add_to_stmt_list(pPg); + } + } + }else{ + pPg->needSync = !pPager->journalStarted && !pPager->noSync; + TRACE4("APPEND %d page %d needSync=%d\n", + pPager->fd.h, pPg->pgno, pPg->needSync); + } + if( pPg->needSync ){ + pPager->needSync = 1; + } + pPg->inJournal = 1; + } + + /* If the statement journal is open and the page is not in it, + ** then write the current page to the statement journal. Note that + ** the statement journal format differs from the standard journal format + ** in that it omits the checksums and the header. + */ + if( pPager->stmtInUse && !pPg->inStmt && (int)pPg->pgno<=pPager->stmtSize ){ + assert( pPg->inJournal || (int)pPg->pgno>pPager->origDbSize ); + if( pPager->memDb ){ + PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager); + assert( pHist->pStmt==0 ); + pHist->pStmt = sqliteMallocRaw( pPager->pageSize ); + if( pHist->pStmt ){ + memcpy(pHist->pStmt, PGHDR_TO_DATA(pPg), pPager->pageSize); + } + TRACE3("STMT-JOURNAL %d page %d\n", pPager->fd.h, pPg->pgno); + }else{ + store32bits(pPg->pgno, pPg, -4); + CODEC(pPager, pData, pPg->pgno, 7); + rc = sqlite3OsWrite(&pPager->stfd, ((char*)pData)-4, pPager->pageSize+4); + TRACE3("STMT-JOURNAL %d page %d\n", pPager->fd.h, pPg->pgno); + CODEC(pPager, pData, pPg->pgno, 0); + if( rc!=SQLITE_OK ){ + sqlite3pager_rollback(pPager); + pPager->errMask |= PAGER_ERR_FULL; + return rc; + } + pPager->stmtNRec++; + assert( pPager->aInStmt!=0 ); + pPager->aInStmt[pPg->pgno/8] |= 1<<(pPg->pgno&7); + } + page_add_to_stmt_list(pPg); + } + + /* Update the database size and return. + */ + if( pPager->dbSize<(int)pPg->pgno ){ + pPager->dbSize = pPg->pgno; + if( !pPager->memDb && pPager->dbSize==PENDING_BYTE/pPager->pageSize ){ + pPager->dbSize++; + } + } + return rc; +} + +/* +** Return TRUE if the page given in the argument was previously passed +** to sqlite3pager_write(). In other words, return TRUE if it is ok +** to change the content of the page. +*/ +int sqlite3pager_iswriteable(void *pData){ + PgHdr *pPg = DATA_TO_PGHDR(pData); + return pPg->dirty; +} + +/* +** Replace the content of a single page with the information in the third +** argument. +*/ +int sqlite3pager_overwrite(Pager *pPager, Pgno pgno, void *pData){ + void *pPage; + int rc; + + rc = sqlite3pager_get(pPager, pgno, &pPage); + if( rc==SQLITE_OK ){ + rc = sqlite3pager_write(pPage); + if( rc==SQLITE_OK ){ + memcpy(pPage, pData, pPager->pageSize); + } + sqlite3pager_unref(pPage); + } + return rc; +} + +/* +** A call to this routine tells the pager that it is not necessary to +** write the information on page "pgno" back to the disk, even though +** that page might be marked as dirty. +** +** The overlying software layer calls this routine when all of the data +** on the given page is unused. The pager marks the page as clean so +** that it does not get written to disk. +** +** Tests show that this optimization, together with the +** sqlite3pager_dont_rollback() below, more than double the speed +** of large INSERT operations and quadruple the speed of large DELETEs. +** +** When this routine is called, set the alwaysRollback flag to true. +** Subsequent calls to sqlite3pager_dont_rollback() for the same page +** will thereafter be ignored. This is necessary to avoid a problem +** where a page with data is added to the freelist during one part of +** a transaction then removed from the freelist during a later part +** of the same transaction and reused for some other purpose. When it +** is first added to the freelist, this routine is called. When reused, +** the dont_rollback() routine is called. But because the page contains +** critical data, we still need to be sure it gets rolled back in spite +** of the dont_rollback() call. +*/ +void sqlite3pager_dont_write(Pager *pPager, Pgno pgno){ + PgHdr *pPg; + + if( pPager->memDb ) return; + + pPg = pager_lookup(pPager, pgno); + pPg->alwaysRollback = 1; + if( pPg && pPg->dirty ){ + if( pPager->dbSize==(int)pPg->pgno && pPager->origDbSizedbSize ){ + /* If this pages is the last page in the file and the file has grown + ** during the current transaction, then do NOT mark the page as clean. + ** When the database file grows, we must make sure that the last page + ** gets written at least once so that the disk file will be the correct + ** size. If you do not write this page and the size of the file + ** on the disk ends up being too small, that can lead to database + ** corruption during the next transaction. + */ + }else{ + TRACE3("DONT_WRITE page %d of %d\n", pgno, pPager->fd.h); + pPg->dirty = 0; + } + } +} + +/* +** A call to this routine tells the pager that if a rollback occurs, +** it is not necessary to restore the data on the given page. This +** means that the pager does not have to record the given page in the +** rollback journal. +*/ +void sqlite3pager_dont_rollback(void *pData){ + PgHdr *pPg = DATA_TO_PGHDR(pData); + Pager *pPager = pPg->pPager; + + if( pPager->state!=PAGER_EXCLUSIVE || pPager->journalOpen==0 ) return; + if( pPg->alwaysRollback || pPager->alwaysRollback || pPager->memDb ) return; + if( !pPg->inJournal && (int)pPg->pgno <= pPager->origDbSize ){ + assert( pPager->aInJournal!=0 ); + pPager->aInJournal[pPg->pgno/8] |= 1<<(pPg->pgno&7); + pPg->inJournal = 1; + if( pPager->stmtInUse ){ + pPager->aInStmt[pPg->pgno/8] |= 1<<(pPg->pgno&7); + page_add_to_stmt_list(pPg); + } + TRACE3("DONT_ROLLBACK page %d of %d\n", pPg->pgno, pPager->fd.h); + } + if( pPager->stmtInUse && !pPg->inStmt && (int)pPg->pgno<=pPager->stmtSize ){ + assert( pPg->inJournal || (int)pPg->pgno>pPager->origDbSize ); + assert( pPager->aInStmt!=0 ); + pPager->aInStmt[pPg->pgno/8] |= 1<<(pPg->pgno&7); + page_add_to_stmt_list(pPg); + } +} + + +/* +** Clear a PgHistory block +*/ +static void clearHistory(PgHistory *pHist){ + sqliteFree(pHist->pOrig); + sqliteFree(pHist->pStmt); + pHist->pOrig = 0; + pHist->pStmt = 0; +} + +/* +** Commit all changes to the database and release the write lock. +** +** If the commit fails for any reason, a rollback attempt is made +** and an error code is returned. If the commit worked, SQLITE_OK +** is returned. +*/ +int sqlite3pager_commit(Pager *pPager){ + int rc; + PgHdr *pPg; + + if( pPager->errMask==PAGER_ERR_FULL ){ + rc = sqlite3pager_rollback(pPager); + if( rc==SQLITE_OK ){ + rc = SQLITE_FULL; + } + return rc; + } + if( pPager->errMask!=0 ){ + rc = pager_errcode(pPager); + return rc; + } + if( pPager->statefd.h); + if( pPager->memDb ){ + pPg = pager_get_all_dirty_pages(pPager); + while( pPg ){ + clearHistory(PGHDR_TO_HIST(pPg, pPager)); + pPg->dirty = 0; + pPg->inJournal = 0; + pPg->inStmt = 0; + pPg->pPrevStmt = pPg->pNextStmt = 0; + pPg = pPg->pDirty; + } +#ifndef NDEBUG + for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ + PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager); + assert( !pPg->alwaysRollback ); + assert( !pHist->pOrig ); + assert( !pHist->pStmt ); + } +#endif + pPager->pStmt = 0; + pPager->state = PAGER_SHARED; + return SQLITE_OK; + } + if( pPager->dirtyCache==0 ){ + /* Exit early (without doing the time-consuming sqlite3OsSync() calls) + ** if there have been no changes to the database file. */ + assert( pPager->needSync==0 ); + rc = pager_unwritelock(pPager); + pPager->dbSize = -1; + return rc; + } + assert( pPager->journalOpen ); + rc = sqlite3pager_sync(pPager, 0); + if( rc!=SQLITE_OK ){ + goto commit_abort; + } + rc = pager_unwritelock(pPager); + pPager->dbSize = -1; + return rc; + + /* Jump here if anything goes wrong during the commit process. + */ +commit_abort: + sqlite3pager_rollback(pPager); + return rc; +} + +/* +** Rollback all changes. The database falls back to PAGER_SHARED mode. +** All in-memory cache pages revert to their original data contents. +** The journal is deleted. +** +** This routine cannot fail unless some other process is not following +** the correct locking protocol (SQLITE_PROTOCOL) or unless some other +** process is writing trash into the journal file (SQLITE_CORRUPT) or +** unless a prior malloc() failed (SQLITE_NOMEM). Appropriate error +** codes are returned for all these occasions. Otherwise, +** SQLITE_OK is returned. +*/ +int sqlite3pager_rollback(Pager *pPager){ + int rc; + TRACE2("ROLLBACK %d\n", pPager->fd.h); + if( pPager->memDb ){ + PgHdr *p; + for(p=pPager->pAll; p; p=p->pNextAll){ + PgHistory *pHist; + assert( !p->alwaysRollback ); + if( !p->dirty ){ + assert( !((PgHistory *)PGHDR_TO_HIST(p, pPager))->pOrig ); + assert( !((PgHistory *)PGHDR_TO_HIST(p, pPager))->pStmt ); + continue; + } + + pHist = PGHDR_TO_HIST(p, pPager); + if( pHist->pOrig ){ + memcpy(PGHDR_TO_DATA(p), pHist->pOrig, pPager->pageSize); + TRACE3("ROLLBACK-PAGE %d of %d\n", p->pgno, pPager->fd.h); + }else{ + TRACE3("PAGE %d is clean on %d\n", p->pgno, pPager->fd.h); + } + clearHistory(pHist); + p->dirty = 0; + p->inJournal = 0; + p->inStmt = 0; + p->pPrevStmt = p->pNextStmt = 0; + + if( pPager->xReiniter ){ + pPager->xReiniter(PGHDR_TO_DATA(p), pPager->pageSize); + } + + } + pPager->pStmt = 0; + pPager->dbSize = pPager->origDbSize; + memoryTruncate(pPager); + pPager->stmtInUse = 0; + pPager->state = PAGER_SHARED; + return SQLITE_OK; + } + + if( !pPager->dirtyCache || !pPager->journalOpen ){ + rc = pager_unwritelock(pPager); + pPager->dbSize = -1; + return rc; + } + + if( pPager->errMask!=0 && pPager->errMask!=PAGER_ERR_FULL ){ + if( pPager->state>=PAGER_EXCLUSIVE ){ + pager_playback(pPager); + } + return pager_errcode(pPager); + } + if( pPager->state==PAGER_RESERVED ){ + int rc2, rc3; + rc = pager_reload_cache(pPager); + rc2 = pager_truncate(pPager, pPager->origDbSize); + rc3 = pager_unwritelock(pPager); + if( rc==SQLITE_OK ){ + rc = rc2; + if( rc3 ) rc = rc3; + } + }else{ + rc = pager_playback(pPager); + } + if( rc!=SQLITE_OK ){ + rc = SQLITE_CORRUPT; /* bkpt-CORRUPT */ + pPager->errMask |= PAGER_ERR_CORRUPT; + } + pPager->dbSize = -1; + return rc; +} + +/* +** Return TRUE if the database file is opened read-only. Return FALSE +** if the database is (in theory) writable. +*/ +int sqlite3pager_isreadonly(Pager *pPager){ + return pPager->readOnly; +} + +/* +** This routine is used for testing and analysis only. +*/ +int *sqlite3pager_stats(Pager *pPager){ + static int a[9]; + a[0] = pPager->nRef; + a[1] = pPager->nPage; + a[2] = pPager->mxPage; + a[3] = pPager->dbSize; + a[4] = pPager->state; + a[5] = pPager->errMask; + a[6] = pPager->nHit; + a[7] = pPager->nMiss; + a[8] = pPager->nOvfl; + return a; +} + +/* +** Set the statement rollback point. +** +** This routine should be called with the transaction journal already +** open. A new statement journal is created that can be used to rollback +** changes of a single SQL command within a larger transaction. +*/ +int sqlite3pager_stmt_begin(Pager *pPager){ + int rc; + char zTemp[SQLITE_TEMPNAME_SIZE]; + assert( !pPager->stmtInUse ); + assert( pPager->dbSize>=0 ); + TRACE2("STMT-BEGIN %d\n", pPager->fd.h); + if( pPager->memDb ){ + pPager->stmtInUse = 1; + pPager->stmtSize = pPager->dbSize; + return SQLITE_OK; + } + if( !pPager->journalOpen ){ + pPager->stmtAutoopen = 1; + return SQLITE_OK; + } + assert( pPager->journalOpen ); + pPager->aInStmt = sqliteMalloc( pPager->dbSize/8 + 1 ); + if( pPager->aInStmt==0 ){ + sqlite3OsLock(&pPager->fd, SHARED_LOCK); + return SQLITE_NOMEM; + } +#ifndef NDEBUG + rc = sqlite3OsFileSize(&pPager->jfd, &pPager->stmtJSize); + if( rc ) goto stmt_begin_failed; + assert( pPager->stmtJSize == pPager->journalOff ); +#endif + pPager->stmtJSize = pPager->journalOff; + pPager->stmtSize = pPager->dbSize; + pPager->stmtHdrOff = 0; + pPager->stmtCksum = pPager->cksumInit; + if( !pPager->stmtOpen ){ + rc = sqlite3pager_opentemp(zTemp, &pPager->stfd); + if( rc ) goto stmt_begin_failed; + pPager->stmtOpen = 1; + pPager->stmtNRec = 0; + } + pPager->stmtInUse = 1; + return SQLITE_OK; + +stmt_begin_failed: + if( pPager->aInStmt ){ + sqliteFree(pPager->aInStmt); + pPager->aInStmt = 0; + } + return rc; +} + +/* +** Commit a statement. +*/ +int sqlite3pager_stmt_commit(Pager *pPager){ + if( pPager->stmtInUse ){ + PgHdr *pPg, *pNext; + TRACE2("STMT-COMMIT %d\n", pPager->fd.h); + if( !pPager->memDb ){ + sqlite3OsSeek(&pPager->stfd, 0); + /* sqlite3OsTruncate(&pPager->stfd, 0); */ + sqliteFree( pPager->aInStmt ); + pPager->aInStmt = 0; + } + for(pPg=pPager->pStmt; pPg; pPg=pNext){ + pNext = pPg->pNextStmt; + assert( pPg->inStmt ); + pPg->inStmt = 0; + pPg->pPrevStmt = pPg->pNextStmt = 0; + if( pPager->memDb ){ + PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager); + sqliteFree(pHist->pStmt); + pHist->pStmt = 0; + } + } + pPager->stmtNRec = 0; + pPager->stmtInUse = 0; + pPager->pStmt = 0; + } + pPager->stmtAutoopen = 0; + return SQLITE_OK; +} + +/* +** Rollback a statement. +*/ +int sqlite3pager_stmt_rollback(Pager *pPager){ + int rc; + if( pPager->stmtInUse ){ + TRACE2("STMT-ROLLBACK %d\n", pPager->fd.h); + if( pPager->memDb ){ + PgHdr *pPg; + for(pPg=pPager->pStmt; pPg; pPg=pPg->pNextStmt){ + PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager); + if( pHist->pStmt ){ + memcpy(PGHDR_TO_DATA(pPg), pHist->pStmt, pPager->pageSize); + sqliteFree(pHist->pStmt); + pHist->pStmt = 0; + } + } + pPager->dbSize = pPager->stmtSize; + memoryTruncate(pPager); + rc = SQLITE_OK; + }else{ + rc = pager_stmt_playback(pPager); + } + sqlite3pager_stmt_commit(pPager); + }else{ + rc = SQLITE_OK; + } + pPager->stmtAutoopen = 0; + return rc; +} + +/* +** Return the full pathname of the database file. +*/ +const char *sqlite3pager_filename(Pager *pPager){ + return pPager->zFilename; +} + +/* +** Return the directory of the database file. +*/ +const char *sqlite3pager_dirname(Pager *pPager){ + return pPager->zDirectory; +} + +/* +** Return the full pathname of the journal file. +*/ +const char *sqlite3pager_journalname(Pager *pPager){ + return pPager->zJournal; +} + +/* +** Set the codec for this pager +*/ +void sqlite3pager_set_codec( + Pager *pPager, + void (*xCodec)(void*,void*,Pgno,int), + void *pCodecArg +){ + pPager->xCodec = xCodec; + pPager->pCodecArg = pCodecArg; +} + +/* +** This routine is called to increment the database file change-counter, +** stored at byte 24 of the pager file. +*/ +static int pager_incr_changecounter(Pager *pPager){ + void *pPage; + PgHdr *pPgHdr; + u32 change_counter; + int rc; + + /* Open page 1 of the file for writing. */ + rc = sqlite3pager_get(pPager, 1, &pPage); + if( rc!=SQLITE_OK ) return rc; + rc = sqlite3pager_write(pPage); + if( rc!=SQLITE_OK ) return rc; + + /* Read the current value at byte 24. */ + pPgHdr = DATA_TO_PGHDR(pPage); + change_counter = retrieve32bits(pPgHdr, 24); + + /* Increment the value just read and write it back to byte 24. */ + change_counter++; + store32bits(change_counter, pPgHdr, 24); + + /* Release the page reference. */ + sqlite3pager_unref(pPage); + return SQLITE_OK; +} + +/* +** Sync the database file for the pager pPager. zMaster points to the name +** of a master journal file that should be written into the individual +** journal file. zMaster may be NULL, which is interpreted as no master +** journal (a single database transaction). +** +** This routine ensures that the journal is synced, all dirty pages written +** to the database file and the database file synced. The only thing that +** remains to commit the transaction is to delete the journal file (or +** master journal file if specified). +** +** Note that if zMaster==NULL, this does not overwrite a previous value +** passed to an sqlite3pager_sync() call. +*/ +int sqlite3pager_sync(Pager *pPager, const char *zMaster){ + int rc = SQLITE_OK; + + /* If this is an in-memory db, or no pages have been written to, or this + ** function has already been called, it is a no-op. + */ + if( pPager->state!=PAGER_SYNCED && !pPager->memDb && pPager->dirtyCache ){ + PgHdr *pPg; + assert( pPager->journalOpen ); + + /* If a master journal file name has already been written to the + ** journal file, then no sync is required. This happens when it is + ** written, then the process fails to upgrade from a RESERVED to an + ** EXCLUSIVE lock. The next time the process tries to commit the + ** transaction the m-j name will have already been written. + */ + if( !pPager->setMaster ){ + rc = pager_incr_changecounter(pPager); + if( rc!=SQLITE_OK ) goto sync_exit; + rc = writeMasterJournal(pPager, zMaster); + if( rc!=SQLITE_OK ) goto sync_exit; + rc = syncJournal(pPager); + if( rc!=SQLITE_OK ) goto sync_exit; + } + + /* Write all dirty pages to the database file */ + pPg = pager_get_all_dirty_pages(pPager); + rc = pager_write_pagelist(pPg); + if( rc!=SQLITE_OK ) goto sync_exit; + + /* Sync the database file. */ + if( !pPager->noSync ){ + rc = sqlite3OsSync(&pPager->fd); + } + + pPager->state = PAGER_SYNCED; + } + +sync_exit: + return rc; +} + +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) +/* +** Return the current state of the file lock for the given pager. +** The return value is one of NO_LOCK, SHARED_LOCK, RESERVED_LOCK, +** PENDING_LOCK, or EXCLUSIVE_LOCK. +*/ +int sqlite3pager_lockstate(Pager *pPager){ +#ifdef OS_TEST + return pPager->fd->fd.locktype; +#else + return pPager->fd.locktype; +#endif +} +#endif + +#ifdef SQLITE_TEST +/* +** Print a listing of all referenced pages and their ref count. +*/ +void sqlite3pager_refdump(Pager *pPager){ + PgHdr *pPg; + for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ + if( pPg->nRef<=0 ) continue; + sqlite3DebugPrintf("PAGE %3d addr=%p nRef=%d\n", + pPg->pgno, PGHDR_TO_DATA(pPg), pPg->nRef); + } +} +#endif diff --git a/kopete/plugins/statistics/sqlite/pager.h b/kopete/plugins/statistics/sqlite/pager.h new file mode 100644 index 00000000..0231e27a --- /dev/null +++ b/kopete/plugins/statistics/sqlite/pager.h @@ -0,0 +1,102 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the sqlite page cache +** subsystem. The page cache subsystem reads and writes a file a page +** at a time and provides a journal for rollback. +** +** @(#) $Id$ +*/ + +/* +** The default size of a database page. +*/ +#ifndef SQLITE_DEFAULT_PAGE_SIZE +# define SQLITE_DEFAULT_PAGE_SIZE 1024 +#endif + +/* Maximum page size. The upper bound on this value is 65536 (a limit +** imposed by the 2-byte size of cell array pointers.) The +** maximum page size determines the amount of stack space allocated +** by many of the routines in pager.c and btree.c On embedded architectures +** or any machine where memory and especially stack memory is limited, +** one may wish to chose a smaller value for the maximum page size. +*/ +#ifndef SQLITE_MAX_PAGE_SIZE +# define SQLITE_MAX_PAGE_SIZE 8192 +#endif + +/* +** Maximum number of pages in one database. +*/ +#define SQLITE_MAX_PAGE 1073741823 + +/* +** The type used to represent a page number. The first page in a file +** is called page 1. 0 is used to represent "not a page". +*/ +typedef unsigned int Pgno; + +/* +** Each open file is managed by a separate instance of the "Pager" structure. +*/ +typedef struct Pager Pager; + + +/* +** See source code comments for a detailed description of the following +** routines: +*/ +int sqlite3pager_open(Pager **ppPager, const char *zFilename, + int nExtra, int useJournal); +void sqlite3pager_set_busyhandler(Pager*, BusyHandler *pBusyHandler); +void sqlite3pager_set_destructor(Pager*, void(*)(void*,int)); +void sqlite3pager_set_reiniter(Pager*, void(*)(void*,int)); +void sqlite3pager_set_pagesize(Pager*, int); +void sqlite3pager_read_fileheader(Pager*, int, unsigned char*); +void sqlite3pager_set_cachesize(Pager*, int); +int sqlite3pager_close(Pager *pPager); +int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage); +void *sqlite3pager_lookup(Pager *pPager, Pgno pgno); +int sqlite3pager_ref(void*); +int sqlite3pager_unref(void*); +Pgno sqlite3pager_pagenumber(void*); +int sqlite3pager_write(void*); +int sqlite3pager_iswriteable(void*); +int sqlite3pager_overwrite(Pager *pPager, Pgno pgno, void*); +int sqlite3pager_pagecount(Pager*); +int sqlite3pager_truncate(Pager*,Pgno); +int sqlite3pager_begin(void*, int exFlag); +int sqlite3pager_commit(Pager*); +int sqlite3pager_sync(Pager*,const char *zMaster); +int sqlite3pager_rollback(Pager*); +int sqlite3pager_isreadonly(Pager*); +int sqlite3pager_stmt_begin(Pager*); +int sqlite3pager_stmt_commit(Pager*); +int sqlite3pager_stmt_rollback(Pager*); +void sqlite3pager_dont_rollback(void*); +void sqlite3pager_dont_write(Pager*, Pgno); +int *sqlite3pager_stats(Pager*); +void sqlite3pager_set_safety_level(Pager*,int); +const char *sqlite3pager_filename(Pager*); +const char *sqlite3pager_dirname(Pager*); +const char *sqlite3pager_journalname(Pager*); +int sqlite3pager_rename(Pager*, const char *zNewName); +void sqlite3pager_set_codec(Pager*,void(*)(void*,void*,Pgno,int),void*); + +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) +int sqlite3pager_lockstate(Pager*); +#endif + +#ifdef SQLITE_TEST +void sqlite3pager_refdump(Pager*); +int pager3_refinfo_enable; +#endif diff --git a/kopete/plugins/statistics/sqlite/parse.c b/kopete/plugins/statistics/sqlite/parse.c new file mode 100644 index 00000000..d3e68e02 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/parse.c @@ -0,0 +1,3143 @@ +/* Driver template for the LEMON parser generator. +** The author disclaims copyright to this source code. +*/ +/* First off, code is include which follows the "include" declaration +** in the input file. */ +#include +#line 33 "parse.y" + +#include "sqliteInt.h" +#include "parse.h" + +/* +** An instance of this structure holds information about the +** LIMIT clause of a SELECT statement. +*/ +struct LimitVal { + int limit; /* The LIMIT value. -1 if there is no limit */ + int offset; /* The OFFSET. 0 if there is none */ +}; + +/* +** An instance of this structure is used to store the LIKE, +** GLOB, NOT LIKE, and NOT GLOB operators. +*/ +struct LikeOp { + int opcode; /* Either TK_GLOB or TK_LIKE */ + int not; /* True if the NOT keyword is present */ +}; + +/* +** An instance of the following structure describes the event of a +** TRIGGER. "a" is the event type, one of TK_UPDATE, TK_INSERT, +** TK_DELETE, or TK_INSTEAD. If the event is of the form +** +** UPDATE ON (a,b,c) +** +** Then the "b" IdList records the list "a,b,c". +*/ +struct TrigEvent { int a; IdList * b; }; + +/* +** An instance of this structure holds the ATTACH key and the key type. +*/ +struct AttachKey { int type; Token key; }; + +#line 48 "parse.c" +/* Next is all token values, in a form suitable for use by makeheaders. +** This section will be null unless lemon is run with the -m switch. +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ +/* Make sure the INTERFACE macro is defined. +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** YYCODETYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 terminals +** and nonterminals. "int" is used otherwise. +** YYNOCODE is a number of type YYCODETYPE which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** YYACTIONTYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 rules and +** states combined. "int" is used otherwise. +** sqlite3ParserTOKENTYPE is the data type used for minor tokens given +** directly to the parser from the tokenizer. +** YYMINORTYPE is the data type used for all minor tokens. +** This is typically a union of many types, one of +** which is sqlite3ParserTOKENTYPE. The entry in the union +** for base tokens is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. +** sqlite3ParserARG_SDECL A static variable declaration for the %extra_argument +** sqlite3ParserARG_PDECL A parameter declaration for the %extra_argument +** sqlite3ParserARG_STORE Code to store %extra_argument into yypParser +** sqlite3ParserARG_FETCH Code to extract %extra_argument from yypParser +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ +#define YYCODETYPE unsigned char +#define YYNOCODE 225 +#define YYACTIONTYPE unsigned short int +#define sqlite3ParserTOKENTYPE Token +typedef union { + sqlite3ParserTOKENTYPE yy0; + struct {int value; int mask;} yy47; + TriggerStep* yy91; + Token yy98; + Select* yy107; + struct TrigEvent yy146; + ExprList* yy210; + Expr* yy258; + SrcList* yy259; + IdList* yy272; + int yy284; + struct AttachKey yy292; + struct LikeOp yy342; + struct LimitVal yy404; + int yy449; +} YYMINORTYPE; +#define YYSTACKDEPTH 100 +#define sqlite3ParserARG_SDECL Parse *pParse; +#define sqlite3ParserARG_PDECL ,Parse *pParse +#define sqlite3ParserARG_FETCH Parse *pParse = yypParser->pParse +#define sqlite3ParserARG_STORE yypParser->pParse = pParse +#define YYNSTATE 537 +#define YYNRULE 292 +#define YYERRORSYMBOL 130 +#define YYERRSYMDT yy449 +#define YYFALLBACK 1 +#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) +#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) +#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) + +/* Next are that tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < YYNSTATE Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. +** +** N == YYNSTATE+YYNRULE A syntax error has occurred. +** +** N == YYNSTATE+YYNRULE+1 The parser accepts its input. +** +** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as +** +** yy_action[ yy_shift_ofst[S] + X ] +** +** If the index value yy_shift_ofst[S]+X is out of range or if the value +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] +** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table +** and that yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of +** YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +*/ +static const YYACTIONTYPE yy_action[] = { + /* 0 */ 257, 325, 255, 138, 140, 142, 144, 146, 148, 150, + /* 10 */ 152, 154, 156, 89, 87, 88, 159, 12, 4, 6, + /* 20 */ 158, 537, 38, 24, 830, 1, 536, 3, 329, 488, + /* 30 */ 534, 535, 319, 50, 124, 112, 160, 169, 174, 179, + /* 40 */ 168, 173, 134, 136, 128, 130, 126, 132, 138, 140, + /* 50 */ 142, 144, 146, 148, 150, 152, 154, 156, 26, 73, + /* 60 */ 384, 256, 39, 58, 64, 66, 299, 330, 612, 611, + /* 70 */ 351, 30, 92, 332, 326, 159, 13, 14, 353, 158, + /* 80 */ 5, 355, 361, 366, 499, 146, 148, 150, 152, 154, + /* 90 */ 156, 12, 369, 124, 112, 160, 169, 174, 179, 168, + /* 100 */ 173, 134, 136, 128, 130, 126, 132, 138, 140, 142, + /* 110 */ 144, 146, 148, 150, 152, 154, 156, 128, 130, 126, + /* 120 */ 132, 138, 140, 142, 144, 146, 148, 150, 152, 154, + /* 130 */ 156, 659, 353, 244, 62, 355, 361, 366, 79, 12, + /* 140 */ 63, 98, 96, 289, 159, 280, 369, 349, 158, 181, + /* 150 */ 13, 14, 27, 12, 546, 383, 32, 10, 368, 273, + /* 160 */ 515, 765, 124, 112, 160, 169, 174, 179, 168, 173, + /* 170 */ 134, 136, 128, 130, 126, 132, 138, 140, 142, 144, + /* 180 */ 146, 148, 150, 152, 154, 156, 810, 349, 47, 73, + /* 190 */ 222, 763, 223, 114, 246, 31, 32, 48, 13, 14, + /* 200 */ 74, 274, 252, 166, 175, 180, 275, 304, 49, 8, + /* 210 */ 255, 45, 13, 14, 159, 290, 350, 382, 158, 245, + /* 220 */ 441, 46, 378, 183, 247, 185, 186, 15, 16, 17, + /* 230 */ 73, 205, 124, 112, 160, 169, 174, 179, 168, 173, + /* 240 */ 134, 136, 128, 130, 126, 132, 138, 140, 142, 144, + /* 250 */ 146, 148, 150, 152, 154, 156, 542, 306, 438, 159, + /* 260 */ 98, 96, 332, 158, 272, 475, 447, 437, 12, 256, + /* 270 */ 288, 12, 304, 339, 287, 50, 77, 124, 112, 160, + /* 280 */ 169, 174, 179, 168, 173, 134, 136, 128, 130, 126, + /* 290 */ 132, 138, 140, 142, 144, 146, 148, 150, 152, 154, + /* 300 */ 156, 547, 36, 335, 39, 58, 64, 66, 299, 330, + /* 310 */ 35, 334, 291, 545, 114, 332, 114, 329, 12, 625, + /* 320 */ 353, 187, 306, 355, 361, 366, 422, 13, 14, 159, + /* 330 */ 13, 14, 184, 158, 369, 636, 188, 259, 188, 764, + /* 340 */ 91, 87, 88, 100, 87, 88, 219, 124, 112, 160, + /* 350 */ 169, 174, 179, 168, 173, 134, 136, 128, 130, 126, + /* 360 */ 132, 138, 140, 142, 144, 146, 148, 150, 152, 154, + /* 370 */ 156, 297, 282, 114, 292, 51, 237, 13, 14, 150, + /* 380 */ 152, 154, 156, 114, 12, 225, 53, 225, 159, 166, + /* 390 */ 175, 180, 158, 380, 303, 111, 433, 658, 69, 92, + /* 400 */ 379, 183, 92, 185, 186, 111, 124, 112, 160, 169, + /* 410 */ 174, 179, 168, 173, 134, 136, 128, 130, 126, 132, + /* 420 */ 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, + /* 430 */ 103, 230, 561, 159, 773, 12, 286, 158, 631, 534, + /* 440 */ 535, 105, 815, 13, 14, 166, 175, 180, 203, 808, + /* 450 */ 215, 124, 112, 160, 169, 174, 179, 168, 173, 134, + /* 460 */ 136, 128, 130, 126, 132, 138, 140, 142, 144, 146, + /* 470 */ 148, 150, 152, 154, 156, 2, 3, 183, 159, 185, + /* 480 */ 186, 813, 158, 43, 44, 569, 33, 633, 41, 348, + /* 490 */ 340, 413, 415, 414, 13, 14, 124, 112, 160, 169, + /* 500 */ 174, 179, 168, 173, 134, 136, 128, 130, 126, 132, + /* 510 */ 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, + /* 520 */ 249, 336, 697, 159, 337, 338, 183, 158, 185, 186, + /* 530 */ 56, 57, 183, 11, 185, 186, 183, 416, 185, 186, + /* 540 */ 402, 124, 112, 160, 169, 174, 179, 168, 173, 134, + /* 550 */ 136, 128, 130, 126, 132, 138, 140, 142, 144, 146, + /* 560 */ 148, 150, 152, 154, 156, 342, 87, 88, 159, 345, + /* 570 */ 87, 88, 158, 98, 96, 183, 404, 185, 186, 240, + /* 580 */ 9, 183, 92, 185, 186, 802, 124, 177, 160, 169, + /* 590 */ 174, 179, 168, 173, 134, 136, 128, 130, 126, 132, + /* 600 */ 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, + /* 610 */ 787, 341, 257, 159, 255, 255, 183, 158, 185, 186, + /* 620 */ 94, 95, 480, 518, 92, 307, 314, 316, 92, 548, + /* 630 */ 325, 171, 112, 160, 169, 174, 179, 168, 173, 134, + /* 640 */ 136, 128, 130, 126, 132, 138, 140, 142, 144, 146, + /* 650 */ 148, 150, 152, 154, 156, 255, 25, 486, 159, 482, + /* 660 */ 170, 358, 158, 19, 241, 242, 252, 266, 513, 267, + /* 670 */ 259, 553, 72, 256, 256, 402, 68, 244, 160, 169, + /* 680 */ 174, 179, 168, 173, 134, 136, 128, 130, 126, 132, + /* 690 */ 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, + /* 700 */ 207, 255, 72, 326, 780, 260, 68, 267, 514, 47, + /* 710 */ 189, 428, 388, 385, 256, 325, 259, 21, 48, 162, + /* 720 */ 395, 12, 114, 161, 516, 517, 195, 193, 294, 49, + /* 730 */ 207, 484, 209, 312, 191, 70, 71, 387, 246, 113, + /* 740 */ 189, 164, 165, 73, 198, 114, 363, 396, 114, 391, + /* 750 */ 73, 277, 529, 313, 436, 182, 195, 193, 72, 467, + /* 760 */ 256, 623, 68, 245, 191, 70, 71, 188, 163, 113, + /* 770 */ 188, 119, 120, 121, 122, 197, 114, 803, 691, 72, + /* 780 */ 13, 14, 92, 68, 73, 73, 207, 77, 326, 73, + /* 790 */ 199, 807, 99, 436, 452, 293, 189, 223, 474, 325, + /* 800 */ 309, 119, 120, 121, 122, 197, 423, 207, 221, 460, + /* 810 */ 434, 419, 195, 193, 418, 90, 224, 189, 77, 225, + /* 820 */ 191, 70, 71, 73, 442, 113, 420, 114, 325, 444, + /* 830 */ 372, 468, 114, 195, 193, 283, 325, 311, 310, 402, + /* 840 */ 470, 191, 70, 71, 114, 7, 113, 41, 460, 474, + /* 850 */ 18, 20, 22, 386, 296, 114, 457, 119, 120, 121, + /* 860 */ 122, 197, 766, 446, 521, 554, 123, 430, 444, 23, + /* 870 */ 531, 114, 326, 114, 114, 481, 114, 125, 119, 120, + /* 880 */ 121, 122, 197, 510, 72, 441, 114, 238, 68, 114, + /* 890 */ 508, 506, 114, 127, 114, 129, 131, 114, 133, 411, + /* 900 */ 412, 322, 114, 114, 114, 114, 407, 114, 135, 326, + /* 910 */ 660, 137, 207, 114, 139, 114, 141, 451, 114, 143, + /* 920 */ 114, 114, 189, 114, 145, 147, 149, 151, 114, 153, + /* 930 */ 489, 493, 437, 114, 114, 155, 479, 157, 195, 193, + /* 940 */ 167, 77, 176, 178, 114, 190, 191, 70, 71, 114, + /* 950 */ 192, 113, 114, 114, 114, 194, 196, 114, 691, 114, + /* 960 */ 269, 320, 343, 321, 344, 269, 204, 114, 359, 284, + /* 970 */ 321, 206, 114, 555, 216, 218, 220, 114, 364, 234, + /* 980 */ 321, 239, 660, 119, 120, 121, 122, 197, 373, 271, + /* 990 */ 321, 281, 114, 114, 367, 227, 227, 269, 431, 408, + /* 1000 */ 321, 503, 439, 44, 465, 473, 267, 471, 114, 77, + /* 1010 */ 402, 402, 402, 402, 455, 459, 265, 457, 402, 402, + /* 1020 */ 823, 417, 504, 507, 556, 471, 28, 29, 560, 37, + /* 1030 */ 472, 73, 34, 55, 40, 41, 42, 54, 59, 67, + /* 1040 */ 570, 571, 52, 75, 60, 78, 483, 485, 487, 491, + /* 1050 */ 61, 65, 76, 464, 495, 501, 101, 527, 77, 238, + /* 1060 */ 233, 235, 85, 93, 86, 80, 97, 238, 102, 81, + /* 1070 */ 104, 82, 108, 107, 109, 110, 83, 115, 497, 84, + /* 1080 */ 117, 116, 156, 172, 637, 217, 638, 118, 202, 226, + /* 1090 */ 639, 208, 106, 211, 227, 210, 213, 214, 212, 229, + /* 1100 */ 228, 231, 236, 223, 200, 243, 201, 251, 248, 250, + /* 1110 */ 254, 253, 232, 258, 261, 270, 264, 263, 262, 268, + /* 1120 */ 276, 278, 285, 295, 318, 279, 300, 303, 301, 305, + /* 1130 */ 333, 346, 298, 323, 327, 356, 357, 362, 370, 302, + /* 1140 */ 371, 53, 374, 394, 399, 354, 331, 375, 401, 409, + /* 1150 */ 308, 347, 315, 324, 406, 317, 405, 328, 795, 390, + /* 1160 */ 389, 392, 397, 410, 421, 800, 360, 381, 365, 393, + /* 1170 */ 398, 352, 376, 403, 801, 377, 400, 425, 426, 424, + /* 1180 */ 427, 429, 771, 432, 772, 435, 440, 698, 443, 794, + /* 1190 */ 445, 438, 809, 449, 699, 450, 453, 448, 454, 456, + /* 1200 */ 811, 458, 461, 462, 463, 469, 812, 814, 476, 630, + /* 1210 */ 478, 632, 779, 821, 490, 477, 690, 492, 494, 496, + /* 1220 */ 498, 693, 500, 505, 696, 509, 781, 511, 782, 783, + /* 1230 */ 466, 784, 785, 502, 512, 786, 520, 822, 519, 530, + /* 1240 */ 524, 824, 523, 825, 525, 528, 533, 828, 518, 518, + /* 1250 */ 518, 518, 518, 518, 522, 518, 526, 518, 518, 532, +}; +static const YYCODETYPE yy_lookahead[] = { + /* 0 */ 24, 139, 26, 72, 73, 74, 75, 76, 77, 78, + /* 10 */ 79, 80, 81, 154, 155, 156, 40, 26, 135, 136, + /* 20 */ 44, 0, 158, 140, 131, 132, 133, 134, 164, 146, + /* 30 */ 9, 10, 170, 60, 58, 59, 60, 61, 62, 63, + /* 40 */ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + /* 50 */ 74, 75, 76, 77, 78, 79, 80, 81, 22, 176, + /* 60 */ 24, 85, 89, 90, 91, 92, 93, 94, 23, 23, + /* 70 */ 25, 25, 213, 100, 212, 40, 85, 86, 87, 44, + /* 80 */ 9, 90, 91, 92, 201, 76, 77, 78, 79, 80, + /* 90 */ 81, 26, 101, 58, 59, 60, 61, 62, 63, 64, + /* 100 */ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + /* 110 */ 75, 76, 77, 78, 79, 80, 81, 68, 69, 70, + /* 120 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + /* 130 */ 81, 23, 87, 25, 29, 90, 91, 92, 179, 26, + /* 140 */ 35, 76, 77, 23, 40, 186, 101, 139, 44, 22, + /* 150 */ 85, 86, 144, 26, 9, 147, 148, 12, 159, 146, + /* 160 */ 95, 126, 58, 59, 60, 61, 62, 63, 64, 65, + /* 170 */ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + /* 180 */ 76, 77, 78, 79, 80, 81, 17, 139, 18, 176, + /* 190 */ 23, 17, 25, 139, 86, 147, 148, 27, 85, 86, + /* 200 */ 146, 188, 189, 204, 205, 206, 193, 45, 38, 137, + /* 210 */ 26, 41, 85, 86, 40, 161, 168, 169, 44, 111, + /* 220 */ 51, 51, 60, 103, 111, 105, 106, 13, 14, 15, + /* 230 */ 176, 127, 58, 59, 60, 61, 62, 63, 64, 65, + /* 240 */ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + /* 250 */ 76, 77, 78, 79, 80, 81, 9, 95, 58, 40, + /* 260 */ 76, 77, 100, 44, 22, 96, 97, 98, 26, 85, + /* 270 */ 104, 26, 45, 89, 108, 60, 107, 58, 59, 60, + /* 280 */ 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + /* 290 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + /* 300 */ 81, 9, 87, 88, 89, 90, 91, 92, 93, 94, + /* 310 */ 157, 158, 23, 9, 139, 100, 139, 164, 26, 119, + /* 320 */ 87, 23, 95, 90, 91, 92, 21, 85, 86, 40, + /* 330 */ 85, 86, 104, 44, 101, 107, 161, 152, 161, 17, + /* 340 */ 154, 155, 156, 154, 155, 156, 127, 58, 59, 60, + /* 350 */ 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + /* 360 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + /* 370 */ 81, 23, 187, 139, 199, 89, 199, 85, 86, 78, + /* 380 */ 79, 80, 81, 139, 26, 210, 100, 210, 40, 204, + /* 390 */ 205, 206, 44, 164, 165, 161, 91, 23, 22, 213, + /* 400 */ 171, 103, 213, 105, 106, 161, 58, 59, 60, 61, + /* 410 */ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + /* 420 */ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + /* 430 */ 196, 197, 9, 40, 129, 26, 78, 44, 9, 9, + /* 440 */ 10, 197, 9, 85, 86, 204, 205, 206, 126, 11, + /* 450 */ 128, 58, 59, 60, 61, 62, 63, 64, 65, 66, + /* 460 */ 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, + /* 470 */ 77, 78, 79, 80, 81, 133, 134, 103, 40, 105, + /* 480 */ 106, 9, 44, 173, 174, 109, 149, 9, 95, 152, + /* 490 */ 153, 96, 97, 98, 85, 86, 58, 59, 60, 61, + /* 500 */ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + /* 510 */ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + /* 520 */ 111, 152, 9, 40, 155, 156, 103, 44, 105, 106, + /* 530 */ 13, 14, 103, 139, 105, 106, 103, 47, 105, 106, + /* 540 */ 139, 58, 59, 60, 61, 62, 63, 64, 65, 66, + /* 550 */ 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, + /* 560 */ 77, 78, 79, 80, 81, 154, 155, 156, 40, 154, + /* 570 */ 155, 156, 44, 76, 77, 103, 175, 105, 106, 25, + /* 580 */ 138, 103, 213, 105, 106, 95, 58, 59, 60, 61, + /* 590 */ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + /* 600 */ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + /* 610 */ 9, 22, 24, 40, 26, 26, 103, 44, 105, 106, + /* 620 */ 121, 122, 20, 22, 213, 96, 97, 98, 213, 9, + /* 630 */ 139, 60, 59, 60, 61, 62, 63, 64, 65, 66, + /* 640 */ 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, + /* 650 */ 77, 78, 79, 80, 81, 26, 141, 55, 40, 57, + /* 660 */ 89, 170, 44, 138, 110, 188, 189, 23, 67, 25, + /* 670 */ 152, 9, 22, 85, 85, 139, 26, 25, 60, 61, + /* 680 */ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + /* 690 */ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + /* 700 */ 50, 26, 22, 212, 9, 187, 26, 25, 139, 18, + /* 710 */ 60, 175, 20, 146, 85, 139, 152, 138, 27, 40, + /* 720 */ 146, 26, 139, 44, 155, 156, 76, 77, 78, 38, + /* 730 */ 50, 129, 41, 32, 84, 85, 86, 142, 86, 89, + /* 740 */ 60, 62, 63, 176, 161, 139, 170, 55, 139, 57, + /* 750 */ 176, 187, 123, 52, 146, 146, 76, 77, 22, 146, + /* 760 */ 85, 9, 26, 111, 84, 85, 86, 161, 89, 89, + /* 770 */ 161, 121, 122, 123, 124, 125, 139, 95, 9, 22, + /* 780 */ 85, 86, 213, 26, 176, 176, 50, 107, 212, 176, + /* 790 */ 207, 11, 25, 146, 25, 23, 60, 25, 161, 139, + /* 800 */ 99, 121, 122, 123, 124, 125, 211, 50, 199, 201, + /* 810 */ 215, 28, 76, 77, 31, 48, 210, 60, 107, 210, + /* 820 */ 84, 85, 86, 176, 216, 89, 43, 139, 139, 221, + /* 830 */ 170, 120, 139, 76, 77, 78, 139, 88, 89, 139, + /* 840 */ 203, 84, 85, 86, 139, 11, 89, 95, 201, 161, + /* 850 */ 16, 17, 18, 19, 161, 139, 139, 121, 122, 123, + /* 860 */ 124, 125, 126, 216, 30, 9, 161, 170, 221, 138, + /* 870 */ 36, 139, 212, 139, 139, 175, 139, 161, 121, 122, + /* 880 */ 123, 124, 125, 49, 22, 51, 139, 118, 26, 139, + /* 890 */ 56, 203, 139, 161, 139, 161, 161, 139, 161, 53, + /* 900 */ 54, 212, 139, 139, 139, 139, 126, 139, 161, 212, + /* 910 */ 24, 161, 50, 139, 161, 139, 161, 200, 139, 161, + /* 920 */ 139, 139, 60, 139, 161, 161, 161, 161, 139, 161, + /* 930 */ 96, 97, 98, 139, 139, 161, 102, 161, 76, 77, + /* 940 */ 161, 107, 161, 161, 139, 161, 84, 85, 86, 139, + /* 950 */ 161, 89, 139, 139, 139, 161, 161, 139, 9, 139, + /* 960 */ 139, 23, 23, 25, 25, 139, 161, 139, 23, 139, + /* 970 */ 25, 161, 139, 9, 161, 161, 161, 139, 23, 161, + /* 980 */ 25, 161, 95, 121, 122, 123, 124, 125, 23, 161, + /* 990 */ 25, 161, 139, 139, 161, 109, 109, 139, 23, 161, + /* 1000 */ 25, 146, 173, 174, 23, 23, 25, 25, 139, 107, + /* 1010 */ 139, 139, 139, 139, 161, 161, 195, 139, 139, 139, + /* 1020 */ 9, 195, 120, 23, 9, 25, 145, 23, 9, 139, + /* 1030 */ 161, 176, 150, 42, 159, 95, 33, 167, 46, 22, + /* 1040 */ 109, 109, 159, 177, 160, 178, 175, 175, 175, 175, + /* 1050 */ 159, 159, 176, 195, 175, 175, 113, 46, 107, 118, + /* 1060 */ 116, 115, 185, 214, 117, 180, 214, 118, 114, 181, + /* 1070 */ 25, 182, 94, 160, 26, 151, 183, 109, 200, 184, + /* 1080 */ 109, 139, 81, 89, 107, 126, 107, 139, 17, 139, + /* 1090 */ 107, 22, 198, 174, 109, 23, 139, 23, 25, 143, + /* 1100 */ 139, 198, 114, 25, 208, 190, 209, 111, 139, 139, + /* 1110 */ 143, 139, 160, 139, 191, 95, 22, 112, 192, 139, + /* 1120 */ 23, 191, 109, 23, 22, 192, 139, 165, 162, 139, + /* 1130 */ 167, 23, 159, 198, 198, 46, 22, 22, 46, 163, + /* 1140 */ 22, 100, 93, 24, 217, 139, 151, 139, 95, 39, + /* 1150 */ 166, 152, 166, 160, 220, 166, 219, 160, 11, 143, + /* 1160 */ 139, 139, 139, 37, 47, 95, 159, 169, 159, 143, + /* 1170 */ 143, 169, 162, 143, 95, 163, 218, 139, 143, 129, + /* 1180 */ 95, 22, 9, 159, 129, 11, 172, 119, 17, 9, + /* 1190 */ 9, 58, 17, 139, 119, 99, 139, 172, 67, 181, + /* 1200 */ 9, 67, 119, 139, 22, 22, 9, 9, 110, 9, + /* 1210 */ 181, 9, 9, 9, 110, 139, 9, 181, 172, 99, + /* 1220 */ 181, 9, 119, 22, 9, 139, 9, 139, 9, 9, + /* 1230 */ 202, 9, 9, 202, 143, 9, 23, 9, 139, 34, + /* 1240 */ 24, 9, 152, 9, 139, 152, 139, 9, 224, 224, + /* 1250 */ 224, 224, 224, 224, 222, 224, 223, 224, 224, 222, +}; +#define YY_SHIFT_USE_DFLT (-70) +static const short yy_shift_ofst[] = { + /* 0 */ 430, 21, -70, 834, 71, -70, 247, 214, 145, 304, + /* 10 */ 292, 620, -70, -70, -70, -70, -70, -70, 145, 662, + /* 20 */ 145, 856, 145, 964, 36, 1015, 245, 46, 1004, 1019, + /* 30 */ -9, -70, 675, -70, 215, -70, 245, -27, -70, 940, + /* 40 */ -70, 1003, 170, -70, -70, -70, -70, -70, -70, -70, + /* 50 */ 286, 940, -70, 991, -70, 517, -70, -70, 992, 105, + /* 60 */ 940, -70, -70, -70, 940, -70, 1017, 862, 376, 650, + /* 70 */ 931, 932, 680, -70, 120, 951, -70, 166, -70, 554, + /* 80 */ 941, 946, 944, 943, 947, -70, 497, -70, -70, 767, + /* 90 */ 497, -70, 499, -70, -70, -70, 499, -70, -70, 497, + /* 100 */ -70, 954, 862, 1045, 862, 978, 105, -70, 1048, -70, + /* 110 */ -70, 483, 862, -70, 968, 245, 971, 245, -70, -70, + /* 120 */ -70, -70, -70, 618, 862, 573, 862, -69, 862, -69, + /* 130 */ 862, -69, 862, -69, 862, 49, 862, 49, 862, 9, + /* 140 */ 862, 9, 862, 9, 862, 9, 862, 301, 862, 301, + /* 150 */ 862, 1001, 862, 1001, 862, 1001, 862, -70, -70, -70, + /* 160 */ 679, -70, -70, -70, -70, -70, 862, 49, -70, 571, + /* 170 */ -70, 994, -70, -70, -70, 862, 528, 862, 49, -70, + /* 180 */ 127, 680, 298, 228, 977, 979, 983, -70, 483, 862, + /* 190 */ 618, 862, -70, 862, -70, 862, -70, 736, 35, 959, + /* 200 */ 322, 1071, -70, 862, 104, 862, 483, 1069, 691, 1072, + /* 210 */ -70, 1073, 245, 1074, -70, 862, 174, 862, 219, 862, + /* 220 */ 483, 167, -70, 862, -70, -70, 985, 245, -70, -70, + /* 230 */ 978, 105, -70, 862, 483, 988, 862, 1078, 862, 483, + /* 240 */ -70, -70, 652, -70, -70, -70, 113, -70, 409, -70, + /* 250 */ 996, -70, 242, 985, 588, -70, -70, 245, -70, -70, + /* 260 */ 1020, 1005, -70, 1094, 245, 644, -70, 245, -70, -70, + /* 270 */ 862, 483, 951, 374, 108, 1097, 588, 1020, 1005, -70, + /* 280 */ 757, -24, -70, -70, 1013, 358, -70, -70, -70, -70, + /* 290 */ 289, -70, 772, -70, 1100, -70, 348, 940, -70, 245, + /* 300 */ 1102, -70, 227, -70, 245, -70, 529, 701, -70, 749, + /* 310 */ -70, -70, -70, -70, 701, -70, 701, -70, 245, 938, + /* 320 */ -70, 245, 978, 105, -70, -70, 978, 105, -70, -70, + /* 330 */ 1048, -70, 991, -70, -70, 184, -70, -70, -70, -70, + /* 340 */ 589, 497, 939, -70, 497, 1108, -70, -70, -70, -70, + /* 350 */ 45, 233, -70, 245, -70, 1089, 1114, 245, 945, 940, + /* 360 */ -70, 1115, 245, 955, 940, -70, 862, 393, -70, 1092, + /* 370 */ 1118, 245, 965, 1049, 245, 1102, -70, 162, 1041, -70, + /* 380 */ -70, -70, -70, -70, 951, 423, 305, 692, 245, 985, + /* 390 */ -70, 245, 886, 1119, 951, 429, 245, 985, 783, 395, + /* 400 */ 1053, 245, 985, -70, 1110, 780, 1147, 862, 438, 1126, + /* 410 */ 846, -70, -70, 1070, 1079, 490, 245, 682, -70, -70, + /* 420 */ 1117, -70, -70, 1050, 245, 887, 1085, 245, 1159, 245, + /* 430 */ 975, 752, 1173, 1055, 1174, 169, 433, 200, 170, -70, + /* 440 */ 1068, 1075, 1171, 1180, 1181, 169, 1175, 1133, 245, 1096, + /* 450 */ 245, 769, 245, 1131, 862, 483, 1191, 1134, 862, 483, + /* 460 */ 1083, 245, 1182, 245, 981, -70, 711, 472, 1183, 862, + /* 470 */ 982, 862, 483, 1197, 483, 1098, 245, 949, 1198, 602, + /* 480 */ 245, 1200, 245, 1202, 245, 1203, 245, 1204, 478, 1104, + /* 490 */ 245, 949, 1207, 1133, 245, 1120, 245, 769, 1212, 1103, + /* 500 */ 245, 1182, 902, 513, 1201, 862, 1000, 1215, 695, 1217, + /* 510 */ 245, 985, 601, 65, 1219, 1220, 1222, 1223, 245, 1213, + /* 520 */ 1226, 1205, 675, 1216, 245, 1011, 1228, 629, 1232, 1234, + /* 530 */ -70, 1205, 245, 1238, -70, -70, -70, +}; +#define YY_REDUCE_USE_DFLT (-142) +static const short yy_reduce_ofst[] = { + /* 0 */ -107, 342, -142, -117, -142, -142, -142, 72, 442, -142, + /* 10 */ 394, -142, -142, -142, -142, -142, -142, -142, 525, -142, + /* 20 */ 579, -142, 731, -142, 515, -142, 8, 881, -142, -142, + /* 30 */ 48, -142, 337, 882, 153, -142, 890, -136, -142, 875, + /* 40 */ -142, -142, 310, -142, -142, -142, -142, -142, -142, -142, + /* 50 */ -142, 883, -142, 870, -142, -142, -142, -142, -142, 884, + /* 60 */ 891, -142, -142, -142, 892, -142, -142, 693, -142, 175, + /* 70 */ -142, -142, 54, -142, 866, 876, -142, 867, -41, 885, + /* 80 */ 888, 889, 893, 895, 877, -142, -141, -142, -142, -142, + /* 90 */ 186, -142, 849, -142, -142, -142, 852, -142, -142, 189, + /* 100 */ -142, -142, 234, -142, 244, 894, 913, -142, 924, -142, + /* 110 */ -142, 241, 705, -142, -142, 942, -142, 948, -142, -142, + /* 120 */ -142, -142, -142, 241, 716, 241, 732, 241, 734, 241, + /* 130 */ 735, 241, 737, 241, 747, 241, 750, 241, 753, 241, + /* 140 */ 755, 241, 758, 241, 763, 241, 764, 241, 765, 241, + /* 150 */ 766, 241, 768, 241, 774, 241, 776, 241, -142, -142, + /* 160 */ -142, -142, -142, -142, -142, -142, 779, 241, -142, -142, + /* 170 */ -142, -142, -142, -142, -142, 781, 241, 782, 241, -142, + /* 180 */ 950, 609, 866, -142, -142, -142, -142, -142, 241, 784, + /* 190 */ 241, 789, 241, 794, 241, 795, 241, 583, 241, 896, + /* 200 */ 897, -142, -142, 805, 241, 810, 241, -142, 919, -142, + /* 210 */ -142, -142, 957, -142, -142, 813, 241, 814, 241, 815, + /* 220 */ 241, -142, -142, 606, -142, -142, 956, 961, -142, -142, + /* 230 */ 903, 952, -142, 818, 241, -142, 177, -142, 820, 241, + /* 240 */ -142, 477, 915, -142, -142, -142, 969, -142, 970, -142, + /* 250 */ -142, -142, 972, 967, 518, -142, -142, 974, -142, -142, + /* 260 */ 923, 926, -142, -142, 821, -142, -142, 980, -142, -142, + /* 270 */ 828, 241, 13, 866, 915, -142, 564, 930, 933, -142, + /* 280 */ 830, 185, -142, -142, -142, 942, -142, -142, -142, -142, + /* 290 */ 241, -142, -142, -142, -142, -142, 241, 973, -142, 987, + /* 300 */ 966, 976, 962, -142, 990, -142, -142, 984, -142, -142, + /* 310 */ -142, -142, -142, -142, 986, -142, 989, -142, -138, -142, + /* 320 */ -142, 689, 935, 993, -142, -142, 936, 997, -142, -142, + /* 330 */ 995, -142, 963, -142, -142, 369, -142, -142, -142, -142, + /* 340 */ 999, 411, -142, -142, 415, -142, -142, -142, -142, -142, + /* 350 */ 998, 1002, -142, 1006, -142, -142, -142, 491, -142, 1007, + /* 360 */ -142, -142, 576, -142, 1009, -142, 833, -1, -142, -142, + /* 370 */ -142, 660, -142, -142, 1008, 1010, 1012, 229, -142, -142, + /* 380 */ -142, -142, -142, -142, 567, 866, 595, -142, 1021, 1016, + /* 390 */ -142, 1022, 1026, -142, 574, 866, 1023, 1027, 927, 958, + /* 400 */ -142, 401, 1030, -142, 937, 934, -142, 838, 241, -142, + /* 410 */ -142, -142, -142, -142, -142, -142, 826, -142, -142, -142, + /* 420 */ -142, -142, -142, -142, 1038, 1035, -142, 536, -142, 697, + /* 430 */ -142, 1024, -142, -142, -142, 608, 866, 1014, 829, -142, + /* 440 */ -142, -142, -142, -142, -142, 647, -142, 1025, 1054, -142, + /* 450 */ 717, 1018, 1057, -142, 853, 241, -142, -142, 854, 241, + /* 460 */ -142, 1064, 1028, 858, -142, -142, 613, 866, -142, 637, + /* 470 */ -142, 869, 241, -142, 241, -142, 1076, 1029, -142, -142, + /* 480 */ 700, -142, 871, -142, 872, -142, 873, -142, 866, -142, + /* 490 */ 874, 1036, -142, 1046, 879, -142, 878, 1039, -142, -142, + /* 500 */ 880, 1031, 855, 866, -142, 688, -142, -142, 1086, -142, + /* 510 */ 1088, 1091, -142, 569, -142, -142, -142, -142, 1099, -142, + /* 520 */ -142, 1032, 1090, -142, 1105, 1033, -142, 1093, -142, -142, + /* 530 */ -142, 1037, 1107, -142, -142, -142, -142, +}; +static const YYACTIONTYPE yy_default[] = { + /* 0 */ 544, 544, 538, 829, 829, 540, 829, 549, 829, 829, + /* 10 */ 829, 829, 569, 570, 571, 550, 551, 552, 829, 829, + /* 20 */ 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, + /* 30 */ 829, 562, 572, 581, 564, 580, 829, 829, 582, 623, + /* 40 */ 588, 829, 829, 624, 627, 628, 629, 818, 819, 820, + /* 50 */ 829, 623, 589, 608, 606, 829, 609, 610, 829, 679, + /* 60 */ 623, 590, 677, 678, 623, 591, 829, 829, 708, 770, + /* 70 */ 714, 709, 829, 634, 829, 829, 635, 643, 645, 652, + /* 80 */ 691, 682, 684, 672, 686, 640, 793, 578, 579, 687, + /* 90 */ 793, 688, 829, 788, 790, 791, 829, 789, 792, 793, + /* 100 */ 689, 829, 829, 673, 829, 680, 679, 674, 829, 566, + /* 110 */ 681, 676, 829, 707, 829, 829, 710, 829, 711, 712, + /* 120 */ 713, 715, 716, 719, 829, 720, 829, 721, 829, 722, + /* 130 */ 829, 723, 829, 724, 829, 725, 829, 726, 829, 727, + /* 140 */ 829, 728, 829, 729, 829, 730, 829, 731, 829, 732, + /* 150 */ 829, 733, 829, 734, 829, 735, 829, 736, 737, 738, + /* 160 */ 829, 739, 740, 745, 753, 756, 829, 741, 742, 829, + /* 170 */ 743, 829, 746, 744, 752, 829, 829, 829, 754, 755, + /* 180 */ 829, 770, 829, 829, 829, 829, 829, 758, 769, 829, + /* 190 */ 747, 829, 748, 829, 749, 829, 750, 829, 829, 829, + /* 200 */ 829, 829, 760, 829, 829, 829, 761, 829, 829, 829, + /* 210 */ 816, 829, 829, 829, 817, 829, 829, 829, 829, 829, + /* 220 */ 762, 829, 757, 770, 767, 768, 660, 829, 661, 759, + /* 230 */ 680, 679, 675, 829, 685, 829, 770, 683, 829, 692, + /* 240 */ 644, 655, 653, 654, 663, 664, 829, 665, 829, 666, + /* 250 */ 829, 667, 829, 660, 651, 567, 568, 829, 649, 650, + /* 260 */ 669, 671, 656, 829, 829, 829, 670, 829, 704, 705, + /* 270 */ 829, 668, 655, 829, 829, 829, 651, 669, 671, 657, + /* 280 */ 829, 651, 646, 647, 829, 829, 648, 641, 642, 751, + /* 290 */ 829, 706, 829, 717, 829, 718, 829, 623, 592, 829, + /* 300 */ 774, 596, 593, 597, 829, 598, 829, 829, 599, 829, + /* 310 */ 602, 603, 604, 605, 829, 600, 829, 601, 829, 829, + /* 320 */ 775, 829, 680, 679, 776, 778, 680, 679, 777, 594, + /* 330 */ 829, 595, 608, 607, 583, 793, 584, 585, 586, 587, + /* 340 */ 573, 793, 829, 574, 793, 829, 575, 577, 576, 565, + /* 350 */ 829, 829, 613, 829, 616, 829, 829, 829, 829, 623, + /* 360 */ 617, 829, 829, 829, 623, 618, 829, 623, 619, 829, + /* 370 */ 829, 829, 829, 829, 829, 774, 596, 621, 829, 620, + /* 380 */ 622, 614, 615, 563, 829, 829, 559, 829, 829, 660, + /* 390 */ 557, 829, 829, 829, 829, 829, 829, 660, 799, 829, + /* 400 */ 829, 829, 660, 662, 804, 829, 829, 829, 829, 829, + /* 410 */ 829, 805, 806, 829, 829, 829, 829, 829, 796, 797, + /* 420 */ 829, 798, 558, 829, 829, 829, 829, 829, 829, 829, + /* 430 */ 829, 829, 829, 829, 829, 829, 829, 829, 829, 626, + /* 440 */ 829, 829, 829, 829, 829, 829, 829, 625, 829, 829, + /* 450 */ 829, 829, 829, 829, 829, 694, 829, 829, 829, 695, + /* 460 */ 829, 829, 702, 829, 829, 703, 829, 829, 829, 829, + /* 470 */ 829, 829, 700, 829, 701, 829, 829, 829, 829, 829, + /* 480 */ 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, + /* 490 */ 829, 829, 829, 625, 829, 829, 829, 829, 829, 829, + /* 500 */ 829, 702, 829, 829, 829, 829, 829, 829, 829, 829, + /* 510 */ 829, 660, 829, 793, 829, 829, 829, 829, 829, 829, + /* 520 */ 829, 827, 829, 829, 829, 829, 829, 829, 829, 829, + /* 530 */ 826, 827, 829, 829, 541, 543, 539, +}; +#define YY_SZ_ACTTAB (sizeof(yy_action)/sizeof(yy_action[0])) + +/* The next table maps tokens into fallback tokens. If a construct +** like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammer, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { + 0, /* $ => nothing */ + 0, /* END_OF_FILE => nothing */ + 0, /* ILLEGAL => nothing */ + 0, /* SPACE => nothing */ + 0, /* UNCLOSED_STRING => nothing */ + 0, /* COMMENT => nothing */ + 0, /* FUNCTION => nothing */ + 0, /* COLUMN => nothing */ + 0, /* AGG_FUNCTION => nothing */ + 0, /* SEMI => nothing */ + 26, /* EXPLAIN => ID */ + 26, /* BEGIN => ID */ + 0, /* TRANSACTION => nothing */ + 26, /* DEFERRED => ID */ + 26, /* IMMEDIATE => ID */ + 26, /* EXCLUSIVE => ID */ + 0, /* COMMIT => nothing */ + 26, /* END => ID */ + 0, /* ROLLBACK => nothing */ + 0, /* CREATE => nothing */ + 0, /* TABLE => nothing */ + 26, /* TEMP => ID */ + 0, /* LP => nothing */ + 0, /* RP => nothing */ + 0, /* AS => nothing */ + 0, /* COMMA => nothing */ + 0, /* ID => nothing */ + 26, /* ABORT => ID */ + 26, /* AFTER => ID */ + 26, /* ASC => ID */ + 26, /* ATTACH => ID */ + 26, /* BEFORE => ID */ + 26, /* CASCADE => ID */ + 26, /* CONFLICT => ID */ + 26, /* DATABASE => ID */ + 26, /* DESC => ID */ + 26, /* DETACH => ID */ + 26, /* EACH => ID */ + 26, /* FAIL => ID */ + 26, /* FOR => ID */ + 26, /* GLOB => ID */ + 26, /* IGNORE => ID */ + 26, /* INITIALLY => ID */ + 26, /* INSTEAD => ID */ + 26, /* LIKE => ID */ + 26, /* MATCH => ID */ + 26, /* KEY => ID */ + 26, /* OF => ID */ + 26, /* OFFSET => ID */ + 26, /* PRAGMA => ID */ + 26, /* RAISE => ID */ + 26, /* REPLACE => ID */ + 26, /* RESTRICT => ID */ + 26, /* ROW => ID */ + 26, /* STATEMENT => ID */ + 26, /* TRIGGER => ID */ + 26, /* VACUUM => ID */ + 26, /* VIEW => ID */ + 0, /* OR => nothing */ + 0, /* AND => nothing */ + 0, /* NOT => nothing */ + 0, /* IS => nothing */ + 0, /* BETWEEN => nothing */ + 0, /* IN => nothing */ + 0, /* ISNULL => nothing */ + 0, /* NOTNULL => nothing */ + 0, /* NE => nothing */ + 0, /* EQ => nothing */ + 0, /* GT => nothing */ + 0, /* LE => nothing */ + 0, /* LT => nothing */ + 0, /* GE => nothing */ + 0, /* BITAND => nothing */ + 0, /* BITOR => nothing */ + 0, /* LSHIFT => nothing */ + 0, /* RSHIFT => nothing */ + 0, /* PLUS => nothing */ + 0, /* MINUS => nothing */ + 0, /* STAR => nothing */ + 0, /* SLASH => nothing */ + 0, /* REM => nothing */ + 0, /* CONCAT => nothing */ + 0, /* UMINUS => nothing */ + 0, /* UPLUS => nothing */ + 0, /* BITNOT => nothing */ + 0, /* STRING => nothing */ + 0, /* JOIN_KW => nothing */ + 0, /* CONSTRAINT => nothing */ + 0, /* DEFAULT => nothing */ + 0, /* NULL => nothing */ + 0, /* PRIMARY => nothing */ + 0, /* UNIQUE => nothing */ + 0, /* CHECK => nothing */ + 0, /* REFERENCES => nothing */ + 0, /* COLLATE => nothing */ + 0, /* ON => nothing */ + 0, /* DELETE => nothing */ + 0, /* UPDATE => nothing */ + 0, /* INSERT => nothing */ + 0, /* SET => nothing */ + 0, /* DEFERRABLE => nothing */ + 0, /* FOREIGN => nothing */ + 0, /* DROP => nothing */ + 0, /* UNION => nothing */ + 0, /* ALL => nothing */ + 0, /* INTERSECT => nothing */ + 0, /* EXCEPT => nothing */ + 0, /* SELECT => nothing */ + 0, /* DISTINCT => nothing */ + 0, /* DOT => nothing */ + 0, /* FROM => nothing */ + 0, /* JOIN => nothing */ + 0, /* USING => nothing */ + 0, /* ORDER => nothing */ + 0, /* BY => nothing */ + 0, /* GROUP => nothing */ + 0, /* HAVING => nothing */ + 0, /* LIMIT => nothing */ + 0, /* WHERE => nothing */ + 0, /* INTO => nothing */ + 0, /* VALUES => nothing */ + 0, /* INTEGER => nothing */ + 0, /* FLOAT => nothing */ + 0, /* BLOB => nothing */ + 0, /* VARIABLE => nothing */ + 0, /* CASE => nothing */ + 0, /* WHEN => nothing */ + 0, /* THEN => nothing */ + 0, /* ELSE => nothing */ + 0, /* INDEX => nothing */ +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +*/ +struct yyStackEntry { + int stateno; /* The state-number */ + int major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + int yyidx; /* Index of top element in stack */ + int yyerrcnt; /* Shifts left before out of the error */ + sqlite3ParserARG_SDECL /* A place to hold %extra_argument */ + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +**
      +**
    • A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +**
    • A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +**
    +** +** Outputs: +** None. +*/ +void sqlite3ParserTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { + "$", "END_OF_FILE", "ILLEGAL", "SPACE", + "UNCLOSED_STRING", "COMMENT", "FUNCTION", "COLUMN", + "AGG_FUNCTION", "SEMI", "EXPLAIN", "BEGIN", + "TRANSACTION", "DEFERRED", "IMMEDIATE", "EXCLUSIVE", + "COMMIT", "END", "ROLLBACK", "CREATE", + "TABLE", "TEMP", "LP", "RP", + "AS", "COMMA", "ID", "ABORT", + "AFTER", "ASC", "ATTACH", "BEFORE", + "CASCADE", "CONFLICT", "DATABASE", "DESC", + "DETACH", "EACH", "FAIL", "FOR", + "GLOB", "IGNORE", "INITIALLY", "INSTEAD", + "LIKE", "MATCH", "KEY", "OF", + "OFFSET", "PRAGMA", "RAISE", "REPLACE", + "RESTRICT", "ROW", "STATEMENT", "TRIGGER", + "VACUUM", "VIEW", "OR", "AND", + "NOT", "IS", "BETWEEN", "IN", + "ISNULL", "NOTNULL", "NE", "EQ", + "GT", "LE", "LT", "GE", + "BITAND", "BITOR", "LSHIFT", "RSHIFT", + "PLUS", "MINUS", "STAR", "SLASH", + "REM", "CONCAT", "UMINUS", "UPLUS", + "BITNOT", "STRING", "JOIN_KW", "CONSTRAINT", + "DEFAULT", "NULL", "PRIMARY", "UNIQUE", + "CHECK", "REFERENCES", "COLLATE", "ON", + "DELETE", "UPDATE", "INSERT", "SET", + "DEFERRABLE", "FOREIGN", "DROP", "UNION", + "ALL", "INTERSECT", "EXCEPT", "SELECT", + "DISTINCT", "DOT", "FROM", "JOIN", + "USING", "ORDER", "BY", "GROUP", + "HAVING", "LIMIT", "WHERE", "INTO", + "VALUES", "INTEGER", "FLOAT", "BLOB", + "VARIABLE", "CASE", "WHEN", "THEN", + "ELSE", "INDEX", "error", "input", + "cmdlist", "ecmd", "explain", "cmdx", + "cmd", "transtype", "trans_opt", "nm", + "create_table", "create_table_args", "temp", "dbnm", + "columnlist", "conslist_opt", "select", "column", + "columnid", "type", "carglist", "id", + "ids", "typename", "signed", "plus_num", + "minus_num", "carg", "ccons", "onconf", + "sortorder", "expr", "idxlist_opt", "refargs", + "defer_subclause", "refarg", "refact", "init_deferred_pred_opt", + "conslist", "tcons", "idxlist", "defer_subclause_opt", + "orconf", "resolvetype", "raisetype", "fullname", + "oneselect", "multiselect_op", "distinct", "selcollist", + "from", "where_opt", "groupby_opt", "having_opt", + "orderby_opt", "limit_opt", "sclp", "as", + "seltablist", "stl_prefix", "joinop", "on_opt", + "using_opt", "seltablist_paren", "joinop2", "inscollist", + "sortlist", "sortitem", "collate", "exprlist", + "setlist", "insert_cmd", "inscollist_opt", "itemlist", + "likeop", "between_op", "in_op", "case_operand", + "case_exprlist", "case_else", "expritem", "uniqueflag", + "idxitem", "plus_opt", "number", "trigger_decl", + "trigger_cmd_list", "trigger_time", "trigger_event", "foreach_clause", + "when_clause", "trigger_cmd", "database_kw_opt", "key_opt", +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { + /* 0 */ "input ::= cmdlist", + /* 1 */ "cmdlist ::= cmdlist ecmd", + /* 2 */ "cmdlist ::= ecmd", + /* 3 */ "ecmd ::= explain cmdx SEMI", + /* 4 */ "ecmd ::= SEMI", + /* 5 */ "cmdx ::= cmd", + /* 6 */ "explain ::= EXPLAIN", + /* 7 */ "explain ::=", + /* 8 */ "cmd ::= BEGIN transtype trans_opt", + /* 9 */ "trans_opt ::=", + /* 10 */ "trans_opt ::= TRANSACTION", + /* 11 */ "trans_opt ::= TRANSACTION nm", + /* 12 */ "transtype ::=", + /* 13 */ "transtype ::= DEFERRED", + /* 14 */ "transtype ::= IMMEDIATE", + /* 15 */ "transtype ::= EXCLUSIVE", + /* 16 */ "cmd ::= COMMIT trans_opt", + /* 17 */ "cmd ::= END trans_opt", + /* 18 */ "cmd ::= ROLLBACK trans_opt", + /* 19 */ "cmd ::= create_table create_table_args", + /* 20 */ "create_table ::= CREATE temp TABLE nm dbnm", + /* 21 */ "temp ::= TEMP", + /* 22 */ "temp ::=", + /* 23 */ "create_table_args ::= LP columnlist conslist_opt RP", + /* 24 */ "create_table_args ::= AS select", + /* 25 */ "columnlist ::= columnlist COMMA column", + /* 26 */ "columnlist ::= column", + /* 27 */ "column ::= columnid type carglist", + /* 28 */ "columnid ::= nm", + /* 29 */ "id ::= ID", + /* 30 */ "ids ::= ID", + /* 31 */ "ids ::= STRING", + /* 32 */ "nm ::= ID", + /* 33 */ "nm ::= STRING", + /* 34 */ "nm ::= JOIN_KW", + /* 35 */ "type ::=", + /* 36 */ "type ::= typename", + /* 37 */ "type ::= typename LP signed RP", + /* 38 */ "type ::= typename LP signed COMMA signed RP", + /* 39 */ "typename ::= ids", + /* 40 */ "typename ::= typename ids", + /* 41 */ "signed ::= plus_num", + /* 42 */ "signed ::= minus_num", + /* 43 */ "carglist ::= carglist carg", + /* 44 */ "carglist ::=", + /* 45 */ "carg ::= CONSTRAINT nm ccons", + /* 46 */ "carg ::= ccons", + /* 47 */ "carg ::= DEFAULT ids", + /* 48 */ "carg ::= DEFAULT plus_num", + /* 49 */ "carg ::= DEFAULT minus_num", + /* 50 */ "carg ::= DEFAULT NULL", + /* 51 */ "ccons ::= NULL onconf", + /* 52 */ "ccons ::= NOT NULL onconf", + /* 53 */ "ccons ::= PRIMARY KEY sortorder onconf", + /* 54 */ "ccons ::= UNIQUE onconf", + /* 55 */ "ccons ::= CHECK LP expr RP onconf", + /* 56 */ "ccons ::= REFERENCES nm idxlist_opt refargs", + /* 57 */ "ccons ::= defer_subclause", + /* 58 */ "ccons ::= COLLATE id", + /* 59 */ "refargs ::=", + /* 60 */ "refargs ::= refargs refarg", + /* 61 */ "refarg ::= MATCH nm", + /* 62 */ "refarg ::= ON DELETE refact", + /* 63 */ "refarg ::= ON UPDATE refact", + /* 64 */ "refarg ::= ON INSERT refact", + /* 65 */ "refact ::= SET NULL", + /* 66 */ "refact ::= SET DEFAULT", + /* 67 */ "refact ::= CASCADE", + /* 68 */ "refact ::= RESTRICT", + /* 69 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", + /* 70 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", + /* 71 */ "init_deferred_pred_opt ::=", + /* 72 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", + /* 73 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", + /* 74 */ "conslist_opt ::=", + /* 75 */ "conslist_opt ::= COMMA conslist", + /* 76 */ "conslist ::= conslist COMMA tcons", + /* 77 */ "conslist ::= conslist tcons", + /* 78 */ "conslist ::= tcons", + /* 79 */ "tcons ::= CONSTRAINT nm", + /* 80 */ "tcons ::= PRIMARY KEY LP idxlist RP onconf", + /* 81 */ "tcons ::= UNIQUE LP idxlist RP onconf", + /* 82 */ "tcons ::= CHECK expr onconf", + /* 83 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt", + /* 84 */ "defer_subclause_opt ::=", + /* 85 */ "defer_subclause_opt ::= defer_subclause", + /* 86 */ "onconf ::=", + /* 87 */ "onconf ::= ON CONFLICT resolvetype", + /* 88 */ "orconf ::=", + /* 89 */ "orconf ::= OR resolvetype", + /* 90 */ "resolvetype ::= raisetype", + /* 91 */ "resolvetype ::= IGNORE", + /* 92 */ "resolvetype ::= REPLACE", + /* 93 */ "cmd ::= DROP TABLE fullname", + /* 94 */ "cmd ::= CREATE temp VIEW nm dbnm AS select", + /* 95 */ "cmd ::= DROP VIEW fullname", + /* 96 */ "cmd ::= select", + /* 97 */ "select ::= oneselect", + /* 98 */ "select ::= select multiselect_op oneselect", + /* 99 */ "multiselect_op ::= UNION", + /* 100 */ "multiselect_op ::= UNION ALL", + /* 101 */ "multiselect_op ::= INTERSECT", + /* 102 */ "multiselect_op ::= EXCEPT", + /* 103 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", + /* 104 */ "distinct ::= DISTINCT", + /* 105 */ "distinct ::= ALL", + /* 106 */ "distinct ::=", + /* 107 */ "sclp ::= selcollist COMMA", + /* 108 */ "sclp ::=", + /* 109 */ "selcollist ::= sclp expr as", + /* 110 */ "selcollist ::= sclp STAR", + /* 111 */ "selcollist ::= sclp nm DOT STAR", + /* 112 */ "as ::= AS nm", + /* 113 */ "as ::= ids", + /* 114 */ "as ::=", + /* 115 */ "from ::=", + /* 116 */ "from ::= FROM seltablist", + /* 117 */ "stl_prefix ::= seltablist joinop", + /* 118 */ "stl_prefix ::=", + /* 119 */ "seltablist ::= stl_prefix nm dbnm as on_opt using_opt", + /* 120 */ "seltablist ::= stl_prefix LP seltablist_paren RP as on_opt using_opt", + /* 121 */ "seltablist_paren ::= select", + /* 122 */ "seltablist_paren ::= seltablist", + /* 123 */ "dbnm ::=", + /* 124 */ "dbnm ::= DOT nm", + /* 125 */ "fullname ::= nm dbnm", + /* 126 */ "joinop ::= COMMA", + /* 127 */ "joinop ::= JOIN", + /* 128 */ "joinop ::= JOIN_KW JOIN", + /* 129 */ "joinop ::= JOIN_KW nm JOIN", + /* 130 */ "joinop ::= JOIN_KW nm nm JOIN", + /* 131 */ "on_opt ::= ON expr", + /* 132 */ "on_opt ::=", + /* 133 */ "using_opt ::= USING LP inscollist RP", + /* 134 */ "using_opt ::=", + /* 135 */ "orderby_opt ::=", + /* 136 */ "orderby_opt ::= ORDER BY sortlist", + /* 137 */ "sortlist ::= sortlist COMMA sortitem collate sortorder", + /* 138 */ "sortlist ::= sortitem collate sortorder", + /* 139 */ "sortitem ::= expr", + /* 140 */ "sortorder ::= ASC", + /* 141 */ "sortorder ::= DESC", + /* 142 */ "sortorder ::=", + /* 143 */ "collate ::=", + /* 144 */ "collate ::= COLLATE id", + /* 145 */ "groupby_opt ::=", + /* 146 */ "groupby_opt ::= GROUP BY exprlist", + /* 147 */ "having_opt ::=", + /* 148 */ "having_opt ::= HAVING expr", + /* 149 */ "limit_opt ::=", + /* 150 */ "limit_opt ::= LIMIT signed", + /* 151 */ "limit_opt ::= LIMIT signed OFFSET signed", + /* 152 */ "limit_opt ::= LIMIT signed COMMA signed", + /* 153 */ "cmd ::= DELETE FROM fullname where_opt", + /* 154 */ "where_opt ::=", + /* 155 */ "where_opt ::= WHERE expr", + /* 156 */ "cmd ::= UPDATE orconf fullname SET setlist where_opt", + /* 157 */ "setlist ::= setlist COMMA nm EQ expr", + /* 158 */ "setlist ::= nm EQ expr", + /* 159 */ "cmd ::= insert_cmd INTO fullname inscollist_opt VALUES LP itemlist RP", + /* 160 */ "cmd ::= insert_cmd INTO fullname inscollist_opt select", + /* 161 */ "insert_cmd ::= INSERT orconf", + /* 162 */ "insert_cmd ::= REPLACE", + /* 163 */ "itemlist ::= itemlist COMMA expr", + /* 164 */ "itemlist ::= expr", + /* 165 */ "inscollist_opt ::=", + /* 166 */ "inscollist_opt ::= LP inscollist RP", + /* 167 */ "inscollist ::= inscollist COMMA nm", + /* 168 */ "inscollist ::= nm", + /* 169 */ "expr ::= LP expr RP", + /* 170 */ "expr ::= NULL", + /* 171 */ "expr ::= ID", + /* 172 */ "expr ::= JOIN_KW", + /* 173 */ "expr ::= nm DOT nm", + /* 174 */ "expr ::= nm DOT nm DOT nm", + /* 175 */ "expr ::= INTEGER", + /* 176 */ "expr ::= FLOAT", + /* 177 */ "expr ::= STRING", + /* 178 */ "expr ::= BLOB", + /* 179 */ "expr ::= VARIABLE", + /* 180 */ "expr ::= ID LP exprlist RP", + /* 181 */ "expr ::= ID LP STAR RP", + /* 182 */ "expr ::= expr AND expr", + /* 183 */ "expr ::= expr OR expr", + /* 184 */ "expr ::= expr LT expr", + /* 185 */ "expr ::= expr GT expr", + /* 186 */ "expr ::= expr LE expr", + /* 187 */ "expr ::= expr GE expr", + /* 188 */ "expr ::= expr NE expr", + /* 189 */ "expr ::= expr EQ expr", + /* 190 */ "expr ::= expr BITAND expr", + /* 191 */ "expr ::= expr BITOR expr", + /* 192 */ "expr ::= expr LSHIFT expr", + /* 193 */ "expr ::= expr RSHIFT expr", + /* 194 */ "expr ::= expr PLUS expr", + /* 195 */ "expr ::= expr MINUS expr", + /* 196 */ "expr ::= expr STAR expr", + /* 197 */ "expr ::= expr SLASH expr", + /* 198 */ "expr ::= expr REM expr", + /* 199 */ "expr ::= expr CONCAT expr", + /* 200 */ "likeop ::= LIKE", + /* 201 */ "likeop ::= GLOB", + /* 202 */ "likeop ::= NOT LIKE", + /* 203 */ "likeop ::= NOT GLOB", + /* 204 */ "expr ::= expr likeop expr", + /* 205 */ "expr ::= expr ISNULL", + /* 206 */ "expr ::= expr IS NULL", + /* 207 */ "expr ::= expr NOTNULL", + /* 208 */ "expr ::= expr NOT NULL", + /* 209 */ "expr ::= expr IS NOT NULL", + /* 210 */ "expr ::= NOT expr", + /* 211 */ "expr ::= BITNOT expr", + /* 212 */ "expr ::= MINUS expr", + /* 213 */ "expr ::= PLUS expr", + /* 214 */ "expr ::= LP select RP", + /* 215 */ "between_op ::= BETWEEN", + /* 216 */ "between_op ::= NOT BETWEEN", + /* 217 */ "expr ::= expr between_op expr AND expr", + /* 218 */ "in_op ::= IN", + /* 219 */ "in_op ::= NOT IN", + /* 220 */ "expr ::= expr in_op LP exprlist RP", + /* 221 */ "expr ::= expr in_op LP select RP", + /* 222 */ "expr ::= expr in_op nm dbnm", + /* 223 */ "expr ::= CASE case_operand case_exprlist case_else END", + /* 224 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", + /* 225 */ "case_exprlist ::= WHEN expr THEN expr", + /* 226 */ "case_else ::= ELSE expr", + /* 227 */ "case_else ::=", + /* 228 */ "case_operand ::= expr", + /* 229 */ "case_operand ::=", + /* 230 */ "exprlist ::= exprlist COMMA expritem", + /* 231 */ "exprlist ::= expritem", + /* 232 */ "expritem ::= expr", + /* 233 */ "expritem ::=", + /* 234 */ "cmd ::= CREATE uniqueflag INDEX nm dbnm ON fullname LP idxlist RP onconf", + /* 235 */ "uniqueflag ::= UNIQUE", + /* 236 */ "uniqueflag ::=", + /* 237 */ "idxlist_opt ::=", + /* 238 */ "idxlist_opt ::= LP idxlist RP", + /* 239 */ "idxlist ::= idxlist COMMA idxitem collate sortorder", + /* 240 */ "idxlist ::= idxitem collate sortorder", + /* 241 */ "idxitem ::= nm", + /* 242 */ "cmd ::= DROP INDEX fullname", + /* 243 */ "cmd ::= VACUUM", + /* 244 */ "cmd ::= VACUUM nm", + /* 245 */ "cmd ::= PRAGMA nm dbnm EQ nm", + /* 246 */ "cmd ::= PRAGMA nm dbnm EQ ON", + /* 247 */ "cmd ::= PRAGMA nm dbnm EQ plus_num", + /* 248 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", + /* 249 */ "cmd ::= PRAGMA nm dbnm LP nm RP", + /* 250 */ "cmd ::= PRAGMA nm dbnm", + /* 251 */ "plus_num ::= plus_opt number", + /* 252 */ "minus_num ::= MINUS number", + /* 253 */ "number ::= INTEGER", + /* 254 */ "number ::= FLOAT", + /* 255 */ "plus_opt ::= PLUS", + /* 256 */ "plus_opt ::=", + /* 257 */ "cmd ::= CREATE trigger_decl BEGIN trigger_cmd_list END", + /* 258 */ "trigger_decl ::= temp TRIGGER nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", + /* 259 */ "trigger_time ::= BEFORE", + /* 260 */ "trigger_time ::= AFTER", + /* 261 */ "trigger_time ::= INSTEAD OF", + /* 262 */ "trigger_time ::=", + /* 263 */ "trigger_event ::= DELETE", + /* 264 */ "trigger_event ::= INSERT", + /* 265 */ "trigger_event ::= UPDATE", + /* 266 */ "trigger_event ::= UPDATE OF inscollist", + /* 267 */ "foreach_clause ::=", + /* 268 */ "foreach_clause ::= FOR EACH ROW", + /* 269 */ "foreach_clause ::= FOR EACH STATEMENT", + /* 270 */ "when_clause ::=", + /* 271 */ "when_clause ::= WHEN expr", + /* 272 */ "trigger_cmd_list ::= trigger_cmd SEMI trigger_cmd_list", + /* 273 */ "trigger_cmd_list ::=", + /* 274 */ "trigger_cmd ::= UPDATE orconf nm SET setlist where_opt", + /* 275 */ "trigger_cmd ::= insert_cmd INTO nm inscollist_opt VALUES LP itemlist RP", + /* 276 */ "trigger_cmd ::= insert_cmd INTO nm inscollist_opt select", + /* 277 */ "trigger_cmd ::= DELETE FROM nm where_opt", + /* 278 */ "trigger_cmd ::= select", + /* 279 */ "expr ::= RAISE LP IGNORE RP", + /* 280 */ "expr ::= RAISE LP raisetype COMMA nm RP", + /* 281 */ "raisetype ::= ROLLBACK", + /* 282 */ "raisetype ::= ABORT", + /* 283 */ "raisetype ::= FAIL", + /* 284 */ "cmd ::= DROP TRIGGER fullname", + /* 285 */ "cmd ::= ATTACH database_kw_opt ids AS nm key_opt", + /* 286 */ "key_opt ::=", + /* 287 */ "key_opt ::= KEY ids", + /* 288 */ "key_opt ::= KEY BLOB", + /* 289 */ "database_kw_opt ::= DATABASE", + /* 290 */ "database_kw_opt ::=", + /* 291 */ "cmd ::= DETACH database_kw_opt nm", +}; +#endif /* NDEBUG */ + +/* +** This function returns the symbolic name associated with a token +** value. +*/ +const char *sqlite3ParserTokenName(int tokenType){ +#ifndef NDEBUG + if( tokenType>0 && tokenType<(sizeof(yyTokenName)/sizeof(yyTokenName[0])) ){ + return yyTokenName[tokenType]; + }else{ + return "Unknown"; + } +#else + return ""; +#endif +} + +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to sqlite3Parser and sqlite3ParserFree. +*/ +void *sqlite3ParserAlloc(void *(*mallocProc)(size_t)){ + yyParser *pParser; + pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); + if( pParser ){ + pParser->yyidx = -1; + } + return pParser; +} + +/* The following function deletes the value associated with a +** symbol. The symbol can be either a terminal or nonterminal. +** "yymajor" is the symbol code, and "yypminor" is a pointer to +** the value. +*/ +static void yy_destructor(YYCODETYPE yymajor, YYMINORTYPE *yypminor){ + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ + case 146: + case 176: + case 193: +#line 303 "parse.y" +{sqlite3SelectDelete((yypminor->yy107));} +#line 1236 "parse.c" + break; + case 161: + case 181: + case 183: + case 191: + case 197: + case 210: +#line 552 "parse.y" +{sqlite3ExprDelete((yypminor->yy258));} +#line 1246 "parse.c" + break; + case 162: + case 170: + case 179: + case 182: + case 184: + case 186: + case 196: + case 199: + case 200: + case 203: + case 208: +#line 744 "parse.y" +{sqlite3ExprListDelete((yypminor->yy210));} +#line 1261 "parse.c" + break; + case 175: + case 180: + case 188: + case 189: +#line 428 "parse.y" +{sqlite3SrcListDelete((yypminor->yy259));} +#line 1269 "parse.c" + break; + case 192: + case 195: + case 202: +#line 446 "parse.y" +{sqlite3IdListDelete((yypminor->yy272));} +#line 1276 "parse.c" + break; + case 216: + case 221: +#line 833 "parse.y" +{sqlite3DeleteTriggerStep((yypminor->yy91));} +#line 1282 "parse.c" + break; + case 218: +#line 817 "parse.y" +{sqlite3IdListDelete((yypminor->yy146).b);} +#line 1287 "parse.c" + break; + default: break; /* If no destructor action specified: do nothing */ + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +** +** Return the major token number for the symbol popped. +*/ +static int yy_pop_parser_stack(yyParser *pParser){ + YYCODETYPE yymajor; + yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; + + if( pParser->yyidx<0 ) return 0; +#ifndef NDEBUG + if( yyTraceFILE && pParser->yyidx>=0 ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yymajor = yytos->major; + yy_destructor( yymajor, &yytos->minor); + pParser->yyidx--; + return yymajor; +} + +/* +** Deallocate and destroy a parser. Destructors are all called for +** all stack elements before shutting the parser down. +** +** Inputs: +**
      +**
    • A pointer to the parser. This should be a pointer +** obtained from sqlite3ParserAlloc. +**
    • A pointer to a function used to reclaim memory obtained +** from malloc. +**
    +*/ +void sqlite3ParserFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ + yyParser *pParser = (yyParser*)p; + if( pParser==0 ) return; + while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); + (*freeProc)((void*)pParser); +} + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_shift_action( + yyParser *pParser, /* The parser */ + int iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + + /* if( pParser->yyidx<0 ) return YY_NO_ACTION; */ + i = yy_shift_ofst[stateno]; + if( i==YY_SHIFT_USE_DFLT ){ + return yy_default[stateno]; + } + if( iLookAhead==YYNOCODE ){ + return YY_NO_ACTION; + } + i += iLookAhead; + if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){ +#ifdef YYFALLBACK + int iFallback; /* Fallback token */ + if( iLookAhead %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + return yy_find_shift_action(pParser, iFallback); + } +#endif + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_reduce_action( + yyParser *pParser, /* The parser */ + int iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + + i = yy_reduce_ofst[stateno]; + if( i==YY_REDUCE_USE_DFLT ){ + return yy_default[stateno]; + } + if( iLookAhead==YYNOCODE ){ + return YY_NO_ACTION; + } + i += iLookAhead; + if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + int yyNewState, /* The new state to shift in */ + int yyMajor, /* The major token to shift in */ + YYMINORTYPE *yypMinor /* Pointer ot the minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yyidx++; + if( yypParser->yyidx>=YYSTACKDEPTH ){ + sqlite3ParserARG_FETCH; + yypParser->yyidx--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ + sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument var */ + return; + } + yytos = &yypParser->yystack[yypParser->yyidx]; + yytos->stateno = yyNewState; + yytos->major = yyMajor; + yytos->minor = *yypMinor; +#ifndef NDEBUG + if( yyTraceFILE && yypParser->yyidx>0 ){ + int i; + fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); + fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"\n"); + } +#endif +} + +/* The following table contains information about every rule that +** is used during the reduce. +*/ +static const struct { + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + unsigned char nrhs; /* Number of right-hand side symbols in the rule */ +} yyRuleInfo[] = { + { 131, 1 }, + { 132, 2 }, + { 132, 1 }, + { 133, 3 }, + { 133, 1 }, + { 135, 1 }, + { 134, 1 }, + { 134, 0 }, + { 136, 3 }, + { 138, 0 }, + { 138, 1 }, + { 138, 2 }, + { 137, 0 }, + { 137, 1 }, + { 137, 1 }, + { 137, 1 }, + { 136, 2 }, + { 136, 2 }, + { 136, 2 }, + { 136, 2 }, + { 140, 5 }, + { 142, 1 }, + { 142, 0 }, + { 141, 4 }, + { 141, 2 }, + { 144, 3 }, + { 144, 1 }, + { 147, 3 }, + { 148, 1 }, + { 151, 1 }, + { 152, 1 }, + { 152, 1 }, + { 139, 1 }, + { 139, 1 }, + { 139, 1 }, + { 149, 0 }, + { 149, 1 }, + { 149, 4 }, + { 149, 6 }, + { 153, 1 }, + { 153, 2 }, + { 154, 1 }, + { 154, 1 }, + { 150, 2 }, + { 150, 0 }, + { 157, 3 }, + { 157, 1 }, + { 157, 2 }, + { 157, 2 }, + { 157, 2 }, + { 157, 2 }, + { 158, 2 }, + { 158, 3 }, + { 158, 4 }, + { 158, 2 }, + { 158, 5 }, + { 158, 4 }, + { 158, 1 }, + { 158, 2 }, + { 163, 0 }, + { 163, 2 }, + { 165, 2 }, + { 165, 3 }, + { 165, 3 }, + { 165, 3 }, + { 166, 2 }, + { 166, 2 }, + { 166, 1 }, + { 166, 1 }, + { 164, 3 }, + { 164, 2 }, + { 167, 0 }, + { 167, 2 }, + { 167, 2 }, + { 145, 0 }, + { 145, 2 }, + { 168, 3 }, + { 168, 2 }, + { 168, 1 }, + { 169, 2 }, + { 169, 6 }, + { 169, 5 }, + { 169, 3 }, + { 169, 10 }, + { 171, 0 }, + { 171, 1 }, + { 159, 0 }, + { 159, 3 }, + { 172, 0 }, + { 172, 2 }, + { 173, 1 }, + { 173, 1 }, + { 173, 1 }, + { 136, 3 }, + { 136, 7 }, + { 136, 3 }, + { 136, 1 }, + { 146, 1 }, + { 146, 3 }, + { 177, 1 }, + { 177, 2 }, + { 177, 1 }, + { 177, 1 }, + { 176, 9 }, + { 178, 1 }, + { 178, 1 }, + { 178, 0 }, + { 186, 2 }, + { 186, 0 }, + { 179, 3 }, + { 179, 2 }, + { 179, 4 }, + { 187, 2 }, + { 187, 1 }, + { 187, 0 }, + { 180, 0 }, + { 180, 2 }, + { 189, 2 }, + { 189, 0 }, + { 188, 6 }, + { 188, 7 }, + { 193, 1 }, + { 193, 1 }, + { 143, 0 }, + { 143, 2 }, + { 175, 2 }, + { 190, 1 }, + { 190, 1 }, + { 190, 2 }, + { 190, 3 }, + { 190, 4 }, + { 191, 2 }, + { 191, 0 }, + { 192, 4 }, + { 192, 0 }, + { 184, 0 }, + { 184, 3 }, + { 196, 5 }, + { 196, 3 }, + { 197, 1 }, + { 160, 1 }, + { 160, 1 }, + { 160, 0 }, + { 198, 0 }, + { 198, 2 }, + { 182, 0 }, + { 182, 3 }, + { 183, 0 }, + { 183, 2 }, + { 185, 0 }, + { 185, 2 }, + { 185, 4 }, + { 185, 4 }, + { 136, 4 }, + { 181, 0 }, + { 181, 2 }, + { 136, 6 }, + { 200, 5 }, + { 200, 3 }, + { 136, 8 }, + { 136, 5 }, + { 201, 2 }, + { 201, 1 }, + { 203, 3 }, + { 203, 1 }, + { 202, 0 }, + { 202, 3 }, + { 195, 3 }, + { 195, 1 }, + { 161, 3 }, + { 161, 1 }, + { 161, 1 }, + { 161, 1 }, + { 161, 3 }, + { 161, 5 }, + { 161, 1 }, + { 161, 1 }, + { 161, 1 }, + { 161, 1 }, + { 161, 1 }, + { 161, 4 }, + { 161, 4 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 161, 3 }, + { 204, 1 }, + { 204, 1 }, + { 204, 2 }, + { 204, 2 }, + { 161, 3 }, + { 161, 2 }, + { 161, 3 }, + { 161, 2 }, + { 161, 3 }, + { 161, 4 }, + { 161, 2 }, + { 161, 2 }, + { 161, 2 }, + { 161, 2 }, + { 161, 3 }, + { 205, 1 }, + { 205, 2 }, + { 161, 5 }, + { 206, 1 }, + { 206, 2 }, + { 161, 5 }, + { 161, 5 }, + { 161, 4 }, + { 161, 5 }, + { 208, 5 }, + { 208, 4 }, + { 209, 2 }, + { 209, 0 }, + { 207, 1 }, + { 207, 0 }, + { 199, 3 }, + { 199, 1 }, + { 210, 1 }, + { 210, 0 }, + { 136, 11 }, + { 211, 1 }, + { 211, 0 }, + { 162, 0 }, + { 162, 3 }, + { 170, 5 }, + { 170, 3 }, + { 212, 1 }, + { 136, 3 }, + { 136, 1 }, + { 136, 2 }, + { 136, 5 }, + { 136, 5 }, + { 136, 5 }, + { 136, 5 }, + { 136, 6 }, + { 136, 3 }, + { 155, 2 }, + { 156, 2 }, + { 214, 1 }, + { 214, 1 }, + { 213, 1 }, + { 213, 0 }, + { 136, 5 }, + { 215, 10 }, + { 217, 1 }, + { 217, 1 }, + { 217, 2 }, + { 217, 0 }, + { 218, 1 }, + { 218, 1 }, + { 218, 1 }, + { 218, 3 }, + { 219, 0 }, + { 219, 3 }, + { 219, 3 }, + { 220, 0 }, + { 220, 2 }, + { 216, 3 }, + { 216, 0 }, + { 221, 6 }, + { 221, 8 }, + { 221, 5 }, + { 221, 4 }, + { 221, 1 }, + { 161, 4 }, + { 161, 6 }, + { 174, 1 }, + { 174, 1 }, + { 174, 1 }, + { 136, 3 }, + { 136, 6 }, + { 223, 0 }, + { 223, 2 }, + { 223, 2 }, + { 222, 1 }, + { 222, 0 }, + { 136, 3 }, +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +*/ +static void yy_reduce( + yyParser *yypParser, /* The parser */ + int yyruleno /* Number of the rule by which to reduce */ +){ + int yygoto; /* The next state */ + int yyact; /* The next action */ + YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + sqlite3ParserARG_FETCH; + yymsp = &yypParser->yystack[yypParser->yyidx]; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno>=0 + && yyruleno + ** { ... } // User supplied code + ** #line + ** break; + */ + case 5: +#line 86 "parse.y" +{ sqlite3FinishCoding(pParse); } +#line 1794 "parse.c" + break; + case 6: +#line 87 "parse.y" +{ sqlite3BeginParse(pParse, 1); } +#line 1799 "parse.c" + break; + case 7: +#line 88 "parse.y" +{ sqlite3BeginParse(pParse, 0); } +#line 1804 "parse.c" + break; + case 8: +#line 93 "parse.y" +{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy284);} +#line 1809 "parse.c" + break; + case 12: +#line 98 "parse.y" +{yygotominor.yy284 = TK_DEFERRED;} +#line 1814 "parse.c" + break; + case 13: + case 14: + case 15: + case 99: + case 101: + case 102: +#line 99 "parse.y" +{yygotominor.yy284 = yymsp[0].major;} +#line 1824 "parse.c" + break; + case 16: + case 17: +#line 102 "parse.y" +{sqlite3CommitTransaction(pParse);} +#line 1830 "parse.c" + break; + case 18: +#line 104 "parse.y" +{sqlite3RollbackTransaction(pParse);} +#line 1835 "parse.c" + break; + case 20: +#line 109 "parse.y" +{ + sqlite3StartTable(pParse,&yymsp[-4].minor.yy0,&yymsp[-1].minor.yy98,&yymsp[0].minor.yy98,yymsp[-3].minor.yy284,0); +} +#line 1842 "parse.c" + break; + case 21: + case 72: + case 104: + case 216: + case 219: +#line 113 "parse.y" +{yygotominor.yy284 = 1;} +#line 1851 "parse.c" + break; + case 22: + case 71: + case 73: + case 84: + case 105: + case 106: + case 215: + case 218: +#line 114 "parse.y" +{yygotominor.yy284 = 0;} +#line 1863 "parse.c" + break; + case 23: +#line 115 "parse.y" +{ + sqlite3EndTable(pParse,&yymsp[0].minor.yy0,0); +} +#line 1870 "parse.c" + break; + case 24: +#line 118 "parse.y" +{ + sqlite3EndTable(pParse,0,yymsp[0].minor.yy107); + sqlite3SelectDelete(yymsp[0].minor.yy107); +} +#line 1878 "parse.c" + break; + case 28: +#line 130 "parse.y" +{sqlite3AddColumn(pParse,&yymsp[0].minor.yy98);} +#line 1883 "parse.c" + break; + case 29: + case 30: + case 31: + case 32: + case 33: + case 34: + case 253: + case 254: +#line 136 "parse.y" +{yygotominor.yy98 = yymsp[0].minor.yy0;} +#line 1895 "parse.c" + break; + case 36: +#line 185 "parse.y" +{sqlite3AddColumnType(pParse,&yymsp[0].minor.yy98,&yymsp[0].minor.yy98);} +#line 1900 "parse.c" + break; + case 37: +#line 186 "parse.y" +{sqlite3AddColumnType(pParse,&yymsp[-3].minor.yy98,&yymsp[0].minor.yy0);} +#line 1905 "parse.c" + break; + case 38: +#line 188 "parse.y" +{sqlite3AddColumnType(pParse,&yymsp[-5].minor.yy98,&yymsp[0].minor.yy0);} +#line 1910 "parse.c" + break; + case 39: + case 112: + case 113: + case 124: + case 144: + case 241: + case 251: + case 252: +#line 190 "parse.y" +{yygotominor.yy98 = yymsp[0].minor.yy98;} +#line 1922 "parse.c" + break; + case 40: +#line 191 "parse.y" +{yygotominor.yy98.z=yymsp[-1].minor.yy98.z; yygotominor.yy98.n=yymsp[0].minor.yy98.n+(yymsp[0].minor.yy98.z-yymsp[-1].minor.yy98.z);} +#line 1927 "parse.c" + break; + case 41: +#line 193 "parse.y" +{ yygotominor.yy284 = atoi(yymsp[0].minor.yy98.z); } +#line 1932 "parse.c" + break; + case 42: +#line 194 "parse.y" +{ yygotominor.yy284 = -atoi(yymsp[0].minor.yy98.z); } +#line 1937 "parse.c" + break; + case 47: + case 48: +#line 199 "parse.y" +{sqlite3AddDefaultValue(pParse,&yymsp[0].minor.yy98,0);} +#line 1943 "parse.c" + break; + case 49: +#line 201 "parse.y" +{sqlite3AddDefaultValue(pParse,&yymsp[0].minor.yy98,1);} +#line 1948 "parse.c" + break; + case 52: +#line 208 "parse.y" +{sqlite3AddNotNull(pParse, yymsp[0].minor.yy284);} +#line 1953 "parse.c" + break; + case 53: +#line 209 "parse.y" +{sqlite3AddPrimaryKey(pParse,0,yymsp[0].minor.yy284);} +#line 1958 "parse.c" + break; + case 54: +#line 210 "parse.y" +{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy284,0,0);} +#line 1963 "parse.c" + break; + case 56: +#line 213 "parse.y" +{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy98,yymsp[-1].minor.yy210,yymsp[0].minor.yy284);} +#line 1968 "parse.c" + break; + case 57: +#line 214 "parse.y" +{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy284);} +#line 1973 "parse.c" + break; + case 58: +#line 215 "parse.y" +{sqlite3AddCollateType(pParse, yymsp[0].minor.yy98.z, yymsp[0].minor.yy98.n);} +#line 1978 "parse.c" + break; + case 59: +#line 223 "parse.y" +{ yygotominor.yy284 = OE_Restrict * 0x010101; } +#line 1983 "parse.c" + break; + case 60: +#line 224 "parse.y" +{ yygotominor.yy284 = (yymsp[-1].minor.yy284 & yymsp[0].minor.yy47.mask) | yymsp[0].minor.yy47.value; } +#line 1988 "parse.c" + break; + case 61: +#line 226 "parse.y" +{ yygotominor.yy47.value = 0; yygotominor.yy47.mask = 0x000000; } +#line 1993 "parse.c" + break; + case 62: +#line 227 "parse.y" +{ yygotominor.yy47.value = yymsp[0].minor.yy284; yygotominor.yy47.mask = 0x0000ff; } +#line 1998 "parse.c" + break; + case 63: +#line 228 "parse.y" +{ yygotominor.yy47.value = yymsp[0].minor.yy284<<8; yygotominor.yy47.mask = 0x00ff00; } +#line 2003 "parse.c" + break; + case 64: +#line 229 "parse.y" +{ yygotominor.yy47.value = yymsp[0].minor.yy284<<16; yygotominor.yy47.mask = 0xff0000; } +#line 2008 "parse.c" + break; + case 65: +#line 231 "parse.y" +{ yygotominor.yy284 = OE_SetNull; } +#line 2013 "parse.c" + break; + case 66: +#line 232 "parse.y" +{ yygotominor.yy284 = OE_SetDflt; } +#line 2018 "parse.c" + break; + case 67: +#line 233 "parse.y" +{ yygotominor.yy284 = OE_Cascade; } +#line 2023 "parse.c" + break; + case 68: +#line 234 "parse.y" +{ yygotominor.yy284 = OE_Restrict; } +#line 2028 "parse.c" + break; + case 69: + case 70: + case 85: + case 87: + case 89: + case 90: + case 161: +#line 236 "parse.y" +{yygotominor.yy284 = yymsp[0].minor.yy284;} +#line 2039 "parse.c" + break; + case 80: +#line 253 "parse.y" +{sqlite3AddPrimaryKey(pParse,yymsp[-2].minor.yy210,yymsp[0].minor.yy284);} +#line 2044 "parse.c" + break; + case 81: +#line 255 "parse.y" +{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy210,yymsp[0].minor.yy284,0,0);} +#line 2049 "parse.c" + break; + case 83: +#line 258 "parse.y" +{ + sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy210, &yymsp[-3].minor.yy98, yymsp[-2].minor.yy210, yymsp[-1].minor.yy284); + sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy284); +} +#line 2057 "parse.c" + break; + case 86: + case 88: +#line 272 "parse.y" +{yygotominor.yy284 = OE_Default;} +#line 2063 "parse.c" + break; + case 91: +#line 277 "parse.y" +{yygotominor.yy284 = OE_Ignore;} +#line 2068 "parse.c" + break; + case 92: + case 162: +#line 278 "parse.y" +{yygotominor.yy284 = OE_Replace;} +#line 2074 "parse.c" + break; + case 93: +#line 282 "parse.y" +{ + sqlite3DropTable(pParse, yymsp[0].minor.yy259, 0); +} +#line 2081 "parse.c" + break; + case 94: +#line 288 "parse.y" +{ + sqlite3CreateView(pParse, &yymsp[-6].minor.yy0, &yymsp[-3].minor.yy98, &yymsp[-2].minor.yy98, yymsp[0].minor.yy107, yymsp[-5].minor.yy284); +} +#line 2088 "parse.c" + break; + case 95: +#line 291 "parse.y" +{ + sqlite3DropTable(pParse, yymsp[0].minor.yy259, 1); +} +#line 2095 "parse.c" + break; + case 96: +#line 297 "parse.y" +{ + sqlite3Select(pParse, yymsp[0].minor.yy107, SRT_Callback, 0, 0, 0, 0, 0); + sqlite3SelectDelete(yymsp[0].minor.yy107); +} +#line 2103 "parse.c" + break; + case 97: + case 121: +#line 307 "parse.y" +{yygotominor.yy107 = yymsp[0].minor.yy107;} +#line 2109 "parse.c" + break; + case 98: +#line 308 "parse.y" +{ + if( yymsp[0].minor.yy107 ){ + yymsp[0].minor.yy107->op = yymsp[-1].minor.yy284; + yymsp[0].minor.yy107->pPrior = yymsp[-2].minor.yy107; + } + yygotominor.yy107 = yymsp[0].minor.yy107; +} +#line 2120 "parse.c" + break; + case 100: +#line 317 "parse.y" +{yygotominor.yy284 = TK_ALL;} +#line 2125 "parse.c" + break; + case 103: +#line 321 "parse.y" +{ + yygotominor.yy107 = sqlite3SelectNew(yymsp[-6].minor.yy210,yymsp[-5].minor.yy259,yymsp[-4].minor.yy258,yymsp[-3].minor.yy210,yymsp[-2].minor.yy258,yymsp[-1].minor.yy210,yymsp[-7].minor.yy284,yymsp[0].minor.yy404.limit,yymsp[0].minor.yy404.offset); +} +#line 2132 "parse.c" + break; + case 107: + case 238: +#line 342 "parse.y" +{yygotominor.yy210 = yymsp[-1].minor.yy210;} +#line 2138 "parse.c" + break; + case 108: + case 135: + case 145: + case 237: +#line 343 "parse.y" +{yygotominor.yy210 = 0;} +#line 2146 "parse.c" + break; + case 109: +#line 344 "parse.y" +{ + yygotominor.yy210 = sqlite3ExprListAppend(yymsp[-2].minor.yy210,yymsp[-1].minor.yy258,yymsp[0].minor.yy98.n?&yymsp[0].minor.yy98:0); +} +#line 2153 "parse.c" + break; + case 110: +#line 347 "parse.y" +{ + yygotominor.yy210 = sqlite3ExprListAppend(yymsp[-1].minor.yy210, sqlite3Expr(TK_ALL, 0, 0, 0), 0); +} +#line 2160 "parse.c" + break; + case 111: +#line 350 "parse.y" +{ + Expr *pRight = sqlite3Expr(TK_ALL, 0, 0, 0); + Expr *pLeft = sqlite3Expr(TK_ID, 0, 0, &yymsp[-2].minor.yy98); + yygotominor.yy210 = sqlite3ExprListAppend(yymsp[-3].minor.yy210, sqlite3Expr(TK_DOT, pLeft, pRight, 0), 0); +} +#line 2169 "parse.c" + break; + case 114: +#line 362 "parse.y" +{yygotominor.yy98.n = 0;} +#line 2174 "parse.c" + break; + case 115: +#line 374 "parse.y" +{yygotominor.yy259 = sqliteMalloc(sizeof(*yygotominor.yy259));} +#line 2179 "parse.c" + break; + case 116: +#line 375 "parse.y" +{yygotominor.yy259 = yymsp[0].minor.yy259;} +#line 2184 "parse.c" + break; + case 117: +#line 380 "parse.y" +{ + yygotominor.yy259 = yymsp[-1].minor.yy259; + if( yygotominor.yy259 && yygotominor.yy259->nSrc>0 ) yygotominor.yy259->a[yygotominor.yy259->nSrc-1].jointype = yymsp[0].minor.yy284; +} +#line 2192 "parse.c" + break; + case 118: +#line 384 "parse.y" +{yygotominor.yy259 = 0;} +#line 2197 "parse.c" + break; + case 119: +#line 385 "parse.y" +{ + yygotominor.yy259 = sqlite3SrcListAppend(yymsp[-5].minor.yy259,&yymsp[-4].minor.yy98,&yymsp[-3].minor.yy98); + if( yymsp[-2].minor.yy98.n ) sqlite3SrcListAddAlias(yygotominor.yy259,&yymsp[-2].minor.yy98); + if( yymsp[-1].minor.yy258 ){ + if( yygotominor.yy259 && yygotominor.yy259->nSrc>1 ){ yygotominor.yy259->a[yygotominor.yy259->nSrc-2].pOn = yymsp[-1].minor.yy258; } + else { sqlite3ExprDelete(yymsp[-1].minor.yy258); } + } + if( yymsp[0].minor.yy272 ){ + if( yygotominor.yy259 && yygotominor.yy259->nSrc>1 ){ yygotominor.yy259->a[yygotominor.yy259->nSrc-2].pUsing = yymsp[0].minor.yy272; } + else { sqlite3IdListDelete(yymsp[0].minor.yy272); } + } +} +#line 2213 "parse.c" + break; + case 120: +#line 398 "parse.y" +{ + yygotominor.yy259 = sqlite3SrcListAppend(yymsp[-6].minor.yy259,0,0); + yygotominor.yy259->a[yygotominor.yy259->nSrc-1].pSelect = yymsp[-4].minor.yy107; + if( yymsp[-2].minor.yy98.n ) sqlite3SrcListAddAlias(yygotominor.yy259,&yymsp[-2].minor.yy98); + if( yymsp[-1].minor.yy258 ){ + if( yygotominor.yy259 && yygotominor.yy259->nSrc>1 ){ yygotominor.yy259->a[yygotominor.yy259->nSrc-2].pOn = yymsp[-1].minor.yy258; } + else { sqlite3ExprDelete(yymsp[-1].minor.yy258); } + } + if( yymsp[0].minor.yy272 ){ + if( yygotominor.yy259 && yygotominor.yy259->nSrc>1 ){ yygotominor.yy259->a[yygotominor.yy259->nSrc-2].pUsing = yymsp[0].minor.yy272; } + else { sqlite3IdListDelete(yymsp[0].minor.yy272); } + } +} +#line 2230 "parse.c" + break; + case 122: +#line 419 "parse.y" +{ + yygotominor.yy107 = sqlite3SelectNew(0,yymsp[0].minor.yy259,0,0,0,0,0,-1,0); +} +#line 2237 "parse.c" + break; + case 123: +#line 424 "parse.y" +{yygotominor.yy98.z=0; yygotominor.yy98.n=0;} +#line 2242 "parse.c" + break; + case 125: +#line 429 "parse.y" +{yygotominor.yy259 = sqlite3SrcListAppend(0,&yymsp[-1].minor.yy98,&yymsp[0].minor.yy98);} +#line 2247 "parse.c" + break; + case 126: + case 127: +#line 433 "parse.y" +{ yygotominor.yy284 = JT_INNER; } +#line 2253 "parse.c" + break; + case 128: +#line 435 "parse.y" +{ yygotominor.yy284 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); } +#line 2258 "parse.c" + break; + case 129: +#line 436 "parse.y" +{ yygotominor.yy284 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy98,0); } +#line 2263 "parse.c" + break; + case 130: +#line 438 "parse.y" +{ yygotominor.yy284 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy98,&yymsp[-1].minor.yy98); } +#line 2268 "parse.c" + break; + case 131: + case 139: + case 148: + case 155: + case 226: + case 228: + case 232: +#line 442 "parse.y" +{yygotominor.yy258 = yymsp[0].minor.yy258;} +#line 2279 "parse.c" + break; + case 132: + case 147: + case 154: + case 227: + case 229: + case 233: +#line 443 "parse.y" +{yygotominor.yy258 = 0;} +#line 2289 "parse.c" + break; + case 133: + case 166: +#line 447 "parse.y" +{yygotominor.yy272 = yymsp[-1].minor.yy272;} +#line 2295 "parse.c" + break; + case 134: + case 165: +#line 448 "parse.y" +{yygotominor.yy272 = 0;} +#line 2301 "parse.c" + break; + case 136: + case 146: +#line 459 "parse.y" +{yygotominor.yy210 = yymsp[0].minor.yy210;} +#line 2307 "parse.c" + break; + case 137: +#line 460 "parse.y" +{ + yygotominor.yy210 = sqlite3ExprListAppend(yymsp[-4].minor.yy210,yymsp[-2].minor.yy258,yymsp[-1].minor.yy98.n>0?&yymsp[-1].minor.yy98:0); + if( yygotominor.yy210 ) yygotominor.yy210->a[yygotominor.yy210->nExpr-1].sortOrder = yymsp[0].minor.yy284; +} +#line 2315 "parse.c" + break; + case 138: +#line 464 "parse.y" +{ + yygotominor.yy210 = sqlite3ExprListAppend(0,yymsp[-2].minor.yy258,yymsp[-1].minor.yy98.n>0?&yymsp[-1].minor.yy98:0); + if( yygotominor.yy210 && yygotominor.yy210->a ) yygotominor.yy210->a[0].sortOrder = yymsp[0].minor.yy284; +} +#line 2323 "parse.c" + break; + case 140: + case 142: +#line 473 "parse.y" +{yygotominor.yy284 = SQLITE_SO_ASC;} +#line 2329 "parse.c" + break; + case 141: +#line 474 "parse.y" +{yygotominor.yy284 = SQLITE_SO_DESC;} +#line 2334 "parse.c" + break; + case 143: +#line 476 "parse.y" +{yygotominor.yy98.z = 0; yygotominor.yy98.n = 0;} +#line 2339 "parse.c" + break; + case 149: +#line 490 "parse.y" +{yygotominor.yy404.limit = -1; yygotominor.yy404.offset = 0;} +#line 2344 "parse.c" + break; + case 150: +#line 491 "parse.y" +{yygotominor.yy404.limit = yymsp[0].minor.yy284; yygotominor.yy404.offset = 0;} +#line 2349 "parse.c" + break; + case 151: +#line 493 "parse.y" +{yygotominor.yy404.limit = yymsp[-2].minor.yy284; yygotominor.yy404.offset = yymsp[0].minor.yy284;} +#line 2354 "parse.c" + break; + case 152: +#line 495 "parse.y" +{yygotominor.yy404.limit = yymsp[0].minor.yy284; yygotominor.yy404.offset = yymsp[-2].minor.yy284;} +#line 2359 "parse.c" + break; + case 153: +#line 499 "parse.y" +{sqlite3DeleteFrom(pParse,yymsp[-1].minor.yy259,yymsp[0].minor.yy258);} +#line 2364 "parse.c" + break; + case 156: +#line 513 "parse.y" +{sqlite3Update(pParse,yymsp[-3].minor.yy259,yymsp[-1].minor.yy210,yymsp[0].minor.yy258,yymsp[-4].minor.yy284);} +#line 2369 "parse.c" + break; + case 157: +#line 516 "parse.y" +{yygotominor.yy210 = sqlite3ExprListAppend(yymsp[-4].minor.yy210,yymsp[0].minor.yy258,&yymsp[-2].minor.yy98);} +#line 2374 "parse.c" + break; + case 158: +#line 517 "parse.y" +{yygotominor.yy210 = sqlite3ExprListAppend(0,yymsp[0].minor.yy258,&yymsp[-2].minor.yy98);} +#line 2379 "parse.c" + break; + case 159: +#line 523 "parse.y" +{sqlite3Insert(pParse, yymsp[-5].minor.yy259, yymsp[-1].minor.yy210, 0, yymsp[-4].minor.yy272, yymsp[-7].minor.yy284);} +#line 2384 "parse.c" + break; + case 160: +#line 525 "parse.y" +{sqlite3Insert(pParse, yymsp[-2].minor.yy259, 0, yymsp[0].minor.yy107, yymsp[-1].minor.yy272, yymsp[-4].minor.yy284);} +#line 2389 "parse.c" + break; + case 163: + case 230: +#line 535 "parse.y" +{yygotominor.yy210 = sqlite3ExprListAppend(yymsp[-2].minor.yy210,yymsp[0].minor.yy258,0);} +#line 2395 "parse.c" + break; + case 164: + case 231: +#line 536 "parse.y" +{yygotominor.yy210 = sqlite3ExprListAppend(0,yymsp[0].minor.yy258,0);} +#line 2401 "parse.c" + break; + case 167: +#line 545 "parse.y" +{yygotominor.yy272 = sqlite3IdListAppend(yymsp[-2].minor.yy272,&yymsp[0].minor.yy98);} +#line 2406 "parse.c" + break; + case 168: +#line 546 "parse.y" +{yygotominor.yy272 = sqlite3IdListAppend(0,&yymsp[0].minor.yy98);} +#line 2411 "parse.c" + break; + case 169: +#line 554 "parse.y" +{yygotominor.yy258 = yymsp[-1].minor.yy258; sqlite3ExprSpan(yygotominor.yy258,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); } +#line 2416 "parse.c" + break; + case 170: + case 175: + case 176: + case 177: + case 178: +#line 555 "parse.y" +{yygotominor.yy258 = sqlite3Expr(yymsp[0].major, 0, 0, &yymsp[0].minor.yy0);} +#line 2425 "parse.c" + break; + case 171: + case 172: +#line 556 "parse.y" +{yygotominor.yy258 = sqlite3Expr(TK_ID, 0, 0, &yymsp[0].minor.yy0);} +#line 2431 "parse.c" + break; + case 173: +#line 558 "parse.y" +{ + Expr *temp1 = sqlite3Expr(TK_ID, 0, 0, &yymsp[-2].minor.yy98); + Expr *temp2 = sqlite3Expr(TK_ID, 0, 0, &yymsp[0].minor.yy98); + yygotominor.yy258 = sqlite3Expr(TK_DOT, temp1, temp2, 0); +} +#line 2440 "parse.c" + break; + case 174: +#line 563 "parse.y" +{ + Expr *temp1 = sqlite3Expr(TK_ID, 0, 0, &yymsp[-4].minor.yy98); + Expr *temp2 = sqlite3Expr(TK_ID, 0, 0, &yymsp[-2].minor.yy98); + Expr *temp3 = sqlite3Expr(TK_ID, 0, 0, &yymsp[0].minor.yy98); + Expr *temp4 = sqlite3Expr(TK_DOT, temp2, temp3, 0); + yygotominor.yy258 = sqlite3Expr(TK_DOT, temp1, temp4, 0); +} +#line 2451 "parse.c" + break; + case 179: +#line 574 "parse.y" +{ + Token *pToken = &yymsp[0].minor.yy0; + Expr *pExpr = yygotominor.yy258 = sqlite3Expr(TK_VARIABLE, 0, 0, pToken); + sqlite3ExprAssignVarNumber(pParse, pExpr); +} +#line 2460 "parse.c" + break; + case 180: +#line 579 "parse.y" +{ + yygotominor.yy258 = sqlite3ExprFunction(yymsp[-1].minor.yy210, &yymsp[-3].minor.yy0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); +} +#line 2468 "parse.c" + break; + case 181: +#line 583 "parse.y" +{ + yygotominor.yy258 = sqlite3ExprFunction(0, &yymsp[-3].minor.yy0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); +} +#line 2476 "parse.c" + break; + case 182: + case 183: + case 184: + case 185: + case 186: + case 187: + case 188: + case 189: + case 190: + case 191: + case 192: + case 193: + case 194: + case 195: + case 196: + case 197: + case 198: + case 199: +#line 587 "parse.y" +{yygotominor.yy258 = sqlite3Expr(yymsp[-1].major, yymsp[-2].minor.yy258, yymsp[0].minor.yy258, 0);} +#line 2498 "parse.c" + break; + case 200: +#line 606 "parse.y" +{yygotominor.yy342.opcode = TK_LIKE; yygotominor.yy342.not = 0;} +#line 2503 "parse.c" + break; + case 201: +#line 607 "parse.y" +{yygotominor.yy342.opcode = TK_GLOB; yygotominor.yy342.not = 0;} +#line 2508 "parse.c" + break; + case 202: +#line 608 "parse.y" +{yygotominor.yy342.opcode = TK_LIKE; yygotominor.yy342.not = 1;} +#line 2513 "parse.c" + break; + case 203: +#line 609 "parse.y" +{yygotominor.yy342.opcode = TK_GLOB; yygotominor.yy342.not = 1;} +#line 2518 "parse.c" + break; + case 204: +#line 610 "parse.y" +{ + ExprList *pList = sqlite3ExprListAppend(0, yymsp[0].minor.yy258, 0); + pList = sqlite3ExprListAppend(pList, yymsp[-2].minor.yy258, 0); + yygotominor.yy258 = sqlite3ExprFunction(pList, 0); + if( yygotominor.yy258 ) yygotominor.yy258->op = yymsp[-1].minor.yy342.opcode; + if( yymsp[-1].minor.yy342.not ) yygotominor.yy258 = sqlite3Expr(TK_NOT, yygotominor.yy258, 0, 0); + sqlite3ExprSpan(yygotominor.yy258, &yymsp[-2].minor.yy258->span, &yymsp[0].minor.yy258->span); +} +#line 2530 "parse.c" + break; + case 205: +#line 618 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(TK_ISNULL, yymsp[-1].minor.yy258, 0, 0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-1].minor.yy258->span,&yymsp[0].minor.yy0); +} +#line 2538 "parse.c" + break; + case 206: +#line 622 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(TK_ISNULL, yymsp[-2].minor.yy258, 0, 0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-2].minor.yy258->span,&yymsp[0].minor.yy0); +} +#line 2546 "parse.c" + break; + case 207: +#line 626 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(TK_NOTNULL, yymsp[-1].minor.yy258, 0, 0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-1].minor.yy258->span,&yymsp[0].minor.yy0); +} +#line 2554 "parse.c" + break; + case 208: +#line 630 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(TK_NOTNULL, yymsp[-2].minor.yy258, 0, 0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-2].minor.yy258->span,&yymsp[0].minor.yy0); +} +#line 2562 "parse.c" + break; + case 209: +#line 634 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(TK_NOTNULL, yymsp[-3].minor.yy258, 0, 0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-3].minor.yy258->span,&yymsp[0].minor.yy0); +} +#line 2570 "parse.c" + break; + case 210: + case 211: +#line 638 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(yymsp[-1].major, yymsp[0].minor.yy258, 0, 0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy258->span); +} +#line 2579 "parse.c" + break; + case 212: +#line 646 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(TK_UMINUS, yymsp[0].minor.yy258, 0, 0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy258->span); +} +#line 2587 "parse.c" + break; + case 213: +#line 650 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(TK_UPLUS, yymsp[0].minor.yy258, 0, 0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy258->span); +} +#line 2595 "parse.c" + break; + case 214: +#line 654 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(TK_SELECT, 0, 0, 0); + if( yygotominor.yy258 ) yygotominor.yy258->pSelect = yymsp[-1].minor.yy107; + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); +} +#line 2604 "parse.c" + break; + case 217: +#line 662 "parse.y" +{ + ExprList *pList = sqlite3ExprListAppend(0, yymsp[-2].minor.yy258, 0); + pList = sqlite3ExprListAppend(pList, yymsp[0].minor.yy258, 0); + yygotominor.yy258 = sqlite3Expr(TK_BETWEEN, yymsp[-4].minor.yy258, 0, 0); + if( yygotominor.yy258 ) yygotominor.yy258->pList = pList; + if( yymsp[-3].minor.yy284 ) yygotominor.yy258 = sqlite3Expr(TK_NOT, yygotominor.yy258, 0, 0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-4].minor.yy258->span,&yymsp[0].minor.yy258->span); +} +#line 2616 "parse.c" + break; + case 220: +#line 673 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(TK_IN, yymsp[-4].minor.yy258, 0, 0); + if( yygotominor.yy258 ) yygotominor.yy258->pList = yymsp[-1].minor.yy210; + if( yymsp[-3].minor.yy284 ) yygotominor.yy258 = sqlite3Expr(TK_NOT, yygotominor.yy258, 0, 0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-4].minor.yy258->span,&yymsp[0].minor.yy0); +} +#line 2626 "parse.c" + break; + case 221: +#line 679 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(TK_IN, yymsp[-4].minor.yy258, 0, 0); + if( yygotominor.yy258 ) yygotominor.yy258->pSelect = yymsp[-1].minor.yy107; + if( yymsp[-3].minor.yy284 ) yygotominor.yy258 = sqlite3Expr(TK_NOT, yygotominor.yy258, 0, 0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-4].minor.yy258->span,&yymsp[0].minor.yy0); +} +#line 2636 "parse.c" + break; + case 222: +#line 685 "parse.y" +{ + SrcList *pSrc = sqlite3SrcListAppend(0,&yymsp[-1].minor.yy98,&yymsp[0].minor.yy98); + yygotominor.yy258 = sqlite3Expr(TK_IN, yymsp[-3].minor.yy258, 0, 0); + if( yygotominor.yy258 ) yygotominor.yy258->pSelect = sqlite3SelectNew(0,pSrc,0,0,0,0,0,-1,0); + if( yymsp[-2].minor.yy284 ) yygotominor.yy258 = sqlite3Expr(TK_NOT, yygotominor.yy258, 0, 0); + sqlite3ExprSpan(yygotominor.yy258,&yymsp[-3].minor.yy258->span,yymsp[0].minor.yy98.z?&yymsp[0].minor.yy98:&yymsp[-1].minor.yy98); +} +#line 2647 "parse.c" + break; + case 223: +#line 695 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(TK_CASE, yymsp[-3].minor.yy258, yymsp[-1].minor.yy258, 0); + if( yygotominor.yy258 ) yygotominor.yy258->pList = yymsp[-2].minor.yy210; + sqlite3ExprSpan(yygotominor.yy258, &yymsp[-4].minor.yy0, &yymsp[0].minor.yy0); +} +#line 2656 "parse.c" + break; + case 224: +#line 702 "parse.y" +{ + yygotominor.yy210 = sqlite3ExprListAppend(yymsp[-4].minor.yy210, yymsp[-2].minor.yy258, 0); + yygotominor.yy210 = sqlite3ExprListAppend(yygotominor.yy210, yymsp[0].minor.yy258, 0); +} +#line 2664 "parse.c" + break; + case 225: +#line 706 "parse.y" +{ + yygotominor.yy210 = sqlite3ExprListAppend(0, yymsp[-2].minor.yy258, 0); + yygotominor.yy210 = sqlite3ExprListAppend(yygotominor.yy210, yymsp[0].minor.yy258, 0); +} +#line 2672 "parse.c" + break; + case 234: +#line 731 "parse.y" +{ + if( yymsp[-9].minor.yy284!=OE_None ) yymsp[-9].minor.yy284 = yymsp[0].minor.yy284; + if( yymsp[-9].minor.yy284==OE_Default) yymsp[-9].minor.yy284 = OE_Abort; + sqlite3CreateIndex(pParse, &yymsp[-7].minor.yy98, &yymsp[-6].minor.yy98, yymsp[-4].minor.yy259, yymsp[-2].minor.yy210, yymsp[-9].minor.yy284, &yymsp[-10].minor.yy0, &yymsp[-1].minor.yy0); +} +#line 2681 "parse.c" + break; + case 235: + case 282: +#line 738 "parse.y" +{yygotominor.yy284 = OE_Abort;} +#line 2687 "parse.c" + break; + case 236: +#line 739 "parse.y" +{yygotominor.yy284 = OE_None;} +#line 2692 "parse.c" + break; + case 239: +#line 749 "parse.y" +{ + Expr *p = 0; + if( yymsp[-1].minor.yy98.n>0 ){ + p = sqlite3Expr(TK_COLUMN, 0, 0, 0); + if( p ) p->pColl = sqlite3LocateCollSeq(pParse, yymsp[-1].minor.yy98.z, yymsp[-1].minor.yy98.n); + } + yygotominor.yy210 = sqlite3ExprListAppend(yymsp[-4].minor.yy210, p, &yymsp[-2].minor.yy98); +} +#line 2704 "parse.c" + break; + case 240: +#line 757 "parse.y" +{ + Expr *p = 0; + if( yymsp[-1].minor.yy98.n>0 ){ + p = sqlite3Expr(TK_COLUMN, 0, 0, 0); + if( p ) p->pColl = sqlite3LocateCollSeq(pParse, yymsp[-1].minor.yy98.z, yymsp[-1].minor.yy98.n); + } + yygotominor.yy210 = sqlite3ExprListAppend(0, p, &yymsp[-2].minor.yy98); +} +#line 2716 "parse.c" + break; + case 242: +#line 770 "parse.y" +{sqlite3DropIndex(pParse, yymsp[0].minor.yy259);} +#line 2721 "parse.c" + break; + case 243: + case 244: +#line 774 "parse.y" +{sqlite3Vacuum(pParse,0);} +#line 2727 "parse.c" + break; + case 245: + case 247: +#line 779 "parse.y" +{sqlite3Pragma(pParse,&yymsp[-3].minor.yy98,&yymsp[-2].minor.yy98,&yymsp[0].minor.yy98,0);} +#line 2733 "parse.c" + break; + case 246: +#line 780 "parse.y" +{sqlite3Pragma(pParse,&yymsp[-3].minor.yy98,&yymsp[-2].minor.yy98,&yymsp[0].minor.yy0,0);} +#line 2738 "parse.c" + break; + case 248: +#line 782 "parse.y" +{ + sqlite3Pragma(pParse,&yymsp[-3].minor.yy98,&yymsp[-2].minor.yy98,&yymsp[0].minor.yy98,1); +} +#line 2745 "parse.c" + break; + case 249: +#line 785 "parse.y" +{sqlite3Pragma(pParse,&yymsp[-4].minor.yy98,&yymsp[-3].minor.yy98,&yymsp[-1].minor.yy98,0);} +#line 2750 "parse.c" + break; + case 250: +#line 786 "parse.y" +{sqlite3Pragma(pParse,&yymsp[-1].minor.yy98,&yymsp[0].minor.yy98,0,0);} +#line 2755 "parse.c" + break; + case 257: +#line 796 "parse.y" +{ + Token all; + all.z = yymsp[-3].minor.yy98.z; + all.n = (yymsp[0].minor.yy0.z - yymsp[-3].minor.yy98.z) + yymsp[0].minor.yy0.n; + sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy91, &all); +} +#line 2765 "parse.c" + break; + case 258: +#line 805 "parse.y" +{ + sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy98, &yymsp[-6].minor.yy98, yymsp[-5].minor.yy284, yymsp[-4].minor.yy146.a, yymsp[-4].minor.yy146.b, yymsp[-2].minor.yy259, yymsp[-1].minor.yy284, yymsp[0].minor.yy258, yymsp[-9].minor.yy284); + yygotominor.yy98 = (yymsp[-6].minor.yy98.n==0?yymsp[-7].minor.yy98:yymsp[-6].minor.yy98); +} +#line 2773 "parse.c" + break; + case 259: + case 262: +#line 811 "parse.y" +{ yygotominor.yy284 = TK_BEFORE; } +#line 2779 "parse.c" + break; + case 260: +#line 812 "parse.y" +{ yygotominor.yy284 = TK_AFTER; } +#line 2784 "parse.c" + break; + case 261: +#line 813 "parse.y" +{ yygotominor.yy284 = TK_INSTEAD;} +#line 2789 "parse.c" + break; + case 263: + case 264: + case 265: +#line 818 "parse.y" +{yygotominor.yy146.a = yymsp[0].major; yygotominor.yy146.b = 0;} +#line 2796 "parse.c" + break; + case 266: +#line 821 "parse.y" +{yygotominor.yy146.a = TK_UPDATE; yygotominor.yy146.b = yymsp[0].minor.yy272;} +#line 2801 "parse.c" + break; + case 267: + case 268: +#line 824 "parse.y" +{ yygotominor.yy284 = TK_ROW; } +#line 2807 "parse.c" + break; + case 269: +#line 826 "parse.y" +{ yygotominor.yy284 = TK_STATEMENT; } +#line 2812 "parse.c" + break; + case 270: +#line 829 "parse.y" +{ yygotominor.yy258 = 0; } +#line 2817 "parse.c" + break; + case 271: +#line 830 "parse.y" +{ yygotominor.yy258 = yymsp[0].minor.yy258; } +#line 2822 "parse.c" + break; + case 272: +#line 834 "parse.y" +{ + yymsp[-2].minor.yy91->pNext = yymsp[0].minor.yy91; + yygotominor.yy91 = yymsp[-2].minor.yy91; +} +#line 2830 "parse.c" + break; + case 273: +#line 838 "parse.y" +{ yygotominor.yy91 = 0; } +#line 2835 "parse.c" + break; + case 274: +#line 844 "parse.y" +{ yygotominor.yy91 = sqlite3TriggerUpdateStep(&yymsp[-3].minor.yy98, yymsp[-1].minor.yy210, yymsp[0].minor.yy258, yymsp[-4].minor.yy284); } +#line 2840 "parse.c" + break; + case 275: +#line 849 "parse.y" +{yygotominor.yy91 = sqlite3TriggerInsertStep(&yymsp[-5].minor.yy98, yymsp[-4].minor.yy272, yymsp[-1].minor.yy210, 0, yymsp[-7].minor.yy284);} +#line 2845 "parse.c" + break; + case 276: +#line 852 "parse.y" +{yygotominor.yy91 = sqlite3TriggerInsertStep(&yymsp[-2].minor.yy98, yymsp[-1].minor.yy272, 0, yymsp[0].minor.yy107, yymsp[-4].minor.yy284);} +#line 2850 "parse.c" + break; + case 277: +#line 856 "parse.y" +{yygotominor.yy91 = sqlite3TriggerDeleteStep(&yymsp[-1].minor.yy98, yymsp[0].minor.yy258);} +#line 2855 "parse.c" + break; + case 278: +#line 859 "parse.y" +{yygotominor.yy91 = sqlite3TriggerSelectStep(yymsp[0].minor.yy107); } +#line 2860 "parse.c" + break; + case 279: +#line 862 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(TK_RAISE, 0, 0, 0); + yygotominor.yy258->iColumn = OE_Ignore; + sqlite3ExprSpan(yygotominor.yy258, &yymsp[-3].minor.yy0, &yymsp[0].minor.yy0); +} +#line 2869 "parse.c" + break; + case 280: +#line 867 "parse.y" +{ + yygotominor.yy258 = sqlite3Expr(TK_RAISE, 0, 0, &yymsp[-1].minor.yy98); + yygotominor.yy258->iColumn = yymsp[-3].minor.yy284; + sqlite3ExprSpan(yygotominor.yy258, &yymsp[-5].minor.yy0, &yymsp[0].minor.yy0); +} +#line 2878 "parse.c" + break; + case 281: +#line 873 "parse.y" +{yygotominor.yy284 = OE_Rollback;} +#line 2883 "parse.c" + break; + case 283: +#line 875 "parse.y" +{yygotominor.yy284 = OE_Fail;} +#line 2888 "parse.c" + break; + case 284: +#line 879 "parse.y" +{ + sqlite3DropTrigger(pParse,yymsp[0].minor.yy259); +} +#line 2895 "parse.c" + break; + case 285: +#line 884 "parse.y" +{ + sqlite3Attach(pParse, &yymsp[-3].minor.yy98, &yymsp[-1].minor.yy98, yymsp[0].minor.yy292.type, &yymsp[0].minor.yy292.key); +} +#line 2902 "parse.c" + break; + case 286: +#line 888 "parse.y" +{ yygotominor.yy292.type = 0; } +#line 2907 "parse.c" + break; + case 287: +#line 889 "parse.y" +{ yygotominor.yy292.type=1; yygotominor.yy292.key = yymsp[0].minor.yy98; } +#line 2912 "parse.c" + break; + case 288: +#line 890 "parse.y" +{ yygotominor.yy292.type=2; yygotominor.yy292.key = yymsp[0].minor.yy0; } +#line 2917 "parse.c" + break; + case 291: +#line 896 "parse.y" +{ + sqlite3Detach(pParse, &yymsp[0].minor.yy98); +} +#line 2924 "parse.c" + break; + }; + yygoto = yyRuleInfo[yyruleno].lhs; + yysize = yyRuleInfo[yyruleno].nrhs; + yypParser->yyidx -= yysize; + yyact = yy_find_reduce_action(yypParser,yygoto); + if( yyact < YYNSTATE ){ + yy_shift(yypParser,yyact,yygoto,&yygotominor); + }else if( yyact == YYNSTATE + YYNRULE + 1 ){ + yy_accept(yypParser); + } +} + +/* +** The following code executes when the parse fails +*/ +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + sqlite3ParserARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ + sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + YYMINORTYPE yyminor /* The minor type of the error token */ +){ + sqlite3ParserARG_FETCH; +#define TOKEN (yyminor.yy0) +#line 23 "parse.y" + + if( pParse->zErrMsg==0 ){ + if( TOKEN.z[0] ){ + sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN); + }else{ + sqlite3ErrorMsg(pParse, "incomplete SQL statement"); + } + } +#line 2976 "parse.c" + sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + sqlite3ParserARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ + sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "sqlite3ParserAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +**
      +**
    • A pointer to the parser (an opaque structure.) +**
    • The major token number. +**
    • The minor token number. +**
    • An option argument of a grammar-specified type. +**
    +** +** Outputs: +** None. +*/ +void sqlite3Parser( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + sqlite3ParserTOKENTYPE yyminor /* The value for the token */ + sqlite3ParserARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + int yyact; /* The parser action. */ + int yyendofinput; /* True if we are at the end of input */ + int yyerrorhit = 0; /* True if yymajor has invoked an error */ + yyParser *yypParser; /* The parser */ + + /* (re)initialize the parser, if necessary */ + yypParser = (yyParser*)yyp; + if( yypParser->yyidx<0 ){ + if( yymajor==0 ) return; + yypParser->yyidx = 0; + yypParser->yyerrcnt = -1; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; + } + yyminorunion.yy0 = yyminor; + yyendofinput = (yymajor==0); + sqlite3ParserARG_STORE; + +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]); + } +#endif + + do{ + yyact = yy_find_shift_action(yypParser,yymajor); + if( yyactyyerrcnt--; + if( yyendofinput && yypParser->yyidx>=0 ){ + yymajor = 0; + }else{ + yymajor = YYNOCODE; + } + }else if( yyact < YYNSTATE + YYNRULE ){ + yy_reduce(yypParser,yyact-YYNSTATE); + }else if( yyact == YY_ERROR_ACTION ){ + int yymx; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yymx = yypParser->yystack[yypParser->yyidx].major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yymajor,&yyminorunion); + yymajor = YYNOCODE; + }else{ + while( + yypParser->yyidx >= 0 && + yymx != YYERRORSYMBOL && + (yyact = yy_find_shift_action(yypParser,YYERRORSYMBOL)) >= YYNSTATE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yyidx < 0 || yymajor==0 ){ + yy_destructor(yymajor,&yyminorunion); + yy_parse_failed(yypParser); + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + YYMINORTYPE u2; + u2.YYERRSYMDT = 0; + yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); + } + } + yypParser->yyerrcnt = 3; + yyerrorhit = 1; +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yypParser->yyerrcnt = 3; + yy_destructor(yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); + } + yymajor = YYNOCODE; +#endif + }else{ + yy_accept(yypParser); + yymajor = YYNOCODE; + } + }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); + return; +} diff --git a/kopete/plugins/statistics/sqlite/parse.h b/kopete/plugins/statistics/sqlite/parse.h new file mode 100644 index 00000000..547319ed --- /dev/null +++ b/kopete/plugins/statistics/sqlite/parse.h @@ -0,0 +1,129 @@ +#define TK_END_OF_FILE 1 +#define TK_ILLEGAL 2 +#define TK_SPACE 3 +#define TK_UNCLOSED_STRING 4 +#define TK_COMMENT 5 +#define TK_FUNCTION 6 +#define TK_COLUMN 7 +#define TK_AGG_FUNCTION 8 +#define TK_SEMI 9 +#define TK_EXPLAIN 10 +#define TK_BEGIN 11 +#define TK_TRANSACTION 12 +#define TK_DEFERRED 13 +#define TK_IMMEDIATE 14 +#define TK_EXCLUSIVE 15 +#define TK_COMMIT 16 +#define TK_END 17 +#define TK_ROLLBACK 18 +#define TK_CREATE 19 +#define TK_TABLE 20 +#define TK_TEMP 21 +#define TK_LP 22 +#define TK_RP 23 +#define TK_AS 24 +#define TK_COMMA 25 +#define TK_ID 26 +#define TK_ABORT 27 +#define TK_AFTER 28 +#define TK_ASC 29 +#define TK_ATTACH 30 +#define TK_BEFORE 31 +#define TK_CASCADE 32 +#define TK_CONFLICT 33 +#define TK_DATABASE 34 +#define TK_DESC 35 +#define TK_DETACH 36 +#define TK_EACH 37 +#define TK_FAIL 38 +#define TK_FOR 39 +#define TK_GLOB 40 +#define TK_IGNORE 41 +#define TK_INITIALLY 42 +#define TK_INSTEAD 43 +#define TK_LIKE 44 +#define TK_MATCH 45 +#define TK_KEY 46 +#define TK_OF 47 +#define TK_OFFSET 48 +#define TK_PRAGMA 49 +#define TK_RAISE 50 +#define TK_REPLACE 51 +#define TK_RESTRICT 52 +#define TK_ROW 53 +#define TK_STATEMENT 54 +#define TK_TRIGGER 55 +#define TK_VACUUM 56 +#define TK_VIEW 57 +#define TK_OR 58 +#define TK_AND 59 +#define TK_NOT 60 +#define TK_IS 61 +#define TK_BETWEEN 62 +#define TK_IN 63 +#define TK_ISNULL 64 +#define TK_NOTNULL 65 +#define TK_NE 66 +#define TK_EQ 67 +#define TK_GT 68 +#define TK_LE 69 +#define TK_LT 70 +#define TK_GE 71 +#define TK_BITAND 72 +#define TK_BITOR 73 +#define TK_LSHIFT 74 +#define TK_RSHIFT 75 +#define TK_PLUS 76 +#define TK_MINUS 77 +#define TK_STAR 78 +#define TK_SLASH 79 +#define TK_REM 80 +#define TK_CONCAT 81 +#define TK_UMINUS 82 +#define TK_UPLUS 83 +#define TK_BITNOT 84 +#define TK_STRING 85 +#define TK_JOIN_KW 86 +#define TK_CONSTRAINT 87 +#define TK_DEFAULT 88 +#define TK_NULL 89 +#define TK_PRIMARY 90 +#define TK_UNIQUE 91 +#define TK_CHECK 92 +#define TK_REFERENCES 93 +#define TK_COLLATE 94 +#define TK_ON 95 +#define TK_DELETE 96 +#define TK_UPDATE 97 +#define TK_INSERT 98 +#define TK_SET 99 +#define TK_DEFERRABLE 100 +#define TK_FOREIGN 101 +#define TK_DROP 102 +#define TK_UNION 103 +#define TK_ALL 104 +#define TK_INTERSECT 105 +#define TK_EXCEPT 106 +#define TK_SELECT 107 +#define TK_DISTINCT 108 +#define TK_DOT 109 +#define TK_FROM 110 +#define TK_JOIN 111 +#define TK_USING 112 +#define TK_ORDER 113 +#define TK_BY 114 +#define TK_GROUP 115 +#define TK_HAVING 116 +#define TK_LIMIT 117 +#define TK_WHERE 118 +#define TK_INTO 119 +#define TK_VALUES 120 +#define TK_INTEGER 121 +#define TK_FLOAT 122 +#define TK_BLOB 123 +#define TK_VARIABLE 124 +#define TK_CASE 125 +#define TK_WHEN 126 +#define TK_THEN 127 +#define TK_ELSE 128 +#define TK_INDEX 129 diff --git a/kopete/plugins/statistics/sqlite/pragma.c b/kopete/plugins/statistics/sqlite/pragma.c new file mode 100644 index 00000000..94a21863 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/pragma.c @@ -0,0 +1,754 @@ +/* +** 2003 April 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used to implement the PRAGMA command. +** +** $Id$ +*/ +#include "sqliteInt.h" +#include + +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) +# include "pager.h" +# include "btree.h" +#endif + +/* +** Interpret the given string as a boolean value. +*/ +static int getBoolean(const u8 *z){ + static const u8 *azTrue[] = { "yes", "on", "true" }; + int i; + if( z[0]==0 ) return 0; + if( sqlite3IsNumber(z, 0, SQLITE_UTF8) ){ + return atoi(z); + } + for(i=0; i='0' && z[0]<='2' ){ + return z[0] - '0'; + }else if( sqlite3StrICmp(z, "file")==0 ){ + return 1; + }else if( sqlite3StrICmp(z, "memory")==0 ){ + return 2; + }else{ + return 0; + } +} + +/* +** If the TEMP database is open, close it and mark the database schema +** as needing reloading. This must be done when using the TEMP_STORE +** or DEFAULT_TEMP_STORE pragmas. +*/ +static int changeTempStorage(Parse *pParse, const char *zStorageType){ + int ts = getTempStore(zStorageType); + sqlite3 *db = pParse->db; + if( db->temp_store==ts ) return SQLITE_OK; + if( db->aDb[1].pBt!=0 ){ + if( db->flags & SQLITE_InTrans ){ + sqlite3ErrorMsg(pParse, "temporary storage cannot be changed " + "from within a transaction"); + return SQLITE_ERROR; + } + sqlite3BtreeClose(db->aDb[1].pBt); + db->aDb[1].pBt = 0; + sqlite3ResetInternalSchema(db, 0); + } + db->temp_store = ts; + return SQLITE_OK; +} + +/* +** Generate code to return a single integer value. +*/ +static void returnSingleInt(Parse *pParse, const char *zLabel, int value){ + Vdbe *v = sqlite3GetVdbe(pParse); + sqlite3VdbeAddOp(v, OP_Integer, value, 0); + if( pParse->explain==0 ){ + sqlite3VdbeSetNumCols(v, 1); + sqlite3VdbeSetColName(v, 0, zLabel, P3_STATIC); + } + sqlite3VdbeAddOp(v, OP_Callback, 1, 0); +} + +/* +** Check to see if zRight and zLeft refer to a pragma that queries +** or changes one of the flags in db->flags. Return 1 if so and 0 if not. +** Also, implement the pragma. +*/ +static int flagPragma(Parse *pParse, const char *zLeft, const char *zRight){ + static const struct { + const char *zName; /* Name of the pragma */ + int mask; /* Mask for the db->flags value */ + } aPragma[] = { + { "vdbe_trace", SQLITE_VdbeTrace }, + { "sql_trace", SQLITE_SqlTrace }, + { "vdbe_listing", SQLITE_VdbeListing }, +#if 1 /* FIX ME: Remove the following pragmas */ + { "full_column_names", SQLITE_FullColNames }, + { "short_column_names", SQLITE_ShortColNames }, + { "count_changes", SQLITE_CountRows }, + { "empty_result_callbacks", SQLITE_NullCallback }, +#endif + }; + int i; + for(i=0; idb; + Vdbe *v; + if( zRight==0 ){ + v = sqlite3GetVdbe(pParse); + if( v ){ + returnSingleInt(pParse, + aPragma[i].zName, (db->flags&aPragma[i].mask)!=0); + } + }else if( getBoolean(zRight) ){ + db->flags |= aPragma[i].mask; + }else{ + db->flags &= ~aPragma[i].mask; + } + return 1; + } + } + return 0; +} + +/* +** Process a pragma statement. +** +** Pragmas are of this form: +** +** PRAGMA [database.]id [= value] +** +** The identifier might also be a string. The value is a string, and +** identifier, or a number. If minusFlag is true, then the value is +** a number that was preceded by a minus sign. +** +** If the left side is "database.id" then pId1 is the database name +** and pId2 is the id. If the left side is just "id" then pId1 is the +** id and pId2 is any empty string. +*/ +void sqlite3Pragma( + Parse *pParse, + Token *pId1, /* First part of [database.]id field */ + Token *pId2, /* Second part of [database.]id field, or NULL */ + Token *pValue, /* Token for , or NULL */ + int minusFlag /* True if a '-' sign preceded */ +){ + char *zLeft = 0; /* Nul-terminated UTF-8 string */ + char *zRight = 0; /* Nul-terminated UTF-8 string , or NULL */ + const char *zDb = 0; /* The database name */ + Token *pId; /* Pointer to token */ + int iDb; /* Database index for */ + sqlite3 *db = pParse->db; + Db *pDb; + Vdbe *v = sqlite3GetVdbe(pParse); + if( v==0 ) return; + + /* Interpret the [database.] part of the pragma statement. iDb is the + ** index of the database this pragma is being applied to in db.aDb[]. */ + iDb = sqlite3TwoPartName(pParse, pId1, pId2, &pId); + if( iDb<0 ) return; + pDb = &db->aDb[iDb]; + + zLeft = sqlite3NameFromToken(pId); + if( !zLeft ) return; + if( minusFlag ){ + zRight = sqlite3MPrintf("-%T", pValue); + }else{ + zRight = sqlite3NameFromToken(pValue); + } + + zDb = ((iDb>0)?pDb->zName:0); + if( sqlite3AuthCheck(pParse, SQLITE_PRAGMA, zLeft, zRight, zDb) ){ + goto pragma_out; + } + + /* + ** PRAGMA [database.]default_cache_size + ** PRAGMA [database.]default_cache_size=N + ** + ** The first form reports the current persistent setting for the + ** page cache size. The value returned is the maximum number of + ** pages in the page cache. The second form sets both the current + ** page cache size value and the persistent page cache size value + ** stored in the database file. + ** + ** The default cache size is stored in meta-value 2 of page 1 of the + ** database file. The cache size is actually the absolute value of + ** this memory location. The sign of meta-value 2 determines the + ** synchronous setting. A negative value means synchronous is off + ** and a positive value means synchronous is on. + */ + if( sqlite3StrICmp(zLeft,"default_cache_size")==0 ){ + static const VdbeOpList getCacheSize[] = { + { OP_ReadCookie, 0, 2, 0}, /* 0 */ + { OP_AbsValue, 0, 0, 0}, + { OP_Dup, 0, 0, 0}, + { OP_Integer, 0, 0, 0}, + { OP_Ne, 0, 6, 0}, + { OP_Integer, 0, 0, 0}, /* 5 */ + { OP_Callback, 1, 0, 0}, + }; + int addr; + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + if( !zRight ){ + sqlite3VdbeSetNumCols(v, 1); + sqlite3VdbeSetColName(v, 0, "cache_size", P3_STATIC); + addr = sqlite3VdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize); + sqlite3VdbeChangeP1(v, addr, iDb); + sqlite3VdbeChangeP1(v, addr+5, MAX_PAGES); + }else{ + int size = atoi(zRight); + if( size<0 ) size = -size; + sqlite3BeginWriteOperation(pParse, 0, iDb); + sqlite3VdbeAddOp(v, OP_Integer, size, 0); + sqlite3VdbeAddOp(v, OP_ReadCookie, iDb, 2); + addr = sqlite3VdbeAddOp(v, OP_Integer, 0, 0); + sqlite3VdbeAddOp(v, OP_Ge, 0, addr+3); + sqlite3VdbeAddOp(v, OP_Negative, 0, 0); + sqlite3VdbeAddOp(v, OP_SetCookie, iDb, 2); + pDb->cache_size = size; + sqlite3BtreeSetCacheSize(pDb->pBt, pDb->cache_size); + } + }else + + /* + ** PRAGMA [database.]page_size + ** PRAGMA [database.]page_size=N + ** + ** The first form reports the current setting for the + ** database page size in bytes. The second form sets the + ** database page size value. The value can only be set if + ** the database has not yet been created. + */ + if( sqlite3StrICmp(zLeft,"page_size")==0 ){ + Btree *pBt = pDb->pBt; + if( !zRight ){ + int size = pBt ? sqlite3BtreeGetPageSize(pBt) : 0; + returnSingleInt(pParse, "page_size", size); + }else{ + sqlite3BtreeSetPageSize(pBt, atoi(zRight), sqlite3BtreeGetReserve(pBt)); + } + }else + + /* + ** PRAGMA [database.]cache_size + ** PRAGMA [database.]cache_size=N + ** + ** The first form reports the current local setting for the + ** page cache size. The local setting can be different from + ** the persistent cache size value that is stored in the database + ** file itself. The value returned is the maximum number of + ** pages in the page cache. The second form sets the local + ** page cache size value. It does not change the persistent + ** cache size stored on the disk so the cache size will revert + ** to its default value when the database is closed and reopened. + ** N should be a positive integer. + */ + if( sqlite3StrICmp(zLeft,"cache_size")==0 ){ + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + if( !zRight ){ + returnSingleInt(pParse, "cache_size", pDb->cache_size); + }else{ + int size = atoi(zRight); + if( size<0 ) size = -size; + pDb->cache_size = size; + sqlite3BtreeSetCacheSize(pDb->pBt, pDb->cache_size); + } + }else + + /* + ** PRAGMA temp_store + ** PRAGMA temp_store = "default"|"memory"|"file" + ** + ** Return or set the local value of the temp_store flag. Changing + ** the local value does not make changes to the disk file and the default + ** value will be restored the next time the database is opened. + ** + ** Note that it is possible for the library compile-time options to + ** override this setting + */ + if( sqlite3StrICmp(zLeft, "temp_store")==0 ){ + if( !zRight ){ + returnSingleInt(pParse, "temp_store", db->temp_store); + }else{ + changeTempStorage(pParse, zRight); + } + }else + + /* + ** PRAGMA [database.]synchronous + ** PRAGMA [database.]synchronous=OFF|ON|NORMAL|FULL + ** + ** Return or set the local value of the synchronous flag. Changing + ** the local value does not make changes to the disk file and the + ** default value will be restored the next time the database is + ** opened. + */ + if( sqlite3StrICmp(zLeft,"synchronous")==0 ){ + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + if( !zRight ){ + returnSingleInt(pParse, "synchronous", pDb->safety_level-1); + }else{ + if( !db->autoCommit ){ + sqlite3ErrorMsg(pParse, + "Safety level may not be changed inside a transaction"); + }else{ + pDb->safety_level = getSafetyLevel(zRight)+1; + sqlite3BtreeSetSafetyLevel(pDb->pBt, pDb->safety_level); + } + } + }else + +#if 0 /* Used once during development. No longer needed */ + if( sqlite3StrICmp(zLeft, "trigger_overhead_test")==0 ){ + if( getBoolean(zRight) ){ + sqlite3_always_code_trigger_setup = 1; + }else{ + sqlite3_always_code_trigger_setup = 0; + } + }else +#endif + + if( flagPragma(pParse, zLeft, zRight) ){ + /* The flagPragma() subroutine also generates any necessary code + ** there is nothing more to do here */ + }else + + /* + ** PRAGMA table_info(
    ) + ** + ** Return a single row for each column of the named table. The columns of + ** the returned data set are: + ** + ** cid: Column id (numbered from left to right, starting at 0) + ** name: Column name + ** type: Column declaration type. + ** notnull: True if 'NOT NULL' is part of column declaration + ** dflt_value: The default value for the column, if any. + */ + if( sqlite3StrICmp(zLeft, "table_info")==0 && zRight ){ + Table *pTab; + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + pTab = sqlite3FindTable(db, zRight, zDb); + if( pTab ){ + int i; + sqlite3VdbeSetNumCols(v, 6); + sqlite3VdbeSetColName(v, 0, "cid", P3_STATIC); + sqlite3VdbeSetColName(v, 1, "name", P3_STATIC); + sqlite3VdbeSetColName(v, 2, "type", P3_STATIC); + sqlite3VdbeSetColName(v, 3, "notnull", P3_STATIC); + sqlite3VdbeSetColName(v, 4, "dflt_value", P3_STATIC); + sqlite3VdbeSetColName(v, 5, "pk", P3_STATIC); + sqlite3ViewGetColumnNames(pParse, pTab); + for(i=0; inCol; i++){ + sqlite3VdbeAddOp(v, OP_Integer, i, 0); + sqlite3VdbeOp3(v, OP_String8, 0, 0, pTab->aCol[i].zName, 0); + sqlite3VdbeOp3(v, OP_String8, 0, 0, + pTab->aCol[i].zType ? pTab->aCol[i].zType : "numeric", 0); + sqlite3VdbeAddOp(v, OP_Integer, pTab->aCol[i].notNull, 0); + sqlite3VdbeOp3(v, OP_String8, 0, 0, + pTab->aCol[i].zDflt, P3_STATIC); + sqlite3VdbeAddOp(v, OP_Integer, pTab->aCol[i].isPrimKey, 0); + sqlite3VdbeAddOp(v, OP_Callback, 6, 0); + } + } + }else + + if( sqlite3StrICmp(zLeft, "index_info")==0 && zRight ){ + Index *pIdx; + Table *pTab; + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + pIdx = sqlite3FindIndex(db, zRight, zDb); + if( pIdx ){ + int i; + pTab = pIdx->pTable; + sqlite3VdbeSetNumCols(v, 3); + sqlite3VdbeSetColName(v, 0, "seqno", P3_STATIC); + sqlite3VdbeSetColName(v, 1, "cid", P3_STATIC); + sqlite3VdbeSetColName(v, 2, "name", P3_STATIC); + for(i=0; inColumn; i++){ + int cnum = pIdx->aiColumn[i]; + sqlite3VdbeAddOp(v, OP_Integer, i, 0); + sqlite3VdbeAddOp(v, OP_Integer, cnum, 0); + assert( pTab->nCol>cnum ); + sqlite3VdbeOp3(v, OP_String8, 0, 0, pTab->aCol[cnum].zName, 0); + sqlite3VdbeAddOp(v, OP_Callback, 3, 0); + } + } + }else + + if( sqlite3StrICmp(zLeft, "index_list")==0 && zRight ){ + Index *pIdx; + Table *pTab; + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + pTab = sqlite3FindTable(db, zRight, zDb); + if( pTab ){ + v = sqlite3GetVdbe(pParse); + pIdx = pTab->pIndex; + if( pIdx ){ + int i = 0; + sqlite3VdbeSetNumCols(v, 3); + sqlite3VdbeSetColName(v, 0, "seq", P3_STATIC); + sqlite3VdbeSetColName(v, 1, "name", P3_STATIC); + sqlite3VdbeSetColName(v, 2, "unique", P3_STATIC); + while(pIdx){ + sqlite3VdbeAddOp(v, OP_Integer, i, 0); + sqlite3VdbeOp3(v, OP_String8, 0, 0, pIdx->zName, 0); + sqlite3VdbeAddOp(v, OP_Integer, pIdx->onError!=OE_None, 0); + sqlite3VdbeAddOp(v, OP_Callback, 3, 0); + ++i; + pIdx = pIdx->pNext; + } + } + } + }else + + if( sqlite3StrICmp(zLeft, "foreign_key_list")==0 && zRight ){ + FKey *pFK; + Table *pTab; + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + pTab = sqlite3FindTable(db, zRight, zDb); + if( pTab ){ + v = sqlite3GetVdbe(pParse); + pFK = pTab->pFKey; + if( pFK ){ + int i = 0; + sqlite3VdbeSetNumCols(v, 5); + sqlite3VdbeSetColName(v, 0, "id", P3_STATIC); + sqlite3VdbeSetColName(v, 1, "seq", P3_STATIC); + sqlite3VdbeSetColName(v, 2, "table", P3_STATIC); + sqlite3VdbeSetColName(v, 3, "from", P3_STATIC); + sqlite3VdbeSetColName(v, 4, "to", P3_STATIC); + while(pFK){ + int j; + for(j=0; jnCol; j++){ + sqlite3VdbeAddOp(v, OP_Integer, i, 0); + sqlite3VdbeAddOp(v, OP_Integer, j, 0); + sqlite3VdbeOp3(v, OP_String8, 0, 0, pFK->zTo, 0); + sqlite3VdbeOp3(v, OP_String8, 0, 0, + pTab->aCol[pFK->aCol[j].iFrom].zName, 0); + sqlite3VdbeOp3(v, OP_String8, 0, 0, pFK->aCol[j].zCol, 0); + sqlite3VdbeAddOp(v, OP_Callback, 5, 0); + } + ++i; + pFK = pFK->pNextFrom; + } + } + } + }else + + if( sqlite3StrICmp(zLeft, "database_list")==0 ){ + int i; + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + sqlite3VdbeSetNumCols(v, 3); + sqlite3VdbeSetColName(v, 0, "seq", P3_STATIC); + sqlite3VdbeSetColName(v, 1, "name", P3_STATIC); + sqlite3VdbeSetColName(v, 2, "file", P3_STATIC); + for(i=0; inDb; i++){ + if( db->aDb[i].pBt==0 ) continue; + assert( db->aDb[i].zName!=0 ); + sqlite3VdbeAddOp(v, OP_Integer, i, 0); + sqlite3VdbeOp3(v, OP_String8, 0, 0, db->aDb[i].zName, 0); + sqlite3VdbeOp3(v, OP_String8, 0, 0, + sqlite3BtreeGetFilename(db->aDb[i].pBt), 0); + sqlite3VdbeAddOp(v, OP_Callback, 3, 0); + } + }else + +#ifndef NDEBUG + if( sqlite3StrICmp(zLeft, "parser_trace")==0 ){ + extern void sqlite3ParserTrace(FILE*, char *); + if( getBoolean(zRight) ){ + sqlite3ParserTrace(stdout, "parser: "); + }else{ + sqlite3ParserTrace(0, 0); + } + }else +#endif + + if( sqlite3StrICmp(zLeft, "integrity_check")==0 ){ + int i, j, addr; + + /* Code that initializes the integrity check program. Set the + ** error count 0 + */ + static const VdbeOpList initCode[] = { + { OP_Integer, 0, 0, 0}, + { OP_MemStore, 0, 1, 0}, + }; + + /* Code that appears at the end of the integrity check. If no error + ** messages have been generated, output OK. Otherwise output the + ** error message + */ + static const VdbeOpList endCode[] = { + { OP_MemLoad, 0, 0, 0}, + { OP_Integer, 0, 0, 0}, + { OP_Ne, 0, 0, 0}, /* 2 */ + { OP_String8, 0, 0, "ok"}, + { OP_Callback, 1, 0, 0}, + }; + + /* Initialize the VDBE program */ + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + sqlite3VdbeSetNumCols(v, 1); + sqlite3VdbeSetColName(v, 0, "integrity_check", P3_STATIC); + sqlite3VdbeAddOpList(v, ArraySize(initCode), initCode); + + /* Do an integrity check on each database file */ + for(i=0; inDb; i++){ + HashElem *x; + int cnt = 0; + + sqlite3CodeVerifySchema(pParse, i); + + /* Do an integrity check of the B-Tree + */ + for(x=sqliteHashFirst(&db->aDb[i].tblHash); x; x=sqliteHashNext(x)){ + Table *pTab = sqliteHashData(x); + Index *pIdx; + sqlite3VdbeAddOp(v, OP_Integer, pTab->tnum, 0); + cnt++; + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( sqlite3CheckIndexCollSeq(pParse, pIdx) ) goto pragma_out; + sqlite3VdbeAddOp(v, OP_Integer, pIdx->tnum, 0); + cnt++; + } + } + assert( cnt>0 ); + sqlite3VdbeAddOp(v, OP_IntegrityCk, cnt, i); + sqlite3VdbeAddOp(v, OP_Dup, 0, 1); + addr = sqlite3VdbeOp3(v, OP_String8, 0, 0, "ok", P3_STATIC); + sqlite3VdbeAddOp(v, OP_Eq, 0, addr+6); + sqlite3VdbeOp3(v, OP_String8, 0, 0, + sqlite3MPrintf("*** in database %s ***\n", db->aDb[i].zName), + P3_DYNAMIC); + sqlite3VdbeAddOp(v, OP_Pull, 1, 0); + sqlite3VdbeAddOp(v, OP_Concat, 0, 1); + sqlite3VdbeAddOp(v, OP_Callback, 1, 0); + + /* Make sure all the indices are constructed correctly. + */ + sqlite3CodeVerifySchema(pParse, i); + for(x=sqliteHashFirst(&db->aDb[i].tblHash); x; x=sqliteHashNext(x)){ + Table *pTab = sqliteHashData(x); + Index *pIdx; + int loopTop; + + if( pTab->pIndex==0 ) continue; + sqlite3OpenTableAndIndices(pParse, pTab, 1, OP_OpenRead); + sqlite3VdbeAddOp(v, OP_Integer, 0, 0); + sqlite3VdbeAddOp(v, OP_MemStore, 1, 1); + loopTop = sqlite3VdbeAddOp(v, OP_Rewind, 1, 0); + sqlite3VdbeAddOp(v, OP_MemIncr, 1, 0); + for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + int jmp2; + static const VdbeOpList idxErr[] = { + { OP_MemIncr, 0, 0, 0}, + { OP_String8, 0, 0, "rowid "}, + { OP_Recno, 1, 0, 0}, + { OP_String8, 0, 0, " missing from index "}, + { OP_String8, 0, 0, 0}, /* 4 */ + { OP_Concat, 2, 0, 0}, + { OP_Callback, 1, 0, 0}, + }; + sqlite3GenerateIndexKey(v, pIdx, 1); + jmp2 = sqlite3VdbeAddOp(v, OP_Found, j+2, 0); + addr = sqlite3VdbeAddOpList(v, ArraySize(idxErr), idxErr); + sqlite3VdbeChangeP3(v, addr+4, pIdx->zName, P3_STATIC); + sqlite3VdbeChangeP2(v, jmp2, sqlite3VdbeCurrentAddr(v)); + } + sqlite3VdbeAddOp(v, OP_Next, 1, loopTop+1); + sqlite3VdbeChangeP2(v, loopTop, sqlite3VdbeCurrentAddr(v)); + for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + static const VdbeOpList cntIdx[] = { + { OP_Integer, 0, 0, 0}, + { OP_MemStore, 2, 1, 0}, + { OP_Rewind, 0, 0, 0}, /* 2 */ + { OP_MemIncr, 2, 0, 0}, + { OP_Next, 0, 0, 0}, /* 4 */ + { OP_MemLoad, 1, 0, 0}, + { OP_MemLoad, 2, 0, 0}, + { OP_Eq, 0, 0, 0}, /* 7 */ + { OP_MemIncr, 0, 0, 0}, + { OP_String8, 0, 0, "wrong # of entries in index "}, + { OP_String8, 0, 0, 0}, /* 10 */ + { OP_Concat, 0, 0, 0}, + { OP_Callback, 1, 0, 0}, + }; + if( pIdx->tnum==0 ) continue; + addr = sqlite3VdbeAddOpList(v, ArraySize(cntIdx), cntIdx); + sqlite3VdbeChangeP1(v, addr+2, j+2); + sqlite3VdbeChangeP2(v, addr+2, addr+5); + sqlite3VdbeChangeP1(v, addr+4, j+2); + sqlite3VdbeChangeP2(v, addr+4, addr+3); + sqlite3VdbeChangeP2(v, addr+7, addr+ArraySize(cntIdx)); + sqlite3VdbeChangeP3(v, addr+10, pIdx->zName, P3_STATIC); + } + } + } + addr = sqlite3VdbeAddOpList(v, ArraySize(endCode), endCode); + sqlite3VdbeChangeP2(v, addr+2, addr+ArraySize(endCode)); + }else + /* + ** PRAGMA encoding + ** PRAGMA encoding = "utf-8"|"utf-16"|"utf-16le"|"utf-16be" + ** + ** In it's first form, this pragma returns the encoding of the main + ** database. If the database is not initialized, it is initialized now. + ** + ** The second form of this pragma is a no-op if the main database file + ** has not already been initialized. In this case it sets the default + ** encoding that will be used for the main database file if a new file + ** is created. If an existing main database file is opened, then the + ** default text encoding for the existing database is used. + ** + ** In all cases new databases created using the ATTACH command are + ** created to use the same default text encoding as the main database. If + ** the main database has not been initialized and/or created when ATTACH + ** is executed, this is done before the ATTACH operation. + ** + ** In the second form this pragma sets the text encoding to be used in + ** new database files created using this database handle. It is only + ** useful if invoked immediately after the main database i + */ + if( sqlite3StrICmp(zLeft, "encoding")==0 ){ + static struct EncName { + char *zName; + u8 enc; + } encnames[] = { + { "UTF-8", SQLITE_UTF8 }, + { "UTF8", SQLITE_UTF8 }, + { "UTF-16le", SQLITE_UTF16LE }, + { "UTF16le", SQLITE_UTF16LE }, + { "UTF-16be", SQLITE_UTF16BE }, + { "UTF16be", SQLITE_UTF16BE }, + { "UTF-16", 0 /* Filled in at run-time */ }, + { "UTF16", 0 /* Filled in at run-time */ }, + { 0, 0 } + }; + struct EncName *pEnc; + encnames[6].enc = encnames[7].enc = SQLITE_UTF16NATIVE; + if( !zRight ){ /* "PRAGMA encoding" */ + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + sqlite3VdbeSetNumCols(v, 1); + sqlite3VdbeSetColName(v, 0, "encoding", P3_STATIC); + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + for(pEnc=&encnames[0]; pEnc->zName; pEnc++){ + if( pEnc->enc==pParse->db->enc ){ + sqlite3VdbeChangeP3(v, -1, pEnc->zName, P3_STATIC); + break; + } + } + sqlite3VdbeAddOp(v, OP_Callback, 1, 0); + }else{ /* "PRAGMA encoding = XXX" */ + /* Only change the value of sqlite.enc if the database handle is not + ** initialized. If the main database exists, the new sqlite.enc value + ** will be overwritten when the schema is next loaded. If it does not + ** already exists, it will be created to use the new encoding value. + */ + if( !(pParse->db->flags&SQLITE_Initialized) ){ + for(pEnc=&encnames[0]; pEnc->zName; pEnc++){ + if( 0==sqlite3StrICmp(zRight, pEnc->zName) ){ + pParse->db->enc = pEnc->enc; + break; + } + } + if( !pEnc->zName ){ + sqlite3ErrorMsg(pParse, "unsupported encoding: %s", zRight); + } + } + } + }else + +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + /* + ** Report the current state of file logs for all databases + */ + if( sqlite3StrICmp(zLeft, "lock_status")==0 ){ + static const char *const azLockName[] = { + "unlocked", "shared", "reserved", "pending", "exclusive" + }; + int i; + Vdbe *v = sqlite3GetVdbe(pParse); + sqlite3VdbeSetNumCols(v, 2); + sqlite3VdbeSetColName(v, 0, "database", P3_STATIC); + sqlite3VdbeSetColName(v, 1, "status", P3_STATIC); + for(i=0; inDb; i++){ + Btree *pBt; + Pager *pPager; + if( db->aDb[i].zName==0 ) continue; + sqlite3VdbeOp3(v, OP_String, 0, 0, db->aDb[i].zName, P3_STATIC); + pBt = db->aDb[i].pBt; + if( pBt==0 || (pPager = sqlite3BtreePager(pBt))==0 ){ + sqlite3VdbeOp3(v, OP_String, 0, 0, "closed", P3_STATIC); + }else{ + int j = sqlite3pager_lockstate(pPager); + sqlite3VdbeOp3(v, OP_String, 0, 0, + (j>=0 && j<=4) ? azLockName[j] : "unknown", P3_STATIC); + } + sqlite3VdbeAddOp(v, OP_Callback, 2, 0); + } + }else +#endif + + {} +pragma_out: + sqliteFree(zLeft); + sqliteFree(zRight); +} diff --git a/kopete/plugins/statistics/sqlite/printf.c b/kopete/plugins/statistics/sqlite/printf.c new file mode 100644 index 00000000..43e12863 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/printf.c @@ -0,0 +1,825 @@ +/* +** The "printf" code that follows dates from the 1980's. It is in +** the public domain. The original comments are included here for +** completeness. They are very out-of-date but might be useful as +** an historical reference. Most of the "enhancements" have been backed +** out so that the functionality is now the same as standard printf(). +** +************************************************************************** +** +** The following modules is an enhanced replacement for the "printf" subroutines +** found in the standard C library. The following enhancements are +** supported: +** +** + Additional functions. The standard set of "printf" functions +** includes printf, fprintf, sprintf, vprintf, vfprintf, and +** vsprintf. This module adds the following: +** +** * snprintf -- Works like sprintf, but has an extra argument +** which is the size of the buffer written to. +** +** * mprintf -- Similar to sprintf. Writes output to memory +** obtained from malloc. +** +** * xprintf -- Calls a function to dispose of output. +** +** * nprintf -- No output, but returns the number of characters +** that would have been output by printf. +** +** * A v- version (ex: vsnprintf) of every function is also +** supplied. +** +** + A few extensions to the formatting notation are supported: +** +** * The "=" flag (similar to "-") causes the output to be +** be centered in the appropriately sized field. +** +** * The %b field outputs an integer in binary notation. +** +** * The %c field now accepts a precision. The character output +** is repeated by the number of times the precision specifies. +** +** * The %' field works like %c, but takes as its character the +** next character of the format string, instead of the next +** argument. For example, printf("%.78'-") prints 78 minus +** signs, the same as printf("%.78c",'-'). +** +** + When compiled using GCC on a SPARC, this version of printf is +** faster than the library printf for SUN OS 4.1. +** +** + All functions are fully reentrant. +** +*/ +#include "sqliteInt.h" + +/* +** Conversion types fall into various categories as defined by the +** following enumeration. +*/ +#define etRADIX 1 /* Integer types. %d, %x, %o, and so forth */ +#define etFLOAT 2 /* Floating point. %f */ +#define etEXP 3 /* Exponentional notation. %e and %E */ +#define etGENERIC 4 /* Floating or exponential, depending on exponent. %g */ +#define etSIZE 5 /* Return number of characters processed so far. %n */ +#define etSTRING 6 /* Strings. %s */ +#define etDYNSTRING 7 /* Dynamically allocated strings. %z */ +#define etPERCENT 8 /* Percent symbol. %% */ +#define etCHARX 9 /* Characters. %c */ +#define etERROR 10 /* Used to indicate no such conversion type */ +/* The rest are extensions, not normally found in printf() */ +#define etCHARLIT 11 /* Literal characters. %' */ +#define etSQLESCAPE 12 /* Strings with '\'' doubled. %q */ +#define etSQLESCAPE2 13 /* Strings with '\'' doubled and enclosed in '', + NULL pointers replaced by SQL NULL. %Q */ +#define etTOKEN 14 /* a pointer to a Token structure */ +#define etSRCLIST 15 /* a pointer to a SrcList */ +#define etPOINTER 16 /* The %p conversion */ + + +/* +** An "etByte" is an 8-bit unsigned value. +*/ +typedef unsigned char etByte; + +/* +** Each builtin conversion character (ex: the 'd' in "%d") is described +** by an instance of the following structure +*/ +typedef struct et_info { /* Information about each format field */ + char fmttype; /* The format field code letter */ + etByte base; /* The base for radix conversion */ + etByte flags; /* One or more of FLAG_ constants below */ + etByte type; /* Conversion paradigm */ + etByte charset; /* Offset into aDigits[] of the digits string */ + etByte prefix; /* Offset into aPrefix[] of the prefix string */ +} et_info; + +/* +** Allowed values for et_info.flags +*/ +#define FLAG_SIGNED 1 /* True if the value to convert is signed */ +#define FLAG_INTERN 2 /* True if for internal use only */ + + +/* +** The following table is searched linearly, so it is good to put the +** most frequently used conversion types first. +*/ +static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; +static const char aPrefix[] = "-x0\000X0"; +static const et_info fmtinfo[] = { + { 'd', 10, 1, etRADIX, 0, 0 }, + { 's', 0, 0, etSTRING, 0, 0 }, + { 'z', 0, 2, etDYNSTRING, 0, 0 }, + { 'q', 0, 0, etSQLESCAPE, 0, 0 }, + { 'Q', 0, 0, etSQLESCAPE2, 0, 0 }, + { 'c', 0, 0, etCHARX, 0, 0 }, + { 'o', 8, 0, etRADIX, 0, 2 }, + { 'u', 10, 0, etRADIX, 0, 0 }, + { 'x', 16, 0, etRADIX, 16, 1 }, + { 'X', 16, 0, etRADIX, 0, 4 }, + { 'f', 0, 1, etFLOAT, 0, 0 }, + { 'e', 0, 1, etEXP, 30, 0 }, + { 'E', 0, 1, etEXP, 14, 0 }, + { 'g', 0, 1, etGENERIC, 30, 0 }, + { 'G', 0, 1, etGENERIC, 14, 0 }, + { 'i', 10, 1, etRADIX, 0, 0 }, + { 'n', 0, 0, etSIZE, 0, 0 }, + { '%', 0, 0, etPERCENT, 0, 0 }, + { 'p', 16, 0, etPOINTER, 0, 1 }, + { 'T', 0, 2, etTOKEN, 0, 0 }, + { 'S', 0, 2, etSRCLIST, 0, 0 }, +}; +#define etNINFO (sizeof(fmtinfo)/sizeof(fmtinfo[0])) + +/* +** If NOFLOATINGPOINT is defined, then none of the floating point +** conversions will work. +*/ +#ifndef etNOFLOATINGPOINT +/* +** "*val" is a double such that 0.1 <= *val < 10.0 +** Return the ascii code for the leading digit of *val, then +** multiply "*val" by 10.0 to renormalize. +** +** Example: +** input: *val = 3.14159 +** output: *val = 1.4159 function return = '3' +** +** The counter *cnt is incremented each time. After counter exceeds +** 16 (the number of significant digits in a 64-bit float) '0' is +** always returned. +*/ +static int et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ + int digit; + LONGDOUBLE_TYPE d; + if( (*cnt)++ >= 16 ) return '0'; + digit = (int)*val; + d = digit; + digit += '0'; + *val = (*val - d)*10.0; + return digit; +} +#endif + +#define etBUFSIZE 1000 /* Size of the output buffer */ + +/* +** The root program. All variations call this core. +** +** INPUTS: +** func This is a pointer to a function taking three arguments +** 1. A pointer to anything. Same as the "arg" parameter. +** 2. A pointer to the list of characters to be output +** (Note, this list is NOT null terminated.) +** 3. An integer number of characters to be output. +** (Note: This number might be zero.) +** +** arg This is the pointer to anything which will be passed as the +** first argument to "func". Use it for whatever you like. +** +** fmt This is the format string, as in the usual print. +** +** ap This is a pointer to a list of arguments. Same as in +** vfprint. +** +** OUTPUTS: +** The return value is the total number of characters sent to +** the function "func". Returns -1 on a error. +** +** Note that the order in which automatic variables are declared below +** seems to make a big difference in determining how fast this beast +** will run. +*/ +static int vxprintf( + void (*func)(void*,const char*,int), /* Consumer of text */ + void *arg, /* First argument to the consumer */ + int useExtended, /* Allow extended %-conversions */ + const char *fmt, /* Format string */ + va_list ap /* arguments */ +){ + int c; /* Next character in the format string */ + char *bufpt; /* Pointer to the conversion buffer */ + int precision; /* Precision of the current field */ + int length; /* Length of the field */ + int idx; /* A general purpose loop counter */ + int count; /* Total number of characters output */ + int width; /* Width of the current field */ + etByte flag_leftjustify; /* True if "-" flag is present */ + etByte flag_plussign; /* True if "+" flag is present */ + etByte flag_blanksign; /* True if " " flag is present */ + etByte flag_alternateform; /* True if "#" flag is present */ + etByte flag_zeropad; /* True if field width constant starts with zero */ + etByte flag_long; /* True if "l" flag is present */ + etByte flag_longlong; /* True if the "ll" flag is present */ + UINT64_TYPE longvalue; /* Value for integer types */ + LONGDOUBLE_TYPE realvalue; /* Value for real types */ + const et_info *infop; /* Pointer to the appropriate info structure */ + char buf[etBUFSIZE]; /* Conversion buffer */ + char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ + etByte errorflag = 0; /* True if an error is encountered */ + etByte xtype; /* Conversion paradigm */ + char *zExtra; /* Extra memory used for etTCLESCAPE conversions */ + static const char spaces[] = + " "; +#define etSPACESIZE (sizeof(spaces)-1) +#ifndef etNOFLOATINGPOINT + int exp; /* exponent of real numbers */ + double rounder; /* Used for rounding floating point values */ + etByte flag_dp; /* True if decimal point should be shown */ + etByte flag_rtz; /* True if trailing zeros should be removed */ + etByte flag_exp; /* True to force display of the exponent */ + int nsd; /* Number of significant digits returned */ +#endif + + func(arg,"",0); + count = length = 0; + bufpt = 0; + for(; (c=(*fmt))!=0; ++fmt){ + if( c!='%' ){ + int amt; + bufpt = (char *)fmt; + amt = 1; + while( (c=(*++fmt))!='%' && c!=0 ) amt++; + (*func)(arg,bufpt,amt); + count += amt; + if( c==0 ) break; + } + if( (c=(*++fmt))==0 ){ + errorflag = 1; + (*func)(arg,"%",1); + count++; + break; + } + /* Find out what flags are present */ + flag_leftjustify = flag_plussign = flag_blanksign = + flag_alternateform = flag_zeropad = 0; + do{ + switch( c ){ + case '-': flag_leftjustify = 1; c = 0; break; + case '+': flag_plussign = 1; c = 0; break; + case ' ': flag_blanksign = 1; c = 0; break; + case '#': flag_alternateform = 1; c = 0; break; + case '0': flag_zeropad = 1; c = 0; break; + default: break; + } + }while( c==0 && (c=(*++fmt))!=0 ); + /* Get the field width */ + width = 0; + if( c=='*' ){ + width = va_arg(ap,int); + if( width<0 ){ + flag_leftjustify = 1; + width = -width; + } + c = *++fmt; + }else{ + while( c>='0' && c<='9' ){ + width = width*10 + c - '0'; + c = *++fmt; + } + } + if( width > etBUFSIZE-10 ){ + width = etBUFSIZE-10; + } + /* Get the precision */ + if( c=='.' ){ + precision = 0; + c = *++fmt; + if( c=='*' ){ + precision = va_arg(ap,int); + if( precision<0 ) precision = -precision; + c = *++fmt; + }else{ + while( c>='0' && c<='9' ){ + precision = precision*10 + c - '0'; + c = *++fmt; + } + } + /* Limit the precision to prevent overflowing buf[] during conversion */ + if( precision>etBUFSIZE-40 ) precision = etBUFSIZE-40; + }else{ + precision = -1; + } + /* Get the conversion type modifier */ + if( c=='l' ){ + flag_long = 1; + c = *++fmt; + if( c=='l' ){ + flag_longlong = 1; + c = *++fmt; + }else{ + flag_longlong = 0; + } + }else{ + flag_long = flag_longlong = 0; + } + /* Fetch the info entry for the field */ + infop = 0; + xtype = etERROR; + for(idx=0; idxflags & FLAG_INTERN)==0 ){ + xtype = infop->type; + } + break; + } + } + zExtra = 0; + + /* + ** At this point, variables are initialized as follows: + ** + ** flag_alternateform TRUE if a '#' is present. + ** flag_plussign TRUE if a '+' is present. + ** flag_leftjustify TRUE if a '-' is present or if the + ** field width was negative. + ** flag_zeropad TRUE if the width began with 0. + ** flag_long TRUE if the letter 'l' (ell) prefixed + ** the conversion character. + ** flag_longlong TRUE if the letter 'll' (ell ell) prefixed + ** the conversion character. + ** flag_blanksign TRUE if a ' ' is present. + ** width The specified field width. This is + ** always non-negative. Zero is the default. + ** precision The specified precision. The default + ** is -1. + ** xtype The class of the conversion. + ** infop Pointer to the appropriate info struct. + */ + switch( xtype ){ + case etPOINTER: + flag_longlong = sizeof(char*)==sizeof(i64); + flag_long = sizeof(char*)==sizeof(long int); + /* Fall through into the next case */ + case etRADIX: + if( infop->flags & FLAG_SIGNED ){ + i64 v; + if( flag_longlong ) v = va_arg(ap,i64); + else if( flag_long ) v = va_arg(ap,long int); + else v = va_arg(ap,int); + if( v<0 ){ + longvalue = -v; + prefix = '-'; + }else{ + longvalue = v; + if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + } + }else{ + if( flag_longlong ) longvalue = va_arg(ap,u64); + else if( flag_long ) longvalue = va_arg(ap,unsigned long int); + else longvalue = va_arg(ap,unsigned int); + prefix = 0; + } + if( longvalue==0 ) flag_alternateform = 0; + if( flag_zeropad && precisioncharset]; + base = infop->base; + do{ /* Convert to ascii */ + *(--bufpt) = cset[longvalue%base]; + longvalue = longvalue/base; + }while( longvalue>0 ); + } + length = &buf[etBUFSIZE-1]-bufpt; + for(idx=precision-length; idx>0; idx--){ + *(--bufpt) = '0'; /* Zero pad */ + } + if( prefix ) *(--bufpt) = prefix; /* Add sign */ + if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ + const char *pre; + char x; + pre = &aPrefix[infop->prefix]; + if( *bufpt!=pre[0] ){ + for(; (x=(*pre))!=0; pre++) *(--bufpt) = x; + } + } + length = &buf[etBUFSIZE-1]-bufpt; + break; + case etFLOAT: + case etEXP: + case etGENERIC: + realvalue = va_arg(ap,double); +#ifndef etNOFLOATINGPOINT + if( precision<0 ) precision = 6; /* Set default precision */ + if( precision>etBUFSIZE-10 ) precision = etBUFSIZE-10; + if( realvalue<0.0 ){ + realvalue = -realvalue; + prefix = '-'; + }else{ + if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + } + if( infop->type==etGENERIC && precision>0 ) precision--; + rounder = 0.0; +#if 0 + /* Rounding works like BSD when the constant 0.4999 is used. Wierd! */ + for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); +#else + /* It makes more sense to use 0.5 */ + for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1); +#endif + if( infop->type==etFLOAT ) realvalue += rounder; + /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ + exp = 0; + if( realvalue>0.0 ){ + while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } + while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } + while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } + while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } + if( exp>350 || exp<-350 ){ + bufpt = "NaN"; + length = 3; + break; + } + } + bufpt = buf; + /* + ** If the field type is etGENERIC, then convert to either etEXP + ** or etFLOAT, as appropriate. + */ + flag_exp = xtype==etEXP; + if( xtype!=etFLOAT ){ + realvalue += rounder; + if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } + } + if( xtype==etGENERIC ){ + flag_rtz = !flag_alternateform; + if( exp<-4 || exp>precision ){ + xtype = etEXP; + }else{ + precision = precision - exp; + xtype = etFLOAT; + } + }else{ + flag_rtz = 0; + } + /* + ** The "exp+precision" test causes output to be of type etEXP if + ** the precision is too large to fit in buf[]. + */ + nsd = 0; + if( xtype==etFLOAT && exp+precision0 || flag_alternateform); + if( prefix ) *(bufpt++) = prefix; /* Sign */ + if( exp<0 ) *(bufpt++) = '0'; /* Digits before "." */ + else for(; exp>=0; exp--) *(bufpt++) = et_getdigit(&realvalue,&nsd); + if( flag_dp ) *(bufpt++) = '.'; /* The decimal point */ + for(exp++; exp<0 && precision>0; precision--, exp++){ + *(bufpt++) = '0'; + } + while( (precision--)>0 ) *(bufpt++) = et_getdigit(&realvalue,&nsd); + *(bufpt--) = 0; /* Null terminate */ + if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */ + while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; + if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; + } + bufpt++; /* point to next free slot */ + }else{ /* etEXP or etGENERIC */ + flag_dp = (precision>0 || flag_alternateform); + if( prefix ) *(bufpt++) = prefix; /* Sign */ + *(bufpt++) = et_getdigit(&realvalue,&nsd); /* First digit */ + if( flag_dp ) *(bufpt++) = '.'; /* Decimal point */ + while( (precision--)>0 ) *(bufpt++) = et_getdigit(&realvalue,&nsd); + bufpt--; /* point to last digit */ + if( flag_rtz && flag_dp ){ /* Remove tail zeros */ + while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; + if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; + } + bufpt++; /* point to next free slot */ + if( exp || flag_exp ){ + *(bufpt++) = aDigits[infop->charset]; + if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; } /* sign of exp */ + else { *(bufpt++) = '+'; } + if( exp>=100 ){ + *(bufpt++) = (exp/100)+'0'; /* 100's digit */ + exp %= 100; + } + *(bufpt++) = exp/10+'0'; /* 10's digit */ + *(bufpt++) = exp%10+'0'; /* 1's digit */ + } + } + /* The converted number is in buf[] and zero terminated. Output it. + ** Note that the number is in the usual order, not reversed as with + ** integer conversions. */ + length = bufpt-buf; + bufpt = buf; + + /* Special case: Add leading zeros if the flag_zeropad flag is + ** set and we are not left justified */ + if( flag_zeropad && !flag_leftjustify && length < width){ + int i; + int nPad = width - length; + for(i=width; i>=nPad; i--){ + bufpt[i] = bufpt[i-nPad]; + } + i = prefix!=0; + while( nPad-- ) bufpt[i++] = '0'; + length = width; + } +#endif + break; + case etSIZE: + *(va_arg(ap,int*)) = count; + length = width = 0; + break; + case etPERCENT: + buf[0] = '%'; + bufpt = buf; + length = 1; + break; + case etCHARLIT: + case etCHARX: + c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt); + if( precision>=0 ){ + for(idx=1; idx=0 && precisionetBUFSIZE ){ + bufpt = zExtra = sqliteMalloc( n ); + if( bufpt==0 ) return -1; + }else{ + bufpt = buf; + } + j = 0; + if( !isnull && xtype==etSQLESCAPE2 ) bufpt[j++] = '\''; + for(i=0; (c=arg[i])!=0; i++){ + bufpt[j++] = c; + if( c=='\'' ) bufpt[j++] = c; + } + if( !isnull && xtype==etSQLESCAPE2 ) bufpt[j++] = '\''; + bufpt[j] = 0; + length = j; + if( precision>=0 && precisionz ){ + (*func)(arg, pToken->z, pToken->n); + } + length = width = 0; + break; + } + case etSRCLIST: { + SrcList *pSrc = va_arg(ap, SrcList*); + int k = va_arg(ap, int); + struct SrcList_item *pItem = &pSrc->a[k]; + assert( k>=0 && knSrc ); + if( pItem->zDatabase && pItem->zDatabase[0] ){ + (*func)(arg, pItem->zDatabase, strlen(pItem->zDatabase)); + (*func)(arg, ".", 1); + } + (*func)(arg, pItem->zName, strlen(pItem->zName)); + length = width = 0; + break; + } + case etERROR: + buf[0] = '%'; + buf[1] = c; + errorflag = 0; + idx = 1+(c!=0); + (*func)(arg,"%",idx); + count += idx; + if( c==0 ) fmt--; + break; + }/* End switch over the format type */ + /* + ** The text of the conversion is pointed to by "bufpt" and is + ** "length" characters long. The field width is "width". Do + ** the output. + */ + if( !flag_leftjustify ){ + register int nspace; + nspace = width-length; + if( nspace>0 ){ + count += nspace; + while( nspace>=etSPACESIZE ){ + (*func)(arg,spaces,etSPACESIZE); + nspace -= etSPACESIZE; + } + if( nspace>0 ) (*func)(arg,spaces,nspace); + } + } + if( length>0 ){ + (*func)(arg,bufpt,length); + count += length; + } + if( flag_leftjustify ){ + register int nspace; + nspace = width-length; + if( nspace>0 ){ + count += nspace; + while( nspace>=etSPACESIZE ){ + (*func)(arg,spaces,etSPACESIZE); + nspace -= etSPACESIZE; + } + if( nspace>0 ) (*func)(arg,spaces,nspace); + } + } + if( zExtra ){ + sqliteFree(zExtra); + } + }/* End for loop over the format string */ + return errorflag ? -1 : count; +} /* End of function */ + + +/* This structure is used to store state information about the +** write to memory that is currently in progress. +*/ +struct sgMprintf { + char *zBase; /* A base allocation */ + char *zText; /* The string collected so far */ + int nChar; /* Length of the string so far */ + int nTotal; /* Output size if unconstrained */ + int nAlloc; /* Amount of space allocated in zText */ + void *(*xRealloc)(void*,int); /* Function used to realloc memory */ +}; + +/* +** This function implements the callback from vxprintf. +** +** This routine add nNewChar characters of text in zNewText to +** the sgMprintf structure pointed to by "arg". +*/ +static void mout(void *arg, const char *zNewText, int nNewChar){ + struct sgMprintf *pM = (struct sgMprintf*)arg; + pM->nTotal += nNewChar; + if( pM->nChar + nNewChar + 1 > pM->nAlloc ){ + if( pM->xRealloc==0 ){ + nNewChar = pM->nAlloc - pM->nChar - 1; + }else{ + pM->nAlloc = pM->nChar + nNewChar*2 + 1; + if( pM->zText==pM->zBase ){ + pM->zText = pM->xRealloc(0, pM->nAlloc); + if( pM->zText && pM->nChar ){ + memcpy(pM->zText, pM->zBase, pM->nChar); + } + }else{ + pM->zText = pM->xRealloc(pM->zText, pM->nAlloc); + } + } + } + if( pM->zText ){ + if( nNewChar>0 ){ + memcpy(&pM->zText[pM->nChar], zNewText, nNewChar); + pM->nChar += nNewChar; + } + pM->zText[pM->nChar] = 0; + } +} + +/* +** This routine is a wrapper around xprintf() that invokes mout() as +** the consumer. +*/ +static char *base_vprintf( + void *(*xRealloc)(void*,int), /* Routine to realloc memory. May be NULL */ + int useInternal, /* Use internal %-conversions if true */ + char *zInitBuf, /* Initially write here, before mallocing */ + int nInitBuf, /* Size of zInitBuf[] */ + const char *zFormat, /* format string */ + va_list ap /* arguments */ +){ + struct sgMprintf sM; + sM.zBase = sM.zText = zInitBuf; + sM.nChar = sM.nTotal = 0; + sM.nAlloc = nInitBuf; + sM.xRealloc = xRealloc; + vxprintf(mout, &sM, useInternal, zFormat, ap); + if( xRealloc ){ + if( sM.zText==sM.zBase ){ + sM.zText = xRealloc(0, sM.nChar+1); + if( sM.zText ){ + memcpy(sM.zText, sM.zBase, sM.nChar+1); + } + }else if( sM.nAlloc>sM.nChar+10 ){ + sM.zText = xRealloc(sM.zText, sM.nChar+1); + } + } + return sM.zText; +} + +/* +** Realloc that is a real function, not a macro. +*/ +static void *printf_realloc(void *old, int size){ + return sqliteRealloc(old,size); +} + +/* +** Print into memory obtained from sqliteMalloc(). Use the internal +** %-conversion extensions. +*/ +char *sqlite3VMPrintf(const char *zFormat, va_list ap){ + char zBase[1000]; + return base_vprintf(printf_realloc, 1, zBase, sizeof(zBase), zFormat, ap); +} + +/* +** Print into memory obtained from sqliteMalloc(). Use the internal +** %-conversion extensions. +*/ +char *sqlite3MPrintf(const char *zFormat, ...){ + va_list ap; + char *z; + char zBase[1000]; + va_start(ap, zFormat); + z = base_vprintf(printf_realloc, 1, zBase, sizeof(zBase), zFormat, ap); + va_end(ap); + return z; +} + +/* +** Print into memory obtained from malloc(). Do not use the internal +** %-conversion extensions. This routine is for use by external users. +*/ +char *sqlite3_mprintf(const char *zFormat, ...){ + va_list ap; + char *z; + char zBuf[200]; + + va_start(ap,zFormat); + z = base_vprintf((void*(*)(void*,int))realloc, 0, + zBuf, sizeof(zBuf), zFormat, ap); + va_end(ap); + return z; +} + +/* This is the varargs version of sqlite3_mprintf. +*/ +char *sqlite3_vmprintf(const char *zFormat, va_list ap){ + char zBuf[200]; + return base_vprintf((void*(*)(void*,int))realloc, 0, + zBuf, sizeof(zBuf), zFormat, ap); +} + +/* +** sqlite3_snprintf() works like snprintf() except that it ignores the +** current locale settings. This is important for SQLite because we +** are not able to use a "," as the decimal point in place of "." as +** specified by some locales. +*/ +char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){ + char *z; + va_list ap; + + va_start(ap,zFormat); + z = base_vprintf(0, 0, zBuf, n, zFormat, ap); + va_end(ap); + return z; +} + +#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG) +/* +** A version of printf() that understands %lld. Used for debugging. +** The printf() built into some versions of windows does not understand %lld +** and segfaults if you give it a long long int. +*/ +void sqlite3DebugPrintf(const char *zFormat, ...){ + extern int getpid(void); + va_list ap; + char zBuf[500]; + va_start(ap, zFormat); + base_vprintf(0, 0, zBuf, sizeof(zBuf), zFormat, ap); + va_end(ap); + fprintf(stdout,"%d: %s", getpid(), zBuf); + fflush(stdout); +} +#endif diff --git a/kopete/plugins/statistics/sqlite/random.c b/kopete/plugins/statistics/sqlite/random.c new file mode 100644 index 00000000..de74e291 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/random.c @@ -0,0 +1,100 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code to implement a pseudo-random number +** generator (PRNG) for SQLite. +** +** Random numbers are used by some of the database backends in order +** to generate random integer keys for tables or random filenames. +** +** $Id$ +*/ +#include "sqliteInt.h" +#include "os.h" + + +/* +** Get a single 8-bit random value from the RC4 PRNG. The Mutex +** must be held while executing this routine. +** +** Why not just use a library random generator like lrand48() for this? +** Because the OP_NewRecno opcode in the VDBE depends on having a very +** good source of random numbers. The lrand48() library function may +** well be good enough. But maybe not. Or maybe lrand48() has some +** subtle problems on some systems that could cause problems. It is hard +** to know. To minimize the risk of problems due to bad lrand48() +** implementations, SQLite uses this random number generator based +** on RC4, which we know works very well. +*/ +static int randomByte(){ + unsigned char t; + + /* All threads share a single random number generator. + ** This structure is the current state of the generator. + */ + static struct { + unsigned char isInit; /* True if initialized */ + unsigned char i, j; /* State variables */ + unsigned char s[256]; /* State variables */ + } prng; + + /* Initialize the state of the random number generator once, + ** the first time this routine is called. The seed value does + ** not need to contain a lot of randomness since we are not + ** trying to do secure encryption or anything like that... + ** + ** Nothing in this file or anywhere else in SQLite does any kind of + ** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random + ** number generator) not as an encryption device. + */ + if( !prng.isInit ){ + int i; + char k[256]; + prng.j = 0; + prng.i = 0; + sqlite3OsRandomSeed(k); + for(i=0; i<256; i++){ + prng.s[i] = i; + } + for(i=0; i<256; i++){ + prng.j += prng.s[i] + k[i]; + t = prng.s[prng.j]; + prng.s[prng.j] = prng.s[i]; + prng.s[i] = t; + } + prng.isInit = 1; + } + + /* Generate and return single random byte + */ + prng.i++; + t = prng.s[prng.i]; + prng.j += t; + prng.s[prng.i] = prng.s[prng.j]; + prng.s[prng.j] = t; + t += prng.s[prng.i]; + return prng.s[t]; +} + +/* +** Return N random bytes. +*/ +void sqlite3Randomness(int N, void *pBuf){ + unsigned char *zBuf = pBuf; + sqlite3OsEnterMutex(); + while( N-- ){ + *(zBuf++) = randomByte(); + } + sqlite3OsLeaveMutex(); +} + + + diff --git a/kopete/plugins/statistics/sqlite/select.c b/kopete/plugins/statistics/sqlite/select.c new file mode 100644 index 00000000..8bee7897 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/select.c @@ -0,0 +1,2628 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains C code routines that are called by the parser +** to handle SELECT statements in SQLite. +** +** $Id$ +*/ +#include "sqliteInt.h" + + +/* +** Allocate a new Select structure and return a pointer to that +** structure. +*/ +Select *sqlite3SelectNew( + ExprList *pEList, /* which columns to include in the result */ + SrcList *pSrc, /* the FROM clause -- which tables to scan */ + Expr *pWhere, /* the WHERE clause */ + ExprList *pGroupBy, /* the GROUP BY clause */ + Expr *pHaving, /* the HAVING clause */ + ExprList *pOrderBy, /* the ORDER BY clause */ + int isDistinct, /* true if the DISTINCT keyword is present */ + int nLimit, /* LIMIT value. -1 means not used */ + int nOffset /* OFFSET value. 0 means no offset */ +){ + Select *pNew; + pNew = sqliteMalloc( sizeof(*pNew) ); + if( pNew==0 ){ + sqlite3ExprListDelete(pEList); + sqlite3SrcListDelete(pSrc); + sqlite3ExprDelete(pWhere); + sqlite3ExprListDelete(pGroupBy); + sqlite3ExprDelete(pHaving); + sqlite3ExprListDelete(pOrderBy); + }else{ + if( pEList==0 ){ + pEList = sqlite3ExprListAppend(0, sqlite3Expr(TK_ALL,0,0,0), 0); + } + pNew->pEList = pEList; + pNew->pSrc = pSrc; + pNew->pWhere = pWhere; + pNew->pGroupBy = pGroupBy; + pNew->pHaving = pHaving; + pNew->pOrderBy = pOrderBy; + pNew->isDistinct = isDistinct; + pNew->op = TK_SELECT; + pNew->nLimit = nLimit; + pNew->nOffset = nOffset; + pNew->iLimit = -1; + pNew->iOffset = -1; + } + return pNew; +} + +/* +** Given 1 to 3 identifiers preceeding the JOIN keyword, determine the +** type of join. Return an integer constant that expresses that type +** in terms of the following bit values: +** +** JT_INNER +** JT_OUTER +** JT_NATURAL +** JT_LEFT +** JT_RIGHT +** +** A full outer join is the combination of JT_LEFT and JT_RIGHT. +** +** If an illegal or unsupported join type is seen, then still return +** a join type, but put an error in the pParse structure. +*/ +int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){ + int jointype = 0; + Token *apAll[3]; + Token *p; + static const struct { + const char *zKeyword; + u8 nChar; + u8 code; + } keywords[] = { + { "natural", 7, JT_NATURAL }, + { "left", 4, JT_LEFT|JT_OUTER }, + { "right", 5, JT_RIGHT|JT_OUTER }, + { "full", 4, JT_LEFT|JT_RIGHT|JT_OUTER }, + { "outer", 5, JT_OUTER }, + { "inner", 5, JT_INNER }, + { "cross", 5, JT_INNER }, + }; + int i, j; + apAll[0] = pA; + apAll[1] = pB; + apAll[2] = pC; + for(i=0; i<3 && apAll[i]; i++){ + p = apAll[i]; + for(j=0; jn==keywords[j].nChar + && sqlite3StrNICmp(p->z, keywords[j].zKeyword, p->n)==0 ){ + jointype |= keywords[j].code; + break; + } + } + if( j>=sizeof(keywords)/sizeof(keywords[0]) ){ + jointype |= JT_ERROR; + break; + } + } + if( + (jointype & (JT_INNER|JT_OUTER))==(JT_INNER|JT_OUTER) || + (jointype & JT_ERROR)!=0 + ){ + const char *zSp1 = " "; + const char *zSp2 = " "; + if( pB==0 ){ zSp1++; } + if( pC==0 ){ zSp2++; } + sqlite3ErrorMsg(pParse, "unknown or unsupported join type: " + "%T%s%T%s%T", pA, zSp1, pB, zSp2, pC); + jointype = JT_INNER; + }else if( jointype & JT_RIGHT ){ + sqlite3ErrorMsg(pParse, + "RIGHT and FULL OUTER JOINs are not currently supported"); + jointype = JT_INNER; + } + return jointype; +} + +/* +** Return the index of a column in a table. Return -1 if the column +** is not contained in the table. +*/ +static int columnIndex(Table *pTab, const char *zCol){ + int i; + for(i=0; inCol; i++){ + if( sqlite3StrICmp(pTab->aCol[i].zName, zCol)==0 ) return i; + } + return -1; +} + +/* +** Set the value of a token to a '\000'-terminated string. +*/ +static void setToken(Token *p, const char *z){ + p->z = z; + p->n = strlen(z); + p->dyn = 0; +} + + +/* +** Add a term to the WHERE expression in *ppExpr that requires the +** zCol column to be equal in the two tables pTab1 and pTab2. +*/ +static void addWhereTerm( + const char *zCol, /* Name of the column */ + const Table *pTab1, /* First table */ + const Table *pTab2, /* Second table */ + Expr **ppExpr /* Add the equality term to this expression */ +){ + Token dummy; + Expr *pE1a, *pE1b, *pE1c; + Expr *pE2a, *pE2b, *pE2c; + Expr *pE; + + setToken(&dummy, zCol); + pE1a = sqlite3Expr(TK_ID, 0, 0, &dummy); + pE2a = sqlite3Expr(TK_ID, 0, 0, &dummy); + setToken(&dummy, pTab1->zName); + pE1b = sqlite3Expr(TK_ID, 0, 0, &dummy); + setToken(&dummy, pTab2->zName); + pE2b = sqlite3Expr(TK_ID, 0, 0, &dummy); + pE1c = sqlite3Expr(TK_DOT, pE1b, pE1a, 0); + pE2c = sqlite3Expr(TK_DOT, pE2b, pE2a, 0); + pE = sqlite3Expr(TK_EQ, pE1c, pE2c, 0); + ExprSetProperty(pE, EP_FromJoin); + *ppExpr = sqlite3ExprAnd(*ppExpr, pE); +} + +/* +** Set the EP_FromJoin property on all terms of the given expression. +** +** The EP_FromJoin property is used on terms of an expression to tell +** the LEFT OUTER JOIN processing logic that this term is part of the +** join restriction specified in the ON or USING clause and not a part +** of the more general WHERE clause. These terms are moved over to the +** WHERE clause during join processing but we need to remember that they +** originated in the ON or USING clause. +*/ +static void setJoinExpr(Expr *p){ + while( p ){ + ExprSetProperty(p, EP_FromJoin); + setJoinExpr(p->pLeft); + p = p->pRight; + } +} + +/* +** This routine processes the join information for a SELECT statement. +** ON and USING clauses are converted into extra terms of the WHERE clause. +** NATURAL joins also create extra WHERE clause terms. +** +** The terms of a FROM clause are contained in the Select.pSrc structure. +** The left most table is the first entry in Select.pSrc. The right-most +** table is the last entry. The join operator is held in the entry to +** the left. Thus entry 0 contains the join operator for the join between +** entries 0 and 1. Any ON or USING clauses associated with the join are +** also attached to the left entry. +** +** This routine returns the number of errors encountered. +*/ +static int sqliteProcessJoin(Parse *pParse, Select *p){ + SrcList *pSrc; /* All tables in the FROM clause */ + int i, j; /* Loop counters */ + struct SrcList_item *pLeft; /* Left table being joined */ + struct SrcList_item *pRight; /* Right table being joined */ + + pSrc = p->pSrc; + pLeft = &pSrc->a[0]; + pRight = &pLeft[1]; + for(i=0; inSrc-1; i++, pRight++, pLeft++){ + Table *pLeftTab = pLeft->pTab; + Table *pRightTab = pRight->pTab; + + if( pLeftTab==0 || pRightTab==0 ) continue; + + /* When the NATURAL keyword is present, add WHERE clause terms for + ** every column that the two tables have in common. + */ + if( pLeft->jointype & JT_NATURAL ){ + if( pLeft->pOn || pLeft->pUsing ){ + sqlite3ErrorMsg(pParse, "a NATURAL join may not have " + "an ON or USING clause", 0); + return 1; + } + for(j=0; jnCol; j++){ + char *zName = pLeftTab->aCol[j].zName; + if( columnIndex(pRightTab, zName)>=0 ){ + addWhereTerm(zName, pLeftTab, pRightTab, &p->pWhere); + } + } + } + + /* Disallow both ON and USING clauses in the same join + */ + if( pLeft->pOn && pLeft->pUsing ){ + sqlite3ErrorMsg(pParse, "cannot have both ON and USING " + "clauses in the same join"); + return 1; + } + + /* Add the ON clause to the end of the WHERE clause, connected by + ** an AND operator. + */ + if( pLeft->pOn ){ + setJoinExpr(pLeft->pOn); + p->pWhere = sqlite3ExprAnd(p->pWhere, pLeft->pOn); + pLeft->pOn = 0; + } + + /* Create extra terms on the WHERE clause for each column named + ** in the USING clause. Example: If the two tables to be joined are + ** A and B and the USING clause names X, Y, and Z, then add this + ** to the WHERE clause: A.X=B.X AND A.Y=B.Y AND A.Z=B.Z + ** Report an error if any column mentioned in the USING clause is + ** not contained in both tables to be joined. + */ + if( pLeft->pUsing ){ + IdList *pList = pLeft->pUsing; + for(j=0; jnId; j++){ + char *zName = pList->a[j].zName; + if( columnIndex(pLeftTab, zName)<0 || columnIndex(pRightTab, zName)<0 ){ + sqlite3ErrorMsg(pParse, "cannot join using column %s - column " + "not present in both tables", zName); + return 1; + } + addWhereTerm(zName, pLeftTab, pRightTab, &p->pWhere); + } + } + } + return 0; +} + +/* +** Delete the given Select structure and all of its substructures. +*/ +void sqlite3SelectDelete(Select *p){ + if( p==0 ) return; + sqlite3ExprListDelete(p->pEList); + sqlite3SrcListDelete(p->pSrc); + sqlite3ExprDelete(p->pWhere); + sqlite3ExprListDelete(p->pGroupBy); + sqlite3ExprDelete(p->pHaving); + sqlite3ExprListDelete(p->pOrderBy); + sqlite3SelectDelete(p->pPrior); + sqliteFree(p->zSelect); + sqliteFree(p); +} + +/* +** Delete the aggregate information from the parse structure. +*/ +static void sqliteAggregateInfoReset(Parse *pParse){ + sqliteFree(pParse->aAgg); + pParse->aAgg = 0; + pParse->nAgg = 0; + pParse->useAgg = 0; +} + +/* +** Insert code into "v" that will push the record on the top of the +** stack into the sorter. +*/ +static void pushOntoSorter(Parse *pParse, Vdbe *v, ExprList *pOrderBy){ + int i; + for(i=0; inExpr; i++){ + sqlite3ExprCode(pParse, pOrderBy->a[i].pExpr); + } + sqlite3VdbeAddOp(v, OP_MakeRecord, pOrderBy->nExpr, 0); + sqlite3VdbeAddOp(v, OP_SortPut, 0, 0); +} + +/* +** Add code to implement the OFFSET and LIMIT +*/ +static void codeLimiter( + Vdbe *v, /* Generate code into this VM */ + Select *p, /* The SELECT statement being coded */ + int iContinue, /* Jump here to skip the current record */ + int iBreak, /* Jump here to end the loop */ + int nPop /* Number of times to pop stack when jumping */ +){ + if( p->iOffset>=0 ){ + int addr = sqlite3VdbeCurrentAddr(v) + 2; + if( nPop>0 ) addr++; + sqlite3VdbeAddOp(v, OP_MemIncr, p->iOffset, addr); + if( nPop>0 ){ + sqlite3VdbeAddOp(v, OP_Pop, nPop, 0); + } + sqlite3VdbeAddOp(v, OP_Goto, 0, iContinue); + VdbeComment((v, "# skip OFFSET records")); + } + if( p->iLimit>=0 ){ + sqlite3VdbeAddOp(v, OP_MemIncr, p->iLimit, iBreak); + VdbeComment((v, "# exit when LIMIT reached")); + } +} + +/* +** This routine generates the code for the inside of the inner loop +** of a SELECT. +** +** If srcTab and nColumn are both zero, then the pEList expressions +** are evaluated in order to get the data for this row. If nColumn>0 +** then data is pulled from srcTab and pEList is used only to get the +** datatypes for each column. +*/ +static int selectInnerLoop( + Parse *pParse, /* The parser context */ + Select *p, /* The complete select statement being coded */ + ExprList *pEList, /* List of values being extracted */ + int srcTab, /* Pull data from this table */ + int nColumn, /* Number of columns in the source table */ + ExprList *pOrderBy, /* If not NULL, sort results using this key */ + int distinct, /* If >=0, make sure results are distinct */ + int eDest, /* How to dispose of the results */ + int iParm, /* An argument to the disposal method */ + int iContinue, /* Jump here to continue with next row */ + int iBreak, /* Jump here to break out of the inner loop */ + char *aff /* affinity string if eDest is SRT_Union */ +){ + Vdbe *v = pParse->pVdbe; + int i; + int hasDistinct; /* True if the DISTINCT keyword is present */ + + if( v==0 ) return 0; + assert( pEList!=0 ); + + /* If there was a LIMIT clause on the SELECT statement, then do the check + ** to see if this row should be output. + */ + hasDistinct = distinct>=0 && pEList && pEList->nExpr>0; + if( pOrderBy==0 && !hasDistinct ){ + codeLimiter(v, p, iContinue, iBreak, 0); + } + + /* Pull the requested columns. + */ + if( nColumn>0 ){ + for(i=0; inExpr; + for(i=0; inExpr; i++){ + sqlite3ExprCode(pParse, pEList->a[i].pExpr); + } + } + + /* If the DISTINCT keyword was present on the SELECT statement + ** and this row has been seen before, then do not make this row + ** part of the result. + */ + if( hasDistinct ){ +#if NULL_ALWAYS_DISTINCT + sqlite3VdbeAddOp(v, OP_IsNull, -pEList->nExpr, sqlite3VdbeCurrentAddr(v)+7); +#endif + /* Deliberately leave the affinity string off of the following + ** OP_MakeRecord */ + sqlite3VdbeAddOp(v, OP_MakeRecord, pEList->nExpr * -1, 0); + sqlite3VdbeAddOp(v, OP_Distinct, distinct, sqlite3VdbeCurrentAddr(v)+3); + sqlite3VdbeAddOp(v, OP_Pop, pEList->nExpr+1, 0); + sqlite3VdbeAddOp(v, OP_Goto, 0, iContinue); + VdbeComment((v, "# skip indistinct records")); + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + sqlite3VdbeAddOp(v, OP_PutStrKey, distinct, 0); + if( pOrderBy==0 ){ + codeLimiter(v, p, iContinue, iBreak, nColumn); + } + } + + switch( eDest ){ + /* In this mode, write each query result to the key of the temporary + ** table iParm. + */ + case SRT_Union: { + sqlite3VdbeAddOp(v, OP_MakeRecord, nColumn, NULL_ALWAYS_DISTINCT); + sqlite3VdbeChangeP3(v, -1, aff, P3_STATIC); + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + sqlite3VdbeAddOp(v, OP_PutStrKey, iParm, 0); + break; + } + + /* Store the result as data using a unique key. + */ + case SRT_Table: + case SRT_TempTable: { + sqlite3VdbeAddOp(v, OP_MakeRecord, nColumn, 0); + if( pOrderBy ){ + pushOntoSorter(pParse, v, pOrderBy); + }else{ + sqlite3VdbeAddOp(v, OP_NewRecno, iParm, 0); + sqlite3VdbeAddOp(v, OP_Pull, 1, 0); + sqlite3VdbeAddOp(v, OP_PutIntKey, iParm, 0); + } + break; + } + + /* Construct a record from the query result, but instead of + ** saving that record, use it as a key to delete elements from + ** the temporary table iParm. + */ + case SRT_Except: { + int addr; + addr = sqlite3VdbeAddOp(v, OP_MakeRecord, nColumn, NULL_ALWAYS_DISTINCT); + sqlite3VdbeChangeP3(v, -1, aff, P3_STATIC); + sqlite3VdbeAddOp(v, OP_NotFound, iParm, addr+3); + sqlite3VdbeAddOp(v, OP_Delete, iParm, 0); + break; + } + + /* If we are creating a set for an "expr IN (SELECT ...)" construct, + ** then there should be a single item on the stack. Write this + ** item into the set table with bogus data. + */ + case SRT_Set: { + int addr1 = sqlite3VdbeCurrentAddr(v); + int addr2; + + assert( nColumn==1 ); + sqlite3VdbeAddOp(v, OP_NotNull, -1, addr1+3); + sqlite3VdbeAddOp(v, OP_Pop, 1, 0); + addr2 = sqlite3VdbeAddOp(v, OP_Goto, 0, 0); + if( pOrderBy ){ + pushOntoSorter(pParse, v, pOrderBy); + }else{ + char aff = (iParm>>16)&0xFF; + aff = sqlite3CompareAffinity(pEList->a[0].pExpr, aff); + sqlite3VdbeOp3(v, OP_MakeRecord, 1, 0, &aff, 1); + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + sqlite3VdbeAddOp(v, OP_PutStrKey, (iParm&0x0000FFFF), 0); + } + sqlite3VdbeChangeP2(v, addr2, sqlite3VdbeCurrentAddr(v)); + break; + } + + /* If this is a scalar select that is part of an expression, then + ** store the results in the appropriate memory cell and break out + ** of the scan loop. + */ + case SRT_Mem: { + assert( nColumn==1 ); + if( pOrderBy ){ + pushOntoSorter(pParse, v, pOrderBy); + }else{ + sqlite3VdbeAddOp(v, OP_MemStore, iParm, 1); + sqlite3VdbeAddOp(v, OP_Goto, 0, iBreak); + } + break; + } + + /* Send the data to the callback function. + */ + case SRT_Callback: + case SRT_Sorter: { + if( pOrderBy ){ + sqlite3VdbeAddOp(v, OP_MakeRecord, nColumn, 0); + pushOntoSorter(pParse, v, pOrderBy); + }else{ + assert( eDest==SRT_Callback ); + sqlite3VdbeAddOp(v, OP_Callback, nColumn, 0); + } + break; + } + + /* Invoke a subroutine to handle the results. The subroutine itself + ** is responsible for popping the results off of the stack. + */ + case SRT_Subroutine: { + if( pOrderBy ){ + sqlite3VdbeAddOp(v, OP_MakeRecord, nColumn, 0); + pushOntoSorter(pParse, v, pOrderBy); + }else{ + sqlite3VdbeAddOp(v, OP_Gosub, 0, iParm); + } + break; + } + + /* Discard the results. This is used for SELECT statements inside + ** the body of a TRIGGER. The purpose of such selects is to call + ** user-defined functions that have side effects. We do not care + ** about the actual results of the select. + */ + default: { + assert( eDest==SRT_Discard ); + sqlite3VdbeAddOp(v, OP_Pop, nColumn, 0); + break; + } + } + return 0; +} + +/* +** If the inner loop was generated using a non-null pOrderBy argument, +** then the results were placed in a sorter. After the loop is terminated +** we need to run the sorter and output the results. The following +** routine generates the code needed to do that. +*/ +static void generateSortTail( + Parse *pParse, /* The parsing context */ + Select *p, /* The SELECT statement */ + Vdbe *v, /* Generate code into this VDBE */ + int nColumn, /* Number of columns of data */ + int eDest, /* Write the sorted results here */ + int iParm /* Optional parameter associated with eDest */ +){ + int end1 = sqlite3VdbeMakeLabel(v); + int end2 = sqlite3VdbeMakeLabel(v); + int addr; + KeyInfo *pInfo; + ExprList *pOrderBy; + int nCol, i; + sqlite3 *db = pParse->db; + + if( eDest==SRT_Sorter ) return; + pOrderBy = p->pOrderBy; + nCol = pOrderBy->nExpr; + pInfo = sqliteMalloc( sizeof(*pInfo) + nCol*(sizeof(CollSeq*)+1) ); + if( pInfo==0 ) return; + pInfo->aSortOrder = (char*)&pInfo->aColl[nCol]; + pInfo->nField = nCol; + for(i=0; ia[i].zName. Otherwise, use the default + ** collation type for the expression. + */ + pInfo->aColl[i] = sqlite3ExprCollSeq(pParse, pOrderBy->a[i].pExpr); + if( !pInfo->aColl[i] ){ + pInfo->aColl[i] = db->pDfltColl; + } + pInfo->aSortOrder[i] = pOrderBy->a[i].sortOrder; + } + sqlite3VdbeOp3(v, OP_Sort, 0, 0, (char*)pInfo, P3_KEYINFO_HANDOFF); + addr = sqlite3VdbeAddOp(v, OP_SortNext, 0, end1); + codeLimiter(v, p, addr, end2, 1); + switch( eDest ){ + case SRT_Table: + case SRT_TempTable: { + sqlite3VdbeAddOp(v, OP_NewRecno, iParm, 0); + sqlite3VdbeAddOp(v, OP_Pull, 1, 0); + sqlite3VdbeAddOp(v, OP_PutIntKey, iParm, 0); + break; + } + case SRT_Set: { + assert( nColumn==1 ); + sqlite3VdbeAddOp(v, OP_NotNull, -1, sqlite3VdbeCurrentAddr(v)+3); + sqlite3VdbeAddOp(v, OP_Pop, 1, 0); + sqlite3VdbeAddOp(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+3); + sqlite3VdbeOp3(v, OP_MakeRecord, 1, 0, "n", P3_STATIC); + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + sqlite3VdbeAddOp(v, OP_PutStrKey, (iParm&0x0000FFFF), 0); + break; + } + case SRT_Mem: { + assert( nColumn==1 ); + sqlite3VdbeAddOp(v, OP_MemStore, iParm, 1); + sqlite3VdbeAddOp(v, OP_Goto, 0, end1); + break; + } + case SRT_Callback: + case SRT_Subroutine: { + int i; + sqlite3VdbeAddOp(v, OP_Integer, p->pEList->nExpr, 0); + sqlite3VdbeAddOp(v, OP_Pull, 1, 0); + for(i=0; iop ){ + case TK_COLUMN: { + Table *pTab; + int iCol = pExpr->iColumn; + for(j=0; jnSrc && pTabList->a[j].iCursor!=pExpr->iTable; j++){} + assert( jnSrc ); + pTab = pTabList->a[j].pTab; + if( iCol<0 ) iCol = pTab->iPKey; + assert( iCol==-1 || (iCol>=0 && iColnCol) ); + if( iCol<0 ){ + zType = "INTEGER"; + }else{ + zType = pTab->aCol[iCol].zType; + } + break; + } + case TK_AS: + zType = columnType(pParse, pTabList, pExpr->pLeft); + break; + case TK_SELECT: { + Select *pS = pExpr->pSelect; + zType = columnType(pParse, pS->pSrc, pS->pEList->a[0].pExpr); + break; + } + default: + zType = 0; + } + + return zType; +} + +/* +** Generate code that will tell the VDBE the declaration types of columns +** in the result set. +*/ +static void generateColumnTypes( + Parse *pParse, /* Parser context */ + SrcList *pTabList, /* List of tables */ + ExprList *pEList /* Expressions defining the result set */ +){ + Vdbe *v = pParse->pVdbe; + int i; + for(i=0; inExpr; i++){ + Expr *p = pEList->a[i].pExpr; + const char *zType = columnType(pParse, pTabList, p); + if( zType==0 ) continue; + /* The vdbe must make it's own copy of the column-type, in case the + ** schema is reset before this virtual machine is deleted. + */ + sqlite3VdbeSetColName(v, i+pEList->nExpr, zType, strlen(zType)); + } +} + +/* +** Generate code that will tell the VDBE the names of columns +** in the result set. This information is used to provide the +** azCol[] values in the callback. +*/ +static void generateColumnNames( + Parse *pParse, /* Parser context */ + SrcList *pTabList, /* List of tables */ + ExprList *pEList /* Expressions defining the result set */ +){ + Vdbe *v = pParse->pVdbe; + int i, j; + sqlite3 *db = pParse->db; + int fullNames, shortNames; + + /* If this is an EXPLAIN, skip this step */ + if( pParse->explain ){ + return; + } + + assert( v!=0 ); + if( pParse->colNamesSet || v==0 || sqlite3_malloc_failed ) return; + pParse->colNamesSet = 1; + fullNames = (db->flags & SQLITE_FullColNames)!=0; + shortNames = (db->flags & SQLITE_ShortColNames)!=0; + sqlite3VdbeSetNumCols(v, pEList->nExpr); + for(i=0; inExpr; i++){ + Expr *p; + p = pEList->a[i].pExpr; + if( p==0 ) continue; + if( pEList->a[i].zName ){ + char *zName = pEList->a[i].zName; + sqlite3VdbeSetColName(v, i, zName, strlen(zName)); + continue; + } + if( p->op==TK_COLUMN && pTabList ){ + Table *pTab; + char *zCol; + int iCol = p->iColumn; + for(j=0; jnSrc && pTabList->a[j].iCursor!=p->iTable; j++){} + assert( jnSrc ); + pTab = pTabList->a[j].pTab; + if( iCol<0 ) iCol = pTab->iPKey; + assert( iCol==-1 || (iCol>=0 && iColnCol) ); + if( iCol<0 ){ + zCol = "_ROWID_"; + }else{ + zCol = pTab->aCol[iCol].zName; + } + if( !shortNames && !fullNames && p->span.z && p->span.z[0] ){ + sqlite3VdbeSetColName(v, i, p->span.z, p->span.n); + }else if( fullNames || (!shortNames && pTabList->nSrc>1) ){ + char *zName = 0; + char *zTab; + + zTab = pTabList->a[j].zAlias; + if( fullNames || zTab==0 ) zTab = pTab->zName; + sqlite3SetString(&zName, zTab, ".", zCol, 0); + sqlite3VdbeSetColName(v, i, zName, P3_DYNAMIC); + }else{ + sqlite3VdbeSetColName(v, i, zCol, 0); + } + }else if( p->span.z && p->span.z[0] ){ + sqlite3VdbeSetColName(v, i, p->span.z, p->span.n); + /* sqlite3VdbeCompressSpace(v, addr); */ + }else{ + char zName[30]; + assert( p->op!=TK_COLUMN || pTabList==0 ); + sprintf(zName, "column%d", i+1); + sqlite3VdbeSetColName(v, i, zName, 0); + } + } + generateColumnTypes(pParse, pTabList, pEList); +} + +/* +** Name of the connection operator, used for error messages. +*/ +static const char *selectOpName(int id){ + char *z; + switch( id ){ + case TK_ALL: z = "UNION ALL"; break; + case TK_INTERSECT: z = "INTERSECT"; break; + case TK_EXCEPT: z = "EXCEPT"; break; + default: z = "UNION"; break; + } + return z; +} + +/* +** Forward declaration +*/ +static int fillInColumnList(Parse*, Select*); + +/* +** Given a SELECT statement, generate a Table structure that describes +** the result set of that SELECT. +*/ +Table *sqlite3ResultSetOfSelect(Parse *pParse, char *zTabName, Select *pSelect){ + Table *pTab; + int i, j; + ExprList *pEList; + Column *aCol, *pCol; + + if( fillInColumnList(pParse, pSelect) ){ + return 0; + } + pTab = sqliteMalloc( sizeof(Table) ); + if( pTab==0 ){ + return 0; + } + pTab->zName = zTabName ? sqliteStrDup(zTabName) : 0; + pEList = pSelect->pEList; + pTab->nCol = pEList->nExpr; + assert( pTab->nCol>0 ); + pTab->aCol = aCol = sqliteMalloc( sizeof(pTab->aCol[0])*pTab->nCol ); + for(i=0, pCol=aCol; inCol; i++, pCol++){ + Expr *pR; + char *zType; + char *zName; + Expr *p = pEList->a[i].pExpr; + assert( p->pRight==0 || p->pRight->token.z==0 || p->pRight->token.z[0]!=0 ); + if( (zName = pEList->a[i].zName)!=0 ){ + zName = sqliteStrDup(zName); + }else if( p->op==TK_DOT + && (pR=p->pRight)!=0 && pR->token.z && pR->token.z[0] ){ + int cnt; + zName = sqlite3MPrintf("%T", &pR->token); + for(j=cnt=0; jtoken, ++cnt); + j = -1; + } + } + }else if( p->span.z && p->span.z[0] ){ + zName = sqlite3MPrintf("%T", &p->span); + }else{ + zName = sqlite3MPrintf("column%d", i+1); + } + sqlite3Dequote(zName); + pCol->zName = zName; + + zType = sqliteStrDup(columnType(pParse, pSelect->pSrc ,p)); + pCol->zType = zType; + pCol->affinity = SQLITE_AFF_NUMERIC; + if( zType ){ + pCol->affinity = sqlite3AffinityType(zType, strlen(zType)); + } + pCol->pColl = sqlite3ExprCollSeq(pParse, p); + if( !pCol->pColl ){ + pCol->pColl = pParse->db->pDfltColl; + } + } + pTab->iPKey = -1; + return pTab; +} + +/* +** For the given SELECT statement, do three things. +** +** (1) Fill in the pTabList->a[].pTab fields in the SrcList that +** defines the set of tables that should be scanned. For views, +** fill pTabList->a[].pSelect with a copy of the SELECT statement +** that implements the view. A copy is made of the view's SELECT +** statement so that we can freely modify or delete that statement +** without worrying about messing up the presistent representation +** of the view. +** +** (2) Add terms to the WHERE clause to accomodate the NATURAL keyword +** on joins and the ON and USING clause of joins. +** +** (3) Scan the list of columns in the result set (pEList) looking +** for instances of the "*" operator or the TABLE.* operator. +** If found, expand each "*" to be every column in every table +** and TABLE.* to be every column in TABLE. +** +** Return 0 on success. If there are problems, leave an error message +** in pParse and return non-zero. +*/ +static int fillInColumnList(Parse *pParse, Select *p){ + int i, j, k, rc; + SrcList *pTabList; + ExprList *pEList; + Table *pTab; + struct SrcList_item *pFrom; + + if( p==0 || p->pSrc==0 ) return 1; + pTabList = p->pSrc; + pEList = p->pEList; + + /* Look up every table in the table list. + */ + for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ + if( pFrom->pTab ){ + /* This routine has run before! No need to continue */ + return 0; + } + if( pFrom->zName==0 ){ + /* A sub-query in the FROM clause of a SELECT */ + assert( pFrom->pSelect!=0 ); + if( pFrom->zAlias==0 ){ + pFrom->zAlias = + sqlite3MPrintf("sqlite_subquery_%p_", (void*)pFrom->pSelect); + } + pFrom->pTab = pTab = + sqlite3ResultSetOfSelect(pParse, pFrom->zAlias, pFrom->pSelect); + if( pTab==0 ){ + return 1; + } + /* The isTransient flag indicates that the Table structure has been + ** dynamically allocated and may be freed at any time. In other words, + ** pTab is not pointing to a persistent table structure that defines + ** part of the schema. */ + pTab->isTransient = 1; + }else{ + /* An ordinary table or view name in the FROM clause */ + pFrom->pTab = pTab = + sqlite3LocateTable(pParse,pFrom->zName,pFrom->zDatabase); + if( pTab==0 ){ + return 1; + } + if( pTab->pSelect ){ + /* We reach here if the named table is a really a view */ + if( sqlite3ViewGetColumnNames(pParse, pTab) ){ + return 1; + } + /* If pFrom->pSelect!=0 it means we are dealing with a + ** view within a view. The SELECT structure has already been + ** copied by the outer view so we can skip the copy step here + ** in the inner view. + */ + if( pFrom->pSelect==0 ){ + pFrom->pSelect = sqlite3SelectDup(pTab->pSelect); + } + } + } + } + + /* Process NATURAL keywords, and ON and USING clauses of joins. + */ + if( sqliteProcessJoin(pParse, p) ) return 1; + + /* For every "*" that occurs in the column list, insert the names of + ** all columns in all tables. And for every TABLE.* insert the names + ** of all columns in TABLE. The parser inserted a special expression + ** with the TK_ALL operator for each "*" that it found in the column list. + ** The following code just has to locate the TK_ALL expressions and expand + ** each one to the list of all columns in all tables. + ** + ** The first loop just checks to see if there are any "*" operators + ** that need expanding. + */ + for(k=0; knExpr; k++){ + Expr *pE = pEList->a[k].pExpr; + if( pE->op==TK_ALL ) break; + if( pE->op==TK_DOT && pE->pRight && pE->pRight->op==TK_ALL + && pE->pLeft && pE->pLeft->op==TK_ID ) break; + } + rc = 0; + if( knExpr ){ + /* + ** If we get here it means the result set contains one or more "*" + ** operators that need to be expanded. Loop through each expression + ** in the result set and expand them one by one. + */ + struct ExprList_item *a = pEList->a; + ExprList *pNew = 0; + for(k=0; knExpr; k++){ + Expr *pE = a[k].pExpr; + if( pE->op!=TK_ALL && + (pE->op!=TK_DOT || pE->pRight==0 || pE->pRight->op!=TK_ALL) ){ + /* This particular expression does not need to be expanded. + */ + pNew = sqlite3ExprListAppend(pNew, a[k].pExpr, 0); + pNew->a[pNew->nExpr-1].zName = a[k].zName; + a[k].pExpr = 0; + a[k].zName = 0; + }else{ + /* This expression is a "*" or a "TABLE.*" and needs to be + ** expanded. */ + int tableSeen = 0; /* Set to 1 when TABLE matches */ + char *zTName; /* text of name of TABLE */ + if( pE->op==TK_DOT && pE->pLeft ){ + zTName = sqlite3NameFromToken(&pE->pLeft->token); + }else{ + zTName = 0; + } + for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ + Table *pTab = pFrom->pTab; + char *zTabName = pFrom->zAlias; + if( zTabName==0 || zTabName[0]==0 ){ + zTabName = pTab->zName; + } + if( zTName && (zTabName==0 || zTabName[0]==0 || + sqlite3StrICmp(zTName, zTabName)!=0) ){ + continue; + } + tableSeen = 1; + for(j=0; jnCol; j++){ + Expr *pExpr, *pLeft, *pRight; + char *zName = pTab->aCol[j].zName; + + if( i>0 ){ + struct SrcList_item *pLeft = &pTabList->a[i-1]; + if( (pLeft->jointype & JT_NATURAL)!=0 && + columnIndex(pLeft->pTab, zName)>=0 ){ + /* In a NATURAL join, omit the join columns from the + ** table on the right */ + continue; + } + if( sqlite3IdListIndex(pLeft->pUsing, zName)>=0 ){ + /* In a join with a USING clause, omit columns in the + ** using clause from the table on the right. */ + continue; + } + } + pRight = sqlite3Expr(TK_ID, 0, 0, 0); + if( pRight==0 ) break; + setToken(&pRight->token, zName); + if( zTabName && pTabList->nSrc>1 ){ + pLeft = sqlite3Expr(TK_ID, 0, 0, 0); + pExpr = sqlite3Expr(TK_DOT, pLeft, pRight, 0); + if( pExpr==0 ) break; + setToken(&pLeft->token, zTabName); + setToken(&pExpr->span, sqlite3MPrintf("%s.%s", zTabName, zName)); + pExpr->span.dyn = 1; + pExpr->token.z = 0; + pExpr->token.n = 0; + pExpr->token.dyn = 0; + }else{ + pExpr = pRight; + pExpr->span = pExpr->token; + } + pNew = sqlite3ExprListAppend(pNew, pExpr, 0); + } + } + if( !tableSeen ){ + if( zTName ){ + sqlite3ErrorMsg(pParse, "no such table: %s", zTName); + }else{ + sqlite3ErrorMsg(pParse, "no tables specified"); + } + rc = 1; + } + sqliteFree(zTName); + } + } + sqlite3ExprListDelete(pEList); + p->pEList = pNew; + } + return rc; +} + +/* +** This routine recursively unlinks the Select.pSrc.a[].pTab pointers +** in a select structure. It just sets the pointers to NULL. This +** routine is recursive in the sense that if the Select.pSrc.a[].pSelect +** pointer is not NULL, this routine is called recursively on that pointer. +** +** This routine is called on the Select structure that defines a +** VIEW in order to undo any bindings to tables. This is necessary +** because those tables might be DROPed by a subsequent SQL command. +** If the bindings are not removed, then the Select.pSrc->a[].pTab field +** will be left pointing to a deallocated Table structure after the +** DROP and a coredump will occur the next time the VIEW is used. +*/ +void sqlite3SelectUnbind(Select *p){ + int i; + SrcList *pSrc = p->pSrc; + struct SrcList_item *pItem; + Table *pTab; + if( p==0 ) return; + for(i=0, pItem=pSrc->a; inSrc; i++, pItem++){ + if( (pTab = pItem->pTab)!=0 ){ + if( pTab->isTransient ){ + sqlite3DeleteTable(0, pTab); + } + pItem->pTab = 0; + if( pItem->pSelect ){ + sqlite3SelectUnbind(pItem->pSelect); + } + } + } +} + +/* +** This routine associates entries in an ORDER BY expression list with +** columns in a result. For each ORDER BY expression, the opcode of +** the top-level node is changed to TK_COLUMN and the iColumn value of +** the top-level node is filled in with column number and the iTable +** value of the top-level node is filled with iTable parameter. +** +** If there are prior SELECT clauses, they are processed first. A match +** in an earlier SELECT takes precedence over a later SELECT. +** +** Any entry that does not match is flagged as an error. The number +** of errors is returned. +*/ +static int matchOrderbyToColumn( + Parse *pParse, /* A place to leave error messages */ + Select *pSelect, /* Match to result columns of this SELECT */ + ExprList *pOrderBy, /* The ORDER BY values to match against columns */ + int iTable, /* Insert this value in iTable */ + int mustComplete /* If TRUE all ORDER BYs must match */ +){ + int nErr = 0; + int i, j; + ExprList *pEList; + + if( pSelect==0 || pOrderBy==0 ) return 1; + if( mustComplete ){ + for(i=0; inExpr; i++){ pOrderBy->a[i].done = 0; } + } + if( fillInColumnList(pParse, pSelect) ){ + return 1; + } + if( pSelect->pPrior ){ + if( matchOrderbyToColumn(pParse, pSelect->pPrior, pOrderBy, iTable, 0) ){ + return 1; + } + } + pEList = pSelect->pEList; + for(i=0; inExpr; i++){ + Expr *pE = pOrderBy->a[i].pExpr; + int iCol = -1; + if( pOrderBy->a[i].done ) continue; + if( sqlite3ExprIsInteger(pE, &iCol) ){ + if( iCol<=0 || iCol>pEList->nExpr ){ + sqlite3ErrorMsg(pParse, + "ORDER BY position %d should be between 1 and %d", + iCol, pEList->nExpr); + nErr++; + break; + } + if( !mustComplete ) continue; + iCol--; + } + for(j=0; iCol<0 && jnExpr; j++){ + if( pEList->a[j].zName && (pE->op==TK_ID || pE->op==TK_STRING) ){ + char *zName, *zLabel; + zName = pEList->a[j].zName; + zLabel = sqlite3NameFromToken(&pE->token); + assert( zLabel!=0 ); + if( sqlite3StrICmp(zName, zLabel)==0 ){ + iCol = j; + } + sqliteFree(zLabel); + } + if( iCol<0 && sqlite3ExprCompare(pE, pEList->a[j].pExpr) ){ + iCol = j; + } + } + if( iCol>=0 ){ + pE->op = TK_COLUMN; + pE->iColumn = iCol; + pE->iTable = iTable; + pOrderBy->a[i].done = 1; + } + if( iCol<0 && mustComplete ){ + sqlite3ErrorMsg(pParse, + "ORDER BY term number %d does not match any result column", i+1); + nErr++; + break; + } + } + return nErr; +} + +/* +** Get a VDBE for the given parser context. Create a new one if necessary. +** If an error occurs, return NULL and leave a message in pParse. +*/ +Vdbe *sqlite3GetVdbe(Parse *pParse){ + Vdbe *v = pParse->pVdbe; + if( v==0 ){ + v = pParse->pVdbe = sqlite3VdbeCreate(pParse->db); + } + return v; +} + +/* +** Compute the iLimit and iOffset fields of the SELECT based on the +** nLimit and nOffset fields. nLimit and nOffset hold the integers +** that appear in the original SQL statement after the LIMIT and OFFSET +** keywords. Or that hold -1 and 0 if those keywords are omitted. +** iLimit and iOffset are the integer memory register numbers for +** counters used to compute the limit and offset. If there is no +** limit and/or offset, then iLimit and iOffset are negative. +** +** This routine changes the values if iLimit and iOffset only if +** a limit or offset is defined by nLimit and nOffset. iLimit and +** iOffset should have been preset to appropriate default values +** (usually but not always -1) prior to calling this routine. +** Only if nLimit>=0 or nOffset>0 do the limit registers get +** redefined. The UNION ALL operator uses this property to force +** the reuse of the same limit and offset registers across multiple +** SELECT statements. +*/ +static void computeLimitRegisters(Parse *pParse, Select *p){ + /* + ** If the comparison is p->nLimit>0 then "LIMIT 0" shows + ** all rows. It is the same as no limit. If the comparision is + ** p->nLimit>=0 then "LIMIT 0" show no rows at all. + ** "LIMIT -1" always shows all rows. There is some + ** contraversy about what the correct behavior should be. + ** The current implementation interprets "LIMIT 0" to mean + ** no rows. + */ + if( p->nLimit>=0 ){ + int iMem = pParse->nMem++; + Vdbe *v = sqlite3GetVdbe(pParse); + if( v==0 ) return; + sqlite3VdbeAddOp(v, OP_Integer, -p->nLimit, 0); + sqlite3VdbeAddOp(v, OP_MemStore, iMem, 1); + VdbeComment((v, "# LIMIT counter")); + p->iLimit = iMem; + } + if( p->nOffset>0 ){ + int iMem = pParse->nMem++; + Vdbe *v = sqlite3GetVdbe(pParse); + if( v==0 ) return; + sqlite3VdbeAddOp(v, OP_Integer, -p->nOffset, 0); + sqlite3VdbeAddOp(v, OP_MemStore, iMem, 1); + VdbeComment((v, "# OFFSET counter")); + p->iOffset = iMem; + } +} + +/* +** Generate VDBE instructions that will open a transient table that +** will be used for an index or to store keyed results for a compound +** select. In other words, open a transient table that needs a +** KeyInfo structure. The number of columns in the KeyInfo is determined +** by the result set of the SELECT statement in the second argument. +** +** Specifically, this routine is called to open an index table for +** DISTINCT, UNION, INTERSECT and EXCEPT select statements (but not +** UNION ALL). +** +** Make the new table a KeyAsData table if keyAsData is true. +** +** The value returned is the address of the OP_OpenTemp instruction. +*/ +static int openTempIndex(Parse *pParse, Select *p, int iTab, int keyAsData){ + KeyInfo *pKeyInfo; + int nColumn; + sqlite3 *db = pParse->db; + int i; + Vdbe *v = pParse->pVdbe; + int addr; + + if( fillInColumnList(pParse, p) ){ + return 0; + } + nColumn = p->pEList->nExpr; + pKeyInfo = sqliteMalloc( sizeof(*pKeyInfo)+nColumn*sizeof(CollSeq*) ); + if( pKeyInfo==0 ) return 0; + pKeyInfo->enc = db->enc; + pKeyInfo->nField = nColumn; + for(i=0; iaColl[i] = sqlite3ExprCollSeq(pParse, p->pEList->a[i].pExpr); + if( !pKeyInfo->aColl[i] ){ + pKeyInfo->aColl[i] = db->pDfltColl; + } + } + addr = sqlite3VdbeOp3(v, OP_OpenTemp, iTab, 0, + (char*)pKeyInfo, P3_KEYINFO_HANDOFF); + if( keyAsData ){ + sqlite3VdbeAddOp(v, OP_KeyAsData, iTab, 1); + } + return addr; +} + +/* +** Add the address "addr" to the set of all OpenTemp opcode addresses +** that are being accumulated in p->ppOpenTemp. +*/ +static int multiSelectOpenTempAddr(Select *p, int addr){ + IdList *pList = *p->ppOpenTemp = sqlite3IdListAppend(*p->ppOpenTemp, 0); + if( pList==0 ){ + return SQLITE_NOMEM; + } + pList->a[pList->nId-1].idx = addr; + return SQLITE_OK; +} + +/* +** Return the appropriate collating sequence for the iCol-th column of +** the result set for the compound-select statement "p". Return NULL if +** the column has no default collating sequence. +** +** The collating sequence for the compound select is taken from the +** left-most term of the select that has a collating sequence. +*/ +static CollSeq *multiSelectCollSeq(Parse *pParse, Select *p, int iCol){ + CollSeq *pRet; + if( p->pPrior ){ + pRet = multiSelectCollSeq(pParse, p->pPrior, iCol); + }else{ + pRet = 0; + } + if( pRet==0 ){ + pRet = sqlite3ExprCollSeq(pParse, p->pEList->a[iCol].pExpr); + } + return pRet; +} + +/* +** This routine is called to process a query that is really the union +** or intersection of two or more separate queries. +** +** "p" points to the right-most of the two queries. the query on the +** left is p->pPrior. The left query could also be a compound query +** in which case this routine will be called recursively. +** +** The results of the total query are to be written into a destination +** of type eDest with parameter iParm. +** +** Example 1: Consider a three-way compound SQL statement. +** +** SELECT a FROM t1 UNION SELECT b FROM t2 UNION SELECT c FROM t3 +** +** This statement is parsed up as follows: +** +** SELECT c FROM t3 +** | +** `-----> SELECT b FROM t2 +** | +** `------> SELECT a FROM t1 +** +** The arrows in the diagram above represent the Select.pPrior pointer. +** So if this routine is called with p equal to the t3 query, then +** pPrior will be the t2 query. p->op will be TK_UNION in this case. +** +** Notice that because of the way SQLite parses compound SELECTs, the +** individual selects always group from left to right. +*/ +static int multiSelect( + Parse *pParse, /* Parsing context */ + Select *p, /* The right-most of SELECTs to be coded */ + int eDest, /* \___ Store query results as specified */ + int iParm, /* / by these two parameters. */ + char *aff /* If eDest is SRT_Union, the affinity string */ +){ + int rc = SQLITE_OK; /* Success code from a subroutine */ + Select *pPrior; /* Another SELECT immediately to our left */ + Vdbe *v; /* Generate code to this VDBE */ + IdList *pOpenTemp = 0;/* OP_OpenTemp opcodes that need a KeyInfo */ + int aAddr[5]; /* Addresses of SetNumColumns operators */ + int nAddr = 0; /* Number used */ + int nCol; /* Number of columns in the result set */ + + /* Make sure there is no ORDER BY or LIMIT clause on prior SELECTs. Only + ** the last (right-most) SELECT in the series may have an ORDER BY or LIMIT. + */ + if( p==0 || p->pPrior==0 ){ + rc = 1; + goto multi_select_end; + } + pPrior = p->pPrior; + if( pPrior->pOrderBy ){ + sqlite3ErrorMsg(pParse,"ORDER BY clause should come after %s not before", + selectOpName(p->op)); + rc = 1; + goto multi_select_end; + } + if( pPrior->nLimit>=0 || pPrior->nOffset>0 ){ + sqlite3ErrorMsg(pParse,"LIMIT clause should come after %s not before", + selectOpName(p->op)); + rc = 1; + goto multi_select_end; + } + + /* Make sure we have a valid query engine. If not, create a new one. + */ + v = sqlite3GetVdbe(pParse); + if( v==0 ){ + rc = 1; + goto multi_select_end; + } + + /* If *p this is the right-most select statement, then initialize + ** p->ppOpenTemp to point to pOpenTemp. If *p is not the right most + ** statement then p->ppOpenTemp will have already been initialized + ** by a prior call to this same procedure. Pass along the pOpenTemp + ** pointer to pPrior, the next statement to our left. + */ + if( p->ppOpenTemp==0 ){ + p->ppOpenTemp = &pOpenTemp; + } + pPrior->ppOpenTemp = p->ppOpenTemp; + + /* Create the destination temporary table if necessary + */ + if( eDest==SRT_TempTable ){ + assert( p->pEList ); + sqlite3VdbeAddOp(v, OP_OpenTemp, iParm, 0); + assert( nAddr==0 ); + aAddr[nAddr++] = sqlite3VdbeAddOp(v, OP_SetNumColumns, iParm, 0); + eDest = SRT_Table; + } + + /* Generate code for the left and right SELECT statements. + */ + switch( p->op ){ + case TK_ALL: { + if( p->pOrderBy==0 ){ + pPrior->nLimit = p->nLimit; + pPrior->nOffset = p->nOffset; + rc = sqlite3Select(pParse, pPrior, eDest, iParm, 0, 0, 0, aff); + if( rc ){ + goto multi_select_end; + } + p->pPrior = 0; + p->iLimit = pPrior->iLimit; + p->iOffset = pPrior->iOffset; + p->nLimit = -1; + p->nOffset = 0; + rc = sqlite3Select(pParse, p, eDest, iParm, 0, 0, 0, aff); + p->pPrior = pPrior; + if( rc ){ + goto multi_select_end; + } + break; + } + /* For UNION ALL ... ORDER BY fall through to the next case */ + } + case TK_EXCEPT: + case TK_UNION: { + int unionTab; /* Cursor number of the temporary table holding result */ + int op = 0; /* One of the SRT_ operations to apply to self */ + int priorOp; /* The SRT_ operation to apply to prior selects */ + int nLimit, nOffset; /* Saved values of p->nLimit and p->nOffset */ + ExprList *pOrderBy; /* The ORDER BY clause for the right SELECT */ + int addr; + + priorOp = p->op==TK_ALL ? SRT_Table : SRT_Union; + if( eDest==priorOp && p->pOrderBy==0 && p->nLimit<0 && p->nOffset==0 ){ + /* We can reuse a temporary table generated by a SELECT to our + ** right. + */ + unionTab = iParm; + }else{ + /* We will need to create our own temporary table to hold the + ** intermediate results. + */ + unionTab = pParse->nTab++; + if( p->pOrderBy + && matchOrderbyToColumn(pParse, p, p->pOrderBy, unionTab, 1) ){ + rc = 1; + goto multi_select_end; + } + addr = sqlite3VdbeAddOp(v, OP_OpenTemp, unionTab, 0); + if( p->op!=TK_ALL ){ + rc = multiSelectOpenTempAddr(p, addr); + if( rc!=SQLITE_OK ){ + goto multi_select_end; + } + sqlite3VdbeAddOp(v, OP_KeyAsData, unionTab, 1); + } + assert( nAddrpEList ); + } + + /* Code the SELECT statements to our left + */ + rc = sqlite3Select(pParse, pPrior, priorOp, unionTab, 0, 0, 0, aff); + if( rc ){ + goto multi_select_end; + } + + /* Code the current SELECT statement + */ + switch( p->op ){ + case TK_EXCEPT: op = SRT_Except; break; + case TK_UNION: op = SRT_Union; break; + case TK_ALL: op = SRT_Table; break; + } + p->pPrior = 0; + pOrderBy = p->pOrderBy; + p->pOrderBy = 0; + nLimit = p->nLimit; + p->nLimit = -1; + nOffset = p->nOffset; + p->nOffset = 0; + rc = sqlite3Select(pParse, p, op, unionTab, 0, 0, 0, aff); + p->pPrior = pPrior; + p->pOrderBy = pOrderBy; + p->nLimit = nLimit; + p->nOffset = nOffset; + if( rc ){ + goto multi_select_end; + } + + + /* Convert the data in the temporary table into whatever form + ** it is that we currently need. + */ + if( eDest!=priorOp || unionTab!=iParm ){ + int iCont, iBreak, iStart; + assert( p->pEList ); + if( eDest==SRT_Callback ){ + generateColumnNames(pParse, 0, p->pEList); + } + iBreak = sqlite3VdbeMakeLabel(v); + iCont = sqlite3VdbeMakeLabel(v); + sqlite3VdbeAddOp(v, OP_Rewind, unionTab, iBreak); + computeLimitRegisters(pParse, p); + iStart = sqlite3VdbeCurrentAddr(v); + rc = selectInnerLoop(pParse, p, p->pEList, unionTab, p->pEList->nExpr, + p->pOrderBy, -1, eDest, iParm, + iCont, iBreak, 0); + if( rc ){ + rc = 1; + goto multi_select_end; + } + sqlite3VdbeResolveLabel(v, iCont); + sqlite3VdbeAddOp(v, OP_Next, unionTab, iStart); + sqlite3VdbeResolveLabel(v, iBreak); + sqlite3VdbeAddOp(v, OP_Close, unionTab, 0); + } + break; + } + case TK_INTERSECT: { + int tab1, tab2; + int iCont, iBreak, iStart; + int nLimit, nOffset; + int addr; + + /* INTERSECT is different from the others since it requires + ** two temporary tables. Hence it has its own case. Begin + ** by allocating the tables we will need. + */ + tab1 = pParse->nTab++; + tab2 = pParse->nTab++; + if( p->pOrderBy && matchOrderbyToColumn(pParse,p,p->pOrderBy,tab1,1) ){ + rc = 1; + goto multi_select_end; + } + + addr = sqlite3VdbeAddOp(v, OP_OpenTemp, tab1, 0); + rc = multiSelectOpenTempAddr(p, addr); + if( rc!=SQLITE_OK ){ + goto multi_select_end; + } + sqlite3VdbeAddOp(v, OP_KeyAsData, tab1, 1); + assert( nAddrpEList ); + + /* Code the SELECTs to our left into temporary table "tab1". + */ + rc = sqlite3Select(pParse, pPrior, SRT_Union, tab1, 0, 0, 0, aff); + if( rc ){ + goto multi_select_end; + } + + /* Code the current SELECT into temporary table "tab2" + */ + addr = sqlite3VdbeAddOp(v, OP_OpenTemp, tab2, 0); + rc = multiSelectOpenTempAddr(p, addr); + if( rc!=SQLITE_OK ){ + goto multi_select_end; + } + sqlite3VdbeAddOp(v, OP_KeyAsData, tab2, 1); + assert( nAddrpPrior = 0; + nLimit = p->nLimit; + p->nLimit = -1; + nOffset = p->nOffset; + p->nOffset = 0; + rc = sqlite3Select(pParse, p, SRT_Union, tab2, 0, 0, 0, aff); + p->pPrior = pPrior; + p->nLimit = nLimit; + p->nOffset = nOffset; + if( rc ){ + goto multi_select_end; + } + + /* Generate code to take the intersection of the two temporary + ** tables. + */ + assert( p->pEList ); + if( eDest==SRT_Callback ){ + generateColumnNames(pParse, 0, p->pEList); + } + iBreak = sqlite3VdbeMakeLabel(v); + iCont = sqlite3VdbeMakeLabel(v); + sqlite3VdbeAddOp(v, OP_Rewind, tab1, iBreak); + computeLimitRegisters(pParse, p); + iStart = sqlite3VdbeAddOp(v, OP_FullKey, tab1, 0); + sqlite3VdbeAddOp(v, OP_NotFound, tab2, iCont); + rc = selectInnerLoop(pParse, p, p->pEList, tab1, p->pEList->nExpr, + p->pOrderBy, -1, eDest, iParm, + iCont, iBreak, 0); + if( rc ){ + rc = 1; + goto multi_select_end; + } + sqlite3VdbeResolveLabel(v, iCont); + sqlite3VdbeAddOp(v, OP_Next, tab1, iStart); + sqlite3VdbeResolveLabel(v, iBreak); + sqlite3VdbeAddOp(v, OP_Close, tab2, 0); + sqlite3VdbeAddOp(v, OP_Close, tab1, 0); + break; + } + } + + /* Make sure all SELECTs in the statement have the same number of elements + ** in their result sets. + */ + assert( p->pEList && pPrior->pEList ); + if( p->pEList->nExpr!=pPrior->pEList->nExpr ){ + sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s" + " do not have the same number of result columns", selectOpName(p->op)); + rc = 1; + goto multi_select_end; + } + + /* Set the number of columns in temporary tables + */ + nCol = p->pEList->nExpr; + while( nAddr>0 ){ + nAddr--; + sqlite3VdbeChangeP2(v, aAddr[nAddr], nCol); + } + + /* Compute collating sequences used by either the ORDER BY clause or + ** by any temporary tables needed to implement the compound select. + ** Attach the KeyInfo structure to all temporary tables. Invoke the + ** ORDER BY processing if there is an ORDER BY clause. + ** + ** This section is run by the right-most SELECT statement only. + ** SELECT statements to the left always skip this part. The right-most + ** SELECT might also skip this part if it has no ORDER BY clause and + ** no temp tables are required. + */ + if( p->pOrderBy || (pOpenTemp && pOpenTemp->nId>0) ){ + int i; /* Loop counter */ + KeyInfo *pKeyInfo; /* Collating sequence for the result set */ + + assert( p->ppOpenTemp == &pOpenTemp ); + pKeyInfo = sqliteMalloc(sizeof(*pKeyInfo)+nCol*sizeof(CollSeq*)); + if( !pKeyInfo ){ + rc = SQLITE_NOMEM; + goto multi_select_end; + } + + pKeyInfo->enc = pParse->db->enc; + pKeyInfo->nField = nCol; + + for(i=0; iaColl[i] = multiSelectCollSeq(pParse, p, i); + if( !pKeyInfo->aColl[i] ){ + pKeyInfo->aColl[i] = pParse->db->pDfltColl; + } + } + + for(i=0; pOpenTemp && inId; i++){ + int p3type = (i==0?P3_KEYINFO_HANDOFF:P3_KEYINFO); + int addr = pOpenTemp->a[i].idx; + sqlite3VdbeChangeP3(v, addr, (char *)pKeyInfo, p3type); + } + + if( p->pOrderBy ){ + struct ExprList_item *pOrderByTerm = p->pOrderBy->a; + for(i=0; ipOrderBy->nExpr; i++, pOrderByTerm++){ + Expr *pExpr = pOrderByTerm->pExpr; + char *zName = pOrderByTerm->zName; + assert( pExpr->op==TK_COLUMN && pExpr->iColumnpColl ); + if( zName ){ + pExpr->pColl = sqlite3LocateCollSeq(pParse, zName, -1); + }else{ + pExpr->pColl = pKeyInfo->aColl[pExpr->iColumn]; + } + } + generateSortTail(pParse, p, v, p->pEList->nExpr, eDest, iParm); + } + + if( !pOpenTemp ){ + /* This happens for UNION ALL ... ORDER BY */ + sqliteFree(pKeyInfo); + } + } + +multi_select_end: + if( pOpenTemp ){ + sqlite3IdListDelete(pOpenTemp); + } + p->ppOpenTemp = 0; + return rc; +} + +/* +** Scan through the expression pExpr. Replace every reference to +** a column in table number iTable with a copy of the iColumn-th +** entry in pEList. (But leave references to the ROWID column +** unchanged.) +** +** This routine is part of the flattening procedure. A subquery +** whose result set is defined by pEList appears as entry in the +** FROM clause of a SELECT such that the VDBE cursor assigned to that +** FORM clause entry is iTable. This routine make the necessary +** changes to pExpr so that it refers directly to the source table +** of the subquery rather the result set of the subquery. +*/ +static void substExprList(ExprList*,int,ExprList*); /* Forward Decl */ +static void substExpr(Expr *pExpr, int iTable, ExprList *pEList){ + if( pExpr==0 ) return; + if( pExpr->op==TK_COLUMN && pExpr->iTable==iTable ){ + if( pExpr->iColumn<0 ){ + pExpr->op = TK_NULL; + }else{ + Expr *pNew; + assert( pEList!=0 && pExpr->iColumnnExpr ); + assert( pExpr->pLeft==0 && pExpr->pRight==0 && pExpr->pList==0 ); + pNew = pEList->a[pExpr->iColumn].pExpr; + assert( pNew!=0 ); + pExpr->op = pNew->op; + assert( pExpr->pLeft==0 ); + pExpr->pLeft = sqlite3ExprDup(pNew->pLeft); + assert( pExpr->pRight==0 ); + pExpr->pRight = sqlite3ExprDup(pNew->pRight); + assert( pExpr->pList==0 ); + pExpr->pList = sqlite3ExprListDup(pNew->pList); + pExpr->iTable = pNew->iTable; + pExpr->iColumn = pNew->iColumn; + pExpr->iAgg = pNew->iAgg; + sqlite3TokenCopy(&pExpr->token, &pNew->token); + sqlite3TokenCopy(&pExpr->span, &pNew->span); + } + }else{ + substExpr(pExpr->pLeft, iTable, pEList); + substExpr(pExpr->pRight, iTable, pEList); + substExprList(pExpr->pList, iTable, pEList); + } +} +static void +substExprList(ExprList *pList, int iTable, ExprList *pEList){ + int i; + if( pList==0 ) return; + for(i=0; inExpr; i++){ + substExpr(pList->a[i].pExpr, iTable, pEList); + } +} + +/* +** This routine attempts to flatten subqueries in order to speed +** execution. It returns 1 if it makes changes and 0 if no flattening +** occurs. +** +** To understand the concept of flattening, consider the following +** query: +** +** SELECT a FROM (SELECT x+y AS a FROM t1 WHERE z<100) WHERE a>5 +** +** The default way of implementing this query is to execute the +** subquery first and store the results in a temporary table, then +** run the outer query on that temporary table. This requires two +** passes over the data. Furthermore, because the temporary table +** has no indices, the WHERE clause on the outer query cannot be +** optimized. +** +** This routine attempts to rewrite queries such as the above into +** a single flat select, like this: +** +** SELECT x+y AS a FROM t1 WHERE z<100 AND a>5 +** +** The code generated for this simpification gives the same result +** but only has to scan the data once. And because indices might +** exist on the table t1, a complete scan of the data might be +** avoided. +** +** Flattening is only attempted if all of the following are true: +** +** (1) The subquery and the outer query do not both use aggregates. +** +** (2) The subquery is not an aggregate or the outer query is not a join. +** +** (3) The subquery is not the right operand of a left outer join, or +** the subquery is not itself a join. (Ticket #306) +** +** (4) The subquery is not DISTINCT or the outer query is not a join. +** +** (5) The subquery is not DISTINCT or the outer query does not use +** aggregates. +** +** (6) The subquery does not use aggregates or the outer query is not +** DISTINCT. +** +** (7) The subquery has a FROM clause. +** +** (8) The subquery does not use LIMIT or the outer query is not a join. +** +** (9) The subquery does not use LIMIT or the outer query does not use +** aggregates. +** +** (10) The subquery does not use aggregates or the outer query does not +** use LIMIT. +** +** (11) The subquery and the outer query do not both have ORDER BY clauses. +** +** (12) The subquery is not the right term of a LEFT OUTER JOIN or the +** subquery has no WHERE clause. (added by ticket #350) +** +** In this routine, the "p" parameter is a pointer to the outer query. +** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query +** uses aggregates and subqueryIsAgg is true if the subquery uses aggregates. +** +** If flattening is not attempted, this routine is a no-op and returns 0. +** If flattening is attempted this routine returns 1. +** +** All of the expression analysis must occur on both the outer query and +** the subquery before this routine runs. +*/ +static int flattenSubquery( + Parse *pParse, /* The parsing context */ + Select *p, /* The parent or outer SELECT statement */ + int iFrom, /* Index in p->pSrc->a[] of the inner subquery */ + int isAgg, /* True if outer SELECT uses aggregate functions */ + int subqueryIsAgg /* True if the subquery uses aggregate functions */ +){ + Select *pSub; /* The inner query or "subquery" */ + SrcList *pSrc; /* The FROM clause of the outer query */ + SrcList *pSubSrc; /* The FROM clause of the subquery */ + ExprList *pList; /* The result set of the outer query */ + int iParent; /* VDBE cursor number of the pSub result set temp table */ + int i; /* Loop counter */ + Expr *pWhere; /* The WHERE clause */ + struct SrcList_item *pSubitem; /* The subquery */ + + /* Check to see if flattening is permitted. Return 0 if not. + */ + if( p==0 ) return 0; + pSrc = p->pSrc; + assert( pSrc && iFrom>=0 && iFromnSrc ); + pSubitem = &pSrc->a[iFrom]; + pSub = pSubitem->pSelect; + assert( pSub!=0 ); + if( isAgg && subqueryIsAgg ) return 0; + if( subqueryIsAgg && pSrc->nSrc>1 ) return 0; + pSubSrc = pSub->pSrc; + assert( pSubSrc ); + if( pSubSrc->nSrc==0 ) return 0; + if( (pSub->isDistinct || pSub->nLimit>=0) && (pSrc->nSrc>1 || isAgg) ){ + return 0; + } + if( (p->isDistinct || p->nLimit>=0) && subqueryIsAgg ) return 0; + if( p->pOrderBy && pSub->pOrderBy ) return 0; + + /* Restriction 3: If the subquery is a join, make sure the subquery is + ** not used as the right operand of an outer join. Examples of why this + ** is not allowed: + ** + ** t1 LEFT OUTER JOIN (t2 JOIN t3) + ** + ** If we flatten the above, we would get + ** + ** (t1 LEFT OUTER JOIN t2) JOIN t3 + ** + ** which is not at all the same thing. + */ + if( pSubSrc->nSrc>1 && iFrom>0 && (pSrc->a[iFrom-1].jointype & JT_OUTER)!=0 ){ + return 0; + } + + /* Restriction 12: If the subquery is the right operand of a left outer + ** join, make sure the subquery has no WHERE clause. + ** An examples of why this is not allowed: + ** + ** t1 LEFT OUTER JOIN (SELECT * FROM t2 WHERE t2.x>0) + ** + ** If we flatten the above, we would get + ** + ** (t1 LEFT OUTER JOIN t2) WHERE t2.x>0 + ** + ** But the t2.x>0 test will always fail on a NULL row of t2, which + ** effectively converts the OUTER JOIN into an INNER JOIN. + */ + if( iFrom>0 && (pSrc->a[iFrom-1].jointype & JT_OUTER)!=0 + && pSub->pWhere!=0 ){ + return 0; + } + + /* If we reach this point, it means flattening is permitted for the + ** iFrom-th entry of the FROM clause in the outer query. + */ + + /* Move all of the FROM elements of the subquery into the + ** the FROM clause of the outer query. Before doing this, remember + ** the cursor number for the original outer query FROM element in + ** iParent. The iParent cursor will never be used. Subsequent code + ** will scan expressions looking for iParent references and replace + ** those references with expressions that resolve to the subquery FROM + ** elements we are now copying in. + */ + iParent = pSubitem->iCursor; + { + int nSubSrc = pSubSrc->nSrc; + int jointype = pSubitem->jointype; + Table *pTab = pSubitem->pTab; + + if( pTab && pTab->isTransient ){ + sqlite3DeleteTable(0, pSubitem->pTab); + } + sqliteFree(pSubitem->zDatabase); + sqliteFree(pSubitem->zName); + sqliteFree(pSubitem->zAlias); + if( nSubSrc>1 ){ + int extra = nSubSrc - 1; + for(i=1; ipSrc = pSrc; + for(i=pSrc->nSrc-1; i-extra>=iFrom; i--){ + pSrc->a[i] = pSrc->a[i-extra]; + } + } + for(i=0; ia[i+iFrom] = pSubSrc->a[i]; + memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); + } + pSrc->a[iFrom+nSubSrc-1].jointype = jointype; + } + + /* Now begin substituting subquery result set expressions for + ** references to the iParent in the outer query. + ** + ** Example: + ** + ** SELECT a+5, b*10 FROM (SELECT x*3 AS a, y+10 AS b FROM t1) WHERE a>b; + ** \ \_____________ subquery __________/ / + ** \_____________________ outer query ______________________________/ + ** + ** We look at every expression in the outer query and every place we see + ** "a" we substitute "x*3" and every place we see "b" we substitute "y+10". + */ + substExprList(p->pEList, iParent, pSub->pEList); + pList = p->pEList; + for(i=0; inExpr; i++){ + Expr *pExpr; + if( pList->a[i].zName==0 && (pExpr = pList->a[i].pExpr)->span.z!=0 ){ + pList->a[i].zName = sqliteStrNDup(pExpr->span.z, pExpr->span.n); + } + } + if( isAgg ){ + substExprList(p->pGroupBy, iParent, pSub->pEList); + substExpr(p->pHaving, iParent, pSub->pEList); + } + if( pSub->pOrderBy ){ + assert( p->pOrderBy==0 ); + p->pOrderBy = pSub->pOrderBy; + pSub->pOrderBy = 0; + }else if( p->pOrderBy ){ + substExprList(p->pOrderBy, iParent, pSub->pEList); + } + if( pSub->pWhere ){ + pWhere = sqlite3ExprDup(pSub->pWhere); + }else{ + pWhere = 0; + } + if( subqueryIsAgg ){ + assert( p->pHaving==0 ); + p->pHaving = p->pWhere; + p->pWhere = pWhere; + substExpr(p->pHaving, iParent, pSub->pEList); + p->pHaving = sqlite3ExprAnd(p->pHaving, sqlite3ExprDup(pSub->pHaving)); + assert( p->pGroupBy==0 ); + p->pGroupBy = sqlite3ExprListDup(pSub->pGroupBy); + }else{ + substExpr(p->pWhere, iParent, pSub->pEList); + p->pWhere = sqlite3ExprAnd(p->pWhere, pWhere); + } + + /* The flattened query is distinct if either the inner or the + ** outer query is distinct. + */ + p->isDistinct = p->isDistinct || pSub->isDistinct; + + /* Transfer the limit expression from the subquery to the outer + ** query. + */ + if( pSub->nLimit>=0 ){ + if( p->nLimit<0 ){ + p->nLimit = pSub->nLimit; + }else if( p->nLimit+p->nOffset > pSub->nLimit+pSub->nOffset ){ + p->nLimit = pSub->nLimit + pSub->nOffset - p->nOffset; + } + } + p->nOffset += pSub->nOffset; + + /* Finially, delete what is left of the subquery and return + ** success. + */ + sqlite3SelectDelete(pSub); + return 1; +} + +/* +** Analyze the SELECT statement passed in as an argument to see if it +** is a simple min() or max() query. If it is and this query can be +** satisfied using a single seek to the beginning or end of an index, +** then generate the code for this SELECT and return 1. If this is not a +** simple min() or max() query, then return 0; +** +** A simply min() or max() query looks like this: +** +** SELECT min(a) FROM table; +** SELECT max(a) FROM table; +** +** The query may have only a single table in its FROM argument. There +** can be no GROUP BY or HAVING or WHERE clauses. The result set must +** be the min() or max() of a single column of the table. The column +** in the min() or max() function must be indexed. +** +** The parameters to this routine are the same as for sqlite3Select(). +** See the header comment on that routine for additional information. +*/ +static int simpleMinMaxQuery(Parse *pParse, Select *p, int eDest, int iParm){ + Expr *pExpr; + int iCol; + Table *pTab; + Index *pIdx; + int base; + Vdbe *v; + int seekOp; + int cont; + ExprList *pEList, *pList, eList; + struct ExprList_item eListItem; + SrcList *pSrc; + + + /* Check to see if this query is a simple min() or max() query. Return + ** zero if it is not. + */ + if( p->pGroupBy || p->pHaving || p->pWhere ) return 0; + pSrc = p->pSrc; + if( pSrc->nSrc!=1 ) return 0; + pEList = p->pEList; + if( pEList->nExpr!=1 ) return 0; + pExpr = pEList->a[0].pExpr; + if( pExpr->op!=TK_AGG_FUNCTION ) return 0; + pList = pExpr->pList; + if( pList==0 || pList->nExpr!=1 ) return 0; + if( pExpr->token.n!=3 ) return 0; + if( sqlite3StrNICmp(pExpr->token.z,"min",3)==0 ){ + seekOp = OP_Rewind; + }else if( sqlite3StrNICmp(pExpr->token.z,"max",3)==0 ){ + seekOp = OP_Last; + }else{ + return 0; + } + pExpr = pList->a[0].pExpr; + if( pExpr->op!=TK_COLUMN ) return 0; + iCol = pExpr->iColumn; + pTab = pSrc->a[0].pTab; + + /* If we get to here, it means the query is of the correct form. + ** Check to make sure we have an index and make pIdx point to the + ** appropriate index. If the min() or max() is on an INTEGER PRIMARY + ** key column, no index is necessary so set pIdx to NULL. If no + ** usable index is found, return 0. + */ + if( iCol<0 ){ + pIdx = 0; + }else{ + CollSeq *pColl = sqlite3ExprCollSeq(pParse, pExpr); + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + assert( pIdx->nColumn>=1 ); + if( pIdx->aiColumn[0]==iCol && pIdx->keyInfo.aColl[0]==pColl ) break; + } + if( pIdx==0 ) return 0; + } + + /* Identify column types if we will be using the callback. This + ** step is skipped if the output is going to a table or a memory cell. + ** The column names have already been generated in the calling function. + */ + v = sqlite3GetVdbe(pParse); + if( v==0 ) return 0; + + /* If the output is destined for a temporary table, open that table. + */ + if( eDest==SRT_TempTable ){ + sqlite3VdbeAddOp(v, OP_OpenTemp, iParm, 0); + sqlite3VdbeAddOp(v, OP_SetNumColumns, iParm, 1); + } + + /* Generating code to find the min or the max. Basically all we have + ** to do is find the first or the last entry in the chosen index. If + ** the min() or max() is on the INTEGER PRIMARY KEY, then find the first + ** or last entry in the main table. + */ + sqlite3CodeVerifySchema(pParse, pTab->iDb); + base = pSrc->a[0].iCursor; + computeLimitRegisters(pParse, p); + if( pSrc->a[0].pSelect==0 ){ + sqlite3OpenTableForReading(v, base, pTab); + } + cont = sqlite3VdbeMakeLabel(v); + if( pIdx==0 ){ + sqlite3VdbeAddOp(v, seekOp, base, 0); + }else{ + sqlite3VdbeAddOp(v, OP_Integer, pIdx->iDb, 0); + sqlite3VdbeOp3(v, OP_OpenRead, base+1, pIdx->tnum, + (char*)&pIdx->keyInfo, P3_KEYINFO); + if( seekOp==OP_Rewind ){ + sqlite3VdbeAddOp(v, OP_String, 0, 0); + sqlite3VdbeAddOp(v, OP_MakeRecord, 1, 0); + seekOp = OP_MoveGt; + } + sqlite3VdbeAddOp(v, seekOp, base+1, 0); + sqlite3VdbeAddOp(v, OP_IdxRecno, base+1, 0); + sqlite3VdbeAddOp(v, OP_Close, base+1, 0); + sqlite3VdbeAddOp(v, OP_MoveGe, base, 0); + } + eList.nExpr = 1; + memset(&eListItem, 0, sizeof(eListItem)); + eList.a = &eListItem; + eList.a[0].pExpr = pExpr; + selectInnerLoop(pParse, p, &eList, 0, 0, 0, -1, eDest, iParm, cont, cont, 0); + sqlite3VdbeResolveLabel(v, cont); + sqlite3VdbeAddOp(v, OP_Close, base, 0); + + return 1; +} + +/* +** Analyze and ORDER BY or GROUP BY clause in a SELECT statement. Return +** the number of errors seen. +** +** An ORDER BY or GROUP BY is a list of expressions. If any expression +** is an integer constant, then that expression is replaced by the +** corresponding entry in the result set. +*/ +static int processOrderGroupBy( + Parse *pParse, /* Parsing context */ + ExprList *pOrderBy, /* The ORDER BY or GROUP BY clause to be processed */ + SrcList *pTabList, /* The FROM clause */ + ExprList *pEList, /* The result set */ + int isAgg, /* True if aggregate functions are involved */ + const char *zType /* Either "ORDER" or "GROUP", as appropriate */ +){ + int i; + if( pOrderBy==0 ) return 0; + for(i=0; inExpr; i++){ + int iCol; + Expr *pE = pOrderBy->a[i].pExpr; + if( sqlite3ExprIsInteger(pE, &iCol) && iCol>0 && iCol<=pEList->nExpr ){ + sqlite3ExprDelete(pE); + pE = pOrderBy->a[i].pExpr = sqlite3ExprDup(pEList->a[iCol-1].pExpr); + } + if( sqlite3ExprResolveAndCheck(pParse, pTabList, pEList, pE, isAgg, 0) ){ + return 1; + } + if( sqlite3ExprIsConstant(pE) ){ + if( sqlite3ExprIsInteger(pE, &iCol)==0 ){ + sqlite3ErrorMsg(pParse, + "%s BY terms must not be non-integer constants", zType); + return 1; + }else if( iCol<=0 || iCol>pEList->nExpr ){ + sqlite3ErrorMsg(pParse, + "%s BY column number %d out of range - should be " + "between 1 and %d", zType, iCol, pEList->nExpr); + return 1; + } + } + } + return 0; +} + +/* +** Generate code for the given SELECT statement. +** +** The results are distributed in various ways depending on the +** value of eDest and iParm. +** +** eDest Value Result +** ------------ ------------------------------------------- +** SRT_Callback Invoke the callback for each row of the result. +** +** SRT_Mem Store first result in memory cell iParm +** +** SRT_Set Store results as keys of table iParm. +** +** SRT_Union Store results as a key in a temporary table iParm +** +** SRT_Except Remove results from the temporary table iParm. +** +** SRT_Table Store results in temporary table iParm +** +** The table above is incomplete. Additional eDist value have be added +** since this comment was written. See the selectInnerLoop() function for +** a complete listing of the allowed values of eDest and their meanings. +** +** This routine returns the number of errors. If any errors are +** encountered, then an appropriate error message is left in +** pParse->zErrMsg. +** +** This routine does NOT free the Select structure passed in. The +** calling function needs to do that. +** +** The pParent, parentTab, and *pParentAgg fields are filled in if this +** SELECT is a subquery. This routine may try to combine this SELECT +** with its parent to form a single flat query. In so doing, it might +** change the parent query from a non-aggregate to an aggregate query. +** For that reason, the pParentAgg flag is passed as a pointer, so it +** can be changed. +** +** Example 1: The meaning of the pParent parameter. +** +** SELECT * FROM t1 JOIN (SELECT x, count(*) FROM t2) JOIN t3; +** \ \_______ subquery _______/ / +** \ / +** \____________________ outer query ___________________/ +** +** This routine is called for the outer query first. For that call, +** pParent will be NULL. During the processing of the outer query, this +** routine is called recursively to handle the subquery. For the recursive +** call, pParent will point to the outer query. Because the subquery is +** the second element in a three-way join, the parentTab parameter will +** be 1 (the 2nd value of a 0-indexed array.) +*/ +int sqlite3Select( + Parse *pParse, /* The parser context */ + Select *p, /* The SELECT statement being coded. */ + int eDest, /* How to dispose of the results */ + int iParm, /* A parameter used by the eDest disposal method */ + Select *pParent, /* Another SELECT for which this is a sub-query */ + int parentTab, /* Index in pParent->pSrc of this query */ + int *pParentAgg, /* True if pParent uses aggregate functions */ + char *aff /* If eDest is SRT_Union, the affinity string */ +){ + int i; + WhereInfo *pWInfo; + Vdbe *v; + int isAgg = 0; /* True for select lists like "count(*)" */ + ExprList *pEList; /* List of columns to extract. */ + SrcList *pTabList; /* List of tables to select from */ + Expr *pWhere; /* The WHERE clause. May be NULL */ + ExprList *pOrderBy; /* The ORDER BY clause. May be NULL */ + ExprList *pGroupBy; /* The GROUP BY clause. May be NULL */ + Expr *pHaving; /* The HAVING clause. May be NULL */ + int isDistinct; /* True if the DISTINCT keyword is present */ + int distinct; /* Table to use for the distinct set */ + int rc = 1; /* Value to return from this function */ + + if( sqlite3_malloc_failed || pParse->nErr || p==0 ) return 1; + if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1; + + /* If there is are a sequence of queries, do the earlier ones first. + */ + if( p->pPrior ){ + return multiSelect(pParse, p, eDest, iParm, aff); + } + + /* Make local copies of the parameters for this query. + */ + pTabList = p->pSrc; + pWhere = p->pWhere; + pOrderBy = p->pOrderBy; + pGroupBy = p->pGroupBy; + pHaving = p->pHaving; + isDistinct = p->isDistinct; + + /* Allocate VDBE cursors for each table in the FROM clause + */ + sqlite3SrcListAssignCursors(pParse, pTabList); + + /* + ** Do not even attempt to generate any code if we have already seen + ** errors before this routine starts. + */ + if( pParse->nErr>0 ) goto select_end; + + /* Expand any "*" terms in the result set. (For example the "*" in + ** "SELECT * FROM t1") The fillInColumnlist() routine also does some + ** other housekeeping - see the header comment for details. + */ + if( fillInColumnList(pParse, p) ){ + goto select_end; + } + pWhere = p->pWhere; + pEList = p->pEList; + if( pEList==0 ) goto select_end; + + /* If writing to memory or generating a set + ** only a single column may be output. + */ + if( (eDest==SRT_Mem || eDest==SRT_Set) && pEList->nExpr>1 ){ + sqlite3ErrorMsg(pParse, "only a single result allowed for " + "a SELECT that is part of an expression"); + goto select_end; + } + + /* ORDER BY is ignored for some destinations. + */ + switch( eDest ){ + case SRT_Union: + case SRT_Except: + case SRT_Discard: + pOrderBy = 0; + break; + default: + break; + } + + /* At this point, we should have allocated all the cursors that we + ** need to handle subquerys and temporary tables. + ** + ** Resolve the column names and do a semantics check on all the expressions. + */ + for(i=0; inExpr; i++){ + if( sqlite3ExprResolveAndCheck(pParse, pTabList, 0, pEList->a[i].pExpr, + 1, &isAgg) ){ + goto select_end; + } + } + if( sqlite3ExprResolveAndCheck(pParse, pTabList, pEList, pWhere, 0, 0) ){ + goto select_end; + } + if( pHaving ){ + if( pGroupBy==0 ){ + sqlite3ErrorMsg(pParse, "a GROUP BY clause is required before HAVING"); + goto select_end; + } + if( sqlite3ExprResolveAndCheck(pParse, pTabList, pEList,pHaving,1,&isAgg) ){ + goto select_end; + } + } + if( processOrderGroupBy(pParse, pOrderBy, pTabList, pEList, isAgg, "ORDER") + || processOrderGroupBy(pParse, pGroupBy, pTabList, pEList, isAgg, "GROUP") + ){ + goto select_end; + } + + /* Begin generating code. + */ + v = sqlite3GetVdbe(pParse); + if( v==0 ) goto select_end; + + /* Identify column names if we will be using them in a callback. This + ** step is skipped if the output is going to some other destination. + */ + if( eDest==SRT_Callback ){ + generateColumnNames(pParse, pTabList, pEList); + } + + /* Generate code for all sub-queries in the FROM clause + */ + for(i=0; inSrc; i++){ + const char *zSavedAuthContext = 0; + int needRestoreContext; + + if( pTabList->a[i].pSelect==0 ) continue; + if( pTabList->a[i].zName!=0 ){ + zSavedAuthContext = pParse->zAuthContext; + pParse->zAuthContext = pTabList->a[i].zName; + needRestoreContext = 1; + }else{ + needRestoreContext = 0; + } + sqlite3Select(pParse, pTabList->a[i].pSelect, SRT_TempTable, + pTabList->a[i].iCursor, p, i, &isAgg, 0); + if( needRestoreContext ){ + pParse->zAuthContext = zSavedAuthContext; + } + pTabList = p->pSrc; + pWhere = p->pWhere; + if( eDest!=SRT_Union && eDest!=SRT_Except && eDest!=SRT_Discard ){ + pOrderBy = p->pOrderBy; + } + pGroupBy = p->pGroupBy; + pHaving = p->pHaving; + isDistinct = p->isDistinct; + } + + /* Check for the special case of a min() or max() function by itself + ** in the result set. + */ + if( simpleMinMaxQuery(pParse, p, eDest, iParm) ){ + rc = 0; + goto select_end; + } + + /* Check to see if this is a subquery that can be "flattened" into its parent. + ** If flattening is a possiblity, do so and return immediately. + */ + if( pParent && pParentAgg && + flattenSubquery(pParse, pParent, parentTab, *pParentAgg, isAgg) ){ + if( isAgg ) *pParentAgg = 1; + return rc; + } + + /* If there is an ORDER BY clause, resolve any collation sequences + ** names that have been explicitly specified. + */ + if( pOrderBy ){ + for(i=0; inExpr; i++){ + if( pOrderBy->a[i].zName ){ + pOrderBy->a[i].pExpr->pColl = + sqlite3LocateCollSeq(pParse, pOrderBy->a[i].zName, -1); + } + } + if( pParse->nErr ){ + goto select_end; + } + } + + /* Set the limiter. + */ + computeLimitRegisters(pParse, p); + + /* If the output is destined for a temporary table, open that table. + */ + if( eDest==SRT_TempTable ){ + sqlite3VdbeAddOp(v, OP_OpenTemp, iParm, 0); + sqlite3VdbeAddOp(v, OP_SetNumColumns, iParm, pEList->nExpr); + } + + /* Do an analysis of aggregate expressions. + */ + sqliteAggregateInfoReset(pParse); + if( isAgg || pGroupBy ){ + assert( pParse->nAgg==0 ); + isAgg = 1; + for(i=0; inExpr; i++){ + if( sqlite3ExprAnalyzeAggregates(pParse, pEList->a[i].pExpr) ){ + goto select_end; + } + } + if( pGroupBy ){ + for(i=0; inExpr; i++){ + if( sqlite3ExprAnalyzeAggregates(pParse, pGroupBy->a[i].pExpr) ){ + goto select_end; + } + } + } + if( pHaving && sqlite3ExprAnalyzeAggregates(pParse, pHaving) ){ + goto select_end; + } + if( pOrderBy ){ + for(i=0; inExpr; i++){ + if( sqlite3ExprAnalyzeAggregates(pParse, pOrderBy->a[i].pExpr) ){ + goto select_end; + } + } + } + } + + /* Reset the aggregator + */ + if( isAgg ){ + int addr = sqlite3VdbeAddOp(v, OP_AggReset, (pGroupBy?0:1), pParse->nAgg); + for(i=0; inAgg; i++){ + FuncDef *pFunc; + if( (pFunc = pParse->aAgg[i].pFunc)!=0 && pFunc->xFinalize!=0 ){ + sqlite3VdbeOp3(v, OP_AggInit, 0, i, (char*)pFunc, P3_FUNCDEF); + } + } + if( pGroupBy ){ + int sz = sizeof(KeyInfo) + pGroupBy->nExpr*sizeof(CollSeq*); + KeyInfo *pKey = (KeyInfo *)sqliteMalloc(sz); + if( 0==pKey ){ + goto select_end; + } + pKey->enc = pParse->db->enc; + pKey->nField = pGroupBy->nExpr; + for(i=0; inExpr; i++){ + pKey->aColl[i] = sqlite3ExprCollSeq(pParse, pGroupBy->a[i].pExpr); + if( !pKey->aColl[i] ){ + pKey->aColl[i] = pParse->db->pDfltColl; + } + } + sqlite3VdbeChangeP3(v, addr, (char *)pKey, P3_KEYINFO_HANDOFF); + } + } + + /* Initialize the memory cell to NULL + */ + if( eDest==SRT_Mem ){ + sqlite3VdbeAddOp(v, OP_String8, 0, 0); + sqlite3VdbeAddOp(v, OP_MemStore, iParm, 1); + } + + /* Open a temporary table to use for the distinct set. + */ + if( isDistinct ){ + distinct = pParse->nTab++; + openTempIndex(pParse, p, distinct, 0); + }else{ + distinct = -1; + } + + /* Begin the database scan + */ + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, + pGroupBy ? 0 : &pOrderBy); + if( pWInfo==0 ) goto select_end; + + /* Use the standard inner loop if we are not dealing with + ** aggregates + */ + if( !isAgg ){ + if( selectInnerLoop(pParse, p, pEList, 0, 0, pOrderBy, distinct, eDest, + iParm, pWInfo->iContinue, pWInfo->iBreak, aff) ){ + goto select_end; + } + } + + /* If we are dealing with aggregates, then do the special aggregate + ** processing. + */ + else{ + AggExpr *pAgg; + if( pGroupBy ){ + int lbl1; + for(i=0; inExpr; i++){ + sqlite3ExprCode(pParse, pGroupBy->a[i].pExpr); + } + /* No affinity string is attached to the following OP_MakeRecord + ** because we do not need to do any coercion of datatypes. */ + sqlite3VdbeAddOp(v, OP_MakeRecord, pGroupBy->nExpr, 0); + lbl1 = sqlite3VdbeMakeLabel(v); + sqlite3VdbeAddOp(v, OP_AggFocus, 0, lbl1); + for(i=0, pAgg=pParse->aAgg; inAgg; i++, pAgg++){ + if( pAgg->isAgg ) continue; + sqlite3ExprCode(pParse, pAgg->pExpr); + sqlite3VdbeAddOp(v, OP_AggSet, 0, i); + } + sqlite3VdbeResolveLabel(v, lbl1); + } + for(i=0, pAgg=pParse->aAgg; inAgg; i++, pAgg++){ + Expr *pE; + int nExpr; + FuncDef *pDef; + if( !pAgg->isAgg ) continue; + assert( pAgg->pFunc!=0 ); + assert( pAgg->pFunc->xStep!=0 ); + pDef = pAgg->pFunc; + pE = pAgg->pExpr; + assert( pE!=0 ); + assert( pE->op==TK_AGG_FUNCTION ); + nExpr = sqlite3ExprCodeExprList(pParse, pE->pList); + sqlite3VdbeAddOp(v, OP_Integer, i, 0); + if( pDef->needCollSeq ){ + CollSeq *pColl = 0; + int j; + for(j=0; !pColl && jpList->a[j].pExpr); + } + if( !pColl ) pColl = pParse->db->pDfltColl; + sqlite3VdbeOp3(v, OP_CollSeq, 0, 0, (char *)pColl, P3_COLLSEQ); + } + sqlite3VdbeOp3(v, OP_AggFunc, 0, nExpr, (char*)pDef, P3_POINTER); + } + } + + /* End the database scan loop. + */ + sqlite3WhereEnd(pWInfo); + + /* If we are processing aggregates, we need to set up a second loop + ** over all of the aggregate values and process them. + */ + if( isAgg ){ + int endagg = sqlite3VdbeMakeLabel(v); + int startagg; + startagg = sqlite3VdbeAddOp(v, OP_AggNext, 0, endagg); + pParse->useAgg = 1; + if( pHaving ){ + sqlite3ExprIfFalse(pParse, pHaving, startagg, 1); + } + if( selectInnerLoop(pParse, p, pEList, 0, 0, pOrderBy, distinct, eDest, + iParm, startagg, endagg, aff) ){ + goto select_end; + } + sqlite3VdbeAddOp(v, OP_Goto, 0, startagg); + sqlite3VdbeResolveLabel(v, endagg); + sqlite3VdbeAddOp(v, OP_Noop, 0, 0); + pParse->useAgg = 0; + } + + /* If there is an ORDER BY clause, then we need to sort the results + ** and send them to the callback one by one. + */ + if( pOrderBy ){ + generateSortTail(pParse, p, v, pEList->nExpr, eDest, iParm); + } + + /* If this was a subquery, we have now converted the subquery into a + ** temporary table. So delete the subquery structure from the parent + ** to prevent this subquery from being evaluated again and to force the + ** the use of the temporary table. + */ + if( pParent ){ + assert( pParent->pSrc->nSrc>parentTab ); + assert( pParent->pSrc->a[parentTab].pSelect==p ); + sqlite3SelectDelete(p); + pParent->pSrc->a[parentTab].pSelect = 0; + } + + /* The SELECT was successfully coded. Set the return code to 0 + ** to indicate no errors. + */ + rc = 0; + + /* Control jumps to here if an error is encountered above, or upon + ** successful coding of the SELECT. + */ +select_end: + sqliteAggregateInfoReset(pParse); + return rc; +} diff --git a/kopete/plugins/statistics/sqlite/shell.c b/kopete/plugins/statistics/sqlite/shell.c new file mode 100644 index 00000000..bdd13cc9 --- /dev/null +++ b/kopete/plugins/statistics/sqlite/shell.c @@ -0,0 +1,1786 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code to implement the "sqlite" command line +** utility for accessing SQLite databases. +** +** $Id$ +*/ +#include +#include +#include +#include +#include "sqlite3.h" +#include + +#if !defined(_WIN32) && !defined(WIN32) && !defined(__MACOS__) +# include +# include +# include +# include +#endif + +#ifdef __MACOS__ +# include +# include +# include +# include +# include +# include +#endif + +#if defined(HAVE_READLINE) && HAVE_READLINE==1 +# include +# include +#else +# define readline(p) local_getline(p,stdin) +# define add_history(X) +# define read_history(X) +# define write_history(X) +# define stifle_history(X) +#endif + +/* Make sure isatty() has a prototype. +*/ +extern int isatty(); + +/* +** The following is the open SQLite database. We make a pointer +** to this database a static variable so that it can be accessed +** by the SIGINT handler to interrupt database processing. +*/ +static sqlite3 *db = 0; + +/* +** True if an interrupt (Control-C) has been received. +*/ +static int seenInterrupt = 0; + +/* +** This is the name of our program. It is set in main(), used +** in a number of other places, mostly for error messages. +*/ +static char *Argv0; + +/* +** Prompt strings. Initialized in main. Settable with +** .prompt main continue +*/ +static char mainPrompt[20]; /* First line prompt. default: "sqlite> "*/ +static char continuePrompt[20]; /* Continuation prompt. default: " ...> " */ + + +/* +** Determines if a string is a number of not. +*/ +static int isNumber(const unsigned char *z, int *realnum){ + if( *z=='-' || *z=='+' ) z++; + if( !isdigit(*z) ){ + return 0; + } + z++; + if( realnum ) *realnum = 0; + while( isdigit(*z) ){ z++; } + if( *z=='.' ){ + z++; + if( !isdigit(*z) ) return 0; + while( isdigit(*z) ){ z++; } + if( realnum ) *realnum = 1; + } + if( *z=='e' || *z=='E' ){ + z++; + if( *z=='+' || *z=='-' ) z++; + if( !isdigit(*z) ) return 0; + while( isdigit(*z) ){ z++; } + if( realnum ) *realnum = 1; + } + return *z==0; +} + +/* +** A global char* and an SQL function to access its current value +** from within an SQL statement. This program used to use the +** sqlite_exec_printf() API to substitue a string into an SQL statement. +** The correct way to do this with sqlite3 is to use the bind API, but +** since the shell is built around the callback paradigm it would be a lot +** of work. Instead just use this hack, which is quite harmless. +*/ +static const char *zShellStatic = 0; +static void shellstaticFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( 0==argc ); + assert( zShellStatic ); + sqlite3_result_text(context, zShellStatic, -1, SQLITE_STATIC); +} + + +/* +** This routine reads a line of text from FILE in, stores +** the text in memory obtained from malloc() and returns a pointer +** to the text. NULL is returned at end of file, or if malloc() +** fails. +** +** The interface is like "readline" but no command-line editing +** is done. +*/ +static char *local_getline(char *zPrompt, FILE *in){ + char *zLine; + int nLine; + int n; + int eol; + + if( zPrompt && *zPrompt ){ + printf("%s",zPrompt); + fflush(stdout); + } + nLine = 100; + zLine = malloc( nLine ); + if( zLine==0 ) return 0; + n = 0; + eol = 0; + while( !eol ){ + if( n+100>nLine ){ + nLine = nLine*2 + 100; + zLine = realloc(zLine, nLine); + if( zLine==0 ) return 0; + } + if( fgets(&zLine[n], nLine - n, in)==0 ){ + if( n==0 ){ + free(zLine); + return 0; + } + zLine[n] = 0; + eol = 1; + break; + } + while( zLine[n] ){ n++; } + if( n>0 && zLine[n-1]=='\n' ){ + n--; + zLine[n] = 0; + eol = 1; + } + } + zLine = realloc( zLine, n+1 ); + return zLine; +} + +/* +** Retrieve a single line of input text. "isatty" is true if text +** is coming from a terminal. In that case, we issue a prompt and +** attempt to use "readline" for command-line editing. If "isatty" +** is false, use "local_getline" instead of "readline" and issue no prompt. +** +** zPrior is a string of prior text retrieved. If not the empty +** string, then issue a continuation prompt. +*/ +static char *one_input_line(const char *zPrior, FILE *in){ + char *zPrompt; + char *zResult; + if( in!=0 ){ + return local_getline(0, in); + } + if( zPrior && zPrior[0] ){ + zPrompt = continuePrompt; + }else{ + zPrompt = mainPrompt; + } + zResult = readline(zPrompt); + if( zResult ) add_history(zResult); + return zResult; +} + +struct previous_mode_data { + int valid; /* Is there legit data in here? */ + int mode; + int showHeader; + int colWidth[100]; +}; +/* +** An pointer to an instance of this structure is passed from +** the main program to the callback. This is used to communicate +** state and mode information. +*/ +struct callback_data { + sqlite3 *db; /* The database */ + int echoOn; /* True to echo input commands */ + int cnt; /* Number of records displayed so far */ + FILE *out; /* Write results here */ + int mode; /* An output mode setting */ + int showHeader; /* True to show column names in List or Column mode */ + char *zDestTable; /* Name of destination table when MODE_Insert */ + char separator[20]; /* Separator character for MODE_List */ + int colWidth[100]; /* Requested width of each column when in column mode*/ + int actualWidth[100]; /* Actual width of each column */ + char nullvalue[20]; /* The text to print when a NULL comes back from + ** the database */ + struct previous_mode_data explainPrev; + /* Holds the mode information just before + ** .explain ON */ + char outfile[FILENAME_MAX]; /* Filename for *out */ + const char *zDbFilename; /* name of the database file */ + char *zKey; /* Encryption key */ +}; + +/* +** These are the allowed modes. +*/ +#define MODE_Line 0 /* One column per line. Blank line between records */ +#define MODE_Column 1 /* One record per line in neat columns */ +#define MODE_List 2 /* One record per line with a separator */ +#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */ +#define MODE_Html 4 /* Generate an XHTML table */ +#define MODE_Insert 5 /* Generate SQL "insert" statements */ +#define MODE_Tcl 6 /* Generate ANSI-C or TCL quoted elements */ +#define MODE_Csv 7 /* Quote strings, numbers are plain */ +#define MODE_NUM_OF 8 /* The number of modes (not a mode itself) */ + +char *modeDescr[MODE_NUM_OF] = { + "line", + "column", + "list", + "semi", + "html", + "insert", + "tcl", + "csv", +}; + +/* +** Number of elements in an array +*/ +#define ArraySize(X) (sizeof(X)/sizeof(X[0])) + +/* +** Output the given string as a quoted string using SQL quoting conventions. +*/ +static void output_quoted_string(FILE *out, const char *z){ + int i; + int nSingle = 0; + for(i=0; z[i]; i++){ + if( z[i]=='\'' ) nSingle++; + } + if( nSingle==0 ){ + fprintf(out,"'%s'",z); + }else{ + fprintf(out,"'"); + while( *z ){ + for(i=0; z[i] && z[i]!='\''; i++){} + if( i==0 ){ + fprintf(out,"''"); + z++; + }else if( z[i]=='\'' ){ + fprintf(out,"%.*s''",i,z); + z += i+1; + }else{ + fprintf(out,"%s",z); + break; + } + } + fprintf(out,"'"); + } +} + +/* +** Output the given string as a quoted according to C or TCL quoting rules. +*/ +static void output_c_string(FILE *out, const char *z){ + unsigned int c; + fputc('"', out); + while( (c = *(z++))!=0 ){ + if( c=='\\' ){ + fputc(c, out); + fputc(c, out); + }else if( c=='\t' ){ + fputc('\\', out); + fputc('t', out); + }else if( c=='\n' ){ + fputc('\\', out); + fputc('n', out); + }else if( c=='\r' ){ + fputc('\\', out); + fputc('r', out); + }else if( !isprint(c) ){ + fprintf(out, "\\%03o", c); + }else{ + fputc(c, out); + } + } + fputc('"', out); +} + +/* +** Output the given string with characters that are special to +** HTML escaped. +*/ +static void output_html_string(FILE *out, const char *z){ + int i; + while( *z ){ + for(i=0; z[i] && z[i]!='<' && z[i]!='&'; i++){} + if( i>0 ){ + fprintf(out,"%.*s",i,z); + } + if( z[i]=='<' ){ + fprintf(out,"<"); + }else if( z[i]=='&' ){ + fprintf(out,"&"); + }else{ + break; + } + z += i + 1; + } +} + +/* +** Output a single term of CSV. Actually, p->separator is used for +** the separator, which may or may not be a comma. p->nullvalue is +** the null value. Strings are quoted using ANSI-C rules. Numbers +** appear outside of quotes. +*/ +static void output_csv(struct callback_data *p, const char *z, int bSep){ + if( z==0 ){ + fprintf(p->out,"%s",p->nullvalue); + }else if( isNumber(z, 0) ){ + fprintf(p->out,"%s",z); + }else{ + output_c_string(p->out, z); + } + if( bSep ){ + fprintf(p->out, p->separator); + } +} + +/* +** This routine runs when the user presses Ctrl-C +*/ +static void interrupt_handler(int NotUsed){ + seenInterrupt = 1; + if( db ) sqlite3_interrupt(db); +} + +/* +** This is the callback routine that the SQLite library +** invokes for each row of a query result. +*/ +static int callback(void *pArg, int nArg, char **azArg, char **azCol){ + int i; + struct callback_data *p = (struct callback_data*)pArg; + switch( p->mode ){ + case MODE_Line: { + int w = 5; + if( azArg==0 ) break; + for(i=0; iw ) w = len; + } + if( p->cnt++>0 ) fprintf(p->out,"\n"); + for(i=0; iout,"%*s = %s\n", w, azCol[i], + azArg[i] ? azArg[i] : p->nullvalue); + } + break; + } + case MODE_Column: { + if( p->cnt++==0 ){ + for(i=0; icolWidth) ){ + w = p->colWidth[i]; + }else{ + w = 0; + } + if( w<=0 ){ + w = strlen(azCol[i] ? azCol[i] : ""); + if( w<10 ) w = 10; + n = strlen(azArg && azArg[i] ? azArg[i] : p->nullvalue); + if( wactualWidth) ){ + p->actualWidth[i] = w; + } + if( p->showHeader ){ + fprintf(p->out,"%-*.*s%s",w,w,azCol[i], i==nArg-1 ? "\n": " "); + } + } + if( p->showHeader ){ + for(i=0; iactualWidth) ){ + w = p->actualWidth[i]; + }else{ + w = 10; + } + fprintf(p->out,"%-*.*s%s",w,w,"-----------------------------------" + "----------------------------------------------------------", + i==nArg-1 ? "\n": " "); + } + } + } + if( azArg==0 ) break; + for(i=0; iactualWidth) ){ + w = p->actualWidth[i]; + }else{ + w = 10; + } + fprintf(p->out,"%-*.*s%s",w,w, + azArg[i] ? azArg[i] : p->nullvalue, i==nArg-1 ? "\n": " "); + } + break; + } + case MODE_Semi: + case MODE_List: { + if( p->cnt++==0 && p->showHeader ){ + for(i=0; iout,"%s%s",azCol[i], i==nArg-1 ? "\n" : p->separator); + } + } + if( azArg==0 ) break; + for(i=0; inullvalue; + fprintf(p->out, "%s", z); + if( iout, "%s", p->separator); + }else if( p->mode==MODE_Semi ){ + fprintf(p->out, ";\n"); + }else{ + fprintf(p->out, "\n"); + } + } + break; + } + case MODE_Html: { + if( p->cnt++==0 && p->showHeader ){ + fprintf(p->out,""); + for(i=0; iout,"",azCol[i]); + } + fprintf(p->out,"\n"); + } + if( azArg==0 ) break; + fprintf(p->out,""); + for(i=0; iout,"\n"); + } + fprintf(p->out,"\n"); + break; + } + case MODE_Tcl: { + if( p->cnt++==0 && p->showHeader ){ + for(i=0; iout,azCol[i]); + fprintf(p->out, "%s", p->separator); + } + fprintf(p->out,"\n"); + } + if( azArg==0 ) break; + for(i=0; iout, azArg[i] ? azArg[i] : p->nullvalue); + fprintf(p->out, "%s", p->separator); + } + fprintf(p->out,"\n"); + break; + } + case MODE_Csv: { + if( p->cnt++==0 && p->showHeader ){ + for(i=0; iout,"\n"); + } + if( azArg==0 ) break; + for(i=0; iout,"\n"); + break; + } + case MODE_Insert: { + if( azArg==0 ) break; + fprintf(p->out,"INSERT INTO %s VALUES(",p->zDestTable); + for(i=0; i0 ? ",": ""; + if( azArg[i]==0 ){ + fprintf(p->out,"%sNULL",zSep); + }else if( isNumber(azArg[i], 0) ){ + fprintf(p->out,"%s%s",zSep, azArg[i]); + }else{ + if( zSep[0] ) fprintf(p->out,"%s",zSep); + output_quoted_string(p->out, azArg[i]); + } + } + fprintf(p->out,");\n"); + break; + } + } + return 0; +} + +/* +** Set the destination table field of the callback_data structure to +** the name of the table given. Escape any quote characters in the +** table name. +*/ +static void set_table_name(struct callback_data *p, const char *zName){ + int i, n; + int needQuote; + char *z; + + if( p->zDestTable ){ + free(p->zDestTable); + p->zDestTable = 0; + } + if( zName==0 ) return; + needQuote = !isalpha((unsigned char)*zName) && *zName!='_'; + for(i=n=0; zName[i]; i++, n++){ + if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ){ + needQuote = 1; + if( zName[i]=='\'' ) n++; + } + } + if( needQuote ) n += 2; + z = p->zDestTable = malloc( n+1 ); + if( z==0 ){ + fprintf(stderr,"Out of memory!\n"); + exit(1); + } + n = 0; + if( needQuote ) z[n++] = '\''; + for(i=0; zName[i]; i++){ + z[n++] = zName[i]; + if( zName[i]=='\'' ) z[n++] = '\''; + } + if( needQuote ) z[n++] = '\''; + z[n] = 0; +} + +/* zIn is either a pointer to a NULL-terminated string in memory obtained +** from malloc(), or a NULL pointer. The string pointed to by zAppend is +** added to zIn, and the result returned in memory obtained from malloc(). +** zIn, if it was not NULL, is freed. +** +** If the third argument, quote, is not '\0', then it is used as a +** quote character for zAppend. +*/ +static char * appendText(char *zIn, char const *zAppend, char quote){ + int len; + int i; + int nAppend = strlen(zAppend); + int nIn = (zIn?strlen(zIn):0); + + len = nAppend+nIn+1; + if( quote ){ + len += 2; + for(i=0; iout, "%s;\n", zSql); + + if( strcmp(zType, "table")==0 ){ + sqlite3_stmt *pTableInfo = 0; + char *zSelect = 0; + char *zTableInfo = 0; + char *zTmp = 0; + + zTableInfo = appendText(zTableInfo, "PRAGMA table_info(", 0); + zTableInfo = appendText(zTableInfo, zTable, '"'); + zTableInfo = appendText(zTableInfo, ");", 0); + + rc = sqlite3_prepare(p->db, zTableInfo, -1, &pTableInfo, 0); + if( zTableInfo ) free(zTableInfo); + if( rc!=SQLITE_OK || !pTableInfo ){ + return 1; + } + + zSelect = appendText(zSelect, "SELECT 'INSERT INTO ' || ", 0); + zTmp = appendText(zTmp, zTable, '"'); + if( zTmp ){ + zSelect = appendText(zSelect, zTmp, '\''); + } + zSelect = appendText(zSelect, " || ' VALUES(' || ", 0); + rc = sqlite3_step(pTableInfo); + while( rc==SQLITE_ROW ){ + zSelect = appendText(zSelect, "quote(", 0); + zSelect = appendText(zSelect, sqlite3_column_text(pTableInfo, 1), '"'); + rc = sqlite3_step(pTableInfo); + if( rc==SQLITE_ROW ){ + zSelect = appendText(zSelect, ") || ', ' || ", 0); + }else{ + zSelect = appendText(zSelect, ") ", 0); + } + } + rc = sqlite3_finalize(pTableInfo); + if( rc!=SQLITE_OK ){ + if( zSelect ) free(zSelect); + return 1; + } + zSelect = appendText(zSelect, "|| ')' FROM ", 0); + zSelect = appendText(zSelect, zTable, '"'); + + rc = run_table_dump_query(p->out, p->db, zSelect); + if( rc==SQLITE_CORRUPT ){ + zSelect = appendText(zSelect, " ORDER BY rowid DESC", 0); + rc = run_table_dump_query(p->out, p->db, zSelect); + } + if( zSelect ) free(zSelect); + if( rc!=SQLITE_OK ){ + return 1; + } + } + return 0; +} + +/* +** Run zQuery. Update dump_callback() as the callback routine. +** If we get a SQLITE_CORRUPT error, rerun the query after appending +** "ORDER BY rowid DESC" to the end. +*/ +static int run_schema_dump_query( + struct callback_data *p, + const char *zQuery, + char **pzErrMsg +){ + int rc; + rc = sqlite3_exec(p->db, zQuery, dump_callback, p, pzErrMsg); + if( rc==SQLITE_CORRUPT ){ + char *zQ2; + int len = strlen(zQuery); + if( pzErrMsg ) sqlite3_free(*pzErrMsg); + zQ2 = malloc( len+100 ); + if( zQ2==0 ) return rc; + sprintf(zQ2, "%s ORDER BY rowid DESC", zQuery); + rc = sqlite3_exec(p->db, zQ2, dump_callback, p, pzErrMsg); + free(zQ2); + } + return rc; +} + +/* +** Text of a help message +*/ +static char zHelp[] = + ".databases List names and files of attached databases\n" + ".dump ?TABLE? ... Dump the database in an SQL text format\n" + ".echo ON|OFF Turn command echo on or off\n" + ".exit Exit this program\n" + ".explain ON|OFF Turn output mode suitable for EXPLAIN on or off.\n" + ".header(s) ON|OFF Turn display of headers on or off\n" + ".help Show this message\n" + ".import FILE TABLE Import data from FILE into TABLE\n" + ".indices TABLE Show names of all indices on TABLE\n" + ".mode MODE ?TABLE? Set output mode where MODE is on of:\n" + " csv Comma-separated values\n" + " column Left-aligned columns. (See .width)\n" + " html HTML
    %s
    "); + output_html_string(p->out, azArg[i] ? azArg[i] : p->nullvalue); + fprintf(p->out,"
    code\n" + " insert SQL insert statements for TABLE\n" + " line One value per line\n" + " list Values delimited by .separator string\n" + " tabs Tab-separated values\n" + " tcl TCL list elements\n" + ".nullvalue STRING Print STRING in place of NULL values\n" + ".output FILENAME Send output to FILENAME\n" + ".output stdout Send output to the screen\n" + ".prompt MAIN CONTINUE Replace the standard prompts\n" + ".quit Exit this program\n" + ".read FILENAME Execute SQL in FILENAME\n" +#ifdef SQLITE_HAS_CODEC + ".rekey OLD NEW NEW Change the encryption key\n" +#endif + ".schema ?TABLE? Show the CREATE statements\n" + ".separator STRING Change separator used by output mode and .import\n" + ".show Show the current values for various settings\n" + ".tables ?PATTERN? List names of tables matching a LIKE pattern\n" + ".timeout MS Try opening locked tables for MS milliseconds\n" + ".width NUM NUM ... Set column widths for \"column\" mode\n" +; + +/* Forward reference */ +static void process_input(struct callback_data *p, FILE *in); + +/* +** Make sure the database is open. If it is not, then open it. If +** the database fails to open, print an error message and exit. +*/ +static void open_db(struct callback_data *p){ + if( p->db==0 ){ + sqlite3_open(p->zDbFilename, &p->db); + db = p->db; +#ifdef SQLITE_HAS_CODEC + sqlite3_key(p->db, p->zKey, p->zKey ? strlen(p->zKey) : 0); +#endif + sqlite3_create_function(db, "shellstatic", 0, SQLITE_UTF8, 0, + shellstaticFunc, 0, 0); + if( SQLITE_OK!=sqlite3_errcode(db) ){ + fprintf(stderr,"Unable to open database \"%s\": %s\n", + p->zDbFilename, sqlite3_errmsg(db)); + exit(1); + } + } +} + +/* +** Do C-language style dequoting. +** +** \t -> tab +** \n -> newline +** \r -> carriage return +** \NNN -> ascii character NNN in octal +** \\ -> backslash +*/ +static void resolve_backslashes(char *z){ + int i, j, c; + for(i=j=0; (c = z[i])!=0; i++, j++){ + if( c=='\\' ){ + c = z[++i]; + if( c=='n' ){ + c = '\n'; + }else if( c=='t' ){ + c = '\t'; + }else if( c=='r' ){ + c = '\r'; + }else if( c>='0' && c<='7' ){ + c =- '0'; + if( z[i+1]>='0' && z[i+1]<='7' ){ + i++; + c = (c<<3) + z[i] - '0'; + if( z[i+1]>='0' && z[i+1]<='7' ){ + i++; + c = (c<<3) + z[i] - '0'; + } + } + } + } + z[j] = c; + } + z[j] = 0; +} + +/* +** If an input line begins with "." then invoke this routine to +** process that line. +** +** Return 1 to exit and 0 to continue. +*/ +static int do_meta_command(char *zLine, struct callback_data *p){ + int i = 1; + int nArg = 0; + int n, c; + int rc = 0; + char *azArg[50]; + + /* Parse the input line into tokens. + */ + while( zLine[i] && nArg1 && strncmp(azArg[0], "databases", n)==0 ){ + struct callback_data data; + char *zErrMsg = 0; + open_db(p); + memcpy(&data, p, sizeof(data)); + data.showHeader = 1; + data.mode = MODE_Column; + data.colWidth[0] = 3; + data.colWidth[1] = 15; + data.colWidth[2] = 58; + sqlite3_exec(p->db, "PRAGMA database_list; ", callback, &data, &zErrMsg); + if( zErrMsg ){ + fprintf(stderr,"Error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + } + }else + + if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ + char *zErrMsg = 0; + open_db(p); + fprintf(p->out, "BEGIN TRANSACTION;\n"); + if( nArg==1 ){ + run_schema_dump_query(p, + "SELECT name, type, sql FROM sqlite_master " + "WHERE sql NOT NULL AND type=='table'", 0 + ); + run_schema_dump_query(p, + "SELECT name, type, sql FROM sqlite_master " + "WHERE sql NOT NULL AND type!='table' AND type!='meta'", 0 + ); + }else{ + int i; + for(i=1; iout, "COMMIT;\n"); + } + }else + + if( c=='e' && strncmp(azArg[0], "echo", n)==0 && nArg>1 ){ + int j; + char *z = azArg[1]; + int val = atoi(azArg[1]); + for(j=0; z[j]; j++){ + z[j] = tolower((unsigned char)z[j]); + } + if( strcmp(z,"on")==0 ){ + val = 1; + }else if( strcmp(z,"yes")==0 ){ + val = 1; + } + p->echoOn = val; + }else + + if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){ + rc = 1; + }else + + if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){ + int j; + static char zOne[] = "1"; + char *z = nArg>=2 ? azArg[1] : zOne; + int val = atoi(z); + for(j=0; z[j]; j++){ + z[j] = tolower((unsigned char)z[j]); + } + if( strcmp(z,"on")==0 ){ + val = 1; + }else if( strcmp(z,"yes")==0 ){ + val = 1; + } + if(val == 1) { + if(!p->explainPrev.valid) { + p->explainPrev.valid = 1; + p->explainPrev.mode = p->mode; + p->explainPrev.showHeader = p->showHeader; + memcpy(p->explainPrev.colWidth,p->colWidth,sizeof(p->colWidth)); + } + /* We could put this code under the !p->explainValid + ** condition so that it does not execute if we are already in + ** explain mode. However, always executing it allows us an easy + ** was to reset to explain mode in case the user previously + ** did an .explain followed by a .width, .mode or .header + ** command. + */ + p->mode = MODE_Column; + p->showHeader = 1; + memset(p->colWidth,0,ArraySize(p->colWidth)); + p->colWidth[0] = 4; + p->colWidth[1] = 12; + p->colWidth[2] = 10; + p->colWidth[3] = 10; + p->colWidth[4] = 35; + }else if (p->explainPrev.valid) { + p->explainPrev.valid = 0; + p->mode = p->explainPrev.mode; + p->showHeader = p->explainPrev.showHeader; + memcpy(p->colWidth,p->explainPrev.colWidth,sizeof(p->colWidth)); + } + }else + + if( c=='h' && (strncmp(azArg[0], "header", n)==0 + || + strncmp(azArg[0], "headers", n)==0 )&& nArg>1 ){ + int j; + char *z = azArg[1]; + int val = atoi(azArg[1]); + for(j=0; z[j]; j++){ + z[j] = tolower((unsigned char)z[j]); + } + if( strcmp(z,"on")==0 ){ + val = 1; + }else if( strcmp(z,"yes")==0 ){ + val = 1; + } + p->showHeader = val; + }else + + if( c=='h' && strncmp(azArg[0], "help", n)==0 ){ + fprintf(stderr,zHelp); + }else + + if( c=='i' && strncmp(azArg[0], "import", n)==0 && nArg>=3 ){ + char *zTable = azArg[2]; /* Insert data into this table */ + char *zFile = azArg[1]; /* The file from which to extract data */ + sqlite3_stmt *pStmt; /* A statement */ + int rc; /* Result code */ + int nCol; /* Number of columns in the table */ + int nByte; /* Number of bytes in an SQL string */ + int i, j; /* Loop counters */ + int nSep; /* Number of bytes in p->separator[] */ + char *zSql; /* An SQL statement */ + char *zLine; /* A single line of input from the file */ + char **azCol; /* zLine[] broken up into columns */ + char *zCommit; /* How to commit changes */ + FILE *in; /* The input file */ + int lineno = 0; /* Line number of input file */ + + nSep = strlen(p->separator); + if( nSep==0 ){ + fprintf(stderr, "non-null separator required for import\n"); + return 0; + } + zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable); + if( zSql==0 ) return 0; + nByte = strlen(zSql); + rc = sqlite3_prepare(p->db, zSql, 0, &pStmt, 0); + sqlite3_free(zSql); + if( rc ){ + fprintf(stderr,"Error: %s\n", sqlite3_errmsg(db)); + nCol = 0; + }else{ + nCol = sqlite3_column_count(pStmt); + } + sqlite3_finalize(pStmt); + if( nCol==0 ) return 0; + zSql = malloc( nByte + 20 + nCol*2 ); + if( zSql==0 ) return 0; + sqlite3_snprintf(nByte+20, zSql, "INSERT INTO '%q' VALUES(?", zTable); + j = strlen(zSql); + for(i=1; idb, zSql, 0, &pStmt, 0); + free(zSql); + if( rc ){ + fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db)); + sqlite3_finalize(pStmt); + return 0; + } + in = fopen(zFile, "rb"); + if( in==0 ){ + fprintf(stderr, "cannot open file: %s\n", zFile); + sqlite3_finalize(pStmt); + return 0; + } + azCol = malloc( sizeof(azCol[0])*(nCol+1) ); + if( azCol==0 ) return 0; + sqlite3_exec(p->db, "BEGIN", 0, 0, 0); + zCommit = "COMMIT"; + while( (zLine = local_getline(0, in))!=0 ){ + char *z; + i = 0; + lineno++; + azCol[0] = zLine; + for(i=0, z=zLine; *z && *z!='\n' && *z!='\r'; z++){ + if( *z==p->separator[0] && strncmp(z, p->separator, nSep)==0 ){ + *z = 0; + i++; + if( idb, zCommit, 0, 0, 0); + }else + + if( c=='i' && strncmp(azArg[0], "indices", n)==0 && nArg>1 ){ + struct callback_data data; + char *zErrMsg = 0; + open_db(p); + memcpy(&data, p, sizeof(data)); + data.showHeader = 0; + data.mode = MODE_List; + zShellStatic = azArg[1]; + sqlite3_exec(p->db, + "SELECT name FROM sqlite_master " + "WHERE type='index' AND tbl_name LIKE shellstatic() " + "UNION ALL " + "SELECT name FROM sqlite_temp_master " + "WHERE type='index' AND tbl_name LIKE shellstatic() " + "ORDER BY 1", + callback, &data, &zErrMsg + ); + zShellStatic = 0; + if( zErrMsg ){ + fprintf(stderr,"Error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + } + }else + + if( c=='m' && strncmp(azArg[0], "mode", n)==0 && nArg>=2 ){ + int n2 = strlen(azArg[1]); + if( strncmp(azArg[1],"line",n2)==0 + || + strncmp(azArg[1],"lines",n2)==0 ){ + p->mode = MODE_Line; + }else if( strncmp(azArg[1],"column",n2)==0 + || + strncmp(azArg[1],"columns",n2)==0 ){ + p->mode = MODE_Column; + }else if( strncmp(azArg[1],"list",n2)==0 ){ + p->mode = MODE_List; + }else if( strncmp(azArg[1],"html",n2)==0 ){ + p->mode = MODE_Html; + }else if( strncmp(azArg[1],"tcl",n2)==0 ){ + p->mode = MODE_Tcl; + }else if( strncmp(azArg[1],"csv",n2)==0 ){ + p->mode = MODE_Csv; + strcpy(p->separator, ","); + }else if( strncmp(azArg[1],"tabs",n2)==0 ){ + p->mode = MODE_List; + strcpy(p->separator, "\t"); + }else if( strncmp(azArg[1],"insert",n2)==0 ){ + p->mode = MODE_Insert; + if( nArg>=3 ){ + set_table_name(p, azArg[2]); + }else{ + set_table_name(p, "table"); + } + }else { + fprintf(stderr,"mode should be on of: " + "column csv html insert line list tabs tcl\n"); + } + }else + + if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 && nArg==2 ) { + sprintf(p->nullvalue, "%.*s", (int)ArraySize(p->nullvalue)-1, azArg[1]); + }else + + if( c=='o' && strncmp(azArg[0], "output", n)==0 && nArg==2 ){ + if( p->out!=stdout ){ + fclose(p->out); + } + if( strcmp(azArg[1],"stdout")==0 ){ + p->out = stdout; + strcpy(p->outfile,"stdout"); + }else{ + p->out = fopen(azArg[1], "wb"); + if( p->out==0 ){ + fprintf(stderr,"can't write to \"%s\"\n", azArg[1]); + p->out = stdout; + } else { + strcpy(p->outfile,azArg[1]); + } + } + }else + + if( c=='p' && strncmp(azArg[0], "prompt", n)==0 && (nArg==2 || nArg==3)){ + if( nArg >= 2) { + strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1); + } + if( nArg >= 3) { + strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1); + } + }else + + if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){ + rc = 1; + }else + + if( c=='r' && strncmp(azArg[0], "read", n)==0 && nArg==2 ){ + FILE *alt = fopen(azArg[1], "rb"); + if( alt==0 ){ + fprintf(stderr,"can't open \"%s\"\n", azArg[1]); + }else{ + process_input(p, alt); + fclose(alt); + } + }else + +#ifdef SQLITE_HAS_CODEC + if( c=='r' && strncmp(azArg[0],"rekey", n)==0 && nArg==4 ){ + char *zOld = p->zKey; + if( zOld==0 ) zOld = ""; + if( strcmp(azArg[1],zOld) ){ + fprintf(stderr,"old key is incorrect\n"); + }else if( strcmp(azArg[2], azArg[3]) ){ + fprintf(stderr,"2nd copy of new key does not match the 1st\n"); + }else{ + sqlite3_free(p->zKey); + p->zKey = sqlite3_mprintf("%s", azArg[2]); + sqlite3_rekey(p->db, p->zKey, strlen(p->zKey)); + } + }else +#endif + + if( c=='s' && strncmp(azArg[0], "schema", n)==0 ){ + struct callback_data data; + char *zErrMsg = 0; + open_db(p); + memcpy(&data, p, sizeof(data)); + data.showHeader = 0; + data.mode = MODE_Semi; + if( nArg>1 ){ + int i; + for(i=0; azArg[1][i]; i++) azArg[1][i] = tolower(azArg[1][i]); + if( strcmp(azArg[1],"sqlite_master")==0 ){ + char *new_argv[2], *new_colv[2]; + new_argv[0] = "CREATE TABLE sqlite_master (\n" + " type text,\n" + " name text,\n" + " tbl_name text,\n" + " rootpage integer,\n" + " sql text\n" + ")"; + new_argv[1] = 0; + new_colv[0] = "sql"; + new_colv[1] = 0; + callback(&data, 1, new_argv, new_colv); + }else if( strcmp(azArg[1],"sqlite_temp_master")==0 ){ + char *new_argv[2], *new_colv[2]; + new_argv[0] = "CREATE TEMP TABLE sqlite_temp_master (\n" + " type text,\n" + " name text,\n" + " tbl_name text,\n" + " rootpage integer,\n" + " sql text\n" + ")"; + new_argv[1] = 0; + new_colv[0] = "sql"; + new_colv[1] = 0; + callback(&data, 1, new_argv, new_colv); + }else{ + zShellStatic = azArg[1]; + sqlite3_exec(p->db, + "SELECT sql FROM " + " (SELECT * FROM sqlite_master UNION ALL" + " SELECT * FROM sqlite_temp_master) " + "WHERE tbl_name LIKE shellstatic() AND type!='meta' AND sql NOTNULL " + "ORDER BY substr(type,2,1), name", + callback, &data, &zErrMsg); + zShellStatic = 0; + } + }else{ + sqlite3_exec(p->db, + "SELECT sql FROM " + " (SELECT * FROM sqlite_master UNION ALL" + " SELECT * FROM sqlite_temp_master) " + "WHERE type!='meta' AND sql NOTNULL " + "ORDER BY substr(type,2,1), name", + callback, &data, &zErrMsg + ); + } + if( zErrMsg ){ + fprintf(stderr,"Error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + } + }else + + if( c=='s' && strncmp(azArg[0], "separator", n)==0 && nArg==2 ){ + sprintf(p->separator, "%.*s", (int)ArraySize(p->separator)-1, azArg[1]); + }else + + if( c=='s' && strncmp(azArg[0], "show", n)==0){ + int i; + fprintf(p->out,"%9.9s: %s\n","echo", p->echoOn ? "on" : "off"); + fprintf(p->out,"%9.9s: %s\n","explain", p->explainPrev.valid ? "on" :"off"); + fprintf(p->out,"%9.9s: %s\n","headers", p->showHeader ? "on" : "off"); + fprintf(p->out,"%9.9s: %s\n","mode", modeDescr[p->mode]); + fprintf(p->out,"%9.9s: ", "nullvalue"); + output_c_string(p->out, p->nullvalue); + fprintf(p->out, "\n"); + fprintf(p->out,"%9.9s: %s\n","output", + strlen(p->outfile) ? p->outfile : "stdout"); + fprintf(p->out,"%9.9s: ", "separator"); + output_c_string(p->out, p->separator); + fprintf(p->out, "\n"); + fprintf(p->out,"%9.9s: ","width"); + for (i=0;i<(int)ArraySize(p->colWidth) && p->colWidth[i] != 0;i++) { + fprintf(p->out,"%d ",p->colWidth[i]); + } + fprintf(p->out,"\n"); + }else + + if( c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0 ){ + char **azResult; + int nRow, rc; + char *zErrMsg; + open_db(p); + if( nArg==1 ){ + rc = sqlite3_get_table(p->db, + "SELECT name FROM sqlite_master " + "WHERE type IN ('table','view') " + "UNION ALL " + "SELECT name FROM sqlite_temp_master " + "WHERE type IN ('table','view') " + "ORDER BY 1", + &azResult, &nRow, 0, &zErrMsg + ); + }else{ + zShellStatic = azArg[1]; + rc = sqlite3_get_table(p->db, + "SELECT name FROM sqlite_master " + "WHERE type IN ('table','view') AND name LIKE '%'||shellstatic()||'%' " + "UNION ALL " + "SELECT name FROM sqlite_temp_master " + "WHERE type IN ('table','view') AND name LIKE '%'||shellstatic()||'%' " + "ORDER BY 1", + &azResult, &nRow, 0, &zErrMsg + ); + zShellStatic = 0; + } + if( zErrMsg ){ + fprintf(stderr,"Error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + } + if( rc==SQLITE_OK ){ + int len, maxlen = 0; + int i, j; + int nPrintCol, nPrintRow; + for(i=1; i<=nRow; i++){ + if( azResult[i]==0 ) continue; + len = strlen(azResult[i]); + if( len>maxlen ) maxlen = len; + } + nPrintCol = 80/(maxlen+2); + if( nPrintCol<1 ) nPrintCol = 1; + nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; + for(i=0; i1 && strncmp(azArg[0], "timeout", n)==0 && nArg>=2 ){ + open_db(p); + sqlite3_busy_timeout(p->db, atoi(azArg[1])); + }else + + if( c=='w' && strncmp(azArg[0], "width", n)==0 ){ + int j; + for(j=1; jcolWidth); j++){ + p->colWidth[j-1] = atoi(azArg[j]); + } + }else + + { + fprintf(stderr, "unknown command or invalid arguments: " + " \"%s\". Enter \".help\" for help\n", azArg[0]); + } + + return rc; +} + +/* +** Return TRUE if the last non-whitespace character in z[] is a semicolon. +** z[] is N characters long. +*/ +static int _ends_with_semicolon(const char *z, int N){ + while( N>0 && isspace((unsigned char)z[N-1]) ){ N--; } + return N>0 && z[N-1]==';'; +} + +/* +** Test to see if a line consists entirely of whitespace. +*/ +static int _all_whitespace(const char *z){ + for(; *z; z++){ + if( isspace(*(unsigned char*)z) ) continue; + if( *z=='/' && z[1]=='*' ){ + z += 2; + while( *z && (*z!='*' || z[1]!='/') ){ z++; } + if( *z==0 ) return 0; + z++; + continue; + } + if( *z=='-' && z[1]=='-' ){ + z += 2; + while( *z && *z!='\n' ){ z++; } + if( *z==0 ) return 1; + continue; + } + return 0; + } + return 1; +} + +/* +** Return TRUE if the line typed in is an SQL command terminator other +** than a semi-colon. The SQL Server style "go" command is understood +** as is the Oracle "/". +*/ +static int _is_command_terminator(const char *zLine){ + while( isspace(*(unsigned char*)zLine) ){ zLine++; }; + if( zLine[0]=='/' && _all_whitespace(&zLine[1]) ) return 1; /* Oracle */ + if( tolower(zLine[0])=='g' && tolower(zLine[1])=='o' + && _all_whitespace(&zLine[2]) ){ + return 1; /* SQL Server */ + } + return 0; +} + +/* +** Read input from *in and process it. If *in==0 then input +** is interactive - the user is typing it it. Otherwise, input +** is coming from a file or device. A prompt is issued and history +** is saved only if input is interactive. An interrupt signal will +** cause this routine to exit immediately, unless input is interactive. +*/ +static void process_input(struct callback_data *p, FILE *in){ + char *zLine; + char *zSql = 0; + int nSql = 0; + char *zErrMsg; + int rc; + while( fflush(p->out), (zLine = one_input_line(zSql, in))!=0 ){ + if( seenInterrupt ){ + if( in!=0 ) break; + seenInterrupt = 0; + } + if( p->echoOn ) printf("%s\n", zLine); + if( (zSql==0 || zSql[0]==0) && _all_whitespace(zLine) ) continue; + if( zLine && zLine[0]=='.' && nSql==0 ){ + int rc = do_meta_command(zLine, p); + free(zLine); + if( rc ) break; + continue; + } + if( _is_command_terminator(zLine) ){ + strcpy(zLine,";"); + } + if( zSql==0 ){ + int i; + for(i=0; zLine[i] && isspace((unsigned char)zLine[i]); i++){} + if( zLine[i]!=0 ){ + nSql = strlen(zLine); + zSql = malloc( nSql+1 ); + strcpy(zSql, zLine); + } + }else{ + int len = strlen(zLine); + zSql = realloc( zSql, nSql + len + 2 ); + if( zSql==0 ){ + fprintf(stderr,"%s: out of memory!\n", Argv0); + exit(1); + } + strcpy(&zSql[nSql++], "\n"); + strcpy(&zSql[nSql], zLine); + nSql += len; + } + free(zLine); + if( zSql && _ends_with_semicolon(zSql, nSql) && sqlite3_complete(zSql) ){ + p->cnt = 0; + open_db(p); + rc = sqlite3_exec(p->db, zSql, callback, p, &zErrMsg); + if( rc || zErrMsg ){ + if( in!=0 && !p->echoOn ) printf("%s\n",zSql); + if( zErrMsg!=0 ){ + printf("SQL error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + zErrMsg = 0; + }else{ + printf("SQL error: %s\n", sqlite3_errmsg(p->db)); + } + } + free(zSql); + zSql = 0; + nSql = 0; + } + } + if( zSql ){ + if( !_all_whitespace(zSql) ) printf("Incomplete SQL: %s\n", zSql); + free(zSql); + } +} + +/* +** Return a pathname which is the user's home directory. A +** 0 return indicates an error of some kind. Space to hold the +** resulting string is obtained from malloc(). The calling +** function should free the result. +*/ +static char *find_home_dir(void){ + char *home_dir = NULL; + +#if !defined(_WIN32) && !defined(WIN32) && !defined(__MACOS__) + struct passwd *pwent; + uid_t uid = getuid(); + if( (pwent=getpwuid(uid)) != NULL) { + home_dir = pwent->pw_dir; + } +#endif + +#ifdef __MACOS__ + char home_path[_MAX_PATH+1]; + home_dir = getcwd(home_path, _MAX_PATH); +#endif + + if (!home_dir) { + home_dir = getenv("HOME"); + if (!home_dir) { + home_dir = getenv("HOMEPATH"); /* Windows? */ + } + } + +#if defined(_WIN32) || defined(WIN32) + if (!home_dir) { + home_dir = "c:"; + } +#endif + + if( home_dir ){ + char *z = malloc( strlen(home_dir)+1 ); + if( z ) strcpy(z, home_dir); + home_dir = z; + } + + return home_dir; +} + +/* +** Read input from the file given by sqliterc_override. Or if that +** parameter is NULL, take input from ~/.sqliterc +*/ +static void process_sqliterc( + struct callback_data *p, /* Configuration data */ + const char *sqliterc_override /* Name of config file. NULL to use default */ +){ + char *home_dir = NULL; + const char *sqliterc = sqliterc_override; + char *zBuf; + FILE *in = NULL; + + if (sqliterc == NULL) { + home_dir = find_home_dir(); + if( home_dir==0 ){ + fprintf(stderr,"%s: cannot locate your home directory!\n", Argv0); + return; + } + zBuf = malloc(strlen(home_dir) + 15); + if( zBuf==0 ){ + fprintf(stderr,"%s: out of memory!\n", Argv0); + exit(1); + } + sprintf(zBuf,"%s/.sqliterc",home_dir); + free(home_dir); + sqliterc = (const char*)zBuf; + } + in = fopen(sqliterc,"rb"); + if( in ){ + if( isatty(fileno(stdout)) ){ + printf("Loading resources from %s\n",sqliterc); + } + process_input(p,in); + fclose(in); + } + return; +} + +/* +** Show available command line options +*/ +static const char zOptions[] = + " -init filename read/process named file\n" + " -echo print commands before execution\n" + " -[no]header turn headers on or off\n" + " -column set output mode to 'column'\n" + " -html set output mode to HTML\n" +#ifdef SQLITE_HAS_CODEC + " -key KEY encryption key\n" +#endif + " -line set output mode to 'line'\n" + " -list set output mode to 'list'\n" + " -separator 'x' set output field separator (|)\n" + " -nullvalue 'text' set text string for NULL values\n" + " -version show SQLite version\n" + " -help show this text, also show dot-commands\n" +; +static void usage(int showDetail){ + fprintf(stderr, "Usage: %s [OPTIONS] FILENAME [SQL]\n", Argv0); + if( showDetail ){ + fprintf(stderr, "Options are:\n%s", zOptions); + }else{ + fprintf(stderr, "Use the -help option for additional information\n"); + } + exit(1); +} + +/* +** Initialize the state information in data +*/ +void main_init(struct callback_data *data) { + memset(data, 0, sizeof(*data)); + data->mode = MODE_List; + strcpy(data->separator,"|"); + data->showHeader = 0; + strcpy(mainPrompt,"sqlite> "); + strcpy(continuePrompt," ...> "); +} + +int main(int argc, char **argv){ + char *zErrMsg = 0; + struct callback_data data; + const char *zInitFile = 0; + char *zFirstCmd = 0; + int i; + +#ifdef __MACOS__ + argc = ccommand(&argv); +#endif + + Argv0 = argv[0]; + main_init(&data); + + /* Make sure we have a valid signal handler early, before anything + ** else is done. + */ +#ifdef SIGINT + signal(SIGINT, interrupt_handler); +#endif + + /* Do an initial pass through the command-line argument to locate + ** the name of the database file, the name of the initialization file, + ** and the first command to execute. + */ + for(i=1; i /* Needed for the definition of va_list */ + +/* +** Make sure we can call this stuff from C++. +*/ +#ifdef __cplusplus +extern "C" { +#endif + +/* +** The version of the SQLite library. +*/ +#ifdef SQLITE_VERSION +# undef SQLITE_VERSION +#else +# define SQLITE_VERSION "3.0.8" +#endif + +/* +** The version string is also compiled into the library so that a program +** can check to make sure that the lib*.a file and the *.h file are from +** the same version. The sqlite3_libversion() function returns a pointer +** to the sqlite3_version variable - useful in DLLs which cannot access +** global variables. +*/ +extern const char sqlite3_version[]; +const char *sqlite3_libversion(void); + +/* +** Each open sqlite database is represented by an instance of the +** following opaque structure. +*/ +typedef struct sqlite3 sqlite3; + + +/* +** Some compilers do not support the "long long" datatype. So we have +** to do a typedef that for 64-bit integers that depends on what compiler +** is being used. +*/ +#if defined(_MSC_VER) || defined(__BORLANDC__) + typedef __int64 sqlite_int64; + typedef unsigned __int64 sqlite_uint64; +#else + typedef long long int sqlite_int64; + typedef unsigned long long int sqlite_uint64; +#endif + + +/* +** A function to close the database. +** +** Call this function with a pointer to a structure that was previously +** returned from sqlite3_open() and the corresponding database will by closed. +** +** All SQL statements prepared using sqlite3_prepare() or +** sqlite3_prepare16() must be deallocated using sqlite3_finalize() before +** this routine is called. Otherwise, SQLITE_BUSY is returned and the +** database connection remains open. +*/ +int sqlite3_close(sqlite3 *); + +/* +** The type for a callback function. +*/ +typedef int (*sqlite3_callback)(void*,int,char**, char**); + +/* +** A function to executes one or more statements of SQL. +** +** If one or more of the SQL statements are queries, then +** the callback function specified by the 3rd parameter is +** invoked once for each row of the query result. This callback +** should normally return 0. If the callback returns a non-zero +** value then the query is aborted, all subsequent SQL statements +** are skipped and the sqlite3_exec() function returns the SQLITE_ABORT. +** +** The 4th parameter is an arbitrary pointer that is passed +** to the callback function as its first parameter. +** +** The 2nd parameter to the callback function is the number of +** columns in the query result. The 3rd parameter to the callback +** is an array of strings holding the values for each column. +** The 4th parameter to the callback is an array of strings holding +** the names of each column. +** +** The callback function may be NULL, even for queries. A NULL +** callback is not an error. It just means that no callback +** will be invoked. +** +** If an error occurs while parsing or evaluating the SQL (but +** not while executing the callback) then an appropriate error +** message is written into memory obtained from malloc() and +** *errmsg is made to point to that message. The calling function +** is responsible for freeing the memory that holds the error +** message. Use sqlite3_free() for this. If errmsg==NULL, +** then no error message is ever written. +** +** The return value is is SQLITE_OK if there are no errors and +** some other return code if there is an error. The particular +** return value depends on the type of error. +** +** If the query could not be executed because a database file is +** locked or busy, then this function returns SQLITE_BUSY. (This +** behavior can be modified somewhat using the sqlite3_busy_handler() +** and sqlite3_busy_timeout() functions below.) +*/ +int sqlite3_exec( + sqlite3*, /* An open database */ + const char *sql, /* SQL to be executed */ + sqlite3_callback, /* Callback function */ + void *, /* 1st argument to callback function */ + char **errmsg /* Error msg written here */ +); + +/* +** Return values for sqlite3_exec() and sqlite3_step() +*/ +#define SQLITE_OK 0 /* Successful result */ +#define SQLITE_ERROR 1 /* SQL error or missing database */ +#define SQLITE_INTERNAL 2 /* An internal logic error in SQLite */ +#define SQLITE_PERM 3 /* Access permission denied */ +#define SQLITE_ABORT 4 /* Callback routine requested an abort */ +#define SQLITE_BUSY 5 /* The database file is locked */ +#define SQLITE_LOCKED 6 /* A table in the database is locked */ +#define SQLITE_NOMEM 7 /* A malloc() failed */ +#define SQLITE_READONLY 8 /* Attempt to write a readonly database */ +#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/ +#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */ +#define SQLITE_CORRUPT 11 /* The database disk image is malformed */ +#define SQLITE_NOTFOUND 12 /* (Internal Only) Table or record not found */ +#define SQLITE_FULL 13 /* Insertion failed because database is full */ +#define SQLITE_CANTOPEN 14 /* Unable to open the database file */ +#define SQLITE_PROTOCOL 15 /* Database lock protocol error */ +#define SQLITE_EMPTY 16 /* Database is empty */ +#define SQLITE_SCHEMA 17 /* The database schema changed */ +#define SQLITE_TOOBIG 18 /* Too much data for one row of a table */ +#define SQLITE_CONSTRAINT 19 /* Abort due to contraint violation */ +#define SQLITE_MISMATCH 20 /* Data type mismatch */ +#define SQLITE_MISUSE 21 /* Library used incorrectly */ +#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ +#define SQLITE_AUTH 23 /* Authorization denied */ +#define SQLITE_FORMAT 24 /* Auxiliary database format error */ +#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ +#define SQLITE_NOTADB 26 /* File opened that is not a database file */ +#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ +#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ + +/* +** Each entry in an SQLite table has a unique integer key. (The key is +** the value of the INTEGER PRIMARY KEY column if there is such a column, +** otherwise the key is generated at random. The unique key is always +** available as the ROWID, OID, or _ROWID_ column.) The following routine +** returns the integer key of the most recent insert in the database. +** +** This function is similar to the mysql_insert_id() function from MySQL. +*/ +sqlite_int64 sqlite3_last_insert_rowid(sqlite3*); + +/* +** This function returns the number of database rows that were changed +** (or inserted or deleted) by the most recent called sqlite3_exec(). +** +** All changes are counted, even if they were later undone by a +** ROLLBACK or ABORT. Except, changes associated with creating and +** dropping tables are not counted. +** +** If a callback invokes sqlite3_exec() recursively, then the changes +** in the inner, recursive call are counted together with the changes +** in the outer call. +** +** SQLite implements the command "DELETE FROM table" without a WHERE clause +** by dropping and recreating the table. (This is much faster than going +** through and deleting individual elements form the table.) Because of +** this optimization, the change count for "DELETE FROM table" will be +** zero regardless of the number of elements that were originally in the +** table. To get an accurate count of the number of rows deleted, use +** "DELETE FROM table WHERE 1" instead. +*/ +int sqlite3_changes(sqlite3*); + +/* +** This function returns the number of database rows that have been +** modified by INSERT, UPDATE or DELETE statements since the database handle +** was opened. This includes UPDATE, INSERT and DELETE statements executed +** as part of trigger programs. All changes are counted as soon as the +** statement that makes them is completed (when the statement handle is +** passed to sqlite3_reset() or sqlite_finalise()). +** +** SQLite implements the command "DELETE FROM table" without a WHERE clause +** by dropping and recreating the table. (This is much faster than going +** through and deleting individual elements form the table.) Because of +** this optimization, the change count for "DELETE FROM table" will be +** zero regardless of the number of elements that were originally in the +** table. To get an accurate count of the number of rows deleted, use +** "DELETE FROM table WHERE 1" instead. +*/ +int sqlite3_total_changes(sqlite3*); + +/* This function causes any pending database operation to abort and +** return at its earliest opportunity. This routine is typically +** called in response to a user action such as pressing "Cancel" +** or Ctrl-C where the user wants a long query operation to halt +** immediately. +*/ +void sqlite3_interrupt(sqlite3*); + + +/* These functions return true if the given input string comprises +** one or more complete SQL statements. For the sqlite3_complete() call, +** the parameter must be a nul-terminated UTF-8 string. For +** sqlite3_complete16(), a nul-terminated machine byte order UTF-16 string +** is required. +** +** The algorithm is simple. If the last token other than spaces +** and comments is a semicolon, then return true. otherwise return +** false. +*/ +int sqlite3_complete(const char *sql); +int sqlite3_complete16(const void *sql); + +/* +** This routine identifies a callback function that is invoked +** whenever an attempt is made to open a database table that is +** currently locked by another process or thread. If the busy callback +** is NULL, then sqlite3_exec() returns SQLITE_BUSY immediately if +** it finds a locked table. If the busy callback is not NULL, then +** sqlite3_exec() invokes the callback with three arguments. The +** second argument is the name of the locked table and the third +** argument is the number of times the table has been busy. If the +** busy callback returns 0, then sqlite3_exec() immediately returns +** SQLITE_BUSY. If the callback returns non-zero, then sqlite3_exec() +** tries to open the table again and the cycle repeats. +** +** The default busy callback is NULL. +** +** Sqlite is re-entrant, so the busy handler may start a new query. +** (It is not clear why anyone would every want to do this, but it +** is allowed, in theory.) But the busy handler may not close the +** database. Closing the database from a busy handler will delete +** data structures out from under the executing query and will +** probably result in a coredump. +*/ +int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*); + +/* +** This routine sets a busy handler that sleeps for a while when a +** table is locked. The handler will sleep multiple times until +** at least "ms" milleseconds of sleeping have been done. After +** "ms" milleseconds of sleeping, the handler returns 0 which +** causes sqlite3_exec() to return SQLITE_BUSY. +** +** Calling this routine with an argument less than or equal to zero +** turns off all busy handlers. +*/ +int sqlite3_busy_timeout(sqlite3*, int ms); + +/* +** This next routine is really just a wrapper around sqlite3_exec(). +** Instead of invoking a user-supplied callback for each row of the +** result, this routine remembers each row of the result in memory +** obtained from malloc(), then returns all of the result after the +** query has finished. +** +** As an example, suppose the query result where this table: +** +** Name | Age +** ----------------------- +** Alice | 43 +** Bob | 28 +** Cindy | 21 +** +** If the 3rd argument were &azResult then after the function returns +** azResult will contain the following data: +** +** azResult[0] = "Name"; +** azResult[1] = "Age"; +** azResult[2] = "Alice"; +** azResult[3] = "43"; +** azResult[4] = "Bob"; +** azResult[5] = "28"; +** azResult[6] = "Cindy"; +** azResult[7] = "21"; +** +** Notice that there is an extra row of data containing the column +** headers. But the *nrow return value is still 3. *ncolumn is +** set to 2. In general, the number of values inserted into azResult +** will be ((*nrow) + 1)*(*ncolumn). +** +** After the calling function has finished using the result, it should +** pass the result data pointer to sqlite3_free_table() in order to +** release the memory that was malloc-ed. Because of the way the +** malloc() happens, the calling function must not try to call +** malloc() directly. Only sqlite3_free_table() is able to release +** the memory properly and safely. +** +** The return value of this routine is the same as from sqlite3_exec(). +*/ +int sqlite3_get_table( + sqlite3*, /* An open database */ + const char *sql, /* SQL to be executed */ + char ***resultp, /* Result written to a char *[] that this points to */ + int *nrow, /* Number of result rows written here */ + int *ncolumn, /* Number of result columns written here */ + char **errmsg /* Error msg written here */ +); + +/* +** Call this routine to free the memory that sqlite3_get_table() allocated. +*/ +void sqlite3_free_table(char **result); + +/* +** The following routines are variants of the "sprintf()" from the +** standard C library. The resulting string is written into memory +** obtained from malloc() so that there is never a possiblity of buffer +** overflow. These routines also implement some additional formatting +** options that are useful for constructing SQL statements. +** +** The strings returned by these routines should be freed by calling +** sqlite3_free(). +** +** All of the usual printf formatting options apply. In addition, there +** is a "%q" option. %q works like %s in that it substitutes a null-terminated +** string from the argument list. But %q also doubles every '\'' character. +** %q is designed for use inside a string literal. By doubling each '\'' +** character it escapes that character and allows it to be inserted into +** the string. +** +** For example, so some string variable contains text as follows: +** +** char *zText = "It's a happy day!"; +** +** We can use this text in an SQL statement as follows: +** +** sqlite3_exec_printf(db, "INSERT INTO table VALUES('%q')", +** callback1, 0, 0, zText); +** +** Because the %q format string is used, the '\'' character in zText +** is escaped and the SQL generated is as follows: +** +** INSERT INTO table1 VALUES('It''s a happy day!') +** +** This is correct. Had we used %s instead of %q, the generated SQL +** would have looked like this: +** +** INSERT INTO table1 VALUES('It's a happy day!'); +** +** This second example is an SQL syntax error. As a general rule you +** should always use %q instead of %s when inserting text into a string +** literal. +*/ +char *sqlite3_mprintf(const char*,...); +char *sqlite3_vmprintf(const char*, va_list); +void sqlite3_free(char *z); +char *sqlite3_snprintf(int,char*,const char*, ...); + +#ifndef SQLITE_OMIT_AUTHORIZATION +/* +** This routine registers a callback with the SQLite library. The +** callback is invoked (at compile-time, not at run-time) for each +** attempt to access a column of a table in the database. The callback +** returns SQLITE_OK if access is allowed, SQLITE_DENY if the entire +** SQL statement should be aborted with an error and SQLITE_IGNORE +** if the column should be treated as a NULL value. +*/ +int sqlite3_set_authorizer( + sqlite3*, + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), + void *pUserData +); +#endif + +/* +** The second parameter to the access authorization function above will +** be one of the values below. These values signify what kind of operation +** is to be authorized. The 3rd and 4th parameters to the authorization +** function will be parameters or NULL depending on which of the following +** codes is used as the second parameter. The 5th parameter is the name +** of the database ("main", "temp", etc.) if applicable. The 6th parameter +** is the name of the inner-most trigger or view that is responsible for +** the access attempt or NULL if this access attempt is directly from +** input SQL code. +** +** Arg-3 Arg-4 +*/ +#define SQLITE_COPY 0 /* Table Name File Name */ +#define SQLITE_CREATE_INDEX 1 /* Index Name Table Name */ +#define SQLITE_CREATE_TABLE 2 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_INDEX 3 /* Index Name Table Name */ +#define SQLITE_CREATE_TEMP_TABLE 4 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_TRIGGER 5 /* Trigger Name Table Name */ +#define SQLITE_CREATE_TEMP_VIEW 6 /* View Name NULL */ +#define SQLITE_CREATE_TRIGGER 7 /* Trigger Name Table Name */ +#define SQLITE_CREATE_VIEW 8 /* View Name NULL */ +#define SQLITE_DELETE 9 /* Table Name NULL */ +#define SQLITE_DROP_INDEX 10 /* Index Name Table Name */ +#define SQLITE_DROP_TABLE 11 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_INDEX 12 /* Index Name Table Name */ +#define SQLITE_DROP_TEMP_TABLE 13 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_TRIGGER 14 /* Trigger Name Table Name */ +#define SQLITE_DROP_TEMP_VIEW 15 /* View Name NULL */ +#define SQLITE_DROP_TRIGGER 16 /* Trigger Name Table Name */ +#define SQLITE_DROP_VIEW 17 /* View Name NULL */ +#define SQLITE_INSERT 18 /* Table Name NULL */ +#define SQLITE_PRAGMA 19 /* Pragma Name 1st arg or NULL */ +#define SQLITE_READ 20 /* Table Name Column Name */ +#define SQLITE_SELECT 21 /* NULL NULL */ +#define SQLITE_TRANSACTION 22 /* NULL NULL */ +#define SQLITE_UPDATE 23 /* Table Name Column Name */ +#define SQLITE_ATTACH 24 /* Filename NULL */ +#define SQLITE_DETACH 25 /* Database Name NULL */ + + +/* +** The return value of the authorization function should be one of the +** following constants: +*/ +/* #define SQLITE_OK 0 // Allow access (This is actually defined above) */ +#define SQLITE_DENY 1 /* Abort the SQL statement with an error */ +#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */ + +/* +** Register a function that is called at every invocation of sqlite3_exec() +** or sqlite3_prepare(). This function can be used (for example) to generate +** a log file of all SQL executed against a database. +*/ +void *sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*); + +/* +** This routine configures a callback function - the progress callback - that +** is invoked periodically during long running calls to sqlite3_exec(), +** sqlite3_step() and sqlite3_get_table(). An example use for this API is to keep +** a GUI updated during a large query. +** +** The progress callback is invoked once for every N virtual machine opcodes, +** where N is the second argument to this function. The progress callback +** itself is identified by the third argument to this function. The fourth +** argument to this function is a void pointer passed to the progress callback +** function each time it is invoked. +** +** If a call to sqlite3_exec(), sqlite3_step() or sqlite3_get_table() results +** in less than N opcodes being executed, then the progress callback is not +** invoked. +** +** To remove the progress callback altogether, pass NULL as the third +** argument to this function. +** +** If the progress callback returns a result other than 0, then the current +** query is immediately terminated and any database changes rolled back. If the +** query was part of a larger transaction, then the transaction is not rolled +** back and remains active. The sqlite3_exec() call returns SQLITE_ABORT. +** +******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ****** +*/ +void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); + +/* +** Register a callback function to be invoked whenever a new transaction +** is committed. The pArg argument is passed through to the callback. +** callback. If the callback function returns non-zero, then the commit +** is converted into a rollback. +** +** If another function was previously registered, its pArg value is returned. +** Otherwise NULL is returned. +** +** Registering a NULL function disables the callback. +** +******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ****** +*/ +void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); + +/* +** Open the sqlite database file "filename". The "filename" is UTF-8 +** encoded for sqlite3_open() and UTF-16 encoded in the native byte order +** for sqlite3_open16(). An sqlite3* handle is returned in *ppDb, even +** if an error occurs. If the database is opened (or created) successfully, +** then SQLITE_OK is returned. Otherwise an error code is returned. The +** sqlite3_errmsg() or sqlite3_errmsg16() routines can be used to obtain +** an English language description of the error. +** +** If the database file does not exist, then a new database is created. +** The encoding for the database is UTF-8 if sqlite3_open() is called and +** UTF-16 if sqlite3_open16 is used. +** +** Whether or not an error occurs when it is opened, resources associated +** with the sqlite3* handle should be released by passing it to +** sqlite3_close() when it is no longer required. +*/ +int sqlite3_open( + const char *filename, /* Database filename (UTF-8) */ + sqlite3 **ppDb /* OUT: SQLite db handle */ +); +int sqlite3_open16( + const void *filename, /* Database filename (UTF-16) */ + sqlite3 **ppDb /* OUT: SQLite db handle */ +); + +/* +** Return the error code for the most recent sqlite3_* API call associated +** with sqlite3 handle 'db'. SQLITE_OK is returned if the most recent +** API call was successful. +** +** Calls to many sqlite3_* functions set the error code and string returned +** by sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16() +** (overwriting the previous values). Note that calls to sqlite3_errcode(), +** sqlite3_errmsg() and sqlite3_errmsg16() themselves do not affect the +** results of future invocations. +** +** Assuming no other intervening sqlite3_* API calls are made, the error +** code returned by this function is associated with the same error as +** the strings returned by sqlite3_errmsg() and sqlite3_errmsg16(). +*/ +int sqlite3_errcode(sqlite3 *db); + +/* +** Return a pointer to a UTF-8 encoded string describing in english the +** error condition for the most recent sqlite3_* API call. The returned +** string is always terminated by an 0x00 byte. +** +** The string "not an error" is returned when the most recent API call was +** successful. +*/ +const char *sqlite3_errmsg(sqlite3*); + +/* +** Return a pointer to a UTF-16 native byte order encoded string describing +** in english the error condition for the most recent sqlite3_* API call. +** The returned string is always terminated by a pair of 0x00 bytes. +** +** The string "not an error" is returned when the most recent API call was +** successful. +*/ +const void *sqlite3_errmsg16(sqlite3*); + +/* +** An instance of the following opaque structure is used to represent +** a compiled SQL statment. +*/ +typedef struct sqlite3_stmt sqlite3_stmt; + +/* +** To execute an SQL query, it must first be compiled into a byte-code +** program using one of the following routines. The only difference between +** them is that the second argument, specifying the SQL statement to +** compile, is assumed to be encoded in UTF-8 for the sqlite3_prepare() +** function and UTF-16 for sqlite3_prepare16(). +** +** The first parameter "db" is an SQLite database handle. The second +** parameter "zSql" is the statement to be compiled, encoded as either +** UTF-8 or UTF-16 (see above). If the next parameter, "nBytes", is less +** than zero, then zSql is read up to the first nul terminator. If +** "nBytes" is not less than zero, then it is the length of the string zSql +** in bytes (not characters). +** +** *pzTail is made to point to the first byte past the end of the first +** SQL statement in zSql. This routine only compiles the first statement +** in zSql, so *pzTail is left pointing to what remains uncompiled. +** +** *ppStmt is left pointing to a compiled SQL statement that can be +** executed using sqlite3_step(). Or if there is an error, *ppStmt may be +** set to NULL. If the input text contained no SQL (if the input is and +** empty string or a comment) then *ppStmt is set to NULL. +** +** On success, SQLITE_OK is returned. Otherwise an error code is returned. +*/ +int sqlite3_prepare( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nBytes, /* Length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +int sqlite3_prepare16( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nBytes, /* Length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); + +/* +** Pointers to the following two opaque structures are used to communicate +** with the implementations of user-defined functions. +*/ +typedef struct sqlite3_context sqlite3_context; +typedef struct Mem sqlite3_value; + +/* +** In the SQL strings input to sqlite3_prepare() and sqlite3_prepare16(), +** one or more literals can be replace by a wildcard "?" or ":N:" where +** N is an integer. These value of these wildcard literals can be set +** using the routines listed below. +** +** In every case, the first parameter is a pointer to the sqlite3_stmt +** structure returned from sqlite3_prepare(). The second parameter is the +** index of the wildcard. The first "?" has an index of 1. ":N:" wildcards +** use the index N. +** +** The fifth parameter to sqlite3_bind_blob(), sqlite3_bind_text(), and +** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or +** text after SQLite has finished with it. If the fifth argument is the +** special value SQLITE_STATIC, then the library assumes that the information +** is in static, unmanaged space and does not need to be freed. If the +** fifth argument has the value SQLITE_TRANSIENT, then SQLite makes its +** own private copy of the data. +** +** The sqlite3_bind_* routine must be called before sqlite3_step() after +** an sqlite3_prepare() or sqlite3_reset(). Unbound wildcards are interpreted +** as NULL. +*/ +int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); +int sqlite3_bind_double(sqlite3_stmt*, int, double); +int sqlite3_bind_int(sqlite3_stmt*, int, int); +int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite_int64); +int sqlite3_bind_null(sqlite3_stmt*, int); +int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*)); +int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); +int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); + +/* +** Return the number of wildcards in a compiled SQL statement. This +** routine was added to support DBD::SQLite. +*/ +int sqlite3_bind_parameter_count(sqlite3_stmt*); + +/* +** Return the name of the i-th parameter. Ordinary wildcards "?" are +** nameless and a NULL is returned. For wildcards of the form :N or +** $vvvv the complete text of the wildcard is returned. +** NULL is returned if the index is out of range. +*/ +const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int); + +/* +** Return the index of a parameter with the given name. The name +** must match exactly. If no parameter with the given name is found, +** return 0. +*/ +int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName); + +/* +** Return the number of columns in the result set returned by the compiled +** SQL statement. This routine returns 0 if pStmt is an SQL statement +** that does not return data (for example an UPDATE). +*/ +int sqlite3_column_count(sqlite3_stmt *pStmt); + +/* +** The first parameter is a compiled SQL statement. This function returns +** the column heading for the Nth column of that statement, where N is the +** second function parameter. The string returned is UTF-8 for +** sqlite3_column_name() and UTF-16 for sqlite3_column_name16(). +*/ +const char *sqlite3_column_name(sqlite3_stmt*,int); +const void *sqlite3_column_name16(sqlite3_stmt*,int); + +/* +** The first parameter is a compiled SQL statement. If this statement +** is a SELECT statement, the Nth column of the returned result set +** of the SELECT is a table column then the declared type of the table +** column is returned. If the Nth column of the result set is not at table +** column, then a NULL pointer is returned. The returned string is always +** UTF-8 encoded. For example, in the database schema: +** +** CREATE TABLE t1(c1 VARIANT); +** +** And the following statement compiled: +** +** SELECT c1 + 1, 0 FROM t1; +** +** Then this routine would return the string "VARIANT" for the second +** result column (i==1), and a NULL pointer for the first result column +** (i==0). +*/ +const char *sqlite3_column_decltype(sqlite3_stmt *, int i); + +/* +** The first parameter is a compiled SQL statement. If this statement +** is a SELECT statement, the Nth column of the returned result set +** of the SELECT is a table column then the declared type of the table +** column is returned. If the Nth column of the result set is not at table +** column, then a NULL pointer is returned. The returned string is always +** UTF-16 encoded. For example, in the database schema: +** +** CREATE TABLE t1(c1 INTEGER); +** +** And the following statement compiled: +** +** SELECT c1 + 1, 0 FROM t1; +** +** Then this routine would return the string "INTEGER" for the second +** result column (i==1), and a NULL pointer for the first result column +** (i==0). +*/ +const void *sqlite3_column_decltype16(sqlite3_stmt*,int); + +/* +** After an SQL query has been compiled with a call to either +** sqlite3_prepare() or sqlite3_prepare16(), then this function must be +** called one or more times to execute the statement. +** +** The return value will be either SQLITE_BUSY, SQLITE_DONE, +** SQLITE_ROW, SQLITE_ERROR, or SQLITE_MISUSE. +** +** SQLITE_BUSY means that the database engine attempted to open +** a locked database and there is no busy callback registered. +** Call sqlite3_step() again to retry the open. +** +** SQLITE_DONE means that the statement has finished executing +** successfully. sqlite3_step() should not be called again on this virtual +** machine. +** +** If the SQL statement being executed returns any data, then +** SQLITE_ROW is returned each time a new row of data is ready +** for processing by the caller. The values may be accessed using +** the sqlite3_column_*() functions described below. sqlite3_step() +** is called again to retrieve the next row of data. +** +** SQLITE_ERROR means that a run-time error (such as a constraint +** violation) has occurred. sqlite3_step() should not be called again on +** the VM. More information may be found by calling sqlite3_errmsg(). +** +** SQLITE_MISUSE means that the this routine was called inappropriately. +** Perhaps it was called on a virtual machine that had already been +** finalized or on one that had previously returned SQLITE_ERROR or +** SQLITE_DONE. Or it could be the case the the same database connection +** is being used simulataneously by two or more threads. +*/ +int sqlite3_step(sqlite3_stmt*); + +/* +** Return the number of values in the current row of the result set. +** +** After a call to sqlite3_step() that returns SQLITE_ROW, this routine +** will return the same value as the sqlite3_column_count() function. +** After sqlite3_step() has returned an SQLITE_DONE, SQLITE_BUSY or +** error code, or before sqlite3_step() has been called on a +** compiled SQL statement, this routine returns zero. +*/ +int sqlite3_data_count(sqlite3_stmt *pStmt); + +/* +** Values are stored in the database in one of the following fundamental +** types. +*/ +#define SQLITE_INTEGER 1 +#define SQLITE_FLOAT 2 +/* #define SQLITE_TEXT 3 // See below */ +#define SQLITE_BLOB 4 +#define SQLITE_NULL 5 + +/* +** SQLite version 2 defines SQLITE_TEXT differently. To allow both +** version 2 and version 3 to be included, undefine them both if a +** conflict is seen. Define SQLITE3_TEXT to be the version 3 value. +*/ +#ifdef SQLITE_TEXT +# undef SQLITE_TEXT +#else +# define SQLITE_TEXT 3 +#endif +#define SQLITE3_TEXT 3 + +/* +** The next group of routines returns information about the information +** in a single column of the current result row of a query. In every +** case the first parameter is a pointer to the SQL statement that is being +** executed (the sqlite_stmt* that was returned from sqlite3_prepare()) and +** the second argument is the index of the column for which information +** should be returned. iCol is zero-indexed. The left-most column as an +** index of 0. +** +** If the SQL statement is not currently point to a valid row, or if the +** the colulmn index is out of range, the result is undefined. +** +** These routines attempt to convert the value where appropriate. For +** example, if the internal representation is FLOAT and a text result +** is requested, sprintf() is used internally to do the conversion +** automatically. The following table details the conversions that +** are applied: +** +** Internal Type Requested Type Conversion +** ------------- -------------- -------------------------- +** NULL INTEGER Result is 0 +** NULL FLOAT Result is 0.0 +** NULL TEXT Result is an empty string +** NULL BLOB Result is a zero-length BLOB +** INTEGER FLOAT Convert from integer to float +** INTEGER TEXT ASCII rendering of the integer +** INTEGER BLOB Same as for INTEGER->TEXT +** FLOAT INTEGER Convert from float to integer +** FLOAT TEXT ASCII rendering of the float +** FLOAT BLOB Same as FLOAT->TEXT +** TEXT INTEGER Use atoi() +** TEXT FLOAT Use atof() +** TEXT BLOB No change +** BLOB INTEGER Convert to TEXT then use atoi() +** BLOB FLOAT Convert to TEXT then use atof() +** BLOB TEXT Add a \000 terminator if needed +** +** The following access routines are provided: +** +** _type() Return the datatype of the result. This is one of +** SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, +** or SQLITE_NULL. +** _blob() Return the value of a BLOB. +** _bytes() Return the number of bytes in a BLOB value or the number +** of bytes in a TEXT value represented as UTF-8. The \000 +** terminator is included in the byte count for TEXT values. +** _bytes16() Return the number of bytes in a BLOB value or the number +** of bytes in a TEXT value represented as UTF-16. The \u0000 +** terminator is included in the byte count for TEXT values. +** _double() Return a FLOAT value. +** _int() Return an INTEGER value in the host computer's native +** integer representation. This might be either a 32- or 64-bit +** integer depending on the host. +** _int64() Return an INTEGER value as a 64-bit signed integer. +** _text() Return the value as UTF-8 text. +** _text16() Return the value as UTF-16 text. +*/ +const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); +int sqlite3_column_bytes(sqlite3_stmt*, int iCol); +int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); +double sqlite3_column_double(sqlite3_stmt*, int iCol); +int sqlite3_column_int(sqlite3_stmt*, int iCol); +sqlite_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); +const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); +const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); +int sqlite3_column_type(sqlite3_stmt*, int iCol); + +/* +** The sqlite3_finalize() function is called to delete a compiled +** SQL statement obtained by a previous call to sqlite3_prepare() +** or sqlite3_prepare16(). If the statement was executed successfully, or +** not executed at all, then SQLITE_OK is returned. If execution of the +** statement failed then an error code is returned. +** +** This routine can be called at any point during the execution of the +** virtual machine. If the virtual machine has not completed execution +** when this routine is called, that is like encountering an error or +** an interrupt. (See sqlite3_interrupt().) Incomplete updates may be +** rolled back and transactions cancelled, depending on the circumstances, +** and the result code returned will be SQLITE_ABORT. +*/ +int sqlite3_finalize(sqlite3_stmt *pStmt); + +/* +** The sqlite3_reset() function is called to reset a compiled SQL +** statement obtained by a previous call to sqlite3_prepare() or +** sqlite3_prepare16() back to it's initial state, ready to be re-executed. +** Any SQL statement variables that had values bound to them using +** the sqlite3_bind_*() API retain their values. +*/ +int sqlite3_reset(sqlite3_stmt *pStmt); + +/* +** The following two functions are used to add user functions or aggregates +** implemented in C to the SQL langauge interpreted by SQLite. The +** difference only between the two is that the second parameter, the +** name of the (scalar) function or aggregate, is encoded in UTF-8 for +** sqlite3_create_function() and UTF-16 for sqlite3_create_function16(). +** +** The first argument is the database handle that the new function or +** aggregate is to be added to. If a single program uses more than one +** database handle internally, then user functions or aggregates must +** be added individually to each database handle with which they will be +** used. +** +** The third parameter is the number of arguments that the function or +** aggregate takes. If this parameter is negative, then the function or +** aggregate may take any number of arguments. +** +** The fourth parameter is one of SQLITE_UTF* values defined below, +** indicating the encoding that the function is most likely to handle +** values in. This does not change the behaviour of the programming +** interface. However, if two versions of the same function are registered +** with different encoding values, SQLite invokes the version likely to +** minimize conversions between text encodings. +** +** The seventh, eighth and ninth parameters, xFunc, xStep and xFinal, are +** pointers to user implemented C functions that implement the user +** function or aggregate. A scalar function requires an implementation of +** the xFunc callback only, NULL pointers should be passed as the xStep +** and xFinal parameters. An aggregate function requires an implementation +** of xStep and xFinal, but NULL should be passed for xFunc. To delete an +** existing user function or aggregate, pass NULL for all three function +** callback. Specifying an inconstent set of callback values, such as an +** xFunc and an xFinal, or an xStep but no xFinal, SQLITE_ERROR is +** returned. +*/ +int sqlite3_create_function( + sqlite3 *, + const char *zFunctionName, + int nArg, + int eTextRep, + void*, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); +int sqlite3_create_function16( + sqlite3*, + const void *zFunctionName, + int nArg, + int eTextRep, + void*, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); + +/* +** The next routine returns the number of calls to xStep for a particular +** aggregate function instance. The current call to xStep counts so this +** routine always returns at least 1. +*/ +int sqlite3_aggregate_count(sqlite3_context*); + +/* +** The next group of routines returns information about parameters to +** a user-defined function. Function implementations use these routines +** to access their parameters. These routines are the same as the +** sqlite3_column_* routines except that these routines take a single +** sqlite3_value* pointer instead of an sqlite3_stmt* and an integer +** column number. +*/ +const void *sqlite3_value_blob(sqlite3_value*); +int sqlite3_value_bytes(sqlite3_value*); +int sqlite3_value_bytes16(sqlite3_value*); +double sqlite3_value_double(sqlite3_value*); +int sqlite3_value_int(sqlite3_value*); +sqlite_int64 sqlite3_value_int64(sqlite3_value*); +const unsigned char *sqlite3_value_text(sqlite3_value*); +const void *sqlite3_value_text16(sqlite3_value*); +const void *sqlite3_value_text16le(sqlite3_value*); +const void *sqlite3_value_text16be(sqlite3_value*); +int sqlite3_value_type(sqlite3_value*); + +/* +** Aggregate functions use the following routine to allocate +** a structure for storing their state. The first time this routine +** is called for a particular aggregate, a new structure of size nBytes +** is allocated, zeroed, and returned. On subsequent calls (for the +** same aggregate instance) the same buffer is returned. The implementation +** of the aggregate can use the returned buffer to accumulate data. +** +** The buffer allocated is freed automatically by SQLite. +*/ +void *sqlite3_aggregate_context(sqlite3_context*, int nBytes); + +/* +** The pUserData parameter to the sqlite3_create_function() and +** sqlite3_create_aggregate() routines used to register user functions +** is available to the implementation of the function using this +** call. +*/ +void *sqlite3_user_data(sqlite3_context*); + +/* +** The following two functions may be used by scalar user functions to +** associate meta-data with argument values. If the same value is passed to +** multiple invocations of the user-function during query execution, under +** some circumstances the associated meta-data may be preserved. This may +** be used, for example, to add a regular-expression matching scalar +** function. The compiled version of the regular expression is stored as +** meta-data associated with the SQL value passed as the regular expression +** pattern. +** +** Calling sqlite3_get_auxdata() returns a pointer to the meta data +** associated with the Nth argument value to the current user function +** call, where N is the second parameter. If no meta-data has been set for +** that value, then a NULL pointer is returned. +** +** The sqlite3_set_auxdata() is used to associate meta data with a user +** function argument. The third parameter is a pointer to the meta data +** to be associated with the Nth user function argument value. The fourth +** parameter specifies a 'delete function' that will be called on the meta +** data pointer to release it when it is no longer required. If the delete +** function pointer is NULL, it is not invoked. +** +** In practice, meta-data is preserved between function calls for +** expressions that are constant at compile time. This includes literal +** values and SQL variables. +*/ +void *sqlite3_get_auxdata(sqlite3_context*, int); +void sqlite3_set_auxdata(sqlite3_context*, int, void*, void (*)(void*)); + + +/* +** These are special value for the destructor that is passed in as the +** final argument to routines like sqlite3_result_blob(). If the destructor +** argument is SQLITE_STATIC, it means that the content pointer is constant +** and will never change. It does not need to be destroyed. The +** SQLITE_TRANSIENT value means that the content will likely change in +** the near future and that SQLite should make its own private copy of +** the content before returning. +*/ +#define SQLITE_STATIC ((void(*)(void *))0) +#define SQLITE_TRANSIENT ((void(*)(void *))-1) + +/* +** User-defined functions invoke the following routines in order to +** set their return value. +*/ +void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); +void sqlite3_result_double(sqlite3_context*, double); +void sqlite3_result_error(sqlite3_context*, const char*, int); +void sqlite3_result_error16(sqlite3_context*, const void*, int); +void sqlite3_result_int(sqlite3_context*, int); +void sqlite3_result_int64(sqlite3_context*, sqlite_int64); +void sqlite3_result_null(sqlite3_context*); +void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); +void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); +void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); +void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); +void sqlite3_result_value(sqlite3_context*, sqlite3_value*); + +/* +** These are the allowed values for the eTextRep argument to +** sqlite3_create_collation and sqlite3_create_function. +*/ +#define SQLITE_UTF8 1 +#define SQLITE_UTF16LE 2 +#define SQLITE_UTF16BE 3 +#define SQLITE_UTF16 4 /* Use native byte order */ +#define SQLITE_ANY 5 /* sqlite3_create_function only */ + +/* +** These two functions are used to add new collation sequences to the +** sqlite3 handle specified as the first argument. +** +** The name of the new collation sequence is specified as a UTF-8 string +** for sqlite3_create_collation() and a UTF-16 string for +** sqlite3_create_collation16(). In both cases the name is passed as the +** second function argument. +** +** The third argument must be one of the constants SQLITE_UTF8, +** SQLITE_UTF16LE or SQLITE_UTF16BE, indicating that the user-supplied +** routine expects to be passed pointers to strings encoded using UTF-8, +** UTF-16 little-endian or UTF-16 big-endian respectively. +** +** A pointer to the user supplied routine must be passed as the fifth +** argument. If it is NULL, this is the same as deleting the collation +** sequence (so that SQLite cannot call it anymore). Each time the user +** supplied function is invoked, it is passed a copy of the void* passed as +** the fourth argument to sqlite3_create_collation() or +** sqlite3_create_collation16() as its first parameter. +** +** The remaining arguments to the user-supplied routine are two strings, +** each represented by a [length, data] pair and encoded in the encoding +** that was passed as the third argument when the collation sequence was +** registered. The user routine should return negative, zero or positive if +** the first string is less than, equal to, or greater than the second +** string. i.e. (STRING1 - STRING2). +*/ +int sqlite3_create_collation( + sqlite3*, + const char *zName, + int eTextRep, + void*, + int(*xCompare)(void*,int,const void*,int,const void*) +); +int sqlite3_create_collation16( + sqlite3*, + const char *zName, + int eTextRep, + void*, + int(*xCompare)(void*,int,const void*,int,const void*) +); + +/* +** To avoid having to register all collation sequences before a database +** can be used, a single callback function may be registered with the +** database handle to be called whenever an undefined collation sequence is +** required. +** +** If the function is registered using the sqlite3_collation_needed() API, +** then it is passed the names of undefined collation sequences as strings +** encoded in UTF-8. If sqlite3_collation_needed16() is used, the names +** are passed as UTF-16 in machine native byte order. A call to either +** function replaces any existing callback. +** +** When the user-function is invoked, the first argument passed is a copy +** of the second argument to sqlite3_collation_needed() or +** sqlite3_collation_needed16(). The second argument is the database +** handle. The third argument is one of SQLITE_UTF8, SQLITE_UTF16BE or +** SQLITE_UTF16LE, indicating the most desirable form of the collation +** sequence function required. The fourth parameter is the name of the +** required collation sequence. +** +** The collation sequence is returned to SQLite by a collation-needed +** callback using the sqlite3_create_collation() or +** sqlite3_create_collation16() APIs, described above. +*/ +int sqlite3_collation_needed( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const char*) +); +int sqlite3_collation_needed16( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const void*) +); + +/* +** Specify the key for an encrypted database. This routine should be +** called right after sqlite3_open(). +** +** The code to implement this API is not available in the public release +** of SQLite. +*/ +int sqlite3_key( + sqlite3 *db, /* Database to be rekeyed */ + const void *pKey, int nKey /* The key */ +); + +/* +** Change the key on an open database. If the current database is not +** encrypted, this routine will encrypt it. If pNew==0 or nNew==0, the +** database is decrypted. +** +** The code to implement this API is not available in the public release +** of SQLite. +*/ +int sqlite3_rekey( + sqlite3 *db, /* Database to be rekeyed */ + const void *pKey, int nKey /* The new key */ +); + +/* +** If the following global variable is made to point to a constant +** string which is the name of a directory, then all temporary files +** created by SQLite will be placed in that directory. If this variable +** is NULL pointer, then SQLite does a search for an appropriate temporary +** file directory. +** +** This variable should only be changed when there are no open databases. +** Once sqlite3_open() has been called, this variable should not be changed +** until all database connections are closed. +*/ +extern const char *sqlite3_temp_directory; + +#ifdef __cplusplus +} /* End of the 'extern "C"' block */ +#endif +#endif diff --git a/kopete/plugins/statistics/sqlite/sqliteInt.h b/kopete/plugins/statistics/sqlite/sqliteInt.h new file mode 100644 index 00000000..b4fa474b --- /dev/null +++ b/kopete/plugins/statistics/sqlite/sqliteInt.h @@ -0,0 +1,1419 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Internal interface definitions for SQLite. +** +** @(#) $Id$ +*/ +#ifndef _SQLITEINT_H_ +#define _SQLITEINT_H_ + +/* +** These #defines should enable >2GB file support on Posix if the +** underlying operating system supports it. If the OS lacks +** large file support, or if the OS is windows, these should be no-ops. +** +** Large file support can be disabled using the -DSQLITE_DISABLE_LFS switch +** on the compiler command line. This is necessary if you are compiling +** on a recent machine (ex: RedHat 7.2) but you want your code to work +** on an older machine (ex: RedHat 6.0). If you compile on RedHat 7.2 +** without this option, LFS is enable. But LFS does not exist in the kernel +** in RedHat 6.0, so the code won't work. Hence, for maximum binary +** portability you should omit LFS. +** +** Similar is true for MacOS. LFS is only supported on MacOS 9 and later. +*/ +#ifndef SQLITE_DISABLE_LFS +# define _LARGE_FILE 1 +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# define _LARGEFILE_SOURCE 1 +#endif + +#include "config.h" +#include "sqlite3.h" +#include "hash.h" +#include "parse.h" +#include +#include +#include +#include + +/* +** The maximum number of in-memory pages to use for the main database +** table and for temporary tables. +*/ +#define MAX_PAGES 2000 +#define TEMP_PAGES 500 + +/* +** If the following macro is set to 1, then NULL values are considered +** distinct for the SELECT DISTINCT statement and for UNION or EXCEPT +** compound queries. No other SQL database engine (among those tested) +** works this way except for OCELOT. But the SQL92 spec implies that +** this is how things should work. +** +** If the following macro is set to 0, then NULLs are indistinct for +** SELECT DISTINCT and for UNION. +*/ +#define NULL_ALWAYS_DISTINCT 0 + +/* +** If the following macro is set to 1, then NULL values are considered +** distinct when determining whether or not two entries are the same +** in a UNIQUE index. This is the way PostgreSQL, Oracle, DB2, MySQL, +** OCELOT, and Firebird all work. The SQL92 spec explicitly says this +** is the way things are suppose to work. +** +** If the following macro is set to 0, the NULLs are indistinct for +** a UNIQUE index. In this mode, you can only have a single NULL entry +** for a column declared UNIQUE. This is the way Informix and SQL Server +** work. +*/ +#define NULL_DISTINCT_FOR_UNIQUE 1 + +/* +** The maximum number of attached databases. This must be at least 2 +** in order to support the main database file (0) and the file used to +** hold temporary tables (1). And it must be less than 32 because +** we use a bitmask of databases with a u32 in places (for example +** the Parse.cookieMask field). +*/ +#define MAX_ATTACHED 10 + +/* +** The maximum value of a ?nnn wildcard that the parser will accept. +*/ +#define SQLITE_MAX_VARIABLE_NUMBER 999 + +/* +** When building SQLite for embedded systems where memory is scarce, +** you can define one or more of the following macros to omit extra +** features of the library and thus keep the size of the library to +** a minimum. +*/ +/* #define SQLITE_OMIT_AUTHORIZATION 1 */ +/* #define SQLITE_OMIT_INMEMORYDB 1 */ +/* #define SQLITE_OMIT_VACUUM 1 */ +/* #define SQLITE_OMIT_DATETIME_FUNCS 1 */ +/* #define SQLITE_OMIT_PROGRESS_CALLBACK 1 */ + +/* +** Integers of known sizes. These typedefs might change for architectures +** where the sizes very. Preprocessor macros are available so that the +** types can be conveniently redefined at compile-type. Like this: +** +** cc '-DUINTPTR_TYPE=long long int' ... +*/ +#ifndef UINT64_TYPE +# if defined(_MSC_VER) || defined(__BORLANDC__) +# define UINT64_TYPE unsigned __int64 +# else +# define UINT64_TYPE unsigned long long int +# endif +#endif +#ifndef UINT32_TYPE +# define UINT32_TYPE unsigned int +#endif +#ifndef UINT16_TYPE +# define UINT16_TYPE unsigned short int +#endif +#ifndef INT16_TYPE +# define INT16_TYPE short int +#endif +#ifndef UINT8_TYPE +# define UINT8_TYPE unsigned char +#endif +#ifndef INT8_TYPE +# define INT8_TYPE signed char +#endif +#ifndef LONGDOUBLE_TYPE +# define LONGDOUBLE_TYPE long double +#endif +#ifndef INTPTR_TYPE +# if SQLITE_PTR_SZ==4 +# define INTPTR_TYPE int +# else +# define INTPTR_TYPE sqlite_int64 +# endif +#endif +#ifndef UINTPTR_TYPE +# if SQLITE_PTR_SZ==4 +# define UINTPTR_TYPE unsigned int +# else +# define UINTPTR_TYPE sqlite_uint64 +# endif +#endif +typedef sqlite_int64 i64; /* 8-byte signed integer */ +typedef UINT64_TYPE u64; /* 8-byte unsigned integer */ +typedef UINT32_TYPE u32; /* 4-byte unsigned integer */ +typedef UINT16_TYPE u16; /* 2-byte unsigned integer */ +typedef INT16_TYPE i16; /* 2-byte signed integer */ +typedef UINT8_TYPE u8; /* 1-byte unsigned integer */ +typedef UINT8_TYPE i8; /* 1-byte signed integer */ +typedef INTPTR_TYPE ptr; /* Big enough to hold a pointer */ +typedef UINTPTR_TYPE uptr; /* Big enough to hold a pointer */ + +/* +** Macros to determine whether the machine is big or little endian, +** evaluated at runtime. +*/ +extern const int sqlite3one; +#define SQLITE_BIGENDIAN (*(char *)(&sqlite3one)==0) +#define SQLITE_LITTLEENDIAN (*(char *)(&sqlite3one)==1) + +/* +** An instance of the following structure is used to store the busy-handler +** callback for a given sqlite handle. +** +** The sqlite.busyHandler member of the sqlite struct contains the busy +** callback for the database handle. Each pager opened via the sqlite +** handle is passed a pointer to sqlite.busyHandler. The busy-handler +** callback is currently invoked only from within pager.c. +*/ +typedef struct BusyHandler BusyHandler; +struct BusyHandler { + int (*xFunc)(void *,int); /* The busy callback */ + void *pArg; /* First arg to busy callback */ +}; + +/* +** Defer sourcing vdbe.h and btree.h until after the "u8" and +** "BusyHandler typedefs. +*/ +#include "vdbe.h" +#include "btree.h" + +/* +** This macro casts a pointer to an integer. Useful for doing +** pointer arithmetic. +*/ +#define Addr(X) ((uptr)X) + +/* +** If memory allocation problems are found, recompile with +** +** -DSQLITE_DEBUG=1 +** +** to enable some sanity checking on malloc() and free(). To +** check for memory leaks, recompile with +** +** -DSQLITE_DEBUG=2 +** +** and a line of text will be written to standard error for +** each malloc() and free(). This output can be analyzed +** by an AWK script to determine if there are any leaks. +*/ +#ifdef SQLITE_DEBUG +# define sqliteMalloc(X) sqlite3Malloc_(X,1,__FILE__,__LINE__) +# define sqliteMallocRaw(X) sqlite3Malloc_(X,0,__FILE__,__LINE__) +# define sqliteFree(X) sqlite3Free_(X,__FILE__,__LINE__) +# define sqliteRealloc(X,Y) sqlite3Realloc_(X,Y,__FILE__,__LINE__) +# define sqliteStrDup(X) sqlite3StrDup_(X,__FILE__,__LINE__) +# define sqliteStrNDup(X,Y) sqlite3StrNDup_(X,Y,__FILE__,__LINE__) +#else +# define sqliteFree sqlite3FreeX +# define sqliteMalloc sqlite3Malloc +# define sqliteMallocRaw sqlite3MallocRaw +# define sqliteRealloc sqlite3Realloc +# define sqliteStrDup sqlite3StrDup +# define sqliteStrNDup sqlite3StrNDup +#endif + +/* +** This variable gets set if malloc() ever fails. After it gets set, +** the SQLite library shuts down permanently. +*/ +extern int sqlite3_malloc_failed; + +/* +** The following global variables are used for testing and debugging +** only. They only work if SQLITE_DEBUG is defined. +*/ +#ifdef SQLITE_DEBUG +extern int sqlite3_nMalloc; /* Number of sqliteMalloc() calls */ +extern int sqlite3_nFree; /* Number of sqliteFree() calls */ +extern int sqlite3_iMallocFail; /* Fail sqliteMalloc() after this many calls */ +#endif + +/* +** Name of the master database table. The master database table +** is a special table that holds the names and attributes of all +** user tables and indices. +*/ +#define MASTER_NAME "sqlite_master" +#define TEMP_MASTER_NAME "sqlite_temp_master" + +/* +** The root-page of the master database table. +*/ +#define MASTER_ROOT 1 + +/* +** The name of the schema table. +*/ +#define SCHEMA_TABLE(x) (x==1?TEMP_MASTER_NAME:MASTER_NAME) + +/* +** A convenience macro that returns the number of elements in +** an array. +*/ +#define ArraySize(X) (sizeof(X)/sizeof(X[0])) + +/* +** Forward references to structures +*/ +typedef struct Column Column; +typedef struct Table Table; +typedef struct Index Index; +typedef struct Instruction Instruction; +typedef struct Expr Expr; +typedef struct ExprList ExprList; +typedef struct Parse Parse; +typedef struct Token Token; +typedef struct IdList IdList; +typedef struct SrcList SrcList; +typedef struct WhereInfo WhereInfo; +typedef struct WhereLevel WhereLevel; +typedef struct Select Select; +typedef struct AggExpr AggExpr; +typedef struct FuncDef FuncDef; +typedef struct Trigger Trigger; +typedef struct TriggerStep TriggerStep; +typedef struct TriggerStack TriggerStack; +typedef struct FKey FKey; +typedef struct Db Db; +typedef struct AuthContext AuthContext; +typedef struct KeyClass KeyClass; +typedef struct CollSeq CollSeq; +typedef struct KeyInfo KeyInfo; + +/* +** Each database file to be accessed by the system is an instance +** of the following structure. There are normally two of these structures +** in the sqlite.aDb[] array. aDb[0] is the main database file and +** aDb[1] is the database file used to hold temporary tables. Additional +** databases may be attached. +*/ +struct Db { + char *zName; /* Name of this database */ + Btree *pBt; /* The B*Tree structure for this database file */ + int schema_cookie; /* Database schema version number for this file */ + Hash tblHash; /* All tables indexed by name */ + Hash idxHash; /* All (named) indices indexed by name */ + Hash trigHash; /* All triggers indexed by name */ + Hash aFKey; /* Foreign keys indexed by to-table */ + u16 flags; /* Flags associated with this database */ + u8 inTrans; /* 0: not writable. 1: Transaction. 2: Checkpoint */ + u8 safety_level; /* How aggressive at synching data to disk */ + int cache_size; /* Number of pages to use in the cache */ + void *pAux; /* Auxiliary data. Usually NULL */ + void (*xFreeAux)(void*); /* Routine to free pAux */ +}; + +/* +** These macros can be used to test, set, or clear bits in the +** Db.flags field. +*/ +#define DbHasProperty(D,I,P) (((D)->aDb[I].flags&(P))==(P)) +#define DbHasAnyProperty(D,I,P) (((D)->aDb[I].flags&(P))!=0) +#define DbSetProperty(D,I,P) (D)->aDb[I].flags|=(P) +#define DbClearProperty(D,I,P) (D)->aDb[I].flags&=~(P) + +/* +** Allowed values for the DB.flags field. +** +** The DB_SchemaLoaded flag is set after the database schema has been +** read into internal hash tables. +** +** DB_UnresetViews means that one or more views have column names that +** have been filled out. If the schema changes, these column names might +** changes and so the view will need to be reset. +*/ +#define DB_SchemaLoaded 0x0001 /* The schema has been loaded */ +#define DB_UnresetViews 0x0002 /* Some views have defined column names */ + +#define SQLITE_UTF16NATIVE (SQLITE_BIGENDIAN?SQLITE_UTF16BE:SQLITE_UTF16LE) + +/* +** Each database is an instance of the following structure. +** +** The sqlite.lastRowid records the last insert rowid generated by an +** insert statement. Inserts on views do not affect its value. Each +** trigger has its own context, so that lastRowid can be updated inside +** triggers as usual. The previous value will be restored once the trigger +** exits. Upon entering a before or instead of trigger, lastRowid is no +** longer (since after version 2.8.12) reset to -1. +** +** The sqlite.nChange does not count changes within triggers and keeps no +** context. It is reset at start of sqlite3_exec. +** The sqlite.lsChange represents the number of changes made by the last +** insert, update, or delete statement. It remains constant throughout the +** length of a statement and is then updated by OP_SetCounts. It keeps a +** context stack just like lastRowid so that the count of changes +** within a trigger is not seen outside the trigger. Changes to views do not +** affect the value of lsChange. +** The sqlite.csChange keeps track of the number of current changes (since +** the last statement) and is used to update sqlite_lsChange. +** +** The member variables sqlite.errCode, sqlite.zErrMsg and sqlite.zErrMsg16 +** store the most recent error code and, if applicable, string. The +** internal function sqlite3Error() is used to set these variables +** consistently. +*/ +struct sqlite3 { + int nDb; /* Number of backends currently in use */ + Db *aDb; /* All backends */ + Db aDbStatic[2]; /* Static space for the 2 default backends */ + int flags; /* Miscellanous flags. See below */ + u8 file_format; /* What file format version is this database? */ + u8 temp_store; /* 1: file 2: memory 0: default */ + int nTable; /* Number of tables in the database */ + BusyHandler busyHandler; /* Busy callback */ + void *pCommitArg; /* Argument to xCommitCallback() */ + int (*xCommitCallback)(void*);/* Invoked at every commit. */ + Hash aFunc; /* All functions that can be in SQL exprs */ + Hash aCollSeq; /* All collating sequences */ + CollSeq *pDfltColl; /* The default collating sequence (BINARY) */ + i64 lastRowid; /* ROWID of most recent insert (see above) */ + i64 priorNewRowid; /* Last randomly generated ROWID */ + int magic; /* Magic number for detect library misuse */ + int nChange; /* Value returned by sqlite3_changes() */ + int nTotalChange; /* Value returned by sqlite3_total_changes() */ + struct sqlite3InitInfo { /* Information used during initialization */ + int iDb; /* When back is being initialized */ + int newTnum; /* Rootpage of table being initialized */ + u8 busy; /* TRUE if currently initializing */ + } init; + struct Vdbe *pVdbe; /* List of active virtual machines */ + int activeVdbeCnt; /* Number of vdbes currently executing */ + void (*xTrace)(void*,const char*); /* Trace function */ + void *pTraceArg; /* Argument to the trace function */ +#ifndef SQLITE_OMIT_AUTHORIZATION + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*); + /* Access authorization function */ + void *pAuthArg; /* 1st argument to the access auth function */ +#endif +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + int (*xProgress)(void *); /* The progress callback */ + void *pProgressArg; /* Argument to the progress callback */ + int nProgressOps; /* Number of opcodes for progress callback */ +#endif + + int errCode; /* Most recent error code (SQLITE_*) */ + u8 enc; /* Text encoding for this database. */ + u8 autoCommit; /* The auto-commit flag. */ + void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*); + void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*); + void *pCollNeededArg; + sqlite3_value *pValue; /* Value used for transient conversions */ + sqlite3_value *pErr; /* Most recent error message */ + + char *zErrMsg; /* Most recent error message (UTF-8 encoded) */ + char *zErrMsg16; /* Most recent error message (UTF-8 encoded) */ +}; + +/* +** Possible values for the sqlite.flags and or Db.flags fields. +** +** On sqlite.flags, the SQLITE_InTrans value means that we have +** executed a BEGIN. On Db.flags, SQLITE_InTrans means a statement +** transaction is active on that particular database file. +*/ +#define SQLITE_VdbeTrace 0x00000001 /* True to trace VDBE execution */ +#define SQLITE_Initialized 0x00000002 /* True after initialization */ +#define SQLITE_Interrupt 0x00000004 /* Cancel current operation */ +#define SQLITE_InTrans 0x00000008 /* True if in a transaction */ +#define SQLITE_InternChanges 0x00000010 /* Uncommitted Hash table changes */ +#define SQLITE_FullColNames 0x00000020 /* Show full column names on SELECT */ +#define SQLITE_ShortColNames 0x00000040 /* Show short columns names */ +#define SQLITE_CountRows 0x00000080 /* Count rows changed by INSERT, */ + /* DELETE, or UPDATE and return */ + /* the count using a callback. */ +#define SQLITE_NullCallback 0x00000100 /* Invoke the callback once if the */ + /* result set is empty */ +#define SQLITE_SqlTrace 0x00000200 /* Debug print SQL as it executes */ +#define SQLITE_VdbeListing 0x00000400 /* Debug listings of VDBE programs */ + +/* +** Possible values for the sqlite.magic field. +** The numbers are obtained at random and have no special meaning, other +** than being distinct from one another. +*/ +#define SQLITE_MAGIC_OPEN 0xa029a697 /* Database is open */ +#define SQLITE_MAGIC_CLOSED 0x9f3c2d33 /* Database is closed */ +#define SQLITE_MAGIC_BUSY 0xf03b7906 /* Database currently in use */ +#define SQLITE_MAGIC_ERROR 0xb5357930 /* An SQLITE_MISUSE error occurred */ + +/* +** Each SQL function is defined by an instance of the following +** structure. A pointer to this structure is stored in the sqlite.aFunc +** hash table. When multiple functions have the same name, the hash table +** points to a linked list of these structures. +*/ +struct FuncDef { + char *zName; /* SQL name of the function */ + int nArg; /* Number of arguments. -1 means unlimited */ + u8 iPrefEnc; /* Preferred text encoding (SQLITE_UTF8, 16LE, 16BE) */ + void *pUserData; /* User data parameter */ + FuncDef *pNext; /* Next function with same name */ + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); /* Regular function */ + void (*xStep)(sqlite3_context*,int,sqlite3_value**); /* Aggregate step */ + void (*xFinalize)(sqlite3_context*); /* Aggregate finializer */ + u8 needCollSeq; /* True if sqlite3GetFuncCollSeq() might be called */ +}; + +/* +** information about each column of an SQL table is held in an instance +** of this structure. +*/ +struct Column { + char *zName; /* Name of this column */ + char *zDflt; /* Default value of this column */ + char *zType; /* Data type for this column */ + CollSeq *pColl; /* Collating sequence. If NULL, use the default */ + u8 notNull; /* True if there is a NOT NULL constraint */ + u8 isPrimKey; /* True if this column is part of the PRIMARY KEY */ + char affinity; /* One of the SQLITE_AFF_... values */ +}; + +/* +** A "Collating Sequence" is defined by an instance of the following +** structure. Conceptually, a collating sequence consists of a name and +** a comparison routine that defines the order of that sequence. +** +** There may two seperate implementations of the collation function, one +** that processes text in UTF-8 encoding (CollSeq.xCmp) and another that +** processes text encoded in UTF-16 (CollSeq.xCmp16), using the machine +** native byte order. When a collation sequence is invoked, SQLite selects +** the version that will require the least expensive encoding +** transalations, if any. +** +** The CollSeq.pUser member variable is an extra parameter that passed in +** as the first argument to the UTF-8 comparison function, xCmp. +** CollSeq.pUser16 is the equivalent for the UTF-16 comparison function, +** xCmp16. +** +** If both CollSeq.xCmp and CollSeq.xCmp16 are NULL, it means that the +** collating sequence is undefined. Indices built on an undefined +** collating sequence may not be read or written. +*/ +struct CollSeq { + char *zName; /* Name of the collating sequence, UTF-8 encoded */ + u8 enc; /* Text encoding handled by xCmp() */ + void *pUser; /* First argument to xCmp() */ + int (*xCmp)(void*,int, const void*, int, const void*); +}; + +/* +** A sort order can be either ASC or DESC. +*/ +#define SQLITE_SO_ASC 0 /* Sort in ascending order */ +#define SQLITE_SO_DESC 1 /* Sort in ascending order */ + +/* +** Column affinity types. +*/ +#define SQLITE_AFF_INTEGER 'i' +#define SQLITE_AFF_NUMERIC 'n' +#define SQLITE_AFF_TEXT 't' +#define SQLITE_AFF_NONE 'o' + + +/* +** Each SQL table is represented in memory by an instance of the +** following structure. +** +** Table.zName is the name of the table. The case of the original +** CREATE TABLE statement is stored, but case is not significant for +** comparisons. +** +** Table.nCol is the number of columns in this table. Table.aCol is a +** pointer to an array of Column structures, one for each column. +** +** If the table has an INTEGER PRIMARY KEY, then Table.iPKey is the index of +** the column that is that key. Otherwise Table.iPKey is negative. Note +** that the datatype of the PRIMARY KEY must be INTEGER for this field to +** be set. An INTEGER PRIMARY KEY is used as the rowid for each row of +** the table. If a table has no INTEGER PRIMARY KEY, then a random rowid +** is generated for each row of the table. Table.hasPrimKey is true if +** the table has any PRIMARY KEY, INTEGER or otherwise. +** +** Table.tnum is the page number for the root BTree page of the table in the +** database file. If Table.iDb is the index of the database table backend +** in sqlite.aDb[]. 0 is for the main database and 1 is for the file that +** holds temporary tables and indices. If Table.isTransient +** is true, then the table is stored in a file that is automatically deleted +** when the VDBE cursor to the table is closed. In this case Table.tnum +** refers VDBE cursor number that holds the table open, not to the root +** page number. Transient tables are used to hold the results of a +** sub-query that appears instead of a real table name in the FROM clause +** of a SELECT statement. +*/ +struct Table { + char *zName; /* Name of the table */ + int nCol; /* Number of columns in this table */ + Column *aCol; /* Information about each column */ + int iPKey; /* If not less then 0, use aCol[iPKey] as the primary key */ + Index *pIndex; /* List of SQL indexes on this table. */ + int tnum; /* Root BTree node for this table (see note above) */ + Select *pSelect; /* NULL for tables. Points to definition if a view. */ + u8 readOnly; /* True if this table should not be written by the user */ + u8 iDb; /* Index into sqlite.aDb[] of the backend for this table */ + u8 isTransient; /* True if automatically deleted when VDBE finishes */ + u8 hasPrimKey; /* True if there exists a primary key */ + u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ + Trigger *pTrigger; /* List of SQL triggers on this table */ + FKey *pFKey; /* Linked list of all foreign keys in this table */ + char *zColAff; /* String defining the affinity of each column */ +}; + +/* +** Each foreign key constraint is an instance of the following structure. +** +** A foreign key is associated with two tables. The "from" table is +** the table that contains the REFERENCES clause that creates the foreign +** key. The "to" table is the table that is named in the REFERENCES clause. +** Consider this example: +** +** CREATE TABLE ex1( +** a INTEGER PRIMARY KEY, +** b INTEGER CONSTRAINT fk1 REFERENCES ex2(x) +** ); +** +** For foreign key "fk1", the from-table is "ex1" and the to-table is "ex2". +** +** Each REFERENCES clause generates an instance of the following structure +** which is attached to the from-table. The to-table need not exist when +** the from-table is created. The existance of the to-table is not checked +** until an attempt is made to insert data into the from-table. +** +** The sqlite.aFKey hash table stores pointers to this structure +** given the name of a to-table. For each to-table, all foreign keys +** associated with that table are on a linked list using the FKey.pNextTo +** field. +*/ +struct FKey { + Table *pFrom; /* The table that constains the REFERENCES clause */ + FKey *pNextFrom; /* Next foreign key in pFrom */ + char *zTo; /* Name of table that the key points to */ + FKey *pNextTo; /* Next foreign key that points to zTo */ + int nCol; /* Number of columns in this key */ + struct sColMap { /* Mapping of columns in pFrom to columns in zTo */ + int iFrom; /* Index of column in pFrom */ + char *zCol; /* Name of column in zTo. If 0 use PRIMARY KEY */ + } *aCol; /* One entry for each of nCol column s */ + u8 isDeferred; /* True if constraint checking is deferred till COMMIT */ + u8 updateConf; /* How to resolve conflicts that occur on UPDATE */ + u8 deleteConf; /* How to resolve conflicts that occur on DELETE */ + u8 insertConf; /* How to resolve conflicts that occur on INSERT */ +}; + +/* +** SQLite supports many different ways to resolve a contraint +** error. ROLLBACK processing means that a constraint violation +** causes the operation in process to fail and for the current transaction +** to be rolled back. ABORT processing means the operation in process +** fails and any prior changes from that one operation are backed out, +** but the transaction is not rolled back. FAIL processing means that +** the operation in progress stops and returns an error code. But prior +** changes due to the same operation are not backed out and no rollback +** occurs. IGNORE means that the particular row that caused the constraint +** error is not inserted or updated. Processing continues and no error +** is returned. REPLACE means that preexisting database rows that caused +** a UNIQUE constraint violation are removed so that the new insert or +** update can proceed. Processing continues and no error is reported. +** +** RESTRICT, SETNULL, and CASCADE actions apply only to foreign keys. +** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the +** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign +** key is set to NULL. CASCADE means that a DELETE or UPDATE of the +** referenced table row is propagated into the row that holds the +** foreign key. +** +** The following symbolic values are used to record which type +** of action to take. +*/ +#define OE_None 0 /* There is no constraint to check */ +#define OE_Rollback 1 /* Fail the operation and rollback the transaction */ +#define OE_Abort 2 /* Back out changes but do no rollback transaction */ +#define OE_Fail 3 /* Stop the operation but leave all prior changes */ +#define OE_Ignore 4 /* Ignore the error. Do not do the INSERT or UPDATE */ +#define OE_Replace 5 /* Delete existing record, then do INSERT or UPDATE */ + +#define OE_Restrict 6 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */ +#define OE_SetNull 7 /* Set the foreign key value to NULL */ +#define OE_SetDflt 8 /* Set the foreign key value to its default */ +#define OE_Cascade 9 /* Cascade the changes */ + +#define OE_Default 99 /* Do whatever the default action is */ + + +/* +** An instance of the following structure is passed as the first +** argument to sqlite3VdbeKeyCompare and is used to control the +** comparison of the two index keys. +** +** If the KeyInfo.incrKey value is true and the comparison would +** otherwise be equal, then return a result as if the second key larger. +*/ +struct KeyInfo { + u8 enc; /* Text encoding - one of the TEXT_Utf* values */ + u8 incrKey; /* Increase 2nd key by epsilon before comparison */ + int nField; /* Number of entries in aColl[] */ + u8 *aSortOrder; /* If defined an aSortOrder[i] is true, sort DESC */ + CollSeq *aColl[1]; /* Collating sequence for each term of the key */ +}; + +/* +** Each SQL index is represented in memory by an +** instance of the following structure. +** +** The columns of the table that are to be indexed are described +** by the aiColumn[] field of this structure. For example, suppose +** we have the following table and index: +** +** CREATE TABLE Ex1(c1 int, c2 int, c3 text); +** CREATE INDEX Ex2 ON Ex1(c3,c1); +** +** In the Table structure describing Ex1, nCol==3 because there are +** three columns in the table. In the Index structure describing +** Ex2, nColumn==2 since 2 of the 3 columns of Ex1 are indexed. +** The value of aiColumn is {2, 0}. aiColumn[0]==2 because the +** first column to be indexed (c3) has an index of 2 in Ex1.aCol[]. +** The second column to be indexed (c1) has an index of 0 in +** Ex1.aCol[], hence Ex2.aiColumn[1]==0. +** +** The Index.onError field determines whether or not the indexed columns +** must be unique and what to do if they are not. When Index.onError=OE_None, +** it means this is not a unique index. Otherwise it is a unique index +** and the value of Index.onError indicate the which conflict resolution +** algorithm to employ whenever an attempt is made to insert a non-unique +** element. +*/ +struct Index { + char *zName; /* Name of this index */ + int nColumn; /* Number of columns in the table used by this index */ + int *aiColumn; /* Which columns are used by this index. 1st is 0 */ + Table *pTable; /* The SQL table being indexed */ + int tnum; /* Page containing root of this index in database file */ + u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ + u8 autoIndex; /* True if is automatically created (ex: by UNIQUE) */ + u8 iDb; /* Index in sqlite.aDb[] of where this index is stored */ + char *zColAff; /* String defining the affinity of each column */ + Index *pNext; /* The next index associated with the same table */ + KeyInfo keyInfo; /* Info on how to order keys. MUST BE LAST */ +}; + +/* +** Each token coming out of the lexer is an instance of +** this structure. Tokens are also used as part of an expression. +** +** Note if Token.z==0 then Token.dyn and Token.n are undefined and +** may contain random values. Do not make any assuptions about Token.dyn +** and Token.n when Token.z==0. +*/ +struct Token { + const unsigned char *z; /* Text of the token. Not NULL-terminated! */ + unsigned dyn : 1; /* True for malloced memory, false for static */ + unsigned n : 31; /* Number of characters in this token */ +}; + +/* +** Each node of an expression in the parse tree is an instance +** of this structure. +** +** Expr.op is the opcode. The integer parser token codes are reused +** as opcodes here. For example, the parser defines TK_GE to be an integer +** code representing the ">=" operator. This same integer code is reused +** to represent the greater-than-or-equal-to operator in the expression +** tree. +** +** Expr.pRight and Expr.pLeft are subexpressions. Expr.pList is a list +** of argument if the expression is a function. +** +** Expr.token is the operator token for this node. For some expressions +** that have subexpressions, Expr.token can be the complete text that gave +** rise to the Expr. In the latter case, the token is marked as being +** a compound token. +** +** An expression of the form ID or ID.ID refers to a column in a table. +** For such expressions, Expr.op is set to TK_COLUMN and Expr.iTable is +** the integer cursor number of a VDBE cursor pointing to that table and +** Expr.iColumn is the column number for the specific column. If the +** expression is used as a result in an aggregate SELECT, then the +** value is also stored in the Expr.iAgg column in the aggregate so that +** it can be accessed after all aggregates are computed. +** +** If the expression is a function, the Expr.iTable is an integer code +** representing which function. If the expression is an unbound variable +** marker (a question mark character '?' in the original SQL) then the +** Expr.iTable holds the index number for that variable. +** +** The Expr.pSelect field points to a SELECT statement. The SELECT might +** be the right operand of an IN operator. Or, if a scalar SELECT appears +** in an expression the opcode is TK_SELECT and Expr.pSelect is the only +** operand. +*/ +struct Expr { + u8 op; /* Operation performed by this node */ + char affinity; /* The affinity of the column or 0 if not a column */ + u8 iDb; /* Database referenced by this expression */ + u8 flags; /* Various flags. See below */ + CollSeq *pColl; /* The collation type of the column or 0 */ + Expr *pLeft, *pRight; /* Left and right subnodes */ + ExprList *pList; /* A list of expressions used as function arguments + ** or in " IN (useAgg==TRUE, pull + ** result from the iAgg-th element of the aggregator */ + Select *pSelect; /* When the expression is a sub-select. Also the + ** right side of " IN (
    ")); + + bool today; + + int totalTime = 0; // this is in seconds + int totalAwayTime = 0; // this is in seconds + int totalOnlineTime = 0; // this is in seconds + int totalOfflineTime = 0; // idem + + int hours[24]; // in seconds, too + int iMaxHours = 0; + int hoursOnline[24]; // this is in seconds + int iMaxHoursOnline = 0; + int hoursAway[24]; // this is in seconds + int iMaxHoursAway = 0; + int hoursOffline[24]; // this is in seconds. Hours where we are sure contact is offline + int iMaxHoursOffline = 0; + + for (uint i=0; i<24; i++) + { + hours[i] = 0; + hoursOnline[i] = 0; + hoursAway[i] = 0; + hoursOffline[i] = 0; + } + + for (uint i=0; i").arg(color, status, dateTime1.time().toString(), dateTime2.time().toString())); + + } + + // We add a listview item to the log list + // QDateTime listViewDT1, listViewDT2; + // listViewDT1.setTime_t(values[i+1].toInt()); + // listViewDT2.setTime_t(values[i+2].toInt()); + // new KListViewItem(mainWidget->listView, values[i], values[i+1], values[i+2], listViewDT1.toString(), listViewDT2.toString()); + } + + + todayString.append("
    StatusFromTo
    %2%3%4
    "); + + // Get the max from the hours* + for (uint i=1; i<24; i++) + { + if (hours[iMaxHours] < hours[i]) + iMaxHours = i; + if (hoursOnline[iMaxHoursOnline] < hoursOnline[i]) + iMaxHoursOnline = i; + if (hoursOffline[iMaxHoursOffline] < hoursOffline[i]) + iMaxHoursOffline = i; + if (hoursAway[iMaxHoursAway] < hoursAway[i]) + iMaxHoursAway = i; + } + + // + + /* + * Here we really generate the page + */ + // Some "total times" + generalHTMLPart->write(i18n("
    ")); + generalHTMLPart->write(i18n("" + "Total seen time : %2 hour(s)
    ").arg(m_contact->metaContact()->displayName()).arg(stringFromSeconds(totalTime))); + generalHTMLPart->write(i18n("" + "Total online time : %2 hour(s)
    ").arg(m_contact->metaContact()->displayName()).arg(stringFromSeconds(totalOnlineTime))); + generalHTMLPart->write(i18n("Total busy time : %2 hour(s)
    ").arg(m_contact->metaContact()->displayName()).arg(stringFromSeconds(totalAwayTime))); + generalHTMLPart->write(i18n("Total offline time : %2 hour(s)").arg(m_contact->metaContact()->displayName()).arg(stringFromSeconds(totalOfflineTime))); + generalHTMLPart->write(QString("
    ")); + + if (subTitle == i18n("General information")) + /* + * General stats that should not be shown on "day" or "month" pages + */ + { + generalHTMLPart->write(QString("
    ")); + generalHTMLPart->write(i18n("Average message length : %1 characters
    ").arg(m_contact->messageLength())); + generalHTMLPart->write(i18n("Time between two messages : %1 second(s)").arg(m_contact->timeBetweenTwoMessages())); + generalHTMLPart->write(QString("
    ")); + + generalHTMLPart->write(QString("
    ")); + generalHTMLPart->write(i18n("Last talk : %2
    ").arg(m_contact->metaContact()->displayName()).arg(KGlobal::locale()->formatDateTime(m_contact->lastTalk()))); + generalHTMLPart->write(i18n("Last time contact was present : %2").arg(m_contact->metaContact()->displayName()).arg(KGlobal::locale()->formatDateTime(m_contact->lastPresent()))); + generalHTMLPart->write(QString("
    ")); + + //generalHTMLPart->write(QString("
    ")); + //generalHTMLPart->write(i18n("Main online events :
    ").arg(m_contact->metaContact()->displayName())); + //QValueList mainEvents = m_contact->mainEvents(Kopete::OnlineStatus::Online); + //for (uint i=0; iwrite(QString("%1
    ").arg(mainEvents[i].toString())); + //generalHTMLPart->write(QString("
    ")); + + generalHTMLPart->write("
    "); + generalHTMLPart->write(i18n("Is %1 since %2").arg( + Kopete::OnlineStatus(m_contact->oldStatus()).description(), + KGlobal::locale()->formatDateTime(m_contact->oldStatusDateTime()))); + generalHTMLPart->write(QString("
    ")); + } + + /* + * Chart which show the hours where plugin has seen this contact online + */ + generalHTMLPart->write(QString("
    ")); + generalHTMLPart->write(QString("")); + generalHTMLPart->write(QString("")); + + + + generalHTMLPart->write(QString( "" + "" + "" + "
    ") + i18n("When have I seen this contact ?") + QString("
    ")); + + QString chartString; + QString colorPath = ::locate("appdata", "pics/statistics/black.png"); + for (uint i=0; i<24; i++) + { + + int hrWidth = qRound((double)hours[i]/(double)hours[iMaxHours]*100.); + chartString += QString("metaContact()->displayName()).arg(hrWidth) + +QString("\">"); + } + generalHTMLPart->write(chartString); + generalHTMLPart->write(QString("
    ")+i18n("Online time")+QString("")+i18n("Away time")+QString("")+i18n("Offline time")+QString("
    ")); + + + generalHTMLPart->write(generateHTMLChart(hoursOnline, hoursAway, hoursOffline, i18n("online"), "blue")); + generalHTMLPart->write(QString("")); + generalHTMLPart->write(generateHTMLChart(hoursAway, hoursOnline, hoursOffline, i18n("away"), "navy")); + generalHTMLPart->write(QString("")); + generalHTMLPart->write(generateHTMLChart(hoursOffline, hoursAway, hoursOnline, i18n("offline"), "gray")); + generalHTMLPart->write(QString("
    ")); + + if (subTitle == i18n("General information")) + /* On main page, show the different status of the contact today + */ + { + generalHTMLPart->write(QString(todayString)); + } + generalHTMLPart->write(QString("")); + + generalHTMLPart->end(); + +} + +void StatisticsDialog::generatePageGeneral() +{ + QStringList values; + values = m_db->query(QString("SELECT status, datetimebegin, datetimeend " + "FROM contactstatus WHERE metacontactid LIKE '%1' ORDER BY datetimebegin;") + .arg(m_contact->statisticsContactId())); + generatePageFromQStringList(values, i18n("General information")); +} + +QString StatisticsDialog::generateHTMLChart(const int *hours, const int *hours2, const int *hours3, const QString & caption, const QString & color) +{ + QString chartString; + + QString colorPath = ::locate("appdata", "pics/statistics/"+color+".png"); + + + for (uint i=0; i<24; i++) + { + int totalTime = hours[i] + hours2[i] + hours3[i]; + + int hrWidth = qRound((double)hours[i]/(double)totalTime*100.); + chartString += QString("metaContact()->displayName()). + arg(hrWidth). + arg(caption) + +".\">"; + } + return chartString; +} + +QString StatisticsDialog::stringFromSeconds(const int seconds) +{ + int h, m, s; + h = seconds/3600; + m = (seconds % 3600)/60; + s = (seconds % 3600) % 60; + return QString::number(h)+":"+QString::number(m)+":"+QString::number(s); +} + +void StatisticsDialog::slotAskButtonClicked() +{ + if (mainWidget->questionComboBox->currentItem()==0) + { + QString text = i18n("1 is date, 2 is contact name, 3 is online status", "%1, %2 was %3") + .arg(KGlobal::locale()->formatDateTime(QDateTime(mainWidget->datePicker->date(), mainWidget->timePicker->time()))) + .arg(m_contact->metaContact()->displayName()) + .arg(m_contact->statusAt(QDateTime(mainWidget->datePicker->date(), mainWidget->timePicker->time()))); + mainWidget->answerEdit->setText(text); + } + else if (mainWidget->questionComboBox->currentItem()==1) + { + mainWidget->answerEdit->setText(m_contact->mainStatusDate(mainWidget->datePicker->date())); + } + else if (mainWidget->questionComboBox->currentItem()==2) + // Next online + { + + } +} + +#include "statisticsdialog.moc" diff --git a/kopete/plugins/statistics/statisticsdialog.h b/kopete/plugins/statistics/statisticsdialog.h new file mode 100644 index 00000000..32a5aaaf --- /dev/null +++ b/kopete/plugins/statistics/statisticsdialog.h @@ -0,0 +1,81 @@ +/* + statisticsdialog.h - Kopete History Dialog + + Copyright (c) 2003-2004 by Marc Cramdal + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef _STATISTICSDIALOG_H +#define _STATISTICSDIALOG_H + +#include +#include +#include "kopetemetacontact.h" + +class QCanvasView; +class QCanvas; +class QStringList; + +class StatisticsWidget; +class StatisticsPlugin; +class StatisticsDB; +class StatisticsContact; + +class KHTMLPart; +class KURL; +namespace KParts +{ + class URLArgs; +} + +class StatisticsDialog : public KDialogBase +{ + Q_OBJECT + public: + StatisticsDialog(StatisticsContact *contact, StatisticsDB* db, QWidget* parent=0, + const char* name="StatisticsDialog"); + private: + QString generateHTMLChart(const int *hours, const int *hours2, const int *hours3, const QString & caption, const QString & color); + QString generateHTMLChartBar(int height, const QString & color, const QString & caption); + QString stringFromSeconds(const int seconds); + + StatisticsWidget *mainWidget; + KHTMLPart *generalHTMLPart; + + /// Database from which we get the statistics + StatisticsDB *m_db; + /// Metacontact for which we get the statistics from m_db + StatisticsContact *m_contact; + + void generatePageFromQStringList(QStringList values, const QString & subTitle); + + /// Generates the main page + void generatePageGeneral(); + /** + * @brief Generates the page for a given day of the week. + * \param dayOfWeek Monday..Sunday, 0..7 + */ + void generatePageForDay(const int dayOfWeek); + void generatePageForMonth(const int monthOfYear); + + +private slots: + /** + * We manage the openURLRequestDelayed signal from the generalHTMLPart->browserExtension() in order to + * generate requested pages on the flow. + */ + void slotOpenURLRequest(const KURL& url, const KParts::URLArgs&); + void slotAskButtonClicked(); + +}; + + +#endif // _STATISTICSDIALOG_H diff --git a/kopete/plugins/statistics/statisticsplugin.cpp b/kopete/plugins/statistics/statisticsplugin.cpp new file mode 100644 index 00000000..f0d190b3 --- /dev/null +++ b/kopete/plugins/statistics/statisticsplugin.cpp @@ -0,0 +1,283 @@ +/* + statisticsplugin.cpp + + Copyright (c) 2003-2004 by Marc Cramdal + + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "kopetechatsessionmanager.h" +#include "kopetemetacontact.h" +#include "kopeteview.h" +#include "kopetecontactlist.h" +#include "kopeteuiglobal.h" +#include "kopetemessageevent.h" +#include "kopeteonlinestatus.h" +#include "kopeteaccountmanager.h" +#include "kopeteaccount.h" + +#include "statisticscontact.h" +#include "statisticsdialog.h" +#include "statisticsplugin.h" +#include "statisticsdb.h" + +typedef KGenericFactory StatisticsPluginFactory; + + +static const KAboutData aboutdata("kopete_statistics", I18N_NOOP("Statistics") , "0.1" ); +K_EXPORT_COMPONENT_FACTORY( kopete_statistics, StatisticsPluginFactory( &aboutdata ) ) + +StatisticsPlugin::StatisticsPlugin( QObject *parent, const char *name, const QStringList &) + : DCOPObject("StatisticsDCOPIface"), + Kopete::Plugin( StatisticsPluginFactory::instance(), parent, name ) + + +{ + KAction *viewMetaContactStatistics = new KAction( i18n("View &Statistics" ), + QString::fromLatin1( "log" ), 0, this, SLOT(slotViewStatistics()), + actionCollection(), "viewMetaContactStatistics" ); + viewMetaContactStatistics->setEnabled(Kopete::ContactList::self()->selectedMetaContacts().count() == 1); + + connect(Kopete::ChatSessionManager::self(),SIGNAL(chatSessionCreated(Kopete::ChatSession*)), + this, SLOT(slotViewCreated(Kopete::ChatSession*))); + connect(Kopete::ChatSessionManager::self(),SIGNAL(aboutToReceive(Kopete::Message&)), + this, SLOT(slotAboutToReceive(Kopete::Message&))); + + connect(Kopete::ContactList::self(), SIGNAL(metaContactSelected(bool)), + viewMetaContactStatistics, SLOT(setEnabled(bool))); + connect(Kopete::ContactList::self(), SIGNAL(metaContactAdded(Kopete::MetaContact*)), + this, SLOT(slotMetaContactAdded(Kopete::MetaContact*))); + connect(Kopete::ContactList::self(), SIGNAL(metaContactRemoved(Kopete::MetaContact*)), + this, SLOT(slotMetaContactRemoved(Kopete::MetaContact*))); + + setXMLFile("statisticsui.rc"); + + /* Initialization reads the database, so it could be a bit time-consuming + due to disk access. This should overcome the problem and makes it non-blocking. */ + QTimer::singleShot(0, this, SLOT(slotInitialize())); +} + +void StatisticsPlugin::slotInitialize() +{ + // Initializes the database + m_db = new StatisticsDB(); + + QPtrList list = Kopete::ContactList::self()->metaContacts(); + QPtrListIterator it( list ); + for (; it.current(); ++it) + { + slotMetaContactAdded(it.current()); + } +} + +StatisticsPlugin::~StatisticsPlugin() +{ + QMap::Iterator it; + for ( it = statisticsMetaContactMap.begin(); it != statisticsMetaContactMap.end(); ++it ) + { + delete it.data(); + } + delete m_db; +} + +void StatisticsPlugin::slotAboutToReceive(Kopete::Message& m) +{ + if ( statisticsMetaContactMap.contains(m.from()->metaContact()) ) + statisticsMetaContactMap[m.from()->metaContact()]->newMessageReceived(m); +} + +void StatisticsPlugin::slotViewCreated(Kopete::ChatSession* session) +{ + connect(session, SIGNAL(closing(Kopete::ChatSession*)), this, SLOT(slotViewClosed(Kopete::ChatSession*))); +} + +void StatisticsPlugin::slotViewClosed(Kopete::ChatSession* session) +{ + QPtrList list = session->members(); + QPtrListIterator it( list ); + + for (; it.current(); ++it) + { + // If this contact is not in other chat sessions + if (!it.current()->manager() && statisticsMetaContactMap.contains(it.current()->metaContact())) + statisticsMetaContactMap[it.current()->metaContact()]->setIsChatWindowOpen(false); + } +} + +void StatisticsPlugin::slotViewStatistics() +{ + Kopete::MetaContact *mc=Kopete::ContactList::self()->selectedMetaContacts().first(); + + kdDebug() << k_funcinfo << "statistics - dialog :"+ mc->displayName() << endl; + + if ( mc && statisticsMetaContactMap.contains(mc) ) + { + (new StatisticsDialog(statisticsMetaContactMap[mc], db()))->show(); + } +} + +void StatisticsPlugin::slotOnlineStatusChanged(Kopete::MetaContact *mc, Kopete::OnlineStatus::StatusType status) +{ + if ( statisticsMetaContactMap.contains(mc) ) + statisticsMetaContactMap[mc]->onlineStatusChanged(status); +} + +void StatisticsPlugin::slotMetaContactAdded(Kopete::MetaContact *mc) +{ + statisticsMetaContactMap[mc] = new StatisticsContact(mc, db()); + + QPtrList clist = mc->contacts(); + Kopete::Contact *contact; + + // we need to call slotContactAdded if MetaContact allready have contacts + for ( contact = clist.first(); contact; contact = clist.next() ) + { + this->slotContactAdded(contact); + } + + connect(mc, SIGNAL(onlineStatusChanged( Kopete::MetaContact *, Kopete::OnlineStatus::StatusType)), this, + SLOT(slotOnlineStatusChanged(Kopete::MetaContact*, Kopete::OnlineStatus::StatusType))); + connect(mc, SIGNAL(contactAdded( Kopete::Contact *)), this, + SLOT(slotContactAdded( Kopete::Contact *))); + connect(mc, SIGNAL(contactRemoved( Kopete::Contact *)), this, + SLOT(slotContactRemoved( Kopete::Contact *))); +} + +void StatisticsPlugin::slotMetaContactRemoved(Kopete::MetaContact *mc) +{ + if (statisticsMetaContactMap.contains(mc)) + { + StatisticsContact *sc = statisticsMetaContactMap[mc]; + statisticsMetaContactMap.remove(mc); + sc->removeFromDB(); + delete sc; + } +} + +void StatisticsPlugin::slotContactAdded( Kopete::Contact *c) +{ + if (statisticsMetaContactMap.contains(c->metaContact())) + { + StatisticsContact *sc = statisticsMetaContactMap[c->metaContact()]; + sc->contactAdded(c); + statisticsContactMap[c->contactId()] = sc; + } +} + +void StatisticsPlugin::slotContactRemoved( Kopete::Contact *c) +{ + if (statisticsMetaContactMap.contains(c->metaContact())) + statisticsMetaContactMap[c->metaContact()]->contactRemoved(c); + + statisticsContactMap.remove(c->contactId()); +} + +void StatisticsPlugin::dcopStatisticsDialog(QString id) +{ + kdDebug() << k_funcinfo << "statistics - DCOP dialog :" << id << endl; + + if (statisticsContactMap.contains(id)) + { + (new StatisticsDialog(statisticsContactMap[id], db()))->show(); + } +} + +bool StatisticsPlugin::dcopWasOnline(QString id, int timeStamp) +{ + QDateTime dt; + dt.setTime_t(timeStamp); + return dcopWasStatus(id, dt, Kopete::OnlineStatus::Online); +} + +bool StatisticsPlugin::dcopWasOnline(QString id, QString dateTime) +{ + return dcopWasStatus(id, QDateTime::fromString(dateTime), Kopete::OnlineStatus::Online); +} + +bool StatisticsPlugin::dcopWasAway(QString id, int timeStamp) +{ + QDateTime dt; + dt.setTime_t(timeStamp); + return dcopWasStatus(id, dt, Kopete::OnlineStatus::Away); +} + +bool StatisticsPlugin::dcopWasAway(QString id, QString dateTime) +{ + return dcopWasStatus(id, QDateTime::fromString(dateTime), Kopete::OnlineStatus::Away); +} + +bool StatisticsPlugin::dcopWasOffline(QString id, int timeStamp) +{ + QDateTime dt; + dt.setTime_t(timeStamp); + return dcopWasStatus(id, dt, Kopete::OnlineStatus::Offline); +} + +bool StatisticsPlugin::dcopWasOffline(QString id, QString dateTime) +{ + return dcopWasStatus(id, QDateTime::fromString(dateTime), Kopete::OnlineStatus::Offline); +} + +bool StatisticsPlugin::dcopWasStatus(QString id, QDateTime dateTime, Kopete::OnlineStatus::StatusType status) +{ + kdDebug() << k_funcinfo << "statistics - DCOP wasOnline :" << id << endl; + + if (dateTime.isValid() && statisticsContactMap.contains(id)) + { + return statisticsContactMap[id]->wasStatus(dateTime, status); + } + + return false; +} + +QString StatisticsPlugin::dcopStatus(QString id, int timeStamp) +{ + QDateTime dt; + dt.setTime_t(timeStamp); + return dcopStatus(id, dt.toString()); + +} + +QString StatisticsPlugin::dcopStatus(QString id, QString dateTime) +{ + QDateTime dt = QDateTime::fromString(dateTime); + + if (dt.isValid() && statisticsContactMap.contains(id)) + { + return statisticsContactMap[id]->statusAt(dt); + } + + return ""; +} + +QString StatisticsPlugin::dcopMainStatus(QString id, int timeStamp) +{ + QDateTime dt; + dt.setTime_t(timeStamp); + if (dt.isValid() && statisticsContactMap.contains(id)) + { + return statisticsContactMap[id]->mainStatusDate(dt.date()); + } + + return ""; +} +#include "statisticsplugin.moc" diff --git a/kopete/plugins/statistics/statisticsplugin.h b/kopete/plugins/statistics/statisticsplugin.h new file mode 100644 index 00000000..d757b424 --- /dev/null +++ b/kopete/plugins/statistics/statisticsplugin.h @@ -0,0 +1,213 @@ +/* + statisticsplugin.h + + Copyright (c) 2003-2004 by Marc Cramdal + + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef STATISTICSPLUGIN_H +#define STATISTICSPLUGIN_H + +#include +#include +#include +#include + +#include + +#include "kopeteplugin.h" + +#include "kopetemessage.h" +#include "kopetemessagehandler.h" +#include "kopeteonlinestatus.h" + +#include "statisticsdcopiface.h" + +class QString; + +class StatisticsDB; +class StatisticsContact; +class StatisticsDCOPIface; + +class KopeteView; +class KActionCollection; + +/** \section Kopete Statistics Plugin + * + * \subsection intro_sec Introduction + * + * This plugin aims at giving detailed statistics on metacontacts, for instance, how long was + * the metacontact online, how long was it busy etc. + * In the future, it will maybe make prediction on when the contact should be available for chat. + * + * \subsection install_sec How it works ... + * Each Metacontact is bound to a StatisticsContact which has access to the SQLITE database. + * This StatisticsContact stores the last status of the metacontact; the member function onlineStatusChanged is called when the + * metacontact status changed (this is managed in the slot slotOnlineStatusChanged of StatisticsPlugin) and then the DB is + * updated for the contact. + * + * More exactly the DB is updated only if the oldstatus was not Offline + + * To have an idea how it works, here is a table : + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    EventChanges to databaseoldStatus
    John 17:44 Away (connexion) - (oldstatus was offline)oldstatus = away
    John 18:01 Online(+) Away 17:44 18:01oldstatus = online
    John 18:30 Offline (disconnect)(+) Online 18:01 18:30oldstatus = offline
    John 18:45 Online (connexion) - (oldstatus was offline)oldstatus = online
    John 20:30 Offline (disconnect)(+) Online 18:45 20:30oldstatus = offline
    + * + * etc. + * + * \subsection install_sec Some little stats + * This plugin is able to record some other stats, not based on events. Theyre saved in the commonstat table in which we store stats + * like this : + * + * statname, statvalue1, statvalue2 + * + * Generally, we store the value, and its ponderation. If an average on one hundred messages says that the contact X takes about + * 3 seconds between two messages, we store "timebetweentwomessages", "3", "100" + * + * + * + * StatisticsPlugin is the main Statistics plugin class. + * Contains mainly slots. + */ +class StatisticsPlugin : public Kopete::Plugin, virtual public StatisticsDCOPIface +{ + Q_OBJECT +public: + /// Standard plugin constructors + StatisticsPlugin(QObject *parent, const char *name, const QStringList &args); + ~StatisticsPlugin(); + + /// Method to access m_db member + StatisticsDB *db() { return m_db; } +private slots: + // Do the initializations + void slotInitialize(); + +public slots: + + /** \brief This slot is called when the status of a contact changed. + * + * Then it searches for the contact bind to the metacontact who triggered the signal and calls + * the specific StatisticsContact::onlineStatusChanged of the StatisticsContact to update the StatisticsContact status, + * and maybe, update the database. + */ + void slotOnlineStatusChanged(Kopete::MetaContact *contact, Kopete::OnlineStatus::StatusType status ); + + /** + * Builds and show the StatisticsDialog for a contact + */ + void slotViewStatistics(); + + /** + * + * Extract the metaContactId from the message, and calls the + * StatisticsContact::newMessageReceived(Kopete::Message& m) function + * for the corresponding contact + */ + void slotAboutToReceive(Kopete::Message& m); + + /* + * Managing views + */ + + /** + * \brief Only connects the Kopete::ChatSession::closing() signal to our slotViewClosed(). + */ + void slotViewCreated(Kopete::ChatSession* session); + + /** + * One aim of this slot is to be able to stop recording time between two messages + * for the contact in the chatsession. But, we only + * want to do this if the contact is not in an other chatsession. + */ + void slotViewClosed(Kopete::ChatSession* session); + + /** + * Slot called when a new metacontact is added to make some slots connections and to create a new + * StatisticsContact object. + * + * In the constructor, we connect the metacontacts already existing to some slots, but we need to do this + * when new metacontacts are added. + * This function is also called when we loop over the contact list in the constructor. + */ + void slotMetaContactAdded(Kopete::MetaContact *mc); + + /** + * Slot called when a metacontact is removed to delete statistic data from db and to remove StatisticsContact object. + */ + void slotMetaContactRemoved(Kopete::MetaContact *mc); + + /** + * Slot called when a contact is added to metacontact. + */ + void slotContactAdded(Kopete::Contact *c); + + /** + * Slot called when a contact is removed from metacontact. + */ + void slotContactRemoved(Kopete::Contact *c); + + + /* + * DCOP functions + * See statisticsdcopiface.h for the documentation + */ + void dcopStatisticsDialog(QString id); + + bool dcopWasOnline(QString id, int timeStamp); + bool dcopWasOnline(QString id, QString dt); + + bool dcopWasAway(QString id, int timeStamp); + bool dcopWasAway(QString id, QString dt); + + bool dcopWasOffline(QString id, int timeStamp); + bool dcopWasOffline(QString id, QString dt); + + bool dcopWasStatus(QString id, QDateTime dateTime, Kopete::OnlineStatus::StatusType status); + + QString dcopStatus(QString id, QString dateTime); + QString dcopStatus(QString id, int timeStamp); + + QString dcopMainStatus(QString id, int timeStamp); + +private: + StatisticsDB *m_db; + /** Associate a Kopete::Contact id to a StatisticsContact to retrieve + * the StatisticsContact corresponding to the Kopete::Contact + */ + QMap statisticsContactMap; + /** Associate a Kopete::MetaContact to a StatisticsContact to retrieve + * the StatisticsContact corresponding to the MetaContact + */ + QMap statisticsMetaContactMap; +}; + + +#endif diff --git a/kopete/plugins/statistics/statisticsui.rc b/kopete/plugins/statistics/statisticsui.rc new file mode 100644 index 00000000..79d5898c --- /dev/null +++ b/kopete/plugins/statistics/statisticsui.rc @@ -0,0 +1,12 @@ + + + + + &Edit + + + + + + + diff --git a/kopete/plugins/statistics/statisticswidget.ui b/kopete/plugins/statistics/statisticswidget.ui new file mode 100644 index 00000000..ca866e18 --- /dev/null +++ b/kopete/plugins/statistics/statisticswidget.ui @@ -0,0 +1,246 @@ + +StatisticsWidget + + + StatisticsWidget + + + + 0 + 0 + 586 + 506 + + + + + 2 + 2 + 0 + 0 + + + + + unnamed + + + + tabWidget + + + + TabPage + + + Ask &Database + + + + unnamed + + + + groupBox1 + + + Date && Time + + + + unnamed + + + + layout11 + + + + unnamed + + + + spacer5 + + + Horizontal + + + Expanding + + + + 61 + 31 + + + + + + layout9 + + + + unnamed + + + + datePicker + + + + 5 + 7 + 0 + 0 + + + + + + layout7 + + + + unnamed + + + + textLabel1 + + + Time : + + + + + timePicker + + + + + spacer3 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + + + spacer4 + + + Horizontal + + + Expanding + + + + 60 + 41 + + + + + + + + + + groupBox2 + + + Question + + + + unnamed + + + + layout5 + + + + unnamed + + + + + Contact Status at Date & Time + + + + + Most Used Status at Date + + + + questionComboBox + + + + 7 + 0 + 0 + 0 + + + + + + askButton + + + &Ask + + + + + + + + + groupBox3 + + + Answer + + + + unnamed + + + + answerEdit + + + + + + + + + + + + kdatepicker.h + klineedit.h + kdatetbl.h + ktimewidget.h + + diff --git a/kopete/plugins/texteffect/Makefile.am b/kopete/plugins/texteffect/Makefile.am new file mode 100644 index 00000000..0d657dc5 --- /dev/null +++ b/kopete/plugins/texteffect/Makefile.am @@ -0,0 +1,20 @@ +METASOURCES = AUTO + +SUBDIRS = icons +INCLUDES = $(KOPETE_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kopete_texteffect.la kcm_kopete_texteffect.la + +kopete_texteffect_la_SOURCES = texteffectplugin.cpp texteffectconfig.cpp +kopete_texteffect_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kopete_texteffect_la_LIBADD = ../../libkopete/libkopete.la + +kcm_kopete_texteffect_la_SOURCES = texteffectconfig.cpp texteffectprefs.ui texteffectpreferences.cpp +kcm_kopete_texteffect_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_texteffect_la_LIBADD = ../../libkopete/libkopete.la $(LIB_KUTILS) + +service_DATA = kopete_texteffect.desktop +servicedir = $(kde_servicesdir) + +kcm_DATA = kopete_texteffect_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog diff --git a/kopete/plugins/texteffect/icons/Makefile.am b/kopete/plugins/texteffect/icons/Makefile.am new file mode 100644 index 00000000..224eb420 --- /dev/null +++ b/kopete/plugins/texteffect/icons/Makefile.am @@ -0,0 +1,3 @@ +kopeteicondir = $(kde_datadir)/kopete/icons +kopeteicon_ICON = AUTO + diff --git a/kopete/plugins/texteffect/icons/cr32-app-texteffect.png b/kopete/plugins/texteffect/icons/cr32-app-texteffect.png new file mode 100644 index 00000000..9f44eb65 Binary files /dev/null and b/kopete/plugins/texteffect/icons/cr32-app-texteffect.png differ diff --git a/kopete/plugins/texteffect/kopete_texteffect.desktop b/kopete/plugins/texteffect/kopete_texteffect.desktop new file mode 100644 index 00000000..f929b2f2 --- /dev/null +++ b/kopete/plugins/texteffect/kopete_texteffect.desktop @@ -0,0 +1,131 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=texteffect +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_texteffect +X-KDE-PluginInfo-Author=Olivier Goffart +X-KDE-PluginInfo-Email=ogoffart@tiscalinet.be +X-KDE-PluginInfo-Name=kopete_texteffect +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Text Effect +Name[ar]=التأثير النصي +Name[be]=ТÑкÑтавы Ñфект +Name[bg]=ТекÑтови ефекти +Name[bn]=টেকà§à¦¸à¦Ÿ পà§à¦°à¦­à¦¾à¦¬ +Name[bs]=Tekst efekti +Name[ca]=Efecte de text +Name[cs]=Textový efekt +Name[cy]=Effaith Testun +Name[da]=Teksteffekt +Name[de]=Texteffekte +Name[el]=Εφέ κειμένου +Name[eo]=Tekst-efektoj +Name[es]=Efecto de texto +Name[et]=Tekstiefektid +Name[eu]=Testuaren efektua +Name[fa]=جلوۀ متن +Name[fi]=Tekstitehoste +Name[fr]=Effets de texte +Name[ga]=Maisíocht Téacs +Name[gl]=Efecto de texto +Name[he]=××¤×§×˜×™× ×©×œ טקסט +Name[hi]=पाठ पà¥à¤°à¤­à¤¾à¤µ +Name[hr]=Tekstualni efekti +Name[hu]=Szövegeffektus +Name[is]=Breyta letri +Name[it]=Effetto di testo +Name[ja]=テキスト効果 +Name[ka]=ტექსტის ეფექტები +Name[kk]=Мәтінді безендіру +Name[km]=បែបផែន​អážáŸ’ážáž”ទ​ +Name[lt]=Teksto efektai +Name[mk]=Ефекти за текÑÑ‚ +Name[nb]=Teksteffekt +Name[nds]=Texteffekt +Name[ne]=पाठ पà¥à¤°à¤­à¤¾à¤µ +Name[nl]=Teksteffect +Name[nn]=Teksteffekt +Name[pa]=ਪਾਠ ਪਰਭਾਵ +Name[pl]=Efekty tekstowe +Name[pt]=Efeito de Texto +Name[pt_BR]=Efeito de texto +Name[ro]=Efect text +Name[ru]=ТекÑтовые Ñффекты +Name[se]=Teakstaeffeakta +Name[sk]=Textový efekt +Name[sl]=Besedilni uÄinki +Name[sr]=ТекÑтуални ефекти +Name[sr@Latn]=Tekstualni efekti +Name[sv]=Texteffekter +Name[ta]=செயல௠+Name[tg]=Ðатиҷаҳои Матн +Name[tr]=Metin Efekti +Name[uk]=ТекÑтові ефекти +Name[uz]=Matn effekti +Name[uz@cyrillic]=Матн Ñффекти +Name[zh_CN]=文字特效 +Name[zh_HK]=文字效果 +Name[zh_TW]=文字效果 +Comment=Add nice effects to your messages +Comment[ar]=تضي٠مؤثرات لطيÙØ© لرسائلك +Comment[be]=Дадаць Ñ„Ð°Ð¹Ð½Ñ‹Ñ Ñфекты да вашых паведамленнÑÑž +Comment[bg]=ДобавÑне на текÑтови ефекти към ÑъобщениÑта +Comment[bn]=আপনার বারà§à¦¤à¦¾à¦¤à§‡ সà§à¦¨à§à¦¦à¦° পà§à¦°à¦­à¦¾à¦¬ যোগ করে +Comment[bs]=Dodaj efekte porukama +Comment[ca]=Afegeix bonics efectes als vostres missatges +Comment[cs]=PÅ™idává efekty ke zprávám +Comment[cy]=Ychwanegu effeithiau del i'ch negeseuon +Comment[da]=Tilføj rare effekter til dine beskeder +Comment[de]=Verschönern Sie eigene Nachrichten durch nette Effekte +Comment[el]=ΠÏοσθέτει όμοÏφα εφέ στα μηνÏματά σας +Comment[eo]=Ornami viajn mesaÄojn per belaj efektoj +Comment[es]=Añade efectos agradables a sus mensajes +Comment[et]=Lisab sõnumitele vahvaid efekte +Comment[eu]=Gehitu efektu atseginak zure mezuei +Comment[fa]=اÙزودن جلوه‌های زیبا به پیامهایتان +Comment[fi]=Lisää tehosteita viesteihisi +Comment[fr]=Ajouter des effets sympathiques à vos messages +Comment[ga]=Cuir maisíochtaí deasa le do theachtaireachtaí +Comment[gl]=Engadir efectos agradables ás túas mensaxes +Comment[he]=הוסף ××¤×§×˜×™× × ×—×ž×“×™× ×œ×”×•×“×¢×•×ª×š +Comment[hi]=आपके संदेशों में सà¥à¤‚दर पà¥à¤°à¤­à¤¾à¤µ जोड़े +Comment[hu]=Effektusok hozzáadása az üzenetekhez +Comment[is]=Gera skeytin þín flottari +Comment[it]=Aggiungi effetti carini ai tuoi messaggi +Comment[ja]=メッセージã«åŠ¹æžœã‚’付加 +Comment[ka]=თქვენს შეტყáƒáƒ‘ინებებს áƒáƒ›áƒáƒ¢áƒ”ბს სáƒáƒ¡áƒ˜áƒáƒ›áƒáƒ•áƒœáƒ ეფექტებს +Comment[kk]=Хабарыңызды безендіру Ñ‚Ó™Ñілдері +Comment[km]=បន្ážáŸ‚ម​បែបផែន​ស្រស់ស្អាážâ€‹áž‘ៅ​សារ​របស់​អ្នក +Comment[lt]=PapuoÅ¡kite žinutes gražiais teksto efektais +Comment[mk]=Додадете фини ефекти на вашите пораки +Comment[nb]=Legg til effekter pÃ¥ meldingene dine +Comment[nds]=Dien Narichten smucke Effekten tofögen +Comment[ne]=तपाईà¤à¤•à¥‹ सनà¥à¤¦à¥‡à¤¶à¤®à¤¾ उतà¥à¤¤à¤® पà¥à¤°à¤­à¤¾à¤µ थपà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Voegt leuke effecten to aan uw berichten +Comment[nn]=Legg til effektar pÃ¥ meldingane +Comment[pl]=Dodaje Å‚adne efekty do Twoich wiadomoÅ›ci +Comment[pt]=Adicionar efeitos engraçados às suas mensagens +Comment[pt_BR]=Adiciona um efeito às suas mensagens +Comment[ro]=Adaucă efecte drăguÅ£e la mesajele dumneavoastră +Comment[ru]=Добавить Ñффекты к вашим ÑообщениÑм +Comment[se]=Lasit fiinna effeavttaid du dieđáhusaide +Comment[sk]=Pridá pekné efekty k vaÅ¡im správam +Comment[sl]=Doda lepe uÄinke vaÅ¡im sporoÄilom +Comment[sr]=Додаје лепе ефекте вашим порукама +Comment[sr@Latn]=Dodaje lepe efekte vaÅ¡im porukama +Comment[sv]=Lägg till trevliga effekter i dina meddelanden +Comment[ta]=இனிய விளைவà¯à®•à®³à¯ˆ உஙà¯à®•à®³à¯à®•à¯à®•à¯ சேரà¯à®•à¯à®•à¯à®®à¯ +Comment[tg]=Ðатиҷаҳои хубро ба пайёмҳои шумо ҳамроҳ мекунад +Comment[tr]=Mesajlarınıza hoÅŸ efektler ekleyin +Comment[uk]=Додати ефекти до ваших повідомлень +Comment[uz]=Xabarlarga chiroyli effektlarni qoÊ»shish +Comment[uz@cyrillic]=Хабарларга чиройли Ñффектларни қўшиш +Comment[zh_CN]=在您的消æ¯ä¸­æ·»åŠ æ–‡å­—特效 +Comment[zh_HK]=為您的訊æ¯å¢žåŠ æœ‰è¶£çš„效果 +Comment[zh_TW]=在您的訊æ¯ä¸­åŠ å…¥ä¸€äº›æ•ˆæžœ diff --git a/kopete/plugins/texteffect/kopete_texteffect_config.desktop b/kopete/plugins/texteffect/kopete_texteffect_config.desktop new file mode 100644 index 00000000..06678e51 --- /dev/null +++ b/kopete/plugins/texteffect/kopete_texteffect_config.desktop @@ -0,0 +1,126 @@ +[Desktop Entry] +Icon=texteffect +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_texteffect +X-KDE-FactoryName=TextEffectConfigFactory +X-KDE-ParentApp=kopete_texteffect +X-KDE-ParentComponents=kopete_texteffect + +Name=Text Effect +Name[ar]=التأثير النصي +Name[be]=ТÑкÑтавы Ñфект +Name[bg]=ТекÑтови ефекти +Name[bn]=টেকà§à¦¸à¦Ÿ পà§à¦°à¦­à¦¾à¦¬ +Name[bs]=Tekst efekti +Name[ca]=Efecte de text +Name[cs]=Textový efekt +Name[cy]=Effaith Testun +Name[da]=Teksteffekt +Name[de]=Texteffekte +Name[el]=Εφέ κειμένου +Name[eo]=Tekst-efektoj +Name[es]=Efecto de texto +Name[et]=Tekstiefektid +Name[eu]=Testuaren efektua +Name[fa]=جلوۀ متن +Name[fi]=Tekstitehoste +Name[fr]=Effets de texte +Name[ga]=Maisíocht Téacs +Name[gl]=Efecto de texto +Name[he]=××¤×§×˜×™× ×©×œ טקסט +Name[hi]=पाठ पà¥à¤°à¤­à¤¾à¤µ +Name[hr]=Tekstualni efekti +Name[hu]=Szövegeffektus +Name[is]=Breyta letri +Name[it]=Effetto di testo +Name[ja]=テキスト効果 +Name[ka]=ტექსტის ეფექტები +Name[kk]=Мәтінді безендіру +Name[km]=បែបផែន​អážáŸ’ážáž”ទ​ +Name[lt]=Teksto efektai +Name[mk]=Ефекти за текÑÑ‚ +Name[nb]=Teksteffekt +Name[nds]=Texteffekt +Name[ne]=पाठ पà¥à¤°à¤­à¤¾à¤µ +Name[nl]=Teksteffect +Name[nn]=Teksteffekt +Name[pa]=ਪਾਠ ਪਰਭਾਵ +Name[pl]=Efekty tekstowe +Name[pt]=Efeito de Texto +Name[pt_BR]=Efeito de texto +Name[ro]=Efect text +Name[ru]=ТекÑтовые Ñффекты +Name[se]=Teakstaeffeakta +Name[sk]=Textový efekt +Name[sl]=Besedilni uÄinki +Name[sr]=ТекÑтуални ефекти +Name[sr@Latn]=Tekstualni efekti +Name[sv]=Texteffekter +Name[ta]=செயல௠+Name[tg]=Ðатиҷаҳои Матн +Name[tr]=Metin Efekti +Name[uk]=ТекÑтові ефекти +Name[uz]=Matn effekti +Name[uz@cyrillic]=Матн Ñффекти +Name[zh_CN]=文字特效 +Name[zh_HK]=文字效果 +Name[zh_TW]=文字效果 +Comment=Adds special effects to your text +Comment[ar]=يضي٠مؤثرات خاصة لنصوصك +Comment[be]=Дадае адмыÑÐ»Ð¾Ð²Ñ‹Ñ Ñфекты да вашага Ñ‚ÑкÑту +Comment[bg]=ДобавÑне на текÑтови ефекти към ÑъобщениÑта +Comment[bn]=আপনার টেকà§à¦¸à¦Ÿà§‡ বিশেষ পà§à¦°à¦­à¦¾à¦¬ যোগ করে +Comment[bs]=Dodaje specijalne efekte vaÅ¡im porukama +Comment[ca]=Afegeix bonics efectes al vostre text +Comment[cs]=PÅ™idává speciální efekty do vaÅ¡eho textu +Comment[cy]=Ychwanegu effeithiau arbennig i'ch testun +Comment[da]=Tilføj specielle effekter til din tekst +Comment[de]=Verschönern Sie eigene Nachrichten durch nette Effekte +Comment[el]=ΠÏοσθέτει ειδικά εφέ στο κείμενό σας +Comment[es]=Añade efectos especiales a su texto +Comment[et]=Lisab tekstile eriefekte +Comment[eu]=Gehitu efektu bereziak zure testuar +Comment[fa]=جلوه‌های ویژه را به متن شما اضاÙÙ‡ می‌کند +Comment[fi]=Lisää erikoisefektejä tekstiisi +Comment[fr]=Ajoute des effets spéciaux à vos messages +Comment[ga]=Cuir maisíochtaí speisialta le do théacs +Comment[gl]=Engadir efectos especiáis ó teu texto +Comment[he]=הוסף ××¤×§×˜×™× ×ž×™×•×—×“×™× ×œ×”×•×“×¢×•×ª×š +Comment[hi]=आपके संदेशों में विशिषà¥à¤Ÿ पà¥à¤°à¤­à¤¾à¤µ जोड़े +Comment[hr]=Dodaje specijalne efekte vaÅ¡em tekstu +Comment[hu]=Speciális effektusok hozzáadása az üzenetek szövegéhez +Comment[is]=Bæta skreytingum í textann +Comment[it]=Aggiunti effetti speciali al tuo testo +Comment[ja]=テキストã«ç‰¹åˆ¥ãªåŠ¹æžœã‚’付加 +Comment[ka]=თქვენს ტექსტს áƒáƒ›áƒáƒ¢áƒ”ბს ეფექტებს +Comment[kk]=Мәтініңізді арнаулы безендіру Ñ‚Ó™Ñілдері +Comment[km]=បន្ážáŸ‚ម​បែបផែន​ពិសáŸážŸáŸ—​ទៅ​អážáŸ’ážáž”ទ​របស់​អ្នក +Comment[lt]=PridÄ—kite į tekstÄ… specialiųjų efektų +Comment[mk]=Додава Ñпцијални ефекти на вашиот текÑÑ‚ +Comment[nb]=Legg til spesielle effekter pÃ¥ teksten +Comment[nds]=Föögt Dien Text smucke Effekten to +Comment[ne]=तपाईà¤à¤•à¥‹ पाठमा विशेष पà¥à¤°à¤­à¤¾à¤µ थपà¥à¤¦à¤› +Comment[nl]=Voegt speciale effecten aan uw teksten toe +Comment[nn]=Legg til spesielle effektar pÃ¥ teksten +Comment[pl]=Dodaje specjalne efekty do Twojego tekstu +Comment[pt]=Adiciona efeitos especiais ao seu texto +Comment[pt_BR]=Adiciona efeitos especiais em seu texto +Comment[ro]=Adaugă efecte speciale la textele dumneavoastră +Comment[ru]=ДобавлÑет Ñффекты к вашим ÑообщениÑм +Comment[se]=Lasiha erenoamaÅ¡ effeavttaid du tekstii +Comment[sk]=Pridá Å¡peciálne efekty k vášmu textu +Comment[sl]=Doda posebne uÄinke vaÅ¡emu besedilu +Comment[sr]=Додаје Ñпецијалне ефекте вашем текÑту +Comment[sr@Latn]=Dodaje specijalne efekte vaÅ¡em tekstu +Comment[sv]=Lägger till specialeffekter till din text +Comment[ta]=உஙà¯à®•à®³à¯ உரையில௠சிறபà¯à®ªà¯ விளைவிகளை சேரà¯à®•à¯à®•à¯à®®à¯ +Comment[tg]=Ðатиҷаҳои махÑуÑро ба матни шумо ҳамроҳ мекунад +Comment[tr]=Metinlerinize özel efektler ekleyin +Comment[uk]=Додає ефекти до ваших повідомлень +Comment[zh_CN]=在您的文字中添加特殊效果 +Comment[zh_HK]=為您的訊æ¯å¢žåŠ ç‰¹åˆ¥çš„效果 +Comment[zh_TW]=在您的文字中加入一些特效 + diff --git a/kopete/plugins/texteffect/texteffectconfig.cpp b/kopete/plugins/texteffect/texteffectconfig.cpp new file mode 100644 index 00000000..9ecca3f0 --- /dev/null +++ b/kopete/plugins/texteffect/texteffectconfig.cpp @@ -0,0 +1,140 @@ +/* + texteffectconfig.cpp + + Copyright (c) 2003 by Olivier Goffart + Copyright (c) 2003 by Matt Rogers + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include + +#include +#include + +#include "texteffectconfig.h" + +TextEffectConfig::TextEffectConfig() +{ + load(); +} + +void TextEffectConfig::load() +{ + KConfig *config = KGlobal::config(); + config->setGroup("TextEffect Plugin"); + + mColors = config->readListEntry("Colors"); + if(mColors.isEmpty()) + { + mColors= defaultColorList(); + } + mColorRandom = config->readBoolEntry("Color Random Order", false); + mColorLines = config->readBoolEntry("Color change every lines", true); + mColorWords = config->readBoolEntry("Color change every words", false); + mColorChar = config->readBoolEntry("Color change every char", false); + + mLamer = config->readBoolEntry("L4m3r", false); + mWaves = config->readBoolEntry("WaVeS", false); +} + +QStringList TextEffectConfig::defaultColorList() +{ + return QStringList::split( ",", "#00BBDD,#0088DD,#0000DD,#8800DD,#DD00DD,#DD0088,#DD0000,#DD8800,#DDBB00,#88BB00,#00BB00" ); +} + +void TextEffectConfig::save() +{ + KConfig *config = KGlobal::config(); + config->setGroup("TextEffect Plugin"); + + config->writeEntry("Colors", mColors ); + config->writeEntry("Color Random Order", mColorRandom); + config->writeEntry("Color change every lines", mColorLines); + config->writeEntry("Color change every words", mColorWords); + config->writeEntry("Color change every char", mColorChar); + + config->writeEntry("L4m3r", mLamer); + config->writeEntry("WaVeS", mWaves); + + config->sync(); +} + +QStringList TextEffectConfig::colors() const +{ + return mColors; +} + +bool TextEffectConfig::colorRandom() const +{ + return mColorRandom; +} + +bool TextEffectConfig::colorWords() const +{ + return mColorWords; +} + +bool TextEffectConfig::colorLines() const +{ + return mColorLines; +} + +bool TextEffectConfig::colorChar() const +{ + return mColorChar; +} + +bool TextEffectConfig::lamer() const +{ + return mLamer; +} + +bool TextEffectConfig::waves() const +{ + return mWaves; +} + +void TextEffectConfig::setColors(const QStringList &newColors) +{ + mColors = newColors; +} + +void TextEffectConfig::setColorWords(bool newWords) +{ + mColorWords = newWords; +} + +void TextEffectConfig::setColorLines(bool newLines) +{ + mColorLines = newLines; +} + +void TextEffectConfig::setColorRandom(bool newRandom) +{ + mColorRandom = newRandom; +} + +void TextEffectConfig::setColorChar(bool newChar) +{ + mColorChar = newChar; +} + +void TextEffectConfig::setLamer(bool newLamers) +{ + mLamer = newLamers; +} + +void TextEffectConfig::setWaves(bool newWaves) +{ + mWaves = newWaves; +} diff --git a/kopete/plugins/texteffect/texteffectconfig.h b/kopete/plugins/texteffect/texteffectconfig.h new file mode 100644 index 00000000..80b19151 --- /dev/null +++ b/kopete/plugins/texteffect/texteffectconfig.h @@ -0,0 +1,62 @@ +/* + texteffectconfig.h + + Copyright (c) 2003 by Olivier Goffart + Copyright (c) 2003 by Matt Rogers + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef TEXTEFFECTCONFIG_H +#define TEXTEFFECTCONFIG_H + +class QStringList; + +class TextEffectConfig +{ +public: + TextEffectConfig(); + + void load(); + void save(); + + //accessor functions + QStringList colors() const; + bool colorLines() const; + bool colorWords() const; + bool colorChar() const; + bool colorRandom() const; + bool lamer() const; + bool waves() const; + + void setColors(const QStringList &newColors = QStringList()); + void setColorLines(bool newLines); + void setColorChar(bool newChar); + void setColorWords(bool newWords); + void setColorRandom(bool newRandom); + void setLamer(bool newLamer); + void setWaves(bool newWaves); + QStringList defaultColorList(); + + +private: + QStringList mColors; + bool mColorLines; + bool mColorWords; + bool mColorChar; + bool mColorRandom; + bool mLamer; + bool mWaves; + +}; + +#endif diff --git a/kopete/plugins/texteffect/texteffectplugin.cpp b/kopete/plugins/texteffect/texteffectplugin.cpp new file mode 100644 index 00000000..5374b2ca --- /dev/null +++ b/kopete/plugins/texteffect/texteffectplugin.cpp @@ -0,0 +1,198 @@ +/*************************************************************************** + texteffectplugin.cpp - description + ------------------- + begin : jeu nov 14 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#include + +#include +#include + +#include "kopetechatsessionmanager.h" + +#include "texteffectplugin.h" +#include "texteffectconfig.h" + +typedef KGenericFactory TextEffectPluginFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_texteffect, TextEffectPluginFactory( "kopete_texteffect" ) ) + +TextEffectPlugin::TextEffectPlugin( QObject *parent, const char *name, const QStringList &/*args*/ ) +: Kopete::Plugin( TextEffectPluginFactory::instance(), parent, name ) +{ + if( !pluginStatic_ ) + pluginStatic_=this; + + m_config = new TextEffectConfig; + + connect ( this , SIGNAL( settingsChanged() ) , this , SLOT( slotSettingsChanged() ) ); + + connect( Kopete::ChatSessionManager::self(), + SIGNAL( aboutToSend( Kopete::Message & ) ), + SLOT( slotOutgoingMessage( Kopete::Message & ) ) ); + + last_color=0; +} + +TextEffectPlugin::~TextEffectPlugin() +{ + delete m_config; + pluginStatic_ = 0L; +} + +TextEffectPlugin* TextEffectPlugin::plugin() +{ + return pluginStatic_ ; +} + +TextEffectPlugin* TextEffectPlugin::pluginStatic_ = 0L; + + +void TextEffectPlugin::slotOutgoingMessage( Kopete::Message& msg ) +{ + if(msg.direction() != Kopete::Message::Outbound) + return; + + QStringList colors=m_config->colors(); + + if(m_config->colorChar() || m_config->colorWords() || m_config->lamer() || m_config->waves() ) + { + QString original=msg.plainBody(); + QString resultat; + + unsigned int c=0; + bool wavein=false; + + for(unsigned int f=0;fcolorChar() || (m_config->colorWords() && x==' ' )) + { + if(f!=0) + resultat+=""; + resultat+="colorRandom()) + c=rand()%colors.count(); + else + { + c++; + if(c >= colors.count()) + c=0; + } + resultat+="\">"; + } + switch (x.latin1()) + { + case '>': + resultat+=">"; + break; + case '<': + resultat+="<"; + break; + case '&': + resultat+="&"; + break; + case '\n': + resultat+="
    "; + case 'a': + case 'A': + if(m_config->lamer()) + { + resultat+="4"; + break; + } //else, go to the default, all other case have this check + case 'e': + case 'E': + if(m_config->lamer()) + { + resultat+="3"; + break; + }//else, go to the default, all other case have this check + case 'i': + case 'I': + if(m_config->lamer()) + { + resultat+="1"; + break; + }//else, go to the default, all other case have this check + case 'l': + case 'L': + if(m_config->lamer()) + { + resultat+="|"; + break; + }//else, go to the default, all other case have this check + case 't': + case 'T': + if(m_config->lamer()) + { + resultat+="7"; + break; + }//else, go to the default, all other case have this check + case 's': + case 'S': + if(m_config->lamer()) + { + resultat+="5"; + break; + }//else, go to the default, all other case have this check + case 'o': + case 'O': + if(m_config->lamer()) + { + resultat+="0"; + break; + }//else, go to the default, all other case have this check + default: + if(m_config->waves()) + { + resultat+= wavein ? x.lower() : x.upper(); + wavein=!wavein; + } + else + resultat+=x; + break; + } + } + if( m_config->colorChar() || m_config->colorWords() ) + resultat+="
    "; + msg.setBody(resultat,Kopete::Message::RichText); + } + + if(m_config->colorLines()) + { + if(m_config->colorRandom()) + { + last_color=rand()%colors.count(); + } + else + { + last_color++; + if(last_color >= colors.count()) + last_color=0; + } + + msg.setFg(QColor (colors[last_color])); + } +} + +void TextEffectPlugin::slotSettingsChanged() +{ + m_config->load(); +} + + +#include "texteffectplugin.moc" + diff --git a/kopete/plugins/texteffect/texteffectplugin.h b/kopete/plugins/texteffect/texteffectplugin.h new file mode 100644 index 00000000..db34fdcb --- /dev/null +++ b/kopete/plugins/texteffect/texteffectplugin.h @@ -0,0 +1,71 @@ +/*************************************************************************** + texteffectplugin.h - description + ------------------- + begin : jeu nov 14 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef TextEffectPLUGIN_H +#define TextEffectPLUGIN_H + +#include +#include +#include + +#include "kopetemessage.h" +#include "kopeteplugin.h" + + +class QStringList; +class QString; + +namespace Kopete { class Message; } +namespace Kopete { class MetaContact; } +namespace Kopete { class ChatSession; } +class TextEffectConfig; + +/** + * @author Olivier Goffart + */ + +class TextEffectPlugin : public Kopete::Plugin +{ + Q_OBJECT + +public: + static TextEffectPlugin *plugin(); + + TextEffectPlugin( QObject *parent, const char *name, const QStringList &args ); + ~TextEffectPlugin(); + +public slots: + void slotOutgoingMessage( Kopete::Message& msg ); + void slotSettingsChanged(); + +private: + static TextEffectPlugin* pluginStatic_; + unsigned int last_color; + TextEffectConfig *m_config; +}; + +#endif + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/texteffect/texteffectpreferences.cpp b/kopete/plugins/texteffect/texteffectpreferences.cpp new file mode 100644 index 00000000..c9f0c03b --- /dev/null +++ b/kopete/plugins/texteffect/texteffectpreferences.cpp @@ -0,0 +1,232 @@ +/*************************************************************************** + texteffectpreferences.cpp - description + ------------------- + begin : jeu nov 14 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "texteffectprefs.h" +#include "texteffectpreferences.h" +#include "texteffectconfig.h" + +typedef KGenericFactory TextEffectPreferencesFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_texteffect, TextEffectPreferencesFactory( "kcm_kopete_texteffect" ) ) + +TextEffectPreferences::TextEffectPreferences(QWidget *parent, + const char* /*name*/, + const QStringList &args) + : KCModule(TextEffectPreferencesFactory::instance(), parent, args) +{ + ( new QVBoxLayout( this ) )->setAutoAdd( true ); + + kdDebug( 14310 ) << "Creating preferences dialog" << endl; + + preferencesDialog = new TextEffectPrefs(this); + + kdDebug( 14310 ) << "Creating config object" << endl; + + config = new TextEffectConfig; + + kdDebug( 14310 ) << "Setting up connections" << endl; + + connect(preferencesDialog->mColorsAdd , SIGNAL(pressed()) , + this , SLOT(slotAddPressed())); + + connect(preferencesDialog->mColorsRemove , SIGNAL(pressed()) , + this , SLOT(slotRemovePressed())); + + connect(preferencesDialog->mColorsUp , SIGNAL(pressed()) , + this , SLOT(slotUpPressed())); + + connect(preferencesDialog->mColorsDown , SIGNAL(pressed()) , + this , SLOT(slotDownPressed())); + + // Connect up all the check boxes + connect( preferencesDialog->m_lamer, SIGNAL( clicked() ), + this, SLOT( slotSettingChanged() ) ); + connect( preferencesDialog->m_casewaves, SIGNAL( clicked() ), + this, SLOT( slotSettingChanged() ) ); + + connect( preferencesDialog->m_colorRandom, SIGNAL( clicked() ), + this, SLOT( slotSettingChanged() ) ); + connect( preferencesDialog->m_fg, SIGNAL( clicked() ), + this, SLOT( slotSettingChanged() ) ); + connect( preferencesDialog->m_char, SIGNAL( clicked() ), + this, SLOT( slotSettingChanged() ) ); + connect( preferencesDialog->m_words, SIGNAL( clicked() ), + this, SLOT( slotSettingChanged() ) ); + + //setMainWidget( preferencesDialog, "Text Effect Plugin" ); + + load(); + +} + +TextEffectPreferences::~TextEffectPreferences() +{ + delete preferencesDialog; + delete config; +} + + +void TextEffectPreferences::load() +{ + kdDebug( 14310 ) << k_funcinfo << "ENTER" << endl; + + config->load(); + + preferencesDialog->mColorsListBox->insertStringList(config->colors()); + preferencesDialog->m_fg->setChecked(config->colorLines()); + preferencesDialog->m_words->setChecked(config->colorWords()); + preferencesDialog->m_char->setChecked(config->colorChar()); + preferencesDialog->m_lamer->setChecked(config->lamer()); + preferencesDialog->m_casewaves->setChecked(config->waves()); + + + // Call parent's save method + KCModule::load(); + + // Indicate that we have not changed ^_^ + emit changed( false ); + + kdDebug( 14310 ) << k_funcinfo << "EXIT" << endl; + +} + +void TextEffectPreferences::save() +{ + kdDebug() << k_funcinfo << "ENTER" << endl; + // Save the settings + config->setColors(colors()); + config->setColorRandom(preferencesDialog->m_colorRandom->isChecked()); + config->setColorLines(preferencesDialog->m_fg->isChecked()); + config->setColorWords(preferencesDialog->m_words->isChecked()); + config->setColorChar(preferencesDialog->m_char->isChecked()); + + config->setLamer(preferencesDialog->m_lamer->isChecked()); + config->setWaves(preferencesDialog->m_casewaves->isChecked()); + + config->save(); + + // Notify the plugin that the settings have changed + //TextEffectPlugin::plugin()->slotSettingsChanged(); + + // Call parent's save method + KCModule::save(); + + // Indicate that we have not changed ^_^ + emit changed( false ); + kdDebug() << k_funcinfo << "EXIT" << endl; +} + +QStringList TextEffectPreferences::colors() +{ + QStringList ret; + for(unsigned int f=0; fmColorsListBox->count() ; f++) + { + ret.append(preferencesDialog->mColorsListBox->text(f)); + } + return ret; +} + +void TextEffectPreferences::slotAddPressed() +{ + QColor myColor; + if( KColorDialog::getColor( myColor ) == KColorDialog::Accepted ) + { + preferencesDialog->mColorsListBox->insertItem(myColor.name()); + } + + // Indicate that something has changed + slotSettingChanged(); + +} +void TextEffectPreferences::slotRemovePressed() +{ + delete preferencesDialog->mColorsListBox->selectedItem(); + // Indicate that something has changed + slotSettingChanged(); +} + + +void TextEffectPreferences::slotUpPressed() +{ + int p=preferencesDialog->mColorsListBox->currentItem(); + if(p <= 0 ) + return; + QListBoxItem *i=preferencesDialog->mColorsListBox->selectedItem(); + if(!i) + return; + preferencesDialog->mColorsListBox->setSelected(i,false); + preferencesDialog->mColorsListBox->takeItem(i); + preferencesDialog->mColorsListBox->insertItem(i , p-1 ); + preferencesDialog->mColorsListBox->setSelected(i,true); + + // Indicate that something has changed + slotSettingChanged(); + +} +void TextEffectPreferences::slotDownPressed() +{ + int p=preferencesDialog->mColorsListBox->currentItem(); + if(p < 0 ) + return; + QListBoxItem *i=preferencesDialog->mColorsListBox->selectedItem(); + if(!i) + return; + preferencesDialog->mColorsListBox->setSelected(i,false); + preferencesDialog->mColorsListBox->takeItem(i); + preferencesDialog->mColorsListBox->insertItem(i , p+1 ); + preferencesDialog->mColorsListBox->setSelected(i,true); + + // Indicate that something has changed + slotSettingChanged(); +} + + + +void TextEffectPreferences::slotSettingChanged() +{ + kdDebug() << k_funcinfo << "Called" + << endl; + // Indicate that our settings have changed + emit changed( true ); +} + +void TextEffectPreferences::defaults() +{ + preferencesDialog->mColorsListBox->clear(); + preferencesDialog->mColorsListBox->insertStringList(config->defaultColorList()); + preferencesDialog->m_fg->setChecked(false); + preferencesDialog->m_words->setChecked(false); + preferencesDialog->m_char->setChecked(false); + preferencesDialog->m_lamer->setChecked(false); + preferencesDialog->m_casewaves->setChecked(false); + preferencesDialog->m_colorRandom->setChecked( false ); + emit changed( true ); +} + +#include "texteffectpreferences.moc" diff --git a/kopete/plugins/texteffect/texteffectpreferences.h b/kopete/plugins/texteffect/texteffectpreferences.h new file mode 100644 index 00000000..21dc7bff --- /dev/null +++ b/kopete/plugins/texteffect/texteffectpreferences.h @@ -0,0 +1,58 @@ +/*************************************************************************** + texteffectpreferences.h - description + ------------------- + begin : jeu nov 14 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef TextEffectPREFERENCES_H +#define TextEffectPREFERENCES_H + +#include + +class TextEffectPrefs; +class TextEffectConfig; +class QStringList; + +/** + *@author Olivier Goffart + */ + +class TextEffectPreferences : public KCModule { + Q_OBJECT +public: + + TextEffectPreferences(QWidget *parent = 0, const char* name = 0, const QStringList &args = QStringList()); + ~TextEffectPreferences(); + + // Overloaded from parent + virtual void save(); + virtual void load(); + virtual void defaults(); + +private: + QStringList colors(); + TextEffectPrefs *preferencesDialog; + TextEffectConfig *config; + +private slots: // Public slots + void slotAddPressed(); + void slotRemovePressed(); + void slotUpPressed(); + void slotDownPressed(); + void slotSettingChanged(); + +}; + +#endif + diff --git a/kopete/plugins/texteffect/texteffectprefs.ui b/kopete/plugins/texteffect/texteffectprefs.ui new file mode 100644 index 00000000..95ff801c --- /dev/null +++ b/kopete/plugins/texteffect/texteffectprefs.ui @@ -0,0 +1,231 @@ + +TextEffectPrefs +Olivier Goffart + + + TextEffectPrefs + + + + 0 + 0 + 630 + 529 + + + + + unnamed + + + 5 + + + 0 + + + + TabWidget3 + + + + tab + + + &Colors + + + + unnamed + + + + groupBox1 + + + Colors + + + + unnamed + + + + mColorsListBox + + + + + mColorsAdd + + + &Add... + + + + + mColorsRemove + + + &Remove + + + + + mColorsUp + + + Move &Up + + + + + mColorsDown + + + Move &Down + + + + + spacer2 + + + Vertical + + + Expanding + + + + 21 + 81 + + + + + + + + m_colorRandom + + + Random order + + + + + Line1 + + + HLine + + + Sunken + + + Horizontal + + + + + m_fg + + + Change global text foreground color + + + + + m_char + + + Change color every letter + + + + + m_words + + + Change color every word + + + + + + + tab + + + Effects + + + + unnamed + + + + m_lamer + + + + 7 + 0 + 0 + 0 + + + + L4m3r t4lk + + + + + m_casewaves + + + + 7 + 0 + 0 + 0 + + + + CasE wAVes + + + + + Spacer5_2 + + + Vertical + + + Expanding + + + + 20 + 279 + + + + + + + + + + TabWidget3 + + + knuminput.h + + + + klistbox.h + + diff --git a/kopete/plugins/translator/Makefile.am b/kopete/plugins/translator/Makefile.am new file mode 100644 index 00000000..7ab367f9 --- /dev/null +++ b/kopete/plugins/translator/Makefile.am @@ -0,0 +1,25 @@ +METASOURCES = AUTO + +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kopete_translator.la kcm_kopete_translator.la + +kopete_translator_la_SOURCES = translatorplugin.cpp \ + translatordialog.cpp translatorguiclient.cpp translatorlanguages.cpp +kopete_translator_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kopete_translator_la_LIBADD = ../../libkopete/libkopete.la $(LIB_KIO) + +kcm_kopete_translator_la_SOURCES = translatorprefsbase.ui translatorprefs.cpp translatorlanguages.cpp +kcm_kopete_translator_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_translator_la_LIBADD = ../../libkopete/libkopete.la $(LIB_KUTILS) + +service_DATA = kopete_translator.desktop +servicedir = $(kde_servicesdir) + +kcm_DATA = kopete_translator_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog + +mydatadir = $(kde_datadir)/kopete_translator +mydata_DATA = translatorui.rc translatorchatui.rc + + diff --git a/kopete/plugins/translator/kopete_translator.desktop b/kopete/plugins/translator/kopete_translator.desktop new file mode 100644 index 00000000..5da3f373 --- /dev/null +++ b/kopete/plugins/translator/kopete_translator.desktop @@ -0,0 +1,128 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=locale +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_translator +X-KDE-PluginInfo-Author=Duncan Mac-Vicar Prett +X-KDE-PluginInfo-Email=duncan@kde.org +X-KDE-PluginInfo-Name=kopete_translator +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Translator +Name[ar]=المترجم +Name[be]=Перакладнік +Name[bg]=Ðвтоматичен превод +Name[bn]=অনà§à¦¬à¦¾à¦¦à¦• +Name[br]=Troer +Name[bs]=Prevodilac +Name[ca]=Traductor +Name[cs]=PÅ™ekladaÄ +Name[cy]=Cyfieithydd +Name[da]=Oversætter +Name[de]=Ãœbersetzer +Name[el]=ΜεταφÏαστής +Name[eo]=Tradukilo +Name[es]=Traductor +Name[et]=Tõlketeenus +Name[eu]=Itzultzailea +Name[fa]=مترجم +Name[fi]=Kääntäjä +Name[fr]=Traducteur +Name[ga]=Aistritheoir +Name[gl]=Traductor +Name[he]=×ž×ª×¨×’× +Name[hi]=अनà¥à¤µà¤¾à¤¦à¤• +Name[hr]=Prevoditelj +Name[hu]=Tolmács +Name[is]=Þýðandi +Name[it]=Traduttore +Name[ja]=翻訳ソフト +Name[ka]=მთáƒáƒ áƒ’მნელი +Name[km]=កម្មវិធី​បកប្រែ +Name[lt]=VertÄ—jas +Name[mk]=Преведувач +Name[nb]=Oversetter +Name[nds]=Översetter +Name[ne]=अनà¥à¤µà¤¾à¤¦à¤• +Name[nl]=Vertaler +Name[nn]=Omsetjar +Name[pa]=ਅਨà©à¨µà¨¾à¨¦ +Name[pl]=TÅ‚umacz +Name[pt]=Tradutor +Name[pt_BR]=Tradutor +Name[ro]=Traducător +Name[ru]=Переводчик +Name[se]=Jorgaleaddji +Name[sk]=Preklad +Name[sl]=Prevajalec +Name[sr]=Преводилац +Name[sr@Latn]=Prevodilac +Name[sv]=Översättning +Name[ta]=மொழிமாறà¯à®±à®¿ +Name[tg]=Тарҷумон +Name[tr]=Çevirmen +Name[uk]=Перекладач +Name[uz]=Tarjimon +Name[uz@cyrillic]=Таржимон +Name[wa]=Ratournaedje +Name[zh_CN]=翻译家 +Name[zh_HK]=翻譯者 +Name[zh_TW]=翻譯器 +Comment=Chat with foreign buddies in your native language +Comment[ar]=تحدث مع أصدقائك اﻷجانب بلغتك اﻷم +Comment[be]=Гутарка з замежнікамі на роднай мове +Comment[bg]=ПриÑтавка за превод от един език на друг в реално време +Comment[bn]=আপনার সà§à¦¥à¦¾à¦¨à§€à§Ÿ ভাষাতে বিদেশী বনà§à¦§à§à¦° সঙà§à¦—ে চà§à¦¯à¦¾à¦Ÿ করà§à¦¨ +Comment[bs]=Razgovarajte sa prijateljima iz inostranstva u vaÅ¡em jeziku +Comment[ca]=Feu un xat amb els vostres amics en la vostra llengua nativa +Comment[cs]=Pokecejte si s cizinci ve svém rodném jazyku +Comment[cy]=Sgwrsio â chyfeillion tramor yn eich iaith gyntaf +Comment[da]=Chat med udenlandske venner pÃ¥ dit eget sprog +Comment[de]=Chatten mit fremdsprachigen Freunden in der eigenen Muttersprache +Comment[el]=Συνομιλήστε με ξένους φίλους στη μητÏική σας γλώσσα +Comment[es]=Charle con compañeros extranjeros en su lengua +Comment[et]=Vestlemine välismaiste semudega enda emakeeles +Comment[eu]=Atzerriko lagunekin zure ama-hizkuntzan hitz egin +Comment[fa]=Ú¯Ù¾ زدن با دوستان خارجی با زبان مادریتان +Comment[fi]=Juttele ulkomaisten kaveriesi kanssa omalla kielelläsi +Comment[fr]=Discutez avec des étrangers dans votre langue natale +Comment[gl]=Fala con amigos extranxeiros na túa lingua nativa +Comment[he]=שוחח ×¢× ×—×‘×¨×™× ×ž×—×•"ל בשפת ×”×× ×©×œ×š +Comment[hi]=आपकी मातृ भाषा में विदेशी बडà¥à¤¡à¥€à¤¸ से गपशप करें +Comment[hr]=Razgovo sa stranim prijateljima na vaÅ¡em materinjem jeziku +Comment[hu]=Csevegés más nyelvet beszélÅ‘kkel a saját nyelven +Comment[is]=rabbaðu við útlenda félaga á þínu máli +Comment[it]=Parla con un tuo amico straniero nella tua lingua +Comment[ja]=æ¯å›½èªžã®ã¾ã¾ã§ä»–国ã®äººã¨ãƒãƒ£ãƒƒãƒˆã™ã‚‹ +Comment[ka]=უცხáƒáƒ”ლ მეგáƒáƒ‘რებთáƒáƒœ სáƒáƒ™áƒ£áƒ—áƒáƒ  ენáƒáƒ–ე სáƒáƒ£áƒ‘áƒáƒ áƒ˜ +Comment[kk]=Шетел доÑтарымен өз тіліңізде әңгімелеÑу +Comment[km]=ជជែក​កំសាន្ážâ€‹áž‡áž¶áž˜áž½áž™â€‹ážŸáž˜áŸ’លាញ់​ជនជាážáž·â€‹áž”ážšáž‘áŸážŸ ដោយ​ប្រើ​ភាសា​កំណើážâ€‹ážšáž”ស់​អ្នក +Comment[lt]=Bendraukite su biÄiuliais iÅ¡ užsieno gimtÄ…ja kalba +Comment[mk]=Разговарајте Ñо ÑтранÑки пријатели на вашиот мајчин јазик +Comment[nb]=Snakk med utenlandske venner pÃ¥ ditt eget sprÃ¥k +Comment[nds]=Klöön in Dien Moderspraak mit frömdspreken Frünnen +Comment[ne]=तपाईà¤à¤•à¥‹ मातृ भाषामा विदेशी साथीसà¤à¤— कà¥à¤°à¤¾à¤•à¤¾à¤¨à¥€ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Praat met vrienden uit andere landen in uw eigen taal +Comment[nn]=Prat med utanlandske venner pÃ¥ ditt eige sprÃ¥k +Comment[pl]=Umożliwia rozmowÄ™ z zagranicznymi znajomymi w Twoim macierzystym jÄ™zyku +Comment[pt]=Converse com amigos estrangeiros na sua língua nativa +Comment[pt_BR]=Conversa com colegas estrangeiros em seu idioma nativo +Comment[ru]=ПозволÑет говорить Ñ Ð¸Ð½Ð¾Ñтранцами на вашем родном Ñзыке +Comment[se]=Čátte olgoriikkalaÅ¡ ustibiiguin iežat gillii +Comment[sk]=Rozprávajte sa s vaÅ¡imi priateľmi v cudzine v inom jazyku +Comment[sl]=Klepetajte s prijatelji iz tujine v svojem domaÄem jeziku +Comment[sr]=ЋаÑкајте Ñа вашим другарима Ñтранцима на вашем матерњем језику +Comment[sr@Latn]=Ćaskajte sa vaÅ¡im drugarima strancima na vaÅ¡em maternjem jeziku +Comment[sv]=Chatta med utländska kompisar pÃ¥ ditt modersmÃ¥l +Comment[ta]=வெளிநாடà¯à®Ÿà¯ நபரà¯à®Ÿà®©à¯ உஙà¯à®•à®³à¯ சொநà¯à®¤ மொழியில௠அரடà¯à®Ÿà¯ˆ அடிகà¯à®• à®®à¯à®Ÿà®¿à®¯à¯à®®à¯ +Comment[tg]=Чат бо дӯÑтони хориҷӣ дар забони модарии шумо +Comment[tr]=Anadilinizdeki arkadaÅŸlarla sohbet edin +Comment[uk]=Розмова з іноземними друзÑми на вашій рідній мові +Comment[zh_CN]=以您的æ¯è¯­ä¸Žå¤–国朋å‹èŠå¤© +Comment[zh_HK]=以您的æ¯èªžå’Œå¤–地的伙伴èŠå¤© +Comment[zh_TW]=用您的æ¯èªžè·Ÿå¤–國朋å‹èŠå¤© diff --git a/kopete/plugins/translator/kopete_translator_config.desktop b/kopete/plugins/translator/kopete_translator_config.desktop new file mode 100644 index 00000000..288709c4 --- /dev/null +++ b/kopete/plugins/translator/kopete_translator_config.desktop @@ -0,0 +1,127 @@ +[Desktop Entry] +Icon=locale +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_translator +X-KDE-FactoryName=TranslatorConfigFactory +X-KDE-ParentApp=kopete_translator +X-KDE-ParentComponents=kopete_translator + +Name=Translator +Name[ar]=المترجم +Name[be]=Перакладнік +Name[bg]=Ðвтоматичен превод +Name[bn]=অনà§à¦¬à¦¾à¦¦à¦• +Name[br]=Troer +Name[bs]=Prevodilac +Name[ca]=Traductor +Name[cs]=PÅ™ekladaÄ +Name[cy]=Cyfieithydd +Name[da]=Oversætter +Name[de]=Ãœbersetzer +Name[el]=ΜεταφÏαστής +Name[eo]=Tradukilo +Name[es]=Traductor +Name[et]=Tõlketeenus +Name[eu]=Itzultzailea +Name[fa]=مترجم +Name[fi]=Kääntäjä +Name[fr]=Traducteur +Name[ga]=Aistritheoir +Name[gl]=Traductor +Name[he]=×ž×ª×¨×’× +Name[hi]=अनà¥à¤µà¤¾à¤¦à¤• +Name[hr]=Prevoditelj +Name[hu]=Tolmács +Name[is]=Þýðandi +Name[it]=Traduttore +Name[ja]=翻訳ソフト +Name[ka]=მთáƒáƒ áƒ’მნელი +Name[km]=កម្មវិធី​បកប្រែ +Name[lt]=VertÄ—jas +Name[mk]=Преведувач +Name[nb]=Oversetter +Name[nds]=Översetter +Name[ne]=अनà¥à¤µà¤¾à¤¦à¤• +Name[nl]=Vertaler +Name[nn]=Omsetjar +Name[pa]=ਅਨà©à¨µà¨¾à¨¦ +Name[pl]=TÅ‚umacz +Name[pt]=Tradutor +Name[pt_BR]=Tradutor +Name[ro]=Traducător +Name[ru]=Переводчик +Name[se]=Jorgaleaddji +Name[sk]=Preklad +Name[sl]=Prevajalec +Name[sr]=Преводилац +Name[sr@Latn]=Prevodilac +Name[sv]=Översättning +Name[ta]=மொழிமாறà¯à®±à®¿ +Name[tg]=Тарҷумон +Name[tr]=Çevirmen +Name[uk]=Перекладач +Name[uz]=Tarjimon +Name[uz@cyrillic]=Таржимон +Name[wa]=Ratournaedje +Name[zh_CN]=翻译家 +Name[zh_HK]=翻譯者 +Name[zh_TW]=翻譯器 +Comment=Translates messages from your native language to another language +Comment[ar]=يترجم رسائل من لغتك اﻷم إلى لغة أخرى +Comment[be]=Перакладае паведамленні з роднай мовы на Ð·Ð°Ð¼ÐµÐ¶Ð½Ñ‹Ñ +Comment[bg]=ПриÑтавка за превод от един език на друг в реално време +Comment[bn]=আপনার সà§à¦¥à¦¾à¦¨à§€à§Ÿ ভাষা থেকে অনà§à¦¯ à¦à¦•à¦Ÿà¦¿ ভাষাতে বারà§à¦¤à¦¾ অনà§à¦¬à¦¾à¦¦ করে +Comment[bs]=Prevodi poruke iz vaÅ¡eg maternjeg jezika u neki drugi +Comment[ca]=Tradueix els missatges des del vostre idioma natiu cap a un altre +Comment[cs]=PÅ™ekládá zprávy z vaÅ¡eho rodného jazyka do jiného +Comment[cy]=Cyfieithu negeseuon o'ch iaith gyntaf i iaith arall +Comment[da]=Oversætter beskeder fra dit indfødte sprog til et andet sprog +Comment[de]=Ãœbersetzt Nachrichten von der eigenen in eine fremde Sprache +Comment[el]=ΜεταφÏάζει μηνÏματα από τη μητÏική σας γλώσσα σε μια άλλη +Comment[es]=Traduce mensajes de su lengua a otra +Comment[et]=Tõlgib sõnumid sinu emakeelest mingisse muusse keelde +Comment[eu]=Mezuak zure ama-hizkuntzatik beste hizkuntzara itzultzen ditu +Comment[fa]=ترجمۀ پیامهایی با زبان مادری شما به زبانهای دیگر +Comment[fi]=Kääntää viestit omasta kielestä toiseen kieleen +Comment[fr]=Traduit les messages de votre langue natale en une autre langue +Comment[ga]=Aistríonn seo teachtaireachtaí ó do theanga dúchais go teanga eile +Comment[gl]=Traduce as túas mensaxes da túa lingua nativa a outra +Comment[he]=×ž×ª×¨×’× ×ת מסריך משפת ×”×× ×©×œ×š לשפה ×חרת +Comment[hi]=आपकी मातृ भाषा के संदेशों को अनà¥à¤¯ भाषा में अनà¥à¤µà¤¾à¤¦ करे +Comment[hr]=Prevodi poruke s vaÅ¡eg maternjeg jezika na neki drugi +Comment[hu]=Az üzenetek lefordítása a saját nyelvrÅ‘l egy idegen nyelvre +Comment[is]=Þýðir skeyti úr þinni tungu yfir í annað +Comment[it]=Traduci messaggi dalla tua lingua in un'altra +Comment[ja]=æ¯å›½èªžã‹ã‚‰ä»–ã®è¨€èªžã«ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’翻訳ã™ã‚‹ +Comment[ka]=შეტყáƒáƒ‘ინებებს თáƒáƒ áƒ’მნის თქვენი მშáƒáƒ‘ლიური ენიდáƒáƒœ სხვრენáƒáƒ–ე +Comment[kk]=Хабарларды бір тілден баÑқа тілге аудару +Comment[km]=បកប្រែ​សារ​ពី​ភាសា​កំណើážâ€‹ážšáž”ស់​អ្នក​ទៅ​ជា​ភាសា​មួយ​ទៀហ+Comment[lt]=VerÄia žinutes iÅ¡ gimtosios kalbos į užsienio kalbas +Comment[mk]=Ги преведува пораките од вашиот мајчин јазик на друг јазик +Comment[nb]=Oversetter meldinger fra ditt eget sprÃ¥k til et annet +Comment[nds]=Översett Narichten vun Dien Moderspraak na anner Spraken +Comment[ne]=तपाईठमातृ भाषाबाट अरà¥à¤•à¥‹ भाषामा सनà¥à¤¦à¥‡à¤¶ अनà¥à¤µà¤¾à¤¦ गरà¥à¤¦à¤› +Comment[nl]=Vertaalt berichten van uw eigen taal naar een andere taal +Comment[nn]=Set om meldingar frÃ¥ ditt eige sprÃ¥k til eit anna +Comment[pl]=TÅ‚umaczy wiadomoÅ›ci z Twojego jÄ™zyka na inny +Comment[pt]=Traduz as mensagens da sua língua nativa para outra língua +Comment[pt_BR]=Traduz mensagens de seu idioma nativo para outro idioma +Comment[ru]=Переводит Ñообщение Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ родного Ñзыка на Ñзык вашего ÑобеÑедника +Comment[se]=Jorgala dieđáhusaid du gielas muhton eará gillii +Comment[sk]=Prekladá správy z jedného jazyka do iného +Comment[sl]=Prevede sporoÄila iz vaÅ¡ega domaÄega jezika v drugi jezik +Comment[sr]=Преводи поруке Ñа вашег матерњег језика на неки други +Comment[sr@Latn]=Prevodi poruke sa vaÅ¡eg maternjeg jezika na neki drugi +Comment[sv]=Översätt meddelanden frÃ¥n ditt modersmÃ¥l till ett annat sprÃ¥k +Comment[ta]=உஙà¯à®•à®³à¯ மொழியிலிரà¯à®¨à¯à®¤à¯ வேற௠மொழிகà¯à®•à¯ மொழிபெயர௠+Comment[tg]=Пайёмҳоро аз забони модарии шумо ба дигар забонҳо тарҷума мекунад +Comment[tr]=DiÄŸer dillerden ana dilinize çevrilen mesajlar +Comment[uk]=Перекладає Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· вашої рідної мови на ÑкуÑÑŒ іншу мову +Comment[zh_CN]=将消æ¯ä»Žæ‚¨çš„æ¯è¯­ç¿»è¯‘æˆå…¶å®ƒè¯­è¨€ +Comment[zh_HK]=將您æ¯èªžçš„訊æ¯ç¿»è­¯ç‚ºå¦ä¸€ç¨®èªžè¨€ +Comment[zh_TW]=將訊æ¯å¾žæ‚¨çš„æ¯èªžç¿»æˆå…¶ä»–語言 + + diff --git a/kopete/plugins/translator/translatorchatui.rc b/kopete/plugins/translator/translatorchatui.rc new file mode 100644 index 00000000..7dc86e2c --- /dev/null +++ b/kopete/plugins/translator/translatorchatui.rc @@ -0,0 +1,9 @@ + + + + + &Tools + + + + diff --git a/kopete/plugins/translator/translatordialog.cpp b/kopete/plugins/translator/translatordialog.cpp new file mode 100644 index 00000000..c03921fc --- /dev/null +++ b/kopete/plugins/translator/translatordialog.cpp @@ -0,0 +1,44 @@ +/*************************************************************************** + translatordialog.cpp - description + ------------------- + begin : sam oct 19 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#include +#include + +#include "translatordialog.h" + + +TranslatorDialog::TranslatorDialog(const QString &text,QWidget *parent, const char *name ) : KDialogBase(parent,name,true,i18n("Translator Plugin"), Ok) +{ + m_textEdit=new KTextEdit(this); + setMainWidget(m_textEdit); + m_textEdit->setText(text); +} +TranslatorDialog::~TranslatorDialog() +{ +} + +QString TranslatorDialog::translatedText() +{ + return m_textEdit->text(); +} +/*void TranslatorDialog::slotFinished() +{ + emit finished(m_textEdit->text()); +} */ + + +#include "translatordialog.moc" diff --git a/kopete/plugins/translator/translatordialog.h b/kopete/plugins/translator/translatordialog.h new file mode 100644 index 00000000..70fd46c0 --- /dev/null +++ b/kopete/plugins/translator/translatordialog.h @@ -0,0 +1,51 @@ +/*************************************************************************** + translatordialog.h - description + ------------------- + begin : sam oct 19 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef TRANSLATORDIALOG_H +#define TRANSLATORDIALOG_H + +#include +#include + +//#include + +class KTextEdit; + +/** + * @author Olivier Goffart + */ +class TranslatorDialog : public KDialogBase +{ + Q_OBJECT + +public: + TranslatorDialog(const QString &translated, QWidget *parent=0, const char *name=0); + ~TranslatorDialog(); + + QString translatedText(); + +private: + KTextEdit *m_textEdit; + +private slots: // Public slots +// void slotFinished(); +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/translator/translatorguiclient.cpp b/kopete/plugins/translator/translatorguiclient.cpp new file mode 100644 index 00000000..ae175f41 --- /dev/null +++ b/kopete/plugins/translator/translatorguiclient.cpp @@ -0,0 +1,100 @@ +/* + translatorguiclient.cpp + + Kopete Translator plugin + + Copyright (c) 2003-2004 by Olivier Goffart + + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include + +#include +#include +#include + +#include "kopetemessagemanager.h" +#include "kopeteview.h" +#include "kopetecontact.h" +#include "kopetemetacontact.h" +#include "kopetemessage.h" + +#include "translatorplugin.h" +#include "translatorguiclient.h" +#include "translatorlanguages.h" + +TranslatorGUIClient::TranslatorGUIClient( Kopete::ChatSession *parent, const char *name ) +: QObject( parent, name ), KXMLGUIClient( parent ) +{ + setInstance( TranslatorPlugin::plugin()->instance() ); + connect( TranslatorPlugin::plugin(), SIGNAL( destroyed( QObject * ) ), this, SLOT( deleteLater() ) ); + + m_manager = parent; + + new KAction( i18n( "Translate" ), "locale", CTRL + Key_T, this, SLOT( slotTranslateChat() ), actionCollection(), "translateCurrentMessage" ); + + setXMLFile( "translatorchatui.rc" ); +} + +TranslatorGUIClient::~TranslatorGUIClient() +{ +} + +void TranslatorGUIClient::slotTranslateChat() +{ + if ( !m_manager->view() ) + return; + + Kopete::Message msg = m_manager->view()->currentMessage(); + QString body = msg.plainBody(); + if ( body.isEmpty() ) + return; + + QString src_lang = TranslatorPlugin::plugin()->m_myLang; + QString dst_lang; + + QPtrList list = m_manager->members(); + Kopete::MetaContact *to = list.first()->metaContact(); + dst_lang = to->pluginData( TranslatorPlugin::plugin(), "languageKey" ); + if ( dst_lang.isEmpty() || dst_lang == "null" ) + { + kdDebug( 14308 ) << k_funcinfo << "Cannot determine dst Metacontact language (" << to->displayName() << ")" << endl; + return; + } + + // We search for src_dst + TranslatorPlugin::plugin()->translateMessage( body, src_lang, dst_lang, this, SLOT( messageTranslated( const QVariant & ) ) ); +} + +void TranslatorGUIClient::messageTranslated( const QVariant &result ) +{ + QString translated = result.toString(); + if ( translated.isEmpty() ) + { + kdDebug( 14308 ) << k_funcinfo << "Empty string returned" << endl; + return; + } + + //if the user close the window before the translation arrive, return + if ( !m_manager->view() ) + return; + + Kopete::Message msg = m_manager->view()->currentMessage(); + msg.setBody( translated ); + m_manager->view()->setCurrentMessage( msg ); +} + +#include "translatorguiclient.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/translator/translatorguiclient.h b/kopete/plugins/translator/translatorguiclient.h new file mode 100644 index 00000000..32ff015f --- /dev/null +++ b/kopete/plugins/translator/translatorguiclient.h @@ -0,0 +1,63 @@ +/* + translatorguiclient.h + + Kopete Translator Plugin + + Copyright (c) 2003 by Olivier Goffart + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef TRANSLATORGUICLIENT_H +#define TRANSLATORGUICLIENT_H + +#include +#include + +#include + +#include "kopetemessage.h" +#include "kopeteplugin.h" + +namespace Kopete { class ChatSession; } + +/** + * @author Olivier Goffart + */ + +class TranslatorGUIClient : public QObject , public KXMLGUIClient +{ + Q_OBJECT + +public: + TranslatorGUIClient( Kopete::ChatSession *parent, const char *name=0L); + ~TranslatorGUIClient(); + +private slots: + void slotTranslateChat(); + void messageTranslated(const QVariant&); + +private: + Kopete::ChatSession *m_manager; +}; + +#endif + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/translator/translatorlanguages.cpp b/kopete/plugins/translator/translatorlanguages.cpp new file mode 100644 index 00000000..4e59fa79 --- /dev/null +++ b/kopete/plugins/translator/translatorlanguages.cpp @@ -0,0 +1,101 @@ +/* + translatorlanguages.cpp + + Kopete Translatorfish Translator plugin + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2003 by Olivier Goffart + Copyright (c) 2003 by Matt Rogers + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#include "translatorlanguages.h" + +TranslatorLanguages::TranslatorLanguages() +{ + m_lc = 0; + m_sc = 0; + m_services.insert("babelfish", "BabelFish"); + m_services.insert("google", "Google"); + + m_langs.insert("null", i18n("Unknown")); + m_langs.insert("en", i18n("English")); + m_langs.insert("zh", i18n("Chinese")); + m_langs.insert("fr", i18n("French")); + m_langs.insert("de", i18n("German")); + m_langs.insert("it", i18n("Italian")); + m_langs.insert("ja", i18n("Japanese")); + m_langs.insert("ko", i18n("Korean")); + m_langs.insert("pt", i18n("Portuguese")); + m_langs.insert("ru", i18n("Russian")); + m_langs.insert("es", i18n("Spanish")); + + /* English to .. */ + m_supported["babelfish"].append("en_zh"); + m_supported["babelfish"].append("en_fr"); + m_supported["babelfish"].append("en_de"); + m_supported["babelfish"].append("en_it"); + m_supported["babelfish"].append("en_ja"); + m_supported["babelfish"].append("en_ko"); + m_supported["babelfish"].append("en_pt"); + m_supported["babelfish"].append("en_es"); + /* Chinese to .. */ + m_supported["babelfish"].append("zh_en"); + /* French to ... */ + m_supported["babelfish"].append("fr_en"); + m_supported["babelfish"].append("fr_de"); + /* German to ... */ + m_supported["babelfish"].append("de_en"); + m_supported["babelfish"].append("de_fr"); + + m_supported["babelfish"].append("it_en"); + m_supported["babelfish"].append("ja_en"); + m_supported["babelfish"].append("ko_en"); + m_supported["babelfish"].append("pt_en"); + m_supported["babelfish"].append("ru_en"); + m_supported["babelfish"].append("es_en"); + + /* Google Service */ + m_supported["google"].append("en_de"); + m_supported["google"].append("en_es"); + m_supported["google"].append("en_fr"); + m_supported["google"].append("en_it"); + m_supported["google"].append("en_pt"); + m_supported["google"].append("de_en"); + m_supported["google"].append("de_fr"); + m_supported["google"].append("es_en"); + m_supported["google"].append("fr_en"); + m_supported["google"].append("fr_de"); + m_supported["google"].append("it_en"); + m_supported["google"].append("pt_en"); + + QMap::ConstIterator i; + + for ( i = m_langs.begin(); i != m_langs.end() ; ++i ) + { + m_langIntKeyMap[m_lc] = i.key(); + m_langKeyIntMap[i.key()] = m_lc; + m_lc++; + } + + for ( i = m_services.begin(); i != m_services.end() ; ++i ) + { + m_servicesIntKeyMap[m_sc] = i.key(); + m_servicesKeyIntMap[i.key()] = m_sc; + m_sc++; + } +} diff --git a/kopete/plugins/translator/translatorlanguages.h b/kopete/plugins/translator/translatorlanguages.h new file mode 100644 index 00000000..cbd6385f --- /dev/null +++ b/kopete/plugins/translator/translatorlanguages.h @@ -0,0 +1,94 @@ +/* + translatorlanguages.h + + Kopete Translatorfish Translator plugin + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2003 by Olivier Goffart + Copyright (c) 2003 by Matt Rogers + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef TRANSLATORLANGUAGES_H_ +#define TRANSLATORLANGUAGES_H_ + +#include +#include + +class QString; + + +class TranslatorLanguages +{ +public: + TranslatorLanguages(); + + /*************************************************************************** + * Language APIs * + ***************************************************************************/ + + const QString& languageName( const QString &key ) + { return m_langs[key]; }; + + const int languageIndex ( const QString &key ) + { return m_langKeyIntMap[key]; }; + + const QString& languageKey( const int index ) + { return m_langIntKeyMap[index]; }; + + const QMap& languagesMap() + { return m_langs; }; + + const QMap& servicesMap() + { return m_services; }; + + const QStringList& supported( const QString &servicekey ) + { return m_supported[servicekey]; }; + + const int serviceIndex ( const QString &key ) + { return m_servicesKeyIntMap[key]; }; + + const QString& serviceKey( const int index ) + { return m_servicesIntKeyMap[index]; }; + + int numLanguages() { return m_lc; }; + + int numServices() { return m_sc; }; + +private: + + /* Known Languages key -> desc ie: en -> English */ + QMap< QString, QString> m_langs; + + /* Known Services key -> desc ie: en -> English */ + QMap< QString, QString> m_services; + + /* Supported translations per service, src_dst format ( ie: en_es )*/ + QMap< QString, QStringList > m_supported; + + /* int to lang key and viceversa*/ + QMap m_langIntKeyMap; + QMap m_langKeyIntMap; + + /* int to services key and viceversa*/ + QMap m_servicesIntKeyMap; + QMap m_servicesKeyIntMap; + + /* Lang counter */ + int m_lc; + /* Service counter */ + int m_sc; + +}; + +#endif diff --git a/kopete/plugins/translator/translatorplugin.cpp b/kopete/plugins/translator/translatorplugin.cpp new file mode 100644 index 00000000..694f0bd1 --- /dev/null +++ b/kopete/plugins/translator/translatorplugin.cpp @@ -0,0 +1,402 @@ +/* + translatorplugin.cpp + + Kopete Translator plugin + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2004 by Olivier Goffart + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kopetemetacontact.h" +#include "kopetecontactlist.h" +#include "kopetemessagemanagerfactory.h" +#include "kopetecontact.h" + +#include "translatorplugin.h" +#include "translatordialog.h" +#include "translatorguiclient.h" +#include "translatorlanguages.h" + +typedef KGenericFactory TranslatorPluginFactory; +#if KDE_IS_VERSION(3,2,90) +static const KAboutData aboutdata("kopete_translator", I18N_NOOP("Translator") , "1.0" ); +K_EXPORT_COMPONENT_FACTORY( kopete_translator, TranslatorPluginFactory( &aboutdata ) ) +#else +K_EXPORT_COMPONENT_FACTORY( kopete_translator, TranslatorPluginFactory( "kopete_translator" ) ) +#endif + +TranslatorPlugin::TranslatorPlugin( QObject *parent, const char *name, const QStringList & /* args */ ) +: Kopete::Plugin( TranslatorPluginFactory::instance(), parent, name ) +{ + kdDebug( 14308 ) << k_funcinfo << endl; + + + if ( pluginStatic_ ) + kdWarning( 14308 ) << k_funcinfo << "Translator already initialized" << endl; + else + pluginStatic_ = this; + + m_languages = new TranslatorLanguages; + + connect( Kopete::ChatSessionManager::self(), SIGNAL( aboutToDisplay( Kopete::Message & ) ), + this, SLOT( slotIncomingMessage( Kopete::Message & ) ) ); + connect( Kopete::ChatSessionManager::self(), SIGNAL( aboutToSend( Kopete::Message & ) ), + this, SLOT( slotOutgoingMessage( Kopete::Message & ) ) ); + connect( Kopete::ChatSessionManager::self(), SIGNAL( chatSessionCreated( Kopete::ChatSession * ) ), + this, SLOT( slotNewKMM( Kopete::ChatSession * ) ) ); + + QStringList keys; + QMap m = m_languages->languagesMap(); + for ( int k = 0; k <= m_languages->numLanguages(); k++ ) + keys << m[ m_languages->languageKey( k ) ]; + + m_actionLanguage = new KSelectAction( i18n( "Set &Language" ), "locale", 0, actionCollection(), "contactLanguage" ); + m_actionLanguage->setItems( keys ); + connect( m_actionLanguage, SIGNAL( activated() ), this, SLOT(slotSetLanguage() ) ); + connect( Kopete::ContactList::self(), SIGNAL( metaContactSelected( bool ) ), this, SLOT( slotSelectionChanged( bool ) ) ); + + setXMLFile( "translatorui.rc" ); + + //Add GUI action to all already existing kmm (if the plugin is launched when kopete already rining) + QValueList sessions = Kopete::ChatSessionManager::self()->sessions(); + for (QValueListIterator it= sessions.begin(); it!=sessions.end() ; ++it) + slotNewKMM( *it ); + + loadSettings(); + connect( this, SIGNAL( settingsChanged() ), this, SLOT( loadSettings() ) ); +} + +TranslatorPlugin::~TranslatorPlugin() +{ + kdDebug( 14308 ) << k_funcinfo << endl; + pluginStatic_ = 0L; +} + +TranslatorPlugin* TranslatorPlugin::plugin() +{ + return pluginStatic_; +} + +TranslatorPlugin* TranslatorPlugin::pluginStatic_ = 0L; + +void TranslatorPlugin::loadSettings() +{ + KConfig *config = KGlobal::config(); + int mode = 0; + + config->setGroup( "Translator Plugin" ); + m_myLang = m_languages->languageKey( config->readNumEntry( "myLang" , 0 ) ); + m_service = m_languages->serviceKey( config->readNumEntry( "Service", 0 ) ); + + if ( config->readBoolEntry( "IncomingDontTranslate", true ) ) + mode = 0; + else if ( config->readBoolEntry( "IncomingShowOriginal", false ) ) + mode = 1; + else if ( config->readBoolEntry( "IncomingTranslate", false ) ) + mode = 2; + + m_incomingMode = mode; + + if ( config->readBoolEntry( "OutgoingDontTranslate", true ) ) + mode = 0; + else if ( config->readBoolEntry( "OutgoingShowOriginal", false ) ) + mode = 1; + else if ( config->readBoolEntry( "OutgoingTranslate", false ) ) + mode = 2; + else if ( config->readBoolEntry( "OutgoingAsk", false ) ) + mode = 3; + + m_outgoingMode = mode; +} + +void TranslatorPlugin::slotSelectionChanged( bool b ) +{ + m_actionLanguage->setEnabled( b ); + + if ( !b ) + return; + + Kopete::MetaContact *m = Kopete::ContactList::self()->selectedMetaContacts().first(); + + if( !m ) + return; + + QString languageKey = m->pluginData( this, "languageKey" ); + if ( !languageKey.isEmpty() && languageKey != "null" ) + m_actionLanguage->setCurrentItem( m_languages->languageIndex( languageKey ) ); + else + m_actionLanguage->setCurrentItem( m_languages->languageIndex( "null" ) ); +} + +void TranslatorPlugin::slotNewKMM( Kopete::ChatSession *KMM ) +{ + new TranslatorGUIClient( KMM ); +} + +void TranslatorPlugin::slotIncomingMessage( Kopete::Message &msg ) +{ + if ( m_incomingMode == DontTranslate ) + return; + + QString src_lang; + QString dst_lang; + + if ( ( msg.direction() == Kopete::Message::Inbound ) && !msg.plainBody().isEmpty() ) + { + Kopete::MetaContact *from = msg.from()->metaContact(); + if ( !from ) + { +// kdDebug( 14308 ) << k_funcinfo << "No metaContact for source contact" << endl; + return; + } + src_lang = from->pluginData( this, "languageKey" ); + if( src_lang.isEmpty() || src_lang == "null" ) + { +// kdDebug( 14308 ) << k_funcinfo << "Cannot determine src Metacontact language (" << from->displayName() << ")" << endl; + return; + } + + dst_lang = m_myLang; + + sendTranslation( msg, translateMessage( msg.plainBody(), src_lang, dst_lang ) ); + } +} + +void TranslatorPlugin::slotOutgoingMessage( Kopete::Message &msg ) +{ + if ( m_outgoingMode == DontTranslate ) + return; + + QString src_lang; + QString dst_lang; + + if ( ( msg.direction() == Kopete::Message::Outbound ) && ( !msg.plainBody().isEmpty() ) ) + { + src_lang = m_myLang; + + // Sad, we have to consider only the first To: metacontact + Kopete::MetaContact *to = msg.to().first()->metaContact(); + if ( !to ) + { +// kdDebug( 14308 ) << k_funcinfo << "No metaContact for first contact" << endl; + return; + } + dst_lang = to->pluginData( this, "languageKey" ); + if ( dst_lang.isEmpty() || dst_lang == "null" ) + { +// kdDebug( 14308 ) << k_funcinfo << "Cannot determine dst Metacontact language (" << to->displayName() << ")" << endl; + return; + } + + sendTranslation( msg, translateMessage( msg.plainBody(), src_lang, dst_lang ) ); + } +} + +void TranslatorPlugin::translateMessage( const QString &msg, const QString &from, const QString &to, QObject *obj, const char* slot ) +{ + QSignal completeSignal; + completeSignal.connect( obj, slot ); + + QString result = translateMessage( msg, from, to ); + + if(!result.isNull()) + { + completeSignal.setValue( result ); + completeSignal.activate(); + } +} + +QString TranslatorPlugin::translateMessage( const QString &msg, const QString &from, const QString &to ) +{ + if ( from == to ) + { + kdDebug( 14308 ) << k_funcinfo << "Src and Dst languages are the same" << endl; + return QString::null; + } + + // We search for src_dst + if(! m_languages->supported( m_service ).contains( from + "_" + to ) ) + { + kdDebug( 14308 ) << k_funcinfo << from << "_" << to << " is not supported by service " << m_service << endl; + return QString::null; + } + + + if ( m_service == "babelfish" ) + return babelTranslateMessage( msg ,from, to ); + else if ( m_service == "google" ) + return googleTranslateMessage( msg ,from, to ); + else + return QString::null; +} + +QString TranslatorPlugin::googleTranslateMessage( const QString &msg, const QString &from, const QString &to ) +{ + KURL translatorURL ( "http://translate.google.com/translate_t"); + + QString body = KURL::encode_string( msg ); + QString lp = from + "|" + to; + + QCString postData = QString( "text=" + body + "&langpair=" + lp ).utf8(); + + QString gurl = "http://translate.google.com/translate_t?text=" + body +"&langpair=" + lp; + kdDebug(14308) << k_funcinfo << " URL: " << gurl << endl; + KURL geturl ( gurl ); + + KIO::TransferJob *job = KIO::get( geturl, false, true ); + //job = KIO::http_post( translatorURL, postData, true ); + + //job->addMetaData( "content-type", "application/x-www-form-urlencoded" ); + //job->addMetaData( "referrer", "http://www.google.com" ); + + QObject::connect( job, SIGNAL( data( KIO::Job *, const QByteArray & ) ), this, SLOT( slotDataReceived( KIO::Job *, const QByteArray & ) ) ); + QObject::connect( job, SIGNAL( result( KIO::Job * ) ), this, SLOT( slotJobDone( KIO::Job * ) ) ); + + // KIO is async and we use a sync API, so use the processEvents hack to work around that + // FIXME: We need to make the libkopete API async to get rid of this processEvents. + // It often causes crashes in the code. - Martijn + while ( !m_completed[ job ] ) + qApp->processEvents(); + + QString data = QString::fromLatin1( m_data[ job ] ); + + // After hacks, we need to clean + m_data.remove( job ); + m_completed.remove( job ); + +// kdDebug( 14308 ) << k_funcinfo << "Google response:"<< endl << data << endl; + + QRegExp re( "" ); + re.setMinimal( true ); + re.search( data ); + + return re.cap( 1 ); +} + +QString TranslatorPlugin::babelTranslateMessage( const QString &msg, const QString &from, const QString &to ) +{ + QString body = KURL::encode_string( msg); + QString lp = from + "_" + to; + QString gurl = "http://babelfish.altavista.com/babelfish/tr?enc=utf8&doit=done&tt=urltext&urltext=" + body + "&lp=" + lp; + KURL geturl ( gurl ); + + kdDebug( 14308 ) << k_funcinfo << "URL: " << gurl << endl; + + KIO::TransferJob *job = KIO::get( geturl, false, true ); + + QObject::connect( job, SIGNAL( data( KIO::Job *, const QByteArray & ) ), this, SLOT( slotDataReceived( KIO::Job *, const QByteArray & ) ) ); + QObject::connect( job, SIGNAL( result( KIO::Job * ) ), this, SLOT( slotJobDone( KIO::Job * ) ) ); + + // KIO is async and we use a sync API, so use the processEvents hack to work around that + // FIXME: We need to make the libkopete API async to get rid of this processEvents. + // It often causes crashes in the code. - Martijn + while ( !m_completed[ job ] ) + qApp->processEvents(); + + QString data = QString::fromUtf8( m_data[ job ] ); + + // After hacks, we need to clean + m_data.remove( job ); + m_completed.remove( job ); + + //kdDebug( 14308 ) << k_funcinfo << "Babelfish response: " << endl << data << endl; + + QRegExp re( "
    (.*)exec(); + msg.setBody( d->translatedText(), msg.format() ); + delete d; + break; + } + case DontTranslate: + default: + //do nothing + break; + }; +} + +void TranslatorPlugin::slotDataReceived ( KIO::Job *job, const QByteArray &data ) +{ + m_data[ job ] += QCString( data, data.size() + 1 ); +} + +void TranslatorPlugin::slotJobDone ( KIO::Job *job ) +{ + m_completed[ job ] = true; + QObject::disconnect( job, SIGNAL( data( KIO::Job *, const QByteArray & ) ), this, SLOT( slotDataReceived( KIO::Job *, const QByteArray & ) ) ); + QObject::disconnect( job, SIGNAL( result( KIO::Job * ) ), this, SLOT( slotJobDone( KIO::Job * ) ) ); +} + +void TranslatorPlugin::slotSetLanguage() +{ + Kopete::MetaContact *m = Kopete::ContactList::self()->selectedMetaContacts().first(); + if( m && m_actionLanguage ) + m->setPluginData( this, "languageKey", m_languages->languageKey( m_actionLanguage->currentItem() ) ); +} + +#include "translatorplugin.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/translator/translatorplugin.h b/kopete/plugins/translator/translatorplugin.h new file mode 100644 index 00000000..2a04b509 --- /dev/null +++ b/kopete/plugins/translator/translatorplugin.h @@ -0,0 +1,113 @@ +/* + translatorplugin.h + + Kopete Translatorfish Translator plugin + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2003 by Olivier Goffart + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef BABELFISHPLUGIN_H +#define BABELFISHPLUGIN_H + +#include +#include +#include +#include + + +#include + +#include "kopetemessage.h" +#include "kopeteplugin.h" + + +class QString; +class KSelectAction; + +namespace Kopete { class Message; } +namespace Kopete { class MetaContact; } +namespace Kopete { class ChatSession; } + +class TranslatorPreferences; +class TranslatorGUIClient; +class TranslatorLanguages; + +/** + * @author Duncan Mac-Vicar Prett + * + * Kopete Translator Plugin + */ +class TranslatorPlugin : public Kopete::Plugin +{ + Q_OBJECT + +friend class TranslatorGUIClient; + +public: + static TranslatorPlugin *plugin(); + + TranslatorPlugin( QObject *parent, const char *name, const QStringList &args ); + ~TranslatorPlugin(); + + enum TranslateMode + { + DontTranslate = 0, + ShowOriginal = 1, + JustTranslate = 2, + ShowDialog = 3 + }; + +private slots: + void slotIncomingMessage( Kopete::Message& msg ); + void slotOutgoingMessage( Kopete::Message& msg ); + void slotDataReceived ( KIO::Job *, const QByteArray &data); + void slotJobDone ( KIO::Job *); + void slotSetLanguage(); + void slotSelectionChanged(bool); + void slotNewKMM(Kopete::ChatSession *); + void loadSettings(); + +public: + QString translateMessage( const QString &, const QString &, const QString & ); + void translateMessage( const QString &, const QString &, const QString & , QObject * , const char*); + +protected: + QString googleTranslateMessage( const QString &, const QString &, const QString & ); + QString babelTranslateMessage(const QString &, const QString &, const QString & ); + +private: + + QMap< KIO::Job *, QCString> m_data; + QMap< KIO::Job *, bool> m_completed; + + KSelectAction* m_actionLanguage; + + static TranslatorPlugin* pluginStatic_; + TranslatorLanguages *m_languages; + + //Settings + QString m_myLang; + QString m_service; + unsigned int m_outgoingMode; + unsigned int m_incomingMode; + +private: + void sendTranslation(Kopete::Message &msg, const QString &translated); +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/plugins/translator/translatorprefs.cpp b/kopete/plugins/translator/translatorprefs.cpp new file mode 100644 index 00000000..cb67c4c6 --- /dev/null +++ b/kopete/plugins/translator/translatorprefs.cpp @@ -0,0 +1,52 @@ +/* + translatorprefs.cpp - Kopete Translator plugin + + Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002-2003 by Olivier Goffart + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + + +#include +#include "kcautoconfigmodule.h" +#include "translatorprefsbase.h" +#include "translatorlanguages.h" +#include + + +class TranslatorPreferences; +typedef KGenericFactory TranslatorConfigFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_translator, TranslatorConfigFactory( "kcm_kopete_translator" ) ) + +class TranslatorPreferences : public KCAutoConfigModule +{ +public: + TranslatorPreferences( QWidget *parent = 0, const char * = 0, const QStringList &args = QStringList() ) : KCAutoConfigModule( TranslatorConfigFactory::instance(), parent, args ) + { + TranslatorPrefsUI *preferencesDialog = new TranslatorPrefsUI(this); + + TranslatorLanguages languages; + QMap::ConstIterator i; + QMap m; + + m = languages.languagesMap(); + for ( i = m.begin(); i != m.end() ; ++i ) + preferencesDialog->myLang->insertItem( i.data(), languages.languageIndex(i.key()) ); + + m = languages.servicesMap(); + for ( i = m.begin(); i != m.end() ; ++i ) + preferencesDialog->Service->insertItem( i.data(), languages.serviceIndex(i.key()) ); + + setMainWidget( preferencesDialog , "Translator Plugin"); + } +}; + diff --git a/kopete/plugins/translator/translatorprefsbase.ui b/kopete/plugins/translator/translatorprefsbase.ui new file mode 100644 index 00000000..56b75543 --- /dev/null +++ b/kopete/plugins/translator/translatorprefsbase.ui @@ -0,0 +1,191 @@ + +TranslatorPrefsUI +Duncan Mac-Vicar P. + + + TranslatorPrefsUI + + + + 0 + 0 + 401 + 417 + + + + + 5 + 5 + 1 + 0 + + + + + unnamed + + + + Service + + + + + TextLabel2_2 + + + Translation service: + + + + + TextLabel2 + + + Default native language: + + + + + myLang + + + + 1 + 0 + 1 + 0 + + + + + + IncomingMessages + + + Incoming Messages + + + + unnamed + + + + IncomingDontTranslate + + + Do not translate + + + true + + + 0 + + + + + IncomingShowOriginal + + + Show the original message + + + false + + + 1 + + + + + IncomingTranslate + + + Translate directly + + + 2 + + + + + + + OutgoingMessages + + + Outgoing Messages + + + + unnamed + + + + OutgoingDontTranslate + + + Do not translate + + + true + + + 0 + + + + + OutgoingShowOriginal + + + Show the original message + + + false + + + 1 + + + + + OutgoingTranslate + + + Translate directly + + + + + OutgoingAsk + + + Show dialog before sending + + + + + + + Spacer2 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + + diff --git a/kopete/plugins/translator/translatorui.rc b/kopete/plugins/translator/translatorui.rc new file mode 100644 index 00000000..d583b6a4 --- /dev/null +++ b/kopete/plugins/translator/translatorui.rc @@ -0,0 +1,12 @@ + + + + + &Edit + + + + + + + diff --git a/kopete/plugins/webpresence/DESIGN b/kopete/plugins/webpresence/DESIGN new file mode 100644 index 00000000..d62c9c34 --- /dev/null +++ b/kopete/plugins/webpresence/DESIGN @@ -0,0 +1,12 @@ +Kopete Web Presence Plugin + +What It Does +Provides a view of the current state of your contact list as a webpage. + +How It Does It +Every so often, it writes a file containing a snapshot of who is online and who is not in your contactlist to a location you specify. This can be a local file, an FTP server, a HTTP server, or anywhere else that KIO can access. + +Use KIO::NetAccess to upload the files! + +Getting Info about Local User's Status +Goal is to allow ppl who don't have us on their contactlist to see what our current status is and what our UIN/id is for each protocol. So we need to know (protocol, uin, status). diff --git a/kopete/plugins/webpresence/Makefile.am b/kopete/plugins/webpresence/Makefile.am new file mode 100644 index 00000000..7f859379 --- /dev/null +++ b/kopete/plugins/webpresence/Makefile.am @@ -0,0 +1,27 @@ +METASOURCES = AUTO + +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) $(LIBXML_CFLAGS) $(LIBXSLT_CFLAGS) + +kde_module_LTLIBRARIES = kopete_webpresence.la kcm_kopete_webpresence.la + +kopete_webpresence_la_SOURCES = webpresenceplugin.cpp + +kopete_webpresence_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kopete_webpresence_la_LIBADD = ../../libkopete/libkopete.la $(LIBXML_LIBS) $(LIBXSLT_LIBS) + +kcm_kopete_webpresence_la_SOURCES = webpresencepreferences.cpp webpresenceprefs.ui +kcm_kopete_webpresence_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_webpresence_la_LIBADD = ../../libkopete/libkopete.la $(LIB_KUTILS) + +service_DATA = kopete_webpresence.desktop +servicedir = $(kde_servicesdir) + +kcm_DATA = kopete_webpresence_config.desktop +kcmdir = $(kde_servicesdir)/kconfiguredialog + +mydata_DATA = webpresence_html.xsl\ + webpresence_html_images.xsl\ + webpresence_xhtml.xsl\ + webpresence_xhtml_images.xsl +mydatadir = $(kde_datadir)/kopete/webpresence +EXTRA_DIST = $(mydata_DATA) diff --git a/kopete/plugins/webpresence/TODO b/kopete/plugins/webpresence/TODO new file mode 100644 index 00000000..28b78c3c --- /dev/null +++ b/kopete/plugins/webpresence/TODO @@ -0,0 +1,5 @@ +1) Investigate how to include the plugin's output server side to give users a start. +1a) XSLT processing? + +2) Icon + diff --git a/kopete/plugins/webpresence/kopete_webpresence.desktop b/kopete/plugins/webpresence/kopete_webpresence.desktop new file mode 100644 index 00000000..85068ce2 --- /dev/null +++ b/kopete/plugins/webpresence/kopete_webpresence.desktop @@ -0,0 +1,120 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=html +ServiceTypes=Kopete/Plugin +X-KDE-Library=kopete_webpresence +X-KDE-PluginInfo-Author=Will Stephenson +X-KDE-PluginInfo-Email=will@stevello.free-online.co.uk +X-KDE-PluginInfo-Name=kopete_webpresence +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://www.cs.ncl.ac.uk/old/people/william.stephenson +X-KDE-PluginInfo-Category=Plugins +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Comment=Show the status of (parts of) your contact list on a webpage +Comment[ar]=يظهر حالة أجزاء من قائمة أتصالاتك على صÙحة الشبكة +Comment[be]=Паказвае ÑÐ¿Ñ–Ñ ÐºÐ°Ð½Ñ‚Ð°ÐºÑ‚Ð°Ñž ці Ñго чаÑтку на Ñтаронцы Сеціва +Comment[bg]=ПриÑтавка за показвана ÑÑŠÑтоÑнието на (чаÑÑ‚ от) ÑпиÑъка Ñ ÐºÐ¾Ð½Ñ‚Ð°ÐºÑ‚Ð¸ като уеб Ñтраница +Comment[bn]=à¦à¦•à¦Ÿà¦¿ ওয়েবপেজে আপনার যোগাযোগ তালিকার (à¦à¦° অংশের) অবসà§à¦¥à¦¾ পà§à¦°à¦¦à¦°à§à¦¶à¦¨ করে +Comment[bs]=Pokazuje status (dijelova) vaÅ¡e kontakt liste na web stranici +Comment[ca]=Mostra l'estatus de (parts de) la vostra llista de contactes a una pàgina web +Comment[cs]=Zobrazí stav (Äásti) seznamu kontaktů na webu +Comment[cy]=Dangos cyflwr o (rannau o) eich rhestr cysylltiadau ar dudalen wê +Comment[da]=Vis status for (dele af) din kontaktliste pÃ¥ en netside +Comment[de]=Zeigt den Status (von Teilen) der eigenen Kontaktliste auf einer Internetseite +Comment[el]=Εμφάνιση της κατάστασης της λίστας επαφών σας σε ιστοσελίδα +Comment[es]=Mostrar el estado de (partes de) su lista de contactos en una página web +Comment[et]=Näitab (vähemalt osa) kontaktide staatust veebileheküljel +Comment[eu]=Erakutsi zure kontaktu zerrendaren (edo zati baten) egoera web orri batean +Comment[fa]=وضعیت )اجزایی از( Ùهرست تماس شما را روی صÙØ­Û€ وب نمایش می‌دهد +Comment[fi]=Näytä kontaktien tila www-sivulla +Comment[fr]=Affiche l'état de connexion de votre liste (ou d'une partie) sur une page internet +Comment[gl]=Mostra o status da túa lista de contactos (ou parte) nunha páxina web +Comment[he]=×ž×¦×™× ×ת מצב ההתחברות של ×—×‘×¨×™× ×‘×¨×©×™×ž×ª הקשר שלך )×ו של חלק×( בעמוד WEB. +Comment[hi]=वेब पृषà¥à¤  पर आपकी समà¥à¤ªà¤°à¥à¤• सूची की सà¥à¤¥à¤¿à¤¤à¤¿ (या आंशिक) दिखाठ+Comment[hr]=Prikazivanje statusa (dijelova) vaÅ¡e liste kontakata na web stranici +Comment[hu]=A partnerlista-tagok állapotának megjelenítése weboldalon +Comment[is]=Sýnir stöðu (hluta) lista þinna á vefsíðu +Comment[it]=Mostra lo stato della (o parte della) tua lista contatti su una pagina web +Comment[ja]=コンタクトリスト (ã®ä¸€éƒ¨) ã®çŠ¶æ…‹ã‚’ウェブページã«è¡¨ç¤º +Comment[ka]=სტáƒáƒ¢áƒ£áƒ¡áƒ˜áƒ¡ ვებ გვერდზე ჩვენებრ+Comment[kk]=Контакттарыңыздың тізімінің (не оның бөлігінің) күйін веб парақта көрÑетеді +Comment[km]=បង្ហាញ​ស្ážáž¶áž“ភាព (ផ្នែក) នៃ​បញ្ជី​ទំនាក់​ទំនង​របស់​អ្នក​នៅ​លើ​ទំពáŸážšâ€‹ážœáŸ‰áŸáž” +Comment[lt]=Rodyti kontaktų sÄ…raÅ¡o (sÄ…raÅ¡o dalies) bÅ«klÄ™ internetiniame puslapyje +Comment[mk]=Го прикажува ÑтатуÑот на (деловите на) вашата лиÑта Ñо контакти на вебÑтраница +Comment[nb]=Vis tilstanden til (deler av) kontaktlista de pÃ¥ en nettside +Comment[nds]=Wiest den Status (vun en poor) vun Dien Kontakten op en Nettsiet +Comment[ne]=वेबपृषà¥à¤ à¤®à¤¾ तपाईà¤à¤•à¥‹ समà¥à¤ªà¤°à¥à¤• सूची (भागको) वसà¥à¤¤à¥à¤¸à¥à¤¥à¤¿à¤¤à¤¿ देखाउनà¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Toont de status van (delen van) uw contactenlijst op een webpagina +Comment[nn]=Vis tilstanden til (delar av) kontaktlista pÃ¥ ei nettside +Comment[pl]=Pokazuje status (części) Twojej listy kontaktów na stronie WWW +Comment[pt]=Mostra o estado (de partes) da sua lista de contactos numa página Web +Comment[pt_BR]=Mostra o status de (ou parte de) sua lista de contatos em uma página web +Comment[ru]=Отображает ÑоÑтоÑние вашего ÑпиÑка контактов (или его чаÑти) на Ñтранице в Сети +Comment[sk]=Zobrazenie stavu (Äasti) zo zoznamu kontaktov na webovej stránke +Comment[sl]=Pokaže stanje (dela) vaÅ¡ega seznama stikov na spletni strani +Comment[sr]=Приказивање ÑтатуÑа (делова) ваше лиÑте контаката на веб Ñтраници +Comment[sr@Latn]=Prikazivanje statusa (delova) vaÅ¡e liste kontakata na veb stranici +Comment[sv]=Visa status för (delar av) din kontaktlista pÃ¥ en webbsida +Comment[ta]=இணைய பகà¯à®•à®¤à¯à®¤à®¿à®²à¯ உஙà¯à®•à®³à¯ தொடரà¯à®ªà®¾à®³à®°à®¿à®©à¯ நிலைபà¯à®ªà®Ÿà¯à®Ÿà®¿à®¯à®²à¯ˆ காடà¯à®Ÿà¯ +Comment[tg]=Ҳолати (қиÑме аз) рӯйхати пайваÑтшавии шуморо дар Ñаҳифаи шабака нишон медиҳад +Comment[tr]=BaÄŸlantı listesinde web sayfası olanların (bölümler) durumunu göster +Comment[uk]=Показує Ñтан вашого ÑпиÑку контактів (або його чаÑтини) на Ñторінці у Тенетах +Comment[zh_CN]=在网页上显示您è”ç³»äººçš„çŠ¶æ€ +Comment[zh_HK]=在網é ä¸Šé¡¯ç¤ºæ‚¨ï¼ˆä¸€éƒ¨ä»½ï¼‰è¯çµ¡äººæ¸…單的狀態 +Comment[zh_TW]=在網é ä¸Šé¡¯ç¤ºï¼ˆéƒ¨ä»½ï¼‰æ‚¨çš„è¯çµ¡äººçš„狀態 +Name=Web Presence +Name[ar]=موقع على الشبكة +Name[be]=Ð¡ÐµÑ†Ñ–ÑžÐ½Ð°Ñ Ð¿Ñ€Ñ‹ÑутнаÑць +Name[bg]=Уеб приÑÑŠÑтвие +Name[bn]=ওয়েব উপসà§à¦¥à¦¿à¦¤à¦¿ +Name[bs]=Web prisustvo +Name[ca]=Web de presència +Name[cs]=Přítomnost na webu +Name[cy]=Presenoldeb Gwê +Name[da]=WWW-nærvær +Name[de]=Web-Präsenz +Name[el]=ΠαÏουσία σε ιστοσελίδα +Name[eo]=TTT-Prezenco +Name[es]=Presencia Web +Name[et]=Veebinimekiri +Name[fa]=حضور وب +Name[fi]=WWW-paikallaolo +Name[fr]=Présence sur le web +Name[gl]=Presencia na web +Name[he]=נוכחות רשת +Name[hi]=वेब उपसà¥à¤¥à¤¿à¤¤à¤¿ +Name[hr]=Prisutnost na webu +Name[hu]=Webes jelenlét +Name[is]=à vefnum +Name[it]=Presenza web +Name[ja]=ウェブプレゼンス +Name[ka]=ვებ თვისებები +Name[kk]=Вебте қатыÑу +Name[km]=ážœážáŸ’ážáž˜áž¶áž“​វ៉áŸáž” +Name[lt]=Rodyti internete +Name[mk]=ПриÑуÑтво на веб +Name[nb]=Profil pÃ¥ nettet +Name[nds]=Nett-Praatschap +Name[ne]=वेब उपसà¥à¤¥à¤¿à¤¤à¤¿ +Name[nl]=Web-aanwezigheid +Name[nn]=Profil pÃ¥ nettet +Name[pl]=Status na WWW +Name[pt]=Presença na Web +Name[pt_BR]=Presença Web +Name[ro]=Prezenţă Web +Name[ru]=ПриÑутÑтвие в Сети +Name[sk]=PrítomnosÅ¥ na webe +Name[sl]=Spletna prisotnost +Name[sr]=ПриÑутноÑÑ‚ на Вебу +Name[sr@Latn]=Prisutnost na Vebu +Name[sv]=Webbnärvaro +Name[ta]=இணைய இரà¯à®ªà¯à®ªà¯ +Name[tg]=МавҷудиÑти Web +Name[tr]=Web Hazır Bulunması +Name[uk]=ПриÑутніÑÑ‚ÑŒ у Тенет +Name[zh_CN]=Web çŠ¶æ€ +Name[zh_HK]=網上狀態 +Name[zh_TW]=網é é€£ç·š diff --git a/kopete/plugins/webpresence/kopete_webpresence_config.desktop b/kopete/plugins/webpresence/kopete_webpresence_config.desktop new file mode 100644 index 00000000..a714d602 --- /dev/null +++ b/kopete/plugins/webpresence/kopete_webpresence_config.desktop @@ -0,0 +1,116 @@ +[Desktop Entry] +Type=Service +Icon=html +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_webpresence +X-KDE-FactoryName=WebPrecencePreferencesFactory +X-KDE-ParentApp=kopete_webpresence +X-KDE-ParentComponents=kopete_webpresence + +Comment=Show the status of (parts of) your contact list on a web page +Comment[ar]=يظهر حالة أجزاء من قائمة أتصالاتك على الشبكة +Comment[be]=Паказвае Ñтан ÑпіÑа кантактаў ці Ñго чаÑткі на Ñтаронцы Сеціва +Comment[bg]=ПриÑтавка за показвана ÑÑŠÑтоÑнието на (чаÑÑ‚ от) ÑпиÑъка Ñ ÐºÐ¾Ð½Ñ‚Ð°ÐºÑ‚Ð¸ като уеб Ñтраница +Comment[bn]=à¦à¦•à¦Ÿà¦¿ ওয়েবপেজে আপনার যোগাযোগ তালিকার (à¦à¦° অংশের) অবসà§à¦¥à¦¾ পà§à¦°à¦¦à¦°à§à¦¶à¦¨ করে +Comment[bs]=Pokazuje status (dijelova) vaÅ¡e kontakt liste na web stranici +Comment[ca]=Mostra l'estatus de (parts de) la vostra llista de contactes sobre una pàgina web +Comment[cs]=Zobrazí stav (Äásti) seznamu kontaktů na webu +Comment[cy]=Dangos cyflwr o (rannau o) eich rhestr cysylltiadau ar dudalen wê +Comment[da]=Vis status for (dele af) din kontaktliste pÃ¥ en netside +Comment[de]=Zeigt den Status (von Teilen) der eigenen Kontaktliste auf einer Internetseite +Comment[el]=Εμφάνιση της κατάστασης της λίστας επαφών σας σε ιστοσελίδα +Comment[es]=Mostrar el estado de (partes de) su lista de contactos en una página web +Comment[et]=Näitab (vähemalt osa) kontaktide staatust veebileheküljel +Comment[eu]=Erakutsi zure kontaktu zerrendaren (edo zati baten) egoera web orri batean +Comment[fa]=وضعیت )اجزایی از( Ùهرست تماس شما را روی صÙØ­Û€ وب نمایش می‌دهد +Comment[fi]=Näytä kontaktien tila www-sivulla +Comment[fr]=Afficher l'état de connexion de votre liste (ou d'une partie) sur une page internet +Comment[gl]=Amosar o estado da (parte da) súa lista de contactos nunha páxina web +Comment[he]=×ž×¦×™× ×ת מצב ההתחברות של ×—×‘×¨×™× ×‘×¨×©×™×ž×ª הקשר שלך )×ו של חלק×( בעמוד WEB. +Comment[hi]=वेब पृषà¥à¤  पर आपकी समà¥à¤ªà¤°à¥à¤• सूची की सà¥à¤¥à¤¿à¤¤à¤¿ (या आंशिक) दिखाठ+Comment[hr]=Prikazivanje statusa (dijelova) vaÅ¡e liste kontakata na web stranici +Comment[hu]=A partnerek állapotának megjelenítése weboldalon +Comment[is]=Sýnir stöðu (hluta) lista þinna á vefsíðu +Comment[it]=Mostra lo stato (o una parte) della tua lista contatti su una pagina web +Comment[ja]=コンタクトリスト (ã®ä¸€éƒ¨) ã®çŠ¶æ…‹ã‚’ウェブページã«è¡¨ç¤º +Comment[ka]=სტáƒáƒ¢áƒ£áƒ¡áƒ˜áƒ¡ ვებ გვერდზე ჩვენებრ+Comment[kk]=Контакттарыңыздың тізімінің (не оның бөлігінің) күйін веб парақта көрÑетеді +Comment[km]=បង្ហាញ​ស្ážáž¶áž“ភាព (ផ្នែក) នៃ​បញ្ជី​ទំនាក់​ទំនង​របស់​អ្នក​នៅ​លើ​ទំពáŸážšâ€‹ážœáŸ‰áŸáž” +Comment[lt]=Rodyti kontaktų sÄ…raÅ¡o (sÄ…raÅ¡o dalies) bÅ«klÄ™ internetiniame puslapyje +Comment[mk]=Го прикажува ÑтатуÑот на (деловите на) вашата лиÑта Ñо контакти на вебÑтраница +Comment[nb]=Vis tilstanden til (deler av) kontaktlista de pÃ¥ en nettside +Comment[nds]=Wiest den Status (vun en poor) vun Dien Kontakten op en Nettsiet +Comment[ne]=वेबपृषà¥à¤ à¤®à¤¾ तपाईà¤à¤•à¥‹ समà¥à¤ªà¤°à¥à¤• सूची (भागको) वसà¥à¤¤à¥à¤¸à¥à¤¥à¤¿à¤¤à¤¿ देखाउनà¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Toont de status van (delen van) uw contactenlijst op een webpagina +Comment[nn]=Vis tilstanden til (delar av) kontaktlista pÃ¥ ei nettside +Comment[pl]=Pokazuje status (części) Twojej listy kontaktów na stronie WWW +Comment[pt]=Mostra o estado (de partes) da sua lista de contactos numa página Web +Comment[pt_BR]=Mostra o status de (ou parte de) sua lista de contatos em uma página web +Comment[ru]=Отображает ÑоÑтоÑние вашего ÑпиÑка контактов (или его чаÑти) на Ñтранице в Сети +Comment[sk]=Zobrazenie stavu (Äasti) zo zoznamu kontaktov na webovej stránke +Comment[sl]=Pokaže stanje (dela) vaÅ¡ega seznama stikov na spletni strani +Comment[sr]=Приказивање ÑтатуÑа (делова) ваше лиÑте контаката на веб Ñтраници +Comment[sr@Latn]=Prikazivanje statusa (delova) vaÅ¡e liste kontakata na veb stranici +Comment[sv]=Visa status för (delar av) din kontaktlista pÃ¥ en webbsida +Comment[ta]=இணைய பகà¯à®•à®¤à¯à®¤à®¿à®²à¯ உஙà¯à®•à®³à¯ தொடரà¯à®ªà®¾à®³à®°à¯ நிலைபà¯à®ªà®Ÿà¯à®Ÿà®¿à®¯à®²à¯ˆ காடà¯à®Ÿà¯ +Comment[tg]=Ҳолати (қиÑме аз) рӯйхати пайваÑтшавии шуморо дар Ñаҳифаи шабака нишон медиҳад +Comment[tr]=BaÄŸlantı listesinde web sayfası olanların (bölümler) durumunu göster +Comment[uk]=Показує Ñтан вашого ÑпиÑку контактів (або його чаÑтини) на Ñторінці у Тенетах +Comment[zh_CN]=在网页上显示您è”ç³»äººçš„çŠ¶æ€ +Comment[zh_HK]=在網é ä¸Šé¡¯ç¤ºæ‚¨ï¼ˆä¸€éƒ¨ä»½ï¼‰è¯çµ¡äººæ¸…單的狀態 +Comment[zh_TW]=在網é ä¸Šé¡¯ç¤ºï¼ˆéƒ¨ä»½ï¼‰æ‚¨çš„è¯çµ¡äººçš„狀態 +Name=Web Presence +Name[ar]=موقع على الشبكة +Name[be]=Ð¡ÐµÑ†Ñ–ÑžÐ½Ð°Ñ Ð¿Ñ€Ñ‹ÑутнаÑць +Name[bg]=Уеб приÑÑŠÑтвие +Name[bn]=ওয়েব উপসà§à¦¥à¦¿à¦¤à¦¿ +Name[bs]=Web prisustvo +Name[ca]=Web de presència +Name[cs]=Přítomnost na webu +Name[cy]=Presenoldeb Gwê +Name[da]=WWW-nærvær +Name[de]=Web-Präsenz +Name[el]=ΠαÏουσία σε ιστοσελίδα +Name[eo]=TTT-Prezenco +Name[es]=Presencia Web +Name[et]=Veebinimekiri +Name[fa]=حضور وب +Name[fi]=WWW-paikallaolo +Name[fr]=Présence sur le web +Name[gl]=Presencia na web +Name[he]=נוכחות רשת +Name[hi]=वेब उपसà¥à¤¥à¤¿à¤¤à¤¿ +Name[hr]=Prisutnost na webu +Name[hu]=Webes jelenlét +Name[is]=à vefnum +Name[it]=Presenza web +Name[ja]=ウェブプレゼンス +Name[ka]=ვებ თვისებები +Name[kk]=Вебте қатыÑу +Name[km]=ážœážáŸ’ážáž˜áž¶áž“​វ៉áŸáž” +Name[lt]=Rodyti internete +Name[mk]=ПриÑуÑтво на веб +Name[nb]=Profil pÃ¥ nettet +Name[nds]=Nett-Praatschap +Name[ne]=वेब उपसà¥à¤¥à¤¿à¤¤à¤¿ +Name[nl]=Web-aanwezigheid +Name[nn]=Profil pÃ¥ nettet +Name[pl]=Status na WWW +Name[pt]=Presença na Web +Name[pt_BR]=Presença Web +Name[ro]=Prezenţă Web +Name[ru]=ПриÑутÑтвие в Сети +Name[sk]=PrítomnosÅ¥ na webe +Name[sl]=Spletna prisotnost +Name[sr]=ПриÑутноÑÑ‚ на Вебу +Name[sr@Latn]=Prisutnost na Vebu +Name[sv]=Webbnärvaro +Name[ta]=இணைய இரà¯à®ªà¯à®ªà¯ +Name[tg]=МавҷудиÑти Web +Name[tr]=Web Hazır Bulunması +Name[uk]=ПриÑутніÑÑ‚ÑŒ у Тенет +Name[zh_CN]=Web çŠ¶æ€ +Name[zh_HK]=網上狀態 +Name[zh_TW]=網é é€£ç·š diff --git a/kopete/plugins/webpresence/webpresence_html.xsl b/kopete/plugins/webpresence/webpresence_html.xsl new file mode 100644 index 00000000..f768ccf5 --- /dev/null +++ b/kopete/plugins/webpresence/webpresence_html.xsl @@ -0,0 +1,140 @@ + + + + + + + + + + + My IM Status + + +

    + +
    + + Last update at: + + + + +
    + + + + + + + + + + + +
    + + + + + + + +
    +
    + + + + + AIM + + + MSN + + + ICQ + + + Jabber + + + Yahoo + + + Gadu-Gadu + + + WinPopup + + + SMS + + + IRC + + + Unknown + + + + + + + + + + + + + + + + + + + + + + ( + + + + + : + + + + + + + ) + + + + + + + + ( + + ) + + + + + + + + +
    + + diff --git a/kopete/plugins/webpresence/webpresence_html_images.xsl b/kopete/plugins/webpresence/webpresence_html_images.xsl new file mode 100644 index 00000000..5b707046 --- /dev/null +++ b/kopete/plugins/webpresence/webpresence_html_images.xsl @@ -0,0 +1,59 @@ + + + + + + + + + + + + images + + + + + MSN + + + ICQ + + + Jabber + + + Yahoo + + + AIM + + + IRC + + + SMS + + + Gadu-Gadu + + + WinPopup + + + + + + + diff --git a/kopete/plugins/webpresence/webpresence_xhtml.xsl b/kopete/plugins/webpresence/webpresence_xhtml.xsl new file mode 100644 index 00000000..9d749c14 --- /dev/null +++ b/kopete/plugins/webpresence/webpresence_xhtml.xsl @@ -0,0 +1,139 @@ + + + + + + + + + + + My IM Status + + +

    + +
    +

    + Last update at: + +

    + + +
    + + + + + + + + + + + +
    + + + + + + + +
    +
    + + + + + AIM + + + MSN + + + ICQ + + + Jabber + + + Yahoo + + + Gadu-Gadu + + + WinPopup + + + SMS + + + IRC + + + Unknown + + + + + + + + + + + + + + + + + + + + + + ( + + + + + : + + + + + + + ) + + + + + + + + ( + + ) + + + + + + + + +
    + + diff --git a/kopete/plugins/webpresence/webpresence_xhtml_images.xsl b/kopete/plugins/webpresence/webpresence_xhtml_images.xsl new file mode 100644 index 00000000..8254a674 --- /dev/null +++ b/kopete/plugins/webpresence/webpresence_xhtml_images.xsl @@ -0,0 +1,60 @@ + + + + + + + + + + + + images + + + + + MSN + + + ICQ + + + Jabber + + + Yahoo + + + AIM + + + IRC + + + SMS + + + Gadu-Gadu + + + WinPopup + + + + + + + diff --git a/kopete/plugins/webpresence/webpresenceplugin.cpp b/kopete/plugins/webpresence/webpresenceplugin.cpp new file mode 100644 index 00000000..1856d94c --- /dev/null +++ b/kopete/plugins/webpresence/webpresenceplugin.cpp @@ -0,0 +1,473 @@ +/* + webpresenceplugin.cpp + + Kopete Web Presence plugin + + Copyright (c) 2005 by Tommi Rantala + Copyright (c) 2002,2003 by Will Stephenson + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_XSLT +#include +#include + +#include +#include +#include +#include +#endif + +#include "kopetepluginmanager.h" +#include "kopeteprotocol.h" +#include "kopeteaccountmanager.h" +#include "kopeteaccount.h" + +#include "webpresenceplugin.h" + +typedef KGenericFactory WebPresencePluginFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_webpresence, WebPresencePluginFactory( "kopete_webpresence" ) ) + +WebPresencePlugin::WebPresencePlugin( QObject *parent, const char *name, const QStringList& /*args*/ ) + : Kopete::Plugin( WebPresencePluginFactory::instance(), parent, name ), + shuttingDown( false ), resultFormatting( WEB_HTML ) +{ + m_writeScheduler = new QTimer( this ); + connect ( m_writeScheduler, SIGNAL( timeout() ), this, SLOT( slotWriteFile() ) ); + connect( Kopete::AccountManager::self(), SIGNAL(accountRegistered(Kopete::Account*)), + this, SLOT( listenToAllAccounts() ) ); + connect( Kopete::AccountManager::self(), SIGNAL(accountUnregistered(Kopete::Account*)), + this, SLOT( listenToAllAccounts() ) ); + + connect(this, SIGNAL(settingsChanged()), this, SLOT( loadSettings() ) ); + loadSettings(); + listenToAllAccounts(); +} + +WebPresencePlugin::~WebPresencePlugin() +{ +} + +void WebPresencePlugin::loadSettings() +{ + KConfig *kconfig = KGlobal::config(); + kconfig->setGroup( "Web Presence Plugin" ); + + frequency = kconfig->readNumEntry("UploadFrequency", 15); + resultURL = kconfig->readPathEntry("uploadURL"); + + resultFormatting = WEB_UNDEFINED; + + if ( kconfig->readBoolEntry( "formatHTML", false ) ) { + resultFormatting = WEB_HTML; + } else if ( kconfig->readBoolEntry( "formatXHTML", false ) ) { + resultFormatting = WEB_XHTML; + } else if ( kconfig->readBoolEntry( "formatXML", false ) ) { + resultFormatting = WEB_XML; + } else if ( kconfig->readBoolEntry( "formatStylesheet", false ) ) { + resultFormatting = WEB_CUSTOM; + userStyleSheet = kconfig->readEntry("formatStylesheetURL"); + } + + // Default to HTML if we dont get anything useful from config file. + if ( resultFormatting == WEB_UNDEFINED ) + resultFormatting = WEB_HTML; + + useImagesInHTML = kconfig->readBoolEntry( "useImagesHTML", false ); + useImName = kconfig->readBoolEntry("showName", true); + userName = kconfig->readEntry("showThisName"); + showAddresses = kconfig->readBoolEntry("includeIMAddress", false); + + // Update file when settings are changed. + slotWriteFile(); +} + +void WebPresencePlugin::listenToAllAccounts() +{ + // connect to signals notifying of all accounts' status changes + ProtocolList protocols = allProtocols(); + + for ( ProtocolList::Iterator it = protocols.begin(); + it != protocols.end(); ++it ) + { + QDict accounts = Kopete::AccountManager::self()->accounts( *it ); + QDictIterator acIt( accounts ); + + for( ; Kopete::Account *account = acIt.current(); ++acIt ) + { + listenToAccount( account ); + } + } + slotWaitMoreStatusChanges(); +} + +void WebPresencePlugin::listenToAccount( Kopete::Account* account ) +{ + if(account && account->myself()) + { + // Connect to the account's status changed signal + // because we can't know if the account has already connected + QObject::disconnect( account->myself(), + SIGNAL(onlineStatusChanged( Kopete::Contact *, + const Kopete::OnlineStatus &, + const Kopete::OnlineStatus & ) ), + this, + SLOT( slotWaitMoreStatusChanges() ) ) ; + QObject::connect( account->myself(), + SIGNAL(onlineStatusChanged( Kopete::Contact *, + const Kopete::OnlineStatus &, + const Kopete::OnlineStatus & ) ), + this, + SLOT( slotWaitMoreStatusChanges() ) ); + } +} + +void WebPresencePlugin::slotWaitMoreStatusChanges() +{ + if ( !m_writeScheduler->isActive() ) + m_writeScheduler->start( frequency * 1000 ); +} + +void WebPresencePlugin::slotWriteFile() +{ + m_writeScheduler->stop(); + + // generate the (temporary) XML file representing the current contactlist + KURL dest( resultURL ); + if ( resultURL.isEmpty() || !dest.isValid() ) + { + kdDebug(14309) << "url is empty or not valid. NOT UPDATING!" << endl; + return; + } + + KTempFile* xml = generateFile(); + xml->setAutoDelete( true ); + kdDebug(14309) << k_funcinfo << " " << xml->name() << endl; + + switch( resultFormatting ) { + case WEB_XML: + m_output = xml; + xml = 0L; + break; + case WEB_HTML: + case WEB_XHTML: + case WEB_CUSTOM: + m_output = new KTempFile(); + m_output->setAutoDelete( true ); + + if ( !transform( xml, m_output ) ) + { + //TODO: give some error to user, even better if shown only once + delete m_output; + m_output = 0L; + + delete xml; + return; + } + + delete xml; // might make debugging harder! + break; + default: + return; + } + + // upload it to the specified URL + KURL src( m_output->name() ); + KIO::FileCopyJob *job = KIO::file_move( src, dest, -1, true, false, false ); + connect( job, SIGNAL( result( KIO::Job * ) ), + SLOT( slotUploadJobResult( KIO::Job * ) ) ); +} + +void WebPresencePlugin::slotUploadJobResult( KIO::Job *job ) +{ + if ( job->error() ) { + kdDebug(14309) << "Error uploading presence info." << endl; + KMessageBox::queuedDetailedError( 0, i18n("An error occurred when uploading your presence page.\nCheck the path and write permissions of the destination."), 0, displayName() ); + delete m_output; + m_output = 0L; + } +} + +KTempFile* WebPresencePlugin::generateFile() +{ + // generate the (temporary) XML file representing the current contactlist + kdDebug( 14309 ) << k_funcinfo << endl; + QString notKnown = i18n( "Not yet known" ); + + QDomDocument doc; + + doc.appendChild( doc.createProcessingInstruction( "xml", + "version=\"1.0\" encoding=\"UTF-8\"" ) ); + + QDomElement root = doc.createElement( "webpresence" ); + doc.appendChild( root ); + + // insert the current date/time + QDomElement date = doc.createElement( "listdate" ); + QDomText t = doc.createTextNode( + KGlobal::locale()->formatDateTime( QDateTime::currentDateTime() ) ); + date.appendChild( t ); + root.appendChild( date ); + + // insert the user's name + QDomElement name = doc.createElement( "name" ); + QDomText nameText; + if ( !useImName && !userName.isEmpty() ) + nameText = doc.createTextNode( userName ); + else + nameText = doc.createTextNode( notKnown ); + name.appendChild( nameText ); + root.appendChild( name ); + + // insert the list of the user's accounts + QDomElement accounts = doc.createElement( "accounts" ); + root.appendChild( accounts ); + + QPtrList list = Kopete::AccountManager::self()->accounts(); + // If no accounts, stop here + if ( !list.isEmpty() ) + { + for( QPtrListIterator it( list ); + Kopete::Account *account=it.current(); + ++it ) + { + QDomElement acc = doc.createElement( "account" ); + //output += h.openTag( "account" ); + + QDomElement protoName = doc.createElement( "protocol" ); + QDomText protoNameText = doc.createTextNode( + account->protocol()->pluginId() ); + protoName.appendChild( protoNameText ); + acc.appendChild( protoName ); + + Kopete::Contact* me = account->myself(); + QString displayName = me->property( Kopete::Global::Properties::self()->nickName() ).value().toString(); + QDomElement accName = doc.createElement( "accountname" ); + QDomText accNameText = doc.createTextNode( ( me ) + ? displayName + : notKnown ); + accName.appendChild( accNameText ); + acc.appendChild( accName ); + + QDomElement accStatus = doc.createElement( "accountstatus" ); + QDomText statusText = doc.createTextNode( ( me ) + ? statusAsString( me->onlineStatus() ) + : notKnown ) ; + accStatus.appendChild( statusText ); + + // Dont add these if we're shutting down, because the result + // would be quite weird. + if ( !shuttingDown ) { + + // Add away message as an attribute, if one exists. + if ( me->onlineStatus().status() == Kopete::OnlineStatus::Away && + !me->property("awayMessage").value().toString().isEmpty() ) { + accStatus.setAttribute( "awayreason", + me->property("awayMessage").value().toString() ); + } + + // Add the online status description as an attribute, if one exits. + if ( !me->onlineStatus().description().isEmpty() ) { + accStatus.setAttribute( "statusdescription", + me->onlineStatus().description() ); + } + } + acc.appendChild( accStatus ); + + if ( showAddresses ) + { + QDomElement accAddress = doc.createElement( "accountaddress" ); + QDomText addressText = doc.createTextNode( ( me ) + ? me->contactId() + : notKnown ); + accAddress.appendChild( addressText ); + acc.appendChild( accAddress ); + } + + accounts.appendChild( acc ); + } + } + + // write the XML to a temporary file + KTempFile* file = new KTempFile(); + QTextStream *stream = file->textStream(); + stream->setEncoding( QTextStream::UnicodeUTF8 ); + doc.save( *stream, 4 ); + file->close(); + return file; +} + +bool WebPresencePlugin::transform( KTempFile * src, KTempFile * dest ) +{ +#ifdef HAVE_XSLT + bool retval = true; + xmlSubstituteEntitiesDefault( 1 ); + xmlLoadExtDtdDefaultValue = 1; + + QFile sheet; + + switch ( resultFormatting ) { + case WEB_XML: + // Oops! We tried to call transform() but XML was requested. + return false; + case WEB_HTML: + if ( useImagesInHTML ) { + sheet.setName( locate( "appdata", "webpresence/webpresence_html_images.xsl" ) ); + } else { + sheet.setName( locate( "appdata", "webpresence/webpresence_html.xsl" ) ); + } + break; + case WEB_XHTML: + if ( useImagesInHTML ) { + sheet.setName( locate( "appdata", "webpresence/webpresence_xhtml_images.xsl" ) ); + } else { + sheet.setName( locate( "appdata", "webpresence/webpresence_xhtml.xsl" ) ); + } + break; + case WEB_CUSTOM: + sheet.setName( userStyleSheet ); + break; + default: + // Shouldn't ever reach here. + return false; + } + + // TODO: auto / smart pointers would be useful here + xsltStylesheetPtr cur = 0; + xmlDocPtr doc = 0; + xmlDocPtr res = 0; + + if ( !sheet.exists() ) { + kdDebug(14309) << k_funcinfo << "ERROR: Style sheet not found" << endl; + retval = false; + goto end; + } + + // is the cast safe? + cur = xsltParseStylesheetFile( (const xmlChar *) sheet.name().latin1() ); + if ( !cur ) { + kdDebug(14309) << k_funcinfo << "ERROR: Style sheet parsing failed" << endl; + retval = false; + goto end; + } + + doc = xmlParseFile( QFile::encodeName( src->name() ) ); + if ( !doc ) { + kdDebug(14309) << k_funcinfo << "ERROR: XML parsing failed" << endl; + retval = false; + goto end; + } + + res = xsltApplyStylesheet( cur, doc, 0 ); + if ( !res ) { + kdDebug(14309) << k_funcinfo << "ERROR: Style sheet apply failed" << endl; + retval = false; + goto end; + } + + if ( xsltSaveResultToFile(dest->fstream(), res, cur) == -1 ) { + kdDebug(14309) << k_funcinfo << "ERROR: Style sheet apply failed" << endl; + retval = false; + goto end; + } + + // then it all worked! + dest->close(); + +end: + xsltCleanupGlobals(); + xmlCleanupParser(); + if (doc) xmlFreeDoc(doc); + if (res) xmlFreeDoc(res); + if (cur) xsltFreeStylesheet(cur); + + return retval; + +#else + Q_UNUSED( src ); + Q_UNUSED( dest ); + + return false; +#endif +} + +ProtocolList WebPresencePlugin::allProtocols() +{ + kdDebug( 14309 ) << k_funcinfo << endl; + + Kopete::PluginList plugins = Kopete::PluginManager::self()->loadedPlugins( "Protocols" ); + Kopete::PluginList::ConstIterator it; + + ProtocolList result; + + for ( it = plugins.begin(); it != plugins.end(); ++it ) { + result.append( static_cast( *it ) ); + } + + return result; +} + +QString WebPresencePlugin::statusAsString( const Kopete::OnlineStatus &newStatus ) +{ + if (shuttingDown) + return "OFFLINE"; + + QString status; + switch ( newStatus.status() ) + { + case Kopete::OnlineStatus::Online: + status = "ONLINE"; + break; + case Kopete::OnlineStatus::Away: + status = "AWAY"; + break; + case Kopete::OnlineStatus::Offline: + case Kopete::OnlineStatus::Invisible: + status = "OFFLINE"; + break; + default: + status = "UNKNOWN"; + } + + return status; +} + +void WebPresencePlugin::aboutToUnload() +{ + // Stop timer. Dont need it anymore. + m_writeScheduler->stop(); + + // Force statusAsString() report all accounts as OFFLINE. + shuttingDown = true; + + // Do final update of webpresence file. + slotWriteFile(); + + emit readyForUnload(); +} + +// vim: set noet ts=4 sts=4 sw=4: +#include "webpresenceplugin.moc" diff --git a/kopete/plugins/webpresence/webpresenceplugin.h b/kopete/plugins/webpresence/webpresenceplugin.h new file mode 100644 index 00000000..3aea9af0 --- /dev/null +++ b/kopete/plugins/webpresence/webpresenceplugin.h @@ -0,0 +1,123 @@ +/* + webpresenceplugin.h + + Kopete Web Presence plugin + + Copyright (c) 2002,2003 by Will Stephenson + + Kopete (c) 2002,2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef WEBPRESENCEPLUGIN_H +#define WEBPRESENCEPLUGIN_H + +#include + +#include + +#include "kopetecontact.h" +#include "kopeteonlinestatus.h" + +class QTimer; +class KTempFile; +namespace Kopete { class MetaContact; } +class KToggleAction; +class KActionCollection; + +typedef QValueList ProtocolList; + +class WebPresencePlugin : public Kopete::Plugin +{ + Q_OBJECT + +private: + int frequency; + bool showAddresses; + bool useImName; + QString userName; + QString userStyleSheet; + bool useImagesInHTML; + + // Is set to true when Kopete has notified us + // that we're about to be unloaded. + bool shuttingDown; + + enum { + WEB_HTML, + WEB_XHTML, + WEB_XML, + WEB_CUSTOM, + WEB_UNDEFINED + } resultFormatting; + + QString resultURL; + +public: + WebPresencePlugin( QObject *parent, const char *name, const QStringList &args ); + virtual ~WebPresencePlugin(); + + virtual void aboutToUnload(); + +protected slots: + void loadSettings(); + + /** + * Write a file to the specified location, + */ + void slotWriteFile(); + /** + * Called when an upload finished, displays error if needed + */ + void slotUploadJobResult( KIO::Job * ); + /** + * Called to schedule a write, after waiting to see if more changes + * occur (accounts tend to change status together) + */ + void slotWaitMoreStatusChanges(); + /** + * Sets us up to respond to account status changes + */ + void listenToAllAccounts(); + /** + * Sets us up to respond to a new account + */ + void listenToAccount( Kopete::Account* account ); + +protected: + /** + * Generate the file (HTML, text) to be uploaded + */ + KTempFile* generateFile(); + /** + * Apply named stylesheet to get content and presentation + */ + bool transform( KTempFile* src, KTempFile* dest ); + /** + * Helper method, generates list of all IM protocols + */ + ProtocolList allProtocols(); + /** + * Converts numeric status to a string + */ + QString statusAsString( const Kopete::OnlineStatus &newStatus ); + /** + * Schedules writes + */ + QTimer* m_writeScheduler; + + // The file to be uploaded to the WWW + KTempFile *m_output; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/webpresence/webpresencepreferences.cpp b/kopete/plugins/webpresence/webpresencepreferences.cpp new file mode 100644 index 00000000..9b00435a --- /dev/null +++ b/kopete/plugins/webpresence/webpresencepreferences.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** + webpresencepreferences.cpp + ------------------- + begin : jeu nov 14 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#include + +#include +#include +#include + +#include "webpresenceprefs.h" +#include "webpresencepreferences.h" + +typedef KGenericFactory WebPresencePreferencesFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_webpresence, WebPresencePreferencesFactory("kcm_kopete_webpresence")) + +WebPresencePreferences::WebPresencePreferences(QWidget *parent, const char* /*name*/, const QStringList &args) + : KCModule(WebPresencePreferencesFactory::instance(), parent, args) +{ + // Add actuall widget generated from ui file. + ( new QVBoxLayout( this ) )->setAutoAdd( true ); + preferencesDialog = new WebPresencePrefsUI(this); + preferencesDialog->uploadURL->setMode( KFile::File ); + preferencesDialog->formatStylesheetURL->setFilter( "*.xsl" ); + + // KAutoConfig stuff + kautoconfig = new KAutoConfig(KGlobal::config(), this, "kautoconfig"); + connect(kautoconfig, SIGNAL(widgetModified()), SLOT(widgetModified())); + connect(kautoconfig, SIGNAL(settingsChanged()), SLOT(widgetModified())); + kautoconfig->addWidget(preferencesDialog, "Web Presence Plugin"); + kautoconfig->retrieveSettings(true); +} + +void WebPresencePreferences::widgetModified() +{ + emit KCModule::changed(kautoconfig->hasChanged()); +} + +void WebPresencePreferences::save() +{ + kautoconfig->saveSettings(); +} + +void WebPresencePreferences::defaults () +{ + kautoconfig->resetSettings(); +} + +#include "webpresencepreferences.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/webpresence/webpresencepreferences.h b/kopete/plugins/webpresence/webpresencepreferences.h new file mode 100644 index 00000000..120e7a9a --- /dev/null +++ b/kopete/plugins/webpresence/webpresencepreferences.h @@ -0,0 +1,50 @@ +/*************************************************************************** + webpresencepreferences.h + ------------------- + begin : jeu nov 14 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ 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. * + * * + ***************************************************************************/ + +#ifndef WEBPRSENCEPREFERECES_H +#define WEBPRSENCEPREFERECES_H + +#include "kcmodule.h" + +class WebPresencePrefsUI; +class KAutoConfig; + +/** + * Preference widget for the Now Listening plugin, copied from the Cryptography plugin + * @author Olivier Goffart + */ +class WebPresencePreferences : public KCModule { + Q_OBJECT + +public: + WebPresencePreferences(QWidget *parent = 0, const char *name = 0, const QStringList &args = QStringList()); + + virtual void save(); + virtual void defaults(); + +private: + WebPresencePrefsUI *preferencesDialog; + KAutoConfig *kautoconfig; + +private slots: // Public slots + void widgetModified(); + +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/plugins/webpresence/webpresenceprefs.ui b/kopete/plugins/webpresence/webpresenceprefs.ui new file mode 100644 index 00000000..9aae819a --- /dev/null +++ b/kopete/plugins/webpresence/webpresenceprefs.ui @@ -0,0 +1,369 @@ + +WebPresencePrefsUI + + + WebPresencePrefsUI + + + true + + + + 0 + 0 + 426 + 554 + + + + + unnamed + + + 11 + + + 6 + + + + groupBox1 + + + Uploading + + + + unnamed + + + 11 + + + 6 + + + + textLabel1 + + + NoFrame + + + Plain + + + Uplo&ad to: + + + uploadURL + + + + + uploadURL + + + + + spacer5 + + + Horizontal + + + Expanding + + + + 449 + 0 + + + + + + + + buttonGroup2_2 + + + Formatting + + + + unnamed + + + 11 + + + 6 + + + + formatHTML + + + HTML (simple loo&k) + + + true + + + HTML 4.01 Transitional using the ISO-8859-1 (aka. Latin 1) character set encoding. + + + HTML 4.01 Transitional formatting using ISO-8859-1 (aka. Latin 1) character set encoding. + +This version should be easily opened by most web browsers. + + + + + formatXHTML + + + XHTML (simple look) + + + XHTML 1.0 Strict + + + The resulting page will be formatted using the XHTML 1.0 Strict W3C Recommendation. The character set encoding is UTF-8. + +Note that some web browsers do not support XHTML. You should also make sure your web server serves it out with the correct mime type, such as application/xhtml+xml. + + + + + formatXML + + + &XML + + + Save the output in XML format using UTF-8 character set. + + + Save the output in XML format using the UTF-8 encoding. + + + + + formatStylesheet + + + XML transformation &using this XSLT sheet: + + + + + layout1 + + + + unnamed + + + + spacer10 + + + Horizontal + + + Fixed + + + + 30 + 20 + + + + + + formatStylesheetURL + + + false + + + + + + + useImagesHTML + + + Repla&ce protocol text with images in (X)HTML + + + Replaces the protocol names, such as MSN and IRC with images. + + + Replaces the protocol names, such as MSN and IRC with images. + +Note that you have to manually copy the PNG files into place. + +The following files are used by default: + +images/msn_protocol.png +images/icq_protocol.png +images/jabber_protocol.png +images/yahoo_protocol.png +images/aim_protocol.png +images/irc_protocol.png +images/sms_protocol.png +images/gadu_protocol.png +images/winpopup_protocol.png + + + + + + + buttonGroup2 + + + Display Name + + + + unnamed + + + 11 + + + 6 + + + + showName + + + Use one of &your IM names + + + true + + + + + showAnotherName + + + Use another &name: + + + + + layout2 + + + + unnamed + + + + spacer4_2 + + + Horizontal + + + Fixed + + + + 30 + 20 + + + + + + showThisName + + + false + + + + + + + includeIMAddress + + + Include &IM addresses + + + + + + + Spacer7 + + + Vertical + + + Expanding + + + + 16 + 93 + + + + + + + + formatStylesheet + toggled(bool) + formatStylesheetURL + setEnabled(bool) + + + showAnotherName + toggled(bool) + showThisName + setEnabled(bool) + + + formatXML + toggled(bool) + useImagesHTML + setDisabled(bool) + + + formatStylesheet + toggled(bool) + useImagesHTML + setDisabled(bool) + + + + uploadURL + formatHTML + formatStylesheetURL + useImagesHTML + showName + showThisName + includeIMAddress + + + + kurlrequester.h + klineedit.h + kpushbutton.h + kurlrequester.h + klineedit.h + kpushbutton.h + + diff --git a/kopete/protocols/Makefile.am b/kopete/protocols/Makefile.am new file mode 100644 index 00000000..6612e8a9 --- /dev/null +++ b/kopete/protocols/Makefile.am @@ -0,0 +1,21 @@ +if include_meanwhile +MEANWHILE=meanwhile +endif + +if include_gadu +GADU=gadu +endif + +if include_jabber +JABBER=jabber +endif + +if include_testbed +TESTBED=testbed +endif + +if include_smsgsm +SMS=sms +endif + +SUBDIRS = $(TESTBED) groupwise msn irc oscar yahoo winpopup $(SMS) $(JABBER) $(GADU) $(MEANWHILE) diff --git a/kopete/protocols/configure.in.bot b/kopete/protocols/configure.in.bot new file mode 100644 index 00000000..6b2c2a18 --- /dev/null +++ b/kopete/protocols/configure.in.bot @@ -0,0 +1,11 @@ +# if libidn test fails the following line will be written: +# include_jabber_TRUE = # +# the following test will then issue a warning at the end of configure output +# so users see it more easily +if test "$have_glib" = no; then + echo "" + echo "You're missing glib >= 2.0 and its development files. Kopete's MSN" + echo "plugin will not have webcam support. If you want webcam support for" + echo "MSN, be sure to install glib and its development packages" + all_tests=bad +fi diff --git a/kopete/protocols/configure.in.in b/kopete/protocols/configure.in.in new file mode 100644 index 00000000..bc946d92 --- /dev/null +++ b/kopete/protocols/configure.in.in @@ -0,0 +1,243 @@ +LIBGG_INCLUDES="" +LIBGG_LIBS="" +ac_libgadu_includes="" +ac_libgadu_libs="" + +AC_ARG_WITH(external-libgadu, + [AC_HELP_STRING(--with-external-libgadu, + [use external libgadu library @<:@default=check@:>@])], + [], with_external_libgadu=check) + +AC_ARG_WITH(libgadu-includes, + AC_HELP_STRING([--with-libgadu-includes=DIR], [where the libgadu includes are.]), + [ ac_libgadu_includes="$withval" ]) + +if test "$ac_libgadu_includes" != "" ; then +LIBGG_INCLUDES="-I$ac_libgadu_includes" +fi + +AC_ARG_WITH(libgadu-libs, + AC_HELP_STRING([--with-libgadu-libs=DIR], [where the libgadu libraries are.]), + [ ac_libgadu_libs="$withval" ]) + +if test "$ac_libgadu_libs" != "" ; then + LIBGG_LIBS="-L$ac_libgadu_libs" +fi + +if test "x$with_external_libgadu" != xno; then + ac_save_LIBS="$LIBS" + ac_save_CFLAGS="$CFLAGS" + LIBS="$LIBGG_LIBS -lgadu $LIBPTHREAD" + CFLAGS="$CFLAGS $LIBGG_INCLUDES" + AC_MSG_CHECKING([libgadu version 1.5(rcX) with pthread support]) + AC_TRY_RUN( + [ + + #include + #include + #include + + int main() + { +#if defined __GG_LIBGADU_HAVE_PTHREAD && defined GG_LOGIN60 + int maj, min, date; + sscanf( gg_libgadu_version(), "%u.%u.%u", &maj,&min,&date ); + if ( maj != 1 ) { + return 1; + } + if ( ( min == 4 || min == 5 ) && date < 20040520 ) { + return 1; + } + + if ( min == 5 || min == 6 ){ + return 0; + } + +#endif + return 1; + } + ], [ + LIBGG_LIBS="$LIBGG_LIBS -lgadu $LIBPTHREAD" + AC_MSG_RESULT([yes]) + COMPILE_GADU=true + use_libgadu_copy= + ], [ + AC_MSG_RESULT([no]) + ]) + LIBS="$ac_save_LIBS" + CFLAGS="$ac_save_CFLAGS" + + if test "x$with_external_libgadu" != xcheck && test -z "$COMPILE_GADU"; then + AC_MSG_ERROR([--with-external-libgadu was given, but test for libgadu failed]) + fi +fi + +if test -z "$COMPILE_GADU"; then + AC_MSG_CHECKING([if supplied libgadu-copy can be used]) + if test "$kde_use_threading" = "yes"; then + AC_MSG_RESULT([yes]) + use_libgadu_copy=yes + COMPILE_GADU=true + else + AC_MSG_RESULT([no (no pthread), support for Gadu-Gadu will be disabled]) + use_libgadu_copy= + COMPILE_GADU= + fi +fi + +AC_SUBST(LIBGG_INCLUDES) +AC_SUBST(LIBGG_LIBS) +AC_SUBST(COMPILE_GADU) +AM_CONDITIONAL(include_gadu, test -n "$COMPILE_GADU") +AM_CONDITIONAL(include_libggcopy, test -n "$use_libgadu_copy") + +if test "$use_libgadu_copy" = "yes"; then + AM_CONFIG_HEADER(kopete/protocols/gadu/libgadu/libgadu-config.h) + + if test "$ac_cv_c_bigendian" = "yes"; then + AC_DEFINE_UNQUOTED([__GG_LIBGADU_BIGENDIAN], 1, [Define if big endian]) + fi + KDE_CHECK_LONG_LONG() + if test "$kde_cv_c_long_long" = "yes"; then + AC_DEFINE_UNQUOTED([__GG_LIBGADU_HAVE_LONG_LONG], 1, [long long support]) + fi + KDE_CHECK_SSL() + if test "$have_ssl" = "yes"; then + AC_DEFINE_UNQUOTED([__GG_LIBGADU_HAVE_OPENSSL], 1, [Define if SSL support is available]) + fi + AC_MSG_CHECKING([for C99-compatible vsnprintf()]) + AC_TRY_RUN( + [ + #include + int main() + { + char tmp; + return (snprintf(&tmp, sizeof(tmp), "test") != 4); + } + ],[ + AC_MSG_RESULT([yes]) + AC_DEFINE_UNQUOTED([__GG_LIBGADU_HAVE_C99_VSNPRINTF], 1, [C99 vsnprintf() available]) + ], [ + AC_MSG_RESULT([no]) + ]) + AC_CHECK_FUNCS([va_copy], + [AC_DEFINE_UNQUOTED([__GG_LIBGADU_HAVE_VA_COPY], 1, [va_copy])],[]) + AC_CHECK_FUNCS([_va_copy], + [AC_DEFINE_UNQUOTED([__GG_LIBGADU_HAVE__VA_COPY], 1, [__va_copy])],[]) +fi + +KDE_PKG_CHECK_MODULES(IDN, libidn, have_libidn=yes, have_libidn=no) +if test x$have_libidn = xno; then + AC_MSG_WARN([Libidn not found, Kopete Jabber plugin will not be compiled]) +else + AC_DEFINE(LIBIDN, 1, [Define to 1 if you want IDN support.]) +fi +AC_SUBST(IDN_CFLAGS) +AC_SUBST(IDN_LIBS) + +AC_MSG_CHECKING([if Libidn can be used]) +AC_MSG_RESULT($have_libidn) + +AM_CONDITIONAL(include_jabber, test "$have_libidn" = "yes") + +# Sametime support + +# lower and upper-bound versions of Meanwhile library +m4_define(libmeanwhile_version_min, 1.0.1) +m4_define(libmeanwhile_version_max, 1.1.0) + +# Let the user disable the plugin +AC_ARG_ENABLE(meanwhile, + AC_HELP_STRING([--disable-meanwhile], + [disable the Kopete Meanwhile plugin (Lotus Sametime support) @<:@default=enabled@:>@]), + ) + +if test "x$enable_meanwhile" != "xno"; then + # Check and setup for libmeanwhile + KDE_PKG_CHECK_MODULES(MEANWHILE, + [meanwhile >= libmeanwhile_version_min meanwhile < libmeanwhile_version_max], + [have_libmeanwhile=yes], [have_libmeanwhile=no]) + + if test "x$have_libmeanwhile" = "xno"; then + enable_meanwhile=no + AC_MSG_RESULT([not found]) + else + AC_MSG_RESULT([found]) + fi +fi + +AC_SUBST(MEANWHILE_CFLAGS) +AC_SUBST(MEANWHILE_LIBS) + +AC_MSG_CHECKING([if Meanwhile plugin should be compiled]) +if test "x$enable_meanwhile" != "xno"; then + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) +fi + +# Set the flag to compile meanwhile +AM_CONDITIONAL(include_meanwhile, [test "x$enable_meanwhile" != "xno"]) + +# testbed protocol +dnl define the configure option that disables testbed protocol +AC_ARG_ENABLE(testbed, [ --disable-testbed disable kopete testbed protocol compilation ], with_testbed=$enableval, with_testbed=yes) +AM_CONDITIONAL(include_testbed, test "$with_testbed" = "yes") + +PKG_CHECK_MODULES(GLIB, glib-2.0 gmodule-2.0, have_glib=yes, have_glib=no) +if test x$have_glib = xno; then + AC_MSG_WARN([GLib 2.0 is required for MSN webcam and Jabber Jingle. You can get it from http://www.gtk.org/]) +else + AC_SUBST(GLIB_CFLAGS) + AC_SUBST(GLIB_LIBS) + AC_DEFINE(HAVE_GLIB, 1, [Glib is required for oRTP code and libmimic code]) +fi + +if test "x$have_glib" != "xyes"; then + compile_msn_webcam=no + msn_webcam_val=0 +else + compile_msn_webcam=yes + msn_webcam_val=1 +fi + +AC_MSG_CHECKING([if MSN webcam support should be enabled]) +AC_MSG_RESULT($compile_msn_webcam) +AC_DEFINE_UNQUOTED(MSN_WEBCAM, $msn_webcam_val, [Define if MSN webcam support can be enabled]) + +AM_CONDITIONAL(include_msn_webcam, test "x$compile_msn_webcam" = "xyes") + +# Check for sms protocol +AC_ARG_ENABLE(smsgsm, + AC_HELP_STRING([--disable-smsgsm], [disable the GSM SMS protocol]), + [compile_smsgsm=$enableval], + [compile_smsgsm=yes] + ) + +AC_LANG_PUSH(C++) +ac_save_LIBS="$LIBS" +LIBS="-lgsmme $LIBS" +AC_TRY_LINK([#include ],[(void)gsmlib::latin1ToGsm("text");], + [have_smsgsm_lib=yes], + [have_smsgsm_lib=no]) +LIBS=$ac_save_LIBS + +AC_CHECK_HEADER(gsmlib/gsm_util.h, + [have_smsgsm_inc=yes], + [have_smsgsm_inc=no]) + +if test "x$have_smsgsm_lib" != "xyes" || test "x$have_smsgsm_inc" != "xyes"; then + compile_smsgsm=no +fi +AC_LANG_POP(C++) + +# Let the user know +AC_MSG_CHECKING([if SMSGSM Plugin should be compiled]) +AC_MSG_RESULT($compile_smsgsm) + +# Here we go +AM_CONDITIONAL(include_smsgsm, [test "x$compile_smsgsm" = "xyes"]) + +if test "x$compile_smsgsm" = "xyes"; then + AC_DEFINE(INCLUDE_SMSGSM, 1, [Define to compile with GSM SMS support]) +fi diff --git a/kopete/protocols/gadu/Makefile.am b/kopete/protocols/gadu/Makefile.am new file mode 100644 index 00000000..d896950a --- /dev/null +++ b/kopete/protocols/gadu/Makefile.am @@ -0,0 +1,38 @@ +METASOURCES = AUTO +if include_libggcopy +LIBGADU_COPY=libgadu +GG_INCLUDES=-Ilibgadu -I$(srcdir)/libgadu $(SSL_INCLUDES) +GG_LIBS=$(top_builddir)/kopete/protocols/gadu/libgadu/libgadu_copy.la \ + $(LIBPTHREAD) +else +LIBGADU_COPY= +GG_INCLUDES=$(LIBGG_INCLUDES) +GG_LIBS=$(LIBGG_LIBS) +endif + + +SUBDIRS = ui icons $(LIBGADU_COPY) +AM_CPPFLAGS = -I$(srcdir)/ui \ + -I./ui \ + -I./lib \ + -I$(srcdir)/lib \ + $(KOPETE_INCLUDES) \ + $(all_includes) \ + $(GG_INCLUDES) + +kde_module_LTLIBRARIES = kopete_gadu.la + +kopete_gadu_la_SOURCES = gaduaway.cpp gadueditcontact.cpp gaducommands.cpp \ + gadueditaccount.cpp gadusession.cpp gaducontact.cpp \ + gaduaddcontactpage.cpp gaduprotocol.cpp gaduaccount.cpp \ + gadupubdir.cpp gaduregisteraccount.cpp \ + gaducontactlist.cpp gadurichtextformat.cpp \ + gadudccserver.cpp gadudcctransaction.cpp gadudcc.cpp + +kopete_gadu_la_LDFLAGS = -no-undefined -module $(KDE_PLUGIN) $(all_libraries) +kopete_gadu_la_LIBADD = ./ui/libgaduui.la \ + $(top_builddir)/kopete/libkopete/libkopete.la \ + $(GG_LIBS) + +service_DATA = kopete_gadu.desktop +servicedir = $(kde_servicesdir) diff --git a/kopete/protocols/gadu/README.gadu b/kopete/protocols/gadu/README.gadu new file mode 100644 index 00000000..6c7a929e --- /dev/null +++ b/kopete/protocols/gadu/README.gadu @@ -0,0 +1,43 @@ + +GaduGadu is primarily used and created for Poles, but it does not mean that it +cannot be used by anyone else. There is only one small issue, by design +protocol uses cp1250 encoding. Unfortunately, there's nothing we can do about +that, as it's needed to maintain compatibility with the Windows client, which +is the only one supported (and created) by the creators of gadu-gadu - sms- +express company. + +Gadu-Gadu plugin uses libgadu. If it's not avaliable in the system, +it will automaticaly switch to it's copy in /protocols/gadu/lib (v1.5). + +Following describes what to do if you still want to use external library version: + +You can download libgadu (part of ekg package) from http://dev.null.pl/ekg, +This should be version 1.5 (release candidate or stable), please stick with version +1.5 if possible. Author of libgadu/ekg does not keep any compatibility between +minor releases. It should support protocol version 6.0 by default. + +You have to download the ekg communicator package +(which is a gadugadu client for the console). +To install from sources, run: + +./configure --enable-shared --disable-static --enable-dynamic --with-pthread --prefix=/usr +make +(and as root) +make install. + +It is also available pre-packaged, e.g. for debian unstable, by default as +libgadu2 (binary) and libgadu-dev (development). +In order to compile kopete from sources, including the gadu-gadu plugin you +will need the libgadu headers and libraries installed and set up in your +system. + +Please refer INSTALL for information about building kopete from sources. + +If you're looking for more information, please refer to http://kopete.kde.org +and read the FAQ . + +If you have found an error in kopete, or in the gadu-gadu plugin +- please use the kde bug tracking system at http://bugs.kde.org/ + +Grzegorz Jaskiewicz aka Kain/K4 +gj AT pointblue DOT com DOT pl diff --git a/kopete/protocols/gadu/gaduaccount.cpp b/kopete/protocols/gadu/gaduaccount.cpp new file mode 100644 index 00000000..6dd737ce --- /dev/null +++ b/kopete/protocols/gadu/gaduaccount.cpp @@ -0,0 +1,1261 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003-2004 Grzegorz Jaskiewicz +// Copyright (C) 2003 Zack Rusin +// +// gaduaccount.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA + +#include "gaduaccount.h" +#include "gaducontact.h" +#include "gaduprotocol.h" +#include "gaduawayui.h" +#include "gaduaway.h" +#include "gadupubdir.h" +#include "gadudcc.h" +#include "gadudcctransaction.h" + +#include "kopetemetacontact.h" +#include "kopetecontactlist.h" +#include "kopetegroup.h" +#include "kopetepassword.h" +#include "kopeteuiglobal.h" +#include "kopeteglobal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +class GaduAccountPrivate { + +public: + GaduAccountPrivate() {} + + GaduSession* session_; + GaduDCC* gaduDcc_; + + QTimer* pingTimer_; + + QTextCodec* textcodec_; + KFileDialog* saveListDialog; + KFileDialog* loadListDialog; + + KActionMenu* actionMenu_; + KAction* searchAction; + KAction* listputAction; + KAction* listToFileAction; + KAction* listFromFileAction; + KAction* friendsModeAction; + bool connectWithSSL; + + int currentServer; + unsigned int serverIP; + + QString lastDescription; + bool forFriends; + bool ignoreAnons; + + QTimer* exportTimer_; + bool exportUserlist; + + KConfigGroup* config; + Kopete::OnlineStatus status; + QValueList servers; + KGaduLoginParams loginInfo; +}; + +// 10s is enough ;p +#define USERLISTEXPORT_TIMEOUT (10*1000) + +// FIXME: use dynamic cache please, i consider this as broken resolution of this problem +static const char* const servers_ip[] = { + "217.17.41.85", + "217.17.41.83", + "217.17.41.84", + "217.17.41.86", + "217.17.41.87", + "217.17.41.88", + "217.17.41.92", + "217.17.41.93", + "217.17.45.133", + "217.17.45.143", + "217.17.45.144" +}; + +#define NUM_SERVERS (sizeof(servers_ip)/sizeof(char*)) + + GaduAccount::GaduAccount( Kopete::Protocol* parent, const QString& accountID,const char* name ) +: Kopete::PasswordedAccount( parent, accountID, 0, name ) +{ + QHostAddress ip; + p = new GaduAccountPrivate; + + p->pingTimer_ = NULL; + p->saveListDialog = NULL; + p->loadListDialog = NULL; + p->forFriends = false; + + p->textcodec_ = QTextCodec::codecForName( "CP1250" ); + p->session_ = new GaduSession( this, "GaduSession" ); + + KGlobal::config()->setGroup( "Gadu" ); + + setMyself( new GaduContact( accountId().toInt(), accountId(), this, Kopete::ContactList::self()->myself() ) ); + + p->status = GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL ); + p->lastDescription = QString::null; + + for ( unsigned int i = 0; i < NUM_SERVERS ; i++ ) { + ip.setAddress( QString( servers_ip[i] ) ); + p->servers.append( htonl( ip.toIPv4Address() ) ); + kdDebug( 14100 ) << "adding IP: " << p->servers[ i ] << " to cache" << endl; + } + p->currentServer = -1; + p->serverIP = 0; + + // initialize KGaduLogin structure to default values + p->loginInfo.uin = accountId().toInt(); + p->loginInfo.useTls = false; + p->loginInfo.status = GG_STATUS_AVAIL; + p->loginInfo.server = 0; + p->loginInfo.client_port = 0; + p->loginInfo.client_addr = 0; + + p->pingTimer_ = new QTimer( this ); + p->exportTimer_ = new QTimer( this ); + + p->exportUserlist = false; + p->gaduDcc_ = NULL; + + p->config = configGroup(); + + p->ignoreAnons = ignoreAnons(); + p->forFriends = loadFriendsMode(); + + initConnections(); + initActions(); + + QString nick = p->config->readEntry( QString::fromAscii( "nickName" ) ); + if ( !nick.isNull() ) { + myself()->setProperty( Kopete::Global::Properties::self()->nickName(), nick ); + } + else { + myself()->setProperty( Kopete::Global::Properties::self()->nickName(), accountId() ); + p->config->writeEntry( QString::fromAscii( "nickName" ), accountId() ); + } +} + +GaduAccount::~GaduAccount() +{ + delete p; +} + +void +GaduAccount::initActions() +{ + p->searchAction = new KAction( i18n( "&Search for Friends" ), "", 0, + this, SLOT( slotSearch() ), this, "actionSearch" ); + p->listputAction = new KAction( i18n( "Export Contacts to Server" ), "", 0, + this, SLOT( slotExportContactsList() ), this, "actionListput" ); + p->listToFileAction = new KAction( i18n( "Export Contacts to File..." ), "", 0, + this, SLOT( slotExportContactsListToFile() ), this, "actionListputFile" ); + p->listFromFileAction = new KAction( i18n( "Import Contacts From File..." ), "", 0, + this, SLOT( slotImportContactsFromFile() ), this, "actionListgetFile" ); + p->friendsModeAction = new KToggleAction( i18n( "Only for Friends" ), "", 0, + this, SLOT( slotFriendsMode() ), this, + "actionFriendsMode" ); + + static_cast(p->friendsModeAction)->setChecked( p->forFriends ); +} + +void +GaduAccount::initConnections() +{ + QObject::connect( p->session_, SIGNAL( error( const QString&, const QString& ) ), + SLOT( error( const QString&, const QString& ) ) ); + QObject::connect( p->session_, SIGNAL( messageReceived( KGaduMessage* ) ), + SLOT( messageReceived( KGaduMessage* ) ) ); + QObject::connect( p->session_, SIGNAL( contactStatusChanged( KGaduNotify* ) ), + SLOT( contactStatusChanged( KGaduNotify* ) ) ); + QObject::connect( p->session_, SIGNAL( connectionFailed( gg_failure_t )), + SLOT( connectionFailed( gg_failure_t ) ) ); + QObject::connect( p->session_, SIGNAL( connectionSucceed( ) ), + SLOT( connectionSucceed( ) ) ); + QObject::connect( p->session_, SIGNAL( disconnect( Kopete::Account::DisconnectReason ) ), + SLOT( slotSessionDisconnect( Kopete::Account::DisconnectReason ) ) ); + QObject::connect( p->session_, SIGNAL( ackReceived( unsigned int ) ), + SLOT( ackReceived( unsigned int ) ) ); + QObject::connect( p->session_, SIGNAL( pubDirSearchResult( const SearchResult&, unsigned int ) ), + SLOT( slotSearchResult( const SearchResult&, unsigned int ) ) ); + QObject::connect( p->session_, SIGNAL( userListExported() ), + SLOT( userListExportDone() ) ); + QObject::connect( p->session_, SIGNAL( userListRecieved( const QString& ) ), + SLOT( userlist( const QString& ) ) ); + QObject::connect( p->session_, SIGNAL( incomingCtcp( unsigned int ) ), + SLOT( slotIncomingDcc( unsigned int ) ) ); + + QObject::connect( p->pingTimer_, SIGNAL( timeout() ), + SLOT( pingServer() ) ); + + QObject::connect( p->exportTimer_, SIGNAL( timeout() ), + SLOT( slotUserlistSynch() ) ); +} + +void +GaduAccount::setAway( bool isAway, const QString& awayMessage ) +{ + unsigned int currentStatus; + + if ( isAway ) { + currentStatus = ( awayMessage.isEmpty() ) ? GG_STATUS_BUSY : GG_STATUS_BUSY_DESCR; + } + else{ + currentStatus = ( awayMessage.isEmpty() ) ? GG_STATUS_AVAIL : GG_STATUS_AVAIL_DESCR; + } + changeStatus( GaduProtocol::protocol()->convertStatus( currentStatus ), awayMessage ); +} + + +KActionMenu* +GaduAccount::actionMenu() +{ + kdDebug(14100) << "actionMenu() " << endl; + + p->actionMenu_ = new KActionMenu( accountId(), myself()->onlineStatus().iconFor( this ), this ); + p->actionMenu_->popupMenu()->insertTitle( myself()->onlineStatus().iconFor( myself() ), i18n( "%1 <%2> " ). + arg( myself()->property( Kopete::Global::Properties::self()->nickName()).value().toString(), accountId() ) ); + + if ( p->session_->isConnected() ) { + p->searchAction->setEnabled( TRUE ); + p->listputAction->setEnabled( TRUE ); + p->friendsModeAction->setEnabled( TRUE ); + } + else { + p->searchAction->setEnabled( FALSE ); + p->listputAction->setEnabled( FALSE ); + p->friendsModeAction->setEnabled( FALSE ); + } + + if ( contacts().count() > 1 ) { + if ( p->saveListDialog ) { + p->listToFileAction->setEnabled( FALSE ); + } + else { + p->listToFileAction->setEnabled( TRUE ); + } + + p->listToFileAction->setEnabled( TRUE ); + } + else { + p->listToFileAction->setEnabled( FALSE ); + } + + if ( p->loadListDialog ) { + p->listFromFileAction->setEnabled( FALSE ); + } + else { + p->listFromFileAction->setEnabled( TRUE ); + } + p->actionMenu_->insert( new KAction( i18n( "Go O&nline" ), + GaduProtocol::protocol()->convertStatus( GG_STATUS_AVAIL ).iconFor( this ), + 0, this, SLOT( slotGoOnline() ), this, "actionGaduConnect" ) ); + + p->actionMenu_->insert( new KAction( i18n( "Set &Busy" ), + GaduProtocol::protocol()->convertStatus( GG_STATUS_BUSY ).iconFor( this ), + 0, this, SLOT( slotGoBusy() ), this, "actionGaduConnect" ) ); + + p->actionMenu_->insert( new KAction( i18n( "Set &Invisible" ), + GaduProtocol::protocol()->convertStatus( GG_STATUS_INVISIBLE ).iconFor( this ), + 0, this, SLOT( slotGoInvisible() ), this, "actionGaduConnect" ) ); + + p->actionMenu_->insert( new KAction( i18n( "Go &Offline" ), + GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL ).iconFor( this ), + 0, this, SLOT( slotGoOffline() ), this, "actionGaduConnect" ) ); + + p->actionMenu_->insert( new KAction( i18n( "Set &Description..." ), "info", + 0, this, SLOT( slotDescription() ), this, "actionGaduDescription" ) ); + + p->actionMenu_->insert( p->friendsModeAction ); + + p->actionMenu_->popupMenu()->insertSeparator(); + + p->actionMenu_->insert( p->searchAction ); + + p->actionMenu_->popupMenu()->insertSeparator(); + + p->actionMenu_->insert( p->listputAction ); + + p->actionMenu_->popupMenu()->insertSeparator(); + + p->actionMenu_->insert( p->listToFileAction ); + p->actionMenu_->insert( p->listFromFileAction ); + + return p->actionMenu_; +} + +void +GaduAccount::connectWithPassword(const QString& password) +{ + if (password.isEmpty()) { + return; + } + if (isConnected ()) + return; + // FIXME: add status description to this mechainsm, this is a hack now. libkopete design issue. + changeStatus( initialStatus(), p->lastDescription ); +} + +void +GaduAccount::disconnect() +{ + disconnect( Manual ); +} + +void +GaduAccount::disconnect( DisconnectReason reason ) +{ + slotGoOffline(); + p->connectWithSSL = true; + Kopete::Account::disconnected( reason ); +} + +void +GaduAccount::setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason ) +{ + kdDebug(14100) << k_funcinfo << "Called" << endl; + changeStatus( status, reason); +} + +void +GaduAccount::slotUserlistSynch() +{ + if ( !p->exportUserlist ) { + return; + } + p->exportUserlist = false; + kdDebug(14100) << "userlist changed, exporting" << endl; + slotExportContactsList(); +} + +void +GaduAccount::userlistChanged() +{ + p->exportUserlist = true; + p->exportTimer_->changeInterval( USERLISTEXPORT_TIMEOUT ); +} + +bool +GaduAccount::createContact( const QString& contactId, Kopete::MetaContact* parentContact ) +{ + kdDebug(14100) << "createContact " << contactId << endl; + + uin_t uinNumber = contactId.toUInt(); + GaduContact* newContact = new GaduContact( uinNumber, parentContact->displayName(),this, parentContact ); + newContact->setParentIdentity( accountId() ); + addNotify( uinNumber ); + + userlistChanged(); + + return true; +} + +void +GaduAccount::changeStatus( const Kopete::OnlineStatus& status, const QString& descr ) +{ + unsigned int ns; + + kdDebug(14100) << "##### change status #####" << endl; + kdDebug(14100) << "### Status = " << p->session_->isConnected() << endl; + kdDebug(14100) << "### Status description = \"" << descr << "\"" << endl; + + // if change to not available, log off + if ( GG_S_NA( status.internalStatus() ) ) { + if ( !p->session_->isConnected() ) { + return;//already logged off + } + else { + if ( status.internalStatus() == GG_STATUS_NOT_AVAIL_DESCR ) { + if ( p->session_->changeStatusDescription( status.internalStatus(), descr, p->forFriends ) != 0 ) { + return; + } + } + } + p->session_->logoff(); + dccOff(); + } + else { + // if status is for no desc, but we get some desc, than convert it to status with desc + if (!descr.isEmpty() && !GaduProtocol::protocol()->statusWithDescription( status.internalStatus() ) ) { + // and rerun us again. This won't cause any recursive call, as both conversions are static + ns = GaduProtocol::protocol()->statusToWithDescription( status ); + changeStatus( GaduProtocol::protocol()->convertStatus( ns ), descr ); + return; + } + + // well, if it's empty but we want to set status with desc, change it too + if (descr.isEmpty() && GaduProtocol::protocol()->statusWithDescription( status.internalStatus() ) ) { + ns = GaduProtocol::protocol()->statusToWithoutDescription( status ); + changeStatus( GaduProtocol::protocol()->convertStatus( ns ), descr ); + return; + } + + if ( !p->session_->isConnected() ) { + if ( password().cachedValue().isEmpty() ) { + // FIXME: when status string added to connect(), use it here + p->lastDescription = descr; + connect( status/*, descr*/ ); + return; + } + + if ( useTls() != TLS_no ) { + p->connectWithSSL = true; + } + else { + p->connectWithSSL = false; + } + dccOn(); + p->serverIP = 0; + p->currentServer = -1; + p->status = status; + kdDebug(14100) << "#### Connecting..., tls option "<< (int)useTls() << " " << endl; + p->lastDescription = descr; + slotLogin( status.internalStatus(), descr ); + return; + } + else { + p->status = status; + if ( descr.isEmpty() ) { + if ( p->session_->changeStatus( status.internalStatus(), p->forFriends ) != 0 ) + return; + } + else { + if ( p->session_->changeStatusDescription( status.internalStatus(), descr, p->forFriends ) != 0 ) + return; + } + } + } + + myself()->setOnlineStatus( status ); + myself()->setProperty( GaduProtocol::protocol()->propAwayMessage, descr ); + + if ( status.internalStatus() == GG_STATUS_NOT_AVAIL || status.internalStatus() == GG_STATUS_NOT_AVAIL_DESCR ) { + if ( p->pingTimer_ ){ + p->pingTimer_->stop(); + } + } + p->lastDescription = descr; +} + +void +GaduAccount::slotLogin( int status, const QString& dscr ) +{ + p->lastDescription = dscr; + + myself()->setOnlineStatus( GaduProtocol::protocol()->convertStatus( GG_STATUS_CONNECTING )); + myself()->setProperty( GaduProtocol::protocol()->propAwayMessage, dscr ); + + if ( !p->session_->isConnected() ) { + if ( password().cachedValue().isEmpty() ) { + connectionFailed( GG_FAILURE_PASSWORD ); + } + else { + p->loginInfo.password = password().cachedValue(); + p->loginInfo.useTls = p->connectWithSSL; + p->loginInfo.status = status; + p->loginInfo.statusDescr = dscr; + p->loginInfo.forFriends = p->forFriends; + p->loginInfo.server = p->serverIP; + if ( dccEnabled() ) { + p->loginInfo.client_addr = gg_dcc_ip; + p->loginInfo.client_port = gg_dcc_port; + } + else { + p->loginInfo.client_addr = 0; + p->loginInfo.client_port = 0; + } + p->session_->login( &p->loginInfo ); + } + } + else { + p->session_->changeStatus( status ); + } +} + +void +GaduAccount::slotLogoff() +{ + if ( p->session_->isConnected() || p->status == GaduProtocol::protocol()->convertStatus( GG_STATUS_CONNECTING )) { + p->status = GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL ); + changeStatus( p->status ); + p->session_->logoff(); + dccOff(); + } +} + +void +GaduAccount::slotGoOnline() +{ + changeStatus( GaduProtocol::protocol()->convertStatus( GG_STATUS_AVAIL ) ); +} +void +GaduAccount::slotGoOffline() +{ + slotLogoff(); + dccOff(); +} + +void +GaduAccount::slotGoInvisible() +{ + changeStatus( GaduProtocol::protocol()->convertStatus( GG_STATUS_INVISIBLE ) ); +} + +void +GaduAccount::slotGoBusy() +{ + changeStatus( GaduProtocol::protocol()->convertStatus( GG_STATUS_BUSY ) ); +} + +void +GaduAccount::removeContact( const GaduContact* c ) +{ + if ( isConnected() ) { + const uin_t u = c->uin(); + p->session_->removeNotify( u ); + userlistChanged(); + } +} + +void +GaduAccount::addNotify( uin_t uin ) +{ + if ( p->session_->isConnected() ) { + p->session_->addNotify( uin ); + } +} + +void +GaduAccount::notify( uin_t* userlist, int count ) +{ + if ( p->session_->isConnected() ) { + p->session_->notify( userlist, count ); + } +} + +void +GaduAccount::sendMessage( uin_t recipient, const Kopete::Message& msg, int msgClass ) +{ + if ( p->session_->isConnected() ) { + p->session_->sendMessage( recipient, msg, msgClass ); + } +} + +void +GaduAccount::error( const QString& title, const QString& message ) +{ + KMessageBox::error( Kopete::UI::Global::mainWidget(), title, message ); +} + +void +GaduAccount::messageReceived( KGaduMessage* gaduMessage ) +{ + GaduContact* contact = 0; + QPtrList contactsListTmp; + + // FIXME:check for ignored users list + + if ( gaduMessage->sender_id == 0 ) { + //system message, display them or not? + kdDebug(14100) << "####" << " System Message " << gaduMessage->message << endl; + return; + } + + contact = static_cast ( contacts()[ QString::number( gaduMessage->sender_id ) ] ); + + if ( !contact ) { + if ( p->ignoreAnons == true ) { + return; + } + + Kopete::MetaContact* metaContact = new Kopete::MetaContact (); + metaContact->setTemporary ( true ); + contact = new GaduContact( gaduMessage->sender_id, + QString::number( gaduMessage->sender_id ), this, metaContact ); + Kopete::ContactList::self ()->addMetaContact( metaContact ); + addNotify( gaduMessage->sender_id ); + } + + contactsListTmp.append( myself() ); + Kopete::Message msg( gaduMessage->sendTime, contact, contactsListTmp, gaduMessage->message, Kopete::Message::Inbound, Kopete::Message::RichText ); + contact->messageReceived( msg ); +} + +void +GaduAccount::ackReceived( unsigned int recipient ) +{ + GaduContact* contact; + + contact = static_cast ( contacts()[ QString::number( recipient ) ] ); + if ( contact ) { + kdDebug(14100) << "####" << "Received an ACK from " << contact->uin() << endl; + contact->messageAck(); + } + else { + kdDebug(14100) << "####" << "Received an ACK from an unknown user : " << recipient << endl; + } +} + +void +GaduAccount::contactStatusChanged( KGaduNotify* gaduNotify ) +{ + kdDebug(14100) << "####" << " contact's status changed, uin:" << gaduNotify->contact_id <( contacts()[ QString::number( gaduNotify->contact_id ) ] ); + if( !contact ) { + kdDebug(14100) << "Notify not in the list " << gaduNotify->contact_id << endl; + return; + } + + contact->changedStatus( gaduNotify ); +} + +void +GaduAccount::pong() +{ + kdDebug(14100) << "####" << " Pong..." << endl; +} + +void +GaduAccount::pingServer() +{ + kdDebug(14100) << "####" << " Ping..." << endl; + p->session_->ping(); +} + +void +GaduAccount::connectionFailed( gg_failure_t failure ) +{ + bool tryReconnect = false; + QString pass; + + + switch (failure) { + case GG_FAILURE_PASSWORD: + password().setWrong(); + // user pressed CANCEL + p->status = GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL ); + myself()->setOnlineStatus( p->status ); + disconnected( BadPassword ); + return; + default: + if ( p->connectWithSSL ) { + if ( useTls() != TLS_only ) { + slotCommandDone( QString::null, i18n( "connection using SSL was not possible, retrying without." ) ); + kdDebug( 14100 ) << "try without tls now" << endl; + p->connectWithSSL = false; + tryReconnect = true; + p->currentServer = -1; + p->serverIP = 0; + break; + } + } + else { + if ( p->currentServer == NUM_SERVERS - 1 ) { + p->serverIP = 0; + p->currentServer = -1; + kdDebug(14100) << "trying : " << "IP from hub " << endl; + } + else { + p->serverIP = p->servers[ ++p->currentServer ]; + kdDebug(14100) << "trying : " << p->currentServer << " IP " << p->serverIP << endl; + tryReconnect = true; + } + } + break; + } + + if ( tryReconnect ) { + slotLogin( p->status.internalStatus() , p->lastDescription ); + } + else { + error( i18n( "unable to connect to the Gadu-Gadu server(\"%1\")." ).arg( GaduSession::failureDescription( failure ) ), + i18n( "Connection Error" ) ); + p->status = GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL ); + myself()->setOnlineStatus( p->status ); + disconnected( InvalidHost ); + } +} + +void +GaduAccount::dccOn() +{ + if ( dccEnabled() ) { + if ( !p->gaduDcc_ ) { + p->gaduDcc_ = new GaduDCC( this ); + } + kdDebug( 14100 ) << " turn DCC on for " << accountId() << endl; + p->gaduDcc_->registerAccount( this ); + p->loginInfo.client_port = p->gaduDcc_->listeingPort(); + } +} + +void +GaduAccount::dccOff() +{ + if ( p->gaduDcc_ ) { + kdDebug( 14100 ) << "destroying dcc in gaduaccount " << endl; + delete p->gaduDcc_; + p->gaduDcc_ = NULL; + p->loginInfo.client_port = 0; + p->loginInfo.client_addr = 0; + } +} + +void +GaduAccount::slotIncomingDcc( unsigned int uin ) +{ + GaduContact* contact; + GaduDCCTransaction* trans; + + if ( !uin ) { + return; + } + + contact = static_cast( contacts()[ QString::number( uin ) ] ); + + if ( !contact ) { + kdDebug(14100) << "attempt to make dcc connection from unknown uin " << uin << endl; + return; + } + + // if incapabile to transfer files, forget about it. + if ( contact->contactPort() < 10 ) { + kdDebug(14100) << "can't respond to " << uin << " request, his listeing port is too low" << endl; + return; + } + + trans = new GaduDCCTransaction( p->gaduDcc_ ); + if ( trans->setupIncoming( p->loginInfo.uin, contact ) == false ) { + delete trans; + } + +} + +void +GaduAccount::connectionSucceed( ) +{ + kdDebug(14100) << "#### Gadu-Gadu connected! " << endl; + p->status = GaduProtocol::protocol()->convertStatus( p->session_->status() ); + myself()->setOnlineStatus( p->status ); + myself()->setProperty( GaduProtocol::protocol()->propAwayMessage, p->lastDescription ); + startNotify(); + + p->session_->requestContacts(); + p->pingTimer_->start( 3*60*1000 );//3 minute timeout + pingServer(); + + // check if we need to export userlist every USERLISTEXPORT_TIMEOUT ms + p->exportTimer_->start( USERLISTEXPORT_TIMEOUT ); +} + +void +GaduAccount::startNotify() +{ + int i = 0; + if ( !contacts().count() ) { + return; + } + + QDictIterator kopeteContactsList( contacts() ); + + uin_t* userlist = 0; + userlist = new uin_t[ contacts().count() ]; + + for( i=0 ; kopeteContactsList.current() ; ++kopeteContactsList ) { + userlist[i++] = static_cast ((*kopeteContactsList))->uin(); + } + + p->session_->notify( userlist, contacts().count() ); + delete [] userlist; +} + +void +GaduAccount::slotSessionDisconnect( Kopete::Account::DisconnectReason reason ) +{ + uin_t status; + + kdDebug(14100) << "Disconnecting" << endl; + + if (p->pingTimer_) { + p->pingTimer_->stop(); + } + + setAllContactsStatus( GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL ) ); + + status = myself()->onlineStatus().internalStatus(); + if ( status != GG_STATUS_NOT_AVAIL || status != GG_STATUS_NOT_AVAIL_DESCR ) { + myself()->setOnlineStatus( GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL ) ); + } + GaduAccount::disconnect( reason ); +} + +void +GaduAccount::userlist( const QString& contactsListString ) +{ + kdDebug(14100)<<"### Got userlist - gadu account"<exportTimer_->stop(); + + for ( i = 0; i != contactsList.size() ; i++ ) { + kdDebug(14100) << "uin " << contactsList[i].uin << endl; + + if ( contactsList[i].uin.isNull() ) { + kdDebug(14100) << "no Uin, strange.. "<( contacts()[ contactsList[i].uin ] ); + if ( contact == NULL ) { + kdDebug(14100) << "oops, no Kopete::Contact in contacts()[] for some reason, for \"" << contactsList[i].uin << "\"" << endl; + continue; + } + + // update/add infor for contact + contact->setContactDetails( &contactsList[i] ); + + if ( !( contactsList[i].group.isEmpty() ) ) { + // FIXME: libkopete bug i guess, by default contact goes to top level group + // if user desrired to see contact somewhere else, remove it from top level one + metaContact = contact->metaContact(); + metaContact->removeFromGroup( Kopete::Group::topLevel() ); + // put him in all desired groups: + groups = QStringList::split( ",", contactsList[i].group ); + for ( QStringList::Iterator groupsIterator = groups.begin(); groupsIterator != groups.end(); ++groupsIterator ) { + metaContact->addToGroup( Kopete::ContactList::self ()->findGroup ( *groupsIterator) ); + } + } + } + // start to check if we need to export userlist + p->exportUserlist = false; + p->exportTimer_->start( USERLISTEXPORT_TIMEOUT ); +} + +void +GaduAccount::userListExportDone() +{ + slotCommandDone( QString::null, i18n( "Contacts exported to the server.") ); +} + +void +GaduAccount::slotFriendsMode() +{ + p->forFriends = !p->forFriends; + kdDebug( 14100 ) << "for friends mode: " << p->forFriends << " desc" << p->lastDescription << endl; + // now change status, it will changing it with p->forFriends flag + changeStatus( p->status, p->lastDescription ); + + saveFriendsMode( p->forFriends ); + +} + +// FIXME: make loading and saving nonblocking (at the moment KFileDialog stops plugin/kopete) + +void +GaduAccount::slotExportContactsListToFile() +{ + KTempFile tempFile; + tempFile.setAutoDelete( true ); + + if ( p->saveListDialog ) { + kdDebug( 14100 ) << " save contacts to file: alread waiting for input " << endl ; + return; + } + + p->saveListDialog = new KFileDialog( "::kopete-gadu" + accountId(), QString::null, + Kopete::UI::Global::mainWidget(), "gadu-list-save", false ); + p->saveListDialog->setCaption( + i18n("Save Contacts List for Account %1 As").arg( + myself()->property( Kopete::Global::Properties::self()->nickName()).value().toString() ) ); + + if ( p->saveListDialog->exec() == QDialog::Accepted ) { + QCString list = p->textcodec_->fromUnicode( userlist()->asString() ); + + if ( tempFile.status() ) { + // say cheese, can't create file..... + error( i18n( "Unable to create temporary file." ), i18n( "Save Contacts List Failed" ) ); + } + else { + QTextStream* tempStream = tempFile.textStream(); + (*tempStream) << list.data(); + tempFile.close(); + + bool res = KIO::NetAccess::upload( + tempFile.name() , + p->saveListDialog->selectedURL() , + Kopete::UI::Global::mainWidget() + ); + if ( !res ) { + // say it failed + error( KIO::NetAccess::lastErrorString(), i18n( "Save Contacts List Failed" ) ); + } + } + + } + delete p->saveListDialog; + p->saveListDialog = NULL; +} + +void +GaduAccount::slotImportContactsFromFile() +{ + KURL url; + QCString list; + QString oname; + + if ( p->loadListDialog ) { + kdDebug( 14100 ) << "load contacts from file: alread waiting for input " << endl ; + return; + } + + p->loadListDialog = new KFileDialog( "::kopete-gadu" + accountId(), QString::null, + Kopete::UI::Global::mainWidget(), "gadu-list-load", true ); + p->loadListDialog->setCaption( + i18n("Load Contacts List for Account %1 As").arg( + myself()->property( Kopete::Global::Properties::self()->nickName()).value().toString() ) ); + + if ( p->loadListDialog->exec() == QDialog::Accepted ) { + url = p->loadListDialog->selectedURL(); + kdDebug(14100) << "a:" << url << "\nb:" << oname << endl; + if ( KIO::NetAccess::download( url, oname, Kopete::UI::Global::mainWidget() ) ) { + QFile tempFile( oname ); + if ( tempFile.open( IO_ReadOnly ) ) { + list = tempFile.readAll(); + tempFile.close(); + KIO::NetAccess::removeTempFile( oname ); + // and store it + kdDebug( 14100 ) << "loaded list:" << endl; + kdDebug( 14100 ) << list << endl; + kdDebug( 14100 ) << " --------------- " << endl; + userlist( p->textcodec_->toUnicode( list ) ); + } + else { + error( tempFile.errorString(), + i18n( "Contacts List Load Has Failed" ) ); + } + } + else { + // say, it failed misourably + error( KIO::NetAccess::lastErrorString(), + i18n( "Contacts List Load Has Failed" ) ); + } + + } + delete p->loadListDialog; + p->loadListDialog = NULL; +} + +unsigned int +GaduAccount::getPersonalInformation() +{ + return p->session_->getPersonalInformation(); +} + +bool +GaduAccount::publishPersonalInformation( ResLine& d ) +{ + return p->session_->publishPersonalInformation( d ); +} + +void +GaduAccount::slotExportContactsList() +{ + p->session_->exportContactsOnServer( userlist() ); +} + +GaduContactsList* +GaduAccount::userlist() +{ + GaduContact* contact; + GaduContactsList* contactsList = new GaduContactsList(); + int i; + + if ( !contacts().count() ) { + return contactsList; + } + + QDictIterator contactsIterator( contacts() ); + + for( i=0 ; contactsIterator.current() ; ++contactsIterator ) { + contact = static_cast( *contactsIterator ); + if ( contact->uin() != static_cast( myself() )->uin() ) { + contactsList->addContact( *contact->contactDetails() ); + } + } + + return contactsList; +} + +void +GaduAccount::slotSearch( int uin ) +{ + new GaduPublicDir( this, uin ); +} + +void +GaduAccount::slotChangePassword() +{ +} + +void +GaduAccount::slotCommandDone( const QString& /*title*/, const QString& what ) +{ + //FIXME: any chance to have my own title in event popup ? + KNotifyClient::userEvent( 0, what, + KNotifyClient::PassivePopup, KNotifyClient::Notification ); +} + +void +GaduAccount::slotCommandError(const QString& title, const QString& what ) +{ + error( title, what ); +} + +void +GaduAccount::slotDescription() +{ + GaduAway* away = new GaduAway( this ); + + if( away->exec() == QDialog::Accepted ) { + changeStatus( GaduProtocol::protocol()->convertStatus( away->status() ), + away->awayText() ); + } + delete away; +} + +unsigned int +GaduAccount::pubDirSearch( ResLine& query, int ageFrom, int ageTo, bool onlyAlive ) +{ + return p->session_->pubDirSearch( query, ageFrom, ageTo, onlyAlive ); +} + +void +GaduAccount::pubDirSearchClose() +{ + p->session_->pubDirSearchClose(); +} + +void +GaduAccount::slotSearchResult( const SearchResult& result, unsigned int seq ) +{ + emit pubDirSearchResult( result, seq ); +} + +void +GaduAccount::sendFile( GaduContact* peer, QString& filePath ) +{ + GaduDCCTransaction* gtran = new GaduDCCTransaction( p->gaduDcc_ ); + gtran->setupOutgoing( peer, filePath ); +} + +void +GaduAccount::dccRequest( GaduContact* peer ) +{ + if ( peer && p->session_ ) { + p->session_->dccRequest( peer->uin() ); + } +} + +// dcc settings +bool +GaduAccount::dccEnabled() +{ + QString s = p->config->readEntry( QString::fromAscii( "useDcc" ) ); + kdDebug( 14100 ) << "dccEnabled: "<< s << endl; + if ( s == QString::fromAscii( "enabled" ) ) { + return true; + } + return false; +} + +bool +GaduAccount::setDcc( bool d ) +{ + QString s; + bool f = true; + + if ( d == false ) { + dccOff(); + s = QString::fromAscii( "disabled" ); + } + else { + s = QString::fromAscii( "enabled" ); + } + + p->config->writeEntry( QString::fromAscii( "useDcc" ), s ); + + if ( p->session_->isConnected() && d ) { + dccOn(); + } + + kdDebug( 14100 ) << "s: "<config->writeEntry( QString::fromAscii( "forFriends" ), + i == true ? QString::fromAscii( "1" ) : QString::fromAscii( "0" ) ); +} + +bool +GaduAccount::loadFriendsMode() +{ + QString s; + bool r; + int n; + + s = p->config->readEntry( QString::fromAscii( "forFriends" ) ); + n = s.toInt( &r ); + + if ( n ) { + return true; + } + + return false; + +} + +// might be bit inconsistent with what I used in DCC, but hell, it is so much easier to parse :-) +bool +GaduAccount::ignoreAnons() +{ + QString s; + bool r; + int n; + + s = p->config->readEntry( QString::fromAscii( "ignoreAnons" ) ); + n = s.toInt( &r ); + + if ( n ) { + return true; + } + + return false; + +} + +void +GaduAccount::setIgnoreAnons( bool i ) +{ + p->ignoreAnons = i; + p->config->writeEntry( QString::fromAscii( "ignoreAnons" ), + i == true ? QString::fromAscii( "1" ) : QString::fromAscii( "0" ) ); +} + +GaduAccount::tlsConnection +GaduAccount::useTls() +{ + QString s; + bool c; + unsigned int oldC; + tlsConnection Tls; + + s = p->config->readEntry( QString::fromAscii( "useEncryptedConnection" ) ); + oldC = s.toUInt( &c ); + // we have old format + if ( c ) { + kdDebug( 14100 ) << "old format for param useEncryptedConnection, value " << + oldC << " willl be converted to new string value" << endl; + setUseTls( (tlsConnection) oldC ); + // should be string now, unless there was an error reading + s = p->config->readEntry( QString::fromAscii( "useEncryptedConnection" ) ); + kdDebug( 14100 ) << "new useEncryptedConnection value : " << s << endl; + } + + Tls = TLS_no; + if ( s == "TLS_ifAvaliable" ) { + Tls = TLS_ifAvaliable; + } + if ( s == "TLS_only" ) { + Tls = TLS_only; + } + + return Tls; +} + +void +GaduAccount::setUseTls( tlsConnection ut ) +{ + QString s; + switch( ut ) { + case TLS_ifAvaliable: + s = "TLS_ifAvaliable"; + break; + + case TLS_only: + s = "TLS_only"; + break; + + default: + case TLS_no: + s = "TLS_no"; + break; + } + + p->config->writeEntry( QString::fromAscii( "useEncryptedConnection" ), s ); +} + +#include "gaduaccount.moc" diff --git a/kopete/protocols/gadu/gaduaccount.h b/kopete/protocols/gadu/gaduaccount.h new file mode 100644 index 00000000..b71e0d6e --- /dev/null +++ b/kopete/protocols/gadu/gaduaccount.h @@ -0,0 +1,173 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003-2004 Grzegorz Jaskiewicz +// Copyright (C) 2003 Zack Rusin +// +// gaduaccount.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef GADUACCOUNT_H +#define GADUACCOUNT_H + +#include "kopetepasswordedaccount.h" +#include "kopeteonlinestatus.h" +#include "kopetecontact.h" + +#include "gaducontactlist.h" +#include "gadusession.h" + +#include + +#include +#include +#include +#include +#include +#include + +class GaduAccountPrivate; + +class GaduContact; +class GaduProtocol; +namespace Kopete { class Protocol; } +namespace Kopete { class Message; } +class GaduCommand; +class QTimer; +class KActionMenu; +class GaduDCC; +class GaduDCCTransaction; + +class GaduAccount : public Kopete::PasswordedAccount +{ + Q_OBJECT + +public: + GaduAccount( Kopete::Protocol*, const QString& accountID, const char* name = 0L ); + ~GaduAccount(); + //{ + void setAway( bool isAway, const QString& awayMessage = QString::null ); + KActionMenu* actionMenu(); + void dccRequest( GaduContact* ); + void sendFile( GaduContact* , QString& ); + //} + enum tlsConnection{ TLS_ifAvaliable = 0, TLS_only, TLS_no }; + unsigned int getPersonalInformation(); + bool publishPersonalInformation( ResLine& d ); + +public slots: + //{ + void connectWithPassword(const QString &password); + void disconnect( DisconnectReason ); + void disconnect(); + void setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason = QString::null); + //} + + void changeStatus( const Kopete::OnlineStatus& status, const QString& descr = QString::null ); + void slotLogin( int status = GG_STATUS_AVAIL, const QString& dscr = QString::null ); + void slotLogoff(); + void slotGoOnline(); + void slotGoOffline(); + void slotGoInvisible(); + void slotGoBusy(); + void slotDescription(); + void slotSearch( int uin = 0); + + void removeContact( const GaduContact* ); + + void addNotify( uin_t ); + void notify( uin_t*, int ); + + void sendMessage( uin_t recipient, const Kopete::Message& msg, + int msgClass = GG_CLASS_CHAT ); + + void error( const QString& title, const QString& message ); + + void pong(); + void pingServer(); + + // those two functions are connected straight to gadusession ones + // with the same names/params. This was the easiest way to + // make this interface public + unsigned int pubDirSearch( ResLine& query, int ageFrom, int ageTo, bool onlyAlive ); + void pubDirSearchClose(); + + // tls + tlsConnection useTls(); + void setUseTls( tlsConnection ); + + // dcc + bool dccEnabled(); + bool setDcc( bool ); + + // anons + bool ignoreAnons(); + void setIgnoreAnons( bool ); + + // forFriends + bool loadFriendsMode(); + void saveFriendsMode( bool ); + +signals: + void pubDirSearchResult( const SearchResult&, unsigned int ); + +protected: + //{ + bool createContact( const QString& contactId, + Kopete::MetaContact* parentContact ); + //} + +private slots: + void startNotify(); + + void messageReceived( KGaduMessage* ); + void ackReceived( unsigned int ); + void contactStatusChanged( KGaduNotify* ); + void slotSessionDisconnect( Kopete::Account::DisconnectReason ); + + void slotExportContactsList(); + void slotExportContactsListToFile(); + void slotImportContactsFromFile(); + void slotFriendsMode(); + + void userlist( const QString& contacts ); + GaduContactsList* userlist(); + void slotUserlistSynch(); + + void connectionFailed( gg_failure_t failure ); + void connectionSucceed( ); + + void slotChangePassword(); + + void slotCommandDone( const QString&, const QString& ); + void slotCommandError( const QString&, const QString& ); + + void slotSearchResult( const SearchResult& result, unsigned int seq ); + void userListExportDone(); + + void slotIncomingDcc( unsigned int ); + +private: + void initConnections(); + void initActions(); + void dccOn(); + void dccOff(); + void userlistChanged(); + + GaduAccountPrivate* p; +}; + +#endif diff --git a/kopete/protocols/gadu/gaduaddcontactpage.cpp b/kopete/protocols/gadu/gaduaddcontactpage.cpp new file mode 100644 index 00000000..d2aed62b --- /dev/null +++ b/kopete/protocols/gadu/gaduaddcontactpage.cpp @@ -0,0 +1,134 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// Copyright (C) 2002-2003 Zack Rusin +// +// gaduaddconectpage.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include "kopetemetacontact.h" + +#include "gaduadd.h" +#include "gaduprotocol.h" +#include "gaduaccount.h" +#include "gaduaddcontactpage.h" +#include "gaducontact.h" +#include "gaducontactlist.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +GaduAddContactPage::GaduAddContactPage( GaduAccount* owner, QWidget* parent, const char* name ) +: AddContactPage( parent, name ) +{ + account_ = owner; + ( new QVBoxLayout( this ) )->setAutoAdd( true ); + addUI_ = new GaduAddUI( this ); + connect( addUI_->addEdit_, SIGNAL( textChanged( const QString & ) ), SLOT( slotUinChanged( const QString & ) ) ); + addUI_->addEdit_->setValidChars( "1234567890" ); + addUI_->addEdit_->setText( "" ); + addUI_->groups->setDisabled( TRUE ); + + kdDebug(14100) << "filling gropus" << endl; + + fillGroups(); +} + +GaduAddContactPage::~GaduAddContactPage() +{ + delete addUI_; +} + +void +GaduAddContactPage::fillGroups() +{ + /* + Kopete::Group *g; + QPtrList gl = Kopete::ContactList::self()->groups(); + for( g = gl.first(); g; g = gl.next() ) { + QCheckListItem* item = new QCheckListItem( addUI_->groups, g->displayName(), QCheckListItem::CheckBox ); + kdDebug(14100) << g->displayName() << " " << g->groupId() << endl; + } + */ +} + +void +GaduAddContactPage::showEvent( QShowEvent* e ) +{ + slotUinChanged( QString::null ); + AddContactPage::showEvent( e ); +} + +void +GaduAddContactPage::slotUinChanged( const QString & ) +{ + emit dataValid( this, validateData() ); +} + +bool +GaduAddContactPage::validateData() +{ + bool ok; + long u; + + u = addUI_->addEdit_->text().toULong( &ok ); + if ( u == 0 ) { + return false; + } + + return ok; +} + +bool +GaduAddContactPage::apply( Kopete::Account* a , Kopete::MetaContact* mc ) +{ + if ( validateData() ) { + QString userid = addUI_->addEdit_->text().stripWhiteSpace(); + QString name = addUI_->nickEdit_->text().stripWhiteSpace(); + if ( a != account_ ) { + kdDebug(14100) << "Problem because accounts differ: " << a->accountId() + << " , " << account_->accountId() << endl; + } + if ( !a->addContact( userid, mc, Kopete::Account::ChangeKABC ) ) { + return false; + } + GaduContact *contact = static_cast( a->contacts()[ userid ] ); + + contact->setProperty( GaduProtocol::protocol()->propEmail, addUI_->emailEdit_->text().stripWhiteSpace() ); + contact->setProperty( GaduProtocol::protocol()->propFirstName, addUI_->fornameEdit_->text().stripWhiteSpace() ); + contact->setProperty( GaduProtocol::protocol()->propLastName, addUI_->snameEdit_->text().stripWhiteSpace() ); + contact->setProperty( GaduProtocol::protocol()->propPhoneNr, addUI_->telephoneEdit_ ->text().stripWhiteSpace() ); + /* + contact->setProperty( "ignored", i18n( "ignored" ), "false" ); + contact->setProperty( "nickName", i18n( "nick name" ), name ); + */ + } + return true; +} + +#include "gaduaddcontactpage.moc" diff --git a/kopete/protocols/gadu/gaduaddcontactpage.h b/kopete/protocols/gadu/gaduaddcontactpage.h new file mode 100644 index 00000000..39e4d52e --- /dev/null +++ b/kopete/protocols/gadu/gaduaddcontactpage.h @@ -0,0 +1,61 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// Copyright (C) 2002-2003 Zack Rusin +// +// gaduaddconectpage.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef GADUADDCONTACTPAGE_H +#define GADUADDCONTACTPAGE_H + +#include "addcontactpage.h" + +class GaduAccount; +class GaduAddUI; +class QLabel; +namespace Kopete { class MetaContact; } +class QString; +class QShowEvent; +class GaduAddUI; + + +class GaduAddContactPage : public AddContactPage +{ + Q_OBJECT + +public: + GaduAddContactPage( GaduAccount*, QWidget* parent = 0, const char* name = 0 ); + ~GaduAddContactPage(); + virtual bool validateData(); + virtual bool apply( Kopete::Account* , Kopete::MetaContact * ); + + protected: + void showEvent(QShowEvent *e); + +public slots: + void slotUinChanged( const QString& ); + +private: + void fillGroups(); + GaduAccount* account_; + GaduAddUI* addUI_; + QLabel* noaddMsg1_; + QLabel* noaddMsg2_; +}; + +#endif diff --git a/kopete/protocols/gadu/gaduaway.cpp b/kopete/protocols/gadu/gaduaway.cpp new file mode 100644 index 00000000..a84fcd0f --- /dev/null +++ b/kopete/protocols/gadu/gaduaway.cpp @@ -0,0 +1,86 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// +// gaduaway.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include "gaduaccount.h" +#include "gaduprotocol.h" +#include "gaduawayui.h" +#include "gaduaway.h" + +#include "kopeteonlinestatus.h" + +#include +#include + +#include +#include +#include + +#include "gaduawayui.h" +#include "gaduaway.h" + +GaduAway::GaduAway( GaduAccount* account, QWidget* parent, const char* name ) +: KDialogBase( parent, name, true, i18n( "Away Dialog" ), + KDialogBase::Ok | KDialogBase::Cancel, + KDialogBase::Ok, true ), account_( account ) +{ + Kopete::OnlineStatus ks; + int s; + + ui_ = new GaduAwayUI( this ); + setMainWidget( ui_ ); + + ks = account->myself()->onlineStatus(); + s = GaduProtocol::protocol()->statusToWithDescription( ks ); + + if ( s == GG_STATUS_NOT_AVAIL_DESCR ) { + ui_->statusGroup_->find( GG_STATUS_NOT_AVAIL_DESCR )->setDisabled( TRUE ); + ui_->statusGroup_->setButton( GG_STATUS_AVAIL_DESCR ); + } + else { + ui_->statusGroup_->setButton( s ); + } + + ui_->textEdit_->setText( account->myself()->property( "awayMessage" ).value().toString() ); + connect( this, SIGNAL( applyClicked() ), SLOT( slotApply() ) ); +} + +int +GaduAway::status() const +{ + return ui_->statusGroup_->id( ui_->statusGroup_->selected() ); +} + +QString +GaduAway::awayText() const +{ + return ui_->textEdit_->text(); +} + + +void +GaduAway::slotApply() +{ + if ( account_ ) { + account_->changeStatus( GaduProtocol::protocol()->convertStatus( status() ),awayText() ); + } +} + +#include "gaduaway.moc" diff --git a/kopete/protocols/gadu/gaduaway.h b/kopete/protocols/gadu/gaduaway.h new file mode 100644 index 00000000..bebbcd9f --- /dev/null +++ b/kopete/protocols/gadu/gaduaway.h @@ -0,0 +1,49 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// Copyright (C) 2002-2003 Zack Rusin +// +// gaduaway.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef GADUAWAY_H +#define GADUAWAY_H + +#include +#include + +class GaduAccount; +class GaduAwayUI; + +class GaduAway : public KDialogBase +{ + Q_OBJECT + +public: + GaduAway( GaduAccount*, QWidget* parent = 0, const char* name = 0 ); + int status() const; + QString awayText() const; + +protected slots: + void slotApply(); + +private: + GaduAccount* account_; + GaduAwayUI* ui_; +}; + +#endif diff --git a/kopete/protocols/gadu/gaducommands.cpp b/kopete/protocols/gadu/gaducommands.cpp new file mode 100644 index 00000000..431b1ab4 --- /dev/null +++ b/kopete/protocols/gadu/gaducommands.cpp @@ -0,0 +1,411 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003-2004 Grzegorz Jaskiewicz +// Copyright (C) 2002-2003 Zack Rusin +// +// gaducommands.cpp - all basic, and not-session dependent commands +// (meaning you don't have to be logged in for any of these). +// These delete themselves, meaning you don't +// have to/can't delete them explicitly and have to create +// them dynamically (via the 'new' call). +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include "gaducommands.h" +#include "gadusession.h" + +#include +#include +#include +#include + +#include +#include + +#include + +GaduCommand::GaduCommand( QObject* parent, const char* name ) +: QObject( parent, name ), read_( 0 ), write_( 0 ) +{ +} + +GaduCommand::~GaduCommand() +{ + //QSocketNotifiers are children and will + //be deleted anyhow +} + +bool +GaduCommand::done() const +{ + return done_; +} + +void +GaduCommand::checkSocket( int fd, int checkWhat ) +{ + read_ = new QSocketNotifier( fd, QSocketNotifier::Read, this ); + read_->setEnabled( false ); + QObject::connect( read_, SIGNAL( activated(int) ), SLOT( forwarder() ) ); + + write_ = new QSocketNotifier( fd, QSocketNotifier::Write, this ); + write_->setEnabled( false ); + QObject::connect( write_, SIGNAL( activated(int) ), SLOT( forwarder() ) ); + + enableNotifiers( checkWhat ); +} + +void +GaduCommand::enableNotifiers( int checkWhat ) +{ + if ( read_ ) { + if( checkWhat & GG_CHECK_READ ) { + read_->setEnabled( true ); + } + } + + if ( write_ ) { + if( checkWhat & GG_CHECK_WRITE ) { + write_->setEnabled( true ); + } + } +} + +void +GaduCommand::disableNotifiers() +{ + if ( read_ ) { + read_->setEnabled( false ); + } + if ( write_ ) { + write_->setEnabled( false ); + } +} + +void +GaduCommand::deleteNotifiers() +{ + if ( read_ ) { + delete read_; + read_ = NULL; + } + if ( write_ ) { + delete write_; + write_ = NULL; + } +} + +void +GaduCommand::forwarder() +{ + emit socketReady(); +} + +RegisterCommand::RegisterCommand( QObject* parent, const char* name ) +:GaduCommand( parent, name ), state( RegisterStateNoToken ), session_( 0 ), uin( 0 ) +{ +} + +RegisterCommand::RegisterCommand( const QString& email, const QString& password, QObject* parent, const char* name ) +:GaduCommand( parent, name ), state( RegisterStateNoToken ), email_( email ), password_( password ), session_( 0 ), uin( 0 ) +{ +} + +RegisterCommand::~RegisterCommand() +{ +} + +unsigned int RegisterCommand::newUin() +{ + if ( state == RegisterStateDone ) { + return uin; + } +// else + return 0; +} +void +RegisterCommand::requestToken() +{ + kdDebug( 14100 ) << "requestToken Initialisation" << endl; + state = RegisterStateWaitingForToken; + + if ( !( session_ = gg_token( 1 ) ) ) { + emit error( i18n( "Gadu-Gadu" ), i18n( "Unable to retrieve token." ) ); + state = RegisterStateNoToken; + return; + } + + connect( this, SIGNAL( socketReady() ), SLOT( watcher() ) ); + checkSocket( session_->fd, session_->check ); + + return; +} + +void +RegisterCommand::setUserinfo( const QString& email, const QString& password, const QString& token ) +{ + email_ = email; + password_ = password; + tokenString = token; +} + +void +RegisterCommand::cancel() +{ + deleteNotifiers(); + session_ = NULL; + +} + +void +RegisterCommand::execute() +{ + if ( state != RegisterStateGotToken || email_.isEmpty() || password_.isEmpty() || tokenString.isEmpty() ) { + // get token first || fill information + kdDebug(14100) << "not enough info to run execute, state: " << state << " , email: " << email_ << ", password present " << !password_.isEmpty() << ", token string:" << tokenString << endl; + return; + } + session_ = gg_register3( email_.ascii(), password_.ascii(), tokenId.ascii(), tokenString.ascii(), 1 ); + if ( !session_ ) { + error( i18n( "Gadu-Gadu" ), i18n( "Registration FAILED" ) ); + return; + } + state = RegisterStateWaitingForNumber; + connect( this, SIGNAL( socketReady() ), SLOT( watcher() ) ); + checkSocket( session_->fd, session_->check ); +} + +void RegisterCommand::watcher() +{ + gg_pubdir* pubDir; + + if ( state == RegisterStateWaitingForToken ) { + disableNotifiers(); + if ( gg_token_watch_fd( session_ ) == -1 ) { + deleteNotifiers(); + emit error( i18n( "Gadu-Gadu" ), i18n( "Unknown connection error while retrieving token." ) ); + gg_token_free( session_ ); + session_ = NULL; + state = RegisterStateNoToken; + return; + } + + pubDir = (struct gg_pubdir *)session_->data; + emit operationStatus( i18n( "Token retrieving status: %1" ).arg( GaduSession::stateDescription( session_->state ) ) ); + switch ( session_->state ) { + case GG_STATE_CONNECTING: + kdDebug( 14100 ) << "Recreating notifiers " << endl; + deleteNotifiers(); + checkSocket( session_->fd, 0); + break; + case GG_STATE_ERROR: + deleteNotifiers(); + emit error( i18n( "Gadu-Gadu token retrieve problem" ), GaduSession::errorDescription( session_->error ) ); + gg_token_free( session_ ); + session_ = NULL; + state = RegisterStateNoToken; + return; + break; + case GG_STATE_DONE: + struct gg_token* sp = ( struct gg_token* )session_->data; + tokenId = (char *)sp->tokenid; + kdDebug( 14100 ) << "got Token!, ID: " << tokenId << endl; + deleteNotifiers(); + if ( pubDir->success ) { + QPixmap tokenImg; + tokenImg.loadFromData( (const unsigned char *)session_->body, session_->body_size ); + state = RegisterStateGotToken; + emit tokenRecieved( tokenImg, tokenId ); + } + else { + emit error( i18n( "Gadu-Gadu" ), i18n( "Unable to retrieve token." ) ); + state = RegisterStateNoToken; + deleteLater(); + } + gg_token_free( session_ ); + session_ = NULL; + disconnect( this, SLOT( watcher() ) ); + return; + break; + } + enableNotifiers( session_->check ); + } + if ( state == RegisterStateWaitingForNumber ) { + disableNotifiers(); + if ( gg_register_watch_fd( session_ ) == -1 ) { + deleteNotifiers(); + emit error( i18n( "Gadu-Gadu" ), i18n( "Unknown connection error while registering." ) ); + gg_free_register( session_ ); + session_ = NULL; + state = RegisterStateGotToken; + return; + } + pubDir = (gg_pubdir*) session_->data; + emit operationStatus( i18n( "Registration status: %1" ).arg( GaduSession::stateDescription( session_->state ) ) ); + switch ( session_->state ) { + case GG_STATE_CONNECTING: + kdDebug( 14100 ) << "Recreating notifiers " << endl; + deleteNotifiers(); + checkSocket( session_->fd, 0); + break; + case GG_STATE_ERROR: + deleteNotifiers(); + emit error( i18n( "Gadu-Gadu Registration Error" ), GaduSession::errorDescription( session_->error ) ); + gg_free_register( session_ ); + session_ = NULL; + state = RegisterStateGotToken; + return; + break; + + case GG_STATE_DONE: + deleteNotifiers(); + if ( pubDir->success && pubDir->uin ) { + uin= pubDir->uin; + state = RegisterStateDone; + emit done( i18n( "Registration Finished" ), i18n( "Registration has completed successfully." ) ); + } + else { + emit error( i18n( "Registration Error" ), i18n( "Incorrect data sent to server." ) ); + state = RegisterStateGotToken; + } + gg_free_register( session_ ); + session_ = NULL; + disconnect( this, SLOT( watcher() ) ); + deleteLater(); + return; + break; + } + enableNotifiers( session_->check ); + return; + } +} + +RemindPasswordCommand::RemindPasswordCommand( QObject* parent, const char* name ) +: GaduCommand( parent, name ), uin_( 0 ), session_( 0 ) +{ +} + +RemindPasswordCommand::RemindPasswordCommand( uin_t uin, QObject* parent, const char* name ) +: GaduCommand( parent, name ), uin_( uin ), session_( 0 ) +{ +} + +RemindPasswordCommand::~RemindPasswordCommand() +{ +} + +void +RemindPasswordCommand::setUIN( uin_t uin ) +{ + uin_ = uin; +} + +void +RemindPasswordCommand::execute() +{ +} + +void +RemindPasswordCommand::watcher() +{ + disableNotifiers(); + + if ( gg_remind_passwd_watch_fd( session_ ) == -1 ) { + gg_free_remind_passwd( session_ ); + emit error( i18n( "Connection Error" ), i18n( "Password reminding finished prematurely due to a connection error." ) ); + done_ = true; + deleteLater(); + return; + } + + if ( session_->state == GG_STATE_ERROR ) { + gg_free_remind_passwd( session_ ); + emit error( i18n( "Connection Error" ), i18n( "Password reminding finished prematurely due to a connection error." ) ); + done_ = true; + deleteLater(); + return; + } + + if ( session_->state == GG_STATE_DONE ) { + struct gg_pubdir* p = static_cast( session_->data ); + QString finished = (p->success) ? i18n( "Successfully" ) : i18n( "Unsuccessful. Please retry." ); + emit done( i18n( "Remind Password" ), i18n( "Remind password finished: " ) + finished ); + gg_free_remind_passwd( session_ ); + done_ = true; + deleteLater(); + return; + } + enableNotifiers( session_->check ); +} + +ChangePasswordCommand::ChangePasswordCommand( QObject* parent, const char* name ) +: GaduCommand( parent, name ), session_( 0 ) +{ +} + +ChangePasswordCommand::~ChangePasswordCommand() +{ +} + +void +ChangePasswordCommand::setInfo( uin_t uin, const QString& passwd, const QString& newpasswd, const QString& newemail ) +{ + uin_ = uin; + passwd_ = passwd; + newpasswd_ = newpasswd; + newemail_ = newemail; +} + +void +ChangePasswordCommand::execute() +{ +} + +void +ChangePasswordCommand::watcher() +{ + disableNotifiers(); + + if ( gg_pubdir_watch_fd( session_ ) == -1 ) { + gg_change_passwd_free( session_ ); + emit error( i18n( "Connection Error" ), i18n( "Password changing finished prematurely due to a connection error." ) ); + done_ = true; + deleteLater(); + return; + } + + if ( session_->state == GG_STATE_ERROR ) { + gg_free_change_passwd( session_ ); + emit error( i18n( "State Error" ), + i18n( "Password changing finished prematurely due to a session related problem (try again later)." ) ); + done_ = true; + deleteLater(); + return; + } + + if ( session_->state == GG_STATE_DONE ) { + emit done( i18n( "Changed Password" ), i18n( "Your password has been changed." ) ); + gg_free_change_passwd( session_ ); + done_ = true; + deleteLater(); + return; + } + + enableNotifiers( session_->check ); +} + + +#include "gaducommands.moc" diff --git a/kopete/protocols/gadu/gaducommands.h b/kopete/protocols/gadu/gaducommands.h new file mode 100644 index 00000000..8a48ec3d --- /dev/null +++ b/kopete/protocols/gadu/gaducommands.h @@ -0,0 +1,150 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003-2004 Grzegorz Jaskiewicz +// Copyright (C) 2002-2003 Zack Rusin +// +// gaducommands.h - all basic, and not-session dependent commands +// (meaning you don't have to be logged in for any of these). +// These delete themselves, meaning you don't +// have to/can't delete them explicitly and have to create +// them dynamically (via the 'new' call). +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef GADUCOMMANDS_H +#define GADUCOMMANDS_H + +#include + +#include + +class QSocketNotifier; +class QStringList; +class QPixmap; + +class GaduCommand : public QObject +{ + Q_OBJECT + +public: + GaduCommand( QObject* parent = 0, const char* name = 0 ); + virtual ~GaduCommand(); + + virtual void execute() = 0; + + bool done() const; + +signals: + //e.g. emit done( i18n("Done"), i18n("Registration complete") ); + void done( const QString& title, const QString& what ); + void error( const QString& title, const QString& error ); + void socketReady(); + void operationStatus( const QString ); + +protected: + void checkSocket( int, int ); + void enableNotifiers( int ); + void disableNotifiers(); + void deleteNotifiers(); + + bool done_; + +protected slots: + void forwarder(); + +private: + QSocketNotifier* read_; + QSocketNotifier* write_; +}; + +class RegisterCommand : public GaduCommand +{ + Q_OBJECT + +public: + RegisterCommand( QObject* parent = 0, const char* name = 0 ); + RegisterCommand( const QString& email, const QString& password , + QObject* parent = 0, const char* name = 0 ); + ~RegisterCommand(); + + void setUserinfo( const QString& email, const QString& password, const QString& token ); + void execute(); + unsigned int newUin(); + void requestToken(); + void cancel(); + +signals: + void tokenRecieved( QPixmap, QString ); + +protected slots: + void watcher(); + +private: + enum RegisterState{ RegisterStateNoToken, RegisterStateWaitingForToken, RegisterStateGotToken, RegisterStateWaitingForNumber, RegisterStateDone }; + RegisterState state; + QString email_; + QString password_; + struct gg_http* session_; + int uin; + QString tokenId; + QString tokenString; +}; + +class RemindPasswordCommand : public GaduCommand +{ + Q_OBJECT + +public: + RemindPasswordCommand( uin_t uin, QObject* parent = 0, const char* name = 0 ); + RemindPasswordCommand( QObject* parent = 0, const char* name = 0 ); + ~RemindPasswordCommand(); + + void setUIN( uin_t ); + void execute(); + +protected slots: + void watcher(); + +private: + uin_t uin_; + struct gg_http* session_; +}; + +class ChangePasswordCommand : public GaduCommand +{ + Q_OBJECT + +public: + ChangePasswordCommand( QObject* parent = 0, const char* name = 0 ); + ~ChangePasswordCommand(); + + void setInfo( uin_t uin, const QString& passwd, const QString& newpasswd, + const QString& newemail ); + void execute(); + +protected slots: + void watcher(); + +private: + struct gg_http* session_; + QString passwd_; + QString newpasswd_; + QString newemail_; + uin_t uin_; +}; + + +#endif diff --git a/kopete/protocols/gadu/gaducontact.cpp b/kopete/protocols/gadu/gaducontact.cpp new file mode 100644 index 00000000..dddd965f --- /dev/null +++ b/kopete/protocols/gadu/gaducontact.cpp @@ -0,0 +1,384 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// Copyright (C) 2002-2003 Zack Rusin +// +// gaducontact.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include +#include +#include +#include +#include + +#include "gaduaccount.h" +#include "gaduprotocol.h" +#include "gaducontact.h" +#include "gadupubdir.h" +#include "gadueditcontact.h" +#include "gaducontactlist.h" +#include "gadusession.h" + +#include "kopetechatsessionmanager.h" +#include "kopetegroup.h" +#include "kopetemetacontact.h" +#include "kopetestdaction.h" +#include "kopeteuiglobal.h" + +#include "userinfodialog.h" + +using Kopete::UserInfoDialog; + +GaduContact::GaduContact( uin_t uin, const QString& name, Kopete::Account* account, Kopete::MetaContact* parent ) +: Kopete::Contact( account, QString::number( uin ), parent ), uin_( uin ) +{ + msgManager_ = 0L; + account_ = static_cast( account ); + + remote_port = 0; + version = 0; + image_size = 0; + // let us not ignore the contact by default right? causes ugly bug if + // setContactDetails is not run on a contact right after it is added + ignored_ = false; + + thisContact_.append( this ); + + initActions(); + + // don't call libkopete functions like these until the object is fully + // constructed. all GaduContact construction must be above this point. + setFileCapable( true ); + + //offline + setOnlineStatus( GaduProtocol::protocol()->convertStatus( 0 ) ); + + setProperty( Kopete::Global::Properties::self()->nickName(), name ); +} + +QString +GaduContact::identityId() const +{ + return parentIdentity_; +} + +void +GaduContact::setParentIdentity( const QString& id) +{ + parentIdentity_ = id; +} + +uin_t +GaduContact::uin() const +{ + return uin_; +} + +void +GaduContact::sendFile( const KURL &sourceURL, const QString &/*fileName*/, uint /*fileSize*/ ) +{ + QString filePath; + + //If the file location is null, then get it from a file open dialog + if( !sourceURL.isValid() ) + filePath = KFileDialog::getOpenFileName(QString::null, "*", 0l , i18n("Kopete File Transfer")); + else + filePath = sourceURL.path(-1); + + kdDebug(14120) << k_funcinfo << "File chosen to send:" << filePath << endl; + + account_->sendFile( this, filePath ); +} + + +void +GaduContact::changedStatus( KGaduNotify* newstatus ) +{ + if ( newstatus->description.isNull() ) { + setOnlineStatus( GaduProtocol::protocol()->convertStatus( newstatus->status ) ); + removeProperty( GaduProtocol::protocol()->propAwayMessage ); + } + else { + setOnlineStatus( GaduProtocol::protocol()->convertStatus( newstatus->status ) ); + setProperty( GaduProtocol::protocol()->propAwayMessage, newstatus->description ); + } + + remote_ip = newstatus->remote_ip; + remote_port = newstatus->remote_port; + version = newstatus->version; + image_size = newstatus->image_size; + + setFileCapable( newstatus->fileCap ); + + kdDebug(14100) << "uin:" << uin() << " port: " << remote_port << " remote ip: " << remote_ip.ip4Addr() << " image size: " << image_size << " version: " << version << endl; + +} + +QHostAddress& +GaduContact::contactIp() +{ + return remote_ip; +} + +unsigned short +GaduContact::contactPort() +{ + return remote_port; +} + +Kopete::ChatSession* +GaduContact::manager( Kopete::Contact::CanCreateFlags canCreate ) +{ + if ( !msgManager_ && canCreate ) { + msgManager_ = Kopete::ChatSessionManager::self()->create( account_->myself(), thisContact_, GaduProtocol::protocol() ); + connect( msgManager_, SIGNAL( messageSent( Kopete::Message&, Kopete::ChatSession*) ), + this, SLOT( messageSend( Kopete::Message&, Kopete::ChatSession*) ) ); + connect( msgManager_, SIGNAL( destroyed() ), this, SLOT( slotChatSessionDestroyed() ) ); + + } + kdDebug(14100) << "GaduContact::manager returning: " << msgManager_ << endl; + return msgManager_; +} + +void +GaduContact::slotChatSessionDestroyed() +{ + msgManager_ = 0L; +} + +void +GaduContact::initActions() +{ + actionSendMessage_ = KopeteStdAction::sendMessage( this, SLOT( execute() ), this, "actionMessage" ); + actionInfo_ = KopeteStdAction::contactInfo( this, SLOT( slotUserInfo() ), this, "actionInfo" ); +} + +void +GaduContact::messageReceived( Kopete::Message& msg ) +{ + manager(Kopete::Contact::CanCreate)->appendMessage( msg ); +} + +void +GaduContact::messageSend( Kopete::Message& msg, Kopete::ChatSession* mgr ) +{ + if ( msg.plainBody().isEmpty() ) { + return; + } + mgr->appendMessage( msg ); + account_->sendMessage( uin_, msg ); +} + +bool +GaduContact::isReachable() +{ + return account_->isConnected(); +} + +QPtrList* +GaduContact::customContextMenuActions() +{ + QPtrList *fakeCollection = new QPtrList(); + //show profile + KAction* actionShowProfile = new KAction( i18n("Show Profile") , "info", 0, + this, SLOT( slotShowPublicProfile() ), + this, "actionShowPublicProfile" ); + + fakeCollection->append( actionShowProfile ); + + KAction* actionEditContact = new KAction( i18n("Edit...") , "edit", 0, + this, SLOT( slotEditContact() ), + this, "actionEditContact" ); + + fakeCollection->append( actionEditContact ); + + return fakeCollection; +} + +void +GaduContact::slotEditContact() +{ + new GaduEditContact( static_cast(account()), this, Kopete::UI::Global::mainWidget() ); +} + +void +GaduContact::slotShowPublicProfile() +{ + account_->slotSearch( uin_ ); +} + +void +GaduContact::slotUserInfo() +{ + /// FIXME: use more decent information here + UserInfoDialog *dlg = new UserInfoDialog( i18n( "Gadu contact" ) ); + + dlg->setName( metaContact()->displayName() ); + dlg->setId( QString::number( uin_ ) ); + dlg->setStatus( onlineStatus().description() ); + dlg->setAwayMessage( description_ ); + dlg->show(); +} + +void +GaduContact::deleteContact() +{ + if ( account_->isConnected() ) { + account_->removeContact( this ); + deleteLater(); + } + else { + KMessageBox::error( Kopete::UI::Global::mainWidget(), + i18n( "Please go online to remove a contact from your contact list." ), + i18n( "Gadu-Gadu Plugin" )); + } +} + +void +GaduContact::serialize( QMap& serializedData, QMap& ) +{ + serializedData[ "email" ] = property( GaduProtocol::protocol()->propEmail ).value().toString(); + serializedData[ "FirstName" ] = property( GaduProtocol::protocol()->propFirstName ).value().toString(); + serializedData[ "SecondName" ] = property( GaduProtocol::protocol()->propLastName ).value().toString(); + serializedData[ "telephone" ] = property( GaduProtocol::protocol()->propPhoneNr ).value().toString(); + serializedData[ "ignored" ] = ignored_ ? "true" : "false"; +} + +bool +GaduContact::setContactDetails( const GaduContactsList::ContactLine* cl ) +{ + setProperty( GaduProtocol::protocol()->propEmail, cl->email ); + setProperty( GaduProtocol::protocol()->propFirstName, cl->firstname ); + setProperty( GaduProtocol::protocol()->propLastName, cl->surname ); + setProperty( GaduProtocol::protocol()->propPhoneNr, cl->phonenr ); + //setProperty( "ignored", i18n( "ignored" ), cl->ignored ? "true" : "false" ); + ignored_ = cl->ignored; + //setProperty( "nickName", i18n( "nick name" ), cl->nickname ); + + return true; +} + +GaduContactsList::ContactLine* +GaduContact::contactDetails() +{ + Kopete::GroupList groupList; + QString groups; + + GaduContactsList::ContactLine* cl = new GaduContactsList::ContactLine; + + cl->firstname = property( GaduProtocol::protocol()->propFirstName ).value().toString(); + cl->surname = property( GaduProtocol::protocol()->propLastName ).value().toString(); + //cl->nickname = property( "nickName" ).value().toString(); + cl->email = property( GaduProtocol::protocol()->propEmail ).value().toString(); + cl->phonenr = property( GaduProtocol::protocol()->propPhoneNr ).value().toString(); + cl->ignored = ignored_; //( property( "ignored" ).value().toString() == "true" ); + + cl->uin = QString::number( uin_ ); + cl->displayname = metaContact()->displayName(); + + cl->offlineTo = false; + cl->landline = QString(""); + + groupList = metaContact()->groups(); + + Kopete::Group* gr; + for ( gr = groupList.first (); gr ; gr = groupList.next () ) { +// if present in any group, don't export to top level +// FIXME: again, probably bug in libkopete +// in case of topLevel group, Kopete::Group::displayName() returns "TopLevel" ineasted of just " " or "/" +// imo TopLevel group should be detected like i am doing that below + if ( gr!=Kopete::Group::topLevel() ) { + groups += gr->displayName()+","; + } + } + + if ( groups.length() ) { + groups.truncate( groups.length()-1 ); + } + cl->group = groups; + + return cl; +} + +QString +GaduContact::findBestContactName( const GaduContactsList::ContactLine* cl ) +{ + QString name; + + if ( cl == NULL ) { + return name; + } + + if ( cl->uin.isEmpty() ) { + return name; + } + + name = cl->uin; + + if ( cl->displayname.length() ) { + name = cl->displayname; + } + else { + // no name either + if ( cl->nickname.isEmpty() ) { + // maybe we can use fistname + surname ? + if ( cl->firstname.isEmpty() && cl->surname.isEmpty() ) { + name = cl->uin; + } + // what a shame, i have to use UIN than :/ + else { + if ( cl->firstname.isEmpty() ) { + name = cl->surname; + } + else { + if ( cl->surname.isEmpty() ) { + name = cl->firstname; + } + else { + name = cl->firstname + " " + cl->surname; + } + } + } + } + else { + name = cl->nickname; + } + } + + return name; +} + +void +GaduContact::messageAck() +{ + manager(Kopete::Contact::CanCreate)->messageSucceeded(); +} + +void +GaduContact::setIgnored( bool val ) +{ + ignored_ = val; +} + +bool +GaduContact::ignored() +{ + return ignored_; +} + +#include "gaducontact.moc" diff --git a/kopete/protocols/gadu/gaducontact.h b/kopete/protocols/gadu/gaducontact.h new file mode 100644 index 00000000..9a51838e --- /dev/null +++ b/kopete/protocols/gadu/gaducontact.h @@ -0,0 +1,119 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// Copyright (C) 2002-2003 Zack Rusin +// +// gaducontact.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef GADUCONTACT_H +#define GADUCONTACT_H + +#include +#include + +#include "gaducontactlist.h" + +#include "kopeteaccount.h" +#include "kopetecontact.h" +#include "kopetemessage.h" + +#include + +class KAction; +class GaduAccount; +namespace Kopete { class Account; } +namespace Kopete { class ChatSession; } +class KGaduNotify; +class QString; +class QStringList; + +class GaduContact : public Kopete::Contact +{ + Q_OBJECT + +public: + GaduContact( unsigned int, const QString&, Kopete::Account*, Kopete::MetaContact* ); + + virtual bool isReachable(); + virtual void serialize( QMap&, QMap& ); + virtual QPtrList* customContextMenuActions(); + virtual QString identityId() const; + + GaduContactsList::ContactLine* contactDetails(); + + // this one set's only: + // email, firstname, surname, phonenr, ignored, nickname + // uin is const for GaduContact, and displayname needs to be changed through metaContact + bool setContactDetails( const GaduContactsList::ContactLine* ); + + void setParentIdentity( const QString& ); + void setIgnored( bool ); + bool ignored(); + + static QString findBestContactName( const GaduContactsList::ContactLine* ); + void changedStatus( KGaduNotify* ); + + uin_t uin() const; + + QHostAddress& contactIp(); + unsigned short contactPort(); + +public slots: + void slotUserInfo(); + void deleteContact(); + void messageReceived( Kopete::Message& ); + void messageSend( Kopete::Message&, Kopete::ChatSession* ); + void messageAck(); + void slotShowPublicProfile(); + void slotEditContact(); + virtual void sendFile( const KURL &sourceURL = KURL(), + const QString &fileName = QString::null, uint fileSize = 0L ); + + +protected: + virtual Kopete::ChatSession* manager( Kopete::Contact::CanCreateFlags canCreate = Kopete::Contact::CanCreate ); + void initActions(); + +private: + const uin_t uin_; + bool ignored_; + + Kopete::ChatSession* msgManager_; + QString description_; + QString parentIdentity_; + GaduAccount* account_; + + KAction* actionSendMessage_; + KAction* actionInfo_; + KAction* actionRemove_; + + QPtrList thisContact_; + + + QHostAddress remote_ip; + unsigned int remote_port; + unsigned int version; + unsigned int image_size; + + +private slots: + void slotChatSessionDestroyed(); + +}; + +#endif diff --git a/kopete/protocols/gadu/gaducontactlist.cpp b/kopete/protocols/gadu/gaducontactlist.cpp new file mode 100644 index 00000000..750f5224 --- /dev/null +++ b/kopete/protocols/gadu/gaducontactlist.cpp @@ -0,0 +1,207 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2004 Grzegorz Jaskiewicz +// +// gaducontactlist.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// + + +#include "gaducontactlist.h" +#include "qstringlist.h" +#include "kdebug.h" + +GaduContactsList::GaduContactsList() +{ +} + +GaduContactsList::~GaduContactsList() +{ +} + +GaduContactsList::GaduContactsList( QString sList ) +{ + QStringList::iterator stringIterator; + QStringList strList; + QString empty; + ContactLine cl; + bool email; + + if ( sList.isEmpty() || sList.isNull() ) { + return; + } + + if ( ( !sList.contains( '\n' ) && sList.contains( ';' ) ) || !sList.contains( ';' ) ) { + return; + } + + QStringList ln = QStringList::split( QChar( '\n' ), sList, true ); + QStringList::iterator lni = ln.begin( ); + + while( lni != ln.end() ) { + + QString cline = (*lni); + if ( cline.isNull() ) { + break; + } + + strList = QStringList::split( QChar( ';' ), cline, true ); + + stringIterator = strList.begin(); + + if ( strList.count() >= 12 ) { + email = true; + } + else { + email = false; + } + + +//each line ((firstname);(secondname);(nickname);(altnick);(tel);(group);(uin); +// new stuff attached at the end: +// email;aliveSoundfile;notifyType;msgSoundType;messageSound;offlineTo;homePhone; + stringIterator = strList.begin(); + + cl.firstname = (*stringIterator); + + if ( cl.firstname == QString( "i" ) ) { + kdDebug(14100) << cline << " ignored" << endl; + cl.ignored = true; + cl.uin = strList[6]; + ++lni; + cList.append( cl ); + continue; + } + else { + cl.ignored = false; + } + + cl.surname = (*++stringIterator); + cl.nickname = (*++stringIterator); + cl.displayname = (*++stringIterator); + cl.phonenr = (*++stringIterator); + cl.group = (*++stringIterator); + cl.uin = (*++stringIterator); + if ( email ) { + cl.email = (*++stringIterator); + // no use for custom sounds, at least now + ++stringIterator; + ++stringIterator; + ++stringIterator; + ++stringIterator; + + if ( stringIterator != strList.end() ) { + cl.offlineTo = (*++stringIterator) == QString("0") ? false : true; + cl.landline = (*++stringIterator); + } + } + else { + cl.email = empty; + } + + ++lni; + + if ( cl.uin.isNull() ) { + continue; + } + + cList.append( cl ); + } + + return; +} + +void +GaduContactsList::addContact( ContactLine& cl ) +{ + cList.append( cl ); +} + +void +GaduContactsList::addContact( + QString& displayname, + QString& group, + QString& uin, + QString& firstname, + QString& surname, + QString& nickname, + QString& phonenr, + QString& email, + bool ignored, + bool offlineTo, + QString& landline +) +{ + ContactLine cl; + + cl.displayname = displayname; + cl.group = group; + cl.uin = uin; + cl.firstname = firstname; + cl.surname = surname; + cl.nickname = nickname; + cl.phonenr = phonenr; + cl.email = email; + cl.ignored = ignored; + cl.offlineTo = offlineTo; + cl.landline = landline; + + cList.append( cl ); + +} + +QString +GaduContactsList::asString() +{ + QString contacts; + + for ( it = cList.begin(); it != cList.end(); ++it ) { + if ( (*it).ignored ) { + contacts += "i;;;;;;" + (*it).uin + "\n"; + } + else { +// name;surname;nick;displayname;telephone;group(s);uin;email;;0;0;;offlineTo;homePhone; + contacts += + (*it).firstname + ";"+ + (*it).surname + ";"+ + (*it).nickname + ";"+ + (*it).displayname + ";"+ + (*it).phonenr + ";"+ + (*it).group + ";"+ + (*it).uin + ";"+ + (*it).email + + ";;0;0;;" + + ((*it).offlineTo == true ? QString("1") : QString("0")) + + ";" + + (*it).landline + + ";\r\n"; + } + } + return contacts; +} + +unsigned int +GaduContactsList::size() +{ + return cList.size(); +} + +const GaduContactsList::ContactLine& +GaduContactsList::operator[]( unsigned int i ) +{ + return cList[i]; +} diff --git a/kopete/protocols/gadu/gaducontactlist.h b/kopete/protocols/gadu/gaducontactlist.h new file mode 100644 index 00000000..cfedeba4 --- /dev/null +++ b/kopete/protocols/gadu/gaducontactlist.h @@ -0,0 +1,67 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2004 Grzegorz Jaskiewicz +// +// gaducontactlist.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// + +#ifndef GADUCONTACTLIST_H +#define GADUCONTACTLIST_H + +#include + +class QString; + +class GaduContactsList +{ +public: + struct ContactLine { + + QString displayname; + QString group; + QString uin; + QString firstname; + QString surname; + QString nickname; + QString phonenr; + QString email; + bool ignored; + bool offlineTo; + QString landline; + }; + + GaduContactsList(); + GaduContactsList( QString ); + ~GaduContactsList(); + QString asString(); + void addContact( ContactLine &cl ); + void addContact( QString& displayname, QString& group, + QString& uin, QString& firstname, + QString& surname, QString& nickname, + QString& phonenr, QString& email, + bool ignored, bool offlineTo, + QString& landline + ); + unsigned int size(); + const GaduContactsList::ContactLine& operator[]( unsigned int i ); +private: + typedef QValueList CList; + CList cList; + CList::iterator it; +}; +#endif diff --git a/kopete/protocols/gadu/gadudcc.cpp b/kopete/protocols/gadu/gadudcc.cpp new file mode 100644 index 00000000..ab6a6223 --- /dev/null +++ b/kopete/protocols/gadu/gadudcc.cpp @@ -0,0 +1,186 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2004 Grzegorz Jaskiewicz +// +// gadudcc.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include +#include +#include +#include + +#include + +#include + +#include "gadudccserver.h" +#include "gadudcc.h" +#include "gadudcctransaction.h" +#include "gaduaccount.h" + +#include "libgadu.h" + +#include +#include +#include +#include +#include + +volatile unsigned int GaduDCC::referenceCount = 0; + +GaduDCCServer* GaduDCC::dccServer = NULL; + +static QMutex initmutex; + +typedef QMap< unsigned int, GaduAccount* > gaduAccounts; +static gaduAccounts accounts; + +GaduDCC::GaduDCC( QObject* parent, const char* name ) +:QObject( parent, name ) +{ +} + +bool +GaduDCC::unregisterAccount() +{ + return unregisterAccount( accountId ); +} + +GaduAccount* +GaduDCC::account( unsigned int uin ) +{ + return accounts[ uin ]; +} + +bool +GaduDCC::unregisterAccount( unsigned int id ) +{ + initmutex.lock(); + + if ( id == 0 ) { + kdDebug(14100) << "ID nan" << endl; + initmutex.unlock(); + return false; + } + + if ( !accounts.contains( id ) ) { + kdDebug(14100) << "attempt to unregister not registered account" << endl; + initmutex.unlock(); + return false; + } + + accounts.remove( id ); + + if ( --referenceCount <= 0 ) { + kdDebug(14100) << "closing dcc socket" << endl; + referenceCount = 0; + if ( dccServer ) { + delete dccServer; + dccServer = NULL; + } + } + kdDebug(14100) << "reference count " << referenceCount << endl; + initmutex.unlock(); + + return true; +} + +bool +GaduDCC::registerAccount( GaduAccount* account ) +{ + unsigned int aid; + + if ( !account ) { + return false; + } + + if ( account->accountId().isEmpty() ) { + kdDebug(14100) << "attempt to register account with empty ID" << endl; + return false; + } + + initmutex.lock(); + + aid = account->accountId().toInt(); + + if ( accounts.contains( aid ) ) { + kdDebug(14100) << "attempt to register already registered account" << endl; + initmutex.unlock(); + return false; + } + + accountId = aid; + kdDebug( 14100 ) << " attempt to register " << accountId << endl; + + accounts[ accountId ] = account; + + referenceCount++; + + if ( !dccServer) { + dccServer = new GaduDCCServer(); + } + + connect( dccServer, SIGNAL( incoming( gg_dcc*, bool& ) ), SLOT( slotIncoming( gg_dcc*, bool& ) ) ); + + initmutex.unlock(); + + return true; +} +void +GaduDCC::slotIncoming( gg_dcc* incoming, bool& handled ) +{ + gg_dcc* newdcc; + GaduDCCTransaction* dt; + + kdDebug( 14100 ) << "slotIncomming for UIN: " << incoming->uin << endl; + + // no uin? I'm so sorry + // this screws file receiving (using kadu 0.4.x as peer) for me +// if ( !incoming->uin ) { +// return; +// } + + handled = true; + // TODO: limit number of connections per contact, or maybe even use parametr for that + newdcc = new gg_dcc; + memcpy( newdcc, incoming, sizeof( gg_dcc ) ); + dt = new GaduDCCTransaction( this ); + if ( dt->setupIncoming( newdcc ) == false ) { + // FIXME: write something to user, maybe, or not... + delete dt; + } +} + +GaduDCC::~GaduDCC() +{ + if ( accounts.contains( accountId ) ) { + kdDebug( 14100 ) << "unregister account " << accountId << " in destructor " << endl; + unregisterAccount( accountId ); + } +} + +unsigned int +GaduDCC::listeingPort() +{ + if ( dccServer ) { + return dccServer->listeingPort(); + } + return 0; +} + +#include "gadudcc.moc" diff --git a/kopete/protocols/gadu/gadudcc.h b/kopete/protocols/gadu/gadudcc.h new file mode 100644 index 00000000..37ae3e9a --- /dev/null +++ b/kopete/protocols/gadu/gadudcc.h @@ -0,0 +1,66 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2004 Grzegorz Jaskiewicz +// +// gadurichtextformat.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + + +#ifndef GADUDCC_H +#define GADUDCC_H + +#include + +class QSocketNotifier; +class QHostAddress; +class QString; +class gg_dcc; +class GaduDCCTransaction; +class GaduAccount; +class GaduDCCServer; + +class GaduDCC: public QObject { + Q_OBJECT +public: + GaduDCC( QObject* parent, const char* name = NULL ); + ~GaduDCC(); + bool unregisterAccount(); + bool registerAccount( GaduAccount* ); + unsigned int listeingPort(); + void unset(); + void execute(); + GaduAccount* account( unsigned int ); + + QMap requests; +signals: + void dccConnect( GaduDCCTransaction* dccTransaction ); + +private slots: + void slotIncoming( gg_dcc*, bool& ); + +private: + void closeDCC(); + bool unregisterAccount( unsigned int ); + + unsigned int accountId; + + static GaduDCCServer* dccServer; + + static volatile unsigned int referenceCount; +}; + +#endif diff --git a/kopete/protocols/gadu/gadudccserver.cpp b/kopete/protocols/gadu/gadudccserver.cpp new file mode 100644 index 00000000..fb14277e --- /dev/null +++ b/kopete/protocols/gadu/gadudccserver.cpp @@ -0,0 +1,196 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2004 Grzegorz Jaskiewicz +// +// gadurichtextformat.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include +#include +#include +#include + +#include + +#include + +#include "gadudccserver.h" +#include "libgadu.h" +#include "gaduaccount.h" + +#include +#include +#include + +GaduDCCServer::GaduDCCServer( QHostAddress* dccIp, unsigned int port ) +:QObject() +{ + kdDebug( 14100 ) << "dcc socket NULL, creating new liteining socket " << endl; + + // don't care about UIN at that point + dccSock = gg_dcc_socket_create( (unsigned int)-1, port ); + + if ( dccSock == NULL ){ + kdDebug(14100) << "attempt to initialize gadu-dcc listeing socket FAILED" << endl; + return; + } + + kdDebug(14100) << "attempt to initialize gadu-dcc listeing socket sucess" << endl; + + // using global variables sucks, don't have too much choice thou + if ( dccIp == NULL ) { + gg_dcc_ip = 0xffffffff; // 255.255.255.255 + } + else { + gg_dcc_ip = htonl( dccIp->ip4Addr() ); + } + gg_dcc_port = dccSock->port; + + createNotifiers( true ); + enableNotifiers( dccSock->check ); +} + +GaduDCCServer::~GaduDCCServer() +{ + kdDebug( 14100 ) << "gadu dcc server destructor " << endl; + closeDCC(); +} + +void +GaduDCCServer::closeDCC() +{ + if ( dccSock ) { + disableNotifiers(); + destroyNotifiers(); + gg_dcc_free( dccSock ); + dccSock = NULL; + gg_dcc_ip = 0; + gg_dcc_port = 0; + } + +} + +unsigned int +GaduDCCServer::listeingPort() +{ + if ( dccSock == NULL ) { + return 0; + } +// else + return dccSock->port; +} + +void +GaduDCCServer::destroyNotifiers() +{ + disableNotifiers(); + if ( read_ ) { + delete read_; + read_ = NULL; + } + if ( write_ ) { + delete write_; + write_ = NULL; + } +} + +void +GaduDCCServer::createNotifiers( bool connect ) +{ + if ( !dccSock ){ + return; + } + + read_ = new QSocketNotifier( dccSock->fd, QSocketNotifier::Read, this ); + read_->setEnabled( false ); + + write_ = new QSocketNotifier( dccSock->fd, QSocketNotifier::Write, this ); + write_->setEnabled( false ); + + if ( connect ) { + QObject::connect( read_, SIGNAL( activated( int ) ), SLOT( watcher() ) ); + QObject::connect( write_, SIGNAL( activated( int ) ), SLOT( watcher() ) ); + } +} + +void +GaduDCCServer::enableNotifiers( int checkWhat ) +{ + if( (checkWhat & GG_CHECK_READ) && read_ ) { + read_->setEnabled( true ); + } + if( (checkWhat & GG_CHECK_WRITE) && write_ ) { + write_->setEnabled( true ); + } +} + +void +GaduDCCServer::disableNotifiers() +{ + if ( read_ ) { + read_->setEnabled( false ); + } + if ( write_ ) { + write_->setEnabled( false ); + } +} + +void +GaduDCCServer::watcher() { + + gg_event* dccEvent; + bool handled = false; + + disableNotifiers(); + + dccEvent = gg_dcc_watch_fd( dccSock ); + if ( ! dccEvent ) { + // connection is fucked + // we should try to reenable it +// closeDCC(); + return; + } + switch ( dccEvent->type ) { + case GG_EVENT_NONE: + break; + case GG_EVENT_DCC_ERROR: + kdDebug( 14100 ) << " dcc error occured " << endl; + break; + case GG_EVENT_DCC_NEW: + // I do expect reciver to set this boolean to true if he handled signal + // if so, no other reciver should be bothered with it, and I shall not close it + // otherwise connection is closed as not handled + emit incoming( dccEvent->event.dcc_new, handled ); + if ( !handled ) { + if ( dccEvent->event.dcc_new->file_fd > 0) { + close( dccEvent->event.dcc_new->file_fd ); + } + gg_dcc_free( dccEvent->event.dcc_new ); + } + break; + default: + kdDebug(14100) << "unknown/unhandled DCC EVENT: " << dccEvent->type << endl; + break; + } + + if ( dccEvent ) { + gg_free_event( dccEvent ); + } + + enableNotifiers( dccSock->check ); +} +#include "gadudccserver.moc" diff --git a/kopete/protocols/gadu/gadudccserver.h b/kopete/protocols/gadu/gadudccserver.h new file mode 100644 index 00000000..50916533 --- /dev/null +++ b/kopete/protocols/gadu/gadudccserver.h @@ -0,0 +1,66 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2004 Grzegorz Jaskiewicz +// +// gadurichtextformat.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + + +#ifndef GADUDCCSERVER_H +#define GADUDCCSERVER_H + +#include +#include + +class QSocketNotifier; +class QString; +class gg_dcc; +class GaduDCCTransaction; +class GaduAccount; + +class GaduDCCServer: public QObject { + Q_OBJECT +public: + GaduDCCServer( QHostAddress* dccIp = NULL, unsigned int port = 1550 ); + ~GaduDCCServer(); + unsigned int listeingPort(); + +signals: + void incoming( gg_dcc*, bool& ); + +private slots: + void watcher(); + +private: + void enableNotifiers( int ); + void disableNotifiers(); + void checkDescriptor(); + + void destroyNotifiers(); + void createNotifiers( bool ); + void closeDCC(); + + QHostAddress config_dccip; + QHostAddress config_extip; + + gg_dcc* dccSock; + + QSocketNotifier* read_; + QSocketNotifier* write_; +}; + +#endif diff --git a/kopete/protocols/gadu/gadudcctransaction.cpp b/kopete/protocols/gadu/gadudcctransaction.cpp new file mode 100644 index 00000000..561852fe --- /dev/null +++ b/kopete/protocols/gadu/gadudcctransaction.cpp @@ -0,0 +1,454 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2004 Grzegorz Jaskiewicz +// +// gadudcctransaction.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "kopetetransfermanager.h" +#include "kopetemetacontact.h" +#include "kopeteuiglobal.h" + +#include +#include + +#include "gadudcctransaction.h" +#include "gaducontact.h" +#include "gaduaccount.h" +#include "gadudcc.h" + +#include "libgadu.h" + +GaduDCCTransaction::GaduDCCTransaction( GaduDCC* parent, const char* name ) +:QObject( parent, name ), gaduDCC_( parent ) +{ + read_ = NULL; + write_ = NULL; + contact = NULL; + transfer_ = NULL; + dccSock_ = NULL; + peer = 0; +} + +GaduDCCTransaction::~GaduDCCTransaction() +{ + closeDCC(); +} + +unsigned int +GaduDCCTransaction::recvUIN() +{ + if ( dccSock_ ) { + return dccSock_->uin; + } + return 0; +} + +unsigned int +GaduDCCTransaction::peerUIN() +{ + if ( dccSock_ ) { + return dccSock_->peer_uin; + } + return 0; +} + +bool +GaduDCCTransaction::setupOutgoing( GaduContact* peerContact, QString& filePath ) +{ + GaduContact* me; + GaduAccount* metoo; + + if ( !peerContact ) { + return false; + } + + me = static_cast( peerContact->account()->myself() ); + + QString aaa = peerContact->contactIp().toString(); + kdDebug( 14100 ) << "slotOutgoin for UIN: " << peerContact->uin() << " port " << peerContact->contactPort() << " ip " <contactPort() >= 10 ) { + dccSock_ = gg_dcc_send_file( htonl( peerContact->contactIp().ip4Addr() ), peerContact->contactPort(), me->uin(), peerContact->uin() ); + gg_dcc_fill_file_info(dccSock_,filePath.ascii()); + transfer_ = Kopete::TransferManager::transferManager()->addTransfer ( peerContact, + filePath, dccSock_->file_info.size, peerContact->metaContact()->displayName(), Kopete::FileTransferInfo::Outgoing ); + createNotifiers( true ); + enableNotifiers( dccSock_->check ); + } + else { + kdDebug( 14100 ) << "Peer " << peerContact->uin() << " is passive, requesting reverse connection" << endl; + metoo = static_cast( me->account() ); + gaduDCC_->requests[peerContact->uin()]=filePath; + metoo->dccRequest( peerContact ); + } + + return false; +} + +bool +GaduDCCTransaction::setupIncoming( const unsigned int uin, GaduContact* peerContact ) +{ + + if ( !peerContact ) { + kdDebug( 14100 ) << "setupIncoming called with peerContact == NULL " << endl; + return false; + } + + QString aaa = peerContact->contactIp().toString(); + kdDebug( 14100 ) << "setupIncoming for UIN: " << uin << " port " << peerContact->contactPort() << " ip " <uin(); + dccSock_ = gg_dcc_get_file( htonl( peerContact->contactIp().ip4Addr() ), peerContact->contactPort(), uin, peer ); + + contact = peerContact; + return setupIncoming( dccSock_ ); + +} + +bool +GaduDCCTransaction::setupIncoming( gg_dcc* dccS ) +{ + if ( !dccS ) { + kdDebug(14100) << "gg_dcc_get_file failed in GaduDCCTransaction::setupIncoming" << endl; + return false; + } + + dccSock_ = dccS; + + peer = dccS->uin; + + connect ( Kopete::TransferManager::transferManager(), SIGNAL( accepted( Kopete::Transfer *, const QString & ) ), + this, SLOT( slotIncomingTransferAccepted ( Kopete::Transfer *, const QString & ) ) ); + connect ( Kopete::TransferManager::transferManager(), SIGNAL( refused( const Kopete::FileTransferInfo & ) ), + this, SLOT( slotTransferRefused( const Kopete::FileTransferInfo & ) ) ); + + incoming = true; + createNotifiers( true ); + enableNotifiers( dccSock_->check ); + + return true; +} + + +void +GaduDCCTransaction::closeDCC() +{ + kdDebug(14100) << "closeDCC()" << endl; + + disableNotifiers(); + destroyNotifiers(); + gg_dcc_free( dccSock_ ); + dccSock_ = NULL; +} + +void +GaduDCCTransaction::destroyNotifiers() +{ + disableNotifiers(); + if ( read_ ) { + delete read_; + read_ = NULL; + } + if ( write_ ) { + delete write_; + write_ = NULL; + } +} + +void +GaduDCCTransaction::createNotifiers( bool connect ) +{ + if ( !dccSock_ ){ + return; + } + + read_ = new QSocketNotifier( dccSock_->fd, QSocketNotifier::Read, this ); + read_->setEnabled( false ); + + write_ = new QSocketNotifier( dccSock_->fd, QSocketNotifier::Write, this ); + write_->setEnabled( false ); + + if ( connect ) { + QObject::connect( read_, SIGNAL( activated( int ) ), SLOT( watcher() ) ); + QObject::connect( write_, SIGNAL( activated( int ) ), SLOT( watcher() ) ); + } +} + +void +GaduDCCTransaction::enableNotifiers( int checkWhat ) +{ + if( (checkWhat & GG_CHECK_READ) && read_ ) { + read_->setEnabled( true ); + } + if( (checkWhat & GG_CHECK_WRITE) && write_ ) { + write_->setEnabled( true ); + } +} + +void +GaduDCCTransaction::disableNotifiers() +{ + if ( read_ ) { + read_->setEnabled( false ); + } + if ( write_ ) { + write_->setEnabled( false ); + } +} +void +GaduDCCTransaction::slotIncomingTransferAccepted ( Kopete::Transfer* transfer, const QString& fileName ) +{ + + if ( (long)transfer->info().transferId () != transferId_ ) { + return; + } + + transfer_ = transfer; + localFile_.setName( fileName ); + + if ( localFile_.exists() ) { + KGuiItem resumeButton( i18n ( "&Resume" ) ); + KGuiItem overwriteButton( i18n ( "Over&write" ) ); + switch ( KMessageBox::questionYesNoCancel( Kopete::UI::Global::mainWidget (), + i18n( "The file %1 already exists, do you want to resume or overwrite it?" ).arg( fileName ), + i18n( "File Exists: %1" ).arg( fileName ), resumeButton, overwriteButton ) ) + { + // resume + case KMessageBox::Yes: + if ( localFile_.open( IO_WriteOnly | IO_Append ) ) { + dccSock_->offset = localFile_.size(); + dccSock_->file_fd = localFile_.handle(); + } + break; + // overwrite + case KMessageBox::No: + if ( localFile_.open( IO_ReadWrite ) ) { + dccSock_->offset = 0; + dccSock_->file_fd = localFile_.handle(); + } + break; + + // cancel + default: + closeDCC(); + deleteLater(); + return; + break; + } + if ( localFile_.handle() < 1 ) { + closeDCC(); + deleteLater(); + return; + } + } + else { + // overwrite by default + if ( localFile_.open( IO_ReadWrite ) == FALSE ) { + transfer->slotError ( KIO::ERR_COULD_NOT_WRITE, fileName ); + closeDCC(); + deleteLater (); + return; + } + dccSock_->offset = 0; + dccSock_->file_fd = localFile_.handle(); + } + + connect ( transfer, SIGNAL( result( KIO::Job * ) ), this, SLOT( slotTransferResult() ) ); + + // reenable notifiers + enableNotifiers( dccSock_->check ); + +} + +void +GaduDCCTransaction::slotTransferResult() +{ + if ( transfer_->error() == KIO::ERR_USER_CANCELED ) { + closeDCC(); + deleteLater(); + } +} + +void +GaduDCCTransaction::slotTransferRefused ( const Kopete::FileTransferInfo& transfer ) +{ + if ( (long)transfer.transferId () != transferId_ ) + return; + closeDCC(); + deleteLater(); +} + +void +GaduDCCTransaction::askIncommingTransfer() +{ + + transferId_ = Kopete::TransferManager::transferManager()->askIncomingTransfer ( contact, + QString( (const char*)dccSock_->file_info.filename ), dccSock_->file_info.size ); + +} + +void +GaduDCCTransaction::watcher() { + + gg_event* dccEvent; + GaduAccount* account; + + disableNotifiers(); + + dccEvent = gg_dcc_watch_fd( dccSock_ ); + if ( ! dccEvent ) { + // connection is fucked + closeDCC(); + return; + } + switch ( dccEvent->type ) { + case GG_EVENT_DCC_CLIENT_ACCEPT: + kdDebug(14100) << " GG_EVENT_DCC_CLIENT_ACCEPT " << endl; + // check dccsock->peer_uin, if unknown, fuck it; + + // is it for us ? + account = gaduDCC_->account( dccSock_->uin ); + if ( !account ) { + kdDebug( 14100 ) << " this dcc transaction is for uin " << dccSock_->uin << ", which is not quite for me... closing" << endl; + // unknown 'to' ?, we're off + gg_free_event( dccEvent ); + closeDCC(); + deleteLater(); + return; + } + + if ( !peer ) { + contact = static_cast (account->contacts()[ QString::number( dccSock_->peer_uin ) ]); + } + else { + contact = static_cast (account->contacts()[ QString::number( peer ) ]); + } + + if ( contact == NULL ) { + // refusing, contact on the list + kdDebug(14100) << " dcc connection from " << dccSock_->peer_uin << " refused, UIN not on the list " <peer_uin << endl; + } + + break; + case GG_EVENT_DCC_CALLBACK: + kdDebug(14100) << "GG_DCC_EVENT_CALLBACK" << endl; + break; + case GG_EVENT_NONE: + kdDebug(14100) << " GG_EVENT_NONE" << endl; + // update gui with progress + if ( transfer_ ) { + transfer_->slotProcessed( dccSock_->offset ); + } + break; + + case GG_EVENT_DCC_NEED_FILE_ACK: + kdDebug(14100) << " GG_EVENT_DCC_NEED_FILE_ACK " << endl; + gg_free_event( dccEvent ); + askIncommingTransfer(); + return; + break; + case GG_EVENT_DCC_NEED_FILE_INFO: + if (gaduDCC_->requests.contains(dccSock_->peer_uin)) { + QString filePath = gaduDCC_->requests[dccSock_->peer_uin]; + kdDebug() << "Callback request found. Sending " << filePath << endl; + gaduDCC_->requests.remove(dccSock_->peer_uin); + gg_dcc_fill_file_info(dccSock_,filePath.ascii()); + transfer_ = Kopete::TransferManager::transferManager()->addTransfer ( contact, + filePath, dccSock_->file_info.size, contact->metaContact()->displayName(), Kopete::FileTransferInfo::Outgoing ); + } else { + gg_free_event( dccEvent ); + closeDCC(); + deleteLater(); + return; + } + break; + + case GG_EVENT_DCC_ERROR: + kdDebug(14100) << " GG_EVENT_DCC_ERROR :" << dccEvent->event.dcc_error << endl; + if ( transfer_ ) { + switch( dccEvent->event.dcc_error ) { + + case GG_ERROR_DCC_REFUSED: + transfer_->slotError( KIO::ERR_SLAVE_DEFINED, i18n( "Connection to peer was refused; it possibly does not listen for incoming connections." ) ); + break; + + case GG_ERROR_DCC_EOF: + transfer_->slotError( KIO::ERR_SLAVE_DEFINED, i18n( "File transfer transaction was not agreed by peer." ) ); + break; + + case GG_ERROR_DCC_HANDSHAKE: + transfer_->slotError( KIO::ERR_SLAVE_DEFINED, i18n( "File-transfer handshake failure." ) ); + break; + case GG_ERROR_DCC_FILE: + transfer_->slotError( KIO::ERR_SLAVE_DEFINED, i18n( "File transfer had problems with the file." ) ); + break; + case GG_ERROR_DCC_NET: + transfer_->slotError( KIO::ERR_SLAVE_DEFINED, i18n( "There was network error during file transfer." ) ); + break; + default: + transfer_->slotError( KIO::ERR_SLAVE_DEFINED, i18n( "Unknown File-Transfer error." ) ); + break; + } + } + gg_free_event( dccEvent ); + closeDCC(); + deleteLater(); + return; + + case GG_EVENT_DCC_DONE: + if ( transfer_ ) { + transfer_->slotComplete(); + } + closeDCC(); + deleteLater(); + return; + + default: + kdDebug(14100) << "unknown/unhandled DCC EVENT: " << dccEvent->type << endl; + break; + } + + if ( dccEvent ) { + gg_free_event( dccEvent ); + } + + enableNotifiers( dccSock_->check ); +} + +#include "gadudcctransaction.moc" diff --git a/kopete/protocols/gadu/gadudcctransaction.h b/kopete/protocols/gadu/gadudcctransaction.h new file mode 100644 index 00000000..4c2edb58 --- /dev/null +++ b/kopete/protocols/gadu/gadudcctransaction.h @@ -0,0 +1,87 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2004 Grzegorz Jaskiewicz +// +// gadudcctransaction.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + + +#ifndef GADUDCCTRANS_H +#define GADUDCCTRANS_H + +#include +#include + +class QSocketNotifier; +class gg_dcc; +class GaduAccount; +class GaduContact; +namespace Kopete { class Transfer; } +namespace Kopete { class FileTransferInfo; } +class GaduDCC; + +class GaduDCCTransaction: QObject { + Q_OBJECT +public: + GaduDCCTransaction( GaduDCC*, const char* name = NULL ); + ~GaduDCCTransaction(); + + bool setupIncoming( const unsigned int, GaduContact* ); + bool setupIncoming( gg_dcc* ); + bool setupOutgoing( GaduContact*, QString& ); + unsigned int recvUIN(); + unsigned int peerUIN(); + +public slots: + +signals: + +protected: + +protected slots: + +private slots: + void watcher(); + void slotIncomingTransferAccepted ( Kopete::Transfer*, const QString& ); + void slotTransferRefused ( const Kopete::FileTransferInfo& ); + void slotTransferResult(); + +private: + void enableNotifiers( int ); + void disableNotifiers(); + void checkDescriptor(); + void closeDCC(); + void destroyNotifiers(); + void createNotifiers( bool ); + void askIncommingTransfer(); + + gg_dcc* dccSock_; + + QSocketNotifier* read_; + QSocketNotifier* write_; + + GaduContact* contact; + + Kopete::Transfer* transfer_; + long transferId_; + QFile localFile_; + int peer; + unsigned int incoming; + GaduDCC* gaduDCC_; +}; + +#endif diff --git a/kopete/protocols/gadu/gadueditaccount.cpp b/kopete/protocols/gadu/gadueditaccount.cpp new file mode 100644 index 00000000..250ae4cd --- /dev/null +++ b/kopete/protocols/gadu/gadueditaccount.cpp @@ -0,0 +1,263 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003-2004 Grzegorz Jaskiewicz +// +// gadueditaccount.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include "gadueditaccount.h" +#include "gaduaccount.h" +#include "gaduprotocol.h" +#include "gadusession.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "kopetepasswordwidget.h" + +GaduEditAccount::GaduEditAccount( GaduProtocol* proto, Kopete::Account* ident, QWidget* parent, const char* name ) +: GaduAccountEditUI( parent, name ), KopeteEditAccountWidget( ident ), protocol_( proto ), rcmd( 0 ) +{ + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + isSsl = true; +#else + isSsl = false; +#endif + + useTls_->setDisabled( !isSsl ); + + if ( account() == NULL ) { + useTls_->setCurrentItem( GaduAccount::TLS_no ); + registerNew->setEnabled( true ); + account_ = NULL; + } + else { + account_ = static_cast(ident); + + registerNew->setDisabled( true ); + loginEdit_->setDisabled( true ); + loginEdit_->setText( account_->accountId() ); + + passwordWidget_->load( &account_->password() ); + + QString nick = account()->myself()->property( + Kopete::Global::Properties::self()->nickName() ).value().toString(); + if ( nick.isEmpty() ) { + nick = account_->myself()->contactId(); + } + + nickName->setText( nick ); + + autoLoginCheck_->setChecked( account_->excludeConnect() ); + dccCheck_->setChecked( account_->dccEnabled() ); + useTls_->setCurrentItem( isSsl ? ( account_->useTls() ) : 2 ); + ignoreCheck_->setChecked( account_->ignoreAnons() ); + + connect( account(), SIGNAL( pubDirSearchResult( const SearchResult&, unsigned int ) ), + SLOT( slotSearchResult( const SearchResult&, unsigned int ) ) ); + connectLabel->setText( i18n( "personal information being fetched from server", + "

    Fetching from server

    " ) ); + seqNr = account_->getPersonalInformation(); + } + + connect( registerNew, SIGNAL( clicked( ) ), SLOT( registerNewAccount( ) ) ); + + QWidget::setTabOrder( loginEdit_, passwordWidget_->mRemembered ); + QWidget::setTabOrder( passwordWidget_->mRemembered, passwordWidget_->mPassword ); + QWidget::setTabOrder( passwordWidget_->mPassword, autoLoginCheck_ ); +} + +void +GaduEditAccount::publishUserInfo() +{ + ResLine sr; + + enableUserInfo( false ); + + sr.firstname = uiName->text(); + sr.surname = uiSurname->text(); + sr.nickname = nickName->text(); + sr.age = uiYOB->text(); + sr.city = uiCity->text(); + sr.meiden = uiMeiden->text(); + sr.orgin = uiOrgin->text(); + + kdDebug(14100) << uiGender->currentItem() << " gender " << endl; + if ( uiGender->currentItem() == 1 ) { + kdDebug(14100) << "so you become female now" << endl; + sr.gender = QString( GG_PUBDIR50_GENDER_SET_FEMALE ); + } + if ( uiGender->currentItem() == 2 ) { + kdDebug(14100) << "so you become male now" << endl; + sr.gender = QString( GG_PUBDIR50_GENDER_SET_MALE ); + } + + if ( account_ ) { + account_->publishPersonalInformation( sr ); + } +} + +void +GaduEditAccount::slotSearchResult( const SearchResult& result, unsigned int seq ) +{ + if ( !( seq != 0 && seqNr != 0 && seq == seqNr ) ) { + return; + } + + connectLabel->setText( " " ); + + uiName->setText( result[0].firstname ); + uiSurname->setText( result[0].surname ); + nickName->setText( result[0].nickname ); + uiYOB->setText( result[0].age ); + uiCity->setText( result[0].city ); + + kdDebug( 14100 ) << "gender found: " << result[0].gender << endl; + if ( result[0].gender == QString( GG_PUBDIR50_GENDER_SET_FEMALE ) ) { + uiGender->setCurrentItem( 1 ); + kdDebug(14100) << "looks like female" << endl; + } + else { + if ( result[0].gender == QString( GG_PUBDIR50_GENDER_SET_MALE ) ) { + uiGender->setCurrentItem( 2 ); + kdDebug( 14100 ) <<" looks like male" << endl; + } + } + + uiMeiden->setText( result[0].meiden ); + uiOrgin->setText( result[0].orgin ); + + enableUserInfo( true ); + + disconnect( SLOT( slotSearchResult( const SearchResult&, unsigned int ) ) ); +} + +void +GaduEditAccount::enableUserInfo( bool e ) +{ + uiName->setEnabled( e ); + uiSurname->setEnabled( e ); + uiYOB->setEnabled( e ); + uiCity->setEnabled( e ); + uiGender->setEnabled( e ); + uiMeiden->setEnabled( e ); + uiOrgin->setEnabled( e ); + +// connectLabel->setEnabled( !e ); +} + +void +GaduEditAccount::registerNewAccount() +{ + registerNew->setDisabled( true ); + regDialog = new GaduRegisterAccount( NULL , "Register account dialog" ); + connect( regDialog, SIGNAL( registeredNumber( unsigned int, QString ) ), SLOT( newUin( unsigned int, QString ) ) ); + if ( regDialog->exec() != QDialog::Accepted ) { + loginEdit_->setText( "" ); + return; + } + registerNew->setDisabled( false ); +} + +void +GaduEditAccount::registrationFailed() +{ + KMessageBox::sorry( this, i18n( "Registration FAILED." ), i18n( "Gadu-Gadu" ) ); +} + +void +GaduEditAccount::newUin( unsigned int uin, QString password ) +{ + if ( uin ) { + loginEdit_->setText( QString::number( uin ) ); + passwordWidget_->setPassword( password ); + } + else { + // registration failed, enable button again + registerNew->setDisabled( false ); + } +} + +bool +GaduEditAccount::validateData() +{ + + if ( loginEdit_->text().isEmpty() ) { + KMessageBox::sorry( this, i18n( "Enter UIN please." ), i18n( "Gadu-Gadu" ) ); + return false; + } + + if ( loginEdit_->text().toInt() < 0 || loginEdit_->text().toInt() == 0 ) { + KMessageBox::sorry( this, i18n( "UIN should be a positive number." ), i18n( "Gadu-Gadu" ) ); + return false; + } + + if ( !passwordWidget_->validate() ) { + KMessageBox::sorry( this, i18n( "Enter password please." ), i18n( "Gadu-Gadu" ) ); + return false; + } + + return true; +} + +Kopete::Account* +GaduEditAccount::apply() +{ + publishUserInfo(); + + if ( account() == NULL ) { + setAccount( new GaduAccount( protocol_, loginEdit_->text() ) ); + account_ = static_cast( account() ); + } + + account_->setExcludeConnect( autoLoginCheck_->isChecked() ); + + passwordWidget_->save( &account_->password() ); + + account_->myself()->setProperty( Kopete::Global::Properties::self()->nickName(), nickName->text() ); + + // this is changed only here, so i won't add any proper handling now + account_->configGroup()->writeEntry( QString::fromAscii( "nickName" ), nickName->text() ); + + account_->setExcludeConnect( autoLoginCheck_->isChecked() ); + account_->setUseTls( (GaduAccount::tlsConnection) useTls_->currentItem() ); + account_->setIgnoreAnons( ignoreCheck_->isChecked() ); + + if ( account_->setDcc( dccCheck_->isChecked() ) == false ) { + KMessageBox::sorry( this, i18n( "Starting DCC listening socket failed; dcc is not working now." ), i18n( "Gadu-Gadu" ) ); + } + + return account(); +} + +#include "gadueditaccount.moc" + diff --git a/kopete/protocols/gadu/gadueditaccount.h b/kopete/protocols/gadu/gadueditaccount.h new file mode 100644 index 00000000..87775f2a --- /dev/null +++ b/kopete/protocols/gadu/gadueditaccount.h @@ -0,0 +1,63 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// +// gadueditaccount.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef GADUEDITACCOUNT_H +#define GADUEDITACCOUNT_H + +#include "gadueditaccountui.h" +#include "editaccountwidget.h" +#include "gaduregisteraccount.h" +#include "gadusession.h" + +class GaduAccount; +class GaduProtocol; + +namespace Kopete { class Account; } + +class GaduEditAccount : public GaduAccountEditUI, public KopeteEditAccountWidget +{ + Q_OBJECT + +public: + GaduEditAccount( GaduProtocol*, Kopete::Account*, QWidget* parent = 0, const char* name = 0 ); + virtual bool validateData(); + Kopete::Account* apply(); + +private slots: + void registerNewAccount(); + void newUin( unsigned int, QString ); + void registrationFailed(); + void slotSearchResult( const SearchResult&, unsigned int ); + +private: + void enableUserInfo( bool ); + void publishUserInfo(); + + GaduProtocol* protocol_; + bool reg_in_progress; + bool isSsl; + RegisterCommand* rcmd; + GaduRegisterAccount* regDialog; + GaduAccount* account_; + unsigned int seqNr; +}; + +#endif diff --git a/kopete/protocols/gadu/gadueditcontact.cpp b/kopete/protocols/gadu/gadueditcontact.cpp new file mode 100644 index 00000000..3844e691 --- /dev/null +++ b/kopete/protocols/gadu/gadueditcontact.cpp @@ -0,0 +1,203 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// +// gadueditcontact.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include "gaduaccount.h" +#include "gaducontact.h" +#include "gadueditcontact.h" +#include "kopeteonlinestatus.h" + +#include "gaducontactlist.h" +#include "gaduadd.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +// FIXME: this and gaduadcontactpage should have one base class, with some code duplicated in both. + +GaduEditContact::GaduEditContact( GaduAccount* account, GaduContact* contact, + QWidget* parent, const char* name ) +: KDialogBase( parent, name, true, i18n( "Edit Contact's Properties" ), + KDialogBase::Ok | KDialogBase::Cancel, + KDialogBase::Ok, true ), account_( account ), contact_( contact ) +{ + if ( contact && account ) { + cl_ = contact->contactDetails(); + } + else { + return; + } + + init(); + fillGroups(); + fillIn(); +} + +GaduEditContact::GaduEditContact( GaduAccount* account, GaduContactsList::ContactLine* clin, + QWidget* parent , const char* name ) +: KDialogBase( parent, name, true, i18n( "Edit Contact's Properties" ), + KDialogBase::Ok | KDialogBase::Cancel, + KDialogBase::Ok, true ), account_( account ), contact_( NULL ) +{ + + if ( !account ) { + return; + } + cl_ = clin; + init(); + fillGroups(); + fillIn(); +} + +void +GaduEditContact::fillGroups() +{ + Kopete::Group *g, *cg; + QPtrList cgl; + QPtrList gl; + + if ( contact_ ) { + cgl = contact_->metaContact()->groups(); + } + + gl = Kopete::ContactList::self()->groups(); + + for( g = gl.first(); g; g = gl.next() ) { + if ( g->type() == Kopete::Group::Temporary ) { + continue; + } + QCheckListItem* item = new QCheckListItem( ui_->groups, g->displayName(), QCheckListItem::CheckBox ); + // FIXME: optimize this O(2) search + for( cg = cgl.first(); cg; cg = cgl.next() ) { + if ( cg->groupId() == g->groupId() ) { + item->setOn( TRUE ); + break; + } + } + kdDebug(14100) << g->displayName() << " " << g->groupId() << endl; + } +} + +void +GaduEditContact::init() +{ + ui_ = new GaduAddUI( this ); + setMainWidget( ui_ ); + ui_->addEdit_->setValidChars( "1234567890" ); + + // fill values from cl into proper fields on widget + + show(); + connect( this, SIGNAL( okClicked() ), SLOT( slotApply() ) ); + connect( ui_->groups, SIGNAL( clicked( QListViewItem * ) ), SLOT( listClicked( QListViewItem * ) ) ); +} + +void +GaduEditContact::listClicked( QListViewItem* /*item*/ ) +{ + +} + +void +GaduEditContact::fillIn() +{ +// grey it out, it shouldn't be editable + ui_->addEdit_->setReadOnly( true ); + ui_->addEdit_->setText( cl_->uin ); + + ui_->fornameEdit_->setText( cl_->firstname ); + ui_->snameEdit_->setText( cl_->surname ); + ui_->nickEdit_->setText( cl_->nickname ); + ui_->emailEdit_->setText( cl_->email ); + ui_->telephoneEdit_->setText( cl_->phonenr ); +// ui_->notAFriend_; + +} + +void +GaduEditContact::slotApply() +{ + QPtrList gl; + Kopete::Group* group; + + cl_->firstname = ui_->fornameEdit_->text().stripWhiteSpace(); + cl_->surname = ui_->snameEdit_->text().stripWhiteSpace(); + cl_->nickname = ui_->nickEdit_->text().stripWhiteSpace(); + cl_->email = ui_->emailEdit_->text().stripWhiteSpace(); + cl_->phonenr = ui_->telephoneEdit_->text().stripWhiteSpace(); + + if ( contact_ == NULL ) { + // contact doesn't exists yet, create it and set all the details + bool s = account_->addContact( cl_->uin, GaduContact::findBestContactName( cl_ ), 0L, Kopete::Account::DontChangeKABC); + if ( s == false ) { + kdDebug(14100) << "There was a problem adding UIN "<< cl_->uin << "to users list" << endl; + return; + } + contact_ = static_cast( account_->contacts()[ cl_->uin ] ); + if ( contact_ == NULL ) { + kdDebug(14100) << "oops, no Kopete::Contact in contacts()[] for some reason, for \"" << cl_->uin << "\"" << endl; + return; + } + } + + contact_->setContactDetails( cl_ ); + + gl = Kopete::ContactList::self()->groups(); + for ( QListViewItemIterator it( ui_->groups ); it.current(); ++it ) { + QCheckListItem *check = dynamic_cast( it.current() ); + + if ( !check ) { + continue; + } + + if ( check->isOn() ) { + for( group = gl.first(); group; group = gl.next() ) { + if ( group->displayName() == check->text() ) { + contact_->metaContact()->addToGroup( group ); + } + } + } + else { + // check metacontact's in the group, and if so, remove it from + for( group = gl.first(); group; group = gl.next() ) { + if ( group->displayName() == check->text() ) { + contact_->metaContact()->removeFromGroup( group ); + } + } + } + } + + if( contact_->metaContact()->groups().isEmpty() == TRUE ) + contact_->metaContact()->addToGroup( Kopete::Group::topLevel() ); +} +#include "gadueditcontact.moc" diff --git a/kopete/protocols/gadu/gadueditcontact.h b/kopete/protocols/gadu/gadueditcontact.h new file mode 100644 index 00000000..6b209b79 --- /dev/null +++ b/kopete/protocols/gadu/gadueditcontact.h @@ -0,0 +1,60 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// Copyright (C) 2002-2003 Zack Rusin +// +// gadueditcontact.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef GADUEDITCONTACT_H +#define GADUEDITCONTACT_H + +#include + +class GaduAccount; +class GaduAddUI; +class QLabel; +class QString; +class QWidget; +class GaduContact; +class GaduContactsList::ContactLine; +class QListViewItem; + +class GaduEditContact : public KDialogBase +{ + Q_OBJECT + +public: + GaduEditContact( GaduAccount*, GaduContact*, + QWidget* parent = 0, const char* name = 0 ); + GaduEditContact( GaduAccount*, GaduContactsList::ContactLine*, + QWidget* parent = 0, const char* name = 0 ); +protected slots: + void slotApply(); + void listClicked( QListViewItem* ); +private: + + void init(); + void fillIn(); + void fillGroups(); + GaduAccount* account_; + GaduContact* contact_; + GaduAddUI* ui_; + GaduContactsList::ContactLine* cl_; +}; + +#endif diff --git a/kopete/protocols/gadu/gaduprotocol.cpp b/kopete/protocols/gadu/gaduprotocol.cpp new file mode 100644 index 00000000..cab6bfe0 --- /dev/null +++ b/kopete/protocols/gadu/gaduprotocol.cpp @@ -0,0 +1,243 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// Copyright (C) 2002-2003 Zack Rusin +// +// gaduprotocol.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include +#include +#include + +#include + +#include "gaduaccount.h" +#include "gaducontact.h" +#include "gaduprotocol.h" + +#include "gadueditaccount.h" +#include "gaduaddcontactpage.h" + +#include "kopeteaccountmanager.h" +#include "kopeteaccount.h" +#include "kopetemetacontact.h" +#include "kopeteglobal.h" +#include "kopeteonlinestatusmanager.h" + +typedef KGenericFactory GaduProtocolFactory; + +K_EXPORT_COMPONENT_FACTORY( kopete_gadu, KGenericFactory( "kopete_gadu" ) ) + +GaduProtocol* GaduProtocol::protocolStatic_ = 0L; + +GaduProtocol::GaduProtocol( QObject* parent, const char* name, const QStringList& ) +:Kopete::Protocol( GaduProtocolFactory::instance(), parent, name ), + propFirstName(Kopete::Global::Properties::self()->firstName()), + propLastName(Kopete::Global::Properties::self()->lastName()), + propEmail(Kopete::Global::Properties::self()->emailAddress()), + propAwayMessage(Kopete::Global::Properties::self()->awayMessage()), + propPhoneNr(Kopete::Global::Properties::self()->privatePhone()), + defaultAccount_( 0 ), + gaduStatusBlocked_( Kopete::OnlineStatus::Away, GG_STATUS_BLOCKED, this, GG_STATUS_BLOCKED, + "gg_ignored", i18n( "Blocked" ) ), + gaduStatusOffline_( Kopete::OnlineStatus::Offline, GG_STATUS_NOT_AVAIL, this, GG_STATUS_NOT_AVAIL, + "gg_offline", i18n( "Offline" ) , i18n( "O&ffline" ) , Kopete::OnlineStatusManager::Offline ), + gaduStatusOfflineDescr_( Kopete::OnlineStatus::Offline, GG_STATUS_NOT_AVAIL_DESCR, this, GG_STATUS_NOT_AVAIL_DESCR, + QStringList::split( '|', "contact_away_overlay|gg_description_overlay" ), i18n( "Offline" ), i18n( "A&way" ) , Kopete::OnlineStatusManager::Offline ), + gaduStatusBusy_(Kopete::OnlineStatus::Away, GG_STATUS_BUSY, this, GG_STATUS_BUSY, + "contact_away_overlay", i18n( "Busy" ) , i18n( "B&usy" ) , Kopete::OnlineStatusManager::Busy ), + gaduStatusBusyDescr_(Kopete::OnlineStatus::Away, GG_STATUS_BUSY_DESCR, this, GG_STATUS_BUSY_DESCR, + QStringList::split( '|', "contact_away_overlay|gg_description_overlay" ), i18n( "Busy" ) , i18n( "B&usy" ) , Kopete::OnlineStatusManager::Idle ), + gaduStatusInvisible_( Kopete::OnlineStatus::Invisible, GG_STATUS_INVISIBLE, this, GG_STATUS_INVISIBLE, + "contact_invisible_overlay", i18n( "Invisible" ) , i18n( "I&nvisible" ) , Kopete::OnlineStatusManager::Invisible), + gaduStatusInvisibleDescr_(Kopete::OnlineStatus::Invisible, GG_STATUS_INVISIBLE_DESCR, this, GG_STATUS_INVISIBLE_DESCR, + QStringList::split( '|', "contact_invisible_overlay|gg_description_overlay" ), i18n( "Invisible" ) , i18n( "I&nvisible" )), + gaduStatusAvail_(Kopete::OnlineStatus::Online, GG_STATUS_AVAIL, this, GG_STATUS_AVAIL, + QString::null, i18n( "Online" ) , i18n( "&Online" ) , Kopete::OnlineStatusManager::Online ), + gaduStatusAvailDescr_(Kopete::OnlineStatus::Online, GG_STATUS_AVAIL_DESCR, this, GG_STATUS_AVAIL_DESCR, + "gg_description_overlay", i18n( "Online" ) , i18n( "&Online" )), + gaduConnecting_(Kopete::OnlineStatus::Offline, GG_STATUS_CONNECTING, this, GG_STATUS_CONNECTING, + "gg_con", i18n( "Connecting" ) ) +{ + if ( protocolStatic_ ) { + kdDebug(14100)<<"####"<<"GaduProtocol already initialized"<( account ), parent ); +} + +void +GaduProtocol::settingsChanged() +{ +} + +Kopete::Contact * +GaduProtocol::deserializeContact( Kopete::MetaContact* metaContact, + const QMap& serializedData, + const QMap& /* addressBookData */ ) +{ + + const QString aid = serializedData[ "accountId" ]; + const QString cid = serializedData[ "contactId" ]; + const QString dn = serializedData[ "displayName" ]; + + QDict daccounts = Kopete::AccountManager::self()->accounts( this ); + + Kopete::Account* account = daccounts[ aid ]; + if (!account) { + account = createNewAccount(aid); + } + + GaduAccount* gaccount = static_cast( account ); + + GaduContact* contact = new GaduContact( cid.toUInt(), dn, account, metaContact ); + + contact->setParentIdentity( aid ); + gaccount->addNotify( cid.toUInt() ); + + contact->setProperty( propEmail, serializedData["email"] ); + contact->setProperty( propFirstName, serializedData["FirstName"] ); + contact->setProperty( propLastName, serializedData["SecondName"] ); + contact->setProperty( propPhoneNr, serializedData["telephone"] ); + contact->setIgnored(serializedData["ignored"] == "true"); + return contact; +} + +uint +GaduProtocol::statusToWithDescription( Kopete::OnlineStatus status ) +{ + + if ( status == gaduStatusOffline_ || status == gaduStatusOfflineDescr_ ) { + return GG_STATUS_NOT_AVAIL_DESCR; + } + + if ( status == gaduStatusBusyDescr_ || status == gaduStatusBusy_ ){ + return GG_STATUS_BUSY_DESCR; + } + + if ( status == gaduStatusInvisibleDescr_ || status == gaduStatusInvisible_ ){ + return GG_STATUS_INVISIBLE_DESCR; + } + + return GG_STATUS_AVAIL_DESCR; +} + +uint +GaduProtocol::statusToWithoutDescription( Kopete::OnlineStatus status ) +{ + if ( status == gaduStatusOffline_ || status == gaduStatusOfflineDescr_ ) { + return GG_STATUS_NOT_AVAIL; + } + + if ( status == gaduStatusBusyDescr_ || status == gaduStatusBusy_ ){ + return GG_STATUS_BUSY; + } + + if ( status == gaduStatusInvisibleDescr_ || status == gaduStatusInvisible_ ){ + return GG_STATUS_INVISIBLE; + } + + return GG_STATUS_AVAIL; +} + +bool +GaduProtocol::statusWithDescription( uint status ) +{ + switch( status ) { + case GG_STATUS_NOT_AVAIL: + case GG_STATUS_BUSY: + case GG_STATUS_INVISIBLE: + case GG_STATUS_AVAIL: + case GG_STATUS_CONNECTING: + case GG_STATUS_BLOCKED: + return false; + case GG_STATUS_INVISIBLE_DESCR: + case GG_STATUS_NOT_AVAIL_DESCR: + case GG_STATUS_BUSY_DESCR: + case GG_STATUS_AVAIL_DESCR: + return true; + } + return false; +} + +Kopete::OnlineStatus +GaduProtocol::convertStatus( uint status ) const +{ + switch( status ) { + case GG_STATUS_NOT_AVAIL: + return gaduStatusOffline_; + case GG_STATUS_NOT_AVAIL_DESCR: + return gaduStatusOfflineDescr_; + case GG_STATUS_BUSY: + return gaduStatusBusy_; + case GG_STATUS_BUSY_DESCR: + return gaduStatusBusyDescr_; + case GG_STATUS_INVISIBLE: + return gaduStatusInvisible_; + case GG_STATUS_INVISIBLE_DESCR: + return gaduStatusInvisibleDescr_; + case GG_STATUS_AVAIL: + return gaduStatusAvail_; + case GG_STATUS_AVAIL_DESCR: + return gaduStatusAvailDescr_; + case GG_STATUS_CONNECTING: + return gaduConnecting_; + case GG_STATUS_BLOCKED: + return gaduStatusBlocked_; + default: + return gaduStatusOffline_; + } +} + +Kopete::Account* +GaduProtocol::createNewAccount( const QString& accountId ) +{ + defaultAccount_ = new GaduAccount( this, accountId ); + return defaultAccount_ ; +} + +KopeteEditAccountWidget* +GaduProtocol::createEditAccountWidget( Kopete::Account* account, QWidget* parent ) +{ + return( new GaduEditAccount( this, account, parent ) ); +} + +#include "gaduprotocol.moc" diff --git a/kopete/protocols/gadu/gaduprotocol.h b/kopete/protocols/gadu/gaduprotocol.h new file mode 100644 index 00000000..079763c4 --- /dev/null +++ b/kopete/protocols/gadu/gaduprotocol.h @@ -0,0 +1,108 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// Copyright (C) 2002-2003 Zack Rusin +// +// gaduprotocol.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef GADUPROTOCOL_H +#define GADUPROTOCOL_H + +#include + +#include "kopeteprotocol.h" +#include "kopeteonlinestatus.h" +#include "kopetecontactproperty.h" + +#include "gaducommands.h" + +class KAction; +class KActionMenu; + +class QWidget; +class QString; + +namespace Kopete { class Contact; } +namespace Kopete { class MetaContact; } + +class GaduSession; +class GaduContact; +class GaduAccount; +class GaduPreferences; + +#define GG_STATUS_CONNECTING 0x0100 + +class GaduProtocol : public Kopete::Protocol +{ + Q_OBJECT + +public: + GaduProtocol( QObject* parent, const char* name, const QStringList& str); + ~GaduProtocol(); + + static GaduProtocol *protocol(); + + // Plugin reimplementation + // { + AddContactPage* createAddContactWidget( QWidget* parent, Kopete::Account* account ); + Kopete::Account* createNewAccount( const QString& accountId ); + KopeteEditAccountWidget *createEditAccountWidget( Kopete::Account* account, QWidget* parent ); + bool canSendOffline() const { return true; } + + virtual Kopete::Contact *deserializeContact( Kopete::MetaContact* metaContact, + const QMap& serializedData, + const QMap& addressBookData ); + // } + //!Plugin reimplementation + + Kopete::OnlineStatus convertStatus( uint ) const; + bool statusWithDescription( uint status ); + + uint statusToWithDescription( Kopete::OnlineStatus status ); + uint statusToWithoutDescription( Kopete::OnlineStatus status ); + + const Kopete::ContactPropertyTmpl propFirstName; + const Kopete::ContactPropertyTmpl propLastName; + const Kopete::ContactPropertyTmpl propEmail; + const Kopete::ContactPropertyTmpl propAwayMessage; + const Kopete::ContactPropertyTmpl propPhoneNr; + //const Kopete::ContactPropertyTmpl propIgnore; + +private slots: + void settingsChanged(); + +private: + static GaduProtocol* protocolStatic_; + GaduAccount* defaultAccount_; + //GaduPreferences* prefs_; + + const Kopete::OnlineStatus gaduStatusBlocked_; + const Kopete::OnlineStatus gaduStatusOffline_; + const Kopete::OnlineStatus gaduStatusOfflineDescr_; + const Kopete::OnlineStatus gaduStatusBusy_; + const Kopete::OnlineStatus gaduStatusBusyDescr_; + const Kopete::OnlineStatus gaduStatusInvisible_; + const Kopete::OnlineStatus gaduStatusInvisibleDescr_; + const Kopete::OnlineStatus gaduStatusAvail_; + const Kopete::OnlineStatus gaduStatusAvailDescr_; + const Kopete::OnlineStatus gaduConnecting_; + +}; + + +#endif diff --git a/kopete/protocols/gadu/gadupubdir.cpp b/kopete/protocols/gadu/gadupubdir.cpp new file mode 100644 index 00000000..8b722894 --- /dev/null +++ b/kopete/protocols/gadu/gadupubdir.cpp @@ -0,0 +1,344 @@ +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// +// gadupubdir.cpp +// Gadu-Gadu Public directory contains people data, using it you can search friends +// different criteria +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// + +#include "gadupubdir.h" +#include "gadueditcontact.h" +#include "gaducontactlist.h" +#include "gaduaccount.h" +#include "gaduprotocol.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +GaduPublicDir::GaduPublicDir( GaduAccount* account, QWidget* parent, const char* name ) +: KDialogBase( parent, name, false, QString::null, User1|User2|User3|Cancel, User2 ) +{ + mAccount = account; + createWidget(); + initConnections(); + + show(); +} + +GaduPublicDir::GaduPublicDir( GaduAccount* account, int searchFor, QWidget* parent, const char* name ) +: KDialogBase( parent, name, false, QString::null, User1|User2|User3|Cancel, User2 ) +{ + ResLine rs; + + mAccount = account; + createWidget(); + initConnections(); + + kdDebug( 14100 ) << "search for Uin: " << searchFor << endl; + + mMainWidget->listFound->clear(); + show(); + + if ( searchFor == 0 ) { + return; + } + + mMainWidget->pubsearch->raiseWidget( 1 ); + mMainWidget->radioByUin->setChecked( true ); + + setButtonText( User2, i18n( "Search &More..." ) ); + showButton( User3, true ); + showButton( User1, true ); + enableButton( User3, false ); + enableButton( User2, false ); + + // now it is time to switch to Right Page(tm) + rs.uin = searchFor; + + fName = fSurname = fNick = fCity = QString::null; + fUin = searchFor; + fGender = fAgeFrom = fAgeTo = 0; + fOnlyOnline = false; + + mAccount->pubDirSearch( rs, fAgeFrom, fAgeTo, fOnlyOnline ); + +} + +void +GaduPublicDir::createWidget() +{ + setCaption( i18n( "Gadu-Gadu Public Directory" ) ); + + mMainWidget = new GaduPublicDirectory( this ); + setMainWidget( mMainWidget ); + + mMainWidget->UIN->setValidChars( "1234567890" ); + + setButtonText( User1, i18n( "&New Search" ) ); + setButtonText( User2, i18n( "S&earch" ) ); + setButtonText( User3, i18n( "&Add User..." ) ); + setButtonText( Cancel, i18n( "&Close" ) ); + + showButton( User1, false ); + showButton( User3, false ); + enableButton( User2, false ); + + mMainWidget->radioByData->setChecked( true ); + + mAccount->pubDirSearchClose(); + +} + +void +GaduPublicDir::slotAddContact() +{ + GaduContactsList::ContactLine* cl = new GaduContactsList::ContactLine; + QListViewItem* item = mMainWidget->listFound->currentItem(); + + cl->ignored = false; + cl->firstname = item->text( 1 ); + cl->uin = item->text( 5 ); + cl->nickname = item->text( 2 ); + + cl->surname = fSurname; + +// GaduEditContact *ed = + new GaduEditContact( mAccount, cl, this ); +} + +void +GaduPublicDir::slotListSelected( ) +{ + QListViewItem* item = mMainWidget->listFound->currentItem(); + if ( item ) { + enableButton( User3, true ); + } + else { + enableButton( User3, false ); + } +} + +void +GaduPublicDir::initConnections() +{ + connect( this, SIGNAL( user2Clicked() ), SLOT( slotSearch() ) ); + connect( this, SIGNAL( user1Clicked() ), SLOT( slotNewSearch() ) ); + connect( this, SIGNAL( user3Clicked() ), SLOT( slotAddContact() ) ); + + connect( mAccount, SIGNAL( pubDirSearchResult( const SearchResult&, unsigned int ) ), + SLOT( slotSearchResult( const SearchResult&, unsigned int ) ) ); + + connect( mMainWidget->nameS, SIGNAL( textChanged( const QString &) ), SLOT( inputChanged( const QString & ) ) ); + connect( mMainWidget->surname, SIGNAL( textChanged( const QString &) ), SLOT( inputChanged( const QString & ) ) ); + connect( mMainWidget->nick, SIGNAL( textChanged( const QString &) ), SLOT( inputChanged( const QString & ) ) ); + connect( mMainWidget->UIN, SIGNAL( textChanged( const QString &) ), SLOT( inputChanged( const QString & ) ) ); + connect( mMainWidget->cityS, SIGNAL( textChanged( const QString &) ), SLOT( inputChanged( const QString & ) ) ); + connect( mMainWidget->gender, SIGNAL( activated( const QString &) ), SLOT( inputChanged( const QString & ) ) ); + connect( mMainWidget->ageFrom, SIGNAL( valueChanged( const QString &) ), SLOT( inputChanged( const QString & ) ) ); + connect( mMainWidget->ageTo, SIGNAL( valueChanged( const QString &) ), SLOT( inputChanged( const QString & ) ) ); + connect( mMainWidget->radioByData, SIGNAL( toggled( bool ) ), SLOT( inputChanged( bool ) ) ); + + connect( mMainWidget->listFound, SIGNAL( selectionChanged () ), SLOT( slotListSelected() ) ); + +} + +void +GaduPublicDir::inputChanged( bool ) +{ + inputChanged( QString::null ); +} + +void +GaduPublicDir::inputChanged( const QString& ) +{ + if ( validateData() == false ) { + enableButton( User2, false ); + } + else { + enableButton( User2, true ); + } +} + +void +GaduPublicDir::getData() +{ + fName = mMainWidget->nameS->text(); + fSurname = mMainWidget->surname->text(); + fNick = mMainWidget->nick->text(); + fUin = mMainWidget->UIN->text().toInt(); + fGender = mMainWidget->gender->currentItem(); + fOnlyOnline = mMainWidget->onlyOnline->isChecked(); + fAgeFrom = mMainWidget->ageFrom->value(); + fAgeTo = mMainWidget->ageTo->value(); + fCity = mMainWidget->cityS->text(); +} + +// return true if not empty +#define CHECK_STRING(A) { if ( !A.isEmpty() ) { return true; } } +#define CHECK_INT(A) { if ( A ) { return true; } } + +bool +GaduPublicDir::validateData() +{ + getData(); + + if ( mMainWidget->radioByData->isChecked() ) { + CHECK_STRING( fCity ); + CHECK_STRING( fName ); + CHECK_STRING( fSurname ); + CHECK_STRING( fNick ); + CHECK_INT( fGender ); + CHECK_INT( fAgeFrom ); + CHECK_INT( fAgeTo ); + } + else { + fSurname = QString::null; + CHECK_INT( fUin ); + } + return false; +} + +// Move to GaduProtocol someday +QPixmap +GaduPublicDir::iconForStatus( uint status ) +{ + QPixmap n; + + if ( GaduProtocol::protocol() ) { + return GaduProtocol::protocol()->convertStatus( status ).protocolIcon(); + } + return n; +} + +void +GaduPublicDir::slotSearchResult( const SearchResult& result, unsigned int ) +{ + QListView* list = mMainWidget->listFound; + + kdDebug(14100) << "searchResults(" << result.count() <<")" << endl; + + QListViewItem* sl; + + SearchResult::const_iterator r; + + for ( r = result.begin(); r != result.end() ; ++r ){ + kdDebug(14100) << "adding" << (*r).uin << endl; + sl= new QListViewItem( + list, QString::fromAscii(""), + (*r).firstname, + (*r).nickname, + (*r).age, + (*r).city, + QString::number( (*r).uin ).ascii() + ); + sl->setPixmap( 0, iconForStatus( (*r).status ) ); + } + + // if not found anything, obviously we don't want to search for more + // if we are looking just for one UIN, don't allow search more - it is pointless + + if ( result.count() && fUin==0 ) { + enableButton( User2, true ); + } + + enableButton( User1, true ); + enableButton( User3, false ); + mMainWidget->pubsearch->setDisabled( false ); + +} + +void +GaduPublicDir::slotNewSearch() +{ + mMainWidget->pubsearch->raiseWidget( 0 ); + + setButtonText( User2, i18n( "S&earch" ) ); + + showButton( User1, false ); + showButton( User3, false ); + enableButton( User2, false ); + inputChanged( QString::null ); + mAccount->pubDirSearchClose(); +} + +void +GaduPublicDir::slotSearch() +{ + + mMainWidget->listFound->clear(); + QString empty; + + // search more, or search ? + if ( mMainWidget->pubsearch->id( mMainWidget->pubsearch->visibleWidget() ) == 0 ) { + kdDebug(14100) << "start search... " << endl; + getData(); + + // validate data + if ( validateData() == false ) { + return; + } + + // go on + mMainWidget->pubsearch->raiseWidget( 1 ); + + } + else{ + kdDebug(14100) << "search more... " << endl; + // Search for more + } + mMainWidget->pubsearch->setDisabled( true ); + setButtonText( User2, i18n( "Search &More..." ) ); + showButton( User3, true ); + showButton( User1, true ); + enableButton( User3, false ); + enableButton( User2, false ); + + ResLine rs; + rs.firstname = fName; + rs.surname = fSurname; + rs.nickname = fNick; + rs.uin = fUin; + rs.city = fCity; + + if ( fGender == 1 ) { + rs.gender = GG_PUBDIR50_GENDER_MALE; + } + if ( fGender == 2 ) { + rs.gender = GG_PUBDIR50_GENDER_FEMALE; + } + + + if ( mMainWidget->radioByData->isChecked() ) { + mAccount->pubDirSearch( rs, fAgeFrom, fAgeTo, fOnlyOnline ); + } + else { + mAccount->pubDirSearch( rs, 0, 0, fOnlyOnline ); + } +} + +#include "gadupubdir.moc" diff --git a/kopete/protocols/gadu/gadupubdir.h b/kopete/protocols/gadu/gadupubdir.h new file mode 100644 index 00000000..11c03981 --- /dev/null +++ b/kopete/protocols/gadu/gadupubdir.h @@ -0,0 +1,80 @@ +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// +// gadupubdir.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// + +#ifndef GADUPUBDIR_H +#define GADUPUBDIR_H + +#include "gadusearch.h" +#include "gadusession.h" + +#include +#include + +class GaduAccount; +class GaduProtocol; +class GaduContact; +class GaduAccount; +class GaduPublicDirectory; +class QListViewItem; +class GaduContact; + +class GaduPublicDir : public KDialogBase +{ +Q_OBJECT + +public: + GaduPublicDir( GaduAccount* , QWidget *parent = 0, const char* name = "GaduPublicDir" ); + GaduPublicDir( GaduAccount* , int searchFor, QWidget* parent = 0, const char* name = "GaduPublicDir" ); + QPixmap iconForStatus( uint status ); + +private slots: + void slotSearch(); + void slotNewSearch(); + void slotSearchResult( const SearchResult& result, unsigned int seq ); + void slotAddContact(); + void inputChanged( const QString& ); + void inputChanged( bool ); + void slotListSelected(); + + +private: + void getData(); + bool validateData(); + void createWidget(); + void initConnections(); + + GaduProtocol* p; + GaduAccount* mAccount; + GaduContact* mContact; + GaduPublicDirectory* mMainWidget; + +// form data + QString fName; + QString fSurname; + QString fNick; + QString fCity; + int fUin; + int fGender; + bool fOnlyOnline; + int fAgeFrom; + int fAgeTo; +}; +#endif diff --git a/kopete/protocols/gadu/gaduregisteraccount.cpp b/kopete/protocols/gadu/gaduregisteraccount.cpp new file mode 100644 index 00000000..e48ee551 --- /dev/null +++ b/kopete/protocols/gadu/gaduregisteraccount.cpp @@ -0,0 +1,212 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// +// gaduregisteraccount.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "gaduregisteraccountui.h" +#include "gaduregisteraccount.h" +#include "gaducommands.h" + +GaduRegisterAccount::GaduRegisterAccount( QWidget* parent, const char* name ) +: KDialogBase( parent, name, true, i18n( "Register New Account" ), KDialogBase::User1 | KDialogBase::Ok, KDialogBase::User1, true ) +{ + ui = new GaduRegisterAccountUI( this ); + setMainWidget( ui ); + + ui->valueVerificationSequence->setDisabled( true ); + setButtonText( User1, i18n( "&Register" ) ); + setButtonText( Ok, i18n( "&Cancel" ) ); + enableButton( User1, false ); + + cRegister = new RegisterCommand( this ); + + emailRegexp = new QRegExp( "[\\w\\d.+_-]{1,}@[\\w\\d.-]{1,}" ); + hintPixmap = KGlobal::iconLoader()->loadIcon ( "gadu_protocol", KIcon::Small ); + + connect( this, SIGNAL( user1Clicked() ), SLOT( doRegister() ) ); + connect( this, SIGNAL( okClicked() ), SLOT( slotClose() ) ); + + connect( ui->valueEmailAddress, SIGNAL( textChanged( const QString &) ), SLOT( inputChanged( const QString & ) ) ); + connect( ui->valuePassword, SIGNAL( textChanged( const QString & ) ), SLOT( inputChanged( const QString & ) ) ); + connect( ui->valuePasswordVerify, SIGNAL( textChanged( const QString & ) ), SLOT( inputChanged( const QString & ) ) ); + connect( ui->valueVerificationSequence, SIGNAL( textChanged( const QString & ) ), SLOT( inputChanged( const QString & ) ) ); + + connect( cRegister, SIGNAL( tokenRecieved( QPixmap, QString ) ), SLOT( displayToken( QPixmap, QString ) ) ); + connect( cRegister, SIGNAL( done( const QString&, const QString& ) ), SLOT( registrationDone( const QString&, const QString& ) ) ); + connect( cRegister, SIGNAL( error( const QString&, const QString& ) ), SLOT( registrationError( const QString&, const QString& ) ) ); + connect( cRegister, SIGNAL( operationStatus( const QString ) ), SLOT( updateStatus( const QString ) ) ); + + updateStatus( i18n( "Retrieving token" ) ); + cRegister->requestToken(); + + show(); +} + +void +GaduRegisterAccount::doRegister( ) +{ + cRegister->setUserinfo( ui->valueEmailAddress->text(), ui->valuePassword->text(), ui->valueVerificationSequence->text() ); + cRegister->execute(); + enableButton( User1, false ); +} + +void +GaduRegisterAccount::validateInput() +{ + int valid = true; + int passwordHighlight = false; + + if ( !emailRegexp->exactMatch( ui->valueEmailAddress->text() ) ) + { + updateStatus( i18n( "Please enter a valid E-Mail Address." ) ); + ui->pixmapEmailAddress->setPixmap ( hintPixmap ); + valid = false; + } + else { + ui->pixmapEmailAddress->setText ( "" ); + } + + if ( valid && ( ( ui->valuePassword->text().isEmpty() ) || ( ui->valuePasswordVerify->text().isEmpty() ) ) ) + { + updateStatus( i18n( "Please enter the same password twice." ) ); + valid = false; + passwordHighlight = true; + } + + if ( valid && ( ui->valuePassword->text() != ui->valuePasswordVerify->text() ) ) + { + updateStatus( i18n( "Password entries do not match." ) ); + valid = false; + passwordHighlight = true; + } + + if ( valid && ( ui->valueVerificationSequence->text().isEmpty() ) ) + { + updateStatus( i18n( "Please enter the verification sequence." ) ); + ui->pixmapVerificationSequence->setPixmap ( hintPixmap ); + valid = false; + } + else { + ui->pixmapVerificationSequence->setText ( "" ); + } + + if ( passwordHighlight == true ) + { + ui->pixmapPassword->setPixmap ( hintPixmap ); + ui->pixmapPasswordVerify->setPixmap ( hintPixmap ); + } + else { + ui->pixmapPassword->setText ( "" ); + ui->pixmapPasswordVerify->setText ( "" ); + } + + if ( valid ) + { + // clear status message if we have valid data + updateStatus( i18n( "" ) ); + } + + enableButton( User1, valid ); +} + +void +GaduRegisterAccount::inputChanged( const QString & ) +{ + validateInput(); +} + +void +GaduRegisterAccount::registrationDone( const QString& /*title*/, const QString& /*what */ ) +{ + ui->valueEmailAddress->setDisabled( true ); + ui->valuePassword->setDisabled( true ); + ui->valuePasswordVerify->setDisabled( true ); + ui->valueVerificationSequence->setDisabled( true ); + ui->labelEmailAddress->setDisabled( true ); + ui->labelPassword->setDisabled( true ); + ui->labelPasswordVerify->setDisabled( true ); + ui->labelVerificationSequence->setDisabled( true ); + ui->labelInstructions->setDisabled( true ); + emit registeredNumber( cRegister->newUin(), ui->valuePassword->text() ); + updateStatus( i18n( "Account created; your new UIN is %1." ).arg(QString::number( cRegister->newUin() ) ) ); + enableButton( User1, false ); + setButtonText( Ok, i18n( "&Close" ) ); +} + +void +GaduRegisterAccount::registrationError( const QString& title, const QString& what ) +{ + updateStatus( i18n( "Registration failed: %1" ).arg( what ) ); + KMessageBox::sorry( this, "Registration was unsucessful, please try again.", title ); + + disconnect( this, SLOT( displayToken( QPixmap, QString ) ) ); + disconnect( this, SLOT( registrationDone( const QString&, const QString& ) ) ); + disconnect( this, SLOT( registrationError( const QString&, const QString& ) ) ); + disconnect( this, SLOT( updateStatus( const QString ) ) ); + + ui->valueVerificationSequence->setDisabled( true ); + ui->valueVerificationSequence->setText( "" ); + enableButton( User1, false ); + updateStatus( "" ); + + // emit UIN 0, to enable 'register new account' button again in dialog below + emit registeredNumber( 0, QString( "" ) ); + + deleteLater(); +} + +void +GaduRegisterAccount::displayToken( QPixmap image, QString /*tokenId */ ) +{ + ui->valueVerificationSequence->setDisabled( false ); + ui->pixmapToken->setPixmap( image ); + validateInput(); +} + +void +GaduRegisterAccount::updateStatus( const QString status ) +{ + ui->labelStatusMessage->setAlignment( AlignCenter ); + ui->labelStatusMessage->setText( status ); +} + +void +GaduRegisterAccount::slotClose() +{ + deleteLater(); +} + +GaduRegisterAccount::~GaduRegisterAccount( ) +{ + kdDebug( 14100 ) << " register Cancel " << endl; +} + +#include "gaduregisteraccount.moc" diff --git a/kopete/protocols/gadu/gaduregisteraccount.h b/kopete/protocols/gadu/gaduregisteraccount.h new file mode 100644 index 00000000..339e4c36 --- /dev/null +++ b/kopete/protocols/gadu/gaduregisteraccount.h @@ -0,0 +1,62 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003 Grzegorz Jaskiewicz +// +// gaduregisteraccount.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef GADUREGISTERACCOUNT_H +#define GADUREGISTERACCOUNT_H + +#include + +class QString; +class QPixmap; +class RegisterCommand; +class QRegExp; +class GaduRegisterAccountUI; + +class GaduRegisterAccount : public KDialogBase +{ + Q_OBJECT + +public: + GaduRegisterAccount( QWidget* , const char* ); + ~GaduRegisterAccount( ); + +signals: + void registeredNumber( unsigned int, QString ); + +protected slots: + void slotClose(); + void displayToken( QPixmap, QString ); + void registrationError( const QString&, const QString& ); + void registrationDone( const QString&, const QString& ); + void inputChanged( const QString & ); + void doRegister(); + void updateStatus( const QString status ); + +private: + void validateInput(); + + GaduRegisterAccountUI* ui; + RegisterCommand* cRegister; + QRegExp* emailRegexp; + QPixmap hintPixmap; +}; + +#endif diff --git a/kopete/protocols/gadu/gadurichtextformat.cpp b/kopete/protocols/gadu/gadurichtextformat.cpp new file mode 100644 index 00000000..a93b95fd --- /dev/null +++ b/kopete/protocols/gadu/gadurichtextformat.cpp @@ -0,0 +1,291 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2004 Grzegorz Jaskiewicz +// +// gadurichtextformat.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#include +#include +#include + +#include "gadurichtextformat.h" +#include "gadusession.h" + +#include +#include + +GaduRichTextFormat::GaduRichTextFormat() +{ +} + +GaduRichTextFormat::~GaduRichTextFormat() +{ +} + +QString +GaduRichTextFormat::convertToHtml( const QString& msg, unsigned int formats, void* formatStructure) +{ + QString tmp, nb; + gg_msg_richtext_format *format; + char *pointer = (char*) formatStructure; + + unsigned int i,j; + int r, g, b; + r = g = b = 0; + bool opened = false; + + if ( formatStructure == NULL || formats == 0 ) { + tmp = msg; + escapeBody( tmp ); + return tmp; + } + + for ( i = 0, j = 0 ; i < formats ; ) { + format = (gg_msg_richtext_format*) pointer; + unsigned int position = format->position; + char font = format->font; + QString style; + + if ( position < j || position > msg.length() ) { + break; + } + + if ( font & GG_FONT_IMAGE ) { + i += sizeof( gg_msg_richtext_image ); + pointer += sizeof( gg_msg_richtext_image ); + tmp += "[this should be a picture, not yet implemented]"; + } + else { + nb = msg.mid( j, position - j ); + tmp += escapeBody( nb ); + + j = position; + + // add message bit between formating + if ( opened ) { + tmp += formatClosingTag("span"); + opened = false; + } + // set font attributes + if ( font & GG_FONT_BOLD ) { + style += (" font-weight:bold; "); + } + if ( font & GG_FONT_ITALIC ) { + style += (" font-style:italic; "); + } + if ( font & GG_FONT_UNDERLINE ) { + style += (" text-decoration:underline; "); + } + // add color + if ( font & GG_FONT_COLOR ) { + pointer += sizeof( gg_msg_richtext_format ); + i += sizeof( gg_msg_richtext_format ); + gg_msg_richtext_color *color = (gg_msg_richtext_color*)( pointer ); + r = (int)color->red; + g = (int)color->green; + b = (int)color->blue; + } + style += QString(" color: rgb( %1, %2, %3 ); ").arg( r ).arg( g ).arg( b ); + + tmp += formatOpeningTag( QString::fromLatin1("span"), QString::fromLatin1("style=\"%1\"").arg( style ) ); + opened = true; + + } + + // advance to next structure in row + pointer += sizeof( gg_msg_richtext_format ); + i += sizeof( gg_msg_richtext_format ); + } + + nb = msg.mid( j, msg.length() ); + tmp += escapeBody( nb ); + if ( opened ) { + tmp += formatClosingTag("span"); + } + + return tmp; +} + +QString +GaduRichTextFormat::formatOpeningTag( const QString& tag, const QString& attributes ) +{ + QString res = "<" + tag; + if(!attributes.isEmpty()) + res.append(" " + attributes); + return res + ">"; +} + +QString +GaduRichTextFormat::formatClosingTag( const QString& tag ) +{ + return ""; +} + +// the initial idea stolen from IRC plugin +KGaduMessage* +GaduRichTextFormat::convertToGaduMessage( const Kopete::Message& message ) +{ + QString htmlString = message.escapedBody(); + KGaduMessage* output = new KGaduMessage; + rtcs.blue = rtcs.green = rtcs.red = 0; + color = QColor(); + int position = 0; + + rtf.resize( sizeof( gg_msg_richtext) ); + output->rtf.resize(0); + + // test first if there is any HTML formating in it + if( htmlString.find( QString::fromLatin1(" -1 ) { + QRegExp findTags( QString::fromLatin1("(.*)") ); + findTags.setMinimal( true ); + int pos = 0; + int lastpos = 0; + + while ( pos >= 0 ){ + pos = findTags.search( htmlString ); + rtfs.font = 0; + if ( pos != lastpos ) { + QString tmp; + if ( pos < 0 ) { + tmp = htmlString.mid( lastpos ); + } + else { + tmp = htmlString.mid( lastpos, pos - lastpos ); + } + if ( !tmp.isEmpty() ) { + color.setRgb( 0, 0, 0 ); + if ( insertRtf( position ) == false ) { + delete output; + return NULL; + } + tmp = unescapeGaduMessage( tmp ); + output->message += tmp; + position += tmp.length(); + } + } + + if ( pos > -1 ) { + QString styleHTML = findTags.cap(1); + QString replacement = findTags.cap(2); + QStringList styleAttrs = QStringList::split( ';', styleHTML ); + rtfs.font = 0; + + lastpos = pos + replacement.length(); + + for( QStringList::Iterator attrPair = styleAttrs.begin(); attrPair != styleAttrs.end(); ++attrPair ) { + QString attribute = (*attrPair).section(':',0,0); + QString value = (*attrPair).section(':',1); + parseAttributes( attribute, value ); + } + + if ( insertRtf( position ) == false ) { + delete output; + return NULL; + } + + QString rep = QString("%2" ).arg( styleHTML ).arg( replacement ); + htmlString.replace( findTags.pos( 0 ), rep.length(), replacement ); + + replacement = unescapeGaduMessage( replacement ); + output->message += replacement; + position += replacement.length(); + } + + } + output->rtf = rtf; + // this is sick, but that's the way libgadu is designed + // here I am adding network header !, should sit in libgadu IMO + header = (gg_msg_richtext*) output->rtf.data(); + header->length = output->rtf.size() - sizeof( gg_msg_richtext ); + header->flag = 2; + } + else { + output->message = message.escapedBody(); + output->message = unescapeGaduMessage( output->message ); + } + + return output; + +} + +void +GaduRichTextFormat::parseAttributes( const QString attribute, const QString value ) +{ + if( attribute == QString::fromLatin1("color") ) { + color.setNamedColor( value ); + } + if( attribute == QString::fromLatin1("font-weight") && value == QString::fromLatin1("600") ) { + rtfs.font |= GG_FONT_BOLD; + } + if( attribute == QString::fromLatin1("text-decoration") && value == QString::fromLatin1("underline") ) { + rtfs.font |= GG_FONT_UNDERLINE ; + } + if( attribute == QString::fromLatin1("font-style") && value == QString::fromLatin1("italic") ) { + rtfs.font |= GG_FONT_ITALIC; + } +} + +QString +GaduRichTextFormat::unescapeGaduMessage( QString& ns ) +{ + QString s; + s = Kopete::Message::unescape( ns ); + s.replace( QString::fromAscii( "\n" ), QString::fromAscii( "\r\n" ) ); + return s; +} + +bool +GaduRichTextFormat::insertRtf( uint position) +{ + if ( color != QColor( rtcs.red, rtcs.green, rtcs.blue ) ) { + rtcs.red = color.red(); + rtcs.green = color.green(); + rtcs.blue = color.blue(); + rtfs.font |= GG_FONT_COLOR; + } + + if ( rtfs.font ) { + // append font description + rtfs.position = position; + uint csize = rtf.size(); + if ( rtf.resize( csize + sizeof( gg_msg_richtext_format ) ) == FALSE ) { + return false; + }; + memcpy( rtf.data() + csize, &rtfs, sizeof( rtfs ) ); + // append color description, if color has changed + if ( rtfs.font & GG_FONT_COLOR ) { + csize = rtf.size(); + if ( rtf.resize( csize + sizeof( gg_msg_richtext_color ) ) == FALSE ) { + return false; + }; + memcpy( rtf.data() + csize, &rtcs, sizeof( rtcs ) ); + } + } + return true; +} + +QString +GaduRichTextFormat::escapeBody( QString& input ) +{ + input.replace( '<', QString::fromLatin1("<") ); + input.replace( '>', QString::fromLatin1(">") ); + input.replace( '\n', QString::fromLatin1( "
    " ) ); + input.replace( '\t', QString::fromLatin1( "    " ) ); + input.replace( QRegExp( QString::fromLatin1( "\\s\\s" ) ), QString::fromLatin1( "  " ) ); + return input; +} diff --git a/kopete/protocols/gadu/gadurichtextformat.h b/kopete/protocols/gadu/gadurichtextformat.h new file mode 100644 index 00000000..34dfcd37 --- /dev/null +++ b/kopete/protocols/gadu/gadurichtextformat.h @@ -0,0 +1,53 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2004 Grzegorz Jaskiewicz +// +// gadurichtextformat.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + + +#ifndef GADURTF_H +#define GADURTF_H + +#include + +class Qstring; +namespace Kopete { class Message; } +class KGaduMessage; + +class GaduRichTextFormat { +public: + GaduRichTextFormat(); + ~GaduRichTextFormat(); + QString convertToHtml( const QString&, unsigned int, void* ); + KGaduMessage* convertToGaduMessage( const Kopete::Message& ); + +private: + QString formatOpeningTag( const QString& , const QString& = QString::null ); + QString formatClosingTag( const QString& ); + bool insertRtf( uint ); + QString unescapeGaduMessage( QString& ); + void parseAttributes( const QString, const QString ); + QString escapeBody( QString& ); + QColor color; + gg_msg_richtext_format rtfs; + gg_msg_richtext_color rtcs; + gg_msg_richtext* header; + QByteArray rtf; + +}; +#endif diff --git a/kopete/protocols/gadu/gadusession.cpp b/kopete/protocols/gadu/gadusession.cpp new file mode 100644 index 00000000..92f9137d --- /dev/null +++ b/kopete/protocols/gadu/gadusession.cpp @@ -0,0 +1,811 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003-2004 Grzegorz Jaskiewicz +// Copyright (C) 2002 Zack Rusin +// +// gadusession.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// + +#include "ctime" + +#include "gadusession.h" + +#include +#include +#include "kopetemessage.h" + +#include +#include +#include +#include "gadurichtextformat.h" + +#include +#include +#include + +GaduSession::GaduSession( QObject* parent, const char* name ) +: QObject( parent, name ), session_( 0 ), searchSeqNr_( 0 ) +{ + textcodec = QTextCodec::codecForName( "CP1250" ); + rtf = new GaduRichTextFormat; +} + +GaduSession::~GaduSession() +{ + logoff(); +} + +bool +GaduSession::isConnected() const +{ + if ( session_ ) { + return ( session_->state & GG_STATE_CONNECTED ); + } + return false; +} + +int +GaduSession::status() const +{ + kdDebug(14100)<<"Status = " << session_->status <<", initial = "<< session_->initial_status <status & ( ~GG_STATUS_FRIENDS_MASK ); + } + return GG_STATUS_NOT_AVAIL; +} + +void +GaduSession::login( struct gg_login_params* p ) +{ + if ( !isConnected() ) { + +// turn on in case you have any problems, and you want +// to report it better. libgadu needs to be recompiled with debug enabled +// gg_debug_level=GG_DEBUG_MISC|GG_DEBUG_FUNCTION; + + kdDebug(14100) << "Login" << endl; + + if ( !( session_ = gg_login( p ) ) ) { + destroySession(); + kdDebug( 14100 ) << "libgadu internal error " << endl; + emit connectionFailed( GG_FAILURE_CONNECTING ); + return; + } + + createNotifiers( true ); + enableNotifiers( session_->check ); + searchSeqNr_=0; + } +} + +void +GaduSession::destroyNotifiers() +{ + disableNotifiers(); + if ( read_ ) { + delete read_; + read_ = NULL; + } + if ( write_ ) { + delete write_; + write_ = NULL; + } +} + +void +GaduSession::createNotifiers( bool connect ) +{ + if ( !session_ ){ + return; + } + + read_ = new QSocketNotifier( session_->fd, QSocketNotifier::Read, this ); + read_->setEnabled( false ); + + write_ = new QSocketNotifier( session_->fd, QSocketNotifier::Write, this ); + write_->setEnabled( false ); + + if ( connect ) { + QObject::connect( read_, SIGNAL( activated( int ) ), SLOT( checkDescriptor() ) ); + QObject::connect( write_, SIGNAL( activated( int ) ), SLOT( checkDescriptor() ) ); + } +} + +void +GaduSession::enableNotifiers( int checkWhat ) +{ + if( (checkWhat & GG_CHECK_READ) && read_ ) { + read_->setEnabled( true ); + } + if( (checkWhat & GG_CHECK_WRITE) && write_ ) { + write_->setEnabled( true ); + } +} + +void +GaduSession::disableNotifiers() +{ + if ( read_ ) { + read_->setEnabled( false ); + } + if ( write_ ) { + write_->setEnabled( false ); + } +} + +void +GaduSession::dccRequest( const unsigned int uin ) +{ + if ( session_ ) { + gg_dcc_request( session_, uin ); + } +} + +void +GaduSession::login( KGaduLoginParams* loginp ) +{ + QCString desc = textcodec->fromUnicode( loginp->statusDescr ); + + memset( ¶ms_, 0, sizeof(params_) ); + + params_.status_descr = (char*)desc.data(); + + params_.uin = loginp->uin; + params_.password = (char *)( loginp->password.ascii() ); + params_.status = loginp->status | ( loginp->forFriends ? GG_STATUS_FRIENDS_MASK : 0 ); + params_.async = 1; + params_.tls = loginp->useTls; + params_ .server_addr = loginp->server; + params_.client_addr = loginp->client_addr; + params_.client_port = loginp->client_port; + + kdDebug(14100) << "LOGIN IP: " << loginp->client_addr << endl; + + if ( loginp->useTls ) { + params_.server_port = GG_HTTPS_PORT; + } + else { + if ( loginp->server ) { + params_.server_port = GG_DEFAULT_PORT; + } + } + + kdDebug(14100)<<"gadusession::login, server ( " << loginp->server << " ), tls(" << loginp->useTls << ") " <convertToGaduMessage( msg ); + if ( gadumessage ) { + const void* data = (const void*)gadumessage->rtf.data(); + cpMsg = textcodec->fromUnicode( gadumessage->message ); + int o; + o = gg_send_message_richtext( session_, msgClass, recipient, (const unsigned char *)cpMsg.data(), (const unsigned char*) data, gadumessage->rtf.size() ); + gadumessage->rtf.resize(0); + delete gadumessage; + return o; + } + else { + sendMsg = msg.plainBody(); + sendMsg.replace( QString::fromAscii( "\n" ), QString::fromAscii( "\r\n" ) ); + cpMsg = textcodec->fromUnicode( sendMsg ); + + return gg_send_message( session_, msgClass, recipient, (const unsigned char *)cpMsg.data() ); + } + } + else { + emit error( i18n("Not Connected"), i18n("You are not connected to the server.") ); + } + + return 1; +} + +int +GaduSession::changeStatus( int status, bool forFriends ) +{ + kdDebug(14101)<<"## Changing to "<fromUnicode(descr); + + if ( isConnected() ) { + return gg_change_status_descr( session_, + status | ( forFriends ? GG_STATUS_FRIENDS_MASK : 0), ndescr.data() ); + } + else { + emit error( i18n("Not Connected"), i18n("You have to be connected to the server to change your status.") ); + } + + return 1; +} + +int +GaduSession::ping() +{ + if ( isConnected() ) { + return gg_ping( session_ ); + } + + return 1; +} + +void +GaduSession::pubDirSearchClose() +{ + searchSeqNr_=0; +} + +unsigned int +GaduSession::getPersonalInformation() +{ + gg_pubdir50_t searchRequest; + unsigned int seqNr; + + if ( isConnected() == false ) { + return 0; + } + + searchRequest = gg_pubdir50_new( GG_PUBDIR50_READ ); + if ( !searchRequest ) { + return 0; + } + + seqNr = gg_pubdir50( session_, searchRequest ); + gg_pubdir50_free( searchRequest ); + + return seqNr; +} + +bool +GaduSession::publishPersonalInformation( ResLine& d ) +{ + gg_pubdir50_t r; + + if ( !session_ ) { + return 0; + } + + r = gg_pubdir50_new( GG_PUBDIR50_WRITE ); + + if ( d.firstname.length() ) + gg_pubdir50_add( r, GG_PUBDIR50_FIRSTNAME, + (const char *)((const char*)textcodec->fromUnicode( d.firstname ) ) ); + if ( d.surname.length() ) + gg_pubdir50_add( r, GG_PUBDIR50_LASTNAME, + (const char *)((const char*)textcodec->fromUnicode( d.surname ) ) ); + if ( d.nickname.length() ) + gg_pubdir50_add( r, GG_PUBDIR50_NICKNAME, + (const char *)((const char*)textcodec->fromUnicode( d.nickname ) ) ); + if ( d.age.length() ) + gg_pubdir50_add( r, GG_PUBDIR50_BIRTHYEAR, + (const char *)((const char*)textcodec->fromUnicode( d.age ) ) ); + if ( d.city.length() ) + gg_pubdir50_add( r, GG_PUBDIR50_CITY, + (const char *)((const char*)textcodec->fromUnicode( d.city ) ) ); + if ( d.meiden.length() ) + gg_pubdir50_add( r, GG_PUBDIR50_FAMILYNAME, + (const char *)((const char*)textcodec->fromUnicode( d.meiden ) ) ); + if ( d.orgin.length() ) + gg_pubdir50_add( r, GG_PUBDIR50_FAMILYCITY, + (const char *)((const char*)textcodec->fromUnicode( d.orgin ) ) ); + if ( d.gender.length() == 1 ) + gg_pubdir50_add( r, GG_PUBDIR50_GENDER, + (const char *)((const char*)textcodec->fromUnicode( d.gender ) ) ); + + gg_pubdir50( session_, r ); + + gg_pubdir50_free( r ); + + return true; +} + +unsigned int +GaduSession::pubDirSearch( ResLine& query, int ageFrom, int ageTo, bool onlyAlive ) +{ + QString bufYear; + unsigned int reqNr; + gg_pubdir50_t searchRequest; + + if ( !session_ ) { + return 0; + } + + searchRequest = gg_pubdir50_new( GG_PUBDIR50_SEARCH_REQUEST ); + if ( !searchRequest ) { + return 0; + } + + if ( query.uin == 0 ) { + if (query.firstname.length()) { + gg_pubdir50_add( searchRequest, GG_PUBDIR50_FIRSTNAME, + (const char*)textcodec->fromUnicode( query.firstname ) ); + } + if ( query.surname.length() ) { + gg_pubdir50_add( searchRequest, GG_PUBDIR50_LASTNAME, + (const char*)textcodec->fromUnicode( query.surname ) ); + } + if ( query.nickname.length() ) { + gg_pubdir50_add( searchRequest, GG_PUBDIR50_NICKNAME, + (const char*)textcodec->fromUnicode( query.nickname ) ); + } + if ( query.city.length() ) { + gg_pubdir50_add( searchRequest, GG_PUBDIR50_CITY, + (const char*)textcodec->fromUnicode( query.city ) ); + } + if ( ageFrom || ageTo ) { + QString yearFrom = QString::number( QDate::currentDate().year() - ageFrom ); + QString yearTo = QString::number( QDate::currentDate().year() - ageTo ); + + if ( ageFrom && ageTo ) { + gg_pubdir50_add( searchRequest, GG_PUBDIR50_BIRTHYEAR, + (const char*)textcodec->fromUnicode( yearFrom + " " + yearTo ) ); + } + if ( ageFrom ) { + gg_pubdir50_add( searchRequest, GG_PUBDIR50_BIRTHYEAR, + (const char*)textcodec->fromUnicode( yearFrom ) ); + } + else { + gg_pubdir50_add( searchRequest, GG_PUBDIR50_BIRTHYEAR, + (const char*)textcodec->fromUnicode( yearTo ) ); + } + } + + if ( query.gender.length() == 1 ) { + gg_pubdir50_add( searchRequest, GG_PUBDIR50_GENDER, + (const char *)((const char*)textcodec->fromUnicode( query.gender ) ) ); + } + + if ( onlyAlive ) { + gg_pubdir50_add( searchRequest, GG_PUBDIR50_ACTIVE, GG_PUBDIR50_ACTIVE_TRUE ); + } + } + // otherwise we are looking only for one fellow with this nice UIN + else{ + gg_pubdir50_add( searchRequest, GG_PUBDIR50_UIN, QString::number( query.uin ).ascii() ); + } + + gg_pubdir50_add( searchRequest, GG_PUBDIR50_START, QString::number( searchSeqNr_ ).ascii() ); + reqNr = gg_pubdir50( session_, searchRequest ); + gg_pubdir50_free( searchRequest ); + + return reqNr; +} + +void +GaduSession::sendResult( gg_pubdir50_t result ) +{ + int i, count, age; + ResLine resultLine; + SearchResult sres; + + count = gg_pubdir50_count( result ); + + if ( !count ) { + kdDebug(14100) << "there was nothing found in public directory for requested details" << endl; + } + + for ( i = 0; i < count; i++ ) { + resultLine.uin = QString( gg_pubdir50_get( result, i, GG_PUBDIR50_UIN ) ).toInt(); + resultLine.firstname = textcodec->toUnicode( gg_pubdir50_get( result, i, GG_PUBDIR50_FIRSTNAME ) ); + resultLine.surname = textcodec->toUnicode( gg_pubdir50_get( result, i, GG_PUBDIR50_LASTNAME ) ); + resultLine.nickname = textcodec->toUnicode( gg_pubdir50_get( result, i, GG_PUBDIR50_NICKNAME ) ); + resultLine.age = textcodec->toUnicode( gg_pubdir50_get( result, i, GG_PUBDIR50_BIRTHYEAR ) ); + resultLine.city = textcodec->toUnicode( gg_pubdir50_get( result, i, GG_PUBDIR50_CITY ) ); + QString stat = textcodec->toUnicode( gg_pubdir50_get( result, i, GG_PUBDIR50_STATUS ) ); + resultLine.orgin = textcodec->toUnicode( gg_pubdir50_get( result, i, GG_PUBDIR50_FAMILYCITY ) ); + resultLine.meiden = textcodec->toUnicode( gg_pubdir50_get( result, i, GG_PUBDIR50_FAMILYNAME ) ); + resultLine.gender = textcodec->toUnicode( gg_pubdir50_get( result, i, GG_PUBDIR50_GENDER ) ); + + resultLine.status = stat.toInt(); + age = resultLine.age.toInt(); + if ( age ) { + resultLine.age = QString::number( QDate::currentDate().year() - age ); + } + else { + resultLine.age.truncate( 0 ); + } + sres.append( resultLine ); + kdDebug(14100) << "found line "<< resultLine.uin << " " << resultLine.firstname << endl; + } + + searchSeqNr_ = gg_pubdir50_next( result ); + emit pubDirSearchResult( sres, gg_pubdir50_seq( result ) ); +} + +void +GaduSession::requestContacts() +{ + if ( !session_ || session_->state != GG_STATE_CONNECTED ) { + kdDebug(14100) <<" you need to be connected to send " << endl; + return; + } + + if ( gg_userlist_request( session_, GG_USERLIST_GET, NULL ) == -1 ) { + kdDebug(14100) <<" userlist export ERROR " << endl; + return; + } + kdDebug( 14100 ) << "Contacts list import..started " << endl; +} + + +void +GaduSession::exportContactsOnServer( GaduContactsList* contactsList ) +{ + QCString plist; + + if ( !session_ || session_->state != GG_STATE_CONNECTED ) { + kdDebug( 14100 ) << "you need to connect to export Contacts list " << endl; + return; + } + + plist = textcodec->fromUnicode( contactsList->asString() ); + kdDebug(14100) <<"--------------------userlists\n" << plist << endl; + kdDebug(14100) << "----------------------------" << endl; + + if ( gg_userlist_request( session_, GG_USERLIST_PUT, plist.data() ) == -1 ) { + kdDebug( 14100 ) << "export contact list failed " << endl; + return; + } + kdDebug( 14100 ) << "Contacts list export..started " << endl; +} + + +void +GaduSession::handleUserlist( gg_event* event ) +{ + QString ul; + switch( event->event.userlist.type ) { + case GG_USERLIST_GET_REPLY: + if ( event->event.userlist.reply ) { + ul = event->event.userlist.reply; + kdDebug( 14100 ) << "Got Contacts list OK " << endl; + } + else { + kdDebug( 14100 ) << "Got Contacts list FAILED/EMPTY " << endl; + // FIXME: send failed? + } + emit userListRecieved( ul ); + break; + + case GG_USERLIST_PUT_REPLY: + kdDebug( 14100 ) << "Contacts list exported OK " << endl; + emit userListExported(); + break; + + } +} + +QString +GaduSession::stateDescription( int state ) +{ + switch( state ) { + case GG_STATE_IDLE: + return i18n( "idle" ); + case GG_STATE_RESOLVING: + return i18n( "resolving host" ); + case GG_STATE_CONNECTING: + return i18n( "connecting" ); + case GG_STATE_READING_DATA: + return i18n( "reading data" ); + case GG_STATE_ERROR: + return i18n( "error" ); + case GG_STATE_CONNECTING_HUB: + return i18n( "connecting to hub" ); + case GG_STATE_CONNECTING_GG: + return i18n( "connecting to server" ); + case GG_STATE_READING_KEY: + return i18n( "retrieving key" ); + case GG_STATE_READING_REPLY: + return i18n( "waiting for reply" ); + case GG_STATE_CONNECTED: + return i18n( "connected" ); + case GG_STATE_SENDING_QUERY: + return i18n( "sending query" ); + case GG_STATE_READING_HEADER: + return i18n( "reading header" ); + case GG_STATE_PARSING: + return i18n( "parse data" ); + case GG_STATE_DONE: + return i18n( "done" ); + case GG_STATE_TLS_NEGOTIATION: + return i18n( "Tls connection negotiation" ); + default: + return i18n( "unknown" ); + } +} +QString +GaduSession::errorDescription( int err ) +{ + switch( err ){ + case GG_ERROR_RESOLVING: + return i18n( "Resolving error." ); + case GG_ERROR_CONNECTING: + return i18n( "Connecting error." ); + case GG_ERROR_READING: + return i18n( "Reading error." ); + case GG_ERROR_WRITING: + return i18n( "Writing error." ); + default: + return i18n( "Unknown error number %1." ).arg( QString::number( (unsigned int)err ) ); + } +} + +QString +GaduSession::failureDescription( gg_failure_t f ) +{ + switch( f ) { + case GG_FAILURE_RESOLVING: + return i18n( "Unable to resolve server address. DNS failure." ); + case GG_FAILURE_CONNECTING: + return i18n( "Unable to connect to server." ); + case GG_FAILURE_INVALID: + return i18n( "Server send incorrect data. Protocol error." ); + case GG_FAILURE_READING: + return i18n( "Problem reading data from server." ); + case GG_FAILURE_WRITING: + return i18n( "Problem sending data to server." ); + case GG_FAILURE_PASSWORD: + return i18n( "Incorrect password." ); + case GG_FAILURE_404: + return QString::fromAscii( "404." ); + case GG_FAILURE_TLS: + return i18n( "Unable to connect over encrypted channel.\nTry to turn off encryption support in Gadu account settings and reconnect." ); + default: + return i18n( "Unknown error number %1." ).arg( QString::number( (unsigned int)f ) ); + } +} + +void +GaduSession::notify60( gg_event* event ) +{ + KGaduNotify* gn = NULL; + unsigned int n; + + if ( event->event.notify60[0].uin ) { + gn = new KGaduNotify; + } + else { + return; + } + + for( n=0 ; event->event.notify60[n].uin ; n++ ) { + gn->contact_id = event->event.notify60[n].uin; + gn->status = event->event.notify60[n].status; + gn->remote_ip.setAddress( ntohl( event->event.notify60[n].remote_ip ) ); + gn->remote_port = event->event.notify60[n].remote_port; + if ( event->event.notify60[n].remote_ip && gn->remote_port > 10 ) { + gn->fileCap = true; + } + else { + gn->fileCap = false; + } + gn->version = event->event.notify60[n].version; + gn->image_size = event->event.notify60[n].image_size; + gn->description = textcodec->toUnicode( event->event.notify60[n].descr ); + emit contactStatusChanged( gn ); + } + delete gn; +} + +void +GaduSession::checkDescriptor() +{ + disableNotifiers(); + + struct gg_event* event; +// struct gg_dcc* dccSock; + KGaduMessage gaduMessage; + KGaduNotify gaduNotify; + + if ( !( event = gg_watch_fd( session_ ) ) ) { + kdDebug(14100)<<"Connection was broken for some reason"<state == GG_STATE_CONNECTING_HUB || session_->state == GG_STATE_CONNECTING_GG ) { + kdDebug(14100)<<"recreating notifiers"<type ) { + case GG_EVENT_MSG: + kdDebug(14100) << "incoming message:class:" << event->event.msg.msgclass << endl; + if ( event->event.msg.msgclass & GG_CLASS_CTCP ) { + kdDebug( 14100 ) << "incomming ctcp " << endl; + // TODO: DCC CONNECTION + emit incomingCtcp( event->event.msg.sender ); + } + + if ( (event->event.msg.msgclass & GG_CLASS_MSG) || (event->event.msg.msgclass & GG_CLASS_CHAT) ) { + gaduMessage.message = + textcodec->toUnicode((const char*)event->event.msg.message); + gaduMessage.sender_id = event->event.msg.sender; + gaduMessage.sendTime.setTime_t( event->event.msg.time, Qt::LocalTime ); + gaduMessage.message = rtf->convertToHtml( gaduMessage.message, event->event.msg.formats_length, event->event.msg.formats ); + emit messageReceived( &gaduMessage ); + } + break; + case GG_EVENT_ACK: + emit ackReceived( event->event.ack.recipient ); + break; + case GG_EVENT_STATUS: + gaduNotify.status = event->event.status.status; + gaduNotify.contact_id = event->event.status.uin; + if ( event->event.status.descr ) { + gaduNotify.description = textcodec->toUnicode( event->event.status.descr ); + } + else { + gaduNotify.description = QString::null; + } + gaduNotify.remote_port = 0; + gaduNotify.version = 0; + gaduNotify.image_size = 0; + gaduNotify.time = 0; + gaduNotify.fileCap = false; + + emit contactStatusChanged( &gaduNotify ); + break; + case GG_EVENT_STATUS60: + gaduNotify.status = event->event.status60.status; + gaduNotify.contact_id = event->event.status60.uin; + if ( event->event.status60.descr ) { + gaduNotify.description = textcodec->toUnicode( event->event.status60.descr ); + } + else { + gaduNotify.description = QString::null; + } + gaduNotify.remote_ip.setAddress( ntohl( event->event.status60.remote_ip ) ); + gaduNotify.remote_port = event->event.status60.remote_port; + gaduNotify.version = event->event.status60.version; + gaduNotify.image_size = event->event.status60.image_size; + gaduNotify.time = event->event.status60.time; + if ( event->event.status60.remote_ip && gaduNotify.remote_port > 10 ) { + gaduNotify.fileCap = true; + } + else { + gaduNotify.fileCap = false; + } + + emit contactStatusChanged( &gaduNotify ); + break; + case GG_EVENT_NOTIFY60: + notify60( event ); + break; + case GG_EVENT_CONN_SUCCESS: + kdDebug(14100) << "success server: " << session_->server_addr << endl; + emit connectionSucceed(); + break; + case GG_EVENT_CONN_FAILED: + kdDebug(14100) << "failed server: " << session_->server_addr << endl; + destroySession(); + kdDebug(14100) << "emit connection failed(" << event->event.failure << ") signal" << endl; + emit connectionFailed( (gg_failure_t)event->event.failure ); + break; + case GG_EVENT_DISCONNECT: + kdDebug(14100)<<"event Disconnected"<event.pubdir50 ); + break; + case GG_EVENT_USERLIST: + handleUserlist( event ); + break; + default: + kdDebug(14100)<<"Unprocessed GaduGadu Event = "<type<check ); + } +} + +#include "gadusession.moc" diff --git a/kopete/protocols/gadu/gadusession.h b/kopete/protocols/gadu/gadusession.h new file mode 100644 index 00000000..79626d7f --- /dev/null +++ b/kopete/protocols/gadu/gadusession.h @@ -0,0 +1,178 @@ +// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*- +// +// Copyright (C) 2003-2004 Grzegorz Jaskiewicz +// Copyright (C) 2002 Zack Rusin +// +// gadusession.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., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. + +#ifndef GADUSESSION_H +#define GADUSESSION_H + +#include "kopeteaccount.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gaducontactlist.h" + +#include + +struct KGaduMessage { + QString message; // Unicode + unsigned int sender_id; // sender's UIN + QDateTime sendTime; + QByteArray rtf; +}; + +struct KGaduLoginParams { + uin_t uin; + QString password; + bool useTls; + int status; + QString statusDescr; + unsigned int server; + bool forFriends; + unsigned int client_addr; + unsigned int client_port; +}; + +struct KGaduNotify { + int status; + QHostAddress remote_ip; + unsigned short remote_port; + bool fileCap; + int version; + int image_size; + int time; + QString description; + unsigned int contact_id; +}; + +struct ResLine{ + unsigned int uin; + QString firstname; + QString surname; + QString nickname; + QString age; + QString city; + QString orgin; + QString meiden; + QString gender; + int status; +}; + +typedef QValueList SearchResult; + +class QSocketNotifier; +class QStringList; +namespace Kopete { class Message; } +class GaduRichTextFormat; + +class GaduSession : public QObject +{ + Q_OBJECT + +public: + GaduSession( QObject* parent = 0, const char* name = 0 ); + virtual ~GaduSession(); + bool isConnected() const; + int status() const; + QString contactsToString( GaduContactsList* contactsList ); + bool stringToContacts( GaduContactsList& , const QString& ); + static QString failureDescription( gg_failure_t ); + static QString errorDescription( int err ); + static QString stateDescription( int state ); + void dccRequest( const unsigned int ); + unsigned int getPersonalInformation(); + /* + * Initiates search in public directory, we need to be logged on to perform search ! + * This returns 0, if you are unable to search (fe you are not logged on, you don't have memory) + * This does not checks parametrs ! + * Calling this function more times with the same params, will continue this search as long as + * @ref pubDirSearchClose() will not be called + * You must set @ref pubDirSearchResult() signal before calling this function, otherwise no result + * will be returned + */ + unsigned int pubDirSearch( ResLine&, int, int, bool ); + +public slots: + void login( KGaduLoginParams* login ); + void logoff( Kopete::Account::DisconnectReason reason = Kopete::Account::Manual ); + int notify( uin_t*, int ); + int addNotify( uin_t ); + int removeNotify( uin_t ); + int sendMessage( uin_t recipient, const Kopete::Message& msg, int msgClass ); + int changeStatus( int, bool forFriends = false ); + int changeStatusDescription( int, const QString&, bool forFriends = false ); + int ping(); + + void requestContacts(); + + /* + * Releases all allocated memory needed to perform search. + * This will be done on each @ref pubDirNewSearch(), if previuos is not released + */ + void pubDirSearchClose(); + void exportContactsOnServer( GaduContactsList* ); + bool publishPersonalInformation( ResLine& ); + +signals: + void error( const QString&, const QString& ); + void messageReceived( KGaduMessage* ); + void ackReceived( unsigned int ); + void contactStatusChanged( KGaduNotify* ); + void pong(); + void connectionFailed( gg_failure_t failure ); + void connectionSucceed( ); + void disconnect( Kopete::Account::DisconnectReason ); + void pubDirSearchResult( const SearchResult&, unsigned int ); + void userListRecieved( const QString& ); + void userListExported(); + void incomingCtcp( unsigned int ); + +protected slots: + void enableNotifiers( int ); + void disableNotifiers(); + void checkDescriptor(); + void login( struct gg_login_params* ); + +private: + + void sendResult( gg_pubdir50_t ); + void handleUserlist( gg_event* ); + void notify60( gg_event* ); + void destroySession(); + void destroyNotifiers(); + void createNotifiers( bool connect ); + + gg_session* session_; + QSocketNotifier* read_; + QSocketNotifier* write_; + gg_login_params params_; + QTextCodec* textcodec; + int searchSeqNr_; + GaduRichTextFormat* rtf; +}; + +#endif diff --git a/kopete/protocols/gadu/icons/Makefile.am b/kopete/protocols/gadu/icons/Makefile.am new file mode 100644 index 00000000..9143c6b4 --- /dev/null +++ b/kopete/protocols/gadu/icons/Makefile.am @@ -0,0 +1,2 @@ +kopeteicondir = $(kde_datadir)/kopete/icons +kopeteicon_ICON = AUTO diff --git a/kopete/protocols/gadu/icons/cr16-action-gg_away.png b/kopete/protocols/gadu/icons/cr16-action-gg_away.png new file mode 100644 index 00000000..a3ecf056 Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-action-gg_away.png differ diff --git a/kopete/protocols/gadu/icons/cr16-action-gg_busy.png b/kopete/protocols/gadu/icons/cr16-action-gg_busy.png new file mode 100644 index 00000000..18d1640c Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-action-gg_busy.png differ diff --git a/kopete/protocols/gadu/icons/cr16-action-gg_busy_d.png b/kopete/protocols/gadu/icons/cr16-action-gg_busy_d.png new file mode 100644 index 00000000..d9790b54 Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-action-gg_busy_d.png differ diff --git a/kopete/protocols/gadu/icons/cr16-action-gg_con.mng b/kopete/protocols/gadu/icons/cr16-action-gg_con.mng new file mode 100644 index 00000000..64eea9ea Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-action-gg_con.mng differ diff --git a/kopete/protocols/gadu/icons/cr16-action-gg_connecting.png b/kopete/protocols/gadu/icons/cr16-action-gg_connecting.png new file mode 100644 index 00000000..23d80cb4 Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-action-gg_connecting.png differ diff --git a/kopete/protocols/gadu/icons/cr16-action-gg_description_overlay.png b/kopete/protocols/gadu/icons/cr16-action-gg_description_overlay.png new file mode 100644 index 00000000..128e27eb Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-action-gg_description_overlay.png differ diff --git a/kopete/protocols/gadu/icons/cr16-action-gg_ignored.png b/kopete/protocols/gadu/icons/cr16-action-gg_ignored.png new file mode 100644 index 00000000..98674ef5 Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-action-gg_ignored.png differ diff --git a/kopete/protocols/gadu/icons/cr16-action-gg_invi.png b/kopete/protocols/gadu/icons/cr16-action-gg_invi.png new file mode 100644 index 00000000..ab994042 Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-action-gg_invi.png differ diff --git a/kopete/protocols/gadu/icons/cr16-action-gg_invi_d.png b/kopete/protocols/gadu/icons/cr16-action-gg_invi_d.png new file mode 100644 index 00000000..e2f44f62 Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-action-gg_invi_d.png differ diff --git a/kopete/protocols/gadu/icons/cr16-action-gg_offline.png b/kopete/protocols/gadu/icons/cr16-action-gg_offline.png new file mode 100644 index 00000000..65569e12 Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-action-gg_offline.png differ diff --git a/kopete/protocols/gadu/icons/cr16-action-gg_offline_d.png b/kopete/protocols/gadu/icons/cr16-action-gg_offline_d.png new file mode 100644 index 00000000..5b5740b3 Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-action-gg_offline_d.png differ diff --git a/kopete/protocols/gadu/icons/cr16-action-gg_online.png b/kopete/protocols/gadu/icons/cr16-action-gg_online.png new file mode 100644 index 00000000..652c127a Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-action-gg_online.png differ diff --git a/kopete/protocols/gadu/icons/cr16-action-gg_online_d.png b/kopete/protocols/gadu/icons/cr16-action-gg_online_d.png new file mode 100644 index 00000000..26b28d49 Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-action-gg_online_d.png differ diff --git a/kopete/protocols/gadu/icons/cr16-app-gadu_protocol.png b/kopete/protocols/gadu/icons/cr16-app-gadu_protocol.png new file mode 100644 index 00000000..46f062c3 Binary files /dev/null and b/kopete/protocols/gadu/icons/cr16-app-gadu_protocol.png differ diff --git a/kopete/protocols/gadu/icons/cr32-app-gadu_protocol.png b/kopete/protocols/gadu/icons/cr32-app-gadu_protocol.png new file mode 100644 index 00000000..2ff51736 Binary files /dev/null and b/kopete/protocols/gadu/icons/cr32-app-gadu_protocol.png differ diff --git a/kopete/protocols/gadu/kopete_gadu.desktop b/kopete/protocols/gadu/kopete_gadu.desktop new file mode 100644 index 00000000..5c2750d5 --- /dev/null +++ b/kopete/protocols/gadu/kopete_gadu.desktop @@ -0,0 +1,78 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=gadu_protocol +ServiceTypes=Kopete/Protocol +X-Kopete-Messaging-Protocol=messaging/gadu +X-KDE-Library=kopete_gadu +X-KDE-PluginInfo-Author=Grzegorz Jaskiewicz +X-KDE-PluginInfo-Email=gj@pointblue.com.pl +X-KDE-PluginInfo-Name=kopete_gadu +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Protocols +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Gadu-Gadu +Name[fa]=Gadu-Gadu‌ +Name[hi]=गडू-गडू +Name[ne]=गादà¥-गादॠ+Name[tr]=Gadu Gadu +Comment=Protocol to connect to Gadu-Gadu +Comment[ar]=البرتوكول سيتصل بـ Gadu-Gadu +Comment[be]=Пратакол Gadu-Gadu +Comment[bg]=Протокол за връзка Ñ Gadu-Gadu +Comment[bn]=Gadu-Gadu-তে সংযোগ করতে পà§à¦°à§‹à¦Ÿà§‹à¦•à¦² +Comment[br]=Komenad kevreañ ouzh Gadu-Gadu +Comment[bs]=Gadu-Gadu protokol +Comment[ca]=Protocol per a connectar-se a Gadu-Gadu +Comment[cs]=Protokol k pÅ™ipojení ke Gadu-Gadu +Comment[cy]=Protocol i gysylltu â Gadu-Gadu +Comment[da]=Protokol til at forbinde til Gadu-Gadu +Comment[de]=Protokoll zur Verbindung mit Gadu-Gadu +Comment[el]=ΠÏωτόκολλο για σÏνδεση στο Gadu-Gadu +Comment[es]=Protocolo de conexión a Gadu-Gadu +Comment[et]=Protokoll ühendumiseks Gadu-Gaduga +Comment[eu]=Gadu-Gadu-ra konektatzeko protokoloa +Comment[fa]=قرارداد برای اتصال Gadu-Gadu‌ +Comment[fi]=Yhteyskäytäntö Gadu-Gadu-verkkoon kytkeytymiseen +Comment[fr]=Protocole pour se connecter sur Gadu-Gadu +Comment[ga]=Prótacal chun ceangal le Gadu-Gadu +Comment[gl]=Protocolo para conectarse a Gadu-Gadu +Comment[he]=פרוטוקול התחברות ל- Gadu-Gadu +Comment[hi]=गडू-गडू से जà¥à¤¡à¤¼à¤¨à¥‡ का पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¥‰à¤² +Comment[hr]=Protokol za povezivanje na Gadu-Gadu +Comment[hu]=Protokoll a Gadu-Gadu használatához +Comment[is]=Samskiptamáti til að tengjast Gadu-Gadu +Comment[it]=Protocollo per connessione a Gadu-Gadu +Comment[ja]=Gadu-Gadu ã«æŽ¥ç¶šã™ã‚‹ãƒ—ロトコル +Comment[ka]=Gadu-Gadu-სთáƒáƒœ დáƒáƒ™áƒáƒ•áƒ¨áƒ˜áƒ áƒ”ბის áƒáƒ¥áƒ›áƒ˜ +Comment[kk]=Gadu-Gadu-ға қоÑылу протоколы +Comment[km]=ពិធីការ​​ភ្ជាប់​ទៅ Gadu-Gadu​ +Comment[lt]=Protokolas prisijungimui prie Gadu-Gadu +Comment[mk]=Протокол за поврзување на Gadu-Gadu +Comment[nb]=Protokoll for Ã¥ koble til Gadu-Gadu +Comment[nds]=Protokoll för't Tokoppeln na Gadu-Gadu +Comment[ne]=गादà¥-गादà¥à¤®à¤¾ जडान गरà¥à¤¨à¥‡ पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¤² +Comment[nl]=Protocol voor Gadu-Gadu +Comment[nn]=Protokoll for Ã¥ kopla til Gadu-Gadu +Comment[pl]=Protokół poÅ‚Ä…czenia z serwerem Gadu-Gadu +Comment[pt]=Um protocolo para se ligar ao Gadu-Gadu +Comment[pt_BR]=Protocolo para conexão ao Gadu-Gadu +Comment[ro]=Protocol de conectare la Gadu-Gadu +Comment[ru]=Протокол Ð´Ð»Ñ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ðº Gadu-Gadu +Comment[sk]=Protokol pre pripojenie k Gadu-Gadu +Comment[sl]=Protokol za povezavo na Gadu-Gadu +Comment[sr]=Протокол за повезивање на Gadu-Gadu +Comment[sr@Latn]=Protokol za povezivanje na Gadu-Gadu +Comment[sv]=Protokoll för att ansluta till Gadu-Gadu +Comment[ta]=Gadu-Gadu உடன௠இணைகà¯à®• விதிமà¯à®±à¯ˆ +Comment[tg]=Қарордоди пайваÑтшавӣ ба Gadu-Gadu +Comment[tr]=Gadu Gadu'ya baÄŸlantı iletiÅŸim kuralı +Comment[uk]=Протокол Ð´Ð»Ñ Ð·'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· Gadu-Gadu +Comment[uz]=Gadu-Gadu bilan aloqa oÊ»rnatish uchun protokol +Comment[uz@cyrillic]=Gadu-Gadu билан алоқа ўрнатиш учун протокол +Comment[zh_CN]=连接到 Gadu-Gadu åè®® +Comment[zh_HK]=用來連接至 Gadu-Gadu 的通訊å”定 +Comment[zh_TW]=連線到 Gadu-Gadu çš„å”定 diff --git a/kopete/protocols/gadu/libgadu/COPYING b/kopete/protocols/gadu/libgadu/COPYING new file mode 100644 index 00000000..512ede36 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 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. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; 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. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/kopete/protocols/gadu/libgadu/Makefile.am b/kopete/protocols/gadu/libgadu/Makefile.am new file mode 100644 index 00000000..94693d38 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/Makefile.am @@ -0,0 +1,12 @@ +METASOURCES = AUTO +noinst_LTLIBRARIES = libgadu_copy.la +INCLUDES = $(SSL_INCLUDES) $(all_includes) +libgadu_copy_la_LDFLAGS = $(SSL_LDFLAGS) $(all_libraries) +libgadu_copy_la_LIBADD = $(LIBSSL) $(LIBPTHREAD) +libgadu_copy_la_SOURCES = common.c \ + dcc.c \ + events.c \ + http.c \ + libgadu.c \ + pubdir50.c \ + pubdir.c diff --git a/kopete/protocols/gadu/libgadu/common.c b/kopete/protocols/gadu/libgadu/common.c new file mode 100644 index 00000000..2e835fca --- /dev/null +++ b/kopete/protocols/gadu/libgadu/common.c @@ -0,0 +1,822 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski + * Robert J. Wo¼ny + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include +#include +#ifdef sun +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libgadu.h" + +FILE *gg_debug_file = NULL; + +#ifndef GG_DEBUG_DISABLE + +/* + * gg_debug() // funkcja wewnêtrzna + * + * wy¶wietla komunikat o danym poziomie, o ile u¿ytkownik sobie tego ¿yczy. + * + * - level - poziom wiadomo¶ci + * - format... - tre¶æ wiadomo¶ci (kompatybilna z printf()) + */ +void gg_debug(int level, const char *format, ...) +{ + va_list ap; + int old_errno = errno; + + if (gg_debug_handler) { + va_start(ap, format); + (*gg_debug_handler)(level, format, ap); + va_end(ap); + + goto cleanup; + } + + if ((gg_debug_level & level)) { + va_start(ap, format); + vfprintf((gg_debug_file) ? gg_debug_file : stderr, format, ap); + va_end(ap); + } + +cleanup: + errno = old_errno; +} + +#endif + +/* + * gg_vsaprintf() // funkcja pomocnicza + * + * robi dok³adnie to samo, co vsprintf(), tyle ¿e alokuje sobie wcze¶niej + * miejsce na dane. powinno dzia³aæ na tych maszynach, które maj± funkcjê + * vsnprintf() zgodn± z C99, jak i na wcze¶niejszych. + * + * - format - opis wy¶wietlanego tekstu jak dla printf() + * - ap - lista argumentów dla printf() + * + * zaalokowany bufor, który nale¿y pó¼niej zwolniæ, lub NULL + * je¶li nie uda³o siê wykonaæ zadania. + */ +char *gg_vsaprintf(const char *format, va_list ap) +{ + int size = 0; + const char *start; + char *buf = NULL; + +#ifdef __GG_LIBGADU_HAVE_VA_COPY + va_list aq; + + va_copy(aq, ap); +#else +# ifdef __GG_LIBGADU_HAVE___VA_COPY + va_list aq; + + __va_copy(aq, ap); +# endif +#endif + + start = format; + +#ifndef __GG_LIBGADU_HAVE_C99_VSNPRINTF + { + int res; + char *tmp; + + size = 128; + do { + size *= 2; + if (!(tmp = realloc(buf, size))) { + free(buf); + return NULL; + } + buf = tmp; + res = vsnprintf(buf, size, format, ap); + } while (res == size - 1 || res == -1); + } +#else + { + char tmp[2]; + + /* libce Solarisa przy buforze NULL zawsze zwracaj± -1, wiêc + * musimy podaæ co¶ istniej±cego jako cel printf()owania. */ + size = vsnprintf(tmp, sizeof(tmp), format, ap); + if (!(buf = malloc(size + 1))) + return NULL; + } +#endif + + format = start; + +#ifdef __GG_LIBGADU_HAVE_VA_COPY + vsnprintf(buf, size + 1, format, aq); + va_end(aq); +#else +# ifdef __GG_LIBGADU_HAVE___VA_COPY + vsnprintf(buf, size + 1, format, aq); + va_end(aq); +# else + vsnprintf(buf, size + 1, format, ap); +# endif +#endif + + return buf; +} + +/* + * gg_saprintf() // funkcja pomocnicza + * + * robi dok³adnie to samo, co sprintf(), tyle ¿e alokuje sobie wcze¶niej + * miejsce na dane. powinno dzia³aæ na tych maszynach, które maj± funkcjê + * vsnprintf() zgodn± z C99, jak i na wcze¶niejszych. + * + * - format... - tre¶æ taka sama jak w funkcji printf() + * + * zaalokowany bufor, który nale¿y pó¼niej zwolniæ, lub NULL + * je¶li nie uda³o siê wykonaæ zadania. + */ +char *gg_saprintf(const char *format, ...) +{ + va_list ap; + char *res; + + va_start(ap, format); + res = gg_vsaprintf(format, ap); + va_end(ap); + + return res; +} + +/* + * gg_get_line() // funkcja pomocnicza + * + * podaje kolejn± liniê z bufora tekstowego. niszczy go bezpowrotnie, dziel±c + * na kolejne stringi. zdarza siê, nie ma potrzeby pisania funkcji dubluj±cej + * bufor ¿eby tylko mieæ nieruszone dane wej¶ciowe, skoro i tak nie bêd± nam + * po¼niej potrzebne. obcina `\r\n'. + * + * - ptr - wska¼nik do zmiennej, która przechowuje aktualn± pozycjê + * w przemiatanym buforze + * + * wska¼nik do kolejnej linii tekstu lub NULL, je¶li to ju¿ koniec bufora. + */ +char *gg_get_line(char **ptr) +{ + char *foo, *res; + + if (!ptr || !*ptr || !strcmp(*ptr, "")) + return NULL; + + res = *ptr; + + if (!(foo = strchr(*ptr, '\n'))) + *ptr += strlen(*ptr); + else { + *ptr = foo + 1; + *foo = 0; + if (strlen(res) > 1 && res[strlen(res) - 1] == '\r') + res[strlen(res) - 1] = 0; + } + + return res; +} + +/* + * gg_connect() // funkcja pomocnicza + * + * ³±czy siê z serwerem. pierwszy argument jest typu (void *), ¿eby nie + * musieæ niczego inkludowaæ w libgadu.h i nie psuæ jaki¶ g³upich zale¿no¶ci + * na dziwnych systemach. + * + * - addr - adres serwera (struct in_addr *) + * - port - port serwera + * - async - asynchroniczne po³±czenie + * + * deskryptor gniazda lub -1 w przypadku b³êdu (kod b³êdu w zmiennej errno). + */ +int gg_connect(void *addr, int port, int async) +{ + int sock, one = 1, errno2; + struct sockaddr_in sin; + struct in_addr *a = addr; + struct sockaddr_in myaddr; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_connect(%s, %d, %d);\n", inet_ntoa(*a), port, async); + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() socket() failed (errno=%d, %s)\n", errno, strerror(errno)); + return -1; + } + + memset(&myaddr, 0, sizeof(myaddr)); + myaddr.sin_family = AF_INET; + + myaddr.sin_addr.s_addr = gg_local_ip; + + if (bind(sock, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() bind() failed (errno=%d, %s)\n", errno, strerror(errno)); + return -1; + } + +#ifdef ASSIGN_SOCKETS_TO_THREADS + gg_win32_thread_socket(0, sock); +#endif + + if (async) { +#ifdef FIONBIO + if (ioctl(sock, FIONBIO, &one) == -1) { +#else + if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_connect() ioctl() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + close(sock); + errno = errno2; + return -1; + } + } + + sin.sin_port = htons(port); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = a->s_addr; + + if (connect(sock, (struct sockaddr*) &sin, sizeof(sin)) == -1) { + if (errno && (!async || errno != EINPROGRESS)) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + close(sock); + errno = errno2; + return -1; + } + gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() in progress\n"); + } + + return sock; +} + +/* + * gg_read_line() // funkcja pomocnicza + * + * czyta jedn± liniê tekstu z gniazda. + * + * - sock - deskryptor gniazda + * - buf - wska¼nik do bufora + * - length - d³ugo¶æ bufora + * + * je¶li trafi na b³±d odczytu lub podano nieprawid³owe parametry, zwraca NULL. + * inaczej zwraca buf. + */ +char *gg_read_line(int sock, char *buf, int length) +{ + int ret; + + if (!buf || length < 0) + return NULL; + + for (; length > 1; buf++, length--) { + do { + if ((ret = read(sock, buf, 1)) == -1 && errno != EINTR) { + gg_debug(GG_DEBUG_MISC, "// gg_read_line() error on read (errno=%d, %s)\n", errno, strerror(errno)); + *buf = 0; + return NULL; + } else if (ret == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_read_line() eof reached\n"); + *buf = 0; + return NULL; + } + } while (ret == -1 && errno == EINTR); + + if (*buf == '\n') { + buf++; + break; + } + } + + *buf = 0; + return buf; +} + +/* + * gg_chomp() // funkcja pomocnicza + * + * ucina "\r\n" lub "\n" z koñca linii. + * + * - line - linia do przyciêcia + */ +void gg_chomp(char *line) +{ + int len; + + if (!line) + return; + + len = strlen(line); + + if (len > 0 && line[len - 1] == '\n') + line[--len] = 0; + if (len > 0 && line[len - 1] == '\r') + line[--len] = 0; +} + +/* + * gg_urlencode() // funkcja wewnêtrzna + * + * zamienia podany tekst na ci±g znaków do formularza http. przydaje siê + * przy ró¿nych us³ugach katalogu publicznego. + * + * - str - ci±g znaków do zakodowania + * + * zaalokowany bufor, który nale¿y pó¼niej zwolniæ albo NULL + * w przypadku b³êdu. + */ +char *gg_urlencode(const char *str) +{ + char *q, *buf, hex[] = "0123456789abcdef"; + const char *p; + unsigned int size = 0; + + if (!str) + str = ""; + + for (p = str; *p; p++, size++) { + if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == ' ') || (*p == '@') || (*p == '.') || (*p == '-')) + size += 2; + } + + if (!(buf = malloc(size + 1))) + return NULL; + + for (p = str, q = buf; *p; p++, q++) { + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || (*p == '@') || (*p == '.') || (*p == '-')) + *q = *p; + else { + if (*p == ' ') + *q = '+'; + else { + *q++ = '%'; + *q++ = hex[*p >> 4 & 15]; + *q = hex[*p & 15]; + } + } + } + + *q = 0; + + return buf; +} + +/* + * gg_http_hash() // funkcja wewnêtrzna + * + * funkcja licz±ca hash dla adresu e-mail, has³a i paru innych. + * + * - format... - format kolejnych parametrów ('s' je¶li dany parametr jest + * ci±giem znaków lub 'u' je¶li numerem GG) + * + * hash wykorzystywany przy rejestracji i wszelkich manipulacjach w³asnego + * wpisu w katalogu publicznym. + */ +int gg_http_hash(const char *format, ...) +{ + unsigned int a, c, i, j; + va_list ap; + int b = -1; + + va_start(ap, format); + + for (j = 0; j < strlen(format); j++) { + char *arg, buf[16]; + + if (format[j] == 'u') { + snprintf(buf, sizeof(buf), "%d", va_arg(ap, uin_t)); + arg = buf; + } else { + if (!(arg = va_arg(ap, char*))) + arg = ""; + } + + i = 0; + while ((c = (unsigned char) arg[i++]) != 0) { + a = (c ^ b) + (c << 8); + b = (a >> 24) | (a << 8); + } + } + + va_end(ap); + + return (b < 0 ? -b : b); +} + +/* + * gg_gethostbyname() // funkcja pomocnicza + * + * odpowiednik gethostbyname() troszcz±cy siê o wspó³bie¿no¶æ, gdy mamy do + * dyspozycji funkcjê gethostbyname_r(). + * + * - hostname - nazwa serwera + * + * zwraca wska¼nik na strukturê in_addr, któr± nale¿y zwolniæ. + */ +struct in_addr *gg_gethostbyname(const char *hostname) +{ + struct in_addr *addr = NULL; + +#ifdef HAVE_GETHOSTBYNAME_R + char *tmpbuf = NULL, *buf = NULL; + struct hostent *hp = NULL, *hp2 = NULL; + int h_errnop, ret; + size_t buflen = 1024; + int new_errno; + + new_errno = ENOMEM; + + if (!(addr = malloc(sizeof(struct in_addr)))) + goto cleanup; + + if (!(hp = calloc(1, sizeof(*hp)))) + goto cleanup; + + if (!(buf = malloc(buflen))) + goto cleanup; + + tmpbuf = buf; + + while ((ret = gethostbyname_r(hostname, hp, buf, buflen, &hp2, &h_errnop)) == ERANGE) { + buflen *= 2; + + if (!(tmpbuf = realloc(buf, buflen))) + break; + + buf = tmpbuf; + } + + if (ret) + new_errno = h_errnop; + + if (ret || !hp2 || !tmpbuf) + goto cleanup; + + memcpy(addr, hp->h_addr, sizeof(struct in_addr)); + + free(buf); + free(hp); + + return addr; + +cleanup: + errno = new_errno; + + if (addr) + free(addr); + if (hp) + free(hp); + if (buf) + free(buf); + + return NULL; +#else + struct hostent *hp; + + if (!(addr = malloc(sizeof(struct in_addr)))) { + goto cleanup; + } + + if (!(hp = gethostbyname(hostname))) + goto cleanup; + + memcpy(addr, hp->h_addr, sizeof(struct in_addr)); + + return addr; + +cleanup: + if (addr) + free(addr); + + return NULL; +#endif +} + +#ifdef ASSIGN_SOCKETS_TO_THREADS + +typedef struct gg_win32_thread { + int id; + int socket; + struct gg_win32_thread *next; +} gg_win32_thread; + +struct gg_win32_thread *gg_win32_threads = 0; + +/* + * gg_win32_thread_socket() // funkcja pomocnicza, tylko dla win32 + * + * zwraca deskryptor gniazda, które by³o ostatnio tworzone dla w±tku + * o podanym identyfikatorze. + * + * je¶li na win32 przy po³±czeniach synchronicznych zapamiêtamy w jakim + * w±tku uruchomili¶my funkcjê, która siê z czymkolwiek ³±czy, to z osobnego + * w±tku mo¿emy anulowaæ po³±czenie poprzez gg_win32_thread_socket(watek, -1); + * + * - thread_id - id w±tku. je¶li jest równe 0, brany jest aktualny w±tek, + * je¶li równe -1, usuwa wpis o podanym sockecie. + * - socket - deskryptor gniazda. je¶li równe 0, zwraca deskryptor gniazda + * dla podanego w±tku, je¶li równe -1, usuwa wpis, je¶li co¶ + * innego, ustawia dla podanego w±tku dany numer deskryptora. + * + * je¶li socket jest równe 0, zwraca deskryptor gniazda dla podanego w±tku. + */ +int gg_win32_thread_socket(int thread_id, int socket) +{ + char close = (thread_id == -1) || socket == -1; + gg_win32_thread *wsk = gg_win32_threads; + gg_win32_thread **p_wsk = &gg_win32_threads; + + if (!thread_id) + thread_id = GetCurrentThreadId(); + + while (wsk) { + if ((thread_id == -1 && wsk->socket == socket) || wsk->id == thread_id) { + if (close) { + /* socket zostaje usuniety */ + closesocket(wsk->socket); + *p_wsk = wsk->next; + free(wsk); + return 1; + } else if (!socket) { + /* socket zostaje zwrocony */ + return wsk->socket; + } else { + /* socket zostaje ustawiony */ + wsk->socket = socket; + return socket; + } + } + p_wsk = &(wsk->next); + wsk = wsk->next; + } + + if (close && socket != -1) + closesocket(socket); + if (close || !socket) + return 0; + + /* Dodaje nowy element */ + wsk = malloc(sizeof(gg_win32_thread)); + wsk->id = thread_id; + wsk->socket = socket; + wsk->next = 0; + *p_wsk = wsk; + + return socket; +} + +#endif /* ASSIGN_SOCKETS_TO_THREADS */ + +static char gg_base64_charset[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* + * gg_base64_encode() + * + * zapisuje ci±g znaków w base64. + * + * - buf - ci±g znaków. + * + * zaalokowany bufor. + */ +char *gg_base64_encode(const char *buf) +{ + char *out, *res; + unsigned int i = 0, j = 0, k = 0, len = strlen(buf); + + res = out = malloc((len / 3 + 1) * 4 + 2); + + if (!res) + return NULL; + + while (j <= len) { + switch (i % 4) { + case 0: + k = (buf[j] & 252) >> 2; + break; + case 1: + if (j < len) + k = ((buf[j] & 3) << 4) | ((buf[j + 1] & 240) >> 4); + else + k = (buf[j] & 3) << 4; + + j++; + break; + case 2: + if (j < len) + k = ((buf[j] & 15) << 2) | ((buf[j + 1] & 192) >> 6); + else + k = (buf[j] & 15) << 2; + + j++; + break; + case 3: + k = buf[j++] & 63; + break; + } + *out++ = gg_base64_charset[k]; + i++; + } + + if (i % 4) + for (j = 0; j < 4 - (i % 4); j++, out++) + *out = '='; + + *out = 0; + + return res; +} + +/* + * gg_base64_decode() + * + * dekoduje ci±g znaków z base64. + * + * - buf - ci±g znaków. + * + * zaalokowany bufor. + */ +char *gg_base64_decode(const char *buf) +{ + char *res, *save, *foo, val; + const char *end; + unsigned int index = 0; + + if (!buf) + return NULL; + + save = res = calloc(1, (strlen(buf) / 4 + 1) * 3 + 2); + + if (!save) + return NULL; + + end = buf + strlen(buf); + + while (*buf && buf < end) { + if (*buf == '\r' || *buf == '\n') { + buf++; + continue; + } + if (!(foo = strchr(gg_base64_charset, *buf))) + foo = gg_base64_charset; + val = (int)(foo - gg_base64_charset); + buf++; + switch (index) { + case 0: + *res |= val << 2; + break; + case 1: + *res++ |= val >> 4; + *res |= val << 4; + break; + case 2: + *res++ |= val >> 2; + *res |= val << 6; + break; + case 3: + *res++ |= val; + break; + } + index++; + index %= 4; + } + *res = 0; + + return save; +} + +/* + * gg_proxy_auth() // funkcja wewnêtrzna + * + * tworzy nag³ówek autoryzacji dla proxy. + * + * zaalokowany tekst lub NULL, je¶li proxy nie jest w³±czone lub nie wymaga + * autoryzacji. + */ +char *gg_proxy_auth() +{ + char *tmp, *enc, *out; + unsigned int tmp_size; + + if (!gg_proxy_enabled || !gg_proxy_username || !gg_proxy_password) + return NULL; + + if (!(tmp = malloc((tmp_size = strlen(gg_proxy_username) + strlen(gg_proxy_password) + 2)))) + return NULL; + + snprintf(tmp, tmp_size, "%s:%s", gg_proxy_username, gg_proxy_password); + + if (!(enc = gg_base64_encode(tmp))) { + free(tmp); + return NULL; + } + + free(tmp); + + if (!(out = malloc(strlen(enc) + 40))) { + free(enc); + return NULL; + } + + snprintf(out, strlen(enc) + 40, "Proxy-Authorization: Basic %s\r\n", enc); + + free(enc); + + return out; +} + +static uint32_t gg_crc32_table[256]; +static int gg_crc32_initialized = 0; + +/* + * gg_crc32_make_table() // funkcja wewnêtrzna + */ +static void gg_crc32_make_table() +{ + uint32_t h = 1; + unsigned int i, j; + + memset(gg_crc32_table, 0, sizeof(gg_crc32_table)); + + for (i = 128; i; i >>= 1) { + h = (h >> 1) ^ ((h & 1) ? 0xedb88320L : 0); + + for (j = 0; j < 256; j += 2 * i) + gg_crc32_table[i + j] = gg_crc32_table[j] ^ h; + } + + gg_crc32_initialized = 1; +} + +/* + * gg_crc32() + * + * wyznacza sumê kontroln± CRC32 danego bloku danych. + * + * - crc - suma kontrola poprzedniego bloku danych lub 0 je¶li pierwszy + * - buf - bufor danych + * - size - ilo¶æ danych + * + * suma kontrolna CRC32. + */ +uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len) +{ + if (!gg_crc32_initialized) + gg_crc32_make_table(); + + if (!buf || len < 0) + return crc; + + crc ^= 0xffffffffL; + + while (len--) + crc = (crc >> 8) ^ gg_crc32_table[(crc ^ *buf++) & 0xff]; + + return crc ^ 0xffffffffL; +} + + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/compat.h b/kopete/protocols/gadu/libgadu/compat.h new file mode 100644 index 00000000..715fcb3a --- /dev/null +++ b/kopete/protocols/gadu/libgadu/compat.h @@ -0,0 +1,29 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski + * Robert J. Wo¼ny + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef __COMPAT_H +#define __COMPAT_H + +#ifdef sun +# define INADDR_NONE ((in_addr_t) 0xffffffff) +#endif + +#endif diff --git a/kopete/protocols/gadu/libgadu/dcc.c b/kopete/protocols/gadu/libgadu/dcc.c new file mode 100644 index 00000000..085d9d01 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/dcc.c @@ -0,0 +1,1298 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski + * Tomasz Chiliñski + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include +#include +#include +#ifdef sun +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include "libgadu.h" + +#ifndef GG_DEBUG_DISABLE +/* + * gg_dcc_debug_data() // funkcja wewnêtrzna + * + * wy¶wietla zrzut pakietu w hexie. + * + * - prefix - prefiks zrzutu pakietu + * - fd - deskryptor gniazda + * - buf - bufor z danymi + * - size - rozmiar danych + */ +static void gg_dcc_debug_data(const char *prefix, int fd, const void *buf, unsigned int size) +{ + unsigned int i; + + gg_debug(GG_DEBUG_MISC, "++ gg_dcc %s (fd=%d,len=%d)", prefix, fd, size); + + for (i = 0; i < size; i++) + gg_debug(GG_DEBUG_MISC, " %.2x", ((unsigned char*) buf)[i]); + + gg_debug(GG_DEBUG_MISC, "\n"); +} +#else +#define gg_dcc_debug_data(a,b,c,d) do { } while (0) +#endif + +/* + * gg_dcc_request() + * + * wysy³a informacjê o tym, ¿e dany klient powinien siê z nami po³±czyæ. + * wykorzystywane, kiedy druga strona, której chcemy co¶ wys³aæ jest za + * maskarad±. + * + * - sess - struktura opisuj±ca sesjê GG + * - uin - numerek odbiorcy + * + * patrz gg_send_message_ctcp(). + */ +int gg_dcc_request(struct gg_session *sess, uin_t uin) +{ + return gg_send_message_ctcp(sess, GG_CLASS_CTCP, uin, "\002", 1); +} + +/* + * gg_dcc_fill_filetime() // funkcja wewnêtrzna + * + * zamienia czas w postaci unixowej na windowsowy. + * + * - unix - czas w postaci unixowej + * - filetime - czas w postaci windowsowej + */ +static void gg_dcc_fill_filetime(uint32_t ut, uint32_t *ft) +{ +#ifdef __GG_LIBGADU_HAVE_LONG_LONG + unsigned long long tmp; + + tmp = ut; + tmp += 11644473600LL; + tmp *= 10000000LL; + +#ifndef __GG_LIBGADU_BIGENDIAN + ft[0] = (uint32_t) tmp; + ft[1] = (uint32_t) (tmp >> 32); +#else + ft[0] = gg_fix32((uint32_t) (tmp >> 32)); + ft[1] = gg_fix32((uint32_t) tmp); +#endif + +#endif +} + +/* + * gg_dcc_fill_file_info() + * + * wype³nia pola struct gg_dcc niezbêdne do wys³ania pliku. + * + * - d - struktura opisuj±ca po³±czenie DCC + * - filename - nazwa pliku + * + * 0, -1. + */ +int gg_dcc_fill_file_info(struct gg_dcc *d, const char *filename) +{ + return gg_dcc_fill_file_info2(d, filename, filename); +} + +/* + * gg_dcc_fill_file_info2() + * + * wype³nia pola struct gg_dcc niezbêdne do wys³ania pliku. + * + * - d - struktura opisuj±ca po³±czenie DCC + * - filename - nazwa pliku + * - local_filename - nazwa na lokalnym systemie plików + * + * 0, -1. + */ +int gg_dcc_fill_file_info2(struct gg_dcc *d, const char *filename, const char *local_filename) +{ + struct stat st; + const char *name, *ext, *p; + unsigned char *q; + int i, j; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_fill_file_info2(%p, \"%s\", \"%s\");\n", d, filename, local_filename); + + if (!d || d->type != GG_SESSION_DCC_SEND) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() invalid arguments\n"); + errno = EINVAL; + return -1; + } + + if (stat(local_filename, &st) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() stat() failed (%s)\n", strerror(errno)); + return -1; + } + + if ((st.st_mode & S_IFDIR)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() that's a directory\n"); + errno = EINVAL; + return -1; + } + + if ((d->file_fd = open(local_filename, O_RDONLY)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() open() failed (%s)\n", strerror(errno)); + return -1; + } + + memset(&d->file_info, 0, sizeof(d->file_info)); + + if (!(st.st_mode & S_IWUSR)) + d->file_info.mode |= gg_fix32(GG_DCC_FILEATTR_READONLY); + + gg_dcc_fill_filetime(st.st_atime, d->file_info.atime); + gg_dcc_fill_filetime(st.st_mtime, d->file_info.mtime); + gg_dcc_fill_filetime(st.st_ctime, d->file_info.ctime); + + d->file_info.size = gg_fix32(st.st_size); + d->file_info.mode = gg_fix32(0x20); /* FILE_ATTRIBUTE_ARCHIVE */ + + if (!(name = strrchr(filename, '/'))) + name = filename; + else + name++; + + if (!(ext = strrchr(name, '.'))) + ext = name + strlen(name); + + for (i = 0, p = name; i < 8 && p < ext; i++, p++) + d->file_info.short_filename[i] = toupper(name[i]); + + if (i == 8 && p < ext) { + d->file_info.short_filename[6] = '~'; + d->file_info.short_filename[7] = '1'; + } + + if (strlen(ext) > 0) { + for (j = 0; *ext && j < 4; j++, p++) + d->file_info.short_filename[i + j] = toupper(ext[j]); + } + + for (q = d->file_info.short_filename; *q; q++) { + if (*q == 185) { + *q = 165; + } else if (*q == 230) { + *q = 198; + } else if (*q == 234) { + *q = 202; + } else if (*q == 179) { + *q = 163; + } else if (*q == 241) { + *q = 209; + } else if (*q == 243) { + *q = 211; + } else if (*q == 156) { + *q = 140; + } else if (*q == 159) { + *q = 143; + } else if (*q == 191) { + *q = 175; + } + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() short name \"%s\", dos name \"%s\"\n", name, d->file_info.short_filename); + strncpy(d->file_info.filename, name, sizeof(d->file_info.filename) - 1); + + return 0; +} + +/* + * gg_dcc_transfer() // funkcja wewnêtrzna + * + * inicjuje proces wymiany pliku z danym klientem. + * + * - ip - adres ip odbiorcy + * - port - port odbiorcy + * - my_uin - w³asny numer + * - peer_uin - numer obiorcy + * - type - rodzaj wymiany (GG_SESSION_DCC_SEND lub GG_SESSION_DCC_GET) + * + * zaalokowana struct gg_dcc lub NULL je¶li wyst±pi³ b³±d. + */ +static struct gg_dcc *gg_dcc_transfer(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin, int type) +{ + struct gg_dcc *d = NULL; + struct in_addr addr; + + addr.s_addr = ip; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_transfer(%s, %d, %ld, %ld, %s);\n", inet_ntoa(addr), port, my_uin, peer_uin, (type == GG_SESSION_DCC_SEND) ? "SEND" : "GET"); + + if (!ip || ip == INADDR_NONE || !port || !my_uin || !peer_uin) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + if (!(d = (void*) calloc(1, sizeof(*d)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() not enough memory\n"); + return NULL; + } + + d->check = GG_CHECK_WRITE; + d->state = GG_STATE_CONNECTING; + d->type = type; + d->timeout = GG_DEFAULT_TIMEOUT; + d->file_fd = -1; + d->active = 1; + d->fd = -1; + d->uin = my_uin; + d->peer_uin = peer_uin; + + if ((d->fd = gg_connect(&addr, port, 1)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() connection failed\n"); + free(d); + return NULL; + } + + return d; +} + +/* + * gg_dcc_get_file() + * + * inicjuje proces odbierania pliku od danego klienta, gdy ten wys³a³ do + * nas ¿±danie po³±czenia. + * + * - ip - adres ip odbiorcy + * - port - port odbiorcy + * - my_uin - w³asny numer + * - peer_uin - numer obiorcy + * + * zaalokowana struct gg_dcc lub NULL je¶li wyst±pi³ b³±d. + */ +struct gg_dcc *gg_dcc_get_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin) +{ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_get_file() handing over to gg_dcc_transfer()\n"); + + return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_GET); +} + +/* + * gg_dcc_send_file() + * + * inicjuje proces wysy³ania pliku do danego klienta. + * + * - ip - adres ip odbiorcy + * - port - port odbiorcy + * - my_uin - w³asny numer + * - peer_uin - numer obiorcy + * + * zaalokowana struct gg_dcc lub NULL je¶li wyst±pi³ b³±d. + */ +struct gg_dcc *gg_dcc_send_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin) +{ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_send_file() handing over to gg_dcc_transfer()\n"); + + return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_SEND); +} + +/* + * gg_dcc_voice_chat() + * + * próbuje nawi±zaæ po³±czenie g³osowe. + * + * - ip - adres ip odbiorcy + * - port - port odbiorcy + * - my_uin - w³asny numer + * - peer_uin - numer obiorcy + * + * zaalokowana struct gg_dcc lub NULL je¶li wyst±pi³ b³±d. + */ +struct gg_dcc *gg_dcc_voice_chat(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin) +{ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_chat() handing over to gg_dcc_transfer()\n"); + + return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_VOICE); +} + +/* + * gg_dcc_set_type() + * + * po zdarzeniu GG_EVENT_DCC_CALLBACK nale¿y ustawiæ typ po³±czenia za + * pomoc± tej funkcji. + * + * - d - struktura opisuj±ca po³±czenie + * - type - typ po³±czenia (GG_SESSION_DCC_SEND lub GG_SESSION_DCC_VOICE) + */ +void gg_dcc_set_type(struct gg_dcc *d, int type) +{ + d->type = type; + d->state = (type == GG_SESSION_DCC_SEND) ? GG_STATE_SENDING_FILE_INFO : GG_STATE_SENDING_VOICE_REQUEST; +} + +/* + * gg_dcc_callback() // funkcja wewnêtrzna + * + * wywo³ywana z struct gg_dcc->callback, odpala gg_dcc_watch_fd i umieszcza + * rezultat w struct gg_dcc->event. + * + * - d - structura opisuj±ca po³±czenie + * + * 0, -1. + */ +static int gg_dcc_callback(struct gg_dcc *d) +{ + struct gg_event *e = gg_dcc_watch_fd(d); + + d->event = e; + + return (e != NULL) ? 0 : -1; +} + +/* + * gg_dcc_socket_create() + * + * tworzy gniazdo dla bezpo¶redniej komunikacji miêdzy klientami. + * + * - uin - w³asny numer + * - port - preferowany port, je¶li równy 0 lub -1, próbuje domy¶lnego + * + * zaalokowana struct gg_dcc, któr± po¼niej nale¿y zwolniæ funkcj± + * gg_dcc_free(), albo NULL je¶li wyst±pi³ b³±d. + */ +struct gg_dcc *gg_dcc_socket_create(uin_t uin, uint16_t port) +{ + struct gg_dcc *c; + struct sockaddr_in sin; + int sock, bound = 0, errno2; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_create_dcc_socket(%d, %d);\n", uin, port); + + if (!uin) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() can't create socket (%s)\n", strerror(errno)); + return NULL; + } + + if (!port) + port = GG_DEFAULT_DCC_PORT; + + while (!bound) { + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(port); + + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() trying port %d\n", port); + if (!bind(sock, (struct sockaddr*) &sin, sizeof(sin))) + bound = 1; + else { + if (++port == 65535) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() no free port found\n"); + close(sock); + return NULL; + } + } + } + + if (listen(sock, 10)) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() unable to listen (%s)\n", strerror(errno)); + errno2 = errno; + close(sock); + errno = errno2; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() bound to port %d\n", port); + + if (!(c = malloc(sizeof(*c)))) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() not enough memory for struct\n"); + close(sock); + return NULL; + } + memset(c, 0, sizeof(*c)); + + c->port = c->id = port; + c->fd = sock; + c->type = GG_SESSION_DCC_SOCKET; + c->uin = uin; + c->timeout = -1; + c->state = GG_STATE_LISTENING; + c->check = GG_CHECK_READ; + c->callback = gg_dcc_callback; + c->destroy = gg_dcc_free; + + return c; +} + +/* + * gg_dcc_voice_send() + * + * wysy³a ramkê danych dla rozmowy g³osowej. + * + * - d - struktura opisuj±ca po³±czenie dcc + * - buf - bufor z danymi + * - length - rozmiar ramki + * + * 0, -1. + */ +int gg_dcc_voice_send(struct gg_dcc *d, char *buf, int length) +{ + struct packet_s { + uint8_t type; + uint32_t length; + } GG_PACKED; + struct packet_s packet; + + gg_debug(GG_DEBUG_FUNCTION, "++ gg_dcc_voice_send(%p, %p, %d);\n", d, buf, length); + if (!d || !buf || length < 0 || d->type != GG_SESSION_DCC_VOICE) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() invalid argument\n"); + errno = EINVAL; + return -1; + } + + packet.type = 0x03; /* XXX */ + packet.length = gg_fix32(length); + + if (write(d->fd, &packet, sizeof(packet)) < (signed)sizeof(packet)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() write() failed\n"); + return -1; + } + gg_dcc_debug_data("write", d->fd, &packet, sizeof(packet)); + + if (write(d->fd, buf, length) < length) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() write() failed\n"); + return -1; + } + gg_dcc_debug_data("write", d->fd, buf, length); + + return 0; +} + +#define gg_read(fd, buf, size) \ +{ \ + int tmp = read(fd, buf, size); \ + \ + if (tmp < (int) size) { \ + if (tmp == -1) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); \ + } else if (tmp == 0) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed, connection broken\n"); \ + } else { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (%d bytes, %d needed)\n", tmp, size); \ + } \ + e->type = GG_EVENT_DCC_ERROR; \ + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; \ + return e; \ + } \ + gg_dcc_debug_data("read", fd, buf, size); \ +} + +#define gg_write(fd, buf, size) \ +{ \ + int tmp; \ + gg_dcc_debug_data("write", fd, buf, size); \ + tmp = write(fd, buf, size); \ + if (tmp < (int) size) { \ + if (tmp == -1) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (errno=%d, %s)\n", errno, strerror(errno)); \ + } else { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%d needed, %d done)\n", size, tmp); \ + } \ + e->type = GG_EVENT_DCC_ERROR; \ + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; \ + return e; \ + } \ +} + +/* + * gg_dcc_watch_fd() + * + * funkcja, któr± nale¿y wywo³aæ, gdy co¶ siê zmieni na gg_dcc->fd. + * + * - h - struktura zwrócona przez gg_create_dcc_socket() + * + * zaalokowana struct gg_event lub NULL, je¶li zabrak³o pamiêci na ni±. + */ +struct gg_event *gg_dcc_watch_fd(struct gg_dcc *h) +{ + struct gg_event *e; + int foo; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_watch_fd(%p);\n", h); + + if (!h || (h->type != GG_SESSION_DCC && h->type != GG_SESSION_DCC_SOCKET && h->type != GG_SESSION_DCC_SEND && h->type != GG_SESSION_DCC_GET && h->type != GG_SESSION_DCC_VOICE)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() invalid argument\n"); + errno = EINVAL; + return NULL; + } + + if (!(e = (void*) calloc(1, sizeof(*e)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() not enough memory\n"); + return NULL; + } + + e->type = GG_EVENT_NONE; + + if (h->type == GG_SESSION_DCC_SOCKET) { + struct sockaddr_in sin; + struct gg_dcc *c; + int fd, sin_len = sizeof(sin), one = 1; + + if ((fd = accept(h->fd, (struct sockaddr*) &sin, &sin_len)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() can't accept() new connection (errno=%d, %s)\n", errno, strerror(errno)); + return e; + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() new direct connection from %s:%d\n", inet_ntoa(sin.sin_addr), htons(sin.sin_port)); + +#ifdef FIONBIO + if (ioctl(fd, FIONBIO, &one) == -1) { +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() can't set nonblocking (errno=%d, %s)\n", errno, strerror(errno)); + close(fd); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + if (!(c = (void*) calloc(1, sizeof(*c)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() not enough memory for client data\n"); + + free(e); + close(fd); + return NULL; + } + + c->fd = fd; + c->check = GG_CHECK_READ; + c->state = GG_STATE_READING_UIN_1; + c->type = GG_SESSION_DCC; + c->timeout = GG_DEFAULT_TIMEOUT; + c->file_fd = -1; + c->remote_addr = sin.sin_addr.s_addr; + c->remote_port = ntohs(sin.sin_port); + + e->type = GG_EVENT_DCC_NEW; + e->event.dcc_new = c; + + return e; + } else { + struct gg_dcc_tiny_packet tiny; + struct gg_dcc_small_packet small; + struct gg_dcc_big_packet big; + int size, tmp, res, res_size = sizeof(res); + unsigned int utmp; + char buf[1024], ack[] = "UDAG"; + + struct gg_dcc_file_info_packet { + struct gg_dcc_big_packet big; + struct gg_file_info file_info; + } GG_PACKED; + struct gg_dcc_file_info_packet file_info_packet; + + switch (h->state) { + case GG_STATE_READING_UIN_1: + case GG_STATE_READING_UIN_2: + { + uin_t uin; + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_READING_UIN_%d\n", (h->state == GG_STATE_READING_UIN_1) ? 1 : 2); + + gg_read(h->fd, &uin, sizeof(uin)); + + if (h->state == GG_STATE_READING_UIN_1) { + h->state = GG_STATE_READING_UIN_2; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->peer_uin = gg_fix32(uin); + } else { + h->state = GG_STATE_SENDING_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->uin = gg_fix32(uin); + e->type = GG_EVENT_DCC_CLIENT_ACCEPT; + } + + return e; + } + + case GG_STATE_SENDING_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_SENDING_ACK\n"); + + gg_write(h->fd, ack, 4); + + h->state = GG_STATE_READING_TYPE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_READING_TYPE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_TYPE\n"); + + gg_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + switch (small.type) { + case 0x0003: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() callback\n"); + h->type = GG_SESSION_DCC_SEND; + h->state = GG_STATE_SENDING_FILE_INFO; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_CALLBACK; + + break; + + case 0x0002: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() dialin\n"); + h->type = GG_SESSION_DCC_GET; + h->state = GG_STATE_READING_REQUEST; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->incoming = 1; + + break; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown dcc type (%.4x) from %ld\n", small.type, h->peer_uin); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_REQUEST\n"); + + gg_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + switch (small.type) { + case 0x0001: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() file transfer request\n"); + h->state = GG_STATE_READING_FILE_INFO; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + + case 0x0003: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() voice chat request\n"); + h->state = GG_STATE_SENDING_VOICE_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DCC_TIMEOUT_VOICE_ACK; + h->type = GG_SESSION_DCC_VOICE; + e->type = GG_EVENT_DCC_NEED_VOICE_ACK; + + break; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown dcc request (%.4x) from %ld\n", small.type, h->peer_uin); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_FILE_INFO: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_INFO\n"); + + gg_read(h->fd, &file_info_packet, sizeof(file_info_packet)); + + memcpy(&h->file_info, &file_info_packet.file_info, sizeof(h->file_info)); + + h->file_info.mode = gg_fix32(h->file_info.mode); + h->file_info.size = gg_fix32(h->file_info.size); + + h->state = GG_STATE_SENDING_FILE_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DCC_TIMEOUT_FILE_ACK; + + e->type = GG_EVENT_DCC_NEED_FILE_ACK; + + return e; + + case GG_STATE_SENDING_FILE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_ACK\n"); + + big.type = gg_fix32(0x0006); /* XXX */ + big.dunno1 = gg_fix32(h->offset); + big.dunno2 = 0; + + gg_write(h->fd, &big, sizeof(big)); + + h->state = GG_STATE_READING_FILE_HEADER; + h->chunk_size = sizeof(big); + h->chunk_offset = 0; + if (!(h->chunk_buf = malloc(sizeof(big)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n"); + free(e); + return NULL; + } + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_SENDING_VOICE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_VOICE_ACK\n"); + + tiny.type = 0x01; /* XXX */ + + gg_write(h->fd, &tiny, sizeof(tiny)); + + h->state = GG_STATE_READING_VOICE_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + h->offset = 0; + + return e; + + case GG_STATE_READING_FILE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_HEADER\n"); + + tmp = read(h->fd, h->chunk_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + gg_dcc_debug_data("read", h->fd, h->chunk_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + + h->chunk_offset += tmp; + + if (h->chunk_offset < h->chunk_size) + return e; + + memcpy(&big, h->chunk_buf, sizeof(big)); + free(h->chunk_buf); + h->chunk_buf = NULL; + + big.type = gg_fix32(big.type); + h->chunk_size = gg_fix32(big.dunno1); + h->chunk_offset = 0; + + if (big.type == 0x0005) { /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() transfer refused\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_REFUSED; + return e; + } + + if (h->chunk_size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() empty chunk, EOF\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->state = GG_STATE_GETTING_FILE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + + return e; + + case GG_STATE_READING_VOICE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_HEADER\n"); + + gg_read(h->fd, &tiny, sizeof(tiny)); + + switch (tiny.type) { + case 0x03: /* XXX */ + h->state = GG_STATE_READING_VOICE_SIZE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + break; + case 0x04: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() peer breaking connection\n"); + /* XXX zwracaæ odpowiedni event */ + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown request (%.2x)\n", tiny.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_VOICE_SIZE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_SIZE\n"); + + gg_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + if (small.type < 16 || small.type > sizeof(buf)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() invalid voice frame size (%d)\n", small.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + + return e; + } + + h->chunk_size = small.type; + h->chunk_offset = 0; + + if (!(h->voice_buf = malloc(h->chunk_size))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory for voice frame\n"); + free(e); + return NULL; + } + + h->state = GG_STATE_READING_VOICE_DATA; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_READING_VOICE_DATA: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_DATA\n"); + + tmp = read(h->fd, h->voice_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + if (tmp < 1) { + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); + } else { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed, connection broken\n"); + } + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + gg_dcc_debug_data("read", h->fd, h->voice_buf + h->chunk_offset, tmp); + + h->chunk_offset += tmp; + + if (h->chunk_offset >= h->chunk_size) { + e->type = GG_EVENT_DCC_VOICE_DATA; + e->event.dcc_voice_data.data = h->voice_buf; + e->event.dcc_voice_data.length = h->chunk_size; + h->state = GG_STATE_READING_VOICE_HEADER; + h->voice_buf = NULL; + } + + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_CONNECTING: + { + uin_t uins[2]; + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_CONNECTING\n"); + + res = 0; + if ((foo = getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &res, &res_size)) || res) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() connection failed (fd=%d,errno=%d(%s),foo=%d,res=%d(%s))\n", h->fd, errno, strerror(errno), foo, res, strerror(res)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() connected, sending uins\n"); + + uins[0] = gg_fix32(h->uin); + uins[1] = gg_fix32(h->peer_uin); + + gg_write(h->fd, uins, sizeof(uins)); + + h->state = GG_STATE_READING_ACK; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + } + + case GG_STATE_READING_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_ACK\n"); + + gg_read(h->fd, buf, 4); + + if (strncmp(buf, ack, 4)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() did't get ack\n"); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->state = GG_STATE_SENDING_REQUEST; + + return e; + + case GG_STATE_SENDING_VOICE_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_VOICE_REQUEST\n"); + + small.type = gg_fix32(0x0003); + + gg_write(h->fd, &small, sizeof(small)); + + h->state = GG_STATE_READING_VOICE_ACK; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_SENDING_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_REQUEST\n"); + + small.type = (h->type == GG_SESSION_DCC_GET) ? gg_fix32(0x0003) : gg_fix32(0x0002); /* XXX */ + + gg_write(h->fd, &small, sizeof(small)); + + switch (h->type) { + case GG_SESSION_DCC_GET: + h->state = GG_STATE_READING_REQUEST; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + + case GG_SESSION_DCC_SEND: + h->state = GG_STATE_SENDING_FILE_INFO; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + if (h->file_fd == -1) + e->type = GG_EVENT_DCC_NEED_FILE_INFO; + break; + + case GG_SESSION_DCC_VOICE: + h->state = GG_STATE_SENDING_VOICE_REQUEST; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + return e; + + case GG_STATE_SENDING_FILE_INFO: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_INFO\n"); + + if (h->file_fd == -1) { + e->type = GG_EVENT_DCC_NEED_FILE_INFO; + return e; + } + + small.type = gg_fix32(0x0001); /* XXX */ + + gg_write(h->fd, &small, sizeof(small)); + + file_info_packet.big.type = gg_fix32(0x0003); /* XXX */ + file_info_packet.big.dunno1 = 0; + file_info_packet.big.dunno2 = 0; + + memcpy(&file_info_packet.file_info, &h->file_info, sizeof(h->file_info)); + + /* zostaj± teraz u nas, wiêc odwracamy z powrotem */ + h->file_info.size = gg_fix32(h->file_info.size); + h->file_info.mode = gg_fix32(h->file_info.mode); + + gg_write(h->fd, &file_info_packet, sizeof(file_info_packet)); + + h->state = GG_STATE_READING_FILE_ACK; + h->check = GG_CHECK_READ; + h->timeout = GG_DCC_TIMEOUT_FILE_ACK; + + return e; + + case GG_STATE_READING_FILE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_ACK\n"); + + gg_read(h->fd, &big, sizeof(big)); + + /* XXX sprawdzaæ wynik */ + h->offset = gg_fix32(big.dunno1); + + h->state = GG_STATE_SENDING_FILE_HEADER; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_ACK; + + return e; + + case GG_STATE_READING_VOICE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_ACK\n"); + + gg_read(h->fd, &tiny, sizeof(tiny)); + + if (tiny.type != 0x01) { + gg_debug(GG_DEBUG_MISC, "// invalid reply (%.2x), connection refused\n", tiny.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_REFUSED; + return e; + } + + h->state = GG_STATE_READING_VOICE_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_ACK; + + return e; + + case GG_STATE_SENDING_FILE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_HEADER\n"); + + h->chunk_offset = 0; + + if ((h->chunk_size = h->file_info.size - h->offset) > 4096) { + h->chunk_size = 4096; + big.type = gg_fix32(0x0003); /* XXX */ + } else + big.type = gg_fix32(0x0002); /* XXX */ + + big.dunno1 = gg_fix32(h->chunk_size); + big.dunno2 = 0; + + gg_write(h->fd, &big, sizeof(big)); + + h->state = GG_STATE_SENDING_FILE; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + + return e; + + case GG_STATE_SENDING_FILE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE\n"); + + if ((utmp = h->chunk_size - h->chunk_offset) > sizeof(buf)) + utmp = sizeof(buf); + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() offset=%d, size=%d\n", h->offset, h->file_info.size); + + /* koniec pliku? */ + if (h->file_info.size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof on empty file\n"); + e->type = GG_EVENT_DCC_DONE; + + return e; + } + + lseek(h->file_fd, h->offset, SEEK_SET); + + size = read(h->file_fd, buf, utmp); + + /* b³±d */ + if (size == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed. (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_FILE; + + return e; + } + + /* koniec pliku? */ + if (size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_EOF; + + return e; + } + + /* je¶li wczytali¶my wiêcej, utnijmy. */ + if (h->offset + size > h->file_info.size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() too much (read=%d, ofs=%d, size=%d)\n", size, h->offset, h->file_info.size); + size = h->file_info.size - h->offset; + + if (size < 1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() reached EOF after cutting\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + } + + tmp = write(h->fd, buf, size); + + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%s)\n", strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + h->offset += size; + + if (h->offset >= h->file_info.size) { + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->chunk_offset += size; + + if (h->chunk_offset >= h->chunk_size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() chunk finished\n"); + h->state = GG_STATE_SENDING_FILE_HEADER; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + h->state = GG_STATE_SENDING_FILE; + h->timeout = GG_DCC_TIMEOUT_SEND; + } + + h->check = GG_CHECK_WRITE; + + return e; + + case GG_STATE_GETTING_FILE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_GETTING_FILE\n"); + + if ((utmp = h->chunk_size - h->chunk_offset) > sizeof(buf)) + utmp = sizeof(buf); + + size = read(h->fd, buf, utmp); + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() ofs=%d, size=%d, read()=%d\n", h->offset, h->file_info.size, size); + + /* b³±d */ + if (size == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed. (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + + return e; + } + + /* koniec? */ + if (size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_EOF; + + return e; + } + + tmp = write(h->file_fd, buf, size); + + if (tmp == -1 || tmp < size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%d:fd=%d:res=%d:%s)\n", tmp, h->file_fd, size, strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + h->offset += size; + + if (h->offset >= h->file_info.size) { + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->chunk_offset += size; + + if (h->chunk_offset >= h->chunk_size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() chunk finished\n"); + h->state = GG_STATE_READING_FILE_HEADER; + h->timeout = GG_DEFAULT_TIMEOUT; + h->chunk_offset = 0; + h->chunk_size = sizeof(big); + if (!(h->chunk_buf = malloc(sizeof(big)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n"); + free(e); + return NULL; + } + } else { + h->state = GG_STATE_GETTING_FILE; + h->timeout = GG_DCC_TIMEOUT_GET; + } + + h->check = GG_CHECK_READ; + + return e; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_???\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + + return e; + } + } + + return e; +} + +#undef gg_read +#undef gg_write + +/* + * gg_dcc_free() + * + * zwalnia pamiêæ po strukturze po³±czenia dcc. + * + * - d - zwalniana struktura + */ +void gg_dcc_free(struct gg_dcc *d) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_free(%p);\n", d); + + if (!d) + return; + + if (d->fd != -1) + close(d->fd); + + if (d->chunk_buf) { + free(d->chunk_buf); + d->chunk_buf = NULL; + } + + free(d); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/events.c b/kopete/protocols/gadu/libgadu/events.c new file mode 100644 index 00000000..97b84912 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/events.c @@ -0,0 +1,1580 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski + * Robert J. Wo¼ny + * Arkadiusz Mi¶kiewicz + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "libgadu-config.h" + +#include +#ifdef __GG_LIBGADU_HAVE_PTHREAD +# include +#endif +#include +#include +#include +#include +#include +#ifdef __GG_LIBGADU_HAVE_OPENSSL +# include +# include +#endif + +#include "compat.h" +#include "libgadu.h" + +/* + * gg_event_free() + * + * zwalnia pamiêæ zajmowan± przez informacjê o zdarzeniu. + * + * - e - wska¼nik do informacji o zdarzeniu + */ +void gg_event_free(struct gg_event *e) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_event_free(%p);\n", e); + + if (!e) + return; + + switch (e->type) { + case GG_EVENT_MSG: + free(e->event.msg.message); + free(e->event.msg.formats); + free(e->event.msg.recipients); + break; + + case GG_EVENT_NOTIFY: + free(e->event.notify); + break; + + case GG_EVENT_NOTIFY60: + { + int i; + + for (i = 0; e->event.notify60[i].uin; i++) + free(e->event.notify60[i].descr); + + free(e->event.notify60); + + break; + } + + case GG_EVENT_STATUS60: + free(e->event.status60.descr); + break; + + case GG_EVENT_STATUS: + free(e->event.status.descr); + break; + + case GG_EVENT_NOTIFY_DESCR: + free(e->event.notify_descr.notify); + free(e->event.notify_descr.descr); + break; + + case GG_EVENT_DCC_VOICE_DATA: + free(e->event.dcc_voice_data.data); + break; + + case GG_EVENT_PUBDIR50_SEARCH_REPLY: + case GG_EVENT_PUBDIR50_READ: + case GG_EVENT_PUBDIR50_WRITE: + gg_pubdir50_free(e->event.pubdir50); + break; + + case GG_EVENT_USERLIST: + free(e->event.userlist.reply); + break; + + case GG_EVENT_IMAGE_REPLY: + free(e->event.image_reply.filename); + free(e->event.image_reply.image); + break; + } + + free(e); +} + +/* + * gg_image_queue_remove() + * + * usuwa z kolejki dany wpis. + * + * - s - sesja + * - q - kolejka + * - freeq - czy zwolniæ kolejkê + * + * 0/-1 + */ +int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq) +{ + if (!s || !q) { + errno = EFAULT; + return -1; + } + + if (s->images == q) + s->images = q->next; + else { + struct gg_image_queue *qq; + + for (qq = s->images; qq; qq = qq->next) { + if (qq->next == q) { + qq->next = q->next; + break; + } + } + } + + if (freeq) { + free(q->image); + free(q->filename); + free(q); + } + + return 0; +} + +/* + * gg_image_queue_parse() // funkcja wewnêtrzna + * + * parsuje przychodz±cy pakiet z obrazkiem. + * + * - e - opis zdarzenia + * - + */ +static void gg_image_queue_parse(struct gg_event *e, char *p, unsigned int len, struct gg_session *sess, uin_t sender) +{ + struct gg_msg_image_reply *i = (void*) p; + struct gg_image_queue *q, *qq; + + if (!p || !sess || !e) { + errno = EFAULT; + return; + } + + /* znajd¼ dany obrazek w kolejce danej sesji */ + + for (qq = sess->images, q = NULL; qq; qq = qq->next) { + if (sender == qq->sender && i->size == qq->size && i->crc32 == qq->crc32) { + q = qq; + break; + } + } + + if (!q) { + gg_debug(GG_DEBUG_MISC, "// gg_image_queue_parse() unknown image from %d, size=%d, crc32=%.8x\n", sender, i->size, i->crc32); + return; + } + + if (p[0] == 0x05) { + int i, ok = 0; + + q->done = 0; + + len -= sizeof(struct gg_msg_image_reply); + p += sizeof(struct gg_msg_image_reply); + + /* sprawd¼, czy mamy tekst zakoñczony \0 */ + + for (i = 0; i < len; i++) { + if (!p[i]) { + ok = 1; + break; + } + } + + if (!ok) { + gg_debug(GG_DEBUG_MISC, "// gg_image_queue_parse() malformed packet from %d, unlimited filename\n", sender); + return; + } + + if (!(q->filename = strdup(p))) { + gg_debug(GG_DEBUG_MISC, "// gg_image_queue_parse() not enough memory for filename\n"); + return; + } + + len -= strlen(p) + 1; + p += strlen(p) + 1; + } else { + len -= sizeof(struct gg_msg_image_reply); + p += sizeof(struct gg_msg_image_reply); + } + + if (q->done + len > q->size) + len = q->size - q->done; + + memcpy(q->image + q->done, p, len); + q->done += len; + + /* je¶li skoñczono odbieraæ obrazek, wygeneruj zdarzenie */ + + if (q->done >= q->size) { + e->type = GG_EVENT_IMAGE_REPLY; + e->event.image_reply.sender = sender; + e->event.image_reply.size = q->size; + e->event.image_reply.crc32 = q->crc32; + e->event.image_reply.filename = q->filename; + e->event.image_reply.image = q->image; + + gg_image_queue_remove(sess, q, 0); + + free(q); + } +} + +/* + * gg_handle_recv_msg() // funkcja wewnêtrzna + * + * obs³uguje pakiet z przychodz±c± wiadomo¶ci±, rozbijaj±c go na dodatkowe + * struktury (konferencje, kolorki) w razie potrzeby. + * + * - h - nag³ówek pakietu + * - e - opis zdarzenia + * + * 0, -1. + */ +static int gg_handle_recv_msg(struct gg_header *h, struct gg_event *e, struct gg_session *sess) +{ + struct gg_recv_msg *r = (struct gg_recv_msg*) ((char*) h + sizeof(struct gg_header)); + char *p, *packet_end = (char*) r + h->length; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_handle_recv_msg(%p, %p);\n", h, e); + + if (!r->seq && !r->msgclass) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() oops, silently ignoring the bait\n"); + e->type = GG_EVENT_NONE; + return 0; + } + + for (p = (char*) r + sizeof(*r); *p; p++) { + if (*p == 0x02 && p == packet_end - 1) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() received ctcp packet\n"); + break; + } + if (p >= packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() malformed packet, message out of bounds (0)\n"); + goto malformed; + } + } + + p++; + + /* przeanalizuj dodatkowe opcje */ + while (p < packet_end) { + switch (*p) { + case 0x01: /* konferencja */ + { + struct gg_msg_recipients *m = (void*) p; + uint32_t i, count; + + p += sizeof(*m); + + if (p > packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1)\n"); + goto malformed; + } + + count = gg_fix32(m->count); + + if (p + count * sizeof(uin_t) > packet_end || p + count * sizeof(uin_t) < p || count > 0xffff) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1.5)\n"); + goto malformed; + } + + if (!(e->event.msg.recipients = (void*) malloc(count * sizeof(uin_t)))) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for recipients data\n"); + goto fail; + } + + for (i = 0; i < count; i++, p += sizeof(uint32_t)) { + uint32_t u; + memcpy(&u, p, sizeof(uint32_t)); + e->event.msg.recipients[i] = gg_fix32(u); + } + + e->event.msg.recipients_count = count; + + break; + } + + case 0x02: /* richtext */ + { + uint16_t len; + char *buf; + + if (p + 3 > packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (2)\n"); + goto malformed; + } + + memcpy(&len, p + 1, sizeof(uint16_t)); + len = gg_fix16(len); + + if (!(buf = malloc(len))) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for richtext data\n"); + goto fail; + } + + p += 3; + + if (p + len > packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n"); + free(buf); + goto malformed; + } + + memcpy(buf, p, len); + + e->event.msg.formats = buf; + e->event.msg.formats_length = len; + + p += len; + + break; + } + + case 0x04: /* image_request */ + { + struct gg_msg_image_request *i = (void*) p; + + if (p + sizeof(*i) > packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n"); + goto malformed; + } + + e->event.image_request.sender = gg_fix32(r->sender); + e->event.image_request.size = gg_fix32(i->size); + e->event.image_request.crc32 = gg_fix32(i->crc32); + + e->type = GG_EVENT_IMAGE_REQUEST; + + return 0; + } + + case 0x05: /* image_reply */ + case 0x06: + { + struct gg_msg_image_reply *rep = (void*) p; + + if (p + sizeof(struct gg_msg_image_reply) == packet_end) { + + /* pusta odpowied¼ - klient po drugiej stronie nie ma ¿±danego obrazka */ + + e->type = GG_EVENT_IMAGE_REPLY; + e->event.image_reply.sender = gg_fix32(r->sender); + e->event.image_reply.size = 0; + e->event.image_reply.crc32 = gg_fix32(rep->crc32); + e->event.image_reply.filename = NULL; + e->event.image_reply.image = NULL; + return 0; + + } else if (p + sizeof(struct gg_msg_image_reply) + 1 > packet_end) { + + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (4)\n"); + goto malformed; + } + + rep->size = gg_fix32(rep->size); + rep->crc32 = gg_fix32(rep->crc32); + gg_image_queue_parse(e, p, (unsigned int)(packet_end - p), sess, gg_fix32(r->sender)); + + return 0; + } + + default: + { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() unknown payload 0x%.2x\n", *p); + p = packet_end; + } + } + } + + e->type = GG_EVENT_MSG; + e->event.msg.msgclass = gg_fix32(r->msgclass); + e->event.msg.sender = gg_fix32(r->sender); + e->event.msg.time = gg_fix32(r->time); + e->event.msg.message = strdup((char*) r + sizeof(*r)); + + return 0; + +malformed: + e->type = GG_EVENT_NONE; + + free(e->event.msg.recipients); + free(e->event.msg.formats); + + return 0; + +fail: + free(e->event.msg.recipients); + free(e->event.msg.formats); + return -1; +} + +/* + * gg_watch_fd_connected() // funkcja wewnêtrzna + * + * patrzy na gniazdo, odbiera pakiet i wype³nia strukturê zdarzenia. + * + * - sess - struktura opisuj±ca sesjê + * - e - opis zdarzenia + * + * 0, -1. + */ +static int gg_watch_fd_connected(struct gg_session *sess, struct gg_event *e) +{ + struct gg_header *h = NULL; + char *p; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_watch_fd_connected(%p, %p);\n", sess, e); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (!(h = gg_recv_packet(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + + p = (char*) h + sizeof(struct gg_header); + + switch (h->type) { + case GG_RECV_MSG: + { + if (h->length >= sizeof(struct gg_recv_msg)) + if (gg_handle_recv_msg(h, e, sess)) + goto fail; + + break; + } + + case GG_NOTIFY_REPLY: + { + struct gg_notify_reply *n = (void*) p; + unsigned int count, i; + char *tmp; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + if (h->length < sizeof(*n)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() incomplete packet\n"); + errno = EINVAL; + goto fail; + } + + if (gg_fix32(n->status) == GG_STATUS_BUSY_DESCR || gg_fix32(n->status) == GG_STATUS_NOT_AVAIL_DESCR || gg_fix32(n->status) == GG_STATUS_AVAIL_DESCR) { + e->type = GG_EVENT_NOTIFY_DESCR; + + if (!(e->event.notify_descr.notify = (void*) malloc(sizeof(*n) * 2))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + e->event.notify_descr.notify[1].uin = 0; + memcpy(e->event.notify_descr.notify, p, sizeof(*n)); + e->event.notify_descr.notify[0].uin = gg_fix32(e->event.notify_descr.notify[0].uin); + e->event.notify_descr.notify[0].status = gg_fix32(e->event.notify_descr.notify[0].status); + e->event.notify_descr.notify[0].remote_port = gg_fix16(e->event.notify_descr.notify[0].remote_port); + + count = h->length - sizeof(*n); + if (!(tmp = malloc(count + 1))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + memcpy(tmp, p + sizeof(*n), count); + tmp[count] = 0; + e->event.notify_descr.descr = tmp; + + } else { + e->type = GG_EVENT_NOTIFY; + + if (!(e->event.notify = (void*) malloc(h->length + 2 * sizeof(*n)))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + memcpy(e->event.notify, p, h->length); + count = h->length / sizeof(*n); + e->event.notify[count].uin = 0; + + for (i = 0; i < count; i++) { + e->event.notify[i].uin = gg_fix32(e->event.notify[i].uin); + e->event.notify[i].status = gg_fix32(e->event.notify[i].status); + e->event.notify[i].remote_port = gg_fix16(e->event.notify[i].remote_port); + } + } + + break; + } + + case GG_STATUS: + { + struct gg_status *s = (void*) p; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + if (h->length >= sizeof(*s)) { + e->type = GG_EVENT_STATUS; + memcpy(&e->event.status, p, sizeof(*s)); + e->event.status.uin = gg_fix32(e->event.status.uin); + e->event.status.status = gg_fix32(e->event.status.status); + if (h->length > sizeof(*s)) { + int len = h->length - sizeof(*s); + char *buf = malloc(len + 1); + if (buf) { + memcpy(buf, p + sizeof(*s), len); + buf[len] = 0; + } + e->event.status.descr = buf; + } else + e->event.status.descr = NULL; + } + + break; + } + + case GG_NOTIFY_REPLY60: + { + struct gg_notify_reply60 *n = (void*) p; + unsigned int length = h->length, i = 0; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + e->type = GG_EVENT_NOTIFY60; + e->event.notify60 = malloc(sizeof(*e->event.notify60)); + + if (!e->event.notify60) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + e->event.notify60[0].uin = 0; + + while (length >= sizeof(struct gg_notify_reply60)) { + uin_t uin = gg_fix32(n->uin); + char *tmp; + + e->event.notify60[i].uin = uin & 0x00ffffff; + e->event.notify60[i].status = n->status; + e->event.notify60[i].remote_ip = n->remote_ip; + e->event.notify60[i].remote_port = gg_fix16(n->remote_port); + e->event.notify60[i].version = n->version; + e->event.notify60[i].image_size = n->image_size; + e->event.notify60[i].descr = NULL; + e->event.notify60[i].time = 0; + + if (uin & 0x40000000) + e->event.notify60[i].version |= GG_HAS_AUDIO_MASK; + if (uin & 0x08000000) + e->event.notify60[i].version |= GG_ERA_OMNIX_MASK; + + if (GG_S_D(n->status)) { + unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply60)); + + if (descr_len < length) { + if (!(e->event.notify60[i].descr = malloc(descr_len + 1))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + memcpy(e->event.notify60[i].descr, (char*) n + sizeof(struct gg_notify_reply60) + 1, descr_len); + e->event.notify60[i].descr[descr_len] = 0; + + /* XXX czas */ + } + + length -= sizeof(struct gg_notify_reply60) + descr_len + 1; + n = (void*) ((char*) n + sizeof(struct gg_notify_reply60) + descr_len + 1); + } else { + length -= sizeof(struct gg_notify_reply60); + n = (void*) ((char*) n + sizeof(struct gg_notify_reply60)); + } + + if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + free(e->event.notify60); + goto fail; + } + + e->event.notify60 = (void*) tmp; + e->event.notify60[++i].uin = 0; + } + + break; + } + + case GG_STATUS60: + { + struct gg_status60 *s = (void*) p; + uint32_t uin; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + if (h->length < sizeof(*s)) + break; + + uin = gg_fix32(s->uin); + + e->type = GG_EVENT_STATUS60; + e->event.status60.uin = uin & 0x00ffffff; + e->event.status60.status = s->status; + e->event.status60.remote_ip = s->remote_ip; + e->event.status60.remote_port = gg_fix16(s->remote_port); + e->event.status60.version = s->version; + e->event.status60.image_size = s->image_size; + e->event.status60.descr = NULL; + e->event.status60.time = 0; + + if (uin & 0x40000000) + e->event.status60.version |= GG_HAS_AUDIO_MASK; + if (uin & 0x08000000) + e->event.status60.version |= GG_ERA_OMNIX_MASK; + + if (h->length > sizeof(*s)) { + int len = h->length - sizeof(*s); + char *buf = malloc(len + 1); + + if (buf) { + memcpy(buf, (char*) p + sizeof(*s), len); + buf[len] = 0; + } + + e->event.status60.descr = buf; + + if (len > 4 && p[h->length - 5] == 0) { + uint32_t t; + memcpy(&t, p + h->length - 4, sizeof(uint32_t)); + e->event.status60.time = gg_fix32(t); + } + } + + break; + } + + case GG_SEND_MSG_ACK: + { + struct gg_send_msg_ack *s = (void*) p; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a message ack\n"); + + if (h->length < sizeof(*s)) + break; + + e->type = GG_EVENT_ACK; + e->event.ack.status = gg_fix32(s->status); + e->event.ack.recipient = gg_fix32(s->recipient); + e->event.ack.seq = gg_fix32(s->seq); + + break; + } + + case GG_PONG: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a pong\n"); + + e->type = GG_EVENT_PONG; + sess->last_pong = time(NULL); + + break; + } + + case GG_DISCONNECTING: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection warning\n"); + e->type = GG_EVENT_DISCONNECT; + break; + } + + case GG_PUBDIR50_REPLY: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received pubdir/search reply\n"); + if (gg_pubdir50_handle_reply(e, p, h->length) == -1) + goto fail; + break; + } + + case GG_USERLIST_REPLY: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist reply\n"); + + if (h->length < 1) + break; + + /* je¶li odpowied¼ na eksport, wywo³aj zdarzenie tylko + * gdy otrzymano wszystkie odpowiedzi */ + if (p[0] == GG_USERLIST_PUT_REPLY || p[0] == GG_USERLIST_PUT_MORE_REPLY) { + if (--sess->userlist_blocks) + break; + + p[0] = GG_USERLIST_PUT_REPLY; + } + + if (h->length > 1) { + char *tmp; + unsigned int len = (sess->userlist_reply) ? strlen(sess->userlist_reply) : 0; + + gg_debug(GG_DEBUG_MISC, "userlist_reply=%p, len=%d\n", sess->userlist_reply, len); + + if (!(tmp = realloc(sess->userlist_reply, len + h->length))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for userlist reply\n"); + free(sess->userlist_reply); + sess->userlist_reply = NULL; + goto fail; + } + + sess->userlist_reply = tmp; + sess->userlist_reply[len + h->length - 1] = 0; + memcpy(sess->userlist_reply + len, p + 1, h->length - 1); + } + + if (p[0] == GG_USERLIST_GET_MORE_REPLY) + break; + + e->type = GG_EVENT_USERLIST; + e->event.userlist.type = p[0]; + e->event.userlist.reply = sess->userlist_reply; + sess->userlist_reply = NULL; + + break; + } + + default: + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received unknown packet 0x%.2x\n", h->type); + } + + free(h); + return 0; + +fail: + free(h); + return -1; +} + +/* + * gg_watch_fd() + * + * funkcja, któr± nale¿y wywo³aæ, gdy co¶ siê stanie z obserwowanym + * deskryptorem. zwraca klientowi informacjê o tym, co siê dzieje. + * + * - sess - opis sesji + * + * wska¼nik do struktury gg_event, któr± trzeba zwolniæ pó¼niej + * za pomoc± gg_event_free(). jesli rodzaj zdarzenia jest równy + * GG_EVENT_NONE, nale¿y je zignorowaæ. je¶li zwróci³o NULL, + * sta³o siê co¶ niedobrego -- albo zabrak³o pamiêci albo zerwa³o + * po³±czenie. + */ +struct gg_event *gg_watch_fd(struct gg_session *sess) +{ + struct gg_event *e; + int res = 0; + int port = 0; + int errno2 = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_watch_fd(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return NULL; + } + + if (!(e = (void*) calloc(1, sizeof(*e)))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for event data\n"); + return NULL; + } + + e->type = GG_EVENT_NONE; + + switch (sess->state) { + case GG_STATE_RESOLVING: + { + struct in_addr addr; + int failed = 0; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING\n"); + + if (read(sess->fd, &addr, sizeof(addr)) < (signed)sizeof(addr) || addr.s_addr == INADDR_NONE) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() resolving failed\n"); + failed = 1; + errno2 = errno; + } + + close(sess->fd); + sess->fd = -1; + +#ifndef __GG_LIBGADU_HAVE_PTHREAD + waitpid(sess->pid, NULL, 0); + sess->pid = -1; +#else + if (sess->resolver) { + pthread_cancel(*((pthread_t*) sess->resolver)); + free(sess->resolver); + sess->resolver = NULL; + } +#endif + + if (failed) { + errno = errno2; + goto fail_resolving; + } + + /* je¶li jeste¶my w resolverze i mamy ustawiony port + * proxy, znaczy, ¿e resolvowali¶my proxy. zatem + * wpiszmy jego adres. */ + if (sess->proxy_port) + sess->proxy_addr = addr.s_addr; + + /* zapiszmy sobie adres huba i adres serwera (do + * bezpo¶redniego po³±czenia, je¶li hub le¿y) + * z resolvera. */ + if (sess->proxy_addr && sess->proxy_port) + port = sess->proxy_port; + else { + sess->server_addr = sess->hub_addr = addr.s_addr; + port = GG_APPMSG_PORT; + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), port); + + /* ³±czymy siê albo z hubem, albo z proxy, zale¿nie + * od tego, co resolvowali¶my. */ + if ((sess->fd = gg_connect(&addr, port, sess->async)) == -1) { + /* je¶li w trybie asynchronicznym gg_connect() + * zwróci b³±d, nie ma sensu próbowaæ dalej. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno)); + goto fail_connecting; + } + + /* je¶li podano serwer i ³±czmy siê przez proxy, + * jest to bezpo¶rednie po³±czenie, inaczej jest + * do huba. */ + sess->state = (sess->proxy_addr && sess->proxy_port && sess->server_addr) ? GG_STATE_CONNECTING_GG : GG_STATE_CONNECTING_HUB; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + + case GG_STATE_CONNECTING_HUB: + { + char buf[1024], *client, *auth; + int res = 0, res_size = sizeof(res); + const char *host, *appmsg; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_HUB\n"); + + /* je¶li asynchroniczne, sprawdzamy, czy nie wyst±pi³ + * przypadkiem jaki¶ b³±d. */ + if (sess->async && (getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + /* no tak, nie uda³o siê po³±czyæ z proxy. nawet + * nie próbujemy dalej. */ + if (sess->proxy_addr && sess->proxy_port) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res)); + goto fail_connecting; + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to hub failed (errno=%d, %s), trying direct connection\n", res, strerror(res)); + close(sess->fd); + + if ((sess->fd = gg_connect(&sess->hub_addr, GG_DEFAULT_PORT, sess->async)) == -1) { + /* przy asynchronicznych, gg_connect() + * zwraca -1 przy b³êdach socket(), + * ioctl(), braku routingu itd. dlatego + * nawet nie próbujemy dalej. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() direct connection failed (errno=%d, %s), critical\n", errno, strerror(errno)); + goto fail_connecting; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connected to hub, sending query\n"); + + if (!(client = gg_urlencode((sess->client_version) ? sess->client_version : GG_DEFAULT_CLIENT_VERSION))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() out of memory for client version\n"); + goto fail_connecting; + } + + if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) + host = "http://" GG_APPMSG_HOST; + else + host = ""; + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) + appmsg = "appmsg3.asp"; + else +#endif + appmsg = "appmsg2.asp"; + + auth = gg_proxy_auth(); + + snprintf(buf, sizeof(buf) - 1, + "GET %s/appsvc/%s?fmnumber=%u&version=%s&lastmsg=%d HTTP/1.0\r\n" + "Host: " GG_APPMSG_HOST "\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Pragma: no-cache\r\n" + "%s" + "\r\n", host, appmsg, sess->uin, client, sess->last_sysmsg, (auth) ? auth : ""); + + if (auth) + free(auth); + + free(client); + + /* zwolnij pamiêæ po wersji klienta. */ + if (sess->client_version) { + free(sess->client_version); + sess->client_version = NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", buf); + + /* zapytanie jest krótkie, wiêc zawsze zmie¶ci siê + * do bufora gniazda. je¶li write() zwróci mniej, + * sta³o siê co¶ z³ego. */ + if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_WRITING; + sess->state = GG_STATE_IDLE; + close(sess->fd); + sess->fd = -1; + break; + } + + sess->state = GG_STATE_READING_DATA; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + + case GG_STATE_READING_DATA: + { + char buf[1024], *tmp, *host; + int port = GG_DEFAULT_PORT; + struct in_addr addr; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_DATA\n"); + + /* czytamy liniê z gniazda i obcinamy \r\n. */ + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http header (%s)\n", buf); + + /* sprawdzamy, czy wszystko w porz±dku. */ + if (strncmp(buf, "HTTP/1.", 7) || strncmp(buf + 9, "200", 3)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() that's not what we've expected, trying direct connection\n"); + + close(sess->fd); + + /* je¶li otrzymali¶my jakie¶ dziwne informacje, + * próbujemy siê ³±czyæ z pominiêciem huba. */ + if (sess->proxy_addr && sess->proxy_port) { + if ((sess->fd = gg_connect(&sess->proxy_addr, sess->proxy_port, sess->async)) == -1) { + /* trudno. nie wysz³o. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + sess->port = GG_DEFAULT_PORT; + + /* ³±czymy siê na port 8074 huba. */ + if ((sess->fd = gg_connect(&sess->hub_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno)); + + sess->port = GG_HTTPS_PORT; + + /* ³±czymy siê na port 443. */ + if ((sess->fd = gg_connect(&sess->hub_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + /* ignorujemy resztê nag³ówka. */ + while (strcmp(buf, "\r\n") && strcmp(buf, "")) + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + + /* czytamy pierwsz± liniê danych. */ + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + + /* je¶li pierwsza liczba w linii nie jest równa zeru, + * oznacza to, ¿e mamy wiadomo¶æ systemow±. */ + if (atoi(buf)) { + char tmp[1024], *foo, *sysmsg_buf = NULL; + int len = 0; + + while (gg_read_line(sess->fd, tmp, sizeof(tmp) - 1)) { + if (!(foo = realloc(sysmsg_buf, len + strlen(tmp) + 2))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() out of memory for system message, ignoring\n"); + break; + } + + sysmsg_buf = foo; + + if (!len) + strcpy(sysmsg_buf, tmp); + else + strcat(sysmsg_buf, tmp); + + len += strlen(tmp); + } + + e->type = GG_EVENT_MSG; + e->event.msg.msgclass = atoi(buf); + e->event.msg.sender = 0; + e->event.msg.message = sysmsg_buf; + } + + close(sess->fd); + + gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http data (%s)\n", buf); + + /* analizujemy otrzymane dane. */ + tmp = buf; + + while (*tmp && *tmp != ' ') + tmp++; + while (*tmp && *tmp == ' ') + tmp++; + host = tmp; + while (*tmp && *tmp != ' ') + tmp++; + *tmp = 0; + + if ((tmp = strchr(host, ':'))) { + *tmp = 0; + port = atoi(tmp + 1); + } + + if (!strcmp(host, "notoperating")) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() service unavailable\n", errno, strerror(errno)); + sess->fd = -1; + goto fail_unavailable; + } + + addr.s_addr = inet_addr(host); + sess->server_addr = addr.s_addr; + + if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) { + /* je¶li mamy proxy, ³±czymy siê z nim. */ + if ((sess->fd = gg_connect(&sess->proxy_addr, sess->proxy_port, sess->async)) == -1) { + /* nie wysz³o? trudno. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + sess->port = port; + + /* ³±czymy siê z w³a¶ciwym serwerem. */ + if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno)); + + sess->port = GG_HTTPS_PORT; + + /* nie wysz³o? próbujemy portu 443. */ + if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, sess->async)) == -1) { + /* ostatnia deska ratunku zawiod³a? + * w takim razie zwijamy manatki. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + + case GG_STATE_CONNECTING_GG: + { + int res = 0, res_size = sizeof(res); + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_GG\n"); + + /* je¶li wyst±pi³ b³±d podczas ³±czenia siê... */ + if (sess->async && (sess->timeout == 0 || getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + /* je¶li nie uda³o siê po³±czenie z proxy, + * nie mamy czego próbowaæ wiêcej. */ + if (sess->proxy_addr && sess->proxy_port) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res)); + goto fail_connecting; + } + + close(sess->fd); + sess->fd = -1; + +#ifdef ETIMEDOUT + if (sess->timeout == 0) + errno = ETIMEDOUT; +#endif + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + /* je¶li logujemy siê po TLS, nie próbujemy + * siê ³±czyæ ju¿ z niczym innym w przypadku + * b³êdu. nie do¶æ, ¿e nie ma sensu, to i + * trzeba by siê bawiæ w tworzenie na nowo + * SSL i SSL_CTX. */ + + if (sess->ssl) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res)); + goto fail_connecting; + } +#endif + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", res, strerror(res)); + + sess->port = GG_HTTPS_PORT; + + /* próbujemy na port 443. */ + if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connected\n"); + + if (gg_proxy_http_only) + sess->proxy_port = 0; + + /* je¶li mamy proxy, wy¶lijmy zapytanie. */ + if (sess->proxy_addr && sess->proxy_port) { + char buf[100], *auth = gg_proxy_auth(); + struct in_addr addr; + + if (sess->server_addr) + addr.s_addr = sess->server_addr; + else + addr.s_addr = sess->hub_addr; + + snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n", inet_ntoa(addr), sess->port); + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() proxy request:\n// %s", buf); + + /* wysy³amy zapytanie. jest ono na tyle krótkie, + * ¿e musi siê zmie¶ciæ w buforze gniazda. je¶li + * write() zawiedzie, sta³o siê co¶ z³ego. */ + if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + if (auth) + free(auth); + goto fail_connecting; + } + + if (auth) { + gg_debug(GG_DEBUG_MISC, "// %s", auth); + if (write(sess->fd, auth, strlen(auth)) < (signed)strlen(auth)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + free(auth); + goto fail_connecting; + } + + free(auth); + } + + if (write(sess->fd, "\r\n", 2) < 2) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + goto fail_connecting; + } + } + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) { + SSL_set_fd(sess->ssl, sess->fd); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } +#endif + + sess->state = GG_STATE_READING_KEY; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + case GG_STATE_TLS_NEGOTIATION: + { + int res; + X509 *peer; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n"); + + if ((res = SSL_connect(sess->ssl)) <= 0) { + int err = SSL_get_error(sess->ssl, res); + + if (res == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() disconnected during TLS negotiation\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_TLS; + sess->state = GG_STATE_IDLE; + close(sess->fd); + sess->fd = -1; + break; + } + + if (err == SSL_ERROR_WANT_READ) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to read\n"); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } else if (err == SSL_ERROR_WANT_WRITE) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to write\n"); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } else { + char buf[1024]; + + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() bailed out: %s\n", buf); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_TLS; + sess->state = GG_STATE_IDLE; + close(sess->fd); + sess->fd = -1; + break; + } + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n// cipher: %s\n", SSL_get_cipher_name(sess->ssl)); + + peer = SSL_get_peer_certificate(sess->ssl); + + if (!peer) + gg_debug(GG_DEBUG_MISC, "// WARNING! unable to get peer certificate!\n"); + else { + char buf[1024]; + + X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// cert subject: %s\n", buf); + + X509_NAME_oneline(X509_get_issuer_name(peer), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// cert issuer: %s\n", buf); + } + + sess->state = GG_STATE_READING_KEY; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } +#endif + + case GG_STATE_READING_KEY: + { + struct gg_header *h; + struct gg_welcome *w; + struct gg_login60 l; + unsigned int hash; + unsigned char *password = sess->password; + int ret; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_KEY\n"); + + memset(&l, 0, sizeof(l)); + l.dunno2 = 0xbe; + + /* XXX bardzo, bardzo, bardzo g³upi pomys³ na pozbycie + * siê tekstu wrzucanego przez proxy. */ + if (sess->proxy_addr && sess->proxy_port) { + char buf[100]; + + strcpy(buf, ""); + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() proxy response:\n// %s\n", buf); + + while (strcmp(buf, "")) { + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + if (strcmp(buf, "")) + gg_debug(GG_DEBUG_MISC, "// %s\n", buf); + } + + /* XXX niech czeka jeszcze raz w tej samej + * fazie. g³upio, ale dzia³a. */ + sess->proxy_port = 0; + + break; + } + + /* czytaj pierwszy pakiet. */ + if (!(h = gg_recv_packet(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_READING; + sess->state = GG_STATE_IDLE; + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + break; + } + + if (h->type != GG_WELCOME) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() invalid packet received\n"); + free(h); + close(sess->fd); + sess->fd = -1; + errno = EINVAL; + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_INVALID; + sess->state = GG_STATE_IDLE; + break; + } + + w = (struct gg_welcome*) ((char*) h + sizeof(struct gg_header)); + w->key = gg_fix32(w->key); + + hash = gg_login_hash(password, w->key); + + gg_debug(GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> hash %.8x\n", w->key, hash); + + free(h); + + free(sess->password); + sess->password = NULL; + + { + struct in_addr dcc_ip; + dcc_ip.s_addr = gg_dcc_ip; + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() gg_dcc_ip = %s\n", inet_ntoa(dcc_ip)); + } + + if (gg_dcc_ip == (unsigned long) inet_addr("255.255.255.255")) { + struct sockaddr_in sin; + int sin_len = sizeof(sin); + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() detecting address\n"); + + if (!getsockname(sess->fd, (struct sockaddr*) &sin, &sin_len)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() detected address to %s\n", inet_ntoa(sin.sin_addr)); + l.local_ip = sin.sin_addr.s_addr; + } else { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() unable to detect address\n"); + l.local_ip = 0; + } + } else + l.local_ip = gg_dcc_ip; + + l.uin = gg_fix32(sess->uin); + l.hash = gg_fix32(hash); + l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL); + l.version = gg_fix32(sess->protocol_version); + l.local_port = gg_fix16(gg_dcc_port); + l.image_size = sess->image_size; + + if (sess->external_addr && sess->external_port > 1023) { + l.external_ip = sess->external_addr; + l.external_port = gg_fix16(sess->external_port); + } + + gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN60 packet\n"); + ret = gg_send_packet(sess, GG_LOGIN60, &l, sizeof(l), sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0, NULL); + + free(sess->initial_descr); + sess->initial_descr = NULL; + + if (ret == -1) { + gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending packet failed. (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_WRITING; + sess->state = GG_STATE_IDLE; + break; + } + + sess->state = GG_STATE_READING_REPLY; + + break; + } + + case GG_STATE_READING_REPLY: + { + struct gg_header *h; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_REPLY\n"); + + if (!(h = gg_recv_packet(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno)); + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_READING; + sess->state = GG_STATE_IDLE; + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + break; + } + + if (h->type == GG_LOGIN_OK || h->type == GG_NEED_EMAIL) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() login succeded\n"); + e->type = GG_EVENT_CONN_SUCCESS; + sess->state = GG_STATE_CONNECTED; + sess->timeout = -1; + sess->status = (sess->initial_status) ? sess->initial_status : GG_STATUS_AVAIL; + free(h); + break; + } + + if (h->type == GG_LOGIN_FAILED) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() login failed\n"); + e->event.failure = GG_FAILURE_PASSWORD; + errno = EACCES; + } else if (h->type == GG_DISCONNECTING) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() too many incorrect password attempts\n"); + e->event.failure = GG_FAILURE_INTRUDER; + errno = EACCES; + } else { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() invalid packet\n"); + e->event.failure = GG_FAILURE_INVALID; + errno = EINVAL; + } + + e->type = GG_EVENT_CONN_FAILED; + sess->state = GG_STATE_IDLE; + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + free(h); + + break; + } + + case GG_STATE_CONNECTED: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTED\n"); + + sess->last_event = time(NULL); + + if ((res = gg_watch_fd_connected(sess, e)) == -1) { + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() watch_fd_connected failed (errno=%d, %s)\n", errno, strerror(errno)); + + if (errno == EAGAIN) { + e->type = GG_EVENT_NONE; + res = 0; + } else + res = -1; + } + break; + } + } + +done: + if (res == -1) { + free(e); + e = NULL; + } + + return e; + +fail_connecting: + if (sess->fd != -1) { + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + } + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_CONNECTING; + sess->state = GG_STATE_IDLE; + goto done; + +fail_resolving: + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_RESOLVING; + sess->state = GG_STATE_IDLE; + goto done; + +fail_unavailable: + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_UNAVAILABLE; + sess->state = GG_STATE_IDLE; + goto done; +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/http.c b/kopete/protocols/gadu/libgadu/http.c new file mode 100644 index 00000000..77ebb319 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/http.c @@ -0,0 +1,522 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include +#include + +#include "libgadu-config.h" + +#include +#include +#include +#ifdef __GG_LIBGADU_HAVE_PTHREAD +# include +#endif +#include +#include +#include +#include +#include + +#include "compat.h" +#include "libgadu.h" + +/* + * gg_http_connect() // funkcja pomocnicza + * + * rozpoczyna po³±czenie po http. + * + * - hostname - adres serwera + * - port - port serwera + * - async - asynchroniczne po³±czenie + * - method - metoda http (GET, POST, cokolwiek) + * - path - ¶cie¿ka do zasobu (musi byæ poprzedzona ,,/'') + * - header - nag³ówek zapytania plus ewentualne dane dla POST + * + * zaalokowana struct gg_http, któr± po¼niej nale¿y + * zwolniæ funkcj± gg_http_free(), albo NULL je¶li wyst±pi³ b³±d. + */ +struct gg_http *gg_http_connect(const char *hostname, int port, int async, const char *method, const char *path, const char *header) +{ + struct gg_http *h; + + if (!hostname || !port || !method || !path || !header) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() invalid arguments\n"); + errno = EFAULT; + return NULL; + } + + if (!(h = malloc(sizeof(*h)))) + return NULL; + memset(h, 0, sizeof(*h)); + + h->async = async; + h->port = port; + h->fd = -1; + h->type = GG_SESSION_HTTP; + + if (gg_proxy_enabled) { + char *auth = gg_proxy_auth(); + + h->query = gg_saprintf("%s http://%s:%d%s HTTP/1.0\r\n%s%s", + method, hostname, port, path, (auth) ? auth : + "", header); + hostname = gg_proxy_host; + h->port = port = gg_proxy_port; + + if (auth) + free(auth); + } else { + h->query = gg_saprintf("%s %s HTTP/1.0\r\n%s", + method, path, header); + } + + if (!h->query) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() not enough memory for query\n"); + free(h); + errno = ENOMEM; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", h->query); + + if (async) { +#ifndef __GG_LIBGADU_HAVE_PTHREAD + if (gg_resolve(&h->fd, &h->pid, hostname)) { +#else + if (gg_resolve_pthread(&h->fd, &h->resolver, hostname)) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver failed\n"); + gg_http_free(h); + errno = ENOENT; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver = %p\n", h->resolver); + + h->state = GG_STATE_RESOLVING; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + struct in_addr *hn, a; + + if (!(hn = gg_gethostbyname(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() host not found\n"); + gg_http_free(h); + errno = ENOENT; + return NULL; + } else { + a.s_addr = hn->s_addr; + free(hn); + } + + if (!(h->fd = gg_connect(&a, port, 0)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + gg_http_free(h); + return NULL; + } + + h->state = GG_STATE_CONNECTING; + + while (h->state != GG_STATE_ERROR && h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) + break; + } + + if (h->state != GG_STATE_PARSING) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() some strange error\n"); + gg_http_free(h); + return NULL; + } + } + + h->callback = gg_http_watch_fd; + h->destroy = gg_http_free; + + return h; +} + +#define gg_http_error(x) \ + close(h->fd); \ + h->fd = -1; \ + h->state = GG_STATE_ERROR; \ + h->error = x; \ + return 0; + +/* + * gg_http_watch_fd() + * + * przy asynchronicznej obs³udze HTTP funkcjê t± nale¿y wywo³aæ, je¶li + * zmieni³o siê co¶ na obserwowanym deskryptorze. + * + * - h - struktura opisuj±ca po³±czenie + * + * je¶li wszystko posz³o dobrze to 0, inaczej -1. po³±czenie bêdzie + * zakoñczone, je¶li h->state == GG_STATE_PARSING. je¶li wyst±pi jaki¶ + * b³±d, to bêdzie tam GG_STATE_ERROR i odpowiedni kod b³êdu w h->error. + */ +int gg_http_watch_fd(struct gg_http *h) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_http_watch_fd(%p);\n", h); + + if (!h) { + gg_debug(GG_DEBUG_MISC, "// gg_http_watch_fd() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_RESOLVING) { + struct in_addr a; + + gg_debug(GG_DEBUG_MISC, "=> http, resolving done\n"); + + if (read(h->fd, &a, sizeof(a)) < (signed)sizeof(a) || a.s_addr == INADDR_NONE) { + gg_debug(GG_DEBUG_MISC, "=> http, resolver thread failed\n"); + gg_http_error(GG_ERROR_RESOLVING); + } + + close(h->fd); + h->fd = -1; + +#ifndef __GG_LIBGADU_HAVE_PTHREAD + waitpid(h->pid, NULL, 0); +#else + if (h->resolver) { + pthread_cancel(*((pthread_t *) h->resolver)); + free(h->resolver); + h->resolver = NULL; + } +#endif + + gg_debug(GG_DEBUG_MISC, "=> http, connecting to %s:%d\n", inet_ntoa(a), h->port); + + if ((h->fd = gg_connect(&a, h->port, h->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, connection failed (errno=%d, %s)\n", errno, strerror(errno)); + gg_http_error(GG_ERROR_CONNECTING); + } + + h->state = GG_STATE_CONNECTING; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + return 0; + } + + if (h->state == GG_STATE_CONNECTING) { + int res = 0; + unsigned int res_size = sizeof(res); + + if (h->async && (getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + gg_debug(GG_DEBUG_MISC, "=> http, async connection failed (errno=%d, %s)\n", (res) ? res : errno , strerror((res) ? res : errno)); + close(h->fd); + h->fd = -1; + h->state = GG_STATE_ERROR; + h->error = GG_ERROR_CONNECTING; + if (res) + errno = res; + return 0; + } + + gg_debug(GG_DEBUG_MISC, "=> http, connected, sending request\n"); + + h->state = GG_STATE_SENDING_QUERY; + } + + if (h->state == GG_STATE_SENDING_QUERY) { + int res; + + if ((res = write(h->fd, h->query, strlen(h->query))) < 1) { + gg_debug(GG_DEBUG_MISC, "=> http, write() failed (len=%d, res=%d, errno=%d)\n", strlen(h->query), res, errno); + gg_http_error(GG_ERROR_WRITING); + } + + if (res < strlen(h->query)) { + gg_debug(GG_DEBUG_MISC, "=> http, partial header sent (led=%d, sent=%d)\n", strlen(h->query), res); + + memmove(h->query, h->query + res, strlen(h->query) - res + 1); + h->state = GG_STATE_SENDING_QUERY; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + gg_debug(GG_DEBUG_MISC, "=> http, request sent (len=%d)\n", strlen(h->query)); + free(h->query); + h->query = NULL; + + h->state = GG_STATE_READING_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } + + return 0; + } + + if (h->state == GG_STATE_READING_HEADER) { + char buf[1024], *tmp; + int res; + + if ((res = read(h->fd, buf, sizeof(buf))) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, reading header failed (errno=%d)\n", errno); + if (h->header) { + free(h->header); + h->header = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + if (!res) { + gg_debug(GG_DEBUG_MISC, "=> http, connection reset by peer\n"); + if (h->header) { + free(h->header); + h->header = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of header\n", res); + + if (!(tmp = realloc(h->header, h->header_size + res + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for header\n"); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_READING); + } + + h->header = tmp; + + memcpy(h->header + h->header_size, buf, res); + h->header_size += res; + + gg_debug(GG_DEBUG_MISC, "=> http, header_buf=%p, header_size=%d\n", h->header, h->header_size); + + h->header[h->header_size] = 0; + + if ((tmp = strstr(h->header, "\r\n\r\n")) || (tmp = strstr(h->header, "\n\n"))) { + int sep_len = (*tmp == '\r') ? 4 : 2; + unsigned int left; + char *line; + + left = h->header_size - ((long)(tmp) - (long)(h->header) + sep_len); + + gg_debug(GG_DEBUG_MISC, "=> http, got all header (%d bytes, %d left)\n", h->header_size - left, left); + + /* HTTP/1.1 200 OK */ + if (strlen(h->header) < 16 || strncmp(h->header + 9, "200", 3)) { + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header); + + gg_debug(GG_DEBUG_MISC, "=> http, didn't get 200 OK -- no results\n"); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_CONNECTING); + } + + h->body_size = 0; + line = h->header; + *tmp = 0; + + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header); + + while (line) { + if (!strncasecmp(line, "Content-length: ", 16)) { + h->body_size = atoi(line + 16); + } + line = strchr(line, '\n'); + if (line) + line++; + } + + if (h->body_size <= 0) { + gg_debug(GG_DEBUG_MISC, "=> http, content-length not found\n"); + h->body_size = left; + } + + if (left > h->body_size) { + gg_debug(GG_DEBUG_MISC, "=> http, oversized reply (%d bytes needed, %d bytes left)\n", h->body_size, left); + h->body_size = left; + } + + gg_debug(GG_DEBUG_MISC, "=> http, body_size=%d\n", h->body_size); + + if (!(h->body = malloc(h->body_size + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory (%d bytes for body_buf)\n", h->body_size + 1); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_READING); + } + + if (left) { + memcpy(h->body, tmp + sep_len, left); + h->body_done = left; + } + + h->body[left] = 0; + + h->state = GG_STATE_READING_DATA; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } + + return 0; + } + + if (h->state == GG_STATE_READING_DATA) { + char buf[1024]; + int res; + + if ((res = read(h->fd, buf, sizeof(buf))) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, reading body failed (errno=%d)\n", errno); + if (h->body) { + free(h->body); + h->body = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + if (!res) { + if (h->body_done >= h->body_size) { + gg_debug(GG_DEBUG_MISC, "=> http, we're done, closing socket\n"); + h->state = GG_STATE_PARSING; + close(h->fd); + h->fd = -1; + } else { + gg_debug(GG_DEBUG_MISC, "=> http, connection closed while reading (have %d, need %d)\n", h->body_done, h->body_size); + if (h->body) { + free(h->body); + h->body = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + return 0; + } + + gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of body\n", res); + + if (h->body_done + res > h->body_size) { + char *tmp; + + gg_debug(GG_DEBUG_MISC, "=> http, too much data (%d bytes, %d needed), enlarging buffer\n", h->body_done + res, h->body_size); + + if (!(tmp = realloc(h->body, h->body_done + res + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for data (%d needed)\n", h->body_done + res + 1); + free(h->body); + h->body = NULL; + gg_http_error(GG_ERROR_READING); + } + + h->body = tmp; + h->body_size = h->body_done + res; + } + + h->body[h->body_done + res] = 0; + memcpy(h->body + h->body_done, buf, res); + h->body_done += res; + + gg_debug(GG_DEBUG_MISC, "=> body_done=%d, body_size=%d\n", h->body_done, h->body_size); + + return 0; + } + + if (h->fd != -1) + close(h->fd); + + h->fd = -1; + h->state = GG_STATE_ERROR; + h->error = 0; + + return -1; +} + +#undef gg_http_error + +/* + * gg_http_stop() + * + * je¶li po³±czenie jest w trakcie, przerywa je. nie zwalnia h->data. + * + * - h - struktura opisuj±ca po³±czenie + */ +void gg_http_stop(struct gg_http *h) +{ + if (!h) + return; + + if (h->state == GG_STATE_ERROR || h->state == GG_STATE_DONE) + return; + + if (h->fd != -1) + close(h->fd); + h->fd = -1; +} + +/* + * gg_http_free_fields() // funkcja wewnêtrzna + * + * zwalnia pola struct gg_http, ale nie zwalnia samej struktury. + */ +void gg_http_free_fields(struct gg_http *h) +{ + if (!h) + return; + + if (h->body) { + free(h->body); + h->body = NULL; + } + + if (h->query) { + free(h->query); + h->query = NULL; + } + + if (h->header) { + free(h->header); + h->header = NULL; + } +} + +/* + * gg_http_free() + * + * próbuje zamkn±æ po³±czenie i zwalnia pamiêæ po nim. + * + * - h - struktura, któr± nale¿y zlikwidowaæ + */ +void gg_http_free(struct gg_http *h) +{ + if (!h) + return; + + gg_http_stop(h); + gg_http_free_fields(h); + free(h); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/libgadu-config.h.in b/kopete/protocols/gadu/libgadu/libgadu-config.h.in new file mode 100644 index 00000000..dc4fb435 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/libgadu-config.h.in @@ -0,0 +1,30 @@ +/* Local libgadu configuration. */ + +#ifndef __GG_LIBGADU_CONFIG_H +#define __GG_LIBGADU_CONFIG_H + +/* Defined if libgadu was compiled for bigendian machine. */ +#undef __GG_LIBGADU_BIGENDIAN + +/* Defined if libgadu was compiled and linked with pthread support. */ +#define __GG_LIBGADU_HAVE_PTHREAD + +/* Defined if this machine has C99-compiliant vsnprintf(). */ +#undef __GG_LIBGADU_HAVE_C99_VSNPRINTF + +/* Defined if this machine has va_copy(). */ +#undef __GG_LIBGADU_HAVE_VA_COPY + +/* Defined if this machine has __va_copy(). */ +#undef __GG_LIBGADU_HAVE___VA_COPY + +/* Defined if this machine supports long long. */ +#undef __GG_LIBGADU_HAVE_LONG_LONG + +/* Defined if libgadu was compiled and linked with TLS support. */ +#undef __GG_LIBGADU_HAVE_OPENSSL + +/* Include file containing uintXX_t declarations. */ +#include + +#endif /* __GG_LIBGADU_CONFIG_H */ diff --git a/kopete/protocols/gadu/libgadu/libgadu.c b/kopete/protocols/gadu/libgadu/libgadu.c new file mode 100644 index 00000000..47b687f0 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/libgadu.c @@ -0,0 +1,1818 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski + * Robert J. Wo¼ny + * Arkadiusz Mi¶kiewicz + * Tomasz Chiliñski + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include +#include +#ifdef sun +# include +#endif + +#include "libgadu-config.h" + +#include +#include +#ifdef __GG_LIBGADU_HAVE_PTHREAD +# include +#endif +#include +#include +#include +#include +#include +#include +#ifdef __GG_LIBGADU_HAVE_OPENSSL +# include +# include +#endif + +#include "compat.h" +#include "libgadu.h" + +int gg_debug_level = 0; +void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL; + +int gg_dcc_port = 0; +unsigned long gg_dcc_ip = 0; + +unsigned long gg_local_ip = 0; +/* + * zmienne opisuj±ce parametry proxy http. + */ +char *gg_proxy_host = NULL; +int gg_proxy_port = 0; +int gg_proxy_enabled = 0; +int gg_proxy_http_only = 0; +char *gg_proxy_username = NULL; +char *gg_proxy_password = NULL; + +#ifndef lint +static char rcsid[] +#ifdef __GNUC__ +__attribute__ ((unused)) +#endif += "$Id$"; +#endif + +/* + * gg_libgadu_version() + * + * zwraca wersjê libgadu. + * + * - brak + * + * wersja libgadu. + */ +const char *gg_libgadu_version() +{ + return GG_LIBGADU_VERSION; +} + +/* + * gg_fix32() + * + * zamienia kolejno¶æ bajtów w liczbie 32-bitowej tak, by odpowiada³a + * kolejno¶ci bajtów w protokole GG. ze wzglêdu na LE-owo¶æ serwera, + * zamienia tylko na maszynach BE-wych. + * + * - x - liczba do zamiany + * + * liczba z odpowiedni± kolejno¶ci± bajtów. + */ +uint32_t gg_fix32(uint32_t x) +{ +#ifndef __GG_LIBGADU_BIGENDIAN + return x; +#else + return (uint32_t) + (((x & (uint32_t) 0x000000ffU) << 24) | + ((x & (uint32_t) 0x0000ff00U) << 8) | + ((x & (uint32_t) 0x00ff0000U) >> 8) | + ((x & (uint32_t) 0xff000000U) >> 24)); +#endif +} + +/* + * gg_fix16() + * + * zamienia kolejno¶æ bajtów w liczbie 16-bitowej tak, by odpowiada³a + * kolejno¶ci bajtów w protokole GG. ze wzglêdu na LE-owo¶æ serwera, + * zamienia tylko na maszynach BE-wych. + * + * - x - liczba do zamiany + * + * liczba z odpowiedni± kolejno¶ci± bajtów. + */ +uint16_t gg_fix16(uint16_t x) +{ +#ifndef __GG_LIBGADU_BIGENDIAN + return x; +#else + return (uint16_t) + (((x & (uint16_t) 0x00ffU) << 8) | + ((x & (uint16_t) 0xff00U) >> 8)); +#endif +} + +/* + * gg_login_hash() // funkcja wewnêtrzna + * + * liczy hash z has³a i danego seeda. + * + * - password - has³o do hashowania + * - seed - warto¶æ podana przez serwer + * + * hash. + */ +unsigned int gg_login_hash(const unsigned char *password, unsigned int seed) +{ + unsigned int x, y, z; + + y = seed; + + for (x = 0; *password; password++) { + x = (x & 0xffffff00) | *password; + y ^= x; + y += x; + x <<= 8; + y ^= x; + x <<= 8; + y -= x; + x <<= 8; + y ^= x; + + z = y & 0x1F; + y = (y << z) | (y >> (32 - z)); + } + + return y; +} + +/* + * gg_resolve() // funkcja wewnêtrzna + * + * tworzy potok, forkuje siê i w drugim procesie zaczyna resolvowaæ + * podanego hosta. zapisuje w sesji deskryptor potoku. je¶li co¶ tam + * bêdzie gotowego, znaczy, ¿e mo¿na wczytaæ struct in_addr. je¶li + * nie znajdzie, zwraca INADDR_NONE. + * + * - fd - wska¼nik gdzie wrzuciæ deskryptor + * - pid - gdzie wrzuciæ pid procesu potomnego + * - hostname - nazwa hosta do zresolvowania + * + * 0, -1. + */ +int gg_resolve(int *fd, int *pid, const char *hostname) +{ + int pipes[2], res; + struct in_addr a; + int errno2; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve(%p, %p, \"%s\");\n", fd, pid, hostname); + + if (!fd || !pid) { + errno = EFAULT; + return -1; + } + + if (pipe(pipes) == -1) + return -1; + + if ((res = fork()) == -1) { + errno2 = errno; + close(pipes[0]); + close(pipes[1]); + errno = errno2; + return -1; + } + + if (!res) { + close(pipes[0]); + + if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(hostname))) + a.s_addr = INADDR_NONE; + else { + a.s_addr = hn->s_addr; + free(hn); + } + } + + write(pipes[1], &a, sizeof(a)); + + exit(0); + } + + close(pipes[1]); + + *fd = pipes[0]; + *pid = res; + + return 0; +} + +#ifdef __GG_LIBGADU_HAVE_PTHREAD + +struct gg_resolve_pthread_data { + char *hostname; + int fd; +}; + +static void *gg_resolve_pthread_thread(void *arg) +{ + struct gg_resolve_pthread_data *d = arg; + struct in_addr a; + + pthread_detach(pthread_self()); + + if ((a.s_addr = inet_addr(d->hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(d->hostname))) + a.s_addr = INADDR_NONE; + else { + a.s_addr = hn->s_addr; + free(hn); + } + } + + write(d->fd, &a, sizeof(a)); + close(d->fd); + + free(d->hostname); + d->hostname = NULL; + + free(d); + + pthread_exit(NULL); + + return NULL; /* ¿eby kompilator nie marudzi³ */ +} + +/* + * gg_resolve_pthread() // funkcja wewnêtrzna + * + * tworzy potok, nowy w±tek i w nim zaczyna resolvowaæ podanego hosta. + * zapisuje w sesji deskryptor potoku. je¶li co¶ tam bêdzie gotowego, + * znaczy, ¿e mo¿na wczytaæ struct in_addr. je¶li nie znajdzie, zwraca + * INADDR_NONE. + * + * - fd - wska¼nik do zmiennej przechowuj±cej desktyptor resolvera + * - resolver - wska¼nik do wska¼nika resolvera + * - hostname - nazwa hosta do zresolvowania + * + * 0, -1. + */ +int gg_resolve_pthread(int *fd, void **resolver, const char *hostname) +{ + struct gg_resolve_pthread_data *d = NULL; + pthread_t *tmp; + int pipes[2], new_errno; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve_pthread(%p, %p, \"%s\");\n", fd, resolver, hostname); + + if (!resolver || !fd || !hostname) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (!(tmp = malloc(sizeof(pthread_t)))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory for pthread id\n"); + return -1; + } + + if (pipe(pipes) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); + free(tmp); + return -1; + } + + if (!(d = malloc(sizeof(*d)))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory\n"); + new_errno = errno; + goto cleanup; + } + + d->hostname = NULL; + + if (!(d->hostname = strdup(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory\n"); + new_errno = errno; + goto cleanup; + } + + d->fd = pipes[1]; + + if (pthread_create(tmp, NULL, gg_resolve_pthread_thread, d)) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_phread() unable to create thread\n"); + new_errno = errno; + goto cleanup; + } + + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() %p\n", tmp); + + *resolver = tmp; + + *fd = pipes[0]; + + return 0; + +cleanup: + if (d) { + free(d->hostname); + free(d); + } + + close(pipes[0]); + close(pipes[1]); + + free(tmp); + + errno = new_errno; + + return -1; +} + +#endif + +/* + * gg_read() // funkcja pomocnicza + * + * czyta z gniazda okre¶lon± ilo¶æ bajtów. bierze pod uwagê, czy mamy + * po³±czenie zwyk³e czy TLS. + * + * - sess - sesja, + * - buf - bufor, + * - length - ilo¶æ bajtów, + * + * takie same warto¶ci jak read(). + */ +int gg_read(struct gg_session *sess, char *buf, int length) +{ + int res; + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) { + int err; + + res = SSL_read(sess->ssl, buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_WANT_READ) + errno = EAGAIN; + + return -1; + } + } else +#endif + res = read(sess->fd, buf, length); + + return res; +} + +/* + * gg_write() // funkcja pomocnicza + * + * zapisuje do gniazda okre¶lon± ilo¶æ bajtów. bierze pod uwagê, czy mamy + * po³±czenie zwyk³e czy TLS. + * + * - sess - sesja, + * - buf - bufor, + * - length - ilo¶æ bajtów, + * + * takie same warto¶ci jak write(). + */ +int gg_write(struct gg_session *sess, const char *buf, int length) +{ + int res = 0; + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) { + int err; + + res = SSL_write(sess->ssl, buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_WANT_WRITE) + errno = EAGAIN; + + return -1; + } + } else +#endif + { + int written = 0; + + while (written < length) { + res = write(sess->fd, buf + written, length - written); + + if (res == -1) { + if (errno == EAGAIN) + continue; + else + break; + } else { + written += res; + res = written; + } + } + } + + return res; +} + +/* + * gg_recv_packet() // funkcja wewnêtrzna + * + * odbiera jeden pakiet i zwraca wska¼nik do niego. pamiêæ po nim + * nale¿y zwolniæ za pomoc± free(). + * + * - sess - opis sesji + * + * w przypadku b³êdu NULL, kod b³êdu w errno. nale¿y zwróciæ uwagê, ¿e gdy + * po³±czenie jest nieblokuj±ce, a kod b³êdu wynosi EAGAIN, nie uda³o siê + * odczytaæ ca³ego pakietu i nie nale¿y tego traktowaæ jako b³±d. + */ +void *gg_recv_packet(struct gg_session *sess) +{ + struct gg_header h; + char *buf = NULL; + int ret = 0; + unsigned int offset, size = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_recv_packet(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return NULL; + } + + if (sess->recv_left < 1) { + if (sess->header_buf) { + memcpy(&h, sess->header_buf, sess->header_done); + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv: resuming last read (%d bytes left)\n", sizeof(h) - sess->header_done); + free(sess->header_buf); + sess->header_buf = NULL; + } else + sess->header_done = 0; + + while (sess->header_done < sizeof(h)) { + ret = gg_read(sess, (char*) &h + sess->header_done, sizeof(h) - sess->header_done); + + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv(%d,%p,%d) = %d\n", sess->fd, &h + sess->header_done, sizeof(h) - sess->header_done, ret); + + if (!ret) { + errno = ECONNRESET; + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: connection broken\n"); + return NULL; + } + + if (ret == -1) { + if (errno == EINTR) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() interrupted system call, resuming\n"); + continue; + } + + if (errno == EAGAIN) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() incomplete header received\n"); + + if (!(sess->header_buf = malloc(sess->header_done))) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() not enough memory\n"); + return NULL; + } + + memcpy(sess->header_buf, &h, sess->header_done); + + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: errno=%d, %s\n", errno, strerror(errno)); + + return NULL; + } + + sess->header_done += ret; + + } + + h.type = gg_fix32(h.type); + h.length = gg_fix32(h.length); + } else + memcpy(&h, sess->recv_buf, sizeof(h)); + + /* jakie¶ sensowne limity na rozmiar pakietu */ + if (h.length > 65535) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() invalid packet length (%d)\n", h.length); + errno = ERANGE; + return NULL; + } + + if (sess->recv_left > 0) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() resuming last gg_recv_packet()\n"); + size = sess->recv_left; + offset = sess->recv_done; + buf = sess->recv_buf; + } else { + if (!(buf = malloc(sizeof(h) + h.length + 1))) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() not enough memory for packet data\n"); + return NULL; + } + + memcpy(buf, &h, sizeof(h)); + + offset = 0; + size = h.length; + } + + while (size > 0) { + ret = gg_read(sess, buf + sizeof(h) + offset, size); + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv(%d,%p,%d) = %d\n", sess->fd, buf + sizeof(h) + offset, size, ret); + if (!ret) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed: connection broken\n"); + errno = ECONNRESET; + return NULL; + } + if (ret > -1 && ret <= size) { + offset += ret; + size -= ret; + } else if (ret == -1) { + int errno2 = errno; + + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno = errno2; + + if (errno == EAGAIN) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() %d bytes received, %d left\n", offset, size); + sess->recv_buf = buf; + sess->recv_left = size; + sess->recv_done = offset; + return NULL; + } + if (errno != EINTR) { + free(buf); + return NULL; + } + } + } + + sess->recv_left = 0; + + if ((gg_debug_level & GG_DEBUG_DUMP)) { + unsigned int i; + + gg_debug(GG_DEBUG_DUMP, "// gg_recv_packet(%.2x)", h.type); + for (i = 0; i < sizeof(h) + h.length; i++) + gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) buf[i]); + gg_debug(GG_DEBUG_DUMP, "\n"); + } + + return buf; +} + +/* + * gg_send_packet() // funkcja wewnêtrzna + * + * konstruuje pakiet i wysy³a go do serwera. + * + * - sock - deskryptor gniazda + * - type - typ pakietu + * - payload_1 - pierwsza czê¶æ pakietu + * - payload_length_1 - d³ugo¶æ pierwszej czê¶ci + * - payload_2 - druga czê¶æ pakietu + * - payload_length_2 - d³ugo¶æ drugiej czê¶ci + * - ... - kolejne czê¶ci pakietu i ich d³ugo¶ci + * - NULL - koñcowym parametr (konieczny!) + * + * je¶li siê powiod³o, zwraca 0, w przypadku b³êdu -1. je¶li errno == ENOMEM, + * zabrak³o pamiêci. inaczej by³ b³±d przy wysy³aniu pakietu. dla errno == 0 + * nie wys³ano ca³ego pakietu. + */ +int gg_send_packet(struct gg_session *sess, int type, ...) +{ + struct gg_header *h; + char *tmp; + unsigned int tmp_length; + void *payload; + unsigned int payload_length; + va_list ap; + int res; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_packet(%p, 0x%.2x, ...)\n", sess, type); + + tmp_length = sizeof(struct gg_header); + + if (!(tmp = malloc(tmp_length))) { + gg_debug(GG_DEBUG_MISC, "// gg_send_packet() not enough memory for packet header\n"); + return -1; + } + + va_start(ap, type); + + payload = va_arg(ap, void *); + + while (payload) { + char *tmp2; + + payload_length = va_arg(ap, unsigned int); + + if (!(tmp2 = realloc(tmp, tmp_length + payload_length))) { + gg_debug(GG_DEBUG_MISC, "// gg_send_packet() not enough memory for payload\n"); + free(tmp); + va_end(ap); + return -1; + } + + tmp = tmp2; + + memcpy(tmp + tmp_length, payload, payload_length); + tmp_length += payload_length; + + payload = va_arg(ap, void *); + } + + va_end(ap); + + h = (struct gg_header*) tmp; + h->type = gg_fix32(type); + h->length = gg_fix32(tmp_length - sizeof(struct gg_header)); + + if ((gg_debug_level & GG_DEBUG_DUMP)) { + unsigned int i; + + gg_debug(GG_DEBUG_DUMP, "// gg_send_packet(0x%.2x)", gg_fix32(h->type)); + for (i = 0; i < tmp_length; ++i) + gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) tmp[i]); + gg_debug(GG_DEBUG_DUMP, "\n"); + } + + if ((res = gg_write(sess, tmp, tmp_length)) < tmp_length) { + gg_debug(GG_DEBUG_MISC, "// gg_send_packet() write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno)); + free(tmp); + return -1; + } + + free(tmp); + return 0; +} + +/* + * gg_session_callback() // funkcja wewnêtrzna + * + * wywo³ywany z gg_session->callback, wykonuje gg_watch_fd() i pakuje + * do gg_session->event jego wynik. + */ +static int gg_session_callback(struct gg_session *s) +{ + if (!s) { + errno = EFAULT; + return -1; + } + + return ((s->event = gg_watch_fd(s)) != NULL) ? 0 : -1; +} + +/* + * gg_login() + * + * rozpoczyna procedurê ³±czenia siê z serwerem. resztê obs³uguje siê przez + * gg_watch_fd(). + * + * UWAGA! program musi obs³u¿yæ SIGCHLD, je¶li ³±czy siê asynchronicznie, + * ¿eby poprawnie zamkn±æ proces resolvera. + * + * - p - struktura opisuj±ca pocz±tkowy stan. wymagane pola: uin, + * password + * + * w przypadku b³êdu NULL, je¶li idzie dobrze (async) albo posz³o + * dobrze (sync), zwróci wska¼nik do zaalokowanej struct gg_session. + */ +struct gg_session *gg_login(const struct gg_login_params *p) +{ + struct gg_session *sess = NULL; + char *hostname; + int port; + + if (!p) { + gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p);\n", p); + errno = EFAULT; + return NULL; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p: [uin=%u, async=%d, ...]);\n", p, p->uin, p->async); + + if (!(sess = malloc(sizeof(struct gg_session)))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for session data\n"); + goto fail; + } + + memset(sess, 0, sizeof(struct gg_session)); + + if (!p->password || !p->uin) { + gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. uin and password needed\n"); + errno = EFAULT; + goto fail; + } + + if (!(sess->password = strdup(p->password))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for password\n"); + goto fail; + } + + if (p->status_descr && !(sess->initial_descr = strdup(p->status_descr))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for status\n"); + goto fail; + } + + sess->uin = p->uin; + sess->state = GG_STATE_RESOLVING; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->async = p->async; + sess->type = GG_SESSION_GG; + sess->initial_status = p->status; + sess->callback = gg_session_callback; + sess->destroy = gg_free_session; + sess->port = (p->server_port) ? p->server_port : ((gg_proxy_enabled) ? GG_HTTPS_PORT : GG_DEFAULT_PORT); + sess->server_addr = p->server_addr; + sess->external_port = p->external_port; + sess->external_addr = p->external_addr; + sess->protocol_version = (p->protocol_version) ? p->protocol_version : GG_DEFAULT_PROTOCOL_VERSION; + if (p->era_omnix) + sess->protocol_version |= GG_ERA_OMNIX_MASK; + if (p->has_audio) + sess->protocol_version |= GG_HAS_AUDIO_MASK; + sess->client_version = (p->client_version) ? strdup(p->client_version) : NULL; + sess->last_sysmsg = p->last_sysmsg; + sess->image_size = p->image_size; + sess->pid = -1; + + if (p->tls == 1) { +#ifdef __GG_LIBGADU_HAVE_OPENSSL + char buf[1024]; + + OpenSSL_add_ssl_algorithms(); + + if (!RAND_status()) { + char rdata[1024]; + struct { + time_t time; + void *ptr; + } rstruct; + + time(&rstruct.time); + rstruct.ptr = (void *) &rstruct; + + RAND_seed((void *) rdata, sizeof(rdata)); + RAND_seed((void *) &rstruct, sizeof(rstruct)); + } + + sess->ssl_ctx = SSL_CTX_new(TLSv1_client_method()); + + if (!sess->ssl_ctx) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_CTX_new() failed: %s\n", buf); + goto fail; + } + + SSL_CTX_set_verify(sess->ssl_ctx, SSL_VERIFY_NONE, NULL); + + sess->ssl = SSL_new(sess->ssl_ctx); + + if (!sess->ssl) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_new() failed: %s\n", buf); + goto fail; + } +#else + gg_debug(GG_DEBUG_MISC, "// gg_login() client requested TLS but no support compiled in\n"); +#endif + } + + if (gg_proxy_enabled) { + hostname = gg_proxy_host; + sess->proxy_port = port = gg_proxy_port; + } else { + hostname = GG_APPMSG_HOST; + port = GG_APPMSG_PORT; + } + + if (!p->async) { + struct in_addr a; + + if (!p->server_addr || !p->server_port) { + if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() host \"%s\" not found\n", hostname); + goto fail; + } else { + a.s_addr = hn->s_addr; + free(hn); + } + } + } else { + a.s_addr = p->server_addr; + port = p->server_port; + } + + sess->hub_addr = a.s_addr; + + if (gg_proxy_enabled) + sess->proxy_addr = a.s_addr; + + if ((sess->fd = gg_connect(&a, port, 0)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + + if (p->server_addr && p->server_port) + sess->state = GG_STATE_CONNECTING_GG; + else + sess->state = GG_STATE_CONNECTING_HUB; + + while (sess->state != GG_STATE_CONNECTED) { + struct gg_event *e; + + if (!(e = gg_watch_fd(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() critical error in gg_watch_fd()\n"); + goto fail; + } + + if (e->type == GG_EVENT_CONN_FAILED) { + errno = EACCES; + gg_debug(GG_DEBUG_MISC, "// gg_login() could not login\n"); + gg_event_free(e); + goto fail; + } + + gg_event_free(e); + } + + return sess; + } + + if (!sess->server_addr || gg_proxy_enabled) { +#ifndef __GG_LIBGADU_HAVE_PTHREAD + if (gg_resolve(&sess->fd, &sess->pid, hostname)) { +#else + if (gg_resolve_pthread(&sess->fd, &sess->resolver, hostname)) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + } else { + if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() direct connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + } + + return sess; + +fail: + if (sess) { + if (sess->password) + free(sess->password); + if (sess->initial_descr) + free(sess->initial_descr); + free(sess); + } + + return NULL; +} + +/* + * gg_free_session() + * + * próbuje zamkn±æ po³±czenia i zwalnia pamiêæ zajmowan± przez sesjê. + * + * - sess - opis sesji + */ +void gg_free_session(struct gg_session *sess) +{ + if (!sess) + return; + + /* XXX dopisaæ zwalnianie i zamykanie wszystkiego, co mog³o zostaæ */ + + if (sess->password) + free(sess->password); + + if (sess->initial_descr) + free(sess->initial_descr); + + if (sess->client_version) + free(sess->client_version); + + if (sess->header_buf) + free(sess->header_buf); + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) + SSL_free(sess->ssl); + + if (sess->ssl_ctx) + SSL_CTX_free(sess->ssl_ctx); +#endif + +#ifdef __GG_LIBGADU_HAVE_PTHREAD + if (sess->resolver) { + pthread_cancel(*((pthread_t*) sess->resolver)); + free(sess->resolver); + sess->resolver = NULL; + } +#else + if (sess->pid != -1) { + kill(sess->pid, SIGTERM); + waitpid(sess->pid, NULL, WNOHANG); + } +#endif + + if (sess->fd != -1) + close(sess->fd); + + while (sess->images) + gg_image_queue_remove(sess, sess->images, 1); + + free(sess); +} + +/* + * gg_change_status() + * + * zmienia status u¿ytkownika. przydatne do /away i /busy oraz /quit. + * + * - sess - opis sesji + * - status - nowy status u¿ytkownika + * + * 0, -1. + */ +int gg_change_status(struct gg_session *sess, int status) +{ + struct gg_new_status p; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status(%p, %d);\n", sess, status); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + p.status = gg_fix32(status); + + sess->status = status; + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), NULL); +} + +/* + * gg_change_status_descr() + * + * zmienia status u¿ytkownika na opisowy. + * + * - sess - opis sesji + * - status - nowy status u¿ytkownika + * - descr - opis statusu + * + * 0, -1. + */ +int gg_change_status_descr(struct gg_session *sess, int status, const char *descr) +{ + struct gg_new_status p; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status_descr(%p, %d, \"%s\");\n", sess, status, descr); + + if (!sess || !descr) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + p.status = gg_fix32(status); + + sess->status = status; + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), descr, (strlen(descr) > GG_STATUS_DESCR_MAXSIZE) ? GG_STATUS_DESCR_MAXSIZE : strlen(descr), NULL); +} + +/* + * gg_change_status_descr_time() + * + * zmienia status u¿ytkownika na opisowy z godzin± powrotu. + * + * - sess - opis sesji + * - status - nowy status u¿ytkownika + * - descr - opis statusu + * - time - czas w formacie uniksowym + * + * 0, -1. + */ +int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time) +{ + struct gg_new_status p; + uint32_t newtime; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status_descr_time(%p, %d, \"%s\", %d);\n", sess, status, descr, time); + + if (!sess || !descr || !time) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + p.status = gg_fix32(status); + + sess->status = status; + + newtime = gg_fix32(time); + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), descr, (strlen(descr) > GG_STATUS_DESCR_MAXSIZE) ? GG_STATUS_DESCR_MAXSIZE : strlen(descr), &newtime, sizeof(newtime), NULL); +} + +/* + * gg_logoff() + * + * wylogowuje u¿ytkownika i zamyka po³±czenie, ale nie zwalnia pamiêci. + * + * - sess - opis sesji + */ +void gg_logoff(struct gg_session *sess) +{ + if (!sess) + return; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_logoff(%p);\n", sess); + + if (GG_S_NA(sess->status & ~GG_STATUS_FRIENDS_MASK)) + gg_change_status(sess, GG_STATUS_NOT_AVAIL); + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) + SSL_shutdown(sess->ssl); +#endif + +#ifdef __GG_LIBGADU_HAVE_PTHREAD + if (sess->resolver) { + pthread_cancel(*((pthread_t*) sess->resolver)); + free(sess->resolver); + sess->resolver = NULL; + } +#else + if (sess->pid != -1) { + kill(sess->pid, SIGTERM); + waitpid(sess->pid, NULL, WNOHANG); + sess->pid = -1; + } +#endif + + if (sess->fd != -1) { + shutdown(sess->fd, SHUT_RDWR); + close(sess->fd); + sess->fd = -1; + } +} + +/* + * gg_image_request() + * + * wysy³a ¿±danie wys³ania obrazka o podanych parametrach. + * + * - sess - opis sesji + * - recipient - numer adresata + * - size - rozmiar obrazka + * - crc32 - suma kontrolna obrazka + * + * 0/-1 + */ +int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32) +{ + struct gg_send_msg s; + struct gg_msg_image_request r; + char dummy = 0; + int res; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_image_request(%p, %d, %u, 0x%.4x);\n", sess, recipient, size, crc32); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (size < 0) { + errno = EINVAL; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(GG_CLASS_MSG); + + r.flag = 0x04; + r.size = gg_fix32(size); + r.crc32 = gg_fix32(crc32); + + res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), &dummy, 1, &r, sizeof(r), NULL); + + if (!res) { + struct gg_image_queue *q = malloc(sizeof(*q)); + char *buf; + + if (!q) { + gg_debug(GG_DEBUG_MISC, "// gg_image_request() not enough memory for image queue\n"); + return -1; + } + + buf = malloc(size); + if (size && !buf) + { + gg_debug(GG_DEBUG_MISC, "// gg_image_request() not enough memory for image\n"); + free(q); + return -1; + } + + memset(q, 0, sizeof(*q)); + + q->sender = recipient; + q->size = size; + q->crc32 = crc32; + q->image = buf; + + if (!sess->images) + sess->images = q; + else { + struct gg_image_queue *qq; + + for (qq = sess->images; qq->next; qq = qq->next) + ; + + qq->next = q; + } + } + + return res; +} + +/* + * gg_image_reply() + * + * wysy³a ¿±dany obrazek. + * + * - sess - opis sesji + * - recipient - numer adresata + * - filename - nazwa pliku + * - image - bufor z obrazkiem + * - size - rozmiar obrazka + * + * 0/-1 + */ +int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size) +{ + struct gg_msg_image_reply *r; + struct gg_send_msg s; + const char *tmp; + char buf[1910]; + int res = -1; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_image_reply(%p, %d, \"%s\", %p, %d);\n", sess, recipient, filename, image, size); + + if (!sess || !filename || !image) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (size < 0) { + errno = EINVAL; + return -1; + } + + /* wytnij ¶cie¿ki, zostaw tylko nazwê pliku */ + while ((tmp = strrchr(filename, '/')) || (tmp = strrchr(filename, '\\'))) + filename = tmp + 1; + + if (strlen(filename) < 1 || strlen(filename) > 1024) { + errno = EINVAL; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(GG_CLASS_MSG); + + buf[0] = 0; + r = (void*) &buf[1]; + + r->flag = 0x05; + r->size = gg_fix32(size); + r->crc32 = gg_fix32(gg_crc32(0, image, size)); + + while (size > 0) { + int buflen, chunklen; + + /* \0 + struct gg_msg_image_reply */ + buflen = sizeof(struct gg_msg_image_reply) + 1; + + /* w pierwszym kawa³ku jest nazwa pliku */ + if (r->flag == 0x05) { + strcpy(buf + buflen, filename); + buflen += strlen(filename) + 1; + } + + chunklen = (size >= sizeof(buf) - buflen) ? (sizeof(buf) - buflen) : size; + + memcpy(buf + buflen, image, chunklen); + size -= chunklen; + image += chunklen; + + res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), buf, buflen + chunklen, NULL); + + if (res == -1) + break; + + r->flag = 0x06; + } + + return res; +} + +/* + * gg_send_message_ctcp() + * + * wysy³a wiadomo¶æ do innego u¿ytkownika. zwraca losowy numer + * sekwencyjny, który mo¿na zignorowaæ albo wykorzystaæ do potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomo¶ci + * - recipient - numer adresata + * - message - tre¶æ wiadomo¶ci + * - message_len - d³ugo¶æ + * + * numer sekwencyjny wiadomo¶ci lub -1 w przypadku b³êdu. + */ +int gg_send_message_ctcp(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, int message_len) +{ + struct gg_send_msg s; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_ctcp(%p, %d, %u, ...);\n", sess, msgclass, recipient); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(msgclass); + + return gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, message_len, NULL); +} + +/* + * gg_send_message() + * + * wysy³a wiadomo¶æ do innego u¿ytkownika. zwraca losowy numer + * sekwencyjny, który mo¿na zignorowaæ albo wykorzystaæ do potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomo¶ci + * - recipient - numer adresata + * - message - tre¶æ wiadomo¶ci + * + * numer sekwencyjny wiadomo¶ci lub -1 w przypadku b³êdu. + */ +int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message(%p, %d, %u, %p)\n", sess, msgclass, recipient, message); + + return gg_send_message_richtext(sess, msgclass, recipient, message, NULL, 0); +} + +/* + * gg_send_message_richtext() + * + * wysy³a kolorow± wiadomo¶æ do innego u¿ytkownika. zwraca losowy numer + * sekwencyjny, który mo¿na zignorowaæ albo wykorzystaæ do potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomo¶ci + * - recipient - numer adresata + * - message - tre¶æ wiadomo¶ci + * - format - informacje o formatowaniu + * - formatlen - d³ugo¶æ informacji o formatowaniu + * + * numer sekwencyjny wiadomo¶ci lub -1 w przypadku b³êdu. + */ +int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen) +{ + struct gg_send_msg s; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_richtext(%p, %d, %u, %p, %p, %d);\n", sess, msgclass, recipient, message, format, formatlen); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!message) { + errno = EFAULT; + return -1; + } + + s.recipient = gg_fix32(recipient); + if (!sess->seq) + sess->seq = 0x01740000 | (rand() & 0xffff); + s.seq = gg_fix32(sess->seq); + s.msgclass = gg_fix32(msgclass); + sess->seq += (rand() % 0x300) + 0x300; + + if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, strlen(message) + 1, format, formatlen, NULL) == -1) + return -1; + + return gg_fix32(s.seq); +} + +/* + * gg_send_message_confer() + * + * wysy³a wiadomo¶æ do kilku u¿ytkownikow (konferencja). zwraca losowy numer + * sekwencyjny, który mo¿na zignorowaæ albo wykorzystaæ do potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomo¶ci + * - recipients_count - ilo¶æ adresatów + * - recipients - numerki adresatów + * - message - tre¶æ wiadomo¶ci + * + * numer sekwencyjny wiadomo¶ci lub -1 w przypadku b³êdu. + */ +int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_confer(%p, %d, %d, %p, %p);\n", sess, msgclass, recipients_count, recipients, message); + + return gg_send_message_confer_richtext(sess, msgclass, recipients_count, recipients, message, NULL, 0); +} + +/* + * gg_send_message_confer_richtext() + * + * wysy³a kolorow± wiadomo¶æ do kilku u¿ytkownikow (konferencja). zwraca + * losowy numer sekwencyjny, który mo¿na zignorowaæ albo wykorzystaæ do + * potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomo¶ci + * - recipients_count - ilo¶æ adresatów + * - recipients - numerki adresatów + * - message - tre¶æ wiadomo¶ci + * - format - informacje o formatowaniu + * - formatlen - d³ugo¶æ informacji o formatowaniu + * + * numer sekwencyjny wiadomo¶ci lub -1 w przypadku b³êdu. + */ +int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen) +{ + struct gg_send_msg s; + struct gg_msg_recipients r; + int i, j, k; + uin_t *recps; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_confer_richtext(%p, %d, %d, %p, %p, %p, %d);\n", sess, msgclass, recipients_count, recipients, message, format, formatlen); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!message || recipients_count <= 0 || recipients_count > 0xffff || !recipients) { + errno = EINVAL; + return -1; + } + + r.flag = 0x01; + r.count = gg_fix32(recipients_count - 1); + + if (!sess->seq) + sess->seq = 0x01740000 | (rand() & 0xffff); + s.seq = gg_fix32(sess->seq); + s.msgclass = gg_fix32(msgclass); + + recps = malloc(sizeof(uin_t) * recipients_count); + if (!recps) + return -1; + + for (i = 0; i < recipients_count; i++) { + + s.recipient = gg_fix32(recipients[i]); + + for (j = 0, k = 0; j < recipients_count; j++) + if (recipients[j] != recipients[i]) { + recps[k] = gg_fix32(recipients[j]); + k++; + } + + if (!i) + sess->seq += (rand() % 0x300) + 0x300; + + if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, strlen(message) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1) { + free(recps); + return -1; + } + } + + free(recps); + + return gg_fix32(s.seq); +} + +/* + * gg_ping() + * + * wysy³a do serwera pakiet ping. + * + * - sess - opis sesji + * + * 0, -1. + */ +int gg_ping(struct gg_session *sess) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_ping(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + return gg_send_packet(sess, GG_PING, NULL); +} + +/* + * gg_notify_ex() + * + * wysy³a serwerowi listê kontaktów (wraz z odpowiadaj±cymi im typami userów), + * dziêki czemu wie, czyj stan nas interesuje. + * + * - sess - opis sesji + * - userlist - wska¼nik do tablicy numerów + * - types - wska¼nik do tablicy typów u¿ytkowników + * - count - ilo¶æ numerków + * + * 0, -1. + */ +int gg_notify_ex(struct gg_session *sess, uin_t *userlist, char *types, int count) +{ + struct gg_notify *n; + uin_t *u; + char *t; + int i, res = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_notify_ex(%p, %p, %p, %d);\n", sess, userlist, types, count); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!userlist || !count) + return gg_send_packet(sess, GG_LIST_EMPTY, NULL); + + while (count > 0) { + int part_count, packet_type; + + if (count > 400) { + part_count = 400; + packet_type = GG_NOTIFY_FIRST; + } else { + part_count = count; + packet_type = GG_NOTIFY_LAST; + } + + if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count))) + return -1; + + for (u = userlist, t = types, i = 0; i < part_count; u++, t++, i++) { + n[i].uin = gg_fix32(*u); + n[i].dunno1 = *t; + } + + if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) { + free(n); + res = -1; + break; + } + + count -= part_count; + userlist += part_count; + types += part_count; + + free(n); + } + + return res; +} + +/* + * gg_notify() + * + * wysy³a serwerowi listê kontaktów, dziêki czemu wie, czyj stan nas + * interesuje. + * + * - sess - opis sesji + * - userlist - wska¼nik do tablicy numerów + * - count - ilo¶æ numerków + * + * 0, -1. + */ +int gg_notify(struct gg_session *sess, uin_t *userlist, int count) +{ + struct gg_notify *n; + uin_t *u; + int i, res = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_notify(%p, %p, %d);\n", sess, userlist, count); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!userlist || !count) + return gg_send_packet(sess, GG_LIST_EMPTY, NULL); + + while (count > 0) { + int part_count, packet_type; + + if (count > 400) { + part_count = 400; + packet_type = GG_NOTIFY_FIRST; + } else { + part_count = count; + packet_type = GG_NOTIFY_LAST; + } + + if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count))) + return -1; + + for (u = userlist, i = 0; i < part_count; u++, i++) { + n[i].uin = gg_fix32(*u); + n[i].dunno1 = GG_USER_NORMAL; + } + + if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) { + res = -1; + free(n); + break; + } + + free(n); + + userlist += part_count; + count -= part_count; + } + + return res; +} + +/* + * gg_add_notify_ex() + * + * dodaje do listy kontaktów dany numer w trakcie po³±czenia. + * dodawanemu u¿ytkownikowi okre¶lamy jego typ (patrz protocol.html) + * + * - sess - opis sesji + * - uin - numer + * - type - typ + * + * 0, -1. + */ +int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_add_notify_ex(%p, %u, %d);\n", sess, uin, type); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + a.uin = gg_fix32(uin); + a.dunno1 = type; + + return gg_send_packet(sess, GG_ADD_NOTIFY, &a, sizeof(a), NULL); +} + +/* + * gg_add_notify() + * + * dodaje do listy kontaktów dany numer w trakcie po³±czenia. + * + * - sess - opis sesji + * - uin - numer + * + * 0, -1. + */ +int gg_add_notify(struct gg_session *sess, uin_t uin) +{ + return gg_add_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/* + * gg_remove_notify_ex() + * + * usuwa z listy kontaktów w trakcie po³±czenia. + * usuwanemu u¿ytkownikowi okre¶lamy jego typ (patrz protocol.html) + * + * - sess - opis sesji + * - uin - numer + * - type - typ + * + * 0, -1. + */ +int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_remove_notify_ex(%p, %u, %d);\n", sess, uin, type); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + a.uin = gg_fix32(uin); + a.dunno1 = type; + + return gg_send_packet(sess, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL); +} + +/* + * gg_remove_notify() + * + * usuwa z listy kontaktów w trakcie po³±czenia. + * + * - sess - opis sesji + * - uin - numer + * + * 0, -1. + */ +int gg_remove_notify(struct gg_session *sess, uin_t uin) +{ + return gg_remove_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/* + * gg_userlist_request() + * + * wysy³a ¿±danie/zapytanie listy kontaktów na serwerze. + * + * - sess - opis sesji + * - type - rodzaj zapytania/¿±dania + * - request - tre¶æ zapytania/¿±dania (mo¿e byæ NULL) + * + * 0, -1 + */ +int gg_userlist_request(struct gg_session *sess, char type, const char *request) +{ + int len; + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!request) { + sess->userlist_blocks = 1; + return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), NULL); + } + + len = strlen(request); + + sess->userlist_blocks = 0; + + while (len > 2047) { + sess->userlist_blocks++; + + if (gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, 2047, NULL) == -1) + return -1; + + if (type == GG_USERLIST_PUT) + type = GG_USERLIST_PUT_MORE; + + request += 2047; + len -= 2047; + } + + sess->userlist_blocks++; + + return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, len, NULL); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/libgadu.h b/kopete/protocols/gadu/libgadu/libgadu.h new file mode 100644 index 00000000..18588500 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/libgadu.h @@ -0,0 +1,1310 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2003 Wojtek Kaniewski + * Robert J. Wo¼ny + * Arkadiusz Mi¶kiewicz + * Tomasz Chiliñski + * Piotr Wysocki + * Dawid Jarosz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef __GG_LIBGADU_H +#define __GG_LIBGADU_H + +#ifdef __cplusplus +#ifdef _WIN32 +#pragma pack(push, 1) +#endif +extern "C" { +#endif + +#include +#include +#include +#include + +#ifdef __GG_LIBGADU_HAVE_OPENSSL +#include +#endif + +/* + * typedef uin_t + * + * typ reprezentuj±cy numer osoby. + */ +typedef uint32_t uin_t; + +/* + * ogólna struktura opisuj±ca ró¿ne sesje. przydatna w klientach. + */ +#define gg_common_head(x) \ + int fd; /* podgl±dany deskryptor */ \ + int check; /* sprawdzamy zapis czy odczyt */ \ + int state; /* aktualny stan maszynki */ \ + int error; /* kod b³êdu dla GG_STATE_ERROR */ \ + int type; /* rodzaj sesji */ \ + int id; /* identyfikator */ \ + int timeout; /* sugerowany timeout w sekundach */ \ + int (*callback)(x*); /* callback przy zmianach */ \ + void (*destroy)(x*); /* funkcja niszczenia */ + +struct gg_common { + gg_common_head(struct gg_common) +}; + +struct gg_image_queue; + +/* + * struct gg_session + * + * struktura opisuj±ca dan± sesjê. tworzona przez gg_login(), zwalniana + * przez gg_free_session(). + */ +struct gg_session { + gg_common_head(struct gg_session) + + int async; /* czy po³±czenie jest asynchroniczne */ + int pid; /* pid procesu resolvera */ + int port; /* port, z którym siê ³±czymy */ + int seq; /* numer sekwencyjny ostatniej wiadomo¶ci */ + int last_pong; /* czas otrzymania ostatniego ping/pong */ + int last_event; /* czas otrzymania ostatniego pakietu */ + + struct gg_event *event; /* zdarzenie po ->callback() */ + + uint32_t proxy_addr; /* adres proxy, keszowany */ + uint16_t proxy_port; /* port proxy */ + + uint32_t hub_addr; /* adres huba po resolvniêciu */ + uint32_t server_addr; /* adres serwera, od huba */ + + uint32_t client_addr; /* adres klienta */ + uint16_t client_port; /* port, na którym klient s³ucha */ + + uint32_t external_addr; /* adres zewnetrzny klienta */ + uint16_t external_port; /* port zewnetrzny klienta */ + + uin_t uin; /* numerek klienta */ + char *password; /* i jego has³o. zwalniane automagicznie */ + + int initial_status; /* pocz±tkowy stan klienta */ + int status; /* aktualny stan klienta */ + + char *recv_buf; /* bufor na otrzymywane pakiety */ + int recv_done; /* ile ju¿ wczytano do bufora */ + int recv_left; /* i ile jeszcze trzeba wczytaæ */ + + int protocol_version; /* wersja u¿ywanego protoko³u */ + char *client_version; /* wersja u¿ywanego klienta */ + int last_sysmsg; /* ostatnia wiadomo¶æ systemowa */ + + char *initial_descr; /* pocz±tkowy opis stanu klienta */ + + void *resolver; /* wska¼nik na informacje resolvera */ + + char *header_buf; /* bufor na pocz±tek nag³ówka */ + unsigned int header_done;/* ile ju¿ mamy */ + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + SSL *ssl; /* sesja TLS */ + SSL_CTX *ssl_ctx; /* kontekst sesji? */ +#else + void *ssl; /* zachowujemy ABI */ + void *ssl_ctx; +#endif + + int image_size; /* maksymalny rozmiar obrazków w KiB */ + + char *userlist_reply; /* fragment odpowiedzi listy kontaktów */ + + int userlist_blocks; /* na ile kawa³ków podzielono listê kontaktów */ + + struct gg_image_queue *images; /* aktualnie wczytywane obrazki */ +}; + +/* + * struct gg_http + * + * ogólna struktura opisuj±ca stan wszystkich operacji HTTP. tworzona + * przez gg_http_connect(), zwalniana przez gg_http_free(). + */ +struct gg_http { + gg_common_head(struct gg_http) + + int async; /* czy po³±czenie asynchroniczne */ + int pid; /* pid procesu resolvera */ + int port; /* port, z którym siê ³±czymy */ + + char *query; /* bufor zapytania http */ + char *header; /* bufor nag³ówka */ + int header_size; /* rozmiar wczytanego nag³ówka */ + char *body; /* bufor otrzymanych informacji */ + unsigned int body_size; /* oczekiwana ilo¶æ informacji */ + + void *data; /* dane danej operacji http */ + + char *user_data; /* dane u¿ytkownika, nie s± zwalniane przez gg_http_free() */ + + void *resolver; /* wska¼nik na informacje resolvera */ + + unsigned int body_done; /* ile ju¿ tre¶ci odebrano? */ +}; + +#ifdef __GNUC__ +#define GG_PACKED __attribute__ ((packed)) +#else +#define GG_PACKED +#endif + +#define GG_MAX_PATH 276 + +/* + * struct gg_file_info + * + * odpowiednik windowsowej struktury WIN32_FIND_DATA niezbêdnej przy + * wysy³aniu plików. + */ +struct gg_file_info { + uint32_t mode; /* dwFileAttributes */ + uint32_t ctime[2]; /* ftCreationTime */ + uint32_t atime[2]; /* ftLastAccessTime */ + uint32_t mtime[2]; /* ftLastWriteTime */ + uint32_t size_hi; /* nFileSizeHigh */ + uint32_t size; /* nFileSizeLow */ + uint32_t reserved0; /* dwReserved0 */ + uint32_t reserved1; /* dwReserved1 */ + unsigned char filename[GG_MAX_PATH - 14]; /* cFileName */ + unsigned char short_filename[14]; /* cAlternateFileName */ +} GG_PACKED; + +/* + * struct gg_dcc + * + * struktura opisuj±ca nas³uchuj±ce gniazdo po³±czeñ miêdzy klientami. + * tworzona przez gg_dcc_socket_create(), zwalniana przez gg_dcc_free(). + */ +struct gg_dcc { + gg_common_head(struct gg_dcc) + + struct gg_event *event; /* opis zdarzenia */ + + int active; /* czy to my siê ³±czymy? */ + int port; /* port, na którym siedzi */ + uin_t uin; /* uin klienta */ + uin_t peer_uin; /* uin drugiej strony */ + int file_fd; /* deskryptor pliku */ + unsigned int offset; /* offset w pliku */ + unsigned int chunk_size;/* rozmiar kawa³ka */ + unsigned int chunk_offset;/* offset w aktualnym kawa³ku */ + struct gg_file_info file_info; + /* informacje o pliku */ + int established; /* po³±czenie ustanowione */ + char *voice_buf; /* bufor na pakiet po³±czenia g³osowego */ + int incoming; /* po³±czenie przychodz±ce */ + char *chunk_buf; /* bufor na kawa³ek danych */ + uint32_t remote_addr; /* adres drugiej strony */ + uint16_t remote_port; /* port drugiej strony */ +}; + +/* + * enum gg_session_t + * + * rodzaje sesji. + */ +enum gg_session_t { + GG_SESSION_GG = 1, /* po³±czenie z serwerem gg */ + GG_SESSION_HTTP, /* ogólna sesja http */ + GG_SESSION_SEARCH, /* szukanie */ + GG_SESSION_REGISTER, /* rejestrowanie */ + GG_SESSION_REMIND, /* przypominanie has³a */ + GG_SESSION_PASSWD, /* zmiana has³a */ + GG_SESSION_CHANGE, /* zmiana informacji o sobie */ + GG_SESSION_DCC, /* ogólne po³±czenie DCC */ + GG_SESSION_DCC_SOCKET, /* nas³uchuj±cy socket */ + GG_SESSION_DCC_SEND, /* wysy³anie pliku */ + GG_SESSION_DCC_GET, /* odbieranie pliku */ + GG_SESSION_DCC_VOICE, /* rozmowa g³osowa */ + GG_SESSION_USERLIST_GET, /* pobieranie userlisty */ + GG_SESSION_USERLIST_PUT, /* wysy³anie userlisty */ + GG_SESSION_UNREGISTER, /* usuwanie konta */ + GG_SESSION_USERLIST_REMOVE, /* usuwanie userlisty */ + GG_SESSION_TOKEN, /* pobieranie tokenu */ + + GG_SESSION_USER0 = 256, /* zdefiniowana dla u¿ytkownika */ + GG_SESSION_USER1, /* j.w. */ + GG_SESSION_USER2, /* j.w. */ + GG_SESSION_USER3, /* j.w. */ + GG_SESSION_USER4, /* j.w. */ + GG_SESSION_USER5, /* j.w. */ + GG_SESSION_USER6, /* j.w. */ + GG_SESSION_USER7 /* j.w. */ +}; + +/* + * enum gg_state_t + * + * opisuje stan asynchronicznej maszyny. + */ +enum gg_state_t { + /* wspólne */ + GG_STATE_IDLE = 0, /* nie powinno wyst±piæ. */ + GG_STATE_RESOLVING, /* wywo³a³ gethostbyname() */ + GG_STATE_CONNECTING, /* wywo³a³ connect() */ + GG_STATE_READING_DATA, /* czeka na dane http */ + GG_STATE_ERROR, /* wyst±pi³ b³±d. kod w x->error */ + + /* gg_session */ + GG_STATE_CONNECTING_HUB, /* wywo³a³ connect() na huba */ + GG_STATE_CONNECTING_GG, /* wywo³a³ connect() na serwer */ + GG_STATE_READING_KEY, /* czeka na klucz */ + GG_STATE_READING_REPLY, /* czeka na odpowied¼ */ + GG_STATE_CONNECTED, /* po³±czy³ siê */ + + /* gg_http */ + GG_STATE_SENDING_QUERY, /* wysy³a zapytanie http */ + GG_STATE_READING_HEADER, /* czeka na nag³ówek http */ + GG_STATE_PARSING, /* przetwarza dane */ + GG_STATE_DONE, /* skoñczy³ */ + + /* gg_dcc */ + GG_STATE_LISTENING, /* czeka na po³±czenia */ + GG_STATE_READING_UIN_1, /* czeka na uin peera */ + GG_STATE_READING_UIN_2, /* czeka na swój uin */ + GG_STATE_SENDING_ACK, /* wysy³a potwierdzenie dcc */ + GG_STATE_READING_ACK, /* czeka na potwierdzenie dcc */ + GG_STATE_READING_REQUEST, /* czeka na komendê */ + GG_STATE_SENDING_REQUEST, /* wysy³a komendê */ + GG_STATE_SENDING_FILE_INFO, /* wysy³a informacje o pliku */ + GG_STATE_READING_PRE_FILE_INFO, /* czeka na pakiet przed file_info */ + GG_STATE_READING_FILE_INFO, /* czeka na informacje o pliku */ + GG_STATE_SENDING_FILE_ACK, /* wysy³a potwierdzenie pliku */ + GG_STATE_READING_FILE_ACK, /* czeka na potwierdzenie pliku */ + GG_STATE_SENDING_FILE_HEADER, /* wysy³a nag³ówek pliku */ + GG_STATE_READING_FILE_HEADER, /* czeka na nag³ówek */ + GG_STATE_GETTING_FILE, /* odbiera plik */ + GG_STATE_SENDING_FILE, /* wysy³a plik */ + GG_STATE_READING_VOICE_ACK, /* czeka na potwierdzenie voip */ + GG_STATE_READING_VOICE_HEADER, /* czeka na rodzaj bloku voip */ + GG_STATE_READING_VOICE_SIZE, /* czeka na rozmiar bloku voip */ + GG_STATE_READING_VOICE_DATA, /* czeka na dane voip */ + GG_STATE_SENDING_VOICE_ACK, /* wysy³a potwierdzenie voip */ + GG_STATE_SENDING_VOICE_REQUEST, /* wysy³a ¿±danie voip */ + GG_STATE_READING_TYPE, /* czeka na typ po³±czenia */ + + /* nowe. bez sensu jest to API. */ + GG_STATE_TLS_NEGOTIATION /* negocjuje po³±czenie TLS */ +}; + +/* + * enum gg_check_t + * + * informuje, co proces klienta powinien sprawdziæ na deskryptorze danego + * po³±czenia. + */ +enum gg_check_t { + GG_CHECK_NONE = 0, /* nic. nie powinno wyst±piæ */ + GG_CHECK_WRITE = 1, /* sprawdzamy mo¿liwo¶æ zapisu */ + GG_CHECK_READ = 2 /* sprawdzamy mo¿liwo¶æ odczytu */ +}; + +/* + * struct gg_login_params + * + * parametry gg_login(). przeniesiono do struktury, ¿eby unikn±æ problemów + * z ci±g³ymi zmianami API, gdy dodano co¶ nowego do protoko³u. + */ +struct gg_login_params { + uin_t uin; /* numerek */ + char *password; /* has³o */ + int async; /* asynchroniczne sockety? */ + int status; /* pocz±tkowy status klienta */ + char *status_descr; /* opis statusu */ + uint32_t server_addr; /* adres serwera gg */ + uint16_t server_port; /* port serwera gg */ + uint32_t client_addr; /* adres dcc klienta */ + uint16_t client_port; /* port dcc klienta */ + int protocol_version; /* wersja protoko³u */ + char *client_version; /* wersja klienta */ + int has_audio; /* czy ma d¼wiêk? */ + int last_sysmsg; /* ostatnia wiadomo¶æ systemowa */ + uint32_t external_addr; /* adres widziany na zewnatrz */ + uint16_t external_port; /* port widziany na zewnatrz */ + int tls; /* czy ³±czymy po TLS? */ + int image_size; /* maksymalny rozmiar obrazka w KiB */ + int era_omnix; /* czy udawaæ klienta era omnix? */ + + char dummy[6 * sizeof(int)]; /* miejsce na kolejnych 6 zmiennych, + * ¿eby z dodaniem parametru nie + * zmienia³ siê rozmiar struktury */ +}; + +struct gg_session *gg_login(const struct gg_login_params *p); +void gg_free_session(struct gg_session *sess); +void gg_logoff(struct gg_session *sess); +int gg_change_status(struct gg_session *sess, int status); +int gg_change_status_descr(struct gg_session *sess, int status, const char *descr); +int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time); +int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message); +int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen); +int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message); +int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen); +int gg_send_message_ctcp(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, int message_len); +int gg_ping(struct gg_session *sess); +int gg_userlist_request(struct gg_session *sess, char type, const char *request); +int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32); +int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size); + +uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len); + +struct gg_image_queue { + uin_t sender; /* nadawca obrazka */ + uint32_t size; /* rozmiar */ + uint32_t crc32; /* suma kontrolna */ + char *filename; /* nazwa pliku */ + char *image; /* bufor z obrazem */ + uint32_t done; /* ile ju¿ wczytano */ + + struct gg_image_queue *next; /* nastêpny na li¶cie */ +}; + +/* + * enum gg_event_t + * + * rodzaje zdarzeñ. + */ +enum gg_event_t { + GG_EVENT_NONE = 0, /* nic siê nie wydarzy³o */ + GG_EVENT_MSG, /* otrzymano wiadomo¶æ */ + GG_EVENT_NOTIFY, /* kto¶ siê pojawi³ */ + GG_EVENT_NOTIFY_DESCR, /* kto¶ siê pojawi³ z opisem */ + GG_EVENT_STATUS, /* kto¶ zmieni³ stan */ + GG_EVENT_ACK, /* potwierdzenie wys³ania wiadomo¶ci */ + GG_EVENT_PONG, /* pakiet pong */ + GG_EVENT_CONN_FAILED, /* po³±czenie siê nie uda³o */ + GG_EVENT_CONN_SUCCESS, /* po³±czenie siê powiod³o */ + GG_EVENT_DISCONNECT, /* serwer zrywa po³±czenie */ + + GG_EVENT_DCC_NEW, /* nowe po³±czenie miêdzy klientami */ + GG_EVENT_DCC_ERROR, /* b³±d po³±czenia miêdzy klientami */ + GG_EVENT_DCC_DONE, /* zakoñczono po³±czenie */ + GG_EVENT_DCC_CLIENT_ACCEPT, /* moment akceptacji klienta */ + GG_EVENT_DCC_CALLBACK, /* klient siê po³±czy³ na ¿±danie */ + GG_EVENT_DCC_NEED_FILE_INFO, /* nale¿y wype³niæ file_info */ + GG_EVENT_DCC_NEED_FILE_ACK, /* czeka na potwierdzenie pliku */ + GG_EVENT_DCC_NEED_VOICE_ACK, /* czeka na potwierdzenie rozmowy */ + GG_EVENT_DCC_VOICE_DATA, /* ramka danych rozmowy g³osowej */ + + GG_EVENT_PUBDIR50_SEARCH_REPLY, /* odpowiedz wyszukiwania */ + GG_EVENT_PUBDIR50_READ, /* odczytano w³asne dane z katalogu */ + GG_EVENT_PUBDIR50_WRITE, /* wpisano w³asne dane do katalogu */ + + GG_EVENT_STATUS60, /* kto¶ zmieni³ stan w GG 6.0 */ + GG_EVENT_NOTIFY60, /* kto¶ siê pojawi³ w GG 6.0 */ + GG_EVENT_USERLIST, /* odpowied¼ listy kontaktów w GG 6.0 */ + GG_EVENT_IMAGE_REQUEST, /* pro¶ba o wys³anie obrazka GG 6.0 */ + GG_EVENT_IMAGE_REPLY, /* podes³any obrazek GG 6.0 */ + GG_EVENT_DCC_ACK /* potwierdzenie transmisji */ +}; + +#define GG_EVENT_SEARCH50_REPLY GG_EVENT_PUBDIR50_SEARCH_REPLY + +/* + * enum gg_failure_t + * + * okre¶la powód nieudanego po³±czenia. + */ +enum gg_failure_t { + GG_FAILURE_RESOLVING = 1, /* nie znaleziono serwera */ + GG_FAILURE_CONNECTING, /* nie mo¿na siê po³±czyæ */ + GG_FAILURE_INVALID, /* serwer zwróci³ nieprawid³owe dane */ + GG_FAILURE_READING, /* zerwano po³±czenie podczas odczytu */ + GG_FAILURE_WRITING, /* zerwano po³±czenie podczas zapisu */ + GG_FAILURE_PASSWORD, /* nieprawid³owe has³o */ + GG_FAILURE_404, /* XXX nieu¿ywane */ + GG_FAILURE_TLS, /* b³±d negocjacji TLS */ + GG_FAILURE_NEED_EMAIL, /* serwer roz³±czy³ nas z pro¶b± o zmianê emaila */ + GG_FAILURE_INTRUDER, /* za du¿o prób po³±czenia siê z nieprawid³owym has³em */ + GG_FAILURE_UNAVAILABLE /* serwery s± wy³±czone */ +}; + +/* + * enum gg_error_t + * + * okre¶la rodzaj b³êdu wywo³anego przez dan± operacjê. nie zawiera + * przesadnie szczegó³owych informacji o powodzie b³êdu, by nie komplikowaæ + * obs³ugi b³êdów. je¶li wymagana jest wiêksza dok³adno¶æ, nale¿y sprawdziæ + * zawarto¶æ zmiennej errno. + */ +enum gg_error_t { + GG_ERROR_RESOLVING = 1, /* b³±d znajdowania hosta */ + GG_ERROR_CONNECTING, /* b³±d ³aczenia siê */ + GG_ERROR_READING, /* b³±d odczytu */ + GG_ERROR_WRITING, /* b³±d wysy³ania */ + + GG_ERROR_DCC_HANDSHAKE, /* b³±d negocjacji */ + GG_ERROR_DCC_FILE, /* b³±d odczytu/zapisu pliku */ + GG_ERROR_DCC_EOF, /* plik siê skoñczy³? */ + GG_ERROR_DCC_NET, /* b³±d wysy³ania/odbierania */ + GG_ERROR_DCC_REFUSED /* po³±czenie odrzucone przez usera */ +}; + +/* + * struktury dotycz±ce wyszukiwania w GG 5.0. NIE NALE¯Y SIÊ DO NICH + * ODWO£YWAÆ BEZPO¦REDNIO! do dostêpu do nich s³u¿± funkcje gg_pubdir50_*() + */ +struct gg_pubdir50_entry { + int num; + char *field; + char *value; +}; + +struct gg_pubdir50_s { + int count; + uin_t next; + int type; + uint32_t seq; + struct gg_pubdir50_entry *entries; + int entries_count; +}; + +/* + * typedef gg_pubdir_50_t + * + * typ opisuj±cy zapytanie lub wynik zapytania katalogu publicznego + * z protoko³u GG 5.0. nie nale¿y siê odwo³ywaæ bezpo¶rednio do jego + * pól -- s³u¿± do tego funkcje gg_pubdir50_*() + */ +typedef struct gg_pubdir50_s *gg_pubdir50_t; + +/* + * struct gg_event + * + * struktura opisuj±ca rodzaj zdarzenia. wychodzi z gg_watch_fd() lub + * z gg_dcc_watch_fd() + */ +struct gg_event { + int type; /* rodzaj zdarzenia -- gg_event_t */ + union { /* @event */ + struct gg_notify_reply *notify; /* informacje o li¶cie kontaktów -- GG_EVENT_NOTIFY */ + + enum gg_failure_t failure; /* b³±d po³±czenia -- GG_EVENT_FAILURE */ + + struct gg_dcc *dcc_new; /* nowe po³±czenie bezpo¶rednie -- GG_EVENT_DCC_NEW */ + + int dcc_error; /* b³±d po³±czenia bezpo¶redniego -- GG_EVENT_DCC_ERROR */ + + gg_pubdir50_t pubdir50; /* wynik operacji zwi±zanej z katalogiem publicznym -- GG_EVENT_PUBDIR50_* */ + + struct { /* @msg odebrano wiadomo¶æ -- GG_EVENT_MSG */ + uin_t sender; /* numer nadawcy */ + int msgclass; /* klasa wiadomo¶ci */ + time_t time; /* czas nadania */ + unsigned char *message; /* tre¶æ wiadomo¶ci */ + + int recipients_count; /* ilo¶æ odbiorców konferencji */ + uin_t *recipients; /* odbiorcy konferencji */ + + int formats_length; /* d³ugo¶æ informacji o formatowaniu tekstu */ + void *formats; /* informacje o formatowaniu tekstu */ + } msg; + + struct { /* @notify_descr informacje o li¶cie kontaktów z opisami stanu -- GG_EVENT_NOTIFY_DESCR */ + struct gg_notify_reply *notify; /* informacje o li¶cie kontaktów */ + char *descr; /* opis stanu */ + } notify_descr; + + struct { /* @status zmiana stanu -- GG_EVENT_STATUS */ + uin_t uin; /* numer */ + uint32_t status; /* nowy stan */ + char *descr; /* opis stanu */ + } status; + + struct { /* @status60 zmiana stanu -- GG_EVENT_STATUS60 */ + uin_t uin; /* numer */ + int status; /* nowy stan */ + uint32_t remote_ip; /* adres ip */ + uint16_t remote_port; /* port */ + int version; /* wersja klienta */ + int image_size; /* maksymalny rozmiar grafiki w KiB */ + char *descr; /* opis stanu */ + time_t time; /* czas powrotu */ + } status60; + + struct { /* @notify60 informacja o li¶cie kontaktów -- GG_EVENT_NOTIFY60 */ + uin_t uin; /* numer */ + int status; /* stan */ + uint32_t remote_ip; /* adres ip */ + uint16_t remote_port; /* port */ + int version; /* wersja klienta */ + int image_size; /* maksymalny rozmiar grafiki w KiB */ + char *descr; /* opis stanu */ + time_t time; /* czas powrotu */ + } *notify60; + + struct { /* @ack potwierdzenie wiadomo¶ci -- GG_EVENT_ACK */ + uin_t recipient; /* numer odbiorcy */ + int status; /* stan dorêczenia wiadomo¶ci */ + int seq; /* numer sekwencyjny wiadomo¶ci */ + } ack; + + struct { /* @dcc_voice_data otrzymano dane d¼wiêkowe -- GG_EVENT_DCC_VOICE_DATA */ + uint8_t *data; /* dane d¼wiêkowe */ + int length; /* ilo¶æ danych d¼wiêkowych */ + } dcc_voice_data; + + struct { /* @userlist odpowied¼ listy kontaktów serwera */ + char type; /* rodzaj odpowiedzi */ + char *reply; /* tre¶æ odpowiedzi */ + } userlist; + + struct { /* @image_request pro¶ba o obrazek */ + uin_t sender; /* nadawca pro¶by */ + uint32_t size; /* rozmiar obrazka */ + uint32_t crc32; /* suma kontrolna */ + } image_request; + + struct { /* @image_reply odpowied¼ z obrazkiem */ + uin_t sender; /* nadawca odpowiedzi */ + uint32_t size; /* rozmiar obrazka */ + uint32_t crc32; /* suma kontrolna */ + char *filename; /* nazwa pliku */ + char *image; /* bufor z obrazkiem */ + } image_reply; + } event; +}; + +struct gg_event *gg_watch_fd(struct gg_session *sess); +void gg_event_free(struct gg_event *e); +#define gg_free_event gg_event_free + +/* + * funkcje obs³ugi listy kontaktów. + */ +int gg_notify_ex(struct gg_session *sess, uin_t *userlist, char *types, int count); +int gg_notify(struct gg_session *sess, uin_t *userlist, int count); +int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type); +int gg_add_notify(struct gg_session *sess, uin_t uin); +int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type); +int gg_remove_notify(struct gg_session *sess, uin_t uin); + +/* + * funkcje obs³ugi http. + */ +struct gg_http *gg_http_connect(const char *hostname, int port, int async, const char *method, const char *path, const char *header); +int gg_http_watch_fd(struct gg_http *h); +void gg_http_stop(struct gg_http *h); +void gg_http_free(struct gg_http *h); +void gg_http_free_fields(struct gg_http *h); +#define gg_free_http gg_http_free + +/* + * struktury opisuj±ca kryteria wyszukiwania dla gg_search(). nieaktualne, + * zast±pione przez gg_pubdir50_t. pozostawiono je dla zachowania ABI. + */ +struct gg_search_request { + int active; + unsigned int start; + char *nickname; + char *first_name; + char *last_name; + char *city; + int gender; + int min_birth; + int max_birth; + char *email; + char *phone; + uin_t uin; +}; + +struct gg_search { + int count; + struct gg_search_result *results; +}; + +struct gg_search_result { + uin_t uin; + char *first_name; + char *last_name; + char *nickname; + int born; + int gender; + char *city; + int active; +}; + +#define GG_GENDER_NONE 0 +#define GG_GENDER_FEMALE 1 +#define GG_GENDER_MALE 2 + +/* + * funkcje wyszukiwania. + */ +struct gg_http *gg_search(const struct gg_search_request *r, int async); +int gg_search_watch_fd(struct gg_http *f); +void gg_free_search(struct gg_http *f); +#define gg_search_free gg_free_search + +const struct gg_search_request *gg_search_request_mode_0(char *nickname, char *first_name, char *last_name, char *city, int gender, int min_birth, int max_birth, int active, int start); +const struct gg_search_request *gg_search_request_mode_1(char *email, int active, int start); +const struct gg_search_request *gg_search_request_mode_2(char *phone, int active, int start); +const struct gg_search_request *gg_search_request_mode_3(uin_t uin, int active, int start); +void gg_search_request_free(struct gg_search_request *r); + +/* + * funkcje obs³ugi katalogu publicznego zgodne z GG 5.0. tym razem funkcje + * zachowuj± pewien poziom abstrakcji, ¿eby unikn±æ zmian ABI przy zmianach + * w protokole. + * + * NIE NALE¯Y SIÊ ODWO£YWAÆ DO PÓL gg_pubdir50_t BEZPO¦REDNIO! + */ +uint32_t gg_pubdir50(struct gg_session *sess, gg_pubdir50_t req); +gg_pubdir50_t gg_pubdir50_new(int type); +int gg_pubdir50_add(gg_pubdir50_t req, const char *field, const char *value); +int gg_pubdir50_seq_set(gg_pubdir50_t req, uint32_t seq); +const char *gg_pubdir50_get(gg_pubdir50_t res, int num, const char *field); +int gg_pubdir50_type(gg_pubdir50_t res); +int gg_pubdir50_count(gg_pubdir50_t res); +uin_t gg_pubdir50_next(gg_pubdir50_t res); +uint32_t gg_pubdir50_seq(gg_pubdir50_t res); +void gg_pubdir50_free(gg_pubdir50_t res); + +#define GG_PUBDIR50_UIN "FmNumber" +#define GG_PUBDIR50_STATUS "FmStatus" +#define GG_PUBDIR50_FIRSTNAME "firstname" +#define GG_PUBDIR50_LASTNAME "lastname" +#define GG_PUBDIR50_NICKNAME "nickname" +#define GG_PUBDIR50_BIRTHYEAR "birthyear" +#define GG_PUBDIR50_CITY "city" +#define GG_PUBDIR50_GENDER "gender" +#define GG_PUBDIR50_GENDER_FEMALE "1" +#define GG_PUBDIR50_GENDER_MALE "2" +#define GG_PUBDIR50_GENDER_SET_FEMALE "2" +#define GG_PUBDIR50_GENDER_SET_MALE "1" +#define GG_PUBDIR50_ACTIVE "ActiveOnly" +#define GG_PUBDIR50_ACTIVE_TRUE "1" +#define GG_PUBDIR50_START "fmstart" +#define GG_PUBDIR50_FAMILYNAME "familyname" +#define GG_PUBDIR50_FAMILYCITY "familycity" + +int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length); + +/* + * struct gg_pubdir + * + * operacje na katalogu publicznym. + */ +struct gg_pubdir { + int success; /* czy siê uda³o */ + uin_t uin; /* otrzymany numerek. 0 je¶li b³±d */ +}; + +/* ogólne funkcje, nie powinny byæ u¿ywane */ +int gg_pubdir_watch_fd(struct gg_http *f); +void gg_pubdir_free(struct gg_http *f); +#define gg_free_pubdir gg_pubdir_free + +struct gg_token { + int width; /* szeroko¶æ obrazka */ + int height; /* wysoko¶æ obrazka */ + int length; /* ilo¶æ znaków w tokenie */ + char *tokenid; /* id tokenu */ +}; + +/* funkcje dotycz±ce tokenów */ +struct gg_http *gg_token(int async); +int gg_token_watch_fd(struct gg_http *h); +void gg_token_free(struct gg_http *h); + +/* rejestracja nowego numerka */ +struct gg_http *gg_register(const char *email, const char *password, int async); +struct gg_http *gg_register2(const char *email, const char *password, const char *qa, int async); +struct gg_http *gg_register3(const char *email, const char *password, const char *tokenid, const char *tokenval, int async); +#define gg_register_watch_fd gg_pubdir_watch_fd +#define gg_register_free gg_pubdir_free +#define gg_free_register gg_pubdir_free + +struct gg_http *gg_unregister(uin_t uin, const char *password, const char *email, int async); +struct gg_http *gg_unregister2(uin_t uin, const char *password, const char *qa, int async); +struct gg_http *gg_unregister3(uin_t uin, const char *password, const char *tokenid, const char *tokenval, int async); +#define gg_unregister_watch_fd gg_pubdir_watch_fd +#define gg_unregister_free gg_pubdir_free + +/* przypomnienie has³a e-mailem */ +struct gg_http *gg_remind_passwd(uin_t uin, int async); +struct gg_http *gg_remind_passwd2(uin_t uin, const char *tokenid, const char *tokenval, int async); +struct gg_http *gg_remind_passwd3(uin_t uin, const char *email, const char *tokenid, const char *tokenval, int async); +#define gg_remind_passwd_watch_fd gg_pubdir_watch_fd +#define gg_remind_passwd_free gg_pubdir_free +#define gg_free_remind_passwd gg_pubdir_free + +/* zmiana has³a */ +struct gg_http *gg_change_passwd(uin_t uin, const char *passwd, const char *newpasswd, const char *newemail, int async); +struct gg_http *gg_change_passwd2(uin_t uin, const char *passwd, const char *newpasswd, const char *email, const char *newemail, int async); +struct gg_http *gg_change_passwd3(uin_t uin, const char *passwd, const char *newpasswd, const char *qa, int async); +struct gg_http *gg_change_passwd4(uin_t uin, const char *email, const char *passwd, const char *newpasswd, const char *tokenid, const char *tokenval, int async); +#define gg_change_passwd_free gg_pubdir_free +#define gg_free_change_passwd gg_pubdir_free + +/* + * struct gg_change_info_request + * + * opis ¿±dania zmiany informacji w katalogu publicznym. + */ +struct gg_change_info_request { + char *first_name; /* imiê */ + char *last_name; /* nazwisko */ + char *nickname; /* pseudonim */ + char *email; /* email */ + int born; /* rok urodzenia */ + int gender; /* p³eæ */ + char *city; /* miasto */ +}; + +struct gg_change_info_request *gg_change_info_request_new(const char *first_name, const char *last_name, const char *nickname, const char *email, int born, int gender, const char *city); +void gg_change_info_request_free(struct gg_change_info_request *r); + +struct gg_http *gg_change_info(uin_t uin, const char *passwd, const struct gg_change_info_request *request, int async); +#define gg_change_pubdir_watch_fd gg_pubdir_watch_fd +#define gg_change_pubdir_free gg_pubdir_free +#define gg_free_change_pubdir gg_pubdir_free + +/* + * funkcje dotycz±ce listy kontaktów na serwerze. + */ +struct gg_http *gg_userlist_get(uin_t uin, const char *password, int async); +int gg_userlist_get_watch_fd(struct gg_http *f); +void gg_userlist_get_free(struct gg_http *f); + +struct gg_http *gg_userlist_put(uin_t uin, const char *password, const char *contacts, int async); +int gg_userlist_put_watch_fd(struct gg_http *f); +void gg_userlist_put_free(struct gg_http *f); + +struct gg_http *gg_userlist_remove(uin_t uin, const char *password, int async); +int gg_userlist_remove_watch_fd(struct gg_http *f); +void gg_userlist_remove_free(struct gg_http *f); + + + +/* + * funkcje dotycz±ce komunikacji miêdzy klientami. + */ +extern int gg_dcc_port; /* port, na którym nas³uchuje klient */ +extern unsigned long gg_dcc_ip; /* adres, na którym nas³uchuje klient */ + +int gg_dcc_request(struct gg_session *sess, uin_t uin); + +struct gg_dcc *gg_dcc_send_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin); +struct gg_dcc *gg_dcc_get_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin); +struct gg_dcc *gg_dcc_voice_chat(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin); +void gg_dcc_set_type(struct gg_dcc *d, int type); +int gg_dcc_fill_file_info(struct gg_dcc *d, const char *filename); +int gg_dcc_fill_file_info2(struct gg_dcc *d, const char *filename, const char *local_filename); +int gg_dcc_voice_send(struct gg_dcc *d, char *buf, int length); + +#define GG_DCC_VOICE_FRAME_LENGTH 195 +#define GG_DCC_VOICE_FRAME_LENGTH_505 326 + +struct gg_dcc *gg_dcc_socket_create(uin_t uin, uint16_t port); +#define gg_dcc_socket_free gg_free_dcc +#define gg_dcc_socket_watch_fd gg_dcc_watch_fd + +struct gg_event *gg_dcc_watch_fd(struct gg_dcc *d); + +void gg_dcc_free(struct gg_dcc *c); +#define gg_free_dcc gg_dcc_free + +/* + * je¶li chcemy sobie podebugowaæ, wystarczy ustawiæ `gg_debug_level'. + * niestety w miarê przybywania wpisów `gg_debug(...)' nie chcia³o mi + * siê ustawiaæ odpowiednich leveli, wiêc wiêkszo¶æ sz³a do _MISC. + */ +extern int gg_debug_level; /* poziom debugowania. mapa bitowa sta³ych GG_DEBUG_* */ + +/* + * mo¿na podaæ wska¼nik do funkcji obs³uguj±cej wywo³ania gg_debug(). + * nieoficjalne, nieudokumentowane, mo¿e siê zmieniæ. je¶li kto¶ jest + * zainteresowany, niech da znaæ na ekg-devel. + */ +extern void (*gg_debug_handler)(int level, const char *format, va_list ap); + +/* + * mo¿na podaæ plik, do którego bêd± zapisywane teksty z gg_debug(). + */ +extern FILE *gg_debug_file; + +#define GG_DEBUG_NET 1 +#define GG_DEBUG_TRAFFIC 2 +#define GG_DEBUG_DUMP 4 +#define GG_DEBUG_FUNCTION 8 +#define GG_DEBUG_MISC 16 + +#ifdef GG_DEBUG_DISABLE +#define gg_debug(x, y...) do { } while(0) +#else +void gg_debug(int level, const char *format, ...); +#endif + +const char *gg_libgadu_version(void); + +/* + * konfiguracja http proxy. + */ +extern int gg_proxy_enabled; /* w³±cza obs³ugê proxy */ +extern char *gg_proxy_host; /* okre¶la adres serwera proxy */ +extern int gg_proxy_port; /* okre¶la port serwera proxy */ +extern char *gg_proxy_username; /* okre¶la nazwê u¿ytkownika przy autoryzacji serwera proxy */ +extern char *gg_proxy_password; /* okre¶la has³o u¿ytkownika przy autoryzacji serwera proxy */ +extern int gg_proxy_http_only; /* w³±cza obs³ugê proxy wy³±cznie dla us³ug HTTP */ + + +/* + * adres, z którego ¶lemy pakiety (np ³±czymy siê z serwerem) + * u¿ywany przy gg_connect() + */ +extern unsigned long gg_local_ip; +/* + * ------------------------------------------------------------------------- + * poni¿ej znajduj± siê wewnêtrzne sprawy biblioteki. zwyk³y klient nie + * powinien ich w ogóle ruszaæ, bo i nie ma po co. wszystko mo¿na za³atwiæ + * procedurami wy¿szego poziomu, których definicje znajduj± siê na pocz±tku + * tego pliku. + * ------------------------------------------------------------------------- + */ + +#ifdef __GG_LIBGADU_HAVE_PTHREAD +int gg_resolve_pthread(int *fd, void **resolver, const char *hostname); +#endif + +#ifdef _WIN32 +int gg_thread_socket(int thread_id, int socket); +#endif + +int gg_resolve(int *fd, int *pid, const char *hostname); + +#ifdef __GNUC__ +char *gg_saprintf(const char *format, ...) __attribute__ ((format (printf, 1, 2))); +#else +char *gg_saprintf(const char *format, ...); +#endif + +char *gg_vsaprintf(const char *format, va_list ap); + +#define gg_alloc_sprintf gg_saprintf + +char *gg_get_line(char **ptr); + +int gg_connect(void *addr, int port, int async); +struct in_addr *gg_gethostbyname(const char *hostname); +char *gg_read_line(int sock, char *buf, int length); +void gg_chomp(char *line); +char *gg_urlencode(const char *str); +int gg_http_hash(const char *format, ...); +int gg_read(struct gg_session *sess, char *buf, int length); +int gg_write(struct gg_session *sess, const char *buf, int length); +void *gg_recv_packet(struct gg_session *sess); +int gg_send_packet(struct gg_session *sess, int type, ...); +unsigned int gg_login_hash(const unsigned char *password, unsigned int seed); +uint32_t gg_fix32(uint32_t x); +uint16_t gg_fix16(uint16_t x); +#define fix16 gg_fix16 +#define fix32 gg_fix32 +char *gg_proxy_auth(void); +char *gg_base64_encode(const char *buf); +char *gg_base64_decode(const char *buf); +int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq); + +#define GG_APPMSG_HOST "appmsg.gadu-gadu.pl" +#define GG_APPMSG_PORT 80 +#define GG_PUBDIR_HOST "pubdir.gadu-gadu.pl" +#define GG_PUBDIR_PORT 80 +#define GG_REGISTER_HOST "register.gadu-gadu.pl" +#define GG_REGISTER_PORT 80 +#define GG_REMIND_HOST "retr.gadu-gadu.pl" +#define GG_REMIND_PORT 80 + +#define GG_DEFAULT_PORT 8074 +#define GG_HTTPS_PORT 443 +#define GG_HTTP_USERAGENT "Mozilla/4.7 [en] (Win98; I)" + +#define GG_DEFAULT_CLIENT_VERSION "6, 1, 0, 158" +#define GG_DEFAULT_PROTOCOL_VERSION 0x24 +#define GG_DEFAULT_TIMEOUT 30 +#define GG_HAS_AUDIO_MASK 0x40000000 +#define GG_ERA_OMNIX_MASK 0x04000000 +#define GG_LIBGADU_VERSION "CVS" + +#define GG_DEFAULT_DCC_PORT 1550 + +struct gg_header { + uint32_t type; /* typ pakietu */ + uint32_t length; /* d³ugo¶æ reszty pakietu */ +} GG_PACKED; + +#define GG_WELCOME 0x0001 +#define GG_NEED_EMAIL 0x0014 + +struct gg_welcome { + uint32_t key; /* klucz szyfrowania has³a */ +} GG_PACKED; + +#define GG_LOGIN 0x000c + +struct gg_login { + uint32_t uin; /* mój numerek */ + uint32_t hash; /* hash has³a */ + uint32_t status; /* status na dzieñ dobry */ + uint32_t version; /* moja wersja klienta */ + uint32_t local_ip; /* mój adres ip */ + uint16_t local_port; /* port, na którym s³ucham */ +} GG_PACKED; + +#define GG_LOGIN_EXT 0x0013 + +struct gg_login_ext { + uint32_t uin; /* mój numerek */ + uint32_t hash; /* hash has³a */ + uint32_t status; /* status na dzieñ dobry */ + uint32_t version; /* moja wersja klienta */ + uint32_t local_ip; /* mój adres ip */ + uint16_t local_port; /* port, na którym s³ucham */ + uint32_t external_ip; /* zewnêtrzny adres ip */ + uint16_t external_port; /* zewnêtrzny port */ +} GG_PACKED; + +#define GG_LOGIN60 0x0015 + +struct gg_login60 { + uint32_t uin; /* mój numerek */ + uint32_t hash; /* hash has³a */ + uint32_t status; /* status na dzieñ dobry */ + uint32_t version; /* moja wersja klienta */ + uint8_t dunno1; /* 0x00 */ + uint32_t local_ip; /* mój adres ip */ + uint16_t local_port; /* port, na którym s³ucham */ + uint32_t external_ip; /* zewnêtrzny adres ip */ + uint16_t external_port; /* zewnêtrzny port */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno2; /* 0xbe */ +} GG_PACKED; + +#define GG_LOGIN_OK 0x0003 + +#define GG_LOGIN_FAILED 0x0009 + +#define GG_PUBDIR50_REQUEST 0x0014 + +#define GG_PUBDIR50_WRITE 0x01 +#define GG_PUBDIR50_READ 0x02 +#define GG_PUBDIR50_SEARCH 0x03 +#define GG_PUBDIR50_SEARCH_REQUEST GG_PUBDIR50_SEARCH +#define GG_PUBDIR50_SEARCH_REPLY 0x05 + +struct gg_pubdir50_request { + uint8_t type; /* GG_PUBDIR50_* */ + uint32_t seq; /* czas wys³ania zapytania */ +} GG_PACKED; + +#define GG_PUBDIR50_REPLY 0x000e + +struct gg_pubdir50_reply { + uint8_t type; /* GG_PUBDIR50_* */ + uint32_t seq; /* czas wys³ania zapytania */ +} GG_PACKED; + +#define GG_NEW_STATUS 0x0002 + +#define GG_STATUS_NOT_AVAIL 0x0001 /* niedostêpny */ +#define GG_STATUS_NOT_AVAIL_DESCR 0x0015 /* niedostêpny z opisem (4.8) */ +#define GG_STATUS_AVAIL 0x0002 /* dostêpny */ +#define GG_STATUS_AVAIL_DESCR 0x0004 /* dostêpny z opisem (4.9) */ +#define GG_STATUS_BUSY 0x0003 /* zajêty */ +#define GG_STATUS_BUSY_DESCR 0x0005 /* zajêty z opisem (4.8) */ +#define GG_STATUS_INVISIBLE 0x0014 /* niewidoczny (4.6) */ +#define GG_STATUS_INVISIBLE_DESCR 0x0016 /* niewidoczny z opisem (4.9) */ +#define GG_STATUS_BLOCKED 0x0006 /* zablokowany */ + +#define GG_STATUS_FRIENDS_MASK 0x8000 /* tylko dla znajomych (4.6) */ + +#define GG_STATUS_DESCR_MAXSIZE 70 + +/* + * makra do ³atwego i szybkiego sprawdzania stanu. + */ + +/* GG_S_F() tryb tylko dla znajomych */ +#define GG_S_F(x) (((x) & GG_STATUS_FRIENDS_MASK) != 0) + +/* GG_S() stan bez uwzglêdnienia trybu tylko dla znajomych */ +#define GG_S(x) ((x) & ~GG_STATUS_FRIENDS_MASK) + +/* GG_S_A() dostêpny */ +#define GG_S_A(x) (GG_S(x) == GG_STATUS_AVAIL || GG_S(x) == GG_STATUS_AVAIL_DESCR) + +/* GG_S_NA() niedostêpny */ +#define GG_S_NA(x) (GG_S(x) == GG_STATUS_NOT_AVAIL || GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR) + +/* GG_S_B() zajêty */ +#define GG_S_B(x) (GG_S(x) == GG_STATUS_BUSY || GG_S(x) == GG_STATUS_BUSY_DESCR) + +/* GG_S_I() niewidoczny */ +#define GG_S_I(x) (GG_S(x) == GG_STATUS_INVISIBLE || GG_S(x) == GG_STATUS_INVISIBLE_DESCR) + +/* GG_S_D() stan opisowy */ +#define GG_S_D(x) (GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR || GG_S(x) == GG_STATUS_AVAIL_DESCR || GG_S(x) == GG_STATUS_BUSY_DESCR || GG_S(x) == GG_STATUS_INVISIBLE_DESCR) + +/* GG_S_BL() blokowany lub blokuj±cy */ +#define GG_S_BL(x) (GG_S(x) == GG_STATUS_BLOCKED) + +struct gg_new_status { + uint32_t status; /* na jaki zmieniæ? */ +} GG_PACKED; + +#define GG_NOTIFY_FIRST 0x000f +#define GG_NOTIFY_LAST 0x0010 + +#define GG_NOTIFY 0x0010 + +struct gg_notify { + uint32_t uin; /* numerek danej osoby */ + uint8_t dunno1; /* rodzaj wpisu w li¶cie */ +} GG_PACKED; + +#define GG_USER_OFFLINE 0x01 /* bêdziemy niewidoczni dla u¿ytkownika */ +#define GG_USER_NORMAL 0x03 /* zwyk³y u¿ytkownik */ +#define GG_USER_BLOCKED 0x04 /* zablokowany u¿ytkownik */ + +#define GG_LIST_EMPTY 0x0012 + +#define GG_NOTIFY_REPLY 0x000c /* tak, to samo co GG_LOGIN */ + +struct gg_notify_reply { + uint32_t uin; /* numerek */ + uint32_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na którym s³ucha klient */ + uint32_t version; /* wersja klienta */ + uint16_t dunno2; /* znowu port? */ +} GG_PACKED; + +#define GG_NOTIFY_REPLY60 0x0011 + +struct gg_notify_reply60 { + uint32_t uin; /* numerek plus flagi w MSB */ + uint8_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na którym s³ucha klient */ + uint8_t version; /* wersja klienta */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno1; /* 0x00 */ +} GG_PACKED; + +#define GG_STATUS60 0x000f + +struct gg_status60 { + uint32_t uin; /* numerek plus flagi w MSB */ + uint8_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na którym s³ucha klient */ + uint8_t version; /* wersja klienta */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno1; /* 0x00 */ +} GG_PACKED; + +#define GG_ADD_NOTIFY 0x000d +#define GG_REMOVE_NOTIFY 0x000e + +struct gg_add_remove { + uint32_t uin; /* numerek */ + uint8_t dunno1; /* bitmapa */ +} GG_PACKED; + +#define GG_STATUS 0x0002 + +struct gg_status { + uint32_t uin; /* numerek */ + uint32_t status; /* nowy stan */ +} GG_PACKED; + +#define GG_SEND_MSG 0x000b + +#define GG_CLASS_QUEUED 0x0001 +#define GG_CLASS_OFFLINE GG_CLASS_QUEUED +#define GG_CLASS_MSG 0x0004 +#define GG_CLASS_CHAT 0x0008 +#define GG_CLASS_CTCP 0x0010 +#define GG_CLASS_ACK 0x0020 +#define GG_CLASS_EXT GG_CLASS_ACK /* kompatybilno¶æ wstecz */ + +#define GG_MSG_MAXSIZE 2000 + +struct gg_send_msg { + uint32_t recipient; + uint32_t seq; + uint32_t msgclass; +} GG_PACKED; + +struct gg_msg_richtext { + uint8_t flag; + uint16_t length; +} GG_PACKED; + +struct gg_msg_richtext_format { + uint16_t position; + uint8_t font; +} GG_PACKED; + +struct gg_msg_richtext_image { + uint16_t unknown1; + uint32_t size; + uint32_t crc32; +} GG_PACKED; + +#define GG_FONT_BOLD 0x01 +#define GG_FONT_ITALIC 0x02 +#define GG_FONT_UNDERLINE 0x04 +#define GG_FONT_COLOR 0x08 +#define GG_FONT_IMAGE 0x80 + +struct gg_msg_richtext_color { + uint8_t red; + uint8_t green; + uint8_t blue; +} GG_PACKED; + +struct gg_msg_recipients { + uint8_t flag; + uint32_t count; +} GG_PACKED; + +struct gg_msg_image_request { + uint8_t flag; + uint32_t size; + uint32_t crc32; +} GG_PACKED; + +struct gg_msg_image_reply { + uint8_t flag; + uint32_t size; + uint32_t crc32; + /* char filename[]; */ + /* char image[]; */ +} GG_PACKED; + +#define GG_SEND_MSG_ACK 0x0005 + +#define GG_ACK_BLOCKED 0x0001 +#define GG_ACK_DELIVERED 0x0002 +#define GG_ACK_QUEUED 0x0003 +#define GG_ACK_MBOXFULL 0x0004 +#define GG_ACK_NOT_DELIVERED 0x0006 + +struct gg_send_msg_ack { + uint32_t status; + uint32_t recipient; + uint32_t seq; +} GG_PACKED; + +#define GG_RECV_MSG 0x000a + +struct gg_recv_msg { + uint32_t sender; + uint32_t seq; + uint32_t time; + uint32_t msgclass; +} GG_PACKED; + +#define GG_PING 0x0008 + +#define GG_PONG 0x0007 + +#define GG_DISCONNECTING 0x000b + +#define GG_USERLIST_REQUEST 0x0016 + +#define GG_USERLIST_PUT 0x00 +#define GG_USERLIST_PUT_MORE 0x01 +#define GG_USERLIST_GET 0x02 + +struct gg_userlist_request { + uint8_t type; +} GG_PACKED; + +#define GG_USERLIST_REPLY 0x0010 + +#define GG_USERLIST_PUT_REPLY 0x00 +#define GG_USERLIST_PUT_MORE_REPLY 0x02 +#define GG_USERLIST_GET_REPLY 0x06 +#define GG_USERLIST_GET_MORE_REPLY 0x04 + +struct gg_userlist_reply { + uint8_t type; +} GG_PACKED; + +/* + * pakiety, sta³e, struktury dla DCC + */ + +struct gg_dcc_tiny_packet { + uint8_t type; /* rodzaj pakietu */ +} GG_PACKED; + +struct gg_dcc_small_packet { + uint32_t type; /* rodzaj pakietu */ +} GG_PACKED; + +struct gg_dcc_big_packet { + uint32_t type; /* rodzaj pakietu */ + uint32_t dunno1; /* niewiadoma */ + uint32_t dunno2; /* niewiadoma */ +} GG_PACKED; + +/* + * póki co, nie znamy dok³adnie protoko³u. nie wiemy, co czemu odpowiada. + * nazwy s± niepowa¿ne i tymczasowe. + */ +#define GG_DCC_WANT_FILE 0x0003 /* peer chce plik */ +#define GG_DCC_HAVE_FILE 0x0001 /* wiêc mu damy */ +#define GG_DCC_HAVE_FILEINFO 0x0003 /* niech ma informacje o pliku */ +#define GG_DCC_GIMME_FILE 0x0006 /* peer jest pewny */ +#define GG_DCC_CATCH_FILE 0x0002 /* wysy³amy plik */ + +#define GG_DCC_FILEATTR_READONLY 0x0020 + +#define GG_DCC_TIMEOUT_SEND 1800 /* 30 minut */ +#define GG_DCC_TIMEOUT_GET 1800 /* 30 minut */ +#define GG_DCC_TIMEOUT_FILE_ACK 300 /* 5 minut */ +#define GG_DCC_TIMEOUT_VOICE_ACK 300 /* 5 minut */ + +#ifdef __cplusplus +} +#ifdef _WIN32 +#pragma pack(pop) +#endif +#endif + +#endif /* __GG_LIBGADU_H */ + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/pubdir.c b/kopete/protocols/gadu/libgadu/pubdir.c new file mode 100644 index 00000000..7ed545ff --- /dev/null +++ b/kopete/protocols/gadu/libgadu/pubdir.c @@ -0,0 +1,689 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski + * Dawid Jarosz + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "libgadu.h" + +/* + * gg_register3() + * + * rozpoczyna rejestracjê u¿ytkownika protoko³em GG 6.0. wymaga wcze¶niejszego + * pobrania tokenu za pomoc± funkcji gg_token(). + * + * - email - adres e-mail klienta + * - password - has³o klienta + * - tokenid - identyfikator tokenu + * - tokenval - warto¶æ tokenu + * - async - po³±czenie asynchroniczne + * + * zaalokowana struct gg_http, któr± po¼niej nale¿y zwolniæ + * funkcj± gg_register_free(), albo NULL je¶li wyst±pi³ b³±d. + */ +struct gg_http *gg_register3(const char *email, const char *password, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *__pwd, *__email, *__tokenid, *__tokenval, *form, *query; + + if (!email || !password || !tokenid || !tokenval) { + gg_debug(GG_DEBUG_MISC, "=> register, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __pwd = gg_urlencode(password); + __email = gg_urlencode(email); + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + + if (!__pwd || !__email || !__tokenid || !__tokenval) { + gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for form fields\n"); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + return NULL; + } + + form = gg_saprintf("pwd=%s&email=%s&tokenid=%s&tokenval=%s&code=%u", + __pwd, __email, __tokenid, __tokenval, + gg_http_hash("ss", email, password)); + + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + + if (!form) { + gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for form query\n"); + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> register, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> register, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_REGISTER; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +/* + * gg_unregister3() + * + * usuwa konto u¿ytkownika z serwera protoko³em GG 6.0 + * + * - uin - numerek GG + * - password - has³o klienta + * - tokenid - identyfikator tokenu + * - tokenval - warto¶æ tokenu + * - async - po³±czenie asynchroniczne + * + * zaalokowana struct gg_http, któr± po¼niej nale¿y zwolniæ + * funkcj± gg_unregister_free(), albo NULL je¶li wyst±pi³ b³±d. + */ +struct gg_http *gg_unregister3(uin_t uin, const char *password, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *__fmpwd, *__pwd, *__tokenid, *__tokenval, *form, *query; + + if (!password || !tokenid || !tokenval) { + gg_debug(GG_DEBUG_MISC, "=> unregister, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __pwd = gg_saprintf("%ld", random()); + __fmpwd = gg_urlencode(password); + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + + if (!__fmpwd || !__pwd || !__tokenid || !__tokenval) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for form fields\n"); + free(__pwd); + free(__fmpwd); + free(__tokenid); + free(__tokenval); + return NULL; + } + + form = gg_saprintf("fmnumber=%d&fmpwd=%s&delete=1&pwd=%s&email=deletedaccount@gadu-gadu.pl&tokenid=%s&tokenval=%s&code=%u", uin, __fmpwd, __pwd, __tokenid, __tokenval, gg_http_hash("ss", "deletedaccount@gadu-gadu.pl", __pwd)); + + free(__fmpwd); + free(__pwd); + free(__tokenid); + free(__tokenval); + + if (!form) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for form query\n"); + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> unregister, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> unregister, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_UNREGISTER; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +/* + * gg_change_passwd4() + * + * wysy³a ¿±danie zmiany has³a zgodnie z protoko³em GG 6.0. wymaga + * wcze¶niejszego pobrania tokenu za pomoc± funkcji gg_token(). + * + * - uin - numer + * - email - adres e-mail + * - passwd - stare has³o + * - newpasswd - nowe has³o + * - tokenid - identyfikator tokenu + * - tokenval - warto¶æ tokenu + * - async - po³±czenie asynchroniczne + * + * zaalokowana struct gg_http, któr± po¼niej nale¿y zwolniæ + * funkcj± gg_change_passwd_free(), albo NULL je¶li wyst±pi³ b³±d. + */ +struct gg_http *gg_change_passwd4(uin_t uin, const char *email, const char *passwd, const char *newpasswd, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *form, *query, *__email, *__fmpwd, *__pwd, *__tokenid, *__tokenval; + + if (!uin || !email || !passwd || !newpasswd || !tokenid || !tokenval) { + gg_debug(GG_DEBUG_MISC, "=> change, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __fmpwd = gg_urlencode(passwd); + __pwd = gg_urlencode(newpasswd); + __email = gg_urlencode(email); + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + + if (!__fmpwd || !__pwd || !__email || !__tokenid || !__tokenval) { + gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for form fields\n"); + free(__fmpwd); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + return NULL; + } + + if (!(form = gg_saprintf("fmnumber=%d&fmpwd=%s&pwd=%s&email=%s&tokenid=%s&tokenval=%s&code=%u", uin, __fmpwd, __pwd, __email, __tokenid, __tokenval, gg_http_hash("ss", email, newpasswd)))) { + gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for form fields\n"); + free(__fmpwd); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + + return NULL; + } + + free(__fmpwd); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + + gg_debug(GG_DEBUG_MISC, "=> change, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> change, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_PASSWD; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +/* + * gg_remind_passwd3() + * + * wysy³a ¿±danie przypomnienia has³a e-mailem. + * + * - uin - numer + * - email - adres e-mail taki, jak ten zapisany na serwerze + * - async - po³±czenie asynchroniczne + * - tokenid - identyfikator tokenu + * - tokenval - warto¶æ tokenu + * + * zaalokowana struct gg_http, któr± po¼niej nale¿y zwolniæ + * funkcj± gg_remind_passwd_free(), albo NULL je¶li wyst±pi³ b³±d. + */ +struct gg_http *gg_remind_passwd3(uin_t uin, const char *email, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *form, *query, *__tokenid, *__tokenval, *__email; + + if (!tokenid || !tokenval || !email) { + gg_debug(GG_DEBUG_MISC, "=> remind, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + __email = gg_urlencode(email); + + if (!__tokenid || !__tokenval || !__email) { + gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for form fields\n"); + free(__tokenid); + free(__tokenval); + free(__email); + return NULL; + } + + if (!(form = gg_saprintf("userid=%d&code=%u&tokenid=%s&tokenval=%s&email=%s", uin, gg_http_hash("u", uin), __tokenid, __tokenval, __email))) { + gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for form fields\n"); + free(__tokenid); + free(__tokenval); + free(__email); + return NULL; + } + + free(__tokenid); + free(__tokenval); + free(__email); + + gg_debug(GG_DEBUG_MISC, "=> remind, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REMIND_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REMIND_HOST, GG_REMIND_PORT, async, "POST", "/appsvc/fmsendpwd3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> remind, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_REMIND; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +/* + * gg_pubdir_watch_fd() + * + * przy asynchronicznych operacjach na katalogu publicznym nale¿y wywo³ywaæ + * tê funkcjê przy zmianach na obserwowanym deskryptorze. + * + * - h - struktura opisuj±ca po³±czenie + * + * je¶li wszystko posz³o dobrze to 0, inaczej -1. operacja bêdzie + * zakoñczona, je¶li h->state == GG_STATE_DONE. je¶li wyst±pi jaki¶ + * b³±d, to bêdzie tam GG_STATE_ERROR i odpowiedni kod b³êdu w h->error. + */ +int gg_pubdir_watch_fd(struct gg_http *h) +{ + struct gg_pubdir *p; + char *tmp; + + if (!h) { + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_ERROR) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, watch_fd issued on failed session\n"); + errno = EINVAL; + return -1; + } + + if (h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, http failure\n"); + errno = EINVAL; + return -1; + } + } + + if (h->state != GG_STATE_PARSING) + return 0; + + h->state = GG_STATE_DONE; + + if (!(h->data = p = malloc(sizeof(struct gg_pubdir)))) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, not enough memory for results\n"); + return -1; + } + + p->success = 0; + p->uin = 0; + + gg_debug(GG_DEBUG_MISC, "=> pubdir, let's parse \"%s\"\n", h->body); + + if ((tmp = strstr(h->body, "Tokens okregisterreply_packet.reg.dwUserId="))) { + p->success = 1; + p->uin = strtol(tmp + sizeof("Tokens okregisterreply_packet.reg.dwUserId=") - 1, NULL, 0); + gg_debug(GG_DEBUG_MISC, "=> pubdir, success (okregisterreply, uin=%d)\n", p->uin); + } else if ((tmp = strstr(h->body, "success")) || (tmp = strstr(h->body, "results"))) { + p->success = 1; + if (tmp[7] == ':') + p->uin = strtol(tmp + 8, NULL, 0); + gg_debug(GG_DEBUG_MISC, "=> pubdir, success (uin=%d)\n", p->uin); + } else + gg_debug(GG_DEBUG_MISC, "=> pubdir, error.\n"); + + return 0; +} + +/* + * gg_pubdir_free() + * + * zwalnia pamiêæ po efektach operacji na katalogu publicznym. + * + * - h - zwalniana struktura + */ +void gg_pubdir_free(struct gg_http *h) +{ + if (!h) + return; + + free(h->data); + gg_http_free(h); +} + +/* + * gg_token() + * + * pobiera z serwera token do autoryzacji zak³adania konta, usuwania + * konta i zmiany has³a. + * + * zaalokowana struct gg_http, któr± po¼niej nale¿y zwolniæ + * funkcj± gg_token_free(), albo NULL je¶li wyst±pi³ b³±d. + */ +struct gg_http *gg_token(int async) +{ + struct gg_http *h; + const char *query; + + query = "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: 0\r\n" + "Pragma: no-cache\r\n" + "\r\n"; + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/regtoken.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> token, gg_http_connect() failed mysteriously\n"); + return NULL; + } + + h->type = GG_SESSION_TOKEN; + + h->callback = gg_token_watch_fd; + h->destroy = gg_token_free; + + if (!async) + gg_token_watch_fd(h); + + return h; +} + +/* + * gg_token_watch_fd() + * + * przy asynchronicznych operacjach zwi±zanych z tokenem nale¿y wywo³ywaæ + * tê funkcjê przy zmianach na obserwowanym deskryptorze. + * + * - h - struktura opisuj±ca po³±czenie + * + * je¶li wszystko posz³o dobrze to 0, inaczej -1. operacja bêdzie + * zakoñczona, je¶li h->state == GG_STATE_DONE. je¶li wyst±pi jaki¶ + * b³±d, to bêdzie tam GG_STATE_ERROR i odpowiedni kod b³êdu w h->error. + */ +int gg_token_watch_fd(struct gg_http *h) +{ + if (!h) { + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_ERROR) { + gg_debug(GG_DEBUG_MISC, "=> token, watch_fd issued on failed session\n"); + errno = EINVAL; + return -1; + } + + if (h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) { + gg_debug(GG_DEBUG_MISC, "=> token, http failure\n"); + errno = EINVAL; + return -1; + } + } + + if (h->state != GG_STATE_PARSING) + return 0; + + /* je¶li h->data jest puste, to ¶ci±gali¶my tokenid i url do niego, + * ale je¶li co¶ tam jest, to znaczy, ¿e mamy drugi etap polegaj±cy + * na pobieraniu tokenu. */ + if (!h->data) { + int width, height, length; + char *url = NULL, *tokenid = NULL, *path, *headers; + const char *host; + struct gg_http *h2; + struct gg_token *t; + + gg_debug(GG_DEBUG_MISC, "=> token body \"%s\"\n", h->body); + + if (h->body && (!(url = malloc(strlen(h->body))) || !(tokenid = malloc(strlen(h->body))))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for results\n"); + free(url); + return -1; + } + + if (!h->body || sscanf(h->body, "%d %d %d\r\n%s\r\n%s", &width, &height, &length, tokenid, url) != 5) { + gg_debug(GG_DEBUG_MISC, "=> token, parsing failed\n"); + free(url); + free(tokenid); + errno = EINVAL; + return -1; + } + + /* dostali¶my tokenid i wszystkie niezbêdne informacje, + * wiêc pobierzmy obrazek z tokenem */ + + if (strncmp(url, "http://", 7)) { + path = gg_saprintf("%s?tokenid=%s", url, tokenid); + host = GG_REGISTER_HOST; + } else { + char *slash = strchr(url + 7, '/'); + + if (slash) { + path = gg_saprintf("%s?tokenid=%s", slash, tokenid); + *slash = 0; + host = url + 7; + } else { + gg_debug(GG_DEBUG_MISC, "=> token, url parsing failed\n"); + free(url); + free(tokenid); + errno = EINVAL; + return -1; + } + } + + if (!path) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token url\n"); + free(url); + free(tokenid); + return -1; + } + + if (!(headers = gg_saprintf("Host: %s\r\nUser-Agent: " GG_HTTP_USERAGENT "\r\n\r\n", host))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token url\n"); + free(path); + free(url); + free(tokenid); + return -1; + } + + if (!(h2 = gg_http_connect(host, GG_REGISTER_PORT, h->async, "GET", path, headers))) { + gg_debug(GG_DEBUG_MISC, "=> token, gg_http_connect() failed mysteriously\n"); + free(headers); + free(url); + free(path); + free(tokenid); + return -1; + } + + free(headers); + free(path); + free(url); + + memcpy(h, h2, sizeof(struct gg_http)); + free(h2); + + h->type = GG_SESSION_TOKEN; + + h->callback = gg_token_watch_fd; + h->destroy = gg_token_free; + + if (!h->async) + gg_token_watch_fd(h); + + if (!(h->data = t = malloc(sizeof(struct gg_token)))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token data\n"); + free(tokenid); + return -1; + } + + t->width = width; + t->height = height; + t->length = length; + t->tokenid = tokenid; + } else { + /* obrazek mamy w h->body */ + h->state = GG_STATE_DONE; + } + + return 0; +} + +/* + * gg_token_free() + * + * zwalnia pamiêæ po efektach pobierania tokenu. + * + * - h - zwalniana struktura + */ +void gg_token_free(struct gg_http *h) +{ + struct gg_token *t; + + if (!h) + return; + + if ((t = h->data)) + free(t->tokenid); + + free(h->data); + gg_http_free(h); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/pubdir50.c b/kopete/protocols/gadu/libgadu/pubdir50.c new file mode 100644 index 00000000..6ef285e7 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/pubdir50.c @@ -0,0 +1,467 @@ +/* $Id$ */ + +/* + * (C) Copyright 2003 Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include + +#include "libgadu.h" + +/* + * gg_pubdir50_new() + * + * tworzy now± zmienn± typu gg_pubdir50_t. + * + * zaalokowana zmienna lub NULL w przypadku braku pamiêci. + */ +gg_pubdir50_t gg_pubdir50_new(int type) +{ + gg_pubdir50_t res = malloc(sizeof(struct gg_pubdir50_s)); + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_new(%d);\n", type); + + if (!res) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_new() out of memory\n"); + return NULL; + } + + memset(res, 0, sizeof(struct gg_pubdir50_s)); + + res->type = type; + + return res; +} + +/* + * gg_pubdir50_add_n() // funkcja wewnêtrzna + * + * funkcja dodaje lub zastêpuje istniej±ce pole do zapytania lub odpowiedzi. + * + * - req - wska¼nik opisu zapytania, + * - num - numer wyniku (0 dla zapytania), + * - field - nazwa pola, + * - value - warto¶æ pola, + * + * 0/-1 + */ +static int gg_pubdir50_add_n(gg_pubdir50_t req, int num, const char *field, const char *value) +{ + struct gg_pubdir50_entry *tmp = NULL, *entry; + char *dupfield, *dupvalue; + int i; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_add_n(%p, %d, \"%s\", \"%s\");\n", req, num, field, value); + + if (!(dupvalue = strdup(value))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n"); + return -1; + } + + for (i = 0; i < req->entries_count; i++) { + if (req->entries[i].num != num || strcmp(req->entries[i].field, field)) + continue; + + free(req->entries[i].value); + req->entries[i].value = dupvalue; + + return 0; + } + + if (!(dupfield = strdup(field))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n"); + free(dupvalue); + return -1; + } + + if (!(tmp = realloc(req->entries, sizeof(struct gg_pubdir50_entry) * (req->entries_count + 1)))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n"); + free(dupfield); + free(dupvalue); + return -1; + } + + req->entries = tmp; + + entry = &req->entries[req->entries_count]; + entry->num = num; + entry->field = dupfield; + entry->value = dupvalue; + + req->entries_count++; + + return 0; +} + +/* + * gg_pubdir50_add() + * + * funkcja dodaje pole do zapytania. + * + * - req - wska¼nik opisu zapytania, + * - field - nazwa pola, + * - value - warto¶æ pola, + * + * 0/-1 + */ +int gg_pubdir50_add(gg_pubdir50_t req, const char *field, const char *value) +{ + return gg_pubdir50_add_n(req, 0, field, value); +} + +/* + * gg_pubdir50_seq_set() + * + * ustawia numer sekwencyjny zapytania. + * + * - req - zapytanie, + * - seq - nowy numer sekwencyjny. + * + * 0/-1. + */ +int gg_pubdir50_seq_set(gg_pubdir50_t req, uint32_t seq) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_seq_set(%p, %d);\n", req, seq); + + if (!req) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_seq_set() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + req->seq = seq; + + return 0; +} + +/* + * gg_pubdir50_free() + * + * zwalnia pamiêæ po zapytaniu lub rezultacie szukania u¿ytkownika. + * + * - s - zwalniana zmienna, + */ +void gg_pubdir50_free(gg_pubdir50_t s) +{ + int i; + + if (!s) + return; + + for (i = 0; i < s->entries_count; i++) { + free(s->entries[i].field); + free(s->entries[i].value); + } + + free(s->entries); + free(s); +} + +/* + * gg_pubdir50() + * + * wysy³a zapytanie katalogu publicznego do serwera. + * + * - sess - sesja, + * - req - zapytanie. + * + * numer sekwencyjny wyszukiwania lub 0 w przypadku b³êdu. + */ +uint32_t gg_pubdir50(struct gg_session *sess, gg_pubdir50_t req) +{ + int i, size = 5; + uint32_t res; + char *buf, *p; + struct gg_pubdir50_request *r; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50(%p, %p);\n", sess, req); + + if (!sess || !req) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50() invalid arguments\n"); + errno = EFAULT; + return 0; + } + + if (sess->state != GG_STATE_CONNECTED) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50() not connected\n"); + errno = ENOTCONN; + return 0; + } + + for (i = 0; i < req->entries_count; i++) { + /* wyszukiwanie bierze tylko pierwszy wpis */ + if (req->entries[i].num) + continue; + + size += strlen(req->entries[i].field) + 1; + size += strlen(req->entries[i].value) + 1; + } + + if (!(buf = malloc(size))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50() out of memory (%d bytes)\n", size); + return 0; + } + + r = (struct gg_pubdir50_request*) buf; + res = time(NULL); + r->type = req->type; + r->seq = (req->seq) ? gg_fix32(req->seq) : gg_fix32(time(NULL)); + req->seq = gg_fix32(r->seq); + + for (i = 0, p = buf + 5; i < req->entries_count; i++) { + if (req->entries[i].num) + continue; + + strcpy(p, req->entries[i].field); + p += strlen(p) + 1; + + strcpy(p, req->entries[i].value); + p += strlen(p) + 1; + } + + if (gg_send_packet(sess, GG_PUBDIR50_REQUEST, buf, size, NULL, 0) == -1) + res = 0; + + free(buf); + + return res; +} + +/* + * gg_pubdir50_handle_reply() // funkcja wewnêtrzna + * + * analizuje przychodz±cy pakiet odpowiedzi i zapisuje wynik w struct gg_event. + * + * - e - opis zdarzenia + * - packet - zawarto¶æ pakietu odpowiedzi + * - length - d³ugo¶æ pakietu odpowiedzi + * + * 0/-1 + */ +int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length) +{ + const char *end = packet + length, *p; + struct gg_pubdir50_reply *r = (struct gg_pubdir50_reply*) packet; + gg_pubdir50_t res; + int num = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_handle_reply(%p, %p, %d);\n", e, packet, length); + + if (!e || !packet) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (length < 5) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() packet too short\n"); + errno = EINVAL; + return -1; + } + + if (!(res = gg_pubdir50_new(r->type))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() unable to allocate reply\n"); + return -1; + } + + e->event.pubdir50 = res; + + res->seq = gg_fix32(r->seq); + + switch (res->type) { + case GG_PUBDIR50_READ: + e->type = GG_EVENT_PUBDIR50_READ; + break; + + case GG_PUBDIR50_WRITE: + e->type = GG_EVENT_PUBDIR50_WRITE; + break; + + default: + e->type = GG_EVENT_PUBDIR50_SEARCH_REPLY; + break; + } + + /* brak wyników? */ + if (length == 5) + return 0; + + /* pomiñ pocz±tek odpowiedzi */ + p = packet + 5; + + while (p < end) { + const char *field, *value; + + field = p; + + /* sprawd¼, czy nie mamy podzia³u na kolejne pole */ + if (!*field) { + num++; + field++; + } + + value = NULL; + + for (p = field; p < end; p++) { + /* je¶li mamy koniec tekstu... */ + if (!*p) { + /* ...i jeszcze nie mieli¶my warto¶ci pola to + * wiemy, ¿e po tym zerze jest warto¶æ... */ + if (!value) + value = p + 1; + else + /* ...w przeciwym wypadku koniec + * warto¶ci i mo¿emy wychodziæ + * grzecznie z pêtli */ + break; + } + } + + /* sprawd¼my, czy pole nie wychodzi poza pakiet, ¿eby nie + * mieæ segfaultów, je¶li serwer przestanie zakañczaæ pakietów + * przez \0 */ + + if (p == end) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() premature end of packet\n"); + goto failure; + } + + p++; + + /* je¶li dostali¶my namier na nastêpne wyniki, to znaczy ¿e + * mamy koniec wyników i nie jest to kolejna osoba. */ + if (!strcasecmp(field, "nextstart")) { + res->next = atoi(value); + num--; + } else { + if (gg_pubdir50_add_n(res, num, field, value) == -1) + goto failure; + } + } + + res->count = num + 1; + + return 0; + +failure: + gg_pubdir50_free(res); + return -1; +} + +/* + * gg_pubdir50_get() + * + * pobiera informacjê z rezultatu wyszukiwania. + * + * - res - rezultat wyszukiwania, + * - num - numer odpowiedzi, + * - field - nazwa pola (wielko¶æ liter nie ma znaczenia). + * + * warto¶æ pola lub NULL, je¶li nie znaleziono. + */ +const char *gg_pubdir50_get(gg_pubdir50_t res, int num, const char *field) +{ + char *value = NULL; + int i; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_get(%p, %d, \"%s\");\n", res, num, field); + + if (!res || num < 0 || !field) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_get() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + for (i = 0; i < res->entries_count; i++) { + if (res->entries[i].num == num && !strcasecmp(res->entries[i].field, field)) { + value = res->entries[i].value; + break; + } + } + + return value; +} + +/* + * gg_pubdir50_count() + * + * zwraca ilo¶æ wyników danego zapytania. + * + * - res - odpowied¼ + * + * ilo¶æ lub -1 w przypadku b³êdu. + */ +int gg_pubdir50_count(gg_pubdir50_t res) +{ + return (!res) ? -1 : res->count; +} + +/* + * gg_pubdir50_type() + * + * zwraca rodzaj zapytania lub odpowiedzi. + * + * - res - zapytanie lub odpowied¼ + * + * ilo¶æ lub -1 w przypadku b³êdu. + */ +int gg_pubdir50_type(gg_pubdir50_t res) +{ + return (!res) ? -1 : res->type; +} + +/* + * gg_pubdir50_next() + * + * zwraca numer, od którego nale¿y rozpocz±æ kolejne wyszukiwanie, je¶li + * zale¿y nam na kolejnych wynikach. + * + * - res - odpowied¼ + * + * numer lub -1 w przypadku b³êdu. + */ +uin_t gg_pubdir50_next(gg_pubdir50_t res) +{ + return (!res) ? (unsigned) -1 : res->next; +} + +/* + * gg_pubdir50_seq() + * + * zwraca numer sekwencyjny zapytania lub odpowiedzi. + * + * - res - zapytanie lub odpowied¼ + * + * numer lub -1 w przypadku b³êdu. + */ +uint32_t gg_pubdir50_seq(gg_pubdir50_t res) +{ + return (!res) ? (unsigned) -1 : res->seq; +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/ui/Makefile.am b/kopete/protocols/gadu/ui/Makefile.am new file mode 100644 index 00000000..12ee702a --- /dev/null +++ b/kopete/protocols/gadu/ui/Makefile.am @@ -0,0 +1,12 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) \ + -I$(srcdir)/.. \ + $(all_includes) + +noinst_LTLIBRARIES = libgaduui.la + +libgaduui_la_SOURCES = gaduadd.ui gadusearch.ui gadueditaccountui.ui gaduawayui.ui gaduregisteraccountui.ui \ + empty.cpp + +EXTRA_DIST = gaduadd.ui gadusearch.ui gadueditaccountui.ui gaduawayui.ui gaduregisteraccountui.ui \ + empty.cpp diff --git a/kopete/protocols/gadu/ui/empty.cpp b/kopete/protocols/gadu/ui/empty.cpp new file mode 100644 index 00000000..e69de29b diff --git a/kopete/protocols/gadu/ui/gaduadd.ui b/kopete/protocols/gadu/ui/gaduadd.ui new file mode 100644 index 00000000..f8d673b6 --- /dev/null +++ b/kopete/protocols/gadu/ui/gaduadd.ui @@ -0,0 +1,360 @@ + +GaduAddUI + + + GaduAddUI + + + + 0 + 0 + 394 + 340 + + + + + unnamed + + + + layout39 + + + + unnamed + + + + TextLabel1 + + + + 0 + 0 + 0 + 0 + + + + Gadu-Gadu &UIN: + + + AutoText + + + false + + + AlignVCenter + + + addEdit_ + + + The user ID of the Gadu-Gadu account you would like to add. + + + The user ID of the Gadu-Gadu account you would like to add. This should be in the form of a number (no decimals, no spaces). This field is mandatory. + + + + + addEdit_ + + + The user ID of the Gadu-Gadu account you would like to add. + + + The user ID of the Gadu-Gadu account you would like to add. This should be in the form of a number (no decimals, no spaces). This field is mandatory. + + + + + + + textLabel2 + + + <i>(for example: 1234567)</i> + + + AlignVCenter|AlignRight + + + + + layout10 + + + + unnamed + + + + layout8 + + + + unnamed + + + + textLabel1 + + + true + + + &Forename: + + + fornameEdit_ + + + The forename of the contact you wish to add. + + + The forename (first name) of the contact you wish to add. Optionally this may include a middle name. + + + + + TextLabel1_2 + + + true + + + &Surname: + + + snameEdit_ + + + The surname of the contact you wish to add. + + + The surname (last name) of the contact you wish to add. + + + + + TextLabel1_2_2 + + + + 0 + 1 + 0 + 0 + + + + N&ickname: + + + nickEdit_ + + + A nickname for the contact you wish to add. + + + A nickname for the contact you wish to add. + + + + + TextLabel1_4 + + + true + + + + 0 + 5 + 0 + 0 + + + + &Email address: + + + emailEdit_ + + + E-Mail address for this contact. + + + E-Mail address for this contact. + + + + + TextLabel1_4_2 + + + true + + + + 0 + 5 + 0 + 0 + + + + &Telephone number: + + + emailEdit_ + + + E-Mail address for this contact. + + + E-Mail address for this contact. + + + + + + + layout9 + + + + unnamed + + + + fornameEdit_ + + + true + + + The forename of the contact you wish to add. + + + The forename (first name) of the contact you wish to add. Optionally this may include a middle name. + + + + + snameEdit_ + + + true + + + The surname of the contact you wish to add. + + + The surname (last name) of the contact you wish to add. + + + + + nickEdit_ + + + A nickname for the contact you wish to add. + + + A nickname for the contact you wish to add. + + + + + emailEdit_ + + + true + + + E-Mail address for this contact. + + + E-Mail address for this contact. + + + + + telephoneEdit_ + + + true + + + E-Mail address for this contact. + + + E-Mail address for this contact. + + + + + + + + + notAFriend_ + + + false + + + Offline to contact when you set "&Just for friends" + + + Check if you want to exclude this contact from the "Just for friends" status mode. + + + Check if you want to exclude this contact from the "Just for friends" status mode. + + + + + + Group + + + false + + + false + + + + + + + + + + + + + + + + + + + + groups + + + + + + + + + krestrictedline.h + + diff --git a/kopete/protocols/gadu/ui/gaduawayui.ui b/kopete/protocols/gadu/ui/gaduawayui.ui new file mode 100644 index 00000000..3e475bce --- /dev/null +++ b/kopete/protocols/gadu/ui/gaduawayui.ui @@ -0,0 +1,194 @@ + +GaduAwayUI + + + GaduAwayUI + + + + 0 + 0 + 332 + 188 + + + + WidgetOrigin + + + Away Dialog + + + TabFocus + + + + unnamed + + + 0 + + + 6 + + + + layout3 + + + + unnamed + + + + statusGroup_ + + + Status + + + Choose status, by default present status is selected. +So all you need to do is just to type in your description. +Choosing Offline status will disconnect you, with given description. + + + + unnamed + + + + layout2 + + + + unnamed + + + + onlineButton_ + + + O&nline + + + 4 + + + Set your status to Online. + + + Set your status to Online, indicating that you are available to chat with anyone who wishes. + + + + + awayButton_ + + + &Busy + + + 5 + + + Set your status to busy. + + + Set your status to busy, indicating that you may should not be bothered with trivial chat, and may not be able to reply immediately. + + + + + invisibleButton_ + + + &Invisible + + + 22 + + + Set status to invisible, which will hide your presence from other users. + + + Set status to invisible, which will hide your presence from other users (who will see you as offline). However you may still chat, and see the online presence of others. + + + + + offlineButton_ + + + O&ffline + + + 21 + + + Choose this status to disconnect with description entered below. + + + Choose this status to disconnect with description entered below. + + + + + + + + + layout278 + + + + unnamed + + + + textLabel3 + + + &Message: + + + textEdit_ + + + Description of your status. + + + Description of your status (up to 70 characters). + + + + + textEdit_ + + + false + + + 70 + + + Description of your status. + + + Description of your status (up to 70 characters). + + + + + + + + + + textEdit_ + onlineButton_ + awayButton_ + invisibleButton_ + offlineButton_ + + + diff --git a/kopete/protocols/gadu/ui/gadueditaccountui.ui b/kopete/protocols/gadu/ui/gadueditaccountui.ui new file mode 100644 index 00000000..01edaa08 --- /dev/null +++ b/kopete/protocols/gadu/ui/gadueditaccountui.ui @@ -0,0 +1,873 @@ + +GaduAccountEditUI + + + GaduAccountEditUI + + + + 0 + 0 + 580 + 390 + + + + + 5 + 5 + 0 + 0 + + + + Account Preferences - Gadu-Gadu + + + + unnamed + + + + tabWidget4 + + + true + + + + tab + + + B&asic Setup + + + + unnamed + + + + groupBox63 + + + Account Information + + + + unnamed + + + + layout8 + + + + unnamed + + + + textLabel1 + + + Gadu-Gadu &UIN: + + + loginEdit_ + + + The user ID of your Gadu-Gadu account. + + + The user ID of your Gadu-Gadu account. This should be in the form of a number (no decimals, no spaces). + + + + + loginEdit_ + + + true + + + 16 + + + true + + + false + + + The user ID of your Gadu-Gadu account. + + + The user ID of your Gadu-Gadu account. This should be in the form of a number (no decimals, no spaces). + + + + + + + passwordWidget_ + + + + + autoLoginCheck_ + + + E&xclude from connect all + + + + + + Check to disable automatic connection. If checked, you may connect to this account manually using the icon in the bottom of the main Kopete window. + + + + + + + groupBox5 + + + + 3 + 1 + 0 + 0 + + + + Registration + + + + unnamed + + + + textLabel6 + + + + 3 + 1 + 0 + 0 + + + + + 0 + 0 + + + + To connect to the Gadu-Gadu network, you will need a Gadu-Gadu account.<br><br> +If you do not currently have an account, please click the button to create one. + + + WordBreak|AlignVCenter + + + + + registerNew + + + true + + + Re&gister New Account + + + Register a new account on this network. + + + Register a new account on this network. + + + + + + + spacer15 + + + Vertical + + + Expanding + + + + 20 + 80 + + + + + + + + tab + + + A&ccount Preferences + + + + unnamed + + + + spacer16 + + + Vertical + + + Expanding + + + + 20 + 160 + + + + + + groupBox64 + + + Connection Preferences + + + + unnamed + + + + dccCheck_ + + + true + + + &Use direct connections (DCC) + + + true + + + + + layout65 + + + + unnamed + + + + textLabel1_2 + + + Use protocol encr&yption (SSL): + + + useTls_ + + + Whether or not you want to enable SSL encrypted communication with the server. + + + Whether or not you want to enable SSL encrypted communication with the server. Note that this is not end-to-end encryption, but rather encrypted communication with the server. + + + + + + If Available + + + + + Required + + + + + Do Not Use + + + + useTls_ + + + false + + + false + + + Whether or not you want to enable SSL encrypted communication with the server. + + + Whether or not you want to enable SSL encrypted communication with the server. Note that this is not end-to-end encryption, but rather encrypted communication with the server. + + + + + + + cacheServersCheck__ + + + false + + + C&ache server information + + + true + + + Cache connection information for each server connected to in case the main load-balancing server fails. + + + This option is used whenever the primary Gadu-Gadu load-balancing server fails. If this is checked, Kopete will try to connect to the actual servers directly using cached information about them. This prevents connection errors when the main load-balancing server does not answer. In practice it only helps very rarely. + + + + + ignoreCheck_ + + + true + + + Ignore people off your contact list + + + + + + false + + + + + + + + + TabPage + + + U&ser Information + + + + unnamed + + + + connectLabel + + + true + + + + 255 + 0 + 0 + + + + + 1 + + + + <p align="center">You must be connected to change your Personal Information.</p> + + + + + userInformation + + + true + + + User Information + + + + unnamed + + + + layout9 + + + + unnamed + + + + layout8 + + + + unnamed + + + + textLabel1_5 + + + Name: + + + + + uiSurnamea + + + Surname: + + + + + textLabel1_5_3 + + + Your nick name: + + + + + textLabel1_5_3_2 + + + Gender: + + + + + textLabel1_5_3_2_2 + + + Year of birth: + + + + + textLabel1_5_3_2_3 + + + City: + + + + + + + layout7 + + + + unnamed + + + + uiName + + + false + + + + + uiSurname + + + false + + + + + nickName + + + true + + + + + + + + + + + Female + + + + + Male + + + + uiGender + + + false + + + + + uiYOB + + + false + + + + + uiCity + + + false + + + + + + + + + textLabel1_5_3_2_4_2 + + + Values below are going to be used in search, but will not appear in results. + + + + + spacer15_3 + + + Vertical + + + Expanding + + + + 20 + 30 + + + + + + layout12 + + + + unnamed + + + + layout11 + + + + unnamed + + + + textLabel1_5_3_2_4 + + + Maiden name: + + + + + textLabel1_5_3_2_4_3 + + + City of origin: + + + + + + + layout10 + + + + unnamed + + + + uiMeiden + + + false + + + + + uiOrgin + + + false + + + + + + + + + + + + + TabPage + + + &File Transfer + + + + unnamed + + + + dcc + + + false + + + Global DCC Options + + + + unnamed + + + + textLabel1_4 + + + <qt><p align="center"><font color="#ff0000">These options affect <b>all</b> Gadu-Gadu accounts.</font></p></qt> + + + + + optionOverrideDCC + + + &Override default configuration + + + + + layout33 + + + + unnamed + + + + layout32 + + + + unnamed + + + 3 + + + + textLabel2 + + + false + + + Local &IP address / + + + ipAddress + + + + + textLabel2_3 + + + false + + + po&rt: + + + dccPort + + + + + + + ipAddress + + + false + + + + 7 + 0 + 0 + 0 + + + + 0.0.0.0 + + + + + dccPort + + + false + + + 65535 + + + 8010 + + + + + + + + + spacer9 + + + Vertical + + + Expanding + + + + 20 + 180 + + + + + + + + + labelStatusMessage + + + + + + AlignCenter + + + + + + + Kopete::UI::PasswordWidget +
    kopetepasswordwidget.h
    + + 50 + 50 + + 0 + + 1 + 0 + 0 + 0 + + image0 + changed() +
    +
    + + + 89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b0000032949444154388db59531681b6714c77f32373c8186ef0305eea005093258900eca26d30e3174a8a807d1c9ee940e5d4a276f09a414e22974ee609a4c75a0857a70a20c199ce93424e43414aee0c26910dc8105f7410df706413a7c915551db049a3e38b87bf7bedffddfc7ff7d578be398456c6c6cbce13d441cc7b5da02fcf4e8e99bde7a8f899b501515d959f64e10e71cd949c6e8d508e6cb7cb050fae49727444d87ed08a566dc0cea545a621b96725e62c522f312c4929ff9e7725e6203439282ec0bc72f74150c30c927d89690163f539619a044564973a1980ae54c01c136a1db518a0024808942780dead16a27e7e0ca55949a81668023b242fcd2901c394663072cd408ad75e18b6d43a7076143710aa1b9049ccd326e064a5979e8f0191cfc5878544368af1b24807caa4cfe507ef8aea0bf6dd8b92de7f00bc1562c95e64416e297f216aadcfa3ca43f10da1f8243112286871507fb05c3c7059d568bde96c5885b01af2d6e4a2db10dc8ff128e0fdd39f4cbaf8576dbe170702afcf6b86467bbce57df8680f0d3230767e0e62bdc55c5e53c476742fabbc318437f209886c3cd41d4b0f74049c78ef21476ef5846cf7ded2831848d55f0aa62816caade11adb7ed2fa0f71ce9d8619ac2e627824a45a72b00e413c5a95c0cf63e052bbe2014bfa738c3de3d251dfb0f8a80fda04e6480600113cc558a11a0e10b93a9225886cff04a8d10868662eab87f37271e59f2136f85a855bfda15f9594eb7a3b4ae0b933f95e161c5ceed88f254e97f2ad49b75eedf8562e2d8fb264355314da1dbada866abe47fedb106d01f78b71fec170c8f7276ef58da3de8f64a76bf6f634283730e9d2b9b8390ce0dae565c6a8e04b0710b746678f8a8e0e18382d173a1d7151c909fe4e84ccf57be3e76245b115143584ee73f27afc8e80b4c667e4c37b7054c8be1afde0de978a9c63485fea0457cec70aa089015ab9297e0938c240573cdb7651a4a7f20f43feb304a72aac2e73bd723da1fe5746ec0682bc26070f38c345905d7e238f6077c00dd8f85280211fcd91af84b02ef94a50c004502c1394813252f14575ca09839242f9484cb42df31e763edd237ff31d6c0ffa3fe17f0fb86c7715cfb1ba8bd86cc8d2decd30000000049454e44ae426082 + + + + + optionOverrideDCC + toggled(bool) + dccPort + setEnabled(bool) + + + optionOverrideDCC + toggled(bool) + ipAddress + setEnabled(bool) + + + optionOverrideDCC + toggled(bool) + textLabel2_3 + setEnabled(bool) + + + optionOverrideDCC + toggled(bool) + textLabel2 + setEnabled(bool) + + + + tabWidget4 + loginEdit_ + autoLoginCheck_ + registerNew + cacheServersCheck__ + dccCheck_ + useTls_ + optionOverrideDCC + ipAddress + dccPort + + + + klineedit.h + krestrictedline.h + +
    diff --git a/kopete/protocols/gadu/ui/gaduregisteraccountui.ui b/kopete/protocols/gadu/ui/gaduregisteraccountui.ui new file mode 100644 index 00000000..5a0e475e --- /dev/null +++ b/kopete/protocols/gadu/ui/gaduregisteraccountui.ui @@ -0,0 +1,423 @@ + +GaduRegisterAccountUI + + + GaduRegisterAccountUI + + + + 0 + 0 + 376 + 394 + + + + Register Account - Gadu-Gadu + + + + unnamed + + + + layout33 + + + + unnamed + + + + pixmapEmailAddress + + + + 0 + 0 + 0 + 0 + + + + + 16 + 16 + + + + + 32767 + 32767 + + + + true + + + + + labelPasswordVerify + + + true + + + Repeat pass&word: + + + valuePasswordVerify + + + A confirmation of the password you would like to use. + + + A confirmation of the password you would like to use for this account. + + + + + valuePassword + + + Password + + + The password you would like to use. + + + The password you would like to use for this account. + + + + + valueEmailAddress + + + Your E-mail address. + + + The E-mail address you would like to use to register this account. + + + + + pixmapVerificationSequence + + + + 0 + 0 + 0 + 0 + + + + + 16 + 16 + + + + + 32767 + 32767 + + + + true + + + + + labelEmailAddress + + + &E-Mail address: + + + valueEmailAddress + + + Your E-mail address. + + + The E-mail address you would like to use to register this account. + + + + + pixmapPasswordVerify + + + + 0 + 0 + 0 + 0 + + + + + 16 + 16 + + + + + 32767 + 32767 + + + + true + + + + + labelVerificationSequence + + + true + + + &Verification sequence: + + + valueVerificationSequence + + + The text from the image below. + + + The text from the image below. This is used to prevent abusive automated registration scripts. + + + + + valueVerificationSequence + + + The text from the image below. + + + The text from the image below. This is used to prevent abusive automated registration scripts. + + + + + pixmapPassword + + + + 0 + 0 + 0 + 0 + + + + + 16 + 16 + + + + + 32767 + 32767 + + + + true + + + + + labelPassword + + + &Password: + + + valuePassword + + + The password you would like to use. + + + The password you would like to use for this account. + + + + + valuePasswordVerify + + + Password + + + A confirmation of the password you would like to use. + + + A confirmation of the password you would like to use for this account. + + + + + + + layoutImageCenter + + + + unnamed + + + + spacerImageLeft + + + Horizontal + + + Expanding + + + + 23 + 20 + + + + + + pixmapToken + + + + 0 + 0 + 20 + 13 + + + + + 256 + 64 + + + + + 256 + 64 + + + + PaletteForeground + + + + 255 + 255 + 255 + + + + Box + + + Sunken + + + true + + + Gadu-Gadu registration token. + + + This field contains an image with number that you need to type into the <b>Verification Sequence</b> field above. + + + + + spacerImageRight + + + Horizontal + + + Expanding + + + + 22 + 20 + + + + + + + + labelInstructions + + + + 3 + 3 + 0 + 0 + + + + <i>Type the letters and numbers shown in the image above into the <b>Verification Sequence</b> field. This is used to prevent automated registration abuse.</i> + + + WordBreak|AlignTop + + + + + spacerMiddle + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + labelStatusMessage + + + + + + AlignCenter + + + + + + valueEmailAddress + valuePassword + valuePasswordVerify + valueVerificationSequence + + + + klineedit.h + klineedit.h + klineedit.h + + diff --git a/kopete/protocols/gadu/ui/gadusearch.ui b/kopete/protocols/gadu/ui/gadusearch.ui new file mode 100644 index 00000000..ac215b1a --- /dev/null +++ b/kopete/protocols/gadu/ui/gadusearch.ui @@ -0,0 +1,692 @@ + +GaduPublicDirectory + + + GaduPublicDirectory + + + + 0 + 0 + 570 + 386 + + + + + unnamed + + + + pubsearch + + + + page + + + 0 + + + + unnamed + + + + buttonGroup2 + + + 0 + + + + + + true + + + + unnamed + + + + layout38 + + + + unnamed + + + + layout36 + + + + unnamed + + + + layout31 + + + + unnamed + + + + layout29 + + + + unnamed + + + + textLabel1a + + + Name: + + + + + textLabel1_2a + + + Surname: + + + + + textLabel1_3a + + + Nick: + + + + + textLabel1_3_2a + + + City: + + + + + + + layout30 + + + + unnamed + + + + nameS + + + + + surname + + + + + nick + + + + + cityS + + + + + + + + + layout34 + + + + unnamed + + + + textLabel1_2_2a + + + Age from: + + + + + ageFrom + + + 0 + + + PlusMinus + + + 120 + + + 0 + + + 0 + + + + + textLabel1_2_3 + + + to: + + + + + ageTo + + + 0 + + + PlusMinus + + + 120 + + + 0 + + + 0 + + + + + spacer1 + + + Horizontal + + + Expanding + + + + 297 + 21 + + + + + + + + layout32 + + + + unnamed + + + + textLabel1_4a + + + Gender: + + + PlainText + + + false + + + + + + + + + + + Male + + + + + Female + + + + gender + + + TabFocus + + + + + + + + + + + layout37 + + + + unnamed + + + + layout33 + + + + unnamed + + + + uin_static + + + User number: + + + + + UIN + + + 32 + + + Normal + + + + + + + + + radioByUin + + + Request information about user: + + + true + + + + + radioByData + + + true + + + Search by specified data: + + + + + layout35 + + + + unnamed + + + + onlyOnline + + + Lookup only those that are currently online + + + + + spacer2 + + + Horizontal + + + Expanding + + + + 224 + 21 + + + + + + + + spacer5 + + + Vertical + + + Expanding + + + + 20 + 21 + + + + + + + + + + page + + + 1 + + + + unnamed + + + + + Status + + + image0 + + + true + + + false + + + + + Name + + + true + + + false + + + + + Nick Name + + + true + + + false + + + + + Age + + + true + + + false + + + + + City + + + true + + + false + + + + + UIN + + + true + + + false + + + + + 12 + + + DONT_TRANSLATE + + + DONT_TRANSL + + + 999 + + + DONT_TRANSL + + + 245324956234 + + + + + + + + + + + + + + + + + + + + + + listFound + + + + 3 + 3 + 0 + 0 + + + + + 512 + 256 + + + + + 640 + 512 + + + + 2 + + + AutoOneFit + + + Extended + + + true + + + true + + + NoColumn + + + false + + + false + + + false + + + false + + + + + + + + + + 789c8590cf6ac3300cc6ef790a13dd4259d37414c6e823acec58183be85fd21dda42d71ec6d8bb57929d50b6c28463f4fba4cf913d6fd2f6f52535f3eaf38ce70f4ebcc3536ae4b2df7fbdbdafbfab7ab14ab69ed2a29e55f543e2b4391ed473b01cda08c7de9127544765796cc3288e7d275dbb74c4829aab43603f7a29a362ae7246afc70c39004a5234434084488a0686a179923543f42f3698fa90984990a63e1389894455a7f3008818544004217bdd695d117e60d19054c49650d1c22b7e07d5d1ebffb08e61b0e6db996d0a4421fd6fe63f77bbfb06bfdeeae7b9ba0259af7424 + + + + + radioByData + toggled(bool) + UIN + setDisabled(bool) + + + radioByData + toggled(bool) + uin_static + setDisabled(bool) + + + radioByUin + toggled(bool) + ageFrom + setDisabled(bool) + + + radioByUin + toggled(bool) + ageTo + setDisabled(bool) + + + radioByUin + toggled(bool) + cityS + setDisabled(bool) + + + radioByUin + toggled(bool) + gender + setDisabled(bool) + + + radioByUin + toggled(bool) + nameS + setDisabled(bool) + + + radioByUin + toggled(bool) + surname + setDisabled(bool) + + + radioByUin + toggled(bool) + textLabel1_2_2a + setDisabled(bool) + + + radioByUin + toggled(bool) + textLabel1_2a + setDisabled(bool) + + + radioByUin + toggled(bool) + textLabel1_3_2a + setDisabled(bool) + + + radioByUin + toggled(bool) + textLabel1a + setDisabled(bool) + + + radioByUin + toggled(bool) + nick + setDisabled(bool) + + + radioByUin + toggled(bool) + textLabel1_4a + setDisabled(bool) + + + radioByUin + toggled(bool) + textLabel1_3a + setDisabled(bool) + + + + nameS + surname + nick + cityS + gender + UIN + ageFrom + ageTo + onlyOnline + listFound + + + + krestrictedline.h + klistview.h + + diff --git a/kopete/protocols/groupwise/DESIGN b/kopete/protocols/groupwise/DESIGN new file mode 100644 index 00000000..aa976191 --- /dev/null +++ b/kopete/protocols/groupwise/DESIGN @@ -0,0 +1,50 @@ +Plan for updating Kopete's groupwise support for GW7. + +Review protocol DONE + +libgroupwise: + +Update error codes DONE +Extend event protocol to handle new event formats PROGRESS +Make protocol version selectable in Client DONE +Add new event tasks + Event codes DONE + Broadcast in ConferenceTask - small Event proto update DONE + Chat events in ChatroomTask + ConfAttribUpdate - large Event proto update + ConfTopicChanged + ChatroomNameChanged - large Event proto update + ConfRightsChanged - vlarge Event proto update + ConfRemoved - large Event proto update and more info from Mike. + ChatOwner changed - vlarge Event proto update +(large update -> requires new EventTransfer subclass) + +Update Client with event signals + Broadcast DONE + Chatroom + +Add request tasks + +Update Attribs + +Search for chats + Get Results + Stop Search + Update Numbers + +Add Client interface for requests + +kopete_groupwise: + +Handle broadcasts DONE + +Set custom statuses on the server + +search for chats + +join chat + +create chat + +modify chat + diff --git a/kopete/protocols/groupwise/Makefile.am b/kopete/protocols/groupwise/Makefile.am new file mode 100644 index 00000000..0c8a38fc --- /dev/null +++ b/kopete/protocols/groupwise/Makefile.am @@ -0,0 +1,26 @@ + + +SUBDIRS = icons libgroupwise ui + +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +METASOURCES = AUTO + +noinst_HEADERS = gwprotocol.h gwcontact.h gwaccount.h gwbytestream.h \ + gwconnector.h gwmessagemanager.h gwcontactlist.h +kde_module_LTLIBRARIES = kopete_groupwise.la +kopete_groupwise_la_SOURCES = gwprotocol.cpp gwcontact.cpp gwaccount.cpp \ + gwbytestream.cpp gwconnector.cpp gwmessagemanager.cpp gwcontactlist.cpp +kopete_groupwise_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) \ + $(all_libraries) +kopete_groupwise_la_LIBADD = ui/libkopetegroupwiseui.la \ + libgroupwise/libgroupwise.la ../../libkopete/libkopete.la $(LIB_KIO) + +service_DATA = kopete_groupwise.desktop +servicedir = $(kde_servicesdir) +INCLUDES = -I$(top_srcdir)/kopete/protocols/groupwise/libgroupwise/tasks \ + -I$(top_srcdir)/kopete/protocols/groupwise/libgroupwise/qca/src -I$(top_srcdir)/kopete/protocols/groupwise/libgroupwise \ + -I$(top_srcdir)/kopete/protocols/groupwise/ui -I$(top_builddir)/kopete/protocols/groupwise/ui + +mydatadir = $(kde_datadir)/kopete_groupwise +mydata_DATA = gwchatui.rc diff --git a/kopete/protocols/groupwise/gwaccount.cpp b/kopete/protocols/groupwise/gwaccount.cpp new file mode 100644 index 00000000..48ef3833 --- /dev/null +++ b/kopete/protocols/groupwise/gwaccount.cpp @@ -0,0 +1,1646 @@ +/* + gwaccount.cpp - Kopete GroupWise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Testbed + Copyright (c) 2003 by Will Stephenson + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "client.h" +#include "qca.h" +#include "gwcontact.h" +#include "gwcontactlist.h" +#include "gwprotocol.h" +#include "gwconnector.h" +#include "gwmessagemanager.h" +#include "privacymanager.h" +#include "qcatlshandler.h" +#include "userdetailsmanager.h" +#include "tasks/createcontacttask.h" +#include "tasks/createcontactinstancetask.h" +#include "tasks/deleteitemtask.h" +#include "tasks/movecontacttask.h" +#include "tasks/updatecontacttask.h" +#include "tasks/updatefoldertask.h" +#include "ui/gwchatsearchdialog.h" +#include "ui/gwprivacy.h" +#include "ui/gwprivacydialog.h" +#include "ui/gwreceiveinvitationdialog.h" + +#include "gwaccount.h" + +GroupWiseAccount::GroupWiseAccount( GroupWiseProtocol *parent, const QString& accountID, const char *name ) +: Kopete::ManagedConnectionAccount ( parent, accountID, 0, "groupwiseaccount" ) +{ + Q_UNUSED( name ); + // Init the myself contact + setMyself( new GroupWiseContact( this, accountId(), Kopete::ContactList::self()->myself(), 0, 0, 0 ) ); + myself()->setOnlineStatus( GroupWiseProtocol::protocol()->groupwiseOffline ); + + // Contact list management + QObject::connect( Kopete::ContactList::self(), SIGNAL( groupRenamed( Kopete::Group *, const QString & ) ), + SLOT( slotKopeteGroupRenamed( Kopete::Group * ) ) ); + QObject::connect( Kopete::ContactList::self(), SIGNAL( groupRemoved( Kopete::Group * ) ), + SLOT( slotKopeteGroupRemoved( Kopete::Group * ) ) ); + + m_actionAutoReply = new KAction ( i18n( "&Set Auto-Reply..." ), QString::null, 0, this, + SLOT( slotSetAutoReply() ), this, "actionSetAutoReply"); + m_actionJoinChatRoom = new KAction ( i18n( "&Join Channel..." ), QString::null, 0, this, + SLOT( slotJoinChatRoom() ), this, "actionJoinChatRoom"); + m_actionManagePrivacy = new KAction ( i18n( "&Manage Privacy..." ), QString::null, 0, this, + SLOT( slotPrivacy() ), this, "actionPrivacy"); + + m_connector = 0; + m_QCATLS = 0; + m_tlsHandler = 0; + m_clientStream = 0; + m_client = 0; + m_dontSync = false; + m_serverListModel = 0; +} + +GroupWiseAccount::~GroupWiseAccount() +{ + cleanup(); +} + +KActionMenu* GroupWiseAccount::actionMenu() +{ + KActionMenu *m_actionMenu=Kopete::Account::actionMenu(); + + m_actionAutoReply->setEnabled( isConnected() ); + m_actionManagePrivacy->setEnabled( isConnected() ); + m_actionJoinChatRoom->setEnabled( isConnected() ); + m_actionMenu->insert( m_actionManagePrivacy ); + m_actionMenu->insert( m_actionAutoReply ); + m_actionMenu->insert( m_actionJoinChatRoom ); + /* Used for debugging */ + /* + theActionMenu->insert( new KAction ( "Test rtfize()", QString::null, 0, this, + SLOT( slotTestRTFize() ), this, + "actionTestRTFize") ); + */ + return m_actionMenu; +} + +int GroupWiseAccount::port() const +{ + return configGroup()->readNumEntry( "Port" ); +} + +const QString GroupWiseAccount::server() const +{ + return configGroup()->readEntry( "Server" ); +} + +Client * GroupWiseAccount::client() const +{ + return m_client; +} + +GroupWiseProtocol *GroupWiseAccount::protocol() const +{ + return static_cast( Kopete::Account::protocol() ); +} + +GroupWiseChatSession * GroupWiseAccount::chatSession( Kopete::ContactPtrList others, const GroupWise::ConferenceGuid & guid, Kopete::Contact::CanCreateFlags canCreate ) +{ + GroupWiseChatSession * chatSession = 0; + do // one iteration misuse of do...while to enable an easy drop-out once we locate a manager + { + // do we have a manager keyed by GUID? + if ( !guid.isEmpty() ) + { + chatSession = findChatSessionByGuid( guid ); + if ( chatSession ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " found a message manager by GUID: " << guid << endl; + break; + } + } + // does the factory know about one, going on the chat members? + chatSession = dynamic_cast( + Kopete::ChatSessionManager::self()->findChatSession( myself(), others, protocol() ) ); + if ( chatSession ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " found a message manager by members with GUID: " << chatSession->guid() << endl; + // re-add the returning contact(s) (very likely only one) to the chat + Kopete::Contact * returningContact; + for ( returningContact = others.first(); returningContact; returningContact = others.next() ) + chatSession->joined( static_cast( returningContact ) ); + + if ( !guid.isEmpty() ) + chatSession->setGuid( guid ); + break; + } + // we don't have an existing message manager for this chat, so create one if we may + if ( canCreate ) + { + chatSession = new GroupWiseChatSession( myself(), others, protocol(), guid ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << + " created a new message manager with GUID: " << chatSession->guid() << endl; + m_chatSessions.append( chatSession ); + // listen for the message manager telling us that the user + //has left the conference so we remove it from our map + QObject::connect( chatSession, SIGNAL( leavingConference( GroupWiseChatSession * ) ), + SLOT( slotLeavingConference( GroupWiseChatSession * ) ) ); + break; + } + //kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << + // " no message manager available." << endl; + } + while ( 0 ); + //dumpManagers(); + return chatSession; +} + +GroupWiseChatSession * GroupWiseAccount::findChatSessionByGuid( const GroupWise::ConferenceGuid & guid ) +{ + GroupWiseChatSession * chatSession = 0; + QValueList::ConstIterator it; + for ( it = m_chatSessions.begin(); it != m_chatSessions.end(); ++it ) + { + if ( (*it)->guid() == guid ) + { + chatSession = *it; + break; + } + } + return chatSession; +} + +GroupWiseContact * GroupWiseAccount::contactForDN( const QString & dn ) +{ + QDictIterator it( contacts() ); + // check if we have a DN for them + for( ; it.current(); ++it ) + { + GroupWiseContact * candidate = static_cast( it.current() ); + if ( candidate && candidate->dn() == dn ) + return candidate; + } + // we might have just added the contact with a user ID, try the first section of the dotted dn + return static_cast< GroupWiseContact * >( contacts()[ protocol()->dnToDotted( dn ).section( '.', 0, 0 ) ] ); +} + +void GroupWiseAccount::setAway( bool away, const QString & reason ) +{ + if ( away ) + { + if ( Kopete::Away::getInstance()->idleTime() > 10 ) // don't go AwayIdle unless the user has actually been idle this long + setOnlineStatus( protocol()->groupwiseAwayIdle, QString::null ); + else + setOnlineStatus( protocol()->groupwiseAway, reason ); + } + else + setOnlineStatus( protocol()->groupwiseAvailable ); +} + +void GroupWiseAccount::performConnectWithPassword( const QString &password ) +{ + if ( password.isEmpty() ) + { + disconnect(); + return; + } + // don't try and connect if we are already connected + if ( isConnected () ) + return; + + bool sslPossible = QCA::isSupported(QCA::CAP_TLS); + + if (!sslPossible) + { + KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget (), KMessageBox::Error, + i18n ("SSL support could not be initialized for account %1. This is most likely because the QCA TLS plugin is not installed on your system."). + arg(myself()->contactId()), + i18n ("GroupWise SSL Error")); + return; + } + if ( m_client ) + { + m_client->close(); + cleanup(); + } + // set up network classes + m_connector = new KNetworkConnector( 0 ); + //myConnector->setOptHostPort( "localhost", 8300 ); + m_connector->setOptHostPort( server(), port() ); + m_connector->setOptSSL( true ); + Q_ASSERT( QCA::isSupported(QCA::CAP_TLS) ); + m_QCATLS = new QCA::TLS; + m_tlsHandler = new QCATLSHandler( m_QCATLS ); + m_clientStream = new ClientStream( m_connector, m_tlsHandler, 0); + + QObject::connect( m_connector, SIGNAL( error() ), this, SLOT( slotConnError() ) ); + QObject::connect( m_connector, SIGNAL( connected() ), this, SLOT( slotConnConnected() ) ); + + QObject::connect (m_clientStream, SIGNAL (connectionClosed()), + this, SLOT (slotCSDisconnected())); + QObject::connect (m_clientStream, SIGNAL (delayedCloseFinished()), + this, SLOT (slotCSDisconnected())); + // Notify us when the transport layer is connected + QObject::connect( m_clientStream, SIGNAL( connected() ), SLOT( slotCSConnected() ) ); + // it's necessary to catch this signal and tell the TLS handler to proceed + // even if we don't check cert validity + QObject::connect( m_tlsHandler, SIGNAL(tlsHandshaken()), SLOT( slotTLSHandshaken()) ); + // starts the client once the security layer is up, but see below + QObject::connect( m_clientStream, SIGNAL( securityLayerActivated(int) ), SLOT( slotTLSReady(int) ) ); + // we could handle login etc in start(), in which case we would emit this signal after that + //QObject::connect (jabberClientStream, SIGNAL (authenticated()), + // this, SLOT (slotCSAuthenticated ())); + // we could also get do the actual login in response to this.. + //QObject::connect (m_clientStream, SIGNAL (needAuthParams(bool, bool, bool)), + // this, SLOT (slotCSNeedAuthParams (bool, bool, bool))); + + // not implemented: warning + QObject::connect( m_clientStream, SIGNAL( warning(int) ), SLOT( slotCSWarning(int) ) ); + // not implemented: error + QObject::connect( m_clientStream, SIGNAL( error(int) ), SLOT( slotCSError(int) ) ); + + m_client = new Client( 0, CMSGPRES_GW_6_5 ); + + // NB these are prefixed with QObject:: to avoid any chance of a clash with our connect() methods. + // we connected successfully + QObject::connect( m_client, SIGNAL( loggedIn() ), SLOT( slotLoggedIn() ) ); + // or connection failed + QObject::connect( m_client, SIGNAL( loginFailed() ), SLOT( slotLoginFailed() ) ); + // folder listed + QObject::connect( m_client, SIGNAL( folderReceived( const FolderItem & ) ), SLOT( receiveFolder( const FolderItem & ) ) ); + // contact listed + QObject::connect( m_client, SIGNAL( contactReceived( const ContactItem & ) ), SLOT( receiveContact( const ContactItem & ) ) ); + // contact details listed + QObject::connect( m_client, SIGNAL( contactUserDetailsReceived( const GroupWise::ContactDetails & ) ), SLOT( receiveContactUserDetails( const GroupWise::ContactDetails & ) ) ); + // contact status changed + QObject::connect( m_client, SIGNAL( statusReceived( const QString &, Q_UINT16, const QString & ) ), SLOT( receiveStatus( const QString &, Q_UINT16 , const QString & ) ) ); + // incoming message + QObject::connect( m_client, SIGNAL( messageReceived( const ConferenceEvent & ) ), SLOT( handleIncomingMessage( const ConferenceEvent & ) ) ); + // auto reply to one of our messages because the recipient is away + QObject::connect( m_client, SIGNAL( autoReplyReceived( const ConferenceEvent & ) ), SLOT( handleIncomingMessage( const ConferenceEvent & ) ) ); + + QObject::connect( m_client, SIGNAL( ourStatusChanged( GroupWise::Status, const QString &, const QString & ) ), SLOT( changeOurStatus( GroupWise::Status, const QString &, const QString & ) ) ); + // conference events + QObject::connect( m_client, + SIGNAL( conferenceCreated( const int, const GroupWise::ConferenceGuid & ) ), + SIGNAL( conferenceCreated( const int, const GroupWise::ConferenceGuid & ) ) ); + QObject::connect( m_client, SIGNAL( conferenceCreationFailed( const int, const int ) ), SIGNAL( conferenceCreationFailed( const int, const int ) ) ); + QObject::connect( m_client, SIGNAL( invitationReceived( const ConferenceEvent & ) ), SLOT( receiveInvitation( const ConferenceEvent & ) ) ); + QObject::connect( m_client, SIGNAL( conferenceLeft( const ConferenceEvent & ) ), SLOT( receiveConferenceLeft( const ConferenceEvent & ) ) ); + QObject::connect( m_client, SIGNAL( conferenceJoinNotifyReceived( const ConferenceEvent & ) ), SLOT( receiveConferenceJoinNotify( const ConferenceEvent & ) ) ); + QObject::connect( m_client, SIGNAL( inviteNotifyReceived( const ConferenceEvent & ) ), SLOT( receiveInviteNotify( const ConferenceEvent & ) ) ); + QObject::connect( m_client, SIGNAL( invitationDeclined( const ConferenceEvent & ) ), SLOT( receiveInviteDeclined( const ConferenceEvent & ) ) ); + + QObject::connect( m_client, SIGNAL( conferenceJoined( const GroupWise::ConferenceGuid &, const QStringList &, const QStringList & ) ), SLOT( receiveConferenceJoin( const GroupWise::ConferenceGuid &, const QStringList & , const QStringList & ) ) ); + + // typing events + QObject::connect( m_client, SIGNAL( contactTyping( const ConferenceEvent & ) ), + SIGNAL( contactTyping( const ConferenceEvent & ) ) ); + QObject::connect( m_client, SIGNAL( contactNotTyping( const ConferenceEvent & ) ), + SIGNAL( contactNotTyping( const ConferenceEvent & ) ) ); + // misc + QObject::connect( m_client, SIGNAL( accountDetailsReceived( const GroupWise::ContactDetails &) ), SLOT( receiveAccountDetails( const GroupWise::ContactDetails & ) ) ); + QObject::connect( m_client, SIGNAL( connectedElsewhere() ), SLOT( slotConnectedElsewhere() ) ); + // privacy - contacts can't connect directly to this signal because myself() is initialised before m_client + QObject::connect( m_client->privacyManager(), SIGNAL( privacyChanged( const QString &, bool ) ), SIGNAL( privacyChanged( const QString &, bool ) ) ); + + // GW7 + QObject::connect( m_client, SIGNAL( broadcastReceived( const ConferenceEvent & ) ), SLOT( handleIncomingMessage( const ConferenceEvent & ) ) ); + QObject::connect( m_client, SIGNAL( systemBroadcastReceived( const ConferenceEvent & ) ), SLOT( handleIncomingMessage( const ConferenceEvent & ) ) ); + + struct utsname utsBuf; + uname (&utsBuf); + m_client->setClientName ("Kopete"); + m_client->setClientVersion ( kapp->aboutData ()->version () ); + m_client->setOSName (QString ("%1 %2").arg (utsBuf.sysname, 1).arg (utsBuf.release, 2)); + + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Connecting to GroupWise server " << server() << ":" << port() << endl; + + NovellDN dn; + dn.dn = "maeuschen"; + dn.server = "reiser.suse.de"; + m_serverListModel = new GWContactList( this ); + myself()->setOnlineStatus( protocol()->groupwiseConnecting ); + m_client->connectToServer( m_clientStream, dn, true ); + + QObject::connect( m_client, SIGNAL( messageSendingFailed() ), SLOT( slotMessageSendingFailed() ) ); +} + +void GroupWiseAccount::slotMessageSendingFailed() +{ + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n("Message Sending Failed", "Kopete was not able to send the last message sent on account '%1'.\nIf possible, please send the console output from Kopete to for analysis." ).arg( accountId() ) , i18n ("Unable to Send Message on Account '%1'").arg( accountId() ) ); +} + +void GroupWiseAccount::setOnlineStatus( const Kopete::OnlineStatus& status, const QString &reason ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + if ( status == protocol()->groupwiseUnknown + || status == protocol()->groupwiseConnecting + || status == protocol()->groupwiseInvalid ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " called with invalid status \"" + << status.description() << "\"" << endl; + } + // going offline + else if ( status == protocol()->groupwiseOffline ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " DISCONNECTING" << endl; + disconnect(); + } + // changing status + else if ( isConnected() ) + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "changing status to \"" << status.description() << "\"" << endl; + // Appear Offline is achieved by explicitly setting the status to offline, + // rather than disconnecting as when really going offline. + if ( status == protocol()->groupwiseAppearOffline ) + m_client->setStatus( GroupWise::Offline, reason, configGroup()->readEntry( "AutoReply" ) ); + else + m_client->setStatus( ( GroupWise::Status )status.internalStatus(), reason, configGroup()->readEntry( "AutoReply" ) ); + } + // going online + else + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Must be connected before changing status" << endl; + m_initialReason = reason; + connect( status ); + } +} + +void GroupWiseAccount::disconnect () +{ + disconnect ( Manual ); +} + +void GroupWiseAccount::disconnect( Kopete::Account::DisconnectReason reason ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + + if( isConnected () ) + { + kdDebug (GROUPWISE_DEBUG_GLOBAL) << k_funcinfo << "Still connected, closing connection..." << endl; + QValueList::ConstIterator it; + for ( it = m_chatSessions.begin() ; it != m_chatSessions.end(); ++it ) + (*it)->setClosed(); + + /* Tell backend class to disconnect. */ + m_client->close (); + } + + // clear the model of the server side contact list, so that when we reconnect, there will not be any stale entries to confuse GroupWiseContact::syncGroups() + delete m_serverListModel; + m_serverListModel = 0; + + // make sure that the connection animation gets stopped if we're still + // in the process of connecting + myself()->setOnlineStatus( GroupWiseProtocol::protocol()->groupwiseOffline ); + + disconnected( reason ); + kdDebug(GROUPWISE_DEBUG_GLOBAL) << k_funcinfo << "Disconnected." << endl; +} + +void GroupWiseAccount::cleanup() +{ + delete m_client; + delete m_clientStream; + delete m_QCATLS; + delete m_connector; + + m_connector = 0; + m_QCATLS = 0; + m_clientStream = 0; + m_client = 0; +} + +void GroupWiseAccount::createConference( const int clientId, const QStringList& invitees ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + // TODO: remove this it prevents sending a list of participants with the createconf + if ( isConnected() ) + m_client->createConference( clientId , invitees ); +} + +void GroupWiseAccount::sendInvitation( const GroupWise::ConferenceGuid & guid, const QString & dn, const QString & message ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + if ( isConnected() ) + { + GroupWise::OutgoingMessage msg; + msg.guid = guid; + msg.message = message; + m_client->sendInvitation( guid, dn, msg ); + } +} + +void GroupWiseAccount::slotLoggedIn() +{ + reconcileOfflineChanges(); + // set local status display + myself()->setOnlineStatus( protocol()->groupwiseAvailable ); + // set status on server + if ( initialStatus() != Kopete::OnlineStatus(Kopete::OnlineStatus::Online) && + ( ( GroupWise::Status )initialStatus().internalStatus() != GroupWise::Unknown ) ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "Initial status is not online, setting status to " << initialStatus().internalStatus() << endl; + m_client->setStatus( ( GroupWise::Status )initialStatus().internalStatus(), m_initialReason, configGroup()->readEntry( "AutoReply" ) ); + } +} + +void GroupWiseAccount::reconcileOfflineChanges() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + m_dontSync = true; + //sanity check the server side model vs our contact list. + //Contacts might have been removed from some groups or entirely on the server. + //Any contact not present on the server should be deleted locally. + + // for each metacontact group membership: + // for each GroupWiseContact + // get its contact list instances + // get its metacontact's groups + // for each group + // is there no CLI with the same id? + // if MC has no other contacts + // if MC's groups size is 1 + // remove MC + // else + // remove from group + // else + // if MC's groups size is 1 and group is topLevel + // remove contact + // else // Contact's group membership were changed elsewhere, but we can't change it here without + // // affecting other protocols' contacts + // set flag to warn user that incompatible changes were made on other client + bool conflicts = false; + QDictIterator it( contacts() ); + for ( ; it.current(); ++it ) + { + if ( *it == myself() ) + continue; + + GroupWiseContact * c = static_cast< GroupWiseContact *>( *it ); + GWContactInstanceList instances = m_serverListModel->instancesWithDn( c->dn() ); + QPtrList groups = c->metaContact()->groups(); + QPtrListIterator grpIt( groups ); + while ( *grpIt ) + { + QPtrListIterator candidate = grpIt; + ++grpIt; + bool found = false; + GWContactInstanceList::Iterator instIt = instances.begin(); + for ( ; instIt != instances.end(); ++instIt ) + { + QString groupId = ( *candidate )->pluginData( protocol(), accountId() + " objectId" ); + if ( groupId.isEmpty() ) + if ( *candidate == Kopete::Group::topLevel() ) + groupId = "0"; // hack the top level's objectId to 0 + else + continue; + + GWFolder * folder = ::qt_cast( ( *instIt )->parent() ); + if ( folder->id == ( unsigned int )groupId.toInt() ) + { + found = true; + instances.remove( instIt ); + break; + } + } + if ( !found ) + { + if ( c->metaContact()->contacts().count() == 1 ) + { + if ( c->metaContact()->groups().count() == 1 ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "contact instance " << c->dn() << " not found on server side list, deleting metacontact with only this contact, in one group" << c->metaContact()->displayName() << endl; + Kopete::ContactList::self()->removeMetaContact( c->metaContact() ); + break; + } + else + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "contact instance " << c->dn() << " not found, removing metacontact " << c->metaContact()->displayName() << " from group " << ( *candidate )->displayName() << endl; + c->metaContact()->removeFromGroup( *candidate ); + } + } + else + { + if ( c->metaContact()->groups().count() == 1 ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "contact instance " << c->dn() << " not found, removing contact " << c->metaContact()->displayName() << " from metacontact with other contacts " << endl; + c->deleteLater(); + break; + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "metacontact " << c->metaContact()->displayName( ) << "has multiple children and group membership, and contact " << c->dn() << " was removed from one group on the server." << endl; + conflicts = true; + } + } // + } //end while, now check the next group membership + } //end for, now check the next groupwise contact + if ( conflicts ) + // show queuedmessagebox + KPassivePopup::message( i18n( "Conflicting Changes Made Offline" ), i18n( "A change happened to your GroupWise contact list while you were offline which was impossible to reconcile." ), Kopete::UI::Global::mainWidget() ); + m_dontSync = false; +} + +void GroupWiseAccount::slotLoginFailed() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + password().setWrong(); + disconnect(); + connect(); +} + +void GroupWiseAccount::slotKopeteGroupRenamed( Kopete::Group * renamedGroup ) +{ + if ( isConnected() ) + { + QString objectIdString = renamedGroup->pluginData( protocol(), accountId() + " objectId" ); + // if this group exists on the server + if ( !objectIdString.isEmpty() ) + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + + GroupWise::FolderItem fi; + fi.id = objectIdString.toInt(); + if ( fi.id != 0 ) + { + fi.sequence = renamedGroup->pluginData( protocol(), accountId() + " sequence" ).toInt(); + fi.name= renamedGroup->pluginData( protocol(), accountId() + " serverDisplayName" ); + + UpdateFolderTask * uft = new UpdateFolderTask( client()->rootTask() ); + uft->renameFolder( renamedGroup->displayName(), fi ); + uft->go( true ); + // would be safer to do this in a slot fired on uft's finished() signal + renamedGroup->setPluginData( protocol(), accountId() + " serverDisplayName", + renamedGroup->displayName() ); + } + } + } + //else + // errornotconnected +} + +void GroupWiseAccount::slotKopeteGroupRemoved( Kopete::Group * group ) +{ + if ( isConnected() ) + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + // the member contacts should be deleted separately, so just delete the folder here + // get the folder object id + QString objectIdString = group->pluginData( protocol(), accountId() + " objectId" ); + if ( !objectIdString.isEmpty() ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "deleting folder with objectId: " << objectIdString << endl; + int objectId = objectIdString.toInt(); + if ( objectId == 0 ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "deleted folder " << group->displayName() << " has root folder objectId 0!" << endl; + return; + } + DeleteItemTask * dit = new DeleteItemTask( client()->rootTask() ); + dit->item( 0, objectId ); + // the group is deleted synchronously after this slot returns; so there is no point listening for signals + dit->go( true ); + } + } + //else + // errornotconnected +} + +void GroupWiseAccount::slotConnError() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "Error shown when connecting failed", "Kopete was not able to connect to the GroupWise Messenger server for account '%1'.\nPlease check your server and port settings and try again." ).arg( accountId() ) , i18n ("Unable to Connect '%1'").arg( accountId() ) ); + + disconnect(); +} + +void GroupWiseAccount::slotConnConnected() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; +} + +void GroupWiseAccount::slotCSDisconnected() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Disconnected from Groupwise server." << endl; + myself()->setOnlineStatus( protocol()->groupwiseOffline ); + QValueList::ConstIterator it; + for ( it = m_chatSessions.begin() ; it != m_chatSessions.end(); ++it ) + (*it)->setClosed(); + setAllContactsStatus( protocol()->groupwiseOffline ); + client()->close(); +} + +void GroupWiseAccount::slotCSConnected() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Connected to Groupwise server." << endl; + +} + +void GroupWiseAccount::slotCSError( int error ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Got error from ClientStream:" << error << endl; +} + +void GroupWiseAccount::slotCSWarning( int warning ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Got warning from ClientStream:" << warning << endl; +} + +void GroupWiseAccount::slotTLSHandshaken() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "TLS handshake complete" << endl; + int validityResult = m_QCATLS->certificateValidityResult (); + + if( validityResult == QCA::TLS::Valid ) + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << "Certificate is valid, continuing." << endl; + // valid certificate, continue + m_tlsHandler->continueAfterHandshake (); + } + else + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << "Certificate is not valid, continuing anyway" << endl; + // certificate is not valid, query the user + if(handleTLSWarning (validityResult, server (), myself()->contactId ()) == KMessageBox::Continue) + { + m_tlsHandler->continueAfterHandshake (); + } + else + { + disconnect ( Kopete::Account::Manual ); + } + } +} + +int GroupWiseAccount::handleTLSWarning (int warning, QString server, QString accountId) +{ + QString validityString, code; + + switch(warning) + { + case QCA::TLS::NoCert: + validityString = i18n("No certificate was presented."); + code = "NoCert"; + break; + case QCA::TLS::HostMismatch: + validityString = i18n("The host name does not match the one in the certificate."); + code = "HostMismatch"; + break; + case QCA::TLS::Rejected: + validityString = i18n("The Certificate Authority rejected the certificate."); + code = "Rejected"; + break; + case QCA::TLS::Untrusted: + // FIXME: write better error message here + validityString = i18n("The certificate is untrusted."); + code = "Untrusted"; + break; + case QCA::TLS::SignatureFailed: + validityString = i18n("The signature is invalid."); + code = "SignatureFailed"; + break; + case QCA::TLS::InvalidCA: + validityString = i18n("The Certificate Authority is invalid."); + code = "InvalidCA"; + break; + case QCA::TLS::InvalidPurpose: + // FIXME: write better error message here + validityString = i18n("Invalid certificate purpose."); + code = "InvalidPurpose"; + break; + case QCA::TLS::SelfSigned: + validityString = i18n("The certificate is self-signed."); + code = "SelfSigned"; + break; + case QCA::TLS::Revoked: + validityString = i18n("The certificate has been revoked."); + code = "Revoked"; + break; + case QCA::TLS::PathLengthExceeded: + validityString = i18n("Maximum certificate chain length was exceeded."); + code = "PathLengthExceeded"; + break; + case QCA::TLS::Expired: + validityString = i18n("The certificate has expired."); + code = "Expired"; + break; + case QCA::TLS::Unknown: + default: + validityString = i18n("An unknown error occurred trying to validate the certificate."); + code = "Unknown"; + break; + } + + return KMessageBox::warningContinueCancel(Kopete::UI::Global::mainWidget (), + i18n("The certificate of server %1 could not be validated for account %2: %3"). + arg(server). + arg(accountId). + arg(validityString), + i18n("GroupWise Connection Certificate Problem"), + KStdGuiItem::cont(), + QString("KopeteTLSWarning") + server + code); +} + +void GroupWiseAccount::slotTLSReady( int secLayerCode ) +{ + // i don't know what secLayerCode is for... + Q_UNUSED( secLayerCode ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + m_client->start( server(), port(), accountId(), password().cachedValue() ); +} + +void GroupWiseAccount::handleIncomingMessage( const ConferenceEvent & message ) +{ + QString typeName = "UNKNOWN"; + if ( message.type == ReceiveMessage ) + typeName = "message"; + else if ( message.type == ReceiveAutoReply ) + typeName = "autoreply"; + else if ( message.type == ReceivedBroadcast ) + typeName = "broadcast"; + else if ( message.type == ReceivedSystemBroadcast ) + typeName = "system broadcast"; + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " received a " << typeName << " from " << message.user << ", to conference: " << message.guid << ", message: " << message.message << endl; + + GroupWiseContact * sender = contactForDN( message.user ); + if ( !sender ) + sender = createTemporaryContact( message.user ); + + // if we receive a message from an Offline contact, they are probably blocking us + // but we have to set their status to Unknown so that we can reply to them. + kdDebug( GROUPWISE_DEBUG_GLOBAL) << "sender is: " << sender->onlineStatus().description() << endl; + if ( sender->onlineStatus() == protocol()->groupwiseOffline ) { + sender->setMessageReceivedOffline( true ); + } + + Kopete::ContactPtrList contactList; + contactList.append( sender ); + // FIND A MESSAGE MANAGER FOR THIS CONTACT + GroupWiseChatSession *sess = chatSession( contactList, message.guid, Kopete::Contact::CanCreate ); + + // add an auto-reply indicator if needed + QString messageMunged = message.message; + if ( message.type == ReceiveAutoReply ) + { + QString prefix = i18n("Prefix used for automatically generated auto-reply" + " messages when the contact is Away, contains contact's name", + "Auto reply from %1: " ).arg( sender->metaContact()->displayName() ); + messageMunged = prefix + message.message; + } + if ( message.type == GroupWise::ReceivedBroadcast ) + { + QString prefix = i18n("Prefix used for broadcast messages", + "Broadcast message from %1: " ).arg( sender->metaContact()->displayName() ); + messageMunged = prefix + message.message; + } + if ( message.type == GroupWise::ReceivedSystemBroadcast ) + { + QString prefix = i18n("Prefix used for system broadcast messages", + "System Broadcast message from %1: " ).arg( sender->metaContact()->displayName() ); + messageMunged = prefix + message.message; + } + + kdDebug(GROUPWISE_DEBUG_GLOBAL) << k_funcinfo << " message before KopeteMessage and appending: " << messageMunged << endl; + Kopete::Message * newMessage = + new Kopete::Message( message.timeStamp, sender, contactList, messageMunged, + Kopete::Message::Inbound, + ( message.type == ReceiveAutoReply ) ? Kopete::Message::PlainText : Kopete::Message::RichText ); + Q_ASSERT( sess ); + sess->appendMessage( *newMessage ); + kdDebug(GROUPWISE_DEBUG_GLOBAL) << "message from KopeteMessage: plainbody: " << newMessage->plainBody() << " parsedbody: " << newMessage->parsedBody() << endl; + delete newMessage; +} + +void GroupWiseAccount::receiveFolder( const FolderItem & folder ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo + << " objectId: " << folder.id + << " sequence: " << folder.sequence + << " parentId: " << folder.parentId + << " displayName: " << folder.name << endl; + if ( folder.parentId != 0 ) + { + kdWarning( GROUPWISE_DEBUG_GLOBAL ) << " - received a nested folder. These were not supported in GroupWise or Kopete as of Sept 2004, aborting! (parentId = " << folder.parentId << ")" << endl; + return; + } + + GWFolder * fld = m_serverListModel->addFolder( folder.id, folder.sequence, folder.name ); + Q_ASSERT( fld ); + + // either find a local group and record these details there, or create a new group to suit + Kopete::Group * found = 0; + QPtrList groupList = Kopete::ContactList::self()->groups(); + for ( Kopete::Group *grp = groupList.first(); grp; grp = groupList.next() ) + { + // see if there is already a local group that matches this group + QString groupId = grp->pluginData( protocol(), accountId() + " objectId" ); + if ( groupId.isEmpty() ) + if ( folder.name == grp->displayName() ) // no match on id, match on display name instead + { + grp->setPluginData( protocol(), accountId() + " objectId", QString::number( folder.id ) ); + found = grp; + break; + } + if ( folder.id == (unsigned int)groupId.toInt() ) + { + // was it renamed locally while we were offline? + if ( grp->displayName() != folder.name ) + { + slotKopeteGroupRenamed( grp ); + grp->setPluginData( protocol(), accountId() + " serverDisplayName", grp->displayName() ); + fld->displayName = grp->displayName(); + } + + found = grp; + break; + } + } + + if ( !found ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - not found locally, creating Kopete::Group" << endl; + Kopete::Group * grp = new Kopete::Group( folder.name ); + grp->setPluginData( protocol(), accountId() + " serverDisplayName", folder.name ); + grp->setPluginData( protocol(), accountId() + " objectId", QString::number( folder.id ) ); + Kopete::ContactList::self()->addGroup( grp ); + } +} + +void GroupWiseAccount::receiveContact( const ContactItem & contact ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo + << " objectId: " << contact.id + << ", sequence: " << contact.sequence + << ", parentId: " << contact.parentId + << ", dn: " << contact.dn + << ", displayName: " << contact.displayName << endl; + //kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "\n dotted notation is '" << protocol()->dnToDotted( contact.dn ) << "'\n" <addContactInstance( contact.id, contact.parentId, contact.sequence, contact.displayName, contact.dn ); + Q_ASSERT( gwInst ); + + GroupWiseContact * c = contactForDN( contact.dn ); + // this contact is new to us, create him on the server + if ( !c ) + { + Kopete::MetaContact *metaContact = new Kopete::MetaContact(); + metaContact->setDisplayName( contact.displayName ); + c = new GroupWiseContact( this, contact.dn, metaContact, contact.id, contact.parentId, contact.sequence ); + Kopete::ContactList::self()->addMetaContact( metaContact ); + } + // add the metacontact to the ContactItem's group, if not there aleady + if ( contact.parentId == 0 ) + c->metaContact()->addToGroup( Kopete::Group::topLevel() ); + else + { + // check the metacontact is in the group this listing-of-the-contact is in... + GWFolder * folder = m_serverListModel->findFolderById( contact.parentId ); + if ( !folder ) // inconsistent + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - ERROR - contact's folder doesn't exist on server" << endl; + DeleteItemTask * dit = new DeleteItemTask( client()->rootTask() ); + dit->item( contact.parentId, contact.id ); +// QObject::connect( dit, SIGNAL( gotContactDeleted( const ContactItem & ) ), SLOT( receiveContactDeleted( const ContactItem & ) ) ); + dit->go( true ); + return; + } + Kopete::Group *grp = Kopete::ContactList::self()->findGroup( folder->displayName ); + // grp should exist, because we receive the folders from the server before the contacts + if ( grp ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - making sure MC is in group " << grp->displayName() << endl; + m_dontSync = true; + c->metaContact()->addToGroup( grp ); //addToGroup() is safe to call if already a member + m_dontSync = false; + } + } + + c->setNickName( contact.displayName ); + //m_serverListModel->dump(); +} + +void GroupWiseAccount::receiveAccountDetails( const ContactDetails & details ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo + << "Auth attribute: " << details.authAttribute + << ", Away message: " << details.awayMessage + << ", CN" << details.cn + << ", DN" << details.dn + << ", fullName" << details.fullName + << ", surname" << details.surname + << ", givenname" << details.givenName + << ", status" << details.status + << endl; + if ( details.cn.lower() == accountId().lower().section('@', 0, 0) ) // incase user set account ID foo@novell.com + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " - got our details in contact list, updating them" << endl; + GroupWiseContact * detailsOwner= static_cast( myself() ); + detailsOwner->updateDetails( details ); + //detailsOwner->setProperty( Kopete::Global::Properties::self()->nickName(), details.fullName ); + + // Very important, without knowing our DN we can't do much else + Q_ASSERT( !details.dn.isEmpty() ); + m_client->setUserDN( details.dn ); + return; + } + else + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " - passed someone else's details in contact list!" << endl; + } +} + +void GroupWiseAccount::receiveContactUserDetails( const ContactDetails & details ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo + << "Auth attribute: " << details.authAttribute + << ", Away message: " << details.awayMessage + << ", CN" << details.cn + << ", DN" << details.dn + << ", fullName" << details.fullName + << ", surname" << details.surname + << ", givenname" << details.givenName + << ", status" << details.status + << endl; + // HACK: lowercased DN + if ( !details.dn.isNull() ) + { + // are the details for someone in our contact list? + GroupWiseContact * detailsOwner = contactForDN( details.dn ); + + if( detailsOwner ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - updating details for " << details.dn << endl; + detailsOwner->updateDetails( details ); + } + else + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - got details for " << details.dn << ", but they aren't in our contact list!" << endl; + } + } +} + +GroupWiseContact * GroupWiseAccount::createTemporaryContact( const QString & dn ) +{ + ContactDetails details = client()->userDetailsManager()->details( dn ); + GroupWiseContact * c = static_cast( contacts()[ details.dn.lower() ] ); + if ( !c && details.dn != accountId() ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "Got a temporary contact DN: " << details.dn << endl; + // the client is telling us about a temporary contact we need to know about so add them + Kopete::MetaContact *metaContact = new Kopete::MetaContact (); + metaContact->setTemporary (true); + QString displayName = details.fullName; + if ( displayName.isEmpty() ) + displayName = details.givenName + " " + details.surname; + + metaContact->setDisplayName( displayName ); + c = new GroupWiseContact( this, details.dn, metaContact, 0, 0, 0 ); + c->updateDetails( details ); + c->setProperty( Kopete::Global::Properties::self()->nickName(), protocol()->dnToDotted( details.dn ) ); + Kopete::ContactList::self()->addMetaContact( metaContact ); + // the contact details probably don't contain status - but we can ask for it + if ( details.status == GroupWise::Invalid && isConnected() ) + m_client->requestStatus( details.dn ); + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "Notified of existing temporary contact DN: " << details.dn << endl; + return c; +} + +void GroupWiseAccount::receiveStatus( const QString & contactId, Q_UINT16 status, const QString &awayMessage ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "got status for: " << contactId << ", status: " << status << ", away message: " << awayMessage << endl; + GroupWiseContact * c = contactForDN( contactId ); + if ( c ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - their KOS is: " << protocol()->gwStatusToKOS( status ).description() << endl; + Kopete::OnlineStatus kos = protocol()->gwStatusToKOS( status ); + c->setOnlineStatus( kos ); + c->setProperty( protocol()->propAwayMessage, awayMessage ); + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " couldn't find " << contactId << endl; +} + +void GroupWiseAccount::changeOurStatus( GroupWise::Status status, const QString & awayMessage, const QString & autoReply ) +{ + if ( status == GroupWise::Offline ) + myself()->setOnlineStatus( protocol()->groupwiseAppearOffline ); + else + myself()->setOnlineStatus( protocol()->gwStatusToKOS( status ) ); + myself()->setProperty( protocol()->propAwayMessage, awayMessage ); + myself()->setProperty( protocol()->propAutoReply, autoReply ); +} + +void GroupWiseAccount::sendMessage( const GroupWise::ConferenceGuid &guid, const Kopete::Message & message ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + // make an outgoing message + if ( isConnected() ) + { + GroupWise::OutgoingMessage outMsg; + outMsg.guid = guid; + outMsg.message = message.plainBody(); + outMsg.rtfMessage = protocol()->rtfizeText( message.plainBody() ); + // make a list of DNs to send to + QStringList addresseeDNs; + Kopete::ContactPtrList addressees = message.to(); + for ( Kopete::Contact * contact = addressees.first(); contact; contact = addressees.next() ) + addresseeDNs.append( static_cast< GroupWiseContact* >( contact )->dn() ); + // send the message + m_client->sendMessage( addresseeDNs, outMsg ); + } +} + +bool GroupWiseAccount::createContact( const QString& contactId, Kopete::MetaContact* parentContact ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "contactId: " << contactId << endl; + + // first find all the groups that this contact is a member of + // record, in a folderitem, their display names and groupwise object id + // Set object id to 0 if not found - they do not exist on the server + bool topLevel = false; + QValueList< FolderItem > folders; + Kopete::GroupList groupList = parentContact->groups(); + for ( Kopete::Group *group = groupList.first(); group; group = groupList.next() ) + { + if ( group->type() == Kopete::Group::TopLevel ) // no need to create it on the server + { + topLevel = true; + continue; + } + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "looking up: " << group->displayName() << endl; + GWFolder * fld = m_serverListModel->findFolderByName( group->displayName() ); + FolderItem fi; + if ( fld ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << fld->displayName << endl; + //FIXME - get rid of FolderItem & co + fi.parentId = ::qt_cast( fld->parent() )->id; + fi.id = fld->id; + fi.name = fld->displayName; + } + else + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "folder: " << group->displayName() << + "not found in server list model." << endl; + fi.parentId = 0; + fi.id = 0; + fi.name = group->displayName(); + } + folders.append( fi ); + + } + + // find out the sequence number to use for any new folders + int highestFreeSequence = m_serverListModel->maxSequenceNumber() + 1; + + // send this list along with the contact details to the server + // CreateContactTask will create the missing folders on the server + // and then add the contact to each one + // finally it will signal finished(), and we can query it for the details + // we gave it earlier and make sure the contact was successfully created. + // + // Since ToMetaContact expects synchronous contact creation + // we have to create the contact optimistically. + GroupWiseContact * gc = new GroupWiseContact( this, contactId, parentContact, 0, 0, 0 ); + ContactDetails dt = client()->userDetailsManager()->details( contactId ); + QString displayAs; + if ( dt.fullName.isEmpty() ) + displayAs = dt.givenName + " " + dt.surname; + else + displayAs = dt.fullName; + + gc->setNickName( displayAs ); + // If the CreateContactTask finishes with an error, we have to + // delete the contact we just created, in receiveContactCreated :/ + + if ( folders.isEmpty() && !topLevel ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "aborting because we didn't find any groups to add them to" << endl; + return false; + } + + // get the contact's full name to use as the display name of the created contact + CreateContactTask * cct = new CreateContactTask( client()->rootTask() ); + cct->contactFromUserId( contactId, parentContact->displayName(), highestFreeSequence, folders, topLevel ); + QObject::connect( cct, SIGNAL( finished() ), SLOT( receiveContactCreated() ) ); + cct->go( true ); + return true; +} + +void GroupWiseAccount::receiveContactCreated() +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + m_serverListModel->dump(); + + CreateContactTask * cct = ( CreateContactTask * )sender(); + if ( cct->success() ) + { + if ( client()->userDetailsManager()->known( cct->dn() ) ) + { + ContactDetails dt = client()->userDetailsManager()->details( cct->dn() ); + GroupWiseContact * c = contactForDN( cct->dn() ); + c->setOnlineStatus( protocol()->gwStatusToKOS( dt.status ) ); + c->setNickName( dt.fullName ); + c->updateDetails( dt ); + } + else + { + client()->requestDetails( QStringList( cct->dn() ) ); + client()->requestStatus( cct->dn() ); + } + } + else + { + // delete the contact created optimistically using the supplied userid; + Kopete::Contact * c = contacts()[ protocol()->dnToDotted( cct->userId() ) ]; + if ( c ) + { + // if the contact creation failed because it already exists on the server, don't delete it + if (!cct->statusCode() == NMERR_DUPLICATE_CONTACT ) + { + if ( c->metaContact()->contacts().count() == 1 ) + Kopete::ContactList::self()->removeMetaContact( c->metaContact() ); + else + delete c; + } + } + + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget (), KMessageBox::Error, + i18n ("The contact %1 could not be added to the contact list, with error message: %2"). + arg(cct->userId() ).arg( cct->statusString() ), + i18n ("Error Adding Contact") ); + } +} + +void GroupWiseAccount::deleteContact( GroupWiseContact * contact ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + contact->setDeleting( true ); + if ( isConnected() ) + { + // remove all the instances of this contact from the server's contact list + GWContactInstanceList instances = m_serverListModel->instancesWithDn( contact->dn() ); + GWContactInstanceList::iterator it = instances.begin(); + for ( ; it != instances.end(); ++it ) + { + DeleteItemTask * dit = new DeleteItemTask( client()->rootTask() ); + dit->item( ::qt_cast( (*it)->parent() )->id, (*it)->id ); + QObject::connect( dit, SIGNAL( gotContactDeleted( const ContactItem & ) ), SLOT( receiveContactDeleted( const ContactItem & ) ) ); + dit->go( true ); + } + } +} + +void GroupWiseAccount::receiveContactDeleted( const ContactItem & instance ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + // an instance of this contact was deleted on the server. + // Remove it from the model of the server side list, + // and if there are no other instances of this contact, delete the contact + m_serverListModel->removeInstanceById( instance.id ); + m_serverListModel->dump(); + + GWContactInstanceList instances = m_serverListModel->instancesWithDn( instance.dn ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - " << instance.dn << " now has " << instances.count() << " instances remaining." << endl; + GroupWiseContact * c = contactForDN( instance.dn ); + if ( c && instances.count() == 0 && c->deleting() ) + { + c->deleteLater(); + } +} + + +void GroupWiseAccount::slotConnectedElsewhere() +{ + KPassivePopup::message( i18n ("Signed in as %1 Elsewhere").arg( accountId() ), + i18n( "The parameter is the user's own account id for this protocol", "You have been disconnected from GroupWise Messenger because you signed in as %1 elsewhere" ).arg( accountId() ) , Kopete::UI::Global::mainWidget() ); + disconnect(); +} + +void GroupWiseAccount::receiveInvitation( const ConferenceEvent & event ) +{ + // ask the user if they want to accept the invitation or not + GroupWiseContact * contactFrom = contactForDN( event.user ); + if ( !contactFrom ) + contactFrom = createTemporaryContact( event.user ); + if ( configGroup()->readEntry( "AlwaysAcceptInvitations" ) == "true" ) + { + client()->joinConference( event.guid ); + } + else + { + ReceiveInvitationDialog * dlg = new ReceiveInvitationDialog( this, event, + Kopete::UI::Global::mainWidget(), "invitedialog" ); + dlg->show(); + } + +} + +void GroupWiseAccount::receiveConferenceJoin( const GroupWise::ConferenceGuid & guid, const QStringList & participants, const QStringList & invitees ) +{ + // get a new GWMM + Kopete::ContactPtrList others; + GroupWiseChatSession * sess = chatSession( others, guid, Kopete::Contact::CanCreate); + // find each contact and add them to the GWMM, and tell them they are in the conference + for ( QValueList::ConstIterator it = participants.begin(); it != participants.end(); ++it ) + { + //kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " adding participant " << *it << endl; + GroupWiseContact * c = contactForDN( *it ); + if ( !c ) + c = createTemporaryContact( *it ); + sess->joined( c ); + } + // add each invitee too + for ( QValueList::ConstIterator it = invitees.begin(); it != invitees.end(); ++it ) + { + //kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " adding invitee " << *it << endl; + GroupWiseContact * c = contactForDN( *it ); + if ( !c ) + c = createTemporaryContact( *it ); + sess->addInvitee( c ); + } + sess->view( true )->raise( false ); +} + +void GroupWiseAccount::receiveConferenceJoinNotify( const ConferenceEvent & event ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + GroupWiseChatSession * sess = findChatSessionByGuid( event.guid ); + if ( sess ) + { + GroupWiseContact * c = contactForDN( event.user ); + if ( !c ) + c = createTemporaryContact( event.user ); + sess->joined( c ); + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " couldn't find a GWCS for conference: " << event.guid << endl; +} + +void GroupWiseAccount::receiveConferenceLeft( const ConferenceEvent & event ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + GroupWiseChatSession * sess = findChatSessionByGuid( event.guid ); + if ( sess ) + { + GroupWiseContact * c = contactForDN( event.user ); + if ( c ) + { + sess->left( c ); + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " couldn't find a contact for DN: " << event.user << endl; + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " couldn't find a GWCS for conference: " << event.guid << endl; + +} + +void GroupWiseAccount::receiveInviteDeclined( const ConferenceEvent & event ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + GroupWiseChatSession * sess = findChatSessionByGuid( event.guid ); + if ( sess ) + { + GroupWiseContact * c = contactForDN( event.user ); + if ( c ) + sess->inviteDeclined( c ); + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " couldn't find a GWCS for conference: " << event.guid << endl; +} + +void GroupWiseAccount::receiveInviteNotify( const ConferenceEvent & event ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + GroupWiseChatSession * sess = findChatSessionByGuid( event.guid ); + if ( sess ) + { + GroupWiseContact * c = contactForDN( event.user ); + if ( !c ) + c = createTemporaryContact( event.user ); + + sess->addInvitee( c ); + Kopete::Message declined = Kopete::Message( myself(), sess->members(), i18n("%1 has been invited to join this conversation.").arg( c->metaContact()->displayName() ), Kopete::Message::Internal, Kopete::Message::PlainText ); + sess->appendMessage( declined ); + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " couldn't find a GWCS for conference: " << event.guid << endl; +} + +void GroupWiseAccount::slotLeavingConference( GroupWiseChatSession * sess ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "unregistering message manager:" << sess->guid()<< endl; + if( isConnected () ) + m_client->leaveConference( sess->guid() ); + m_chatSessions.remove( sess ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "m_chatSessions now contains:" << m_chatSessions.count() << " managers" << endl; + Kopete::ContactPtrList members = sess->members(); + for ( Kopete::Contact * contact = members.first(); contact; contact = members.next() ) + { + static_cast< GroupWiseContact * >( contact )->setMessageReceivedOffline( false ); + } +} + +void GroupWiseAccount::slotSetAutoReply() +{ + bool ok; + QRegExp rx( ".*" ); + QRegExpValidator validator( rx, this ); + QString newAutoReply = KInputDialog::getText( i18n( "Enter Auto-Reply Message" ), + i18n( "Please enter an Auto-Reply message that will be shown to users who message you while Away or Busy" ), configGroup()->readEntry( "AutoReply" ), + &ok, Kopete::UI::Global::mainWidget(), "autoreplymessagedlg", &validator ); + if ( ok ) + configGroup()->writeEntry( "AutoReply", newAutoReply ); +} + +void GroupWiseAccount::slotTestRTFize() +{ +/* bool ok; + const QString query = QString::fromLatin1("Enter a string to rtfize:"); + QString testText = KLineEditDlg::getText( query, QString::null, &ok, Kopete::UI::Global::mainWidget() ); + if ( ok ) + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "Converted text is: '" << protocol()->rtfizeText( testText ) << "'" << endl;*/ + +// bool ok; +// const QString query = i18n("Enter a contactId:"); +// QString testText = KInputDialog::getText( query, i18n("This is a test dialog and will not be in the final product!" ), QString::null, &ok, Kopete::UI::Global::mainWidget() ); +// if ( !ok ) +// return; +// kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "Trying to add contact: '" << protocol()->rtfizeText( testText ) << "'" << endl; +// Kopete::MetaContact *metaContact = new Kopete::MetaContact (); +// metaContact->setDisplayName( "Test Add MC" ); +// metaContact->setTemporary (true); +// createContact( testText, "Test Add Contact", metaContact ); +} + +void GroupWiseAccount::slotPrivacy() +{ + new GroupWisePrivacyDialog( this, Kopete::UI::Global::mainWidget(), "gwprivacydialog" ); +} + +void GroupWiseAccount::slotJoinChatRoom() +{ + new GroupWiseChatSearchDialog( this, Kopete::UI::Global::mainWidget(), "gwjoinchatdialog" ); +} + +bool GroupWiseAccount::isContactBlocked( const QString & dn ) +{ + if ( isConnected() ) + return client()->privacyManager()->isBlocked( dn ); + else + return false; +} + +void GroupWiseAccount::dumpManagers() +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " for: " << accountId() + << " containing: " << m_chatSessions.count() << " managers " << endl; + QValueList::ConstIterator it; + for ( it = m_chatSessions.begin() ; it != m_chatSessions.end(); ++it ) + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "guid: " << (*it)->guid() << endl; +} + +bool GroupWiseAccount::dontSync() +{ + return m_dontSync; +} + +void GroupWiseAccount::syncContact( GroupWiseContact * contact ) +{ + if ( dontSync() ) + return; + + if ( contact != myself() ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + if ( !isConnected() ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "not connected, can't sync display name or group membership" << endl; + return; + } + + // if this is a temporary contact, don't bother + if ( contact->metaContact()->isTemporary() ) + return; + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " = CONTACT '" << contact->nickName() << "' IS IN " << contact->metaContact()->groups().count() << " MC GROUPS, AND HAS " << m_serverListModel->instancesWithDn( contact->dn() ).count() << " CONTACT LIST INSTANCES." << endl; + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " = LOOKING FOR NOOP GROUP MEMBERSHIPS" << endl; + // 1) Seek matches between CLIs and MCGs and remove from the lists without taking any action. match on objectid, parentid + // 2) Each remaining unmatched pair is a move, initiate and remove - need to take care to always use greatest unused sequence number - if we have to set the sequence number to the following sequence number within the folder, we may have a problem where after the first move, we have to wait for the state of the CLIs to be updated pending the completion of the first move - this would be difficult to cope with, because our current lists would be out of date, or we'd have to restart the sync - assuming the first move created a new matched CLI-MCG pair, we could do that with little cost. + // 3) Any remaining entries in MCG list are adds, carry out + // 4) Any remaining entries in CLI list are removes, carry out + + // start by discovering the next free group sequence number in case we have to add any groups + int nextFreeSequence = m_serverListModel->maxSequenceNumber() + 1; + // 1) + // make a list of all the groups the metacontact is in + QPtrList groupList = contact->metaContact()->groups(); + // make a list of all the groups this contact is in, according to the server model + GWContactInstanceList instances = m_serverListModel->instancesWithDn( contact->dn() ); + + // seek corresponding pairs in both lists and remove + // ( for each group ) + QPtrListIterator< Kopete::Group > grpIt( groupList ); + while ( *grpIt ) + { + QPtrListIterator< Kopete::Group > candidateGrp( groupList ); + candidateGrp = grpIt; + ++grpIt; + + GWContactInstanceList::Iterator instIt = instances.begin(); + const GWContactInstanceList::Iterator instEnd = instances.end(); + // ( see if a contactlist instance matches the group) + while ( instIt != instEnd ) + { + GWContactInstanceList::Iterator candidateInst = instIt; + ++instIt; + GWFolder * folder = ::qt_cast( ( *candidateInst )->parent() ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - Looking for a match, MC grp '" + << ( *candidateGrp )->displayName() + << "', GWFolder '" << folder->displayName << "', objectId is " << folder->id << endl; + + if ( ( folder->id == 0 && ( ( *candidateGrp ) == Kopete::Group::topLevel() ) ) + || ( ( *candidateGrp )->displayName() == folder->displayName ) ) + { + //this pair matches, we can remove its members from both lists ) + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - match! removing both entries" << endl; + instances.remove( candidateInst ); + groupList.remove( *candidateGrp ); + break; + } + } + } + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " = LOOKING FOR UNMATCHED PAIRS => GROUP MOVES" << endl; + grpIt.toFirst(); + // ( take the first pair and carry out a move ) + while ( *grpIt && !instances.isEmpty() ) + { + QPtrListIterator< Kopete::Group > candidateGrp( groupList ); + candidateGrp = grpIt; + ++grpIt; + GWContactInstanceList::Iterator instIt = instances.begin(); + GWFolder * sourceFolder =::qt_cast( ( *instIt)->parent() ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - moving contact instance from group '" << sourceFolder->displayName << "' to group '" << ( *candidateGrp )->displayName() << "'" << endl; + + // create contactItem parameter + ContactItem instance; + instance.id = ( *instIt )->id; + instance.parentId = sourceFolder->id; + instance.sequence = ( *instIt )->sequence; + instance.dn = ( *instIt )->dn; + instance.displayName = contact->nickName(); + // identify the destination folder + GWFolder * destinationFolder = m_serverListModel->findFolderByName( ( ( *candidateGrp )->displayName() ) ); + if ( destinationFolder ) // folder already exists on the server + { + MoveContactTask * mit = new MoveContactTask( client()->rootTask() ); + mit->moveContact( instance, destinationFolder->id ); + QObject::connect( mit, SIGNAL( gotContactDeleted( const ContactItem & ) ), SLOT( receiveContactDeleted( const ContactItem & ) ) ); + mit->go(); + } + else if ( *candidateGrp == Kopete::Group::topLevel() ) + { + MoveContactTask * mit = new MoveContactTask( client()->rootTask() ); + mit->moveContact( instance, 0 ); + QObject::connect( mit, SIGNAL( gotContactDeleted( const ContactItem & ) ), SLOT( receiveContactDeleted( const ContactItem & ) ) ); + mit->go(); + } + else + { + MoveContactTask * mit = new MoveContactTask( client()->rootTask() ); + QObject::connect( mit, SIGNAL( gotContactDeleted( const ContactItem & ) ), + SLOT( receiveContactDeleted( const ContactItem & ) ) ); + // discover the next free sequence number and add the group using that + mit->moveContactToNewFolder( instance, nextFreeSequence++, + ( *candidateGrp )->displayName() ); + mit->go( true ); + } + groupList.remove( candidateGrp ); + instances.remove( instIt ); + } + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " = LOOKING FOR ADDS" << endl; + grpIt.toFirst(); + while ( *grpIt ) + { + QPtrListIterator< Kopete::Group > candidateGrp( groupList ); + candidateGrp = grpIt; + ++grpIt; + GWFolder * destinationFolder = m_serverListModel->findFolderByName( ( ( *candidateGrp )->displayName() ) ); + CreateContactInstanceTask * ccit = new CreateContactInstanceTask( client()->rootTask() ); + + contact->setNickName( contact->metaContact()->displayName() ); + // does this group exist on the server? Create the contact appropriately + if ( destinationFolder ) + { + int parentId = destinationFolder->id; + ccit->contactFromUserId( contact->dn(), contact->metaContact()->displayName(), parentId ); + } + else + { + if ( ( *candidateGrp ) == Kopete::Group::topLevel() ) + ccit->contactFromUserId( contact->dn(), contact->metaContact()->displayName(), + m_serverListModel->rootFolder->id ); + else + // discover the next free sequence number and add the group using that + ccit->contactFromUserIdAndFolder( contact->dn(), contact->metaContact()->displayName(), + nextFreeSequence++, ( *candidateGrp )->displayName() ); + } + ccit->go( true ); + groupList.remove( candidateGrp ); + } + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " = LOOKING FOR REMOVES" << endl; + GWContactInstanceList::Iterator instIt = instances.begin(); + const GWContactInstanceList::Iterator instEnd = instances.end(); + // ( remove each remaining contactlist instance, because it doesn't exist locally any more ) + while ( instIt != instEnd ) + { + GWContactInstanceList::Iterator candidateInst = instIt; + ++instIt; + GWFolder * folder =::qt_cast( ( *candidateInst )->parent() ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - remove contact instance '"<< ( *candidateInst )->id << "' in group '" << folder->displayName << "'" << endl; + + DeleteItemTask * dit = new DeleteItemTask( client()->rootTask() ); + dit->item( folder->id, (*candidateInst)->id ); + QObject::connect( dit, SIGNAL( gotContactDeleted( const ContactItem & ) ), SLOT( receiveContactDeleted( const ContactItem & ) ) ); + dit->go( true ); + + instances.remove( candidateInst ); + } + + // start an UpdateItem + if ( contact->metaContact()->displayName() != contact->nickName() ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " updating the contact's display name to the metacontact's: " << contact->metaContact()->displayName() << endl; + // form a list of the contact's groups + GWContactInstanceList instances = m_serverListModel->instancesWithDn( contact->dn() ); + GWContactInstanceList::Iterator it = instances.begin(); + const GWContactInstanceList::Iterator end = instances.end(); + for ( ; it != end; ++it ) + { + QValueList< ContactItem > instancesToChange; + ContactItem instance; + instance.id = (*it)->id; + instance.parentId = ::qt_cast( (*it)->parent() )->id; + instance.sequence = (*it)->sequence; + instance.dn = contact->dn(); + instance.displayName = contact->nickName(); + instancesToChange.append( instance ); + + UpdateContactTask * uct = new UpdateContactTask( client()->rootTask() ); + uct->renameContact( contact->metaContact()->displayName(), instancesToChange ); + QObject::connect ( uct, SIGNAL( finished() ), contact, SLOT( renamedOnServer() ) ); + uct->go( true ); + } + } + } +} + +#include "gwaccount.moc" diff --git a/kopete/protocols/groupwise/gwaccount.h b/kopete/protocols/groupwise/gwaccount.h new file mode 100644 index 00000000..2e8f8348 --- /dev/null +++ b/kopete/protocols/groupwise/gwaccount.h @@ -0,0 +1,360 @@ +/* + gwaccount.h - Kopete GroupWise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Testbed + Copyright (c) 2003 by Will Stephenson + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#ifndef GW_ACCOUNT_H +#define GW_ACCOUNT_H + +#include + +#include + +#include "gwerror.h" + +#include + +class KActionMenu; + +namespace Kopete { + class Contact; + class Group; + class MetaContact; +} + +class GroupWiseContact; +class GroupWiseChatSession; +class GroupWiseProtocol; +class KNetworkConnector; +namespace QCA { + class TLS; +} +class QCATLSHandler; +class ClientStream; +class Client; +class GWContactList; + +using namespace GroupWise; + +/** + * This represents an account on a Novell GroupWise Messenger Server + */ +class GroupWiseAccount : public Kopete::ManagedConnectionAccount +{ + Q_OBJECT +public: + GroupWiseAccount( GroupWiseProtocol *parent, const QString& accountID, const char *name = 0 ); + ~GroupWiseAccount(); + + /** + * Construct the context menu used for the status bar icon + */ + virtual KActionMenu* actionMenu(); + + // DEBUG ONLY + void dumpManagers(); + // DEBUG ONLY + /** + * Creates a protocol specific Kopete::Contact subclass and adds it to the supplied + * Kopete::MetaContact + */ + virtual bool createContact(const QString& contactId, Kopete::MetaContact* parentContact); + /** + * Delete a contact on the server + */ + void deleteContact( GroupWiseContact * contact ); + /** + * Called when Kopete is set globally away + */ + virtual void setAway(bool away, const QString& reason); + /** + * Utility access to the port given by the user + */ + int port() const; + /** + * Utility access to the server given by the user + */ + const QString server() const; + /** + * Utility access to our protocol + */ + GroupWiseProtocol * protocol() const; + /** + * Utility access to the @ref Client which is the main interface exposed by libgroupwise. + * Most protocol actions are carried out using the client's member functions but the possibility exists + * to start Tasks directly on the client and respond directly to their signals. + */ + Client * client() const; + /** + * Utility to create or access a message manager instance for a given GUID and set of contacts + */ + GroupWiseChatSession * chatSession( Kopete::ContactPtrList others, const ConferenceGuid & guid, Kopete::Contact::CanCreateFlags canCreate ); + /** + * Look up a contact given a DN + * Returns 0 if none found + */ + GroupWiseContact * contactForDN( const QString & dn ); + /** + * Create a conference (start a chat) on the server + */ + void createConference( const int clientId, const QStringList& invitees ); + + /** + * Send a message + */ + void sendMessage( const ConferenceGuid & guid, const Kopete::Message & message ); + + /** + * Invite someone to join a conference + */ + void sendInvitation( const ConferenceGuid & guid, const QString & dn, const QString & message ); + + /** + * Check a contact's blocking status + * Only works when connected - otherwise always returns false + */ + bool isContactBlocked( const QString & m_dn ); + /** + * Set up a temporary contact (not on our contact list but is messaging us or involved in a conversation that we have been invited to. + */ + GroupWiseContact * createTemporaryContact( const QString & dn ); + + /** + * Check whether sync is not currently needed + */ + bool dontSync(); + + void syncContact( GroupWiseContact * contact ); + +public slots: + + void slotTestRTFize(); + + /* Connects to the server. */ + void performConnectWithPassword ( const QString &password ); + + /* Disconnects from the server. */ + virtual void disconnect(); + virtual void disconnect( Kopete::Account::DisconnectReason reason ); + + /** Set the online status for the account. Reimplemented from Kopete::Account */ + void setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason = QString::null); +signals: + void conferenceCreated( const int mmId, const GroupWise::ConferenceGuid & guid ); + void conferenceCreationFailed( const int mmId, const int statusCode ); + void contactTyping( const ConferenceEvent & ); + void contactNotTyping( const ConferenceEvent & ); + void privacyChanged( const QString & dn, bool allowed ); + + +protected slots: + void slotMessageSendingFailed(); + /** + * Set an auto reply message for use when the account is away + * TODO: Extend Kopete::AwayAction so you can set multiple ones there. + */ + void slotSetAutoReply(); + /** + * Manage the user's privacy settings + */ + void slotPrivacy(); + + /** + * Show a dialog to join a chatroom without first adding it to the contact list + */ + void slotJoinChatRoom(); + + /** + * Slot informing GroupWise when a group is renamed + */ + void slotKopeteGroupRenamed( Kopete::Group * ); + /** + * Slot informing GroupWise when a group is removed + */ + void slotKopeteGroupRemoved( Kopete::Group * ); + + // SERVER SIDE CONTACT LIST PROCESSING + /** + * Called when we receive a FOLDER from the server side contact list + * Adds to the Kopete contact list if not already present. + */ + void receiveFolder( const FolderItem & folder ); + /** + * Called when we receive a CONTACT from the server side contact list + * Adds to a folder in the Kopete contact list. + */ + void receiveContact( const ContactItem & ); + /** + * Called when we receive a CONTACT'S METADATA (including initial status) from the server side contact list, + * or in response to an explicity query. This is necessary to handle some events from the server. + * These events are queued in the account until the data arrives and then we handle the event. + */ + void receiveContactUserDetails( const GroupWise::ContactDetails & ); + /** + * Called after we create a contact on the server + */ + void receiveContactCreated(); + /** + * Handles the response to deleting a contact on the server + */ + void receiveContactDeleted( const ContactItem & instance ); + + // SLOTS HANDLING PROTOCOL EVENTS + /** + * Received a message from the server. + * Find the conversation that this message belongs to, and display it there. + * @param event contains event type, sender, content, flags. Type is used to handle autoreplies, normal messages, and [system] broadcasts. + */ + void handleIncomingMessage( const ConferenceEvent & ); + /** + * A contact changed status + */ + void receiveStatus( const QString &, Q_UINT16, const QString & ); + /** + * Our status changed on the server + */ + void changeOurStatus( GroupWise::Status, const QString &, const QString & ); + /** + * Called when we've been disconnected for logging in as this user somewhere else + */ + void slotConnectedElsewhere(); + /** + * Called when we've logged in successfully + */ + void slotLoggedIn(); + /** + * Called when a login attempt failed + */ + void slotLoginFailed(); + /** + * We joined a conference having accepted an invitation, create a message manager + */ + void receiveConferenceJoin( const GroupWise::ConferenceGuid & guid, const QStringList & participants, const QStringList & invitees ); + /** + * Someone joined a conference, add them to the appropriate message manager + */ + void receiveConferenceJoinNotify( const ConferenceEvent & ); + /** + * Someone left a conference, remove them from the message manager + */ + void receiveConferenceLeft( const ConferenceEvent & ); + /** + * The user was invited to join a conference + */ + void receiveInvitation( const ConferenceEvent & ); + /** + * Notification that a third party was invited to join conference + */ + void receiveInviteNotify( const ConferenceEvent & ); + /** + * Notification that a third party declined an invitation + */ + void receiveInviteDeclined( const ConferenceEvent & ); + /** + * A conference was closed by the server because everyone has left or declined invitations + * Prevents any further messages to this conference + */ +// void closeConference(); + // SLOTS HANDLING NETWORK EVENTS + /** + * Update the local user's metadata + */ + void receiveAccountDetails( const GroupWise::ContactDetails & details ); + /** + * The TLS handshake has happened, check the result + */ + void slotTLSHandshaken(); + /** The connection is ready for a login */ + void slotTLSReady( int secLayerCode ); + /** + * Called when the clientstream is connected, debug only + */ + void slotCSConnected(); + /** + * Performs necessary actions when the client stream has been disconnected + */ + void slotCSDisconnected(); + void slotCSError( int error ); + void slotCSWarning( int warning ); + + // HOUSEKEEPING + /** + * We listen for the destroyed() signal and leave any conferences we + * might have been in, and remove it from our map. + */ + void slotLeavingConference( GroupWiseChatSession * ); + + /** Debug slots */ + void slotConnError(); + void slotConnConnected(); +protected: + /** + * Sends a status message to the server - called by the status specific slotGoAway etc + */ + //void setStatus( GroupWise::Status status, const QString & reason = QString::null ); + + int handleTLSWarning (int warning, QString server, QString accountId); + + GroupWiseChatSession * findChatSessionByGuid( const GroupWise::ConferenceGuid & guid ); + /** + * reconcile any changes to the contact list which happened offline + */ + void reconcileOfflineChanges(); + /** + * Memory management + */ + void cleanup(); +private: + // action menu and its actions + KActionMenu * m_actionMenu; + KAction * m_actionAutoReply; + KAction * m_actionManagePrivacy; + KAction * m_actionJoinChatRoom; + // Network code + KNetworkConnector * m_connector; + QCA::TLS * m_QCATLS; + QCATLSHandler * m_tlsHandler; + ClientStream * m_clientStream; + // Client, entry point of libgroupwise + Client * m_client; + + QString m_initialReason; + QValueList m_chatSessions; + bool m_dontSync; + GWContactList * m_serverListModel; +}; + +/** + * @internal + * An action that selects an OnlineStatus and provides a status message, but not using Kopete::Away, because the status message relates only to this status. + */ +/*class OnlineStatusMessageAction : public KAction +{ + Q_OBJECT + public: + OnlineStatusMessageAction ( const Kopete::OnlineStatus& status, const QString &text, const QString &message, const QIconSet &pix, QObject *parent=0, const char *name=0); + signals: + void activated( const Kopete::OnlineStatus& status, const QString & ); + private slots: + void slotActivated(); + private: + Kopete::OnlineStatus m_status; + QString m_message; +}; +*/ +#endif diff --git a/kopete/protocols/groupwise/gwaddui.ui b/kopete/protocols/groupwise/gwaddui.ui new file mode 100644 index 00000000..97bdd3b4 --- /dev/null +++ b/kopete/protocols/groupwise/gwaddui.ui @@ -0,0 +1,113 @@ + +GroupWiseAddUI + + + GroupWiseAddUI + + + + 0 + 0 + 406 + 343 + + + + + unnamed + + + 0 + + + 0 + + + + layout2 + + + + unnamed + + + + textLabel1 + + + &Account name: + + + m_uniqueName + + + The account name of the account you would like to add. + + + The account name of the account you would like to add. + + + + + m_uniqueName + + + The account name of the account you would like to add. + + + The account name of the account you would like to add. + + + + + + + buttonGroup1 + + + Contact Type + + + + unnamed + + + + m_rbEcho + + + &Echo + + + true + + + Hey look! Only one option. Could you please make this a dropdown and add Null? + + + Hey look! Only one option. Could you please make this a dropdown and add Null? + + + + + + + spacer2 + + + Vertical + + + Expanding + + + + 20 + 100 + + + + + + + diff --git a/kopete/protocols/groupwise/gwbytestream.cpp b/kopete/protocols/groupwise/gwbytestream.cpp new file mode 100644 index 00000000..cd476070 --- /dev/null +++ b/kopete/protocols/groupwise/gwbytestream.cpp @@ -0,0 +1,156 @@ + +/*************************************************************************** + gwbytestream.cpp - Byte Stream using KNetwork sockets + ------------------- + begin : Wed Jul 7 2004 + copyright : (C) 2004 by Till Gerken + + Kopete (C) 2004 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include +#include +#include +#include + +#include "gwbytestream.h" +#include "gwerror.h" + +KNetworkByteStream::KNetworkByteStream ( QObject *parent, const char */*name*/ ) + : ByteStream ( parent ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Instantiating new KNetwork byte stream." << endl; + + // reset close tracking flag + mClosing = false; + + mSocket = new KNetwork::KBufferedSocket; + + // make sure we get a signal whenever there's data to be read + mSocket->enableRead ( true ); + + // connect signals and slots + QObject::connect ( mSocket, SIGNAL ( gotError ( int ) ), this, SLOT ( slotError ( int ) ) ); + QObject::connect ( mSocket, SIGNAL ( connected ( const KResolverEntry& ) ), this, SLOT ( slotConnected () ) ); + QObject::connect ( mSocket, SIGNAL ( closed () ), this, SLOT ( slotConnectionClosed () ) ); + QObject::connect ( mSocket, SIGNAL ( readyRead () ), this, SLOT ( slotReadyRead () ) ); + QObject::connect ( mSocket, SIGNAL ( bytesWritten ( int ) ), this, SLOT ( slotBytesWritten ( int ) ) ); + +} + +bool KNetworkByteStream::connect ( QString host, QString service ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Connecting to " << host << ", service " << service << endl; + + return socket()->connect ( host, service ); + +} + +bool KNetworkByteStream::isOpen () const +{ + + // determine if socket is open + return socket()->isOpen (); + +} + +void KNetworkByteStream::close () +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Closing stream." << endl; + + // close the socket and set flag that we are closing it ourselves + mClosing = true; + socket()->close(); + +} + +int KNetworkByteStream::tryWrite () +{ + + // send all data from the buffers to the socket + QByteArray writeData = takeWrite(); + socket()->writeBlock ( writeData.data (), writeData.size () ); + + return writeData.size (); + +} + +KNetwork::KBufferedSocket *KNetworkByteStream::socket () const +{ + + return mSocket; + +} + +KNetworkByteStream::~KNetworkByteStream () +{ + + delete mSocket; + +} + +void KNetworkByteStream::slotConnected () +{ + + emit connected (); + +} + +void KNetworkByteStream::slotConnectionClosed () +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Socket has been closed." << endl; + + // depending on who closed the socket, emit different signals + if ( mClosing ) + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << "..by ourselves!" << endl; + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "socket error is \"" << socket()->errorString( socket()->error() ) << "\"" << endl; + emit connectionClosed (); + } + else + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << "..by the other end" << endl; + emit delayedCloseFinished (); + } + +} + +void KNetworkByteStream::slotReadyRead () +{ + + // stuff all available data into our buffers + QByteArray readBuffer ( socket()->bytesAvailable () ); + + socket()->readBlock ( readBuffer.data (), readBuffer.size () ); + + appendRead ( readBuffer ); + + emit readyRead (); + +} + +void KNetworkByteStream::slotBytesWritten ( int bytes ) +{ + + emit bytesWritten ( bytes ); + +} + +void KNetworkByteStream::slotError ( int code ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Socket error " << code << endl; + + emit error ( code ); + +} + +#include "gwbytestream.moc" diff --git a/kopete/protocols/groupwise/gwbytestream.h b/kopete/protocols/groupwise/gwbytestream.h new file mode 100644 index 00000000..9422f9c3 --- /dev/null +++ b/kopete/protocols/groupwise/gwbytestream.h @@ -0,0 +1,69 @@ + +/*************************************************************************** + gwbytestream.h - Byte Stream using KNetwork sockets + adapted from jabberbytestream.h + ------------------- + begin : Wed Jul 7 2004 + copyright : (C) 2004 by Till Gerken + + Kopete (C) 2004 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KNETWORKBYTESTREAM_H +#define KNETWORKBYTESTREAM_H + +#include + +#include "bytestream.h" + + +/** + * Low level socket class, using KDE's KNetwork socket classes + * @author Till Gerken + */ + +class KNetworkByteStream : public ByteStream +{ + +Q_OBJECT + +public: + KNetworkByteStream ( QObject *parent = 0, const char *name = 0 ); + + ~KNetworkByteStream (); + + bool connect ( QString host, QString service ); + virtual bool isOpen () const; + virtual void close (); + + KNetwork::KBufferedSocket *socket () const; + +signals: + void connected (); + +protected: + virtual int tryWrite (); + +private slots: + void slotConnected (); + void slotConnectionClosed (); + void slotReadyRead (); + void slotBytesWritten ( int ); + void slotError ( int ); + +private: + KNetwork::KBufferedSocket *mSocket; + bool mClosing; + +}; + +#endif diff --git a/kopete/protocols/groupwise/gwchatui.rc b/kopete/protocols/groupwise/gwchatui.rc new file mode 100644 index 00000000..5871ae50 --- /dev/null +++ b/kopete/protocols/groupwise/gwchatui.rc @@ -0,0 +1,17 @@ + + + + + &Chat + + + + + + + + + + + + diff --git a/kopete/protocols/groupwise/gwconnector.cpp b/kopete/protocols/groupwise/gwconnector.cpp new file mode 100644 index 00000000..c145ddfe --- /dev/null +++ b/kopete/protocols/groupwise/gwconnector.cpp @@ -0,0 +1,129 @@ + +/*************************************************************************** + gwconnector.cpp - Socket Connector for KNetwork + ------------------- + begin : Wed Jul 7 2004 + copyright : (C) 2004 by Till Gerken + + Kopete (C) 2004 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include + +#include "gwconnector.h" +#include "gwerror.h" +#include "gwbytestream.h" + +KNetworkConnector::KNetworkConnector ( QObject *parent, const char */*name*/ ) + : Connector ( parent ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "New KNetwork connector." << endl; + + mErrorCode = KNetwork::KSocketBase::NoError; + + mByteStream = new KNetworkByteStream ( this ); + + connect ( mByteStream, SIGNAL ( connected () ), this, SLOT ( slotConnected () ) ); + connect ( mByteStream, SIGNAL ( error ( int ) ), this, SLOT ( slotError ( int ) ) ); + mPort = 0; +} + +KNetworkConnector::~KNetworkConnector () +{ + + delete mByteStream; + +} + +void KNetworkConnector::connectToServer ( const QString &server ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Initiating connection to " << mHost << endl; + Q_ASSERT( !mHost.isNull() ); + Q_ASSERT( mPort ); + /* + * FIXME: we should use a SRV lookup to determine the + * actual server to connect to. As this is currently + * not supported yet, we're using setOptHostPort(). + * For XMPP 1.0, we need to enable this! + */ + + mErrorCode = KNetwork::KSocketBase::NoError; + + if ( !mByteStream->connect ( mHost, QString::number ( mPort ) ) ) + { + // Houston, we have a problem + mErrorCode = mByteStream->socket()->error (); + emit error (); + } + +} + +void KNetworkConnector::slotConnected () +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "We are connected." << endl; + + // FIXME: setPeerAddress() is something different, find out correct usage later + //KInetSocketAddress inetAddress = mStreamSocket->address().asInet().makeIPv6 (); + //setPeerAddress ( QHostAddress ( inetAddress.ipAddress().addr () ), inetAddress.port () ); + + emit connected (); + +} + +void KNetworkConnector::slotError ( int code ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Error detected: " << code << endl; + + mErrorCode = code; + emit error (); +} + +int KNetworkConnector::errorCode () +{ + + return mErrorCode; + +} + +ByteStream *KNetworkConnector::stream () const +{ + + return mByteStream; + +} + +void KNetworkConnector::done () +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + mByteStream->close (); +} + +void KNetworkConnector::setOptHostPort ( const QString &host, Q_UINT16 port ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Manually specifying host " << host << " and port " << port << endl; + + mHost = host; + mPort = port; + +} + +void KNetworkConnector::setOptSSL ( bool ssl ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Setting SSL to " << ssl << endl; + + setUseSSL ( ssl ); + +} + +#include "gwconnector.moc" diff --git a/kopete/protocols/groupwise/gwconnector.h b/kopete/protocols/groupwise/gwconnector.h new file mode 100644 index 00000000..12dc59d2 --- /dev/null +++ b/kopete/protocols/groupwise/gwconnector.h @@ -0,0 +1,66 @@ + +/*************************************************************************** + gwconnector.h - Socket Connector for KNetwork + ------------------- + begin : Wed Jul 7 2004 + copyright : (C) 2004 by Till Gerken + + Kopete (C) 2004 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef GWCONNECTOR_H +#define GWCONNECTOR_H + +#include "gwbytestream.h" + +#include "connector.h" + +class ByteStream; +class KNetworkByteStream; +class KResolverEntry; + +/** +@author Till Gerken +*/ +class KNetworkConnector : public Connector +{ + +Q_OBJECT + +public: + KNetworkConnector ( QObject *parent = 0, const char *name = 0 ); + + virtual ~KNetworkConnector (); + + virtual void connectToServer ( const QString &server ); + virtual ByteStream *stream () const; + virtual void done (); + + void setOptHostPort ( const QString &host, Q_UINT16 port ); + void setOptSSL ( bool ); + + int errorCode (); + +private slots: + void slotConnected (); + void slotError ( int ); + +private: + QString mHost; + Q_UINT16 mPort; + int mErrorCode; + + KNetworkByteStream *mByteStream; + +}; + +#endif diff --git a/kopete/protocols/groupwise/gwcontact.cpp b/kopete/protocols/groupwise/gwcontact.cpp new file mode 100644 index 00000000..6dbb8c1b --- /dev/null +++ b/kopete/protocols/groupwise/gwcontact.cpp @@ -0,0 +1,317 @@ +/* + gwcontact.cpp - Kopete GroupWise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Testbed + Copyright (c) 2003 by Will Stephenson + + Blocking status taken from MSN + Copyright (c) 2003 by Will Stephenson + Copyright (c) 2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002 by Ryan Cumming + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2004 by Olivier Goffart + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#include + +#include +#include +#include + +#include +#include + +#include "client.h" +#include "gwaccount.h" +#include "gwprotocol.h" +#include "privacymanager.h" +#include "userdetailsmanager.h" +#include "tasks/updatecontacttask.h" +#include "ui/gwcontactproperties.h" + +#include "gwcontact.h" + +using namespace GroupWise; + +GroupWiseContact::GroupWiseContact( Kopete::Account* account, const QString &dn, + Kopete::MetaContact *parent, + const int objectId, const int parentId, const int sequence ) +: Kopete::Contact( account, GroupWiseProtocol::dnToDotted( dn ), parent ), m_objectId( objectId ), m_parentId( parentId ), + m_sequence( sequence ), m_actionBlock( 0 ), m_archiving( false ), m_deleting( false ), m_messageReceivedOffline( false ) +{ + if ( dn.find( '=' ) != -1 ) + { + m_dn = dn; + } + connect( static_cast< GroupWiseAccount *>( account ), SIGNAL( privacyChanged( const QString &, bool ) ), + SLOT( receivePrivacyChanged( const QString &, bool ) ) ); + setOnlineStatus( ( parent && parent->isTemporary() ) ? protocol()->groupwiseUnknown : protocol()->groupwiseOffline ); +} + +GroupWiseContact::~GroupWiseContact() +{ + // This is necessary because otherwise the userDetailsManager + // would not fetch details for this contact if they contact you + // again from off-contact-list. + if ( metaContact()->isTemporary() ) + account()->client()->userDetailsManager()->removeContact( contactId() ); +} + +QString GroupWiseContact::dn() const +{ + return m_dn; +} + +void GroupWiseContact::updateDetails( const ContactDetails & details ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + if ( !details.cn.isNull() ) + setProperty( protocol()->propCN, details.cn ); + if ( !details.dn.isNull() ) + m_dn = details.dn; + if ( !details.givenName.isNull() ) + setProperty( protocol()->propGivenName, details.givenName ); + if ( !details.surname.isNull() ) + setProperty( protocol()->propLastName, details.surname ); + if ( !details.fullName.isNull() ) + setProperty( protocol()->propFullName, details.fullName ); + m_archiving = details.archive; + if ( !details.awayMessage.isNull() ) + setProperty( protocol()->propAwayMessage, details.awayMessage ); + + m_serverProperties = details.properties; + + QMap::Iterator it; + // work phone number + if ( ( it = m_serverProperties.find( "telephoneNumber" ) ) + != m_serverProperties.end() ) + setProperty( protocol()->propPhoneWork, it.data() ); + + // mobile phone number + if ( ( it = m_serverProperties.find( "mobile" ) ) + != m_serverProperties.end() ) + setProperty( protocol()->propPhoneMobile, it.data() ); + + // email + if ( ( it = m_serverProperties.find( "Internet EMail Address" ) ) + != m_serverProperties.end() ) + setProperty( protocol()->propEmail, it.data() ); + + if ( details.status != GroupWise::Invalid ) + { + Kopete::OnlineStatus status = protocol()->gwStatusToKOS( details.status ); + setOnlineStatus( status ); + } +} + +GroupWiseProtocol *GroupWiseContact::protocol() +{ + return static_cast( Kopete::Contact::protocol() ); +} + +GroupWiseAccount *GroupWiseContact::account() +{ + return static_cast( Kopete::Contact::account() ); +} + +bool GroupWiseContact::isReachable() +{ + // When we are invisible we can't start a chat with others, but we don't make isReachable return false, because then we + // don't get any notification when we click on someone in the contact list. Instead we warn the user when they try to send a message, + // in GWChatSession + // (This is a GroupWise rule, not a problem in Kopete) + + if ( account()->isConnected() && ( isOnline() || messageReceivedOffline() ) /* && account()->myself()->onlineStatus() != protocol()->groupwiseAppearOffline */) + return true; + if ( !account()->isConnected()/* || account()->myself()->onlineStatus() == protocol()->groupwiseAppearOffline*/ ) + return false; + + // fallback, something went wrong + return false; +} + +void GroupWiseContact::serialize( QMap< QString, QString > &serializedData, QMap< QString, QString > & /* addressBookData */ ) +{ + serializedData[ "DN" ] = m_dn; +} + +Kopete::ChatSession * GroupWiseContact::manager( Kopete::Contact::CanCreateFlags canCreate ) +{ + //kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "called, canCreate: " << canCreate << endl; + + Kopete::ContactPtrList chatMembers; + chatMembers.append( this ); + + return account()->chatSession( chatMembers, QString::null, canCreate ); +} + +QPtrList *GroupWiseContact::customContextMenuActions() +{ + QPtrList *m_actionCollection = new QPtrList; + + // Block/unblock Contact + QString label = account()->isContactBlocked( m_dn ) ? i18n( "Unblock User" ) : i18n( "Block User" ); + if( !m_actionBlock ) + { + m_actionBlock = new KAction( label, "msn_blocked",0, this, SLOT( slotBlock() ), + this, "actionBlock" ); + } + else + m_actionBlock->setText( label ); + m_actionBlock->setEnabled( account()->isConnected() ); + + m_actionCollection->append( m_actionBlock ); + + return m_actionCollection; +} + +void GroupWiseContact::slotUserInfo() +{ + new GroupWiseContactProperties( this, Kopete::UI::Global::mainWidget(), "gwcontactproperties" ); +} + +QMap< QString, QString > GroupWiseContact::serverProperties() +{ + return m_serverProperties; +} + +void GroupWiseContact::sendMessage( Kopete::Message &message ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + manager()->appendMessage( message ); + // tell the manager it was sent successfully + manager()->messageSucceeded(); +} + +void GroupWiseContact::deleteContact() +{ + account()->deleteContact( this ); +} + +void GroupWiseContact::sync( unsigned int) +{ + account()->syncContact( this ); +} + +void GroupWiseContact::slotBlock() +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + if ( account()->isConnected() ) + { + if ( account()->isContactBlocked( m_dn ) ) + account()->client()->privacyManager()->setAllow( m_dn ); + else + account()->client()->privacyManager()->setDeny( m_dn ); + } +} + +void GroupWiseContact::receivePrivacyChanged( const QString & dn, bool allow ) +{ + Q_UNUSED( allow ); + if ( dn == m_dn ) // set the online status back to itself. this will set the blocking state + setOnlineStatus( this->onlineStatus() ); +} + +void GroupWiseContact::setOnlineStatus( const Kopete::OnlineStatus& status ) +{ + setMessageReceivedOffline( false ); + if ( status == protocol()->groupwiseAwayIdle && status != onlineStatus() ) + setIdleTime( 1 ); + else if ( onlineStatus() == protocol()->groupwiseAwayIdle && status != onlineStatus() ) + setIdleTime( 0 ); + + if ( account()->isContactBlocked( m_dn ) && status.internalStatus() < 15 ) + { + Kopete::Contact::setOnlineStatus(Kopete::OnlineStatus(status.status() , (status.weight()==0) ? 0 : (status.weight() -1) , + protocol() , status.internalStatus()+15 , QString::fromLatin1("msn_blocked"), + i18n("%1|Blocked").arg( status.description() ) ) ); + } + else + { + if(status.internalStatus() >= 15) + { //the user is not blocked, but the status is blocked + switch(status.internalStatus()-15) + { + case 0: + Kopete::Contact::setOnlineStatus( GroupWiseProtocol::protocol()->groupwiseUnknown ); + break; + case 1: + Kopete::Contact::setOnlineStatus( GroupWiseProtocol::protocol()->groupwiseOffline ); + break; + case 2: + Kopete::Contact::setOnlineStatus( GroupWiseProtocol::protocol()->groupwiseAvailable ); + break; + case 3: + Kopete::Contact::setOnlineStatus( GroupWiseProtocol::protocol()->groupwiseBusy ); + break; + case 4: + Kopete::Contact::setOnlineStatus( GroupWiseProtocol::protocol()->groupwiseAway ); + break; + case 5: + Kopete::Contact::setOnlineStatus( GroupWiseProtocol::protocol()->groupwiseAwayIdle ); + break; + default: + Kopete::Contact::setOnlineStatus( GroupWiseProtocol::protocol()->groupwiseUnknown ); + break; + } + } + else + Kopete::Contact::setOnlineStatus(status); + } +} + +bool GroupWiseContact::archiving() const +{ + return m_archiving; +} + +bool GroupWiseContact::deleting() const +{ + return m_deleting; +} + +void GroupWiseContact::setDeleting( bool deleting ) +{ + m_deleting = deleting; +} + +void GroupWiseContact::renamedOnServer() +{ + UpdateContactTask * uct = ( UpdateContactTask * )sender(); + if ( uct->success() ) + { + if( uct->displayName() != + property( Kopete::Global::Properties::self()->nickName() ).value().toString() ) + setProperty( Kopete::Global::Properties::self()->nickName(), uct->displayName() ); + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "rename failed, return code: " << uct->statusCode() << endl; +} + +void GroupWiseContact::setMessageReceivedOffline( bool on ) +{ + m_messageReceivedOffline = on; +} + +bool GroupWiseContact::messageReceivedOffline() const +{ + return m_messageReceivedOffline; +} + +#include "gwcontact.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/groupwise/gwcontact.h b/kopete/protocols/groupwise/gwcontact.h new file mode 100644 index 00000000..e5079387 --- /dev/null +++ b/kopete/protocols/groupwise/gwcontact.h @@ -0,0 +1,194 @@ +/* + gwcontact.h - Kopete GroupWise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Testbed + Copyright (c) 2003 by Will Stephenson + + Blocking status taken from MSN + Copyright (c) 2002 by Duncan Mac-Vicar Prett + Copyright (c) 2002 by Ryan Cumming + Copyright (c) 2002-2003 by Martijn Klingens + Copyright (c) 2002-2004 by Olivier Goffart + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#ifndef GW_CONTACT_H +#define GW_CONTACT_H + +#include +#include + +#include "kopetecontact.h" +#include "kopetemessage.h" + +#include "gwerror.h" +#include "gwfield.h" +#include "gwmessagemanager.h" + +class KAction; +class KActionCollection; +namespace Kopete { class Account; } +class GroupWiseAccount; +class GroupWiseChatSession; +class GroupWiseProtocol; +namespace Kopete { class MetaContact; } + +using namespace GroupWise; + +/** +@author Will Stephenson +*/ +class GroupWiseContact : public Kopete::Contact +{ + Q_OBJECT +public: + /** + * Constructor + * @param account The GroupWiseAccount this belongs to. + * @param uniqueName The userId for this contact. May be a DN, in which case it will be converted to dotted format for the contactId and stored. + * @param parent The Kopete::MetaContact this contact is part of. + * @param objectId The contact's numeric object ID. + * @param parentId The ID of this contact's parent (folder). + * @param sequence This contact's sequence number (The position it appears in within its parent). + */ + GroupWiseContact( Kopete::Account* account, const QString &uniqueName, + Kopete::MetaContact *parent, + const int objectId, const int parentId, const int sequence ); + + ~GroupWiseContact(); + + /** + * Access this contact's Kopete::Account subclass + */ + GroupWiseAccount * account(); + + /** + * Access this contact's Kopete::Protocol subclass + */ + GroupWiseProtocol * protocol(); + + /** + * Get the contact's DN (used for communications with the server, not the contactId ) + */ + QString dn() const; + + /** + * Update the contact's status and metadata from the supplied fields + */ + void updateDetails( const GroupWise::ContactDetails & details ); + + virtual bool isReachable(); + /** + * Serialize the contact's data into a key-value map + * suitable for writing to a file + */ + virtual void serialize(QMap< QString, QString >& serializedData, + QMap< QString, QString >& addressBookData); + /** + * Return the actions for this contact + */ + virtual QPtrList *customContextMenuActions(); + + /** + * Returns a Kopete::ChatSession associated with this contact + */ + virtual Kopete::ChatSession *manager( Kopete::Contact::CanCreateFlags canCreate = Kopete::Contact::CannotCreate ); + + /** + * Access the contact's server properties + */ + QMap< QString, QString > serverProperties(); + /** + * Updates this contact's group membership and display name on the server + */ + void sync( unsigned int); + /** + * Updates this contact's online status, including blocking status + */ + void setOnlineStatus(const Kopete::OnlineStatus& status); + /** + * Are this contact's chats being administratively logged? + */ + bool archiving() const; + /** + * Is this contact in the process of being deleted + */ + bool deleting() const; + /** + * Mark this contact as being deleted + */ + void setDeleting( bool deleting ); + /** + * Marks this contact as having sent a message whilst apparently offline + */ + void setMessageReceivedOffline( bool on ); + /** + * Has this contact sent a message whilst apparently offline? + */ + bool messageReceivedOffline() const; + +public slots: + /** + * Transmits an outgoing message to the server + * Called when the chat window send button has been pressed + * (in response to the relevant Kopete::ChatSession signal) + */ + void sendMessage( Kopete::Message &message ); + /** + * Delete this contact on the server + */ + virtual void deleteContact(); + /** + * Called when the call to rename the contact on the server has completed + */ + void renamedOnServer(); + +protected: + // debug function to see what message managers we have on the server + void dumpManagers(); +protected slots: + /** + * Show the contact's properties + */ + void slotUserInfo(); + /** + * Block or unblock the contact, toggle its current blocking state + */ + void slotBlock(); + /** + * Receive notification that this contact's privacy setting changed - update status + */ + void receivePrivacyChanged( const QString &, bool ); +protected: + KActionCollection* m_actionCollection; + + int m_objectId; + int m_parentId; + int m_sequence; + QString m_dn; + QString m_displayName; + KAction* m_actionPrefs; + KAction *m_actionBlock; + // Novell Messenger Properties, as received by the server. + // Unfortunately we don't the domain of the set of keys, so they are not easily mappable to KopeteContactProperties + QMap< QString, QString > m_serverProperties; + bool m_archiving; + // HACK: flag used to differentiate between 'all contact list instances gone while we are moving on the server' + // and 'all contact list instances gone because we wanted to delete them all' + bool m_deleting; + bool m_messageReceivedOffline; +}; + +#endif diff --git a/kopete/protocols/groupwise/gwcontactlist.cpp b/kopete/protocols/groupwise/gwcontactlist.cpp new file mode 100644 index 00000000..2af6d42a --- /dev/null +++ b/kopete/protocols/groupwise/gwcontactlist.cpp @@ -0,0 +1,237 @@ +/* + gwcontactlist.cpp - Kopete GroupWise Protocol + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#include + +#include + +#include "gwcontactlist.h" +#include "gwerror.h" //debug area + +GWContactList::GWContactList( QObject * parent ) + : QObject( parent ), rootFolder( new GWFolder( this, 0, 0, QString::null ) ) +{ } + +GWFolder * GWContactList::addFolder( unsigned int id, unsigned int sequence, const QString & displayName ) +{ + if ( rootFolder ) + return new GWFolder( rootFolder, id, sequence, displayName ); + else + return 0; +} + +GWContactInstance * GWContactList::addContactInstance( unsigned int id, unsigned int parent, unsigned int sequence, const QString & displayName, const QString & dn ) +{ + QObjectList * l = queryList( "GWFolder", 0, false, true ); + QObjectListIt it( *l ); // iterate over the buttons + QObject *obj; + GWContactInstance * contact = 0; + while ( (obj = it.current()) != 0 ) + { + GWFolder * folder = ::qt_cast< GWFolder * >( obj ); + if ( folder && folder->id == parent ) + { + contact = new GWContactInstance( folder, id, sequence, displayName, dn ); + break; + } + ++it; + } + delete l; + return contact; +} + +GWFolder * GWContactList::findFolderById( unsigned int id ) +{ + QObjectList * l = queryList( "GWFolder", 0, false, true ); + QObjectListIt it( *l ); // iterate over the buttons + QObject *obj; + GWFolder * candidate, * folder = 0; + while ( (obj = it.current()) != 0 ) + { + candidate = ::qt_cast< GWFolder * >( obj ); + if ( candidate->id == id ) + { + folder = candidate; + break; + } + ++it; + } + delete l; + return folder; +} + +GWFolder * GWContactList::findFolderByName( const QString & displayName ) +{ + QObjectList * l = queryList( "GWFolder", 0, false, true ); + QObjectListIt it( *l ); // iterate over the buttons + QObject *obj; + GWFolder * folder = 0; + while ( (obj = it.current()) != 0 ) + { + GWFolder * candidate = ::qt_cast< GWFolder * >( obj ); + if ( candidate->displayName == displayName ) + { + folder = candidate; + break; + } + ++it; + } + delete l; + return folder; +} + +int GWContactList::maxSequenceNumber() +{ + QObjectList * l = queryList( "GWFolder", 0, false, true ); + QObjectListIt it( *l ); // iterate over the buttons + QObject *obj; + unsigned int sequence = 0; + while ( (obj = it.current()) != 0 ) + { + GWFolder * current = ::qt_cast< GWFolder * >( obj ); + sequence = QMAX( sequence, current->sequence ); + ++it; + } + delete l; + return sequence; +} + +GWContactInstanceList GWContactList::instancesWithDn( const QString & dn ) +{ + QObjectList * l = queryList( "GWContactInstance", 0, false, true ); + QObjectListIt it( *l ); // iterate over the buttons + QObject *obj; + GWContactInstanceList matches; + while ( (obj = it.current()) != 0 ) + { + ++it; + GWContactInstance * current = ::qt_cast( obj ); + if ( current->dn == dn ) + matches.append( current ); + } + delete l; + return matches; +} + +void GWContactList::removeInstance( GWContactListItem * instance ) +{ + delete instance; +} + +void GWContactList::removeInstanceById( unsigned int id ) +{ + QObjectList * l = queryList( "GWContactInstance", 0, false, true ); + QObjectListIt it( *l ); // iterate over the buttons + QObject *obj; + GWContactInstanceList matches; + while ( (obj = it.current()) != 0 ) + { + ++it; + GWContactInstance * current = ::qt_cast( obj ); + if ( current->id == id ) + { + delete current; + break; + } + } + delete l; +} + +void GWContactList::dump() +{ + kdDebug(GROUPWISE_DEBUG_GLOBAL) << k_funcinfo << endl; + const QObjectList * l = children(); + if ( l && !l->isEmpty() ) + { + QObjectListIt it( *l ); // iterate over the buttons + QObject *obj; + while ( (obj = it.current()) != 0 ) + { + GWFolder * folder = ::qt_cast< GWFolder * >( obj ); + if ( folder ) + folder->dump( 1 ); + ++it; + } + } + else + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << " contact list is empty." << endl; +} + +void GWContactList::clear() +{ + kdDebug(GROUPWISE_DEBUG_GLOBAL) << k_funcinfo << endl; + const QObjectList * l = children(); + if ( l && !l->isEmpty() ) + { + QObjectListIt it( *l ); + QObject *obj; + while ( (obj = it.current()) != 0 ) + { + delete obj; + ++it; + } + } +} + +GWContactListItem::GWContactListItem( QObject * parent, unsigned int theId, unsigned int theSequence, const QString & theDisplayName ) : + QObject( parent), id( theId ), sequence( theSequence ), displayName( theDisplayName ) +{ } + +GWFolder::GWFolder( QObject * parent, unsigned int theId, unsigned int theSequence, const QString & theDisplayName ) : + GWContactListItem( parent, theId, theSequence, theDisplayName ) +{ } + +void GWFolder::dump( unsigned int depth ) +{ + QString s; + s.fill( ' ', ++depth * 2 ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << s <<"Folder " << displayName << " id: " << id << " contains: " << endl; + const QObjectList * l = children(); + if ( l ) + { + QObjectListIt it( *l ); // iterate over the buttons + QObject *obj; + while ( (obj = it.current()) != 0 ) + { + ++it; + GWContactInstance * instance = ::qt_cast< GWContactInstance * >( obj ); + if (instance) + instance->dump( depth ); + else + { + GWFolder * folder = ::qt_cast< GWFolder * >( obj ); + if ( folder ) + folder->dump( depth ); + } + } + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << s << " no contacts." << endl; +} + +GWContactInstance::GWContactInstance( QObject * parent, unsigned int theId, unsigned int theSequence, const QString & theDisplayName, const QString & theDn ) : + GWContactListItem( parent, theId, theSequence, theDisplayName ), dn( theDn ) +{ } + +void GWContactInstance::dump( unsigned int depth ) +{ + QString s; + s.fill( ' ', ++depth * 2 ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << s << "Contact " << displayName << " id: " << id << " dn: " << dn << endl; +} +#include "gwcontactlist.moc" + diff --git a/kopete/protocols/groupwise/gwcontactlist.h b/kopete/protocols/groupwise/gwcontactlist.h new file mode 100644 index 00000000..e596b96c --- /dev/null +++ b/kopete/protocols/groupwise/gwcontactlist.h @@ -0,0 +1,90 @@ +/* + gwcontactlist.h - Kopete GroupWise Protocol + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +// GROUPWISE SERVER SIDE CONTACT LIST MODEL + +#include + +#ifndef GW_CONTACTLIST_H +#define GW_CONTACTLIST_H + +class GWFolder; +class GWContactInstance; +class GWContactListItem; + +typedef QValueList GWContactInstanceList; + + /** + * These functions model the server side contact list structure enough to allow Kopete to manipulate it correctly + * In GroupWise, a contactlist is composed of folders, containing contacts. But the contacts don't record which + * folders they are in. Instead, each contact entry represents an instance of that contact within the list. + * In Kopete's model, this looks like duplicate contacts (illegal), so instead we have unique contacts, + * each (by way of its metacontact) knowing membership of potentially >1 KopeteGroups. Contacts contain a list of the + * server side list instances. Contact list management operations affect this list, which is updated during every + * operation. Having this list allows us to update the server side contact list and keep changes synchronised across + * different clients. + * The list is volatile - it is not stored in stable storage, but is purged on disconnect and recreated at login. + */ +class GWContactList : public QObject +{ +Q_OBJECT +public: + GWContactList( QObject * parent ); + GWFolder * addFolder( unsigned int id, unsigned int sequence, const QString & displayName ); + GWContactInstance * addContactInstance( unsigned int id, unsigned int parent, unsigned int sequence, const QString & displayName, const QString & dn ); + GWFolder * findFolderById( unsigned int id ); + GWFolder * findFolderByName( const QString & name ); + GWContactInstanceList instancesWithDn( const QString & dn ); + void removeInstance( GWContactListItem * instance ); + void removeInstanceById( unsigned int id ); + int maxSequenceNumber(); + virtual void dump(); + void clear(); + GWFolder * rootFolder; +}; + +class GWContactListItem : public QObject +{ +Q_OBJECT +public: + GWContactListItem( QObject * parent, unsigned int theId, unsigned int theSequence, const QString & theDisplayName ); + + unsigned int id; // OBJECT_ID + unsigned int sequence; // SEQUENCE_NUMBER + QString displayName; // DISPLAY_NAME +}; + +class GWFolder : public GWContactListItem +{ +Q_OBJECT +public: + GWFolder( QObject * parent, unsigned int theId, unsigned int theSequence, const QString & theDisplayName ); + virtual void dump( unsigned int depth ); +}; + +class GWContactInstance : public GWContactListItem +{ +Q_OBJECT +public: + GWContactInstance( QObject * parent, unsigned int theId, unsigned int theSequence, const QString & theDisplayName, const QString & theDn ); + QString dn; // DN + virtual void dump( unsigned int depth ); +}; + +// END OF SERVER SIDE CONTACT LIST MODEL + +#endif diff --git a/kopete/protocols/groupwise/gwmessagemanager.cpp b/kopete/protocols/groupwise/gwmessagemanager.cpp new file mode 100644 index 00000000..d9467dfd --- /dev/null +++ b/kopete/protocols/groupwise/gwmessagemanager.cpp @@ -0,0 +1,516 @@ +// +// C++ Implementation: gwmessagemanager +// +// Description: +// +// +// Author: SUSE AG <>, (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "client.h" +#include "gwaccount.h" +#include "gwcontact.h" +#include "gwerror.h" +#include "gwprotocol.h" +#include "gwsearch.h" + +#include "gwmessagemanager.h" + +GroupWiseChatSession::GroupWiseChatSession(const Kopete::Contact* user, Kopete::ContactPtrList others, Kopete::Protocol* protocol, const GroupWise::ConferenceGuid & guid, int id, const char* name): Kopete::ChatSession(user, others, protocol, name), m_guid( guid ), m_flags( 0 ), m_searchDlg( 0 ), m_memberCount( others.count() ) +{ + Q_UNUSED( id ); + static uint s_id=0; + m_mmId=++s_id; + + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "New message manager for " << user->contactId() << endl; + + // Needed because this is (indirectly) a KXMLGuiClient, so it can find the gui description .rc file + setInstance( protocol->instance() ); + + // make sure Kopete knows about this instance + Kopete::ChatSessionManager::self()->registerChatSession ( this ); + + connect ( this, SIGNAL( messageSent ( Kopete::Message &, Kopete::ChatSession * ) ), + SLOT( slotMessageSent ( Kopete::Message &, Kopete::ChatSession * ) ) ); + connect( this, SIGNAL( myselfTyping ( bool ) ), SLOT( slotSendTypingNotification ( bool ) ) ); + connect( account(), SIGNAL( contactTyping( const ConferenceEvent & ) ), + SLOT( slotGotTypingNotification( const ConferenceEvent & ) ) ); + connect( account(), SIGNAL( contactNotTyping( const ConferenceEvent & ) ), + SLOT( slotGotNotTypingNotification( const ConferenceEvent & ) ) ); + + // Set up the Invite menu + m_actionInvite = new KActionMenu( i18n( "&Invite" ), actionCollection() , "gwInvite" ); + connect( m_actionInvite->popupMenu(), SIGNAL( aboutToShow() ), this, SLOT(slotActionInviteAboutToShow() ) ) ; + + m_secure = new KAction( i18n( "Security Status" ), "encrypted", KShortcut(), this, SLOT( slotShowSecurity() ), actionCollection(), "gwSecureChat" ); + m_secure->setToolTip( i18n( "Conversation is secure" ) ); + + m_logging = new KAction( i18n( "Archiving Status" ), "logchat", KShortcut(), this, SLOT( slotShowArchiving() ), actionCollection(), "gwLoggingChat" ); + updateArchiving(); + + setXMLFile("gwchatui.rc"); + setMayInvite( true ); + + m_invitees.setAutoDelete( true ); +} + +GroupWiseChatSession::~GroupWiseChatSession() +{ + emit leavingConference( this ); +} + +uint GroupWiseChatSession::mmId() const +{ + return m_mmId; +} + +void GroupWiseChatSession::setGuid( const GroupWise::ConferenceGuid & guid ) +{ + if ( m_guid.isEmpty() ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "setting GUID to: " << guid << endl; + m_guid = guid; + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "attempted to change the conference's GUID when already set!" << endl; +} + +bool GroupWiseChatSession::closed() +{ + return m_flags & GroupWise::Closed; +} + +bool GroupWiseChatSession::logging() +{ + return m_flags & GroupWise::Logging; +} + +bool GroupWiseChatSession::secure() +{ + return m_flags & GroupWise::Secure; +} + +void GroupWiseChatSession::setClosed() +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " Conference " << m_guid << " is now Closed " << endl; + m_guid = QString::null; + m_flags = m_flags | GroupWise::Closed; +} + +void GroupWiseChatSession::setLogging( bool logging ) +{ + if ( logging ) + m_flags = m_flags | GroupWise::Logging; + else + m_flags = m_flags & !GroupWise::Logging; +} + +void GroupWiseChatSession::setSecure( bool secure ) +{ + if ( secure ) + m_flags = m_flags | GroupWise::Secure; + else + m_flags = m_flags & !GroupWise::Secure; +} + +GroupWiseAccount * GroupWiseChatSession::account() +{ + return static_cast( Kopete::ChatSession::account() ); +} + +void GroupWiseChatSession::createConference() +{ + if ( m_guid.isEmpty() ) + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + // form a list of invitees + QStringList invitees; + Kopete::ContactPtrList chatMembers = members(); + for ( Kopete::Contact * contact = chatMembers.first(); contact; contact = chatMembers.next() ) + { + invitees.append( static_cast< GroupWiseContact * >( contact )->dn() ); + } + // this is where we will set the GUID and send any pending messages + connect( account(), SIGNAL( conferenceCreated( const int, const GroupWise::ConferenceGuid & ) ), SLOT( receiveGuid( const int, const GroupWise::ConferenceGuid & ) ) ); + connect( account(), SIGNAL( conferenceCreationFailed( const int, const int ) ), SLOT( slotCreationFailed( const int, const int ) ) ); + + // create the conference + account()->createConference( mmId(), invitees ); + } + else + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " tried to create conference on the server when it was already instantiated" << endl; +} + +void GroupWiseChatSession::receiveGuid( const int newMmId, const GroupWise::ConferenceGuid & guid ) +{ + if ( newMmId == mmId() ) + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " got GUID from server" << endl; + m_memberCount = members().count(); + setGuid( guid ); + // re-add all the members. This is because when the last member leaves the conference, + // they are removed from the chat member list GUI. By re-adding them here, we guarantee they appear + // in the UI again, at the price of a debug message when starting up a new chatwindow + + QPtrListIterator< Kopete::Contact > it( members() ); + Kopete::Contact * contact; + while ( ( contact = it.current() ) ) + { + ++it; + addContact( contact, true ); + } + + // notify the contact(s) using this message manager that it's been instantiated on the server + emit conferenceCreated(); + // TODO: send invitations if we're not inviting in the conf create... + dequeueMessagesAndInvites(); + } +} + +void GroupWiseChatSession::slotCreationFailed( const int failedId, const int statusCode ) +{ + if ( failedId == mmId() ) + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " couldn't start a chat, no GUID.\n" << endl; + //emit creationFailed(); + Kopete::Message failureNotify = Kopete::Message( myself(), members(), i18n("An error occurred when trying to start a chat: %1").arg( statusCode ), Kopete::Message::Internal, Kopete::Message::PlainText); + appendMessage( failureNotify ); + setClosed(); + } +} + +void GroupWiseChatSession::slotSendTypingNotification( bool typing ) +{ + // only send a notification if we've got a conference going and we are not Appear Offline + if ( !m_guid.isEmpty() && m_memberCount && + ( account()->myself()->onlineStatus() != GroupWiseProtocol::protocol()->groupwiseAppearOffline ) ) + account()->client()->sendTyping( guid(), typing ); +} + +void GroupWiseChatSession::slotMessageSent( Kopete::Message & message, Kopete::ChatSession * ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + if( account()->isConnected() ) + { + /*if ( closed() ) + { + Kopete::Message failureNotify = Kopete::Message( myself(), members(), i18n("Your message could not be sent. This conversation has been closed by the server, because all the other participants left or declined invitations. "), Kopete::Message::Internal, Kopete::Message::PlainText); + appendMessage( failureNotify ); + messageSucceeded(); + } + else*/ if ( account()->myself()->onlineStatus() == ( static_cast( protocol() ) )->groupwiseAppearOffline ) + { + Kopete::Message failureNotify = Kopete::Message( myself(), members(), i18n("Your message could not be sent. You cannot send messages while your status is Appear Offline. "), Kopete::Message::Internal, Kopete::Message::PlainText); + appendMessage( failureNotify ); + messageSucceeded(); + } + else + { + // if the conference has not been instantiated yet, or if all the members have left + if ( m_guid.isEmpty() || m_memberCount == 0 ) + { + // if there are still invitees, the conference is instantiated, and there are only + if ( m_invitees.count() ) + { + // the message won't go anywhere, as there's noone there except invitees, but we warn the user + // when the last participant leaves. + messageSucceeded(); + } + else + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << "waiting for server to create a conference, queuing message" << endl; + // the conference hasn't been instantiated on the server yet, so queue the message + m_guid = ConferenceGuid(); + createConference(); + m_pendingOutgoingMessages.append( message ); + } + } + else + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << "sending message" << endl; + account()->sendMessage( guid(), message ); + // we could wait until the server acks our send, + // but we'd need a UID for outgoing messages and a list to track them + appendMessage( message ); + messageSucceeded(); + } + } + } +} + +void GroupWiseChatSession::slotGotTypingNotification( const ConferenceEvent& event ) +{ + if ( event.guid == guid() ) + receivedTypingMsg( static_cast( protocol() )->dnToDotted( event.user ), true ); +} + +void GroupWiseChatSession::slotGotNotTypingNotification( const ConferenceEvent& event ) +{ + if ( event.guid == guid() ) + receivedTypingMsg( static_cast( protocol() )->dnToDotted( event.user ), false ); +} + +void GroupWiseChatSession::dequeueMessagesAndInvites() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + for ( QValueListIterator< Kopete::Message > it = m_pendingOutgoingMessages.begin(); + it != m_pendingOutgoingMessages.end(); + ++it ) + { + slotMessageSent( *it, this ); + } + m_pendingOutgoingMessages.clear(); + QPtrListIterator< Kopete::Contact > it( m_pendingInvites ); + Kopete::Contact * contact; + while ( ( contact = it.current() ) ) + { + ++it; + slotInviteContact( contact ); + } + m_pendingInvites.clear(); +} + +void GroupWiseChatSession::slotActionInviteAboutToShow() +{ + // We can't simply insert KAction in this menu bebause we don't know when to delete them. + // items inserted with insert items are automatically deleted when we call clear + + m_inviteActions.setAutoDelete(true); + m_inviteActions.clear(); + + m_actionInvite->popupMenu()->clear(); + + + QDictIterator it( account()->contacts() ); + for( ; it.current(); ++it ) + { + if( !members().contains( it.current() ) && it.current()->isOnline() && it.current() != myself() ) + { + KAction *a=new KopeteContactAction( it.current(), this, + SLOT( slotInviteContact( Kopete::Contact * ) ), m_actionInvite ); + m_actionInvite->insert( a ); + m_inviteActions.append( a ) ; + } + } + // Invite someone off-list + KAction *b=new KAction( i18n ("&Other..."), 0, this, SLOT( slotInviteOtherContact() ), m_actionInvite, "actionOther" ); + m_actionInvite->insert( b ); + m_inviteActions.append( b ) ; +} + +void GroupWiseChatSession::slotInviteContact( Kopete::Contact * contact ) +{ + if ( m_guid.isEmpty() ) + { + m_pendingInvites.append( contact ); + createConference(); + } + else + { + QWidget * w = view(false) ? dynamic_cast( view(false)->mainWidget()->topLevelWidget() ) : 0L; + + bool ok; + QRegExp rx( ".*" ); + QRegExpValidator validator( rx, this ); + QString inviteMessage = KInputDialog::getText( i18n( "Enter Invitation Message" ), + i18n( "Enter the reason for the invitation, or leave blank for no reason:" ), QString(), + &ok, w ? w : Kopete::UI::Global::mainWidget(), "invitemessagedlg", &validator ); + if ( ok ) + { + GroupWiseContact * gwc = static_cast< GroupWiseContact *>( contact ); + static_cast< GroupWiseAccount * >(account())->sendInvitation( m_guid, gwc->dn(), inviteMessage ); + } + } +} + +void GroupWiseChatSession::inviteContact( const QString &contactId ) +{ + Kopete::Contact * contact = account()->contacts()[ contactId ]; + if ( contact ) + slotInviteContact( contact ); +} + +void GroupWiseChatSession::slotInviteOtherContact() +{ + if ( !m_searchDlg ) + { + // show search dialog + QWidget * w = ( view(false) ? dynamic_cast( view(false)->mainWidget()->topLevelWidget() ) : + Kopete::UI::Global::mainWidget() ); + m_searchDlg = new KDialogBase( w, "invitesearchdialog", false, i18n( "Search for Contact to Invite" ), KDialogBase::Ok|KDialogBase::Cancel ); + m_search = new GroupWiseContactSearch( account(), QListView::Single, true, m_searchDlg, "invitesearchwidget" ); + m_searchDlg->setMainWidget( m_search ); + connect( m_search, SIGNAL( selectionValidates( bool ) ), m_searchDlg, SLOT( enableButtonOK( bool ) ) ); + m_searchDlg->enableButtonOK( false ); + } + m_searchDlg->show(); +} + +void GroupWiseChatSession::slotSearchedForUsers() +{ + // create an item for each result, in the block list + QValueList< ContactDetails > selected = m_search->selectedResults(); + if ( selected.count() ) + { + QWidget * w = ( view(false) ? dynamic_cast( view(false)->mainWidget()->topLevelWidget() ) : + Kopete::UI::Global::mainWidget() ); + ContactDetails cd = selected.first(); + bool ok; + QRegExp rx( ".*" ); + QRegExpValidator validator( rx, this ); + QString inviteMessage = KInputDialog::getText( i18n( "Enter Invitation Message" ), + i18n( "Enter the reason for the invitation, or leave blank for no reason:" ), QString(), + &ok, w , "invitemessagedlg", &validator ); + if ( ok ) + { + account()->sendInvitation( m_guid, cd.dn, inviteMessage ); + } + } +} + +void GroupWiseChatSession::addInvitee( const Kopete::Contact * c ) +{ + // create a placeholder contact for each invitee + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + QString pending = i18n("label attached to contacts who have been invited but are yet to join a chat", "(pending)"); + Kopete::MetaContact * inviteeMC = new Kopete::MetaContact(); + inviteeMC->setDisplayName( c->metaContact()->displayName() + pending ); + GroupWiseContact * invitee = new GroupWiseContact( account(), c->contactId() + " " + pending, inviteeMC, 0, 0, 0 ); + invitee->setOnlineStatus( c->onlineStatus() ); + // TODO: we could set all the placeholder's properties etc here too + addContact( invitee, true ); + m_invitees.append( invitee ); +} + +void GroupWiseChatSession::joined( GroupWiseContact * c ) +{ + // we add the real contact before removing the placeholder, + // because otherwise KMM will delete itself when the last member leaves. + addContact( c ); + + // look for the invitee and remove it + Kopete::Contact * pending; + for ( pending = m_invitees.first(); pending; pending = m_invitees.next() ) + { + if ( pending->contactId().startsWith( c->contactId() ) ) + { + removeContact( pending, QString::null, Kopete::Message::PlainText, true ); + break; + } + } + + m_invitees.remove( pending ); + + updateArchiving(); + + ++m_memberCount; +} + +void GroupWiseChatSession::left( GroupWiseContact * c ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + removeContact( c ); + --m_memberCount; + + updateArchiving(); + + if ( m_memberCount == 0 ) + { + if ( m_invitees.count() ) + { + Kopete::Message failureNotify = Kopete::Message( myself(), members(), + i18n("All the other participants have left, and other invitations are still pending. Your messages will not be delivered until someone else joins the chat."), + Kopete::Message::Internal, Kopete::Message::PlainText ); + appendMessage( failureNotify ); + } + else + setClosed(); + } +} + +void GroupWiseChatSession::inviteDeclined( GroupWiseContact * c ) +{ + // look for the invitee and remove it + Kopete::Contact * pending; + for ( pending = m_invitees.first(); pending; pending = m_invitees.next() ) + { + if ( pending->contactId().startsWith( c->contactId() ) ) + { + removeContact( pending, QString::null, Kopete::Message::PlainText, true ); + break; + } + } + m_invitees.remove( pending ); + + QString from = c->metaContact()->displayName(); + + Kopete::Message declined = Kopete::Message( myself(), members(), + i18n("%1 has rejected an invitation to join this conversation.").arg( from ), + Kopete::Message::Internal, Kopete::Message::PlainText ); + appendMessage( declined ); +} + +void GroupWiseChatSession::updateArchiving() +{ + bool archiving = false; + QPtrListIterator< Kopete::Contact > it( members() ); + GroupWiseContact * contact; + while ( ( contact = static_cast( it.current() ) ) ) + { + ++it; + if ( contact->archiving() ) + { + archiving = true; + break; + } + } + if ( archiving ) + { + m_logging->setEnabled( true ); + m_logging->setToolTip( i18n( "Conversation is being administratively logged" ) ); + } + else + { + m_logging->setEnabled( false ); + m_logging->setToolTip( i18n( "Conversation is not being administratively logged" ) ); + } +} + +void GroupWiseChatSession::slotShowSecurity() +{ + QWidget * w = ( view(false) ? dynamic_cast( view(false)->mainWidget()->topLevelWidget() ) : + Kopete::UI::Global::mainWidget() ); + KMessageBox::queuedMessageBox( w, KMessageBox::Information, i18n( "This conversation is secured with SSL security." ), i18n("Security Status" ) ); +} + +void GroupWiseChatSession::slotShowArchiving() +{ + QWidget * w = ( view(false) ? dynamic_cast( view(false)->mainWidget()->topLevelWidget() ) : + Kopete::UI::Global::mainWidget() ); + KMessageBox::queuedMessageBox( w, KMessageBox::Information, i18n( "This conversation is being logged administratively." ), i18n("Archiving Status" ) ); +} + +#include "gwmessagemanager.moc" diff --git a/kopete/protocols/groupwise/gwmessagemanager.h b/kopete/protocols/groupwise/gwmessagemanager.h new file mode 100644 index 00000000..5413fd23 --- /dev/null +++ b/kopete/protocols/groupwise/gwmessagemanager.h @@ -0,0 +1,177 @@ +// +// C++ Interface: gwmessagemanager +// +// Description: +// +// +// Author: SUSE AG <>, (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#ifndef GWMESSAGEMANAGER_H +#define GWMESSAGEMANAGER_H + +#include +#include +#include + +#include "gwerror.h" + +class QLabel; +class KAction; +class KActionMenu; +class KDialogBase; +class GroupWiseAccount; +class GroupWiseContact; +class GroupWiseContactSearch; +/** + * Specialised message manager, which tracks the GUID used by GroupWise to uniquely identify a given chat, and provides invite actions and logging and security indicators. To instantiate call @ref GroupWiseAccount::chatSession(). + * @author SUSE AG +*/ + +using namespace GroupWise; + +class GroupWiseChatSession : public Kopete::ChatSession +{ +Q_OBJECT + +friend class GroupWiseAccount; + +public: + /** + * The destructor emits leavingConference so that the account can tell the server that the user has left the chat + */ + ~GroupWiseChatSession(); + /** + * The conference's globally unique identifier, which is given to it by the server + */ + ConferenceGuid guid() const { return m_guid; } + /** + * Change the GUID + */ + void setGuid( const ConferenceGuid & guid ); + /** + * Utility account access + */ + GroupWiseAccount * account(); + /** + * Accessors and mutators for secure chat, logged chat, and closed conference flags + */ + void setSecure( bool secure ); + void setLogging( bool logged ); + void setClosed(); + bool secure(); + bool logging(); + bool closed(); + /** + * Add invitees to the conference + */ + void addInvitee( const Kopete::Contact * ); + /** + * Add members to the conference + */ + void joined( GroupWiseContact * ); + /** + * Remove members from conference + */ + void left( GroupWiseContact * ); + /** + * An invitation was declined + */ + void inviteDeclined( GroupWiseContact * ); + /** + * Check whether the conversation being administratively logged and update the UI to indicate this + */ + void updateArchiving(); + /** + * Reimplemented from Kopete::ChatSession - invites contacts via DND + */ + virtual void inviteContact(const QString& ); +signals: + /** + * Tell the contact we got a GUID so it can route incoming messages here. + */ + void conferenceCreated(); + /** + * Tell the account that the GroupWiseChatSession is closing so it can tell the server that the user has left the conference + */ + void leavingConference( GroupWiseChatSession * ); +protected: + /** + * Start the process of creating a conference for this GWMM on the server. + */ + void createConference(); + /** + * Sends any messages and invitations that were queued while waiting for the conference to be created + */ + void dequeueMessagesAndInvites(); +protected slots: + /** + * Receive the GUID returned by the server when we start a chat. + * @param mmId Message Manager ID, used to determine if this GUID is meant for this message manager + * @param guid The GUID allotted us by the server. + */ + void receiveGuid( const int mmId, const GroupWise::ConferenceGuid & guid ); + /** + * An attempt to create a conference on the server failed. + * @param mmId Message Manager ID to see if the failure refers to this message manager + */ + void slotCreationFailed( const int mmId,const int statusCode ); + + void slotSendTypingNotification ( bool typing ); + void slotMessageSent( Kopete::Message &message, Kopete::ChatSession * ); + // TODO: slots for us leaving conference, us inviting someone, someone joining, someone leaving, someone sending an invitation, getting typing? + void slotGotTypingNotification( const ConferenceEvent & ); + void slotGotNotTypingNotification( const ConferenceEvent & ); + /** + * Popupulate the menu of invitable contacts + */ + void slotActionInviteAboutToShow(); + /** + * Invite a contact to join this chat + */ + void slotInviteContact( Kopete::Contact * ); + /** + * Show the search dialog to invite another contact to the chat + */ + void slotInviteOtherContact(); + /** + * Process the response from the search dialog; send the actual invitation + */ + void slotSearchedForUsers(); + + void slotShowSecurity(); + void slotShowArchiving(); +private: + + GroupWiseChatSession(const Kopete::Contact* user, Kopete::ContactPtrList others, Kopete::Protocol* protocol, const ConferenceGuid & guid, int id = 0, const char* name = 0); + + ConferenceGuid m_guid; // The conference's globally unique identifier, which is given to it by the server + int m_flags; // flags for secure connections, central logging and "conference closed" as given by the server + + QValueList< Kopete::Message > m_pendingOutgoingMessages; // messages queued while we wait for the server to tell us the conference is created. + Kopete::ContactPtrList m_pendingInvites; // people we wanted to invite to the conference, queued while waiting for the conference to be created. + KActionMenu *m_actionInvite; + QPtrList m_inviteActions; + // labels showing secure and logging status + KAction *m_secure; + KAction *m_logging; + // search widget and dialog used for inviting contacts + GroupWiseContactSearch * m_search; + KDialogBase * m_searchDlg; + // contacts who have been invited to join but have not yet joined the chat + Kopete::ContactPtrList m_invitees; + // track the number of members actually in the chat + uint m_memberCount; + + /** + * return an unique identifier for that kmm + * @todo check it! + */ + uint mmId() const; + uint m_mmId; + +}; + +#endif diff --git a/kopete/protocols/groupwise/gwprotocol.cpp b/kopete/protocols/groupwise/gwprotocol.cpp new file mode 100644 index 00000000..dcf92a17 --- /dev/null +++ b/kopete/protocols/groupwise/gwprotocol.cpp @@ -0,0 +1,283 @@ +/* + gwprotocol.cpp - Kopete GroupWise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Testbed + Copyright (c) 2003 by Will Stephenson + rtfizeTest from nm_rtfize_text, from Gaim src/protocols/novell/nmuser.c + Copyright (c) 2004 Novell, Inc. All Rights Reserved + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ +#include +#include + +#include +#include + +#include "kopeteaccountmanager.h" +#include "kopeteonlinestatusmanager.h" +#include "kopeteglobal.h" + +#include "gwaccount.h" +#include "gwerror.h" +#include "gwcontact.h" +#include "gwprotocol.h" +#include "ui/gwaddcontactpage.h" +#include "ui/gweditaccountwidget.h" + +typedef KGenericFactory GroupWiseProtocolFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_groupwise, GroupWiseProtocolFactory( "kopete_groupwise" ) ) + +GroupWiseProtocol *GroupWiseProtocol::s_protocol = 0L; + +GroupWiseProtocol::GroupWiseProtocol( QObject* parent, const char *name, const QStringList &/*args*/ ) + : Kopete::Protocol( GroupWiseProtocolFactory::instance(), parent, name ), +/* initialise Kopete::OnlineStatus that should be user selectable in the user interface */ + groupwiseOffline ( Kopete::OnlineStatus::Offline, 0, this, GroupWise::Offline, QString::null, + i18n( "Offline" ), i18n( "O&ffline" ), Kopete::OnlineStatusManager::Offline ), + groupwiseAvailable ( Kopete::OnlineStatus::Online, 25, this, GroupWise::Available, QString::null, + i18n( "Online" ), i18n( "&Online" ), Kopete::OnlineStatusManager::Online ), + groupwiseBusy ( Kopete::OnlineStatus::Away, 18, this, GroupWise::Busy, "contact_busy_overlay", + i18n( "Busy" ), i18n( "&Busy" ), Kopete::OnlineStatusManager::Busy, Kopete::OnlineStatusManager::HasAwayMessage ), + groupwiseAway ( Kopete::OnlineStatus::Away, 20, this, GroupWise::Away, "contact_away_overlay", + i18n( "Away" ), i18n( "&Away" ), Kopete::OnlineStatusManager::Away, Kopete::OnlineStatusManager::HasAwayMessage ), + groupwiseAwayIdle ( Kopete::OnlineStatus::Away, 15, this, GroupWise::AwayIdle, "contact_away_overlay", + i18n( "Idle" ), "FIXME: Make groupwiseAwayIdle unselectable", Kopete::OnlineStatusManager::Idle, + Kopete::OnlineStatusManager::HideFromMenu ), + groupwiseAppearOffline( Kopete::OnlineStatus::Invisible, 2, this, 98, "contact_invisible_overlay", + i18n( "Appear Offline" ), i18n( "A&ppear Offline" ), Kopete::OnlineStatusManager::Invisible ), +/* initialise Kopete::OnlineStatus used by the protocol, but that are not user selectable */ + groupwiseUnknown ( Kopete::OnlineStatus::Unknown, 25, this, GroupWise::Unknown, "status_unknown", + i18n( "Unknown" ) ), + groupwiseInvalid ( Kopete::OnlineStatus::Unknown, 25, this, GroupWise::Invalid, "status_unknown", + i18n( "Invalid Status" ) ), + groupwiseConnecting ( Kopete::OnlineStatus::Connecting, 25, this, 99, "groupwise_connecting", + i18n( "Connecting" ) ), + propGivenName( Kopete::Global::Properties::self()->firstName() ), + propLastName( Kopete::Global::Properties::self()->lastName() ), + propFullName( Kopete::Global::Properties::self()->fullName() ), + propAwayMessage( Kopete::Global::Properties::self()->awayMessage() ), + propAutoReply( "groupwiseAutoReply", i18n( "Auto Reply Message" ), QString::null, false, false ), + propCN( "groupwiseCommonName", i18n( "Common Name" ), QString::null, true, false ), + propPhoneWork( Kopete::Global::Properties::self()->workPhone() ), + propPhoneMobile( Kopete::Global::Properties::self()->privateMobilePhone() ), + propEmail( Kopete::Global::Properties::self()->emailAddress() ) +{ + // ^^ That is all member initialiser syntax, not broken indentation! + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + + s_protocol = this; + + addAddressBookField( "messaging/groupwise", Kopete::Plugin::MakeIndexField ); +} + +GroupWiseProtocol::~GroupWiseProtocol() +{ +} + +Kopete::Contact *GroupWiseProtocol::deserializeContact( + Kopete::MetaContact *metaContact, const QMap &serializedData, + const QMap &/* addressBookData */) +{ + QString dn = serializedData[ "DN" ]; + QString accountId = serializedData[ "accountId" ]; + QString displayName = serializedData[ "displayName" ]; + int objectId = serializedData[ "objectId" ].toInt(); + int parentId = serializedData[ "parentId" ].toInt(); + int sequence = serializedData[ "sequenceNumber" ].toInt(); + + QDict accounts = Kopete::AccountManager::self()->accounts( this ); + + Kopete::Account *account = accounts[ accountId ]; + if ( !account ) + { + kdDebug(GROUPWISE_DEBUG_GLOBAL) << "Account doesn't exist, skipping" << endl; + return 0; + } + + // FIXME: creating a contact with a userId here + return new GroupWiseContact(account, dn, metaContact, objectId, parentId, sequence ); +} + +AddContactPage * GroupWiseProtocol::createAddContactWidget( QWidget *parent, Kopete::Account * account ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "Creating Add Contact Page" << endl; + return new GroupWiseAddContactPage( account, parent, "addcontactpage"); +} + +KopeteEditAccountWidget * GroupWiseProtocol::createEditAccountWidget( Kopete::Account *account, QWidget *parent ) +{ + kdDebug(GROUPWISE_DEBUG_GLOBAL) << "Creating Edit Account Page" << endl; + return new GroupWiseEditAccountWidget( parent, account ); +} + +Kopete::Account *GroupWiseProtocol::createNewAccount( const QString &accountId ) +{ + return new GroupWiseAccount( this, accountId ); +} + +GroupWiseProtocol *GroupWiseProtocol::protocol() +{ + return s_protocol; +} + +Kopete::OnlineStatus GroupWiseProtocol::gwStatusToKOS( const int gwInternal ) +{ + Kopete::OnlineStatus status; + switch ( gwInternal ) + { + case GroupWise::Unknown: + status = groupwiseUnknown; + break; + case GroupWise::Offline: + status = groupwiseOffline; + break; + case GroupWise::Available: + status = groupwiseAvailable; + break; + case GroupWise::Busy: + status = groupwiseBusy; + break; + case GroupWise::Away: + status = groupwiseAway; + break; + case GroupWise::AwayIdle: + status = groupwiseAwayIdle; + break; + case GroupWise::Invalid: + status = groupwiseInvalid; + break; + default: + status = groupwiseInvalid; + kdWarning( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Got unrecognised status value" << gwInternal << endl; + } + return status; +} + +QString GroupWiseProtocol::rtfizeText( const QString & plain ) +{ + // transcode a utf-8 encoded string into an rtf string + // iterate through the input string converting each char into the equivalent rtf + // of single-byte characters with first byte =< 0x7f (127), { } \ are escaped. \n are converted into \par , the rest are appended verbatim + // of multi-byte UTF-8 characters 2 to 6 bytes long (with first byte > 0x7f), these are recoded as 32 bit values, escaped as \u? strings + + // vanilla RTF "envelope" that doesn't say much but causes other clients to accept the message + QString rtfTemplate = QString::fromLatin1("{\\rtf1\\ansi\n" + "{\\fonttbl{\\f0\\fnil Unknown;}}\n" + "{\\colortbl ;\\red0\\green0\\blue0;}\n" + "\\uc1\\cf1\\f0\\fs18 %1\\par\n}"); + QString outputText; // output text + QCString plainUtf8 = plain.utf8(); // encoded as UTF8, because that's what this encoding algorithm, taken from Gaim's Novell plugin + uint index = 0; // current char to transcode + while ( index < plainUtf8.length() ) + { + Q_UINT8 current = plainUtf8.data()[ index ]; + if ( current <= 0x7F ) + { + switch ( current ) + { + case '{': + case '}': + case '\\': + outputText.append( QString( "\\%1" ).arg( QChar( current ) ) ); + break; + case '\n': + outputText.append( "\\par " ); + break; + default: + outputText.append( QChar( current ) ); + break; + } + ++index; + } + else + { + Q_UINT32 ucs4Char; + int bytesEncoded; + QString escapedUnicodeChar; + if ( current <= 0xDF ) + { + ucs4Char = (( plainUtf8.data()[ index ] & 0x001F) << 6) | + ( plainUtf8.data()[ index+1 ] & 0x003F); + bytesEncoded = 2; + } + else if ( current <= 0xEF ) + { + ucs4Char = (( plainUtf8.data()[ index ] & 0x000F) << 12) | + (( plainUtf8.data()[ index+1 ] & 0x003F) << 6) | + ( plainUtf8.data()[ index+2 ] & 0x003F); + bytesEncoded = 3; + } + else if ( current <= 0xF7 ) + { + ucs4Char = (( plainUtf8.data()[ index ] & 0x0007) << 18) | + (( plainUtf8.data()[ index+1 ] & 0x003F) << 12) | + (( plainUtf8.data()[ index+2 ] & 0x003F) << 6) | + ( plainUtf8.data()[ index+3 ] & 0x003F); + bytesEncoded = 4; + } + else if ( current <= 0xFB ) + { + ucs4Char = (( plainUtf8.data()[ index ] & 0x0003) << 24 ) | + (( plainUtf8.data()[ index+1 ] & 0x003F) << 18) | + (( plainUtf8.data()[ index+2 ] & 0x003F) << 12) | + (( plainUtf8.data()[ index+3 ] & 0x003F) << 6) | + ( plainUtf8.data()[ index+4 ] & 0x003F); + bytesEncoded = 5; + } + else if ( current <= 0xFD ) + { + ucs4Char = (( plainUtf8.data()[ index ] & 0x0001) << 30 ) | + (( plainUtf8.data()[ index+1 ] & 0x003F) << 24) | + (( plainUtf8.data()[ index+2 ] & 0x003F) << 18) | + (( plainUtf8.data()[ index+3 ] & 0x003F) << 12) | + (( plainUtf8.data()[ index+4 ] & 0x003F) << 6) | + ( plainUtf8.data()[ index+5 ] & 0x003F); + bytesEncoded = 6; + } + else + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "bogus utf-8 lead byte: 0x" << QTextStream::hex << current << endl; + ucs4Char = 0x003F; + bytesEncoded = 1; + } + index += bytesEncoded; + escapedUnicodeChar = QString("\\u%1?").arg( ucs4Char ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "unicode escaped char: " << escapedUnicodeChar << endl; + outputText.append( escapedUnicodeChar ); + } + } + return rtfTemplate.arg( outputText ); +} + +QString GroupWiseProtocol::dnToDotted( const QString & dn ) +{ + QRegExp rx("[a-zA-Z]*=(.*)$", false ); + if( !dn.find( '=' ) ) // if it's not a DN, return it unprocessed + return dn; + + // split the dn into elements + QStringList elements = QStringList::split( ',', dn ); + // remove the key, keep the value + for ( QStringList::Iterator it = elements.begin(); it != elements.end(); ++it ) + { + if ( rx.search( *it ) != -1 ) + *it = rx.cap( 1 ); + } + QString dotted = elements.join( "." ); + // reassemble as dotted + + return dotted; +} +#include "gwprotocol.moc" diff --git a/kopete/protocols/groupwise/gwprotocol.h b/kopete/protocols/groupwise/gwprotocol.h new file mode 100644 index 00000000..c3271f18 --- /dev/null +++ b/kopete/protocols/groupwise/gwprotocol.h @@ -0,0 +1,112 @@ +/* + gwprotocol.h - Kopete GroupWise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Testbed + Copyright (c) 2003 by Will Stephenson + rtfizeTest from nm_rtfize_text, from Gaim src/protocols/novell/nmuser.c + Copyright (c) 2004 Novell, Inc. All Rights Reserved + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#ifndef TESTBEDPROTOCOL_H +#define TESTBEDPROTOCOL_H + +#include +#include "kopetecontactproperty.h" +#include "kopeteonlinestatus.h" + +/** + * Encapsulates the generic actions associated with this protocol + * @author Will Stephenson + */ +class GroupWiseProtocol : public Kopete::Protocol +{ + Q_OBJECT +public: + GroupWiseProtocol(QObject *parent, const char *name, const QStringList &args); + ~GroupWiseProtocol(); + /** + * Convert the serialised data back into a GroupWiseContact and add this + * to its Kopete::MetaContact + */ + virtual Kopete::Contact *deserializeContact( + Kopete::MetaContact *metaContact, + const QMap< QString, QString > & serializedData, + const QMap< QString, QString > & addressBookData + ); + /** + * Generate the widget needed to add GroupWiseContacts + */ + virtual AddContactPage * createAddContactWidget( QWidget *parent, Kopete::Account *account ); + /** + * Generate the widget needed to add/edit accounts for this protocol + */ + virtual KopeteEditAccountWidget * createEditAccountWidget( Kopete::Account *account, QWidget *parent ); + /** + * Generate a GroupWiseAccount + */ + virtual Kopete::Account * createNewAccount( const QString &accountId ); + /** + * Access the instance of this protocol + */ + static GroupWiseProtocol *protocol(); + /** + * Transform a GroupWise internal status into a Kopete::OnlineStatus + */ + Kopete::OnlineStatus gwStatusToKOS( const int gwInternal ); + /** + * Wrap unformatted text in RTF formatting so that other GroupWise clients will display it + * @param plain unformatted text + * @return RTF text (in UCS-4 encoding) + */ + QString rtfizeText( const QString & plain ); + /** + * Convert full DNs to dotted-untyped format + * Assumes the DN is normalised - comma separated, no spaces between elements + * eg cn=wstephenson,o=suse becomes wstephenson.suse + */ + static QString dnToDotted( const QString & dn ); + /** + * Online statuses used for contacts' presence + */ + const Kopete::OnlineStatus groupwiseOffline; + const Kopete::OnlineStatus groupwiseAvailable; + const Kopete::OnlineStatus groupwiseBusy; + const Kopete::OnlineStatus groupwiseAway; + const Kopete::OnlineStatus groupwiseAwayIdle; + const Kopete::OnlineStatus groupwiseAppearOffline; + const Kopete::OnlineStatus groupwiseUnknown; + const Kopete::OnlineStatus groupwiseInvalid; + const Kopete::OnlineStatus groupwiseConnecting; + + /** + * Contact properties + */ + const Kopete::ContactPropertyTmpl propGivenName; + const Kopete::ContactPropertyTmpl propLastName; + const Kopete::ContactPropertyTmpl propFullName; + const Kopete::ContactPropertyTmpl propAwayMessage; + const Kopete::ContactPropertyTmpl propAutoReply; + const Kopete::ContactPropertyTmpl propCN; + const Kopete::ContactPropertyTmpl propPhoneWork; + const Kopete::ContactPropertyTmpl propPhoneMobile; + const Kopete::ContactPropertyTmpl propEmail; + + +protected: + static GroupWiseProtocol *s_protocol; +}; + +#endif diff --git a/kopete/protocols/groupwise/icons/Makefile.am b/kopete/protocols/groupwise/icons/Makefile.am new file mode 100644 index 00000000..224eb420 --- /dev/null +++ b/kopete/protocols/groupwise/icons/Makefile.am @@ -0,0 +1,3 @@ +kopeteicondir = $(kde_datadir)/kopete/icons +kopeteicon_ICON = AUTO + diff --git a/kopete/protocols/groupwise/icons/cr16-action-groupwise_away.png b/kopete/protocols/groupwise/icons/cr16-action-groupwise_away.png new file mode 100644 index 00000000..43d03896 Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr16-action-groupwise_away.png differ diff --git a/kopete/protocols/groupwise/icons/cr16-action-groupwise_busy.png b/kopete/protocols/groupwise/icons/cr16-action-groupwise_busy.png new file mode 100644 index 00000000..1ddd82b6 Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr16-action-groupwise_busy.png differ diff --git a/kopete/protocols/groupwise/icons/cr16-action-groupwise_connecting.mng b/kopete/protocols/groupwise/icons/cr16-action-groupwise_connecting.mng new file mode 100644 index 00000000..e2a10ead Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr16-action-groupwise_connecting.mng differ diff --git a/kopete/protocols/groupwise/icons/cr16-action-groupwise_invisible.png b/kopete/protocols/groupwise/icons/cr16-action-groupwise_invisible.png new file mode 100644 index 00000000..ee9b37f1 Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr16-action-groupwise_invisible.png differ diff --git a/kopete/protocols/groupwise/icons/cr16-action-groupwise_online.png b/kopete/protocols/groupwise/icons/cr16-action-groupwise_online.png new file mode 100644 index 00000000..87dcf0dc Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr16-action-groupwise_online.png differ diff --git a/kopete/protocols/groupwise/icons/cr16-action-logging.png b/kopete/protocols/groupwise/icons/cr16-action-logging.png new file mode 100644 index 00000000..3d7b72fb Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr16-action-logging.png differ diff --git a/kopete/protocols/groupwise/icons/cr16-app-groupwise_protocol.png b/kopete/protocols/groupwise/icons/cr16-app-groupwise_protocol.png new file mode 100644 index 00000000..87dcf0dc Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr16-app-groupwise_protocol.png differ diff --git a/kopete/protocols/groupwise/icons/cr22-action-logging.png b/kopete/protocols/groupwise/icons/cr22-action-logging.png new file mode 100644 index 00000000..270ce62d Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr22-action-logging.png differ diff --git a/kopete/protocols/groupwise/icons/cr22-app-groupwise_protocol.png b/kopete/protocols/groupwise/icons/cr22-app-groupwise_protocol.png new file mode 100644 index 00000000..b2217573 Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr22-app-groupwise_protocol.png differ diff --git a/kopete/protocols/groupwise/icons/cr32-action-logging.png b/kopete/protocols/groupwise/icons/cr32-action-logging.png new file mode 100644 index 00000000..0c228e11 Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr32-action-logging.png differ diff --git a/kopete/protocols/groupwise/icons/cr32-app-groupwise_protocol.png b/kopete/protocols/groupwise/icons/cr32-app-groupwise_protocol.png new file mode 100644 index 00000000..20c6764e Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr32-app-groupwise_protocol.png differ diff --git a/kopete/protocols/groupwise/icons/cr48-action-logging.png b/kopete/protocols/groupwise/icons/cr48-action-logging.png new file mode 100644 index 00000000..0d348816 Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr48-action-logging.png differ diff --git a/kopete/protocols/groupwise/icons/cr48-app-groupwise_protocol.png b/kopete/protocols/groupwise/icons/cr48-app-groupwise_protocol.png new file mode 100644 index 00000000..cf209bc6 Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr48-app-groupwise_protocol.png differ diff --git a/kopete/protocols/groupwise/icons/cr64-action-logging.png b/kopete/protocols/groupwise/icons/cr64-action-logging.png new file mode 100644 index 00000000..7bb0ef56 Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr64-action-logging.png differ diff --git a/kopete/protocols/groupwise/icons/cr64-app-groupwise_protocol.png b/kopete/protocols/groupwise/icons/cr64-app-groupwise_protocol.png new file mode 100644 index 00000000..75b7c634 Binary files /dev/null and b/kopete/protocols/groupwise/icons/cr64-app-groupwise_protocol.png differ diff --git a/kopete/protocols/groupwise/kopete_groupwise.desktop b/kopete/protocols/groupwise/kopete_groupwise.desktop new file mode 100644 index 00000000..6a692cf4 --- /dev/null +++ b/kopete/protocols/groupwise/kopete_groupwise.desktop @@ -0,0 +1,60 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=groupwise_protocol +ServiceTypes=Kopete/Protocol +X-KDE-Library=kopete_groupwise +X-Kopete-Messaging-Protocol=messaging/groupwise +X-KDE-PluginInfo-Author=Will Stephenson +X-KDE-PluginInfo-Email=will@stevello.free-online.co.uk +X-KDE-PluginInfo-Name=kopete_groupwise +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Protocols +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=GroupWise +Name[bn]=গà§à¦°à§à¦ª-ওয়াইজ +Name[fa]=گروه هوشیار +Name[ne]=समूहगत +Name[ta]=கà¯à®´à¯ படி +Name[tr]=Akıllı Gruplama +Comment=Novell GroupWise Messenger +Comment[bn]=নোভেল গà§à¦°à§à¦ª-ওয়াইজ বারà§à¦¤à¦¾à¦¬à¦¾à¦¹à¦• +Comment[cs]=Novell GroupWise komunikátor +Comment[de]=Novell GroupWise-Nachrichtendienst +Comment[es]=Mensajería GroupWise de Novell +Comment[fa]=پیام‌رسان GroupWise ناول +Comment[fi]=Novell GroupWise -viestijä +Comment[fr]=Messagerie GroupWise Novell +Comment[he]=תוכנת ×”×ž×¡×¨×™× ×©×œ נובל GroupWise +Comment[is]=Novell GroupWise skilaboðaþjónustan +Comment[it]=Messaggistica per GroupWise di Novell +Comment[ja]=Novell GroupWise メッセンジャー +Comment[ka]= Novell GroupWise მყისიერი შეტყáƒáƒ‘ინებების მáƒáƒªáƒœáƒ” +Comment[kk]=Novell GroupWise хабарлаÑуы +Comment[km]=កម្មវិធី​ផ្ញើ​សារ​របស់​ណូវែល - GroupWise +Comment[lt]=Novell GroupWise žinuÄių klientas +Comment[mk]=ГлаÑник за Novell GroupWise +Comment[nb]=Novell GroupWise meldingsprogram +Comment[nds]=Novell GroupWise-Kortnarichtendeenst +Comment[ne]=नोभेल समूहगत मेसेनà¥à¤œà¤° +Comment[nl]=Protocol voor Novell GroupWise Messenger +Comment[nn]=Lynmeldingsprogrammet Novell GroupWise +Comment[pl]=Komunikator Novell GroupWise +Comment[pt_BR]=Mensageiro do GroupWise da Novell +Comment[ru]=Программа обмена ÑообщениÑми Novell GroupWise +Comment[se]=Novell GroupWise-Å¡leaÄ‘gadieđáhusprográmma +Comment[sl]=SporoÄilnik za Novell GroupWise +Comment[sr]=Novell-ов глаÑник GroupWise +Comment[sr@Latn]=Novell-ov glasnik GroupWise +Comment[sv]=Novell GroupWise-meddelandeklient +Comment[ta]=Novell GroupWise உடனடி தூதர௠+Comment[tg]=Пайёмбари Novell GroupWise +Comment[tr]=Novell Akıllı Grup Ä°letiÅŸimi +Comment[uk]=Програма обміну повідомленнÑми Novell GroupWise +Comment[uz]=Novell GroupWise mesenjer +Comment[uz@cyrillic]=Novell GroupWise меÑенжер +Comment[zh_CN]=Novell GroupWise 信使 +Comment[zh_TW]=Novell GroupWise å³æ™‚è¨Šæ¯ diff --git a/kopete/protocols/groupwise/libgroupwise/Makefile.am b/kopete/protocols/groupwise/libgroupwise/Makefile.am new file mode 100644 index 00000000..2df23a01 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/Makefile.am @@ -0,0 +1,40 @@ +INCLUDES = -I$(top_srcdir)/kopete/protocols/groupwise/libgroupwise/qca/src \ + -I$(top_srcdir)/kopete/protocols/groupwise \ + $(all_includes) +METASOURCES = AUTO + +AM_CFLAGS = -DUSE_TLSHANDLER + +CLEANFILES = securestream.moc + +SUBDIRS = qca tasks + +KDE_OPTIONS = nofinal + +noinst_HEADERS = connector.h tlshandler.h qcatlshandler.h bytestream.h \ + gwclientstream.h securestream.h stream.h coreprotocol.h gwfield.h gwerror.h \ + usertransfer.h eventtransfer.h transfer.h request.h requestfactory.h client.h task.h \ + safedelete.h response.h rtf2html.h userdetailsmanager.h eventprotocol.h \ + inputprotocolbase.h responseprotocol.h privacymanager.h gwchatrooms.h chatroommanager.h + +noinst_LTLIBRARIES = libgroupwise.la libgwtest.la +libgroupwise_la_COMPILE_FIRST = securestream.moc +libgroupwise_la_SOURCES = bytestream.cpp chatroommanager.cpp client.cpp \ + connector.cpp coreprotocol.cpp eventprotocol.cpp eventtransfer.cpp gwclientstream.cpp \ + gwerror.cpp gwfield.cpp gwglobal.cpp inputprotocolbase.cpp privacymanager.cpp \ + qcatlshandler.cpp request.cpp requestfactory.cpp response.cpp responseprotocol.cpp rtf.cc \ + safedelete.cpp securestream.cpp stream.cpp task.cpp tlshandler.cpp transfer.cpp \ + transferbase.cpp userdetailsmanager.cpp usertransfer.cpp +libgroupwise_la_LDFLAGS = -no-undefined $(all_libraries) +libgroupwise_la_LIBADD = tasks/libgroupwise_tasks.la -lqt-mt qca/src/libqca.la + +tests_COMPILE_FIRST = libgroupwise.la libgwtest.la + +libgwtest_la_SOURCES = coreprotocol.cpp eventtransfer.cpp \ + gwfield.cpp request.cpp requestfactory.cpp transfer.cpp usertransfer.cpp \ + client.cpp task.cpp safedelete.cpp gwclientstream.cpp qcatlshandler.cpp \ + stream.cpp tlshandler.cpp response.cpp connector.cpp securestream.cpp \ + bytestream.cpp +libgwtest_la_LDFLAGS = $(all_libraries) -no-undefined +libgwtest_la_LIBADD = qca/src/libqca.la \ + $(top_builddir)/kopete/protocols/groupwise/libgroupwise/tasks/libgroupwise_tasks.la -lqt-mt diff --git a/kopete/protocols/groupwise/libgroupwise/bytestream.cpp b/kopete/protocols/groupwise/libgroupwise/bytestream.cpp new file mode 100644 index 00000000..328c4a20 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/bytestream.cpp @@ -0,0 +1,269 @@ +/* + * bytestream.cpp - base class for bytestreams + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"bytestream.h" + +// CS_NAMESPACE_BEGIN + +//! \class ByteStream bytestream.h +//! \brief Base class for "bytestreams" +//! +//! This class provides a basic framework for a "bytestream", here defined +//! as a bi-directional, asynchronous pipe of data. It can be used to create +//! several different kinds of bytestream-applications, such as a console or +//! TCP connection, or something more abstract like a security layer or tunnel, +//! all with the same interface. The provided functions make creating such +//! classes simpler. ByteStream is a pure-virtual class, so you do not use it +//! on its own, but instead through a subclass such as \a BSocket. +//! +//! The signals connectionClosed(), delayedCloseFinished(), readyRead(), +//! bytesWritten(), and error() serve the exact same function as those from +//! QSocket. +//! +//! The simplest way to create a ByteStream is to reimplement isOpen(), close(), +//! and tryWrite(). Call appendRead() whenever you want to make data available for +//! reading. ByteStream will take care of the buffers with regards to the caller, +//! and will call tryWrite() when the write buffer gains data. It will be your +//! job to call tryWrite() whenever it is acceptable to write more data to +//! the underlying system. +//! +//! If you need more advanced control, reimplement read(), write(), bytesAvailable(), +//! and/or bytesToWrite() as necessary. +//! +//! Use appendRead(), appendWrite(), takeRead(), and takeWrite() to modify the +//! buffers. If you have more advanced requirements, the buffers can be accessed +//! directly with readBuf() and writeBuf(). +//! +//! Also available are the static convenience functions ByteStream::appendArray() +//! and ByteStream::takeArray(), which make dealing with byte queues very easy. + +class ByteStream::Private +{ +public: + Private() {} + + QByteArray readBuf, writeBuf; +}; + +//! +//! Constructs a ByteStream object with parent \a parent. +ByteStream::ByteStream(QObject *parent) +:QObject(parent) +{ + d = new Private; +} + +//! +//! Destroys the object and frees allocated resources. +ByteStream::~ByteStream() +{ + delete d; +} + +//! +//! Returns TRUE if the stream is open, meaning that you can write to it. +bool ByteStream::isOpen() const +{ + return false; +} + +//! +//! Closes the stream. If there is data in the write buffer then it will be +//! written before actually closing the stream. Once all data has been written, +//! the delayedCloseFinished() signal will be emitted. +//! \sa delayedCloseFinished() +void ByteStream::close() +{ +} + +//! +//! Writes array \a a to the stream. +void ByteStream::write(const QByteArray &a) +{ + if(!isOpen()) + return; + + bool doWrite = bytesToWrite() == 0 ? true: false; + appendWrite(a); + if(doWrite) + tryWrite(); +} + +//! +//! Reads bytes \a bytes of data from the stream and returns them as an array. If \a bytes is 0, then +//! \a read will return all available data. +QByteArray ByteStream::read(int bytes) +{ + return takeRead(bytes); +} + +//! +//! Returns the number of bytes available for reading. +int ByteStream::bytesAvailable() const +{ + return d->readBuf.size(); +} + +//! +//! Returns the number of bytes that are waiting to be written. +int ByteStream::bytesToWrite() const +{ + return d->writeBuf.size(); +} + +//! +//! Writes string \a cs to the stream. +void ByteStream::write(const QCString &cs) +{ + QByteArray block(cs.length()); + memcpy(block.data(), cs.data(), block.size()); + write(block); +} + +//! +//! Clears the read buffer. +void ByteStream::clearReadBuffer() +{ + d->readBuf.resize(0); +} + +//! +//! Clears the write buffer. +void ByteStream::clearWriteBuffer() +{ + d->writeBuf.resize(0); +} + +//! +//! Appends \a block to the end of the read buffer. +void ByteStream::appendRead(const QByteArray &block) +{ + appendArray(&d->readBuf, block); +} + +//! +//! Appends \a block to the end of the write buffer. +void ByteStream::appendWrite(const QByteArray &block) +{ + appendArray(&d->writeBuf, block); +} + +//! +//! Returns \a size bytes from the start of the read buffer. +//! If \a size is 0, then all available data will be returned. +//! If \a del is TRUE, then the bytes are also removed. +QByteArray ByteStream::takeRead(int size, bool del) +{ + return takeArray(&d->readBuf, size, del); +} + +//! +//! Returns \a size bytes from the start of the write buffer. +//! If \a size is 0, then all available data will be returned. +//! If \a del is TRUE, then the bytes are also removed. +QByteArray ByteStream::takeWrite(int size, bool del) +{ + return takeArray(&d->writeBuf, size, del); +} + +//! +//! Returns a reference to the read buffer. +QByteArray & ByteStream::readBuf() +{ + return d->readBuf; +} + +//! +//! Returns a reference to the write buffer. +QByteArray & ByteStream::writeBuf() +{ + return d->writeBuf; +} + +//! +//! Attempts to try and write some bytes from the write buffer, and returns the number +//! successfully written or -1 on error. The default implementation returns -1. +int ByteStream::tryWrite() +{ + return -1; +} + +//! +//! Append array \a b to the end of the array pointed to by \a a. +void ByteStream::appendArray(QByteArray *a, const QByteArray &b) +{ + int oldsize = a->size(); + a->resize(oldsize + b.size()); + memcpy(a->data() + oldsize, b.data(), b.size()); +} + +//! +//! Returns \a size bytes from the start of the array pointed to by \a from. +//! If \a size is 0, then all available data will be returned. +//! If \a del is TRUE, then the bytes are also removed. +QByteArray ByteStream::takeArray(QByteArray *from, int size, bool del) +{ + QByteArray a; + if(size == 0) { + a = from->copy(); + if(del) + from->resize(0); + } + else { + if(size > (int)from->size()) + size = from->size(); + a.resize(size); + char *r = from->data(); + memcpy(a.data(), r, size); + if(del) { + int newsize = from->size()-size; + memmove(r, r+size, newsize); + from->resize(newsize); + } + } + return a; +} + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); + void bytesWritten(int); + void error(int); + +//! \fn void ByteStream::connectionClosed() +//! This signal is emitted when the remote end of the stream closes. + +//! \fn void ByteStream::delayedCloseFinished() +//! This signal is emitted when all pending data has been written to the stream +//! after an attempt to close. + +//! \fn void ByteStream::readyRead() +//! This signal is emitted when data is available to be read. + +//! \fn void ByteStream::bytesWritten(int x); +//! This signal is emitted when data has been successfully written to the stream. +//! \a x is the number of bytes written. + +//! \fn void ByteStream::error(int code) +//! This signal is emitted when an error occurs in the stream. The reason for +//! error is indicated by \a code. + +// CS_NAMESPACE_END + +#include "bytestream.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/bytestream.h b/kopete/protocols/groupwise/libgroupwise/bytestream.h new file mode 100644 index 00000000..c33b3976 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/bytestream.h @@ -0,0 +1,78 @@ +/* + * bytestream.h - base class for bytestreams + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_BYTESTREAM_H +#define CS_BYTESTREAM_H + +#include +#include + +// CS_NAMESPACE_BEGIN + +// CS_EXPORT_BEGIN +class ByteStream : public QObject +{ + Q_OBJECT +public: + enum Error { ErrRead, ErrWrite, ErrCustom = 10 }; + ByteStream(QObject *parent=0); + virtual ~ByteStream()=0; + + virtual bool isOpen() const; + virtual void close(); + virtual void write(const QByteArray &); + virtual QByteArray read(int bytes=0); + virtual int bytesAvailable() const; + virtual int bytesToWrite() const; + + void write(const QCString &); + + static void appendArray(QByteArray *a, const QByteArray &b); + static QByteArray takeArray(QByteArray *from, int size=0, bool del=true); + +signals: + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); + void bytesWritten(int); + void error(int); + +protected: + void clearReadBuffer(); + void clearWriteBuffer(); + void appendRead(const QByteArray &); + void appendWrite(const QByteArray &); + QByteArray takeRead(int size=0, bool del=true); + QByteArray takeWrite(int size=0, bool del=true); + QByteArray & readBuf(); + QByteArray & writeBuf(); + virtual int tryWrite(); + +private: +//! \if _hide_doc_ + class Private; + Private *d; +//! \endif +}; +// CS_EXPORT_END + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/chatroommanager.cpp b/kopete/protocols/groupwise/libgroupwise/chatroommanager.cpp new file mode 100644 index 00000000..adbb66de --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/chatroommanager.cpp @@ -0,0 +1,140 @@ +/* + Kopete Groupwise Protocol + chatroommanager.cpp - tracks our knowledge of server side chatrooms + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include + +#include + +#include "client.h" +#include "tasks/chatcountstask.h" +#include "tasks/chatpropertiestask.h" +#include "tasks/searchchattask.h" + +#include "chatroommanager.h" + +ChatroomManager::ChatroomManager( Client * parent, const char *name) + : QObject(parent, name), m_client( parent ), m_replace( false ) +{ +} + +ChatroomManager::~ChatroomManager() +{ +} + +void ChatroomManager::updateRooms() +{ + getChatrooms( !m_rooms.isEmpty() ); +} + +GroupWise::ChatroomMap ChatroomManager::rooms() +{ + return m_rooms; +} + +void ChatroomManager::getChatrooms( bool refresh ) +{ + m_replace = !refresh; + SearchChatTask * sct = new SearchChatTask( m_client->rootTask() ); + sct->search( ( refresh ? SearchChatTask::SinceLastSearch : SearchChatTask::FetchAll ) ); + connect( sct, SIGNAL( finished() ), SLOT( slotGotChatroomList() ) ); + sct->go( true ); +} + +void ChatroomManager::slotGotChatroomList() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + SearchChatTask * sct = (SearchChatTask *)sender(); + if ( sct ) + { + if ( m_replace ) + m_rooms.clear(); + + QValueList roomsFound = sct->results(); + QValueList::Iterator it = roomsFound.begin(); + const QValueList::Iterator end = roomsFound.end(); + for ( ; it != end; ++it ) + { + GroupWise::Chatroom c( *it ); + m_rooms.insert( c.displayName, c ); + } + } + emit updated(); +} + +void ChatroomManager::updateCounts() +{ + ChatCountsTask * cct = new ChatCountsTask( m_client->rootTask() ); + connect( cct, SIGNAL( finished() ), SLOT( slotGotChatCounts() ) ); + cct->go( true ); +} + +void ChatroomManager::slotGotChatCounts() +{ + ChatCountsTask * cct = (ChatCountsTask *)sender(); + if ( cct ) + { + QMap< QString, int > newCounts = cct->results(); + QMap< QString, int >::iterator it = newCounts.begin(); + const QMap< QString, int >::iterator end = newCounts.end(); + + for ( ; it != end; ++it ) + if ( m_rooms.contains( it.key() ) ) + m_rooms[ it.key() ].participantsCount = it.data(); + } + emit updated(); +} + +void ChatroomManager::requestProperties( const QString & displayName ) +{ + if ( 0 /*m_rooms.contains( displayName ) */) + emit gotProperties( m_rooms[ displayName ] ); + else + { + ChatPropertiesTask * cpt = new ChatPropertiesTask( m_client->rootTask() ); + cpt->setChat( displayName ); + connect ( cpt, SIGNAL( finished() ), SLOT( slotGotChatProperties() ) ); + cpt->go( true ); + } +} + +void ChatroomManager::slotGotChatProperties() +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + ChatPropertiesTask * cpt = (ChatPropertiesTask *)sender(); + if ( cpt ) + { + Chatroom room = m_rooms[ cpt->m_chat ]; + room.displayName = cpt->m_chat; + room.ownerDN = cpt->m_ownerDn; + room.description = cpt->m_description; + room.disclaimer = cpt->m_disclaimer; + room.query = cpt->m_query; + room.archive = ( cpt->m_archive == "0" ); + room.maxUsers = cpt->m_maxUsers.toInt(); + room.topic = cpt->m_topic; + room.creatorDN = cpt->m_creatorDn; + room.createdOn = cpt->m_creationTime; + room.acl = cpt->m_aclEntries; + room.chatRights = cpt->m_rights; + m_rooms.insert( room.displayName, room ); + emit gotProperties( room ); + } +} + +#include "chatroommanager.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/chatroommanager.h b/kopete/protocols/groupwise/libgroupwise/chatroommanager.h new file mode 100644 index 00000000..4d0e888b --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/chatroommanager.h @@ -0,0 +1,66 @@ +/* + Kopete Groupwise Protocol + chatroommanager.h - tracks our knowledge of server side chatrooms + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef CHATROOMMANAGER_H +#define CHATROOMMANAGER_H + +#include + +#include "gwchatrooms.h" + +class Client; + +/** + * Keeps a record of the server side chatrooms + * @author SUSE Linux Products GmbH + */ +class ChatroomManager : public QObject +{ + Q_OBJECT + public: + ChatroomManager( Client * client, const char *name = 0); + ~ChatroomManager(); + GroupWise::ChatroomMap rooms(); + void requestProperties( const QString & displayName ); + void updateRooms(); + void updateCounts(); + signals: + void gotProperties( const GroupWise::Chatroom & ); + void updated(); + protected: + void getChatrooms( bool refresh ); + protected slots: + /** + * Used to initialise the list of chatrooms in response to a SearchChatTask. + */ + void slotGotChatroomList(); + /** + * Used to update the user counts of chatrooms. + */ + void slotGotChatCounts(); + /** + * Get the properties of a specific room. + */ + void slotGotChatProperties(); + private: + Client * m_client; + GroupWise::ChatroomMap m_rooms; + bool m_replace; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/client.cpp b/kopete/protocols/groupwise/libgroupwise/client.cpp new file mode 100644 index 00000000..274a9ea8 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/client.cpp @@ -0,0 +1,541 @@ +/* + Kopete Groupwise Protocol + client.cpp - The main interface for the Groupwise protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + (c) 2008 Novell, Inc. + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include + +#include "chatroommanager.h" +#include "gwclientstream.h" +#include "privacymanager.h" +#include "requestfactory.h" +#include "task.h" +#include "tasks/conferencetask.h" +#include "tasks/connectiontask.h" +#include "tasks/createconferencetask.h" +#include "tasks/getdetailstask.h" +#include "tasks/getstatustask.h" +#include "tasks/joinconferencetask.h" +#include "tasks/keepalivetask.h" +#include "tasks/leaveconferencetask.h" +#include "tasks/logintask.h" +#include "tasks/rejectinvitetask.h" +#include "tasks/sendinvitetask.h" +#include "tasks/sendmessagetask.h" +#include "tasks/setstatustask.h" +#include "tasks/statustask.h" +#include "tasks/typingtask.h" +#include "userdetailsmanager.h" +#include "client.h" + +class Client::ClientPrivate +{ +public: + ClientPrivate() {} + + ClientStream *stream; + int id_seed; + Task *root; + QString host, user, userDN, pass; + QString osname, tzname, clientName, clientVersion; + uint port; +/* int tzoffset;*/ + bool active; + RequestFactory * requestFactory; + ChatroomManager * chatroomMgr; + UserDetailsManager * userDetailsMgr; + PrivacyManager * privacyMgr; + uint protocolVersion; + QValueList customStatuses; + QTimer * keepAliveTimer; +}; + +Client::Client(QObject *par, uint protocolVersion ) +:QObject(par, "groupwiseclient") +{ + d = new ClientPrivate; +/* d->tzoffset = 0;*/ + d->active = false; + d->osname = "N/A"; + d->clientName = "N/A"; + d->clientVersion = "0.0"; + d->id_seed = 0xaaaa; + d->root = new Task(this, true); + d->chatroomMgr = 0; + d->requestFactory = new RequestFactory; + d->userDetailsMgr = new UserDetailsManager( this, "userdetailsmgr" ); + d->privacyMgr = new PrivacyManager( this, "privacymgr" ); + d->stream = 0; + d->protocolVersion = protocolVersion; + // Sends regular keepalives so the server knows we are still running + d->keepAliveTimer = new QTimer( this ); + connect( d->keepAliveTimer, SIGNAL( timeout() ), SLOT( sendKeepAlive() ) ); +} + +Client::~Client() +{ + delete d->root; + delete d->requestFactory; + delete d->userDetailsMgr; + delete d; +} + +void Client::connectToServer( ClientStream *s, const NovellDN &server, bool auth ) +{ + d->stream = s; + //connect(d->stream, SIGNAL(connected()), SLOT(streamConnected())); + //connect(d->stream, SIGNAL(handshaken()), SLOT(streamHandshaken())); + connect(d->stream, SIGNAL(error(int)), SLOT(streamError(int))); + //connect(d->stream, SIGNAL(sslCertificateReady(const QSSLCert &)), SLOT(streamSSLCertificateReady(const QSSLCert &))); + connect(d->stream, SIGNAL(readyRead()), SLOT(streamReadyRead())); + //connect(d->stream, SIGNAL(closeFinished()), SLOT(streamCloseFinished())); + + d->stream->connectToServer(server, auth); +} + +void Client::setOSName(const QString &name) +{ + d->osname = name; +} + +void Client::setClientName(const QString &s) +{ + d->clientName = s; +} + +void Client::setClientVersion(const QString &s) +{ + d->clientVersion = s; +} + +void Client::start( const QString &host, const uint port, const QString &userId, const QString &pass ) +{ + d->host = host; + d->port = port; + d->user = userId; + d->pass = pass; + + initialiseEventTasks(); + + LoginTask * login = new LoginTask( d->root ); + + connect( login, SIGNAL( gotMyself( const GroupWise::ContactDetails & ) ), + this, SIGNAL( accountDetailsReceived( const GroupWise::ContactDetails & ) ) ); + + connect( login, SIGNAL( gotFolder( const FolderItem & ) ), + this, SIGNAL( folderReceived( const FolderItem & ) ) ); + + connect( login, SIGNAL( gotContact( const ContactItem & ) ), + this, SIGNAL( contactReceived( const ContactItem & ) ) ); + + connect( login, SIGNAL( gotContactUserDetails( const GroupWise::ContactDetails & ) ), + this, SIGNAL( contactUserDetailsReceived( const GroupWise::ContactDetails & ) ) ) ; + + connect( login, SIGNAL( gotPrivacySettings( bool, bool, const QStringList &, const QStringList & ) ), + privacyManager(), SLOT( slotGotPrivacySettings( bool, bool, const QStringList &, const QStringList & ) ) ); + + connect( login, SIGNAL( gotCustomStatus( const GroupWise::CustomStatus & ) ), + SLOT( lt_gotCustomStatus( const GroupWise::CustomStatus & ) ) ); + + connect( login, SIGNAL( gotKeepalivePeriod( int ) ), SLOT( lt_gotKeepalivePeriod( int ) ) ); + + connect( login, SIGNAL( finished() ), this, SLOT( lt_loginFinished() ) ); + + login->initialise(); + login->go( true ); + + d->active = true; +} + +void Client::close() +{ + debug( "Client::close()" ); + d->keepAliveTimer->stop(); + if(d->stream) { + d->stream->disconnect(this); + d->stream->close(); + d->stream = 0; + } +} + +QString Client::host() +{ + return d->host; +} + +int Client::port() +{ + return d->port; +} + +QValueList Client::customStatuses() +{ + return d->customStatuses; +} + +void Client::initialiseEventTasks() +{ + // The StatusTask handles incoming status changes + StatusTask * st = new StatusTask( d->root ); // FIXME - add an additional EventRoot? + connect( st, SIGNAL( gotStatus( const QString &, Q_UINT16, const QString & ) ), SIGNAL( statusReceived( const QString &, Q_UINT16, const QString & ) ) ); + // The ConferenceTask handles incoming conference events, messages, joins, leaves, etc + ConferenceTask * ct = new ConferenceTask( d->root ); + connect( ct, SIGNAL( message( const ConferenceEvent & ) ), SLOT( ct_messageReceived( const ConferenceEvent & ) ) ); + connect( ct, SIGNAL( typing( const ConferenceEvent & ) ), SIGNAL( contactTyping( const ConferenceEvent & ) ) ); + connect( ct, SIGNAL( notTyping( const ConferenceEvent & ) ), SIGNAL( contactNotTyping( const ConferenceEvent & ) ) ); + connect( ct, SIGNAL( joined( const ConferenceEvent & ) ), SIGNAL( conferenceJoinNotifyReceived( const ConferenceEvent & ) ) ); + connect( ct, SIGNAL( left( const ConferenceEvent & ) ), SIGNAL( conferenceLeft( const ConferenceEvent & ) ) ); + connect( ct, SIGNAL( invited( const ConferenceEvent & ) ), SIGNAL( invitationReceived( const ConferenceEvent & ) ) ); + connect( ct, SIGNAL( otherInvited( const ConferenceEvent & ) ), SIGNAL( inviteNotifyReceived( const ConferenceEvent & ) ) ); + connect( ct, SIGNAL( invitationDeclined( const ConferenceEvent & ) ), SIGNAL( invitationDeclined( const ConferenceEvent & ) ) ); + connect( ct, SIGNAL( closed( const ConferenceEvent & ) ), SIGNAL( conferenceClosed( const ConferenceEvent & ) ) ); + connect( ct, SIGNAL( autoReply( const ConferenceEvent & ) ), SIGNAL( autoReplyReceived( const ConferenceEvent & ) ) ); + connect( ct, SIGNAL( broadcast( const ConferenceEvent & ) ), SIGNAL( broadcastReceived( const ConferenceEvent & ) ) ); + connect( ct, SIGNAL( systemBroadcast( const ConferenceEvent & ) ), SIGNAL( systemBroadcastReceived( const ConferenceEvent & ) ) ); + + + // The ConnectionTask handles incoming connection events + ConnectionTask* cont = new ConnectionTask( d->root ); + connect( cont, SIGNAL( connectedElsewhere() ), SIGNAL( connectedElsewhere() ) ); +} + +void Client::setStatus( GroupWise::Status status, const QString & reason, const QString & autoReply ) +{ + debug( QString("Setting status to %1").arg( status ) );; + SetStatusTask * sst = new SetStatusTask( d->root ); + sst->status( status, reason, autoReply ); + connect( sst, SIGNAL( finished() ), this, SLOT( sst_statusChanged() ) ); + sst->go( true ); + // TODO: set status change in progress flag +} + +void Client::requestStatus( const QString & userDN ) +{ + GetStatusTask * gst = new GetStatusTask( d->root ); + gst->userDN( userDN ); + connect( gst, SIGNAL( gotStatus( const QString &, Q_UINT16, const QString & ) ), SIGNAL( statusReceived( const QString &, Q_UINT16, const QString & ) ) ); + gst->go( true ); +} + +void Client::sendMessage( const QStringList & addresseeDNs, const OutgoingMessage & message ) +{ + SendMessageTask * smt = new SendMessageTask( d->root ); + smt->message( addresseeDNs, message ); + connect( smt, SIGNAL( finished() ), SLOT( smt_messageSent() ) ); + smt->go( true ); +} + +void Client::sendTyping( const GroupWise::ConferenceGuid & conferenceGuid, bool typing ) +{ + TypingTask * tt = new TypingTask( d->root ); + tt->typing( conferenceGuid, typing ); + tt->go( true ); +} + +void Client::createConference( const int clientId ) +{ + QStringList dummy; + createConference( clientId, dummy ); +} + +void Client::createConference( const int clientId, const QStringList & participants ) +{ + CreateConferenceTask * cct = new CreateConferenceTask( d->root ); + cct->conference( clientId, participants ); + connect( cct, SIGNAL( finished() ), SLOT( cct_conferenceCreated() ) ); + cct->go( true ); +} +void Client::requestDetails( const QStringList & userDNs ) +{ + GetDetailsTask * gdt = new GetDetailsTask( d->root ); + gdt->userDNs( userDNs ); + connect( gdt, SIGNAL( gotContactUserDetails( const GroupWise::ContactDetails & ) ), + this, SIGNAL( contactUserDetailsReceived( const GroupWise::ContactDetails & ) ) ); + gdt->go( true ); +} + +void Client::joinConference( const GroupWise::ConferenceGuid & guid ) +{ + JoinConferenceTask * jct = new JoinConferenceTask( d->root ); + jct->join( guid ); + connect( jct, SIGNAL( finished() ), SLOT( jct_joinConfCompleted() ) ); + jct->go( true ); +} + +void Client::rejectInvitation( const GroupWise::ConferenceGuid & guid ) +{ + RejectInviteTask * rit = new RejectInviteTask ( d->root ); + rit->reject( guid ); + // we don't do anything with the results of this task + rit->go( true ); +} + +void Client::leaveConference( const GroupWise::ConferenceGuid & guid ) +{ + LeaveConferenceTask * lct = new LeaveConferenceTask( d->root ); + lct->leave( guid ); + //connect( lct, SIGNAL( finished() ), SLOT( lct_leftConference() ) ); + lct->go( true ); +} + +void Client::sendInvitation( const GroupWise::ConferenceGuid & guid, const QString & dn, const GroupWise::OutgoingMessage & message ) +{ + SendInviteTask * sit = new SendInviteTask( d->root ); + QStringList invitees( dn ); + sit->invite( guid, dn, message ); + sit->go( true ); +} + +// SLOTS // +void Client::streamError( int error ) +{ + debug( QString( "CLIENT ERROR (Error %1)" ).arg( error ) ); +} + +void Client::streamReadyRead() +{ + debug( "CLIENT STREAM READY READ" ); + // take the incoming transfer and distribute it to the task tree + Transfer * transfer = d->stream->read(); + distribute( transfer ); +} + +void Client::lt_loginFinished() +{ + debug( "Client::lt_loginFinished()" ); + const LoginTask * lt = (LoginTask *)sender(); + if ( lt->success() ) + { + debug( "Client::lt_loginFinished() LOGIN SUCCEEDED" ); + // set our initial status + SetStatusTask * sst = new SetStatusTask( d->root ); + sst->status( GroupWise::Available, QString::null, QString::null ); + sst->go( true ); + emit loggedIn(); + // fetch details for any privacy list items that aren't in our contact list. + // There is a chicken-and-egg case regarding this: We need the privacy before reading the contact list so + // blocked contacts are shown as blocked. But we need not fetch user details for the privacy lists + // before reading the contact list, as many privacy items' details are already in the contact list + privacyManager()->getDetailsForPrivacyLists(); + } + else + { + debug( "Client::lt_loginFinished() LOGIN FAILED" ); + emit loginFailed(); + } + // otherwise client should disconnect and signal failure that way?? +} + +void Client::sst_statusChanged() +{ + const SetStatusTask * sst = (SetStatusTask *)sender(); + if ( sst->success() ) + { + emit ourStatusChanged( sst->requestedStatus(), sst->awayMessage(), sst->autoReply() ); + } +} + +void Client::ct_messageReceived( const ConferenceEvent & messageEvent ) +{ + debug( "parsing received message's RTF" ); + ConferenceEvent transformedEvent = messageEvent; + RTF2HTML parser; + QString rtf = messageEvent.message; + if ( !rtf.isEmpty() ) + transformedEvent.message = parser.Parse( rtf.latin1(), "" ); + + // fixes for RTF to HTML conversion problems + // we can drop these once the server reenables the sending of unformatted text + // redundant linebreak at the end of the message + QRegExp rx("
    $"); + transformedEvent.message.replace( rx, "" ); + // missing linebreak after first line of an encrypted message + QRegExp ry("-----BEGIN PGP MESSAGE----- "); + transformedEvent.message.replace( ry, "-----BEGIN PGP MESSAGE-----
    " ); + + emit messageReceived( transformedEvent ); +} + +void Client::cct_conferenceCreated() +{ + const CreateConferenceTask * cct = ( CreateConferenceTask * )sender(); + if ( cct->success() ) + { + emit conferenceCreated( cct->clientConfId(), cct->conferenceGUID() ); + } + else + { + emit conferenceCreationFailed( cct->clientConfId(), cct->statusCode() ); + } +} + +void Client::jct_joinConfCompleted() +{ + const JoinConferenceTask * jct = ( JoinConferenceTask * )sender(); +#ifdef LIBGW_DEBUG + debug( QString( "Joined conference %1, participants are: " ).arg( jct->guid() ) ); + QStringList parts = jct->participants(); + for ( QStringList::Iterator it = parts.begin(); it != parts.end(); ++it ) + debug( QString( " - %1" ).arg(*it) ); + debug( "invitees are: " ); + QStringList invitees = jct->invitees(); + for ( QStringList::Iterator it = invitees.begin(); it != invitees.end(); ++it ) + debug( QString( " - %1" ).arg(*it) ); +#endif + emit conferenceJoined( jct->guid(), jct->participants(), jct->invitees() ); +} + +void Client::lt_gotCustomStatus( const GroupWise::CustomStatus & custom ) +{ + d->customStatuses.append( custom ); +} + +// INTERNALS // + +QString Client::userId() +{ + return d->user; +} + +void Client::setUserDN( const QString & userDN ) +{ + d->userDN = userDN; +} + +QString Client::userDN() +{ + return d->userDN; +} + +QString Client::password() +{ + return d->pass; +} + +QString Client::userAgent() +{ + return QString::fromLatin1( "%1/%2 (%3)" ).arg( d->clientName, d->clientVersion, d->osname ); +} + +QCString Client::ipAddress() +{ + // TODO: remove hardcoding + return "10.10.11.103"; +} + +void Client::distribute( Transfer * transfer ) +{ + if( !rootTask()->take( transfer ) ) + debug( "CLIENT: root task refused transfer" ); + // at this point the transfer is no longer needed + delete transfer; +} + +void Client::send( Request * request ) +{ + debug( "CLIENT::send()" ); + if( !d->stream ) + { + debug( "CLIENT - NO STREAM TO SEND ON!"); + return; + } +// QString out = request.toString(); +// debug(QString("Client: outgoing: [\n%1]\n").arg(out)); +// xmlOutgoing(out); + + d->stream->write( request ); +} + +void Client::debug( const QString &str ) +{ +#ifdef LIBGW_USE_KDEBUG + kdDebug( GROUPWISE_DEBUG_LIBGW ) << "debug: " << str << endl; +#else + qDebug( "CLIENT: %s\n", str.ascii() ); +#endif +} + +QString Client::genUniqueId() +{ + QString s; + s.sprintf("a%x", d->id_seed); + d->id_seed += 0x10; + return s; +} + +PrivacyManager * Client::privacyManager() +{ + return d->privacyMgr; +} + +RequestFactory * Client::requestFactory() +{ + return d->requestFactory; +} + +UserDetailsManager * Client::userDetailsManager() +{ + return d->userDetailsMgr; +} + +Task * Client::rootTask() +{ + return d->root; +} + +uint Client::protocolVersion() const +{ + return d->protocolVersion; +} + +ChatroomManager * Client::chatroomManager() +{ + if ( !d->chatroomMgr ) + d->chatroomMgr = new ChatroomManager( this, "chatroommgr" ); + return d->chatroomMgr; +} + +void Client::lt_gotKeepalivePeriod( int period ) +{ + d->keepAliveTimer->start( period * 60 * 1000 ); +} + +void Client::sendKeepAlive() +{ + KeepAliveTask * kat = new KeepAliveTask( d->root ); + kat->setup(); + kat->go( true ); +} + +void Client::smt_messageSent() +{ + const SendMessageTask * smt = ( SendMessageTask * )sender(); + if ( smt->success() ) + { + debug( "message sent OK" ); + } + else + { + debug( "message sending failed!" ); + emit messageSendingFailed(); + } +} + +#include "client.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/client.h b/kopete/protocols/groupwise/libgroupwise/client.h new file mode 100644 index 00000000..102b0c27 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/client.h @@ -0,0 +1,401 @@ +/* + Kopete Groupwise Protocol + client.h - The main interface for the Groupwise protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef LIBGW_CLIENT_H +#define LIBGW_CLIENT_H + +#include + +#include "gwclientstream.h" +#include "gwerror.h" +#include "rtf2html.h" +#include "transfer.h" + +class ChatroomManager; +class PrivacyManager; +class RequestFactory; +class UserDetailsManager; +class Task; + +using namespace GroupWise; + +class Client : public QObject +{ +Q_OBJECT + + public: + + /************* + EXTERNAL API + *************/ + + Client( QObject *parent = 0, uint protocolVersion = 2 ); + ~Client(); + void setOSName( const QString &name ); + void setClientName( const QString &s ); + void setClientVersion( const QString &s ); + void setUserDN( const QString & userDN ); + /** + * Start a connection to the server using the supplied @ref ClientStream. + * This is only a transport layer connection. + * Needed for protocol action P1. + * @param s initialised client stream to use for the connection. + * @param server the server to connect to - but this is also set on the connector used to construct the clientstream?? + * @param auth needed for jabber protocol layer only? + */ + void connectToServer( ClientStream *s, const NovellDN &server, bool auth=true ); + + /** + * Login to the GroupWise server using the supplied credentials + * Protocol action P1, needed for all + * @param host - probably could obtain this back from the connector - used for outgoing tasks to determine destination + * @param user The user name to log in as. +fd * @param password + */ + void start( const QString &host, const uint port, const QString &userId, const QString &pass ); + + /** + * Logout and disconnect + * Protocol action P4 void distribute(const QDomElement &); + + */ + void close(); + + /** + * Accessors needed for login + */ + QString host(); + int port(); + + /** + * Set the user's presence on the server + * Protocol action P2 + * @param status status enum + * @param reason custom status name for away statuses + * @param autoReply auto reply message for use in this status + */ + void setStatus( GroupWise::Status status, const QString & reason, const QString & autoReply ); + + /** + * Send a message + * Protocol action P10 + * @param message contains the text and the recipient. + */ + void sendMessage( const QStringList & addresseeDNs, const OutgoingMessage & message ); + + /** + * Send a typing notification + * Protocol action P11 + * @param conference The conference where the typing took place. + * @param typing True if the user is now typing, false otherwise. + */ + void sendTyping( const ConferenceGuid & conferenceGuid, bool typing ); + + /** + * Request details for one or more users, for example, if we receive a message from someone who isn't on our contact list + * @param userDNs A list of one or more user's DNs to fetch details for + */ + void requestDetails( const QStringList & userDNs ); + + /** + * Request the status of a single user, for example, if they have messaged us and are not on our contact list + */ + void requestStatus( const QString & userDN ); + + /** + * Add a contact to the contact list + * Protocol action P12 + */ + + /** + * Remove a contact from the contact list + * Protocol action P13 + */ + + /** + * Instantiate a conference on the server + * Protocol action P5 + */ + void createConference( const int clientId ); + /** + * Overloaded version of the above to create a conference with a supplied list of invitees + */ + void createConference( const int clientId, const QStringList & participants ); + + /** + * Join a conference, accepting an invitation + * Protocol action P7 + */ + void joinConference( const ConferenceGuid & guid ); + + /** + * Reject a conference invitation + * Protocol action P8 + */ + void rejectInvitation( const ConferenceGuid & guid ); + + /** + * Leave a conference, notifying + * Protocol action P6 + */ + void leaveConference( const ConferenceGuid & guid ); + + /** + * Send an invitation to join a conference + * Protocol action P9 + */ + void sendInvitation( const ConferenceGuid & guid, const QString & dn, const GroupWise::OutgoingMessage & message ); + /************* + INTERNAL (FOR USE BY TASKS) METHODS + *************/ + /** + * Send an outgoing request to the server + */ + void send( Request *request ); + /** + * Print a debug statement + */ + void debug( const QString &str ); + + /** + * The protocol version of the Client + */ + uint protocolVersion() const; + /** + * Generate a unique ID for Tasks. + */ + QString genUniqueId(); + + /** + * The current user's user ID + */ + QString userId(); + + /** + * The current user's DN + */ + QString userDN(); + /** + * The current user's password + */ + QString password(); + + /** + * User agent details for this host + */ + QString userAgent(); + + /** + * Host's IP address + */ + QCString ipAddress(); + + /** + * Obtain the list of custom statuses stored on the server + */ + QValueList customStatuses(); + + /** + * Get a reference to the RequestFactory for this Client. + * Used by Tasks to generate Requests with an ascending sequence of transaction IDs + * for this connection + */ + RequestFactory * requestFactory(); + + /** + * Get a reference to the ChatroomManager for this Client. + * This is constructed the first time this function is called. Used to manipulate chat rooms on the server. + */ + ChatroomManager * chatroomManager(); + + /** + * Get a reference to the UserDetailsManager for this Client. + * Used to track known user details and issue new details requests + */ + UserDetailsManager * userDetailsManager(); + /** + * Get a reference to the PrivacyManager for this Client. + * Used to track and manipulate server side privacy settings + */ + PrivacyManager * privacyManager(); + /** + * Access the root Task for this client, so tasks may be added to it. + */ + Task* rootTask(); + + signals: + /** CONNECTION EVENTS */ + /** + * Notifies that the login process has succeeded. + */ + void loggedIn(); + void loginFailed(); + /** + * Notifies tasks and account so they can react properly + */ + void disconnected(); + /** + * We were disconnected because we connected elsewhere + */ + void connectedElsewhere(); + + /** STATUS AND METADATA EVENTS */ + /** + * We've just got the user's own details from the server. + */ + void accountDetailsReceived( const GroupWise::ContactDetails & ); + /** + * We've just found out about a folder from the server. + */ + void folderReceived( const FolderItem & ); + /** + * We've just found out about a folder from the server. + */ + void contactReceived( const ContactItem & ); + /** + * We've just received a contact's metadata from the server. + */ + void contactUserDetailsReceived( const GroupWise::ContactDetails & ); + /** + * A remote contact changed status + */ + void statusReceived( const QString & contactId, Q_UINT16 status, const QString & statusText ); + /** + * Our status changed on the server + */ + void ourStatusChanged( GroupWise::Status status, const QString & statusText, const QString & autoReply ); + + /** CONFERENCE (MANAGEMENT) EVENTS */ + /** + * Notify that we've just received a message. Sender may not be on our contact list + */ + void messageReceived( const ConferenceEvent & ); + /** + * Notify that we've received an auto reply. This Event does not contain any rtf, unlike a normal message. + */ + void autoReplyReceived( const ConferenceEvent & ); + /** + * A conference was successfully created on the server + */ + void conferenceCreated( const int clientId, const GroupWise::ConferenceGuid & guid ); + /** + * A third party was invited to join a chat. They may not be on our contact list. + */ + void inviteNotifyReceived( const ConferenceEvent & ); + /** + * We were invited to join a chat. The inviter may not be on our contact list + */ + void invitationReceived( const ConferenceEvent & ); + /** + * Someone joined a chat. They may not be on our contact list if it is a group chat + * and they were invited to join the chat prior to our being invited to join and joining + */ + void conferenceJoinNotifyReceived( const ConferenceEvent & ); + /** + * Someone left a conference. This may close a conference, see @ref conferenceClosed. + */ + void conferenceLeft( const ConferenceEvent & ); + /** + * Someone declined an invitation to join a conference. This may close a conference, see @ref conferenceClosed. + */ + void invitationDeclined( const ConferenceEvent & ); + /** + * A conference was closed by the server. This occurs if we are the only participant and there + * are no outstanding invitations. + */ + void conferenceClosed( const ConferenceEvent & ); + /** + * We joined a conference. + */ + void conferenceJoined( const GroupWise::ConferenceGuid &, const QStringList &, const QStringList & ); + /** + * We received an "is typing" event in a conference + */ + void contactTyping( const ConferenceEvent & ); + /** + * We received an "is not typing event" in a conference + */ + void contactNotTyping( const ConferenceEvent & ); + /** + * An attempt to create a conference failed. + */ + void conferenceCreationFailed( const int clientId, const int error ); + /** + * We received a temporary contact related to a conference + */ + void tempContactReceived( const GroupWise::ContactDetails & ); + /** + * We received a broadcast message + */ + void broadcastReceived( const ConferenceEvent & ); + /** + * We received a system broadcast + */ + void systemBroadcastReceived ( const ConferenceEvent & ); + /** CONTACT LIST MANAGEMENT EVENTS */ + /** TBD! */ + void messageSendingFailed(); + protected: + /** + * Instantiate all the event handling tasks + */ + void initialiseEventTasks(); + protected slots: + // INTERNAL, FOR USE BY TASKS' finished() SIGNALS // + void lt_loginFinished(); + void sst_statusChanged(); + void cct_conferenceCreated(); + /** + * Transforms an RTF message into an HTML message and emits messageReceived() + */ + void ct_messageReceived( const ConferenceEvent & ); + void jct_joinConfCompleted(); + /** + * Receive a custom status during login and record it + */ + void lt_gotCustomStatus( const GroupWise::CustomStatus & ); + /** + * Notify us of the keepalive period contained in the login response + */ + void lt_gotKeepalivePeriod( int ); + + /** + * Used by the client stream to notify errors to upper layers. + */ + void streamError( int error ); + + /** + * The client stream has data ready to read. + */ + void streamReadyRead(); + + /** + * sendout a 'ping' keepalive message so that the server does not disconnect us + */ + void sendKeepAlive(); + void smt_messageSent(); + + private: + void distribute( Transfer *transfer ); + class ClientPrivate; + ClientPrivate* d; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/connector.cpp b/kopete/protocols/groupwise/libgroupwise/connector.cpp new file mode 100644 index 00000000..55e866ee --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/connector.cpp @@ -0,0 +1,73 @@ +/* + Kopete Groupwise Protocol + connector.cpp - the Groupwise socket connector + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "connector.h" + +Connector::Connector(QObject *parent) +:QObject(parent) +{ + setUseSSL(false); + setPeerAddressNone(); +} + +Connector::~Connector() +{ +} + +bool Connector::useSSL() const +{ + return ssl; +} + +bool Connector::havePeerAddress() const +{ + return haveaddr; +} + +QHostAddress Connector::peerAddress() const +{ + return addr; +} + +Q_UINT16 Connector::peerPort() const +{ + return port; +} + +void Connector::setUseSSL(bool b) +{ + ssl = b; +} + +void Connector::setPeerAddressNone() +{ + haveaddr = false; + addr = QHostAddress(); + port = 0; +} + +void Connector::setPeerAddress(const QHostAddress &_addr, Q_UINT16 _port) +{ + haveaddr = true; + addr = _addr; + port = _port; +} + +#include "connector.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/connector.h b/kopete/protocols/groupwise/libgroupwise/connector.h new file mode 100644 index 00000000..1cae3426 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/connector.h @@ -0,0 +1,61 @@ +/* + Kopete Groupwise Protocol + connector.h - the Groupwise socket connector + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef LIBGW_CONNECTOR_H +#define LIBGW_CONNECTOR_H + +#include +#include + +class ByteStream; + +class Connector : public QObject +{ + Q_OBJECT +public: + Connector(QObject *parent=0); + virtual ~Connector(); + + virtual void connectToServer(const QString &server)=0; + virtual ByteStream *stream() const=0; + virtual void done()=0; + + bool useSSL() const; + bool havePeerAddress() const; + QHostAddress peerAddress() const; + Q_UINT16 peerPort() const; + +signals: + void connected(); + void error(); + +protected: + void setUseSSL(bool b); + void setPeerAddressNone(); + void setPeerAddress(const QHostAddress &addr, Q_UINT16 port); + +private: + bool ssl; + bool haveaddr; + QHostAddress addr; + Q_UINT16 port; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/coreprotocol.cpp b/kopete/protocols/groupwise/libgroupwise/coreprotocol.cpp new file mode 100644 index 00000000..1e15287e --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/coreprotocol.cpp @@ -0,0 +1,507 @@ +/* + Kopete Groupwise Protocol + coreprotocol.h- the core GroupWise protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + url_escape_string from Gaim src/protocols/novell/nmconn.c + Copyright (c) 2004 Novell, Inc. All Rights Reserved + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#include +#include +#include + + +#include +#include + +#include "eventprotocol.h" +#include "eventtransfer.h" +#include "gwerror.h" +#include "gwfield.h" +#include "request.h" +#include "response.h" +#include "responseprotocol.h" + +#include "coreprotocol.h" + +#define NO_ESCAPE(ch) ((ch == 0x20) || (ch >= 0x30 && ch <= 0x39) || (ch >= 0x41 && ch <= 0x5a) || (ch >= 0x61 && ch <= 0x7a)) +#define GW_URLVAR_TAG "&tag=" +#define GW_URLVAR_METHOD "&cmd=" +#define GW_URLVAR_VAL "&val=" +#define GW_URLVAR_TYPE "&type=" + +//#define GW_COREPROTOCOL_DEBUG + +QCString +url_escape_string( const char *src) +{ + uint escape = 0; + const char *p; + uint q; + //char *encoded = NULL; + int ch; + + static const char hex_table[17] = "0123456789abcdef"; + + if (src == NULL) { + return QCString(); + } + + /* Find number of chars to escape */ + for (p = src; *p != '\0'; p++) { + ch = (uchar) *p; + if (!NO_ESCAPE(ch)) { + escape++; + } + } + + QCString encoded((p - src) + (escape * 2) + 1); + + /* Escape the string */ + for (p = src, q = 0; *p != '\0'; p++) { + ch = (uchar) * p; + if (NO_ESCAPE(ch)) { + if (ch != 0x20) { + encoded.insert( q, (char)ch ); + q++; + } else { + encoded.insert( q, '+' ); + q++; + } + } else { + encoded.insert( q, '%' ); + q++; + + encoded.insert( q, hex_table[ch >> 4] ); + q++; + + encoded.insert( q, hex_table[ch & 15] ); + q++; + } + } + encoded.insert( q, '\0' ); + + return encoded; +} + +CoreProtocol::CoreProtocol() : QObject() +{ + m_eventProtocol = new EventProtocol( this, "eventprotocol" ); + m_responseProtocol = new ResponseProtocol( this, "responseprotocol" ); +} + +CoreProtocol::~CoreProtocol() +{ +} + +int CoreProtocol::state() +{ + return m_state; +} + +void CoreProtocol::debug( const QString &str ) +{ +#ifdef LIBGW_USE_KDEBUG + kdDebug( 14191 ) << "debug: " << str << endl; +#else + qDebug( "GW RAW PROTO: %s\n", str.ascii() ); +#endif +} + +void CoreProtocol::addIncomingData( const QByteArray & incomingBytes ) +{ +// store locally + debug( "CoreProtocol::addIncomingData()"); + int oldsize = m_in.size(); + m_in.resize( oldsize + incomingBytes.size() ); + memcpy( m_in.data() + oldsize, incomingBytes.data(), incomingBytes.size() ); + m_state = Available; + // convert every event in the chunk to a Transfer, signalling it back to the clientstream + + int parsedBytes = 0; + int transferCount = 0; + // while there is data left in the input buffer, and we are able to parse something out of it + while ( m_in.size() && ( parsedBytes = wireToTransfer( m_in ) ) ) + { + transferCount++; + debug( QString( "CoreProtocol::addIncomingData() - parsed transfer #%1 in chunk" ).arg( transferCount ) ); + int size = m_in.size(); + if ( parsedBytes < size ) + { + debug( " - more data in chunk!" ); + // copy the unparsed bytes into a new qbytearray and replace m_in with that + QByteArray remainder( size - parsedBytes ); + memcpy( remainder.data(), m_in.data() + parsedBytes, remainder.size() ); + m_in = remainder; + } + else + m_in.truncate( 0 ); + } + if ( m_state == NeedMore ) + debug( " - message was incomplete, waiting for more..." ); + if ( m_eventProtocol->state() == EventProtocol::OutOfSync ) + { + debug( " - protocol thinks it's out of sync, discarding the rest of the buffer and hoping the server regains sync soon..." ); + m_in.truncate( 0 ); + } + debug( " - done processing chunk" ); +} + +Transfer* CoreProtocol::incomingTransfer() +{ + debug( "CoreProtocol::incomingTransfer()" ); + if ( m_state == Available ) + { + debug( " - got a transfer" ); + m_state = NoData; + return m_inTransfer; + m_inTransfer = 0; + } + else + { + debug( " - no milk today." ); + return 0; + } +} + +void cp_dump( const QByteArray &bytes ) +{ +#ifdef LIBGW_DEBUG + CoreProtocol::debug( QString( "contains: %1 bytes" ).arg( bytes.count() ) ); + for ( uint i = 0; i < bytes.count(); ++i ) + { + printf( "%02x ", bytes[ i ] ); + } + printf( "\n" ); +#else + Q_UNUSED(bytes); +#endif +} + +void CoreProtocol::outgoingTransfer( Request* outgoing ) +{ + debug( "CoreProtocol::outgoingTransfer()" ); + // Convert the outgoing data into wire format + Request * request = dynamic_cast( outgoing ); + Field::FieldList fields = request->fields(); + if ( fields.isEmpty() ) + { + debug( " CoreProtocol::outgoingTransfer: Transfer contained no fields, it must be a ping."); +/* m_error = NMERR_BAD_PARM; + return;*/ + } + // Append field containing transaction id + Field::SingleField * fld = new Field::SingleField( NM_A_SZ_TRANSACTION_ID, NMFIELD_METHOD_VALID, + 0, NMFIELD_TYPE_UTF8, request->transactionId() ); + fields.append( fld ); + + // convert to QByteArray + QByteArray bytesOut; + QTextStream dout( bytesOut, IO_WriteOnly ); + dout.setEncoding( QTextStream::Latin1 ); + //dout.setByteOrder( QDataStream::LittleEndian ); + + // strip out any embedded host and port in the command string + QCString command, host, port; + if ( request->command().section( ':', 0, 0 ) == "login" ) + { + command = "login"; + host = request->command().section( ':', 1, 1 ).ascii(); + port = request->command().section( ':', 2, 2 ).ascii(); + debug( QString( "Host: %1 Port: %2" ).arg( host.data() ).arg( port.data() ) ); + } + else + command = request->command().ascii(); + + // add the POST + dout << "POST /"; + dout << command; + dout << " HTTP/1.0\r\n"; + + // if a login, add Host arg + if ( command == "login" ) + { + dout << "Host: "; + dout << host; //FIXME: Get this from somewhere else!! + dout << ":" << port << "\r\n\r\n"; + } + else + dout << "\r\n"; + + debug( QString( "data out: %1" ).arg( bytesOut.data() ) ); + + emit outgoingData( bytesOut ); + // now convert + fieldsToWire( fields ); + delete request; + delete fld; + return; +} + +void CoreProtocol::fieldsToWire( Field::FieldList fields, int depth ) +{ + debug( "CoreProtocol::fieldsToWire()"); + int subFieldCount = 0; + + // TODO: consider constructing this as a QStringList and then join()ing it. + Field::FieldListIterator it; + Field::FieldListIterator end = fields.end(); + Field::FieldBase* field; + for ( it = fields.begin(); it != end ; ++it ) + { + field = *it; + //debug( " - writing a field" ); + QByteArray bytesOut; + QDataStream dout( bytesOut, IO_WriteOnly ); + dout.setByteOrder( QDataStream::LittleEndian ); + + // these fields are ignored by Gaim's novell + if ( field->type() == NMFIELD_TYPE_BINARY || field->method() == NMFIELD_METHOD_IGNORE ) + continue; + + // GAIM writes these tags to the secure socket separately - if we can't connect, check here + // NM Protocol 1 writes them in an apparently arbitrary order + // tag + //dout.writeRawBytes( GW_URLVAR_TAG, sizeof( GW_URLVAR_TAG ) ); + //dout << field->tag(); + + // method + //dout.writeRawBytes( GW_URLVAR_METHOD, sizeof( GW_URLVAR_METHOD ) ); + // char methodChar = encode_method( field->method() ); + //dout << (Q_UINT8)methodChar; + + // value + //dout.writeRawBytes( GW_URLVAR_VAL, sizeof( GW_URLVAR_VAL ) ); + + char valString[ NMFIELD_MAX_STR_LENGTH ]; + switch ( field->type() ) + { + case NMFIELD_TYPE_UTF8: // Field contains UTF-8 + case NMFIELD_TYPE_DN: // Field contains a Distinguished Name + { + //debug( " - it's a single string" ); + const Field::SingleField *sField = static_cast( field ); +// QString encoded = KURL::encode_string( sField->value().toString(), 106 /* UTF-8 */); +// encoded.replace( "%20", "+" ); +// dout << encoded.ascii(); + + snprintf( valString, NMFIELD_MAX_STR_LENGTH, "%s", url_escape_string( sField->value().toString().utf8() ).data() ); + //dout << sField->value().toString().ascii(); + break; + } + case NMFIELD_TYPE_ARRAY: // Field contains a field array + case NMFIELD_TYPE_MV: // Field contains a multivalue + { + //debug( " - it's a multi" ); + const Field::MultiField *mField = static_cast( field ); + subFieldCount = mField->fields().count(); // determines if we have a subarray to send after this field + //dout << QString::number( subFieldCount ).ascii(); + snprintf( valString, NMFIELD_MAX_STR_LENGTH, "%u", subFieldCount ); + break; + } + default: // Field contains a numeric value + { + //debug( " - it's a number" ); + const Field::SingleField *sField = static_cast( field ); + //dout << QString::number( sField->value().toInt() ).ascii(); + snprintf( valString, NMFIELD_MAX_STR_LENGTH, "%u", sField->value().toInt() ); + } + } + + // type + //dout.writeRawBytes( GW_URLVAR_TYPE, sizeof( GW_URLVAR_TYPE ) ); + + //dout << QString::number( field->type() ).ascii(); + QCString typeString; + typeString.setNum( field->type() ); + QCString outgoing = GW_URLVAR_TAG + field->tag() + + GW_URLVAR_METHOD + (char)encode_method( field->method() ) + + GW_URLVAR_VAL + (const char *)valString + + GW_URLVAR_TYPE + typeString; + + debug( QString( "CoreProtocol::fieldsToWire - outgoing data: %1" ).arg( outgoing.data() ) ); + dout.writeRawBytes( outgoing.data(), outgoing.length() ); + // write what we have so far, we may be calling this function recursively + //kdDebug( 14999 ) << k_funcinfo << "writing \'" << bout << "\'" << endl; + //debug( " - signalling data" ); + emit outgoingData( bytesOut ); + + // write fields of subarray, if that's what the current field is + if ( subFieldCount > 0 && + ( field->type() == NMFIELD_TYPE_ARRAY || field->type() == NMFIELD_TYPE_MV ) ) + { + const Field::MultiField *mField = static_cast( field ); + fieldsToWire( mField->fields(), depth + 1 ); + } + //debug( " - field done" ); + } + if ( depth == 0 ) // this call to the function was not recursive, so the entire request has been sent at this point + { + // very important, don't send put the \r\n on the wire as a string or it will be preceded with the string length and 0 terminated, which the server reads as a request to disconnect. + QByteArray bytesOut; + QDataStream dout( bytesOut, IO_WriteOnly ); + dout.setByteOrder( QDataStream::LittleEndian ); + dout.writeRawBytes( "\r\n", 2 ); + emit outgoingData( bytesOut ); + debug( "CoreProtocol::fieldsToWire - request completed" ); + } + //debug( " - method done" ); +} + +int CoreProtocol::wireToTransfer( const QByteArray& wire ) +{ + // processing incoming data and reassembling it into transfers + // may be an event or a response + uint bytesParsed = 0; + m_din = new QDataStream( wire, IO_ReadOnly ); + m_din->setByteOrder( QDataStream::LittleEndian ); + + // look at first four bytes and decide what to do with the chunk + Q_UINT32 val; + if ( okToProceed() ) + { + *m_din >> val; + + // if is 'HTTP', it's a Response. PTTH it is after endian conversion + if ( !qstrncmp( (const char *)&val, "HTTP", strlen( "HTTP" ) ) || + !qstrncmp( (const char *)&val, "PTTH", strlen( "PTTH" ) ) + ) { + Transfer * t = m_responseProtocol->parse( wire, bytesParsed ); + if ( t ) + { + m_inTransfer = t; + debug( "CoreProtocol::wireToTransfer() - got a RESPONSE " ); + + m_state = Available; + emit incomingData(); + } + else + bytesParsed = 0; + } + else // otherwise -> Event code + { + debug( QString( "CoreProtocol::wireToTransfer() - looks like an EVENT: %1, length %2" ).arg( val ).arg( wire.size() ) ); + Transfer * t = m_eventProtocol->parse( wire, bytesParsed ); + if ( t ) + { + m_inTransfer = t; + debug( QString( "CoreProtocol::wireToTransfer() - got an EVENT: %1, parsed: %2" ).arg( val ).arg( bytesParsed ) ); + m_state = Available; + emit incomingData(); + } + else + { + debug( "CoreProtocol::wireToTransfer() - EventProtocol was unable to parse it" ); + bytesParsed = 0; + } + } + } + delete m_din; + return bytesParsed; +} + +void CoreProtocol::reset() +{ + m_in.resize( 0 ); +} + +QChar CoreProtocol::encode_method( Q_UINT8 method ) +{ + QChar str; + + switch (method) { + case NMFIELD_METHOD_EQUAL: + str = 'G'; + break; + case NMFIELD_METHOD_UPDATE: + str = 'F'; + break; + case NMFIELD_METHOD_GTE: + str = 'E'; + break; + case NMFIELD_METHOD_LTE: + str = 'D'; + break; + case NMFIELD_METHOD_NE: + str = 'C'; + break; + case NMFIELD_METHOD_EXIST: + str = 'B'; + break; + case NMFIELD_METHOD_NOTEXIST: + str = 'A'; + break; + case NMFIELD_METHOD_SEARCH: + str = '9'; + break; + case NMFIELD_METHOD_MATCHBEGIN: + str = '8'; + break; + case NMFIELD_METHOD_MATCHEND: + str = '7'; + break; + case NMFIELD_METHOD_NOT_ARRAY: + str = '6'; + break; + case NMFIELD_METHOD_OR_ARRAY: + str = '5'; + break; + case NMFIELD_METHOD_AND_ARRAY: + str = '4'; + break; + case NMFIELD_METHOD_DELETE_ALL: + str = '3'; + break; + case NMFIELD_METHOD_DELETE: + str = '2'; + break; + case NMFIELD_METHOD_ADD: + str = '1'; + break; + default: /* NMFIEL D_METHOD_VALID */ + str = '0'; + break; + } + + return str; +} + +void CoreProtocol::slotOutgoingData( const QCString &out ) +{ + debug( QString( "CoreProtocol::slotOutgoingData() %1" ).arg( out ) ); +} + +bool CoreProtocol::okToProceed() +{ + if ( m_din ) + { + if ( m_din->atEnd() ) + { + m_state = NeedMore; + debug( "CoreProtocol::okToProceed() - Server message ended prematurely!" ); + } + else + return true; + } + return false; +} + +#include "coreprotocol.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/coreprotocol.h b/kopete/protocols/groupwise/libgroupwise/coreprotocol.h new file mode 100644 index 00000000..4cd30b88 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/coreprotocol.h @@ -0,0 +1,202 @@ +/* + Kopete Groupwise Protocol + coreprotocol.h- the core GroupWise protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GW_CORE_PROTOCOL_H +#define GW_CORE_PROTOCOL_H + +#include +#include +#include + +#include "gwfield.h" + +class EventProtocol; +class ResponseProtocol; +class Request; +class Transfer; + +/** + * This class handles transforming data between structured high level messages and encoded bytes that are sent + * and received over the network. + * + * 0) FIELD ARRAYS + * --------------- + * This is relevant to both input and output handling. + * Requests (out) and Responses (in) are messages containing, after a HTTP header, a series of 'Fields'. + * A message may contain a flat series of Fields, or each Field may mark the start of a nested array of more Fields. + * In this case the Field's value is the length of the following nested array. + * The length of the top level Field series is not given. The message ends when there are no more Fields expected as part of a nested array, + * and is marked by a terminator. + * The encoding used for Fields differs for Requests and Responses, and is described below. + * + * 1) INPUT + * -------- + * The input functionality is a finite state machine that processes the stream of data from the GroupWise server. + * Since the server may arbitrarily truncate or run together protocol level messages, we buffer the incoming data stream, + * parsing it into individual messages that are removed from the buffer and passed back to the ClientStream, which propagates + * them to higher layers. + * + * Incoming data may be in either of two formats; a Response or an Event. + * All binary data is Little Endian on the network. + * + * 1.1) INPUT MESSAGE 'SPECIES' + * + * 1.1.1) Events + * + * Events are independently occuring notifications generated by the server or by the activity of other users. + * Events are represented on the wire in binary format: + * + * BYTE 1 + * 0 8 6.... + * AAAABBBBCCCCCCCCC....DDDDDDDD..... + * AAAA is a UINT32 giving the type of event + * BBBB is a UINT32 giving the length of the event source, + * CCCC... is the event source, a UTF8 encoded string, which is observed to be zero terminated + * DDDD... is event dependent binary data, which frequently consists of the conference the event relates to, + * conference flags describing the logging, chat security and closed status, and message data. + * + * As the DDDD portion is irregularly structured, it must be processed knowing the semantics of the event type. + * See the @ref EventProtocol documentation. + * + * Event message data is always a UINT32 giving the message length, then a message in RTF format. + * The message length may be zero. + * + * 1.1.2) Responses + * Responses are the server's response to client Requests. Each Request generates one Response. Requests and Responses are regularly structured + * and can be parsed/generated without any knowledge of their content. + * Responses consist of text/line oriented standard HTTP headers, followed by a binary payload. The payload is a series of Fields as described above, + * and the terminator following the last field is a null (0x0) byte. + * + * TODO: Add Field structure format: type, tag, method, flags, and value. see ResponseProtocol::readFields() for reference if this is incomplete. + * + * 1.3) INPUT PROCESSING IMPLEMENTATION + * CoreProtocol input handling operates on an event driven basis. It starts processing when it receives data via @ref addIncomingData(), + * and emits @ref incomingData() as each complete message is parsed in off the wire. + * Each call to addIncomingData() may result in zero or more incomingData() signals + * + * 2) REQUESTS + * ----------- + * The output functionality is an encoding function that transforms outgoing Requests into the wire request format + * - a HTTP POST made up of the request operation type as the path, followed by a series of (repeated) variables that form the arguments. + * Order of the arguments is significant! + * Argument values are URL-encoded with spaces encoded as + rather than %20. + * The terminator used is a CRLF pair ("\r\n"). + * HTTP headers are only used in a login operation, where they contain a Host: hostname:port line. + * Headers are separated from the arguments by a blank line (only CRLF) as usual. + * + * 3) USER MESSAGE BODY TEXT REPRESENTATION + * ----------------------------------- + * Message text sent by users (found in both Requests and Events) is generally formatted as Rich Text Format. + * Text portions of the RTF may be be encoded in + * any of three ways - + * ascii text, + * latin1 as hexadecimal, + * escaped unicode code points (encoded/escaped as \uUNICODEVALUE?, with or without a space between the end of the unicode value and the ? ) + * Outgoing messages may contain rich text, and additionally the plain text encoded as UTF8, but this plain payload is apparently ignored by the server + * + */ +class CoreProtocol : public QObject +{ +Q_OBJECT +public: + enum State { NeedMore, Available, NoData }; + + CoreProtocol(); + + virtual ~CoreProtocol(); + /** + * Debug output + */ + static void debug(const QString &str); + + /** + * Reset the protocol, clear buffers + */ + void reset(); + + /** + * Accept data from the network, and buffer it into a useful message + * @param incomingBytes Raw data in wire format. + */ + void addIncomingData( const QByteArray& incomingBytes ); + + /** + * @return the incoming transfer or 0 if none is available. + */ + Transfer* incomingTransfer(); + + /** + * Convert a request into an outgoing transfer + * emits @ref outgoingData() with each part of the transfer + */ + void outgoingTransfer( Request* outgoing ); + + /** + * Get the state of the protocol + */ + int state(); + +signals: + /** + * Emitted as the core protocol converts fields to wire ready data + */ + void outgoingData( const QByteArray& ); + /** + * Emitted when there is incoming data, parsed into a Transfer + */ + void incomingData(); +protected slots: + /** + * Just a debug method to test emitting to the socket, atm - should go to the ClientStream + */ + void slotOutgoingData( const QCString & ); + +protected: + /** + * Check that there is data to read, and set the protocol's state if there isn't any. + */ + bool okToProceed(); + /** + * Convert incoming wire data into a Transfer object and queue it + * @return number of bytes from the input that were parsed into a Transfer + */ + int wireToTransfer( const QByteArray& wire ); + /** + * Convert fields to a wire representation. Emits outgoingData as each field is written. + * Calls itself recursively to process nested fields, hence + * @param depth Current depth of recursion. Don't use this parameter yourself! + */ + void fieldsToWire( Field::FieldList fields, int depth = 0 ); + /** + * encodes a method number (usually supplied as a #defined symbol) to a char + */ + QChar encode_method( Q_UINT8 method ); +private: + QByteArray m_in; // buffer containing unprocessed bytes we received + QDataStream* m_din; // contains the packet currently being parsed + int m_error; + Transfer* m_inTransfer; // the transfer that is being received + int m_state; // represents the protocol's overall state + EventProtocol* m_eventProtocol; + ResponseProtocol * m_responseProtocol; +}; + +#endif + diff --git a/kopete/protocols/groupwise/libgroupwise/eventprotocol.cpp b/kopete/protocols/groupwise/libgroupwise/eventprotocol.cpp new file mode 100644 index 00000000..05320676 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/eventprotocol.cpp @@ -0,0 +1,216 @@ +/* + Kopete Groupwise Protocol + eventprotocol.cpp - reads the protocol used by GroupWise for signalling Events + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include "gwerror.h" + +#include "eventtransfer.h" +#include "eventprotocol.h" + +using namespace GroupWise; + +EventProtocol::EventProtocol(QObject *parent, const char *name) + : InputProtocolBase(parent, name) +{ +} + +EventProtocol::~EventProtocol() +{ +} + +Transfer * EventProtocol::parse( const QByteArray & wire, uint& bytes ) +{ + m_bytes = 0; + //m_din = new QDataStream( wire, IO_ReadOnly ); + QBuffer inBuf( wire ); + inBuf.open( IO_ReadOnly); + m_din.setDevice( &inBuf ); + m_din.setByteOrder( QDataStream::LittleEndian ); + Q_UINT32 type; + + if ( !okToProceed() ) + { + m_din.unsetDevice(); + return 0; + } + // read the event type + m_din >> type; + m_bytes += sizeof( Q_UINT32 ); + + debug( QString( "EventProtocol::parse() Reading event of type %1" ).arg( type ) ); + if ( type > Stop ) + { + debug( QString ( "EventProtocol::parse() - found unexpected event type %1 - assuming out of sync" ).arg( type ) ); + m_state = OutOfSync; + return 0; + } + + // read the event source + QString source; + if ( !readString( source ) ) + { + m_din.unsetDevice(); + return 0; + } + + // now create an event object + //HACK: lowercased DN + EventTransfer * tentative = new EventTransfer( type, source.lower(), QDateTime::currentDateTime() ); + + // add any additional data depending on the type of event + // Note: if there are any errors in the way the data is read below, we will soon be OutOfSync + QString statusText; + QString guid; + Q_UINT16 status; + Q_UINT32 flags; + QString message; + + switch ( type ) + { + case StatusChange: //103 - STATUS + STATUSTEXT + if ( !okToProceed() ) + { + m_din.unsetDevice(); + return 0; + } + m_din >> status; + m_bytes += sizeof( Q_UINT16 ); + if ( !readString( statusText ) ) + { + m_din.unsetDevice(); + return 0; + } + debug( QString( "got status: %1").arg( status ) ); + tentative->setStatus( status ); + debug( QString( "tentative status: %1").arg( tentative->status() ) ); + tentative->setStatusText( statusText ); + break; + case ConferenceJoined: // 106 - GUID + FLAGS + case ConferenceLeft: // 107 + if ( !readString( guid ) ) + { + m_din.unsetDevice(); + return 0; + } + tentative->setGuid( guid ); + if ( !readFlags( flags ) ) + { + m_din.unsetDevice(); + return 0; + } + tentative->setFlags( flags ); + break; + case UndeliverableStatus: //102 - GUID + case ConferenceClosed: //105 + case ConferenceInviteNotify://118 + case ConferenceReject: //119 + case UserTyping: //112 + case UserNotTyping: //113 + if ( !readString( guid ) ) + { + m_din.unsetDevice(); + return 0; + } + tentative->setGuid( guid ); + break; + case ReceiveAutoReply: //121 - GUID + FLAGS + MESSAGE + case ReceiveMessage: //108 + // guid + if ( !readString( guid ) ) + { + m_din.unsetDevice(); + return 0; + } + tentative->setGuid( guid ); + // flags + if ( !readFlags( flags ) ) + { + m_din.unsetDevice(); + return 0; + } + tentative->setFlags( flags ); + // message + if ( !readString( message ) ) + { + m_din.unsetDevice(); + return 0; + } + tentative->setMessage( message ); + break; + case ConferenceInvite: //117 GUID + MESSAGE + // guid + if ( !readString( guid ) ) + { + m_din.unsetDevice(); + return 0; + } + tentative->setGuid( guid ); + // message + if ( !readString( message ) ) + { + m_din.unsetDevice(); + return 0; + } + tentative->setMessage( message ); + break; + case UserDisconnect: //114 (NOTHING) + case ServerDisconnect: //115 + // nothing else to read + break; + case InvalidRecipient: //101 + case ContactAdd: //104 + case ReceiveFile: //109 + case ConferenceRename: //116 + // unhandled because unhandled in Gaim + break; + /* GW7 */ + case ReceivedBroadcast: //122 + case ReceivedSystemBroadcast: //123 + // message + if ( !readString( message ) ) + { + m_din.unsetDevice(); + return 0; + } + tentative->setMessage( message ); + break; + default: + debug( QString( "EventProtocol::parse() - found unexpected event type %1" ).arg( type ) ); + break; + } + // if we got this far, the parse succeeded, return the Transfer + m_state = Success; + //delete m_din; + bytes = m_bytes; + m_din.unsetDevice(); + return tentative; +} + +bool EventProtocol::readFlags( Q_UINT32 &flags) +{ + if ( okToProceed() ) + { + m_din >> flags; + m_bytes += sizeof( Q_UINT32 ); + return true; + } + return false; +} + +#include "eventprotocol.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/eventprotocol.h b/kopete/protocols/groupwise/libgroupwise/eventprotocol.h new file mode 100644 index 00000000..4f6d94e5 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/eventprotocol.h @@ -0,0 +1,130 @@ +/* + Kopete Groupwise Protocol + eventprotocol.h - reads the protocol used by GroupWise for signalling Events + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GW_EVENTPROTOCOL_H +#define GW_EVENTPROTOCOL_H + +#include "inputprotocolbase.h" + +class EventTransfer; +/** + * This class converts incoming event data into EventTransfer objects. Since it requires knowledge of the binary event format, which + * differs for each event type, it is implemented as a separate class. See also @ref CoreProtocol, which detects event messages in the + * data stream and hands them to this class for processing. + * Event Types + * +@author SUSE AG + Ablauf: + CoreProtocol receives data in addIncomingData, and passes to wireToTransfer. + wireToTransfer detects an event. + Passes whole chunk to EventProtocol ( as QByteArray ) + In to EventProtocol - QByteArray + Returned from EventProtocol - EventTransfer *, bytes read, set state? + EventProtocol tries to parse data into eventTransfer + If not complete, sets state to NeedMore and returns 0 + If complete, returns number of bytes read for the event + If bytes less than length of chunk, CoreProtocol::addIncomingData places the unread bytes back in m_in and calls wireToTransfer again. + if ResponseProtocol or EventProtocol set state to NeedMore, don't call wireToTransfer again. + + What event dependent data does EventTransfer contain? + + What if some events contain EXTRA bytes off the end that we don't know about? Then we will put those bytes back on the buffer, and try and parse those as the start of a new message!!! Need to react SAFELY then. + + What event dependent binary data does each event type contain? + + All Events contain an event code, and a source ( a DN ) + NOTHANDLED indicates that there is no further data and we don't handle events of that type, because they are not sent by the server + NONE indicates there is no further data + STATUSTEXT, GUID, MESSAGE indicate a string encoded in the usual GroupWise binary string encoding: a UINT32 containing the string length in little-endian, followed by the string itself, as UTF-8 encoded unicode. The string length value includes a terminating NUL, so when converting to a QString, subtract one from the string length. + FLAGS contains a UINT32 containing the server's flags for this conference. See gwerror.h for the possible values and meanings of these flags. Only Logging has been observed in practice. + + All events are timestamped with the local time on receipt. + + From gwerror.h: + enum Event { InvalidRecipient = 101, + NOTHANDLED + UndeliverableStatus = 102, + NOTHANDLED * + StatusChange = 103, + Q_UINT16 STATUS + STATUSTEXT + ContactAdd = 104, + NOTHANDLED + ConferenceClosed = 105, + GUID + ConferenceJoined = 106, + GUID + FLAGS + ConferenceLeft = 107, + GUID + FLAGS + ReceiveMessage = 108, + GUID + FLAGS + MESSAGE + ReceiveFile = 109, + NOTHANDLED + UserTyping = 112, + GUID + UserNotTyping = 113, + GUID + UserDisconnect = 114, + NONE + ServerDisconnect = 115, + NONE + ConferenceRename = 116, + NOTHANDLED + ConferenceInvite = 117, + GUID + MESSAGE + ConferenceInviteNotify = 118, + GUID + ConferenceReject = 119, + GUID + ReceiveAutoReply = 121, + GUID + FLAGS + MESSAGE + Start = InvalidRecipient, + Stop = ReceiveAutoReply + }; + Therefore we have GUID, FLAGS, MESSAGE, STATUS, STATUSTEXT. All transfers have TYPE and SOURCE, and a TIMESTAMP is added on receipt. +*/ + +class EventProtocol : public InputProtocolBase +{ +Q_OBJECT +public: + EventProtocol(QObject *parent = 0, const char *name = 0); + ~EventProtocol(); + /** + * Attempt to parse the supplied data into an @ref EventTransfer object. + * The exact state of the parse attempt can be read using @ref state. + * @param rawData The unparsed data. + * @param bytes An integer used to return the number of bytes read. + * @return A pointer to an EventTransfer object if successfull, otherwise 0. The caller is responsible for deleting this object. + */ + Transfer * parse( const QByteArray &, uint & bytes ); +protected: + /** + * Reads a conference's flags + */ + bool readFlags( Q_UINT32 &flags); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/eventtransfer.cpp b/kopete/protocols/groupwise/libgroupwise/eventtransfer.cpp new file mode 100644 index 00000000..06178f21 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/eventtransfer.cpp @@ -0,0 +1,145 @@ +/* + eventtransfer.cpp - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "eventtransfer.h" + +EventTransfer::EventTransfer( const Q_UINT32 eventType, const QString & source, QDateTime timeStamp ) + : Transfer(), m_eventType( eventType ), m_source( source ), m_timeStamp( timeStamp ) +{ + m_contentFlags |= ( EventType | Source | TimeStamp ); +} + + +EventTransfer::~EventTransfer() +{ +} + +// query contents + +bool EventTransfer::hasEventType() +{ + return ( m_contentFlags & EventType ); +} + +bool EventTransfer::hasSource() +{ + return ( m_contentFlags & Source ); +} + +bool EventTransfer::hasTimeStamp() +{ + return ( m_contentFlags & TimeStamp ); +} + +bool EventTransfer::hasGuid() +{ + return ( m_contentFlags & Guid ); +} + +bool EventTransfer::hasFlags() +{ + return ( m_contentFlags & Flags ); +} + +bool EventTransfer::hasMessage() +{ + return ( m_contentFlags & Message ); +} + +bool EventTransfer::hasStatus() +{ + return ( m_contentFlags & Status ); +} + +bool EventTransfer::hasStatusText() +{ + return ( m_contentFlags & StatusText ); +} + +// accessors + +int EventTransfer::eventType() +{ + return m_eventType; +} + +QString EventTransfer::source() +{ + return m_source; +} + +QDateTime EventTransfer::timeStamp() +{ + return m_timeStamp; +} + +GroupWise::ConferenceGuid EventTransfer::guid() +{ + return m_guid; +} + +Q_UINT32 EventTransfer::flags() +{ + return m_flags; +} + +QString EventTransfer::message() +{ + return m_message; +} + +Q_UINT16 EventTransfer::status() +{ + return m_status; +} + +QString EventTransfer::statusText() +{ + return m_statusText; +} + +// mutators +void EventTransfer::setGuid( const GroupWise::ConferenceGuid & guid ) +{ + m_contentFlags |= Guid; + m_guid = guid; +} + +void EventTransfer::setFlags( const Q_UINT32 flags ) +{ + m_contentFlags |= Flags; + m_flags = flags; +} + +void EventTransfer::setMessage( const QString & message ) +{ + m_contentFlags |= Message; + m_message = message; +} + +void EventTransfer::setStatus( const Q_UINT16 inStatus ) +{ + m_contentFlags |= Status; + m_status = inStatus; +} + +void EventTransfer::setStatusText( const QString & statusText ) +{ + m_contentFlags |= StatusText; + m_statusText = statusText; +} + diff --git a/kopete/protocols/groupwise/libgroupwise/eventtransfer.h b/kopete/protocols/groupwise/libgroupwise/eventtransfer.h new file mode 100644 index 00000000..335d4593 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/eventtransfer.h @@ -0,0 +1,110 @@ +/* + eventtransfer.h - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GW_EVENTTRANSFER_H +#define GW_EVENTTRANSFER_H + +#include +#include + +#include "gwerror.h" + +#include "transfer.h" + +namespace Event { + +} + +/** + * Transfer representing an event, a message generated by the server in response to external stimulus + * This class can contain varying data items depending on the type of event. + * You can query which data is present before trying to access it + * @author Kopete Developers + */ +class EventTransfer : public Transfer +{ +public: + /** + * Flags describing the possible contents of an event transfer + */ + enum Contents { EventType = 0x00000001, + Source = 0x00000002, + TimeStamp = 0x00000004, + Guid = 0x00000008, + Flags = 0x00000010, + Message = 0x00000020, + Status = 0x00000040, + StatusText = 0x00000080 }; + /** + * Constructor + * @param eventType the event code describing the event, see @refGroupWise::Event. + * @param source the user generating the event. + * @param timeStamp the time at which the event was received. + */ + EventTransfer( const Q_UINT32 eventType, const QString & source, QDateTime timeStamp ); + ~EventTransfer(); + /** + * Access the bitmask that describes the transfer's contents. Use @ref Contents to determine what it contains + */ + Q_UINT32 contents(); + /** + * Convenience accessors to see what the transfer contains + */ + bool hasEventType(); + bool hasSource(); + bool hasTimeStamp(); + bool hasGuid(); + bool hasFlags(); + bool hasMessage(); + bool hasStatus(); + bool hasStatusText(); + + /** + * Accessors for the transfer's contents + */ + TransferType type() { return Transfer::EventTransfer; } + int eventType(); + QString source(); + QDateTime timeStamp(); + GroupWise::ConferenceGuid guid(); + Q_UINT32 flags(); + QString message(); + Q_UINT16 status(); + QString statusText(); + + /** + * Mutators to set the transfer's contents + */ + void setGuid( const GroupWise::ConferenceGuid & guid ); + void setFlags( const Q_UINT32 flags ); + void setMessage( const QString & message ); + void setStatus( const Q_UINT16 status ); + void setStatusText( const QString & statusText); + +private: + Q_UINT32 m_contentFlags; + int m_eventType; + QString m_source; + QDateTime m_timeStamp; + GroupWise::ConferenceGuid m_guid; + Q_UINT32 m_flags; + QString m_message; + Q_UINT16 m_status; + QString m_statusText; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/gwchatrooms.h b/kopete/protocols/groupwise/libgroupwise/gwchatrooms.h new file mode 100644 index 00000000..207531bb --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/gwchatrooms.h @@ -0,0 +1,78 @@ +/* + Kopete Groupwise Protocol + gwchatrooms.h - Data types for group chat + + Copyright (c) 2005 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include + +#ifndef GROUPWISE_CHATROOMS_H +#define GROUPWISE_CHATROOMS_H + +namespace GroupWise +{ + +class ChatContact +{ + public: + QString dn; + uint chatRights; +}; +typedef QValueList ChatContactList; + +struct ChatroomSearchResult +{ + QString name; + QString ownerDN; + uint participants; +}; + + +class Chatroom +{ + public: + enum UserStatus { Participating, NotParticipating }; + enum Rights { Read = 1, Write = 2, Modify = 4, Moderator = 8, Owner = 16 }; + QString creatorDN; + QString description; + QString disclaimer; + QString displayName; + QString objectId; + QString ownerDN; + QString query; + QString topic; + bool archive; + uint maxUsers; + uint chatRights; + UserStatus userStatus; + QDateTime createdOn; + uint participantsCount; + // haveParticipants, Acl, Invites indicate if we have obtained these lists from the server, so we can tell 'not fetched list' and 'fetched empty list' apart. + bool haveParticipants; + ChatContactList participants; + bool haveAcl; + ChatContactList acl; + bool haveInvites; + ChatContactList invites; + + Chatroom() { archive = false; maxUsers = 0; chatRights = 0; participantsCount = 0; haveParticipants = false; haveAcl = false; haveInvites = false; } + Chatroom( ChatroomSearchResult csr ) { archive = false; maxUsers = 0; chatRights = 0; participantsCount = csr.participants; haveParticipants = false; haveAcl = false; haveInvites = false; ownerDN = csr.ownerDN; displayName = csr.name; } +}; + +typedef QValueList ChatroomList; +typedef QMap ChatroomMap; +} +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/gwclientstream.cpp b/kopete/protocols/groupwise/libgroupwise/gwclientstream.cpp new file mode 100644 index 00000000..7d58de93 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/gwclientstream.cpp @@ -0,0 +1,606 @@ +/* + gwclientstream.cpp - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + encode_method from Gaim src/protocols/novell/nmconn.c + Copyright (c) 2004 Novell, Inc. All Rights Reserved + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +//#include +//#include +// #include +// #include +// #include"bytestream.h" +// #include"base64.h" +// #include"hash.h" +// #include"simplesasl.h" +// #include"securestream.h" +// #include"protocol.h" + +#include // for qdebug +#include +#include +#include +#include + +#include "bytestream.h" +#include "connector.h" +#include "coreprotocol.h" +#include "request.h" +#include "securestream.h" +#include "tlshandler.h" + +//#include "iostream.h" + +#include "gwclientstream.h" + +//#define LIBGW_DEBUG 1 + +void cs_dump( const QByteArray &bytes ); + +enum { + Idle, + Connecting, + WaitVersion, + WaitTLS, + NeedParams, + Active, + Closing +}; + +enum { + Client, + Server +}; + +class ClientStream::Private +{ +public: + Private() + { + conn = 0; + bs = 0; + ss = 0; + tlsHandler = 0; + tls = 0; +// sasl = 0; + in.setAutoDelete(true); + + allowPlain = false; + mutualAuth = false; + haveLocalAddr = false; +/* minimumSSF = 0; + maximumSSF = 0;*/ + doBinding = true; + + in_rrsig = false; + + reset(); + } + void reset() + { + state = Idle; + notify = 0; + newTransfers = false; +// sasl_ssf = 0; + tls_warned = false; + using_tls = false; + } + + NovellDN id; + QString server; + bool oldOnly; + bool allowPlain, mutualAuth; + bool haveLocalAddr; + QHostAddress localAddr; + Q_UINT16 localPort; +// int minimumSSF, maximumSSF; +// QString sasl_mech; + bool doBinding; + + bool in_rrsig; + + Connector *conn; + ByteStream *bs; + TLSHandler *tlsHandler; + QCA::TLS *tls; +// QCA::SASL *sasl; + SecureStream *ss; + CoreProtocol client; + //CoreProtocol srv; + + QString defRealm; + + int mode; + int state; + int notify; + bool newTransfers; +// int sasl_ssf; + bool tls_warned, using_tls; + bool doAuth; + +// QStringList sasl_mechlist; + + int errCond; + QString errText; + + QPtrQueue in; + + QTimer noopTimer; // probably not needed + int noop_time; +}; + +ClientStream::ClientStream(Connector *conn, TLSHandler *tlsHandler, QObject *parent) +:Stream(parent) +{ + d = new Private; + d->mode = Client; + d->conn = conn; + connect( d->conn, SIGNAL(connected()), SLOT(cr_connected()) ); + connect( d->conn, SIGNAL(error()), SLOT(cr_error()) ); + connect( &d->client, SIGNAL( outgoingData( const QByteArray& ) ), SLOT ( cp_outgoingData( const QByteArray & ) ) ); + connect( &d->client, SIGNAL( incomingData() ), SLOT ( cp_incomingData() ) ); + + d->noop_time = 0; + connect(&d->noopTimer, SIGNAL(timeout()), SLOT(doNoop())); + + d->tlsHandler = tlsHandler; // all the extra stuff happening in the larger ctor happens at connect time :) +} + +ClientStream::~ClientStream() +{ + reset(); + delete d; +} + +void ClientStream::reset(bool all) +{ + d->reset(); + d->noopTimer.stop(); + + // delete securestream + delete d->ss; + d->ss = 0; + + // reset sasl +// delete d->sasl; +// d->sasl = 0; + + // client + if(d->mode == Client) { + // reset tls + if(d->tlsHandler) + d->tlsHandler->reset(); + + // reset connector + if(d->bs) { + d->bs->close(); + d->bs = 0; + } + d->conn->done(); + + // reset state machine + d->client.reset(); + } + if(all) + d->in.clear(); +} + +// Jid ClientStream::jid() const +// { +// return d->jid; +// } + +void ClientStream::connectToServer(const NovellDN &id, bool auth) +{ + reset(true); + d->state = Connecting; + d->id = id; + d->doAuth = auth; + d->server = d->id.server; + + d->conn->connectToServer( d->server ); +} + +void ClientStream::continueAfterWarning() +{ + if(d->state == WaitVersion) { + // if we don't have TLS yet, then we're never going to get it + if(!d->tls_warned && !d->using_tls) { + d->tls_warned = true; + d->state = WaitTLS; + emit warning(WarnNoTLS); + return; + } + d->state = Connecting; + processNext(); + } + else if(d->state == WaitTLS) { + d->state = Connecting; + processNext(); + } +} + +void ClientStream::accept() +{ +/* d->srv.host = d->server; + processNext();*/ +} + +bool ClientStream::isActive() const +{ + return (d->state != Idle); +} + +bool ClientStream::isAuthenticated() const +{ + return (d->state == Active); +} + +// void ClientStream::setPassword(const QString &s) +// { +// if(d->client.old) { +// d->client.setPassword(s); +// } +// else { +// if(d->sasl) +// d->sasl->setPassword(s); +// } +// } + +// void ClientStream::setRealm(const QString &s) +// { +// if(d->sasl) +// d->sasl->setRealm(s); +// } + +void ClientStream::continueAfterParams() +{ +/* if(d->state == NeedParams) { + d->state = Connecting; + if(d->client.old) { + processNext(); + } + else { + if(d->sasl) + d->sasl->continueAfterParams(); + } + }*/ +} + +void ClientStream::setNoopTime(int mills) +{ + d->noop_time = mills; + + if(d->state != Active) + return; + + if(d->noop_time == 0) { + d->noopTimer.stop(); + return; + } + d->noopTimer.start(d->noop_time); +} + +void ClientStream::setLocalAddr(const QHostAddress &addr, Q_UINT16 port) +{ + d->haveLocalAddr = true; + d->localAddr = addr; + d->localPort = port; +} + +int ClientStream::errorCondition() const +{ + return d->errCond; +} + +QString ClientStream::errorText() const +{ + return d->errText; +} + +// QDomElement ClientStream::errorAppSpec() const +// { +// return d->errAppSpec;cr_error +// } + +// bool ClientStream::old() const +// { +// return d->client.old; +// } + +void ClientStream::close() +{ + if(d->state == Active) { + d->state = Closing; +// d->client.shutdown(); + processNext(); + } + else if(d->state != Idle && d->state != Closing) { + reset(); + } +} + +void ClientStream::setAllowPlain(bool b) +{ + d->allowPlain = b; +} + +void ClientStream::setRequireMutualAuth(bool b) +{ + d->mutualAuth = b; +} + +// void ClientStream::setSSFRange(int low, int high) +// { +// d->minimumSSF = low; +// d->maximumSSF = high; +// } + +// void ClientStream::setOldOnly(bool b) +// { +// d->oldOnly = b; +// } + +bool ClientStream::transfersAvailable() const +{ + return ( !d->in.isEmpty() ); +} + +Transfer * ClientStream::read() +{ + if(d->in.isEmpty()) + return 0; //first from queue... + else + return d->in.dequeue(); +} + +void ClientStream::write( Request *request ) +{ + // pass to CoreProtocol for transformation into wire format + d->client.outgoingTransfer( request ); +} + +void cs_dump( const QByteArray &bytes ) +{ +//#define GW_CLIENTSTREAM_DEBUG 1 +#ifdef GW_CLIENTSTREAM_DEBUG + CoreProtocol::debug( QString( "contains: %1 bytes " ).arg( bytes.count() ) ); + uint count = 0; + while ( count < bytes.count() ) + { + int dword = 0; + for ( int i = 0; i < 8; ++i ) + { + if ( count + i < bytes.count() ) + printf( "%02x ", bytes[ count + i ] ); + else + printf( " " ); + if ( i == 3 ) + printf( " " ); + } + printf(" | "); + dword = 0; + for ( int i = 0; i < 8; ++i ) + { + if ( count + i < bytes.count() ) + { + int j = bytes [ count + i ]; + if ( j >= 0x20 && j <= 0x7e ) + printf( "%2c ", j ); + else + printf( "%2c ", '.' ); + } + else + printf( " " ); + if ( i == 3 ) + printf( " " ); + } + printf( "\n" ); + count += 8; + } + printf( "\n" ); +#else + Q_UNUSED( bytes ); +#endif +} + +void ClientStream::cp_outgoingData( const QByteArray& outgoingBytes ) +{ + // take formatted bytes from CoreProtocol and put them on the wire +#ifdef LIBGW_DEBUG + CoreProtocol::debug( "ClientStream::cp_outgoingData:" ); + cs_dump( outgoingBytes ); +#endif + d->ss->write( outgoingBytes ); +} + +void ClientStream::cp_incomingData() +{ + CoreProtocol::debug( "ClientStream::cp_incomingData:" ); + Transfer * incoming = d->client.incomingTransfer(); + if ( incoming ) + { + CoreProtocol::debug( " - got a new transfer" ); + d->in.enqueue( incoming ); + d->newTransfers = true; + emit doReadyRead(); + } + else + CoreProtocol::debug( QString( " - client signalled incomingData but none was available, state is: %1" ).arg( d->client.state() ) ); +} + +void ClientStream::cr_connected() +{ + d->bs = d->conn->stream(); + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); + + QByteArray spare = d->bs->read(); + + d->ss = new SecureStream(d->bs); + connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); + connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int))); + connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken())); + connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed())); + connect(d->ss, SIGNAL(error(int)), SLOT(ss_error(int))); + + //d->client.startDialbackOut("andbit.net", "im.pyxa.org"); + //d->client.startServerOut(d->server); + +// d->client.startClientOut(d->jid, d->oldOnly, d->conn->useSSL(), d->doAuth); +// d->client.setAllowTLS(d->tlsHandler ? true: false); +// d->client.setAllowBind(d->doBinding); +// d->client.setAllowPlain(d->allowPlain); + + /*d->client.jid = d->jid; + d->client.server = d->server; + d->client.allowPlain = d->allowPlain; + d->client.oldOnly = d->oldOnly; + d->client.sasl_mech = d->sasl_mech; + d->client.doTLS = d->tlsHandler ? true: false; + d->client.doBinding = d->doBinding;*/ + + QGuardedPtr self = this; + emit connected(); + if(!self) + return; + + // immediate SSL? + if(d->conn->useSSL()) { + CoreProtocol::debug( "CLIENTSTREAM: cr_connected(), starting TLS" ); + d->using_tls = true; + d->ss->startTLSClient(d->tlsHandler, d->server, spare); + } + else { +/* d->client.addIncomingData(spare); + processNext();*/ + } +} + +void ClientStream::cr_error() +{ + reset(); + emit error(ErrConnection); +} + +void ClientStream::bs_connectionClosed() +{ + reset(); + emit connectionClosed(); +} + +void ClientStream::bs_delayedCloseFinished() +{ + // we don't care about this (we track all important data ourself) +} + +void ClientStream::bs_error(int) +{ + // TODO +} + +void ClientStream::ss_readyRead() +{ + QByteArray a; + a = d->ss->read(); + +#ifdef LIBGW_DEBUG + QCString cs(a.data(), a.size()+1); + CoreProtocol::debug( QString( "ClientStream: ss_readyRead() recv: %1 bytes" ).arg( a.size() ) ); + cs_dump( a ); +#endif + + d->client.addIncomingData(a); +/* if(d->notify & CoreProtocol::NRecv) { */ + //processNext(); +} + +void ClientStream::ss_bytesWritten(int bytes) +{ +#ifdef LIBGW_DEBUG + CoreProtocol::debug( QString( "ClientStream::ss_bytesWritten: %1 bytes written" ).arg( bytes ) ); +#else + Q_UNUSED( bytes ); +#endif +} + +void ClientStream::ss_tlsHandshaken() +{ + QGuardedPtr self = this; + emit securityLayerActivated(LayerTLS); + if(!self) + return; + processNext(); +} + +void ClientStream::ss_tlsClosed() +{ + CoreProtocol::debug( "ClientStream::ss_tlsClosed()" ); + reset(); + emit connectionClosed(); +} + +void ClientStream::ss_error(int x) +{ + CoreProtocol::debug( QString( "ClientStream::ss_error() x=%1 ").arg( x ) ); + if(x == SecureStream::ErrTLS) { + reset(); + d->errCond = TLSFail; + emit error(ErrTLS); + } + else { + reset(); + emit error(ErrSecurityLayer); + } +} + +void ClientStream::srvProcessNext() +{ +} + +void ClientStream::doReadyRead() +{ + //QGuardedPtr self = this; + emit readyRead(); + //if(!self) + // return; + //d->in_rrsig = false; +} + +void ClientStream::processNext() +{ + if( !d->in.isEmpty() ) { + //d->in_rrsig = true; + QTimer::singleShot(0, this, SLOT(doReadyRead())); + } +} + +bool ClientStream::handleNeed() +{ + return false; +} + + +void ClientStream::doNoop() +{ +} + +void ClientStream::handleError() +{ +} + +#include "gwclientstream.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/gwclientstream.h b/kopete/protocols/groupwise/libgroupwise/gwclientstream.h new file mode 100644 index 00000000..5ae5ec8c --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/gwclientstream.h @@ -0,0 +1,185 @@ +/* + gwclientstream.h - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GW_CLIENTSTREAM_H +#define GW_CLIENTSTREAM_H + +#include + +#include "gwfield.h" +#include "stream.h" + +// forward defines +class ByteStream; +class Connector; +class Request; +class TLSHandler; + +typedef struct NovellDN +{ + QString dn; + QString server; +}; + +class ClientStream : public Stream +{ + Q_OBJECT +public: + enum Error { + ErrConnection = ErrCustom, // Connection error, ask Connector-subclass what's up + ErrNeg, // Negotiation error, see condition + ErrTLS, // TLS error, see condition + ErrAuth, // Auth error, see condition + ErrSecurityLayer, // broken SASL security layer + ErrBind // Resource binding error + }; + enum Warning { +/*# WarnOldVersion, // server uses older XMPP/Jabber "0.9" protocol // can be customised for novell versions*/ + WarnNoTLS // there is no chance for TLS at this point + }; + enum NegCond { + HostGone, // host no longer hosted + HostUnknown, // unknown host + RemoteConnectionFailed, // unable to connect to a required remote resource + SeeOtherHost, // a 'redirect', see errorText() for other host + UnsupportedVersion // unsupported XMPP version + }; + enum TLSCond { + TLSStart, // server rejected STARTTLS + TLSFail // TLS failed, ask TLSHandler-subclass what's up + }; + enum SecurityLayer { + LayerTLS, + LayerSASL + }; + enum AuthCond { + GenericAuthError, // all-purpose "can't login" error + NoMech, // No appropriate auth mech available + BadProto, // Bad SASL auth protocol + BadServ, // Server failed mutual auth + EncryptionRequired, // can't use mech without TLS +/*# InvalidAuthzid, // bad input JID // need to change this to novell DN*/ + InvalidMech, // bad mechanism + InvalidRealm, // bad realm + MechTooWeak, // can't use mech with this authzid + NotAuthorized, // bad user, bad password, bad creditials + TemporaryAuthFailure // please try again later! + }; + enum BindCond { + BindNotAllowed, // not allowed to bind a resource + BindConflict // resource in-use + }; + + ClientStream(Connector *conn, TLSHandler *tlsHandler=0, QObject *parent=0); + ~ClientStream(); + + void connectToServer(const NovellDN &id, bool auth=true); + void accept(); // server + bool isActive() const; + bool isAuthenticated() const; + + // login params + void setUsername(const QString &s); + void setPassword(const QString &s); + void setRealm(const QString &s); + void continueAfterParams(); + + // security options (old protocol only uses the first !) + void setAllowPlain(bool); + void setRequireMutualAuth(bool); + void setLocalAddr(const QHostAddress &addr, Q_UINT16 port); + + void close(); + + /** + * Are there any messages waiting to be read + */ + bool transfersAvailable() const; + /** + * Read a message received from the server + */ + Transfer * read(); + + /** + * Send a message to the server + */ + void write( Request * request ); + + int errorCondition() const; + QString errorText() const; +// # QDomElement errorAppSpec() const; // redondo + + // extrahttp://bugs.kde.org/show_bug.cgi?id=85158 +/*# void writeDirect(const QString &s); // must be for debug testing*/ + void setNoopTime(int mills); + +signals: + void connected(); + void securityLayerActivated(int); + //void needAuthParams(bool user, bool pass, bool realm); + void authenticated(); // this signal is ordinarily emitted in processNext + void warning(int); +// # void incomingXml(const QString &s); // signals emitted in processNext but don't seem to go anywhere... +// # void outgoingXml(const QString &s); // +// void readyRead(); //signals that there is a transfer ready to be read - defined in stream +public slots: + void continueAfterWarning(); + +private slots: + void cr_connected(); + void cr_error(); + /** + * collects wire ready outgoing data from the core protocol and sends + */ + void cp_outgoingData( const QByteArray& ); + /** + * collects parsed incoming data as a transfer from the core protocol and queues + */ + void cp_incomingData(); + + void bs_connectionClosed(); + void bs_delayedCloseFinished(); + void bs_error(int); // server only + + void ss_readyRead(); + void ss_bytesWritten(int); + void ss_tlsHandshaken(); + void ss_tlsClosed(); + void ss_error(int); + + void doNoop(); + void doReadyRead(); + +private: + class Private; + Private *d; + + void reset(bool all=false); + void processNext(); + bool handleNeed(); + void handleError(); + void srvProcessNext(); + + /** + * convert internal method representation to wire + */ + static char* encode_method(Q_UINT8 method); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/gwerror.cpp b/kopete/protocols/groupwise/libgroupwise/gwerror.cpp new file mode 100644 index 00000000..5338cea0 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/gwerror.cpp @@ -0,0 +1,276 @@ +/* + gwerror.cpp - Kopete Groupwise Protocol + + Copyright (c) 2007 Novell, Inc http://www.novell.com/linux + + Kopete (c) 2002-2007 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include "gwerror.h" + +QString GroupWise::errorCodeToString( int errorCode ) +{ + QString errorString; + switch ( errorCode ) + { +#if 0 + case NMERR_ACCESS_DENIED: + errorString = i18n( "Access denied" ); + break; + case NMERR_NOT_SUPPORTED: + errorString = i18n( "Not supported" ); + break; + case NMERR_PASSWORD_EXPIRED: + errorString = i18n( "Password expired" ); + break; + case NMERR_PASSWORD_INVALID: + errorString = i18n( "Invalid password" ); + break; + case NMERR_USER_NOT_FOUND: + errorString = i18n( "User not found" ); + break; + case NMERR_ATTRIBUTE_NOT_FOUND: + errorString = i18n( "Attribute not found" ); + break; + case NMERR_USER_DISABLED: + errorString = i18n( "User is disabled" ); + break; + case NMERR_DIRECTORY_FAILURE: + errorString = i18n( "Directory failure" ); + break; + case NMERR_HOST_NOT_FOUND: + errorString = i18n( "Host not found" ); + break; + case NMERR_ADMIN_LOCKED: + errorString = i18n( "Locked by admin" ); + break; + case NMERR_DUPLICATE_PARTICIPANT: + errorString = i18n( "Duplicate participant" ); + break; + case NMERR_SERVER_BUSY: + errorString = i18n( "Server busy" ); + break; + case NMERR_OBJECT_NOT_FOUND: + errorString = i18n( "Object not found" ); + break; + case NMERR_DIRECTORY_UPDATE: + errorString = i18n( "Directory update" ); + break; + case NMERR_DUPLICATE_FOLDER: + errorString = i18n( "Duplicate folder" ); + break; + case NMERR_DUPLICATE_CONTACT: + errorString = i18n( "Contact list entry already exists" ); + break; + case NMERR_USER_NOT_ALLOWED: + errorString = i18n( "User not allowed" ); + break; + case NMERR_TOO_MANY_CONTACTS: + errorString = i18n( "Too many contacts" ); + break; + case NMERR_CONFERENCE_NOT_FOUND_2: + errorString = i18n( "Conference not found" ); + break; + case NMERR_TOO_MANY_FOLDERS: + errorString = i18n( "Too many folders" ); + break; + case NMERR_SERVER_PROTOCOL: + errorString = i18n( "Server protocol error" ); + break; + case NMERR_CONVERSATION_INVITE: + errorString = i18n( "Conversation invitation error" ); + break; + case NMERR_USER_BLOCKED: + errorString = i18n( "User is blocked" ); + break; + case NMERR_MASTER_ARCHIVE_MISSING: + errorString = i18n( "Master archive is missing" ); + break; + case NMERR_PASSWORD_EXPIRED_2: + errorString = i18n( "Expired password in use" ); + break; + case NMERR_CREDENTIALS_MISSING: + errorString = i18n( "Credentials missing" ); + break; + case NMERR_AUTHENTICATION_FAILED: + errorString = i18n( "Authentication failed" ); + break; + case NMERR_EVAL_CONNECTION_LIMIT: + errorString = i18n( "Eval connection limit" ); + break; + case MSGPRES_ERR_UNSUPPORTED_CLIENT_VERSION: + errorString = i18n( "Unsupported client version" ); + break; + case MSGPRES_ERR_DUPLICATE_CHAT: + errorString = i18n( "A duplicate chat was found" ); + break; + case MSGPRES_ERR_CHAT_NOT_FOUND: + errorString = i18n( "Chat not found" ); + break; + case MSGPRES_ERR_INVALID_NAME: + errorString = i18n( "Invalid chat name" ); + break; + case MSGPRES_ERR_CHAT_ACTIVE: + errorString = i18n( "The chat is active" ); + break; + case MSGPRES_ERR_CHAT_BUSY: + errorString = i18n( "Chat is busy; try again" ); + break; + case MSGPRES_ERR_REQUEST_TOO_SOON: + errorString = i18n( "Tried request too soon after another; try again" ); + break; + case MSGPRES_ERR_CHAT_NOT_ACTIVE: + errorString = i18n( "Server's chat subsystem is not active" ); + break; + case MSGPRES_ERR_INVALID_CHAT_UPDATE: + errorString = i18n( "The chat update request is invalid" ); + break; + case MSGPRES_ERR_DIRECTORY_MISMATCH: + errorString = i18n( "Write failed due to directory mismatch" ); + break; + case MSGPRES_ERR_RECIPIENT_TOO_OLD: + errorString = i18n( "Recipient's client version is too old" ); + break; + case MSGPRES_ERR_CHAT_NO_LONGER_VALID: + errorString = i18n( "Chat has been removed from server" ); + break; + default: + errorString = i18n("Unrecognized error code: %s").arg( errorCode ); +#else + case NMERR_ACCESS_DENIED: + errorString = "Access denied"; + break; + case NMERR_NOT_SUPPORTED: + errorString = "Not supported"; + break; + case NMERR_PASSWORD_EXPIRED: + errorString = "Password expired"; + break; + case NMERR_PASSWORD_INVALID: + errorString = "Invalid password"; + break; + case NMERR_USER_NOT_FOUND: + errorString = "User not found"; + break; + case NMERR_ATTRIBUTE_NOT_FOUND: + errorString = "Attribute not found"; + break; + case NMERR_USER_DISABLED: + errorString = "User is disabled"; + break; + case NMERR_DIRECTORY_FAILURE: + errorString = "Directory failure"; + break; + case NMERR_HOST_NOT_FOUND: + errorString = "Host not found"; + break; + case NMERR_ADMIN_LOCKED: + errorString = "Locked by admin"; + break; + case NMERR_DUPLICATE_PARTICIPANT: + errorString = "Duplicate participant"; + break; + case NMERR_SERVER_BUSY: + errorString = "Server busy"; + break; + case NMERR_OBJECT_NOT_FOUND: + errorString = "Object not found"; + break; + case NMERR_DIRECTORY_UPDATE: + errorString = "Directory update"; + break; + case NMERR_DUPLICATE_FOLDER: + errorString = "Duplicate folder"; + break; + case NMERR_DUPLICATE_CONTACT: + errorString = "Contact list entry already exists"; + break; + case NMERR_USER_NOT_ALLOWED: + errorString = "User not allowed"; + break; + case NMERR_TOO_MANY_CONTACTS: + errorString = "Too many contacts"; + break; + case NMERR_CONFERENCE_NOT_FOUND_2: + errorString = "Conference not found"; + break; + case NMERR_TOO_MANY_FOLDERS: + errorString = "Too many folders"; + break; + case NMERR_SERVER_PROTOCOL: + errorString = "Server protocol error"; + break; + case NMERR_CONVERSATION_INVITE: + errorString = "Conversation invitation error"; + break; + case NMERR_USER_BLOCKED: + errorString = "User is blocked"; + break; + case NMERR_MASTER_ARCHIVE_MISSING: + errorString = "Master archive is missing"; + break; + case NMERR_PASSWORD_EXPIRED_2: + errorString = "Expired password in use"; + break; + case NMERR_CREDENTIALS_MISSING: + errorString = "Credentials missing"; + break; + case NMERR_AUTHENTICATION_FAILED: + errorString = "Authentication failed"; + break; + case NMERR_EVAL_CONNECTION_LIMIT: + errorString = "Eval connection limit"; + break; + case MSGPRES_ERR_UNSUPPORTED_CLIENT_VERSION: + errorString = "Unsupported client version"; + break; + case MSGPRES_ERR_DUPLICATE_CHAT: + errorString = "A duplicate chat was found"; + break; + case MSGPRES_ERR_CHAT_NOT_FOUND: + errorString = "Chat not found"; + break; + case MSGPRES_ERR_INVALID_NAME: + errorString = "Invalid chat name"; + break; + case MSGPRES_ERR_CHAT_ACTIVE: + errorString = "The chat is active"; + break; + case MSGPRES_ERR_CHAT_BUSY: + errorString = "Chat is busy; try again"; + break; + case MSGPRES_ERR_REQUEST_TOO_SOON: + errorString = "Tried request too soon after another; try again"; + break; + case MSGPRES_ERR_CHAT_NOT_ACTIVE: + errorString = "Server's chat subsystem is not active"; + break; + case MSGPRES_ERR_INVALID_CHAT_UPDATE: + errorString = "The chat update request is invalid"; + break; + case MSGPRES_ERR_DIRECTORY_MISMATCH: + errorString = "Write failed due to directory mismatch"; + break; + case MSGPRES_ERR_RECIPIENT_TOO_OLD: + errorString = "Recipient's client version is too old"; + break; + case MSGPRES_ERR_CHAT_NO_LONGER_VALID: + errorString = "Chat has been removed from server"; + break; + default: + errorString = QString("Unrecognized error code: %s").arg( errorCode ); +#endif + } + return errorString; +} \ No newline at end of file diff --git a/kopete/protocols/groupwise/libgroupwise/gwerror.h b/kopete/protocols/groupwise/libgroupwise/gwerror.h new file mode 100644 index 00000000..5300f788 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/gwerror.h @@ -0,0 +1,241 @@ +/* + gwerror.h - Kopete Groupwise Protocol + + Copyright (c) 2004-2007 Novell, Inc http://www.novell.com/linux + + Kopete (c) 2002-2007 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GW_ERROR_H +#define GW_ERROR_H + +#include +#include +#include +#include + +typedef Q_UINT16 NMERR_T; +#define GROUPWISE_DEBUG_GLOBAL 14190 +#define GROUPWISE_DEBUG_LIBGW 14191 +#define GROUPWISE_DEBUG_RAW 14192 + +#define BLANK_GUID "[00000000-00000000-00000000-0000-0000]" +#define CONF_GUID_END 27 + +//#define LIBGW_DEBUG 1 +#define LIBGW_USE_KDEBUG 1 + +namespace GroupWise +{ + enum Status { Unknown = 0, + Offline = 1, + Available = 2, + Busy = 3, + Away = 4, + AwayIdle = 5, + Invalid = 6 + }; + + enum Error { None = 0, + ErrorBase = 0x2000L, + BadParm, + TCPWrite, + TCPRead, + Protocol, + ServerRedirect, + ConferenceNotFound, + ConferenceNotInstantiated, + FolderExists + }; + + enum Event { InvalidRecipient = 101, + UndeliverableStatus = 102, + StatusChange = 103, + ContactAdd = 104, + ConferenceClosed = 105, + ConferenceJoined = 106, + ConferenceLeft = 107, + ReceiveMessage = 108, + ReceiveFile = 109, + UserTyping = 112, + UserNotTyping = 113, + UserDisconnect = 114, + ServerDisconnect = 115, + ConferenceRename = 116, + ConferenceInvite = 117, + ConferenceInviteNotify = 118, + ConferenceReject = 119, + ReceiveAutoReply = 121, + Start = InvalidRecipient, + /* Event codes >= 122 are new in GW7 protocol */ + ReceivedBroadcast = 122, + ReceivedSystemBroadcast = 123, + ConferenceAttribUpdate = 128, + ConferenceTopicChanged = 129, + ChatroomNameChanged = 130, + ConferenceRightsChanged = 131, + ConferenceRemoved = 132, /* you were kicked */ + ChatOwnerChanged = 133, + Stop = ChatOwnerChanged + + }; + + enum ConferenceFlags { Logging = 0x00000001, + Secure = 0x00000002, + Closed = 0x10000000 + }; + + QString errorCodeToString( int errorCode ); + + // helpful structs used to pass data between the client library and the application using it + class ConferenceGuid : public QString + { + public: + ConferenceGuid(); + ConferenceGuid( const QString & string ); + ~ConferenceGuid(); + }; + + bool operator==( const ConferenceGuid & g1, const ConferenceGuid & g2 ); + bool operator==( const QString & s, const ConferenceGuid & g ); + bool operator==( const ConferenceGuid & g, const QString & s ); + + struct ConferenceEvent + { + Event type; + ConferenceGuid guid; + QString user; + QDateTime timeStamp; + Q_UINT32 flags; + QString message; + }; + + struct FolderItem + { + uint id; + uint sequence; + uint parentId; + QString name; + }; + + struct ContactItem + { + uint id; + uint parentId; + uint sequence; + QString dn; + QString displayName; + }; + + struct ContactDetails + { + QString cn, + dn, + givenName, + surname, + fullName, + awayMessage, + authAttribute; + int status; + bool archive; + QMap< QString, QString > properties; + }; + + struct OutgoingMessage + { + ConferenceGuid guid; + QString message; + QString rtfMessage; + }; + + struct UserSearchQueryTerm + { + QString field; + QString argument; + int operation; + }; + + struct CustomStatus + { + GroupWise::Status status; + QString name; + QString autoReply; + }; +} + +// temporary typedef pending implementation + +// #define NMERR_BASE 0x2000L +// #define NM_OK 0L +// #define NMERR_BAD_PARM (NMERR_BASE + 0x0001) +// #define NMERR_TCP_WRITE (NMERR_BASE + 0x0002) +// #define NMERR_TCP_READ (NMERR_BASE + 0x0003) +// #define NMERR_PROTOCOL (NMERR_BASE + 0x0004) +// #define NMERR_SERVER_REDIRECT (NMERR_BASE + 0x0005) +// #define NMERR_CONFERENCE_NOT_FOUND (NMERR_BASE + 0x0006) +// #define NMERR_CONFERENCE_NOT_INSTANTIATED (NMERR_BASE + 0x0007) +// #define NMERR_FOLDER_EXISTS (NMERR_BASE + 0x0008) + +/* Errors that are returned from the server */ +#define NMERR_SERVER_BASE 0xD100L +#define NMERR_ACCESS_DENIED (NMERR_SERVER_BASE + 0x0006) +#define NMERR_NOT_SUPPORTED (NMERR_SERVER_BASE + 0x000A) +#define NMERR_PASSWORD_EXPIRED (NMERR_SERVER_BASE + 0x000B) +#define NMERR_PASSWORD_INVALID (NMERR_SERVER_BASE + 0x000C) +#define NMERR_USER_NOT_FOUND (NMERR_SERVER_BASE + 0x000D) +#define NMERR_ATTRIBUTE_NOT_FOUND (NMERR_SERVER_BASE + 0x000E) +#define NMERR_USER_DISABLED (NMERR_SERVER_BASE + 0x0010) +#define NMERR_DIRECTORY_FAILURE (NMERR_SERVER_BASE + 0x0011) +#define NMERR_HOST_NOT_FOUND (NMERR_SERVER_BASE + 0x0019) +#define NMERR_ADMIN_LOCKED (NMERR_SERVER_BASE + 0x001C) +#define NMERR_DUPLICATE_PARTICIPANT (NMERR_SERVER_BASE + 0x001F) +#define NMERR_SERVER_BUSY (NMERR_SERVER_BASE + 0x0023) +#define NMERR_OBJECT_NOT_FOUND (NMERR_SERVER_BASE + 0x0024) +#define NMERR_DIRECTORY_UPDATE (NMERR_SERVER_BASE + 0x0025) +#define NMERR_DUPLICATE_FOLDER (NMERR_SERVER_BASE + 0x0026) +#define NMERR_DUPLICATE_CONTACT (NMERR_SERVER_BASE + 0x0027) +#define NMERR_USER_NOT_ALLOWED (NMERR_SERVER_BASE + 0x0028) +#define NMERR_TOO_MANY_CONTACTS (NMERR_SERVER_BASE + 0x0029) +#define NMERR_CONFERENCE_NOT_FOUND_2 (NMERR_SERVER_BASE + 0x002B) +#define NMERR_TOO_MANY_FOLDERS (NMERR_SERVER_BASE + 0x002C) +#define NMERR_SERVER_PROTOCOL (NMERR_SERVER_BASE + 0x0030) +#define NMERR_CONVERSATION_INVITE (NMERR_SERVER_BASE + 0x0035) +#define NMERR_USER_BLOCKED (NMERR_SERVER_BASE + 0x0039) +#define NMERR_MASTER_ARCHIVE_MISSING (NMERR_SERVER_BASE + 0x003A) +#define NMERR_PASSWORD_EXPIRED_2 (NMERR_SERVER_BASE + 0x0042) +#define NMERR_CREDENTIALS_MISSING (NMERR_SERVER_BASE + 0x0046) +#define NMERR_AUTHENTICATION_FAILED (NMERR_SERVER_BASE + 0x0049) +#define NMERR_EVAL_CONNECTION_LIMIT (NMERR_SERVER_BASE + 0x004A) + +/* Error codes that are new in GW7 */ +#define MSGPRES_ERR_UNSUPPORTED_CLIENT_VERSION (NMERR_SERVER_BASE + 0x004B) // This version of the client is not supported. +#define MSGPRES_ERR_DUPLICATE_CHAT (NMERR_SERVER_BASE + 0x0051) // A duplicate chat was found. +#define MSGPRES_ERR_CHAT_NOT_FOUND (NMERR_SERVER_BASE + 0x0052) // The chat was not found. +#define MSGPRES_ERR_INVALID_NAME (NMERR_SERVER_BASE + 0x0053) // The chat name is not valid. +#define MSGPRES_ERR_CHAT_ACTIVE (NMERR_SERVER_BASE + 0x0054) // Cannot delete an active chat. +#define MSGPRES_ERR_INSUF_CONV_RIGHTS (NMERR_SERVER_BASE + 0x0055) // Insufficient conversation rights to perform an action. +#define MSGPRES_ERR_CHAT_BUSY (NMERR_SERVER_BASE + 0x0056) // Chat is busy; try again. +#define MSGPRES_ERR_REQUEST_TOO_SOON (NMERR_SERVER_BASE + 0x0057) // Tried a request too soon after another one; try again. +#define MSGPRES_INFO_NO_LIST_CHANGE (NMERR_SERVER_BASE + 0x0058) // The chat list has not changed since the last search. +#define MSGPRES_ERR_CHAT_NOT_ACTIVE (NMERR_SERVER_BASE + 0x0059) // The chat subsystem is not active! +#define MSGPRES_ERR_INVALID_CHAT_UPDATE (NMERR_SERVER_BASE + 0x005A) // The chat update request is invalid. +#define MSGPRES_ERR_DIRECTORY_MISMATCH (NMERR_SERVER_BASE + 0x005B) // Write failed due to directory mismatch. +#define MSGPRES_ERR_RECIPIENT_TOO_OLD (NMERR_SERVER_BASE + 0x005C) // The recipient's client version is too old. +#define MSGPRES_ERR_CHAT_NO_LONGER_VALID (NMERR_SERVER_BASE + 0x005D) // The chat has been removed from the server. + +/* protocol version capabilities */ +#define CMSGPRES_GW_6_5 2 +#define CMSGPRES_SUPPORTS_NO_DETAILS_ON_LOGIN 3 +#define CMSGPRES_SUPPORTS_BROADCAST 4 +#define CMSGPRES_SUPPORTS_CHAT 5 + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/gwfield.cpp b/kopete/protocols/groupwise/libgroupwise/gwfield.cpp new file mode 100644 index 00000000..e0d3c5db --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/gwfield.cpp @@ -0,0 +1,223 @@ +/* + gwfield.cpp - Fields used for Request/Response data in GroupWise + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Testbed + Copyright (c) 2003 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include "gwerror.h" + +#ifdef LIBGW_USE_KDEBUG + #include +#endif + +#include "gwfield.h" +#include + +using namespace Field; +using namespace std; + +/* === FieldList ==================================================== */ +FieldList::~FieldList() +{ +} + +FieldListIterator FieldList::find( QCString tag ) +{ + FieldListIterator it = begin(); + return find( it, tag ); +} + +FieldListIterator FieldList::find( FieldListIterator &it, QCString tag ) +{ + FieldListIterator theEnd = end(); + //cout << "FieldList::find() looking for " << tag.data() << endl; + for ( ; it != theEnd; ++it ) + { + //cout << " - on " << (*it)->tag().data() << endl; + if ( (*it)->tag() == tag ) + break; + } + return it; +} + +int FieldList::findIndex( QCString tag ) +{ + FieldListIterator it = begin(); + FieldListIterator theEnd = end(); + int index = 0; + for ( ; it != theEnd; ++it, ++index ) + if ( (*it)->tag() == tag ) + return index; + + return -1; +} + +void FieldList::dump( bool recursive, int offset ) +{ + const FieldListIterator myEnd = end(); + if ( !offset ) + kdDebug( GROUPWISE_DEBUG_LIBGW ) << k_funcinfo << ( recursive ? ", recursively" : ", non-recursive" ) << endl; + for( FieldListIterator it = begin(); it != myEnd; ++it ) + { + QString s; + s.fill(' ', offset*2 ); + s.append( (*it)->tag() ); + SingleField * sf; + if ( ( sf = dynamic_cast( *it ) ) ) + { + s.append( " :" ); + s.append( sf->value().toString() ); + } + kdDebug( GROUPWISE_DEBUG_LIBGW ) << s << endl; + if ( recursive ) + { + MultiField * mf; + if ( ( mf = dynamic_cast( *it ) ) ) + mf->fields().dump( recursive, offset+1 ); + } + } +} + +void FieldList::purge() +{ + Field::FieldListIterator it = begin(); + Field::FieldListIterator theEnd = end(); + int index = 0; + for ( ; it != theEnd; ++it, ++index ) + delete *it; +} + +// THIS IS AN ATTEMPT TO HIDE THE POLYMORPHISM INSIDE THE LIST +// HOWEVER IT FAILS BECAUSE WE NEED BOTH THE ITERATOR AND THE CASTED Single|MultiField it points to + +SingleField * FieldList::findSingleField( QCString tag ) +{ + FieldListIterator it = begin(); + return findSingleField( it, tag ); +} + +SingleField * FieldList::findSingleField( FieldListIterator &it, QCString tag ) +{ + FieldListIterator found = find( it, tag ); + if ( found == end() ) + return 0; + else + return dynamic_cast( *found ); +} + +MultiField * FieldList::findMultiField( QCString tag ) +{ + FieldListIterator it = begin(); + return findMultiField( it, tag ); +} + +MultiField * FieldList::findMultiField( FieldListIterator &it, QCString tag ) +{ + FieldListIterator found = find( it, tag ); + if ( found == end() ) + return 0; + else + return dynamic_cast( *found ); +} + + +/* === FieldBase ========================================================= */ + +FieldBase::FieldBase( QCString tag, Q_UINT8 method, Q_UINT8 flags, Q_UINT8 type ) +: m_tag( tag ), m_method( method ), m_flags( flags ), m_type( type ) +{ + +} + +QCString FieldBase::tag() const +{ + return m_tag; +} + +Q_UINT8 FieldBase::method() const +{ + return m_method; +} + +Q_UINT8 FieldBase::flags() const +{ + return m_flags; +} + +Q_UINT8 FieldBase::type() const +{ + return m_type; +} + +void FieldBase::setFlags( const Q_UINT8 flags ) +{ + m_flags = flags; +} + +/* === SingleField ========================================================= */ + +SingleField::SingleField( QCString tag, Q_UINT8 method, Q_UINT8 flags, Q_UINT8 type, QVariant value ) +: FieldBase( tag, method, flags, type ), m_value( value ) +{ +} + +SingleField::SingleField( QCString tag, Q_UINT8 flags, Q_UINT8 type, QVariant value ) +: FieldBase( tag, NMFIELD_METHOD_VALID, flags, type ), m_value( value ) +{ +} + +SingleField::~SingleField() +{ +} + +void SingleField::setValue( const QVariant v ) +{ + m_value = v; +} + +QVariant SingleField::value() const +{ + return m_value; +} + +/* === MultiField ========================================================= */ + +MultiField::MultiField( QCString tag, Q_UINT8 method, Q_UINT8 flags, Q_UINT8 type, FieldList fields ) +: FieldBase( tag, method, flags, type ), m_fields( fields ) +{ +} + +MultiField::MultiField( QCString tag, Q_UINT8 method, Q_UINT8 flags, Q_UINT8 type ) +: FieldBase( tag, method, flags, type ) +{ +} + +MultiField::~MultiField() +{ + m_fields.purge(); +} + +FieldList MultiField::fields() const +{ + return m_fields; +} + +void MultiField::setFields( FieldList fields ) +{ + m_fields = fields; +} diff --git a/kopete/protocols/groupwise/libgroupwise/gwfield.h b/kopete/protocols/groupwise/libgroupwise/gwfield.h new file mode 100644 index 00000000..9362b5ad --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/gwfield.h @@ -0,0 +1,275 @@ +/* + gwfield.h - Fields used for Request/Response data in GroupWise + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Testbed + Copyright (c) 2003 by Will Stephenson + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GWFIELD_H +#define GWFIELD_H + +/* Field types */ +/* Comments: ^1 not used ^2 ignored ^3 apparently only used in _field_to_string for debug */ +/* Otherwise: widely used */ +#define NMFIELD_TYPE_INVALID 0 +/* ^1 */ +#define NMFIELD_TYPE_NUMBER 1 +/* ^1 */ +#define NMFIELD_TYPE_BINARY 2 +/* ^2? */ +#define NMFIELD_TYPE_BYTE 3 +/* ^3 */ +#define NMFIELD_TYPE_UBYTE 4 +/* ^3 */ +#define NMFIELD_TYPE_WORD 5 +/* ^3 */ +#define NMFIELD_TYPE_UWORD 6 +/* ^3 */ +#define NMFIELD_TYPE_DWORD 7 +/* ^3 */ +#define NMFIELD_TYPE_UDWORD 8 +/*WILLNOTE used in nm_send_login ( build ID ) and nm_send_message ( message type = 0 ) */ +#define NMFIELD_TYPE_ARRAY 9 +#define NMFIELD_TYPE_UTF8 10 +#define NMFIELD_TYPE_BOOL 11 +/* ^3 */ +#define NMFIELD_TYPE_MV 12 +#define NMFIELD_TYPE_DN 13 + +/* Field methods */ +#define NMFIELD_METHOD_VALID 0 +#define NMFIELD_METHOD_IGNORE 1 +#define NMFIELD_METHOD_DELETE 2 +#define NMFIELD_METHOD_DELETE_ALL 3 +#define NMFIELD_METHOD_EQUAL 4 +#define NMFIELD_METHOD_ADD 5 +#define NMFIELD_METHOD_UPDATE 6 +#define NMFIELD_METHOD_GTE 10 +#define NMFIELD_METHOD_LTE 12 +#define NMFIELD_METHOD_NE 14 +#define NMFIELD_METHOD_EXIST 15 +#define NMFIELD_METHOD_NOTEXIST 16 +#define NMFIELD_METHOD_SEARCH 17 +#define NMFIELD_METHOD_MATCHBEGIN 19 +#define NMFIELD_METHOD_MATCHEND 20 +#define NMFIELD_METHOD_NOT_ARRAY 40 +#define NMFIELD_METHOD_OR_ARRAY 41 +#define NMFIELD_METHOD_AND_ARRAY 42 + +/* Attribute Names (field tags) */ +#define NM_A_IP_ADDRESS "nnmIPAddress" +#define NM_A_PORT "nnmPort" +#define NM_A_FA_FOLDER "NM_A_FA_FOLDER" +#define NM_A_FA_CONTACT "NM_A_FA_CONTACT" +#define NM_A_FA_CONVERSATION "NM_A_FA_CONVERSATION" +#define NM_A_FA_MESSAGE "NM_A_FA_MESSAGE" +#define NM_A_FA_CONTACT_LIST "NM_A_FA_CONTACT_LIST" +#define NM_A_FA_RESULTS "NM_A_FA_RESULTS" +#define NM_A_FA_INFO_DISPLAY_ARRAY "NM_A_FA_INFO_DISPLAY_ARRAY" +#define NM_A_FA_USER_DETAILS "NM_A_FA_USER_DETAILS" +#define NM_A_SZ_OBJECT_ID "NM_A_SZ_OBJECT_ID" +#define NM_A_SZ_PARENT_ID "NM_A_SZ_PARENT_ID" +#define NM_A_SZ_SEQUENCE_NUMBER "NM_A_SZ_SEQUENCE_NUMBER" +#define NM_A_SZ_TYPE "NM_A_SZ_TYPE" +#define NM_A_SZ_STATUS "NM_A_SZ_STATUS" +#define NM_A_SZ_STATUS_TEXT "NM_A_SZ_STATUS_TEXT" +#define NM_A_SZ_DN "NM_A_SZ_DN" +#define NM_A_SZ_DISPLAY_NAME "NM_A_SZ_DISPLAY_NAME" +#define NM_A_SZ_USERID "NM_A_SZ_USERID" +#define NM_A_SZ_CREDENTIALS "NM_A_SZ_CREDENTIALS" +#define NM_A_SZ_MESSAGE_BODY "NM_A_SZ_MESSAGE_BODY" +#define NM_A_SZ_MESSAGE_TEXT "NM_A_SZ_MESSAGE_TEXT" +#define NM_A_UD_MESSAGE_TYPE "NM_A_UD_MESSAGE_TYPE" +#define NM_A_FA_PARTICIPANTS "NM_A_FA_PARTICIPANTS" +#define NM_A_FA_INVITES "NM_A_FA_INVITES" +#define NM_A_FA_EVENT "NM_A_FA_EVENT" +#define NM_A_UD_COUNT "NM_A_UD_COUNT" +#define NM_A_UD_DATE "NM_A_UD_DATE" +#define NM_A_UD_EVENT "NM_A_UD_EVENT" +#define NM_A_B_NO_CONTACTS "NM_A_B_NO_CONTACTS" +#define NM_A_B_NO_CUSTOMS "NM_A_B_NO_CUSTOMS" +#define NM_A_B_NO_PRIVACY "NM_A_B_NO_PRIVACY" +#define NM_A_B_ONLY_MODIFIED "NM_A_B_ONLY_MODIFIED" +#define NM_A_UW_STATUS "NM_A_UW_STATUS" +#define NM_A_UD_OBJECT_ID "NM_A_UD_OBJECT_ID" +#define NM_A_SZ_TRANSACTION_ID "NM_A_SZ_TRANSACTION_ID" +#define NM_A_SZ_RESULT_CODE "NM_A_SZ_RESULT_CODE" +#define NM_A_UD_BUILD "NM_A_UD_BUILD" +#define NM_A_SZ_AUTH_ATTRIBUTE "NM_A_SZ_AUTH_ATTRIBUTE" +#define NM_A_UD_KEEPALIVE "NM_A_UD_KEEPALIVE" +#define NM_A_SZ_USER_AGENT "NM_A_SZ_USER_AGENT" +#define NM_A_BLOCKING "nnmBlocking" +#define NM_A_BLOCKING_DENY_LIST "nnmBlockingDenyList" +#define NM_A_BLOCKING_ALLOW_LIST "nnmBlockingAllowList" +#define NM_A_SZ_BLOCKING_ALLOW_ITEM "NM_A_SZ_BLOCKING_ALLOW_ITEM" +#define NM_A_SZ_BLOCKING_DENY_ITEM "NM_A_SZ_BLOCKING_DENY_ITEM" +#define NM_A_LOCKED_ATTR_LIST "nnmLockedAttrList" +#define NM_A_SZ_DEPARTMENT "OU" +#define NM_A_SZ_TITLE "Title" +// GW7 +#define NM_A_FA_CUSTOM_STATUSES "NM_A_FA_CUSTOM_STATUSES" +#define NM_A_FA_STATUS "NM_A_FA_STATUS" +#define NM_A_UD_QUERY_COUNT "NM_A_UD_QUERY_COUNT" +#define NM_A_FA_CHAT "NM_A_FA_CHAT" +#define NM_A_DISPLAY_NAME "nnmDisplayName" +#define NM_A_CHAT_OWNER_DN "nnmChatOwnerDN" +#define NM_A_UD_PARTICIPANTS "NM_A_UD_PARTICIPANTS" +#define NM_A_DESCRIPTION "nnmDescription" +#define NM_A_DISCLAIMER "nnmDisclaimer" +#define NM_A_QUERY "nnmQuery" +#define NM_A_ARCHIVE "nnmArchive" +#define NM_A_MAX_USERS "nnmMaxUsers" +#define NM_A_SZ_TOPIC "NM_A_SZ_TOPIC" +#define NM_A_FA_CHAT_ACL "NM_A_FA_CHAT_ACL" +#define NM_A_FA_CHAT_ACL_ENTRY "NM_A_FA_CHAT_ACL_ENTRY" +#define NM_A_SZ_ACCESS_FLAGS "NM_A_SZ_ACCESS_FLAGS" +#define NM_A_CHAT_CREATOR_DN "nnmCreatorDN" +#define NM_A_CREATION_TIME "nnmCreationTime" +#define NM_A_UD_CHAT_RIGHTS "NM_A_UD_CHAT_RIGHTS" + +#define NM_PROTOCOL_VERSION 5 +#define NM_FIELD_TRUE "1" +#define NM_FIELD_FALSE "0" + +#define NMFIELD_MAX_STR_LENGTH 32768 + +#include +#include +#include +#include + +/** + * Fields are typed units of information interchanged between the groupwise server and its clients. + * In this implementation Fields are assumed to have a straight data flow from a Task to a socket and vice versa, + * so the @ref Task::take() is responsible for deleting incoming Fields and the netcode is responsible for + * deleting outgoing Fields. + */ + +namespace Field +{ + /** + * Abstract base class of all field types + */ + class FieldBase + { + public: + FieldBase() {} + FieldBase( QCString tag, Q_UINT8 method, Q_UINT8 flags, Q_UINT8 type ); + virtual ~FieldBase() {} + QCString tag() const; + Q_UINT8 method() const; + Q_UINT8 flags() const; + Q_UINT8 type() const; + void setFlags( const Q_UINT8 flags ); + protected: + QCString m_tag; + Q_UINT8 m_method; + Q_UINT8 m_flags; + Q_UINT8 m_type; // doch needed + }; + + typedef QValueListIterator FieldListIterator; + typedef QValueListConstIterator FieldListConstIterator; + class SingleField; + class MultiField; + + class FieldList : public QValueList + { + public: + /** + * Destructor - doesn't delete the fields because FieldLists are passed by value + */ + virtual ~FieldList(); + /** + * Locate the first occurrence of a given field in the list. Same semantics as QValueList::find(). + * @param tag The tag name of the field to search for. + * @return An iterator pointing to the first occurrence found, or end() if none was found. + */ + FieldListIterator find( QCString tag ); + /** + * Locate the first occurrence of a given field in the list, starting at the supplied iterator + * @param tag The tag name of the field to search for. + * @param it An iterator within the list, to start searching from. + * @return An iterator pointing to the first occurrence found, or end() if none was found. + */ + FieldListIterator find( FieldListIterator &it, QCString tag ); + /** + * Get the index of the first occurrence of tag, or -1 if not found + */ + int findIndex( QCString tag ); + /** + * Debug function, dumps to stdout + */ + void dump( bool recursive = false, int offset = 0 ); + /** + * Delete the contents of the list + */ + void purge(); + /** + * Utility functions for finding the first instance of a tag + * @return 0 if no field of the right tag and type was found. + */ + SingleField * findSingleField( QCString tag ); + MultiField * findMultiField( QCString tag ); + protected: + SingleField * findSingleField( FieldListIterator &it, QCString tag ); + MultiField * findMultiField( FieldListIterator &it, QCString tag ); + + }; + + /** + * This class is responsible for storing all Groupwise single value field types, eg + * NMFIELD_TYPE_INVALID, NMFIELD_TYPE_NUMBER, NMFIELD_TYPE_BINARY, NMFIELD_TYPE_BYTE + * NMFIELD_TYPE_UBYTE, NMFIELD_TYPE_DWORD, NMFIELD_TYPE_UDWORD, NMFIELD_TYPE_UTF8, NMFIELD_TYPE_BOOL + * NMFIELD_TYPE_DN + */ + class SingleField : public FieldBase + { + public: + /** + * Single field constructor + */ + SingleField( QCString tag, Q_UINT8 method, Q_UINT8 flags, Q_UINT8 type, QVariant value ); + /** + * Convenience constructor for NMFIELD_METHOD_VALID fields + */ + SingleField( QCString tag, Q_UINT8 flags, Q_UINT8 type, QVariant value ); + ~SingleField(); + void setValue( const QVariant v ); + QVariant value() const; + private: + QVariant m_value; + }; + + /** + * This class is responsible for storing multi-value GroupWise field types, eg + * NMFIELD_TYPE_ARRAY, NMFIELD_TYPE_MV + */ + class MultiField : public FieldBase + { + public: + MultiField( QCString tag, Q_UINT8 method, Q_UINT8 flags, Q_UINT8 type ); + MultiField( QCString tag, Q_UINT8 method, Q_UINT8 flags, Q_UINT8 type, FieldList fields ); + ~MultiField(); + FieldList fields() const; + void setFields( FieldList ); + private: + FieldList m_fields; // nb implicitly shared, copy-on-write - is there a case where this is bad? + }; + +} + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/gwglobal.cpp b/kopete/protocols/groupwise/libgroupwise/gwglobal.cpp new file mode 100644 index 00000000..4ea25779 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/gwglobal.cpp @@ -0,0 +1,39 @@ +/* + gwglobal.cpp - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "gwerror.h" + +namespace GroupWise +{ + ConferenceGuid::ConferenceGuid() {} + ConferenceGuid::ConferenceGuid( const QString & string ) : QString( string ) {} + + ConferenceGuid::~ConferenceGuid() {} + + bool operator==( const ConferenceGuid & g1, const ConferenceGuid & g2 ) + { + return g1.left( CONF_GUID_END ) == g2.left( CONF_GUID_END ); + } + bool operator==( const QString & s, const ConferenceGuid & g ) + { + return s.left( CONF_GUID_END ) == g.left( CONF_GUID_END ); + } + bool operator==( const ConferenceGuid & g, const QString & s ) + { + return s.left( CONF_GUID_END ) == g.left( CONF_GUID_END ); + } +} diff --git a/kopete/protocols/groupwise/libgroupwise/inputprotocolbase.cpp b/kopete/protocols/groupwise/libgroupwise/inputprotocolbase.cpp new file mode 100644 index 00000000..30627a81 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/inputprotocolbase.cpp @@ -0,0 +1,112 @@ +/* + Kopete Groupwise Protocol + inputprotocolbase.cpp - Ancestor of all protocols used for reading GroupWise input + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include "gwerror.h" + +#include "gwfield.h" +#include "inputprotocolbase.h" + +InputProtocolBase::InputProtocolBase(QObject *parent, const char *name) + : QObject(parent, name) +{ +} + + +InputProtocolBase::~InputProtocolBase() +{ +} + +void InputProtocolBase::debug( const QString &str ) +{ +#ifdef LIBGW_USE_KDEBUG + kdDebug( 14191 ) << "debug: " << str << endl; +#else + qDebug( "GW RAW PROTO: %s\n", str.ascii() ); +#endif +} + +uint InputProtocolBase::state() const +{ + return m_state; +} + +bool InputProtocolBase::readString( QString &message ) +{ + uint len; + QCString rawData; + if ( !safeReadBytes( rawData, len ) ) + return false; + message = QString::fromUtf8( rawData.data(), len - 1 ); + return true; +} + + +bool InputProtocolBase::okToProceed() +{ + if ( m_din.device() ) + { + if ( m_din.atEnd() ) + { + m_state = NeedMore; + debug( "InputProtocol::okToProceed() - Server message ended prematurely!" ); + } + else + return true; + } + return false; +} + +bool InputProtocolBase::safeReadBytes( QCString & data, uint & len ) +{ + // read the length of the bytes + Q_UINT32 val; + if ( !okToProceed() ) + return false; + m_din >> val; + m_bytes += sizeof( Q_UINT32 ); + if ( val > NMFIELD_MAX_STR_LENGTH ) + return false; + //qDebug( "EventProtocol::safeReadBytes() - expecting %i bytes", val ); + QCString temp( val ); + if ( val != 0 ) + { + if ( !okToProceed() ) + return false; + // if the server splits packets here we are in trouble, + // as there is no way to see how much data was actually read + m_din.readRawBytes( temp.data(), val ); + // the rest of the string will be filled with FF, + // so look for that in the last position instead of \0 + // this caused a crash - guessing that temp.length() is set to the number of bytes actually read... + // if ( (Q_UINT8)( * ( temp.data() + ( temp.length() - 1 ) ) ) == 0xFF ) + if ( temp.length() < ( val - 1 ) ) + { + debug( QString( "InputProtocol::safeReadBytes() - string broke, giving up, only got: %1 bytes out of %2" ).arg( temp.length() ).arg( val ) ); + m_state = NeedMore; + return false; + } + } + data = temp; + len = val; + m_bytes += val; + return true; +} + +#include "inputprotocolbase.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/inputprotocolbase.h b/kopete/protocols/groupwise/libgroupwise/inputprotocolbase.h new file mode 100644 index 00000000..efd2979f --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/inputprotocolbase.h @@ -0,0 +1,78 @@ +/* + Kopete Groupwise Protocol + inputprotocolbase.h - Ancestor of all protocols used for reading GroupWise input + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef INPUTPROTOCOLBASE_H +#define INPUTPROTOCOLBASE_H + +#include + +class Transfer; +/** +Defines a basic interface for protocols dealing with input from the GroupWise server. + +@author Kopete Developers +*/ +class InputProtocolBase : public QObject +{ +Q_OBJECT +public: + enum EventProtocolState { Success, NeedMore, OutOfSync, ProtocolError }; + InputProtocolBase(QObject *parent = 0, const char *name = 0); + ~InputProtocolBase(); + + /** + * Debug output + */ + static void debug(const QString &str); + + /** + * Returns a value describing the state of the object. + * If the object is given data to parse that does not begin with a recognised event code, + * it will become OutOfSync, to indicate that the input data probably contains leftover data not processed during a previous parse. + */ + uint state() const; + /** + * Attempt to parse the supplied data into a Transfer object + * @param bytes this will be set to the number of bytes that were successfully parsed. It is no indication of the success of the whole procedure + * @return On success, a Transfer object that the caller is responsible for deleting. It will be either an EventTransfer or a Response, delete as appropriate. On failure, returns 0. + */ + virtual Transfer * parse( const QByteArray &, uint & bytes ) = 0 ; +protected: + /** + * Reads an arbitrary string + * updates the bytes parsed counter + */ + bool readString( QString &message ); + /** + * Check that there is data to read, and set the protocol's state if there isn't any. + */ + bool okToProceed(); + /** + * read a Q_UINT32 giving the number of following bytes, then a string of that length + * updates the bytes parsed counter + * @return false if the string was broken or there was no data available at all + */ + bool safeReadBytes( QCString & data, uint & len ); + +protected: + uint m_state; + uint m_bytes; + QDataStream m_din; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/privacymanager.cpp b/kopete/protocols/groupwise/libgroupwise/privacymanager.cpp new file mode 100644 index 00000000..3d42207b --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/privacymanager.cpp @@ -0,0 +1,251 @@ +/* + Kopete Groupwise Protocol + privacymanager.cpp - stores the user's privacy information and maintains it on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "client.h" +#include "tasks/privacyitemtask.h" +#include "userdetailsmanager.h" + +#include "privacymanager.h" + +PrivacyManager::PrivacyManager( Client * client, const char *name) + : QObject(client, name), m_client( client ) +{ +} + +PrivacyManager::~PrivacyManager() +{ +} + +bool PrivacyManager::defaultAllow() +{ + return !m_defaultDeny; +} + +bool PrivacyManager::defaultDeny() +{ + return m_defaultDeny; +} + +QStringList PrivacyManager::allowList() +{ + return m_allowList; +} + +QStringList PrivacyManager::denyList() +{ + return m_denyList; +} + +bool PrivacyManager::isPrivacyLocked() +{ + return m_locked; +} + +bool PrivacyManager::isBlocked( const QString & dn ) +{ + if ( m_defaultDeny ) + return !m_allowList.contains( dn ); + else + return m_denyList.contains( dn ); +} + +void PrivacyManager::setAllow( const QString & dn ) +{ + if ( m_defaultDeny ) + { + if ( !m_allowList.contains( dn ) ) + addAllow( dn ); + } + else + { + if ( m_denyList.contains( dn ) ) + removeDeny( dn ); + } +} + +void PrivacyManager::setDeny( const QString & dn ) +{ + if ( m_defaultDeny ) + { + if ( m_allowList.contains( dn ) ) + removeAllow( dn ); + } + else + { + if ( !m_denyList.contains( dn ) ) + addDeny( dn ); + } +} + + +void PrivacyManager::setDefaultAllow( bool allow ) +{ + PrivacyItemTask * pit = new PrivacyItemTask( m_client->rootTask() ); + pit->defaultPolicy( !allow ); + connect( pit, SIGNAL( finished() ), SLOT( slotDefaultPolicyChanged() ) ); + pit->go( true ); +} + +void PrivacyManager::setDefaultDeny( bool deny ) +{ + PrivacyItemTask * pit = new PrivacyItemTask( m_client->rootTask() ); + pit->defaultPolicy( deny); + connect( pit, SIGNAL( finished() ), SLOT( slotDefaultPolicyChanged() ) ); + pit->go( true ); +} + +void PrivacyManager::addAllow( const QString & dn ) +{ + // start off a CreatePrivacyItemTask + PrivacyItemTask * pit = new PrivacyItemTask( m_client->rootTask() ); + pit->allow( dn ); + connect( pit, SIGNAL( finished() ), SLOT( slotAllowAdded() ) ); + pit->go( true ); +} + +void PrivacyManager::addDeny( const QString & dn ) +{ + // start off a CreatePrivacyItemTask + PrivacyItemTask * pit = new PrivacyItemTask( m_client->rootTask() ); + pit->deny( dn ); + connect( pit, SIGNAL( finished() ), SLOT( slotDenyAdded() ) ); + pit->go( true ); +} + +void PrivacyManager::removeAllow( const QString & dn ) +{ + PrivacyItemTask * pit = new PrivacyItemTask( m_client->rootTask() ); + pit->removeAllow( dn ); + connect( pit, SIGNAL( finished() ), SLOT( slotAllowRemoved() ) ); + pit->go( true ); +} + +void PrivacyManager::removeDeny( const QString & dn ) +{ + // start off a CreatePrivacyItemTask + PrivacyItemTask * pit = new PrivacyItemTask( m_client->rootTask() ); + pit->removeDeny( dn ); + connect( pit, SIGNAL( finished() ), SLOT( slotDenyRemoved() ) ); + pit->go( true ); +} + +void PrivacyManager::setPrivacy( bool defaultIsDeny, const QStringList & allowList, const QStringList & denyList ) +{ + if ( defaultIsDeny != m_defaultDeny ) + setDefaultDeny( defaultIsDeny ); + // find the DNs no longer in the allow list + QStringList allowsToRemove = difference( m_allowList, allowList ); + // find the DNs no longer in the deny list + QStringList denysToRemove = difference( m_denyList, denyList ); + // find the DNs new in the allow list + QStringList allowsToAdd = difference( allowList, m_allowList ); + // find the DNs new in the deny list + QStringList denysToAdd = difference( denyList, m_denyList ); + + QStringList::ConstIterator end = allowsToRemove.end(); + for ( QStringList::ConstIterator it = allowsToRemove.begin(); it != end; ++it ) + removeAllow( *it ); + end = denysToRemove.end(); + for ( QStringList::ConstIterator it = denysToRemove.begin(); it != end; ++it ) + removeDeny( *it ); + end = allowsToAdd.end(); + for ( QStringList::ConstIterator it = allowsToAdd.begin(); it != end; ++it ) + addAllow( *it ); + end = denysToAdd.end(); + for ( QStringList::ConstIterator it = denysToAdd.begin(); it != end; ++it ) + addDeny( *it ); +} + +void PrivacyManager::slotGotPrivacySettings( bool locked, bool defaultDeny, const QStringList & allowList, const QStringList & denyList ) +{ + m_locked = locked; + m_defaultDeny = defaultDeny; + m_allowList = allowList; + m_denyList = denyList; +} + +void PrivacyManager::getDetailsForPrivacyLists() +{ + if ( !m_allowList.isEmpty() ) + { + m_client->userDetailsManager()->requestDetails( m_allowList ); + } + if ( !m_denyList.isEmpty() ) + m_client->userDetailsManager()->requestDetails( m_denyList ); +} + +void PrivacyManager::slotDefaultPolicyChanged() +{ + PrivacyItemTask * pit = ( PrivacyItemTask * )sender(); + if ( pit->success() ) + m_defaultDeny = pit->defaultDeny(); +} + +void PrivacyManager::slotAllowAdded() +{ + PrivacyItemTask * pit = ( PrivacyItemTask * )sender(); + if ( pit->success() ) + { + m_allowList.append( pit->dn() ); + emit privacyChanged( pit->dn(), isBlocked( pit->dn() ) ); + } +} + +void PrivacyManager::slotDenyAdded() +{ + PrivacyItemTask * pit = ( PrivacyItemTask * )sender(); + if ( pit->success() ) + { + m_denyList.append( pit->dn() ); + emit privacyChanged( pit->dn(), isBlocked( pit->dn() ) ); + } +} + +void PrivacyManager::slotAllowRemoved() +{ + PrivacyItemTask * pit = ( PrivacyItemTask * )sender(); + if ( pit->success() ) + { + m_allowList.remove( pit->dn() ); + emit privacyChanged( pit->dn(), isBlocked( pit->dn() ) ); + } +} + +void PrivacyManager::slotDenyRemoved() +{ + PrivacyItemTask * pit = ( PrivacyItemTask * )sender(); + if ( pit->success() ) + { + m_denyList.remove( pit->dn() ); + emit privacyChanged( pit->dn(), isBlocked( pit->dn() ) ); + } +} + +QStringList PrivacyManager::difference( const QStringList & lhs, const QStringList & rhs ) +{ + QStringList diff; + const QStringList::ConstIterator lhsEnd = lhs.end(); + const QStringList::ConstIterator rhsEnd = rhs.end(); + for ( QStringList::ConstIterator lhsIt = lhs.begin(); lhsIt != lhsEnd; ++lhsIt ) + { + if ( rhs.find( *lhsIt ) == rhsEnd ) + diff.append( *lhsIt ); + } + return diff; +} +#include "privacymanager.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/privacymanager.h b/kopete/protocols/groupwise/libgroupwise/privacymanager.h new file mode 100644 index 00000000..102c2b0a --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/privacymanager.h @@ -0,0 +1,88 @@ +/* + Kopete Groupwise Protocol + privacymanager.cpp - stores the user's privacy information and maintains it on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef PRIVACYMANAGER_H +#define PRIVACYMANAGER_H + +#include +#include + +class Client; + +/** +Keeps a record of the server side privacy allow and deny lists, default policy and whether the user is allowed to change privacy settings + +@author SUSE AG +*/ +class PrivacyManager : public QObject +{ +Q_OBJECT +public: + PrivacyManager( Client * client, const char *name = 0); + ~PrivacyManager(); + // accessors + bool isBlocked( const QString & dn ); + QStringList allowList(); + QStringList denyList(); + bool isPrivacyLocked(); + bool defaultDeny(); + bool defaultAllow(); + // mutators + void setDefaultAllow( bool allow ); + void setDefaultDeny( bool deny ); + void setAllow( const QString & dn ); + void setDeny( const QString & dn ); + void getDetailsForPrivacyLists(); + // change everything at once + void setPrivacy( bool defaultIsDeny, const QStringList & allowList, const QStringList & denyList ); + +signals: + void privacyChanged( const QString &dn, bool allowed ); +public slots: + /** + * Used to initialise the privacy manager using the server side privacy list + */ + void slotGotPrivacySettings( bool locked, bool defaultDeny, const QStringList & allowList, const QStringList & denyList ); +protected: + void addAllow( const QString & dn ); + void addDeny( const QString & dn ); + void removeAllow( const QString & dn ); + void removeDeny( const QString & dn ); + /** + * A set difference function + * @param lhs The set of strings to be subtracted from + * @param rhs The set of string to subtract + * @return The difference between the two sets + */ + QStringList difference( const QStringList & lhs, const QStringList & rhs ); +protected slots: + // Receive the results of Tasks manipulating the privacy lists + void slotDefaultPolicyChanged(); + void slotAllowAdded(); + void slotDenyAdded(); + void slotAllowRemoved(); + void slotDenyRemoved(); +private: + Client * m_client; + bool m_locked; + bool m_defaultDeny; + QStringList m_allowList; + QStringList m_denyList; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/qca/COPYING b/kopete/protocols/groupwise/libgroupwise/qca/COPYING new file mode 100644 index 00000000..b1e3f5a2 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/qca/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 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. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; 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. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/kopete/protocols/groupwise/libgroupwise/qca/INSTALL b/kopete/protocols/groupwise/libgroupwise/qca/INSTALL new file mode 100644 index 00000000..8dd34099 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/qca/INSTALL @@ -0,0 +1,12 @@ +Installing QCA +-------------- + +Installation should be straightforward: + + ./configure + make + make install + +NOTE: You may also need to run '/sbin/ldconfig' or a similar tool to + get the new library files recognized by the system. If you are + using Linux, just run it for good measure. diff --git a/kopete/protocols/groupwise/libgroupwise/qca/Makefile.am b/kopete/protocols/groupwise/libgroupwise/qca/Makefile.am new file mode 100644 index 00000000..af437a64 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/qca/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = src diff --git a/kopete/protocols/groupwise/libgroupwise/qca/README b/kopete/protocols/groupwise/libgroupwise/qca/README new file mode 100644 index 00000000..0641713a --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/qca/README @@ -0,0 +1,29 @@ +Qt Cryptographic Architecture +----------------------------- +Version: API v1.0, Plugin v1 +Author: Justin Karneges +Date: September 10th 2003 + +This library provides an easy API for the following features: + + SSL/TLS + X509 + SASL + RSA + Hashing (SHA1, MD5) + Ciphers (BlowFish, 3DES, AES) + +Functionality is supplied via plugins. This is useful for avoiding +dependence on a particular crypto library and makes upgrading easier, +as there is no need to recompile your application when adding or +upgrading a crypto plugin. Also, by pushing crypto functionality into +plugins, your application is free of legal issues, such as export +regulation. + +And of course, you get a very simple crypto API for Qt, where you can +do things like: + + QString hash = QCA::SHA1::hashToString(blockOfData); + +Have fun! + diff --git a/kopete/protocols/groupwise/libgroupwise/qca/TODO b/kopete/protocols/groupwise/libgroupwise/qca/TODO new file mode 100644 index 00000000..bc8247e0 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/qca/TODO @@ -0,0 +1,6 @@ +* plugins: thread safety ? + +* dsa +* diffie-hellman +* entropy + diff --git a/kopete/protocols/groupwise/libgroupwise/qca/src/Makefile.am b/kopete/protocols/groupwise/libgroupwise/qca/src/Makefile.am new file mode 100644 index 00000000..b7ae1bb4 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/qca/src/Makefile.am @@ -0,0 +1,8 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libqca.la +INCLUDES = $(all_includes) + +libqca_la_SOURCES = \ + qca.cpp +libqca_la_LIBADD = -lqt-mt diff --git a/kopete/protocols/groupwise/libgroupwise/qca/src/qca.cpp b/kopete/protocols/groupwise/libgroupwise/qca/src/qca.cpp new file mode 100644 index 00000000..9edb0fb3 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/qca/src/qca.cpp @@ -0,0 +1,1486 @@ +/* + * qca.cpp - Qt Cryptographic Architecture + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"qca.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include"qcaprovider.h" + +#if defined(Q_OS_WIN32) +#define PLUGIN_EXT "dll" +#elif defined(Q_OS_MAC) +#define PLUGIN_EXT "dylib" +#else +#define PLUGIN_EXT "so" +#endif + +using namespace QCA; + +class ProviderItem +{ +public: + QCAProvider *p; + QString fname; + + static ProviderItem *load(const QString &fname) + { + QLibrary *lib = new QLibrary(fname); + if(!lib->load()) { + delete lib; + return 0; + } + void *s = lib->resolve("createProvider"); + if(!s) { + delete lib; + return 0; + } + QCAProvider *(*createProvider)() = (QCAProvider *(*)())s; + QCAProvider *p = createProvider(); + if(!p) { + delete lib; + return 0; + } + ProviderItem *i = new ProviderItem(lib, p); + i->fname = fname; + return i; + } + + static ProviderItem *fromClass(QCAProvider *p) + { + ProviderItem *i = new ProviderItem(0, p); + return i; + } + + ~ProviderItem() + { + delete p; + delete lib; + } + + void ensureInit() + { + if(init_done) + return; + init_done = true; + p->init(); + } + +private: + QLibrary *lib; + bool init_done; + + ProviderItem(QLibrary *_lib, QCAProvider *_p) + { + lib = _lib; + p = _p; + init_done = false; + } +}; + +static QPtrList providerList; +static bool qca_init = false; + +static bool plugin_have(const QString &fname) +{ + QPtrListIterator it(providerList); + for(ProviderItem *i; (i = it.current()); ++it) { + if(i->fname == fname) + return true; + } + return false; +} + +static void plugin_scan() +{ + QStringList dirs = QApplication::libraryPaths(); + for(QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) { + QDir libpath(*it); + QDir dir(libpath.filePath("crypto")); + if(!dir.exists()) + continue; + + QStringList list = dir.entryList(); + for(QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) { + QFileInfo fi(dir.filePath(*it)); + if(fi.isDir()) + continue; + if(fi.extension() != PLUGIN_EXT) + continue; + QString fname = fi.filePath(); + + // don't load the same plugin again! + if(plugin_have(fname)) + continue; + //printf("f=[%s]\n", fname.latin1()); + + ProviderItem *i = ProviderItem::load(fname); + if(!i) + continue; + if(i->p->qcaVersion() != QCA_PLUGIN_VERSION) { + delete i; + continue; + } + + providerList.append(i); + } + } +} + +static void plugin_addClass(QCAProvider *p) +{ + ProviderItem *i = ProviderItem::fromClass(p); + providerList.prepend(i); +} + +static void plugin_unloadall() +{ + providerList.clear(); +} + +static int plugin_caps() +{ + int caps = 0; + QPtrListIterator it(providerList); + for(ProviderItem *i; (i = it.current()); ++it) + caps |= i->p->capabilities(); + return caps; +} + +QString QCA::arrayToHex(const QByteArray &a) +{ + QString out; + for(int n = 0; n < (int)a.size(); ++n) { + QString str; + str.sprintf("%02x", (uchar)a[n]); + out.append(str); + } + return out; +} + +QByteArray QCA::hexToArray(const QString &str) +{ + QByteArray out(str.length() / 2); + int at = 0; + for(int n = 0; n + 1 < (int)str.length(); n += 2) { + uchar a = str[n]; + uchar b = str[n+1]; + uchar c = ((a & 0x0f) << 4) + (b & 0x0f); + out[at++] = c; + } + return out; +} + +void QCA::init() +{ + if(qca_init) + return; + qca_init = true; + providerList.setAutoDelete(true); +} + +bool QCA::isSupported(int capabilities) +{ + init(); + + int caps = plugin_caps(); + if(caps & capabilities) + return true; + + // ok, try scanning for new stuff + plugin_scan(); + caps = plugin_caps(); + if(caps & capabilities) + return true; + + return false; +} + +void QCA::insertProvider(QCAProvider *p) +{ + plugin_addClass(p); +} + +void QCA::unloadAllPlugins() +{ + plugin_unloadall(); +} + +static void *getContext(int cap) +{ + init(); + + // this call will also trip a scan for new plugins if needed + if(!QCA::isSupported(cap)) + return 0; + + QPtrListIterator it(providerList); + for(ProviderItem *i; (i = it.current()); ++it) { + if(i->p->capabilities() & cap) { + i->ensureInit(); + return i->p->context(cap); + } + } + return 0; +} + + +//---------------------------------------------------------------------------- +// Hash +//---------------------------------------------------------------------------- +class Hash::Private +{ +public: + Private() + { + c = 0; + } + + ~Private() + { + delete c; + } + + void reset() + { + c->reset(); + } + + QCA_HashContext *c; +}; + +Hash::Hash(QCA_HashContext *c) +{ + d = new Private; + d->c = c; +} + +Hash::Hash(const Hash &from) +{ + d = new Private; + *this = from; +} + +Hash & Hash::operator=(const Hash &from) +{ + delete d->c; + d->c = from.d->c->clone(); + return *this; +} + +Hash::~Hash() +{ + delete d; +} + +void Hash::clear() +{ + d->reset(); +} + +void Hash::update(const QByteArray &a) +{ + d->c->update(a.data(), a.size()); +} + +QByteArray Hash::final() +{ + QByteArray buf; + d->c->final(&buf); + return buf; +} + + +//---------------------------------------------------------------------------- +// Cipher +//---------------------------------------------------------------------------- +class Cipher::Private +{ +public: + Private() + { + c = 0; + } + + ~Private() + { + delete c; + } + + void reset() + { + dir = Encrypt; + key.resize(0); + iv.resize(0); + err = false; + } + + QCA_CipherContext *c; + int dir; + int mode; + QByteArray key, iv; + bool err; +}; + +Cipher::Cipher(QCA_CipherContext *c, int dir, int mode, const QByteArray &key, const QByteArray &iv, bool pad) +{ + d = new Private; + d->c = c; + reset(dir, mode, key, iv, pad); +} + +Cipher::Cipher(const Cipher &from) +{ + d = new Private; + *this = from; +} + +Cipher & Cipher::operator=(const Cipher &from) +{ + delete d->c; + d->c = from.d->c->clone(); + d->dir = from.d->dir; + d->mode = from.d->mode; + d->key = from.d->key.copy(); + d->iv = from.d->iv.copy(); + d->err = from.d->err; + return *this; +} + +Cipher::~Cipher() +{ + delete d; +} + +QByteArray Cipher::dyn_generateKey(int size) const +{ + QByteArray buf; + if(size != -1) + buf.resize(size); + else + buf.resize(d->c->keySize()); + if(!d->c->generateKey(buf.data(), size)) + return QByteArray(); + return buf; +} + +QByteArray Cipher::dyn_generateIV() const +{ + QByteArray buf(d->c->blockSize()); + if(!d->c->generateIV(buf.data())) + return QByteArray(); + return buf; +} + +void Cipher::reset(int dir, int mode, const QByteArray &key, const QByteArray &iv, bool pad) +{ + d->reset(); + + d->dir = dir; + d->mode = mode; + d->key = key.copy(); + d->iv = iv.copy(); + if(!d->c->setup(d->dir, d->mode, d->key.isEmpty() ? 0: d->key.data(), d->key.size(), d->iv.isEmpty() ? 0 : d->iv.data(), pad)) { + d->err = true; + return; + } +} + +bool Cipher::update(const QByteArray &a) +{ + if(d->err) + return false; + + if(!a.isEmpty()) { + if(!d->c->update(a.data(), a.size())) { + d->err = true; + return false; + } + } + return true; +} + +QByteArray Cipher::final(bool *ok) +{ + if(ok) + *ok = false; + if(d->err) + return QByteArray(); + + QByteArray out; + if(!d->c->final(&out)) { + d->err = true; + return QByteArray(); + } + if(ok) + *ok = true; + return out; +} + + +//---------------------------------------------------------------------------- +// SHA1 +//---------------------------------------------------------------------------- +SHA1::SHA1() +:Hash((QCA_HashContext *)getContext(CAP_SHA1)) +{ +} + + +//---------------------------------------------------------------------------- +// SHA256 +//---------------------------------------------------------------------------- +SHA256::SHA256() +:Hash((QCA_HashContext *)getContext(CAP_SHA256)) +{ +} + + +//---------------------------------------------------------------------------- +// MD5 +//---------------------------------------------------------------------------- +MD5::MD5() +:Hash((QCA_HashContext *)getContext(CAP_MD5)) +{ +} + + +//---------------------------------------------------------------------------- +// BlowFish +//---------------------------------------------------------------------------- +BlowFish::BlowFish(int dir, int mode, const QByteArray &key, const QByteArray &iv, bool pad) +:Cipher((QCA_CipherContext *)getContext(CAP_BlowFish), dir, mode, key, iv, pad) +{ +} + + +//---------------------------------------------------------------------------- +// TripleDES +//---------------------------------------------------------------------------- +TripleDES::TripleDES(int dir, int mode, const QByteArray &key, const QByteArray &iv, bool pad) +:Cipher((QCA_CipherContext *)getContext(CAP_TripleDES), dir, mode, key, iv, pad) +{ +} + + +//---------------------------------------------------------------------------- +// AES128 +//---------------------------------------------------------------------------- +AES128::AES128(int dir, int mode, const QByteArray &key, const QByteArray &iv, bool pad) +:Cipher((QCA_CipherContext *)getContext(CAP_AES128), dir, mode, key, iv, pad) +{ +} + + +//---------------------------------------------------------------------------- +// AES256 +//---------------------------------------------------------------------------- +AES256::AES256(int dir, int mode, const QByteArray &key, const QByteArray &iv, bool pad) +:Cipher((QCA_CipherContext *)getContext(CAP_AES256), dir, mode, key, iv, pad) +{ +} + + +//---------------------------------------------------------------------------- +// RSAKey +//---------------------------------------------------------------------------- +class RSAKey::Private +{ +public: + Private() + { + c = 0; + } + + ~Private() + { + delete c; + } + + QCA_RSAKeyContext *c; +}; + +RSAKey::RSAKey() +{ + d = new Private; + d->c = (QCA_RSAKeyContext *)getContext(CAP_RSA); +} + +RSAKey::RSAKey(const RSAKey &from) +{ + d = new Private; + *this = from; +} + +RSAKey & RSAKey::operator=(const RSAKey &from) +{ + delete d->c; + d->c = from.d->c->clone(); + return *this; +} + +RSAKey::~RSAKey() +{ + delete d; +} + +bool RSAKey::isNull() const +{ + return d->c->isNull(); +} + +bool RSAKey::havePublic() const +{ + return d->c->havePublic(); +} + +bool RSAKey::havePrivate() const +{ + return d->c->havePrivate(); +} + +QByteArray RSAKey::toDER(bool publicOnly) const +{ + QByteArray out; + if(!d->c->toDER(&out, publicOnly)) + return QByteArray(); + return out; +} + +bool RSAKey::fromDER(const QByteArray &a) +{ + return d->c->createFromDER(a.data(), a.size()); +} + +QString RSAKey::toPEM(bool publicOnly) const +{ + QByteArray out; + if(!d->c->toPEM(&out, publicOnly)) + return QByteArray(); + + QCString cs; + cs.resize(out.size()+1); + memcpy(cs.data(), out.data(), out.size()); + return QString::fromLatin1(cs); +} + +bool RSAKey::fromPEM(const QString &str) +{ + QCString cs = str.latin1(); + QByteArray a(cs.length()); + memcpy(a.data(), cs.data(), a.size()); + return d->c->createFromPEM(a.data(), a.size()); +} + +bool RSAKey::fromNative(void *p) +{ + return d->c->createFromNative(p); +} + +bool RSAKey::encrypt(const QByteArray &a, QByteArray *b, bool oaep) const +{ + QByteArray out; + if(!d->c->encrypt(a, &out, oaep)) + return false; + *b = out; + return true; +} + +bool RSAKey::decrypt(const QByteArray &a, QByteArray *b, bool oaep) const +{ + QByteArray out; + if(!d->c->decrypt(a, &out, oaep)) + return false; + *b = out; + return true; +} + +bool RSAKey::generate(unsigned int bits) +{ + return d->c->generate(bits); +} + + +//---------------------------------------------------------------------------- +// RSA +//---------------------------------------------------------------------------- +RSA::RSA() +{ +} + +RSA::~RSA() +{ +} + +RSAKey RSA::key() const +{ + return v_key; +} + +void RSA::setKey(const RSAKey &k) +{ + v_key = k; +} + +bool RSA::encrypt(const QByteArray &a, QByteArray *b, bool oaep) const +{ + if(v_key.isNull()) + return false; + return v_key.encrypt(a, b, oaep); +} + +bool RSA::decrypt(const QByteArray &a, QByteArray *b, bool oaep) const +{ + if(v_key.isNull()) + return false; + return v_key.decrypt(a, b, oaep); +} + +RSAKey RSA::generateKey(unsigned int bits) +{ + RSAKey k; + k.generate(bits); + return k; +} + + +//---------------------------------------------------------------------------- +// Cert +//---------------------------------------------------------------------------- +class Cert::Private +{ +public: + Private() + { + c = 0; + } + + ~Private() + { + delete c; + } + + QCA_CertContext *c; +}; + +Cert::Cert() +{ + d = new Private; + // crash because this is returning 0 + d->c = (QCA_CertContext *)getContext(CAP_X509); +} + +Cert::Cert(const Cert &from) +{ + d = new Private; + *this = from; +} + +Cert & Cert::operator=(const Cert &from) +{ + delete d->c; + if ( from.d->c ) + d->c = from.d->c->clone(); + else + d->c = 0; + return *this; +} + +Cert::~Cert() +{ + delete d; +} + +void Cert::fromContext(QCA_CertContext *ctx) +{ + delete d->c; + d->c = ctx; +} + +bool Cert::isNull() const +{ + return d->c->isNull(); +} + +QString Cert::commonName() const +{ + CertProperties props = subject(); + return props["CN"]; +} + +QString Cert::serialNumber() const +{ + return d->c->serialNumber(); +} + +QString Cert::subjectString() const +{ + return d->c->subjectString(); +} + +QString Cert::issuerString() const +{ + return d->c->issuerString(); +} + +CertProperties Cert::subject() const +{ + QValueList list = d->c->subject(); + CertProperties props; + for(QValueList::ConstIterator it = list.begin(); it != list.end(); ++it) + props[(*it).var] = (*it).val; + return props; +} + +CertProperties Cert::issuer() const +{ + QValueList list = d->c->issuer(); + CertProperties props; + for(QValueList::ConstIterator it = list.begin(); it != list.end(); ++it) + props[(*it).var] = (*it).val; + return props; +} + +QDateTime Cert::notBefore() const +{ + return d->c->notBefore(); +} + +QDateTime Cert::notAfter() const +{ + return d->c->notAfter(); +} + +QByteArray Cert::toDER() const +{ + QByteArray out; + if(!d->c->toDER(&out)) + return QByteArray(); + return out; +} + +bool Cert::fromDER(const QByteArray &a) +{ + return d->c->createFromDER(a.data(), a.size()); +} + +QString Cert::toPEM() const +{ + QByteArray out; + if(!d->c->toPEM(&out)) + return QByteArray(); + + QCString cs; + cs.resize(out.size()+1); + memcpy(cs.data(), out.data(), out.size()); + return QString::fromLatin1(cs); +} + +bool Cert::fromPEM(const QString &str) +{ + QCString cs = str.latin1(); + QByteArray a(cs.length()); + memcpy(a.data(), cs.data(), a.size()); + return d->c->createFromPEM(a.data(), a.size()); +} + + +//---------------------------------------------------------------------------- +// TLS +//---------------------------------------------------------------------------- +class TLS::Private +{ +public: + Private() + { + c = (QCA_TLSContext *)getContext(CAP_TLS); + } + + ~Private() + { + delete c; + } + + void reset() + { + handshaken = false; + closing = false; + in.resize(0); + out.resize(0); + from_net.resize(0); + to_net.resize(0); + host = ""; + hostMismatch = false; + // this causes the crash, because the Cert ctor is setting a null context + cert = Cert(); + bytesEncoded = 0; + tryMore = false; + } + + void appendArray(QByteArray *a, const QByteArray &b) + { + int oldsize = a->size(); + a->resize(oldsize + b.size()); + memcpy(a->data() + oldsize, b.data(), b.size()); + } + + Cert cert; + QCA_TLSContext *c; + QByteArray in, out, to_net, from_net; + int bytesEncoded; + bool tryMore; + bool handshaken; + QString host; + bool hostMismatch; + bool closing; + + Cert ourCert; + RSAKey ourKey; + QPtrList store; +}; + +TLS::TLS(QObject *parent) +:QObject(parent) +{ + d = new Private; +} + +TLS::~TLS() +{ + delete d; +} + +void TLS::setCertificate(const Cert &cert, const RSAKey &key) +{ + d->ourCert = cert; + d->ourKey = key; +} + +void TLS::setCertificateStore(const QPtrList &store) +{ + // convert the cert list into a context list + d->store.clear(); + QPtrListIterator it(store); + for(Cert *cert; (cert = it.current()); ++it) + d->store.append(cert->d->c); +} + +void TLS::reset() +{ + d->reset(); +} + +bool TLS::startClient(const QString &host) +{ + d->reset(); + d->host = host; + + if(!d->c->startClient(d->store, *d->ourCert.d->c, *d->ourKey.d->c)) + return false; + QTimer::singleShot(0, this, SLOT(update())); + return true; +} + +bool TLS::startServer() +{ + d->reset(); + + if(!d->c->startServer(d->store, *d->ourCert.d->c, *d->ourKey.d->c)) + return false; + QTimer::singleShot(0, this, SLOT(update())); + return true; +} + +void TLS::close() +{ + if(!d->handshaken || d->closing) + return; + + d->closing = true; + QTimer::singleShot(0, this, SLOT(update())); +} + +bool TLS::isHandshaken() const +{ + return d->handshaken; +} + +void TLS::write(const QByteArray &a) +{ + d->appendArray(&d->out, a); + update(); +} + +QByteArray TLS::read() +{ + QByteArray a = d->in.copy(); + d->in.resize(0); + return a; +} + +void TLS::writeIncoming(const QByteArray &a) +{ + d->appendArray(&d->from_net, a); + update(); +} + +QByteArray TLS::readOutgoing() +{ + QByteArray a = d->to_net.copy(); + d->to_net.resize(0); + return a; +} + +QByteArray TLS::readUnprocessed() +{ + QByteArray a = d->from_net.copy(); + d->from_net.resize(0); + return a; +} + +const Cert & TLS::peerCertificate() const +{ + return d->cert; +} + +int TLS::certificateValidityResult() const +{ + if(d->hostMismatch) + return QCA::TLS::HostMismatch; + else + return d->c->validityResult(); +} + +void TLS::update() +{ + bool force_read = false; + bool eof = false; + bool done = false; + QGuardedPtr self = this; + + if(d->closing) { + QByteArray a; + int r = d->c->shutdown(d->from_net, &a); + d->from_net.resize(0); + if(r == QCA_TLSContext::Error) { + reset(); + error(ErrHandshake); + return; + } + if(r == QCA_TLSContext::Success) { + d->from_net = d->c->unprocessed().copy(); + done = true; + } + d->appendArray(&d->to_net, a); + } + else { + if(!d->handshaken) { + QByteArray a; + int r = d->c->handshake(d->from_net, &a); + d->from_net.resize(0); + if(r == QCA_TLSContext::Error) { + reset(); + error(ErrHandshake); + return; + } + d->appendArray(&d->to_net, a); + if(r == QCA_TLSContext::Success) { + QCA_CertContext *cc = d->c->peerCertificate(); + if(cc && !d->host.isEmpty() && d->c->validityResult() == QCA::TLS::Valid) { + if(!cc->matchesAddress(d->host)) + d->hostMismatch = true; + } + d->cert.fromContext(cc); + d->handshaken = true; + handshaken(); + if(!self) + return; + + // there is a teeny tiny possibility that incoming data awaits. let us get it. + force_read = true; + } + } + + if(d->handshaken) { + if(!d->out.isEmpty() || d->tryMore) { + d->tryMore = false; + QByteArray a; + int enc; + bool more = false; + bool ok = d->c->encode(d->out, &a, &enc); + eof = d->c->eof(); + if(ok && enc < (int)d->out.size()) + more = true; + d->out.resize(0); + if(!eof) { + if(!ok) { + reset(); + error(ErrCrypt); + return; + } + d->bytesEncoded += enc; + if(more) + d->tryMore = true; + d->appendArray(&d->to_net, a); + } + } + if(!d->from_net.isEmpty() || force_read) { + QByteArray a, b; + bool ok = d->c->decode(d->from_net, &a, &b); + eof = d->c->eof(); + d->from_net.resize(0); + if(!ok) { + reset(); + error(ErrCrypt); + return; + } + d->appendArray(&d->in, a); + d->appendArray(&d->to_net, b); + } + + if(!d->in.isEmpty()) { + readyRead(); + if(!self) + return; + } + } + } + + if(!d->to_net.isEmpty()) { + int bytes = d->bytesEncoded; + d->bytesEncoded = 0; + readyReadOutgoing(bytes); + if(!self) + return; + } + + if(eof) { + close(); + if(!self) + return; + return; + } + + if(d->closing && done) { + reset(); + closed(); + } +} + + +//---------------------------------------------------------------------------- +// SASL +//---------------------------------------------------------------------------- +QString saslappname = "qca"; +class SASL::Private +{ +public: + Private() + { + c = (QCA_SASLContext *)getContext(CAP_SASL); + } + + ~Private() + { + delete c; + } + + void setSecurityProps() + { + c->setSecurityProps(noPlain, noActive, noDict, noAnon, reqForward, reqCreds, reqMutual, ssfmin, ssfmax, ext_authid, ext_ssf); + } + + // security opts + bool noPlain, noActive, noDict, noAnon, reqForward, reqCreds, reqMutual; + int ssfmin, ssfmax; + QString ext_authid; + int ext_ssf; + + bool tried; + QCA_SASLContext *c; + QHostAddress localAddr, remoteAddr; + int localPort, remotePort; + QByteArray stepData; + bool allowCSF; + bool first, server; + + QByteArray inbuf, outbuf; +}; + +SASL::SASL(QObject *parent) +:QObject(parent) +{ + d = new Private; + reset(); +} + +SASL::~SASL() +{ + delete d; +} + +void SASL::setAppName(const QString &name) +{ + saslappname = name; +} + +void SASL::reset() +{ + d->localPort = -1; + d->remotePort = -1; + + d->noPlain = false; + d->noActive = false; + d->noDict = false; + d->noAnon = false; + d->reqForward = false; + d->reqCreds = false; + d->reqMutual = false; + d->ssfmin = 0; + d->ssfmax = 0; + d->ext_authid = ""; + d->ext_ssf = 0; + + d->inbuf.resize(0); + d->outbuf.resize(0); + + d->c->reset(); +} + +int SASL::errorCondition() const +{ + return d->c->errorCond(); +} + +void SASL::setAllowPlain(bool b) +{ + d->noPlain = !b; +} + +void SASL::setAllowAnonymous(bool b) +{ + d->noAnon = !b; +} + +void SASL::setAllowActiveVulnerable(bool b) +{ + d->noActive = !b; +} + +void SASL::setAllowDictionaryVulnerable(bool b) +{ + d->noDict = !b; +} + +void SASL::setRequireForwardSecrecy(bool b) +{ + d->reqForward = b; +} + +void SASL::setRequirePassCredentials(bool b) +{ + d->reqCreds = b; +} + +void SASL::setRequireMutualAuth(bool b) +{ + d->reqMutual = b; +} + +void SASL::setMinimumSSF(int x) +{ + d->ssfmin = x; +} + +void SASL::setMaximumSSF(int x) +{ + d->ssfmax = x; +} + +void SASL::setExternalAuthID(const QString &authid) +{ + d->ext_authid = authid; +} + +void SASL::setExternalSSF(int x) +{ + d->ext_ssf = x; +} + +void SASL::setLocalAddr(const QHostAddress &addr, Q_UINT16 port) +{ + d->localAddr = addr; + d->localPort = port; +} + +void SASL::setRemoteAddr(const QHostAddress &addr, Q_UINT16 port) +{ + d->remoteAddr = addr; + d->remotePort = port; +} + +bool SASL::startClient(const QString &service, const QString &host, const QStringList &mechlist, bool allowClientSendFirst) +{ + QCA_SASLHostPort la, ra; + if(d->localPort != -1) { + la.addr = d->localAddr; + la.port = d->localPort; + } + if(d->remotePort != -1) { + ra.addr = d->remoteAddr; + ra.port = d->remotePort; + } + + d->allowCSF = allowClientSendFirst; + d->c->setCoreProps(service, host, d->localPort != -1 ? &la : 0, d->remotePort != -1 ? &ra : 0); + d->setSecurityProps(); + + if(!d->c->clientStart(mechlist)) + return false; + d->first = true; + d->server = false; + d->tried = false; + QTimer::singleShot(0, this, SLOT(tryAgain())); + return true; +} + +bool SASL::startServer(const QString &service, const QString &host, const QString &realm, QStringList *mechlist) +{ + QCA_SASLHostPort la, ra; + if(d->localPort != -1) { + la.addr = d->localAddr; + la.port = d->localPort; + } + if(d->remotePort != -1) { + ra.addr = d->remoteAddr; + ra.port = d->remotePort; + } + + d->c->setCoreProps(service, host, d->localPort != -1 ? &la : 0, d->remotePort != -1 ? &ra : 0); + d->setSecurityProps(); + + if(!d->c->serverStart(realm, mechlist, saslappname)) + return false; + d->first = true; + d->server = true; + d->tried = false; + return true; +} + +void SASL::putServerFirstStep(const QString &mech) +{ + int r = d->c->serverFirstStep(mech, 0); + handleServerFirstStep(r); +} + +void SASL::putServerFirstStep(const QString &mech, const QByteArray &clientInit) +{ + int r = d->c->serverFirstStep(mech, &clientInit); + handleServerFirstStep(r); +} + +void SASL::handleServerFirstStep(int r) +{ + if(r == QCA_SASLContext::Success) + authenticated(); + else if(r == QCA_SASLContext::Continue) + nextStep(d->c->result()); + else if(r == QCA_SASLContext::AuthCheck) + tryAgain(); + else + error(ErrAuth); +} + +void SASL::putStep(const QByteArray &stepData) +{ + d->stepData = stepData.copy(); + tryAgain(); +} + +void SASL::setUsername(const QString &user) +{ + d->c->setClientParams(&user, 0, 0, 0); +} + +void SASL::setAuthzid(const QString &authzid) +{ + d->c->setClientParams(0, &authzid, 0, 0); +} + +void SASL::setPassword(const QString &pass) +{ + d->c->setClientParams(0, 0, &pass, 0); +} + +void SASL::setRealm(const QString &realm) +{ + d->c->setClientParams(0, 0, 0, &realm); +} + +void SASL::continueAfterParams() +{ + tryAgain(); +} + +void SASL::continueAfterAuthCheck() +{ + tryAgain(); +} + +void SASL::tryAgain() +{ + int r; + + if(d->server) { + if(!d->tried) { + r = d->c->nextStep(d->stepData); + d->tried = true; + } + else { + r = d->c->tryAgain(); + } + + if(r == QCA_SASLContext::Error) { + error(ErrAuth); + return; + } + else if(r == QCA_SASLContext::Continue) { + d->tried = false; + nextStep(d->c->result()); + return; + } + else if(r == QCA_SASLContext::AuthCheck) { + authCheck(d->c->username(), d->c->authzid()); + return; + } + } + else { + if(d->first) { + if(!d->tried) { + r = d->c->clientFirstStep(d->allowCSF); + d->tried = true; + } + else + r = d->c->tryAgain(); + + if(r == QCA_SASLContext::Error) { + error(ErrAuth); + return; + } + else if(r == QCA_SASLContext::NeedParams) { + //d->tried = false; + QCA_SASLNeedParams np = d->c->clientParamsNeeded(); + needParams(np.user, np.authzid, np.pass, np.realm); + return; + } + + QString mech = d->c->mech(); + const QByteArray *clientInit = d->c->clientInit(); + + d->first = false; + d->tried = false; + clientFirstStep(mech, clientInit); + } + else { + if(!d->tried) { + r = d->c->nextStep(d->stepData); + d->tried = true; + } + else + r = d->c->tryAgain(); + + if(r == QCA_SASLContext::Error) { + error(ErrAuth); + return; + } + else if(r == QCA_SASLContext::NeedParams) { + //d->tried = false; + QCA_SASLNeedParams np = d->c->clientParamsNeeded(); + needParams(np.user, np.authzid, np.pass, np.realm); + return; + } + d->tried = false; + //else if(r == QCA_SASLContext::Continue) { + nextStep(d->c->result()); + // return; + //} + } + } + + if(r == QCA_SASLContext::Success) + authenticated(); + else if(r == QCA_SASLContext::Error) + error(ErrAuth); +} + +int SASL::ssf() const +{ + return d->c->security(); +} + +void SASL::write(const QByteArray &a) +{ + QByteArray b; + if(!d->c->encode(a, &b)) { + error(ErrCrypt); + return; + } + int oldsize = d->outbuf.size(); + d->outbuf.resize(oldsize + b.size()); + memcpy(d->outbuf.data() + oldsize, b.data(), b.size()); + readyReadOutgoing(a.size()); +} + +QByteArray SASL::read() +{ + QByteArray a = d->inbuf.copy(); + d->inbuf.resize(0); + return a; +} + +void SASL::writeIncoming(const QByteArray &a) +{ + QByteArray b; + if(!d->c->decode(a, &b)) { + error(ErrCrypt); + return; + } + int oldsize = d->inbuf.size(); + d->inbuf.resize(oldsize + b.size()); + memcpy(d->inbuf.data() + oldsize, b.data(), b.size()); + readyRead(); +} + +QByteArray SASL::readOutgoing() +{ + QByteArray a = d->outbuf.copy(); + d->outbuf.resize(0); + return a; +} + +#include "qca.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/qca/src/qca.h b/kopete/protocols/groupwise/libgroupwise/qca/src/qca.h new file mode 100644 index 00000000..e7cd1609 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/qca/src/qca.h @@ -0,0 +1,466 @@ +/* + * qca.h - Qt Cryptographic Architecture + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef QCA_H +#define QCA_H + +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN32 +# ifndef QCA_STATIC +# ifdef QCA_MAKEDLL +# define QCA_EXPORT __declspec(dllexport) +# else +# define QCA_EXPORT __declspec(dllimport) +# endif +# endif +#endif +#ifndef QCA_EXPORT +#define QCA_EXPORT +#endif + +#ifdef Q_OS_WIN32 +# ifdef QCA_PLUGIN_DLL +# define QCA_PLUGIN_EXPORT extern "C" __declspec(dllexport) +# else +# define QCA_PLUGIN_EXPORT extern "C" __declspec(dllimport) +# endif +#endif +#ifndef QCA_PLUGIN_EXPORT +#define QCA_PLUGIN_EXPORT extern "C" +#endif + +class QHostAddress; +class QStringList; + +class QCAProvider; +class QCA_HashContext; +class QCA_CipherContext; +class QCA_CertContext; + +namespace QCA +{ + enum { + CAP_SHA1 = 0x0001, + CAP_SHA256 = 0x0002, + CAP_MD5 = 0x0004, + CAP_BlowFish = 0x0008, + CAP_TripleDES = 0x0010, + CAP_AES128 = 0x0020, + CAP_AES256 = 0x0040, + CAP_RSA = 0x0080, + CAP_X509 = 0x0100, + CAP_TLS = 0x0200, + CAP_SASL = 0x0400 + }; + + enum { + CBC = 0x0001, + CFB = 0x0002 + }; + + enum { + Encrypt = 0x0001, + Decrypt = 0x0002 + }; + + QCA_EXPORT void init(); + QCA_EXPORT bool isSupported(int capabilities); + QCA_EXPORT void insertProvider(QCAProvider *); + QCA_EXPORT void unloadAllPlugins(); + + QCA_EXPORT QString arrayToHex(const QByteArray &); + QCA_EXPORT QByteArray hexToArray(const QString &); + + class QCA_EXPORT Hash + { + public: + Hash(const Hash &); + Hash & operator=(const Hash &); + ~Hash(); + + void clear(); + void update(const QByteArray &a); + QByteArray final(); + + protected: + Hash(QCA_HashContext *); + + private: + class Private; + Private *d; + }; + + template + class QCA_EXPORT HashStatic + { + public: + HashStatic() {} + + static QByteArray hash(const QByteArray &a) + { + T obj; + obj.update(a); + return obj.final(); + } + + static QByteArray hash(const QCString &cs) + { + QByteArray a(cs.length()); + memcpy(a.data(), cs.data(), a.size()); + return hash(a); + } + + static QString hashToString(const QByteArray &a) + { + return arrayToHex(hash(a)); + } + + static QString hashToString(const QCString &cs) + { + return arrayToHex(hash(cs)); + } + }; + + class QCA_EXPORT Cipher + { + public: + Cipher(const Cipher &); + Cipher & operator=(const Cipher &); + ~Cipher(); + + QByteArray dyn_generateKey(int size=-1) const; + QByteArray dyn_generateIV() const; + void reset(int dir, int mode, const QByteArray &key, const QByteArray &iv, bool pad=true); + bool update(const QByteArray &a); + QByteArray final(bool *ok=0); + + protected: + Cipher(QCA_CipherContext *, int dir, int mode, const QByteArray &key, const QByteArray &iv, bool pad); + + private: + class Private; + Private *d; + }; + + template + class QCA_EXPORT CipherStatic + { + public: + CipherStatic() {} + + static QByteArray generateKey(int size=-1) + { + T obj; + return obj.dyn_generateKey(size); + } + + static QByteArray generateIV() + { + T obj; + return obj.dyn_generateIV(); + } + }; + + class QCA_EXPORT SHA1 : public Hash, public HashStatic + { + public: + SHA1(); + }; + + class QCA_EXPORT SHA256 : public Hash, public HashStatic + { + public: + SHA256(); + }; + + class QCA_EXPORT MD5 : public Hash, public HashStatic + { + public: + MD5(); + }; + + class QCA_EXPORT BlowFish : public Cipher, public CipherStatic + { + public: + BlowFish(int dir=Encrypt, int mode=CBC, const QByteArray &key=QByteArray(), const QByteArray &iv=QByteArray(), bool pad=true); + }; + + class QCA_EXPORT TripleDES : public Cipher, public CipherStatic + { + public: + TripleDES(int dir=Encrypt, int mode=CBC, const QByteArray &key=QByteArray(), const QByteArray &iv=QByteArray(), bool pad=true); + }; + + class QCA_EXPORT AES128 : public Cipher, public CipherStatic + { + public: + AES128(int dir=Encrypt, int mode=CBC, const QByteArray &key=QByteArray(), const QByteArray &iv=QByteArray(), bool pad=true); + }; + + class QCA_EXPORT AES256 : public Cipher, public CipherStatic + { + public: + AES256(int dir=Encrypt, int mode=CBC, const QByteArray &key=QByteArray(), const QByteArray &iv=QByteArray(), bool pad=true); + }; + + class RSA; + class QCA_EXPORT RSAKey + { + public: + RSAKey(); + RSAKey(const RSAKey &from); + RSAKey & operator=(const RSAKey &from); + ~RSAKey(); + + bool isNull() const; + bool havePublic() const; + bool havePrivate() const; + + QByteArray toDER(bool publicOnly=false) const; + bool fromDER(const QByteArray &a); + + QString toPEM(bool publicOnly=false) const; + bool fromPEM(const QString &); + + // only call if you know what you are doing + bool fromNative(void *); + + private: + class Private; + Private *d; + + friend class RSA; + friend class TLS; + bool encrypt(const QByteArray &a, QByteArray *out, bool oaep) const; + bool decrypt(const QByteArray &a, QByteArray *out, bool oaep) const; + bool generate(unsigned int bits); + }; + + class QCA_EXPORT RSA + { + public: + RSA(); + ~RSA(); + + RSAKey key() const; + void setKey(const RSAKey &); + + bool encrypt(const QByteArray &a, QByteArray *out, bool oaep=false) const; + bool decrypt(const QByteArray &a, QByteArray *out, bool oaep=false) const; + + static RSAKey generateKey(unsigned int bits); + + private: + RSAKey v_key; + }; + + typedef QMap CertProperties; + class QCA_EXPORT Cert + { + public: + Cert(); + Cert(const Cert &); + Cert & operator=(const Cert &); + ~Cert(); + + bool isNull() const; + + QString commonName() const; + QString serialNumber() const; + QString subjectString() const; + QString issuerString() const; + CertProperties subject() const; + CertProperties issuer() const; + QDateTime notBefore() const; + QDateTime notAfter() const; + + QByteArray toDER() const; + bool fromDER(const QByteArray &a); + + QString toPEM() const; + bool fromPEM(const QString &); + + private: + class Private; + Private *d; + + friend class TLS; + void fromContext(QCA_CertContext *); + }; + + class QCA_EXPORT TLS : public QObject + { + Q_OBJECT + public: + enum Validity { + NoCert, + Valid, + HostMismatch, + Rejected, + Untrusted, + SignatureFailed, + InvalidCA, + InvalidPurpose, + SelfSigned, + Revoked, + PathLengthExceeded, + Expired, + Unknown + }; + enum Error { ErrHandshake, ErrCrypt }; + + TLS(QObject *parent=0); + ~TLS(); + + void setCertificate(const Cert &cert, const RSAKey &key); + void setCertificateStore(const QPtrList &store); // note: store must persist + + void reset(); + bool startClient(const QString &host=""); + bool startServer(); + void close(); + bool isHandshaken() const; + + // plain (application side) + void write(const QByteArray &a); + QByteArray read(); + + // encoded (socket side) + void writeIncoming(const QByteArray &a); + QByteArray readOutgoing(); + QByteArray readUnprocessed(); + + // cert related + const Cert & peerCertificate() const; + int certificateValidityResult() const; + + signals: + void handshaken(); + void readyRead(); + void readyReadOutgoing(int plainBytes); + void closed(); + void error(int); + + private slots: + void update(); + + private: + class Private; + Private *d; + }; + + class QCA_EXPORT SASL : public QObject + { + Q_OBJECT + public: + enum Error { ErrAuth, ErrCrypt }; + enum ErrorCond { + NoMech, + BadProto, + BadServ, + BadAuth, + NoAuthzid, + TooWeak, + NeedEncrypt, + Expired, + Disabled, + NoUser, + RemoteUnavail + }; + SASL(QObject *parent=0); + ~SASL(); + + static void setAppName(const QString &name); + + void reset(); + int errorCondition() const; + + // options + void setAllowPlain(bool); + void setAllowAnonymous(bool); + void setAllowActiveVulnerable(bool); + void setAllowDictionaryVulnerable(bool); + void setRequireForwardSecrecy(bool); + void setRequirePassCredentials(bool); + void setRequireMutualAuth(bool); + + void setMinimumSSF(int); + void setMaximumSSF(int); + void setExternalAuthID(const QString &authid); + void setExternalSSF(int); + + void setLocalAddr(const QHostAddress &addr, Q_UINT16 port); + void setRemoteAddr(const QHostAddress &addr, Q_UINT16 port); + + // initialize + bool startClient(const QString &service, const QString &host, const QStringList &mechlist, bool allowClientSendFirst=true); + bool startServer(const QString &service, const QString &host, const QString &realm, QStringList *mechlist); + + // authentication + void putStep(const QByteArray &stepData); + void putServerFirstStep(const QString &mech); + void putServerFirstStep(const QString &mech, const QByteArray &clientInit); + void setUsername(const QString &user); + void setAuthzid(const QString &auth); + void setPassword(const QString &pass); + void setRealm(const QString &realm); + void continueAfterParams(); + void continueAfterAuthCheck(); + + // security layer + int ssf() const; + void write(const QByteArray &a); + QByteArray read(); + void writeIncoming(const QByteArray &a); + QByteArray readOutgoing(); + + signals: + // for authentication + void clientFirstStep(const QString &mech, const QByteArray *clientInit); + void nextStep(const QByteArray &stepData); + void needParams(bool user, bool authzid, bool pass, bool realm); + void authCheck(const QString &user, const QString &authzid); + void authenticated(); + + // for security layer + void readyRead(); + void readyReadOutgoing(int plainBytes); + + // error + void error(int); + + private slots: + void tryAgain(); + + private: + class Private; + Private *d; + + void handleServerFirstStep(int r); + }; +} + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/qca/src/qcaprovider.h b/kopete/protocols/groupwise/libgroupwise/qca/src/qcaprovider.h new file mode 100644 index 00000000..a7f1805b --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/qca/src/qcaprovider.h @@ -0,0 +1,191 @@ +/* + * qcaprovider.h - QCA Plugin API + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef QCAPROVIDER_H +#define QCAPROVIDER_H + +#include +#include +#include +#include +#include +#include"qca.h" + +#define QCA_PLUGIN_VERSION 1 + +class QCAProvider +{ +public: + QCAProvider() {} + virtual ~QCAProvider() {} + + virtual void init()=0; + virtual int qcaVersion() const=0; + virtual int capabilities() const=0; + virtual void *context(int cap)=0; +}; + +class QCA_HashContext +{ +public: + virtual ~QCA_HashContext() {} + + virtual QCA_HashContext *clone()=0; + virtual void reset()=0; + virtual void update(const char *in, unsigned int len)=0; + virtual void final(QByteArray *out)=0; +}; + +class QCA_CipherContext +{ +public: + virtual ~QCA_CipherContext() {} + + virtual QCA_CipherContext *clone()=0; + virtual int keySize()=0; + virtual int blockSize()=0; + virtual bool generateKey(char *out, int keysize=-1)=0; + virtual bool generateIV(char *out)=0; + + virtual bool setup(int dir, int mode, const char *key, int keysize, const char *iv, bool pad)=0; + virtual bool update(const char *in, unsigned int len)=0; + virtual bool final(QByteArray *out)=0; +}; + +class QCA_RSAKeyContext +{ +public: + virtual ~QCA_RSAKeyContext() {} + + virtual QCA_RSAKeyContext *clone() const=0; + virtual bool isNull() const=0; + virtual bool havePublic() const=0; + virtual bool havePrivate() const=0; + virtual bool createFromDER(const char *in, unsigned int len)=0; + virtual bool createFromPEM(const char *in, unsigned int len)=0; + virtual bool createFromNative(void *in)=0; + virtual bool generate(unsigned int bits)=0; + virtual bool toDER(QByteArray *out, bool publicOnly)=0; + virtual bool toPEM(QByteArray *out, bool publicOnly)=0; + + virtual bool encrypt(const QByteArray &in, QByteArray *out, bool oaep)=0; + virtual bool decrypt(const QByteArray &in, QByteArray *out, bool oaep)=0; +}; + +struct QCA_CertProperty +{ + QString var; + QString val; +}; + +class QCA_CertContext +{ +public: + virtual ~QCA_CertContext() {} + + virtual QCA_CertContext *clone() const=0; + virtual bool isNull() const=0; + virtual bool createFromDER(const char *in, unsigned int len)=0; + virtual bool createFromPEM(const char *in, unsigned int len)=0; + virtual bool toDER(QByteArray *out)=0; + virtual bool toPEM(QByteArray *out)=0; + + virtual QString serialNumber() const=0; + virtual QString subjectString() const=0; + virtual QString issuerString() const=0; + virtual QValueList subject() const=0; + virtual QValueList issuer() const=0; + virtual QDateTime notBefore() const=0; + virtual QDateTime notAfter() const=0; + virtual bool matchesAddress(const QString &realHost) const=0; +}; + +class QCA_TLSContext +{ +public: + enum Result { Success, Error, Continue }; + virtual ~QCA_TLSContext() {} + + virtual void reset()=0; + virtual bool startClient(const QPtrList &store, const QCA_CertContext &cert, const QCA_RSAKeyContext &key)=0; + virtual bool startServer(const QPtrList &store, const QCA_CertContext &cert, const QCA_RSAKeyContext &key)=0; + + virtual int handshake(const QByteArray &in, QByteArray *out)=0; + virtual int shutdown(const QByteArray &in, QByteArray *out)=0; + virtual bool encode(const QByteArray &plain, QByteArray *to_net, int *encoded)=0; + virtual bool decode(const QByteArray &from_net, QByteArray *plain, QByteArray *to_net)=0; + virtual bool eof() const=0; + virtual QByteArray unprocessed()=0; + + virtual QCA_CertContext *peerCertificate() const=0; + virtual int validityResult() const=0; +}; + +struct QCA_SASLHostPort +{ + QHostAddress addr; + Q_UINT16 port; +}; + +struct QCA_SASLNeedParams +{ + bool user, authzid, pass, realm; +}; + +class QCA_SASLContext +{ +public: + enum Result { Success, Error, NeedParams, AuthCheck, Continue }; + virtual ~QCA_SASLContext() {} + + // common + virtual void reset()=0; + virtual void setCoreProps(const QString &service, const QString &host, QCA_SASLHostPort *local, QCA_SASLHostPort *remote)=0; + virtual void setSecurityProps(bool noPlain, bool noActive, bool noDict, bool noAnon, bool reqForward, bool reqCreds, bool reqMutual, int ssfMin, int ssfMax, const QString &_ext_authid, int _ext_ssf)=0; + virtual int security() const=0; + virtual int errorCond() const=0; + + // init / first step + virtual bool clientStart(const QStringList &mechlist)=0; + virtual int clientFirstStep(bool allowClientSendFirst)=0; + virtual bool serverStart(const QString &realm, QStringList *mechlist, const QString &name)=0; + virtual int serverFirstStep(const QString &mech, const QByteArray *in)=0; + + // get / set params + virtual QCA_SASLNeedParams clientParamsNeeded() const=0; + virtual void setClientParams(const QString *user, const QString *authzid, const QString *pass, const QString *realm)=0; + virtual QString username() const=0; + virtual QString authzid() const=0; + + // continue steps + virtual int nextStep(const QByteArray &in)=0; + virtual int tryAgain()=0; + + // results + virtual QString mech() const=0; + virtual const QByteArray *clientInit() const=0; + virtual QByteArray result() const=0; + + // security layer + virtual bool encode(const QByteArray &in, QByteArray *out)=0; + virtual bool decode(const QByteArray &in, QByteArray *out)=0; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/qcatlshandler.cpp b/kopete/protocols/groupwise/libgroupwise/qcatlshandler.cpp new file mode 100644 index 00000000..366f2afa --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/qcatlshandler.cpp @@ -0,0 +1,122 @@ +/* + qcatlshandler.cpp - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include "qca.h" + +#include "qcatlshandler.h" + +class QCATLSHandler::Private +{ +public: + QCA::TLS *tls; + int state, err; +}; + +QCATLSHandler::QCATLSHandler(QCA::TLS *parent) +:TLSHandler(parent) +{ + d = new Private; + d->tls = parent; + connect(d->tls, SIGNAL(handshaken()), SLOT(tls_handshaken())); + connect(d->tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); + connect(d->tls, SIGNAL(readyReadOutgoing(int)), SLOT(tls_readyReadOutgoing(int))); + connect(d->tls, SIGNAL(closed()), SLOT(tls_closed())); + connect(d->tls, SIGNAL(error(int)), SLOT(tls_error(int))); + d->state = 0; + d->err = -1; +} + +QCATLSHandler::~QCATLSHandler() +{ + delete d; +} + +QCA::TLS *QCATLSHandler::tls() const +{ + return d->tls; +} + +int QCATLSHandler::tlsError() const +{ + return d->err; +} + +void QCATLSHandler::reset() +{ + d->tls->reset(); + d->state = 0; +} + +void QCATLSHandler::startClient(const QString &host) +{ + d->state = 0; + d->err = -1; + if(!d->tls->startClient(host)) + QTimer::singleShot(0, this, SIGNAL(fail())); +} + +void QCATLSHandler::write(const QByteArray &a) +{ + d->tls->write(a); +} + +void QCATLSHandler::writeIncoming(const QByteArray &a) +{ + d->tls->writeIncoming(a); +} + +void QCATLSHandler::continueAfterHandshake() +{ + if(d->state == 2) { + success(); + d->state = 3; + } +} + +void QCATLSHandler::tls_handshaken() +{ + d->state = 2; + tlsHandshaken(); +} + +void QCATLSHandler::tls_readyRead() +{ + readyRead(d->tls->read()); +} + +void QCATLSHandler::tls_readyReadOutgoing(int plainBytes) +{ + readyReadOutgoing(d->tls->readOutgoing(), plainBytes); +} + +void QCATLSHandler::tls_closed() +{ + closed(); +} + +void QCATLSHandler::tls_error(int x) +{ + d->err = x; + d->state = 0; + fail(); +} + +#include "qcatlshandler.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/qcatlshandler.h b/kopete/protocols/groupwise/libgroupwise/qcatlshandler.h new file mode 100644 index 00000000..a550d54b --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/qcatlshandler.h @@ -0,0 +1,61 @@ +/* + qcatlshandler.h - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GWQCATLSHANDLER_H +#define GWQCATLSHANDLER_H + +//#include +#include "tlshandler.h" + +class QCA::TLS; + +class QCATLSHandler : public TLSHandler +{ + Q_OBJECT +public: + QCATLSHandler(QCA::TLS *parent); + ~QCATLSHandler(); + + QCA::TLS *tls() const; + int tlsError() const; + + void reset(); + void startClient(const QString &host); + void write(const QByteArray &a); + void writeIncoming(const QByteArray &a); + +signals: + void tlsHandshaken(); + +public slots: + void continueAfterHandshake(); + +private slots: + void tls_handshaken(); + void tls_readyRead(); + void tls_readyReadOutgoing(int); + void tls_closed(); + void tls_error(int); + +private: + class Private; + Private *d; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/request.cpp b/kopete/protocols/groupwise/libgroupwise/request.cpp new file mode 100644 index 00000000..508bf6a0 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/request.cpp @@ -0,0 +1,34 @@ +/* + request.cpp - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "request.h" + +Request::Request( const int transactionId, const QString &command ) +: UserTransfer(transactionId), m_command( command ) +{ +} + +Request::~Request() +{ +} + +QString Request::command() +{ + return m_command; +} + + diff --git a/kopete/protocols/groupwise/libgroupwise/request.h b/kopete/protocols/groupwise/libgroupwise/request.h new file mode 100644 index 00000000..85a55e8a --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/request.h @@ -0,0 +1,40 @@ +/* + request.h - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ +#ifndef LIBGW_REQUEST_H +#define LIBGW_REQUEST_H + +#include "usertransfer.h" + +/** + * Represents a client generated request to the server + * Create with @ref RequestFactory::request(). + * @author Kopete Developers +*/ +class Request : public UserTransfer +{ +friend class RequestFactory; + +public: + ~Request( ); + QString command(); + TransferType type() { return Transfer::RequestTransfer; } +private: + Request( const int transactionId, const QString &command ); + QString m_command; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/requestfactory.cpp b/kopete/protocols/groupwise/libgroupwise/requestfactory.cpp new file mode 100644 index 00000000..6387370f --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/requestfactory.cpp @@ -0,0 +1,37 @@ +/* + requestfactory.cpp - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "request.h" + +#include "requestfactory.h" + +#define GW_REQUESTFACTORY_FIRST_TID 1 +RequestFactory::RequestFactory() +: m_nextTransaction( GW_REQUESTFACTORY_FIRST_TID ) +{ +} + +RequestFactory::~RequestFactory() +{ +} + +Request* RequestFactory::request( const QString &command ) +{ + return new Request( m_nextTransaction++, command ); +} + + diff --git a/kopete/protocols/groupwise/libgroupwise/requestfactory.h b/kopete/protocols/groupwise/libgroupwise/requestfactory.h new file mode 100644 index 00000000..e4ec073e --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/requestfactory.h @@ -0,0 +1,44 @@ +/* + requestfactory.h - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef REQUESTFACTORY_H +#define REQUESTFACTORY_H + +#include + +class Request; + +/** + * Factory for obtaining @ref Request instances. + * @author Kopete Developers + */ +class RequestFactory{ +public: + RequestFactory(); + ~RequestFactory(); + + /** + * Obtain a new @ref Request instance + * The consumer is responsible for deleting this + * TODO: Allow the user to provide the fields for the request in this call + */ + Request * request( const QString &request); +private: + int m_nextTransaction; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/response.cpp b/kopete/protocols/groupwise/libgroupwise/response.cpp new file mode 100644 index 00000000..837c7810 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/response.cpp @@ -0,0 +1,34 @@ +/* + response.cpp - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ +#include + +#include "response.h" + +Response::Response( int transactionId, int resultCode, Field::FieldList fields ) +: UserTransfer( transactionId ), m_resultCode( resultCode ) +{ + setFields( fields ); +} + +Response::~Response() +{ +} + +int Response::resultCode() const +{ + return m_resultCode; +} diff --git a/kopete/protocols/groupwise/libgroupwise/response.h b/kopete/protocols/groupwise/libgroupwise/response.h new file mode 100644 index 00000000..8f4fb970 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/response.h @@ -0,0 +1,39 @@ +/* + response.h - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GW_RESPONSE_H +#define GW_RESPONSE_H + +#include "usertransfer.h" + +/** + * Represents the server's reply to a client generated request + * @author Kopete Developers +*/ +class Response: public UserTransfer +{ +public: + Response( int transactionId, int resultCode, Field::FieldList fields ); + virtual ~Response( ); + + TransferType type() { return Transfer::ResponseTransfer; } + int resultCode() const; +private: + int m_resultCode; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/responseprotocol.cpp b/kopete/protocols/groupwise/libgroupwise/responseprotocol.cpp new file mode 100644 index 00000000..6784fd15 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/responseprotocol.cpp @@ -0,0 +1,314 @@ +/* + Kopete Groupwise Protocol + responseprotocol.cpp - Protocol used for reading incoming GroupWise Responses + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include "response.h" + +#include "responseprotocol.h" + +ResponseProtocol::ResponseProtocol(QObject* parent, const char* name): InputProtocolBase(parent, name) +{ +} + + +ResponseProtocol::~ResponseProtocol() +{ +} + +Transfer * ResponseProtocol::parse( const QByteArray & wire, uint & bytes ) +{ + m_bytes = 0; + m_collatingFields.clear(); + //m_din = new QDataStream( wire, IO_ReadOnly ); + QBuffer inBuf( wire ); + inBuf.open( IO_ReadOnly); + m_din.setDevice( &inBuf ); + m_din.setByteOrder( QDataStream::LittleEndian ); + + // check that this begins with a HTTP (is a response) + Q_UINT32 val; + m_din >> val; + m_bytes += sizeof( Q_UINT32 ); + + Q_ASSERT( qstrncmp( (const char *)&val, "HTTP", strlen( "HTTP" ) ) == 0 ); + + // read rest of HTTP header and look for a 301 redirect. + QCString headerFirst; + if ( !readGroupWiseLine( headerFirst ) ) + return 0; + // pull out the HTTP return code + int firstSpace = headerFirst.find( ' ' ); + QString rtnField = headerFirst.mid( firstSpace, headerFirst.find( ' ', firstSpace + 1 ) ); + bool ok = true; + int rtnCode; + int packetState = -1; + rtnCode = rtnField.toInt( &ok ); + debug( "CoreProtocol::readResponse() got HTTP return code " ); + // read rest of header + QStringList headerRest; + QCString line; + while ( line != "\r\n" ) + { + if ( !readGroupWiseLine( line ) ) + { + m_din.unsetDevice(); + return 0; + } + headerRest.append( line ); + debug( QString( "- read header line - (%1) : %2" ).arg( line.length() ).arg( line.data() ) ); + } + debug( "ResponseProtocol::readResponse() header finished" ); + // if it's a redirect, set flag + if ( ok && rtnCode == 301 ) + { + debug( "- server redirect " ); + packetState = ServerRedirect; + m_din.unsetDevice(); + return 0; + } + // other header processing ( 500! ) + if ( ok && rtnCode == 500 ) + { + debug( QString( "- server error %1" ).arg( rtnCode ) ); + packetState = ServerError; + m_din.unsetDevice(); + return 0; + } + if ( ok && rtnCode == 404 ) + { + debug( QString( "- server error %1" ).arg( rtnCode ) ); + packetState = ServerError; + m_din.unsetDevice(); + return 0; + } + if ( m_din.atEnd() ) + { + debug( "- no fields" ); + packetState = ProtocolError; + m_din.unsetDevice(); + return 0; + } + + // read fields + if ( !readFields( -1 ) ) + { + m_din.unsetDevice(); + return 0; + } + // find transaction id field and create Response object if nonzero + int tId = 0; + int resultCode = 0; + Field::FieldListIterator it; + Field::FieldListIterator end = m_collatingFields.end(); + it = m_collatingFields.find( NM_A_SZ_TRANSACTION_ID ); + if ( it != end ) + { + Field::SingleField * sf = dynamic_cast( *it ); + if ( sf ) + { + tId = sf->value().toInt(); + debug( QString( "ResponseProtocol::readResponse() - transaction ID is %1" ).arg( tId ) ); + m_collatingFields.remove( it ); + delete sf; + } + } + it = m_collatingFields.find( NM_A_SZ_RESULT_CODE ); + if ( it != end ) + { + Field::SingleField * sf = dynamic_cast( *it ); + if ( sf ) + { + resultCode = sf->value().toInt(); + debug( QString( "ResponseProtocol::readResponse() - result code is %1" ).arg( resultCode ) ); + m_collatingFields.remove( it ); + delete sf; + } + } + // append to inQueue + if ( tId ) + { + debug( QString( "ResponseProtocol::readResponse() - setting state Available, got %1 fields in base array" ).arg(m_collatingFields.count() ) ); + packetState = Available; + bytes = m_bytes; + m_din.unsetDevice(); + return new Response( tId, resultCode, m_collatingFields ); + } + else + { + debug( "- WARNING - NO TRANSACTION ID FOUND!" ); + m_state = ProtocolError; + m_din.unsetDevice(); + m_collatingFields.purge(); + return 0; + } +} + +bool ResponseProtocol::readFields( int fieldCount, Field::FieldList * list ) +{ + // build a list of fields. + // If there is already a list of fields stored in m_collatingFields, + // the list we're reading on this iteration must be a nested list + // so when we're done reading it, add it to the MultiList element + // that is the last element in the top list in m_collatingFields. + // if we find the beginning of a new nested list, push the current list onto m_collatingFields + debug( "ResponseProtocol::readFields()" ); + if ( fieldCount > 0 ) + debug( QString( "reading %1 fields" ).arg( fieldCount ) ); + Field::FieldList currentList; + while ( fieldCount != 0 ) // prevents bad input data from ruining our day + { + // the field being read + // read field + Q_UINT8 type, method; + Q_UINT32 val; + QCString tag; + // read uint8 type + if ( !okToProceed() ) + { + currentList.purge(); + return false; + } + m_din >> type; + m_bytes += sizeof( Q_UINT8 ); + // if type is 0 SOMETHING_INVALID, we're at the end of the fields + if ( type == 0 ) /*&& m_din->atEnd() )*/ + { + debug( "- end of field list" ); + m_packetState = FieldsRead; + // do something to indicate we're done + break; + } + // read uint8 method + if ( !okToProceed() ) + { + currentList.purge(); + return false; + } + m_din >> method; + m_bytes += sizeof( Q_UINT8 ); + // read tag and length + if ( !safeReadBytes( tag, val ) ) + { + currentList.purge(); + return false; + } + + debug( QString( "- type: %1, method: %2, tag: %3," ).arg( type ).arg( method ).arg( tag.data() ) ); + // if multivalue or array + if ( type == NMFIELD_TYPE_MV || type == NMFIELD_TYPE_ARRAY ) + { + // read length uint32 + if ( !okToProceed() ) + { + currentList.purge(); + return false; + } + m_din >> val; + m_bytes += sizeof( Q_UINT32 ); + + // create multifield + debug( QString( " multi field containing: %1" ).arg( val ) ); + Field::MultiField* m = new Field::MultiField( tag, method, 0, type ); + currentList.append( m ); + if ( !readFields( val, ¤tList) ) + { + currentList.purge(); + return false; + } + } + else + { + + if ( type == NMFIELD_TYPE_UTF8 || type == NMFIELD_TYPE_DN ) + { + QCString rawData; + if( !safeReadBytes( rawData, val ) ) + { + currentList.purge(); + return false; + } + if ( val > NMFIELD_MAX_STR_LENGTH ) + { + m_packetState = ProtocolError; + break; + } + // convert to unicode - ignore the terminating NUL, because Qt<3.3.2 doesn't sanity check val. + QString fieldValue = QString::fromUtf8( rawData.data(), val - 1 ); + debug( QString( "- utf/dn single field: %1" ).arg( fieldValue ) ); + // create singlefield + Field::SingleField* s = new Field::SingleField( tag, method, 0, type, fieldValue ); + currentList.append( s ); + } + else + { + // otherwise ( numeric ) + // read value uint32 + if ( !okToProceed() ) + { + currentList.purge(); + return false; + } + m_din >> val; + m_bytes += sizeof( Q_UINT32 ); + debug( QString( "- numeric field: %1" ).arg( val ) ); + Field::SingleField* s = new Field::SingleField( tag, method, 0, type, val ); + currentList.append( s ); + } + } + // decrease the fieldCount if we're using it + if ( fieldCount > 0 ) + fieldCount--; + } + // got a whole list! + // if fieldCount == 0, we've just read a whole nested list, so add this list to the last element in 'list' + if ( fieldCount == 0 && list ) + { + debug( "- finished reading nested list" ); + Field::MultiField * m = dynamic_cast( list->last() ); + m->setFields( currentList ); + } + + // if fieldCount == -1; we're done reading the top level fieldlist, so store it. + if ( fieldCount == -1 ) + { + debug( "- finished reading ALL FIELDS!" ); + m_collatingFields = currentList; + } + return true; +} + +bool ResponseProtocol::readGroupWiseLine( QCString & line ) +{ + line = QCString(); + while ( true ) + { + Q_UINT8 c; + + if (! okToProceed() ) + return false; + m_din >> c; + m_bytes++; + line += QChar(c); + if ( c == '\n' ) + break; + } + return true; +} + +#include "responseprotocol.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/responseprotocol.h b/kopete/protocols/groupwise/libgroupwise/responseprotocol.h new file mode 100644 index 00000000..5957ad19 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/responseprotocol.h @@ -0,0 +1,76 @@ +/* + Kopete Groupwise Protocol + responseprotocol.h - Protocol used for reading incoming GroupWise Responses + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef RESPONSEPROTOCOL_H +#define RESPONSEPROTOCOL_H + +#include "gwerror.h" +#include "gwfield.h" + +#include "inputprotocolbase.h" + +/** +Handles the parsing of incoming Response messages + +@author Kopete Developers +*/ +class ResponseProtocol : public InputProtocolBase +{ +Q_OBJECT +public: + /** + * Describes the current state of the protocol + */ + enum State { NeedMore, Available, ServerError, ServerRedirect, ReadingEvent, NoData }; + + /** + * Describes the parsing of the last received packet + */ + enum PacketState { FieldsRead, ProtocolError }; + + ResponseProtocol(QObject* parent, const char* name); + ~ResponseProtocol(); + /** + * Attempt to parse the supplied data into an @ref Response object. + * The exact state of the parse attempt can be read using @ref state. + * @param rawData The unparsed data. + * @param bytes An integer used to return the number of bytes read. + * @return A pointer to an Response object if successfull, otherwise 0. The caller is responsible for deleting this object. + */ + Transfer * parse( const QByteArray &, uint & bytes ); +protected: + /** + * read a line ending in \r\n, including the \r\n + */ + bool readGroupWiseLine( QCString & ); + /** + * Read in a response + */ + bool readResponse(); + /** + * Parse received fields and store in m_collatingFields + */ + bool readFields( int fieldCount, Field::FieldList * list = 0 ); +private: + // fields from a packet being parsed, before it has been completely received + //QValueStack m_collatingFields; + Field::FieldList m_collatingFields; + int m_packetState; // represents the state of the parsing of the last incoming data received +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/rtf.cc b/kopete/protocols/groupwise/libgroupwise/rtf.cc new file mode 100644 index 00000000..eb5da80e --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/rtf.cc @@ -0,0 +1,2532 @@ +#line 2 "rtf.cc" + +#line 4 "rtf.cc" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 31 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; +#endif /* ! C99 */ + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +#if __STDC__ + +#define YY_USE_CONST + +#endif /* __STDC__ */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE rtfrestart(rtfin ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#define YY_BUF_SIZE 16384 +#endif + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +extern int rtfleng; + +extern FILE *rtfin, *rtfout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up rtftext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up rtftext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, (yytext_ptr) ) + +/* The following is because we cannot portably get our hands on size_t + * (without autoconf's help, which isn't available because we want + * flex-generated scanners to compile on their own). + */ + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef unsigned int yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via rtfrestart()), so that the user can continue scanning by + * just pointing rtfin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* Stack of input buffers. */ +static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* yy_hold_char holds the character lost when rtftext is formed. */ +static char yy_hold_char; +static int yy_n_chars; /* number of characters read into yy_ch_buf */ +int rtfleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = (char *) 0; +static int yy_init = 1; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow rtfwrap()'s to do buffer switches + * instead of setting up a fresh rtfin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +void rtfrestart (FILE *input_file ); +void rtf_switch_to_buffer (YY_BUFFER_STATE new_buffer ); +YY_BUFFER_STATE rtf_create_buffer (FILE *file,int size ); +void rtf_delete_buffer (YY_BUFFER_STATE b ); +void rtf_flush_buffer (YY_BUFFER_STATE b ); +void rtfpush_buffer_state (YY_BUFFER_STATE new_buffer ); +void rtfpop_buffer_state (void ); + +static void rtfensure_buffer_stack (void ); +static void rtf_load_buffer_state (void ); +static void rtf_init_buffer (YY_BUFFER_STATE b,FILE *file ); + +#define YY_FLUSH_BUFFER rtf_flush_buffer(YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE rtf_scan_buffer (char *base,yy_size_t size ); +YY_BUFFER_STATE rtf_scan_string (yyconst char *yy_str ); +YY_BUFFER_STATE rtf_scan_bytes (yyconst char *bytes,int len ); + +void *rtfalloc (yy_size_t ); +void *rtfrealloc (void *,yy_size_t ); +void rtffree (void * ); + +#define yy_new_buffer rtf_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + rtfensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + rtf_create_buffer(rtfin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + rtfensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + rtf_create_buffer(rtfin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +typedef unsigned char YY_CHAR; + +FILE *rtfin = (FILE *) 0, *rtfout = (FILE *) 0; + +typedef int yy_state_type; + +extern int rtflineno; + +int rtflineno = 1; + +extern char *rtftext; +#define yytext_ptr rtftext + +static yy_state_type yy_get_previous_state (void ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ); +static int yy_get_next_buffer (void ); +static void yy_fatal_error (yyconst char msg[] ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up rtftext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + rtfleng = (size_t) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + (yy_c_buf_p) = yy_cp; + +#define YY_NUM_RULES 10 +#define YY_END_OF_BUFFER 11 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_accept[33] = + { 0, + 0, 0, 11, 8, 8, 9, 9, 1, 2, 8, + 0, 0, 5, 3, 5, 0, 0, 5, 5, 5, + 0, 6, 5, 7, 5, 5, 5, 4, 5, 5, + 5, 0 + } ; + +static yyconst flex_int32_t yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 3, 1, 1, 4, 1, 1, 1, 5, 1, + 1, 1, 1, 1, 1, 1, 1, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 1, 1, 7, + 1, 8, 9, 1, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 1, 12, 1, 1, 1, 1, 10, 10, 10, 10, + + 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 13, 11, 11, 11, + 11, 11, 14, 1, 15, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static yyconst flex_int32_t yy_meta[16] = + { 0, + 1, 1, 2, 1, 1, 2, 3, 4, 1, 2, + 2, 3, 2, 3, 3 + } ; + +static yyconst flex_int16_t yy_base[37] = + { 0, + 0, 14, 45, 0, 0, 39, 25, 59, 59, 0, + 38, 0, 2, 59, 14, 0, 3, 59, 16, 21, + 25, 59, 28, 59, 38, 23, 19, 59, 17, 12, + 5, 59, 47, 51, 1, 55 + } ; + +static yyconst flex_int16_t yy_def[37] = + { 0, + 33, 33, 32, 34, 34, 32, 32, 32, 32, 34, + 32, 32, 35, 32, 35, 36, 32, 32, 32, 32, + 36, 32, 32, 32, 32, 32, 25, 32, 25, 25, + 25, 0, 32, 32, 32, 32 + } ; + +static yyconst flex_int16_t yy_nxt[75] = + { 0, + 32, 5, 13, 32, 18, 17, 6, 19, 22, 17, + 19, 7, 22, 8, 9, 5, 18, 31, 18, 20, + 6, 19, 30, 18, 29, 7, 23, 8, 9, 12, + 18, 28, 24, 25, 13, 13, 14, 15, 14, 14, + 26, 16, 11, 27, 32, 32, 28, 4, 4, 4, + 4, 10, 10, 32, 10, 21, 21, 21, 3, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32 + } ; + +static yyconst flex_int16_t yy_chk[75] = + { 0, + 0, 1, 35, 0, 13, 12, 1, 13, 17, 12, + 31, 1, 17, 1, 1, 2, 15, 30, 19, 15, + 2, 19, 29, 20, 27, 2, 20, 2, 2, 7, + 23, 26, 21, 23, 7, 7, 7, 7, 7, 7, + 25, 11, 6, 25, 3, 0, 25, 33, 33, 33, + 33, 34, 34, 0, 34, 36, 36, 36, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32 + } ; + +static yy_state_type yy_last_accepting_state; +static char *yy_last_accepting_cpos; + +extern int rtf_flex_debug; +int rtf_flex_debug = 0; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +char *rtftext; +#line 1 "rtf.ll" +#line 2 "rtf.ll" +/* + rtf.ll - A simple RTF Parser (Flex code) + + Copyright (c) 2002 by Vladimir Shutoff (original code) + Copyright (c) 2004 by Thiago S. Barcelos (Kopete port) + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* + +update rtf.cc: +flex -olex.yy.c `test -f rtf.ll || echo './'`rtf.ll +sed '/^#/ s|lex.yy\.c|rtf.cc|' lex.yy.c >rtf.cc +rm -f lex.yy.c + +*/ + +#define UP 1 +#define DOWN 2 +#define CMD 3 +#define TXT 4 +#define HEX 5 +#define IMG 6 +#define UNICODE_CHAR 7 +#define SKIP 8 +#define SLASH 9 +#define S_TXT 10 + +#define YY_NEVER_INTERACTIVE 1 +#define YY_ALWAYS_INTERACTIVE 0 +#define YY_MAIN 0 + +#line 505 "rtf.cc" + +#define INITIAL 0 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int rtfwrap (void ); +#else +extern int rtfwrap (void ); +#endif +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (void ); +#else +static int input (void ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO (void) fwrite( rtftext, rtfleng, 1, rtfout ) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + size_t n; \ + for ( n = 0; n < max_size && \ + (c = getc( rtfin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( rtfin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = fread(buf, 1, max_size, rtfin))==0 && ferror(rtfin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(rtfin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int rtflex (void); + +#define YY_DECL int rtflex (void) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after rtftext and rtfleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + +#line 46 "rtf.ll" + + +#line 657 "rtf.cc" + + if ( (yy_init) ) + { + (yy_init) = 0; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! rtfin ) + rtfin = stdin; + + if ( ! rtfout ) + rtfout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + rtfensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + rtf_create_buffer(rtfin,YY_BUF_SIZE ); + } + + rtf_load_buffer_state( ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = (yy_c_buf_p); + + /* Support of rtftext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = (yy_start); +yy_match: + do + { + register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 33 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 59 ); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + if ( yy_act == 0 ) + { /* have to back up */ + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + yy_act = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = (yy_hold_char); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + +case 1: +YY_RULE_SETUP +#line 48 "rtf.ll" +{ return UP; } + YY_BREAK +case 2: +YY_RULE_SETUP +#line 49 "rtf.ll" +{ return DOWN; } + YY_BREAK +case 3: +YY_RULE_SETUP +#line 50 "rtf.ll" +{ return SLASH; } + YY_BREAK +case 4: +YY_RULE_SETUP +#line 51 "rtf.ll" +{ return UNICODE_CHAR; } + YY_BREAK +case 5: +YY_RULE_SETUP +#line 52 "rtf.ll" +{ return CMD; } + YY_BREAK +case 6: +YY_RULE_SETUP +#line 53 "rtf.ll" +{ return HEX; } + YY_BREAK +case 7: +/* rule 7 can match eol */ +YY_RULE_SETUP +#line 54 "rtf.ll" +{ return IMG; } + YY_BREAK +case 8: +/* rule 8 can match eol */ +YY_RULE_SETUP +#line 55 "rtf.ll" +{ return TXT; } + YY_BREAK +case 9: +YY_RULE_SETUP +#line 56 "rtf.ll" +{ return TXT; } + YY_BREAK +case 10: +YY_RULE_SETUP +#line 57 "rtf.ll" +ECHO; + YY_BREAK +#line 792 "rtf.cc" +case YY_STATE_EOF(INITIAL): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed rtfin at a new source and called + * rtflex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = rtfin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_c_buf_p); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( rtfwrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * rtftext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of rtflex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (void) +{ + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = (yytext_ptr); + register int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + size_t num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + + int yy_c_buf_p_offset = + (int) ((yy_c_buf_p) - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + rtfrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + rtfrestart(rtfin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (void) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + + yy_current_state = (yy_start); + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 33 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +{ + register int yy_is_jam; + register char *yy_cp = (yy_c_buf_p); + + register YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 33 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_is_jam = (yy_current_state == 32); + + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (yy_c_buf_p) - (yytext_ptr); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + rtfrestart(rtfin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( rtfwrap( ) ) + return EOF; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve rtftext */ + (yy_hold_char) = *++(yy_c_buf_p); + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + void rtfrestart (FILE * input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + rtfensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + rtf_create_buffer(rtfin,YY_BUF_SIZE ); + } + + rtf_init_buffer(YY_CURRENT_BUFFER,input_file ); + rtf_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + void rtf_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * rtfpop_buffer_state(); + * rtfpush_buffer_state(new_buffer); + */ + rtfensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + rtf_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (rtfwrap()) processing, but the only time this flag + * is looked at is after rtfwrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +static void rtf_load_buffer_state (void) +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + rtfin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ + YY_BUFFER_STATE rtf_create_buffer (FILE * file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) rtfalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in rtf_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) rtfalloc(b->yy_buf_size + 2 ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in rtf_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + rtf_init_buffer(b,file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with rtf_create_buffer() + * + */ + void rtf_delete_buffer (YY_BUFFER_STATE b ) +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + rtffree((void *) b->yy_ch_buf ); + + rtffree((void *) b ); +} + +#ifndef __cplusplus +extern int isatty (int ); +#endif /* __cplusplus */ + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a rtfrestart() or at EOF. + */ + static void rtf_init_buffer (YY_BUFFER_STATE b, FILE * file ) + +{ + int oerrno = errno; + + rtf_flush_buffer(b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then rtf_init_buffer was _probably_ + * called from rtfrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ + void rtf_flush_buffer (YY_BUFFER_STATE b ) +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + rtf_load_buffer_state( ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +void rtfpush_buffer_state (YY_BUFFER_STATE new_buffer ) +{ + if (new_buffer == NULL) + return; + + rtfensure_buffer_stack(); + + /* This block is copied from rtf_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from rtf_switch_to_buffer. */ + rtf_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +void rtfpop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + rtf_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + rtf_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void rtfensure_buffer_stack (void) +{ + int num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + (yy_buffer_stack) = (struct yy_buffer_state**)rtfalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)rtfrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE rtf_scan_buffer (char * base, yy_size_t size ) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) rtfalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in rtf_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + rtf_switch_to_buffer(b ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to rtflex() will + * scan from a @e copy of @a str. + * @param str a NUL-terminated string to scan + * + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * rtf_scan_bytes() instead. + */ +YY_BUFFER_STATE rtf_scan_string (yyconst char * yy_str ) +{ + + return rtf_scan_bytes(yy_str,strlen(yy_str) ); +} + +/** Setup the input buffer state to scan the given bytes. The next call to rtflex() will + * scan from a @e copy of @a bytes. + * @param bytes the byte buffer to scan + * @param len the number of bytes in the buffer pointed to by @a bytes. + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE rtf_scan_bytes (yyconst char * bytes, int len ) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = len + 2; + buf = (char *) rtfalloc(n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in rtf_scan_bytes()" ); + + for ( i = 0; i < len; ++i ) + buf[i] = bytes[i]; + + buf[len] = buf[len+1] = YY_END_OF_BUFFER_CHAR; + + b = rtf_scan_buffer(buf,n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in rtf_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg ) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up rtftext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + rtftext[rtfleng] = (yy_hold_char); \ + (yy_c_buf_p) = rtftext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + rtfleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the current line number. + * + */ +int rtfget_lineno (void) +{ + + return rtflineno; +} + +/** Get the input stream. + * + */ +FILE *rtfget_in (void) +{ + return rtfin; +} + +/** Get the output stream. + * + */ +FILE *rtfget_out (void) +{ + return rtfout; +} + +/** Get the length of the current token. + * + */ +int rtfget_leng (void) +{ + return rtfleng; +} + +/** Get the current token. + * + */ + +char *rtfget_text (void) +{ + return rtftext; +} + +/** Set the current line number. + * @param line_number + * + */ +void rtfset_lineno (int line_number ) +{ + + rtflineno = line_number; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * + * @see rtf_switch_to_buffer + */ +void rtfset_in (FILE * in_str ) +{ + rtfin = in_str ; +} + +void rtfset_out (FILE * out_str ) +{ + rtfout = out_str ; +} + +int rtfget_debug (void) +{ + return rtf_flex_debug; +} + +void rtfset_debug (int bdebug ) +{ + rtf_flex_debug = bdebug ; +} + +/* rtflex_destroy is for both reentrant and non-reentrant scanners. */ +int rtflex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + rtf_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + rtfpop_buffer_state(); + } + + /* Destroy the stack itself. */ + rtffree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n ) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s ) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *rtfalloc (yy_size_t size ) +{ + return (void *) malloc( size ); +} + +void *rtfrealloc (void * ptr, yy_size_t size ) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +void rtffree (void * ptr ) +{ + free( (char *) ptr ); /* see rtfrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#undef YY_NEW_FILE +#undef YY_FLUSH_BUFFER +#undef yy_set_bol +#undef yy_new_buffer +#undef yy_set_interactive +#undef yytext_ptr +#undef YY_DO_BEFORE_ACTION + +#ifdef YY_DECL_IS_OURS +#undef YY_DECL_IS_OURS +#undef YY_DECL +#endif +#line 57 "rtf.ll" + + + +#include "rtf2html.h" + +void ParStyle::clearFormatting() +{ + // For now, do nothing. + // dir is not a formatting item. +} + +QString RTF2HTML::quoteString(const QString &_str, quoteMode mode) +{ + QString str = _str; + str.replace(QRegExp("&"), "&"); + str.replace(QRegExp("<"), "<"); + str.replace(QRegExp(">"), ">"); + str.replace(QRegExp("\""), """); + str.replace(QRegExp("\r"), ""); + switch (mode){ + case quoteHTML: + str.replace(QRegExp("\n"), "
    \n"); + break; + case quoteXML: + str.replace(QRegExp("\n"), "
    \n"); + break; + default: + break; + } + QRegExp re(" +"); + int len; + int pos = 0; + + while ((pos = re.search(str, pos)) != -1) { + len = re.matchedLength(); + + if (len == 1) + continue; + QString s = " "; + for (int i = 1; i < len; i++) + s += " "; + str.replace(pos, len, s); + } + return str; +} + +RTF2HTML::RTF2HTML() + : cur_level(this) +{ + rtf_ptr = NULL; + bExplicitParagraph = false; +} + +OutTag* RTF2HTML::getTopOutTag(TagEnum tagType) +{ + vector::iterator it, it_end; + for(it = oTags.begin(), it_end = oTags.end(); it != it_end; ++it) + if (it->tag == tagType) + return &(*it); + return NULL; +} + +void RTF2HTML::FlushOutTags() +{ + vector::iterator iter; + for (iter = oTags.begin(); iter != oTags.end(); iter++) + { + OutTag &t = *iter; + switch (t.tag){ + case TAG_FONT_COLOR: + { + // RTF colors are 1-based; colors[] is a 0-based array. + if (t.param > colors.size() || t.param == 0) + break; + QColor &c = colors[t.param-1]; + PrintUnquoted("", c.red(), c.green(), c.blue()); + } + break; + case TAG_FONT_SIZE: + PrintUnquoted("", t.param); + break; + case TAG_FONT_FAMILY: + { + if (t.param > fonts.size() || t.param == 0) + break; + FontDef &f = fonts[t.param-1]; + string name = (!f.nonTaggedName.empty()) ? f.nonTaggedName : f.taggedName; + PrintUnquoted("", name.c_str()); + } + break; + case TAG_BG_COLOR:{ + if (t.param > colors.size() || t.param == 0) + break; + QColor &c = colors[t.param-1]; + PrintUnquoted("", c.red(), c.green(), c.blue()); + break; + } + case TAG_BOLD: + PrintUnquoted(""); + break; + case TAG_ITALIC: + PrintUnquoted(""); + break; + case TAG_UNDERLINE: + PrintUnquoted(""); + break; + default: + break; + } + } + oTags.clear(); +} + +// This function will close the already-opened tag 'tag'. It will take +// care of closing the tags which 'tag' contains first (ie. it will unroll +// the stack till the point where 'tag' is). +void Level::resetTag(TagEnum tag) +{ + // A stack which'll keep tags we had to close in order to reach 'tag'. + // After we close 'tag', we will reopen them. + stack s; + + while (p->tags.size() > m_nTagsStartPos){ // Don't go further than the point where this level starts. + + TagEnum nTag = p->tags.top(); + + /* A tag will be located in oTags if it still wasn't printed out. + A tag will get printed out only if necessary (e.g. will + be optimized away). + Thus, for each tag we remove from the actual tag stack, we also + try to remove a yet-to-be-printed tag, and only if there are no + yet-to-be-printed tags left, we start closing the tags we pop. + The tags have one space - needed for umlaute (�) and .utf8() + */ + if (p->oTags.empty()){ + switch (nTag){ + case TAG_FONT_COLOR: + case TAG_FONT_SIZE: + case TAG_BG_COLOR: + case TAG_FONT_FAMILY: + p->PrintUnquoted(" "); + break; + case TAG_BOLD: + p->PrintUnquoted(" "); + break; + case TAG_ITALIC: + p->PrintUnquoted(" "); + break; + case TAG_UNDERLINE: + p->PrintUnquoted(" "); + break; + default: + break; + } + }else{ + p->oTags.pop_back(); + } + + p->tags.pop(); + if (nTag == tag) break; // if we reached the tag we were looking to close. + s.push(nTag); // remember to reopen this tag + } + + if (tag == TAG_ALL) return; + + while (!s.empty()){ + TagEnum nTag = s.top(); + switch (nTag){ + case TAG_FONT_COLOR:{ + unsigned nFontColor = m_nFontColor; + m_nFontColor = 0; + setFontColor(nFontColor); + break; + } + case TAG_FONT_SIZE:{ + unsigned nFontSize = m_nFontSize; + m_nFontSize = 0; + setFontSize(nFontSize); + break; + } + case TAG_BG_COLOR:{ + unsigned nFontBgColor = m_nFontBgColor; + m_nFontBgColor = 0; + setFontBgColor(nFontBgColor); + break; + } + case TAG_FONT_FAMILY:{ + unsigned nFont = m_nFont; + m_nFont = 0; + setFont(nFont); + break; + } + case TAG_BOLD:{ + bool nBold = m_bBold; + m_bBold = false; + setBold(nBold); + break; + } + case TAG_ITALIC:{ + bool nItalic = m_bItalic; + m_bItalic = false; + setItalic(nItalic); + break; + } + case TAG_UNDERLINE:{ + bool nUnderline = m_bUnderline; + m_bUnderline = false; + setUnderline(nUnderline); + break; + } + default: + break; + } + s.pop(); + } +} + +Level::Level(RTF2HTML *_p) : + p(_p), + m_bFontTbl(false), + m_bColors(false), + m_bFontName(false), + m_bTaggedFontNameOk(false), + m_nFont(0), + m_nEncoding(0) +{ + m_nTagsStartPos = p->tags.size(); + Init(); +} + +Level::Level(const Level &l) : + p(l.p), + m_bFontTbl(l.m_bFontTbl), + m_bColors(l.m_bColors), + m_bFontName(false), + m_bTaggedFontNameOk(l.m_bTaggedFontNameOk), + m_nFont(l.m_nFont), + m_nEncoding(l.m_nEncoding) +{ + m_nTagsStartPos = p->tags.size(); + Init(); +} + +void Level::Init() +{ + m_nFontColor = 0; + m_nFontBgColor = 0; + m_nFontSize = 0; + m_bFontName = false; + m_bBold = false; + m_bItalic = false; + m_bUnderline = false; +} + +void RTF2HTML::PrintUnquoted(const char *str, ...) +{ + char buff[1024]; + va_list ap; + va_start(ap, str); + vsnprintf(buff, sizeof(buff), str, ap); + va_end(ap); + sParagraph += buff; +} + +void RTF2HTML::PrintQuoted(const QString &str) +{ + sParagraph += quoteString(str); +} + +void RTF2HTML::FlushParagraph() +{ + if (!bExplicitParagraph || sParagraph.isEmpty()) + return; + + /* + s += "

    "; + s += sParagraph; + s += "

    "; + */ + + s += sParagraph; + s += "
    "; + + // Clear up the paragraph members + sParagraph = ""; + bExplicitParagraph = false; +} + +void Level::setFont(unsigned nFont) +{ + if (nFont <= 0) + return; + + if (m_bFontTbl){ + if (nFont > p->fonts.size() +1){ + kdDebug(14200) << "Invalid font index (" << + nFont << ") while parsing font table." << endl; + return; + } + if (nFont > p->fonts.size()){ + FontDef f; + f.charset = 0; + p->fonts.push_back(f); + } + m_nFont = nFont; + } + else + { + if (nFont > p->fonts.size()) + { + kdDebug(14200) << "Invalid font index (" << + nFont << ")." << endl; + return; + } + if (m_nFont == nFont) + return; + m_nFont = nFont; + if (m_nFont) resetTag(TAG_FONT_FAMILY); + m_nEncoding = p->fonts[nFont-1].charset; + p->oTags.push_back(OutTag(TAG_FONT_FAMILY, nFont)); + p->PutTag(TAG_FONT_FAMILY); + } +} + +void Level::setFontName() +{ + // This function is only valid during font table parsing. + if (m_bFontTbl){ + if ((m_nFont > 0) && (m_nFont <= p->fonts.size())) + // Be prepared to accept a font name. + m_bFontName = true; + } +} + +void Level::setEncoding(unsigned nEncoding) +{ + if (m_bFontTbl){ + if ((m_nFont > 0) && (m_nFont <= p->fonts.size())) + p->fonts[m_nFont-1].charset = nEncoding; + return; + } + m_nEncoding = nEncoding; +} + +void Level::setBold(bool bBold) +{ + if (m_bBold == bBold) return; + if (m_bBold) resetTag(TAG_BOLD); + m_bBold = bBold; + if (!m_bBold) return; + p->oTags.push_back(OutTag(TAG_BOLD, 0)); + p->PutTag(TAG_BOLD); +} + +void Level::setItalic(bool bItalic) +{ + if (m_bItalic == bItalic) return; + if (m_bItalic) resetTag(TAG_ITALIC); + m_bItalic = bItalic; + if (!m_bItalic) return; + p->oTags.push_back(OutTag(TAG_ITALIC, 0)); + p->PutTag(TAG_ITALIC); +} + +void Level::setUnderline(bool bUnderline) +{ + if (m_bUnderline == bUnderline) return; + if (m_bUnderline) resetTag(TAG_UNDERLINE); + m_bUnderline = bUnderline; + if (!m_bUnderline) return; + p->oTags.push_back(OutTag(TAG_UNDERLINE, 0)); + p->PutTag(TAG_UNDERLINE); +} + +void Level::setFontColor(unsigned short nColor) +{ + if (m_nFontColor == nColor) return; + if (m_nFontColor) resetTag(TAG_FONT_COLOR); + if (nColor > p->colors.size()) return; + m_nFontColor = nColor; + p->oTags.push_back(OutTag(TAG_FONT_COLOR, m_nFontColor)); + p->PutTag(TAG_FONT_COLOR); +} + +void Level::setFontBgColor(unsigned short nColor) +{ + if (m_nFontBgColor == nColor) return; + if (m_nFontBgColor != 0) resetTag(TAG_BG_COLOR); + if (nColor > p->colors.size()) return; + m_nFontBgColor = nColor; + p->oTags.push_back(OutTag(TAG_BG_COLOR, m_nFontBgColor)); + p->PutTag(TAG_BG_COLOR); +} + +void Level::setFontSizeHalfPoints(unsigned short nSize) +{ + setFontSize(nSize / 2); +} + +void Level::setFontSize(unsigned short nSize) +{ + if (m_nFontSize == nSize) return; + if (m_nFontSize) resetTag(TAG_FONT_SIZE); + p->oTags.push_back(OutTag(TAG_FONT_SIZE, nSize)); + p->PutTag(TAG_FONT_SIZE); + m_nFontSize = nSize; +} + +void Level::startParagraph() +{ + // Whatever tags we have open now, close them. + // We cannot carry let character formatting tags wrap paragraphs, + // since a formatting tag can close at any time and we cannot + // close the paragraph any time we want. + resetTag(TAG_ALL); + + // Flush the current paragraph HTML to the document HTML. + p->FlushParagraph(); + + // Mark this new paragraph as an explicit one (from \par etc.). + p->bExplicitParagraph = true; + + // Restore character formatting + p->oTags.push_back(OutTag(TAG_FONT_SIZE, m_nFontSize)); + p->PutTag(TAG_FONT_SIZE); + p->oTags.push_back(OutTag(TAG_FONT_COLOR, m_nFontColor)); + p->PutTag(TAG_FONT_COLOR); + p->oTags.push_back(OutTag(TAG_FONT_FAMILY, m_nFont)); + p->PutTag(TAG_FONT_FAMILY); + if (m_nFontBgColor != 0) + { + p->oTags.push_back(OutTag(TAG_BG_COLOR, m_nFontBgColor)); + p->PutTag(TAG_BG_COLOR); + } + if (m_bBold) + { + p->oTags.push_back(OutTag(TAG_BOLD, 0)); + p->PutTag(TAG_BOLD); + } + if (m_bItalic) + { + p->PutTag(TAG_ITALIC); + p->oTags.push_back(OutTag(TAG_ITALIC, 0)); + } + if (m_bUnderline) + { + p->oTags.push_back(OutTag(TAG_UNDERLINE, 0)); + p->PutTag(TAG_UNDERLINE); + } +} + +bool Level::isParagraphOpen() const +{ + return p->bExplicitParagraph; +} + +void Level::clearParagraphFormatting() +{ + // implicitly start a paragraph + if (!isParagraphOpen()) + startParagraph(); + // Since we don't implement any of the paragraph formatting tags (e.g. alignment), + // we don't clean up anything here. Note that \pard does NOT clean character + // formatting (such as font size, font weight, italics...). + p->parStyle.clearFormatting(); +} + +void Level::setParagraphDirLTR() +{ + // implicitly start a paragraph + if (!isParagraphOpen()) + startParagraph(); + p->parStyle.dir = ParStyle::DirLTR; +} + +void Level::setParagraphDirRTL() +{ + // implicitly start a paragraph + if (!isParagraphOpen()) + startParagraph(); + p->parStyle.dir = ParStyle::DirRTL; +} + +void Level::addLineBreak() +{ + p->PrintUnquoted("
    "); +} + +void Level::reset() +{ + resetTag(TAG_ALL); + if (m_bColors){ + if (m_bColorInit){ + QColor c(m_nRed, m_nGreen, m_nBlue); + p->colors.push_back(c); + resetColors(); + } + return; + } +} + +void Level::setText(const char *str) +{ + if (m_bColors) + { + reset(); + } + else if (m_bFontTbl) + { + if ((m_nFont <= 0) || (m_nFont > p->fonts.size())) + return; + + FontDef& def = p->fonts[m_nFont-1]; + + char *pp = strchr(str, ';'); + unsigned size; + if (pp != NULL) + size = (pp - str); + else + size = strlen(str); + + if (m_bFontName) + { + def.nonTaggedName.append(str, size); + // We know we have the entire name + if (pp != NULL) + m_bFontName = false; + } + else if (!m_bTaggedFontNameOk) + { + def.taggedName.append(str, size); + if (pp != NULL) + m_bTaggedFontNameOk = true; + } + } + else + { + for (; *str; str++) + if ((unsigned char)(*str) >= ' ') break; + if (!*str) return; + p->FlushOutTags(); + text += str; + } +} + +void Level::flush() +{ + if (text.length() == 0) return; + // TODO: Make encoding work in Kopete + /* + const char *encoding = NULL; + if (m_nEncoding){ + for (const ENCODING *c = ICQPlugin::core->encodings; c->language; c++){ + if (!c->bMain) + continue; + if ((unsigned)c->rtf_code == m_nEncoding){ + encoding = c->codec; + break; + } + } + } + if (encoding == NULL) + encoding = p->encoding; + + QTextCodec *codec = ICQClient::_getCodec(encoding); + */ + //p->PrintQuoted(codec->toUnicode(text.c_str(), text.length())); + p->PrintQuoted(text.c_str()); + text = ""; +} + +const unsigned FONTTBL = 0; +const unsigned COLORTBL = 1; +const unsigned RED = 2; +const unsigned GREEN = 3; +const unsigned BLUE = 4; +const unsigned CF = 5; +const unsigned FS = 6; +const unsigned HIGHLIGHT = 7; +const unsigned PARD = 8; +const unsigned PAR = 9; +const unsigned I = 10; +const unsigned B = 11; +const unsigned UL = 12; +const unsigned F = 13; +const unsigned FCHARSET = 14; +const unsigned FNAME = 15; +const unsigned ULNONE = 16; +const unsigned LTRPAR = 17; +const unsigned RTLPAR = 18; +const unsigned LINE = 19; + +static char cmds[] = + "fonttbl\x00" + "colortbl\x00" + "red\x00" + "green\x00" + "blue\x00" + "cf\x00" + "fs\x00" + "highlight\x00" + "pard\x00" + "par\x00" + "i\x00" + "b\x00" + "ul\x00" + "f\x00" + "fcharset\x00" + "fname\x00" + "ulnone\x00" + "ltrpar\x00" + "rtlpar\x00" + "line\x00" + "\x00"; + +int rtfwrap() { return 1; } + +static char h2d(char c) +{ + if ((c >= '0') && (c <= '9')) + return c - '0'; + if ((c >= 'A') && (c <= 'F')) + return (c - 'A') + 10; + if ((c >= 'a') && (c <= 'f')) + return (c - 'a') + 10; + return 0; +} + +QString RTF2HTML::Parse(const char *rtf, const char *_encoding) +{ + encoding = _encoding; + YY_BUFFER_STATE yy_current_buffer = rtf_scan_string(rtf); + rtf_ptr = rtf; + for (;;){ + int res = rtflex(); + if (!res) break; + switch (res){ + case UP:{ + cur_level.flush(); + levels.push(cur_level); + break; + } + case DOWN:{ + if (!levels.empty()){ + cur_level.flush(); + cur_level.reset(); + cur_level = levels.top(); + levels.pop(); + } + break; + } + case IMG:{ + cur_level.flush(); + const char ICQIMAGE[] = "icqimage"; + const char *smiles[] = { ":-)" , ":-O" , ":-|" , ":-/" , // 0-3 + ":-(" , ":-*" , ":-/" , ":'(" , // 4-7 + ";-)" , ":-@" , ":-$" , ":-X" , // 8-B + ":-P" , "8-)" , "O:)" , ":-D" }; // C-F + const char *p = rtftext + 3; + if ((strlen(p) > strlen(ICQIMAGE)) && !memcmp(p, ICQIMAGE, strlen(ICQIMAGE))){ + unsigned n = 0; + for (p += strlen(ICQIMAGE); *p; p++){ + if ((*p >= '0') && (*p <= '9')){ + n = n << 4; + n += (*p - '0'); + continue; + } + if ((*p >= 'A') && (*p <= 'F')){ + n = n << 4; + n += (*p - 'A') + 10; + continue; + } + if ((*p >= 'a') && (*p <= 'f')){ + n = n << 4; + n += (*p - 'a') + 10; + continue; + } + break; + } + if (n < 16) + PrintUnquoted(" %s ", smiles[n] ); + }else{ + kdDebug(14200) << "Unknown image " << rtftext << endl; + } + break; + } + case SKIP: + break; + case SLASH: + cur_level.setText(rtftext+1); + break; + case TXT: + cur_level.setText(rtftext); + break; + case UNICODE_CHAR:{ + cur_level.flush(); + sParagraph += QChar((unsigned short)(atol(rtftext + 2))); + break; + } + case HEX:{ + char s[2]; + s[0] = (h2d(rtftext[2]) << 4) + h2d(rtftext[3]); + s[1] = 0; + cur_level.setText(s); + break; + } + case CMD: + { + cur_level.flush(); + const char *cmd = rtftext + 1; + unsigned n_cmd = 0; + unsigned cmd_size = 0; + int cmd_value = -1; + const char *p; + for (p = cmd; *p; p++, cmd_size++) + if (((*p >= '0') && (*p <= '9')) || (*p == ' ')) break; + if (*p && (*p != ' ')) cmd_value = atol(p); + for (p = cmds; *p; p += strlen(p) + 1, n_cmd++){ + if (strlen(p) > cmd_size) continue; + if (!memcmp(p, cmd, cmd_size)) break; + } + cmd += strlen(p); + switch (n_cmd){ + case FONTTBL: // fonttbl + cur_level.setFontTbl(); + break; + case COLORTBL: + cur_level.setColors(); + break; + case RED: + cur_level.setRed(cmd_value); + break; + case GREEN: + cur_level.setGreen(cmd_value); + break; + case BLUE: + cur_level.setBlue(cmd_value); + break; + case CF: + cur_level.setFontColor(cmd_value); + break; + case FS: + cur_level.setFontSizeHalfPoints(cmd_value); + break; + case HIGHLIGHT: + cur_level.setFontBgColor(cmd_value); + break; + case PARD: + cur_level.clearParagraphFormatting(); + break; + case PAR: + cur_level.startParagraph(); + break; + case I: + cur_level.setItalic(cmd_value != 0); + break; + case B: + cur_level.setBold(cmd_value != 0); + break; + case UL: + cur_level.setUnderline(cmd_value != 0); + break; + case ULNONE: + cur_level.setUnderline(false); + break; + case F: + // RTF fonts are 0-based; our font index is 1-based. + cur_level.setFont(cmd_value+1); + break; + case FCHARSET: + cur_level.setEncoding(cmd_value); + break; + case FNAME: + cur_level.setFontName(); + break; + case LTRPAR: + cur_level.setParagraphDirLTR(); + break; + case RTLPAR: + cur_level.setParagraphDirRTL(); + break; + case LINE: + cur_level.addLineBreak(); + } + break; + } + } + } + rtf_delete_buffer(yy_current_buffer); + yy_current_buffer = NULL; + FlushParagraph(); + return s; +} + +/* +bool ICQClient::parseRTF(const char *rtf, const char *encoding, QString &res) +{ + char _RTF[] = "{\\rtf"; + if ((strlen(rtf) > strlen(_RTF)) && !memcmp(rtf, _RTF, strlen(_RTF))){ + RTF2HTML p; + res = p.Parse(rtf, encoding); + return true; + } + QTextCodec *codec = ICQClient::_getCodec(encoding); + res = codec->toUnicode(rtf, strlen(rtf)); + return false; +} +*/ + diff --git a/kopete/protocols/groupwise/libgroupwise/rtf.ll b/kopete/protocols/groupwise/libgroupwise/rtf.ll new file mode 100644 index 00000000..37ebd9a3 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/rtf.ll @@ -0,0 +1,866 @@ +%{ +/* + rtf.ll - A simple RTF Parser (Flex code) + + Copyright (c) 2002 by Vladimir Shutoff (original code) + Copyright (c) 2004 by Thiago S. Barcelos (Kopete port) + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* + +update rtf.cc: +flex -olex.yy.c `test -f rtf.ll || echo './'`rtf.ll +sed '/^#/ s|lex.yy\.c|rtf.cc|' lex.yy.c >rtf.cc +rm -f lex.yy.c + +*/ + +#define UP 1 +#define DOWN 2 +#define CMD 3 +#define TXT 4 +#define HEX 5 +#define IMG 6 +#define UNICODE_CHAR 7 +#define SKIP 8 +#define SLASH 9 +#define S_TXT 10 + +#define YY_NEVER_INTERACTIVE 1 +#define YY_ALWAYS_INTERACTIVE 0 +#define YY_MAIN 0 + +%} + +%option nounput +%option nostack +%option prefix="rtf" + +%% + +"{" { return UP; } +"}" { return DOWN; } +"\\"[\\\{\}] { return SLASH; } +"\\u"[0-9]{3,7}[ ]?"?" { return UNICODE_CHAR; } +"\\"[A-Za-z]+[0-9]*[ ]? { return CMD; } +"\\'"[0-9A-Fa-f][0-9A-Fa-f] { return HEX; } +"<##"[^>]+">" { return IMG; } +[^\\{}<]+ { return TXT; } +. { return TXT; } +%% + +#include "rtf2html.h" + +void ParStyle::clearFormatting() +{ + // For now, do nothing. + // dir is not a formatting item. +} + +QString RTF2HTML::quoteString(const QString &_str, quoteMode mode) +{ + QString str = _str; + str.replace(QRegExp("&"), "&"); + str.replace(QRegExp("<"), "<"); + str.replace(QRegExp(">"), ">"); + str.replace(QRegExp("\""), """); + str.replace(QRegExp("\r"), ""); + switch (mode){ + case quoteHTML: + str.replace(QRegExp("\n"), "
    \n"); + break; + case quoteXML: + str.replace(QRegExp("\n"), "
    \n"); + break; + default: + break; + } + QRegExp re(" +"); + int len; + int pos = 0; + + while ((pos = re.search(str, pos)) != -1) { + len = re.matchedLength(); + + if (len == 1) + continue; + QString s = " "; + for (int i = 1; i < len; i++) + s += " "; + str.replace(pos, len, s); + } + return str; +} + +RTF2HTML::RTF2HTML() + : cur_level(this) +{ + rtf_ptr = NULL; + bExplicitParagraph = false; +} + +OutTag* RTF2HTML::getTopOutTag(TagEnum tagType) +{ + vector::iterator it, it_end; + for(it = oTags.begin(), it_end = oTags.end(); it != it_end; ++it) + if (it->tag == tagType) + return &(*it); + return NULL; +} + +void RTF2HTML::FlushOutTags() +{ + vector::iterator iter; + for (iter = oTags.begin(); iter != oTags.end(); iter++) + { + OutTag &t = *iter; + switch (t.tag){ + case TAG_FONT_COLOR: + { + // RTF colors are 1-based; colors[] is a 0-based array. + if (t.param > colors.size() || t.param == 0) + break; + QColor &c = colors[t.param-1]; + PrintUnquoted("", c.red(), c.green(), c.blue()); + } + break; + case TAG_FONT_SIZE: + PrintUnquoted("", t.param); + break; + case TAG_FONT_FAMILY: + { + if (t.param > fonts.size() || t.param == 0) + break; + FontDef &f = fonts[t.param-1]; + string name = (!f.nonTaggedName.empty()) ? f.nonTaggedName : f.taggedName; + PrintUnquoted("", name.c_str()); + } + break; + case TAG_BG_COLOR:{ + if (t.param > colors.size() || t.param == 0) + break; + QColor &c = colors[t.param-1]; + PrintUnquoted("", c.red(), c.green(), c.blue()); + break; + } + case TAG_BOLD: + PrintUnquoted(""); + break; + case TAG_ITALIC: + PrintUnquoted(""); + break; + case TAG_UNDERLINE: + PrintUnquoted(""); + break; + default: + break; + } + } + oTags.clear(); +} + +// This function will close the already-opened tag 'tag'. It will take +// care of closing the tags which 'tag' contains first (ie. it will unroll +// the stack till the point where 'tag' is). +void Level::resetTag(TagEnum tag) +{ + // A stack which'll keep tags we had to close in order to reach 'tag'. + // After we close 'tag', we will reopen them. + stack s; + + while (p->tags.size() > m_nTagsStartPos){ // Don't go further than the point where this level starts. + + TagEnum nTag = p->tags.top(); + + /* A tag will be located in oTags if it still wasn't printed out. + A tag will get printed out only if necessary (e.g. will + be optimized away). + Thus, for each tag we remove from the actual tag stack, we also + try to remove a yet-to-be-printed tag, and only if there are no + yet-to-be-printed tags left, we start closing the tags we pop. + The tags have one space - needed for umlaute (�) and .utf8() + */ + if (p->oTags.empty()){ + switch (nTag){ + case TAG_FONT_COLOR: + case TAG_FONT_SIZE: + case TAG_BG_COLOR: + case TAG_FONT_FAMILY: + p->PrintUnquoted(" "); + break; + case TAG_BOLD: + p->PrintUnquoted(" "); + break; + case TAG_ITALIC: + p->PrintUnquoted(" "); + break; + case TAG_UNDERLINE: + p->PrintUnquoted(" "); + break; + default: + break; + } + }else{ + p->oTags.pop_back(); + } + + p->tags.pop(); + if (nTag == tag) break; // if we reached the tag we were looking to close. + s.push(nTag); // remember to reopen this tag + } + + if (tag == TAG_ALL) return; + + while (!s.empty()){ + TagEnum nTag = s.top(); + switch (nTag){ + case TAG_FONT_COLOR:{ + unsigned nFontColor = m_nFontColor; + m_nFontColor = 0; + setFontColor(nFontColor); + break; + } + case TAG_FONT_SIZE:{ + unsigned nFontSize = m_nFontSize; + m_nFontSize = 0; + setFontSize(nFontSize); + break; + } + case TAG_BG_COLOR:{ + unsigned nFontBgColor = m_nFontBgColor; + m_nFontBgColor = 0; + setFontBgColor(nFontBgColor); + break; + } + case TAG_FONT_FAMILY:{ + unsigned nFont = m_nFont; + m_nFont = 0; + setFont(nFont); + break; + } + case TAG_BOLD:{ + bool nBold = m_bBold; + m_bBold = false; + setBold(nBold); + break; + } + case TAG_ITALIC:{ + bool nItalic = m_bItalic; + m_bItalic = false; + setItalic(nItalic); + break; + } + case TAG_UNDERLINE:{ + bool nUnderline = m_bUnderline; + m_bUnderline = false; + setUnderline(nUnderline); + break; + } + default: + break; + } + s.pop(); + } +} + +Level::Level(RTF2HTML *_p) : + p(_p), + m_bFontTbl(false), + m_bColors(false), + m_bFontName(false), + m_bTaggedFontNameOk(false), + m_nFont(0), + m_nEncoding(0) +{ + m_nTagsStartPos = p->tags.size(); + Init(); +} + +Level::Level(const Level &l) : + p(l.p), + m_bFontTbl(l.m_bFontTbl), + m_bColors(l.m_bColors), + m_bFontName(false), + m_bTaggedFontNameOk(l.m_bTaggedFontNameOk), + m_nFont(l.m_nFont), + m_nEncoding(l.m_nEncoding) +{ + m_nTagsStartPos = p->tags.size(); + Init(); +} + +void Level::Init() +{ + m_nFontColor = 0; + m_nFontBgColor = 0; + m_nFontSize = 0; + m_bFontName = false; + m_bBold = false; + m_bItalic = false; + m_bUnderline = false; +} + +void RTF2HTML::PrintUnquoted(const char *str, ...) +{ + char buff[1024]; + va_list ap; + va_start(ap, str); + vsnprintf(buff, sizeof(buff), str, ap); + va_end(ap); + sParagraph += buff; +} + +void RTF2HTML::PrintQuoted(const QString &str) +{ + sParagraph += quoteString(str); +} + +void RTF2HTML::FlushParagraph() +{ + if (!bExplicitParagraph || sParagraph.isEmpty()) + return; + + /* + s += "

    "; + s += sParagraph; + s += "

    "; + */ + + s += sParagraph; + s += "
    "; + + // Clear up the paragraph members + sParagraph = ""; + bExplicitParagraph = false; +} + +void Level::setFont(unsigned nFont) +{ + if (nFont <= 0) + return; + + if (m_bFontTbl){ + if (nFont > p->fonts.size() +1){ + kdDebug(14200) << "Invalid font index (" << + nFont << ") while parsing font table." << endl; + return; + } + if (nFont > p->fonts.size()){ + FontDef f; + f.charset = 0; + p->fonts.push_back(f); + } + m_nFont = nFont; + } + else + { + if (nFont > p->fonts.size()) + { + kdDebug(14200) << "Invalid font index (" << + nFont << ")." << endl; + return; + } + if (m_nFont == nFont) + return; + m_nFont = nFont; + if (m_nFont) resetTag(TAG_FONT_FAMILY); + m_nEncoding = p->fonts[nFont-1].charset; + p->oTags.push_back(OutTag(TAG_FONT_FAMILY, nFont)); + p->PutTag(TAG_FONT_FAMILY); + } +} + +void Level::setFontName() +{ + // This function is only valid during font table parsing. + if (m_bFontTbl){ + if ((m_nFont > 0) && (m_nFont <= p->fonts.size())) + // Be prepared to accept a font name. + m_bFontName = true; + } +} + +void Level::setEncoding(unsigned nEncoding) +{ + if (m_bFontTbl){ + if ((m_nFont > 0) && (m_nFont <= p->fonts.size())) + p->fonts[m_nFont-1].charset = nEncoding; + return; + } + m_nEncoding = nEncoding; +} + +void Level::setBold(bool bBold) +{ + if (m_bBold == bBold) return; + if (m_bBold) resetTag(TAG_BOLD); + m_bBold = bBold; + if (!m_bBold) return; + p->oTags.push_back(OutTag(TAG_BOLD, 0)); + p->PutTag(TAG_BOLD); +} + +void Level::setItalic(bool bItalic) +{ + if (m_bItalic == bItalic) return; + if (m_bItalic) resetTag(TAG_ITALIC); + m_bItalic = bItalic; + if (!m_bItalic) return; + p->oTags.push_back(OutTag(TAG_ITALIC, 0)); + p->PutTag(TAG_ITALIC); +} + +void Level::setUnderline(bool bUnderline) +{ + if (m_bUnderline == bUnderline) return; + if (m_bUnderline) resetTag(TAG_UNDERLINE); + m_bUnderline = bUnderline; + if (!m_bUnderline) return; + p->oTags.push_back(OutTag(TAG_UNDERLINE, 0)); + p->PutTag(TAG_UNDERLINE); +} + +void Level::setFontColor(unsigned short nColor) +{ + if (m_nFontColor == nColor) return; + if (m_nFontColor) resetTag(TAG_FONT_COLOR); + if (nColor > p->colors.size()) return; + m_nFontColor = nColor; + p->oTags.push_back(OutTag(TAG_FONT_COLOR, m_nFontColor)); + p->PutTag(TAG_FONT_COLOR); +} + +void Level::setFontBgColor(unsigned short nColor) +{ + if (m_nFontBgColor == nColor) return; + if (m_nFontBgColor != 0) resetTag(TAG_BG_COLOR); + if (nColor > p->colors.size()) return; + m_nFontBgColor = nColor; + p->oTags.push_back(OutTag(TAG_BG_COLOR, m_nFontBgColor)); + p->PutTag(TAG_BG_COLOR); +} + +void Level::setFontSizeHalfPoints(unsigned short nSize) +{ + setFontSize(nSize / 2); +} + +void Level::setFontSize(unsigned short nSize) +{ + if (m_nFontSize == nSize) return; + if (m_nFontSize) resetTag(TAG_FONT_SIZE); + p->oTags.push_back(OutTag(TAG_FONT_SIZE, nSize)); + p->PutTag(TAG_FONT_SIZE); + m_nFontSize = nSize; +} + +void Level::startParagraph() +{ + // Whatever tags we have open now, close them. + // We cannot carry let character formatting tags wrap paragraphs, + // since a formatting tag can close at any time and we cannot + // close the paragraph any time we want. + resetTag(TAG_ALL); + + // Flush the current paragraph HTML to the document HTML. + p->FlushParagraph(); + + // Mark this new paragraph as an explicit one (from \par etc.). + p->bExplicitParagraph = true; + + // Restore character formatting + p->oTags.push_back(OutTag(TAG_FONT_SIZE, m_nFontSize)); + p->PutTag(TAG_FONT_SIZE); + p->oTags.push_back(OutTag(TAG_FONT_COLOR, m_nFontColor)); + p->PutTag(TAG_FONT_COLOR); + p->oTags.push_back(OutTag(TAG_FONT_FAMILY, m_nFont)); + p->PutTag(TAG_FONT_FAMILY); + if (m_nFontBgColor != 0) + { + p->oTags.push_back(OutTag(TAG_BG_COLOR, m_nFontBgColor)); + p->PutTag(TAG_BG_COLOR); + } + if (m_bBold) + { + p->oTags.push_back(OutTag(TAG_BOLD, 0)); + p->PutTag(TAG_BOLD); + } + if (m_bItalic) + { + p->PutTag(TAG_ITALIC); + p->oTags.push_back(OutTag(TAG_ITALIC, 0)); + } + if (m_bUnderline) + { + p->oTags.push_back(OutTag(TAG_UNDERLINE, 0)); + p->PutTag(TAG_UNDERLINE); + } +} + +bool Level::isParagraphOpen() const +{ + return p->bExplicitParagraph; +} + +void Level::clearParagraphFormatting() +{ + // implicitly start a paragraph + if (!isParagraphOpen()) + startParagraph(); + // Since we don't implement any of the paragraph formatting tags (e.g. alignment), + // we don't clean up anything here. Note that \pard does NOT clean character + // formatting (such as font size, font weight, italics...). + p->parStyle.clearFormatting(); +} + +void Level::setParagraphDirLTR() +{ + // implicitly start a paragraph + if (!isParagraphOpen()) + startParagraph(); + p->parStyle.dir = ParStyle::DirLTR; +} + +void Level::setParagraphDirRTL() +{ + // implicitly start a paragraph + if (!isParagraphOpen()) + startParagraph(); + p->parStyle.dir = ParStyle::DirRTL; +} + +void Level::addLineBreak() +{ + p->PrintUnquoted("
    "); +} + +void Level::reset() +{ + resetTag(TAG_ALL); + if (m_bColors){ + if (m_bColorInit){ + QColor c(m_nRed, m_nGreen, m_nBlue); + p->colors.push_back(c); + resetColors(); + } + return; + } +} + +void Level::setText(const char *str) +{ + if (m_bColors) + { + reset(); + } + else if (m_bFontTbl) + { + if ((m_nFont <= 0) || (m_nFont > p->fonts.size())) + return; + + FontDef& def = p->fonts[m_nFont-1]; + + char *pp = strchr(str, ';'); + unsigned size; + if (pp != NULL) + size = (pp - str); + else + size = strlen(str); + + if (m_bFontName) + { + def.nonTaggedName.append(str, size); + // We know we have the entire name + if (pp != NULL) + m_bFontName = false; + } + else if (!m_bTaggedFontNameOk) + { + def.taggedName.append(str, size); + if (pp != NULL) + m_bTaggedFontNameOk = true; + } + } + else + { + for (; *str; str++) + if ((unsigned char)(*str) >= ' ') break; + if (!*str) return; + p->FlushOutTags(); + text += str; + } +} + +void Level::flush() +{ + if (text.length() == 0) return; + // TODO: Make encoding work in Kopete + /* + const char *encoding = NULL; + if (m_nEncoding){ + for (const ENCODING *c = ICQPlugin::core->encodings; c->language; c++){ + if (!c->bMain) + continue; + if ((unsigned)c->rtf_code == m_nEncoding){ + encoding = c->codec; + break; + } + } + } + if (encoding == NULL) + encoding = p->encoding; + + QTextCodec *codec = ICQClient::_getCodec(encoding); + */ + //p->PrintQuoted(codec->toUnicode(text.c_str(), text.length())); + p->PrintQuoted(text.c_str()); + text = ""; +} + +const unsigned FONTTBL = 0; +const unsigned COLORTBL = 1; +const unsigned RED = 2; +const unsigned GREEN = 3; +const unsigned BLUE = 4; +const unsigned CF = 5; +const unsigned FS = 6; +const unsigned HIGHLIGHT = 7; +const unsigned PARD = 8; +const unsigned PAR = 9; +const unsigned I = 10; +const unsigned B = 11; +const unsigned UL = 12; +const unsigned F = 13; +const unsigned FCHARSET = 14; +const unsigned FNAME = 15; +const unsigned ULNONE = 16; +const unsigned LTRPAR = 17; +const unsigned RTLPAR = 18; +const unsigned LINE = 19; + +static char cmds[] = + "fonttbl\x00" + "colortbl\x00" + "red\x00" + "green\x00" + "blue\x00" + "cf\x00" + "fs\x00" + "highlight\x00" + "pard\x00" + "par\x00" + "i\x00" + "b\x00" + "ul\x00" + "f\x00" + "fcharset\x00" + "fname\x00" + "ulnone\x00" + "ltrpar\x00" + "rtlpar\x00" + "line\x00" + "\x00"; + +int yywrap() { return 1; } + +static char h2d(char c) +{ + if ((c >= '0') && (c <= '9')) + return c - '0'; + if ((c >= 'A') && (c <= 'F')) + return (c - 'A') + 10; + if ((c >= 'a') && (c <= 'f')) + return (c - 'a') + 10; + return 0; +} + +QString RTF2HTML::Parse(const char *rtf, const char *_encoding) +{ + encoding = _encoding; + YY_BUFFER_STATE yy_current_buffer = yy_scan_string(rtf); + rtf_ptr = rtf; + for (;;){ + int res = yylex(); + if (!res) break; + switch (res){ + case UP:{ + cur_level.flush(); + levels.push(cur_level); + break; + } + case DOWN:{ + if (!levels.empty()){ + cur_level.flush(); + cur_level.reset(); + cur_level = levels.top(); + levels.pop(); + } + break; + } + case IMG:{ + cur_level.flush(); + const char ICQIMAGE[] = "icqimage"; + const char *smiles[] = { ":-)" , ":-O" , ":-|" , ":-/" , // 0-3 + ":-(" , ":-*" , ":-/" , ":'(" , // 4-7 + ";-)" , ":-@" , ":-$" , ":-X" , // 8-B + ":-P" , "8-)" , "O:)" , ":-D" }; // C-F + const char *p = yytext + 3; + if ((strlen(p) > strlen(ICQIMAGE)) && !memcmp(p, ICQIMAGE, strlen(ICQIMAGE))){ + unsigned n = 0; + for (p += strlen(ICQIMAGE); *p; p++){ + if ((*p >= '0') && (*p <= '9')){ + n = n << 4; + n += (*p - '0'); + continue; + } + if ((*p >= 'A') && (*p <= 'F')){ + n = n << 4; + n += (*p - 'A') + 10; + continue; + } + if ((*p >= 'a') && (*p <= 'f')){ + n = n << 4; + n += (*p - 'a') + 10; + continue; + } + break; + } + if (n < 16) + PrintUnquoted(" %s ", smiles[n] ); + }else{ + kdDebug(14200) << "Unknown image " << yytext << endl; + } + break; + } + case SKIP: + break; + case SLASH: + cur_level.setText(yytext+1); + break; + case TXT: + cur_level.setText(yytext); + break; + case UNICODE_CHAR:{ + cur_level.flush(); + sParagraph += QChar((unsigned short)(atol(yytext + 2))); + break; + } + case HEX:{ + char s[2]; + s[0] = (h2d(yytext[2]) << 4) + h2d(yytext[3]); + s[1] = 0; + cur_level.setText(s); + break; + } + case CMD: + { + cur_level.flush(); + const char *cmd = yytext + 1; + unsigned n_cmd = 0; + unsigned cmd_size = 0; + int cmd_value = -1; + const char *p; + for (p = cmd; *p; p++, cmd_size++) + if (((*p >= '0') && (*p <= '9')) || (*p == ' ')) break; + if (*p && (*p != ' ')) cmd_value = atol(p); + for (p = cmds; *p; p += strlen(p) + 1, n_cmd++){ + if (strlen(p) > cmd_size) continue; + if (!memcmp(p, cmd, cmd_size)) break; + } + cmd += strlen(p); + switch (n_cmd){ + case FONTTBL: // fonttbl + cur_level.setFontTbl(); + break; + case COLORTBL: + cur_level.setColors(); + break; + case RED: + cur_level.setRed(cmd_value); + break; + case GREEN: + cur_level.setGreen(cmd_value); + break; + case BLUE: + cur_level.setBlue(cmd_value); + break; + case CF: + cur_level.setFontColor(cmd_value); + break; + case FS: + cur_level.setFontSizeHalfPoints(cmd_value); + break; + case HIGHLIGHT: + cur_level.setFontBgColor(cmd_value); + break; + case PARD: + cur_level.clearParagraphFormatting(); + break; + case PAR: + cur_level.startParagraph(); + break; + case I: + cur_level.setItalic(cmd_value != 0); + break; + case B: + cur_level.setBold(cmd_value != 0); + break; + case UL: + cur_level.setUnderline(cmd_value != 0); + break; + case ULNONE: + cur_level.setUnderline(false); + break; + case F: + // RTF fonts are 0-based; our font index is 1-based. + cur_level.setFont(cmd_value+1); + break; + case FCHARSET: + cur_level.setEncoding(cmd_value); + break; + case FNAME: + cur_level.setFontName(); + break; + case LTRPAR: + cur_level.setParagraphDirLTR(); + break; + case RTLPAR: + cur_level.setParagraphDirRTL(); + break; + case LINE: + cur_level.addLineBreak(); + } + break; + } + } + } + yy_delete_buffer(yy_current_buffer); + yy_current_buffer = NULL; + FlushParagraph(); + return s; +} + +/* +bool ICQClient::parseRTF(const char *rtf, const char *encoding, QString &res) +{ + char _RTF[] = "{\\rtf"; + if ((strlen(rtf) > strlen(_RTF)) && !memcmp(rtf, _RTF, strlen(_RTF))){ + RTF2HTML p; + res = p.Parse(rtf, encoding); + return true; + } + QTextCodec *codec = ICQClient::_getCodec(encoding); + res = codec->toUnicode(rtf, strlen(rtf)); + return false; +} +*/ diff --git a/kopete/protocols/groupwise/libgroupwise/rtf2html.h b/kopete/protocols/groupwise/libgroupwise/rtf2html.h new file mode 100644 index 00000000..a305b89d --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/rtf2html.h @@ -0,0 +1,207 @@ +/* + rtf2html.h - A simple RTF Parser + + Copyright (c) 2002 by Vladimir Shutoff (original code) + Copyright (c) 2004 by Thiago S. Barcelos (Kopete port) + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef RTF2HTML_H +#define RTF2HTML_H + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace std; + +struct FontDef +{ + int charset; + string taggedName; + string nonTaggedName; +}; + +class RTF2HTML; + +enum TagEnum +{ + TAG_ALL = 0, + TAG_FONT_SIZE, + TAG_FONT_COLOR, + TAG_FONT_FAMILY, + TAG_BG_COLOR, + TAG_BOLD, + TAG_ITALIC, + TAG_UNDERLINE +}; + +class ParStyle +{ +public: + ParStyle() { dir = DirLTR; } + void clearFormatting(); + +public: + enum {DirLTR, DirRTL} dir; +}; + +class Level +{ +public: + Level(RTF2HTML *_p); + Level(const Level&); + void setText(const char* str); + void setFontTbl() { m_bFontTbl = true; } + void setColors() { m_bColors = true; resetColors(); } + void setRed(unsigned char val) { setColor(val, &m_nRed); } + void setGreen(unsigned char val) { setColor(val, &m_nGreen); } + void setBlue(unsigned char val) { setColor(val, &m_nBlue); } + void setFont(unsigned nFont); + void setEncoding(unsigned nFont); + void setFontName(); + void setFontColor(unsigned short color); + void setFontBgColor(unsigned short color); + void setFontSizeHalfPoints(unsigned short sizeInHalfPoints); + void setFontSize(unsigned short sizeInPoints); + void setBold(bool); + void setItalic(bool); + void setUnderline(bool); + void startParagraph(); + bool isParagraphOpen() const; + void clearParagraphFormatting(); + void setParagraphDirLTR(); + void setParagraphDirRTL(); + void addLineBreak(); + void flush(); + void reset(); + void resetTag(TagEnum tag); +protected: + string text; + void Init(); + RTF2HTML *p; + void resetColors() { m_nRed = m_nGreen = m_nBlue = 0; m_bColorInit = false; } + void setColor(unsigned char val, unsigned char *p) + { *p = val; m_bColorInit=true; } + + // Marks the position in m_tags where this level begun. + unsigned m_nTagsStartPos; + + // True when parsing the fonts table + bool m_bFontTbl; + // True when parsing the colors table. + bool m_bColors; + // True when inside a 'fname' block. + bool m_bFontName; + // False until we get the tagged font name. + bool m_bTaggedFontNameOk; + + unsigned char m_nRed; + unsigned char m_nGreen; + unsigned char m_nBlue; + bool m_bColorInit; + unsigned m_nFont; // 1-based + unsigned m_nEncoding; + unsigned m_nFontColor; // 1-based + unsigned m_nFontSize; + unsigned m_nFontBgColor; // 1-based + bool m_bBold; + bool m_bItalic; + bool m_bUnderline; +}; + +class OutTag +{ +public: + OutTag(TagEnum _tag, unsigned _param) : tag(_tag), param(_param) {} + TagEnum tag; + unsigned param; +}; + +enum quoteMode +{ + quoteHTML, + quoteXML, + quoteNOBR +}; + +class RTF2HTML +{ + friend class Level; + +public: + RTF2HTML(); + QString Parse(const char *rtf, const char *encoding); + + // Paragraph-specific functions: + + QString quoteString(const QString &_str, quoteMode mode = quoteHTML); + // Appends a string with formatting into the paragraph buffer. + void PrintUnquoted(const char *str, ...); + // Quotes and appends a string to the paragraph buffer. + void PrintQuoted(const QString &str); + // Writes down the tags from oTags into the paragraph buffer. + void FlushOutTags(); + // Retrieves the top not-yet-written tag. + OutTag* getTopOutTag(TagEnum tagType); + // Writes down the paragraph buffer and resets the paragraph state. + void FlushParagraph(); + + // Document-wide functions: + + void PutTag(TagEnum n) + { + tags.push(n); + } + +protected: + +// Paragraph members + + // True if the paragraph was opened explicitly. + bool bExplicitParagraph; + // The paragraph's HTML buffer. + QString sParagraph; + // Defines the paragraph's formatting. + ParStyle parStyle; + // Tags which weren't yet printed out. + vector oTags; + +// Document members + + // The document HTML buffer. + QString s; + // Fonts table. + vector fonts; + // Colors table. + vector colors; + // Stack of tags (across all levels, not just current level) + stack tags; + +// RTF parser internals + + const char *rtf_ptr; + const char *encoding; + Level cur_level; + stack levels; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/safedelete.cpp b/kopete/protocols/groupwise/libgroupwise/safedelete.cpp new file mode 100644 index 00000000..703e8ed3 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/safedelete.cpp @@ -0,0 +1,139 @@ +/* + safedelete.cpp - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "safedelete.h" + +#include + +//---------------------------------------------------------------------------- +// SafeDelete +//---------------------------------------------------------------------------- +SafeDelete::SafeDelete() +{ + lock = 0; +} + +SafeDelete::~SafeDelete() +{ + if(lock) + lock->dying(); +} + +void SafeDelete::deleteLater(QObject *o) +{ + if(!lock) + deleteSingle(o); + else + list.append(o); +} + +void SafeDelete::unlock() +{ + lock = 0; + deleteAll(); +} + +void SafeDelete::deleteAll() +{ + if(list.isEmpty()) + return; + + QObjectListIt it(list); + for(QObject *o; (o = it.current()); ++it) + deleteSingle(o); + list.clear(); +} + +void SafeDelete::deleteSingle(QObject *o) +{ +#if QT_VERSION < 0x030000 + // roll our own QObject::deleteLater() + SafeDeleteLater *sdl = SafeDeleteLater::ensureExists(); + sdl->deleteItLater(o); +#else + o->deleteLater(); +#endif +} + +//---------------------------------------------------------------------------- +// SafeDeleteLock +//---------------------------------------------------------------------------- +SafeDeleteLock::SafeDeleteLock(SafeDelete *sd) +{ + own = false; + if(!sd->lock) { + _sd = sd; + _sd->lock = this; + } + else + _sd = 0; +} + +SafeDeleteLock::~SafeDeleteLock() +{ + if(_sd) { + _sd->unlock(); + if(own) + delete _sd; + } +} + +void SafeDeleteLock::dying() +{ + _sd = new SafeDelete(*_sd); + own = true; +} + +//---------------------------------------------------------------------------- +// SafeDeleteLater +//---------------------------------------------------------------------------- +SafeDeleteLater *SafeDeleteLater::self = 0; + +SafeDeleteLater *SafeDeleteLater::ensureExists() +{ + if(!self) + new SafeDeleteLater(); + return self; +} + +SafeDeleteLater::SafeDeleteLater() +{ + list.setAutoDelete(true); + self = this; + QTimer::singleShot(0, this, SLOT(explode())); +} + +SafeDeleteLater::~SafeDeleteLater() +{ + list.clear(); + self = 0; +} + +void SafeDeleteLater::deleteItLater(QObject *o) +{ + list.append(o); +} + +void SafeDeleteLater::explode() +{ + delete this; +} + +#include "safedelete.moc" + diff --git a/kopete/protocols/groupwise/libgroupwise/safedelete.h b/kopete/protocols/groupwise/libgroupwise/safedelete.h new file mode 100644 index 00000000..e8215c06 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/safedelete.h @@ -0,0 +1,79 @@ +/* + gwclientstream.h - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef SAFEDELETE_H +#define SAFEDELETE_H + +#include +#include + +class SafeDelete; +class SafeDeleteLock +{ +public: + SafeDeleteLock(SafeDelete *sd); + ~SafeDeleteLock(); + +private: + SafeDelete *_sd; + bool own; + friend class SafeDelete; + void dying(); +}; + +class SafeDelete +{ +public: + SafeDelete(); + ~SafeDelete(); + + void deleteLater(QObject *o); + + // same as QObject::deleteLater() + static void deleteSingle(QObject *o); + +private: + QObjectList list; + void deleteAll(); + + friend class SafeDeleteLock; + SafeDeleteLock *lock; + void unlock(); +}; + +class SafeDeleteLater : public QObject +{ + Q_OBJECT +public: + static SafeDeleteLater *ensureExists(); + void deleteItLater(QObject *o); + +private slots: + void explode(); + +private: + SafeDeleteLater(); + ~SafeDeleteLater(); + + QObjectList list; + friend class SafeDelete; + static SafeDeleteLater *self; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/securestream.cpp b/kopete/protocols/groupwise/libgroupwise/securestream.cpp new file mode 100644 index 00000000..583be03e --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/securestream.cpp @@ -0,0 +1,542 @@ +/* + securestream.h - Kopete Groupwise Protocol + Combines a ByteStream with TLS and SASL + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +/* + Note: SecureStream depends on the underlying security layers to signal + plain-to-encrypted results immediately (as opposed to waiting for the + event loop) so that the user cannot add/remove security layers during + this conversion moment. QCA::TLS and QCA::SASL behave as expected, + but future layers might not. +*/ + +#include +#include +#include + +#include"securestream.h" + +//---------------------------------------------------------------------------- +// LayerTracker +//---------------------------------------------------------------------------- +LayerTracker::LayerTracker() +{ + p = 0; +} + +void LayerTracker::reset() +{USE_TLSHANDLER + p = 0; + list.clear(); +} + +void LayerTracker::addPlain(int plain) +{ + p += plain; +} + +void LayerTracker::specifyEncoded(int encoded, int plain) +{ + // can't specify more bytes than we have + if(plain > p) + plain = p; + p -= plain; + Item i; + i.plain = plain; + i.encoded = encoded; + list += i; +} + +int LayerTracker::finished(int encoded) +{ + int plain = 0; + for(QValueList::Iterator it = list.begin(); it != list.end();) { + Item &i = *it; + + // not enough? + if(encoded < i.encoded) { + i.encoded -= encoded; + break; + } + + encoded -= i.encoded; + plain += i.plain; + it = list.remove(it); + } + return plain; +} + +//---------------------------------------------------------------------------- +// SecureStream +//---------------------------------------------------------------------------- + +SecureLayer::SecureLayer(QCA::TLS *t) +{ + type = TLS; + p.tls = t; + init(); + connect(p.tls, SIGNAL(handshaken()), SLOT(tls_handshaken())); + connect(p.tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); + connect(p.tls, SIGNAL(readyReadOutgoing(int)), SLOT(tls_readyReadOutgoing(int))); + connect(p.tls, SIGNAL(closed()), SLOT(tls_closed())); + connect(p.tls, SIGNAL(error(int)), SLOT(tls_error(int))); +} + +SecureLayer::SecureLayer(QCA::SASL *s) +{ + type = SASL; + p.sasl = s; + init(); + connect(p.sasl, SIGNAL(readyRead()), SLOT(sasl_readyRead())); + connect(p.sasl, SIGNAL(readyReadOutgoing(int)), SLOT(sasl_readyReadOutgoing(int))); + connect(p.sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); +} + +#ifdef USE_TLSHANDLER +SecureLayer::SecureLayer(TLSHandler *t) +{ + type = TLSH; + p.tlsHandler = t; + init(); + connect(p.tlsHandler, SIGNAL(success()), SLOT(tlsHandler_success())); + connect(p.tlsHandler, SIGNAL(fail()), SLOT(tlsHandler_fail())); + connect(p.tlsHandler, SIGNAL(closed()), SLOT(tlsHandler_closed())); + connect(p.tlsHandler, SIGNAL(readyRead(const QByteArray &)), SLOT(tlsHandler_readyRead(const QByteArray &))); + connect(p.tlsHandler, SIGNAL(readyReadOutgoing(const QByteArray &, int)), SLOT(tlsHandler_readyReadOutgoing(const QByteArray &, int))); +} +#endif + +void SecureLayer::init() +{ + tls_done = false; + prebytes = 0; +} + +void SecureLayer::write(const QByteArray &a) +{ + layer.addPlain(a.size()); + switch(type) { + case TLS: { p.tls->write(a); break; } + case SASL: { p.sasl->write(a); break; } +#ifdef USE_TLSHANDLER + case TLSH: { p.tlsHandler->write(a); break; } +#endif + } +} + +void SecureLayer::writeIncoming(const QByteArray &a) +{ + switch(type) { + case TLS: { p.tls->writeIncoming(a); break; } + case SASL: { p.sasl->writeIncoming(a); break; } +#ifdef USE_TLSHANDLER + case TLSH: { p.tlsHandler->writeIncoming(a); break; } +#endif + } +} + +int SecureLayer::finished(int plain) +{ + int written = 0; + + // deal with prebytes (bytes sent prior to this security layer) + if(prebytes > 0) { + if(prebytes >= plain) { + written += plain; + prebytes -= plain; + plain = 0; + } + else { + written += prebytes; + plain -= prebytes; + prebytes = 0; + } + } + + // put remainder into the layer tracker + if(type == SASL || tls_done) + written += layer.finished(plain); + + return written; +} + +void SecureLayer::tls_handshaken() +{ + tls_done = true; + tlsHandshaken(); +} + +void SecureLayer::tls_readyRead() +{ + QByteArray a = p.tls->read(); + readyRead(a); +} + +void SecureLayer::tls_readyReadOutgoing(int plainBytes) +{ + QByteArray a = p.tls->readOutgoing(); + if(tls_done) + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); +} + +void SecureLayer::tls_closed() +{ + QByteArray a = p.tls->readUnprocessed(); + tlsClosed(a); +} + +void SecureLayer::tls_error(int x) +{ + error(x); +} + +void SecureLayer::sasl_readyRead() +{ + QByteArray a = p.sasl->read(); + readyRead(a); +} + +void SecureLayer::sasl_readyReadOutgoing(int plainBytes) +{ + QByteArray a = p.sasl->readOutgoing(); + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); +} + +void SecureLayer::sasl_error(int x) +{ + error(x); +} + +#ifdef USE_TLSHANDLER +void SecureLayer::tlsHandler_success() +{ + tls_done = true; + tlsHandshaken(); +} + +void SecureLayer::tlsHandler_fail() +{ + error(0); +} + +void SecureLayer::tlsHandler_closed() +{ + tlsClosed(QByteArray()); +} + +void SecureLayer::tlsHandler_readyRead(const QByteArray &a) +{ + readyRead(a); +} + +void SecureLayer::tlsHandler_readyReadOutgoing(const QByteArray &a, int plainBytes) +{ + if(tls_done) + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); +} +#endif + +class SecureStream::Private +{ +public: + ByteStream *bs; + QPtrList layers; + int pending; + int errorCode; + bool active; + bool topInProgress; + + bool haveTLS() const + { + QPtrListIterator it(layers); + for(SecureLayer *s; (s = it.current()); ++it) { + if(s->type == SecureLayer::TLS +#ifdef USE_TLSHANDLER + || s->type == SecureLayer::TLSH +#endif + ) { + return true; + } + } + return false; + } + + bool haveSASL() const + { + QPtrListIterator it(layers); + for(SecureLayer *s; (s = it.current()); ++it) { + if(s->type == SecureLayer::SASL) + return true; + } + return false; + } +}; + +SecureStream::SecureStream(ByteStream *s) +:ByteStream(0) +{ + d = new Private; + + d->bs = s; + connect(d->bs, SIGNAL(readyRead()), SLOT(bs_readyRead())); + connect(d->bs, SIGNAL(bytesWritten(int)), SLOT(bs_bytesWritten(int))); + + d->layers.setAutoDelete(true); + d->pending = 0; + d->active = true; + d->topInProgress = false; +} + +SecureStream::~SecureStream() +{ + delete d; +} + +void SecureStream::linkLayer(QObject *s) +{ + connect(s, SIGNAL(tlsHandshaken()), SLOT(layer_tlsHandshaken())); + connect(s, SIGNAL(tlsClosed(const QByteArray &)), SLOT(layer_tlsClosed(const QByteArray &))); + connect(s, SIGNAL(readyRead(const QByteArray &)), SLOT(layer_readyRead(const QByteArray &))); + connect(s, SIGNAL(needWrite(const QByteArray &)), SLOT(layer_needWrite(const QByteArray &))); + connect(s, SIGNAL(error(int)), SLOT(layer_error(int))); +} + +int SecureStream::calcPrebytes() const +{ + int x = 0; + QPtrListIterator it(d->layers); + for(SecureLayer *s; (s = it.current()); ++it) + x += s->prebytes; + return (d->pending - x); +} + +void SecureStream::startTLSClient(QCA::TLS *t, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + insertData(spare); +} + +void SecureStream::startTLSServer(QCA::TLS *t, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + insertData(spare); +} + +void SecureStream::setLayerSASL(QCA::SASL *sasl, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveSASL()) + return; + + SecureLayer *s = new SecureLayer(sasl); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + + insertData(spare); +} + +#ifdef USE_TLSHANDLER +void SecureStream::startTLSClient(TLSHandler *t, const QString &server, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + // unlike QCA::TLS, TLSHandler has no return value + s->p.tlsHandler->startClient(server); + + insertData(spare); +} +#endif + +void SecureStream::closeTLS() +{ + SecureLayer *s = d->layers.getLast(); + if(s) { + if(s->type == SecureLayer::TLS) + s->p.tls->close(); + } +} + +int SecureStream::errorCode() const +{ + return d->errorCode; +} + +bool SecureStream::isOpen() const +{ + return d->active; +} + +void SecureStream::write(const QByteArray &a) +{ + if(!isOpen()) + return; + + d->pending += a.size(); + + // send to the last layer + SecureLayer *s = d->layers.getLast(); + if(s) + s->write(a); + else + writeRawData(a); +} + +int SecureStream::bytesToWrite() const +{ + return d->pending; +} + +void SecureStream::bs_readyRead() +{ + QByteArray a = d->bs->read(); + + // send to the first layer + SecureLayer *s = d->layers.getFirst(); + if(s) + s->writeIncoming(a); + else + incomingData(a); +} + +void SecureStream::bs_bytesWritten(int bytes) +{ + QPtrListIterator it(d->layers); + for(SecureLayer *s; (s = it.current()); ++it) + bytes = s->finished(bytes); + + if(bytes > 0) { + d->pending -= bytes; + bytesWritten(bytes); + } +} + +void SecureStream::layer_tlsHandshaken() +{ + d->topInProgress = false; + tlsHandshaken(); +} + +void SecureStream::layer_tlsClosed(const QByteArray &) +{ + d->active = false; + d->layers.clear(); + tlsClosed(); +} + +void SecureStream::layer_readyRead(const QByteArray &a) +{ + SecureLayer *s = (SecureLayer *)sender(); + QPtrListIterator it(d->layers); + while(it.current() != s) + ++it; + + // pass upwards + ++it; + s = it.current(); + if(s) + s->writeIncoming(a); + else + incomingData(a); +} + +void SecureStream::layer_needWrite(const QByteArray &a) +{ + SecureLayer *s = (SecureLayer *)sender(); + QPtrListIterator it(d->layers); + while(it.current() != s) + ++it; + + // pass downwards + --it; + s = it.current(); + if(s) + s->write(a); + else + writeRawData(a); +} + +void SecureStream::layer_error(int x) +{ + SecureLayer *s = (SecureLayer *)sender(); + int type = s->type; + d->errorCode = x; + d->active = false; + d->layers.clear(); + if(type == SecureLayer::TLS) + error(ErrTLS); + else if(type == SecureLayer::SASL) + error(ErrSASL); +#ifdef USE_TLSHANDLER + else if(type == SecureLayer::TLSH) + error(ErrTLS); +#endif +} + +void SecureStream::insertData(const QByteArray &a) +{ + if(!a.isEmpty()) { + SecureLayer *s = d->layers.getLast(); + if(s) + s->writeIncoming(a); + else + incomingData(a); + } +} + +void SecureStream::writeRawData(const QByteArray &a) +{ + d->bs->write(a); +} + +void SecureStream::incomingData(const QByteArray &a) +{ + appendRead(a); + //qDebug( "SecureStream::incomingData() got %i bytes ", a.size() ); + + if(bytesAvailable()) + readyRead(); +} + +#include "securestream.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/securestream.h b/kopete/protocols/groupwise/libgroupwise/securestream.h new file mode 100644 index 00000000..36999b14 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/securestream.h @@ -0,0 +1,156 @@ +/* + securestream.h - Kopete Groupwise Protocol + Combines a ByteStream with TLS and SASL + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef SECURESTREAM_H +#define SECURESTREAM_H + +#include +#include "tlshandler.h" +#include"bytestream.h" + +#define USE_TLSHANDLER + +#ifdef USE_TLSHANDLER + class TLSHandler; +#endif + +class SecureStream : public ByteStream +{ + Q_OBJECT +public: + enum Error { ErrTLS = ErrCustom, ErrSASL }; + SecureStream(ByteStream *s); + ~SecureStream(); + + void startTLSClient(QCA::TLS *t, const QByteArray &spare=QByteArray()); + void startTLSServer(QCA::TLS *t, const QByteArray &spare=QByteArray()); + void setLayerSASL(QCA::SASL *s, const QByteArray &spare=QByteArray()); +#ifdef USE_TLSHANDLER + void startTLSClient(TLSHandler *t, const QString &server, const QByteArray &spare=QByteArray()); +#endif + + void closeTLS(); + int errorCode() const; + + // reimplemented + bool isOpen() const; + void write(const QByteArray &); + int bytesToWrite() const; + +signals: + void tlsHandshaken(); + void tlsClosed(); + +private slots: + void bs_readyRead(); + void bs_bytesWritten(int); + + void layer_tlsHandshaken(); + void layer_tlsClosed(const QByteArray &); + void layer_readyRead(const QByteArray &); + void layer_needWrite(const QByteArray &); + void layer_error(int); + +private: + void linkLayer(QObject *); + int calcPrebytes() const; + void insertData(const QByteArray &a); + void writeRawData(const QByteArray &a); + void incomingData(const QByteArray &a); + + class Private; + Private *d; +}; + +class LayerTracker +{ +public: + struct Item + { + int plain; + int encoded; + }; +USE_TLSHANDLER + LayerTracker(); + + void reset(); + void addPlain(int plain); + void specifyEncoded(int encoded, int plain); + int finished(int encoded); + + int p; + QValueList list; +}; + + +class SecureLayer : public QObject +{ + Q_OBJECT +public: + SecureLayer(QCA::TLS *t); + SecureLayer(QCA::SASL *s); +#ifdef USE_TLSHANDLER + SecureLayer(TLSHandler *t); +#endif + void init(); + void write(const QByteArray &a); + void writeIncoming(const QByteArray &a); + int finished(int plain); + + enum { TLS, SASL, TLSH }; + int type; + union { + QCA::TLS *tls; + QCA::SASL *sasl; +#ifdef USE_TLSHANDLER + TLSHandler *tlsHandler; +#endif + } p; + LayerTracker layer; + bool tls_done; + int prebytes; + +signals: + void tlsHandshaken(); + void tlsClosed(const QByteArray &); + void readyRead(const QByteArray &); + void needWrite(const QByteArray &); + void error(int); + +private slots: + void tls_handshaken(); + void tls_readyRead(); + void tls_readyReadOutgoing(int plainBytes); + void tls_closed(); + void tls_error(int x); + void sasl_readyRead(); + void sasl_readyReadOutgoing(int plainBytes); + void sasl_error(int x); +#ifdef USE_TLSHANDLER + void tlsHandler_success(); + void tlsHandler_fail(); + void tlsHandler_closed(); + void tlsHandler_readyRead(const QByteArray &a); + void tlsHandler_readyReadOutgoing(const QByteArray &a, int plainBytes); +#endif + +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/stream.cpp b/kopete/protocols/groupwise/libgroupwise/stream.cpp new file mode 100644 index 00000000..5817f4c3 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/stream.cpp @@ -0,0 +1,31 @@ +/* + stream.cpp - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "stream.h" + +Stream::Stream(QObject *parent) +:QObject(parent) +{ +} + +Stream::~Stream() +{ +} + +#include "stream.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/stream.h b/kopete/protocols/groupwise/libgroupwise/stream.h new file mode 100644 index 00000000..37a63652 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/stream.h @@ -0,0 +1,92 @@ +/* + stream.h - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include "qobject.h" + +#include "gwerror.h" +#include "gwfield.h" +#include "request.h" + +#ifndef GW_STREAM_H +#define GW_STREAM_H + + +class Stream : public QObject +{ + Q_OBJECT +public: + enum Error { ErrParse, ErrProtocol, ErrStream, ErrCustom = 10 }; + enum StreamCond { + GenericStreamError, + Conflict, + ConnectionTimeout, + InternalServerError, + InvalidFrom, +/*# InvalidXml, // not required*/ + PolicyViolation, + ResourceConstraint, + SystemShutdown + }; + + Stream(QObject *parent=0); + virtual ~Stream(); + + virtual void close()=0; + virtual int errorCondition() const=0; + virtual QString errorText() const=0; + //virtual QDomElement errorAppSpec() const=0; + + /** + * Are there any messages waiting to be read + */ + virtual bool transfersAvailable() const = 0; // adapt to messages + /** + * Read a message received from the server + */ + virtual Transfer * read() = 0; + + /** + * Send a message to the server + */ + virtual void write( Request *request) = 0; // ", ends up on a send queue, by a very roundabout way, see analysis at bottom of + +// # virtual bool stanzaAvailable() const=0; +// # virtual Stanza read()=0; +// # virtual void write(const Stanza &s)=0; + +// # virtual QDomDocument & doc() const=0; +// # virtual QString baseNS() const=0; +// # virtual bool old() const=0; + +// # Stanza createStanza(Stanza::Kind k, const Jid &to="", const QString &type="", const QString &id=""); +// # Stanza createStanza(const QDomElement &e); + +// static QString xmlToString(const static XmlProtocol *foo = 0; +//QDomElement &e, bool clip=false); + +signals: + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); +// void stanzaWritten(); + void error(int); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/task.cpp b/kopete/protocols/groupwise/libgroupwise/task.cpp new file mode 100644 index 00000000..786bf36b --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/task.cpp @@ -0,0 +1,268 @@ +/* + task.cpp - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include "client.h" +#include "gwfield.h" +#include "request.h" +#include "safedelete.h" + +#include "task.h" + +class Task::TaskPrivate +{ +public: + TaskPrivate() {} + + QString id; + bool success; + int statusCode; + QString statusString; + Client *client; + bool insignificant, deleteme, autoDelete; + bool done; + Transfer * transfer; +}; + +Task::Task(Task *parent) +:QObject(parent) +{ + init(); + d->transfer = 0; + d->client = parent->client(); + d->id = client()->genUniqueId(); + connect(d->client, SIGNAL(disconnected()), SLOT(clientDisconnected())); +} + +Task::Task(Client *parent, bool) +:QObject(0) +{ + init(); + + d->client = parent; + connect(d->client, SIGNAL(disconnected()), SLOT(clientDisconnected())); +} + +Task::~Task() +{ + delete d; +} + +void Task::init() +{ + d = new TaskPrivate; + d->success = false; + d->insignificant = false; + d->deleteme = false; + d->autoDelete = false; + d->done = false; + d->transfer = 0; + d->statusCode = 0; +} + +Task *Task::parent() const +{ + return (Task *)QObject::parent(); +} + +Client *Task::client() const +{ + return d->client; +} + +Transfer * Task::transfer() const +{ + return d->transfer; +} + +void Task::setTransfer( Transfer * transfer ) +{ + d->transfer = transfer; +} + +QString Task::id() const +{ + return d->id; +} + +bool Task::success() const +{ + return d->success; +} + +int Task::statusCode() const +{ + return d->statusCode; +} + +const QString & Task::statusString() const +{ + return d->statusString; +} + +void Task::go(bool autoDelete) +{ + d->autoDelete = autoDelete; + + onGo(); +} + +bool Task::take( Transfer * transfer) +{ + const QObjectList *p = children(); + if(!p) + return false; + + // pass along the transfer to our children + QObjectListIt it(*p); + Task *t; + for(; it.current(); ++it) { + QObject *obj = it.current(); + if(!obj->inherits("Task")) + continue; + + t = static_cast(obj); + + if(t->take( transfer )) + { + client()->debug( QString( "Transfer ACCEPTED by: %1" ).arg( t->className() ) ); + return true; + } + } + + return false; +} + +void Task::safeDelete() +{ + if(d->deleteme) + return; + + d->deleteme = true; + if(!d->insignificant) + SafeDelete::deleteSingle(this); +} + +void Task::onGo() +{ + client()->debug( "ERROR: calling default NULL onGo() for this task, you should reimplement this!"); +} + +void Task::onDisconnect() +{ + if(!d->done) { + d->success = false; + d->statusCode = ErrDisc; + d->statusString = tr("Disconnected"); + + // delay this so that tasks that react don't block the shutdown + QTimer::singleShot(0, this, SLOT(done())); + } +} + +void Task::send( Request * request ) +{ + client()->send( request ); +} + +void Task::setSuccess(int code, const QString &str) +{ + if(!d->done) { + d->success = true; + d->statusCode = code; + d->statusString = str; + done(); + } +} + +void Task::setError(int code, const QString &str) +{ + if(!d->done) { + d->success = false; + d->statusCode = code; + if ( str.isEmpty() ) + d->statusString = GroupWise::errorCodeToString( code ); + else + d->statusString = str; + done(); + } +} + +void Task::done() +{ + debug("Task::done()"); + if(d->done || d->insignificant) + return; + d->done = true; + + if(d->deleteme || d->autoDelete) + d->deleteme = true; + + d->insignificant = true; + debug("emitting finished"); + finished(); + d->insignificant = false; + + if(d->deleteme) + SafeDelete::deleteSingle(this); +} + +void Task::clientDisconnected() +{ + onDisconnect(); +} + +// void Task::debug(const char *fmt, ...) +// { +// char *buf; +// QString str; +// int size = 1024; +// int r; +// +// do { +// buf = new char[size]; +// va_list ap; +// va_start(ap, fmt); +// r = vsnprintf(buf, size, fmt, ap); +// va_end(ap); +// +// if(r != -1) +// str = QString(buf); +// +// delete [] buf; +// +// size *= 2; +// } while(r == -1); +// +// debug(str); +// } + +void Task::debug(const QString &str) +{ + client()->debug(QString("%1: ").arg(className()) + str); +} + +bool Task::forMe( const Transfer * transfer ) const +{ + Q_UNUSED( transfer ); + return false; +} + +#include "task.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/task.h b/kopete/protocols/groupwise/libgroupwise/task.h new file mode 100644 index 00000000..0a34cafa --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/task.h @@ -0,0 +1,97 @@ +/* + task.h - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GW_TASK_H +#define GW_TASK_H + +#include + +#include "gwerror.h" +#include "gwfield.h" +#include "transfer.h" + +class Client; +class Request; + +class Task : public QObject +{ + Q_OBJECT +public: + enum { ErrDisc }; + Task(Task *parent); + Task( Client *, bool isRoot ); + virtual ~Task(); + + Task *parent() const; + Client *client() const; + Transfer *transfer() const; + + QString id() const; + + bool success() const; + int statusCode() const; + const QString & statusString() const; + + void go( bool autoDelete=false ); + /** + * Allows a task to examine an incoming Transfer and decide whether to 'take' it + * for further processing. + */ + virtual bool take( Transfer* transfer ); + void safeDelete(); + +signals: + void finished(); + +protected: + virtual void onGo(); + virtual void onDisconnect(); + void send( Request * request ); + void setSuccess( int code=0, const QString &str="" ); + /** + * If an empty string is passed, this sets the error string based on the error code using GroupWise::errorCodeToString + */ + void setError( int code=0, const QString &str="" ); +// void debug( const char *, ... ); + void debug( const QString & ); + /** + * Used in take() to check if the offered transfer is for this Task + * @return true if this Task should take the Transfer. Default impl always returns false. + */ + virtual bool forMe( const Transfer * transfer ) const; + /** + * Creates a transfer with the given command and field list + */ + void createTransfer( const QString & command, const Field::FieldList fields ); + /** + * Direct setter for Tasks which don't have any fields + */ + void setTransfer( Transfer * transfer ); +private slots: + void clientDisconnected(); + void done(); + +private: + void init(); + + class TaskPrivate; + TaskPrivate *d; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/Makefile.am b/kopete/protocols/groupwise/libgroupwise/tasks/Makefile.am new file mode 100644 index 00000000..cf966ca2 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/Makefile.am @@ -0,0 +1,30 @@ +INCLUDES = -I$(top_srcdir)/protocols/groupwise/libgroupwise/qca/src \ + -I$(srcdir)/../../libgroupwise/ -I$(top_srcdir)/kopete/protocols/groupwise/libgroupwise/qca/src \ + $(all_includes) +METASOURCES = AUTO +noinst_LTLIBRARIES = libgroupwise_tasks.la + +libgroupwise_tasks_la_SOURCES = requesttask.cpp eventtask.cpp logintask.cpp \ + setstatustask.cpp statustask.cpp conferencetask.cpp createconferencetask.cpp \ + sendmessagetask.cpp getdetailstask.cpp getstatustask.cpp typingtask.cpp connectiontask.cpp \ + sendinvitetask.cpp joinconferencetask.cpp leaveconferencetask.cpp rejectinvitetask.cpp \ + keepalivetask.cpp createcontacttask.cpp modifycontactlisttask.cpp createfoldertask.cpp \ + movecontacttask.cpp updateitemtask.cpp createcontactinstancetask.cpp deleteitemtask.cpp \ + updatefoldertask.cpp updatecontacttask.cpp pollsearchresultstask.cpp privacyitemtask.cpp \ + needfoldertask.cpp searchchattask.cpp searchusertask.cpp searchusertask.h \ + getchatsearchresultstask.cpp chatcountstask.cpp chatpropertiestask.cpp joinchattask.cpp +noinst_HEADERS = requesttask.h eventtask.h logintask.h setstatustask.h \ + statustask.h conferencetask.h createconferencetask.h sendmessagetask.h \ + getdetailstask.h getstatustask.h typingtask.h connectiontask.h sendinvitetask.h \ + joinconferencetask.h leaveconferencetask.h rejectinvitetask.h createcontacttask.h \ + modifycontactlisttask.h createfoldertask.h movecontacttask.h updateitemtask.h deleteitemtask.h \ + updatefoldertask.h updatecontacttask.h pollsearchresultstask.h privacyitemtask.h \ + needfoldertask.h searchchattask.h getchatsearchresultstask.h searchusertask.h \ + chatcountstask.h joinchattask.h + + +libgroupwise_tasks_la_LDFLAGS = -no-undefined $(all_libraries) +libgroupwise_tasks_la_LIBADD = $(LIB_QT) + + + diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/chatcountstask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/chatcountstask.cpp new file mode 100644 index 00000000..9e9837f7 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/chatcountstask.cpp @@ -0,0 +1,87 @@ +/* + Kopete Groupwise Protocol + ChatCountsTask.cpp - Task to update chatroom participant counts + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include + +#include "gwfield.h" +#include "response.h" + +#include "chatcountstask.h" + +using namespace GroupWise; + +ChatCountsTask::ChatCountsTask(Task* parent): RequestTask(parent) +{ + Field::FieldList lst; + createTransfer( "chatcounts", lst ); +} + + +ChatCountsTask::~ChatCountsTask() +{ +} + +bool ChatCountsTask::take( Transfer * transfer ) +{ + if ( !forMe( transfer ) ) + return false; + Response * response = dynamic_cast( transfer ); + if ( !response ) + return false; + if ( response->resultCode() ) + { + setError( response->resultCode() ); + return true; + } + + Field::FieldList responseFields = response->fields(); + Field::MultiField * resultsArray = responseFields.findMultiField( NM_A_FA_RESULTS ); + if ( !resultsArray ) + { + setError( Protocol ); + return true; + } + Field::FieldList counts = resultsArray->fields(); + const Field::FieldListIterator end = counts.end(); + for ( Field::FieldListIterator it = counts.find( NM_A_FA_CHAT ); + it != end; + it = counts.find( ++it, NM_A_FA_CHAT ) ) + { + Field::MultiField * mf = static_cast( *it ); + Field::FieldList chat = mf->fields(); + QString roomName; + int participants; + // read the supplied fields, set metadata and status. + Field::SingleField * sf; + if ( ( sf = chat.findSingleField ( NM_A_DISPLAY_NAME ) ) ) + roomName = sf->value().toString(); + if ( ( sf = chat.findSingleField ( NM_A_UD_PARTICIPANTS ) ) ) + participants = sf->value().toInt(); + + m_results.insert( roomName, participants ); + } + return true; +} + +QMap< QString, int > ChatCountsTask::results() +{ + return m_results; +} + +#include "chatcountstask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/chatcountstask.h b/kopete/protocols/groupwise/libgroupwise/tasks/chatcountstask.h new file mode 100644 index 00000000..c80a219a --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/chatcountstask.h @@ -0,0 +1,49 @@ +/* + Kopete Groupwise Protocol + chatcountstask.cpp - Task to update chatroom participant counts + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef CHATCOUNTSTASK_H +#define CHATCOUNTSTASK_H + +#include + +#include "gwerror.h" +#include "gwfield.h" + +#include "requesttask.h" + +/** +Get the current number of users in each chat on the server + +@author SUSE Linux Products GmbH + */ +class ChatCountsTask : public RequestTask +{ + Q_OBJECT + public: + ChatCountsTask(Task* parent); + ~ChatCountsTask(); + bool take( Transfer * transfer ); + /** + * Contains a list of all the chatrooms that have participants on the server. If a chatroom exists but is empty, this task does not return a result, so update the participants count to 0. + */ + QMap< QString, int > results(); + private: + QMap< QString, int > m_results; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/chatpropertiestask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/chatpropertiestask.cpp new file mode 100644 index 00000000..66b2da42 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/chatpropertiestask.cpp @@ -0,0 +1,139 @@ +/* + Kopete Groupwise Protocol + ChatPropertiesTask.cpp - Task to update chatroom participant counts + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include "gwfield.h" +#include "response.h" + +#include "chatpropertiestask.h" + +using namespace GroupWise; + +ChatPropertiesTask::ChatPropertiesTask(Task* parent): RequestTask(parent) +{ +} + + +ChatPropertiesTask::~ChatPropertiesTask() +{ +} + +void ChatPropertiesTask::setChat( const QString &displayName ) +{ + Field::FieldList lst; + m_chat = displayName; + lst.append( new Field::SingleField( NM_A_DISPLAY_NAME, 0, NMFIELD_TYPE_UTF8, m_chat ) ); + createTransfer( "chatproperties", lst ); +} + +bool ChatPropertiesTask::take( Transfer * transfer ) +{ + if ( !forMe( transfer ) ) + return false; + Response * response = dynamic_cast( transfer ); + if ( !response ) + return false; + if ( response->resultCode() ) + { + setError( response->resultCode() ); + return true; + } + + Field::FieldList responseFields = response->fields(); + Field::MultiField * resultsArray = responseFields.findMultiField( NM_A_FA_CHAT ); + if ( !resultsArray ) + { + setError( Protocol ); + return true; + } + + Field::FieldList lst = resultsArray->fields(); + const Field::FieldListIterator end = lst.end(); + for ( Field::FieldListIterator it = lst.begin(); + it != end; + ++it ) + { + Field::SingleField * sf = dynamic_cast( *it ); + if ( sf ) + { + if ( sf->tag() == NM_A_DISPLAY_NAME ) + continue; + else if ( sf->tag() == NM_A_CHAT_OWNER_DN ) + m_ownerDn = sf->value().toString(); + else if ( sf->tag() == NM_A_CHAT_CREATOR_DN ) + m_creatorDn= sf->value().toString(); + else if ( sf->tag() == NM_A_DESCRIPTION ) + m_description = sf->value().toString(); + else if ( sf->tag() == NM_A_DISCLAIMER ) + m_disclaimer = sf->value().toString(); + else if ( sf->tag() == NM_A_QUERY ) + m_query = sf->value().toString(); + else if ( sf->tag() == NM_A_ARCHIVE ) + m_archive = sf->value().toString(); + else if ( sf->tag() == NM_A_SZ_TOPIC ) + m_topic = sf->value().toString(); + else if ( sf->tag() == NM_A_CREATION_TIME ) + m_creationTime.setTime_t( sf->value().toInt() ); + else if ( sf->tag() == NM_A_UD_CHAT_RIGHTS ) + m_rights = sf->value().toInt(); + + } + else + { + Field::MultiField * mf = dynamic_cast( *it ); + if ( mf ) + { + if ( mf->tag() == NM_A_FA_CHAT_ACL ) + { + Field::FieldList acl = mf->fields(); + const Field::FieldListIterator aclEnd = acl.end(); + for ( Field::FieldListIterator aclIt = acl.begin(); + aclIt != aclEnd; + ++aclIt ) + { + Field::MultiField * aclEntryFields = dynamic_cast( *aclIt ); + if ( aclEntryFields ) + { + ChatContact entry; + Field::FieldList entryFields = aclEntryFields->fields(); + Field::SingleField * sf; + if ( ( sf = entryFields.findSingleField ( NM_A_SZ_DN ) ) ) + entry.dn = sf->value().toString(); + if ( ( sf = entryFields.findSingleField ( NM_A_SZ_ACCESS_FLAGS ) ) ) + entry.chatRights = sf->value().toInt(); + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << "got acl entry: " << entry.dn << ", " << entry.chatRights << endl; + m_aclEntries.append( entry ); + } + + } + } + } + } + } + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << "Got chatroom properties: " << m_chat << " : " << m_ownerDn << ", " << m_description << ", " << m_disclaimer << ", " << m_query << ", " << m_archive << ", " << m_topic << ", " << m_creatorDn << ", " << m_creationTime.toString() << ", " << m_rights << endl; + finished(); + return true; +} + +QValueList< ChatContact > ChatPropertiesTask::aclEntries() +{ + return m_aclEntries; +} + +#include "chatpropertiestask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/chatpropertiestask.h b/kopete/protocols/groupwise/libgroupwise/tasks/chatpropertiestask.h new file mode 100644 index 00000000..c9f890dd --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/chatpropertiestask.h @@ -0,0 +1,64 @@ +/* + Kopete Groupwise Protocol + chatcountstask.cpp - Task to update chatroom participant counts + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef CHATPROPERTIESTASK_H +#define CHATPROPERTIESTASK_H + +#include +#include +#include "gwchatrooms.h" +#include "gwerror.h" +#include "gwfield.h" + +#include "requesttask.h" + +/** +Get the current number of users in each chat on the server + +@author SUSE Linux Products GmbH + */ +class ChatPropertiesTask : public RequestTask +{ + Q_OBJECT + public: + ChatPropertiesTask(Task* parent); + ~ChatPropertiesTask(); + /** + * Specify which chatroom to get properties for + */ + void setChat( const QString & ); + bool take( Transfer * transfer ); + /** + * Contains a list of the ACL entries for the specified chatroom + */ + QValueList< GroupWise::ChatContact > aclEntries(); + QString m_chat; + QString m_ownerDn; + QString m_description; + QString m_disclaimer; + QString m_query; + QString m_archive; + QString m_maxUsers; + QString m_topic; + QString m_creatorDn; + QDateTime m_creationTime; + uint m_rights; + QValueList< GroupWise::ChatContact > m_aclEntries; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/conferencetask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/conferencetask.cpp new file mode 100644 index 00000000..9773a622 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/conferencetask.cpp @@ -0,0 +1,230 @@ +/* + Kopete Groupwise Protocol + conferencetask.cpp - Event Handling task responsible for all conference related events + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "client.h" +#include "userdetailsmanager.h" + +#include "conferencetask.h" + +ConferenceTask::ConferenceTask( Task* parent ) + : EventTask( parent ) +{ + // register all the events that this task monitors + registerEvent( GroupWise::ConferenceClosed ); + registerEvent( GroupWise::ConferenceJoined ); + registerEvent( GroupWise::ConferenceLeft ); + registerEvent( GroupWise::ReceiveMessage ); + registerEvent( GroupWise::UserTyping ); + registerEvent( GroupWise::UserNotTyping ); + registerEvent( GroupWise::ConferenceInvite ); + registerEvent( GroupWise::ConferenceInviteNotify ); + registerEvent( GroupWise::ConferenceReject ); + registerEvent( GroupWise::ReceiveAutoReply ); + // GW7 + registerEvent( GroupWise::ReceivedBroadcast ); + registerEvent( GroupWise::ReceivedSystemBroadcast ); + + // listen to the UserDetailsManager telling us that user details are available + connect( client()->userDetailsManager(), SIGNAL( gotContactDetails( const GroupWise::ContactDetails & ) ), + SLOT( slotReceiveUserDetails( const GroupWise::ContactDetails & ) ) ); +} + + +ConferenceTask::~ConferenceTask() +{ +} + +void ConferenceTask::dumpConferenceEvent( ConferenceEvent & evt ) +{ + client()->debug( QString( "Conference Event - guid: %1 user: %2 timestamp: %3:%4:%5" ).arg + ( evt.guid ).arg( evt.user.ascii() ).arg( evt.timeStamp.time().hour() ).arg + ( evt.timeStamp.time().minute() ).arg( evt.timeStamp.time().second() ) ); + client()->debug( QString( " flags: %1" ).arg( evt.flags, 8 ) ); +} + +bool ConferenceTask::take( Transfer * transfer ) +{ + EventTransfer * incomingEvent; + if ( forMe( transfer, incomingEvent ) ) + { + client()->debug( "Got a conference event:" ); + ConferenceEvent event; + event.type = (GroupWise::Event)( incomingEvent->eventType() ); + event.timeStamp = incomingEvent->timeStamp(); + event.user = incomingEvent->source(); + event.flags = 0; + Q_ASSERT( incomingEvent->hasGuid() ); + event.guid = incomingEvent->guid(); + + switch ( event.type ) + { + case GroupWise::ConferenceClosed: + // extra debug - we never see these events, against spec. + client()->debug( "********************" ); + client()->debug( "* ConferenceClosed *" ); + client()->debug( "* ConferenceClosed *" ); + client()->debug( "* ConferenceClosed *" ); + client()->debug( "********************" ); + emit closed( event ); + break; + case GroupWise::ConferenceJoined: + Q_ASSERT( incomingEvent->hasFlags() ); + event.flags = incomingEvent->flags(); + client()->debug( "ConferenceJoined" ); + if ( !queueWhileAwaitingData( event ) ) + emit joined( event ); + break; + case GroupWise::ConferenceLeft: + Q_ASSERT( incomingEvent->hasFlags() ); + event.flags = incomingEvent->flags(); + client()->debug( "ConferenceLeft" ); + emit left( event ); + break; + case GroupWise::ReceiveMessage: + Q_ASSERT( incomingEvent->hasFlags() ); + event.flags = incomingEvent->flags(); + Q_ASSERT( incomingEvent->hasMessage() ); + event.message = incomingEvent->message(); + client()->debug( "ReceiveMessage" ); + client()->debug( QString( "message: %1" ).arg( event.message ) ); + if ( !queueWhileAwaitingData( event ) ) + emit message( event ); + break; + case GroupWise::UserTyping: + client()->debug( "UserTyping" ); + emit typing( event ); + break; + case GroupWise::UserNotTyping: + client()->debug( "UserNotTyping" ); + emit notTyping( event ); + break; + case GroupWise::ConferenceInvite: + Q_ASSERT( incomingEvent->hasMessage() ); + event.message = incomingEvent->message(); + client()->debug( "ConferenceInvite" ); + client()->debug( QString( "message: %1" ).arg( event.message ) ); + if ( !queueWhileAwaitingData( event ) ) + emit invited( event ); + break; + case GroupWise::ConferenceInviteNotify: + client()->debug( "ConferenceInviteNotify" ); + if ( !queueWhileAwaitingData( event ) ) + emit otherInvited( event ); + break; + case GroupWise::ConferenceReject: + client()->debug( "ConferenceReject" ); + if ( !queueWhileAwaitingData( event ) ) + emit invitationDeclined( event ); + break; + case GroupWise::ReceiveAutoReply: + Q_ASSERT( incomingEvent->hasFlags() ); + event.flags = incomingEvent->flags(); + Q_ASSERT( incomingEvent->hasMessage() ); + event.message = incomingEvent->message(); + client()->debug( "ReceiveAutoReply" ); + client()->debug( QString( "message: %1" ).arg( event.message.ascii() ) ); + emit autoReply( event ); + break; + case GroupWise::ReceivedBroadcast: + Q_ASSERT( incomingEvent->hasMessage() ); + event.message = incomingEvent->message(); + client()->debug( "ReceivedBroadCast" ); + client()->debug( QString( "message: %1" ).arg( event.message ) ); + if ( !queueWhileAwaitingData( event ) ) + emit broadcast( event ); + break; + case GroupWise::ReceivedSystemBroadcast: + Q_ASSERT( incomingEvent->hasMessage() ); + event.message = incomingEvent->message(); + client()->debug( "ReceivedSystemBroadCast" ); + client()->debug( QString( "message: %1" ).arg( event.message ) ); + emit systemBroadcast( event ); + break; + default: + client()->debug( QString( "WARNING: didn't handle registered event %1, on conference %2" ).arg( incomingEvent->eventType() ).arg( event.guid.ascii() ) ); + } + dumpConferenceEvent( event ); + + return true; + } + return false; +} + +void ConferenceTask::slotReceiveUserDetails( const GroupWise::ContactDetails & details ) +{ + client()->debug( "ConferenceTask::slotReceiveUserDetails()" ); + + // dequeue any events which are deliverable now we have these details + QValueListIterator< ConferenceEvent > end = m_pendingEvents.end(); + QValueListIterator< ConferenceEvent > it = m_pendingEvents.begin(); + while ( it != end ) + { + QValueListIterator< ConferenceEvent > current = it; + ++it; + // if the details relate to event, try again to handle it + if ( details.dn == (*current).user ) + { + client()->debug( QString( " - got details for event involving %1" ).arg( (*current).user ) ); + switch ( (*current).type ) + { + case GroupWise::ConferenceJoined: + client()->debug( "ConferenceJoined" ); + emit joined( *current ); + break; + case GroupWise::ReceiveMessage: + client()->debug( "ReceiveMessage" ); + emit message( *current ); + break; + case GroupWise::ConferenceInvite: + client()->debug( "ConferenceInvite" ); + emit invited( *current ); + break; + case GroupWise::ConferenceInviteNotify: + client()->debug( "ConferenceInviteNotify" ); + emit otherInvited( *current ); + break; + default: + client()->debug( "Queued an event while waiting for more data, but didn't write a handler for the dequeue!" ); + } + m_pendingEvents.remove( current ); + client()->debug( QString( "Event handled - now %1 pending events" ).arg + ( (uint)m_pendingEvents.count() ) ); + } + } +} + + +bool ConferenceTask::queueWhileAwaitingData( const ConferenceEvent & event ) +{ + if ( client()->userDetailsManager()->known( event.user ) ) + { + client()->debug( "ConferenceTask::queueWhileAwaitingData() - source is known!" ); + return false; + } + else + { + client()->debug( QString( "ConferenceTask::queueWhileAwaitingData() - queueing event involving %1" ).arg( event.user ) ); + client()->userDetailsManager()->requestDetails( event.user ); + m_pendingEvents.append( event ); + return true; + } +} + +#include "conferencetask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/conferencetask.h b/kopete/protocols/groupwise/libgroupwise/tasks/conferencetask.h new file mode 100644 index 00000000..42f4fc2b --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/conferencetask.h @@ -0,0 +1,74 @@ +/* + Kopete Groupwise Protocol + conferencetask.h - Event Handling task responsible for all conference related events + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef CONFERENCETASK_H +#define CONFERENCETASK_H + +#include "gwerror.h" +#include "eventtask.h" + +/** + * This Task is responsible for handling all conference related events, and signalling them up to @ref GroupWiseAccount + * Implementation note: It would be fit the model more cleanly to have each of these in their own Task, but the amount + * of code they share is quite large, and the differences in the way each event uses it are small + * @author SUSE AG + */ + +using namespace GroupWise; + +class ConferenceTask : public EventTask +{ +Q_OBJECT +public: + ConferenceTask( Task* parent ); + ~ConferenceTask(); + bool take( Transfer * transfer ); +signals: + void typing( const ConferenceEvent & ); + void notTyping( const ConferenceEvent & ); + void joined( const ConferenceEvent & ); + void left( const ConferenceEvent &); + void invited( const ConferenceEvent & ); + void otherInvited( const ConferenceEvent & ); + void invitationDeclined( const ConferenceEvent & ); + void closed( const ConferenceEvent & ); + void message( const ConferenceEvent &); + void autoReply( const ConferenceEvent & ); + // GW7 + void broadcast( const ConferenceEvent &); + void systemBroadcast( const ConferenceEvent &); +protected slots: + void slotReceiveUserDetails( const GroupWise::ContactDetails & ); +protected: + Q_UINT32 readFlags( QDataStream & din ); + QString readMessage( QDataStream & din ); + /** + * Checks to see if we need more data from the client before we can propagate this event + * and queues the event if so + * @return whether the event was queued pending more data + */ + bool queueWhileAwaitingData( const ConferenceEvent & event ); + void dumpConferenceEvent( ConferenceEvent & evt ); +private: + // A list of events which are waiting for more data from the server before they can be exposed to the client + QValueList< ConferenceEvent > m_pendingEvents; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/connectiontask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/connectiontask.cpp new file mode 100644 index 00000000..3d041208 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/connectiontask.cpp @@ -0,0 +1,55 @@ +/* + Kopete Groupwise Protocol + connectiontask.cpp - Event Handling task responsible for all connection related events + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ +#include "client.h" + +#include "connectiontask.h" + +ConnectionTask::ConnectionTask(Task* parent): EventTask(parent) +{ + registerEvent( GroupWise::UserDisconnect ); + registerEvent( GroupWise::ServerDisconnect ); +} + + +ConnectionTask::~ConnectionTask() +{ +} + +bool ConnectionTask::take( Transfer * transfer ) +{ + EventTransfer * incomingEvent; + if ( forMe( transfer, incomingEvent ) ) + { + client()->debug( "Got a connection event:" ); + switch ( incomingEvent->eventType() ) + { + case GroupWise::UserDisconnect: + emit connectedElsewhere(); + break; + case GroupWise::ServerDisconnect: + emit serverDisconnect(); + break; + } + return true; + } + return false; +} + +#include "connectiontask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/connectiontask.h b/kopete/protocols/groupwise/libgroupwise/tasks/connectiontask.h new file mode 100644 index 00000000..95df34f9 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/connectiontask.h @@ -0,0 +1,43 @@ +/* + Kopete Groupwise Protocol + connectiontask.h - Event Handling task responsible for all connection related events + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef CONNECTIONTASK_H +#define CONNECTIONTASK_H + +#include "eventtask.h" + +/** +This task monitors connection related events, currently 'connected elsewhere' disconnects and server disconnect notification. + +@author Kopete Developers +*/ +class ConnectionTask : public EventTask +{ +Q_OBJECT +public: + ConnectionTask(Task* parent); + ~ConnectionTask(); + bool take( Transfer * transfer ); +signals: + void connectedElsewhere(); + void serverDisconnect(); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/createconferencetask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/createconferencetask.cpp new file mode 100644 index 00000000..8be16888 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/createconferencetask.cpp @@ -0,0 +1,85 @@ +/* + Kopete Groupwise Protocol + createconferencetask.cpp - Request task that creates conferences on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "client.h" +#include "response.h" + + +#include "createconferencetask.h" + +CreateConferenceTask::CreateConferenceTask(Task* parent): RequestTask(parent), m_confId( 0 ), m_guid( BLANK_GUID ) +{ + +} + +CreateConferenceTask::~CreateConferenceTask() +{ +} + +void CreateConferenceTask::conference( const int confId, const QStringList &participants ) +{ + m_confId = confId; + Field::FieldList lst, tmp; + // list containing blank GUID + tmp.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, m_guid ) ); + lst.append( new Field::MultiField( NM_A_FA_CONVERSATION, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, tmp ) ); + // series of participants (may be empty ) + QValueListConstIterator end = participants.end(); + for ( QValueListConstIterator it = participants.begin(); it != end; ++it ) + lst.append( new Field::SingleField( NM_A_SZ_DN, 0, NMFIELD_TYPE_DN, *it ) ); + lst.append( new Field::SingleField( NM_A_SZ_DN, 0, NMFIELD_TYPE_DN, client()->userDN() ) ); + createTransfer( "createconf", lst ); +} + +bool CreateConferenceTask::take( Transfer * transfer ) +{ + if ( !forMe( transfer ) ) + return false; + Response * response = dynamic_cast( transfer ); + if ( !response ) + return false; + + // if the createconf was successful, read the GUID and store it + Field::FieldList responseFields = response->fields(); + if ( response->resultCode() == GroupWise::None ) + { + Field::MultiField * listField = responseFields.findMultiField( NM_A_FA_CONVERSATION ); + Field::FieldList guidList = listField->fields(); + Field::SingleField * guidField = guidList.findSingleField( NM_A_SZ_OBJECT_ID ); + m_guid = guidField->value().toString(); + setSuccess(); + } + else + setError( response->resultCode() ); + return true; + +} + +GroupWise::ConferenceGuid CreateConferenceTask::conferenceGUID() const +{ + return m_guid; +} + +int CreateConferenceTask::clientConfId() const +{ + return m_confId; +} + +#include "createconferencetask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/createconferencetask.h b/kopete/protocols/groupwise/libgroupwise/tasks/createconferencetask.h new file mode 100644 index 00000000..48d5702e --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/createconferencetask.h @@ -0,0 +1,54 @@ +/* + Kopete Groupwise Protocol + createconferencetask.h - Request task that creates conferences on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef CREATECONFERENCETASK_H +#define CREATECONFERENCETASK_H + +#include "requesttask.h" + +/** +This task is responsible for creating a conference at the server, and confirming that the server allowed the conference to be created. + +@author SUSE AG +*/ +class CreateConferenceTask : public RequestTask +{ +Q_OBJECT +public: + CreateConferenceTask(Task* parent); + ~CreateConferenceTask(); + /** + * Set up a create conference request + * @param confId The client-unique conference Id. + * @param participants A list of Novell DNs of the people taking part in the conference. + */ + void conference( const int confId, const QStringList &participants ); + bool take( Transfer * transfer ); + int clientConfId() const; + GroupWise::ConferenceGuid conferenceGUID() const; + +signals: + void created( const GroupWise::ConferenceGuid & guid ); +private: + int m_confId; // the conference id given us before making the request + ConferenceGuid m_guid; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/createcontactinstancetask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/createcontactinstancetask.cpp new file mode 100644 index 00000000..832b5900 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/createcontactinstancetask.cpp @@ -0,0 +1,97 @@ +/* + Kopete Groupwise Protocol + createcontactinstancetask.h - Request Task that creates an instance of a contact on the server side contact list + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ +#include "client.h" + +#include "createcontactinstancetask.h" + +CreateContactInstanceTask::CreateContactInstanceTask(Task* parent) : NeedFolderTask(parent) +{ + // make the client tell the client app (Kopete) when we receive a contact + connect( this, SIGNAL( gotContactAdded( const ContactItem & ) ), client(), SIGNAL( contactReceived( const ContactItem & ) ) ); +} + +CreateContactInstanceTask::~CreateContactInstanceTask() +{ +} + +void CreateContactInstanceTask::contactFromUserId( const QString & userId, const QString & displayName, const int parentFolder ) +{ + contact( new Field::SingleField( NM_A_SZ_USERID, 0, NMFIELD_TYPE_UTF8, userId ), displayName, parentFolder ); +} + +void CreateContactInstanceTask::contactFromUserIdAndFolder( const QString & userId, const QString & displayName, const int folderSequence, const QString & folderDisplayName ) +{ + // record the user details + m_userId = userId; + m_displayName = displayName; + // record the folder details + m_folderSequence = folderSequence; + m_folderDisplayName = folderDisplayName; +} + +void CreateContactInstanceTask::contactFromDN( const QString & dn, const QString & displayName, const int parentFolder ) +{ + contact( new Field::SingleField( NM_A_SZ_DN, 0, NMFIELD_TYPE_UTF8, dn ), displayName, parentFolder ); +} + +void CreateContactInstanceTask::contactFromDNAndFolder( const QString & dn, const QString & displayName, const int folderSequence, const QString & folderDisplayName ) +{ + // record the user details + m_dn = dn; + m_displayName = displayName; + // record the folder details + m_folderSequence = folderSequence; + m_folderDisplayName = folderDisplayName; +} + +void CreateContactInstanceTask::contact( Field::SingleField * id, const QString & displayName, const int parentFolder ) +{ + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_SZ_PARENT_ID, 0, NMFIELD_TYPE_UTF8, QString::number( parentFolder ) ) ); + // this is either a user Id or a DN + lst.append( id ); + if ( displayName.isEmpty() ) // fallback so that the contact is created + lst.append( new Field::SingleField( NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_TYPE_UTF8, m_dn ) ); + else + lst.append( new Field::SingleField( NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_TYPE_UTF8, displayName ) ); + createTransfer( "createcontact", lst ); +} + +void CreateContactInstanceTask::onGo() +{ + // are we creating a folder first or can we just proceed as normal? + if ( m_folderDisplayName.isEmpty() ) + RequestTask::onGo(); + else // create the folder, when the folder has been created, onFolderCreated gets called and creates the contact + createFolder(); +} + +void CreateContactInstanceTask::onFolderCreated() +{ + // now the folder exists, perform the requested type of contact instance creation + if ( m_userId.isEmpty() ) + contact( new Field::SingleField( NM_A_SZ_DN, 0, NMFIELD_TYPE_UTF8, m_dn ), m_displayName, m_folderId ); + else + contact( new Field::SingleField( NM_A_SZ_USERID, 0, NMFIELD_TYPE_UTF8, m_userId ), m_displayName, m_folderId ); + // send the transfer immediately + RequestTask::onGo(); +} + +#include "createcontactinstancetask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/createcontactinstancetask.h b/kopete/protocols/groupwise/libgroupwise/tasks/createcontactinstancetask.h new file mode 100644 index 00000000..d6be5933 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/createcontactinstancetask.h @@ -0,0 +1,54 @@ +/* + Kopete Groupwise Protocol + createcontactinstancetask.h - Request Task that creates an instance of a contact on the server side contact list + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef CreateContactInstanceTask_H +#define CreateContactInstanceTask_H + +#include "needfoldertask.h" + +/** +Creates a contact on the server. The response to this action is handled by its parent + +@author SUSE AG +*/ +class CreateContactInstanceTask : public NeedFolderTask +{ +Q_OBJECT +public: + CreateContactInstanceTask(Task* parent); + ~CreateContactInstanceTask(); + /** + * Sets up the request message. + */ + void contactFromUserId( const QString & userId, const QString & displayName, const int parentFolder ); + void contactFromDN( const QString & dn, const QString & displayName, const int parentFolder ); + void contactFromUserIdAndFolder( const QString & userId, const QString & displayName, const int folderSequence, const QString & folderDisplayName ); + void contactFromDNAndFolder( const QString & dn, const QString & displayName, const int folderSequence, const QString & folderDisplayName ); + void onGo(); +protected: + void contact( Field::SingleField * id, const QString & displayName, const int parentFolder ); + void onFolderCreated(); +private: + QString m_userId; + QString m_dn; + QString m_displayName; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/createcontacttask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/createcontacttask.cpp new file mode 100644 index 00000000..aac16042 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/createcontacttask.cpp @@ -0,0 +1,144 @@ +/* + Kopete Groupwise Protocol + createcontacttask.cpp - high level task responsible for creating both a contact and any folders it belongs to locally, on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "client.h" +#include "createfoldertask.h" +#include "createcontactinstancetask.h" + +#include "createcontacttask.h" + +CreateContactTask::CreateContactTask(Task* parent): Task(parent) +{ +} + +CreateContactTask::~CreateContactTask() +{ +} + +QString CreateContactTask::userId() +{ + return m_userId; +} + +QString CreateContactTask::dn() +{ + return m_dn; +} + +QString CreateContactTask::displayName() +{ + return m_displayName; +} + +bool CreateContactTask::take( Transfer * transfer ) +{ + Q_UNUSED( transfer ); + return false; +} + +void CreateContactTask::contactFromUserId( const QString & userId, const QString & displayName, const int firstSeqNo, const QValueList< FolderItem > folders, bool topLevel ) +{ + m_userId = userId; + m_displayName = displayName; + m_firstSequenceNumber = firstSeqNo; + m_folders = folders; + m_topLevel = topLevel; +} + +void CreateContactTask::onGo() +{ + client()->debug( "CreateContactTask::onGo() - Welcome to the Create Contact Task Show!"); + QValueList::ConstIterator it = m_folders.begin(); + const QValueList::ConstIterator end = m_folders.end(); + + // create contacts on the server + for ( ; it != end; ++it ) + { + client()->debug( QString( " - contact is in folder %1 with id %2" ).arg( (*it).name ).arg( (*it).id ) ); + CreateContactInstanceTask * ccit = new CreateContactInstanceTask( client()->rootTask() ); + // the add contact action may cause other contacts' sequence numbers to change + // CreateContactInstanceTask signals these changes, so we propagate the signal via the Client, to the GroupWiseAccount + // This updates our local versions of those contacts using the same mechanism by which they are updated at login. + connect( ccit, SIGNAL( gotContactAdded( const ContactItem & ) ), SLOT( slotContactAdded( const ContactItem & ) ) ); + connect( ccit, SIGNAL( finished() ), SLOT( slotCheckContactInstanceCreated() ) ); + if ( (*it).id == 0 ) // caller asserts that this isn't on the server... + { + ccit->contactFromDNAndFolder( m_userId, m_displayName, m_firstSequenceNumber++, ( *it ).name ); + } + else + ccit->contactFromDN( m_userId, m_displayName, (*it).id ); + + ccit->go( true ); + } + + if ( m_topLevel ) + { + client()->debug( " - contact is in top level folder " ); + CreateContactInstanceTask * ccit = new CreateContactInstanceTask( client()->rootTask() ); + connect( ccit, SIGNAL( gotContactAdded( const ContactItem & ) ), SLOT( slotContactAdded( const ContactItem & ) ) ); + connect( ccit, SIGNAL( finished() ), SLOT( slotCheckContactInstanceCreated() ) ); + ccit->contactFromDN( m_userId, m_displayName, 0 ); + ccit->go( true ); + } + client()->debug( "CreateContactTask::onGo() - DONE" ); +} + +void CreateContactTask::slotContactAdded( const ContactItem & addedContact ) +{ + client()->debug( "CreateContactTask::slotContactAdded()" ); + // as each contact instance has been added on the server, + // remove the folderitem it belongs in. + // once the list is empty, we have been successful + + if ( addedContact.displayName != m_displayName ) + { + client()->debug( " - addedContact is not the one we were trying to add, ignoring it ( Account will update it )" ); + return; + } + client()->debug( QString( "CreateContactTask::slotContactAdded() - Contact Instance %1 was created on the server, with objectId %2 in folder %3" ).arg + ( addedContact.displayName ).arg( addedContact.id ).arg( addedContact.parentId ) ); + + if ( m_dn.isEmpty() ) + m_dn = addedContact.dn; + + + if ( !m_folders.isEmpty() ) + m_folders.pop_back(); + + // clear the topLevel flag once the corresponding server side entry has been successfully created + if ( addedContact.parentId == 0 ) + m_topLevel = false; + + if ( m_folders.isEmpty() && !m_topLevel ) + { + client()->debug( "CreateContactTask::slotContactAdded() - All contacts were created on the server, we're finished!" ); + setSuccess(); + } +} +void CreateContactTask::slotCheckContactInstanceCreated() +{ + CreateContactInstanceTask * ccit = ( CreateContactInstanceTask * )sender(); + if ( !ccit->success() ) + { + setError( ccit->statusCode(), ccit->statusString() ); + } +} + +#include "createcontacttask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/createcontacttask.h b/kopete/protocols/groupwise/libgroupwise/tasks/createcontacttask.h new file mode 100644 index 00000000..a9e4ab06 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/createcontacttask.h @@ -0,0 +1,88 @@ +/* + Kopete Groupwise Protocol + createcontacttask.cpp - high level task responsible for creating both a contact and any folders it belongs to locally, on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef CREATECONTACTTASK_H +#define CREATECONTACTTASK_H + +#include + +#include "gwerror.h" + +#include "task.h" + +using namespace GroupWise; + +/** + Creates a contact on the server, as well as any folders that do not exist on the server, and add the contact to those folders. + This is a meta-task to suit Kopete. If you maintain your own copy of the server side contact list and follow the server's + contact semantics (contact instances rather than contacts in the contact list), you can just use CreateContactInstanceTask. + This task causes the @ref Client to emit folderReceived() and contactReceived() as the task proceeds. Kopete processes these + signals as usual, because it created the contact optimistically, before invoking this task. + + The finished() signal indicates the whole procedure has completed and the sender can be queried for success as usual +@author SUSE AG +*/ +class CreateContactTask : public Task +{ +Q_OBJECT +public: + CreateContactTask(Task* parent); + ~CreateContactTask(); + /** + * Get the userId of the contact just created + */ + QString userId(); + /** + * Get the DN of the contact just created + */ + QString dn(); + QString displayName(); + + /** + * Sets up the task. + * @param userId the user Id of the contact to create + * @param displayName the display name we should give to this contact + * @param firstSeqNo Used to create the folders - the first unused folder sequence number we know of + * @param folders A list of folders that the contact should belong to - any folders that do not exist on the server should have a objectId of 0, and will be created + * @param topLevel is the folder also in the top level folder? + */ + void contactFromUserId( const QString & userId, const QString & displayName, const int firstSeqNo, const QValueList< FolderItem > folders, bool topLevel ); + //void contactFromDN( const QString & dn, const QString & displayName, const int parentFolder ); + /** + * This task doesn't do any I/O itself, so this take prints an error and returns false; + */ + bool take( Transfer * ); + /** + * Starts off the whole process + */ + void onGo(); +protected slots: + void slotContactAdded( const ContactItem & ); + void slotCheckContactInstanceCreated(); +private: + int m_firstSequenceNumber; + QString m_userId; + QString m_dn; + QString m_displayName; + QValueList< FolderItem > m_folders; + bool m_topLevel; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/createfoldertask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/createfoldertask.cpp new file mode 100644 index 00000000..c7e9933b --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/createfoldertask.cpp @@ -0,0 +1,41 @@ +/* + Kopete Groupwise Protocol + createfoldertask.h - Request Task for creating a single folder on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "createfoldertask.h" + +CreateFolderTask::CreateFolderTask(Task* parent): ModifyContactListTask(parent) +{ +} + + +CreateFolderTask::~CreateFolderTask() +{ +} + +void CreateFolderTask::folder( const int parentId, const int sequence, const QString & displayName ) +{ + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_SZ_PARENT_ID, 0, NMFIELD_TYPE_UTF8, QString::number( parentId ) ) ); + lst.append( new Field::SingleField( NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_TYPE_UTF8, displayName ) ); + lst.append( new Field::SingleField( NM_A_SZ_SEQUENCE_NUMBER, 0, NMFIELD_TYPE_UTF8, QString::number( sequence ) ) ); + createTransfer( "createfolder", lst ); +} + +#include "createfoldertask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/createfoldertask.h b/kopete/protocols/groupwise/libgroupwise/tasks/createfoldertask.h new file mode 100644 index 00000000..f3c6ebb9 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/createfoldertask.h @@ -0,0 +1,40 @@ +/* + Kopete Groupwise Protocol + createfoldertask.h - Request Task for creating a single folder on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef CREATEFOLDERTASK_H +#define CREATEFOLDERTASK_H + +#include "modifycontactlisttask.h" + +/** +Creates a folder on the server + +@author SUSE AG +*/ +class CreateFolderTask : public ModifyContactListTask +{ +Q_OBJECT +public: + CreateFolderTask(Task* parent); + ~CreateFolderTask(); + void folder( const int parentId, const int sequence, const QString & displayName ); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/deleteitemtask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/deleteitemtask.cpp new file mode 100644 index 00000000..89480d10 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/deleteitemtask.cpp @@ -0,0 +1,46 @@ +/* + Kopete Groupwise Protocol + deleteitemtask.cpp - Delete a contact or folder on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "deleteitemtask.h" + +DeleteItemTask::DeleteItemTask(Task* parent): ModifyContactListTask(parent) +{ +} + + +DeleteItemTask::~DeleteItemTask() +{ +} + +void DeleteItemTask::item( const int parentFolder, const int objectId ) +{ + if ( objectId == 0 ) + { + setError( 1, "Can't delete the root folder" ); + return; + } + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_SZ_PARENT_ID, 0, NMFIELD_TYPE_UTF8, QString::number( parentFolder ) ) ); + // this is either a user Id or a DN + lst.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, QString::number( objectId ) ) ); + createTransfer( "deletecontact", lst ); +} + +#include "deleteitemtask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/deleteitemtask.h b/kopete/protocols/groupwise/libgroupwise/tasks/deleteitemtask.h new file mode 100644 index 00000000..f249c2f5 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/deleteitemtask.h @@ -0,0 +1,38 @@ +/* + Kopete Groupwise Protocol + deleteitemtask.h - Delete a contact or folder on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef DELETEITEMTASK_H +#define DELETEITEMTASK_H + +#include "modifycontactlisttask.h" + +/** +@author SUSE AG +*/ +class DeleteItemTask : public ModifyContactListTask +{ +Q_OBJECT +public: + DeleteItemTask(Task* parent); + ~DeleteItemTask(); + void item( const int parentFolder, const int objectId ); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/eventtask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/eventtask.cpp new file mode 100644 index 00000000..c6bd2d85 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/eventtask.cpp @@ -0,0 +1,48 @@ +/* + Kopete Groupwise Protocol + eventtask.cpp - Ancestor of all Event Handling tasks + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "gwfield.h" +#include "eventtask.h" + +EventTask::EventTask( Task * parent ) +: Task( parent ) +{ +} + +void EventTask::registerEvent( GroupWise::Event e ) +{ + m_eventCodes.append( e ); +} + +bool EventTask::forMe( Transfer * transfer, EventTransfer*& event ) const +{ + // see if we can down-cast transfer to an EventTransfer + /*EventTransfer * */ + event = dynamic_cast(transfer); + if ( event ) + { + // see if we are supposed to handle this kind of event + // consider speeding this up by having 1 handler per event + return ( m_eventCodes.find( event->eventType() ) != m_eventCodes.end() ); + } + return false; +} + +#include "eventtask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/eventtask.h b/kopete/protocols/groupwise/libgroupwise/tasks/eventtask.h new file mode 100644 index 00000000..50b84ac5 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/eventtask.h @@ -0,0 +1,43 @@ +/* + Kopete Groupwise Protocol + eventtask.h - Ancestor of all Event Handling tasks + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GW_EVENTTASK_H +#define GW_EVENTTASK_H + +#include + +#include "eventtransfer.h" +#include "task.h" + +class Transfer; + +class EventTask : public Task +{ +Q_OBJECT + public: + EventTask( Task *parent ); + protected: + bool forMe( Transfer * transfer, EventTransfer *& event ) const; + void registerEvent( GroupWise::Event e ); + private: + QValueList m_eventCodes; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/getchatsearchresultstask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/getchatsearchresultstask.cpp new file mode 100644 index 00000000..fe1d61f9 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/getchatsearchresultstask.cpp @@ -0,0 +1,122 @@ +/* + Kopete Groupwise Protocol + getchatsearchresultstask.cpp - Poll the server to see if it has processed our search yet. + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include + +#include "gwfield.h" +#include "response.h" + +#include "logintask.h" + +#include "getchatsearchresultstask.h" + +using namespace GroupWise; + +GetChatSearchResultsTask::GetChatSearchResultsTask(Task* parent): RequestTask(parent) +{ +} + + +GetChatSearchResultsTask::~GetChatSearchResultsTask() +{ +} + +void GetChatSearchResultsTask::poll( int queryHandle ) +{ + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_UD_OBJECT_ID, 0, NMFIELD_TYPE_UDWORD, queryHandle ) ); + lst.append( new Field::SingleField( NM_A_UD_QUERY_COUNT, 0, NMFIELD_TYPE_UDWORD, 10 ) ); + createTransfer( "getchatsearchresults", lst ); +} + +bool GetChatSearchResultsTask::take( Transfer * transfer ) +{ + if ( !forMe( transfer ) ) + return false; + Response * response = dynamic_cast( transfer ); + if ( !response ) + return false; + if ( response->resultCode() ) + { + setError( response->resultCode() ); + return true; + } + + // look for the status code + Field::FieldList responseFields = response->fields(); + Field::SingleField * sf = responseFields.findSingleField( NM_A_UW_STATUS ); + m_queryStatus = (SearchResultCode)sf->value().toInt(); + + Field::MultiField * resultsArray = responseFields.findMultiField( NM_A_FA_RESULTS ); + if ( !resultsArray ) + { + setError( Protocol ); + return true; + } + Field::FieldList matches = resultsArray->fields(); + const Field::FieldListIterator end = matches.end(); + for ( Field::FieldListIterator it = matches.find( NM_A_FA_CHAT ); + it != end; + it = matches.find( ++it, NM_A_FA_CHAT ) ) + { + Field::MultiField * mf = static_cast( *it ); + Field::FieldList chat = mf->fields(); + GroupWise::ChatroomSearchResult cd = extractChatDetails( chat ); + m_results.append( cd ); + } + + if ( m_queryStatus != DataRetrieved ) + setError( m_queryStatus ); + else + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << " we won!" << endl; + setSuccess( m_queryStatus ); + } + return true; +} + +QValueList< GroupWise::ChatroomSearchResult > GetChatSearchResultsTask::results() +{ + return m_results; +} + +int GetChatSearchResultsTask::queryStatus() +{ + return m_queryStatus; +} + +GroupWise::ChatroomSearchResult GetChatSearchResultsTask::extractChatDetails( Field::FieldList & fields ) +{ + ChatroomSearchResult csr; + csr.participants = 0; + // read the supplied fields, set metadata and status. + Field::SingleField * sf; + if ( ( sf = fields.findSingleField ( NM_A_DISPLAY_NAME ) ) ) + csr.name = sf->value().toString(); + if ( ( sf = fields.findSingleField ( NM_A_CHAT_OWNER_DN ) ) ) + csr.ownerDN = sf->value().toString().lower(); // HACK: lowercased DN + if ( ( sf = fields.findSingleField ( NM_A_UD_PARTICIPANTS ) ) ) + csr.participants = sf->value().toInt(); + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << csr.name << ", " << csr.ownerDN << ", " << csr.participants << endl; + return csr; +} + +#include "getchatsearchresultstask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/getchatsearchresultstask.h b/kopete/protocols/groupwise/libgroupwise/tasks/getchatsearchresultstask.h new file mode 100644 index 00000000..31db19ed --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/getchatsearchresultstask.h @@ -0,0 +1,52 @@ +/* + Kopete Groupwise Protocol + getchatsearchresultstask.h - Poll the server once to see if it has processed our chatroom search yet. + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef CHATSEARCHRESULTSTASK_H +#define CHATSEARCHRESULTSTASK_H + +#include + +#include "gwchatrooms.h" + +#include "requesttask.h" + +/** +Search results are polled on the server, using the search handle returned by the server with the original query. This is a single poll request, which if successful, will retrieve the results. Otherwise, it will set a status code, so the SearchChatTask can decide whether to poll again. + +@author SUSE Linux Products GmbH + */ +class GetChatSearchResultsTask : public RequestTask +{ + Q_OBJECT + public: + enum SearchResultCode { Completed=2, Cancelled=4, Error=5, GettingData=8, DataRetrieved=9 }; + GetChatSearchResultsTask(Task* parent); + ~GetChatSearchResultsTask(); + void poll( int queryHandle); + bool take( Transfer * transfer ); + int queryStatus(); + QValueList< GroupWise::ChatroomSearchResult > results(); + private: + GroupWise::ChatroomSearchResult extractChatDetails( Field::FieldList & fields ); + SearchResultCode m_queryStatus; + QValueList< GroupWise::ChatroomSearchResult > m_results; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/getdetailstask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/getdetailstask.cpp new file mode 100644 index 00000000..0b37efb4 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/getdetailstask.cpp @@ -0,0 +1,136 @@ +/* + Kopete Groupwise Protocol + getdetailstask.cpp - fetch a contact's details from the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "client.h" +#include "response.h" +#include "userdetailsmanager.h" + +#include "getdetailstask.h" + +using namespace GroupWise; + +GetDetailsTask::GetDetailsTask( Task * parent ) + : RequestTask( parent ) +{ +} + + +GetDetailsTask::~GetDetailsTask() +{ +} + +void GetDetailsTask::userDNs( const QStringList & userDNs ) +{ + Field::FieldList lst; + for ( QStringList::ConstIterator it = userDNs.begin(); it != userDNs.end(); ++it ) + { + lst.append( new Field::SingleField( NM_A_SZ_DN, 0, NMFIELD_TYPE_UTF8, *it ) ); + } + createTransfer( "getdetails", lst ); +} + +bool GetDetailsTask::take( Transfer * transfer ) +{ + if ( !forMe( transfer ) ) + return false; + Response * response = dynamic_cast( transfer ); + if ( !response ) + return false; + + Field::FieldList detailsFields = response->fields(); + // parse received details and signal like billio + Field::MultiField * container = 0; + Field::FieldListIterator end = detailsFields.end(); + for ( Field::FieldListIterator it = detailsFields.find( NM_A_FA_RESULTS ); + it != end; + it = detailsFields.find( ++it, NM_A_FA_RESULTS ) ) + { + container = static_cast( *it ); + ContactDetails cd = extractUserDetails( container ); + emit gotContactUserDetails( cd ); + } + + return true; +} + +ContactDetails GetDetailsTask::extractUserDetails(Field::MultiField * details ) +{ + ContactDetails cd; + cd.status = GroupWise::Invalid; + cd.archive = false; + Field::FieldList fields = details->fields(); + // TODO: not sure what this means, ask Mike + Field::SingleField * sf; + if ( ( sf = fields.findSingleField ( NM_A_SZ_AUTH_ATTRIBUTE ) ) ) + cd.authAttribute = sf->value().toString(); + if ( ( sf = fields.findSingleField ( NM_A_SZ_DN ) ) ) + cd.dn =sf->value().toString().lower(); // HACK: lowercased DN + if ( ( sf = fields.findSingleField ( "CN" ) ) ) + cd.cn = sf->value().toString(); + if ( ( sf = fields.findSingleField ( "Given Name" ) ) ) + cd.givenName = sf->value().toString(); + if ( ( sf = fields.findSingleField ( "Surname" ) ) ) + cd.surname = sf->value().toString(); + if ( ( sf = fields.findSingleField ( "nnmArchive" ) ) ) + cd.archive = ( sf->value().toInt() == 1 ); + if ( ( sf = fields.findSingleField ( "Full Name" ) ) ) + cd.fullName = sf->value().toString(); + if ( ( sf = fields.findSingleField ( NM_A_SZ_STATUS ) ) ) + cd.status = sf->value().toInt(); + if ( ( sf = fields.findSingleField ( NM_A_SZ_MESSAGE_BODY ) ) ) + cd.awayMessage = sf->value().toString(); + Field::MultiField * mf; + QMap< QString, QString > propMap; + if ( ( mf = fields.findMultiField ( NM_A_FA_INFO_DISPLAY_ARRAY ) ) ) + { + Field::FieldList fl = mf->fields(); + const Field::FieldListIterator end = fl.end(); + for ( Field::FieldListIterator it = fl.begin(); it != end; ++it ) + { + Field::SingleField * propField = dynamic_cast( *it ); + if ( propField ) { + QString propName = propField->tag(); + QString propValue = propField->value().toString(); + propMap.insert( propName, propValue ); + } else { + Field::MultiField * mf2; + if ( ( mf2 = dynamic_cast( *it ) ) ) { + Field::FieldList fl2 = mf2->fields(); + const Field::FieldListIterator end = fl2.end(); + for ( Field::FieldListIterator it2 = fl2.begin(); it2 != end; ++it2 ) + { + propField = dynamic_cast( *it2 ); + if ( propField ) { + QString propName = propField->tag(); + QString propValue = propField->value().toString(); + propMap.insert( propName, propValue ); + } + } + } + } + } + } + if ( !propMap.empty() ) + { + cd.properties = propMap; + } + return cd; +} +#include "getdetailstask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/getdetailstask.h b/kopete/protocols/groupwise/libgroupwise/tasks/getdetailstask.h new file mode 100644 index 00000000..d263f50b --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/getdetailstask.h @@ -0,0 +1,49 @@ +/* + Kopete Groupwise Protocol + getdetailstask.h - fetch a contact's details from the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GETDETAILSTASK_H +#define GETDETAILSTASK_H + +#include "gwerror.h" +#include "requesttask.h" + +/** +This task fetches the details for a set of user IDs from the server. Sometimes we get an event that only has a DN, and we need other details before showing the event to the user. + +@author SUSE AG +*/ +using namespace GroupWise; + +class GetDetailsTask : public RequestTask +{ +Q_OBJECT +public: + GetDetailsTask( Task * parent ); + ~GetDetailsTask(); + bool take( Transfer * transfer ); + void userDNs( const QStringList & userDNs ); +signals: + void gotContactUserDetails( const GroupWise::ContactDetails & ); +protected: + GroupWise::ContactDetails extractUserDetails( Field::MultiField * details ); + +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/getstatustask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/getstatustask.cpp new file mode 100644 index 00000000..dde055a6 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/getstatustask.cpp @@ -0,0 +1,72 @@ +/* + Kopete Groupwise Protocol + getstatustask.cpp - fetch a contact's details from the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "response.h" + +#include "getstatustask.h" + +GetStatusTask::GetStatusTask(Task* parent): RequestTask(parent) +{ +} + +GetStatusTask::~GetStatusTask() +{ +} + +void GetStatusTask::userDN( const QString & dn ) +{ + m_userDN = dn; + // set up Transfer + Field::FieldList lst; + // changed from USERID to DN as per Gaim/GWIM + lst.append( new Field::SingleField( NM_A_SZ_DN, 0, NMFIELD_TYPE_UTF8, m_userDN ) ); + createTransfer( "getstatus", lst ); +} + +bool GetStatusTask::take( Transfer * transfer ) +{ + if ( !forMe( transfer ) ) + return false; + Response * response = dynamic_cast( transfer ); + if ( !response ) + return false; + + Field::FieldList responseFields = response->fields(); + responseFields.dump( true ); + // parse received details and signal like billio + Field::SingleField * sf = 0; + Q_UINT16 status; + sf = responseFields.findSingleField( NM_A_SZ_STATUS ); + if ( sf ) + { + // As of Sept 2004 the server always responds with 2 (Available) here, even if the sender is not + // This must be because the sender is not on our contact list but has sent us a message. + // TODO: Check that the change to sending DNs above has fixed this problem. + status = sf->value().toInt(); + // unfortunately getstatus doesn't give us an away message so we pass QString::null here + emit gotStatus( m_userDN, status, QString::null ); + setSuccess(); + } + else + setError(); + return true; +} + +#include "getstatustask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/getstatustask.h b/kopete/protocols/groupwise/libgroupwise/tasks/getstatustask.h new file mode 100644 index 00000000..59422342 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/getstatustask.h @@ -0,0 +1,44 @@ +/* + Kopete Groupwise Protocol + getstatustask.h - fetch a contact's details from the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GETSTATUSTASK_H +#define GETSTATUSTASK_H + +#include "requesttask.h" + +/** + * Request the status for a specific contact (e.g. one who's not on our contact list) + * @author SUSE AG +*/ +class GetStatusTask : public RequestTask +{ +Q_OBJECT +public: + GetStatusTask(Task* parent); + ~GetStatusTask(); + void userDN( const QString & dn ); + bool take( Transfer * transfer ); +signals: + void gotStatus( const QString & contactId, Q_UINT16 status, const QString & statusText ); +private: + QString m_userDN; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/gwtasklogin.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/gwtasklogin.cpp new file mode 100644 index 00000000..e69de29b diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/gwtasklogin.h b/kopete/protocols/groupwise/libgroupwise/tasks/gwtasklogin.h new file mode 100644 index 00000000..e69de29b diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/joinchattask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/joinchattask.cpp new file mode 100644 index 00000000..4e9e4f57 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/joinchattask.cpp @@ -0,0 +1,131 @@ +/* + Kopete Groupwise Protocol + joinchattask.cpp - Join a Chat on the server, after having been invited. + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "gwerror.h" +#include "client.h" +#include "response.h" +#include "userdetailsmanager.h" + +#include "joinchattask.h" + +JoinChatTask::JoinChatTask(Task* parent): RequestTask(parent) +{ +} + +JoinChatTask::~JoinChatTask() +{ +} + +void JoinChatTask::join( const QString & displayName ) +{ + m_displayName = displayName; + Field::FieldList lst, tmp; + tmp.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, displayName ) ); + lst.append( new Field::MultiField( NM_A_FA_CONVERSATION, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, tmp ) ); + createTransfer( "joinchat", lst ); +} + +bool JoinChatTask::take( Transfer * transfer ) +{ + if ( forMe( transfer ) ) + { + client()->debug( "JoinChatTask::take()" ); + Response * response = dynamic_cast( transfer ); + Field::FieldList responseFields = response->fields(); + // if the request was successful + if ( response->resultCode() == GroupWise::None ) + { + // extract the list of participants and store them + Field::MultiField * participants = responseFields.findMultiField( NM_A_FA_CONTACT_LIST ); + if ( participants ) + { + Field::SingleField * contact = 0; + Field::FieldList contactList = participants->fields(); + const Field::FieldListIterator end = contactList.end(); + for ( Field::FieldListIterator it = contactList.find( NM_A_SZ_DN ); + it != end; + it = contactList.find( ++it, NM_A_SZ_DN ) ) + { + contact = static_cast( *it ); + if ( contact ) + { + // HACK: lowercased DN + QString dn = contact->value().toString().lower(); + m_participants.append( dn ); + // need to ask for details for these contacts + } + } + } + else + setError( GroupWise::Protocol ); + + // now, extract the list of pending invites and store them + Field::MultiField * invitees = responseFields.findMultiField( NM_A_FA_RESULTS ); + if ( invitees ) + { + Field::SingleField * contact = 0; + Field::FieldList contactList = invitees->fields(); + const Field::FieldListIterator end = contactList.end(); + for ( Field::FieldListIterator it = contactList.find( NM_A_SZ_DN ); + it != end; + it = contactList.find( ++it, NM_A_SZ_DN ) ) + { + contact = static_cast( *it ); + if ( contact ) + { + // HACK: lowercased DN + QString dn = contact->value().toString().lower(); + m_invitees.append( dn ); + // need to ask for details for these contacts + if ( !client()->userDetailsManager()->known( dn ) ) + ; // don't request details for chatrooms, there could be too many + } + } + } + else + setError( GroupWise::Protocol ); + + client()->debug( "JoinChatTask::finished()" ); + finished(); + } + else + setError( response->resultCode() ); + return true; + } + else + return false; +} + +QStringList JoinChatTask::participants() const +{ + return m_participants; +} + +QStringList JoinChatTask::invitees() const +{ + return m_invitees; +} + +QString JoinChatTask::displayName() const +{ + return m_displayName; +} + +#include "joinchattask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/joinchattask.h b/kopete/protocols/groupwise/libgroupwise/tasks/joinchattask.h new file mode 100644 index 00000000..a7cc4119 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/joinchattask.h @@ -0,0 +1,52 @@ +/* + Kopete Groupwise Protocol + joinchattask.h - Join a chatroom on the server, after having been invited. + + Copyright (c) 2004 SUSE Linux Products GmbH http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef JOINCHATTASK_H +#define JOINCHATTASK_H + +#include "requesttask.h" + +using namespace GroupWise; + +/** +Sends Join Conference messages when the user accepts an invitation + +@author SUSE Linux Products GmbH + */ + +class JoinChatTask : public RequestTask +{ + Q_OBJECT + public: + JoinChatTask(Task* parent); + ~JoinChatTask(); + void join( const QString & displayName ); + bool take( Transfer * transfer ); + QStringList participants() const; + QStringList invitees() const; + QString displayName() const; + private: + ConferenceGuid m_displayName; + QStringList m_participants; + QStringList m_invitees; + QStringList m_unknowns; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/joinconferencetask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/joinconferencetask.cpp new file mode 100644 index 00000000..c2cf0f02 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/joinconferencetask.cpp @@ -0,0 +1,175 @@ +/* + Kopete Groupwise Protocol + joinconferencetask.cpp - Join a conference on the server, after having been invited. + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "gwerror.h" +#include "client.h" +#include "response.h" +#include "userdetailsmanager.h" + +#include "joinconferencetask.h" + +JoinConferenceTask::JoinConferenceTask(Task* parent): RequestTask(parent) +{ +} + +JoinConferenceTask::~JoinConferenceTask() +{ +} + +void JoinConferenceTask::join( const GroupWise::ConferenceGuid & guid ) +{ + m_guid = guid; + Field::FieldList lst, tmp; + tmp.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, guid ) ); + lst.append( new Field::MultiField( NM_A_FA_CONVERSATION, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, tmp ) ); + createTransfer( "joinconf", lst ); +} + +bool JoinConferenceTask::take( Transfer * transfer ) +{ + if ( forMe( transfer ) ) + { + client()->debug( "JoinConferenceTask::take()" ); + Response * response = dynamic_cast( transfer ); + Field::FieldList responseFields = response->fields(); + // if the request was successful + if ( response->resultCode() == GroupWise::None ) + { + // extract the list of participants and store them + Field::MultiField * participants = responseFields.findMultiField( NM_A_FA_CONTACT_LIST ); + if ( participants ) + { + Field::SingleField * contact = 0; + Field::FieldList contactList = participants->fields(); + const Field::FieldListIterator end = contactList.end(); + for ( Field::FieldListIterator it = contactList.find( NM_A_SZ_DN ); + it != end; + it = contactList.find( ++it, NM_A_SZ_DN ) ) + { + contact = static_cast( *it ); + if ( contact ) + { + // HACK: lowercased DN + QString dn = contact->value().toString().lower(); + m_participants.append( dn ); + // need to ask for details for these contacts + if ( !client()->userDetailsManager()->known( dn ) ) + m_unknowns.append( dn ); + } + } + } + else + setError( GroupWise::Protocol ); + + // now, extract the list of pending invites and store them + Field::MultiField * invitees = responseFields.findMultiField( NM_A_FA_RESULTS ); + if ( invitees ) + { + Field::SingleField * contact = 0; + Field::FieldList contactList = invitees->fields(); + const Field::FieldListIterator end = contactList.end(); + for ( Field::FieldListIterator it = contactList.find( NM_A_SZ_DN ); + it != end; + it = contactList.find( ++it, NM_A_SZ_DN ) ) + { + contact = static_cast( *it ); + if ( contact ) + { + // HACK: lowercased DN + QString dn = contact->value().toString().lower(); + m_invitees.append( dn ); + // need to ask for details for these contacts + if ( !client()->userDetailsManager()->known( dn ) ) + m_unknowns.append( dn ); + } + } + } + else + setError( GroupWise::Protocol ); + + if ( m_unknowns.empty() ) // ready to chat + { + client()->debug( "JoinConferenceTask::finished()" ); + finished(); + } + else // need to get some more details first + { + client()->debug( "JoinConferenceTask::slotReceiveUserDetails(), requesting details" ); + connect( client()->userDetailsManager(), + SIGNAL( gotContactDetails( const GroupWise::ContactDetails & ) ), + SLOT( slotReceiveUserDetails( const GroupWise::ContactDetails & ) ) ); + client()->userDetailsManager()->requestDetails( m_unknowns ); + } + } + else + setError( response->resultCode() ); + return true; + } + else + return false; +} + +void JoinConferenceTask::slotReceiveUserDetails( const ContactDetails & details ) +{ + client()->debug( QString( "JoinConferenceTask::slotReceiveUserDetails() - got %1" ).arg( details.dn ) ); + QStringList::Iterator it = m_unknowns.begin(); + QStringList::Iterator end = m_unknowns.end(); + while( it != end ) + { + QString current = *it; + ++it; + client()->debug( QString( " - can we remove %1?" ).arg(current ) ); + if ( current == details.dn ) + { + client()->debug( " - it's gone!" ); + m_unknowns.remove( current ); + break; + } + } + client()->debug( QString( " - now %1 unknowns").arg( m_unknowns.count() ) ); + if ( m_unknowns.empty() ) + { + client()->debug( " - finished()" ); + finished(); + } +// would be better to count the number of received details and listen to the getdetails task's error signal. +// else +// { +// client()->debug( " - ERROR - we requested details for the list of chat participants/invitees, but the server did not send us all the details! - setting finished() anyway, so the chat can take place." ); +// finished(); +// } +} + +QStringList JoinConferenceTask::participants() const +{ + return m_participants; +} + +QStringList JoinConferenceTask::invitees() const +{ + return m_invitees; +} + +GroupWise::ConferenceGuid JoinConferenceTask::guid() const +{ + return m_guid; +} + +#include "joinconferencetask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/joinconferencetask.h b/kopete/protocols/groupwise/libgroupwise/tasks/joinconferencetask.h new file mode 100644 index 00000000..68316147 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/joinconferencetask.h @@ -0,0 +1,54 @@ +/* + Kopete Groupwise Protocol + joinconferencetask.h - Join a conference on the server, after having been invited. + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef JOINCONFERENCETASK_H +#define JOINCONFERENCETASK_H + +#include "requesttask.h" + +using namespace GroupWise; + +/** +Sends Join Conference messages when the user accepts an invitation + +@author SUSE AG +*/ + +class JoinConferenceTask : public RequestTask +{ +Q_OBJECT +public: + JoinConferenceTask(Task* parent); + ~JoinConferenceTask(); + void join( const ConferenceGuid & guid ); + bool take( Transfer * transfer ); + QStringList participants() const; + QStringList invitees() const; + ConferenceGuid guid() const; +public slots: + void slotReceiveUserDetails( const GroupWise::ContactDetails & details ); +private: + ConferenceGuid m_guid; + QStringList m_participants; + QStringList m_invitees; + QStringList m_unknowns; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/keepalivetask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/keepalivetask.cpp new file mode 100644 index 00000000..ac84ac2b --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/keepalivetask.cpp @@ -0,0 +1,42 @@ +/* + Kopete Groupwise Protocol + keepalivetask.cpp - Send keepalive pings to the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + (c) 2006 Novell, Inc. + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "client.h" +#include "request.h" +#include "requestfactory.h" +#include "keepalivetask.h" + +KeepAliveTask::KeepAliveTask(Task* parent): RequestTask(parent) +{ +} + + +KeepAliveTask::~KeepAliveTask() +{ +} + +void KeepAliveTask::setup() +{ + Field::FieldList lst; + createTransfer( "ping", lst ); +} + +#include "keepalivetask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/keepalivetask.h b/kopete/protocols/groupwise/libgroupwise/tasks/keepalivetask.h new file mode 100644 index 00000000..04f9a352 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/keepalivetask.h @@ -0,0 +1,38 @@ +/* + Kopete Groupwise Protocol + keepalivetask.h - Send keepalive pings to the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef KEEPALIVETASK_H +#define KEEPALIVETASK_H + +#include "requesttask.h" + +/** +@author Kopete Developers +*/ +class KeepAliveTask : public RequestTask +{ +Q_OBJECT +public: + KeepAliveTask(Task* parent); + ~KeepAliveTask(); + void setup(); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/leaveconferencetask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/leaveconferencetask.cpp new file mode 100644 index 00000000..d2d58b83 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/leaveconferencetask.cpp @@ -0,0 +1,40 @@ +/* + Kopete Groupwise Protocol + leaveconferencetask.cpp - Tell the server we are leaving a conference + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "leaveconferencetask.h" + +LeaveConferenceTask::LeaveConferenceTask(Task* parent): RequestTask(parent) +{ +} + + +LeaveConferenceTask::~LeaveConferenceTask() +{ +} + +void LeaveConferenceTask::leave( const GroupWise::ConferenceGuid & guid ) +{ + Field::FieldList lst, tmp; + tmp.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, guid ) ); + lst.append( new Field::MultiField( NM_A_FA_CONVERSATION, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, tmp ) ); + createTransfer( "leaveconf", lst ); +} + +#include "leaveconferencetask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/leaveconferencetask.h b/kopete/protocols/groupwise/libgroupwise/tasks/leaveconferencetask.h new file mode 100644 index 00000000..65ebe540 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/leaveconferencetask.h @@ -0,0 +1,40 @@ +/* + Kopete Groupwise Protocol + leaveconferencetask.h - Tell the server we are leaving a conference + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef LEAVECONFERENCETASK_H +#define LEAVECONFERENCETASK_H + +#include "requesttask.h" + +/** +Tells the server that you are leaving a conference (closed the chatwindow) + +@author SUSE AG +*/ +class LeaveConferenceTask : public RequestTask +{ +Q_OBJECT +public: + LeaveConferenceTask(Task* parent); + ~LeaveConferenceTask(); + void leave( const GroupWise::ConferenceGuid & guid ); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/logintask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/logintask.cpp new file mode 100644 index 00000000..1f679a6c --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/logintask.cpp @@ -0,0 +1,360 @@ +/* + Kopete Groupwise Protocol + logintask.cpp - Send our credentials to the server and process the contact list and privacy details that it returns. + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "client.h" +#include "response.h" +#include "privacymanager.h" +#include "userdetailsmanager.h" + +#include "logintask.h" + +LoginTask::LoginTask( Task * parent ) + : RequestTask( parent ) +{ +} + +LoginTask::~LoginTask() +{ +} + +void LoginTask::initialise() +{ + QString command = QString::fromLatin1("login:%1:%2").arg( client()->host() ).arg( client()->port() ); + + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_SZ_USERID, 0, NMFIELD_TYPE_UTF8, client()->userId() ) ); + lst.append( new Field::SingleField( NM_A_SZ_CREDENTIALS, 0, NMFIELD_TYPE_UTF8, client()->password() ) ); + lst.append( new Field::SingleField( NM_A_SZ_USER_AGENT, 0, NMFIELD_TYPE_UTF8, client()->userAgent() ) ); + lst.append( new Field::SingleField( NM_A_UD_BUILD, 0, NMFIELD_TYPE_UDWORD, client()->protocolVersion() ) ); + lst.append( new Field::SingleField( NM_A_IP_ADDRESS, 0, NMFIELD_TYPE_UTF8, client()->ipAddress() ) ); + createTransfer( command, lst ); +} + +bool LoginTask::take( Transfer * transfer ) +{ + if ( !forMe( transfer ) ) + return false; + Response * response = dynamic_cast( transfer ); + if ( !response ) + return false; + if ( response->resultCode() ) + { + setError( response->resultCode() ); + return true; + } + response->fields().dump( true ); + + // read in myself()'s metadata fields and emit signal + Field::FieldList loginResponseFields = response->fields(); + + ContactDetails cd = extractUserDetails( loginResponseFields ); + emit gotMyself( cd ); + + // read the privacy settings first, because this affects all contacts' apparent status + extractPrivacy( loginResponseFields ); + + extractCustomStatuses( loginResponseFields ); + + // CREATE CONTACT LIST + // locate contact list + Field::MultiField * contactList = loginResponseFields.findMultiField( NM_A_FA_CONTACT_LIST ); + if ( contactList ) + { + Field::FieldList contactListFields = contactList->fields(); + Field::MultiField * container; + // read folders + for ( Field::FieldListIterator it = contactListFields.find( NM_A_FA_FOLDER ); + it != contactListFields.end(); + it = contactListFields.find( ++it, NM_A_FA_FOLDER ) ) + { + container = static_cast( *it ); + extractFolder( container ); + } + + // read contacts + for ( Field::FieldListIterator it = contactListFields.find( NM_A_FA_CONTACT ); + it != contactListFields.end(); + it = contactListFields.find( ++it, NM_A_FA_CONTACT ) ) + { + container = static_cast( *it ); + extractContact( container ); + } + } + + extractKeepalivePeriod( loginResponseFields ); + + setSuccess(); + + return true; +} + +void LoginTask::extractFolder( Field::MultiField * folderContainer ) +{ + FolderItem folder; + Field::SingleField * current; + Field::FieldList fl = folderContainer->fields(); + // object id + current = fl.findSingleField( NM_A_SZ_OBJECT_ID ); + folder.id = current->value().toInt(); + // sequence number + current = fl.findSingleField( NM_A_SZ_SEQUENCE_NUMBER ); + folder.sequence = current->value().toInt(); + // name + current = fl.findSingleField( NM_A_SZ_DISPLAY_NAME ); + folder.name = current->value().toString(); + // parent + current = fl.findSingleField( NM_A_SZ_PARENT_ID ); + folder.parentId = current->value().toInt(); + + client()->debug( QString( "Got folder: %1, obj: %2, parent: %3, seq: %3." ).arg( folder.name ).arg( folder.id ).arg( folder.parentId ).arg( folder.sequence ) ); + // tell the world about it + emit gotFolder( folder ); +} + +void LoginTask::extractContact( Field::MultiField * contactContainer ) +{ + if ( contactContainer->tag() != NM_A_FA_CONTACT ) + return; + ContactItem contact; + Field::SingleField * current; + Field::FieldList fl = contactContainer->fields(); + // sequence number, object and parent IDs are a numeric values but are stored as strings... + current = fl.findSingleField( NM_A_SZ_OBJECT_ID ); + contact.id = current->value().toInt(); + current = fl.findSingleField( NM_A_SZ_PARENT_ID ); + contact.parentId = current->value().toInt(); + current = fl.findSingleField( NM_A_SZ_SEQUENCE_NUMBER ); + contact.sequence = current->value().toInt(); + current = fl.findSingleField( NM_A_SZ_DISPLAY_NAME ); + contact.displayName = current->value().toString(); + current = fl.findSingleField( NM_A_SZ_DN ); + contact.dn = current->value().toString().lower(); + emit gotContact( contact ); + Field::MultiField * details = fl.findMultiField( NM_A_FA_USER_DETAILS ); + if ( details ) // not all contact list contacts have these + { + Field::FieldList detailsFields = details->fields(); + ContactDetails cd = extractUserDetails( detailsFields ); + if ( cd.dn.isEmpty() ) + cd.dn = contact.dn; + // tell the UserDetailsManager that we have this contact's details + client()->userDetailsManager()->addDetails( cd ); + emit gotContactUserDetails( cd ); + } +} + +ContactDetails LoginTask::extractUserDetails( Field::FieldList & fields ) +{ + ContactDetails cd; + cd.status = GroupWise::Invalid; + cd.archive = false; + // read the supplied fields, set metadata and status. + Field::SingleField * sf; + if ( ( sf = fields.findSingleField ( NM_A_SZ_AUTH_ATTRIBUTE ) ) ) + cd.authAttribute = sf->value().toString(); + if ( ( sf = fields.findSingleField ( NM_A_SZ_DN ) ) ) + cd.dn = sf->value().toString().lower(); // HACK: lowercased DN + if ( ( sf = fields.findSingleField ( "CN" ) ) ) + cd.cn = sf->value().toString(); + if ( ( sf = fields.findSingleField ( "Given Name" ) ) ) + cd.givenName = sf->value().toString(); + if ( ( sf = fields.findSingleField ( "Surname" ) ) ) + cd.surname = sf->value().toString(); + if ( ( sf = fields.findSingleField ( "Full Name" ) ) ) + cd.fullName = sf->value().toString(); + if ( ( sf = fields.findSingleField ( "nnmArchive" ) ) ) + cd.archive = ( sf->value().toInt() == 1 ); + if ( ( sf = fields.findSingleField ( NM_A_SZ_STATUS ) ) ) + cd.status = sf->value().toInt(); + if ( ( sf = fields.findSingleField ( NM_A_SZ_MESSAGE_BODY ) ) ) + cd.awayMessage = sf->value().toString(); + Field::MultiField * mf; + QMap< QString, QString > propMap; + if ( ( mf = fields.findMultiField ( NM_A_FA_INFO_DISPLAY_ARRAY ) ) ) + { + Field::FieldList fl = mf->fields(); + const Field::FieldListIterator end = fl.end(); + for ( Field::FieldListIterator it = fl.begin(); it != end; ++it ) + { + Field::SingleField * propField = dynamic_cast( *it ); + if ( propField ) + { + QString propName = propField->tag(); + QString propValue = propField->value().toString(); + propMap.insert( propName, propValue ); + } + else + { + Field::MultiField * propList = dynamic_cast( *it ); + if ( propList ) + { + // Hello A Nagappan. GW gave us a multiple field where we previously got a single field + QString parentName = propList->tag(); + Field::FieldList propFields = propList->fields(); + const Field::FieldListIterator end = propFields.end(); + for ( Field::FieldListIterator it = propFields.begin(); it != end; ++it ) + { + propField = dynamic_cast( *it ); + if ( propField /*&& propField->tag() == parentName */) + { + QString propValue = propField->value().toString(); + QString contents = propMap[ propField->tag() ]; + if ( !contents.isEmpty() ) + contents.append( ", " ); + contents.append( propField->value().toString()); + propMap.insert( propField->tag(), contents ); + } + } + } + } + } + } + if ( !propMap.empty() ) + { + cd.properties = propMap; + } + return cd; +} + +void LoginTask::extractPrivacy( Field::FieldList & fields ) +{ + bool privacyLocked = false; + bool defaultDeny = false; + QStringList allowList; + QStringList denyList; + // read blocking + // may be a single field or may be an array + Field::FieldListIterator it = fields.find( NM_A_LOCKED_ATTR_LIST ); + if ( it != fields.end() ) + { + if ( Field::SingleField * sf = dynamic_cast( *it ) ) + { + if ( sf->value().toString().find( NM_A_BLOCKING ) ) + privacyLocked = true; + } + else if ( Field::MultiField * mf = dynamic_cast( *it ) ) + { + Field::FieldList fl = mf->fields(); + for ( Field::FieldListIterator it = fl.begin(); it != fl.end(); ++it ) + { + if ( Field::SingleField * sf = dynamic_cast( *it ) ) + { + if ( sf->tag() == NM_A_BLOCKING ) + { + privacyLocked = true; + break; + } + } + } + } + } + + // read default privacy policy + Field::SingleField * sf = fields.findSingleField( NM_A_BLOCKING ); + if ( sf ) + defaultDeny = ( sf->value().toInt() != 0 ); + + + // read deny list + denyList = readPrivacyItems( NM_A_BLOCKING_DENY_LIST, fields ); + // read allow list + allowList = readPrivacyItems( NM_A_BLOCKING_ALLOW_LIST, fields ); + emit gotPrivacySettings( privacyLocked, defaultDeny, allowList, denyList ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "locked is " << privacyLocked << ", default is " << defaultDeny << "\nallow list is: " << allowList << "\ndeny list is: " << denyList << endl; +} + +QStringList LoginTask::readPrivacyItems( const QCString & tag, Field::FieldList & fields ) +{ + QStringList items; + + Field::FieldListIterator it = fields.find( tag ); + if ( it != fields.end() ) + { + if ( Field::SingleField * sf = dynamic_cast( *it ) ) + { + items.append( sf->value().toString().lower() ); + } + else if ( Field::MultiField * mf = dynamic_cast( *it ) ) + { + Field::FieldList fl = mf->fields(); + for ( Field::FieldListIterator it = fl.begin(); it != fl.end(); ++it ) + { + if ( Field::SingleField * sf = dynamic_cast( *it ) ) + { + items.append( sf->value().toString().lower() ); + } + } + } + } + return items; +} + +void LoginTask::extractCustomStatuses( Field::FieldList & fields ) +{ + Field::FieldListIterator it = fields.find( NM_A_FA_CUSTOM_STATUSES ); + if ( it != fields.end() ) + { + if ( Field::MultiField * mf = dynamic_cast( *it ) ) + { + Field::FieldList fl = mf->fields(); + for ( Field::FieldListIterator custStatIt = fl.begin(); custStatIt != fl.end(); ++custStatIt ) + { + Field::MultiField * mf2 = dynamic_cast( *custStatIt ); + if ( mf2 && ( mf2->tag() == NM_A_FA_STATUS ) ) + { + GroupWise::CustomStatus custom; + Field::FieldList fl2 = mf2->fields(); + for ( Field::FieldListIterator custContentIt = fl2.begin(); custContentIt != fl2.end(); ++custContentIt ) + { + if ( Field::SingleField * sf3 = dynamic_cast( *custContentIt ) ) + { + if ( sf3->tag() == NM_A_SZ_TYPE ) + custom.status = (GroupWise::Status)sf3->value().toInt(); + else if ( sf3->tag() == NM_A_SZ_DISPLAY_NAME ) + custom.name = sf3->value().toString(); + else if ( sf3->tag() == NM_A_SZ_MESSAGE_BODY ) + custom.autoReply = sf3->value().toString(); + } + } + emit gotCustomStatus( custom ); + } + } + } + } +} + +void LoginTask::extractKeepalivePeriod( Field::FieldList & fields ) +{ + Field::FieldListIterator it = fields.find( NM_A_UD_KEEPALIVE ); + if ( it != fields.end() ) + { + if ( Field::SingleField * sf = dynamic_cast( *it ) ) + { + bool ok; + int period = sf->value().toInt( &ok ); + if ( ok ) + { + emit gotKeepalivePeriod( period ); + } + } + } +} + +#include "logintask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/logintask.h b/kopete/protocols/groupwise/libgroupwise/tasks/logintask.h new file mode 100644 index 00000000..0b2acdfd --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/logintask.h @@ -0,0 +1,64 @@ +/* + Kopete Groupwise Protocol + logintask.h - Send our credentials to the server and process the contact list and privacy details that it returns. + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef LOGINTASK_H +#define LOGINTASK_H + +#include "requesttask.h" + +using namespace GroupWise; + +/** +@author Kopete Developers +*/ +class LoginTask : public RequestTask +{ +Q_OBJECT +public: + LoginTask( Task * parent ); + ~LoginTask(); + /** + * Get the login fields ready to go + */ + void initialise(); + /** + * Only accepts the contactlist that comes back from the server, + * processes it and notifies the client of the contactlist + */ + bool take( Transfer * transfer ); +protected: + void extractFolder( Field::MultiField * folderContainer ); + void extractContact( Field::MultiField * contactContainer ); + ContactDetails extractUserDetails( Field::FieldList & fields ); + void extractPrivacy( Field::FieldList & fields ); + QStringList readPrivacyItems( const QCString & tag, Field::FieldList & fields ); + void extractCustomStatuses( Field::FieldList & fields ); + void extractKeepalivePeriod( Field::FieldList & fields ); +signals: + void gotMyself( const GroupWise::ContactDetails & ); + void gotFolder( const FolderItem & ); + void gotContact( const ContactItem & ); + void gotContactUserDetails( const GroupWise::ContactDetails & ); + void gotPrivacySettings( bool locked, bool defaultDeny, const QStringList & allowList, const QStringList & denyList ); + void gotCustomStatus( const GroupWise::CustomStatus & ); + void gotKeepalivePeriod( int ); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/modifycontactlisttask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/modifycontactlisttask.cpp new file mode 100644 index 00000000..10233a18 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/modifycontactlisttask.cpp @@ -0,0 +1,139 @@ +/* + Kopete Groupwise Protocol + modifycontactlisttask.cpp - Ancestor of all tasks that change the server side contact list. + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "client.h" +#include "response.h" +#include "gwerror.h" +#include "modifycontactlisttask.h" + +ModifyContactListTask::ModifyContactListTask(Task* parent): RequestTask(parent) +{ +} + +ModifyContactListTask::~ModifyContactListTask() +{ +} + +bool ModifyContactListTask::take( Transfer * transfer ) +{ + if ( !forMe( transfer ) ) + return false; + Response * response = dynamic_cast( transfer ); + if ( !response ) + return false; + client()->debug( "ModifyContactListTask::take()" ); + + // scan the contact list received + // emit each add and delete as a signal + Field::FieldList fl = response->fields(); + fl.dump( true ); + Field::FieldListIterator it = fl.begin(); + Field::FieldListIterator end = fl.end(); + Field::MultiField * current = fl.findMultiField( NM_A_FA_RESULTS ); + if ( current ) + fl = current->fields(); + current = fl.findMultiField( NM_A_FA_CONTACT_LIST ); + if ( current ) + { + Field::FieldList contactList = current->fields(); + Field::FieldListIterator cursor = contactList.begin(); + const Field::FieldListIterator end = contactList.end(); + while ( cursor != end ) + { + Field::MultiField * mf = dynamic_cast< Field::MultiField * >( *cursor ); + if ( mf->tag() == NM_A_FA_CONTACT ) + { + // contact change + processContactChange( mf ); + } + else if ( mf->tag() == NM_A_FA_FOLDER ) + { + // folder change + processFolderChange( mf ); + } + ++cursor; + } + } + // TODO: call virtual here to read any fields after the contact list... + if ( response->resultCode() == GroupWise::None ) + setSuccess(); + else + setError( response->resultCode() ); + return true; +} + +void ModifyContactListTask::processContactChange( Field::MultiField * container ) +{ + if ( !( container->method() == NMFIELD_METHOD_ADD + || container->method() == NMFIELD_METHOD_DELETE ) ) + return; + + client()->debug( "ModifyContactListTask::processContactChange()" ); + Field::SingleField * current; + Field::FieldList fl = container->fields(); + ContactItem contact; + current = fl.findSingleField( NM_A_SZ_OBJECT_ID ); + contact.id = current->value().toInt(); + current = fl.findSingleField( NM_A_SZ_PARENT_ID ); + contact.parentId = current->value().toInt(); + current = fl.findSingleField( NM_A_SZ_SEQUENCE_NUMBER ); + contact.sequence = current->value().toInt(); + current = fl.findSingleField( NM_A_SZ_DISPLAY_NAME ); + contact.displayName = current->value().toString(); + current = fl.findSingleField( NM_A_SZ_DN ); + contact.dn = current->value().toString(); + + if ( container->method() == NMFIELD_METHOD_ADD ) + emit gotContactAdded( contact ); + else if ( container->method() == NMFIELD_METHOD_DELETE ) + emit gotContactDeleted( contact ); +} + +void ModifyContactListTask::processFolderChange( Field::MultiField * container ) +{ + if ( !( container->method() == NMFIELD_METHOD_ADD + || container->method() == NMFIELD_METHOD_DELETE ) ) + return; + + client()->debug( "ModifyContactListTask::processFolderChange()" ); + FolderItem folder; + Field::SingleField * current; + Field::FieldList fl = container->fields(); + // object id + current = fl.findSingleField( NM_A_SZ_OBJECT_ID ); + folder.id = current->value().toInt(); + // sequence number + current = fl.findSingleField( NM_A_SZ_SEQUENCE_NUMBER ); + folder.sequence = current->value().toInt(); + // name + current = fl.findSingleField( NM_A_SZ_DISPLAY_NAME ); + folder.name = current->value().toString(); + // parent + current = fl.findSingleField( NM_A_SZ_PARENT_ID ); + folder.parentId = current->value().toInt(); + if ( container->method() == NMFIELD_METHOD_ADD ) + emit gotFolderAdded( folder ); + else if ( container->method() == NMFIELD_METHOD_DELETE ) + emit gotFolderDeleted( folder ); + +} + + +#include "modifycontactlisttask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/modifycontactlisttask.h b/kopete/protocols/groupwise/libgroupwise/tasks/modifycontactlisttask.h new file mode 100644 index 00000000..2f5a4939 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/modifycontactlisttask.h @@ -0,0 +1,51 @@ +/* + Kopete Groupwise Protocol + modifycontactlisttask.h - Ancestor of all tasks that change the server side contact list. + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef MODIFYCONTACTLISTTASK_H +#define MODIFYCONTACTLISTTASK_H + +#include "requesttask.h" + +/** +This is the parent of all tasks that manipulate the contact list. The server responds to each one in the same way, and this task contains a take() to process this response. + +@author SUSE AG +*/ + +using namespace GroupWise; + +class ModifyContactListTask : public RequestTask +{ +Q_OBJECT +public: + ModifyContactListTask(Task* parent); + ~ModifyContactListTask(); + bool take( Transfer * transfer ); +signals: + void gotFolderAdded( const FolderItem &); + void gotFolderDeleted( const FolderItem & ); + void gotContactAdded( const ContactItem & ); + void gotContactDeleted( const ContactItem & ); +private: + void processFolderChange( Field::MultiField * container ); + void processContactChange( Field::MultiField * container ); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/movecontacttask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/movecontacttask.cpp new file mode 100644 index 00000000..713315ee --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/movecontacttask.cpp @@ -0,0 +1,83 @@ +/* + Kopete Groupwise Protocol + movecontacttask.cpp - Move a contact between folders on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "client.h" + +#include "movecontacttask.h" + +MoveContactTask::MoveContactTask(Task* parent): NeedFolderTask(parent) +{ + // make the client tell the client app (Kopete) when we receive a contact + connect( this, SIGNAL( gotContactAdded( const ContactItem & ) ), client(), SIGNAL( contactReceived( const ContactItem & ) ) ); +} + + +MoveContactTask::~MoveContactTask() +{ +} + +void MoveContactTask::moveContact( const ContactItem & contact, const int newParent ) +{ + Field::FieldList lst; + // TODO: - write a contact_item_to_fields method and factor duplicate code like this out + Field::FieldList contactFields; + contactFields.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, contact.id ) ); + contactFields.append( new Field::SingleField( NM_A_SZ_PARENT_ID, 0, NMFIELD_TYPE_UTF8, contact.parentId ) ); + contactFields.append( new Field::SingleField( NM_A_SZ_SEQUENCE_NUMBER, 0, NMFIELD_TYPE_UTF8, contact.sequence ) ); + if ( !contact.dn.isNull() ) + contactFields.append( new Field::SingleField( NM_A_SZ_DN, 0, NMFIELD_TYPE_UTF8, contact.dn ) ); + if ( !contact.displayName.isNull() ) + contactFields.append( new Field::SingleField( NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_TYPE_UTF8, contact.displayName ) ); + Field::FieldList contactList; + contactList.append( + new Field::MultiField( NM_A_FA_CONTACT, NMFIELD_METHOD_DELETE, 0, NMFIELD_TYPE_ARRAY, contactFields ) ); + + lst.append( new Field::MultiField( NM_A_FA_CONTACT_LIST, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, contactList ) ); + + lst.append( new Field::SingleField( NM_A_SZ_SEQUENCE_NUMBER, 0, NMFIELD_TYPE_UTF8, "-1" ) ); + lst.append( new Field::SingleField( NM_A_SZ_PARENT_ID, 0, NMFIELD_TYPE_UTF8, QString::number( newParent ) ) ); + createTransfer( "movecontact", lst ); +} + +void MoveContactTask::moveContactToNewFolder( const ContactItem & contact, const int newSequenceNumber, const QString & folderDisplayName ) +{ + client()->debug("MoveContactTask::moveContactToNewFolder()" ); + m_folderSequence = newSequenceNumber; + m_folderDisplayName = folderDisplayName; + m_contactToMove = contact; + +} + +void MoveContactTask::onGo() +{ + // are we creating a folder first or can we just proceed as normal? + if ( m_folderDisplayName.isEmpty() ) + RequestTask::onGo(); + else // create the folder, when the folder has been created, onFolderCreated gets called and creates the contact + createFolder(); +} + +void MoveContactTask::onFolderCreated() +{ + client()->debug("MoveContactTask::onFolderCreated()" ); + moveContact( m_contactToMove, m_folderId ); + RequestTask::onGo(); +} +#include "movecontacttask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/movecontacttask.h b/kopete/protocols/groupwise/libgroupwise/tasks/movecontacttask.h new file mode 100644 index 00000000..f423981a --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/movecontacttask.h @@ -0,0 +1,49 @@ +/* + Kopete Groupwise Protocol + movecontacttask.h - Move a contact between folders on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef MOVECONTACTTASK_H +#define MOVECONTACTTASK_H + +#include "needfoldertask.h" + +/** +Moves a contact between folders on the server + +@author SUSE AG +*/ +class MoveContactTask : public NeedFolderTask +{ +Q_OBJECT +public: + MoveContactTask(Task* parent); + ~MoveContactTask(); + void moveContact( const ContactItem & contact, const int newParent ); + void moveContactToNewFolder( const ContactItem & contact, const int newSequenceNumber, const QString & folderDisplayName ); + void onGo(); +protected: + void onFolderCreated(); +private: + int m_targetFolder; + QString m_dn; + QString m_displayName; + ContactItem m_contactToMove; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/needfoldertask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/needfoldertask.cpp new file mode 100644 index 00000000..810326ee --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/needfoldertask.cpp @@ -0,0 +1,58 @@ +// +// C++ Implementation: %{MODULE} +// +// Description: +// +// +// Author: %{AUTHOR} <%{EMAIL}>, (C) %{YEAR} +// +// Copyright: See COPYING file that comes with this distribution +// +// +#include "client.h" +#include "tasks/createcontactinstancetask.h" +#include "tasks/createfoldertask.h" + +#include "needfoldertask.h" + +NeedFolderTask::NeedFolderTask(Task* parent): ModifyContactListTask(parent) +{ +} + +NeedFolderTask::~NeedFolderTask() +{ +} + +void NeedFolderTask::createFolder() +{ + CreateFolderTask * cct = new CreateFolderTask( client()->rootTask() ); + cct->folder( 0, m_folderSequence, m_folderDisplayName ); + connect( cct, SIGNAL( gotFolderAdded( const FolderItem & ) ), client(), SIGNAL( folderReceived( const FolderItem & ) ) ); + connect( cct, SIGNAL( gotFolderAdded( const FolderItem & ) ), SLOT( slotFolderAdded( const FolderItem & ) ) ); + connect( cct, SIGNAL( finished() ), SLOT( slotFolderTaskFinished() ) ); + cct->go( true ); +} + +void NeedFolderTask::slotFolderAdded( const FolderItem & addedFolder ) +{ + // if this is the folder we were trying to create + if ( m_folderDisplayName == addedFolder.name ) + { + client()->debug( QString( "NeedFolderTask::slotFolderAdded() - Folder %1 was created on the server, now has objectId %2" ).arg( addedFolder.name ).arg( addedFolder.id ) ); + m_folderId = addedFolder.id; + } +} + +void NeedFolderTask::slotFolderTaskFinished() +{ + CreateFolderTask *cct = ( CreateFolderTask* )sender(); + if ( cct->success() ) + { + // call our child class's action to be performed + onFolderCreated(); + } + else + setError( 1, "Folder creation failed" ); +} + +#include "needfoldertask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/needfoldertask.h b/kopete/protocols/groupwise/libgroupwise/tasks/needfoldertask.h new file mode 100644 index 00000000..8d6278df --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/needfoldertask.h @@ -0,0 +1,39 @@ +// +// C++ Interface: %{MODULE} +// +// Description: +// +// +// Author: %{AUTHOR} <%{EMAIL}>, (C) %{YEAR} +// +// Copyright: See COPYING file that comes with this distribution +// +// +#ifndef NEEDFOLDERTASK_H +#define NEEDFOLDERTASK_H + +#include "modifycontactlisttask.h" + +/** +This Task is the ancestor of Tasks that may need to create a folder on the server before they can carry out their own operation. + +@author Kopete Developers +*/ +class NeedFolderTask : public ModifyContactListTask +{ +Q_OBJECT +public: + NeedFolderTask(Task* parent); + ~NeedFolderTask(); + void createFolder(); + virtual void onFolderCreated() = 0; +protected slots: + void slotFolderAdded( const FolderItem & ); + void slotFolderTaskFinished(); +protected: + int m_folderSequence; + int m_folderId; + QString m_folderDisplayName; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/pollsearchresultstask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/pollsearchresultstask.cpp new file mode 100644 index 00000000..772a0888 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/pollsearchresultstask.cpp @@ -0,0 +1,185 @@ +/* + Kopete Groupwise Protocol + pollsearchresultstask.cpp - Poll the server to see if it has processed our search yet. + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "gwfield.h" +#include "response.h" + +#include "logintask.h" + +#include "pollsearchresultstask.h" + +using namespace GroupWise; + +PollSearchResultsTask::PollSearchResultsTask(Task* parent): RequestTask(parent) +{ +} + + +PollSearchResultsTask::~PollSearchResultsTask() +{ +} + +void PollSearchResultsTask::poll( const QString & queryHandle ) +{ + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, queryHandle ) ); + createTransfer( "getresults", lst ); +} + +bool PollSearchResultsTask::take( Transfer * transfer ) +{ + if ( !forMe( transfer ) ) + return false; + Response * response = dynamic_cast( transfer ); + if ( !response ) + return false; + if ( response->resultCode() ) + { + setError( response->resultCode() ); + return true; + } + + // look for the status code + Field::FieldList responseFields = response->fields(); + Field::SingleField * sf = responseFields.findSingleField( NM_A_SZ_STATUS ); + m_queryStatus = sf->value().toInt(); + + Field::MultiField * resultsArray = responseFields.findMultiField( NM_A_FA_RESULTS ); + if ( !resultsArray ) + { + setError( Protocol ); + return true; + } + Field::FieldList matches = resultsArray->fields(); + const Field::FieldListIterator end = matches.end(); + for ( Field::FieldListIterator it = matches.find( NM_A_FA_CONTACT ); + it != end; + it = matches.find( ++it, NM_A_FA_CONTACT ) ) + { + Field::MultiField * mf = static_cast( *it ); + Field::FieldList contact = mf->fields(); + GroupWise::ContactDetails cd = extractUserDetails( contact ); + m_results.append( cd ); + } + + // first field: NM_A_SZ_STATUS contains + #define SEARCH_PENDING 0 + #define SEARCH_INPROGRESS 1 + #define SEARCH_COMPLETED 2 + #define SEARCH_TIMEOUT 3 + #define SEARCH_CANCELLED 4 + #define SEARCH_ERROR 5 + // set a status code if needed + // followed by NM_A_FA_RESULTS, looks like a getdetails + // add an accessor to get at the results list of ContactItems, probably + + if ( m_queryStatus != 2 ) + setError( m_queryStatus ); + else + setSuccess( m_queryStatus ); + return true; +} + +QValueList< GroupWise::ContactDetails > PollSearchResultsTask::results() +{ + return m_results; +} + +int PollSearchResultsTask::queryStatus() +{ + return m_queryStatus; +} + +GroupWise::ContactDetails PollSearchResultsTask::extractUserDetails( Field::FieldList & fields ) +{ + ContactDetails cd; + cd.status = GroupWise::Invalid; + cd.archive = false; + // read the supplied fields, set metadata and status. + Field::SingleField * sf; + if ( ( sf = fields.findSingleField ( NM_A_SZ_AUTH_ATTRIBUTE ) ) ) + cd.authAttribute = sf->value().toString(); + if ( ( sf = fields.findSingleField ( NM_A_SZ_DN ) ) ) + cd.dn =sf->value().toString().lower(); // HACK: lowercased DN + if ( ( sf = fields.findSingleField ( "CN" ) ) ) + cd.cn = sf->value().toString(); + if ( ( sf = fields.findSingleField ( "Given Name" ) ) ) + cd.givenName = sf->value().toString(); + if ( ( sf = fields.findSingleField ( "Surname" ) ) ) + cd.surname = sf->value().toString(); + if ( ( sf = fields.findSingleField ( "Full Name" ) ) ) + cd.fullName = sf->value().toString(); + if ( ( sf = fields.findSingleField ( "nnmArchive" ) ) ) + cd.archive = ( sf->value().toInt() == 1 ); + if ( ( sf = fields.findSingleField ( NM_A_SZ_STATUS ) ) ) + cd.status = sf->value().toInt(); + if ( ( sf = fields.findSingleField ( NM_A_SZ_MESSAGE_BODY ) ) ) + cd.awayMessage = sf->value().toString(); + Field::MultiField * mf; + QMap< QString, QString > propMap; + if ( ( mf = fields.findMultiField ( NM_A_FA_INFO_DISPLAY_ARRAY ) ) ) + { + Field::FieldList fl = mf->fields(); + const Field::FieldListIterator end = fl.end(); + for ( Field::FieldListIterator it = fl.begin(); it != end; ++it ) + { + // assumes each property only present once + // check in logintask.cpp and if it's a multi field, + // get the value of this instance, check if it's already in the property map and append if found. + Field::SingleField * propField = dynamic_cast( *it ); + if ( propField ) + { + QString propName = propField->tag(); + QString propValue = propField->value().toString(); + propMap.insert( propName, propValue ); + } + else + { + Field::MultiField * propList = dynamic_cast( *it ); + if ( propList ) + { + QString parentName = propList->tag(); + Field::FieldList propFields = propList->fields(); + const Field::FieldListIterator end = propFields.end(); + for ( Field::FieldListIterator it = propFields.begin(); it != end; ++it ) + { + propField = dynamic_cast( *it ); + if ( propField ) + { + QString propValue = propField->value().toString(); + QString contents = propMap[ propField->tag() ]; + if ( !contents.isEmpty() ) + contents.append( ", " ); + contents.append( propField->value().toString()); + propMap.insert( propField->tag(), contents ); + } + } + } + } + } + } + if ( !propMap.empty() ) + { + cd.properties = propMap; + } + return cd; +} + +#include "pollsearchresultstask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/pollsearchresultstask.h b/kopete/protocols/groupwise/libgroupwise/tasks/pollsearchresultstask.h new file mode 100644 index 00000000..11f810c0 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/pollsearchresultstask.h @@ -0,0 +1,52 @@ +/* + Kopete Groupwise Protocol + pollsearchresultstask.h - Poll the server once to see if it has processed our search yet. + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef POLLSEARCHRESULTSTASK_H +#define POLLSEARCHRESULTSTASK_H + +#include + +#include "gwerror.h" + +#include "requesttask.h" + +/** +Search results are polled on the server, using the search handle supplied by the client with the original query. This is a single poll request, which if successful, will retrieve the results. Otherwise, it will set a status code, so the ContactSearchTask can decide whether to poll again. + +@author SUSE AG +*/ +class PollSearchResultsTask : public RequestTask +{ +Q_OBJECT +public: + enum SearchResultCode { Pending=0, InProgess=1, Completed=2, TimeOut=3, Cancelled=4, Error=5 }; + PollSearchResultsTask(Task* parent); + ~PollSearchResultsTask(); + void poll( const QString & queryHandle); + bool take( Transfer * transfer ); + int queryStatus(); + QValueList< GroupWise::ContactDetails > results(); +GroupWise::ContactDetails extractUserDetails( Field::FieldList & fields ); +private: + int m_queryStatus; + QValueList< GroupWise::ContactDetails > m_results; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/privacyitemtask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/privacyitemtask.cpp new file mode 100644 index 00000000..003a6d60 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/privacyitemtask.cpp @@ -0,0 +1,82 @@ +/* + Kopete Groupwise Protocol + privacyitemtask.cpp - Add an entry to the server side deny or allow lists + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "privacyitemtask.h" + +PrivacyItemTask::PrivacyItemTask( Task* parent) : RequestTask( parent ) +{ +} + +PrivacyItemTask::~PrivacyItemTask() +{ +} + +QString PrivacyItemTask::dn() const +{ + return m_dn; +} + +bool PrivacyItemTask::defaultDeny() const +{ + return m_default; +} + +void PrivacyItemTask::allow( const QString & dn ) +{ + m_dn = dn; + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_SZ_BLOCKING_ALLOW_ITEM, NMFIELD_METHOD_ADD, 0, NMFIELD_TYPE_UTF8, dn ) ); + createTransfer( "createblock", lst ); +} + +void PrivacyItemTask::deny( const QString & dn ) +{ + m_dn = dn; + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_SZ_BLOCKING_DENY_ITEM, NMFIELD_METHOD_ADD, 0, NMFIELD_TYPE_UTF8, dn ) ); + createTransfer( "createblock", lst ); +} + +void PrivacyItemTask::removeAllow( const QString & dn ) +{ + m_dn = dn; + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_BLOCKING_ALLOW_LIST, NMFIELD_METHOD_DELETE, 0, NMFIELD_TYPE_UTF8, dn ) ); + createTransfer( "updateblocks", lst ); + +} + +void PrivacyItemTask::removeDeny( const QString & dn ) +{ + m_dn = dn; + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_BLOCKING_DENY_LIST, NMFIELD_METHOD_DELETE, 0, NMFIELD_TYPE_UTF8, dn ) ); + createTransfer( "updateblocks", lst ); +} + +void PrivacyItemTask::defaultPolicy( bool defaultDeny ) +{ + m_default = defaultDeny; + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_BLOCKING, NMFIELD_METHOD_UPDATE, 0, NMFIELD_TYPE_UTF8, ( defaultDeny ? "1" :"0" ) ) ); + createTransfer( "updateblocks", lst ); +} + +#include "privacyitemtask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/privacyitemtask.h b/kopete/protocols/groupwise/libgroupwise/tasks/privacyitemtask.h new file mode 100644 index 00000000..809cb7a4 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/privacyitemtask.h @@ -0,0 +1,50 @@ +/* + Kopete Groupwise Protocol + privacyitemtask.h - Add an entry to the server side deny or allow lists + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef PRIVACYITEMTASK_H +#define PRIVACYITEMTASK_H + +#include "requesttask.h" + +/** +Adds a contact to the server side allow or deny lists + +@author SUSE AG +*/ +class PrivacyItemTask : public RequestTask +{ +Q_OBJECT +public: + PrivacyItemTask( Task* parent); + ~PrivacyItemTask(); + void allow( const QString & dn ); + void deny( const QString & dn ); + void removeAllow( const QString & dn ); + void removeDeny( const QString & dn ); + void defaultPolicy( bool defaultDeny ); + QString dn() const; + bool defaultDeny() const; + // void contacts( const QStringList & contacts ); +private: + bool m_default; + QString m_dn; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/rejectinvitetask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/rejectinvitetask.cpp new file mode 100644 index 00000000..2b252ff5 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/rejectinvitetask.cpp @@ -0,0 +1,39 @@ +/* + Kopete Groupwise Protocol + rejectinvitetask.cpp - Decline an invitation to chat + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "rejectinvitetask.h" + +RejectInviteTask::RejectInviteTask(Task* parent): RequestTask(parent) +{ +} + +RejectInviteTask::~RejectInviteTask() +{ +} + +void RejectInviteTask::reject( const GroupWise::ConferenceGuid & guid ) +{ + Field::FieldList lst, tmp; + tmp.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, guid ) ); + lst.append( new Field::MultiField( NM_A_FA_CONVERSATION, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, tmp ) ); + createTransfer( "rejectconf", lst ); +} + +#include "rejectinvitetask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/rejectinvitetask.h b/kopete/protocols/groupwise/libgroupwise/tasks/rejectinvitetask.h new file mode 100644 index 00000000..b82f4e77 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/rejectinvitetask.h @@ -0,0 +1,41 @@ +/* + Kopete Groupwise Protocol + rejectinvitetask.h - Decline an invitation to chat + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef REJECTINVITETASK_H +#define REJECTINVITETASK_H + +#include "requesttask.h" + +/** +Used to reject an invitation to join a conference + +@author SUSE AG +*/ +class RejectInviteTask : public RequestTask +{ +Q_OBJECT +public: + RejectInviteTask(Task* parent); + ~RejectInviteTask(); + void reject( const GroupWise::ConferenceGuid & guid ); + +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/requesttask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/requesttask.cpp new file mode 100644 index 00000000..3788bb6e --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/requesttask.cpp @@ -0,0 +1,76 @@ +/* + Kopete Groupwise Protocol + requesttask.cpp - Ancestor of all tasks that carry out a user request + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "gwfield.h" +#include "client.h" +#include "request.h" +#include "response.h" +#include "requestfactory.h" + +#include "requesttask.h" + +RequestTask::RequestTask( Task * parent ) +: Task( parent ) +{ +} + +bool RequestTask::forMe( Transfer * transfer ) const +{ + // see if we can down-cast transfer to a Response + Response * theResponse = dynamic_cast(transfer); + return (theResponse && theResponse->transactionId() == m_transactionId ); +} + +void RequestTask::createTransfer( const QString & command, const Field::FieldList & fields ) +{ + Request * request = client()->requestFactory()->request( command ); + m_transactionId = request->transactionId(); + request->setFields( fields ); + Task::setTransfer( request ); +} + +void RequestTask::onGo() +{ + if ( transfer() ) + { + client()->debug( QString( "%1::onGo() - sending %2 fields" ).arg( className() ).arg( static_cast( transfer() )->command() ) ); + send( static_cast( transfer() ) ); + } + else + client()->debug( "RequestTask::onGo() - called prematurely, no transfer set." ); +} + +bool RequestTask::take( Transfer * transfer ) +{ + if ( forMe( transfer ) ) + { + client()->debug( "RequestTask::take() - Default take() Accepting transaction ack, taking no further action" ); + Response * response = dynamic_cast( transfer ); + if ( response->resultCode() == GroupWise::None ) + setSuccess(); + else + setError( response->resultCode() ); + return true; + } + else + return false; +} + +#include "requesttask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/requesttask.h b/kopete/protocols/groupwise/libgroupwise/tasks/requesttask.h new file mode 100644 index 00000000..30ee57ed --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/requesttask.h @@ -0,0 +1,42 @@ +/* + Kopete Groupwise Protocol + requesttask.h - Ancestor of all tasks that carry out a user request + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GW_REQUESTTASK_H +#define GW_REQUESTTASK_H + +#include "task.h" + +class Transfer; + +class RequestTask : public Task +{ +Q_OBJECT + public: + RequestTask( Task *parent ); + bool take( Transfer * transfer ); + virtual void onGo(); + protected: + bool forMe( Transfer * transfer ) const; + void createTransfer( const QString & command, const Field::FieldList & fields ); + private: + int m_transactionId; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/searchchattask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/searchchattask.cpp new file mode 100644 index 00000000..4ee35549 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/searchchattask.cpp @@ -0,0 +1,127 @@ +/* + Kopete Groupwise Protocol + searchchattask.cpp - high level search for users on the server - spawns PollSearchResultsTasks + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include + +#include "client.h" +#include "gwerror.h" +#include "gwfield.h" +#include "response.h" + +#include "getchatsearchresultstask.h" + +#include "searchchattask.h" + + +// the delay we allow the server to initially do the search +#define GW_POLL_INITIAL_DELAY 1000 +// the maximum number of times to poll the server +#define GW_POLL_MAXIMUM 5 +// the frequency between subsequent polls +#define GW_POLL_FREQUENCY_MS 8000 + +using namespace GroupWise; + +SearchChatTask::SearchChatTask(Task* parent): RequestTask(parent), m_polls( 0 ) +{ +} + + +SearchChatTask::~SearchChatTask() +{ +} + +void SearchChatTask::search( SearchType type ) +{ + Field::FieldList lst; + // object Id identifies the search for later reference + lst.append( new Field::SingleField( NM_A_B_ONLY_MODIFIED, 0, NMFIELD_TYPE_BOOL, ( type == FetchAll ? 0 : 1 ) ) ); + createTransfer( "chatsearch", lst ); +} + +bool SearchChatTask::take( Transfer * transfer ) +{ + if ( !forMe( transfer ) ) + return false; + Response * response = dynamic_cast( transfer ); + if ( !response ) + return false; + if ( response->resultCode() ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "got return code in response << " << response->resultCode() << endl; + setError( response->resultCode() ); + return true; + } + Field::FieldList responseFields = response->fields(); + Field::SingleField * sf = responseFields.findSingleField( NM_A_UD_OBJECT_ID ); + m_objectId = sf->value().toInt(); + + // now start the results poll timer + QTimer::singleShot( GW_POLL_INITIAL_DELAY, this, SLOT( slotPollForResults() ) ); + return true; +} + +void SearchChatTask::slotPollForResults() +{ + //create a PollSearchResultsTask + GetChatSearchResultsTask * gcsrt = new GetChatSearchResultsTask( client()->rootTask() ); + gcsrt->poll( m_objectId ); + connect( gcsrt, SIGNAL( finished() ), SLOT( slotGotPollResults() ) ); + gcsrt->go( true ); +} + +void SearchChatTask::slotGotPollResults() +{ + GetChatSearchResultsTask * gcsrt = (GetChatSearchResultsTask *)sender(); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "status code is " << gcsrt->queryStatus() << endl; + m_polls++; + switch ( gcsrt->queryStatus() ) + { + case GetChatSearchResultsTask::GettingData: + if ( m_polls < GW_POLL_MAXIMUM ) // restart timer + QTimer::singleShot( GW_POLL_FREQUENCY_MS, this, SLOT( slotPollForResults() ) ); + else + setSuccess( gcsrt->statusCode() ); + break; + case GetChatSearchResultsTask::DataRetrieved: + // got some results, there may be more. + m_results += gcsrt->results(); + QTimer::singleShot( 0, this, SLOT( slotPollForResults() ) ); + break; + case GetChatSearchResultsTask::Completed: + m_results += gcsrt->results(); + setSuccess(); + break; + case GetChatSearchResultsTask::Cancelled: + setError(gcsrt->statusCode() ); + break; + case GetChatSearchResultsTask::Error: + setError( gcsrt->statusCode() ); + break; + } +} + +QValueList< GroupWise::ChatroomSearchResult > SearchChatTask::results() +{ + return m_results; +} + +#include "searchchattask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/searchchattask.h b/kopete/protocols/groupwise/libgroupwise/tasks/searchchattask.h new file mode 100644 index 00000000..2f24e075 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/searchchattask.h @@ -0,0 +1,66 @@ +/* + Kopete Groupwise Protocol + searchchattask.h - search for chatrooms on the server - spawns PollSearchResultsTasks + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef SEARCHCHATTASK_H +#define SEARCHCHATTASK_H + +#include "gwerror.h" + +#include "requesttask.h" + +class QTimer; + +/** +This Task searches for chatrooms on the server + +@author SUSE Linux Products GmbH + */ +class SearchChatTask : public RequestTask +{ + Q_OBJECT + public: + enum SearchType { FetchAll=0, SinceLastSearch }; + + SearchChatTask(Task* parent); + + ~SearchChatTask(); + /** + * Create the search query + */ + void search( SearchType type ); + /** + * If the query was accepted, start a timer to poll for results using PollSearchResultsTask + */ + virtual bool take( Transfer * transfer ); + /** + * Access the results of the search + */ + QValueList< GroupWise::ChatroomSearchResult > results(); + protected slots: + void slotPollForResults(); + void slotGotPollResults(); + private: + QTimer * m_resultsPollTimer; + QValueList< GroupWise::ChatroomSearchResult > m_results; + int m_polls; + int m_objectId; // used to identify our query to the server, so we can poll for its results +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/searchusertask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/searchusertask.cpp new file mode 100644 index 00000000..cd199ad8 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/searchusertask.cpp @@ -0,0 +1,137 @@ +/* + Kopete Groupwise Protocol + searchusertask.cpp - high level search for users on the server - spawns PollSearchResultsTasks + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include +#include + +#include "client.h" +#include "gwerror.h" +#include "gwfield.h" +#include "response.h" + +#include "pollsearchresultstask.h" + +#include "searchusertask.h" + +// the delay we allow the server to initially do the search +#define GW_POLL_INITIAL_DELAY 1000 +// the maximum number of times to poll the server +#define GW_POLL_MAXIMUM 5 +// the frequency between subsequent polls +#define GW_POLL_FREQUENCY_MS 8000 + +using namespace GroupWise; + +SearchUserTask::SearchUserTask(Task* parent): RequestTask(parent), m_polls( 0 ) +{ +} + + +SearchUserTask::~SearchUserTask() +{ +} + +void SearchUserTask::search( const QValueList & query ) +{ + m_queryHandle = QString::number( QDateTime::currentDateTime().toTime_t () ); + Field::FieldList lst; + if ( query.isEmpty() ) + { + setError( 1, "no query terms" ); + return; + } + // object Id identifies the search for later reference + lst.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, m_queryHandle ) ); + QValueList::ConstIterator it = query.begin(); + const QValueList::ConstIterator end = query.end(); + for ( ; it != end; ++it ) + { + Field::SingleField * fld = new Field::SingleField( (*it).field.ascii(), (*it).operation, 0, NMFIELD_TYPE_UTF8, (*it).argument ); + lst.append( fld ); + } + //lst.append( new Field::SingleField( "Given Name", 0, NMFIELD_TYPE_UTF8, [ NMFIELD_METHOD_EQUAL | NMFIELD_METHOD_MATCHBEGIN | NMFIELD_METHOD_MATCHEND | NMFIELD_METHOD_SEARCH ], searchTerm ); + // Or "Surname", NM_A_SZ_USERID, NM_A_SZ_TITLE, NM_A_SZ_DEPARTMENT in other fields + + createTransfer( "createsearch", lst ); +} + +bool SearchUserTask::take( Transfer * transfer ) +{ + if ( !forMe( transfer ) ) + return false; + Response * response = dynamic_cast( transfer ); + if ( !response ) + return false; + if ( response->resultCode() ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "got return code in response << " << response->resultCode() << endl; + setError( response->resultCode() ); + return true; + } + // now start the results poll timer + QTimer::singleShot( GW_POLL_INITIAL_DELAY, this, SLOT( slotPollForResults() ) ); + return true; +} + +void SearchUserTask::slotPollForResults() +{ + //create a PollSearchResultsTask + PollSearchResultsTask * psrt = new PollSearchResultsTask( client()->rootTask() ); + psrt->poll( m_queryHandle ); + connect( psrt, SIGNAL( finished() ), SLOT( slotGotPollResults() ) ); + psrt->go( true ); +} + +void SearchUserTask::slotGotPollResults() +{ + PollSearchResultsTask * psrt = (PollSearchResultsTask *)sender(); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "status code is " << psrt->queryStatus() << endl; + m_polls++; + switch ( psrt->queryStatus() ) + { + case PollSearchResultsTask::Pending: + case PollSearchResultsTask::InProgess: + if ( m_polls < GW_POLL_MAXIMUM ) // restart timer + QTimer::singleShot( GW_POLL_FREQUENCY_MS, this, SLOT( slotPollForResults() ) ); + else + setSuccess( psrt->statusCode() ); + break; + case PollSearchResultsTask::Completed: + m_results = psrt->results(); + setSuccess(); + break; + case PollSearchResultsTask::Cancelled: + setError(psrt->statusCode() ); + break; + case PollSearchResultsTask::Error: + setError( psrt->statusCode() ); + break; + case PollSearchResultsTask::TimeOut: + setError( psrt->statusCode() ); + break; + } +} + +QValueList< GroupWise::ContactDetails > SearchUserTask::results() +{ + return m_results; +} + +#include "searchusertask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/searchusertask.h b/kopete/protocols/groupwise/libgroupwise/tasks/searchusertask.h new file mode 100644 index 00000000..28c09b02 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/searchusertask.h @@ -0,0 +1,63 @@ +/* + Kopete Groupwise Protocol + searchusertask.h - high level search for users on the server - spawns PollSearchResultsTasks + + Copyright (c) 2005 SUSE Linux Products GmbH http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef SEARCHUSERTASK_H +#define SEARCHUSERTASK_H + +#include "requesttask.h" + +class QTimer; + +/** +This Task performs user searching on the server + +@author SUSE AG +*/ +class SearchUserTask : public RequestTask +{ +Q_OBJECT +public: + SearchUserTask(Task* parent); + + ~SearchUserTask(); + /** + * Create the search query + * @param query a list of search terms + */ + void search( const QValueList & query); + /** + * If the query was accepted, start a timer to poll for results using PollSearchResultsTask + */ + virtual bool take( Transfer * transfer ); + /** + * Access the results of the search + */ + QValueList< GroupWise::ContactDetails > results(); +protected slots: + void slotPollForResults(); + void slotGotPollResults(); +private: + QString m_queryHandle; // used to identify our query to the server, so we can poll for its results + QTimer * m_resultsPollTimer; + QValueList< GroupWise::ContactDetails > m_results; + int m_polls; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/sendinvitetask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/sendinvitetask.cpp new file mode 100644 index 00000000..b3a9614f --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/sendinvitetask.cpp @@ -0,0 +1,42 @@ +/* + Kopete Groupwise Protocol + sendinvitetask.cpp - invites someone to join a conference + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "sendinvitetask.h" + +SendInviteTask::SendInviteTask(Task* parent): RequestTask(parent) +{ +} + +SendInviteTask::~SendInviteTask() +{ +} + +void SendInviteTask::invite( const GroupWise::ConferenceGuid & guid, const QStringList & invitees, const GroupWise::OutgoingMessage & msg) +{ + Field::FieldList lst, tmp; + tmp.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, guid ) ); + lst.append( new Field::MultiField( NM_A_FA_CONVERSATION, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, tmp ) ); + QValueListConstIterator end = invitees.end(); + for ( QValueListConstIterator it = invitees.begin(); it != end; ++it ) + lst.append( new Field::SingleField( NM_A_SZ_DN, 0, NMFIELD_TYPE_DN, *it ) ); + if ( !msg.message.isEmpty() ) + lst.append( new Field::SingleField( NM_A_SZ_MESSAGE_BODY, 0, NMFIELD_TYPE_UTF8, msg.message ) ); + createTransfer( "sendinvite", lst ); +} diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/sendinvitetask.h b/kopete/protocols/groupwise/libgroupwise/tasks/sendinvitetask.h new file mode 100644 index 00000000..c8cf1d9b --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/sendinvitetask.h @@ -0,0 +1,43 @@ +/* + Kopete Groupwise Protocol + sendinvitetask.h - invites someone to join a conference + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef SENDINVITETASK_H +#define SENDINVITETASK_H + +#include "gwerror.h" + +#include "requesttask.h" + +/** +This sends an invitation to a conference + +@author SUSE AG +*/ +class SendInviteTask : public RequestTask +{ +public: + SendInviteTask(Task* parent); + ~SendInviteTask(); + void invite( const GroupWise::ConferenceGuid & guid, const QStringList & invitees, const GroupWise::OutgoingMessage & msg ); +private: + QString m_confId; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/sendmessagetask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/sendmessagetask.cpp new file mode 100644 index 00000000..290b9d9b --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/sendmessagetask.cpp @@ -0,0 +1,51 @@ +/* + Kopete Groupwise Protocol + sendmessagetask.cpp - sends a message to a conference + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "sendmessagetask.h" + +SendMessageTask::SendMessageTask(Task* parent): RequestTask(parent) +{ +} + + +SendMessageTask::~SendMessageTask() +{ +} + +void SendMessageTask::message( const QStringList & recipientDNList, const OutgoingMessage & msg ) +{ + // Assumes the conference is instantiated, unlike Gaim's nm_send_message + Field::FieldList lst, tmp, msgBodies; + // list containing GUID + tmp.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, msg.guid ) ); + lst.append( new Field::MultiField( NM_A_FA_CONVERSATION, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, tmp ) ); + msgBodies.append( new Field::SingleField( NM_A_SZ_MESSAGE_BODY, 0, NMFIELD_TYPE_UTF8, msg.rtfMessage ) ); + // message body type indicator / separator? + msgBodies.append( new Field::SingleField( NM_A_UD_MESSAGE_TYPE, 0, NMFIELD_TYPE_UDWORD, 0 ) ); + // message body plaintext + msgBodies.append( new Field::SingleField( NM_A_SZ_MESSAGE_TEXT, 0, NMFIELD_TYPE_UTF8, msg.message ) ); + // list containing message bodies + lst.append( new Field::MultiField( NM_A_FA_MESSAGE, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, msgBodies ) ); + // series of participants (may be empty ) + QValueListConstIterator end = recipientDNList.end(); + for ( QValueListConstIterator it = recipientDNList.begin(); it != end; ++it ) + lst.append( new Field::SingleField( NM_A_SZ_DN, 0, NMFIELD_TYPE_DN, *it ) ); + createTransfer( "sendmessage", lst ); +} diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/sendmessagetask.h b/kopete/protocols/groupwise/libgroupwise/tasks/sendmessagetask.h new file mode 100644 index 00000000..f45e491f --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/sendmessagetask.h @@ -0,0 +1,41 @@ +/* + Kopete Groupwise Protocol + sendmessagetask.h - sends a message to a conference + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef SENDMESSAGETASK_H +#define SENDMESSAGETASK_H + +#include "client.h" +#include "requesttask.h" + +/** +Sends messages to a particular conference on the server + +@author SUSE AG +*/ +class SendMessageTask : public RequestTask +{ +public: + SendMessageTask(Task* parent); + ~SendMessageTask(); + + void message( const QStringList & recipientDNList, const OutgoingMessage & msg ); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/setstatustask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/setstatustask.cpp new file mode 100644 index 00000000..0744ff8a --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/setstatustask.cpp @@ -0,0 +1,69 @@ +/* + Kopete Groupwise Protocol + setstatustask.cpp - Sets our status on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "setstatustask.h" + +using namespace GroupWise; + +SetStatusTask::SetStatusTask(Task* parent): RequestTask(parent) +{ +} + +SetStatusTask::~SetStatusTask() +{ +} + +void SetStatusTask::status( Status newStatus, const QString &awayMessage, const QString &autoReply ) +{ + if ( newStatus > GroupWise::Invalid ) + { + setError( 1, "Invalid Status" ); + return; + } + + m_status = newStatus; + m_awayMessage = awayMessage; + m_autoReply = autoReply; + + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_SZ_STATUS, 0, NMFIELD_TYPE_UTF8, QString::number( newStatus ) ) ); + if ( !awayMessage.isNull() ) + lst.append( new Field::SingleField( NM_A_SZ_STATUS_TEXT, 0, NMFIELD_TYPE_UTF8, awayMessage ) ); + if ( !autoReply.isNull() ) + lst.append( new Field::SingleField( NM_A_SZ_MESSAGE_BODY, 0, NMFIELD_TYPE_UTF8, autoReply ) ); + createTransfer( "setstatus", lst ); +} + +Status SetStatusTask::requestedStatus() const +{ + return m_status; +} + +QString SetStatusTask::awayMessage() const +{ + return m_awayMessage; +} + +QString SetStatusTask::autoReply() const +{ + return m_autoReply; +} + +#include "setstatustask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/setstatustask.h b/kopete/protocols/groupwise/libgroupwise/tasks/setstatustask.h new file mode 100644 index 00000000..2d3c53d7 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/setstatustask.h @@ -0,0 +1,46 @@ +/* + Kopete Groupwise Protocol + setstatustask.h - Sets our status on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef SETSTATUSTASK_H +#define SETSTATUSTASK_H + +#include "gwerror.h" +#include "requesttask.h" + +/** +@author Kopete Developers +*/ +class SetStatusTask : public RequestTask +{ +Q_OBJECT +public: + SetStatusTask(Task* parent); + ~SetStatusTask(); + void status( GroupWise::Status newStatus, const QString &awayMessage, const QString &autoReply ); + GroupWise::Status requestedStatus() const; + QString awayMessage() const; + QString autoReply() const; +private: + GroupWise::Status m_status; + QString m_awayMessage; + QString m_autoReply; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/statustask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/statustask.cpp new file mode 100644 index 00000000..8f8eccd4 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/statustask.cpp @@ -0,0 +1,47 @@ +/* + Kopete Groupwise Protocol + statustask.cpp - Event handling task responsible for status change events + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "client.h" + +#include "statustask.h" + +StatusTask::StatusTask(Task* parent): EventTask(parent) +{ + registerEvent( GroupWise::StatusChange ); +} + +StatusTask::~StatusTask() +{ +} + +bool StatusTask::take( Transfer * transfer ) +{ + EventTransfer * event; + if ( forMe( transfer, event ) ) + { + client()->debug( "Got a status change!" ); + client()->debug( QString( "%1 changed status to %2, message: %3" ).arg( event->source() ).arg( event->status() ).arg( event->statusText() ) ); + emit gotStatus( event->source().lower(), event->status(), event->statusText() ); + return true; + } + else + return false; +} +#include "statustask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/statustask.h b/kopete/protocols/groupwise/libgroupwise/tasks/statustask.h new file mode 100644 index 00000000..8e4994ff --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/statustask.h @@ -0,0 +1,40 @@ +/* + Kopete Groupwise Protocol + statustask.h - Event handling task responsible for status change events + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef STATUSTASK_H +#define STATUSTASK_H + +#include "eventtask.h" + +/** +@author Kopete Developers +*/ +class StatusTask : public EventTask +{ +Q_OBJECT +public: + StatusTask(Task* parent); + ~StatusTask(); + bool take( Transfer * transfer ); +signals: + void gotStatus( const QString & contactId, Q_UINT16 status, const QString & statusText ); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/tests/Makefile.am b/kopete/protocols/groupwise/libgroupwise/tasks/tests/Makefile.am new file mode 100644 index 00000000..6a10925b --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/tests/Makefile.am @@ -0,0 +1,7 @@ +INCLUDES = -I$(top_srcdir)/protocols/groupwise/libgroupwise/qca/src -I$(srcdir)/../../libgroupwise/ -I$(top_srcdir)/kopete/protocols/groupwise/libgroupwise/qca/src $(all_includes) +METASOURCES = AUTO +noinst_PROGRAMS = task_take_test + +task_take_test_LDADD = -lqt-mt ../../libgwtest.la + +task_take_test_SOURCES = task_take_test.cpp diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/tests/task_take_test.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/tests/task_take_test.cpp new file mode 100644 index 00000000..140e851f --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/tests/task_take_test.cpp @@ -0,0 +1,18 @@ +// +// C++ Implementation: task_take_test +// +// Description: +// +// +// Author: Kopete Developers , (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// + +//#include "requesttask.h" + +int main() +{ + // balls, root task requires client, will test in situ instead +} diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/typingtask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/typingtask.cpp new file mode 100644 index 00000000..b835c525 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/typingtask.cpp @@ -0,0 +1,44 @@ +/* + Kopete Groupwise Protocol + typingtask.cpp - sends typing notifications to the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +//#include "eventtransfer.h" + +#include "typingtask.h" + +TypingTask::TypingTask(Task* parent): RequestTask(parent) +{ +} + + +TypingTask::~TypingTask() +{ +} + +void TypingTask::typing( const GroupWise::ConferenceGuid & conferenceGuid, const bool typing ) +{ + Field::FieldList typingNotification, outgoingList; + typingNotification.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, conferenceGuid ) ); + typingNotification.append( new Field::SingleField( NM_A_SZ_TYPE, 0, NMFIELD_TYPE_UTF8, + QString::number( typing ? GroupWise::UserTyping : GroupWise::UserNotTyping ) ) ); + outgoingList.append( new Field::MultiField( NM_A_FA_CONVERSATION, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, typingNotification ) ); + createTransfer( "sendtyping", outgoingList ); +} + +#include "typingtask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/typingtask.h b/kopete/protocols/groupwise/libgroupwise/tasks/typingtask.h new file mode 100644 index 00000000..4f4da1cd --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/typingtask.h @@ -0,0 +1,41 @@ +/* + Kopete Groupwise Protocol + typingtask.h - sends typing notifications to the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef TYPINGTASK_H +#define TYPINGTASK_H + +#include "requesttask.h" + +/** + Notifies the server that we are typing or are no longer typing in a particular conversation + +@author Kopete Developers +*/ +class TypingTask : public RequestTask +{ +Q_OBJECT + +public: + TypingTask(Task* parent); + ~TypingTask(); + void typing( const GroupWise::ConferenceGuid & guid, const bool typing ); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/updatecontacttask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/updatecontacttask.cpp new file mode 100644 index 00000000..d8c1a68a --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/updatecontacttask.cpp @@ -0,0 +1,76 @@ +/* + Kopete Groupwise Protocol + updatecontacttask.cpp - rename a contact on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "gwfield.h" + +#include "updatecontacttask.h" + +using namespace GroupWise; + +UpdateContactTask::UpdateContactTask(Task* parent): UpdateItemTask(parent) +{ +} + + +UpdateContactTask::~UpdateContactTask() +{ +} + +QString UpdateContactTask::displayName() +{ + return m_name; +} + +void UpdateContactTask::renameContact( const QString & newName, const QValueList & contactInstances ) +{ + m_name = newName; + // build a list of delete, add fields that removes each instance on the server and then readds it with the new name + Field::FieldList lst; + const QValueList::ConstIterator end = contactInstances.end(); + for( QValueList::ConstIterator it = contactInstances.begin(); it != end; ++it ) + { + Field::FieldList contactFields; + contactFields.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, (*it).id ) ); + contactFields.append( new Field::SingleField( NM_A_SZ_PARENT_ID, 0, NMFIELD_TYPE_UTF8, (*it).parentId ) ); + contactFields.append( new Field::SingleField( NM_A_SZ_SEQUENCE_NUMBER, 0, NMFIELD_TYPE_UTF8, (*it).sequence ) ); + if ( !(*it).dn.isNull() ) + contactFields.append( new Field::SingleField( NM_A_SZ_DN, 0, NMFIELD_TYPE_UTF8, (*it).dn ) ); + if ( !(*it).displayName.isNull() ) + contactFields.append( new Field::SingleField( NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_TYPE_UTF8, (*it).displayName ) ); + lst.append( + new Field::MultiField( NM_A_FA_CONTACT, NMFIELD_METHOD_DELETE, 0, NMFIELD_TYPE_ARRAY, contactFields ) ); + } + for( QValueList::ConstIterator it = contactInstances.begin(); it != end; ++it ) + { + Field::FieldList contactFields; + contactFields.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, (*it).id ) ); + contactFields.append( new Field::SingleField( NM_A_SZ_PARENT_ID, 0, NMFIELD_TYPE_UTF8, (*it).parentId ) ); + contactFields.append( new Field::SingleField( NM_A_SZ_SEQUENCE_NUMBER, 0, NMFIELD_TYPE_UTF8, (*it).sequence ) ); + if ( !(*it).dn.isNull() ) + contactFields.append( new Field::SingleField( NM_A_SZ_DN, 0, NMFIELD_TYPE_UTF8, (*it).dn ) ); + contactFields.append( new Field::SingleField( NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_TYPE_UTF8, newName ) ); + lst.append( + new Field::MultiField( NM_A_FA_CONTACT, NMFIELD_METHOD_ADD, 0, NMFIELD_TYPE_ARRAY, contactFields ) ); + } + //lst.dump( true ); + UpdateItemTask::item( lst ); +} + +#include "updatecontacttask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/updatecontacttask.h b/kopete/protocols/groupwise/libgroupwise/tasks/updatecontacttask.h new file mode 100644 index 00000000..7e6ac899 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/updatecontacttask.h @@ -0,0 +1,44 @@ +/* + Kopete Groupwise Protocol + updatecontacttask.h - rename a contact on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef UPDATECONTACTTASK_H +#define UPDATECONTACTTASK_H + +#include "gwerror.h" + +#include "updateitemtask.h" + +/** + * Renames a contact on the server + * @author Kopete Developers + */ +class UpdateContactTask : public UpdateItemTask +{ +Q_OBJECT +public: + UpdateContactTask(Task* parent); + ~UpdateContactTask(); + void renameContact( const QString& newName, const QValueList & contactInstances ); + QString displayName(); +private: + QString m_name; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/updatefoldertask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/updatefoldertask.cpp new file mode 100644 index 00000000..fef5d2fe --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/updatefoldertask.cpp @@ -0,0 +1,59 @@ +/* + Kopete Groupwise Protocol + updatefoldertask.cpp - rename a folder on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ +#include "gwfield.h" + +#include "updatefoldertask.h" + +UpdateFolderTask::UpdateFolderTask(Task* parent): UpdateItemTask(parent) +{ +} + +UpdateFolderTask::~UpdateFolderTask() +{ +} + +void UpdateFolderTask::renameFolder( const QString & newName, const GroupWise::FolderItem & existing ) +{ + Field::FieldList lst; + // add the old version of the folder, marked delete + lst.append( new Field::MultiField( NM_A_FA_FOLDER, NMFIELD_METHOD_DELETE, 0, NMFIELD_TYPE_ARRAY, folderToFields( existing) ) ); + + GroupWise::FolderItem renamed = existing; + renamed.name = newName; + // add the new version of the folder, marked add + lst.append( new Field::MultiField( NM_A_FA_FOLDER, NMFIELD_METHOD_ADD, 0, NMFIELD_TYPE_ARRAY, folderToFields( renamed ) ) ); + // let our parent class package it up as a contactlist in a transfer + UpdateItemTask::item( lst ); +} + +Field::FieldList UpdateFolderTask::folderToFields( const GroupWise::FolderItem & folder ) +{ + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, folder.id ) ); + lst.append( new Field::SingleField( NM_A_SZ_PARENT_ID, 0, NMFIELD_TYPE_UTF8, 0 ) ); + lst.append( new Field::SingleField( NM_A_SZ_TYPE, 0, NMFIELD_TYPE_UTF8, 1 ) ); + lst.append( new Field::SingleField( NM_A_SZ_SEQUENCE_NUMBER, 0, NMFIELD_TYPE_UTF8, folder.sequence ) ); + if ( !folder.name.isEmpty() ) + lst.append( new Field::SingleField( NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_TYPE_UTF8, folder.name ) ); + return lst; +} + +#include "updatefoldertask.moc" + diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/updatefoldertask.h b/kopete/protocols/groupwise/libgroupwise/tasks/updatefoldertask.h new file mode 100644 index 00000000..230bd563 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/updatefoldertask.h @@ -0,0 +1,42 @@ +/* + Kopete Groupwise Protocol + updatefoldertask.h - rename a folder on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef UPDATEFOLDERTASK_H +#define UPDATEFOLDERTASK_H + +#include + +/** +Renames a folder on the server + +@author Kopete Developers +*/ +class UpdateFolderTask : public UpdateItemTask +{ +Q_OBJECT +public: + UpdateFolderTask(Task* parent); + ~UpdateFolderTask(); + void renameFolder( const QString & newName, const GroupWise::FolderItem & existing ); +protected: + Field::FieldList folderToFields( const GroupWise::FolderItem & folder ); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/updateitemtask.cpp b/kopete/protocols/groupwise/libgroupwise/tasks/updateitemtask.cpp new file mode 100644 index 00000000..1af4ef12 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/updateitemtask.cpp @@ -0,0 +1,39 @@ +/* + Kopete Groupwise Protocol + updateitemtask.cpp - ancestor for tasks that rename objects on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "updateitemtask.h" + +UpdateItemTask::UpdateItemTask( Task* parent) : RequestTask( parent ) +{ +} + + +UpdateItemTask::~UpdateItemTask() +{ +} + +void UpdateItemTask::item( Field::FieldList updateItemFields ) +{ + Field::FieldList lst; + lst.append( new Field::MultiField( NM_A_FA_CONTACT_LIST, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, updateItemFields ) ); + createTransfer( "updateitem", lst ); +} + +#include "updateitemtask.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tasks/updateitemtask.h b/kopete/protocols/groupwise/libgroupwise/tasks/updateitemtask.h new file mode 100644 index 00000000..a087d276 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tasks/updateitemtask.h @@ -0,0 +1,40 @@ +/* + Kopete Groupwise Protocol + updateitemtask.h - ancestor for tasks that rename objects on the server + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef UPDATEITEMTASK_H +#define UPDATEITEMTASK_H + +#include "requesttask.h" + +/** +Rename a folder or contact on the server. In future may be used for changing the order of folders or contacts relative to one another, but this is not supported by Kopete yet. + +@author SUSE AG +*/ +class UpdateItemTask : public RequestTask +{ +Q_OBJECT +public: + UpdateItemTask( Task* parent ); + ~UpdateItemTask(); + void item( Field::FieldList updateItemFields ); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tests/Makefile.am b/kopete/protocols/groupwise/libgroupwise/tests/Makefile.am new file mode 100644 index 00000000..33a603ad --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tests/Makefile.am @@ -0,0 +1,22 @@ +INCLUDES = -I$(top_srcdir)/kopete/protocols/groupwise/libgroupwise/qca/src \ + -I$(top_srcdir)/kopete/protocols/groupwise/libgroupwise \ + -I$(top_srcdir)/kopete/protocols/groupwise \ + $(all_includes) +METASOURCES = AUTO +noinst_PROGRAMS = clientstream_test field_test coreprotocol_test client_test +coreprotocol_test_LDFLAGS = -no-undefined $(all_libraries) +coreprotocol_test_SOURCES = coreprotocol_test.cpp +coreprotocol_test_LDADD = \ + ../libgwtest.la -lqt-mt +field_test_LDFLAGS = -no-undefined $(all_libraries) +field_test_SOURCES = field_test.cpp +field_test_LDADD = \ + ../libgwtest.la -lqt-mt + +clientstream_test_SOURCES = clientstream_test.cpp +clientstream_test_LDADD = -lqt-mt \ + ../../kopete_groupwise.la + +client_test_SOURCES = client_test.cpp +client_test_LDADD = ../../../../protocols/groupwise/kopete_groupwise.la \ + ../libgwtest.la -lqt-mt diff --git a/kopete/protocols/groupwise/libgroupwise/tests/client_test.cpp b/kopete/protocols/groupwise/libgroupwise/tests/client_test.cpp new file mode 100644 index 00000000..22f92282 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tests/client_test.cpp @@ -0,0 +1,10 @@ +#include "client.h" +#include "task.h" + +int main() +{ + Client c; + Task rootTask( &c, true ); + + return 0; +} diff --git a/kopete/protocols/groupwise/libgroupwise/tests/clientstream_test.cpp b/kopete/protocols/groupwise/libgroupwise/tests/clientstream_test.cpp new file mode 100644 index 00000000..bbd10ee8 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tests/clientstream_test.cpp @@ -0,0 +1,107 @@ +#include "clientstream_test.h" + +ClientStreamTest::ClientStreamTest(int argc, char ** argv) : QApplication( argc, argv ) +{ + // set up client stream + myConnector = new KNetworkConnector( 0 ); + //myConnector->setOptHostPort( "localhost", 8300 ); + myConnector->setOptHostPort( "reiser.suse.de", 8300 ); + myConnector->setOptSSL( true ); + Q_ASSERT( QCA::isSupported(QCA::CAP_TLS) ); + myTLS = new QCA::TLS; + myTLSHandler = new QCATLSHandler( myTLS ); + myTestObject = new ClientStream( myConnector, myTLSHandler, 0); + // notify when the transport layer is connected + connect( myTestObject, SIGNAL( connected() ), SLOT( slotConnected() ) ); + // it's necessary to catch this signal and tell the TLS handler to proceed, even if we don't check cert validity + connect( myTLSHandler, SIGNAL(tlsHandshaken()), SLOT(slotTLSHandshaken()) ); + // notify and start sending + connect( myTestObject, SIGNAL( securityLayerActivated(int) ), SLOT( slotSend(int) ) ); + connect( myTestObject, SIGNAL( warning(int) ), SLOT( slotWarning(int) ) ); + + // do test once the event loop is running + QTimer::singleShot( 0, this, SLOT( slotDoTest() ) ); +} + +ClientStreamTest::~ClientStreamTest() +{ + delete myTestObject; + delete myTLSHandler; + delete myTLS; + delete myConnector; +} + +void ClientStreamTest::slotDoTest() +{ + NovellDN dn; + dn.dn = "maeuschen"; + dn.server = "reiser.suse.de"; + // connect to server + qDebug( "connecting to server "); + myTestObject->connectToServer( dn, true ); // fine up to here... +} + +void ClientStreamTest::slotConnected() +{ + qDebug( "connection is up"); +} + +void ClientStreamTest::slotWarning(int warning) +{ + qDebug( "warning: %i", warning); +} + +void ClientStreamTest::slotsend(int layer) +{ + qDebug( "security layer is up: %i", layer); + RequestFactory testFactory; + // we're not connecting... + qDebug( "sending request" ); + // send a request + QCString command("login"); + Request * firstRequest = testFactory.request( command ); + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_SZ_USERID, 0, NMFIELD_TYPE_UTF8, "maeuschen" ) ); + lst.append( new Field::SingleField( NM_A_SZ_CREDENTIALS, 0, NMFIELD_TYPE_UTF8, "maeuschen" ) ); + lst.append( new Field::SingleField( NM_A_SZ_USER_AGENT, 0, NMFIELD_TYPE_UTF8, "libgroupwise/0.1 (linux, 2.6.5-7.97-smp)" ) ); + lst.append( new Field::SingleField( NM_A_UD_BUILD, 0, NMFIELD_TYPE_UDWORD, 2 ) ); + lst.append( new Field::SingleField( NM_A_IP_ADDRESS, 0, NMFIELD_TYPE_UTF8, "10.10.11.103" ) ); + firstRequest->setFields( lst ); + myTestObject->write( firstRequest ); + qDebug( "done"); +} + +void ClientStreamTest::slotTLSHandshaken() +{ + qDebug( "TLS handshake complete" ); + int validityResult = myTLS->certificateValidityResult (); + + if( validityResult == QCA::TLS::Valid ) + { + qDebug( "Certificate is valid, continuing."); + // valid certificate, continue + myTLSHandler->continueAfterHandshake (); + } + else + { + qDebug( "Certificate is not valid, continuing" ); + // certificate is not valid, query the user + /* if(handleTLSWarning (validityResult, server (), myself()->contactId ()) == KMessageBox::Continue) + {*/ + myTLSHandler->continueAfterHandshake (); + /* } + else + { + disconnect ( Kopete::Account::Manual ); + }*/ + } + +} +int main(int argc, char ** argv) +{ + ClientStreamTest a( argc, argv ); + a.exec(); + return 0; +} + +#include "clientstream_test.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tests/clientstream_test.h b/kopete/protocols/groupwise/libgroupwise/tests/clientstream_test.h new file mode 100644 index 00000000..2c77f4e1 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tests/clientstream_test.h @@ -0,0 +1,57 @@ +// +// C++ Implementation: clientstream_test +// +// Description: +// +// +// Author: Kopete Developers , (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// + +#ifndef clientstream_test_h +#define clientstream_test_h + +#include +#include +#include + +#include "gwclientstream.h" +#include "gwconnector.h" +#include +#include "qcatlshandler.h" +#include "requestfactory.h" +#include "request.h" +#include "usertransfer.h" + +#include "coreprotocol.h" + +#define QT_FATAL_ASSERT 1 + +class ClientStreamTest : public QApplication +{ +Q_OBJECT +public: + ClientStreamTest(int argc, char ** argv); + + ~ClientStreamTest(); + +public slots: + void slotDoTest(); + + void slotConnected(); + + void slotWarning(int warning); + + void slotsend(int layer); + void slotTLSHandshaken(); + +private: + KNetworkConnector *myConnector; + QCA::TLS *myTLS; + QCATLSHandler *myTLSHandler; + ClientStream *myTestObject; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/tests/coreprotocol_test.cpp b/kopete/protocols/groupwise/libgroupwise/tests/coreprotocol_test.cpp new file mode 100644 index 00000000..d1de6084 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tests/coreprotocol_test.cpp @@ -0,0 +1,30 @@ +// +// C++ Implementation: coreprotocol_test +// +// Description: +// +// +// Author: Kopete Developers , (C) 2004 +// +// Copyright: See COPYING file that comes with this distribution +// +// + +#include "requestfactory.h" +#include "request.h" +#include "usertransfer.h" + +#include "coreprotocol.h" + +int main() +{ + CoreProtocol testObject; + RequestFactory testFactory; + QCString command("login"); + Request * firstRequest = testFactory.request( command ); + Field::FieldList lst; + lst.append( new Field::SingleField( NM_A_SZ_USERID, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_UTF8, "blah@fasel.org" ) ); + firstRequest->setFields( lst ); + testObject.outgoingTransfer( firstRequest ); + return 0; +} diff --git a/kopete/protocols/groupwise/libgroupwise/tests/field_test.cpp b/kopete/protocols/groupwise/libgroupwise/tests/field_test.cpp new file mode 100644 index 00000000..eec3f1f2 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tests/field_test.cpp @@ -0,0 +1,154 @@ +#include "gwfield.h" +#include + +static Field::FieldList fl; + +void buildList(); +void buildFakeContactList(); +void extractFields( Field::FieldList ); + +int main() +{ + buildFakeContactList(); + // look for a field in the list +/* if ( fl.find( NM_A_FA_MESSAGE ) != fl.end() ) + printf( "Found a field, where there was supposed to be one :)\n" ); + else + printf( "Didn't find a field, where there was supposed to be one :(\n" ); + Field::FieldListIterator it; + if ( (it = fl.find( NM_A_SZ_OBJECT_ID ) ) != fl.end() ) + printf( "Found a field, where there was NOT supposed to be one :(\n" ); + else + printf( "Didn't find a field, where there wasn't supposed to be one :)\n" );*/ + //printf( "%i\n", static_cast(*it) ); + // dump the list + fl.dump( true ); + + printf( "\nNow testing find routines.\n"); + // find the field containing the contact list + Field::MultiField * clf = dynamic_cast< Field::MultiField * >( *(fl.find( NM_A_FA_CONTACT_LIST ) ) ); + if ( clf ) + { + Field::FieldList cl = clf->fields(); + // look for a folder in the list + Field::FieldListIterator it = cl.find( NM_A_FA_FOLDER ); + if ( it != cl.end() ) + printf( "Found the first folder :)\n"); + else + printf( "Didn't find the first folder, where did it go? :(\n"); + + printf( "Looking for a second folder :)\n"); + it = cl.find( ++it, NM_A_FA_FOLDER ); + if ( it == cl.end() ) + printf( "Didn't find a second folder :)\n" ); + else + printf( "Found a second folder, now did that get there? :(\n"); + } + else + printf( "Didn't find the contact list, where did it go? :(\n"); + + //extractFields( fl ); + return 0; +} +// test Field subclasses by creating various FieldLists and recovering the data + +void buildList() +{ + // STRUCTURE + // fl - top list + // sf - faust quote + // mf - Multifield - participants, containing + // nl - nested list + // sf - contactlist (empty field array) + // sf - message body + + Field::SingleField* sf = new Field::SingleField( NM_A_FA_MESSAGE, 0, NMFIELD_TYPE_UTF8, QString::fromLatin1( "Da steh ich nun, ich armer Tor! Und bin so klug als wie zuvor..." ) ); + fl.append( sf ); + sf = new Field::SingleField( NM_A_SZ_TRANSACTION_ID, 0, NMFIELD_TYPE_UTF8, QString::fromLatin1( "maeuschen" ) ); + fl.append( sf ); + // nested list + Field::FieldList nl; + sf = new Field::SingleField( NM_A_SZ_STATUS, 0, NMFIELD_TYPE_UDWORD, 123 ); + nl.append( sf ); + sf = new Field::SingleField( NM_A_SZ_MESSAGE_BODY, 0, NMFIELD_TYPE_UTF8, QString::fromLatin1( "bla bla" ) ); + nl.append( sf ); + Field::MultiField* mf = new Field::MultiField( NM_A_FA_PARTICIPANTS, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY ); + mf->setFields( nl ); + fl.append( mf ); + +/* Field::SingleField * ext = sf; + printf( "tag: %s flags: %i type: %i value: %s\n", ext->tag().data(), ext->flags(), ext->type(), ext->value().toString().ascii() );*/ +} + +void buildFakeContactList() +{ + using namespace Field; + + FieldList contactlist; + // add a few contacts + { + const char* names[] = { "apple", "banana", "cherry", "damson", "elderberry", "framboise" }; + for ( int i = 0; i < 6; i ++ ) + { + FieldList contact; + Field::SingleField* sf = new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, QString::number( i ) ); + contact.append( sf ); + sf = new Field::SingleField( NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_TYPE_UTF8, names[i] ); + contact.append( sf ); + MultiField* mf = new MultiField( NM_A_FA_CONTACT, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, contact ); + contactlist.append( mf ); + } + } + // add a folder + { + FieldList folder; + Field::SingleField* sf = new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, QString::number( 1 ) ); + folder.append( sf ); + sf = new Field::SingleField( NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_TYPE_UTF8, "buddies" ); + folder.append( sf ); + MultiField* mf = new MultiField( NM_A_FA_FOLDER, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, folder ); + contactlist.append( mf ); + } + // add some more contacts + { + const char* names[] = { "aardvark", "boar", "cat" }; + for ( int i = 0; i < 3; i ++ ) + { + FieldList contact; + Field::SingleField* sf = new Field::SingleField( NM_A_SZ_OBJECT_ID, 0, NMFIELD_TYPE_UTF8, QString::number( i ) ); + contact.append( sf ); + sf = new Field::SingleField( NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_TYPE_UTF8, names[i] ); + contact.append( sf ); + MultiField* mf = new MultiField( NM_A_FA_CONTACT, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, contact ); + contactlist.append( mf ); + } + } + + + MultiField * cl = new MultiField( NM_A_FA_CONTACT_LIST, NMFIELD_METHOD_VALID, 0, NMFIELD_TYPE_ARRAY, contactlist ); + fl.append( cl ); +} + +void extractFields( Field::FieldList l ) +{ + Field::FieldListIterator it; + printf ("iterating over %i fields\n", l.count() ); + for ( it = l.begin(); it != l.end() ; ++it ) + { + printf ("field\n"); + Field::SingleField * ext = dynamic_cast( *it ); + if ( ext ) + printf( "tag: %s flags: %i type: %i value: %s\n", ext->tag().data(), ext->flags(), ext->type(), ext->value().toString().ascii() ); + else + { + Field::MultiField* mf = dynamic_cast( *it ); + if ( mf ) + { + printf( "found a multi value field\n" ); + extractFields( mf->fields() ); + } + } + } + + printf ("done\n"); +} diff --git a/kopete/protocols/groupwise/libgroupwise/tlshandler.cpp b/kopete/protocols/groupwise/libgroupwise/tlshandler.cpp new file mode 100644 index 00000000..290dba36 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tlshandler.cpp @@ -0,0 +1,31 @@ +/* + tlshandler.cpp - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "tlshandler.h" + +TLSHandler::TLSHandler(QObject *parent) +:QObject(parent) +{ +} + +TLSHandler::~TLSHandler() +{ +} + +#include "tlshandler.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/tlshandler.h b/kopete/protocols/groupwise/libgroupwise/tlshandler.h new file mode 100644 index 00000000..61c8fe7d --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/tlshandler.h @@ -0,0 +1,51 @@ +/* + tlshandler.h - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Iris, Copyright (C) 2003 Justin Karneges + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef GWTLSHANDLER_H +#define GWTLSHANDLER_H + +#include +//#include +//#include +//#include +//#include +//#include +//#include + +class TLSHandler : public QObject +{ + Q_OBJECT +public: + TLSHandler(QObject *parent=0); + virtual ~TLSHandler(); + + virtual void reset()=0; + virtual void startClient(const QString &host)=0; + virtual void write(const QByteArray &a)=0; + virtual void writeIncoming(const QByteArray &a)=0; + +signals: + void success(); + void fail(); + void closed(); + void readyRead(const QByteArray &a); + void readyReadOutgoing(const QByteArray &a, int plainBytes); +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/transfer.cpp b/kopete/protocols/groupwise/libgroupwise/transfer.cpp new file mode 100644 index 00000000..366deed0 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/transfer.cpp @@ -0,0 +1,30 @@ +/* + transfer.cpp - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ +#include + +#include "transfer.h" + +Transfer::Transfer() +{ +} + + +Transfer::~Transfer() +{ +} + + diff --git a/kopete/protocols/groupwise/libgroupwise/transfer.h b/kopete/protocols/groupwise/libgroupwise/transfer.h new file mode 100644 index 00000000..b46f81ea --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/transfer.h @@ -0,0 +1,34 @@ +/* + transfer.h - Kopete Groupwise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef TRANSFER_H +#define TRANSFER_H + +/** +@author Kopete Developers +*/ +class Transfer{ +public: + enum TransferType { EventTransfer, RequestTransfer, ResponseTransfer }; + Transfer(); + virtual ~Transfer(); + + virtual TransferType type() = 0; + +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/transferbase.cpp b/kopete/protocols/groupwise/libgroupwise/transferbase.cpp new file mode 100644 index 00000000..6864ea48 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/transferbase.cpp @@ -0,0 +1,29 @@ +/* + transferbase.cpp - Base class of all I/O transfers + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "transferbase.h" + +TransferBase::TransferBase() +{ +} + + +TransferBase::~TransferBase() +{ +} + + diff --git a/kopete/protocols/groupwise/libgroupwise/transferbase.h b/kopete/protocols/groupwise/libgroupwise/transferbase.h new file mode 100644 index 00000000..de7a688d --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/transferbase.h @@ -0,0 +1,32 @@ +/* + transferbase.h - Base class of all I/O transfers + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef TRANSFERBASE_H +#define TRANSFERBASE_H + +/** +@author Kopete Developers +*/ +class TransferBase{ +public: + TransferBase(); + + ~TransferBase(); + +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/userdetailsmanager.cpp b/kopete/protocols/groupwise/libgroupwise/userdetailsmanager.cpp new file mode 100644 index 00000000..2527968e --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/userdetailsmanager.cpp @@ -0,0 +1,129 @@ +/* + userdetailsmanager.cpp - Storage of all user details seen during this session + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "client.h" +#include "tasks/getdetailstask.h" + +#include "userdetailsmanager.h" + +UserDetailsManager::UserDetailsManager( Client * parent, const char *name) + : QObject(parent, name), m_client( parent ) +{ +} + +UserDetailsManager::~UserDetailsManager() +{ +} + +void UserDetailsManager::dump( const QStringList & list ) +{ + for ( QStringList::ConstIterator it = list.begin(); it != list.end(); ++it ) + { + m_client->debug( QString( " - %1" ).arg (*it) ); + } +} + +bool UserDetailsManager::known( const QString & dn ) +{ + if ( dn == m_client->userDN() ) + return true; + // TODO: replace with m_detailsMap.contains( dn ); + QStringList::Iterator found = m_detailsMap.keys().find( dn ); + // we always know the local user's details, so don't look them up + return ( found !=m_detailsMap.keys().end() ); +} + +ContactDetails UserDetailsManager::details( const QString & dn ) +{ + return m_detailsMap[ dn ]; +} + +QStringList UserDetailsManager::knownDNs() +{ + return m_detailsMap.keys(); +} + +void UserDetailsManager::addDetails( const ContactDetails & details ) +{ + //qDebug( "UserDetailsManager::addContact, got %s, we now know: ", details.dn.ascii() ); + m_detailsMap.insert( details.dn, details ); +/* QStringList keys = m_detailsMap.keys(); + dump( keys ); + qDebug( "UserDetailsManager::addContact, pending: " ); + dump( m_pendingDNs );*/ +} + +void UserDetailsManager::removeContact( const QString & dn ) +{ + m_detailsMap.remove( dn ); +} + +void UserDetailsManager::requestDetails( const QStringList & dnList, bool onlyUnknown ) +{ + // build a list of DNs that are not already subject to a pending request + QStringList requestList; + QValueListConstIterator end = dnList.end(); + for ( QValueListConstIterator it = dnList.begin(); it != end; ++it ) + { + // don't request our own details + if ( *it == m_client->userDN() ) + break; + // don't request details we already have unless the caller specified this + if ( onlyUnknown && known( *it ) ) + break; + QStringList::Iterator found = m_pendingDNs.find( *it ); + if ( found == m_pendingDNs.end() ) + { + m_client->debug( QString( "UserDetailsManager::requestDetails - including %1" ).arg( (*it) ) ); + requestList.append( *it ); + m_pendingDNs.append( *it ); + } + } + if ( !requestList.empty() ) + { + GetDetailsTask * gdt = new GetDetailsTask( m_client->rootTask() ); + gdt->userDNs( requestList ); + connect( gdt, SIGNAL( gotContactUserDetails( const GroupWise::ContactDetails & ) ), + SLOT( slotReceiveContactDetails( const GroupWise::ContactDetails & ) ) ); + // TODO: connect to gdt's finished() signal, check for failures, expand gdt to maintain a list of not found DNs? + gdt->go( true ); + } + else + { + m_client->debug( "UserDetailsManager::requestDetails - all requested contacts are already available or pending" ); + } +} + +void UserDetailsManager::requestDetails( const QString & dn, bool onlyUnknown ) +{ + m_client->debug( QString( "UserDetailsManager::requestDetails for %1" ).arg( dn ) ); + QStringList list; + list.append( dn ); + requestDetails( list, onlyUnknown ); +} + +void UserDetailsManager::slotReceiveContactDetails( const GroupWise::ContactDetails & details ) +{ + m_client->debug( "UserDetailsManager::slotReceiveContactDetails()" ); + m_pendingDNs.remove( details.dn ); + /*client()->userDetailsManager()->*/ + addDetails( details ); + //emit temporaryContact( details ); + emit gotContactDetails( details ); +} + +#include "userdetailsmanager.moc" diff --git a/kopete/protocols/groupwise/libgroupwise/userdetailsmanager.h b/kopete/protocols/groupwise/libgroupwise/userdetailsmanager.h new file mode 100644 index 00000000..4e9b6022 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/userdetailsmanager.h @@ -0,0 +1,84 @@ +/* + userdetailsmanager.h - Storage of all user details seen during this session + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef USERDETAILSMANAGER_H +#define USERDETAILSMANAGER_H + +#include +#include +#include + +#include "gwerror.h" +class Client; + +/** +Several client event handling processes require that a contact's details are available before exposing the event to the user. This class is responsible for issuing details requests, tracking which users the client already has received details for, and signalling when details have been received. The manager allows multiple interleaved get details requests to be replaced by a single request. + +@author SUSE AG +*/ + +class UserDetailsManager : public QObject +{ +Q_OBJECT +public: + UserDetailsManager( Client * parent, const char *name = 0); + ~UserDetailsManager(); + /** + * List of DNs that we have already received details for + */ + QStringList knownDNs(); + /** + * Check if we have details for a single DN + */ + bool known( const QString &dn ); + /** + * Get details for a given DN + */ + ContactDetails details( const QString &dn ); + /** + * Add a ContactDetails object to our cache. + * This SHOULD be called when receiving details in contactlist receive and manipulation, to prevent unnecessary additional requests. + */ + void addDetails( const GroupWise::ContactDetails & details ); + /** + * Remove a contact from the list of known DNs. This MUST be performed when a client removes a DN from its local contact list, + * otherwise new events from this DN will not receive user details. + */ + void removeContact( const QString & dn ); + /** + * Explicitly request details for a set of contacts from the server. + * Will signal @ref gotContactUserDetails for each one when they are available. + */ + void requestDetails( const QStringList & dnList, bool onlyUnknown = true ); + /** + * Explicitly request a contact's details from the server. Will signal @ref gotContactUserDetails when they are available. + */ + void requestDetails( const QString & dn, bool onlyUnknown = true ); + +signals: + void gotContactDetails( const GroupWise::ContactDetails & ); +protected slots: + void slotReceiveContactDetails( const GroupWise::ContactDetails & ); +protected: + void dump( const QStringList & list ); +private: + QStringList m_pendingDNs; // a list of DNs that have pending requests + Client * m_client; + QMap< QString, GroupWise::ContactDetails > m_detailsMap; +}; + +#endif diff --git a/kopete/protocols/groupwise/libgroupwise/usertransfer.cpp b/kopete/protocols/groupwise/libgroupwise/usertransfer.cpp new file mode 100644 index 00000000..85f0f395 --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/usertransfer.cpp @@ -0,0 +1,46 @@ +/* + usertransfer.cpp - Ancestor of In- or outgoing Transfers (Requests and Response) + initated by the user. + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "usertransfer.h" + +UserTransfer::UserTransfer( int transactionId ) +{ + m_transactionId = transactionId; +} + +UserTransfer::~UserTransfer() +{ + m_fields.purge(); +} + +void UserTransfer::setFields( Field::FieldList fields ) +{ + m_fields = fields; +} + +int UserTransfer::transactionId() +{ + return m_transactionId; +} + +Field::FieldList UserTransfer::fields() +{ + return m_fields; +} + + diff --git a/kopete/protocols/groupwise/libgroupwise/usertransfer.h b/kopete/protocols/groupwise/libgroupwise/usertransfer.h new file mode 100644 index 00000000..d4d30dbc --- /dev/null +++ b/kopete/protocols/groupwise/libgroupwise/usertransfer.h @@ -0,0 +1,45 @@ +/* + usertransfer.h - Ancestor of In- or outgoing Transfers (Requests and Response) + initated by the user. + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef USERTRANSFER_H +#define USERTRANSFER_H + +#include "gwfield.h" + +#include "transfer.h" + +/** + * Represents transfers of data in response to a user action, either outgoing Requests, or incoming Responses + * @author Kopete Developers + */ +class UserTransfer : public Transfer +{ +public: + UserTransfer( int transactionId ); + virtual ~UserTransfer(); + int transactionId(); + Field::FieldList fields(); + void setFields( Field::FieldList fields ); + +private: + int m_transactionId; + Field::FieldList m_fields; + +}; + +#endif diff --git a/kopete/protocols/groupwise/ui/Makefile.am b/kopete/protocols/groupwise/ui/Makefile.am new file mode 100644 index 00000000..3cd76e27 --- /dev/null +++ b/kopete/protocols/groupwise/ui/Makefile.am @@ -0,0 +1,19 @@ +INCLUDES = -I$(top_srcdir)/protocols/groupwise/libgroupwise \ + -I$(top_srcdir)/kopete/protocols/groupwise/libgroupwise -I$(top_srcdir)/kopete/protocols/groupwise/libgroupwise/qca/src +METASOURCES = AUTO +noinst_LTLIBRARIES = libkopetegroupwiseui.la +AM_CPPFLAGS = $(KOPETE_INCLUDES) \ + -I$(srcdir)/..\ + -I$(srcdir)/../libgroupwise \ + $(all_includes) + +libkopetegroupwiseui_la_LDFLAGS = $(all_libraries) +libkopetegroupwiseui_la_SOURCES = gwaccountpreferences.ui gwaddcontactpage.cpp \ + gwaddui.ui gweditaccountwidget.cpp gwreceiveinvitationdialog.cpp \ + gwshowinvitation.ui gwcontactpropswidget.ui gwcontactproperties.cpp gwprivacy.ui \ + gwprivacydialog.cpp gwsearch.cpp gwcustomstatuswidget.ui gwcustomstatusedit.ui \ + gwcontactsearch.ui gwchatsearchwidget.ui gwchatsearchdialog.cpp gwchatpropswidget.ui \ + gwchatpropsdialog.cpp + +noinst_HEADERS = gwreceiveinvitationdialog.h gwcontactproperties.h \ + gwprivacydialog.h gwsearch.h gwchatsearchdialog.h gwchatpropsdialog.h diff --git a/kopete/protocols/groupwise/ui/gwaccountpreferences.ui b/kopete/protocols/groupwise/ui/gwaccountpreferences.ui new file mode 100644 index 00000000..b5cfabcc --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwaccountpreferences.ui @@ -0,0 +1,320 @@ + +GroupWiseAccountPreferences + + + GroupWiseAccountPreferences + + + + 0 + 0 + 366 + 404 + + + + Account Preferences - Groupwise + + + + unnamed + + + 0 + + + 0 + + + + tabWidget11 + + + + tab + + + B&asic Setup + + + + unnamed + + + + groupBox55 + + + Account Information + + + + unnamed + + + + layout1 + + + + unnamed + + + + textLabel1 + + + &User ID: + + + m_userId + + + The account name of your account. + + + The account name of your account. + + + + + m_userId + + + The account name of your account. + + + The account name of your account. + + + + + + + m_password + + + + + m_autoConnect + + + E&xclude from connect all + + + Check to disable automatic connection. If checked, you may connect to this account manually using the icon in the bottom of the main Kopete window + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + layout66 + + + + unnamed + + + + labelServer + + + true + + + + 5 + 0 + 0 + 0 + + + + Ser&ver: + + + m_server + + + The IP address or hostname of the server you would like to connect to. + + + The IP address or hostname of the server you would like to connect to (for example im.yourcorp.com). + + + + + m_server + + + true + + + + + + The IP address or hostname of the server you would like to connect to. + + + The IP address or hostname of the server you would like to connect to (for example im.yourcorp.com). + + + + + labelPort + + + true + + + + 5 + 0 + 0 + 0 + + + + Po&rt: + + + m_port + + + The port on the server that you would like to connect to. + + + The port on the server that you would like to connect to (default is 5222). + + + + + m_port + + + true + + + + 0 + 0 + 0 + 0 + + + + UpDownArrows + + + 65535 + + + 1 + + + 8300 + + + The port on the server that you would like to connect to. + + + The port on the server that you would like to connect to (default is 5222). + + + + + + + + + + + TabPage + + + Advanced &Options + + + + unnamed + + + + m_alwaysAccept + + + A&lways accept invitations + + + + + spacer7 + + + Vertical + + + Expanding + + + + 20 + 91 + + + + + + + + + labelStatusMessage + + + + + + AlignCenter + + + + + + + Kopete::UI::PasswordWidget +
    kopetepasswordwidget.h
    + + 50 + 50 + + 0 + + 1 + 0 + 0 + 0 + + image0 + changed() +
    +
    + + + 89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b0000032949444154388db59531681b6714c77f32373c8186ef0305eea005093258900eca26d30e3174a8a807d1c9ee940e5d4a276f09a414e22974ee609a4c75a0857a70a20c199ce93424e43414aee0c26910dc8105f7410df706413a7c915551db049a3e38b87bf7bedffddfc7ff7d578be398456c6c6cbce13d441cc7b5da02fcf4e8e99bde7a8f899b501515d959f64e10e71cd949c6e8d508e6cb7cb050fae49727444d87ed08a566dc0cea545a621b96725e62c522f312c4929ff9e7725e6203439282ec0bc72f74150c30c927d89690163f539619a044564973a1980ae54c01c136a1db518a0024808942780dead16a27e7e0ca55949a81668023b242fcd2901c394663072cd408ad75e18b6d43a7076143710aa1b9049ccd326e064a5979e8f0191cfc5878544368af1b24807caa4cfe507ef8aea0bf6dd8b92de7f00bc1562c95e64416e297f216aadcfa3ca43f10da1f8243112286871507fb05c3c7059d568bde96c5885b01af2d6e4a2db10dc8ff128e0fdd39f4cbaf8576dbe170702afcf6b86467bbce57df8680f0d3230767e0e62bdc55c5e53c476742fabbc318437f209886c3cd41d4b0f74049c78ef21476ef5846cf7ded2831848d55f0aa62816caade11adb7ed2fa0f71ce9d8619ac2e627824a45a72b00e413c5a95c0cf63e052bbe2014bfa738c3de3d251dfb0f8a80fda04e6480600113cc558a11a0e10b93a9225886cff04a8d10868662eab87f37271e59f2136f85a855bfda15f9594eb7a3b4ae0b933f95e161c5ceed88f254e97f2ad49b75eedf8562e2d8fb264355314da1dbada866abe47fedb106d01f78b71fec170c8f7276ef58da3de8f64a76bf6f634283730e9d2b9b8390ce0dae565c6a8e04b0710b746678f8a8e0e18382d173a1d7151c909fe4e84ccf57be3e76245b115143584ee73f27afc8e80b4c667e4c37b7054c8be1afde0de978a9c63485fea0457cec70aa089015ab9297e0938c240573cdb7651a4a7f20f43feb304a72aac2e73bd723da1fe5746ec0682bc26070f38c345905d7e238f6077c00dd8f85280211fcd91af84b02ef94a50c004502c1394813252f14575ca09839242f9484cb42df31e763edd237ff31d6c0ffa3fe17f0fb86c7715cfb1ba8bd86cc8d2decd30000000049454e44ae426082 + + + +
    diff --git a/kopete/protocols/groupwise/ui/gwaddcontactpage.cpp b/kopete/protocols/groupwise/ui/gwaddcontactpage.cpp new file mode 100644 index 00000000..93616f95 --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwaddcontactpage.cpp @@ -0,0 +1,108 @@ +/* + Kopete GroupWise Protocol + gweditaccountwidget.cpp - widget for adding GroupWise contacts + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Testbed + Copyright (c) 2003 by Will Stephenson + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#include "gwaddcontactpage.h" + +//#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kopeteaccount.h" +#include "kopetemetacontact.h" + +#include "client.h" +#include "gwaccount.h" +#include "gwerror.h" +//#include "gwprotocol.h" +#include "gwsearch.h" +#include "gwaddui.h" +#include "userdetailsmanager.h" + +GroupWiseAddContactPage::GroupWiseAddContactPage( Kopete::Account * owner, QWidget* parent, const char* name ) + : AddContactPage(parent, name) +{ + m_account = static_cast( owner ); + kdDebug(GROUPWISE_DEBUG_GLOBAL) << k_funcinfo << endl; + ( new QVBoxLayout( this ) )->setAutoAdd( true ); + if (owner->isConnected ()) + { + m_searchUI = new GroupWiseContactSearch( m_account, QListView::Single, false, + this, "acwsearchwidget" ); + show(); + m_canadd = true; + } + else + { + m_noaddMsg1 = new QLabel (i18n ("You need to be connected to be able to add contacts."), this); + m_noaddMsg2 = new QLabel (i18n ("Connect to GroupWise Messenger and try again."), this); + m_canadd = false; + } +} + +GroupWiseAddContactPage::~GroupWiseAddContactPage() +{ +// , i18n( "The search was cancelled" ) +// , i18n( "There was an error while carrying out your search. Please change your search terms or try again later." ) +// i18n( "There was an error while carrying out your search. Please change your search terms or try again later." ) +} + +bool GroupWiseAddContactPage::apply( Kopete::Account* account, Kopete::MetaContact* parentContact ) +{ + if ( validateData() ) + { + QString contactId; + QString displayName; + + QValueList< ContactDetails > selected = m_searchUI->selectedResults(); + if ( selected.count() == 1 ) + { + ContactDetails dt = selected.first(); + m_account->client()->userDetailsManager()->addDetails( dt ); + contactId = dt.dn; + displayName = dt.givenName + " " + dt.surname; + } + else + return false; + + return ( account->addContact ( contactId, parentContact, Kopete::Account::ChangeKABC ) ); + } + else + return false; +} + +bool GroupWiseAddContactPage::validateData() +{ + if ( m_canadd ) + return ( m_searchUI->m_results->selectedItem() ); + else + return false; +} + +#include "gwaddcontactpage.moc" diff --git a/kopete/protocols/groupwise/ui/gwaddcontactpage.h b/kopete/protocols/groupwise/ui/gwaddcontactpage.h new file mode 100644 index 00000000..aa195edd --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwaddcontactpage.h @@ -0,0 +1,68 @@ +/* + Kopete GroupWise Protocol + gweditaccountwidget.h- widget for adding GroupWise contacts + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Testbed + Copyright (c) 2003 by Will Stephenson + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#ifndef TESTBEDADDCONTACTPAGE_H +#define TESTBEDADDCONTACTPAGE_H + +#include "gwerror.h" + +#include + +class QLabel; +namespace Kopete { class Account; } +namespace Kopete { class MetaContact; } +class GroupWiseAccount; +class GroupWiseAddUI; +//TODO: change this to a wrapper around Contact Search and Chatroom Search +class GroupWiseContactSearch; + +/** + * A page in the Add Contact Wizard + * @author Will Stephenson +*/ +class GroupWiseAddContactPage : public AddContactPage +{ + Q_OBJECT +public: + GroupWiseAddContactPage( Kopete::Account * owner, QWidget* parent = 0, const char* name = 0 ); + ~GroupWiseAddContactPage(); + + /** + * Make a contact out of the entered data + */ + virtual bool apply(Kopete::Account* a, Kopete::MetaContact* m); + /** + * Is the data correct? + */ + virtual bool validateData(); +protected: + QValueList< GroupWise::ContactDetails > m_searchResults; + unsigned char searchOperation( int comboIndex ); + GroupWiseAccount * m_account; + GroupWiseAddUI * m_gwAddUI; + //TODO: make wrapper + GroupWiseContactSearch * m_searchUI; + QLabel *m_noaddMsg1; + QLabel *m_noaddMsg2; + bool m_canadd; +}; + +#endif diff --git a/kopete/protocols/groupwise/ui/gwaddui.ui b/kopete/protocols/groupwise/ui/gwaddui.ui new file mode 100644 index 00000000..16859bef --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwaddui.ui @@ -0,0 +1,137 @@ + +GroupWiseAddUI + + + GroupWiseAddUI + + + + 0 + 0 + 392 + 343 + + + + TestbedAddUI + + + + + + + unnamed + + + 0 + + + 0 + + + + m_tabWidget + + + + tab + + + &Basic + + + + unnamed + + + + bg_addMethod + + + Add Using + + + + unnamed + + + + m_userName + + + false + + + A full or partial name. Asterisks are ignored + + + Type some or all of the contact's name. Matches will be shown below + + + + + rb_userId + + + User &ID: + + + true + + + + + rb_userName + + + true + + + Userna&me: + + + + + m_userId + + + StrongFocus + + + A correct User ID + + + Use this field to add a contact if you already know the user's exact User ID + + + + + + + + + tab + + + Ad&vanced + + + + + + + + rb_userId + toggled(bool) + m_userId + setEnabled(bool) + + + rb_userName + toggled(bool) + m_userName + setEnabled(bool) + + + + diff --git a/kopete/protocols/groupwise/ui/gwchatpropsdialog.cpp b/kopete/protocols/groupwise/ui/gwchatpropsdialog.cpp new file mode 100644 index 00000000..eabb75ab --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwchatpropsdialog.cpp @@ -0,0 +1,122 @@ +/* + Kopete Groupwise Protocol + gwchatpropsdialog.h - dialog for viewing/modifying chat properties + + Copyright (c) 2005 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include "gwerror.h" +#include "gwchatpropswidget.h" + +#include "gwchatpropsdialog.h" + +GroupWiseChatPropsDialog::GroupWiseChatPropsDialog( QWidget * parent, const char * name ) + : KDialogBase( parent, name, false, i18n( "Chatroom properties" ), + KDialogBase::Ok|KDialogBase::Cancel, Ok, true ), m_dirty( false ) +{ + initialise(); +} + +GroupWiseChatPropsDialog::GroupWiseChatPropsDialog( const GroupWise::Chatroom & room, bool readOnly, + QWidget * parent, const char * name ) + : KDialogBase( parent, name, false, i18n( "Chatroom properties" ), + KDialogBase::Ok|KDialogBase::Cancel, Ok, true ), m_dirty( false ) +{ + initialise(); + m_widget->m_description->setText( room.description ); + m_widget->m_displayName->setText( room.displayName ); + m_widget->m_disclaimer->setText( room.disclaimer ); + m_widget->m_owner->setText( room.ownerDN ); + m_widget->m_query->setText( room.query ); + m_widget->m_topic->setText( room.topic ); + m_widget->m_archive->setChecked( room.archive ); + m_widget->m_maxUsers->setText( QString::number( room.maxUsers ) ); + m_widget->m_createdOn->setText( room.createdOn.toString() ); + m_widget->m_creator->setText( room.creatorDN ); + + m_widget->m_chkRead->setChecked( room.chatRights & GroupWise::Chatroom::Read || room.chatRights & GroupWise::Chatroom::Write || room.chatRights & GroupWise::Chatroom::Owner ); + m_widget->m_chkWrite->setChecked( room.chatRights & GroupWise::Chatroom::Write || room.chatRights & GroupWise::Chatroom::Owner ); + m_widget->m_chkModify->setChecked( room.chatRights & GroupWise::Chatroom::Modify || room.chatRights & GroupWise::Chatroom::Owner ); + + if ( readOnly ) + { + m_widget->m_description->setReadOnly( true ); + m_widget->m_disclaimer->setReadOnly( true ); + m_widget->m_owner->setReadOnly( true ); + m_widget->m_query->setReadOnly( true ); + m_widget->m_topic->setReadOnly( true ); + m_widget->m_archive->setEnabled( false ); + m_widget->m_maxUsers->setReadOnly( true ); + m_widget->m_createdOn->setReadOnly( true ); + m_widget->m_creator->setReadOnly( true ); + m_widget->m_chkRead->setEnabled( false ); + m_widget->m_chkWrite->setEnabled( false ); + m_widget->m_chkModify->setEnabled( false ); + m_widget->m_btnAddAcl->setEnabled( false ); + m_widget->m_btnEditAcl->setEnabled( false ); + m_widget->m_btnDeleteAcl->setEnabled( false ); + } + +} + +void GroupWiseChatPropsDialog::initialise() +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + m_widget = new GroupWiseChatPropsWidget( this ); + connect( m_widget->m_topic, SIGNAL( textChanged( const QString & ) ), SLOT( slotWidgetChanged() ) ); + connect( m_widget->m_owner, SIGNAL( textChanged( const QString & ) ), SLOT( slotWidgetChanged() ) ); + connect( m_widget->m_createdOn, SIGNAL( textChanged( const QString & ) ), SLOT( slotWidgetChanged() ) ); + connect( m_widget->m_creator, SIGNAL( textChanged( const QString & ) ), SLOT( slotWidgetChanged() ) ); + connect( m_widget->m_description, SIGNAL( textChanged( const QString & ) ), SLOT( slotWidgetChanged() ) ); + connect( m_widget->m_disclaimer, SIGNAL( textChanged( const QString & ) ), SLOT( slotWidgetChanged() ) ); + connect( m_widget->m_query, SIGNAL( textChanged( const QString & ) ), SLOT( slotWidgetChanged() ) ); + connect( m_widget->m_archive, SIGNAL( textChanged( const QString & ) ), SLOT( slotWidgetChanged() ) ); + connect( m_widget->m_maxUsers, SIGNAL( textChanged( const QString & ) ), SLOT( slotWidgetChanged() ) ); + connect( m_widget->m_btnAddAcl, SIGNAL( clicked() ), SLOT( slotWidgetChanged() ) ); + connect( m_widget->m_btnEditAcl, SIGNAL( clicked() ), SLOT( slotWidgetChanged() ) ); + connect( m_widget->m_btnDeleteAcl, SIGNAL( clicked() ), SLOT( slotWidgetChanged() ) ); + setMainWidget( m_widget ); + show(); +} + +GroupWise::Chatroom GroupWiseChatPropsDialog::room() +{ + GroupWise::Chatroom room; + room.description = m_widget->m_description->text(); + room.displayName = m_widget->m_displayName->text(); + room.disclaimer = m_widget->m_disclaimer->text(); + room.ownerDN = m_widget->m_owner->text(); + room.query = m_widget->m_query->text(); + room.topic = m_widget->m_topic->text(); + room.archive = m_widget->m_archive->isChecked(); + room.maxUsers = m_widget->m_maxUsers->text().toInt(); + +// room. + return room; +} + +void GroupWiseChatPropsDialog::slotWidgetChanged() +{ + m_dirty = true; +} + +#include "gwchatpropsdialog.moc" diff --git a/kopete/protocols/groupwise/ui/gwchatpropsdialog.h b/kopete/protocols/groupwise/ui/gwchatpropsdialog.h new file mode 100644 index 00000000..058d6b20 --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwchatpropsdialog.h @@ -0,0 +1,69 @@ +/* + Kopete Groupwise Protocol + gwchatpropsdialog.h - dialog for viewing/modifying chat properties + + Copyright (c) 2005 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#ifndef GWCHATPROPSDIALOG_H +#define GWCHATPROPSDIALOG_H + +#include + +#include "gwchatrooms.h" + +class GroupWiseChatPropsWidget; +/** + * Dialog for viewing/modifying chat properties. + * Chatroom list dialog gets props from manager + * Chatroom list dialog opens chatpropsdlg using props, connects to OkClicked signal + * User makes changes + * CLD asks CPD for changes. + * CLD passes changes to manager + * manager sends ChatUpdate to server + * on success, manager updates own model. + + 1) Create dialog with populated widget from supplied Chatroom. + 2) Add readonly mode. + 3) Track which things changed? Easier to get the changed Chatroom back and diff in the manager, simpler api connecting + */ +class GroupWiseChatPropsDialog : public KDialogBase +{ + Q_OBJECT + public: + /** + * Construct an empty dialog + */ + GroupWiseChatPropsDialog( QWidget * parent, const char * name ); + /** + * Construct a populated dialog + */ + GroupWiseChatPropsDialog( const GroupWise::Chatroom & room, bool readOnly, + QWidget * parent, const char * name ); + + ~GroupWiseChatPropsDialog() {} + + bool dirty() { return m_dirty; }; + GroupWise::Chatroom room(); + protected: + void initialise(); + protected slots: + void slotWidgetChanged(); + private: + GroupWiseChatPropsWidget * m_widget; + GroupWise::Chatroom m_room; + bool m_dirty; +}; + +#endif diff --git a/kopete/protocols/groupwise/ui/gwchatpropswidget.ui b/kopete/protocols/groupwise/ui/gwchatpropswidget.ui new file mode 100644 index 00000000..ecb764b9 --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwchatpropswidget.ui @@ -0,0 +1,394 @@ + +GroupWiseChatPropsWidget + + + GroupWiseChatPropsWidget + + + + 0 + 0 + 493 + 425 + + + + GroupWiseChatPropertiesWidget + + + + unnamed + + + + m_displayName + + + DISPLAY NAME + + + + + layout16 + + + + unnamed + + + + m_creator + + + + + + true + + + The user who created the chatroom + + + + + textLabel10_2 + + + Query: + + + m_firstName_2 + + + + + lblTopic + + + Topic: + + + m_displayName + + + + + m_disclaimer + + + + + + false + + + A disclaimer for users entering the chatroom + + + + + m__2_2 + + + Owner: + + + m_displayName_3 + + + + + m_topic + + + + + + false + + + The current topic of the discussion + + + + + m_query + + + + + + true + + + UNKNOWN + + + + + textLabel11_2_2 + + + Maximum Users: + + + m_lastName_2_2 + + + + + m__2_2_2 + + + Created on: + + + m_displayName_3 + + + + + lbl_displayName_2 + + + Disclaimer: + + + m_displayName_2 + + + + + m_description + + + + + + true + + + General description of the chatroom + + + + + m_maxUsers + + + + + + true + + + Maximum simultaneous users allowed in the chatroom + + + + + textLabel10 + + + Creator: + + + m_firstName + + + + + textLabel11 + + + Description: + + + m_lastName + + + + + m_createdOn + + + + + + false + + + Date and time the chatroom was created + + + + + m_archive + + + Archived + + + + + + Indicates if the chatroom is being archived on the server + + + + + m_owner + + + + + + false + + + The user who owns this chatroom + + + + + line4 + + + HLine + + + Sunken + + + Horizontal + + + + + + + buttonGroup2 + + + Default Access + + + + unnamed + + + + m_chkRead + + + Read Message + + + + + + General permission to read messages in the chatroom + + + + + m_chkWrite + + + Write Message + + + + + + General permission to write messages in the chatroom + + + + + m_chkModify + + + Modify Access + + + + + + General permission to modify the chatroom's access control list + + + + + + + textLabel1 + + + Access Control List + + + kListBox1 + + + + + m_acl + + + Access permissions for specific users + + + + + layout15 + + + + unnamed + + + + m_btnAddAcl + + + A&dd + + + Add a new ACL entry + + + + + m_btnEditAcl + + + Ed&it + + + Edit an existing ACL entry + + + + + m_btnDeleteAcl + + + D&elete + + + Delete a ACL entry + + + + + + + + + klistbox.h + kpushbutton.h + kpushbutton.h + kpushbutton.h + + diff --git a/kopete/protocols/groupwise/ui/gwchatsearchdialog.cpp b/kopete/protocols/groupwise/ui/gwchatsearchdialog.cpp new file mode 100644 index 00000000..fb67a03e --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwchatsearchdialog.cpp @@ -0,0 +1,106 @@ +/* + Kopete Groupwise Protocol + gwchatsearchdialog.cpp - dialog for searching for chatrooms + + Copyright (c) 2005 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#include + +#include +#include + +#include +#include +#include +#include "client.h" +#include "chatroommanager.h" + +#include "gwaccount.h" +#include "gwprotocol.h" +#include "gwchatsearchwidget.h" +#include "gwchatpropsdialog.h" + +#include "gwchatsearchdialog.h" + +GroupWiseChatSearchDialog::GroupWiseChatSearchDialog( GroupWiseAccount * account, QWidget *parent, const char *name ) + : KDialogBase( parent, name, false, i18n( "Search Chatrooms" ), + KDialogBase::Ok|KDialogBase::Apply|KDialogBase::Cancel, Ok, true ), m_account( account ) +{ + m_widget = new GroupWiseChatSearchWidget( this ); +// m_widget->m_searchLineWidget->createSearchLine( m_widget->m_chatrooms ); + setMainWidget( m_widget ); + + m_manager = m_account->client()->chatroomManager(); + + connect ( m_manager, SIGNAL( updated() ), SLOT( slotManagerUpdated() ) ); + connect ( m_manager, SIGNAL( gotProperties( const GroupWise::Chatroom & ) ), + SLOT( slotGotProperties( const GroupWise::Chatroom & ) ) ); + + connect( m_widget->m_btnRefresh, SIGNAL( clicked() ), SLOT( slotUpdateClicked() ) ); + connect( m_widget->m_btnProperties, SIGNAL( clicked() ), SLOT( slotPropertiesClicked() ) ); + + m_manager->updateRooms(); + show(); +} + +GroupWiseChatSearchDialog::~GroupWiseChatSearchDialog() +{ +} + +void GroupWiseChatSearchDialog::slotUpdateClicked() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << "updating chatroom list " << endl; + m_widget->m_chatrooms->clear(); + QListViewItem * first = m_widget->m_chatrooms->firstChild(); + QString updateMessage = i18n("Updating chatroom list..." ); +/* if ( first ) + new QListViewItem( first, updateMessage ); + else*/ + new QListViewItem( m_widget->m_chatrooms, updateMessage ); + m_manager->updateRooms(); + +} + +void GroupWiseChatSearchDialog::slotManagerUpdated() +{ + ChatroomMap rooms = m_manager->rooms(); + ChatroomMap::iterator it = rooms.begin(); + const ChatroomMap::iterator end = rooms.end(); + while ( it != end ) + { + new QListViewItem( m_widget->m_chatrooms, + it.data().displayName, + m_account->protocol()->dnToDotted( it.data().ownerDN ), + QString::number( it.data().participantsCount ) ); + ++it; + } +} + +void GroupWiseChatSearchDialog::slotPropertiesClicked() +{ + QListViewItem * selected = m_widget->m_chatrooms->selectedItem(); + if ( selected ) + { + m_manager->requestProperties( selected->text( 0 ) ); + } +} + +void GroupWiseChatSearchDialog::slotGotProperties(const GroupWise::Chatroom & room) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + new GroupWiseChatPropsDialog( room, true, this, "chatpropsdlg" ); +} + +#include "gwchatsearchdialog.moc" diff --git a/kopete/protocols/groupwise/ui/gwchatsearchdialog.h b/kopete/protocols/groupwise/ui/gwchatsearchdialog.h new file mode 100644 index 00000000..667e6394 --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwchatsearchdialog.h @@ -0,0 +1,49 @@ +/* + Kopete Groupwise Protocol + gwchatsearchdialog.h - dialog for searching for chatrooms + + Copyright (c) 2005 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#ifndef GWCHATSEARCHDIALOG_H +#define GWCHATSEARCHDIALOG_H + +class GroupWiseAccount; +class GroupWiseChatSearchWidget; + +#include "gwchatrooms.h" + +#include + +class GroupWiseChatSearchDialog : public KDialogBase +{ + Q_OBJECT + public: + GroupWiseChatSearchDialog( GroupWiseAccount * account, QWidget * parent, const char * name ); + ~GroupWiseChatSearchDialog(); + protected: + void populateWidget(); + protected slots: + /* Button handlers */ + void slotPropertiesClicked(); + void slotUpdateClicked(); + /* Manager update handler */ + void slotManagerUpdated(); + void slotGotProperties( const GroupWise::Chatroom & room ); + private: + GroupWiseAccount * m_account; + ChatroomManager * m_manager; + GroupWiseChatSearchWidget * m_widget; +}; +#endif diff --git a/kopete/protocols/groupwise/ui/gwchatsearchwidget.ui b/kopete/protocols/groupwise/ui/gwchatsearchwidget.ui new file mode 100644 index 00000000..f6f2014c --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwchatsearchwidget.ui @@ -0,0 +1,116 @@ + +GroupWiseChatSearchWidget + + + GroupWiseChatSearchWidget + + + + 0 + 0 + 579 + 480 + + + + + unnamed + + + + + Chatroom + + + true + + + true + + + + + Owner + + + true + + + true + + + + + Members + + + true + + + true + + + + m_chatrooms + + + true + + + true + + + false + + + + + layout2 + + + + unnamed + + + + m_btnProperties + + + &Properties + + + + + spacer3 + + + Horizontal + + + Expanding + + + + 340 + 20 + + + + + + m_btnRefresh + + + &Refresh + + + + + + + + + klistview.h + kpushbutton.h + + diff --git a/kopete/protocols/groupwise/ui/gwcontactproperties.cpp b/kopete/protocols/groupwise/ui/gwcontactproperties.cpp new file mode 100644 index 00000000..120296ce --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwcontactproperties.cpp @@ -0,0 +1,144 @@ +/* + Kopete Groupwise Protocol + gwcontactproperties.cpp - dialog showing a contact's server side properties + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gwcontact.h" +#include "gwcontactpropswidget.h" +#include "gwprotocol.h" + +#include "gwcontactproperties.h" + +GroupWiseContactProperties::GroupWiseContactProperties( GroupWiseContact * contact, QWidget *parent, const char *name) + : QObject(parent, name) +{ + init(); + // set up the contents of the props widget + m_propsWidget->m_userId->setText( contact->contactId() ); + m_propsWidget->m_status->setText( contact->onlineStatus().description() ); + m_propsWidget->m_displayName->setText( contact->metaContact()->displayName() ); + m_propsWidget->m_firstName->setText( contact->property( Kopete::Global::Properties::self()->firstName() ).value().toString() ); + m_propsWidget->m_lastName->setText( contact->property( Kopete::Global::Properties::self()->lastName() ).value().toString() ); + + setupProperties( contact->serverProperties() ); + m_dialog->show(); +} + +GroupWiseContactProperties::GroupWiseContactProperties( GroupWise::ContactDetails cd, QWidget *parent, const char *name ) + : QObject(parent, name) +{ + init(); + // set up the contents of the props widget + m_propsWidget->m_userId->setText( GroupWiseProtocol::protocol()->dnToDotted( cd.dn ) ); + m_propsWidget->m_status->setText( GroupWiseProtocol::protocol()->gwStatusToKOS( cd.status ).description() ); + m_propsWidget->m_displayName->setText( cd.fullName.isEmpty() ? ( cd.givenName + " " + cd.surname ) : cd.fullName ); + m_propsWidget->m_firstName->setText( cd.givenName ); + m_propsWidget->m_lastName->setText( cd.surname ); + + setupProperties( cd.properties ); + + m_dialog->show(); +} + +GroupWiseContactProperties::~GroupWiseContactProperties() +{ +} + +void GroupWiseContactProperties::init() +{ + m_dialog = new KDialogBase( ::qt_cast( parent() ), "gwcontactpropsdialog", false, i18n( "Contact Properties" ), KDialogBase::Ok ); + m_propsWidget = new GroupWiseContactPropsWidget( m_dialog ); + // set up the context menu and copy action + m_copyAction = KStdAction::copy( this, SLOT( slotCopy() ), 0 ); + connect( m_propsWidget->m_propsView, + SIGNAL( contextMenuRequested( QListViewItem *, const QPoint & , int) ), + SLOT( slotShowContextMenu( QListViewItem *, const QPoint & ) ) ); + + // insert the props widget into the dialog + m_dialog->setMainWidget( m_propsWidget ); +} + +void GroupWiseContactProperties::setupProperties( QMap< QString, QString > serverProps ) +{ + m_propsWidget->m_propsView->header()->hide(); + QMap< QString, QString >::Iterator it; + QMap< QString, QString >::Iterator end = serverProps.end(); + for ( it = serverProps.begin(); it != end; ++it ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " adding property: " << it.key() << ", " << it.data() << endl; + QString key = it.key(); + QString localised; + if ( key == "telephoneNumber" ) + localised = i18n( "Telephone Number" ); + else if ( key == "OU" ) + localised = i18n( "Department" ); + else if ( key == "L" ) + localised = i18n( "Location" ); + else if ( key == "mailstop" ) + localised = i18n( "Mailstop" ); + else if ( key == "personalTitle" ) + localised = i18n( "Personal Title" ); + else if ( key == "title" ) + localised = i18n( "Title" ); + else if ( key == "Internet EMail Address" ) + localised = i18n( "Email Address" ); + else + localised = key; + + new KListViewItem( m_propsWidget->m_propsView, localised, it.data() ); + } +} + +void GroupWiseContactProperties::slotShowContextMenu( QListViewItem * item, const QPoint & pos ) +{ + if ( item ) + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "for item " << item->text(0) << ", " << item->text(1) << endl; + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "no selected item" << endl; + QPopupMenu * popupMenu = new QPopupMenu( m_propsWidget->m_propsView ); + m_copyAction->plug( popupMenu ); + popupMenu->exec( pos ); +} + +void GroupWiseContactProperties::slotCopy() +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + if ( m_propsWidget->m_propsView->currentItem() ) + { + QClipboard *cb = kapp->clipboard(); + cb->setText( m_propsWidget->m_propsView->currentItem()->text( 1 ) ); + } +} +#include "gwcontactproperties.moc" diff --git a/kopete/protocols/groupwise/ui/gwcontactproperties.h b/kopete/protocols/groupwise/ui/gwcontactproperties.h new file mode 100644 index 00000000..5684cf2a --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwcontactproperties.h @@ -0,0 +1,60 @@ +/* + Kopete Groupwise Protocol + gwcontactproperties.h - dialog showing a contact's server side properties + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#ifndef GROUPWISECONTACTPROPERTIES_H +#define GROUPWISECONTACTPROPERTIES_H + + +#include + +class GroupWiseContactPropsWidget; +class KDialogBase; +class QListViewItem; +class KAction; + +/** +Logic, wrapping UI, for displaying contact properties + +@author SUSE AG +*/ +class GroupWiseContactProperties : public QObject +{ +Q_OBJECT +public: + /** + * Display properties given a GroupWiseContact + */ + GroupWiseContactProperties( GroupWiseContact * contact, QWidget *parent, const char *name ); + /** + * Display properties given a GroupWise::ContactDetails + */ + GroupWiseContactProperties( GroupWise::ContactDetails contactDetails, QWidget *parent = 0, const char *name = 0 ); + ~GroupWiseContactProperties(); +protected: + void setupProperties( QMap< QString, QString > serverProps ); + void init(); +protected slots: + void slotShowContextMenu( QListViewItem *, const QPoint & ); + void slotCopy(); +private: + GroupWiseContactPropsWidget * m_propsWidget; + KAction * m_copyAction; + KDialogBase * m_dialog; +}; + +#endif diff --git a/kopete/protocols/groupwise/ui/gwcontactpropswidget.ui b/kopete/protocols/groupwise/ui/gwcontactpropswidget.ui new file mode 100644 index 00000000..3aece991 --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwcontactpropswidget.ui @@ -0,0 +1,211 @@ + +GroupWiseContactPropsWidget + + + GroupWiseContactPropsWidget + + + + 0 + 0 + 373 + 444 + + + + + unnamed + + + + m_userId + + + + 5 + 5 + 3 + 0 + + + + USER_ID + + + + + line4 + + + HLine + + + Sunken + + + Horizontal + + + + + layout15 + + + + unnamed + + + + m_lastName + + + + + + true + + + Change the display name used for this contact + + + + + textLabel14 + + + Status: + + + + + m_displayName + + + + + + false + + + Change the display name used for this contact + + + + + m_status + + + USER_STATUS + + + + + textLabel10 + + + First name: + + + + + lbl_displayName + + + &Display name: + + + m_displayName + + + + + m_firstName + + + + + + true + + + Change the display name used for this contact + + + + + textLabel11 + + + Last name: + + + + + + + line1_2 + + + HLine + + + Sunken + + + Horizontal + + + + + textLabel15 + + + Additional properties: + + + + + + Property + + + true + + + true + + + + + Value + + + true + + + true + + + + m_propsView + + + true + + + AllColumns + + + true + + + false + + + + + + + klistview.h + + diff --git a/kopete/protocols/groupwise/ui/gwcontactsearch.ui b/kopete/protocols/groupwise/ui/gwcontactsearch.ui new file mode 100644 index 00000000..868072ce --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwcontactsearch.ui @@ -0,0 +1,386 @@ + +GroupWiseContactSearchWidget + + + GroupWiseContactSearchWidget + + + + 0 + 0 + 435 + 410 + + + + Search GroupWise Messenger + + + + unnamed + + + + layout13 + + + + unnamed + + + + textLabel1 + + + &First name + + + m_firstName + + + + + textLabel3 + + + &User ID + + + m_userId + + + + + textLabel4 + + + &Title + + + m_title + + + + + m_userId + + + + + m_firstName + + + + + textLabel5 + + + &Department + + + m_dept + + + + + + contains + + + + + begins with + + + + + equals + + + + m_userIdOperation + + + + + + contains + + + + + begins with + + + + + equals + + + + m_firstNameOperation + + + + + m_dept + + + + + + contains + + + + + begins with + + + + + equals + + + + m_lastNameOperation + + + + + textLabel2 + + + Last &name + + + m_lastName + + + + + m_clear + + + Cl&ear + + + + + + contains + + + + + begins with + + + + + equals + + + + m_deptOperation + + + + + m_title + + + + + m_lastName + + + + + m_search + + + &Search + + + true + + + + + + contains + + + + + begins with + + + + + equals + + + + m_titleOperation + + + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + textLabel9 + + + &Results: + + + m_results + + + + + layout12 + + + + unnamed + + + + + Status + + + true + + + true + + + + + First Name + + + true + + + true + + + + + Last Name + + + true + + + true + + + + + User ID + + + true + + + true + + + + m_results + + + AutoOneFit + + + AllColumns + + + + + layout8 + + + + unnamed + + + + m_details + + + true + + + Detai&ls + + + + + spacer6 + + + Vertical + + + Expanding + + + + 20 + 141 + + + + + + + + + + m_matchCount + + + 0 matching users found + + + + + + m_firstName + m_lastNameOperation + m_lastName + m_userIdOperation + m_userId + m_titleOperation + m_title + m_deptOperation + m_dept + m_search + m_clear + m_results + m_details + m_firstNameOperation + + + diff --git a/kopete/protocols/groupwise/ui/gwcustomstatusedit.ui b/kopete/protocols/groupwise/ui/gwcustomstatusedit.ui new file mode 100644 index 00000000..43a9af15 --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwcustomstatusedit.ui @@ -0,0 +1,92 @@ + +GroupWiseCustomStatusEdit + + + GroupWiseCustomStatusEdit + + + + 0 + 0 + 260 + 113 + + + + + + + + unnamed + + + + layout3 + + + + unnamed + + + + m_name + + + LineEditPanel + + + Sunken + + + + + + + + m_cmbStatus + + + + + textLabel3 + + + &Status: + + + comboBox1 + + + + + textLabel2 + + + Awa&y message: + + + lineEdit2 + + + + + textLabel1 + + + &Name: + + + lineEdit1 + + + + + m_awayMessage + + + + + + + + diff --git a/kopete/protocols/groupwise/ui/gwcustomstatuswidget.ui b/kopete/protocols/groupwise/ui/gwcustomstatuswidget.ui new file mode 100644 index 00000000..8c69aa76 --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwcustomstatuswidget.ui @@ -0,0 +1,112 @@ + +GroupWiseCustomStatusWidget + + + GroupWiseCustomStatusWidget + + + + 0 + 0 + 343 + 215 + + + + + + + + unnamed + + + + + Name + + + true + + + true + + + + + Auto Reply + + + true + + + true + + + + m_list + + + true + + + true + + + + + layout2 + + + + unnamed + + + + m_btnAdd + + + &Add + + + + + m_btnEdit + + + &Edit + + + + + m_btnRemove + + + &Remove + + + + + spacer1 + + + Vertical + + + Expanding + + + + 20 + 41 + + + + + + + + + + klistview.h + + diff --git a/kopete/protocols/groupwise/ui/gweditaccountwidget.cpp b/kopete/protocols/groupwise/ui/gweditaccountwidget.cpp new file mode 100644 index 00000000..87468ccf --- /dev/null +++ b/kopete/protocols/groupwise/ui/gweditaccountwidget.cpp @@ -0,0 +1,136 @@ +/* + Kopete GroupWise Protocol + gweditaccountwidget.cpp - widget for adding or editing GroupWise accounts + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Testbed + Copyright (c) 2003 by Will Stephenson + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "kopetepasswordedaccount.h" +#include "kopetepasswordwidget.h" + +#include "gwaccountpreferences.h" +#include "gwaccount.h" +#include "gwerror.h" +#include "gwprotocol.h" + +#include "gweditaccountwidget.h" + +GroupWiseEditAccountWidget::GroupWiseEditAccountWidget( QWidget* parent, Kopete::Account* theAccount) +: QWidget( parent ), KopeteEditAccountWidget( theAccount ) +{ + kdDebug(GROUPWISE_DEBUG_GLOBAL) << k_funcinfo << endl; + m_layout = new QVBoxLayout( this ); + m_preferencesDialog = new GroupWiseAccountPreferences( this ); + m_layout->addWidget( m_preferencesDialog ); + connect( m_preferencesDialog->m_password, SIGNAL( changed() ), this, SLOT( configChanged() ) ); + connect( m_preferencesDialog->m_server, SIGNAL( textChanged( const QString & ) ), this, SLOT( configChanged() ) ); + connect( m_preferencesDialog->m_port, SIGNAL( valueChanged( int ) ), this, SLOT( configChanged() ) ); + if ( account() ) + reOpen(); + else + { + // look for a default server and port setting + KConfig *config = kapp->config(); + config->setGroup("GroupWise Messenger"); + m_preferencesDialog->m_server->setText( config->readEntry( "DefaultServer" ) ); + m_preferencesDialog->m_port->setValue( config->readNumEntry( "DefaultPort", 8300 ) ); + } + QWidget::setTabOrder( m_preferencesDialog->m_userId, m_preferencesDialog->m_password->mRemembered ); + QWidget::setTabOrder( m_preferencesDialog->m_password->mRemembered, m_preferencesDialog->m_password->mPassword ); + QWidget::setTabOrder( m_preferencesDialog->m_password->mPassword, m_preferencesDialog->m_autoConnect ); + +} + +GroupWiseEditAccountWidget::~GroupWiseEditAccountWidget() +{ +} + +GroupWiseAccount *GroupWiseEditAccountWidget::account () +{ + Q_ASSERT( KopeteEditAccountWidget::account() ); + return dynamic_cast< GroupWiseAccount *>( KopeteEditAccountWidget::account() ); +} + +void GroupWiseEditAccountWidget::reOpen() +{ + kdDebug(GROUPWISE_DEBUG_GLOBAL) << k_funcinfo << endl; + + m_preferencesDialog->m_password->load( &account()->password () ); + // Kopete at least <=0.90 doesn't support changing account IDs + m_preferencesDialog->m_userId->setDisabled( true ); + m_preferencesDialog->m_userId->setText( account()->accountId() ); + m_preferencesDialog->m_password->load( &account()->password() ); + m_preferencesDialog->m_server->setText( account()->configGroup()->readEntry( "Server") ); + m_preferencesDialog->m_port->setValue( account()->configGroup()->readNumEntry( "Port" ) ); + m_preferencesDialog->m_autoConnect->setChecked( account()->excludeConnect() ); + m_preferencesDialog->m_alwaysAccept->setChecked( account()->configGroup()->readBoolEntry( "AlwaysAcceptInvitations" ) ); +} + +Kopete::Account* GroupWiseEditAccountWidget::apply() +{ + kdDebug(GROUPWISE_DEBUG_GLOBAL) << k_funcinfo << endl; + + if ( !account() ) + setAccount( new GroupWiseAccount( GroupWiseProtocol::protocol(), m_preferencesDialog->m_userId->text() ) ); + + if(account()->isConnected()) + { + KMessageBox::information(this, + i18n("The changes you just made will take effect next time you log in with GroupWise."), + i18n("GroupWise Settings Changed While Signed In")); + } + + writeConfig(); + + return account(); +} + +bool GroupWiseEditAccountWidget::validateData() +{ + return !( m_preferencesDialog->m_userId->text().isEmpty() || m_preferencesDialog->m_server->text().isEmpty() ); +} + +void GroupWiseEditAccountWidget::writeConfig() +{ + kdDebug(GROUPWISE_DEBUG_GLOBAL) << k_funcinfo << endl; + account()->configGroup()->writeEntry( "Server", m_preferencesDialog->m_server->text() ); + account()->configGroup()->writeEntry( "Port", QString::number( m_preferencesDialog->m_port->value() ) ); + account()->configGroup()->writeEntry( "AlwaysAcceptInvitations", + m_preferencesDialog->m_alwaysAccept->isChecked() ? "true" : "false" ); + + account()->setExcludeConnect( m_preferencesDialog->m_autoConnect->isChecked() ); + m_preferencesDialog->m_password->save( &account()->password() ); + settings_changed = false; +} + +void GroupWiseEditAccountWidget::configChanged () +{ + settings_changed = true; +} + +#include "gweditaccountwidget.moc" diff --git a/kopete/protocols/groupwise/ui/gweditaccountwidget.h b/kopete/protocols/groupwise/ui/gweditaccountwidget.h new file mode 100644 index 00000000..27c54ee2 --- /dev/null +++ b/kopete/protocols/groupwise/ui/gweditaccountwidget.h @@ -0,0 +1,64 @@ +/* + Kopete GroupWise Protocol + gweditaccountwidget.h - widget for adding or editing GroupWise accounts + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Testbed + Copyright (c) 2003 by Will Stephenson + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#ifndef TESTBEDEDITACCOUNTWIDGET_H +#define TESTBEDEDITACCOUNTWIDGET_H + +#include +#include + +class QVBoxLayout; +namespace Kopete { class Account; } +class GroupWiseAccountPreferences; + +/** + * A widget for editing this protocol's accounts + * @author Will Stephenson +*/ +class GroupWiseEditAccountWidget : public QWidget, public KopeteEditAccountWidget +{ +Q_OBJECT +public: + GroupWiseEditAccountWidget( QWidget* parent, Kopete::Account* account); + + ~GroupWiseEditAccountWidget(); + + /** + * Make an account out of the entered data + */ + virtual Kopete::Account* apply(); + /** + * Is the data correct? + */ + virtual bool validateData(); +protected slots: + void configChanged(); +protected: + bool settings_changed; + GroupWiseAccount * account(); + void reOpen(); + void writeConfig(); + Kopete::Account *m_account; + QVBoxLayout *m_layout; + GroupWiseAccountPreferences *m_preferencesDialog; +}; + +#endif diff --git a/kopete/protocols/groupwise/ui/gwprivacy.ui b/kopete/protocols/groupwise/ui/gwprivacy.ui new file mode 100644 index 00000000..8b09cab6 --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwprivacy.ui @@ -0,0 +1,193 @@ + +GroupWisePrivacyWidget + + + GroupWisePrivacyWidget + + + + 0 + 0 + 463 + 314 + + + + + unnamed + + + + textLabel1 + + + Who can see my online status and send me messages: + + + + + layout11 + + + + unnamed + + + + layout9 + + + + unnamed + + + + textLabel2 + + + A&llowed + + + m_allowList + + + + + m_allowList + + + + + + + layout8 + + + + unnamed + + + + spacer9 + + + Vertical + + + Expanding + + + + 20 + 21 + + + + + + m_btnBlock + + + &Block >> + + + + + m_btnAllow + + + << Allo&w + + + + + spacer1 + + + Vertical + + + Expanding + + + + 20 + 53 + + + + + + m_btnAdd + + + A&dd... + + + + + m_btnRemove + + + &Remove + + + + + spacer2 + + + Vertical + + + Expanding + + + + 20 + 52 + + + + + + + + layout10 + + + + unnamed + + + + textLabel3 + + + Bloc&ked + + + m_denyList + + + + + m_denyList + + + + + + + + + m_status + + + + + + + + + diff --git a/kopete/protocols/groupwise/ui/gwprivacydialog.cpp b/kopete/protocols/groupwise/ui/gwprivacydialog.cpp new file mode 100644 index 00000000..d46daf4a --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwprivacydialog.cpp @@ -0,0 +1,349 @@ +/* + Kopete Groupwise Protocol + gwprivacydialog.cpp - dialog summarising, and editing, the user's privacy settings + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "client.h" +#include "gwaccount.h" +#include "gwprivacy.h" +#include "gwprotocol.h" +#include "gwsearch.h" +#include "privacymanager.h" +#include "userdetailsmanager.h" +#include "gwprivacydialog.h" + +class PrivacyLBI : public QListBoxPixmap +{ +public: + PrivacyLBI( QListBox * listBox, const QPixmap & pixmap, const QString & text, const QString & dn ) + : QListBoxPixmap( listBox, pixmap, text ), m_dn( dn ) + { + } + QString dn() { return m_dn; } +private: + QString m_dn; +}; + +GroupWisePrivacyDialog::GroupWisePrivacyDialog( GroupWiseAccount * account, QWidget *parent, const char *name ) + : KDialogBase( parent, name, false, i18n( "Account specific privacy settings", "Manage Privacy for %1" ).arg( account->accountId() ), + KDialogBase::Ok|KDialogBase::Apply|KDialogBase::Cancel, Ok, true ), m_account( account ), m_dirty( false ), m_searchDlg(0) +{ + m_privacy = new GroupWisePrivacyWidget( this ); + setMainWidget( m_privacy ); + PrivacyManager * mgr = m_account->client()->privacyManager(); + // populate the widget; + // admin lock + if ( mgr->isPrivacyLocked() ) + { + m_privacy->m_status->setText( i18n( "Privacy settings have been administratively locked" ) ); + disableWidgets(); + } + + populateWidgets(); + + m_privacy->m_allowList->setSelectionMode( QListBox::Extended ); + m_privacy->m_denyList->setSelectionMode( QListBox::Extended ); + + connect( m_privacy->m_btnAllow, SIGNAL( clicked() ), SLOT( slotAllowClicked() ) ); + connect( m_privacy->m_btnBlock, SIGNAL( clicked() ), SLOT( slotBlockClicked() ) ); + connect( m_privacy->m_btnAdd, SIGNAL( clicked() ), SLOT( slotAddClicked() ) ); + connect( m_privacy->m_btnRemove, SIGNAL( clicked() ), SLOT( slotRemoveClicked() ) ); + connect( m_privacy->m_allowList, SIGNAL( selectionChanged() ), SLOT( slotAllowListClicked() ) ); + connect( m_privacy->m_denyList, SIGNAL( selectionChanged() ), SLOT( slotDenyListClicked() ) ); + connect( mgr, SIGNAL( privacyChanged( const QString &, bool ) ), SLOT( slotPrivacyChanged() ) ); + m_privacy->m_btnAdd->setEnabled( true ); + m_privacy->m_btnAllow->setEnabled( false ); + m_privacy->m_btnBlock->setEnabled( false ); + m_privacy->m_btnRemove->setEnabled( false ); + +/* showButtonOK( true ); + showButtonApply( true ); + showButtonCancel( true ); + */ + show(); +} + +GroupWisePrivacyDialog::~GroupWisePrivacyDialog() +{ +} + +void GroupWisePrivacyDialog::populateWidgets() +{ + m_dirty = false; + PrivacyManager * mgr = m_account->client()->privacyManager(); + + // default policy + QString defaultPolicyText = i18n( "" ); + if ( mgr->defaultAllow() ) + m_defaultPolicy = new QListBoxText( m_privacy->m_allowList, defaultPolicyText ); + else + m_defaultPolicy = new QListBoxText( m_privacy->m_denyList, defaultPolicyText ); + + QPixmap icon = m_account->protocol()->groupwiseAvailable.iconFor( m_account ); + + // allow list + QStringList allowList = mgr->allowList(); + QStringList::Iterator end = allowList.end(); + for ( QStringList::Iterator it = allowList.begin(); it != end; ++it ) + { + GroupWise::ContactDetails cd = m_account->client()->userDetailsManager()->details( *it ); + if ( cd.fullName.isEmpty() ) + cd.fullName = cd.givenName + " " + cd.surname; + new PrivacyLBI( m_privacy->m_allowList, icon, cd.fullName, *it ); + } + // deny list + QStringList denyList = mgr->denyList(); + end = denyList.end(); + for ( QStringList::Iterator it = denyList.begin(); it != end; ++it ) + { + GroupWise::ContactDetails cd = m_account->client()->userDetailsManager()->details( *it ); + if ( cd.fullName.isEmpty() ) + cd.fullName = cd.givenName + " " + cd.surname; + new PrivacyLBI( m_privacy->m_denyList, icon, cd.fullName, *it ); + } + updateButtonState(); +} + +void GroupWisePrivacyDialog::disableWidgets() +{ + if ( m_privacy ) + { + m_privacy->m_btnAllow->setEnabled( false ); + m_privacy->m_btnBlock->setEnabled( false ); + m_privacy->m_btnAdd->setEnabled( false ); + m_privacy->m_btnRemove->setEnabled( false ); + } +} + +void GroupWisePrivacyDialog::slotBlockClicked() +{ + // take each selected item from the allow list and add it to the deny list + // start at the bottom, as we are changing the contents of the list as we go + for( int i = m_privacy->m_allowList->count() - 1; i >= 0 ; --i ) + { + if ( m_privacy->m_allowList->isSelected( i ) ) + { + m_dirty = true; + QListBoxItem * lbi = m_privacy->m_allowList->item( i ); + m_privacy->m_allowList->takeItem( lbi ); + m_privacy->m_denyList->insertItem( lbi ); + } + } + updateButtonState(); +} + +void GroupWisePrivacyDialog::slotAllowClicked() +{ + // take each selected item from the deny list and add it to the allow list + for( int i = m_privacy->m_denyList->count() - 1; i >= 0 ; --i ) + { + if ( m_privacy->m_denyList->isSelected( i ) ) + { + m_dirty = true; + QListBoxItem * lbi = m_privacy->m_denyList->item( i ); + m_privacy->m_denyList->takeItem( lbi ); + m_privacy->m_allowList->insertItem( lbi ); + } + } + updateButtonState(); +} + +void GroupWisePrivacyDialog::slotAddClicked() +{ + if ( !m_searchDlg ) + { + m_searchDlg = new KDialogBase( this, "privacysearchdialog", false, + i18n( "Search for Contact to Block" ), + KDialogBase::Ok|KDialogBase::Cancel ); + m_search = new GroupWiseContactSearch( m_account, QListView::Multi, false, m_searchDlg, "privacysearchwidget" ); + m_searchDlg->setMainWidget( m_search ); + connect( m_searchDlg, SIGNAL( okClicked() ), SLOT( slotSearchedForUsers() ) ); + connect( m_search, SIGNAL( selectionValidates( bool ) ), m_searchDlg, SLOT( enableButtonOK( bool ) ) ); + m_searchDlg->enableButtonOK( false ); + } + m_searchDlg->show(); +} + +void GroupWisePrivacyDialog::slotSearchedForUsers() +{ + // create an item for each result, in the block list + QValueList< ContactDetails > selected = m_search->selectedResults(); + QValueList< ContactDetails >::Iterator it = selected.begin(); + const QValueList< ContactDetails >::Iterator end = selected.end(); + QPixmap icon = m_account->protocol()->groupwiseAvailable.iconFor( m_account ); + for ( ; it != end; ++it ) + { + m_dirty = true; + m_account->client()->userDetailsManager()->addDetails( *it ); + if ( (*it).fullName.isEmpty() ) + (*it).fullName = (*it).givenName + " " + (*it).surname; + new PrivacyLBI( m_privacy->m_denyList, icon, (*it).fullName, (*it).dn ); + } +} + +void GroupWisePrivacyDialog::slotRemoveClicked() +{ + // remove any selected items from either list, except the default policy + for( int i = m_privacy->m_denyList->count() - 1; i >= 0 ; --i ) + { + if ( m_privacy->m_denyList->isSelected( i ) ) + { + m_dirty = true; + QListBoxItem * lbi = m_privacy->m_denyList->item( i ); + // can't remove the default policy + if ( lbi == m_defaultPolicy ) + continue; + m_privacy->m_denyList->removeItem( i ); + } + } + for( int i = m_privacy->m_allowList->count() - 1; i >= 0 ; --i ) + { + if ( m_privacy->m_allowList->isSelected( i ) ) + { + m_dirty = true; + QListBoxItem * lbi = m_privacy->m_allowList->item( i ); + // can't remove the default policy + if ( lbi == m_defaultPolicy ) + continue; + m_privacy->m_allowList->removeItem( i ); + } + } + updateButtonState(); +} + +void GroupWisePrivacyDialog::slotAllowListClicked() +{ + // don't get into feedback + disconnect( m_privacy->m_denyList, SIGNAL( selectionChanged() ), this, SLOT( slotDenyListClicked() ) ); + m_privacy->m_denyList->clearSelection(); + connect( m_privacy->m_denyList, SIGNAL( selectionChanged() ), SLOT( slotDenyListClicked() ) ); + bool selected = false; + for( int i = m_privacy->m_allowList->count() - 1; i >= 0 ; --i ) + { + if ( m_privacy->m_allowList->isSelected( i ) ) + { + selected = true; + break; + } + } + m_privacy->m_btnAllow->setEnabled( false ); + m_privacy->m_btnBlock->setEnabled( selected ); + m_privacy->m_btnRemove->setEnabled( selected ); +} + +void GroupWisePrivacyDialog::slotDenyListClicked() +{ + // don't get into feedback + disconnect( m_privacy->m_allowList, SIGNAL( selectionChanged() ), this, SLOT( slotAllowListClicked() ) ); + m_privacy->m_allowList->clearSelection(); + connect( m_privacy->m_allowList, SIGNAL( selectionChanged() ), SLOT( slotAllowListClicked() ) ); + bool selected = false; + for( int i = m_privacy->m_denyList->count() - 1; i >= 0 ; --i ) + { + if ( m_privacy->m_denyList->isSelected( i ) ) + { + selected = true; + break; + } + } + m_privacy->m_btnAllow->setEnabled( selected ); + m_privacy->m_btnBlock->setEnabled( false ); + m_privacy->m_btnRemove->setEnabled( selected ); +} + +void GroupWisePrivacyDialog::slotPrivacyChanged() +{ + m_privacy->m_denyList->clear(); + m_privacy->m_allowList->clear(); + populateWidgets(); +} + +void GroupWisePrivacyDialog::updateButtonState() +{ + enableButtonApply( m_dirty ); +} + +void GroupWisePrivacyDialog::slotOk() +{ + if ( m_dirty ) + commitChanges(); + KDialogBase::slotOk(); +} + +void GroupWisePrivacyDialog::slotApply() +{ + if ( m_dirty ) + { + commitChanges(); + m_dirty = false; + updateButtonState(); + } + KDialogBase::slotApply(); +} + +void GroupWisePrivacyDialog::commitChanges() +{ + if ( m_account->isConnected() ) + { + bool defaultDeny = false; + QStringList denyList; + QStringList allowList; + // pass on our current allow, deny and default policy to the PrivacyManager + for( int i = 0; i < (int)m_privacy->m_denyList->count(); ++i ) + { + if ( m_privacy->m_denyList->item( i ) == m_defaultPolicy ) + defaultDeny = true; + else + { + PrivacyLBI * lbi = static_cast( m_privacy->m_denyList->item( i ) ); + denyList.append( lbi->dn() ); + } + } + for( int i = 0; i < (int)m_privacy->m_allowList->count(); ++i ) + { + if ( m_privacy->m_allowList->item( i ) == m_defaultPolicy ) + defaultDeny = false; + else + { + PrivacyLBI * lbi = static_cast( m_privacy->m_allowList->item( i ) ); + allowList.append( lbi->dn() ); + } + } + PrivacyManager * mgr = m_account->client()->privacyManager(); + mgr->setPrivacy( defaultDeny, allowList, denyList ); + } + else + errorNotConnected(); +} + +void GroupWisePrivacyDialog::errorNotConnected() +{ + KMessageBox::queuedMessageBox( this, KMessageBox::Information, + i18n( "You can only change privacy settings while you are logged in to the GroupWise Messenger server." ) , i18n("'%1' Not Logged In").arg( m_account->accountId() ) ); +} + +#include "gwprivacydialog.moc" diff --git a/kopete/protocols/groupwise/ui/gwprivacydialog.h b/kopete/protocols/groupwise/ui/gwprivacydialog.h new file mode 100644 index 00000000..a5467e47 --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwprivacydialog.h @@ -0,0 +1,67 @@ +/* + Kopete Groupwise Protocol + gwprivacydialog.h - dialog summarising, and editing, the user's privacy settings + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#ifndef GWPRIVACYDIALOG_H +#define GWPRIVACYDIALOG_H + +#include + +class GroupWiseAccount; +class GroupWisePrivacyWidget; +class GroupWiseContactSearch; +class QListBoxItem; + +/** +Logic for the UI part managing the allow and deny lists, and the default privacy setting. + +@author Kopete Developers +*/ +class GroupWisePrivacyDialog : public KDialogBase +{ +Q_OBJECT +public: + GroupWisePrivacyDialog( GroupWiseAccount * account, QWidget * parent, const char * name ); + ~GroupWisePrivacyDialog(); +protected: + void commitChanges(); + void errorNotConnected(); + void disableWidgets(); + void populateWidgets(); + void updateButtonState(); +protected slots: + void slotAllowClicked(); + void slotBlockClicked(); + void slotAddClicked(); + void slotRemoveClicked(); + void slotAllowListClicked(); + void slotDenyListClicked(); + void slotPrivacyChanged(); + void slotSearchedForUsers(); + void slotOk(); + void slotApply(); + +private: + GroupWiseAccount * m_account; + GroupWisePrivacyWidget * m_privacy; + GroupWiseContactSearch * m_search; + QListBoxItem * m_defaultPolicy; + bool m_dirty; + KDialogBase * m_searchDlg; +}; + +#endif diff --git a/kopete/protocols/groupwise/ui/gwreceiveinvitationdialog.cpp b/kopete/protocols/groupwise/ui/gwreceiveinvitationdialog.cpp new file mode 100644 index 00000000..5b0b4014 --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwreceiveinvitationdialog.cpp @@ -0,0 +1,77 @@ +/* + Kopete Groupwise Protocol + gwreceiveinvitationdialog.cpp - dialog shown when the user receives an invitation to chat + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include "client.h" +#include "gwaccount.h" +#include "gwcontact.h" +#include "gwerror.h" +#include "gwprotocol.h" +#include "gwshowinvitation.h" + +#include "gwreceiveinvitationdialog.h" + +ReceiveInvitationDialog::ReceiveInvitationDialog( GroupWiseAccount * account, const ConferenceEvent & event, QWidget *parent, const char *name) + : KDialogBase( i18n("Invitation to Conversation"), KDialogBase::Yes|KDialogBase::No, KDialogBase::Yes, KDialogBase::No, parent, name, false ) +{ + m_account = account; + m_guid = event.guid; + connect( this, SIGNAL( yesClicked() ), SLOT( slotYesClicked() ) ); + connect( this, SIGNAL( noClicked() ), SLOT( slotNoClicked() ) ); + + GroupWiseContact * c = account->contactForDN( event.user ); + + m_wid = new ShowInvitationWidget ( this ); + if ( c ) + m_wid->m_contactName->setText( c->metaContact()->displayName() ); + else //something is very wrong + m_wid->m_contactName->setText( event.user ); + + m_wid->m_dateTime->setText( KGlobal::locale()->formatDateTime( event.timeStamp ) ); + m_wid->m_message->setText( QString("%1").arg( event.message ) ); + + setMainWidget( m_wid ); +} + +ReceiveInvitationDialog::~ReceiveInvitationDialog() +{ +} + +void ReceiveInvitationDialog::slotYesClicked() +{ + m_account->client()->joinConference( m_guid ); + // save the state of always accept invitations + QString alwaysAccept = m_wid->cb_dontShowAgain->isChecked() ? "true" : "false"; + m_account->configGroup()->writeEntry( "AlwaysAcceptInvitations", alwaysAccept ); + deleteLater(); +} + +void ReceiveInvitationDialog::slotNoClicked() +{ + m_account->client()->rejectInvitation( m_guid ); + deleteLater(); +} + +#include "gwreceiveinvitationdialog.moc" diff --git a/kopete/protocols/groupwise/ui/gwreceiveinvitationdialog.h b/kopete/protocols/groupwise/ui/gwreceiveinvitationdialog.h new file mode 100644 index 00000000..0486c964 --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwreceiveinvitationdialog.h @@ -0,0 +1,48 @@ +/* + Kopete Groupwise Protocol + gwreceiveinvitationdialog.h - dialog shown when the user receives an invitation to chat + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#ifndef GWRECEIVEINVITATIONDIALOG_H +#define GWRECEIVEINVITATIONDIALOG_H + +#include + +class ShowInvitationWidget; + +/** +This is the dialog that is shown when you receive an invitation to chat. + +@author SUSE AG +*/ +class ReceiveInvitationDialog : public KDialogBase +{ +Q_OBJECT +public: + ReceiveInvitationDialog( GroupWiseAccount * account, const ConferenceEvent & event, QWidget *parent, const char *name ); + ~ReceiveInvitationDialog(); +signals: + void invitationAccepted( bool, const GroupWise::ConferenceGuid & guid ); +protected slots: + void slotYesClicked(); + void slotNoClicked(); +private: + GroupWiseAccount * m_account; + ConferenceGuid m_guid; // the conference we were invited to join. + ShowInvitationWidget * m_wid; +}; + +#endif diff --git a/kopete/protocols/groupwise/ui/gwsearch.cpp b/kopete/protocols/groupwise/ui/gwsearch.cpp new file mode 100644 index 00000000..1c80e3eb --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwsearch.cpp @@ -0,0 +1,281 @@ +/* + Kopete Groupwise Protocol + gwsearch.cpp - logic for server side search widget + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include +#include +#include +//#include + +#include +#include + +#include + +#include "client.h" +#include "gwaccount.h" +#include "gwcontact.h" +#include "gwcontactproperties.h" +#include "gwprotocol.h" +#include "tasks/searchusertask.h" + +#include "gwsearch.h" + +class GWSearchResultsLVI : public QListViewItem +{ +public: + GWSearchResultsLVI( QListView * parent, GroupWise::ContactDetails details, int status, const QPixmap & statusPM/*, const QString & userId */) + : QListViewItem( parent, QString::null, details.givenName, details.surname, GroupWiseProtocol::protocol()->dnToDotted( details.dn ) ), m_details( details ), m_status( status ) + { + setPixmap( 0, statusPM ); + } + QString key( int column, bool ascending ) const + { + if ( column == 0 ) + return QString::number( 99 - m_status ); + else + return QListViewItem::key( column, ascending ); + } + GroupWise::ContactDetails m_details; + int m_status; +}; + +GroupWiseContactSearch::GroupWiseContactSearch( GroupWiseAccount * account, QListView::SelectionMode mode, bool onlineOnly, QWidget *parent, const char *name) + : GroupWiseContactSearchWidget(parent, name), m_account( account ), m_onlineOnly( onlineOnly ) +{ + m_results->setSelectionMode( mode ); + m_results->setAllColumnsShowFocus( true ); + connect( m_details, SIGNAL( clicked() ), SLOT( slotShowDetails() ) ); + connect( m_results, SIGNAL( selectionChanged() ), SLOT( slotValidateSelection() ) ); + connect( m_search, SIGNAL( clicked() ), SLOT( slotDoSearch() ) ); + connect( m_clear, SIGNAL( clicked() ), SLOT( slotClear() ) ); +} + + +GroupWiseContactSearch::~GroupWiseContactSearch() +{ +} + +void GroupWiseContactSearch::slotClear() +{ + m_firstName->clear(); + m_lastName->clear(); + m_userId->clear(); + m_title->clear(); + m_dept->clear(); +} + +void GroupWiseContactSearch::slotDoSearch() +{ + // build a query + QValueList< GroupWise::UserSearchQueryTerm > searchTerms; + if ( !m_firstName->text().isEmpty() ) + { + GroupWise::UserSearchQueryTerm arg; + arg.argument = m_firstName->text(); + arg.field = "Given Name"; + arg.operation = searchOperation( m_firstNameOperation->currentItem() ); + searchTerms.append( arg ); + } + if ( !m_lastName->text().isEmpty() ) + { + GroupWise::UserSearchQueryTerm arg; + arg.argument = m_lastName->text(); + arg.field = "Surname"; + arg.operation = searchOperation( m_lastNameOperation->currentItem() ); + searchTerms.append( arg ); + } + if ( !m_userId->text().isEmpty() ) + { + GroupWise::UserSearchQueryTerm arg; + arg.argument = m_userId->text(); + arg.field = NM_A_SZ_USERID; + arg.operation = searchOperation( m_userIdOperation->currentItem() ); + searchTerms.append( arg ); + } + if ( !m_title->text().isEmpty() ) + { + GroupWise::UserSearchQueryTerm arg; + arg.argument = m_title->text(); + arg.field = NM_A_SZ_TITLE; + arg.operation = searchOperation( m_titleOperation->currentItem() ); + searchTerms.append( arg ); + } + if ( !m_dept->text().isEmpty() ) + { + GroupWise::UserSearchQueryTerm arg; + arg.argument = m_dept->text(); + arg.field = NM_A_SZ_DEPARTMENT; + arg.operation = searchOperation( m_deptOperation->currentItem() ); + searchTerms.append( arg ); + } + if ( !searchTerms.isEmpty() ) + { + // start a search task + SearchUserTask * st = new SearchUserTask( m_account->client()->rootTask() ); + st->search( searchTerms ); + connect( st, SIGNAL( finished() ), SLOT( slotGotSearchResults() ) ); + st->go( true ); + m_matchCount->setText( i18n( "Searching" ) ); + } + else + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "no query to perform!" << endl; + } + +} + +void GroupWiseContactSearch::slotShowDetails() +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + // get the first selected result + QValueList< ContactDetails > selected = selectedResults(); + if ( selected.count() ) + { + // if they are already in our contact list, show that version + ContactDetails dt = selected.first(); + GroupWiseContact * c = m_account->contactForDN( dt.dn ); + if ( c ) + new GroupWiseContactProperties( c, this, "gwcontactproperties" ); + else + new GroupWiseContactProperties( dt, this, "gwcontactproperties" ); + } +} + +void GroupWiseContactSearch::slotGotSearchResults() +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + SearchUserTask * st = ( SearchUserTask * ) sender(); + m_searchResults = st->results(); + + m_matchCount->setText( i18n( "1 matching user found", "%n matching users found", m_searchResults.count() ) ); + + m_results->clear(); + QValueList< GroupWise::ContactDetails >::Iterator it = m_searchResults.begin(); + QValueList< GroupWise::ContactDetails >::Iterator end = m_searchResults.end(); + for ( ; it != end; ++it ) + { + // it's necessary to change the status used for the LVIs, + // because the status returned by the server does not go linearly from Unknown to Available + // which is no use for us to sort on, and converting it to a Kopete::OnlineStatus is overkill here + int statusOrdered; + switch ( (*it).status ) + { + case 0: //unknown + statusOrdered = 0; + break; + case 1: //offline + statusOrdered = 1; + break; + case 2: //online + statusOrdered = 5; + break; + case 3: //busy + statusOrdered = 2; + break; + case 4: // away + statusOrdered = 3; + break; + case 5: //idle + statusOrdered = 4; + break; + default: + statusOrdered = 0; + break; + } + + new GWSearchResultsLVI( m_results, *it, statusOrdered, + m_account->protocol()->gwStatusToKOS( (*it).status ).iconFor( m_account ) ); + } + // if there was only one hit, select it + if ( m_results->childCount() == 1 ) + m_results->firstChild()->setSelected( true ); + + slotValidateSelection(); +} + +QValueList< GroupWise::ContactDetails > GroupWiseContactSearch::selectedResults() +{ + QValueList< GroupWise::ContactDetails > lst; + QListViewItemIterator it( m_results ); + while ( it.current() ) { + if ( it.current()->isSelected() ) + lst.append( static_cast< GWSearchResultsLVI * >( it.current() )->m_details ); + ++it; + } + return lst; +} +// GWSearchResultsLVI * selection = static_cast< GWSearchResultsLVI * >( m_results->selectedItem() ); +// contactId = selection->m_dn; +// if ( displayName.isEmpty() ) +// displayName = selection->text( 1 ) + " " + selection->text( 3 ); + + +unsigned char GroupWiseContactSearch::searchOperation( int comboIndex ) +{ + switch ( comboIndex ) + { + case 0: + return NMFIELD_METHOD_SEARCH; + case 1: + return NMFIELD_METHOD_MATCHBEGIN; + case 2: + return NMFIELD_METHOD_EQUAL; + } + return NMFIELD_METHOD_IGNORE; +} + +void GroupWiseContactSearch::slotValidateSelection() +{ + bool ok = false; + // if we only allow online contacts to be selected + if ( m_onlineOnly ) + { + // check that one of the selected items is online + QListViewItemIterator it( m_results ); + while ( it.current() ) + { + if ( it.current()->isSelected() && + !( static_cast< GWSearchResultsLVI * >( it.current() )->m_status == 1 ) ) + { + ok = true; + break; + } + ++it; + } + } + else + { + // check that at least one item is selected + QListViewItemIterator it( m_results ); + while ( it.current() ) + { + if ( it.current()->isSelected() ) + { + ok = true; + break; + } + ++it; + } + } + + emit selectionValidates( ok ); +} + +#include "gwsearch.moc" diff --git a/kopete/protocols/groupwise/ui/gwsearch.h b/kopete/protocols/groupwise/ui/gwsearch.h new file mode 100644 index 00000000..83ee205e --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwsearch.h @@ -0,0 +1,58 @@ +/* + Kopete Groupwise Protocol + gwsearch.h - logic for server side search widget + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * This library 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. * + * * + ************************************************************************* +*/ + +#ifndef GWSEARCH_H +#define GWSEARCH_H +#include +#include "gwcontactsearch.h" + +class GroupWiseAccount; +class GroupWiseContactProperties; +class GroupWiseContactSearchWidget; + +/** +Logic for searching for and displaying users and chat rooms using a GroupWiseContactSearchWidget + +@author SUSE Linux Products GmbH +*/ +class GroupWiseContactSearch : public GroupWiseContactSearchWidget +{ +Q_OBJECT +public: + GroupWiseContactSearch( GroupWiseAccount * account, QListView::SelectionMode mode, bool onlineOnly, + QWidget *parent = 0, const char *name = 0); + ~GroupWiseContactSearch(); + QValueList< GroupWise::ContactDetails > selectedResults(); +signals: + void selectionValidates( bool ); +protected: + unsigned char searchOperation( int comboIndex ); +protected slots: + void slotClear(); + void slotDoSearch(); + void slotGotSearchResults(); + // shows a GroupWiseContactProperties for the selected contact. Dialog's parent is this instance + void slotShowDetails(); + void slotValidateSelection(); +private: + QValueList< GroupWise::ContactDetails > m_searchResults; + GroupWiseAccount * m_account; + bool m_onlineOnly; +}; + +#endif diff --git a/kopete/protocols/groupwise/ui/gwshowinvitation.ui b/kopete/protocols/groupwise/ui/gwshowinvitation.ui new file mode 100644 index 00000000..28dd1a7d --- /dev/null +++ b/kopete/protocols/groupwise/ui/gwshowinvitation.ui @@ -0,0 +1,135 @@ + +ShowInvitationWidget + + + ShowInvitationWidget + + + + 0 + 0 + 495 + 204 + + + + + + + + unnamed + + + + layout13 + + + + unnamed + + + + textLabel1 + + + <p align="right">From:</p> + + + + + textLabel3 + + + <p align="right">Sent:</p> + + + + + m_dateTime + + + + 5 + 5 + 2 + 0 + + + + INVITE_DATE_TIME + + + + + m_contactName + + + CONTACT_NAME + + + + + + + m_message + + + Panel + + + Sunken + + + INVITE_MESSAGE + + + WordBreak|AlignVCenter + + + + + layout14 + + + + unnamed + + + + textLabel6 + + + Would you like to join the conversation? + + + + + spacer8 + + + Vertical + + + Expanding + + + + 20 + 31 + + + + + + + + cb_dontShowAgain + + + A&lways accept invitations + + + + + + diff --git a/kopete/protocols/irc/Makefile.am b/kopete/protocols/irc/Makefile.am new file mode 100644 index 00000000..7bd37813 --- /dev/null +++ b/kopete/protocols/irc/Makefile.am @@ -0,0 +1,40 @@ +METASOURCES = AUTO + +SUBDIRS = icons libkirc ui +AM_CPPFLAGS = -I$(srcdir)/ui $(KOPETE_INCLUDES) \ + -I./ui \ + -I$(srcdir)/libkirc \ + $(all_includes) +kde_module_LTLIBRARIES = kopete_irc.la + +kopete_irc_la_SOURCES = \ + ircaccount.cpp \ + ircaddcontactpage.cpp \ + ircchannelcontact.cpp \ + irccontact.cpp \ + ircguiclient.cpp \ + ircprotocol.cpp \ + ircservercontact.cpp \ + ircsignalhandler.cpp \ + irctransferhandler.cpp \ + ircusercontact.cpp \ + irccontactmanager.cpp \ + kcodecaction.cpp \ + ksparser.cpp + +kopete_irc_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) +kopete_irc_la_LIBADD = ../../libkopete/libkopete.la \ + ./ui/libkopeteircui.la \ + ./libkirc/libkirc.la \ + $(LIB_KIO) + +service_DATA = kopete_irc.desktop irc.protocol +servicedir = $(kde_servicesdir) + +xmldata_DATA = ircnetworks.xml +xmldatadir = $(kde_datadir)/kopete + +EXTRA_DIST = $(xmldata_DATA) + +mydatadir = $(kde_datadir)/kopete +mydata_DATA = ircchatui.rc diff --git a/kopete/protocols/irc/icons/Makefile.am b/kopete/protocols/irc/icons/Makefile.am new file mode 100644 index 00000000..9143c6b4 --- /dev/null +++ b/kopete/protocols/irc/icons/Makefile.am @@ -0,0 +1,2 @@ +kopeteicondir = $(kde_datadir)/kopete/icons +kopeteicon_ICON = AUTO diff --git a/kopete/protocols/irc/icons/cr16-action-irc_away.png b/kopete/protocols/irc/icons/cr16-action-irc_away.png new file mode 100644 index 00000000..d7572e1f Binary files /dev/null and b/kopete/protocols/irc/icons/cr16-action-irc_away.png differ diff --git a/kopete/protocols/irc/icons/cr16-action-irc_channel.png b/kopete/protocols/irc/icons/cr16-action-irc_channel.png new file mode 100644 index 00000000..0353e7dc Binary files /dev/null and b/kopete/protocols/irc/icons/cr16-action-irc_channel.png differ diff --git a/kopete/protocols/irc/icons/cr16-action-irc_connecting.mng b/kopete/protocols/irc/icons/cr16-action-irc_connecting.mng new file mode 100644 index 00000000..486102e4 Binary files /dev/null and b/kopete/protocols/irc/icons/cr16-action-irc_connecting.mng differ diff --git a/kopete/protocols/irc/icons/cr16-action-irc_normal.png b/kopete/protocols/irc/icons/cr16-action-irc_normal.png new file mode 100644 index 00000000..b7a3cc24 Binary files /dev/null and b/kopete/protocols/irc/icons/cr16-action-irc_normal.png differ diff --git a/kopete/protocols/irc/icons/cr16-action-irc_online.png b/kopete/protocols/irc/icons/cr16-action-irc_online.png new file mode 100644 index 00000000..c5678d1e Binary files /dev/null and b/kopete/protocols/irc/icons/cr16-action-irc_online.png differ diff --git a/kopete/protocols/irc/icons/cr16-action-irc_op.png b/kopete/protocols/irc/icons/cr16-action-irc_op.png new file mode 100644 index 00000000..6b14cf14 Binary files /dev/null and b/kopete/protocols/irc/icons/cr16-action-irc_op.png differ diff --git a/kopete/protocols/irc/icons/cr16-action-irc_server.png b/kopete/protocols/irc/icons/cr16-action-irc_server.png new file mode 100644 index 00000000..b7a3cc24 Binary files /dev/null and b/kopete/protocols/irc/icons/cr16-action-irc_server.png differ diff --git a/kopete/protocols/irc/icons/cr16-action-irc_voice.png b/kopete/protocols/irc/icons/cr16-action-irc_voice.png new file mode 100644 index 00000000..6a9b5aaf Binary files /dev/null and b/kopete/protocols/irc/icons/cr16-action-irc_voice.png differ diff --git a/kopete/protocols/irc/icons/cr16-app-irc_protocol.png b/kopete/protocols/irc/icons/cr16-app-irc_protocol.png new file mode 100644 index 00000000..c5678d1e Binary files /dev/null and b/kopete/protocols/irc/icons/cr16-app-irc_protocol.png differ diff --git a/kopete/protocols/irc/icons/cr32-app-irc_protocol.png b/kopete/protocols/irc/icons/cr32-app-irc_protocol.png new file mode 100644 index 00000000..f2747b49 Binary files /dev/null and b/kopete/protocols/irc/icons/cr32-app-irc_protocol.png differ diff --git a/kopete/protocols/irc/irc.protocol b/kopete/protocols/irc/irc.protocol new file mode 100644 index 00000000..f57982bb --- /dev/null +++ b/kopete/protocols/irc/irc.protocol @@ -0,0 +1,12 @@ +[Protocol] +exec=kopete %u +protocol=irc +input=none +output=none +helper=true +listing= +reading=false +writing=false +makedir=false +deleting=false +Icon=irc_normal diff --git a/kopete/protocols/irc/ircaccount.cpp b/kopete/protocols/irc/ircaccount.cpp new file mode 100644 index 00000000..1a1bf75f --- /dev/null +++ b/kopete/protocols/irc/ircaccount.cpp @@ -0,0 +1,904 @@ +/* + ircaccount.cpp - IRC Account + + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2003-2004 by Jason Keirstead + Copyright (c) 2003-2005 by Michel Hermier + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "ircaccount.h" +#include "irccontact.h" +#include "irccontactmanager.h" +#include "ircprotocol.h" + +#include "ircservercontact.h" +#include "ircchannelcontact.h" +#include "ircusercontact.h" + +#include "channellistdialog.h" + +#include "kircengine.h" + +#include "kopeteaccountmanager.h" +#include "kopeteaway.h" +#include "kopeteawayaction.h" +#include "kopetecommandhandler.h" +#include "kopetecontactlist.h" +#include "kopetemetacontact.h" +#include "kopeteuiglobal.h" +#include "kopeteview.h" +#include "kopetepassword.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +const QString IRCAccount::CONFIG_CODECMIB = QString::fromLatin1("Codec"); +const QString IRCAccount::CONFIG_NETWORKNAME = QString::fromLatin1("NetworkName"); +const QString IRCAccount::CONFIG_NICKNAME = QString::fromLatin1("NickName"); +const QString IRCAccount::CONFIG_USERNAME = QString::fromLatin1("UserName"); +const QString IRCAccount::CONFIG_REALNAME = QString::fromLatin1("RealName"); + +IRCAccount::IRCAccount(IRCProtocol *protocol, const QString &accountId, const QString &autoChan, const QString& netName, const QString &nickName) + : Kopete::PasswordedAccount(protocol, accountId, 0, true), autoConnect( autoChan ), commandSource(0) +{ + m_manager = 0L; + m_channelList = 0L; + m_network = 0L; + + triedAltNick = false; + + m_contactManager = 0; + m_engine = new KIRC::Engine(this); + + QMap< QString, QString> replies = customCtcpReplies(); + for( QMap< QString, QString >::ConstIterator it = replies.begin(); it != replies.end(); ++it ) + m_engine->addCustomCtcp( it.key(), it.data() ); + + QString version=i18n("Kopete IRC Plugin %1 [http://kopete.kde.org]").arg(kapp->aboutData()->version()); + m_engine->setVersionString( version ); + + QObject::connect(m_engine, SIGNAL(successfullyChangedNick(const QString &, const QString &)), + this, SLOT(successfullyChangedNick(const QString &, const QString &))); + + QObject::connect(m_engine, SIGNAL(incomingFailedServerPassword()), + this, SLOT(slotFailedServerPassword())); + + QObject::connect(m_engine, SIGNAL(incomingNickInUse(const QString &)), + this, SLOT(slotNickInUseAlert( const QString &)) ); + + QObject::connect(m_engine, SIGNAL(incomingFailedNickOnLogin(const QString &)), + this, SLOT(slotNickInUse( const QString &)) ); + + QObject::connect(m_engine, SIGNAL(incomingJoinedChannel(const QString &, const QString &)), + this, SLOT(slotJoinedUnknownChannel(const QString &, const QString &))); + + QObject::connect(m_engine, SIGNAL(incomingCtcpReply(const QString &, const QString &, const QString &)), + this, SLOT( slotNewCtcpReply(const QString&, const QString &, const QString &))); + + QObject::connect(m_engine, SIGNAL(statusChanged(KIRC::Engine::Status)), + this, SLOT(engineStatusChanged(KIRC::Engine::Status))); + + QObject::connect(m_engine, SIGNAL(incomingServerLoadTooHigh()), + this, SLOT(slotServerBusy())); + + QObject::connect(m_engine, SIGNAL(incomingNoSuchNickname(const QString &)), + this, SLOT(slotNoSuchNickname(const QString &))); + + mAwayAction = new Kopete::AwayAction ( i18n("Set Away"), + m_protocol->m_UserStatusAway.iconFor( this ), 0, this, + SLOT(slotGoAway( const QString & )), this ); + + currentHost = 0; + + KConfigGroup *config = configGroup(); + + QString networkName = netName; + if (networkName.isNull()) + networkName = config->readEntry(CONFIG_NETWORKNAME); + + if (!nickName.isNull()) + setNickName(nickName); + else + mNickName = config->readEntry(CONFIG_NICKNAME); + + QString codecMib = config->readEntry(CONFIG_CODECMIB); + // int codecMib = config->readNumEntry(CONFIG_CODECMIB, UTF-8); + + m_serverNotices = (MessageDestination)config->readNumEntry( "ServerNotices", ServerWindow ); + m_serverMessages = (MessageDestination)config->readNumEntry( "ServerMessages", ServerWindow ); + m_informationReplies = (MessageDestination)config->readNumEntry( "InformationReplies", ActiveWindow ); + m_errorMessages = (MessageDestination)config->readNumEntry( "ErrorMessages", ActiveWindow ); + autoShowServerWindow = config->readBoolEntry( "AutoShowServerWindow", false ); + + if( !codecMib.isEmpty() ) + { + mCodec = QTextCodec::codecForMib( codecMib.toInt() ); + m_engine->setDefaultCodec( mCodec ); + } + else + mCodec = 0; + + QString m_accountId = this->accountId(); + if( networkName.isEmpty() && QRegExp( "[^#+&\\s]+@[\\w-\\.]+:\\d+" ).exactMatch( m_accountId ) ) + { + kdDebug(14120) << "Creating account from " << m_accountId << endl; + + mNickName = m_accountId.section('@',0,0); + QString serverInfo = m_accountId.section('@',1); + QString hostName = serverInfo.section(':',0,0); + + for( QDictIterator it( m_protocol->networks() ); it.current(); ++it ) + { + IRCNetwork *net = it.current(); + for( QValueList::iterator it2 = net->hosts.begin(); it2 != net->hosts.end(); ++it2 ) + { + if( (*it2)->host == hostName ) + { + setNetwork(net->name); + break; + } + } + + if( !networkName.isEmpty() ) + break; + } + + if( networkName.isEmpty() ) + { + /* Could not find this host. Add it to the networks structure */ + + m_network = new IRCNetwork; + m_network->name = i18n("Temporary Network - %1").arg( hostName ); + m_network->description = i18n("Network imported from previous version of Kopete, or an IRC URI"); + + IRCHost *host = new IRCHost; + host->host = hostName; + host->port = serverInfo.section(':',1).toInt(); + if( !password().cachedValue().isEmpty() ) + host->password = password().cachedValue(); + host->ssl = false; + + m_network->hosts.append( host ); + m_protocol->addNetwork( m_network ); + + config->writeEntry(CONFIG_NETWORKNAME, m_network->name); + config->writeEntry(CONFIG_NICKNAME, mNickName); + } + } + else if( !networkName.isEmpty() ) + { + setNetwork(networkName); + } + else + { + kdError() << "No network name defined, and could not import network information from ID" << endl; + } + + m_engine->setUserName(userName()); + m_engine->setRealName(realName()); + + m_contactManager = new IRCContactManager(mNickName, this); + setMyself( m_contactManager->mySelf() ); + setAccountLabel( QString::fromLatin1("%1@%2").arg(mNickName,networkName) ); + m_myServer = m_contactManager->myServer(); + + m_joinChannelAction = new KAction ( i18n("Join Channel..."), QString::null, 0, this, + SLOT(slotJoinChannel()), this); + m_searchChannelAction = new KAction ( i18n("Search Channels..."), QString::null, 0, this, + SLOT(slotSearchChannels()), this); +} + +IRCAccount::~IRCAccount() +{ + if (m_engine->isConnected()) + m_engine->quit(i18n("Plugin Unloaded"), true); +} + +void IRCAccount::slotNickInUse( const QString &nick ) +{ + QString altNickName = altNick(); + if( triedAltNick || altNickName.isEmpty() ) + { + QString newNick = KInputDialog::getText( + i18n("IRC Plugin"), + i18n("The nickname %1 is already in use. Please enter an alternate nickname:").arg(nick), + nick); + + if (newNick.isNull()) + disconnect(); + else + m_engine->nick(newNick); + } + else + { + triedAltNick = true; + m_engine->nick(altNickName); + } +} + +void IRCAccount::slotNickInUseAlert( const QString &nick ) +{ + KMessageBox::error(Kopete::UI::Global::mainWidget(), i18n("The nickname %1 is already in use").arg(nick), i18n("IRC Plugin")); +} + +void IRCAccount::setAltNick( const QString &altNick ) +{ + configGroup()->writeEntry(QString::fromLatin1( "altNick" ), altNick); +} + +const QString IRCAccount::altNick() const +{ + return configGroup()->readEntry(QString::fromLatin1("altNick")); +} + +void IRCAccount::setAutoShowServerWindow( bool show ) +{ + autoShowServerWindow = show; + configGroup()->writeEntry(QString::fromLatin1( "AutoShowServerWindow" ), autoShowServerWindow); +} + +const QString IRCAccount::networkName() const +{ + if( m_network ) + return m_network->name; + else + return i18n("Unknown"); +} + +void IRCAccount::setUserName( const QString &userName ) +{ + m_engine->setUserName(userName); + configGroup()->writeEntry(CONFIG_USERNAME, userName); +} + +const QString IRCAccount::userName() const +{ + return configGroup()->readEntry(CONFIG_USERNAME); +} + +void IRCAccount::setRealName( const QString &userName ) +{ + m_engine->setRealName(userName); + configGroup()->writeEntry(CONFIG_REALNAME, userName); +} + +const QString IRCAccount::realName() const +{ + return configGroup()->readEntry(CONFIG_REALNAME); +} + +void IRCAccount::setNetwork( const QString &network ) +{ + IRCNetwork *net = m_protocol->networks()[ network ]; + if( net ) + { + m_network = net; + configGroup()->writeEntry(CONFIG_NETWORKNAME, network); + setAccountLabel(network); + } + else + { + KMessageBox::queuedMessageBox( + Kopete::UI::Global::mainWidget(), KMessageBox::Error, + i18n("The network associated with this account, %1, no longer exists. Please" + " ensure that the account has a valid network. The account will not be enabled until you do so.").arg(network), + i18n("Problem Loading %1").arg( accountId() ), 0 ); + } +} + +void IRCAccount::setNickName( const QString &nick ) +{ + mNickName = nick; + configGroup()->writeEntry(CONFIG_NICKNAME, mNickName); + + if( mySelf() ) + mySelf()->setNickName( mNickName ); +} + +// FIXME: Possible null pointer usage here +void IRCAccount::setCodec( QTextCodec *codec ) +{ + mCodec = codec; + configGroup()->writeEntry(CONFIG_CODECMIB, codec->mibEnum()); + + if( mCodec ) + m_engine->setDefaultCodec( mCodec ); +} + +QTextCodec *IRCAccount::codec() const +{ + return mCodec; +} + +// FIXME: Move this to a dictionnary +void IRCAccount::setDefaultPart( const QString &defaultPart ) +{ + configGroup()->writeEntry( QString::fromLatin1( "defaultPart" ), defaultPart ); +} + +// FIXME: Move this to a dictionnary +void IRCAccount::setDefaultQuit( const QString &defaultQuit ) +{ + configGroup()->writeEntry( QString::fromLatin1( "defaultQuit" ), defaultQuit ); +} + +// FIXME: Move this to a dictionnary +const QString IRCAccount::defaultPart() const +{ + QString partMsg = configGroup()->readEntry(QString::fromLatin1("defaultPart")); + if( partMsg.isEmpty() ) + return QString::fromLatin1("Kopete %1 : http://kopete.kde.org").arg( kapp->aboutData()->version() ); + return partMsg; +} + +const QString IRCAccount::defaultQuit() const +{ + QString quitMsg = configGroup()->readEntry(QString::fromLatin1("defaultQuit")); + if( quitMsg.isEmpty() ) + return QString::fromLatin1("Kopete %1 : http://kopete.kde.org").arg(kapp->aboutData()->version()); + return quitMsg; +} + +void IRCAccount::setCustomCtcpReplies( const QMap< QString, QString > &replies ) const +{ + QStringList val; + for( QMap< QString, QString >::ConstIterator it = replies.begin(); it != replies.end(); ++it ) + { + m_engine->addCustomCtcp( it.key(), it.data() ); + val.append( QString::fromLatin1("%1=%2").arg( it.key() ).arg( it.data() ) ); + } + + configGroup()->writeEntry( "CustomCtcp", val ); +} + +const QMap< QString, QString > IRCAccount::customCtcpReplies() const +{ + QMap< QString, QString > replies; + QStringList replyList; + + replyList = configGroup()->readListEntry( "CustomCtcp" ); + + for( QStringList::Iterator it = replyList.begin(); it != replyList.end(); ++it ) + replies[ (*it).section('=', 0, 0 ) ] = (*it).section('=', 1 ); + + return replies; +} + +void IRCAccount::setConnectCommands( const QStringList &commands ) const +{ + configGroup()->writeEntry( "ConnectCommands", commands ); +} + +const QStringList IRCAccount::connectCommands() const +{ + return configGroup()->readListEntry( "ConnectCommands" ); +} + +void IRCAccount::setMessageDestinations( int serverNotices, int serverMessages, + int informationReplies, int errorMessages ) +{ + KConfigGroup *config = configGroup(); + config->writeEntry( "ServerNotices", serverNotices ); + config->writeEntry( "ServerMessages", serverMessages ); + config->writeEntry( "InformationReplies", informationReplies ); + config->writeEntry( "ErrorMessages", errorMessages ); + + m_serverNotices = (MessageDestination)serverNotices; + m_serverMessages = (MessageDestination)serverMessages; + m_informationReplies = (MessageDestination)informationReplies; + m_errorMessages = (MessageDestination)errorMessages; +} + +KActionMenu *IRCAccount::actionMenu() +{ + QString menuTitle = QString::fromLatin1( " %1 <%2> " ).arg( accountId() ).arg( myself()->onlineStatus().description() ); + + KActionMenu *mActionMenu = Kopete::Account::actionMenu(); + + m_joinChannelAction->setEnabled( isConnected() ); + m_searchChannelAction->setEnabled( isConnected() ); + + mActionMenu->popupMenu()->insertSeparator(); + mActionMenu->insert(m_joinChannelAction); + mActionMenu->insert(m_searchChannelAction); + mActionMenu->insert( new KAction ( i18n("Show Server Window"), QString::null, 0, this, SLOT(slotShowServerWindow()), mActionMenu ) ); + + if( m_engine->isConnected() && m_engine->useSSL() ) + { + mActionMenu->insert( new KAction ( i18n("Show Security Information"), "", 0, m_engine, + SLOT(showInfoDialog()), mActionMenu ) ); + } + + return mActionMenu; +} + +void IRCAccount::connectWithPassword(const QString &password) +{ + //TODO: honor the initial status + + if( m_engine->isConnected() ) + { + if( isAway() ) + setAway( false ); + } + else if( m_engine->isDisconnected() ) + { + if( m_network ) + { + QValueList &hosts = m_network->hosts; + if( hosts.count() == 0 ) + { + KMessageBox::queuedMessageBox( + Kopete::UI::Global::mainWidget(), KMessageBox::Error, + i18n("The network associated with this account, %1, has no valid hosts. Please ensure that the account has a valid network.").arg(m_network->name), + i18n("Network is Empty"), 0 ); + } + else if( currentHost == hosts.count() ) + { + KMessageBox::queuedMessageBox( + Kopete::UI::Global::mainWidget(), KMessageBox::Error, + i18n("Kopete could not connect to any of the servers in the network associated with this account (%1). Please try again later.").arg(m_network->name), + i18n("Network is Unavailable"), 0 ); + + currentHost = 0; + } + else + { + // if prefer SSL is set, sort by SSL first + if (configGroup()->readBoolEntry("PreferSSL")) + { + typedef QValueList IRCHostList; + IRCHostList sslFirst; + IRCHostList::iterator it; + for ( it = hosts.begin(); it != hosts.end(); ++it ) + { + if ( (*it)->ssl == true ) + { + sslFirst.append( *it ); + it = hosts.remove( it ); + } + } + for ( it = hosts.begin(); it != hosts.end(); ++it ) + sslFirst.append( *it ); + + hosts = sslFirst; + } + + IRCHost *host = hosts[ currentHost++ ]; + myServer()->appendMessage( i18n("Connecting to %1...").arg( host->host ) ); + if( host->ssl ) + myServer()->appendMessage( i18n("Using SSL") ); + + m_engine->setPassword(password); + m_engine->connectToServer( host->host, host->port, mNickName, host->ssl ); + } + } + else + { + kdWarning() << "No network defined!" << endl; + } + } +} + +void IRCAccount::engineStatusChanged(KIRC::Engine::Status newStatus) +{ + kdDebug(14120) << k_funcinfo << endl; + + mySelf()->updateStatus(); + + switch (newStatus) + { + case KIRC::Engine::Idle: + // Do nothing. + break; + case KIRC::Engine::Connecting: + { + if( autoShowServerWindow ) + myServer()->startChat(); + break; + } + case KIRC::Engine::Authentifying: + break; + case KIRC::Engine::Connected: + { + //Reset the host so re-connection will start over at first server + currentHost = 0; + m_contactManager->addToNotifyList( m_engine->nickName() ); + + // HACK! See bug #85200 for details. Some servers still cannot accept commands + // after the 001 is sent, you need to wait until all the init junk is done. + // Unfortunatly, there is no way for us to know when it is done (it could be + // spewing out any number of replies), so just try delaying it + QTimer::singleShot( 250, this, SLOT( slotPerformOnConnectCommands() ) ); + } + break; + case KIRC::Engine::Closing: + triedAltNick = false; +// mySelf()->setOnlineStatus( m_protocol->m_UserStatusOffline ); + m_contactManager->removeFromNotifyList( m_engine->nickName() ); + +// if (m_contactManager && !autoConnect.isNull()) +// Kopete::AccountManager::self()->removeAccount( this ); + break; + case KIRC::Engine::AuthentifyingFailed: + break; + case KIRC::Engine::Timeout: + //Try next server + connect(); + break; + case KIRC::Engine::Disconnected: + break; + } +} + +void IRCAccount::slotPerformOnConnectCommands() +{ + Kopete::ChatSession *manager = myServer()->manager(Kopete::Contact::CanCreate); + if (!manager) + return; + + if (!autoConnect.isEmpty()) + Kopete::CommandHandler::commandHandler()->processMessage( QString::fromLatin1("/join %1").arg(autoConnect), manager); + + QStringList commands(connectCommands()); + for (QStringList::Iterator it=commands.begin(); it != commands.end(); ++it) + Kopete::CommandHandler::commandHandler()->processMessage(*it, manager); +} + +void IRCAccount::slotJoinedUnknownChannel(const QString &channel, const QString &nick) +{ + if ( nick.lower() == m_contactManager->mySelf()->nickName().lower() ) + { + m_contactManager->findChannel(channel)->join(); + } +} + +void IRCAccount::disconnect() +{ + quit(); +} + +void IRCAccount::slotServerBusy() +{ + KMessageBox::queuedMessageBox( + Kopete::UI::Global::mainWidget(), KMessageBox::Error, + i18n("The IRC server is currently too busy to respond to this request."), + i18n("Server is Busy"), 0 + ); +} + +void IRCAccount::slotSearchChannels() +{ + if( !m_channelList ) + { + m_channelList = new ChannelListDialog( m_engine, + i18n("Channel List for %1").arg( m_engine->currentHost() ), this, + SLOT( slotJoinNamedChannel( const QString & ) ) ); + } + else + m_channelList->clear(); + + m_channelList->show(); +} + +void IRCAccount::listChannels() +{ + slotSearchChannels(); + m_channelList->search(); +} + +void IRCAccount::quit( const QString &quitMessage ) +{ + kdDebug(14120) << "Quitting IRC: " << quitMessage << endl; + + if( quitMessage.isNull() || quitMessage.isEmpty() ) + m_engine->quit( defaultQuit() ); + else + m_engine->quit( quitMessage ); +} + +void IRCAccount::setAway(bool isAway, const QString &awayMessage) +{ + kdDebug(14120) << k_funcinfo << isAway << " " << awayMessage << endl; + if(m_engine->isConnected()) + { + static_cast( myself() )->setAway( isAway ); + engine()->away(isAway, awayMessage); + } +} + +/* + * Ask for server password, and reconnect + */ +void IRCAccount::slotFailedServerPassword() +{ + // JLN + password().setWrong(); + connect(); +} +void IRCAccount::slotGoAway( const QString &reason ) +{ + setAway( true, reason ); +} + +void IRCAccount::slotShowServerWindow() +{ + m_myServer->startChat(); +} + +bool IRCAccount::isConnected() +{ +// return ( myself()->onlineStatus().status() != Kopete::OnlineStatus::Offline ); + return m_engine->isConnected(); +} + +void IRCAccount::setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason ) +{ + if ( status.status() == Kopete::OnlineStatus::Online && + myself()->onlineStatus().status() == Kopete::OnlineStatus::Offline ) + connect(); + else if (status.status() == Kopete::OnlineStatus::Online && + myself()->onlineStatus().status() == Kopete::OnlineStatus::Away ) + setAway( false ); + else if ( status.status() == Kopete::OnlineStatus::Offline ) + disconnect(); + else if ( status.status() == Kopete::OnlineStatus::Away ) + slotGoAway( reason ); +} + + +void IRCAccount::successfullyChangedNick(const QString &oldnick, const QString &newnick) +{ + kdDebug(14120) << k_funcinfo << "Changing nick to " << newnick << endl; + mNickName = newnick; + mySelf()->setNickName( mNickName ); + m_contactManager->removeFromNotifyList( oldnick ); + m_contactManager->addToNotifyList( newnick ); +} + +bool IRCAccount::createContact( const QString &contactId, Kopete::MetaContact *m ) +{ + kdDebug(14120) << k_funcinfo << contactManager() << endl; + IRCContact *c; + + if( !m ) + {//This should NEVER happen + m = new Kopete::MetaContact(); + Kopete::ContactList::self()->addMetaContact(m); + } + + if( contactId == mNickName ) + { + KMessageBox::error( Kopete::UI::Global::mainWidget(), + i18n("\"You are not allowed to add yourself to your contact list."), i18n("IRC Plugin") + ); + + return false; + } + else if (contactId.startsWith(QString::fromLatin1("#"))) + { + c = static_cast(contactManager()->findChannel(contactId, m)); + } + else + { + m_contactManager->addToNotifyList( contactId ); + c = static_cast(contactManager()->findUser(contactId, m)); + } + + if( c->metaContact() != m ) + {//This should NEVER happen + Kopete::MetaContact *old = c->metaContact(); + c->setMetaContact( m ); + Kopete::ContactPtrList children = old->contacts(); + if (children.isEmpty()) + Kopete::ContactList::self()->removeMetaContact( old ); + } + else if( c->metaContact()->isTemporary() ) + m->setTemporary(false); + + return true; +} + +void IRCAccount::slotJoinNamedChannel(const QString &chan) +{ + contactManager()->findChannel(chan)->startChat(); +} + +void IRCAccount::setCurrentCommandSource( Kopete::ChatSession *session ) +{ + commandSource = session; +} + +Kopete::ChatSession *IRCAccount::currentCommandSource() +{ + return commandSource; +} + +void IRCAccount::slotJoinChannel() +{ + if (!isConnected()) + return; + + QStringList chans = configGroup()->readListEntry( "Recent Channel list" ); + //kdDebug(14120) << "Recent channel list from config: " << chans << endl; + + KLineEditDlg dlg( + i18n("Please enter name of the channel you want to join:"), + QString::null, + Kopete::UI::Global::mainWidget() + ); + + KCompletion comp; + comp.insertItems( chans ); + + dlg.lineEdit()->setCompletionObject( &comp ); + dlg.lineEdit()->setCompletionMode( KGlobalSettings::CompletionPopup ); + + while( true ) + { + if( dlg.exec() != QDialog::Accepted ) + break; + + QString chan = dlg.text(); + if( chan.isNull() ) + break; + + if( KIRC::Entity::isChannel( chan ) ) + { + contactManager()->findChannel( chan )->startChat(); + + // push the joined channel to first in list + chans.remove( chan ); + chans.prepend( chan ); + + configGroup()->writeEntry( "Recent Channel list", chans ); + break; + } + + KMessageBox::error( Kopete::UI::Global::mainWidget(), + i18n("\"%1\" is an invalid channel. Channels must start with '#', '!', '+', or '&'.").arg(chan), + i18n("IRC Plugin") + ); + } +} + +void IRCAccount::slotNewCtcpReply(const QString &type, const QString &/*target*/, const QString &messageReceived) +{ + appendMessage( i18n("CTCP %1 REPLY: %2").arg(type).arg(messageReceived), InfoReply ); +} + +void IRCAccount::slotNoSuchNickname( const QString &nick ) +{ + if( KIRC::Entity::isChannel(nick) ) + appendMessage( i18n("The channel \"%1\" does not exist").arg(nick), UnknownReply ); + else + appendMessage( i18n("The nickname \"%1\" does not exist").arg(nick), UnknownReply ); +} + +void IRCAccount::appendMessage( const QString &message, MessageType type ) +{ + // TODO: Impliment a UI where people can pick multiple destinations + // for a message type, and make codethis handle it + + MessageDestination destination; + + switch( type ) + { + case ConnectReply: + destination = m_serverMessages; + break; + case InfoReply: + destination = m_informationReplies; + break; + case NoticeReply: + destination = m_serverNotices; + break; + case ErrorReply: + destination = m_errorMessages; + break; + case UnknownReply: + default: + destination = ActiveWindow; + break; + } + + if( destination == ActiveWindow ) + { + KopeteView *activeView = Kopete::ChatSessionManager::self()->activeView(); + if( activeView && activeView->msgManager()->account() == this ) + { + Kopete::ChatSession *manager = activeView->msgManager(); + Kopete::Message msg( manager->myself(), manager->members(), message, + Kopete::Message::Internal, Kopete::Message::RichText, CHAT_VIEW ); + activeView->appendMessage(msg); + } + } + + if( destination == AnonymousWindow ) + { + //TODO: Create an anonymous window??? What will this mean... + } + + if( destination == ServerWindow ) + { + myServer()->appendMessage(message); + } + + if( destination == KNotify ) + { + KNotifyClient::event( + Kopete::UI::Global::mainWidget()->winId(), QString::fromLatin1("irc_event"), message + ); + } +} + +IRCUserContact *IRCAccount::mySelf() const +{ + return static_cast( myself() ); +} + +IRCServerContact *IRCAccount::myServer() const +{ + return m_myServer; +} + +IRCContact *IRCAccount::getContact(const QString &name, Kopete::MetaContact *metac) +{ + return getContact(m_engine->getEntity(name), metac); +} + +IRCContact *IRCAccount::getContact(KIRC::EntityPtr entity, Kopete::MetaContact *metac) +{ + IRCContact *contact = 0; + +#ifdef __GNUC__ + #warning Do the search code here. +#endif + + if (!contact) + { +#ifdef __GNUC__ + #warning Make a temporary meta contact if metac is null +#endif + contact = new IRCContact(this, entity, metac); + m_contacts.append(contact); + } + + QObject::connect(contact, SIGNAL(destroyed(IRCContact *)), SLOT(destroyed(IRCContact *))); + return contact; +} + +void IRCAccount::destroyed(IRCContact *contact) +{ + m_contacts.remove(contact); +} + +#include "ircaccount.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/irc/ircaccount.h b/kopete/protocols/irc/ircaccount.h new file mode 100644 index 00000000..e5917360 --- /dev/null +++ b/kopete/protocols/irc/ircaccount.h @@ -0,0 +1,248 @@ +/* + ircaccount.h - IRC Account + + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2003 by Jason Keirstead + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef IRCACCOUNT_H +#define IRCACCOUNT_H + +#include "ircprotocol.h" + +#include "kircengine.h" + +#include "kopetepasswordedaccount.h" + +#include + +#include +#include + +class ChannelListDialog; + +class IRCContact; +class IRCChannelContact; +class IRCContactManager; +class IRCServerContact; +class IRCProtocol; +class IRCUserContact; + +namespace Kopete +{ +class AwayAction; +class Contact; +class Message; +class ChatSession; +class MetaContact; +} + +class KAction; +class KActionMenu; + +struct IRCHost +{ + QString host; + uint port; + QString password; + bool ssl; +}; + +struct IRCNetwork +{ + QString name; + QString description; + QValueList hosts; +}; + +class IRCAccount + : public Kopete::PasswordedAccount +{ + friend class IRCEditAccountWidget; + friend class IRCProtocolHandler; + + Q_OBJECT + +public: + static const QString CONFIG_CODECMIB; + static const QString CONFIG_NETWORKNAME; + static const QString CONFIG_NICKNAME; + static const QString CONFIG_USERNAME; + static const QString CONFIG_REALNAME; + + enum MessageType + { + ConnectReply = 1, + InfoReply = 2, + NoticeReply = 4, + ErrorReply = 8, + UnknownReply = 16, + Default = 32 + }; + + enum MessageDestination + { + ActiveWindow = 1, + ServerWindow = 2, + AnonymousWindow = 3, + KNotify = 4, + Ignore = 5 + }; + + IRCAccount(IRCProtocol *p, const QString &accountid, const QString &autoConnect = QString::null, + const QString& networkName = QString::null, const QString &nickName = QString::null); + virtual ~IRCAccount(); + + void setNickName( const QString & ); + + void setAutoShowServerWindow( bool show ); + + void setAltNick( const QString & ); + const QString altNick() const; + + void setUserName( const QString & ); + const QString userName() const; + + void setRealName( const QString & ); + const QString realName() const; + + const QStringList connectCommands() const; + + void setConnectCommands( const QStringList & ) const; + + void setDefaultPart( const QString & ); + + void setNetwork( const QString & ); + + void setDefaultQuit( const QString & ); + + void setCodec( QTextCodec *codec ); + + void setMessageDestinations( int serverNotices, int serverMessages, + int informationReplies, int errorMessages ); + + QTextCodec *codec() const; + + const QString defaultPart() const; + + const QString defaultQuit() const; + + const QString networkName() const; + + QMap< QString, QString > customCtcp() const; + + void setCustomCtcpReplies( const QMap< QString, QString > &replys ) const; + + const QMap customCtcpReplies() const; + + void setCurrentCommandSource( Kopete::ChatSession *session ); + + Kopete::ChatSession *currentCommandSource(); + + IRCContact *getContact(const QString &name, Kopete::MetaContact *metac=0); + IRCContact *getContact(KIRC::EntityPtr entity, Kopete::MetaContact *metac=0); + +public slots: + + virtual KActionMenu *actionMenu(); + + virtual void setAway( bool isAway, const QString &awayMessage = QString::null ); + + virtual bool isConnected(); + + /** Reimplemented from Kopete::Account */ + void setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason = QString::null); + + // Returns the KIRC engine instance + KIRC::Engine *engine() const { return m_engine; } + + // Returns the IRCProtocol instance for contacts + IRCProtocol *protocol() const { return m_protocol; } + + IRCContactManager *contactManager() const { return m_contactManager; } + + // Returns the Kopete::Contact of the user + IRCUserContact *mySelf() const; + + // Returns the Kopete::Contact of the server of the user + IRCServerContact *myServer() const; + + void successfullyChangedNick(const QString &, const QString &); + + virtual void connectWithPassword( const QString & ); + virtual void disconnect(); + + void quit( const QString &quitMessage = QString::null ); + + void listChannels(); + + void appendMessage( const QString &message, MessageType type = Default ); + +protected: + virtual bool createContact( const QString &contactId, Kopete::MetaContact *parentContact ) ; + +private slots: + void engineStatusChanged(KIRC::Engine::Status newStatus); + + void destroyed(IRCContact *contact); + + void slotFailedServerPassword(); + void slotGoAway( const QString &reason ); + void slotJoinNamedChannel( const QString &channel ); + void slotJoinChannel(); + void slotShowServerWindow(); + void slotNickInUse( const QString &nick ); + void slotNickInUseAlert( const QString &nick ); + void slotServerBusy(); + void slotNoSuchNickname( const QString &nick ); + void slotSearchChannels(); + void slotNewCtcpReply(const QString &type, const QString &target, const QString &messageReceived); + void slotJoinedUnknownChannel( const QString &channel, const QString &nick ); + void slotPerformOnConnectCommands(); + +private: + Kopete::ChatSession *m_manager; + QString mNickName; + Kopete::AwayAction *mAwayAction; + bool triedAltNick; + bool autoShowServerWindow; + QString autoConnect; + + KIRC::Engine *m_engine; + IRCNetwork *m_network; + uint currentHost; + QTextCodec *mCodec; + + MessageDestination m_serverNotices; + MessageDestination m_serverMessages; + MessageDestination m_informationReplies; + MessageDestination m_errorMessages; + + ChannelListDialog *m_channelList; + + QValueList m_contacts; + IRCContactManager *m_contactManager; + IRCServerContact *m_myServer; + + QMap< QString, QString > m_customCtcp; + Kopete::ChatSession *commandSource; + + KAction *m_joinChannelAction; + KAction *m_searchChannelAction; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/irc/ircaddcontactpage.cpp b/kopete/protocols/irc/ircaddcontactpage.cpp new file mode 100644 index 00000000..db4ca3b2 --- /dev/null +++ b/kopete/protocols/irc/ircaddcontactpage.cpp @@ -0,0 +1,83 @@ +/* + ircaddcontactpage.cpp - IRC Add Contact Widget + + Copyright (c) 2002 by Nick Betcher + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "ircadd.h" +#include "ircaddcontactpage.h" +#include "channellist.h" + +#include "kircengine.h" + +#include "ircaccount.h" + +#include +#include +#include +#include +#include +#include +#include + +IRCAddContactPage::IRCAddContactPage( QWidget *parent, IRCAccount *a ) : AddContactPage(parent, 0) +{ + (new QVBoxLayout(this))->setAutoAdd(true); + ircdata = new ircAddUI(this); + mSearch = new ChannelList( (QWidget*)ircdata->hbox, a->engine() ); + mAccount = a; + + connect( mSearch, SIGNAL( channelSelected( const QString & ) ), + this, SLOT( slotChannelSelected( const QString & ) ) ); + + connect( mSearch, SIGNAL( channelDoubleClicked( const QString & ) ), + this, SLOT( slotChannelDoubleClicked( const QString & ) ) ); +} + +IRCAddContactPage::~IRCAddContactPage() +{ +} + +void IRCAddContactPage::slotChannelSelected( const QString &channel ) +{ + ircdata->addID->setText( channel ); +} + +void IRCAddContactPage::slotChannelDoubleClicked( const QString &channel ) +{ + ircdata->addID->setText( channel ); + ircdata->tabWidget3->setCurrentPage(0); +} + +bool IRCAddContactPage::apply(Kopete::Account *account , Kopete::MetaContact *m) +{ + QString name = ircdata->addID->text(); + return account->addContact(name, m, Kopete::Account::ChangeKABC ); +} + +bool IRCAddContactPage::validateData() +{ + QString name = ircdata->addID->text(); + if (name.isEmpty() == true) + { + KMessageBox::sorry(this, i18n("You need to specify a channel to join, or query to open."), i18n("You Must Specify a Channel")); + return false; + } + return true; +} + +#include "ircaddcontactpage.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/irc/ircaddcontactpage.h b/kopete/protocols/irc/ircaddcontactpage.h new file mode 100644 index 00000000..c6b897ff --- /dev/null +++ b/kopete/protocols/irc/ircaddcontactpage.h @@ -0,0 +1,61 @@ +/* + ircaddcontactpage.h - IRC Add Contact Widget + + Copyright (c) 2002 by Nick Betcher + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef IRCADDCONTACTPAGE_H +#define IRCADDCONTACTPAGE_H + +#include "addcontactpage.h" + +class ircAddUI; +namespace Kopete { class MetaContact; } +class IRCAccount; +class QListViewItem; +class ChannelList; + +/** + *@author Nick Betcher + */ +class IRCAddContactPage : public AddContactPage +{ + Q_OBJECT +public: + IRCAddContactPage(QWidget *parent=0, IRCAccount* account = 0); + ~IRCAddContactPage(); + ircAddUI *ircdata; + +public slots: + virtual bool apply(Kopete::Account *account , Kopete::MetaContact *m); + +private slots: + virtual bool validateData(); + void slotChannelSelected( const QString &channel ); + void slotChannelDoubleClicked( const QString &channel ); +private: + IRCAccount *mAccount; + ChannelList *mSearch; +}; + +#endif +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/irc/ircchannelcontact.cpp b/kopete/protocols/irc/ircchannelcontact.cpp new file mode 100644 index 00000000..cc99acf3 --- /dev/null +++ b/kopete/protocols/irc/ircchannelcontact.cpp @@ -0,0 +1,749 @@ +/* + ircchannelcontact.cpp - IRC Channel Contact + + Copyright (c) 2002 by Nick Betcher + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "irccontactmanager.h" +#include "ircchannelcontact.h" +#include "ircusercontact.h" +#include "ircservercontact.h" +#include "ircaccount.h" +#include "ircprotocol.h" + +#include "kopeteview.h" +#include "kopeteuiglobal.h" +#include "kcodecaction.h" +#include "kopetemetacontact.h" +#include "kopetestdaction.h" +#include "kopetechatsessionmanager.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +//This is the number of nicknames we will process concurrently when joining a channel +//Lower numbers ensure less GUI blocking, but take marginally longer to complete. +//Higher numbers are absolute fastest, but block GUI until all members are added +#define NICK_BATCH_LENGTH 1 + +IRCChannelContact::IRCChannelContact(IRCContactManager *contactManager, const QString &channel, Kopete::MetaContact *metac) + : IRCContact(contactManager, channel, metac, "irc_channel") +{ + KIRC::Engine *engine = kircEngine(); + + mInfoTimer = new QTimer( this ); + QObject::connect(mInfoTimer, SIGNAL(timeout()), this, SLOT( slotUpdateInfo() ) ); + + QObject::connect(engine, SIGNAL(incomingUserIsAway(const QString &, const QString &)), + this, SLOT(slotIncomingUserIsAway(const QString &, const QString &))); + + QObject::connect(engine, SIGNAL(incomingListedChan(const QString &, uint, const QString &)), + this, SLOT(slotChannelListed(const QString &, uint, const QString &))); + + actionJoin = 0L; + actionModeT = new KToggleAction(i18n("Only Operators Can Change &Topic"), 0, this, SLOT(slotModeChanged()), this ); + actionModeN = new KToggleAction(i18n("&No Outside Messages"), 0, this, SLOT(slotModeChanged()), this ); + actionModeS = new KToggleAction(i18n("&Secret"), 0, this, SLOT(slotModeChanged()), this ); + actionModeM = new KToggleAction(i18n("&Moderated"), 0, this, SLOT(slotModeChanged()), this ); + actionModeI = new KToggleAction(i18n("&Invite Only"), 0, this, SLOT(slotModeChanged()), this ); + actionHomePage = 0L; + + updateStatus(); +} + +IRCChannelContact::~IRCChannelContact() +{ +} + +void IRCChannelContact::slotUpdateInfo() +{ + /** This woudl be nice, but it generates server errors too often + + if( !manager(Kopete::Contact::CannotCreate) && onlineStatus() == m_protocol->m_ChannelStatusOnline ) + kircEngine()->writeMessage( QString::fromLatin1("LIST %1").arg(m_nickName) ); + else + setProperty( QString::fromLatin1("channelMembers"), i18n("Members"), manager()->members().count() ); + + */ + KIRC::Engine *engine = kircEngine(); + + if (manager(Kopete::Contact::CannotCreate)) + { + setProperty(m_protocol->propChannelMembers, manager()->members().count()); + engine->writeMessage(QString::fromLatin1("WHO %1").arg(m_nickName)); + } + else + { + removeProperty(m_protocol->propChannelMembers); + removeProperty(m_protocol->propChannelTopic); + } + + mInfoTimer->start( 45000, true ); +} + +void IRCChannelContact::slotChannelListed( const QString &channel, uint members, const QString &topic ) +{ + if (!manager(Kopete::Contact::CannotCreate) && + onlineStatus() == m_protocol->m_ChannelStatusOnline && + channel.lower() == m_nickName.lower()) + { + mTopic = topic; + setProperty(m_protocol->propChannelMembers, members); + setProperty(m_protocol->propChannelTopic, topic); + } +} + +void IRCChannelContact::toggleOperatorActions(bool enabled) +{ + if (enabled) { + actionTopic->setEnabled(true); + } else if (modeEnabled('t')) { + actionTopic->setEnabled(false); + } + + actionModeT->setEnabled(enabled); + actionModeN->setEnabled(enabled); + actionModeS->setEnabled(enabled); + actionModeM->setEnabled(enabled); + actionModeI->setEnabled(enabled); +} + +void IRCChannelContact::slotOnlineStatusChanged(Kopete::Contact *c, const Kopete::OnlineStatus &status, const Kopete::OnlineStatus &oldStatus) +{ + Q_UNUSED(oldStatus); + + if (c == account()->myself()) { + if (status.internalStatus() & IRCProtocol::Operator) { + kdDebug(14120) << k_funcinfo << "WE NOW HAVE OP STATUS" << endl; + toggleOperatorActions(true); + } else { + kdDebug(14120) << k_funcinfo << "WE NOW dont HAVE OP STATUS" << endl; + toggleOperatorActions(false); + } + } +} + +void IRCChannelContact::updateStatus() +{ + KIRC::Engine::Status status = kircEngine()->status(); + switch (status) + { + case KIRC::Engine::Idle: + case KIRC::Engine::Connecting: + case KIRC::Engine::Authentifying: + setOnlineStatus(m_protocol->m_ChannelStatusOffline); + break; + case KIRC::Engine::Connected: + case KIRC::Engine::Closing: + setOnlineStatus(m_protocol->m_ChannelStatusOnline); + break; + default: + setOnlineStatus(m_protocol->m_StatusUnknown); + } +} + +void IRCChannelContact::chatSessionDestroyed() +{ + if (manager(Kopete::Contact::CannotCreate)) + { + part(); + Kopete::ContactPtrList contacts = manager()->members(); + + // remove all the users on the channel + for (Kopete::Contact *c = contacts.first(); c; c = contacts.next()) + { + if (c->metaContact()->isTemporary() && + !static_cast(c)->isChatting(manager())) + c->deleteLater(); + } + } + + IRCContact::chatSessionDestroyed(); +} + +void IRCChannelContact::initConversation() +{ + kircEngine()->join(m_nickName, password()); +} + +void IRCChannelContact::slotConnectedToServer() +{ + setOnlineStatus(m_protocol->m_ChannelStatusOnline); + if (manager(Kopete::Contact::CannotCreate)) + kircEngine()->join(m_nickName, password()); +} + +void IRCChannelContact::namesList(const QStringList &nicknames) +{ + mInfoTimer->stop(); + mJoinedNicks += nicknames; + slotAddNicknames(); +} + +void IRCChannelContact::endOfNames() +{ + setMode(QString::null); + slotUpdateInfo(); +} + +void IRCChannelContact::slotAddNicknames() +{ + if( !manager(Kopete::Contact::CannotCreate) || mJoinedNicks.isEmpty()) + { + return; + } + + IRCAccount *account = ircAccount(); + + for( uint i = 0; !mJoinedNicks.isEmpty() && i < NICK_BATCH_LENGTH; ++i ) + { + // Pick a nick from the front of the list. + + QString nickToAdd = mJoinedNicks.front(); + QChar firstChar = nickToAdd[0]; + if( firstChar == '@' || firstChar == '%' || firstChar == '+' ) + nickToAdd = nickToAdd.remove(0, 1); + + IRCUserContact *user; + + if ( nickToAdd.lower() != account->mySelf()->nickName().lower() ) + { + //kdDebug(14120) << k_funcinfo << m_nickName << " nick to add: " << nickToAdd << endl; + + user = account->contactManager()->findUser(nickToAdd); + + // If the user is already present in some channel, dont flip the status + // back to online, because the other channels listen to + // onlineStatusChanged() emits, and they would adjust their statuses. + + if (account->contactManager()->findChannelsByMember(user).isEmpty()) { + //kdDebug(14120) << k_funcinfo << "Setting nick ONLINE" << endl; + user->setOnlineStatus(m_protocol->m_UserStatusOnline); + } + } + else + { + // Handling my nick in the list. + user = account->mySelf(); + } + + Kopete::OnlineStatus status; + if ( firstChar == '@' || firstChar == '%' ) + status = m_protocol->m_UserStatusOp; + else if( firstChar == '+') + status = m_protocol->m_UserStatusVoice; + else + status = user->onlineStatus(); + + if( user != account->mySelf() ) + manager()->addContact(user , status, true); + else + manager()->setContactOnlineStatus(user, status); + + mJoinedNicks.pop_front(); + } + + QTimer::singleShot( 0, this, SLOT( slotAddNicknames() ) ); +} + +void IRCChannelContact::channelTopic(const QString &topic) +{ + mTopic = topic; + setProperty( m_protocol->propChannelTopic, mTopic ); + manager()->setDisplayName(caption()); + + if (mTopic.isEmpty()) { + Kopete::Message msg((Kopete::Contact*)this, mMyself, + i18n("Topic for %1 is set empty.").arg(m_nickName), + Kopete::Message::Internal, Kopete::Message::RichText, CHAT_VIEW); + appendMessage(msg); + } else { + Kopete::Message msg((Kopete::Contact*)this, mMyself, + i18n("Topic for %1 is %2").arg(m_nickName).arg(mTopic), + Kopete::Message::Internal, Kopete::Message::RichText, CHAT_VIEW); + appendMessage(msg); + } +} + +void IRCChannelContact::channelHomePage(const QString &url) +{ + kdDebug(14120) << k_funcinfo << endl; + setProperty( m_protocol->propHomepage, url ); +} + +void IRCChannelContact::join() +{ + if (!manager(Kopete::Contact::CannotCreate) && + onlineStatus().status() == Kopete::OnlineStatus::Online) + { + kdDebug() << k_funcinfo << "My nickname:" << m_nickName << endl; + kdDebug() << k_funcinfo << "My manager:" << manager(Kopete::Contact::CannotCreate) << endl; + if( manager(Kopete::Contact::CannotCreate) ) + kdDebug() << k_funcinfo << "My view:" << manager(Kopete::Contact::CannotCreate)->view(false) << endl; + startChat(); + } + + if (manager()) { + connect(manager(), + SIGNAL(onlineStatusChanged(Kopete::Contact *, const Kopete::OnlineStatus &, + const Kopete::OnlineStatus &)), + SLOT(slotOnlineStatusChanged(Kopete::Contact *, const Kopete::OnlineStatus &, + const Kopete::OnlineStatus &))); + } +} + +void IRCChannelContact::partAction() +{ + if (manager()) + manager()->view()->closeView(); +} + +void IRCChannelContact::part() +{ + if (manager() && !kircEngine()->isDisconnected()) + kircEngine()->part(m_nickName, ircAccount()->defaultPart()); +} + +void IRCChannelContact::slotIncomingUserIsAway( const QString &nick, const QString & ) +{ + IRCAccount *account = ircAccount(); + + if( nick.lower() == account->mySelf()->nickName().lower() ) + { + IRCUserContact *c = account->mySelf(); + if (manager() && manager()->members().contains(c)) + { + Kopete::OnlineStatus status = manager()->contactOnlineStatus(c); + if (status == m_protocol->m_UserStatusOp) + manager()->setContactOnlineStatus(c, m_protocol->m_UserStatusOpAway ); + else if (status == m_protocol->m_UserStatusOpAway) + manager()->setContactOnlineStatus(c, m_protocol->m_UserStatusOp); + else if (status == m_protocol->m_UserStatusVoice) + manager()->setContactOnlineStatus(c, m_protocol->m_UserStatusVoiceAway); + else if (status == m_protocol->m_UserStatusVoiceAway) + manager()->setContactOnlineStatus(c, m_protocol->m_UserStatusVoice); + else if (status == m_protocol->m_UserStatusAway) + manager()->setContactOnlineStatus(c, m_protocol->m_UserStatusOnline); + else + manager()->setContactOnlineStatus(c, m_protocol->m_UserStatusAway); + } + } +} + +void IRCChannelContact::userJoinedChannel(const QString &nickname) +{ + IRCAccount *account = ircAccount(); + + if (nickname.lower() == account->mySelf()->nickName().lower()) + { + kdDebug() << k_funcinfo << "Me:" << this << endl; + kdDebug() << k_funcinfo << "My nickname:" << m_nickName << endl; + kdDebug() << k_funcinfo << "My manager:" << manager(Kopete::Contact::CannotCreate) << endl; + + if (manager(Kopete::Contact::CannotCreate)) + kdDebug() << k_funcinfo << "My view:" << manager(Kopete::Contact::CannotCreate)->view(false) << endl; + + Kopete::Message msg((Kopete::Contact *)this, mMyself, + i18n("You have joined channel %1").arg(m_nickName), + Kopete::Message::Internal, Kopete::Message::PlainText, + CHAT_VIEW); + msg.setImportance( Kopete::Message::Low); //set the importance manualy to low + appendMessage(msg); + } + else + { + // If we have lag or huge channels, we might receive a JOIN after we have left a channel. + if (!manager()) + return; + + IRCUserContact *contact = account->contactManager()->findUser( nickname ); + contact->setOnlineStatus( m_protocol->m_UserStatusOnline ); + manager()->addContact((Kopete::Contact *)contact, true); + Kopete::Message msg((Kopete::Contact *)this, mMyself, + i18n("User %1 joined channel %2").arg(nickname).arg(m_nickName), + Kopete::Message::Internal, Kopete::Message::RichText, CHAT_VIEW); + msg.setImportance( Kopete::Message::Low); //set the importance manualy to low + manager()->appendMessage(msg); + } +} + +void IRCChannelContact::userPartedChannel(const QString &nickname,const QString &reason) +{ + IRCAccount *account = ircAccount(); + + if (nickname.lower() != account->engine()->nickName().lower()) + { + Kopete::Contact *c = locateUser( nickname ); + if ( c ) + { + manager()->removeContact( c, Kopete::Message::unescape(reason) ); + if( c->metaContact()->isTemporary() && !static_cast(c)->isChatting( manager(Kopete::Contact::CannotCreate) ) ) + c->deleteLater(); + } + } +} + +void IRCChannelContact::userKicked(const QString &nick, const QString &nickKicked, const QString &reason) +{ + IRCAccount *account = ircAccount(); + + if( nickKicked.lower() != account->engine()->nickName().lower() ) + { + Kopete::Contact *c = locateUser( nickKicked ); + if (c) + { + QString r; + + if ((reason != nick) && (reason != nickKicked)) { + r = i18n( "%1 was kicked by %2. Reason: %3" ).arg(nickKicked, nick, reason); + } else { + r = i18n( "%1 was kicked by %2." ).arg(nickKicked, nick); + } + + manager()->removeContact( c, r ); + Kopete::Message msg( this, mMyself, r, + Kopete::Message::Internal, Kopete::Message::PlainText, CHAT_VIEW); + msg.setImportance(Kopete::Message::Low); + appendMessage(msg); + + if( c->metaContact()->isTemporary() && + !static_cast(c)->isChatting( manager() ) ) + c->deleteLater(); + } + } + else + { + QString r; + + if ((reason != nick) && (reason != nickKicked)) { + r = i18n( "You were kicked from %1 by %2. Reason: %3" ).arg(m_nickName, nickKicked, reason); + } else { + r = i18n( "You were kicked from %1 by %2." ).arg(m_nickName, nickKicked); + } + + KMessageBox::error(Kopete::UI::Global::mainWidget(), r, i18n("IRC Plugin")); + manager()->view()->closeView(); + } +} + +void IRCChannelContact::setTopic(const QString &topic) +{ + IRCAccount *account = ircAccount(); + + if (manager(Kopete::Contact::CannotCreate)) + { + if( manager()->contactOnlineStatus( manager()->myself() ) == + m_protocol->m_UserStatusOp || !modeEnabled('t') ) + { + bool okPressed = true; + QString newTopic = topic; + if( newTopic.isNull() ) + newTopic = KInputDialog::getText( i18n("New Topic"), i18n("Enter the new topic:"), + Kopete::Message::unescape(mTopic), &okPressed, 0L ); + + if( okPressed ) + { + mTopic = newTopic; + kircEngine()->topic(m_nickName, newTopic); + } + } + else + { + Kopete::Message msg(account->myServer(), manager()->members(), + i18n("You must be a channel operator on %1 to do that.").arg(m_nickName), + Kopete::Message::Internal, Kopete::Message::PlainText, CHAT_VIEW); + manager()->appendMessage(msg); + } + } +} + +void IRCChannelContact::topicChanged(const QString &nick, const QString &newtopic) +{ + IRCAccount *account = ircAccount(); + + mTopic = newtopic; + setProperty( m_protocol->propChannelTopic, mTopic ); + manager()->setDisplayName( caption() ); + Kopete::Message msg(account->myServer(), mMyself, + i18n("%1 has changed the topic to: %2").arg(nick).arg(newtopic), + Kopete::Message::Internal, Kopete::Message::RichText, CHAT_VIEW); + msg.setImportance(Kopete::Message::Low); //set the importance manualy to low + appendMessage(msg); +} + +void IRCChannelContact::topicUser(const QString &nick, const QDateTime &time) +{ + IRCAccount *account = ircAccount(); + + Kopete::Message msg(account->myServer(), mMyself, + i18n("Topic set by %1 at %2").arg(nick).arg( + KGlobal::locale()->formatDateTime(time, true) + ), Kopete::Message::Internal, Kopete::Message::PlainText, CHAT_VIEW); + msg.setImportance(Kopete::Message::Low); //set the importance manualy to low + appendMessage(msg); +} + +void IRCChannelContact::incomingModeChange( const QString &nick, const QString &mode ) +{ + Kopete::Message msg(this, mMyself, i18n("%1 sets mode %2 on %3").arg(nick).arg(mode).arg(m_nickName), Kopete::Message::Internal, Kopete::Message::PlainText, CHAT_VIEW); + msg.setImportance( Kopete::Message::Low); //set the importance manualy to low + appendMessage(msg); + + bool inParams = false; + bool modeEnabled = false; + QString params = QString::null; + for( uint i=0; i < mode.length(); i++ ) + { + switch( mode[i] ) + { + case '+': + modeEnabled = true; + break; + + case '-': + modeEnabled = false; + break; + + case ' ': + inParams = true; + break; + default: + if( inParams ) + params.append( mode[i] ); + else + toggleMode( mode[i], modeEnabled, false ); + break; + } + } +} + +void IRCChannelContact::incomingChannelMode( const QString &mode, + const QString &/*params*/ ) +{ + for( uint i=1; i < mode.length(); i++ ) + { + if( mode[i] != 'l' && mode[i] != 'k' ) + toggleMode( mode[i], true, false ); + } +} + +void IRCChannelContact::setMode(const QString &mode) +{ + if (manager(Kopete::Contact::CannotCreate)) + kircEngine()->mode(m_nickName, mode); +} + +void IRCChannelContact::slotModeChanged() +{ + toggleMode( 't', actionModeT->isChecked(), true ); + toggleMode( 'n', actionModeN->isChecked(), true ); + toggleMode( 's', actionModeS->isChecked(), true ); + toggleMode( 'm', actionModeM->isChecked(), true ); + toggleMode( 'i', actionModeI->isChecked(), true ); +} + +void IRCChannelContact::failedChanBanned() +{ + manager()->deleteLater(); + KMessageBox::error( Kopete::UI::Global::mainWidget(), + i18n("You can not join %1 because you have been banned.").arg(m_nickName), + i18n("IRC Plugin") ); +} + +void IRCChannelContact::failedChanInvite() +{ + manager()->deleteLater(); + KMessageBox::error( Kopete::UI::Global::mainWidget(), + i18n("You can not join %1 because it is set to invite only, and no one has invited you.").arg(m_nickName), i18n("IRC Plugin") ); +} + +void IRCChannelContact::failedChanFull() +{ + manager()->deleteLater(); + KMessageBox::error( Kopete::UI::Global::mainWidget(), + i18n("You can not join %1 because it has reached its user limit.").arg(m_nickName), + i18n("IRC Plugin") ); +} + +void IRCChannelContact::failedChankey() +{ + bool ok; + QString diaPassword = KInputDialog::getText( i18n( "IRC Plugin" ), + i18n( "Please enter key for channel %1: ").arg(m_nickName), + QString::null, + &ok ); + + if ( !ok ) + manager()->deleteLater(); + else + { + setPassword(diaPassword); + kircEngine()->join(m_nickName, password()); + } +} + +void IRCChannelContact::toggleMode( QChar mode, bool enabled, bool update ) +{ + if( manager(Kopete::Contact::CannotCreate) ) + { + switch( mode ) + { + case 't': + actionModeT->setChecked( enabled ); + + // If someones sets +t and we're not channel operators, disable the action. + if (enabled && !(manager()->contactOnlineStatus(ircAccount()->myself()).internalStatus() & IRCProtocol::Operator)) { + actionTopic->setEnabled( false ); + } else { + actionTopic->setEnabled( true ); + } + break; + case 'n': + actionModeN->setChecked( enabled ); + break; + case 's': + actionModeS->setChecked( enabled ); + break; + case 'm': + actionModeM->setChecked( enabled ); + break; + case 'i': + actionModeI->setChecked( enabled ); + break; + } + } + + if( update ) + { + if( modeMap[mode] != enabled ) + { + if( enabled ) + setMode( QString::fromLatin1("+") + mode ); + else + setMode( QString::fromLatin1("-") + mode ); + } + } + + modeMap[mode] = enabled; +} + +bool IRCChannelContact::modeEnabled( QChar mode, QString *value ) +{ + if( !value ) + return modeMap[mode]; + + return false; +} + +QPtrList *IRCChannelContact::customContextMenuActions() +{ + QPtrList *mCustomActions = new QPtrList(); + if( !actionJoin ) + { + actionJoin = new KAction(i18n("&Join"), 0, this, SLOT(join()), this, "actionJoin"); + actionPart = new KAction(i18n("&Part"), 0, this, SLOT(partAction()), this, "actionPart"); + actionTopic = new KAction(i18n("Change &Topic..."), 0, this, SLOT(setTopic()), this, "actionTopic"); + actionModeMenu = new KActionMenu(i18n("Channel Modes"), 0, this, "actionModeMenu"); + + if( !property(m_protocol->propHomepage).value().isNull() ) + { + actionHomePage = new KAction( i18n("Visit &Homepage"), 0, this, + SLOT(slotHomepage()), this, "actionHomepage"); + } + else if( actionHomePage ) + { + delete actionHomePage; + } + + actionModeMenu->insert( actionModeT ); + actionModeMenu->insert( actionModeN ); + actionModeMenu->insert( actionModeS ); + actionModeMenu->insert( actionModeM ); + actionModeMenu->insert( actionModeI ); + actionModeMenu->setEnabled( true ); + + codecAction = new KCodecAction( i18n("&Encoding"), 0, this, "selectcharset" ); + connect( codecAction, SIGNAL( activated( const QTextCodec * ) ), + this, SLOT( setCodec( const QTextCodec *) ) ); + codecAction->setCodec( codec() ); + } + + mCustomActions->append( actionJoin ); + mCustomActions->append( actionPart ); + mCustomActions->append( actionTopic ); + mCustomActions->append( actionModeMenu ); + mCustomActions->append( codecAction ); + if( actionHomePage ) + mCustomActions->append( actionHomePage ); + + bool isOperator = manager(Kopete::Contact::CannotCreate) && + (manager()->contactOnlineStatus(ircAccount()->myself()).internalStatus() & IRCProtocol::Operator); + + actionJoin->setEnabled( !manager(Kopete::Contact::CannotCreate) ); + actionPart->setEnabled( manager(Kopete::Contact::CannotCreate) ); + actionTopic->setEnabled( manager(Kopete::Contact::CannotCreate) && ( !modeEnabled('t') || isOperator ) ); + + toggleOperatorActions(isOperator); + + return mCustomActions; +} + +void IRCChannelContact::slotHomepage() +{ + QString homePage = property(m_protocol->propHomepage).value().toString(); + if( !homePage.isEmpty() ) + { + new KRun( KURL( homePage ), 0, false); + } +} + +const QString IRCChannelContact::caption() const +{ + QString cap = QString::fromLatin1("%1 @ %2").arg(m_nickName).arg(kircEngine()->currentHost()); + if(!mTopic.isEmpty()) + cap.append( QString::fromLatin1(" - %1").arg(Kopete::Message::unescape(mTopic)) ); + + return cap; +} + +void IRCChannelContact::privateMessage(IRCContact *from, IRCContact *to, const QString &message) +{ + if(to == this) + { + Kopete::Message msg(from, manager()->members(), message, Kopete::Message::Inbound, + Kopete::Message::RichText, CHAT_VIEW); + appendMessage(msg); + } +} + +void IRCChannelContact::newAction(const QString &from, const QString &action) +{ + IRCAccount *account = ircAccount(); + + IRCUserContact *f = account->contactManager()->findUser(from); + Kopete::Message::MessageDirection dir = + (f == account->mySelf()) ? Kopete::Message::Outbound : Kopete::Message::Inbound; + Kopete::Message msg(f, manager()->members(), action, dir, Kopete::Message::RichText, + CHAT_VIEW, Kopete::Message::TypeAction); + appendMessage(msg); +} + +#include "ircchannelcontact.moc" diff --git a/kopete/protocols/irc/ircchannelcontact.h b/kopete/protocols/irc/ircchannelcontact.h new file mode 100644 index 00000000..15a72e17 --- /dev/null +++ b/kopete/protocols/irc/ircchannelcontact.h @@ -0,0 +1,156 @@ +/* + ircchannelcontact.h - IRC Channel Contact + + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2003 by Jason Keirstead + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef IRCCHANNELCONTACT_H +#define IRCCHANNELCONTACT_H + +#include "irccontact.h" + +class KActionCollection; +class KAction; +class KActionMenu; +class KCodecAction; +class KToggleAction; + +namespace Kopete { class MetaContact; } +namespace Kopete { class ChatSession; } +namespace Kopete { class Message; } +class KopeteView; + +class IRCAccount; +class IRCContactManager; + +/** + * @author Jason Keirstead + * + * This class is the @ref Kopete::Contact object representing IRC Channels, not users. + * It is derived from IRCContact where much of its functionality is shared with @ref IRCUserContact. + */ +class IRCChannelContact + : public IRCContact +{ + friend class IRCSignalMapper; + + Q_OBJECT + +public: + IRCChannelContact(IRCContactManager *, const QString &channel, Kopete::MetaContact *metac); + ~IRCChannelContact(); + + /** + * Returns the current topic for this channel. + */ + const QString &topic() const { return mTopic; }; + + /* Set password for a channel */ + void setPassword(const QString &password) { mPassword = password; } + /* Get password for a channel */ + const QString &password() const { return mPassword; } + + /** + * Returns if a mode is enabled for this channel. + * @param mode The mode you want to check ( 't', 'n', etc. ) + * @param value This is a pointer to a QString which is set to + * the value of the mode if it has one. Example, the mode 'l' or + * the mode 'k'. If the mode has no such value then the pointer + * is always returned null. + */ + bool modeEnabled( QChar mode, QString *value = 0 ); + + // Kopete::Contact stuff + virtual QPtrList *customContextMenuActions(); + virtual const QString caption() const; + + //Methods handled by the signal mapper + void userJoinedChannel(const QString &user); + void userPartedChannel(const QString &user, const QString &reason); + void userKicked(const QString &nick, const QString &nickKicked, const QString &reason); + void channelTopic(const QString &topic); + void channelHomePage(const QString &url); + void topicChanged(const QString &nick, const QString &newtopic); + void topicUser(const QString &nick, const QDateTime &time); + void namesList(const QStringList &nicknames); + void endOfNames(); + void incomingModeChange(const QString &nick, const QString &mode); + void incomingChannelMode(const QString &mode, const QString ¶ms ); + void failedChankey(); + void failedChanBanned(); + void failedChanInvite(); + void failedChanFull(); + void newAction(const QString &from, const QString &action); + +public slots: + void updateStatus(); + + /** + * Sets the topic of this channel + * @param topic The topic you want set + */ + void setTopic( const QString &topic = QString::null ); + + /** + * Sets or unsets a mode on this channel + * @param mode The full text of the mode change you want performed + */ + void setMode( const QString &mode = QString::null ); + + void part(); + void partAction(); + void join(); + +protected slots: + void chatSessionDestroyed(); + + virtual void privateMessage(IRCContact *from, IRCContact *to, const QString &message); + virtual void initConversation(); + +private slots: + void slotIncomingUserIsAway( const QString &nick, const QString &reason ); + void slotModeChanged(); + void slotAddNicknames(); + void slotConnectedToServer(); + void slotUpdateInfo(); + void slotHomepage(); + void slotChannelListed(const QString &channel, uint members, const QString &topic); + void slotOnlineStatusChanged(Kopete::Contact *c, const Kopete::OnlineStatus &status, const Kopete::OnlineStatus &oldStatus); + +private: + KAction *actionJoin; + KAction *actionPart; + KAction *actionTopic; + KAction *actionHomePage; + KActionMenu *actionModeMenu; + KCodecAction *codecAction; + + KToggleAction *actionModeT; // Only Operators Can Change Topic + KToggleAction *actionModeN; // No Outside Messages + KToggleAction *actionModeS; // Secret + KToggleAction *actionModeI; // Invite Only + KToggleAction *actionModeM; // Moderated + + QString mTopic; + QString mPassword; + QStringList mJoinedNicks; + QMap modeMap; + QTimer *mInfoTimer; + + void toggleMode( QChar mode, bool enabled, bool update ); + void toggleOperatorActions( bool enabled ); +}; + +#endif diff --git a/kopete/protocols/irc/ircchatui.rc b/kopete/protocols/irc/ircchatui.rc new file mode 100644 index 00000000..9c1b9dbb --- /dev/null +++ b/kopete/protocols/irc/ircchatui.rc @@ -0,0 +1,10 @@ + + + + + IRC + + + + + diff --git a/kopete/protocols/irc/irccontact.cpp b/kopete/protocols/irc/irccontact.cpp new file mode 100644 index 00000000..64f89322 --- /dev/null +++ b/kopete/protocols/irc/irccontact.cpp @@ -0,0 +1,425 @@ +/* + irccontact.cpp - IRC Contact + + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2004 by Michel Hermier + Copyright (c) 2005 by Tommi Rantala + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#include +#include + +#include "ircaccount.h" +#include "kopeteglobal.h" +#include "kopeteuiglobal.h" +#include "kopetemetacontact.h" +#include "kopeteview.h" +#include "ircusercontact.h" +#include "irccontact.h" +#include "ircprotocol.h" +#include "ircservercontact.h" +#include "irccontactmanager.h" +#include "ksparser.h" + +IRCContact::IRCContact(IRCAccount *account, KIRC::EntityPtr entity, Kopete::MetaContact *metac, const QString& icon) + : Kopete::Contact(account, entity->name(), metac, icon), + m_chatSession(0) +{ +} + +IRCContact::IRCContact(IRCContactManager *contactManager, const QString &nick, Kopete::MetaContact *metac, const QString& icon) + : Kopete::Contact(contactManager->account(), nick, metac, icon), + m_nickName(nick), + m_chatSession(0) +{ + KIRC::Engine *engine = kircEngine(); + + // Contact list display name + setProperty( Kopete::Global::Properties::self()->nickName(), m_nickName ); + + // IRCContactManager stuff + QObject::connect(contactManager, SIGNAL(privateMessage(IRCContact *, IRCContact *, const QString &)), + this, SLOT(privateMessage(IRCContact *, IRCContact *, const QString &))); + + // Kopete::ChatSessionManager stuff + mMyself.append( static_cast( this ) ); + + // KIRC stuff + QObject::connect(engine, SIGNAL(incomingNickChange(const QString &, const QString &)), + this, SLOT( slotNewNickChange(const QString&, const QString&))); + QObject::connect(engine, SIGNAL(successfullyChangedNick(const QString &, const QString &)), + this, SLOT(slotNewNickChange(const QString &, const QString &))); + QObject::connect(engine, SIGNAL(incomingQuitIRC(const QString &, const QString &)), + this, SLOT( slotUserDisconnected(const QString&, const QString&))); + + QObject::connect(engine, SIGNAL(statusChanged(KIRC::Engine::Status)), + this, SLOT(updateStatus())); + + engine->setCodec( m_nickName, codec() ); +} + +IRCContact::~IRCContact() +{ +// kdDebug(14120) << k_funcinfo << m_nickName << endl; + if (metaContact() && metaContact()->isTemporary() && !isChatting(m_chatSession)) + metaContact()->deleteLater(); + + emit destroyed(this); +} + +IRCAccount *IRCContact::ircAccount() const +{ + return static_cast(account()); +} + +KIRC::Engine *IRCContact::kircEngine() const +{ + return ircAccount()->engine(); +} + +bool IRCContact::isReachable() +{ + if (onlineStatus().status() != Kopete::OnlineStatus::Offline && + onlineStatus().status() != Kopete::OnlineStatus::Unknown) + return true; + + return false; +} + +const QString IRCContact::caption() const +{ + return QString::null; +} +/* +const QString IRCContact::formatedName() const +{ + return QString::null; +} +*/ +void IRCContact::updateStatus() +{ +} + +void IRCContact::privateMessage(IRCContact *, IRCContact *, const QString &) +{ +} + +void IRCContact::setCodec(const QTextCodec *codec) +{ + kircEngine()->setCodec(m_nickName, codec); + metaContact()->setPluginData(m_protocol, QString::fromLatin1("Codec"), QString::number(codec->mibEnum())); +} + +const QTextCodec *IRCContact::codec() +{ + QString codecId = metaContact()->pluginData(m_protocol, QString::fromLatin1("Codec")); + QTextCodec *codec = ircAccount()->codec(); + + if( !codecId.isEmpty() ) + { + bool test = true; + uint mib = codecId.toInt(&test); + if (test) + codec = QTextCodec::codecForMib(mib); + else + codec = QTextCodec::codecForName(codecId.latin1()); + } + + if( !codec ) + return kircEngine()->codec(); + + return codec; +} + +Kopete::ChatSession *IRCContact::manager(Kopete::Contact::CanCreateFlags canCreate) +{ + IRCAccount *account = ircAccount(); + KIRC::Engine *engine = kircEngine(); + + if (canCreate == Kopete::Contact::CanCreate && !m_chatSession) + { + if( engine->status() == KIRC::Engine::Idle && dynamic_cast(this) == 0 ) + account->connect(); + + m_chatSession = Kopete::ChatSessionManager::self()->create(account->myself(), mMyself, account->protocol()); + m_chatSession->setDisplayName(caption()); + + QObject::connect(m_chatSession, SIGNAL(messageSent(Kopete::Message&, Kopete::ChatSession *)), + this, SLOT(slotSendMsg(Kopete::Message&, Kopete::ChatSession *))); + QObject::connect(m_chatSession, SIGNAL(closing(Kopete::ChatSession *)), + this, SLOT(chatSessionDestroyed())); + + initConversation(); + } + + return m_chatSession; +} + +void IRCContact::chatSessionDestroyed() +{ + m_chatSession = 0; + + if (metaContact()->isTemporary() && !isChatting()) + deleteLater(); +} + +void IRCContact::slotUserDisconnected(const QString &user, const QString &reason) +{ + if (m_chatSession) + { + QString nickname = user.section('!', 0, 0); + Kopete::Contact *c = locateUser( nickname ); + if ( c ) + { + m_chatSession->removeContact(c, i18n("Quit: \"%1\" ").arg(reason), Kopete::Message::RichText); + c->setOnlineStatus(m_protocol->m_UserStatusOffline); + } + } +} + +void IRCContact::setNickName( const QString &nickname ) +{ + kdDebug(14120) << k_funcinfo << m_nickName << " changed to " << nickname << endl; + m_nickName = nickname; + Kopete::Contact::setNickName( nickname ); +} + +void IRCContact::slotNewNickChange(const QString &oldnickname, const QString &newnickname) +{ + IRCAccount *account = ircAccount(); + + IRCContact *user = static_cast( locateUser(oldnickname) ); + if( user ) + { + user->setNickName( newnickname ); + + //If the user is in our contact list, then change the notify list nickname + if (!user->metaContact()->isTemporary()) + { + account->contactManager()->removeFromNotifyList( oldnickname ); + account->contactManager()->addToNotifyList( newnickname ); + } + } +} + +void IRCContact::slotSendMsg(Kopete::Message &message, Kopete::ChatSession *) +{ + QString htmlString = message.escapedBody(); + + // Messages we get with RichText enabled: + // + // Hello world in bold and color: + // Hello World + // + // Two-liner in color: + // Hello
    World
    + + if (htmlString.find(QString::fromLatin1(" -1) + { + QRegExp findTags( QString::fromLatin1("(.*)") ); + findTags.setMinimal( true ); + int pos = 0; + + while (pos >= 0) + { + pos = findTags.search(htmlString); + if (pos > -1) + { + QString styleHTML = findTags.cap(1); + QString replacement = findTags.cap(2); + QStringList styleAttrs = QStringList::split(';', styleHTML); + + for (QStringList::Iterator attrPair = styleAttrs.begin(); attrPair != styleAttrs.end(); ++attrPair) + { + QString attribute = (*attrPair).section(':',0,0); + QString value = (*attrPair).section(':',1); + + if( attribute == QString::fromLatin1("color") ) + { + int ircColor = KSParser::colorForHTML( value ); + if( ircColor > -1 ) + replacement.prepend( QString( QChar(0x03) ).append( QString::number(ircColor) ) ).append( QChar( 0x03 ) ); + } + else if( attribute == QString::fromLatin1("font-weight") && + value == QString::fromLatin1("600") ) { + // Bolding + replacement.prepend( QChar(0x02) ).append( QChar(0x02) ); + } + else if( attribute == QString::fromLatin1("text-decoration") && + value == QString::fromLatin1("underline") ) { + replacement.prepend( QChar(31) ).append( QChar(31) ); + } + } + + htmlString = htmlString.left( pos ) + replacement + htmlString.mid( pos + findTags.matchedLength() ); + } + } + } + + htmlString = Kopete::Message::unescape(htmlString); + + QStringList messages = QStringList::split( '\n', htmlString ); + + for( QStringList::Iterator it = messages.begin(); it != messages.end(); ++it ) + { + // Dont use the resulting string(s). The problem is that we'd have to parse them + // back to format that would be suitable for appendMessage(). + // + // TODO: If the given message was plaintext, we could easily show what was + // actually sent. + + sendMessage(*it); + } + + if (message.requestedPlugin() != CHAT_VIEW) { + Kopete::Message msg(message.from(), message.to(), message.escapedBody(), message.direction(), + Kopete::Message::RichText, CHAT_VIEW, message.type()); + + msg.setBg(QColor()); + msg.setFg(QColor()); + + appendMessage(msg); + } else { + // Lets not modify the given message object. + Kopete::Message msg = message; + msg.setBg(QColor()); + appendMessage(msg); + } + + manager(Kopete::Contact::CanCreate)->messageSucceeded(); +} + +QStringList IRCContact::sendMessage( const QString &msg ) +{ + QStringList messages; + + QString newMessage = msg; + + // IRC limits the message size to 512 characters. So split the given + // message into pieces. + // + // This can of course give nasty results, but most of us dont write + // that long lines anyway ;-)... And this is how other clients also + // seem to behave. + + int l = 500 - m_nickName.length(); + + do { + messages.append(newMessage.mid(0, l)); + newMessage.remove(0, l); + } while (!newMessage.isEmpty()); + + for (QStringList::const_iterator it = messages.begin(); + it != messages.end(); ++it) + kircEngine()->privmsg(m_nickName, *it); + + return messages; +} + +Kopete::Contact *IRCContact::locateUser(const QString &nick) +{ + IRCAccount *account = ircAccount(); + + if (m_chatSession) + { + if( nick == account->mySelf()->nickName() ) + return account->mySelf(); + else + { + Kopete::ContactPtrList mMembers = m_chatSession->members(); + for (Kopete::Contact *it = mMembers.first(); it; it = mMembers.next()) + { + if (static_cast(it)->nickName() == nick) + return it; + } + } + } + return 0; +} + +bool IRCContact::isChatting(const Kopete::ChatSession *avoid) const +{ + IRCAccount *account = ircAccount(); + + if (!account) + return false; + + QValueList sessions = Kopete::ChatSessionManager::self()->sessions(); + for (QValueList::Iterator it= sessions.begin(); it!=sessions.end() ; ++it) + { + if( (*it) != avoid && (*it)->account() == account && + (*it)->members().contains(this) ) + { + return true; + } + } + return false; +} + +void IRCContact::deleteContact() +{ + kdDebug(14120) << k_funcinfo << m_nickName << endl; + + delete m_chatSession; + + if (!isChatting()) + { + kdDebug(14120) << k_funcinfo << "will delete " << m_nickName << endl; + Kopete::Contact::deleteContact(); + } + else + { + metaContact()->removeContact(this); + Kopete::MetaContact *m = new Kopete::MetaContact(); + m->setTemporary(true); + setMetaContact(m); + } +} + +void IRCContact::appendMessage(Kopete::Message &msg) +{ + manager(Kopete::Contact::CanCreate)->appendMessage(msg); +} + +KopeteView *IRCContact::view() +{ + if (m_chatSession) + return m_chatSession->view(false); + return 0L; +} +void IRCContact::serialize(QMap & /*serializedData*/, QMap &addressBookData) +{ + // write the + addressBookData[ protocol()->addressBookIndexField() ] = ( contactId() + QChar(0xE120) + account()->accountId() ); +} + +void IRCContact::receivedMessage( KIRC::Engine::ServerMessageType type, + const KIRC::EntityPtr &from, + const KIRC::EntityPtrList &to, + const QString &msg) +{ + if (to.contains(m_entity)) + { + IRCContact *fromContact = ircAccount()->getContact(from); + Kopete::Message message(fromContact, manager()->members(), msg, Kopete::Message::Inbound, + Kopete::Message::RichText, CHAT_VIEW); + appendMessage(message); + } +} + +#include "irccontact.moc" diff --git a/kopete/protocols/irc/irccontact.h b/kopete/protocols/irc/irccontact.h new file mode 100644 index 00000000..058315fb --- /dev/null +++ b/kopete/protocols/irc/irccontact.h @@ -0,0 +1,153 @@ +/* + irccontact.h - IRC Contact + + Copyright (c) 2005 by Tommi Rantala + Copyright (c) 2003-2004 by Michel Hermier + Copyright (c) 2003 by Jason Keirstead + Copyright (c) 2002 by Nick Betcher + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef IRCCONTACT_H +#define IRCCONTACT_H + +#include "kircengine.h" +#include "kircentity.h" + +#include "kopetecontact.h" +#include "kopetemessage.h" + +#include +#include + +class IRCProtocol; +class IRCAccount; +class IRCContactManager; + +namespace KIRC +{ +class Engine; +} + +namespace Kopete +{ +class ChatSession; +class MetaContact; +} + +class KopeteView; + +class QTextCodec; + +/** + * @author Jason Keirstead + * @author Michel Hermier + * + * This class is the base class for @ref IRCUserContact and @ref IRCChannelContact. + * Common routines and signal connections that are required for both types of + * contacts reside here, to avoid code duplication between these two classes. + */ +class IRCContact + : public Kopete::Contact +{ + Q_OBJECT + +public: + IRCContact(IRCAccount *account, KIRC::EntityPtr entity, Kopete::MetaContact *metac, const QString& icon = QString::null); + IRCContact(IRCContactManager *contactManager, const QString &nick, Kopete::MetaContact *metac, const QString& icon = QString::null); + virtual ~IRCContact(); + + IRCAccount *ircAccount() const; + KIRC::Engine *kircEngine() const; + + /** + * Sets the nickname of this contact. The nickname is distinct from the displayName + * in case trackNameChanges is disabled. + */ + void setNickName(const QString &nickname); + + /** + * Returns the nickname / channel name + */ + const QString &nickName() const { return m_nickName; } + + /** + * This function attempts to find the nickname specified within the current chat + * session. Returns a pointer to that IRCUserContact, or 0L if the user does not + * exist in this session. More useful for channels. Calling IRCChannelContact::locateUser() + * for example tells you if a user is in a certain channel. + */ + Kopete::Contact *locateUser( const QString &nickName ); + + virtual bool isReachable(); + + /** + * return true if the contact is in a chat. false if the contact is in no chats + * that loop over all manager, and checks the presence of the user + */ + bool isChatting( const Kopete::ChatSession *avoid = 0L ) const; + + virtual const QString caption() const; +// virtual const QString formatedName() const; + + virtual Kopete::ChatSession *manager(Kopete::Contact::CanCreateFlags = Kopete::Contact::CannotCreate); + + virtual void appendMessage( Kopete::Message & ); + + const QTextCodec *codec(); + + KopeteView *view(); + + /** + * We serialise the contactId and the server group in 'contactId' + * so that other IRC programs reading this from KAddressBook have a chance of figuring + * which server the contact relates to + */ + virtual void serialize( QMap &serializedData, QMap &addressBookData ); + +signals: + void destroyed(IRCContact *self); + +public slots: + void setCodec( const QTextCodec *codec ); + virtual void updateStatus(); + +protected slots: + virtual void slotSendMsg(Kopete::Message &message, Kopete::ChatSession *); + QStringList sendMessage( const QString &msg ); + + virtual void chatSessionDestroyed(); + + void slotNewNickChange( const QString &oldnickname, const QString &newnickname); + void slotUserDisconnected( const QString &nickname, const QString &reason); + + virtual void deleteContact(); + virtual void privateMessage(IRCContact *from, IRCContact *to, const QString &message); + virtual void initConversation() {}; + + void receivedMessage( KIRC::Engine::ServerMessageType type, + const KIRC::EntityPtr &from, + const KIRC::EntityPtrList &to, + const QString &msg); + +protected: + KIRC::EntityPtr m_entity; + + QString m_nickName; + Kopete::ChatSession *m_chatSession; + + QPtrList mMyself; + Kopete::Message::MessageDirection execDir; +}; + +#endif diff --git a/kopete/protocols/irc/irccontactmanager.cpp b/kopete/protocols/irc/irccontactmanager.cpp new file mode 100644 index 00000000..7808668b --- /dev/null +++ b/kopete/protocols/irc/irccontactmanager.cpp @@ -0,0 +1,297 @@ +/* + irccontactmanager.cpp - Manager of IRC Contacts + + Copyright (c) 2003 by Michel Hermier + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "ircusercontact.h" +#include "ircaccount.h" +#include "irccontactmanager.h" +#include "ircprotocol.h" +#include "ircsignalhandler.h" + +#include "ircservercontact.h" +#include "ircchannelcontact.h" + +#include "kircengine.h" + +#include +#include +#include +#include + +#include +#include + +#include + +IRCContactManager::IRCContactManager(const QString &nickName, IRCAccount *account, const char *name) + : QObject(account, name), + m_channels( QDict( 17, false ) ), + m_users( QDict( 577, false ) ), + m_account( account ) +{ + m_mySelf = findUser(nickName); + + Kopete::MetaContact *m = new Kopete::MetaContact(); +// m->setTemporary( true ); + m_myServer = new IRCServerContact(this, account->networkName(), m); + + QObject::connect(account->engine(), SIGNAL(incomingMessage(const QString &, const QString &, const QString &)), + this, SLOT(slotNewMessage(const QString &, const QString &, const QString &))); + + QObject::connect(account->engine(), SIGNAL(incomingPrivMessage(const QString &, const QString &, const QString &)), + this, SLOT(slotNewPrivMessage(const QString &, const QString &, const QString &))); + + QObject::connect(account->engine(), SIGNAL(incomingNickChange(const QString &, const QString &)), + this, SLOT( slotNewNickChange(const QString&, const QString&))); + + QObject::connect(account->engine(), SIGNAL(successfullyChangedNick(const QString &, const QString &)), + this, SLOT( slotNewNickChange(const QString &, const QString &))); + + QObject::connect(account->engine(), SIGNAL(incomingUserOnline(const QString &)), + this, SLOT( slotIsonRecieved())); + + QObject::connect(Kopete::ContactList::self(), SIGNAL(metaContactAdded( Kopete::MetaContact * )), + this, SLOT( slotContactAdded( Kopete::MetaContact* ))); + + socketTimeout = 15000; + QString timeoutPath = locate( "config", "kioslaverc" ); + if( !timeoutPath.isEmpty() ) + { + KConfig config( timeoutPath ); + socketTimeout = config.readNumEntry( "ReadTimeout", 15 ) * 1000; + } + + m_NotifyTimer = new QTimer(this); + QObject::connect(m_NotifyTimer, SIGNAL(timeout()), + this, SLOT(checkOnlineNotifyList())); + m_NotifyTimer->start(30000); // check online every 30sec + + new IRCSignalHandler(this); +} + +void IRCContactManager::slotNewNickChange(const QString &oldnick, const QString &newnick) +{ + IRCUserContact *c = m_users[ oldnick ]; + if( c ) + { + m_users.insert(newnick, c); + m_users.remove(oldnick); + } +} + +void IRCContactManager::slotNewMessage(const QString &originating, const QString &channel, const QString &message) +{ + IRCContact *from = findUser(originating); + IRCChannelContact *to = findChannel(channel); + emit privateMessage(from, to, message); +} + +void IRCContactManager::slotNewPrivMessage(const QString &originating, const QString &user, const QString &message) +{ + IRCContact *from = findUser(originating); + IRCUserContact *to = findUser(user); + emit privateMessage(from, to, message); +} + +void IRCContactManager::unregister(Kopete::Contact *contact) +{ + unregisterChannel(contact, true); + unregisterUser(contact, true); +} + +QValueList IRCContactManager::findChannelsByMember( IRCUserContact *contact ) +{ + QValueList retVal; + for( QDictIterator it(m_channels); it.current(); ++it ) + { + if( it.current()->manager(Kopete::Contact::CannotCreate) ) + { + if( contact == m_mySelf ) + retVal.push_back( it.current() ); + else + { + bool c = true; + + Kopete::ContactPtrList members = it.current()->manager()->members(); + for( QPtrListIterator it2( members ); c && it2.current(); ++it2 ) + { + if( it2.current() == contact ) + { + retVal.push_back( it.current() ); + c = false; + } + } + } + } + } + + return retVal; +} + +IRCChannelContact *IRCContactManager::findChannel(const QString &name, Kopete::MetaContact *m) +{ + IRCChannelContact *channel = m_channels[ name ]; + + if ( !channel ) + { + if( !m ) + { + m = new Kopete::MetaContact(); + m->setTemporary( true ); + } + + channel = new IRCChannelContact(this, name, m); + m_channels.insert( name, channel ); + QObject::connect(channel, SIGNAL(contactDestroyed(Kopete::Contact *)), + this, SLOT(unregister(Kopete::Contact *))); + } + + return channel; +} + +IRCChannelContact *IRCContactManager::existChannel( const QString &channel ) const +{ + return m_channels[ channel ]; +} + +void IRCContactManager::unregisterChannel(Kopete::Contact *contact, bool force ) +{ + IRCChannelContact *channel = (IRCChannelContact*)contact; + if( force || ( + channel!=0 && + !channel->isChatting() && + channel->metaContact()->isTemporary() ) ) + { + m_channels.remove( channel->nickName() ); + } +} + +IRCUserContact *IRCContactManager::findUser(const QString &name, Kopete::MetaContact *m) +{ + IRCUserContact *user = m_users[name.section('!', 0, 0)]; + + if ( !user ) + { + if( !m ) + { + m = new Kopete::MetaContact(); + m->setTemporary( true ); + } + + user = new IRCUserContact(this, name, m); + m_users.insert( name, user ); + QObject::connect(user, SIGNAL(contactDestroyed(Kopete::Contact *)), + this, SLOT(unregister(Kopete::Contact *))); + } + + return user; +} + +IRCUserContact *IRCContactManager::existUser( const QString &user ) const +{ + return m_users[user]; +} + +IRCContact *IRCContactManager::findContact( const QString &id, Kopete::MetaContact *m ) +{ + if( KIRC::Entity::isChannel(id) ) + return findChannel( id, m ); + else + return findUser( id, m ); +} + +IRCContact *IRCContactManager::existContact( const KIRC::Engine *engine, const QString &id ) +{ + QDict accounts = Kopete::AccountManager::self()->accounts( IRCProtocol::protocol() ); + QDictIterator it(accounts); + for( ; it.current(); ++it ) + { + IRCAccount *account = (IRCAccount *)it.current(); + if( account && account->engine() == engine ) + return account->contactManager()->existContact(id); + } + return 0L; +} + +IRCContact *IRCContactManager::existContact( const QString &id ) const +{ + if( KIRC::Entity::isChannel(id) ) + return existChannel( id ); + else + return existUser( id ); +} + +void IRCContactManager::unregisterUser(Kopete::Contact *contact, bool force ) +{ + IRCUserContact *user = (IRCUserContact *)contact; + if( force || ( + user!=0 && + user!=mySelf() && + !user->isChatting() && + user->metaContact()->isTemporary() ) ) + { + m_users.remove( user->nickName() ); + } +} + +void IRCContactManager::slotContactAdded( Kopete::MetaContact *contact ) +{ + for( QPtrListIterator it( contact->contacts() ); it.current(); ++it ) + { + if( it.current()->account() == m_account ) + { + addToNotifyList( static_cast( it.current() )->nickName() ); + } + } +} + +void IRCContactManager::addToNotifyList(const QString &nick) +{ + if (!m_NotifyList.contains(nick.lower())) + { + m_NotifyList.append(nick); + checkOnlineNotifyList(); + } +} + +void IRCContactManager::removeFromNotifyList(const QString &nick) +{ + if (m_NotifyList.contains(nick.lower())) + m_NotifyList.remove(nick.lower()); +} + +void IRCContactManager::checkOnlineNotifyList() +{ + if( m_account->engine()->isConnected() ) + { + isonRecieved = false; + m_account->engine()->ison( m_NotifyList ); + //QTimer::singleShot( socketTimeout, this, SLOT( slotIsonTimeout() ) ); + } +} + +void IRCContactManager::slotIsonRecieved() +{ + isonRecieved = true; +} + +void IRCContactManager::slotIsonTimeout() +{ + if( !isonRecieved ) + m_account->engine()->quit("", true); +} + +#include "irccontactmanager.moc" diff --git a/kopete/protocols/irc/irccontactmanager.h b/kopete/protocols/irc/irccontactmanager.h new file mode 100644 index 00000000..4a8ae05f --- /dev/null +++ b/kopete/protocols/irc/irccontactmanager.h @@ -0,0 +1,117 @@ +/* + irccontactmanager.h - Manager of IRC Contacts + + Copyright (c) 2003 by Michel Hermier + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef IRCCONTACTMANAGER_H +#define IRCCONTACTMANAGER_H + +#include +#include +#include +#include + +class IRCContact; +class IRCAccount; + +class IRCServerContact; +class IRCChannelContact; +class IRCUserContact; + +namespace KIRC +{ +class Engine; +} + +namespace Kopete +{ +class Contact; +class MetaContact; +} + +class KopeteView; + +class QTimer; + +/** + * @author Michel Hermier + * + * This class is the repository for all the reference of the @ref IRCContact childs. + * It manage the life cycle of all the @ref IRCServerContact, @ref IRCChannelContact and @ref IRCUserContact objects for the given account. + */ +class IRCContactManager + : public QObject +{ + Q_OBJECT + + public: + IRCContactManager(const QString &nickName, IRCAccount *account, const char *name=0); + + IRCAccount *account() const { return m_account; } + + IRCServerContact *myServer() const { return m_myServer; } + IRCUserContact *mySelf() const { return m_mySelf; } + + IRCChannelContact *findChannel(const QString &channel, Kopete::MetaContact *m=0); + IRCChannelContact *existChannel(const QString &channel) const; + + IRCUserContact *findUser(const QString &nick, Kopete::MetaContact *m=0); + IRCUserContact *existUser(const QString &nick) const; + + IRCContact *findContact(const QString &nick, Kopete::MetaContact *m=0); + IRCContact *existContact( const QString &id ) const; + + QValueList findChannelsByMember( IRCUserContact *contact ); + + static IRCContact *existContact(const KIRC::Engine *engine, const QString &nick); + + public slots: + void unregister(Kopete::Contact *contact); + void unregisterUser(Kopete::Contact *contact, bool force = false ); + void unregisterChannel(Kopete::Contact *contact, bool force = false ); + + void addToNotifyList(const QString &nick); + void removeFromNotifyList(const QString &nick); + void checkOnlineNotifyList(); + + signals: + void privateMessage(IRCContact *from, IRCContact *to, const QString &message); + + private slots: + void slotNewMessage(const QString &originating, const QString &channel, const QString &message); + void slotNewPrivMessage(const QString &originating, const QString &, const QString &message); + void slotIsonRecieved(); + void slotIsonTimeout(); + void slotNewNickChange(const QString &oldnick, const QString &newnick); + void slotContactAdded( Kopete::MetaContact *contact ); + + private: + QDict m_channels; + QDict m_users; + + IRCAccount *m_account; + IRCServerContact *m_myServer; + IRCUserContact *m_mySelf; + + QStringList m_NotifyList; + QTimer *m_NotifyTimer; + bool isonRecieved; + int socketTimeout; + + static const QRegExp isChannel; +}; + +#endif + diff --git a/kopete/protocols/irc/ircguiclient.cpp b/kopete/protocols/irc/ircguiclient.cpp new file mode 100644 index 00000000..b4c36973 --- /dev/null +++ b/kopete/protocols/irc/ircguiclient.cpp @@ -0,0 +1,100 @@ +/* + ircguiclient.cpp + + Copyright (c) 2003 by Jason Keirstead + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include + +#include +#if KDE_IS_VERSION( 3, 1, 90 ) + #include +#else +// ------------------------------------------------------------ +// TODO: UGLY HACK, remove when we drop KDE 3.1 compatibility +#ifdef KDE_NO_COMPAT +#undef KDE_NO_COMPAT +#include +#define KDE_NO_COMPAT 1 +#endif +// ------------------------------------------------------------ +#endif + +#include +#include +#include + +#include "kopetechatsession.h" +#include "kcodecaction.h" +#include "ircguiclient.h" +#include "ircaccount.h" +#include "irccontact.h" + +IRCGUIClient::IRCGUIClient( Kopete::ChatSession *parent ) : QObject(parent) , KXMLGUIClient(parent) +{ + Kopete::ContactPtrList members = parent->members(); + if( members.count() > 0 ) + { + m_user = static_cast( members.first() ); + + /*** + FIXME: Why doesn't this work???? Have to use DOM hack below now... + + setXMLFile("ircchatui.rc"); + + unplugActionList( "irccontactactionlist" ); + QPtrList *actions = m_user->customContextMenuActions( parent ); + plugActionList( "irccontactactionlist", *actions ); + delete actions; + */ + + setXMLFile("ircchatui.rc"); + + QDomDocument doc = domDocument(); + QDomNode menu = doc.documentElement().firstChild().firstChild(); + QPtrList *actions = m_user->customContextMenuActions( parent ); + if( actions ) + { + for( KAction *a = actions->first(); a; a = actions->next() ) + { + actionCollection()->insert( a ); + QDomElement newNode = doc.createElement( "Action" ); + newNode.setAttribute( "name", a->name() ); + menu.appendChild( newNode ); + } + } + else + { + kdDebug(14120) << k_funcinfo << "Actions == 0" << endl; + } + + delete actions; + + setDOMDocument( doc ); + } + else + { + kdDebug(14120) << k_funcinfo << "Members == 0" << endl; + } +} + +IRCGUIClient::~IRCGUIClient() +{ +} + +void IRCGUIClient::slotSelectCodec( const QTextCodec *codec ) +{ + m_user->setCodec( codec ); +} + +#include "ircguiclient.moc" diff --git a/kopete/protocols/irc/ircguiclient.h b/kopete/protocols/irc/ircguiclient.h new file mode 100644 index 00000000..b81aa632 --- /dev/null +++ b/kopete/protocols/irc/ircguiclient.h @@ -0,0 +1,42 @@ +/* + ircguiclient.h + + Copyright (c) 2003 by Jason Keirstead + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef IRCGUICLIENT_H +#define IRCGUICLIENT_H + +#include +#include + +namespace Kopete { class ChatSession; } +class IRCContact; + +/** + *@author Jason Keirstead + */ +class IRCGUIClient : public QObject , public KXMLGUIClient +{ + Q_OBJECT + public: + IRCGUIClient( Kopete::ChatSession *parent = 0 ); + ~IRCGUIClient(); + + private slots: + void slotSelectCodec( const QTextCodec *codec ); + + private: + IRCContact *m_user; +}; + +#endif diff --git a/kopete/protocols/irc/ircnetworks.xml b/kopete/protocols/irc/ircnetworks.xml new file mode 100644 index 00000000..c743e9e0 --- /dev/null +++ b/kopete/protocols/irc/ircnetworks.xml @@ -0,0 +1,1463 @@ + + + + AnyNet + AnyNet + + + irc.anynet.org + 6667 + false + + + + + IRCNet + IRCNet + + + eu.ircnet.org + 6667 + false + + + irc.ircd.it + 6667 + false + + + au.ircnet.org + 6667 + false + + + irc.stealth.net + 6660 + false + + + + + KewlNet + KewlNet + + + irc.kewl.org + 6667 + false + + + la.defense.fr.eu.kewl.org + 6667 + false + + + nanterre.fr.eu.kewl.org + 6667 + false + + + + + TrekLink + TrekLink + + + neutron.treklink.net + 6667 + false + + + irc.treklink.net + 6667 + false + + + + + SlashNET + SlashNET + + + irc.slashnet.org + 6667 + false + + + area51.slashnet.org + 6667 + false + + + moo.slashnet.org + 6667 + false + + + radon.slashnet.org + 6667 + false + + + devnull.slashnet.org + 6667 + false + + + + + NeverNET + NeverNET + + + irc.nevernet.net + 6667 + false + + + imagine.nevernet.net + 6667 + false + + + dimension.nevernet.net + 6667 + false + + + universe.nevernet.net + 6667 + false + + + wayland.nevernet.net + 6667 + false + + + forte.nevernet.net + 6667 + false + + + + + CoolChat + CoolChat + + + irc.coolchat.net + 6667 + false + + + unix.coolchat.net + 6667 + false + + + south.coolchat.net + 6667 + false + + + toronto.coolchat.net + 6667 + false + + + + + UnderNet + UnderNet + + + us.undernet.org + 6667 + false + + + eu.undernet.org + 6667 + false + + + + + MagicStar + MagicStar + + + irc.magicstar.net + 6667 + false + + + + + PTNet, UNI + PTNet, UNI + + + irc.PTNet.org + 6667 + false + + + rccn.PTnet.org + 6667 + false + + + uevora.PTnet.org + 6667 + false + + + umoderna.PTnet.org + 6667 + false + + + ist.PTnet.org + 6667 + false + + + aaum.PTnet.org + 6667 + false + + + uc.PTnet.org + 6667 + false + + + ualg.ptnet.org + 6667 + false + + + madinfo.PTnet.org + 6667 + false + + + isep.PTnet.org + 6667 + false + + + ua.PTnet.org + 6667 + false + + + ipg.PTnet.org + 6667 + false + + + isec.PTnet.org + 6667 + false + + + utad.PTnet.org + 6667 + false + + + iscte.PTnet.org + 6667 + false + + + ubi.PTnet.org + 6667 + false + + + + + FDFNet + FDFNet + + + irc.fdfnet.net + 6667 + false + + + irc.eu.fdfnet.net + 6667 + false + + + + + FEFNet + FEFNet + + + irc.fef.net + 6667 + false + + + irc.villagenet.com + 6667 + false + + + irc.ggn.net + 6667 + false + + + irc.vendetta.com + 6667 + false + + + + + KrushNet.Org + KrushNet.Org + + + Jeffersonville.IN.US.KrushNet.Org + 6667 + false + + + Auckland.NZ.KrushNet.Org + 6667 + false + + + Hastings.NZ.KrushNet.Org + 6667 + false + + + Seattle-R.WA.US.KrushNet.Org + 6667 + false + + + Minneapolis.MN.US.KrushNet.Org + 6667 + false + + + Cullowhee.NC.US.KrushNet.Org + 6667 + false + + + Asheville-R.NC.US.KrushNet.Org + 6667 + false + + + San-Antonio.TX.US.KrushNet.Org + 6667 + false + + + + + AfterNET + AfterNET + + + irc.afternet.org + 6667 + false + + + ic5.eu.afternet.org + 6667 + false + + + baltimore.md.us.afternet.org + 6667 + false + + + boston.afternet.org + 6667 + false + + + + + DragonLynk + DragonLynk + + + irc.dragonlynk.net + 6667 + false + + + + + EFNet + EFNet + + + us.rr.efnet.net + 6667 + false + + + irc.arcti.ca + 6667 + false + + + eu.rr.efnet.net + 6667 + false + + + au.rr.efnet.net + 6667 + false + + + irc.efnet.org + 6667 + false + + + irc.light.se + 6667 + false + + + irc.stanford.edu + 6667 + false + + + irc.solidstreaming.net + 6667 + false + + + + + IrcLink + IrcLink + + + irc.irclink.net + 6667 + false + + + Alesund.no.eu.irclink.net + 6667 + false + + + Oslo.no.eu.irclink.net + 6667 + false + + + frogn.no.eu.irclink.net + 6667 + false + + + tonsberg.no.eu.irclink.net + 6667 + false + + + + + AstroLINK.Org + AstroLINK.Org + + + irc.astrolink.org + 6667 + false + + + + + GalaxyNet + GalaxyNet + + + sprynet.us.galaxynet.org + 6667 + false + + + atlanta.ga.us.galaxynet.org + 6667 + false + + + irc.galaxynet.org + 6667 + false + + + + + SceneNet + SceneNet + + + irc.scene.org + 6667 + false + + + irc.eu.scene.org + 6667 + false + + + irc.us.scene.org + 6667 + false + + + + + EUIrc + EUIrc + + + irc.euirc.net + 6667 + false + + + irc.ham.de.euirc.net + 6667 + false + + + irc.ber.de.euirc.net + 6667 + false + + + irc.ffm.de.euirc.net + 6667 + false + + + irc.bre.de.euirc.net + 6667 + false + + + irc.hes.de.euirc.net + 6667 + false + + + irc.vie.at.euirc.net + 6667 + false + + + irc.inn.at.euirc.net + 6667 + false + + + irc.bas.ch.euirc.net + 6667 + false + + + + + RebelChat + RebelChat + + + irc.rebelchat.org + 6667 + false + + + interquad.rebelchat.org + 6667 + false + + + rebel.rebelchat.org + 6667 + false + + + bigcove.rebelchat.org + 6667 + false + + + + + ARCNet + ARCNet + + + se1.arcnet.vapor.com + 6667 + false + + + us1.arcnet.vapor.com + 6667 + false + + + us2.arcnet.vapor.com + 6667 + false + + + us3.arcnet.vapor.com + 6667 + false + + + ca1.arcnet.vapor.com + 6667 + false + + + de1.arcnet.vapor.com + 6667 + false + + + de3.arcnet.vapor.com + 6667 + false + + + ch1.arcnet.vapor.com + 6667 + false + + + be1.arcnet.vapor.com + 6667 + false + + + nl3.arcnet.vapor.com + 6667 + false + + + uk1.arcnet.vapor.com + 6667 + false + + + uk2.arcnet.vapor.com + 6667 + false + + + uk3.arcnet.vapor.com + 6667 + false + + + fr1.arcnet.vapor.com + 6667 + false + + + + + Librenet + Librenet + + + irc.librenet.net + 6667 + false + + + famipow.fr.librenet.net + 6667 + false + + + ielf.fr.librenet.net + 6667 + false + + + + + SubCultNet + SubCultNet + + + irc.subcult.ch + 6667 + false + + + irc.phuncrew.ch + 6668 + false + + + irc.mgz.ch + 6667 + false + + + + + freenode + freenode, a service by Peer-directed Projects Center + + + irc.kde.org + 6667 + false + + + chat.freenode.net + 6667 + false + + + chat.us.freenode.net + 6667 + false + + + chat.eu.freenode.net + 6667 + false + + + chat.au.freenode.net + 6667 + false + + + + + OFTC + The Open and Free Technology Community + + + irc.oftc.net + 6667 + false + + + ircs.oftc.net + 9999 + true + + + + + XWorld + XWorld + + + Buffalo.NY.US.XWorld.org + 6667 + false + + + Minneapolis.MN.US.Xworld.Org + 6667 + false + + + PalmSprings.CA.US.XWorld.Org + 6667 + false + + + Quebec.QC.CA.XWorld.Org + 6667 + false + + + Rochester.NY.US.XWorld.org + 6667 + false + + + Bayern.DE.EU.XWorld.Org + 6667 + false + + + Chicago.IL.US.XWorld.Org + 6667 + false + + + + + ChatNet + ChatNet + + + US.ChatNet.Org + 6667 + false + + + EU.ChatNet.Org + 6667 + false + + + + + Neohorizon + Neohorizon + + + irc.nhn.net + 6667 + false + + + + + QChat.net + QChat.net + + + irc.qchat.net + 6667 + false + + + + + StarChat + StarChat + + + irc.starchat.net + 6667 + false + + + galatea.starchat.net + 6667 + false + + + stargate.starchat.net + 6667 + false + + + powerzone.starchat.net + 6667 + false + + + utopia.starchat.net + 6667 + false + + + cairns.starchat.net + 6667 + false + + + + + Infinity-IRC.org + Infinity-IRC.org + + + Atlanta.GA.US.Infinity-IRC.Org + 6667 + false + + + Babylon.NY.US.Infinity-IRC.Org + 6667 + false + + + Dewspeak.TX.US.Infinity-IRC.Org + 6667 + false + + + Sunshine.Ca.US.Infinity-IRC.Org + 6667 + false + + + MNC.MD.Infinity-IRC.Org + 6667 + false + + + IRC.Infinity-IRC.Org + 6667 + false + + + + + HabberNet + HabberNet + + + irc.habber.net + 6667 + false + + + + + Mellorien + Mellorien + + + Irc.mellorien.net + 6667 + false + + + us.mellorien.net + 6667 + false + + + eu.mellorien.net + 6667 + false + + + + + DwarfStarNet + DwarfStarNet + + + IRC.dwarfstar.net + 6667 + false + + + US.dwarfstar.net + 6667 + false + + + EU.dwarfstar.net + 6667 + false + + + AU.dwarfstar.net + 6667 + false + + + + + ChatJunkiesNet + ChatJunkiesNet + + + irc.xchat.org + 6667 + false + + + us.xchat.org + 6667 + false + + + + + OtherNet + OtherNet + + + irc.othernet.org + 6667 + false + + + + + AxeNet + AxeNet + + + irc.axenet.org + 6667 + false + + + angel.axenet.org + 6667 + false + + + energy.axenet.org + 6667 + false + + + python.axenet.org + 6667 + false + + + + + unsecurity.org + unsecurity.org + + + irc.unsecurity.org + 6667 + false + + + wc.unsecurity.org + 6667 + false + + + thegift.unsecurity.org + 6667 + false + + + sysgate.unsecurity.org + 6667 + false + + + + + DALNet + DALNet + + + irc.dal.net + 6667 + false + + + irc.eu.dal.net + 6667 + false + + + + + AustNet + AustNet + + + us.austnet.org + 6667 + false + + + ca.austnet.org + 6667 + false + + + au.austnet.org + 6667 + false + + + + + PTNet, ISP's + PTNet, ISP's + + + irc.PTNet.org + 6667 + false + + + rccn.PTnet.org + 6667 + false + + + EUnet.PTnet.org + 6667 + false + + + madinfo.PTnet.org + 6667 + false + + + netc2.PTnet.org + 6667 + false + + + netc1.PTnet.org + 6667 + false + + + teleweb.PTnet.org + 6667 + false + + + netway.PTnet.org + 6667 + false + + + telepac1.ptnet.org + 6667 + false + + + services.ptnet.org + 6667 + false + + + esoterica.PTnet.org + 6667 + false + + + ip-hub.ptnet.org + 6667 + false + + + telepac1.ptnet.org + 6667 + false + + + nortenet.PTnet.org + 6667 + false + + + + + NixHelpNet + NixHelpNet + + + irc.nixhelp.org + 6667 + false + + + us.nixhelp.org + 6667 + false + + + uk.nixhelp.org + 6667 + false + + + uk2.nixhelp.org + 6667 + false + + + uk3.nixhelp.org + 6667 + false + + + nl.nixhelp.org + 6667 + false + + + ca.ld.nixhelp.org + 6667 + false + + + us.co.nixhelp.org + 6667 + false + + + us.ca.nixhelp.org + 6667 + false + + + us.pa.nixhelp.org + 6667 + false + + + + + Gamma Force + Gamma Force + + + irc.gammaforce.org + 6667 + false + + + sphinx.or.us.gammaforce.org + 6667 + false + + + monolith.ok.us.gammaforce.org + 6667 + false + + + + + PTlink + PTlink + + + irc.PTlink.net + 6667 + false + + + dark.PTlink.net + 6667 + false + + + uc.PTlink.net + 6667 + false + + + kungfoo.PTlink.net + 6667 + false + + + matrix.PTlink.net + 6667 + false + + + illusion.PTlink.net + 6667 + false + + + Cibercultura.PTlink.net + 6667 + false + + + aaia.PTlink.net + 6667 + false + + + gaesi.PTlink.net + 6667 + false + + + BuBix.PTlink.net + 6667 + false + + + montijo.PTlink.net + 6667 + false + + + queima.PTlink.net + 6667 + false + + + + + OzNet + OzNet + + + sydney.oz.org + 6667 + false + + + melbourne.oz.org + 6667 + false + + + + + FoxChat + FoxChat + + + irc.FoxChat.net + 6667 + false + + + irc.ac6.org + 6667 + false + + + beastie.ac6.org + 6667 + false + + + wild.FoxChat.net + 6667 + false + + + roadkill.FoxChat.net + 6667 + false + + + slick.FoxChat.net + 6667 + false + + + + + AzzurraNet + AzzurraNet + + + irc.bitchx.it + 6667 + false + + + irc.jnet.it + 6667 + false + + + irc.net36.com + 6667 + false + + + irc.noflyzone.net + 6667 + false + + + irc.swappoint.com + 6667 + false + + + irc.azzurra.com + 6667 + false + + + irc.leonet.it + 6667 + false + + + irc.libero.it + 6667 + false + + + irc.estranet.it + 6667 + false + + + irc.filmaker.it + 6667 + false + + + + + AbleNET + AbleNET + + + california.ablenet.org + 6667 + false + + + amazon.ablenet.org + 6667 + false + + + agora.ablenet.org + 6667 + false + + + extreme.ablenet.org + 6667 + false + + + irc.ablenet.org + 6667 + false + + + + diff --git a/kopete/protocols/irc/ircprotocol.cpp b/kopete/protocols/irc/ircprotocol.cpp new file mode 100644 index 00000000..176c74d7 --- /dev/null +++ b/kopete/protocols/irc/ircprotocol.cpp @@ -0,0 +1,1241 @@ +/* + ircprotocol - IRC Protocol + + Copyright (c) 2002 by Nick Betcher + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "ircaccount.h" +#include "ircprotocol.h" +#include "ksparser.h" + +#include "ircaddcontactpage.h" +#include "ircchannelcontact.h" +#include "irccontactmanager.h" + +#include "networkconfig.h" +#include "channellist.h" +#include "ircguiclient.h" +#include "ircusercontact.h" +#include "irceditaccountwidget.h" +#include "irctransferhandler.h" + +#include "kircengine.h" + +#include "kopeteaccountmanager.h" +#include "kopetecommandhandler.h" +#include "kopeteglobal.h" +#include "kopeteonlinestatusmanager.h" +#include "kopeteonlinestatus.h" +#include "kopeteview.h" +#include "kopeteuiglobal.h" + +#undef KDE_NO_COMPAT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +typedef KGenericFactory IRCProtocolFactory; +K_EXPORT_COMPONENT_FACTORY( kopete_irc, IRCProtocolFactory( "kopete_irc" ) ) + +IRCProtocol *IRCProtocol::s_protocol = 0L; + +IRCProtocolHandler::IRCProtocolHandler() : Kopete::MimeTypeHandler( false ) +{ + registerAsProtocolHandler( QString::fromLatin1("irc") ); +} + +void IRCProtocolHandler::handleURL( const KURL &url ) const +{ + kdDebug(14120) << url << endl; + if( !url.isValid() ) + return; + + unsigned short port = url.port(); + if( port == 0 ) + port = 6667; + + QString chan = url.url().section('/',3); + if( chan.isEmpty() ) + return; + + KUser user( getuid() ); + QString accountId = QString::fromLatin1("%1@%2:%3").arg( + user.loginName(), + url.host(), + QString::number(port) + ); + + kdDebug(14120) << accountId << endl; + + IRCAccount *newAccount = new IRCAccount( IRCProtocol::protocol(), accountId, chan ); + newAccount->setNickName( user.loginName() ); + newAccount->setUserName( user.loginName() ); + newAccount->connect(); +} + +IRCProtocol::IRCProtocol( QObject *parent, const char *name, const QStringList & /* args */ ) +: Kopete::Protocol( IRCProtocolFactory::instance(), parent, name ), + + m_ServerStatusOnline(Kopete::OnlineStatus::Online, + 100, this, OnlineServer, QString::null, i18n("Online")), + m_ServerStatusOffline(Kopete::OnlineStatus::Offline, + 90, this, OfflineServer, QString::null, i18n("Offline")), + + m_ChannelStatusOnline(Kopete::OnlineStatus::Online, + 80, this, OnlineChannel, QString::null, i18n("Online")), + m_ChannelStatusOffline(Kopete::OnlineStatus::Offline, + 70, this, OfflineChannel, QString::null, i18n("Offline")), + + m_UserStatusOpVoice(Kopete::OnlineStatus::Online, + 60, this, Online | Operator | Voiced, QStringList::split(' ',"irc_voice irc_op"), i18n("Op")), + m_UserStatusOpVoiceAway(Kopete::OnlineStatus::Away, + 55, this, Online | Operator | Voiced | Away, + QStringList::split(' ',"irc_voice irc_op contact_away_overlay"), i18n("Away")), + + m_UserStatusOp(Kopete::OnlineStatus::Online, + 50, this, Online | Operator, "irc_op", i18n("Op")), + m_UserStatusOpAway(Kopete::OnlineStatus::Away, + 45, this, Online | Operator | Away, + QStringList::split(' ',"irc_op contact_away_overlay"), i18n("Away")), + + m_UserStatusVoice(Kopete::OnlineStatus::Online, + 40, this, Online | Voiced, "irc_voice", i18n("Voice")), + m_UserStatusVoiceAway(Kopete::OnlineStatus::Away, + 35, this, Online | Voiced | Away, + QStringList::split(' ',"irc_voice contact_away_overlay"), i18n("Away")), + + m_UserStatusOnline(Kopete::OnlineStatus::Online, + 25, this, Online, QString::null, i18n("Online"), i18n("Online"), Kopete::OnlineStatusManager::Online), + + m_UserStatusAway(Kopete::OnlineStatus::Away, + 2, this, Online | Away, "contact_away_overlay", + i18n("Away"), i18n("Away"), Kopete::OnlineStatusManager::Away), + m_UserStatusConnecting(Kopete::OnlineStatus::Connecting, + 1, this, Connecting, "irc_connecting", i18n("Connecting")), + m_UserStatusOffline(Kopete::OnlineStatus::Offline, + 0, this, Offline, QString::null, i18n("Offline"), i18n("Offline"), Kopete::OnlineStatusManager::Offline), + + m_StatusUnknown(Kopete::OnlineStatus::Unknown, + 999, this, 999, "status_unknown", i18n("Status not available")), + + propChannelTopic(QString::fromLatin1("channelTopic"), i18n("Topic"), QString::null, false, true ), + propChannelMembers(QString::fromLatin1("channelMembers"), i18n("Members")), + propHomepage(QString::fromLatin1("homePage"), i18n("Home Page")), + propLastSeen(Kopete::Global::Properties::self()->lastSeen()), + propUserInfo(QString::fromLatin1("userInfo"), i18n("IRC User")), + propServer(QString::fromLatin1("ircServer"), i18n("IRC Server")), + propChannels( QString::fromLatin1("ircChannels"), i18n("IRC Channels")), + propHops(QString::fromLatin1("ircHops"), i18n("IRC Hops")), + propFullName(QString::fromLatin1("FormattedName"), i18n("Full Name")), + propIsIdentified(QString::fromLatin1("identifiedUser"), i18n("User Is Authenticated")) +{ +// kdDebug(14120) << k_funcinfo << endl; + + s_protocol = this; + + //m_status = m_unknownStatus = m_Unknown; + + addAddressBookField("messaging/irc", Kopete::Plugin::MakeIndexField); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("raw"), + SLOT( slotRawCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /raw - Sends the text in raw form to the server."), 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("quote"), + SLOT( slotQuoteCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /quote - Sends the text in quoted form to the server."), 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("ctcp"), + SLOT( slotCtcpCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /ctcp - Send the CTCP message to nick."), 2 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("ping"), + SLOT( slotPingCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /ping - Alias for /CTCP PING."), 1, 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("motd"), + SLOT( slotMotdCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /motd [] - Shows the message of the day for the current or the given server.") ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("list"), + SLOT( slotListCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /list - List the public channels on the server.") ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("join"), + SLOT( slotJoinCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /join <#channel 1> [] - Joins the specified channel."), 1, 2 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("topic"), + SLOT( slotTopicCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /topic [] - Sets and/or displays the topic for the active channel.") ); + + //FIXME: Update help text + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("whois"), + SLOT( slotWhoisCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /whois - Display whois info on this user."), 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("whowas"), + SLOT( slotWhoWasCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /whowas - Display whowas info on this user."), 1, 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("who"), + SLOT( slotWhoCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /who - Display who info on this user/channel."), 1, 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("query"), + SLOT( slotQueryCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /query [] - Open a private chat with this user."), 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("mode"), + SLOT( slotModeCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /mode - Set modes on the given channel."), 2 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("nick"), + SLOT( slotNickCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /nick - Change your nickname to the given one."), 1, 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("me"), + SLOT( slotMeCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /me - Do something."), 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("ame"), + SLOT( slotAllMeCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /ame - Do something in every open chat."), 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("kick"), + SLOT( slotKickCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /kick [] - Kick someone from the channel (requires operator status).") + , 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("ban"), + SLOT( slotBanCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /ban - Add someone to this channel's ban list. (requires operator status)."), + 1, 1 ); + + Kopete::CommandHandler::commandHandler()->registerAlias( this, QString::fromLatin1("bannick"), + QString::fromLatin1("ban %1!*@*"), + i18n("USAGE: /bannick - Add someone to this channel's ban list. Uses the hostmask nickname!*@* (requires operator status)."), Kopete::CommandHandler::SystemAlias, 1, 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("op"), + SLOT( slotOpCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /op [ <...>] - Give channel operator status to someone (requires operator status)."), + 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("deop"), + SLOT( slotDeopCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /deop [ <...>]- Remove channel operator status from someone (requires operator status)."), 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("voice"), + SLOT( slotVoiceCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /voice [ <...>]- Give channel voice status to someone (requires operator status)."), + 1); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("devoice"), + SLOT( slotDevoiceCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /devoice [ <...>]- Remove channel voice status from someone (requires operator status)."), 1 ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("quit"), + SLOT( slotQuitCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /quit [] - Disconnect from IRC, optionally leaving a message.") ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("part"), + SLOT( slotPartCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /part [] - Part from a channel, optionally leaving a message.") ); + + Kopete::CommandHandler::commandHandler()->registerCommand( this, QString::fromLatin1("invite"), + SLOT( slotInviteCommand( const QString &, Kopete::ChatSession*) ), + i18n("USAGE: /invite [] - Invite a user to join a channel."), 1 ); + + Kopete::CommandHandler::commandHandler()->registerAlias( this, QString::fromLatin1("j"), + QString::fromLatin1("join %1"), + i18n("USAGE: /j <#channel 1> [] - Alias for JOIN."), Kopete::CommandHandler::SystemAlias, + 1, 2 ); + + Kopete::CommandHandler::commandHandler()->registerAlias( this, QString::fromLatin1("msg"), + QString::fromLatin1("query %s"), + i18n("USAGE: /msg [] - Alias for QUERY ."), Kopete::CommandHandler::SystemAlias, 1 ); + + QObject::connect( Kopete::ChatSessionManager::self(), SIGNAL(aboutToDisplay(Kopete::Message &)), + this, SLOT(slotMessageFilter(Kopete::Message &)) ); + + QObject::connect( Kopete::ChatSessionManager::self(), SIGNAL( viewCreated( KopeteView* ) ), + this, SLOT( slotViewCreated( KopeteView* ) ) ); + + setCapabilities( Kopete::Protocol::RichBFormatting | Kopete::Protocol::RichUFormatting | Kopete::Protocol::RichColor ); + + netConf = 0L; + + slotReadNetworks(); + + m_protocolHandler = new IRCProtocolHandler(); + + IRCTransferHandler::self(); // Initiate the transfer handling system. +} + +IRCProtocol * IRCProtocol::protocol() +{ + return s_protocol; +} + +IRCProtocol::~IRCProtocol() +{ + delete m_protocolHandler; +} + +const Kopete::OnlineStatus IRCProtocol::statusLookup( IRCStatus status ) const +{ + kdDebug(14120) << k_funcinfo << "Looking up status for " << status << endl; + + switch( status ) + { + case Offline: + return m_UserStatusOffline; + case Connecting: + return m_UserStatusConnecting; + + // Regular user + case Online: + return m_UserStatusOnline; + case Online | Away: + return m_UserStatusAway; + + // Voiced + case Online | Voiced: + return m_UserStatusVoice; + case Online | Away | Voiced: + return m_UserStatusVoiceAway; + + // Operator + case Online | Operator: + return m_UserStatusOp; + case Online | Away | Operator: + return m_UserStatusOpAway; + case Online | Operator | Voiced: + return m_UserStatusOpVoice; + case Online | Operator | Voiced | Away: + return m_UserStatusOpVoiceAway; + + // Server + case OnlineServer: + return m_ServerStatusOnline; + case OfflineServer: + return m_ServerStatusOffline; + + // Channel + case OnlineChannel: + return m_ChannelStatusOnline; + case OfflineChannel: + return m_ChannelStatusOffline; + + default: + return m_StatusUnknown; + } +} + +void IRCProtocol::slotViewCreated( KopeteView *view ) +{ + if( view->msgManager()->protocol() == this ) + new IRCGUIClient( view->msgManager() ); +} + +void IRCProtocol::slotMessageFilter( Kopete::Message &msg ) +{ + if( msg.from()->protocol() == this ) + { + QString messageText = msg.escapedBody(); + + //Add right click for channels, only replace text not in HTML tags + messageText.replace( QRegExp( QString::fromLatin1("(?![^<]+>)(#[^#\\s]+)(?![^<]+>)") ), QString::fromLatin1("\\1") ); + + msg.setBody( messageText, Kopete::Message::RichText ); + } +} + +QPtrList *IRCProtocol::customChatWindowPopupActions( const Kopete::Message &m, DOM::Node &n ) +{ + DOM::HTMLElement e = n; + + //isNull checks that the cast was successful + if( !e.isNull() && !m.to().isEmpty() ) + { + activeNode = n; + activeAccount = static_cast( m.from()->account() ); + if( e.getAttribute( QString::fromLatin1("type") ) == QString::fromLatin1("IRCChannel") ) + return activeAccount->contactManager()->findChannel( + e.innerText().string() )->customContextMenuActions(); + } + + return 0L; +} + +AddContactPage *IRCProtocol::createAddContactWidget(QWidget *parent, Kopete::Account *account) +{ + return new IRCAddContactPage(parent,static_cast(account)); +} + +KopeteEditAccountWidget *IRCProtocol::createEditAccountWidget(Kopete::Account *account, QWidget *parent) +{ + return new IRCEditAccountWidget(this, static_cast(account),parent); +} + +Kopete::Account *IRCProtocol::createNewAccount(const QString &accountId) +{ + return new IRCAccount( this, accountId ); +} + +Kopete::Contact *IRCProtocol::deserializeContact( Kopete::MetaContact *metaContact, const QMap &serializedData, + const QMap & /* addressBookData */ ) +{ + kdDebug(14120) << k_funcinfo << endl; + + QString contactId = serializedData[ "contactId" ]; + QString displayName = serializedData[ "displayName" ]; + + if( displayName.isEmpty() ) + displayName = contactId; + + QDict accounts = Kopete::AccountManager::self()->accounts( this ); + if( !accounts.isEmpty() ) + { + Kopete::Account *a = accounts[ serializedData[ "accountId" ] ]; + if( a ) + { + a->addContact( contactId, metaContact ); + return a->contacts()[contactId]; + } + else + kdDebug(14120) << k_funcinfo << serializedData[ "accountId" ] << " was a contact's account," + " but we don't have it in the accounts list" << endl; + } + else + kdDebug(14120) << k_funcinfo << "No accounts loaded!" << endl; + + return 0; +} + +void IRCProtocol::slotRawCommand( const QString &args, Kopete::ChatSession *manager ) +{ + IRCAccount *account = static_cast( manager->account() ); + + if (!args.isEmpty()) + { + account->engine()->writeRawMessage(args); + } + else + { + account->appendMessage(i18n("You must enter some text to send to the server."), + IRCAccount::ErrorReply ); + } +} + +void IRCProtocol::slotQuoteCommand( const QString &args, Kopete::ChatSession *manager ) +{ + IRCAccount *account = static_cast( manager->account() ); + + if( !args.isEmpty() ) + { + account->engine()->writeMessage( args ); + } + else + { + account->appendMessage(i18n("You must enter some text to send to the server."), + IRCAccount::ErrorReply ); + } +} + +void IRCProtocol::slotCtcpCommand( const QString &args, Kopete::ChatSession *manager ) +{ + if( !args.isEmpty() ) + { + QString user = args.section( ' ', 0, 0 ); + QString message = args.section( ' ', 1 ); + static_cast( manager->account() )->engine()->writeCtcpQueryMessage( user, QString::null, message ); + } +} + +void IRCProtocol::slotMotdCommand( const QString &args, Kopete::ChatSession *manager ) +{ + QStringList argsList = Kopete::CommandHandler::parseArguments( args ); + static_cast( manager->account() )->engine()->motd(argsList.front()); +} + +void IRCProtocol::slotPingCommand( const QString &args, Kopete::ChatSession *manager ) +{ + QStringList argsList = Kopete::CommandHandler::parseArguments(args); + static_cast( manager->account() )->engine()->CtcpRequest_ping(argsList.front()); +} + +void IRCProtocol::slotListCommand( const QString &/*args*/, Kopete::ChatSession *manager ) +{ + static_cast( manager->account() )->listChannels(); +} + +void IRCProtocol::slotTopicCommand( const QString &args, Kopete::ChatSession *manager ) +{ + Kopete::ContactPtrList members = manager->members(); + IRCChannelContact *chan = dynamic_cast( members.first() ); + if( chan ) + { + if( !args.isEmpty() ) + chan->setTopic( args ); + else + { + static_cast(manager->account())->engine()-> + writeRawMessage(QString::fromLatin1("TOPIC %1").arg(chan->nickName())); + } + } + else + { + static_cast( manager->account() )->appendMessage( + i18n("You must be in a channel to use this command."), IRCAccount::ErrorReply ); + } +} + +void IRCProtocol::slotJoinCommand( const QString &arg, Kopete::ChatSession *manager ) +{ + QStringList args = Kopete::CommandHandler::parseArguments( arg ); + if( KIRC::Entity::isChannel(args[0]) ) + { + IRCChannelContact *chan = static_cast( manager->account() )->contactManager()->findChannel( args[0] ); + if( args.count() == 2 ) + chan->setPassword( args[1] ); + static_cast( manager->account() )->engine()->join(args[0], chan->password()); + } + else + { + static_cast( manager->account() )->appendMessage( + i18n("\"%1\" is an invalid channel. Channels must start with '#', '!', '+', or '&'.") + .arg(args[0]), IRCAccount::ErrorReply ); + } +} + +void IRCProtocol::slotInviteCommand( const QString &args, Kopete::ChatSession *manager ) +{ + IRCChannelContact *c = 0L; + QStringList argsList = Kopete::CommandHandler::parseArguments( args ); + + if( argsList.count() > 1 ) + { + if( KIRC::Entity::isChannel(argsList[1]) ) + { + c = static_cast( manager->account() )->contactManager()-> + findChannel( argsList[1] ); + } + else + { + static_cast( manager->account() )->appendMessage( + i18n("\"%1\" is an invalid channel. Channels must start with '#', '!', '+', or '&'.") + .arg(argsList[1]), IRCAccount::ErrorReply ); + } + } + else + { + Kopete::ContactPtrList members = manager->members(); + c = dynamic_cast( members.first() ); + } + + if( c && c->manager()->contactOnlineStatus( manager->myself() ) == m_UserStatusOp ) + { + static_cast( manager->account() )->engine()->writeMessage( + QString::fromLatin1("INVITE %1 %2").arg( argsList[0] ). + arg( c->nickName() ) + ); + } + else + { + static_cast( manager->account() )->appendMessage( + i18n("You must be a channel operator to perform this operation."), IRCAccount::ErrorReply ); + } +} + +void IRCProtocol::slotQueryCommand( const QString &args, Kopete::ChatSession *manager ) +{ + QString user = args.section( ' ', 0, 0 ); + QString rest = args.section( ' ', 1 ); + + if( !KIRC::Entity::isChannel(user) ) + { + IRCUserContact *c = static_cast( manager->account() )-> + contactManager()->findUser( user ); + c->startChat(); + if( !rest.isEmpty() ) + { + Kopete::Message msg( c->manager()->myself(), c->manager()->members(), rest, + Kopete::Message::Outbound, Kopete::Message::PlainText, CHAT_VIEW); + c->manager()->sendMessage(msg); + } + } + else + { + static_cast( manager->account() )->appendMessage( + i18n("\"%1\" is an invalid nickname. Nicknames must not start with '#','!','+', or '&'.").arg(user), + IRCAccount::ErrorReply ); + } +} + +void IRCProtocol::slotWhoisCommand( const QString &args, Kopete::ChatSession *manager ) +{ + static_cast( manager->account() )->engine()->whois( args ); + static_cast( manager->account() )->setCurrentCommandSource( manager ); +} + +void IRCProtocol::slotWhoCommand( const QString &args, Kopete::ChatSession *manager ) +{ + QStringList argsList = Kopete::CommandHandler::parseArguments( args ); + static_cast( manager->account() )->engine()->writeMessage( + QString::fromLatin1("WHO %1").arg( argsList.first() ) ); + static_cast( manager->account() )->setCurrentCommandSource( manager ); +} + +void IRCProtocol::slotWhoWasCommand( const QString &args, Kopete::ChatSession *manager ) +{ + QStringList argsList = Kopete::CommandHandler::parseArguments( args ); + static_cast( manager->account() )->engine()->writeMessage( + QString::fromLatin1("WHOWAS %1").arg( argsList.first() ) ); + static_cast( manager->account() )->setCurrentCommandSource( manager ); +} + +void IRCProtocol::slotQuitCommand( const QString &args, Kopete::ChatSession *manager ) +{ + static_cast( manager->account() )->quit( args ); +} + +void IRCProtocol::slotNickCommand( const QString &args, Kopete::ChatSession *manager ) +{ + QStringList argsList = Kopete::CommandHandler::parseArguments( args ); + static_cast( manager->account() )->engine()->nick( argsList.front() ); +} + +void IRCProtocol::slotModeCommand(const QString &args, Kopete::ChatSession *manager) +{ + QStringList argsList = Kopete::CommandHandler::parseArguments( args ); + static_cast( manager->account() )->engine()->mode( argsList.front(), + args.section( QRegExp(QString::fromLatin1("\\s+")), 1 ) ); +} + +void IRCProtocol::slotMeCommand(const QString &args, Kopete::ChatSession *manager) +{ + Kopete::ContactPtrList members = manager->members(); + static_cast( manager->account() )->engine()->CtcpRequest_action( + static_cast(members.first())->nickName(), args + ); +} + +void IRCProtocol::slotAllMeCommand(const QString &args, Kopete::ChatSession *) +{ + QValueList sessions = Kopete::ChatSessionManager::self()->sessions(); + + for( QValueList::iterator it = sessions.begin(); it != sessions.end(); ++it ) + { + Kopete::ChatSession *session = *it; + if( session->protocol() == this ) + slotMeCommand(args, session); + } +} + +void IRCProtocol::slotKickCommand(const QString &args, Kopete::ChatSession *manager) +{ + if (manager->contactOnlineStatus( manager->myself() ) == m_UserStatusOp) + { + QRegExp spaces(QString::fromLatin1("\\s+")); + QString nick = args.section( spaces, 0, 0); + QString reason = args.section( spaces, 1); + Kopete::ContactPtrList members = manager->members(); + QString channel = static_cast( members.first() )->nickName(); + if (KIRC::Entity::isChannel(channel)) + static_cast(manager->account())->engine()->kick(nick, channel, reason); + } + else + { + static_cast( manager->account() )->appendMessage( + i18n("You must be a channel operator to perform this operation."), IRCAccount::ErrorReply ); + } +} + +void IRCProtocol::slotBanCommand( const QString &args, Kopete::ChatSession *manager ) +{ + if( manager->contactOnlineStatus( manager->myself() ) == m_UserStatusOp ) + { + QStringList argsList = Kopete::CommandHandler::parseArguments( args ); + Kopete::ContactPtrList members = manager->members(); + IRCChannelContact *chan = static_cast( members.first() ); + if( chan && chan->locateUser( argsList.front() ) ) + chan->setMode( QString::fromLatin1("+b %1").arg( argsList.front() ) ); + } + else + { + static_cast( manager->account() )->appendMessage( + i18n("You must be a channel operator to perform this operation."), IRCAccount::ErrorReply ); + } +} + +void IRCProtocol::slotPartCommand( const QString &args, Kopete::ChatSession *manager ) +{ + QStringList argsList = Kopete::CommandHandler::parseArguments(args); + Kopete::ContactPtrList members = manager->members(); + IRCChannelContact *chan = static_cast(members.first()); + + if (chan) + { + if(!args.isEmpty()) + static_cast(manager->account())->engine()->part(chan->nickName(), args); + else + chan->part(); + if( manager->view() ) + manager->view()->closeView(true); + } + else + { + static_cast( manager->account() )->appendMessage( + i18n("You must be in a channel to use this command."), IRCAccount::ErrorReply ); + } +} + +void IRCProtocol::slotOpCommand( const QString &args, Kopete::ChatSession *manager ) +{ + simpleModeChange( args, manager, QString::fromLatin1("+o") ); +} + +void IRCProtocol::slotDeopCommand( const QString &args, Kopete::ChatSession *manager ) +{ + simpleModeChange( args, manager, QString::fromLatin1("-o") ); +} + +void IRCProtocol::slotVoiceCommand( const QString &args, Kopete::ChatSession *manager ) +{ + simpleModeChange( args, manager, QString::fromLatin1("+v") ); +} + +void IRCProtocol::slotDevoiceCommand( const QString &args, Kopete::ChatSession *manager ) +{ + simpleModeChange( args, manager, QString::fromLatin1("-v") ); +} + +void IRCProtocol::simpleModeChange( const QString &args, Kopete::ChatSession *manager, const QString &mode ) +{ + if( manager->contactOnlineStatus( manager->myself() ) == m_UserStatusOp ) + { + QStringList argsList = Kopete::CommandHandler::parseArguments( args ); + Kopete::ContactPtrList members = manager->members(); + IRCChannelContact *chan = static_cast( members.first() ); + if( chan ) + { + for( QStringList::iterator it = argsList.begin(); it != argsList.end(); ++it ) + { + if( chan->locateUser( *it ) ) + chan->setMode( QString::fromLatin1("%1 %2").arg( mode ).arg( *it ) ); + } + } + } + else + { + static_cast( manager->account() )->appendMessage( + i18n("You must be a channel operator to perform this operation."), IRCAccount::ErrorReply ); + } +} + +void IRCProtocol::editNetworks( const QString &networkName ) +{ + if( !netConf ) + { + netConf = new NetworkConfig( Kopete::UI::Global::mainWidget(), "network_config", true ); + netConf->host->setValidator( new QRegExpValidator( QString::fromLatin1("^[\\w-\\.]*$"), netConf ) ); + netConf->upButton->setIconSet( SmallIconSet( "up" ) ); + netConf->downButton->setIconSet( SmallIconSet( "down" ) ); + + connect( netConf->networkList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkConfig() ) ); + connect( netConf->hostList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkHostConfig() ) ); + connect( netConf, SIGNAL( accepted() ), this, SLOT( slotSaveNetworkConfig() ) ); + connect( netConf, SIGNAL( rejected() ), this, SLOT( slotReadNetworks() ) ); + connect( netConf->upButton, SIGNAL( clicked() ), this, SLOT( slotMoveServerUp() ) ); + connect( netConf->downButton, SIGNAL( clicked() ), this, SLOT( slotMoveServerDown() ) ); + connect( netConf->removeNetwork, SIGNAL( clicked() ), this, SLOT( slotDeleteNetwork() ) ); + connect( netConf->removeHost, SIGNAL( clicked() ), this, SLOT( slotDeleteHost() ) ); + connect( netConf->newHost, SIGNAL( clicked() ), this, SLOT( slotNewHost() ) ); + connect( netConf->newNetwork, SIGNAL( clicked() ), this, SLOT( slotNewNetwork() ) ); + connect( netConf->renameNetwork, SIGNAL( clicked() ), this, SLOT( slotRenameNetwork() ) ); + connect( netConf->port, SIGNAL( valueChanged( int ) ), this, SLOT( slotHostPortChanged( int ) ) ); + connect( netConf->networkList, SIGNAL( doubleClicked ( QListBoxItem * )), SLOT(slotRenameNetwork())); + + } + + disconnect( netConf->networkList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkConfig() ) ); + disconnect( netConf->hostList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkHostConfig() ) ); + + netConf->networkList->clear(); + + for( QDictIterator it( m_networks ); it.current(); ++it ) + { + IRCNetwork *net = it.current(); + netConf->networkList->insertItem( net->name ); + } + + netConf->networkList->sort(); + + connect( netConf->networkList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkConfig() ) ); + connect( netConf->hostList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkHostConfig() ) ); + + if( !networkName.isEmpty() ) + netConf->networkList->setSelected( netConf->networkList->findItem( networkName ), true ); + + //slotUpdateNetworkConfig(); // unnecessary, setSelected emits selectionChanged + + netConf->show(); +} + +void IRCProtocol::slotUpdateNetworkConfig() +{ + // update the data structure of the previous selection from the UI + storeCurrentNetwork(); + + // update the UI from the data for the current selection + IRCNetwork *net = m_networks[ netConf->networkList->currentText() ]; + if( net ) + { + netConf->description->setText( net->description ); + netConf->hostList->clear(); + + for( QValueList::iterator it = net->hosts.begin(); it != net->hosts.end(); ++it ) + netConf->hostList->insertItem( (*it)->host + QString::fromLatin1(":") + QString::number((*it)->port) ); + + // prevent nested event loop crash + disconnect( netConf->hostList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkHostConfig() ) ); + netConf->hostList->setSelected( 0, true ); + slotUpdateNetworkHostConfig(); + connect( netConf->hostList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkHostConfig() ) ); + } + + // record the current selection + m_uiCurrentNetworkSelection = netConf->networkList->currentText(); +} + +void IRCProtocol::storeCurrentNetwork() +{ + if ( !m_uiCurrentNetworkSelection.isEmpty() ) + { + IRCNetwork *net = m_networks[ m_uiCurrentNetworkSelection ]; + if ( net ) + { + net->description = netConf->description->text(); // crash on 2nd dialog show here! + } + else + kdDebug( 14120 ) << m_uiCurrentNetworkSelection << " was already gone from the cache!" << endl; + } +} + +void IRCProtocol::storeCurrentHost() +{ + if ( !m_uiCurrentHostSelection.isEmpty() ) + { + IRCHost *host = m_hosts[ m_uiCurrentHostSelection ]; + if ( host ) + { + host->host = netConf->host->text(); + host->password = netConf->password->text(); + host->port = netConf->port->text().toInt(); + host->ssl = netConf->useSSL->isChecked(); + } + } +} + +void IRCProtocol::slotHostPortChanged( int value ) +{ + QString entryText = m_uiCurrentHostSelection + QString::fromLatin1(":") + QString::number( value ); + // changeItem causes a take() and insert, and we don't want a selectionChanged() signal that sets all this off again. + disconnect( netConf->hostList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkHostConfig() ) ); + netConf->hostList->changeItem( entryText, netConf->hostList->currentItem() ); + connect( netConf->hostList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkHostConfig() ) ); +} + +void IRCProtocol::slotUpdateNetworkHostConfig() +{ + storeCurrentHost(); + + if ( netConf->hostList->selectedItem() ) + { + m_uiCurrentHostSelection = netConf->hostList->currentText().section(':', 0, 0); + IRCHost *host = m_hosts[ m_uiCurrentHostSelection ]; + + if( host ) + { + netConf->host->setText( host->host ); + netConf->password->setText( host->password ); + disconnect( netConf->port, SIGNAL( valueChanged( int ) ), this, SLOT( slotHostPortChanged( int ) ) ); + netConf->port->setValue( host->port ); + connect( netConf->port, SIGNAL( valueChanged( int ) ), this, SLOT( slotHostPortChanged( int ) ) ); + netConf->useSSL->setChecked( host->ssl ); + + netConf->upButton->setEnabled( netConf->hostList->currentItem() > 0 ); + netConf->downButton->setEnabled( netConf->hostList->currentItem() < (int)( netConf->hostList->count() - 1 ) ); + } + } + else + { + m_uiCurrentHostSelection = QString(); + disconnect( netConf->port, SIGNAL( valueChanged( int ) ), this, SLOT( slotHostPortChanged( int ) ) ); + netConf->host->clear(); + netConf->password->clear(); + netConf->port->setValue( 6667 ); + netConf->useSSL->setChecked( false ); + connect( netConf->port, SIGNAL( valueChanged( int ) ), this, SLOT( slotHostPortChanged( int ) ) ); + } +} + +void IRCProtocol::slotDeleteNetwork() +{ + QString network = netConf->networkList->currentText(); + if( KMessageBox::warningContinueCancel( + Kopete::UI::Global::mainWidget(), i18n("Are you sure you want to delete the network %1?
    " + "Any accounts which use this network will have to be modified.
    ") + .arg(network), i18n("Deleting Network"), + KGuiItem(i18n("&Delete Network"),"editdelete"), QString::fromLatin1("AskIRCDeleteNetwork") ) == KMessageBox::Continue ) + { + disconnect( netConf->networkList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkConfig() ) ); + disconnect( netConf->hostList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkHostConfig() ) ); + IRCNetwork *net = m_networks[ network ]; + for( QValueList::iterator it = net->hosts.begin(); it != net->hosts.end(); ++it ) + { + m_hosts.remove( (*it)->host ); + delete (*it); + } + m_networks.remove( network ); + delete net; + netConf->networkList->removeItem( netConf->networkList->currentItem() ); + connect( netConf->networkList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkConfig() ) ); + connect( netConf->hostList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkHostConfig() ) ); + slotUpdateNetworkHostConfig(); + + } +} + +void IRCProtocol::slotDeleteHost() +{ + QString hostName = netConf->host->text(); + if ( KMessageBox::warningContinueCancel( + Kopete::UI::Global::mainWidget(), i18n("Are you sure you want to delete the host %1?") + .arg(hostName), i18n("Deleting Host"), + KGuiItem(i18n("&Delete Host"),"editdelete"), QString::fromLatin1("AskIRCDeleteHost")) == KMessageBox::Continue ) + { + IRCHost *host = m_hosts[ hostName ]; + if ( host ) + { + disconnect( netConf->hostList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkHostConfig() ) ); + QString entryText = host->host + QString::fromLatin1(":") + QString::number(host->port); + QListBoxItem * justAdded = netConf->hostList->findItem( entryText ); + netConf->hostList->removeItem( netConf->hostList->index( justAdded ) ); + connect( netConf->hostList, SIGNAL( selectionChanged() ), this, SLOT( slotUpdateNetworkHostConfig() ) ); + + // remove from network as well + IRCNetwork *net = m_networks[ m_uiCurrentNetworkSelection ]; + net->hosts.remove( host ); + + m_hosts.remove( host->host ); + delete host; + } + } +} + +void IRCProtocol::slotNewNetwork() +{ + // create a new network struct + IRCNetwork *net = new IRCNetwork; + // give it the name of 'New Network' (incrementing number if needed) + QString netName = QString::fromLatin1( "New Network" ); + if ( m_networks.find( netName ) ) + { + int newIdx = 1; + do { + netName = QString::fromLatin1( "New Network #%1" ).arg( newIdx++ ); + } + while ( m_networks.find( netName ) && newIdx < 100 ); + if ( newIdx == 100 ) // pathological case + return; + } + net->name = netName; + // and add it to the networks dict and list + m_networks.insert( net->name, net ); + netConf->networkList->insertItem( net->name ); + QListBoxItem * justAdded = netConf->networkList->findItem( net->name ); + netConf->networkList->setSelected( justAdded, true ); + netConf->networkList->setBottomItem( netConf->networkList->index( justAdded ) ); +} + +void IRCProtocol::slotNewHost() +{ + // create a new host + IRCHost *host = new IRCHost; + // prompt for a name + bool ok; + QString name = KInputDialog::getText( + i18n("New Host"), + i18n("Enter the hostname of the new server:"), + QString::null, &ok, Kopete::UI::Global::mainWidget() ); + if ( ok ) + { + // dupe check + if ( m_hosts[ name ] ) + { + KMessageBox::sorry(netConf, i18n( "A host already exists with that name" ) ); + return; + } + // set defaults on others + host->host = name; + host->port = 6667; + host->ssl = false; + // add it to the dict + m_hosts.insert( host->host, host ); + // add it to the network! + IRCNetwork *net = m_networks[ netConf->networkList->currentText() ]; + net->hosts.append( host ); + // add it to the gui + QString entryText = host->host + QString::fromLatin1(":") + QString::number(host->port); + netConf->hostList->insertItem( entryText ); + // select it in the gui + QListBoxItem * justAdded = netConf->hostList->findItem( entryText ); + netConf->hostList->setSelected( justAdded, true ); + //netConf->hostList->setBottomItem( netConf->hostList->index( justAdded ) ); + } +} + +void IRCProtocol::slotRenameNetwork() +{ + IRCNetwork *net = m_networks[ m_uiCurrentNetworkSelection ]; + if ( net ) + { + bool ok; + // popup up a dialog containing the current name + QString name = KInputDialog::getText( + i18n("Rename Network"), + i18n("Enter the new name for this network:"), + m_uiCurrentNetworkSelection, &ok, + Kopete::UI::Global::mainWidget() ); + if ( ok ) + { + if ( m_uiCurrentNetworkSelection != name ) + { + // dupe check + if ( m_networks[ name ] ) + { + KMessageBox::sorry(netConf, i18n( "A network already exists with that name" ) ); + return; + } + + net->name = name; + // dict + m_networks.remove( m_uiCurrentNetworkSelection ); + m_networks.insert( net->name, net ); + // ui + int idx = netConf->networkList->index( netConf->networkList->findItem( m_uiCurrentNetworkSelection ) ); + m_uiCurrentNetworkSelection = net->name; + netConf->networkList->changeItem( net->name, idx ); // changes the selection!!! + netConf->networkList->sort(); + } + } + } +} + +void IRCProtocol::addNetwork( IRCNetwork *network ) +{ + m_networks.insert( network->name, network ); + slotSaveNetworkConfig(); +} + +void IRCProtocol::slotSaveNetworkConfig() +{ + // store any changes in the UI + storeCurrentNetwork(); + kdDebug( 14120 ) << k_funcinfo << m_uiCurrentHostSelection << endl; + storeCurrentHost(); + + QDomDocument doc("irc-networks"); + QDomNode root = doc.appendChild( doc.createElement("networks") ); + + for( QDictIterator it( m_networks ); it.current(); ++it ) + { + IRCNetwork *net = it.current(); + + QDomNode networkNode = root.appendChild( doc.createElement("network") ); + QDomNode nameNode = networkNode.appendChild( doc.createElement("name") ); + nameNode.appendChild( doc.createTextNode( net->name ) ); + + QDomNode descNode = networkNode.appendChild( doc.createElement("description") ); + descNode.appendChild( doc.createTextNode( net->description ) ); + + QDomNode serversNode = networkNode.appendChild( doc.createElement("servers") ); + + for( QValueList::iterator it2 = net->hosts.begin(); it2 != net->hosts.end(); ++it2 ) + { + QDomNode serverNode = serversNode.appendChild( doc.createElement("server") ); + + QDomNode hostNode = serverNode.appendChild( doc.createElement("host") ); + hostNode.appendChild( doc.createTextNode( (*it2)->host ) ); + + QDomNode portNode = serverNode.appendChild( doc.createElement("port" ) ); + portNode.appendChild( doc.createTextNode( QString::number( (*it2)->port ) ) ); + + QDomNode sslNode = serverNode.appendChild( doc.createElement("useSSL") ); + sslNode.appendChild( doc.createTextNode( (*it2)->ssl ? "true" : "false" ) ); + } + } + +// kdDebug(14121) << k_funcinfo << doc.toString(4) << endl; + QFile xmlFile( locateLocal( "appdata", "ircnetworks.xml" ) ); + + if (xmlFile.open(IO_WriteOnly)) + { + QTextStream stream(&xmlFile); + stream << doc.toString(4); + xmlFile.close(); + } + else + kdDebug(14121) << k_funcinfo << "Failed to save the Networks definition file" << endl; + + if (netConf) + emit networkConfigUpdated( netConf->networkList->currentText() ); +} + +void IRCProtocol::slotReadNetworks() +{ + m_networks.clear(); + m_hosts.clear(); + + QFile xmlFile( locate( "appdata", "ircnetworks.xml" ) ); + xmlFile.open( IO_ReadOnly ); + + QDomDocument doc; + doc.setContent( &xmlFile ); + QDomElement networkNode = doc.documentElement().firstChild().toElement(); + while( !networkNode.isNull () ) + { + IRCNetwork *net = new IRCNetwork; + + QDomElement networkChild = networkNode.firstChild().toElement(); + while( !networkChild.isNull() ) + { + if( networkChild.tagName() == "name" ) + net->name = networkChild.text(); + else if( networkChild.tagName() == "description" ) + net->description = networkChild.text(); + else if( networkChild.tagName() == "servers" ) + { + QDomElement server = networkChild.firstChild().toElement(); + while( !server.isNull() ) + { + IRCHost *host = new IRCHost; + + QDomElement serverChild = server.firstChild().toElement(); + while( !serverChild.isNull() ) + { + if( serverChild.tagName() == "host" ) + host->host = serverChild.text(); + else if( serverChild.tagName() == "port" ) + host->port = serverChild.text().toInt(); + else if( serverChild.tagName() == "useSSL" ) + host->ssl = ( serverChild.text() == "true" ); + + serverChild = serverChild.nextSibling().toElement(); + } + + net->hosts.append( host ); + m_hosts.insert( host->host, host ); + server = server.nextSibling().toElement(); + } + } + networkChild = networkChild.nextSibling().toElement(); + } + + m_networks.insert( net->name, net ); + networkNode = networkNode.nextSibling().toElement(); + } + + xmlFile.close(); +} + +void IRCProtocol::slotMoveServerUp() +{ + IRCHost *selectedHost = m_hosts[ netConf->hostList->currentText().section(':', 0, 0) ]; + IRCNetwork *selectedNetwork = m_networks[ netConf->networkList->currentText() ]; + + if( !selectedNetwork || !selectedHost ) + return; + + QValueList::iterator pos = selectedNetwork->hosts.find( selectedHost ); + if( pos != selectedNetwork->hosts.begin() ) + { + QValueList::iterator lastPos = pos; + lastPos--; + selectedNetwork->hosts.insert( lastPos, selectedHost ); + selectedNetwork->hosts.remove( pos ); + } + + unsigned int currentPos = netConf->hostList->currentItem(); + if( currentPos > 0 ) + { + netConf->hostList->removeItem( currentPos ); + QString entryText = selectedHost->host + QString::fromLatin1(":") + QString::number( selectedHost->port ); + netConf->hostList->insertItem( entryText, --currentPos ); + netConf->hostList->setSelected( currentPos, true ); + } +} + +void IRCProtocol::slotMoveServerDown() +{ + IRCHost *selectedHost = m_hosts[ netConf->hostList->currentText().section(':', 0, 0) ]; + IRCNetwork *selectedNetwork = m_networks[ netConf->networkList->currentText() ]; + + if( !selectedNetwork || !selectedHost ) + return; + + QValueList::iterator pos = selectedNetwork->hosts.find( selectedHost ); + if( *pos != selectedNetwork->hosts.back() ) + { + QValueList::iterator nextPos = selectedNetwork->hosts.remove( pos ); + selectedNetwork->hosts.insert( ++nextPos, selectedHost ); + } + + unsigned int currentPos = netConf->hostList->currentItem(); + if( currentPos < ( netConf->hostList->count() - 1 ) ) + { + netConf->hostList->removeItem( currentPos ); + QString entryText = selectedHost->host + QString::fromLatin1(":") + QString::number( selectedHost->port ); + netConf->hostList->insertItem( entryText, ++currentPos ); + netConf->hostList->setSelected( currentPos, true ); + } +} + + + +#include "ircprotocol.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/protocols/irc/ircprotocol.h b/kopete/protocols/irc/ircprotocol.h new file mode 100644 index 00000000..2a1700e5 --- /dev/null +++ b/kopete/protocols/irc/ircprotocol.h @@ -0,0 +1,228 @@ +/* + ircprotocol.h - IRC Protocol + + Copyright (c) 2002 by Nick Betcher + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef IRCPROTOCOL_H +#define IRCPROTOCOL_H + +#include "kopeteonlinestatus.h" +#include "kopeteprotocol.h" +#include "kopetecontactproperty.h" +#include "kopetemimetypehandler.h" + +#include +#include + +#define m_protocol (IRCProtocol::protocol()) + +namespace Kopete +{ +class Account; +class MetaContact; +} + +class AddContactPage; + +class EditAccountWidget; +class IRCAccount; + +class QStringList; +class QWidget; +class KopeteView; + +class IRCNetwork; +class IRCHost; +class NetworkConfig; + +class IRCProtocolHandler : public Kopete::MimeTypeHandler +{ + public: + + IRCProtocolHandler(); + + void handleURL( const KURL &url ) const; +}; + +static const QString CHAT_VIEW( QString::fromLatin1("kopete_chatwindow") ); + +/** + * @author Nick Betcher + */ +class IRCProtocol : public Kopete::Protocol +{ + Q_OBJECT + +public: + enum IRCStatus + { + Offline = 1, //! An offline user. + Connecting = 2, //! User that is connecting. + Away = 4, //! User that is away. May be regular user, voiced user or (server) operator. + Online = 8, //! This user is online. + Voiced = 16, //! This user is voiced. + Operator = 32, //! This user is a channel operator. + ServerOperator = 1024, //! This user is a server operator. + OfflineChannel = 4096, //! This channel is offline. + OnlineChannel = 8192, //! This channel is online. + OfflineServer = 16384, //! This server is offline. + OnlineServer = 32768 //! This server is online. + }; + + IRCProtocol( QObject *parent, const char *name, const QStringList &args ); + ~IRCProtocol(); + + /** Kopete::Protocol reimplementation */ + virtual AddContactPage *createAddContactWidget(QWidget *parent, Kopete::Account *account); + + /** + * Deserialize contact data + */ + virtual Kopete::Contact *deserializeContact( Kopete::MetaContact *metaContact, + const QMap &serializedData, const QMap &addressBookData ); + + virtual KopeteEditAccountWidget* createEditAccountWidget(Kopete::Account *account, QWidget *parent); + + virtual Kopete::Account* createNewAccount(const QString &accountId); + + virtual QPtrList *customChatWindowPopupActions( const Kopete::Message &, DOM::Node & ); + + static IRCProtocol *protocol(); + + /** + * Maps the given IRC status to Kopete::OnlineStatus. + */ + const Kopete::OnlineStatus statusLookup( IRCStatus status ) const; + + const Kopete::OnlineStatus m_ServerStatusOnline; + const Kopete::OnlineStatus m_ServerStatusOffline; + + const Kopete::OnlineStatus m_ChannelStatusOnline; + const Kopete::OnlineStatus m_ChannelStatusOffline; + + const Kopete::OnlineStatus m_UserStatusOpVoice; + const Kopete::OnlineStatus m_UserStatusOpVoiceAway; + const Kopete::OnlineStatus m_UserStatusOp; + const Kopete::OnlineStatus m_UserStatusOpAway; + const Kopete::OnlineStatus m_UserStatusVoice; + const Kopete::OnlineStatus m_UserStatusVoiceAway; + const Kopete::OnlineStatus m_UserStatusOnline; + const Kopete::OnlineStatus m_UserStatusAway; + const Kopete::OnlineStatus m_UserStatusConnecting; + const Kopete::OnlineStatus m_UserStatusOffline; + + const Kopete::OnlineStatus m_StatusUnknown; + + // irc channnel-contact properties + const Kopete::ContactPropertyTmpl propChannelTopic; + const Kopete::ContactPropertyTmpl propChannelMembers; + const Kopete::ContactPropertyTmpl propHomepage; + + // irc user-contact properties + const Kopete::ContactPropertyTmpl propLastSeen; + const Kopete::ContactPropertyTmpl propUserInfo; + const Kopete::ContactPropertyTmpl propServer; + const Kopete::ContactPropertyTmpl propChannels; + const Kopete::ContactPropertyTmpl propHops; + const Kopete::ContactPropertyTmpl propFullName; + const Kopete::ContactPropertyTmpl propIsIdentified; + + bool commandInProgress(){ return m_commandInProgress; } + void setCommandInProgress( bool ip ) { m_commandInProgress = ip; } + + QDict &networks(){ return m_networks; } + void addNetwork( IRCNetwork *network ); + + void editNetworks( const QString &networkName = QString::null ); + +signals: + void networkConfigUpdated( const QString &selectedNetwork ); + +private slots: + // FIXME: All the code for managing the networks list should be in another class - Will + void slotUpdateNetworkConfig(); + void slotUpdateNetworkHostConfig(); + void slotMoveServerUp(); + void slotMoveServerDown(); + void slotSaveNetworkConfig(); + void slotReadNetworks(); + void slotDeleteNetwork(); + void slotDeleteHost(); + void slotNewNetwork(); + void slotRenameNetwork(); + void slotNewHost(); + void slotHostPortChanged( int value ); + // end of network list specific code + + void slotMessageFilter( Kopete::Message &msg ); + + void slotRawCommand( const QString &args, Kopete::ChatSession *manager ); + void slotQuoteCommand( const QString &args, Kopete::ChatSession *manager ); + void slotCtcpCommand( const QString &args, Kopete::ChatSession *manager ); + void slotPingCommand( const QString &args, Kopete::ChatSession *manager ); + + void slotMotdCommand( const QString &args, Kopete::ChatSession *manager); + void slotListCommand( const QString &args, Kopete::ChatSession *manager); + void slotTopicCommand( const QString &args, Kopete::ChatSession *manager); + void slotJoinCommand( const QString &args, Kopete::ChatSession *manager); + void slotNickCommand( const QString &args, Kopete::ChatSession *manager); + void slotWhoisCommand( const QString &args, Kopete::ChatSession *manager); + void slotWhoWasCommand( const QString &args, Kopete::ChatSession *manager); + void slotWhoCommand( const QString &args, Kopete::ChatSession *manager); + void slotMeCommand( const QString &args, Kopete::ChatSession *manager); + void slotAllMeCommand( const QString &args, Kopete::ChatSession *manager); + void slotModeCommand( const QString &args, Kopete::ChatSession *manager); + void slotQueryCommand( const QString &args, Kopete::ChatSession *manager); + + void slotKickCommand( const QString &args, Kopete::ChatSession *manager); + void slotBanCommand( const QString &args, Kopete::ChatSession *manager); + void slotOpCommand( const QString &args, Kopete::ChatSession *manager); + void slotDeopCommand( const QString &args, Kopete::ChatSession *manager); + void slotVoiceCommand( const QString &args, Kopete::ChatSession *manager); + void slotDevoiceCommand( const QString &args, Kopete::ChatSession *manager); + void slotQuitCommand( const QString &args, Kopete::ChatSession *manager); + void slotPartCommand( const QString &args, Kopete::ChatSession *manager); + void slotInviteCommand( const QString &args, Kopete::ChatSession *manager); + + void slotViewCreated( KopeteView * ); + +private: + static IRCProtocol *s_protocol; + + void simpleModeChange( const QString &, Kopete::ChatSession *, const QString &mode ); + + // FIXME: All the code for managing the networks list should be in another class - Will + void storeCurrentNetwork(); + void storeCurrentHost(); + + NetworkConfig *netConf; + QString m_uiCurrentNetworkSelection; + QString m_uiCurrentHostSelection; + // end of network list specific code + + DOM::Node activeNode; + IRCAccount *activeAccount; + + bool m_commandInProgress; + + QDict m_networks; + QDict m_hosts; + IRCProtocolHandler *m_protocolHandler; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/irc/ircservercontact.cpp b/kopete/protocols/irc/ircservercontact.cpp new file mode 100644 index 00000000..3ca21643 --- /dev/null +++ b/kopete/protocols/irc/ircservercontact.cpp @@ -0,0 +1,220 @@ +/* + ircservercontact.cpp - IRC Server Contact + + Copyright (c) 2003 by Michel Hermier + Copyright (c) 2002 by Nick Betcher + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "ircusercontact.h" +#include "ircservercontact.h" +#include "ircaccount.h" +#include "ircprotocol.h" + +#include "kopetechatsessionmanager.h" +#include "kopeteview.h" + +#include +#include +#include + +#include + +IRCServerContact::IRCServerContact(IRCContactManager *contactManager, const QString &servername, Kopete::MetaContact *m) + : IRCContact(contactManager, servername, m, "irc_server") +{ + KIRC::Engine *engine = kircEngine(); + + QObject::connect(engine, SIGNAL(internalError(KIRC::Engine::Error, KIRC::Message &)), + this, SLOT(engineInternalError(KIRC::Engine::Error, KIRC::Message &))); +/* + //FIXME: Have some kind of a debug option for raw input/ouput display?? + QObject::connect(engine, SIGNAL(sentMessage(KIRC::Message &)), + this, SLOT(engineSentMessage(KIRC::Message &))); + QObject::connect(engine, SIGNAL(receivedMessage(KIRC::Message &)), + this, SLOT(engineReceivedMessage(KIRC::Message &))); +*/ + + QObject::connect(engine, SIGNAL(incomingNotice(const QString &, const QString &)), + this, SLOT(slotIncomingNotice(const QString &, const QString &))); + + QObject::connect(engine, SIGNAL(incomingCannotSendToChannel(const QString &, const QString &)), + this, SLOT(slotCannotSendToChannel(const QString &, const QString &))); + + QObject::connect(engine, SIGNAL(incomingUnknown(const QString &)), + this, SLOT(slotIncomingUnknown(const QString &))); + + QObject::connect(engine, SIGNAL(incomingConnectString(const QString &)), + this, SLOT(slotIncomingConnect(const QString &))); + + QObject::connect(engine, SIGNAL(incomingMotd(const QString &)), + this, SLOT(slotIncomingMotd(const QString &))); + + QObject::connect(Kopete::ChatSessionManager::self(), SIGNAL(viewCreated(KopeteView*)), + this, SLOT(slotViewCreated(KopeteView*)) ); + + updateStatus(); +} + +void IRCServerContact::updateStatus() +{ + KIRC::Engine::Status status = kircEngine()->status(); + switch( status ) + { + case KIRC::Engine::Idle: + case KIRC::Engine::Connecting: + if( m_chatSession ) + m_chatSession->setDisplayName( caption() ); + setOnlineStatus( m_protocol->m_ServerStatusOffline ); + break; + + case KIRC::Engine::Authentifying: + case KIRC::Engine::Connected: + case KIRC::Engine::Closing: + // should make some extra check here + setOnlineStatus( m_protocol->m_ServerStatusOnline ); + break; + + default: + setOnlineStatus( m_protocol->m_StatusUnknown ); + } +} + +const QString IRCServerContact::caption() const +{ + return i18n("%1 @ %2").arg(ircAccount()->mySelf()->nickName() ).arg( + kircEngine()->currentHost().isEmpty() ? ircAccount()->networkName() : kircEngine()->currentHost() + ); +} + +void IRCServerContact::engineInternalError(KIRC::Engine::Error engineError, KIRC::Message &ircmsg) +{ + QString error; + switch (engineError) + { + case KIRC::Engine::ParsingFailed: + error = i18n("KIRC Error - Parse error: "); + break; + case KIRC::Engine::UnknownCommand: + error = i18n("KIRC Error - Unknown command: "); + break; + case KIRC::Engine::UnknownNumericReply: + error = i18n("KIRC Error - Unknown numeric reply: "); + break; + case KIRC::Engine::InvalidNumberOfArguments: + error = i18n("KIRC Error - Invalid number of arguments: "); + break; + case KIRC::Engine::MethodFailed: + error = i18n("KIRC Error - Method failed: "); + break; + default: + error = i18n("KIRC Error - Unknown error: "); + } + + ircAccount()->appendMessage(error + QString(ircmsg.raw()), IRCAccount::ErrorReply); +} + +void IRCServerContact::slotSendMsg(Kopete::Message &, Kopete::ChatSession *manager) +{ + manager->messageSucceeded(); + Kopete::Message msg( manager->myself(), manager->members(), + i18n("You can not talk to the server, you can only issue commands here. Type /help for supported commands."), Kopete::Message::Internal, Kopete::Message::PlainText, CHAT_VIEW); + manager->appendMessage(msg); +} + +void IRCServerContact::appendMessage( const QString &message ) +{ + Kopete::ContactPtrList members; + members.append( this ); + Kopete::Message msg( this, members, message, Kopete::Message::Internal, + Kopete::Message::RichText, CHAT_VIEW ); + appendMessage(msg); +} + +void IRCServerContact::slotIncomingNotice( const QString &orig, const QString ¬ice ) +{ + if (orig.isEmpty()) { + // Prefix missing. + // NOTICE AUTH :*** Checking Ident + + ircAccount()->appendMessage(i18n("NOTICE from %1: %2").arg(kircEngine()->currentHost(), notice), + IRCAccount::NoticeReply); + + } else { + // :Global!service@rizon.net NOTICE foobar :[Logon News - Oct 12 2005] Due to growing problems ... + // :somenick!~fooobar@somehostname.fi NOTICE foobar :hello + + if (orig.contains('!')) { + ircAccount()->appendMessage(i18n("NOTICE from %1 (%2): %3").arg( + orig.section('!', 0, 0), + orig.section('!', 1, 1), + notice), + IRCAccount::NoticeReply); + } else { + ircAccount()->appendMessage(i18n("NOTICE from %1: %2").arg( + orig, notice), IRCAccount::NoticeReply); + } + } +} + +void IRCServerContact::slotIncomingUnknown(const QString &message) +{ + ircAccount()->appendMessage(message, IRCAccount::UnknownReply); +} + +void IRCServerContact::slotIncomingConnect(const QString &message) +{ + ircAccount()->appendMessage(message, IRCAccount::ConnectReply); +} + +void IRCServerContact::slotIncomingMotd(const QString &message) +{ + ircAccount()->appendMessage(message, IRCAccount::InfoReply); +} + +void IRCServerContact::slotCannotSendToChannel(const QString &channel, const QString &message) +{ + ircAccount()->appendMessage(QString::fromLatin1("%1: %2").arg(channel).arg(message), IRCAccount::ErrorReply); +} + +void IRCServerContact::appendMessage(Kopete::Message &msg) +{ + msg.setImportance( Kopete::Message::Low ); //to don't distrub the user + + if (m_chatSession && m_chatSession->view(false)) + m_chatSession->appendMessage(msg); +/* +// disable the buffering for now: cause a memleak since we don't made it a *fixed size fifo* + else + mMsgBuffer.append( msg ); +*/ +} + +void IRCServerContact::slotDumpMessages() +{ + if (!mMsgBuffer.isEmpty()) + { + manager()->appendMessage( mMsgBuffer.front() ); + mMsgBuffer.pop_front(); + QTimer::singleShot( 0, this, SLOT( slotDumpMessages() ) ); + } +} + +void IRCServerContact::slotViewCreated( KopeteView *v ) +{ + kdDebug(14121) << k_funcinfo << "Created: " << v << ", mgr: " << v->msgManager() << ", Mine: " << m_chatSession << endl; + if (m_chatSession && v->msgManager() == m_chatSession) + QTimer::singleShot(500, this, SLOT(slotDumpMessages())); +} + +#include "ircservercontact.moc" diff --git a/kopete/protocols/irc/ircservercontact.h b/kopete/protocols/irc/ircservercontact.h new file mode 100644 index 00000000..1ca1475b --- /dev/null +++ b/kopete/protocols/irc/ircservercontact.h @@ -0,0 +1,80 @@ +/* + ircservercontact.h - IRC User Contact + + Copyright (c) 2003 by Michel Hermier + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef IRCSERVERCONTACT_H +#define IRCSERVERCONTACT_H + +#include "irccontact.h" + +#include "kircengine.h" + +#include "kopetechatsessionmanager.h" + +#include +#include + +class KActionCollection; +class KAction; +class KActionMenu; +class KopeteView; + +class IRCContactManager; +class IRCChannelContact; + +/** + * @author Michel Hermier + * + * This class is the @ref Kopete::Contact object representing IRC Servers. + * It is derrived from @ref IRCContact where much of its functionality is shared with @ref IRCChannelContact and @ref IRCUserContact. + */ +class IRCServerContact + : public IRCContact +{ + Q_OBJECT + + public: + // This class provides a Kopete::Contact for each server of a given IRC connection. + IRCServerContact(IRCContactManager *, const QString &servername, Kopete::MetaContact *mc); + + virtual const QString caption() const; + + virtual void appendMessage(Kopete::Message &); + void appendMessage( const QString &message ); + + protected slots: + void engineInternalError(KIRC::Engine::Error error, KIRC::Message &ircmsg); + virtual void slotSendMsg(Kopete::Message &message, Kopete::ChatSession *); + + private slots: + virtual void updateStatus(); + void slotViewCreated( KopeteView* ); + void slotDumpMessages(); + + void slotIncomingUnknown( const QString &message ); + void slotIncomingConnect( const QString &message ); + void slotIncomingMotd( const QString &motd ); + void slotIncomingNotice( const QString &orig, const QString ¬ice ); + void slotCannotSendToChannel( const QString &channel, const QString &msg ); + + private: + QValueList mMsgBuffer; +}; + +#endif + +// vim: set noet ts=4 sts=4 tw=4: + diff --git a/kopete/protocols/irc/ircsignalhandler.cpp b/kopete/protocols/irc/ircsignalhandler.cpp new file mode 100644 index 00000000..5bfab0cc --- /dev/null +++ b/kopete/protocols/irc/ircsignalhandler.cpp @@ -0,0 +1,173 @@ + +/* + ircsignalhandler.cpp - Maps signals from the IRC engine to contacts + + Copyright (c) 2004 by Jason Keirstead + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "ircusercontact.h" +#include "ircchannelcontact.h" +#include "ircsignalhandler.h" + +#include "kircengine.h" + +IRCSignalHandler::IRCSignalHandler(IRCContactManager *m) + : QObject(m), + manager(m) +{ + KIRC::Engine *m_engine = static_cast( manager->mySelf()->account() )->engine(); + + //Channel Connections to ourself + QObject::connect(m_engine, SIGNAL(incomingNamesList(const QString &, const QStringList &)), + this, SLOT(slotNamesList(const QString &, const QStringList &))); + + QObject::connect(m_engine, SIGNAL(incomingEndOfNames(const QString &)), + this, SLOT(slotEndOfNames(const QString &))); + + QObject::connect(m_engine, SIGNAL(incomingTopicUser(const QString &, const QString &, const QDateTime &)), + this, SLOT(slotTopicUser(const QString&,const QString&,const QDateTime&))); + + //Channel String mappings + map( m, SIGNAL(incomingFailedChankey(const QString &)), + &IRCChannelContact::failedChankey ); + + map( m, SIGNAL(incomingFailedChanFull(const QString &)), + &IRCChannelContact::failedChanInvite ); + + map( m, SIGNAL(incomingFailedChanInvite(const QString &)), + &IRCChannelContact::failedChanInvite ); + + map( m, SIGNAL(incomingFailedChanBanned(const QString &)), + &IRCChannelContact::failedChanBanned ); + + mapSingle( m, SIGNAL(incomingJoinedChannel(const QString &, const QString &)), + &IRCChannelContact::userJoinedChannel ); + + mapSingle( m, SIGNAL(incomingExistingTopic(const QString &, const QString &)), + &IRCChannelContact::channelTopic ); + + mapSingle( m, SIGNAL(incomingChannelHomePage(const QString &, const QString &)), + &IRCChannelContact::channelHomePage ); + + mapDouble( m, + SIGNAL(incomingPartedChannel(const QString &, const QString &,const QString &)), + &IRCChannelContact::userPartedChannel ); + + mapDouble( m, + SIGNAL(incomingTopicChange(const QString &, const QString &,const QString &)), + &IRCChannelContact::topicChanged ); + + mapDouble( m, + SIGNAL(incomingChannelModeChange(const QString &, const QString &,const QString &)), + &IRCChannelContact::incomingModeChange ); + + mapDouble( m, + SIGNAL(incomingChannelMode(const QString &, const QString &,const QString &)), + &IRCChannelContact::incomingChannelMode ); + + mapTriple( m, + SIGNAL(incomingKick(const QString &, const QString &,const QString &,const QString &)), + &IRCChannelContact::userKicked ); + + //User connections to ourself + QObject::connect(m_engine, SIGNAL(incomingWhoIsIdle(const QString &, unsigned long )), + this, SLOT(slotNewWhoIsIdle(const QString &, unsigned long ))); + + QObject::connect(m_engine, SIGNAL(incomingWhoReply(const QString &, const QString &, const QString &, + const QString &, const QString &, bool, const QString &, uint, const QString & )), + this, SLOT( slotNewWhoReply(const QString &, const QString &, const QString &, const QString &, + const QString &, bool, const QString &, uint, const QString &))); + + //User signal mappings + map( m, SIGNAL(incomingUserOnline( const QString & )), &IRCUserContact::userOnline ); + + map( m, SIGNAL(incomingWhoIsOperator( const QString & )), &IRCUserContact::newWhoIsOperator ); + + map( m, SIGNAL(incomingWhoIsIdentified( const QString & )), &IRCUserContact::newWhoIsIdentified ); + + map( m, SIGNAL(incomingEndOfWhois( const QString & )), &IRCUserContact::whoIsComplete ); + + map( m, SIGNAL(incomingEndOfWhoWas( const QString & )), &IRCUserContact::whoWasComplete ); + + mapSingle( m, SIGNAL(incomingUserIsAway( const QString &, const QString & )), + &IRCUserContact::incomingUserIsAway ); + + mapSingle( m, SIGNAL(incomingWhoIsChannels( const QString &, const QString & )), + &IRCUserContact::newWhoIsChannels ); + + mapDouble( m, + SIGNAL(incomingWhoIsServer(const QString &, const QString &, const QString &)), + &IRCUserContact::newWhoIsServer ); + + mapDouble( m, + SIGNAL(incomingPrivAction(const QString &, const QString &, const QString &)), + &IRCUserContact::newAction ); + + mapDouble( m, + SIGNAL(incomingAction(const QString &, const QString &, const QString &)), + &IRCChannelContact::newAction ); + + mapTriple( m, + SIGNAL(incomingWhoIsUser(const QString &, const QString &, const QString &, const QString &)), + &IRCUserContact::newWhoIsUser ); + + mapTriple( m, + SIGNAL(incomingWhoWasUser(const QString &, const QString &, const QString &, const QString &)), + &IRCUserContact::newWhoIsUser ); +} + +IRCSignalHandler::~IRCSignalHandler() +{ + //Delete our mapping pointers + for( QValueList::iterator it = mappings.begin(); it != mappings.end(); ++it ) + delete *it; +} + +void IRCSignalHandler::slotNamesList( const QString &chan, const QStringList &list ) +{ + IRCChannelContact *c = manager->existChannel( chan ); + if( c ) + c->namesList( list ); +} + +void IRCSignalHandler::slotEndOfNames( const QString &chan ) +{ + IRCChannelContact *c = manager->existChannel( chan ); + if ( c ) + c->endOfNames(); +} + +void IRCSignalHandler::slotTopicUser(const QString &chan, const QString &user,const QDateTime &time) +{ + IRCChannelContact *c = manager->existChannel( chan ); + if( c ) + c->topicUser( user, time ); +} + +void IRCSignalHandler::slotNewWhoIsIdle(const QString &nick, unsigned long val ) +{ + IRCUserContact *c = manager->findUser( nick ); + if( c ) + c->newWhoIsIdle( val ); +} + +void IRCSignalHandler::slotNewWhoReply(const QString &nick, const QString &arg1, const QString &arg2, + const QString &arg3, const QString &arg4, bool arg5, const QString &arg6, uint arg7, const QString &arg8 ) +{ + IRCUserContact *c = manager->findUser( nick ); + if( c ) + c->newWhoReply( arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 ); +} + +#include "ircsignalhandler.moc" diff --git a/kopete/protocols/irc/ircsignalhandler.h b/kopete/protocols/irc/ircsignalhandler.h new file mode 100644 index 00000000..a87f814c --- /dev/null +++ b/kopete/protocols/irc/ircsignalhandler.h @@ -0,0 +1,334 @@ + +/* + ircsignalhandler.h - Maps signals from the IRC engine to contacts + + Copyright (c) 2004 by Jason Keirstead + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef _IRC_SIGNAL_HANDLER_H +#define _IRC_SIGNAL_HANDLER_H + +#include +#include +#include + +#include + +#include "ircaccount.h" +#include "irccontactmanager.h" + +/*** +* IRC Signal handler. Mapps a KIRC engine signal to the right contact. Avoids +* Having a signal connected to 500+ slots where only one is valid, instead +* uses the contact dictionary. +* +* Warning: This file has a lot of black magic in it. Avoid it if +* you don't want your eyes to bleed. More below... +* +* Define some templated classes and methods to map a KIRC signal to the +* right contact. Having these templates greatly cuts down *A LOT* on the amount of +* code that would need to be in the signal mapper, at the expense of some readability. +* +* There are four IRCSignalMapping classes, one each for signals with 0, 1, 2, +* and 3 arguments ( plus the contact ID ). The classes take the signal, look +* up the contact it is for, and call the function passed into the class by the +* mapping function. +* +* Since QObjects cannot be inside templates, the QMember classes that connect +* to the slots are seperate. +*/ + +/*** Pre-declare mapping types for the QObjects **/ +struct IRCSignalMappingBase{}; + +struct IRCSignalMappingT : IRCSignalMappingBase +{ + virtual void exec( const QString & ) = 0; + virtual ~IRCSignalMappingT() {}; +}; + +struct IRCSignalMappingSingleT : IRCSignalMappingBase +{ + virtual void exec( const QString &, const QString & ) = 0; + virtual ~IRCSignalMappingSingleT() {}; +}; + +struct IRCSignalMappingDoubleT : IRCSignalMappingBase +{ + virtual void exec( const QString &, const QString &, const QString & ) = 0; + virtual ~IRCSignalMappingDoubleT() {}; +}; + +struct IRCSignalMappingTripleT : IRCSignalMappingBase +{ + virtual void exec( const QString &, const QString &, const QString &, const QString & ) = 0; + virtual ~IRCSignalMappingTripleT() {}; +}; + +/*** +QObject members, these connect to the KIRC signals and call +the Mapping functions when they emit. +**/ + +class QMember : public QObject +{ + Q_OBJECT + + public: + QMember( IRCSignalMappingT *m, QObject *p ) : QObject( p ), mapping( m ){}; + + public slots: + void slotEmit( const QString &id ) + { + //kdDebug(14120) << k_funcinfo << id << endl; + mapping->exec(id); + } + + private: + IRCSignalMappingT *mapping; +}; + +class QMemberSingle : public QObject +{ + Q_OBJECT + + public: + QMemberSingle( IRCSignalMappingSingleT *m, QObject *p ) : QObject( p ), mapping( m ){} + + public slots: + void slotEmit( const QString &id, const QString &arg ) + { + //kdDebug(14120) << k_funcinfo << id << " : " << arg << endl; + mapping->exec(id,arg); + } + + private: + IRCSignalMappingSingleT *mapping; +}; + +class QMemberDouble : public QObject +{ + Q_OBJECT + + public: + QMemberDouble( IRCSignalMappingDoubleT *m, QObject *p ) : QObject( p ), mapping( m ){} + + public slots: + void slotEmit( const QString &id, const QString &arg, const QString &arg2 ) + { + //kdDebug(14120) << k_funcinfo << id << " : " << arg << " : " << arg2 << endl; + mapping->exec(id,arg,arg2); + } + + private: + IRCSignalMappingDoubleT *mapping; +}; + +class QMemberTriple : public QObject +{ + Q_OBJECT + + public: + QMemberTriple( IRCSignalMappingTripleT *m, QObject *p ) : QObject( p ), mapping( m ){} + + public slots: + void slotEmit( const QString &id, const QString &arg, const QString &arg2, const QString &arg3 ) + { + //kdDebug(14120) << k_funcinfo << id << " : " << arg << " : " << arg2 << " : " << arg3 << endl; + mapping->exec(id,arg,arg2,arg3); + } + + private: + IRCSignalMappingTripleT *mapping; +}; + +/*** +Mapping classes. These contain pointers to the functions to call. We first +look up the right contact in the contact manager's dictionary, and then +call the method +**/ + +template +class IRCSignalMapping : public IRCSignalMappingT +{ + public: + IRCSignalMapping( IRCContactManager *mgr, const char * /*signal*/, + void (TClass::*m)() ) : manager(mgr), method(m){} + + void exec( const QString &id ) + { + TClass *c = (TClass*)manager->findContact( id ); + if( c ) + { + void (TClass::*tmp)() = (void (TClass::*)())method; + (*c.*tmp)(); + } + } + + private: + IRCContactManager *manager; + void (TClass::*method)(); +}; + +template +class IRCSignalMappingSingle : public IRCSignalMappingSingleT +{ + public: + IRCSignalMappingSingle( IRCContactManager *mgr, const char * /*signal*/, + void (TClass::*m)(const QString&) ) : manager(mgr), method(m){} + + void exec( const QString &id, const QString &arg ) + { + TClass *c = (TClass*)manager->findContact( id ); + if( c ) + { + void (TClass::*tmp)(const QString&) = (void (TClass::*)(const QString&))method; + (*c.*tmp)( arg ); + } + } + + private: + IRCContactManager *manager; + void (TClass::*method)(const QString &); +}; + +template +class IRCSignalMappingDouble : public IRCSignalMappingDoubleT +{ + public: + IRCSignalMappingDouble( IRCContactManager *mgr, const char * /*signal*/, + void (TClass::*m)(const QString&,const QString&) ) : manager(mgr), method(m){} + + void exec( const QString &id,const QString &arg, const QString &arg2 ) + { + TClass *c = (TClass*)manager->findContact( id ); + if( c ) + { + void (TClass::*tmp)(const QString&,const QString&) = + (void (TClass::*)(const QString&,const QString&))method; + (*c.*tmp)(arg,arg2); + } + } + + private: + IRCContactManager *manager; + void (TClass::*method)(const QString &,const QString &); +}; + +template +class IRCSignalMappingTriple : public IRCSignalMappingTripleT +{ + public: + IRCSignalMappingTriple( IRCContactManager *mgr, const char * /*signal*/, + void (TClass::*m)(const QString&,const QString&,const QString&) ) + : manager(mgr), method(m){} + + void exec( const QString &id,const QString&arg, const QString &arg2, const QString &arg3 ) + { + TClass *c = (TClass*)manager->findContact( id ); + if( c ) + { + void (TClass::*tmp)(const QString&,const QString&,const QString&) = + (void (TClass::*)(const QString&,const QString&,const QString&))method; + (*c.*tmp)(arg,arg2,arg3); + } + } + + private: + IRCContactManager *manager; + void (TClass::*method)(const QString &,const QString &,const QString &); +}; + +class IRCSignalHandler : public QObject +{ + Q_OBJECT + + public: + IRCSignalHandler( IRCContactManager *manager ); + ~IRCSignalHandler(); + + private slots: + + /**** + Slots for signals with non-QString types + */ + + //Channel contact slots + void slotNamesList( const QString &, const QStringList & ); + void slotEndOfNames( const QString & ); + void slotTopicUser( const QString &, const QString&, const QDateTime &); + + //User contact slots + void slotNewWhoIsIdle(const QString &, unsigned long ); + void slotNewWhoReply(const QString &, const QString &, const QString &, const QString &, + const QString &, bool , const QString &, uint , const QString & ); + + private: + IRCContactManager *manager; + QValueList mappings; + + /**** + Signal mapping functions + */ + + template + inline void map( IRCContactManager *m, const char* signal, void (TClass::*method)() ) + { + IRCSignalMappingT *mapping = new IRCSignalMapping( m, signal, method ); + mappings.append(mapping); + QObject::connect( static_cast( m->mySelf()->account() )->engine(), signal, + new QMember( mapping, this), + SLOT( slotEmit( const QString &) ) + ); + } + + template + inline void mapSingle( IRCContactManager *m, + const char* signal, void (TClass::*method)(const QString&) ) + { + IRCSignalMappingSingleT *mapping = new IRCSignalMappingSingle( m, signal, method ); + mappings.append(mapping); + QObject::connect( static_cast( m->mySelf()->account() )->engine(), signal, + new QMemberSingle( mapping, this), + SLOT( slotEmit( const QString &, const QString &) ) + ); + } + + template + inline void mapDouble( IRCContactManager *m, + const char* signal, void (TClass::*method)(const QString&,const QString&) ) + { + IRCSignalMappingDoubleT *mapping = new IRCSignalMappingDouble( m, signal, method ); + mappings.append(mapping); + QObject::connect( static_cast( m->mySelf()->account() )->engine(), signal, + new QMemberDouble( mapping, this), + SLOT( slotEmit( const QString &, const QString &,const QString &) ) + ); + } + + template + inline void mapTriple( IRCContactManager *m, + const char* signal, + void (TClass::*method)(const QString&,const QString &, const QString &) ) + { + IRCSignalMappingTripleT *mapping = new IRCSignalMappingTriple( m, signal, method ); + mappings.append(mapping); + QObject::connect( static_cast( m->mySelf()->account() )->engine(), signal, + new QMemberTriple( mapping, this), + SLOT( slotEmit( const QString &, const QString &,const QString &,const QString &) ) + ); + } +}; + +#endif diff --git a/kopete/protocols/irc/irctransferhandler.cpp b/kopete/protocols/irc/irctransferhandler.cpp new file mode 100644 index 00000000..4715679e --- /dev/null +++ b/kopete/protocols/irc/irctransferhandler.cpp @@ -0,0 +1,183 @@ +/* + irctransferhandler.cpp - IRC transfer. + + Copyright (c) 2004 by Michel Hermier + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include + +#include + +#include "libkirc/kirctransfer.h" +#include "libkirc/kirctransferhandler.h" + +#include "kopetemetacontact.h" +#include "irccontact.h" +#include "irccontactmanager.h" + +#include "irctransferhandler.h" + +IRCTransferHandler *IRCTransferHandler::self() +{ + static IRCTransferHandler sm_self; + return &sm_self; +} + +KIRC::TransferHandler *IRCTransferHandler::handler() +{ + return KIRC::TransferHandler::self(); +} + +IRCTransferHandler::IRCTransferHandler() +{ + connect(handler(), SIGNAL(transferCreated(KIRC::Transfer *)), + this, SLOT(transferCreated(KIRC::Transfer *))); + + connect(Kopete::TransferManager::transferManager(), SIGNAL(accepted(Kopete::Transfer *, const QString &)), + this, SLOT(transferAccepted(Kopete::Transfer *, const QString&))); + connect( Kopete::TransferManager::transferManager(), SIGNAL(refused(const Kopete::FileTransferInfo &)), + this, SLOT(transferRefused(const Kopete::FileTransferInfo &))); +} + +void IRCTransferHandler::transferCreated(KIRC::Transfer *t) +{ + kdDebug(14120) << k_funcinfo << endl; + + IRCContact *contact = IRCContactManager::existContact(t->engine(), t->nick()); + QString fileName = t->fileName(); + unsigned long fileSize = t->fileSize(); + + if(!contact) + { + kdDebug(14120) << k_funcinfo << "Trying to create transfer for a non existing contact(" << t->nick() << ")." << endl; + return; + } + + switch(t->type()) + { +// case KIRC::Transfer::Chat: + case KIRC::Transfer::FileOutgoing: + { + Kopete::Transfer *kt = Kopete::TransferManager::transferManager()->addTransfer( + contact, fileName, fileSize, contact->metaContact()->displayName(), + Kopete::FileTransferInfo::Outgoing); + connectKopeteTransfer(kt, t); + } + break; + case KIRC::Transfer::FileIncoming: + { + int ID = Kopete::TransferManager::transferManager()->askIncomingTransfer( + contact , fileName, fileSize); + m_idMap.insert(ID, t); + } + break; + default: + kdDebug(14120) << k_funcinfo << "Unknown transfer type" << endl; + t->deleteLater(); + } +} + +void IRCTransferHandler::transferAccepted(Kopete::Transfer *kt, const QString &file) +{ + kdDebug(14120) << k_funcinfo << endl; + + KIRC::Transfer *t = getKIRCTransfer(kt->info()); + if(t) + { + t->setFileName(file); + connectKopeteTransfer(kt, t); + } +} +void IRCTransferHandler::transferRefused(const Kopete::FileTransferInfo &info) +{ + kdDebug(14120) << k_funcinfo << endl; + + KIRC::Transfer *t = getKIRCTransfer(info); + if(t) + { + t->deleteLater(); + } +} + +void IRCTransferHandler::connectKopeteTransfer(Kopete::Transfer *kt, KIRC::Transfer *t) +{ + kdDebug(14120) << k_funcinfo << endl; + + if(kt && t) + { + switch(t->type()) + { +// case KIRC::Transfer::Chat: + case KIRC::Transfer::FileOutgoing: + case KIRC::Transfer::FileIncoming: + connect(t , SIGNAL(fileSizeAcknowledge(unsigned int)), + kt, SLOT(slotProcessed(unsigned int))); + break; + default: + kdDebug(14120) << k_funcinfo << "Unknown transfer connections for type" << endl; + t->deleteLater(); + return; + } + + connect(t , SIGNAL(complete()), + kt, SLOT(slotComplete())); + +// connect(kt , SIGNAL(transferCanceled()), +// t, SLOT(abort())); +// connect(kt, SIGNAL(destroyed()), +// t, SLOT(slotKopeteTransferDestroyed())); + + connect(kt, SIGNAL(result(KIO::Job *)), + this , SLOT(kioresult(KIO::Job *))); + + t->initiate(); + } +} + +void IRCTransferHandler::kioresult(KIO::Job *job) +{ + Kopete::Transfer *kt= (Kopete::Transfer *)job; // FIXME: move to *_cast + if(!kt) + { + kdDebug(14120) << k_funcinfo << "Kopete::Transfer not found from kio:" << job << endl; + return; + } + + switch(kt->error()) + { + case 0: // 0 means no error + break; + case KIO::ERR_USER_CANCELED: + kdDebug(14120) << k_funcinfo << "User canceled transfer." << endl; + // KIO::buildErrorString form error don't provide a result string ... +// if (t->) +// kt->userAbort(i18n("User canceled transfer.")); +// else +// kt->userAbort(i18n("User canceled transfer for file:%1").arg(t->fileName())); + break; + default: + kdDebug(14120) << k_funcinfo << "Transfer halted:" << kt->error() << endl; +// kt->userAbort(KIO::buildErrorString(kt->error(), kt->fileName())); + break; + } +} + +KIRC::Transfer *IRCTransferHandler::getKIRCTransfer(const Kopete::FileTransferInfo &info) +{ + KIRC::Transfer *t = m_idMap[info.transferId()]; + m_idMap.remove(info.transferId()); + return t; +} + +#include "irctransferhandler.moc" diff --git a/kopete/protocols/irc/irctransferhandler.h b/kopete/protocols/irc/irctransferhandler.h new file mode 100644 index 00000000..17c419ae --- /dev/null +++ b/kopete/protocols/irc/irctransferhandler.h @@ -0,0 +1,65 @@ +/* + irctransferhandler.h - IRC transfer. + + Copyright (c) 2003 by Michel Hermier + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef IRCTRANSFERHANDLER_H +#define IRCTRANSFERHANDLER_H + +#include + +#include + +namespace Kopete +{ + class Transfer; +} + +namespace KIRC +{ +class Transfer; +class TransferHandler; +} + +class IRCTransferHandler + : public QObject +{ + Q_OBJECT + +public: + static IRCTransferHandler *self(); + +private slots: + void transferCreated(KIRC::Transfer *); + void transferAccepted(Kopete::Transfer *kt, const QString&file); + void transferRefused(const Kopete::FileTransferInfo &info); + + void kioresult(KIO::Job *job); + +private: + IRCTransferHandler(); + + void connectKopeteTransfer(Kopete::Transfer *kt, KIRC::Transfer *t); + + /* warning: After calling this method the KIRC::Transfer is removed from the m_idMap. + */ + KIRC::Transfer *getKIRCTransfer(const Kopete::FileTransferInfo &info); + + KIRC::TransferHandler *handler(); + + QIntDict m_idMap; +}; + +#endif diff --git a/kopete/protocols/irc/ircusercontact.cpp b/kopete/protocols/irc/ircusercontact.cpp new file mode 100644 index 00000000..dc9dcbf2 --- /dev/null +++ b/kopete/protocols/irc/ircusercontact.cpp @@ -0,0 +1,734 @@ +/* + ircusercontact.cpp - IRC User Contact + + Copyright (c) 2002 by Nick Betcher + + Kopete (c) 2002 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "ircusercontact.h" +#include "ircservercontact.h" +#include "ircchannelcontact.h" +#include "irccontactmanager.h" +#include "ircaccount.h" +#include "ircprotocol.h" +#include "kcodecaction.h" + +#include "kopetemetacontact.h" +#include "kopeteview.h" + +#include +#include +#include +#include + +#include + +IRCUserContact::IRCUserContact(IRCContactManager *contactManager, const QString &nickname, Kopete::MetaContact *m ) + : IRCContact(contactManager, nickname, m ), + actionCtcpMenu(0L) +{ + setFileCapable(true); + + mOnlineTimer = new QTimer( this ); + + QObject::connect(mOnlineTimer, SIGNAL(timeout()), this, SLOT( slotUserOffline() ) ); + + QObject::connect(kircEngine(), SIGNAL(incomingChannelModeChange(const QString&, const QString&, const QString&)), + this, SLOT(slotIncomingModeChange(const QString&,const QString&, const QString&))); + + mInfo.isOperator = false; + mInfo.isIdentified = false; + mInfo.idle = 0; + mInfo.hops = 0; + mInfo.away = false; + mInfo.online = metaContact()->isTemporary(); + + updateStatus(); +} + +void IRCUserContact::updateStatus() +{ + //kdDebug(14120) << k_funcinfo << endl; + + Kopete::OnlineStatus newStatus; + + switch (kircEngine()->status()) + { + case KIRC::Engine::Idle: + newStatus = m_protocol->m_UserStatusOffline; + break; + + case KIRC::Engine::Connecting: + case KIRC::Engine::Authentifying: + if (this == ircAccount()->mySelf()) + newStatus = m_protocol->m_UserStatusConnecting; + else + newStatus = m_protocol->m_UserStatusOffline; + break; + + case KIRC::Engine::Connected: + case KIRC::Engine::Closing: + if (mInfo.away) + newStatus = m_protocol->m_UserStatusAway; + else if (mInfo.online) + newStatus = m_protocol->m_UserStatusOnline; + break; + + default: + newStatus = m_protocol->m_StatusUnknown; + } + + // Try hard not to emit several onlineStatusChanged() signals. + bool onlineStatusChanged = false; + + + /* The away status is global, so if the user goes away, we must set + * the new status on all channels. + */ + + + // This may not be created yet ( for myself() on startup ) + if( ircAccount()->contactManager() ) + { + QValueList channels = ircAccount()->contactManager()->findChannelsByMember(this); + + for( QValueList::iterator it = channels.begin(); it != channels.end(); ++it ) + { + IRCChannelContact *channel = *it; + Kopete::OnlineStatus currentStatus = channel->manager()->contactOnlineStatus(this); + + //kdDebug(14120) << k_funcinfo << "iterating channel " << channel->nickName() << " internal status: " << currentStatus.internalStatus() << endl; + + if( currentStatus.internalStatus() >= IRCProtocol::Online ) + { + onlineStatusChanged = true; + + if( !(currentStatus.internalStatus() & IRCProtocol::Away) && newStatus == m_protocol->m_UserStatusAway ) + { + setOnlineStatus( newStatus ); + //kdDebug(14120) << k_funcinfo << "was NOT away, but is now, channel " << channel->nickName() << endl; + adjustInternalOnlineStatusBits(channel, IRCProtocol::Away, AddBits); + } + else if( (currentStatus.internalStatus() & IRCProtocol::Away) && newStatus == m_protocol->m_UserStatusOnline ) + { + setOnlineStatus( newStatus ); + //kdDebug(14120) << k_funcinfo << "was away, but not anymore, channel " << channel->nickName() << endl; + adjustInternalOnlineStatusBits(channel, IRCProtocol::Away, RemoveBits); + + } + else if( newStatus.internalStatus() < IRCProtocol::Away ) + { + //kdDebug(14120) << k_funcinfo << "offline or connecting?" << endl; + channel->manager()->setContactOnlineStatus( this, newStatus ); + } + } + } + } + + if (!onlineStatusChanged) { + //kdDebug(14120) << k_funcinfo << "setting status at last" << endl; + setOnlineStatus( newStatus ); + } +} + +void IRCUserContact::sendFile(const KURL &sourceURL, const QString&, unsigned int) +{ + QString filePath; + + //If the file location is null, then get it from a file open dialog + if( !sourceURL.isValid() ) + filePath = KFileDialog::getOpenFileName(QString::null, "*", 0l , i18n("Kopete File Transfer")); + else + filePath = sourceURL.path(-1); + + kdDebug(14120) << k_funcinfo << "File chosen to send:" << filePath << endl; + + if (!filePath.isEmpty()) + kircEngine()->CtcpRequest_dcc( m_nickName, filePath, 0, KIRC::Transfer::FileOutgoing); +} + +void IRCUserContact::slotUserOffline() +{ + mInfo.online = false; + mInfo.away = false; + + updateStatus(); + + if( !metaContact()->isTemporary() ) + kircEngine()->writeMessage( QString::fromLatin1("WHOWAS %1").arg(m_nickName) ); + + removeProperty( m_protocol->propUserInfo ); + removeProperty( m_protocol->propServer ); + removeProperty( m_protocol->propChannels ); +} + +void IRCUserContact::setAway(bool isAway) +{ + //kdDebug(14120) << k_funcinfo << isAway << endl; + + mInfo.away = isAway; + updateStatus(); +} + +void IRCUserContact::incomingUserIsAway(const QString &reason) +{ + if( manager( Kopete::Contact::CannotCreate ) ) + { + Kopete::Message msg( (Kopete::Contact*)ircAccount()->myServer(), mMyself, + i18n("%1 is away (%2)").arg( m_nickName ).arg( reason ), + Kopete::Message::Internal, Kopete::Message::RichText, CHAT_VIEW ); + manager(Kopete::Contact::CanCreate)->appendMessage(msg); + } +} + +void IRCUserContact::userOnline() +{ + mInfo.online = true; + updateStatus(); + if (this != ircAccount()->mySelf() && !metaContact()->isTemporary() && ircAccount()->isConnected()) + { + mOnlineTimer->start( 45000, true ); + ircAccount()->setCurrentCommandSource(0); + kircEngine()->whois(m_nickName); + } + + removeProperty( m_protocol->propLastSeen ); +} + +void IRCUserContact::slotUserInfo() +{ + if (isChatting()) + { + ircAccount()->setCurrentCommandSource(manager()); + kircEngine()->whois(m_nickName); + } +} + +const QString IRCUserContact::caption() const +{ + return i18n("%1 @ %2").arg(m_nickName).arg(kircEngine()->currentHost()); +} + +void IRCUserContact::slotOp() +{ + contactMode( QString::fromLatin1("+o") ); +} + +void IRCUserContact::slotDeop() +{ + contactMode( QString::fromLatin1("-o") ); +} + +void IRCUserContact::slotVoice() +{ + contactMode( QString::fromLatin1("+v") ); +} + +void IRCUserContact::slotDevoice() +{ + contactMode( QString::fromLatin1("-v") ); +} + +void IRCUserContact::slotBanHost() +{ + // MODE #foofoofoo +b *!*@host.domain.net + + if (mInfo.hostName.isEmpty()) { + if (kircEngine()->isConnected()) { + kircEngine()->whois(m_nickName); + QTimer::singleShot( 750, this, SLOT( slotBanHostOnce() ) ); + } + } else { + slotBanHostOnce(); + } +} +void IRCUserContact::slotBanHostOnce() +{ + if (mInfo.hostName.isEmpty()) + return; + + Kopete::ContactPtrList members = mActiveManager->members(); + QString channelName = static_cast(members.first())->nickName(); + + kircEngine()->mode(channelName, QString::fromLatin1("+b *!*@%1").arg(mInfo.hostName)); +} + +void IRCUserContact::slotBanUserHost() +{ + // MODE #foofoofoo +b *!*user@host.domain.net + + if (mInfo.hostName.isEmpty()) { + if (kircEngine()->isConnected()) { + kircEngine()->whois(m_nickName); + QTimer::singleShot( 750, this, SLOT( slotBanUserHostOnce() ) ); + } + } else { + slotBanUserHostOnce(); + } +} +void IRCUserContact::slotBanUserHostOnce() +{ + if (mInfo.hostName.isEmpty()) + return; + + Kopete::ContactPtrList members = mActiveManager->members(); + QString channelName = static_cast(members.first())->nickName(); + + kircEngine()->mode(channelName, QString::fromLatin1("+b *!*%1@%2").arg(mInfo.userName, mInfo.hostName)); +} + +void IRCUserContact::slotBanDomain() +{ + // MODE #foofoofoo +b *!*@*.domain.net + + if (mInfo.hostName.isEmpty()) { + if (kircEngine()->isConnected()) { + kircEngine()->whois(m_nickName); + QTimer::singleShot( 750, this, SLOT( slotBanDomainOnce() ) ); + } + } else { + slotBanDomainOnce(); + } +} +void IRCUserContact::slotBanDomainOnce() +{ + if (mInfo.hostName.isEmpty()) + return; + + Kopete::ContactPtrList members = mActiveManager->members(); + QString channelName = static_cast(members.first())->nickName(); + + QString domain = mInfo.hostName.section('.', 1); + + kircEngine()->mode(channelName, QString::fromLatin1("+b *!*@*.%1").arg(domain)); +} + +void IRCUserContact::slotBanUserDomain() +{ + // MODE #foofoofoo +b *!*user@*.domain.net + + if (mInfo.hostName.isEmpty()) { + if (kircEngine()->isConnected()) { + kircEngine()->whois(m_nickName); + QTimer::singleShot( 750, this, SLOT( slotBanUserDomainOnce() ) ); + } + } else { + slotBanUserDomainOnce(); + } +} +void IRCUserContact::slotBanUserDomainOnce() +{ + if (mInfo.hostName.isEmpty()) + return; + + Kopete::ContactPtrList members = mActiveManager->members(); + QString channelName = static_cast(members.first())->nickName(); + + QString domain = mInfo.hostName.section('.', 1); + + kircEngine()->mode(channelName, QString::fromLatin1("+b *!*%1@*.%2").arg(mInfo.userName, domain)); +} + +void IRCUserContact::slotKick() +{ + Kopete::ContactPtrList members = mActiveManager->members(); + QString channelName = static_cast(members.first())->nickName(); + kircEngine()->kick(m_nickName, channelName, QString::null); +} + +void IRCUserContact::contactMode(const QString &mode) +{ + Kopete::ContactPtrList members = mActiveManager->members(); + QString channelName = static_cast(members.first())->nickName(); + kircEngine()->mode(channelName, QString::fromLatin1("%1 %2").arg(mode).arg(m_nickName)); +} + +void IRCUserContact::slotCtcpPing() +{ + kircEngine()->CtcpRequest_ping(m_nickName); +} + +void IRCUserContact::slotCtcpVersion() +{ + kircEngine()->CtcpRequest_version(m_nickName); +} + +void IRCUserContact::newWhoIsUser(const QString &username, const QString &hostname, const QString &realname) +{ + mInfo.channels.clear(); + mInfo.userName = username; + mInfo.hostName = hostname; + mInfo.realName = realname; + + if( onlineStatus().status() == Kopete::OnlineStatus::Offline ) + { + setProperty( m_protocol->propUserInfo, QString::fromLatin1("%1@%2") + .arg(mInfo.userName).arg(mInfo.hostName) ); + setProperty( m_protocol->propServer, mInfo.serverName ); + setProperty( m_protocol->propFullName, mInfo.realName ); + } +} + +void IRCUserContact::newWhoIsServer(const QString &servername, const QString &serverinfo) +{ + mInfo.serverName = servername; + if( metaContact()->isTemporary() || onlineStatus().status() == Kopete::OnlineStatus::Online + || onlineStatus().status() == Kopete::OnlineStatus::Away ) + mInfo.serverInfo = serverinfo; + else + { + //kdDebug(14120)<< "Setting last online: " << serverinfo << endl; + + // Try to convert first, since server can return depending if + // user is online or not: + // + // 312 mynick othernick localhost.localdomain :FooNet Server + // 312 mynick othernick localhost.localdomain :Thu Jun 16 21:00:36 2005 + + QDateTime lastSeen = QDateTime::fromString( serverinfo ); + if( lastSeen.isValid() ) + setProperty( m_protocol->propLastSeen, lastSeen ); + } +} + +void IRCUserContact::newWhoIsIdle(unsigned long idle) +{ + mInfo.idle = idle; +} + +void IRCUserContact::newWhoIsOperator() +{ + mInfo.isOperator = true; +} + +void IRCUserContact::newWhoIsIdentified() +{ + mInfo.isIdentified = true; + setProperty( m_protocol->propIsIdentified, i18n("True") ); +} + +void IRCUserContact::newWhoIsChannels(const QString &channel) +{ + mInfo.channels.append( channel ); +} + +void IRCUserContact::whoIsComplete() +{ + Kopete::ChatSession *commandSource = ircAccount()->currentCommandSource(); + + updateInfo(); + + if( isChatting() && commandSource && + commandSource == manager(Kopete::Contact::CannotCreate) ) + { + //User info + QString msg = i18n("%1 is (%2@%3): %4
    ") + .arg(m_nickName) + .arg(mInfo.userName) + .arg(mInfo.hostName) + .arg(mInfo.realName); + + if( mInfo.isIdentified ) + msg += i18n("%1 is authenticated with NICKSERV
    ").arg(m_nickName); + + if( mInfo.isOperator ) + msg += i18n("%1 is an IRC operator
    ").arg(m_nickName); + + //Channels + msg += i18n("on channels %1
    ").arg(mInfo.channels.join(" ; ")); + + //Server + msg += i18n("on IRC via server %1 ( %2 )
    ").arg(mInfo.serverName).arg(mInfo.serverInfo); + + //Idle + QString idleTime = formattedIdleTime(); + msg += i18n("idle: %2
    ").arg( idleTime.isEmpty() ? QString::number(0) : idleTime ); + + //End + ircAccount()->appendMessage(msg, IRCAccount::InfoReply ); + ircAccount()->setCurrentCommandSource(0); + } +} + +void IRCUserContact::whoWasComplete() +{ + if( isChatting() && ircAccount()->currentCommandSource() == manager() ) + { + //User info + QString msg = i18n("%1 was (%2@%3): %4\n") + .arg(m_nickName) + .arg(mInfo.userName) + .arg(mInfo.hostName) + .arg(mInfo.realName); + + msg += i18n("Last Online: %1\n").arg( + KGlobal::locale()->formatDateTime( + property( m_protocol->propLastSeen ).value().toDateTime() + ) + ); + + ircAccount()->appendMessage(msg, IRCAccount::InfoReply ); + ircAccount()->setCurrentCommandSource(0); + } +} + +QString IRCUserContact::formattedName() const +{ + return mInfo.realName; +} + +void IRCUserContact::updateInfo() +{ + setProperty( m_protocol->propUserInfo, QString::fromLatin1("%1@%2") + .arg(mInfo.userName).arg(mInfo.hostName) ); + setProperty( m_protocol->propServer, mInfo.serverName ); + setProperty( m_protocol->propChannels, mInfo.channels.join(" ") ); + setProperty( m_protocol->propHops, QString::number(mInfo.hops) ); + setProperty( m_protocol->propFullName, mInfo.realName ); + + setIdleTime( mInfo.idle ); + + mInfo.lastUpdate = QTime::currentTime(); +} + +void IRCUserContact::newWhoReply( const QString &channel, const QString &user, const QString &host, + const QString &server, bool away, const QString &flags, uint hops, const QString &realName ) +{ + if( !mInfo.channels.contains( channel ) ) + mInfo.channels.append( channel ); + + mInfo.userName = user; + mInfo.hostName = host; + mInfo.serverName = server; + mInfo.flags = flags; + mInfo.hops = hops; + mInfo.realName = realName; + + setAway(away); + + updateInfo(); + + if( isChatting() && ircAccount()->currentCommandSource() == manager() ) + { + ircAccount()->setCurrentCommandSource(0); + } +} + +QPtrList *IRCUserContact::customContextMenuActions( Kopete::ChatSession *manager ) +{ + if( manager ) + { + QPtrList *mCustomActions = new QPtrList (); + mActiveManager = manager; + Kopete::ContactPtrList members = mActiveManager->members(); + IRCChannelContact *isChannel = dynamic_cast( members.first() ); + + if( !actionCtcpMenu ) + { + actionCtcpMenu = new KActionMenu(i18n("C&TCP"), 0, this ); + actionCtcpMenu->insert( new KAction(i18n("&Version"), 0, this, + SLOT(slotCtcpVersion()), actionCtcpMenu) ); + actionCtcpMenu->insert( new KAction(i18n("&Ping"), 0, this, + SLOT(slotCtcpPing()), actionCtcpMenu) ); + + actionModeMenu = new KActionMenu(i18n("&Modes"), 0, this, "actionModeMenu"); + actionModeMenu->insert( new KAction(i18n("&Op"), 0, this, + SLOT(slotOp()), actionModeMenu, "actionOp") ); + actionModeMenu->insert( new KAction(i18n("&Deop"), 0, this, + SLOT(slotDeop()), actionModeMenu, "actionDeop") ); + actionModeMenu->insert( new KAction(i18n("&Voice"), 0, this, + SLOT(slotVoice()), actionModeMenu, "actionVoice") ); + actionModeMenu->insert( new KAction(i18n("Devoice"), 0, this, + SLOT(slotDevoice()), actionModeMenu, "actionDevoice") ); + actionModeMenu->setEnabled( false ); + + actionKick = new KAction(i18n("&Kick"), 0, this, SLOT(slotKick()), this); + actionKick->setEnabled( false ); + + actionBanMenu = new KActionMenu(i18n("&Ban"), 0, this, "actionBanMenu"); + actionBanMenu->insert( new KAction(i18n("Host (*!*@host.domain.net)"), 0, this, + SLOT(slotBanHost()), actionBanMenu ) ); + actionBanMenu->insert( new KAction(i18n("Domain (*!*@*.domain.net)"), 0, this, + SLOT(slotBanDomain()), actionBanMenu ) ); + actionBanMenu->insert( new KAction(i18n("User@Host (*!*user@host.domain.net)"), 0, this, + SLOT(slotBanUserHost()), actionBanMenu ) ); + actionBanMenu->insert( new KAction(i18n("User@Domain (*!*user@*.domain.net)"), 0, this, + SLOT(slotBanUserDomain()), actionBanMenu ) ); + actionBanMenu->setEnabled( false ); + + codecAction = new KCodecAction( i18n("&Encoding"), 0, this, "selectcharset" ); + connect( codecAction, SIGNAL( activated( const QTextCodec * ) ), + this, SLOT( setCodec( const QTextCodec *) ) ); + codecAction->setCodec( codec() ); + } + + mCustomActions->append( actionCtcpMenu ); + mCustomActions->append( actionModeMenu ); + mCustomActions->append( actionKick ); + mCustomActions->append( actionBanMenu ); + mCustomActions->append( codecAction ); + + if( isChannel ) + { + bool isOperator = ( manager->contactOnlineStatus( account()->myself() ).internalStatus() & IRCProtocol::Operator ); + actionModeMenu->setEnabled(isOperator); + actionBanMenu->setEnabled(isOperator); + actionKick->setEnabled(isOperator); + } + + return mCustomActions; + } + + mActiveManager = 0L; + + return 0L; +} + +void IRCUserContact::slotIncomingModeChange( const QString &channel, const QString &, const QString &mode_ ) +{ + kdDebug(14120) << k_funcinfo << "channel: " << channel << " mode: " << mode_ << endl; + + IRCChannelContact *chan = ircAccount()->contactManager()->findChannel( channel ); + + if( !chan->locateUser( m_nickName ) ) + return; + + // :foobar_!~fooobar@dhcp.inet.fi MODE #foofoofoo2 +o kakkonen + // :foobar_!~fooobar@dhcp.inet.fi MODE #foofoofoo2 +o-o foobar001 kakkonen + // :foobar_!~fooobar@dhcp.inet.fi MODE #foofoofoo2 +oo kakkonen foobar001 + // :foobar_!~fooobar@dhcp.inet.fi MODE #foofoofoo2 +o-ov foobar001 kakkonen foobar001 + // + // irssi manual example: /MODE #channel +nto-o+v nick1 nick2 nick3 + + QStringList users = QStringList::split(' ', mode_); + users.pop_front(); + + const QString mode = mode_.section(' ', 0, 0); + + bitAdjustment adjMode = RemoveBits; + QStringList::iterator user = users.begin(); + + //kdDebug(14120) << "me: " << m_nickName << " users: " << users << " mode: " << mode << endl; + + for( uint i=0; i < mode.length(); i++ ) + { + switch( mode[i] ) + { + case '+': + adjMode = AddBits; + break; + + case '-': + adjMode = RemoveBits; + break; + + default: + //kdDebug(14120) << "got " << mode[i] << ", user: " << *user << endl; + + if (mode[i] == 'o') { + if (user == users.end()) + return; + + if ((*user).lower() == m_nickName.lower()) + adjustInternalOnlineStatusBits(chan, IRCProtocol::Operator, adjMode); + + ++user; + } + else if (mode[i] == 'v') { + if (user == users.end()) + return; + + if ((*user).lower() == m_nickName.lower()) + adjustInternalOnlineStatusBits(chan, IRCProtocol::Voiced, adjMode); + + ++user; + } + + break; + } + } +} + + +/* Remove or add the given bits for the given channel from the current internal online status. + * + * You could fiddle with bits like IRCProtocol::Operator, IRCProtocol::Voiced, etc. + */ + +void IRCUserContact::adjustInternalOnlineStatusBits(IRCChannelContact *channel, unsigned statusAdjustment, bitAdjustment adj) +{ + Kopete::OnlineStatus currentStatus = channel->manager()->contactOnlineStatus(this); + Kopete::OnlineStatus newStatus; + + if (adj == RemoveBits) { + + // If the bit is not set in the current internal status, stop here. + if ((currentStatus.internalStatus() & ~statusAdjustment) == currentStatus.internalStatus()) + return; + + newStatus = m_protocol->statusLookup( + (IRCProtocol::IRCStatus)(currentStatus.internalStatus() & ~statusAdjustment) + ); + + } else if (adj == AddBits) { + + // If the bit is already set in the current internal status, stop here. + if ((currentStatus.internalStatus() | statusAdjustment) == currentStatus.internalStatus()) + return; + + newStatus = m_protocol->statusLookup( + (IRCProtocol::IRCStatus)(currentStatus.internalStatus() | statusAdjustment) + ); + + } + + channel->manager()->setContactOnlineStatus(this, newStatus); +} + +void IRCUserContact::privateMessage(IRCContact *from, IRCContact *to, const QString &message) +{ + if (to == this) + { + if(to==account()->myself()) + { + Kopete::Message msg(from, from->manager(Kopete::Contact::CanCreate)->members(), message, + Kopete::Message::Inbound, Kopete::Message::RichText, CHAT_VIEW); + from->appendMessage(msg); + } + else + { + kdDebug(14120) << "IRC Server error: Received a private message for " << to->nickName() << ":" << message << endl; + // emit/call something on main ircservercontact + } + } +} + +void IRCUserContact::newAction(const QString &to, const QString &action) +{ + IRCAccount *account = ircAccount(); + + IRCContact *t = account->contactManager()->findUser(to); + + Kopete::Message::MessageDirection dir = + (this == account->mySelf()) ? Kopete::Message::Outbound : Kopete::Message::Inbound; + Kopete::Message msg(this, t, action, dir, Kopete::Message::RichText, + CHAT_VIEW, Kopete::Message::TypeAction); + + //Either this is from me to a guy, or from a guy to me. Either way its a PM + if (dir == Kopete::Message::Outbound) + t->appendMessage(msg); + else + appendMessage(msg); +} + +#include "ircusercontact.moc" diff --git a/kopete/protocols/irc/ircusercontact.h b/kopete/protocols/irc/ircusercontact.h new file mode 100644 index 00000000..3373fa9c --- /dev/null +++ b/kopete/protocols/irc/ircusercontact.h @@ -0,0 +1,146 @@ +/* + ircusercontact.h - IRC User Contact + + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2003 by Jason Keirstead + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef IRCUSERCONTACT_H +#define IRCUSERCONTACT_H + +#include "kopetechatsessionmanager.h" +#include "irccontact.h" +#include "kopeteonlinestatus.h" + +class QTimer; + +class KActionCollection; +class KAction; +class KActionMenu; +class KCodecAction; + +class IRCContactManager; +class IRCChannelContact; + +struct IRCUserInfo +{ + QString userName; + QString hostName; + QString realName; + QString serverName; + QString serverInfo; + QString flags; + QStringList channels; + unsigned long idle; + bool isOperator; + bool isIdentified; + bool away; + bool online; + uint hops; + QDateTime lastOnline; + QTime lastUpdate; +}; + +/** + * @author Jason Keirstead *customContextMenuActions( Kopete::ChatSession *manager ); + virtual const QString caption() const; + + void setAway(bool isAway); + + QString formattedName() const; + + //Methods handled by the signal mapper + void incomingUserIsAway(const QString &message ); + void userOnline(); + void newAction( const QString &from, const QString &action ); + void newWhoIsUser(const QString &username, const QString &hostname, const QString &realname); + void newWhoIsServer(const QString &server, const QString &serverInfo); + void newWhoIsOperator(); + void newWhoIsIdentified(); + void newWhoIsIdle(unsigned long seconds); + void newWhoIsChannels(const QString &channel); + void whoIsComplete(); + void whoWasComplete(); + void newWhoReply( const QString &channel, const QString &user, const QString &host, + const QString &server, bool away, const QString &flags, uint hops, + const QString &realName ); + +public slots: + /** \brief Updates online status for channels based on current internal status. + */ + virtual void updateStatus(); + + virtual void sendFile(const KURL &sourceURL, const QString&, unsigned int); + +protected slots: + virtual void privateMessage(IRCContact *from, IRCContact *to, const QString &message); + +private slots: + void slotOp(); + void slotDeop(); + void slotVoice(); + void slotDevoice(); + void slotCtcpPing(); + void slotCtcpVersion(); + void slotBanHost(); + void slotBanUserHost(); + void slotBanDomain(); + void slotBanUserDomain(); + void slotKick(); + void slotUserOffline(); + + void slotBanHostOnce(); + void slotBanUserHostOnce(); + void slotBanDomainOnce(); + void slotBanUserDomainOnce(); + + virtual void slotUserInfo(); + + //This can't be handled by the contact manager since + void slotIncomingModeChange(const QString &nick, const QString &channel, const QString &mode); + +private: + enum bitAdjustment { RemoveBits, AddBits }; + void adjustInternalOnlineStatusBits(IRCChannelContact *channel, unsigned statusAdjustment, bitAdjustment adj); + + void contactMode(const QString &mode); + void updateInfo(); + + KActionMenu *actionModeMenu; + KActionMenu *actionCtcpMenu; + KAction *actionKick; + KActionMenu *actionBanMenu; + KCodecAction *codecAction; + Kopete::ChatSession *mActiveManager; + QTimer *mOnlineTimer; + IRCUserInfo mInfo; +}; + +#endif + +// vim: set noet ts=4 sts=4 tw=4: diff --git a/kopete/protocols/irc/kcodecaction.cpp b/kopete/protocols/irc/kcodecaction.cpp new file mode 100644 index 00000000..e32a1787 --- /dev/null +++ b/kopete/protocols/irc/kcodecaction.cpp @@ -0,0 +1,87 @@ +/* + kcodecaction.cpp + + Copyright (c) 2005 by Tommi Rantala + Copyright (c) 2003 by Jason Keirstead + Kopete (c) 2003-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#include +#include +#include + +#include "kcodecaction.h" + +KCodecAction::KCodecAction( const QString &text, const KShortcut &cut, + QObject *parent, const char *name ) : KSelectAction( text, "", cut, parent, name ) +{ + QObject::connect( this, SIGNAL( activated( const QString & ) ), + this, SLOT( slotActivated( const QString & ) ) ); + + setItems( KCodecAction::supportedEncodings() ); +} + +void KCodecAction::slotActivated( const QString & text ) +{ + /* text is something like "Western European ( iso-8859-1 )", but we must give + * codecForName() only the "iso-8859-1" part. + */ + QString encoding = KGlobal::charsets()->encodingForName(text); + + emit activated( KGlobal::charsets()->codecForName(encoding) ); +} + +void KCodecAction::setCodec( const QTextCodec *codec ) +{ + QStringList items = this->items(); + int i = 0; + for (QStringList::ConstIterator it = items.begin(), end = items.end(); it != end; ++it, ++i) { + QString encoding = KGlobal::charsets()->encodingForName(*it); + + if (KGlobal::charsets()->codecForName(encoding)->mibEnum() == codec->mibEnum()) { + setCurrentItem(i); + break; + } + } +} + +/* Create a list of supported encodings, and keep only one of each encoding + * mime name. + * + * This piece of code from kdepim/kmail/kmmsgbase.cpp + */ + +QStringList KCodecAction::supportedEncodings(bool usAscii) +{ + QStringList encodingNames = KGlobal::charsets()->availableEncodingNames(); + QStringList encodings; + QMap mimeNames; + + for (QStringList::ConstIterator it = encodingNames.begin(); + it != encodingNames.end(); ++it) + { + QTextCodec *codec = KGlobal::charsets()->codecForName(*it); + QString mimeName = (codec) ? QString(codec->mimeName()).lower() : (*it); + if (mimeNames.find(mimeName) == mimeNames.end()) + { + encodings.append(KGlobal::charsets()->languageForEncoding(*it) + + " ( " + mimeName + " )"); + mimeNames.insert(mimeName, true); + } + } + + encodings.sort(); + if (usAscii) encodings.prepend(KGlobal::charsets() + ->languageForEncoding("us-ascii") + " ( us-ascii )"); + return encodings; +} + +#include "kcodecaction.moc" diff --git a/kopete/protocols/irc/kcodecaction.h b/kopete/protocols/irc/kcodecaction.h new file mode 100644 index 00000000..93f9d6c1 --- /dev/null +++ b/kopete/protocols/irc/kcodecaction.h @@ -0,0 +1,47 @@ +/* + kcodecaction.h + + Copyright (c) 2005 by Tommi Rantala + Copyright (c) 2003 by Jason Keirstead + Kopete (c) 2003-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef KCODECACTION_H +#define KCODECACTION_H + +#include +#include + +#if KDE_IS_VERSION( 3, 1, 90 ) + #include +#else + #include +#endif + +class KCodecAction : public KSelectAction +{ + Q_OBJECT + public: + KCodecAction( const QString &text, const KShortcut &cut = KShortcut(), + QObject *parent = 0, const char *name = 0 ); + + void setCodec( const QTextCodec *codec ); + + static QStringList supportedEncodings( bool usAscii = false ); + + signals: + void activated( const QTextCodec * ); + + private slots: + void slotActivated( const QString & ); +}; + +#endif diff --git a/kopete/protocols/irc/kopete_irc.desktop b/kopete/protocols/irc/kopete_irc.desktop new file mode 100644 index 00000000..6e3cf144 --- /dev/null +++ b/kopete/protocols/irc/kopete_irc.desktop @@ -0,0 +1,79 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=irc_protocol +ServiceTypes=Kopete/Protocol +X-KDE-Library=kopete_irc +X-Kopete-Messaging-Protocol=messaging/irc +X-KDE-PluginInfo-Author=Kopete Developers +X-KDE-PluginInfo-Email=kopete-devel@kde.org +X-KDE-PluginInfo-Name=kopete_irc +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Protocols +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=IRC +Name[bn]=আই-আর-সি +Name[hi]=आईआरसी +Name[ne]=आइआरसी +Comment=Protocol to connect to IRC +Comment[ar]=البروتوكل سيتصل بـ IRC +Comment[be]=Пратакол IRC +Comment[bg]=Протокол за връзка Ñ IRC +Comment[bn]=আই-আর-সিতে সংযোগ করতে পà§à¦°à§‹à¦Ÿà§‹à¦•à¦² +Comment[br]=Komenad kevreañ ouzh IRC +Comment[bs]=IRC protokol +Comment[ca]=Protocol per a connectar-se a l'IRC +Comment[cs]=Protokol k pÅ™ipojení k IRC +Comment[cy]=Protocol i gysylltu ag IRC +Comment[da]=Protokol til at forbinde til IRC +Comment[de]=Protokoll zur Verbindung mit IRC +Comment[el]=ΠÏωτόκολλο για σÏνδεση στο IRC +Comment[es]=Protocolo de conexión al IRC +Comment[et]=Protokoll ühendumiseks IRC-ga +Comment[eu]=IRC-ra konektatzeko protokoloa +Comment[fa]=قرار داد برای اتصال به IRC +Comment[fi]=Yhteyskäytänötö IRC-verkkoon kytkeytymiseen +Comment[fr]=Protocole pour se connecter sur IRC +Comment[ga]=Prótacal chun ceangal le IRC +Comment[gl]=Protocolo para conectar a IRC +Comment[he]=פרוטוקול התחברות ל- IRC +Comment[hi]=आईआरसी से जà¥à¤¡à¤¼à¤¨à¥‡ का पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¥‰à¤² +Comment[hr]=Protokol za povezivanje na IRC +Comment[hu]=Protokoll az IRC használatához +Comment[is]=Samskiptamáti til að tengjast IRC +Comment[it]=Protocollo per connessione a IRC +Comment[ja]=IRC ã«æŽ¥ç¶šã™ã‚‹ãƒ—ロトコル +Comment[ka]=IRC-თáƒáƒœ დáƒáƒ™áƒáƒ•áƒ¨áƒ˜áƒ áƒ”ბის áƒáƒ¥áƒ›áƒ˜ +Comment[kk]=IRC-ге қоÑылу протоколы +Comment[km]=ពិធីការ​​ភ្ជាប់​ទៅ IRC +Comment[lt]=Protokolas prisijungimui prie IRC +Comment[mk]=Протокол за поврзување на IRC +Comment[nb]=Protokoll for Ã¥ koble til IRC +Comment[nds]=Protokoll för't Tokoppeln na IRC +Comment[ne]=आइआरसी मा जडान गरà¥à¤¨à¥‡ पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¤² +Comment[nl]=Protocol voor Internet Relay Chat (IRC) +Comment[nn]=Protokoll for Ã¥ kopla til IRC +Comment[pl]=Protokół poÅ‚Ä…czenia z serwerem IRC +Comment[pt]=Protocolo para ligar ao IRC +Comment[pt_BR]=Protocolo de conexão ao IRC +Comment[ro]=Protocol de conectare la IRC +Comment[ru]=Протокол Ð´Ð»Ñ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ðº IRC +Comment[sk]=Protokol pre pripojenie k IRC +Comment[sl]=Protokol za povezavo na IRC +Comment[sr]=Протокол за повезивање на IRC +Comment[sr@Latn]=Protokol za povezivanje na IRC +Comment[sv]=Protokoll för att ansluta till IRC +Comment[ta]=IRC உடன௠இணைகà¯à®• விதிமà¯à®±à¯ˆ +Comment[tg]=Қарордоди пайваÑтшавӣ ба IRC +Comment[tr]=IRC'ye baÄŸlantı iletiÅŸim kuralı +Comment[uk]=Протокол Ð´Ð»Ñ Ð·'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· IRC +Comment[uz]=IRC bilan aloqa oÊ»rnatish uchun protokol +Comment[uz@cyrillic]=IRC билан алоқа ўрнатиш учун протокол +Comment[wa]=Protocole po s' raloyî so les canÃ¥s IRC +Comment[zh_CN]=连接到 IRC åè®® +Comment[zh_HK]=用來連接至 IRC 的通訊å”定 +Comment[zh_TW]=連線到 IRC çš„å”定 + diff --git a/kopete/protocols/irc/ksparser.cpp b/kopete/protocols/irc/ksparser.cpp new file mode 100644 index 00000000..c101a79e --- /dev/null +++ b/kopete/protocols/irc/ksparser.cpp @@ -0,0 +1,265 @@ +/* This file is part of ksirc + Copyright (c) 2001 Malte Starostik + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* +Color parser code courtesy of ksirc +Modified by Jason Keirstead +*/ + +#include +#include +#include +#include +#include +#include "ksparser.h" +#include + +KSParser KSParser::m_parser; + +const QColor KSParser::IRC_Colors[17]= +{ + Qt::white, + Qt::black, + Qt::darkBlue, + Qt::darkGreen, + Qt::red, + Qt::darkRed, + Qt::darkMagenta, + Qt::darkYellow, + Qt::yellow, + Qt::green, + Qt::darkCyan, + Qt::cyan, + Qt::blue, + Qt::magenta, + Qt::darkGray, + Qt::gray, + QColor() // default invalid color, must be the last +}; + +const QRegExp KSParser::sm_colorsModeRegexp("(\\d{1,2})(?:,(\\d{1,2}))?"); + +template + inline void swap(_TYPE_ &o1, _TYPE_ &o2) +{ + _TYPE_ tmp = o1; + o1 = o2; + o2 = tmp; +} + +KSParser::KSParser() +{ + kdDebug(14120) << k_funcinfo << endl; +} + +KSParser::~KSParser() +{ + kdDebug(14120) << k_funcinfo << endl; +} + +/* NOTE: If thread corruption are seen simply ad a qlock here */ +QCString KSParser::parse(const QCString &message) +{ + return m_parser._parse(message); +} + +QCString KSParser::_parse(const QCString &message) +{ + QCString data( message.size() * 2 ); + QBuffer buff( data ); + buff.open( IO_WriteOnly ); + + m_tags.clear(); + m_attributes.clear(); + + QRegExp colorsModeRegexp(sm_colorsModeRegexp); + + // should be set to the current default colors .... + QColor fgColor; /*KopeteMesage::fg().name()*/ + QColor bgColor; /*KopeteMesage::bg().name()*/ + + uint chars = 0; + for(uint i = 0; i < message.length(); ++i) + { + const QChar &cur = message[i]; + QString toAppend; + + switch (cur) + { + case 0x02: //Bold: ^B + toAppend= toggleTag("b"); + break; + case 0x03: //Color code: ^C + if (colorsModeRegexp.search(message, i+1) == (int)i+1) + { + i += colorsModeRegexp.matchedLength(); // + 1 will be added by ++ + QString tagStyle; + + fgColor = ircColor(colorsModeRegexp.cap(1)); + bgColor = ircColor(colorsModeRegexp.cap(2)); + + toAppend = pushColorTag(fgColor, bgColor); + } + else + { + toAppend = popTag(QString::fromLatin1("span")); + } + break; + case 0x07: //System bell: ^G + KNotifyClient::beep( QString::fromLatin1("IRC beep event received in a message") ); + break; + case '\t': // 0x09 + toAppend = QString::fromLatin1("    "); + break; + case '\n': // 0x0D + toAppend= QString::fromLatin1("
    "); + break; + case 0x0D: // Italics: ^N + toAppend = toggleTag("i"); + break; + case 0x0F: //Plain Text, close all tags: ^O + toAppend = popAll(); + break; + // case 0x12: // Reverse original text colors: ^R + // break; + case 0x16: //Invert Colors: ^V + swap(fgColor, bgColor); + toAppend = pushColorTag(fgColor, bgColor); + break; + case 0x1F: //Underline + toAppend = toggleTag("u"); + break; + case '<': + toAppend = QString::fromLatin1("<"); + break; + case '>': + toAppend = QString::fromLatin1(">"); + break; + default: + if (cur < QChar(' ')) // search for control characters + toAppend = QString::fromLatin1("<%1>").arg(cur, 2, 16).upper(); + else + toAppend = QStyleSheet::escape(cur); + } + + chars += toAppend.length(); + buff.writeBlock( toAppend.latin1(), toAppend.length() ); + } + + QString toAppend = popAll(); + chars += toAppend.length(); + buff.writeBlock( toAppend.latin1(), toAppend.length() ); + + // Make sure we have enough room for NULL character. + if (data.size() < chars+1) + data.resize(chars+1); + + data[chars] = '\0'; + + return data; +} + +QString KSParser::pushTag(const QString &tag, const QString &attributes) +{ + QString res; + m_tags.push(tag); + if(!m_attributes.contains(tag)) + m_attributes.insert(tag, attributes); + else if(!attributes.isEmpty()) + m_attributes.replace(tag, attributes); + res.append("<" + tag); + if(!m_attributes[tag].isEmpty()) + res.append(" " + m_attributes[tag]); + return res + ">"; +} + +QString KSParser::pushColorTag(const QColor &fgColor, const QColor &bgColor) +{ + QString tagStyle; + + if (fgColor.isValid()) + tagStyle += QString::fromLatin1("color:%1;").arg(fgColor.name()); + if (bgColor.isValid()) + tagStyle += QString::fromLatin1("background-color:%1;").arg(bgColor.name()); + + if(!tagStyle.isEmpty()) + tagStyle = QString::fromLatin1("style=\"%1\"").arg(tagStyle); + + return pushTag(QString::fromLatin1("span"), tagStyle);; +} + +QString KSParser::popTag(const QString &tag) +{ + if (!m_tags.contains(tag)) + return QString::null; + + QString res; + QValueStack savedTags; + while(m_tags.top() != tag) + { + savedTags.push(m_tags.pop()); + res.append(""); + } + res.append(""); + m_attributes.remove(tag); + while(!savedTags.isEmpty()) + res.append(pushTag(savedTags.pop())); + return res; +} + +QString KSParser::toggleTag(const QString &tag) +{ + return m_attributes.contains(tag)?popTag(tag):pushTag(tag); +} + +QString KSParser::popAll() +{ + QString res; + while(!m_tags.isEmpty()) + res.append(""); + m_attributes.clear(); + return res; +} + +QColor KSParser::ircColor(const QString &color) +{ + bool success; + unsigned int intColor = color.toUInt(&success); + + if (success) + return ircColor(intColor); + else + return QColor(); +} + +QColor KSParser::ircColor(unsigned int color) +{ + unsigned int maxcolor = sizeof(IRC_Colors)/sizeof(QColor); + return color<=maxcolor?IRC_Colors[color]:IRC_Colors[maxcolor]; +} + +int KSParser::colorForHTML(const QString &html) +{ + QColor color(html); + for(uint i=0; i + + This program is free software; you can redistribute it and/or + modify it under the terms of the Artistic License. +*/ +#ifndef __ksparser_h__ +#define __ksparser_h__ + +#include +#include +#include +#include +#include + +/* + * Helper class to parse IRC color/style codes and convert them to + * richtext. The parser maintains an internal stack of the styles + * applied because the IRC message could contain sequences as + * (bold)Hello (red)World(endbold)! (blue)blue text + * which needs to be converted to + * Hello World! blue text + * to get correctly nested tags. (malte) + */ +class KSParser +{ +public: + static QCString parse(const QCString &); + static int colorForHTML( const QString &html ); + + static QColor ircColor(const QString &color); + static QColor ircColor(unsigned int color); + + ~KSParser(); +private: + KSParser(); + + QCString _parse(const QCString &); + QString pushTag(const QString &, const QString & = QString::null); + QString pushColorTag(const QColor &fgColor, const QColor &bgColor); + QString popTag(const QString &); + QString toggleTag(const QString &); + QString popAll(); + +private: + static KSParser m_parser; + static const QColor IRC_Colors[17]; + static const QRegExp sm_colorsModeRegexp; + + QValueStack m_tags; + QMap m_attributes; +}; + +#endif + + diff --git a/kopete/protocols/irc/libkirc/Makefile.am b/kopete/protocols/irc/libkirc/Makefile.am new file mode 100644 index 00000000..e2ebe543 --- /dev/null +++ b/kopete/protocols/irc/libkirc/Makefile.am @@ -0,0 +1,20 @@ +KDE_OPTIONS = nofinal +noinst_LTLIBRARIES = libkirc.la + +libkirc_la_SOURCES = \ + kircengine.cpp \ + kircengine_commands.cpp \ + kircengine_ctcp.cpp \ + kircengine_numericreplies.cpp \ + kircentity.cpp \ + kircmessage.cpp \ + kircmessageredirector.cpp \ + kirctransfer.cpp \ + kirctransferhandler.cpp \ + kirctransferserver.cpp \ + ksslsocket.cpp +libkirc_la_LDFLAGS = -no-undefined $(KDE_PLUGIN) $(all_libraries) +libkirc_la_LIBADD = $(LIB_KIO) + +AM_CPPFLAGS = -I$(top_srcdir)/kopete/protocols/irc $(KOPETE_INCLUDES) $(all_includes) +METASOURCES = AUTO diff --git a/kopete/protocols/irc/libkirc/kircengine.cpp b/kopete/protocols/irc/libkirc/kircengine.cpp new file mode 100644 index 00000000..5b70d5fc --- /dev/null +++ b/kopete/protocols/irc/libkirc/kircengine.cpp @@ -0,0 +1,497 @@ +/* + kirc.cpp - IRC Client + + Copyright (c) 2005 by Tommi Rantala + Copyright (c) 2003-2004 by Michel Hermier + Copyright (c) 2002 by Nick Betcher + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "kircengine.h" +#include "ksslsocket.h" + +#include +#include +#include +#include +#include + +#include +#include + +//Needed for getuid / getpwuid +#include +#include +#include + +#include + +#ifndef KIRC_SSL_SUPPORT +#define KIRC_SSL_SUPPORT +#endif + +using namespace KIRC; + +// FIXME: Remove slotConnected() and error(int errCode) while going to KNetwork namespace + +/* Please note that the regular expression "[\\r\\n]*$" is used in a QString::replace statement many times. + * This gets rid of trailing \r\n, \r, \n, and \n\r characters. + */ +const QRegExp Engine::m_RemoveLinefeeds( QString::fromLatin1("[\\r\\n]*$") ); + +Engine::Engine(QObject *parent, const char *name) + : QObject(parent, QString::fromLatin1("[KIRC::Engine]%1").arg(name).latin1()), + m_status(Idle), + m_FailedNickOnLogin(false), + m_useSSL(false), + m_commands(101, false), +// m_numericCommands(101), + m_ctcpQueries(17, false), + m_ctcpReplies(17, false), + codecs(577,false) +{ + setUserName(QString::null); + + m_commands.setAutoDelete(true); + m_ctcpQueries.setAutoDelete(true); + m_ctcpReplies.setAutoDelete(true); + + bindCommands(); + bindNumericReplies(); + bindCtcp(); + + m_VersionString = QString::fromLatin1("Anonymous client using the KIRC engine."); + m_UserString = QString::fromLatin1("Response not supplied by user."); + m_SourceString = QString::fromLatin1("Unknown client, known source."); + + defaultCodec = QTextCodec::codecForMib(106); // UTF8 mib is 106 + kdDebug(14120) << "Setting default engine codec, " << defaultCodec->name() << endl; + + m_sock = 0L; +} + +Engine::~Engine() +{ + kdDebug(14120) << k_funcinfo << m_Host << endl; + quit("KIRC Deleted", true); + if( m_sock ) + delete m_sock; +} + +void Engine::setUseSSL( bool useSSL ) +{ + kdDebug(14120) << k_funcinfo << useSSL << endl; + + if( !m_sock || useSSL != m_useSSL ) + { + if( m_sock ) + delete m_sock; + + m_useSSL = useSSL; + + + if( m_useSSL ) + { + #ifdef KIRC_SSL_SUPPORT + m_sock = new KSSLSocket; + m_sock->setSocketFlags( KExtendedSocket::inetSocket ); + + connect(m_sock, SIGNAL(certificateAccepted()), SLOT(slotConnected())); + connect(m_sock, SIGNAL(certificateRejected()), SLOT(slotConnectionClosed())); + connect(m_sock, SIGNAL(sslFailure()), SLOT(slotConnectionClosed())); + } + else + #else + kdWarning(14120) << "You tried to use SSL, but this version of Kopete was" + " not compiled with IRC SSL support. A normal IRC connection will be attempted." << endl; + } + #endif + { + m_sock = new KExtendedSocket; + m_sock->setSocketFlags( KExtendedSocket::inputBufferedSocket | KExtendedSocket::inetSocket ); + + connect(m_sock, SIGNAL(connectionSuccess()), SLOT(slotConnected())); + connect(m_sock, SIGNAL(connectionFailed(int)), SLOT(error(int))); + } + + connect(m_sock, SIGNAL(closed(int)), SLOT(slotConnectionClosed())); + connect(m_sock, SIGNAL(readyRead()), SLOT(slotReadyRead())); + } +} + +void Engine::setStatus(Engine::Status status) +{ + kdDebug(14120) << k_funcinfo << status << endl; + + if (m_status == status) + return; + +// Engine::Status oldStatus = m_status; + m_status = status; + emit statusChanged(status); + + switch (m_status) + { + case Idle: + // Do nothing. + break; + case Connecting: + // Do nothing. + break; + case Authentifying: + m_sock->enableRead(true); + + // If password is given for this server, send it now, and don't expect a reply + if (!(password()).isEmpty()) + pass(password()); + + user(m_Username, 0, m_realName); + nick(m_Nickname); + + break; + case Connected: + // Do nothing. + break; + case Closing: + m_sock->close(); + m_sock->reset(); + setStatus(Idle); + break; + case AuthentifyingFailed: + setStatus(Closing); + break; + case Timeout: + setStatus(Closing); + break; + case Disconnected: + setStatus(Closing); + break; + } +} + +void Engine::connectToServer(const QString &host, Q_UINT16 port, const QString &nickname, bool useSSL ) +{ + setUseSSL(useSSL); + + m_Nickname = nickname; + m_Host = host; + m_Port = port; + + kdDebug(14120) << "Trying to connect to server " << m_Host << ":" << m_Port << endl; + kdDebug(14120) << "Sock status: " << m_sock->socketStatus() << endl; + + if( !m_sock->setAddress(m_Host, m_Port) ) + kdDebug(14120) << k_funcinfo << "setAddress failed. Status: " << m_sock->socketStatus() << endl; + + if( m_sock->startAsyncConnect() == 0 ) + { + kdDebug(14120) << k_funcinfo << "Success!. Status: " << m_sock->socketStatus() << endl; + setStatus(Connecting); + } + else + { + kdDebug(14120) << k_funcinfo << "Failed. Status: " << m_sock->socketStatus() << endl; + setStatus(Disconnected); + } +} + +void Engine::slotConnected() +{ + setStatus(Authentifying); +} + +void Engine::slotConnectionClosed() +{ + setStatus(Disconnected); +} + +void Engine::error(int errCode) +{ + kdDebug(14120) << k_funcinfo << "Socket error: " << errCode << endl; + if (m_sock->socketStatus () != KExtendedSocket::connecting) + { + // Connection in progress.. This is a signal fired wrong + setStatus(Disconnected); + } +} + +void Engine::setVersionString(const QString &newString) +{ + m_VersionString = newString; + m_VersionString.remove(m_RemoveLinefeeds); +} + +void Engine::setUserString(const QString &newString) +{ + m_UserString = newString; + m_UserString.remove(m_RemoveLinefeeds); +} + +void Engine::setSourceString(const QString &newString) +{ + m_SourceString = newString; + m_SourceString.remove(m_RemoveLinefeeds); +} + +void Engine::setUserName(const QString &newName) +{ + if(newName.isEmpty()) + m_Username = QString::fromLatin1(getpwuid(getuid())->pw_name); + else + m_Username = newName; + m_Username.remove(m_RemoveLinefeeds); +} + +void Engine::setRealName(const QString &newName) +{ + if(newName.isEmpty()) + m_realName = QString::fromLatin1(getpwuid(getuid())->pw_gecos); + else + m_realName = newName; + m_realName.remove(m_RemoveLinefeeds); +} + +bool Engine::_bind(QDict &dict, + QString command, QObject *object, const char *member, + int minArgs, int maxArgs, const QString &helpMessage) +{ +// FIXME: Force upper case. +// FIXME: Force number format. + + MessageRedirector *mr = dict[command]; + + if (!mr) + { + mr = new MessageRedirector(this, minArgs, maxArgs, helpMessage); + dict.replace(command, mr); + } + + return mr->connect(object, member); +} + +bool Engine::bind(const QString &command, QObject *object, const char *member, + int minArgs, int maxArgs, const QString &helpMessage) +{ + return _bind(m_commands, command, object, member, + minArgs, maxArgs, helpMessage); +} + +bool Engine::bind(int id, QObject *object, const char *member, + int minArgs, int maxArgs, const QString &helpMessage) +{ + return _bind(m_commands, QString::number(id), object, member, + minArgs, maxArgs, helpMessage); +} + +bool Engine::bindCtcpQuery(const QString &command, QObject *object, const char *member, + int minArgs, int maxArgs, const QString &helpMessage) +{ + return _bind(m_ctcpQueries, command, object, member, + minArgs, maxArgs, helpMessage); +} + +bool Engine::bindCtcpReply(const QString &command, QObject *object, const char *member, + int minArgs, int maxArgs, const QString &helpMessage) +{ + return _bind(m_ctcpReplies, command, object, member, + minArgs, maxArgs, helpMessage); +} + +/* Message will be send as passed. + */ +void Engine::writeRawMessage(const QString &rawMsg) +{ + Message::writeRawMessage(this, defaultCodec, rawMsg); +} + +/* Message will be quoted before beeing send. + */ +void Engine::writeMessage(const QString &msg, const QTextCodec *codec) +{ + Message::writeMessage(this, codec ? codec : defaultCodec, msg); +} + +void Engine::writeMessage(const QString &command, const QStringList &args, const QString &suffix, const QTextCodec *codec) +{ + Message::writeMessage(this, codec ? codec : defaultCodec, command, args, suffix ); +} + +void Engine::writeCtcpMessage(const QString &command, const QString &to, const QString &ctcpMessage) +{ + Message::writeCtcpMessage(this, defaultCodec, command, to, ctcpMessage); +} + +void Engine::writeCtcpMessage(const QString &command, const QString &to, const QString &suffix, + const QString &ctcpCommand, const QStringList &ctcpArgs, const QString &ctcpSuffix, bool ) +{ + QString nick = Entity::userNick(to); + + Message::writeCtcpMessage(this, codecForNick( nick ), command, nick, suffix, + ctcpCommand, ctcpArgs, ctcpSuffix ); +} + +void Engine::slotReadyRead() +{ + // This condition is buggy when the peer server + // close the socket unexpectedly + bool parseSuccess; + + if (m_sock->socketStatus() == KExtendedSocket::connected && m_sock->canReadLine()) + { + Message msg = Message::parse(this, defaultCodec, &parseSuccess); + if (parseSuccess) + { + emit receivedMessage(msg); + + KIRC::MessageRedirector *mr; + if (msg.isNumeric()) +// mr = m_numericCommands[ msg.command().toInt() ]; + // we do this conversion because some dummy servers sends 1 instead of 001 + // numbers are stored as "1" instead of "001" to make convertion faster (no 0 pading). + mr = m_commands[ QString::number(msg.command().toInt()) ]; + else + mr = m_commands[ msg.command() ]; + + if (mr) + { + QStringList errors = mr->operator()(msg); + + if (!errors.isEmpty()) + { + kdDebug(14120) << "Method error for line:" << msg.raw() << endl; + emit internalError(MethodFailed, msg); + } + } + else if (msg.isNumeric()) + { + kdWarning(14120) << "Unknown IRC numeric reply for line:" << msg.raw() << endl; + emit incomingUnknown(msg.raw()); + } + else + { + kdWarning(14120) << "Unknown IRC command for line:" << msg.raw() << endl; + emit internalError(UnknownCommand, msg); + } + } + else + { + emit incomingUnknown(msg.raw()); + emit internalError(ParsingFailed, msg); + } + + QTimer::singleShot( 0, this, SLOT( slotReadyRead() ) ); + } + + if(m_sock->socketStatus() != KExtendedSocket::connected) + error(); +} + +const QTextCodec *Engine::codecForNick( const QString &nick ) const +{ + if( nick.isEmpty() ) + return defaultCodec; + + QTextCodec *codec = codecs[ nick ]; + kdDebug(14120) << nick << " has codec " << codec << endl; + + if( !codec ) + return defaultCodec; + else + return codec; +} + +void Engine::showInfoDialog() +{ + if( m_useSSL ) + { + static_cast( m_sock )->showInfoDialog(); + } +} + +/* + * The ctcp commands seems to follow the same message behaviours has normal IRC command. + * (Only missing the \n\r final characters) + * So applying the same parsing rules to the messages. + */ +bool Engine::invokeCtcpCommandOfMessage(const QDict &map, Message &msg) +{ + if(msg.hasCtcpMessage() && msg.ctcpMessage().isValid()) + { + Message &ctcpMsg = msg.ctcpMessage(); + + MessageRedirector *mr = map[ctcpMsg.command()]; + if (mr) + { + QStringList errors = mr->operator()(msg); + + if (errors.isEmpty()) + return true; + + kdDebug(14120) << "Method error for line:" << ctcpMsg.raw() << endl; + writeCtcpErrorMessage(msg.prefix(), msg.ctcpRaw(), + QString::fromLatin1("%1 internal error(s)").arg(errors.size())); + } + else + { + kdDebug(14120) << "Unknow IRC/CTCP command for line:" << ctcpMsg.raw() << endl; + // Don't send error message on unknown CTCP command + // None of the client send it, and it makes the client as infected by virus for IRC network scanners + // writeCtcpErrorMessage(msg.prefix(), msg.ctcpRaw(), "Unknown CTCP command"); + + emit incomingUnknownCtcp(msg.ctcpRaw()); + } + } + else + { + kdDebug(14120) << "Message do not embed a CTCP message:" << msg.raw(); + } + return false; +} + +EntityPtr Engine::getEntity(const QString &name) +{ + Entity *entity = 0; + + #pragma warning Do the searching code here. + + if (!entity) + { + entity = new Entity(name); + m_entities.append(entity); + } + + connect(entity, SIGNAL(destroyed(KIRC::Entity *)), SLOT(destroyed(KIRC::Entity *))); + return EntityPtr(entity); +} + +void Engine::destroyed(KIRC::Entity *entity) +{ + m_entities.remove(entity); +} + +void Engine::ignoreMessage(KIRC::Message &/*msg*/) +{ +} + +void Engine::emitSuffix(KIRC::Message &msg) +{ + emit receivedMessage(InfoMessage, m_server, m_server, msg.suffix()); +} + +#include "kircengine.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/protocols/irc/libkirc/kircengine.h b/kopete/protocols/irc/libkirc/kircengine.h new file mode 100644 index 00000000..50cb8f49 --- /dev/null +++ b/kopete/protocols/irc/libkirc/kircengine.h @@ -0,0 +1,532 @@ +/* + kircengine.h - IRC Client + + Copyright (c) 2003-2004 by Michel Hermier + Copyright (c) 2003 by Jason Keirstead + Copyright (c) 2002 by Nick Betcher + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KIRCENGINE_H +#define KIRCENGINE_H + +#include "kircentity.h" +#include "kircmessage.h" +#include "kircmessageredirector.h" +#include "kirctransfer.h" + +#include + +// FIXME: Move the following kdedebug class to the *.cpp. +#include +#if KDE_VERSION < KDE_MAKE_VERSION( 3, 1, 90 ) +#include +#endif + +#include +#include +#include +#include +#include +#include + +class QRegExp; + +namespace KIRC +{ + +/** + * @author Nick Betcher + * @author Michel Hermier + * @author Jason Keirstead + */ +class Engine + : public QObject +{ + Q_OBJECT + +// Q_PROPERTY(QUrl serverURL READ serverURL WRITE setServerURL) + +// Extracted from the base of the serverURL. +// Q_PROPERTY(bool useSSL); +// Q_PROPERTY(QString user READ user); +// Q_PROPERTY(QString password); +// Q_PROPERTY(QString host READ host); +// Q_PROPERTY(int port READ host); + +// Extracted from the query of the serverURL. +// Q_PROPERTY(bool reqsPasswd); +// Q_PROPERTY(QString name); // real name +// Q_PROPERTY(QStringList nickList READ nickList WRITE setNickList) +// Q_PROPERTY(QString nick READ nick) +// Q_PROPERTY(QStringList portList) + + Q_ENUMS(Status) + +public: + enum Error + { + ParsingFailed, + UnknownCommand, + UnknownNumericReply, + InvalidNumberOfArguments, + MethodFailed + }; + + enum Status + { + Idle, + Connecting, + Authentifying, + Connected, + Closing, + AuthentifyingFailed, + Timeout, + Disconnected + }; + + enum ServerMessageType + { + ErrorMessage = -1, + PrivateMessage, + InfoMessage, + + MessageOfTheDayMessage, + MessageOfTheDayCondensedMessage + }; + + Engine( QObject *parent = 0, const char* name = 0 ); + ~Engine(); + +// QString nick() const; +// QStringList nickList() const; +// void setNickList(const QStringList& nickList); + +// QUrl serverURL() const; +// bool setServerURL(const QUrl &url); + + inline const QString ¤tHost() const + { return m_Host; }; + + inline Q_UINT16 currentPort() + { return m_Port; } + + inline const QString &nickName() const + { return m_Nickname; }; + + inline const QString &password() const + { return m_Passwd; } + + inline void setPassword(const QString &passwd) + { m_Passwd = passwd; }; + + inline const QString &userName() const + { return m_Username; } + + void setUserName(const QString &newName); + + void setRealName(const QString &newName); + inline const QString &realName() const + { return m_realName; } + + inline const bool reqsPassword() const + { return m_ReqsPasswd; }; + + inline void setReqsPassword(bool b) + { m_ReqsPasswd = b; }; + + const bool useSSL() const { return m_useSSL; }; + void setUseSSL( bool useSSL ); + + inline const QTextCodec *codec() const + { return defaultCodec; }; + + const QTextCodec *codecForNick( const QString &nick ) const; + + inline void setDefaultCodec( QTextCodec* codec ) + { defaultCodec = codec; }; + + void setVersionString(const QString &versionString); + void setUserString(const QString &userString); + void setSourceString(const QString &sourceString); + void connectToServer(const QString &host, Q_UINT16 port, const QString &nickname, bool useSSL = false); + + KExtendedSocket *socket() + { return m_sock; }; + + inline KIRC::Engine::Status status() const + { return m_status; } + + inline bool isDisconnected() const + { return m_status == Disconnected || m_status == Idle; } + + inline bool isConnected() const + { return m_status == Connected; } + + inline void setCodec( const QString &nick, const QTextCodec *codec ) + { codecs.replace( nick, codec ); } + + /* Custom CTCP replies handling */ + inline QString &customCtcp( const QString &s ) + { return customCtcpMap[s]; } + + inline void addCustomCtcp( const QString &ctcp, const QString &reply ) + { customCtcpMap[ ctcp.lower() ] = reply; } + + KIRC::EntityPtr getEntity(const QString &name); + +public slots: + //Message output + void writeRawMessage(const QString &message); + + void writeMessage(const QString &message, const QTextCodec *codec = 0 ); + void writeMessage(const QString &command, const QStringList &args, + const QString &suffix = QString::null, const QTextCodec *codec = 0); + + void writeCtcpMessage(const QString &command, const QString &to, const QString &ctcpMessage); + + void writeCtcpMessage(const QString &command, const QString &to, const QString &suffix, + const QString &ctcpCommand, const QStringList &ctcpArgs, const QString &ctcpSuffix = QString::null, + bool emitRepliedCtcp = true); + + inline void writeCtcpQueryMessage(const QString &to, const QString &suffix, + const QString &ctcpCommand, const QStringList &ctcpArgs = QStringList(), const QString &ctcpSuffix = QString::null, + bool emitRepliedCtcp = true) + { return writeCtcpMessage("PRIVMSG", to, suffix, ctcpCommand, ctcpArgs, ctcpSuffix, emitRepliedCtcp); } + + inline void writeCtcpReplyMessage(const QString &to, const QString &ctcpMessage) + { writeCtcpMessage("NOTICE", to, ctcpMessage); } + + inline void writeCtcpReplyMessage(const QString &to, const QString &suffix, + const QString &ctcpCommand, const QStringList &ctcpArgs = QStringList(), const QString &ctcpSuffix = QString::null, + bool emitRepliedCtcp = true) + { return writeCtcpMessage("NOTICE", to, suffix, ctcpCommand, ctcpArgs, ctcpSuffix, emitRepliedCtcp); } + + inline void writeCtcpErrorMessage(const QString &to, const QString &ctcpLine, const QString &errorMsg, + bool emitRepliedCtcp=true) + { return writeCtcpReplyMessage(to, QString::null, "ERRMSG", ctcpLine, errorMsg, emitRepliedCtcp); } + + bool bind(const QString &command, QObject *object, const char *member, + int minArgs = KIRC::MessageRedirector::Unknown, + int maxArgs = KIRC::MessageRedirector::Unknown, + const QString &helpMessage = QString::null); + + bool bind(int id, QObject *object, const char *member, + int minArgs = KIRC::MessageRedirector::Unknown, + int maxArgs = KIRC::MessageRedirector::Unknown, + const QString &helpMessage = QString::null); + + bool bindCtcpQuery(const QString &command, QObject *object, const char *member, + int minArgs = KIRC::MessageRedirector::Unknown, + int maxArgs = KIRC::MessageRedirector::Unknown, + const QString &helpMessage = QString::null); + + bool bindCtcpReply(const QString &command, QObject *object, const char *member, + int minArgs = KIRC::MessageRedirector::Unknown, + int maxArgs = KIRC::MessageRedirector::Unknown, + const QString &helpMessage = QString::null); + + + void away(bool isAway, const QString &awayMessage = QString::null); + void ison(const QStringList &nickList); + void join(const QString &name, const QString &key); + void kick(const QString &user, const QString &channel, const QString &reason); + void list(); + void mode(const QString &target, const QString &mode); + void motd(const QString &server = QString::null); + void nick(const QString &newNickname); + void notice(const QString &target, const QString &message); + void part(const QString &name, const QString &reason); + void pass(const QString &password); + void privmsg(const QString &contact, const QString &message); + + /** + * Send a quit message for the given reason. + * If now is set to true the connection is closed and no event message is sent. + * Therefore setting now to true should only be used while destroying the object. + */ + void quit(const QString &reason, bool now=false); + + void topic(const QString &channel, const QString &topic); + void user(const QString &newUsername, const QString &hostname, const QString &newRealname); + void user(const QString &newUsername, Q_UINT8 mode, const QString &newRealname); + void whois(const QString &user); + + + /* CTCP commands */ + void CtcpRequestCommand(const QString &contact, const QString &command); + void CtcpRequest_action(const QString &contact, const QString &message); + void CtcpRequest_dcc(const QString &, const QString &, unsigned int port, KIRC::Transfer::Type type); + void CtcpRequest_ping(const QString &target); + void CtcpRequest_version(const QString &target); + +public slots: + void showInfoDialog(); + +signals: + void statusChanged(KIRC::Engine::Status newStatus); + void internalError(KIRC::Engine::Error, KIRC::Message &); + + void receivedMessage(KIRC::Message &); + + /** + * Emit a received message. + * The received message could have been translated to your locale. + * + * @param type the message type. + * @param from the originator of the message. + * @param to is the list of entities that are related to this message. + * @param msg the message (usually translated). + * + * @note Most of the following numeric messages should be deprecated, and call this method instead. + * Most of the methods, using it, update KIRC::Entities. + * Lists based messages are sent via dedicated API, therefore they don't use this. + */ + // @param args the args to apply to this message. + void receivedMessage( KIRC::Engine::ServerMessageType type, + const KIRC::EntityPtr &from, + const KIRC::EntityPtrList &to, + const QString &msg); + + void successfullyChangedNick(const QString &, const QString &); + + //ServerContact Signals + void incomingMotd(const QString &motd); + void incomingNotice(const QString &originating, const QString &message); + void incomingHostInfo(const QString &servername, const QString &version, + const QString &userModes, const QString &channelModes); + void incomingYourHostInfo(const QString &servername, const QString &version, + const QString &userModes, const QString &channelModes); + void incomingConnectString(const QString &clients); + + //Channel Contact Signals + void incomingMessage(const QString &originating, const QString &target, const QString &message); + void incomingTopicChange(const QString &, const QString &, const QString &); + void incomingExistingTopic(const QString &, const QString &); + void incomingTopicUser(const QString &channel, const QString &user, const QDateTime &time); + void incomingJoinedChannel(const QString &channel,const QString &nick); + void incomingPartedChannel(const QString &channel,const QString &nick, const QString &reason); + void incomingNamesList(const QString &channel, const QStringList &nicknames); + void incomingEndOfNames(const QString &channel); + void incomingChannelMode(const QString &channel, const QString &mode, const QString ¶ms); + void incomingCannotSendToChannel(const QString &channel, const QString &message); + void incomingChannelModeChange(const QString &channel, const QString &nick, const QString &mode); + void incomingChannelHomePage(const QString &channel, const QString &url); + + //Contact Signals + void incomingPrivMessage(const QString &, const QString &, const QString &); + void incomingQuitIRC(const QString &user, const QString &reason); + void incomingUserModeChange(const QString &nick, const QString &mode); + void incomingNoSuchNickname(const QString &nick); + + // CTCP Signals +// void action(const QString &from, const QString &to, const QString &message); + void incomingAction(const QString &channel, const QString &originating, const QString &message); + void incomingPrivAction(const QString &target, const QString &originating, const QString &message); + + //Response Signals + void incomingUserOnline(const QString &nick); + void incomingWhoIsUser(const QString &nickname, const QString &username, + const QString &hostname, const QString &realname); + void incomingWhoWasUser(const QString &nickname, const QString &username, + const QString &hostname, const QString &realname); + void incomingWhoIsServer(const QString &nickname, const QString &server, const QString &serverInfo); + void incomingWhoIsOperator(const QString &nickname); + void incomingWhoIsIdentified(const QString &nickname); + void incomingWhoIsChannels(const QString &nickname, const QString &channel); + void incomingWhoIsIdle(const QString &nickname, unsigned long seconds); /* 317 */ + void incomingSignOnTime(const QString &nickname, unsigned long seconds); /* 317 */ + void incomingEndOfWhois(const QString &nickname); + void incomingEndOfWhoWas(const QString &nickname); + + void incomingWhoReply( const QString &nick, const QString &channel, const QString &user, const QString &host, + const QString &server,bool away, const QString &flag, uint hops, const QString &realName ); + + void incomingEndOfWho( const QString &query ); + + //Error Message Signals + void incomingServerLoadTooHigh(); + void incomingNickInUse(const QString &usingNick); + void incomingNickChange(const QString &, const QString &); + void incomingFailedServerPassword(); + void incomingFailedChankey(const QString &); + void incomingFailedChanBanned(const QString &); + void incomingFailedChanInvite(const QString &); + void incomingFailedChanFull(const QString &); + void incomingFailedNickOnLogin(const QString &); + void incomingNoNickChan(const QString &); + void incomingWasNoNick(const QString &); + + //General Signals + void incomingUnknown(const QString &); + void incomingUnknownCtcp(const QString &); + void incomingKick(const QString &channel, const QString &nick, + const QString &nickKicked, const QString &reason); + + void incomingUserIsAway(const QString &nick, const QString &awayMessage); + void incomingListedChan(const QString &chan, uint users, const QString &topic); + void incomingEndOfList(); + + void incomingCtcpReply(const QString &type, const QString &target, const QString &messageReceived); + +private slots: + void destroyed(KIRC::Entity *entity); + + void slotReadyRead(); + + void slotConnected(); + void slotConnectionClosed(); + void error(int errCode = 0); + + void ignoreMessage(KIRC::Message &msg); + void emitSuffix(KIRC::Message &); + + void error(KIRC::Message &msg); + void join(KIRC::Message &msg); + void kick(KIRC::Message &msg); + void mode(KIRC::Message &msg); + void nick(KIRC::Message &msg); + void notice(KIRC::Message &msg); + void part(KIRC::Message &msg); + void ping(KIRC::Message &msg); + void pong(KIRC::Message &msg); + void privmsg(KIRC::Message &msg); +// void squit(KIRC::Message &msg); + void quit(KIRC::Message &msg); + void topic(KIRC::Message &msg); + + void numericReply_001(KIRC::Message &msg); + void numericReply_002(KIRC::Message &msg); + void numericReply_003(KIRC::Message &msg); + void numericReply_004(KIRC::Message &msg); + void numericReply_005(KIRC::Message &msg); + void numericReply_250(KIRC::Message &msg); + void numericReply_251(KIRC::Message &msg); + void numericReply_252(KIRC::Message &msg); + void numericReply_253(KIRC::Message &msg); + void numericReply_254(KIRC::Message &msg); + void numericReply_255(KIRC::Message &msg); + void numericReply_263(KIRC::Message &msg); + void numericReply_265(KIRC::Message &msg); + void numericReply_266(KIRC::Message &msg); + void numericReply_301(KIRC::Message &msg); + void numericReply_303(KIRC::Message &msg); +// void numericReply_305(KIRC::Message &msg); +// void numericReply_306(KIRC::Message &msg); + void numericReply_307(KIRC::Message &msg); + void numericReply_311(KIRC::Message &msg); + void numericReply_312(KIRC::Message &msg); + void numericReply_313(KIRC::Message &msg); + void numericReply_314(KIRC::Message &msg); + void numericReply_315(KIRC::Message &msg); + void numericReply_317(KIRC::Message &msg); + void numericReply_318(KIRC::Message &msg); + void numericReply_319(KIRC::Message &msg); + void numericReply_320(KIRC::Message &msg); + void numericReply_322(KIRC::Message &msg); + void numericReply_323(KIRC::Message &msg); + void numericReply_324(KIRC::Message &msg); + void numericReply_328(KIRC::Message &msg); + void numericReply_329(KIRC::Message &msg); + void numericReply_331(KIRC::Message &msg); + void numericReply_332(KIRC::Message &msg); + void numericReply_333(KIRC::Message &msg); + void numericReply_352(KIRC::Message &msg); + void numericReply_353(KIRC::Message &msg); + void numericReply_366(KIRC::Message &msg); + void numericReply_369(KIRC::Message &msg); + void numericReply_372(KIRC::Message &msg); +// void numericReply_376(KIRC::Message &msg); + + void numericReply_401(KIRC::Message &msg); + void numericReply_406(KIRC::Message &msg); + void numericReply_422(KIRC::Message &msg); + void numericReply_433(KIRC::Message &msg); + void numericReply_464(KIRC::Message &msg); + void numericReply_471(KIRC::Message &msg); + void numericReply_473(KIRC::Message &msg); + void numericReply_474(KIRC::Message &msg); + void numericReply_475(KIRC::Message &msg); + + + void CtcpQuery_action(KIRC::Message &msg); + void CtcpQuery_clientinfo(KIRC::Message &msg); + void CtcpQuery_finger(KIRC::Message &msg); + void CtcpQuery_dcc(KIRC::Message &msg); + void CtcpQuery_ping(KIRC::Message &msg); + void CtcpQuery_source(KIRC::Message &msg); + void CtcpQuery_time(KIRC::Message &msg); + void CtcpQuery_userinfo(KIRC::Message &msg); + void CtcpQuery_version(KIRC::Message &msg); + + void CtcpReply_errmsg(KIRC::Message &msg); + void CtcpReply_ping(KIRC::Message &msg); + void CtcpReply_version(KIRC::Message &msg); + +private: + void bindCommands(); + void bindNumericReplies(); + void bindCtcp(); + + void setStatus(KIRC::Engine::Status status); + bool invokeCtcpCommandOfMessage(const QDict &map, KIRC::Message &message); + + /* + * Methods that handles all the bindings creations. + * This methods is used by all the bind(...) methods. + */ + bool _bind(QDict &dict, + QString command, QObject *object, const char *member, + int minArgs, int maxArgs, const QString &helpMessage); + + //Static regexes + static const QRegExp m_RemoveLinefeeds; + + KIRC::Engine::Status m_status; + QString m_Host; + Q_UINT16 m_Port; + +// QUrl serverURL; +// QUrl currentServerURL; + QString m_Nickname; + QString m_Username; + QString m_realName; + QString m_Passwd; + bool m_ReqsPasswd; + bool m_FailedNickOnLogin; + bool m_useSSL; + + QValueList m_entities; + KIRC::EntityPtr m_server; + KIRC::EntityPtr m_self; + + QString m_VersionString; + QString m_UserString; + QString m_SourceString; + QString m_PendingNick; + + QDict m_commands; +// QIntDict m_numericCommands; + QDict m_ctcpQueries; + QDict m_ctcpReplies; + + QMap customCtcpMap; + QDict codecs; + QTextCodec *defaultCodec; + + KExtendedSocket *m_sock; +}; + +} + +#endif diff --git a/kopete/protocols/irc/libkirc/kircengine_commands.cpp b/kopete/protocols/irc/libkirc/kircengine_commands.cpp new file mode 100644 index 00000000..0a0f9002 --- /dev/null +++ b/kopete/protocols/irc/libkirc/kircengine_commands.cpp @@ -0,0 +1,312 @@ +/* + kirc_commands.h - IRC Client + + Copyright (c) 2003-2004 by Michel Hermier + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2003 by Jason Keirstead + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kircengine.h" + +#include + +#include + +using namespace KIRC; + +void Engine::bindCommands() +{ + bind("ERROR", this, SLOT(error(KIRC::Message &)), 0, 0); + bind("JOIN", this, SLOT(join(KIRC::Message &)), 0, 1); + bind("KICK", this, SLOT(kick(KIRC::Message &)), 2, 2); + bind("NICK", this, SLOT(nick(KIRC::Message &)), 0, 0); + bind("MODE", this, SLOT(mode(KIRC::Message &)), 1, 1); + bind("NOTICE", this, SLOT(notice(KIRC::Message &)), 1, 1); + bind("PART", this, SLOT(part(KIRC::Message &)), 1, 1); + bind("PING", this, SLOT(ping(KIRC::Message &)), 0, 0); + bind("PONG", this, SLOT(pong(KIRC::Message &)), 0, 0); + bind("PRIVMSG", this, SLOT(privmsg(KIRC::Message &)), 1, 1); + bind("QUIT", this, SLOT(quit(KIRC::Message &)), 0, 0); +// bind("SQUIT", this, SLOT(squit(KIRC::Message &)), 1, 1); + bind("TOPIC", this, SLOT(topic(KIRC::Message &)), 1, 1); +} + +void Engine::away(bool isAway, const QString &awayMessage) +{ + if(isAway) + if( !awayMessage.isEmpty() ) + writeMessage("AWAY", QString::null, awayMessage); + else + writeMessage("AWAY", QString::null, QString::fromLatin1("I'm away.")); + else + writeMessage("AWAY", QString::null); +} + +// FIXME: Really handle this message +void Engine::error(Message &) +{ + setStatus(Closing); +} + +void Engine::ison(const QStringList &nickList) +{ + if (!nickList.isEmpty()) + { + QString statement = QString::fromLatin1("ISON"); + for (QStringList::ConstIterator it = nickList.begin(); it != nickList.end(); ++it) + { + if ((statement.length()+(*it).length())>509) // 512(max buf)-2("\r\n")-1() + { + writeMessage(statement); + statement = QString::fromLatin1("ISON ") + (*it); + } + else + statement.append(QChar(' ') + (*it)); + } + writeMessage(statement); + } +} + +void Engine::join(const QString &name, const QString &key) +{ + QStringList args(name); + if ( !key.isNull() ) + args << key; + + writeMessage("JOIN", args); +} + +void Engine::join(Message &msg) +{ + /* RFC say: "( *( "," ) [ *( "," ) ] ) / "0"" + * suspected: ": *(" "/"," )" + * assumed ":" + * This is the response of someone joining a channel. + * Remember that this will be emitted when *you* /join a room for the first time */ + + if (msg.argsSize()==1) + emit incomingJoinedChannel(Kopete::Message::unescape(msg.arg(0)), msg.nickFromPrefix()); + else + emit incomingJoinedChannel(Kopete::Message::unescape(msg.suffix()), msg.nickFromPrefix()); +} + +void Engine::kick(const QString &user, const QString &channel, const QString &reason) +{ + writeMessage("KICK", QStringList(channel) << user << reason); +} + +void Engine::kick(Message &msg) +{ + /* The given user is kicked. + * " *( "," ) *( "," ) []" + */ + emit incomingKick( Kopete::Message::unescape(msg.arg(0)), msg.nickFromPrefix(), msg.arg(1), msg.suffix()); +} + +void Engine::mode(const QString &target, const QString &mode) +{ + writeMessage("MODE", QStringList(target) << mode); +} + +void Engine::mode(Message &msg) +{ + /* Change the mode of a user. + * " *( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) )" + */ + QStringList args = msg.args(); + args.pop_front(); + if( Entity::isChannel( msg.arg(0) ) ) + emit incomingChannelModeChange( msg.arg(0), msg.nickFromPrefix(), args.join(" ")); + else + emit incomingUserModeChange( msg.nickFromPrefix(), args.join(" ")); +} + +void Engine::nick(const QString &newNickname) +{ + m_PendingNick = newNickname; + writeMessage("NICK", newNickname); +} + +void Engine::nick(Message &msg) +{ + /* Nick name of a user changed + * "" */ + QString oldNick = msg.prefix().section('!', 0, 0); + QString newNick = msg.suffix(); + + if( codecs[ oldNick ] ) + { + QTextCodec *c = codecs[ oldNick ]; + codecs.remove( oldNick ); + codecs.insert( newNick, c ); + } + + if (oldNick.lower() == m_Nickname.lower()) + { + emit successfullyChangedNick(oldNick, msg.suffix()); + m_Nickname = msg.suffix(); + } + else + emit incomingNickChange(oldNick, msg.suffix()); +} + +void Engine::part(const QString &channel, const QString &reason) +{ + /* This will part a channel with 'reason' as the reason for parting + */ + writeMessage("PART", channel, reason); +} + +void Engine::part(Message &msg) +{ + /* This signal emits when a user parts a channel + * " *( "," ) [ ]" + */ + kdDebug(14120) << "User parting" << endl; + emit incomingPartedChannel(msg.arg(0), msg.nickFromPrefix(), msg.suffix()); +} + +void Engine::pass(const QString &password) +{ + writeMessage("PASS", password); +} + +void Engine::ping(Message &msg) +{ + writeMessage("PONG", msg.arg(0), msg.suffix()); +} + +void Engine::pong(Message &/*msg*/) +{ +} + +void Engine::quit(const QString &reason, bool /*now*/) +{ + kdDebug(14120) << k_funcinfo << reason << endl; + + if (isDisconnected()) + return; + + if (isConnected()) + writeMessage("QUIT", QString::null, reason); + + setStatus(Closing); +} + +void Engine::quit(Message &msg) +{ + /* This signal emits when a user quits irc. + */ + kdDebug(14120) << "User quiting" << endl; + emit incomingQuitIRC(msg.prefix(), msg.suffix()); +} + +void Engine::user(const QString &newUserName, const QString &hostname, const QString &newRealName) +{ + /* RFC1459: " " + * The USER command is used at the beginning of connection to specify + * the username, hostname and realname of a new user. + * hostname is usualy set to "127.0.0.1" */ + m_Username = newUserName; + m_realName = newRealName; + + writeMessage("USER", QStringList(m_Username) << hostname << m_Host, m_realName); +} + +void Engine::user(const QString &newUserName, Q_UINT8 mode, const QString &newRealName) +{ + /* RFC2812: " " + * mode is a numeric value (from a bit mask). + * 0x00 normal + * 0x04 request +w + * 0x08 request +i */ + m_Username = newUserName; + m_realName = newRealName; + + writeMessage("USER", QStringList(m_Username) << QString::number(mode) << QChar('*'), m_realName); +} + +void Engine::topic(const QString &channel, const QString &topic) +{ + writeMessage("TOPIC", channel, topic); +} + +void Engine::topic(Message &msg) +{ + /* The topic of a channel changed. emit the channel, new topic, and the person who changed it. + * " [ ]" + */ + emit incomingTopicChange(msg.arg(0), msg.nickFromPrefix(), msg.suffix()); +} + +void Engine::list() +{ + writeMessage("LIST", QString::null); +} + +void Engine::motd(const QString &server) +{ + writeMessage("MOTD", server); +} + +void Engine::privmsg(const QString &contact, const QString &message) +{ + writeMessage("PRIVMSG", contact, message, codecForNick( contact ) ); +} + +void Engine::privmsg(Message &msg) +{ + /* This is a signal that indicates there is a new message. + * This can be either from a channel or from a specific user. */ + Message m = msg; + if (!m.suffix().isEmpty()) + { + QString user = m.arg(0); + QString message = m.suffix(); + const QTextCodec *codec = codecForNick( user ); + if (codec != defaultCodec) { + m.decodeAgain( codec ); + message = m.suffix(); + } + if (Entity::isChannel(user)) + emit incomingMessage(m.nickFromPrefix(), Kopete::Message::unescape(m.arg(0)), message ); + else + emit incomingPrivMessage(m.nickFromPrefix(), Kopete::Message::unescape(m.arg(0)), message ); +// emit receivedMessage(PrivateMessage, msg.entityFrom(), msg.entityTo(), message); + } + + if( m.hasCtcpMessage() ) + { + invokeCtcpCommandOfMessage(m_ctcpQueries, m); + } +} + +void Engine::notice(const QString &target, const QString &message) +{ + writeMessage("NOTICE", target, message); +} + +void Engine::notice(Message &msg) +{ + if(!msg.suffix().isEmpty()) + emit incomingNotice(msg.prefix(), msg.suffix()); + + if(msg.hasCtcpMessage()) + invokeCtcpCommandOfMessage(m_ctcpReplies, msg); +} + +void Engine::whois(const QString &user) +{ + writeMessage("WHOIS", user); +} diff --git a/kopete/protocols/irc/libkirc/kircengine_ctcp.cpp b/kopete/protocols/irc/libkirc/kircengine_ctcp.cpp new file mode 100644 index 00000000..db1903f3 --- /dev/null +++ b/kopete/protocols/irc/libkirc/kircengine_ctcp.cpp @@ -0,0 +1,351 @@ +/* + kirc_ctcp.h - IRC Client + + Copyright (c) 2003 by Michel Hermier + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2003 by Jason Keirstead + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "config.h" + +#include "kircengine.h" +#include "kirctransferhandler.h" +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#include +#include +#include + +#include +#include + +using namespace KIRC; + +void Engine::bindCtcp() +{ + bindCtcpQuery("ACTION", this, SLOT(CtcpQuery_action(KIRC::Message &)), + -1, -1); + bindCtcpQuery("CLIENTINFO", this, SLOT(CtcpQuery_clientinfo(KIRC::Message &)), + -1, 1); + bindCtcpQuery("DCC", this, SLOT(CtcpQuery_dcc(KIRC::Message &)), + 4, 5); + bindCtcpQuery("FINGER", this, SLOT(CtcpQuery_finger(KIRC::Message &)), + -1, 0); + bindCtcpQuery("PING", this, SLOT(CtcpQuery_ping(KIRC::Message &)), + 1, 1); + bindCtcpQuery("SOURCE", this, SLOT(CtcpQuery_source(KIRC::Message &)), + -1, 0); + bindCtcpQuery("TIME", this, SLOT(CtcpQuery_time(KIRC::Message &)), + -1, 0); + bindCtcpQuery("USERINFO", this, SLOT(CtcpQuery_userinfo(KIRC::Message &)), + -1, 0); + bindCtcpQuery("VERSION", this, SLOT(CtcpQuery_version(KIRC::Message &)), + -1, 0); + + bindCtcpReply("ERRMSG", this, SLOT(CtcpReply_errmsg(KIRC::Message &)), + 1, -1); + bindCtcpReply("PING", this, SLOT(CtcpReply_ping(KIRC::Message &)), + 1, 1, ""); + bindCtcpReply("VERSION", this, SLOT(CtcpReply_version(KIRC::Message &)), + -1, -1, ""); +} + +// Normal order for a ctcp command: +// CtcpRequest_* +// CtcpQuery_* +// CtcpReply_* (if any) + +/* Generic ctcp commnd for the /ctcp trigger */ +void Engine::CtcpRequestCommand(const QString &contact, const QString &command) +{ + if(m_status == Connected) + { + writeCtcpQueryMessage(contact, QString::null, command); +// emit ctcpCommandMessage( contact, command ); + } +} + +void Engine::CtcpRequest_action(const QString &contact, const QString &message) +{ + if(m_status == Connected) + { + writeCtcpQueryMessage(contact, QString::null, "ACTION", message); + + if( Entity::isChannel(contact) ) + emit incomingAction(Kopete::Message::unescape(contact), Kopete::Message::unescape(m_Nickname), message); + else + emit incomingPrivAction(Kopete::Message::unescape(m_Nickname), Kopete::Message::unescape(contact), message); + } +} + +void Engine::CtcpQuery_action(Message &msg) +{ + QString target = msg.arg(0); + if (target[0] == '#' || target[0] == '!' || target[0] == '&') + emit incomingAction(target, msg.nickFromPrefix(), msg.ctcpMessage().ctcpRaw()); + else + emit incomingPrivAction(msg.nickFromPrefix(), Kopete::Message::unescape(target), msg.ctcpMessage().ctcpRaw()); +} + +/* +NO REPLY EXIST FOR THE CTCP ACTION COMMAND ! +bool Engine::CtcpReply_action(Message &msg) +{ +} +*/ + +// FIXME: the API can now answer to help commands. +void Engine::CtcpQuery_clientinfo(Message &msg) +{ + QString clientinfo = customCtcpMap[ QString::fromLatin1("clientinfo") ]; + + if (clientinfo.isNull()) + clientinfo = QString::fromLatin1("The following commands are supported, but " + "without sub-command help: VERSION, CLIENTINFO, USERINFO, TIME, SOURCE, PING," + "ACTION."); + + writeCtcpReplyMessage( msg.nickFromPrefix(), QString::null, + msg.ctcpMessage().command(), QString::null, clientinfo); +} + +void Engine::CtcpRequest_dcc(const QString &nickname, const QString &fileName, uint port, Transfer::Type type) +{ + if( m_status != Connected || + m_sock->localAddress() == 0 || + m_sock->localAddress()->nodeName().isNull()) + return; + + switch(type) + { + case Transfer::Chat: + { + writeCtcpQueryMessage(nickname, QString::null, + QString::fromLatin1("DCC"), + QStringList(QString::fromLatin1("CHAT")) << QString::fromLatin1("chat") << + m_sock->localAddress()->nodeName() << QString::number(port) + ); + break; + } + + case Transfer::FileOutgoing: + { + QFileInfo file(fileName); + QString noWhiteSpace = file.fileName(); + if (noWhiteSpace.contains(' ') > 0) + noWhiteSpace.replace(QRegExp("\\s+"), "_"); + + TransferServer *server = TransferHandler::self()->createServer(this, nickname, type, fileName, file.size()); + + QString ip = m_sock->localAddress()->nodeName(); + QString ipNumber = QString::number( ntohl( inet_addr( ip.latin1() ) ) ); + + kdDebug(14120) << "Starting DCC file outgoing transfer." << endl; + + writeCtcpQueryMessage(nickname, QString::null, + QString::fromLatin1("DCC"), + QStringList(QString::fromLatin1("SEND")) << noWhiteSpace << ipNumber << + QString::number(server->port()) << QString::number(file.size()) + ); + break; + } + + case Transfer::FileIncoming: + case Transfer::Unknown: + default: + break; + } +} + +void Engine::CtcpQuery_dcc(Message &msg) +{ + Message &ctcpMsg = msg.ctcpMessage(); + QString dccCommand = ctcpMsg.arg(0).upper(); + + if (dccCommand == QString::fromLatin1("CHAT")) + { +// if(ctcpMsg.argsSize()!=4) return false; + + /* DCC CHAT type longip port + * + * type = Either Chat or Talk, but almost always Chat these days + * longip = 32-bit Internet address of originator's machine + * port = Port on which the originator is waitng for a DCC chat + */ + bool okayHost, okayPort; + // should ctctMsg.arg(1) be tested? + QHostAddress address(ctcpMsg.arg(2).toUInt(&okayHost)); + unsigned int port = ctcpMsg.arg(3).toUInt(&okayPort); + if (okayHost && okayPort) + { + kdDebug(14120) << "Starting DCC chat window." << endl; + TransferHandler::self()->createClient( + this, msg.nickFromPrefix(), + address, port, + Transfer::Chat ); + } + } + else if (dccCommand == QString::fromLatin1("SEND")) + { +// if(ctcpMsg.argsSize()!=5) return false; + + /* DCC SEND (filename) (longip) (port) (filesize) + * + * filename = Name of file being sent + * longip = 32-bit Internet address of originator's machine + * port = Port on which the originator is waiitng for a DCC chat + * filesize = Size of file being sent + */ + bool okayHost, okayPort, okaySize; +// QFileInfo realfile(msg.arg(1)); + QHostAddress address(ctcpMsg.arg(2).toUInt(&okayHost)); + unsigned int port = ctcpMsg.arg(3).toUInt(&okayPort); + unsigned int size = ctcpMsg.arg(4).toUInt(&okaySize); + if (okayHost && okayPort && okaySize) + { + kdDebug(14120) << "Starting DCC send file transfert for file:" << ctcpMsg.arg(1) << endl; + TransferHandler::self()->createClient( + this, msg.nickFromPrefix(), + address, port, + Transfer::FileIncoming, + ctcpMsg.arg(1), size ); + } + } +// else +// ((MessageRedirector *)sender())->error("Unknow dcc command"); +} + +/* +NO REPLY EXIST FOR THE CTCP DCC COMMAND ! +bool Engine::CtcpReply_dcc(Message &msg) +{ +} +*/ + +void Engine::CtcpReply_errmsg(Message &) +{ + // should emit one signal +} + +void Engine::CtcpQuery_finger( Message &) +{ + // To be implemented +} + +void Engine::CtcpRequest_ping(const QString &target) +{ + kdDebug(14120) << k_funcinfo << endl; + + timeval time; + if (gettimeofday(&time, 0) == 0) + { + QString timeReply; + + if( Entity::isChannel(target) ) + timeReply = QString::fromLatin1("%1.%2").arg(time.tv_sec).arg(time.tv_usec); + else + timeReply = QString::number( time.tv_sec ); + + writeCtcpQueryMessage( target, QString::null, "PING", timeReply); + } +// else +// ((MessageRedirector *)sender())->error("failed to get current time"); +} + +void Engine::CtcpQuery_ping(Message &msg) +{ + writeCtcpReplyMessage( msg.nickFromPrefix(), QString::null, + msg.ctcpMessage().command(), msg.ctcpMessage().arg(0)); +} + +void Engine::CtcpReply_ping(Message &msg) +{ + timeval time; + if (gettimeofday(&time, 0) == 0) + { + // FIXME: the time code is wrong for usec + QString timeReply = QString::fromLatin1("%1.%2").arg(time.tv_sec).arg(time.tv_usec); + double newTime = timeReply.toDouble(); + double oldTime = msg.suffix().section(' ',0, 0).toDouble(); + double difference = newTime - oldTime; + QString diffString; + + if (difference < 1) + { + diffString = QString::number(difference); + diffString.remove((diffString.find('.') -1), 2); + diffString.truncate(3); + diffString.append("milliseconds"); + } + else + { + diffString = QString::number(difference); + QString seconds = diffString.section('.', 0, 0); + QString millSec = diffString.section('.', 1, 1); + millSec.remove(millSec.find('.'), 1); + millSec.truncate(3); + diffString = QString::fromLatin1("%1 seconds, %2 milliseconds").arg(seconds).arg(millSec); + } + + emit incomingCtcpReply(QString::fromLatin1("PING"), msg.nickFromPrefix(), diffString); + } +// else +// ((MessageRedirector *)sender())->error("failed to get current time"); +} + +void Engine::CtcpQuery_source(Message &msg) +{ + writeCtcpReplyMessage(msg.nickFromPrefix(), QString::null, + msg.ctcpMessage().command(), m_SourceString); +} + +void Engine::CtcpQuery_time(Message &msg) +{ + writeCtcpReplyMessage(msg.nickFromPrefix(), QString::null, + msg.ctcpMessage().command(), QDateTime::currentDateTime().toString(), + QString::null, false); +} + +void Engine::CtcpQuery_userinfo(Message &msg) +{ + QString userinfo = customCtcpMap[ QString::fromLatin1("userinfo") ]; + + if (userinfo.isNull()) + userinfo = m_UserString; + + writeCtcpReplyMessage(msg.nickFromPrefix(), QString::null, + msg.ctcpMessage().command(), QString::null, userinfo); +} + +void Engine::CtcpRequest_version(const QString &target) +{ + writeCtcpQueryMessage(target, QString::null, "VERSION"); +} + +void Engine::CtcpQuery_version(Message &msg) +{ + QString response = customCtcpMap[ QString::fromLatin1("version") ]; + kdDebug(14120) << "Version check: " << response << endl; + + if (response.isNull()) + response = m_VersionString; + + writeCtcpReplyMessage(msg.nickFromPrefix(), + msg.ctcpMessage().command() + " " + response); +} + +void Engine::CtcpReply_version(Message &msg) +{ + emit incomingCtcpReply(msg.ctcpMessage().command(), msg.nickFromPrefix(), msg.ctcpMessage().ctcpRaw()); +} diff --git a/kopete/protocols/irc/libkirc/kircengine_numericreplies.cpp b/kopete/protocols/irc/libkirc/kircengine_numericreplies.cpp new file mode 100644 index 00000000..c47b8b05 --- /dev/null +++ b/kopete/protocols/irc/libkirc/kircengine_numericreplies.cpp @@ -0,0 +1,570 @@ + +/* + kircnumericreplies.cpp - IRC Client + + Copyright (c) 2003 by Michel Hermier + Copyright (c) 2002 by Nick Betcher + Copyright (c) 2003 by Jason Keirstead + + Kopete (c) 2002-2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kircengine.h" + +#include + +using namespace KIRC; + +/* IMPORTANT NOTE: + * Numeric replies always have the current nick or * as first argmuent. + * NOTE: * means undefined in most (all ?) of the cases. + */ + +void Engine::bindNumericReplies() +{ + bind(1, this, SLOT(numericReply_001(KIRC::Message &)), 1, 1); + bind(2, this, SLOT(numericReply_002(KIRC::Message &)), 1, 1); + bind(3, this, SLOT(numericReply_003(KIRC::Message &)), 1, 1); + bind(4, this, SLOT(numericReply_004(KIRC::Message &)), 5, 5); + bind(5, this, SLOT(numericReply_004(KIRC::Message &)), 1, 1); + + bind(250, this, SLOT(numericReply_250(KIRC::Message &))); + bind(251, this, SLOT(numericReply_251(KIRC::Message &))); + bind(252, this, SLOT(numericReply_252(KIRC::Message &)), 2, 2); + bind(253, this, SLOT(numericReply_253(KIRC::Message &)), 2, 2); + bind(254, this, SLOT(numericReply_254(KIRC::Message &)), 2, 2); + bind(255, this, SLOT(numericReply_255(KIRC::Message &)), 1, 1); // incomingConnectString + + bind(263, this, SLOT(numericReply_263(KIRC::Message &))); // incomingServerLoadTooHigh + bind(265, this, SLOT(numericReply_265(KIRC::Message &))); + bind(266, this, SLOT(numericReply_266(KIRC::Message &))); + + bind(301, this, SLOT(numericReply_301(KIRC::Message &)), 2, 2); + bind(303, this, SLOT(numericReply_303(KIRC::Message &)), 1, 1); + bind(305, this, SLOT(ignoreMessage(KIRC::Message &)), 0, 0 ); // You are no longer marked as away + bind(306, this, SLOT(ignoreMessage(KIRC::Message &)), 0, 0 ); // You are marked as away + bind(307, this, SLOT(numericReply_307(KIRC::Message &)), 1, 1); + bind(311, this, SLOT(numericReply_311(KIRC::Message &)), 5, 5); + bind(312, this, SLOT(numericReply_312(KIRC::Message &)), 3, 3); + bind(313, this, SLOT(numericReply_313(KIRC::Message &)), 2, 2); + bind(314, this, SLOT(numericReply_314(KIRC::Message &)), 5, 5); + bind(315, this, SLOT(numericReply_315(KIRC::Message &)), 2, 2); + bind(317, this, SLOT(numericReply_317(KIRC::Message &)), 3, 4); + bind(318, this, SLOT(numericReply_318(KIRC::Message &)), 2, 2); + bind(319, this, SLOT(numericReply_319(KIRC::Message &)), 2, 2); + bind(320, this, SLOT(numericReply_320(KIRC::Message &)), 2, 2); + bind(321, this, SLOT(ignoreMessage(KIRC::Message &)), 0, 0 ); + bind(322, this, SLOT(numericReply_322(KIRC::Message &)), 3, 3); + bind(323, this, SLOT(numericReply_323(KIRC::Message &)), 1, 1); + bind(324, this, SLOT(numericReply_324(KIRC::Message &)), 2, 4); + bind(328, this, SLOT(numericReply_328(KIRC::Message &)), 2, 2); + bind(329, this, SLOT(numericReply_329(KIRC::Message &)), 3, 3); + bind(330, this, SLOT(ignoreMessage(KIRC::Message &)), 0, 0); // ??? + bind(331, this, SLOT(numericReply_331(KIRC::Message &)), 2, 2); + bind(332, this, SLOT(numericReply_332(KIRC::Message &)), 2, 2); + bind(333, this, SLOT(numericReply_333(KIRC::Message &)), 4, 4); + bind(352, this, SLOT(numericReply_352(KIRC::Message &)), 5, 10); + bind(353, this, SLOT(numericReply_353(KIRC::Message &)), 3, 3); + bind(366, this, SLOT(numericReply_366(KIRC::Message &)), 2, 2); + bind(369, this, SLOT(numericReply_369(KIRC::Message &)), 2, 2); + bind(372, this, SLOT(numericReply_372(KIRC::Message &)), 1, 1); + bind(375, this, SLOT(ignoreMessage(KIRC::Message&)), 0, 0 ); + bind(376, this, SLOT(ignoreMessage(KIRC::Message&)), 0, 0 ); + + bind(401, this, SLOT(numericReply_401(KIRC::Message &)), 2, 2); // incomingNoNickChan +// bind(404, this, SLOT(numericReply_404(KIRC::Message &)), 2, 2); // incomingCannotSendToChannel + bind(406, this, SLOT(numericReply_406(KIRC::Message &)), 2, 2); // incomingWasNoNick + bind(422, this, SLOT(numericReply_422(KIRC::Message &)), 1, 1); + bind(433, this, SLOT(numericReply_433(KIRC::Message &)), 2, 2); +// bind(442, this, SLOT(numericReply_442(KIRC::Message &)), 2, 2); // incomingCannotSendToChannel + bind(464, this, SLOT(numericReply_464(KIRC::Message &)), 1, 1); + bind(471, this, SLOT(numericReply_471(KIRC::Message &)), 2, 2); + bind(473, this, SLOT(numericReply_473(KIRC::Message &)), 2, 2); + bind(474, this, SLOT(numericReply_474(KIRC::Message &)), 2, 2); + bind(475, this, SLOT(numericReply_475(KIRC::Message &)), 2, 2); + + //Freenode seems to use this for a non-RFC compliant purpose, as does Unreal + bind(477, this, SLOT(emitSuffix(KIRC::Message&)),0,0); +} + +/* 001: "Welcome to the Internet Relay Network !@" + * Gives a welcome message in the form of: + */ +void Engine::numericReply_001(Message &msg) +{ + kdDebug(14121) << k_funcinfo << endl; + + if (m_FailedNickOnLogin) + { + // this is if we had a "Nickname in use" message when connecting and we set another nick. + // This signal emits that the nick was accepted and we are now logged in + emit successfullyChangedNick(m_Nickname, m_PendingNick); + m_Nickname = m_PendingNick; + m_FailedNickOnLogin = false; + } + + /* At this point we are connected and the server is ready for us to being taking commands + * although the MOTD comes *after* this. + */ + emitSuffix(msg); + + setStatus(Connected); +} + +/* 002: ":Your host is , running version " + * Gives information about the host. The given informations are close to 004. + */ +void Engine::numericReply_002(Message &msg) +{ + emitSuffix(msg); +} + +/* 003: "This server was created " + * Gives the date that this server was created. + * NOTE: This is useful for determining the uptime of the server). + */ +void Engine::numericReply_003(Message &msg) +{ + emitSuffix(msg); +} + +/* 004: " " + * Gives information about the servername, version, available modes, etc. + */ +void Engine::numericReply_004(Message &msg) +{ + emit incomingHostInfo(msg.arg(1),msg.arg(2),msg.arg(3),msg.arg(4)); +} + +/* 005: + * Gives capability information. TODO: This is important! + */ +void Engine::numericReply_005(Message &msg) +{ + emit incomingConnectString( msg.toString() ); +} + +/* 250: ":Highest connection count: ( clients) + * ( since server was (re)started)" + * Tells connections statistics about the server for the uptime activity. + * NOT IN RFC1459 NOR RFC2812 + */ +void Engine::numericReply_250(Message &msg) +{ + emit incomingConnectString( msg.suffix() ); +} + +/* 251: ":There are users and services on servers" + * Tells how many user there are on all the different servers in the form of: + */ +void Engine::numericReply_251(Message &msg) +{ + emit incomingConnectString( msg.suffix() ); +} +/* 252: " :operator(s) online" + * Issues a number of operators on the server in the form of: + */ +void Engine::numericReply_252(Message &msg) +{ + emit incomingConnectString( msg.arg(1) + ' ' + msg.suffix() ); +} + +/* 253: " :unknown connection(s)" + * Tells how many unknown connections the server has in the form of: + */ +void Engine::numericReply_253(Message &msg) +{ + emit incomingConnectString( msg.arg(1) + ' ' + msg.suffix() ); +} + +/* Tells how many total channels there are on this network in the form of: + * " :channels formed" */ +void Engine::numericReply_254(Message &msg) +{ + emit incomingConnectString( msg.arg(1) + ' ' + msg.suffix() ); +} + +/* 255: ":I have clients and servers" + * Tells how many clients and servers *this* server handles. + */ +void Engine::numericReply_255(Message &msg) +{ + emit incomingConnectString( msg.suffix() ); +} + +/* 263: + * Server is too busy. + */ +void Engine::numericReply_263(Message &) +{ + emit incomingServerLoadTooHigh(); +} + +/* 265: ":Current local users: Max: " + * Tells statistics about the current local server state. + * NOT IN RFC2812 + */ +void Engine::numericReply_265(Message &msg) +{ + emit incomingConnectString( msg.suffix() ); +} + +/* 266: ":Current global users: Max: " + * Tells statistics about the current global(the whole irc server chain) server state: + */ +void Engine::numericReply_266(Message &msg) +{ + emit incomingConnectString( msg.suffix() ); +} + +/* 301: " :" + */ +void Engine::numericReply_301(Message &msg) +{ + emit incomingUserIsAway(Kopete::Message::unescape(msg.arg(1)), msg.suffix()); +} + +/* 303: ":*1 *(" " )" + */ +void Engine::numericReply_303(Message &msg) +{ + QStringList nicks = QStringList::split(QRegExp(QChar(' ')), msg.suffix()); + for(QStringList::Iterator it = nicks.begin(); it != nicks.end(); ++it) + { + if (!(*it).stripWhiteSpace().isEmpty()) + emit incomingUserOnline(Kopete::Message::unescape(*it)); + } +} + +/* 305: ":You are no longer marked as being away" + */ +// void Engine::numericReply_305(Message &msg) +// { +// } + + +/* 306: ":You have been marked as being away" + */ +// void Engine::numericReply_306(Message &msg) +// { +// } + +/* 307: ":is a registered nick" + * DALNET: Indicates that this user is identified with NICSERV. + */ +void Engine::numericReply_307(Message & /*msg*/) +{ +// emit incomingWhoiIsUserNickIsRegistered(Kopete::Message::unescape(msg.arg(1))); +} + +/* 311: " * :" + * Show info about a user (part of a /whois) in the form of: + */ +void Engine::numericReply_311(Message &msg) +{ + emit incomingWhoIsUser(Kopete::Message::unescape(msg.arg(1)), msg.arg(2), msg.arg(3), msg.suffix()); +} + +/* 312: " :" + * Show info about a server (part of a /whois). + */ +void Engine::numericReply_312(Message &msg) +{ + emit incomingWhoIsServer(Kopete::Message::unescape(msg.arg(1)), msg.arg(2), msg.suffix()); +} + +/* 313: " :is an IRC operator" + * Show info about an operator (part of a /whois). + */ +void Engine::numericReply_313(Message & /*msg*/) +{ +} + +/* 314: " * :" + * Show WHOWAS Info + */ +void Engine::numericReply_314(Message &msg) +{ + emit incomingWhoWasUser(Kopete::Message::unescape(msg.arg(1)), msg.arg(2), msg.arg(3), msg.suffix()); +} + +void Engine::numericReply_315(Message &msg) +{ + emit incomingEndOfWho(Kopete::Message::unescape(msg.arg(1))); +} + +void Engine::numericReply_317(Message &msg) +{ + /* RFC say: " :seconds idle" + * Some servers say: " :seconds idle, signon time" + * Show info about someone who is idle (part of a /whois) in the form of: + */ + emit incomingWhoIsIdle(Kopete::Message::unescape(msg.arg(1)), msg.arg(2).toULong()); + if (msg.argsSize()==4) + emit incomingSignOnTime(Kopete::Message::unescape(msg.arg(1)),msg.arg(3).toULong()); +} + +/* 318: "{} :End of /WHOIS list" + * End of WHOIS for a given nick. + */ +void Engine::numericReply_318(Message &msg) +{ + emit incomingEndOfWhois(Kopete::Message::unescape(msg.arg(1))); +} + +void Engine::numericReply_319(Message &msg) +{ + /* Show info a channel a user is logged in (part of a /whois) in the form of: + * " :{[@|+]}" + */ + emit incomingWhoIsChannels(Kopete::Message::unescape(msg.arg(1)), msg.suffix()); +} + +/* 320: + * Indicates that this user is identified with NICSERV on FREENODE. + */ +void Engine::numericReply_320(Message &msg) +{ + emit incomingWhoIsIdentified(Kopete::Message::unescape(msg.arg(1))); +} + +/* 321: " :Users Name" ("Channel :Users Name") + * RFC1459: Declared. + * RFC2812: Obsoleted. + */ + +/* 322: " <# visible> :" + * Received one channel from the LIST command. + */ +void Engine::numericReply_322(Message &msg) +{ + //kdDebug(14120) << k_funcinfo << "Listed " << msg.arg(1) << endl; + + emit incomingListedChan(Kopete::Message::unescape(msg.arg(1)), msg.arg(2).toUInt(), msg.suffix()); +} + +/* 323: ":End of LIST" + * End of the LIST command. + */ +void Engine::numericReply_323(Message &) +{ + emit incomingEndOfList(); +} + +/* 324: " " + */ +void Engine::numericReply_324(Message &msg) +{ + emit incomingChannelMode(Kopete::Message::unescape(msg.arg(1)), msg.arg(2), msg.arg(3)); +} + +/* 328: " " + */ +void Engine::numericReply_328(Message &msg) +{ + kdDebug(14120) << k_funcinfo << endl; + emit incomingChannelHomePage(Kopete::Message::unescape(msg.arg(1)), msg.suffix()); +} + +/* 329: "%s %lu" + * NOTE: What is the meaning of this arguments. DAL-ircd say it's a RPL_CREATIONTIME + * NOT IN RFC1459 NOR RFC2812 + */ +void Engine::numericReply_329( Message &) +{ +} + +/* 331: " :No topic is set" + * Gives the existing topic for a channel after a join. + */ +void Engine::numericReply_331( Message &) +{ +// emit incomingExistingTopic(msg.arg(1), suffix); +} + +/* 332: " :" + * Gives the existing topic for a channel after a join. + */ +void Engine::numericReply_332(Message &msg) +{ + emit incomingExistingTopic(Kopete::Message::unescape(msg.arg(1)), msg.suffix()); +} + +/* 333: + * Gives the nickname and time who changed the topic + */ +void Engine::numericReply_333( Message &msg ) +{ + kdDebug(14120) << k_funcinfo << endl; + QDateTime d; + d.setTime_t( msg.arg(3).toLong() ); + emit incomingTopicUser( Kopete::Message::unescape(msg.arg(1)), Kopete::Message::unescape(msg.arg(2)), d ); +} + +/* 352: + * WHO Reply + * + * " ("H" / "G") ["*"] [("@" / "+")] : " + * + * :efnet.cs.hut.fi 352 userNick #foobar username some.host.name efnet.cs.hut.fi someNick H :0 foobar + * :efnet.cs.hut.fi 352 userNick #foobar ~fooobar other.hostname irc.dkom.at anotherNick G+ :3 Unknown + */ +void Engine::numericReply_352(Message &msg) +{ + emit incomingWhoReply( + Kopete::Message::unescape(msg.arg(5)), // nick name + Kopete::Message::unescape(msg.arg(1)), // channel name + msg.arg(2), // user name + msg.arg(3), // host name + msg.arg(4), // server name + msg.arg(6)[0] != 'H', // G=away (true), H=not away (false) + msg.arg(7), // @ (op), + (voiced) + msg.suffix().section(' ', 0, 1 ).toUInt(), // hopcount + msg.suffix().section(' ', 1 ) // real name + ); +} + + +/* 353: + * NAMES list + */ +void Engine::numericReply_353(Message &msg) +{ + emit incomingNamesList(Kopete::Message::unescape(msg.arg(2)), QStringList::split(' ', msg.suffix())); +} + +/* 366: " :End of NAMES list" + * Gives a signal to indicate that the NAMES list has ended for channel. + */ +void Engine::numericReply_366(Message &msg) +{ + emit incomingEndOfNames(msg.arg(1)); +} + +/* 369: + * End of WHOWAS Request + */ +void Engine::numericReply_369(Message & /*msg*/) +{ +} + +/* 372: ":- " + * Part of the MOTD. + */ +void Engine::numericReply_372(Message &msg) +{ + emit incomingMotd(msg.suffix()); +} + +/* 375: ":- Message of the day - " + * Beginging the motd. This isn't emitted because the MOTD is sent out line by line. + */ + +/* 376: ":End of MOTD command" + * End of the motd. + */ + +/* 401: " :No such nick/channel" + * Gives a signal to indicate that the command issued failed because the person/channel not being on IRC. + * - Used to indicate the nickname parameter supplied to a command is currently unused. + */ +void Engine::numericReply_401(Message &msg) +{ + emit incomingNoSuchNickname( Kopete::Message::unescape(msg.arg(1)) ); +} + +/* 406: " :There was no such nickname" + * Like case 401, but when there *was* no such nickname. + */ +void Engine::numericReply_406(Message &msg) +{ + emit incomingNoSuchNickname( Kopete::Message::unescape(msg.arg(1)) ); +} + +/* 422: ":MOTD File is missing" + * + * Server's MOTD file could not be opened by the server. + */ +void Engine::numericReply_422(Message &msg) +{ + emit incomingMotd(msg.suffix()); +} + +/* 433: " :Nickname is already in use" + * Tells us that our nickname is already in use. + */ +void Engine::numericReply_433(Message &msg) +{ + if(m_status == Authentifying) + { + // This tells us that our nickname is, but we aren't logged in. + // This differs because the server won't send us a response back telling us our nick changed + // (since we aren't logged in). + m_FailedNickOnLogin = true; + emit incomingFailedNickOnLogin(Kopete::Message::unescape(msg.arg(1))); + } + else + { + // And this is the signal for if someone is trying to use the /nick command or such when already logged in, + // but it's already in use + emit incomingNickInUse(Kopete::Message::unescape(msg.arg(1))); + } +} + +/* 464: ":Password Incorrect" + * Bad server password + */ +void Engine::numericReply_464(Message &/*msg*/) +{ + /* Server need pass.. Call disconnect*/ + emit incomingFailedServerPassword(); +} + +/* 471: + * Channel is Full + */ +void Engine::numericReply_471(Message &msg) +{ + emit incomingFailedChanFull(Kopete::Message::unescape(msg.arg(1))); +} + +/* 473: + * Invite Only. + */ +void Engine::numericReply_473(Message &msg) +{ + emit incomingFailedChanInvite(Kopete::Message::unescape(msg.arg(1))); +} + +/* 474: + * Banned. + */ +void Engine::numericReply_474(Message &msg) +{ + emit incomingFailedChanBanned(Kopete::Message::unescape(msg.arg(1))); +} + +/* 475: + * Wrong Chan-key. + */ +void Engine::numericReply_475(Message &msg) +{ + emit incomingFailedChankey(Kopete::Message::unescape(msg.arg(1))); +} + +/* 477: " :You need a registered nick to join that channel." + * Available on DALNET servers only ? + */ +// void Engine::numericReply_477(Message &msg) +// { +// emit incomingChannelNeedRegistration(msg.arg(2), msg.suffix()); +// } diff --git a/kopete/protocols/irc/libkirc/kircentity.cpp b/kopete/protocols/irc/libkirc/kircentity.cpp new file mode 100644 index 00000000..6aa6fd55 --- /dev/null +++ b/kopete/protocols/irc/libkirc/kircentity.cpp @@ -0,0 +1,132 @@ +/* + kircentity.cpp - IRC Client + + Copyright (c) 2004 by Michel Hermier + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kircengine.h" +#include "kircentity.h" + +#include + +using namespace KIRC; +using namespace KNetwork; + +/** + * Match a possible user definition: + * nick!user@host + * where user and host are optionnal. + * NOTE: If changes are done to the regexp string, update also the sm_userStrictRegExp regexp string. + */ +const QRegExp Entity::sm_userRegExp(QString::fromLatin1("^([^\\s,:!@]+)(?:(?:!([^\\s,:!@]+))?(?:@([^\\s,!@]+)))?$")); + +/** + * Regexp to match strictly the complete user definition: + * nick!user@host + * NOTE: If changes are done to the regexp string, update also the sm_userRegExp regexp string. + */ +const QRegExp Entity::sm_userStrictRegExp(QString::fromLatin1("^([^\\s,:!@]+)!([^\\s,:!@]+)@([^\\s,:!@]+)$")); + +const QRegExp Entity::sm_channelRegExp( QString::fromLatin1("^[#!+&][^\\s,]+$") ); + +Entity::Entity(const QString &, const Type type) + : QObject(0, "KIRC::Entity"), + m_type(type) +{ +// rename(name, type); +} + +Entity::~Entity() +{ + emit destroyed(this); +} + +QString Entity::name() const +{ + return m_name; +} + +QString Entity::host() const +{ + switch(m_type) + { +// case Unknown: + case Server: + return m_name; +// case Channel: + case Service: + case User: + return userHost(); + default: + kdDebug(14121) << k_funcinfo << "No host defined for type:" << m_type; + return QString::null; + } +} + +KIRC::Entity::Type Entity::type() const +{ + return m_type; +} + +KIRC::Entity::Type Entity::guessType() +{ + m_type = guessType(m_name); + return m_type; +} + +// FIXME: Implement me +KIRC::Entity::Type Entity::guessType(const QString &) +{ + return Unknown; +} + +QString Entity::userNick() const +{ + return userNick(m_name); +} + +QString Entity::userNick(const QString &s) +{ + return userInfo(s, 1); +} + +QString Entity::userName() const +{ + return userName(m_name); +} + +QString Entity::userName(const QString &s) +{ + return userInfo(s, 2); +} + +QString Entity::userHost() const +{ + return userHost(m_name); +} + +QString Entity::userHost(const QString &s) +{ + return userInfo(s, 3); +} + +QString Entity::userInfo(const QString &s, int num) +{ + QRegExp userRegExp(sm_userRegExp); + userRegExp.search(s); + return userRegExp.cap(num); +} + +#include "kircentity.moc" + diff --git a/kopete/protocols/irc/libkirc/kircentity.h b/kopete/protocols/irc/libkirc/kircentity.h new file mode 100644 index 00000000..c9336439 --- /dev/null +++ b/kopete/protocols/irc/libkirc/kircentity.h @@ -0,0 +1,128 @@ +/* + kircentity.h - IRC Client + + Copyright (c) 2004 by Michel Hermier + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KIRCENTITY_H +#define KIRCENTITY_H + +#include +#include +#include + +#include +#include +#include +#include + +namespace KIRC +{ + +class Engine; + +class Entity + : public QObject, + public KShared +{ + Q_OBJECT + +public: + typedef enum Type + { + Unknown, + Server, + Channel, + Service, + User + }; + + Entity(const QString &name, const Type type = Unknown); + virtual ~Entity(); + + QString name() const; + QString host() const; + + KIRC::Entity::Type type() const; + KIRC::Entity::Type guessType(); + static KIRC::Entity::Type guessType(const QString &name); + + // FIXME: Remove these is* functions ... They are duplicate with the ::guessType(const QString&) + inline static bool isUser( const QString &s ) + { return sm_userRegExp.exactMatch(s); }; + inline bool isChannel() + { return isChannel(m_name); }; + inline static bool isChannel( const QString &s ) + { return sm_channelRegExp.exactMatch(s); }; + + QString userNick() const; + static QString userNick(const QString &s); + + QString userName() const; + static QString userName(const QString &s); + + QString userHost() const; + static QString userHost(const QString &s); + +signals: + void destroyed(KIRC::Entity *self); + +private: + + static QString userInfo(const QString &s, int num_cap); + + static const QRegExp sm_userRegExp; + static const QRegExp sm_userStrictRegExp; + static const QRegExp sm_channelRegExp; + + KIRC::Entity::Type m_type; + QString m_name; + + // peer ip address if the entity is a User. + QString m_address; +}; + +class EntityPtr + : public KSharedPtr +{ +public: + EntityPtr(KIRC::Entity *entity = 0) + : KSharedPtr(entity) + { } + + EntityPtr(const KIRC::EntityPtr &entity) + : KSharedPtr(entity) + { } +}; + +class EntityPtrList + : public QValueList +{ +public: + EntityPtrList() + { } + + EntityPtrList(const EntityPtr &entity) + { + append(entity); + } + + EntityPtrList(const QValueList &list) + : QValueList(list) + { } +}; + +} + +#endif diff --git a/kopete/protocols/irc/libkirc/kircmessage.cpp b/kopete/protocols/irc/libkirc/kircmessage.cpp new file mode 100644 index 00000000..f1a5b61f --- /dev/null +++ b/kopete/protocols/irc/libkirc/kircmessage.cpp @@ -0,0 +1,370 @@ +/* + kircmessage.cpp - IRC Client + + Copyright (c) 2003 by Michel Hermier + + Kopete (c) 2003 by the Kopete engineelopers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kircengine.h" +#include "kircmessage.h" + +// FIXME: Remove the following dependencies. +#include "kopetemessage.h" +#include "ksparser.h" + +#include +#include +#include +#include + +using namespace KIRC; + +#ifndef _IRC_STRICTNESS_ +QRegExp Message::m_IRCNumericCommand("^\\d{1,3}$"); + +// TODO: This regexp parsing is no good. It's slower than it needs to be, and +// is not codec-safe since QString requires a codec. NEed to parse this with +// our own parsing class that operates on the raw QCStrings +QRegExp Message::m_IRCCommandType1( + "^(?::([^ ]+) )?([A-Za-z]+|\\d{1,3})((?: [^ :][^ ]*)*) ?(?: :(.*))?$"); + // Extra end arg space check -------------------------^ +#else // _IRC_STRICTNESS_ +QRegExp Message::m_IRCNumericCommand("^\\d{3,3}$"); + +QRegExp Message::m_IRCCommandType1( + "^(?::([^ ]+) )?([A-Za-z]+|\\d{3,3})((?: [^ :][^ ]*){0,13})(?: :(.*))?$"); +QRegExp Message::m_IRCCommandType2( + "^(?::[[^ ]+) )?([A-Za-z]+|\\d{3,3})((?: [^ :][^ ]*){14,14})(?: (.*))?$"); +#endif // _IRC_STRICTNESS_ + +Message::Message() + : m_ctcpMessage(0) +{ +} + +Message::Message(const Message &obj) + : m_ctcpMessage(0) +{ + m_raw = obj.m_raw; + + m_prefix = obj.m_prefix; + m_command = obj.m_command; + m_args = obj.m_args; + m_suffix = obj.m_suffix; + + m_ctcpRaw = obj.m_ctcpRaw; + + if (obj.m_ctcpMessage) + m_ctcpMessage = new Message(obj.m_ctcpMessage); +} + +Message::Message(const Message *obj) + : m_ctcpMessage(0) +{ + m_raw = obj->m_raw; + + m_prefix = obj->m_prefix; + m_command = obj->m_command; + m_args = obj->m_args; + m_suffix = obj->m_suffix; + + m_ctcpRaw = obj->m_ctcpRaw; + + if (obj->m_ctcpMessage) + m_ctcpMessage = new Message(obj->m_ctcpMessage); +} + +Message::~Message() +{ + if (m_ctcpMessage) + delete m_ctcpMessage; +} + +void Message::writeRawMessage(Engine *engine, const QTextCodec *codec, const QString &str) +{ + // FIXME: Really handle this + if (!engine->socket()) + { + kdDebug(14121) << k_funcinfo << "Not connected while attempting to write:" << str << endl; + return; + } + + QString txt = str + QString::fromLatin1("\r\n"); + + QCString s(codec->fromUnicode(txt)); + kdDebug(14120) << "Message is " << s.length() << " chars" << endl; + // FIXME: Should check the amount of data really writen. + int wrote = engine->socket()->writeBlock(s.data(), s.length()); + + kdDebug(14121) << QString::fromLatin1("(%1 bytes) >> %2").arg(wrote).arg(str) << endl; +} + +void Message::writeMessage(Engine *engine, const QTextCodec *codec, const QString &message) +{ + writeRawMessage(engine, codec, quote(message)); +} + +void Message::writeMessage(Engine *engine, const QTextCodec *codec, + const QString &command, const QStringList &args, const QString &suffix) +{ + QString msg = command; + + if (!args.isEmpty()) + msg += QChar(' ') + args.join(QChar(' ')).stripWhiteSpace(); // some extra check should be done here + + if (!suffix.isNull()) + msg = msg.stripWhiteSpace() + QString::fromLatin1(" :") + suffix; + + writeMessage(engine, codec, msg); +} + +void Message::writeCtcpMessage(Engine *engine, const QTextCodec *codec, + const QString &command, const QString&to, + const QString &ctcpMessage) +{ + writeMessage(engine, codec, command, to, QChar(0x01) + ctcpQuote(ctcpMessage) + QChar(0x01)); +} + +void Message::writeCtcpMessage(Engine *engine, const QTextCodec *codec, + const QString &command, const QString &to, const QString &suffix, + const QString &ctcpCommand, const QStringList &ctcpArgs, const QString &ctcpSuffix ) +{ + QString ctcpMsg = ctcpCommand; + + if (!ctcpArgs.isEmpty()) + ctcpMsg += QChar(' ') + ctcpArgs.join(QChar(' ')).stripWhiteSpace(); // some extra check should be done here + + if (!ctcpSuffix.isNull()) + ctcpMsg += QString::fromLatin1(" :") + ctcpSuffix; + + writeMessage(engine, codec, command, to, suffix + QChar(0x01) + ctcpQuote(ctcpMsg) + QChar(0x01)); +} + +Message Message::parse(Engine *engine, const QTextCodec *codec, bool *parseSuccess) +{ + if (parseSuccess) + *parseSuccess=false; + + if (engine->socket()->canReadLine()) + { + QCString raw(engine->socket()->bytesAvailable()+1); + Q_LONG length = engine->socket()->readLine(raw.data(), raw.count()); + + if( length > -1 ) + { + raw.resize( length ); + + // Remove trailing '\r\n' or '\n'. + // + // Some servers send '\n' instead of '\r\n' that the RFCs say they should be sending. + + if (length > 1 && raw.at(length-2) == '\n') { + raw.at(length-2) = '\0'; + } + if (length > 2 && raw.at(length-3) == '\r') { + raw.at(length-3) = '\0'; + } + + kdDebug(14121) << "<< " << raw << endl; + + Message msg; + if(matchForIRCRegExp(raw, codec, msg)) + { + if(parseSuccess) + *parseSuccess = true; + } + else + { + kdDebug(14120) << k_funcinfo << "Unmatched line: \"" << raw << "\"" << endl; + } + + return msg; + } + else + kdWarning(14121) << k_funcinfo << "Failed to read a line while canReadLine returned true!" << endl; + } + + return Message(); +} + +QString Message::quote(const QString &str) +{ + QString tmp = str; + QChar q('\020'); + tmp.replace(q, q+QString(q)); + tmp.replace(QChar('\r'), q+QString::fromLatin1("r")); + tmp.replace(QChar('\n'), q+QString::fromLatin1("n")); + tmp.replace(QChar('\0'), q+QString::fromLatin1("0")); + return tmp; +} + +// FIXME: The unquote system is buggy. +QString Message::unquote(const QString &str) +{ + QString tmp = str; + + char b[3] = { 020, 020, '\0' }; + const char b2[2] = { 020, '\0' }; + + tmp.replace( b, b2 ); + b[1] = 'r'; + tmp.replace( b, "\r"); + b[1] = 'n'; + tmp.replace( b, "\n"); + b[1] = '0'; + tmp.replace( b, "\0"); + + return tmp; +} + +QString Message::ctcpQuote(const QString &str) +{ + QString tmp = str; + tmp.replace( QChar('\\'), QString::fromLatin1("\\\\")); + tmp.replace( (char)1, QString::fromLatin1("\\1")); + return tmp; +} + +QString Message::ctcpUnquote(const QString &str) +{ + QString tmp = str; + tmp.replace("\\\\", "\\"); + tmp.replace("\\1", "\1" ); + return tmp; +} + +bool Message::matchForIRCRegExp(const QCString &line, const QTextCodec *codec, Message &message) +{ + if(matchForIRCRegExp(m_IRCCommandType1, codec, line, message)) + return true; +#ifdef _IRC_STRICTNESS_ + if(!matchForIRCRegExp(m_IRCCommandType2, codec, line, message)) + return true; +#endif // _IRC_STRICTNESS_ + return false; +} + +// FIXME: remove the decodeStrings calls or update them. +// FIXME: avoid the recursive call, it make the ctcp command unquoted twice (wich is wrong, but valid in most of the cases) +bool Message::matchForIRCRegExp(QRegExp ®exp, const QTextCodec *codec, const QCString &line, Message &msg ) +{ + if( regexp.exactMatch( codec->toUnicode(line) ) ) + { + msg.m_raw = line; + msg.m_prefix = unquote(regexp.cap(1)); + msg.m_command = unquote(regexp.cap(2)); + msg.m_args = QStringList::split(' ', regexp.cap(3)); + + QCString suffix = codec->fromUnicode(unquote(regexp.cap(4))); + if (!suffix.isNull() && suffix.length() > 0) + { + QCString ctcpRaw; + if (extractCtcpCommand(suffix, ctcpRaw)) + { + msg.m_ctcpRaw = codec->toUnicode(ctcpRaw); + + msg.m_ctcpMessage = new Message(); + msg.m_ctcpMessage->m_raw = codec->fromUnicode(ctcpUnquote(msg.m_ctcpRaw)); + + int space = ctcpRaw.find(' '); + if (!matchForIRCRegExp(msg.m_ctcpMessage->m_raw, codec, *msg.m_ctcpMessage)) + { + QCString command; + if (space > 0) + command = ctcpRaw.mid(0, space).upper(); + else + command = ctcpRaw.upper(); + msg.m_ctcpMessage->m_command = + Kopete::Message::decodeString( KSParser::parse(command), codec ); + } + + if (space > 0) + msg.m_ctcpMessage->m_ctcpRaw = + Kopete::Message::decodeString( KSParser::parse(ctcpRaw.mid(space)), codec ); + } + + msg.m_suffix = Kopete::Message::decodeString( KSParser::parse(suffix), codec ); + } + else + msg.m_suffix = QString::null; + return true; + } + return false; +} + +void Message::decodeAgain( const QTextCodec *codec ) +{ + matchForIRCRegExp(m_raw, codec, *this); +} + +// FIXME: there are missing parts +QString Message::toString() const +{ + if( !isValid() ) + return QString::null; + + QString msg = m_command; + for (QStringList::ConstIterator it = m_args.begin(); it != m_args.end(); ++it) + msg += QChar(' ') + *it; + if (!m_suffix.isNull()) + msg += QString::fromLatin1(" :") + m_suffix; + + return msg; +} + +bool Message::isNumeric() const +{ + return m_IRCNumericCommand.exactMatch(m_command); +} + +bool Message::isValid() const +{ +// This could/should be more complex but the message validity is tested durring the parsing +// So this is enougth as we don't allow the editing the content. + return !m_command.isEmpty(); +} + +/* Return true if the given string is a special command string + * (i.e start and finish with the ascii code \001), and the given + * string is splited to get the first part of the message and fill the ctcp command. + * FIXME: The code currently only match for a textual message or a ctcp message not both mixed as it can be (even if very rare). + */ +bool Message::extractCtcpCommand(QCString &message, QCString &ctcpline) +{ + uint len = message.length(); + + if( message[0] == 1 && message[len-1] == 1 ) + { + ctcpline = message.mid(1,len-2); + message.truncate(0); + + return true; + } + + return false; +} + +void Message::dump() const +{ + kdDebug(14120) << "Raw:" << m_raw << endl + << "Prefix:" << m_prefix << endl + << "Command:" << m_command << endl + << "Args:" << m_args << endl + << "Suffix:" << m_suffix << endl + << "CtcpRaw:" << m_ctcpRaw << endl; + if(m_ctcpMessage) + { + kdDebug(14120) << "Contains CTCP Message:" << endl; + m_ctcpMessage->dump(); + } +} diff --git a/kopete/protocols/irc/libkirc/kircmessage.h b/kopete/protocols/irc/libkirc/kircmessage.h new file mode 100644 index 00000000..e37f3fb2 --- /dev/null +++ b/kopete/protocols/irc/libkirc/kircmessage.h @@ -0,0 +1,198 @@ +/* + kircmessage.h - IRC Client + + Copyright (c) 2003 by Michel Hermier + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KIRCMESSAGE_H +#define KIRCMESSAGE_H + +#include "kircentity.h" + +#include + +#include +#include +#include +#include +#include + +#include + +// Uncoment this if you want a really rfc compliant message handling. +// This is due to some changes of the message encoding with 14 arguments.(not very frequent :) +// #define _IRC_STRICTNESS_ + +namespace KIRC +{ + +class Engine; + +class Message +{ +public: + /** \brief Sends the message as-is to the server. + */ + static void writeRawMessage(KIRC::Engine *engine, const QTextCodec *codec, const QString &str); + + static void writeMessage(KIRC::Engine *engine, const QTextCodec *codec, const QString &str); + + static void writeMessage(KIRC::Engine *engine, const QTextCodec *codec, + const QString &command, const QStringList &args, const QString &suffix); + + static void writeCtcpMessage(KIRC::Engine *engine, const QTextCodec *codec, + const QString &command, const QString &to, + const QString &ctcpMessage); + + static void writeCtcpMessage(KIRC::Engine *engine, const QTextCodec *codec, + const QString &command, const QString &to, const QString &suffix, + const QString &ctcpCommand, const QStringList &ctcpArgs = QStringList(), const QString &ctcpSuffix = QString::null ); + + Message(); + Message(const KIRC::Message &obj); + Message(const KIRC::Message *obj); + + ~Message(); + + inline const QString nickFromPrefix() const + { return Kopete::Message::unescape(KIRC::Entity::userNick(m_prefix)); } + + QString toString() const; + + /** \brief Returns true if the message command is numeric. + */ + bool isNumeric() const; + + /** \brief Message is valid if it was parsed correctly. + */ + bool isValid() const; + + /** \brief Writes internal message information about this message through kdDebug(). + */ + void dump() const; + + /** \brief Re-decodes the message with given codec. + */ + void decodeAgain( const QTextCodec *codec ); + + /** \brief The whole message as received. + */ + inline const QCString &raw() const + { return m_raw; } + + /** \brief Prefix of this message. + * + * Returns the prefix of the message. Note that it can be empty. + * + * Prefix is the server name or the nick name of the sender. + * + * message = [ ":" prefix SPACE ] command [ params ] crlf + * prefix = servername / ( nickname [ [ "!" user ] "@" host ] ) + */ + inline const QString &prefix() const + { return m_prefix; } + + /** \brief The command part of this message. + * + * Returns the command of this message. Can be numerical. + * + * Examples: "MODE", "PRIVMSG", 303, 001, ... + */ + inline const QString &command() const + { return m_command; } + + /** \brief The number of command arguments this message contains. + */ + inline size_t argsSize() const + { return m_args.size(); } + + /** \brief i:th command argument. + */ + inline const QString &arg(size_t i) const + { return m_args[i]; } + + /** \brief All command arguments. + */ + inline const QStringList &args() const + { return m_args; } + + /** \brief Message suffix. + */ + inline const QString &suffix() const + { return m_suffix; } + inline const QString &ctcpRaw() const + { return m_ctcpRaw; } + + inline bool hasCtcpMessage() const + { return m_ctcpMessage!=0; } + inline class KIRC::Message &ctcpMessage() const + { return *m_ctcpMessage; } + + static KIRC::Message parse(KIRC::Engine *engine, const QTextCodec *codec, bool *parseSuccess=0); + +private: + /** + * Contains the low level dequoted message. + */ + QCString m_raw; + + /** + * Contains the completely dequoted prefix. + */ + QString m_prefix; + /** + * Contains the completely dequoted command. + */ + QString m_command; + /** + * Contains the completely dequoted args. + */ + QStringList m_args; + /** + * Contains the completely dequoted suffix. + */ + QString m_suffix; + + /** + * If it is a message contains the completely dequoted rawCtcpLine. + * If it is a ctcp message contains the completely dequoted rawCtcpArgsLine. + */ + QString m_ctcpRaw; + + // low level quoting, message quoting + static QString quote(const QString &str); + static QString unquote(const QString &str); + + // ctcp level quoting + static QString ctcpQuote(const QString &str); + static QString ctcpUnquote(const QString &str); + + static bool extractCtcpCommand(QCString &str, QCString &ctcpline); + + static bool matchForIRCRegExp(const QCString &line, const QTextCodec *codec, KIRC::Message &message); + static bool matchForIRCRegExp(QRegExp ®exp, const QTextCodec *codec, const QCString &line, KIRC::Message &message); + + class KIRC::Message *m_ctcpMessage; + + static QRegExp m_IRCCommandType1; + #ifdef _IRC_STRICTNESS_ + static QRegExp m_IRCCommandType2; + #endif // _IRC_STRICTNESS_ + + static QRegExp m_IRCNumericCommand; +}; + +} + +#endif // KIRCMESSAGE_H diff --git a/kopete/protocols/irc/libkirc/kircmessageredirector.cpp b/kopete/protocols/irc/libkirc/kircmessageredirector.cpp new file mode 100644 index 00000000..49194ce0 --- /dev/null +++ b/kopete/protocols/irc/libkirc/kircmessageredirector.cpp @@ -0,0 +1,97 @@ +/* + kircmessageredirector.cpp - IRC Client + + Copyright (c) 2004 by Michel Hermier + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "kircengine.h" +#include "kircmessage.h" +#include "kircmessageredirector.h" + +using namespace KIRC; + +MessageRedirector::MessageRedirector(KIRC::Engine *engine, + int argsSize_min, int argsSize_max, const QString &helpMessage) + : QObject(engine, "KIRC::MessageRedirector"), + m_argsSize_min(argsSize_min), + m_argsSize_max(argsSize_max), + m_helpMessage(helpMessage) +{ +} + +bool MessageRedirector::connect(QObject *object, const char *member) +{ + return QObject::connect(this, SIGNAL(redirect(KIRC::Message &)), + object, member); +} + +QStringList MessageRedirector::operator () (Message &msg) +{ + m_errors.clear(); + +// if (m_connectedObjects == 0) +// m_errors.append(i18n("Internal error: no more connected object, triggered by:")+msg); + + if (checkValidity(msg)) + emit redirect(msg); + + return m_errors; +} + +QString MessageRedirector::helpMessage() +{ + return m_helpMessage; +} + +void MessageRedirector::error(QString &message) +{ + m_errors.append(message); +} + +bool MessageRedirector::checkValidity(const Message &msg) +{ + bool success = true; + int argsSize = msg.argsSize(); + + if (m_argsSize_min >= 0 && argsSize < m_argsSize_min) + { +// m_errors.append(i18n("Not enougth arguments in message:")+msg); + success = false; + } + +#ifdef _IRC_STRICTNESS_ + if (m_argsSize_max >= 0 && argsSize > m_argsSize_max) + { +// m_errors.append(i18n("Too many arguments in message:")+msg); + success = false; + } +#endif +/* + if ( msg.isNumeric() && + ( msg.argsSize() > 0 && ( + msg.arg(0) == m_Nickname || + msg.arg(0) == m_PendingNick || + msg.arg(0) == QString::fromLatin1("*") + ) + ) + ) + { +// m_errors.append(i18n("Too many arguments in message:")+msg); + success = false; + } +*/ + return success; +} + +#include "kircmessageredirector.moc" diff --git a/kopete/protocols/irc/libkirc/kircmessageredirector.h b/kopete/protocols/irc/libkirc/kircmessageredirector.h new file mode 100644 index 00000000..f87a2af6 --- /dev/null +++ b/kopete/protocols/irc/libkirc/kircmessageredirector.h @@ -0,0 +1,86 @@ +/* + kircmessageredirector.h - IRC Client + + Copyright (c) 2004 by Michel Hermier + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KIRC_MESSAGEREDIRECTOR_H +#define KIRC_MESSAGEREDIRECTOR_H + +#include +#include + +namespace KIRC +{ + +class Engine; + +class Message; + +class MessageRedirector + : public QObject +{ + Q_OBJECT + +public: + enum { + Unknown = -1, + Unlimited = -2 + }; + + MessageRedirector(KIRC::Engine *engine, + int argsSize_min = KIRC::MessageRedirector::Unknown, + int argsSize_max = KIRC::MessageRedirector::Unknown, + const QString &helpMessage = QString::null); + + /** + * Connects the given object member signal/slot to this message redirector. + * The member signal slot should be looking like: + * SIGNAL(mysignal(KIRC::Message &msg)) + * or + * SIGNAL(myslot(KIRC::Message &msg)) + */ + bool connect(QObject *object, const char *member); + + /** + * Attempt to send the message. + * @return a not empty QStringList on errors or no slots connected. + * The returned string list contains all the errors. + */ + QStringList operator()(KIRC::Message &msg); + + void error(QString &errorMessage); + + QString helpMessage(); + +signals: + void redirect(KIRC::Message &); + +private: + /** + * Check that the given message as the correct number of args + * and do some message format checks. + */ + bool checkValidity(const KIRC::Message &msg); + + QStringList m_errors; + + int m_argsSize_min; + int m_argsSize_max; + QString m_helpMessage; +}; + +} + +#endif diff --git a/kopete/protocols/irc/libkirc/kirctransfer.cpp b/kopete/protocols/irc/libkirc/kirctransfer.cpp new file mode 100644 index 00000000..2466d6a9 --- /dev/null +++ b/kopete/protocols/irc/libkirc/kirctransfer.cpp @@ -0,0 +1,365 @@ +/* + kirctransfer.cpp - IRC transfer. + + Copyright (c) 2003-2004 by Michel Hermier + + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#include +#include + +#include "kirctransfer.h" + +using namespace KIRC; + +Transfer::Transfer( Engine *engine, QString nick,// QString nick_peer_adress + Type type, + QObject *parent, const char *name ) + : QObject( parent, name ), + m_engine(engine), m_nick(nick), + m_type(type), m_socket(0), + m_initiated(false), + m_file(0), m_fileName(QString::null), m_fileSize(0), m_fileSizeCur(0), m_fileSizeAck(0), + m_receivedBytes(0), m_receivedBytesLimit(0), m_sentBytes(0), m_sentBytesLimit(0) +{ +} + +Transfer::Transfer( Engine *engine, QString nick,// QString nick_peer_adress + Transfer::Type type, + QString fileName, Q_UINT32 fileSize, // put this in a QVariant ? + QObject *parent, const char *name ) + : QObject( parent, name ), + m_engine(engine), m_nick(nick), + m_type(type), m_socket(0), + m_initiated(false), + m_file(0), m_fileName(fileName), m_fileSize(fileSize), m_fileSizeCur(0), m_fileSizeAck(0), + m_receivedBytes(0), m_receivedBytesLimit(0), m_sentBytes(0), m_sentBytesLimit(0) +{ +} + +Transfer::Transfer( Engine *engine, QString nick,// QString nick_peer_adress + QHostAddress hostAdress, Q_UINT16 port, // put this in a QVariant ? + Transfer::Type type, + QString fileName, Q_UINT32 fileSize, // put this in a QVariant ? + QObject *parent, const char *name ) + : QObject( parent, name ), + m_engine(engine), m_nick(nick), + m_type(type), m_socket(0), + m_initiated(false), + m_file(0), m_fileName(fileName), m_fileSize(fileSize), m_fileSizeCur(0), m_fileSizeAck(0), + m_receivedBytes(0), m_receivedBytesLimit(0), m_sentBytes(0), m_sentBytesLimit(0) +{ + setSocket(new KExtendedSocket(hostAdress.toString(), port)); +} +/* +Transfer::Transfer( Engine *engine, QString nick,// QString nick_peer_adress + Transfer::Type type, QVariant properties, + QObject *parent, const char *name ) + : QObject( parent, name ), + m_engine(engine), m_nick(nick), + m_type(type), m_socket(properties[socket]), + m_initiated(false), + m_file(0), m_fileName(properties[fileName]), m_fileSize(properties[fileSize]), m_fileSizeCur(0), m_fileSizeAck(0), + m_receivedBytes(0), m_receivedBytesLimit(0), m_sentBytes(0), m_sentBytesLimit(0) +{ + if(!properites["socket"].isNull()) + setSocket(properites["socket"]); + else if(!properites["hostAddress"].isNull() && !properites["hostPort"].isNull()) + setSocket(new KExtendedSocket(properites["hostAddress"], properites["hostPort"])); + + connect(this, SIGNAL(complete()), + this, SLOT(closeSocket())); + + connect(this, SIGNAL(abort(QString)), + this, SLOT(closeSocket())); +} +*/ +Transfer::~Transfer() +{ + closeSocket(); + // m_file is automatically closed on destroy. +} + +Transfer::Status Transfer::status() const +{ + if(m_socket) + { +// return (Transfer::Status)m_socket->socketStatus(); + return Connected; + } + return Error_NoSocket; +} + +void Transfer::slotError( int error ) +{ + // Connection in progress.. This is a signal fired wrong + if (m_socket->socketStatus () != KExtendedSocket::connecting) + { + abort(KExtendedSocket::strError(m_socket->status(), m_socket->systemError())); +// closeSocket(); + } +} + +bool Transfer::initiate() +{ + QTimer *timer = 0; + + if(m_initiated) + { + kdDebug(14121) << k_funcinfo << "Transfer allready initiated" << endl; + return false; + } + + if(!m_socket) + { + kdDebug(14121) << k_funcinfo << "Socket not set" << endl; + return false; + } + + m_initiated = true; + + m_file.setName(m_fileName); + + connect(this, SIGNAL(complete()), + this, SLOT(closeSocket())); + connect(this, SIGNAL(abort(QString)), + this, SLOT(closeSocket())); + +// connect(m_socket, SIGNAL(connectionClosed()), +// this, SLOT(slotConnectionClosed())); +// connect(m_socket, SIGNAL(delayedCloseFinished()), +// this, SLOT(slotConnectionClosed())); + connect(m_socket, SIGNAL(error(int)), // FIXME: connection failed: No such signal KExtendedSocket::error(int) + this, SLOT(slotError(int))); + + switch( m_type ) + { + case Chat: + kdDebug(14121) << k_funcinfo << "Stting up a chat." << endl; + connect(m_socket, SIGNAL(readyRead()), + this, SLOT(readyReadFileIncoming())); + break; + case FileIncoming: + kdDebug(14121) << k_funcinfo << "Stting up an incoming file transfer." << endl; + m_file.open(IO_WriteOnly); + connect(m_socket, SIGNAL(readyRead()), + this, SLOT(readyReadFileIncoming())); + break; + case FileOutgoing: + kdDebug(14121) << k_funcinfo << "Stting up an outgoing file transfer." << endl; + m_file.open(IO_ReadOnly); + connect(m_socket, SIGNAL(readyRead()), + this, SLOT(readyReadFileOutgoing())); +// timer = new QTimer(this); +// connect(timer, SIGNAL(timeout()), +// this, SLOT(writeFileOutgoing())); +// timer->start(1000, false); + writeFileOutgoing(); // send a first packet. + break; + default: + kdDebug(14121) << k_funcinfo << "Closing transfer: Unknown extra initiation for type:" << m_type << endl; + m_socket->close(); + return false; + break; + } + +// if(status()==Idle) + if(m_socket->status()==KExtendedSocket::nothing) + m_socket->connect(); + + m_socket->enableRead(true); + m_socket->enableWrite(true); + + m_socketDataStream.setDevice(m_socket); + + // I wonder if calling this is really necessary + // As far as I understand, buffer (socket buffer at least) should be flushed while event-looping. + // But I'm not really sure of this, so I force the flush. + timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), + this, SLOT(flush())); + timer->start(1000, FALSE); // flush the streams at every seconds + + return true; +} + +bool Transfer::setSocket( KExtendedSocket *socket ) +{ + if (!m_socket) + { + m_socket = socket; + return true; + } + else + kdDebug(14121) << k_funcinfo << "Socket allready set" << endl; + return false; +} + +void Transfer::closeSocket() +{ + if(m_socket) + { + m_socket->close(); +// m_socket->reset(); + m_socket->deleteLater(); + } + m_socket = 0; +} + +/* + * This slot ensure that all the stream are flushed. + * This slot is called periodically internaly. + */ + void Transfer::flush() +{ + /* + * Enure the incoming file content in case of a crash. + */ + if(m_file.isOpen() && m_file.isWritable()) + m_file.flush(); + + /* + * Ensure that non interactive streams outputs (i.e file transfer acknowledge by example) + * are sent (Don't stay in a local buffer). + */ + if(m_socket && status() == Connected) + m_socket->flush(); +} + +void Transfer::userAbort(QString msg) +{ + emit abort(msg); +} + +void Transfer::setCodec( QTextCodec *codec ) +{ + switch( m_type ) + { + case Chat: + m_socket_textStream.setCodec( codec ); + break; + default: +// operation not permitted on this type. + break; + } +} + +void Transfer::writeLine( const QString &line ) +{ + switch( m_type ) + { + case Chat: +// m_socket.flush(); + break; + default: +// operation not permitted on this type. + break; + } +} + +void Transfer::readyReadLine() +{ + if( m_socket->canReadLine() ) + { + QString msg = m_socket_textStream.readLine(); + emit readLine(msg); + } +} + +void Transfer::readyReadFileIncoming() +{ + kdDebug(14121) << k_funcinfo << endl; + + m_bufferLength = m_socket->readBlock(m_buffer, sizeof(m_buffer)); + + if(m_bufferLength > 0) + { + int written = m_file.writeBlock(m_buffer, m_bufferLength); + if(m_bufferLength == written) + { + m_fileSizeCur += written; + m_fileSizeAck = m_fileSizeCur; + m_socketDataStream << m_fileSizeAck; + checkFileTransferEnd(m_fileSizeAck); + return; + } + else + // Something bad happened while writting. + abort(m_file.errorString()); + } + else if(m_bufferLength == -1) + abort("Error while reading socket."); +} + +void Transfer::readyReadFileOutgoing() +{ + kdDebug(14121) << k_funcinfo << "Available bytes:" << m_socket->bytesAvailable() << endl; + + bool hadData = false; + Q_UINT32 fileSizeAck = 0; + +// if (m_socket->bytesAvailable() >= sizeof(fileSizeAck)) // BUGGY: bytesAvailable() that allways return 0 on unbuffered sockets. + { + m_socketDataStream >> fileSizeAck; + hadData = true; + } + + if (hadData) + { + checkFileTransferEnd(fileSizeAck); + writeFileOutgoing(); + } +} + +void Transfer::writeFileOutgoing() +{ + kdDebug(14121) << k_funcinfo << endl; + + if (m_fileSizeAck < m_fileSize) + { + m_bufferLength = m_file.readBlock(m_buffer, sizeof(m_buffer)); + if (m_bufferLength > 0) + { + Q_UINT32 read = m_socket->writeBlock(m_buffer, m_bufferLength); // should check written == read + +// if(read != m_buffer_length) +// buffer is not cleared still + + m_fileSizeCur += read; +// m_socket->flush(); // Should think on using this + emit fileSizeCurrent( m_fileSizeCur ); + } + else if(m_bufferLength == -1) + abort("Error while reading file."); + } +} + +void Transfer::checkFileTransferEnd(Q_UINT32 fileSizeAck) +{ + kdDebug(14121) << k_funcinfo << "Acknowledged:" << fileSizeAck << endl; + + m_fileSizeAck = fileSizeAck; + emit fileSizeAcknowledge(m_fileSizeAck); + + if(m_fileSizeAck > m_fileSize) + abort(i18n("Acknowledge size is greater than the expected file size")); + + if(m_fileSizeAck == m_fileSize) + emit complete(); +} + +#include "kirctransfer.moc" diff --git a/kopete/protocols/irc/libkirc/kirctransfer.h b/kopete/protocols/irc/libkirc/kirctransfer.h new file mode 100644 index 00000000..3453f5cb --- /dev/null +++ b/kopete/protocols/irc/libkirc/kirctransfer.h @@ -0,0 +1,191 @@ +/* + kirctransfer.h - DCC Handler + + Copyright (c) 2003 by Michel Hermier + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KIRCTRANSFER_H +#define KIRCTRANSFER_H + +#include +#include +#include +#include +#include + +class KExtendedSocket; + +class QFile; +class QTextCodec; + +namespace KIRC +{ +class Engine; + +class Transfer + : public QObject +{ + Q_OBJECT + +public: + enum Type { + Unknown, + Chat, + FileOutgoing, + FileIncoming + }; + + enum Status { + Error_NoSocket = -2, + Error = -1, + Idle = 0, + HostLookup, + Connecting, + Connected, + Closed + }; +public: + Transfer( KIRC::Engine *engine, QString nick,// QString nick_peer_adress + Type type = Unknown, + QObject *parent = 0L, const char *name = 0L ); + + Transfer( KIRC::Engine *engine, QString nick,// QString nick_peer_adress, + QHostAddress peer_address, Q_UINT16 peer_port, + Transfer::Type type, + QObject *parent = 0L, const char *name = 0L ); + + Transfer( KIRC::Engine *engine, QString nick,// QString nick_peer_adress, + Transfer::Type type, + QString fileName, Q_UINT32 fileSize, + QObject *parent = 0L, const char *name = 0L ); + + Transfer( KIRC::Engine *engine, QString nick,// QString nick_peer_adress, + QHostAddress peer_address, Q_UINT16 peer_port, + Transfer::Type type, + QString fileName, Q_UINT32 fileSize, + QObject *parent = 0L, const char *name = 0L ); +/* + For a file transfer properties are: + + KExntendedSocket *socket + or + QHostAddress peerAddress + Q_UINT16 peerPort + for determining the socket. + + QString fileName + Q_UINT32 fileSize + for detemining the file propeties. +*//* + Transfer( KIRC *engine, QString nick,// QString nick_peer_adress, + Transfer::Type type, QVariant properties, + QObject *parent = 0L, const char *name = 0L ); +*/ + ~Transfer(); + + KIRC::Engine *engine() const + { return m_engine; } + QString nick() const + { return m_nick; } + Type type() const + { return m_type; } + Status status() const; + + /* Start the transfer. + * If not connected connect to client. + * Allow receiving/emitting data. + */ + bool initiate(); + + QString fileName() const + { return m_fileName; } + /* Change the file name. + */ + void setFileName(QString fileName) + { m_fileName = fileName; } + unsigned long fileSize() const + { return m_fileSize; } +public slots: + bool setSocket( KExtendedSocket *socket ); + void closeSocket(); + + void setCodec( QTextCodec *codec ); + void writeLine( const QString &msg ); + + void flush(); + + void userAbort(QString); + +signals: + void readLine( const QString &msg ); + + void fileSizeCurrent( unsigned int ); + void fileSizeAcknowledge( unsigned int ); + +// void received(Q_UINT32); +// void sent(Q_UINT32); + + void abort(QString); + + /* Emited when the transfer is complete. + * Usually it means that the file transfer has successfully finished. + */ + void complete(); + +protected slots: + void slotError(int); + + void readyReadLine(); + + void readyReadFileIncoming(); + + void writeFileOutgoing(); + void readyReadFileOutgoing(); + +protected: +// void emitSignals(); + void checkFileTransferEnd( Q_UINT32 fileSizeAck ); + + KIRC::Engine * m_engine; + QString m_nick; + + Type m_type; + KExtendedSocket *m_socket; + bool m_initiated; + + // Text member data + QTextStream m_socket_textStream; +// QTextCodec * m_socket_codec; + + // File member data + QFile m_file; + QString m_fileName; + Q_UINT32 m_fileSize; + Q_UINT32 /*usize_t*/ m_fileSizeCur; + Q_UINT32 /*usize_t*/ m_fileSizeAck; + QDataStream m_socketDataStream; + char m_buffer[1024]; + int m_bufferLength; + + // Data transfer measures + Q_UINT32 m_receivedBytes; + Q_UINT32 m_receivedBytesLimit; + + Q_UINT32 m_sentBytes; + Q_UINT32 m_sentBytesLimit; +}; + +} + +#endif diff --git a/kopete/protocols/irc/libkirc/kirctransferhandler.cpp b/kopete/protocols/irc/libkirc/kirctransferhandler.cpp new file mode 100644 index 00000000..3fa73dff --- /dev/null +++ b/kopete/protocols/irc/libkirc/kirctransferhandler.cpp @@ -0,0 +1,97 @@ +/* + kirctransferhandler.cpp - DCC Handler + + Copyright (c) 2003 by Michel Hermier + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +#include +#include +#include + +#include "kirctransferserver.h" + +#include "kirctransferhandler.h" + +using namespace KIRC; + +TransferHandler *TransferHandler::self() +{ + static TransferHandler sm_self; + return &sm_self; +} + +TransferServer *TransferHandler::server() +{ + if( m_server ) +// server( m_default_server_port, m_default_server_backlog ); + server( 0, 1 ); + return m_server; +} + +TransferServer *TransferHandler::server( Q_UINT16 port, int backlog ) +{ +// if( m_server ) +// m_server->terminate(); + TransferServer *m_server = new TransferServer( port, backlog, this ); + + // here connect the slots of the server + + return m_server; +} + +TransferServer *TransferHandler::createServer(Engine *engine, QString m_userName, + Transfer::Type type, + QString fileName, Q_UINT32 fileSize) +{ + TransferServer *server = new TransferServer(engine, m_userName, type, fileName, fileSize, this); + transferServerCreated(server); + return server; +} + +Transfer *TransferHandler::createClient( + Engine *engine, QString nick,// QString nick_peer_adress, + QHostAddress peer_address, Q_UINT16 peer_port, + Transfer::Type type, + QString fileName, Q_UINT32 fileSize ) +{ + Transfer *client = new Transfer( + engine, nick,// QString nick_peer_adress, + peer_address, peer_port, + type, + fileName, fileSize, + this ); + transferCreated(client); + return client; +} + +/* +File *DCCHandler::openFile( QString file, int mode = IO_ReadWrite ) +{ + QFile *file = new QFile(filename); + if (!file->open(mode)) + { + delete file; + file = 0L; + } + return file; +} +*/ + +#include "kirctransferhandler.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/protocols/irc/libkirc/kirctransferhandler.h b/kopete/protocols/irc/libkirc/kirctransferhandler.h new file mode 100644 index 00000000..81774c02 --- /dev/null +++ b/kopete/protocols/irc/libkirc/kirctransferhandler.h @@ -0,0 +1,79 @@ +/* + kirctransferhandler.h - DCC Handler + + Copyright (c) 2003-2004 by Michel Hermier + + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KIRCTRANSFERHANDLER_H +#define KIRCTRANSFERHANDLER_H + +#include + +#include "kirctransfer.h" +#include "kirctransferserver.h" + +class QFile; +class QTextCodec; + +class KExtendedSocket; + +namespace KIRC +{ + +class TransferHandler + : public QObject +{ + Q_OBJECT + +public: + static TransferHandler *self(); + + TransferServer *server(); + TransferServer *server( Q_UINT16 port, int backlog = 1 ); + + TransferServer *createServer(KIRC::Engine *engine, QString m_userName, + Transfer::Type type, + QString fileName, Q_UINT32 fileSize); + + Transfer *createClient( + KIRC::Engine *engine, QString nick,// QString nick_peer_adress, + QHostAddress peer_address, Q_UINT16 peer_port, + Transfer::Type type, + QString file = QString::null, Q_UINT32 fileSize = 0 ); + +// void registerServer( DCCServer * ); +// QPtrList getRegisteredServers(); +// static QPtrList getAllRegisteredServers(); +// void unregisterServer( DCCServer * ); + +// void registerClient( DCCClient * ); +// QPtrList getRegisteredClients(); +// static QPtrList getAllRegisteredClients(); +// void unregisterClient( DCCClient * ); + +signals: + void transferServerCreated(KIRC::TransferServer *server); + void transferCreated(KIRC::Transfer *transfer); + +private: +// TransferHandler(); + + TransferServer *m_server; +// QPtrList m_servers; +// QPtrList m_clients; +}; + +} + +#endif diff --git a/kopete/protocols/irc/libkirc/kirctransferserver.cpp b/kopete/protocols/irc/libkirc/kirctransferserver.cpp new file mode 100644 index 00000000..96cc66fb --- /dev/null +++ b/kopete/protocols/irc/libkirc/kirctransferserver.cpp @@ -0,0 +1,154 @@ +/* + kirctransfer.cpp - IRC transfer. + + Copyright (c) 2003 by Michel Hermier + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include + +#include "kirctransferhandler.h" + +#include "kirctransferserver.h" + +using namespace KIRC; + +/* +TransferServer::TransferServer( QObject *parent, const char *name ) + : QObject( parent, name ), + m_socket( 0 ), + m_port( 0 ), + m_backlog( 1 ) +{ +} +*/ +TransferServer::TransferServer(Q_UINT16 port, int backlog, QObject *parent, const char *name) + : QObject( parent, name ), + m_socket( 0 ), + m_port( port ), + m_backlog( backlog ) +{ +} + +TransferServer::TransferServer(Engine *engine, QString nick,// QString nick_peer_adress, + Transfer::Type type, + QString fileName, Q_UINT32 fileSize, + QObject *parent, const char *name) + : QObject( parent, name ), + m_socket(0), + m_port(0), + m_backlog(1), + m_engine(engine), + m_nick(nick), + m_type(type), + m_fileName(fileName), + m_fileSize(fileSize) +{ + initServer(); +} + +TransferServer::~TransferServer() +{ + if (m_socket) + delete m_socket; +} + +bool TransferServer::initServer() +{ + if (!m_socket) + { + QObject::connect(this, SIGNAL(incomingNewTransfer(Transfer *)), + TransferHandler::self(), SIGNAL(transferCreated(Transfer *))); + + m_socket = new KExtendedSocket(); + +// m_socket->setHost(m_socket->localAddress()->nodeName()); + if (!m_socket->setPort(m_port)) + kdDebug(14120) << k_funcinfo << "Failed to set port to" << m_port << endl; + m_socket->setSocketFlags(KExtendedSocket::noResolve + |KExtendedSocket::passiveSocket + |KExtendedSocket::inetSocket ); + + if (!m_socket->setTimeout(2*60)) // FIXME: allow configuration of this. + kdDebug(14120) << k_funcinfo << "Failed to set timeout." << endl; + + QObject::connect(m_socket, SIGNAL(readyAccept()), + this, SLOT(readyAccept())); + QObject::connect(m_socket, SIGNAL(connectionFailed(int)), + this, SLOT(connectionFailed(int))); + + m_socket->listen(m_backlog); + m_socket->setBlockingMode(true); + + const KInetSocketAddress *localAddress = static_cast(m_socket->localAddress()); + if (!localAddress) + { + kdDebug(14120) << k_funcinfo << "Not a KInetSocketAddress." << endl; + deleteLater(); + return false; + } + + m_port = localAddress->port(); + } + return (m_socket->socketStatus() != KExtendedSocket::error); +} + +bool TransferServer::initServer( Q_UINT16 port, int backlog ) +{ + if (m_socket) + { + m_port = port; + m_backlog = backlog; + } + return initServer(); +} + +void TransferServer::readyAccept() +{ + KExtendedSocket *socket; + m_socket->accept( socket ); + Transfer *transfer = new Transfer(m_engine, m_nick, m_type, m_fileName, m_fileSize); + transfer->setSocket(socket); + transfer->initiate(); + emit incomingNewTransfer(transfer); +} + +void TransferServer::connectionFailed(int error) +{ + if (error!=0) + { + kdDebug(14120) << k_funcinfo << "Connection failed with " << m_nick << endl; + deleteLater(); + } +} +/* +void Transfer::initClient() +{ + if(!m_socket) + { + connect(m_socket, SIGNAL(connectionClosed()), + this, SLOT(slotConnectionClosed())); + connect(m_socket, SIGNAL(delayedCloseFinished()), + this, SLOT(slotConnectionClosed())); + connect(m_socket, SIGNAL(error(int)), + this, SLOT(slotError(int))); + connect(m_socket, SIGNAL(readyRead()), + this, SLOT(readyReadFileOut)); + + m_socket->enableRead( true ); + m_socket->enableWrite( true ); + } +} +*/ +#include "kirctransferserver.moc" diff --git a/kopete/protocols/irc/libkirc/kirctransferserver.h b/kopete/protocols/irc/libkirc/kirctransferserver.h new file mode 100644 index 00000000..8ac016ef --- /dev/null +++ b/kopete/protocols/irc/libkirc/kirctransferserver.h @@ -0,0 +1,81 @@ +/* + kirctransfer.h - DCC Handler + + Copyright (c) 2003-2004 by Michel Hermier + + Kopete (c) 2003-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef KIRCTRANSFERSERVER_H +#define KIRCTRANSFERSERVER_H + +#include "kirctransfer.h" + +#include + +class KExtendedSocket; + +class QFile; +class QTextCodec; + +namespace KIRC +{ + +class TransferServer + : public QObject +{ + Q_OBJECT + +public: +// TransferServer(QObject *parent = 0, const char *name = 0); + TransferServer(Q_UINT16 port, int backlog = 1, QObject *parent = 0, const char *name = 0); + TransferServer(KIRC::Engine *engine, QString nick,// QString nick_peer_adress, + Transfer::Type type, + QString fileName, Q_UINT32 fileSize, + QObject *parent = 0, const char *name = 0); + + ~TransferServer(); + + int port() + { return m_port; } + +protected: + bool initServer(); + bool initServer( Q_UINT16 port, int backlog = 1 ); + +signals: + void incomingNewTransfer(Transfer *transfer); + +protected slots: + void readyAccept(); + void connectionFailed(int error); + +private: + KExtendedSocket * m_socket; + Q_UINT16 m_port; + int m_backlog; + + // The following will be deprecated ... + KIRC::Engine * m_engine; + QString m_nick; + Transfer::Type m_type; + QString m_fileName; + Q_UINT32 m_fileSize; + // by + // QPtrList m_pendingTransfers; + // QPtrList m_activeTransfers; + +}; + +} + +#endif diff --git a/kopete/protocols/irc/libkirc/ksslsocket.cpp b/kopete/protocols/irc/libkirc/ksslsocket.cpp new file mode 100644 index 00000000..fb2d5161 --- /dev/null +++ b/kopete/protocols/irc/libkirc/ksslsocket.cpp @@ -0,0 +1,458 @@ +/* + ksslsocket.cpp - KDE SSL Socket + + Copyright (c) 2005 by Tommi Rantala + Copyright (c) 2004 by Jason Keirstead + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ksslsocket.h" + +struct KSSLSocketPrivate +{ + mutable KSSL *kssl; + KSSLCertificateCache *cc; + DCOPClient *dcc; + QMap metaData; + QSocketNotifier *socketNotifier; +}; + +KSSLSocket::KSSLSocket() : KExtendedSocket() +{ + d = new KSSLSocketPrivate; + d->kssl = 0; + d->dcc = KApplication::kApplication()->dcopClient(); + d->cc = new KSSLCertificateCache; + d->cc->reload(); + + //No blocking + setBlockingMode(false); + + //Connect internal slots + QObject::connect( this, SIGNAL(connectionSuccess()), this, SLOT(slotConnected()) ); + QObject::connect( this, SIGNAL(closed(int)), this, SLOT(slotDisconnected()) ); + QObject::connect( this, SIGNAL(connectionFailed(int)), this, SLOT(slotDisconnected())); +} + +KSSLSocket::~KSSLSocket() +{ + //Close connection + closeNow(); + + if( d->kssl ) + { + d->kssl->close(); + delete d->kssl; + } + + delete d->cc; + + delete d; +} + +Q_LONG KSSLSocket::readBlock( char* data, Q_ULONG maxLen ) +{ + //Re-implemented because KExtSocket doesn't use this when not in buffered mode + Q_LONG retval = consumeReadBuffer(maxLen, data); + + if( retval == 0 ) + { + if (sockfd == -1) + return 0; + + retval = -1; + } + + return retval; +} + +int KSSLSocket::peekBlock( char* data, uint maxLen ) +{ + //Re-implemented because KExtSocket doesn't use this when not in buffered mode + if( socketStatus() < connected ) + return -2; + + if( sockfd == -1 ) + return -2; + + return consumeReadBuffer(maxLen, data, false); +} + +Q_LONG KSSLSocket::writeBlock( const char* data, Q_ULONG len ) +{ + return d->kssl->write( data, len ); +} + +int KSSLSocket::bytesAvailable() const +{ + if( socketStatus() < connected ) + return -2; + + //Re-implemented because KExtSocket doesn't use this when not in buffered mode + return KBufferedIO::bytesAvailable(); +} + +void KSSLSocket::slotReadData() +{ + kdDebug(14120) << k_funcinfo << d->kssl->pending() << endl; + QByteArray buff(512); + int bytesRead = d->kssl->read( buff.data(), 512 ); + + //Fill the read buffer + feedReadBuffer( bytesRead, buff.data() ); + emit readyRead(); +} + +void KSSLSocket::slotConnected() +{ + if (!KSSL::doesSSLWork()) { + kdError(14120) << k_funcinfo << "SSL not functional!" << endl; + + closeNow(); + emit sslFailure(); + return; + } + + delete d->kssl; + d->kssl = new KSSL(); + + if (d->kssl->connect( sockfd ) != 1) { + kdError(14120) << k_funcinfo << "SSL connect() failed." << endl; + + closeNow(); + emit sslFailure(); + return; + } + + //Disconnect the KExtSocket notifier slot, we use our own + QObject::disconnect( readNotifier(), SIGNAL(activated( int )), + this, SLOT(socketActivityRead()) ); + + QObject::connect( readNotifier(), SIGNAL(activated( int )), + this, SLOT(slotReadData()) ); + + readNotifier()->setEnabled(true); + + if (verifyCertificate() != 1) { + closeNow(); + emit certificateRejected(); + return; + } + + emit certificateAccepted(); +} + +void KSSLSocket::slotDisconnected() +{ + kdDebug(14120) << k_funcinfo << "Disconnected" << endl; + + if( readNotifier() ) + readNotifier()->setEnabled(false); + + delete d->kssl; + d->kssl = 0L; +} + +void KSSLSocket::showInfoDialog() +{ + if( socketStatus() == connected ) + { + if (!d->dcc->isApplicationRegistered("kio_uiserver")) + { + KApplication::startServiceByDesktopPath("kio_uiserver.desktop",QStringList()); + } + + QByteArray data, ignore; + QCString ignoretype; + QDataStream arg(data, IO_WriteOnly); + arg << "irc://" + peerAddress()->pretty() + ":" + port() << d->metaData; + d->dcc->call("kio_uiserver", "UIServer", + "showSSLInfoDialog(QString,KIO::MetaData)", data, ignoretype, ignore); + } +} + +void KSSLSocket::setMetaData( const QString &key, const QVariant &data ) +{ + QVariant v = data; + d->metaData[key] = v.asString(); +} + +bool KSSLSocket::hasMetaData( const QString &key ) +{ + return d->metaData.contains(key); +} + +QString KSSLSocket::metaData( const QString &key ) +{ + if( d->metaData.contains(key) ) + return d->metaData[key]; + return QString::null; +} + +/* +I basically copied the below from tcpKIO::SlaveBase.hpp, with some modificaions and formatting. + + * Copyright (C) 2000 Alex Zepeda + * Copyright (C) 2001 Dawit Alemayehu +*/ + +int KSSLSocket::messageBox( KIO::SlaveBase::MessageBoxType type, const QString &text, const QString &caption, + const QString &buttonYes, const QString &buttonNo ) +{ + kdDebug(14120) << "messageBox " << type << " " << text << " - " << caption << buttonYes << buttonNo << endl; + QByteArray data, result; + QCString returnType; + QDataStream arg(data, IO_WriteOnly); + arg << (int)1 << (int)type << text << caption << buttonYes << buttonNo; + + if (!d->dcc->isApplicationRegistered("kio_uiserver")) + { + KApplication::startServiceByDesktopPath("kio_uiserver.desktop",QStringList()); + } + + d->dcc->call("kio_uiserver", "UIServer", + "messageBox(int,int,QString,QString,QString,QString)", data, returnType, result); + + if( returnType == "int" ) + { + int res; + QDataStream r(result, IO_ReadOnly); + r >> res; + return res; + } + else + return 0; // communication failure +} + + +// Returns 0 for failed verification, -1 for rejected cert and 1 for ok +int KSSLSocket::verifyCertificate() +{ + int rc = 0; + bool permacache = false; + bool _IPmatchesCN = false; + int result; + bool doAddHost = false; + QString ourHost = host(); + QString ourIp = peerAddress()->pretty(); + + QString theurl = "irc://" + ourHost + ":" + port(); + + if (!d->cc) + d->cc = new KSSLCertificateCache; + + KSSLCertificate& pc = d->kssl->peerInfo().getPeerCertificate(); + + KSSLCertificate::KSSLValidationList ksvl = pc.validateVerbose(KSSLCertificate::SSLServer); + + _IPmatchesCN = d->kssl->peerInfo().certMatchesAddress(); + + if (!_IPmatchesCN) + { + ksvl << KSSLCertificate::InvalidHost; + } + + KSSLCertificate::KSSLValidation ksv = KSSLCertificate::Ok; + if (!ksvl.isEmpty()) + ksv = ksvl.first(); + + /* Setting the various bits of meta-info that will be needed. */ + setMetaData("ssl_cipher", d->kssl->connectionInfo().getCipher()); + setMetaData("ssl_cipher_desc", d->kssl->connectionInfo().getCipherDescription()); + setMetaData("ssl_cipher_version", d->kssl->connectionInfo().getCipherVersion()); + setMetaData("ssl_cipher_used_bits", QString::number(d->kssl->connectionInfo().getCipherUsedBits())); + setMetaData("ssl_cipher_bits", QString::number(d->kssl->connectionInfo().getCipherBits())); + setMetaData("ssl_peer_ip", ourIp ); + + QString errorStr; + for(KSSLCertificate::KSSLValidationList::ConstIterator it = ksvl.begin(); + it != ksvl.end(); ++it) + { + errorStr += QString::number(*it)+":"; + } + + setMetaData("ssl_cert_errors", errorStr); + setMetaData("ssl_peer_certificate", pc.toString()); + + if (pc.chain().isValid() && pc.chain().depth() > 1) + { + QString theChain; + QPtrList chain = pc.chain().getChain(); + for (KSSLCertificate *c = chain.first(); c; c = chain.next()) + { + theChain += c->toString(); + theChain += "\n"; + } + setMetaData("ssl_peer_chain", theChain); + } + else + { + setMetaData("ssl_peer_chain", ""); + } + + setMetaData("ssl_cert_state", QString::number(ksv)); + + if (ksv == KSSLCertificate::Ok) + { + rc = 1; + setMetaData("ssl_action", "accept"); + } + + // Since we're the parent, we need to teach the child. + setMetaData("ssl_parent_ip", ourIp ); + setMetaData("ssl_parent_cert", pc.toString()); + + // - Read from cache and see if there is a policy for this + KSSLCertificateCache::KSSLCertificatePolicy cp = d->cc->getPolicyByCertificate(pc); + + // - validation code + if (ksv != KSSLCertificate::Ok) + { + if( cp == KSSLCertificateCache::Unknown || cp == KSSLCertificateCache::Ambiguous) + { + cp = KSSLCertificateCache::Prompt; + } + else + { + // A policy was already set so let's honor that. + permacache = d->cc->isPermanent(pc); + } + + if (!_IPmatchesCN && cp == KSSLCertificateCache::Accept) + { + cp = KSSLCertificateCache::Prompt; + } + + // Precondition: cp is one of Reject, Accept or Prompt + switch (cp) + { + case KSSLCertificateCache::Accept: + rc = 1; + break; + + case KSSLCertificateCache::Reject: + rc = -1; + break; + + case KSSLCertificateCache::Prompt: + { + do + { + if (ksv == KSSLCertificate::InvalidHost) + { + QString msg = i18n("The IP address of the host %1 " + "does not match the one the " + "certificate was issued to."); + result = messageBox( KIO::SlaveBase::WarningYesNoCancel, + msg.arg(ourHost), + i18n("Server Authentication"), + i18n("&Details"), + i18n("Co&ntinue") ); + } + else + { + QString msg = i18n("The server certificate failed the " + "authenticity test (%1)."); + result = messageBox( KIO::SlaveBase::WarningYesNoCancel, + msg.arg(ourHost), + i18n("Server Authentication"), + i18n("&Details"), + i18n("Co&ntinue") ); + } + + if (result == KMessageBox::Yes) + { + showInfoDialog(); + } + } + while (result == KMessageBox::Yes); + + if (result == KMessageBox::No) + { + rc = 1; + cp = KSSLCertificateCache::Accept; + doAddHost = true; + result = messageBox( KIO::SlaveBase::WarningYesNo, + i18n("Would you like to accept this " + "certificate forever without " + "being prompted?"), + i18n("Server Authentication"), + i18n("&Forever"), + i18n("&Current Sessions Only")); + if (result == KMessageBox::Yes) + permacache = true; + else + permacache = false; + } + else + { + rc = -1; + cp = KSSLCertificateCache::Prompt; + } + + break; + } + default: + kdDebug(14120) << "SSL error in cert code." + << "Please report this to kopete-devel@kde.org." + << endl; + break; + } + } + + // - cache the results + d->cc->addCertificate(pc, cp, permacache); + if (doAddHost) + d->cc->addHost(pc, ourHost); + + + if (rc == -1) + return rc; + + + kdDebug(14120) << "SSL connection information follows:" << endl + << "+-----------------------------------------------" << endl + << "| Cipher: " << d->kssl->connectionInfo().getCipher() << endl + << "| Description: " << d->kssl->connectionInfo().getCipherDescription() << endl + << "| Version: " << d->kssl->connectionInfo().getCipherVersion() << endl + << "| Strength: " << d->kssl->connectionInfo().getCipherUsedBits() + << " of " << d->kssl->connectionInfo().getCipherBits() + << " bits used." << endl + << "| PEER:" << endl + << "| Subject: " << d->kssl->peerInfo().getPeerCertificate().getSubject() << endl + << "| Issuer: " << d->kssl->peerInfo().getPeerCertificate().getIssuer() << endl + << "| Validation: " << (int)ksv << endl + << "| Certificate matches IP: " << _IPmatchesCN << endl + << "+-----------------------------------------------" + << endl; + + // sendMetaData(); Do not call this function!! + return rc; +} + + +#include "ksslsocket.moc" diff --git a/kopete/protocols/irc/libkirc/ksslsocket.h b/kopete/protocols/irc/libkirc/ksslsocket.h new file mode 100644 index 00000000..692d5288 --- /dev/null +++ b/kopete/protocols/irc/libkirc/ksslsocket.h @@ -0,0 +1,68 @@ + +#ifndef _K_SSL_SOCKET_H_ +#define _K_SSL_SOCKET_H_ + +/* + ksslsocket.h - KDE SSL Socket + + Copyright (c) 2005 by Tommi Rantala + Copyright (c) 2004 by Jason Keirstead + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include +#include +#include + +class KSSLSocketPrivate; + +class KSSLSocket : public KExtendedSocket +{ + Q_OBJECT + + public: + KSSLSocket(); + ~KSSLSocket(); + + Q_LONG readBlock( char* data, Q_ULONG maxLen ); + Q_LONG writeBlock( const char* data, Q_ULONG len ); + int peekBlock( char *data, uint maxLen ); + int bytesAvailable() const; + + void showInfoDialog(); + + signals: + void sslFailure(); + void certificateAccepted(); + void certificateRejected(); + + private slots: + void slotConnected(); + void slotDisconnected(); + void slotReadData(); + + private: + int verifyCertificate(); + int messageBox( KIO::SlaveBase::MessageBoxType type, const QString &text, + const QString &caption, const QString &buttonYes, const QString &buttonNo ); + + + //Copied frm tcpslavebase to simply integrating their dialog function + void setMetaData( const QString &, const QVariant & ); + bool hasMetaData( const QString & ); + QString metaData( const QString & ); + + KSSLSocketPrivate *d; +}; + +#endif diff --git a/kopete/protocols/irc/ui/Makefile.am b/kopete/protocols/irc/ui/Makefile.am new file mode 100644 index 00000000..854a7398 --- /dev/null +++ b/kopete/protocols/irc/ui/Makefile.am @@ -0,0 +1,12 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libkopeteircui.la +AM_CPPFLAGS = $(KOPETE_INCLUDES) \ + -I$(srcdir)/..\ + -I$(srcdir)/../libkirc \ + $(all_includes) + + +libkopeteircui_la_SOURCES = ircadd.ui empty.cpp irceditaccountwidget.cpp \ + irceditaccount.ui channellist.cpp channellistdialog.cpp networkconfig.ui +EXTRA_DIST = ircadd.ui ircprefs.ui empty.cpp diff --git a/kopete/protocols/irc/ui/channellist.cpp b/kopete/protocols/irc/ui/channellist.cpp new file mode 100644 index 00000000..5c66ede0 --- /dev/null +++ b/kopete/protocols/irc/ui/channellist.cpp @@ -0,0 +1,346 @@ +/* + channellist.cpp - IRC Channel Search Widget + + Copyright (c) 2004 by Jason Keirstead + + Kopete (c) 2002-2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "channellist.h" + +#include "kircengine.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ChannelListItem : public KListViewItem +{ + public: + ChannelListItem( KListView *parent, QString arg1, QString arg2, QString arg3 ); + virtual int compare( QListViewItem *i, int col, bool ascending ) const; + virtual void paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ); + + private: + KListView *parentList; +}; + +ChannelListItem::ChannelListItem( KListView *parent, QString arg1, QString arg2, QString arg3 ) : + KListViewItem( parent, parent->lastItem() ), parentList( parent ) +{ + setText(0, arg1); + setText(1, arg2); + setText(2, arg3); +} + +int ChannelListItem::compare( QListViewItem *i, int col, bool ascending ) const +{ + if( col == 1 ) + { + if( text(1).toUInt() < i->text(1).toUInt() ) + return -1; + else if ( text(1).toUInt() == i->text(1).toUInt() ) + return 0; + else + return 1; + } + else + return QListViewItem::compare( i, col, ascending ); +} + +void ChannelListItem::paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int align ) +{ + QPixmap back( width, height() ); + QPainter paint( &back ); + //KListViewItem::paintCell( &paint, cg, column, width, align ); + // PASTED FROM KLISTVIEWITEM: + // set the alternate cell background colour if necessary + QColorGroup _cg = cg; + if (isAlternate()) + if (listView()->viewport()->backgroundMode()==Qt::FixedColor) + _cg.setColor(QColorGroup::Background, static_cast< KListView* >(listView())->alternateBackground()); + else + _cg.setColor(QColorGroup::Base, static_cast< KListView* >(listView())->alternateBackground()); + // PASTED FROM QLISTVIEWITEM + { + QPainter *p = &paint; + + QListView *lv = listView(); + if ( !lv ) + return; + QFontMetrics fm( p->fontMetrics() ); + + // any text we render is done by the Components, not by this class, so make sure we've nothing to write + QString t; + + // removed text truncating code from Qt - we do that differently, further on + + int marg = lv->itemMargin(); + int r = marg; + // const QPixmap * icon = pixmap( column ); + + const BackgroundMode bgmode = lv->viewport()->backgroundMode(); + const QColorGroup::ColorRole crole = QPalette::backgroundRoleFromMode( bgmode ); + + if ( _cg.brush( crole ) != lv->colorGroup().brush( crole ) ) + p->fillRect( 0, 0, width, height(), _cg.brush( crole ) ); + else + { + // all copied from QListView::paintEmptyArea + + //lv->paintEmptyArea( p, QRect( 0, 0, width, height() ) ); + QStyleOption opt( lv->sortColumn(), 0 ); // ### hack; in 3.1, add a property in QListView and QHeader + QStyle::SFlags how = QStyle::Style_Default; + if ( lv->isEnabled() ) + how |= QStyle::Style_Enabled; + + lv->style().drawComplexControl( QStyle::CC_ListView, + p, lv, QRect( 0, 0, width, height() ), lv->colorGroup(), + how, QStyle::SC_ListView, QStyle::SC_None, + opt ); + } + + + + if ( isSelected() && + (column == 0 || lv->allColumnsShowFocus()) ) { + p->fillRect( r - marg, 0, width - r + marg, height(), + _cg.brush( QColorGroup::Highlight ) ); + // removed text pen setting code from Qt + } + + // removed icon drawing code from Qt + + // draw the tree gubbins + if ( multiLinesEnabled() && column == 0 && isOpen() && childCount() ) { + int textheight = fm.size( align, t ).height() + 2 * lv->itemMargin(); + textheight = QMAX( textheight, QApplication::globalStrut().height() ); + if ( textheight % 2 > 0 ) + textheight++; + if ( textheight < height() ) { + int w = lv->treeStepSize() / 2; + lv->style().drawComplexControl( QStyle::CC_ListView, p, lv, + QRect( 0, textheight, w + 1, height() - textheight + 1 ), _cg, + lv->isEnabled() ? QStyle::Style_Enabled : QStyle::Style_Default, + QStyle::SC_ListViewExpand, + (uint)QStyle::SC_All, QStyleOption( this ) ); + } + } + } + // END OF PASTE + + + //do you see a better way to tell the TextComponent we are selected ? - Olivier 2004-09-02 + if ( isSelected() ) + _cg.setColor(QColorGroup::Text , _cg.highlightedText() ); + + QSimpleRichText myrichtext( text(column), paint.font() ); + myrichtext.draw( &paint, 0, 0, paint.window(), _cg ); + + paint.end(); + p->drawPixmap( 0, 0, back ); +} + +ChannelList::ChannelList( QWidget* parent, KIRC::Engine *engine ) + : QWidget( parent ), m_engine( engine ) +{ + ChannelListLayout = new QVBoxLayout( this, 11, 6, "ChannelListLayout"); + + layout72_2 = new QHBoxLayout( 0, 0, 6, "layout72_2"); + + textLabel1_2 = new QLabel( this, "textLabel1_2" ); + layout72_2->addWidget( textLabel1_2 ); + + channelSearch = new QLineEdit( this, "channelSearch" ); + layout72_2->addWidget( channelSearch ); + + numUsers = new QSpinBox( 0, 32767, 1, this, "num_users" ); + numUsers->setSuffix( i18n(" members") ); + layout72_2->addWidget( numUsers ); + + mSearchButton = new QPushButton( this, "mSearchButton" ); + layout72_2->addWidget( mSearchButton ); + ChannelListLayout->addLayout( layout72_2 ); + + mChannelList = new KListView( this, "mChannelList" ); + mChannelList->addColumn( i18n( "Channel" ) ); + mChannelList->addColumn( i18n( "Users" ) ); + mChannelList->header()->setResizeEnabled( FALSE, mChannelList->header()->count() - 1 ); + mChannelList->addColumn( i18n( "Topic" ) ); + mChannelList->setAllColumnsShowFocus( TRUE ); + mChannelList->setShowSortIndicator( TRUE ); + ChannelListLayout->addWidget( mChannelList ); + + clearWState( WState_Polished ); + + textLabel1_2->setText( i18n( "Search for:" ) ); + QToolTip::add( textLabel1_2, i18n( "You may search for channels on the IRC server for a text string entered here." ) ); + QToolTip::add( numUsers, i18n( "Channels returned must have at least this many members." ) ); + QWhatsThis::add( numUsers, i18n( "Channels returned must have at least this many members." ) ); + QWhatsThis::add( textLabel1_2, i18n( "You may search for channels on the IRC server for a text string entered here. For instance, you may type 'linux' to find channels that have something to do with linux." ) ); + QToolTip::add( channelSearch, i18n( "You may search for channels on the IRC server for a text string entered here." ) ); + QWhatsThis::add( channelSearch, i18n( "You may search for channels on the IRC server for a text string entered here. For instance, you may type 'linux' to find channels that have something to do with linux." ) ); + mSearchButton->setText( i18n( "S&earch" ) ); + QToolTip::add( mSearchButton, i18n( "Perform a channel search." ) ); + QWhatsThis::add( mSearchButton, i18n( "Perform a channel search. Please be patient, as this can be slow depending on the number of channels on the server." ) ); + QToolTip::add( mChannelList, i18n( "Double click on a channel to select it." ) ); + mChannelList->header()->setLabel( 0, i18n( "Channel" ) ); + mChannelList->header()->setLabel( 1, i18n( "Users" ) ); + mChannelList->header()->setLabel( 2, i18n( "Topic" ) ); + + // signals and slots connections + connect( mChannelList, SIGNAL( doubleClicked(QListViewItem*) ), + this, SLOT( slotItemDoubleClicked(QListViewItem*) ) ); + + connect( mSearchButton, SIGNAL( clicked() ), this, SLOT( search() ) ); + + connect( mChannelList, SIGNAL( selectionChanged( QListViewItem*) ), this, + SLOT( slotItemSelected( QListViewItem *) ) ); + + connect( m_engine, SIGNAL( incomingListedChan( const QString &, uint, const QString & ) ), + this, SLOT( slotChannelListed( const QString &, uint, const QString & ) ) ); + + connect( m_engine, SIGNAL( incomingEndOfList() ), this, SLOT( slotListEnd() ) ); + + connect( m_engine, SIGNAL( statusChanged(KIRC::Engine::Status) ), + this, SLOT( slotStatusChanged(KIRC::Engine::Status) ) ); + + show(); +} + +void ChannelList::slotItemDoubleClicked( QListViewItem *i ) +{ + emit channelDoubleClicked( i->text(0) ); +} + +void ChannelList::slotItemSelected( QListViewItem *i ) +{ + emit channelSelected( i->text(0) ); +} + +void ChannelList::slotStatusChanged(KIRC::Engine::Status newStatus) +{ + switch(newStatus) { + case KIRC::Engine::Connected: + this->reset(); + break; + case KIRC::Engine::Disconnected: + if (mSearching) { + KMessageBox::queuedMessageBox( + this, KMessageBox::Error, + i18n("You have been disconnected from the IRC server."), + i18n("Disconnected"), 0 + ); + } + + slotListEnd(); + break; + default: + break; + } +} + +void ChannelList::reset() +{ + channelCache.clear(); + clear(); +} + +void ChannelList::clear() +{ + mChannelList->clear(); + channelSearch->clear(); + channelSearch->setFocus(); +} + +void ChannelList::search() +{ + if( m_engine->isConnected() || !channelCache.isEmpty() ) + { + mChannelList->clear(); + mChannelList->setSorting( -1 ); + mSearchButton->setEnabled(false); + mSearch = channelSearch->text(); + mSearching = true; + mUsers = numUsers->value(); + + if( channelCache.isEmpty() ) + m_engine->list(); + else + { + cacheIterator = channelCache.begin(); + slotSearchCache(); + } + } + else + { + KMessageBox::queuedMessageBox( + this, KMessageBox::Error, + i18n("You must be connected to the IRC server to perform a channel listing."), + i18n("Not Connected"), 0 + ); + } +} + +void ChannelList::slotChannelListed( const QString &channel, uint users, const QString &topic ) +{ + checkSearchResult( channel, users, topic ); + channelCache.insert( channel, QPair< uint, QString >( users, topic ) ); +} + +void ChannelList::checkSearchResult( const QString &channel, uint users, const QString &topic ) +{ + if( ( mUsers == 0 || mUsers <= users ) && + ( mSearch.isEmpty() || channel.contains( mSearch, false ) || topic.contains( mSearch, false ) ) + ) + { + new ChannelListItem( mChannelList, channel, QString::number(users), topic ); + } +} + +void ChannelList::slotSearchCache() +{ + if( cacheIterator != channelCache.end() ) + { + checkSearchResult( cacheIterator.key(), cacheIterator.data().first, cacheIterator.data().second ); + ++cacheIterator; + QTimer::singleShot( 0, this, SLOT( slotSearchCache() ) ); + } + else + { + slotListEnd(); + } +} + +void ChannelList::slotListEnd() +{ + mChannelList->setSorting(0, true); + mSearchButton->setEnabled(true); + mSearching = false; +} + +#include "channellist.moc" diff --git a/kopete/protocols/irc/ui/channellist.h b/kopete/protocols/irc/ui/channellist.h new file mode 100644 index 00000000..c6f435a0 --- /dev/null +++ b/kopete/protocols/irc/ui/channellist.h @@ -0,0 +1,80 @@ + /* + channellist.h - IRC Channel Search Widget + + Copyright (c) 2004 by Jason Keirstead + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef CHANNELLIST_H +#define CHANNELLIST_H + +#include +#include +#include + +#include "kircengine.h" + +class QVBoxLayout; +class QHBoxLayout; +class QGridLayout; +class QLabel; +class QLineEdit; +class QPushButton; +class KListView; +class QSpinBox; +class QListViewItem; + +class ChannelList + : public QWidget +{ + Q_OBJECT + + public: + ChannelList( QWidget *parent, KIRC::Engine *engine ); + + public slots: + void search(); + void reset(); + void clear(); + + signals: + void channelDoubleClicked( const QString &channel ); + void channelSelected( const QString &channel ); + + private slots: + void slotItemDoubleClicked( QListViewItem * i ); + void slotItemSelected( QListViewItem * i ); + void slotChannelListed( const QString & channel, uint users, const QString & topic ); + void slotListEnd(); + void slotSearchCache(); + void slotStatusChanged( KIRC::Engine::Status ); + + private: + void checkSearchResult( const QString & channel, uint users, const QString & topic ); + + QLabel* textLabel1_2; + QLineEdit* channelSearch; + QSpinBox* numUsers; + QPushButton* mSearchButton; + KListView* mChannelList; + QVBoxLayout* ChannelListLayout; + QHBoxLayout* layout72_2; + KIRC::Engine *m_engine; + bool mSearching; + QString mSearch; + uint mUsers; + QMap< QString, QPair< uint, QString > > channelCache; + QMap< QString, QPair< uint, QString > >::const_iterator cacheIterator; +}; + +#endif diff --git a/kopete/protocols/irc/ui/channellistdialog.cpp b/kopete/protocols/irc/ui/channellistdialog.cpp new file mode 100644 index 00000000..46128730 --- /dev/null +++ b/kopete/protocols/irc/ui/channellistdialog.cpp @@ -0,0 +1,61 @@ +/* + channellistdialog.cpp - IRC Channel Search Dialog + + Copyright (c) 2004 by Jason Keirstead + Copyright (c) 2005 by Michel Hermier + + Kopete (c) 2002-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "channellistdialog.h" + +#include "kircengine.h" + +#include "kopeteuiglobal.h" + +#include "qlayout.h" + +ChannelListDialog::ChannelListDialog(KIRC::Engine *engine, const QString &caption, QObject *target, const char* slotJoinChan) + : KDialogBase(Kopete::UI::Global::mainWidget(), "channel_list_widget", false, caption, Close) +{ + m_engine = engine; + m_list = new ChannelList( this, engine ); + + connect( m_list, SIGNAL( channelDoubleClicked( const QString & ) ), + target, slotJoinChan ); + + connect( m_list, SIGNAL( channelDoubleClicked( const QString & ) ), + this, SLOT( slotChannelDoubleClicked( const QString & ) ) ); + + new QHBoxLayout( m_list, 0, spacingHint() ); + + setInitialSize( QSize( 500, 400 ) ); + setMainWidget( m_list ); + show(); +} + +void ChannelListDialog::clear() +{ + m_list->clear(); +} + +void ChannelListDialog::search() +{ + m_list->search(); +} + +void ChannelListDialog::slotChannelDoubleClicked( const QString & ) +{ + close(); +} + +#include "channellistdialog.moc" diff --git a/kopete/protocols/irc/ui/channellistdialog.h b/kopete/protocols/irc/ui/channellistdialog.h new file mode 100644 index 00000000..2bb85f5b --- /dev/null +++ b/kopete/protocols/irc/ui/channellistdialog.h @@ -0,0 +1,45 @@ + /* + channellist.h - IRC Channel Search Widget + + Copyright (c) 2004 by Jason Keirstead + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef CHANNELLISTDIALOG_H +#define CHANNELLISTDIALOG_H + +#include "channellist.h" + +#include "kdialogbase.h" + +class ChannelListDialog + : public KDialogBase +{ + Q_OBJECT + + public: + ChannelListDialog(KIRC::Engine *engine, const QString &caption, QObject *target, const char* slotJoinChan); + + void clear(); + + void search(); + + private slots: + void slotChannelDoubleClicked( const QString & ); + + private: + KIRC::Engine *m_engine; + ChannelList *m_list; +}; + +#endif diff --git a/kopete/protocols/irc/ui/empty.cpp b/kopete/protocols/irc/ui/empty.cpp new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/kopete/protocols/irc/ui/empty.cpp @@ -0,0 +1 @@ + diff --git a/kopete/protocols/irc/ui/ircadd.ui b/kopete/protocols/irc/ui/ircadd.ui new file mode 100644 index 00000000..f1025112 --- /dev/null +++ b/kopete/protocols/irc/ui/ircadd.ui @@ -0,0 +1,163 @@ + +ircAddUI + + + ircAddUI + + + + 0 + 0 + 389 + 350 + + + + + unnamed + + + 0 + + + 6 + + + + tabWidget3 + + + + tab + + + &Add Contact + + + + unnamed + + + 6 + + + 6 + + + + layout70 + + + + unnamed + + + + TextLabel1 + + + N&ickname/channel to add: + + + addID + + + The name of the IRC contact or channel you would like to add. + + + The name of the IRC contact or channel you would like to add. You may type simply the text of a person's nickname, or you may type a channel name, preceded by a pound sign ('#'). + + + + + addID + + + The name of the IRC contact or channel you would like to add. + + + The name of the IRC contact or channel you would like to add. You may type simply the text of a person's nickname, or you may type a channel name, preceded by a pound sign ('#') + + + + + + + textLabel3 + + + <i>(for example: joe_bob or #somechannel)</i> + + + AlignVCenter|AlignRight + + + + + spacer25 + + + Vertical + + + Expanding + + + + 20 + 110 + + + + + + + + tab + + + &Search Channels + + + + unnamed + + + + hbox + + + + + + + + + + QHBox +
    qhbox.h
    + + -1 + -1 + + 0 + + 5 + 5 + 0 + 0 + + image0 +
    +
    + + + 789c9d97c76e24490e86effd1442f3d65870d2451a0ce6206f5adeb4cc620f8c34f2553225b5a4c1befb46927fe6a1d4c0ccac4287fa8a0c26834193f5dbb785b3fd9d856fbf7d799ec9ecba5ea8afe469e15bf3727ffffeeffffcf1e797af49b2d0ffc7d142f2f55f5fbe1ecc16ea85dde9a4ed81290045faa77ca49cf4ab67ba1e3953969173651ab9d4fdf1c8a27c3872ad7c3c72d3b32c2a67c3f3444636fbef23eb7e590267e68fcc4656395d8dacf6d938ef97eaef289781cddebdb2846fccff44b989ba58e3411f3dc75158e6df1d3889539517ca49bf54fe43398d1dec4f46567f685fd9c539f43fc025f80c1c3ce8d93f28e77185f8bf0c6cfae4c0b5f9c3c6e5c0542a4b5876fe13708df39d2bd7716372aa8c93c4e4723bb2f977aadc26ceec4bdb7310e6b0bfab9c2445ecd49f1370694cebca65d260ffa6711a41aef14d24e94cee6be3348e0ae5e9c8765f07ca3e8db17f0f9c825794eb3489351fe9bb7217e4969f29388bd51ee9fda5715a19730696b852fea95c6471647ca95cf64bcf43e0cef4657b647bfe6acf213d6bb32f9a9f5992b5a64f9a8f990b6cf9ecc15d6cf9acf6b2da55a8a75cb973dee4b2d1b38b5c05de02434e87e01aacfb351df5bebddea74b5c67f9c795711ea15e357e2ecde358fb87efc0a867df8cac728a074e62e527706afb59f3cf6583be5c28bb3c35f693812d1fbdc6dbe57966fec932d899ffa4f7e38adca17e34beaeca4b9c271a18f1d5fc739257b0d70d9c68be7bed7fcee7827ab91cd8e4740cf6b19d4ffb87ab07b9bf516e72d4a3ac821b9cef6e60d84f47367ded2fae1d9f7704f6163f79000ffde27660f387ed3c5dbf54dfeebb0bf6acbe6b706bfa5ef32f8f8b18f5bf0f463f20edb77956a4e68fbc80b3c4facd163847be6bfde72ec86dbeac82715fbc0286be683cf3bc081d44f7df80d344fb0b6b7de64581fb916765297263d6fccd9b7ee97e566e8b06cfdb1f59cf2bda2f8bbc2c11df0c5c41aefdb328ca22b1feb0012eedbce24736f91b18fb796d64eb87cb60817dcdafb07d906bfd14d22f659d8785ef97b2f6d7b2df6ef1d6fb2babaac6f92e8c25b27ecc1f239b7f3a5fca5a8678ea7c2d9bc0e6ef1238b3fca2c7814d5f4cbf9334b5f833384bed3c7a5f5514f4adbe2ec07962e7590457f06f6f649b977afe2a16f42f5e070bfc591e18f7a5f1a812a9e0df39d827da4fbd8c6ccfd7785569bf945f953371a9d5a3de67950bfa2d75c63eb2fb15bdcfaaf011e6c7263846bf8f4636f91618f3c7d3c0f047fb69550efab2074e12bd6fd27957553ec33c5b043bcc7f9d0795f818fdb501a3df8ae673e507ff69021eea37063bc45ffb73550736ff36c0a84f3e0317a86f8b5f139e6ffe1f821de6d9127898ff3be00a7c3ab2cd03e3d697560ff40016e47b3bb2e96bbd559d8f32bbff4b7065f193042c98873acf240af6adbf3c83c5f2954bb0c7bcd77c107d81d2fdebc63e33ff640aaecc9e1c8151df5c803dfaadc64b52dfa03ed64636ffb4ff4b2867f453ede7227586fcd3f9264ded91fffafe236d5da37fe83c91ceb7b9e5b7f6731ff92ed7f747d6fbf7e185cf58347e3e6932e47b3430fa893edf87d795c2fc3f003b67f5f902cec17abfde0dfa3c053b3c5ffb832fc2eb8fddcf233887fc195c801f46b6f3cdc02558fba72f7d2d1a7f7e321ee58fe0cad86bbff24dd3e2fe74fef836b0c6eb60d62fa6bf5e07b3419f853dd7dc70fb8bd5f1255fd90ed30f9f3c5ff30ddff21ddff384a7fcc08ffcc4cf61cdf8855ff9e79c7e1db4dff89d3f7891977899577895d7789d377893b7f83b6fcfe937bc13b477798ff7f9800ff928ac633ee11f7cca6761d7f99c7e1b3cb908da11c7413be1943376e153cc39175c72f549ff9e17c31744429e6a6aa8a58e2ee98aaee9866e7f617fc24b7417a4f734a1293dd0233dd133cd8285177aa5f9f3b63ca5377aa78f607b919668995682e62aadd17ab0b1419b9ff41f682b48bed336edd02eed05ed7d5ea3033a0cdf1ed1f127fd273ae123fa41a774a6b685cee982228a837e42e927fd47caf8985c38651eb40b2ac38e4a5842658a97fab33fd248cb87d2c9a55cc9b5dcc82d1fc99ddccb44a6f230af2f8ff224cf417f262ff22a3fe54ddee543166549966545567f617f4dd66523dc6b2c9bb225df655b7682f692ecca9eeccfe97772c09b722847722c27c1f3eb70f66bf9116c9fca999ccbc59cfe25bf4a143a5ef8992561324a78bd92522acf9ebc78efe7cf7b153276db37bef59dbff457fedadff85b7f27abfede4ffcd4cf9ff76faeff4fffefeff8c7f5fedfdfbffc0fa355c495 + + + + addID + tabWidget3 + + +
    diff --git a/kopete/protocols/irc/ui/irceditaccount.ui b/kopete/protocols/irc/ui/irceditaccount.ui new file mode 100644 index 00000000..682e9be9 --- /dev/null +++ b/kopete/protocols/irc/ui/irceditaccount.ui @@ -0,0 +1,1022 @@ + +IRCEditAccountBase + + + IRCEditAccountBase + + + + 0 + 0 + 689 + 528 + + + + + 3 + 3 + 0 + 0 + + + + + 440 + 575 + + + + + unnamed + + + + tabWidget2 + + + + 3 + 3 + 0 + 0 + + + + + tab + + + B&asic Setup + + + + unnamed + + + + spacer8 + + + Vertical + + + Expanding + + + + 20 + 150 + + + + + + textLabel3 + + + + 3 + 1 + 0 + 0 + + + + + 32767 + 32767 + + + + <p><b>Note:</b> Most IRC servers do not require a password, and only a nickname is required to connect</p> + + + WordBreak|AlignTop + + + + + groupBox59 + + + GroupBoxPanel + + + Sunken + + + Account Information + + + + unnamed + + + + textLabel4 + + + N&ickname: + + + mNickName + + + This is the name that everyone will see everytime you say something + + + + + textLabel1_2 + + + Alternate ni&ckname: + + + mAltNickname + + + When the nickname is already in use when connecting, this name will be used instead + + + + + mNickName + + + + 7 + 0 + 0 + 0 + + + + This is the name that everyone will see everytime you say something + + + The alias you would like to use on IRC. You may change this once online with the /nick command. + + + + + mAltNickname + + + When the nickname is already in use when connecting, this name will be used instead + + + When the nickname is already in use when connecting, this name will be used instead + + + + + mPasswordWidget + + + + + m_realNameLabel + + + &Real name: + + + m_realNameLineEdit + + + + + textLabel5 + + + &Username: + + + mUserName + + + The username you would prefer to use on IRC, if your system does not have identd support. Leave blank to use your system account name. + + + + + mUserName + + + + 7 + 0 + 0 + 0 + + + + Normal + + + The username you would prefer to use on IRC, if your system does not have identd support. Leave blank to use your system account name. + + + The username you would prefer to use on IRC, if your system does not have identd support. Leave blank to use your system account name. + + + + + m_realNameLineEdit + + + + 7 + 0 + 0 + 0 + + + + Normal + + + The username you would prefer to use on IRC, if your system does not have identd support. + + + The username you would prefer to use on IRC, if your system does not have identd support. Leave blank to use your system account name. + + + + + + + + + TabPage + + + Connection + + + + unnamed + + + + layout21 + + + + unnamed + + + + layout19 + + + + unnamed + + + + description + + + + + + + + spacer11 + + + Horizontal + + + Expanding + + + + 161 + 20 + + + + + + + + layout20 + + + + unnamed + + + + network + + + + + editButton + + + &Edit... + + + + + spacer1 + + + Horizontal + + + Expanding + + + + 392 + 20 + + + + + + + + textLabel1_3 + + + &Network: + + + network + + + + + + + groupBox1 + + + + 3 + 0 + 0 + 0 + + + + Connection Preferences + + + + unnamed + + + + preferSSL + + + &Prefer SSL-based connections + + + + + autoConnect + + + E&xclude from connect all + + + If you check that case, the account will not be connected when you press the "Connect All" button, or at startup even if you selected to automatically connect at startup + + + + + layout25 + + + + unnamed + + + + textLabel1_2_2 + + + Default &charset: + + + charset + + + + + charset + + + + + spacer6_2 + + + Horizontal + + + Expanding + + + + 141 + 20 + + + + + + + + + + groupBox5 + + + Default Messages + + + + unnamed + + + + textLabel1 + + + &Part message: + + + partMessage + + + + + textLabel2 + + + &Quit message: + + + quitMessage + + + + + partMessage + + + The message you want people to see when you part a channel without giving a reason. Leave this field blank to use the Kopete default message. + + + The message you want people to see when you part a channel without giving a reason. Leave this field blank to use the Kopete default message. + + + + + quitMessage + + + The message you want people to see when you disconnect from IRC without giving a reason. Leave this field blank to use the Kopete default message. + + + The message you want people to see when you disconnect from IRC without giving a reason. Leave this field blank to use the Kopete default message. + + + + + + + spacer72 + + + Vertical + + + Expanding + + + + 20 + 150 + + + + + + + + tab + + + A&dvanced Configuration + + + + unnamed + + + + groupBox7 + + + Message Destinations + + + + unnamed + + + + autoShowAnonWindows + + + Auto-show anonymous windows + + + + + autoShowServerWindow + + + Auto-show the server window + + + + + layout19 + + + + unnamed + + + + textLabel1_4 + + + Server messages: + + + + + textLabel4_3 + + + Server notices: + + + + + + Active Window + + + + + Server Window + + + + + Anonymous Window + + + + + KNotify + + + + + Ignore + + + + serverNotices + + + 1 + + + + + + Active Window + + + + + Server Window + + + + + Anonymous Window + + + + + KNotify + + + + + Ignore + + + + serverMessages + + + 1 + + + + + + + layout23 + + + + unnamed + + + + textLabel3_3 + + + Error messages: + + + + + + Active Window + + + + + Server Window + + + + + Anonymous Window + + + + + KNotify + + + + + Ignore + + + + informationReplies + + + + + textLabel2_2 + + + Information replies: + + + + + + Active Window + + + + + Server Window + + + + + Anonymous Window + + + + + KNotify + + + + + Ignore + + + + errorMessages + + + + + + + + + groupBox6 + + + + 3 + 3 + 0 + 0 + + + + + 0 + 130 + + + + Custom CTCP Replies + + + + unnamed + + + + + CTCP + + + true + + + true + + + + + Reply + + + true + + + true + + + + ctcpList + + + + 7 + 2 + 0 + 0 + + + + false + + + Accept + + + true + + + true + + + You can use this dialog to add custom replies for when people send CTCP requests to you. You can also use this dialog to override the built-in replies for VERSION, USERINFO, and CLIENTINFO. + + + + + layout153 + + + + unnamed + + + + textLabel3_2 + + + &CTCP: + + + newCTCP + + + + + newCTCP + + + + + textLabel4_2 + + + &Reply: + + + newReply + + + + + newReply + + + + + addReply + + + Add Repl&y + + + + + + + + + groupBox60 + + + + 3 + 3 + 0 + 0 + + + + + 0 + 130 + + + + Run Following Commands on Connect + + + + unnamed + + + + layout29 + + + + unnamed + + + + commandEdit + + + + 3 + 0 + 0 + 0 + + + + + + addButton + + + Add Co&mmand + + + + + + + + Command + + + true + + + true + + + + commandList + + + + 7 + 2 + 0 + 0 + + + + true + + + Accept + + + true + + + true + + + Any commands added here will be run as soon as you are connected to the IRC server. + + + Any commands added here will be run as soon as you are connected to the IRC server. + + + + + + + + + + + + Kopete::UI::PasswordWidget +
    kopetepasswordwidget.h
    + + 50 + 50 + + 0 + + 1 + 0 + 0 + 0 + + image0 + changed() +
    +
    + + + 89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b0000030149444154388db59531681b5718c77f0e377c070e3c810a3a70e0041eac51852e0a19e45134830a1d9a4c69a04bc8928e990a693a640e1d0c8642b08742321894c1507991b484c890902bb8701a047760c3bd21701fe4201dde49b6a41a32b8df72dcbbeffdbefffbbfefbd5b1b0c07cce266ebe667ae2006c3c1dada0cdc3be87d6e6c35b0d692a409d9c7ec8b20d65ae29398d19b1114e7e3de4ce98b3f5e10dc0053cf0951b4506496e1b964bf7ce6c585d9054c62d01d617ca48be0596553cf496d8f2c8b01c5f795fc93904e85ec4c01a152857a5d9175d0b2805c872080f18595ccc1499a10a225d4e2fbc2877786fe81253ab6c04c8d106e09db5d43ab0d146e5c64d1a23938fb98a185cea1c33eecfd9eba49eb427dcb201e245365f2b7b2fb5b4a3a31dcb927178afe07d86901df870fefa4842aed6f6b74ba42e52b4014d580e1eb9cbd9d94de7e4aad16d2f9be02d805f0b5e532f927a1ffcacea1777f122a8105b164a7c25faf323a5d9f1f1fd600e1e5bec59e2d4b5c7ef5209d0ad17b8b31864e57c0b3e0815ac3ee33253ab664a770ff5185d1a1cb8d2267d3e58aa1dc7d2508cbe597d0e74fdd269aaaf0f52d414c4ea3e9762c996869e42560d7a72e41c4799a2586e74f95e8d8151481fa86efbe7b3398ac58b1a2b8527589f15451ad303ac2293542ad6648a796278f13a27185e4c4754310facb98c53a79e19a3fdc1426ff28c3d7399d1f7cb25343eb96106cf83c790ce9c4f2eb831855c55485663327992eb6dc8a6259874ed700b0b793323cccb9ffa842b30d6133e3e75fea989ac15a8b16ca76b746b0b92278d919774c5b6d48a78697fb29bbcf52468742a32120909c24e899ce67beed5be2db01e22d1e9485bb620e47f9ee9e606a21bd3f5d3744c7e7c54d55e87443867d8b554515ac5db4620e8e4f62263170fd1cdee90aad7640141992891b0f367c9adfe4049bb07d3b7022bd8c687c0978f46684ee084150b65ac1fcca94591b7a90a496e4c095164fb016a2b192a497795cc0f84817aebe25f7bf70ccc54a575c555c03f78ffa5fc0570d1f0c076bff0232285a09643cc7ce0000000049454e44ae426082 + + + + tabWidget2 + mNickName + mAltNickname + mUserName + m_realNameLineEdit + network + editButton + preferSSL + autoConnect + charset + partMessage + quitMessage + serverMessages + serverNotices + informationReplies + errorMessages + autoShowServerWindow + autoShowAnonWindows + ctcpList + newCTCP + newReply + addReply + commandList + commandEdit + addButton + + + + klistview.h + +
    diff --git a/kopete/protocols/irc/ui/irceditaccountwidget.cpp b/kopete/protocols/irc/ui/irceditaccountwidget.cpp new file mode 100644 index 00000000..4a1e6ed3 --- /dev/null +++ b/kopete/protocols/irc/ui/irceditaccountwidget.cpp @@ -0,0 +1,282 @@ +/* + irceditaccountwidget.cpp - IRC Account Widget + + Copyright (c) 2005 by Tommi Rantala + Copyright (c) 2003 by Olivier Goffart + Copyright (c) 2003 by Jason Keirstead + Kopete (c) 2003-2005 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "irceditaccountwidget.h" + +#include "ircaccount.h" +#include "ircusercontact.h" +#include "ircprotocol.h" +#include "kcodecaction.h" + +#include "kircengine.h" + +#include "kopetepasswordwidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +IRCEditAccountWidget::IRCEditAccountWidget(IRCProtocol *proto, IRCAccount *ident, QWidget *parent, const char * ) + : IRCEditAccountBase(parent), KopeteEditAccountWidget(ident) +{ + mProtocol = proto; + + // default charset/encoding for new accounts: utf-8, see http://www.iana.org/assignments/character-sets + int currentCodec = 106; + + if( account() ) + { + QString nickName = account()->mySelf()->nickName(); + QString serverInfo = account()->accountId(); + + mNickName->setText( nickName ); + mAltNickname->setText( account()->altNick() ); + mUserName->setText( account()->userName() ); + m_realNameLineEdit->setText( account()->realName() ); + + partMessage->setText( account()->defaultPart() ); + quitMessage->setText( account()->defaultQuit() ); + if( account()->codec() ) + currentCodec = account()->codec()->mibEnum(); + + mPasswordWidget->load ( &account()->password() ); + + preferSSL->setChecked(account()->configGroup()->readBoolEntry("PreferSSL")); + autoShowServerWindow->setChecked( account()->configGroup()->readBoolEntry("AutoShowServerWindow") ); + autoConnect->setChecked( static_cast(account())->excludeConnect() ); + + KConfigGroup *config = account()->configGroup(); + + serverNotices->setCurrentItem( config->readNumEntry( "ServerNotices", IRCAccount::ServerWindow ) - 1 ); + serverMessages->setCurrentItem( config->readNumEntry( "ServerMessages", IRCAccount::ServerWindow ) - 1 ); + informationReplies->setCurrentItem( config->readNumEntry( "InformationReplies", IRCAccount::ActiveWindow ) - 1 ); + errorMessages->setCurrentItem( config->readNumEntry( "ErrorMessages", IRCAccount::ActiveWindow ) - 1 ); + + QStringList cmds = account()->connectCommands(); + for( QStringList::Iterator i = cmds.begin(); i != cmds.end(); ++i ) + new QListViewItem( commandList, *i ); + + const QMap< QString, QString > replies = account()->customCtcpReplies(); + for( QMap< QString, QString >::ConstIterator it = replies.begin(); it != replies.end(); ++it ) + new QListViewItem( ctcpList, it.key(), it.data() ); + } + + mUserName->setValidator( new QRegExpValidator( QString::fromLatin1("^[^\\s]*$"), mUserName ) ); + mNickName->setValidator( new QRegExpValidator( QString::fromLatin1("^[^#+&][^\\s]*$"), mNickName ) ); + mAltNickname->setValidator( new QRegExpValidator( QString::fromLatin1("^[^#+&][^\\s]*$"), mAltNickname ) ); + + charset->insertStringList( KCodecAction::supportedEncodings() ); + + for (int i = 0; i < charset->count(); ++i) { + QString encoding = KGlobal::charsets()->encodingForName(charset->text(i)); + + if (KGlobal::charsets()->codecForName(encoding)->mibEnum() == currentCodec) { + charset->setCurrentItem( i ); + break; + } + } + + connect( commandList, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), + this, SLOT( slotCommandContextMenu( KListView *, QListViewItem *, const QPoint & ) ) ); + + connect( ctcpList, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), + this, SLOT( slotCtcpContextMenu( KListView *, QListViewItem *, const QPoint & ) ) ); + + connect( addButton, SIGNAL( clicked() ), this, SLOT( slotAddCommand() ) ); + connect( editButton, SIGNAL( clicked() ), this, SLOT(slotEditNetworks() ) ); + connect( addReply, SIGNAL( clicked() ), this, SLOT( slotAddCtcp() ) ); + + connect( network, SIGNAL( activated( const QString & ) ), + this, SLOT( slotUpdateNetworkDescription( const QString &) ) ); + + connect( IRCProtocol::protocol(), SIGNAL( networkConfigUpdated( const QString & ) ), + this, SLOT( slotUpdateNetworks( const QString & ) ) ); + + slotUpdateNetworks( QString::null ); +} + +IRCEditAccountWidget::~IRCEditAccountWidget() +{ +} + +IRCAccount *IRCEditAccountWidget::account () +{ + return dynamic_cast(KopeteEditAccountWidget::account () ); +} + +void IRCEditAccountWidget::slotUpdateNetworks( const QString & selectedNetwork ) +{ + network->clear(); + + uint i = 0; + QStringList keys; + for( QDictIterator it( IRCProtocol::protocol()->networks() ); it.current(); ++it ) + keys.append( it.currentKey() ); + + keys.sort(); + + QStringList::Iterator end = keys.end(); + for( QStringList::Iterator it = keys.begin(); it != end; ++it ) + { + IRCNetwork * current = IRCProtocol::protocol()->networks()[*it]; + network->insertItem( current->name ); + if ( ( account() && account()->networkName() == current->name ) || current->name == selectedNetwork ) + { + network->setCurrentItem( i ); + description->setText( current->description ); + } + ++i; + } +} + +void IRCEditAccountWidget::slotEditNetworks() +{ + IRCProtocol::protocol()->editNetworks( network->currentText() ); +} + +void IRCEditAccountWidget::slotUpdateNetworkDescription( const QString &network ) +{ + description->setText( + IRCProtocol::protocol()->networks()[ network ]->description + ); +} + +void IRCEditAccountWidget::slotCommandContextMenu( KListView *, QListViewItem *item, const QPoint &p ) +{ + QPopupMenu popup; + popup.insertItem( i18n("Remove Command"), 1 ); + if( popup.exec( p ) == 1 ) + delete item; +} + +void IRCEditAccountWidget::slotCtcpContextMenu( KListView *, QListViewItem *item, const QPoint &p ) +{ + QPopupMenu popup; + popup.insertItem( i18n("Remove CTCP Reply"), 1 ); + if( popup.exec( p ) == 1 ) + delete item; +} + +void IRCEditAccountWidget::slotAddCommand() +{ + if ( !commandEdit->text().isEmpty() ) + { + new QListViewItem( commandList, commandEdit->text() ); + commandEdit->clear(); + } +} + +void IRCEditAccountWidget::slotAddCtcp() +{ + if ( !newCTCP->text().isEmpty() && !newReply->text().isEmpty() ) + { + new QListViewItem( ctcpList, newCTCP->text(), newReply->text() ); + newCTCP->clear(); + newReply->clear(); + } +} + +QString IRCEditAccountWidget::generateAccountId( const QString &network ) +{ + KConfig *config = KGlobal::config(); + QString nextId = network; + + uint accountNumber = 1; + while( config->hasGroup( QString("Account_%1_%2").arg( m_protocol->pluginId() ).arg( nextId ) ) ) + { + nextId = QString::fromLatin1("%1_%2").arg( network ).arg( ++accountNumber ); + } + kdDebug( 14120 ) << k_funcinfo << " ID IS: " << nextId << endl; + return nextId; +} + +Kopete::Account *IRCEditAccountWidget::apply() +{ + QString nickName = mNickName->text(); + QString networkName = network->currentText(); + + if( !account() ) + { + setAccount( new IRCAccount( mProtocol, generateAccountId(networkName), QString::null, networkName, nickName ) ); + + } + else + { + account()->setNickName( nickName ); + account()->setNetwork( networkName ); + } + + mPasswordWidget->save( &account()->password() ); + + account()->setAltNick( mAltNickname->text() ); + account()->setUserName( mUserName->text() ); + account()->setRealName( m_realNameLineEdit->text() ); + account()->setDefaultPart( partMessage->text() ); + account()->setDefaultQuit( quitMessage->text() ); + account()->setAutoShowServerWindow( autoShowServerWindow->isChecked() ); + account()->setExcludeConnect( autoConnect->isChecked() ); + account()->setMessageDestinations( serverNotices->currentItem() + 1, serverMessages->currentItem() + 1, + informationReplies->currentItem() + 1, errorMessages->currentItem() + 1 + ); + + account()->configGroup()->writeEntry("PreferSSL", preferSSL->isChecked()); + + QStringList cmds; + for( QListViewItem *i = commandList->firstChild(); i; i = i->nextSibling() ) + cmds.append( i->text(0) ); + + QMap< QString, QString > replies; + for( QListViewItem *i = ctcpList->firstChild(); i; i = i->nextSibling() ) + replies[ i->text(0) ] = i->text(1); + + account()->setCustomCtcpReplies( replies ); + account()->setConnectCommands( cmds ); + + KCharsets *c = KGlobal::charsets(); + account()->setCodec( c->codecForName( c->encodingForName( charset->currentText() ) ) ); + + return account(); +} + + +bool IRCEditAccountWidget::validateData() +{ + if( mNickName->text().isEmpty() ) + KMessageBox::sorry(this, i18n("You must enter a nickname."), i18n("Kopete")); + else + return true; + + return false; +} + +#include "irceditaccountwidget.moc" diff --git a/kopete/protocols/irc/ui/irceditaccountwidget.h b/kopete/protocols/irc/ui/irceditaccountwidget.h new file mode 100644 index 00000000..365acaf3 --- /dev/null +++ b/kopete/protocols/irc/ui/irceditaccountwidget.h @@ -0,0 +1,60 @@ +/* + irceditaccountwidget.h - IRC Account Widget + + Kopete (c) 2003 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + + + +#ifndef IRCEDITACCOUNTWIDEGET_H +#define IRCEDITACCOUNTWIDEGET_H + +#include "editaccountwidget.h" +#include "irceditaccount.h" + +class IRCProtocol; +class IRCAccount; +class KListView; +class QListViewItem; + +class IRCEditAccountWidget : public IRCEditAccountBase, public KopeteEditAccountWidget +{ + Q_OBJECT + + public: + IRCEditAccountWidget(IRCProtocol *proto, IRCAccount *, QWidget *parent=0, const char *name=0); + ~IRCEditAccountWidget(); + + IRCAccount *account(); + virtual bool validateData(); + virtual Kopete::Account *apply(); + + private slots: + void slotCommandContextMenu( KListView*, QListViewItem*, const QPoint & ); + void slotCtcpContextMenu( KListView*, QListViewItem*, const QPoint & ); + void slotAddCommand(); + void slotAddCtcp(); + void slotEditNetworks(); + void slotUpdateNetworks( const QString & ); + void slotUpdateNetworkDescription( const QString & ); + + private: + void readNetworks(); + QString generateAccountId( const QString &network ); + + IRCProtocol *mProtocol; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/irc/ui/networkconfig.ui b/kopete/protocols/irc/ui/networkconfig.ui new file mode 100644 index 00000000..d1000e37 --- /dev/null +++ b/kopete/protocols/irc/ui/networkconfig.ui @@ -0,0 +1,382 @@ + +NetworkConfig + + + NetworkConfig + + + + 0 + 0 + 670 + 468 + + + + Network Configuration + + + + unnamed + + + + description + + + + + textLabel10 + + + + 1 + 5 + 0 + 0 + + + + &Description: + + + description + + + + + groupBox2 + + + + 3 + 3 + 0 + 0 + + + + 4 + + + Host Con&figuration + + + + unnamed + + + + hostList + + + + 3 + 3 + 0 + 0 + + + + The IRC servers associated with this network + + + The IRC servers associated with this network. Use the up and down buttons to alter the order in which connections are attempted. + + + + + password + + + Password + + + Most IRC servers do not require a password + + + + + textLabel6 + + + Por&t: + + + port + + + + + port + + + 65536 + + + 1 + + + 6667 + + + + + textLabel4 + + + &Password: + + + password + + + + + textLabel5 + + + &Host: + + + host + + + + + host + + + true + + + + + + + + useSSL + + + Use SS&L + + + Check this to enable SSL for this connection + + + + + removeHost + + + + 1 + 0 + 0 + 0 + + + + &Remove + + + + + newHost + + + + 1 + 0 + 0 + 0 + + + + &New... + + + + + spacer3 + + + Horizontal + + + Expanding + + + + 210 + 20 + + + + + + downButton + + + false + + + Down + + + Move this server down + + + Move this server down in connection attempt priority + + + + + spacer4 + + + Vertical + + + Expanding + + + + 20 + 151 + + + + + + upButton + + + false + + + Up + + + Move this server up + + + Move this server up in connection attempt priority + + + + + + + cancelButton + + + &Cancel + + + + + saveButton + + + &Save + + + + + newNetwork + + + Ne&w + + + + + networkList + + + + 3 + 7 + 0 + 0 + + + + + + spacer5 + + + Horizontal + + + Expanding + + + + 260 + 20 + + + + + + renameNetwork + + + Rena&me... + + + + + removeNetwork + + + Remo&ve + + + + + + + cancelButton + clicked() + NetworkConfig + reject() + + + saveButton + clicked() + NetworkConfig + accept() + + + + networkList + newNetwork + renameNetwork + removeNetwork + description + hostList + upButton + downButton + host + port + password + useSSL + newHost + removeHost + saveButton + cancelButton + + + accepted() + rejected() + + + accept() + reject() + + + diff --git a/kopete/protocols/irc/ui/networkconfig.ui.h b/kopete/protocols/irc/ui/networkconfig.ui.h new file mode 100644 index 00000000..7716e75f --- /dev/null +++ b/kopete/protocols/irc/ui/networkconfig.ui.h @@ -0,0 +1,26 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename functions or slots use +** Qt Designer which will update this file, preserving your code. Create an +** init() function in place of a constructor, and a destroy() function in +** place of a destructor. +*****************************************************************************/ + + + + + + +void NetworkConfig::accept() +{ + emit accepted(); + QDialog::accept(); +} + + +void NetworkConfig::reject() +{ + emit rejected(); + QDialog::reject(); +} diff --git a/kopete/protocols/jabber/Makefile.am b/kopete/protocols/jabber/Makefile.am new file mode 100644 index 00000000..ce462f74 --- /dev/null +++ b/kopete/protocols/jabber/Makefile.am @@ -0,0 +1,67 @@ +if include_jingle +JINGLE=jingle +JINGLE_LIBS=jingle/libkopetejabberjingle.la +JINGLE_INCLUDES=-I$(srcdir)/jingle -I$(top_builddir)/kopete/protocols/jabber/jingle +endif + +METASOURCES = AUTO +SUBDIRS = ui icons libiris $(JINGLE) . kioslave +AM_CPPFLAGS = $(KOPETE_INCLUDES) \ + -I$(srcdir)/libiris/iris/include \ + -I$(srcdir)/libiris/iris/xmpp-im \ + -I$(srcdir)/libiris/iris/jabber \ + -I$(srcdir)/libiris/qca/src \ + -I$(srcdir)/libiris/cutestuff/util \ + -I$(srcdir)/libiris/cutestuff/network \ + -I$(srcdir)/ui \ + -I./ui \ + $(all_includes) $(JINGLE_INCLUDES) + +noinst_LTLIBRARIES = libjabberclient.la +libjabberclient_la_SOURCES = \ + jabberclient.cpp \ + jabberconnector.cpp \ + jabberbytestream.cpp + +kde_module_LTLIBRARIES = kopete_jabber.la + +kopete_jabber_la_SOURCES = \ + jabberprotocol.cpp \ + jabberaccount.cpp \ + jabberresource.cpp \ + jabberresourcepool.cpp \ + jabberbasecontact.cpp \ + jabbercontact.cpp \ + jabbergroupcontact.cpp \ + jabbergroupmembercontact.cpp \ + jabbercontactpool.cpp \ + jabberformtranslator.cpp \ + jabberformlineedit.cpp \ + jabberchatsession.cpp \ + jabbergroupchatmanager.cpp \ + jabberfiletransfer.cpp \ + jabbercapabilitiesmanager.cpp\ + jabbertransport.cpp\ + jabberbookmarks.cpp + +kopete_jabber_la_LDFLAGS = -no-undefined -module $(KDE_PLUGIN) $(all_libraries) +kopete_jabber_la_LIBADD = $(top_builddir)/kopete/libkopete/libkopete.la \ + ui/libkopetejabberui.la \ + libiris/iris/include/libiris.la \ + libiris/iris/jabber/libiris_jabber.la \ + libiris/iris/xmpp-core/libiris_xmpp_core.la \ + libiris/iris/xmpp-im/libiris_xmpp_im.la \ + libiris/qca/src/libqca.la \ + libiris/cutestuff/network/libcutestuff_network.la \ + libiris/cutestuff/util/libcutestuff_util.la \ + libjabberclient.la \ + $(JINGLE_LIBS) + +service_DATA = kopete_jabber.desktop +servicedir = $(kde_servicesdir) + +mydatadir = $(kde_datadir)/kopete_jabber +mydata_DATA = jabberchatui.rc + +noinst_HEADERS = jabberresourcepool.h jabbercontact.h jabbergroupcontact.h \ + jabberclient.h diff --git a/kopete/protocols/jabber/TODO b/kopete/protocols/jabber/TODO new file mode 100644 index 00000000..64da5133 --- /dev/null +++ b/kopete/protocols/jabber/TODO @@ -0,0 +1,27 @@ +TODO for Jabber: + +- implement support for transports/agents +- support all message types (chat/ticker/etc) +- add a button for server defaults +- port dialogs to KDialogBase +- support different icons for contacts from servers with broken connections etc. +- show (i.e. with a QToolTip) the subscription status: both, to, from +- add "querying..." feedback while waiting for vCard +- clean up class names in the ui directory, no real scheme there right now +- if a contact subscribed to you, it is being added as a real contact, + should either be added as temporary or not at all +- provide better feedback for dialogs querying the server +- support avatars and idle times for tooltips +- when trying to register an account, try to display the actual server error + message +- clean up JabberAddContactPage (needs rewrite) +- support advanced auth methods +- subclass TLS to make use of KDE classes +- allow SSL fallback with setOptProbe +- support account deletion +- factor out client backend to single class JabberClient +- make vCard dialog better, maybe use KIMProxy somehow +- allow fetching vCard from "auth user?" dialog +- allow adding file transfer reasons +- support resuming +- support addAddressBookField diff --git a/kopete/protocols/jabber/icons/Makefile.am b/kopete/protocols/jabber/icons/Makefile.am new file mode 100644 index 00000000..56681824 --- /dev/null +++ b/kopete/protocols/jabber/icons/Makefile.am @@ -0,0 +1 @@ +KDE_ICON=AUTO \ No newline at end of file diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_away.png b/kopete/protocols/jabber/icons/cr16-action-jabber_away.png new file mode 100644 index 00000000..a6727e71 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_away.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_chatty.png b/kopete/protocols/jabber/icons/cr16-action-jabber_chatty.png new file mode 100644 index 00000000..baca2e22 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_chatty.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_connecting.mng b/kopete/protocols/jabber/icons/cr16-action-jabber_connecting.mng new file mode 100644 index 00000000..3098ca1f Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_connecting.mng differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_group.png b/kopete/protocols/jabber/icons/cr16-action-jabber_group.png new file mode 100644 index 00000000..0240ab6e Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_group.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_invisible.png b/kopete/protocols/jabber/icons/cr16-action-jabber_invisible.png new file mode 100644 index 00000000..279f1397 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_invisible.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_na.png b/kopete/protocols/jabber/icons/cr16-action-jabber_na.png new file mode 100644 index 00000000..b1aa91af Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_na.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_offline.png b/kopete/protocols/jabber/icons/cr16-action-jabber_offline.png new file mode 100644 index 00000000..5e473faa Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_offline.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_online.png b/kopete/protocols/jabber/icons/cr16-action-jabber_online.png new file mode 100644 index 00000000..48dd715e Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_online.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_original.png b/kopete/protocols/jabber/icons/cr16-action-jabber_original.png new file mode 100644 index 00000000..3a8e5042 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_original.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_raw.png b/kopete/protocols/jabber/icons/cr16-action-jabber_raw.png new file mode 100644 index 00000000..7b170c19 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_raw.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_serv_off.png b/kopete/protocols/jabber/icons/cr16-action-jabber_serv_off.png new file mode 100644 index 00000000..8d5005e7 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_serv_off.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_serv_on.png b/kopete/protocols/jabber/icons/cr16-action-jabber_serv_on.png new file mode 100644 index 00000000..378afc5c Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_serv_on.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_xa.png b/kopete/protocols/jabber/icons/cr16-action-jabber_xa.png new file mode 100644 index 00000000..347a4753 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_xa.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_aim.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_aim.png new file mode 100644 index 00000000..32aea50a Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_aim.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_gadu.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_gadu.png new file mode 100644 index 00000000..85c7ee81 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_gadu.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_http-ws.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_http-ws.png new file mode 100644 index 00000000..71da6df4 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_http-ws.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_icq.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_icq.png new file mode 100644 index 00000000..77df20aa Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_icq.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_irc.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_irc.png new file mode 100644 index 00000000..c4283af4 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_irc.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_msn.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_msn.png new file mode 100644 index 00000000..2a349148 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_msn.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_qq.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_qq.png new file mode 100644 index 00000000..0dd883dd Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_qq.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_sms.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_sms.png new file mode 100644 index 00000000..eeb212a3 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_sms.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_smtp.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_smtp.png new file mode 100644 index 00000000..bebd2e69 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_smtp.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_tlen.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_tlen.png new file mode 100644 index 00000000..d4421eed Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_tlen.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_yahoo.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_yahoo.png new file mode 100644 index 00000000..6f1bb81d Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_yahoo.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_protocol.png b/kopete/protocols/jabber/icons/cr16-app-jabber_protocol.png new file mode 100644 index 00000000..48dd715e Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_protocol.png differ diff --git a/kopete/protocols/jabber/icons/cr32-app-jabber_protocol.png b/kopete/protocols/jabber/icons/cr32-app-jabber_protocol.png new file mode 100644 index 00000000..9d091b13 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr32-app-jabber_protocol.png differ diff --git a/kopete/protocols/jabber/icons/cr48-app-jabber_protocol.png b/kopete/protocols/jabber/icons/cr48-app-jabber_protocol.png new file mode 100644 index 00000000..0964749c Binary files /dev/null and b/kopete/protocols/jabber/icons/cr48-app-jabber_protocol.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_away.png b/kopete/protocols/jabber/icons/hi16-action-jabber_away.png new file mode 100644 index 00000000..b3959b1a Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_away.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_chatty.png b/kopete/protocols/jabber/icons/hi16-action-jabber_chatty.png new file mode 100644 index 00000000..8fd5a24b Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_chatty.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_connecting.mng b/kopete/protocols/jabber/icons/hi16-action-jabber_connecting.mng new file mode 100644 index 00000000..5fabdd4c Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_connecting.mng differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_group.png b/kopete/protocols/jabber/icons/hi16-action-jabber_group.png new file mode 100644 index 00000000..fe2062a9 Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_group.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_invisible.png b/kopete/protocols/jabber/icons/hi16-action-jabber_invisible.png new file mode 100644 index 00000000..e1a30342 Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_invisible.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_na.png b/kopete/protocols/jabber/icons/hi16-action-jabber_na.png new file mode 100644 index 00000000..d4950ec0 Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_na.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_offline.png b/kopete/protocols/jabber/icons/hi16-action-jabber_offline.png new file mode 100644 index 00000000..199b75ed Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_offline.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_online.png b/kopete/protocols/jabber/icons/hi16-action-jabber_online.png new file mode 100644 index 00000000..f3566eab Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_online.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_original.png b/kopete/protocols/jabber/icons/hi16-action-jabber_original.png new file mode 100644 index 00000000..4613cb67 Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_original.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_raw.png b/kopete/protocols/jabber/icons/hi16-action-jabber_raw.png new file mode 100644 index 00000000..7b170c19 Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_raw.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_serv_off.png b/kopete/protocols/jabber/icons/hi16-action-jabber_serv_off.png new file mode 100644 index 00000000..822b70fb Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_serv_off.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_serv_on.png b/kopete/protocols/jabber/icons/hi16-action-jabber_serv_on.png new file mode 100644 index 00000000..b123c82e Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_serv_on.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_xa.png b/kopete/protocols/jabber/icons/hi16-action-jabber_xa.png new file mode 100644 index 00000000..e6a17e7c Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_xa.png differ diff --git a/kopete/protocols/jabber/icons/hi16-app-jabber_protocol.png b/kopete/protocols/jabber/icons/hi16-app-jabber_protocol.png new file mode 100644 index 00000000..f3566eab Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-app-jabber_protocol.png differ diff --git a/kopete/protocols/jabber/icons/hi32-app-jabber_protocol.png b/kopete/protocols/jabber/icons/hi32-app-jabber_protocol.png new file mode 100644 index 00000000..2cb3ef57 Binary files /dev/null and b/kopete/protocols/jabber/icons/hi32-app-jabber_protocol.png differ diff --git a/kopete/protocols/jabber/icons/hi48-app-jabber_protocol.png b/kopete/protocols/jabber/icons/hi48-app-jabber_protocol.png new file mode 100644 index 00000000..7d4cf34b Binary files /dev/null and b/kopete/protocols/jabber/icons/hi48-app-jabber_protocol.png differ diff --git a/kopete/protocols/jabber/jabberaccount.cpp b/kopete/protocols/jabber/jabberaccount.cpp new file mode 100644 index 00000000..785e9c53 --- /dev/null +++ b/kopete/protocols/jabber/jabberaccount.cpp @@ -0,0 +1,1752 @@ + +/*************************************************************************** + jabberaccount.cpp - core Jabber account class + ------------------- + begin : Sat M??? 8 2003 + copyright : (C) 2003 by Till Gerken + Based on JabberProtocol by Daniel Stone + and Till Gerken . + copyright : (C) 2006 by Olivier Goffart + + Kopete (C) 2001-2003 Kopete developers + . + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "im.h" +#include "filetransfer.h" +#include "xmpp.h" +#include "xmpp_tasks.h" +#include "qca.h" +#include "bsocket.h" + +#include "jabberaccount.h" +#include "jabberbookmarks.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopetepassword.h" +#include "kopeteawayaction.h" +#include "kopetemetacontact.h" +#include "kopeteuiglobal.h" +#include "kopetegroup.h" +#include "kopetecontactlist.h" +#include "kopeteaccountmanager.h" +#include "contactaddednotifydialog.h" + +#include "jabberconnector.h" +#include "jabberclient.h" +#include "jabberprotocol.h" +#include "jabberresourcepool.h" +#include "jabbercontactpool.h" +#include "jabberfiletransfer.h" +#include "jabbercontact.h" +#include "jabbergroupcontact.h" +#include "jabbercapabilitiesmanager.h" +#include "jabbertransport.h" +#include "dlgjabbersendraw.h" +#include "dlgjabberservices.h" +#include "dlgjabberchatjoin.h" + +#include + +#ifdef SUPPORT_JINGLE +#include "voicecaller.h" +#include "jinglevoicecaller.h" + +// NOTE: Disabled for 0.12, will develop them futher in KDE4 +// #include "jinglesessionmanager.h" +// #include "jinglesession.h" +// #include "jinglevoicesession.h" +#include "jinglevoicesessiondialog.h" +#endif + +#define KOPETE_CAPS_NODE "http://kopete.kde.org/jabber/caps" + + + +JabberAccount::JabberAccount (JabberProtocol * parent, const QString & accountId, const char *name) + :Kopete::PasswordedAccount ( parent, accountId, 0, name ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Instantiating new account " << accountId << endl; + + m_protocol = parent; + + m_jabberClient = 0L; + + m_resourcePool = 0L; + m_contactPool = 0L; +#ifdef SUPPORT_JINGLE + m_voiceCaller = 0L; + //m_jingleSessionManager = 0L; // NOTE: Disabled for 0.12 +#endif + m_bookmarks = new JabberBookmarks(this); + m_removing=false; + m_notifiedUserCannotBindTransferPort = false; + // add our own contact to the pool + JabberContact *myContact = contactPool()->addContact ( XMPP::RosterItem ( accountId ), Kopete::ContactList::self()->myself(), false ); + setMyself( myContact ); + + QObject::connect(Kopete::ContactList::self(), SIGNAL( globalIdentityChanged(const QString&, const QVariant& ) ), SLOT( slotGlobalIdentityChanged(const QString&, const QVariant& ) ) ); + + m_initialPresence = XMPP::Status ( "", "", 5, true ); + +} + +JabberAccount::~JabberAccount () +{ + disconnect ( Kopete::Account::Manual ); + + // Remove this account from Capabilities manager. + protocol()->capabilitiesManager()->removeAccount( this ); + + cleanup (); + + QMap tranposrts_copy=m_transports; + QMap::Iterator it; + for ( it = tranposrts_copy.begin(); it != tranposrts_copy.end(); ++it ) + delete it.data(); +} + +void JabberAccount::cleanup () +{ + + delete m_jabberClient; + + m_jabberClient = 0L; + + delete m_resourcePool; + m_resourcePool = 0L; + + delete m_contactPool; + m_contactPool = 0L; + +#ifdef SUPPORT_JINGLE + delete m_voiceCaller; + m_voiceCaller = 0L; + +// delete m_jingleSessionManager; +// m_jingleSessionManager = 0L; +#endif +} + +void JabberAccount::setS5BServerPort ( int port ) +{ + + if ( !m_jabberClient ) + { + return; + } + + if ( !m_jabberClient->setS5BServerPort ( port ) && !m_notifiedUserCannotBindTransferPort) + { + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), KMessageBox::Sorry, + i18n ( "Could not bind Jabber file transfer manager to local port. Please check if the file transfer port is already in use or choose another port in the account settings." ), + i18n ( "Failed to start Jabber File Transfer Manager" ) ); + m_notifiedUserCannotBindTransferPort = true; + } + +} + +KActionMenu *JabberAccount::actionMenu () +{ + KActionMenu *m_actionMenu = Kopete::Account::actionMenu(); + + m_actionMenu->popupMenu()->insertSeparator(); + + KAction *action; + + action = new KAction (i18n ("Join Groupchat..."), "jabber_group", 0, this, SLOT (slotJoinNewChat ()), this, "actionJoinChat"); + m_actionMenu->insert(action); + action->setEnabled( isConnected() ); + + action = m_bookmarks->bookmarksAction( m_bookmarks ); + m_actionMenu->insert(action); + action->setEnabled( isConnected() ); + + + m_actionMenu->popupMenu()->insertSeparator(); + + action = new KAction ( i18n ("Services..."), "jabber_serv_on", 0, + this, SLOT ( slotGetServices () ), this, "actionJabberServices"); + action->setEnabled( isConnected() ); + m_actionMenu->insert ( action ); + + action = new KAction ( i18n ("Send Raw Packet to Server..."), "mail_new", 0, + this, SLOT ( slotSendRaw () ), this, "actionJabberSendRaw") ; + action->setEnabled( isConnected() ); + m_actionMenu->insert ( action ); + + action = new KAction ( i18n ("Edit User Info..."), "identity", 0, + this, SLOT ( slotEditVCard () ), this, "actionEditVCard") ; + action->setEnabled( isConnected() ); + m_actionMenu->insert ( action ); + + + return m_actionMenu; + +} + +JabberResourcePool *JabberAccount::resourcePool () +{ + + if ( !m_resourcePool ) + m_resourcePool = new JabberResourcePool ( this ); + + return m_resourcePool; + +} + +JabberContactPool *JabberAccount::contactPool () +{ + + if ( !m_contactPool ) + m_contactPool = new JabberContactPool ( this ); + + return m_contactPool; + +} + +bool JabberAccount::createContact (const QString & contactId, Kopete::MetaContact * metaContact) +{ + + // collect all group names + QStringList groupNames; + Kopete::GroupList groupList = metaContact->groups(); + for(Kopete::Group *group = groupList.first(); group; group = groupList.next()) + groupNames += group->displayName(); + + XMPP::Jid jid ( contactId ); + XMPP::RosterItem item ( jid ); + item.setName ( metaContact->displayName () ); + item.setGroups ( groupNames ); + + // this contact will be created with the "dirty" flag set + // (it will get reset if the contact appears in the roster during connect) + JabberContact *contact = contactPool()->addContact ( item, metaContact, true ); + + return ( contact != 0 ); + +} + +void JabberAccount::errorConnectFirst () +{ + + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), + KMessageBox::Error, + i18n ("Please connect first."), i18n ("Jabber Error") ); + +} + +void JabberAccount::errorConnectionLost () +{ + disconnected( Kopete::Account::ConnectionReset ); +} + +bool JabberAccount::isConnecting () +{ + + XMPP::Jid jid ( myself()->contactId () ); + + // see if we are currently trying to connect + return resourcePool()->bestResource ( jid ).status().show () == QString("connecting"); + +} + +void JabberAccount::connectWithPassword ( const QString &password ) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "called" << endl; + + /* Cancel connection process if no password has been supplied. */ + if ( password.isEmpty () ) + { + disconnect ( Kopete::Account::Manual ); + return; + } + + /* Don't do anything if we are already connected. */ + if ( isConnected () ) + return; + + // instantiate new client backend or clean up old one + if ( !m_jabberClient ) + { + m_jabberClient = new JabberClient; + + QObject::connect ( m_jabberClient, SIGNAL ( csDisconnected () ), this, SLOT ( slotCSDisconnected () ) ); + QObject::connect ( m_jabberClient, SIGNAL ( csError ( int ) ), this, SLOT ( slotCSError ( int ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( tlsWarning ( int ) ), this, SLOT ( slotHandleTLSWarning ( int ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( connected () ), this, SLOT ( slotConnected () ) ); + QObject::connect ( m_jabberClient, SIGNAL ( error ( JabberClient::ErrorCode ) ), this, SLOT ( slotClientError ( JabberClient::ErrorCode ) ) ); + + QObject::connect ( m_jabberClient, SIGNAL ( subscription ( const XMPP::Jid &, const QString & ) ), + this, SLOT ( slotSubscription ( const XMPP::Jid &, const QString & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( rosterRequestFinished ( bool ) ), + this, SLOT ( slotRosterRequestFinished ( bool ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( newContact ( const XMPP::RosterItem & ) ), + this, SLOT ( slotContactUpdated ( const XMPP::RosterItem & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( contactUpdated ( const XMPP::RosterItem & ) ), + this, SLOT ( slotContactUpdated ( const XMPP::RosterItem & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( contactDeleted ( const XMPP::RosterItem & ) ), + this, SLOT ( slotContactDeleted ( const XMPP::RosterItem & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( resourceAvailable ( const XMPP::Jid &, const XMPP::Resource & ) ), + this, SLOT ( slotResourceAvailable ( const XMPP::Jid &, const XMPP::Resource & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( resourceUnavailable ( const XMPP::Jid &, const XMPP::Resource & ) ), + this, SLOT ( slotResourceUnavailable ( const XMPP::Jid &, const XMPP::Resource & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( messageReceived ( const XMPP::Message & ) ), + this, SLOT ( slotReceivedMessage ( const XMPP::Message & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( incomingFileTransfer () ), + this, SLOT ( slotIncomingFileTransfer () ) ); + QObject::connect ( m_jabberClient, SIGNAL ( groupChatJoined ( const XMPP::Jid & ) ), + this, SLOT ( slotGroupChatJoined ( const XMPP::Jid & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( groupChatLeft ( const XMPP::Jid & ) ), + this, SLOT ( slotGroupChatLeft ( const XMPP::Jid & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( groupChatPresence ( const XMPP::Jid &, const XMPP::Status & ) ), + this, SLOT ( slotGroupChatPresence ( const XMPP::Jid &, const XMPP::Status & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( groupChatError ( const XMPP::Jid &, int, const QString & ) ), + this, SLOT ( slotGroupChatError ( const XMPP::Jid &, int, const QString & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( debugMessage ( const QString & ) ), + this, SLOT ( slotClientDebugMessage ( const QString & ) ) ); + } + else + { + m_jabberClient->disconnect (); + } + + // we need to use the old protocol for now + m_jabberClient->setUseXMPP09 ( true ); + + // set SSL flag (this should be converted to forceTLS when using the new protocol) + m_jabberClient->setUseSSL ( configGroup()->readBoolEntry ( "UseSSL", false ) ); + + // override server and port (this should be dropped when using the new protocol and no direct SSL) + m_jabberClient->setOverrideHost ( true, server (), port () ); + + // allow plaintext password authentication or not? + m_jabberClient->setAllowPlainTextPassword ( configGroup()->readBoolEntry ( "AllowPlainTextPassword", false ) ); + + // enable file transfer (if empty, IP will be set after connection has been established) + KGlobal::config()->setGroup ( "Jabber" ); + m_jabberClient->setFileTransfersEnabled ( true, KGlobal::config()->readEntry ( "LocalIP" ) ); + setS5BServerPort ( KGlobal::config()->readNumEntry ( "LocalPort", 8010 ) ); + + // + // Determine system name + // + if ( !configGroup()->readBoolEntry ( "HideSystemInfo", false ) ) + { + struct utsname utsBuf; + + uname (&utsBuf); + + m_jabberClient->setClientName ("Kopete"); + m_jabberClient->setClientVersion (kapp->aboutData ()->version ()); + m_jabberClient->setOSName (QString ("%1 %2").arg (utsBuf.sysname, 1).arg (utsBuf.release, 2)); + } + + // Set caps node information + m_jabberClient->setCapsNode(KOPETE_CAPS_NODE); + m_jabberClient->setCapsVersion(kapp->aboutData()->version()); + + // Set Disco Identity information + DiscoItem::Identity identity; + identity.category = "client"; + identity.type = "pc"; + identity.name = "Kopete"; + m_jabberClient->setDiscoIdentity(identity); + + //BEGIN TIMEZONE INFORMATION + // + // Set timezone information (code from Psi) + // Copyright (C) 2001-2003 Justin Karneges + // + time_t x; + time(&x); + char str[256]; + char fmt[32]; + int timezoneOffset; + QString timezoneString; + + strcpy ( fmt, "%z" ); + strftime ( str, 256, fmt, localtime ( &x ) ); + + if ( strcmp ( fmt, str ) ) + { + QString s = str; + if ( s.at ( 0 ) == '+' ) + s.remove ( 0, 1 ); + s.truncate ( s.length () - 2 ); + timezoneOffset = s.toInt(); + } + + strcpy ( fmt, "%Z" ); + strftime ( str, 256, fmt, localtime ( &x ) ); + + if ( strcmp ( fmt, str ) ) + timezoneString = str; + //END of timezone code + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Determined timezone " << timezoneString << " with UTC offset " << timezoneOffset << " hours." << endl; + + m_jabberClient->setTimeZone ( timezoneString, timezoneOffset ); + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Connecting to Jabber server " << server() << ":" << port() << endl; + + setPresence( XMPP::Status ("connecting", "", 0, true) ); + + switch ( m_jabberClient->connect ( XMPP::Jid ( accountId () + QString("/") + resource () ), password ) ) + { + case JabberClient::NoTLS: + // no SSL support, at the connecting stage this means the problem is client-side + KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget (), KMessageBox::Error, + i18n ("SSL support could not be initialized for account %1. This is most likely because the QCA TLS plugin is not installed on your system."). + arg(myself()->contactId()), + i18n ("Jabber SSL Error")); + break; + + case JabberClient::Ok: + default: + // everything alright! + + break; + } + +} + +void JabberAccount::slotClientDebugMessage ( const QString &msg ) +{ + + kdDebug (JABBER_DEBUG_PROTOCOL) << k_funcinfo << msg << endl; + +} + +bool JabberAccount::handleTLSWarning ( JabberClient *jabberClient, int warning ) +{ + QString validityString, code; + + QString server = jabberClient->jid().domain (); + QString accountId = jabberClient->jid().bare (); + + switch ( warning ) + { + case QCA::TLS::NoCert: + validityString = i18n("No certificate was presented."); + code = "NoCert"; + break; + case QCA::TLS::HostMismatch: + validityString = i18n("The host name does not match the one in the certificate."); + code = "HostMismatch"; + break; + case QCA::TLS::Rejected: + validityString = i18n("The Certificate Authority rejected the certificate."); + code = "Rejected"; + break; + case QCA::TLS::Untrusted: + // FIXME: write better error message here + validityString = i18n("The certificate is untrusted."); + code = "Untrusted"; + break; + case QCA::TLS::SignatureFailed: + validityString = i18n("The signature is invalid."); + code = "SignatureFailed"; + break; + case QCA::TLS::InvalidCA: + validityString = i18n("The Certificate Authority is invalid."); + code = "InvalidCA"; + break; + case QCA::TLS::InvalidPurpose: + // FIXME: write better error message here + validityString = i18n("Invalid certificate purpose."); + code = "InvalidPurpose"; + break; + case QCA::TLS::SelfSigned: + validityString = i18n("The certificate is self-signed."); + code = "SelfSigned"; + break; + case QCA::TLS::Revoked: + validityString = i18n("The certificate has been revoked."); + code = "Revoked"; + break; + case QCA::TLS::PathLengthExceeded: + validityString = i18n("Maximum certificate chain length was exceeded."); + code = "PathLengthExceeded"; + break; + case QCA::TLS::Expired: + validityString = i18n("The certificate has expired."); + code = "Expired"; + break; + case QCA::TLS::Unknown: + default: + validityString = i18n("An unknown error occurred trying to validate the certificate."); + code = "Unknown"; + break; + } + + return ( KMessageBox::warningContinueCancel ( Kopete::UI::Global::mainWidget (), + i18n("

    The certificate of server %1 could not be validated for account %2: %3

    Do you want to continue?

    "). + arg(server, accountId, validityString), + i18n("Jabber Connection Certificate Problem"), + KStdGuiItem::cont(), + QString("KopeteTLSWarning") + server + code) == KMessageBox::Continue ); + +} + +void JabberAccount::slotHandleTLSWarning ( int validityResult ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Handling TLS warning..." << endl; + + if ( handleTLSWarning ( m_jabberClient, validityResult ) ) + { + // resume stream + m_jabberClient->continueAfterTLSWarning (); + } + else + { + // disconnect stream + disconnect ( Kopete::Account::Manual ); + } + +} + +void JabberAccount::slotClientError ( JabberClient::ErrorCode errorCode ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Handling client error..." << endl; + + switch ( errorCode ) + { + case JabberClient::NoTLS: + default: + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), KMessageBox::Error, + i18n ("An encrypted connection with the Jabber server could not be established."), + i18n ("Jabber Connection Error")); + disconnect ( Kopete::Account::Manual ); + break; + } + +} + +void JabberAccount::slotConnected () +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Connected to Jabber server." << endl; + +#ifdef SUPPORT_JINGLE + if(!m_voiceCaller) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Creating Jingle Voice caller..." << endl; + m_voiceCaller = new JingleVoiceCaller( this ); + QObject::connect(m_voiceCaller,SIGNAL(incoming(const Jid&)),this,SLOT(slotIncomingVoiceCall( const Jid& ))); + m_voiceCaller->initialize(); + } + + + +#if 0 + if(!m_jingleSessionManager) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Creating Jingle Session Manager..." << endl; + m_jingleSessionManager = new JingleSessionManager( this ); + QObject::connect(m_jingleSessionManager, SIGNAL(incomingSession(const QString &, JingleSession *)), this, SLOT(slotIncomingJingleSession(const QString &, JingleSession *))); + } +#endif + + // Set caps extensions + m_jabberClient->client()->addExtension("voice-v1", Features(QString("http://www.google.com/xmpp/protocol/voice/v1"))); +#endif + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Requesting roster..." << endl; + m_jabberClient->requestRoster (); +} + +void JabberAccount::slotRosterRequestFinished ( bool success ) +{ + + if ( success ) + { + // the roster was imported successfully, clear + // all "dirty" items from the contact list + contactPool()->cleanUp (); + } + + /* Since we are online now, set initial presence. Don't do this + * before the roster request or we will receive presence + * information before we have updated our roster with actual + * contacts from the server! (Iris won't forward presence + * information in that case either). */ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Setting initial presence..." << endl; + setPresence ( m_initialPresence ); + +} + +void JabberAccount::slotIncomingFileTransfer () +{ + + // delegate the work to a file transfer object + new JabberFileTransfer ( this, client()->fileTransferManager()->takeIncoming () ); + +} + +void JabberAccount::setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason) +{ + XMPP::Status xmppStatus = m_protocol->kosToStatus( status, reason); + + if( status.status() == Kopete::OnlineStatus::Offline ) + { + xmppStatus.setIsAvailable( false ); + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "CROSS YOUR FINGERS! THIS IS GONNA BE WILD" << endl; + disconnect (Manual, xmppStatus); + return; + } + + if( isConnecting () ) + { + return; + } + + + if ( !isConnected () ) + { + // we are not connected yet, so connect now + m_initialPresence = xmppStatus; + connect ( status ); + } + else + { + setPresence ( xmppStatus ); + } +} + +void JabberAccount::disconnect ( Kopete::Account::DisconnectReason reason ) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "disconnect() called" << endl; + + if (isConnected ()) + { + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Still connected, closing connection..." << endl; + /* Tell backend class to disconnect. */ + m_jabberClient->disconnect (); + } + + // make sure that the connection animation gets stopped if we're still + // in the process of connecting + setPresence ( XMPP::Status ("", "", 0, false) ); + m_initialPresence = XMPP::Status ("", "", 5, true); + + /* FIXME: + * We should delete the JabberClient instance here, + * but active timers in Iris prevent us from doing so. + * (in a failed connection attempt, these timers will + * try to access an already deleted object). + * Instead, the instance will lurk until the next + * connection attempt. + */ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Disconnected." << endl; + + disconnected ( reason ); +} + +void JabberAccount::disconnect( Kopete::Account::DisconnectReason reason, XMPP::Status &status ) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "disconnect( reason, status ) called" << endl; + + if (isConnected ()) + { + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Still connected, closing connection..." << endl; + /* Tell backend class to disconnect. */ + m_jabberClient->disconnect (status); + } + + // make sure that the connection animation gets stopped if we're still + // in the process of connecting + setPresence ( status ); + + /* FIXME: + * We should delete the JabberClient instance here, + * but active timers in Iris prevent us from doing so. + * (in a failed connection attempt, these timers will + * try to access an already deleted object). + * Instead, the instance will lurk until the next + * connection attempt. + */ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Disconnected." << endl; + + Kopete::Account::disconnected ( reason ); +} + +void JabberAccount::disconnect () +{ + disconnect ( Manual ); +} + +void JabberAccount::slotConnect () +{ + connect (); +} + +void JabberAccount::slotDisconnect () +{ + disconnect ( Kopete::Account::Manual ); +} + +void JabberAccount::slotCSDisconnected () +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Disconnected from Jabber server." << endl; + + /* + * We should delete the JabberClient instance here, + * but timers etc prevent us from doing so. Iris does + * not like to be deleted from a slot. + */ + + /* It seems that we don't get offline notifications when going offline + * with the protocol, so clear all resources manually. */ + resourcePool()->clear(); + +} + +void JabberAccount::handleStreamError (int streamError, int streamCondition, int connectorCode, const QString &server, Kopete::Account::DisconnectReason &errorClass) +{ + QString errorText; + QString errorCondition; + + errorClass = Kopete::Account::InvalidHost; + + /* + * Display error to user. + * FIXME: for unknown errors, maybe add error codes? + */ + switch(streamError) + { + case XMPP::Stream::ErrParse: + errorClass = Kopete::Account::Unknown; + errorText = i18n("Malformed packet received."); + break; + + case XMPP::Stream::ErrProtocol: + errorClass = Kopete::Account::Unknown; + errorText = i18n("There was an unrecoverable error in the protocol."); + break; + + case XMPP::Stream::ErrStream: + switch(streamCondition) + { + case XMPP::Stream::GenericStreamError: + errorCondition = i18n("Generic stream error (sorry, I do not have a more-detailed reason)"); + break; + case XMPP::Stream::Conflict: + // FIXME: need a better error message here + errorCondition = i18n("There was a conflict in the information received."); + break; + case XMPP::Stream::ConnectionTimeout: + errorCondition = i18n("The stream timed out."); + break; + case XMPP::Stream::InternalServerError: + errorCondition = i18n("Internal server error."); + break; + case XMPP::Stream::InvalidFrom: + errorCondition = i18n("Stream packet received from an invalid address."); + break; + case XMPP::Stream::InvalidXml: + errorCondition = i18n("Malformed stream packet received."); + break; + case XMPP::Stream::PolicyViolation: + // FIXME: need a better error message here + errorCondition = i18n("Policy violation in the protocol stream."); + break; + case XMPP::Stream::ResourceConstraint: + // FIXME: need a better error message here + errorCondition = i18n("Resource constraint."); + break; + case XMPP::Stream::SystemShutdown: + // FIXME: need a better error message here + errorCondition = i18n("System shutdown."); + break; + default: + errorCondition = i18n("Unknown reason."); + break; + } + + errorText = i18n("There was an error in the protocol stream: %1").arg(errorCondition); + break; + + case XMPP::ClientStream::ErrConnection: + switch(connectorCode) + { + case KNetwork::KSocketBase::LookupFailure: + errorClass = Kopete::Account::InvalidHost; + errorCondition = i18n("Host not found."); + break; + case KNetwork::KSocketBase::AddressInUse: + errorCondition = i18n("Address is already in use."); + break; + case KNetwork::KSocketBase::AlreadyCreated: + errorCondition = i18n("Cannot recreate the socket."); + break; + case KNetwork::KSocketBase::AlreadyBound: + errorCondition = i18n("Cannot bind the socket again."); + break; + case KNetwork::KSocketBase::AlreadyConnected: + errorCondition = i18n("Socket is already connected."); + break; + case KNetwork::KSocketBase::NotConnected: + errorCondition = i18n("Socket is not connected."); + break; + case KNetwork::KSocketBase::NotBound: + errorCondition = i18n("Socket is not bound."); + break; + case KNetwork::KSocketBase::NotCreated: + errorCondition = i18n("Socket has not been created."); + break; + case KNetwork::KSocketBase::WouldBlock: + errorCondition = i18n("Socket operation would block. You should not see this error, please use \"Report Bug\" from the Help menu."); + break; + case KNetwork::KSocketBase::ConnectionRefused: + errorCondition = i18n("Connection refused."); + break; + case KNetwork::KSocketBase::ConnectionTimedOut: + errorCondition = i18n("Connection timed out."); + break; + case KNetwork::KSocketBase::InProgress: + errorCondition = i18n("Connection attempt already in progress."); + break; + case KNetwork::KSocketBase::NetFailure: + errorCondition = i18n("Network failure."); + break; + case KNetwork::KSocketBase::NotSupported: + errorCondition = i18n("Operation is not supported."); + break; + case KNetwork::KSocketBase::Timeout: + errorCondition = i18n("Socket timed out."); + break; + default: + errorClass = Kopete::Account::ConnectionReset; + //errorCondition = i18n("Sorry, something unexpected happened that I do not know more about."); + break; + } + if(!errorCondition.isEmpty()) + errorText = i18n("There was a connection error: %1").arg(errorCondition); + break; + + case XMPP::ClientStream::ErrNeg: + switch(streamCondition) + { + case XMPP::ClientStream::HostUnknown: + // FIXME: need a better error message here + errorCondition = i18n("Unknown host."); + break; + case XMPP::ClientStream::RemoteConnectionFailed: + // FIXME: need a better error message here + errorCondition = i18n("Could not connect to a required remote resource."); + break; + case XMPP::ClientStream::SeeOtherHost: + errorCondition = i18n("It appears we have been redirected to another server; I do not know how to handle this."); + break; + case XMPP::ClientStream::UnsupportedVersion: + errorCondition = i18n("Unsupported protocol version."); + break; + default: + errorCondition = i18n("Unknown error."); + break; + } + + errorText = i18n("There was a negotiation error: %1").arg(errorCondition); + break; + + case XMPP::ClientStream::ErrTLS: + switch(streamCondition) + { + case XMPP::ClientStream::TLSStart: + errorCondition = i18n("Server rejected our request to start the TLS handshake."); + break; + case XMPP::ClientStream::TLSFail: + errorCondition = i18n("Failed to establish a secure connection."); + break; + default: + errorCondition = i18n("Unknown error."); + break; + } + + errorText = i18n("There was a Transport Layer Security (TLS) error: %1").arg(errorCondition); + break; + + case XMPP::ClientStream::ErrAuth: + switch(streamCondition) + { + case XMPP::ClientStream::GenericAuthError: + errorCondition = i18n("Login failed with unknown reason."); + break; + case XMPP::ClientStream::NoMech: + errorCondition = i18n("No appropriate authentication mechanism available."); + break; + case XMPP::ClientStream::BadProto: + errorCondition = i18n("Bad SASL authentication protocol."); + break; + case XMPP::ClientStream::BadServ: + errorCondition = i18n("Server failed mutual authentication."); + break; + case XMPP::ClientStream::EncryptionRequired: + errorCondition = i18n("Encryption is required but not present."); + break; + case XMPP::ClientStream::InvalidAuthzid: + errorCondition = i18n("Invalid user ID."); + break; + case XMPP::ClientStream::InvalidMech: + errorCondition = i18n("Invalid mechanism."); + break; + case XMPP::ClientStream::InvalidRealm: + errorCondition = i18n("Invalid realm."); + break; + case XMPP::ClientStream::MechTooWeak: + errorCondition = i18n("Mechanism too weak."); + break; + case XMPP::ClientStream::NotAuthorized: + errorCondition = i18n("Wrong credentials supplied. (check your user ID and password)"); + break; + case XMPP::ClientStream::TemporaryAuthFailure: + errorCondition = i18n("Temporary failure, please try again later."); + break; + default: + errorCondition = i18n("Unknown error."); + break; + } + + errorText = i18n("There was an error authenticating with the server: %1").arg(errorCondition); + break; + + case XMPP::ClientStream::ErrSecurityLayer: + switch(streamCondition) + { + case XMPP::ClientStream::LayerTLS: + errorCondition = i18n("Transport Layer Security (TLS) problem."); + break; + case XMPP::ClientStream::LayerSASL: + errorCondition = i18n("Simple Authentication and Security Layer (SASL) problem."); + break; + default: + errorCondition = i18n("Unknown error."); + break; + } + + errorText = i18n("There was an error in the security layer: %1").arg(errorCondition); + break; + + case XMPP::ClientStream::ErrBind: + switch(streamCondition) + { + case XMPP::ClientStream::BindNotAllowed: + errorCondition = i18n("No permission to bind the resource."); + break; + case XMPP::ClientStream::BindConflict: + errorCondition = i18n("The resource is already in use."); + break; + default: + errorCondition = i18n("Unknown error."); + break; + } + + errorText = i18n("Could not bind a resource: %1").arg(errorCondition); + break; + + default: + errorText = i18n("Unknown error."); + break; + } + + /* + * This mustn't be queued as otherwise the reconnection + * API will attempt to reconnect, queueing another + * error until memory is exhausted. + */ + if(!errorText.isEmpty()) + KMessageBox::error (Kopete::UI::Global::mainWidget (), + errorText, + i18n("Connection problem with Jabber server %1").arg(server)); + + +} + +void JabberAccount::slotCSError ( int error ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Error in stream signalled." << endl; + + if ( ( error == XMPP::ClientStream::ErrAuth ) + && ( client()->clientStream()->errorCondition () == XMPP::ClientStream::NotAuthorized ) ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Incorrect password, retrying." << endl; + disconnect(Kopete::Account::BadPassword); + } + else + { + Kopete::Account::DisconnectReason errorClass = Kopete::Account::Unknown; + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Disconnecting." << endl; + + // display message to user + if(!m_removing) //when removing the account, connection errors are normal. + handleStreamError (error, client()->clientStream()->errorCondition (), client()->clientConnector()->errorCode (), server (), errorClass); + + disconnect ( errorClass ); + + /* slotCSDisconnected will not be called*/ + resourcePool()->clear(); + } + +} + +/* Set presence (usually called by dialog widget). */ +void JabberAccount::setPresence ( const XMPP::Status &status ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Status: " << status.show () << ", Reason: " << status.status () << endl; + + // fetch input status + XMPP::Status newStatus = status; + + // TODO: Check if Caps is enabled + // Send entity capabilities + if( client() ) + { + newStatus.setCapsNode( client()->capsNode() ); + newStatus.setCapsVersion( client()->capsVersion() ); + newStatus.setCapsExt( client()->capsExt() ); + } + + // make sure the status gets the correct priority + newStatus.setPriority ( configGroup()->readNumEntry ( "Priority", 5 ) ); + + XMPP::Jid jid ( myself()->contactId () ); + XMPP::Resource newResource ( resource (), newStatus ); + + // update our resource in the resource pool + resourcePool()->addResource ( jid, newResource ); + + // make sure that we only consider our own resource locally + resourcePool()->lockToResource ( jid, newResource ); + + /* + * Unless we are in the connecting status, send a presence packet to the server + */ + if(status.show () != QString("connecting") ) + { + /* + * Make sure we are actually connected before sending out a packet. + */ + if (isConnected()) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Sending new presence to the server." << endl; + + XMPP::JT_Presence * task = new XMPP::JT_Presence ( client()->rootTask ()); + + task->pres ( newStatus ); + task->go ( true ); + } + else + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "We were not connected, presence update aborted." << endl; + } + } + +} + +void JabberAccount::slotSendRaw () +{ + /* Check if we're connected. */ + if ( !isConnected () ) + { + errorConnectFirst (); + return; + } + + new dlgJabberSendRaw ( client (), Kopete::UI::Global::mainWidget()); + +} + +void JabberAccount::slotSubscription (const XMPP::Jid & jid, const QString & type) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << jid.full () << ", " << type << endl; + + if (type == "subscribe") + { + /* + * A user wants to subscribe to our presence. + */ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << jid.full () << " is asking for authorization to subscribe." << endl; + + // Is the user already in our contact list? + JabberBaseContact *contact = contactPool()->findExactMatch( jid ); + Kopete::MetaContact *metaContact=0L; + if(contact) + metaContact=contact->metaContact(); + + int hideFlags=Kopete::UI::ContactAddedNotifyDialog::InfoButton; + if( metaContact && !metaContact->isTemporary() ) + hideFlags |= Kopete::UI::ContactAddedNotifyDialog::AddCheckBox | Kopete::UI::ContactAddedNotifyDialog::AddGroupBox ; + + Kopete::UI::ContactAddedNotifyDialog *dialog= + new Kopete::UI::ContactAddedNotifyDialog( jid.full() ,QString::null,this, hideFlags ); + QObject::connect(dialog,SIGNAL(applyClicked(const QString&)), + this,SLOT(slotContactAddedNotifyDialogClosed(const QString& ))); + dialog->show(); + } + else if (type == "unsubscribed") + { + /* + * Someone else removed our authorization to see them. + */ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << jid.full() << " revoked our presence authorization" << endl; + + XMPP::JT_Roster *task; + + switch (KMessageBox::warningYesNo (Kopete::UI::Global::mainWidget(), + i18n + ("The Jabber user %1 removed %2's subscription to them. " + "This account will no longer be able to view their online/offline status. " + "Do you want to delete the contact?"). + arg (jid.full(), 1).arg (accountId(), 2), i18n ("Notification"), KStdGuiItem::del(), i18n("Keep"))) + { + + case KMessageBox::Yes: + /* + * Delete this contact from our roster. + */ + task = new XMPP::JT_Roster ( client()->rootTask ()); + + task->remove (jid); + task->go (true); + + break; + + default: + /* + * We want to leave the contact in our contact list. + * In this case, we need to delete all the resources + * we have for it, as the Jabber server won't signal us + * that the contact is offline now. + */ + resourcePool()->removeAllResources ( jid ); + break; + + } + } +} + +void JabberAccount::slotContactAddedNotifyDialogClosed( const QString & contactid ) +{ // the dialog that asked the authorisation is closed. (it was shown in slotSubscrition) + + XMPP::JT_Presence *task; + XMPP::Jid jid(contactid); + + const Kopete::UI::ContactAddedNotifyDialog *dialog = + dynamic_cast(sender()); + if(!dialog || !isConnected()) + return; + + if ( dialog->authorized() ) + { + /* + * Authorize user. + */ + + task = new XMPP::JT_Presence ( client()->rootTask () ); + task->sub ( jid, "subscribed" ); + task->go ( true ); + } + else + { + /* + * Reject subscription. + */ + task = new XMPP::JT_Presence ( client()->rootTask () ); + task->sub ( jid, "unsubscribed" ); + task->go ( true ); + } + + + if(dialog->added()) + { + Kopete::MetaContact *parentContact=dialog->addContact(); + if(parentContact) + { + QStringList groupNames; + Kopete::GroupList groupList = parentContact->groups(); + for(Kopete::Group *group = groupList.first(); group; group = groupList.next()) + groupNames += group->displayName(); + + XMPP::RosterItem item; +// XMPP::Jid jid ( contactId ); + + item.setJid ( jid ); + item.setName ( parentContact->displayName() ); + item.setGroups ( groupNames ); + + // add the new contact to our roster. + XMPP::JT_Roster * rosterTask = new XMPP::JT_Roster ( client()->rootTask () ); + + rosterTask->set ( item.jid(), item.name(), item.groups() ); + rosterTask->go ( true ); + + // send a subscription request. + XMPP::JT_Presence *presenceTask = new XMPP::JT_Presence ( client()->rootTask () ); + + presenceTask->sub ( jid, "subscribe" ); + presenceTask->go ( true ); + } + } +} + + + +void JabberAccount::slotContactUpdated (const XMPP::RosterItem & item) +{ + + /** + * Subscription types are: Both, From, To, Remove, None. + * Both: Both sides have authed each other, each side + * can see each other's presence + * From: The other side can see us. + * To: We can see the other side. (implies we are + * authed) + * Remove: Other side revoked our subscription request. + * Not to be handled here. + * None: No subscription. + * + * Regardless of the subscription type, we have to add + * a roster item here. + */ + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "New roster item " << item.jid().full () << " (Subscription: " << item.subscription().toString () << ")" << endl; + + /* + * See if the contact need to be added, according to the criterias of + * JEP-0162: Best Practices for Roster and Subscription Management + * http://www.jabber.org/jeps/jep-0162.html#contacts + */ + bool need_to_add=false; + if(item.subscription().type() == XMPP::Subscription::Both || item.subscription().type() == XMPP::Subscription::To) + need_to_add = true; + else if( !item.ask().isEmpty() ) + need_to_add = true; + else if( !item.name().isEmpty() || !item.groups().isEmpty() ) + need_to_add = true; + + /* + * See if the contact is already on our contact list + */ + Kopete::Contact *c= contactPool()->findExactMatch( item.jid() ); + + if( c && c == c->Kopete::Contact::account()->myself() ) //don't use JabberBaseContact::account() which return alwaus the JabberAccount, and not the transport + { + // don't let remove the gateway contact, eh! + need_to_add = true; + } + + if(need_to_add) + { + Kopete::MetaContact *metaContact=0L; + if (!c) + { + /* + * No metacontact has been found which contains a contact with this ID, + * so add a new metacontact to the list. + */ + metaContact = new Kopete::MetaContact (); + QStringList groups = item.groups (); + + // add this metacontact to all groups the contact is a member of + for (QStringList::Iterator it = groups.begin (); it != groups.end (); ++it) + metaContact->addToGroup (Kopete::ContactList::self ()->findGroup (*it)); + + // put it onto contact list + Kopete::ContactList::self ()->addMetaContact ( metaContact ); + } + else + { + metaContact=c->metaContact(); + //TODO: syncronize groups + } + + /* + * Add / update the contact in our pool. In case the contact is already there, + * it will be updated. In case the contact is not in the meta contact yet, it + * will be added to it. + * The "dirty" flag is false here, because we just received the contact from + * the server's roster. As such, it is now a synchronized entry. + */ + JabberContact *contact = contactPool()->addContact ( item, metaContact, false ); + + /* + * Set authorization property + */ + if ( !item.ask().isEmpty () ) + { + contact->setProperty ( protocol()->propAuthorizationStatus, i18n ( "Waiting for authorization" ) ); + } + else + { + contact->removeProperty ( protocol()->propAuthorizationStatus ); + } + } + else if(c) //we don't need to add it, and it is in the contactlist + { + Kopete::MetaContact *metaContact=c->metaContact(); + if(metaContact->isTemporary()) + return; + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << c->contactId() << + " is on the contactlist while it shouldn't. we are removing it. - " << c << endl; + delete c; + if(metaContact->contacts().isEmpty()) + Kopete::ContactList::self()->removeMetaContact( metaContact ); + } + +} + +void JabberAccount::slotContactDeleted (const XMPP::RosterItem & item) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Deleting contact " << item.jid().full () << endl; + + // since the contact instance will get deleted here, the GUI should be updated + contactPool()->removeContact ( item.jid () ); + +} + +void JabberAccount::slotReceivedMessage (const XMPP::Message & message) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "New message from " << message.from().full () << endl; + + JabberBaseContact *contactFrom; + + if ( message.type() == "groupchat" ) + { + // this is a group chat message, forward it to the group contact + // (the one without resource name) + XMPP::Jid jid ( message.from().userHost () ); + + // try to locate an exact match in our pool first + contactFrom = contactPool()->findExactMatch ( jid ); + + /** + * If there was no exact match, something is really messed up. + * We can't receive group chat messages from rooms that we are + * not a member of and if the room contact vanished somehow, + * we're in deep trouble. + */ + if ( !contactFrom ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "WARNING: Received a groupchat message but couldn't find room contact. Ignoring message." << endl; + return; + } + } + else + { + // try to locate an exact match in our pool first + contactFrom = contactPool()->findExactMatch ( message.from () ); + + if ( !contactFrom ) + { + // we have no exact match, try a broader search + contactFrom = contactPool()->findRelevantRecipient ( message.from () ); + } + + // see if we found the contact in our pool + if ( !contactFrom ) + { + // eliminate the resource from this contact, + // otherwise we will add the contact with the + // resource to our list + // NOTE: This is a stupid way to do it, but + // message.from().setResource("") had no + // effect. Iris bug? + XMPP::Jid jid ( message.from().userHost () ); + + // the contact is not in our pool, add it as a temporary contact + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << jid.full () << " is unknown to us, creating temporary contact." << endl; + + Kopete::MetaContact *metaContact = new Kopete::MetaContact (); + + metaContact->setTemporary (true); + + contactFrom = contactPool()->addContact ( XMPP::RosterItem ( jid ), metaContact, false ); + + Kopete::ContactList::self ()->addMetaContact (metaContact); + } + } + + // pass the message on to the contact + contactFrom->handleIncomingMessage (message); + +} + +void JabberAccount::slotJoinNewChat () +{ + + if (!isConnected ()) + { + errorConnectFirst (); + return; + } + + dlgJabberChatJoin *joinDialog = new dlgJabberChatJoin ( this, Kopete::UI::Global::mainWidget () ); + joinDialog->show (); + +} + +void JabberAccount::slotGroupChatJoined (const XMPP::Jid & jid) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Joined group chat " << jid.full () << endl; + + // Create new meta contact that holds the group chat contact. + Kopete::MetaContact *metaContact = new Kopete::MetaContact (); + + metaContact->setTemporary ( true ); + + // Create a groupchat contact for this room + JabberGroupContact *groupContact = dynamic_cast( contactPool()->addGroupContact ( XMPP::RosterItem ( jid ), true, metaContact, false ) ); + + if(groupContact) + { + // Add the groupchat contact to the meta contact. + //metaContact->addContact ( groupContact ); + + Kopete::ContactList::self ()->addMetaContact ( metaContact ); + } + else + delete metaContact; + + /** + * Add an initial resource for this contact to the pool. We need + * to do this to be able to lock the group status to our own presence. + * Our own presence will be updated right after this method returned + * by slotGroupChatPresence(), since the server will signal our own + * presence back to us. + */ + resourcePool()->addResource ( XMPP::Jid ( jid.userHost () ), XMPP::Resource ( jid.resource () ) ); + + // lock the room to our own status + resourcePool()->lockToResource ( XMPP::Jid ( jid.userHost () ), jid.resource () ); + + m_bookmarks->insertGroupChat(jid); +} + +void JabberAccount::slotGroupChatLeft (const XMPP::Jid & jid) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo "Left groupchat " << jid.full () << endl; + + // remove group contact from list + Kopete::Contact *contact = + Kopete::ContactList::self()->findContact( protocol()->pluginId() , accountId() , jid.userHost() ); + + if ( contact ) + { + Kopete::MetaContact *metaContact= contact->metaContact(); + if( metaContact && metaContact->isTemporary() ) + Kopete::ContactList::self()->removeMetaContact ( metaContact ); + else + contact->deleteLater(); + } + + // now remove it from our pool, which should clean up all subcontacts as well + contactPool()->removeContact ( XMPP::Jid ( jid.userHost () ) ); + +} + +void JabberAccount::slotGroupChatPresence (const XMPP::Jid & jid, const XMPP::Status & status) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Received groupchat presence for room " << jid.full () << endl; + + // fetch room contact (the one without resource) + JabberGroupContact *groupContact = dynamic_cast( contactPool()->findExactMatch ( XMPP::Jid ( jid.userHost () ) ) ); + + if ( !groupContact ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "WARNING: Groupchat presence signalled, but we don't have a room contact?" << endl; + return; + } + + if ( !status.isAvailable () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << jid.full () << " has become unavailable, removing from room" << endl; + + // remove the resource from the pool + resourcePool()->removeResource ( jid, XMPP::Resource ( jid.resource (), status ) ); + + // the person has become unavailable, remove it + groupContact->removeSubContact ( XMPP::RosterItem ( jid ) ); + } + else + { + // add a resource for this contact to the pool (existing resources will be updated) + resourcePool()->addResource ( jid, XMPP::Resource ( jid.resource (), status ) ); + + // make sure the contact exists in the room (if it exists already, it won't be added twice) + groupContact->addSubContact ( XMPP::RosterItem ( jid ) ); + } + +} + +void JabberAccount::slotGroupChatError (const XMPP::Jid &jid, int error, const QString &reason) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Group chat error - room " << jid.full () << " had error " << error << " (" << reason << ")" << endl; + + switch (error) + { + case JabberClient::InvalidPasswordForMUC: + { + QCString password; + int result = KPasswordDialog::getPassword(password, i18n("A password is required to join the room %1.").arg(jid.node())); + if (result == KPasswordDialog::Accepted) + m_jabberClient->joinGroupChat(jid.domain(), jid.node(), jid.resource(), password); + } + break; + + case JabberClient::NicknameConflict: + { + bool ok; + QString nickname = KInputDialog::getText(i18n("Error trying to join %1 : nickname %2 is already in use").arg(jid.node(), jid.resource()), + i18n("Give your nickname"), + QString(), + &ok); + if (ok) + { + m_jabberClient->joinGroupChat(jid.domain(), jid.node(), nickname); + } + } + break; + + case JabberClient::BannedFromThisMUC: + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), + KMessageBox::Error, + i18n ("You can't join the room %1 because you were banned").arg(jid.node()), + i18n ("Jabber Group Chat") ); + break; + + case JabberClient::MaxUsersReachedForThisMuc: + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), + KMessageBox::Error, + i18n ("You can't join the room %1 because the maximum users has been reached").arg(jid.node()), + i18n ("Jabber Group Chat") ); + break; + + default: + { + QString detailedReason = reason.isEmpty () ? i18n ( "No reason given by the server" ) : reason; + + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), + KMessageBox::Error, + i18n ("There was an error processing your request for group chat %1. (Reason: %2, Code %3)").arg ( jid.full (), detailedReason, QString::number ( error ) ), + i18n ("Jabber Group Chat") ); + } + } +} + +void JabberAccount::slotResourceAvailable (const XMPP::Jid & jid, const XMPP::Resource & resource) +{ + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "New resource available for " << jid.full() << endl; + + resourcePool()->addResource ( jid, resource ); + +} + +void JabberAccount::slotResourceUnavailable (const XMPP::Jid & jid, const XMPP::Resource & resource) +{ + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Resource now unavailable for " << jid.full () << endl; + + resourcePool()->removeResource ( jid, resource ); + +} + +void JabberAccount::slotEditVCard () +{ + static_cast( myself() )->slotUserInfo (); +} + +void JabberAccount::slotGlobalIdentityChanged (const QString &key, const QVariant &value) +{ + // Check if this account is excluded from Global Identity. + if( !configGroup()->readBoolEntry("ExcludeGlobalIdentity", false) ) + { + JabberContact *jabberMyself = static_cast( myself() ); + if( key == Kopete::Global::Properties::self()->nickName().key() ) + { + QString oldNick = jabberMyself->property( protocol()->propNickName ).value().toString(); + QString newNick = value.toString(); + + if( newNick != oldNick && isConnected() ) + { + jabberMyself->setProperty( protocol()->propNickName, newNick ); + jabberMyself->slotSendVCard(); + } + } + if( key == Kopete::Global::Properties::self()->photo().key() ) + { + if( isConnected() ) + { + jabberMyself->setPhoto( value.toString() ); + jabberMyself->slotSendVCard(); + } + } + } +} + +const QString JabberAccount::resource () const +{ + + return configGroup()->readEntry ( "Resource", "Kopete" ); + +} + +const QString JabberAccount::server () const +{ + + return configGroup()->readEntry ( "Server" ); + +} + +const int JabberAccount::port () const +{ + + return configGroup()->readNumEntry ( "Port", 5222 ); + +} + +void JabberAccount::slotGetServices () +{ + dlgJabberServices *dialog = new dlgJabberServices (this); + + dialog->show (); + dialog->raise (); +} + +void JabberAccount::slotIncomingVoiceCall( const Jid &jid ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << endl; +#ifdef SUPPORT_JINGLE + if(voiceCaller()) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Showing voice dialog." << endl; + JingleVoiceSessionDialog *voiceDialog = new JingleVoiceSessionDialog( jid, voiceCaller() ); + voiceDialog->show(); + } +#else + Q_UNUSED(jid); +#endif +} + +// void JabberAccount::slotIncomingJingleSession( const QString &sessionType, JingleSession *session ) +// { +// #ifdef SUPPORT_JINGLE +// if(sessionType == "http://www.google.com/session/phone") +// { +// QString from = ((XMPP::Jid)session->peers().first()).full(); +// //KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Information, QString("Received a voice session invitation from %1.").arg(from) ); +// JingleVoiceSessionDialog *voiceDialog = new JingleVoiceSessionDialog( static_cast(session) ); +// voiceDialog->show(); +// } +// #else +// Q_UNUSED( sessionType ); +// Q_UNUSED( session ); +// #endif +// } + + +void JabberAccount::addTransport( JabberTransport * tr, const QString &jid ) +{ + m_transports.insert(jid,tr); +} + +void JabberAccount::removeTransport( const QString &jid ) +{ + m_transports.remove(jid); +} + +bool JabberAccount::removeAccount( ) +{ + if(!m_removing) + { + int result=KMessageBox::warningYesNoCancel( Kopete::UI::Global::mainWidget () , + i18n( "Do you want to also unregister \"%1\" from the Jabber server ?\n" + "If you unregister, all your contact list may be removed on the server," + "And you will never be able to connect to this account with any client").arg( accountLabel() ), + i18n("Unregister"), + KGuiItem(i18n( "Remove and Unregister" ), "editdelete"), + KGuiItem(i18n( "Remove from kopete only"), "edittrash"), + QString(), KMessageBox::Notify | KMessageBox::Dangerous ); + if(result == KMessageBox::Cancel) + { + return false; + } + else if(result == KMessageBox::Yes) + { + if (!isConnected()) + { + errorConnectFirst (); + return false; + } + + XMPP::JT_Register *task = new XMPP::JT_Register ( client()->rootTask () ); + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT ( slotUnregisterFinished ) ); + task->unreg (); + task->go ( true ); + m_removing=true; + // from my experiment, not all server reply us with a response. it simply dosconnect + // so after one seconde, we will force to remove the account + QTimer::singleShot(1111, this, SLOT(slotUnregisterFinished())); + + return false; //the account will be removed when the task will be finished + } + } + + //remove transports from config file. + QMap tranposrts_copy=m_transports; + QMap::Iterator it; + for ( it = tranposrts_copy.begin(); it != tranposrts_copy.end(); ++it ) + { + (*it)->jabberAccountRemoved(); + } + return true; +} + +void JabberAccount::slotUnregisterFinished( ) +{ + const XMPP::JT_Register * task = dynamic_cast(sender ()); + + if ( task && ! task->success ()) + { + KMessageBox::queuedMessageBox ( 0L, KMessageBox::Error, + i18n ("An error occured when trying to remove the account:\n%1").arg(task->statusString()), + i18n ("Jabber Account Unregistration")); + m_removing=false; + return; + } + if(m_removing) //it may be because this is now the timer. + Kopete::AccountManager::self()->removeAccount( this ); //this will delete this +} + + + + + +#include "jabberaccount.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/protocols/jabber/jabberaccount.h b/kopete/protocols/jabber/jabberaccount.h new file mode 100644 index 00000000..3731b590 --- /dev/null +++ b/kopete/protocols/jabber/jabberaccount.h @@ -0,0 +1,309 @@ + +/*************************************************************************** + jabberaccount.h - core Jabber account class + ------------------- + begin : Sat Mar 8 2003 + copyright : (C) 2003 by Till Gerken + Based on JabberProtocol by Daniel Stone + and Till Gerken . + copyright : (C) 2006 by Olivier Goffart + + Kopete (C) 2001-2003 Kopete developers . + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef JABBERACCOUNT_H +#define JABBERACCOUNT_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +// we need these for type reasons +#include +#include +#include +#include "jabberclient.h" + +class QString; +class QStringList; +class KActionMenu; +class JabberResourcePool; +class JabberContact; +class JabberContactPool; +class JabberProtocol; +class JabberTransport; +class JabberBookmarks; + +namespace Kopete { class MetaContact; } + +#ifdef SUPPORT_JINGLE +//class JingleSessionManager; +//class JingleSession; +class VoiceCaller; +#endif + + +/* @author Daniel Stone, Till Gerken */ + +class JabberAccount : public Kopete::PasswordedAccount +{ + Q_OBJECT + +public: + JabberAccount (JabberProtocol * parent, const QString & accountID, const char *name = 0L); + ~JabberAccount (); + + /* Returns the action menu for this account. */ + virtual KActionMenu *actionMenu (); + + /* Return the resource of the client */ + const QString resource () const; + const QString server () const; + const int port () const; + + JabberResourcePool *resourcePool (); + JabberContactPool *contactPool (); + + /* to get the protocol from the account */ + JabberProtocol *protocol () const + { + return m_protocol; + } + + JabberClient *client () const + { + return m_jabberClient; + } + +#ifdef SUPPORT_JINGLE + VoiceCaller *voiceCaller() const + { + return m_voiceCaller; + } + +// JingleSessionManager *sessionManager() const +// { +// return m_jingleSessionManager; +// } +#endif + + // change the default S5B server port + void setS5BServerPort ( int port ); + + /* Tells the user to connect first before they can do whatever it is + * that they want to do. */ + void errorConnectFirst (); + + /* Tells the user that the connection was lost while we waited for + * an answer of him. */ + void errorConnectionLost (); + + /* + * Handle TLS warnings. Displays a dialog and returns the user's choice. + * Parameters: Warning code from QCA::TLS + * Automatically resumes the stream if wanted. + */ + /** + * Handle a TLS warning. Displays a dialog and returns if the + * stream can be continued or not. + * @param client JabberClient instance + * @param warning Warning code from QCA::TLS + * @return True if stream can be resumed. + */ + static bool handleTLSWarning ( JabberClient *client, int warning ); + + /* + * Handle stream errors. Displays a dialog and returns. + */ + static void handleStreamError (int streamError, int streamCondition, int connectorCode, const QString &server, Kopete::Account::DisconnectReason &errorClass); + + const QMap &transports() + { return m_transports; } + + + /** + * called when the account is removed in the config ui + */ + virtual bool removeAccount(); + +public slots: + /* Connects to the server. */ + void connectWithPassword ( const QString &password ); + + /* Disconnects from the server. */ + void disconnect (); + + /* Disconnect with a reason */ + void disconnect ( Kopete::Account::DisconnectReason reason ); + + /* Disconnect with a reason, and status */ + void disconnect( Kopete::Account::DisconnectReason reason, XMPP::Status &status ); + /* Reimplemented from Kopete::Account */ + void setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason = QString::null); + + void addTransport( JabberTransport *tr , const QString &jid); + void removeTransport( const QString &jid ); + + +protected: + /** + * Create a new contact in the specified metacontact + * + * You shouldn't ever call this method yourself, For adding contacts see @ref addContact() + * + * This method is called by @ref Kopete::Account::addContact() in this method, you should + * simply create the new custom @ref Kopete::Contact in the given metacontact. You should + * NOT add the contact to the server here as this method gets only called when synchronizing + * the contact list on disk with the one in memory. As such, all created contacts from this + * method should have the "dirty" flag set. + * + * This method should simply be used to intantiate the new contact, everything else + * (updating the GUI, parenting to meta contact, etc.) is being taken care of. + * + * @param contactId The unique ID for this protocol + * @param parentContact The metacontact to add this contact to + */ + virtual bool createContact (const QString & contactID, Kopete::MetaContact * parentContact); + + + +private: + JabberProtocol *m_protocol; + + // backend for this account + JabberClient *m_jabberClient; + + JabberResourcePool *m_resourcePool; + JabberContactPool *m_contactPool; + +#ifdef SUPPORT_JINGLE + VoiceCaller *m_voiceCaller; + //JingleSessionManager *m_jingleSessionManager; +#endif + + JabberBookmarks *m_bookmarks; + + /* Set up our actions for the status menu. */ + void initActions (); + + void cleanup (); + + /* Initial presence to set after connecting. */ + XMPP::Status m_initialPresence; + + /** + * Sets our own presence. Updates our resource in the + * resource pool and sends a presence packet to the server. + */ + void setPresence ( const XMPP::Status &status ); + + /** + * Returns if a connection attempt is currently in progress. + */ + bool isConnecting (); + + QMapm_transports; + + /* used in removeAccount() */ + bool m_removing; + /* keep track if we told the user we were not able to bind the + jabber transfer port, to avoid popup insanity */ + bool m_notifiedUserCannotBindTransferPort; +private slots: + /* Connects to the server. */ + void slotConnect (); + + /* Disconnects from the server. */ + void slotDisconnect (); + + // handle a TLS warning + void slotHandleTLSWarning ( int validityResult ); + + // handle client errors + void slotClientError ( JabberClient::ErrorCode errorCode ); + + // we are connected to the server + void slotConnected (); + + /* Called from Psi: tells us when we've been disconnected from the server. */ + void slotCSDisconnected (); + + /* Called from Psi: alerts us to a protocol error. */ + void slotCSError (int); + + /* Called from Psi: roster request finished */ + void slotRosterRequestFinished ( bool success ); + + /* Called from Psi: incoming file transfer */ + void slotIncomingFileTransfer (); + + /* Called from Psi: debug messages from the backend. */ + void slotClientDebugMessage (const QString &msg); + + /* Sends a raw message to the server (use with caution) */ + void slotSendRaw (); + + /* Slots for handling group chats. */ + void slotJoinNewChat (); + void slotGroupChatJoined ( const XMPP::Jid &jid ); + void slotGroupChatLeft ( const XMPP::Jid &jid ); + void slotGroupChatPresence ( const XMPP::Jid &jid, const XMPP::Status &status ); + void slotGroupChatError ( const XMPP::Jid &jid, int error, const QString &reason ); + + /* Incoming subscription request. */ + void slotSubscription ( const XMPP::Jid &jid, const QString &type ); + + /* the dialog that asked to add the contact was closed (that dialog is shown in slotSubscription) */ + void slotContactAddedNotifyDialogClosed(const QString& contactid); + + /** + * A new item appeared in our roster, synch it with the + * contact list. + * (or the contact has been updated + */ + void slotContactUpdated ( const XMPP::RosterItem & ); + + /** + * An item has been deleted from our roster, + * delete it from our contact pool. + */ + void slotContactDeleted ( const XMPP::RosterItem & ); + + + /* Someone on our contact list had (another) resource come online. */ + void slotResourceAvailable ( const XMPP::Jid &, const XMPP::Resource & ); + + /* Someone on our contact list had (another) resource go offline. */ + void slotResourceUnavailable ( const XMPP::Jid &, const XMPP::Resource & ); + + /* Displays a new message. */ + void slotReceivedMessage ( const XMPP::Message & ); + + /* Gets the user's vCard from the server for editing. */ + void slotEditVCard (); + + /* Get the services list from the server for management. */ + void slotGetServices (); + + /* Update the myself information if the global identity changes. */ + void slotGlobalIdentityChanged( const QString &key, const QVariant &value ); + + /* we received a voice invitation */ + void slotIncomingVoiceCall(const Jid&); + + /* the unregister task finished */ + void slotUnregisterFinished(); + + //void slotIncomingJingleSession(const QString &sessionType, JingleSession *session); +}; + +#endif diff --git a/kopete/protocols/jabber/jabberbasecontact.cpp b/kopete/protocols/jabber/jabberbasecontact.cpp new file mode 100644 index 00000000..56cc1de4 --- /dev/null +++ b/kopete/protocols/jabber/jabberbasecontact.cpp @@ -0,0 +1,676 @@ + /* + * jabbercontact.cpp - Base class for the Kopete Jabber protocol contact + * + * Copyright (c) 2002-2004 by Till Gerken + * Copyright (c) 2006 by Olivier Goffart + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include + +#include "jabberbasecontact.h" + +#include "xmpp_tasks.h" + +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabberresource.h" +#include "jabberresourcepool.h" +#include "kopetemetacontact.h" +#include "kopetemessage.h" +#include "kopeteuiglobal.h" +#include "jabbertransport.h" +#include "dlgjabbervcard.h" + + +/** + * JabberBaseContact constructor + */ +JabberBaseContact::JabberBaseContact (const XMPP::RosterItem &rosterItem, Kopete::Account *account, Kopete::MetaContact * mc, const QString &legacyId) + : Kopete::Contact (account, legacyId.isEmpty() ? rosterItem.jid().full() : legacyId , mc ) +{ + setDontSync ( false ); + + JabberTransport *t=transport(); + m_account= t ? t->account() : static_cast(Kopete::Contact::account()); + + + // take roster item and update display name + updateContact ( rosterItem ); + +} + + +JabberProtocol *JabberBaseContact::protocol () +{ + + return static_cast(Kopete::Contact::protocol ()); +} + + +JabberTransport * JabberBaseContact::transport( ) +{ + return dynamic_cast(Kopete::Contact::account()); +} + + +/* Return if we are reachable (defaults to true because + we can send on- and offline, only return false if the + account itself is offline, too) */ +bool JabberBaseContact::isReachable () +{ + if (account()->isConnected()) + return true; + + return false; + +} + +void JabberBaseContact::updateContact ( const XMPP::RosterItem & item ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Synchronizing local copy of " << contactId() << " with information received from server. (name='" << item.name() << "' groups='" << item.groups() << "')"<< endl; + + mRosterItem = item; + + // if we don't have a meta contact yet, stop processing here + if ( !metaContact () ) + return; + + /* + * We received the information from the server, as such, + * don't attempt to synch while we update our local copy. + */ + setDontSync ( true ); + + // The myself contact is not in the roster on server, ignore this code + // because the myself MetaContact displayname become the latest + // Jabber acccount jid. + if( metaContact() != Kopete::ContactList::self()->myself() ) + { + // only update the alias if its not empty + if ( !item.name().isEmpty () && item.name() != item.jid().bare() ) + { + QString newName = item.name (); + QString oldName = metaContact()->displayName(); + Kopete::Contact *source=metaContact()->displayNameSourceContact(); +// kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "setting display name of " << contactId () << " to " << newName << endl; + metaContact()->setDisplayName ( newName ); + //automatically set to custom source if the source is to this contact. + if( metaContact()->displayNameSource()==Kopete::MetaContact::SourceContact && newName != oldName && ( source == this || source == 0L ) ) + metaContact()->setDisplayNameSource( Kopete::MetaContact::SourceCustom ); + } + } + + /* + * Set the contact's subscription status + */ + switch ( item.subscription().type () ) + { + case XMPP::Subscription::None: + setProperty ( protocol()->propSubscriptionStatus, + i18n ( "You cannot see each others' status." ) ); + break; + case XMPP::Subscription::To: + setProperty ( protocol()->propSubscriptionStatus, + i18n ( "You can see this contact's status but they cannot see your status." ) ); + break; + case XMPP::Subscription::From: + setProperty ( protocol()->propSubscriptionStatus, + i18n ( "This contact can see your status but you cannot see their status." ) ); + break; + case XMPP::Subscription::Both: + setProperty ( protocol()->propSubscriptionStatus, + i18n ( "You can see each others' status." ) ); + break; + } + + if( !metaContact()->isTemporary() ) + { + /* + * In this method, as opposed to KC::syncGroups(), + * the group list from the server is authoritative. + * As such, we need to find a list of all groups + * that the meta contact resides in but does not + * reside in on the server anymore, as well as all + * groups that the meta contact does not reside in, + * but resides in on the server. + * Then, we'll have to synchronize the KMC using + * that information. + */ + Kopete::GroupList groupsToRemoveFrom, groupsToAddTo; + + // find all groups our contact is in but that are not in the server side roster + for ( unsigned i = 0; i < metaContact()->groups().count (); i++ ) + { + if ( item.groups().find ( metaContact()->groups().at(i)->displayName () ) == item.groups().end () ) + groupsToRemoveFrom.append ( metaContact()->groups().at ( i ) ); + } + + // now find all groups that are in the server side roster but not in the local group + for ( unsigned i = 0; i < item.groups().count (); i++ ) + { + bool found = false; + for ( unsigned j = 0; j < metaContact()->groups().count (); j++) + { + if ( metaContact()->groups().at(j)->displayName () == *item.groups().at(i) ) + { + found = true; + break; + } + } + + if ( !found ) + { + groupsToAddTo.append ( Kopete::ContactList::self()->findGroup ( *item.groups().at(i) ) ); + } + } + + /* + * Special case: if we don't add the contact to any group and the + * list of groups to remove from contains the top level group, we + * risk removing the contact from the visible contact list. In this + * case, we need to make sure at least the top level group stays. + */ + if ( ( groupsToAddTo.count () == 0 ) && ( groupsToRemoveFrom.contains ( Kopete::Group::topLevel () ) ) ) + { + groupsToRemoveFrom.remove ( Kopete::Group::topLevel () ); + } + + for ( Kopete::Group *group = groupsToRemoveFrom.first (); group; group = groupsToRemoveFrom.next () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Removing " << contactId() << " from group " << group->displayName () << endl; + metaContact()->removeFromGroup ( group ); + } + + for ( Kopete::Group *group = groupsToAddTo.first (); group; group = groupsToAddTo.next () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Adding " << contactId() << " to group " << group->displayName () << endl; + metaContact()->addToGroup ( group ); + } + } + + /* + * Enable updates for the server again. + */ + setDontSync ( false ); + + //can't do it now because it's called from contructor at a point some virtual function are not available + QTimer::singleShot(0, this, SLOT(reevaluateStatus())); + +} + +void JabberBaseContact::updateResourceList () +{ + /* + * Set available resources. + * This is a bit more complicated: We need to generate + * all images dynamically from the KOS icons and store + * them into the mime factory, then plug them into + * the richtext. + */ + JabberResourcePool::ResourceList resourceList; + account()->resourcePool()->findResources ( rosterItem().jid() , resourceList ); + + if ( resourceList.isEmpty () ) + { + removeProperty ( protocol()->propAvailableResources ); + return; + } + + QString resourceListStr = ""; + + for ( JabberResourcePool::ResourceList::iterator it = resourceList.begin (); it != resourceList.end (); ++it ) + { + // icon, resource name and priority + resourceListStr += QString ( "" ). + arg ( protocol()->resourceToKOS((*it)->resource()).mimeSourceFor ( account () ), + (*it)->resource().name (), QString::number ( (*it)->resource().priority () ) ); + + // client name, version, OS + if ( !(*it)->clientName().isEmpty () ) + { + resourceListStr += QString ( "" ). + arg ( i18n ( "Client" ), (*it)->clientName (), (*it)->clientSystem () ); + } + + // Supported features +#if 0 //disabled because it's just an ugly and long list of incomprehensible namespaces to the user + QStringList supportedFeatures = (*it)->features().list(); + QStringList::ConstIterator featuresIt, featuresItEnd = supportedFeatures.constEnd(); + if( !supportedFeatures.empty() ) + resourceListStr += QString( "" ); +#endif + + // resource timestamp + resourceListStr += QString ( "" ). + arg ( i18n ( "Timestamp" ), KGlobal::locale()->formatDateTime ( (*it)->resource().status().timeStamp(), true, true ) ); + + // message, if any + if ( !(*it)->resource().status().status().stripWhiteSpace().isEmpty () ) + { + resourceListStr += QString ( "" ). + arg ( + i18n ( "Message" ), + Kopete::Message::escape( (*it)->resource().status().status () ) + ); + } + } + + resourceListStr += "
    %2 (Priority: %3)
    %1: %2 (%3)
    Supported Features:" ); + for( featuresIt = supportedFeatures.constBegin(); featuresIt != featuresItEnd; ++featuresIt ) + { + XMPP::Features tempFeature(*featuresIt); + resourceListStr += QString("\n
    "); + if ( tempFeature.id() > XMPP::Features::FID_None ) + resourceListStr += tempFeature.name() + QString(" ("); + resourceListStr += *featuresIt; + if ( tempFeature.id() > Features::FID_None ) + resourceListStr += QString(")"); + } + if( !supportedFeatures.empty() ) + resourceListStr += QString( "
    %1: %2
    %1: %2
    "; + + setProperty ( protocol()->propAvailableResources, resourceListStr ); +} + +void JabberBaseContact::reevaluateStatus () +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Determining new status for " << contactId () << endl; + + Kopete::OnlineStatus status; + XMPP::Resource resource = account()->resourcePool()->bestResource ( mRosterItem.jid () ); + + status = protocol()->resourceToKOS ( resource ); + + + /* Add some icon to show the subscription */ + if( ( mRosterItem.subscription().type() == XMPP::Subscription::None || mRosterItem.subscription().type() == XMPP::Subscription::From) + && inherits ( "JabberContact" ) && metaContact() != Kopete::ContactList::self()->myself() && account()->isConnected() ) + { + status = Kopete::OnlineStatus(status.status() , + status.weight() , + protocol() , + status.internalStatus() | 0x0100, + status.overlayIcons() + QStringList("status_unknown_overlay") , //FIXME: find better icon + status.description() ); + } + + + updateResourceList (); + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "New status for " << contactId () << " is " << status.description () << endl; + setOnlineStatus ( status ); + + /* + * Set away message property. + * We just need to read it from the current resource. + */ + if ( !resource.status ().status ().isEmpty () ) + { + setProperty ( protocol()->propAwayMessage, resource.status().status () ); + } + else + { + removeProperty ( protocol()->propAwayMessage ); + } + +} + +QString JabberBaseContact::fullAddress () +{ + + XMPP::Jid jid = rosterItem().jid(); + + if ( jid.resource().isEmpty () ) + { + jid.setResource ( account()->resourcePool()->bestResource ( jid ).name () ); + } + + return jid.full (); + +} + +XMPP::Jid JabberBaseContact::bestAddress () +{ + + // see if we are subscribed with a preselected resource + if ( !mRosterItem.jid().resource().isEmpty () ) + { + // we have a preselected resource, so return our default full address + return mRosterItem.jid (); + } + + // construct address out of user@host and current best resource + XMPP::Jid jid = mRosterItem.jid (); + jid.setResource ( account()->resourcePool()->bestResource( mRosterItem.jid() ).name () ); + + return jid; + +} + +void JabberBaseContact::setDontSync ( bool flag ) +{ + + mDontSync = flag; + +} + +bool JabberBaseContact::dontSync () +{ + + return mDontSync; + +} + +void JabberBaseContact::serialize (QMap < QString, QString > &serializedData, QMap < QString, QString > & /* addressBookData */ ) +{ + + // Contact id and display name are already set for us, only add the rest + serializedData["JID"] = mRosterItem.jid().full(); + + serializedData["groups"] = mRosterItem.groups ().join (QString::fromLatin1 (",")); +} + +void JabberBaseContact::slotUserInfo( ) +{ + if ( !account()->isConnected () ) + { + account()->errorConnectFirst (); + return; + } + + // Update the vCard + //slotGetTimedVCard(); + + new dlgJabberVCard ( account(), this, Kopete::UI::Global::mainWidget () ); +} + +void JabberBaseContact::setPropertiesFromVCard ( const XMPP::VCard &vCard ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Updating vCard for " << contactId () << endl; + + // update vCard cache timestamp if this is not a temporary contact + if ( metaContact() && !metaContact()->isTemporary () ) + { + setProperty ( protocol()->propVCardCacheTimeStamp, QDateTime::currentDateTime().toString ( Qt::ISODate ) ); + } + + + /* + * Set the nickname property. + * but ignore it if we are in a groupchat, or it will clash with the normal nickname + */ + if(inherits ( "JabberContact" )) + { + if ( !vCard.nickName().isEmpty () ) + { + setProperty ( protocol()->propNickName, vCard.nickName () ); + } + else + { + removeProperty ( protocol()->propNickName ); + } + } + + /** + * Kopete does not allow a modification of the "full name" + * property. However, some vCards specify only the full name, + * some specify only first and last name. + * Due to these inconsistencies, if first and last name don't + * exist, it is attempted to parse the full name. + */ + + // remove all properties first + removeProperty ( protocol()->propFirstName ); + removeProperty ( protocol()->propLastName ); + removeProperty ( protocol()->propFullName ); + + if ( !vCard.fullName().isEmpty () && vCard.givenName().isEmpty () && vCard.familyName().isEmpty () ) + { + QString lastName = vCard.fullName().section ( ' ', 0, -1 ); + QString firstName = vCard.fullName().left(vCard.fullName().length () - lastName.length ()).stripWhiteSpace (); + + setProperty ( protocol()->propFirstName, firstName ); + setProperty ( protocol()->propLastName, lastName ); + } + else + { + if ( !vCard.givenName().isEmpty () ) + setProperty ( protocol()->propFirstName, vCard.givenName () ); + + if ( !vCard.familyName().isEmpty () ) + setProperty ( protocol()->propLastName, vCard.familyName () ); + } + if( !vCard.fullName().isEmpty() ) + setProperty ( protocol()->propFullName, vCard.fullName() ); + + /* + * Set the general information + */ + removeProperty( protocol()->propJid ); + removeProperty( protocol()->propBirthday ); + removeProperty( protocol()->propTimezone ); + removeProperty( protocol()->propHomepage ); + + setProperty( protocol()->propJid, vCard.jid() ); + + if( !vCard.bdayStr().isEmpty () ) + setProperty( protocol()->propBirthday, vCard.bdayStr() ); + if( !vCard.timezone().isEmpty () ) + setProperty( protocol()->propTimezone, vCard.timezone() ); + if( !vCard.url().isEmpty () ) + setProperty( protocol()->propHomepage, vCard.url() ); + + /* + * Set the work information. + */ + removeProperty( protocol()->propCompanyName ); + removeProperty( protocol()->propCompanyDepartement ); + removeProperty( protocol()->propCompanyPosition ); + removeProperty( protocol()->propCompanyRole ); + + if( !vCard.org().name.isEmpty() ) + setProperty( protocol()->propCompanyName, vCard.org().name ); + if( !vCard.org().unit.join(",").isEmpty() ) + setProperty( protocol()->propCompanyDepartement, vCard.org().unit.join(",")) ; + if( !vCard.title().isEmpty() ) + setProperty( protocol()->propCompanyPosition, vCard.title() ); + if( !vCard.role().isEmpty() ) + setProperty( protocol()->propCompanyRole, vCard.role() ); + + /* + * Set the about information + */ + removeProperty( protocol()->propAbout ); + + if( !vCard.desc().isEmpty() ) + setProperty( protocol()->propAbout, vCard.desc() ); + + + /* + * Set the work and home addresses information + */ + removeProperty( protocol()->propWorkStreet ); + removeProperty( protocol()->propWorkExtAddr ); + removeProperty( protocol()->propWorkPOBox ); + removeProperty( protocol()->propWorkCity ); + removeProperty( protocol()->propWorkPostalCode ); + removeProperty( protocol()->propWorkCountry ); + + removeProperty( protocol()->propHomeStreet ); + removeProperty( protocol()->propHomeExtAddr ); + removeProperty( protocol()->propHomePOBox ); + removeProperty( protocol()->propHomeCity ); + removeProperty( protocol()->propHomePostalCode ); + removeProperty( protocol()->propHomeCountry ); + + for(XMPP::VCard::AddressList::const_iterator it = vCard.addressList().begin(); it != vCard.addressList().end(); it++) + { + XMPP::VCard::Address address = (*it); + + if(address.work) + { + setProperty( protocol()->propWorkStreet, address.street ); + setProperty( protocol()->propWorkExtAddr, address.extaddr ); + setProperty( protocol()->propWorkPOBox, address.pobox ); + setProperty( protocol()->propWorkCity, address.locality ); + setProperty( protocol()->propWorkPostalCode, address.pcode ); + setProperty( protocol()->propWorkCountry, address.country ); + } + else + if(address.home) + { + setProperty( protocol()->propHomeStreet, address.street ); + setProperty( protocol()->propHomeExtAddr, address.extaddr ); + setProperty( protocol()->propHomePOBox, address.pobox ); + setProperty( protocol()->propHomeCity, address.locality ); + setProperty( protocol()->propHomePostalCode, address.pcode ); + setProperty( protocol()->propHomeCountry, address.country ); + } + } + + + /* + * Delete emails first, they might not be present + * in the vCard at all anymore. + */ + removeProperty ( protocol()->propEmailAddress ); + removeProperty ( protocol()->propWorkEmailAddress ); + + /* + * Set the home and work email information. + */ + XMPP::VCard::EmailList::const_iterator emailEnd = vCard.emailList().end (); + for(XMPP::VCard::EmailList::const_iterator it = vCard.emailList().begin(); it != emailEnd; ++it) + { + XMPP::VCard::Email email = (*it); + + if(email.work) + { + if( !email.userid.isEmpty() ) + setProperty ( protocol()->propWorkEmailAddress, email.userid ); + } + else + if(email.home) + { + if( !email.userid.isEmpty() ) + setProperty ( protocol()->propEmailAddress, email.userid ); + } + } + + /* + * Delete phone number properties first as they might have + * been unset during an update and are not present in + * the vCard at all anymore. + */ + removeProperty ( protocol()->propPrivatePhone ); + removeProperty ( protocol()->propPrivateMobilePhone ); + removeProperty ( protocol()->propWorkPhone ); + removeProperty ( protocol()->propWorkMobilePhone ); + + /* + * Set phone numbers. Note that if a mobile phone number + * is specified, it's assigned to the private mobile + * phone number property. This might not be the desired + * behavior for all users. + */ + XMPP::VCard::PhoneList::const_iterator phoneEnd = vCard.phoneList().end (); + for(XMPP::VCard::PhoneList::const_iterator it = vCard.phoneList().begin(); it != phoneEnd; ++it) + { + XMPP::VCard::Phone phone = (*it); + + if(phone.work) + { + setProperty ( protocol()->propWorkPhone, phone.number ); + } + else + if(phone.fax) + { + setProperty ( protocol()->propPhoneFax, phone.number); + } + else + if(phone.cell) + { + setProperty ( protocol()->propPrivateMobilePhone, phone.number ); + } + else + if(phone.home) + { + setProperty ( protocol()->propPrivatePhone, phone.number ); + } + + } + + /* + * Set photo/avatar property. + */ + removeProperty( protocol()->propPhoto ); + + QImage contactPhoto; + QString fullJid = mRosterItem.jid().full(); + QString finalPhotoPath = locateLocal("appdata", "jabberphotos/" + fullJid.replace(QRegExp("[./~]"),"-") +".png"); + + // photo() is a QByteArray + if ( !vCard.photo().isEmpty() ) + { + kdDebug( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Contact has a photo embedded into his vCard." << endl; + + // QImage is used to save to disk in PNG later. + contactPhoto = QImage( vCard.photo() ); + } + // Contact photo is a URI. + else if( !vCard.photoURI().isEmpty() ) + { + QString tempPhotoPath = 0; + + // Downalod photo from URI. + if( !KIO::NetAccess::download( vCard.photoURI(), tempPhotoPath, 0) ) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget (), KMessageBox::Sorry, i18n( "Downloading of Jabber contact photo failed!" ) ); + return; + } + + kdDebug( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Contact photo is a URI." << endl; + + contactPhoto = QImage( tempPhotoPath ); + + KIO::NetAccess::removeTempFile( tempPhotoPath ); + } + + // Save the image to the disk, then set the property. + if( !contactPhoto.isNull() && contactPhoto.save(finalPhotoPath, "PNG") ) + { + kdDebug( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Setting photo for contact: " << fullJid << endl; + setProperty( protocol()->propPhoto, finalPhotoPath ); + } + +} + + + + +#include "jabberbasecontact.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/protocols/jabber/jabberbasecontact.h b/kopete/protocols/jabber/jabberbasecontact.h new file mode 100644 index 00000000..7ba5c3fb --- /dev/null +++ b/kopete/protocols/jabber/jabberbasecontact.h @@ -0,0 +1,185 @@ + /* + * jabbercontact.h - Base class for the Kopete Jabber protocol contact + * + * Copyright (c) 2002-2004 by Till Gerken + * Copyright (c) 2002 by Daniel Stone + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERBASECONTACT_H +#define JABBERBASECONTACT_H + +#include "kopetecontact.h" +#include "xmpp.h" +#include "im.h" + +class dlgJabberVCard; +class JabberProtocol; +class JabberAccount; +class JabberResource; +class JabberTransport; +namespace Kopete { class MetaContact; } +namespace XMPP { class VCard; } + +class JabberBaseContact : public Kopete::Contact +{ + +Q_OBJECT +friend class JabberAccount; /* Friends can touch each other's private parts. */ + +public: + + /** + * @param legacyId is the contactId of the contact if != Jid + */ + JabberBaseContact (const XMPP::RosterItem &rosterItem, + Kopete::Account *account, Kopete::MetaContact * mc, + const QString &legacyId=QString()); + + /******************************************************************** + * + * Kopete::Contact reimplementation start + * + ********************************************************************/ + + /** + * Return the protocol instance associated with this contact + */ + JabberProtocol *protocol (); + + /** + * Return the account instance associated with this contact + */ + JabberAccount *account () const { return m_account; }; + + /** + * return the transport if any, or null + */ + JabberTransport *transport(); + + /** + * Return if the contact is reachable (this is true if the account + * is online) + */ + virtual bool isReachable (); + + /** + * Create custom context menu items for the contact + * FIXME: implement manager version here? + */ + virtual QPtrList *customContextMenuActions () = 0; + + /** + * Serialize contact + */ + virtual void serialize (QMap < QString, QString > &serializedData, QMap < QString, QString > &addressBookData); + + /** + * Update contact if a roster item has been + * received for it. (used during login) + */ + void updateContact ( const XMPP::RosterItem &item ); + + /** + * Deal with an incoming message for this contact. + */ + virtual void handleIncomingMessage ( const XMPP::Message &message ) = 0; + + /** + * Update the resource property of the + * contact, listing all available resources. + */ + void updateResourceList (); + + /** + * Return current full address. + * Uses bestResource() if no presubscribed + * address exists. + */ + QString fullAddress (); + + /** + * Set the dontSync flag for this contact. + * If this flag is set, calls to @ref sync will + * be ignored. This is required if the contact + * has been moved between groups on the server + * after we logged in and we try to update our + * local contact list. Since libkopete can only + * handle one group update at a time, moving + * between groups requires to operations which + * each in turn would cause a call to sync(), + * overwriting the change that is being carried + * out. (besides causing unnecessary traffic) + * This is avoided by setting the dontSync flag + * while synchronizing the local copy. + */ + void setDontSync ( bool flag ); + + /** + * Return the status of the dontSync flag. + * See @ref setDontSync for a full description. + */ + bool dontSync (); + + /** + * return the roster item of the contact. + * to get the jid, use rosterItem().jid().full() don't use contactId as it is not the same with transport + */ + XMPP::RosterItem rosterItem() const { return mRosterItem; } + + /** + * Reads a vCard object and updates the contact's + * properties accordingly. + */ + void setPropertiesFromVCard ( const XMPP::VCard &vCard ); + + +public slots: + + /** + * Retrieve a vCard for the contact + */ + virtual void slotUserInfo (); + + + /** + * Re-evaluate online status. Gets called + * whenever a resource is added, removed, or + * changed in the resource pool. + */ + void reevaluateStatus (); + +protected: + /** + * Construct best address out of + * eventually preselected resource + * (due to subscription) and best + * available resource. + */ + XMPP::Jid bestAddress (); + + /** + * This will simply cache all + * relevant data for this contact. + */ + XMPP::RosterItem mRosterItem; + +private: + bool mDontSync; + JabberAccount *m_account; + +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/protocols/jabber/jabberbookmarks.cpp b/kopete/protocols/jabber/jabberbookmarks.cpp new file mode 100644 index 00000000..720705b2 --- /dev/null +++ b/kopete/protocols/jabber/jabberbookmarks.cpp @@ -0,0 +1,149 @@ + /* + Copyright (c) 2006 by Olivier Goffart + + Kopete (c) 2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* + */ + +#include "jabberbookmarks.h" +#include "jabberaccount.h" + +#include + + +#include +#include +#include + +#include "xmpp_tasks.h" + + +JabberBookmarks::JabberBookmarks(JabberAccount *parent) : QObject(parent) , m_account(parent) +{ + connect( m_account , SIGNAL( isConnectedChanged() ) , this , SLOT( accountConnected() ) ); +} + +void JabberBookmarks::accountConnected() +{ + if(!m_account->isConnected()) + return; + + XMPP::JT_PrivateStorage * task = new XMPP::JT_PrivateStorage ( m_account->client()->rootTask ()); + task->get( "storage" , "storage:bookmarks" ); + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT ( slotReceivedBookmarks() ) ); + task->go ( true ); +} + +void JabberBookmarks::slotReceivedBookmarks( ) +{ + XMPP::JT_PrivateStorage * task = (XMPP::JT_PrivateStorage*)(sender()); + m_storage=QDomDocument("storage"); + m_conferencesJID.clear(); + if(task->success()) + { + QDomElement storage_e=task->element(); + if(!storage_e.isNull() && storage_e.tagName() == "storage") + { + storage_e=m_storage.importNode(storage_e,true).toElement(); + m_storage.appendChild(storage_e); + + for(QDomNode n = storage_e.firstChild(); !n.isNull(); n = n.nextSibling()) + { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + if(i.tagName() == "conference") + { + QString jid=i.attribute("jid"); + QString password; + for(QDomNode n = i.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if(e.isNull()) + continue; + else if(e.tagName() == "nick") + jid+="/"+e.text(); + else if(e.tagName() == "password") + password=e.text(); + + } + m_conferencesJID += jid; + if(i.attribute("autojoin") == "true") + { + XMPP::Jid x_jid(jid); + QString nick=x_jid.resource(); + if(nick.isEmpty()) + nick=m_account->myself()->nickName(); + + if(password.isEmpty()) + m_account->client()->joinGroupChat(x_jid.host() , x_jid.user() , nick ); + else + m_account->client()->joinGroupChat(x_jid.host() , x_jid.user() , nick , password); + } + } + } + } + } +} + + +void JabberBookmarks::insertGroupChat(const XMPP::Jid &jid) +{ + if(m_conferencesJID.contains(jid.full()) || !m_account->isConnected()) + { + return; + } + + QDomElement storage_e=m_storage.documentElement(); + if(storage_e.isNull()) + { + storage_e=m_storage.createElement("storage"); + m_storage.appendChild(storage_e); + storage_e.setAttribute("xmlns","storage:bookmarks"); + } + + QDomElement conference=m_storage.createElement("conference"); + storage_e.appendChild(conference); + conference.setAttribute("jid",jid.userHost()); + QDomElement nick=m_storage.createElement("nick"); + conference.appendChild(nick); + nick.appendChild(m_storage.createTextNode(jid.resource())); + QDomElement name=m_storage.createElement("name"); + conference.appendChild(name); + name.appendChild(m_storage.createTextNode(jid.full())); + + + XMPP::JT_PrivateStorage * task = new XMPP::JT_PrivateStorage ( m_account->client()->rootTask ()); + task->set( storage_e ); + task->go ( true ); + + m_conferencesJID += jid.full(); +} + +KAction * JabberBookmarks::bookmarksAction(QObject *parent) +{ + KSelectAction *groupchatBM = new KSelectAction( i18n("Groupchat bookmark") , "jabber_group" , 0 , parent , "actionBookMark" ); + groupchatBM->setItems(m_conferencesJID); + QObject::connect(groupchatBM, SIGNAL(activated (const QString&)) , this, SLOT(slotJoinChatBookmark(const QString&))); + return groupchatBM; +} + +void JabberBookmarks::slotJoinChatBookmark( const QString & _jid ) +{ + if(!m_account->isConnected()) + return; + XMPP::Jid jid(_jid); + m_account->client()->joinGroupChat( jid.host() , jid.user() , jid.resource() ); +} + + + +#include "jabberbookmarks.moc" + diff --git a/kopete/protocols/jabber/jabberbookmarks.h b/kopete/protocols/jabber/jabberbookmarks.h new file mode 100644 index 00000000..826d15e2 --- /dev/null +++ b/kopete/protocols/jabber/jabberbookmarks.h @@ -0,0 +1,68 @@ + /* + + Copyright (c) 2006 by Olivier Goffart + + Kopete (c) 2006 by the Kopete developers + + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* + */ + +#ifndef JABBERBOOKMARKS_H +#define JABBERBOOKMARKS_H + +#include +#include +#include + +namespace XMPP { class Jid; } +class JabberAccount; +class JabberProtocol; + +class KAction; + +/** + * This is a class that hanlde the bookmark collection (JEP-0048) + * There is one instance of that class by accounts. + * @author Olivier Goffart + */ +class JabberBookmarks : public QObject +{ + Q_OBJECT + public: + /** + * Constructor + */ + JabberBookmarks(JabberAccount *parent); + ~JabberBookmarks(){} + + /** + * update or create en entry with the given jid. + * the jid ressource is the nickname + */ + void insertGroupChat(const XMPP::Jid &jid); + + /** + * return an action that will be added in the jabber popup menu + */ + KAction *bookmarksAction(QObject * parent); + private slots: + void accountConnected(); + void slotReceivedBookmarks(); + void slotJoinChatBookmark(const QString&); + + + private: + JabberAccount *m_account; + QDomDocument m_storage; + QStringList m_conferencesJID; +}; + +#endif diff --git a/kopete/protocols/jabber/jabberbytestream.cpp b/kopete/protocols/jabber/jabberbytestream.cpp new file mode 100644 index 00000000..2f0f5c80 --- /dev/null +++ b/kopete/protocols/jabber/jabberbytestream.cpp @@ -0,0 +1,156 @@ + +/*************************************************************************** + jabberbytestream.cpp - Byte Stream for Jabber + ------------------- + begin : Wed Jul 7 2004 + copyright : (C) 2004 by Till Gerken + + Kopete (C) 2004 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include "jabberbytestream.h" +#include +#include +#include "jabberprotocol.h" + +JabberByteStream::JabberByteStream ( QObject *parent, const char */*name*/ ) + : ByteStream ( parent ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Instantiating new Jabber byte stream." << endl; + + // reset close tracking flag + mClosing = false; + + mSocket = new KNetwork::KBufferedSocket; + + // make sure we get a signal whenever there's data to be read + mSocket->enableRead ( true ); + + // connect signals and slots + QObject::connect ( mSocket, SIGNAL ( gotError ( int ) ), this, SLOT ( slotError ( int ) ) ); + QObject::connect ( mSocket, SIGNAL ( connected ( const KResolverEntry& ) ), this, SLOT ( slotConnected () ) ); + QObject::connect ( mSocket, SIGNAL ( closed () ), this, SLOT ( slotConnectionClosed () ) ); + QObject::connect ( mSocket, SIGNAL ( readyRead () ), this, SLOT ( slotReadyRead () ) ); + QObject::connect ( mSocket, SIGNAL ( bytesWritten ( int ) ), this, SLOT ( slotBytesWritten ( int ) ) ); + +} + +bool JabberByteStream::connect ( QString host, QString service ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Connecting to " << host << ", service " << service << endl; + + mClosing = false; + + return socket()->connect ( host, service ); + +} + +bool JabberByteStream::isOpen () const +{ + + // determine if socket is open + return socket()->isOpen (); + +} + +void JabberByteStream::close () +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Closing stream." << endl; + + // close the socket and set flag that we are closing it ourselves + mClosing = true; + socket()->close(); + +} + +int JabberByteStream::tryWrite () +{ + + // send all data from the buffers to the socket + QByteArray writeData = takeWrite(); + socket()->writeBlock ( writeData.data (), writeData.size () ); + + return writeData.size (); + +} + +KNetwork::KBufferedSocket *JabberByteStream::socket () const +{ + + return mSocket; + +} + +JabberByteStream::~JabberByteStream () +{ + + delete mSocket; + +} + +void JabberByteStream::slotConnected () +{ + + emit connected (); + +} + +void JabberByteStream::slotConnectionClosed () +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Socket has been closed." << endl; + + // depending on who closed the socket, emit different signals + if ( !mClosing ) + { + emit connectionClosed (); + } + else + { + emit delayedCloseFinished (); + } + + mClosing = false; + +} + +void JabberByteStream::slotReadyRead () +{ + + // stuff all available data into our buffers + QByteArray readBuffer ( socket()->bytesAvailable () ); + + socket()->readBlock ( readBuffer.data (), readBuffer.size () ); + + appendRead ( readBuffer ); + + emit readyRead (); + +} + +void JabberByteStream::slotBytesWritten ( int bytes ) +{ + + emit bytesWritten ( bytes ); + +} + +void JabberByteStream::slotError ( int code ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Socket error " << code << endl; + + emit error ( code ); + +} + +#include "jabberbytestream.moc" diff --git a/kopete/protocols/jabber/jabberbytestream.h b/kopete/protocols/jabber/jabberbytestream.h new file mode 100644 index 00000000..97e1ceeb --- /dev/null +++ b/kopete/protocols/jabber/jabberbytestream.h @@ -0,0 +1,65 @@ + +/*************************************************************************** + jabberbytestream.h - Byte Stream for Jabber + ------------------- + begin : Wed Jul 7 2004 + copyright : (C) 2004 by Till Gerken + + Kopete (C) 2004 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef JABBERBYTESTREAM_H +#define JABBERBYTESTREAM_H + +#include +#include + + +/** +@author Kopete Developers +*/ +class JabberByteStream : public ByteStream +{ + +Q_OBJECT + +public: + JabberByteStream ( QObject *parent = 0, const char *name = 0 ); + + ~JabberByteStream (); + + bool connect ( QString host, QString service ); + virtual bool isOpen () const; + virtual void close (); + + KNetwork::KBufferedSocket *socket () const; + +signals: + void connected (); + +protected: + virtual int tryWrite (); + +private slots: + void slotConnected (); + void slotConnectionClosed (); + void slotReadyRead (); + void slotBytesWritten ( int ); + void slotError ( int ); + +private: + KNetwork::KBufferedSocket *mSocket; + bool mClosing; + +}; + +#endif diff --git a/kopete/protocols/jabber/jabbercapabilitiesmanager.cpp b/kopete/protocols/jabber/jabbercapabilitiesmanager.cpp new file mode 100644 index 00000000..e570d241 --- /dev/null +++ b/kopete/protocols/jabber/jabbercapabilitiesmanager.cpp @@ -0,0 +1,656 @@ + /* + jabbercapabilitiesmanager.cpp - Manage entity capabilities(JEP-0115). + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + Imported from caps.cpp from Psi: + Copyright (C) 2005 Remko Troncon + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#include "jabbercapabilitiesmanager.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "jabberaccount.h" +#include "jabberprotocol.h" + +using namespace XMPP; + +//BEGIN Capabilities +JabberCapabilitiesManager::Capabilities::Capabilities() +{} + +JabberCapabilitiesManager::Capabilities::Capabilities(const QString& node, const QString& version, const QString& extensions) + : m_node(node), m_version(version), m_extensions(extensions) +{} + +const QString& JabberCapabilitiesManager::Capabilities::node() const +{ + return m_node; +} + +const QString& JabberCapabilitiesManager::Capabilities::version() const +{ + return m_version; +} + +const QString& JabberCapabilitiesManager::Capabilities::extensions() const +{ + return m_extensions; +} + +JabberCapabilitiesManager::CapabilitiesList JabberCapabilitiesManager::Capabilities::flatten() const +{ + CapabilitiesList capsList; + capsList.append( Capabilities(node(), version(), version()) ); + + QStringList extensionList = QStringList::split(" ",extensions()); + QStringList::ConstIterator it, itEnd = extensionList.constEnd(); + for(it = extensionList.constBegin(); it != itEnd; ++it) + { + capsList.append( Capabilities(node(),version(),*it) ); + } + + return capsList; +} + +bool JabberCapabilitiesManager::Capabilities::operator==(const Capabilities &other) const +{ + return (node() == other.node() && version() == other.version() && extensions() == other.extensions()); +} + +bool JabberCapabilitiesManager::Capabilities::operator!=(const Capabilities &other) const +{ + return !((*this) == other); +} + +bool JabberCapabilitiesManager::Capabilities::operator<(const Capabilities &other) const +{ + return (node() != other.node() ? node() < other.node() : + (version() != other.version() ? version() < other.version() : + extensions() < other.extensions())); +} +//END Capabilities + +//BEGIN CapabilitiesInformation +JabberCapabilitiesManager::CapabilitiesInformation::CapabilitiesInformation() + : m_discovered(false), m_pendingRequests(0) +{ + updateLastSeen(); +} + +const QStringList& JabberCapabilitiesManager::CapabilitiesInformation::features() const +{ + return m_features; +} + +const DiscoItem::Identities& JabberCapabilitiesManager::CapabilitiesInformation::identities() const +{ + return m_identities; +} + +QStringList JabberCapabilitiesManager::CapabilitiesInformation::jids() const +{ + QStringList jids; + + QValueList >::ConstIterator it = m_jids.constBegin(), itEnd = m_jids.constEnd(); + for( ; it != itEnd; ++it) + { + QString jid( (*it).first ); + if( !jids.contains(jid) ) + jids.push_back(jid); + } + + return jids; +} + +bool JabberCapabilitiesManager::CapabilitiesInformation::discovered() const +{ + return m_discovered; +} + +int JabberCapabilitiesManager::CapabilitiesInformation::pendingRequests() const +{ + return m_pendingRequests; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::reset() +{ + m_features.clear(); + m_identities.clear(); + m_discovered = false; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::removeAccount(JabberAccount *account) +{ + QValueList >::Iterator it = m_jids.begin(); + while( it != m_jids.end() ) + { + if( (*it).second == account) + { + QValueList >::Iterator otherIt = it; + it++; + m_jids.remove(otherIt); + } + else + { + it++; + } + } +} + +void JabberCapabilitiesManager::CapabilitiesInformation::addJid(const Jid& jid, JabberAccount* account) +{ + QPair jidAccountPair(jid.full(),account); + + if( !m_jids.contains(jidAccountPair) ) + { + m_jids.push_back(jidAccountPair); + updateLastSeen(); + } +} + +void JabberCapabilitiesManager::CapabilitiesInformation::removeJid(const Jid& jid) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Unregistering " << QString(jid.full()).replace('%',"%%") << endl; + + QValueList >::Iterator it = m_jids.begin(); + while( it != m_jids.end() ) + { + if( (*it).first == jid.full() ) + { + QValueList >::Iterator otherIt = it; + it++; + m_jids.remove(otherIt); + } + else + { + it++; + } + } +} + +QPair JabberCapabilitiesManager::CapabilitiesInformation::nextJid(const Jid& jid, const Task* t) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Looking for next JID" << endl; + + QValueList >::ConstIterator it = m_jids.constBegin(), itEnd = m_jids.constEnd(); + for( ; it != itEnd; ++it) + { + if( (*it).first == jid.full() && (*it).second->client()->rootTask() == t) + { + it++; + if (it == itEnd) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "No more JIDs" << endl; + + return QPair(Jid(),0L); + } + else if( (*it).second->isConnected() ) + { + //qDebug("caps.cpp: Account isn't active"); + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Account isn't connected." << endl; + + return QPair( (*it).first,(*it).second ); + } + } + } + return QPair(Jid(),0L); +} + +void JabberCapabilitiesManager::CapabilitiesInformation::setDiscovered(bool value) +{ + m_discovered = value; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::setPendingRequests(int pendingRequests) +{ + m_pendingRequests = pendingRequests; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::setIdentities(const DiscoItem::Identities& identities) +{ + m_identities = identities; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::setFeatures(const QStringList& featureList) +{ + m_features = featureList; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::updateLastSeen() +{ + m_lastSeen = QDate::currentDate(); +} + +QDomElement JabberCapabilitiesManager::CapabilitiesInformation::toXml(QDomDocument *doc) const +{ + QDomElement info = doc->createElement("info"); + //info.setAttribute("last-seen",lastSeen_.toString(Qt::ISODate)); + + // Identities + DiscoItem::Identities::ConstIterator discoIt = m_identities.constBegin(), discoItEnd = m_identities.constEnd(); + for( ; discoIt != discoItEnd; ++discoIt ) + { + QDomElement identity = doc->createElement("identity"); + identity.setAttribute("category",(*discoIt).category); + identity.setAttribute("name",(*discoIt).name); + identity.setAttribute("type",(*discoIt).type); + info.appendChild(identity); + } + + // Features + QStringList::ConstIterator featuresIt = m_features.constBegin(), featuresItEnd = m_features.constEnd(); + for( ; featuresIt != featuresItEnd; ++featuresIt ) + { + QDomElement feature = doc->createElement("feature"); + feature.setAttribute("node",*featuresIt); + info.appendChild(feature); + } + + return info; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::fromXml(const QDomElement &element) +{ + if( element.tagName() != "info") + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Invalid info element" << endl; + return; + } + + //if (!e.attribute("last-seen").isEmpty()) + // lastSeen_ = QDate::fromString(e.attribute("last-seen"),Qt::ISODate); + + for(QDomNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) + { + QDomElement infoElement = node.toElement(); + if( infoElement.isNull() ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Null element" << endl; + continue; + } + + if( infoElement.tagName() == "identity") + { + DiscoItem::Identity id; + id.category = infoElement.attribute("category"); + id.name = infoElement.attribute("name"); + id.type = infoElement.attribute("type"); + m_identities += id; + } + else if( infoElement.tagName() == "feature" ) + { + m_features += infoElement.attribute("node"); + } + else + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Unknown element" << endl; + } + + m_discovered = true; + } +} +//END CapabilitiesInformation + +//BEGIN Private(d-ptr) +class JabberCapabilitiesManager::Private +{ +public: + Private() + {} + + // Map a full jid to a capabilities + QMap jidCapabilitiesMap; + // Map a capabilities to its detail information + QMap capabilitiesInformationMap; +}; +//END Private(d-ptr) + +JabberCapabilitiesManager::JabberCapabilitiesManager() + : d(new Private) +{ +} + +JabberCapabilitiesManager::~JabberCapabilitiesManager() +{ + saveInformation(); + delete d; +} + +void JabberCapabilitiesManager::removeAccount(JabberAccount *account) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing account " << account->accountId() << endl; + + QValueList info = d->capabilitiesInformationMap.values(); + + QValueList::Iterator it, itEnd = info.end(); + for(it = info.begin(); it != info.end(); ++it) + { + (*it).removeAccount(account); + } +} + +void JabberCapabilitiesManager::updateCapabilities(JabberAccount *account, const XMPP::Jid &jid, const XMPP::Status &status ) +{ + if( !account->client() || !account->client()->rootTask() ) + return; + + + // Do don't anything if the jid correspond to the account's JabberClient jid. + // false means that we don't check for resources. + if( jid.compare(account->client()->jid(), false) ) + return; + + QString node = status.capsNode(), version = status.capsVersion(), extensions = status.capsExt(); + Capabilities capabilities( node, version, extensions ); + + // Check if the capabilities was really updated(i.e the content is different) + if( d->jidCapabilitiesMap[jid.full()] != capabilities) + { + // Unregister from all old caps nodes + // FIXME: We should only unregister & register from changed nodes + CapabilitiesList oldCaps = d->jidCapabilitiesMap[jid.full()].flatten(); + CapabilitiesList::Iterator oldCapsIt = oldCaps.begin(), oldCapsItEnd = oldCaps.end(); + for( ; oldCapsIt != oldCapsItEnd; ++oldCapsIt) + { + if( (*oldCapsIt) != Capabilities() ) + { + d->capabilitiesInformationMap[*oldCapsIt].removeJid(jid); + } + } + + // Check if the jid has caps in his presence message. + if( !status.capsNode().isEmpty() && !status.capsVersion().isEmpty() ) + { + // Register with all new caps nodes + d->jidCapabilitiesMap[jid.full()] = capabilities; + CapabilitiesList caps = capabilities.flatten(); + CapabilitiesList::Iterator newCapsIt = caps.begin(), newCapsItEnd = caps.end(); + for( ; newCapsIt != newCapsItEnd; ++newCapsIt ) + { + d->capabilitiesInformationMap[*newCapsIt].addJid(jid,account); + } + + emit capabilitiesChanged(jid); + + // Register new caps and check if we need to discover features + newCapsIt = caps.begin(); + for( ; newCapsIt != newCapsItEnd; ++newCapsIt ) + { + if( !d->capabilitiesInformationMap[*newCapsIt].discovered() && d->capabilitiesInformationMap[*newCapsIt].pendingRequests() == 0 ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << QString("Sending disco request to %1, node=%2").arg(QString(jid.full()).replace('%',"%%")).arg(node + "#" + (*newCapsIt).extensions()) << endl; + + d->capabilitiesInformationMap[*newCapsIt].setPendingRequests(1); + requestDiscoInfo(account, jid, node + "#" + (*newCapsIt).extensions()); + } + } + } + else + { + // Remove all caps specifications + kdDebug(JABBER_DEBUG_GLOBAL) << QString("Illegal caps info from %1: node=%2, ver=%3").arg(QString(jid.full()).replace('%',"%%")).arg(node).arg(version) << endl; + + d->jidCapabilitiesMap.remove( jid.full() ); + } + } + else + { + // Add to the list of jids + CapabilitiesList caps = capabilities.flatten(); + CapabilitiesList::Iterator capsIt = caps.begin(), capsItEnd = caps.end(); + for( ; capsIt != capsItEnd; ++capsIt) + { + d->capabilitiesInformationMap[*capsIt].addJid(jid,account); + } + } +} + +void JabberCapabilitiesManager::requestDiscoInfo(JabberAccount *account, const Jid& jid, const QString& node) +{ + if( !account->client()->rootTask() ) + return; + + JT_DiscoInfo *discoInfo = new JT_DiscoInfo(account->client()->rootTask()); + connect(discoInfo, SIGNAL(finished()), SLOT(discoRequestFinished())); + discoInfo->get(jid, node); + //pending_++; + //timer_.start(REQUEST_TIMEOUT,true); + discoInfo->go(true); +} + +void JabberCapabilitiesManager::discoRequestFinished() +{ + JT_DiscoInfo *discoInfo = (JT_DiscoInfo*)sender(); + if (!discoInfo) + return; + + DiscoItem item = discoInfo->item(); + Jid jid = discoInfo->jid(); + kdDebug(JABBER_DEBUG_GLOBAL) << QString("Disco response from %1, node=%2, success=%3").arg(QString(jid.full()).replace('%',"%%")).arg(discoInfo->node()).arg(discoInfo->success()) << endl; + + QStringList tokens = QStringList::split("#",discoInfo->node()); + + // Update features + Q_ASSERT(tokens.count() == 2); + QString node = tokens[0]; + QString extensions = tokens[1]; + + Capabilities jidCapabilities = d->jidCapabilitiesMap[jid.full()]; + if( jidCapabilities.node() == node ) + { + Capabilities capabilities(node, jidCapabilities.version(), extensions); + + if( discoInfo->success() ) + { + // Save identities & features + d->capabilitiesInformationMap[capabilities].setIdentities(item.identities()); + d->capabilitiesInformationMap[capabilities].setFeatures(item.features().list()); + d->capabilitiesInformationMap[capabilities].setPendingRequests(0); + d->capabilitiesInformationMap[capabilities].setDiscovered(true); + + // Save(Cache) information + saveInformation(); + + // Notify affected jids. + QStringList jids = d->capabilitiesInformationMap[capabilities].jids(); + QStringList::ConstIterator jidsIt = jids.constBegin(), jidsItEnd = jids.constEnd(); + for( ; jidsIt != jidsItEnd; ++jidsItEnd ) + { + emit capabilitiesChanged(*jidsIt); + } + } + else + { + QPair jidAccountPair = d->capabilitiesInformationMap[capabilities].nextJid(jid,discoInfo->parent()); + if( jidAccountPair.second ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << QString("Falling back on %1.").arg(QString(jidAccountPair.first.full()).replace('%',"%%")) << endl; + requestDiscoInfo( jidAccountPair.second, jidAccountPair.first, discoInfo->node() ); + } + else + { + kdDebug(JABBER_DEBUG_GLOBAL) << "No valid disco request avalable." << endl; + d->capabilitiesInformationMap[capabilities].setPendingRequests(0); + } + } + } + else + kdDebug(JABBER_DEBUG_GLOBAL) << QString("Current client node '%1' does not match response '%2'").arg(jidCapabilities.node()).arg(node) << endl; + + //for (unsigned int i = 0; i < item.features().list().count(); i++) + // printf(" Feature: %s\n",item.features().list()[i].latin1()); + + // Check pending requests +// pending_ = (pending_ > 0 ? pending_-1 : 0); +// if (!pending_) { +// timer_.stop(); +// updatePendingJIDs(); +// } +} + +void JabberCapabilitiesManager::loadCachedInformation() +{ + QString capsFileName; + capsFileName = locateLocal("appdata", QString::fromUtf8("jabber-capabilities-cache.xml")); + + // Load settings + QDomDocument doc; + QFile cacheFile(capsFileName); + if( !cacheFile.open(IO_ReadOnly) ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Could not open the Capabilities cache from disk." << endl; + return; + } + if( !doc.setContent(&cacheFile) ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Could not set the Capabilities cache from file." << endl; + return; + } + cacheFile.close(); + + QDomElement caps = doc.documentElement(); + if( caps.tagName() != "capabilities" ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Invalid capabilities element." << endl; + return; + } + + QDomNode node; + for(node = caps.firstChild(); !node.isNull(); node = node.nextSibling()) + { + QDomElement element = node.toElement(); + if( element.isNull() ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Found a null element." << endl; + continue; + } + + if( element.tagName() == "info" ) + { + CapabilitiesInformation info; + info.fromXml(element); + Capabilities entityCaps( element.attribute("node"),element.attribute("ver"),element.attribute("ext") ); + d->capabilitiesInformationMap[entityCaps] = info; + } + else + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Unknow element" << endl; + } + } +} + +bool JabberCapabilitiesManager::capabilitiesEnabled(const Jid &jid) const +{ + return d->jidCapabilitiesMap.contains( jid.full() ); +} + +XMPP::Features JabberCapabilitiesManager::features(const Jid& jid) const +{ + QStringList featuresList; + + if( capabilitiesEnabled(jid) ) + { + CapabilitiesList capabilitiesList = d->jidCapabilitiesMap[jid.full()].flatten(); + CapabilitiesList::ConstIterator capsIt = capabilitiesList.constBegin(), capsItEnd = capabilitiesList.constEnd(); + for( ; capsIt != capsItEnd; ++capsIt) + { + featuresList += d->capabilitiesInformationMap[*capsIt].features(); + } + } + + return Features(featuresList); +} + +QString JabberCapabilitiesManager::clientName(const Jid& jid) const +{ + if( capabilitiesEnabled(jid) ) + { + Capabilities caps = d->jidCapabilitiesMap[jid.full()]; + QString name = d->capabilitiesInformationMap[Capabilities(caps.node(),caps.version(),caps.version())].identities().first().name; + + // Try to be intelligent about the name + /*if (name.isEmpty()) { + name = cs.node(); + if (name.startsWith("http://")) + name = name.right(name.length() - 7); + + if (name.startsWith("www.")) + name = name.right(name.length() - 4); + + int cut_pos = name.find("."); + if (cut_pos != -1) { + name = name.left(cut_pos); + } + }*/ + + return name; + } + else + { + return QString(); + } +} + +QString JabberCapabilitiesManager::clientVersion(const Jid& jid) const +{ + return (capabilitiesEnabled(jid) ? d->jidCapabilitiesMap[jid.full()].version() : QString()); +} + +void JabberCapabilitiesManager::saveInformation() +{ + QString capsFileName; + capsFileName = locateLocal("appdata", QString::fromUtf8("jabber-capabilities-cache.xml")); + + // Generate XML + QDomDocument doc; + QDomElement capabilities = doc.createElement("capabilities"); + doc.appendChild(capabilities); + QMap::ConstIterator it = d->capabilitiesInformationMap.constBegin(), itEnd = d->capabilitiesInformationMap.constEnd(); + for( ; it != itEnd; ++it ) + { + QDomElement info = it.data().toXml(&doc); + info.setAttribute("node",it.key().node()); + info.setAttribute("ver",it.key().version()); + info.setAttribute("ext",it.key().extensions()); + capabilities.appendChild(info); + } + + // Save + QFile capsFile(capsFileName); + if( !capsFile.open(IO_WriteOnly) ) + { + kdDebug(JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Error while opening Capabilities cache file." << endl; + return; + } + + QTextStream textStream; + textStream.setDevice(&capsFile); + textStream.setEncoding(QTextStream::UnicodeUTF8); + textStream << doc.toString(); + textStream.unsetDevice(); + capsFile.close(); +} + +#include "jabbercapabilitiesmanager.moc" diff --git a/kopete/protocols/jabber/jabbercapabilitiesmanager.h b/kopete/protocols/jabber/jabbercapabilitiesmanager.h new file mode 100644 index 00000000..3010479f --- /dev/null +++ b/kopete/protocols/jabber/jabbercapabilitiesmanager.h @@ -0,0 +1,211 @@ + /* + jabbercapabilitiesmanager.h - Manage entity capabilities(JEP-0115) pool. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + Imported from caps.cpp from Psi: + Copyright (C) 2005 Remko Troncon + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef JABBERCAPABILITIESMANAGER_H +#define JABBERCAPABILITIESMANAGER_H + +#include +#include +#include + +using namespace XMPP; + +class JabberAccount; + +/** + * @brief Manage Jabber entity capabilities (JEP-0115) + * @author Michaël Larouche + * @author Remko Troncon + */ +class JabberCapabilitiesManager : public QObject +{ + Q_OBJECT +public: + /** + * Construct + */ + JabberCapabilitiesManager(); + ~JabberCapabilitiesManager(); + + /** + * Load cached information from local file. + */ + void loadCachedInformation(); + + /** + * Check if the jid support Entity capabitilies. + * @param jid JID to check. + * @return true if the jid support entity capabitilies. + */ + bool capabilitiesEnabled(const Jid& jid) const; + + /** + * Remove account from manager. + */ + void removeAccount(JabberAccount *account); + + /** + * Return the features supported for the JID. + */ + XMPP::Features features(const Jid& jid) const; + /** + * Return the client name for the current JID. + */ + QString clientName(const Jid& jid) const; + /** + * Return the client version for the current JID. + */ + QString clientVersion(const Jid& jid) const; + +signals: + void capabilitiesChanged(const XMPP::Jid &jid); + +public slots: + /** + * Update if necessary the capabities for the JID passed in args. + * Caps are received in Presence messages so that's why we are + * passing a XMPP::Status object. + * + * @param jid JID that capabilities was updated. + * @param status The XMPP::Status that contain the caps. + */ + void updateCapabilities(JabberAccount *account, const XMPP::Jid &jid, const XMPP::Status &status); + +private slots: + /** + * @brief Called when a reply to disco#info request was received. + * If the result was succesful, the resulting features are recorded in the + * features database for the requested node, and all the affected jids are + * put in the queue for update notification. + * If the result was unsuccesful, another jid with the same capabilities is + * selected and sent a disco#info query. + */ + void discoRequestFinished(); + +private: + /** + * @brief Sends a disco#info request to a given node of a jid through an account. + * When the request is finished, the discoRequestFinished() slot is called. + * + * @param account The account through which to send the disco request. + * @param jid The target entity's JID + * @param node The target disco#info node + */ + void requestDiscoInfo(JabberAccount *account, const Jid& jid, const QString& node); + + /** + * Save capabilities information to disk. + */ + void saveInformation(); + + class Capabilities; + typedef QValueList CapabilitiesList; + /** + * @brief A class representing an entity capability specification. + * An entity capability is a combination of a node, a version, and a set of + * extensions. + */ + class Capabilities + { + public: + /** + * Default constructor. + */ + Capabilities(); + /** + * Define capabilities. + * @param node the node + * @param version the version + * @param extensions the list of extensions (separated by spaces) + */ + Capabilities(const QString &node, const QString &version, const QString &extensions); + /** + * Returns the node of the capabilities specification. + */ + const QString& node() const; + /** + * @brief Returns the version of the capabilities specification. + */ + const QString& version() const; + /** + * @brief Returns the extensions of the capabilities specification. + */ + const QString& extensions() const; + /** + * \brief Flattens the caps specification into the set of 'simple' specifications. + * A 'simple' specification is a specification with exactly one extension, + * or with the version number as the extension. + * + * Example: A caps specification with node=http://psi-im.org, version=0.10, + * and ext='achat vchat' would be expanded into the following list of specs: + * node=http://psi-im.org, ver=0.10, ext=0.10 + * node=http://psi-im.org, ver=0.10, ext=achat + * node=http://psi-im.org, ver=0.10, ext=vchat + */ + CapabilitiesList flatten() const; + + bool operator==(const Capabilities&) const; + bool operator!=(const Capabilities&) const; + bool operator<(const Capabilities&) const; + + private: + QString m_node, m_version, m_extensions; + }; + + class CapabilitiesInformation + { + public: + CapabilitiesInformation(); + const QStringList& features() const; + const DiscoItem::Identities& identities() const; + QStringList jids() const; + bool discovered() const; + int pendingRequests() const; + + void reset(); + void removeAccount(JabberAccount* acc); + void removeJid(const Jid&); + void addJid(const Jid&, JabberAccount*); + QPair nextJid(const Jid&, const Task*); + + void setDiscovered(bool); + void setPendingRequests(int); + void setIdentities(const DiscoItem::Identities&); + void setFeatures(const QStringList&); + + QDomElement toXml(QDomDocument *) const; + void fromXml(const QDomElement&); + + protected: + void updateLastSeen(); + + private: + bool m_discovered; + int m_pendingRequests; + QStringList m_features; + DiscoItem::Identities m_identities; + QValueList > m_jids; + QDate m_lastSeen; + }; + + class Private; + Private *d; +}; + +#endif diff --git a/kopete/protocols/jabber/jabberchatsession.cpp b/kopete/protocols/jabber/jabberchatsession.cpp new file mode 100644 index 00000000..faa6f950 --- /dev/null +++ b/kopete/protocols/jabber/jabberchatsession.cpp @@ -0,0 +1,357 @@ +/* + jabberchatsession.cpp - Jabber Chat Session + + Copyright (c) 2004 by Till Gerken + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "jabberchatsession.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "kopetechatsessionmanager.h" +#include "kopetemessage.h" +#include "kopeteviewplugin.h" +#include "kopeteview.h" +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabberclient.h" +#include "jabbercontact.h" +#include "jabberresource.h" +#include "jabberresourcepool.h" +#include "kioslave/jabberdisco.h" + + +JabberChatSession::JabberChatSession ( JabberProtocol *protocol, const JabberBaseContact *user, + Kopete::ContactPtrList others, const QString &resource, const char *name ) + : Kopete::ChatSession ( user, others, protocol, name ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "New message manager for " << user->contactId () << endl; + + // make sure Kopete knows about this instance + Kopete::ChatSessionManager::self()->registerChatSession ( this ); + + connect ( this, SIGNAL ( messageSent ( Kopete::Message &, Kopete::ChatSession * ) ), + this, SLOT ( slotMessageSent ( Kopete::Message &, Kopete::ChatSession * ) ) ); + + connect ( this, SIGNAL ( myselfTyping ( bool ) ), this, SLOT ( slotSendTypingNotification ( bool ) ) ); + + connect ( this, SIGNAL ( onlineStatusChanged(Kopete::Contact*, const Kopete::OnlineStatus&, const Kopete::OnlineStatus& ) ), this, SLOT ( slotUpdateDisplayName () ) ); + + // check if the user ID contains a hardwired resource, + // we'll have to use that one in that case + XMPP::Jid jid = user->rosterItem().jid() ; + + mResource = jid.resource().isEmpty () ? resource : jid.resource (); + slotUpdateDisplayName (); + +#ifdef SUPPORT_JINGLE + KAction *jabber_voicecall = new KAction( i18n("Voice call" ), "voicecall", 0, members().getFirst(), SLOT(voiceCall ()), actionCollection(), "jabber_voicecall" ); + + setInstance(protocol->instance()); + jabber_voicecall->setEnabled( false ); + + + Kopete::ContactPtrList chatMembers = members (); + if ( chatMembers.first () ) + { + // Check if the current contact support Voice calls, also honour lock by default. + // FIXME: we should use the active ressource + JabberResource *bestResource = account()->resourcePool()-> bestJabberResource( static_cast(chatMembers.first())->rosterItem().jid() ); + if( bestResource && bestResource->features().canVoice() ) + { + jabber_voicecall->setEnabled( true ); + } + } + +#endif + + new KAction( i18n( "Send File" ), "attach", 0, this, SLOT( slotSendFile() ), actionCollection(), "jabberSendFile" ); + + setXMLFile("jabberchatui.rc"); + +} + +JabberChatSession::~JabberChatSession( ) +{ + JabberAccount * a = dynamic_cast(Kopete::ChatSession::account ()); + if( !a ) //When closing kopete, the account is partially destroyed already, dynamic_cast return 0 + return; + if ( a->configGroup()->readBoolEntry ("SendEvents", true) && + a->configGroup()->readBoolEntry ("SendGoneEvent", true) ) + sendNotification( XMPP::GoneEvent ); +} + + +void JabberChatSession::slotUpdateDisplayName () +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << endl; + + Kopete::ContactPtrList chatMembers = members (); + + // make sure we do have members in the chat + if ( !chatMembers.first () ) + return; + + XMPP::Jid jid = static_cast(chatMembers.first())->rosterItem().jid(); + + if ( !mResource.isEmpty () ) + jid.setResource ( mResource ); + + QString statusText = i18n("a contact's online status in parenthesis.", " (%1)") + .arg( chatMembers.first()->onlineStatus().description() ); + if ( jid.resource().isEmpty () ) + setDisplayName ( chatMembers.first()->metaContact()->displayName () + statusText ); + else + setDisplayName ( chatMembers.first()->metaContact()->displayName () + "/" + jid.resource () + statusText ); + +} + +const JabberBaseContact *JabberChatSession::user () const +{ + + return static_cast(Kopete::ChatSession::myself()); + +} + +JabberAccount *JabberChatSession::account () const +{ + + return static_cast(Kopete::ChatSession::account ()); + +} + +const QString &JabberChatSession::resource () const +{ + + return mResource; + +} + +void JabberChatSession::appendMessage ( Kopete::Message &msg, const QString &fromResource ) +{ + + mResource = fromResource; + + slotUpdateDisplayName (); + Kopete::ChatSession::appendMessage ( msg ); + + // We send the notifications for Delivered and Displayed events. More granular management + // (ie.: send Displayed event when it is really displayed) + // of these events would require changes in the chatwindow API. + + if ( account()->configGroup()->readBoolEntry ("SendEvents", true) ) + { + if ( account()->configGroup()->readBoolEntry ("SendDeliveredEvent", true) ) + { + sendNotification( XMPP::DeliveredEvent ); + } + + if ( account()->configGroup()->readBoolEntry ("SendDisplayedEvent", true) ) + { + sendNotification( XMPP::DisplayedEvent ); + } + } +} + +void JabberChatSession::sendNotification( XMPP::MsgEvent event ) +{ + if ( !account()->isConnected () ) + return; + + JabberContact *contact; + QPtrListIterator listIterator ( members () ); + + while ( ( contact = dynamic_cast( listIterator.current () ) ) != 0 ) + { + ++listIterator; + if ( contact->isContactRequestingEvent( event ) ) + { + // create JID for the recipient + XMPP::Jid toJid = contact->rosterItem().jid(); + + // set resource properly if it has been selected already + if ( !resource().isEmpty () ) + toJid.setResource ( resource () ); + + XMPP::Message message; + + message.setFrom ( account()->client()->jid() ); + message.setTo ( toJid ); + message.setEventId ( contact->lastReceivedMessageId () ); + // store composing event depending on state + message.addEvent ( event ); + + if (view() && view()->plugin()->pluginId() == "kopete_emailwindow" ) + { + message.setType ( "normal" ); + } + else + { + message.setType ( "chat" ); + } + + + // send message + account()->client()->sendMessage ( message ); + } + } +} + +void JabberChatSession::slotSendTypingNotification ( bool typing ) +{ + if ( !account()->configGroup()->readBoolEntry ("SendEvents", true) + || !account()->configGroup()->readBoolEntry("SendComposingEvent", true) ) + return; + + // create JID for us as sender + XMPP::Jid fromJid = static_cast(myself())->rosterItem().jid(); + fromJid.setResource ( account()->configGroup()->readEntry( "Resource", QString::null ) ); + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Sending out typing notification (" << typing << ") to all chat members." << endl; + + typing ? sendNotification( ComposingEvent ) : sendNotification( CancelEvent ); +} + +void JabberChatSession::slotMessageSent ( Kopete::Message &message, Kopete::ChatSession * ) +{ + + if( account()->isConnected () ) + { + XMPP::Message jabberMessage; + JabberBaseContact *recipient = static_cast(message.to().first()); + + jabberMessage.setFrom ( account()->client()->jid() ); + + + XMPP::Jid toJid = recipient->rosterItem().jid(); + + if( !resource().isEmpty () ) + toJid.setResource ( resource() ); + + jabberMessage.setTo ( toJid ); + + jabberMessage.setSubject ( message.subject () ); + jabberMessage.setTimeStamp ( message.timestamp () ); + + if ( message.plainBody().find ( "-----BEGIN PGP MESSAGE-----" ) != -1 ) + { + /* + * This message is encrypted, so we need to set + * a fake body indicating that this is an encrypted + * message (for clients not implementing this + * functionality) and then generate the encrypted + * payload out of the old message body. + */ + + // please don't translate the following string + jabberMessage.setBody ( i18n ( "This message is encrypted." ) ); + + QString encryptedBody = message.plainBody (); + + // remove PGP header and footer from message + encryptedBody.truncate ( encryptedBody.length () - QString("-----END PGP MESSAGE-----").length () - 2 ); + encryptedBody = encryptedBody.right ( encryptedBody.length () - encryptedBody.find ( "\n\n" ) - 2 ); + + // assign payload to message + jabberMessage.setXEncrypted ( encryptedBody ); + } + else + { + // this message is not encrypted + jabberMessage.setBody ( message.plainBody ()); + if (message.format() == Kopete::Message::RichText) + { + JabberResource *bestResource = account()->resourcePool()->bestJabberResource(toJid); + if( bestResource && bestResource->features().canXHTML() ) + { + QString xhtmlBody = message.escapedBody(); + + // According to JEP-0071 8.9 it is only RECOMMANDED to replace \n with
    + // which mean that some implementation (gaim 2 beta) may still think that \n are linebreak. + // and considered the fact that KTextEditor generate a well indented XHTML, we need to remove all \n from it + // see Bug 121627 + // Anyway, theses client that do like that are *WRONG* considreded the example of jep-71 where there are lot of + // linebreak that are not interpreted. - Olivier 2006-31-03 + xhtmlBody.replace("\n",""); + + //  is not a valid XML entity + xhtmlBody.replace(" " , " "); + + xhtmlBody="

    "+ xhtmlBody +"

    "; + jabberMessage.setXHTMLBody ( xhtmlBody ); + } + } + } + + // determine type of the widget and set message type accordingly + // "kopete_emailwindow" is the default email Kopete::ViewPlugin. If other email plugins + // become available, either jabber will have to provide its own selector or libkopete will need + // a better way of categorising view plugins. + + // FIXME: the view() is a speedy way to solve BUG:108389. A better solution is to be found + // but I don't want to introduce a new bug during the bug hunt ;-). + if (view() && view()->plugin()->pluginId() == "kopete_emailwindow" ) + { + jabberMessage.setType ( "normal" ); + } + else + { + jabberMessage.setType ( "chat" ); + } + + // add request for all notifications + jabberMessage.addEvent( OfflineEvent ); + jabberMessage.addEvent( ComposingEvent ); + jabberMessage.addEvent( DeliveredEvent ); + jabberMessage.addEvent( DisplayedEvent ); + + + // send the message + account()->client()->sendMessage ( jabberMessage ); + + // append the message to the manager + Kopete::ChatSession::appendMessage ( message ); + + // tell the manager that we sent successfully + messageSucceeded (); + } + else + { + account()->errorConnectFirst (); + + // FIXME: there is no messageFailed() yet, + // but we need to stop the animation etc. + messageSucceeded (); + } + +} + + void JabberChatSession::slotSendFile() + { + QPtrListcontacts = members(); + static_cast(contacts.first())->sendFile(); + } + +#include "jabberchatsession.moc" + +// vim: set noet ts=4 sts=4 sw=4: +// kate: tab-width 4; replace-tabs off; space-indent off; diff --git a/kopete/protocols/jabber/jabberchatsession.h b/kopete/protocols/jabber/jabberchatsession.h new file mode 100644 index 00000000..66b4c63b --- /dev/null +++ b/kopete/protocols/jabber/jabberchatsession.h @@ -0,0 +1,103 @@ +/* + jabbermessagemanager.h - Jabber Message Manager + + Copyright (c) 2004 by Till Gerken + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef JABBERCHATSESSION_H +#define JABBERCHATSESSION_H + +#include "kopetechatsession.h" + +#include "im.h" + +class JabberProtocol; +class JabberAccount; +class JabberBaseContact; +namespace Kopete { class Message; } +class QString; + + +/** + * @author Till Gerken + */ +class JabberChatSession : public Kopete::ChatSession +{ + Q_OBJECT + +public: + JabberChatSession ( JabberProtocol *protocol, const JabberBaseContact *user, + Kopete::ContactPtrList others, const QString &resource = "", + const char *name = 0 ); + + ~JabberChatSession(); + + /** + * @brief Get the local user in the session + * @return the local user in the session, same as account()->myself() + */ + const JabberBaseContact *user () const; + + /** + * @brief get the account + * @return the account + */ + JabberAccount *account() const ; + + /** + * @brief Return the resource this manager is currently associated with. + * @return currently associated resource + */ + const QString &resource () const; + +public slots: + /** + * Show a message to the chatwindow, or append it to the queue. + * This is the function protocols HAVE TO call for both incoming and outgoing messages + * if the message must be showed in the chatwindow + * + * This is an overloaded version of the original implementation which + * also accepts a resource the message originates from. The message manager + * will set its own resource to the resource the message was received from. + * See @ref JabberBaseContact::manager() about how to deal with instantiating + * new message managers for messages not originating from the same resource + * a manager already exists for. + */ + void appendMessage ( Kopete::Message &msg, const QString &fromResource ); + +private slots: + void slotSendTypingNotification ( bool typing ); + void slotMessageSent ( Kopete::Message &message, Kopete::ChatSession *kmm ); + + /** + * Re-generate the display name + */ + void slotUpdateDisplayName (); + + void slotSendFile(); + +private: + /** + * Send a notification (XMPP::MsgEvent) to the members of the chatsession. + * SlotSendTypingNotification uses it. + */ + void sendNotification( XMPP::MsgEvent event ); + + QString mResource; +}; + +#endif + +// vim: set noet ts=4 sts=4 tw=4: + diff --git a/kopete/protocols/jabber/jabberchatui.rc b/kopete/protocols/jabber/jabberchatui.rc new file mode 100644 index 00000000..9db9abe2 --- /dev/null +++ b/kopete/protocols/jabber/jabberchatui.rc @@ -0,0 +1,19 @@ + + + + + &Chat + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kopete/protocols/jabber/jabberclient.cpp b/kopete/protocols/jabber/jabberclient.cpp new file mode 100644 index 00000000..b8e05d48 --- /dev/null +++ b/kopete/protocols/jabber/jabberclient.cpp @@ -0,0 +1,1137 @@ + +/*************************************************************************** + jabberclient.cpp - Generic Jabber Client Class + ------------------- + begin : Sat May 25 2005 + copyright : (C) 2005 by Till Gerken + (C) 2006 by Michaël Larouche + + Kopete (C) 2001-2006 Kopete developers + . + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "jabberclient.h" + +#include +#include + +#include +#include +#include +#include + +#include "jabberconnector.h" + +#define JABBER_PENALTY_TIME 2 + +class JabberClient::Private +{ +public: + Private() + : jabberClient(0L), jabberClientStream(0L), jabberClientConnector(0L), jabberTLS(0L), jabberTLSHandler(0L) + {} + ~Private() + { + if ( jabberClient ) + { + jabberClient->close (); + } + + delete jabberClient; + delete jabberClientStream; + delete jabberClientConnector; + delete jabberTLSHandler; + delete jabberTLS; + } + + // connection details + XMPP::Jid jid; + QString password; + + // XMPP backend + XMPP::Client *jabberClient; + XMPP::ClientStream *jabberClientStream; + JabberConnector *jabberClientConnector; + QCA::TLS *jabberTLS; + XMPP::QCATLSHandler *jabberTLSHandler; + + // ignore TLS warnings + bool ignoreTLSWarnings; + + // current S5B server instance + static XMPP::S5BServer *s5bServer; + // address list being handled by the S5B server instance + static QStringList s5bAddressList; + // port of S5B server + static int s5bServerPort; + + // local IP address + QString localAddress; + + // whether TLS (or direct SSL in case of the old protocol) should be used + bool forceTLS; + + // whether direct SSL connections should be used + bool useSSL; + + // use XMPP 1.0 or the older protocol version + bool useXMPP09; + + // whether SSL support should be probed in case the old protocol is used + bool probeSSL; + + // override the default server name and port (only pre-XMPP 1.0) + bool overrideHost; + QString server; + int port; + + // allow transmission of plaintext passwords + bool allowPlainTextPassword; + + // enable file transfers + bool fileTransfersEnabled; + + // current penalty time + int currentPenaltyTime; + + // client information + QString clientName, clientVersion, osName; + + // timezone information + QString timeZoneName; + int timeZoneOffset; + + // Caps(JEP-0115: Entity Capabilities) information + QString capsNode, capsVersion; + DiscoItem::Identity discoIdentity; +}; + +XMPP::S5BServer *JabberClient::Private::s5bServer = 0L; +QStringList JabberClient::Private::s5bAddressList; +int JabberClient::Private::s5bServerPort = 8010; + +JabberClient::JabberClient () +{ + d = new Private(); + + cleanUp (); + + // initiate penalty timer + QTimer::singleShot ( JABBER_PENALTY_TIME * 1000, this, SLOT ( slotUpdatePenaltyTime () ) ); + +} + +JabberClient::~JabberClient () +{ + delete d; +} + +void JabberClient::cleanUp () +{ + if ( d->jabberClient ) + { + d->jabberClient->close (); + } + + delete d->jabberClient; + delete d->jabberClientStream; + delete d->jabberClientConnector; + delete d->jabberTLSHandler; + delete d->jabberTLS; + + d->jabberClient = 0L; + d->jabberClientStream = 0L; + d->jabberClientConnector = 0L; + d->jabberTLSHandler = 0L; + d->jabberTLS = 0L; + + d->currentPenaltyTime = 0; + + d->jid = XMPP::Jid (); + d->password = QString::null; + + setForceTLS ( false ); + setUseSSL ( false ); + setUseXMPP09 ( false ); + setProbeSSL ( false ); + + setOverrideHost ( false ); + + setAllowPlainTextPassword ( true ); + + setFileTransfersEnabled ( false ); + setS5BServerPort ( 8010 ); + + setClientName ( QString::null ); + setClientVersion ( QString::null ); + setOSName ( QString::null ); + + setTimeZone ( "UTC", 0 ); + + setIgnoreTLSWarnings ( false ); + +} + +void JabberClient::slotUpdatePenaltyTime () +{ + + if ( d->currentPenaltyTime >= JABBER_PENALTY_TIME ) + d->currentPenaltyTime -= JABBER_PENALTY_TIME; + else + d->currentPenaltyTime = 0; + + QTimer::singleShot ( JABBER_PENALTY_TIME * 1000, this, SLOT ( slotUpdatePenaltyTime () ) ); + +} + +void JabberClient::setIgnoreTLSWarnings ( bool flag ) +{ + + d->ignoreTLSWarnings = flag; + +} + +bool JabberClient::ignoreTLSWarnings () +{ + + return d->ignoreTLSWarnings; + +} + +bool JabberClient::setS5BServerPort ( int port ) +{ + + d->s5bServerPort = port; + + if ( fileTransfersEnabled () ) + { + return s5bServer()->start ( port ); + } + + return true; + +} + +int JabberClient::s5bServerPort () const +{ + + return d->s5bServerPort; + +} + +XMPP::S5BServer *JabberClient::s5bServer () +{ + + if ( !d->s5bServer ) + { + d->s5bServer = new XMPP::S5BServer (); + QObject::connect ( d->s5bServer, SIGNAL ( destroyed () ), this, SLOT ( slotS5BServerGone () ) ); + + /* + * Try to start the server at the default port here. + * We have no way of notifying the caller of an error. + * However, since the caller will usually also + * use setS5BServerPort() to ensure the correct + * port, we can return an error code there. + */ + if ( fileTransfersEnabled () ) + { + s5bServer()->start ( d->s5bServerPort ); + } + } + + return d->s5bServer; + +} + +void JabberClient::slotS5BServerGone () +{ + + d->s5bServer = 0L; + + if ( d->jabberClient ) + d->jabberClient->s5bManager()->setServer( 0L ); + +} + +void JabberClient::addS5BServerAddress ( const QString &address ) +{ + QStringList newList; + + d->s5bAddressList.append ( address ); + + // now filter the list without dupes + for ( QStringList::Iterator it = d->s5bAddressList.begin (); it != d->s5bAddressList.end (); ++it ) + { + if ( !newList.contains ( *it ) ) + newList.append ( *it ); + } + + s5bServer()->setHostList ( newList ); + +} + +void JabberClient::removeS5BServerAddress ( const QString &address ) +{ + QStringList newList; + + QStringList::iterator it = d->s5bAddressList.find ( address ); + if ( it != d->s5bAddressList.end () ) + { + d->s5bAddressList.remove ( it ); + } + + if ( d->s5bAddressList.isEmpty () ) + { + delete d->s5bServer; + d->s5bServer = 0L; + } + else + { + // now filter the list without dupes + for ( QStringList::Iterator it = d->s5bAddressList.begin (); it != d->s5bAddressList.end (); ++it ) + { + if ( !newList.contains ( *it ) ) + newList.append ( *it ); + } + + s5bServer()->setHostList ( newList ); + } + +} + +void JabberClient::setForceTLS ( bool flag ) +{ + + d->forceTLS = flag; + +} + +bool JabberClient::forceTLS () const +{ + + return d->forceTLS; + +} + +void JabberClient::setUseSSL ( bool flag ) +{ + + d->useSSL = flag; + +} + +bool JabberClient::useSSL () const +{ + + return d->useSSL; + +} + +void JabberClient::setUseXMPP09 ( bool flag ) +{ + + d->useXMPP09 = flag; + +} + +bool JabberClient::useXMPP09 () const +{ + + return d->useXMPP09; + +} + +void JabberClient::setProbeSSL ( bool flag ) +{ + + d->probeSSL = flag; + +} + +bool JabberClient::probeSSL () const +{ + + return d->probeSSL; + +} + +void JabberClient::setOverrideHost ( bool flag, const QString &server, int port ) +{ + + d->overrideHost = flag; + d->server = server; + d->port = port; + +} + +bool JabberClient::overrideHost () const +{ + + return d->overrideHost; + +} + +void JabberClient::setAllowPlainTextPassword ( bool flag ) +{ + + d->allowPlainTextPassword = flag; + +} + +bool JabberClient::allowPlainTextPassword () const +{ + + return d->allowPlainTextPassword; + +} + +void JabberClient::setFileTransfersEnabled ( bool flag, const QString &localAddress ) +{ + + d->fileTransfersEnabled = flag; + d->localAddress = localAddress; + +} + +QString JabberClient::localAddress () const +{ + + return d->localAddress; + +} + +bool JabberClient::fileTransfersEnabled () const +{ + + return d->fileTransfersEnabled; + +} + +void JabberClient::setClientName ( const QString &clientName ) +{ + + d->clientName = clientName; + +} + +QString JabberClient::clientName () const +{ + + return d->clientName; + +} + +void JabberClient::setClientVersion ( const QString &clientVersion ) +{ + + d->clientVersion = clientVersion; + +} + +QString JabberClient::clientVersion () const +{ + + return d->clientVersion; + +} + +void JabberClient::setOSName ( const QString &osName ) +{ + + d->osName = osName; + +} + +QString JabberClient::osName () const +{ + + return d->osName; + +} + +void JabberClient::setCapsNode( const QString &capsNode ) +{ + d->capsNode = capsNode; +} + +QString JabberClient::capsNode() const +{ + return d->capsNode; +} + +void JabberClient::setCapsVersion( const QString &capsVersion ) +{ + d->capsVersion = capsVersion; +} + +QString JabberClient::capsVersion() const +{ + return d->capsVersion; +} + +QString JabberClient::capsExt() const +{ + if(d->jabberClient) + { + return d->jabberClient->capsExt(); + } + + return QString(); +} +void JabberClient::setDiscoIdentity( DiscoItem::Identity identity ) +{ + d->discoIdentity = identity; +} + +DiscoItem::Identity JabberClient::discoIdentity() const +{ + return d->discoIdentity; +} + +void JabberClient::setTimeZone ( const QString &timeZoneName, int timeZoneOffset ) +{ + + d->timeZoneName = timeZoneName; + d->timeZoneOffset = timeZoneOffset; + +} + +QString JabberClient::timeZoneName () const +{ + + return d->timeZoneName; + +} + +int JabberClient::timeZoneOffset () const +{ + + return d->timeZoneOffset; + +} + +int JabberClient::getPenaltyTime () +{ + + int currentTime = d->currentPenaltyTime; + + d->currentPenaltyTime += JABBER_PENALTY_TIME; + + return currentTime; + +} + +XMPP::Client *JabberClient::client () const +{ + + return d->jabberClient; + +} + +XMPP::ClientStream *JabberClient::clientStream () const +{ + + return d->jabberClientStream; + +} + +JabberConnector *JabberClient::clientConnector () const +{ + + return d->jabberClientConnector; + +} + +XMPP::Task *JabberClient::rootTask () const +{ + + if ( client () ) + { + return client()->rootTask (); + } + else + { + return 0l; + } + +} + +XMPP::FileTransferManager *JabberClient::fileTransferManager () const +{ + + if ( client () ) + { + return client()->fileTransferManager (); + } + else + { + return 0L; + } + +} + +XMPP::Jid JabberClient::jid () const +{ + + return d->jid; + +} + +JabberClient::ErrorCode JabberClient::connect ( const XMPP::Jid &jid, const QString &password, bool auth ) +{ + /* + * Close any existing connection. + */ + if ( d->jabberClient ) + { + d->jabberClient->close (); + } + + d->jid = jid; + d->password = password; + + /* + * Return an error if we should force TLS but it's not available. + */ + if ( ( forceTLS () || useSSL () || probeSSL () ) && !QCA::isSupported ( QCA::CAP_TLS ) ) + { + return NoTLS; + } + + /* + * Instantiate connector, responsible for dealing with the socket. + * This class uses KDE's socket code, which in turn makes use of + * the global proxy settings. + */ + d->jabberClientConnector = new JabberConnector; + + d->jabberClientConnector->setOptSSL ( useSSL () ); + + if ( useXMPP09 () ) + { + if ( overrideHost () ) + { + d->jabberClientConnector->setOptHostPort ( d->server, d->port ); + } + + d->jabberClientConnector->setOptProbe ( probeSSL () ); + + } + + /* + * Setup authentication layer + */ + if ( QCA::isSupported ( QCA::CAP_TLS ) ) + { + d->jabberTLS = new QCA::TLS; + d->jabberTLSHandler = new XMPP::QCATLSHandler ( d->jabberTLS ); + + { + using namespace XMPP; + QObject::connect ( d->jabberTLSHandler, SIGNAL ( tlsHandshaken() ), this, SLOT ( slotTLSHandshaken () ) ); + } + + QPtrList certStore; + d->jabberTLS->setCertificateStore ( certStore ); + } + + /* + * Instantiate client stream which handles the network communication by referring + * to a connector (proxying etc.) and a TLS handler (security layer) + */ + d->jabberClientStream = new XMPP::ClientStream ( d->jabberClientConnector, d->jabberTLSHandler ); + + { + using namespace XMPP; + QObject::connect ( d->jabberClientStream, SIGNAL ( needAuthParams(bool, bool, bool) ), + this, SLOT ( slotCSNeedAuthParams (bool, bool, bool) ) ); + QObject::connect ( d->jabberClientStream, SIGNAL ( authenticated () ), + this, SLOT ( slotCSAuthenticated () ) ); + QObject::connect ( d->jabberClientStream, SIGNAL ( connectionClosed () ), + this, SLOT ( slotCSDisconnected () ) ); + QObject::connect ( d->jabberClientStream, SIGNAL ( delayedCloseFinished () ), + this, SLOT ( slotCSDisconnected () ) ); + QObject::connect ( d->jabberClientStream, SIGNAL ( warning (int) ), + this, SLOT ( slotCSWarning (int) ) ); + QObject::connect ( d->jabberClientStream, SIGNAL ( error (int) ), + this, SLOT ( slotCSError (int) ) ); + } + + d->jabberClientStream->setOldOnly ( useXMPP09 () ); + + /* + * Initiate anti-idle timer (will be triggered every 55 seconds). + */ + d->jabberClientStream->setNoopTime ( 55000 ); + + /* + * Allow plaintext password authentication or not? + */ + d->jabberClientStream->setAllowPlain( allowPlainTextPassword () ); + + /* + * Setup client layer. + */ + d->jabberClient = new XMPP::Client ( this ); + + /* + * Enable file transfer (IP and server will be set after connection + * has been established. + */ + if ( fileTransfersEnabled () ) + { + d->jabberClient->setFileTransferEnabled ( true ); + + { + using namespace XMPP; + QObject::connect ( d->jabberClient->fileTransferManager(), SIGNAL ( incomingReady() ), + this, SLOT ( slotIncomingFileTransfer () ) ); + } + } + + /* This should only be done here to connect the signals, otherwise it is a + * bad idea. + */ + { + using namespace XMPP; + QObject::connect ( d->jabberClient, SIGNAL ( subscription (const Jid &, const QString &) ), + this, SLOT ( slotSubscription (const Jid &, const QString &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( rosterRequestFinished ( bool, int, const QString & ) ), + this, SLOT ( slotRosterRequestFinished ( bool, int, const QString & ) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( rosterItemAdded (const RosterItem &) ), + this, SLOT ( slotNewContact (const RosterItem &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( rosterItemUpdated (const RosterItem &) ), + this, SLOT ( slotContactUpdated (const RosterItem &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( rosterItemRemoved (const RosterItem &) ), + this, SLOT ( slotContactDeleted (const RosterItem &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( resourceAvailable (const Jid &, const Resource &) ), + this, SLOT ( slotResourceAvailable (const Jid &, const Resource &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( resourceUnavailable (const Jid &, const Resource &) ), + this, SLOT ( slotResourceUnavailable (const Jid &, const Resource &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( messageReceived (const Message &) ), + this, SLOT ( slotReceivedMessage (const Message &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( groupChatJoined (const Jid &) ), + this, SLOT ( slotGroupChatJoined (const Jid &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( groupChatLeft (const Jid &) ), + this, SLOT ( slotGroupChatLeft (const Jid &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( groupChatPresence (const Jid &, const Status &) ), + this, SLOT ( slotGroupChatPresence (const Jid &, const Status &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( groupChatError (const Jid &, int, const QString &) ), + this, SLOT ( slotGroupChatError (const Jid &, int, const QString &) ) ); + //QObject::connect ( d->jabberClient, SIGNAL (debugText (const QString &) ), + // this, SLOT ( slotPsiDebug (const QString &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( xmlIncoming(const QString& ) ), + this, SLOT ( slotIncomingXML (const QString &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( xmlOutgoing(const QString& ) ), + this, SLOT ( slotOutgoingXML (const QString &) ) ); + } + + d->jabberClient->setClientName ( clientName () ); + d->jabberClient->setClientVersion ( clientVersion () ); + d->jabberClient->setOSName ( osName () ); + + // Set caps information + d->jabberClient->setCapsNode( capsNode() ); + d->jabberClient->setCapsVersion( capsVersion() ); + + // Set Disco Identity + d->jabberClient->setIdentity( discoIdentity() ); + + d->jabberClient->setTimeZone ( timeZoneName (), timeZoneOffset () ); + + d->jabberClient->connectToServer ( d->jabberClientStream, jid, auth ); + + return Ok; + +} + +void JabberClient::disconnect () +{ + + if ( d->jabberClient ) + { + d->jabberClient->close (); + } + else + { + cleanUp (); + } + +} + +void JabberClient::disconnect( XMPP::Status &reason ) +{ + if ( d->jabberClient ) + { + if ( d->jabberClientStream->isActive() ) + { + XMPP::JT_Presence *pres = new JT_Presence(rootTask()); + reason.setIsAvailable( false ); + pres->pres( reason ); + pres->go(); + + d->jabberClientStream->close(); + d->jabberClient->close(); + } + } + else + { + cleanUp(); + } +} + +bool JabberClient::isConnected () const +{ + + if ( d->jabberClient ) + { + return d->jabberClient->isActive (); + } + + return false; + +} + +void JabberClient::joinGroupChat ( const QString &host, const QString &room, const QString &nick ) +{ + + client()->groupChatJoin ( host, room, nick ); + +} + +void JabberClient::joinGroupChat ( const QString &host, const QString &room, const QString &nick, const QString &password ) +{ + + client()->groupChatJoin ( host, room, nick, password ); + +} + +void JabberClient::leaveGroupChat ( const QString &host, const QString &room ) +{ + + client()->groupChatLeave ( host, room ); + +} + +void JabberClient::setGroupChatStatus( const QString & host, const QString & room, const XMPP::Status & status ) +{ + client()->groupChatSetStatus( host, room, status); +} + +void JabberClient::changeGroupChatNick( const QString & host, const QString & room, const QString & nick, const XMPP::Status & status ) +{ + client()->groupChatChangeNick( host, room, nick, status ); +} + + +void JabberClient::sendMessage ( const XMPP::Message &message ) +{ + + client()->sendMessage ( message ); + +} + +void JabberClient::send ( const QString &packet ) +{ + + client()->send ( packet ); + +} + +void JabberClient::requestRoster () +{ + + client()->rosterRequest (); + +} + +void JabberClient::slotPsiDebug ( const QString & _msg ) +{ + QString msg = _msg; + + msg = msg.replace( QRegExp( "[^<]*\n" ), "[Filtered]\n" ); + msg = msg.replace( QRegExp( "[^<]*\n" ), "[Filtered]\n" ); + + emit debugMessage ( "Psi: " + msg ); + +} + +void JabberClient::slotIncomingXML ( const QString & _msg ) +{ + QString msg = _msg; + + msg = msg.replace( QRegExp( "[^<]*\n" ), "[Filtered]\n" ); + msg = msg.replace( QRegExp( "[^<]*\n" ), "[Filtered]\n" ); + + emit debugMessage ( "XML IN: " + msg ); + +} + +void JabberClient::slotOutgoingXML ( const QString & _msg ) +{ + QString msg = _msg; + + msg = msg.replace( QRegExp( "[^<]*\n" ), "[Filtered]\n" ); + msg = msg.replace( QRegExp( "[^<]*\n" ), "[Filtered]\n" ); + + emit debugMessage ( "XML OUT: " + msg ); + +} + +void JabberClient::slotTLSHandshaken () +{ + + emit debugMessage ( "TLS handshake done, testing certificate validity..." ); + + // FIXME: in the future, this should be handled by KDE, not QCA + int validityResult = d->jabberTLS->certificateValidityResult (); + + if ( validityResult == QCA::TLS::Valid ) + { + emit debugMessage ( "Certificate is valid, continuing." ); + + // valid certificate, continue + d->jabberTLSHandler->continueAfterHandshake (); + } + else + { + emit debugMessage ( "Certificate is not valid, asking user what to do next." ); + + // certificate is not valid, query the user + if ( ignoreTLSWarnings () ) + { + emit debugMessage ( "We are supposed to ignore TLS warnings, continuing." ); + d->jabberTLSHandler->continueAfterHandshake (); + } + + emit tlsWarning ( validityResult ); + } + +} + +void JabberClient::continueAfterTLSWarning () +{ + + if ( d->jabberTLSHandler ) + { + d->jabberTLSHandler->continueAfterHandshake (); + } + +} + +void JabberClient::slotCSNeedAuthParams ( bool user, bool pass, bool realm ) +{ + emit debugMessage ( "Sending auth credentials..." ); + + if ( user ) + { + d->jabberClientStream->setUsername ( jid().node () ); + } + + if ( pass ) + { + d->jabberClientStream->setPassword ( d->password ); + } + + if ( realm ) + { + d->jabberClientStream->setRealm ( jid().domain () ); + } + + d->jabberClientStream->continueAfterParams (); + +} + +void JabberClient::slotCSAuthenticated () +{ + emit debugMessage ( "Connected to Jabber server." ); + + /* + * Determine local IP address. + * FIXME: This is ugly! + */ + if ( localAddress().isEmpty () ) + { + // code for Iris-type bytestreams + ByteStream *irisByteStream = d->jabberClientConnector->stream(); + if ( irisByteStream->inherits ( "BSocket" ) || irisByteStream->inherits ( "XMPP::BSocket" ) ) + { + d->localAddress = ( (BSocket *)irisByteStream )->address().toString (); + } + + // code for the KDE-type bytestream + JabberByteStream *kdeByteStream = dynamic_cast(d->jabberClientConnector->stream()); + if ( kdeByteStream ) + { + d->localAddress = kdeByteStream->socket()->localAddress().nodeName (); + } + } + + if ( fileTransfersEnabled () ) + { + // setup file transfer + addS5BServerAddress ( localAddress () ); + d->jabberClient->s5bManager()->setServer ( s5bServer () ); + } + + // start the client operation + d->jabberClient->start ( jid().domain (), jid().node (), d->password, jid().resource () ); + + emit connected (); +} + +void JabberClient::slotCSDisconnected () +{ + + /* FIXME: + * We should delete the XMPP::Client instance here, + * but timers etc prevent us from doing so. (Psi does + * not like to be deleted from a slot). + */ + + emit debugMessage ( "Disconnected, freeing up file transfer port..." ); + + // delete local address from S5B server + removeS5BServerAddress ( localAddress () ); + + emit csDisconnected (); + +} + +void JabberClient::slotCSWarning ( int warning ) +{ + + emit debugMessage ( "Client stream warning." ); + + /* + * FIXME: process all other warnings + */ + switch ( warning ) + { + //case XMPP::ClientStream::WarnOldVersion: + case XMPP::ClientStream::WarnNoTLS: + if ( forceTLS () ) + { + disconnect (); + emit error ( NoTLS ); + return; + } + break; + } + + d->jabberClientStream->continueAfterWarning (); + +} + +void JabberClient::slotCSError ( int error ) +{ + + emit debugMessage ( "Client stream error." ); + + emit csError ( error ); + +} + +void JabberClient::slotRosterRequestFinished ( bool success, int /*statusCode*/, const QString &/*statusString*/ ) +{ + + emit rosterRequestFinished ( success ); + +} + +void JabberClient::slotIncomingFileTransfer () +{ + + emit incomingFileTransfer (); + +} + +void JabberClient::slotNewContact ( const XMPP::RosterItem &item ) +{ + + emit newContact ( item ); + +} + +void JabberClient::slotContactDeleted ( const RosterItem &item ) +{ + + emit contactDeleted ( item ); + +} + +void JabberClient::slotContactUpdated ( const RosterItem &item ) +{ + + emit contactUpdated ( item ); + +} + +void JabberClient::slotResourceAvailable ( const Jid &jid, const Resource &resource ) +{ + + emit resourceAvailable ( jid, resource ); + +} + +void JabberClient::slotResourceUnavailable ( const Jid &jid, const Resource &resource ) +{ + + emit resourceUnavailable ( jid, resource ); + +} + +void JabberClient::slotReceivedMessage ( const Message &message ) +{ + + emit messageReceived ( message ); + +} + +void JabberClient::slotGroupChatJoined ( const Jid &jid ) +{ + + emit groupChatJoined ( jid ); + +} + +void JabberClient::slotGroupChatLeft ( const Jid &jid ) +{ + + emit groupChatLeft ( jid ); + +} + +void JabberClient::slotGroupChatPresence ( const Jid &jid, const Status &status) +{ + + emit groupChatPresence ( jid, status ); + +} + +void JabberClient::slotGroupChatError ( const Jid &jid, int error, const QString &reason) +{ + + emit groupChatError ( jid, error, reason ); + +} + +void JabberClient::slotSubscription ( const Jid &jid, const QString &type ) +{ + + emit subscription ( jid, type ); + +} + + +#include "jabberclient.moc" diff --git a/kopete/protocols/jabber/jabberclient.h b/kopete/protocols/jabber/jabberclient.h new file mode 100644 index 00000000..7cd33e02 --- /dev/null +++ b/kopete/protocols/jabber/jabberclient.h @@ -0,0 +1,600 @@ + +/*************************************************************************** + jabberclient.h - Generic Jabber Client Class + ------------------- + begin : Sat May 25 2005 + copyright : (C) 2005 by Till Gerken + (C) 2006 by Michaël Larouche + + Kopete (C) 2001-2006 Kopete developers + . + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef JABBERCLIENT_H +#define JABBERCLIENT_H + +#include + +// include these because of namespace reasons +#include +#include +#include + +using namespace XMPP; + +class JabberConnector; + +/** + * This class provides an interface to the Iris subsystem. The goal is to + * abstract the Iris layer and manage it via a single, simple to use class. + * By default, @ref JabberClient will attempt to establish a connection + * using XMPP 1.0. This means that apart from the JID and password, no + * further details are necessary to connect. The server and port will be + * determined using a SRV lookup. If TLS is possible (meaning, the TLS + * plugin is available and the server supports TLS) it will automatically + * be used. Otherwise, a non-encrypted connection will be established. + * If XMPP 1.0 is not possible, the connection will fall back to the old + * protocol. By default, this connection is not encrypted. You can, however, + * use @ref setUseSSL to immediately attempt an SSL connection. This is + * most useful if you want to establish an SSL connection to a non-standard + * port, in which case you will also have to use @ref setOverrideHost. In case + * XMPP 1.0 does not work, an automatic attempt to connect to the standard port + * 5223 with SSL can be made with @ref setProbeSSL. If the attempt is not + * sucessful, the connection will fall back to an unencrypted attempt + * at port 5222. + * @brief Provides a Jabber client + * @author Till Gerken + */ +class JabberClient : public QObject +{ + +Q_OBJECT + +public: + /** + * Error codes indicating problems during operation. + */ + enum ErrorCode + { + Ok, /** No error. */ + InvalidPassword, /** Password used to connect to the server was incorrect. */ + AlreadyConnected, /** A new connection was attempted while the previous one hasn't been closed. */ + NoTLS, /** Use of TLS has been forced (see @ref forceTLS) but TLS is not available, either server- or client-side. */ + InvalidPasswordForMUC = 401, /** A password is require to enter on this Multi-User Chat */ + NicknameConflict = 409, /** There is already someone with that nick connected to the Multi-User Chat */ + BannedFromThisMUC = 403, /** You can't join this Multi-User Chat because you were bannished */ + MaxUsersReachedForThisMuc = 503 /** You can't join this Multi-User Chat because it is full */ + }; + + JabberClient(); + ~JabberClient(); + + /** + * Connect to a Jabber server. + * @param jid JID to connect to. + * @param password Password to authenticate with. + * @param auth True if authentication should be done, false if not. + */ + ErrorCode connect ( const XMPP::Jid &jid, const QString &password, bool auth = true ); + + /** + * Disconnect from Jabber server. + */ + void disconnect (); + + /** + * Disconnect from Jabber server with reason + * @param reason The reason for disconnecting + */ + void disconnect (XMPP::Status &reason); + + /** + * Returns if this instance is connected to a server. + */ + bool isConnected () const; + + /** + * Returns the JID associated with this instance. + */ + XMPP::Jid jid () const; + + /** + * Set flag to ignore TLS warnings. If TLS + * warnings are not ignored, the class will emit + * @ref tlsWarning and wait for the user to + * call @ref continueAfterTLSWarning or + * @ref disconnect. Default is false. + */ + void setIgnoreTLSWarnings ( bool flag ); + /** + * Return if TLS warnings are being ignored. + */ + bool ignoreTLSWarnings (); + + /** + * Continue after a @ref tlsWarning signal. + */ + void continueAfterTLSWarning (); + + /** + * Set the port on which the S5B server should listen. + * This is only taken into account if @ref setFileTransfersEnabled + * is set to true. + * @return True if port could be bound, false if not. + */ + bool setS5BServerPort ( int port ); + /** + * Returns the port the S5B server listens on. + */ + int s5bServerPort () const; + + /** + * Force the use of TLS. If TLS connections are forced, + * unencrypted connections will not be established. + * Default is false. + */ + void setForceTLS ( bool flag ); + /** + * Returns if TLS connections are forced. + */ + bool forceTLS () const; + + /** + * Force direct SSL connection, also for the + * handshake. This is only useful if you know + * the server supports it or you want to use + * a non-standard port, in which case @ref setOverrideHost + * will be useful. Default is false. + */ + void setUseSSL ( bool flag ); + /** + * Returns if an SSL connection attempt should be made. + */ + bool useSSL () const; + + /** + * Use only the old protocol (pre-XMPP 1.0). This should only + * be used with servers not supporting XMPP 1.0 or with servers + * that have a broken login procedure. Default is false. If + * a connection attempt is not possible, Iris will automatically + * fall back to the old protocol. + */ + void setUseXMPP09 ( bool flag ); + /** + * Returns if the old protocol should be used. + */ + bool useXMPP09 () const; + + /** + * Probe port 5223 if an SSL connection is possible. If + * a connection is not possible, an unencrypted connection + * will be attempted at port 5222. This is only meaningful + * if @ref useXMPP09 is true. Default is false. + */ + void setProbeSSL ( bool flag ); + /** + * Returns if SSL support will be probed. + */ + bool probeSSL () const; + + /** + * Override the name and port of the server to connect to. + * This only has an effect if the old protocol (@ref useXMPP09) + * has been enabled. Default is false. + */ + void setOverrideHost ( bool flag, const QString &server = "", int port = 5222 ); + /** + * Returns if the server name and port are overridden. + */ + bool overrideHost () const; + + /** + * Allow the transmission of a plain text password. If digested + * passwords are supported by the server, they will still be preferred. + * Defaults to true. + */ + void setAllowPlainTextPassword ( bool flag ); + /** + * Returns if plain text passwords are allowed. + */ + bool allowPlainTextPassword () const; + + /** + * Enable file transfers. Default is false. + * @param flag Whether to enable file transfers. + * @param localAddress Local address to receive file transfers at. Will be determined automatically if not specified. + */ + void setFileTransfersEnabled ( bool flag, const QString &localAddress = QString::null ); + + /** + * Returns the address of the local interface. + */ + QString localAddress () const; + + /** + * Returns if file transfers are enabled. + */ + bool fileTransfersEnabled () const; + + /** + * Set client name. + */ + void setClientName ( const QString &clientName ); + /** + * Return client name. + */ + QString clientName () const; + + /** + * Set client version. + */ + void setClientVersion ( const QString &clientVersion ); + /** + * Return client version. + */ + QString clientVersion () const; + + /** + * Set operating system name. + */ + void setOSName ( const QString &osName ); + /** + * Return operating system name. + */ + QString osName () const; + + /** + * Set the caps(JEP-0115: Entity capabilities) node name. + * @param node Node name. + */ + void setCapsNode( const QString &capsNode ); + /** + * Return the caps node name for this client. + * @return the caps node name. + */ + QString capsNode() const; + + /** + * Set the caps(JEP-0115: Entity capabilities) node version. + * @param capsVersion the node version. + */ + void setCapsVersion( const QString &capsVersion ); + /** + * Return the caps version for this client. + * @return the caps version. + */ + QString capsVersion() const; + + /** + * Return the caps extension list for this client. + * @return A string containing all extensions separated by space. + */ + QString capsExt() const; + + /** + * Set the disco Identity information for this client. + * Create a Disco identity like this: + * @code + * DiscoItem::Identity identity; + * identity.category = "client"; + * identity.type = "pc"; + * identity.name = "Kopete"; + * @endcode + * + * @param identity DiscoItem::Identity for the client. + */ + void setDiscoIdentity(DiscoItem::Identity identity); + /** + * Get the disco Identity information for this client. + * @return the DiscoItem::Identity for this client. + */ + DiscoItem::Identity discoIdentity() const; + + /** + * Set timezone information. Default is UTC. + */ + void setTimeZone ( const QString &timeZoneName, int timeZoneOffset ); + /** + * Return timezone name. + */ + QString timeZoneName () const; + /** + * Return timezone offset. + */ + int timeZoneOffset () const; + + /** + * This method can be used to implement a penalty + * system when a lot of queries need to be sent to the + * server. Using the time returned by this method, + * the caller can determine a delay until the next + * operation in the queue can be carried out. + * @brief Return current penalty time in seconds. + */ + int getPenaltyTime (); + + /** + * Return the XMPP client instance. + */ + XMPP::Client *client () const; + + /** + * Return client stream instance. + */ + XMPP::ClientStream *clientStream () const; + + /** + * Return client connector instance. + */ + JabberConnector *clientConnector () const; + + /** + * Get the root task for this connection. + * You need this instance for every task + * you want to start. + */ + XMPP::Task *rootTask () const; + + /** + * Returns the file transfer manager + * instance that deals with current file + * transfers. + */ + XMPP::FileTransferManager *fileTransferManager () const; + + /** + * Join a group chat. + * @param host Node to join the room at. + * @param room Name of room to join. + * @param nick Nick name you want to join with. + */ + void joinGroupChat ( const QString &host, const QString &room, const QString &nick ); + + /** + * Join a group chat that require a password. + * @param host Node to join the room at. + * @param room Name of room to join. + * @param nick Nick name you want to join with. + * @param password The password to join the room. + */ + void joinGroupChat ( const QString &host, const QString &room, const QString &nick, const QString &password ); + + /** + * Leave a group chat. + * @param host Node to leave room at. + * @param room Name of room to leave. + */ + void leaveGroupChat ( const QString &host, const QString &room ); + + /** + * change the status of a group chat + */ + void setGroupChatStatus(const QString &host, const QString &room, const XMPP::Status &); + /** + * change the nick in a group chat + */ + void changeGroupChatNick(const QString &host, const QString &room, const QString &nick, const XMPP::Status &status =XMPP::Status()); + + /** + * Send a message. + */ + void sendMessage ( const XMPP::Message &message ); + + /** + * Send raw packet to the server. + */ + void send ( const QString &packet ); + + /** + * Request the roster from the Jabber server. + */ + void requestRoster (); + +signals: + /** + * Connected successfully. + */ + void connected (); + + /** + * Client stream authenticated. This + * signal is emitted when the socket + * connection has been successfully + * established, before sending the login + * packet. + */ + void csAuthenticated (); + + /** + * Client stream error. + */ + void csError ( int error ); + + /** + * Client stream was disconnected. + */ + void csDisconnected (); + + /** + * TLS problem encountered. + */ + void tlsWarning ( int validityResult ); + + /** + * A new file transfer needs to be handled. + * The file transfer can be dealt with by + * querying the file transfer manager from + * @ref client. + */ + void incomingFileTransfer (); + + /** + * Fatal error has been encountered, + * further operations are not possible. + */ + void error ( JabberClient::ErrorCode code ); + + /** + * Roster has been transmitted and processed. + */ + void rosterRequestFinished ( bool success ); + + /** + * A new contact appeared on the roster. + */ + void newContact ( const XMPP::RosterItem &item ); + + /** + * A contact has been removed from the roster. + */ + void contactDeleted ( const XMPP::RosterItem &item ); + + /** + * A roster item has changed. + */ + void contactUpdated ( const XMPP::RosterItem &item ); + + /** + * New resource is available for a contact. + */ + void resourceAvailable ( const XMPP::Jid &jid, const XMPP::Resource &resource ); + + /** + * An existing resource has been removed. + */ + void resourceUnavailable ( const XMPP::Jid &jid, const XMPP::Resource &resource ); + + /** + * A new message has been received. + */ + void messageReceived ( const XMPP::Message &message ); + + /** + * Group chat has been joined. + */ + void groupChatJoined ( const XMPP::Jid &jid ); + + /** + * Group chat has been left. + */ + void groupChatLeft ( const XMPP::Jid &jid ); + + /** + * A presence to a group chat has been signalled. + */ + void groupChatPresence ( const XMPP::Jid &jid, const XMPP::Status &status ); + + /** + * An error was encountered joining or processing a group chat. + */ + void groupChatError ( const XMPP::Jid &jid, int error, const QString &reason ); + + /** + * New subscription request. + */ + void subscription ( const XMPP::Jid &jid, const QString &type ); + + /** + * Dispatches a debug message. Debug messages + * include incoming and outgoing XML packets + * as well as internal status messages. + */ + void debugMessage ( const QString &message ); + +private: + class Private; + Private *d; + + /** + * Delete all member classes and reset the class to a predefined state. + */ + void cleanUp (); + + /** + * Return current instance of the S5B server. + */ + XMPP::S5BServer *s5bServer (); + /** + * Add an address that the S5B server should handle. + */ + void addS5BServerAddress ( const QString &address ); + /** + * Remove an address that the S5B server currently handles. + */ + void removeS5BServerAddress ( const QString &address ); + +private slots: + /* S5B server object has been destroyed. */ + void slotS5BServerGone (); + + /* update the penalty timer */ + void slotUpdatePenaltyTime (); + + /* Login if the connection was OK. */ + void slotCSNeedAuthParams (bool user, bool pass, bool realm); + + /* Called from Psi: tells us when we're logged in OK. */ + void slotCSAuthenticated (); + + /* Called from Psi: tells us when we've been disconnected from the server. */ + void slotCSDisconnected (); + + /* Called from Psi: alerts us to a protocol warning. */ + void slotCSWarning (int); + + /* Called from Psi: alerts us to a protocol error. */ + void slotCSError (int); + + /* Called from Psi: report certificate status */ + void slotTLSHandshaken (); + + /* Called from Psi: roster request finished */ + void slotRosterRequestFinished ( bool success, int statusCode, const QString &statusString ); + + /* Called from Psi: incoming file transfer */ + void slotIncomingFileTransfer (); + + /* A new item appeared in our roster */ + void slotNewContact (const RosterItem &); + + /* An item has been deleted from our roster. */ + void slotContactDeleted (const RosterItem &); + + /* Update a contact's details. */ + void slotContactUpdated (const RosterItem &); + + /* Someone on our contact list had (another) resource come online. */ + void slotResourceAvailable (const Jid &, const Resource &); + + /* Someone on our contact list had a resource go offline. */ + void slotResourceUnavailable (const Jid &, const Resource &); + + /* Incoming message. */ + void slotReceivedMessage (const Message &); + + /* Called from Psi: debug messages from the backend. */ + void slotPsiDebug (const QString & msg); + void slotIncomingXML (const QString &msg); + void slotOutgoingXML (const QString &msg); + + /* Slots for handling group chats. */ + void slotGroupChatJoined (const Jid & jid); + void slotGroupChatLeft (const Jid & jid); + void slotGroupChatPresence (const Jid & jid, const Status & status); + void slotGroupChatError (const Jid & jid, int error, const QString & reason); + + /* Incoming subscription request. */ + void slotSubscription (const Jid & jid, const QString & type); + +}; + +#endif diff --git a/kopete/protocols/jabber/jabberconnector.cpp b/kopete/protocols/jabber/jabberconnector.cpp new file mode 100644 index 00000000..2359dd69 --- /dev/null +++ b/kopete/protocols/jabber/jabberconnector.cpp @@ -0,0 +1,132 @@ + +/*************************************************************************** + jabberconnector.cpp - Socket Connector for Jabber + ------------------- + begin : Wed Jul 7 2004 + copyright : (C) 2004 by Till Gerken + + Kopete (C) 2004 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include "jabberconnector.h" +#include "jabberbytestream.h" +#include "jabberprotocol.h" + +JabberConnector::JabberConnector ( QObject *parent, const char */*name*/ ) + : XMPP::Connector ( parent ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "New Jabber connector." << endl; + + mErrorCode = KNetwork::KSocketBase::NoError; + + mByteStream = new JabberByteStream ( this ); + + connect ( mByteStream, SIGNAL ( connected () ), this, SLOT ( slotConnected () ) ); + connect ( mByteStream, SIGNAL ( error ( int ) ), this, SLOT ( slotError ( int ) ) ); + +} + +JabberConnector::~JabberConnector () +{ + + delete mByteStream; + +} + +void JabberConnector::connectToServer ( const QString &server ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Initiating connection to " << server << endl; + + /* + * FIXME: we should use a SRV lookup to determine the + * actual server to connect to. As this is currently + * not supported yet, we're using setOptHostPort(). + * For XMPP 1.0, we need to enable this! + */ + + mErrorCode = KNetwork::KSocketBase::NoError; + + if ( !mByteStream->connect ( mHost, QString::number ( mPort ) ) ) + { + // Houston, we have a problem + mErrorCode = mByteStream->socket()->error (); + emit error (); + } + +} + +void JabberConnector::slotConnected () +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "We are connected." << endl; + + // FIXME: setPeerAddress() is something different, find out correct usage later + //KInetSocketAddress inetAddress = mStreamSocket->address().asInet().makeIPv6 (); + //setPeerAddress ( QHostAddress ( inetAddress.ipAddress().addr () ), inetAddress.port () ); + + emit connected (); + +} + +void JabberConnector::slotError ( int code ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Error detected: " << code << endl; + + mErrorCode = code; + emit error (); + +} + +int JabberConnector::errorCode () +{ + + return mErrorCode; + +} + +ByteStream *JabberConnector::stream () const +{ + + return mByteStream; + +} + +void JabberConnector::done () +{ + + mByteStream->close (); + +} + +void JabberConnector::setOptHostPort ( const QString &host, Q_UINT16 port ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Manually specifying host " << host << " and port " << port << endl; + + mHost = host; + mPort = port; + +} + +void JabberConnector::setOptSSL ( bool ssl ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Setting SSL to " << ssl << endl; + + setUseSSL ( ssl ); + +} + +void JabberConnector::setOptProbe ( bool ) +{ + // FIXME: Implement this. +} + +#include "jabberconnector.moc" diff --git a/kopete/protocols/jabber/jabberconnector.h b/kopete/protocols/jabber/jabberconnector.h new file mode 100644 index 00000000..6fbc4167 --- /dev/null +++ b/kopete/protocols/jabber/jabberconnector.h @@ -0,0 +1,65 @@ + +/*************************************************************************** + jabberconnector.cpp - Socket Connector for Jabber + ------------------- + begin : Wed Jul 7 2004 + copyright : (C) 2004 by Till Gerken + + Kopete (C) 2004 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef JABBERCONNECTOR_H +#define JABBERCONNECTOR_H + +#include +#include "jabberbytestream.h" + +class ByteStream; +class KResolverEntry; + +/** +@author Till Gerken +*/ +class JabberConnector : public XMPP::Connector +{ + +Q_OBJECT + +public: + JabberConnector ( QObject *parent = 0, const char *name = 0 ); + + ~JabberConnector (); + + void connectToServer ( const QString &server ); + ByteStream *stream () const; + void done (); + + void setOptHostPort ( const QString &host, Q_UINT16 port ); + void setOptSSL ( bool ); + void setOptProbe ( bool ); + + int errorCode (); + +private slots: + void slotConnected (); + void slotError ( int ); + +private: + QString mHost; + Q_UINT16 mPort; + int mErrorCode; + + JabberByteStream *mByteStream; + +}; + +#endif diff --git a/kopete/protocols/jabber/jabbercontact.cpp b/kopete/protocols/jabber/jabbercontact.cpp new file mode 100644 index 00000000..c8589e1e --- /dev/null +++ b/kopete/protocols/jabber/jabbercontact.cpp @@ -0,0 +1,1328 @@ + /* + * jabbercontact.cpp - Regular Kopete Jabber protocol contact + * + * Copyright (c) 2002-2004 by Till Gerken + * Copyright (c) 2006 by Olivier Goffart + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#include "jabbercontact.h" + +#include "xmpp_tasks.h" +#include "im.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopetecontactlist.h" +#include "kopetegroup.h" +#include "kopeteuiglobal.h" +#include "kopetechatsessionmanager.h" +#include "kopeteaccountmanager.h" +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabberclient.h" +#include "jabberchatsession.h" +#include "jabberresource.h" +#include "jabberresourcepool.h" +#include "jabberfiletransfer.h" +#include "jabbertransport.h" +#include "dlgjabbervcard.h" + +#ifdef SUPPORT_JINGLE +// #include "jinglesessionmanager.h" +// #include "jinglevoicesession.h" +#include "jinglevoicesessiondialog.h" +#endif + +/** + * JabberContact constructor + */ +JabberContact::JabberContact (const XMPP::RosterItem &rosterItem, Kopete::Account *_account, Kopete::MetaContact * mc, const QString &legacyId) + : JabberBaseContact ( rosterItem, _account, mc, legacyId) , mDiscoDone(false), m_syncTimer(0L) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << contactId() << " is created - " << this << endl; + // this contact is able to transfer files + setFileCapable ( true ); + + /* + * Catch when we're going online for the first time to + * update our properties from a vCard. (properties are + * not available during startup, so we need to read + * them later - this also serves as a random update + * feature) + * Note: The only time account->myself() could be a + * NULL pointer is if this contact here is the myself() + * instance itself. Since in that case we wouldn't + * get updates at all, we need to treat that as a + * special case. + */ + + mVCardUpdateInProgress = false; + + if ( !account()->myself () ) + { + // this contact is a regular contact + connect ( this, + SIGNAL ( onlineStatusChanged ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT ( slotCheckVCard () ) ); + } + else + { + // this contact is the myself instance + connect ( account()->myself (), + SIGNAL ( onlineStatusChanged ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT ( slotCheckVCard () ) ); + + connect ( account()->myself (), + SIGNAL ( onlineStatusChanged ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT ( slotCheckLastActivity ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + + /* + * Trigger update once if we're already connected for contacts + * that are being added while we are online. + */ + if ( account()->myself()->onlineStatus().isDefinitelyOnline() ) + { + slotGetTimedVCard (); + } + } + + mRequestOfflineEvent = false; + mRequestDisplayedEvent = false; + mRequestDeliveredEvent = false; + mRequestComposingEvent = false; + mRequestGoneEvent = false; +} + + + +JabberContact::~JabberContact() +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << contactId() << " is destroyed - " << this << endl; +} + +QPtrList *JabberContact::customContextMenuActions () +{ + + QPtrList *actionCollection = new QPtrList(); + + KActionMenu *actionAuthorization = new KActionMenu ( i18n ("Authorization"), "connect_established", this, "jabber_authorization"); + + KAction *resendAuthAction, *requestAuthAction, *removeAuthAction; + + resendAuthAction = new KAction (i18n ("(Re)send Authorization To"), "mail_forward", 0, + this, SLOT (slotSendAuth ()), actionAuthorization, "actionSendAuth"); + resendAuthAction->setEnabled( mRosterItem.subscription().type() == XMPP::Subscription::To || mRosterItem.subscription().type() == XMPP::Subscription::None ); + actionAuthorization->insert(resendAuthAction); + + requestAuthAction = new KAction (i18n ("(Re)request Authorization From"), "mail_reply", 0, + this, SLOT (slotRequestAuth ()), actionAuthorization, "actionRequestAuth"); + requestAuthAction->setEnabled( mRosterItem.subscription().type() == XMPP::Subscription::From || mRosterItem.subscription().type() == XMPP::Subscription::None ); + actionAuthorization->insert(requestAuthAction); + + removeAuthAction = new KAction (i18n ("Remove Authorization From"), "mail_delete", 0, + this, SLOT (slotRemoveAuth ()), actionAuthorization, "actionRemoveAuth"); + removeAuthAction->setEnabled( mRosterItem.subscription().type() == XMPP::Subscription::Both || mRosterItem.subscription().type() == XMPP::Subscription::From ); + actionAuthorization->insert(removeAuthAction); + + KActionMenu *actionSetAvailability = new KActionMenu (i18n ("Set Availability"), "kopeteavailable", this, "jabber_online"); + + actionSetAvailability->insert(new KAction (i18n ("Online"), protocol()->JabberKOSOnline.iconFor(this), + 0, this, SLOT (slotStatusOnline ()), actionSetAvailability, "actionOnline")); + actionSetAvailability->insert(new KAction (i18n ("Free to Chat"), protocol()->JabberKOSChatty.iconFor(this), + 0, this, SLOT (slotStatusChatty ()), actionSetAvailability, "actionChatty")); + actionSetAvailability->insert(new KAction (i18n ("Away"), protocol()->JabberKOSAway.iconFor(this), + 0, this, SLOT (slotStatusAway ()), actionSetAvailability, "actionAway")); + actionSetAvailability->insert(new KAction (i18n ("Extended Away"), protocol()->JabberKOSXA.iconFor(this), + 0, this, SLOT (slotStatusXA ()), actionSetAvailability, "actionXA")); + actionSetAvailability->insert(new KAction (i18n ("Do Not Disturb"), protocol()->JabberKOSDND.iconFor(this), + 0, this, SLOT (slotStatusDND ()), actionSetAvailability, "actionDND")); + actionSetAvailability->insert(new KAction (i18n ("Invisible"), protocol()->JabberKOSInvisible.iconFor(this), + 0, this, SLOT (slotStatusInvisible ()), actionSetAvailability, "actionInvisible")); + + KActionMenu *actionSelectResource = new KActionMenu (i18n ("Select Resource"), "connect_no", this, "actionSelectResource"); + + // if the contact is online, display the resources we have for it, + // otherwise disable the menu + if (onlineStatus ().status () == Kopete::OnlineStatus::Offline) + { + actionSelectResource->setEnabled ( false ); + } + else + { + QStringList items; + XMPP::ResourceList availableResources; + + int activeItem = 0, i = 1; + const XMPP::Resource lockedResource = account()->resourcePool()->lockedResource ( mRosterItem.jid () ); + + // put default resource first + items.append (i18n ("Automatic (best/default resource)")); + + account()->resourcePool()->findResources ( mRosterItem.jid (), availableResources ); + + XMPP::ResourceList::const_iterator resourcesEnd = availableResources.end (); + for ( XMPP::ResourceList::const_iterator it = availableResources.begin(); it != resourcesEnd; ++it, i++) + { + items.append ( (*it).name() ); + + if ( (*it).name() == lockedResource.name() ) + activeItem = i; + } + + // now go through the string list and add the resources with their icons + i = 0; + QStringList::const_iterator itemsEnd = items.end (); + for(QStringList::const_iterator it = items.begin(); it != itemsEnd; ++it) + { + if( i == activeItem ) + { + actionSelectResource->insert ( new KAction( ( *it ), "button_ok", 0, this, SLOT( slotSelectResource() ), + actionSelectResource, QString::number( i ).latin1() ) ); + } + else + { + /* + * Select icon, using bestResource() without lock for the automatic entry + * and the resources' respective status icons for the rest. + */ + QIconSet iconSet ( !i ? + protocol()->resourceToKOS ( account()->resourcePool()->bestResource ( mRosterItem.jid(), false ) ).iconFor ( account () ) : protocol()->resourceToKOS ( *availableResources.find(*it) ).iconFor ( account () )); + + actionSelectResource->insert ( new KAction( ( *it ), iconSet, 0, this, SLOT( slotSelectResource() ), + actionSelectResource, QString::number( i ).latin1() ) ); + } + + i++; + } + + } + + actionCollection->append( actionAuthorization ); + actionCollection->append( actionSetAvailability ); + actionCollection->append( actionSelectResource ); + + +#ifdef SUPPORT_JINGLE + KAction *actionVoiceCall = new KAction (i18n ("Voice call"), "voicecall", 0, this, SLOT (voiceCall ()), this, "jabber_voicecall"); + actionVoiceCall->setEnabled( false ); + + actionCollection->append( actionVoiceCall ); + + // Check if the current contact support Voice calls, also honour lock by default. + JabberResource *bestResource = account()->resourcePool()->bestJabberResource( mRosterItem.jid() ); + if( bestResource && bestResource->features().canVoice() ) + { + actionVoiceCall->setEnabled( true ); + } +#endif + + return actionCollection; +} + +void JabberContact::handleIncomingMessage (const XMPP::Message & message) +{ + QString viewPlugin; + Kopete::Message *newMessage = 0L; + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Received Message Type:" << message.type () << endl; + + // fetch message manager + JabberChatSession *mManager = manager ( message.from().resource (), Kopete::Contact::CanCreate ); + + // evaluate notifications + if ( message.type () != "error" ) + { + if (!message.invite().isEmpty()) + { + QString room=message.invite(); + QString originalBody=message.body().isEmpty() ? QString() : + i18n( "The original message is : \" %1 \"
    " ).arg(QStyleSheet::escape(message.body())); + QString mes=i18n("%1 invited you to join the conference %2
    %3
    " + "If you want to accept and join, just enter your nickname and press ok
    " + "If you want to decline, press cancel
    ") + .arg(message.from().full(), room , originalBody); + + bool ok=false; + QString futureNewNickName = KInputDialog::getText( i18n( "Invited to a conference - Jabber Plugin" ), + mes, QString() , &ok , (mManager ? dynamic_cast(mManager->view(false)) : 0) ); + if ( !ok || !account()->isConnected() || futureNewNickName.isEmpty() ) + return; + + XMPP::Jid roomjid(room); + account()->client()->joinGroupChat( roomjid.host() , roomjid.user() , futureNewNickName ); + return; + } + else if (message.body().isEmpty()) + // Then here could be event notifications + { + if (message.containsEvent ( XMPP::CancelEvent ) ) + mManager->receivedTypingMsg ( this, false ); + else if (message.containsEvent ( XMPP::ComposingEvent ) ) + mManager->receivedTypingMsg ( this, true ); + else if (message.containsEvent ( XMPP::DisplayedEvent ) ) + mManager->receivedEventNotification ( i18n("Message has been displayed") ); + else if (message.containsEvent ( XMPP::DeliveredEvent ) ) + mManager->receivedEventNotification ( i18n("Message has been delivered") ); + else if (message.containsEvent ( XMPP::OfflineEvent ) ) + { + mManager->receivedEventNotification( i18n("Message stored on the server, contact offline") ); + } + else if (message.containsEvent ( XMPP::GoneEvent ) ) + { + if(mManager->view( Kopete::Contact::CannotCreate )) + { //show an internal message if the user has not already closed his window + Kopete::Message m=Kopete::Message ( this, mManager->members(), + i18n("%1 has ended their participation in the chat session.").arg(metaContact()->displayName()), + Kopete::Message::Internal ); + m.setImportance(Kopete::Message::Low); + mManager->view()->appendMessage ( m ); //use KopeteView::AppendMessage to bypass notifications + } + } + } + else + // Then here could be event notification requests + { + mRequestComposingEvent = message.containsEvent ( XMPP::ComposingEvent ); + mRequestOfflineEvent = message.containsEvent ( XMPP::OfflineEvent ); + mRequestDeliveredEvent = message.containsEvent ( XMPP::DeliveredEvent ); + mRequestDisplayedEvent = message.containsEvent ( XMPP::DisplayedEvent); + mRequestGoneEvent= message.containsEvent ( XMPP::GoneEvent); + } + } + + /** + * Don't display empty messages, these were most likely just carrying + * event notifications or other payload. + */ + if ( message.body().isEmpty () && message.urlList().isEmpty () && message.xHTMLBody().isEmpty() && !message.xencrypted() ) + return; + + // determine message type + if (message.type () == "chat") + viewPlugin = "kopete_chatwindow"; + else + viewPlugin = "kopete_emailwindow"; + + Kopete::ContactPtrList contactList; + contactList.append ( account()->myself () ); + + // check for errors + if ( message.type () == "error" ) + { + newMessage = new Kopete::Message( message.timeStamp (), this, contactList, + i18n("Your message could not be delivered: \"%1\", Reason: \"%2\""). + arg ( message.body () ).arg ( message.error().text ), + message.subject(), Kopete::Message::Inbound, Kopete::Message::PlainText, viewPlugin ); + } + else + { + // store message id for outgoing notifications + mLastReceivedMessageId = message.id (); + + // retrieve and reformat body + QString body = message.body (); + QString xHTMLBody; + if( !message.xencrypted().isEmpty () ) + { + body = QString ("-----BEGIN PGP MESSAGE-----\n\n") + message.xencrypted () + QString ("\n-----END PGP MESSAGE-----\n"); + } + else + { + xHTMLBody = message.xHTMLBody (); + } + + // convert XMPP::Message into Kopete::Message + if (!xHTMLBody.isEmpty()) { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Received a xHTML message" << endl; + newMessage = new Kopete::Message ( message.timeStamp (), this, contactList, xHTMLBody, + message.subject (), Kopete::Message::Inbound, + Kopete::Message::RichText, viewPlugin ); + } + else if ( !body.isEmpty () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Received a plain text message" << endl; + newMessage = new Kopete::Message ( message.timeStamp (), this, contactList, body, + message.subject (), Kopete::Message::Inbound, + Kopete::Message::PlainText, viewPlugin ); + } + } + + // append message to (eventually new) manager and preselect the originating resource + if ( newMessage ) + { + mManager->appendMessage ( *newMessage, message.from().resource () ); + + delete newMessage; + } + + // append URLs as separate messages + if ( !message.urlList().isEmpty () ) + { + /* + * We need to copy it here because Iris returns a copy + * and we can't work with a returned copy in a for() loop. + */ + XMPP::UrlList urlList = message.urlList(); + + for ( XMPP::UrlList::const_iterator it = urlList.begin (); it != urlList.end (); ++it ) + { + QString description = (*it).desc().isEmpty() ? (*it).url() : QStyleSheet::escape ( (*it).desc() ); + QString url = (*it).url (); + + newMessage = new Kopete::Message ( message.timeStamp (), this, contactList, + QString ( "%2" ).arg ( url, description ), + message.subject (), Kopete::Message::Inbound, + Kopete::Message::RichText, viewPlugin ); + + mManager->appendMessage ( *newMessage, message.from().resource () ); + + delete newMessage; + } + } + +} + +void JabberContact::slotCheckVCard () +{ + QDateTime cacheDate; + Kopete::ContactProperty cacheDateString = property ( protocol()->propVCardCacheTimeStamp ); + + // don't do anything while we are offline + if ( !account()->myself()->onlineStatus().isDefinitelyOnline () ) + { + return; + } + + if(!mDiscoDone) + { + if(transport()) //no need to disco if this is a legacy contact + mDiscoDone = true; + else if(!rosterItem().jid().node().isEmpty()) + mDiscoDone = true; //contact with an @ are not transport for sure + else + { + mDiscoDone = true; //or it will happen twice, we don't want that. + //disco to see if it's not a transport + XMPP::JT_DiscoInfo *jt = new XMPP::JT_DiscoInfo(account()->client()->rootTask()); + QObject::connect(jt, SIGNAL(finished()),this, SLOT(slotDiscoFinished())); + jt->get(rosterItem().jid(), QString()); + jt->go(true); + } + } + + + // avoid warning if key does not exist in configuration file + if ( cacheDateString.isNull () ) + cacheDate = QDateTime::currentDateTime().addDays ( -2 ); + else + cacheDate = QDateTime::fromString ( cacheDateString.value().toString (), Qt::ISODate ); + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Cached vCard data for " << contactId () << " from " << cacheDate.toString () << endl; + + if ( !mVCardUpdateInProgress && ( cacheDate.addDays ( 1 ) < QDateTime::currentDateTime () ) ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Scheduling update." << endl; + + mVCardUpdateInProgress = true; + + // current data is older than 24 hours, request a new one + QTimer::singleShot ( account()->client()->getPenaltyTime () * 1000, this, SLOT ( slotGetTimedVCard () ) ); + } + +} + +void JabberContact::slotGetTimedVCard () +{ + mVCardUpdateInProgress = false; + + // check if we are still connected - eventually we lost our connection in the meantime + if ( !account()->myself()->onlineStatus().isDefinitelyOnline () ) + { + // we are not connected, discard this update + return; + } + + if(!mDiscoDone) + { + if(transport()) //no need to disco if this is a legacy contact + mDiscoDone = true; + else if(!rosterItem().jid().node().isEmpty()) + mDiscoDone = true; //contact with an @ are not transport for sure + else + { + //disco to see if it's not a transport + XMPP::JT_DiscoInfo *jt = new XMPP::JT_DiscoInfo(account()->client()->rootTask()); + QObject::connect(jt, SIGNAL(finished()),this, SLOT(slotDiscoFinished())); + jt->get(rosterItem().jid(), QString()); + jt->go(true); + } + } + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Requesting vCard for " << contactId () << " from update timer." << endl; + + mVCardUpdateInProgress = true; + + // request vCard + XMPP::JT_VCard *task = new XMPP::JT_VCard ( account()->client()->rootTask () ); + // signal to ourselves when the vCard data arrived + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT ( slotGotVCard () ) ); + task->get ( mRosterItem.jid () ); + task->go ( true ); + +} + +void JabberContact::slotGotVCard () +{ + + XMPP::JT_VCard * vCard = (XMPP::JT_VCard *) sender (); + + // update timestamp of last vCard retrieval + if ( metaContact() && !metaContact()->isTemporary () ) + { + setProperty ( protocol()->propVCardCacheTimeStamp, QDateTime::currentDateTime().toString ( Qt::ISODate ) ); + } + + mVCardUpdateInProgress = false; + + if ( !vCard->success() ) + { + /* + * A vCard for the user does not exist or the + * request was unsuccessful or incomplete. + * The timestamp was already updated when + * requesting the vCard, so it's safe to + * just do nothing here. + */ + return; + } + + setPropertiesFromVCard ( vCard->vcard () ); + +} + +void JabberContact::slotCheckLastActivity ( Kopete::Contact *, const Kopete::OnlineStatus &newStatus, const Kopete::OnlineStatus &oldStatus ) +{ + + /* + * Checking the last activity only makes sense if a contact is offline. + * So, this check should only be done in the following cases: + * - Kopete goes online for the first time and this contact is offline, or + * - Kopete is already online and this contact went offline. + * + * Since Kopete already takes care of maintaining the lastSeen property + * if the contact changes its state while we are online, we don't need + * to query its activity after we are already connected. + */ + + if ( onlineStatus().isDefinitelyOnline () ) + { + // Kopete already deals with lastSeen if the contact is online + return; + } + + if ( ( oldStatus.status () == Kopete::OnlineStatus::Connecting ) && newStatus.isDefinitelyOnline () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Scheduling request for last activity for " << mRosterItem.jid().bare () << endl; + + QTimer::singleShot ( account()->client()->getPenaltyTime () * 1000, this, SLOT ( slotGetTimedLastActivity () ) ); + } + +} + +void JabberContact::slotGetTimedLastActivity () +{ + /* + * We have been called from @ref slotCheckLastActivity. + * We could have lost our connection in the meantime, + * so make sure we are online. Additionally, the contact + * itself could have gone online, so make sure it is + * still offline. (otherwise the last seen property is + * maintained by Kopete) + */ + + if ( onlineStatus().isDefinitelyOnline () ) + { + // Kopete already deals with setting lastSeen if the contact is online + return; + } + + if ( account()->myself()->onlineStatus().isDefinitelyOnline () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Requesting last activity from timer for " << mRosterItem.jid().bare () << endl; + + XMPP::JT_GetLastActivity *task = new XMPP::JT_GetLastActivity ( account()->client()->rootTask () ); + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT ( slotGotLastActivity () ) ); + task->get ( mRosterItem.jid () ); + task->go ( true ); + } + +} + +void JabberContact::slotGotLastActivity () +{ + XMPP::JT_GetLastActivity *task = (XMPP::JT_GetLastActivity *) sender (); + + if ( task->success () ) + { + setProperty ( protocol()->propLastSeen, QDateTime::currentDateTime().addSecs ( -task->seconds () ) ); + if( !task->message().isEmpty() ) + { + setProperty( protocol()->propAwayMessage, task->message() ); + } + } + +} + +void JabberContact::slotSendVCard() +{ + XMPP::VCard vCard; + XMPP::VCard::AddressList addressList; + XMPP::VCard::EmailList emailList; + XMPP::VCard::PhoneList phoneList; + + if ( !account()->isConnected () ) + { + account()->errorConnectFirst (); + return; + } + + // General information + vCard.setNickName (property(protocol()->propNickName).value().toString()); + vCard.setFullName (property(protocol()->propFullName).value().toString()); + vCard.setJid (property(protocol()->propJid).value().toString()); + vCard.setBdayStr (property(protocol()->propBirthday).value().toString()); + vCard.setTimezone (property(protocol()->propTimezone).value().toString()); + vCard.setUrl (property(protocol()->propHomepage).value().toString()); + + // home address tab + XMPP::VCard::Address homeAddress; + + homeAddress.home = true; + homeAddress.street = property(protocol()->propHomeStreet).value().toString(); + homeAddress.extaddr = property(protocol()->propHomeExtAddr).value().toString(); + homeAddress.pobox = property(protocol()->propHomePOBox).value().toString(); + homeAddress.locality = property(protocol()->propHomeCity).value().toString(); + homeAddress.pcode = property(protocol()->propHomePostalCode).value().toString(); + homeAddress.country = property(protocol()->propHomeCountry).value().toString(); + + // work address tab + XMPP::VCard::Address workAddress; + + workAddress.work = true; + workAddress.street = property(protocol()->propWorkStreet).value().toString(); + workAddress.extaddr = property(protocol()->propWorkExtAddr).value().toString(); + workAddress.pobox = property(protocol()->propWorkPOBox).value().toString(); + workAddress.locality = property(protocol()->propWorkCity).value().toString(); + workAddress.pcode = property(protocol()->propWorkPostalCode).value().toString(); + workAddress.country = property(protocol()->propWorkCountry).value().toString(); + + addressList.append(homeAddress); + addressList.append(workAddress); + + vCard.setAddressList(addressList); + + // home email + XMPP::VCard::Email homeEmail; + + homeEmail.home = true; + homeEmail.userid = property(protocol()->propEmailAddress).value().toString(); + + // work email + XMPP::VCard::Email workEmail; + + workEmail.work = true; + workEmail.userid = property(protocol()->propWorkEmailAddress).value().toString(); + + emailList.append(homeEmail); + emailList.append(workEmail); + + vCard.setEmailList(emailList); + + // work information tab + XMPP::VCard::Org org; + org.name = property(protocol()->propCompanyName).value().toString(); + org.unit = QStringList::split(",", property(protocol()->propCompanyDepartement).value().toString()); + vCard.setOrg(org); + vCard.setTitle (property(protocol()->propCompanyPosition).value().toString()); + vCard.setRole (property(protocol()->propCompanyRole).value().toString()); + + // phone numbers tab + XMPP::VCard::Phone phoneHome; + phoneHome.home = true; + phoneHome.number = property(protocol()->propPrivatePhone).value().toString(); + + XMPP::VCard::Phone phoneWork; + phoneWork.work = true; + phoneWork.number = property(protocol()->propWorkPhone).value().toString(); + + XMPP::VCard::Phone phoneFax; + phoneFax.fax = true; + phoneFax.number = property(protocol()->propPhoneFax).value().toString(); + + XMPP::VCard::Phone phoneCell; + phoneCell.cell = true; + phoneCell.number = property(protocol()->propPrivateMobilePhone).value().toString(); + + phoneList.append(phoneHome); + phoneList.append(phoneWork); + phoneList.append(phoneFax); + phoneList.append(phoneCell); + + vCard.setPhoneList(phoneList); + + // about tab + vCard.setDesc(property(protocol()->propAbout).value().toString()); + + // Set contact photo as a binary value (if he has set a photo) + if( hasProperty( protocol()->propPhoto.key() ) ) + { + QString photoPath = property( protocol()->propPhoto ).value().toString(); + QImage image( photoPath ); + QByteArray ba; + QBuffer buffer( ba ); + buffer.open( IO_WriteOnly ); + image.save( &buffer, "PNG" ); + vCard.setPhoto( ba ); + } + + vCard.setVersion("3.0"); + vCard.setProdId("Kopete"); + + XMPP::JT_VCard *task = new XMPP::JT_VCard (account()->client()->rootTask ()); + // signal to ourselves when the vCard data arrived + QObject::connect (task, SIGNAL (finished ()), this, SLOT (slotSentVCard ())); + task->set (vCard); + task->go (true); +} + +void JabberContact::setPhoto( const QString &photoPath ) +{ + QImage contactPhoto(photoPath); + QString newPhotoPath = photoPath; + if(contactPhoto.width() > 96 || contactPhoto.height() > 96) + { + // Save image to a new location if the image isn't the correct format. + QString newLocation( locateLocal( "appdata", "jabberphotos/"+ KURL(photoPath).fileName().lower() ) ); + + // Scale and crop the picture. + contactPhoto = contactPhoto.smoothScale( 96, 96, QImage::ScaleMin ); + // crop image if not square + if(contactPhoto.width() < contactPhoto.height()) + contactPhoto = contactPhoto.copy((contactPhoto.width()-contactPhoto.height())/2, 0, 96, 96); + else if (contactPhoto.width() > contactPhoto.height()) + contactPhoto = contactPhoto.copy(0, (contactPhoto.height()-contactPhoto.width())/2, 96, 96); + + // Use the cropped/scaled image now. + if(!contactPhoto.save(newLocation, "PNG")) + newPhotoPath = QString::null; + else + newPhotoPath = newLocation; + } + else if (contactPhoto.width() < 32 || contactPhoto.height() < 32) + { + // Save image to a new location if the image isn't the correct format. + QString newLocation( locateLocal( "appdata", "jabberphotos/"+ KURL(photoPath).fileName().lower() ) ); + + // Scale and crop the picture. + contactPhoto = contactPhoto.smoothScale( 32, 32, QImage::ScaleMin ); + // crop image if not square + if(contactPhoto.width() < contactPhoto.height()) + contactPhoto = contactPhoto.copy((contactPhoto.width()-contactPhoto.height())/2, 0, 32, 32); + else if (contactPhoto.width() > contactPhoto.height()) + contactPhoto = contactPhoto.copy(0, (contactPhoto.height()-contactPhoto.width())/2, 32, 32); + + // Use the cropped/scaled image now. + if(!contactPhoto.save(newLocation, "PNG")) + newPhotoPath = QString::null; + else + newPhotoPath = newLocation; + } + else if (contactPhoto.width() != contactPhoto.height()) + { + // Save image to a new location if the image isn't the correct format. + QString newLocation( locateLocal( "appdata", "jabberphotos/"+ KURL(photoPath).fileName().lower() ) ); + + if(contactPhoto.width() < contactPhoto.height()) + contactPhoto = contactPhoto.copy((contactPhoto.width()-contactPhoto.height())/2, 0, contactPhoto.height(), contactPhoto.height()); + else if (contactPhoto.width() > contactPhoto.height()) + contactPhoto = contactPhoto.copy(0, (contactPhoto.height()-contactPhoto.width())/2, contactPhoto.height(), contactPhoto.height()); + + // Use the cropped/scaled image now. + if(!contactPhoto.save(newLocation, "PNG")) + newPhotoPath = QString::null; + else + newPhotoPath = newLocation; + } + + setProperty( protocol()->propPhoto, newPhotoPath ); +} + +void JabberContact::slotSentVCard () +{ + +} + +void JabberContact::slotChatSessionDeleted ( QObject *sender ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Message manager deleted, collecting the pieces..." << endl; + + JabberChatSession *manager = static_cast(sender); + + mManagers.remove ( mManagers.find ( manager ) ); + +} + +JabberChatSession *JabberContact::manager ( Kopete::ContactPtrList chatMembers, Kopete::Contact::CanCreateFlags canCreate ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "called, canCreate: " << canCreate << endl; + + Kopete::ChatSession *_manager = Kopete::ChatSessionManager::self()->findChatSession ( account()->myself(), chatMembers, protocol() ); + JabberChatSession *manager = dynamic_cast( _manager ); + + /* + * If we didn't find a message manager for this contact, + * instantiate a new one if we are allowed to. (otherwise return 0) + */ + if ( !manager && canCreate ) + { + XMPP::Jid jid = rosterItem().jid(); + + /* + * If we have no hardwired JID, set any eventually + * locked resource as preselected resource. + * If there is no locked resource, the resource field + * will stay empty. + */ + if ( jid.resource().isEmpty () ) + jid.setResource ( account()->resourcePool()->lockedResource ( jid ).name () ); + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "No manager found, creating a new one with resource '" << jid.resource () << "'" << endl; + + manager = new JabberChatSession ( protocol(), static_cast(account()->myself()), chatMembers, jid.resource () ); + connect ( manager, SIGNAL ( destroyed ( QObject * ) ), this, SLOT ( slotChatSessionDeleted ( QObject * ) ) ); + mManagers.append ( manager ); + } + + return manager; + +} + +Kopete::ChatSession *JabberContact::manager ( Kopete::Contact::CanCreateFlags canCreate ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "called, canCreate: " << canCreate << endl; + + Kopete::ContactPtrList chatMembers; + chatMembers.append ( this ); + + return manager ( chatMembers, canCreate ); + +} + +JabberChatSession *JabberContact::manager ( const QString &resource, Kopete::Contact::CanCreateFlags canCreate ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "called, canCreate: " << canCreate << ", Resource: '" << resource << "'" << endl; + + /* + * First of all, see if we already have a manager matching + * the requested resource or if there are any managers with + * an empty resource. + */ + if ( !resource.isEmpty () ) + { + for ( JabberChatSession *mManager = mManagers.first (); mManager; mManager = mManagers.next () ) + { + if ( mManager->resource().isEmpty () || ( mManager->resource () == resource ) ) + { + // we found a matching manager, return this one + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Found an existing message manager for this resource." << endl; + return mManager; + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "No manager found for this resource, creating a new one." << endl; + + /* + * If we have come this far, we were either supposed to create + * a manager with a preselected resource but have found + * no available manager. (not even one with an empty resource) + * This means, we will have to create a new one with a + * preselected resource. + */ + Kopete::ContactPtrList chatmembers; + chatmembers.append ( this ); + JabberChatSession *manager = new JabberChatSession ( protocol(), + static_cast(account()->myself()), + chatmembers, resource ); + connect ( manager, SIGNAL ( destroyed ( QObject * ) ), this, SLOT ( slotChatSessionDeleted ( QObject * ) ) ); + mManagers.append ( manager ); + + return manager; + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Resource is empty, grabbing first available manager." << endl; + + /* + * The resource is empty, so just return first available manager. + */ + return dynamic_cast( manager ( canCreate ) ); + +} + +void JabberContact::deleteContact () +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing user " << contactId () << endl; + + if ( !account()->isConnected () ) + { + account()->errorConnectFirst (); + return; + } + + /* + * Follow the recommendation of + * JEP-0162: Best Practices for Roster and Subscription Management + * http://www.jabber.org/jeps/jep-0162.html#removal + */ + + bool remove_from_roster=false; + + if( mRosterItem.subscription().type() == XMPP::Subscription::Both || mRosterItem.subscription().type() == XMPP::Subscription::From ) + { + int result = KMessageBox::questionYesNoCancel (Kopete::UI::Global::mainWidget(), + i18n ( "Do you also want to remove the authorization from user %1 to see your status?" ). + arg ( mRosterItem.jid().bare () ), i18n ("Notification"), + KStdGuiItem::del (), i18n("Keep"), "JabberRemoveAuthorizationOnDelete" ); + if(result == KMessageBox::Yes ) + remove_from_roster = true; + else if( result == KMessageBox::Cancel) + return; + } + else if( mRosterItem.subscription().type() == XMPP::Subscription::None || mRosterItem.subscription().type() == XMPP::Subscription::To ) + remove_from_roster = true; + + if( remove_from_roster ) + { + XMPP::JT_Roster * rosterTask = new XMPP::JT_Roster ( account()->client()->rootTask () ); + rosterTask->remove ( mRosterItem.jid () ); + rosterTask->go ( true ); + } + else + { + sendSubscription("unsubscribe"); + + XMPP::JT_Roster * rosterTask = new XMPP::JT_Roster ( account()->client()->rootTask () ); + rosterTask->set ( mRosterItem.jid (), QString() , QStringList() ); + rosterTask->go (true); + } + +} + +void JabberContact::sync ( unsigned int ) +{ + // if we are offline or this is a temporary contact or we should not synch, don't bother + if ( dontSync () || !account()->isConnected () || metaContact()->isTemporary () || metaContact() == Kopete::ContactList::self()->myself() ) + return; + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << contactId () /*<< " - " <start(2*1000,true); + /* + the sync operation is delayed, because when we are doing a move to group operation, + kopete first add the contact to the group, then removes it. + Theses two operations should anyway be done in only one pass. + + if there is two jabber contact in one metacontact, this may result in an infinite change of + groups between theses two contacts, and the server is being flooded. + */ +} + +void JabberContact::slotDelayedSync( ) +{ + m_syncTimer->deleteLater(); + m_syncTimer=0L; + // if we are offline or this is a temporary contact or we should not synch, don't bother + if ( dontSync () || !account()->isConnected () || metaContact()->isTemporary () ) + return; + + bool changed=metaContact()->displayName() != mRosterItem.name(); + + + QStringList groups; + Kopete::GroupList groupList = metaContact ()->groups (); + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Synchronizing contact " << contactId () << endl; + + for ( Kopete::Group * g = groupList.first (); g; g = groupList.next () ) + { + if ( g->type () != Kopete::Group::TopLevel ) + groups += g->displayName (); + } + + if(mRosterItem.groups() != groups) + { + changed=true; + mRosterItem.setGroups ( groups ); + } + + if(!changed) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "contact has not changed, abort sync" << endl; + return; + } + + XMPP::JT_Roster * rosterTask = new XMPP::JT_Roster ( account()->client()->rootTask () ); + + rosterTask->set ( mRosterItem.jid (), metaContact()->displayName (), mRosterItem.groups () ); + rosterTask->go (true); + +} + +void JabberContact::sendFile ( const KURL &sourceURL, const QString &/*fileName*/, uint /*fileSize*/ ) +{ + QString filePath; + + // if the file location is null, then get it from a file open dialog + if ( !sourceURL.isValid () ) + filePath = KFileDialog::getOpenFileName( QString::null , "*", 0L, i18n ( "Kopete File Transfer" ) ); + else + filePath = sourceURL.path(-1); + + QFile file ( filePath ); + + if ( file.exists () ) + { + // send the file + new JabberFileTransfer ( account (), this, filePath ); + } + +} + + +void JabberContact::slotSendAuth () +{ + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "(Re)send auth " << contactId () << endl; + + sendSubscription ("subscribed"); + +} + +void JabberContact::slotRequestAuth () +{ + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "(Re)request auth " << contactId () << endl; + + sendSubscription ("subscribe"); + +} + +void JabberContact::slotRemoveAuth () +{ + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Remove auth " << contactId () << endl; + + sendSubscription ("unsubscribed"); + +} + +void JabberContact::sendSubscription ( const QString& subType ) +{ + + if ( !account()->isConnected () ) + { + account()->errorConnectFirst (); + return; + } + + XMPP::JT_Presence * task = new XMPP::JT_Presence ( account()->client()->rootTask () ); + + task->sub ( mRosterItem.jid().full (), subType ); + task->go ( true ); + +} + +void JabberContact::slotSelectResource () +{ + int currentItem = QString ( static_cast( sender() )->name () ).toUInt (); + + /* + * Warn the user if there is already an active chat window. + * The resource selection will only apply for newly opened + * windows. + */ + if ( manager ( Kopete::Contact::CannotCreate ) != 0 ) + { + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), + KMessageBox::Information, + i18n ("You have preselected a resource for contact %1, " + "but you still have open chat windows for this contact. " + "The preselected resource will only apply to newly opened " + "chat windows.").arg ( contactId () ), + i18n ("Jabber Resource Selector") ); + } + + if (currentItem == 0) + { + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing active resource, trusting bestResource()." << endl; + + account()->resourcePool()->removeLock ( rosterItem().jid() ); + } + else + { + QString selectedResource = static_cast(sender())->text(); + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Moving to resource " << selectedResource << endl; + + account()->resourcePool()->lockToResource ( rosterItem().jid() , XMPP::Resource ( selectedResource ) ); + } + +} + +void JabberContact::sendPresence ( const XMPP::Status status ) +{ + + if ( !account()->isConnected () ) + { + account()->errorConnectFirst (); + return; + } + + XMPP::Status newStatus = status; + + // honour our priority + if(newStatus.isAvailable()) + newStatus.setPriority ( account()->configGroup()->readNumEntry ( "Priority", 5 ) ); + + XMPP::JT_Presence * task = new XMPP::JT_Presence ( account()->client()->rootTask () ); + + task->pres ( bestAddress (), newStatus); + task->go ( true ); + +} + + +void JabberContact::slotStatusOnline () +{ + + XMPP::Status status; + status.setShow(""); + + sendPresence ( status ); + +} + +void JabberContact::slotStatusChatty () +{ + + XMPP::Status status; + status.setShow ("chat"); + + sendPresence ( status ); + +} + +void JabberContact::slotStatusAway () +{ + + XMPP::Status status; + status.setShow ("away"); + + sendPresence ( status ); + +} + +void JabberContact::slotStatusXA () +{ + + XMPP::Status status; + status.setShow ("xa"); + + sendPresence ( status ); + +} + +void JabberContact::slotStatusDND () +{ + + XMPP::Status status; + status.setShow ("dnd"); + + sendPresence ( status ); + + +} + +void JabberContact::slotStatusInvisible () +{ + + XMPP::Status status; + status.setIsAvailable( false ); + + sendPresence ( status ); + +} + +bool JabberContact::isContactRequestingEvent( XMPP::MsgEvent event ) +{ + if ( event == OfflineEvent ) + return mRequestOfflineEvent; + else if ( event == DeliveredEvent ) + return mRequestDeliveredEvent; + else if ( event == DisplayedEvent ) + return mRequestDisplayedEvent; + else if ( event == ComposingEvent ) + return mRequestComposingEvent; + else if ( event == CancelEvent ) + return mRequestComposingEvent; + else if ( event == GoneEvent ) + return mRequestGoneEvent; + else + return false; +} + +QString JabberContact::lastReceivedMessageId () const +{ + return mLastReceivedMessageId; +} + +void JabberContact::voiceCall( ) +{ +#ifdef SUPPORT_JINGLE + Jid jid = mRosterItem.jid(); + + // It's honour lock by default. + JabberResource *bestResource = account()->resourcePool()->bestJabberResource( jid ); + if( bestResource ) + { + if( jid.resource().isEmpty() ) + { + // If the jid resource is empty, get the JID from best resource for this contact. + jid = bestResource->jid(); + } + + // Check if the voice caller exist and the current resource support voice. + if( account()->voiceCaller() && bestResource->features().canVoice() ) + { + JingleVoiceSessionDialog *voiceDialog = new JingleVoiceSessionDialog( jid, account()->voiceCaller() ); + voiceDialog->show(); + voiceDialog->start(); + } +#if 0 + if( account()->sessionManager() && bestResource->features().canVoice() ) + { + JingleVoiceSession *session = static_cast(account()->sessionManager()->createSession("http://www.google.com/session/phone", jid)); + + JingleVoiceSessionDialog *voiceDialog = new JingleVoiceSessionDialog(session); + voiceDialog->show(); + voiceDialog->start(); + } +#endif + } + else + { + // Shouldn't never go there. + } +#endif +} + +void JabberContact::slotDiscoFinished( ) +{ + mDiscoDone = true; + JT_DiscoInfo *jt = (JT_DiscoInfo *)sender(); + + bool is_transport=false; + QString tr_type; + + if ( jt->success() ) + { + QValueList identities = jt->item().identities(); + QValueList::Iterator it; + for ( it = identities.begin(); it != identities.end(); ++it ) + { + XMPP::DiscoItem::Identity ident=*it; + if(ident.category == "gateway") + { + is_transport=true; + tr_type=ident.type; + //name=ident.name; + + break; //(we currently only support gateway) + } + else if (ident.category == "service") + { + //The ApaSMSAgent is reporting itself as service (instead of gateway) which is broken. + //we anyway support it. See bug 127811 + if(ident.type == "sms") + { + is_transport=true; + tr_type=ident.type; + } + } + } + } + + if(is_transport && !transport()) + { //ok, we are not a contact, we are a transport.... + + XMPP::RosterItem ri = rosterItem(); + Kopete::MetaContact *mc=metaContact(); + JabberAccount *parentAccount=account(); + Kopete::OnlineStatus status=onlineStatus(); + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << ri.jid().full() << " is not a contact but a gateway - " << this << endl; + + if( Kopete::AccountManager::self()->findAccount( protocol()->pluginId() , account()->accountId() + "/" + ri.jid().bare() ) ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "oops, transport already exists, abort operation " << endl; + return; + } + + delete this; //we are not a contact i said ! + + if(mc->contacts().count() == 0) + Kopete::ContactList::self()->removeMetaContact( mc ); + + //we need to create the transport when 'this' is already deleted, so transport->myself() will not conflict with it + JabberTransport *transport = new JabberTransport( parentAccount , ri , tr_type ); + if(!Kopete::AccountManager::self()->registerAccount( transport )) + return; + transport->myself()->setOnlineStatus( status ); //push back the online status + return; + } +} + + + +#include "jabbercontact.moc" diff --git a/kopete/protocols/jabber/jabbercontact.h b/kopete/protocols/jabber/jabbercontact.h new file mode 100644 index 00000000..a7a3b024 --- /dev/null +++ b/kopete/protocols/jabber/jabbercontact.h @@ -0,0 +1,266 @@ + /* + * jabbercontact.cpp - Regular Kopete Jabber protocol contact + * + * Copyright (c) 2002-2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERCONTACT_H +#define JABBERCONTACT_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "jabberbasecontact.h" +#include "xmpp_vcard.h" + +#include "kopetechatsession.h" // needed for silly Kopete::ContactPtrList + +class JabberChatSession; +class QTimer; + +class JabberContact : public JabberBaseContact +{ + +Q_OBJECT + +public: + + JabberContact (const XMPP::RosterItem &rosterItem, + Kopete::Account *account, Kopete::MetaContact * mc, const QString &legacyId = QString()); + + ~JabberContact(); + + /** + * Create custom context menu items for the contact + * FIXME: implement manager version here? + */ + QPtrList *customContextMenuActions (); + + /** + * Start a rename request. + */ + void rename ( const QString &newName ); + + /** + * Deal with an incoming message for this contact. + */ + void handleIncomingMessage ( const XMPP::Message &message ); + + /** + * Create a message manager for this contact. + * This variant is a pure single-contact version and + * not suitable for groupchat, as it only looks for + * managers with ourselves in the contact list. + */ + Kopete::ChatSession *manager ( Kopete::Contact::CanCreateFlags ); + + + bool isContactRequestingEvent( XMPP::MsgEvent event ); + + QString lastReceivedMessageId () const; + +public slots: + + /** + * Remove this contact from the roster + */ + void deleteContact (); + + /** + * Sync Groups with server + * + * operations are alctually performed in sloDelayedSync() + */ + void sync(unsigned int); + + /** + * This is the JabberContact level slot for sending files. + * + * @param sourceURL The actual KURL of the file you are sending + * @param fileName (Optional) An alternate name for the file - what the + * receiver will see + * @param fileSize (Optional) Size of the file being sent. Used when sending + * a nondeterminate file size (such as over a socket) + */ + virtual void sendFile( const KURL &sourceURL = KURL(), + const QString &fileName = QString::null, uint fileSize = 0L ); + + /** + * Update the vCard on the server. + * @todo is that still used ? + */ + void slotSendVCard(); + + /** + * Set contact photo. + * @param path Path to the photo. + */ + void setPhoto(const QString &photoPath); + + /** + * this will start a voice call to the contact + */ + void voiceCall(); + +private slots: + + /** + * Send type="subscribed" to contact + */ + void slotSendAuth (); + + /** + * Send type="subscribe" to contact + */ + void slotRequestAuth (); + + /** + * Send type="unsubscribed" to contact + */ + void slotRemoveAuth (); + + /** + * Change this contact's status + */ + void slotStatusOnline (); + void slotStatusChatty (); + void slotStatusAway (); + void slotStatusXA (); + void slotStatusDND (); + void slotStatusInvisible (); + + /** + * Select a new resource for the contact + */ + void slotSelectResource (); + + void slotChatSessionDeleted ( QObject *sender ); + + /** + * Check if cached vCard is recent. + * Triggered as soon as Kopete changes its online state. + */ + void slotCheckVCard (); + + /** + * Triggered from a timer, requests the vCard. + * Timer is initiated by @ref slotCheckVCard. + */ + void slotGetTimedVCard (); + + /** + * Passes vCard on to parsing function. + */ + void slotGotVCard (); + + /** + * Get information about last activity of the contact. + * Triggered as soon as Kopete goes online or the contact goes offline. + */ + void slotCheckLastActivity ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ); + + /** + * Triggered from a timer, requests last activity information. + * Timer is initiated by @ref slotCheckLastActivity. + */ + void slotGetTimedLastActivity (); + + /** + * Updates activity information. + */ + void slotGotLastActivity (); + + /** + * Display a error message if the vCard sent was unsuccesful. + */ + void slotSentVCard(); + + /** + * The service discovery on that contact is finished + */ + void slotDiscoFinished(); + + /** + * actually perform operations of sync() with a delay. + * slot received by the syncTimer. + */ + void slotDelayedSync(); +private: + + /** + * Create a message manager for this contact. + * This variant is a pure single-contact version and + * not suitable for groupchat, as it only looks for + * managers with ourselves in the contact list. + * Additionally to the version above, this one adds + * a resource constraint that has to be matched by + * the manager. If a new manager is created, the given + * resource is preselected. + */ + JabberChatSession *manager ( const QString &resource, Kopete::Contact::CanCreateFlags ); + + /** + * Create a message manager for this contact. + * This version is suitable for group chat as it + * looks for a message manager with a given + * list of contacts as members. + */ + JabberChatSession *manager ( Kopete::ContactPtrList chatMembers, Kopete::Contact::CanCreateFlags ); + + /** + * Sends subscription messages. + */ + void sendSubscription (const QString& subType); + + /** + * Sends a presence packet to this contact + */ + void sendPresence ( const XMPP::Status status ); + + /** + * This variable keeps a list of message managers. + * It is required to locate message managers by + * resource name, if one account is interacting + * with several resources of the same contact + * at the same time. Note that this does *not* + * apply to group chats, so this variable + * only contains classes of type JabberChatSession. + * The casts in manager() and slotChatSessionDeleted() + * are thus legal. + */ + QPtrList mManagers; + + /** + * Indicates whether the vCard is currently + * being updated or not. + */ + bool mVCardUpdateInProgress :1; + + bool mRequestComposingEvent :1; + bool mRequestOfflineEvent :1; + bool mRequestDisplayedEvent :1; + bool mRequestDeliveredEvent :1; + bool mRequestGoneEvent :1; + /** + * tell if the disco#info has been done for this contact. + */ + bool mDiscoDone :1; + + QString mLastReceivedMessageId; + QTimer *m_syncTimer; + +}; + +#endif diff --git a/kopete/protocols/jabber/jabbercontactpool.cpp b/kopete/protocols/jabber/jabbercontactpool.cpp new file mode 100644 index 00000000..736c6045 --- /dev/null +++ b/kopete/protocols/jabber/jabbercontactpool.cpp @@ -0,0 +1,355 @@ + /* + * jabbercontactpool.cpp + * + * Copyright (c) 2004 by Till Gerken + * Copyright (c) 2006 by Olivier Goffart + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#include "jabbercontactpool.h" + +#include +#include +#include +#include +#include +#include "kopeteuiglobal.h" +#include "jabberprotocol.h" +#include "jabberbasecontact.h" +#include "jabbercontact.h" +#include "jabbergroupcontact.h" +#include "jabbergroupmembercontact.h" +#include "jabberresourcepool.h" +#include "jabberaccount.h" +#include "jabbertransport.h" + +JabberContactPool::JabberContactPool ( JabberAccount *account ) +{ + + // automatically delete all contacts in the pool upon removal + mPool.setAutoDelete (true); + + mAccount = account; + +} + +JabberContactPool::~JabberContactPool () +{ +} + +JabberContactPoolItem *JabberContactPool::findPoolItem ( const XMPP::RosterItem &contact ) +{ + + // see if the contact already exists + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact()->rosterItem().jid().full().lower() == contact.jid().full().lower() ) + { + return mContactItem; + } + } + + return 0; + +} + +JabberContact *JabberContactPool::addContact ( const XMPP::RosterItem &contact, Kopete::MetaContact *metaContact, bool dirty ) +{ + // see if the contact already exists + JabberContactPoolItem *mContactItem = findPoolItem ( contact ); + if ( mContactItem) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Updating existing contact " << contact.jid().full() << " - " << mContactItem->contact() << endl; + + // It exists, update it. + mContactItem->contact()->updateContact ( contact ); + mContactItem->setDirty ( dirty ); + + JabberContact *retval = dynamic_cast(mContactItem->contact ()); + + if ( !retval ) + { + KMessageBox::error ( Kopete::UI::Global::mainWidget (), + "Fatal error in the Jabber contact pool. Please restart Kopete and submit a debug log " + "of your session to http://bugs.kde.org.", + "Fatal Jabber Error" ); + } + + return retval; + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Adding new contact " << contact.jid().full() << endl; + + JabberTransport *transport=0l; + QString legacyId; + //find if the contact should be added to a transport. + if(mAccount->transports().contains(contact.jid().domain())) + { + transport=mAccount->transports()[contact.jid().domain()]; + legacyId=transport->legacyId( contact.jid() ); + } + + // create new contact instance and add it to the dictionary + JabberContact *newContact = new JabberContact ( contact, transport ? (Kopete::Account*)transport : (Kopete::Account*)mAccount, metaContact , legacyId ); + JabberContactPoolItem *newContactItem = new JabberContactPoolItem ( newContact ); + connect ( newContact, SIGNAL ( contactDestroyed ( Kopete::Contact * ) ), this, SLOT ( slotContactDestroyed ( Kopete::Contact * ) ) ); + newContactItem->setDirty ( dirty ); + mPool.append ( newContactItem ); + + return newContact; + +} + +JabberBaseContact *JabberContactPool::addGroupContact ( const XMPP::RosterItem &contact, bool roomContact, Kopete::MetaContact *metaContact, bool dirty ) +{ + + XMPP::RosterItem mContact ( roomContact ? contact.jid().userHost () : contact.jid().full() ); + + // see if the contact already exists + JabberContactPoolItem *mContactItem = findPoolItem ( mContact ); + if ( mContactItem) + { + if(mContactItem->contact()->inherits(roomContact ? + (const char*)("JabberGroupContact") : (const char*)("JabberGroupMemberContact") ) ) + { + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Updating existing contact " << mContact.jid().full() << endl; + + // It exists, update it. + mContactItem->contact()->updateContact ( mContact ); + mContactItem->setDirty ( dirty ); + + //we must tell to the originating function that no new contact has been added + return 0L;//mContactItem->contact (); + } + else + { + //this happen if we receive a MUC invitaiton: when the invitaiton is received, it's from the muc itself + //and then kopete will create a temporary contact for it. but it will not be a good contact. + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Bad contact will be removed and re-added " << mContact.jid().full() << endl; + Kopete::MetaContact *old_mc=mContactItem->contact()->metaContact(); + delete mContactItem->contact(); + mContactItem = 0L; + if(old_mc->contacts().isEmpty() && old_mc!=metaContact) + { + Kopete::ContactList::self()->removeMetaContact( old_mc ); + } + + } + + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Adding new contact " << mContact.jid().full() << endl; + + // create new contact instance and add it to the dictionary + JabberBaseContact *newContact; + + if ( roomContact ) + newContact = new JabberGroupContact ( contact, mAccount, metaContact ); + else + newContact = new JabberGroupMemberContact ( contact, mAccount, metaContact ); + + JabberContactPoolItem *newContactItem = new JabberContactPoolItem ( newContact ); + + connect ( newContact, SIGNAL ( contactDestroyed ( Kopete::Contact * ) ), this, SLOT ( slotContactDestroyed ( Kopete::Contact * ) ) ); + + newContactItem->setDirty ( dirty ); + mPool.append ( newContactItem ); + + return newContact; + +} + +void JabberContactPool::removeContact ( const XMPP::Jid &jid ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing contact " << jid.full() << endl; + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact()->rosterItem().jid().full().lower() == jid.full().lower() ) + { + /* + * The following deletion will cause slotContactDestroyed() + * to be called, which will clean the up the list. + */ + if(mContactItem->contact()) + { + Kopete::MetaContact *mc=mContactItem->contact()->metaContact(); + delete mContactItem->contact (); + if(mc && mc->contacts().isEmpty()) + { + Kopete::ContactList::self()->removeMetaContact(mc) ; + } + } + return; + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "WARNING: No match found!" << endl; + +} + +void JabberContactPool::slotContactDestroyed ( Kopete::Contact *contact ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Contact deleted, collecting the pieces..." << endl; + + JabberBaseContact *jabberContact = static_cast( contact ); + //WARNING this ptr is not usable, we are in the Kopete::Contact destructor + + // remove contact from the pool + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact() == jabberContact ) + { + mPool.remove (); + break; + } + } + + // delete all resources for it + if(contact->account()==(Kopete::Account*)(mAccount)) + mAccount->resourcePool()->removeAllResources ( XMPP::Jid ( contact->contactId() ) ); + else + { + //this is a legacy contact. we have no way to get the real Jid at this point, we can only guess it. + QString contactId= contact->contactId().replace('@','%') + "@" + contact->account()->myself()->contactId(); + mAccount->resourcePool()->removeAllResources ( XMPP::Jid ( contactId ) ) ; + } + +} + +void JabberContactPool::clear () +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Clearing the contact pool." << endl; + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + /* + * The following deletion will cause slotContactDestroyed() + * to be called, which will clean the up the list. + * NOTE: this is a very inefficient way to clear the list + */ + delete mContactItem->contact (); + } + +} + +void JabberContactPool::setDirty ( const XMPP::Jid &jid, bool dirty ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Setting flag for " << jid.full() << " to " << dirty << endl; + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact()->rosterItem().jid().full().lower() == jid.full().lower() ) + { + mContactItem->setDirty ( dirty ); + return; + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "WARNING: No match found!" << endl; + +} + +void JabberContactPool::cleanUp () +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Cleaning dirty items from contact pool." << endl; + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->dirty () ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing dirty contact " << mContactItem->contact()->contactId () << endl; + + /* + * The following deletion will cause slotContactDestroyed() + * to be called, which will clean the up the list. + */ + delete mContactItem->contact (); + } + } + +} + +JabberBaseContact *JabberContactPool::findExactMatch ( const XMPP::Jid &jid ) +{ + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact()->rosterItem().jid().full().lower () == jid.full().lower () ) + { + return mContactItem->contact (); + } + } + + return 0L; + +} + +JabberBaseContact *JabberContactPool::findRelevantRecipient ( const XMPP::Jid &jid ) +{ + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact()->rosterItem().jid().full().lower () == jid.userHost().lower () ) + { + return mContactItem->contact (); + } + } + + return 0L; + +} + +QPtrList JabberContactPool::findRelevantSources ( const XMPP::Jid &jid ) +{ + QPtrList list; + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact()->rosterItem().jid().userHost().lower () == jid.userHost().lower () ) + { + list.append ( mContactItem->contact () ); + } + } + + return list; + +} + +JabberContactPoolItem::JabberContactPoolItem ( JabberBaseContact *contact ) +{ + mDirty = true; + mContact = contact; +} + +JabberContactPoolItem::~JabberContactPoolItem () +{ +} + +void JabberContactPoolItem::setDirty ( bool dirty ) +{ + mDirty = dirty; +} + +bool JabberContactPoolItem::dirty () +{ + return mDirty; +} + +JabberBaseContact *JabberContactPoolItem::contact () +{ + return mContact; +} + +#include "jabbercontactpool.moc" diff --git a/kopete/protocols/jabber/jabbercontactpool.h b/kopete/protocols/jabber/jabbercontactpool.h new file mode 100644 index 00000000..6582f64c --- /dev/null +++ b/kopete/protocols/jabber/jabbercontactpool.h @@ -0,0 +1,124 @@ + /* + * jabbercontactpool.h + * + * Copyright (c) 2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERCONTACTPOOL_H +#define JABBERCONTACTPOOL_H + +#include +#include + +namespace Kopete { class MetaContact; } +namespace Kopete { class Contact; } +class JabberContactPoolItem; +class JabberBaseContact; +class JabberContact; +class JabberGroupContact; +class JabberAccount; +class JabberTransport; + +/** + * @author Till Gerken + */ +class JabberContactPool : public QObject +{ + +Q_OBJECT + +public: + /** + * Default constructor + */ + JabberContactPool ( JabberAccount *account ); + + /** + * Default destructor + */ + ~JabberContactPool(); + + /** + * Add a contact to the pool + */ + JabberContact *addContact ( const XMPP::RosterItem &contact, Kopete::MetaContact *metaContact, bool dirty = true ); + JabberBaseContact *addGroupContact ( const XMPP::RosterItem &contact, bool roomContact, Kopete::MetaContact *metaContact, bool dirty = true ); + + /** + * Remove a contact from the pool + */ + void removeContact ( const XMPP::Jid &jid ); + + /** + * Remove all contacts from the pool + */ + void clear (); + + /** + * Sets the "dirty" flag for a certain contact + */ + void setDirty ( const XMPP::Jid &jid, bool dirty ); + + /** + * Remove all dirty elements from the pool + * (used after connecting to delete removed items from the roster) + */ + void cleanUp (); + + /** + * Find an exact match in the pool by full JID. + */ + JabberBaseContact *findExactMatch ( const XMPP::Jid &jid ); + + /** + * Find a relevant recipient for a given JID. + * This will match user@domain for a given user@domain/resource, + * but NOT user@domain/resource for a given user@domain. + */ + JabberBaseContact *findRelevantRecipient ( const XMPP::Jid &jid ); + + /** + * Find relevant sources for a given JID. + * This will match user@domain/resource for a given user@domain. + */ + QPtrList findRelevantSources ( const XMPP::Jid &jid ); + +private slots: + void slotContactDestroyed ( Kopete::Contact *contact ); + +private: + JabberContactPoolItem *findPoolItem ( const XMPP::RosterItem &contact ); + + QPtrList mPool; + JabberAccount *mAccount; + +}; + +class JabberContactPoolItem : QObject +{ +Q_OBJECT +public: + JabberContactPoolItem ( JabberBaseContact *contact ); + ~JabberContactPoolItem (); + + void setDirty ( bool dirty ); + bool dirty (); + JabberBaseContact *contact (); + +private: + bool mDirty; + JabberBaseContact *mContact; +}; + +#endif diff --git a/kopete/protocols/jabber/jabberfiletransfer.cpp b/kopete/protocols/jabber/jabberfiletransfer.cpp new file mode 100644 index 00000000..fde5b105 --- /dev/null +++ b/kopete/protocols/jabber/jabberfiletransfer.cpp @@ -0,0 +1,326 @@ + /* + * jabberfiletransfer.cpp + * + * Copyright (c) 2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#include +#include +#include +#include "jabberfiletransfer.h" +#include +#include +#include +#include "kopeteuiglobal.h" +#include "kopetemetacontact.h" +#include "kopetecontactlist.h" +#include "kopetetransfermanager.h" +#include "jabberaccount.h" +#include "jabberprotocol.h" +#include "jabberclient.h" +#include "jabbercontactpool.h" +#include "jabberbasecontact.h" +#include "jabbercontact.h" + +JabberFileTransfer::JabberFileTransfer ( JabberAccount *account, XMPP::FileTransfer *incomingTransfer ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "New incoming transfer from " << incomingTransfer->peer().full () << ", filename " << incomingTransfer->fileName () << ", size " << QString::number ( incomingTransfer->fileSize () ) << endl; + + mAccount = account; + mXMPPTransfer = incomingTransfer; + + // try to locate an exact match in our pool first + JabberBaseContact *contact = mAccount->contactPool()->findExactMatch ( mXMPPTransfer->peer () ); + + if ( !contact ) + { + // we have no exact match, try a broader search + contact = mAccount->contactPool()->findRelevantRecipient ( mXMPPTransfer->peer () ); + } + + if ( !contact ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "No matching local contact found, creating a new one." << endl; + + Kopete::MetaContact *metaContact = new Kopete::MetaContact (); + + metaContact->setTemporary (true); + + contact = mAccount->contactPool()->addContact ( mXMPPTransfer->peer (), metaContact, false ); + + Kopete::ContactList::self ()->addMetaContact ( metaContact ); + } + + connect ( Kopete::TransferManager::transferManager (), SIGNAL ( accepted ( Kopete::Transfer *, const QString & ) ), + this, SLOT ( slotIncomingTransferAccepted ( Kopete::Transfer *, const QString & ) ) ); + connect ( Kopete::TransferManager::transferManager (), SIGNAL ( refused ( const Kopete::FileTransferInfo & ) ), + this, SLOT ( slotTransferRefused ( const Kopete::FileTransferInfo & ) ) ); + + initializeVariables (); + + mTransferId = Kopete::TransferManager::transferManager()->askIncomingTransfer ( contact, + mXMPPTransfer->fileName (), + mXMPPTransfer->fileSize (), + mXMPPTransfer->description () ); + +} + +JabberFileTransfer::JabberFileTransfer ( JabberAccount *account, JabberBaseContact *contact, const QString &file ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "New outgoing transfer for " << contact->contactId() << ": " << file << endl; + + mAccount = account; + mLocalFile.setName ( file ); + mLocalFile.open ( IO_ReadOnly ); + + mKopeteTransfer = Kopete::TransferManager::transferManager()->addTransfer ( contact, + mLocalFile.name (), + mLocalFile.size (), + contact->contactId (), + Kopete::FileTransferInfo::Outgoing ); + + connect ( mKopeteTransfer, SIGNAL ( result ( KIO::Job * ) ), this, SLOT ( slotTransferResult () ) ); + + mXMPPTransfer = mAccount->client()->fileTransferManager()->createTransfer (); + + initializeVariables (); + + connect ( mXMPPTransfer, SIGNAL ( connected () ), this, SLOT ( slotOutgoingConnected () ) ); + connect ( mXMPPTransfer, SIGNAL ( bytesWritten ( int ) ), this, SLOT ( slotOutgoingBytesWritten ( int ) ) ); + connect ( mXMPPTransfer, SIGNAL ( error ( int ) ), this, SLOT ( slotTransferError ( int ) ) ); + + mXMPPTransfer->sendFile ( XMPP::Jid ( contact->fullAddress () ), KURL(file).fileName (), mLocalFile.size (), "" ); + +} + +JabberFileTransfer::~JabberFileTransfer () +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Destroying Jabber file transfer object." << endl; + + mLocalFile.close (); + + mXMPPTransfer->close (); + delete mXMPPTransfer; + +} + +void JabberFileTransfer::initializeVariables () +{ + + mTransferId = -1; + mBytesTransferred = 0; + mBytesToTransfer = 0; + mXMPPTransfer->setProxy ( XMPP::Jid ( mAccount->configGroup()->readEntry ( "ProxyJID" ) ) ); + +} + +void JabberFileTransfer::slotIncomingTransferAccepted ( Kopete::Transfer *transfer, const QString &fileName ) +{ + + if ( (long)transfer->info().transferId () != mTransferId ) + return; + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Accepting transfer for " << mXMPPTransfer->peer().full () << endl; + + mKopeteTransfer = transfer; + mLocalFile.setName ( fileName ); + + bool couldOpen = false; + Q_LLONG offset = 0; + Q_LLONG length = 0; + + mBytesTransferred = 0; + mBytesToTransfer = mXMPPTransfer->fileSize (); + + if ( mXMPPTransfer->rangeSupported () && mLocalFile.exists () ) + { + KGuiItem resumeButton ( i18n ( "&Resume" ) ); + KGuiItem overwriteButton ( i18n ( "Over&write" ) ); + + switch ( KMessageBox::questionYesNoCancel ( Kopete::UI::Global::mainWidget (), + i18n ( "The file %1 already exists, do you want to resume or overwrite it?" ).arg ( fileName ), + i18n ( "File Exists: %1" ).arg ( fileName ), + resumeButton, overwriteButton ) ) + { + case KMessageBox::Yes: // resume + couldOpen = mLocalFile.open ( IO_ReadWrite ); + if ( couldOpen ) + { + offset = mLocalFile.size (); + length = mXMPPTransfer->fileSize () - offset; + mBytesTransferred = offset; + mBytesToTransfer = length; + mLocalFile.at ( mLocalFile.size () ); + } + break; + + case KMessageBox::No: // overwrite + couldOpen = mLocalFile.open ( IO_WriteOnly ); + break; + + default: // cancel + deleteLater (); + return; + } + } + else + { + // overwrite by default + couldOpen = mLocalFile.open ( IO_WriteOnly ); + } + + if ( !couldOpen ) + { + transfer->slotError ( KIO::ERR_COULD_NOT_WRITE, fileName ); + + deleteLater (); + } + else + { + connect ( mKopeteTransfer, SIGNAL ( result ( KIO::Job * ) ), this, SLOT ( slotTransferResult () ) ); + connect ( mXMPPTransfer, SIGNAL ( readyRead ( const QByteArray& ) ), this, SLOT ( slotIncomingDataReady ( const QByteArray & ) ) ); + connect ( mXMPPTransfer, SIGNAL ( error ( int ) ), this, SLOT ( slotTransferError ( int ) ) ); + mXMPPTransfer->accept ( offset, length ); + } + +} + +void JabberFileTransfer::slotTransferRefused ( const Kopete::FileTransferInfo &transfer ) +{ + + if ( (long)transfer.transferId () != mTransferId ) + return; + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Local user refused transfer from " << mXMPPTransfer->peer().full () << endl; + + deleteLater (); + +} + +void JabberFileTransfer::slotTransferResult () +{ + + if ( mKopeteTransfer->error () == KIO::ERR_USER_CANCELED ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Transfer with " << mXMPPTransfer->peer().full () << " has been canceled." << endl; + mXMPPTransfer->close (); + deleteLater (); + } + +} + +void JabberFileTransfer::slotTransferError ( int errorCode ) +{ + + switch ( errorCode ) + { + case XMPP::FileTransfer::ErrReject: + // user rejected the transfer request + mKopeteTransfer->slotError ( KIO::ERR_ACCESS_DENIED, + mXMPPTransfer->peer().full () ); + break; + + case XMPP::FileTransfer::ErrNeg: + // unable to negotiate a suitable connection for the file transfer with the user + mKopeteTransfer->slotError ( KIO::ERR_COULD_NOT_LOGIN, + mXMPPTransfer->peer().full () ); + break; + + case XMPP::FileTransfer::ErrConnect: + // could not connect to the user + mKopeteTransfer->slotError ( KIO::ERR_COULD_NOT_CONNECT, + mXMPPTransfer->peer().full () ); + break; + + case XMPP::FileTransfer::ErrStream: + // data stream was disrupted, probably cancelled + mKopeteTransfer->slotError ( KIO::ERR_CONNECTION_BROKEN, + mXMPPTransfer->peer().full () ); + break; + + default: + // unknown error + mKopeteTransfer->slotError ( KIO::ERR_UNKNOWN, + mXMPPTransfer->peer().full () ); + break; + } + + deleteLater (); + +} + +void JabberFileTransfer::slotIncomingDataReady ( const QByteArray &data ) +{ + + mBytesTransferred += data.size (); + mBytesToTransfer -= data.size (); + + mKopeteTransfer->slotProcessed ( mBytesTransferred ); + + mLocalFile.writeBlock ( data ); + + if ( mBytesToTransfer <= 0 ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Transfer from " << mXMPPTransfer->peer().full () << " done." << endl; + + mKopeteTransfer->slotComplete (); + + deleteLater (); + } + +} + +void JabberFileTransfer::slotOutgoingConnected () +{ + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Outgoing data connection is open." << endl; + + mBytesTransferred = mXMPPTransfer->offset (); + mLocalFile.at ( mXMPPTransfer->offset () ); + mBytesToTransfer = ( mXMPPTransfer->fileSize () > mXMPPTransfer->length () ) ? mXMPPTransfer->length () : mXMPPTransfer->fileSize (); + + slotOutgoingBytesWritten ( 0 ); + +} + +void JabberFileTransfer::slotOutgoingBytesWritten ( int nrWritten ) +{ + + mBytesTransferred += nrWritten; + mBytesToTransfer -= nrWritten; + + mKopeteTransfer->slotProcessed ( mBytesTransferred ); + + if ( mBytesToTransfer ) + { + int nrToWrite = mXMPPTransfer->dataSizeNeeded (); + + QByteArray readBuffer ( nrToWrite ); + + mLocalFile.readBlock ( readBuffer.data (), nrToWrite ); + + mXMPPTransfer->writeFileData ( readBuffer ); + } + else + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Transfer to " << mXMPPTransfer->peer().full () << " done." << endl; + + mKopeteTransfer->slotComplete (); + + deleteLater (); + } + +} + +#include "jabberfiletransfer.moc" diff --git a/kopete/protocols/jabber/jabberfiletransfer.h b/kopete/protocols/jabber/jabberfiletransfer.h new file mode 100644 index 00000000..01ba99e1 --- /dev/null +++ b/kopete/protocols/jabber/jabberfiletransfer.h @@ -0,0 +1,74 @@ + /* + * jabberfiletransfer.h + * + * Copyright (c) 2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERFILETRANSFER_H +#define JABBERFILETRANSFER_H + +#include +#include + +class QString; +class JabberAccount; +namespace Kopete { class Transfer; } +namespace Kopete { class FileTransferInfo; } +class JabberBaseContact; + +class JabberFileTransfer : public QObject +{ + +Q_OBJECT + +public: + /** + * Constructor for an incoming transfer + */ + JabberFileTransfer ( JabberAccount *account, XMPP::FileTransfer *incomingTransfer ); + + /** + * Constructor for an outgoing transfer + */ + JabberFileTransfer ( JabberAccount *account, JabberBaseContact *contact, const QString &file ); + + ~JabberFileTransfer (); + +private slots: + void slotIncomingTransferAccepted ( Kopete::Transfer *transfer, const QString &fileName ); + void slotTransferRefused ( const Kopete::FileTransferInfo &transfer ); + void slotTransferResult (); + void slotTransferError ( int errorCode ); + + void slotOutgoingConnected (); + void slotOutgoingBytesWritten ( int nrWritten ); + + void slotIncomingDataReady ( const QByteArray &data ); + +private: + void initializeVariables (); + + JabberAccount *mAccount; + XMPP::FileTransfer *mXMPPTransfer; + Kopete::Transfer *mKopeteTransfer; + QFile mLocalFile; + int mTransferId; + Q_LLONG mBytesTransferred; + Q_LLONG mBytesToTransfer; + +}; + +#endif + +// vim: set noet ts=4 sts=4 tw=4: diff --git a/kopete/protocols/jabber/jabberformlineedit.cpp b/kopete/protocols/jabber/jabberformlineedit.cpp new file mode 100644 index 00000000..04187b20 --- /dev/null +++ b/kopete/protocols/jabber/jabberformlineedit.cpp @@ -0,0 +1,58 @@ + /* + * jabberformlineedit.cpp + * + * Copyright (c) 2002-2003 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#include "jabberformlineedit.h" + +JabberFormLineEdit::JabberFormLineEdit (const int type, const QString & realName, const QString & value, QWidget * parent, const char *name):QLineEdit (value, + parent, + name) +{ + + fieldType = type; + fieldName = realName; + +} + +void JabberFormLineEdit::slotGatherData (XMPP::Form & form) +{ + + form += XMPP::FormField (fieldName, text ()); + +} + +JabberFormLineEdit::~JabberFormLineEdit () +{ +} + +JabberFormPasswordEdit::JabberFormPasswordEdit (const int type, const QString & realName, const QString & value, QWidget * parent, const char *name):KPasswordEdit(parent, name) +{ + + setText(value); + fieldType = type; + fieldName = realName; + +} + +void JabberFormPasswordEdit::slotGatherData (XMPP::Form & form) +{ + + form += XMPP::FormField (fieldName, password()); + +} + + +#include "jabberformlineedit.moc" diff --git a/kopete/protocols/jabber/jabberformlineedit.h b/kopete/protocols/jabber/jabberformlineedit.h new file mode 100644 index 00000000..770bab39 --- /dev/null +++ b/kopete/protocols/jabber/jabberformlineedit.h @@ -0,0 +1,59 @@ + /* + * jabberformlineedit.h + * + * Copyright (c) 2002-2003 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERFORMLINEEDIT_H +#define JABBERFORMLINEEDIT_H + +#include +#include +#include + +#include "xmpp_tasks.h" + +/** + *@author Till Gerken + */ + +class JabberFormLineEdit:public QLineEdit +{ + + Q_OBJECT public: + JabberFormLineEdit (const int type, const QString & realName, const QString & value, QWidget * parent = 0, const char *name = 0); + ~JabberFormLineEdit (); + + public slots:void slotGatherData (XMPP::Form & form); + + private: + int fieldType; + QString fieldName; + +}; + +class JabberFormPasswordEdit:public KPasswordEdit +{ + + Q_OBJECT public: + JabberFormPasswordEdit(const int type, const QString & realName, const QString & value, QWidget * parent = 0, const char *name = 0); + + public slots:void slotGatherData (XMPP::Form & form); + + private: + int fieldType; + QString fieldName; + +}; +#endif diff --git a/kopete/protocols/jabber/jabberformtranslator.cpp b/kopete/protocols/jabber/jabberformtranslator.cpp new file mode 100644 index 00000000..fe6ec230 --- /dev/null +++ b/kopete/protocols/jabber/jabberformtranslator.cpp @@ -0,0 +1,91 @@ + /* + * jabberformtranslator.cpp + * + * Copyright (c) 2002-2003 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#include + +#include + +#include "jabberformlineedit.h" +#include "jabberformtranslator.h" + +JabberFormTranslator::JabberFormTranslator (const XMPP::Form & form, QWidget * parent, const char *name):QWidget (parent, name) +{ + /* Copy basic form values. */ + privForm.setJid (form.jid ()); + privForm.setInstructions (form.instructions ()); + privForm.setKey (form.key ()); + + emptyForm = privForm; + + /* Add instructions to layout. */ + QVBoxLayout *innerLayout = new QVBoxLayout (this, 0, 4); + + QLabel *label = new QLabel (form.instructions (), this, "InstructionLabel"); + label->setAlignment (int (QLabel::WordBreak | QLabel::AlignVCenter)); + label->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Fixed, true); + label->show (); + + innerLayout->addWidget (label, 0); + + QGridLayout *formLayout = new QGridLayout (innerLayout, form.count (), 2); + + int row = 1; + XMPP::Form::const_iterator formEnd = form.end (); + for (XMPP::Form::const_iterator it = form.begin (); it != formEnd; ++it, ++row) + { + kdDebug (14130) << "[JabberFormTranslator] Adding field realName()==" << + (*it).realName () << ", fieldName()==" << (*it).fieldName () << " to the dialog" << endl; + + label = new QLabel ((*it).fieldName (), this, (*it).fieldName ().latin1 ()); + formLayout->addWidget (label, row, 0); + label->show (); + + QLineEdit *edit; + if ((*it).type() == XMPP::FormField::password) + { + edit = new JabberFormPasswordEdit((*it).type (), (*it).realName (), (*it).value (), this); + } + else + { + edit = new JabberFormLineEdit ((*it).type (), (*it).realName (), + (*it).value (), this); + } + formLayout->addWidget (edit, row, 1); + edit->show (); + + connect (this, SIGNAL (gatherData (XMPP::Form &)), edit, SLOT (slotGatherData (XMPP::Form &))); + } + + innerLayout->addStretch (); +} + +XMPP::Form & JabberFormTranslator::resultData () +{ + // clear form data + privForm = emptyForm; + + // let all line edit fields write into our form + emit gatherData (privForm); + + return privForm; +} + +JabberFormTranslator::~JabberFormTranslator () +{ +} + +#include "jabberformtranslator.moc" diff --git a/kopete/protocols/jabber/jabberformtranslator.h b/kopete/protocols/jabber/jabberformtranslator.h new file mode 100644 index 00000000..d9cf4044 --- /dev/null +++ b/kopete/protocols/jabber/jabberformtranslator.h @@ -0,0 +1,49 @@ + /* + * jabberformtranslator.h + * + * Copyright (c) 2002-2003 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERFORMTRANSLATOR_H +#define JABBERFORMTRANSLATOR_H + +#include +#include + +#include "xmpp_tasks.h" + +/** + *@author Till Gerken + */ + +class JabberFormTranslator:public QWidget +{ + +Q_OBJECT + +public: + JabberFormTranslator (const XMPP::Form & form, QWidget * parent = 0, const char *name = 0); + ~JabberFormTranslator (); + + XMPP::Form & resultData (); + +signals: + void gatherData (XMPP::Form & form); + +private: + XMPP::Form emptyForm, privForm; + +}; + +#endif diff --git a/kopete/protocols/jabber/jabbergroupchatmanager.cpp b/kopete/protocols/jabber/jabbergroupchatmanager.cpp new file mode 100644 index 00000000..7686ba8c --- /dev/null +++ b/kopete/protocols/jabber/jabbergroupchatmanager.cpp @@ -0,0 +1,163 @@ +/* + jabbergroupchatmanager.cpp - Jabber Message Manager for group chats + + Copyright (c) 2004 by Till Gerken + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "jabbergroupchatmanager.h" + +#include +#include +#include +#include "kopetechatsessionmanager.h" +#include "kopeteview.h" +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabberclient.h" +#include "jabbercontact.h" + +JabberGroupChatManager::JabberGroupChatManager ( JabberProtocol *protocol, const JabberBaseContact *user, + Kopete::ContactPtrList others, XMPP::Jid roomJid, const char *name ) + : Kopete::ChatSession ( user, others, protocol, name ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "New message manager for " << user->contactId () << endl; + + mRoomJid = roomJid; + + setMayInvite( true ); + + // make sure Kopete knows about this instance + Kopete::ChatSessionManager::self()->registerChatSession ( this ); + + connect ( this, SIGNAL ( messageSent ( Kopete::Message &, Kopete::ChatSession * ) ), + this, SLOT ( slotMessageSent ( Kopete::Message &, Kopete::ChatSession * ) ) ); + + updateDisplayName (); +} + +JabberGroupChatManager::~JabberGroupChatManager() +{ +} + +void JabberGroupChatManager::updateDisplayName () +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << endl; + + setDisplayName ( mRoomJid.full () ); + +} + +const JabberBaseContact *JabberGroupChatManager::user () const +{ + + return static_cast(Kopete::ChatSession::myself()); + +} + +JabberAccount *JabberGroupChatManager::account () const +{ + + return user()->account(); + +} + +void JabberGroupChatManager::slotMessageSent ( Kopete::Message &message, Kopete::ChatSession * ) +{ + + if( account()->isConnected () ) + { + XMPP::Message jabberMessage; + + jabberMessage.setFrom ( account()->client()->jid() ); + + + XMPP::Jid toJid ( mRoomJid ); + + jabberMessage.setTo ( toJid ); + + jabberMessage.setSubject ( message.subject () ); + jabberMessage.setTimeStamp ( message.timestamp () ); + + if ( message.plainBody().find ( "-----BEGIN PGP MESSAGE-----" ) != -1 ) + { + /* + * This message is encrypted, so we need to set + * a fake body indicating that this is an encrypted + * message (for clients not implementing this + * functionality) and then generate the encrypted + * payload out of the old message body. + */ + + // please don't translate the following string + jabberMessage.setBody ( i18n ( "This message is encrypted." ) ); + + QString encryptedBody = message.plainBody (); + + // remove PGP header and footer from message + encryptedBody.truncate ( encryptedBody.length () - QString("-----END PGP MESSAGE-----").length () - 2 ); + encryptedBody = encryptedBody.right ( encryptedBody.length () - encryptedBody.find ( "\n\n" ) - 2 ); + + // assign payload to message + jabberMessage.setXEncrypted ( encryptedBody ); + } + else + { + // this message is not encrypted + jabberMessage.setBody ( message.plainBody () ); + } + + jabberMessage.setType ( "groupchat" ); + + // send the message + account()->client()->sendMessage ( jabberMessage ); + + // tell the manager that we sent successfully + messageSucceeded (); + } + else + { + account()->errorConnectFirst (); + + // FIXME: there is no messageFailed() yet, + // but we need to stop the animation etc. + messageSucceeded (); + } +} + +void JabberGroupChatManager::inviteContact( const QString & contactId ) +{ + if( account()->isConnected () ) + { + //NOTE: this is the obsolete, NOT RECOMMANDED protocol. + // iris doesn't implement groupchat yet + XMPP::Message jabberMessage; + jabberMessage.setFrom ( account()->client()->jid() ); + jabberMessage.setTo ( contactId ); + jabberMessage.setInvite( mRoomJid.userHost() ); + jabberMessage.setBody( i18n("You have been invited to %1").arg( mRoomJid.userHost() ) ); + + // send the message + account()->client()->sendMessage ( jabberMessage ); + } + else + { + account()->errorConnectFirst (); + } +} + + +#include "jabbergroupchatmanager.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/jabber/jabbergroupchatmanager.h b/kopete/protocols/jabber/jabbergroupchatmanager.h new file mode 100644 index 00000000..96c689d0 --- /dev/null +++ b/kopete/protocols/jabber/jabbergroupchatmanager.h @@ -0,0 +1,78 @@ +/* + jabbergroupchatmanager.h - Jabber Message Manager for group chats + + Copyright (c) 2004 by Till Gerken + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef JABBERGROUPCHATMANAGER_H +#define JABBERGROUPCHATMANAGER_H + +#include "kopetechatsession.h" +#include "xmpp.h" + +class JabberProtocol; +class JabberAccount; +class JabberBaseContact; +namespace Kopete { class Message; } +class QString; + +/** + * @author Till Gerken + */ +class JabberGroupChatManager : public Kopete::ChatSession +{ + Q_OBJECT + +public: + JabberGroupChatManager ( JabberProtocol *protocol, const JabberBaseContact *user, + Kopete::ContactPtrList others, XMPP::Jid roomJid, const char *name = 0 ); + + ~JabberGroupChatManager(); + + /** + * @brief Get the local user in the session + * @return the local user in the session, same as account()->myself() + */ + const JabberBaseContact *user () const; + + /** + * @brief get the account + * @return the account + */ + JabberAccount *account() const ; + + /** + * Re-generate the display name + */ + void updateDisplayName (); + + /** + * reimplemented from Kopete::ChatSession + * called when a contact is droped in the window + */ + virtual void inviteContact(const QString &contactId); + +private slots: + void slotMessageSent ( Kopete::Message &message, Kopete::ChatSession *kmm ); + + + +private: + XMPP::Jid mRoomJid; +}; + +#endif + +// vim: set noet ts=4 sts=4 tw=4: + diff --git a/kopete/protocols/jabber/jabbergroupcontact.cpp b/kopete/protocols/jabber/jabbergroupcontact.cpp new file mode 100644 index 00000000..83d69ab9 --- /dev/null +++ b/kopete/protocols/jabber/jabbergroupcontact.cpp @@ -0,0 +1,378 @@ + /* + * jabbercontact.cpp - Regular Kopete Jabber protocol contact + * + * Copyright (c) 2002-2004 by Till Gerken + * Copyright (c) 2006 by Olivier Goffart + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#include "jabbergroupcontact.h" + +#include +#include +#include +#include +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabberclient.h" +#include "jabberfiletransfer.h" +#include "jabbergroupchatmanager.h" +#include "jabbergroupmembercontact.h" +#include "jabbercontactpool.h" +#include "kopetemetacontact.h" +#include "xmpp_tasks.h" + +/** + * JabberGroupContact constructor + */ +JabberGroupContact::JabberGroupContact (const XMPP::RosterItem &rosterItem, JabberAccount *account, Kopete::MetaContact * mc) + : JabberBaseContact ( XMPP::RosterItem ( rosterItem.jid().userHost () ), account, mc) , mNick( rosterItem.jid().resource() ) +{ + setIcon( "jabber_group" ); + + // initialize here, we need it set before we instantiate the manager below + mManager = 0; + + setFileCapable ( false ); + + /** + * Add our own nick as first subcontact (we need to do that here + * because we need to set this contact as myself() of the message + * manager). + */ + mSelfContact = addSubContact ( rosterItem ); + + /** + * Instantiate a new message manager without members. + */ + mManager = new JabberGroupChatManager ( protocol (), mSelfContact, + Kopete::ContactPtrList (), XMPP::Jid ( rosterItem.jid().userHost () ) ); + + connect ( mManager, SIGNAL ( closing ( Kopete::ChatSession* ) ), this, SLOT ( slotChatSessionDeleted () ) ); + + connect ( account->myself() , SIGNAL(onlineStatusChanged( Kopete::Contact*, const Kopete::OnlineStatus&, const Kopete::OnlineStatus& ) ) , + this , SLOT(slotStatusChanged() ) ) ; + + /** + * FIXME: The first contact in the list of the message manager + * needs to be our own contact. This is a flaw in the Kopete + * API because it can't deal with group chat properly. + * If we are alone in a room, we are myself() already and members() + * is empty. This makes at least the history plugin crash. + */ + mManager->addContact ( this ); + + + + /** + * Let's construct the window: + * otherwise, the ref count of maznager is equal to zero. + * and if we receive a message before the window is shown, + * it will be deleted and we will be out of the channel + * In all case, there are no reason to don't show it. + */ + mManager->view( true , "kopete_chatwindow" ); +} + +JabberGroupContact::~JabberGroupContact () +{ + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << endl; + + if(mManager) + { + mManager->deleteLater(); + } + + for ( Kopete::Contact *contact = mContactList.first (); contact; contact = mContactList.next () ) + { + /*if(mManager) + mManager->removeContact( contact );*/ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Deleting KC " << contact->contactId () << endl; + contact->deleteLater(); + } + + for ( Kopete::MetaContact *metaContact = mMetaContactList.first (); metaContact; metaContact = mMetaContactList.next () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Deleting KMC " << metaContact->metaContactId () << endl; + metaContact->deleteLater(); + } +} + +QPtrList *JabberGroupContact::customContextMenuActions () +{ + QPtrList *actionCollection = new QPtrList(); + + KAction *actionSetNick = new KAction (i18n ("Change nick name"), 0, 0, this, SLOT (slotChangeNick()), this, "jabber_changenick"); + actionCollection->append( actionSetNick ); + + return actionCollection; +} + +Kopete::ChatSession *JabberGroupContact::manager ( Kopete::Contact::CanCreateFlags canCreate ) +{ + if(!mManager && canCreate == Kopete::Contact::CanCreate) + { + kdWarning (JABBER_DEBUG_GLOBAL) << k_funcinfo << "somehow, the chat manager was removed, and the contact is still there" << endl; + mManager = new JabberGroupChatManager ( protocol (), mSelfContact, + Kopete::ContactPtrList (), XMPP::Jid ( rosterItem().jid().userHost() ) ); + + mManager->addContact ( this ); + + connect ( mManager, SIGNAL ( closing ( Kopete::ChatSession* ) ), this, SLOT ( slotChatSessionDeleted () ) ); + + //if we have to recreate the manager, we probably have to connect again to the chat. + slotStatusChanged(); + } + return mManager; + +} + +void JabberGroupContact::handleIncomingMessage (const XMPP::Message & message) +{ + // message type is always chat in a groupchat + QString viewType = "kopete_chatwindow"; + Kopete::Message *newMessage = 0L; + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Received a message" << endl; + + /** + * Don't display empty messages, these were most likely just carrying + * event notifications or other payload. + */ + if ( message.body().isEmpty () ) + return; + + manager(CanCreate); //force to create mManager + + Kopete::ContactPtrList contactList = manager()->members(); + + // check for errors + if ( message.type () == "error" ) + { + newMessage = new Kopete::Message( message.timeStamp (), this, contactList, + i18n("Your message could not be delivered: \"%1\", Reason: \"%2\""). + arg ( message.body () ).arg ( message.error().text ), + message.subject(), Kopete::Message::Inbound, Kopete::Message::PlainText, viewType ); + } + else + { + // retrieve and reformat body + QString body = message.body (); + + if( !message.xencrypted().isEmpty () ) + { + body = QString ("-----BEGIN PGP MESSAGE-----\n\n") + message.xencrypted () + QString ("\n-----END PGP MESSAGE-----\n"); + } + + // locate the originating contact + JabberBaseContact *subContact = account()->contactPool()->findExactMatch ( message.from () ); + + if ( !subContact ) + { + kdWarning (JABBER_DEBUG_GLOBAL) << k_funcinfo << "the contact is not in the list : " << message.from().full()<< endl; + + /** + * We couldn't find the contact for this message. That most likely means + * that it originated from a history backlog or something similar and + * the sending person is not in the channel anymore. We need to create + * a new contact for this which does not show up in the manager. + */ + subContact = addSubContact ( XMPP::RosterItem ( message.from () ), false ); + } + + // convert XMPP::Message into Kopete::Message + newMessage = new Kopete::Message ( message.timeStamp (), subContact, contactList, body, + message.subject (), + subContact != mManager->myself() ? Kopete::Message::Inbound : Kopete::Message::Outbound, + Kopete::Message::PlainText, viewType ); + } + + // append message to manager + mManager->appendMessage ( *newMessage ); + + delete newMessage; + +} + +JabberBaseContact *JabberGroupContact::addSubContact ( const XMPP::RosterItem &rosterItem, bool addToManager ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Adding new subcontact " << rosterItem.jid().full () << " to room " << mRosterItem.jid().full () << endl; + + // see if this contact already exists, skip creation otherwise + JabberBaseContact *subContact = dynamic_cast( account()->contactPool()->findExactMatch ( rosterItem.jid () ) ); + + if ( subContact ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Contact already exists, not adding again." << endl; + return subContact; + } + + // Create new meta contact that holds the group chat contact. + Kopete::MetaContact *metaContact = new Kopete::MetaContact (); + metaContact->setTemporary ( true ); + mMetaContactList.append ( metaContact ); + + // now add contact to the pool, no dirty flag + subContact = account()->contactPool()->addGroupContact ( rosterItem, false, metaContact, false ); + + /** + * Add the contact to our message manager first. We need + * to check the pointer for validity, because this method + * gets called from the constructor, where the manager + * does not exist yet. + */ + if ( mManager && addToManager ) + mManager->addContact ( subContact ); + + // now, add the contact also to our own list + mContactList.append ( subContact ); + + connect(subContact , SIGNAL(contactDestroyed(Kopete::Contact*)) , this , SLOT(slotSubContactDestroyed(Kopete::Contact*))); + + return subContact; + +} + +void JabberGroupContact::removeSubContact ( const XMPP::RosterItem &rosterItem ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Removing subcontact " << rosterItem.jid().full () << " from room " << mRosterItem.jid().full () << endl; + + // make sure that subcontacts are only removed from the room contact, which has no resource + if ( !mRosterItem.jid().resource().isEmpty () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "WARNING: Trying to remove subcontact from subcontact!" << endl; + return; + } + + // find contact in the pool + JabberGroupMemberContact *subContact = dynamic_cast( account()->contactPool()->findExactMatch ( rosterItem.jid () ) ); + + if ( !subContact ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "WARNING: Subcontact couldn't be located!" << endl; + return; + } + + if(mManager && subContact->contactId() == mManager->myself()->contactId() ) + { + //HACK WORKAROUND FIXME KDE4 + //impossible to remove myself, or we will die + //subContact->setNickName( mNick ); //this is even worse than nothing + return; + } + + // remove the contact from the message manager first + if(mManager) + mManager->removeContact ( subContact ); + + // remove the contact's meta contact from our internal list + mMetaContactList.remove ( subContact->metaContact () ); + + // remove the contact from our internal list + mContactList.remove ( subContact ); + + // delete the meta contact first + delete subContact->metaContact (); + + // finally, delete the contact itself from the pool + account()->contactPool()->removeContact ( rosterItem.jid () ); + +} + +void JabberGroupContact::sendFile ( const KURL &sourceURL, const QString &/*fileName*/, uint /*fileSize*/ ) +{ + QString filePath; + + // if the file location is null, then get it from a file open dialog + if ( !sourceURL.isValid () ) + filePath = KFileDialog::getOpenFileName( QString::null , "*", 0L, i18n ( "Kopete File Transfer" ) ); + else + filePath = sourceURL.path(-1); + + QFile file ( filePath ); + + if ( file.exists () ) + { + // send the file + new JabberFileTransfer ( account (), this, filePath ); + } + +} + +void JabberGroupContact::slotChatSessionDeleted () +{ + + mManager = 0; + + if ( account()->isConnected () ) + { + account()->client()->leaveGroupChat ( mRosterItem.jid().host (), mRosterItem.jid().user () ); + } + + //deleteLater(); //we will be deleted later when the the account will know we have left + +} + +void JabberGroupContact::slotStatusChanged( ) +{ + if( !account()->isConnected() ) + { + //we need to remove all contact, because when we connect again, we will not receive the notificaion they are gone. + QPtrList copy_contactlist=mContactList; + for ( Kopete::Contact *contact = copy_contactlist.first (); contact; contact = copy_contactlist.next () ) + { + removeSubContact( XMPP::Jid(contact->contactId()) ); + } + return; + } + + + if( !isOnline() ) + { + //HACK WORKAROUND XMPP::client->d->groupChatList must contains us. + account()->client()->joinGroupChat( rosterItem().jid().host() , rosterItem().jid().user() , mNick ); + } + + //TODO: away message + XMPP::Status newStatus = account()->protocol()->kosToStatus( account()->myself()->onlineStatus() ); + account()->client()->setGroupChatStatus( rosterItem().jid().host() , rosterItem().jid().user() , newStatus ); +} + +void JabberGroupContact::slotChangeNick( ) +{ + + bool ok; + QString futureNewNickName = KInputDialog::getText( i18n( "Change nickanme - Jabber Plugin" ), + i18n( "Please enter the new nick name you want to have on the room %1" ).arg(rosterItem().jid().userHost()), + mNick, &ok ); + if ( !ok || !account()->isConnected()) + return; + + mNick=futureNewNickName; + + XMPP::Status status = account()->protocol()->kosToStatus( account()->myself()->onlineStatus() ); + account()->client()->changeGroupChatNick( rosterItem().jid().host() , rosterItem().jid().user() , mNick , status); + +} + +void JabberGroupContact::slotSubContactDestroyed( Kopete::Contact * deadContact ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "cleaning dead subcontact " << deadContact->contactId() << " from room " << mRosterItem.jid().full () << endl; + + mMetaContactList.remove ( deadContact->metaContact () ); + mContactList.remove ( deadContact ); + +} + +#include "jabbergroupcontact.moc" diff --git a/kopete/protocols/jabber/jabbergroupcontact.h b/kopete/protocols/jabber/jabbergroupcontact.h new file mode 100644 index 00000000..c157b823 --- /dev/null +++ b/kopete/protocols/jabber/jabbergroupcontact.h @@ -0,0 +1,107 @@ + /* + * jabbercontact.cpp - Kopete Jabber protocol groupchat contact + * + * Copyright (c) 2002-2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERGROUPCONTACT_H +#define JABBERGROUPCONTACT_H + +#include "jabberbasecontact.h" + +namespace Kopete { class MetaContact; } +class JabberGroupChatManager; + +class JabberGroupContact : public JabberBaseContact +{ + +Q_OBJECT + +public: + + JabberGroupContact (const XMPP::RosterItem &rosterItem, + JabberAccount *account, Kopete::MetaContact * mc); + + ~JabberGroupContact (); + + /** + * Create custom context menu items for the contact + * FIXME: implement manager version here? + */ + QPtrList *customContextMenuActions (); + + /** + * Deal with an incoming message for this contact. + */ + void handleIncomingMessage ( const XMPP::Message &message ); + + /** + * Add a contact to this room. + */ + JabberBaseContact *addSubContact ( const XMPP::RosterItem &rosterItem, bool addToManager = true ); + + /** + * Remove a contact from this room. + */ + void removeSubContact ( const XMPP::RosterItem &rosterItem ); + + Kopete::ChatSession *manager ( Kopete::Contact::CanCreateFlags canCreate = Kopete::Contact::CannotCreate ); + +public slots: + + /** + * This is the JabberContact level slot for sending files. + * + * @param sourceURL The actual KURL of the file you are sending + * @param fileName (Optional) An alternate name for the file - what the + * receiver will see + * @param fileSize (Optional) Size of the file being sent. Used when sending + * a nondeterminate file size (such as over a socket) + */ + virtual void sendFile( const KURL &sourceURL = KURL(), + const QString &fileName = QString::null, uint fileSize = 0L ); + +private slots: + + /** + * Catch a dying message manager and leave the room. + */ + void slotChatSessionDeleted (); + + /** + * When our own status change, we need to manually send the presence. + */ + void slotStatusChanged(); + + /** + * ask the user to change the nick, and change it + */ + void slotChangeNick(); + + /** + * a subcontact has been destroyed (may happen when closing kopete) + */ + void slotSubContactDestroyed(Kopete::Contact*); + +private: + + QPtrList mContactList; + QPtrList mMetaContactList; + + JabberGroupChatManager *mManager; + JabberBaseContact *mSelfContact; + QString mNick; +}; + +#endif diff --git a/kopete/protocols/jabber/jabbergroupmembercontact.cpp b/kopete/protocols/jabber/jabbergroupmembercontact.cpp new file mode 100644 index 00000000..2e86b898 --- /dev/null +++ b/kopete/protocols/jabber/jabbergroupmembercontact.cpp @@ -0,0 +1,168 @@ + /* + * jabbergroupmembercontact.cpp - Regular Kopete Jabber protocol contact + * + * Copyright (c) 2002-2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#include "jabbergroupmembercontact.h" + +#include +#include +#include +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabberfiletransfer.h" +#include "jabbergroupchatmanager.h" +#include "jabberchatsession.h" +#include "jabbercontactpool.h" +#include "kopetemetacontact.h" + +/** + * JabberGroupMemberContact constructor + */ +JabberGroupMemberContact::JabberGroupMemberContact (const XMPP::RosterItem &rosterItem, + JabberAccount *account, Kopete::MetaContact * mc) + : JabberBaseContact ( rosterItem, account, mc) +{ + + mc->setDisplayName ( rosterItem.jid().resource() ); + setNickName ( rosterItem.jid().resource() ); + + setFileCapable ( true ); + + mManager = 0; + +} + +/** + * JabberGroupMemberContact destructor + */ +JabberGroupMemberContact::~JabberGroupMemberContact () +{ + if(mManager) + { + mManager->deleteLater(); + } +} + +QPtrList *JabberGroupMemberContact::customContextMenuActions () +{ + + return 0; + +} + +Kopete::ChatSession *JabberGroupMemberContact::manager ( Kopete::Contact::CanCreateFlags canCreate ) +{ + + if ( mManager ) + return mManager; + + if ( !mManager && !canCreate ) + return 0; + + Kopete::ContactPtrList chatMembers; + chatMembers.append ( this ); + + /* + * FIXME: We might have to use the group chat contact here instead of + * the global myself() instance for a correct representation. + */ + mManager = new JabberChatSession ( protocol(), static_cast(account()->myself()), chatMembers ); + connect ( mManager, SIGNAL ( destroyed ( QObject * ) ), this, SLOT ( slotChatSessionDeleted () ) ); + + return mManager; + +} + +void JabberGroupMemberContact::slotChatSessionDeleted () +{ + + mManager = 0; + +} + +void JabberGroupMemberContact::handleIncomingMessage ( const XMPP::Message &message ) +{ + // message type is always chat in a groupchat + QString viewType = "kopete_chatwindow"; + Kopete::Message *newMessage = 0L; + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Received Message Type:" << message.type () << endl; + + /** + * Don't display empty messages, these were most likely just carrying + * event notifications or other payload. + */ + if ( message.body().isEmpty () ) + return; + + Kopete::ChatSession *kmm = manager( Kopete::Contact::CanCreate ); + if(!kmm) + return; + Kopete::ContactPtrList contactList = kmm->members(); + + // check for errors + if ( message.type () == "error" ) + { + newMessage = new Kopete::Message( message.timeStamp (), this, contactList, + i18n("Your message could not be delivered: \"%1\", Reason: \"%2\""). + arg ( message.body () ).arg ( message.error().text ), + message.subject(), Kopete::Message::Inbound, Kopete::Message::PlainText, viewType ); + } + else + { + // retrieve and reformat body + QString body = message.body (); + + if( !message.xencrypted().isEmpty () ) + { + body = QString ("-----BEGIN PGP MESSAGE-----\n\n") + message.xencrypted () + QString ("\n-----END PGP MESSAGE-----\n"); + } + + // convert XMPP::Message into Kopete::Message + newMessage = new Kopete::Message ( message.timeStamp (), this, contactList, body, + message.subject (), Kopete::Message::Inbound, + Kopete::Message::PlainText, viewType ); + } + + // append message to manager + kmm->appendMessage ( *newMessage ); + + delete newMessage; + +} + +void JabberGroupMemberContact::sendFile ( const KURL &sourceURL, const QString &/*fileName*/, uint /*fileSize*/ ) +{ + QString filePath; + + // if the file location is null, then get it from a file open dialog + if ( !sourceURL.isValid () ) + filePath = KFileDialog::getOpenFileName( QString::null , "*", 0L, i18n ( "Kopete File Transfer" ) ); + else + filePath = sourceURL.path(-1); + + QFile file ( filePath ); + + if ( file.exists () ) + { + // send the file + new JabberFileTransfer ( account (), this, filePath ); + } + +} + + +#include "jabbergroupmembercontact.moc" diff --git a/kopete/protocols/jabber/jabbergroupmembercontact.h b/kopete/protocols/jabber/jabbergroupmembercontact.h new file mode 100644 index 00000000..d4ec5b06 --- /dev/null +++ b/kopete/protocols/jabber/jabbergroupmembercontact.h @@ -0,0 +1,80 @@ + /* + * jabbergroupmembercontact.cpp - Kopete Jabber protocol groupchat contact (member) + * + * Copyright (c) 2002-2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERGROUPMEMBERCONTACT_H +#define JABBERGROUPMEMBERCONTACT_H + +#include "jabberbasecontact.h" + +namespace Kopete { class MetaContact; } +class JabberGroupChatManager; +class JabberChatSession; + +class JabberGroupMemberContact : public JabberBaseContact +{ + +Q_OBJECT + +public: + + JabberGroupMemberContact (const XMPP::RosterItem &rosterItem, + JabberAccount *account, Kopete::MetaContact * mc); + + ~JabberGroupMemberContact (); + + /** + * Create custom context menu items for the contact + * FIXME: implement manager version here? + */ + QPtrList *customContextMenuActions (); + + /** + * Return message manager for this instance. + */ + Kopete::ChatSession *manager ( Kopete::Contact::CanCreateFlags canCreate = Kopete::Contact::CannotCreate ); + + /** + * Deal with incoming messages. + */ + void handleIncomingMessage ( const XMPP::Message &message ); + +public slots: + + /** + * This is the JabberContact level slot for sending files. + * + * @param sourceURL The actual KURL of the file you are sending + * @param fileName (Optional) An alternate name for the file - what the + * receiver will see + * @param fileSize (Optional) Size of the file being sent. Used when sending + * a nondeterminate file size (such as over a socket) + */ + virtual void sendFile( const KURL &sourceURL = KURL(), + const QString &fileName = QString::null, uint fileSize = 0L ); + +private slots: + /** + * Catch a dying message manager + */ + void slotChatSessionDeleted (); + +private: + JabberChatSession *mManager; + +}; + +#endif diff --git a/kopete/protocols/jabber/jabberprotocol.cpp b/kopete/protocols/jabber/jabberprotocol.cpp new file mode 100644 index 00000000..ea2e8039 --- /dev/null +++ b/kopete/protocols/jabber/jabberprotocol.cpp @@ -0,0 +1,345 @@ + /* + * jabberprotocol.cpp - Base class for the Kopete Jabber protocol + * + * Copyright (c) 2002-2003 by Till Gerken + * Copyright (c) 2002 by Daniel Stone + * Copyright (c) 2006 by Olivier Goffart + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "im.h" +#include "xmpp.h" + +#include + +#include "kopetecontact.h" +#include "kopetecontactlist.h" +#include "kopetemetacontact.h" +#include "kopetechatsession.h" +#include "kopeteonlinestatusmanager.h" +#include "kopeteaway.h" +#include "kopeteglobal.h" +#include "kopeteprotocol.h" +#include "kopeteplugin.h" +#include "kopeteaccountmanager.h" +#include "addcontactpage.h" +#include "kopetecommandhandler.h" + +#include "jabbercontact.h" +#include "jabberaddcontactpage.h" +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabbereditaccountwidget.h" +#include "jabbercapabilitiesmanager.h" +#include "jabbertransport.h" +#include "dlgjabbersendraw.h" +#include "dlgjabberservices.h" +#include "dlgjabberchatjoin.h" +#include "dlgjabberregister.h" + +JabberProtocol *JabberProtocol::protocolInstance = 0; + +typedef KGenericFactory JabberProtocolFactory; + +K_EXPORT_COMPONENT_FACTORY( kopete_jabber, JabberProtocolFactory( "kopete_jabber" ) ) + +JabberProtocol::JabberProtocol (QObject * parent, const char *name, const QStringList &) +: Kopete::Protocol( JabberProtocolFactory::instance(), parent, name ), + JabberKOSChatty(Kopete::OnlineStatus::Online, 100, this, JabberFreeForChat, "jabber_chatty", i18n ("Free for Chat"), i18n ("Free for Chat"), Kopete::OnlineStatusManager::FreeForChat, Kopete::OnlineStatusManager::HasAwayMessage ), + JabberKOSOnline(Kopete::OnlineStatus::Online, 90, this, JabberOnline, QString::null, i18n ("Online"), i18n ("Online"), Kopete::OnlineStatusManager::Online, Kopete::OnlineStatusManager::HasAwayMessage ), + JabberKOSAway(Kopete::OnlineStatus::Away, 80, this, JabberAway, "contact_away_overlay", i18n ("Away"), i18n ("Away"), Kopete::OnlineStatusManager::Away, Kopete::OnlineStatusManager::HasAwayMessage), + JabberKOSXA(Kopete::OnlineStatus::Away, 70, this, JabberXA, "contact_xa_overlay", i18n ("Extended Away"), i18n ("Extended Away"), 0, Kopete::OnlineStatusManager::HasAwayMessage), + JabberKOSDND(Kopete::OnlineStatus::Away, 60, this, JabberDND, "contact_busy_overlay", i18n ("Do not Disturb"), i18n ("Do not Disturb"), Kopete::OnlineStatusManager::Busy, Kopete::OnlineStatusManager::HasAwayMessage), + JabberKOSOffline(Kopete::OnlineStatus::Offline, 50, this, JabberOffline, QString::null, i18n ("Offline") ,i18n ("Offline"), Kopete::OnlineStatusManager::Offline, Kopete::OnlineStatusManager::HasAwayMessage ), + JabberKOSInvisible(Kopete::OnlineStatus::Invisible, 40, this, JabberInvisible, "contact_invisible_overlay", i18n ("Invisible") ,i18n ("Invisible"), Kopete::OnlineStatusManager::Invisible), + JabberKOSConnecting(Kopete::OnlineStatus::Connecting, 30, this, JabberConnecting, "jabber_connecting", i18n("Connecting")), + propLastSeen(Kopete::Global::Properties::self()->lastSeen()), + propAwayMessage(Kopete::Global::Properties::self()->awayMessage()), + propFirstName(Kopete::Global::Properties::self()->firstName()), + propLastName(Kopete::Global::Properties::self()->lastName()), + propFullName(Kopete::Global::Properties::self()->fullName()), + propEmailAddress(Kopete::Global::Properties::self()->emailAddress()), + propPrivatePhone(Kopete::Global::Properties::self()->privatePhone()), + propPrivateMobilePhone(Kopete::Global::Properties::self()->privateMobilePhone()), + propWorkPhone(Kopete::Global::Properties::self()->workPhone()), + propWorkMobilePhone(Kopete::Global::Properties::self()->workMobilePhone()), + propNickName(Kopete::Global::Properties::self()->nickName()), + propSubscriptionStatus("jabberSubscriptionStatus", i18n ("Subscription"), QString::null, true, false), + propAuthorizationStatus("jabberAuthorizationStatus", i18n ("Authorization Status"), QString::null, true, false), + propAvailableResources("jabberAvailableResources", i18n ("Available Resources"), "jabber_chatty", false, true), + propVCardCacheTimeStamp("jabberVCardCacheTimeStamp", i18n ("vCard Cache Timestamp"), QString::null, true, false, true), + propPhoto(Kopete::Global::Properties::self()->photo()), + propJid("jabberVCardJid", i18n("Jabber ID"), QString::null, true, false), + propBirthday("jabberVCardBirthday", i18n("Birthday"), QString::null, true, false), + propTimezone("jabberVCardTimezone", i18n("Timezone"), QString::null, true, false), + propHomepage("jabberVCardHomepage", i18n("Homepage"), QString::null, true, false), + propCompanyName("jabberVCardCompanyName", i18n("Company name"), QString::null, true, false), + propCompanyDepartement("jabberVCardCompanyDepartement", i18n("Company Departement"), QString::null, true, false), + propCompanyPosition("jabberVCardCompanyPosition", i18n("Company Position"), QString::null, true, false), + propCompanyRole("jabberVCardCompanyRole", i18n("Company Role"), QString::null, true, false), + propWorkStreet("jabberVCardWorkStreet", i18n("Work Street"), QString::null, true, false), + propWorkExtAddr("jabberVCardWorkExtAddr", i18n("Work Extra Address"), QString::null, true, false), + propWorkPOBox("jabberVCardWorkPOBox", i18n("Work PO Box"), QString::null, true, false), + propWorkCity("jabberVCardWorkCity", i18n("Work City"), QString::null, true, false), + propWorkPostalCode("jabberVCardWorkPostalCode", i18n("Work Postal Code"), QString::null, true, false), + propWorkCountry("jabberVCardWorkCountry", i18n("Work Country"), QString::null, true, false), + propWorkEmailAddress("jabberVCardWorkEmailAddress", i18n("Work Email Address"), QString::null, true, false), + propHomeStreet("jabberVCardHomeStreet", i18n("Home Street"), QString::null, true, false), + propHomeExtAddr("jabberVCardHomeExt", i18n("Home Extra Address"), QString::null, true, false), + propHomePOBox("jabberVCardHomePOBox", i18n("Home PO Box"), QString::null, true, false), + propHomeCity("jabberVCardHomeCity", i18n("Home City"), QString::null, true, false), + propHomePostalCode("jabberVCardHomePostalCode", i18n("Home Postal Code"), QString::null, true, false), + propHomeCountry("jabberVCardHomeCountry", i18n("Home Country"), QString::null, true, false), + propPhoneFax("jabberVCardPhoneFax", i18n("Fax"), QString::null, true, false), + propAbout("jabberVCardAbout", i18n("About"), QString::null, true, false) + +{ + + kdDebug (JABBER_DEBUG_GLOBAL) << "[JabberProtocol] Loading ..." << endl; + + /* This is meant to be a singleton, so we will check if we have + * been loaded before. */ + if (protocolInstance) + { + kdDebug (JABBER_DEBUG_GLOBAL) << "[JabberProtocol] Warning: Protocol already " << "loaded, not initializing again." << endl; + return; + } + + protocolInstance = this; + + addAddressBookField ("messaging/xmpp", Kopete::Plugin::MakeIndexField); + setCapabilities(Kopete::Protocol::FullRTF|Kopete::Protocol::CanSendOffline); + + // Init the Entity Capabilities manager. + capsManager = new JabberCapabilitiesManager; + capsManager->loadCachedInformation(); +} + +JabberProtocol::~JabberProtocol () +{ + //disconnectAll(); + + delete capsManager; + capsManager = 0L; + + /* make sure that the next attempt to load Jabber + * re-initializes the protocol class. */ + protocolInstance = 0L; +} + + + +AddContactPage *JabberProtocol::createAddContactWidget (QWidget * parent, Kopete::Account * i) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << "[Jabber Protocol] Create Add Contact Widget\n" << endl; + return new JabberAddContactPage (i, parent); +} + +KopeteEditAccountWidget *JabberProtocol::createEditAccountWidget (Kopete::Account * account, QWidget * parent) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << "[Jabber Protocol] Edit Account Widget\n" << endl; + JabberAccount *ja=dynamic_cast < JabberAccount * >(account); + if(ja || !account) + return new JabberEditAccountWidget (this,ja , parent); + else + { + JabberTransport *transport = dynamic_cast < JabberTransport * >(account); + if(!transport) + return 0L; + dlgJabberRegister *registerDialog = new dlgJabberRegister (transport->account(), transport->myself()->contactId()); + registerDialog->show (); + registerDialog->raise (); + return 0l; //we make ourself our own dialog, not an editAccountWidget. + } +} + +Kopete::Account *JabberProtocol::createNewAccount (const QString & accountId) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << "[Jabber Protocol] Create New Account. ID: " << accountId << "\n" << endl; + if( Kopete::AccountManager::self()->findAccount( pluginId() , accountId ) ) + return 0L; //the account may already exist if greated just above + + int slash=accountId.find('/'); + if(slash>=0) + { + QString realAccountId=accountId.left(slash); + JabberAccount *realAccount=dynamic_cast(Kopete::AccountManager::self()->findAccount( pluginId() , realAccountId )); + if(!realAccount) //if it doesn't exist yet, create it + { + realAccount = new JabberAccount( this, realAccountId ); + if(!Kopete::AccountManager::self()->registerAccount( realAccount ) ) + return 0L; + } + if(!realAccount) + return 0L; + return new JabberTransport( realAccount , accountId ); + } + else + { + return new JabberAccount (this, accountId); + } +} + +Kopete::OnlineStatus JabberProtocol::resourceToKOS ( const XMPP::Resource &resource ) +{ + + // update to offline by default + Kopete::OnlineStatus status = JabberKOSOffline; + + if ( !resource.status().isAvailable () ) + { + // resource is offline + status = JabberKOSOffline; + } + else + { + if (resource.status ().show ().isEmpty ()) + { + if (resource.status ().isInvisible ()) + { + status = JabberKOSInvisible; + } + else + { + status = JabberKOSOnline; + } + } + else + if (resource.status ().show () == "chat") + { + status = JabberKOSChatty; + } + else if (resource.status ().show () == "away") + { + status = JabberKOSAway; + } + else if (resource.status ().show () == "xa") + { + status = JabberKOSXA; + } + else if (resource.status ().show () == "dnd") + { + status = JabberKOSDND; + } + else if (resource.status ().show () == "online") + { // the ApaSMSAgent sms gateway report status as "online" even if it's not in the RFC 3921 § 2.2.2.1 + // See Bug 129059 + status = JabberKOSOnline; + } + else if (resource.status ().show () == "connecting") + { // this is for kopete internals + status = JabberKOSConnecting; + } + else + { + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Unknown status " << resource.status ().show () << " for contact. One of your contact is probably using a broken client, ask him to report a bug" << endl; + } + } + + return status; + +} + +JabberCapabilitiesManager *JabberProtocol::capabilitiesManager() +{ + return capsManager; +} + +JabberProtocol *JabberProtocol::protocol () +{ + // return current instance + return protocolInstance; +} + +Kopete::Contact *JabberProtocol::deserializeContact (Kopete::MetaContact * metaContact, + const QMap < QString, QString > &serializedData, const QMap < QString, QString > & /* addressBookData */ ) +{ +// kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Deserializing data for metacontact " << metaContact->displayName () << "\n" << endl; + + QString contactId = serializedData["contactId"]; + QString displayName = serializedData["displayName"]; + QString accountId = serializedData["accountId"]; + QString jid = serializedData["JID"]; + + QDict < Kopete::Account > accounts = Kopete::AccountManager::self ()->accounts (this); + Kopete::Account *account = accounts[accountId]; + + if (!account) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "WARNING: Account for contact does not exist, skipping." << endl; + return 0; + } + + JabberTransport *transport = dynamic_cast(account); + if( transport ) + transport->account()->addContact ( jid.isEmpty() ? contactId : jid , metaContact); + else + account->addContact (contactId, metaContact); + return account->contacts()[contactId]; +} + +XMPP::Status JabberProtocol::kosToStatus( const Kopete::OnlineStatus & status , const QString & message ) +{ + XMPP::Status xmppStatus ( "", message ); + + if( status.status() == Kopete::OnlineStatus::Offline ) + { + xmppStatus.setIsAvailable( false ); + } + + switch ( status.internalStatus () ) + { + case JabberProtocol::JabberFreeForChat: + xmppStatus.setShow ( "chat" ); + break; + + case JabberProtocol::JabberOnline: + xmppStatus.setShow ( "" ); + break; + + case JabberProtocol::JabberAway: + xmppStatus.setShow ( "away" ); + break; + + case JabberProtocol::JabberXA: + xmppStatus.setShow ( "xa" ); + break; + + case JabberProtocol::JabberDND: + xmppStatus.setShow ( "dnd" ); + break; + + case JabberProtocol::JabberInvisible: + xmppStatus.setIsInvisible ( true ); + break; + } + return xmppStatus; +} + +#include "jabberprotocol.moc" diff --git a/kopete/protocols/jabber/jabberprotocol.h b/kopete/protocols/jabber/jabberprotocol.h new file mode 100644 index 00000000..798aafb4 --- /dev/null +++ b/kopete/protocols/jabber/jabberprotocol.h @@ -0,0 +1,164 @@ + /* + * jabberprotocol.h - Base class for the Kopete Jabber protocol + * + * Copyright (c) 2002-2003 by Till Gerken + * Copyright (c) 2002 by Daniel Stone + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERPROTOCOL_H +#define JABBERPROTOCOL_H + +#include +#include +#include +#include +#include +#include +#include + +#include "kopetecontact.h" +#include "kopetecontactproperty.h" +#include "kopetemetacontact.h" +#include "kopeteonlinestatus.h" +#include "addcontactpage.h" + +#define JABBER_DEBUG_GLOBAL 14130 +#define JABBER_DEBUG_PROTOCOL 14131 + +namespace XMPP +{ + class Resource; + class Status; +} + +class JabberContact; +class dlgJabberStatus; +class dlgJabberSendRaw; +class JabberCapabilitiesManager; + +class JabberProtocol:public Kopete::Protocol +{ + Q_OBJECT + +public: + /** + * Object constructor and destructor + */ + JabberProtocol (QObject * parent, const char *name, const QStringList &); + ~JabberProtocol (); + + /** + * Creates the "add contact" dialog specific to this protocol + */ + virtual AddContactPage *createAddContactWidget (QWidget * parent, Kopete::Account * i); + virtual KopeteEditAccountWidget *createEditAccountWidget (Kopete::Account * account, QWidget * parent); + virtual Kopete::Account *createNewAccount (const QString & accountId); + + /** + * Deserialize contact data + */ + virtual Kopete::Contact *deserializeContact (Kopete::MetaContact * metaContact, + const QMap < QString, QString > &serializedData, const QMap < QString, QString > &addressBookData); + + enum OnlineStatus { JabberOnline, JabberFreeForChat, JabberAway, JabberXA, JabberDND, + JabberOffline, JabberInvisible, JabberConnecting }; + + const Kopete::OnlineStatus JabberKOSChatty; + const Kopete::OnlineStatus JabberKOSOnline; + const Kopete::OnlineStatus JabberKOSAway; + const Kopete::OnlineStatus JabberKOSXA; + const Kopete::OnlineStatus JabberKOSDND; + const Kopete::OnlineStatus JabberKOSOffline; + const Kopete::OnlineStatus JabberKOSInvisible; + const Kopete::OnlineStatus JabberKOSConnecting; + + const Kopete::ContactPropertyTmpl propLastSeen; + const Kopete::ContactPropertyTmpl propAwayMessage; + const Kopete::ContactPropertyTmpl propFirstName; + const Kopete::ContactPropertyTmpl propLastName; + const Kopete::ContactPropertyTmpl propFullName; + const Kopete::ContactPropertyTmpl propEmailAddress; + const Kopete::ContactPropertyTmpl propPrivatePhone; + const Kopete::ContactPropertyTmpl propPrivateMobilePhone; + const Kopete::ContactPropertyTmpl propWorkPhone; + const Kopete::ContactPropertyTmpl propWorkMobilePhone; + const Kopete::ContactPropertyTmpl propNickName; + const Kopete::ContactPropertyTmpl propSubscriptionStatus; + const Kopete::ContactPropertyTmpl propAuthorizationStatus; + const Kopete::ContactPropertyTmpl propAvailableResources; + const Kopete::ContactPropertyTmpl propVCardCacheTimeStamp; + const Kopete::ContactPropertyTmpl propPhoto; + // extra properties to match with vCard + const Kopete::ContactPropertyTmpl propJid; + const Kopete::ContactPropertyTmpl propBirthday; + const Kopete::ContactPropertyTmpl propTimezone; + const Kopete::ContactPropertyTmpl propHomepage; + const Kopete::ContactPropertyTmpl propCompanyName; + const Kopete::ContactPropertyTmpl propCompanyDepartement; + const Kopete::ContactPropertyTmpl propCompanyPosition; + const Kopete::ContactPropertyTmpl propCompanyRole; + const Kopete::ContactPropertyTmpl propWorkStreet; + const Kopete::ContactPropertyTmpl propWorkExtAddr; + const Kopete::ContactPropertyTmpl propWorkPOBox; + const Kopete::ContactPropertyTmpl propWorkCity; + const Kopete::ContactPropertyTmpl propWorkPostalCode; + const Kopete::ContactPropertyTmpl propWorkCountry; + const Kopete::ContactPropertyTmpl propWorkEmailAddress; + const Kopete::ContactPropertyTmpl propHomeStreet; + const Kopete::ContactPropertyTmpl propHomeExtAddr; + const Kopete::ContactPropertyTmpl propHomePOBox; + const Kopete::ContactPropertyTmpl propHomeCity; + const Kopete::ContactPropertyTmpl propHomePostalCode; + const Kopete::ContactPropertyTmpl propHomeCountry; + const Kopete::ContactPropertyTmpl propPhoneFax; + const Kopete::ContactPropertyTmpl propAbout; + + /** + * This returns our protocol instance + */ + static JabberProtocol *protocol (); + + /** + * Return whether the protocol supports offline messages. + */ + bool canSendOffline() const { return true; } + + /** + * Convert an XMPP::Resource status to a Kopete::OnlineStatus + */ + Kopete::OnlineStatus resourceToKOS ( const XMPP::Resource &resource ); + + /** + * Convert an online status to a XMPP::Status + */ + XMPP::Status kosToStatus( const Kopete::OnlineStatus & status, const QString& message=QString() ); + + /** + * Return the Entity Capabilities(JEP-0115) manager instance. + */ + JabberCapabilitiesManager *capabilitiesManager(); + +private: + /* + * Singleton instance of our protocol class + */ + static JabberProtocol *protocolInstance; + + /** + * Unique Instance of the Entity Capabilities(JEP-0115) manager for Kopete Jabber plugin. + */ + JabberCapabilitiesManager *capsManager; +}; + +#endif diff --git a/kopete/protocols/jabber/jabberresource.cpp b/kopete/protocols/jabber/jabberresource.cpp new file mode 100644 index 00000000..e74a0fa9 --- /dev/null +++ b/kopete/protocols/jabber/jabberresource.cpp @@ -0,0 +1,171 @@ + /* + * jabberresource.cpp + * + * Copyright (c) 2005-2006 by Michaël Larouche + * Copyright (c) 2004 by Till Gerken + * + * Kopete (c) 2001-2006 by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#include "jabberresource.h" + +// Qt includes +#include + +// KDE includes +#include + +// libiris includes +#include +#include + +// Kopete includes +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabbercapabilitiesmanager.h" + +class JabberResource::Private +{ +public: + Private( JabberAccount *t_account, const XMPP::Jid &t_jid, const XMPP::Resource &t_resource ) + : account(t_account), jid(t_jid), resource(t_resource), capsEnabled(false) + { + // Make sure the resource is always set. + jid.setResource(resource.name()); + } + + JabberAccount *account; + XMPP::Jid jid; + XMPP::Resource resource; + + QString clientName, clientSystem; + XMPP::Features supportedFeatures; + bool capsEnabled; +}; + +JabberResource::JabberResource ( JabberAccount *account, const XMPP::Jid &jid, const XMPP::Resource &resource ) + : d( new Private(account, jid, resource) ) +{ + d->capsEnabled = account->protocol()->capabilitiesManager()->capabilitiesEnabled(jid); + + if ( account->isConnected () ) + { + QTimer::singleShot ( account->client()->getPenaltyTime () * 1000, this, SLOT ( slotGetTimedClientVersion () ) ); + if(!d->capsEnabled) + { + QTimer::singleShot ( account->client()->getPenaltyTime () * 1000, this, SLOT ( slotGetDiscoCapabilties () ) ); + } + } +} + +JabberResource::~JabberResource () +{ + delete d; +} + +const XMPP::Jid &JabberResource::jid () const +{ + return d->jid; +} + +const XMPP::Resource &JabberResource::resource () const +{ + return d->resource; +} + +void JabberResource::setResource ( const XMPP::Resource &resource ) +{ + d->resource = resource; + + // Check if the caps are now available. + d->capsEnabled = d->account->protocol()->capabilitiesManager()->capabilitiesEnabled(d->jid); + + emit updated( this ); +} + +const QString &JabberResource::clientName () const +{ + return d->clientName; +} + +const QString &JabberResource::clientSystem () const +{ + return d->clientSystem; +} + +XMPP::Features JabberResource::features() const +{ + if(d->capsEnabled) + { + return d->account->protocol()->capabilitiesManager()->features(d->jid); + } + else + { + return d->supportedFeatures; + } +} + +void JabberResource::slotGetTimedClientVersion () +{ + if ( d->account->isConnected () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Requesting client version for " << d->jid.full () << endl; + + // request client version + XMPP::JT_ClientVersion *task = new XMPP::JT_ClientVersion ( d->account->client()->rootTask () ); + // signal to ourselves when the vCard data arrived + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT ( slotGotClientVersion () ) ); + task->get ( d->jid ); + task->go ( true ); + } +} + +void JabberResource::slotGotClientVersion () +{ + XMPP::JT_ClientVersion *clientVersion = (XMPP::JT_ClientVersion *) sender (); + + if ( clientVersion->success () ) + { + d->clientName = clientVersion->name () + " " + clientVersion->version (); + d->clientSystem = clientVersion->os (); + + emit updated ( this ); + } +} + +void JabberResource:: slotGetDiscoCapabilties () +{ + if ( d->account->isConnected () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Requesting Client Features for " << d->jid.full () << endl; + + XMPP:: JT_DiscoInfo *task = new XMPP::JT_DiscoInfo ( d->account->client()->rootTask () ); + // Retrive features when service discovery is done. + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT (slotGotDiscoCapabilities () ) ); + task->get ( d->jid); + task->go ( true ); + } +} + +void JabberResource::slotGotDiscoCapabilities () +{ + XMPP::JT_DiscoInfo *discoInfo = (XMPP::JT_DiscoInfo *) sender (); + + if ( discoInfo->success () ) + { + d->supportedFeatures = discoInfo->item().features(); + + emit updated ( this ); + } +} + +#include "jabberresource.moc" diff --git a/kopete/protocols/jabber/jabberresource.h b/kopete/protocols/jabber/jabberresource.h new file mode 100644 index 00000000..7b398c09 --- /dev/null +++ b/kopete/protocols/jabber/jabberresource.h @@ -0,0 +1,86 @@ + /* + * jabberresource.h + * + * Copyright (c) 2005-2006 by Michaël Larouche + * Copyright (c) 2004 by Till Gerken + * + * Kopete (c) 2001-2006 by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERRESOURCE_H +#define JABBERRESOURCE_H + +/** + * Container class for a contact's resource + */ + +#include +#include + +class JabberAccount; + +namespace XMPP +{ +class Resource; +class Jid; +class Features; +} + +class JabberResource : public QObject +{ +Q_OBJECT + +public: + /** + * Create a new Jabber resource. + */ + JabberResource (JabberAccount *account, const XMPP::Jid &jid, const XMPP::Resource &resource); + ~JabberResource (); + + const XMPP::Jid &jid() const; + const XMPP::Resource &resource() const; + + void setResource ( const XMPP::Resource &resource ); + + /** + * Return the client name for this resource. + * @return the client name + */ + const QString &clientName () const; + /** + * Return the client system for this resource. + * @return the client system. + */ + const QString &clientSystem () const; + + /** + * Get the available features for this resource. + */ + XMPP::Features features() const; + +signals: + void updated ( JabberResource * ); + +private slots: + void slotGetTimedClientVersion (); + void slotGotClientVersion (); + void slotGetDiscoCapabilties (); + void slotGotDiscoCapabilities (); + +private: + class Private; + Private *d; +}; + +#endif + +// vim: set noet ts=4 sts=4 tw=4: diff --git a/kopete/protocols/jabber/jabberresourcepool.cpp b/kopete/protocols/jabber/jabberresourcepool.cpp new file mode 100644 index 00000000..9d953ce6 --- /dev/null +++ b/kopete/protocols/jabber/jabberresourcepool.cpp @@ -0,0 +1,394 @@ + /* + * jabberresourcepool.cpp + * + * Copyright (c) 2004 by Till Gerken + * Copyright (c) 2006 by Michaël Larouche + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#include +#include + +#include "jabberresourcepool.h" +#include "jabberresource.h" +#include "jabbercontactpool.h" +#include "jabberbasecontact.h" +#include "jabberaccount.h" +#include "jabberprotocol.h" +#include "jabbercapabilitiesmanager.h" + +/** + * This resource will be returned if no other resource + * for a given JID can be found. It's an empty offline + * resource. + */ +XMPP::Resource JabberResourcePool::EmptyResource ( "", XMPP::Status ( "", "", 0, false ) ); + +class JabberResourcePool::Private +{ +public: + Private(JabberAccount *pAccount) + : account(pAccount) + { + // automatically delete all resources in the pool upon removal + pool.setAutoDelete(true); + } + + QPtrList pool; + QPtrList lockList; + + /** + * Pointer to the JabberAccount instance. + */ + JabberAccount *account; +}; + +JabberResourcePool::JabberResourcePool ( JabberAccount *account ) + : d(new Private(account)) +{} + +JabberResourcePool::~JabberResourcePool () +{ + delete d; +} + +void JabberResourcePool::slotResourceDestroyed (QObject *sender) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Resource has been destroyed, collecting the pieces." << endl; + + JabberResource *oldResource = static_cast(sender); + + // remove this resource from the lock list if it existed + d->lockList.remove ( oldResource ); +} + +void JabberResourcePool::slotResourceUpdated ( JabberResource *resource ) +{ + QPtrList list = d->account->contactPool()->findRelevantSources ( resource->jid () ); + + for(JabberBaseContact *mContact = list.first (); mContact; mContact = list.next ()) + { + mContact->updateResourceList (); + } + + // Update capabilities + if( !resource->resource().status().capsNode().isEmpty() ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Updating capabilities for JID: " << resource->jid().full() << endl; + d->account->protocol()->capabilitiesManager()->updateCapabilities( d->account, resource->jid(), resource->resource().status() ); + } +} + +void JabberResourcePool::notifyRelevantContacts ( const XMPP::Jid &jid ) +{ + QPtrList list = d->account->contactPool()->findRelevantSources ( jid ); + + for(JabberBaseContact *mContact = list.first (); mContact; mContact = list.next ()) + { + mContact->reevaluateStatus (); + } +} + +void JabberResourcePool::addResource ( const XMPP::Jid &jid, const XMPP::Resource &resource ) +{ + // see if the resource already exists + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( (mResource->jid().userHost().lower() == jid.userHost().lower()) && (mResource->resource().name().lower() == resource.name().lower()) ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Updating existing resource " << resource.name() << " for " << jid.userHost() << endl; + + // It exists, update it. Don't do a "lazy" update by deleting + // it here and readding it with new parameters later on, + // any possible lockings to this resource will get lost. + mResource->setResource ( resource ); + + // we still need to notify the contact in case the status + // of this resource changed + notifyRelevantContacts ( jid ); + + return; + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Adding new resource " << resource.name() << " for " << jid.userHost() << endl; + + // Update initial capabilities if available. + // Called before creating JabberResource so JabberResource wouldn't ask for disco information. + if( !resource.status().capsNode().isEmpty() ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Initial update of capabilities for JID: " << jid.full() << endl; + d->account->protocol()->capabilitiesManager()->updateCapabilities( d->account, jid, resource.status() ); + } + + // create new resource instance and add it to the dictionary + JabberResource *newResource = new JabberResource(d->account, jid, resource); + connect ( newResource, SIGNAL ( destroyed (QObject *) ), this, SLOT ( slotResourceDestroyed (QObject *) ) ); + connect ( newResource, SIGNAL ( updated (JabberResource *) ), this, SLOT ( slotResourceUpdated (JabberResource *) ) ); + d->pool.append ( newResource ); + + // send notifications out to the relevant contacts that + // a new resource is available for them + notifyRelevantContacts ( jid ); +} + +void JabberResourcePool::removeResource ( const XMPP::Jid &jid, const XMPP::Resource &resource ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing resource " << resource.name() << " from " << jid.userHost() << endl; + + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( (mResource->jid().userHost().lower() == jid.userHost().lower()) && (mResource->resource().name().lower() == resource.name().lower()) ) + { + d->pool.remove (); + notifyRelevantContacts ( jid ); + return; + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "WARNING: No match found!" << endl; +} + +void JabberResourcePool::removeAllResources ( const XMPP::Jid &jid ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing all resources for " << jid.userHost() << endl; + + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( mResource->jid().userHost().lower() == jid.userHost().lower() ) + { + // only remove preselected resource in case there is one + if ( jid.resource().isEmpty () || ( jid.resource().lower () == mResource->resource().name().lower () ) ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing resource " << jid.userHost() << "/" << mResource->resource().name () << endl; + d->pool.remove (); + } + } + } +} + +void JabberResourcePool::clear () +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Clearing the resource pool." << endl; + + /* + * Since many contacts can have multiple resources, we can't simply delete + * each resource and trigger a notification upon each deletion. This would + * cause lots of status updates in the GUI and create unnecessary flicker + * and API traffic. Instead, collect all JIDs, clear the dictionary + * and then notify all JIDs after the resources have been deleted. + */ + + QStringList jidList; + + for ( JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next () ) + { + jidList += mResource->jid().full (); + } + + /* + * Since mPool has autodeletion enabled, this will cause all + * items to be deleted. The lock list will be cleaned automatically. + */ + d->pool.clear (); + + /* + * Now go through the list of JIDs and notify each contact + * of its status change + */ + for ( QStringList::Iterator it = jidList.begin (); it != jidList.end (); ++it ) + { + notifyRelevantContacts ( XMPP::Jid ( *it ) ); + } + +} + +void JabberResourcePool::lockToResource ( const XMPP::Jid &jid, const XMPP::Resource &resource ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Locking " << jid.full() << " to " << resource.name() << endl; + + // remove all existing locks first + removeLock ( jid ); + + // find the resource in our dictionary that matches + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( (mResource->jid().userHost().lower() == jid.full().lower()) && (mResource->resource().name().lower() == resource.name().lower()) ) + { + d->lockList.append ( mResource ); + return; + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "WARNING: No match found!" << endl; +} + +void JabberResourcePool::removeLock ( const XMPP::Jid &jid ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing resource lock for " << jid.userHost() << endl; + + // find the resource in our dictionary that matches + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( (mResource->jid().userHost().lower() == jid.userHost().lower()) ) + { + d->lockList.remove (mResource); + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "No locks found." << endl; +} + +JabberResource *JabberResourcePool::lockedJabberResource( const XMPP::Jid &jid ) +{ + // check if the JID already carries a resource, then we will have to use that one + if ( !jid.resource().isEmpty () ) + { + // we are subscribed to a JID, find the according resource in the pool + for ( JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next () ) + { + if ( ( mResource->jid().userHost().lower () == jid.userHost().lower () ) && ( mResource->resource().name () == jid.resource () ) ) + { + return mResource; + } + } + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "WARNING: No resource found in pool, returning as offline." << endl; + + return 0L; + } + + // see if we have a locked resource + for(JabberResource *mResource = d->lockList.first (); mResource; mResource = d->lockList.next ()) + { + if ( mResource->jid().userHost().lower() == jid.userHost().lower() ) + { + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Current lock for " << jid.userHost () << " is '" << mResource->resource().name () << "'" << endl; + return mResource; + } + } + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "No lock available for " << jid.userHost () << endl; + + // there's no locked resource, return an empty resource + return 0L; +} + +const XMPP::Resource &JabberResourcePool::lockedResource ( const XMPP::Jid &jid ) +{ + JabberResource *resource = lockedJabberResource( jid ); + return (resource) ? resource->resource() : EmptyResource; +} + +JabberResource *JabberResourcePool::bestJabberResource( const XMPP::Jid &jid, bool honourLock ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Determining best resource for " << jid.full () << endl; + + if ( honourLock ) + { + // if we are locked to a certain resource, always return that one + JabberResource *mResource = lockedJabberResource ( jid ); + if ( mResource ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "We have a locked resource '" << mResource->resource().name () << "' for " << jid.full () << endl; + return mResource; + } + } + + JabberResource *bestResource = 0L; + JabberResource *currentResource = 0L; + + for(currentResource = d->pool.first (); currentResource; currentResource = d->pool.next ()) + { + // make sure we are only looking up resources for the specified JID + if ( currentResource->jid().userHost().lower() != jid.userHost().lower() ) + { + continue; + } + + // take first resource if no resource has been chosen yet + if(!bestResource) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Taking '" << currentResource->resource().name () << "' as first available resource." << endl; + + bestResource = currentResource; + continue; + } + + if(currentResource->resource().priority() > bestResource->resource().priority()) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Using '" << currentResource->resource().name () << "' due to better priority." << endl; + + // got a better match by priority + bestResource = currentResource; + } + else + { + if(currentResource->resource().priority() == bestResource->resource().priority()) + { + if(currentResource->resource().status().timeStamp() > bestResource->resource().status().timeStamp()) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Using '" << currentResource->resource().name () << "' due to better timestamp." << endl; + + // got a better match by timestamp (priorities are equal) + bestResource = currentResource; + } + } + } + } + + return (bestResource) ? bestResource : 0L; +} + +const XMPP::Resource &JabberResourcePool::bestResource ( const XMPP::Jid &jid, bool honourLock ) +{ + JabberResource *bestResource = bestJabberResource( jid, honourLock); + return (bestResource) ? bestResource->resource() : EmptyResource; +} + +//TODO: Find Resources based on certain Features. +void JabberResourcePool::findResources ( const XMPP::Jid &jid, JabberResourcePool::ResourceList &resourceList ) +{ + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( mResource->jid().userHost().lower() == jid.userHost().lower() ) + { + // we found a resource for the JID, let's see if the JID already contains a resource + if ( !jid.resource().isEmpty() && ( jid.resource().lower() != mResource->resource().name().lower() ) ) + // the JID contains a resource but it's not the one we have in the dictionary, + // thus we have to ignore this resource + continue; + + resourceList.append ( mResource ); + } + } +} + +void JabberResourcePool::findResources ( const XMPP::Jid &jid, XMPP::ResourceList &resourceList ) +{ + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( mResource->jid().userHost().lower() == jid.userHost().lower() ) + { + // we found a resource for the JID, let's see if the JID already contains a resource + if ( !jid.resource().isEmpty() && ( jid.resource().lower() != mResource->resource().name().lower() ) ) + // the JID contains a resource but it's not the one we have in the dictionary, + // thus we have to ignore this resource + continue; + + resourceList.append ( mResource->resource () ); + } + } +} + +#include "jabberresourcepool.moc" diff --git a/kopete/protocols/jabber/jabberresourcepool.h b/kopete/protocols/jabber/jabberresourcepool.h new file mode 100644 index 00000000..a6cefcde --- /dev/null +++ b/kopete/protocols/jabber/jabberresourcepool.h @@ -0,0 +1,129 @@ + /* + * jabberresourcepool.h + * + * Copyright (c) 2004 by Till Gerken + * Copyright (c) 2006 by Michaël Larouche + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * 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. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERRESOURCEPOOL_H +#define JABBERRESOURCEPOOL_H + +#include +#include + +class JabberResource; +class JabberAccount; + +/** + * @author Till Gerken + * @author Michaël Larouche + */ +class JabberResourcePool : public QObject +{ + Q_OBJECT +public: + static XMPP::Resource EmptyResource; + + typedef QPtrList ResourceList; + + /** + * Default constructor + */ + JabberResourcePool ( JabberAccount *account ); + + /** + * Default destructor + */ + ~JabberResourcePool(); + + /** + * Notify all relevant contacts in case + * a resource has been added, updated or removed. + */ + void notifyRelevantContacts ( const XMPP::Jid &jid ); + + /** + * Add a resource to the pool + */ + void addResource ( const XMPP::Jid &jid, const XMPP::Resource &resource ); + + /** + * Remove a resource from the pool + */ + void removeResource ( const XMPP::Jid &jid, const XMPP::Resource &resource ); + + /** + * Remove all resources for a given address from the pool + * NOTE: Since this method is mainly used for housekeeping, + * it does NOT notify any contacts. + */ + void removeAllResources ( const XMPP::Jid &jid ); + + /** + * Remove all resources from the pool + */ + void clear (); + + /** + * Lock to a certain resource + */ + void lockToResource ( const XMPP::Jid &jid, const XMPP::Resource &resource ); + + /** + * Remove a resource lock + */ + void removeLock ( const XMPP::Jid &jid ); + + /** + * Return the JabberResource instance for the locked resource, if any. + */ + JabberResource *lockedJabberResource( const XMPP::Jid &jid ); + + /** + * Return currently locked resource, if any + */ + const XMPP::Resource &lockedResource ( const XMPP::Jid &jid ); + + /** + * Return a usable JabberResource for a given JID. + * + * @param jid Jid to look for the best resource. + * @param honourLock Honour the resource locked by the user. + * + * @return a JabberResource instance. + */ + JabberResource *bestJabberResource( const XMPP::Jid &jid, bool honourLock = true ); + + /** + * Return usable resource for a given JID + * Matches by userHost(), honours locks for a JID by default + */ + const XMPP::Resource &bestResource ( const XMPP::Jid &jid, bool honourLock = true ); + + /** + * Find all resources that exist for a given JID + */ + void findResources ( const XMPP::Jid &jid, JabberResourcePool::ResourceList &resourceList ); + void findResources ( const XMPP::Jid &jid, XMPP::ResourceList &resourceList ); + +private slots: + void slotResourceDestroyed ( QObject *sender ); + void slotResourceUpdated ( JabberResource *resource ); + +private: + class Private; + Private *d; +}; + +#endif diff --git a/kopete/protocols/jabber/jabbertransport.cpp b/kopete/protocols/jabber/jabbertransport.cpp new file mode 100644 index 00000000..e7a8e7d3 --- /dev/null +++ b/kopete/protocols/jabber/jabbertransport.cpp @@ -0,0 +1,345 @@ + /* + + Copyright (c) 2006 by Olivier Goffart + + Kopete (c) 2006 by the Kopete developers + + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* + */ + +#include "jabbertransport.h" +#include "jabbercontact.h" +#include "jabberaccount.h" +#include "jabberprotocol.h" +#include "jabbercontactpool.h" +#include "jabberchatsession.h" + +#include +#include +#include + +#include + + +#include +#include +#include +#include +#include +#include + +#include "xmpp_tasks.h" + +JabberTransport::JabberTransport (JabberAccount * parentAccount, const XMPP::RosterItem & item, const QString& gateway_type) + : Kopete::Account ( parentAccount->protocol(), parentAccount->accountId()+"/"+ item.jid().bare() ) +{ + m_status=Creating; + m_account = parentAccount; + m_account->addTransport( this,item.jid().bare() ); + + JabberContact *myContact = m_account->contactPool()->addContact ( item , Kopete::ContactList::self()->myself(), false ); + setMyself( myContact ); + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << accountId() <<" transport created: myself: " << myContact << endl; + + setColor( account()->color() ); + +#if KOPETE_IS_VERSION(0,11,51) //setCustomIcon is new in kopete 0.12 + QString cIcon; + if(gateway_type=="msn") + cIcon="jabber_gateway_msn"; + else if(gateway_type=="icq") + cIcon="jabber_gateway_icq"; + else if(gateway_type=="aim") + cIcon="jabber_gateway_aim"; + else if(gateway_type=="yahoo") + cIcon="jabber_gateway_yahoo"; + else if(gateway_type=="sms") + cIcon="jabber_gateway_sms"; + else if(gateway_type=="gadu-gadu") + cIcon="jabber_gateway_gadu"; + else if(gateway_type=="smtp") + cIcon="jabber_gateway_smtp"; + else if(gateway_type=="http-ws") + cIcon="jabber_gateway_http-ws"; + else if(gateway_type=="qq") + cIcon="jabber_gateway_qq"; + else if(gateway_type=="tlen") + cIcon="jabber_gateway_tlen"; + else if(gateway_type=="irc") //NOTE: this is not official + cIcon="irc_protocol"; + + if( !cIcon.isEmpty() ) + setCustomIcon( cIcon ); +#endif + + configGroup()->writeEntry("GatewayJID" , item.jid().full() ); + + QTimer::singleShot(0, this, SLOT(eatContacts())); + + m_status=Normal; +} + +JabberTransport::JabberTransport( JabberAccount * parentAccount, const QString & _accountId ) + : Kopete::Account ( parentAccount->protocol(), _accountId ) +{ + m_status=Creating; + m_account = parentAccount; + + const QString contactJID_s = configGroup()->readEntry("GatewayJID"); + + if(contactJID_s.isEmpty()) + { + kdError(JABBER_DEBUG_GLOBAL) << k_funcinfo << _accountId <<": GatewayJID is empty: MISCONFIGURATION (have you used Kopete 0.12 beta ?)" << endl; + } + + XMPP::Jid contactJID= XMPP::Jid( contactJID_s ); + + m_account->addTransport( this, contactJID.bare() ); + + JabberContact *myContact = m_account->contactPool()->addContact ( contactJID , Kopete::ContactList::self()->myself(), false ); + setMyself( myContact ); + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << accountId() <<" transport created: myself: " << myContact << endl; + + m_status=Normal; +} + + + + +JabberTransport::~JabberTransport () +{ + m_account->removeTransport( myself()->contactId() ); +} + +KActionMenu *JabberTransport::actionMenu () +{ + KActionMenu *menu = new KActionMenu( accountId(), myself()->onlineStatus().iconFor( this ), this ); + QString nick = myself()->property( Kopete::Global::Properties::self()->nickName()).value().toString(); + + menu->popupMenu()->insertTitle( myself()->onlineStatus().iconFor( myself() ), + nick.isNull() ? accountLabel() : i18n( "%2 <%1>" ).arg( accountLabel(), nick ) + ); + + QPtrList *customActions = myself()->customContextMenuActions( ); + if( customActions && !customActions->isEmpty() ) + { + menu->popupMenu()->insertSeparator(); + + for( KAction *a = customActions->first(); a; a = customActions->next() ) + a->plug( menu->popupMenu() ); + } + delete customActions; + + return menu; + +/* KActionMenu *m_actionMenu = Kopete::Account::actionMenu(); + + m_actionMenu->popupMenu()->insertSeparator(); + + m_actionMenu->insert(new KAction (i18n ("Join Groupchat..."), "jabber_group", 0, + this, SLOT (slotJoinNewChat ()), this, "actionJoinChat")); + + m_actionMenu->popupMenu()->insertSeparator(); + + m_actionMenu->insert ( new KAction ( i18n ("Services..."), "jabber_serv_on", 0, + this, SLOT ( slotGetServices () ), this, "actionJabberServices") ); + + m_actionMenu->insert ( new KAction ( i18n ("Send Raw Packet to Server..."), "mail_new", 0, + this, SLOT ( slotSendRaw () ), this, "actionJabberSendRaw") ); + + m_actionMenu->insert ( new KAction ( i18n ("Edit User Info..."), "identity", 0, + this, SLOT ( slotEditVCard () ), this, "actionEditVCard") ); + + return m_actionMenu;*/ +} + + +bool JabberTransport::createContact (const QString & contactId, Kopete::MetaContact * metaContact) +{ +#if 0 //TODO + // collect all group names + QStringList groupNames; + Kopete::GroupList groupList = metaContact->groups(); + for(Kopete::Group *group = groupList.first(); group; group = groupList.next()) + groupNames += group->displayName(); + + XMPP::Jid jid ( contactId ); + XMPP::RosterItem item ( jid ); + item.setName ( metaContact->displayName () ); + item.setGroups ( groupNames ); + + // this contact will be created with the "dirty" flag set + // (it will get reset if the contact appears in the roster during connect) + JabberContact *contact = contactPool()->addContact ( item, metaContact, true ); + + return ( contact != 0 ); +#endif + return false; +} + + +void JabberTransport::setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason) +{ +#if 0 + if( status.status() == Kopete::OnlineStatus::Offline ) + { + disconnect( Kopete::Account::Manual ); + return; + } + + if( isConnecting () ) + { + errorConnectionInProgress (); + return; + } + + XMPP::Status xmppStatus ( "", reason ); + + switch ( status.internalStatus () ) + { + case JabberProtocol::JabberFreeForChat: + xmppStatus.setShow ( "chat" ); + break; + + case JabberProtocol::JabberOnline: + xmppStatus.setShow ( "" ); + break; + + case JabberProtocol::JabberAway: + xmppStatus.setShow ( "away" ); + break; + + case JabberProtocol::JabberXA: + xmppStatus.setShow ( "xa" ); + break; + + case JabberProtocol::JabberDND: + xmppStatus.setShow ( "dnd" ); + break; + + case JabberProtocol::JabberInvisible: + xmppStatus.setIsInvisible ( true ); + break; + } + + if ( !isConnected () ) + { + // we are not connected yet, so connect now + m_initialPresence = xmppStatus; + connect (); + } + else + { + setPresence ( xmppStatus ); + } +#endif +} + +JabberProtocol * JabberTransport::protocol( ) const +{ + return m_account->protocol(); +} + +bool JabberTransport::removeAccount( ) +{ + if(m_status == Removing || m_status == AccountRemoved) + return true; //so it can be deleted + + if (!account()->isConnected()) + { + account()->errorConnectFirst (); + return false; + } + + m_status = Removing; + XMPP::JT_Register *task = new XMPP::JT_Register ( m_account->client()->rootTask () ); + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT ( removeAllContacts() ) ); + + //JabberContact *my=static_cast(myself()); + task->unreg ( myself()->contactId() ); + task->go ( true ); + return false; //delay the removal +} + +void JabberTransport::removeAllContacts( ) +{ +// XMPP::JT_Register * task = (XMPP::JT_Register *) sender (); + +/* if ( ! task->success ()) + KMessageBox::queuedMessageBox ( 0L, KMessageBox::Error, + i18n ("An error occured when trying to remove the transport:\n%1").arg(task->statusString()), + i18n ("Jabber Service Unregistration")); + */ //we don't really care, we remove everithing anyway. + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "delete all contacts of the transport"<< endl; + QDictIterator it( contacts() ); + for( ; it.current(); ++it ) + { + XMPP::JT_Roster * rosterTask = new XMPP::JT_Roster ( account()->client()->rootTask () ); + rosterTask->remove ( static_cast(it.current())->rosterItem().jid() ); + rosterTask->go ( true ); + } + m_status = Removing; //in theory that's already our status + Kopete::AccountManager::self()->removeAccount( this ); //this will delete this +} + +QString JabberTransport::legacyId( const XMPP::Jid & jid ) +{ + if(jid.node().isEmpty()) + return QString(); + QString node = jid.node(); + return node.replace("%","@"); +} + +void JabberTransport::jabberAccountRemoved( ) +{ + m_status = AccountRemoved; + Kopete::AccountManager::self()->removeAccount( this ); //this will delete this +} + +void JabberTransport::eatContacts( ) +{ + /* + * "Gateway Contact Eating" (c)(r)(tm)(g)(o)(f) + * this comes directly from my mind into the kopete code. + * principle: - the transport is hungry + * - it will eat contacts which belong to him + * - the contact will die + * - a new contact will born, with the same characteristics, but owned by the transport + * - Olivier 2006-01-17 - + */ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << endl; + QDict cts=account()->contacts(); + QDictIterator it( cts ); + for( ; it.current(); ++it ) + { + JabberContact *contact=dynamic_cast(it.current()); + if( contact && !contact->transport() && contact->rosterItem().jid().domain() == myself()->contactId() && contact != account()->myself()) + { + XMPP::RosterItem item=contact->rosterItem(); + Kopete::MetaContact *mc=contact->metaContact(); + Kopete::OnlineStatus status = contact->onlineStatus(); + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << item.jid().full() << " will be soon eat - " << contact << endl; + delete contact; + Kopete::Contact *c2=account()->contactPool()->addContact( item , mc , false ); //not sure this is false; + if(c2) + c2->setOnlineStatus( status ); //put back the old status + } + } +} + + + +#include "jabbertransport.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/protocols/jabber/jabbertransport.h b/kopete/protocols/jabber/jabbertransport.h new file mode 100644 index 00000000..b26fd9c0 --- /dev/null +++ b/kopete/protocols/jabber/jabbertransport.h @@ -0,0 +1,138 @@ + /* + + Copyright (c) 2006 by Olivier Goffart + + Kopete (c) 2006 by the Kopete developers + + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* + */ + +#ifndef JABBERTRANSPORT_H +#define JABBERTRANSPORT_H + +#ifdef HAVE_CONFIG_H +#include +#endif + + +#include + + +namespace XMPP { + class Jid; + class RosterItem; +} +class JabberAccount; +class JabberProtocol; + +/** + * this class handle a jabber gateway + * @author Olivier Goffart */ + +class JabberTransport : public Kopete::Account +{ + Q_OBJECT + +public: + /** + * constructor called when the transport is created by info from server (i.e not when loading kopete) + * @param parentAccount is the parent jabber account. + * @param item is the roster item of the gateway + * @param gateway_type eg: "msn" or "icq" only used when the account is not loaded from config file for determining the icon + */ + JabberTransport (JabberAccount * parentAccount, const XMPP::RosterItem &item, const QString& gateway_type=QString()); + + /** + * constructor called when the transport is loaded from config + * @param parentAccount is the parent jabber account. + * @param accountId is the accountId + */ + JabberTransport (JabberAccount * parentAccount, const QString &accountId ); + + ~JabberTransport (); + + /** Returns the action menu for this account. */ + virtual KActionMenu *actionMenu (); + + /** the parent account */ + JabberAccount *account() const + { return m_account; } + + /* to get the protocol from the account */ + JabberProtocol *protocol () const; + + void connect( const Kopete::OnlineStatus& ) {} + virtual void disconnect( ) {} + + /** + * called when the account is removed in the config ui + * will remove the subscription + */ + virtual bool removeAccount(); + + + enum TransportStatus { Normal , Creating, Removing , AccountRemoved }; + TransportStatus transportStatus() { return m_status; }; + + /** + * return the legacyId conrresponding to the jid + * example: jhon%msn.com@msn.foojabber.org -> jhon@msn.com + */ + QString legacyId( const XMPP::Jid &jid ); + +public slots: + + /* Reimplemented from Kopete::Account */ + void setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason = QString::null); + + /** + * the account has been unregistered. + * loop over all contact and remove them + */ + void removeAllContacts(); + + /** + * the JabberAccount has been removed from Kopete, remove this account also + */ + void jabberAccountRemoved(); + + /** + * "eat" all contact in the account that have the same domain as us. + */ + void eatContacts(); + +protected: + /** + * Create a new contact in the specified metacontact + * + * You shouldn't ever call this method yourself, For adding contacts see @ref addContact() + * + * This method is called by @ref Kopete::Account::addContact() in this method, you should + * simply create the new custom @ref Kopete::Contact in the given metacontact. You should + * NOT add the contact to the server here as this method gets only called when synchronizing + * the contact list on disk with the one in memory. As such, all created contacts from this + * method should have the "dirty" flag set. + * + * This method should simply be used to intantiate the new contact, everything else + * (updating the GUI, parenting to meta contact, etc.) is being taken care of. + * + * @param contactId The unique ID for this protocol + * @param parentContact The metacontact to add this contact to + */ + virtual bool createContact (const QString & contactID, Kopete::MetaContact * parentContact); + +private: + JabberAccount *m_account; + TransportStatus m_status; + +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/DESIGN b/kopete/protocols/jabber/jingle/DESIGN new file mode 100644 index 00000000..b1cbd666 --- /dev/null +++ b/kopete/protocols/jabber/jingle/DESIGN @@ -0,0 +1,121 @@ +Voice Use cases: +---------------- + +In JabberAccount: +-Account is connected: +* Init the JingleSessionManager (accessible via account()->jingleSessionManager()) +* Add voice extension to client features. +* Connect to incomingSession(const QString &sessionType, JingleSession *session) signal in JabberAccount. + +-On incoming session +* Create and show VoiceConversationDialog. +* VoiceConversationDialog will handle the communcation between the user and the session. + +In JabberContact: +-User select "Start voice conversation..." +* Get the best resource that support voice. If no compatible resource is found, show a message box. +* Create a JingleVoiceSession using JingleVoiceSessionManager. +* Create VoiceConversationDialog +* and VoiceConversationDialog will handle the communication between the user and the session. + +In VoiceConversationDialog: +-Incoming voice session +* Accept the session call JingleVoiceSession::accept(); +* Decline the session call JingleVoiceSession::decline(); + +-Accepted voice session +* Change GUI to "Voice session in progress." + +-On declining voice session or terminating a session. +* Remove JingleVoiceSession from JingleVoiceSessionManager. +* Close the dialog. + +=================================================================================================== +Design with future in mind. Only voice session type is available today, but others will come. + +A session is a connection between two or multiple peers. +A session do not handle multiple "calls"(or whatever it called depending of the context). That's will be job of SessionManager +A sesson has a myself user and others users, all identified by their full JID. (maybe their JabberBaseContact or JabberResource object ?) + +-Maybe use the Channel pattern, where Session will hold one or multiple Channels. Think for voice+video for example. ? + +All manager classes must be unique for each account. + +JidList = QValueList or QStringList if QValueList doesn't work. + +JingleSession and derivated are created by the Manager class. + +SessionType is the XML Namespace of the session type (ex: http://jabber.org/protocol/sessions/audio) + +JingleSessionManager +-------------------- +Manage Jingle sessions. +-Manage global (maybe static ?)objects shared by all sessions (cricket::BasicPortAllocator, cricket::SessionManager). + +Has a JingleWatchSessionTask(derived from XMPP::Task) that check for incoming session in JingleSessionManager, that check the session type, +create the right JingleSession subclass, then emit the required signal. This bypass libjingle to have a better +control on incoming session request and avoid using multiple Manager for each session type. + +JingleSessionManager manage the JingleSession pointers. Do not delete it in user classes. + +* JingleSessionManager(JabberAccount *) + +* public slots: +* JingleSession *createSession(const QString &sessionType, const JidList &peers); +* void removeSession(JingleSession *); + +signals: +* void incomingSession(const QString &sessionType, JingleSession *session); + +JingleSession +------------- +Base class for Jingle session. A session is a + +* JingleSession(JingleSessionManager *manager, const JidList &peers); + +* XMPP::Jid &myself(); // account()->client()->jid(); +* JidList &peers(); +* JabberAccount *account(); +* JingleSessionManager *manager(); + +// Start the negociation phase. +* virtual void start() = 0; +// Send the IQ stanza with action "accept" +* virtual void accept() = 0; +// Send the IQ stanza with action " +* virtual void decline() = 0; +* virtual void terminate() = 0; + +// Return Session XML namespace +* virtual QString sessionType() = 0; + +protected slots: + void sendStanza(const QString &stanza) { account()->client->send(stanza); + +signals: + void accepted(); + void declined(); + void terminated(); + +JingleVoiceSession : public JingleSession +------------------ +Define a VoIP voice session between two peers(for the moment). +Hold the PhoneSessionClient object. + +connect(account()->client(),SIGNAL(xmlIncoming(const QString&)),SLOT(receiveStanza(const QString&))); + + +private slots: + void receiveStanza(const QString &stanza); + +VoiceConversationDialog +----------------------- +* VoiceConversationDialog(JingleVoiceSession *) +VoiceConversationDialog will handle the communcation between the user and a session. +Should auto-delete when closed. +It can: +-Accept a voice session. +-Decline a voice session. +-Terminate a voice session(or hang-up). + +It is the Action menu that can start a session. diff --git a/kopete/protocols/jabber/jingle/Makefile.am b/kopete/protocols/jabber/jingle/Makefile.am new file mode 100644 index 00000000..553be0d7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/Makefile.am @@ -0,0 +1,28 @@ +SUBDIRS = libjingle +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) \ + -I$(srcdir)/../libiris/iris/include \ + -I$(srcdir)/../libiris/iris/xmpp-im \ + -I$(srcdir)/../libiris/iris/jabber \ + -I$(srcdir)/../libiris/qca/src \ + -I$(srcdir)/../libiris/cutestuff/util \ + -I$(srcdir)/libjingle \ + -I$(srcdir)/.. \ + $(all_includes) + +noinst_LTLIBRARIES = libkopetejabberjingle.la + +# libkopetejabberjingle_la_SOURCES = jinglevoicecaller.cpp \ +# jinglewatchsessiontask.cpp jinglesession.cpp jinglevoicesession.cpp jinglesessionmanager.cpp \ +# jinglevoicesessiondialogbase.ui jinglevoicesessiondialog.cpp + +libkopetejabberjingle_la_SOURCES = jinglevoicecaller.cpp jinglevoicesessiondialogbase.ui jinglevoicesessiondialog.cpp + +libkopetejabberjingle_la_LIBADD = libjingle/talk/session/phone/libcricketsessionphone.la \ + libjingle/talk/p2p/client/libcricketp2pclient.la \ + libjingle/talk/p2p/base/libcricketp2pbase.la \ + libjingle/talk/xmpp/libcricketxmpp.la \ + libjingle/talk/xmllite/libcricketxmllite.la \ + libjingle/talk/base/libcricketbase.la \ + libjingle/talk/third_party/mediastreamer/libmediastreamer.la \ + $(EXPAT_LIBS) $(ORTP_LIBS) -lpthread $(ILBC_LIBS) $(SPEEX_LIBS) $(GLIB_LIBS) $(ALSA_LIBS) diff --git a/kopete/protocols/jabber/jingle/configure.in.bot b/kopete/protocols/jabber/jingle/configure.in.bot new file mode 100644 index 00000000..f30595e6 --- /dev/null +++ b/kopete/protocols/jabber/jingle/configure.in.bot @@ -0,0 +1,16 @@ +if test "$with_jingle" = yes; then + echo "" + echo Supported Jabber Jingle voice Codecs for Kopete: + echo Speex: $speex_found + echo iLBC: $ilbc_found + echo MULAW: yes +else + echo "" + echo "You have disabled Jabber Jingle voice support or you are missing required libraries required to compile it." + echo "Jingle is a new Jabber standard that define a signaling protocol via XMPP for peer-to-peer applications." + echo "Jingle audio is compatible with the Google Talk voice service." + echo "" + echo "Required Jingle dependencies are listed on this page:" + echo "http://wiki.kde.org/tiki-index.php?page=Kopete+Jabber+Jingle" + all_tests=bad +fi diff --git a/kopete/protocols/jabber/jingle/configure.in.in b/kopete/protocols/jabber/jingle/configure.in.in new file mode 100644 index 00000000..ee4db3fa --- /dev/null +++ b/kopete/protocols/jabber/jingle/configure.in.in @@ -0,0 +1,87 @@ +AC_DEFINE(PRODUCTION_BUILD, 1, [Build as a production build]) +AC_DEFINE(PRODUCTION, 1, [Build as a production build]) +AC_DEFINE(POSIX, 1, [If we're using configure, we're on POSIX]) + +# Check if the user want Jabber Jingle voice support +AC_ARG_ENABLE(jingle, [ --enable-jingle enable Jabber Jingle voice support ], with_jingle=$enableval, with_jingle=no) + +# Here we go +HAVE_EXPAT=no +AC_CHECK_LIB(expat, XML_ParserCreate, HAVE_EXPAT="yes") +if test "x$HAVE_EXPAT" = xyes ; then + EXPAT_LIBS="-lexpat" + AC_SUBST(EXPAT_LIBS) +else + with_jingle=no + AC_MSG_WARN([Expat is required to build Jabber Jingle voice support. You can get it from http://expat.sourceforge.net/]) +fi + +AC_CHECK_HEADERS(alsa/asoundlib.h, + [AC_CHECK_LIB(asound, snd_pcm_open, + [ALSA_LIBS="-lasound" ; AC_DEFINE(__ALSA_ENABLED__,1,[Defined when alsa support is enabled]) ]) + ] +) +AC_SUBST(ALSA_LIBS) + +# We test for GLIB in protocols/configure.in.in +if test x$have_glib = xno; then + with_jingle=no +fi + +PKG_CHECK_MODULES(ORTP, ortp, enable_ortp=yes, enable_ortp=no) +if test x$enable_ortp = xno ; then + with_jingle=no + AC_MSG_WARN([oRTP is required to build Jabber Jingle voice support. You can get it from http://www.linphone.org/ortp/]) +fi +AC_SUBST(ORTP_CFLAGS) +AC_SUBST(ORTP_LIBS) + +AC_ARG_WITH( speex, + [ --with-speex Set prefix where speex lib can be found (ex:/usr, /usr/local) [default=/usr] ], + [ speex_prefix=${withval}],[ speex_prefix="/usr" ]) + +PKG_CHECK_MODULES(SPEEX, speex, speex_found=yes, speex_found=no) +AC_CHECK_HEADERS(speex.h,[AC_CHECK_LIB(speex,speex_encode_int,speex_found=yes,speex_found=no)],speex_found=no) +AC_CHECK_HEADERS(speex/speex.h,[AC_CHECK_LIB(speex,speex_encode_int,speex_found=yes,speex_found=no)],speex_found=no) + +if test x$speex_found = xno; then + AC_MSG_WARN([Could not find a libspeex version that have the speex_encode_int() function. Please install libspeex=1.0.5 or libspeex>=1.1.6 from http://www.speex.org/]) +else + SPEEX_CFLAGS="$SPEEX_CFLAGS -I${speex_prefix}/include -I${speex_prefix}/include/speex" + AC_SUBST(SPEEX_CFLAGS) + AC_SUBST(SPEEX_LIBS) + AC_DEFINE(HAVE_SPEEX,1,[Speex codec is enabled]) +fi + + +# dnl only accept speex>=1.1.6 or 1.0.5 (the versions that have speex_encode_int ) +# AC_ARG_WITH( speex, +# [ --with-speex Set prefix where speex lib can be found (ex:/usr, /usr/local) [default=/usr] ], +# [ speex_prefix=${withval}],[ speex_prefix="/usr" ]) +# +# AC_CHECK_HEADERS(speex.h,[AC_CHECK_LIB(speex,speex_encode_int,speex_found=yes,speex_found=no) +# ],speex_found=no) +# +# if test "$speex_found" = "no" ; then +# AC_MSG_WARN([Could not find a libspeex version that have the speex_encode_int() function. Please install libspeex=1.0.5 or libspeex>=1.1.6 from http://www.speex.org/]) +# else +# SPEEX_CFLAGS=" -I${speex_prefix}/include -I${speex_prefix}/include/speex" +# SPEEX_LIBS="-L${speex_prefix}/lib -lspeex -lm" +# CPPFLAGS_save=$CPPFLAGS +# CPPFLAGS=$SPEEX_CFLAGS +# LDFLAGS_save=$LDFLAGS +# LDFLAGS=$SPEEX_LIBS +# AC_DEFINE(HAVE_SPEEX,1,[has speex]) +# fi +# +# AC_SUBST(SPEEX_CFLAGS) +# AC_SUBST(SPEEX_LIBS) +# CPPFLAGS=$CPPFLAGS_save +# LDFLAGS=$LDFLAGS_save +ilbc_found="no" + +AM_CONDITIONAL(include_jingle, test "$with_jingle" = "yes") + +if test "$with_jingle" = "yes" ; then + AC_DEFINE(SUPPORT_JINGLE,1,[Jingle support is enabled]) +fi diff --git a/kopete/protocols/jabber/jingle/jinglesession.cpp b/kopete/protocols/jabber/jingle/jinglesession.cpp new file mode 100644 index 00000000..6c370fca --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglesession.cpp @@ -0,0 +1,72 @@ +/* + jinglesession.h - Define a Jingle session. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#include "jinglesession.h" + +#include + +#include "jabberaccount.h" +#include "jabberprotocol.h" + +class JingleSession::Private +{ +public: + Private(JabberAccount *t_account, const JidList &t_peers) + : peers(t_peers), account(t_account) + {} + + XMPP::Jid myself; + JidList peers; + JabberAccount *account; +}; + +JingleSession::JingleSession(JabberAccount *account, const JidList &peers) + : QObject(account, 0), d(new Private(account, peers)) +{ + d->myself = account->client()->jid(); +} + +JingleSession::~JingleSession() +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << endl; + delete d; +} + +const XMPP::Jid &JingleSession::myself() const +{ + return d->myself; +} + +const JingleSession::JidList &JingleSession::peers() const +{ + return d->peers; +} + +JingleSession::JidList &JingleSession::peers() +{ + return d->peers; +} +JabberAccount *JingleSession::account() +{ + return d->account; +} + +void JingleSession::sendStanza(const QString &stanza) +{ + account()->client()->send( stanza ); +} + +#include "jinglesession.moc" diff --git a/kopete/protocols/jabber/jingle/jinglesession.h b/kopete/protocols/jabber/jingle/jinglesession.h new file mode 100644 index 00000000..00c192bd --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglesession.h @@ -0,0 +1,94 @@ +/* + jinglesession.h - Define a Jingle session. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef JINGLESESSION_H +#define JINGLESESSION_H + +#include +#include + +#include // XMPP::Jid +#include + +class JabberAccount; +/** + * @brief Base class for peer-to-peer session that use Jingle signaling + * + * @author Michaël Larouche + */ +class JingleSession : public QObject +{ + Q_OBJECT +public: + typedef QValueList JidList; + + JingleSession(JabberAccount *account, const JidList &peers); + virtual ~JingleSession(); + + /** + * Return the JabberAccount associated with this session. + */ + JabberAccount *account(); + + const XMPP::Jid &myself() const; + const JidList &peers() const; + JidList &peers(); + + /** + * Return the type of session(ex: voice, video, games) + * Note that you must return the XML namespace that define + * the session: ex:(http://jabber.org/protocol/jingle/sessions/audio) + */ + virtual QString sessionType() = 0; + +public slots: + /** + * @brief Start a session with the give JID. + * You should begin the negociation here. + */ + virtual void start() = 0; + /** + * @brief Acept a session request. + */ + virtual void accept() = 0; + /** + * @brief Decline a session request. + */ + virtual void decline() = 0; + /** + * @brief Terminate a Jingle session. + */ + virtual void terminate() = 0; + +protected slots: + void sendStanza(const QString &stanza); + +signals: + /** + * Session is started(negocation and connection test are done). + */ + void sessionStarted(); + + void accepted(); + void declined(); + void terminated(); + +private: + class Private; + Private *d; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/jinglesessionmanager.cpp b/kopete/protocols/jabber/jingle/jinglesessionmanager.cpp new file mode 100644 index 00000000..aeec2889 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglesessionmanager.cpp @@ -0,0 +1,205 @@ +/* + jinglesessionmanager.cpp - Manage Jingle sessions. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +// libjingle before everything else to not clash with Qt +#define POSIX +#include "talk/xmpp/constants.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/jid.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/base/network.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/helpers.h" +#include "talk/p2p/client/basicportallocator.h" +#include "talk/p2p/client/sessionclient.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/thread.h" +#include "talk/base/socketaddress.h" +#include "talk/session/phone/call.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/session/sessionsendtask.h" + + +#include "jinglesessionmanager.h" + +//#include "jinglesession.h" +#include "jinglevoicesession.h" + +#include "jinglewatchsessiontask.h" + +#include "jabberaccount.h" +#include "jabberprotocol.h" + +#include + +#define JINGLE_NS "http://www.google.com/session" +#define JINGLE_VOICE_SESSION_NS "http://www.google.com/session/phone" + +//BEGIN JingleSessionManager::SlotsProxy +class JingleSessionManager; +class JingleSessionManager::SlotsProxy : public sigslot::has_slots<> +{ +public: + SlotsProxy(JingleSessionManager *parent) + : sessionManager(parent) + {} + + void OnSignalingRequest() + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Requesting Jingle signaling." << endl; + sessionManager->cricketSessionManager()->OnSignalingReady(); + } + + +private: + JingleSessionManager *sessionManager; +}; + +//END JingleSessionManager::SlotsProxy + +//BEGIN JingleSessionManager::Private +class JingeSession; +class JingleSessionManager::Private +{ +public: + Private(JabberAccount *t_account) + : account(t_account), watchSessionTask(0L) + {} + + ~Private() + { + delete networkManager; + delete portAllocator; + delete sessionThread; + delete cricketSessionManager; + } + + JabberAccount *account; + QValueList sessionList; + JingleWatchSessionTask *watchSessionTask; + + cricket::NetworkManager *networkManager; + cricket::BasicPortAllocator *portAllocator; + cricket::Thread *sessionThread; + cricket::SessionManager *cricketSessionManager; +}; +//END JingleSessionManager::Private + +JingleSessionManager::JingleSessionManager(JabberAccount *account) + : QObject(account, 0), d(new Private(account)) +{ + // Create slots proxy for libjingle + slotsProxy = new SlotsProxy(this); + + // Create watch incoming session task. + d->watchSessionTask = new JingleWatchSessionTask(account->client()->rootTask()); + connect(d->watchSessionTask, SIGNAL(watchSession(const QString &, const QString &)), this, SLOT(slotIncomingSession(const QString &, const QString &))); + + // Create global cricket variables common to all sessions. + // Seed random generation with the JID of the account. + QString accountJid = account->client()->jid().full(); + cricket::InitRandom( accountJid.ascii(), accountJid.length() ); + + // Create the libjingle NetworkManager that manager local network connections + d->networkManager = new cricket::NetworkManager(); + + // Init the port allocator(select best ports) with the Google STUN server to help. + cricket::SocketAddress *googleStunAddress = new cricket::SocketAddress("64.233.167.126", 19302); + // TODO: Define a relay server. + d->portAllocator = new cricket::BasicPortAllocator(d->networkManager, googleStunAddress, 0L); + + // Create the Session manager that manager peer-to-peer sessions. + d->sessionThread = new cricket::Thread(); + d->cricketSessionManager = new cricket::SessionManager(d->portAllocator, d->sessionThread); + d->cricketSessionManager->SignalRequestSignaling.connect(slotsProxy, &JingleSessionManager::SlotsProxy::OnSignalingRequest); + d->cricketSessionManager->OnSignalingReady(); + + d->sessionThread->Start(); +} + +JingleSessionManager::~JingleSessionManager() +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << endl; + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Cleaning up Jingle sessions." << endl; + QValueList::Iterator it, itEnd = d->sessionList.end(); + for(it = d->sessionList.begin(); it != itEnd; ++it) + { + JingleSession *deletedSession = *it; + if( deletedSession ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "deleting a session." << endl; + delete deletedSession; + } + } + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Done Cleaning up Jingle sessions." << endl; + + delete d; +} + +cricket::SessionManager *JingleSessionManager::cricketSessionManager() +{ + return d->cricketSessionManager; +} + +JabberAccount *JingleSessionManager::account() +{ + return d->account; +} + +JingleSession *JingleSessionManager::createSession(const QString &sessionType, const JidList &peers) +{ + JingleSession *newSession = 0L; + + if(sessionType == JINGLE_VOICE_SESSION_NS) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Creating a voice session" << endl; + newSession = new JingleVoiceSession(account(), peers); + } + + if(newSession) + d->sessionList.append(newSession); + + return newSession; +} + +JingleSession *JingleSessionManager::createSession(const QString &sessionType, const XMPP::Jid &user) +{ + JingleSessionManager::JidList jidList; + jidList.append(user); + + return createSession(sessionType, jidList); +} + +void JingleSessionManager::removeSession(JingleSession *session) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing a jingle session." << endl; + + d->sessionList.remove(session); + delete session; +} + +void JingleSessionManager::slotIncomingSession(const QString &sessionType, const QString &initiator) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Incoming session: " << sessionType << ". Initiator: " << initiator << endl; + + JingleSession *newSession = createSession(sessionType, XMPP::Jid(initiator)); + emit incomingSession(sessionType, newSession); +} + +#include "jinglesessionmanager.moc" diff --git a/kopete/protocols/jabber/jingle/jinglesessionmanager.h b/kopete/protocols/jabber/jingle/jinglesessionmanager.h new file mode 100644 index 00000000..06951c2f --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglesessionmanager.h @@ -0,0 +1,89 @@ +/* + jinglesessionmanager.h - Manage Jingle sessions. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef JINGLESESSIONMANAGER_H +#define JINGLESESSIONMANAGER_H + +#include +#include + +#include +#include + +namespace cricket +{ + class SessionManager; +} + +class JingleSession; +class JingleVoiceSession; +class JabberAccount; + +/** + * @brief Manage Jingle sessions. + * @author Michaël Larouche + */ +class JingleSessionManager : public QObject +{ + Q_OBJECT +public: + typedef QValueList JidList; + + JingleSessionManager(JabberAccount *account); + ~JingleSessionManager(); + + /** + * Get the (single) instance of the cricket session manager. + */ + cricket::SessionManager *cricketSessionManager(); + + /** + * Return the JabberAccount associated with this session manager. + */ + JabberAccount *account(); + +public slots: + /** + * Create a new Jingle session. Returned pointer is managed by this class. + * @param sessionType the session you want to create. You must pass its XML namespace(ex: http://jabber.org/protocol/sessions/audio) + * @param peers Lists of participants of the session. + */ + JingleSession *createSession(const QString &sessionType, const JidList &peers); + /** + * Override method that create a session for a one-to-one session. + * It behave like createSession method. + * @param sessionType the sesion you want to create. You must pass its XML namespace(ex: http://jabber.org/protocol/sessions/audio) + * @param user The JID of the user you want to begin a session with. + */ + JingleSession *createSession(const QString &sessionType, const XMPP::Jid &user); + + void removeSession(JingleSession *session); + +signals: + void incomingSession(const QString &sessionType, JingleSession *session); + +private slots: + void slotIncomingSession(const QString &sessionType, const QString &initiator); + +private: + class Private; + Private *d; + + class SlotsProxy; + SlotsProxy *slotsProxy; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/jinglevoicecaller.cpp b/kopete/protocols/jabber/jingle/jinglevoicecaller.cpp new file mode 100644 index 00000000..3ad6a89a --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicecaller.cpp @@ -0,0 +1,376 @@ + +#define POSIX //FIXME +#include "talk/xmpp/constants.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/jid.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/base/network.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/helpers.h" +#include "talk/p2p/client/basicportallocator.h" +#include "talk/p2p/client/sessionclient.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/thread.h" +#include "talk/base/socketaddress.h" +#include "talk/session/phone/call.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/session/sessionsendtask.h" + + + +#include +#include + + + +#include "im.h" +#include "xmpp.h" +#include "xmpp_xmlcommon.h" +#include "jinglevoicecaller.h" +#include "jabberprotocol.h" + +// Should change in the future +#define JINGLE_NS "http://www.google.com/session" + +#include "jabberaccount.h" +#include +#define qDebug( X ) kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << X << endl +#define qWarning( X ) kdWarning() < +{ +public: + JingleClientSlots(JingleVoiceCaller *voiceCaller); + + void callCreated(cricket::Call *call); + void callDestroyed(cricket::Call *call); + void sendStanza(cricket::SessionClient*, const buzz::XmlElement *stanza); + void requestSignaling(); + void stateChanged(cricket::Call *call, cricket::Session *session, cricket::Session::State state); + +private: + JingleVoiceCaller* voiceCaller_; +}; + + +JingleClientSlots::JingleClientSlots(JingleVoiceCaller *voiceCaller) : voiceCaller_(voiceCaller) +{ +} + +void JingleClientSlots::callCreated(cricket::Call *call) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << endl; + call->SignalSessionState.connect(this, &JingleClientSlots::stateChanged); +} + +void JingleClientSlots::callDestroyed(cricket::Call *call) +{ + qDebug("JingleClientSlots: Call destroyed"); + Jid jid(call->sessions()[0]->remote_address().c_str()); + if (voiceCaller_->calling(jid)) { + qDebug(QString("Removing unterminated call to %1").arg(jid.full())); + voiceCaller_->removeCall(jid); + emit voiceCaller_->terminated(jid); + } +} + +void JingleClientSlots::sendStanza(cricket::SessionClient*, const buzz::XmlElement *stanza) +{ + QString st(stanza->Str().c_str()); + st.replace("cli:iq","iq"); + st.replace(":cli=","="); + fprintf(stderr,"bling\n"); + voiceCaller_->sendStanza(st.latin1()); + fprintf(stderr,"blong\n"); + fprintf(stderr,"Sending stanza \n%s\n\n",st.latin1()); +} + +void JingleClientSlots::requestSignaling() +{ + voiceCaller_->session_manager_->OnSignalingReady(); +} + +void JingleClientSlots::stateChanged(cricket::Call *call, cricket::Session *session, cricket::Session::State state) +{ + qDebug(QString("jinglevoicecaller.cpp: State changed (%1)").arg(state)); + // Why is c_str() stuff needed to make it compile on OS X ? + Jid jid(session->remote_address().c_str()); + + if (state == cricket::Session::STATE_INIT) { } + else if (state == cricket::Session::STATE_SENTINITIATE) { + voiceCaller_->registerCall(jid,call); + } + else if (state == cricket::Session::STATE_RECEIVEDINITIATE) { + voiceCaller_->registerCall(jid,call); + emit voiceCaller_->incoming(jid); + } + else if (state == cricket::Session::STATE_SENTACCEPT) { } + else if (state == cricket::Session::STATE_RECEIVEDACCEPT) { + emit voiceCaller_->accepted(jid); + } + else if (state == cricket::Session::STATE_SENTMODIFY) { } + else if (state == cricket::Session::STATE_RECEIVEDMODIFY) { + qWarning(QString("jinglevoicecaller.cpp: RECEIVEDMODIFY not implemented yet (was from %1)").arg(jid.full())); + } + else if (state == cricket::Session::STATE_SENTREJECT) { } + else if (state == cricket::Session::STATE_RECEIVEDREJECT) { + voiceCaller_->removeCall(jid); + emit voiceCaller_->rejected(jid); + } + else if (state == cricket::Session::STATE_SENTREDIRECT) { } + else if (state == cricket::Session::STATE_SENTTERMINATE) { + voiceCaller_->removeCall(jid); + emit voiceCaller_->terminated(jid); + } + else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) { + voiceCaller_->removeCall(jid); + emit voiceCaller_->terminated(jid); + } + else if (state == cricket::Session::STATE_INPROGRESS) { + emit voiceCaller_->in_progress(jid); + } +} + +// ---------------------------------------------------------------------------- + +/** + * \class JingleVoiceCaller + * \brief A Voice Calling implementation using libjingle. + */ + +JingleVoiceCaller::JingleVoiceCaller(PsiAccount* acc) : VoiceCaller(acc) +{ + initialized_ = false; +} + +void JingleVoiceCaller::initialize() +{ + if (initialized_) + return; + + QString jid = ((ClientStream&) account()->client()->client()->stream()).jid().full(); + qDebug(QString("jinglevoicecaller.cpp: Creating new caller for %1").arg(jid)); + if (jid.isEmpty()) { + qWarning("jinglevoicecaller.cpp: Empty JID"); + return; + } + + buzz::Jid j(jid.ascii()); + cricket::InitRandom(j.Str().c_str(),j.Str().size()); + + // Global variables + if (!socket_server_) { + socket_server_ = new cricket::PhysicalSocketServer(); + cricket::Thread *t = new cricket::Thread((cricket::PhysicalSocketServer*)(socket_server_)); + cricket::ThreadManager::SetCurrent(t); + t->Start(); + thread_ = t; + + stun_addr_ = new cricket::SocketAddress("64.233.167.126",19302); + network_manager_ = new cricket::NetworkManager(); + port_allocator_ = new cricket::BasicPortAllocator((cricket::NetworkManager*)(network_manager_), (cricket::SocketAddress*)(stun_addr_), /* relay server */ NULL); + } + + // Session manager + session_manager_ = new cricket::SessionManager((cricket::PortAllocator*)(port_allocator_), thread_); + slots_ = new JingleClientSlots(this); + session_manager_->SignalRequestSignaling.connect(slots_, &JingleClientSlots::requestSignaling); + session_manager_->OnSignalingReady(); + + // Phone Client + phone_client_ = new cricket::PhoneSessionClient(j, (cricket::SessionManager*)(session_manager_)); + phone_client_->SignalCallCreate.connect(slots_, &JingleClientSlots::callCreated); + phone_client_->SignalCallDestroy.connect(slots_, &JingleClientSlots::callDestroyed); + phone_client_->SignalSendStanza.connect(slots_, &JingleClientSlots::sendStanza); + + // IQ Responder + new JingleIQResponder(account()->client()->rootTask()); + + // Listen to incoming packets + connect(account()->client()->client(),SIGNAL(xmlIncoming(const QString&)),SLOT(receiveStanza(const QString&))); + + initialized_ = true; +} + + +void JingleVoiceCaller::deinitialize() +{ + if (!initialized_) + return; + + // Stop listening to incoming packets + disconnect(account()->client(),SIGNAL(xmlIncoming(const QString&)),this,SLOT(receiveStanza(const QString&))); + + // Disconnect signals (is this needed) + //phone_client_->SignalCallCreate.disconnect(slots_); + //phone_client_->SignalSendStanza.disconnect(slots_); + + // Delete objects + delete phone_client_; + delete session_manager_; + delete slots_; + + initialized_ = false; +} + + +JingleVoiceCaller::~JingleVoiceCaller() +{ +} + +bool JingleVoiceCaller::calling(const Jid& jid) +{ + return calls_.contains(jid.full()); +} + +void JingleVoiceCaller::call(const Jid& jid) +{ + qDebug(QString("jinglevoicecaller.cpp: Calling %1").arg(jid.full())); + cricket::Call *c = ((cricket::PhoneSessionClient*)(phone_client_))->CreateCall(); + c->InitiateSession(buzz::Jid(jid.full().ascii())); + phone_client_->SetFocus(c); +} + +void JingleVoiceCaller::accept(const Jid& j) +{ + qDebug("jinglevoicecaller.cpp: Accepting call"); + cricket::Call* call = calls_[j.full()]; + if (call != NULL) { + call->AcceptSession(call->sessions()[0]); + phone_client_->SetFocus(call); + } +} + +void JingleVoiceCaller::reject(const Jid& j) +{ + qDebug("jinglevoicecaller.cpp: Rejecting call"); + cricket::Call* call = calls_[j.full()]; + if (call != NULL) { + call->RejectSession(call->sessions()[0]); + calls_.remove(j.full()); + } +} + +void JingleVoiceCaller::terminate(const Jid& j) +{ + qDebug(QString("jinglevoicecaller.cpp: Terminating call to %1").arg(j.full())); + cricket::Call* call = calls_[j.full()]; + if (call != NULL) { + call->Terminate(); + calls_.remove(j.full()); + } +} + +void JingleVoiceCaller::sendStanza(const char* stanza) +{ + account()->client()->send(QString(stanza)); +} + +void JingleVoiceCaller::registerCall(const Jid& jid, cricket::Call* call) +{ + qDebug("jinglevoicecaller.cpp: Registering call\n"); + kdDebug(14000) << k_funcinfo << jid.full() << endl; + if (!calls_.contains(jid.full())) { + calls_[jid.full()] = call; + } +// else { +// qWarning("jinglevoicecaller.cpp: Auto-rejecting call because another call is currently open"); +// call->RejectSession(call->sessions()[0]); +// } +} + +void JingleVoiceCaller::removeCall(const Jid& j) +{ + qDebug(QString("JingleVoiceCaller: Removing call to %1").arg(j.full())); + calls_.remove(j.full()); +} + +void JingleVoiceCaller::receiveStanza(const QString& stanza) +{ + QDomDocument doc; + doc.setContent(stanza); + + // Check if it is offline presence from an open chat + if (doc.documentElement().tagName() == "presence") { + Jid from = Jid(doc.documentElement().attribute("from")); + QString type = doc.documentElement().attribute("type"); + if (type == "unavailable" && calls_.contains(from.full())) { + qDebug("JingleVoiceCaller: User went offline without closing a call."); + removeCall(from); + emit terminated(from); + } + return; + } + + // Check if the packet is destined for libjingle. + // We could use Session::IsClientStanza to check this, but this one crashes + // for some reason. + QDomNode n = doc.documentElement().firstChild(); + bool ok = false; + while (!n.isNull() && !ok) { + QDomElement e = n.toElement(); + if (!e.isNull() && e.attribute("xmlns") == JINGLE_NS) { + ok = true; + } + n = n.nextSibling(); + } + + // Spread the word + if (ok) { + qDebug(QString("jinglevoicecaller.cpp: Handing down %1").arg(stanza)); + buzz::XmlElement *e = buzz::XmlElement::ForStr(stanza.ascii()); + phone_client_->OnIncomingStanza(e); + } +} + +cricket::SocketServer* JingleVoiceCaller::socket_server_ = NULL; +cricket::Thread* JingleVoiceCaller::thread_ = NULL; +cricket::NetworkManager* JingleVoiceCaller::network_manager_ = NULL; +cricket::BasicPortAllocator* JingleVoiceCaller::port_allocator_ = NULL; +cricket::SocketAddress* JingleVoiceCaller::stun_addr_ = NULL; diff --git a/kopete/protocols/jabber/jingle/jinglevoicecaller.h b/kopete/protocols/jabber/jingle/jinglevoicecaller.h new file mode 100644 index 00000000..4448d530 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicecaller.h @@ -0,0 +1,72 @@ +#define PsiAccount JabberAccount + +#ifndef JINGLEVOICECALLER_H +#define JINGLEVOICECALLER_H + +#include + +#include "im.h" +#include "voicecaller.h" + +using namespace XMPP; + + +class PsiAccount; + +namespace cricket { + class SocketServer; + class Thread; + class NetworkManager; + class BasicPortAllocator; + class SessionManager; + class PhoneSessionClient; + class Call; + class SocketAddress; +} + +class JingleClientSlots; +class JingleCallSlots; + + +class JingleVoiceCaller : public VoiceCaller +{ + Q_OBJECT + + friend class JingleClientSlots; + +public: + JingleVoiceCaller(PsiAccount* account); + ~JingleVoiceCaller(); + + virtual bool calling(const Jid&); + + virtual void initialize(); + virtual void deinitialize(); + + virtual void call(const Jid&); + virtual void accept(const Jid&); + virtual void reject(const Jid&); + virtual void terminate(const Jid&); + +protected: + void sendStanza(const char*); + void registerCall(const Jid&, cricket::Call*); + void removeCall(const Jid&); + +protected slots: + void receiveStanza(const QString&); + +private: + bool initialized_; + static cricket::SocketServer *socket_server_; + static cricket::Thread *thread_; + static cricket::NetworkManager *network_manager_; + static cricket::BasicPortAllocator *port_allocator_; + static cricket::SocketAddress *stun_addr_; + cricket::SessionManager *session_manager_; + cricket::PhoneSessionClient *phone_client_; + JingleClientSlots *slots_; + QMap calls_; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/jinglevoicesession.cpp b/kopete/protocols/jabber/jingle/jinglevoicesession.cpp new file mode 100644 index 00000000..09019ce4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicesession.cpp @@ -0,0 +1,333 @@ +/* + jinglevoicesession.cpp - Define a Jingle voice session. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +// libjingle before everything else to not clash with Qt +#define POSIX +#include "talk/xmpp/constants.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/jid.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/base/network.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/helpers.h" +#include "talk/p2p/client/basicportallocator.h" +#include "talk/p2p/client/sessionclient.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/thread.h" +#include "talk/base/socketaddress.h" +#include "talk/session/phone/call.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/session/sessionsendtask.h" + +#include "jinglevoicesession.h" +#include "jinglesessionmanager.h" + +// Qt includes +#include + +// KDE includes +#include + +// Kopete Jabber includes +#include "jabberaccount.h" +#include "jabberprotocol.h" + +#include +#include + +#define JINGLE_NS "http://www.google.com/session" +#define JINGLE_VOICE_SESSION_NS "http://www.google.com/session/phone" + +static bool hasPeer(const JingleVoiceSession::JidList &jidList, const XMPP::Jid &peer) +{ + JingleVoiceSession::JidList::ConstIterator it, itEnd = jidList.constEnd(); + for(it = jidList.constBegin(); it != itEnd; ++it) + { + if( (*it).compare(peer, true) ) + return true; + } + + return false; +} +//BEGIN SlotsProxy +/** + * This class is used to receive signals from libjingle, + * which is are not compatible with Qt signals. + * So it's a proxy between JingeVoiceSession(qt)<->linjingle class. + */ +class JingleVoiceSession::SlotsProxy : public sigslot::has_slots<> +{ +public: + SlotsProxy(JingleVoiceSession *parent) + : voiceSession(parent) + {} + + void OnCallCreated(cricket::Call* call) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "SlotsProxy: CallCreated." << endl; + + call->SignalSessionState.connect(this, &JingleVoiceSession::SlotsProxy::PhoneSessionStateChanged); + voiceSession->setCall(call); + } + + void PhoneSessionStateChanged(cricket::Call *call, cricket::Session *session, cricket::Session::State state) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "State changed: " << state << endl; + + XMPP::Jid jid(session->remote_address().c_str()); + + // Do nothing if the session do not contain a peers. + //if( !voiceSession->peers().contains(jid) ) + if( !hasPeer(voiceSession->peers(), jid) ) + return; + + if (state == cricket::Session::STATE_INIT) + {} + else if (state == cricket::Session::STATE_SENTINITIATE) + {} + else if (state == cricket::Session::STATE_RECEIVEDINITIATE) + { + voiceSession->setCall(call); + } + else if (state == cricket::Session::STATE_SENTACCEPT) + {} + else if (state == cricket::Session::STATE_RECEIVEDACCEPT) + { + emit voiceSession->accepted(); + } + else if (state == cricket::Session::STATE_SENTMODIFY) + {} + else if (state == cricket::Session::STATE_RECEIVEDMODIFY) + { + //qWarning(QString("jinglevoicecaller.cpp: RECEIVEDMODIFY not implemented yet (was from %1)").arg(jid.full())); + } + else if (state == cricket::Session::STATE_SENTREJECT) + {} + else if (state == cricket::Session::STATE_RECEIVEDREJECT) + { + emit voiceSession->declined(); + } + else if (state == cricket::Session::STATE_SENTREDIRECT) + {} + else if (state == cricket::Session::STATE_SENTTERMINATE) + { + emit voiceSession->terminated(); + } + else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) + { + emit voiceSession->terminated(); + } + else if (state == cricket::Session::STATE_INPROGRESS) + { + emit voiceSession->sessionStarted(); + } + } + + void OnSendingStanza(cricket::SessionClient*, const buzz::XmlElement *buzzStanza) + { + QString irisStanza(buzzStanza->Str().c_str()); + irisStanza.replace("cli:iq","iq"); + irisStanza.replace(":cli=","="); + + voiceSession->sendStanza(irisStanza); + } +private: + JingleVoiceSession *voiceSession; +}; +//END SlotsProxy + +//BEGIN JingleIQResponder +class JingleVoiceSession::JingleIQResponder : public XMPP::Task +{ +public: + JingleIQResponder(XMPP::Task *); + ~JingleIQResponder(); + + bool take(const QDomElement &); +}; + +/** + * \class JingleIQResponder + * \brief A task that responds to jingle candidate queries with an empty reply. + */ + +JingleVoiceSession::JingleIQResponder::JingleIQResponder(Task *parent) :Task(parent) +{ +} + +JingleVoiceSession::JingleIQResponder::~JingleIQResponder() +{ +} + +bool JingleVoiceSession::JingleIQResponder::take(const QDomElement &e) +{ + if(e.tagName() != "iq") + return false; + + QDomElement first = e.firstChild().toElement(); + if (!first.isNull() && first.attribute("xmlns") == JINGLE_NS) { + QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); + send(iq); + return true; + } + + return false; +} +//END JingleIQResponder + +class JingleVoiceSession::Private +{ +public: + Private() + : phoneSessionClient(0L), currentCall(0L) + {} + + ~Private() + { + if(currentCall) + currentCall->Terminate(); + + delete currentCall; + } + + cricket::PhoneSessionClient *phoneSessionClient; + cricket::Call* currentCall; +}; + +JingleVoiceSession::JingleVoiceSession(JabberAccount *account, const JidList &peers) + : JingleSession(account, peers), d(new Private) +{ + slotsProxy = new SlotsProxy(this); + + buzz::Jid buzzJid( account->client()->jid().full().ascii() ); + + // Create the phone(voice) session. + d->phoneSessionClient = new cricket::PhoneSessionClient( buzzJid, account->sessionManager()->cricketSessionManager() ); + + d->phoneSessionClient->SignalSendStanza.connect(slotsProxy, &JingleVoiceSession::SlotsProxy::OnSendingStanza); + d->phoneSessionClient->SignalCallCreate.connect(slotsProxy, &JingleVoiceSession::SlotsProxy::OnCallCreated); + + // Listen to incoming packets + connect(account->client()->client(), SIGNAL(xmlIncoming(const QString&)), this, SLOT(receiveStanza(const QString&))); + + new JingleIQResponder(account->client()->rootTask()); +} + +JingleVoiceSession::~JingleVoiceSession() +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << endl; + delete slotsProxy; + delete d; +} + +QString JingleVoiceSession::sessionType() +{ + return QString(JINGLE_VOICE_SESSION_NS); +} + +void JingleVoiceSession::start() +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Starting a voice session..." << endl; + d->currentCall = d->phoneSessionClient->CreateCall(); + + QString firstPeerJid = ((XMPP::Jid)peers().first()).full(); + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "With peer: " << firstPeerJid << endl; + d->currentCall->InitiateSession( buzz::Jid(firstPeerJid.ascii()) ); + + d->phoneSessionClient->SetFocus(d->currentCall); +} + +void JingleVoiceSession::accept() +{ + if(d->currentCall) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Accepting a voice session..." << endl; + + d->currentCall->AcceptSession(d->currentCall->sessions()[0]); + d->phoneSessionClient->SetFocus(d->currentCall); + } +} + +void JingleVoiceSession::decline() +{ + if(d->currentCall) + { + d->currentCall->RejectSession(d->currentCall->sessions()[0]); + } +} + +void JingleVoiceSession::terminate() +{ + if(d->currentCall) + { + d->currentCall->Terminate(); + } +} + +void JingleVoiceSession::setCall(cricket::Call *call) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Updating cricket::call object." << endl; + d->currentCall = call; + d->phoneSessionClient->SetFocus(d->currentCall); +} + +void JingleVoiceSession::receiveStanza(const QString &stanza) +{ + QDomDocument doc; + doc.setContent(stanza); + + // Check if it is offline presence from an open chat + if( doc.documentElement().tagName() == "presence" ) + { + XMPP::Jid from = XMPP::Jid(doc.documentElement().attribute("from")); + QString type = doc.documentElement().attribute("type"); + if( type == "unavailable" && hasPeer(peers(), from) ) + { + //qDebug("JingleVoiceCaller: User went offline without closing a call."); + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "User went offline without closing a call." << endl; + emit terminated(); + } + return; + } + + // Check if the packet is destined for libjingle. + // We could use Session::IsClientStanza to check this, but this one crashes + // for some reason. + QDomNode node = doc.documentElement().firstChild(); + bool ok = false; + while( !node.isNull() && !ok ) + { + QDomElement element = node.toElement(); + if( !element.isNull() && element.attribute("xmlns") == JINGLE_NS) + { + ok = true; + } + node = node.nextSibling(); + } + + // Spread the word + if( ok ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Handing down buzz::stanza" << endl; + buzz::XmlElement *e = buzz::XmlElement::ForStr(stanza.ascii()); + d->phoneSessionClient->OnIncomingStanza(e); + } +} + +#include "jinglevoicesession.moc" diff --git a/kopete/protocols/jabber/jingle/jinglevoicesession.h b/kopete/protocols/jabber/jingle/jinglevoicesession.h new file mode 100644 index 00000000..e70d3b54 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicesession.h @@ -0,0 +1,70 @@ +/* + jinglevoicesession.h - Define a Jingle voice session. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef JINGLEVOICESESSION_H +#define JINGLEVOICESESSION_H + +#include + +#include // XMPP::Jid +#include + +namespace cricket +{ + class Call; +} + +class JabberAccount; +class JingleSession; + +/** + * Implement a Jingle voice peer-to-peer session that is compatible with Google Talk voice offering. + * + * @author Michaël Larouche +*/ +class JingleVoiceSession : public JingleSession +{ + Q_OBJECT +public: + typedef QValueList JidList; + + JingleVoiceSession(JabberAccount *account, const JidList &peers); + virtual ~JingleVoiceSession(); + + virtual QString sessionType(); + +public slots: + virtual void accept(); + virtual void decline(); + virtual void start(); + virtual void terminate(); + +protected slots: + void receiveStanza(const QString &stanza); + +private: + void setCall(cricket::Call *call); + + class Private; + Private *d; + + class SlotsProxy; + SlotsProxy *slotsProxy; + + class JingleIQResponder; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/jinglevoicesessiondialog.cpp b/kopete/protocols/jabber/jingle/jinglevoicesessiondialog.cpp new file mode 100644 index 00000000..9fb61274 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicesessiondialog.cpp @@ -0,0 +1,208 @@ +/* + jinglevoicesessiondialog.cpp - GUI for a voice session. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#include "jinglevoicesessiondialog.h" + +// Qt includes +#include +#include +#include + +// Jingle includes +// #include "jinglevoicesession.h" +// #include "jinglesessionmanager.h" +#include "voicecaller.h" + +// KDE includes +#include +#include + +// Kopete includes +#include "jabberaccount.h" +#include "jabbercontact.h" +#include "jabbercontactpool.h" + +#include "kopeteglobal.h" +#include "kopetemetacontact.h" + +using namespace XMPP; + +JingleVoiceSessionDialog::JingleVoiceSessionDialog(const Jid &peerJid, VoiceCaller *caller, QWidget *parent, const char *name) + : JingleVoiceSessionDialogBase(parent, name), m_session(caller), m_peerJid(peerJid), m_sessionState(Incoming) +{ + QString contactJid = m_peerJid.full(); + setCaption( i18n("Voice session with %1").arg(contactJid) ); + + connect(buttonAccept, SIGNAL(clicked()), this, SLOT(slotAcceptClicked())); + connect(buttonDecline, SIGNAL(clicked()), this, SLOT(slotDeclineClicked())); + connect(buttonTerminate, SIGNAL(clicked()), this, SLOT(slotTerminateClicked())); + +// NOTE: Disabled for 0.12 +#if 0 + connect(m_session, SIGNAL(sessionStarted()), this, SLOT(sessionStarted())); + connect(m_session, SIGNAL(accepted()), this, SLOT(sessionAccepted())); + connect(m_session, SIGNAL(declined()), this, SLOT(sessionDeclined())); + connect(m_session, SIGNAL(terminated()), this, SLOT(sessionTerminated())); +#endif + connect(m_session, SIGNAL(accepted( const Jid & )), this, SLOT( sessionAccepted(const Jid &) )); + connect(m_session, SIGNAL(in_progress( const Jid & )), this, SLOT( sessionStarted(const Jid &) )); + connect(m_session, SIGNAL(rejected( const Jid& )), this, SLOT( sessionDeclined(const Jid &) )); + connect(m_session, SIGNAL(terminated( const Jid& )), this, SLOT( sessionTerminated(const Jid &) )); + + // Find JabberContact for the peer and fill this dialog with contact information. + JabberContact *peerContact = static_cast( m_session->account()->contactPool()->findRelevantRecipient( m_peerJid ) ); + if( peerContact ) + { + setContactInformation( peerContact ); + } + + labelSessionStatus->setText( i18n("Incoming Session...") ); + buttonAccept->setEnabled(true); + buttonDecline->setEnabled(true); +} + +JingleVoiceSessionDialog::~JingleVoiceSessionDialog() +{ + //m_session->account()->sessionManager()->removeSession(m_session); +} + +void JingleVoiceSessionDialog::setContactInformation(JabberContact *contact) +{ + if( contact->metaContact() ) + { + labelDisplayName->setText( contact->metaContact()->displayName() ); + labelContactPhoto->setPixmap( QPixmap(contact->metaContact()->photo()) ); + } + else + { + labelDisplayName->setText( contact->nickName() ); + labelDisplayName->setPixmap( QPixmap(contact->property(Kopete::Global::Properties::self()->photo()).value().toString()) ); + } +} + +void JingleVoiceSessionDialog::start() +{ + labelSessionStatus->setText( i18n("Waiting for other peer...") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(true); + //m_session->start(); + m_session->call( m_peerJid ); + m_sessionState = Waiting; +} + +void JingleVoiceSessionDialog::slotAcceptClicked() +{ + labelSessionStatus->setText( i18n("Session accepted.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(true); + + //m_session->accept(); + m_session->accept( m_peerJid ); + m_sessionState = Accepted; +} + +void JingleVoiceSessionDialog::slotDeclineClicked() +{ + labelSessionStatus->setText( i18n("Session declined.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(false); + + //m_session->decline(); + m_session->reject( m_peerJid ); + m_sessionState = Declined; + finalize(); +} + +void JingleVoiceSessionDialog::slotTerminateClicked() +{ + labelSessionStatus->setText( i18n("Session terminated.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(false); + + //m_session->terminate(); + m_session->terminate( m_peerJid ); + m_sessionState = Terminated; + finalize(); + close(); +} + +void JingleVoiceSessionDialog::sessionStarted(const Jid &jid) +{ + if( m_peerJid.compare(jid) ) + { + labelSessionStatus->setText( i18n("Session in progress.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(true); + m_sessionState = Started; + } +} + +void JingleVoiceSessionDialog::sessionAccepted(const Jid &jid) +{ + if( m_peerJid.compare(jid) ) + { + labelSessionStatus->setText( i18n("Session accepted.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(true); + m_sessionState = Accepted; + } +} + +void JingleVoiceSessionDialog::sessionDeclined(const Jid &jid) +{ + if( m_peerJid.compare(jid) ) + { + labelSessionStatus->setText( i18n("Session declined.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(false); + m_sessionState = Declined; + } +} + +void JingleVoiceSessionDialog::sessionTerminated(const Jid &jid) +{ + if( m_peerJid.compare(jid) ) + { + labelSessionStatus->setText( i18n("Session terminated.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(false); + m_sessionState = Terminated; + } +} + +void JingleVoiceSessionDialog::reject() +{ + finalize(); + QDialog::reject(); +} + +void JingleVoiceSessionDialog::finalize() +{ + disconnect(m_session, SIGNAL(accepted( const Jid & )), this, SLOT( sessionAccepted(const Jid &) )); + disconnect(m_session, SIGNAL(in_progress( const Jid & )), this, SLOT( sessionStarted(const Jid &) )); + disconnect(m_session, SIGNAL(rejected( const Jid& )), this, SLOT( sessionDeclined(const Jid &) )); + disconnect(m_session, SIGNAL(terminated( const Jid& )), this, SLOT( sessionTerminated(const Jid &) )); +} + +#include "jinglevoicesessiondialog.moc" diff --git a/kopete/protocols/jabber/jingle/jinglevoicesessiondialog.h b/kopete/protocols/jabber/jingle/jinglevoicesessiondialog.h new file mode 100644 index 00000000..29d0c091 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicesessiondialog.h @@ -0,0 +1,66 @@ +/* + jinglevoicesessiondialog.h - GUI for a voice session. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef JINGLEVOICESESSIONDIALOG_H +#define JINGLEVOICESESSIONDIALOG_H + +#include "jinglevoicesessiondialogbase.h" + +#include +#include + +using namespace XMPP; + +class JabberContact; +class VoiceCaller; + +class JingleVoiceSessionDialog : public JingleVoiceSessionDialogBase +{ + Q_OBJECT +public: + enum SessionState { Incoming, Waiting, Accepted, Declined, Started, Terminated }; + + JingleVoiceSessionDialog(const Jid &peerJid, VoiceCaller *caller, QWidget *parent = 0, const char *name = 0); + ~JingleVoiceSessionDialog(); + +public slots: + void start(); + +protected slots: + void reject(); + +protected: + void finalize(); + +private slots: + void slotAcceptClicked(); + void slotDeclineClicked(); + void slotTerminateClicked(); + + void sessionStarted(const Jid &jid); + void sessionAccepted(const Jid &jid); + void sessionDeclined(const Jid &jid); + void sessionTerminated(const Jid &jid); + +private: + void setContactInformation(JabberContact *contact); + + VoiceCaller *m_session; + Jid m_peerJid; + SessionState m_sessionState; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/jinglevoicesessiondialogbase.ui b/kopete/protocols/jabber/jingle/jinglevoicesessiondialogbase.ui new file mode 100644 index 00000000..0dc9ab35 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicesessiondialogbase.ui @@ -0,0 +1,369 @@ + +JingleVoiceSessionDialogBase + + + JingleVoiceSessionDialogBase + + + + 0 + 0 + 329 + 188 + + + + JabberVoiceSessionDialogBase + + + + unnamed + + + + layout8 + + + + unnamed + + + + layout5 + + + + unnamed + + + + spacer9 + + + Horizontal + + + Expanding + + + + 16 + 20 + + + + + + textLabel1 + + + Voice session with: + + + + + spacer10 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + + + layout4 + + + + unnamed + + + + spacer7 + + + Horizontal + + + Expanding + + + + 16 + 20 + + + + + + labelContactPhoto + + + + 4 + 4 + 0 + 0 + + + + + 128 + 128 + + + + true + + + + + spacer8 + + + Horizontal + + + Expanding + + + + 16 + 20 + + + + + + + + layout7 + + + + unnamed + + + + spacer11 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + labelDisplayName + + + Contact displayname + + + + + spacer12 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + + + spacer5 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + layout1 + + + + unnamed + + + + spacer1 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + buttonAccept + + + false + + + Accep&t + + + + + buttonDecline + + + false + + + &Decline + + + + + buttonTerminate + + + false + + + Termi&nate + + + + + spacer2 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + layout3 + + + + unnamed + + + + textLabel4 + + + Current status: + + + + + labelSessionStatus + + + + 5 + 7 + 0 + 0 + + + + Session status + + + + + + + spacer5_2 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + + + kpushbutton.h + kpushbutton.h + kpushbutton.h + + diff --git a/kopete/protocols/jabber/jingle/jinglewatchsessiontask.cpp b/kopete/protocols/jabber/jingle/jinglewatchsessiontask.cpp new file mode 100644 index 00000000..fc7de053 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglewatchsessiontask.cpp @@ -0,0 +1,75 @@ +/* + jingleswatchsessiontask.cpp - Watch for incoming Jingle sessions. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "jinglewatchsessiontask.h" + +#include + +#include "jabberprotocol.h" + +#define JINGLE_NS "http://www.google.com/session" + +JingleWatchSessionTask::JingleWatchSessionTask(XMPP::Task *parent) + : Task(parent) +{} + +JingleWatchSessionTask::~JingleWatchSessionTask() +{} + +//NOTE: This task watch for pre-JEP session. +bool JingleWatchSessionTask::take(const QDomElement &element) +{ + if(element.tagName() != "iq") + return false; + + QString sessionType, initiator; + + QDomElement first = element.firstChild().toElement(); + if( !first.isNull() && first.attribute("xmlns") == JINGLE_NS && first.tagName() == "session" ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Checking for incoming sesssion." << endl; + initiator = first.attribute("initiator"); + + // Only proceed initiate type Jingle XMPP call. + if( first.attribute("type") != QString::fromUtf8("initiate") ) + return false; + + int nodeIndex; + + QDomNodeList nodeList = first.childNodes(); + // Do not check first child + for(nodeIndex = 0; nodeIndex < nodeList.length(); nodeIndex++) + { + QDomElement nodeElement = nodeList.item(nodeIndex).toElement(); + if(nodeElement.tagName() == "description") + { + sessionType = nodeElement.attribute("xmlns"); + } + } + + if( !initiator.isEmpty() && !sessionType.isEmpty() ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Emmiting incoming sesssion." << endl; + emit watchSession(sessionType, initiator); + return true; + } + } + + return false; +} + +#include "jinglewatchsessiontask.moc" \ No newline at end of file diff --git a/kopete/protocols/jabber/jingle/jinglewatchsessiontask.h b/kopete/protocols/jabber/jingle/jinglewatchsessiontask.h new file mode 100644 index 00000000..99b76661 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglewatchsessiontask.h @@ -0,0 +1,39 @@ +/* + jingleswatchsessiontask.h - Watch for incoming Jingle sessions. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef JINGLEWATCHSESSIONTASK_H +#define JINGLEWATCHSESSIONTASK_H + +#include + +/** + * This task watch for incoming Jingle session and notify manager. + * It is declared in the header to be "moc"-able. + */ +class JingleWatchSessionTask : public XMPP::Task +{ + Q_OBJECT +public: + JingleWatchSessionTask(XMPP::Task *parent); + ~JingleWatchSessionTask(); + + bool take(const QDomElement &element); + +signals: + void watchSession(const QString &sessionType, const QString &initiator); +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/AUTHORS b/kopete/protocols/jabber/jingle/libjingle/AUTHORS new file mode 100644 index 00000000..e491a9e7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/AUTHORS @@ -0,0 +1 @@ +Google Inc. diff --git a/kopete/protocols/jabber/jingle/libjingle/COPYING b/kopete/protocols/jabber/jingle/libjingle/COPYING new file mode 100644 index 00000000..d58182b1 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/COPYING @@ -0,0 +1,25 @@ +Copyright (c) 2004--2005, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/kopete/protocols/jabber/jingle/libjingle/ChangeLog b/kopete/protocols/jabber/jingle/libjingle/ChangeLog new file mode 100644 index 00000000..6d15ac52 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/ChangeLog @@ -0,0 +1,4 @@ +Libjingle + +0.1.0 - Dec 15 2005 + - Initial release diff --git a/kopete/protocols/jabber/jingle/libjingle/INSTALL b/kopete/protocols/jabber/jingle/libjingle/INSTALL new file mode 100644 index 00000000..a4b34144 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/INSTALL @@ -0,0 +1,229 @@ +Copyright 1994, 1995, 1996, 1999, 2000, 2001, 2002 Free Software +Foundation, Inc. + + This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. (Caching is +disabled by default to prevent problems with accidental use of stale +cache files.) + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You only need +`configure.ac' if you want to change it or regenerate `configure' using +a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not support the `VPATH' +variable, you have to compile the package for one architecture at a +time in the source code directory. After you have installed the +package for one architecture, use `make distclean' before reconfiguring +for another architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the `--target=TYPE' option to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +will cause the specified gcc to be used as the C compiler (unless it is +overridden in the site shell script). + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/kopete/protocols/jabber/jingle/libjingle/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/Makefile.am new file mode 100644 index 00000000..164f7058 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS=talk + +dist-hook: + sed -i -f talk/sanitize.sed `find $(distdir) -type f` \ No newline at end of file diff --git a/kopete/protocols/jabber/jingle/libjingle/NEWS b/kopete/protocols/jabber/jingle/libjingle/NEWS new file mode 100644 index 00000000..1694c754 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/NEWS @@ -0,0 +1 @@ +* Initial source release diff --git a/kopete/protocols/jabber/jingle/libjingle/README b/kopete/protocols/jabber/jingle/libjingle/README new file mode 100644 index 00000000..ec130b36 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/README @@ -0,0 +1,59 @@ +Libjingle + +Libjingle is a set of components provided by Google to interoperate with Google +Talk's peer-to-peer and voice capabilities. This package will create several +static libraries you may link to your project as needed. + +-talk - No source files in talk/, just these subdirectories +|-base - Contains basic low-level portable utility functions for +| things like threads and sockets +|-p2p - The P2P stack + |-base - Base p2p functionality + |-client - Hooks to tie it into XMPP +|-session - Signaling + |-phone - Signaling code specific to making phone calls +|-third_party - Components that aren't ours + |-mediastreamer - Media components for dealing with sound hardware and + | voice codecs +|-xmllite - XML parser +|-xmpp - XMPP engine + +In addition, this package contains two examples in talk/examples which +illustrate the basic concepts of how the provided classes work. + +The xmllite component of libjingle depends on expat. You can download expat +from http://expat.sourceforge.net/. + +mediastreamer, the media components used by the example applications depend on +the oRTP and iLBC components from linphone, which can be found at +http://www.linphone.org. Linphone, in turn depends on GLib, which can be found +at http://www.gtk.org. This GLib dependency should be removed in future +releases. + +Building Libjingle + +Once the dependencies are installed, run ./configure. ./configure will return +an error if it failed to locate the proper dependencies. If ./configure +succeeds, run 'make' to build the components and examples. + +When the build is complete, you can run the call example from +talk/examples/call. This will ask you for your GMail username and your GMail +auth cookie. Your GMail auth cookie is the GX cookie from mail.google.com +found in your web browser. + +Relay Server + +Libjingle will also build a relay server that may be used to relay traffic +when a direct peer-to-peer connection could not be established. The relay +server will build in talk/p2p/base/relayserver and will listen on UDP +ports 5000 and 5001. See the Libjingle Developer Guide at +http://code.google.com/apis/talk/index.html for information about configuring +a client to use this relay server. + +STUN Server + +Lastly, Libjingle builds a STUN server which implements the STUN protocol for +Simple Traversal of UDP over NAT. The STUN server is built as +talk/p2p/base/stunserver and listens on UDP port 7000. See the Libjingle +Developer Guide at http://code.google.com/apis/talk/index.html for information +about configuring a client to use this STUN server. diff --git a/kopete/protocols/jabber/jingle/libjingle/libjingle.pro b/kopete/protocols/jabber/jingle/libjingle/libjingle.pro new file mode 100644 index 00000000..53c8e293 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/libjingle.pro @@ -0,0 +1,142 @@ +TEMPLATE = lib +CONFIG += staticlib +CONFIG += debug + +target.extra = true + +exists(../../conf.pri) { + include(../../conf.pri) +} + +JINGLE_CPP = . +INCLUDEPATH += $$JINGLE_CPP $$JINGLE_CPP/talk/third_party/mediastreamer +DEFINES += POSIX +OBJECTS_DIR = $$JINGLE_CPP/.obj + +# Base +SOURCES += \ + $$JINGLE_CPP/talk/base/asyncpacketsocket.cc \ + $$JINGLE_CPP/talk/base/asynctcpsocket.cc \ + $$JINGLE_CPP/talk/base/asyncudpsocket.cc \ + $$JINGLE_CPP/talk/base/base64.cc \ + $$JINGLE_CPP/talk/base/bytebuffer.cc \ + $$JINGLE_CPP/talk/base/md5c.c \ + $$JINGLE_CPP/talk/base/messagequeue.cc \ + $$JINGLE_CPP/talk/base/network.cc \ + $$JINGLE_CPP/talk/base/physicalsocketserver.cc \ + $$JINGLE_CPP/talk/base/socketadapters.cc \ + $$JINGLE_CPP/talk/base/socketaddress.cc \ + $$JINGLE_CPP/talk/base/task.cc \ + $$JINGLE_CPP/talk/base/taskrunner.cc \ + $$JINGLE_CPP/talk/base/thread.cc \ + $$JINGLE_CPP/talk/base/time.cc + +# Not needed ? +#$$JINGLE_CPP/talk/base/socketaddresspair.cc \ +#$$JINGLE_CPP/talk/base/host.cc \ + +# P2P Base +SOURCES += \ + $$JINGLE_CPP/talk/p2p/base/helpers.cc \ + $$JINGLE_CPP/talk/p2p/base/p2psocket.cc \ + $$JINGLE_CPP/talk/p2p/base/port.cc \ + $$JINGLE_CPP/talk/p2p/base/relayport.cc \ + $$JINGLE_CPP/talk/p2p/base/session.cc \ + $$JINGLE_CPP/talk/p2p/base/sessionmanager.cc \ + $$JINGLE_CPP/talk/p2p/base/socketmanager.cc \ + $$JINGLE_CPP/talk/p2p/base/stun.cc \ + $$JINGLE_CPP/talk/p2p/base/stunport.cc \ + $$JINGLE_CPP/talk/p2p/base/stunrequest.cc \ + $$JINGLE_CPP/talk/p2p/base/tcpport.cc \ + $$JINGLE_CPP/talk/p2p/base/udpport.cc + +# P2P Client +SOURCES += \ + $$JINGLE_CPP/talk/p2p/client/basicportallocator.cc \ + $$JINGLE_CPP/talk/p2p/client/sessionclient.cc \ + $$JINGLE_CPP/talk/p2p/client/socketmonitor.cc + + +# XMLLite +SOURCES += \ + $$JINGLE_CPP/talk/xmllite/qname.cc \ + $$JINGLE_CPP/talk/xmllite/xmlbuilder.cc \ + $$JINGLE_CPP/talk/xmllite/xmlconstants.cc \ + $$JINGLE_CPP/talk/xmllite/xmlelement.cc \ + $$JINGLE_CPP/talk/xmllite/xmlnsstack.cc \ + $$JINGLE_CPP/talk/xmllite/xmlparser.cc \ + $$JINGLE_CPP/talk/xmllite/xmlprinter.cc + +# XMPP +SOURCES += \ + $$JINGLE_CPP/talk/xmpp/constants.cc \ + $$JINGLE_CPP/talk/xmpp/jid.cc \ + $$JINGLE_CPP/talk/xmpp/saslmechanism.cc \ + $$JINGLE_CPP/talk/xmpp/xmppclient.cc \ + $$JINGLE_CPP/talk/xmpp/xmppengineimpl.cc \ + $$JINGLE_CPP/talk/xmpp/xmppengineimpl_iq.cc \ + $$JINGLE_CPP/talk/xmpp/xmpplogintask.cc \ + $$JINGLE_CPP/talk/xmpp/xmppstanzaparser.cc \ + $$JINGLE_CPP/talk/xmpp/xmpptask.cc + +# Session +SOURCES += \ + $$JINGLE_CPP/talk/session/phone/call.cc \ + $$JINGLE_CPP/talk/session/phone/audiomonitor.cc \ + $$JINGLE_CPP/talk/session/phone/phonesessionclient.cc \ + $$JINGLE_CPP/talk/session/phone/channelmanager.cc \ + $$JINGLE_CPP/talk/session/phone/linphonemediaengine.cc \ + $$JINGLE_CPP/talk/session/phone/voicechannel.cc + +#contains(DEFINES, HAVE_PORTAUDIO) { +# SOURCES += \ +# $$JINGLE_CPP/talk/session/phone/portaudiomediaengine.cc +#} + + +# Mediastreamer +SOURCES += \ + $$JINGLE_CPP/talk/third_party/mediastreamer/audiostream.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/ms.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msAlawdec.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msAlawenc.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msbuffer.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mscodec.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mscopy.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msfdispatcher.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msfifo.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msfilter.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msilbcdec.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msilbcenc.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msMUlawdec.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msMUlawenc.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msnosync.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msossread.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msosswrite.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msqdispatcher.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msqueue.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msread.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msringplayer.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msrtprecv.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msrtpsend.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mssoundread.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mssoundwrite.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msspeexdec.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msspeexenc.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mssync.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mstimer.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mswrite.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/sndcard.c + +contains(DEFINES, HAVE_ALSA_ASOUNDLIB_H) { + SOURCES += $$JINGLE_CPP/talk/third_party/mediastreamer/alsacard.c +} + +contains(DEFINES, HAVE_PORTAUDIO) { + SOURCES += $$JINGLE_CPP/talk/third_party/mediastreamer/portaudiocard.c +} + +#$$JINGLE_CPP/talk/third_party/mediastreamer/osscard.c \ +#$$JINGLE_CPP/talk/third_party/mediastreamer/jackcard.c \ +#$$JINGLE_CPP/talk/third_party/mediastreamer/hpuxsndcard.c \ + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/Makefile.am new file mode 100644 index 00000000..2a845dc0 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=base p2p xmllite xmpp session third_party diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/base/Makefile.am new file mode 100644 index 00000000..2921049a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/Makefile.am @@ -0,0 +1,62 @@ +## Does not compile with final +KDE_OPTIONS = nofinal + +libcricketbase_la_SOURCES = socketaddress.cc \ + jtime.cc \ + asyncudpsocket.cc \ + messagequeue.cc \ + thread.cc \ + physicalsocketserver.cc \ + bytebuffer.cc \ + asyncpacketsocket.cc \ + network.cc \ + asynctcpsocket.cc \ + socketadapters.cc \ + md5c.c \ + base64.cc \ + task.cc \ + taskrunner.cc \ + host.cc \ + socketaddresspair.cc + +noinst_HEADERS = asyncfile.h \ + common.h \ + asyncpacketsocket.h \ + socketfactory.h \ + asyncsocket.h \ + socket.h \ + asynctcpsocket.h \ + linked_ptr.h \ + asyncudpsocket.h \ + logging.h \ + socketserver.h \ + base64.h \ + md5.h \ + stl_decl.h \ + basicdefs.h \ + messagequeue.h \ + basictypes.h \ + stringutils.h \ + bytebuffer.h \ + task.h \ + byteorder.h \ + taskrunner.h \ + criticalsection.h \ + network.h \ + thread.h \ + jtime.h \ + physicalsocketserver.h \ + proxyinfo.h \ + host.h \ + scoped_ptr.h \ + sigslot.h \ + winping.h \ + socketadapters.h \ + socketaddress.h \ + host.h \ + socketaddresspair.h + +AM_CPPFLAGS = -DPOSIX -I$(srcdir)/../.. -I$(top_builddir) $(all_includes) +noinst_LTLIBRARIES = libcricketbase.la +DEFAULT_INCLUDES = -I$(srcdir)/../.. + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncfile.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncfile.h new file mode 100644 index 00000000..0faac9ea --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncfile.h @@ -0,0 +1,56 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRICKET_BASE_ASYNCFILEH_H__ +#define CRICKET_BASE_ASYNCFILEH_H__ + +#include "talk/base/sigslot.h" + +namespace cricket { + +// Provides the ability to perform file I/O asynchronously. +// TODO: Create a common base class with AsyncSocket. +class AsyncFile { +public: + virtual ~AsyncFile() {} + + // Determines whether the file will receive read events. + virtual bool readable() = 0; + virtual void set_readable(bool value) = 0; + + // Determines whether the file will receive write events. + virtual bool writable() = 0; + virtual void set_writable(bool value) = 0; + + sigslot::signal1 SignalReadEvent; + sigslot::signal1 SignalWriteEvent; + sigslot::signal2 SignalCloseEvent; +}; + +} // namespace cricket + +#endif // CRICKET_BASE_ASYNCFILEH_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.cc new file mode 100644 index 00000000..10cfa617 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.cc @@ -0,0 +1,83 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/asyncpacketsocket.h" + +namespace cricket { + +AsyncPacketSocket::AsyncPacketSocket(AsyncSocket* socket) : socket_(socket) { +} + +AsyncPacketSocket::~AsyncPacketSocket() { + delete socket_; +} + +SocketAddress AsyncPacketSocket::GetLocalAddress() const { + return socket_->GetLocalAddress(); +} + +SocketAddress AsyncPacketSocket::GetRemoteAddress() const { + return socket_->GetRemoteAddress(); +} + +int AsyncPacketSocket::Bind(const SocketAddress& addr) { + return socket_->Bind(addr); +} + +int AsyncPacketSocket::Connect(const SocketAddress& addr) { + return socket_->Connect(addr); +} + +int AsyncPacketSocket::Send(const void *pv, size_t cb) { + return socket_->Send(pv, cb); +} + +int AsyncPacketSocket::SendTo( + const void *pv, size_t cb, const SocketAddress& addr) { + return socket_->SendTo(pv, cb, addr); +} + +int AsyncPacketSocket::Close() { + return socket_->Close(); +} + +int AsyncPacketSocket::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int AsyncPacketSocket::GetError() const { + return socket_->GetError(); +} + +void AsyncPacketSocket::SetError(int error) { + return socket_->SetError(error); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.h new file mode 100644 index 00000000..b5119c8d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.h @@ -0,0 +1,63 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __ASYNCPACKETSOCKET_H__ +#define __ASYNCPACKETSOCKET_H__ + +#include "talk/base/asyncsocket.h" + +namespace cricket { + +// Provides the ability to receive packets asynchronously. Sends are not +// buffered since it is acceptable to drop packets under high load. +class AsyncPacketSocket : public sigslot::has_slots<> { +public: + AsyncPacketSocket(AsyncSocket* socket); + virtual ~AsyncPacketSocket(); + + // Relevant socket methods: + virtual SocketAddress GetLocalAddress() const; + virtual SocketAddress GetRemoteAddress() const; + virtual int Bind(const SocketAddress& addr); + virtual int Connect(const SocketAddress& addr); + virtual int Send(const void *pv, size_t cb); + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr); + virtual int Close(); + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError() const; + virtual void SetError(int error); + + // Emitted each time a packet is read. + sigslot::signal4 SignalReadPacket; + +protected: + AsyncSocket* socket_; +}; + +} // namespace cricket + +#endif // __ASYNCPACKETSOCKET_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncsocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncsocket.h new file mode 100644 index 00000000..d6404232 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncsocket.h @@ -0,0 +1,91 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __ASYNCSOCKET_H__ +#define __ASYNCSOCKET_H__ + +#include "talk/base/sigslot.h" +#include "talk/base/socket.h" + +namespace cricket { + +// Provides the ability to perform socket I/O asynchronously. +class AsyncSocket : public Socket, public sigslot::has_slots<> { +public: + virtual ~AsyncSocket() {} + + sigslot::signal1 SignalReadEvent; // ready to read + sigslot::signal1 SignalWriteEvent; // ready to write + sigslot::signal1 SignalConnectEvent; // connected + sigslot::signal2 SignalCloseEvent; // closed + // TODO: error +}; + +class AsyncSocketAdapter : public AsyncSocket { +public: + AsyncSocketAdapter(Socket * socket) : socket_(socket) { + } + AsyncSocketAdapter(AsyncSocket * socket) : socket_(socket) { + socket->SignalConnectEvent.connect(this, &AsyncSocketAdapter::OnConnectEvent); + socket->SignalReadEvent.connect(this, &AsyncSocketAdapter::OnReadEvent); + socket->SignalWriteEvent.connect(this, &AsyncSocketAdapter::OnWriteEvent); + socket->SignalCloseEvent.connect(this, &AsyncSocketAdapter::OnCloseEvent); + } + virtual ~AsyncSocketAdapter() { delete socket_; } + + virtual SocketAddress GetLocalAddress() const { return socket_->GetLocalAddress(); } + virtual SocketAddress GetRemoteAddress() const { return socket_->GetRemoteAddress(); } + + virtual int Bind(const SocketAddress& addr) { return socket_->Bind(addr); } + virtual int Connect(const SocketAddress& addr) { return socket_->Connect(addr); } + virtual int Send(const void *pv, size_t cb) { return socket_->Send(pv, cb); } + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) { return socket_->SendTo(pv, cb, addr); } + virtual int Recv(void *pv, size_t cb) { return socket_->Recv(pv, cb); } + virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) { return socket_->RecvFrom(pv, cb, paddr); } + virtual int Listen(int backlog) { return socket_->Listen(backlog); } + virtual Socket *Accept(SocketAddress *paddr) { return socket_->Accept(paddr); } + virtual int Close() { return socket_->Close(); } + virtual int GetError() const { return socket_->GetError(); } + virtual void SetError(int error) { return socket_->SetError(error); } + + virtual ConnState GetState() const { return socket_->GetState(); } + + virtual int EstimateMTU(uint16* mtu) { return socket_->EstimateMTU(mtu); } + virtual int SetOption(Option opt, int value) { return socket_->SetOption(opt, value); } + +protected: + virtual void OnConnectEvent(AsyncSocket * socket) { SignalConnectEvent(this); } + virtual void OnReadEvent(AsyncSocket * socket) { SignalReadEvent(this); } + virtual void OnWriteEvent(AsyncSocket * socket) { SignalWriteEvent(this); } + virtual void OnCloseEvent(AsyncSocket * socket, int err) { SignalCloseEvent(this, err); } + + Socket * socket_; +}; + +} // namespace cricket + +#endif // __ASYNCSOCKET_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.cc new file mode 100644 index 00000000..6d4697a6 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.cc @@ -0,0 +1,197 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/asynctcpsocket.h" +#include "talk/base/byteorder.h" +#include "talk/base/common.h" +#include "talk/base/logging.h" + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +const size_t MAX_PACKET_SIZE = 64 * 1024; + +typedef uint16 PacketLength; +const size_t PKT_LEN_SIZE = sizeof(PacketLength); + +const size_t BUF_SIZE = MAX_PACKET_SIZE + PKT_LEN_SIZE; + +AsyncTCPSocket::AsyncTCPSocket(AsyncSocket* socket) : AsyncPacketSocket(socket), insize_(BUF_SIZE), inpos_(0), outsize_(BUF_SIZE), outpos_(0) { + inbuf_ = new char[insize_]; + outbuf_ = new char[outsize_]; + + ASSERT(socket_ != NULL); + socket_->SignalConnectEvent.connect(this, &AsyncTCPSocket::OnConnectEvent); + socket_->SignalReadEvent.connect(this, &AsyncTCPSocket::OnReadEvent); + socket_->SignalWriteEvent.connect(this, &AsyncTCPSocket::OnWriteEvent); + socket_->SignalCloseEvent.connect(this, &AsyncTCPSocket::OnCloseEvent); +} + +AsyncTCPSocket::~AsyncTCPSocket() { + delete [] inbuf_; + delete [] outbuf_; +} + +int AsyncTCPSocket::Send(const void *pv, size_t cb) { + if (cb > MAX_PACKET_SIZE) { + socket_->SetError(EMSGSIZE); + return -1; + } + + // If we are blocking on send, then silently drop this packet + if (outpos_) + return static_cast(cb); + + PacketLength pkt_len = HostToNetwork16(static_cast(cb)); + memcpy(outbuf_, &pkt_len, PKT_LEN_SIZE); + memcpy(outbuf_ + PKT_LEN_SIZE, pv, cb); + outpos_ = PKT_LEN_SIZE + cb; + + int res = Flush(); + if (res <= 0) { + // drop packet if we made no progress + outpos_ = 0; + return res; + } + + // We claim to have sent the whole thing, even if we only sent partial + return static_cast(cb); +} + +int AsyncTCPSocket::SendTo(const void *pv, size_t cb, const SocketAddress& addr) { + if (addr == GetRemoteAddress()) + return Send(pv, cb); + + ASSERT(false); + socket_->SetError(ENOTCONN); + return -1; +} + +int AsyncTCPSocket::SendRaw(const void * pv, size_t cb) { + if (outpos_ + cb > outsize_) { + socket_->SetError(EMSGSIZE); + return -1; + } + + memcpy(outbuf_ + outpos_, pv, cb); + outpos_ += cb; + + return Flush(); +} + +void AsyncTCPSocket::ProcessInput(char * data, size_t& len) { + SocketAddress remote_addr(GetRemoteAddress()); + + while (true) { + if (len < PKT_LEN_SIZE) + return; + + PacketLength pkt_len; + memcpy(&pkt_len, data, PKT_LEN_SIZE); + pkt_len = NetworkToHost16(pkt_len); + + if (len < PKT_LEN_SIZE + pkt_len) + return; + + SignalReadPacket(data + PKT_LEN_SIZE, pkt_len, remote_addr, this); + + len -= PKT_LEN_SIZE + pkt_len; + if (len > 0) { + memmove(data, data + PKT_LEN_SIZE + pkt_len, len); + } + } +} + +int AsyncTCPSocket::Flush() { + int res = socket_->Send(outbuf_, outpos_); + if (res <= 0) { + return res; + } + if (static_cast(res) <= outpos_) { + outpos_ -= res; + } else { + ASSERT(false); + return -1; + } + if (outpos_ > 0) { + memmove(outbuf_, outbuf_ + res, outpos_); + } + return res; +} + +void AsyncTCPSocket::OnConnectEvent(AsyncSocket* socket) { + SignalConnect(this); +} + +void AsyncTCPSocket::OnReadEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + + int len = socket_->Recv(inbuf_ + inpos_, insize_ - inpos_); + if (len < 0) { + // TODO: Do something better like forwarding the error to the user. + LOG(INFO) << "recvfrom: " << errno << " " << std::strerror(errno); + return; + } + + inpos_ += len; + + ProcessInput(inbuf_, inpos_); + + if (inpos_ >= insize_) { + LOG(INFO) << "input buffer overflow"; + ASSERT(false); + inpos_ = 0; + } +} + +void AsyncTCPSocket::OnWriteEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + + if (outpos_ > 0) { + Flush(); + } +} + +void AsyncTCPSocket::OnCloseEvent(AsyncSocket* socket, int error) { + SignalClose(this, error); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.h new file mode 100644 index 00000000..e93e5e7d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.h @@ -0,0 +1,68 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __ASYNCTCPSOCKET_H__ +#define __ASYNCTCPSOCKET_H__ + +#include "talk/base/asyncpacketsocket.h" + +namespace cricket { + +// Simulates UDP semantics over TCP. Send and Recv packet sizes +// are preserved, and drops packets silently on Send, rather than +// buffer them in user space. +class AsyncTCPSocket : public AsyncPacketSocket { +public: + AsyncTCPSocket(AsyncSocket* socket); + virtual ~AsyncTCPSocket(); + + virtual int Send(const void *pv, size_t cb); + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr); + + sigslot::signal1 SignalConnect; + sigslot::signal2 SignalClose; + +protected: + int SendRaw(const void * pv, size_t cb); + virtual void ProcessInput(char * data, size_t& len); + +private: + char* inbuf_, * outbuf_; + size_t insize_, inpos_, outsize_, outpos_; + + int Flush(); + + // Called by the underlying socket + void OnConnectEvent(AsyncSocket* socket); + void OnReadEvent(AsyncSocket* socket); + void OnWriteEvent(AsyncSocket* socket); + void OnCloseEvent(AsyncSocket* socket, int error); +}; + +} // namespace cricket + +#endif // __ASYNCSTCPOCKET_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.cc new file mode 100644 index 00000000..5b8c2466 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.cc @@ -0,0 +1,83 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/asyncudpsocket.h" +#include "talk/base/logging.h" +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +const int BUF_SIZE = 64 * 1024; + +AsyncUDPSocket::AsyncUDPSocket(AsyncSocket* socket) : AsyncPacketSocket(socket) { + size_ = BUF_SIZE; + buf_ = new char[size_]; + + assert(socket_); + // The socket should start out readable but not writable. + socket_->SignalReadEvent.connect(this, &AsyncUDPSocket::OnReadEvent); +} + +AsyncUDPSocket::~AsyncUDPSocket() { + delete [] buf_; +} + +void AsyncUDPSocket::OnReadEvent(AsyncSocket* socket) { + assert(socket == socket_); + + SocketAddress remote_addr; + int len = socket_->RecvFrom(buf_, size_, &remote_addr); + if (len < 0) { + // TODO: Do something better like forwarding the error to the user. + PLOG(LS_ERROR, socket_->GetError()) << "recvfrom"; + return; + } + + // TODO: Make sure that we got all of the packet. If we did not, then we + // should resize our buffer to be large enough. + + SignalReadPacket(buf_, (size_t)len, remote_addr, this); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.h new file mode 100644 index 00000000..7fac7713 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.h @@ -0,0 +1,59 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __ASYNCUDPSOCKET_H__ +#define __ASYNCUDPSOCKET_H__ + +#include "talk/base/asyncpacketsocket.h" +#include "talk/base/socketfactory.h" + +namespace cricket { + +// Provides the ability to receive packets asynchronously. Sends are not +// buffered since it is acceptable to drop packets under high load. +class AsyncUDPSocket : public AsyncPacketSocket { +public: + AsyncUDPSocket(AsyncSocket* socket); + virtual ~AsyncUDPSocket(); + +private: + char* buf_; + size_t size_; + + // Called when the underlying socket is ready to be read from. + void OnReadEvent(AsyncSocket* socket); +}; + +// Creates a new socket for sending asynchronous UDP packets using an +// asynchronous socket from the given factory. +inline AsyncUDPSocket* CreateAsyncUDPSocket(SocketFactory* factory) { + return new AsyncUDPSocket(factory->CreateAsyncSocket(SOCK_DGRAM)); +} + +} // namespace cricket + +#endif // __ASYNCSUDPOCKET_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/base64.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/base64.cc new file mode 100644 index 00000000..e0ec1b90 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/base64.cc @@ -0,0 +1,194 @@ + +//********************************************************************* +//* Base64 - a simple base64 encoder and decoder. +//* +//* Copyright (c) 1999, Bob Withers - bwit@pobox.com +//* +//* This code may be freely used for any purpose, either personal +//* or commercial, provided the authors copyright notice remains +//* intact. +//* +//* Enhancements by Stanley Yamane: +//* o reverse lookup table for the decode function +//* o reserve string buffer space in advance +//* +//********************************************************************* + +#include "talk/base/base64.h" + +using namespace std; + +static const char fillchar = '='; +static const string::size_type np = string::npos; + +const string Base64::Base64Table( + // 0000000000111111111122222222223333333333444444444455555555556666 + // 0123456789012345678901234567890123456789012345678901234567890123 + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + +// Decode Table gives the index of any valid base64 character in the Base64 table] +// 65 == A, 97 == a, 48 == 0, 43 == +, 47 == / + + // 0 1 2 3 4 5 6 7 8 9 +const string::size_type Base64::DecodeTable[] = { + np,np,np,np,np,np,np,np,np,np, // 0 - 9 + np,np,np,np,np,np,np,np,np,np, //10 -19 + np,np,np,np,np,np,np,np,np,np, //20 -29 + np,np,np,np,np,np,np,np,np,np, //30 -39 + np,np,np,62,np,np,np,63,52,53, //40 -49 + 54,55,56,57,58,59,60,61,np,np, //50 -59 + np,np,np,np,np, 0, 1, 2, 3, 4, //60 -69 + 5, 6, 7, 8, 9,10,11,12,13,14, //70 -79 + 15,16,17,18,19,20,21,22,23,24, //80 -89 + 25,np,np,np,np,np,np,26,27,28, //90 -99 + 29,30,31,32,33,34,35,36,37,38, //100 -109 + 39,40,41,42,43,44,45,46,47,48, //110 -119 + 49,50,51,np,np,np,np,np,np,np, //120 -129 + np,np,np,np,np,np,np,np,np,np, //130 -139 + np,np,np,np,np,np,np,np,np,np, //140 -149 + np,np,np,np,np,np,np,np,np,np, //150 -159 + np,np,np,np,np,np,np,np,np,np, //160 -169 + np,np,np,np,np,np,np,np,np,np, //170 -179 + np,np,np,np,np,np,np,np,np,np, //180 -189 + np,np,np,np,np,np,np,np,np,np, //190 -199 + np,np,np,np,np,np,np,np,np,np, //200 -209 + np,np,np,np,np,np,np,np,np,np, //210 -219 + np,np,np,np,np,np,np,np,np,np, //220 -229 + np,np,np,np,np,np,np,np,np,np, //230 -239 + np,np,np,np,np,np,np,np,np,np, //240 -249 + np,np,np,np,np,np //250 -256 +}; + +string Base64::encodeFromArray(const char * data, size_t len) { + size_t i; + char c; + string ret; + + ret.reserve(len * 2); + + for (i = 0; i < len; ++i) + { + c = (data[i] >> 2) & 0x3f; + ret.append(1, Base64Table[c]); + c = (data[i] << 4) & 0x3f; + if (++i < len) + c |= (data[i] >> 4) & 0x0f; + + ret.append(1, Base64Table[c]); + if (i < len) + { + c = (data[i] << 2) & 0x3f; + if (++i < len) + c |= (data[i] >> 6) & 0x03; + + ret.append(1, Base64Table[c]); + } + else + { + ++i; + ret.append(1, fillchar); + } + + if (i < len) + { + c = data[i] & 0x3f; + ret.append(1, Base64Table[c]); + } + else + { + ret.append(1, fillchar); + } + } + + return(ret); +} + + +string Base64::encode(const string& data) +{ + string::size_type i; + char c; + string::size_type len = data.length(); + string ret; + + ret.reserve(len * 2); + + for (i = 0; i < len; ++i) + { + c = (data[i] >> 2) & 0x3f; + ret.append(1, Base64Table[c]); + c = (data[i] << 4) & 0x3f; + if (++i < len) + c |= (data[i] >> 4) & 0x0f; + + ret.append(1, Base64Table[c]); + if (i < len) + { + c = (data[i] << 2) & 0x3f; + if (++i < len) + c |= (data[i] >> 6) & 0x03; + + ret.append(1, Base64Table[c]); + } + else + { + ++i; + ret.append(1, fillchar); + } + + if (i < len) + { + c = data[i] & 0x3f; + ret.append(1, Base64Table[c]); + } + else + { + ret.append(1, fillchar); + } + } + + return(ret); +} + +string Base64::decode(const string& data) +{ + string::size_type i; + char c; + char c1; + string::size_type len = data.length(); + string ret; + + ret.reserve(len); + + for (i = 0; i < len; ++i) + { + c = static_cast(DecodeTable[static_cast(data[i])]); + ++i; + c1 = static_cast(DecodeTable[static_cast(data[i])]); + c = (c << 2) | ((c1 >> 4) & 0x3); + ret.append(1, c); + if (++i < len) + { + c = data[i]; + if (fillchar == c) + break; + + c = static_cast(DecodeTable[static_cast(data[i])]); + c1 = ((c1 << 4) & 0xf0) | ((c >> 2) & 0xf); + ret.append(1, c1); + } + + if (++i < len) + { + c1 = data[i]; + if (fillchar == c1) + break; + + c1 = static_cast(DecodeTable[static_cast(data[i])]); + c = ((c << 6) & 0xc0) | c1; + ret.append(1, c); + } + } + + return(ret); +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/base64.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/base64.h new file mode 100644 index 00000000..4622b329 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/base64.h @@ -0,0 +1,29 @@ + +//********************************************************************* +//* C_Base64 - a simple base64 encoder and decoder. +//* +//* Copyright (c) 1999, Bob Withers - bwit@pobox.com +//* +//* This code may be freely used for any purpose, either personal +//* or commercial, provided the authors copyright notice remains +//* intact. +//********************************************************************* + +#ifndef Base64_H +#define Base64_H + +#include +using std::string; // comment if your compiler doesn't use namespaces + +class Base64 +{ +public: + static string encode(const string & data); + static string decode(const string & data); + static string encodeFromArray(const char * data, size_t len); +private: + static const string Base64Table; + static const string::size_type DecodeTable[]; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/basicdefs.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/basicdefs.h new file mode 100644 index 00000000..171bc9f9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/basicdefs.h @@ -0,0 +1,53 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __BASICDEFS_H__ +#define __BASICDEFS_H__ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + + +// Used in GUI when referring to the product name (& Version Resource Product Name) +#define PRODUCT_NAME "Google Talk" + +// Used in GUI when referring to the publisher of the product +#define COMPANY_NAME "Google" + +// Used in filenames, directories, registry key names, etc to refer to the product +#define DIRECTORY_NAME "Google Talk" + +// Used in URLs, registry values, etc, where we prefer not to use a space +#define PRODUCT_SIGNATURE "googletalk" + +// Used whenever we do HTTP +#define USERAGENT_STRING "Google Talk" + +#define ARRAY_SIZE(x) (static_cast((sizeof(x)/sizeof(x[0])))) + +#endif // __BASICDEFS_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/basictypes.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/basictypes.h new file mode 100644 index 00000000..ea393c09 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/basictypes.h @@ -0,0 +1,85 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __BASICTYPES_H__ +#define __BASICTYPES_H__ + +#ifdef COMPILER_MSVC +typedef __int64 int64; +#else +typedef long long int64; +#endif /* COMPILER_MSVC */ +typedef long int32; +typedef short int16; +typedef char int8; + +#ifdef COMPILER_MSVC +typedef unsigned __int64 uint64; +typedef __int64 int64; +#else +typedef unsigned long long uint64; +typedef long long int64; +#endif /* COMPILER_MSVC */ +typedef unsigned long uint32; +typedef unsigned short uint16; +typedef unsigned char uint8; + +#ifdef WIN32 +typedef int socklen_t; +#endif + +namespace cricket { + template inline T _min(T a, T b) { return (a > b) ? b : a; } + template inline T _max(T a, T b) { return (a < b) ? b : a; } +} + +// A macro to disallow the evil copy constructor and operator= functions +// This should be used in the private: declarations for a class +#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +// +// This should be used in the private: declarations for a class +// that wants to prevent anyone from instantiating it. This is +// especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + DISALLOW_EVIL_CONSTRUCTORS(TypeName) + +#ifndef UNUSED +#define UNUSED(x) Unused(static_cast(&x)) +#define UNUSED2(x,y) Unused(static_cast(&x)); Unused(static_cast(&y)) +#define UNUSED3(x,y,z) Unused(static_cast(&x)); Unused(static_cast(&y)); Unused(static_cast(&z)) +#define UNUSED4(x,y,z,a) Unused(static_cast(&x)); Unused(static_cast(&y)); Unused(static_cast(&z)); Unused(static_cast(&a)) +#define UNUSED5(x,y,z,a,b) Unused(static_cast(&x)); Unused(static_cast(&y)); Unused(static_cast(&z)); Unused(static_cast(&a)); Unused(static_cast(&b)) +inline void Unused(const void *) { } +#endif // UNUSED + +#endif // __BASICTYPES_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.cc new file mode 100644 index 00000000..067f50ed --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.cc @@ -0,0 +1,165 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/basictypes.h" +#include "talk/base/bytebuffer.h" +#include "talk/base/byteorder.h" +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::memcpy; +} +#endif + +namespace cricket { + +const int DEFAULT_SIZE = 4096; + +ByteBuffer::ByteBuffer() { + start_ = 0; + end_ = 0; + size_ = DEFAULT_SIZE; + bytes_ = new char[size_]; +} + +ByteBuffer::ByteBuffer(const char* bytes, size_t len) { + start_ = 0; + end_ = len; + size_ = len; + bytes_ = new char[size_]; + memcpy(bytes_, bytes, end_); +} + +ByteBuffer::ByteBuffer(const char* bytes) { + start_ = 0; + end_ = strlen(bytes); + size_ = end_; + bytes_ = new char[size_]; + memcpy(bytes_, bytes, end_); +} + +ByteBuffer::~ByteBuffer() { + delete bytes_; +} + +bool ByteBuffer::ReadUInt8(uint8& val) { + return ReadBytes(reinterpret_cast(&val), 1); +} + +bool ByteBuffer::ReadUInt16(uint16& val) { + uint16 v; + if (!ReadBytes(reinterpret_cast(&v), 2)) { + return false; + } else { + val = NetworkToHost16(v); + return true; + } +} + +bool ByteBuffer::ReadUInt32(uint32& val) { + uint32 v; + if (!ReadBytes(reinterpret_cast(&v), 4)) { + return false; + } else { + val = NetworkToHost32(v); + return true; + } +} + +bool ByteBuffer::ReadString(std::string& val, size_t len) { + if (len > Length()) { + return false; + } else { + val.append(bytes_ + start_, len); + start_ += len; + return true; + } +} + +bool ByteBuffer::ReadBytes(char* val, size_t len) { + if (len > Length()) { + return false; + } else { + memcpy(val, bytes_ + start_, len); + start_ += len; + return true; + } +} + +void ByteBuffer::WriteUInt8(uint8 val) { + WriteBytes(reinterpret_cast(&val), 1); +} + +void ByteBuffer::WriteUInt16(uint16 val) { + uint16 v = HostToNetwork16(val); + WriteBytes(reinterpret_cast(&v), 2); +} + +void ByteBuffer::WriteUInt32(uint32 val) { + uint32 v = HostToNetwork32(val); + WriteBytes(reinterpret_cast(&v), 4); +} + +void ByteBuffer::WriteString(const std::string& val) { + WriteBytes(val.c_str(), val.size()); +} + +void ByteBuffer::WriteBytes(const char* val, size_t len) { + if (Length() + len > Capacity()) + Resize(Length() + len); + + memcpy(bytes_ + end_, val, len); + end_ += len; +} + +void ByteBuffer::Resize(size_t size) { + if (size > size_) + size = _max(size, 3 * size_ / 2); + + size_t len = _min(end_ - start_, size); + char* new_bytes = new char[size]; + memcpy(new_bytes, bytes_ + start_, len); + delete [] bytes_; + + start_ = 0; + end_ = len; + size_ = size; + bytes_ = new_bytes; +} + +void ByteBuffer::Shift(size_t size) { + if (size > Length()) + return; + + end_ = Length() - size; + memmove(bytes_, bytes_ + start_ + size, end_); + start_ = 0; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.h new file mode 100644 index 00000000..b0a52344 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.h @@ -0,0 +1,71 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __BYTEBUFFER_H__ +#define __BYTEBUFFER_H__ + +#include "talk/base/basictypes.h" +#include + +namespace cricket { + +class ByteBuffer { +public: + ByteBuffer(); + ByteBuffer(const char* bytes, size_t len); + ByteBuffer(const char* bytes); // uses strlen + ~ByteBuffer(); + + const char* Data() const { return bytes_ + start_; } + size_t Length() { return end_ - start_; } + size_t Capacity() { return size_ - start_; } + + bool ReadUInt8(uint8& val); + bool ReadUInt16(uint16& val); + bool ReadUInt32(uint32& val); + bool ReadString(std::string& val, size_t len); // append to val + bool ReadBytes(char* val, size_t len); + + void WriteUInt8(uint8 val); + void WriteUInt16(uint16 val); + void WriteUInt32(uint32 val); + void WriteString(const std::string& val); + void WriteBytes(const char* val, size_t len); + + void Resize(size_t size); + void Shift(size_t size); + +private: + char* bytes_; + size_t size_; + size_t start_; + size_t end_; +}; + +} // namespace cricket + +#endif // __BYTEBUFFER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/byteorder.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/byteorder.h new file mode 100644 index 00000000..4b70f47e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/byteorder.h @@ -0,0 +1,63 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __BYTEORDER_H__ +#define __BYTEORDER_H__ + +#include "talk/base/basictypes.h" + +#ifdef POSIX +extern "C" { +#include +} +#endif + +#ifdef WIN32 +#include +#endif + +namespace cricket { + +inline uint16 HostToNetwork16(uint16 n) { + return htons(n); +} + +inline uint32 HostToNetwork32(uint32 n) { + return htonl(n); +} + +inline uint16 NetworkToHost16(uint16 n) { + return ntohs(n); +} + +inline uint32 NetworkToHost32(uint32 n) { + return ntohl(n); +} + +} // namespace cricket + +#endif // __BYTEORDER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/common.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/common.h new file mode 100644 index 00000000..b21be2f1 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/common.h @@ -0,0 +1,231 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _common_h_ +#define _common_h_ + +#if defined(_MSC_VER) +// warning C4355: 'this' : used in base member initializer list +#pragma warning(disable:4355) +#endif + +#if defined(ENABLE_DEBUG_MALLOC) && !defined(ENABLE_DEBUG) +#define ENABLE_DEBUG 1 +#endif + +////////////////////////////////////////////////////////////////////// +// General Utilities +////////////////////////////////////////////////////////////////////// + +#ifndef UNUSED +#define UNUSED(x) Unused(static_cast(&x)) +#define UNUSED2(x,y) Unused(static_cast(&x)); Unused(static_cast(&y)) +#define UNUSED3(x,y,z) Unused(static_cast(&x)); Unused(static_cast(&y)); Unused(static_cast(&z)) +#define UNUSED4(x,y,z,a) Unused(static_cast(&x)); Unused(static_cast(&y)); Unused(static_cast(&z)); Unused(static_cast(&a)) +#define UNUSED5(x,y,z,a,b) Unused(static_cast(&x)); Unused(static_cast(&y)); Unused(static_cast(&z)); Unused(static_cast(&a)); Unused(static_cast(&b)) +inline void Unused(const void *) { } +#endif // UNUSED + +#define ARRAY_SIZE(x) (static_cast((sizeof(x)/sizeof(x[0])))) + +///////////////////////////////////////////////////////////////////////////// +// std:min/std:max on msvc +///////////////////////////////////////////////////////////////////////////// + +#if defined(_MSC_VER) && _MSC_VER <= 1200 // 1200 == VC++ 6.0 + +#undef min +#undef max + +namespace std { + + + /** + * @brief This does what you think it does. + * @param a A thing of arbitrary type. + * @param b Another thing of arbitrary type. + * @return The lesser of the parameters. + * + * This is the simple classic generic implementation. It will work on + * temporary expressions, since they are only evaluated once, unlike a + * preprocessor macro. + */ + template + inline const _Tp& + min(const _Tp& __a, const _Tp& __b) + { + //return __b < __a ? __b : __a; + if (__b < __a) return __b; return __a; + } + + /** + * @brief This does what you think it does. + * @param a A thing of arbitrary type. + * @param b Another thing of arbitrary type. + * @return The greater of the parameters. + * + * This is the simple classic generic implementation. It will work on + * temporary expressions, since they are only evaluated once, unlike a + * preprocessor macro. + */ + template + inline const _Tp& + max(const _Tp& __a, const _Tp& __b) + { + //return __a < __b ? __b : __a; + if (__a < __b) return __b; return __a; + } + + /** + * @brief This does what you think it does. + * @param a A thing of arbitrary type. + * @param b Another thing of arbitrary type. + * @param comp A @link s20_3_3_comparisons comparison functor@endlink. + * @return The lesser of the parameters. + * + * This will work on temporary expressions, since they are only evaluated + * once, unlike a preprocessor macro. + */ + template + inline const _Tp& + min(const _Tp& __a, const _Tp& __b, _Compare __comp) + { + //return __comp(__b, __a) ? __b : __a; + if (__comp(__b, __a)) return __b; return __a; + } + + /** + * @brief This does what you think it does. + * @param a A thing of arbitrary type. + * @param b Another thing of arbitrary type. + * @param comp A @link s20_3_3_comparisons comparison functor@endlink. + * @return The greater of the parameters. + * + * This will work on temporary expressions, since they are only evaluated + * once, unlike a preprocessor macro. + */ + template + inline const _Tp& + max(const _Tp& __a, const _Tp& __b, _Compare __comp) + { + //return __comp(__a, __b) ? __b : __a; + if (__comp(__a, __b)) return __b; return __a; + } + +} + +#endif // _MSC_VER <= 1200 + + +///////////////////////////////////////////////////////////////////////////// +// Assertions +///////////////////////////////////////////////////////////////////////////// + +#ifdef ENABLE_DEBUG + +namespace buzz { + +// Break causes the debugger to stop executing, or the program to abort +void Break(); + +// LogAssert writes information about an assertion to the log +void LogAssert(const char * function, const char * file, int line, const char * expression); + +inline void Assert(bool result, const char * function, const char * file, int line, const char * expression) { + if (!result) { + LogAssert(function, file, line, expression); + Break(); + } +} + +}; // namespace buzz + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#define __FUNCTION__ "" +#endif + +#define ASSERT(x) buzz::Assert((x),__FUNCTION__,__FILE__,__LINE__,#x) +#define VERIFY(x) buzz::Assert((x),__FUNCTION__,__FILE__,__LINE__,#x) + +#else // !ENABLE_DEBUG + +#define ASSERT(x) (void)0 +#define VERIFY(x) (void)(x) + +#endif // !ENABLE_DEBUG + +#define COMPILE_TIME_ASSERT(expr) char CTA_UNIQUE_NAME[expr] +#define CTA_UNIQUE_NAME MAKE_NAME(__LINE__) +#define CTA_MAKE_NAME(line) MAKE_NAME2(line) +#define CTA_MAKE_NAME2(line) constraint_ ## line + +////////////////////////////////////////////////////////////////////// +// Memory leak tracking +////////////////////////////////////////////////////////////////////// + +#include + +#ifdef ENABLE_DEBUG_MALLOC + +namespace buzz { + +void * DebugAllocate(size_t size, const char * fname = 0, int line = 0); +void DebugDeallocate(void * ptr, const char * fname = 0, int line = 0); +bool LeakCheck(); +bool LeakCheckU(); +void LeakMarkBaseline(); +void LeakClearBaseline(); + +}; // namespace buzz + +inline void * operator new(size_t size, const char * fname, int line) { return buzz::DebugAllocate(size, fname, line); } +inline void operator delete(void * ptr, const char * fname, int line) { buzz::DebugDeallocate(ptr, fname, line); } + +#if !(defined(TRACK_ARRAY_ALLOC_PROBLEM) && \ + defined(_MSC_VER) && _MSC_VER <= 1200) // 1200 == VC++ 6.0 + +inline void * operator new[](size_t size, const char * fname, int line) { return buzz::DebugAllocate(size, fname, line); } +inline void operator delete[](void * ptr, const char * fname, int line) { buzz::DebugDeallocate(ptr, fname, line); } + +#endif // TRACK_ARRAY_ALLOC_PROBLEM + + +// If you put "#define new TRACK_NEW" in your .cc file after all includes, it should track the calling function name + +#define TRACK_NEW new(__FILE__,__LINE__) +#define TRACK_DEL delete(__FILE__,__LINE__) + +#else // !ENABLE_DEBUG_MALLOC + +#define TRACK_NEW new +#define TRACK_DEL delete + +#endif // !ENABLE_DEBUG_MALLOC + +////////////////////////////////////////////////////////////////////// + +#endif // _common_h_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/criticalsection.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/criticalsection.h new file mode 100644 index 00000000..b75ad5c7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/criticalsection.h @@ -0,0 +1,120 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _criticalsection_h_ +#define _criticalsection_h_ + +#ifdef WIN32 +#include "talk/base/win32.h" +#endif + +#ifdef POSIX +#include +#endif + +#ifdef _DEBUG +#define CS_TRACK_OWNER 1 +#endif // _DEBUG + +#if CS_TRACK_OWNER +#define TRACK_OWNER(x) x +#else // !CS_TRACK_OWNER +#define TRACK_OWNER(x) +#endif // !CS_TRACK_OWNER + +namespace cricket { + +#ifdef WIN32 +class CriticalSection { +public: + CriticalSection() { + InitializeCriticalSection(&crit_); + // Windows docs say 0 is not a valid thread id + TRACK_OWNER(thread_ = 0); + } + ~CriticalSection() { + DeleteCriticalSection(&crit_); + } + void Enter() { + EnterCriticalSection(&crit_); + TRACK_OWNER(thread_ = GetCurrentThreadId()); + } + void Leave() { + TRACK_OWNER(thread_ = 0); + LeaveCriticalSection(&crit_); + } + +#if CS_TRACK_OWNER + bool CurrentThreadIsOwner() const { return thread_ == GetCurrentThreadId(); } +#endif // CS_TRACK_OWNER + +private: + CRITICAL_SECTION crit_; + TRACK_OWNER(DWORD thread_); // The section's owning thread id +}; +#endif // WIN32 + +#ifdef POSIX +class CriticalSection { +public: + CriticalSection() { + pthread_mutexattr_t mutex_attribute; + pthread_mutexattr_settype(&mutex_attribute, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mutex_, &mutex_attribute); + } + ~CriticalSection() { + pthread_mutex_destroy(&mutex_); + } + void Enter() { + pthread_mutex_lock(&mutex_); + } + void Leave() { + pthread_mutex_unlock(&mutex_); + } +private: + pthread_mutex_t mutex_; +}; +#endif // POSIX + +// CritScope, for serializing exection through a scope + +class CritScope { +public: + CritScope(CriticalSection *pcrit) { + pcrit_ = pcrit; + pcrit_->Enter(); + } + ~CritScope() { + pcrit_->Leave(); + } +private: + CriticalSection *pcrit_; +}; + +} // namespace cricket + +#endif // _criticalsection_h_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/host.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/host.cc new file mode 100644 index 00000000..7b7490d9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/host.cc @@ -0,0 +1,99 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/host.h" +#include "talk/base/logging.h" +#include "talk/base/network.h" +#include "talk/base/socket.h" +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; + using ::exit; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace { + +void FatalError(const std::string& name, int err) { + PLOG(LERROR, err) << name; + std::exit(1); +} + +} + +namespace cricket { + +#ifdef POSIX +std::string GetHostName() { + struct utsname nm; + if (uname(&nm) < 0) + FatalError("uname", errno); + return std::string(nm.nodename); +} +#endif + +#ifdef WIN32 +std::string GetHostName() { + // TODO: fix this + return "cricket"; +} +#endif + +// Records information about the local host. +Host* gLocalHost = 0; + +const Host& LocalHost() { + if (!gLocalHost) { + std::vector* networks = new std::vector; + NetworkManager::CreateNetworks(*networks); +#ifdef WIN32 + // This is sort of problematic... one part of the code (the unittests) wants + // 127.0.0.1 to be present and another part (port allocators) don't. Right + // now, they use different APIs, so we can have different behavior. But + // there is something wrong with this. + networks->push_back(new Network("localhost", + SocketAddress::StringToIP("127.0.0.1"))); +#endif + gLocalHost = new Host(GetHostName(), networks); + assert(gLocalHost->networks().size() > 0); + } + + return *gLocalHost; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/host.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/host.h new file mode 100644 index 00000000..16f31a78 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/host.h @@ -0,0 +1,59 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __HOST_H__ +#define __HOST_H__ + +#include "talk/base/network.h" +#include +#include + +namespace cricket { + +// Provides information about a host in the network. +class Host { +public: + Host(const std::string& name, std::vector* networks) + : name_(name), networks_(networks) { } + + const std::string& name() const { return name_; } + const std::vector& networks() const { return *networks_; } + +private: + std::string name_; + std::vector* networks_; +}; + +// Returns a reference to the description of the local host. +const Host& LocalHost(); + +// Returns the name of the local host. +std::string GetHostName(); + +} // namespace cricket + +#endif // __HOST_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.cc new file mode 100644 index 00000000..5befe9fd --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.cc @@ -0,0 +1,77 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/jtime.h" +#include +#include +#include + +namespace cricket { + +#ifdef POSIX +#include +uint32 Time() { + struct timeval tv; + gettimeofday(&tv, 0); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} +#endif + +#ifdef WIN32 +#include +uint32 Time() { + return GetTickCount(); +} +#endif + +bool TimeIsBetween(uint32 later, uint32 middle, uint32 earlier) { + if (earlier <= later) { + return ((earlier <= middle) && (middle <= later)); + } else { + return !((later < middle) && (middle < earlier)); + } +} + +int32 TimeDiff(uint32 later, uint32 earlier) { + uint32 LAST = 0xFFFFFFFF; + uint32 HALF = 0x80000000; + if (TimeIsBetween(earlier + HALF, later, earlier)) { + if (earlier <= later) { + return static_cast(later - earlier); + } else { + return static_cast(later + (LAST - earlier) + 1); + } + } else { + if (later <= earlier) { + return -static_cast(earlier - later); + } else { + return -static_cast(earlier + (LAST - later) + 1); + } + } +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.h new file mode 100644 index 00000000..d7dff0fb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.h @@ -0,0 +1,47 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __JTIME_H__ +#define __JTIME_H__ + +#include "talk/base/basictypes.h" + +namespace cricket { + +// Returns the current time in milliseconds. +uint32 Time(); + +// TODO: Delete this old version. +#define GetMillisecondCount Time + +// Comparisons between time values, which can wrap around. +bool TimeIsBetween(uint32 later, uint32 middle, uint32 earlier); +int32 TimeDiff(uint32 later, uint32 earlier); + +} // namespace cricket + +#endif // __TIME_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/linked_ptr.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/linked_ptr.h new file mode 100644 index 00000000..94b62ad3 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/linked_ptr.h @@ -0,0 +1,138 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * linked_ptr - simple reference linked pointer + * (like reference counting, just using a linked list of the references + * instead of their count.) + * + * The implementation stores three pointers for every linked_ptr, but + * does not allocate anything on the free store. + */ + +#ifndef _LINKED_PTR_H_ +#define _LINKED_PTR_H_ + +/* For ANSI-challenged compilers, you may want to #define + * NO_MEMBER_TEMPLATES, explicit or mutable */ +#define NO_MEMBER_TEMPLATES + +template class linked_ptr +{ +public: + +#ifndef NO_MEMBER_TEMPLATES +# define TEMPLATE_FUNCTION template + TEMPLATE_FUNCTION friend class linked_ptr; +#else +# define TEMPLATE_FUNCTION + typedef X Y; +#endif + + typedef X element_type; + + explicit linked_ptr(X* p = 0) throw() + : itsPtr(p) {itsPrev = itsNext = this;} + ~linked_ptr() + {release();} + linked_ptr(const linked_ptr& r) throw() + {acquire(r);} + linked_ptr& operator=(const linked_ptr& r) + { + if (this != &r) { + release(); + acquire(r); + } + return *this; + } + +#ifndef NO_MEMBER_TEMPLATES + template friend class linked_ptr; + template linked_ptr(const linked_ptr& r) throw() + {acquire(r);} + template linked_ptr& operator=(const linked_ptr& r) + { + if (this != &r) { + release(); + acquire(r); + } + return *this; + } +#endif // NO_MEMBER_TEMPLATES + + X& operator*() const throw() {return *itsPtr;} + X* operator->() const throw() {return itsPtr;} + X* get() const throw() {return itsPtr;} + bool unique() const throw() {return itsPrev ? itsPrev==this : true;} + +private: + X* itsPtr; + mutable const linked_ptr* itsPrev; + mutable const linked_ptr* itsNext; + + void acquire(const linked_ptr& r) throw() + { // insert this to the list + itsPtr = r.itsPtr; + itsNext = r.itsNext; + itsNext->itsPrev = this; + itsPrev = &r; +#ifndef mutable + r.itsNext = this; +#else // for ANSI-challenged compilers + (const_cast*>(&r))->itsNext = this; +#endif + } + +#ifndef NO_MEMBER_TEMPLATES + template void acquire(const linked_ptr& r) throw() + { // insert this to the list + itsPtr = r.itsPtr; + itsNext = r.itsNext; + itsNext->itsPrev = this; + itsPrev = &r; +#ifndef mutable + r.itsNext = this; +#else // for ANSI-challenged compilers + (const_cast*>(&r))->itsNext = this; +#endif + } +#endif // NO_MEMBER_TEMPLATES + + void release() + { // erase this from the list, delete if unique + if (unique()) delete itsPtr; + else { + itsPrev->itsNext = itsNext; + itsNext->itsPrev = itsPrev; + itsPrev = itsNext = 0; + } + itsPtr = 0; + } +}; + +#endif // LINKED_PTR_H + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/logging.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/logging.h new file mode 100644 index 00000000..500386ed --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/logging.h @@ -0,0 +1,222 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// LOG(...) an ostream target that can be used to send formatted +// output to a variety of logging targets, such as debugger console, stderr, +// file, or any StreamInterface. +// The severity level passed as the first argument to the the LOGging +// functions is used as a filter, to limit the verbosity of the logging. +// Static members of LogMessage documented below are used to control the +// verbosity and target of the output. +// There are several variations on the LOG macro which facilitate logging +// of common error conditions, detailed below. + +#ifndef TALK_BASE_LOGGING_H__ +#define TALK_BASE_LOGGING_H__ + +#include +#include "talk/base/basictypes.h" +class StreamInterface; + +/////////////////////////////////////////////////////////////////////////////// +// ConstantLabel can be used to easily generate string names from constant +// values. This can be useful for logging descriptive names of error messages. +// Usage: +// const ConstantLabel LIBRARY_ERRORS[] = { +// KLABEL(SOME_ERROR), +// KLABEL(SOME_OTHER_ERROR), +// ... +// LASTLABEL +// } +// +// int err = LibraryFunc(); +// LOG(LS_ERROR) << "LibraryFunc returned: " +// << ErrorName(err, LIBRARY_ERRORS); + +struct ConstantLabel { int value; const char * label; }; +#define KLABEL(x) { x, #x } +#define TLABEL(x,y) { x, y } +#define LASTLABEL { 0, 0 } + +const char * FindLabel(int value, const ConstantLabel entries[]); +std::string ErrorName(int err, const ConstantLabel * err_table); + +////////////////////////////////////////////////////////////////////// + +// Note that the non-standard LoggingSeverity aliases exist because they are +// still in broad use. The meanings of the levels are: +// LS_SENSITIVE: Information which should only be logged with the consent +// of the user, due to privacy concerns. +// LS_VERBOSE: This level is for data which we do not want to appear in the +// normal debug log, but should appear in diagnostic logs. +// LS_INFO: Chatty level used in debugging for all sorts of things, the default +// in debug builds. +// LS_WARNING: Something that may warrant investigation. +// LS_ERROR: Something that should not have occurred. +enum LoggingSeverity { LS_SENSITIVE, LS_VERBOSE, LS_INFO, LS_WARNING, LS_ERROR, + INFO = LS_INFO, + WARNING = LS_WARNING, + LERROR = LS_ERROR }; + +// LogErrorContext assists in interpreting the meaning of an error value. +// ERRCTX_ERRNO: the value was read from global 'errno' +// ERRCTX_HRESULT: the value is a Windows HRESULT +enum LogErrorContext { ERRCTX_NONE, ERRCTX_ERRNO, ERRCTX_HRESULT }; + +// If LOGGING is not explicitly defined, default to enabled in debug mode +#if !defined(LOGGING) +#if defined(_DEBUG) && !defined(NDEBUG) +#define LOGGING 1 +#else +#define LOGGING 0 +#endif +#endif // !defined(LOGGING) + +#if LOGGING + +#define LOG(sev) \ + if (LogMessage::Loggable(sev)) \ + LogMessage(__FILE__, __LINE__, sev).stream() + +// PLOG and LOG_ERR attempt to provide a string description of an errno derived +// error. LOG_ERR reads errno directly, so care must be taken to call it before +// errno is reset. +#define PLOG(sev, err) \ + if (LogMessage::Loggable(sev)) \ + LogMessage(__FILE__, __LINE__, sev, ERRCTX_ERRNO, err).stream() +#define LOG_ERR(sev) \ + if (LogMessage::Loggable(sev)) \ + LogMessage(__FILE__, __LINE__, sev, ERRCTX_ERRNO, errno).stream() + +// LOG_GLE(M) attempt to provide a string description of the HRESULT returned +// by GetLastError. The second variant allows searching of a dll's string +// table for the error description. +#ifdef WIN32 +#define LOG_GLE(sev) \ + if (LogMessage::Loggable(sev)) \ + LogMessage(__FILE__, __LINE__, sev, ERRCTX_HRESULT, GetLastError()).stream() +#define LOG_GLEM(sev, mod) \ + if (LogMessage::Loggable(sev)) \ + LogMessage(__FILE__, __LINE__, sev, ERRCTX_HRESULT, GetLastError(), mod) \ + .stream() +#endif // WIN32 + +// TODO: Add an "assert" wrapper that logs in the same manner. + +#else // !LOGGING + +// Hopefully, the compiler will optimize away some of this code. +// Note: syntax of "1 ? (void)0 : LogMessage" was causing errors in g++, +// converted to "while (false)" +#define LOG(sev) \ + while (false) LogMessage(NULL, 0, sev).stream() +#define PLOG(sev, err) \ + while (false) LogMessage(NULL, 0, sev, ERRCTX_ERRNO, 0).stream() +#define LOG_ERR(sev) \ + while (false) LogMessage(NULL, 0, sev, ERRCTX_ERRNO, 0).stream() +#ifdef WIN32 +#define LOG_GLE(sev) \ + while (false) LogMessage(NULL, 0, sev, ERRCTX_HRESULT, 0).stream() +#define LOG_GLEM(sev, mod) \ + while (false) LogMessage(NULL, 0, sev, ERRCTX_HRESULT, 0).stream() +#endif // WIN32 + +#endif // !LOGGING + +class LogMessage { + public: + LogMessage(const char* file, int line, LoggingSeverity sev, + LogErrorContext err_ctx = ERRCTX_NONE, int err = 0, + const char* module = NULL); + ~LogMessage(); + + static inline bool Loggable(LoggingSeverity sev) { return (sev >= min_sev_); } + std::ostream& stream() { return print_stream_; } + + enum { NO_LOGGING = LS_ERROR + 1 }; + + // These are attributes which apply to all logging channels + // LogContext: Display the file and line number of the message + static void LogContext(int min_sev); + // LogThreads: Display the thread identifier of the current thread + static void LogThreads(bool on = true); + // LogTimestamps: Display the elapsed time of the program + static void LogTimestamps(bool on = true); + + // Timestamps begin with program execution, but can be reset with this + // function for measuring the duration of an activity, or to synchronize + // timestamps between multiple instances. + static void ResetTimestamps(); + + // These are the available logging channels + // Debug: Debug console on Windows, otherwise stderr + static void LogToDebug(int min_sev); + static int GetLogToDebug() { return dbg_sev_; } + // Stream: Any non-blocking stream interface. LogMessage takes ownership of + // the stream. + static void LogToStream(StreamInterface* stream, int min_sev); + static int GetLogToStream() { return stream_sev_; } + + // Testing against MinLogSeverity allows code to avoid potentially expensive + // logging operations by pre-checking the logging level. + static int GetMinLogSeverity() { return min_sev_; } + + private: + // These assist in formatting some parts of the debug output. + static const char* Describe(LoggingSeverity sev); + static const char* DescribeFile(const char* file); + + // The ostream that buffers the formatted message before output + std::ostringstream print_stream_; + + // The severity level of this message + LoggingSeverity severity_; + + // String data generated in the constructor, that should be appended to + // the message before output. + std::string extra_; + + // dbg_sev_ and stream_sev_ are the thresholds for those output targets + // min_sev_ is the minimum (most verbose) of those levels, and is used + // as a short-circuit in the logging macros to identify messages that won't + // be logged. + // ctx_sev_ is the minimum level at which file context is displayed + static int min_sev_, dbg_sev_, stream_sev_, ctx_sev_; + + // The output stream, if any + static StreamInterface * stream_; + + // Flags for formatting options + static bool thread_, timestamp_; + + // The timestamp at which logging started. + static uint32 start_; + + DISALLOW_EVIL_CONSTRUCTORS(LogMessage); +}; + +#endif // TALK_BASE_LOGGING_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/md5.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/md5.h new file mode 100644 index 00000000..c2e22cc5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/md5.h @@ -0,0 +1,45 @@ +/* + * This is the header file for the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + */ + +#ifndef MD5_H +#define MD5_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef long unsigned int uint32; +typedef struct MD5Context MD5_CTX; + +#define md5byte unsigned char + +struct MD5Context { + uint32 buf[4]; + uint32 bits[2]; + uint32 in[16]; +}; + +void MD5Init(struct MD5Context *context); +void MD5Update(struct MD5Context *context, md5byte const *buf, unsigned len); +void MD5Final(unsigned char digest[16], struct MD5Context *context); +void MD5Transform(uint32 buf[4], uint32 const in[16]); + +#ifdef __cplusplus +}; +#endif + +#endif /* !MD5_H */ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/md5c.c b/kopete/protocols/jabber/jingle/libjingle/talk/base/md5c.c new file mode 100644 index 00000000..eb2c034d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/md5c.c @@ -0,0 +1,256 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ +#include /* for memcpy() */ +#include "md5.h" + +#ifndef HIGHFIRST +#define byteReverse(buf, len) /* Nothing */ +#else +void byteReverse(unsigned char *buf, unsigned longs); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +void byteReverse(unsigned char *buf, unsigned longs) +{ + uint32 t; + do { + t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 | + ((unsigned)buf[1]<<8 | buf[0]); + *(uint32 *)buf = t; + buf += 4; + } while (--longs); +} +#endif +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len) +{ + uint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32)len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if ( t ) { + unsigned char *p = (unsigned char *)ctx->in + t; + + t = 64-t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += t; + len -= t; + } + + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(unsigned char digest[16], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = (unsigned char*)(ctx->in) + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count-8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32 *)ctx->in)[ 14 ] = ctx->bits[0]; + ((uint32 *)ctx->in)[ 15 ] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32 *)ctx->in); + byteReverse((unsigned char *)ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(uint32 buf[4], uint32 const in[16]) +{ + register uint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.cc new file mode 100644 index 00000000..f10489f7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.cc @@ -0,0 +1,321 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/messagequeue.h" +#include "talk/base/physicalsocketserver.h" + +#ifdef POSIX +extern "C" { +#include +} +#endif + +namespace cricket { + +//------------------------------------------------------------------ +// MessageQueueManager + +MessageQueueManager* MessageQueueManager::instance_; + +MessageQueueManager* MessageQueueManager::Instance() { + // Note: This is not thread safe, but it is first called before threads are + // spawned. + if (!instance_) + instance_ = new MessageQueueManager; + return instance_; +} + +MessageQueueManager::MessageQueueManager() { +} + +MessageQueueManager::~MessageQueueManager() { +} + +void MessageQueueManager::Add(MessageQueue *message_queue) { + CritScope cs(&crit_); + message_queues_.push_back(message_queue); +} + +void MessageQueueManager::Remove(MessageQueue *message_queue) { + CritScope cs(&crit_); + std::vector::iterator iter; + iter = std::find(message_queues_.begin(), message_queues_.end(), message_queue); + if (iter != message_queues_.end()) + message_queues_.erase(iter); +} + +void MessageQueueManager::Clear(MessageHandler *handler) { + CritScope cs(&crit_); + std::vector::iterator iter; + for (iter = message_queues_.begin(); iter != message_queues_.end(); iter++) + (*iter)->Clear(handler); +} + +//------------------------------------------------------------------ +// MessageQueue + +MessageQueue::MessageQueue(SocketServer* ss) + : ss_(ss), new_ss(false), fStop_(false), fPeekKeep_(false) { + if (!ss_) { + new_ss = true; + ss_ = new PhysicalSocketServer(); + } + MessageQueueManager::Instance()->Add(this); +} + +MessageQueue::~MessageQueue() { + Clear(NULL); + if (new_ss) + delete ss_; + MessageQueueManager::Instance()->Remove(this); +} + +void MessageQueue::set_socketserver(SocketServer* ss) { + if (new_ss) + delete ss_; + new_ss = false; + ss_ = ss; +} + +void MessageQueue::Stop() { + fStop_ = true; + ss_->WakeUp(); +} + +bool MessageQueue::IsStopping() { + return fStop_; +} + +void MessageQueue::Restart() { + fStop_ = false; +} + +bool MessageQueue::Peek(Message *pmsg, int cmsWait) { + if (fStop_) + return false; + if (fPeekKeep_) { + *pmsg = msgPeek_; + return true; + } + if (!Get(pmsg, cmsWait)) + return false; + msgPeek_ = *pmsg; + fPeekKeep_ = true; + return true; +} + +bool MessageQueue::Get(Message *pmsg, int cmsWait) { + // Force stopping + + if (fStop_) + return false; + + // Return and clear peek if present + // Always return the peek if it exists so there is Peek/Get symmetry + + if (fPeekKeep_) { + *pmsg = msgPeek_; + fPeekKeep_ = false; + return true; + } + + // Get w/wait + timer scan / dispatch + socket / event multiplexer dispatch + + int cmsTotal = cmsWait; + int cmsElapsed = 0; + uint32 msStart = GetMillisecondCount(); + uint32 msCurrent = msStart; + while (!fStop_) { + // Check for sent messages + + ReceiveSends(); + + // Check queues + + int cmsDelayNext = -1; + { + CritScope cs(&crit_); + + // Check for delayed messages that have been triggered + // Calc the next trigger too + + while (!dmsgq_.empty()) { + if (msCurrent < dmsgq_.top().msTrigger_) { + cmsDelayNext = dmsgq_.top().msTrigger_ - msCurrent; + break; + } + msgq_.push(dmsgq_.top().msg_); + dmsgq_.pop(); + } + + // Check for posted events + + if (!msgq_.empty()) { + *pmsg = msgq_.front(); + msgq_.pop(); + return true; + } + } + + // Which is shorter, the delay wait or the asked wait? + + int cmsNext; + if (cmsWait == -1) { + cmsNext = cmsDelayNext; + } else { + cmsNext = cmsTotal - cmsElapsed; + if (cmsNext < 0) + cmsNext = 0; + if (cmsDelayNext != -1 && cmsDelayNext < cmsNext) + cmsNext = cmsDelayNext; + } + + // Wait and multiplex in the meantime + ss_->Wait(cmsNext, true); + + // If the specified timeout expired, return + + msCurrent = GetMillisecondCount(); + cmsElapsed = msCurrent - msStart; + if (cmsWait != -1) { + if (cmsElapsed >= cmsWait) + return false; + } + } + return false; +} + +void MessageQueue::ReceiveSends() { +} + +void MessageQueue::Post(MessageHandler *phandler, uint32 id, + MessageData *pdata) { + // Keep thread safe + // Add the message to the end of the queue + // Signal for the multiplexer to return + + CritScope cs(&crit_); + Message msg; + msg.phandler = phandler; + msg.message_id = id; + msg.pdata = pdata; + msgq_.push(msg); + ss_->WakeUp(); +} + +void MessageQueue::PostDelayed(int cmsDelay, MessageHandler *phandler, + uint32 id, MessageData *pdata) { + // Keep thread safe + // Add to the priority queue. Gets sorted soonest first. + // Signal for the multiplexer to return. + + CritScope cs(&crit_); + Message msg; + msg.phandler = phandler; + msg.message_id = id; + msg.pdata = pdata; + dmsgq_.push(DelayedMessage(cmsDelay, &msg)); + ss_->WakeUp(); +} + +int MessageQueue::GetDelay() { + CritScope cs(&crit_); + + if (!msgq_.empty()) + return 0; + + if (!dmsgq_.empty()) { + int delay = dmsgq_.top().msTrigger_ - GetMillisecondCount(); + if (delay < 0) + delay = 0; + return delay; + } + + return -1; +} + +void MessageQueue::Clear(MessageHandler *phandler, uint32 id) { + CritScope cs(&crit_); + + // Remove messages with phandler + + if (fPeekKeep_) { + if (phandler == NULL || msgPeek_.phandler == phandler) { + if (id == (uint32)-1 || msgPeek_.message_id == id) { + delete msgPeek_.pdata; + fPeekKeep_ = false; + } + } + } + + // Remove from ordered message queue + + size_t c = msgq_.size(); + while (c-- != 0) { + Message msg = msgq_.front(); + msgq_.pop(); + if (phandler != NULL && msg.phandler != phandler) { + msgq_.push(msg); + } else { + if (id == (uint32)-1 || msg.message_id == id) { + delete msg.pdata; + } else { + msgq_.push(msg); + } + } + } + + // Remove from priority queue. Not directly iterable, so use this approach + + std::queue dmsgs; + while (!dmsgq_.empty()) { + DelayedMessage dmsg = dmsgq_.top(); + dmsgq_.pop(); + if (phandler != NULL && dmsg.msg_.phandler != phandler) { + dmsgs.push(dmsg); + } else { + if (id == (uint32)-1 || dmsg.msg_.message_id == id) { + delete dmsg.msg_.pdata; + } else { + dmsgs.push(dmsg); + } + } + } + while (!dmsgs.empty()) { + dmsgq_.push(dmsgs.front()); + dmsgs.pop(); + } +} + +void MessageQueue::Dispatch(Message *pmsg) { + pmsg->phandler->OnMessage(pmsg); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.h new file mode 100644 index 00000000..2a9cbed6 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.h @@ -0,0 +1,164 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MESSAGEQUEUE_H__ +#define __MESSAGEQUEUE_H__ + +#include "talk/base/basictypes.h" +#include "talk/base/criticalsection.h" +#include "talk/base/socketserver.h" +#include "talk/base/jtime.h" +#include +#include +#include + +namespace cricket { + +struct Message; +class MessageQueue; +class MessageHandler; + +// MessageQueueManager does cleanup of of message queues + +class MessageQueueManager { +public: + static MessageQueueManager* Instance(); + + void Add(MessageQueue *message_queue); + void Remove(MessageQueue *message_queue); + void Clear(MessageHandler *handler); + +private: + MessageQueueManager(); + ~MessageQueueManager(); + + static MessageQueueManager* instance_; + std::vector message_queues_; + CriticalSection crit_; +}; + +// Messages get dispatched to a MessageHandler + +class MessageHandler { +public: + virtual ~MessageHandler() { + MessageQueueManager::Instance()->Clear(this); + } + + virtual void OnMessage(Message *pmsg) = 0; +}; + +// Derive from this for specialized data +// App manages lifetime, except when messages are purged + +class MessageData { +public: + MessageData() {} + virtual ~MessageData() {} +}; + +template +class TypedMessageData : public MessageData { +public: + TypedMessageData(arg1_type data) { + data_ = data; + } + arg1_type data() { + return data_; + } +private: + arg1_type data_; +}; + +// No destructor + +struct Message { + Message() { + memset(this, 0, sizeof(*this)); + } + MessageHandler *phandler; + uint32 message_id; + MessageData *pdata; +}; + +// DelayedMessage goes into a priority queue, sorted by trigger time + +class DelayedMessage { +public: + DelayedMessage(int cmsDelay, Message *pmsg) { + cmsDelay_ = cmsDelay; + msTrigger_ = GetMillisecondCount() + cmsDelay; + msg_ = *pmsg; + } + + bool operator< (const DelayedMessage& dmsg) const { + return dmsg.msTrigger_ < msTrigger_; + } + + int cmsDelay_; // for debugging + uint32 msTrigger_; + Message msg_; +}; + +class MessageQueue { +public: + MessageQueue(SocketServer* ss = 0); + virtual ~MessageQueue(); + + SocketServer* socketserver() { return ss_; } + void set_socketserver(SocketServer* ss); + + // Once the queue is stopped, all calls to Get/Peek will return false. + virtual void Stop(); + virtual bool IsStopping(); + virtual void Restart(); + + virtual bool Get(Message *pmsg, int cmsWait = -1); + virtual bool Peek(Message *pmsg, int cmsWait = 0); + virtual void Post(MessageHandler *phandler, uint32 id = 0, + MessageData *pdata = NULL); + virtual void PostDelayed(int cmsDelay, MessageHandler *phandler, + uint32 id = 0, MessageData *pdata = NULL); + virtual void Clear(MessageHandler *phandler, uint32 id = (uint32)-1); + virtual void Dispatch(Message *pmsg); + virtual void ReceiveSends(); + virtual int GetDelay(); + +protected: + SocketServer* ss_; + bool new_ss; + bool fStop_; + bool fPeekKeep_; + Message msgPeek_; + std::queue msgq_; + std::priority_queue dmsgq_; + CriticalSection crit_; +}; + +} // namespace cricket + +#endif // __MESSAGEQUEUE_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/network.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/network.cc new file mode 100644 index 00000000..21b3a08f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/network.cc @@ -0,0 +1,382 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/host.h" +#include "talk/base/logging.h" +#include "talk/base/network.h" +#include "talk/base/socket.h" // this includes something that makes windows happy +#include "talk/base/jtime.h" +#include "talk/base/basicdefs.h" + +#include +#include +#include +#include +#include + +#ifdef POSIX +extern "C" { +#include +#include +#include +#include +#include +} +#endif // POSIX + +#ifdef WIN32 +#include +#endif + +namespace { + +const double kAlpha = 0.5; // weight for data infinitely far in the past +const double kHalfLife = 2000; // half life of exponential decay (in ms) +const double kLog2 = 0.693147180559945309417; +const double kLambda = kLog2 / kHalfLife; + +// assume so-so quality unless data says otherwise +const double kDefaultQuality = cricket::QUALITY_FAIR; + +typedef std::map StrMap; + +void BuildMap(const StrMap& map, std::string& str) { + str.append("{"); + bool first = true; + for (StrMap::const_iterator i = map.begin(); i != map.end(); ++i) { + if (!first) str.append(","); + str.append(i->first); + str.append("="); + str.append(i->second); + first = false; + } + str.append("}"); +} + +void ParseCheck(std::istringstream& ist, char ch) { + if (ist.get() != ch) + LOG(LERROR) << "Expecting '" << ch << "'"; +} + +std::string ParseString(std::istringstream& ist) { + std::string str; + int count = 0; + while (ist) { + char ch = ist.peek(); + if ((count == 0) && ((ch == '=') || (ch == ',') || (ch == '}'))) { + break; + } else if (ch == '{') { + count += 1; + } else if (ch == '}') { + count -= 1; + if (count < 0) + LOG(LERROR) << "mismatched '{' and '}'"; + } + str.append(1, static_cast(ist.get())); + } + return str; +} + +void ParseMap(const std::string& str, StrMap& map) { + if (str.size() == 0) + return; + std::istringstream ist(str); + ParseCheck(ist, '{'); + for (;;) { + std::string key = ParseString(ist); + ParseCheck(ist, '='); + std::string val = ParseString(ist); + map[key] = val; + if (ist.peek() == ',') + ist.get(); + else + break; + } + ParseCheck(ist, '}'); + if (ist.rdbuf()->in_avail() != 0) + LOG(LERROR) << "Unexpected characters at end"; +} + +#if 0 +const std::string TEST_MAP0_IN = ""; +const std::string TEST_MAP0_OUT = "{}"; +const std::string TEST_MAP1 = "{a=12345}"; +const std::string TEST_MAP2 = "{a=12345,b=67890}"; +const std::string TEST_MAP3 = "{a=12345,b=67890,c=13579}"; +const std::string TEST_MAP4 = "{a={d=12345,e=67890}}"; +const std::string TEST_MAP5 = "{a={d=12345,e=67890},b=67890}"; +const std::string TEST_MAP6 = "{a=12345,b={d=12345,e=67890}}"; +const std::string TEST_MAP7 = "{a=12345,b={d=12345,e=67890},c=13579}"; + +class MyTest { +public: + MyTest() { + test(TEST_MAP0_IN, TEST_MAP0_OUT); + test(TEST_MAP1, TEST_MAP1); + test(TEST_MAP2, TEST_MAP2); + test(TEST_MAP3, TEST_MAP3); + test(TEST_MAP4, TEST_MAP4); + test(TEST_MAP5, TEST_MAP5); + test(TEST_MAP6, TEST_MAP6); + test(TEST_MAP7, TEST_MAP7); + } + void test(const std::string& input, const std::string& exp_output) { + StrMap map; + ParseMap(input, map); + std::string output; + BuildMap(map, output); + LOG(INFO) << " ******** " << (output == exp_output); + } +}; + +static MyTest myTest; +#endif + +template +std::string ToString(T val) { + std::ostringstream ost; + ost << val; + return ost.str(); +} + +template +T FromString(std::string str) { + std::istringstream ist(str); + T val; + ist >> val; + return val; +} + +} + +namespace cricket { + +#ifdef POSIX +void NetworkManager::CreateNetworks(std::vector& networks) { + int fd; + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + PLOG(LERROR, errno) << "socket"; + return; + } + + struct ifconf ifc; + ifc.ifc_len = 64 * sizeof(struct ifreq); + ifc.ifc_buf = new char[ifc.ifc_len]; + + if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) { + PLOG(LERROR, errno) << "ioctl"; + return; + } + assert(ifc.ifc_len < static_cast(64 * sizeof(struct ifreq))); + + struct ifreq* ptr = reinterpret_cast(ifc.ifc_buf); + struct ifreq* end = + reinterpret_cast(ifc.ifc_buf + ifc.ifc_len); + + while (ptr < end) { + struct sockaddr_in* inaddr = + reinterpret_cast(&ptr->ifr_ifru.ifru_addr); + if (inaddr->sin_family == AF_INET) { + uint32 ip = ntohl(inaddr->sin_addr.s_addr); + networks.push_back(new Network(std::string(ptr->ifr_name), ip)); + } +#ifdef _SIZEOF_ADDR_IFREQ + ptr = reinterpret_cast( + reinterpret_cast(ptr) + _SIZEOF_ADDR_IFREQ(*ptr)); +#else + ptr++; +#endif + } + + delete [] ifc.ifc_buf; + close(fd); +} +#endif + +#ifdef WIN32 +void NetworkManager::CreateNetworks(std::vector& networks) { + IP_ADAPTER_INFO info_temp; + ULONG len = 0; + + if (GetAdaptersInfo(&info_temp, &len) != ERROR_BUFFER_OVERFLOW) + return; + IP_ADAPTER_INFO *infos = new IP_ADAPTER_INFO[len]; + if (GetAdaptersInfo(infos, &len) != NO_ERROR) + return; + + int count = 0; + for (IP_ADAPTER_INFO *info = infos; info != NULL; info = info->Next) { + if (info->Type == MIB_IF_TYPE_LOOPBACK) + continue; + if (strcmp(info->IpAddressList.IpAddress.String, "0.0.0.0") == 0) + continue; + + // In production, don't transmit the network name because of + // privacy concerns. Transmit a number instead. + + std::string name; +#if defined(PRODUCTION) + std::ostringstream ost; + ost << count; + name = ost.str(); + count++; +#else + name = info->Description; +#endif + + networks.push_back(new Network(name, + SocketAddress::StringToIP(info->IpAddressList.IpAddress.String))); + } + + delete infos; +} +#endif + +void NetworkManager::GetNetworks(std::vector& result) { + std::vector list; + CreateNetworks(list); + + for (uint32 i = 0; i < list.size(); ++i) { + NetworkMap::iterator iter = networks_.find(list[i]->name()); + + Network* network; + if (iter == networks_.end()) { + network = list[i]; + } else { + network = iter->second; + network->set_ip(list[i]->ip()); + delete list[i]; + } + + networks_[network->name()] = network; + result.push_back(network); + } +} + +std::string NetworkManager::GetState() { + StrMap map; + for (NetworkMap::iterator i = networks_.begin(); i != networks_.end(); ++i) + map[i->first] = i->second->GetState(); + + std::string str; + BuildMap(map, str); + return str; +} + +void NetworkManager::SetState(std::string str) { + StrMap map; + ParseMap(str, map); + + for (StrMap::iterator i = map.begin(); i != map.end(); ++i) { + std::string name = i->first; + std::string state = i->second; + + Network* network = new Network(name, 0); + network->SetState(state); + networks_[name] = network; + } +} + +Network::Network(const std::string& name, uint32 ip) + : name_(name), ip_(ip), uniform_numerator_(0), uniform_denominator_(0), + exponential_numerator_(0), exponential_denominator_(0), + quality_(kDefaultQuality) { + + last_data_time_ = Time(); + + // TODO: seed the historical data with one data point based on the link speed + // metric from XP (4.0 if < 50, 3.0 otherwise). +} + +void Network::StartSession(NetworkSession* session) { + assert(std::find(sessions_.begin(), sessions_.end(), session) == sessions_.end()); + sessions_.push_back(session); +} + +void Network::StopSession(NetworkSession* session) { + SessionList::iterator iter = std::find(sessions_.begin(), sessions_.end(), session); + if (iter != sessions_.end()) + sessions_.erase(iter); +} + +void Network::EstimateQuality() { + uint32 now = Time(); + + // Add new data points for the current time. + for (uint32 i = 0; i < sessions_.size(); ++i) { + if (sessions_[i]->HasQuality()) + AddDataPoint(now, sessions_[i]->GetCurrentQuality()); + } + + // Construct the weighted average using both uniform and exponential weights. + + double exp_shift = exp(-kLambda * (now - last_data_time_)); + double numerator = uniform_numerator_ + exp_shift * exponential_numerator_; + double denominator = uniform_denominator_ + exp_shift * exponential_denominator_; + + if (denominator < DBL_EPSILON) + quality_ = kDefaultQuality; + else + quality_ = numerator / denominator; +} + +void Network::AddDataPoint(uint32 time, double quality) { + uniform_numerator_ += kAlpha * quality; + uniform_denominator_ += kAlpha; + + double exp_shift = exp(-kLambda * (time - last_data_time_)); + exponential_numerator_ = (1 - kAlpha) * quality + exp_shift * exponential_numerator_; + exponential_denominator_ = (1 - kAlpha) + exp_shift * exponential_denominator_; + + last_data_time_ = time; +} + +std::string Network::GetState() { + StrMap map; + map["lt"] = ToString(last_data_time_); + map["un"] = ToString(uniform_numerator_); + map["ud"] = ToString(uniform_denominator_); + map["en"] = ToString(exponential_numerator_); + map["ed"] = ToString(exponential_denominator_); + + std::string str; + BuildMap(map, str); + return str; +} + +void Network::SetState(std::string str) { + StrMap map; + ParseMap(str, map); + + last_data_time_ = FromString(map["lt"]); + uniform_numerator_ = FromString(map["un"]); + uniform_denominator_ = FromString(map["ud"]); + exponential_numerator_ = FromString(map["en"]); + exponential_denominator_ = FromString(map["ed"]); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/network.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/network.h new file mode 100644 index 00000000..2cc9128a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/network.h @@ -0,0 +1,136 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __NETWORK_H__ +#define __NETWORK_H__ + +#include "talk/base/basictypes.h" + +#include +#include +#include +#include + +namespace cricket { + +class Network; +class NetworkSession; + +// Keeps track of the available network interfaces over time so that quality +// information can be aggregated and recorded. +class NetworkManager { +public: + + // Updates and returns the current list of networks available on this machine. + // This version will make sure that repeated calls return the same object for + // a given network, so that quality is tracked appropriately. + void GetNetworks(std::vector& networks); + + // Reads and writes the state of the quality database in a string format. + std::string GetState(); + void SetState(std::string str); + + // Creates a network object for each network available on the machine. + static void CreateNetworks(std::vector& networks); + +private: + typedef std::map NetworkMap; + + NetworkMap networks_; +}; + +// Represents a Unix-type network interface, with a name and single address. +// It also includes the ability to track and estimate quality. +class Network { +public: + Network(const std::string& name, uint32 ip); + + // Returns the OS name of this network. This is considered the primary key + // that identifies each network. + const std::string& name() const { return name_; } + + // Identifies the current IP address used by this network. + uint32 ip() const { return ip_; } + void set_ip(uint32 ip) { ip_ = ip; } + + // Updates the list of sessions that are ongoing. + void StartSession(NetworkSession* session); + void StopSession(NetworkSession* session); + + // Re-computes the estimate of near-future quality based on the information + // as of this exact moment. + void EstimateQuality(); + + // Returns the current estimate of the near-future quality of connections + // that use this local interface. + double quality() { return quality_; } + +private: + typedef std::vector SessionList; + + std::string name_; + uint32 ip_; + SessionList sessions_; + double uniform_numerator_; + double uniform_denominator_; + double exponential_numerator_; + double exponential_denominator_; + uint32 last_data_time_; + double quality_; + + // Updates the statistics maintained to include the given estimate. + void AddDataPoint(uint32 time, double quality); + + // Converts the internal state to and from a string. This is used to record + // quality information into a permanent store. + void SetState(std::string str); + std::string GetState(); + + friend class NetworkManager; +}; + +// Represents a session that is in progress using a particular network and can +// provide data about the quality of the network at any given moment. +class NetworkSession { +public: + // Determines whether this session has an estimate at this moment. We will + // only call GetCurrentQuality when this returns true. + virtual bool HasQuality() = 0; + + // Returns an estimate of the quality at this exact moment. The result should + // be a MOS (mean opinion score) value. + virtual float GetCurrentQuality() = 0; + +}; + +const double QUALITY_BAD = 3.0; +const double QUALITY_FAIR = 3.35; +const double QUALITY_GOOD = 3.7; + +} // namespace cricket + +#endif // __NETWORK_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.cc new file mode 100644 index 00000000..91d2daad --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.cc @@ -0,0 +1,1117 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif + +#include + +#ifdef POSIX +extern "C" { +#include +#include +#include +#include +} +#endif + +#include "talk/base/basictypes.h" +#include "talk/base/byteorder.h" +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/jtime.h" +#include "talk/base/winping.h" + +#ifdef __linux +#define IP_MTU 14 // Until this is integrated from linux/in.h to netinet/in.h +#endif // __linux + +#ifdef WIN32 +#include +#include +#define _WINSOCKAPI_ +#include +#undef SetPort + +#include +#include + +class WinsockInitializer { +public: + WinsockInitializer() { + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(1, 0); + err_ = WSAStartup(wVersionRequested, &wsaData); + } + ~WinsockInitializer() { + WSACleanup(); + } + int error() { + return err_; + } +private: + int err_; +}; +WinsockInitializer g_winsockinit; +#endif + +namespace cricket { + +const int kfRead = 0x0001; +const int kfWrite = 0x0002; +const int kfConnect = 0x0004; +const int kfClose = 0x0008; + + +// Standard MTUs +const uint16 PACKET_MAXIMUMS[] = { + 65535, // Theoretical maximum, Hyperchannel + 32000, // Nothing + 17914, // 16Mb IBM Token Ring + 8166, // IEEE 802.4 + //4464, // IEEE 802.5 (4Mb max) + 4352, // FDDI + //2048, // Wideband Network + 2002, // IEEE 802.5 (4Mb recommended) + //1536, // Expermental Ethernet Networks + //1500, // Ethernet, Point-to-Point (default) + 1492, // IEEE 802.3 + 1006, // SLIP, ARPANET + //576, // X.25 Networks + //544, // DEC IP Portal + //512, // NETBIOS + 508, // IEEE 802/Source-Rt Bridge, ARCNET + 296, // Point-to-Point (low delay) + 68, // Official minimum + 0, // End of list marker +}; + +const uint32 IP_HEADER_SIZE = 20; +const uint32 ICMP_HEADER_SIZE = 8; + +class PhysicalSocket : public AsyncSocket { +public: + PhysicalSocket(PhysicalSocketServer* ss, SOCKET s = INVALID_SOCKET) + : ss_(ss), s_(s), enabled_events_(0), error_(0), + state_((s == INVALID_SOCKET) ? CS_CLOSED : CS_CONNECTED) { + if (s != INVALID_SOCKET) + enabled_events_ = kfRead | kfWrite; + } + + virtual ~PhysicalSocket() { + Close(); + } + + // Creates the underlying OS socket (same as the "socket" function). + virtual bool Create(int type) { + Close(); + s_ = ::socket(AF_INET, type, 0); + UpdateLastError(); + enabled_events_ = kfRead | kfWrite; + return s_ != INVALID_SOCKET; + } + + SocketAddress GetLocalAddress() const { + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + int result = ::getsockname(s_, (struct sockaddr*)&addr, &addrlen); + assert(addrlen == sizeof(addr)); + if (result >= 0) { + return SocketAddress(NetworkToHost32(addr.sin_addr.s_addr), + NetworkToHost16(addr.sin_port)); + } else { + return SocketAddress(); + } + } + + SocketAddress GetRemoteAddress() const { + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + int result = ::getpeername(s_, (struct sockaddr*)&addr, &addrlen); + assert(addrlen == sizeof(addr)); + if (result >= 0) { + return SocketAddress( + NetworkToHost32(addr.sin_addr.s_addr), + NetworkToHost16(addr.sin_port)); + } else { + assert(errno == ENOTCONN); + return SocketAddress(); + } + } + + int Bind(const SocketAddress& addr) { + struct sockaddr_in saddr; + IP2SA(&addr, &saddr); + int err = ::bind(s_, (struct sockaddr*)&saddr, sizeof(saddr)); + UpdateLastError(); + return err; + } + + int Connect(const SocketAddress& addr) { + // TODO: Implicit creation is required to reconnect... + // ...but should we make it more explicit? + if ((s_ == INVALID_SOCKET) && !Create(SOCK_STREAM)) + return SOCKET_ERROR; + SocketAddress addr2(addr); + if (addr2.IsUnresolved()) { + LOG(INFO) << "Resolving addr in PhysicalSocket::Connect"; + addr2.Resolve(); // TODO: Do this async later? + } + struct sockaddr_in saddr; + IP2SA(&addr2, &saddr); + int err = ::connect(s_, (struct sockaddr*)&saddr, sizeof(saddr)); + UpdateLastError(); + //LOG(INFO) << "SOCK[" << static_cast(s_) << "] Connect(" << addr2.ToString() << ") Ret: " << err << " Error: " << error_; + if (err == 0) { + state_ = CS_CONNECTED; + } else if (IsBlockingError(error_)) { + state_ = CS_CONNECTING; + enabled_events_ |= kfConnect; + } + return err; + } + + int GetError() const { + return error_; + } + + void SetError(int error) { + error_ = error; + } + + ConnState GetState() const { + return state_; + } + + int SetOption(Option opt, int value) { + assert(opt == OPT_DONTFRAGMENT); +#ifdef WIN32 + value = (value == 0) ? 0 : 1; + return ::setsockopt( + s_, IPPROTO_IP, IP_DONTFRAGMENT, reinterpret_cast(&value), + sizeof(value)); +#endif +#ifdef __linux + value = (value == 0) ? IP_PMTUDISC_DONT : IP_PMTUDISC_DO; + return ::setsockopt( + s_, IPPROTO_IP, IP_MTU_DISCOVER, &value, sizeof(value)); +#endif +#ifdef OSX + // This is not possible on OSX. + return -1; +#endif + } + + int Send(const void *pv, size_t cb) { + int sent = ::send(s_, reinterpret_cast(pv), (int)cb, 0); + UpdateLastError(); + //LOG(INFO) << "SOCK[" << static_cast(s_) << "] Send(" << cb << ") Ret: " << sent << " Error: " << error_; + ASSERT(sent <= static_cast(cb)); // We have seen minidumps where this may be false + if ((sent < 0) && IsBlockingError(error_)) { + enabled_events_ |= kfWrite; + } + return sent; + } + + int SendTo(const void *pv, size_t cb, const SocketAddress& addr) { + struct sockaddr_in saddr; + IP2SA(&addr, &saddr); + int sent = ::sendto( + s_, (const char *)pv, (int)cb, 0, (struct sockaddr*)&saddr, + sizeof(saddr)); + UpdateLastError(); + ASSERT(sent <= static_cast(cb)); // We have seen minidumps where this may be false + if ((sent < 0) && IsBlockingError(error_)) { + enabled_events_ |= kfWrite; + } + return sent; + } + + int Recv(void *pv, size_t cb) { + int received = ::recv(s_, (char *)pv, (int)cb, 0); + UpdateLastError(); + if ((received >= 0) || IsBlockingError(error_)) { + enabled_events_ |= kfRead; + } + return received; + } + + int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) { + struct sockaddr saddr; + socklen_t cbAddr = sizeof(saddr); + int received = ::recvfrom(s_, (char *)pv, (int)cb, 0, &saddr, &cbAddr); + UpdateLastError(); + if ((received >= 0) && (paddr != NULL)) + SA2IP(&saddr, paddr); + if ((received >= 0) || IsBlockingError(error_)) { + enabled_events_ |= kfRead; + } + return received; + } + + int Listen(int backlog) { + int err = ::listen(s_, backlog); + UpdateLastError(); + if (err == 0) + state_ = CS_CONNECTING; + return err; + } + + Socket* Accept(SocketAddress *paddr) { + struct sockaddr saddr; + socklen_t cbAddr = sizeof(saddr); + SOCKET s = ::accept(s_, &saddr, &cbAddr); + UpdateLastError(); + if (s == INVALID_SOCKET) + return NULL; + if (paddr != NULL) + SA2IP(&saddr, paddr); + return ss_->WrapSocket(s); + } + + int Close() { + if (s_ == INVALID_SOCKET) + return 0; + int err = ::closesocket(s_); + UpdateLastError(); + //LOG(INFO) << "SOCK[" << static_cast(s_) << "] Close() Ret: " << err << " Error: " << error_; + s_ = INVALID_SOCKET; + state_ = CS_CLOSED; + enabled_events_ = 0; + return err; + } + + int EstimateMTU(uint16* mtu) { + SocketAddress addr = GetRemoteAddress(); + if (addr.IsAny()) { + error_ = ENOTCONN; + return -1; + } + +#ifdef WIN32 + + WinPing ping; + if (!ping.IsValid()) { + error_ = EINVAL; // can't think of a better error ID + return -1; + } + + for (int level = 0; PACKET_MAXIMUMS[level + 1] > 0; ++level) { + int32 size = PACKET_MAXIMUMS[level] - IP_HEADER_SIZE - ICMP_HEADER_SIZE; + if (ping.Ping(addr.ip(), size, 0, 1, false) != WinPing::PING_TOO_LARGE) { + *mtu = PACKET_MAXIMUMS[level]; + return 0; + } + } + + assert(false); + return 0; + +#endif // WIN32 + +#ifdef __linux + + int value; + socklen_t vlen = sizeof(value); + int err = getsockopt(s_, IPPROTO_IP, IP_MTU, &value, &vlen); + if (err < 0) { + UpdateLastError(); + return err; + } + + assert((0 <= value) && (value <= 65536)); + *mtu = uint16(value); + return 0; + +#endif // __linux + + // TODO: OSX support + } + + SocketServer* socketserver() { return ss_; } + +protected: + PhysicalSocketServer* ss_; + SOCKET s_; + uint32 enabled_events_; + int error_; + ConnState state_; + + void UpdateLastError() { +#ifdef WIN32 + error_ = WSAGetLastError(); +#endif +#ifdef POSIX + error_ = errno; +#endif + } + + void IP2SA(const SocketAddress *paddr, struct sockaddr_in *psaddr) { + memset(psaddr, 0, sizeof(*psaddr)); + psaddr->sin_family = AF_INET; + psaddr->sin_port = HostToNetwork16(paddr->port()); + if (paddr->ip() == 0) + psaddr->sin_addr.s_addr = INADDR_ANY; + else + psaddr->sin_addr.s_addr = HostToNetwork32(paddr->ip()); + } + + void SA2IP(const struct sockaddr *psaddr, SocketAddress *paddr) { + const struct sockaddr_in *psaddr_in = + reinterpret_cast(psaddr); + paddr->SetIP(NetworkToHost32(psaddr_in->sin_addr.s_addr)); + paddr->SetPort(NetworkToHost16(psaddr_in->sin_port)); + } +}; + +#ifdef POSIX +class Dispatcher { +public: + virtual uint32 GetRequestedEvents() = 0; + virtual void OnPreEvent(uint32 ff) = 0; + virtual void OnEvent(uint32 ff, int err) = 0; + virtual int GetDescriptor() = 0; +}; + +class EventDispatcher : public Dispatcher { +public: + EventDispatcher(PhysicalSocketServer* ss) : ss_(ss), fSignaled_(false) { + if (pipe(afd_) < 0) + LOG(LERROR) << "pipe failed"; + ss_->Add(this); + } + + virtual ~EventDispatcher() { + ss_->Remove(this); + close(afd_[0]); + close(afd_[1]); + } + + virtual void Signal() { + CritScope cs(&crit_); + if (!fSignaled_) { + uint8 b = 0; + if (write(afd_[1], &b, sizeof(b)) < 0) + LOG(LERROR) << "write failed"; + fSignaled_ = true; + } + } + + virtual uint32 GetRequestedEvents() { + return kfRead; + } + + virtual void OnPreEvent(uint32 ff) { + // It is not possible to perfectly emulate an auto-resetting event with + // pipes. This simulates it by resetting before the event is handled. + + CritScope cs(&crit_); + if (fSignaled_) { + uint8 b; + read(afd_[0], &b, sizeof(b)); + fSignaled_ = false; + } + } + + virtual void OnEvent(uint32 ff, int err) { + assert(false); + } + + virtual int GetDescriptor() { + return afd_[0]; + } + +private: + PhysicalSocketServer *ss_; + int afd_[2]; + bool fSignaled_; + CriticalSection crit_; +}; + +class SocketDispatcher : public Dispatcher, public PhysicalSocket { +public: + SocketDispatcher(PhysicalSocketServer *ss) : PhysicalSocket(ss) { + ss_->Add(this); + } + SocketDispatcher(SOCKET s, PhysicalSocketServer *ss) : PhysicalSocket(ss, s) { + ss_->Add(this); + } + + virtual ~SocketDispatcher() { + ss_->Remove(this); + } + + bool Initialize() { + fcntl(s_, F_SETFL, fcntl(s_, F_GETFL, 0) | O_NONBLOCK); + return true; + } + + virtual bool Create(int type) { + // Change the socket to be non-blocking. + if (!PhysicalSocket::Create(type)) + return false; + + return Initialize(); + } + + virtual int GetDescriptor() { + return s_; + } + + virtual uint32 GetRequestedEvents() { + return enabled_events_; + } + + virtual void OnPreEvent(uint32 ff) { + } + + virtual void OnEvent(uint32 ff, int err) { + if ((ff & kfRead) != 0) { + enabled_events_ &= ~kfRead; + SignalReadEvent(this); + } + if ((ff & kfWrite) != 0) { + enabled_events_ &= ~kfWrite; + SignalWriteEvent(this); + } + if ((ff & kfConnect) != 0) { + enabled_events_ &= ~kfConnect; + SignalConnectEvent(this); + } + if ((ff & kfClose) != 0) + SignalCloseEvent(this, err); + } +}; + +class FileDispatcher: public Dispatcher, public AsyncFile { +public: + FileDispatcher(int fd, PhysicalSocketServer *ss) : ss_(ss), fd_(fd) { + set_readable(true); + + ss_->Add(this); + + fcntl(fd_, F_SETFL, fcntl(fd_, F_GETFL, 0) | O_NONBLOCK); + } + + virtual ~FileDispatcher() { + ss_->Remove(this); + } + + SocketServer* socketserver() { return ss_; } + + virtual int GetDescriptor() { + return fd_; + } + + virtual uint32 GetRequestedEvents() { + return flags_; + } + + virtual void OnPreEvent(uint32 ff) { + } + + virtual void OnEvent(uint32 ff, int err) { + if ((ff & kfRead) != 0) + SignalReadEvent(this); + if ((ff & kfWrite) != 0) + SignalWriteEvent(this); + if ((ff & kfClose) != 0) + SignalCloseEvent(this, err); + } + + virtual bool readable() { + return (flags_ & kfRead) != 0; + } + + virtual void set_readable(bool value) { + flags_ = value ? (flags_ | kfRead) : (flags_ & ~kfRead); + } + + virtual bool writable() { + return (flags_ & kfWrite) != 0; + } + + virtual void set_writable(bool value) { + flags_ = value ? (flags_ | kfWrite) : (flags_ & ~kfWrite); + } + +private: + PhysicalSocketServer* ss_; + int fd_; + int flags_; +}; + +AsyncFile* PhysicalSocketServer::CreateFile(int fd) { + return new FileDispatcher(fd, this); +} + +#endif // POSIX + +#ifdef WIN32 +class Dispatcher { +public: + virtual uint32 GetRequestedEvents() = 0; + virtual void OnPreEvent(uint32 ff) = 0; + virtual void OnEvent(uint32 ff, int err) = 0; + virtual WSAEVENT GetWSAEvent() = 0; + virtual SOCKET GetSocket() = 0; + virtual bool CheckSignalClose() = 0; +}; + +uint32 FlagsToEvents(uint32 events) { + uint32 ffFD = FD_CLOSE | FD_ACCEPT; + if (events & kfRead) + ffFD |= FD_READ; + if (events & kfWrite) + ffFD |= FD_WRITE; + if (events & kfConnect) + ffFD |= FD_CONNECT; + return ffFD; +} + +class EventDispatcher : public Dispatcher { +public: + EventDispatcher(PhysicalSocketServer *ss) : ss_(ss) { + if (hev_ = WSACreateEvent()) { + ss_->Add(this); + } + } + + ~EventDispatcher() { + if (hev_ != NULL) { + ss_->Remove(this); + WSACloseEvent(hev_); + hev_ = NULL; + } + } + + virtual void Signal() { + if (hev_ != NULL) + WSASetEvent(hev_); + } + + virtual uint32 GetRequestedEvents() { + return 0; + } + + virtual void OnPreEvent(uint32 ff) { + WSAResetEvent(hev_); + } + + virtual void OnEvent(uint32 ff, int err) { + } + + virtual WSAEVENT GetWSAEvent() { + return hev_; + } + + virtual SOCKET GetSocket() { + return INVALID_SOCKET; + } + + virtual bool CheckSignalClose() { return false; } + +private: + PhysicalSocketServer* ss_; + WSAEVENT hev_; +}; + +class SocketDispatcher : public Dispatcher, public PhysicalSocket { +public: + static int next_id_; + int id_; + bool signal_close_; + int signal_err_; + + SocketDispatcher(PhysicalSocketServer* ss) : PhysicalSocket(ss), id_(0), signal_close_(false) { + } + SocketDispatcher(SOCKET s, PhysicalSocketServer* ss) : PhysicalSocket(ss, s), id_(0), signal_close_(false) { + } + + virtual ~SocketDispatcher() { + Close(); + } + + bool Initialize() { + assert(s_ != INVALID_SOCKET); + // Must be a non-blocking + u_long argp = 1; + ioctlsocket(s_, FIONBIO, &argp); + ss_->Add(this); + return true; + } + + virtual bool Create(int type) { + // Create socket + if (!PhysicalSocket::Create(type)) + return false; + + if (!Initialize()) + return false; + + do { id_ = ++next_id_; } while (id_ == 0); + return true; + } + + virtual int Close() { + if (s_ == INVALID_SOCKET) + return 0; + + id_ = 0; + signal_close_ = false; + ss_->Remove(this); + return PhysicalSocket::Close(); + } + + virtual uint32 GetRequestedEvents() { + return enabled_events_; + } + + virtual void OnPreEvent(uint32 ff) { + if ((ff & kfConnect) != 0) + state_ = CS_CONNECTED; + } + + virtual void OnEvent(uint32 ff, int err) { + int cache_id = id_; + if ((ff & kfRead) != 0) { + enabled_events_ &= ~kfRead; + SignalReadEvent(this); + } + if (((ff & kfWrite) != 0) && (id_ == cache_id)) { + enabled_events_ &= ~kfWrite; + SignalWriteEvent(this); + } + if (((ff & kfConnect) != 0) && (id_ == cache_id)) { + enabled_events_ &= ~kfConnect; + SignalConnectEvent(this); + } + if (((ff & kfClose) != 0) && (id_ == cache_id)) { + //LOG(INFO) << "SOCK[" << static_cast(s_) << "] OnClose() Error: " << err; + signal_close_ = true; + signal_err_ = err; + } + } + + virtual WSAEVENT GetWSAEvent() { + return WSA_INVALID_EVENT; + } + + virtual SOCKET GetSocket() { + return s_; + } + + virtual bool CheckSignalClose() { + if (!signal_close_) + return false; + + char ch; + if (recv(s_, &ch, 1, MSG_PEEK) > 0) + return false; + + signal_close_ = false; + SignalCloseEvent(this, signal_err_); + return true; + } +}; + +int SocketDispatcher::next_id_ = 0; + +#endif // WIN32 + +// Sets the value of a boolean value to false when signaled. +class Signaler : public EventDispatcher { +public: + Signaler(PhysicalSocketServer* ss, bool* pf) + : EventDispatcher(ss), pf_(pf) { + } + virtual ~Signaler() { } + + void OnEvent(uint32 ff, int err) { + if (pf_) + *pf_ = false; + } + +private: + bool *pf_; +}; + +PhysicalSocketServer::PhysicalSocketServer() : fWait_(false), + last_tick_tracked_(0), last_tick_dispatch_count_(0) { + signal_wakeup_ = new Signaler(this, &fWait_); +} + +PhysicalSocketServer::~PhysicalSocketServer() { + delete signal_wakeup_; +} + +void PhysicalSocketServer::WakeUp() { + signal_wakeup_->Signal(); +} + +Socket* PhysicalSocketServer::CreateSocket(int type) { + PhysicalSocket* socket = new PhysicalSocket(this); + if (socket->Create(type)) { + return socket; + } else { + delete socket; + return 0; + } +} + +AsyncSocket* PhysicalSocketServer::CreateAsyncSocket(int type) { + SocketDispatcher* dispatcher = new SocketDispatcher(this); + if (dispatcher->Create(type)) { + return dispatcher; + } else { + delete dispatcher; + return 0; + } +} + +AsyncSocket* PhysicalSocketServer::WrapSocket(SOCKET s) { + SocketDispatcher* dispatcher = new SocketDispatcher(s, this); + if (dispatcher->Initialize()) { + return dispatcher; + } else { + delete dispatcher; + return 0; + } +} + +void PhysicalSocketServer::Add(Dispatcher *pdispatcher) { + CritScope cs(&crit_); + dispatchers_.push_back(pdispatcher); +} + +void PhysicalSocketServer::Remove(Dispatcher *pdispatcher) { + CritScope cs(&crit_); + dispatchers_.erase(std::remove(dispatchers_.begin(), dispatchers_.end(), pdispatcher), dispatchers_.end()); +} + +#ifdef POSIX +bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) { + // Calculate timing information + + struct timeval *ptvWait = NULL; + struct timeval tvWait; + struct timeval tvStop; + if (cmsWait != -1) { + // Calculate wait timeval + tvWait.tv_sec = cmsWait / 1000; + tvWait.tv_usec = (cmsWait % 1000) * 1000; + ptvWait = &tvWait; + + // Calculate when to return in a timeval + gettimeofday(&tvStop, NULL); + tvStop.tv_sec += tvWait.tv_sec; + tvStop.tv_usec += tvWait.tv_usec; + if (tvStop.tv_usec >= 1000000) { + tvStop.tv_usec -= 1000000; + tvStop.tv_sec += 1; + } + } + + // Zero all fd_sets. Don't need to do this inside the loop since + // select() zeros the descriptors not signaled + + fd_set fdsRead; + FD_ZERO(&fdsRead); + fd_set fdsWrite; + FD_ZERO(&fdsWrite); + + fWait_ = true; + + while (fWait_) { + int fdmax = -1; + { + CritScope cr(&crit_); + for (unsigned i = 0; i < dispatchers_.size(); i++) { + // Query dispatchers for read and write wait state + + Dispatcher *pdispatcher = dispatchers_[i]; + assert(pdispatcher); + if (!process_io && (pdispatcher != signal_wakeup_)) + continue; + int fd = pdispatcher->GetDescriptor(); + if (fd > fdmax) + fdmax = fd; + uint32 ff = pdispatcher->GetRequestedEvents(); + if (ff & kfRead) + FD_SET(fd, &fdsRead); + if (ff & (kfWrite | kfConnect)) + FD_SET(fd, &fdsWrite); + } + } + + // Wait then call handlers as appropriate + // < 0 means error + // 0 means timeout + // > 0 means count of descriptors ready + int n = select(fdmax + 1, &fdsRead, &fdsWrite, NULL, ptvWait); + + // If error, return error + // todo: do something intelligent + + if (n < 0) + return false; + + // If timeout, return success + + if (n == 0) + return true; + + // We have signaled descriptors + + { + CritScope cr(&crit_); + for (unsigned i = 0; i < dispatchers_.size(); i++) { + Dispatcher *pdispatcher = dispatchers_[i]; + int fd = pdispatcher->GetDescriptor(); + uint32 ff = 0; + if (FD_ISSET(fd, &fdsRead)) { + FD_CLR(fd, &fdsRead); + ff |= kfRead; + } + if (FD_ISSET(fd, &fdsWrite)) { + FD_CLR(fd, &fdsWrite); + if (pdispatcher->GetRequestedEvents() & kfConnect) { + ff |= kfConnect; + } else { + ff |= kfWrite; + } + } + if (ff != 0) { + pdispatcher->OnPreEvent(ff); + pdispatcher->OnEvent(ff, 0); + } + } + } + + // Recalc the time remaining to wait. Doing it here means it doesn't get + // calced twice the first time through the loop + + if (cmsWait != -1) { + ptvWait->tv_sec = 0; + ptvWait->tv_usec = 0; + struct timeval tvT; + gettimeofday(&tvT, NULL); + if (tvStop.tv_sec >= tvT.tv_sec) { + ptvWait->tv_sec = tvStop.tv_sec - tvT.tv_sec; + ptvWait->tv_usec = tvStop.tv_usec - tvT.tv_usec; + if (ptvWait->tv_usec < 0) { + ptvWait->tv_usec += 1000000; + ptvWait->tv_sec -= 1; + } + } + } + } + + return true; +} +#endif // POSIX + +#ifdef WIN32 +bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) +{ + int cmsTotal = cmsWait; + int cmsElapsed = 0; + uint32 msStart = GetMillisecondCount(); + +#if LOGGING + if (last_tick_dispatch_count_ == 0) { + last_tick_tracked_ = msStart; + } +#endif + + WSAEVENT socket_ev = WSACreateEvent(); + + fWait_ = true; + while (fWait_) { + std::vector events; + std::vector event_owners; + + events.push_back(socket_ev); + + { + CritScope cr(&crit_); + for (size_t i = 0; i < dispatchers_.size(); ++i) { + Dispatcher * disp = dispatchers_[i]; + if (!process_io && (disp != signal_wakeup_)) + continue; + SOCKET s = disp->GetSocket(); + if (disp->CheckSignalClose()) { + // We just signalled close, don't poll this socket + } else if (s != INVALID_SOCKET) { + WSAEventSelect(s, events[0], FlagsToEvents(disp->GetRequestedEvents())); + } else { + events.push_back(disp->GetWSAEvent()); + event_owners.push_back(disp); + } + } + } + + // Which is shorter, the delay wait or the asked wait? + + int cmsNext; + if (cmsWait == -1) { + cmsNext = cmsWait; + } else { + cmsNext = cmsTotal - cmsElapsed; + if (cmsNext < 0) + cmsNext = 0; + } + + // Wait for one of the events to signal + DWORD dw = WSAWaitForMultipleEvents(static_cast(events.size()), &events[0], false, cmsNext, false); + +#if 0 // LOGGING + // we track this information purely for logging purposes. + last_tick_dispatch_count_++; + if (last_tick_dispatch_count_ >= 1000) { + uint32 now = GetMillisecondCount(); + LOG(INFO) << "PhysicalSocketServer took " << TimeDiff(now, last_tick_tracked_) << "ms for 1000 events"; + + // If we get more than 1000 events in a second, we are spinning badly + // (normally it should take about 8-20 seconds). + assert(TimeDiff(now, last_tick_tracked_) > 1000); + + last_tick_tracked_ = now; + last_tick_dispatch_count_ = 0; + } +#endif + + // Failed? + // todo: need a better strategy than this! + + if (dw == WSA_WAIT_FAILED) { + int error = WSAGetLastError(); + assert(false); + WSACloseEvent(socket_ev); + return false; + } + + // Timeout? + + if (dw == WSA_WAIT_TIMEOUT) { + WSACloseEvent(socket_ev); + return true; + } + + // Figure out which one it is and call it + + { + CritScope cr(&crit_); + int index = dw - WSA_WAIT_EVENT_0; + if (index > 0) { + --index; // The first event is the socket event + event_owners[index]->OnPreEvent(0); + event_owners[index]->OnEvent(0, 0); + } else if (process_io) { + for (size_t i = 0; i < dispatchers_.size(); ++i) { + Dispatcher * disp = dispatchers_[i]; + SOCKET s = disp->GetSocket(); + if (s == INVALID_SOCKET) + continue; + + WSANETWORKEVENTS wsaEvents; + int err = WSAEnumNetworkEvents(s, events[0], &wsaEvents); + if (err == 0) { + +#if LOGGING + { + if ((wsaEvents.lNetworkEvents & FD_READ) && wsaEvents.iErrorCode[FD_READ_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_READ_BIT error " << wsaEvents.iErrorCode[FD_READ_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_WRITE) && wsaEvents.iErrorCode[FD_WRITE_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_WRITE_BIT error " << wsaEvents.iErrorCode[FD_WRITE_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_CONNECT) && wsaEvents.iErrorCode[FD_CONNECT_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_CONNECT_BIT error " << wsaEvents.iErrorCode[FD_CONNECT_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_ACCEPT) && wsaEvents.iErrorCode[FD_ACCEPT_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_ACCEPT_BIT error " << wsaEvents.iErrorCode[FD_ACCEPT_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_CLOSE) && wsaEvents.iErrorCode[FD_CLOSE_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_CLOSE_BIT error " << wsaEvents.iErrorCode[FD_CLOSE_BIT]; + } + } +#endif + uint32 ff = 0; + int errcode = 0; + if (wsaEvents.lNetworkEvents & FD_READ) + ff |= kfRead; + if (wsaEvents.lNetworkEvents & FD_WRITE) + ff |= kfWrite; + if (wsaEvents.lNetworkEvents & FD_CONNECT) { + if (wsaEvents.iErrorCode[FD_CONNECT_BIT] == 0) { + ff |= kfConnect; + } else { + // TODO: Decide whether we want to signal connect, but with an error code + ff |= kfClose; + errcode = wsaEvents.iErrorCode[FD_CONNECT_BIT]; + } + } + if (wsaEvents.lNetworkEvents & FD_ACCEPT) + ff |= kfRead; + if (wsaEvents.lNetworkEvents & FD_CLOSE) { + ff |= kfClose; + errcode = wsaEvents.iErrorCode[FD_CLOSE_BIT]; + } + if (ff != 0) { + disp->OnPreEvent(ff); + disp->OnEvent(ff, errcode); + } + } + } + } + + // Reset the network event until new activity occurs + WSAResetEvent(socket_ev); + } + + // Break? + + if (!fWait_) + break; + cmsElapsed = GetMillisecondCount() - msStart; + if (cmsWait != -1) { + if (cmsElapsed >= cmsWait) + break; + } + } + + // Done + + WSACloseEvent(socket_ev); + return true; +} +#endif // WIN32 + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.h new file mode 100644 index 00000000..305b64d9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.h @@ -0,0 +1,80 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __PHYSICALSOCKETSERVER_H__ +#define __PHYSICALSOCKETSERVER_H__ + +#include "talk/base/asyncfile.h" +#include "talk/base/socketserver.h" +#include "talk/base/criticalsection.h" +#include + +#ifdef POSIX +typedef int SOCKET; +#endif // POSIX + +namespace cricket { + +class Dispatcher; +class Signaler; + +// A socket server that provides the real sockets of the underlying OS. +class PhysicalSocketServer : public SocketServer { +public: + PhysicalSocketServer(); + virtual ~PhysicalSocketServer(); + + // SocketFactory: + virtual Socket* CreateSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int type); + + // Internal Factory for Accept + AsyncSocket* WrapSocket(SOCKET s); + + // SocketServer: + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + void Add(Dispatcher* dispatcher); + void Remove(Dispatcher* dispatcher); + +#ifdef POSIX + AsyncFile* CreateFile(int fd); +#endif + +private: + std::vector dispatchers_; + Signaler* signal_wakeup_; + CriticalSection crit_; + bool fWait_; + uint32 last_tick_tracked_; + int last_tick_dispatch_count_; +}; + +} // namespace cricket + +#endif // __PHYSICALSOCKETSERVER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/proxyinfo.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/proxyinfo.h new file mode 100644 index 00000000..1bd817b9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/proxyinfo.h @@ -0,0 +1,52 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __PROXYINFO_H__ +#define __PROXYINFO_H__ + +#include +#include "talk/base/socketaddress.h" +// TODO: move xmpppassword into base +#include "talk/xmpp/xmpppassword.h" + +namespace cricket { + +enum ProxyType { PROXY_NONE, PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN }; +const char * ProxyToString(ProxyType proxy); + +struct ProxyInfo { + ProxyType type; + SocketAddress address; + std::string username; + buzz::XmppPassword password; + + ProxyInfo() : type(PROXY_NONE) { } +}; + +} // namespace cricket + +#endif // __PROXYINFO_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/scoped_ptr.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/scoped_ptr.h new file mode 100644 index 00000000..0470ff83 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/scoped_ptr.h @@ -0,0 +1,259 @@ +#ifndef SCOPED_PTR_H +#define SCOPED_PTR_H + +// (C) Copyright Greg Colvin and Beman Dawes 1998, 1999. +// Copyright (c) 2001, 2002 Peter Dimov +// +// Permission to copy, use, modify, sell and distribute this software +// is granted provided this copyright notice appears in all copies. +// This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// See http://www.boost.org/libs/smart_ptr/scoped_ptr.htm for documentation. +// + +// scoped_ptr mimics a built-in pointer except that it guarantees deletion +// of the object pointed to, either on destruction of the scoped_ptr or via +// an explicit reset(). scoped_ptr is a simple solution for simple needs; +// use shared_ptr or std::auto_ptr if your needs are more complex. + +// scoped_ptr_malloc added in by Google. When one of +// these goes out of scope, instead of doing a delete or delete[], it +// calls free(). scoped_ptr_malloc is likely to see much more +// use than any other specializations. + +// release() added in by Google. Use this to conditionally +// transfer ownership of a heap-allocated object to the caller, usually on +// method success. + +#include // for std::ptrdiff_t +#include // for assert +#include // for free() decl + +#ifdef _WIN32 +namespace std { using ::ptrdiff_t; }; +#endif // _WIN32 + +namespace buzz { + +template +class scoped_ptr { + private: + + T* ptr; + + scoped_ptr(scoped_ptr const &); + scoped_ptr & operator=(scoped_ptr const &); + + public: + + typedef T element_type; + + explicit scoped_ptr(T* p = 0): ptr(p) {} + + ~scoped_ptr() { + typedef char type_must_be_complete[sizeof(T)]; + delete ptr; + } + + void reset(T* p = 0) { + typedef char type_must_be_complete[sizeof(T)]; + + if (ptr != p) { + delete ptr; + ptr = p; + } + } + + T& operator*() const { + assert(ptr != 0); + return *ptr; + } + + T* operator->() const { + assert(ptr != 0); + return ptr; + } + + T* get() const { + return ptr; + } + + void swap(scoped_ptr & b) { + T* tmp = b.ptr; + b.ptr = ptr; + ptr = tmp; + } + + T* release() { + T* tmp = ptr; + ptr = 0; + return tmp; + } + + T** accept() { + if (ptr) { + delete ptr; + ptr = 0; + } + return &ptr; + } + + T** use() { + return &ptr; + } +}; + +template inline +void swap(scoped_ptr& a, scoped_ptr& b) { + a.swap(b); +} + + + + +// scoped_array extends scoped_ptr to arrays. Deletion of the array pointed to +// is guaranteed, either on destruction of the scoped_array or via an explicit +// reset(). Use shared_array or std::vector if your needs are more complex. + +template +class scoped_array { + private: + + T* ptr; + + scoped_array(scoped_array const &); + scoped_array & operator=(scoped_array const &); + + public: + + typedef T element_type; + + explicit scoped_array(T* p = 0) : ptr(p) {} + + ~scoped_array() { + typedef char type_must_be_complete[sizeof(T)]; + delete[] ptr; + } + + void reset(T* p = 0) { + typedef char type_must_be_complete[sizeof(T)]; + + if (ptr != p) { + delete [] ptr; + ptr = p; + } + } + + T& operator[](std::ptrdiff_t i) const { + assert(ptr != 0); + assert(i >= 0); + return ptr[i]; + } + + T* get() const { + return ptr; + } + + void swap(scoped_array & b) { + T* tmp = b.ptr; + b.ptr = ptr; + ptr = tmp; + } + + T* release() { + T* tmp = ptr; + ptr = 0; + return tmp; + } + + T** accept() { + if (ptr) { + delete [] ptr; + ptr = 0; + } + return &ptr; + } +}; + +template inline +void swap(scoped_array& a, scoped_array& b) { + a.swap(b); +} + +// scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a +// second template argument, the function used to free the object. + +template class scoped_ptr_malloc { + private: + + T* ptr; + + scoped_ptr_malloc(scoped_ptr_malloc const &); + scoped_ptr_malloc & operator=(scoped_ptr_malloc const &); + + public: + + typedef T element_type; + + explicit scoped_ptr_malloc(T* p = 0): ptr(p) {} + + ~scoped_ptr_malloc() { + typedef char type_must_be_complete[sizeof(T)]; + FF(static_cast(ptr)); + } + + void reset(T* p = 0) { + typedef char type_must_be_complete[sizeof(T)]; + + if (ptr != p) { + FF(static_cast(ptr)); + ptr = p; + } + } + + T& operator*() const { + assert(ptr != 0); + return *ptr; + } + + T* operator->() const { + assert(ptr != 0); + return ptr; + } + + T* get() const { + return ptr; + } + + void swap(scoped_ptr_malloc & b) { + T* tmp = b.ptr; + b.ptr = ptr; + ptr = tmp; + } + + T* release() { + T* tmp = ptr; + ptr = 0; + return tmp; + } + + T** accept() { + if (ptr) { + FF(static_cast(ptr)); + ptr = 0; + } + return &ptr; + } +}; + +template inline +void swap(scoped_ptr_malloc& a, scoped_ptr_malloc& b) { + a.swap(b); +} + +} + +using buzz::scoped_ptr; + +#endif // #ifndef SCOPED_PTR_H diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/sigslot.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/sigslot.h new file mode 100644 index 00000000..446516b8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/sigslot.h @@ -0,0 +1,2700 @@ +// sigslot.h: Signal/Slot classes +// +// Written by Sarah Thompson (sarah@telergy.com) 2002. +// +// License: Public domain. You are free to use this code however you like, with the proviso that +// the author takes on no responsibility or liability for any use. +// +// QUICK DOCUMENTATION +// +// (see also the full documentation at http://sigslot.sourceforge.net/) +// +// #define switches +// SIGSLOT_PURE_ISO - Define this to force ISO C++ compliance. This also disables +// all of the thread safety support on platforms where it is +// available. +// +// SIGSLOT_USE_POSIX_THREADS - Force use of Posix threads when using a C++ compiler other than +// gcc on a platform that supports Posix threads. (When using gcc, +// this is the default - use SIGSLOT_PURE_ISO to disable this if +// necessary) +// +// SIGSLOT_DEFAULT_MT_POLICY - Where thread support is enabled, this defaults to multi_threaded_global. +// Otherwise, the default is single_threaded. #define this yourself to +// override the default. In pure ISO mode, anything other than +// single_threaded will cause a compiler error. +// +// PLATFORM NOTES +// +// Win32 - On Win32, the WIN32 symbol must be #defined. Most mainstream +// compilers do this by default, but you may need to define it +// yourself if your build environment is less standard. This causes +// the Win32 thread support to be compiled in and used automatically. +// +// Unix/Linux/BSD, etc. - If you're using gcc, it is assumed that you have Posix threads +// available, so they are used automatically. You can override this +// (as under Windows) with the SIGSLOT_PURE_ISO switch. If you're using +// something other than gcc but still want to use Posix threads, you +// need to #define SIGSLOT_USE_POSIX_THREADS. +// +// ISO C++ - If none of the supported platforms are detected, or if +// SIGSLOT_PURE_ISO is defined, all multithreading support is turned off, +// along with any code that might cause a pure ISO C++ environment to +// complain. Before you ask, gcc -ansi -pedantic won't compile this +// library, but gcc -ansi is fine. Pedantic mode seems to throw a lot of +// errors that aren't really there. If you feel like investigating this, +// please contact the author. +// +// +// THREADING MODES +// +// single_threaded - Your program is assumed to be single threaded from the point of view +// of signal/slot usage (i.e. all objects using signals and slots are +// created and destroyed from a single thread). Behaviour if objects are +// destroyed concurrently is undefined (i.e. you'll get the occasional +// segmentation fault/memory exception). +// +// multi_threaded_global - Your program is assumed to be multi threaded. Objects using signals and +// slots can be safely created and destroyed from any thread, even when +// connections exist. In multi_threaded_global mode, this is achieved by a +// single global mutex (actually a critical section on Windows because they +// are faster). This option uses less OS resources, but results in more +// opportunities for contention, possibly resulting in more context switches +// than are strictly necessary. +// +// multi_threaded_local - Behaviour in this mode is essentially the same as multi_threaded_global, +// except that each signal, and each object that inherits has_slots, all +// have their own mutex/critical section. In practice, this means that +// mutex collisions (and hence context switches) only happen if they are +// absolutely essential. However, on some platforms, creating a lot of +// mutexes can slow down the whole OS, so use this option with care. +// +// USING THE LIBRARY +// +// See the full documentation at http://sigslot.sourceforge.net/ +// +// + +#ifndef SIGSLOT_H__ +#define SIGSLOT_H__ + +#include +#include + +// On our copy of sigslot.h, we force single threading +#define SIGSLOT_PURE_ISO + +#if defined(SIGSLOT_PURE_ISO) || (!defined(WIN32) && !defined(__GNUG__) && !defined(SIGSLOT_USE_POSIX_THREADS)) +# define _SIGSLOT_SINGLE_THREADED +#elif defined(WIN32) +# define _SIGSLOT_HAS_WIN32_THREADS +# include +#elif defined(__GNUG__) || defined(SIGSLOT_USE_POSIX_THREADS) +# define _SIGSLOT_HAS_POSIX_THREADS +# include +#else +# define _SIGSLOT_SINGLE_THREADED +#endif + +#ifndef SIGSLOT_DEFAULT_MT_POLICY +# ifdef _SIGSLOT_SINGLE_THREADED +# define SIGSLOT_DEFAULT_MT_POLICY single_threaded +# else +# define SIGSLOT_DEFAULT_MT_POLICY multi_threaded_local +# endif +#endif + + +namespace sigslot { + + class single_threaded + { + public: + single_threaded() + { + ; + } + + virtual ~single_threaded() + { + ; + } + + virtual void lock() + { + ; + } + + virtual void unlock() + { + ; + } + }; + +#ifdef _SIGSLOT_HAS_WIN32_THREADS + // The multi threading policies only get compiled in if they are enabled. + class multi_threaded_global + { + public: + multi_threaded_global() + { + static bool isinitialised = false; + + if(!isinitialised) + { + InitializeCriticalSection(get_critsec()); + isinitialised = true; + } + } + + multi_threaded_global(const multi_threaded_global&) + { + ; + } + + virtual ~multi_threaded_global() + { + ; + } + + virtual void lock() + { + EnterCriticalSection(get_critsec()); + } + + virtual void unlock() + { + LeaveCriticalSection(get_critsec()); + } + + private: + CRITICAL_SECTION* get_critsec() + { + static CRITICAL_SECTION g_critsec; + return &g_critsec; + } + }; + + class multi_threaded_local + { + public: + multi_threaded_local() + { + InitializeCriticalSection(&m_critsec); + } + + multi_threaded_local(const multi_threaded_local&) + { + InitializeCriticalSection(&m_critsec); + } + + virtual ~multi_threaded_local() + { + DeleteCriticalSection(&m_critsec); + } + + virtual void lock() + { + EnterCriticalSection(&m_critsec); + } + + virtual void unlock() + { + LeaveCriticalSection(&m_critsec); + } + + private: + CRITICAL_SECTION m_critsec; + }; +#endif // _SIGSLOT_HAS_WIN32_THREADS + +#ifdef _SIGSLOT_HAS_POSIX_THREADS + // The multi threading policies only get compiled in if they are enabled. + class multi_threaded_global + { + public: + multi_threaded_global() + { + pthread_mutex_init(get_mutex(), NULL); + } + + multi_threaded_global(const multi_threaded_global&) + { + ; + } + + virtual ~multi_threaded_global() + { + ; + } + + virtual void lock() + { + pthread_mutex_lock(get_mutex()); + } + + virtual void unlock() + { + pthread_mutex_unlock(get_mutex()); + } + + private: + pthread_mutex_t* get_mutex() + { + static pthread_mutex_t g_mutex; + return &g_mutex; + } + }; + + class multi_threaded_local + { + public: + multi_threaded_local() + { + pthread_mutex_init(&m_mutex, NULL); + } + + multi_threaded_local(const multi_threaded_local&) + { + pthread_mutex_init(&m_mutex, NULL); + } + + virtual ~multi_threaded_local() + { + pthread_mutex_destroy(&m_mutex); + } + + virtual void lock() + { + pthread_mutex_lock(&m_mutex); + } + + virtual void unlock() + { + pthread_mutex_unlock(&m_mutex); + } + + private: + pthread_mutex_t m_mutex; + }; +#endif // _SIGSLOT_HAS_POSIX_THREADS + + template + class lock_block + { + public: + mt_policy *m_mutex; + + lock_block(mt_policy *mtx) + : m_mutex(mtx) + { + m_mutex->lock(); + } + + ~lock_block() + { + m_mutex->unlock(); + } + }; + + template + class has_slots; + + template + class _connection_base0 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit() = 0; + virtual _connection_base0* clone() = 0; + virtual _connection_base0* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base1 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type) = 0; + virtual _connection_base1* clone() = 0; + virtual _connection_base1* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base2 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type) = 0; + virtual _connection_base2* clone() = 0; + virtual _connection_base2* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base3 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type) = 0; + virtual _connection_base3* clone() = 0; + virtual _connection_base3* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base4 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type) = 0; + virtual _connection_base4* clone() = 0; + virtual _connection_base4* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base5 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type) = 0; + virtual _connection_base5* clone() = 0; + virtual _connection_base5* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base6 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type) = 0; + virtual _connection_base6* clone() = 0; + virtual _connection_base6* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base7 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type, arg7_type) = 0; + virtual _connection_base7* clone() = 0; + virtual _connection_base7* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base8 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type, arg7_type, arg8_type) = 0; + virtual _connection_base8* clone() = 0; + virtual _connection_base8* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _signal_base : public mt_policy + { + public: + virtual void slot_disconnect(has_slots* pslot) = 0; + virtual void slot_duplicate(const has_slots* poldslot, has_slots* pnewslot) = 0; + }; + + template + class has_slots : public mt_policy + { + private: + typedef typename std::set<_signal_base *> sender_set; + typedef typename sender_set::const_iterator const_iterator; + + public: + has_slots() + { + ; + } + + has_slots(const has_slots& hs) + : mt_policy(hs) + { + lock_block lock(this); + const_iterator it = hs.m_senders.begin(); + const_iterator itEnd = hs.m_senders.end(); + + while(it != itEnd) + { + (*it)->slot_duplicate(&hs, this); + m_senders.insert(*it); + ++it; + } + } + + void signal_connect(_signal_base* sender) + { + lock_block lock(this); + m_senders.insert(sender); + } + + void signal_disconnect(_signal_base* sender) + { + lock_block lock(this); + m_senders.erase(sender); + } + + virtual ~has_slots() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_senders.begin(); + const_iterator itEnd = m_senders.end(); + + while(it != itEnd) + { + (*it)->slot_disconnect(this); + ++it; + } + + m_senders.erase(m_senders.begin(), m_senders.end()); + } + + private: + sender_set m_senders; + }; + + template + class _signal_base0 : public _signal_base + { + public: + typedef std::list<_connection_base0 *> connections_list; + + _signal_base0() + { + ; + } + + _signal_base0(const _signal_base0& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + ~_signal_base0() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base1 : public _signal_base + { + public: + typedef std::list<_connection_base1 *> connections_list; + + _signal_base1() + { + ; + } + + _signal_base1(const _signal_base1& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base1() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base2 : public _signal_base + { + public: + typedef std::list<_connection_base2 *> + connections_list; + + _signal_base2() + { + ; + } + + _signal_base2(const _signal_base2& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base2() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base3 : public _signal_base + { + public: + typedef std::list<_connection_base3 *> + connections_list; + + _signal_base3() + { + ; + } + + _signal_base3(const _signal_base3& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base3() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base4 : public _signal_base + { + public: + typedef std::list<_connection_base4 *> connections_list; + + _signal_base4() + { + ; + } + + _signal_base4(const _signal_base4& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base4() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base5 : public _signal_base + { + public: + typedef std::list<_connection_base5 *> connections_list; + + _signal_base5() + { + ; + } + + _signal_base5(const _signal_base5& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base5() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base6 : public _signal_base + { + public: + typedef std::list<_connection_base6 *> connections_list; + + _signal_base6() + { + ; + } + + _signal_base6(const _signal_base6& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base6() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base7 : public _signal_base + { + public: + typedef std::list<_connection_base7 *> connections_list; + + _signal_base7() + { + ; + } + + _signal_base7(const _signal_base7& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base7() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base8 : public _signal_base + { + public: + typedef std::list<_connection_base8 *> + connections_list; + + _signal_base8() + { + ; + } + + _signal_base8(const _signal_base8& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base8() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + + template + class _connection0 : public _connection_base0 + { + public: + _connection0() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection0(dest_type* pobject, void (dest_type::*pmemfun)()) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base0* clone() + { + return new _connection0(*this); + } + + virtual _connection_base0* duplicate(has_slots* pnewdest) + { + return new _connection0((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit() + { + (m_pobject->*m_pmemfun)(); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(); + }; + + template + class _connection1 : public _connection_base1 + { + public: + _connection1() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection1(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base1* clone() + { + return new _connection1(*this); + } + + virtual _connection_base1* duplicate(has_slots* pnewdest) + { + return new _connection1((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1) + { + (m_pobject->*m_pmemfun)(a1); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type); + }; + + template + class _connection2 : public _connection_base2 + { + public: + _connection2() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection2(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base2* clone() + { + return new _connection2(*this); + } + + virtual _connection_base2* duplicate(has_slots* pnewdest) + { + return new _connection2((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2) + { + (m_pobject->*m_pmemfun)(a1, a2); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type); + }; + + template + class _connection3 : public _connection_base3 + { + public: + _connection3() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection3(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base3* clone() + { + return new _connection3(*this); + } + + virtual _connection_base3* duplicate(has_slots* pnewdest) + { + return new _connection3((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3) + { + (m_pobject->*m_pmemfun)(a1, a2, a3); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type); + }; + + template + class _connection4 : public _connection_base4 + { + public: + _connection4() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection4(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base4* clone() + { + return new _connection4(*this); + } + + virtual _connection_base4* duplicate(has_slots* pnewdest) + { + return new _connection4((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, + arg4_type a4) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, + arg4_type); + }; + + template + class _connection5 : public _connection_base5 + { + public: + _connection5() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection5(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base5* clone() + { + return new _connection5(*this); + } + + virtual _connection_base5* duplicate(has_slots* pnewdest) + { + return new _connection5((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type); + }; + + template + class _connection6 : public _connection_base6 + { + public: + _connection6() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection6(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base6* clone() + { + return new _connection6(*this); + } + + virtual _connection_base6* duplicate(has_slots* pnewdest) + { + return new _connection6((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type); + }; + + template + class _connection7 : public _connection_base7 + { + public: + _connection7() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection7(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, arg7_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base7* clone() + { + return new _connection7(*this); + } + + virtual _connection_base7* duplicate(has_slots* pnewdest) + { + return new _connection7((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type, arg7_type); + }; + + template + class _connection8 : public _connection_base8 + { + public: + _connection8() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection8(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type, arg8_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base8* clone() + { + return new _connection8(*this); + } + + virtual _connection_base8* duplicate(has_slots* pnewdest) + { + return new _connection8((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7, a8); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type, arg7_type, arg8_type); + }; + + template + class signal0 : public _signal_base0 + { + public: + typedef _signal_base0 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal0() + { + ; + } + + signal0(const signal0& s) + : _signal_base0(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)()) + { + lock_block lock(this); + _connection0* conn = + new _connection0(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit() + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(); + + it = itNext; + } + } + + void operator()() + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(); + + it = itNext; + } + } + }; + + template + class signal1 : public _signal_base1 + { + public: + typedef _signal_base1 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal1() + { + ; + } + + signal1(const signal1& s) + : _signal_base1(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type)) + { + lock_block lock(this); + _connection1* conn = + new _connection1(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1); + + it = itNext; + } + } + + void operator()(arg1_type a1) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1); + + it = itNext; + } + } + }; + + template + class signal2 : public _signal_base2 + { + public: + typedef _signal_base2 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal2() + { + ; + } + + signal2(const signal2& s) + : _signal_base2(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type)) + { + lock_block lock(this); + _connection2* conn = new + _connection2(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2); + + it = itNext; + } + } + }; + + template + class signal3 : public _signal_base3 + { + public: + typedef _signal_base3 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal3() + { + ; + } + + signal3(const signal3& s) + : _signal_base3(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type)) + { + lock_block lock(this); + _connection3* conn = + new _connection3(pclass, + pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3); + + it = itNext; + } + } + }; + + template + class signal4 : public _signal_base4 + { + public: + typedef _signal_base4 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal4() + { + ; + } + + signal4(const signal4& s) + : _signal_base4(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type)) + { + lock_block lock(this); + _connection4* + conn = new _connection4(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4); + + it = itNext; + } + } + }; + + template + class signal5 : public _signal_base5 + { + public: + typedef _signal_base5 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal5() + { + ; + } + + signal5(const signal5& s) + : _signal_base5(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type)) + { + lock_block lock(this); + _connection5* conn = new _connection5(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5); + + it = itNext; + } + } + }; + + + template + class signal6 : public _signal_base6 + { + public: + typedef _signal_base6 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal6() + { + ; + } + + signal6(const signal6& s) + : _signal_base6(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type)) + { + lock_block lock(this); + _connection6* conn = + new _connection6(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6); + + it = itNext; + } + } + }; + + template + class signal7 : public _signal_base7 + { + public: + typedef _signal_base7 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal7() + { + ; + } + + signal7(const signal7& s) + : _signal_base7(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type)) + { + lock_block lock(this); + _connection7* conn = + new _connection7(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7); + + it = itNext; + } + } + }; + + template + class signal8 : public _signal_base8 + { + public: + typedef _signal_base8 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal8() + { + ; + } + + signal8(const signal8& s) + : _signal_base8(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type, arg8_type)) + { + lock_block lock(this); + _connection8* conn = + new _connection8(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8); + + it = itNext; + } + } + }; + +}; // namespace sigslot + +#endif // SIGSLOT_H__ + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socket.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/socket.h new file mode 100644 index 00000000..d4a49d96 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socket.h @@ -0,0 +1,158 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _socket_h_ +#define _socket_h_ + +#include "talk/base/basictypes.h" +#include "talk/base/socketaddress.h" + +#ifdef POSIX +#include +#include +#include +#include +#include +#endif + +#ifdef WIN32 +#include "talk/base/win32.h" +#endif + +// Rather than converting errors into a private namespace, +// Reuse the POSIX socket api errors. Note this depends on +// Win32 compatibility. + +#ifdef WIN32 +#define EWOULDBLOCK WSAEWOULDBLOCK +#define EINPROGRESS WSAEINPROGRESS +#define EALREADY WSAEALREADY +#define ENOTSOCK WSAENOTSOCK +#define EDESTADDRREQ WSAEDESTADDRREQ +#define EMSGSIZE WSAEMSGSIZE +#define EPROTOTYPE WSAEPROTOTYPE +#define ENOPROTOOPT WSAENOPROTOOPT +#define EPROTONOSUPPORT WSAEPROTONOSUPPORT +#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT +#define EOPNOTSUPP WSAEOPNOTSUPP +#define EPFNOSUPPORT WSAEPFNOSUPPORT +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#define EADDRINUSE WSAEADDRINUSE +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL +#define ENETDOWN WSAENETDOWN +#define ENETUNREACH WSAENETUNREACH +#define ENETRESET WSAENETRESET +#define ECONNABORTED WSAECONNABORTED +#define ECONNRESET WSAECONNRESET +#define ENOBUFS WSAENOBUFS +#define EISCONN WSAEISCONN +#define ENOTCONN WSAENOTCONN +#define ESHUTDOWN WSAESHUTDOWN +#define ETOOMANYREFS WSAETOOMANYREFS +#define ETIMEDOUT WSAETIMEDOUT +#define ECONNREFUSED WSAECONNREFUSED +#define ELOOP WSAELOOP +#undef ENAMETOOLONG // remove errno.h's definition +#define ENAMETOOLONG WSAENAMETOOLONG +#define EHOSTDOWN WSAEHOSTDOWN +#define EHOSTUNREACH WSAEHOSTUNREACH +#undef ENOTEMPTY // remove errno.h's definition +#define ENOTEMPTY WSAENOTEMPTY +#define EPROCLIM WSAEPROCLIM +#define EUSERS WSAEUSERS +#define EDQUOT WSAEDQUOT +#define ESTALE WSAESTALE +#define EREMOTE WSAEREMOTE +#undef EACCES +#define EACCES WSAEACCES +#endif // WIN32 + +#ifdef POSIX +#define INVALID_SOCKET (-1) +#define SOCKET_ERROR (-1) +#define closesocket(s) close(s) +#endif // POSIX + +namespace cricket { + +inline bool IsBlockingError(int e) { + return (e == EWOULDBLOCK) || (e == EAGAIN) || (e == EINPROGRESS); +} + +// General interface for the socket implementations of various networks. The +// methods match those of normal UNIX sockets very closely. +class Socket { +public: + virtual ~Socket() {} + + // Returns the address to which the socket is bound. If the socket is not + // bound, then the any-address is returned. + virtual SocketAddress GetLocalAddress() const = 0; + + // Returns the address to which the socket is connected. If the socket is + // not connected, then the any-address is returned. + virtual SocketAddress GetRemoteAddress() const = 0; + + virtual int Bind(const SocketAddress& addr) = 0; + virtual int Connect(const SocketAddress& addr) = 0; + virtual int Send(const void *pv, size_t cb) = 0; + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) = 0; + virtual int Recv(void *pv, size_t cb) = 0; + virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) = 0; + virtual int Listen(int backlog) = 0; + virtual Socket *Accept(SocketAddress *paddr) = 0; + virtual int Close() = 0; + virtual int GetError() const = 0; + virtual void SetError(int error) = 0; + inline bool IsBlocking() const { return IsBlockingError(GetError()); } + + enum ConnState { + CS_CLOSED, + CS_CONNECTING, + CS_CONNECTED + }; + virtual ConnState GetState() const = 0; + + // Fills in the given uint16 with the current estimate of the MTU along the + // path to the address to which this socket is connected. + virtual int EstimateMTU(uint16* mtu) = 0; + + enum Option { + OPT_DONTFRAGMENT + }; + virtual int SetOption(Option opt, int value) = 0; + +protected: + Socket() {} + +private: + DISALLOW_EVIL_CONSTRUCTORS(Socket); +}; + +} // namespace cricket + +#endif // _socket_h_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.cc new file mode 100644 index 00000000..049e923c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.cc @@ -0,0 +1,1130 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif + +#include + +#ifdef WIN32 +#include +#include +#define _WINSOCKAPI_ +#include +#include // HTTP_STATUS_PROXY_AUTH_REQ +#define SECURITY_WIN32 +#include +#endif + +#include + +#include "talk/base/base64.h" +#include "talk/base/basicdefs.h" +#include "talk/base/bytebuffer.h" +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/md5.h" +#include "talk/base/socketadapters.h" +#include "talk/base/stringutils.h" + +#include + + +#ifdef WIN32 +#include "talk/base/sec_buffer.h" +#endif // WIN32 + +namespace cricket { + +#ifdef WIN32 +extern const ConstantLabel SECURITY_ERRORS[]; +#endif + +BufferedReadAdapter::BufferedReadAdapter(AsyncSocket* socket, size_t buffer_size) + : AsyncSocketAdapter(socket), buffer_size_(buffer_size), data_len_(0), buffering_(false) { + buffer_ = new char[buffer_size_]; +} + +BufferedReadAdapter::~BufferedReadAdapter() { + delete [] buffer_; +} + +int BufferedReadAdapter::Send(const void *pv, size_t cb) { + if (buffering_) { + // TODO: Spoof error better; Signal Writeable + socket_->SetError(EWOULDBLOCK); + return -1; + } + return AsyncSocketAdapter::Send(pv, cb); +} + +int BufferedReadAdapter::Recv(void *pv, size_t cb) { + if (buffering_) { + socket_->SetError(EWOULDBLOCK); + return -1; + } + + size_t read = 0; + + if (data_len_) { + read = _min(cb, data_len_); + memcpy(pv, buffer_, read); + data_len_ -= read; + if (data_len_ > 0) { + memmove(buffer_, buffer_ + read, data_len_); + } + pv = static_cast(pv) + read; + cb -= read; + } + + // FIX: If cb == 0, we won't generate another read event + + int res = AsyncSocketAdapter::Recv(pv, cb); + if (res < 0) + return res; + + return res + static_cast(read); +} + +void BufferedReadAdapter::BufferInput(bool on) { + buffering_ = on; +} + +void BufferedReadAdapter::OnReadEvent(AsyncSocket * socket) { + assert(socket == socket_); + + if (!buffering_) { + AsyncSocketAdapter::OnReadEvent(socket); + return; + } + + if (data_len_ >= buffer_size_) { + LOG(INFO) << "Input buffer overflow"; + assert(false); + data_len_ = 0; + } + + int len = socket_->Recv(buffer_ + data_len_, buffer_size_ - data_len_); + if (len < 0) { + // TODO: Do something better like forwarding the error to the user. + LOG(INFO) << "Recv: " << errno << " " << std::strerror(errno); + return; + } + + data_len_ += len; + + ProcessInput(buffer_, data_len_); +} + +/////////////////////////////////////////////////////////////////////////////// + +const uint8 SSL_SERVER_HELLO[] = { + 22,3,1,0,74,2,0,0,70,3,1,66,133,69,167,39,169,93,160, + 179,197,231,83,218,72,43,63,198,90,202,137,193,88,82, + 161,120,60,91,23,70,0,133,63,32,14,211,6,114,91,91, + 27,95,21,172,19,249,136,83,157,155,232,61,123,12,48, + 50,110,56,77,162,117,87,65,108,52,92,0,4,0 +}; + +const char SSL_CLIENT_HELLO[] = { + -128,70,1,3,1,0,45,0,0,0,16,1,0,-128,3,0,-128,7,0,-64,6,0,64,2,0, + -128,4,0,-128,0,0,4,0,-2,-1,0,0,10,0,-2,-2,0,0,9,0,0,100,0,0,98,0, + 0,3,0,0,6,31,23,12,-90,47,0,120,-4,70,85,46,-79,-125,57,-15,-22 +}; + +AsyncSSLSocket::AsyncSSLSocket(AsyncSocket* socket) : BufferedReadAdapter(socket, 1024) { +} + +int AsyncSSLSocket::Connect(const SocketAddress& addr) { + // Begin buffering before we connect, so that there isn't a race condition between + // potential senders and receiving the OnConnectEvent signal + BufferInput(true); + return BufferedReadAdapter::Connect(addr); +} + +void AsyncSSLSocket::OnConnectEvent(AsyncSocket * socket) { + assert(socket == socket_); + + // TODO: we could buffer output too... + int res = DirectSend(SSL_CLIENT_HELLO, sizeof(SSL_CLIENT_HELLO)); + assert(res == sizeof(SSL_CLIENT_HELLO)); +} + +void AsyncSSLSocket::ProcessInput(char * data, size_t& len) { + if (len < sizeof(SSL_SERVER_HELLO)) + return; + + if (memcmp(SSL_SERVER_HELLO, data, sizeof(SSL_SERVER_HELLO)) != 0) { + Close(); + SignalCloseEvent(this, 0); // TODO: error code? + return; + } + + len -= sizeof(SSL_SERVER_HELLO); + if (len > 0) { + memmove(data, data + sizeof(SSL_SERVER_HELLO), len); + } + + bool remainder = (len > 0); + BufferInput(false); + SignalConnectEvent(this); + + // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble + if (remainder) + SignalReadEvent(this); +} + +/////////////////////////////////////////////////////////////////////////////// + +#define TEST_DIGEST 0 +#if TEST_DIGEST +/* +const char * const DIGEST_CHALLENGE = + "Digest realm=\"testrealm@host.com\"," + " qop=\"auth,auth-int\"," + " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"," + " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""; +const char * const DIGEST_METHOD = "GET"; +const char * const DIGEST_URI = + "/dir/index.html";; +const char * const DIGEST_CNONCE = + "0a4f113b"; +const char * const DIGEST_RESPONSE = + "6629fae49393a05397450978507c4ef1"; +//user_ = "Mufasa"; +//pass_ = "Circle Of Life"; +*/ +const char * const DIGEST_CHALLENGE = + "Digest realm=\"Squid proxy-caching web server\"," + " nonce=\"Nny4QuC5PwiSDixJ\"," + " qop=\"auth\"," + " stale=false"; +const char * const DIGEST_URI = + "/"; +const char * const DIGEST_CNONCE = + "6501d58e9a21cee1e7b5fec894ded024"; +const char * const DIGEST_RESPONSE = + "edffcb0829e755838b073a4a42de06bc"; +#endif + +static std::string MD5(const std::string& data) { + MD5_CTX ctx; + MD5Init(&ctx); + MD5Update(&ctx, const_cast(reinterpret_cast(data.data())), static_cast(data.size())); + unsigned char digest[16]; + MD5Final(digest, &ctx); + std::string hex_digest; + const char HEX[] = "0123456789abcdef"; + for (int i=0; i<16; ++i) { + hex_digest += HEX[digest[i] >> 4]; + hex_digest += HEX[digest[i] & 0xf]; + } + return hex_digest; +} + +static std::string Quote(const std::string& str) { + std::string result; + result.push_back('"'); + for (size_t i=0; i args; + ParseAuth(challenge, len, auth_method, args); + + if (context && (context->auth_method != auth_method)) + return AR_IGNORE; + + // BASIC + if (stricmp(auth_method.c_str(), "basic") == 0) { + if (context) + return AR_CREDENTIALS; // Bad credentials + if (username.empty()) + return AR_CREDENTIALS; // Missing credentials + + context = new AuthContext(auth_method); + + // TODO: convert sensitive to a secure buffer that gets securely deleted + //std::string decoded = username + ":" + password; + size_t len = username.size() + password.GetLength() + 2; + char * sensitive = new char[len]; + size_t pos = strcpyn(sensitive, len, username.data(), username.size()); + pos += strcpyn(sensitive + pos, len - pos, ":"); + password.CopyTo(sensitive + pos, true); + + response = auth_method; + response.append(" "); + // TODO: create a sensitive-source version of Base64::encode + response.append(Base64::encode(sensitive)); + memset(sensitive, 0, len); + delete [] sensitive; + return AR_RESPONSE; + } + + // DIGEST + if (stricmp(auth_method.c_str(), "digest") == 0) { + if (context) + return AR_CREDENTIALS; // Bad credentials + if (username.empty()) + return AR_CREDENTIALS; // Missing credentials + + context = new AuthContext(auth_method); + + std::string cnonce, ncount; +#if TEST_DIGEST + method = DIGEST_METHOD; + uri = DIGEST_URI; + cnonce = DIGEST_CNONCE; +#else + char buffer[256]; + sprintf(buffer, "%d", time(0)); + cnonce = MD5(buffer); +#endif + ncount = "00000001"; + + // TODO: convert sensitive to be secure buffer + //std::string A1 = username + ":" + args["realm"] + ":" + password; + size_t len = username.size() + args["realm"].size() + password.GetLength() + 3; + char * sensitive = new char[len]; // A1 + size_t pos = strcpyn(sensitive, len, username.data(), username.size()); + pos += strcpyn(sensitive + pos, len - pos, ":"); + pos += strcpyn(sensitive + pos, len - pos, args["realm"].c_str()); + pos += strcpyn(sensitive + pos, len - pos, ":"); + password.CopyTo(sensitive + pos, true); + + std::string A2 = method + ":" + uri; + std::string middle; + if (args.find("qop") != args.end()) { + args["qop"] = "auth"; + middle = args["nonce"] + ":" + ncount + ":" + cnonce + ":" + args["qop"]; + } else { + middle = args["nonce"]; + } + std::string HA1 = MD5(sensitive); + memset(sensitive, 0, len); + delete [] sensitive; + std::string HA2 = MD5(A2); + std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2); + +#if TEST_DIGEST + assert(strcmp(dig_response.c_str(), DIGEST_RESPONSE) == 0); +#endif + + std::stringstream ss; + ss << auth_method; + ss << " username=" << Quote(username); + ss << ", realm=" << Quote(args["realm"]); + ss << ", nonce=" << Quote(args["nonce"]); + ss << ", uri=" << Quote(uri); + if (args.find("qop") != args.end()) { + ss << ", qop=" << args["qop"]; + ss << ", nc=" << ncount; + ss << ", cnonce=" << Quote(cnonce); + } + ss << ", response=\"" << dig_response << "\""; + if (args.find("opaque") != args.end()) { + ss << ", opaque=" << Quote(args["opaque"]); + } + response = ss.str(); + return AR_RESPONSE; + } + +#ifdef WIN32 +#if 1 + bool want_negotiate = (stricmp(auth_method.c_str(), "negotiate") == 0); + bool want_ntlm = (stricmp(auth_method.c_str(), "ntlm") == 0); + // SPNEGO & NTLM + if (want_negotiate || want_ntlm) { + const size_t MAX_MESSAGE = 12000, MAX_SPN = 256; + char out_buf[MAX_MESSAGE], spn[MAX_SPN]; + +#if 0 // Requires funky windows versions + DWORD len = MAX_SPN; + if (DsMakeSpn("HTTP", server.IPAsString().c_str(), NULL, server.port(), 0, &len, spn) != ERROR_SUCCESS) { + LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) - DsMakeSpn failed"; + return AR_IGNORE; + } +#else + sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str()); +#endif + + SecBuffer out_sec; + out_sec.pvBuffer = out_buf; + out_sec.cbBuffer = sizeof(out_buf); + out_sec.BufferType = SECBUFFER_TOKEN; + + SecBufferDesc out_buf_desc; + out_buf_desc.ulVersion = 0; + out_buf_desc.cBuffers = 1; + out_buf_desc.pBuffers = &out_sec; + + const ULONG NEG_FLAGS_DEFAULT = + //ISC_REQ_ALLOCATE_MEMORY + ISC_REQ_CONFIDENTIALITY + //| ISC_REQ_EXTENDED_ERROR + //| ISC_REQ_INTEGRITY + | ISC_REQ_REPLAY_DETECT + | ISC_REQ_SEQUENCE_DETECT + //| ISC_REQ_STREAM + //| ISC_REQ_USE_SUPPLIED_CREDS + ; + + TimeStamp lifetime; + SECURITY_STATUS ret = S_OK; + ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT; + + bool specify_credentials = !username.empty(); + size_t steps = 0; + + //uint32 now = cricket::Time(); + + NegotiateAuthContext * neg = static_cast(context); + if (neg) { + const size_t max_steps = 10; + if (++neg->steps >= max_steps) { + LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) too many retries"; + return AR_ERROR; + } + steps = neg->steps; + + std::string decoded_challenge = Base64::decode(args[""]); + if (!decoded_challenge.empty()) { + SecBuffer in_sec; + in_sec.pvBuffer = const_cast(decoded_challenge.data()); + in_sec.cbBuffer = static_cast(decoded_challenge.size()); + in_sec.BufferType = SECBUFFER_TOKEN; + + SecBufferDesc in_buf_desc; + in_buf_desc.ulVersion = 0; + in_buf_desc.cBuffers = 1; + in_buf_desc.pBuffers = &in_sec; + + ret = InitializeSecurityContextA(&neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP, &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime); + //LOG(INFO) << "$$$ InitializeSecurityContext @ " << cricket::TimeDiff(cricket::Time(), now); + if (FAILED(ret)) { + LOG(LS_ERROR) << "InitializeSecurityContext returned: " + << ErrorName(ret, SECURITY_ERRORS); + return AR_ERROR; + } + } else if (neg->specified_credentials) { + // Try again with default credentials + specify_credentials = false; + delete context; + context = neg = 0; + } else { + return AR_CREDENTIALS; + } + } + + if (!neg) { + unsigned char userbuf[256], passbuf[256], domainbuf[16]; + SEC_WINNT_AUTH_IDENTITY_A auth_id, * pauth_id = 0; + if (specify_credentials) { + memset(&auth_id, 0, sizeof(auth_id)); + size_t len = password.GetLength()+1; + char * sensitive = new char[len]; + password.CopyTo(sensitive, true); + std::string::size_type pos = username.find('\\'); + if (pos == std::string::npos) { + auth_id.UserLength = static_cast( + _min(sizeof(userbuf) - 1, username.size())); + memcpy(userbuf, username.c_str(), auth_id.UserLength); + userbuf[auth_id.UserLength] = 0; + auth_id.DomainLength = 0; + domainbuf[auth_id.DomainLength] = 0; + auth_id.PasswordLength = static_cast( + _min(sizeof(passbuf) - 1, password.GetLength())); + memcpy(passbuf, sensitive, auth_id.PasswordLength); + passbuf[auth_id.PasswordLength] = 0; + } else { + auth_id.UserLength = static_cast( + _min(sizeof(userbuf) - 1, username.size() - pos - 1)); + memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength); + userbuf[auth_id.UserLength] = 0; + auth_id.DomainLength = static_cast( + _min(sizeof(domainbuf) - 1, pos)); + memcpy(domainbuf, username.c_str(), auth_id.DomainLength); + domainbuf[auth_id.DomainLength] = 0; + auth_id.PasswordLength = static_cast( + _min(sizeof(passbuf) - 1, password.GetLength())); + memcpy(passbuf, sensitive, auth_id.PasswordLength); + passbuf[auth_id.PasswordLength] = 0; + } + memset(sensitive, 0, len); + delete [] sensitive; + auth_id.User = userbuf; + auth_id.Domain = domainbuf; + auth_id.Password = passbuf; + auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; + pauth_id = &auth_id; + LOG(LS_VERBOSE) << "Negotiate protocol: Using specified credentials"; + } else { + LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials"; + } + + CredHandle cred; + ret = AcquireCredentialsHandleA(0, want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A, SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime); + //LOG(INFO) << "$$$ AcquireCredentialsHandle @ " << cricket::TimeDiff(cricket::Time(), now); + if (ret != SEC_E_OK) { + LOG(LS_ERROR) << "AcquireCredentialsHandle error: " + << ErrorName(ret, SECURITY_ERRORS); + return AR_IGNORE; + } + + //CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out; + + CtxtHandle ctx; + ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0, SECURITY_NATIVE_DREP, 0, 0, &ctx, &out_buf_desc, &ret_flags, &lifetime); + //LOG(INFO) << "$$$ InitializeSecurityContext @ " << cricket::TimeDiff(cricket::Time(), now); + if (FAILED(ret)) { + LOG(LS_ERROR) << "InitializeSecurityContext returned: " + << ErrorName(ret, SECURITY_ERRORS); + FreeCredentialsHandle(&cred); + return AR_IGNORE; + } + + assert(!context); + context = neg = new NegotiateAuthContext(auth_method, cred, ctx); + neg->specified_credentials = specify_credentials; + neg->steps = steps; + } + + if ((ret == SEC_I_COMPLETE_NEEDED) || (ret == SEC_I_COMPLETE_AND_CONTINUE)) { + ret = CompleteAuthToken(&neg->ctx, &out_buf_desc); + //LOG(INFO) << "$$$ CompleteAuthToken @ " << cricket::TimeDiff(cricket::Time(), now); + LOG(LS_VERBOSE) << "CompleteAuthToken returned: " + << ErrorName(ret, SECURITY_ERRORS); + if (FAILED(ret)) { + return AR_ERROR; + } + } + + //LOG(INFO) << "$$$ NEGOTIATE took " << cricket::TimeDiff(cricket::Time(), now) << "ms"; + + std::string decoded(out_buf, out_buf + out_sec.cbBuffer); + response = auth_method; + response.append(" "); + response.append(Base64::encode(decoded)); + return AR_RESPONSE; + } +#endif +#endif // WIN32 + + return AR_IGNORE; +} + +inline bool end_of_name(size_t pos, size_t len, const char * data) { + if (pos >= len) + return true; + if (isspace(data[pos])) + return true; + // The reason for this complexity is that some non-compliant auth schemes (like Negotiate) + // use base64 tokens in the challenge instead of name=value. This could confuse us when the + // base64 ends in equal signs. + if ((pos+1 < len) && (data[pos] == '=') && !isspace(data[pos+1]) && (data[pos+1] != '=')) + return true; + return false; +} + +void AsyncHttpsProxySocket::ParseAuth(const char * data, size_t len, std::string& method, std::map& args) { + size_t pos = 0; + while ((pos < len) && isspace(data[pos])) ++pos; + size_t start = pos; + while ((pos < len) && !isspace(data[pos])) ++pos; + method.assign(data + start, data + pos); + while (pos < len) { + while ((pos < len) && isspace(data[pos])) ++pos; + if (pos >= len) + return; + + start = pos; + while (!end_of_name(pos, len, data)) ++pos; + //while ((pos < len) && !isspace(data[pos]) && (data[pos] != '=')) ++pos; + std::string name(data + start, data + pos), value; + + if ((pos < len) && (data[pos] == '=')) { + ++pos; // Skip '=' + // Check if quoted value + if ((pos < len) && (data[pos] == '"')) { + while (++pos < len) { + if (data[pos] == '"') { + ++pos; + break; + } + if ((data[pos] == '\\') && (pos + 1 < len)) + ++pos; + value.append(1, data[pos]); + } + } else { + while ((pos < len) && !isspace(data[pos]) && (data[pos] != ',')) + value.append(1, data[pos++]); + } + } else { + value = name; + name.clear(); + } + + args.insert(std::make_pair(name, value)); + if ((pos < len) && (data[pos] == ',')) ++pos; // Skip ',' + } +} + +AsyncHttpsProxySocket::AsyncHttpsProxySocket(AsyncSocket* socket, const SocketAddress& proxy, + const std::string& username, const buzz::XmppPassword& password) + : BufferedReadAdapter(socket, 1024), proxy_(proxy), user_(username), pass_(password), + state_(PS_ERROR), context_(0) { +} + +AsyncHttpsProxySocket::~AsyncHttpsProxySocket() { + delete context_; +} + +int AsyncHttpsProxySocket::Connect(const SocketAddress& addr) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::Connect(" << proxy_.ToString() << ")"; + dest_ = addr; + if (dest_.port() != 80) { + BufferInput(true); + } + return BufferedReadAdapter::Connect(proxy_); +} + +SocketAddress AsyncHttpsProxySocket::GetRemoteAddress() const { + return dest_; +} + +int AsyncHttpsProxySocket::Close() { + headers_.clear(); + state_ = PS_ERROR; + delete context_; + context_ = 0; + return BufferedReadAdapter::Close(); +} + +void AsyncHttpsProxySocket::OnConnectEvent(AsyncSocket * socket) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnConnectEvent"; + // TODO: Decide whether tunneling or not should be explicitly set, + // or indicated by destination port (as below) + if (dest_.port() == 80) { + state_ = PS_TUNNEL; + BufferedReadAdapter::OnConnectEvent(socket); + return; + } + SendRequest(); +} + +void AsyncHttpsProxySocket::OnCloseEvent(AsyncSocket * socket, int err) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnCloseEvent(" << err << ")"; + if ((state_ == PS_WAIT_CLOSE) && (err == 0)) { + state_ = PS_ERROR; + Connect(dest_); + } else { + BufferedReadAdapter::OnCloseEvent(socket, err); + } +} + +void AsyncHttpsProxySocket::ProcessInput(char * data, size_t& len) { + size_t start = 0; + for (size_t pos = start; (state_ < PS_TUNNEL) && (pos < len); ) { + if (state_ == PS_SKIP_BODY) { + size_t consume = _min(len - pos, content_length_); + pos += consume; + start = pos; + content_length_ -= consume; + if (content_length_ == 0) { + EndResponse(); + } + continue; + } + + if (data[pos++] != '\n') + continue; + + size_t len = pos - start - 1; + if ((len > 0) && (data[start + len - 1] == '\r')) + --len; + + data[start + len] = 0; + ProcessLine(data + start, len); + start = pos; + } + + len -= start; + if (len > 0) { + memmove(data, data + start, len); + } + + if (state_ != PS_TUNNEL) + return; + + bool remainder = (len > 0); + BufferInput(false); + SignalConnectEvent(this); + + // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble + if (remainder) + SignalReadEvent(this); // TODO: signal this?? +} + +void AsyncHttpsProxySocket::SendRequest() { + std::stringstream ss; + ss << "CONNECT " << dest_.ToString() << " HTTP/1.0\r\n"; + ss << "User-Agent: " USERAGENT_STRING "\r\n"; + ss << "Host: " << dest_.IPAsString() << "\r\n"; + ss << "Content-Length: 0\r\n"; + ss << "Proxy-Connection: Keep-Alive\r\n"; + ss << headers_; + ss << "\r\n"; + std::string str = ss.str(); + DirectSend(str.c_str(), str.size()); + state_ = PS_LEADER; + expect_close_ = true; + content_length_ = 0; + headers_.clear(); + + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket >> " << str; +} + +void AsyncHttpsProxySocket::ProcessLine(char * data, size_t len) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket << " << data; + + if (len == 0) { + if (state_ == PS_TUNNEL_HEADERS) { + state_ = PS_TUNNEL; + } else if (state_ == PS_ERROR_HEADERS) { + Error(defer_error_); + return; + } else if (state_ == PS_SKIP_HEADERS) { + if (content_length_) { + state_ = PS_SKIP_BODY; + } else { + EndResponse(); + return; + } + } else { + static bool report = false; + if (!unknown_mechanisms_.empty() && !report) { + report = true; + std::string msg( + "Unable to connect to the Google Talk service due to an incompatibility " + "with your proxy.\r\nPlease help us resolve this issue by submitting the " + "following information to us using our technical issue submission form " + "at:\r\n\r\n" + "http://www.google.com/support/talk/bin/request.py\r\n\r\n" + "We apologize for the inconvenience.\r\n\r\n" + "Information to submit to Google: " + ); + //std::string msg("Please report the following information to foo@bar.com:\r\nUnknown methods: "); + msg.append(unknown_mechanisms_); +#ifdef WIN32 + MessageBoxA(0, msg.c_str(), "Oops!", MB_OK); +#endif +#ifdef POSIX + //TODO: Raise a signal or something so the UI can be separated. + LOG(LS_ERROR) << "Oops!\n\n" << msg; +#endif + } + // Unexpected end of headers + Error(0); + return; + } + } else if (state_ == PS_LEADER) { + uint32 code; + if (sscanf(data, "HTTP/%*lu.%*lu %lu", &code) != 1) { + Error(0); + return; + } + switch (code) { + case 200: + // connection good! + state_ = PS_TUNNEL_HEADERS; + return; +#if defined(HTTP_STATUS_PROXY_AUTH_REQ) && (HTTP_STATUS_PROXY_AUTH_REQ != 407) +#error Wrong code for HTTP_STATUS_PROXY_AUTH_REQ +#endif + case 407: // HTTP_STATUS_PROXY_AUTH_REQ + state_ = PS_AUTHENTICATE; + return; + default: + defer_error_ = 0; + state_ = PS_ERROR_HEADERS; + return; + } + } else if ((state_ == PS_AUTHENTICATE) && (strnicmp(data, "Proxy-Authenticate:", 19) == 0)) { + std::string response, auth_method; + switch (Authenticate(data + 19, len - 19, proxy_, "CONNECT", "/", user_, pass_, context_, response, auth_method)) { + case AR_IGNORE: + LOG(LS_VERBOSE) << "Ignoring Proxy-Authenticate: " << auth_method; + if (!unknown_mechanisms_.empty()) + unknown_mechanisms_.append(", "); + unknown_mechanisms_.append(auth_method); + break; + case AR_RESPONSE: + headers_ = "Proxy-Authorization: "; + headers_.append(response); + headers_.append("\r\n"); + state_ = PS_SKIP_HEADERS; + unknown_mechanisms_.clear(); + break; + case AR_CREDENTIALS: + defer_error_ = EACCES; + state_ = PS_ERROR_HEADERS; + unknown_mechanisms_.clear(); + break; + case AR_ERROR: + defer_error_ = 0; + state_ = PS_ERROR_HEADERS; + unknown_mechanisms_.clear(); + break; + } + } else if (strnicmp(data, "Content-Length:", 15) == 0) { + content_length_ = strtoul(data + 15, 0, 0); + } else if (strnicmp(data, "Proxy-Connection: Keep-Alive", 28) == 0) { + expect_close_ = false; + /* + } else if (strnicmp(data, "Connection: close", 17) == 0) { + expect_close_ = true; + */ + } +} + +void AsyncHttpsProxySocket::EndResponse() { + if (!expect_close_) { + SendRequest(); + return; + } + + // No point in waiting for the server to close... let's close now + // TODO: Refactor out PS_WAIT_CLOSE + state_ = PS_WAIT_CLOSE; + BufferedReadAdapter::Close(); + OnCloseEvent(this, 0); +} + +void AsyncHttpsProxySocket::Error(int error) { + BufferInput(false); + Close(); + SetError(error); + SignalCloseEvent(this, error); +} + +/////////////////////////////////////////////////////////////////////////////// + +AsyncSocksProxySocket::AsyncSocksProxySocket(AsyncSocket* socket, const SocketAddress& proxy, + const std::string& username, const buzz::XmppPassword& password) + : BufferedReadAdapter(socket, 1024), proxy_(proxy), user_(username), pass_(password), + state_(SS_ERROR) { +} + +int AsyncSocksProxySocket::Connect(const SocketAddress& addr) { + dest_ = addr; + BufferInput(true); + return BufferedReadAdapter::Connect(proxy_); +} + +SocketAddress AsyncSocksProxySocket::GetRemoteAddress() const { + return dest_; +} + +void AsyncSocksProxySocket::OnConnectEvent(AsyncSocket * socket) { + SendHello(); +} + +void AsyncSocksProxySocket::ProcessInput(char * data, size_t& len) { + assert(state_ < SS_TUNNEL); + + ByteBuffer response(data, len); + + if (state_ == SS_HELLO) { + uint8 ver, method; + if (!response.ReadUInt8(ver) || + !response.ReadUInt8(method)) + return; + + if (ver != 5) { + Error(0); + return; + } + + if (method == 0) { + SendConnect(); + } else if (method == 2) { + SendAuth(); + } else { + Error(0); + return; + } + } else if (state_ == SS_AUTH) { + uint8 ver, status; + if (!response.ReadUInt8(ver) || + !response.ReadUInt8(status)) + return; + + if ((ver != 1) || (status != 0)) { + Error(EACCES); + return; + } + + SendConnect(); + } else if (state_ == SS_CONNECT) { + uint8 ver, rep, rsv, atyp; + if (!response.ReadUInt8(ver) || + !response.ReadUInt8(rep) || + !response.ReadUInt8(rsv) || + !response.ReadUInt8(atyp)) + return; + + if ((ver != 5) || (rep != 0)) { + Error(0); + return; + } + + uint16 port; + if (atyp == 1) { + uint32 addr; + if (!response.ReadUInt32(addr) || + !response.ReadUInt16(port)) + return; + LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port; + } else if (atyp == 3) { + uint8 len; + std::string addr; + if (!response.ReadUInt8(len) || + !response.ReadString(addr, len) || + !response.ReadUInt16(port)) + return; + LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port; + } else if (atyp == 4) { + std::string addr; + if (!response.ReadString(addr, 16) || + !response.ReadUInt16(port)) + return; + LOG(LS_VERBOSE) << "Bound on :" << port; + } else { + Error(0); + return; + } + + state_ = SS_TUNNEL; + } + + // Consume parsed data + len = response.Length(); + memcpy(data, response.Data(), len); + + if (state_ != SS_TUNNEL) + return; + + bool remainder = (len > 0); + BufferInput(false); + SignalConnectEvent(this); + + // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble + if (remainder) + SignalReadEvent(this); // TODO: signal this?? +} + +void AsyncSocksProxySocket::SendHello() { + ByteBuffer request; + request.WriteUInt8(5); // Socks Version + if (user_.empty()) { + request.WriteUInt8(1); // Authentication Mechanisms + request.WriteUInt8(0); // No authentication + } else { + request.WriteUInt8(2); // Authentication Mechanisms + request.WriteUInt8(0); // No authentication + request.WriteUInt8(2); // Username/Password + } + DirectSend(request.Data(), request.Length()); + state_ = SS_HELLO; +} + +void AsyncSocksProxySocket::SendAuth() { + ByteBuffer request; + request.WriteUInt8(1); // Negotiation Version + request.WriteUInt8(static_cast(user_.size())); + request.WriteString(user_); // Username + request.WriteUInt8(static_cast(pass_.GetLength())); + size_t len = pass_.GetLength() + 1; + char * sensitive = new char[len]; + pass_.CopyTo(sensitive, true); + request.WriteString(sensitive); // Password + memset(sensitive, 0, len); + delete [] sensitive; + DirectSend(request.Data(), request.Length()); + state_ = SS_AUTH; +} + +void AsyncSocksProxySocket::SendConnect() { + ByteBuffer request; + request.WriteUInt8(5); // Socks Version + request.WriteUInt8(1); // CONNECT + request.WriteUInt8(0); // Reserved + if (dest_.IsUnresolved()) { + std::string hostname = dest_.IPAsString(); + request.WriteUInt8(3); // DOMAINNAME + request.WriteUInt8(static_cast(hostname.size())); + request.WriteString(hostname); // Destination Hostname + } else { + request.WriteUInt8(1); // IPV4 + request.WriteUInt32(dest_.ip()); // Destination IP + } + request.WriteUInt16(dest_.port()); // Destination Port + DirectSend(request.Data(), request.Length()); + state_ = SS_CONNECT; +} + +void AsyncSocksProxySocket::Error(int error) { + state_ = SS_ERROR; + BufferInput(false); + Close(); + SetError(EACCES); + SignalCloseEvent(this, error); +} + +/////////////////////////////////////////////////////////////////////////////// + +LoggingAdapter::LoggingAdapter(AsyncSocket* socket, LoggingSeverity level, + const char * label) + : AsyncSocketAdapter(socket), level_(level) +{ + label_.append("["); + label_.append(label); + label_.append("]"); +} + +int +LoggingAdapter::Send(const void *pv, size_t cb) { + int res = AsyncSocketAdapter::Send(pv, cb); + if (res > 0) + LogMultiline(false, static_cast(pv), res); + return res; +} + +int +LoggingAdapter::SendTo(const void *pv, size_t cb, const SocketAddress& addr) { + int res = AsyncSocketAdapter::SendTo(pv, cb, addr); + if (res > 0) + LogMultiline(false, static_cast(pv), res); + return res; +} + +int +LoggingAdapter::Recv(void *pv, size_t cb) { + int res = AsyncSocketAdapter::Recv(pv, cb); + if (res > 0) + LogMultiline(true, static_cast(pv), res); + return res; +} + +int +LoggingAdapter::RecvFrom(void *pv, size_t cb, SocketAddress *paddr) { + int res = AsyncSocketAdapter::RecvFrom(pv, cb, paddr); + if (res > 0) + LogMultiline(true, static_cast(pv), res); + return res; +} + +void +LoggingAdapter::OnConnectEvent(AsyncSocket * socket) { + LOG(level_) << label_ << " Connected"; + AsyncSocketAdapter::OnConnectEvent(socket); +} + +void +LoggingAdapter::OnCloseEvent(AsyncSocket * socket, int err) { + LOG(level_) << label_ << " Closed with error: " << err; + AsyncSocketAdapter::OnCloseEvent(socket, err); +} + +void +LoggingAdapter::LogMultiline(bool input, const char * data, size_t len) { + const char * direction = (input ? " << " : " >> "); + std::string str(data, len); + while (!str.empty()) { + std::string::size_type pos = str.find('\n'); + std::string substr = str; + if (pos == std::string::npos) { + substr = str; + str.clear(); + } else if ((pos > 0) && (str[pos-1] == '\r')) { + substr = str.substr(0, pos - 1); + str = str.substr(pos + 1); + } else { + substr = str.substr(0, pos); + str = str.substr(pos + 1); + } + + // Filter out any private data + std::string::size_type pos_private = substr.find("Email"); + if (pos_private == std::string::npos) { + pos_private = substr.find("Passwd"); + } + if (pos_private == std::string::npos) { + LOG(level_) << label_ << direction << substr; + } else { + LOG(level_) << label_ << direction << "## TEXT REMOVED ##"; + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.h new file mode 100644 index 00000000..1c65aa79 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.h @@ -0,0 +1,181 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __SOCKETADAPTERS_H__ +#define __SOCKETADAPTERS_H__ + +#include + +#include "talk/base/asyncsocket.h" +#include "talk/base/logging.h" +#include "talk/xmpp/xmpppassword.h" // TODO: move xmpppassword to base + +namespace cricket { + +/////////////////////////////////////////////////////////////////////////////// + +class BufferedReadAdapter : public AsyncSocketAdapter { +public: + BufferedReadAdapter(AsyncSocket* socket, size_t buffer_size); + virtual ~BufferedReadAdapter(); + + virtual int Send(const void *pv, size_t cb); + virtual int Recv(void *pv, size_t cb); + +protected: + int DirectSend(const void *pv, size_t cb) { return AsyncSocketAdapter::Send(pv, cb); } + + void BufferInput(bool on = true); + virtual void ProcessInput(char * data, size_t& len) = 0; + + virtual void OnReadEvent(AsyncSocket * socket); + +private: + char * buffer_; + size_t buffer_size_, data_len_; + bool buffering_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class AsyncSSLSocket : public BufferedReadAdapter { +public: + AsyncSSLSocket(AsyncSocket* socket); + + virtual int Connect(const SocketAddress& addr); + +protected: + virtual void OnConnectEvent(AsyncSocket * socket); + virtual void ProcessInput(char * data, size_t& len); +}; + +/////////////////////////////////////////////////////////////////////////////// + +class AsyncHttpsProxySocket : public BufferedReadAdapter { +public: + AsyncHttpsProxySocket(AsyncSocket* socket, const SocketAddress& proxy, + const std::string& username, const buzz::XmppPassword& password); + virtual ~AsyncHttpsProxySocket(); + + virtual int Connect(const SocketAddress& addr); + virtual SocketAddress GetRemoteAddress() const; + virtual int Close(); + + struct AuthContext { + std::string auth_method; + AuthContext(const std::string& auth) : auth_method(auth) { } + virtual ~AuthContext() { } + }; + + // 'context' is used by this function to record information between calls. + // Start by passing a null pointer, then pass the same pointer each additional + // call. When the authentication attempt is finished, delete the context. + enum AuthResult { AR_RESPONSE, AR_IGNORE, AR_CREDENTIALS, AR_ERROR }; + static AuthResult Authenticate(const char * challenge, size_t len, + const SocketAddress& server, + const std::string& method, const std::string& uri, + const std::string& username, const buzz::XmppPassword& password, + AuthContext *& context, std::string& response, std::string& auth_method); + +protected: + virtual void OnConnectEvent(AsyncSocket * socket); + virtual void OnCloseEvent(AsyncSocket * socket, int err); + virtual void ProcessInput(char * data, size_t& len); + + void SendRequest(); + void ProcessLine(char * data, size_t len); + void EndResponse(); + void Error(int error); + + static void ParseAuth(const char * data, size_t len, std::string& method, std::map& args); + +private: + SocketAddress proxy_, dest_; + std::string user_, headers_; + buzz::XmppPassword pass_; + size_t content_length_; + int defer_error_; + bool expect_close_; + enum ProxyState { PS_LEADER, PS_AUTHENTICATE, PS_SKIP_HEADERS, PS_ERROR_HEADERS, PS_TUNNEL_HEADERS, PS_SKIP_BODY, PS_TUNNEL, PS_WAIT_CLOSE, PS_ERROR } state_; + AuthContext * context_; + std::string unknown_mechanisms_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class AsyncSocksProxySocket : public BufferedReadAdapter { +public: + AsyncSocksProxySocket(AsyncSocket* socket, const SocketAddress& proxy, + const std::string& username, const buzz::XmppPassword& password); + + virtual int Connect(const SocketAddress& addr); + virtual SocketAddress GetRemoteAddress() const; + +protected: + virtual void OnConnectEvent(AsyncSocket * socket); + virtual void ProcessInput(char * data, size_t& len); + + void SendHello(); + void SendConnect(); + void SendAuth(); + void Error(int error); + +private: + SocketAddress proxy_, dest_; + std::string user_; + buzz::XmppPassword pass_; + enum SocksState { SS_HELLO, SS_AUTH, SS_CONNECT, SS_TUNNEL, SS_ERROR } state_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class LoggingAdapter : public AsyncSocketAdapter { +public: + LoggingAdapter(AsyncSocket* socket, LoggingSeverity level, + const char * label); + + virtual int Send(const void *pv, size_t cb); + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr); + virtual int Recv(void *pv, size_t cb); + virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr); + +protected: + virtual void OnConnectEvent(AsyncSocket * socket); + virtual void OnCloseEvent(AsyncSocket * socket, int err); + +private: + void LogMultiline(bool input, const char * data, size_t len); + + LoggingSeverity level_; + std::string label_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace cricket + +#endif // __SOCKETADAPTERS_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.cc new file mode 100644 index 00000000..f0228fbd --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.cc @@ -0,0 +1,267 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/socketaddress.h" +#include "talk/base/byteorder.h" +#include "talk/base/logging.h" +#include +#include +#include + +#ifdef WIN32 +#undef SetPort +int inet_aton(const char * cp, struct in_addr * inp) { + inp->s_addr = inet_addr(cp); + return (inp->s_addr == INADDR_NONE) ? 0 : 1; +} +#endif // WIN32 + +#ifdef POSIX +#include +#include +#include +#include +#include +#endif + +#ifdef _DEBUG +#define DISABLE_DNS 0 +#else // !_DEBUG +#define DISABLE_DNS 0 +#endif // !_DEBUG + +namespace cricket { + +SocketAddress::SocketAddress() { + Zero(); +} + +SocketAddress::SocketAddress(const std::string& hostname, int port, bool use_dns) { + Zero(); + SetIP(hostname, use_dns); + SetPort(port); +} + +SocketAddress::SocketAddress(uint32 ip, int port) { + Zero(); + SetIP(ip); + SetPort(port); +} + +SocketAddress::SocketAddress(const SocketAddress& addr) { + Zero(); + this->operator=(addr); +} + +void SocketAddress::Zero() { + ip_ = 0; + port_ = 0; +} + +SocketAddress& SocketAddress::operator =(const SocketAddress& addr) { + hostname_ = addr.hostname_; + ip_ = addr.ip_; + port_ = addr.port_; + return *this; +} + +void SocketAddress::SetIP(uint32 ip) { + hostname_.clear(); + ip_ = ip; +} + +bool SocketAddress::SetIP(const std::string& hostname, bool use_dns) { + hostname_ = hostname; + ip_ = 0; + return Resolve(true, use_dns); +} + +void SocketAddress::SetResolvedIP(uint32 ip) { + ip_ = ip; +} + +void SocketAddress::SetPort(int port) { + assert((0 <= port) && (port < 65536)); + port_ = port; +} + +uint32 SocketAddress::ip() const { + return ip_; +} + +uint16 SocketAddress::port() const { + return port_; +} + +std::string SocketAddress::IPAsString() const { + if (!hostname_.empty()) + return hostname_; + return IPToString(ip_); +} + +std::string SocketAddress::PortAsString() const { + std::ostringstream ost; + ost << port_; + return ost.str(); +} + +std::string SocketAddress::ToString() const { + std::ostringstream ost; + ost << IPAsString(); + ost << ":"; + ost << port(); + return ost.str(); +} + +bool SocketAddress::IsAny() const { + return (ip_ == 0); +} + +bool SocketAddress::IsLocalIP() const { + return (ip_ >> 24) == 127; +} + +bool SocketAddress::IsPrivateIP() const { + return ((ip_ >> 24) == 127) || + ((ip_ >> 24) == 10) || + ((ip_ >> 20) == ((172 << 4) | 1)) || + ((ip_ >> 16) == ((192 << 8) | 168)); +} + +bool SocketAddress::IsUnresolved() const { + return IsAny() && !hostname_.empty(); +} + +bool SocketAddress::Resolve(bool force, bool use_dns) { + if (hostname_.empty()) { + // nothing to resolve + } else if (!force && !IsAny()) { + // already resolved + } else if (uint32 ip = StringToIP(hostname_, use_dns)) { + ip_ = ip; + } else { + return false; + } + return true; +} + +bool SocketAddress::operator ==(const SocketAddress& addr) const { + return EqualIPs(addr) && EqualPorts(addr); +} + +bool SocketAddress::operator <(const SocketAddress& addr) const { + if (ip_ < addr.ip_) + return true; + else if (addr.ip_ < ip_) + return false; + + // We only check hostnames if both IPs are zero. This matches EqualIPs() + if (addr.ip_ == 0) { + if (hostname_ < addr.hostname_) + return true; + else if (addr.hostname_ < hostname_) + return false; + } + + return port_ < addr.port_; +} + +bool SocketAddress::EqualIPs(const SocketAddress& addr) const { + return (ip_ == addr.ip_) && ((ip_ != 0) || (hostname_ == addr.hostname_)); +} + +bool SocketAddress::EqualPorts(const SocketAddress& addr) const { + return (port_ == addr.port_); +} + +size_t SocketAddress::Hash() const { + size_t h = 0; + h ^= ip_; + h ^= port_ | (port_ << 16); + return h; +} + +size_t SocketAddress::Size_() const { + return sizeof(ip_) + sizeof(port_); +} + +void SocketAddress::Write_(char* buf, int len) const { + // TODO: Depending on how this is used, we may want/need to write hostname + assert((size_t)len >= Size_()); + reinterpret_cast(buf)[0] = ip_; + buf += sizeof(ip_); + reinterpret_cast(buf)[0] = port_; +} + +void SocketAddress::Read_(const char* buf, int len) { + assert((size_t)len >= Size_()); + ip_ = reinterpret_cast(buf)[0]; + buf += sizeof(ip_); + port_ = reinterpret_cast(buf)[0]; +} + +std::string SocketAddress::IPToString(uint32 ip) { + std::ostringstream ost; + ost << ((ip >> 24) & 0xff); + ost << '.'; + ost << ((ip >> 16) & 0xff); + ost << '.'; + ost << ((ip >> 8) & 0xff); + ost << '.'; + ost << ((ip >> 0) & 0xff); + return ost.str(); +} + +uint32 SocketAddress::StringToIP(const std::string& hostname, bool use_dns) { + uint32 ip = 0; + in_addr addr; + if (inet_aton(hostname.c_str(), &addr) != 0) { + ip = NetworkToHost32(addr.s_addr); + } else if (use_dns) { + // Note: this is here so we can spot spurious DNS resolutions for a while + LOG(INFO) << "=== DNS RESOLUTION (" << hostname << ") ==="; +#if DISABLE_DNS + LOG(WARNING) << "*** DNS DISABLED ***"; +#if WIN32 + WSASetLastError(WSAHOST_NOT_FOUND); +#endif // WIN32 +#endif // DISABLE_DNS + if (hostent * pHost = gethostbyname(hostname.c_str())) { + ip = NetworkToHost32(*reinterpret_cast(pHost->h_addr_list[0])); + } else { +#if WIN32 + LOG(LS_ERROR) << "gethostbyname error: " << WSAGetLastError(); +#else + LOG(LS_ERROR) << "gethostbyname error: " << strerror(h_errno); +#endif + } + LOG(INFO) << hostname << " resolved to " << IPToString(ip); + } + return ip; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.h new file mode 100644 index 00000000..b8a165d3 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.h @@ -0,0 +1,154 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __SOCKETADDRESS_H__ +#define __SOCKETADDRESS_H__ + +#include "talk/base/basictypes.h" +#include +#undef SetPort + +namespace cricket { + +// Records an IP address and port, which are 32 and 16 bit integers, +// respectively, both in host byte-order. +class SocketAddress { +public: + // Creates a missing / unknown address. + SocketAddress(); + + // Creates the address with the given host and port. If use_dns is true, + // the hostname will be immediately resolved to an IP (which may block for + // several seconds if DNS is not available). Alternately, set use_dns to + // false, and then call Resolve() to complete resolution later, or use + // SetResolvedIP to set the IP explictly. + SocketAddress(const std::string& hostname, int port = 0, bool use_dns = true); + + // Creates the address with the given IP and port. + SocketAddress(uint32 ip, int port); + + // Creates a copy of the given address. + SocketAddress(const SocketAddress& addr); + + // Replaces our address with the given one. + SocketAddress& operator =(const SocketAddress& addr); + + // Changes the IP of this address to the given one, and clears the hostname. + void SetIP(uint32 ip); + + // Changes the hostname of this address to the given one. + // Calls Resolve and returns the result. + bool SetIP(const std::string& hostname, bool use_dns = true); + + // Sets the IP address while retaining the hostname. Useful for bypassing + // DNS for a pre-resolved IP. + void SetResolvedIP(uint32 ip); + + // Changes the port of this address to the given one. + void SetPort(int port); + + // Returns the IP address. + uint32 ip() const; + + // Returns the port part of this address. + uint16 port() const; + + // Returns the IP address in dotted form. + std::string IPAsString() const; + + // Returns the port as a string + std::string PortAsString() const; + + // Returns a display version of the IP/port. + std::string ToString() const; + + // Determines whether this represents a missing / any address. + bool IsAny() const; + + // Synomym for missing / any. + bool IsNil() const { return IsAny(); } + + // Determines whether the IP address refers to the local host, i.e. within + // the range 127.0.0.0/8. + bool IsLocalIP() const; + + // Determines whether the IP address is in one of the private ranges: + // 127.0.0.0/8 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12. + bool IsPrivateIP() const; + + // Determines whether the hostname has been resolved to an IP + bool IsUnresolved() const; + + // Attempt to resolve a hostname to IP address. + // Returns false if resolution is required but failed. + // 'force' will cause re-resolution of hostname. + // + bool Resolve(bool force = false, bool use_dns = true); + + // Determines whether this address is identical to the given one. + bool operator ==(const SocketAddress& addr) const; + + // Compares based on IP and then port. + bool operator <(const SocketAddress& addr) const; + + // Determines whether this address has the same IP as the one given. + bool EqualIPs(const SocketAddress& addr) const; + + // Deteremines whether this address has the same port as the one given. + bool EqualPorts(const SocketAddress& addr) const; + + // Hashes this address into a small number. + size_t Hash() const; + + // Returns the size of this address when written. + size_t Size_() const; + + // Writes this address into the given buffer. + void Write_(char* buf, int len) const; + + // Reads this address from the given buffer. + void Read_(const char* buf, int len); + + // Converts the IP address given in compact form into dotted form. + static std::string IPToString(uint32 ip); + + // Converts the IP address given in dotted form into compact form. + // Without 'use_dns', only dotted names (A.B.C.D) are resolved. + static uint32 StringToIP(const std::string& str, bool use_dns = true); + +private: + std::string hostname_; + uint32 ip_; + uint16 port_; + + // Initializes the address to missing / any. + void Zero(); +}; + +} // namespace cricket + +#endif // __SOCKETADDRESS_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.cc new file mode 100644 index 00000000..2166be09 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.cc @@ -0,0 +1,58 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/socketaddresspair.h" + +namespace cricket { + +SocketAddressPair::SocketAddressPair( + const SocketAddress& src, const SocketAddress& dest) + : src_(src), dest_(dest) { +} + + +bool SocketAddressPair::operator ==(const SocketAddressPair& p) const { + return (src_ == p.src_) && (dest_ == p.dest_); +} + +bool SocketAddressPair::operator <(const SocketAddressPair& p) const { + if (src_ < p.src_) + return true; + if (p.src_ < src_) + return false; + if (dest_ < p.dest_) + return true; + if (p.dest_ < dest_) + return false; + return false; +} + +size_t SocketAddressPair::Hash() const { + return src_.Hash() ^ dest_.Hash(); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.h new file mode 100644 index 00000000..098bafdb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.h @@ -0,0 +1,58 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __SOCKETADDRESSPAIR_H__ +#define __SOCKETADDRESSPAIR_H__ + +#include "talk/base/socketaddress.h" + +namespace cricket { + +// Records a pair (source,destination) of socket addresses. The two addresses +// identify a connection between two machines. (For UDP, this "connection" is +// not maintained explicitly in a socket.) +class SocketAddressPair { +public: + SocketAddressPair() {} + SocketAddressPair(const SocketAddress& srs, const SocketAddress& dest); + + const SocketAddress& source() const { return src_; } + const SocketAddress& destination() const { return dest_; } + + bool operator ==(const SocketAddressPair& r) const; + bool operator <(const SocketAddressPair& r) const; + + size_t Hash() const; + +private: + SocketAddress src_; + SocketAddress dest_; +}; + +} // namespace cricket + +#endif // __SOCKETADDRESSPAIR_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketfactory.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketfactory.h new file mode 100644 index 00000000..67386160 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketfactory.h @@ -0,0 +1,50 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __SOCKETFACTORY_H__ +#define __SOCKETFACTORY_H__ + +#include "talk/base/socket.h" +#include "talk/base/asyncsocket.h" + +namespace cricket { + +class SocketFactory { +public: + + // Returns a new socket for blocking communication. The type can be + // SOCK_DGRAM and SOCK_STREAM. + virtual Socket* CreateSocket(int type) = 0; + + // Returns a new socket for nonblocking communication. The type can be + // SOCK_DGRAM and SOCK_STREAM. + virtual AsyncSocket* CreateAsyncSocket(int type) = 0; +}; + +} // namespace cricket + +#endif // __SOCKETFACTORY_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketserver.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketserver.h new file mode 100644 index 00000000..d0e7a22a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketserver.h @@ -0,0 +1,53 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __SOCKETSERVER_H__ +#define __SOCKETSERVER_H__ + +#include "talk/base/socketfactory.h" + +namespace cricket { + +// Provides the ability to wait for activity on a set of sockets. The Thread +// class provides a nice wrapper on a socket server. +// +// The server is also a socket factory. The sockets it creates will be +// notified of asynchronous I/O from this server's Wait method. +class SocketServer : public SocketFactory { +public: + + // Performs I/O or sleeps for the given number of milliseconds. + // If process_io is false, just sleeps until WakeUp. + virtual bool Wait(int cms, bool process_io) = 0; + + // Causes the current wait (if one is in progress) to wake up. + virtual void WakeUp() = 0; +}; + +} // namespace cricket + +#endif // __SOCKETSERVER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/stl_decl.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/stl_decl.h new file mode 100644 index 00000000..9c2506f1 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/stl_decl.h @@ -0,0 +1,85 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _STL_DECL_H +#define _STL_DECL_H + +#if defined(_MSC_VER) && _MSC_VER <= 1200 // 1200 == VC++ 6.0 +#pragma warning(disable:4786) +#endif + +#include + +namespace std { + template struct hash; + template struct equal_to; + template struct less; + template class allocator; + template class map; + template class vector; + template class list; + template class slist; + template class deque; + template class stack; + template class queue; + template class priority_queue; + template struct pair; + template class set; +} + +///////////////////////////////////////////////////////////////////////////// +// Workaround declaration problem with defaults +///////////////////////////////////////////////////////////////////////////// + +#if defined(_MSC_VER) && _MSC_VER <= 1200 // 1200 == VC++ 6.0 + +#define STD_MAP(T1, T2) \ + std::map, std::allocator > + +#define STD_VECTOR(T1) \ + std::vector > + +#define STD_SET(T1) \ + std::set, std::allocator > + +#else + +#define STD_MAP(T1, T2) \ + std::map, std::allocator > > + +#define STD_VECTOR(T1) \ + std::vector > + +#define STD_SET(T1) \ + std::set, std::allocator > + +#endif + + +#endif // _STL_DECL_H diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/stringutils.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/stringutils.h new file mode 100644 index 00000000..a23132dd --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/stringutils.h @@ -0,0 +1,266 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __STRINGUTILS_H__ +#define __STRINGUTILS_H__ + +#include +#include +#include +#ifdef WIN32 +#include +#endif // WIN32 + +#include + +/////////////////////////////////////////////////////////////////////////////// +// Rename a bunch of common string functions so they are consistent across +// platforms and between char and wchar_t variants. +// Here is the full list of functions that are unified: +// strlen, strcmp, stricmp, strncmp, strnicmp +// strchr, vsnprintf, strtoul, tolowercase +// tolowercase is like tolower, but not compatible with end-of-file value +// Note that the wchar_t versions are not available on Linux +/////////////////////////////////////////////////////////////////////////////// + +inline char tolowercase(char c) { + return static_cast(tolower(c)); +} + +#ifdef WIN32 + +inline size_t strlen(const wchar_t* s) { + return wcslen(s); +} +inline int strcmp(const wchar_t* s1, const wchar_t* s2) { + return wcscmp(s1, s2); +} +inline int stricmp(const wchar_t* s1, const wchar_t* s2) { + return wcsicmp(s1, s2); +} +inline int strncmp(const wchar_t* s1, const wchar_t* s2, size_t n) { + return wcsncmp(s1, s2, n); +} +inline int strnicmp(const wchar_t* s1, const wchar_t* s2, size_t n) { + return wcsnicmp(s1, s2, n); +} +inline const wchar_t* strchr(const wchar_t* s, wchar_t c) { + return wcschr(s, c); +} +inline int vsnprintf(char* buf, size_t n, const char* fmt, va_list args) { + return _vsnprintf(buf, n, fmt, args); +} +inline int vsnprintf(wchar_t* buf, size_t n, const wchar_t* fmt, va_list args) { + return _vsnwprintf(buf, n, fmt, args); +} +inline unsigned long strtoul(const wchar_t* snum, wchar_t** end, int base) { + return wcstoul(snum, end, base); +} +inline wchar_t tolowercase(wchar_t c) { + return static_cast(towlower(c)); +} + +#endif // WIN32 + +#ifdef POSIX + +inline int stricmp(const char* s1, const char* s2) { + return strcasecmp(s1, s2); +} +inline int strnicmp(const char* s1, const char* s2, size_t n) { + return strncasecmp(s1, s2, n); +} + +#endif // POSIX + +/////////////////////////////////////////////////////////////////////////////// +// Traits simplifies porting string functions to be CTYPE-agnostic +/////////////////////////////////////////////////////////////////////////////// + +namespace cricket { + +const size_t SIZE_UNKNOWN = static_cast(-1); + +template +struct Traits { + // STL string type + //typedef XXX string; + // Null-terminated string + //inline static const CTYPE* empty_str(); +}; + +/////////////////////////////////////////////////////////////////////////////// +// String utilities which work with char or wchar_t +/////////////////////////////////////////////////////////////////////////////// + +template +inline const CTYPE* nonnull(const CTYPE* str, const CTYPE* def_str = NULL) { + return str ? str : (def_str ? def_str : Traits::empty_str()); +} + +template +const CTYPE* strchr(const CTYPE* str, const CTYPE* chs) { + for (size_t i=0; str[i]; ++i) { + for (size_t j=0; chs[j]; ++j) { + if (str[i] == chs[j]) { + return str + i; + } + } + } + return 0; +} + +template +const CTYPE* strchrn(const CTYPE* str, size_t slen, CTYPE ch) { + for (size_t i=0; i +size_t strlenn(const CTYPE* buffer, size_t buflen) { + size_t bufpos = 0; + while (buffer[bufpos] && (bufpos < buflen)) { + ++bufpos; + } + return bufpos; +} + +template +size_t strcpyn(CTYPE* buffer, size_t buflen, + const CTYPE* source, size_t srclen = SIZE_UNKNOWN) { + if (buflen <= 0) + return 0; + + if (srclen == SIZE_UNKNOWN) { + srclen = strlenn(source, buflen - 1); + } else if (srclen >= buflen) { + srclen = buflen - 1; + } + memcpy(buffer, source, srclen * sizeof(CTYPE)); + buffer[srclen] = 0; + return srclen; +} + +// Safe versions of snprintf and vsnprintf that always null-terminate + +template +size_t sprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, ...) { + va_list args; + va_start(args, format); + size_t len = vsprintfn(buffer, buflen, format, args); + va_end(args); + return len; +} + +template +size_t vsprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, + va_list args) { + int len = vsnprintf(buffer, buflen, format, args); + if ((len < 0) || (static_cast(len) >= buflen)) { + len = static_cast(buflen - 1); + buffer[len] = 0; + } + return len; +} + +/////////////////////////////////////////////////////////////////////////////// +// Allow safe comparing and copying ascii (not UTF-8) with both wide and +// non-wide character strings. +/////////////////////////////////////////////////////////////////////////////// + +inline int asccmp(const char* s1, const char* s2) { + return strcmp(s1, s2); +} +inline int ascicmp(const char* s1, const char* s2) { + return stricmp(s1, s2); +} +inline int ascncmp(const char* s1, const char* s2, size_t n) { + return strncmp(s1, s2, n); +} +inline int ascnicmp(const char* s1, const char* s2, size_t n) { + return strnicmp(s1, s2, n); +} +inline size_t asccpyn(char* buffer, size_t buflen, + const char* source, size_t srclen = SIZE_UNKNOWN) { + return strcpyn(buffer, buflen, source, srclen); +} + +#ifdef WIN32 + +typedef wchar_t(*CharacterTransformation)(wchar_t); +inline wchar_t identity(wchar_t c) { return c; } +int ascii_string_compare(const wchar_t* s1, const char* s2, size_t n, + CharacterTransformation transformation); + +inline int asccmp(const wchar_t* s1, const char* s2) { + return ascii_string_compare(s1, s2, static_cast(-1), identity); +} +inline int ascicmp(const wchar_t* s1, const char* s2) { + return ascii_string_compare(s1, s2, static_cast(-1), tolowercase); +} +inline int ascncmp(const wchar_t* s1, const char* s2, size_t n) { + return ascii_string_compare(s1, s2, n, identity); +} +inline int ascnicmp(const wchar_t* s1, const char* s2, size_t n) { + return ascii_string_compare(s1, s2, n, tolowercase); +} +size_t asccpyn(wchar_t* buffer, size_t buflen, + const char* source, size_t srclen = SIZE_UNKNOWN); + +#endif // WIN32 + +/////////////////////////////////////////////////////////////////////////////// +// Traits specializations +/////////////////////////////////////////////////////////////////////////////// + +template<> +struct Traits { + typedef std::string string; + inline static const char* empty_str() { return ""; } +}; + +/////////////////////////////////////////////////////////////////////////////// +// Traits specializations (Windows only, currently) +/////////////////////////////////////////////////////////////////////////////// + +#ifdef WIN32 + +template<> +struct Traits { + typedef std::wstring string; + inline static const wchar_t* Traits::empty_str() { return L""; } +}; + +#endif // WIN32 + +} // namespace cricket + +#endif // __STRINGUTILS_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/task.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/task.cc new file mode 100644 index 00000000..a5a94941 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/task.cc @@ -0,0 +1,238 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "task.h" +#include "taskrunner.h" + +#include + +namespace buzz { + +Task::Task(Task * parent) : + state_(STATE_INIT), + parent_(parent), + blocked_(false), + done_(false), + aborted_(false), + busy_(false), + error_(false), + child_error_(false), + start_time_(0) { + runner_ = ((parent == NULL) ? (TaskRunner *)this : parent->GetRunner()); + if (parent_ != NULL) { + parent_->AddChild(this); + } +} + +unsigned long long +Task::CurrentTime() { + return runner_->CurrentTime(); +} + +unsigned long long +Task::ElapsedTime() { + return CurrentTime() - start_time_; +} + +void +Task::Start() { + if (state_ != STATE_INIT) + return; + GetRunner()->StartTask(this); + start_time_ = CurrentTime(); +} + +void +Task::Step() { + if (done_) { +#ifdef DEBUG + // we do not know how !blocked_ happens when done_ - should be impossible. + // But it causes problems, so in retail build, we force blocked_, and + // under debug we assert. + assert(blocked_); +#else + blocked_ = true; +#endif + return; + } + + // Async Error() was called + if (error_) { + done_ = true; + state_ = STATE_ERROR; + blocked_ = true; +// obsolete - an errored task is not considered done now +// SignalDone(); + Stop(); + return; + } + + busy_ = true; + int new_state = Process(state_); + busy_ = false; + + if (aborted_) { + Abort(true); // no need to wake because we're awake + return; + } + + if (new_state == STATE_BLOCKED) { + blocked_ = true; + } + else { + state_ = new_state; + blocked_ = false; + } + + if (new_state == STATE_DONE) { + done_ = true; + } + else if (new_state == STATE_ERROR) { + done_ = true; + error_ = true; + } + + if (done_) { +// obsolete - call this yourself +// SignalDone(); + Stop(); + blocked_ = true; + } +} + +void +Task::Abort(bool nowake) { + if (aborted_ || done_) + return; + aborted_ = true; + if (!busy_) { + done_ = true; + blocked_ = true; + error_ = true; + Stop(); + if (!nowake) + Wake(); // to self-delete + } +} + +void +Task::Wake() { + if (done_) + return; + if (blocked_) { + blocked_ = false; + GetRunner()->WakeTasks(); + } +} + +void +Task::Error() { + if (error_ || done_) + return; + error_ = true; + Wake(); +} + +std::string +Task::GetStateName(int state) const { + static const std::string STR_BLOCKED("BLOCKED"); + static const std::string STR_INIT("INIT"); + static const std::string STR_START("START"); + static const std::string STR_DONE("DONE"); + static const std::string STR_ERROR("ERROR"); + static const std::string STR_RESPONSE("RESPONSE"); + static const std::string STR_HUH("??"); + switch (state) { + case STATE_BLOCKED: return STR_BLOCKED; + case STATE_INIT: return STR_INIT; + case STATE_START: return STR_START; + case STATE_DONE: return STR_DONE; + case STATE_ERROR: return STR_ERROR; + case STATE_RESPONSE: return STR_RESPONSE; + } + return STR_HUH; +} + +int Task::Process(int state) { + switch (state) { + case STATE_INIT: + return STATE_START; + case STATE_START: + return ProcessStart(); + case STATE_RESPONSE: + return ProcessResponse(); + case STATE_DONE: + case STATE_ERROR: + return STATE_BLOCKED; + } + return STATE_ERROR; +} + +void +Task::AddChild(Task * child) { + children_.insert(child); +} + +bool +Task::AllChildrenDone() { + for (ChildSet::iterator it = children_.begin(); it != children_.end(); ++it) { + if (!(*it)->IsDone()) + return false; + } + return true; +} + +bool +Task::AnyChildError() { + return child_error_; +} + +void +Task::AbortAllChildren() { + if (children_.size() > 0) { + ChildSet copy = children_; + for (ChildSet::iterator it = copy.begin(); it != copy.end(); ++it) { + (*it)->Abort(true); // Note we do not wake + } + } +} + +void +Task::Stop() { + AbortAllChildren(); // No need to wake because we're either awake or in abort + parent_->OnChildStopped(this); +} + +void +Task::OnChildStopped(Task * child) { + if (child->HasError()) + child_error_ = true; + children_.erase(child); +} + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/task.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/task.h new file mode 100644 index 00000000..5a486198 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/task.h @@ -0,0 +1,186 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _TASK_H_ +#define _TASK_H_ + +#include +#include + +#include "talk/base/sigslot.h" + +///////////////////////////////////////////////////////////////////// +// +// TASK +// +///////////////////////////////////////////////////////////////////// +// +// Task is a state machine infrastructure. States are pushed forward by +// pushing forwards a TaskRunner that holds on to all Tasks. The purpose +// of Task is threefold: +// +// (1) It manages ongoing work on the UI thread. Multitasking without +// threads, keeping it easy, keeping it real. :-) It does this by +// organizing a set of states for each task. When you return from your +// Process*() function, you return an integer for the next state. You do +// not go onto the next state yourself. Every time you enter a state, +// you check to see if you can do anything yet. If not, you return +// STATE_BLOCKED. If you _could_ do anything, do not return +// STATE_BLOCKED - even if you end up in the same state, return +// STATE_mysamestate. When you are done, return STATE_DONE and then the +// task will self-delete sometimea afterwards. +// +// (2) It helps you avoid all those reentrancy problems when you chain +// too many triggers on one thread. Basically if you want to tell a task +// to process something for you, you feed your task some information and +// then you Wake() it. Don't tell it to process it right away. If it +// might be working on something as you send it infomration, you may want +// to have a queue in the task. +// +// (3) Finally it helps manage parent tasks and children. If a parent +// task gets aborted, all the children tasks are too. The nice thing +// about this, for example, is if you have one parent task that +// represents, say, and Xmpp connection, then you can spawn a whole bunch +// of infinite lifetime child tasks and now worry about cleaning them up. +// When the parent task goes to STATE_DONE, the task engine will make +// sure all those children are aborted and get deleted. +// +// Notice that Task has a few built-in states, e.g., +// +// STATE_INIT - the task isn't running yet +// STATE_START - the task is in its first state +// STATE_RESPONSE - the task is in its second state +// STATE_DONE - the task is done +// +// STATE_ERROR - indicates an error - we should audit the error code in +// light of any usage of it to see if it should be improved. When I +// first put down the task stuff I didn't have a good sense of what was +// needed for Abort and Error, and now the subclasses of Task will ground +// the design in a stronger way. +// +// STATE_NEXT - the first undefined state number. (like WM_USER) - you +// can start defining more task states there. +// +// When you define more task states, just override Process(int state) and +// add your own switch statement. If you want to delegate to +// Task::Process, you can effectively delegate to its switch statement. +// No fancy method pointers or such - this is all just pretty low tech, +// easy to debug, and fast. +// + +namespace buzz { + +class TaskRunner; + +// A task executes a sequence of steps + +class Task; +class RootTask; + +class Task { +public: + Task(Task * parent); + virtual ~Task() {} + + void Start(); + void Step(); + int GetState() const { return state_; } + bool HasError() const { return (GetState() == STATE_ERROR); } + bool Blocked() const { return blocked_; } + bool IsDone() const { return done_; } + unsigned long long ElapsedTime(); + virtual void Poll() {} + + Task * GetParent() { return parent_; } + TaskRunner * GetRunner() { return runner_; } + virtual Task * GetParent(int code) { return parent_->GetParent(code); } + + // Called from outside to stop task without any more callbacks + void Abort(bool nowake = false); + + // For managing children + bool AllChildrenDone(); + bool AnyChildError(); + + +protected: + + enum { + STATE_BLOCKED = -1, + STATE_INIT = 0, + STATE_START = 1, + STATE_DONE = 2, + STATE_ERROR = 3, + STATE_RESPONSE = 4, + STATE_NEXT = 5, // Subclasses which need more states start here and higher + }; + + // Called inside the task to signal that the task may be unblocked + void Wake(); + + // Called inside to advise that the task should wake and signal an error + void Error(); + + unsigned long long CurrentTime(); + + virtual std::string GetStateName(int state) const; + virtual int Process(int state); + virtual void Stop(); + virtual int ProcessStart() = 0; + virtual int ProcessResponse() { return STATE_DONE; } + + // for managing children (if any) + void AddChild(Task * child); + void AbortAllChildren(); + +private: + void Done(); + void OnChildStopped(Task * child); + + int state_; + Task * parent_; + TaskRunner * runner_; + bool blocked_; + bool done_; + bool aborted_; + bool busy_; + bool error_; + bool child_error_; + unsigned long long start_time_; + + // for managing children + typedef std::set ChildSet; + ChildSet children_; + +}; + + + + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.cc new file mode 100644 index 00000000..b5ecc55e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.cc @@ -0,0 +1,92 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "taskrunner.h" +#include "task.h" +#include + + +namespace buzz { + +TaskRunner::~TaskRunner() { + // this kills and deletes children silently! + AbortAllChildren(); + RunTasks(); +} + +void +TaskRunner::StartTask(Task * task) { + tasks_.push_back(task); + WakeTasks(); +} + +void +TaskRunner::RunTasks() { + // Running continues until all tasks are Blocked (ok for a small # of tasks) + if (tasks_running_) { + return; // don't reenter + } + + tasks_running_ = true; + + int did_run = true; + while (did_run) { + did_run = false; + // use indexing instead of iterators because tasks_ may grow + for (size_t i = 0; i < tasks_.size(); ++i) { + while (!tasks_[i]->Blocked()) { + tasks_[i]->Step(); + did_run = true; + } + } + } + // Tasks are deleted when running has paused + for (size_t i = 0; i < tasks_.size(); ++i) { + if (tasks_[i]->IsDone()) { + Task* task = tasks_[i]; + delete task; + tasks_[i] = NULL; + } + } + // Finally, remove nulls + tasks_.erase(std::remove(tasks_.begin(), tasks_.end(), (Task *)NULL), tasks_.end()); + + tasks_running_ = false; +} + +void +TaskRunner::PollTasks() { + // every task gets hit once with a poll - they wake if needed + for (size_t i = 0; i < tasks_.size(); ++i) { + if (!tasks_[i]->IsDone()) { + tasks_[i]->Poll(); + } + } +} + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.h new file mode 100644 index 00000000..eab16eb9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.h @@ -0,0 +1,64 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _TASKRUNNER_H_ +#define _TASKRUNNER_H_ + +#include + +#include "talk/base/sigslot.h" +#include "talk/base/task.h" + + +namespace buzz { + + +class Task; + +class TaskRunner : public Task, public sigslot::has_slots<> { +public: + TaskRunner() : Task(NULL), tasks_running_(false) {} + virtual ~TaskRunner(); + + virtual void WakeTasks() = 0; + virtual unsigned long long CurrentTime() = 0 ; + + void StartTask(Task * task); + void RunTasks(); + void PollTasks(); + + // dummy state machine - never run. + virtual int ProcessStart() { return STATE_DONE; } + +private: + std::vector tasks_; + bool tasks_running_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/thread.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/thread.cc new file mode 100644 index 00000000..8f18a992 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/thread.cc @@ -0,0 +1,273 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef POSIX +extern "C" { +#include +} +#endif + +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/thread.h" +#include "talk/base/jtime.h" + +namespace cricket { + +ThreadManager g_thmgr; + +#ifdef POSIX +pthread_key_t ThreadManager::key_; + +ThreadManager::ThreadManager() { + pthread_key_create(&key_, NULL); + main_thread_ = new Thread(); + SetCurrent(main_thread_); +} + +ThreadManager::~ThreadManager() { + pthread_key_delete(key_); + delete main_thread_; +} + +Thread *ThreadManager::CurrentThread() { + return (Thread *)pthread_getspecific(key_); +} + +void ThreadManager::SetCurrent(Thread *thread) { + pthread_setspecific(key_, thread); +} +#endif + +#ifdef WIN32 +DWORD ThreadManager::key_; + +ThreadManager::ThreadManager() { + key_ = TlsAlloc(); + main_thread_ = new Thread(); + SetCurrent(main_thread_); +} + +ThreadManager::~ThreadManager() { + TlsFree(key_); + delete main_thread_; +} + +Thread *ThreadManager::CurrentThread() { + return (Thread *)TlsGetValue(key_); +} + +void ThreadManager::SetCurrent(Thread *thread) { + TlsSetValue(key_, thread); +} +#endif + +void ThreadManager::Add(Thread *thread) { + CritScope cs(&crit_); + threads_.push_back(thread); +} + +void ThreadManager::Remove(Thread *thread) { + CritScope cs(&crit_); + threads_.erase(std::remove(threads_.begin(), threads_.end(), thread), threads_.end()); +} + +Thread::Thread(SocketServer* ss) : MessageQueue(ss) { + g_thmgr.Add(this); + started_ = false; + has_sends_ = false; +} + +Thread::~Thread() { + Stop(); + Clear(NULL); + g_thmgr.Remove(this); +} + +#ifdef POSIX +void Thread::Start() { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_create(&thread_, &attr, PreLoop, this); + started_ = true; +} + +void Thread::Join() { + if (started_) { + void *pv; + pthread_join(thread_, &pv); + } +} +#endif + +#ifdef WIN32 +void Thread::Start() { + thread_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PreLoop, this, 0, NULL); + started_ = true; +} + +void Thread::Join() { + if (started_) { + WaitForSingleObject(thread_, INFINITE); + CloseHandle(thread_); + started_ = false; + } +} +#endif + +void *Thread::PreLoop(void *pv) { + Thread *thread = (Thread *)pv; + ThreadManager::SetCurrent(thread); + thread->Loop(); + return NULL; +} + +void Thread::Loop(int cmsLoop) { + uint32 msEnd; + if (cmsLoop != -1) + msEnd = GetMillisecondCount() + cmsLoop; + int cmsNext = cmsLoop; + + while (true) { + Message msg; + if (!Get(&msg, cmsNext)) + return; + Dispatch(&msg); + + if (cmsLoop != -1) { + uint32 msCur = GetMillisecondCount(); + if (msCur >= msEnd) + return; + cmsNext = msEnd - msCur; + } + } +} + +void Thread::Stop() { + MessageQueue::Stop(); + Join(); +} + +void Thread::Send(MessageHandler *phandler, uint32 id, MessageData *pdata) { + // Sent messages are sent to the MessageHandler directly, in the context + // of "thread", like Win32 SendMessage. If in the right context, + // call the handler directly. + + Message msg; + msg.phandler = phandler; + msg.message_id = id; + msg.pdata = pdata; + if (IsCurrent()) { + phandler->OnMessage(&msg); + return; + } + + AutoThread thread; + Thread *current_thread = Thread::Current(); + ASSERT(current_thread != NULL); // AutoThread ensures this + + crit_.Enter(); + bool ready = false; + _SendMessage smsg; + smsg.thread = current_thread; + smsg.msg = msg; + smsg.ready = &ready; + sendlist_.push_back(smsg); + has_sends_ = true; + crit_.Leave(); + + // Wait for a reply + + ss_->WakeUp(); + while (!ready) { + current_thread->ReceiveSends(); + current_thread->socketserver()->Wait(-1, false); + } +} + +void Thread::ReceiveSends() { + // Before entering critical section, check boolean. + + if (!has_sends_) + return; + + // Receive a sent message. Cleanup scenarios: + // - thread sending exits: We don't allow this, since thread can exit + // only via Join, so Send must complete. + // - thread receiving exits: Wakeup/set ready in Thread::Clear() + // - object target cleared: Wakeup/set ready in Thread::Clear() + crit_.Enter(); + while (!sendlist_.empty()) { + _SendMessage smsg = sendlist_.front(); + sendlist_.pop_front(); + crit_.Leave(); + smsg.msg.phandler->OnMessage(&smsg.msg); + crit_.Enter(); + *smsg.ready = true; + smsg.thread->socketserver()->WakeUp(); + } + has_sends_ = false; + crit_.Leave(); +} + +void Thread::Clear(MessageHandler *phandler, uint32 id) { + CritScope cs(&crit_); + + // Remove messages on sendlist_ with phandler + // Object target cleared: remove from send list, wakeup/set ready + // if sender not NULL. + + std::list<_SendMessage>::iterator iter = sendlist_.begin(); + while (iter != sendlist_.end()) { + _SendMessage smsg = *iter; + if (phandler == NULL || smsg.msg.phandler == phandler) { + if (id == (uint32)-1 || smsg.msg.message_id == id) { + iter = sendlist_.erase(iter); + *smsg.ready = true; + smsg.thread->socketserver()->WakeUp(); + continue; + } + } + ++iter; + } + + MessageQueue::Clear(phandler, id); +} + +AutoThread::AutoThread(SocketServer* ss) : Thread(ss) { + if (!ThreadManager::CurrentThread()) { + ThreadManager::SetCurrent(this); + } +} + +AutoThread::~AutoThread() { + if (ThreadManager::CurrentThread() == this) { + ThreadManager::SetCurrent(NULL); + } +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/thread.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/thread.h new file mode 100644 index 00000000..56c23384 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/thread.h @@ -0,0 +1,141 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __THREAD_H__ +#define __THREAD_H__ + +#include "talk/base/messagequeue.h" + +#include +#include +#include + +#ifdef POSIX +#include +#endif + +#ifdef WIN32 +#include "talk/base/win32.h" +#endif + +namespace cricket { + +class Thread; + +class ThreadManager { +public: + ThreadManager(); + ~ThreadManager(); + + static Thread *CurrentThread(); + static void SetCurrent(Thread *thread); + void Add(Thread *thread); + void Remove(Thread *thread); + +private: + Thread *main_thread_; + std::vector threads_; + CriticalSection crit_; + +#ifdef POSIX + static pthread_key_t key_; +#endif + +#ifdef WIN32 + static DWORD key_; +#endif +}; + +class Thread; + +struct _SendMessage { + _SendMessage() {} + Thread *thread; + Message msg; + bool *ready; +}; + +class Thread : public MessageQueue { +public: + Thread(SocketServer* ss = 0); + virtual ~Thread(); + + static inline Thread* Current() { + return ThreadManager::CurrentThread(); + } + inline bool IsCurrent() const { + return (ThreadManager::CurrentThread() == this); + } + + virtual void Start(); + virtual void Stop(); + virtual void Loop(int cms = -1); + virtual void Send(MessageHandler *phandler, uint32 id = 0, + MessageData *pdata = NULL); + + // From MessageQueue + virtual void Clear(MessageHandler *phandler, uint32 id = (uint32)-1); + virtual void ReceiveSends(); + +#ifdef WIN32 + HANDLE GetHandle() { + return thread_; + } +#endif + +private: + static void *PreLoop(void *pv); + void Join(); + + std::list<_SendMessage> sendlist_; + bool started_; + bool has_sends_; + +#ifdef POSIX + pthread_t thread_; +#endif + +#ifdef WIN32 + HANDLE thread_; +#endif + + friend class ThreadManager; +}; + +// AutoThread automatically installs itself at construction +// uninstalls at destruction, if a Thread object is +// _not already_ associated with the current OS thread. + +class AutoThread : public Thread { +public: + AutoThread(SocketServer* ss = 0); + virtual ~AutoThread(); +}; + +} // namespace cricket + +#endif // __THREAD_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/winping.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/winping.h new file mode 100644 index 00000000..99ba2fdc --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/winping.h @@ -0,0 +1,101 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _WINPING_H_ +#define _WINPING_H_ + +#ifdef WIN32 + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "talk/base/basictypes.h" + +#include +#define _WINSOCKAPI_ +#include +#undef SetPort + +// This class wraps a Win32 API for doing ICMP pinging. This API, unlike the +// the normal socket APIs (as implemented on Win9x), will return an error if +// an ICMP packet with the dont-fragment bit set is too large. This means this +// class can be used to detect the MTU to a given address. + +typedef struct ip_option_information { + UCHAR Ttl; // Time To Live + UCHAR Tos; // Type Of Service + UCHAR Flags; // IP header flags + UCHAR OptionsSize; // Size in bytes of options data + PUCHAR OptionsData; // Pointer to options data +} IP_OPTION_INFORMATION, * PIP_OPTION_INFORMATION; + +typedef HANDLE (WINAPI *PIcmpCreateFile)(); + +typedef BOOL (WINAPI *PIcmpCloseHandle)(HANDLE icmp_handle); + +typedef DWORD (WINAPI *PIcmpSendEcho)( + HANDLE IcmpHandle, + ULONG DestinationAddress, + LPVOID RequestData, + WORD RequestSize, + PIP_OPTION_INFORMATION RequestOptions, + LPVOID ReplyBuffer, + DWORD ReplySize, + DWORD Timeout); + +class WinPing { +public: + WinPing(); + ~WinPing(); + + // Determines whether the class was initialized correctly. + bool IsValid() { return valid_; } + + // Attempts to send a ping with the given parameters. + enum PingResult { PING_FAIL, PING_TOO_LARGE, PING_TIMEOUT, PING_SUCCESS }; + PingResult Ping( + uint32 ip, uint32 data_size, uint32 timeout_millis, uint8 ttl, + bool allow_fragments); + +private: + HMODULE dll_; + HANDLE hping_; + PIcmpCreateFile create_; + PIcmpCloseHandle close_; + PIcmpSendEcho send_; + char* data_; + uint32 dlen_; + char* reply_; + uint32 rlen_; + bool valid_; +}; + +#endif // WIN32 + +#endif // _WINPING_H_ + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/examples/Makefile.am new file mode 100644 index 00000000..43b0edb5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=login call diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/Makefile.am new file mode 100644 index 00000000..81cf9345 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/Makefile.am @@ -0,0 +1,16 @@ +bin_PROGRAMS = call +call_CXXFLAGS = $(AM_CXXFLAGS) +call_SOURCES = call_main.cc callclient.cc console.cc presencepushtask.cc presenceouttask.cc +noinst_HEADERS = callclient.h console.h presenceouttask.h presencepushtask.h status.h +call_LDADD = \ + $(srcdir)/../../../talk/examples/login/libcricketexampleslogin.la \ + $(srcdir)/../../../talk/session/phone/libcricketsessionphone.la \ + $(srcdir)/../../../talk/p2p/client/libcricketp2pclient.la \ + $(srcdir)/../../../talk/p2p/base/libcricketp2pbase.la \ + $(srcdir)/../../../talk/xmpp/libcricketxmpp.la \ + $(srcdir)/../../../talk/xmllite/libcricketxmllite.la \ + $(srcdir)/../../../talk/base/libcricketbase.la \ + $(srcdir)/../../../talk/third_party/mediastreamer/libmediastreamer.la \ + $(EXPAT_LIBS) $(ORTP_LIBS) -lpthread $(ILBC_LIBS) $(SPEEX_LIBS) $(GLIB_LIBS) -lasound +AM_CPPFLAGS = -DPOSIX +DEFAULT_INCLUDES = -I$(srcdir)/../../.. \ No newline at end of file diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call.pro b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call.pro new file mode 100644 index 00000000..ccf0638b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call.pro @@ -0,0 +1,19 @@ +TEMPLATE = app +INCLUDEPATH = ../../.. +DEFINES += POSIX + +include(../../../../../conf.pri) + +# Input +SOURCES += \ + call_main.cc \ + callclient.cc \ + console.cc \ + presenceouttask.cc \ + presencepushtask.cc \ + ../login/xmppauth.cc \ + ../login/xmpppump.cc \ + ../login/xmppsocket.cc \ + ../login/xmppthread.cc + +LIBS += ../../../liblibjingle.a diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call_main.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call_main.cc new file mode 100644 index 00000000..1a965326 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call_main.cc @@ -0,0 +1,62 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/examples/login/xmppthread.h" +#include "talk/examples/login/xmppauth.h" +#include "talk/examples/call/callclient.h" +#include "talk/examples/call/console.h" + +void GetString(const char* desc, char* out) { + printf("%s: ", desc); + fflush(stdout); + scanf("%s", out); +} + +int main(int argc, char **argv) { + // TODO: Make this into a console task + char username[256], auth_cookie[256]; + GetString("Username", username); + GetString("Auth Cookie", auth_cookie); + + printf("Logging in as %s@gmail.com\n", username); + + // We will run the console and the XMPP client on the main thread. The + // CallClient maintains a separate worker thread for voice. + + cricket::PhysicalSocketServer ss; + cricket::Thread main_thread(&ss); + cricket::ThreadManager::SetCurrent(&main_thread); + + InitConsole(&ss); + XmppPump pump; + CallClient client(pump.client()); + + buzz::XmppClientSettings xcs; + xcs.set_user(username); + xcs.set_host("gmail.com"); + xcs.set_use_tls(false); + xcs.set_auth_cookie(auth_cookie); + xcs.set_server(cricket::SocketAddress("talk.google.com", 5222)); + pump.DoLogin(xcs, new XmppSocket(false), new XmppAuth()); + + main_thread.Loop(); + + return 0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.cc new file mode 100644 index 00000000..c8c28310 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.cc @@ -0,0 +1,390 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include "talk/xmpp/constants.h" +#include "talk/base/thread.h" +#include "talk/base/network.h" +#include "talk/base/socketaddress.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/helpers.h" +#include "talk/p2p/client/basicportallocator.h" +#include "talk/session/receiver.h" +#include "talk/session/sessionsendtask.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/examples/call/callclient.h" +#include "talk/examples/call/console.h" +#include "talk/examples/call/presencepushtask.h" +#include "talk/examples/call/presenceouttask.h" + +namespace { + +const char* CALL_COMMANDS = +"Available commands:\n" +"\n" +" hangup Ends the call.\n" +" mute Stops sending voice.\n" +" unmute Re-starts sending voice.\n" +""; + +class CallTask: public ConsoleTask, public sigslot::has_slots<> { +public: + CallTask(CallClient* call_client, const buzz::Jid& jid, cricket::Call* call) + : call_client_(call_client), jid_(jid), call_(call) { + } + + virtual ~CallTask() {} + + virtual void Start() { + call_client_->phone_client()->SignalCallDestroy.connect( + this, &CallTask::OnCallDestroy); + if (!call_) { + call_ = call_client_->phone_client()->CreateCall(); + call_->SignalSessionState.connect(this, &CallTask::OnSessionState); + session_ = call_->InitiateSession(jid_); + } + call_client_->phone_client()->SetFocus(call_); + } + + virtual std::string GetPrompt() { return jid_.node(); } + + virtual void ProcessLine(const std::string& line) { + std::vector words; + ParseLine(line, &words); + + if ((words.size() == 1) && (words[0] == "hangup")) { + call_->Terminate(); + SignalDone(this); + } else if ((words.size() == 1) && (words[0] == "mute")) { + call_->Mute(true); + } else if ((words.size() == 1) && (words[0] == "unmute")) { + call_->Mute(false); + } else { + console()->Print(CALL_COMMANDS); + } + } + +private: + CallClient* call_client_; + buzz::Jid jid_; + cricket::Call* call_; + cricket::Session* session_; + + void OnCallDestroy(cricket::Call* call) { + if (call == call_) { + console()->Print("call destroyed"); + SignalDone(this); + } + } + + void OnSessionState(cricket::Call* call, + cricket::Session* session, + cricket::Session::State state) { + if (state == cricket::Session::STATE_SENTINITIATE) { + console()->Print("calling..."); + } else if (state == cricket::Session::STATE_RECEIVEDACCEPT) { + console()->Print("call answered"); + } else if (state == cricket::Session::STATE_RECEIVEDREJECT) { + console()->Print("call not answered"); + SignalDone(this); + } else if (state == cricket::Session::STATE_INPROGRESS) { + console()->Print("call in progress"); + } else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) { + console()->Print("other side hung up"); + SignalDone(this); + } + } +}; + +const char* RECEIVE_COMMANDS = +"Available commands:\n" +"\n" +" accept Accepts the incoming call and switches to it.\n" +" reject Rejects the incoming call and stays with the current call.\n" +""; + +class ReceiveTask: public ConsoleTask { +public: + ReceiveTask(CallClient* call_client, + const buzz::Jid& jid, + cricket::Call* call) + : call_client_(call_client), jid_(jid), call_(call) { + } + + virtual std::string GetPrompt() { return jid_.node(); } + + virtual void ProcessLine(const std::string& line) { + std::vector words; + ParseLine(line, &words); + + if ((words.size() == 1) && (words[0] == "accept")) { + assert(call_->sessions().size() == 1); + call_->AcceptSession(call_->sessions()[0]); + Console()->Push(new CallTask(call_client_, jid_, call_)); + SignalDone(this); + } else if ((words.size() == 1) && (words[0] == "reject")) { + call_->RejectSession(call_->sessions()[0]); + SignalDone(this); + } else { + console()->Print(RECEIVE_COMMANDS); + } + } + +private: + CallClient* call_client_; + buzz::Jid jid_; + cricket::Call* call_; +}; + +const char* CONSOLE_COMMANDS = +"Available commands:\n" +"\n" +" roster Prints the online friends from your roster.\n" +" call Initiates a call to the friend with the given name.\n" +" quit Quits the application.\n" +""; + +class CallConsoleTask: public ConsoleTask { +public: + CallConsoleTask(CallClient* call_client) : call_client_(call_client) {} + virtual ~CallConsoleTask() {} + + virtual std::string GetPrompt() { return "console"; } + + virtual void ProcessLine(const std::string& line) { + std::vector words; + ParseLine(line, &words); + + if ((words.size() == 1) && (words[0] == "quit")) { + SignalDone(this); + } else if ((words.size() == 1) && (words[0] == "roster")) { + call_client_->PrintRoster(); + } else if ((words.size() == 2) && (words[0] == "call")) { + call_client_->MakeCallTo(words[1]); + } else { + console()->Print(CONSOLE_COMMANDS); + } + } + +private: + CallClient* call_client_; +}; + +const char* DescribeStatus(buzz::Status::Show show, const std::string& desc) { + switch (show) { + case buzz::Status::SHOW_XA: return desc.c_str(); + case buzz::Status::SHOW_ONLINE: return "online"; + case buzz::Status::SHOW_AWAY: return "away"; + case buzz::Status::SHOW_DND: return "do not disturb"; + case buzz::Status::SHOW_CHAT: return "ready to chat"; + delault: return "offline"; + } +} + +} // namespace + +CallClient::CallClient(buzz::XmppClient* xmpp_client) + : xmpp_client_(xmpp_client), roster_(new RosterMap) { + xmpp_client_->SignalStateChange.connect(this, &CallClient::OnStateChange); + Console()->Push(new CallConsoleTask(this)); +} + +CallClient::~CallClient() { + delete roster_; +} + +const std::string CallClient::strerror(buzz::XmppEngine::Error err) { + switch (err) { + case buzz::XmppEngine::ERROR_NONE: + return ""; + case buzz::XmppEngine::ERROR_XML: + return "Malformed XML or encoding error"; + case buzz::XmppEngine::ERROR_STREAM: + return "XMPP stream error"; + case buzz::XmppEngine::ERROR_VERSION: + return "XMPP version error"; + case buzz::XmppEngine::ERROR_UNAUTHORIZED: + return "User is not authorized (Confirm your GX cookie at mail.google.com)"; + case buzz::XmppEngine::ERROR_TLS: + return "TLS could not be negotiated"; + case buzz::XmppEngine::ERROR_AUTH: + return "Authentication could not be negotiated"; + case buzz::XmppEngine::ERROR_BIND: + return "Resource or session binding could not be negotiated"; + case buzz::XmppEngine::ERROR_CONNECTION_CLOSED: + return "Connection closed by output handler."; + case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED: + return "Closed by "; + case buzz::XmppEngine::ERROR_SOCKET: + return "Socket error"; + } +} + +void CallClient::OnStateChange(buzz::XmppEngine::State state) { + switch (state) { + case buzz::XmppEngine::STATE_START: + Console()->Print("connecting..."); + break; + + case buzz::XmppEngine::STATE_OPENING: + Console()->Print("logging in..."); + break; + + case buzz::XmppEngine::STATE_OPEN: + Console()->Print("logged in..."); + InitPhone(); + InitPresence(); + break; + + case buzz::XmppEngine::STATE_CLOSED: + buzz::XmppEngine::Error error = xmpp_client_->GetError(); + Console()->Print("logged out..." + strerror(error)); + exit(0); + } +} + +void CallClient::InitPhone() { + std::string client_unique = xmpp_client_->jid().Str(); + cricket::InitRandom(client_unique.c_str(), client_unique.size()); + + worker_thread_ = new cricket::Thread(); + + network_manager_ = new cricket::NetworkManager(); + + cricket::SocketAddress *stun_addr = new cricket::SocketAddress("64.233.167.126", 19302); + port_allocator_ = new cricket::BasicPortAllocator(network_manager_, stun_addr, NULL); + + session_manager_ = new cricket::SessionManager( + port_allocator_, worker_thread_); + session_manager_->SignalRequestSignaling.connect( + this, &CallClient::OnRequestSignaling); + session_manager_->OnSignalingReady(); + + phone_client_ = new cricket::PhoneSessionClient( + xmpp_client_->jid(),session_manager_); + phone_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate); + phone_client_->SignalSendStanza.connect(this, &CallClient::OnSendStanza); + + receiver_ = new cricket::Receiver(xmpp_client_, phone_client_); + receiver_->Start(); + + worker_thread_->Start(); +} + +void CallClient::OnRequestSignaling() { + session_manager_->OnSignalingReady(); +} + +void CallClient::OnCallCreate(cricket::Call* call) { + call->SignalSessionState.connect(this, &CallClient::OnSessionState); +} + +void CallClient::OnSessionState(cricket::Call* call, + cricket::Session* session, + cricket::Session::State state) { + if (state == cricket::Session::STATE_RECEIVEDINITIATE) { + buzz::Jid jid(session->remote_address()); + Console()->Printf("Incoming call from '%s'", jid.Str().c_str()); + Console()->Push(new ReceiveTask(this, jid, call)); + } +} + +void CallClient::OnSendStanza(cricket::SessionClient *client, const buzz::XmlElement* stanza) { + cricket::SessionSendTask* sender = + new cricket::SessionSendTask(xmpp_client_, phone_client_); + sender->Send(stanza); + sender->Start(); +} + +void CallClient::InitPresence() { + presence_push_ = new buzz::PresencePushTask(xmpp_client_); + presence_push_->SignalStatusUpdate.connect( + this, &CallClient::OnStatusUpdate); + presence_push_->Start(); + + buzz::Status my_status; + my_status.set_jid(xmpp_client_->jid()); + my_status.set_available(true); + my_status.set_invisible(false); + my_status.set_show(buzz::Status::SHOW_ONLINE); + my_status.set_priority(0); + my_status.set_know_capabilities(true); + my_status.set_phone_capability(true); + my_status.set_is_google_client(true); + my_status.set_version("1.0.0.66"); + + buzz::PresenceOutTask* presence_out_ = + new buzz::PresenceOutTask(xmpp_client_); + presence_out_->Send(my_status); + presence_out_->Start(); +} + +void CallClient::OnStatusUpdate(const buzz::Status& status) { + RosterItem item; + item.jid = status.jid(); + item.show = status.show(); + item.status = status.status(); + + std::string key = item.jid.Str(); + + if (status.available() && status.phone_capability()) { + Console()->Printf("Adding to roster: %s", key.c_str()); + (*roster_)[key] = item; + } else { + Console()->Printf("Removing from roster: %s", key.c_str()); + RosterMap::iterator iter = roster_->find(key); + if (iter != roster_->end()) + roster_->erase(iter); + } +} + +void CallClient::PrintRoster() { + Console()->Printf("Roster contains %d callable", roster_->size()); + RosterMap::iterator iter = roster_->begin(); + while (iter != roster_->end()) { + Console()->Printf("%s - %s", + iter->second.jid.BareJid().Str().c_str(), + DescribeStatus(iter->second.show, iter->second.status)); + iter++; + } +} + +void CallClient::MakeCallTo(const std::string& name) { + bool found = false; + buzz::Jid found_jid; + + RosterMap::iterator iter = roster_->begin(); + while (iter != roster_->end()) { + if (iter->second.jid.node() == name) { + found = true; + found_jid = iter->second.jid; + break; + } + ++iter; + } + + if (found) { + Console()->Printf("Found online friend '%s'", found_jid.Str().c_str()); + Console()->Push(new CallTask(this, found_jid, NULL)); + } else { + Console()->Printf("Could not find online friend '%s'", name.c_str()); + } +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.h new file mode 100644 index 00000000..2400b7db --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.h @@ -0,0 +1,88 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef CRICKET_EXAMPLES_CALL_CALLCLIENT_H__ +#define CRICKET_EXAMPLES_CALL_CALLCLIENT_H__ + +#include +#include +#include "talk/p2p/base/session.h" +#include "talk/p2p/client/sessionclient.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/examples/call/status.h" + +namespace buzz { +class PresencePushTask; +class Status; +} + +namespace cricket { +class Thread; +class NetworkManager; +class PortAllocator; +class PhoneSessionClient; +class Receiver; +class Call; +} + +struct RosterItem { + buzz::Jid jid; + buzz::Status::Show show; + std::string status; +}; + +class CallClient: public sigslot::has_slots<> { +public: + CallClient(buzz::XmppClient* xmpp_client); + ~CallClient(); + + cricket::PhoneSessionClient* phone_client() const { return phone_client_; } + + void PrintRoster(); + void MakeCallTo(const std::string& name); + +private: + typedef std::map RosterMap; + + buzz::XmppClient* xmpp_client_; + cricket::Thread* worker_thread_; + cricket::NetworkManager* network_manager_; + cricket::PortAllocator* port_allocator_; + cricket::SessionManager* session_manager_; + cricket::PhoneSessionClient* phone_client_; + cricket::Receiver* receiver_; + buzz::PresencePushTask* presence_push_; + RosterMap* roster_; + + void OnStateChange(buzz::XmppEngine::State state); + + void InitPhone(); + void OnRequestSignaling(); + void OnCallCreate(cricket::Call* call); + const std::string strerror(buzz::XmppEngine::Error err); + void OnSessionState(cricket::Call* call, + cricket::Session* session, + cricket::Session::State state); + void OnSendStanza(cricket::SessionClient *client, const buzz::XmlElement* stanza); + + void InitPresence(); + void OnStatusUpdate(const buzz::Status& status); +}; + +#endif // CRICKET_EXAMPLES_CALL_CALLCLIENT_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.cc new file mode 100644 index 00000000..4150f281 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.cc @@ -0,0 +1,196 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * 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 + */ + +extern "C" { +#include +#include +#include +#include +#include +} +#include +#include +#include +#include + +#include "talk/examples/call/console.h" + +namespace { + +void PError(const char* desc) { + perror(desc); + exit(1); +} + +CConsole* gConsole = NULL; + +const uint32 MSG_UPDATE = 1; + +} // namespace + +void InitConsole(cricket::PhysicalSocketServer* ss) { + assert(gConsole == NULL); + assert(ss); + gConsole = new CConsole(ss); +} + +CConsole* Console() { + assert(gConsole); + return gConsole; +} + +CConsole::CConsole(cricket::PhysicalSocketServer* ss) + : prompting_(false), prompt_dirty_(false) { + stdin_ = ss->CreateFile(0); + stdin_->SignalReadEvent.connect(this, &CConsole::OnReadInput); + + tasks_ = new std::vector; +} + +CConsole::~CConsole() { + delete stdin_; + delete tasks_; +} + +void CConsole::Push(ConsoleTask* task) { + task->set_console(this); + task->SignalDone.connect(this, &CConsole::OnTaskDone); + tasks_->push_back(task); + task->Start(); + UpdatePrompt(); +} + +void CConsole::Remove(ConsoleTask* task) { + int index = -1; + for (size_t i = 0; i < tasks_->size(); ++i) { + if ((*tasks_)[i] == task) + index = i; + } + + assert(index >= 0); + tasks_->erase(tasks_->begin() + index); + if (static_cast(tasks_->size()) == index) + UpdatePrompt(); + + delete task; + + if (tasks_->size() == 0) + exit(0); +} + +void CConsole::Print(const char* str) { + if (prompting_) + printf("\r"); + printf("%s\n", str); + prompting_ = false; + UpdatePrompt(); +} + +void CConsole::Print(const std::string& str) { + Print(str.c_str()); +} + +void CConsole::Printf(const char* format, ...) { + va_list ap; + va_start(ap, format); + + char buf[4096]; + int size = vsnprintf(buf, sizeof(buf), format, ap); + assert(size >= 0); + assert(size < static_cast(sizeof(buf))); + buf[size] = '\0'; + Print(buf); + + va_end(ap); +} + +void CConsole::OnTaskDone(ConsoleTask* task) { + Remove(task); +} + +void CConsole::OnReadInput(cricket::AsyncFile* file) { + assert(file == stdin_); + + char buf[4096]; + int size = read(0, buf, sizeof(buf)); + if (size < 0) + PError("read"); + + prompting_ = (buf[size-1] != '\n'); + + int start = 0; + for (int i = 0; i < size; ++i) { + if (buf[i] == '\n') { + std::string line = input_; + line.append(buf + start, i + 1 - start); + input_.clear(); + + assert(tasks_->size() > 0); + tasks_->back()->ProcessLine(line); + + start = i + 1; + } + } + + input_.append(buf + start, size - start); +} + +void CConsole::OnMessage(cricket::Message* pmsg) { + assert(pmsg->message_id == MSG_UPDATE); + assert(tasks_->size() > 0); + if (prompting_) + printf("\n"); + printf("%s: %s", tasks_->back()->GetPrompt().c_str(), input_.c_str()); + fflush(stdout); + prompting_ = true; + prompt_dirty_ = false; +} + +void CConsole::UpdatePrompt() { + if (!prompt_dirty_) { + prompt_dirty_ = true; + cricket::Thread::Current()->Post(this, MSG_UPDATE); + } +} + +void ConsoleTask::ParseLine(const std::string& line, + std::vector* words) { + assert(line.size() > 0); + assert(line[line.size() - 1] == '\n'); + + int start = -1; + int state = 0; + for (int index = 0; index <= static_cast(line.size()); ++index) { + if (state == 0) { + if (!isspace(line[index])) { + start = index; + state = 1; + } + } else { + assert(state == 1); + assert(start >= 0); + if (isspace(line[index])) { + std::string word(line, start, index - start); + words->push_back(word); + start = -1; + state = 0; + } + } + } +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.h new file mode 100644 index 00000000..aca229b9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.h @@ -0,0 +1,82 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef CRICKET_EXAMPLES_CALL_CONSOLE_H__ +#define CRICKET_EXAMPLES_CALL_CONSOLE_H__ + +#include +#include + +#include "talk/base/sigslot.h" +#include "talk/base/thread.h" +#include "talk/base/physicalsocketserver.h" + +class CConsole; + +class ConsoleTask { +public: + ConsoleTask() : console_(NULL) {} + virtual ~ConsoleTask() {} + + CConsole* console() const { return console_; } + void set_console(CConsole* console) { console_ = console; } + + virtual void Start() {} + virtual std::string GetPrompt() = 0; + virtual void ProcessLine(const std::string& line) = 0; // includes newline + + sigslot::signal1 SignalDone; + +protected: + void ParseLine(const std::string& line, std::vector* words); + +private: + CConsole* console_; +}; + +class CConsole: public cricket::MessageHandler, public sigslot::has_slots<> { +public: + CConsole(cricket::PhysicalSocketServer* ss); + ~CConsole(); + + void Push(ConsoleTask* task); + void Remove(ConsoleTask* task); + + // final newline should not be included + void Print(const char* str); + void Print(const std::string& str); + void Printf(const char* format, ...); + +private: + cricket::AsyncFile* stdin_; + std::vector* tasks_; + std::string input_; + bool prompting_; + bool prompt_dirty_; + + void OnTaskDone(ConsoleTask* task); + void OnReadInput(cricket::AsyncFile* file); + void OnMessage(cricket::Message* pmsg); + void UpdatePrompt(); +}; + +void InitConsole(cricket::PhysicalSocketServer* ss); +CConsole* Console(); + +#endif // CRICKET_EXAMPLES_CALL_CONSOLE_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.cc new file mode 100644 index 00000000..3ecdb420 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.cc @@ -0,0 +1,148 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include "talk/xmpp/constants.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/examples/call/presenceouttask.h" + +namespace buzz { + +// string helper functions ----------------------------------------------------- +template static +bool FromString(const std::string& s, + T * t) { + std::istringstream iss(s); + return !(iss>>*t).fail(); +} + +template static +bool ToString(const T &t, + std::string* s) { + std::ostringstream oss; + oss << t; + *s = oss.str(); + return !oss.fail(); +} + +XmppReturnStatus +PresenceOutTask::Send(const Status & s) { + if (GetState() != STATE_INIT) + return XMPP_RETURN_BADSTATE; + + stanza_.reset(TranslateStatus(s)); + return XMPP_RETURN_OK; +} + +XmppReturnStatus +PresenceOutTask::SendDirected(const Jid & j, const Status & s) { + if (GetState() != STATE_INIT) + return XMPP_RETURN_BADSTATE; + + XmlElement * presence = TranslateStatus(s); + presence->AddAttr(QN_TO, j.Str()); + stanza_.reset(presence); + return XMPP_RETURN_OK; +} + +XmppReturnStatus PresenceOutTask::SendProbe(const Jid & jid) { + if (GetState() != STATE_INIT) + return XMPP_RETURN_BADSTATE; + + XmlElement * presence = new XmlElement(QN_PRESENCE); + presence->AddAttr(QN_TO, jid.Str()); + presence->AddAttr(QN_TYPE, "probe"); + + stanza_.reset(presence); + return XMPP_RETURN_OK; +} + +int +PresenceOutTask::ProcessStart() { + if (SendStanza(stanza_.get()) != XMPP_RETURN_OK) + return STATE_ERROR; + return STATE_DONE; +} + +XmlElement * +PresenceOutTask::TranslateStatus(const Status & s) { + XmlElement * result = new XmlElement(QN_PRESENCE); + if (!s.available()) { + result->AddAttr(QN_TYPE, STR_UNAVAILABLE); + } + else { + if (s.invisible()) { + result->AddAttr(QN_TYPE, STR_INVISIBLE); + } + + if (s.show() != Status::SHOW_ONLINE && s.show() != Status::SHOW_OFFLINE) { + result->AddElement(new XmlElement(QN_SHOW)); + switch (s.show()) { + default: + result->AddText(STR_SHOW_AWAY, 1); + break; + case Status::SHOW_XA: + result->AddText(STR_SHOW_XA, 1); + break; + case Status::SHOW_DND: + result->AddText(STR_SHOW_DND, 1); + break; + case Status::SHOW_CHAT: + result->AddText(STR_SHOW_CHAT, 1); + break; + } + } + + result->AddElement(new XmlElement(QN_STATUS)); + result->AddText(s.status(), 1); + + std::string pri; + ToString(s.priority(), &pri); + + result->AddElement(new XmlElement(QN_PRIORITY)); + result->AddText(pri, 1); + + if (s.know_capabilities() && s.is_google_client()) { + result->AddElement(new XmlElement(QN_CAPS_C, true)); + result->AddAttr(QN_NODE, GOOGLE_CLIENT_NODE, 1); + result->AddAttr(QN_VER, s.version(), 1); + result->AddAttr(QN_EXT, s.phone_capability() ? "voice-v1" : "", 1); + } + + // Put the delay mark on the presence according to JEP-0091 + { + result->AddElement(new XmlElement(kQnDelayX, true)); + + // This here is why we *love* the C runtime + time_t current_time_seconds; + time(¤t_time_seconds); + struct tm* current_time = gmtime(¤t_time_seconds); + char output[256]; + strftime(output, ARRAY_SIZE(output), "%Y%m%dT%H:%M:%S", current_time); + result->AddAttr(kQnStamp, output, 1); + } + + } + + return result; +} + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.h new file mode 100644 index 00000000..868bda59 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.h @@ -0,0 +1,46 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _PRESENCEOUTTASK_H_ +#define _PRESENCEOUTTASK_H_ + +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/xmpptask.h" +#include "talk/examples/call/status.h" + +namespace buzz { + +class PresenceOutTask : public XmppTask { +public: + PresenceOutTask(Task * parent) : XmppTask(parent) {} + virtual ~PresenceOutTask() {} + + XmppReturnStatus Send(const Status & s); + XmppReturnStatus SendDirected(const Jid & j, const Status & s); + XmppReturnStatus SendProbe(const Jid& jid); + + virtual int ProcessStart(); +private: + XmlElement * TranslateStatus(const Status & s); + scoped_ptr stanza_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.cc new file mode 100644 index 00000000..d0543b99 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.cc @@ -0,0 +1,172 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "talk/examples/call/presencepushtask.h" +#include "talk/xmpp/constants.h" +#include + + +namespace buzz { + +// string helper functions ----------------------------------------------------- +template static +bool FromString(const std::string& s, + T * t) { + std::istringstream iss(s); + return !(iss>>*t).fail(); +} + +template static +bool ToString(const T &t, + std::string* s) { + std::ostringstream oss; + oss << t; + *s = oss.str(); + return !oss.fail(); +} + +static bool +IsXmlSpace(int ch) { + return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t'; +} + +static bool +ListContainsToken(const std::string & list, const std::string & token) { + size_t i = list.find(token); + if (i == std::string::npos || token.empty()) + return false; + bool boundary_before = (i == 0 || IsXmlSpace(list[i - 1])); + bool boundary_after = (i == list.length() - token.length() || IsXmlSpace(list[i + token.length()])); + return boundary_before && boundary_after; +} + + +bool +PresencePushTask::HandleStanza(const XmlElement * stanza) { + if (stanza->Name() != QN_PRESENCE) + return false; + if (stanza->HasAttr(QN_TYPE) && stanza->Attr(QN_TYPE) != STR_UNAVAILABLE) + return false; + QueueStanza(stanza); + return true; +} + +static bool IsUtf8FirstByte(int c) { + return (((c)&0x80)==0) || // is single byte + ((unsigned char)((c)-0xc0)<0x3e); // or is lead byte +} + +int +PresencePushTask::ProcessStart() { + const XmlElement * stanza = NextStanza(); + if (stanza == NULL) + return STATE_BLOCKED; + Status s; + + s.set_jid(Jid(stanza->Attr(QN_FROM))); + + if (stanza->Attr(QN_TYPE) == STR_UNAVAILABLE) { + s.set_available(false); + SignalStatusUpdate(s); + } + else { + s.set_available(true); + const XmlElement * status = stanza->FirstNamed(QN_STATUS); + if (status != NULL) { + s.set_status(status->BodyText()); + + // Truncate status messages longer than 300 bytes + if (s.status().length() > 300) { + size_t len = 300; + + // Be careful not to split legal utf-8 chars in half + while (!IsUtf8FirstByte(s.status()[len]) && len > 0) { + len -= 1; + } + std::string truncated(s.status(), 0, len); + s.set_status(truncated); + } + } + + const XmlElement * priority = stanza->FirstNamed(QN_PRIORITY); + if (priority != NULL) { + int pri; + if (FromString(priority->BodyText(), &pri)) { + s.set_priority(pri); + } + } + + const XmlElement * show = stanza->FirstNamed(QN_SHOW); + if (show == NULL || show->FirstChild() == NULL) { + s.set_show(Status::SHOW_ONLINE); + } + else { + if (show->BodyText() == "away") { + s.set_show(Status::SHOW_AWAY); + } + else if (show->BodyText() == "xa") { + s.set_show(Status::SHOW_XA); + } + else if (show->BodyText() == "dnd") { + s.set_show(Status::SHOW_DND); + } + else if (show->BodyText() == "chat") { + s.set_show(Status::SHOW_CHAT); + } + else { + s.set_show(Status::SHOW_ONLINE); + } + } + + const XmlElement * caps = stanza->FirstNamed(QN_CAPS_C); + if (caps != NULL) { + std::string node = caps->Attr(QN_NODE); + std::string ver = caps->Attr(QN_VER); + std::string exts = caps->Attr(QN_EXT); + + s.set_know_capabilities(true); + + if (node == GOOGLE_CLIENT_NODE) { + s.set_is_google_client(true); + s.set_version(ver); + if (ListContainsToken(exts, "voice-v1")) { + s.set_phone_capability(true); + } + } + } + + const XmlElement* delay = stanza->FirstNamed(kQnDelayX); + if (delay != NULL) { + // Ideally we would parse this according to the Psuedo ISO-8601 rules + // that are laid out in JEP-0082: + // http://www.jabber.org/jeps/jep-0082.html + std::string stamp = delay->Attr(kQnStamp); + s.set_sent_time(stamp); + } + + SignalStatusUpdate(s); + } + + return STATE_START; +} + + +} + + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.h new file mode 100644 index 00000000..45bc9020 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.h @@ -0,0 +1,44 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _PRESENCEPUSHTASK_H_ +#define _PRESENCEPUSHTASK_H_ + +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/xmpptask.h" +#include "talk/base/sigslot.h" +#include "talk/examples/call/status.h" + +namespace buzz { + +class PresencePushTask : public XmppTask { + +public: + PresencePushTask(Task * parent) : XmppTask(parent, XmppEngine::HL_TYPE) {} + virtual int ProcessStart(); + sigslot::signal1SignalStatusUpdate; + +protected: + virtual bool HandleStanza(const XmlElement * stanza); +}; + + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/status.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/status.h new file mode 100644 index 00000000..a1e76f62 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/status.h @@ -0,0 +1,213 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _STATUS_H_ +#define _STATUS_H_ + +#include "talk/xmpp/jid.h" +#include "talk/xmpp/constants.h" + +#define GOOGLE_CLIENT_NODE "http://www.google.com/xmpp/client/caps" + +namespace buzz { + +class Status { +public: + Status() : + pri_(0), + show_(SHOW_NONE), + available_(false), + invisible_(false), + e_code_(0), + phone_capability_(false), + know_capabilities_(false), + is_google_client_(false), + feedback_probation_(false) {}; + + ~Status() {} + + // These are arranged in "priority order", i.e., if we see + // two statuses at the same priority but with different Shows, + // we will show the one with the highest show in the following + // order. + enum Show { + SHOW_NONE = 0, + SHOW_INVISIBLE = 1, + SHOW_OFFLINE = 2, + SHOW_XA = 3, + SHOW_AWAY = 4, + SHOW_DND = 5, + SHOW_ONLINE = 6, + SHOW_CHAT = 7, + }; + + const Jid & jid() const { return jid_; } + int priority() const { return pri_; } + Show show() const { return show_; } + const std::string & status() const { return status_; } + bool available() const { return available_ ; } + bool invisible() const { return invisible_; } + int error_code() const { return e_code_; } + const std::string & error_string() const { return e_str_; } + bool know_capabilities() const { return know_capabilities_; } + bool phone_capability() const { return phone_capability_; } + bool is_google_client() const { return is_google_client_; } + const std::string & version() const { return version_; } + bool feedback_probation() const { return feedback_probation_; } + const std::string& sent_time() const { return sent_time_; } + + void set_jid(const Jid & jid) { jid_ = jid; } + void set_priority(int pri) { pri_ = pri; } + void set_show(Show show) { show_ = show; } + void set_status(const std::string & status) { status_ = status; } + void set_available(bool a) { available_ = a; } + void set_invisible(bool i) { invisible_ = i; } + void set_error(int e_code, const std::string e_str) + { e_code_ = e_code; e_str_ = e_str; } + void set_know_capabilities(bool f) { know_capabilities_ = f; } + void set_phone_capability(bool f) { phone_capability_ = f; } + void set_is_google_client(bool f) { is_google_client_ = f; } + void set_version(const std::string & v) { version_ = v; } + void set_feedback_probation(bool f) { feedback_probation_ = f; } + void set_sent_time(const std::string& time) { sent_time_ = time; } + + void UpdateWith(const Status & new_value) { + if (!new_value.know_capabilities()) { + bool k = know_capabilities(); + bool i = is_google_client(); + bool p = phone_capability(); + std::string v = version(); + + *this = new_value; + + set_know_capabilities(k); + set_is_google_client(i); + set_phone_capability(p); + set_version(v); + } + else { + *this = new_value; + } + } + + bool HasQuietStatus() const { + if (status_.empty()) + return false; + return !(QuietStatus().empty()); + } + + // Knowledge of other clients' silly automatic status strings - + // Don't show these. + std::string QuietStatus() const { + if (jid_.resource().find("Psi") != std::string::npos) { + if (status_ == "Online" || + status_.find("Auto Status") != std::string::npos) + return STR_EMPTY; + } + if (jid_.resource().find("Gaim") != std::string::npos) { + if (status_ == "Sorry, I ran out for a bit!") + return STR_EMPTY; + } + return TrimStatus(status_); + } + + std::string ExplicitStatus() const { + std::string result = QuietStatus(); + if (result.empty()) { + result = ShowStatus(); + } + return result; + } + + std::string ShowStatus() const { + std::string result; + if (!available()) { + result = "Offline"; + } + else { + switch (show()) { + case SHOW_AWAY: + case SHOW_XA: + result = "Idle"; + break; + case SHOW_DND: + result = "Busy"; + break; + case SHOW_CHAT: + result = "Chatty"; + break; + default: + result = "Available"; + break; + } + } + return result; + } + + static std::string TrimStatus(const std::string & st) { + std::string s(st); + int j = 0; + bool collapsing = true; + for (unsigned int i = 0; i < s.length(); i+= 1) { + if (s[i] <= ' ' && s[i] >= 0) { + if (collapsing) { + continue; + } + else { + s[j] = ' '; + j += 1; + collapsing = true; + } + } + else { + s[j] = s[i]; + j += 1; + collapsing = false; + } + } + if (collapsing && j > 0) { + j -= 1; + } + s.erase(j, s.length()); + return s; + } + +private: + Jid jid_; + int pri_; + Show show_; + std::string status_; + bool available_; + bool invisible_; + int e_code_; + std::string e_str_; + bool feedback_probation_; + + // capabilities (valid only if know_capabilities_ + bool know_capabilities_; + bool phone_capability_; + bool is_google_client_; + std::string version_; + + std::string sent_time_; // from the jabber:x:delay element +}; + +} + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/Makefile.am new file mode 100644 index 00000000..16164fb7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/Makefile.am @@ -0,0 +1,15 @@ +noinst_LTLIBRARIES= libcricketexampleslogin.la +libcricketexampleslogin_la_SOURCES = xmppsocket.cc \ + xmppauth.cc \ + xmppthread.cc \ + xmpppump.cc +noinst_HEADERS = xmppauth.h xmpppump.h xmppsocket.h xmppthread.h +bin_PROGRAMS = login +login_CXXFLAGS = $(AM_CXXFLAGS) +login_SOURCES = login_main.cc xmppsocket.cc xmppthread.cc xmpppump.cc xmppauth.cc +login_LDADD = $(srcdir)/../../../talk/xmpp/libcricketxmpp.la \ + $(srcdir)/../../../talk/xmllite/libcricketxmllite.la \ + $(srcdir)/../../../talk/base/libcricketbase.la \ + $(EXPAT_LIBS) -lpthread +AM_CPPFLAGS = -DPOSIX +DEFAULT_INCLUDES = -I$(srcdir)/../../.. diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/login_main.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/login_main.cc new file mode 100644 index 00000000..2939c79f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/login_main.cc @@ -0,0 +1,63 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/thread.h" +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/examples/login/xmppthread.h" +#include + +int main(int argc, char **argv) { + printf("Auth Cookie: "); + fflush(stdout); + + char auth_cookie[256]; + scanf("%s", auth_cookie); + + char username[256]; + scanf("%s", username); + + // Start xmpp on a different thread + XmppThread thread; + thread.Start(); + + buzz::XmppClientSettings xcs; + xcs.set_user(username); + xcs.set_host("gmail.com"); + xcs.set_use_tls(false); + xcs.set_auth_cookie(auth_cookie); + xcs.set_server(cricket::SocketAddress("talk.google.com", 5222)); + thread.Login(xcs); + + // Use main thread for console input + std::string line; + while (std::getline(std::cin, line)) { + if (line == "quit") + break; + } + return 0; +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.cc new file mode 100644 index 00000000..66191f12 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.cc @@ -0,0 +1,93 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "talk/examples/login/xmppauth.h" +#include "talk/xmpp/saslcookiemechanism.h" +#include "talk/xmpp/saslplainmechanism.h" + +XmppAuth::XmppAuth() : done_(false), error_(false) { +} + +XmppAuth::~XmppAuth() { +} + +void XmppAuth::StartPreXmppAuth(const buzz::Jid & jid, + const cricket::SocketAddress & server, + const buzz::XmppPassword & pass, + const std::string & auth_cookie) { + jid_ = jid; + passwd_ = pass; + auth_cookie_ = auth_cookie; + error_ = auth_cookie.empty(); + done_ = true; + + SignalAuthDone(); +} + +std::string XmppAuth::ChooseBestSaslMechanism( + const std::vector & mechanisms, + bool encrypted) { + std::vector::const_iterator it; + + // a token is the weakest auth - 15s, service-limited, so prefer it. + it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-TOKEN"); + if (it != mechanisms.end()) + return "X-GOOGLE-TOKEN"; + + // a cookie is the next weakest - 14 days + it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-COOKIE"); + if (it != mechanisms.end()) + return "X-GOOGLE-COOKIE"; + + // never pass @google.com passwords without encryption!! + if (!encrypted && (jid_.domain() == "google.com")) + return ""; + + // as a last resort, use plain authentication + if (jid_.domain() != "google.com") { + it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN"); + if (it != mechanisms.end()) + return "PLAIN"; + } + + // No good mechanism found + return ""; +} + +buzz::SaslMechanism* XmppAuth::CreateSaslMechanism( + const std::string & mechanism) { + if (mechanism == "X-GOOGLE-TOKEN") { + return new buzz::SaslCookieMechanism(mechanism, jid_.Str(), auth_cookie_); + //} else if (mechanism == "X-GOOGLE-COOKIE") { + // return new buzz::SaslCookieMechanism(mechanism, jid.Str(), sid_); + } else if (mechanism == "PLAIN") { + return new buzz::SaslPlainMechanism(jid_, passwd_); + } else { + return NULL; + } +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.h new file mode 100644 index 00000000..57743f90 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.h @@ -0,0 +1,71 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _XMPPAUTH_H_ +#define _XMPPAUTH_H_ + +#include + +#include "talk/base/sigslot.h" +#include "talk/xmpp/jid.h" +#include "talk/xmpp/saslhandler.h" +#include "talk/xmpp/prexmppauth.h" +#include "talk/xmpp/xmpppassword.h" + +class XmppAuth: public buzz::PreXmppAuth { +public: + XmppAuth(); + virtual ~XmppAuth(); + + virtual void StartPreXmppAuth(const buzz::Jid & jid, + const cricket::SocketAddress & server, + const buzz::XmppPassword & pass, + const std::string & auth_cookie); + + virtual bool IsAuthDone() { return done_; } + virtual bool IsAuthorized() { return !error_; } + virtual bool HadError() { return error_; } + virtual buzz::CaptchaChallenge GetCaptchaChallenge() { + return buzz::CaptchaChallenge(); + } + virtual std::string GetAuthCookie() { return auth_cookie_; } + + virtual std::string ChooseBestSaslMechanism( + const std::vector & mechanisms, + bool encrypted); + + virtual buzz::SaslMechanism * CreateSaslMechanism( + const std::string & mechanism); + +private: + buzz::Jid jid_; + buzz::XmppPassword passwd_; + std::string auth_cookie_; + bool done_, error_; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.cc new file mode 100644 index 00000000..7d966fb3 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.cc @@ -0,0 +1,73 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/examples/login/xmpppump.h" +#include "talk/examples/login/xmppauth.h" + +XmppPump::XmppPump(XmppPumpNotify * notify) { + state_ = buzz::XmppEngine::STATE_NONE; + notify_ = notify; + client_ = new buzz::XmppClient(this); // NOTE: deleted by TaskRunner +} + +void XmppPump::DoLogin(const buzz::XmppClientSettings & xcs, + buzz::AsyncSocket* socket, + buzz::PreXmppAuth* auth) { + OnStateChange(buzz::XmppEngine::STATE_START); + client_->SignalStateChange.connect(this, &XmppPump::OnStateChange); + client_->Connect(xcs, socket, auth); + client_->Start(); +} + +void XmppPump::DoDisconnect() { + client_->Disconnect(); + OnStateChange(buzz::XmppEngine::STATE_CLOSED); +} + +void XmppPump::OnStateChange(buzz::XmppEngine::State state) { + if (state_ == state) + return; + state_ = state; + if (notify_ != NULL) + notify_->OnStateChange(state); +} + +void XmppPump::WakeTasks() { + cricket::Thread::Current()->Post(this); +} + +unsigned long long XmppPump::CurrentTime() { + return cricket::Time(); +} + +void XmppPump::OnMessage(cricket::Message *pmsg) { + RunTasks(); +} + +buzz::XmppReturnStatus XmppPump::SendStanza(const buzz::XmlElement *stanza) { + return client_->SendStanza(stanza); +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.h new file mode 100644 index 00000000..fd6f88bb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.h @@ -0,0 +1,73 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _XMPPPUMP_H_ +#define _XMPPPUMP_H_ + +#include "talk/base/messagequeue.h" +#include "talk/base/taskrunner.h" +#include "talk/base/thread.h" +#include "talk/base/jtime.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/xmpptask.h" + +// Simple xmpp pump + +class XmppPumpNotify { +public: + virtual void OnStateChange(buzz::XmppEngine::State state) = 0; +}; + +class XmppPump : public cricket::MessageHandler, public buzz::TaskRunner { +public: + XmppPump(XmppPumpNotify * notify = NULL); + + buzz::XmppClient *client() { return client_; } + + void DoLogin(const buzz::XmppClientSettings & xcs, + buzz::AsyncSocket* socket, + buzz::PreXmppAuth* auth); + void DoDisconnect(); + + void OnStateChange(buzz::XmppEngine::State state); + + void WakeTasks(); + + unsigned long long CurrentTime(); + + void OnMessage(cricket::Message *pmsg); + + buzz::XmppReturnStatus SendStanza(const buzz::XmlElement *stanza); + +private: + buzz::XmppClient *client_; + buzz::XmppEngine::State state_; + XmppPumpNotify *notify_; +}; + +#endif // _XMPPPUMP_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.cc new file mode 100644 index 00000000..33aabf3e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.cc @@ -0,0 +1,144 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "talk/base/basicdefs.h" +#include "talk/base/logging.h" +#include "talk/base/thread.h" +#ifdef FEATURE_ENABLE_SSL +#include "talk/base/schanneladapter.h" +#endif +#include "xmppsocket.h" + +XmppSocket::XmppSocket(bool tls) : tls_(tls) { + cricket::Thread* pth = cricket::Thread::Current(); + cricket::AsyncSocket* socket = + pth->socketserver()->CreateAsyncSocket(SOCK_STREAM); +#ifdef FEATURE_ENABLE_SSL + if (tls_) + socket = new cricket::SChannelAdapter(socket); +#endif + cricket_socket_ = socket; + cricket_socket_->SignalReadEvent.connect(this, &XmppSocket::OnReadEvent); + cricket_socket_->SignalWriteEvent.connect(this, &XmppSocket::OnWriteEvent); + cricket_socket_->SignalConnectEvent.connect(this, + &XmppSocket::OnConnectEvent); + state_ = buzz::AsyncSocket::STATE_CLOSED; +} + +XmppSocket::~XmppSocket() { + Close(); + delete cricket_socket_; +} + +void XmppSocket::OnReadEvent(cricket::AsyncSocket * socket) { + SignalRead(); +} + +void XmppSocket::OnWriteEvent(cricket::AsyncSocket * socket) { + // Write bytes if there are any + while (buffer_.Length() != 0) { + int written = cricket_socket_->Send(buffer_.Data(), buffer_.Length()); + if (written > 0) { + buffer_.Shift(written); + continue; + } + if (!cricket_socket_->IsBlocking()) + LOG(LS_ERROR) << "Send error: " << cricket_socket_->GetError(); + return; + } +} + +void XmppSocket::OnConnectEvent(cricket::AsyncSocket * socket) { +#if defined(FEATURE_ENABLE_SSL) + if (state_ == buzz::AsyncSocket::STATE_TLS_CONNECTING) { + state_ = buzz::AsyncSocket::STATE_TLS_OPEN; + SignalSSLConnected(); + OnWriteEvent(cricket_socket_); + return; + } +#endif // !defined(FEATURE_ENABLE_SSL) + state_ = buzz::AsyncSocket::STATE_OPEN; + SignalConnected(); +} + +buzz::AsyncSocket::State XmppSocket::state() { + return state_; +} + +buzz::AsyncSocket::Error XmppSocket::error() { + return buzz::AsyncSocket::ERROR_NONE; +} + +bool XmppSocket::Connect(const cricket::SocketAddress& addr) { + if (cricket_socket_->Connect(addr) < 0) { + return cricket_socket_->IsBlocking(); + } + return true; +} + +bool XmppSocket::Read(char * data, size_t len, size_t* len_read) { + int read = cricket_socket_->Recv(data, len); + if (read > 0) { + *len_read = (size_t)read; + return true; + } + return false; +} + +bool XmppSocket::Write(const char * data, size_t len) { + buffer_.WriteBytes(data, len); + OnWriteEvent(cricket_socket_); + return true; +} + +bool XmppSocket::Close() { + if (state_ != buzz::AsyncSocket::STATE_OPEN) + return false; + if (cricket_socket_->Close() == 0) { + state_ = buzz::AsyncSocket::STATE_CLOSED; + SignalClosed(); + return true; + } + return false; +} + +bool XmppSocket::StartTls(const std::string & domainname) { +#if defined(FEATURE_ENABLE_SSL) + if (!tls_) + return false; + cricket::SChannelAdapter * ssl = + static_cast(cricket_socket_); + ssl->set_ignore_bad_cert(true); + if (ssl->StartSSL(domainname.c_str(), false) != 0) + return false; + state_ = buzz::AsyncSocket::STATE_TLS_CONNECTING; + return true; +#else // !defined(FEATURE_ENABLE_SSL) + return false; +#endif // !defined(FEATURE_ENABLE_SSL) +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.h new file mode 100644 index 00000000..f6c53d7d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.h @@ -0,0 +1,63 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _XMPPSOCKET_H_ +#define _XMPPSOCKET_H_ + +#include "talk/base/asyncsocket.h" +#include "talk/base/bytebuffer.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/asyncsocket.h" + +extern cricket::AsyncSocket* cricket_socket_; + +class XmppSocket : public buzz::AsyncSocket, public sigslot::has_slots<> { +public: + XmppSocket(bool tls); + ~XmppSocket(); + + virtual buzz::AsyncSocket::State state(); + virtual buzz::AsyncSocket::Error error(); + + virtual bool Connect(const cricket::SocketAddress& addr); + virtual bool Read(char * data, size_t len, size_t* len_read); + virtual bool Write(const char * data, size_t len); + virtual bool Close(); + virtual bool StartTls(const std::string & domainname); + +private: + void OnReadEvent(cricket::AsyncSocket * socket); + void OnWriteEvent(cricket::AsyncSocket * socket); + void OnConnectEvent(cricket::AsyncSocket * socket); + + cricket::AsyncSocket * cricket_socket_; + buzz::AsyncSocket::State state_; + cricket::ByteBuffer buffer_; + bool tls_; +}; + +#endif // _XMPPSOCKET_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.cc new file mode 100644 index 00000000..7a1b2a13 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.cc @@ -0,0 +1,80 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/examples/login/xmppthread.h" +#include "talk/examples/login/xmppauth.h" + +namespace { + +const uint32 MSG_LOGIN = 1; +const uint32 MSG_DISCONNECT = 2; + +struct LoginData: public cricket::MessageData { + LoginData(const buzz::XmppClientSettings& s) : xcs(s) {} + virtual ~LoginData() {} + + buzz::XmppClientSettings xcs; +}; + +} // namespace + +XmppThread::XmppThread() { + pump_ = new XmppPump(this); +} + +XmppThread::~XmppThread() { + delete pump_; +} + +void XmppThread::Loop(int cms) { + Thread::Loop(cms); +} + +void XmppThread::Login(const buzz::XmppClientSettings& xcs) { + Post(this, MSG_LOGIN, new LoginData(xcs)); +} + +void XmppThread::Disconnect() { + Post(this, MSG_DISCONNECT); +} + +void XmppThread::OnStateChange(buzz::XmppEngine::State state) { +} + +void XmppThread::OnMessage(cricket::Message* pmsg) { + if (pmsg->message_id == MSG_LOGIN) { + assert(pmsg->pdata); + LoginData* data = reinterpret_cast(pmsg->pdata); + pump_->DoLogin(data->xcs, new XmppSocket(false), new XmppAuth()); + delete data; + } else if (pmsg->message_id == MSG_DISCONNECT) { + pump_->DoDisconnect(); + } else { + assert(false); + } +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.h new file mode 100644 index 00000000..533a2376 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.h @@ -0,0 +1,57 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _XMPPTHREAD_H_ +#define _XMPPTHREAD_H_ + +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/base/thread.h" +#include "talk/examples/login/xmpppump.h" +#include "talk/examples/login/xmppsocket.h" +#include + +class XmppThread: + public cricket::Thread, XmppPumpNotify, cricket::MessageHandler { +public: + XmppThread(); + ~XmppThread(); + + buzz::XmppClient* client() { return pump_->client(); } + + void Loop(int cms); + + void Login(const buzz::XmppClientSettings & xcs); + void Disconnect(); + +private: + XmppPump* pump_; + + void OnStateChange(buzz::XmppEngine::State state); + void OnMessage(cricket::Message* pmsg); +}; + +#endif // _XMPPTHREAD_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/Makefile.am new file mode 100644 index 00000000..c935a6bd --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=base client diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/Makefile.am new file mode 100644 index 00000000..6cc30f15 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/Makefile.am @@ -0,0 +1,47 @@ +## Does not compile with final +KDE_OPTIONS = nofinal + +libcricketp2pbase_la_SOURCES = stun.cc \ + port.cc \ + udpport.cc \ + tcpport.cc \ + helpers.cc \ + sessionmanager.cc \ + session.cc \ + p2psocket.cc \ + relayport.cc \ + stunrequest.cc \ + stunport.cc \ + socketmanager.cc + +noinst_HEADERS = candidate.h \ + portallocator.h \ + relayport.h \ + session.h \ + sessionmessage.h \ + stunport.h \ + tcpport.h \ + helpers.h \ + port.h \ + sessionid.h \ + socketmanager.h \ + stunrequest.h \ + udpport.h \ + p2psocket.h \ + sessiondescription.h \ + sessionmanager.h \ + stun.h \ + relayserver.h \ + stunserver.h + +AM_CPPFLAGS = -DPOSIX $(all_includes) -I$(srcdir)/../../.. + +bin_PROGRAMS = relayserver stunserver +relayserver_SOURCES = relayserver.cc relayserver_main.cc +relayserver_LDADD = ../../base/libcricketbase.la libcricketp2pbase.la -lpthread +stunserver_SOURCES = stunserver.cc stunserver_main.cc +stunserver_LDADD = ../../base/libcricketbase.la libcricketp2pbase.la -lpthread + +noinst_LTLIBRARIES = libcricketp2pbase.la + +DEFAULT_INCLUDES = -I$(srcdir)/../../.. diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/candidate.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/candidate.h new file mode 100644 index 00000000..c2f974b8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/candidate.h @@ -0,0 +1,118 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _CANDIDATE_H_ +#define _CANDIDATE_H_ + +#include +#include +#include "talk/base/socketaddress.h" + +namespace cricket { + +// Candidate for ICE based connection discovery. + +class Candidate { +public: + + const std::string & name() const { return name_; } + void set_name(const std::string & name) { name_ = name; } + + const std::string & protocol() const { return protocol_; } + void set_protocol(const std::string & protocol) { protocol_ = protocol; } + + const SocketAddress & address() const { return address_; } + void set_address(const SocketAddress & address) { address_ = address; } + + const float preference() const { return preference_; } + void set_preference(const float preference) { preference_ = preference; } + const std::string preference_str() const { + std::ostringstream ost; + ost << preference_; + return ost.str(); + } + void set_preference_str(const std::string & preference) { + std::istringstream ist(preference); + ist >> preference_; + } + + const std::string & username() const { return username_; } + void set_username(const std::string & username) { username_ = username; } + + const std::string & password() const { return password_; } + void set_password(const std::string & password) { password_ = password; } + + const std::string & type() const { return type_; } + void set_type(const std::string & type) { type_ = type; } + + const std::string & network_name() const { return network_name_; } + void set_network_name(const std::string & network_name) { + network_name_ = network_name; + } + + // Candidates in a new generation replace those in the old generation. + uint32 generation() const { return generation_; } + void set_generation(uint32 generation) { generation_ = generation; } + const std::string generation_str() const { + std::ostringstream ost; + ost << generation_; + return ost.str(); + } + void set_generation_str(const std::string& str) { + std::istringstream ist(str); + ist >> generation_; + } + + // Determines whether this candidate is equivalent to the given one. + bool IsEquivalent(const Candidate& c) const { + // We ignore the network name, since that is just debug information, and + // the preference, since that should be the same if the rest is (and it's + // a float so equality checking is always worrisome). + return (name_ == c.name_) && + (protocol_ == c.protocol_) && + (address_ == c.address_) && + (username_ == c.username_) && + (password_ == c.password_) && + (type_ == c.type_) && + (generation_ == c.generation_); + } + +private: + std::string name_; + std::string protocol_; + SocketAddress address_; + float preference_; + std::string username_; + std::string password_; + std::string type_; + std::string network_name_; + uint32 generation_; +}; + +} // namespace cricket + +#endif // _CANDIDATE_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.cc new file mode 100644 index 00000000..83e02a27 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.cc @@ -0,0 +1,129 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/p2p/base/helpers.h" +#include "talk/base/jtime.h" +#include +#include + +// TODO: Change this implementation to use OpenSSL's RAND_bytes. That will +// give cryptographically random values on all platforms. + +#ifdef WIN32 +#include +#include +#endif + +namespace cricket { + +static long g_seed = 1L; + +int GetRandom() { + return ((g_seed = g_seed * 214013L + 2531011L) >> 16) & 0x7fff; +} + +void SetRandomSeed(unsigned long seed) +{ + g_seed = (long)seed; +} + +static bool s_initrandom; + +void InitRandom(const char *client_unique, size_t len) { + s_initrandom = true; + + // Hash this string - unique per client + + uint32 hash = 0; + if (client_unique != NULL) { + for (int i = 0; i < (int)len; i++) + hash = ((hash << 2) + hash) + client_unique[i]; + } + + // Now initialize the seed against a high resolution + // counter + +#ifdef WIN32 + LARGE_INTEGER big; + QueryPerformanceCounter(&big); + SetRandomSeed(big.LowPart ^ hash); +#else + SetRandomSeed(Time() ^ hash); +#endif +} + +const char BASE64[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' +}; + +// Generates a random string of the given length. We generate base64 values so +// that they will be printable, though that's not necessary. + +std::string CreateRandomString(int len) { + // Random number generator should of been initialized! + assert(s_initrandom); + if (!s_initrandom) + InitRandom(0, 0); + + std::string str; + for (int i = 0; i < len; i++) +#if defined(_MSC_VER) && _MSC_VER < 1300 + str.insert(str.end(), BASE64[GetRandom() & 63]); +#else + str.push_back(BASE64[GetRandom() & 63]); +#endif + return str; +} + +uint32 CreateRandomId() { + uint8 b1 = (uint8)(GetRandom() & 255); + uint8 b2 = (uint8)(GetRandom() & 255); + uint8 b3 = (uint8)(GetRandom() & 255); + uint8 b4 = (uint8)(GetRandom() & 255); + return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24); +} + +bool IsBase64Char(char ch) { + return (('A' <= ch) && (ch <= 'Z')) || + (('a' <= ch) && (ch <= 'z')) || + (('0' <= ch) && (ch <= '9')) || + (ch == '+') || (ch == '/'); +} + +bool IsBase64Encoded(const std::string& str) { + for (size_t i = 0; i < str.size(); ++i) { + if (!IsBase64Char(str.at(i))) + return false; + } + return true; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.h new file mode 100644 index 00000000..1c8dfa7f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.h @@ -0,0 +1,51 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __HELPERS_H__ +#define __HELPERS_H__ + +#include "talk/base/basictypes.h" +#include + +namespace cricket { + +// srand initializer +void InitRandom(const char *client_unique, size_t len); + +// Generates a (cryptographically) random string of the given length. +std::string CreateRandomString(int length); + +// Generates a random id +uint32 CreateRandomId(); + +// Determines whether the given string consists entirely of valid base64 +// encoded characters. +bool IsBase64Encoded(const std::string& str); + +} // namespace cricket + +#endif // __HELPERS_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.cc new file mode 100644 index 00000000..eb53efeb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.cc @@ -0,0 +1,910 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Description of the P2PSocket class in P2PSocket.h +// +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include +#include +#include "talk/base/logging.h" +#include "talk/p2p/base/p2psocket.h" +#include +namespace { + +// messages for queuing up work for ourselves +const uint32 MSG_SORT = 1; +const uint32 MSG_PING = 2; +const uint32 MSG_ALLOCATE = 3; + +// When the socket is unwritable, we will use 10 Kbps (ignoring IP+UDP headers) +// for pinging. When the socket is writable, we will use only 1 Kbps because +// we don't want to degrade the quality on a modem. These numbers should work +// well on a 28.8K modem, which is the slowest connection on which the voice +// quality is reasonable at all. +static const uint32 PING_PACKET_SIZE = 60 * 8; +static const uint32 WRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 1000; // 480ms +static const uint32 UNWRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 10000;// 50ms + +// If there is a current writable connection, then we will also try hard to +// make sure it is pinged at this rate. +static const uint32 MAX_CURRENT_WRITABLE_DELAY = 900; // 2*WRITABLE_DELAY - bit + +// The minimum improvement in MOS that justifies a switch. +static const double kMinImprovement = 10; + +// Amount of time that we wait when *losing* writability before we try doing +// another allocation. +static const int kAllocateDelay = 1 * 1000; // 1 second + +// We will try creating a new allocator from scratch after a delay of this +// length without becoming writable (or timing out). +static const int kAllocatePeriod = 20 * 1000; // 20 seconds + +cricket::Port::CandidateOrigin GetOrigin(cricket::Port* port, + cricket::Port* origin_port) { + if (!origin_port) + return cricket::Port::ORIGIN_MESSAGE; + else if (port == origin_port) + return cricket::Port::ORIGIN_THIS_PORT; + else + return cricket::Port::ORIGIN_OTHER_PORT; +} + +// Compares two connections based only on static information about them. +int CompareConnectionCandidates(cricket::Connection* a, + cricket::Connection* b) { + // Combine local and remote preferences + assert(a->local_candidate().preference() == a->port()->preference()); + assert(b->local_candidate().preference() == b->port()->preference()); + double a_pref = a->local_candidate().preference() + * a->remote_candidate().preference(); + double b_pref = b->local_candidate().preference() + * b->remote_candidate().preference(); + + // Now check combined preferences. Lower values get sorted last. + if (a_pref > b_pref) + return 1; + if (a_pref < b_pref) + return -1; + + return 0; +} + +// Compare two connections based on their writability and static preferences. +int CompareConnections(cricket::Connection *a, cricket::Connection *b) { + // Sort based on write-state. Better states have lower values. + if (a->write_state() < b->write_state()) + return 1; + if (a->write_state() > b->write_state()) + return -1; + + // Compare the candidate information. + return CompareConnectionCandidates(a, b); +} + +// Wraps the comparison connection into a less than operator that puts higher +// priority writable connections first. +class ConnectionCompare { +public: + bool operator()(const cricket::Connection *ca, + const cricket::Connection *cb) { + cricket::Connection* a = const_cast(ca); + cricket::Connection* b = const_cast(cb); + + // Compare first on writability and static preferences. + int cmp = CompareConnections(a, b); + if (cmp > 0) + return true; + if (cmp < 0) + return false; + + // Otherwise, sort based on latency estimate. + return a->rtt() < b->rtt(); + + // Should we bother checking for the last connection that last received + // data? It would help rendezvous on the connection that is also receiving + // packets. + // + // TODO: Yes we should definitely do this. The TCP protocol gains + // efficiency by being used bidirectionally, as opposed to two separate + // unidirectional streams. This test should probably occur before + // comparison of local prefs (assuming combined prefs are the same). We + // need to be careful though, not to bounce back and forth with both sides + // trying to rendevous with the other. + } +}; + +// Determines whether we should switch between two connections, based first on +// static preferences and then (if those are equal) on latency estimates. +bool ShouldSwitch(cricket::Connection* a_conn, cricket::Connection* b_conn) { + if (a_conn == b_conn) + return false; + + if ((a_conn == NULL) || (b_conn == NULL)) // don't think the latter should happen + return true; + + int prefs_cmp = CompareConnections(a_conn, b_conn); + if (prefs_cmp < 0) + return true; + if (prefs_cmp > 0) + return false; + + return b_conn->rtt() <= a_conn->rtt() + kMinImprovement; +} + +} // unnamed namespace + +namespace cricket { + +P2PSocket::P2PSocket(const std::string &name, PortAllocator *allocator) +: worker_thread_(Thread::Current()), name_(name), allocator_(allocator), + error_(0), state_(STATE_CONNECTING), waiting_for_signaling_(false), + best_connection_(NULL), pinging_started_(false), sort_dirty_(false), + was_writable_(false), was_timed_out_(true) { +} + +P2PSocket::~P2PSocket() { + assert(worker_thread_ == Thread::Current()); + + thread()->Clear(this); + + for (uint32 i = 0; i < allocator_sessions_.size(); ++i) + delete allocator_sessions_[i]; +} + +// Add the allocator session to our list so that we know which sessions +// are still active. +void P2PSocket::AddAllocatorSession(PortAllocatorSession* session) { + session->set_generation(static_cast(allocator_sessions_.size())); + allocator_sessions_.push_back(session); + + // We now only want to apply new candidates that we receive to the ports + // created by this new session because these are replacing those of the + // previous sessions. + ports_.clear(); + + session->SignalPortReady.connect(this, &P2PSocket::OnPortReady); + session->SignalCandidatesReady.connect(this, &P2PSocket::OnCandidatesReady); + session->GetInitialPorts(); + if (pinging_started_) + session->StartGetAllPorts(); +} + +// Go into the state of processing candidates, and running in general +void P2PSocket::StartProcessingCandidates() { + assert(worker_thread_ == Thread::Current()); + + // Kick off an allocator session + OnAllocate(); + + // Start pinging as the ports come in. + thread()->Post(this, MSG_PING); +} + +// Reset the socket, clear up any previous allocations and start over +void P2PSocket::Reset() { + assert(worker_thread_ == Thread::Current()); + + // Get rid of all the old allocators. This should clean up everything. + for (uint32 i = 0; i < allocator_sessions_.size(); ++i) + delete allocator_sessions_[i]; + + allocator_sessions_.clear(); + ports_.clear(); + connections_.clear(); + best_connection_ = NULL; + + // Forget about all of the candidates we got before. + remote_candidates_.clear(); + + // Revert to the connecting state. + set_state(STATE_CONNECTING); + + // Reinitialize the rest of our state. + waiting_for_signaling_ = false; + pinging_started_ = false; + sort_dirty_ = false; + was_writable_ = false; + was_timed_out_ = true; + + // Start a new allocator. + OnAllocate(); + + // Start pinging as the ports come in. + thread()->Clear(this); + thread()->Post(this, MSG_PING); +} + +// A new port is available, attempt to make connections for it +void P2PSocket::OnPortReady(PortAllocatorSession *session, Port* port) { + assert(worker_thread_ == Thread::Current()); + + // Set in-effect options on the new port + for (OptionMap::const_iterator it = options_.begin(); it != options_.end(); ++it) { + int val = port->SetOption(it->first, it->second); + if (val < 0) { + LOG(WARNING) << "SetOption(" << it->first << ", " << it->second << ") failed: " << port->GetError(); + } + } + + // Remember the ports and candidates, and signal that candidates are ready. + // The session will handle this, and send an initiate/accept/modify message + // if one is pending. + + ports_.push_back(port); + port->SignalUnknownAddress.connect(this, &P2PSocket::OnUnknownAddress); + port->SignalDestroyed.connect(this, &P2PSocket::OnPortDestroyed); + + // Attempt to create a connection from this new port to all of the remote + // candidates that we were given so far. + + std::vector::iterator iter; + for (iter = remote_candidates_.begin(); iter != remote_candidates_.end(); + ++iter) + CreateConnection(port, *iter, iter->origin_port(), false); + + SortConnections(); +} + +// A new candidate is available, let listeners know +void P2PSocket::OnCandidatesReady(PortAllocatorSession *session, + const std::vector& candidates) { + SignalCandidatesReady(this, candidates); +} + +// Handle stun packets +void P2PSocket::OnUnknownAddress(Port *port, + const SocketAddress &address, + StunMessage *stun_msg, + const std::string &remote_username) { + assert(worker_thread_ == Thread::Current()); + + // Port has received a valid stun packet from an address that no Connection + // is currently available for. See if the remote user name is in the remote + // candidate list. If it isn't return error to the stun request. + + const Candidate *candidate = NULL; + std::vector::iterator it; + for (it = remote_candidates_.begin(); it != remote_candidates_.end(); ++it) { + if ((*it).username() == remote_username) { + candidate = &(*it); + break; + } + } + if (candidate == NULL) { + // Don't know about this username, the request is bogus + // This sometimes happens if a binding response comes in before the ACCEPT + // message. It is totally valid; the retry state machine will try again. + + port->SendBindingErrorResponse(stun_msg, address, + STUN_ERROR_STALE_CREDENTIALS, STUN_ERROR_REASON_STALE_CREDENTIALS); + delete stun_msg; + return; + } + + // Check for connectivity to this address. Create connections + // to this address across all local ports. First, add this as a new remote + // address + + Candidate new_remote_candidate = *candidate; + new_remote_candidate.set_address(address); + //new_remote_candidate.set_protocol(port->protocol()); + + // This remote username exists. Now create connections using this candidate, + // and resort + + if (CreateConnections(new_remote_candidate, port, true)) { + // Send the pinger a successful stun response. + port->SendBindingResponse(stun_msg, address); + + // Update the list of connections since we just added another. We do this + // after sending the response since it could (in principle) delete the + // connection in question. + SortConnections(); + } else { + // Hopefully this won't occur, because changing a destination address + // shouldn't cause a new connection to fail + assert(false); + port->SendBindingErrorResponse(stun_msg, address, STUN_ERROR_SERVER_ERROR, + STUN_ERROR_REASON_SERVER_ERROR); + } + + delete stun_msg; +} + +// We received a candidate from the other side, make connections so we +// can try to use these remote candidates with our local candidates. +void P2PSocket::AddRemoteCandidates( + const std::vector &remote_candidates) { + assert(worker_thread_ == Thread::Current()); + + // The remote candidates have come in. Remember them and start to establish + // connections + + std::vector::const_iterator it; + for (it = remote_candidates.begin(); it != remote_candidates.end(); ++it) + CreateConnections(*it, NULL, false); + + // Resort the connections + + SortConnections(); +} + +// Creates connections from all of the ports that we care about to the given +// remote candidate. The return value is true iff we created a connection from +// the origin port. +bool P2PSocket::CreateConnections(const Candidate &remote_candidate, + Port* origin_port, + bool readable) { + assert(worker_thread_ == Thread::Current()); + + // Add a new connection for this candidate to every port that allows such a + // connection (i.e., if they have compatible protocols) and that does not + // already have a connection to an equivalent candidate. We must be careful + // to make sure that the origin port is included, even if it was pruned, + // since that may be the only port that can create this connection. + + bool created = false; + + std::vector::reverse_iterator it; + for (it = ports_.rbegin(); it != ports_.rend(); ++it) { + if (CreateConnection(*it, remote_candidate, origin_port, readable)) { + if (*it == origin_port) + created = true; + } + } + + if ((origin_port != NULL) && + find(ports_.begin(), ports_.end(), origin_port) == ports_.end()) { + if (CreateConnection(origin_port, remote_candidate, origin_port, readable)) + created = true; + } + + // Remember this remote candidate so that we can add it to future ports. + RememberRemoteCandidate(remote_candidate, origin_port); + + return created; +} + +// Setup a connection object for the local and remote candidate combination. +// And then listen to connection object for changes. +bool P2PSocket::CreateConnection(Port* port, + const Candidate& remote_candidate, + Port* origin_port, + bool readable) { + // Look for an existing connection with this remote address. If one is not + // found, then we can create a new connection for this address. + Connection* connection = port->GetConnection(remote_candidate.address()); + if (connection != NULL) { + // It is not legal to try to change any of the parameters of an existing + // connection; however, the other side can send a duplicate candidate. + if (!remote_candidate.IsEquivalent(connection->remote_candidate())) { + LOG(INFO) << "Attempt to change a remote candidate"; + return false; + } + } else { + Port::CandidateOrigin origin = GetOrigin(port, origin_port); + connection = port->CreateConnection(remote_candidate, origin); + if (!connection) + return false; + + connections_.push_back(connection); + connection->SignalReadPacket.connect(this, &P2PSocket::OnReadPacket); + connection->SignalStateChange.connect(this, &P2PSocket::OnConnectionStateChange); + connection->SignalDestroyed.connect(this, &P2PSocket::OnConnectionDestroyed); + } + + // If we are readable, it is because we are creating this in response to a + // ping from the other side. This will cause the state to become readable. + if (readable) + connection->ReceivedPing(); + + return true; +} + +// Maintain our remote candidate list, adding this new remote one. +void P2PSocket::RememberRemoteCandidate(const Candidate& remote_candidate, + Port* origin_port) { + // Remove any candidates whose generation is older than this one. The + // presence of a new generation indicates that the old ones are not useful. + uint32 i = 0; + while (i < remote_candidates_.size()) { + if (remote_candidates_[i].generation() < remote_candidate.generation()) { + remote_candidates_.erase(remote_candidates_.begin() + i); + LOG(INFO) << "Pruning candidate from old generation: " + << remote_candidates_[i].address().ToString(); + + } else { + i += 1; + } + } + + // Make sure this candidate is not a duplicate. + for (uint32 i = 0; i < remote_candidates_.size(); ++i) { + if (remote_candidates_[i].IsEquivalent(remote_candidate)) { + LOG(INFO) << "Duplicate candidate: " + << remote_candidate.address().ToString(); + return; + } + } + + // Try this candidate for all future ports. + remote_candidates_.push_back(RemoteCandidate(remote_candidate, origin_port)); + + // We have some candidates from the other side, we are now serious about + // this connection. Let's do the StartGetAllPorts thing. + if (!pinging_started_) { + pinging_started_ = true; + for (size_t i = 0; i < allocator_sessions_.size(); ++i) { + if (!allocator_sessions_[i]->IsGettingAllPorts()) + allocator_sessions_[i]->StartGetAllPorts(); + } + } +} + +// Send data to the other side, using our best connection +int P2PSocket::Send(const char *data, size_t len) { + // This can get called on any thread that is convenient to write from! + if (best_connection_ == NULL) { + error_ = EWOULDBLOCK; + return SOCKET_ERROR; + } + int sent = best_connection_->Send(data, len); + if (sent <= 0) { + assert(sent < 0); + error_ = best_connection_->GetError(); + } + return sent; +} + +// Monitor connection states +void P2PSocket::UpdateConnectionStates() { + uint32 now = Time(); + + // We need to copy the list of connections since some may delete themselves + // when we call UpdateState. + for (uint32 i = 0; i < connections_.size(); ++i) + connections_[i]->UpdateState(now); +} + +// Prepare for best candidate sorting +void P2PSocket::RequestSort() { + if (!sort_dirty_) { + worker_thread_->Post(this, MSG_SORT); + sort_dirty_ = true; + } +} + +// Sort the available connections to find the best one. We also monitor +// the number of available connections and the current state so that we +// can possibly kick off more allocators (for more connections). +void P2PSocket::SortConnections() { + assert(worker_thread_ == Thread::Current()); + + // Make sure the connection states are up-to-date since this affects how they + // will be sorted. + UpdateConnectionStates(); + + // Any changes after this point will require a re-sort. + sort_dirty_ = false; + + // Get a list of the networks that we are using. + std::set networks; + for (uint32 i = 0; i < connections_.size(); ++i) + networks.insert(connections_[i]->port()->network()); + + // Find the best alternative connection by sorting. It is important to note + // that amongst equal preference, writable connections, this will choose the + // one whose estimated latency is lowest. So it is the only one that we + // need to consider switching to. + + ConnectionCompare cmp; + std::stable_sort(connections_.begin(), connections_.end(), cmp); + Connection* top_connection = NULL; + if (connections_.size() > 0) + top_connection = connections_[0]; + + // If necessary, switch to the new choice. + if (ShouldSwitch(best_connection_, top_connection)) + SwitchBestConnectionTo(top_connection); + + // We can prune any connection for which there is a writable connection on + // the same network with better or equal prefences. We leave those with + // better preference just in case they become writable later (at which point, + // we would prune out the current best connection). We leave connections on + // other networks because they may not be using the same resources and they + // may represent very distinct paths over which we can switch. + std::set::iterator network; + for (network = networks.begin(); network != networks.end(); ++network) { + Connection* primier = GetBestConnectionOnNetwork(*network); + if (!primier || (primier->write_state() != Connection::STATE_WRITABLE)) + continue; + + for (uint32 i = 0; i < connections_.size(); ++i) { + if ((connections_[i] != primier) && + (connections_[i]->port()->network() == *network) && + (CompareConnectionCandidates(primier, connections_[i]) >= 0)) { + connections_[i]->Prune(); + } + } + } + + // Count the number of connections in the various states. + + int writable = 0; + int write_connect = 0; + int write_timeout = 0; + + for (uint32 i = 0; i < connections_.size(); ++i) { + switch (connections_[i]->write_state()) { + case Connection::STATE_WRITABLE: + ++writable; + break; + case Connection::STATE_WRITE_CONNECT: + ++write_connect; + break; + case Connection::STATE_WRITE_TIMEOUT: + ++write_timeout; + break; + default: + assert(false); + } + } + + if (writable > 0) { + HandleWritable(); + } else if (write_connect > 0) { + HandleNotWritable(); + } else { + HandleAllTimedOut(); + } + + // Notify of connection state change + SignalConnectionMonitor(this); +} + +// Track the best connection, and let listeners know +void P2PSocket::SwitchBestConnectionTo(Connection* conn) { + best_connection_ = conn; + if (best_connection_) + SignalConnectionChanged(this, + best_connection_->remote_candidate().address()); +} + +// We checked the status of our connections and we had at least one that +// was writable, go into the writable state. +void P2PSocket::HandleWritable() { + // + // One or more connections writable! + // + if (state_ != STATE_WRITABLE) { + for (uint32 i = 0; i < allocator_sessions_.size(); ++i) { + if (allocator_sessions_[i]->IsGettingAllPorts()) { + allocator_sessions_[i]->StopGetAllPorts(); + } + } + + // Stop further allocations. + thread()->Clear(this, MSG_ALLOCATE); + } + + // We're writable, obviously we aren't timed out + was_writable_ = true; + was_timed_out_ = false; + set_state(STATE_WRITABLE); +} + +// We checked the status of our connections and we didn't have any that +// were writable, go into the connecting state (kick off a new allocator +// session). +void P2PSocket::HandleNotWritable() { + // + // No connections are writable but not timed out! + // + if (was_writable_) { + // If we were writable, let's kick off an allocator session immediately + was_writable_ = false; + OnAllocate(); + } + + // We were connecting, obviously not ALL timed out. + was_timed_out_ = false; + set_state(STATE_CONNECTING); +} + +// We checked the status of our connections and not only weren't they writable +// but they were also timed out, we really need a new allocator. +void P2PSocket::HandleAllTimedOut() { + // + // No connections... all are timed out! + // + if (!was_timed_out_) { + // We weren't timed out before, so kick off an allocator now (we'll still + // be in the fully timed out state until the allocator actually gives back + // new ports) + OnAllocate(); + } + + // NOTE: we start was_timed_out_ in the true state so that we don't get + // another allocator created WHILE we are in the process of building up + // our first allocator. + was_timed_out_ = true; + was_writable_ = false; + set_state(STATE_CONNECTING); +} + +// If we have a best connection, return it, otherwise return top one in the +// list (later we will mark it best). +Connection* P2PSocket::GetBestConnectionOnNetwork(Network* network) { + // If the best connection is on this network, then it wins. + if (best_connection_ && (best_connection_->port()->network() == network)) + return best_connection_; + + // Otherwise, we return the top-most in sorted order. + for (uint32 i = 0; i < connections_.size(); ++i) { + if (connections_[i]->port()->network() == network) + return connections_[i]; + } + + return NULL; +} + +// Handle any queued up requests +void P2PSocket::OnMessage(Message *pmsg) { + if (pmsg->message_id == MSG_SORT) + OnSort(); + else if (pmsg->message_id == MSG_PING) + OnPing(); + else if (pmsg->message_id == MSG_ALLOCATE) + OnAllocate(); + else + assert(false); +} + +// Handle queued up sort request +void P2PSocket::OnSort() { + // Resort the connections based on the new statistics. + SortConnections(); +} + +// Handle queued up ping request +void P2PSocket::OnPing() { + // Make sure the states of the connections are up-to-date (since this affects + // which ones are pingable). + UpdateConnectionStates(); + + // Find the oldest pingable connection and have it do a ping. + Connection* conn = FindNextPingableConnection(); + if (conn) + conn->Ping(Time()); + + // Post ourselves a message to perform the next ping. + uint32 delay = (state_ == STATE_WRITABLE) ? WRITABLE_DELAY : UNWRITABLE_DELAY; + thread()->PostDelayed(delay, this, MSG_PING); +} + +// Is the connection in a state for us to even consider pinging the other side? +bool P2PSocket::IsPingable(Connection* conn) { + // An unconnected connection cannot be written to at all, so pinging is out + // of the question. + if (!conn->connected()) + return false; + + if (state_ == STATE_WRITABLE) { + // If we are writable, then we only want to ping connections that could be + // better than this one, i.e., the ones that were not pruned. + return (conn->write_state() != Connection::STATE_WRITE_TIMEOUT); + } else { + // If we are not writable, then we need to try everything that might work. + // This includes both connections that do not have write timeout as well as + // ones that do not have read timeout. A connection could be readable but + // be in write-timeout if we pruned it before. Since the other side is + // still pinging it, it very well might still work. + return (conn->write_state() != Connection::STATE_WRITE_TIMEOUT) || + (conn->read_state() != Connection::STATE_READ_TIMEOUT); + } +} + +// Returns the next pingable connection to ping. This will be the oldest +// pingable connection unless we have a writable connection that is past the +// maximum acceptable ping delay. +Connection* P2PSocket::FindNextPingableConnection() { + uint32 now = Time(); + if (best_connection_ && + (best_connection_->write_state() == Connection::STATE_WRITABLE) && + (best_connection_->last_ping_sent() + + MAX_CURRENT_WRITABLE_DELAY <= now)) { + return best_connection_; + } + + Connection* oldest_conn = NULL; + uint32 oldest_time = 0xFFFFFFFF; + for (uint32 i = 0; i < connections_.size(); ++i) { + if (IsPingable(connections_[i])) { + if (connections_[i]->last_ping_sent() < oldest_time) { + oldest_time = connections_[i]->last_ping_sent(); + oldest_conn = connections_[i]; + } + } + } + return oldest_conn; +} + +// return the number of "pingable" connections +uint32 P2PSocket::NumPingableConnections() { + uint32 count = 0; + for (uint32 i = 0; i < connections_.size(); ++i) { + if (IsPingable(connections_[i])) + count += 1; + } + return count; +} + +// When a connection's state changes, we need to figure out who to use as +// the best connection again. It could have become usable, or become unusable. +void P2PSocket::OnConnectionStateChange(Connection *connection) { + assert(worker_thread_ == Thread::Current()); + + // We have to unroll the stack before doing this because we may be changing + // the state of connections while sorting. + RequestSort(); +} + +// When a connection is removed, edit it out, and then update our best +// connection. +void P2PSocket::OnConnectionDestroyed(Connection *connection) { + assert(worker_thread_ == Thread::Current()); + + // Remove this connection from the list. + std::vector::iterator iter = + find(connections_.begin(), connections_.end(), connection); + assert(iter != connections_.end()); + connections_.erase(iter); + + LOG(INFO) << "Removed connection from p2p socket: " + << static_cast(connections_.size()) << " remaining"; + + // If this is currently the best connection, then we need to pick a new one. + // The call to SortConnections will pick a new one. It looks at the current + // best connection in order to avoid switching between fairly similar ones. + // Since this connection is no longer an option, we can just set best to NULL + // and re-choose a best assuming that there was no best connection. + if (best_connection_ == connection) { + SwitchBestConnectionTo(NULL); + RequestSort(); + } +} + +// When a port is destroyed remove it from our list of ports to use for +// connection attempts. +void P2PSocket::OnPortDestroyed(Port* port) { + assert(worker_thread_ == Thread::Current()); + + // Remove this port from the list (if we didn't drop it already). + std::vector::iterator iter = find(ports_.begin(), ports_.end(), port); + if (iter != ports_.end()) + ports_.erase(iter); + + LOG(INFO) << "Removed port from p2p socket: " + << static_cast(ports_.size()) << " remaining"; +} + +// We data is available, let listeners know +void P2PSocket::OnReadPacket(Connection *connection, + const char *data, size_t len) { + assert(worker_thread_ == Thread::Current()); + + // Let the client know of an incoming packet + + SignalReadPacket(this, data, len); +} + +// return socket name +const std::string &P2PSocket::name() const { + return name_; +} + +// return socket error value +int P2PSocket::GetError() { + return error_; +} + +// return a reference to the list of connections +const std::vector& P2PSocket::connections() { + return connections_; +} + +// Set options on ourselves is simply setting options on all of our available +// port objects. +int P2PSocket::SetOption(Socket::Option opt, int value) { + OptionMap::iterator it = options_.find(opt); + if (it == options_.end()) { + options_.insert(std::make_pair(opt, value)); + } else if (it->second == value) { + return 0; + } else { + it->second = value; + } + + for (uint32 i = 0; i < ports_.size(); ++i) { + int val = ports_[i]->SetOption(opt, value); + if (val < 0) { + // Because this also occurs deferred, probably no point in reporting an error + LOG(WARNING) << "SetOption(" << opt << ", " << value << ") failed: " << ports_[i]->GetError(); + } + } + return 0; +} + +// returns the current state +P2PSocket::State P2PSocket::state() { + return state_; +} + +// Set the current state, and let listeners know when it changes +void P2PSocket::set_state(P2PSocket::State state) { + assert(worker_thread_ == Thread::Current()); + if (state != state_) { + state_ = state; + SignalState(this, state); + } +} + +// Time for a new allocator, lets make sure we have a signalling channel +// to communicate candidates through first. +void P2PSocket::OnAllocate() { + // Allocation timer went off + waiting_for_signaling_ = true; + SignalRequestSignaling(); +} + +// When the signalling channel is ready, we can really kick off the allocator +void P2PSocket::OnSignalingReady() { + if (waiting_for_signaling_) { + waiting_for_signaling_ = false; + AddAllocatorSession(allocator_->CreateSession(name_)); + thread()->PostDelayed(kAllocatePeriod, this, MSG_ALLOCATE); + } +} + +// return the current best connection writable state. +bool P2PSocket::writable() { + assert(worker_thread_ == Thread::Current()); + + if (best_connection_ == NULL) + return false; + return best_connection_->write_state() == Connection::STATE_WRITABLE; +} + +// return the worker thread +Thread *P2PSocket::thread() { + return worker_thread_; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.h new file mode 100644 index 00000000..32eb1f6d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.h @@ -0,0 +1,164 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// P2PSocket wraps up the state management of the connection between two +// P2P clients. Clients have candidate ports for connecting, and connections +// which are combinations of candidates from each end (Alice and Bob each +// have candidates, one candidate from Alice and one candidate from Bob are +// used to make a connection, repeat to make many connections). +// +// When all of the available connections become invalid (non-writable), we +// kick off a process of determining more candidates and more connections. +// +#ifndef _CRICKET_P2P_BASE_P2PSOCKET_H_ +#define _CRICKET_P2P_BASE_P2PSOCKET_H_ + +#include +#include +#include "talk/base/sigslot.h" +#include "talk/p2p/base/candidate.h" +#include "talk/p2p/base/port.h" +#include "talk/p2p/base/portallocator.h" + +namespace cricket { + +// Adds the port on which the candidate originated. +class RemoteCandidate: public Candidate { + public: + RemoteCandidate(const Candidate& c, Port* origin_port) + : Candidate(c), origin_port_(origin_port) {} + + Port* origin_port() { return origin_port_; } + + private: + Port* origin_port_; +}; + +// P2PSocket manages the candidates and connection process to keep two P2P +// clients connected to each other. +class P2PSocket : public MessageHandler, public sigslot::has_slots<> { + public: + enum State { + STATE_CONNECTING = 0, // establishing writability + STATE_WRITABLE, // connected - ready for writing + }; + + P2PSocket(const std::string &name, PortAllocator *allocator); + virtual ~P2PSocket(); + + // Typically SocketManager calls these + + const std::string &name() const; + void StartProcessingCandidates(); + void AddRemoteCandidates(const std::vector &remote_candidates); + void OnSignalingReady(); + void Reset(); + + // Typically the Session Client calls these + + int Send(const char *data, size_t len); + int SetOption(Socket::Option opt, int value); + int GetError(); + + State state(); + bool writable(); + const std::vector& connections(); + Connection* best_connection() { return best_connection_; } + Thread *thread(); + + sigslot::signal2 SignalState; + sigslot::signal0<> SignalRequestSignaling; + sigslot::signal3 SignalReadPacket; + sigslot::signal2 SignalConnectionChanged; + sigslot::signal2&> + SignalCandidatesReady; + sigslot::signal1 SignalConnectionMonitor; + + // Handler for internal messages. + virtual void OnMessage(Message *pmsg); + + private: + void set_state(State state); + void UpdateConnectionStates(); + void RequestSort(); + void SortConnections(); + void SwitchBestConnectionTo(Connection* conn); + void HandleWritable(); + void HandleNotWritable(); + void HandleAllTimedOut(); + Connection* GetBestConnectionOnNetwork(Network* network); + bool CreateConnections(const Candidate &remote_candidate, Port* origin_port, + bool readable); + bool CreateConnection(Port* port, const Candidate& remote_candidate, + Port* origin_port, bool readable); + void RememberRemoteCandidate(const Candidate& remote_candidate, + Port* origin_port); + void OnUnknownAddress(Port *port, const SocketAddress &addr, + StunMessage *stun_msg, + const std::string &remote_username); + void OnPortReady(PortAllocatorSession *session, Port* port); + void OnCandidatesReady(PortAllocatorSession *session, + const std::vector& candidates); + void OnConnectionStateChange(Connection *connection); + void OnConnectionDestroyed(Connection *connection); + void OnPortDestroyed(Port* port); + void OnAllocate(); + void OnReadPacket(Connection *connection, const char *data, size_t len); + void OnSort(); + void OnPing(); + bool IsPingable(Connection* conn); + Connection* FindNextPingableConnection(); + uint32 NumPingableConnections(); + PortAllocatorSession* allocator_session() { + return allocator_sessions_.back(); + } + void AddAllocatorSession(PortAllocatorSession* session); + + Thread *worker_thread_; + State state_; + bool waiting_for_signaling_; + int error_; + std::string name_; + PortAllocator *allocator_; + std::vector allocator_sessions_; + std::vector ports_; + std::vector connections_; + Connection *best_connection_; + std::vector remote_candidates_; + bool pinging_started_; // indicates whether StartGetAllCandidates has been called + bool sort_dirty_; // indicates whether another sort is needed right now + bool was_writable_; + bool was_timed_out_; + typedef std::map OptionMap; + OptionMap options_; + + DISALLOW_EVIL_CONSTRUCTORS(P2PSocket); +}; + +} // namespace cricket + +#endif // _CRICKET_P2P_BASE_P2PSOCKET_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.cc new file mode 100644 index 00000000..14549b5b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.cc @@ -0,0 +1,869 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/logging.h" +#include "talk/base/asyncudpsocket.h" +#include "talk/base/asynctcpsocket.h" +#include "talk/base/socketadapters.h" +#include "talk/p2p/base/port.h" +#include "talk/p2p/base/helpers.h" +#include "talk/base/scoped_ptr.h" +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::memcmp; +} +#endif + +namespace { + +// The length of time we wait before timing out readability on a connection. +const uint32 CONNECTION_READ_TIMEOUT = 30 * 1000; // 30 seconds + +// The length of time we wait before timing out writability on a connection. +const uint32 CONNECTION_WRITE_TIMEOUT = 15 * 1000; // 15 seconds + +// The length of time we wait before we become unwritable. +const uint32 CONNECTION_WRITE_CONNECT_TIMEOUT = 5 * 1000; // 5 seconds + +// The number of pings that must fail to respond before we become unwritable. +const uint32 CONNECTION_WRITE_CONNECT_FAILURES = 5; + +// This is the length of time that we wait for a ping response to come back. +const int CONNECTION_RESPONSE_TIMEOUT = 5 * 1000; // 5 seconds + +// Determines whether we have seen at least the given maximum number of +// pings fail to have a response. +inline bool TooManyFailures( + const std::vector& pings_since_last_response, + uint32 maximum_failures, + uint32 rtt_estimate, + uint32 now) { + + // If we haven't sent that many pings, then we can't have failed that many. + if (pings_since_last_response.size() < maximum_failures) + return false; + + // Check if the window in which we would expect a response to the ping has + // already elapsed. + return pings_since_last_response[maximum_failures - 1] + rtt_estimate < now; +} + +// Determines whether we have gone too long without seeing any response. +inline bool TooLongWithoutResponse( + const std::vector& pings_since_last_response, + uint32 maximum_time, + uint32 now) { + + if (pings_since_last_response.size() == 0) + return false; + + return pings_since_last_response[0] + maximum_time < now; +} + +// We will restrict RTT estimates (when used for determining state) to be +// within a reasonable range. +const uint32 MINIMUM_RTT = 100; // 0.1 seconds +const uint32 MAXIMUM_RTT = 3000; // 3 seconds + +// When we don't have any RTT data, we have to pick something reasonable. We +// use a large value just in case the connection is really slow. +const uint32 DEFAULT_RTT = MAXIMUM_RTT; + +// Computes our estimate of the RTT given the current estimate and the number +// of data points on which it is based. +inline uint32 ConservativeRTTEstimate(uint32 rtt, uint32 rtt_data_points) { + if (rtt_data_points == 0) + return DEFAULT_RTT; + else + return cricket::_max(MINIMUM_RTT, cricket::_min(MAXIMUM_RTT, 2 * rtt)); +} + +// Weighting of the old rtt value to new data. +const int RTT_RATIO = 3; // 3 : 1 + +// The delay before we begin checking if this port is useless. +const int kPortTimeoutDelay = 30 * 1000; // 30 seconds + +const uint32 MSG_CHECKTIMEOUT = 1; +const uint32 MSG_DELETE = 1; + +} + +namespace cricket { + +static const char * const PROTO_NAMES[PROTO_LAST+1] = { "udp", "tcp", "ssltcp" }; + +const char * ProtoToString(ProtocolType proto) { + return PROTO_NAMES[proto]; +} + +bool StringToProto(const char * value, ProtocolType& proto) { + for (size_t i=0; i<=PROTO_LAST; ++i) { + if (strcmp(PROTO_NAMES[i], value) == 0) { + proto = static_cast(i); + return true; + } + } + return false; +} + +ProxyInfo Port::proxy_; + +Port::Port(Thread* thread, const std::string& type, SocketFactory* factory, + Network* network) + : thread_(thread), factory_(factory), type_(type), network_(network), + preference_(-1), lifetime_(LT_PRESTART) { + + if (factory_ == NULL) + factory_ = thread_->socketserver(); + + set_username_fragment(CreateRandomString(16)); + set_password(CreateRandomString(16)); +} + +Port::~Port() { + // Delete all of the remaining connections. We copy the list up front + // because each deletion will cause it to be modified. + + std::vector list; + + AddressMap::iterator iter = connections_.begin(); + while (iter != connections_.end()) { + list.push_back(iter->second); + ++iter; + } + + for (uint32 i = 0; i < list.size(); i++) + delete list[i]; +} + +Connection* Port::GetConnection(const SocketAddress& remote_addr) { + AddressMap::const_iterator iter = connections_.find(remote_addr); + if (iter != connections_.end()) + return iter->second; + else + return NULL; +} + +void Port::set_username_fragment(const std::string& username_fragment) { + username_frag_ = username_fragment; +} + +void Port::set_password(const std::string& password) { + password_ = password; +} + +void Port::add_address(const SocketAddress& address, const std::string& protocol, bool final) { + Candidate c; + c.set_name(name_); + c.set_type(type_); + c.set_protocol(protocol); + c.set_address(address); + c.set_preference(preference_); + c.set_username(username_frag_); + c.set_password(password_); + c.set_network_name(network_->name()); + c.set_generation(generation_); + candidates_.push_back(c); + + if (final) + SignalAddressReady(this); +} + +void Port::AddConnection(Connection* conn) { + connections_[conn->remote_candidate().address()] = conn; + conn->SignalDestroyed.connect(this, &Port::OnConnectionDestroyed); + SignalConnectionCreated(this, conn); +} + +void Port::OnReadPacket( + const char* data, size_t size, const SocketAddress& addr) { + + // If this is an authenticated STUN request, then signal unknown address and + // send back a proper binding response. + StunMessage* msg; + std::string remote_username; + if (!GetStunMessage(data, size, addr, msg, remote_username)) { + LOG(LERROR) << "Received non-STUN packet from unknown address: " + << addr.ToString(); + } else if (!msg) { + // STUN message handled already + } else if (msg->type() == STUN_BINDING_REQUEST) { + SignalUnknownAddress(this, addr, msg, remote_username); + } else { + LOG(LERROR) << "Received unexpected STUN message type (" << msg->type() + << ") from unknown address: " << addr.ToString(); + delete msg; + } +} + +void Port::SendBindingRequest(Connection* conn) { + + // Construct the request message. + + StunMessage request; + request.SetType(STUN_BINDING_REQUEST); + request.SetTransactionID(CreateRandomString(16)); + + StunByteStringAttribute* username_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + std::string username = conn->remote_candidate().username(); + username.append(username_frag_); + username_attr->CopyBytes(username.c_str(), (uint16)username.size()); + request.AddAttribute(username_attr); + + // Send the request message. + // NOTE: If we wanted to, this is where we would add the HMAC. + ByteBuffer buf; + request.Write(&buf); + SendTo(buf.Data(), buf.Length(), conn->remote_candidate().address(), false); +} + +bool Port::GetStunMessage(const char* data, size_t size, + const SocketAddress& addr, StunMessage *& msg, + std::string& remote_username) { + // NOTE: This could clearly be optimized to avoid allocating any memory. + // However, at the data rates we'll be looking at on the client side, + // this probably isn't worth worrying about. + + msg = 0; + + // Parse the request message. If the packet is not a complete and correct + // STUN message, then ignore it. + buzz::scoped_ptr stun_msg(new StunMessage()); + ByteBuffer buf(data, size); + if (!stun_msg->Read(&buf) || (buf.Length() > 0)) { + return false; + } + + // The packet must include a username that either begins or ends with our + // fragment. It should begin with our fragment if it is a request and it + // should end with our fragment if it is a response. + const StunByteStringAttribute* username_attr = + stun_msg->GetByteString(STUN_ATTR_USERNAME); + + int remote_frag_len = (username_attr ? username_attr->length() : 0); + remote_frag_len -= static_cast(username_frag_.size()); + + if (stun_msg->type() == STUN_BINDING_REQUEST) { + if ((remote_frag_len < 0) + || (std::memcmp(username_attr->bytes(), + username_frag_.c_str(), username_frag_.size()) != 0)) { + LOG(LERROR) << "Received STUN request with bad username"; + SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_BAD_REQUEST, + STUN_ERROR_REASON_BAD_REQUEST); + return true; + } + + remote_username.assign(username_attr->bytes() + username_frag_.size(), + username_attr->bytes() + username_attr->length()); + } else if ((stun_msg->type() == STUN_BINDING_RESPONSE) + || (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE)) { + if ((remote_frag_len < 0) + || (std::memcmp(username_attr->bytes() + remote_frag_len, + username_frag_.c_str(), username_frag_.size()) != 0)) { + LOG(LERROR) << "Received STUN response with bad username"; + // Do not send error response to a response + return true; + } + + remote_username.assign(username_attr->bytes(), + username_attr->bytes() + remote_frag_len); + + if (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE) { + if (const StunErrorCodeAttribute* error_code = stun_msg->GetErrorCode()) { + LOG(LERROR) << "Received STUN binding error:" + << " class=" << error_code->error_class() + << " number=" << error_code->number() + << " reason='" << error_code->reason() << "'"; + // Return message to allow error-specific processing + } else { + LOG(LERROR) << "Received STUN error response with no error code"; + // Drop corrupt message + return true; + } + } + } else { + LOG(LERROR) << "Received STUN packet with invalid type: " + << stun_msg->type(); + return true; + } + + // Return the STUN message found. + msg = stun_msg.release(); + return true; +} + +void Port::SendBindingResponse( + StunMessage* request, const SocketAddress& addr) { + + assert(request->type() == STUN_BINDING_REQUEST); + + // Retrieve the username from the request. + const StunByteStringAttribute* username_attr = + request->GetByteString(STUN_ATTR_USERNAME); + assert(username_attr); + + // Fill in the response message. + + StunMessage response; + response.SetType(STUN_BINDING_RESPONSE); + response.SetTransactionID(request->transaction_id()); + + StunByteStringAttribute* username2_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + username2_attr->CopyBytes(username_attr->bytes(), username_attr->length()); + response.AddAttribute(username2_attr); + + StunAddressAttribute* addr_attr = + StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); + addr_attr->SetFamily(1); + addr_attr->SetPort(addr.port()); + addr_attr->SetIP(addr.ip()); + response.AddAttribute(addr_attr); + + // Send the response message. + // NOTE: If we wanted to, this is where we would add the HMAC. + ByteBuffer buf; + response.Write(&buf); + SendTo(buf.Data(), buf.Length(), addr, false); + + // The fact that we received a successful request means that this connection + // (if one exists) should now be readable. + Connection* conn = GetConnection(addr); + assert(conn); + if (conn) + conn->ReceivedPing(); +} + +void Port::SendBindingErrorResponse( + StunMessage* request, const SocketAddress& addr, int error_code, + const std::string& reason) { + + assert(request->type() == STUN_BINDING_REQUEST); + + // Retrieve the username from the request. If it didn't have one, we + // shouldn't be responding at all. + const StunByteStringAttribute* username_attr = + request->GetByteString(STUN_ATTR_USERNAME); + assert(username_attr); + + // Fill in the response message. + + StunMessage response; + response.SetType(STUN_BINDING_ERROR_RESPONSE); + response.SetTransactionID(request->transaction_id()); + + StunByteStringAttribute* username2_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + username2_attr->CopyBytes(username_attr->bytes(), username_attr->length()); + response.AddAttribute(username2_attr); + + StunErrorCodeAttribute* error_attr = StunAttribute::CreateErrorCode(); + error_attr->SetErrorCode(error_code); + error_attr->SetReason(reason); + response.AddAttribute(error_attr); + + // Send the response message. + // NOTE: If we wanted to, this is where we would add the HMAC. + ByteBuffer buf; + response.Write(&buf); + SendTo(buf.Data(), buf.Length(), addr, false); +} + +AsyncPacketSocket * Port::CreatePacketSocket(ProtocolType proto) { + if (proto == PROTO_UDP) { + return new AsyncUDPSocket(factory_->CreateAsyncSocket(SOCK_DGRAM)); + } else if ((proto == PROTO_TCP) || (proto == PROTO_SSLTCP)) { + AsyncSocket * socket = factory_->CreateAsyncSocket(SOCK_STREAM); + switch (proxy().type) { + case PROXY_NONE: + break; + case PROXY_SOCKS5: + socket = new AsyncSocksProxySocket(socket, proxy().address, proxy().username, proxy().password); + break; + case PROXY_HTTPS: + default: + socket = new AsyncHttpsProxySocket(socket, proxy().address, proxy().username, proxy().password); + break; + } + if (proto == PROTO_SSLTCP) { + socket = new AsyncSSLSocket(socket); + } + return new AsyncTCPSocket(socket); + } else { + LOG(INFO) << "Unknown protocol: " << proto; + return 0; + } +} + +void Port::OnMessage(Message *pmsg) { + assert(pmsg->message_id == MSG_CHECKTIMEOUT); + assert(lifetime_ == LT_PRETIMEOUT); + lifetime_ = LT_POSTTIMEOUT; + CheckTimeout(); +} + +void Port::Start() { + // The port sticks around for a minimum lifetime, after which + // we destroy it when it drops to zero connections. + if (lifetime_ == LT_PRESTART) { + lifetime_ = LT_PRETIMEOUT; + thread_->PostDelayed(kPortTimeoutDelay, this, MSG_CHECKTIMEOUT); + } else { + LOG(WARNING) << "Port restart attempted"; + } +} + +void Port::OnConnectionDestroyed(Connection* conn) { + AddressMap::iterator iter = connections_.find(conn->remote_candidate().address()); + assert(iter != connections_.end()); + connections_.erase(iter); + + CheckTimeout(); +} + +void Port::CheckTimeout() { + // If this port has no connections, then there's no reason to keep it around. + // When the connections time out (both read and write), they will delete + // themselves, so if we have any connections, they are either readable or + // writable (or still connecting). + if ((lifetime_ == LT_POSTTIMEOUT) && connections_.empty()) { + LOG(INFO) << "Destroying port: " << name_ << "-" << type_; + SignalDestroyed(this); + delete this; + } +} + +// A ConnectionRequest is a simple STUN ping used to determine writability. +class ConnectionRequest : public StunRequest { +public: + ConnectionRequest(Connection* connection) : connection_(connection) { + } + + virtual ~ConnectionRequest() { + } + + virtual void Prepare(StunMessage* request) { + request->SetType(STUN_BINDING_REQUEST); + StunByteStringAttribute* username_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + std::string username = connection_->remote_candidate().username(); + username.append(connection_->port()->username_fragment()); + username_attr->CopyBytes(username.c_str(), (uint16)username.size()); + request->AddAttribute(username_attr); + } + + virtual void OnResponse(StunMessage* response) { + connection_->OnConnectionRequestResponse(response, Elapsed()); + } + + virtual void OnErrorResponse(StunMessage* response) { + connection_->OnConnectionRequestErrorResponse(response, Elapsed()); + } + + virtual void OnTimeout() { + } + + virtual int GetNextDelay() { + // Each request is sent only once. After a single delay , the request will + // time out. + timeout_ = true; + return CONNECTION_RESPONSE_TIMEOUT; + } + +private: + Connection* connection_; +}; + +// +// Connection +// + +Connection::Connection(Port* port, size_t index, const Candidate& remote_candidate) + : requests_(port->thread()), port_(port), local_candidate_index_(index), + remote_candidate_(remote_candidate), read_state_(STATE_READ_TIMEOUT), + write_state_(STATE_WRITE_CONNECT), connected_(true), pruned_(false), + rtt_(0), rtt_data_points_(0), last_ping_sent_(0), last_ping_received_(0), + recv_total_bytes_(0), recv_bytes_second_(0), + last_recv_bytes_second_time_((uint32)-1), last_recv_bytes_second_calc_(0), + sent_total_bytes_(0), sent_bytes_second_(0), + last_sent_bytes_second_time_((uint32)-1), last_sent_bytes_second_calc_(0) { + + // Wire up to send stun packets + requests_.SignalSendPacket.connect(this, &Connection::OnSendStunPacket); +} + +Connection::~Connection() { +} + +const Candidate& Connection::local_candidate() const { + if (local_candidate_index_ < port_->candidates().size()) + return port_->candidates()[local_candidate_index_]; + assert(false); + static Candidate foo; + return foo; +} + +void Connection::set_read_state(ReadState value) { + ReadState old_value = read_state_; + read_state_ = value; + if (value != old_value) { + SignalStateChange(this); + CheckTimeout(); + } +} + +void Connection::set_write_state(WriteState value) { + WriteState old_value = write_state_; + write_state_ = value; + if (value != old_value) { + SignalStateChange(this); + CheckTimeout(); + } +} + +void Connection::set_connected(bool value) { + bool old_value = connected_; + connected_ = value; + + // When connectedness is turned off, this connection is done. + if (old_value && !value) + set_write_state(STATE_WRITE_TIMEOUT); +} + +void Connection::OnSendStunPacket(const void* data, size_t size) { + port_->SendTo(data, size, remote_candidate_.address(), false); +} + +void Connection::OnReadPacket(const char* data, size_t size) { + StunMessage* msg; + std::string remote_username; + const SocketAddress& addr(remote_candidate_.address()); + if (!port_->GetStunMessage(data, size, addr, msg, remote_username)) { + // The packet did not parse as a valid STUN message + + // If this connection is readable, then pass along the packet. + if (read_state_ == STATE_READABLE) { + // readable means data from this address is acceptable + // Send it on! + + recv_total_bytes_ += size; + SignalReadPacket(this, data, size); + + // If timed out sending writability checks, start up again + if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT)) + set_write_state(STATE_WRITE_CONNECT); + } else { + // Not readable means the remote address hasn't send a valid + // binding request yet. + + LOG(WARNING) << "Received non-STUN packet from an unreadable connection."; + } + } else if (!msg) { + // The packet was STUN, but was already handled + } else if (remote_username != remote_candidate_.username()) { + // Not destined this connection + LOG(LERROR) << "Received STUN packet on wrong address."; + if (msg->type() == STUN_BINDING_REQUEST) { + port_->SendBindingErrorResponse(msg, addr, STUN_ERROR_BAD_REQUEST, + STUN_ERROR_REASON_BAD_REQUEST); + } + delete msg; + } else { + // The packet is STUN, with the current username + // If this is a STUN request, then update the readable bit and respond. + // If this is a STUN response, then update the writable bit. + + switch (msg->type()) { + case STUN_BINDING_REQUEST: + // Incoming, validated stun request from remote peer. + // This call will also set the connection readable. + + port_->SendBindingResponse(msg, addr); + + // If timed out sending writability checks, start up again + if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT)) + set_write_state(STATE_WRITE_CONNECT); + break; + + case STUN_BINDING_RESPONSE: + case STUN_BINDING_ERROR_RESPONSE: + // Response from remote peer. Does it match request sent? + // This doesn't just check, it makes callbacks if transaction + // id's match + requests_.CheckResponse(msg); + break; + + default: + assert(false); + break; + } + + // Done with the message; delete + + delete msg; + } +} + +void Connection::Prune() { + pruned_ = true; + requests_.Clear(); + set_write_state(STATE_WRITE_TIMEOUT); +} + +void Connection::Destroy() { + set_read_state(STATE_READ_TIMEOUT); + set_write_state(STATE_WRITE_TIMEOUT); +} + +void Connection::UpdateState(uint32 now) { + // Check the readable state. + // + // Since we don't know how many pings the other side has attempted, the best + // test we can do is a simple window. + + if ((read_state_ == STATE_READABLE) && + (last_ping_received_ + CONNECTION_READ_TIMEOUT <= now)) { + set_read_state(STATE_READ_TIMEOUT); + } + + // Check the writable state. (The order of these checks is important.) + // + // Before becoming unwritable, we allow for a fixed number of pings to fail + // (i.e., receive no response). We also have to give the response time to + // get back, so we include a conservative estimate of this. + // + // Before timing out writability, we give a fixed amount of time. This is to + // allow for changes in network conditions. + + uint32 rtt = ConservativeRTTEstimate(rtt_, rtt_data_points_); + + if ((write_state_ == STATE_WRITABLE) && + TooManyFailures(pings_since_last_response_, + CONNECTION_WRITE_CONNECT_FAILURES, + rtt, + now) && + TooLongWithoutResponse(pings_since_last_response_, + CONNECTION_WRITE_CONNECT_TIMEOUT, + now)) { + set_write_state(STATE_WRITE_CONNECT); + } + + if ((write_state_ == STATE_WRITE_CONNECT) && + TooLongWithoutResponse(pings_since_last_response_, + CONNECTION_WRITE_TIMEOUT, + now)) { + set_write_state(STATE_WRITE_TIMEOUT); + } +} + +void Connection::Ping(uint32 now) { + assert(connected_); + last_ping_sent_ = now; + pings_since_last_response_.push_back(now); + requests_.Send(new ConnectionRequest(this)); +} + +void Connection::ReceivedPing() { + last_ping_received_ = Time(); + set_read_state(STATE_READABLE); +} + +void Connection::OnConnectionRequestResponse(StunMessage *response, uint32 rtt) { + // We have a potentially valid reply from the remote address. + // The packet must include a username that ends with our fragment, + // since it is a response. + + // Check exact message type + bool valid = true; + if (response->type() != STUN_BINDING_RESPONSE) + valid = false; + + // Must have username attribute + const StunByteStringAttribute* username_attr = + response->GetByteString(STUN_ATTR_USERNAME); + if (valid) { + if (!username_attr) { + LOG(LERROR) << "Received likely STUN packet with no username"; + valid = false; + } + } + + // Length must be at least the size of our fragment (actually, should + // be bigger since our fragment is at the end!) + if (valid) { + if (username_attr->length() <= port_->username_fragment().size()) { + LOG(LERROR) << "Received likely STUN packet with short username"; + valid = false; + } + } + + // Compare our fragment with the end of the username - must be exact match + if (valid) { + std::string username_fragment = port_->username_fragment(); + int offset = (int)(username_attr->length() - username_fragment.size()); + if (std::memcmp(username_attr->bytes() + offset, + username_fragment.c_str(), username_fragment.size()) != 0) { + LOG(LERROR) << "Received STUN response with bad username"; + valid = false; + } + } + + if (valid) { + // Valid response. If we're not already, become writable. We may be + // bringing a pruned connection back to life, but if we don't really want + // it, we can always prune it again. + set_write_state(STATE_WRITABLE); + + pings_since_last_response_.clear(); + rtt_ = (RTT_RATIO * rtt_ + rtt) / (RTT_RATIO + 1); + rtt_data_points_ += 1; + } +} + +void Connection::OnConnectionRequestErrorResponse(StunMessage *response, uint32 rtt) { + const StunErrorCodeAttribute* error = response->GetErrorCode(); + uint32 error_code = error ? error->error_code() : STUN_ERROR_GLOBAL_FAILURE; + + if ((error_code == STUN_ERROR_UNKNOWN_ATTRIBUTE) + || (error_code == STUN_ERROR_SERVER_ERROR) + || (error_code == STUN_ERROR_UNAUTHORIZED)) { + // Recoverable error, retry + } else if (error_code == STUN_ERROR_STALE_CREDENTIALS) { + // Race failure, retry + } else { + // This is not a valid connection. + set_connected(false); + } +} + +void Connection::CheckTimeout() { + // If both read and write have timed out, then this connection can contribute + // no more to p2p socket unless at some later date readability were to come + // back. However, we gave readability a long time to timeout, so at this + // point, it seems fair to get rid of this connectoin. + if ((read_state_ == STATE_READ_TIMEOUT) && + (write_state_ == STATE_WRITE_TIMEOUT)) { + port_->thread()->Post(this, MSG_DELETE); + } +} + +void Connection::OnMessage(Message *pmsg) { + assert(pmsg->message_id == MSG_DELETE); + + LOG(INFO) << "Destroying connection: from " + << local_candidate().address().ToString() + << " to " << remote_candidate_.address().ToString(); + + SignalDestroyed(this); + delete this; +} + +size_t Connection::recv_bytes_second() { + // Snapshot bytes / second calculator + + uint32 current_time = Time(); + if (last_recv_bytes_second_time_ != (uint32)-1) { + int delta = TimeDiff(current_time, last_recv_bytes_second_time_); + if (delta >= 1000) { + int fraction_time = delta % 1000; + int seconds_time = delta - fraction_time; + int fraction_bytes = (int)(recv_total_bytes_ - last_recv_bytes_second_calc_) * fraction_time / delta; + recv_bytes_second_ = (recv_total_bytes_ - last_recv_bytes_second_calc_ - fraction_bytes) * seconds_time / delta; + last_recv_bytes_second_time_ = current_time - fraction_time; + last_recv_bytes_second_calc_ = recv_total_bytes_ - fraction_bytes; + } + } + if (last_recv_bytes_second_time_ == (uint32)-1) { + last_recv_bytes_second_time_ = current_time; + last_recv_bytes_second_calc_ = recv_total_bytes_; + } + + return recv_bytes_second_; +} + +size_t Connection::recv_total_bytes() { + return recv_total_bytes_; +} + +size_t Connection::sent_bytes_second() { + // Snapshot bytes / second calculator + + uint32 current_time = Time(); + if (last_sent_bytes_second_time_ != (uint32)-1) { + int delta = TimeDiff(current_time, last_sent_bytes_second_time_); + if (delta >= 1000) { + int fraction_time = delta % 1000; + int seconds_time = delta - fraction_time; + int fraction_bytes = (int)(sent_total_bytes_ - last_sent_bytes_second_calc_) * fraction_time / delta; + sent_bytes_second_ = (sent_total_bytes_ - last_sent_bytes_second_calc_ - fraction_bytes) * seconds_time / delta; + last_sent_bytes_second_time_ = current_time - fraction_time; + last_sent_bytes_second_calc_ = sent_total_bytes_ - fraction_bytes; + } + } + if (last_sent_bytes_second_time_ == (uint32)-1) { + last_sent_bytes_second_time_ = current_time; + last_sent_bytes_second_calc_ = sent_total_bytes_; + } + + return sent_bytes_second_; +} + +size_t Connection::sent_total_bytes() { + return sent_total_bytes_; +} + +ProxyConnection::ProxyConnection(Port* port, size_t index, const Candidate& candidate) + : Connection(port, index, candidate), error_(0) { +} + +int ProxyConnection::Send(const void* data, size_t size) { + if (write_state() != STATE_WRITABLE) { + error_ = EWOULDBLOCK; + return SOCKET_ERROR; + } + int sent = port_->SendTo(data, size, remote_candidate_.address(), true); + if (sent <= 0) { + assert(sent < 0); + error_ = port_->GetError(); + } else { + sent_total_bytes_ += sent; + } + return sent; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.h new file mode 100644 index 00000000..c22fad28 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.h @@ -0,0 +1,367 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __PORT_H__ +#define __PORT_H__ + +#include "talk/base/network.h" +#include "talk/base/socketaddress.h" +#include "talk/base/proxyinfo.h" +#include "talk/base/sigslot.h" +#include "talk/base/thread.h" +#include "talk/p2p/base/candidate.h" +#include "talk/p2p/base/stun.h" +#include "talk/p2p/base/stunrequest.h" + +#include +#include +#include + +namespace cricket { + +class Connection; +class AsyncPacketSocket; + +enum ProtocolType { PROTO_UDP, PROTO_TCP, PROTO_SSLTCP, PROTO_LAST = PROTO_SSLTCP }; +const char * ProtoToString(ProtocolType proto); +bool StringToProto(const char * value, ProtocolType& proto); + +struct ProtocolAddress { + SocketAddress address; + ProtocolType proto; + + ProtocolAddress(const SocketAddress& a, ProtocolType p) : address(a), proto(p) { } +}; + +// Represents a local communication mechanism that can be used to create +// connections to similar mechanisms of the other client. Subclasses of this +// one add support for specific mechanisms like local UDP ports. +class Port: public MessageHandler, public sigslot::has_slots<> { +public: + Port(Thread* thread, const std::string &type, SocketFactory* factory, + Network* network); + virtual ~Port(); + + // The thread on which this port performs its I/O. + Thread* thread() { return thread_; } + + // The factory used to create the sockets of this port. + SocketFactory* socket_factory() const { return factory_; } + void set_socket_factory(SocketFactory* factory) { factory_ = factory; } + + // Each port is identified by a name (for debugging purposes). + const std::string& name() const { return name_; } + void set_name(const std::string& name) { name_ = name; } + + // In order to establish a connection to this Port (so that real data can be + // sent through), the other side must send us a STUN binding request that is + // authenticated with this username and password. + const std::string& username_fragment() const { return username_frag_; } + const std::string& password() const { return password_; } + + // A value in [0,1] that indicates the preference for this port versus other + // ports on this client. (Larger indicates more preference.) + float preference() const { return preference_; } + void set_preference(float preference) { preference_ = preference; } + + // Identifies the port type. + //const std::string& protocol() const { return proto_; } + const std::string& type() const { return type_; } + + // Identifies network that this port was allocated on. + Network* network() { return network_; } + + // Identifies the generation that this port was created in. + uint32 generation() { return generation_; } + void set_generation(uint32 generation) { generation_ = generation; } + + // PrepareAddress will attempt to get an address for this port that other + // clients can send to. It may take some time before the address is read. + // Once it is ready, we will send SignalAddressReady. + virtual void PrepareAddress() = 0; + sigslot::signal1 SignalAddressReady; + //const SocketAddress& address() const { return address_; } + + // Provides all of the above information in one handy object. + const std::vector& candidates() const { return candidates_; } + + // Returns a map containing all of the connections of this port, keyed by the + // remote address. + typedef std::map AddressMap; + const AddressMap& connections() { return connections_; } + + // Returns the connection to the given address or NULL if none exists. + Connection* GetConnection(const SocketAddress& remote_addr); + + // Creates a new connection to the given address. + enum CandidateOrigin { ORIGIN_THIS_PORT, ORIGIN_OTHER_PORT, ORIGIN_MESSAGE }; + virtual Connection* CreateConnection(const Candidate& remote_candidate, CandidateOrigin origin) = 0; + + // Called each time a connection is created. + sigslot::signal2 SignalConnectionCreated; + + // Sends the given packet to the given address, provided that the address is + // that of a connection or an address that has sent to us already. + virtual int SendTo( + const void* data, size_t size, const SocketAddress& addr, bool payload) = 0; + + // Indicates that we received a successful STUN binding request from an + // address that doesn't correspond to any current connection. To turn this + // into a real connection, call CreateConnection. + sigslot::signal4 SignalUnknownAddress; + + // Sends a response message (normal or error) to the given request. One of + // these methods should be called as a response to SignalUnknownAddress. + // NOTE: You MUST call CreateConnection BEFORE SendBindingResponse. + void SendBindingResponse(StunMessage* request, const SocketAddress& addr); + void SendBindingErrorResponse( + StunMessage* request, const SocketAddress& addr, int error_code, + const std::string& reason); + + // Indicates that errors occurred when performing I/O. + sigslot::signal2 SignalReadError; + sigslot::signal2 SignalWriteError; + + // Functions on the underlying socket(s). + virtual int SetOption(Socket::Option opt, int value) = 0; + virtual int GetError() = 0; + + static void set_proxy(const ProxyInfo& proxy) { proxy_ = proxy; } + static const ProxyInfo& proxy() { return proxy_; } + + AsyncPacketSocket * CreatePacketSocket(ProtocolType proto); + + virtual void OnMessage(Message *pmsg); + + // Indicates to the port that its official use has now begun. This will + // start the timer that checks to see if the port is being used. + void Start(); + + // Signaled when this port decides to delete itself because it no longer has + // any usefulness. + sigslot::signal1 SignalDestroyed; + +protected: + Thread* thread_; + SocketFactory* factory_; + std::string type_; + Network* network_; + uint32 generation_; + std::string name_; + std::string username_frag_; + std::string password_; + float preference_; + std::vector candidates_; + AddressMap connections_; + enum Lifetime { LT_PRESTART, LT_PRETIMEOUT, LT_POSTTIMEOUT } lifetime_; + + // Fills in the username fragment and password. These will be initially set + // in the constructor to random values. Subclasses can override, though. + void set_username_fragment(const std::string& username_fragment); + void set_password(const std::string& password); + + // Fills in the local address of the port. + void add_address(const SocketAddress& address, const std::string& protocol, bool final = true); + + // Adds the given connection to the list. (Deleting removes them.) + void AddConnection(Connection* conn); + + // Called when a packet is received from an unknown address that is not + // currently a connection. If this is an authenticated STUN binding request, + // then we will signal the client. + void OnReadPacket(const char* data, size_t size, const SocketAddress& addr); + + // Constructs a STUN binding request for the given connection and sends it. + void SendBindingRequest(Connection* conn); + + // If the given data comprises a complete and correct STUN message then the + // return value is true, otherwise false. If the message username corresponds + // with this port's username fragment, msg will contain the parsed STUN + // message. Otherwise, the function may send a STUN response internally. + // remote_username contains the remote fragment of the STUN username. + bool GetStunMessage(const char* data, size_t size, const SocketAddress& addr, + StunMessage *& msg, std::string& remote_username); + + friend class Connection; + +private: + // Called when one of our connections deletes itself. + void OnConnectionDestroyed(Connection* conn); + + // Checks if this port is useless, and hence, should be destroyed. + void CheckTimeout(); + + static ProxyInfo proxy_; +}; + +// Represents a communication link between a port on the local client and a +// port on the remote client. +class Connection : public MessageHandler, public sigslot::has_slots<> { +public: + virtual ~Connection(); + + // The local port where this connection sends and receives packets. + Port* port() { return port_; } + const Port* port() const { return port_; } + + // Returns the description of the local port + virtual const Candidate& local_candidate() const; + + // Returns the description of the remote port to which we communicate. + const Candidate& remote_candidate() const { return remote_candidate_; } + + enum ReadState { + STATE_READABLE = 0, // we have received pings recently + STATE_READ_TIMEOUT = 1 // we haven't received pings in a while + }; + + ReadState read_state() const { return read_state_; } + + enum WriteState { + STATE_WRITABLE = 0, // we have received ping responses recently + STATE_WRITE_CONNECT = 1, // we have had a few ping failures + STATE_WRITE_TIMEOUT = 2 // we have had a large number of ping failures + }; + + WriteState write_state() const { return write_state_; } + + // Determines whether the connection has finished connecting. This can only + // be false for TCP connections. + bool connected() const { return connected_; } + + // Estimate of the round-trip time over this connection. + uint32 rtt() const { return rtt_; } + + size_t sent_total_bytes(); + size_t sent_bytes_second(); + size_t recv_total_bytes(); + size_t recv_bytes_second(); + sigslot::signal1 SignalStateChange; + + // Sent when the connection has decided that it is no longer of value. It + // will delete itself immediately after this call. + sigslot::signal1 SignalDestroyed; + + // The connection can send and receive packets asynchronously. This matches + // the interface of AsyncPacketSocket, which may use UDP or TCP under the covers. + virtual int Send(const void* data, size_t size) = 0; + + // Error if Send() returns < 0 + virtual int GetError() = 0; + + sigslot::signal3 SignalReadPacket; + + // Called when a packet is received on this connection. + void OnReadPacket(const char* data, size_t size); + + // Called when a connection is determined to be no longer useful to us. We + // still keep it around in case the other side wants to use it. But we can + // safely stop pinging on it and we can allow it to time out if the other + // side stops using it as well. + bool pruned() { return pruned_; } + void Prune(); + + // Makes the connection go away. + void Destroy(); + + // Checks that the state of this connection is up-to-date. The argument is + // the current time, which is compared against various timeouts. + void UpdateState(uint32 now); + + // Called when this connection should try checking writability again. + uint32 last_ping_sent() { return last_ping_sent_; } + void Ping(uint32 now); + + // Called whenever a valid ping is received on this connection. This is + // public because the connection intercepts the first ping for us. + void ReceivedPing(); + +protected: + Port* port_; + size_t local_candidate_index_; + Candidate remote_candidate_; + ReadState read_state_; + WriteState write_state_; + bool connected_; + bool pruned_; + StunRequestManager requests_; + uint32 rtt_; + uint32 rtt_data_points_; + uint32 last_ping_sent_; // last time we sent a ping to the other side + uint32 last_ping_received_; // last time we received a ping from the other side + std::vector pings_since_last_response_; + + size_t recv_total_bytes_; + size_t recv_bytes_second_; + uint32 last_recv_bytes_second_time_; + size_t last_recv_bytes_second_calc_; + + size_t sent_total_bytes_; + size_t sent_bytes_second_; + uint32 last_sent_bytes_second_time_; + size_t last_sent_bytes_second_calc_; + + // Callbacks from ConnectionRequest + void OnConnectionRequestResponse(StunMessage *response, uint32 rtt); + void OnConnectionRequestErrorResponse(StunMessage *response, uint32 rtt); + + // Called back when StunRequestManager has a stun packet to send + void OnSendStunPacket(const void* data, size_t size); + + // Constructs a new connection to the given remote port. + Connection(Port* port, size_t index, const Candidate& candidate); + + // Changes the state and signals if necessary. + void set_read_state(ReadState value); + void set_write_state(WriteState value); + void set_connected(bool value); + + // Checks if this connection is useless, and hence, should be destroyed. + void CheckTimeout(); + + void OnMessage(Message *pmsg); + + friend class Port; + friend class ConnectionRequest; +}; + +// ProxyConnection defers all the interesting work to the port + +class ProxyConnection : public Connection { +public: + ProxyConnection(Port* port, size_t index, const Candidate& candidate); + + virtual int Send(const void* data, size_t size); + virtual int GetError() { return error_; } + +private: + int error_; +}; + +} // namespace cricket + +#endif // __PORT_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/portallocator.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/portallocator.h new file mode 100644 index 00000000..3246f29f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/portallocator.h @@ -0,0 +1,91 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PORTALLOCATOR_H_ +#define _PORTALLOCATOR_H_ + +#include "talk/base/sigslot.h" +#include "talk/p2p/base/port.h" +#include +#undef SetPort + +namespace cricket { + +// PortAllocator is responsible for allocating Port types for a given +// P2PSocket. It also handles port freeing. +// +// Clients can override this class to control port allocation, including +// what kinds of ports are allocated. + +class PortAllocatorSession : public sigslot::has_slots<> { +public: + // Prepares an initial set of ports to try. + virtual void GetInitialPorts() = 0; + + // Starts and stops the flow of additional ports to try. + virtual void StartGetAllPorts() = 0; + virtual void StopGetAllPorts() = 0; + virtual bool IsGettingAllPorts() = 0; + + sigslot::signal2 SignalPortReady; + sigslot::signal2&> SignalCandidatesReady; + + uint32 generation() { return generation_; } + void set_generation(uint32 generation) { generation_ = generation; } + +private: + uint32 generation_; +}; + +const uint32 PORTALLOCATOR_DISABLE_UDP = 0x01; +const uint32 PORTALLOCATOR_DISABLE_STUN = 0x02; +const uint32 PORTALLOCATOR_DISABLE_RELAY = 0x04; +const uint32 PORTALLOCATOR_DISABLE_TCP = 0x08; +const uint32 PORTALLOCATOR_ENABLE_SHAKER = 0x10; + +const uint32 kDefaultPortAllocatorFlags = 0; + +class PortAllocator { +public: + PortAllocator() : flags_(kDefaultPortAllocatorFlags) {} + + virtual PortAllocatorSession *CreateSession(const std::string &name) = 0; + + uint32 flags() const { return flags_; } + void set_flags(uint32 flags) { flags_ = flags; } + + const ProxyInfo& proxy() const { return proxy_; } + void set_proxy(const ProxyInfo& proxy) { proxy_ = proxy; } + +protected: + uint32 flags_; + ProxyInfo proxy_; +}; + +} // namespace cricket + +#endif // _PORTALLOCATOR_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.cc new file mode 100644 index 00000000..4ba12be3 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.cc @@ -0,0 +1,640 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/logging.h" +#include "talk/base/asynctcpsocket.h" +#include "talk/p2p/base/relayport.h" +#include "talk/p2p/base/helpers.h" +#include +#include +#ifdef OSX +#include +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +const int KEEPALIVE_DELAY = 10 * 60 * 1000; +const int RETRY_DELAY = 50; // 50ms, from ICE spec +const uint32 RETRY_TIMEOUT = 50 * 1000; // ICE says 50 secs + +const uint32 MSG_DISPOSE_SOCKET = 100; // needs to be more than ID used by Port +typedef TypedMessageData DisposeSocketData; + +class AsyncTCPSocket; + +// Manages a single connection to the relayserver. We aim to use each +// connection for only a specific destination address so that we can avoid +// wrapping every packet in a STUN send / data indication. +class RelayEntry : public sigslot::has_slots<> { +public: + RelayEntry(RelayPort* port, const SocketAddress& ext_addr, const SocketAddress& local_addr); + ~RelayEntry(); + + RelayPort* port() { return port_; } + + const SocketAddress& address() { return ext_addr_; } + void set_address(const SocketAddress& addr) { ext_addr_ = addr; } + + AsyncPacketSocket* socket() { return socket_; } + + bool connected() { return connected_; } + void set_connected(bool connected) { connected_ = connected; } + + bool locked() { return locked_; } + + // Returns the last error on the socket of this entry. + int GetError() { return socket_->GetError(); } + + // Sends the STUN requests to the server to initiate this connection. + void Connect(); + + // Called when this entry becomes connected. The address given is the one + // exposed to the outside world on the relay server. + void OnConnect(const SocketAddress& mapped_addr); + + // Sends a packet to the given destination address using the socket of this + // entry. This will wrap the packet in STUN if necessary. + int SendTo(const void* data, size_t size, const SocketAddress& addr); + + // Schedules a keep-alive allocate request. + void ScheduleKeepAlive(); + + void SetServerIndex(size_t sindex) { server_index_ = sindex; } + size_t ServerIndex() const { return server_index_; } + + // Try a different server address + void HandleConnectFailure(); + +private: + RelayPort* port_; + SocketAddress ext_addr_, local_addr_; + size_t server_index_; + AsyncPacketSocket* socket_; + bool connected_; + bool locked_; + StunRequestManager requests_; + + // Called when a TCP connection is established or fails + void OnSocketConnect(AsyncTCPSocket* socket); + void OnSocketClose(AsyncTCPSocket* socket, int error); + + // Called when a packet is received on this socket. + void OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + + // Called on behalf of a StunRequest to write data to the socket. This is + // already STUN intended for the server, so no wrapping is necessary. + void OnSendPacket(const void* data, size_t size); + + // Sends the given data on the socket to the server with no wrapping. This + // returns the number of bytes written or -1 if an error occurred. + int SendPacket(const void* data, size_t size); +}; + +// Handles an allocate request for a particular RelayEntry. +class AllocateRequest : public StunRequest { +public: + AllocateRequest(RelayEntry* entry); + virtual ~AllocateRequest() {} + + virtual void Prepare(StunMessage* request); + + virtual int GetNextDelay(); + + virtual void OnResponse(StunMessage* response); + virtual void OnErrorResponse(StunMessage* response); + virtual void OnTimeout(); + +private: + RelayEntry* entry_; + uint32 start_time_; +}; + +const std::string RELAY_PORT_TYPE("relay"); + +RelayPort::RelayPort( + Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& local_addr, const std::string& username, + const std::string& password, const std::string& magic_cookie) + : Port(thread, RELAY_PORT_TYPE, factory, network), local_addr_(local_addr), + ready_(false), magic_cookie_(magic_cookie), error_(0) { + + entries_.push_back(new RelayEntry(this, SocketAddress(), local_addr_)); + + set_username_fragment(username); + set_password(password); + + if (magic_cookie_.size() == 0) + magic_cookie_.append(STUN_MAGIC_COOKIE_VALUE, 4); +} + +RelayPort::~RelayPort() { + for (unsigned i = 0; i < entries_.size(); ++i) + delete entries_[i]; + thread_->Clear(this); +} + +void RelayPort::AddServerAddress(const ProtocolAddress& addr) { + // Since HTTP proxies usually only allow 443, let's up the priority on PROTO_SSLTCP + if ((addr.proto == PROTO_SSLTCP) + && ((proxy().type == PROXY_HTTPS) || (proxy().type == PROXY_UNKNOWN))) { + server_addr_.push_front(addr); + } else { + server_addr_.push_back(addr); + } +} + +void RelayPort::AddExternalAddress(const ProtocolAddress& addr) { + std::string proto_name = ProtoToString(addr.proto); + for (std::vector::const_iterator it = candidates().begin(); it != candidates().end(); ++it) { + if ((it->address() == addr.address) && (it->protocol() == proto_name)) { + LOG(INFO) << "Redundant relay address: " << proto_name << " @ " << addr.address.ToString(); + return; + } + } + add_address(addr.address, proto_name, false); +} + +void RelayPort::SetReady() { + if (!ready_) { + ready_ = true; + SignalAddressReady(this); + } +} + +const ProtocolAddress * RelayPort::ServerAddress(size_t index) const { + if ((index >= 0) && (index < server_addr_.size())) + return &server_addr_[index]; + return 0; +} + +bool RelayPort::HasMagicCookie(const char* data, size_t size) { + if (size < 24 + magic_cookie_.size()) { + return false; + } else { + return 0 == std::memcmp(data + 24, + magic_cookie_.c_str(), + magic_cookie_.size()); + } +} + +void RelayPort::PrepareAddress() { + // We initiate a connect on the first entry. If this completes, it will fill + // in the server address as the address of this port. + assert(entries_.size() == 1); + entries_[0]->Connect(); + ready_ = false; +} + +Connection* RelayPort::CreateConnection(const Candidate& address, CandidateOrigin origin) { + // We only create connections to non-udp sockets if they are incoming on this port + if ((address.protocol() != "udp") && (origin != ORIGIN_THIS_PORT)) + return 0; + + // We don't support loopback on relays + if (address.type() == type()) + return 0; + + size_t index = 0; + for (size_t i = 0; i < candidates().size(); ++i) { + const Candidate& local = candidates()[i]; + if (local.protocol() == address.protocol()) { + index = i; + break; + } + } + + Connection * conn = new ProxyConnection(this, index, address); + AddConnection(conn); + return conn; +} + +int RelayPort::SendTo(const void* data, + size_t size, + const SocketAddress& addr, bool payload) { + + // Try to find an entry for this specific address. Note that the first entry + // created was not given an address initially, so it can be set to the first + // address that comes along. + + RelayEntry* entry = 0; + + for (unsigned i = 0; i < entries_.size(); ++i) { + if (entries_[i]->address().IsAny() && payload) { + entry = entries_[i]; + entry->set_address(addr); + break; + } else if (entries_[i]->address() == addr) { + entry = entries_[i]; + break; + } + } + + // If we did not find one, then we make a new one. This will not be useable + // until it becomes connected, however. + if (!entry && payload) { + entry = new RelayEntry(this, addr, local_addr_); + if (!entries_.empty()) { + // Use the same port to connect to relay server + entry->SetServerIndex(entries_[0]->ServerIndex()); + } + entry->Connect(); + entries_.push_back(entry); + } + + // If the entry is connected, then we can send on it (though wrapping may + // still be necessary). Otherwise, we can't yet use this connection, so we + // default to the first one. + if (!entry || !entry->connected()) { + assert(!entries_.empty()); + entry = entries_[0]; + if (!entry->connected()) { + error_ = EWOULDBLOCK; + return SOCKET_ERROR; + } + } + + // Send the actual contents to the server using the usual mechanism. + int sent = entry->SendTo(data, size, addr); + if (sent <= 0) { + assert(sent < 0); + error_ = entry->GetError(); + return SOCKET_ERROR; + } + + // The caller of the function is expecting the number of user data bytes, + // rather than the size of the packet. + return (int)size; +} + +void RelayPort::OnMessage(Message *pmsg) { + switch (pmsg->message_id) { + case MSG_DISPOSE_SOCKET: { + DisposeSocketData * data = static_cast(pmsg->pdata); + delete data->data(); + delete data; + break; } + default: + Port::OnMessage(pmsg); + } +} + +int RelayPort::SetOption(Socket::Option opt, int value) { + int result = 0; + for (unsigned i = 0; i < entries_.size(); ++i) { + if (entries_[i]->socket()->SetOption(opt, value) < 0) { + result = -1; + error_ = entries_[i]->socket()->GetError(); + } + } + options_.push_back(OptionValue(opt, value)); + return result; +} + +int RelayPort::GetError() { + return error_; +} + +void RelayPort::OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr) { + if (Connection* conn = GetConnection(remote_addr)) { + conn->OnReadPacket(data, size); + } else { + Port::OnReadPacket(data, size, remote_addr); + } +} + +void RelayPort::DisposeSocket(AsyncPacketSocket * socket) { + thread_->Post(this, MSG_DISPOSE_SOCKET, new DisposeSocketData(socket)); +} + +RelayEntry::RelayEntry(RelayPort* port, const SocketAddress& ext_addr, + const SocketAddress& local_addr) + : port_(port), ext_addr_(ext_addr), local_addr_(local_addr), server_index_(0), + socket_(0), connected_(false), locked_(false), requests_(port->thread()) { + + requests_.SignalSendPacket.connect(this, &RelayEntry::OnSendPacket); +} + +RelayEntry::~RelayEntry() { + delete socket_; +} + +void RelayEntry::Connect() { + assert(socket_ == 0); + const ProtocolAddress * ra = port()->ServerAddress(server_index_); + if (!ra) { + LOG(INFO) << "Out of relay server connections"; + return; + } + + LOG(INFO) << "Connecting to relay via " << ProtoToString(ra->proto) << " @ " << ra->address.ToString(); + + socket_ = port_->CreatePacketSocket(ra->proto); + assert(socket_ != 0); + + socket_->SignalReadPacket.connect(this, &RelayEntry::OnReadPacket); + if (socket_->Bind(local_addr_) < 0) + LOG(INFO) << "bind: " << std::strerror(socket_->GetError()); + + for (unsigned i = 0; i < port_->options().size(); ++i) + socket_->SetOption(port_->options()[i].first, port_->options()[i].second); + + if ((ra->proto == PROTO_TCP) || (ra->proto == PROTO_SSLTCP)) { + AsyncTCPSocket * tcp = static_cast(socket_); + tcp->SignalClose.connect(this, &RelayEntry::OnSocketClose); + tcp->SignalConnect.connect(this, &RelayEntry::OnSocketConnect); + tcp->Connect(ra->address); + } else { + requests_.Send(new AllocateRequest(this)); + } +} + +void RelayEntry::OnConnect(const SocketAddress& mapped_addr) { + ProtocolType proto = PROTO_UDP; + LOG(INFO) << "Relay allocate succeeded: " << ProtoToString(proto) << " @ " << mapped_addr.ToString(); + connected_ = true; + + port_->AddExternalAddress(ProtocolAddress(mapped_addr, proto)); + port_->SetReady(); +} + +int RelayEntry::SendTo(const void* data, + size_t size, + const SocketAddress& addr) { + + // If this connection is locked to the address given, then we can send the + // packet with no wrapper. + if (locked_ && (ext_addr_ == addr)) + return SendPacket(data, size); + + // Otherwise, we must wrap the given data in a STUN SEND request so that we + // can communicate the destination address to the server. + // + // Note that we do not use a StunRequest here. This is because there is + // likely no reason to resend this packet. If it is late, we just drop it. + // The next send to this address will try again. + + StunMessage request; + request.SetType(STUN_SEND_REQUEST); + request.SetTransactionID(CreateRandomString(16)); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE); + magic_cookie_attr->CopyBytes(port_->magic_cookie().c_str(), + (uint16)port_->magic_cookie().size()); + request.AddAttribute(magic_cookie_attr); + + StunByteStringAttribute* username_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + username_attr->CopyBytes(port_->username_fragment().c_str(), + (uint16)port_->username_fragment().size()); + request.AddAttribute(username_attr); + + StunAddressAttribute* addr_attr = + StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS); + addr_attr->SetFamily(1); + addr_attr->SetIP(addr.ip()); + addr_attr->SetPort(addr.port()); + request.AddAttribute(addr_attr); + + // Attempt to lock + if (ext_addr_ == addr) { + StunUInt32Attribute* options_attr = + StunAttribute::CreateUInt32(STUN_ATTR_OPTIONS); + options_attr->SetValue(0x1); + request.AddAttribute(options_attr); + } + + StunByteStringAttribute* data_attr = + StunAttribute::CreateByteString(STUN_ATTR_DATA); + data_attr->CopyBytes(data, (uint16)size); + request.AddAttribute(data_attr); + + // TODO: compute the HMAC. + + ByteBuffer buf; + request.Write(&buf); + + return SendPacket(buf.Data(), buf.Length()); +} + +void RelayEntry::ScheduleKeepAlive() { + requests_.SendDelayed(new AllocateRequest(this), KEEPALIVE_DELAY); +} + +void RelayEntry::HandleConnectFailure() { + //if (GetMillisecondCount() - start_time_ > RETRY_TIMEOUT) + // return; + //ScheduleKeepAlive(); + + connected_ = false; + port()->DisposeSocket(socket_); + socket_ = 0; + server_index_ += 1; + Connect(); +} + +void RelayEntry::OnSocketConnect(AsyncTCPSocket* socket) { + assert(socket == socket_); + LOG(INFO) << "relay tcp connected to " << socket->GetRemoteAddress().ToString(); + requests_.Send(new AllocateRequest(this)); +} + +void RelayEntry::OnSocketClose(AsyncTCPSocket* socket, int error) { + assert(socket == socket_); + PLOG(LERROR, error) << "relay tcp connect failed"; + HandleConnectFailure(); +} + +void RelayEntry::OnReadPacket(const char* data, + size_t size, + const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + assert(socket == socket_); + //assert(remote_addr == port_->server_addr()); TODO: are we worried about this? + + // If the magic cookie is not present, then this is an unwrapped packet sent + // by the server, The actual remote address is the one we recorded. + if (!port_->HasMagicCookie(data, size)) { + if (locked_) { + port_->OnReadPacket(data, size, ext_addr_); + } else { + LOG(WARNING) << "Dropping packet: entry not locked"; + } + return; + } + + ByteBuffer buf(data, size); + StunMessage msg; + if (!msg.Read(&buf)) { + LOG(INFO) << "Incoming packet was not STUN"; + return; + } + + // The incoming packet should be a STUN ALLOCATE response, SEND response, or + // DATA indication. + if (requests_.CheckResponse(&msg)) { + return; + } else if (msg.type() == STUN_SEND_RESPONSE) { + if (const StunUInt32Attribute* options_attr = msg.GetUInt32(STUN_ATTR_OPTIONS)) { + if (options_attr->value() & 0x1) { + locked_ = true; + } + } + return; + } else if (msg.type() != STUN_DATA_INDICATION) { + LOG(INFO) << "Received BAD stun type from server: " << msg.type() + ; + return; + } + + // This must be a data indication. + + const StunAddressAttribute* addr_attr = + msg.GetAddress(STUN_ATTR_SOURCE_ADDRESS2); + if (!addr_attr) { + LOG(INFO) << "Data indication has no source address"; + return; + } else if (addr_attr->family() != 1) { + LOG(INFO) << "Source address has bad family"; + return; + } + + SocketAddress remote_addr2(addr_attr->ip(), addr_attr->port()); + + const StunByteStringAttribute* data_attr = msg.GetByteString(STUN_ATTR_DATA); + if (!data_attr) { + LOG(INFO) << "Data indication has no data"; + return; + } + + // Process the actual data and remote address in the normal manner. + port_->OnReadPacket(data_attr->bytes(), data_attr->length(), remote_addr2); +} + +void RelayEntry::OnSendPacket(const void* data, size_t size) { + SendPacket(data, size); +} + +int RelayEntry::SendPacket(const void* data, size_t size) { + const ProtocolAddress * ra = port_->ServerAddress(server_index_); + if (!ra) { + socket_->SetError(ENOTCONN); + return SOCKET_ERROR; + } + int sent = socket_->SendTo(data, size, ra->address); + if (sent <= 0) { + LOG(LS_VERBOSE) << "sendto: " << std::strerror(socket_->GetError()); + assert(sent < 0); + } + return sent; +} + +AllocateRequest::AllocateRequest(RelayEntry* entry) : entry_(entry) { + start_time_ = GetMillisecondCount(); +} + +void AllocateRequest::Prepare(StunMessage* request) { + request->SetType(STUN_ALLOCATE_REQUEST); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE); + magic_cookie_attr->CopyBytes( + entry_->port()->magic_cookie().c_str(), + (uint16)entry_->port()->magic_cookie().size()); + request->AddAttribute(magic_cookie_attr); + + StunByteStringAttribute* username_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + username_attr->CopyBytes( + entry_->port()->username_fragment().c_str(), + (uint16)entry_->port()->username_fragment().size()); + request->AddAttribute(username_attr); +} + +int AllocateRequest::GetNextDelay() { + int delay = 100 * _max(1 << count_, 2); + count_ += 1; + if (count_ == 5) + timeout_ = true; + return delay; +} + +void AllocateRequest::OnResponse(StunMessage* response) { + const StunAddressAttribute* addr_attr = + response->GetAddress(STUN_ATTR_MAPPED_ADDRESS); + if (!addr_attr) { + LOG(INFO) << "Allocate response missing mapped address."; + } else if (addr_attr->family() != 1) { + LOG(INFO) << "Mapped address has bad family"; + } else { + SocketAddress addr(addr_attr->ip(), addr_attr->port()); + entry_->OnConnect(addr); + } + + // We will do a keep-alive regardless of whether this request suceeds. + // This should have almost no impact on network usage. + entry_->ScheduleKeepAlive(); +} + +void AllocateRequest::OnErrorResponse(StunMessage* response) { + const StunErrorCodeAttribute* attr = response->GetErrorCode(); + if (!attr) { + LOG(INFO) << "Bad allocate response error code"; + } else { + LOG(INFO) << "Allocate error response:" + << " code=" << static_cast(attr->error_code()) + << " reason='" << attr->reason() << "'"; + } + + if (GetMillisecondCount() - start_time_ <= RETRY_TIMEOUT) + entry_->ScheduleKeepAlive(); +} + +void AllocateRequest::OnTimeout() { + LOG(INFO) << "Allocate request timed out"; + entry_->HandleConnectFailure(); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.h new file mode 100644 index 00000000..7cfdc015 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.h @@ -0,0 +1,93 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __RELAYPORT_H__ +#define __RELAYPORT_H__ + +#include "talk/p2p/base/port.h" +#include "talk/p2p/base/stunrequest.h" +#include + +namespace cricket { + +extern const std::string RELAY_PORT_TYPE; +class RelayEntry; + +// Communicates using an allocated port on the relay server. +class RelayPort: public Port { +public: + RelayPort( + Thread* thread, SocketFactory* factory, Network*, + const SocketAddress& local_addr, + const std::string& username, const std::string& password, + const std::string& magic_cookie); + virtual ~RelayPort(); + + void AddServerAddress(const ProtocolAddress& addr); + void AddExternalAddress(const ProtocolAddress& addr); + + typedef std::pair OptionValue; + const std::vector& options() const { return options_; } + + const std::string& magic_cookie() const { return magic_cookie_; } + bool HasMagicCookie(const char* data, size_t size); + + virtual void PrepareAddress(); + virtual Connection* CreateConnection(const Candidate& address, CandidateOrigin origin); + + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError(); + + const ProtocolAddress * ServerAddress(size_t index) const; + + void DisposeSocket(AsyncPacketSocket * socket); + +protected: + void SetReady(); + + virtual int SendTo(const void* data, size_t size, const SocketAddress& addr, bool payload); + virtual void OnMessage(Message *pmsg); + + // Dispatches the given packet to the port or connection as appropriate. + void OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr); + +private: + friend class RelayEntry; + + SocketAddress local_addr_; + std::deque server_addr_; + bool ready_; + std::vector entries_; + std::vector options_; + std::string magic_cookie_; + int error_; +}; + +} // namespace cricket + +#endif // __RELAYPORT_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.cc new file mode 100644 index 00000000..bb52a1d5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.cc @@ -0,0 +1,657 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/p2p/base/relayserver.h" +#include "talk/p2p/base/helpers.h" +#include +#include +#include +#include + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +// By default, we require a ping every 90 seconds. +const int MAX_LIFETIME = 15 * 60 * 1000; + +// The number of bytes in each of the usernames we use. +const uint32 USERNAME_LENGTH = 16; + +// Calls SendTo on the given socket and logs any bad results. +void Send(AsyncPacketSocket* socket, const char* bytes, size_t size, + const SocketAddress& addr) { + int result = socket->SendTo(bytes, size, addr); + if (result < int(size)) { + std::cerr << "SendTo wrote only " << result << " of " << int(size) + << " bytes" << std::endl; + } else if (result < 0) { + std::cerr << "SendTo: " << std::strerror(errno) << std::endl; + } +} + +// Sends the given STUN message on the given socket. +void SendStun(const StunMessage& msg, + AsyncPacketSocket* socket, + const SocketAddress& addr) { + ByteBuffer buf; + msg.Write(&buf); + Send(socket, buf.Data(), buf.Length(), addr); +} + +// Constructs a STUN error response and sends it on the given socket. +void SendStunError(const StunMessage& msg, AsyncPacketSocket* socket, + const SocketAddress& remote_addr, int error_code, + const char* error_desc, const std::string& magic_cookie) { + + StunMessage err_msg; + err_msg.SetType(GetStunErrorResponseType(msg.type())); + err_msg.SetTransactionID(msg.transaction_id()); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE); + if (magic_cookie.size() == 0) + magic_cookie_attr->CopyBytes(cricket::STUN_MAGIC_COOKIE_VALUE, 4); + else + magic_cookie_attr->CopyBytes(magic_cookie.c_str(), magic_cookie.size()); + err_msg.AddAttribute(magic_cookie_attr); + + StunErrorCodeAttribute* err_code = StunAttribute::CreateErrorCode(); + err_code->SetErrorClass(error_code / 100); + err_code->SetNumber(error_code % 100); + err_code->SetReason(error_desc); + err_msg.AddAttribute(err_code); + + SendStun(err_msg, socket, remote_addr); +} + +RelayServer::RelayServer(Thread* thread) : thread_(thread) { +} + +RelayServer::~RelayServer() { + for (unsigned i = 0; i < internal_sockets_.size(); i++) + delete internal_sockets_[i]; + for (unsigned i = 0; i < external_sockets_.size(); i++) + delete external_sockets_[i]; +} + +void RelayServer::AddInternalSocket(AsyncPacketSocket* socket) { + assert(internal_sockets_.end() == + std::find(internal_sockets_.begin(), internal_sockets_.end(), socket)); + internal_sockets_.push_back(socket); + socket->SignalReadPacket.connect(this, &RelayServer::OnInternalPacket); +} + +void RelayServer::RemoveInternalSocket(AsyncPacketSocket* socket) { + SocketList::iterator iter = + std::find(internal_sockets_.begin(), internal_sockets_.end(), socket); + assert(iter != internal_sockets_.end()); + internal_sockets_.erase(iter); + socket->SignalReadPacket.disconnect(this); +} + +void RelayServer::AddExternalSocket(AsyncPacketSocket* socket) { + assert(external_sockets_.end() == + std::find(external_sockets_.begin(), external_sockets_.end(), socket)); + external_sockets_.push_back(socket); + socket->SignalReadPacket.connect(this, &RelayServer::OnExternalPacket); +} + +void RelayServer::RemoveExternalSocket(AsyncPacketSocket* socket) { + SocketList::iterator iter = + std::find(external_sockets_.begin(), external_sockets_.end(), socket); + assert(iter != external_sockets_.end()); + external_sockets_.erase(iter); + socket->SignalReadPacket.disconnect(this); +} + +void RelayServer::OnInternalPacket( + const char* bytes, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + + // Get the address of the connection we just received on. + SocketAddressPair ap(remote_addr, socket->GetLocalAddress()); + assert(!ap.destination().IsAny()); + + // If this did not come from an existing connection, it should be a STUN + // allocate request. + ConnectionMap::iterator piter = connections_.find(ap); + if (piter == connections_.end()) { + HandleStunAllocate(bytes, size, ap, socket); + return; + } + + RelayServerConnection* int_conn = piter->second; + + // Handle STUN requests to the server itself. + if (int_conn->binding()->HasMagicCookie(bytes, size)) { + HandleStun(int_conn, bytes, size); + return; + } + + // Otherwise, this is a non-wrapped packet that we are to forward. Make sure + // that this connection has been locked. (Otherwise, we would not know what + // address to forward to.) + if (!int_conn->locked()) { + std::cerr << "Dropping packet: connection not locked" << std::endl; + return; + } + + // Forward this to the destination address into the connection. + RelayServerConnection* ext_conn = int_conn->binding()->GetExternalConnection( + int_conn->default_destination()); + if (ext_conn) { + // TODO: Check the HMAC. + ext_conn->Send(bytes, size); + } else { + // This happens very often and is not an error. + //std::cerr << "Dropping packet: no external connection" << std::endl; + } +} + +void RelayServer::OnExternalPacket( + const char* bytes, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + + // Get the address of the connection we just received on. + SocketAddressPair ap(remote_addr, socket->GetLocalAddress()); + assert(!ap.destination().IsAny()); + + // If this connection already exists, then forward the traffic. + ConnectionMap::iterator piter = connections_.find(ap); + if (piter != connections_.end()) { + // TODO: Check the HMAC. + RelayServerConnection* ext_conn = piter->second; + RelayServerConnection* int_conn = + ext_conn->binding()->GetInternalConnection( + ext_conn->addr_pair().source()); + assert(int_conn); + int_conn->Send(bytes, size, ext_conn->addr_pair().source()); + return; + } + + // The first packet should always be a STUN / TURN packet. If it isn't, then + // we should just ignore this packet. + StunMessage msg; + ByteBuffer buf = ByteBuffer(bytes, size); + if (!msg.Read(&buf)) { + std::cerr << "Dropping packet: first packet not STUN" << std::endl; + return; + } + + // The initial packet should have a username (which identifies the binding). + const StunByteStringAttribute* username_attr = + msg.GetByteString(STUN_ATTR_USERNAME); + if (!username_attr) { + std::cerr << "Dropping packet: no username" << std::endl; + return; + } + + uint32 length = _min(uint32(username_attr->length()), USERNAME_LENGTH); + std::string username(username_attr->bytes(), length); + // TODO: Check the HMAC. + + // The binding should already be present. + BindingMap::iterator biter = bindings_.find(username); + if (biter == bindings_.end()) { + // TODO: Turn this back on. This is the sign of a client bug. + //std::cerr << "Dropping packet: no binding with username" << std::endl; + return; + } + + // Add this authenticted connection to the binding. + RelayServerConnection* ext_conn = + new RelayServerConnection(biter->second, ap, socket); + ext_conn->binding()->AddExternalConnection(ext_conn); + AddConnection(ext_conn); + + // We always know where external packets should be forwarded, so we can lock + // them from the beginning. + ext_conn->Lock(); + + // Send this message on the appropriate internal connection. + RelayServerConnection* int_conn = ext_conn->binding()->GetInternalConnection( + ext_conn->addr_pair().source()); + assert(int_conn); + int_conn->Send(bytes, size, ext_conn->addr_pair().source()); +} + +bool RelayServer::HandleStun( + const char* bytes, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket, std::string* username, StunMessage* msg) { + + // Parse this into a stun message. + ByteBuffer buf = ByteBuffer(bytes, size); + if (!msg->Read(&buf)) { + SendStunError(*msg, socket, remote_addr, 400, "Bad Request", ""); + return false; + } + + // The initial packet should have a username (which identifies the binding). + const StunByteStringAttribute* username_attr = + msg->GetByteString(STUN_ATTR_USERNAME); + if (!username_attr) { + SendStunError(*msg, socket, remote_addr, 432, "Missing Username", ""); + return false; + } + + // Record the username if requested. + if (username) + username->append(username_attr->bytes(), username_attr->length()); + + // TODO: Check for unknown attributes (<= 0x7fff) + + return true; +} + +void RelayServer::HandleStunAllocate( + const char* bytes, size_t size, const SocketAddressPair& ap, + AsyncPacketSocket* socket) { + + // Make sure this is a valid STUN request. + StunMessage request; + std::string username; + if (!HandleStun(bytes, size, ap.source(), socket, &username, &request)) + return; + + // Make sure this is a an allocate request. + if (request.type() != STUN_ALLOCATE_REQUEST) { + SendStunError(request, + socket, + ap.source(), + 600, + "Operation Not Supported", + ""); + return; + } + + // TODO: Check the HMAC. + + // Find or create the binding for this username. + + RelayServerBinding* binding; + + BindingMap::iterator biter = bindings_.find(username); + if (biter != bindings_.end()) { + + binding = biter->second; + + } else { + + // NOTE: In the future, bindings will be created by the bot only. This + // else-branch will then disappear. + + // Compute the appropriate lifetime for this binding. + uint32 lifetime = MAX_LIFETIME; + const StunUInt32Attribute* lifetime_attr = + request.GetUInt32(STUN_ATTR_LIFETIME); + if (lifetime_attr) + lifetime = _min(lifetime, lifetime_attr->value() * 1000); + + binding = new RelayServerBinding(this, username, "0", lifetime); + binding->SignalTimeout.connect(this, &RelayServer::OnTimeout); + bindings_[username] = binding; + + std::cout << "Added new binding: " << bindings_.size() << " total" << std::endl; + } + + // Add this connection to the binding. It starts out unlocked. + RelayServerConnection* int_conn = + new RelayServerConnection(binding, ap, socket); + binding->AddInternalConnection(int_conn); + AddConnection(int_conn); + + // Now that we have a connection, this other method takes over. + HandleStunAllocate(int_conn, request); +} + +void RelayServer::HandleStun( + RelayServerConnection* int_conn, const char* bytes, size_t size) { + + // Make sure this is a valid STUN request. + StunMessage request; + std::string username; + if (!HandleStun(bytes, size, int_conn->addr_pair().source(), + int_conn->socket(), &username, &request)) + return; + + // Make sure the username is the one were were expecting. + if (username != int_conn->binding()->username()) { + int_conn->SendStunError(request, 430, "Stale Credentials"); + return; + } + + // TODO: Check the HMAC. + + // Send this request to the appropriate handler. + if (request.type() == STUN_SEND_REQUEST) + HandleStunSend(int_conn, request); + else if (request.type() == STUN_ALLOCATE_REQUEST) + HandleStunAllocate(int_conn, request); + else + int_conn->SendStunError(request, 600, "Operation Not Supported"); +} + +void RelayServer::HandleStunAllocate( + RelayServerConnection* int_conn, const StunMessage& request) { + + // Create a response message that includes an address with which external + // clients can communicate. + + StunMessage response; + response.SetType(STUN_ALLOCATE_RESPONSE); + response.SetTransactionID(request.transaction_id()); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE); + magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(), + int_conn->binding()->magic_cookie().size()); + response.AddAttribute(magic_cookie_attr); + + size_t index = rand() % external_sockets_.size(); + SocketAddress ext_addr = external_sockets_[index]->GetLocalAddress(); + + StunAddressAttribute* addr_attr = + StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); + addr_attr->SetFamily(1); + addr_attr->SetIP(ext_addr.ip()); + addr_attr->SetPort(ext_addr.port()); + response.AddAttribute(addr_attr); + + StunUInt32Attribute* res_lifetime_attr = + StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME); + res_lifetime_attr->SetValue(int_conn->binding()->lifetime() / 1000); + response.AddAttribute(res_lifetime_attr); + + // TODO: Support transport-prefs (preallocate RTCP port). + // TODO: Support bandwidth restrictions. + // TODO: Add message integrity check. + + // Send a response to the caller. + int_conn->SendStun(response); +} + +void RelayServer::HandleStunSend( + RelayServerConnection* int_conn, const StunMessage& request) { + + const StunAddressAttribute* addr_attr = + request.GetAddress(STUN_ATTR_DESTINATION_ADDRESS); + if (!addr_attr) { + int_conn->SendStunError(request, 400, "Bad Request"); + return; + } + + const StunByteStringAttribute* data_attr = + request.GetByteString(STUN_ATTR_DATA); + if (!data_attr) { + int_conn->SendStunError(request, 400, "Bad Request"); + return; + } + + SocketAddress ext_addr(addr_attr->ip(), addr_attr->port()); + RelayServerConnection* ext_conn = + int_conn->binding()->GetExternalConnection(ext_addr); + if (!ext_conn) { + // This happens very often and is not an error. + //std::cerr << "Dropping packet: no external connection" << std::endl; + return; + } + + ext_conn->Send(data_attr->bytes(), data_attr->length()); + + const StunUInt32Attribute* options_attr = + request.GetUInt32(STUN_ATTR_OPTIONS); + if (options_attr && (options_attr->value() & 0x01 != 0)) { + int_conn->set_default_destination(ext_addr); + int_conn->Lock(); + + StunMessage response; + response.SetType(STUN_SEND_RESPONSE); + response.SetTransactionID(request.transaction_id()); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE); + magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(), + int_conn->binding()->magic_cookie().size()); + response.AddAttribute(magic_cookie_attr); + + StunUInt32Attribute* options2_attr = + StunAttribute::CreateUInt32(cricket::STUN_ATTR_OPTIONS); + options2_attr->SetValue(0x01); + response.AddAttribute(options2_attr); + + int_conn->SendStun(response); + } +} + +void RelayServer::AddConnection(RelayServerConnection* conn) { + assert(connections_.find(conn->addr_pair()) == connections_.end()); + connections_[conn->addr_pair()] = conn; +} + +void RelayServer::RemoveConnection(RelayServerConnection* conn) { + ConnectionMap::iterator iter = connections_.find(conn->addr_pair()); + assert(iter != connections_.end()); + connections_.erase(iter); +} + +void RelayServer::RemoveBinding(RelayServerBinding* binding) { + BindingMap::iterator iter = bindings_.find(binding->username()); + assert(iter != bindings_.end()); + bindings_.erase(iter); + + std::cout << "Removed a binding: " << bindings_.size() << " remaining" << std::endl; +} + +void RelayServer::OnTimeout(RelayServerBinding* binding) { + // This call will result in all of the necessary clean-up. + delete binding; +} + +RelayServerConnection::RelayServerConnection( + RelayServerBinding* binding, const SocketAddressPair& addrs, + AsyncPacketSocket* socket) + : binding_(binding), addr_pair_(addrs), socket_(socket), locked_(false) { + + // The creation of a new connection constitutes a use of the binding. + binding_->NoteUsed(); +} + +RelayServerConnection::~RelayServerConnection() { + // Remove this connection from the server's map (if it exists there). + binding_->server()->RemoveConnection(this); +} + +void RelayServerConnection::Send(const char* data, size_t size) { + // Note that the binding has been used again. + binding_->NoteUsed(); + + cricket::Send(socket_, data, size, addr_pair_.source()); +} + +void RelayServerConnection::Send( + const char* data, size_t size, const SocketAddress& from_addr) { + // If the from address is known to the client, we don't need to send it. + if (locked() && (from_addr == default_dest_)) { + Send(data, size); + return; + } + + // Wrap the given data in a data-indication packet. + + StunMessage msg; + msg.SetType(STUN_DATA_INDICATION); + msg.SetTransactionID("0000000000000000"); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE); + magic_cookie_attr->CopyBytes(binding_->magic_cookie().c_str(), + binding_->magic_cookie().size()); + msg.AddAttribute(magic_cookie_attr); + + StunAddressAttribute* addr_attr = + StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS2); + addr_attr->SetFamily(1); + addr_attr->SetIP(from_addr.ip()); + addr_attr->SetPort(from_addr.port()); + msg.AddAttribute(addr_attr); + + StunByteStringAttribute* data_attr = + StunAttribute::CreateByteString(STUN_ATTR_DATA); + assert(size <= 65536); + data_attr->CopyBytes(data, uint16(size)); + msg.AddAttribute(data_attr); + + SendStun(msg); +} + +void RelayServerConnection::SendStun(const StunMessage& msg) { + // Note that the binding has been used again. + binding_->NoteUsed(); + + cricket::SendStun(msg, socket_, addr_pair_.source()); +} + +void RelayServerConnection::SendStunError( + const StunMessage& request, int error_code, const char* error_desc) { + // An error does not indicate use. If no legitimate use off the binding + // occurs, we want it to be cleaned up even if errors are still occuring. + + cricket::SendStunError( + request, socket_, addr_pair_.source(), error_code, error_desc, + binding_->magic_cookie()); +} + +void RelayServerConnection::Lock() { + locked_ = true; +} + +void RelayServerConnection::Unlock() { + locked_ = false; +} + +// IDs used for posted messages: +const uint32 MSG_LIFETIME_TIMER = 1; + +RelayServerBinding::RelayServerBinding( + RelayServer* server, const std::string& username, + const std::string& password, uint32 lifetime) + : server_(server), username_(username), password_(password), + lifetime_(lifetime) { + + // For now, every connection uses the standard magic cookie value. + magic_cookie_.append( + reinterpret_cast(STUN_MAGIC_COOKIE_VALUE), 4); + + // Initialize the last-used time to now. + NoteUsed(); + + // Set the first timeout check. + server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER); +} + +RelayServerBinding::~RelayServerBinding() { + // Clear the outstanding timeout check. + server_->thread()->Clear(this); + + // Clean up all of the connections. + for (size_t i = 0; i < internal_connections_.size(); ++i) + delete internal_connections_[i]; + for (size_t i = 0; i < external_connections_.size(); ++i) + delete external_connections_[i]; + + // Remove this binding from the server's map. + server_->RemoveBinding(this); +} + +void RelayServerBinding::AddInternalConnection(RelayServerConnection* conn) { + internal_connections_.push_back(conn); +} + +void RelayServerBinding::AddExternalConnection(RelayServerConnection* conn) { + external_connections_.push_back(conn); +} + +void RelayServerBinding::NoteUsed() { + last_used_ = GetMillisecondCount(); +} + +bool RelayServerBinding::HasMagicCookie(const char* bytes, size_t size) const { + if (size < 24 + magic_cookie_.size()) { + return false; + } else { + return 0 == std::memcmp( + bytes + 24, magic_cookie_.c_str(), magic_cookie_.size()); + } +} + +RelayServerConnection* RelayServerBinding::GetInternalConnection( + const SocketAddress& ext_addr) { + + // Look for an internal connection that is locked to this address. + for (size_t i = 0; i < internal_connections_.size(); ++i) { + if (internal_connections_[i]->locked() && + (ext_addr == internal_connections_[i]->default_destination())) + return internal_connections_[i]; + } + + // If one was not found, we send to the first connection. + assert(internal_connections_.size() > 0); + return internal_connections_[0]; +} + +RelayServerConnection* RelayServerBinding::GetExternalConnection( + const SocketAddress& ext_addr) { + for (size_t i = 0; i < external_connections_.size(); ++i) { + if (ext_addr == external_connections_[i]->addr_pair().source()) + return external_connections_[i]; + } + return 0; +} + +void RelayServerBinding::OnMessage(Message *pmsg) { + if (pmsg->message_id == MSG_LIFETIME_TIMER) { + assert(!pmsg->pdata); + + // If the lifetime timeout has been exceeded, then send a signal. + // Otherwise, just keep waiting. + if (GetMillisecondCount() >= last_used_ + lifetime_) { + SignalTimeout(this); + } else { + server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER); + } + + } else { + assert(false); + } +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.h new file mode 100644 index 00000000..01dc3678 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.h @@ -0,0 +1,210 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __RELAYSERVER_H__ +#define __RELAYSERVER_H__ + +#include "talk/base/asyncudpsocket.h" +#include "talk/base/socketaddresspair.h" +#include "talk/base/thread.h" +#include "talk/base/jtime.h" +#include "talk/p2p/base/stun.h" + +#include +#include +#include + +namespace cricket { + +class RelayServerBinding; +class RelayServerConnection; + +// Relays traffic between connections to the server that are "bound" together. +// All connections created with the same username/password are bound together. +class RelayServer : public sigslot::has_slots<> { +public: + // Creates a server, which will use this thread to post messages to itself. + RelayServer(Thread* thread); + ~RelayServer(); + + Thread* thread() { return thread_; } + + // Updates the set of sockets that the server uses to talk to "internal" + // clients. These are clients that do the "port allocations". + void AddInternalSocket(AsyncPacketSocket* socket); + void RemoveInternalSocket(AsyncPacketSocket* socket); + + // Updates the set of sockets that the server uses to talk to "external" + // clients. These are the clients that do not do allocations. They do not + // know that these addresses represent a relay server. + void AddExternalSocket(AsyncPacketSocket* socket); + void RemoveExternalSocket(AsyncPacketSocket* socket); + +private: + typedef std::vector SocketList; + typedef std::map BindingMap; + typedef std::map ConnectionMap; + + Thread* thread_; + SocketList internal_sockets_; + SocketList external_sockets_; + BindingMap bindings_; + ConnectionMap connections_; + + // Called when a packet is received by the server on one of its sockets. + void OnInternalPacket( + const char* bytes, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + void OnExternalPacket( + const char* bytes, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + + // Processes the relevant STUN request types from the client. + bool HandleStun(const char* bytes, size_t size, + const SocketAddress& remote_addr, AsyncPacketSocket* socket, + std::string* username, StunMessage* msg); + void HandleStunAllocate(const char* bytes, size_t size, + const SocketAddressPair& ap, + AsyncPacketSocket* socket); + void HandleStun(RelayServerConnection* int_conn, const char* bytes, + size_t size); + void HandleStunAllocate(RelayServerConnection* int_conn, + const StunMessage& msg); + void HandleStunSend(RelayServerConnection* int_conn, const StunMessage& msg); + + // Adds/Removes the a connection or binding. + void AddConnection(RelayServerConnection* conn); + void RemoveConnection(RelayServerConnection* conn); + void RemoveBinding(RelayServerBinding* binding); + + // Called when the timer for checking lifetime times out. + void OnTimeout(RelayServerBinding* binding); + + friend class RelayServerConnection; + friend class RelayServerBinding; +}; + +// Maintains information about a connection to the server. Each connection is +// part of one and only one binding. +class RelayServerConnection { +public: + RelayServerConnection(RelayServerBinding* binding, + const SocketAddressPair& addrs, + AsyncPacketSocket* socket); + ~RelayServerConnection(); + + RelayServerBinding* binding() { return binding_; } + AsyncPacketSocket* socket() { return socket_; } + + // Returns a pair where the source is the remote address and the destination + // is the local address. + const SocketAddressPair& addr_pair() { return addr_pair_; } + + // Sends a packet to the connected client. If an address is provided, then + // we make sure the internal client receives it, wrapping if necessary. + void Send(const char* data, size_t size); + void Send(const char* data, size_t size, const SocketAddress& ext_addr); + + // Sends a STUN message to the connected client with no wrapping. + void SendStun(const StunMessage& msg); + void SendStunError(const StunMessage& request, int code, const char* desc); + + // A locked connection is one for which we know the intended destination of + // any raw packet received. + bool locked() const { return locked_; } + void Lock(); + void Unlock(); + + // Records the address that raw packets should be forwarded to (for internal + // packets only; for external, we already know where they go). + const SocketAddress& default_destination() const { return default_dest_; } + void set_default_destination(const SocketAddress& addr) { + default_dest_ = addr; + } + +private: + RelayServerBinding* binding_; + SocketAddressPair addr_pair_; + AsyncPacketSocket* socket_; + bool locked_; + SocketAddress default_dest_; +}; + +// Records a set of internal and external connections that we relay between, +// or in other words, that are "bound" together. +class RelayServerBinding : public MessageHandler { +public: + RelayServerBinding( + RelayServer* server, const std::string& username, + const std::string& password, uint32 lifetime); + virtual ~RelayServerBinding(); + + RelayServer* server() { return server_; } + uint32 lifetime() { return lifetime_; } + const std::string& username() { return username_; } + const std::string& password() { return password_; } + const std::string& magic_cookie() { return magic_cookie_; } + + // Adds/Removes a connection into the binding. + void AddInternalConnection(RelayServerConnection* conn); + void AddExternalConnection(RelayServerConnection* conn); + + // We keep track of the use of each binding. If we detect that it was not + // used for longer than the lifetime, then we send a signal. + void NoteUsed(); + sigslot::signal1 SignalTimeout; + + // Determines whether the given packet has the magic cookie present (in the + // right place). + bool HasMagicCookie(const char* bytes, size_t size) const; + + // Determines the connection to use to send packets to or from the given + // external address. + RelayServerConnection* GetInternalConnection(const SocketAddress& ext_addr); + RelayServerConnection* GetExternalConnection(const SocketAddress& ext_addr); + + // MessageHandler: + void OnMessage(Message *pmsg); + +private: + RelayServer* server_; + + std::string username_; + std::string password_; + std::string magic_cookie_; + + std::vector internal_connections_; + std::vector external_connections_; + + uint32 lifetime_; + uint32 last_used_; + // TODO: bandwidth +}; + +} // namespace cricket + +#endif // __RELAYSERVER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.pro b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.pro new file mode 100644 index 00000000..41bc6b63 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.pro @@ -0,0 +1,14 @@ +TEMPLATE = app +INCLUDEPATH = ../../.. +DEFINES += POSIX + +include(../../../../../conf.pri) + +# Input +SOURCES += \ + relayserver.cc \ + relayserver_main.cc \ + ../../base/host.cc \ + ../../base/socketaddresspair.cc + +LIBS += ../../../liblibjingle.a diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver_main.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver_main.cc new file mode 100644 index 00000000..5f624f37 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver_main.cc @@ -0,0 +1,75 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/host.h" +#include "talk/base/thread.h" +#include "talk/p2p/base/relayserver.h" +#include +#include + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +using namespace cricket; + +int main(int argc, char **argv) { + if (argc != 1) { + std::cerr << "usage: relayserver" << std::endl; + return 1; + } + + assert(LocalHost().networks().size() >= 2); + SocketAddress int_addr(LocalHost().networks()[1]->ip(), 5000); + SocketAddress ext_addr(LocalHost().networks()[1]->ip(), 5001); + + Thread *pthMain = Thread::Current(); + + AsyncUDPSocket* int_socket = CreateAsyncUDPSocket(pthMain->socketserver()); + if (int_socket->Bind(int_addr) < 0) { + std::cerr << "bind: " << std::strerror(errno) << std::endl; + return 1; + } + + AsyncUDPSocket* ext_socket = CreateAsyncUDPSocket(pthMain->socketserver()); + if (ext_socket->Bind(ext_addr) < 0) { + std::cerr << "bind: " << std::strerror(errno) << std::endl; + return 1; + } + + RelayServer server(pthMain); + server.AddInternalSocket(int_socket); + server.AddExternalSocket(ext_socket); + + std::cout << "Listening internally at " << int_addr.ToString() << std::endl; + std::cout << "Listening externally at " << ext_addr.ToString() << std::endl; + + pthMain->Loop(); + return 0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.cc new file mode 100644 index 00000000..73873338 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.cc @@ -0,0 +1,421 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/p2p/base/helpers.h" +#include "talk/p2p/base/session.h" + +namespace cricket { + +const uint32 MSG_TIMEOUT = 1; +const uint32 MSG_ERROR = 2; +const uint32 MSG_STATE = 3; + +Session::Session(SessionManager *session_manager, const std::string &name, + const SessionID& id) { + session_manager_ = session_manager; + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + name_ = name; + id_ = id; + error_ = ERROR_NONE; + state_ = STATE_INIT; + initiator_ = false; + description_ = NULL; + remote_description_ = NULL; + socket_manager_ = new SocketManager(session_manager_); + socket_manager_->SignalCandidatesReady.connect(this, &Session::OnCandidatesReady); + socket_manager_->SignalNetworkError.connect(this, &Session::OnNetworkError); + socket_manager_->SignalState.connect(this, &Session::OnSocketState); + socket_manager_->SignalRequestSignaling.connect(this, &Session::OnRequestSignaling); +} + +Session::~Session() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + delete description_; + delete remote_description_; + delete socket_manager_; + session_manager_->signaling_thread()->Clear(this); +} + +P2PSocket *Session::CreateSocket(const std::string &name) { + return socket_manager_->CreateSocket(name); +} + +void Session::DestroySocket(P2PSocket *socket) { + socket_manager_->DestroySocket(socket); +} + +void Session::OnCandidatesReady(const std::vector& candidates) { + SendSessionMessage(SessionMessage::TYPE_CANDIDATES, NULL, &candidates, NULL); +} + +void Session::OnNetworkError() { + // Socket manager is experiencing a network error trying to allocate + // network resources (usually port allocation) + + set_error(ERROR_NETWORK); +} + +void Session::OnSocketState() { + // If the call is not in progress, then we don't care about writability. + // We have separate timers for making sure we transition back to the in- + // progress state in time. + if (state_ != STATE_INPROGRESS) + return; + + // Put the timer into the write state. This is called when the state changes, + // so we will restart the timer each time we lose writability. + if (socket_manager_->writable()) { + session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); + } else { + session_manager_->signaling_thread()->PostDelayed( + session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); + } +} + +void Session::OnRequestSignaling() { + SignalRequestSignaling(); +} + +void Session::OnSignalingReady() { + socket_manager_->OnSignalingReady(); +} + +void Session::SendSessionMessage(SessionMessage::Type type, + const SessionDescription* description, + const std::vector* candidates, + SessionMessage::Cookie* redirect_cookie) { + SessionMessage m; + m.set_type(type); + m.set_to(remote_address_); + m.set_name(name_); + m.set_description(description); + m.set_session_id(id_); + if (candidates) + m.set_candidates(*candidates); + m.set_redirect_target(redirect_target_); + m.set_redirect_cookie(redirect_cookie); + SignalOutgoingMessage(this, m); +} + +bool Session::Initiate(const std::string &to, const SessionDescription *description) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Only from STATE_INIT + if (state_ != STATE_INIT) + return false; + + // Setup for signaling. Initiate is asynchronous. It occurs once the address + // candidates are ready. + initiator_ = true; + remote_address_ = to; + description_ = description; + SendSessionMessage(SessionMessage::TYPE_INITIATE, description, NULL, NULL); + set_state(Session::STATE_SENTINITIATE); + + // Let the socket manager know we now want the candidates + socket_manager_->StartProcessingCandidates(); + + // Start the session timeout + session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); + session_manager_->signaling_thread()->PostDelayed(session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); + return true; +} + +bool Session::Accept(const SessionDescription *description) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Only if just received initiate + if (state_ != STATE_RECEIVEDINITIATE) + return false; + + // Setup for signaling. Accept is asynchronous. It occurs once the address + // candidates are ready. + initiator_ = false; + description_ = description; + SendSessionMessage(SessionMessage::TYPE_ACCEPT, description, NULL, NULL); + set_state(Session::STATE_SENTACCEPT); + + return true; +} + +bool Session::Modify(const SessionDescription *description) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Only if session already STATE_INPROGRESS + if (state_ != STATE_INPROGRESS) + return false; + + // Modify is asynchronous. It occurs once the address candidates are ready. + // Either side can send a modify. It is only valid in an already accepted + // session. + description_ = description; + SendSessionMessage(SessionMessage::TYPE_MODIFY, description, NULL, NULL); + set_state(Session::STATE_SENTMODIFY); + + // Start the session timeout + session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); + session_manager_->signaling_thread()->PostDelayed(session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); + return true; +} + +bool Session::Redirect(const std::string& target) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Redirect is sent in response to an initiate or modify, to redirect the + // request + if (state_ != STATE_RECEIVEDINITIATE) + return false; + + initiator_ = false; + redirect_target_ = target; + SendSessionMessage(SessionMessage::TYPE_REDIRECT, NULL, NULL, NULL); + + // A redirect puts us in the same state as reject. It just sends a different + // kind of reject message, if you like. + set_state(STATE_SENTREDIRECT); + + return true; +} + +bool Session::Reject() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Reject is sent in response to an initiate or modify, to reject the + // request + if (state_ != STATE_RECEIVEDINITIATE && state_ != STATE_RECEIVEDMODIFY) + return false; + + initiator_ = false; + SendSessionMessage(SessionMessage::TYPE_REJECT, NULL, NULL, NULL); + set_state(STATE_SENTREJECT); + + return true; +} + +bool Session::Terminate() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Either side can terminate, at any time. + if (state_ == STATE_SENTTERMINATE && state_ != STATE_RECEIVEDTERMINATE) + return false; + + // But we don't need to terminate if we already rejected. The other client + // already knows that we're done with this session. + if (state_ != STATE_SENTREDIRECT) + SendSessionMessage(SessionMessage::TYPE_TERMINATE, NULL, NULL, NULL); + + set_state(STATE_SENTTERMINATE); + + return true; +} + +void Session::OnIncomingError(const SessionMessage &m) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // If a candidate message errors out or gets dropped for some reason we + // ignore the error. + if (m.type() != SessionMessage::TYPE_CANDIDATES) { + set_error(ERROR_RESPONSE); + } +} + +void Session::OnIncomingMessage(const SessionMessage &m) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + switch (m.type()) { + case SessionMessage::TYPE_INITIATE: + remote_description_ = m.description(); + remote_address_ = m.from(); + name_ = m.name(); + initiator_ = false; + set_state(STATE_RECEIVEDINITIATE); + + // Let the socket manager know we now want the initial candidates + socket_manager_->StartProcessingCandidates(); + break; + + case SessionMessage::TYPE_ACCEPT: + remote_description_ = m.description(); + set_state(STATE_RECEIVEDACCEPT); + break; + + case SessionMessage::TYPE_MODIFY: + remote_description_ = m.description(); + set_state(STATE_RECEIVEDMODIFY); + break; + + case SessionMessage::TYPE_CANDIDATES: + socket_manager_->AddRemoteCandidates(m.candidates()); + break; + + case SessionMessage::TYPE_REJECT: + set_state(STATE_RECEIVEDREJECT); + break; + + case SessionMessage::TYPE_REDIRECT: + OnRedirectMessage(m); + break; + + case SessionMessage::TYPE_TERMINATE: + set_state(STATE_RECEIVEDTERMINATE); + break; + } +} + +void Session::OnRedirectMessage(const SessionMessage &m) { + ASSERT(state_ == STATE_SENTINITIATE); + if (state_ != STATE_SENTINITIATE) + return; + + ASSERT(m.redirect_target().size() != 0); + remote_address_ = m.redirect_target(); + + SendSessionMessage(SessionMessage::TYPE_INITIATE, description_, NULL, + m.redirect_cookie()->Copy()); + + // Restart the session timeout. + session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); + session_manager_->signaling_thread()->PostDelayed(session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); + + // Reset all of the sockets back into the initial state. + socket_manager_->ResetSockets(); +} + +Session::State Session::state() { + return state_; +} + +void Session::set_state(State state) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + if (state != state_) { + state_ = state; + SignalState(this, state); + session_manager_->signaling_thread()->Post(this, MSG_STATE); + } +} + +Session::Error Session::error() { + return error_; +} + +void Session::set_error(Error error) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + if (error != error_) { + error_ = error; + SignalError(this, error); + session_manager_->signaling_thread()->Post(this, MSG_ERROR); + } +} + +const std::string &Session::name() { + return name_; +} + +const std::string &Session::remote_address() { + return remote_address_; +} + +bool Session::initiator() { + return initiator_; +} + +const SessionID& Session::id() { + return id_; +} + +const SessionDescription *Session::description() { + return description_; +} + +const SessionDescription *Session::remote_description() { + return remote_description_; +} + +SessionManager *Session::session_manager() { + return session_manager_; +} + +void Session::OnMessage(Message *pmsg) { + switch(pmsg->message_id) { + case MSG_TIMEOUT: + // Session timeout has occured. Check to see if the session is still trying + // to signal. If so, the session has timed out. + // The Sockets have their own timeout for connectivity. + set_error(ERROR_TIME); + break; + + case MSG_ERROR: + switch (error_) { + case ERROR_RESPONSE: + // This state could be reached if we get an error in response to an IQ + // or if the network is so slow we time out on an individual IQ exchange. + // In either case, Terminate (send more messages) and ignore the likely + // cascade of more errors. + + // fall through + case ERROR_NETWORK: + case ERROR_TIME: + // Time ran out - no response + Terminate(); + break; + + default: + break; + } + break; + + case MSG_STATE: + switch (state_) { + case STATE_SENTACCEPT: + case STATE_RECEIVEDACCEPT: + set_state(STATE_INPROGRESS); + session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); + OnSocketState(); // Update the writability timeout state. + break; + + case STATE_SENTREJECT: + case STATE_SENTREDIRECT: + case STATE_RECEIVEDREJECT: + Terminate(); + break; + + case STATE_SENTTERMINATE: + case STATE_RECEIVEDTERMINATE: + session_manager_->DestroySession(this); + break; + + default: + // explicitly ignoring some states here + break; + } + break; + } +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.h new file mode 100644 index 00000000..1414a375 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.h @@ -0,0 +1,140 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SESSION_H_ +#define _SESSION_H_ + +#include "talk/base/socketaddress.h" +#include "talk/p2p/base/sessiondescription.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/sessionmessage.h" +#include "talk/p2p/base/socketmanager.h" +#include "talk/p2p/base/p2psocket.h" +#include "talk/p2p/base/port.h" +#include + +namespace cricket { + +class SessionManager; +class SocketManager; + +// A specific Session created by the SessionManager +// A Session manages signaling for session setup and tear down, and connectivity +// with P2PSockets + +class Session : public MessageHandler, public sigslot::has_slots<> { +public: + enum State { + STATE_INIT = 0, + STATE_SENTINITIATE, // sent initiate, waiting for Accept or Reject + STATE_RECEIVEDINITIATE, // received an initiate. Call Accept or Reject + STATE_SENTACCEPT, // sent accept. begin connectivity establishment + STATE_RECEIVEDACCEPT, // received accept. begin connectivity establishment + STATE_SENTMODIFY, // sent modify, waiting for Accept or Reject + STATE_RECEIVEDMODIFY, // received modify, call Accept or Reject + STATE_SENTREJECT, // sent reject after receiving initiate + STATE_RECEIVEDREJECT, // received reject after sending initiate + STATE_SENTREDIRECT, // sent direct after receiving initiate + STATE_SENTTERMINATE, // sent terminate (any time / either side) + STATE_RECEIVEDTERMINATE, // received terminate (any time / either side) + STATE_INPROGRESS, // session accepted and in progress + }; + + enum Error { + ERROR_NONE = 0, // no error + ERROR_TIME, // no response to signaling + ERROR_RESPONSE, // error during signaling + ERROR_NETWORK, // network error, could not allocate network resources + }; + + Session(SessionManager *session_manager, const std::string &name, const SessionID& id); + ~Session(); + + // From MessageHandler + void OnMessage(Message *pmsg); + + P2PSocket *CreateSocket(const std::string & name); + void DestroySocket(P2PSocket *socket); + + bool Initiate(const std::string &to, const SessionDescription *description); + bool Accept(const SessionDescription *description); + bool Modify(const SessionDescription *description); + bool Reject(); + bool Redirect(const std::string& target); + bool Terminate(); + + SessionManager *session_manager(); + const std::string &name(); + const std::string &remote_address(); + bool initiator(); + const SessionID& id(); + const SessionDescription *description(); + const SessionDescription *remote_description(); + + State state(); + Error error(); + + void OnSignalingReady(); + void OnIncomingMessage(const SessionMessage &m); + void OnIncomingError(const SessionMessage &m); + + sigslot::signal2 SignalState; + sigslot::signal2 SignalError; + sigslot::signal2 SignalOutgoingMessage; + sigslot::signal0<> SignalRequestSignaling; + +private: + void SendSessionMessage(SessionMessage::Type type, + const SessionDescription* description, + const std::vector* candidates, + SessionMessage::Cookie* redirect_cookie); + void OnCandidatesReady(const std::vector& candidates); + void OnNetworkError(); + void OnSocketState(); + void OnRequestSignaling(); + void OnRedirectMessage(const SessionMessage &m); + + void set_state(State state); + void set_error(Error error); + + bool initiator_; + SessionManager *session_manager_; + SessionID id_; + SocketManager *socket_manager_; + std::string name_; + std::string remote_address_; + const SessionDescription *description_; + const SessionDescription *remote_description_; + std::string redirect_target_; + State state_; + Error error_; + CriticalSection crit_; +}; + +} // namespace cricket + +#endif // _SESSION_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessiondescription.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessiondescription.h new file mode 100644 index 00000000..28b70845 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessiondescription.h @@ -0,0 +1,42 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SESSIONDESCRIPTION_H_ +#define _SESSIONDESCRIPTION_H_ + +namespace cricket { + +// The client overrides this with whatever + +class SessionDescription { +public: + virtual ~SessionDescription() {} +}; + +} // namespace cricket + +#endif // _SESSIONDESCRIPTION_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionid.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionid.h new file mode 100644 index 00000000..a12535c0 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionid.h @@ -0,0 +1,94 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SESSIONID_H_ +#define _SESSIONID_H_ + +#include "talk/base/basictypes.h" +#include +#include + +namespace cricket { + +// Each session is identified by a pair (from,id), where id is only +// assumed to be unique to the machine identified by from. +class SessionID { +public: + SessionID() : id_str_("0") { + } + SessionID(const std::string& initiator, uint32 id) + : initiator_(initiator) { + set_id(id); + } + SessionID(const SessionID& sid) + : id_str_(sid.id_str_), initiator_(sid.initiator_) { + } + + void set_id(uint32 id) { + std::stringstream st; + st << id; + st >> id_str_; + } + const std::string id_str() const { + return id_str_; + } + void set_id_str(const std::string &id_str) { + id_str_ = id_str; + } + + const std::string &initiator() const { + return initiator_; + } + void set_initiator(const std::string &initiator) { + initiator_ = initiator; + } + + bool operator <(const SessionID& sid) const { + int r = initiator_.compare(sid.initiator_); + if (r == 0) + r = id_str_.compare(sid.id_str_); + return r < 0; + } + + bool operator ==(const SessionID& sid) const { + return (id_str_ == sid.id_str_) && (initiator_ == sid.initiator_); + } + + SessionID& operator =(const SessionID& sid) { + id_str_ = sid.id_str_; + initiator_ = sid.initiator_; + return *this; + } + +private: + std::string id_str_; + std::string initiator_; +}; + +} // namespace cricket + +#endif // _SESSIONID_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.cc new file mode 100644 index 00000000..4c1c09d9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.cc @@ -0,0 +1,173 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/common.h" +#include "talk/p2p/base/helpers.h" +#include "sessionmanager.h" + +namespace cricket { + +SessionManager::SessionManager(PortAllocator *allocator, Thread *worker) { + allocator_ = allocator; + signaling_thread_ = Thread::Current(); + if (worker == NULL) { + worker_thread_ = Thread::Current(); + } else { + worker_thread_ = worker; + } + timeout_ = 50; +} + +SessionManager::~SessionManager() { + // Note: Session::Terminate occurs asynchronously, so it's too late to + // delete them now. They better be all gone. + ASSERT(session_map_.empty()); + //TerminateAll(); +} + +Session *SessionManager::CreateSession(const std::string &name, const std::string& initiator) { + return CreateSession(name, SessionID(initiator, CreateRandomId()), false); +} + +Session *SessionManager::CreateSession(const std::string &name, const SessionID& id, bool received_initiate) { + Session *session = new Session(this, name, id); + session_map_[session->id()] = session; + session->SignalRequestSignaling.connect(this, &SessionManager::OnRequestSignaling); + SignalSessionCreate(session, received_initiate); + return session; +} + +void SessionManager::DestroySession(Session *session) { + if (session != NULL) { + std::map::iterator it = session_map_.find(session->id()); + if (it != session_map_.end()) { + SignalSessionDestroy(session); + session_map_.erase(it); + delete session; + } + } +} + +Session *SessionManager::GetSession(const SessionID& id) { + // If the id isn't present, the [] operator will make a NULL entry + std::map::iterator it = session_map_.find(id); + if (it != session_map_.end()) + return (*it).second; + return NULL; +} + +void SessionManager::TerminateAll() { + while (session_map_.begin() != session_map_.end()) { + Session *session = session_map_.begin()->second; + session->Terminate(); + } +} + +void SessionManager::OnIncomingError(const SessionMessage &m) { + // Incoming signaling error. This means, as the result of trying + // to send message m, and error was generated. In all cases, a + // session should already exist + + Session *session; + switch (m.type()) { + case SessionMessage::TYPE_INITIATE: + case SessionMessage::TYPE_ACCEPT: + case SessionMessage::TYPE_MODIFY: + case SessionMessage::TYPE_CANDIDATES: + case SessionMessage::TYPE_REJECT: + case SessionMessage::TYPE_TERMINATE: + session = GetSession(m.session_id()); + break; + + default: + return; + } + + if (session != NULL) + session->OnIncomingError(m); + +} + +void SessionManager::OnIncomingMessage(const SessionMessage &m) { + // In the case of an incoming initiate, there is no session yet, and one needs to be created. + // The other cases have sessions already. + + Session *session; + switch (m.type()) { + case SessionMessage::TYPE_INITIATE: + session = CreateSession(m.name(), m.session_id(), true); + break; + + case SessionMessage::TYPE_ACCEPT: + case SessionMessage::TYPE_MODIFY: + case SessionMessage::TYPE_CANDIDATES: + case SessionMessage::TYPE_REJECT: + case SessionMessage::TYPE_REDIRECT: + case SessionMessage::TYPE_TERMINATE: + session = GetSession(m.session_id()); + break; + + default: + return; + } + + if (session != NULL) + session->OnIncomingMessage(m); +} + +void SessionManager::OnSignalingReady() { + for (std::map::iterator it = session_map_.begin(); + it != session_map_.end(); ++it) { + it->second->OnSignalingReady(); + } +} + +void SessionManager::OnRequestSignaling() { + SignalRequestSignaling(); +} + +PortAllocator *SessionManager::port_allocator() const { + return allocator_; +} + +Thread *SessionManager::worker_thread() const { + return worker_thread_; +} + +Thread *SessionManager::signaling_thread() const { + return signaling_thread_; +} + +int SessionManager::session_timeout() { + return timeout_; +} + +void SessionManager::set_session_timeout(int timeout) { + timeout_ = timeout; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.h new file mode 100644 index 00000000..5ce0e4c5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.h @@ -0,0 +1,86 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SESSIONMANAGER_H_ +#define _SESSIONMANAGER_H_ + +#include "talk/base/thread.h" +#include "talk/p2p/base/portallocator.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/sessionmessage.h" +#include "talk/base/sigslot.h" + +#include +#include +#include + +namespace cricket { + +class Session; + +// SessionManager manages session instances + +class SessionManager : public sigslot::has_slots<> { +public: + SessionManager(PortAllocator *allocator, Thread *worker_thread = NULL); + virtual ~SessionManager(); + + Session *CreateSession(const std::string &name, const std::string& initiator); + void DestroySession(Session *session); + Session *GetSession(const SessionID& id); + void TerminateAll(); + void OnIncomingMessage(const SessionMessage &m); + void OnIncomingError(const SessionMessage &m); + void OnSignalingReady(); + + PortAllocator *port_allocator() const; + Thread *worker_thread() const; + Thread *signaling_thread() const; + int session_timeout(); + void set_session_timeout(int timeout); + + sigslot::signal2 SignalSessionCreate; + sigslot::signal1 SignalSessionDestroy; + + // Note: you can connect this directly to OnSignalingReady(), if a signalling + // check is not required. + sigslot::signal0<> SignalRequestSignaling; + +private: + Session *CreateSession(const std::string &name, const SessionID& id, bool received_initiate); + void OnRequestSignaling(); + + int timeout_; + Thread *worker_thread_; + Thread *signaling_thread_; + PortAllocator *allocator_; + std::map session_map_; +}; + +} // namespace cricket + +#endif // _SESSIONMANAGER_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmessage.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmessage.h new file mode 100644 index 00000000..fc1b0323 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmessage.h @@ -0,0 +1,133 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SESSIONMESSAGE_H_ +#define _SESSIONMESSAGE_H_ + +#include "talk/p2p/base/candidate.h" +#include "talk/p2p/base/sessiondescription.h" +#include "talk/p2p/base/sessionid.h" +#include "talk/base/basictypes.h" +#include +#include +#include + +namespace cricket { + +class SessionMessage { +public: + enum Type { + TYPE_INITIATE = 0, // Initiate message + TYPE_ACCEPT, // Accept message + TYPE_MODIFY, // Modify message + TYPE_CANDIDATES, // Candidates message + TYPE_REJECT, // Reject message + TYPE_REDIRECT, // Reject message + TYPE_TERMINATE, // Terminate message + }; + + class Cookie { + public: + virtual ~Cookie() {} + + // Returns a copy of this cookie. + virtual Cookie* Copy() = 0; + }; + + Type type() const { + return type_; + } + void set_type(Type type) { + type_ = type; + } + const SessionID& session_id() const { + return id_; + } + SessionID& session_id() { + return id_; + } + void set_session_id(const SessionID& id) { + id_ = id; + } + const std::string &from() const { + return from_; + } + void set_from(const std::string &from) { + from_ = from; + } + const std::string &to() const { + return to_; + } + void set_to(const std::string &to) { + to_ = to; + } + const std::string &name() const { + return name_; + } + void set_name(const std::string &name) { + name_ = name; + } + const std::string &redirect_target() const { + return redirect_target_; + } + void set_redirect_target(const std::string &redirect_target) { + redirect_target_ = redirect_target; + } + Cookie *redirect_cookie() const { + return redirect_cookie_; + } + void set_redirect_cookie(Cookie* redirect_cookie) { + redirect_cookie_ = redirect_cookie; + } + const SessionDescription *description() const { + return description_; + } + void set_description(const SessionDescription *description) { + description_ = description; + } + const std::vector &candidates() const { + return candidates_; + } + void set_candidates(const std::vector &candidates) { + candidates_ = candidates; + } + +private: + Type type_; + SessionID id_; + std::string from_; + std::string to_; + std::string name_; + const SessionDescription *description_; + std::vector candidates_; + std::string redirect_target_; + Cookie* redirect_cookie_; +}; + +} // namespace cricket + +#endif // _SESSIONMESSAGE_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.cc new file mode 100644 index 00000000..2f0d67b8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.cc @@ -0,0 +1,273 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "socketmanager.h" +#include + +namespace cricket { + +const uint32 MSG_CREATESOCKET = 1; +const uint32 MSG_DESTROYSOCKET = 2; +const uint32 MSG_ONSIGNALINGREADY = 3; +const uint32 MSG_CANDIDATESREADY = 4; +const uint32 MSG_ADDREMOTECANDIDATES = 5; +const uint32 MSG_ONREQUESTSIGNALING = 6; +const uint32 MSG_RESETSOCKETS = 7; + +struct CreateParams { + CreateParams() {} + P2PSocket *socket; + std::string name; +}; + +SocketManager::SocketManager(SessionManager *session_manager) { + session_manager_ = session_manager; + candidates_requested_ = false; + writable_ = false; +} + +SocketManager::~SocketManager() { + assert(Thread::Current() == session_manager_->signaling_thread()); + + // Are the sockets destroyed? If not, destroy them + + critSM_.Enter(); + while (sockets_.size() != 0) { + P2PSocket *socket = sockets_[0]; + critSM_.Leave(); + DestroySocket(socket); + critSM_.Enter(); + } + critSM_.Leave(); + + // Clear queues + + session_manager_->signaling_thread()->Clear(this); + session_manager_->worker_thread()->Clear(this); +} + +P2PSocket *SocketManager::CreateSocket(const std::string &name) { + // Can occur on any thread + CreateParams params; + params.name = name; + params.socket = NULL; + TypedMessageData data(¶ms); + session_manager_->worker_thread()->Send(this, MSG_CREATESOCKET, &data); + return data.data()->socket; +} + +P2PSocket *SocketManager::CreateSocket_w(const std::string &name) { + // Only on worker thread + assert(Thread::Current() == session_manager_->worker_thread()); + CritScope cs(&critSM_); + P2PSocket *socket = new P2PSocket(name, session_manager_->port_allocator()); + socket->SignalCandidatesReady.connect(this, &SocketManager::OnCandidatesReady); + socket->SignalState.connect(this, &SocketManager::OnSocketState); + socket->SignalRequestSignaling.connect(this, &SocketManager::OnRequestSignaling); + sockets_.push_back(socket); + socket->StartProcessingCandidates(); + return socket; +} + +void SocketManager::DestroySocket(P2PSocket *socket) { + // Can occur on any thread + TypedMessageData data(socket); + session_manager_->worker_thread()->Send(this, MSG_DESTROYSOCKET, &data); +} + +void SocketManager::DestroySocket_w(P2PSocket *socket) { + // Only on worker thread + assert(Thread::Current() == session_manager_->worker_thread()); + + // Only if socket exists + CritScope cs(&critSM_); + std::vector::iterator it; + it = std::find(sockets_.begin(), sockets_.end(), socket); + if (it == sockets_.end()) + return; + sockets_.erase(it); + delete socket; +} + +void SocketManager::StartProcessingCandidates() { + // Only on signaling thread + assert(Thread::Current() == session_manager_->signaling_thread()); + + // When sockets are created, their candidates are requested. + // When the candidates are ready, the client is signaled + // on the signaling thread + candidates_requested_ = true; + session_manager_->signaling_thread()->Post(this, MSG_CANDIDATESREADY); +} + +void SocketManager::OnSignalingReady() { + session_manager_->worker_thread()->Post(this, MSG_ONSIGNALINGREADY); +} + +void SocketManager::OnSignalingReady_w() { + // Only on worker thread + assert(Thread::Current() == session_manager_->worker_thread()); + for (uint32 i = 0; i < sockets_.size(); ++i) { + sockets_[i]->OnSignalingReady(); + } +} + +void SocketManager::OnCandidatesReady( + P2PSocket *socket, const std::vector& candidates) { + // Only on worker thread + assert(Thread::Current() == session_manager_->worker_thread()); + + // Remember candidates + CritScope cs(&critSM_); + std::vector::const_iterator it; + for (it = candidates.begin(); it != candidates.end(); it++) + candidates_.push_back(*it); + + // If candidates requested, tell signaling thread + if (candidates_requested_) + session_manager_->signaling_thread()->Post(this, MSG_CANDIDATESREADY); +} + +void SocketManager::ResetSockets() { + assert(Thread::Current() == session_manager_->signaling_thread()); + session_manager_->worker_thread()->Post(this, MSG_RESETSOCKETS); +} + +void SocketManager::ResetSockets_w() { + assert(Thread::Current() == session_manager_->worker_thread()); + + for (size_t i = 0; i < sockets_.size(); ++i) + sockets_[i]->Reset(); +} + +void SocketManager::OnSocketState(P2PSocket* socket, P2PSocket::State state) { + assert(Thread::Current() == session_manager_->worker_thread()); + + bool writable = false; + for (uint32 i = 0; i < sockets_.size(); ++i) + if (sockets_[i]->writable()) + writable = true; + + if (writable_ != writable) { + writable_ = writable; + SignalState(); + } +} + +void SocketManager::OnRequestSignaling() { + assert(Thread::Current() == session_manager_->worker_thread()); + session_manager_->signaling_thread()->Post(this, MSG_ONREQUESTSIGNALING); +} + + +void SocketManager::AddRemoteCandidates(const std::vector &remote_candidates) { + assert(Thread::Current() == session_manager_->signaling_thread()); + TypedMessageData > *data = new TypedMessageData >(remote_candidates); + session_manager_->worker_thread()->Post(this, MSG_ADDREMOTECANDIDATES, data); +} + +void SocketManager::AddRemoteCandidates_w(const std::vector &remote_candidates) { + assert(Thread::Current() == session_manager_->worker_thread()); + + // Local and remote candidates now exist, so connectivity checking can + // commence. Tell the P2PSockets about the remote candidates. + // Group candidates by socket name + + CritScope cs(&critSM_); + std::vector::iterator it_socket; + for (it_socket = sockets_.begin(); it_socket != sockets_.end(); it_socket++) { + // Create a vector of remote candidates for each socket + std::string name = (*it_socket)->name(); + std::vector candidate_bundle; + std::vector::const_iterator it_candidate; + for (it_candidate = remote_candidates.begin(); it_candidate != remote_candidates.end(); it_candidate++) { + if ((*it_candidate).name() == name) + candidate_bundle.push_back(*it_candidate); + } + if (candidate_bundle.size() != 0) + (*it_socket)->AddRemoteCandidates(candidate_bundle); + } +} + +void SocketManager::OnMessage(Message *message) { + switch (message->message_id) { + case MSG_CREATESOCKET: + { + assert(Thread::Current() == session_manager_->worker_thread()); + TypedMessageData *params = static_cast *>(message->pdata); + params->data()->socket = CreateSocket_w(params->data()->name); + } + break; + + case MSG_DESTROYSOCKET: + { + assert(Thread::Current() == session_manager_->worker_thread()); + TypedMessageData *data = static_cast *>(message->pdata); + DestroySocket_w(data->data()); + } + break; + + case MSG_ONSIGNALINGREADY: + assert(Thread::Current() == session_manager_->worker_thread()); + OnSignalingReady_w(); + break; + + case MSG_ONREQUESTSIGNALING: + assert(Thread::Current() == session_manager_->signaling_thread()); + SignalRequestSignaling(); + break; + + case MSG_CANDIDATESREADY: + assert(Thread::Current() == session_manager_->signaling_thread()); + if (candidates_requested_) { + CritScope cs(&critSM_); + if (candidates_.size() > 0) { + SignalCandidatesReady(candidates_); + candidates_.clear(); + } + } + break; + + case MSG_ADDREMOTECANDIDATES: + { + assert(Thread::Current() == session_manager_->worker_thread()); + TypedMessageData > *data = static_cast > *>(message->pdata); + AddRemoteCandidates_w(data->data()); + delete data; + } + break; + + case MSG_RESETSOCKETS: + ResetSockets_w(); + break; + } +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.h new file mode 100644 index 00000000..3ca1cf74 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.h @@ -0,0 +1,101 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SOCKETMANAGER_H_ +#define _SOCKETMANAGER_H_ + +#include "talk/base/criticalsection.h" +#include "talk/base/messagequeue.h" +#include "talk/base/sigslot.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/candidate.h" +#include "talk/p2p/base/p2psocket.h" +#include "talk/p2p/base/socketmanager.h" + +#include +#include + +namespace cricket { + +class SessionManager; + +// Manages P2PSocket creation/destruction/readiness. +// Provides thread separation between session and sockets. +// This allows session to execute on the signaling thread, +// and sockets to execute on the worker thread, if desired, +// which is good for some media types (audio/video for example). + +class SocketManager : public MessageHandler, public sigslot::has_slots<> { +public: + SocketManager(SessionManager *session_manager); + virtual ~SocketManager(); + + // Determines whether any of the created sockets are currently writable. + bool writable() { return writable_; } + + P2PSocket *CreateSocket(const std::string & name); + void DestroySocket(P2PSocket *socket); + + // Start discovering local candidates + void StartProcessingCandidates(); + + // Adds the given candidates that were sent by the remote side. + void AddRemoteCandidates(const std::vector& candidates); + + // signaling channel is up, ready to transmit candidates as they are discovered + void OnSignalingReady(); + + // Put all of the sockets back into the initial state. + void ResetSockets(); + + sigslot::signal1&> SignalCandidatesReady; + sigslot::signal0<> SignalNetworkError; + sigslot::signal0<> SignalState; + sigslot::signal0<> SignalRequestSignaling; + +private: + P2PSocket *CreateSocket_w(const std::string &name); + void DestroySocket_w(P2PSocket *socket); + void OnSignalingReady_w(); + void AddRemoteCandidates_w(const std::vector &candidates); + virtual void OnMessage(Message *message); + void OnCandidatesReady(P2PSocket *socket, const std::vector&); + void OnSocketState(P2PSocket* socket, P2PSocket::State state); + void OnRequestSignaling(void); + void ResetSockets_w(); + + SessionManager *session_manager_; + std::vector candidates_; + CriticalSection critSM_; + std::vector sockets_; + bool candidates_requested_; + bool writable_; +}; + +} + +#endif // _SOCKETMANAGER_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.cc new file mode 100644 index 00000000..6a22b238 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.cc @@ -0,0 +1,576 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/logging.h" +#include "talk/p2p/base/stun.h" +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::memcpy; +} +#endif + +namespace cricket { + +const std::string STUN_ERROR_REASON_BAD_REQUEST = "BAD REQUEST"; +const std::string STUN_ERROR_REASON_UNAUTHORIZED = "UNAUTHORIZED"; +const std::string STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE = "UNKNOWN ATTRIBUTE"; +const std::string STUN_ERROR_REASON_STALE_CREDENTIALS = "STALE CREDENTIALS"; +const std::string STUN_ERROR_REASON_INTEGRITY_CHECK_FAILURE = "INTEGRITY CHECK FAILURE"; +const std::string STUN_ERROR_REASON_MISSING_USERNAME = "MISSING USERNAME"; +const std::string STUN_ERROR_REASON_USE_TLS = "USE TLS"; +const std::string STUN_ERROR_REASON_SERVER_ERROR = "SERVER ERROR"; +const std::string STUN_ERROR_REASON_GLOBAL_FAILURE = "GLOBAL FAILURE"; + +StunMessage::StunMessage() : type_(0), length_(0), + transaction_id_("0000000000000000") { + assert(transaction_id_.size() == 16); + attrs_ = new std::vector(); +} + +StunMessage::~StunMessage() { + for (unsigned i = 0; i < attrs_->size(); i++) + delete (*attrs_)[i]; + delete attrs_; +} + +void StunMessage::SetTransactionID(const std::string& str) { + assert(str.size() == 16); + transaction_id_ = str; +} + +void StunMessage::AddAttribute(StunAttribute* attr) { + attrs_->push_back(attr); + length_ += attr->length() + 4; +} + +const StunAddressAttribute* +StunMessage::GetAddress(StunAttributeType type) const { + switch (type) { + case STUN_ATTR_MAPPED_ADDRESS: + case STUN_ATTR_RESPONSE_ADDRESS: + case STUN_ATTR_SOURCE_ADDRESS: + case STUN_ATTR_CHANGED_ADDRESS: + case STUN_ATTR_REFLECTED_FROM: + case STUN_ATTR_ALTERNATE_SERVER: + case STUN_ATTR_DESTINATION_ADDRESS: + case STUN_ATTR_SOURCE_ADDRESS2: + return reinterpret_cast(GetAttribute(type)); + + default: + assert(0); + return 0; + } +} + +const StunUInt32Attribute* +StunMessage::GetUInt32(StunAttributeType type) const { + switch (type) { + case STUN_ATTR_CHANGE_REQUEST: + case STUN_ATTR_LIFETIME: + case STUN_ATTR_BANDWIDTH: + case STUN_ATTR_OPTIONS: + return reinterpret_cast(GetAttribute(type)); + + default: + assert(0); + return 0; + } +} + +const StunByteStringAttribute* +StunMessage::GetByteString(StunAttributeType type) const { + switch (type) { + case STUN_ATTR_USERNAME: + case STUN_ATTR_PASSWORD: + case STUN_ATTR_MESSAGE_INTEGRITY: + case STUN_ATTR_DATA: + case STUN_ATTR_MAGIC_COOKIE: + return reinterpret_cast(GetAttribute(type)); + + default: + assert(0); + return 0; + } +} + +const StunErrorCodeAttribute* StunMessage::GetErrorCode() const { + return reinterpret_cast( + GetAttribute(STUN_ATTR_ERROR_CODE)); +} + +const StunUInt16ListAttribute* StunMessage::GetUnknownAttributes() const { + return reinterpret_cast( + GetAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES)); +} + +const StunTransportPrefsAttribute* StunMessage::GetTransportPrefs() const { + return reinterpret_cast( + GetAttribute(STUN_ATTR_TRANSPORT_PREFERENCES)); +} + +const StunAttribute* StunMessage::GetAttribute(StunAttributeType type) const { + for (unsigned i = 0; i < attrs_->size(); i++) { + if ((*attrs_)[i]->type() == type) + return (*attrs_)[i]; + } + return 0; +} + +bool StunMessage::Read(ByteBuffer* buf) { + if (!buf->ReadUInt16(type_)) + return false; + + if (!buf->ReadUInt16(length_)) + return false; + + std::string transaction_id; + if (!buf->ReadString(transaction_id, 16)) + return false; + assert(transaction_id.size() == 16); + transaction_id_ = transaction_id; + + if (length_ > buf->Length()) + return false; + + attrs_->resize(0); + + size_t rest = buf->Length() - length_; + while (buf->Length() > rest) { + uint16 attr_type, attr_length; + if (!buf->ReadUInt16(attr_type)) + return false; + if (!buf->ReadUInt16(attr_length)) + return false; + + StunAttribute* attr = StunAttribute::Create(attr_type, attr_length); + if (!attr || !attr->Read(buf)) + return false; + + attrs_->push_back(attr); + } + + if (buf->Length() != rest) { + // fixme: shouldn't be doing this + LOG(LERROR) << "wrong message length" + << " (" << (int)rest << " != " << (int)buf->Length() << ")"; + return false; + } + + return true; +} + +void StunMessage::Write(ByteBuffer* buf) const { + buf->WriteUInt16(type_); + buf->WriteUInt16(length_); + buf->WriteString(transaction_id_); + + for (unsigned i = 0; i < attrs_->size(); i++) { + buf->WriteUInt16((*attrs_)[i]->type()); + buf->WriteUInt16((*attrs_)[i]->length()); + (*attrs_)[i]->Write(buf); + } +} + +StunAttribute::StunAttribute(uint16 type, uint16 length) + : type_(type), length_(length) { +} + +StunAttribute* StunAttribute::Create(uint16 type, uint16 length) { + switch (type) { + case STUN_ATTR_MAPPED_ADDRESS: + case STUN_ATTR_RESPONSE_ADDRESS: + case STUN_ATTR_SOURCE_ADDRESS: + case STUN_ATTR_CHANGED_ADDRESS: + case STUN_ATTR_REFLECTED_FROM: + case STUN_ATTR_ALTERNATE_SERVER: + case STUN_ATTR_DESTINATION_ADDRESS: + case STUN_ATTR_SOURCE_ADDRESS2: + if (length != StunAddressAttribute::SIZE) + return 0; + return new StunAddressAttribute(type); + + case STUN_ATTR_CHANGE_REQUEST: + case STUN_ATTR_LIFETIME: + case STUN_ATTR_BANDWIDTH: + case STUN_ATTR_OPTIONS: + if (length != StunUInt32Attribute::SIZE) + return 0; + return new StunUInt32Attribute(type); + + case STUN_ATTR_USERNAME: + case STUN_ATTR_PASSWORD: + case STUN_ATTR_MAGIC_COOKIE: + return (length % 4 == 0) ? new StunByteStringAttribute(type, length) : 0; + + case STUN_ATTR_MESSAGE_INTEGRITY: + return (length == 20) ? new StunByteStringAttribute(type, length) : 0; + + case STUN_ATTR_DATA: + return new StunByteStringAttribute(type, length); + + case STUN_ATTR_ERROR_CODE: + if (length < StunErrorCodeAttribute::MIN_SIZE) + return 0; + return new StunErrorCodeAttribute(type, length); + + case STUN_ATTR_UNKNOWN_ATTRIBUTES: + return (length % 2 == 0) ? new StunUInt16ListAttribute(type, length) : 0; + + case STUN_ATTR_TRANSPORT_PREFERENCES: + if ((length != StunTransportPrefsAttribute::SIZE1) && + (length != StunTransportPrefsAttribute::SIZE2)) + return 0; + return new StunTransportPrefsAttribute(type, length); + + default: + return 0; + } +} + +StunAddressAttribute* StunAttribute::CreateAddress(uint16 type) { + switch (type) { + case STUN_ATTR_MAPPED_ADDRESS: + case STUN_ATTR_RESPONSE_ADDRESS: + case STUN_ATTR_SOURCE_ADDRESS: + case STUN_ATTR_CHANGED_ADDRESS: + case STUN_ATTR_REFLECTED_FROM: + case STUN_ATTR_ALTERNATE_SERVER: + case STUN_ATTR_DESTINATION_ADDRESS: + case STUN_ATTR_SOURCE_ADDRESS2: + return new StunAddressAttribute(type); + + default: + assert(false); + return 0; + } +} + +StunUInt32Attribute* StunAttribute::CreateUInt32(uint16 type) { + switch (type) { + case STUN_ATTR_CHANGE_REQUEST: + case STUN_ATTR_LIFETIME: + case STUN_ATTR_BANDWIDTH: + case STUN_ATTR_OPTIONS: + return new StunUInt32Attribute(type); + + default: + assert(false); + return 0; + } +} + +StunByteStringAttribute* StunAttribute::CreateByteString(uint16 type) { + switch (type) { + case STUN_ATTR_USERNAME: + case STUN_ATTR_PASSWORD: + case STUN_ATTR_MESSAGE_INTEGRITY: + case STUN_ATTR_DATA: + case STUN_ATTR_MAGIC_COOKIE: + return new StunByteStringAttribute(type, 0); + + default: + assert(false); + return 0; + } +} + +StunErrorCodeAttribute* StunAttribute::CreateErrorCode() { + return new StunErrorCodeAttribute( + STUN_ATTR_ERROR_CODE, StunErrorCodeAttribute::MIN_SIZE); +} + +StunUInt16ListAttribute* StunAttribute::CreateUnknownAttributes() { + return new StunUInt16ListAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES, 0); +} + +StunTransportPrefsAttribute* StunAttribute::CreateTransportPrefs() { + return new StunTransportPrefsAttribute( + STUN_ATTR_TRANSPORT_PREFERENCES, StunTransportPrefsAttribute::SIZE1); +} + +StunAddressAttribute::StunAddressAttribute(uint16 type) + : StunAttribute(type, SIZE), family_(0), port_(0), ip_(0) { +} + +bool StunAddressAttribute::Read(ByteBuffer* buf) { + uint8 dummy; + if (!buf->ReadUInt8(dummy)) + return false; + if (!buf->ReadUInt8(family_)) + return false; + if (!buf->ReadUInt16(port_)) + return false; + if (!buf->ReadUInt32(ip_)) + return false; + return true; +} + +void StunAddressAttribute::Write(ByteBuffer* buf) const { + buf->WriteUInt8(0); + buf->WriteUInt8(family_); + buf->WriteUInt16(port_); + buf->WriteUInt32(ip_); +} + +StunUInt32Attribute::StunUInt32Attribute(uint16 type) + : StunAttribute(type, SIZE), bits_(0) { +} + +bool StunUInt32Attribute::GetBit(int index) const { + assert((0 <= index) && (index < 32)); + return static_cast((bits_ >> index) & 0x1); +} + +void StunUInt32Attribute::SetBit(int index, bool value) { + assert((0 <= index) && (index < 32)); + bits_ &= ~(1 << index); + bits_ |= value ? (1 << index) : 0; +} + +bool StunUInt32Attribute::Read(ByteBuffer* buf) { + if (!buf->ReadUInt32(bits_)) + return false; + return true; +} + +void StunUInt32Attribute::Write(ByteBuffer* buf) const { + buf->WriteUInt32(bits_); +} + +StunByteStringAttribute::StunByteStringAttribute(uint16 type, uint16 length) + : StunAttribute(type, length), bytes_(0) { +} + +StunByteStringAttribute::~StunByteStringAttribute() { + delete [] bytes_; +} + +void StunByteStringAttribute::SetBytes(char* bytes, uint16 length) { + delete [] bytes_; + bytes_ = bytes; + SetLength(length); +} + +void StunByteStringAttribute::CopyBytes(const char* bytes) { + CopyBytes(bytes, (uint16)strlen(bytes)); +} + +void StunByteStringAttribute::CopyBytes(const void* bytes, uint16 length) { + char* new_bytes = new char[length]; + std::memcpy(new_bytes, bytes, length); + SetBytes(new_bytes, length); +} + +uint8 StunByteStringAttribute::GetByte(int index) const { + assert(bytes_); + assert((0 <= index) && (index < length())); + return static_cast(bytes_[index]); +} + +void StunByteStringAttribute::SetByte(int index, uint8 value) { + assert(bytes_); + assert((0 <= index) && (index < length())); + bytes_[index] = value; +} + +bool StunByteStringAttribute::Read(ByteBuffer* buf) { + bytes_ = new char[length()]; + if (!buf->ReadBytes(bytes_, length())) + return false; + return true; +} + +void StunByteStringAttribute::Write(ByteBuffer* buf) const { + buf->WriteBytes(bytes_, length()); +} + +StunErrorCodeAttribute::StunErrorCodeAttribute(uint16 type, uint16 length) + : StunAttribute(type, length), class_(0), number_(0) { +} + +StunErrorCodeAttribute::~StunErrorCodeAttribute() { +} + +void StunErrorCodeAttribute::SetErrorCode(uint32 code) { + class_ = (uint8)((code >> 8) & 0x7); + number_ = (uint8)(code & 0xff); +} + +void StunErrorCodeAttribute::SetReason(const std::string& reason) { + SetLength(MIN_SIZE + (uint16)reason.size()); + reason_ = reason; +} + +bool StunErrorCodeAttribute::Read(ByteBuffer* buf) { + uint32 val; + if (!buf->ReadUInt32(val)) + return false; + + if ((val >> 11) != 0) + LOG(LERROR) << "error-code bits not zero"; + + SetErrorCode(val); + + if (!buf->ReadString(reason_, length() - 4)) + return false; + + return true; +} + +void StunErrorCodeAttribute::Write(ByteBuffer* buf) const { + buf->WriteUInt32(error_code()); + buf->WriteString(reason_); +} + +StunUInt16ListAttribute::StunUInt16ListAttribute(uint16 type, uint16 length) + : StunAttribute(type, length) { + attr_types_ = new std::vector(); +} + +StunUInt16ListAttribute::~StunUInt16ListAttribute() { + delete attr_types_; +} + +size_t StunUInt16ListAttribute::Size() const { + return attr_types_->size(); +} + +uint16 StunUInt16ListAttribute::GetType(int index) const { + return (*attr_types_)[index]; +} + +void StunUInt16ListAttribute::SetType(int index, uint16 value) { + (*attr_types_)[index] = value; +} + +void StunUInt16ListAttribute::AddType(uint16 value) { + attr_types_->push_back(value); + SetLength((uint16)attr_types_->size() * 2); +} + +bool StunUInt16ListAttribute::Read(ByteBuffer* buf) { + for (int i = 0; i < length() / 2; i++) { + uint16 attr; + if (!buf->ReadUInt16(attr)) + return false; + attr_types_->push_back(attr); + } + return true; +} + +void StunUInt16ListAttribute::Write(ByteBuffer* buf) const { + for (unsigned i = 0; i < attr_types_->size(); i++) + buf->WriteUInt16((*attr_types_)[i]); +} + +StunTransportPrefsAttribute::StunTransportPrefsAttribute( + uint16 type, uint16 length) + : StunAttribute(type, length), preallocate_(false), prefs_(0), addr_(0) { +} + +StunTransportPrefsAttribute::~StunTransportPrefsAttribute() { + delete addr_; +} + +void StunTransportPrefsAttribute::SetPreallocateAddress( + StunAddressAttribute* addr) { + if (!addr) { + preallocate_ = false; + addr_ = 0; + SetLength(SIZE1); + } else { + preallocate_ = true; + addr_ = addr; + SetLength(SIZE2); + } +} + +bool StunTransportPrefsAttribute::Read(ByteBuffer* buf) { + uint32 val; + if (!buf->ReadUInt32(val)) + return false; + + if ((val >> 3) != 0) + LOG(LERROR) << "transport-preferences bits not zero"; + + preallocate_ = static_cast((val >> 2) & 0x1); + prefs_ = (uint8)(val & 0x3); + + if (preallocate_ && (prefs_ == 3)) + LOG(LERROR) << "transport-preferences imcompatible P and Typ"; + + if (!preallocate_) { + if (length() != StunUInt32Attribute::SIZE) + return false; + } else { + if (length() != StunUInt32Attribute::SIZE + StunAddressAttribute::SIZE) + return false; + + addr_ = new StunAddressAttribute(STUN_ATTR_SOURCE_ADDRESS); + addr_->Read(buf); + } + + return true; +} + +void StunTransportPrefsAttribute::Write(ByteBuffer* buf) const { + buf->WriteUInt32((preallocate_ ? 4 : 0) | prefs_); + + if (preallocate_) + addr_->Write(buf); +} + +StunMessageType GetStunResponseType(StunMessageType request_type) { + switch (request_type) { + case STUN_SHARED_SECRET_REQUEST: + return STUN_SHARED_SECRET_RESPONSE; + case STUN_ALLOCATE_REQUEST: + return STUN_ALLOCATE_RESPONSE; + case STUN_SEND_REQUEST: + return STUN_SEND_RESPONSE; + default: + return STUN_BINDING_RESPONSE; + } +} + +StunMessageType GetStunErrorResponseType(StunMessageType request_type) { + switch (request_type) { + case STUN_SHARED_SECRET_REQUEST: + return STUN_SHARED_SECRET_ERROR_RESPONSE; + case STUN_ALLOCATE_REQUEST: + return STUN_ALLOCATE_ERROR_RESPONSE; + case STUN_SEND_REQUEST: + return STUN_SEND_ERROR_RESPONSE; + default: + return STUN_BINDING_ERROR_RESPONSE; + } +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.h new file mode 100644 index 00000000..27a8e4be --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.h @@ -0,0 +1,364 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __STUN_H__ +#define __STUN_H__ + +// This file contains classes for dealing with the STUN and TURN protocols. +// Both protocols use the same wire format. + +#include "talk/base/basictypes.h" +#include "talk/base/bytebuffer.h" +#include +#include + +namespace cricket { + +// These are the types of STUN & TURN messages as of last check. +enum StunMessageType { + STUN_BINDING_REQUEST = 0x0001, + STUN_BINDING_RESPONSE = 0x0101, + STUN_BINDING_ERROR_RESPONSE = 0x0111, + STUN_SHARED_SECRET_REQUEST = 0x0002, + STUN_SHARED_SECRET_RESPONSE = 0x0102, + STUN_SHARED_SECRET_ERROR_RESPONSE = 0x0112, + STUN_ALLOCATE_REQUEST = 0x0003, + STUN_ALLOCATE_RESPONSE = 0x0103, + STUN_ALLOCATE_ERROR_RESPONSE = 0x0113, + STUN_SEND_REQUEST = 0x0004, + STUN_SEND_RESPONSE = 0x0104, + STUN_SEND_ERROR_RESPONSE = 0x0114, + STUN_DATA_INDICATION = 0x0115 +}; + +// These are the types of attributes defined in STUN & TURN. Next to each is +// the name of the class (T is StunTAttribute) that implements that type. +enum StunAttributeType { + STUN_ATTR_MAPPED_ADDRESS = 0x0001, // Address + STUN_ATTR_RESPONSE_ADDRESS = 0x0002, // Address + STUN_ATTR_CHANGE_REQUEST = 0x0003, // UInt32 + STUN_ATTR_SOURCE_ADDRESS = 0x0004, // Address + STUN_ATTR_CHANGED_ADDRESS = 0x0005, // Address + STUN_ATTR_USERNAME = 0x0006, // ByteString, multiple of 4 bytes + STUN_ATTR_PASSWORD = 0x0007, // ByteString, multiple of 4 bytes + STUN_ATTR_MESSAGE_INTEGRITY = 0x0008, // ByteString, 20 bytes + STUN_ATTR_ERROR_CODE = 0x0009, // ErrorCode + STUN_ATTR_UNKNOWN_ATTRIBUTES = 0x000a, // UInt16List + STUN_ATTR_REFLECTED_FROM = 0x000b, // Address + STUN_ATTR_TRANSPORT_PREFERENCES = 0x000c, // TransportPrefs + STUN_ATTR_LIFETIME = 0x000d, // UInt32 + STUN_ATTR_ALTERNATE_SERVER = 0x000e, // Address + STUN_ATTR_MAGIC_COOKIE = 0x000f, // ByteString, 4 bytes + STUN_ATTR_BANDWIDTH = 0x0010, // UInt32 + STUN_ATTR_DESTINATION_ADDRESS = 0x0011, // Address + STUN_ATTR_SOURCE_ADDRESS2 = 0x0012, // Address + STUN_ATTR_DATA = 0x0013, // ByteString + STUN_ATTR_OPTIONS = 0x8001 // UInt32 +}; + +enum StunErrorCodes { + STUN_ERROR_BAD_REQUEST = 400, + STUN_ERROR_UNAUTHORIZED = 401, + STUN_ERROR_UNKNOWN_ATTRIBUTE = 420, + STUN_ERROR_STALE_CREDENTIALS = 430, + STUN_ERROR_INTEGRITY_CHECK_FAILURE = 431, + STUN_ERROR_MISSING_USERNAME = 432, + STUN_ERROR_USE_TLS = 433, + STUN_ERROR_SERVER_ERROR = 500, + STUN_ERROR_GLOBAL_FAILURE = 600 +}; + +extern const std::string STUN_ERROR_REASON_BAD_REQUEST; +extern const std::string STUN_ERROR_REASON_UNAUTHORIZED; +extern const std::string STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE; +extern const std::string STUN_ERROR_REASON_STALE_CREDENTIALS; +extern const std::string STUN_ERROR_REASON_INTEGRITY_CHECK_FAILURE; +extern const std::string STUN_ERROR_REASON_MISSING_USERNAME; +extern const std::string STUN_ERROR_REASON_USE_TLS; +extern const std::string STUN_ERROR_REASON_SERVER_ERROR; +extern const std::string STUN_ERROR_REASON_GLOBAL_FAILURE; + +class StunAttribute; +class StunAddressAttribute; +class StunUInt32Attribute; +class StunByteStringAttribute; +class StunErrorCodeAttribute; +class StunUInt16ListAttribute; +class StunTransportPrefsAttribute; + +// Records a complete STUN/TURN message. Each message consists of a type and +// any number of attributes. Each attribute is parsed into an instance of an +// appropriate class (see above). The Get* methods will return instances of +// that attribute class. +class StunMessage { +public: + StunMessage(); + ~StunMessage(); + + StunMessageType type() const { return static_cast(type_); } + uint16 length() const { return length_; } + const std::string& transaction_id() const { return transaction_id_; } + + void SetType(StunMessageType type) { type_ = type; } + void SetTransactionID(const std::string& str); + + const StunAddressAttribute* GetAddress(StunAttributeType type) const; + const StunUInt32Attribute* GetUInt32(StunAttributeType type) const; + const StunByteStringAttribute* GetByteString(StunAttributeType type) const; + const StunErrorCodeAttribute* GetErrorCode() const; + const StunUInt16ListAttribute* GetUnknownAttributes() const; + const StunTransportPrefsAttribute* GetTransportPrefs() const; + + void AddAttribute(StunAttribute* attr); + + // Parses the STUN/TURN packet in the given buffer and records it here. The + // return value indicates whether this was successful. + bool Read(ByteBuffer* buf); + + // Writes this object into a STUN/TURN packet. Return value is true if + // successful. + void Write(ByteBuffer* buf) const; + +private: + uint16 type_; + uint16 length_; + std::string transaction_id_; + std::vector* attrs_; + + const StunAttribute* GetAttribute(StunAttributeType type) const; +}; + +// Base class for all STUN/TURN attributes. +class StunAttribute { +public: + virtual ~StunAttribute() {} + + StunAttributeType type() const { + return static_cast(type_); + } + uint16 length() const { return length_; } + + // Reads the body (not the type or length) for this type of attribute from + // the given buffer. Return value is true if successful. + virtual bool Read(ByteBuffer* buf) = 0; + + // Writes the body (not the type or length) to the given buffer. Return + // value is true if successful. + virtual void Write(ByteBuffer* buf) const = 0; + + // Creates an attribute object with the given type and len. + static StunAttribute* Create(uint16 type, uint16 length); + + // Creates an attribute object with the given type and smallest length. + static StunAddressAttribute* CreateAddress(uint16 type); + static StunUInt32Attribute* CreateUInt32(uint16 type); + static StunByteStringAttribute* CreateByteString(uint16 type); + static StunErrorCodeAttribute* CreateErrorCode(); + static StunUInt16ListAttribute* CreateUnknownAttributes(); + static StunTransportPrefsAttribute* CreateTransportPrefs(); + +protected: + StunAttribute(uint16 type, uint16 length); + + void SetLength(uint16 length) { length_ = length; } + +private: + uint16 type_; + uint16 length_; +}; + +// Implements STUN/TURN attributes that record an Internet address. +class StunAddressAttribute : public StunAttribute { +public: + StunAddressAttribute(uint16 type); + +#if (_MSC_VER < 1300) + enum { SIZE = 8 }; +#else + static const uint16 SIZE = 8; +#endif + + uint8 family() const { return family_; } + uint16 port() const { return port_; } + uint32 ip() const { return ip_; } + + void SetFamily(uint8 family) { family_ = family; } + void SetIP(uint32 ip) { ip_ = ip; } + void SetPort(uint16 port) { port_ = port; } + + bool Read(ByteBuffer* buf); + void Write(ByteBuffer* buf) const; + +private: + uint8 family_; + uint16 port_; + uint32 ip_; +}; + +// Implements STUN/TURN attributs that record a 32-bit integer. +class StunUInt32Attribute : public StunAttribute { +public: + StunUInt32Attribute(uint16 type); + +#if (_MSC_VER < 1300) + enum { SIZE = 4 }; +#else + static const uint16 SIZE = 4; +#endif + + uint32 value() const { return bits_; } + + void SetValue(uint32 bits) { bits_ = bits; } + + bool GetBit(int index) const; + void SetBit(int index, bool value); + + bool Read(ByteBuffer* buf); + void Write(ByteBuffer* buf) const; + +private: + uint32 bits_; +}; + +// Implements STUN/TURN attributs that record an arbitrary byte string +class StunByteStringAttribute : public StunAttribute { +public: + StunByteStringAttribute(uint16 type, uint16 length); + ~StunByteStringAttribute(); + + const char* bytes() const { return bytes_; } + + void SetBytes(char* bytes, uint16 length); + + void CopyBytes(const char* bytes); // uses strlen + void CopyBytes(const void* bytes, uint16 length); + + uint8 GetByte(int index) const; + void SetByte(int index, uint8 value); + + bool Read(ByteBuffer* buf); + void Write(ByteBuffer* buf) const; + +private: + char* bytes_; +}; + +// Implements STUN/TURN attributs that record an error code. +class StunErrorCodeAttribute : public StunAttribute { +public: + StunErrorCodeAttribute(uint16 type, uint16 length); + ~StunErrorCodeAttribute(); + +#if (_MSC_VER < 1300) + enum { MIN_SIZE = 4 }; +#else + static const uint16 MIN_SIZE = 4; +#endif + + uint32 error_code() const { return (class_ << 8) | number_; } + uint8 error_class() const { return class_; } + uint8 number() const { return number_; } + const std::string& reason() const { return reason_; } + + void SetErrorCode(uint32 code); + void SetErrorClass(uint8 eclass) { class_ = eclass; } + void SetNumber(uint8 number) { number_ = number; } + void SetReason(const std::string& reason); + + bool Read(ByteBuffer* buf); + void Write(ByteBuffer* buf) const; + +private: + uint8 class_; + uint8 number_; + std::string reason_; +}; + +// Implements STUN/TURN attributs that record a list of attribute names. +class StunUInt16ListAttribute : public StunAttribute { +public: + StunUInt16ListAttribute(uint16 type, uint16 length); + ~StunUInt16ListAttribute(); + + size_t Size() const; + uint16 GetType(int index) const; + void SetType(int index, uint16 value); + void AddType(uint16 value); + + bool Read(ByteBuffer* buf); + void Write(ByteBuffer* buf) const; + +private: + std::vector* attr_types_; +}; + +// Implements the TURN TRANSPORT-PREFS attribute, which provides information +// about the ports to allocate. +class StunTransportPrefsAttribute : public StunAttribute { +public: + StunTransportPrefsAttribute(uint16 type, uint16 length); + ~StunTransportPrefsAttribute(); + +#if (_MSC_VER < 1300) + enum { SIZE1 = 4, SIZE2 = 12 }; +#else + static const uint16 SIZE1 = 4; + static const uint16 SIZE2 = 12; +#endif + + bool preallocate() const { return preallocate_; } + uint8 preference_type() const { return prefs_; } + const StunAddressAttribute* address() const { return addr_; } + + void SetPreferenceType(uint8 prefs) { prefs_ = prefs; } + + // Sets the preallocate address to the given value, or if 0 is given, it sets + // to not preallocate. + void SetPreallocateAddress(StunAddressAttribute* addr); + + bool Read(ByteBuffer* buf); + void Write(ByteBuffer* buf) const; + +private: + bool preallocate_; + uint8 prefs_; + StunAddressAttribute* addr_; +}; + +// The special MAGIC-COOKIE attribute is used to distinguish TURN packets from +// other kinds of traffic. +const char STUN_MAGIC_COOKIE_VALUE[] = { 0x72, char(0xc6), 0x4b, char(0xc6) }; + +// Returns the (successful) response type for the given request type. +StunMessageType GetStunResponseType(StunMessageType request_type); + +// Returns the error response type for the given request type. +StunMessageType GetStunErrorResponseType(StunMessageType request_type); + +} // namespace cricket + +#endif // __STUN_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.cc new file mode 100644 index 00000000..6d1dc6b1 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.cc @@ -0,0 +1,171 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/logging.h" +#include "talk/p2p/base/stunport.h" +#include "talk/p2p/base/helpers.h" +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +const int KEEPALIVE_DELAY = 10 * 1000; // 10 seconds - sort timeouts +const int RETRY_DELAY = 50; // 50ms, from ICE spec +const uint32 RETRY_TIMEOUT = 50 * 1000; // ICE says 50 secs + +// Handles a binding request sent to the STUN server. +class StunPortBindingRequest : public StunRequest { +public: + StunPortBindingRequest(StunPort* port) : port_(port) { + start_time_ = GetMillisecondCount(); + } + + virtual ~StunPortBindingRequest() { + } + + virtual void Prepare(StunMessage* request) { + request->SetType(STUN_BINDING_REQUEST); + } + + virtual void OnResponse(StunMessage* response) { + const StunAddressAttribute* addr_attr = + response->GetAddress(STUN_ATTR_MAPPED_ADDRESS); + if (!addr_attr) { + LOG(LERROR) << "Binding response missing mapped address."; + } else if (addr_attr->family() != 1) { + LOG(LERROR) << "Binding address has bad family"; + } else { + SocketAddress addr(addr_attr->ip(), addr_attr->port()); + if (port_->candidates().empty()) + port_->add_address(addr, "udp"); + } + + // We will do a keep-alive regardless of whether this request suceeds. + // This should have almost no impact on network usage. + port_->requests_.SendDelayed(new StunPortBindingRequest(port_), KEEPALIVE_DELAY); + } + + virtual void OnErrorResponse(StunMessage* response) { + const StunErrorCodeAttribute* attr = response->GetErrorCode(); + if (!attr) { + LOG(LERROR) << "Bad allocate response error code"; + } else { + LOG(LERROR) << "Binding error response:" + << " class=" << attr->error_class() + << " number=" << attr->number() + << " reason='" << attr->reason() << "'"; + } + + if (GetMillisecondCount() - start_time_ <= RETRY_TIMEOUT) + port_->requests_.SendDelayed(new StunPortBindingRequest(port_), KEEPALIVE_DELAY); + } + + virtual void OnTimeout() { + LOG(LERROR) << "Binding request timed out"; + if (GetMillisecondCount() - start_time_ <= RETRY_TIMEOUT) + port_->requests_.SendDelayed(new StunPortBindingRequest(port_), RETRY_DELAY); + } + +private: + uint32 start_time_; + StunPort* port_; +}; + +const std::string STUN_PORT_TYPE("stun"); + +StunPort::StunPort(Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& local_addr, + const SocketAddress& server_addr) + : UDPPort(thread, STUN_PORT_TYPE, factory, network), + server_addr_(server_addr), requests_(thread), error_(0) { + + socket_ = CreatePacketSocket(PROTO_UDP); + socket_->SignalReadPacket.connect(this, &StunPort::OnReadPacket); + if (socket_->Bind(local_addr) < 0) + PLOG(LERROR, socket_->GetError()) << "bind"; + + requests_.SignalSendPacket.connect(this, &StunPort::OnSendPacket); +} + +StunPort::~StunPort() { + delete socket_; +} + +void StunPort::PrepareAddress() { + requests_.Send(new StunPortBindingRequest(this)); +} + +int StunPort::SendTo( + const void* data, size_t size, const SocketAddress& addr, bool payload) { + int sent = socket_->SendTo(data, size, addr); + if (sent < 0) + error_ = socket_->GetError(); + return sent; +} + +int StunPort::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int StunPort::GetError() { + return error_; +} + +void StunPort::OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + assert(socket == socket_); + + // Look for a response to a binding request. + if (requests_.CheckResponse(data, size)) + return; + + // Process this data packet in the normal manner. + UDPPort::OnReadPacket(data, size, remote_addr); +} + +void StunPort::OnSendPacket(const void* data, size_t size) { + if (socket_->SendTo(data, size, server_addr_) < 0) + PLOG(LERROR, socket_->GetError()) << "sendto"; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.h new file mode 100644 index 00000000..f042ae14 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.h @@ -0,0 +1,72 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __STUNPORT_H__ +#define __STUNPORT_H__ + +#include "talk/base/asyncudpsocket.h" +#include "talk/p2p/base/udpport.h" +#include "talk/p2p/base/stunrequest.h" + +namespace cricket { + +extern const std::string STUN_PORT_TYPE; + +// Communicates using the address on the outside of a NAT. +class StunPort : public UDPPort { +public: + StunPort(Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& local_addr, const SocketAddress& server_addr); + virtual ~StunPort(); + + virtual void PrepareAddress(); + + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError(); + +protected: + virtual int SendTo(const void* data, size_t size, const SocketAddress& addr, bool payload); + + void OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + +private: + AsyncPacketSocket* socket_; + SocketAddress server_addr_; + StunRequestManager requests_; + int error_; + + friend class StunPortBindingRequest; + + // Sends STUN requests to the server. + void OnSendPacket(const void* data, size_t size); +}; + +} // namespace cricket + +#endif // __STUNPORT_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.cc new file mode 100644 index 00000000..14d64735 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.cc @@ -0,0 +1,198 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/logging.h" +#include "talk/p2p/base/stunrequest.h" +#include "talk/p2p/base/helpers.h" +#include +#include + +namespace cricket { + +const uint32 MSG_STUN_SEND = 1; + +const int MAX_SENDS = 9; +const int DELAY_UNIT = 100; // 100 milliseconds +const int DELAY_MAX_FACTOR = 16; + +StunRequestManager::StunRequestManager(Thread* thread) : thread_(thread) { +} + +StunRequestManager::~StunRequestManager() { + while (requests_.begin() != requests_.end()) { + StunRequest *request = requests_.begin()->second; + requests_.erase(requests_.begin()); + delete request; + } +} + +void StunRequestManager::Send(StunRequest* request) { + SendDelayed(request, 0); +} + +void StunRequestManager::SendDelayed(StunRequest* request, int delay) { + request->set_manager(this); + assert(requests_.find(request->id()) == requests_.end()); + requests_[request->id()] = request; + thread_->PostDelayed(delay, request, MSG_STUN_SEND, NULL); +} + +void StunRequestManager::Remove(StunRequest* request) { + assert(request->manager() == this); + RequestMap::iterator iter = requests_.find(request->id()); + if (iter != requests_.end()) { + assert(iter->second == request); + requests_.erase(iter); + thread_->Clear(request); + } +} + +void StunRequestManager::Clear() { + std::vector requests; + for (RequestMap::iterator i = requests_.begin(); i != requests_.end(); ++i) + requests.push_back(i->second); + + for (uint32 i = 0; i < requests.size(); ++i) + Remove(requests[i]); +} + +bool StunRequestManager::CheckResponse(StunMessage* msg) { + RequestMap::iterator iter = requests_.find(msg->transaction_id()); + if (iter == requests_.end()) + return false; + + StunRequest* request = iter->second; + if (msg->type() == GetStunResponseType(request->type())) { + request->OnResponse(msg); + } else if (msg->type() == GetStunErrorResponseType(request->type())) { + request->OnErrorResponse(msg); + } else { + LOG(LERROR) << "Received response with wrong type: " << msg->type() + << " (expecting " << GetStunResponseType(request->type()) << ")"; + return false; + } + + delete request; + return true; +} + +bool StunRequestManager::CheckResponse(const char* data, size_t size) { + // Check the appropriate bytes of the stream to see if they match the + // transaction ID of a response we are expecting. + + if (size < 20) + return false; + + std::string id; + id.append(data + 4, 16); + + RequestMap::iterator iter = requests_.find(id); + if (iter == requests_.end()) + return false; + + // Parse the STUN message and continue processing as usual. + + ByteBuffer buf(data, size); + StunMessage msg; + if (!msg.Read(&buf)) + return false; + + return CheckResponse(&msg); +} + +StunRequest::StunRequest() + : manager_(0), id_(CreateRandomString(16)), msg_(0), count_(0), + timeout_(false), tstamp_(0) { +} + +StunRequest::StunRequest(StunMessage* request) + : manager_(0), id_(request->transaction_id()), msg_(request), + count_(0), timeout_(false) { +} + +StunRequest::~StunRequest() { + assert(manager_ != NULL); + if (manager_) { + manager_->Remove(this); + manager_->thread_->Clear(this); + } + delete msg_; +} + +const StunMessageType StunRequest::type() { + assert(msg_); + return msg_->type(); +} + +void StunRequest::set_manager(StunRequestManager* manager) { + assert(!manager_); + manager_ = manager; +} + +void StunRequest::OnMessage(Message* pmsg) { + assert(manager_); + assert(pmsg->message_id == MSG_STUN_SEND); + + if (!msg_) { + msg_ = new StunMessage(); + msg_->SetTransactionID(id_); + Prepare(msg_); + assert(msg_->transaction_id() == id_); + } + + if (timeout_) { + OnTimeout(); + delete this; + return; + } + + tstamp_ = GetMillisecondCount(); + + ByteBuffer buf; + msg_->Write(&buf); + manager_->SignalSendPacket(buf.Data(), buf.Length()); + + int delay = GetNextDelay(); + manager_->thread_->PostDelayed(delay, this, MSG_STUN_SEND, NULL); +} + +uint32 StunRequest::Elapsed() const { + return (GetMillisecondCount() - tstamp_); +} + +int StunRequest::GetNextDelay() { + int delay = DELAY_UNIT * _min(1 << count_, DELAY_MAX_FACTOR); + count_ += 1; + if (count_ == MAX_SENDS) + timeout_ = true; + return delay; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.h new file mode 100644 index 00000000..86acff91 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.h @@ -0,0 +1,126 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __STUNREQUESTMANAGER_H__ +#define __STUNREQUESTMANAGER_H__ + +#include "talk/base/sigslot.h" +#include "talk/base/thread.h" +#include "talk/p2p/base/stun.h" +#include +#include + +namespace cricket { + +class StunRequest; + +// Manages a set of STUN requests, sending and resending until we receive a +// response or determine that the request has timed out. +class StunRequestManager { +public: + StunRequestManager(Thread* thread); + ~StunRequestManager(); + + // Starts sending the given request (perhaps after a delay). + void Send(StunRequest* request); + void SendDelayed(StunRequest* request, int delay); + + // Removes a stun request that was added previously. This will happen + // automatically when a request succeeds, fails, or times out. + void Remove(StunRequest* request); + + // Removes all stun requests that were added previously. + void Clear(); + + // Determines whether the given message is a response to one of the + // outstanding requests, and if so, processes it appropriately. + bool CheckResponse(StunMessage* msg); + bool CheckResponse(const char* data, size_t size); + + // Raised when there are bytes to be sent. + sigslot::signal2 SignalSendPacket; + +private: + typedef std::map RequestMap; + + Thread* thread_; + RequestMap requests_; + + friend class StunRequest; +}; + +// Represents an individual request to be sent. The STUN message can either be +// constructed beforehand or built on demand. +class StunRequest : public MessageHandler { +public: + StunRequest(); + StunRequest(StunMessage* request); + virtual ~StunRequest(); + + // The manager handling this request (if it has been scheduled for sending). + StunRequestManager* manager() { return manager_; } + + // Returns the transaction ID of this request. + const std::string& id() { return id_; } + + // Returns the STUN type of the request message. + const StunMessageType type(); + + // Handles messages for sending and timeout. + void OnMessage(Message* pmsg); + + // Time elapsed since last send (in ms) + uint32 Elapsed() const; + +protected: + int count_; + bool timeout_; + + // Fills in the actual request to be sent. Note that the transaction ID will + // already be set and cannot be changed. + virtual void Prepare(StunMessage* request) {} + + // Called when the message receives a response or times out. + virtual void OnResponse(StunMessage* response) {} + virtual void OnErrorResponse(StunMessage* response) {} + virtual void OnTimeout() {} + virtual int GetNextDelay(); + +private: + StunRequestManager* manager_; + std::string id_; + StunMessage* msg_; + uint32 tstamp_; + + void set_manager(StunRequestManager* manager); + + friend class StunRequestManager; +}; + +} // namespace cricket + +#endif // __STUNREQUESTMANAGER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.cc new file mode 100644 index 00000000..6e4f6b66 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.cc @@ -0,0 +1,160 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/bytebuffer.h" +#include "talk/p2p/base/stunserver.h" +#include + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +StunServer::StunServer(AsyncUDPSocket* socket) : socket_(socket) { + socket_->SignalReadPacket.connect(this, &StunServer::OnPacket); +} + +StunServer::~StunServer() { + socket_->SignalReadPacket.disconnect(this); +} + +void StunServer::OnPacket( + const char* buf, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + + // TODO: If appropriate, look for the magic cookie before parsing. + + // Parse the STUN message. + ByteBuffer bbuf(buf, size); + StunMessage msg; + if (!msg.Read(&bbuf)) { + SendErrorResponse(msg, remote_addr, 400, "Bad Request"); + return; + } + + // TODO: If this is UDP, then we shouldn't allow non-fully-parsed messages. + + // TODO: If unknown non-optiional (<= 0x7fff) attributes are found, send a + // 420 "Unknown Attribute" response. + + // TODO: Check that a message-integrity attribute was given (or send 401 + // "Unauthorized"). Check that a username attribute was given (or send + // 432 "Missing Username"). Look up the username and password. If it + // is missing or the HMAC is wrong, send 431 "Integrity Check Failure". + + // Send the message to the appropriate handler function. + switch (msg.type()) { + case STUN_BINDING_REQUEST: + OnBindingRequest(&msg, remote_addr); + return; + + case STUN_ALLOCATE_REQUEST: + OnAllocateRequest(&msg, remote_addr); + return; + + default: + SendErrorResponse(msg, remote_addr, 600, "Operation Not Supported"); + } +} + +void StunServer::OnBindingRequest( + StunMessage* msg, const SocketAddress& remote_addr) { + StunMessage response; + response.SetType(STUN_BINDING_RESPONSE); + response.SetTransactionID(msg->transaction_id()); + + // Tell the user the address that we received their request from. + StunAddressAttribute* mapped_addr = + StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); + mapped_addr->SetFamily(1); + mapped_addr->SetPort(remote_addr.port()); + mapped_addr->SetIP(remote_addr.ip()); + response.AddAttribute(mapped_addr); + + // Tell the user the address that we are sending the response from. + SocketAddress local_addr = socket_->GetLocalAddress(); + StunAddressAttribute* source_addr = + StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS); + source_addr->SetFamily(1); + source_addr->SetPort(local_addr.port()); + source_addr->SetIP(local_addr.ip()); + response.AddAttribute(source_addr); + + // TODO: Add username and message-integrity. + + // TODO: Add changed-address. (Keep information about three other servers.) + + SendResponse(response, remote_addr); +} + +void StunServer::OnAllocateRequest( + StunMessage* msg, const SocketAddress& addr) { + SendErrorResponse(*msg, addr, 600, "Operation Not Supported"); +} + +void StunServer::OnSharedSecretRequest( + StunMessage* msg, const SocketAddress& addr) { + SendErrorResponse(*msg, addr, 600, "Operation Not Supported"); +} + +void StunServer::OnSendRequest(StunMessage* msg, const SocketAddress& addr) { + SendErrorResponse(*msg, addr, 600, "Operation Not Supported"); +} + +void StunServer::SendErrorResponse( + const StunMessage& msg, const SocketAddress& addr, int error_code, + const char* error_desc) { + + StunMessage err_msg; + err_msg.SetType(GetStunErrorResponseType(msg.type())); + err_msg.SetTransactionID(msg.transaction_id()); + + StunErrorCodeAttribute* err_code = StunAttribute::CreateErrorCode(); + err_code->SetErrorClass(error_code / 100); + err_code->SetNumber(error_code % 100); + err_code->SetReason(error_desc); + err_msg.AddAttribute(err_code); + + SendResponse(err_msg, addr); +} + +void StunServer::SendResponse( + const StunMessage& msg, const SocketAddress& addr) { + + ByteBuffer buf; + msg.Write(&buf); + + // TODO: Allow response addr attribute if sent from another stun server. + + if (socket_->SendTo(buf.Data(), buf.Length(), addr) < 0) + std::cerr << "sendto: " << std::strerror(errno) << std::endl; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.h new file mode 100644 index 00000000..3043645d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.h @@ -0,0 +1,73 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __STUNSERVER_H__ +#define __STUNSERVER_H__ + +#include "talk/base/asyncudpsocket.h" +#include "talk/p2p/base/stun.h" + +namespace cricket { + +const int STUN_SERVER_PORT = 3478; + +class StunServer : public sigslot::has_slots<> { +public: + // Creates a STUN server, which will listen on the given socket. + StunServer(AsyncUDPSocket* socket); + + // Removes the STUN server from the socket, but does not delete the socket. + ~StunServer(); + +protected: + + // Slot for AsyncSocket.PacketRead: + void OnPacket( + const char* buf, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + + // Handlers for the different types of STUN/TURN requests: + void OnBindingRequest(StunMessage* msg, const SocketAddress& addr); + void OnAllocateRequest(StunMessage* msg, const SocketAddress& addr); + void OnSharedSecretRequest(StunMessage* msg, const SocketAddress& addr); + void OnSendRequest(StunMessage* msg, const SocketAddress& addr); + + // Sends an error response to the given message back to the user. + void SendErrorResponse( + const StunMessage& msg, const SocketAddress& addr, int error_code, + const char* error_desc); + + // Sends the given message to the appropriate destination. + void SendResponse(const StunMessage& msg, const SocketAddress& addr); + +private: + AsyncUDPSocket* socket_; +}; + +} // namespace cricket + +#endif // __STUNSERVER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.pro b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.pro new file mode 100644 index 00000000..dce92ec4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.pro @@ -0,0 +1,14 @@ +TEMPLATE = app +INCLUDEPATH = ../../.. +DEFINES += POSIX + +include(../../../../../conf.pri) + +# Input +SOURCES += \ + stunserver.cc \ + stunserver_main.cc \ + ../../base/host.cc #\ +# ../../base/socketaddresspair.cc + +LIBS += ../../../liblibjingle.a diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver_main.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver_main.cc new file mode 100644 index 00000000..bd8a96e5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver_main.cc @@ -0,0 +1,66 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/host.h" +#include "talk/base/thread.h" +#include "talk/p2p/base/stunserver.h" +#include + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +using namespace cricket; + +int main(int argc, char* argv[]) { + if (argc != 1) { + std::cerr << "usage: stunserver" << std::endl; + return 1; + } + + SocketAddress server_addr(LocalHost().networks()[1]->ip(), 7000); + + Thread *pthMain = Thread::Current(); + + AsyncUDPSocket* server_socket = CreateAsyncUDPSocket(pthMain->socketserver()); + if (server_socket->Bind(server_addr) < 0) { + std::cerr << "bind: " << std::strerror(errno) << std::endl; + return 1; + } + + StunServer* server = new StunServer(server_socket); + + std::cout << "Listening at " << server_addr.ToString() << std::endl; + + pthMain->Loop(); + + delete server; + delete server_socket; + return 0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.cc new file mode 100644 index 00000000..a2d2adc6 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.cc @@ -0,0 +1,250 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/p2p/base/tcpport.h" +#include "talk/base/logging.h" +#ifdef WIN32 +#include "talk/base/winfirewall.h" +#endif // WIN32 +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +#ifdef WIN32 +static WinFirewall win_firewall; +#endif // WIN32 + +TCPPort::TCPPort(Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& address) + : Port(thread, LOCAL_PORT_TYPE, factory, network), error_(0) { + incoming_only_ = (address.port() != 0); + socket_ = thread->socketserver()->CreateAsyncSocket(SOCK_STREAM); + socket_->SignalReadEvent.connect(this, &TCPPort::OnAcceptEvent); + if (socket_->Bind(address) < 0) + LOG(INFO) << "bind: " << std::strerror(socket_->GetError()); +} + +TCPPort::~TCPPort() { + delete socket_; +} + +Connection* TCPPort::CreateConnection(const Candidate& address, CandidateOrigin origin) { + // We only support TCP protocols + if ((address.protocol() != "tcp") && (address.protocol() != "ssltcp")) + return 0; + + // We can't accept TCP connections incoming on other ports + if (origin == ORIGIN_OTHER_PORT) + return 0; + + // Check if we are allowed to make outgoing TCP connections + if (incoming_only_ && (origin == ORIGIN_MESSAGE)) + return 0; + + // We don't know how to act as an ssl server yet + if ((address.protocol() == "ssltcp") && (origin == ORIGIN_THIS_PORT)) + return 0; + + TCPConnection* conn = 0; + if (AsyncTCPSocket * socket = GetIncoming(address.address(), true)) { + socket->SignalReadPacket.disconnect(this); + conn = new TCPConnection(this, address, socket); + } else { + conn = new TCPConnection(this, address); + } + AddConnection(conn); + return conn; +} + +void TCPPort::PrepareAddress() { + assert(socket_); + + bool allow_listen = true; +#ifdef WIN32 + if (win_firewall.Initialize()) { + char module_path[MAX_PATH + 1] = { 0 }; + ::GetModuleFileNameA(NULL, module_path, MAX_PATH); + if (win_firewall.Enabled() && !win_firewall.Authorized(module_path)) { + allow_listen = false; + } + } +#endif // WIN32 + if (allow_listen) { + if (socket_->Listen(5) < 0) + LOG(INFO) << "listen: " << std::strerror(socket_->GetError()); + } else { + LOG(INFO) << "not listening due to firewall restrictions"; + } + // Note: We still add the address, since otherwise the remote side won't recognize + // our incoming TCP connections. + add_address(socket_->GetLocalAddress(), "tcp"); +} + +int TCPPort::SendTo(const void* data, size_t size, const SocketAddress& addr, bool payload) { + AsyncTCPSocket * socket = 0; + + if (TCPConnection * conn = static_cast(GetConnection(addr))) { + socket = conn->socket(); + } else { + socket = GetIncoming(addr); + } + if (!socket) { + LOG(INFO) << "Unknown destination for SendTo: " << addr.ToString(); + return -1; // TODO: Set error_ + } + + //LOG(INFO) << "TCPPort::SendTo(" << size << ", " << addr.ToString() << ")"; + + int sent = socket->Send(data, size); + if (sent < 0) + error_ = socket->GetError(); + return sent; +} + +int TCPPort::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int TCPPort::GetError() { + assert(socket_); + return error_; +} + +void TCPPort::OnAcceptEvent(AsyncSocket* socket) { + assert(socket == socket_); + + Incoming incoming; + AsyncSocket * newsocket = static_cast(socket->Accept(&incoming.addr)); + if (!newsocket) { + // TODO: Do something better like forwarding the error to the user. + LOG(INFO) << "accept: " << socket_->GetError() << " " << std::strerror(socket_->GetError()); + return; + } + incoming.socket = new AsyncTCPSocket(newsocket); + incoming.socket->SignalReadPacket.connect(this, &TCPPort::OnReadPacket); + + LOG(INFO) << "accepted incoming connection from " << incoming.addr.ToString(); + incoming_.push_back(incoming); + + // Prime a read event in case data is waiting + newsocket->SignalReadEvent(newsocket); +} + +AsyncTCPSocket * TCPPort::GetIncoming(const SocketAddress& addr, bool remove) { + AsyncTCPSocket * socket = 0; + for (std::list::iterator it = incoming_.begin(); it != incoming_.end(); ++it) { + if (it->addr == addr) { + socket = it->socket; + if (remove) + incoming_.erase(it); + break; + } + } + return socket; +} + +void TCPPort::OnReadPacket(const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + Port::OnReadPacket(data, size, remote_addr); +} + +TCPConnection::TCPConnection(TCPPort* port, const Candidate& candidate, AsyncTCPSocket* socket) + : Connection(port, 0, candidate), socket_(socket), error_(0) { + bool outgoing = (socket_ == 0); + if (outgoing) { + socket_ = static_cast(port->CreatePacketSocket( + (candidate.protocol() == "ssltcp") ? PROTO_SSLTCP : PROTO_TCP)); + } + socket_->SignalReadPacket.connect(this, &TCPConnection::OnReadPacket); + socket_->SignalClose.connect(this, &TCPConnection::OnClose); + if (outgoing) { + connected_ = false; + socket_->SignalConnect.connect(this, &TCPConnection::OnConnect); + socket_->Connect(candidate.address()); + LOG(INFO) << "Connecting to " << candidate.address().ToString(); + } +} + +TCPConnection::~TCPConnection() { +} + +int TCPConnection::Send(const void* data, size_t size) { + if (write_state() != STATE_WRITABLE) + return 0; + + int sent = socket_->Send(data, size); + if (sent < 0) { + error_ = socket_->GetError(); + } else { + sent_total_bytes_ += sent; + } + return sent; +} + +int TCPConnection::GetError() { + return error_; +} + +TCPPort* TCPConnection::tcpport() { + return static_cast(port_); +} + +void TCPConnection::OnConnect(AsyncTCPSocket* socket) { + assert(socket == socket_); + LOG(INFO) << "tcp connected to " << socket->GetRemoteAddress().ToString(); + set_connected(true); +} + +void TCPConnection::OnClose(AsyncTCPSocket* socket, int error) { + assert(socket == socket_); + LOG(INFO) << "tcp closed with error: " << error; + set_connected(false); +} + +void TCPConnection::OnReadPacket(const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + assert(socket == socket_); + Connection::OnReadPacket(data, size); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.h new file mode 100644 index 00000000..f6a9beb7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.h @@ -0,0 +1,116 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __TCPPORT_H__ +#define __TCPPORT_H__ + +#include +#include "talk/base/asynctcpsocket.h" +#include "talk/p2p/base/port.h" + +namespace cricket { + +class TCPConnection; + +extern const std::string LOCAL_PORT_TYPE; // type of TCP ports + +// Communicates using a local TCP port. +// +// This class is designed to allow subclasses to take advantage of the +// connection management provided by this class. A subclass should take of all +// packet sending and preparation, but when a packet is received, it should +// call this TCPPort::OnReadPacket (3 arg) to dispatch to a connection. +class TCPPort : public Port { +public: + TCPPort(Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& address); + virtual ~TCPPort(); + + virtual Connection* CreateConnection(const Candidate& address, CandidateOrigin origin); + + virtual void PrepareAddress(); + + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError(); + +protected: + // Handles sending using the local TCP socket. + virtual int SendTo(const void* data, size_t size, const SocketAddress& addr, bool payload); + + // Creates TCPConnection for incoming sockets + void OnAcceptEvent(AsyncSocket* socket); + + AsyncSocket* socket() { return socket_; } + +private: + bool incoming_only_; + AsyncSocket* socket_; + int error_; + + struct Incoming { + SocketAddress addr; + AsyncTCPSocket * socket; + }; + std::list incoming_; + + AsyncTCPSocket * GetIncoming(const SocketAddress& addr, bool remove = false); + + // Receives packet signal from the local TCP Socket. + void OnReadPacket(const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + + friend class TCPConnection; +}; + +class TCPConnection : public Connection { +public: + // Connection is outgoing unless socket is specified + TCPConnection(TCPPort* port, const Candidate& candidate, AsyncTCPSocket* socket = 0); + virtual ~TCPConnection(); + + virtual int Send(const void* data, size_t size); + virtual int GetError(); + + AsyncTCPSocket * socket() { return socket_; } + +private: + TCPPort* tcpport(); + AsyncTCPSocket* socket_; + bool connected_; + int error_; + + void OnConnect(AsyncTCPSocket* socket); + void OnClose(AsyncTCPSocket* socket, int error); + void OnReadPacket(const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + + friend class TCPPort; +}; + +} // namespace cricket + +#endif // __TCPPORT_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.cc new file mode 100644 index 00000000..fabbb25b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.cc @@ -0,0 +1,117 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/logging.h" +#include "talk/p2p/base/udpport.h" +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +const std::string LOCAL_PORT_TYPE("local"); + +UDPPort::UDPPort(Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& address) + : Port(thread, LOCAL_PORT_TYPE, factory, network), error_(0) { + socket_ = CreatePacketSocket(PROTO_UDP); + socket_->SignalReadPacket.connect(this, &UDPPort::OnReadPacketSlot); + if (socket_->Bind(address) < 0) + PLOG(LERROR, socket_->GetError()) << "bind"; +} + +UDPPort::UDPPort(Thread* thread, const std::string &type, + SocketFactory* factory, Network* network) + : Port(thread, type, factory, network), socket_(0), error_(0) { +} + +UDPPort::~UDPPort() { + delete socket_; +} + +void UDPPort::PrepareAddress() { + assert(socket_); + add_address(socket_->GetLocalAddress(), "udp"); +} + +Connection* UDPPort::CreateConnection(const Candidate& address, CandidateOrigin origin) { + if (address.protocol() != "udp") + return 0; + + Connection * conn = new ProxyConnection(this, 0, address); + AddConnection(conn); + return conn; +} + +int UDPPort::SendTo(const void* data, size_t size, const SocketAddress& addr, bool payload) { + assert(socket_); + int sent = socket_->SendTo(data, size, addr); + if (sent < 0) + error_ = socket_->GetError(); + return sent; +} + +int UDPPort::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int UDPPort::GetError() { + assert(socket_); + return error_; +} + +void UDPPort::OnReadPacketSlot( + const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + assert(socket == socket_); + OnReadPacket(data, size, remote_addr); +} + +void UDPPort::OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr) { + if (Connection* conn = GetConnection(remote_addr)) { + conn->OnReadPacket(data, size); + } else { + Port::OnReadPacket(data, size, remote_addr); + } +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.h new file mode 100644 index 00000000..4bcd113e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.h @@ -0,0 +1,81 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __UDPPORT_H__ +#define __UDPPORT_H__ + +#include "talk/base/asyncudpsocket.h" +#include "talk/p2p/base/port.h" + +namespace cricket { + +extern const std::string LOCAL_PORT_TYPE; // type of UDP ports + +// Communicates using a local UDP port. +// +// This class is designed to allow subclasses to take advantage of the +// connection management provided by this class. A subclass should take of all +// packet sending and preparation, but when a packet is received, it should +// call this UDPPort::OnReadPacket (3 arg) to dispatch to a connection. +class UDPPort : public Port { +public: + UDPPort(Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& address); + virtual ~UDPPort(); + + virtual void PrepareAddress(); + virtual Connection* CreateConnection(const Candidate& address, CandidateOrigin origin); + + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError(); + +protected: + UDPPort(Thread* thread, const std::string &type, SocketFactory* factory, + Network* network); + + // Handles sending using the local UDP socket. + virtual int SendTo(const void* data, size_t size, const SocketAddress& addr, bool payload); + + // Dispatches the given packet to the port or connection as appropriate. + void OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr); + + AsyncPacketSocket* socket() { return socket_; } + +private: + AsyncPacketSocket* socket_; + int error_; + + // Receives packet signal from the local UDP Socket. + void OnReadPacketSlot( + const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); +}; + +} // namespace cricket + +#endif // __UDPPORT_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/Makefile.am new file mode 100644 index 00000000..2bdd95ff --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/Makefile.am @@ -0,0 +1,11 @@ +libcricketp2pclient_la_SOURCES = sessionclient.cc \ + basicportallocator.cc \ + socketmonitor.cc + +noinst_HEADERS = basicportallocator.h \ + sessionclient.h \ + socketmonitor.h + +AM_CPPFLAGS = -I$(srcdir)/../../.. -DLINUX -DPOSIX -DINTERNAL_BUILD + +noinst_LTLIBRARIES = libcricketp2pclient.la diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.cc new file mode 100644 index 00000000..5192595c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.cc @@ -0,0 +1,667 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/host.h" +#include "talk/base/logging.h" +#include "talk/p2p/client/basicportallocator.h" +#include "talk/p2p/base/port.h" +#include "talk/p2p/base/udpport.h" +#include "talk/p2p/base/tcpport.h" +#include "talk/p2p/base/stunport.h" +#include "talk/p2p/base/relayport.h" +#include "talk/p2p/base/helpers.h" +#include + +namespace { + +const uint32 MSG_CONFIG_START = 1; +const uint32 MSG_CONFIG_READY = 2; +const uint32 MSG_ALLOCATE = 3; +const uint32 MSG_ALLOCATION_PHASE = 4; +const uint32 MSG_SHAKE = 5; + +const uint32 ALLOCATE_DELAY = 250; +const uint32 ALLOCATION_STEP_DELAY = 1 * 1000; + +const int PHASE_UDP = 0; +const int PHASE_RELAY = 1; +const int PHASE_TCP = 2; +const int PHASE_SSLTCP = 3; +const int kNumPhases = 4; + +const float PREF_LOCAL_UDP = 1.0f; +const float PREF_LOCAL_STUN = 0.9f; +const float PREF_LOCAL_TCP = 0.8f; +const float PREF_RELAY = 0.5f; + +const float RELAY_PRIMARY_PREF_MODIFIER = 0.0f; // modifiers of the above constants +const float RELAY_BACKUP_PREF_MODIFIER = -0.2f; + + +// Returns the phase in which a given local candidate (or rather, the port that +// gave rise to that local candidate) would have been created. +int LocalCandidateToPhase(const cricket::Candidate& candidate) { + cricket::ProtocolType proto; + bool result = cricket::StringToProto(candidate.protocol().c_str(), proto); + if (result) { + if (candidate.type() == cricket::LOCAL_PORT_TYPE) { + switch (proto) { + case cricket::PROTO_UDP: return PHASE_UDP; + case cricket::PROTO_TCP: return PHASE_TCP; + default: assert(false); + } + } else if (candidate.type() == cricket::STUN_PORT_TYPE) { + return PHASE_UDP; + } else if (candidate.type() == cricket::RELAY_PORT_TYPE) { + switch (proto) { + case cricket::PROTO_UDP: return PHASE_RELAY; + case cricket::PROTO_TCP: return PHASE_TCP; + case cricket::PROTO_SSLTCP: return PHASE_SSLTCP; + default: assert(false); + } + } else { + assert(false); + } + } else { + assert(false); + } + return PHASE_UDP; // reached only with assert failure +} + +const int SHAKE_MIN_DELAY = 45 * 1000; // 45 seconds +const int SHAKE_MAX_DELAY = 90 * 1000; // 90 seconds + +int ShakeDelay() { + int range = SHAKE_MAX_DELAY - SHAKE_MIN_DELAY + 1; + return SHAKE_MIN_DELAY + cricket::CreateRandomId() % range; +} + +} + +namespace cricket { + +// Performs the allocation of ports, in a sequenced (timed) manner, for a given +// network and IP address. +class AllocationSequence: public MessageHandler { +public: + AllocationSequence(BasicPortAllocatorSession* session, + Network* network, + PortConfiguration* config); + ~AllocationSequence(); + + // Determines whether this sequence is operating on an equivalent network + // setup to the one given. + bool IsEquivalent(Network* network); + + // Starts and stops the sequence. When started, it will continue allocating + // new ports on its own timed schedule. + void Start(); + void Stop(); + + // MessageHandler: + void OnMessage(Message* msg); + + void EnableProtocol(ProtocolType proto); + bool ProtocolEnabled(ProtocolType proto) const; + +private: + BasicPortAllocatorSession* session_; + Network* network_; + uint32 ip_; + PortConfiguration* config_; + bool running_; + int step_; + int step_of_phase_[kNumPhases]; + + typedef std::vector ProtocolList; + ProtocolList protocols_; + + void CreateUDPPorts(); + void CreateTCPPorts(); + void CreateStunPorts(); + void CreateRelayPorts(); +}; + + +// BasicPortAllocator + +BasicPortAllocator::BasicPortAllocator(NetworkManager* network_manager) + : network_manager_(network_manager), best_writable_phase_(-1), stun_address_(NULL), relay_address_(NULL) { +} + +BasicPortAllocator::BasicPortAllocator(NetworkManager* network_manager, SocketAddress* stun_address, SocketAddress *relay_address) + : network_manager_(network_manager), best_writable_phase_(-1), stun_address_(stun_address), relay_address_(relay_address) { +} + +BasicPortAllocator::~BasicPortAllocator() { +} + +int BasicPortAllocator::best_writable_phase() const { + // If we are configured with an HTTP proxy, the best bet is to use the relay + if ((best_writable_phase_ == -1) + && ((proxy().type == PROXY_HTTPS) || (proxy().type == PROXY_UNKNOWN))) { + return PHASE_RELAY; + } + return best_writable_phase_; +} + +PortAllocatorSession *BasicPortAllocator::CreateSession(const std::string &name) { + return new BasicPortAllocatorSession(this, name, stun_address_, relay_address_); +} + +void BasicPortAllocator::AddWritablePhase(int phase) { + if ((best_writable_phase_ == -1) || (phase < best_writable_phase_)) + best_writable_phase_ = phase; +} + +// BasicPortAllocatorSession + +BasicPortAllocatorSession::BasicPortAllocatorSession( + BasicPortAllocator *allocator, + const std::string &name) + : allocator_(allocator), name_(name), network_thread_(NULL), + config_thread_(NULL), allocation_started_(false), running_(false), + stun_address_(NULL), relay_address_(NULL) { +} + +BasicPortAllocatorSession::BasicPortAllocatorSession( + BasicPortAllocator *allocator, + const std::string &name, + SocketAddress *stun_address, + SocketAddress *relay_address) + : allocator_(allocator), name_(name), network_thread_(NULL), + config_thread_(NULL), allocation_started_(false), running_(false), + stun_address_(stun_address), relay_address_(relay_address) { +} + +BasicPortAllocatorSession::~BasicPortAllocatorSession() { + if (config_thread_ != NULL) + config_thread_->Clear(this); + if (network_thread_ != NULL) + network_thread_->Clear(this); + + std::vector::iterator it; + for (it = ports_.begin(); it != ports_.end(); it++) + delete it->port; + + for (uint32 i = 0; i < configs_.size(); ++i) + delete configs_[i]; + + for (uint32 i = 0; i < sequences_.size(); ++i) + delete sequences_[i]; +} + +void BasicPortAllocatorSession::GetInitialPorts() { + network_thread_ = Thread::Current(); + if (!config_thread_) + config_thread_ = network_thread_; + + config_thread_->Post(this, MSG_CONFIG_START); + + if (allocator()->flags() & PORTALLOCATOR_ENABLE_SHAKER) + network_thread_->PostDelayed(ShakeDelay(), this, MSG_SHAKE); +} + +void BasicPortAllocatorSession::StartGetAllPorts() { + assert(Thread::Current() == network_thread_); + running_ = true; + if (allocation_started_) + network_thread_->PostDelayed(ALLOCATE_DELAY, this, MSG_ALLOCATE); + for (uint32 i = 0; i < sequences_.size(); ++i) + sequences_[i]->Start(); + for (size_t i = 0; i < ports_.size(); ++i) + ports_[i].port->Start(); +} + +void BasicPortAllocatorSession::StopGetAllPorts() { + assert(Thread::Current() == network_thread_); + running_ = false; + network_thread_->Clear(this, MSG_ALLOCATE); + for (uint32 i = 0; i < sequences_.size(); ++i) + sequences_[i]->Stop(); +} + +void BasicPortAllocatorSession::OnMessage(Message *message) { + switch (message->message_id) { + case MSG_CONFIG_START: + assert(Thread::Current() == config_thread_); + GetPortConfigurations(); + break; + + case MSG_CONFIG_READY: + assert(Thread::Current() == network_thread_); + OnConfigReady(static_cast(message->pdata)); + break; + + case MSG_ALLOCATE: + assert(Thread::Current() == network_thread_); + OnAllocate(); + break; + + case MSG_SHAKE: + assert(Thread::Current() == network_thread_); + OnShake(); + break; + + default: + assert(false); + } +} + +void BasicPortAllocatorSession::GetPortConfigurations() { + PortConfiguration* config = NULL; + if (stun_address_ != NULL) + config = new PortConfiguration(*stun_address_, + CreateRandomString(16), + CreateRandomString(16), + ""); + PortConfiguration::PortList ports; + if (relay_address_ != NULL) { + ports.push_back(ProtocolAddress(*relay_address_, PROTO_UDP)); + config->AddRelay(ports, RELAY_PRIMARY_PREF_MODIFIER); + } + + ConfigReady(config); +} + +void BasicPortAllocatorSession::ConfigReady(PortConfiguration* config) { + network_thread_->Post(this, MSG_CONFIG_READY, config); +} + +// Adds a configuration to the list. +void BasicPortAllocatorSession::OnConfigReady(PortConfiguration* config) { + if (config) + configs_.push_back(config); + + AllocatePorts(); +} + +void BasicPortAllocatorSession::AllocatePorts() { + assert(Thread::Current() == network_thread_); + + if (allocator_->proxy().type != PROXY_NONE) + Port::set_proxy(allocator_->proxy()); + + network_thread_->Post(this, MSG_ALLOCATE); +} + +// For each network, see if we have a sequence that covers it already. If not, +// create a new sequence to create the appropriate ports. +void BasicPortAllocatorSession::OnAllocate() { + std::vector networks; + allocator_->network_manager()->GetNetworks(networks); + + for (uint32 i = 0; i < networks.size(); ++i) { + if (HasEquivalentSequence(networks[i])) + continue; + + PortConfiguration* config = NULL; + if (configs_.size() > 0) + config = configs_.back(); + + AllocationSequence* sequence = + new AllocationSequence(this, networks[i], config); + if (running_) + sequence->Start(); + + sequences_.push_back(sequence); + } + + allocation_started_ = true; + if (running_) + network_thread_->PostDelayed(ALLOCATE_DELAY, this, MSG_ALLOCATE); +} + +bool BasicPortAllocatorSession::HasEquivalentSequence(Network* network) { + for (uint32 i = 0; i < sequences_.size(); ++i) + if (sequences_[i]->IsEquivalent(network)) + return true; + return false; +} + +void BasicPortAllocatorSession::AddAllocatedPort(Port* port, + AllocationSequence * seq, + float pref, + bool prepare_address) { + if (!port) + return; + + port->set_name(name_); + port->set_preference(pref); + port->set_generation(generation()); + PortData data; + data.port = port; + data.sequence = seq; + data.ready = false; + ports_.push_back(data); + port->SignalAddressReady.connect(this, &BasicPortAllocatorSession::OnAddressReady); + port->SignalConnectionCreated.connect(this, &BasicPortAllocatorSession::OnConnectionCreated); + port->SignalDestroyed.connect(this, &BasicPortAllocatorSession::OnPortDestroyed); + if (prepare_address) + port->PrepareAddress(); + if (running_) + port->Start(); +} + +void BasicPortAllocatorSession::OnAddressReady(Port *port) { + assert(Thread::Current() == network_thread_); + std::vector::iterator it = std::find(ports_.begin(), ports_.end(), port); + assert(it != ports_.end()); + assert(!it->ready); + it->ready = true; + SignalPortReady(this, port); + + // Only accumulate the candidates whose protocol has been enabled + std::vector candidates; + const std::vector& potentials = port->candidates(); + for (size_t i=0; isequence->ProtocolEnabled(pvalue)) { + candidates.push_back(potentials[i]); + } + } + if (!candidates.empty()) { + SignalCandidatesReady(this, candidates); + } +} + +void BasicPortAllocatorSession::OnProtocolEnabled(AllocationSequence * seq, ProtocolType proto) { + std::vector candidates; + for (std::vector::iterator it = ports_.begin(); it != ports_.end(); ++it) { + if (!it->ready || (it->sequence != seq)) + continue; + + const std::vector& potentials = it->port->candidates(); + for (size_t i=0; i::iterator iter = + find(ports_.begin(), ports_.end(), port); + assert(iter != ports_.end()); + ports_.erase(iter); + + LOG(INFO) << "Removed port from allocator: " + << static_cast(ports_.size()) << " remaining"; +} + +void BasicPortAllocatorSession::OnConnectionCreated(Port* port, Connection* conn) { + conn->SignalStateChange.connect(this, &BasicPortAllocatorSession::OnConnectionStateChange); +} + +void BasicPortAllocatorSession::OnConnectionStateChange(Connection* conn) { + if (conn->write_state() == Connection::STATE_WRITABLE) + allocator_->AddWritablePhase(LocalCandidateToPhase(conn->local_candidate())); +} + +void BasicPortAllocatorSession::OnShake() { + LOG(INFO) << ">>>>> SHAKE <<<<< >>>>> SHAKE <<<<< >>>>> SHAKE <<<<<"; + + std::vector ports; + std::vector connections; + + for (size_t i = 0; i < ports_.size(); ++i) { + if (ports_[i].ready) + ports.push_back(ports_[i].port); + } + + for (size_t i = 0; i < ports.size(); ++i) { + Port::AddressMap::const_iterator iter; + for (iter = ports[i]->connections().begin(); + iter != ports[i]->connections().end(); + ++iter) { + connections.push_back(iter->second); + } + } + + LOG(INFO) << ">>>>> Destroying " << (int)ports.size() << " ports and " + << (int)connections.size() << " connections"; + + for (size_t i = 0; i < connections.size(); ++i) + connections[i]->Destroy(); + + if (running_ || (ports.size() > 0) || (connections.size() > 0)) + network_thread_->PostDelayed(ShakeDelay(), this, MSG_SHAKE); +} + +// AllocationSequence + +AllocationSequence::AllocationSequence(BasicPortAllocatorSession* session, + Network* network, + PortConfiguration* config) + : session_(session), network_(network), ip_(network->ip()), config_(config), + running_(false), step_(0) { + + // All of the phases up until the best-writable phase so far run in step 0. + // The other phases follow sequentially in the steps after that. If there is + // no best-writable so far, then only phase 0 occurs in step 0. + int last_phase_in_step_zero = + _max(0, session->allocator()->best_writable_phase()); + for (int phase = 0; phase < kNumPhases; ++phase) + step_of_phase_[phase] = _max(0, phase - last_phase_in_step_zero); + + // Immediately perform phase 0. + OnMessage(NULL); +} + +AllocationSequence::~AllocationSequence() { + session_->network_thread()->Clear(this); +} + +bool AllocationSequence::IsEquivalent(Network* network) { + return (network == network_) && (ip_ == network->ip()); +} + +void AllocationSequence::Start() { + running_ = true; + session_->network_thread()->PostDelayed(ALLOCATION_STEP_DELAY, + this, + MSG_ALLOCATION_PHASE); +} + +void AllocationSequence::Stop() { + running_ = false; + session_->network_thread()->Clear(this, MSG_ALLOCATION_PHASE); +} + +void AllocationSequence::OnMessage(Message* msg) { + assert(Thread::Current() == session_->network_thread()); + if (msg) + assert(msg->message_id == MSG_ALLOCATION_PHASE); + + // Perform all of the phases in the current step. + for (int phase = 0; phase < kNumPhases; phase++) { + if (step_of_phase_[phase] != step_) + continue; + + switch (phase) { + case PHASE_UDP: + LOG(INFO) << "Phase=UDP Step=" << step_; + CreateUDPPorts(); + CreateStunPorts(); + EnableProtocol(PROTO_UDP); + break; + + case PHASE_RELAY: + LOG(INFO) << "Phase=RELAY Step=" << step_; + CreateRelayPorts(); + break; + + case PHASE_TCP: + LOG(INFO) << "Phase=TCP Step=" << step_; + CreateTCPPorts(); + EnableProtocol(PROTO_TCP); + break; + + case PHASE_SSLTCP: + LOG(INFO) << "Phase=SSLTCP Step=" << step_; + EnableProtocol(PROTO_SSLTCP); + break; + + default: + // Nothing else we can do. + return; + } + } + + // TODO: use different delays for each stage + step_ += 1; + if (running_) { + session_->network_thread()->PostDelayed(ALLOCATION_STEP_DELAY, + this, + MSG_ALLOCATION_PHASE); + } +} + +void AllocationSequence::EnableProtocol(ProtocolType proto) { + if (!ProtocolEnabled(proto)) { + protocols_.push_back(proto); + session_->OnProtocolEnabled(this, proto); + } +} + +bool AllocationSequence::ProtocolEnabled(ProtocolType proto) const { + for (ProtocolList::const_iterator it = protocols_.begin(); it != protocols_.end(); ++it) { + if (*it == proto) + return true; + } + return false; +} + +void AllocationSequence::CreateUDPPorts() { + if (session_->allocator()->flags() & PORTALLOCATOR_DISABLE_UDP) + return; + + Port* port = new UDPPort(session_->network_thread(), NULL, network_, + SocketAddress(ip_, 0)); + session_->AddAllocatedPort(port, this, PREF_LOCAL_UDP); +} + +void AllocationSequence::CreateTCPPorts() { + if (session_->allocator()->flags() & PORTALLOCATOR_DISABLE_TCP) + return; + + Port* port = new TCPPort(session_->network_thread(), NULL, network_, + SocketAddress(ip_, 0)); + session_->AddAllocatedPort(port, this, PREF_LOCAL_TCP); +} + +void AllocationSequence::CreateStunPorts() { + if (session_->allocator()->flags() & PORTALLOCATOR_DISABLE_STUN) + return; + + if (!config_ || config_->stun_address.IsAny()) + return; + + Port* port = new StunPort(session_->network_thread(), NULL, network_, + SocketAddress(ip_, 0), config_->stun_address); + session_->AddAllocatedPort(port, this, PREF_LOCAL_STUN); +} + +void AllocationSequence::CreateRelayPorts() { + if (session_->allocator()->flags() & PORTALLOCATOR_DISABLE_RELAY) + return; + + if (!config_) + return; + + PortConfiguration::RelayList::const_iterator relay; + for (relay = config_->relays.begin(); + relay != config_->relays.end(); + ++relay) { + + RelayPort *port = new RelayPort(session_->network_thread(), NULL, network_, + SocketAddress(ip_, 0), + config_->username, config_->password, + config_->magic_cookie); + // Note: We must add the allocated port before we add addresses because + // the latter will create candidates that need name and preference + // settings. However, we also can't prepare the address (normally + // done by AddAllocatedPort) until we have these addresses. So we + // wait to do that until below. + session_->AddAllocatedPort(port, this, PREF_RELAY + relay->pref_modifier, false); + + // Add the addresses of this protocol. + PortConfiguration::PortList::const_iterator relay_port; + for (relay_port = relay->ports.begin(); + relay_port != relay->ports.end(); + ++relay_port) { + port->AddServerAddress(*relay_port); + port->AddExternalAddress(*relay_port); + } + + // Start fetching an address for this port. + port->PrepareAddress(); + } +} + +// PortConfiguration + +PortConfiguration::PortConfiguration(const SocketAddress& sa, + const std::string& un, + const std::string& pw, + const std::string& mc) + : stun_address(sa), username(un), password(pw), magic_cookie(mc) { +} + +void PortConfiguration::AddRelay(const PortList& ports, float pref_modifier) { + RelayServer relay; + relay.ports = ports; + relay.pref_modifier = pref_modifier; + relays.push_back(relay); +} + +bool PortConfiguration::SupportsProtocol( + const PortConfiguration::RelayServer& relay, ProtocolType type) { + PortConfiguration::PortList::const_iterator relay_port; + for (relay_port = relay.ports.begin(); + relay_port != relay.ports.end(); + ++relay_port) { + if (relay_port->proto == type) + return true; + } + return false; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.h new file mode 100644 index 00000000..0f7b96b4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.h @@ -0,0 +1,172 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _BASICPORTALLOCATOR_H_ +#define _BASICPORTALLOCATOR_H_ + +#include "talk/base/thread.h" +#include "talk/base/messagequeue.h" +#include "talk/base/network.h" +#include "talk/p2p/base/portallocator.h" +#include +#include + +namespace cricket { + +class BasicPortAllocator: public PortAllocator { +public: + BasicPortAllocator(NetworkManager* network_manager); + BasicPortAllocator(NetworkManager* network_manager, SocketAddress *stun_server, SocketAddress *relay_server); + virtual ~BasicPortAllocator(); + + NetworkManager* network_manager() { return network_manager_; } + + // Returns the best (highest preference) phase that has produced a port that + // produced a writable connection. If no writable connections have been + // produced, this returns -1. + int best_writable_phase() const; + + virtual PortAllocatorSession *CreateSession(const std::string &name); + + // Called whenever a connection becomes writable with the argument being the + // phase that the corresponding port was created in. + void AddWritablePhase(int phase); + +private: + NetworkManager* network_manager_; + SocketAddress* stun_address_; + SocketAddress* relay_address_; + int best_writable_phase_; +}; + +struct PortConfiguration; +class AllocationSequence; + +class BasicPortAllocatorSession: public PortAllocatorSession, public MessageHandler { +public: + BasicPortAllocatorSession(BasicPortAllocator *allocator, + const std::string &name); + BasicPortAllocatorSession(BasicPortAllocator *allocator, + const std::string &name, + SocketAddress *stun_address, + SocketAddress *relay_address); + ~BasicPortAllocatorSession(); + + BasicPortAllocator* allocator() { return allocator_; } + const std::string& name() const { return name_; } + Thread* network_thread() { return network_thread_; } + + Thread* config_thread() { return config_thread_; } + void set_config_thread(Thread* thread) { config_thread_ = thread; } + + virtual void GetInitialPorts(); + virtual void StartGetAllPorts(); + virtual void StopGetAllPorts(); + virtual bool IsGettingAllPorts() { return running_; } + +protected: + // Starts the process of getting the port configurations. + virtual void GetPortConfigurations(); + + // Adds a port configuration that is now ready. Once we have one for each + // network (or a timeout occurs), we will start allocating ports. + void ConfigReady(PortConfiguration* config); + + // MessageHandler. Can be overriden if message IDs do not conflict. + virtual void OnMessage(Message *message); + +private: + void OnConfigReady(PortConfiguration* config); + void OnConfigTimeout(); + void AllocatePorts(); + void OnAllocate(); + bool HasEquivalentSequence(Network* network); + void AddAllocatedPort(Port* port, AllocationSequence * seq, float pref, bool prepare_address = true); + void OnAddressReady(Port *port); + void OnProtocolEnabled(AllocationSequence * seq, ProtocolType proto); + void OnPortDestroyed(Port* port); + void OnConnectionCreated(Port* port, Connection* conn); + void OnConnectionStateChange(Connection* conn); + void OnShake(); + + BasicPortAllocator *allocator_; + std::string name_; + Thread* network_thread_; + Thread* config_thread_; + bool configuration_done_; + bool allocation_started_; + bool running_; // set when StartGetAllPorts is called + std::vector configs_; + std::vector sequences_; + SocketAddress *stun_address_; + SocketAddress *relay_address_; + + struct PortData { + Port * port; + AllocationSequence * sequence; + bool ready; + + bool operator==(Port * rhs) const { return (port == rhs); } + }; + std::vector ports_; + + friend class AllocationSequence; +}; + +// Records configuration information useful in creating ports. +struct PortConfiguration: public MessageData { + SocketAddress stun_address; + std::string username; + std::string password; + std::string magic_cookie; + + typedef std::vector PortList; + struct RelayServer { + PortList ports; + float pref_modifier; // added to the protocol modifier to get the + // preference for this particular server + }; + + typedef std::vector RelayList; + RelayList relays; + + PortConfiguration(const SocketAddress& stun_address, + const std::string& username, + const std::string& password, + const std::string& magic_cookie); + + // Adds another relay server, with the given ports and modifier, to the list. + void AddRelay(const PortList& ports, float pref_modifier); + + // Determines whether the given relay server supports the given protocol. + static bool SupportsProtocol(const PortConfiguration::RelayServer& relay, + ProtocolType type); +}; + +} // namespace cricket + +#endif // _BASICPORTALLOCATOR_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.cc new file mode 100644 index 00000000..09b38a52 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.cc @@ -0,0 +1,545 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/p2p/client/sessionclient.h" +#include "talk/p2p/base/helpers.h" +#include "talk/base/logging.h" +#include "talk/xmllite/qname.h" +#include "talk/xmpp/constants.h" +#include "talk/xmllite/xmlprinter.h" +#include +#undef SetPort + +namespace { + +// We only allow usernames to be this many characters or fewer. +const size_t kMaxUsernameSize = 16; + +} + +namespace cricket { + +#if 0 +>>>>>> + + + + ... + + + + +<<<<<< + + +>>>>>> + + + + + + +> + +<<<<<< + + +#endif + +const std::string NS_GOOGLESESSION("http://www.google.com/session"); +const buzz::QName QN_GOOGLESESSION_SESSION(true, NS_GOOGLESESSION, "session"); +const buzz::QName QN_GOOGLESESSION_CANDIDATE(true, NS_GOOGLESESSION, "candidate"); +const buzz::QName QN_GOOGLESESSION_TARGET(true, NS_GOOGLESESSION, "target"); +const buzz::QName QN_GOOGLESESSION_COOKIE(true, NS_GOOGLESESSION, "cookie"); +const buzz::QName QN_GOOGLESESSION_REGARDING(true, NS_GOOGLESESSION, "regarding"); + +const buzz::QName QN_TYPE(true, buzz::STR_EMPTY, "type"); +const buzz::QName QN_ID(true, buzz::STR_EMPTY, "id"); +const buzz::QName QN_INITIATOR(true, buzz::STR_EMPTY, "initiator"); +const buzz::QName QN_NAME(true, buzz::STR_EMPTY, "name"); +const buzz::QName QN_PORT(true, buzz::STR_EMPTY, "port"); +const buzz::QName QN_NETWORK(true, buzz::STR_EMPTY, "network"); +const buzz::QName QN_GENERATION(true, buzz::STR_EMPTY, "generation"); +const buzz::QName QN_ADDRESS(true, buzz::STR_EMPTY, "address"); +const buzz::QName QN_USERNAME(true, buzz::STR_EMPTY, "username"); +const buzz::QName QN_PASSWORD(true, buzz::STR_EMPTY, "password"); +const buzz::QName QN_PREFERENCE(true, buzz::STR_EMPTY, "preference"); +const buzz::QName QN_PROTOCOL(true, buzz::STR_EMPTY, "protocol"); +const buzz::QName QN_KEY(true, buzz::STR_EMPTY, "key"); + +class XmlCookie: public SessionMessage::Cookie { +public: + XmlCookie(const buzz::XmlElement* elem) + : elem_(new buzz::XmlElement(*elem)) { + } + + virtual ~XmlCookie() { + delete elem_; + } + + const buzz::XmlElement* elem() const { return elem_; } + + virtual Cookie* Copy() { + return new XmlCookie(elem_); + } + +private: + buzz::XmlElement* elem_; +}; + +SessionClient::SessionClient(SessionManager *session_manager) { + session_manager_ = session_manager; + session_manager_->SignalSessionCreate.connect(this, &SessionClient::OnSessionCreateSlot); + session_manager_->SignalSessionDestroy.connect(this, &SessionClient::OnSessionDestroySlot); +} + +SessionClient::~SessionClient() { +} + +void SessionClient::OnSessionCreateSlot(Session *session, bool received_initiate) { + // Does this session belong to this session client? + if (session->name() == GetSessionDescriptionName()) { + session->SignalOutgoingMessage.connect(this, &SessionClient::OnOutgoingMessage); + OnSessionCreate(session, received_initiate); + } +} + +void SessionClient::OnSessionDestroySlot(Session *session) { + if (session->name() == GetSessionDescriptionName()) { + session->SignalOutgoingMessage.disconnect(this); + OnSessionDestroy(session); + } +} + +bool SessionClient::IsClientStanza(const buzz::XmlElement *stanza) { + // Is it a IQ set stanza? + if (stanza->Name() != buzz::QN_IQ) + return false; + if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_SET) + return false; + + // Make sure it has the right child element + const buzz::XmlElement* element + = stanza->FirstNamed(QN_GOOGLESESSION_SESSION); + if (element == NULL) + return false; + + // Is it one of the allowed types? + std::string type; + if (element->HasAttr(QN_TYPE)) { + type = element->Attr(QN_TYPE); + if (type != "initiate" && type != "accept" && type != "modify" && + type != "candidates" && type != "reject" && type != "redirect" && + type != "terminate") { + return false; + } + } + + // Does this client own the session description namespace? + buzz::QName qn_session_desc(GetSessionDescriptionName(), "description"); + const buzz::XmlElement* description = element->FirstNamed(qn_session_desc); + if (type == "initiate" || type == "accept" || type == "modify") { + if (description == NULL) + return false; + } else { + if (description != NULL) + return false; + } + + // It's good + return true; +} + +void SessionClient::OnIncomingStanza(const buzz::XmlElement *stanza) { + SessionMessage message; + if (!ParseIncomingMessage(stanza, message)) + return; + + session_manager_->OnIncomingMessage(message); +} + +void SessionClient::OnFailedSend(const buzz::XmlElement *original_stanza, + const buzz::XmlElement *failure_stanza) { + SessionMessage message; + if (!ParseIncomingMessage(original_stanza, message)) + return; + + // Note the from/to represents the *original* stanza and not the from/to + // on any return path + session_manager_->OnIncomingError(message); +} + +bool SessionClient::ParseIncomingMessage(const buzz::XmlElement *stanza, + SessionMessage& message) { + // Parse stanza into SessionMessage + const buzz::XmlElement* element + = stanza->FirstNamed(QN_GOOGLESESSION_SESSION); + + std::string type = element->Attr(QN_TYPE); + if (type == "initiate" || type == "accept" || type == "modify") { + ParseInitiateAcceptModify(stanza, message); + } else if (type == "candidates") { + ParseCandidates(stanza, message); + } else if (type == "reject" || type == "terminate") { + ParseRejectTerminate(stanza, message); + } else if (type == "redirect") { + ParseRedirect(stanza, message); + } else { + return false; + } + + return true; +} + +void SessionClient::ParseHeader(const buzz::XmlElement *stanza, SessionMessage &message) { + if (stanza->HasAttr(buzz::QN_FROM)) + message.set_from(stanza->Attr(buzz::QN_FROM)); + if (stanza->HasAttr(buzz::QN_TO)) + message.set_to(stanza->Attr(buzz::QN_TO)); + + const buzz::XmlElement *element + = stanza->FirstNamed(QN_GOOGLESESSION_SESSION); + if (element->HasAttr(QN_ID)) + message.session_id().set_id_str(element->Attr(QN_ID)); + + if (element->HasAttr(QN_INITIATOR)) + message.session_id().set_initiator(element->Attr(QN_INITIATOR)); + + std::string type = element->Attr(QN_TYPE); + if (type == "initiate") { + message.set_type(SessionMessage::TYPE_INITIATE); + } else if (type == "accept") { + message.set_type(SessionMessage::TYPE_ACCEPT); + } else if (type == "modify") { + message.set_type(SessionMessage::TYPE_MODIFY); + } else if (type == "candidates") { + message.set_type(SessionMessage::TYPE_CANDIDATES); + } else if (type == "reject") { + message.set_type(SessionMessage::TYPE_REJECT); + } else if (type == "redirect") { + message.set_type(SessionMessage::TYPE_REDIRECT); + } else if (type == "terminate") { + message.set_type(SessionMessage::TYPE_TERMINATE); + } else { + assert(false); + } +} + +void SessionClient::ParseInitiateAcceptModify(const buzz::XmlElement *stanza, SessionMessage &message) { + // Pull the standard header pieces out + ParseHeader(stanza, message); + + // Parse session description + const buzz::XmlElement *session + = stanza->FirstNamed(QN_GOOGLESESSION_SESSION); + buzz::QName qn_session_desc(GetSessionDescriptionName(), "description"); + const buzz::XmlElement* desc_elem = session->FirstNamed(qn_session_desc); + const SessionDescription *description = NULL; + if (desc_elem) + description = CreateSessionDescription(desc_elem); + message.set_name(GetSessionDescriptionName()); + message.set_description(description); +} + +void SessionClient::ParseCandidates(const buzz::XmlElement *stanza, SessionMessage &message) { + // Pull the standard header pieces out + ParseHeader(stanza, message); + + // Parse candidates and session description + std::vector candidates; + const buzz::XmlElement *element + = stanza->FirstNamed(QN_GOOGLESESSION_SESSION); + const buzz::XmlElement *child = element->FirstElement(); + while (child != NULL) { + if (child->Name() == QN_GOOGLESESSION_CANDIDATE) { + Candidate candidate; + if (ParseCandidate(child, &candidate)) + candidates.push_back(candidate); + } + child = child->NextElement(); + } + message.set_name(GetSessionDescriptionName()); + message.set_candidates(candidates); +} + +void SessionClient::ParseRejectTerminate(const buzz::XmlElement *stanza, SessionMessage &message) { + // Reject and terminate are very simple + ParseHeader(stanza, message); +} + +bool SessionClient::ParseCandidate(const buzz::XmlElement *child, + Candidate* candidate) { + // Check for all of the required attributes. + if (!child->HasAttr(QN_NAME) || + !child->HasAttr(QN_ADDRESS) || + !child->HasAttr(QN_PORT) || + !child->HasAttr(QN_USERNAME) || + !child->HasAttr(QN_PREFERENCE) || + !child->HasAttr(QN_PROTOCOL) || + !child->HasAttr(QN_GENERATION)) { + LOG(LERROR) << "Candidate missing required attribute"; + return false; + } + + SocketAddress address; + address.SetIP(child->Attr(QN_ADDRESS)); + std::istringstream ist(child->Attr(QN_PORT)); + int port; + ist >> port; + address.SetPort(port); + + if (address.IsAny()) { + LOG(LERROR) << "Candidate has address 0"; + return false; + } + + // Always disallow addresses that refer to the local host. + if (address.IsLocalIP()) { + LOG(LERROR) << "Candidate has local IP address"; + return false; + } + + // Disallow all ports below 1024, except for 80 and 443 on public addresses. + if (port < 1024) { + if ((port != 80) && (port != 443)) { + LOG(LERROR) << "Candidate has port below 1024, not 80 or 443"; + return false; + } + if (address.IsPrivateIP()) { + LOG(LERROR) << "Candidate has port of 80 or 443 with private IP address"; + return false; + } + } + + candidate->set_name(child->Attr(QN_NAME)); + candidate->set_address(address); + candidate->set_username(child->Attr(QN_USERNAME)); + candidate->set_preference_str(child->Attr(QN_PREFERENCE)); + candidate->set_protocol(child->Attr(QN_PROTOCOL)); + candidate->set_generation_str(child->Attr(QN_GENERATION)); + + // Check that the username is not too long and does not use any bad chars. + if (candidate->username().size() > kMaxUsernameSize) { + LOG(LERROR) << "Candidate username is too long"; + return false; + } + if (!IsBase64Encoded(candidate->username())) { + LOG(LERROR) << "Candidate username has non-base64 encoded characters"; + return false; + } + + // Look for the non-required attributes. + if (child->HasAttr(QN_PASSWORD)) + candidate->set_password(child->Attr(QN_PASSWORD)); + if (child->HasAttr(QN_TYPE)) + candidate->set_type(child->Attr(QN_TYPE)); + if (child->HasAttr(QN_NETWORK)) + candidate->set_network_name(child->Attr(QN_NETWORK)); + + return true; +} + +void SessionClient::ParseRedirect(const buzz::XmlElement *stanza, SessionMessage &message) { + // Pull the standard header pieces out + ParseHeader(stanza, message); + const buzz::XmlElement *session = stanza->FirstNamed(QN_GOOGLESESSION_SESSION); + + // Parse the target and cookie. + + const buzz::XmlElement* target = session->FirstNamed(QN_GOOGLESESSION_TARGET); + if (target) + message.set_redirect_target(target->Attr(QN_NAME)); + + const buzz::XmlElement* cookie = session->FirstNamed(QN_GOOGLESESSION_COOKIE); + if (cookie) + message.set_redirect_cookie(new XmlCookie(cookie)); +} + +void SessionClient::OnOutgoingMessage(Session *session, const SessionMessage &message) { + // Translate the message into an XMPP stanza + + buzz::XmlElement *result = NULL; + switch (message.type()) { + case SessionMessage::TYPE_INITIATE: + case SessionMessage::TYPE_ACCEPT: + case SessionMessage::TYPE_MODIFY: + result = TranslateInitiateAcceptModify(message); + break; + + case SessionMessage::TYPE_CANDIDATES: + result = TranslateCandidates(message); + break; + + case SessionMessage::TYPE_REJECT: + case SessionMessage::TYPE_TERMINATE: + result = TranslateRejectTerminate(message); + break; + + case SessionMessage::TYPE_REDIRECT: + result = TranslateRedirect(message); + break; + } + + // Send the stanza. Note that SessionClient is passing on ownership + // of result. + if (result != NULL) { + SignalSendStanza(this, result); + } +} + +buzz::XmlElement *SessionClient::TranslateHeader(const SessionMessage &message) { + buzz::XmlElement *result = new buzz::XmlElement(buzz::QN_IQ); + result->AddAttr(buzz::QN_TO, message.to()); + result->AddAttr(buzz::QN_TYPE, buzz::STR_SET); + buzz::XmlElement *session = new buzz::XmlElement(QN_GOOGLESESSION_SESSION, true); + result->AddElement(session); + switch (message.type()) { + case SessionMessage::TYPE_INITIATE: + session->AddAttr(QN_TYPE, "initiate"); + break; + case SessionMessage::TYPE_ACCEPT: + session->AddAttr(QN_TYPE, "accept"); + break; + case SessionMessage::TYPE_MODIFY: + session->AddAttr(QN_TYPE, "modify"); + break; + case SessionMessage::TYPE_CANDIDATES: + session->AddAttr(QN_TYPE, "candidates"); + break; + case SessionMessage::TYPE_REJECT: + session->AddAttr(QN_TYPE, "reject"); + break; + case SessionMessage::TYPE_REDIRECT: + session->AddAttr(QN_TYPE, "redirect"); + break; + case SessionMessage::TYPE_TERMINATE: + session->AddAttr(QN_TYPE, "terminate"); + break; + } + session->AddAttr(QN_ID, message.session_id().id_str()); + session->AddAttr(QN_INITIATOR, message.session_id().initiator()); + return result; +} + +buzz::XmlElement *SessionClient::TranslateCandidate(const Candidate &candidate) { + buzz::XmlElement *result = new buzz::XmlElement(QN_GOOGLESESSION_CANDIDATE); + result->AddAttr(QN_NAME, candidate.name()); + result->AddAttr(QN_ADDRESS, candidate.address().IPAsString()); + result->AddAttr(QN_PORT, candidate.address().PortAsString()); + result->AddAttr(QN_USERNAME, candidate.username()); + result->AddAttr(QN_PASSWORD, candidate.password()); + result->AddAttr(QN_PREFERENCE, candidate.preference_str()); + result->AddAttr(QN_PROTOCOL, candidate.protocol()); + result->AddAttr(QN_TYPE, candidate.type()); + result->AddAttr(QN_NETWORK, candidate.network_name()); + result->AddAttr(QN_GENERATION, candidate.generation_str()); + return result; +} + +buzz::XmlElement *SessionClient::TranslateInitiateAcceptModify(const SessionMessage &message) { + // Header info common to all message types + buzz::XmlElement *result = TranslateHeader(message); + buzz::XmlElement *session = result->FirstNamed(QN_GOOGLESESSION_SESSION); + + // Candidates + assert(message.candidates().size() == 0); + + // Session Description + buzz::XmlElement* description = TranslateSessionDescription(message.description()); + assert(description->Name().LocalPart() == "description"); + assert(description->Name().Namespace() == GetSessionDescriptionName()); + session->AddElement(description); + + if (message.redirect_cookie() != NULL) { + const buzz::XmlElement* cookie = + reinterpret_cast(message.redirect_cookie())->elem(); + for (const buzz::XmlElement* elem = cookie->FirstElement(); elem; elem = elem->NextElement()) + session->AddElement(new buzz::XmlElement(*elem)); + } + + return result; +} + +buzz::XmlElement *SessionClient::TranslateCandidates(const SessionMessage &message) { + // Header info common to all message types + buzz::XmlElement *result = TranslateHeader(message); + buzz::XmlElement *session = result->FirstNamed(QN_GOOGLESESSION_SESSION); + + // Candidates + std::vector::const_iterator it; + for (it = message.candidates().begin(); it != message.candidates().end(); it++) + session->AddElement(TranslateCandidate(*it)); + + return result; +} + +buzz::XmlElement *SessionClient::TranslateRejectTerminate(const SessionMessage &message) { + // These messages are simple, and only have a header + return TranslateHeader(message); +} + +buzz::XmlElement *SessionClient::TranslateRedirect(const SessionMessage &message) { + // Header info common to all message types + buzz::XmlElement *result = TranslateHeader(message); + buzz::XmlElement *session = result->FirstNamed(QN_GOOGLESESSION_SESSION); + + assert(message.candidates().size() == 0); + assert(message.description() == NULL); + + assert(message.redirect_target().size() > 0); + buzz::XmlElement* target = new buzz::XmlElement(QN_GOOGLESESSION_TARGET); + target->AddAttr(QN_NAME, message.redirect_target()); + session->AddElement(target); + + buzz::XmlElement* cookie = new buzz::XmlElement(QN_GOOGLESESSION_COOKIE); + session->AddElement(cookie); + + // If the message does not have a redirect cookie, then this is a redirect + // initiated by us. We will automatically add a regarding cookie. + if (message.redirect_cookie() == NULL) { + buzz::XmlElement* regarding = new buzz::XmlElement(QN_GOOGLESESSION_REGARDING); + regarding->AddAttr(QN_NAME, GetJid().BareJid().Str()); + cookie->AddElement(regarding); + } else { + const buzz::XmlElement* cookie_elem = + reinterpret_cast(message.redirect_cookie())->elem(); + const buzz::XmlElement* elem; + for (elem = cookie_elem->FirstElement(); elem; elem = elem->NextElement()) + cookie->AddElement(new buzz::XmlElement(*elem)); + } + + return result; +} + +SessionManager *SessionClient::session_manager() { + return session_manager_; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.h new file mode 100644 index 00000000..69a18422 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.h @@ -0,0 +1,104 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SESSIONCLIENT_H_ +#define _SESSIONCLIENT_H_ + +#include "talk/p2p/base/sessiondescription.h" +#include "talk/p2p/base/sessionmessage.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/jid.h" +namespace cricket { + +// Generic XMPP session client. This class knows how to translate +// a SessionMessage to and from XMPP stanzas. The SessionDescription +// is a custom description implemented by the client. + +// This class knows how to talk to the session manager, however the +// session manager doesn't have knowledge of a particular SessionClient. + +class SessionClient : public sigslot::has_slots<> { +public: + SessionClient(SessionManager *psm); + virtual ~SessionClient(); + + // Call this method to determine if a stanza is for this session client + bool IsClientStanza(const buzz::XmlElement *stanza); + + // Call this method to deliver a stanza to this session client + void OnIncomingStanza(const buzz::XmlElement *stanza); + + // Call this whenever an error is recieved in response to an outgoing + // session IQ. Include the original stanza and any failure stanza. If + // the failure is due to a time out, the failure_stanza should be NULL + void OnFailedSend(const buzz::XmlElement* original_stanza, + const buzz::XmlElement* failure_stanza); + + SessionManager *session_manager(); + + // Implement this method for stanza sending + sigslot::signal2 SignalSendStanza; + +protected: + // Override these to know when sessions belonging to this client create/destroy + + virtual void OnSessionCreate(Session * /*session*/, bool /*received_initiate*/) {} + virtual void OnSessionDestroy(Session * /*session*/) {} + + // Implement these methods for a custom session description + virtual const SessionDescription *CreateSessionDescription(const buzz::XmlElement *element) = 0; + virtual buzz::XmlElement *TranslateSessionDescription(const SessionDescription *description) = 0; + virtual const std::string &GetSessionDescriptionName() = 0; + virtual const buzz::Jid &GetJid() const = 0; + + SessionManager *session_manager_; + +private: + void OnSessionCreateSlot(Session *session, bool received_initiate); + void OnSessionDestroySlot(Session *session); + void OnOutgoingMessage(Session *session, const SessionMessage &message); + void ParseHeader(const buzz::XmlElement *stanza, SessionMessage &message); + bool ParseCandidate(const buzz::XmlElement *child, Candidate* candidate); + bool ParseIncomingMessage(const buzz::XmlElement *stanza, + SessionMessage& message); + void ParseInitiateAcceptModify(const buzz::XmlElement *stanza, SessionMessage &message); + void ParseCandidates(const buzz::XmlElement *stanza, SessionMessage &message); + void ParseRejectTerminate(const buzz::XmlElement *stanza, SessionMessage &message); + void ParseRedirect(const buzz::XmlElement *stanza, SessionMessage &message); + buzz::XmlElement *TranslateHeader(const SessionMessage &message); + buzz::XmlElement *TranslateCandidate(const Candidate &candidate); + buzz::XmlElement *TranslateInitiateAcceptModify(const SessionMessage &message); + buzz::XmlElement *TranslateCandidates(const SessionMessage &message); + buzz::XmlElement *TranslateRejectTerminate(const SessionMessage &message); + buzz::XmlElement *TranslateRedirect(const SessionMessage &message); + +}; + +} // namespace cricket + +#endif // _SESSIONCLIENT_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.cc new file mode 100644 index 00000000..dd9fa67c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.cc @@ -0,0 +1,149 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "socketmonitor.h" +#include + +namespace cricket { + +const uint32 MSG_MONITOR_POLL = 1; +const uint32 MSG_MONITOR_START = 2; +const uint32 MSG_MONITOR_STOP = 3; +const uint32 MSG_MONITOR_SIGNAL = 4; + +SocketMonitor::SocketMonitor(P2PSocket *socket, Thread *monitor_thread) { + socket_ = socket; + monitoring_thread_ = monitor_thread; + monitoring_ = false; +} + +SocketMonitor::~SocketMonitor() { + socket_->thread()->Clear(this); + monitoring_thread_->Clear(this); +} + +void SocketMonitor::Start(int milliseconds) { + rate_ = milliseconds; + if (rate_ < 250) + rate_ = 250; + socket_->thread()->Post(this, MSG_MONITOR_START); +} + +void SocketMonitor::Stop() { + socket_->thread()->Post(this, MSG_MONITOR_STOP); +} + +void SocketMonitor::OnMessage(Message *message) { + CritScope cs(&crit_); + + switch (message->message_id) { + case MSG_MONITOR_START: + assert(Thread::Current() == socket_->thread()); + if (!monitoring_) { + monitoring_ = true; + socket_->SignalConnectionMonitor.connect(this, &SocketMonitor::OnConnectionMonitor); + PollSocket(true); + } + break; + + case MSG_MONITOR_STOP: + assert(Thread::Current() == socket_->thread()); + if (monitoring_) { + monitoring_ = false; + socket_->SignalConnectionMonitor.disconnect(this); + socket_->thread()->Clear(this); + } + break; + + case MSG_MONITOR_POLL: + assert(Thread::Current() == socket_->thread()); + PollSocket(true); + break; + + case MSG_MONITOR_SIGNAL: + { + assert(Thread::Current() == monitoring_thread_); + std::vector infos = connection_infos_; + crit_.Leave(); + SignalUpdate(this, infos); + crit_.Enter(); + } + break; + } +} + +void SocketMonitor::OnConnectionMonitor(P2PSocket *socket) { + CritScope cs(&crit_); + if (monitoring_) + PollSocket(false); +} + +void SocketMonitor::PollSocket(bool poll) { + CritScope cs(&crit_); + assert(Thread::Current() == socket_->thread()); + + // Gather connection infos + + connection_infos_.clear(); + const std::vector &connections = socket_->connections(); + std::vector::const_iterator it; + for (it = connections.begin(); it != connections.end(); it++) { + Connection *connection = *it; + ConnectionInfo info; + info.best_connection = socket_->best_connection() == connection; + info.readable = connection->read_state() == Connection::STATE_READABLE; + info.writable = connection->write_state() == Connection::STATE_WRITABLE; + info.timeout = connection->write_state() == Connection::STATE_WRITE_TIMEOUT; + info.new_connection = false; // connection->new_connection(); + info.rtt = connection->rtt(); + info.sent_total_bytes = connection->sent_total_bytes(); + info.sent_bytes_second = connection->sent_bytes_second(); + info.recv_total_bytes = connection->recv_total_bytes(); + info.recv_bytes_second = connection->recv_bytes_second(); + info.local_candidate = connection->local_candidate(); + info.remote_candidate = connection->remote_candidate(); + info.est_quality = connection->port()->network()->quality(); + info.key = reinterpret_cast(connection); + connection_infos_.push_back(info); + } + + // Signal the monitoring thread, start another poll timer + + monitoring_thread_->Post(this, MSG_MONITOR_SIGNAL); + if (poll) + socket_->thread()->PostDelayed(rate_, this, MSG_MONITOR_POLL); +} + +P2PSocket *SocketMonitor::socket() { + return socket_; +} + +Thread *SocketMonitor::monitor_thread() { + return monitoring_thread_; +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.h new file mode 100644 index 00000000..549e90b6 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.h @@ -0,0 +1,85 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SOCKETMONITOR_H_ +#define _SOCKETMONITOR_H_ + +#include "talk/base/thread.h" +#include "talk/base/sigslot.h" +#include "talk/base/criticalsection.h" +#include "talk/p2p/base/p2psocket.h" +#include "talk/p2p/base/port.h" +#include + +namespace cricket { + +struct ConnectionInfo { + bool best_connection; + bool writable; + bool readable; + bool timeout; + bool new_connection; + size_t rtt; + size_t sent_total_bytes; + size_t sent_bytes_second; + size_t recv_total_bytes; + size_t recv_bytes_second; + Candidate local_candidate; + Candidate remote_candidate; + double est_quality; + void *key; +}; + +class SocketMonitor : public MessageHandler, public sigslot::has_slots<> { +public: + SocketMonitor(P2PSocket *socket, Thread *monitor_thread); + ~SocketMonitor(); + + void Start(int cms); + void Stop(); + + P2PSocket *socket(); + Thread *monitor_thread(); + + sigslot::signal2 &> SignalUpdate; + +protected: + void OnMessage(Message *message); + void OnConnectionMonitor(P2PSocket *socket); + void PollSocket(bool poll); + + std::vector connection_infos_; + P2PSocket *socket_; + Thread *monitoring_thread_; + CriticalSection crit_; + uint32 rate_; + bool monitoring_; +}; + +} + +#endif // _SOCKETMONITOR_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/session/Makefile.am new file mode 100644 index 00000000..6cfc5b24 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/Makefile.am @@ -0,0 +1,3 @@ +noinst_HEADERS = receiver.h sessionsendtask.h +SUBDIRS = phone + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/Makefile.am new file mode 100644 index 00000000..b2acbf81 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/Makefile.am @@ -0,0 +1,18 @@ +libcricketsessionphone_la_SOURCES = audiomonitor.cc \ + channelmanager.cc \ + voicechannel.cc \ + call.cc \ + phonesessionclient.cc \ + linphonemediaengine.cc + +noinst_HEADERS = audiomonitor.h \ + channelmanager.h \ + linphonemediaengine.h \ + mediaengine.h \ + phonesessionclient.h \ + voicechannel.h \ + call.h \ + mediachannel.h + +AM_CPPFLAGS = -DPOSIX $(ORTP_CFLAGS) $(ILBC_CFLAGS) -I$(srcdir)/../../../talk/third_party/mediastreamer -I$(srcdir)/../../.. $(GLIB_CFLAGS) $(SPEEX_CFLAGS) +noinst_LTLIBRARIES = libcricketsessionphone.la diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.cc new file mode 100644 index 00000000..c1b63d1b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.cc @@ -0,0 +1,119 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/session/phone/audiomonitor.h" +#include "talk/session/phone/voicechannel.h" +#include + +namespace cricket { + +const uint32 MSG_MONITOR_POLL = 1; +const uint32 MSG_MONITOR_START = 2; +const uint32 MSG_MONITOR_STOP = 3; +const uint32 MSG_MONITOR_SIGNAL = 4; + +AudioMonitor::AudioMonitor(VoiceChannel *voice_channel, Thread *monitor_thread) { + voice_channel_ = voice_channel; + monitoring_thread_ = monitor_thread; + monitoring_ = false; +} + +AudioMonitor::~AudioMonitor() { + voice_channel_->worker_thread()->Clear(this); + monitoring_thread_->Clear(this); +} + +void AudioMonitor::Start(int milliseconds) { + rate_ = milliseconds; + if (rate_ < 100) + rate_ = 100; + voice_channel_->worker_thread()->Post(this, MSG_MONITOR_START); +} + +void AudioMonitor::Stop() { + voice_channel_->worker_thread()->Post(this, MSG_MONITOR_STOP); +} + +void AudioMonitor::OnMessage(Message *message) { + CritScope cs(&crit_); + + switch (message->message_id) { + case MSG_MONITOR_START: + assert(Thread::Current() == voice_channel_->worker_thread()); + if (!monitoring_) { + monitoring_ = true; + PollVoiceChannel(); + } + break; + + case MSG_MONITOR_STOP: + assert(Thread::Current() == voice_channel_->worker_thread()); + if (monitoring_) { + monitoring_ = false; + voice_channel_->worker_thread()->Clear(this); + } + break; + + case MSG_MONITOR_POLL: + assert(Thread::Current() == voice_channel_->worker_thread()); + PollVoiceChannel(); + break; + + case MSG_MONITOR_SIGNAL: + { + assert(Thread::Current() == monitoring_thread_); + AudioInfo info = audio_info_; + crit_.Leave(); + SignalUpdate(this, audio_info_); + crit_.Enter(); + } + break; + } +} + +void AudioMonitor::PollVoiceChannel() { + CritScope cs(&crit_); + assert(Thread::Current() == voice_channel_->worker_thread()); + + // Gather connection infos + audio_info_.input_level = voice_channel_->GetInputLevel_w(); + audio_info_.output_level = voice_channel_->GetOutputLevel_w(); + + // Signal the monitoring thread, start another poll timer + monitoring_thread_->Post(this, MSG_MONITOR_SIGNAL); + voice_channel_->worker_thread()->PostDelayed(rate_, this, MSG_MONITOR_POLL); +} + +VoiceChannel *AudioMonitor::voice_channel() { + return voice_channel_; +} + +Thread *AudioMonitor::monitor_thread() { + return monitoring_thread_; +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.h new file mode 100644 index 00000000..96b95bd7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.h @@ -0,0 +1,73 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _CRICKET_PHONE_AUDIOMONITOR_H_ +#define _CRICKET_PHONE_AUDIOMONITOR_H_ + +#include "talk/base/thread.h" +#include "talk/base/sigslot.h" +#include "talk/p2p/base/port.h" +#include + +namespace cricket { + +class VoiceChannel; + + +struct AudioInfo { + int input_level; + int output_level; +}; + +class AudioMonitor : public MessageHandler, public sigslot::has_slots<> { +public: + AudioMonitor(VoiceChannel* voice_channel, Thread *monitor_thread); + ~AudioMonitor(); + + void Start(int cms); + void Stop(); + + VoiceChannel* voice_channel(); + Thread *monitor_thread(); + + sigslot::signal2 SignalUpdate; + +protected: + void OnMessage(Message *message); + void PollVoiceChannel(); + + AudioInfo audio_info_; + VoiceChannel* voice_channel_; + Thread* monitoring_thread_; + CriticalSection crit_; + uint32 rate_; + bool monitoring_; +}; + +} + +#endif // _CRICKET_PHONE_AUDIOMONITOR_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.cc new file mode 100644 index 00000000..31b12e92 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.cc @@ -0,0 +1,258 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/thread.h" +#include "talk/p2p/base/helpers.h" +#include "talk/session/phone/call.h" + +namespace cricket { + +const uint32 MSG_CHECKAUTODESTROY = 1; + +Call::Call(PhoneSessionClient *session_client) : muted_(false) { + session_client_ = session_client; + id_ = CreateRandomId(); +} + +Call::~Call() { + while (sessions_.begin() != sessions_.end()) { + Session *session = sessions_[0]; + RemoveSession(session); + session_client_->session_manager()->DestroySession(session); + } + Thread::Current()->Clear(this); +} + +Session *Call::InitiateSession(const buzz::Jid &jid) { + Session *session = session_client_->CreateSession(this); + AddSession(session); + session->Initiate(jid.Str(), session_client_->CreateOfferSessionDescription()); + return session; +} + +void Call::AcceptSession(Session *session) { + std::vector::iterator it; + it = std::find(sessions_.begin(), sessions_.end(), session); + assert(it != sessions_.end()); + if (it != sessions_.end()) + session->Accept(session_client_->CreateAcceptSessionDescription(session->remote_description())); +} + +void Call::RedirectSession(Session *session, const buzz::Jid &to) { + std::vector::iterator it; + it = std::find(sessions_.begin(), sessions_.end(), session); + assert(it != sessions_.end()); + if (it != sessions_.end()) + session->Redirect(to.Str()); +} + +void Call::RejectSession(Session *session) { + std::vector::iterator it; + it = std::find(sessions_.begin(), sessions_.end(), session); + assert(it != sessions_.end()); + if (it != sessions_.end()) + session->Reject(); +} + +void Call::TerminateSession(Session *session) { + assert(std::find(sessions_.begin(), sessions_.end(), session) != sessions_.end()); + std::vector::iterator it; + it = std::find(sessions_.begin(), sessions_.end(), session); + if (it != sessions_.end()) + (*it)->Terminate(); +} + +void Call::Terminate() { + // There may be more than one session to terminate + std::vector::iterator it = sessions_.begin(); + for (it = sessions_.begin(); it != sessions_.end(); it++) + TerminateSession(*it); +} + +void Call::OnMessage(Message *message) { + switch (message->message_id) { + case MSG_CHECKAUTODESTROY: + // If no more sessions for this call, delete it + if (sessions_.size() == 0) + session_client_->DestroyCall(this); + break; + } +} + +const std::vector &Call::sessions() { + return sessions_; +} + +void Call::AddSession(Session *session) { + // Add session to list, create voice channel for this session + sessions_.push_back(session); + session->SignalState.connect(this, &Call::OnSessionState); + session->SignalError.connect(this, &Call::OnSessionError); + + VoiceChannel *channel = session_client_->channel_manager()->CreateVoiceChannel(session); + channel_map_[session->id()] = channel; + + // If this call has the focus, enable this channel + if (session_client_->GetFocus() == this) + channel->Enable(true); + + // Signal client + SignalAddSession(this, session); +} + +void Call::RemoveSession(Session *session) { + // Remove session from list + std::vector::iterator it_session; + it_session = std::find(sessions_.begin(), sessions_.end(), session); + if (it_session == sessions_.end()) + return; + sessions_.erase(it_session); + + // Destroy session channel + std::map::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = it_channel->second; + channel_map_.erase(it_channel); + session_client_->channel_manager()->DestroyVoiceChannel(channel); + } + + // Signal client + SignalRemoveSession(this, session); + + // The call auto destroys when the lass session is removed + Thread::Current()->Post(this, MSG_CHECKAUTODESTROY); +} + +VoiceChannel* Call::GetChannel(Session* session) { + std::map::iterator it = channel_map_.find(session->id()); + assert(it != channel_map_.end()); + return it->second; +} + +void Call::EnableChannels(bool enable) { + std::vector::iterator it; + for (it = sessions_.begin(); it != sessions_.end(); it++) { + VoiceChannel *channel = channel_map_[(*it)->id()]; + if (channel != NULL) + channel->Enable(enable); + } +} + +void Call::Mute(bool mute) { + muted_ = mute; + std::vector::iterator it; + for (it = sessions_.begin(); it != sessions_.end(); it++) { + VoiceChannel *channel = channel_map_[(*it)->id()]; + if (channel != NULL) + channel->Mute(mute); + } +} + +void Call::Join(Call *call, bool enable) { + while (call->sessions_.size() != 0) { + // Move session + Session *session = call->sessions_[0]; + call->sessions_.erase(call->sessions_.begin()); + sessions_.push_back(session); + session->SignalState.connect(this, &Call::OnSessionState); + session->SignalError.connect(this, &Call::OnSessionError); + + // Move channel + std::map::iterator it_channel; + it_channel = call->channel_map_.find(session->id()); + if (it_channel != call->channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + call->channel_map_.erase(it_channel); + channel_map_[session->id()] = channel; + channel->Enable(enable); + } + } +} + +void Call::StartConnectionMonitor(Session *session, int cms) { + std::map::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + channel->SignalConnectionMonitor.connect(this, &Call::OnConnectionMonitor); + channel->StartConnectionMonitor(cms); + } +} + +void Call::StopConnectionMonitor(Session *session) { + std::map::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + channel->StopConnectionMonitor(); + channel->SignalConnectionMonitor.disconnect(this); + } +} + +void Call::StartAudioMonitor(Session *session, int cms) { + std::map::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + channel->SignalAudioMonitor.connect(this, &Call::OnAudioMonitor); + channel->StartAudioMonitor(cms); + } +} + +void Call::StopAudioMonitor(Session *session) { + std::map::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + channel->StopAudioMonitor(); + channel->SignalAudioMonitor.disconnect(this); + } +} + + +void Call::OnConnectionMonitor(VoiceChannel *channel, const std::vector &infos) { + SignalConnectionMonitor(this, channel->session(), infos); +} + +void Call::OnAudioMonitor(VoiceChannel *channel, const AudioInfo& info) { + SignalAudioMonitor(this, channel->session(), info); +} + +uint32 Call::id() { + return id_; +} + +void Call::OnSessionState(Session *session, Session::State state) { + SignalSessionState(this, session, state); +} + +void Call::OnSessionError(Session *session, Session::Error error) { + SignalSessionError(this, session, error); +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.h new file mode 100644 index 00000000..209e13c9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.h @@ -0,0 +1,97 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _CALL_H_ +#define _CALL_H_ + +#include "talk/base/messagequeue.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/client/socketmonitor.h" +#include "talk/xmpp/jid.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/session/phone/voicechannel.h" +#include "talk/session/phone/audiomonitor.h" + +#include +#include + +namespace cricket { + +class PhoneSessionClient; + +class Call : public MessageHandler, public sigslot::has_slots<> { +public: + Call(PhoneSessionClient *session_client); + ~Call(); + + Session *InitiateSession(const buzz::Jid &jid); + void AcceptSession(Session *session); + void RedirectSession(Session *session, const buzz::Jid &to); + void RejectSession(Session *session); + void TerminateSession(Session *session); + void Terminate(); + void StartConnectionMonitor(Session *session, int cms); + void StopConnectionMonitor(Session *session); + void StartAudioMonitor(Session *session, int cms); + void StopAudioMonitor(Session *session); + void Mute(bool mute); + + const std::vector &sessions(); + uint32 id(); + bool muted() const { return muted_; } + + sigslot::signal2 SignalAddSession; + sigslot::signal2 SignalRemoveSession; + sigslot::signal3 SignalSessionState; + sigslot::signal3 SignalSessionError; + sigslot::signal3 &> SignalConnectionMonitor; + sigslot::signal3 SignalAudioMonitor; + +private: + void OnMessage(Message *message); + void OnSessionState(Session *session, Session::State state); + void OnSessionError(Session *session, Session::Error error); + void AddSession(Session *session); + void RemoveSession(Session *session); + void EnableChannels(bool enable); + void Join(Call *call, bool enable); + void OnConnectionMonitor(VoiceChannel *channel, const std::vector &infos); + void OnAudioMonitor(VoiceChannel *channel, const AudioInfo& info); + VoiceChannel* GetChannel(Session* session); + + uint32 id_; + PhoneSessionClient *session_client_; + std::vector sessions_; + std::map channel_map_; + bool muted_; + + friend class PhoneSessionClient; +}; + +} + +#endif // _CALL_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.cc new file mode 100644 index 00000000..98634b12 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.cc @@ -0,0 +1,203 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_GIPS +#include "talk/session/phone/gipsmediaengine.h" +#else +#include "talk/session/phone/linphonemediaengine.h" +#endif +#include "channelmanager.h" +#include +#include +namespace cricket { + +const uint32 MSG_CREATEVOICECHANNEL = 1; +const uint32 MSG_DESTROYVOICECHANNEL = 2; +const uint32 MSG_SETAUDIOOPTIONS = 3; + +ChannelManager::ChannelManager(Thread *worker_thread) { +#ifdef HAVE_GIPS + media_engine_ = new GipsMediaEngine(); +#else + media_engine_ = new LinphoneMediaEngine(); +#endif + worker_thread_ = worker_thread; + initialized_ = false; + Init(); +} + +ChannelManager::~ChannelManager() { + Exit(); +} + +MediaEngine *ChannelManager::media_engine() { + return media_engine_; +} + +bool ChannelManager::Init() { + initialized_ = media_engine_->Init(); + return initialized_; +} + +void ChannelManager::Exit() { + if (!initialized_) + return; + + // Need to destroy the voice channels + + while (true) { + crit_.Enter(); + VoiceChannel *channel = NULL; + if (channels_.begin() != channels_.end()) + channel = channels_[0]; + crit_.Leave(); + if (channel == NULL) + break; + delete channel; + } + media_engine_->Terminate(); +} + +struct CreateParams { + Session *session; + VoiceChannel *channel; +}; + +VoiceChannel *ChannelManager::CreateVoiceChannel(Session *session) { + CreateParams params; + params.session = session; + params.channel = NULL; + TypedMessageData data(¶ms); + worker_thread_->Send(this, MSG_CREATEVOICECHANNEL, &data); + return params.channel; +} + +VoiceChannel *ChannelManager::CreateVoiceChannel_w(Session *session) { + CritScope cs(&crit_); + + // This is ok to alloc from a thread other than the worker thread + assert(initialized_); + MediaChannel *channel = media_engine_->CreateChannel(); + if (channel == NULL) + return NULL; + + VoiceChannel *voice_channel = new VoiceChannel(this, session, channel); + channels_.push_back(voice_channel); + return voice_channel; +} + +void ChannelManager::DestroyVoiceChannel(VoiceChannel *voice_channel) { + TypedMessageData data(voice_channel); + worker_thread_->Send(this, MSG_DESTROYVOICECHANNEL, &data); +} + +void ChannelManager::DestroyVoiceChannel_w(VoiceChannel *voice_channel) { + CritScope cs(&crit_); + // Destroy voice channel. + assert(initialized_); + std::vector::iterator it = std::find(channels_.begin(), + channels_.end(), voice_channel); + assert(it != channels_.end()); + if (it == channels_.end()) + return; + + channels_.erase(it); + MediaChannel *channel = voice_channel->channel(); + delete voice_channel; + delete channel; +} + +void ChannelManager::SetAudioOptions(bool auto_gain_control, int wave_in_device, + int wave_out_device) { + AudioOptions options; + options.auto_gain_control = auto_gain_control; + options.wave_in_device = wave_in_device; + options.wave_out_device = wave_out_device; + TypedMessageData data(options); + worker_thread_->Send(this, MSG_SETAUDIOOPTIONS, &data); +} + +void ChannelManager::SetAudioOptions_w(AudioOptions options) { + assert(worker_thread_ == Thread::Current()); + + // Set auto gain control on + if (media_engine_->SetAudioOptions(options.auto_gain_control?MediaEngine::AUTO_GAIN_CONTROL:0) != 0) { + // TODO: We need to log these failures. + } + + // Set the audio devices + // This will fail if audio is already playing. Stop all of the media + // start it up again after changing the setting. + { + CritScope cs(&crit_); + for (VoiceChannels::iterator it = channels_.begin(); + it < channels_.end(); + ++it) { + (*it)->PauseMedia_w(); + } + + if (media_engine_->SetSoundDevices(options.wave_in_device, options.wave_out_device) == -1) { + // TODO: We need to log these failures. + } + + for (VoiceChannels::iterator it = channels_.begin(); + it < channels_.end(); + ++it) { + (*it)->UnpauseMedia_w(); + } + } +} + +Thread *ChannelManager::worker_thread() { + return worker_thread_; +} + +void ChannelManager::OnMessage(Message *message) { + switch (message->message_id) { + case MSG_CREATEVOICECHANNEL: + { + TypedMessageData *data = static_cast *>(message->pdata); + data->data()->channel = CreateVoiceChannel_w(data->data()->session); + } + break; + + case MSG_DESTROYVOICECHANNEL: + { + TypedMessageData *data = static_cast *>(message->pdata); + DestroyVoiceChannel_w(data->data()); + } + break; + case MSG_SETAUDIOOPTIONS: + { + TypedMessageData *data = static_cast *>(message->pdata); + SetAudioOptions_w(data->data()); + } + break; + } +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.h new file mode 100644 index 00000000..7200f75e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.h @@ -0,0 +1,81 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _CHANNELMANAGER_H_ +#define _CHANNELMANAGER_H_ + +#include "talk/base/thread.h" +#include "talk/base/criticalsection.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/p2psocket.h" +#include "talk/session/phone/voicechannel.h" +#include "talk/session/phone/mediaengine.h" +#include + +namespace cricket { + +class VoiceChannel; + +class ChannelManager : public MessageHandler { +public: + ChannelManager(Thread *worker_thread); + ~ChannelManager(); + + VoiceChannel *CreateVoiceChannel(Session *session); + void DestroyVoiceChannel(VoiceChannel *voice_channel); + void SetAudioOptions(bool auto_gain_control, int wave_in_device, + int wave_out_device); + + MediaEngine *media_engine(); + Thread *worker_thread(); + +private: + VoiceChannel *CreateVoiceChannel_w(Session *session); + void DestroyVoiceChannel_w(VoiceChannel *voice_channel); + void OnMessage(Message *message); + bool Init(); + void Exit(); + + struct AudioOptions { + bool auto_gain_control; + int wave_in_device; + int wave_out_device; + }; + void SetAudioOptions_w(AudioOptions options); + + Thread *worker_thread_; + MediaEngine *media_engine_; + bool initialized_; + CriticalSection crit_; + + typedef std::vector VoiceChannels; + VoiceChannels channels_; +}; + +} + +#endif // _CHANNELMANAGER_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.cc new file mode 100644 index 00000000..7d2305dc --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.cc @@ -0,0 +1,170 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * 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 + */ + +// LinphoneMediaEngine is a Linphone implementation of MediaEngine +extern "C" { +#include "talk/third_party/mediastreamer/mediastream.h" +#ifdef HAVE_ILBC +#include "talk/third_party/mediastreamer/msilbcdec.h" +#endif +#ifdef HAVE_SPEEX +#include "talk/third_party/mediastreamer/msspeexdec.h" +#endif +} +#include +#include +#include +#include +#include +#include "talk/session/phone/linphonemediaengine.h" + +using namespace cricket; + +void *thread_function(void *data) +{ + LinphoneMediaChannel *mc =(LinphoneMediaChannel*) data; + while (mc->dying() == false) { + MediaChannel::NetworkInterface *iface = mc->network_interface(); + char *buf[2048]; + int len; + len = read(mc->fd(), buf, sizeof(buf)); + if (iface && (mc->mute()==FALSE)) + iface->SendPacket(buf, len); + } +} + +LinphoneMediaChannel::LinphoneMediaChannel() { + pt_ = 102; + dying_ = false; + pthread_attr_t attr; + audio_stream_ = NULL; + + struct sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + sockaddr.sin_addr.s_addr = INADDR_ANY; + sockaddr.sin_port = htons(3000); + fd_ = socket(PF_INET, SOCK_DGRAM, 0); + fcntl(fd_, F_SETFL, 0, O_NONBLOCK); + bind (fd_,(struct sockaddr*)&sockaddr, sizeof(sockaddr)); + + pthread_attr_init(&attr); + pthread_create(&thread_, &attr, &thread_function, this); +} + +LinphoneMediaChannel::~LinphoneMediaChannel() { + dying_ = true; + pthread_join(thread_, NULL); + audio_stream_stop(audio_stream_); + close(fd_); +} + +void LinphoneMediaChannel::SetCodec(const char *codec) { + if (!strcmp(codec, "iLBC")) + pt_ = 102; + else if (!strcmp(codec, "speex")) + pt_ = 110; + else + pt_ = 0; + if (audio_stream_) + audio_stream_stop(audio_stream_); + audio_stream_ = audio_stream_start(&av_profile, 2000, "127.0.0.1", 3000, pt_, 250); +} + +void LinphoneMediaChannel::OnPacketReceived(const void *data, int len) { + struct sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + struct hostent *host = gethostbyname("localhost"); + memcpy(&sockaddr.sin_addr.s_addr, host->h_addr, host->h_length); + sockaddr.sin_port = htons(2000); + + char buf[2048]; + memcpy(buf, data, len); + + if (buf[1] == pt_) { + } else if (buf[1] == 13) { + } else if (buf[1] == 102) { + SetCodec("iLBC"); + } else if (buf[1] == 110) { + SetCodec("speex"); + } else if (buf[1] == 0) { + SetCodec("PCMU"); + } + + if (play_ && buf[1] != 13) + sendto(fd_, buf, len, 0, (struct sockaddr*)&sockaddr, sizeof(sockaddr)); +} + +void LinphoneMediaChannel::SetPlayout(bool playout) { + play_ = playout; +} + +void LinphoneMediaChannel::SetSend(bool send) { + mute_ = !send; +} + +float LinphoneMediaChannel::GetCurrentQuality() {} +int LinphoneMediaChannel::GetOutputLevel() {} + +LinphoneMediaEngine::LinphoneMediaEngine() {} +LinphoneMediaEngine::~LinphoneMediaEngine() {} + +static void null_log_handler(const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) { +} + +bool LinphoneMediaEngine::Init() { + g_log_set_handler("MediaStreamer", G_LOG_LEVEL_MASK, null_log_handler, NULL); + g_log_set_handler("oRTP", G_LOG_LEVEL_MASK, null_log_handler, NULL); + g_log_set_handler("oRTP-stats", G_LOG_LEVEL_MASK, null_log_handler, NULL); + ortp_init(); + ms_init(); + +#ifdef HAVE_SPEEX + ms_speex_codec_init(); + rtp_profile_set_payload(&av_profile, 110, &speex_wb); + codecs_.push_back(Codec(110, "speex", 8)); +#endif + +#ifdef HAVE_ILBC + ms_ilbc_codec_init(); + rtp_profile_set_payload(&av_profile, 102, &payload_type_ilbc); + codecs_.push_back(Codec(102, "iLBC", 4)); +#endif + + rtp_profile_set_payload(&av_profile, 0, &pcmu8000); + codecs_.push_back(Codec(0, "PCMU", 2)); + +return true; +} + +void LinphoneMediaEngine::Terminate() { + +} + +MediaChannel *LinphoneMediaEngine::CreateChannel() { + return new LinphoneMediaChannel(); +} + +int LinphoneMediaEngine::SetAudioOptions(int options) {} +int LinphoneMediaEngine::SetSoundDevices(int wave_in_device, int wave_out_device) {} + +float LinphoneMediaEngine::GetCurrentQuality() {} +int LinphoneMediaEngine::GetInputLevel() {} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.h new file mode 100644 index 00000000..ee16d2ee --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.h @@ -0,0 +1,75 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google Inc. + * + * 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 + */ + +// LinphoneMediaEngine is a Linphone implementation of MediaEngine + +#ifndef TALK_SESSION_PHONE_LINPHONEMEDIAENGINE_H_ +#define TALK_SESSION_PHONE_LINPHONEMEDIAENGINE_H_ + +extern "C" { +#include "talk/third_party/mediastreamer/mediastream.h" +} +#include "talk/session/phone/mediaengine.h" + +namespace cricket { + +class LinphoneMediaChannel : public MediaChannel { + public: + LinphoneMediaChannel(); + virtual ~LinphoneMediaChannel(); + virtual void SetCodec(const char *codec); + virtual void OnPacketReceived(const void *data, int len); + + virtual void SetPlayout(bool playout); + virtual void SetSend(bool send); + + virtual float GetCurrentQuality(); + virtual int GetOutputLevel(); + int fd() {return fd_;} + bool mute() {return mute_;} + bool dying() {return dying_;} + private: + AudioStream *audio_stream_; + pthread_t thread_; + int fd_; + int pt_; + bool dying_; + bool mute_; + bool play_; +}; + +class LinphoneMediaEngine : public MediaEngine { + public: + LinphoneMediaEngine(); + ~LinphoneMediaEngine(); + virtual bool Init(); + virtual void Terminate(); + + virtual MediaChannel *CreateChannel(); + + virtual int SetAudioOptions(int options); + virtual int SetSoundDevices(int wave_in_device, int wave_out_device); + + virtual float GetCurrentQuality(); + virtual int GetInputLevel(); +}; + +} // namespace cricket + +#endif // TALK_SESSION_PHONE_LINPHONEMEDIAENGINE_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediachannel.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediachannel.h new file mode 100644 index 00000000..db2f9654 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediachannel.h @@ -0,0 +1,55 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_SESSION_PHONE_MEDIACHANNEL_H_ +#define TALK_SESSION_PHONE_MEDIACHANNEL_H_ + +namespace cricket { + +class MediaChannel { + public: + class NetworkInterface { + public: + virtual void SendPacket(const void *data, size_t len) = 0; + }; + MediaChannel() {network_interface_ = NULL;} + virtual ~MediaChannel() {}; + void SetInterface(NetworkInterface *iface) {network_interface_ = iface;} + virtual void SetCodec(const char *codec) = 0; + virtual void OnPacketReceived(const void *data, int len) = 0; + virtual void SetPlayout(bool playout) = 0; + virtual void SetSend(bool send) = 0; + virtual float GetCurrentQuality() = 0; + virtual int GetOutputLevel() = 0; + NetworkInterface *network_interface() {return network_interface_;} + protected: + NetworkInterface *network_interface_; +}; + +}; // namespace cricket + +#endif // TALK_SESSION_PHONE_MEDIACHANNEL_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediaengine.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediaengine.h new file mode 100644 index 00000000..fa07d2ec --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediaengine.h @@ -0,0 +1,95 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// MediaEngine is an abstraction of a media engine which can be subclassed +// to support different media componentry backends. + +#ifndef TALK_SESSION_PHONE_MEDIAENGINE_H_ +#define TALK_SESSION_PHONE_MEDIAENGINE_H_ + +#include +#include +#include "mediachannel.h" + +namespace cricket { + +class MediaEngine { + public: + + struct Codec { + int id; + std::string name; + int preference; + // Creates a codec with the given parameters. + Codec(int pt, const std::string& nm, int pr) : id(pt), name(nm), preference(pr) {} + // Ranks codecs by their preferences. + bool operator <(const Codec& c) const { return preference > c.preference; } + }; + + // Bitmask flags for options that may be supported by the media engine implementation + enum MediaEngineOptions { + AUTO_GAIN_CONTROL = 1 << 1, + }; + + MediaEngine() {} + + // Initialize + virtual bool Init() = 0; + virtual void Terminate() = 0; + virtual MediaChannel *CreateChannel() = 0; + + virtual int SetAudioOptions(int options) = 0; + virtual int SetSoundDevices(int wave_in_device, int wave_out_device) = 0; + virtual int GetInputLevel() = 0; + + std::vector &codecs() { return codecs_; } + + bool FindCodec(const char* codec) { + for (std::vector::iterator i = codecs_.begin(); i < codecs_.end(); i++) { + if ((*i).name == codec) + return true; + } + return false; + } + + bool GetCodecPreference (const char *codec, int & preference) { + for (std::vector::iterator i = codecs_.begin(); i < codecs_.end(); i++) { + if ((*i).name == codec) { + preference = (*i).preference; + return true; + } + } + return false; + } + + protected: + std::vector codecs_; +}; + +} // namespace cricket + +#endif // TALK_SESSION_PHONE_MEDIAENGINE_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.cc new file mode 100644 index 00000000..d8a31df2 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.cc @@ -0,0 +1,267 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/logging.h" +#include "talk/session/receiver.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/xmllite/qname.h" +namespace { + +const std::string NS_PHONE("http://www.google.com/session/phone"); +const std::string NS_EMPTY(""); + +const buzz::QName QN_PHONE_DESCRIPTION(true, NS_PHONE, "description"); +const buzz::QName QN_PHONE_PAYLOADTYPE(true, NS_PHONE, "payload-type"); +const buzz::QName QN_PHONE_PAYLOADTYPE_ID(true, NS_EMPTY, "id"); +const buzz::QName QN_PHONE_PAYLOADTYPE_NAME(true, NS_EMPTY, "name"); + +} + +namespace cricket { + +PhoneSessionClient::PhoneSessionClient(const buzz::Jid& jid, + SessionManager *manager) : jid_(jid), SessionClient(manager) { + + // No call to start, and certainly no call with focus + focus_call_ = NULL; + + // Start up the channel manager on a worker thread + channel_manager_ = new ChannelManager(session_manager_->worker_thread()); +} + +PhoneSessionClient::~PhoneSessionClient() { + // Destroy all calls + std::map::iterator it; + while (calls_.begin() != calls_.end()) { + std::map::iterator it = calls_.begin(); + DestroyCall((*it).second); + } + + // Delete channel manager. This will wait for the channels to exit + delete channel_manager_; +} + +const std::string &PhoneSessionClient::GetSessionDescriptionName() { + return NS_PHONE; +} + +PhoneSessionDescription* PhoneSessionClient::CreateOfferSessionDescription() { + PhoneSessionDescription* session_desc = new PhoneSessionDescription(); + + MediaEngine *me = channel_manager_->media_engine(); + std::vector codecs = me->codecs(); + std::vector::iterator i; + for (i = codecs.begin(); i < codecs.end(); i++) + session_desc->AddCodec(*i); + + session_desc->Sort(); + return session_desc; +} + +PhoneSessionDescription* PhoneSessionClient::CreateAcceptSessionDescription(const SessionDescription* offer) { + const PhoneSessionDescription* offer_desc = + static_cast(offer); + PhoneSessionDescription* accept_desc = new PhoneSessionDescription(); + std::vector codecs = channel_manager_->media_engine()->codecs(); + std::vector::iterator iter; + for (unsigned int i = 0; i < offer_desc->codecs().size(); ++i) { + for (iter = codecs.begin(); iter < codecs.end(); iter++) { + if ((*iter).name == offer_desc->codecs()[i].name) + accept_desc->AddCodec(*iter); + } + } + + accept_desc->Sort(); + return accept_desc; +} + +bool PhoneSessionClient::FindMediaCodec(MediaEngine* me, + const PhoneSessionDescription* desc, + const char** codec) { + for (size_t i = 0; i < desc->codecs().size(); ++i) { + if (me->FindCodec(desc->codecs()[i].name.c_str())) + *codec = desc->codecs()[i].name.c_str(); + return true; + } + + return false; +} + +const SessionDescription *PhoneSessionClient::CreateSessionDescription(const buzz::XmlElement *element) { + PhoneSessionDescription* desc = new PhoneSessionDescription(); + + const buzz::XmlElement* payload_type = element->FirstNamed(QN_PHONE_PAYLOADTYPE); + int num_payload_types = 0; + + while (payload_type) { + if (payload_type->HasAttr(QN_PHONE_PAYLOADTYPE_ID) && + payload_type->HasAttr(QN_PHONE_PAYLOADTYPE_NAME)) { + int id = atoi(payload_type->Attr(QN_PHONE_PAYLOADTYPE_ID).c_str()); + int pref = 0; + std::string name = payload_type->Attr(QN_PHONE_PAYLOADTYPE_NAME); + desc->AddCodec(MediaEngine::Codec(id, name, 0)); + } + + payload_type = payload_type->NextNamed(QN_PHONE_PAYLOADTYPE); + num_payload_types += 1; + } + + // For backward compatability, we can assume the other client is (an old + // version of Talk) if it has no payload types at all. + if (num_payload_types == 0) { + desc->AddCodec(MediaEngine::Codec(103, "ISAC", 1)); + desc->AddCodec(MediaEngine::Codec(0, "PCMU", 0)); + } + + return desc; +} + +buzz::XmlElement *PhoneSessionClient::TranslateSessionDescription(const SessionDescription *_session_desc) { + const PhoneSessionDescription* session_desc = + static_cast(_session_desc); + buzz::XmlElement* description = new buzz::XmlElement(QN_PHONE_DESCRIPTION, true); + + for (size_t i = 0; i < session_desc->codecs().size(); ++i) { + buzz::XmlElement* payload_type = new buzz::XmlElement(QN_PHONE_PAYLOADTYPE, true); + + char buf[32]; + sprintf(buf, "%d", session_desc->codecs()[i].id); + payload_type->AddAttr(QN_PHONE_PAYLOADTYPE_ID, buf); + + payload_type->AddAttr(QN_PHONE_PAYLOADTYPE_NAME, + session_desc->codecs()[i].name.c_str()); + + description->AddElement(payload_type); + } + + return description; +} + +Call *PhoneSessionClient::CreateCall() { + Call *call = new Call(this); + calls_[call->id()] = call; + SignalCallCreate(call); + return call; +} + +void PhoneSessionClient::OnSessionCreate(Session *session, bool received_initiate) { + if (received_initiate) { + session->SignalState.connect(this, &PhoneSessionClient::OnSessionState); + + Call *call = CreateCall(); + session_map_[session->id()] = call; + call->AddSession(session); + } +} + +void PhoneSessionClient::OnSessionState(Session *session, Session::State state) { + if (state == Session::STATE_RECEIVEDINITIATE) { + // If our accept would have no codecs, then we must reject this call. + PhoneSessionDescription* accept_desc = + CreateAcceptSessionDescription(session->remote_description()); + if (accept_desc->codecs().size() == 0) { + // TODO: include an error description with the rejection. + session->Reject(); + } + delete accept_desc; + } +} + +void PhoneSessionClient::DestroyCall(Call *call) { + // Change focus away, signal destruction + + if (call == focus_call_) + SetFocus(NULL); + SignalCallDestroy(call); + + // Remove it from calls_ map and delete + + std::map::iterator it = calls_.find(call->id()); + if (it != calls_.end()) + calls_.erase(it); + + delete call; +} + +void PhoneSessionClient::OnSessionDestroy(Session *session) { + // Find the call this session is in, remove it + + std::map::iterator it = session_map_.find(session->id()); + assert(it != session_map_.end()); + if (it != session_map_.end()) { + Call *call = (*it).second; + session_map_.erase(it); + call->RemoveSession(session); + } +} + +Call *PhoneSessionClient::GetFocus() { + return focus_call_; +} + +void PhoneSessionClient::SetFocus(Call *call) { + Call *old_focus_call = focus_call_; + if (focus_call_ != call) { + if (focus_call_ != NULL) + focus_call_->EnableChannels(false); + focus_call_ = call; + if (focus_call_ != NULL) + focus_call_->EnableChannels(true); + SignalFocus(focus_call_, old_focus_call); + } +} + +void PhoneSessionClient::JoinCalls(Call *call_to_join, Call *call) { + // Move all sessions from call to call_to_join, delete call. + // If call_to_join has focus, added sessions should have enabled channels. + + if (focus_call_ == call) + SetFocus(NULL); + call_to_join->Join(call, focus_call_ == call_to_join); + DestroyCall(call); +} + +Session *PhoneSessionClient::CreateSession(Call *call) { + Session *session = session_manager_->CreateSession( + GetSessionDescriptionName(), jid().Str()); + session_map_[session->id()] = call; + return session; +} + +ChannelManager *PhoneSessionClient::channel_manager() { + return channel_manager_; +} + +const buzz::Jid &PhoneSessionClient::jid() const { + return jid_; +} + +const buzz::Jid &PhoneSessionClient::GetJid() const { + return jid_; +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.h new file mode 100644 index 00000000..150bf34b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.h @@ -0,0 +1,122 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PHONESESSIONCLIENT_H_ +#define _PHONESESSIONCLIENT_H_ + +#include "talk/session/phone/call.h" +#include "talk/session/phone/channelmanager.h" +#include "talk/base/sigslot.h" +#include "talk/base/messagequeue.h" +#include "talk/base/thread.h" +#include "talk/p2p/client/sessionclient.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/sessiondescription.h" +#include "talk/xmpp/xmppclient.h" +#include + +namespace cricket { + +class Call; +class PhoneSessionDescription; + +class PhoneSessionClient : public SessionClient { +public: + PhoneSessionClient(const buzz::Jid& jid, SessionManager *manager); + ~PhoneSessionClient(); + + const buzz::Jid &jid() const; + + Call *CreateCall(); + void DestroyCall(Call *call); + + Call *GetFocus(); + void SetFocus(Call *call); + + void JoinCalls(Call *call_to_join, Call *call); + + void SetAudioOptions(bool auto_gain_control, int wave_in_device, + int wave_out_device) { + if (channel_manager_) + channel_manager_->SetAudioOptions(auto_gain_control, wave_in_device, + wave_out_device); + } + + sigslot::signal2 SignalFocus; + sigslot::signal1 SignalCallCreate; + sigslot::signal1 SignalCallDestroy; + + PhoneSessionDescription* CreateOfferSessionDescription(); + PhoneSessionDescription* CreateAcceptSessionDescription(const SessionDescription* offer); + + // Returns our preference for the given codec. + static int GetMediaCodecPreference(const char* name); + + // Returns the name of the first codec in the description that + // is found. Return value is false if none was found. + static bool FindMediaCodec(MediaEngine* gips, + const PhoneSessionDescription* desc, + const char **codec); + +private: + void OnSessionCreate(Session *session, bool received_initiate); + void OnSessionState(Session *session, Session::State state); + void OnSessionDestroy(Session *session); + const SessionDescription *CreateSessionDescription(const buzz::XmlElement *element); + buzz::XmlElement *TranslateSessionDescription(const SessionDescription *description); + const std::string &GetSessionDescriptionName(); + const buzz::Jid &GetJid() const; + Session *CreateSession(Call *call); + ChannelManager *channel_manager(); + + buzz::Jid jid_; + Call *focus_call_; + ChannelManager *channel_manager_; + std::map calls_; + std::map session_map_; + + friend class Call; +}; + +class PhoneSessionDescription: public SessionDescription { +public: + // Returns the list of codecs sorted by our preference. + const std::vector& codecs() const { return codecs_; } + + // Adds another codec to the list. + void AddCodec(const MediaEngine::Codec& codec) { codecs_.push_back(codec); } + // Sorts the list of codecs by preference. + void Sort() { /* std::stable_sort(codecs_.begin(), codecs_.end());*/ } + +private: + std::vector codecs_; +}; + +} + +#endif // _PHONESESSIONCLIENT_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.cc new file mode 100644 index 00000000..b65c9a20 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.cc @@ -0,0 +1,331 @@ +#include +#include +#include + +// Socket stuff +#ifndef _WIN32 +#ifdef INET6 +#include +#endif +#include +#include +#include +#else +#include +#endif + +#include "talk/session/phone/mediaengine.h" +#include "talk/session/phone/portaudiomediaengine.h" + +// Engine settings +#define ENGINE_BUFFER_SIZE 2048 + +// PortAudio settings +#define FRAMES_PER_BUFFER 256 +#define SAMPLE_RATE 1 + +// Speex settings +//#define SPEEX_QUALITY 8 + +// ORTP settings +#define MAX_RTP_SIZE 1500 // From mediastreamer + + +// ----------------------------------------------------------------------------- + +static int portAudioCallback( void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, PaTimestamp outTime, void *channel_p ) +{ + PortAudioMediaChannel* channel = (PortAudioMediaChannel*) channel_p; + channel->readOutput((float*) outputBuffer, framesPerBuffer); + channel->writeInput((float*) inputBuffer, framesPerBuffer); + return 0; +} + +// ----------------------------------------------------------------------------- + +PortAudioMediaChannel::PortAudioMediaChannel() : mute_(false), play_(false), stream_(NULL), out_buffer_(NULL), in_buffer_(NULL), speex_frame_(NULL) +{ + // Initialize buffers + out_buffer_ = new float[ENGINE_BUFFER_SIZE]; + out_buffer_read_ = out_buffer_write_ = (float*) out_buffer_; + out_buffer_end_ = (float*) out_buffer_ + ENGINE_BUFFER_SIZE; + in_buffer_ = new float[ENGINE_BUFFER_SIZE]; + in_buffer_read_ = in_buffer_write_ = (float*) in_buffer_; + in_buffer_end_ = (float*) in_buffer_ + ENGINE_BUFFER_SIZE; + + // Initialize PortAudio + int err = Pa_OpenDefaultStream(&stream_, 1, 1, paFloat32, SAMPLE_RATE, FRAMES_PER_BUFFER, 0, portAudioCallback, this ); + if (err != paNoError) + fprintf(stderr, "Error creating a PortAudio stream: %s\n", Pa_GetErrorText(err)); + + // Initialize Speex + speex_bits_init(&speex_bits_); + speex_enc_state_ = speex_encoder_init(&speex_nb_mode); + speex_dec_state_ = speex_decoder_init(&speex_nb_mode); + speex_decoder_ctl(speex_dec_state_, SPEEX_GET_FRAME_SIZE, &speex_frame_size_); + speex_frame_ = new float[speex_frame_size_]; + + // int quality = SPEEX_QUALITY; + // speex_encoder_ctl(state, SPEEX_SET_QUALITY, &quality); + + // Initialize ORTP socket + struct sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + sockaddr.sin_addr.s_addr = INADDR_ANY; + sockaddr.sin_port = htons(3000); + rtp_socket_ = socket(PF_INET, SOCK_DGRAM, 0); + fcntl(rtp_socket_, F_SETFL, 0, O_NONBLOCK); + bind (rtp_socket_,(struct sockaddr*)&sockaddr, sizeof(sockaddr)); + + // Initialize ORTP Session + rtp_session_ = rtp_session_new(RTP_SESSION_SENDRECV); + rtp_session_max_buf_size_set(rtp_session_, MAX_RTP_SIZE); + rtp_session_set_profile(rtp_session_, &av_profile); + rtp_session_set_local_addr(rtp_session_, "127.0.0.1", 2000); + rtp_session_set_remote_addr(rtp_session_, "127.0.0.1", 3000); + rtp_session_set_scheduling_mode(rtp_session_, 0); + rtp_session_set_blocking_mode(rtp_session_, 0); + rtp_session_set_payload_type(rtp_session_, 110); + rtp_session_set_jitter_compensation(rtp_session_, 250); + rtp_session_enable_adaptive_jitter_compensation(rtp_session_, TRUE); + rtp_timestamp_ = 0; + //rtp_session_signal_connect(rtp_session_, "telephone-event", (RtpCallback) ortpTelephoneCallback,this); +} + +PortAudioMediaChannel::~PortAudioMediaChannel() +{ + if (stream_) { + Pa_CloseStream(stream_); + } + + // Clean up other allocated pointers + + close(rtp_socket_); +} + +void PortAudioMediaChannel::SetCodec(const char *codec) +{ + if (strcmp(codec, "speex")) + printf("Unsupported codec: %s\n", codec); +} + +void PortAudioMediaChannel::OnPacketReceived(const void *data, int len) +{ + struct sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + struct hostent *host = gethostbyname("localhost"); + memcpy(&sockaddr.sin_addr.s_addr, host->h_addr, host->h_length); + sockaddr.sin_port = htons(2000); + + char buf[2048]; + memcpy(buf, data, len); + + // Pass packet on to ORTP + if (play_) { + sendto(rtp_socket_, buf, len, 0, (struct sockaddr*)&sockaddr, sizeof(sockaddr)); + } +} + +void PortAudioMediaChannel::SetPlayout(bool playout) +{ + if (!stream_) + return; + + if (play_ && !playout) { + int err = Pa_StopStream(stream_); + if (err != paNoError) { + fprintf(stderr, "Error stopping PortAudio stream: %s\n", Pa_GetErrorText(err)); + return; + } + play_ = false; + } + else if (!play_ && playout) { + int err = Pa_StartStream(stream_); + if (err != paNoError) { + fprintf(stderr, "Error starting PortAudio stream: %s\n", Pa_GetErrorText(err)); + return; + } + play_ = true; + } +} + +void PortAudioMediaChannel::SetSend(bool send) +{ + mute_ = !send; +} + + +float PortAudioMediaChannel::GetCurrentQuality() +{ + return 0; +} + +int PortAudioMediaChannel::GetOutputLevel() +{ + return 0; +} + +void PortAudioMediaChannel::readOutput(float* buf, int len) +{ + //readBuffer(out_buffer_, &out_buffer_read_, out_buffer_write_, out_buffer_end_, buf, len); + + // Receive a packet (if there is one) + mblk_t *mp; + mp = rtp_session_recvm_with_ts(rtp_session_,rtp_timestamp_); + while (mp != NULL) { + gint in_len = mp->b_cont->b_wptr-mp->b_cont->b_rptr; + + // Decode speex stream + speex_bits_read_from(&speex_bits_,mp->b_cont->b_rptr, in_len); + speex_decode(speex_dec_state_, &speex_bits_, speex_frame_); + writeBuffer(out_buffer_, out_buffer_read_, &out_buffer_write_, out_buffer_end_, speex_frame_, speex_frame_size_); + rtp_timestamp_++; + mp = rtp_session_recvm_with_ts(rtp_session_,rtp_timestamp_); + } + + // Read output + readBuffer(out_buffer_, &out_buffer_read_, out_buffer_write_, out_buffer_end_, buf, len); +} + +void PortAudioMediaChannel::writeInput(float* buf, int len) +{ + //writeBuffer(in_buffer_, in_buffer_read_, &in_buffer_write_, in_buffer_end_, buf, len); +} + + +void PortAudioMediaChannel::readBuffer(float* buffer, float** buffer_read_p, float*buffer_write, float* buffer_end, float* target_buffer, int target_len) +{ + float *end, *tmp, *buffer_read = *buffer_read_p; + int remaining; + + // First phase + tmp = buffer_read + target_len; + if (buffer_write < buffer_read && tmp > buffer_end) { + end = buffer_end; + remaining = tmp - buffer_end; + } + else { + end = (tmp > buffer_write ? buffer_write : tmp); + remaining = 0; + } + + while (buffer_read < end) { + *target_buffer++ = *buffer_read++; + } + + // Second phase + if (remaining > 0) { + buffer_read = buffer; + tmp = buffer_read + remaining; + end = (tmp > buffer_write ? buffer_write : tmp); + while (buffer_read < end) { + *target_buffer++ = *buffer_read++; + } + } + + // Finish up + *buffer_read_p = buffer_read; +} + +void PortAudioMediaChannel::writeBuffer(float* buffer, float* buffer_read, float**buffer_write_p, float* buffer_end, float* source_buffer, int source_len) +{ + float *end, *tmp, *buffer_write = *buffer_write_p; + int remaining; + + // First phase + tmp = buffer_write + source_len; + if (buffer_write > buffer_read) { + if (tmp > buffer_end) { + end = buffer_end; + remaining = tmp - buffer_end; + } + else { + end = tmp; + remaining = 0; + } + } + else { + if (tmp > buffer_read) { + printf("Warning: Dropping frame(s)\n"); + end = buffer_read; + remaining = 0; + } + else { + end = tmp; + remaining = 0; + } + } + + while (buffer_write < end) { + *buffer_write++ = *source_buffer++; + } + + // Second phase + if (remaining > 0) { + buffer_write = buffer; + tmp = buffer_write + remaining; + if (tmp > buffer_read) { + printf("Warning: Dropping frame(s)\n"); + end = buffer_read; + } + else { + end = tmp; + } + while (buffer_write < end) { + *buffer_write++ = *source_buffer++; + } + } + + // Finish up + *buffer_write_p = buffer_write; +} + +// ----------------------------------------------------------------------------- + +PortAudioMediaEngine::PortAudioMediaEngine() +{ +} + +PortAudioMediaEngine::~PortAudioMediaEngine() +{ + Pa_Terminate(); +} + +bool PortAudioMediaEngine::Init() +{ + ortp_init(); + + int err = Pa_Initialize(); + if (err != paNoError) { + fprintf(stderr,"Error initializing PortAudio: %s\n",Pa_GetErrorText(err)); + return false; + } + + // Speex + rtp_profile_set_payload(&av_profile, 110, &speex_wb); + codecs_.push_back(Codec(110, "speex", 8)); + + return true; +} + +void PortAudioMediaEngine::Terminate() +{ +} + + +cricket::MediaChannel* PortAudioMediaEngine::CreateChannel() +{ + return new PortAudioMediaChannel(); +} + +int PortAudioMediaEngine::SetAudioOptions(int options) +{ +} + +int PortAudioMediaEngine::SetSoundDevices(int wave_in_device, int wave_out_device) +{ +} + +int PortAudioMediaEngine::GetInputLevel() +{ +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.h new file mode 100644 index 00000000..95c39a1a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.h @@ -0,0 +1,69 @@ +#ifndef PORTAUDIOMEDIAENGINE_H +#define PORTAUDIOMEDIAENGINE_H + +#include +#include +#include + +#include "talk/session/phone/mediaengine.h" + +class PortAudioMediaChannel : public cricket::MediaChannel +{ +public: + PortAudioMediaChannel(); + virtual ~PortAudioMediaChannel(); + virtual void SetCodec(const char *codec); + virtual void OnPacketReceived(const void *data, int len); + + virtual void SetPlayout(bool playout); + virtual void SetSend(bool send); + + virtual float GetCurrentQuality(); + virtual int GetOutputLevel(); + + void readOutput(float*, int); + void writeInput(float*, int); + +protected: + void readBuffer(float*, float**, float*, float*, float*, int); + void writeBuffer(float*, float*, float**, float*, float*, int); + +private: + bool mute_; + bool play_; + PortAudioStream* stream_; + + // Buffers + float *out_buffer_, *out_buffer_read_, *out_buffer_write_, *out_buffer_end_; + float *in_buffer_, *in_buffer_read_, *in_buffer_write_, *in_buffer_end_; + + // Speex + SpeexBits speex_bits_; + void *speex_enc_state_, *speex_dec_state_; + float *speex_frame_; + int speex_frame_size_; + + // ORTP + int rtp_socket_; + RtpSession* rtp_session_; + int rtp_timestamp_; +}; + + +class PortAudioMediaEngine : public cricket::MediaEngine +{ +public: + PortAudioMediaEngine(); + ~PortAudioMediaEngine(); + virtual bool Init(); + virtual void Terminate(); + + virtual cricket::MediaChannel *CreateChannel(); + + virtual int SetAudioOptions(int options); + virtual int SetSoundDevices(int wave_in_device, int wave_out_device); + + virtual int GetInputLevel(); +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.cc new file mode 100644 index 00000000..58e1db60 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.cc @@ -0,0 +1,331 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/session/phone/voicechannel.h" +#include "talk/session/phone/channelmanager.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/base/logging.h" +#include +#undef SetPort + +namespace { + +// Delay before quality estimate is meaningful. +uint32 kQualityDelay = 5000; // in ms + +} + +namespace cricket { + +VoiceChannel::VoiceChannel(ChannelManager *manager, Session *session, MediaChannel *channel) { + channel_manager_ = manager; + assert(channel_manager_->worker_thread() == Thread::Current()); + channel_ = channel; + session_ = session; + socket_monitor_ = NULL; + audio_monitor_ = NULL; + socket_ = session_->CreateSocket("rtp"); + socket_->SignalState.connect(this, &VoiceChannel::OnSocketState); + socket_->SignalReadPacket.connect(this, &VoiceChannel::OnSocketRead); + channel->SetInterface(this); + enabled_ = false; + paused_ = false; + socket_writable_ = false; + muted_ = false; + LOG(INFO) << "Created voice channel"; + start_time_ = 0xFFFFFFFF - kQualityDelay; + + session->SignalState.connect(this, &VoiceChannel::OnSessionState); + OnSessionState(session, session->state()); +} + +VoiceChannel::~VoiceChannel() { + assert(channel_manager_->worker_thread() == Thread::Current()); + enabled_ = false; + ChangeState(); + delete socket_monitor_; + delete audio_monitor_; + Thread::Current()->Clear(this); + if (socket_ != NULL) + session_->DestroySocket(socket_); + LOG(INFO) << "Destroyed voice channel"; +} + +void VoiceChannel::OnMessage(Message *pmsg) { + switch (pmsg->message_id) { + case MSG_ENABLE: + EnableMedia_w(); + break; + + case MSG_DISABLE: + DisableMedia_w(); + break; + + case MSG_MUTE: + MuteMedia_w(); + break; + + case MSG_UNMUTE: + UnmuteMedia_w(); + break; + + case MSG_SETSENDCODEC: + SetSendCodec_w(); + break; + } +} + +void VoiceChannel::Enable(bool enable) { + // Can be called from thread other than worker thread + channel_manager_->worker_thread()->Post(this, enable ? MSG_ENABLE : MSG_DISABLE); +} + +void VoiceChannel::Mute(bool mute) { + // Can be called from thread other than worker thread + channel_manager_->worker_thread()->Post(this, mute ? MSG_MUTE : MSG_UNMUTE); +} + +MediaChannel * VoiceChannel::channel() { + return channel_; +} + +void VoiceChannel::OnSessionState(Session* session, Session::State state) { + if ((state == Session::STATE_RECEIVEDACCEPT) || + (state == Session::STATE_RECEIVEDINITIATE)) { + channel_manager_->worker_thread()->Post(this, MSG_SETSENDCODEC); + } +} + +void VoiceChannel::SetSendCodec_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + + const PhoneSessionDescription* desc = + static_cast(session()->remote_description()); + + const char *codec = NULL; + + if (desc->codecs().size() > 0) + PhoneSessionClient::FindMediaCodec(channel_manager_->media_engine(), desc, &codec); + + // The other client should have returned one of the codecs that we offered. + // If they could not, they should have rejected the session. So, if we get + // into this state, we're dealing with a bad client, so we may as well just + // pick the mostt common format there is: payload type zero. + if (codec == NULL) + codec = "PCMU"; + + channel_->SetCodec(codec); +} + +void VoiceChannel::OnSocketState(P2PSocket *socket, P2PSocket::State state) { + switch (state) { + case P2PSocket::STATE_WRITABLE: + SocketWritable_w(); + break; + + default: + SocketNotWritable_w(); + break; + } +} + +void VoiceChannel::OnSocketRead(P2PSocket *socket, const char *data, size_t len) { + assert(channel_manager_->worker_thread() == Thread::Current()); + // OnSocketRead gets called from P2PSocket; now pass data to MediaEngine + channel_->OnPacketReceived(data, (int)len); +} + +void VoiceChannel::SendPacket(const void *data, size_t len) { + // SendPacket gets called from MediaEngine; send to socket + // MediaEngine will call us on a random thread. The Send operation on the socket is + // special in that it can handle this. + socket_->Send(static_cast(data), len); +} + +void VoiceChannel::ChangeState() { + if (paused_ || !enabled_ || !socket_writable_) { + channel_->SetPlayout(false); + channel_->SetSend(false); + } else { + if (muted_) { + channel_->SetSend(false); + channel_->SetPlayout(true); + } else { + channel_->SetSend(true); + channel_->SetPlayout(true); + } + } +} + +void VoiceChannel::PauseMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + assert(!paused_); + + LOG(INFO) << "Voice channel paused"; + paused_ = true; + ChangeState(); +} + +void VoiceChannel::UnpauseMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + assert(paused_); + + LOG(INFO) << "Voice channel unpaused"; + paused_ = false; + ChangeState(); +} + +void VoiceChannel::EnableMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (enabled_) + return; + + LOG(INFO) << "Voice channel enabled"; + enabled_ = true; + start_time_ = Time(); + ChangeState(); +} + +void VoiceChannel::DisableMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (!enabled_) + return; + + LOG(INFO) << "Voice channel disabled"; + enabled_ = false; + ChangeState(); +} + +void VoiceChannel::MuteMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (muted_) + return; + + LOG(INFO) << "Voice channel muted"; + muted_ = true; + ChangeState(); +} + +void VoiceChannel::UnmuteMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (!muted_) + return; + + LOG(INFO) << "Voice channel unmuted"; + muted_ = false; + ChangeState(); +} + +void VoiceChannel::SocketWritable_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (socket_writable_) + return; + + LOG(INFO) << "Voice channel socket writable"; + socket_writable_ = true; + ChangeState(); +} + +void VoiceChannel::SocketNotWritable_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (!socket_writable_) + return; + + LOG(INFO) << "Voice channel socket not writable"; + socket_writable_ = false; + ChangeState(); +} + +void VoiceChannel::StartConnectionMonitor(int cms) { + delete socket_monitor_; + socket_monitor_ = new SocketMonitor(socket_, Thread::Current()); + socket_monitor_ + ->SignalUpdate.connect(this, &VoiceChannel::OnConnectionMonitorUpdate); + socket_monitor_->Start(cms); +} + +void VoiceChannel::StopConnectionMonitor() { + if (socket_monitor_ != NULL) { + socket_monitor_->Stop(); + socket_monitor_->SignalUpdate.disconnect(this); + delete socket_monitor_; + socket_monitor_ = NULL; + } +} + +void VoiceChannel::OnConnectionMonitorUpdate(SocketMonitor *monitor, + const std::vector &infos) { + SignalConnectionMonitor(this, infos); +} + +void VoiceChannel::StartAudioMonitor(int cms) { + delete audio_monitor_; + audio_monitor_ = new AudioMonitor(this, Thread::Current()); + audio_monitor_ + ->SignalUpdate.connect(this, &VoiceChannel::OnAudioMonitorUpdate); + audio_monitor_->Start(cms); +} + +void VoiceChannel::StopAudioMonitor() { + if (audio_monitor_ != NULL) { + audio_monitor_ ->Stop(); + audio_monitor_ ->SignalUpdate.disconnect(this); + delete audio_monitor_ ; + audio_monitor_ = NULL; + } +} + +void VoiceChannel::OnAudioMonitorUpdate(AudioMonitor *monitor, + const AudioInfo& info) { + SignalAudioMonitor(this, info); +} + +Session *VoiceChannel::session() { + return session_; +} + +bool VoiceChannel::HasQuality() { + return Time() >= start_time_ + kQualityDelay; +} + +float VoiceChannel::GetCurrentQuality() { + return channel_->GetCurrentQuality(); +} + +int VoiceChannel::GetInputLevel_w() { + return channel_manager_->media_engine()->GetInputLevel(); +} + +int VoiceChannel::GetOutputLevel_w() { + return channel_->GetOutputLevel(); +} + +Thread* VoiceChannel::worker_thread() { + return channel_manager_->worker_thread(); +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.h new file mode 100644 index 00000000..4cfa0b11 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.h @@ -0,0 +1,129 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _VOICECHANNEL_H_ +#define _VOICECHANNEL_H_ + +#include "talk/base/asyncudpsocket.h" +#include "talk/base/network.h" +#include "talk/base/sigslot.h" +#include "talk/p2p/client/socketmonitor.h" +#include "talk/p2p/base/p2psocket.h" +#include "talk/p2p/base/session.h" +#include "talk/session/phone/audiomonitor.h" +#include "talk/session/phone/mediaengine.h" + +namespace cricket { + +const uint32 MSG_ENABLE = 1; +const uint32 MSG_DISABLE = 2; +const uint32 MSG_MUTE = 3; +const uint32 MSG_UNMUTE = 4; +const uint32 MSG_SETSENDCODEC = 5; + +class ChannelManager; + +class VoiceChannel + : public MessageHandler, public sigslot::has_slots<>, + public NetworkSession, public MediaChannel::NetworkInterface { + public: + VoiceChannel(ChannelManager *manager, Session *session, MediaChannel *channel); + ~VoiceChannel(); + + void Enable(bool enable); + void Mute(bool mute); + MediaChannel *channel(); + Session *session(); + + // Monitoring + + void StartConnectionMonitor(int cms); + void StopConnectionMonitor(); + sigslot::signal2 &> SignalConnectionMonitor; + + void StartAudioMonitor(int cms); + void StopAudioMonitor(); + sigslot::signal2 SignalAudioMonitor; + Thread* worker_thread(); + + // Pausing so that the ChannelManager can change the audio devices. These + // should only be called from the worker thread + void PauseMedia_w(); + void UnpauseMedia_w(); + + int GetInputLevel_w(); + int GetOutputLevel_w(); + + // Gives a quality estimate to the network quality manager. + virtual bool HasQuality(); + virtual float GetCurrentQuality(); + + // MediaEngine calls this + virtual void SendPacket(const void *data, size_t len); + +private: + void ChangeState(); + void EnableMedia_w(); + void DisableMedia_w(); + void MuteMedia_w(); + void UnmuteMedia_w(); + void SocketWritable_w(); + void SocketNotWritable_w(); + + void OnConnectionMonitorUpdate(SocketMonitor *monitor, const std::vector &infos); + void OnAudioMonitorUpdate(AudioMonitor *monitor, const AudioInfo& info); + + // From MessageHandler + + void OnMessage(Message *pmsg); + + // Setting the send codec based on the remote description. + void OnSessionState(Session* session, Session::State state); + void SetSendCodec_w(); + + // From P2PSocket + + void OnSocketState(P2PSocket *socket, P2PSocket::State state); + void OnSocketRead(P2PSocket *socket, const char *data, size_t len); + + + bool enabled_; + bool paused_; + bool socket_writable_; + bool muted_; + MediaChannel *channel_; + Session *session_; + P2PSocket *socket_; + ChannelManager *channel_manager_; + SocketMonitor *socket_monitor_; + AudioMonitor *audio_monitor_; + uint32 start_time_; +}; + +} + +#endif // _VOICECHANNEL_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/receiver.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/receiver.h new file mode 100644 index 00000000..a5326893 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/receiver.h @@ -0,0 +1,72 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _RECEIVER_H_ +#define _RECEIVER_H_ + +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/xmpptask.h" +#include "talk/p2p/client/sessionclient.h" + +namespace cricket { + +class Receiver : public buzz::XmppTask { +public: + Receiver(Task *parent, SessionClient *session_client) + : buzz::XmppTask(parent, buzz::XmppEngine::HL_TYPE) { + session_client_ = session_client; + } + + virtual int ProcessStart() { + const buzz::XmlElement *stanza = NextStanza(); + if (stanza == NULL) + return STATE_BLOCKED; + session_client_->OnIncomingStanza(stanza); + + // Respond right away to the sender to let them know that we received + // this IQ + buzz::XmlElement * result = MakeIqResult(stanza); + SendStanza(result); + + return STATE_START; + } + +protected: + virtual bool HandleStanza(const buzz::XmlElement *stanza) { + if (!session_client_->IsClientStanza(stanza)) + return false; + QueueStanza(stanza); + return true; + } + +private: + SessionClient *session_client_; +}; + +} + +#endif // _RECEIVER_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/sessionsendtask.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/sessionsendtask.h new file mode 100644 index 00000000..9dc5384c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/sessionsendtask.h @@ -0,0 +1,111 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _CRICKET_PHONE_SESSIONSENDTASK_H_ +#define _CRICKET_PHONE_SESSIONSENDTASK_H_ + +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/xmpptask.h" +#include "talk/p2p/client/sessionclient.h" + +namespace cricket { + +// The job of this task is to send an IQ stanza out (after stamping it with +// an ID attribute) and then wait for a response. If not response happens +// within 5 seconds, it will signal failure on a SessionClient. If an error +// happens it will also signal failure. If, however, the send succeeds this +// task will quietly go away. + +// It is safe for this to hold on to the session client. In the case where +// the xmpp client goes away, this task will automatically be aborted. The +// session_client is guaranteed to outlive the xmpp session. +class SessionSendTask : public buzz::XmppTask { +public: + SessionSendTask(Task *parent, SessionClient *session_client) + : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE), + session_client_(session_client), + timed_out_(false) { + } + + void Send(const buzz::XmlElement* stanza) { + assert(stanza_.get() == NULL); + stanza_.reset(new buzz::XmlElement(*stanza)); + stanza_->SetAttr(buzz::QN_ID, task_id()); + } + +protected: + // This gets called by the task runner every 500 msec + virtual void Poll() { + if (ElapsedTime() > (15 * 1000 * 10000)) { // 15 secs + timed_out_ = true; + Wake(); + } + } + + virtual int ProcessStart() { + SendStanza(stanza_.get()); + return STATE_RESPONSE; + } + + virtual int ProcessResponse() { + if (timed_out_) { + session_client_->OnFailedSend(stanza_.get(), NULL); + return STATE_DONE; + } + + const buzz::XmlElement* next = NextStanza(); + if (next == NULL) + return STATE_BLOCKED; + + if (next->Attr(buzz::QN_TYPE) == "result") { + return STATE_DONE; + } else { + session_client_->OnFailedSend(stanza_.get(), next); + return STATE_DONE; + } + } + + virtual bool HandleStanza(const buzz::XmlElement *stanza) { + if (!MatchResponseIq(stanza, buzz::Jid(stanza_->Attr(buzz::QN_TO)), task_id())) + return false; + if (stanza->Attr(buzz::QN_TYPE) == "result" || + stanza->Attr(buzz::QN_TYPE) == "error") { + QueueStanza(stanza); + return true; + } + return false; + } + +private: + SessionClient *session_client_; + buzz::scoped_ptr stanza_; + bool timed_out_; +}; + +} + +#endif // _CRICKET_PHONE_SESSIONSENDTASK_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/Makefile.am new file mode 100644 index 00000000..3186245a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=mediastreamer diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.am new file mode 100644 index 00000000..268a52fe --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.am @@ -0,0 +1,92 @@ +EXTRA_DIST=Makefile.ms +noinst_LTLIBRARIES = libmediastreamer.la +libmediastreamer_la_SOURCES=msfilter.c msfilter.h msutils.h waveheader.h\ + mscodec.c mscodec.h \ + mssoundread.c mssoundread.h \ + mssoundwrite.c mssoundwrite.h \ + msbuffer.c msbuffer.h \ + msqueue.c msqueue.h \ + msfifo.c msfifo.h \ + ms.c ms.h\ + mssync.c mssync.h \ + msnosync.c msnosync.h \ + msread.c msread.h \ + mswrite.c mswrite.h \ + mscopy.c mscopy.h \ + msosswrite.c msosswrite.h \ + msossread.c msossread.h \ + msringplayer.c msringplayer.h \ + msrtprecv.c msrtprecv.h \ + msrtpsend.c msrtpsend.h \ + msAlawenc.c msAlawenc.h g711common.h \ + msAlawdec.c msAlawdec.h g711common.h \ + msMUlawenc.c msMUlawenc.h g711common.h \ + msMUlawdec.c msMUlawdec.h g711common.h \ + mstimer.c mstimer.h \ + msqdispatcher.c msqdispatcher.h \ + msfdispatcher.c msfdispatcher.h \ + sndcard.c sndcard.h \ + osscard.c osscard.h\ + hpuxsndcard.c \ + alsacard.c alsacard.h \ + jackcard.c jackcard.h \ + audiostream.c mediastream.h \ + msspeexenc.c msspeexenc.h msspeexdec.c msspeexdec.h \ + msilbcdec.c msilbcdec.h msilbcenc.c msilbcenc.h + +noinst_HEADERS = affine.h \ + msAlawenc.h \ + msfdispatcher.h \ + msilbcdec.h \ + msnosync.h \ + msringplayer.h \ + msspeexdec.h \ + msutils.h \ + waveheader.h \ + alsacard.h \ + msavdecoder.h \ + msfifo.h \ + msilbcenc.h \ + msossread.h \ + msrtprecv.h \ + msspeexenc.h \ + msv4l.h \ + g711common.h \ + msavencoder.h \ + msfilter.h \ + msLPC10decoder.h \ + msosswrite.h \ + msrtpsend.h \ + mssync.h \ + msvideosource.h \ + jackcard.h \ + msbuffer.h \ + msGSMdecoder.h \ + msLPC10encoder.h \ + msqdispatcher.h \ + mssdlout.h \ + mstimer.h \ + mswrite.h \ + mediastream.h \ + mscodec.h \ + msGSMencoder.h \ + msMUlawdec.h \ + msqueue.h \ + mssoundread.h \ + mstruespeechdecoder.h \ + osscard.h \ + msAlawdec.h \ + mscopy.h \ + ms.h \ + msMUlawenc.h \ + msread.h \ + mssoundwrite.h \ + mstruespeechencoder.h \ + sndcard.h + + +libmediastreamer_la_LIBADD= $(GLIB_LIBS) $(ORTP_LIBS) $(SPEEX_LIBS) + +AM_CFLAGS=$(GLIB_CFLAGS) -DG_LOG_DOMAIN=\"MediaStreamer\" $(ORTP_CFLAGS) $(IPV6_CFLAGS) $(ILBC_CFLAGS) $(SPEEX_CFLAGS) + +INCLUDES= -I$(srcdir)/../../.. $(ORTP_CFLAGS) diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.ms b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.ms new file mode 100644 index 00000000..8b7427c3 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.ms @@ -0,0 +1,34 @@ + +OBJEXT=o +AR = ar +RANLIB = ranlib +DEFS= -DG_LOG_DOMAIN=\"MediaStreamer\" +INCLUDES=-I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include/ \ + -I../gsmlib/ -I../lpc10-1.5 -I../oRTP +COMPILE= gcc $(DEFS) $(INCLUDES) +LIBTOOL=libtool +LDFLAGS=-L/usr/local/lib/ -lglib-1.3 -lgthread-1.3 -lpthread +LINK = $(LIBTOOL) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ + +libmediastreamer_a_OBJECTS = msfilter.$(OBJEXT) msbuffer.$(OBJEXT) \ +msqueue.$(OBJEXT) msfifo.$(OBJEXT) ms.$(OBJEXT) mssync.$(OBJEXT) \ +msnosync.$(OBJEXT) msread.$(OBJEXT) mswrite.$(OBJEXT) mscopy.$(OBJEXT) \ +msv4lsource.$(OBJEXT) msoss.$(OBJEXT) msosswrite.$(OBJEXT) \ +msossread.$(OBJEXT) msringplayer.$(OBJEXT) msGSMencoder.$(OBJEXT) \ +msGSMdecoder.$(OBJEXT) msLPC10encoder.$(OBJEXT) \ +msLPC10decoder.$(OBJEXT) + +all: libmediastreamer.a mstest + + +.c.o: + $(COMPILE) -c $< + +libmediastreamer.a: $(libmediastreamer_a_OBJECTS) + -rm -f libmediastreamer.a + $(AR) cru libmediastreamer.a $(libmediastreamer_a_OBJECTS) + $(RANLIB) libmediastreamer.a + + +mstest: test.o libmediastreamer.a + gcc -o mstest test.o libmediastreamer.a $(LDFLAGS) -Wl,-rpath /usr/local/lib diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/README b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/README new file mode 100644 index 00000000..1309f534 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/README @@ -0,0 +1,3 @@ +Mediastreamer is the library that handle all media operations: rtp streaming +from file, from soundcard, with codec transcoding, and vice-versa;-). +And also video streaming in the future. diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/affine.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/affine.h new file mode 100644 index 00000000..620fdc9d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/affine.h @@ -0,0 +1,43 @@ +/* + * affine.h -- Affine Transforms for 2d objects + * Copyright (C) 2002 Charles Yates + * Portions Copyright (C) 2003 Dan Dennedy + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _AFFINE_H +#define _AFFINE_H + +#include + +/** Affine transforms for 2d image manipulation. Current provides shearing and + rotating support. +*/ + +typedef struct { + double matrix[2][2]; +} affine_transform_t; + +void affine_transform_init( affine_transform_t *this ); +void affine_transform_rotate( affine_transform_t *this, double angle ); +void affine_transform_shear( affine_transform_t *this, double shear ); +void affine_transform_scale( affine_transform_t *this, double sx, double sy ); +double affine_transform_mapx( affine_transform_t *this, int x, int y ); +double affine_transform_mapy( affine_transform_t *this, int x, int y ); +void affine_scale( const unsigned char *src, unsigned char *dest, int src_width, int src_height, int dest_width, int dest_height, int bpp ); + +#endif + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.c new file mode 100644 index 00000000..c240aa72 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.c @@ -0,0 +1,640 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "alsacard.h" + +#ifdef HAVE_ALSA_ASOUNDLIB_H + +static gchar *over_pcmdev=NULL; + +#include "msossread.h" +#include "msosswrite.h" + +#include + +int __alsa_card_write(AlsaCard *obj,char *buf,int size); + +int alsa_set_params(AlsaCard *obj, int rw, int bits, int stereo, int rate) +{ + snd_pcm_hw_params_t *hwparams=NULL; + snd_pcm_sw_params_t *swparams=NULL; + snd_pcm_t *pcm_handle; + gint dir,exact_value; + gint channels; + gint fsize=0; + gint periods=8; + gint periodsize=256; + gint err; + int format; + + if (rw) { + pcm_handle=obj->write_handle; + } + else pcm_handle=obj->read_handle; + + /* Allocate the snd_pcm_hw_params_t structure on the stack. */ + snd_pcm_hw_params_alloca(&hwparams); + + /* Init hwparams with full configuration space */ + if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) { + g_warning("alsa_set_params: Cannot configure this PCM device.\n"); + return(-1); + } + + if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { + g_warning("alsa_set_params: Error setting access.\n"); + return(-1); + } + /* Set sample format */ +#ifdef WORDS_BIGENDIAN + format=SND_PCM_FORMAT_S16_BE; +#else + format=SND_PCM_FORMAT_S16_LE; +#endif + if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, format) < 0) { + g_warning("alsa_set_params: Error setting format.\n"); + return(-1); + } + /* Set number of channels */ + if (stereo) channels=2; + else channels=1; + if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, channels) < 0) { + g_warning("alsa_set_params: Error setting channels.\n"); + return(-1); + } + /* Set sample rate. If the exact rate is not supported */ + /* by the hardware, use nearest possible rate. */ + exact_value=rate; + dir=0; + if ((err=snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_value, &dir))<0){ + g_warning("alsa_set_params: Error setting rate to %i:%s",rate,snd_strerror(err)); + return -1; + } + if (dir != 0) { + g_warning("alsa_set_params: The rate %d Hz is not supported by your hardware.\n " + "==> Using %d Hz instead.\n", rate, exact_value); + } + /* choose greater period size when rate is high */ + periodsize=periodsize*(rate/8000); + + /* Set buffer size (in frames). The resulting latency is given by */ + /* latency = periodsize * periods / (rate * bytes_per_frame) */ + /* + fsize=periodsize * periods; + exact_value=fsize; + if ((err=snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams,&exact_value)) < 0) { + g_warning("alsa_set_params: Error setting buffer size:%s",snd_strerror(err)); + return(-1); + } + if (fsize!= exact_value) { + g_warning("alsa_set_params: The buffer size %d is not supported by your hardware.\n " + "==> Using %d instead.\n", fsize, exact_value); + } + */ + /* set period size */ + exact_value=periodsize; + dir=0; + if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &exact_value, &dir) < 0) { + g_warning("alsa_set_params: Error setting period size.\n"); + return(-1); + } + if (dir != 0) { + g_warning("alsa_set_params: The period size %d is not supported by your hardware.\n " + "==> Using %d instead.\n", periodsize, exact_value); + } + periodsize=exact_value; + /* Set number of periods. Periods used to be called fragments. */ + exact_value=periods; + dir=0; + if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &exact_value, &dir) < 0) { + g_warning("alsa_set_params: Error setting periods.\n"); + return(-1); + } + if (dir != 0) { + g_warning("alsa_set_params: The number of periods %d is not supported by your hardware.\n " + "==> Using %d instead.\n", periods, exact_value); + } + /* Apply HW parameter settings to */ + /* PCM device and prepare device */ + if ((err=snd_pcm_hw_params(pcm_handle, hwparams)) < 0) { + g_warning("alsa_set_params: Error setting HW params:%s",snd_strerror(err)); + return(-1); + } + /*prepare sw params */ + if (rw){ + snd_pcm_sw_params_alloca(&swparams); + snd_pcm_sw_params_current(pcm_handle, swparams); + if ((err=snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams,periodsize*2 ))<0){ + g_warning("alsa_set_params: Error setting start threshold:%s",snd_strerror(err)); + return -1; + } + if ((err=snd_pcm_sw_params(pcm_handle, swparams))<0){ + g_warning("alsa_set_params: Error setting SW params:%s",snd_strerror(err)); + return(-1); + } + } + obj->frame_size=channels*(bits/8); + SND_CARD(obj)->bsize=periodsize*obj->frame_size; + /* //SND_CARD(obj)->bsize=4096; */ + obj->frames=periodsize; + g_message("alsa_set_params: blocksize=%i.",SND_CARD(obj)->bsize); + return SND_CARD(obj)->bsize; +} + +int alsa_card_open_r(AlsaCard *obj,int bits,int stereo,int rate) +{ + int bsize; + int err; + snd_pcm_t *pcm_handle; + gchar *pcmdev; + if (over_pcmdev!=NULL) pcmdev=over_pcmdev; + else pcmdev=obj->pcmdev; + + if (snd_pcm_open(&pcm_handle, pcmdev,SND_PCM_STREAM_CAPTURE,SND_PCM_NONBLOCK) < 0) { + g_warning("alsa_card_open_r: Error opening PCM device %s\n",obj->pcmdev ); + return -1; + } + g_return_val_if_fail(pcm_handle!=NULL,-1); + obj->read_handle=pcm_handle; + if ((bsize=alsa_set_params(obj,0,bits,stereo,rate))<0){ + snd_pcm_close(pcm_handle); + obj->read_handle=NULL; + return -1; + } + obj->readbuf=g_malloc0(bsize); + + err=snd_pcm_start(obj->read_handle); + if (err<0){ + g_warning("Cannot start read pcm: %s", snd_strerror(err)); + } + obj->readpos=0; + SND_CARD(obj)->bsize=bsize; + SND_CARD(obj)->flags|=SND_CARD_FLAGS_OPENED; + return 0; +} + +int alsa_card_open_w(AlsaCard *obj,int bits,int stereo,int rate) +{ + int err,bsize; + snd_pcm_t *pcm_handle; + gchar *pcmdev; + if (over_pcmdev!=NULL) pcmdev=over_pcmdev; + else pcmdev=obj->pcmdev; + + if (snd_pcm_open(&pcm_handle, pcmdev,SND_PCM_STREAM_PLAYBACK,SND_PCM_NONBLOCK) < 0) { + g_warning("alsa_card_open_w: Error opening PCM device %s\n", obj->pcmdev); + return -1; + } + obj->write_handle=pcm_handle; + if ((bsize=alsa_set_params(obj,1,bits,stereo,rate))<0){ + snd_pcm_close(pcm_handle); + obj->write_handle=NULL; + return -1; + } + obj->writebuf=g_malloc0(bsize); + + obj->writepos=0; + SND_CARD(obj)->bsize=bsize; + SND_CARD(obj)->flags|=SND_CARD_FLAGS_OPENED; + return 0; +} + + +void alsa_card_set_blocking_mode(AlsaCard *obj, gboolean yesno){ + if (obj->read_handle!=NULL) snd_pcm_nonblock(obj->read_handle,!yesno); + if (obj->write_handle!=NULL) snd_pcm_nonblock(obj->write_handle,!yesno); +} + +void alsa_card_close_r(AlsaCard *obj) +{ + if (obj->read_handle!=NULL){ + snd_pcm_close(obj->read_handle); + obj->read_handle=NULL; + g_free(obj->readbuf); + obj->readbuf=NULL; + } +} + +void alsa_card_close_w(AlsaCard *obj) +{ + if (obj->write_handle!=NULL){ + snd_pcm_close(obj->write_handle); + obj->write_handle=NULL; + g_free(obj->writebuf); + obj->writebuf=NULL; + } +} + +int alsa_card_probe(AlsaCard *obj,int bits,int stereo,int rate) +{ + int ret; + ret=alsa_card_open_w(obj,bits,stereo,rate); + if (ret<0) return -1; + ret=SND_CARD(obj)->bsize; + alsa_card_close_w(obj); + return ret; +} + + +void alsa_card_destroy(AlsaCard *obj) +{ + snd_card_uninit(SND_CARD(obj)); + g_free(obj->pcmdev); + if (obj->readbuf!=0) g_free(obj->readbuf); + if (obj->writebuf!=0) g_free(obj->writebuf); +} + +gboolean alsa_card_can_read(AlsaCard *obj) +{ + int frames; + g_return_val_if_fail(obj->read_handle!=NULL,0); + if (obj->readpos!=0) return TRUE; + if ( frames=snd_pcm_avail_update(obj->read_handle)>=obj->frames) return 1; + /* //g_message("frames=%i",frames); */ + return 0; +} + + + +int __alsa_card_read(AlsaCard *obj,char *buf,int bsize) +{ + int err; + sigset_t set; + sigemptyset(&set); + sigaddset(&set,SIGALRM); + sigprocmask(SIG_BLOCK,&set,NULL); + err=snd_pcm_readi(obj->read_handle,buf,bsize/obj->frame_size); + if (err<0) { + if (err!=-EPIPE){ + g_warning("alsa_card_read: snd_pcm_readi() failed:%s.",snd_strerror(err)); + } + snd_pcm_prepare(obj->read_handle); + err=snd_pcm_readi(obj->read_handle,buf,bsize/obj->frame_size); + if (err<0) g_warning("alsa_card_read: snd_pcm_readi() failed:%s.",snd_strerror(err)); + } + sigprocmask(SIG_UNBLOCK,&set,NULL); + return err*obj->frame_size; +} + +int alsa_card_read(AlsaCard *obj,char *buf,int size) +{ + int err; + gint bsize=SND_CARD(obj)->bsize; + g_return_val_if_fail(obj->read_handle!=NULL,-1); + if (sizereadpos,size); + + if (obj->readpos==0){ + err=__alsa_card_read(obj,obj->readbuf,bsize); + } + + memcpy(buf,&obj->readbuf[obj->readpos],canread); + obj->readpos+=canread; + if (obj->readpos>=bsize) obj->readpos=0; + return canread; + }else{ + err=__alsa_card_read(obj,buf,size); + return err; + } + +} + +int __alsa_card_write(AlsaCard *obj,char *buf,int size) +{ + int err; + sigset_t set; + sigemptyset(&set); + sigaddset(&set,SIGALRM); + sigprocmask(SIG_BLOCK,&set,NULL); + if ((err=snd_pcm_writei(obj->write_handle,buf,size/obj->frame_size))<0){ + if (err!=-EPIPE){ + g_warning("alsa_card_write: snd_pcm_writei() failed:%s.",snd_strerror(err)); + } + snd_pcm_prepare(obj->write_handle); + err=snd_pcm_writei(obj->write_handle,buf,size/obj->frame_size); + if (err<0) g_warning("alsa_card_write: Error writing sound buffer (size=%i):%s",size,snd_strerror(err)); + + } + sigprocmask(SIG_UNBLOCK,&set,NULL); + return err; +} + +int alsa_card_write(AlsaCard *obj,char *buf,int size) +{ + int err; + gint bsize=SND_CARD(obj)->bsize; + g_return_val_if_fail(obj->write_handle!=NULL,-1); + if (sizewritepos,size); + memcpy(&obj->writebuf[obj->writepos],buf,canwrite); + obj->writepos+=canwrite; + if (obj->writepos>=bsize){ + err=__alsa_card_write(obj,obj->writebuf,bsize); + obj->writepos=0; + } + return canwrite; + }else{ + return __alsa_card_write(obj,buf,bsize); + } +} + +snd_mixer_t *alsa_mixer_open(AlsaCard *obj){ + snd_mixer_t *mixer=NULL; + int err; + err=snd_mixer_open(&mixer,0); + if (err<0){ + g_warning("Could not open alsa mixer: %s",snd_strerror(err)); + return NULL; + } + if ((err = snd_mixer_attach (mixer, obj->mixdev)) < 0){ + g_warning("Could not attach mixer to card: %s",snd_strerror(err)); + snd_mixer_close(mixer); + return NULL; + } + if ((err = snd_mixer_selem_register (mixer, NULL, NULL)) < 0){ + g_warning("snd_mixer_selem_register: %s",snd_strerror(err)); + snd_mixer_close(mixer); + return NULL; + } + if ((err = snd_mixer_load (mixer)) < 0){ + g_warning("snd_mixer_load: %s",snd_strerror(err)); + snd_mixer_close(mixer); + return NULL; + } + obj->mixer=mixer; + return mixer; +} + +void alsa_mixer_close(AlsaCard *obj){ + snd_mixer_close(obj->mixer); + obj->mixer=NULL; +} + +typedef enum {CAPTURE, PLAYBACK, CAPTURE_SWITCH, PLAYBACK_SWITCH} MixerAction; + +static gint get_mixer_element(snd_mixer_t *mixer,const char *name, MixerAction action){ + long value=0; + const char *elemname; + snd_mixer_elem_t *elem; + int err; + long sndMixerPMin; + long sndMixerPMax; + long newvol; + elem=snd_mixer_first_elem(mixer); + while (elem!=NULL){ + elemname=snd_mixer_selem_get_name(elem); + /* //g_message("Found alsa mixer element %s.",elemname); */ + if (strcmp(elemname,name)==0){ + switch (action){ + case CAPTURE: + if (snd_mixer_selem_has_capture_volume(elem)){ + snd_mixer_selem_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); + err=snd_mixer_selem_get_capture_volume(elem,SND_MIXER_SCHN_UNKNOWN,&newvol); + newvol-=sndMixerPMin; + value=(100*newvol)/(sndMixerPMax-sndMixerPMin); + if (err<0) g_warning("Could not get capture volume for %s:%s",name,snd_strerror(err)); + /* //else g_message("Succesfully get capture level for %s.",elemname); */ + break; + } + break; + case PLAYBACK: + if (snd_mixer_selem_has_playback_volume(elem)){ + snd_mixer_selem_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); + err=snd_mixer_selem_get_playback_volume(elem,SND_MIXER_SCHN_FRONT_LEFT,&newvol); + newvol-=sndMixerPMin; + value=(100*newvol)/(sndMixerPMax-sndMixerPMin); + if (err<0) g_warning("Could not get playback volume for %s:%s",name,snd_strerror(err)); + /* //else g_message("Succesfully get playback level for %s.",elemname); */ + break; + } + break; + case CAPTURE_SWITCH: + + break; + } + } + elem=snd_mixer_elem_next(elem); + } + + return value; +} + + +static void set_mixer_element(snd_mixer_t *mixer,const char *name, gint level,MixerAction action){ + const char *elemname; + snd_mixer_elem_t *elem; + int tmp; + long sndMixerPMin; + long sndMixerPMax; + long newvol; + + elem=snd_mixer_first_elem(mixer); + + while (elem!=NULL){ + elemname=snd_mixer_selem_get_name(elem); + /* //g_message("Found alsa mixer element %s.",elemname); */ + if (strcmp(elemname,name)==0){ + switch(action){ + case CAPTURE: + if (snd_mixer_selem_has_capture_volume(elem)){ + snd_mixer_selem_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); + newvol=(((sndMixerPMax-sndMixerPMin)*level)/100)+sndMixerPMin; + snd_mixer_selem_set_capture_volume_all(elem,newvol); + /* //g_message("Succesfully set capture level for %s.",elemname); */ + return; + } + break; + case PLAYBACK: + if (snd_mixer_selem_has_playback_volume(elem)){ + snd_mixer_selem_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); + newvol=(((sndMixerPMax-sndMixerPMin)*level)/100)+sndMixerPMin; + snd_mixer_selem_set_playback_volume_all(elem,newvol); + /* //g_message("Succesfully set playback level for %s.",elemname); */ + return; + } + break; + case CAPTURE_SWITCH: + if (snd_mixer_selem_has_capture_switch(elem)){ + snd_mixer_selem_set_capture_switch_all(elem,level); + /* //g_message("Succesfully set capture switch for %s.",elemname); */ + } + break; + case PLAYBACK_SWITCH: + if (snd_mixer_selem_has_playback_switch(elem)){ + snd_mixer_selem_set_playback_switch_all(elem,level); + /* //g_message("Succesfully set capture switch for %s.",elemname); */ + } + break; + + } + } + elem=snd_mixer_elem_next(elem); + } + + return ; +} + + +void alsa_card_set_level(AlsaCard *obj,gint way,gint a) +{ + snd_mixer_t *mixer; + mixer=alsa_mixer_open(obj); + if (mixer==NULL) return ; + switch(way){ + case SND_CARD_LEVEL_GENERAL: + set_mixer_element(mixer,"Master",a,PLAYBACK); + break; + case SND_CARD_LEVEL_INPUT: + set_mixer_element(mixer,"Capture",a,CAPTURE); + break; + case SND_CARD_LEVEL_OUTPUT: + set_mixer_element(mixer,"PCM",a,PLAYBACK); + break; + default: + g_warning("oss_card_set_level: unsupported command."); + } + alsa_mixer_close(obj); +} + +gint alsa_card_get_level(AlsaCard *obj,gint way) +{ + snd_mixer_t *mixer; + gint value; + mixer=alsa_mixer_open(obj); + if (mixer==NULL) return 0; + switch(way){ + case SND_CARD_LEVEL_GENERAL: + value=get_mixer_element(mixer,"Master",PLAYBACK); + break; + case SND_CARD_LEVEL_INPUT: + value=get_mixer_element(mixer,"Capture",CAPTURE); + break; + case SND_CARD_LEVEL_OUTPUT: + value=get_mixer_element(mixer,"PCM",PLAYBACK); + break; + default: + g_warning("oss_card_set_level: unsupported command."); + } + alsa_mixer_close(obj); + return value; +} + +void alsa_card_set_source(AlsaCard *obj,int source) +{ + snd_mixer_t *mixer; + mixer=alsa_mixer_open(obj); + if (mixer==NULL) return; + switch (source){ + case 'm': + set_mixer_element(mixer,"Mic",1,CAPTURE_SWITCH); + set_mixer_element(mixer,"Capture",1,CAPTURE_SWITCH); + break; + case 'l': + set_mixer_element(mixer,"Line",1,CAPTURE_SWITCH); + set_mixer_element(mixer,"Capture",1,CAPTURE_SWITCH); + break; + } +} + +MSFilter *alsa_card_create_read_filter(AlsaCard *card) +{ + MSFilter *f=ms_oss_read_new(); + ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index); + return f; +} + +MSFilter *alsa_card_create_write_filter(AlsaCard *card) +{ + MSFilter *f=ms_oss_write_new(); + ms_oss_write_set_device(MS_OSS_WRITE(f),SND_CARD(card)->index); + return f; +} + + +SndCard * alsa_card_new(gint devid) +{ + AlsaCard * obj; + SndCard *base; + int err; + gchar *name=NULL; + + /* carefull: this is an alsalib call despite its name! */ + err=snd_card_get_name(devid,&name); + if (err<0) { + return NULL; + } + obj= g_new0(AlsaCard,1); + base= SND_CARD(obj); + snd_card_init(base); + + base->card_name=g_strdup_printf("%s (Advanced Linux Sound Architecture)",name); + base->_probe=(SndCardOpenFunc)alsa_card_probe; + base->_open_r=(SndCardOpenFunc)alsa_card_open_r; + base->_open_w=(SndCardOpenFunc)alsa_card_open_w; + base->_can_read=(SndCardPollFunc)alsa_card_can_read; + base->_set_blocking_mode=(SndCardSetBlockingModeFunc)alsa_card_set_blocking_mode; + base->_read=(SndCardIOFunc)alsa_card_read; + base->_write=(SndCardIOFunc)alsa_card_write; + base->_close_r=(SndCardCloseFunc)alsa_card_close_r; + base->_close_w=(SndCardCloseFunc)alsa_card_close_w; + base->_set_rec_source=(SndCardMixerSetRecSourceFunc)alsa_card_set_source; + base->_set_level=(SndCardMixerSetLevelFunc)alsa_card_set_level; + base->_get_level=(SndCardMixerGetLevelFunc)alsa_card_get_level; + base->_destroy=(SndCardDestroyFunc)alsa_card_destroy; + base->_create_read_filter=(SndCardCreateFilterFunc)alsa_card_create_read_filter; + base->_create_write_filter=(SndCardCreateFilterFunc)alsa_card_create_write_filter; + + + obj->pcmdev=g_strdup_printf("plughw:%i,0",devid); + obj->mixdev=g_strdup_printf("hw:%i",devid); + obj->readbuf=NULL; + obj->writebuf=NULL; + return base; +} + + +gint alsa_card_manager_init(SndCardManager *m, gint index) +{ + gint devindex; + gint i; + gint found=0; + gchar *name=NULL; + for(devindex=0;indexcards[index]=alsa_card_new(devindex); + m->cards[index]->index=index; + found++; + index++; + } + } + return found; +} + +void alsa_card_manager_set_default_pcm_device(const gchar *pcmdev){ + if (over_pcmdev!=NULL){ + g_free(over_pcmdev); + } + over_pcmdev=g_strdup(pcmdev); +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.h new file mode 100644 index 00000000..df3372fb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.h @@ -0,0 +1,50 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#ifdef HAVE_ALSA_ASOUNDLIB_H + +#include "sndcard.h" +#define ALSA_PCM_NEW_HW_PARAMS_API +#include +struct _AlsaCard +{ + SndCard parent; + gchar *pcmdev; + gchar *mixdev; + snd_pcm_t *read_handle; + snd_pcm_t *write_handle; + gint frame_size; + gint frames; + gchar *readbuf; + gint readpos; + gchar *writebuf; + gint writepos; + snd_mixer_t *mixer; +}; + +typedef struct _AlsaCard AlsaCard; + +SndCard *alsa_card_new(gint dev_id); +gint alsa_card_manager_init(SndCardManager *m, gint index); +void alsa_card_manager_set_default_pcm_device(const gchar *pcmdev); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/audiostream.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/audiostream.c new file mode 100644 index 00000000..f4ff4867 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/audiostream.c @@ -0,0 +1,343 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "mediastream.h" +#ifdef INET6 + #include + #include + #include +#endif + + +#define MAX_RTP_SIZE 1500 + +/* this code is not part of the library itself, it is part of the mediastream program */ +void audio_stream_free(AudioStream *stream) +{ + RtpSession *s; + RtpSession *destroyed=NULL; + if (stream->rtprecv!=NULL) { + s=ms_rtp_recv_get_session(MS_RTP_RECV(stream->rtprecv)); + if (s!=NULL){ + destroyed=s; + rtp_session_destroy(s); + } + ms_filter_destroy(stream->rtprecv); + } + if (stream->rtpsend!=NULL) { + s=ms_rtp_send_get_session(MS_RTP_SEND(stream->rtpsend)); + if (s!=NULL){ + if (s!=destroyed) + rtp_session_destroy(s); + } + ms_filter_destroy(stream->rtpsend); + } + if (stream->soundread!=NULL) ms_filter_destroy(stream->soundread); + if (stream->soundwrite!=NULL) ms_filter_destroy(stream->soundwrite); + if (stream->encoder!=NULL) ms_filter_destroy(stream->encoder); + if (stream->decoder!=NULL) ms_filter_destroy(stream->decoder); + if (stream->timer!=NULL) ms_sync_destroy(stream->timer); + g_free(stream); +} + +static int dtmf_tab[16]={'0','1','2','3','4','5','6','7','8','9','*','#','A','B','C','D'}; + +static void on_dtmf_received(RtpSession *s,gint dtmf,gpointer user_data) +{ + AudioStream *stream=(AudioStream*)user_data; + if (dtmf>15){ + g_warning("Unsupported telephone-event type."); + return; + } + g_message("Receiving dtmf %c.",dtmf_tab[dtmf]); + if (stream!=NULL){ + if (strcmp(stream->soundwrite->klass->name,"OssWrite")==0) + ms_oss_write_play_dtmf(MS_OSS_WRITE(stream->soundwrite),dtmf_tab[dtmf]); + } +} + +static void on_timestamp_jump(RtpSession *s,guint32* ts, gpointer user_data) +{ + g_warning("The remote sip-phone has send data with a future timestamp: %u," + "resynchronising session.",*ts); + rtp_session_reset(s); +} + +static const char *ip4local="0.0.0.0"; +static const char *ip6local="::"; + +const char *get_local_addr_for(const char *remote) +{ + const char *ret; +#ifdef INET6 + char num[8]; + struct addrinfo hints, *res0; + int err; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + err = getaddrinfo(remote,"8000", &hints, &res0); + if (err!=0) { + g_warning ("get_local_addr_for: %s", gai_strerror(err)); + return ip4local; + } + ret=(res0->ai_addr->sa_family==AF_INET6) ? ip6local : ip4local; + freeaddrinfo(res0); +#else + ret=ip4local; +#endif + return ret; +} + +void create_duplex_rtpsession(RtpProfile *profile, int locport,char *remip,int remport, + int payload,int jitt_comp, + RtpSession **recvsend){ + RtpSession *rtpr; + rtpr=rtp_session_new(RTP_SESSION_SENDRECV); + rtp_session_max_buf_size_set(rtpr,MAX_RTP_SIZE); + rtp_session_set_profile(rtpr,profile); + rtp_session_set_local_addr(rtpr,get_local_addr_for(remip),locport); + if (remport>0) rtp_session_set_remote_addr(rtpr,remip,remport); + rtp_session_set_scheduling_mode(rtpr,0); + rtp_session_set_blocking_mode(rtpr,0); + rtp_session_set_payload_type(rtpr,payload); + rtp_session_set_jitter_compensation(rtpr,jitt_comp); + rtp_session_enable_adaptive_jitter_compensation(rtpr,TRUE); + /*rtp_session_signal_connect(rtpr,"timestamp_jump",(RtpCallback)on_timestamp_jump,NULL);*/ + *recvsend=rtpr; +} + +void create_rtp_sessions(RtpProfile *profile, int locport,char *remip,int remport, + int payload,int jitt_comp, + RtpSession **recv, RtpSession **send){ + RtpSession *rtps,*rtpr; + PayloadType *pt; + /* creates two rtp filters to recv send streams (remote part)*/ + + rtps=rtp_session_new(RTP_SESSION_SENDONLY); + rtp_session_max_buf_size_set(rtps,MAX_RTP_SIZE); + rtp_session_set_profile(rtps,profile); +#ifdef INET6 + rtp_session_set_local_addr(rtps,"::",locport+2); +#else + rtp_session_set_local_addr(rtps,"0.0.0.0",locport+2); +#endif + rtp_session_set_remote_addr(rtps,remip,remport); + rtp_session_set_scheduling_mode(rtps,0); + rtp_session_set_blocking_mode(rtps,0); + rtp_session_set_payload_type(rtps,payload); + rtp_session_set_jitter_compensation(rtps,jitt_comp); + + rtpr=rtp_session_new(RTP_SESSION_RECVONLY); + rtp_session_max_buf_size_set(rtpr,MAX_RTP_SIZE); + rtp_session_set_profile(rtpr,profile); +#ifdef INET6 + rtp_session_set_local_addr(rtpr,"::",locport); +#else + rtp_session_set_local_addr(rtpr,"0.0.0.0",locport); +#endif + rtp_session_set_scheduling_mode(rtpr,0); + rtp_session_set_blocking_mode(rtpr,0); + rtp_session_set_payload_type(rtpr,payload); + rtp_session_set_jitter_compensation(rtpr,jitt_comp); + rtp_session_signal_connect(rtpr,"telephone-event",(RtpCallback)on_dtmf_received,NULL); + rtp_session_signal_connect(rtpr,"timestamp_jump",(RtpCallback)on_timestamp_jump,NULL); + *recv=rtpr; + *send=rtps; + +} + + +AudioStream * audio_stream_start_full(RtpProfile *profile, int locport,char *remip,int remport, + int payload,int jitt_comp, gchar *infile, gchar *outfile, SndCard *playcard, SndCard *captcard) +{ + AudioStream *stream=g_new0(AudioStream,1); + RtpSession *rtps,*rtpr; + PayloadType *pt; + + /* //create_rtp_sessions(profile,locport,remip,remport,payload,jitt_comp,&rtpr,&rtps); */ + + create_duplex_rtpsession(profile,locport,remip,remport,payload,jitt_comp,&rtpr); + rtp_session_signal_connect(rtpr,"telephone-event",(RtpCallback)on_dtmf_received,(gpointer)stream); + rtps=rtpr; + + stream->recv_session = rtpr; + stream->send_session = rtps; + stream->rtpsend=ms_rtp_send_new(); + ms_rtp_send_set_session(MS_RTP_SEND(stream->rtpsend),rtps); + stream->rtprecv=ms_rtp_recv_new(); + ms_rtp_recv_set_session(MS_RTP_RECV(stream->rtprecv),rtpr); + + + /* creates the local part */ + if (infile==NULL) stream->soundread=snd_card_create_read_filter(captcard); + else stream->soundread=ms_read_new(infile); + if (outfile==NULL) stream->soundwrite=snd_card_create_write_filter(playcard); + else stream->soundwrite=ms_write_new(outfile); + + /* creates the couple of encoder/decoder */ + pt=rtp_profile_get_payload(profile,payload); + if (pt==NULL){ + g_error("audiostream.c: undefined payload type."); + return NULL; + } + stream->encoder=ms_encoder_new_with_string_id(pt->mime_type); + stream->decoder=ms_decoder_new_with_string_id(pt->mime_type); + if ((stream->encoder==NULL) || (stream->decoder==NULL)){ + /* big problem: we have not a registered codec for this payload...*/ + audio_stream_free(stream); + g_error("mediastream.c: No decoder available for payload %i.",payload); + return NULL; + } + /* give the sound filters some properties */ + ms_filter_set_property(stream->soundread,MS_FILTER_PROPERTY_FREQ,&pt->clock_rate); + ms_filter_set_property(stream->soundwrite,MS_FILTER_PROPERTY_FREQ,&pt->clock_rate); + + /* give the encoder/decoder some parameters*/ + ms_filter_set_property(stream->encoder,MS_FILTER_PROPERTY_FREQ,&pt->clock_rate); + ms_filter_set_property(stream->encoder,MS_FILTER_PROPERTY_BITRATE,&pt->normal_bitrate); + ms_filter_set_property(stream->decoder,MS_FILTER_PROPERTY_FREQ,&pt->clock_rate); + ms_filter_set_property(stream->decoder,MS_FILTER_PROPERTY_BITRATE,&pt->normal_bitrate); + + ms_filter_set_property(stream->encoder,MS_FILTER_PROPERTY_FMTP, (void*)pt->fmtp); + ms_filter_set_property(stream->decoder,MS_FILTER_PROPERTY_FMTP,(void*)pt->fmtp); + /* create the synchronisation source */ + stream->timer=ms_timer_new(); + + /* and then connect all */ + ms_filter_add_link(stream->soundread,stream->encoder); + ms_filter_add_link(stream->encoder,stream->rtpsend); + ms_filter_add_link(stream->rtprecv,stream->decoder); + ms_filter_add_link(stream->decoder,stream->soundwrite); + + ms_sync_attach(stream->timer,stream->soundread); + ms_sync_attach(stream->timer,stream->rtprecv); + + /* and start */ + ms_start(stream->timer); + + return stream; +} + +static int defcard=0; + +void audio_stream_set_default_card(int cardindex){ + defcard=cardindex; +} + +AudioStream * audio_stream_start_with_files(RtpProfile *prof,int locport,char *remip, + int remport,int profile,int jitt_comp,gchar *infile, gchar*outfile) +{ + return audio_stream_start_full(prof,locport,remip,remport,profile,jitt_comp,infile,outfile,NULL,NULL); +} + +AudioStream * audio_stream_start(RtpProfile *prof,int locport,char *remip,int remport,int profile,int jitt_comp) +{ + SndCard *sndcard; + sndcard=snd_card_manager_get_card(snd_card_manager,defcard); + return audio_stream_start_full(prof,locport,remip,remport,profile,jitt_comp,NULL,NULL,sndcard,sndcard); +} + +AudioStream *audio_stream_start_with_sndcards(RtpProfile *prof,int locport,char *remip,int remport,int profile,int jitt_comp,SndCard *playcard, SndCard *captcard) +{ + g_return_val_if_fail(playcard!=NULL,NULL); + g_return_val_if_fail(captcard!=NULL,NULL); + return audio_stream_start_full(prof,locport,remip,remport,profile,jitt_comp,NULL,NULL,playcard,captcard); +} + +void audio_stream_set_rtcp_information(AudioStream *st, const char *cname){ + if (st->send_session!=NULL){ + rtp_session_set_source_description(st->send_session,cname,NULL,NULL,NULL, NULL,"linphone", + "This is free software (GPL) !"); + } +} + +void audio_stream_stop(AudioStream * stream) +{ + + ms_stop(stream->timer); + ortp_global_stats_display(); + ms_sync_detach(stream->timer,stream->soundread); + ms_sync_detach(stream->timer,stream->rtprecv); + + ms_filter_remove_links(stream->soundread,stream->encoder); + ms_filter_remove_links(stream->encoder,stream->rtpsend); + ms_filter_remove_links(stream->rtprecv,stream->decoder); + ms_filter_remove_links(stream->decoder,stream->soundwrite); + + audio_stream_free(stream); +} + +RingStream * ring_start(gchar *file,gint interval,SndCard *sndcard) +{ + return ring_start_with_cb(file,interval,sndcard,NULL,NULL); +} + +RingStream * ring_start_with_cb(gchar *file,gint interval,SndCard *sndcard, MSFilterNotifyFunc func,gpointer user_data) +{ + RingStream *stream; + int tmp; + g_return_val_if_fail(sndcard!=NULL,NULL); + stream=g_new0(RingStream,1); + stream->source=ms_ring_player_new(file,interval); + if (stream->source==NULL) { + g_warning("Could not create ring player. Probably the ring file (%s) does not exist.",file); + return NULL; + } + if (func!=NULL) ms_filter_set_notify_func(MS_FILTER(stream->source),func,user_data); + stream->sndwrite=snd_card_create_write_filter(sndcard); + ms_filter_get_property(stream->source,MS_FILTER_PROPERTY_FREQ,&tmp); + ms_filter_set_property(stream->sndwrite,MS_FILTER_PROPERTY_FREQ,&tmp); + ms_filter_get_property(stream->source,MS_FILTER_PROPERTY_CHANNELS,&tmp); + ms_filter_set_property(stream->sndwrite,MS_FILTER_PROPERTY_CHANNELS,&tmp); + stream->timer=ms_timer_new(); + ms_filter_add_link(stream->source,stream->sndwrite); + ms_sync_attach(stream->timer,stream->source); + ms_start(stream->timer); + return stream; +} + +void ring_stop(RingStream *stream) +{ + ms_stop(stream->timer); + ms_sync_detach(stream->timer,stream->source); + ms_sync_destroy(stream->timer); + ms_filter_remove_links(stream->source,stream->sndwrite); + ms_filter_destroy(stream->source); + ms_filter_destroy(stream->sndwrite); + g_free(stream); +} + +/* returns the latency in samples if the audio device with id dev_id is openable in full duplex mode, else 0 */ +gint test_audio_dev(int dev_id) +{ + gint err; + SndCard *sndcard=snd_card_manager_get_card(snd_card_manager,dev_id); + if (sndcard==NULL) return -1; + err=snd_card_probe(sndcard,16,0,8000); + return err; /* return latency in number of sample */ +} + +gint audio_stream_send_dtmf(AudioStream *stream, gchar dtmf) +{ + ms_rtp_send_dtmf(MS_RTP_SEND(stream->rtpsend), dtmf); + ms_oss_write_play_dtmf(MS_OSS_WRITE(stream->soundwrite),dtmf); +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/g711common.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/g711common.h new file mode 100644 index 00000000..3f5ad16f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/g711common.h @@ -0,0 +1,171 @@ +/* + * PCM - A-Law conversion + * Copyright (c) 2000 by Abramo Bagnara + * + * Wrapper for linphone Codec class by Simon Morlat + */ + +static inline int val_seg(int val) +{ + int r = 0; + val >>= 7; + if (val & 0xf0) { + val >>= 4; + r += 4; + } + if (val & 0x0c) { + val >>= 2; + r += 2; + } + if (val & 0x02) + r += 1; + return r; +} + +/* + * s16_to_alaw() - Convert a 16-bit linear PCM value to 8-bit A-law + * + * s16_to_alaw() accepts an 16-bit integer and encodes it as A-law data. + * + * Linear Input Code Compressed Code + * ------------------------ --------------- + * 0000000wxyza 000wxyz + * 0000001wxyza 001wxyz + * 000001wxyzab 010wxyz + * 00001wxyzabc 011wxyz + * 0001wxyzabcd 100wxyz + * 001wxyzabcde 101wxyz + * 01wxyzabcdef 110wxyz + * 1wxyzabcdefg 111wxyz + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ + +static inline unsigned char s16_to_alaw(int pcm_val) +{ + int mask; + int seg; + unsigned char aval; + + if (pcm_val >= 0) { + mask = 0xD5; + } else { + mask = 0x55; + pcm_val = -pcm_val; + if (pcm_val > 0x7fff) + pcm_val = 0x7fff; + } + + if (pcm_val < 256) + aval = pcm_val >> 4; + else { + /* Convert the scaled magnitude to segment number. */ + seg = val_seg(pcm_val); + aval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f); + } + return aval ^ mask; +} + +/* + * alaw_to_s16() - Convert an A-law value to 16-bit linear PCM + * + */ +static inline int alaw_to_s16(unsigned char a_val) +{ + int t; + int seg; + + a_val ^= 0x55; + t = a_val & 0x7f; + if (t < 16) + t = (t << 4) + 8; + else { + seg = (t >> 4) & 0x07; + t = ((t & 0x0f) << 4) + 0x108; + t <<= seg -1; + } + return ((a_val & 0x80) ? t : -t); +} +/* + * s16_to_ulaw() - Convert a linear PCM value to u-law + * + * In order to simplify the encoding process, the original linear magnitude + * is biased by adding 33 which shifts the encoding range from (0 - 8158) to + * (33 - 8191). The result can be seen in the following encoding table: + * + * Biased Linear Input Code Compressed Code + * ------------------------ --------------- + * 00000001wxyza 000wxyz + * 0000001wxyzab 001wxyz + * 000001wxyzabc 010wxyz + * 00001wxyzabcd 011wxyz + * 0001wxyzabcde 100wxyz + * 001wxyzabcdef 101wxyz + * 01wxyzabcdefg 110wxyz + * 1wxyzabcdefgh 111wxyz + * + * Each biased linear code has a leading 1 which identifies the segment + * number. The value of the segment number is equal to 7 minus the number + * of leading 0's. The quantization interval is directly available as the + * four bits wxyz. * The trailing bits (a - h) are ignored. + * + * Ordinarily the complement of the resulting code word is used for + * transmission, and so the code word is complemented before it is returned. + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ + +static inline unsigned char s16_to_ulaw(int pcm_val) /* 2's complement (16-bit range) */ +{ + int mask; + int seg; + unsigned char uval; + + if (pcm_val < 0) { + pcm_val = 0x84 - pcm_val; + mask = 0x7f; + } else { + pcm_val += 0x84; + mask = 0xff; + } + if (pcm_val > 0x7fff) + pcm_val = 0x7fff; + + /* Convert the scaled magnitude to segment number. */ + seg = val_seg(pcm_val); + + /* + * Combine the sign, segment, quantization bits; + * and complement the code word. + */ + uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f); + return uval ^ mask; +} + +/* + * ulaw_to_s16() - Convert a u-law value to 16-bit linear PCM + * + * First, a biased linear code is derived from the code word. An unbiased + * output can then be obtained by subtracting 33 from the biased code. + * + * Note that this function expects to be passed the complement of the + * original code word. This is in keeping with ISDN conventions. + */ +static inline int ulaw_to_s16(unsigned char u_val) +{ + int t; + + /* Complement to obtain normal u-law value. */ + u_val = ~u_val; + + /* + * Extract and bias the quantization bits. Then + * shift up by the segment number and subtract out the bias. + */ + t = ((u_val & 0x0f) << 3) + 0x84; + t <<= (u_val & 0x70) >> 4; + + return ((u_val & 0x80) ? (0x84 - t) : (t - 0x84)); +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/hpuxsndcard.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/hpuxsndcard.c new file mode 100644 index 00000000..8210e29d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/hpuxsndcard.c @@ -0,0 +1,301 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "sndcard.h" +#include "osscard.h" + +#ifdef HAVE_SYS_AUDIO_H +#include + + +#include "msossread.h" +#include "msosswrite.h" + +#include +#include + + +int hpuxsnd_open(HpuxSndCard *obj, int bits,int stereo, int rate) +{ + int fd; + int p=0,cond=0; + int i=0; + int min_size=0,blocksize=512; + /* do a quick non blocking open to be sure that we are not going to be blocked here + for the eternity */ + fd=open(obj->dev_name,O_RDWR|O_NONBLOCK); + if (fd<0) return -EWOULDBLOCK; + close(fd); + /* open the device */ + fd=open(obj->dev_name,O_RDWR); + + g_return_val_if_fail(fd>0,-errno); + + ioctl(fd,AUDIO_RESET,0); + ioctl(fd,AUDIO_SET_SAMPLE_RATE,rate); + ioctl(fd,AUDIO_SET_CHANNELS,stereo); + p=AUDIO_FORMAT_LINEAR16BIT; + ioctl(fd,AUDIO_SET_DATA_FORMAT,p); + /* ioctl(fd,AUDIO_GET_RXBUFSIZE,&min_size); does not work ? */ + min_size=2048; + + g_message("dsp blocksize is %i.",min_size); + obj->fd=fd; + obj->readpos=0; + obj->writepos=0; + SND_CARD(obj)->bits=bits; + SND_CARD(obj)->stereo=stereo; + SND_CARD(obj)->rate=rate; + SND_CARD(obj)->bsize=min_size; + return fd; +} + +int hpux_snd_card_probe(HpuxSndCard *obj,int bits,int stereo,int rate) +{ + return 2048; +} + + +int hpux_snd_card_open(HpuxSndCard *obj,int bits,int stereo,int rate) +{ + int fd; + obj->ref++; + if (obj->fd==0){ + fd=hpuxsnd_open(obj,bits,stereo,rate); + if (fd<0) { + obj->fd=0; + obj->ref--; + return -1; + } + } + SND_CARD(obj)->flags|=SND_CARD_FLAGS_OPENED; + return 0; +} + +void hpux_snd_card_close(HpuxSndCard *obj) +{ + int i; + obj->ref--; + if (obj->ref==0) { + close(obj->fd); + obj->fd=0; + SND_CARD(obj)->flags&=~SND_CARD_FLAGS_OPENED; + + } +} + +void hpux_snd_card_destroy(HpuxSndCard *obj) +{ + snd_card_uninit(SND_CARD(obj)); + g_free(obj->dev_name); + g_free(obj->mixdev_name); +} + +gboolean hpux_snd_card_can_read(HpuxSndCard *obj) +{ + struct timeval tout={0,0}; + int err; + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(obj->fd,&fdset); + err=select(obj->fd+1,&fdset,NULL,NULL,&tout); + if (err>0) return TRUE; + else return FALSE; +} + +int hpux_snd_card_read(HpuxSndCard *obj,char *buf,int size) +{ + int err; + gint bsize=SND_CARD(obj)->bsize; + if (sizereadpos,size); + if (obj->readbuf==NULL) obj->readbuf=g_malloc0(bsize); + if (obj->readpos==0){ + err=read(obj->fd,obj->readbuf,bsize); + if (err<0) { + g_warning("hpux_snd_card_read: read() failed:%s.",strerror(errno)); + return -1; + } + } + + memcpy(buf,&obj->readbuf[obj->readpos],canread); + obj->readpos+=canread; + if (obj->readpos>=bsize) obj->readpos=0; + return canread; + }else{ + err=read(obj->fd,buf,size); + if (err<0) { + g_warning("hpux_snd_card_read: read-2() failed:%s.",strerror(errno)); + } + return err; + } + +} + +int hpux_snd_card_write(HpuxSndCard *obj,char *buf,int size) +{ + int err; + gint bsize=SND_CARD(obj)->bsize; + if (sizewritepos,size); + if (obj->writebuf==NULL) obj->writebuf=g_malloc0(bsize); + + memcpy(&obj->writebuf[obj->writepos],buf,canwrite); + obj->writepos+=canwrite; + if (obj->writepos>=bsize){ + err=write(obj->fd,obj->writebuf,bsize); + } + return canwrite; + }else{ + return write(obj->fd,buf,bsize); + } +} + +#define SND_CARD_LEVEL_TO_HPUX_LEVEL(a) (((a)*2) - 100) +#define HPUX_LEVEL_TO_SND_CARD_LEVEL(a) (((a)+200)/2) +void hpux_snd_card_set_level(HpuxSndCard *obj,gint way,gint a) +{ + struct audio_gain gain; + int error,mix_fd; + + g_return_if_fail(obj->mixdev_name!=NULL); + memset(&gain,0,sizeof(struct audio_gain)); + switch(way){ + case SND_CARD_LEVEL_GENERAL: + gain.cgain[0].monitor_gain=SND_CARD_LEVEL_TO_HPUX_LEVEL(a); + gain.cgain[1].monitor_gain=SND_CARD_LEVEL_TO_HPUX_LEVEL(a); + break; + case SND_CARD_LEVEL_INPUT: + gain.cgain[0].receive_gain=SND_CARD_LEVEL_TO_HPUX_LEVEL(a); + gain.cgain[1].receive_gain=SND_CARD_LEVEL_TO_HPUX_LEVEL(a); + break; + case SND_CARD_LEVEL_OUTPUT: + gain.cgain[0].transmit_gain=SND_CARD_LEVEL_TO_HPUX_LEVEL(a); + gain.cgain[1].transmit_gain=SND_CARD_LEVEL_TO_HPUX_LEVEL(a); + break; + default: + g_warning("hpux_snd_card_set_level: unsupported command."); + return; + } + gain.channel_mask=AUDIO_CHANNEL_RIGHT|AUDIO_CHANNEL_LEFT; + mix_fd = open(obj->mixdev_name, O_WRONLY); + g_return_if_fail(mix_fd>0); + error=ioctl(mix_fd,AUDIO_SET_GAINS,&gain); + if (error<0){ + g_warning("hpux_snd_card_set_level: Could not set gains: %s",strerror(errno)); + } + close(mix_fd); +} + +gint hpux_snd_card_get_level(HpuxSndCard *obj,gint way) +{ + struct audio_gain gain; + int p=0,mix_fd,error; + g_return_if_fail(obj->mixdev_name!=NULL); + + gain.channel_mask=AUDIO_CHANNEL_RIGHT|AUDIO_CHANNEL_LEFT; + mix_fd = open(obj->mixdev_name, O_RDONLY); + g_return_if_fail(mix_fd>0); + error=ioctl(mix_fd,AUDIO_GET_GAINS,&gain); + if (error<0){ + g_warning("hpux_snd_card_set_level: Could not get gains: %s",strerror(errno)); + } + close(mix_fd); + + switch(way){ + case SND_CARD_LEVEL_GENERAL: + p=gain.cgain[0].monitor_gain; + break; + case SND_CARD_LEVEL_INPUT: + p=gain.cgain[0].receive_gain; + break; + case SND_CARD_LEVEL_OUTPUT: + p=gain.cgain[0].transmit_gain; + break; + default: + g_warning("hpux_snd_card_get_level: unsupported command."); + return -1; + } + return HPUX_LEVEL_TO_SND_CARD_LEVEL(p); +} + +void hpux_snd_card_set_source(HpuxSndCard *obj,int source) +{ + gint p=0; + gint mix_fd; + gint error=0; + g_return_if_fail(obj->mixdev_name!=NULL); + + mix_fd=open("/dev/audio",O_WRONLY); + g_return_if_fail(mix_fd>0); + switch(source){ + case 'm': + error=ioctl(mix_fd,AUDIO_SET_INPUT,AUDIO_IN_MIKE); + break; + case 'l': + error=ioctl(mix_fd,AUDIO_SET_INPUT,AUDIO_IN_LINE); + break; + default: + g_warning("hpux_snd_card_set_source: unsupported source."); + } + close(mix_fd); +} + +MSFilter *hpux_snd_card_create_read_filter(HpuxSndCard *card) +{ + MSFilter *f=ms_oss_read_new(); + ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index); + return f; +} + +MSFilter *hpux_snd_card_create_write_filter(HpuxSndCard *card) +{ + MSFilter *f=ms_oss_write_new(); + ms_oss_write_set_device(MS_OSS_WRITE(f),SND_CARD(card)->index); + return f; +} + + +SndCard * hpux_snd_card_new(char *devname, char *mixdev_name) +{ + HpuxSndCard * obj= g_new0(HpuxSndCard,1); + SndCard *base= SND_CARD(obj); + snd_card_init(base); + obj->dev_name=g_strdup(devname); + obj->mixdev_name=g_strdup( mixdev_name); + base->card_name=g_strdup(devname); + base->_probe=(SndCardOpenFunc)hpux_snd_card_probe; + base->_open_r=(SndCardOpenFunc)hpux_snd_card_open; + base->_open_w=(SndCardOpenFunc)hpux_snd_card_open; + base->_can_read=(SndCardPollFunc)hpux_snd_card_can_read; + base->_read=(SndCardIOFunc)hpux_snd_card_read; + base->_write=(SndCardIOFunc)hpux_snd_card_write; + base->_close_r=(SndCardCloseFunc)hpux_snd_card_close; + base->_close_w=(SndCardCloseFunc)hpux_snd_card_close; + base->_set_rec_source=(SndCardMixerSetRecSourceFunc)hpux_snd_card_set_source; + base->_set_level=(SndCardMixerSetLevelFunc)hpux_snd_card_set_level; + base->_get_level=(SndCardMixerGetLevelFunc)hpux_snd_card_get_level; + base->_destroy=(SndCardDestroyFunc)hpux_snd_card_destroy; + base->_create_read_filter=(SndCardCreateFilterFunc)hpux_snd_card_create_read_filter; + base->_create_write_filter=(SndCardCreateFilterFunc)hpux_snd_card_create_write_filter; + return base; +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.c new file mode 100644 index 00000000..b929cce9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.c @@ -0,0 +1,574 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + JACK support + Copyright (C) 2004 Tobias Gehrig tobias@gehrig.tk +*/ + +#include "jackcard.h" + +#ifdef __JACK_ENABLED__ + +#include "msossread.h" +#include "msosswrite.h" + +#include + +#define READBUFFERSIZE 524288 +#define WRITEBUFFERSIZE 524288 +#define BSIZE 512 + +/** + * jack_shutdown: + * @arg: + * + * This is the shutdown callback for this JACK application. + * It is called by JACK if the server ever shuts down or + * decides to disconnect the client. + * + */ +void +jack_shutdown (void *arg) +{ + JackCard* obj = (JackCard*) arg; + + obj->jack_running = FALSE; + obj->jack_active = FALSE; + obj->read.port = NULL; + if (obj->read.open) + obj->read.init = TRUE; + obj->write.port = NULL; + if (obj->write.open) + obj->write.init = TRUE; +} + +int samplerate(jack_nframes_t rate, void *arg) +{ + JackCard* obj = (JackCard*) arg; + int error; + + obj->rate = rate; + if (obj->read.open) { + obj->read.data.src_ratio = (double)obj->read.rate / (double)obj->rate; + obj->read.data.input_frames = (long)((double)obj->read.frames/obj->read.data.src_ratio); + g_free(obj->read.data.data_in); + obj->read.data.data_in = malloc(obj->read.data.input_frames*sizeof(float)); + if (obj->read.src_state) + if ((error = src_set_ratio(obj->read.src_state, obj->read.data.src_ratio)) != 0) + g_warning("Error while resetting the write samplerate: %s", src_strerror(error)); + } + if (obj->write.open) { + obj->write.data.src_ratio = (double)obj->rate / (double)obj->write.rate; + obj->write.data.output_frames = (long)((double)obj->write.frames*obj->write.data.src_ratio); + g_free(obj->write.data.data_out); + obj->write.data.data_out = malloc(obj->write.data.output_frames*sizeof(float)); + if (obj->write.src_state) + if ((error = src_set_ratio(obj->write.src_state, obj->write.data.src_ratio)) != 0) + g_warning("Error while resetting the write samplerate: %s", src_strerror(error)); + } + return 0; +} + +/* + * The process callback for this JACK application. + * It is called by JACK at the appropriate times. + * @nframes : + * @arg : + */ +int +process (jack_nframes_t nframes, void *arg) +{ + JackCard* obj = (JackCard*) arg; + sample_t *out; + sample_t *in; + + if (obj->clear && !obj->write.can_process) { + out = (sample_t *) jack_port_get_buffer (obj->write.port, nframes); + memset (out, 0, nframes * sizeof(sample_t)); + obj->clear = FALSE; + } + + if (!obj->can_process) + return 0; + + if(obj->read.can_process) { + in = (sample_t *) jack_port_get_buffer (obj->read.port, nframes); + jack_ringbuffer_write (obj->read.buffer, (void *) in, sizeof(sample_t) * nframes); + } + + if (obj->write.can_process) { + out = (sample_t *) jack_port_get_buffer (obj->write.port, nframes); + memset (out, 0, nframes * sizeof(sample_t)); + if (obj->clear && jack_ringbuffer_read_space(obj->write.buffer) == 0) { + obj->write.can_process = FALSE; + if (!obj->read.open) + obj->can_process = FALSE; + obj->clear = FALSE; + return 0; + } + jack_ringbuffer_read (obj->write.buffer, (void *) out, sizeof(sample_t) * nframes); + } + return 0; +} + +int jack_init(JackCard* obj) +{ + char* client_name; + int error; + + if (!obj->jack_running) { + obj->client = NULL; + client_name = g_strdup_printf("linphone-%u", g_random_int()); + if ((obj->client = jack_client_new (client_name)) == NULL) { + g_warning("cannot create jack client"); + g_free(client_name); + return -1; + } + g_message("Found Jack Daemon"); + g_free(client_name); + + /* tell the JACK server to call `process()' whenever + there is work to be done. + */ + jack_set_process_callback (obj->client, process, obj); + + /* tell the JACK server to call `jack_shutdown()' if + it ever shuts down, either entirely, or if it + just decides to stop calling us. + */ + jack_on_shutdown (obj->client, jack_shutdown, obj); + jack_set_sample_rate_callback (obj->client, samplerate, obj); + obj->rate = jack_get_sample_rate (obj->client); + if (obj->rate == 0) { + g_warning ("rate is 0???"); + if (jack_client_close(obj->client) != 0) + g_warning("could not close client"); + return -1; + } + obj->buffer_size = jack_get_buffer_size(obj->client); + obj->jack_running = TRUE; + } + + if (!obj->jack_active) { + if (jack_activate (obj->client)) { + g_warning("cannot activate jack client"); + return -1; + } else obj->jack_active = TRUE; + } + + if (obj->read.init) { + if (!obj->read.port && (obj->read.port = jack_port_register (obj->client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0))==NULL) { + g_warning("error while trying to register input port"); + return -1; + } + if (!obj->read.phys_ports && (obj->read.phys_ports = jack_get_ports (obj->client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput)) == NULL) { + g_warning("Cannot find any physical capture ports\n"); + jack_port_unregister(obj->client, obj->read.port); + obj->read.port = NULL; + return -1; + } + if (!jack_port_connected(obj->read.port)) + if ((error = jack_connect (obj->client, obj->read.phys_ports[0], jack_port_name (obj->read.port))) != 0) { + g_warning("cannot connect input ports: %s -> %s\n", jack_port_name (obj->read.port), obj->read.phys_ports[0]); + if (error == EEXIST) g_warning("connection already made"); + else { + jack_port_unregister(obj->client, obj->read.port); + obj->read.port = NULL; + return -1; + } + } + obj->read.init = FALSE; + } + + if (obj->write.init) { + if (!obj->write.port && (obj->write.port = jack_port_register (obj->client, "output", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0))==NULL) { + g_warning("error while trying to register output port"); + return -1; + } + if (!obj->write.phys_ports && (obj->write.phys_ports = jack_get_ports (obj->client, NULL, NULL, JackPortIsPhysical|JackPortIsInput)) == NULL) { + g_warning("Cannot find any physical playback ports\n"); + jack_port_unregister(obj->client, obj->write.port); + obj->write.port = NULL; + return -1; + } + if (!jack_port_connected(obj->write.port)) { + if ((error = jack_connect (obj->client, jack_port_name (obj->write.port), obj->write.phys_ports[0])) != 0) { + g_warning("cannot connect output ports: %s -> %s\n", jack_port_name (obj->write.port), obj->write.phys_ports[0]); + if (error == EEXIST) g_warning("connection already made"); + else { + jack_port_unregister(obj->client, obj->write.port); + obj->write.port = NULL; + return -1; + } + } + if ((error = jack_connect (obj->client, jack_port_name (obj->write.port), obj->write.phys_ports[1])) != 0) { + g_warning("cannot connect output ports: %s -> %s\n", jack_port_name (obj->write.port), obj->write.phys_ports[1]); + if (error == EEXIST) g_warning("connection already made"); + else { + jack_port_unregister(obj->client, obj->write.port); + obj->write.port = NULL; + return -1; + } + } + } + obj->write.init = FALSE; + } + return 0; +} + +int jack_card_open_r(JackCard *obj,int bits,int stereo,int rate) +{ + int channels = stereo + 1, bsize, error; + obj->read.init = TRUE; + if (jack_init(obj) != 0) return -1; + + obj->read.rate = rate; + obj->sample_size = bits / 8; + obj->frame_size = channels * obj->sample_size; + bsize = BSIZE; + obj->read.frames = bsize / 2; + SND_CARD(obj)->bsize = bsize; + SND_CARD(obj)->flags |= SND_CARD_FLAGS_OPENED; + obj->read.channels = channels; + if ((obj->read.src_state = src_new (SRC_SINC_FASTEST, channels, &error)) == NULL) + g_warning("Error while initializing the samplerate converter: %s", src_strerror(error)); + obj->read.data.src_ratio = (double)rate / (double)obj->rate; + obj->read.data.input_frames = (long)((double)obj->read.frames/obj->read.data.src_ratio); + obj->read.data.data_in = malloc(obj->read.data.input_frames*sizeof(float)); + obj->read.data.data_out = malloc(obj->read.frames*sizeof(float)); + obj->read.data.end_of_input = 0; + if (!obj->read.buffer) + obj->read.buffer = jack_ringbuffer_create(READBUFFERSIZE); + obj->read.can_process = TRUE; + obj->can_process = TRUE; + obj->read.open = TRUE; + obj->read.init = FALSE; + return 0; +} + +int jack_card_open_w(JackCard *obj,int bits,int stereo,int rate) +{ + int channels = stereo + 1, bsize, err; + obj->write.init = TRUE; + if (jack_init(obj) != 0) return -1; + + obj->write.rate = rate; + obj->sample_size = bits / 8; + obj->frame_size = channels * obj->sample_size; + bsize = BSIZE; + obj->write.frames = bsize / 2; + SND_CARD(obj)->bsize = bsize; + SND_CARD(obj)->flags |= SND_CARD_FLAGS_OPENED; + obj->write.channels = channels; + if ((obj->write.src_state = src_new (SRC_SINC_FASTEST, channels, &err)) == NULL) + g_warning("Error while initializing the samplerate converter: %s", src_strerror(err)); + obj->write.data.src_ratio = (double)obj->rate / (double)rate; + obj->write.data.data_in = malloc(obj->write.frames*sizeof(float)); + obj->write.data.end_of_input = 0; + obj->write.data.output_frames = (long)((double)obj->write.frames*obj->write.data.src_ratio); + obj->write.data.data_out = malloc(obj->write.data.output_frames*sizeof(float)); + if (!obj->write.buffer) + obj->write.buffer = jack_ringbuffer_create(WRITEBUFFERSIZE); + obj->write.can_process = TRUE; + obj->can_process = TRUE; + obj->write.open = TRUE; + obj->write.init = FALSE; + return 0; +} + +void jack_card_set_blocking_mode(JackCard *obj, gboolean yesno) +{ +} + +void jack_card_close_r(JackCard *obj) +{ + obj->read.open = FALSE; + obj->read.init = FALSE; + obj->read.can_process = FALSE; + if (!obj->write.open) + obj->can_process = FALSE; + if (obj->read.src_state) + obj->read.src_state = src_delete (obj->read.src_state); + g_free(obj->read.data.data_in); + g_free(obj->read.data.data_out); +} + +void jack_card_close_w(JackCard *obj) +{ + obj->write.open = FALSE; + obj->write.init = FALSE; + obj->clear = TRUE; + if (!obj->jack_running) { + obj->write.can_process = FALSE; + obj->can_process = FALSE; + } + if (obj->write.src_state) + obj->write.src_state = src_delete (obj->write.src_state); + g_free(obj->write.data.data_in); + g_free(obj->write.data.data_out); +} + +int jack_card_probe(JackCard *obj,int bits,int stereo,int rate) +{ + if (obj->jack_running) return BSIZE; + else if (jack_init(obj) == 0) return BSIZE; + else return -1; +} + +void jack_card_destroy(JackCard *obj) +{ + if (obj->jack_running) jack_client_close (obj->client); + snd_card_uninit(SND_CARD(obj)); + if (obj->read.buffer) { + jack_ringbuffer_free(obj->read.buffer); + obj->read.buffer = NULL; + } + if (obj->write.buffer) { + jack_ringbuffer_free(obj->write.buffer); + obj->write.buffer = NULL; + } + if (obj->read.phys_ports) { + g_free(obj->read.phys_ports); + obj->read.phys_ports = NULL; + } + if (obj->write.phys_ports) { + g_free(obj->write.phys_ports); + obj->write.phys_ports = NULL; + } +} + +gboolean jack_card_can_read(JackCard *obj) +{ + g_return_val_if_fail(obj->read.buffer!=NULL,0); + if (jack_ringbuffer_read_space(obj->read.buffer)>=(long)((double)obj->read.frames/obj->read.data.src_ratio)*sizeof(sample_t)) return TRUE; + else return FALSE; +} + +int jack_card_read(JackCard *obj,char *buf,int size) +{ + size_t bytes, can_read, i; + int error; + float norm, value; + + g_return_val_if_fail((obj->read.buffer!=NULL)&&(obj->read.src_state!=NULL),-1); + if (jack_init(obj) != 0) return -1; + size /= 2; + can_read = MIN(size, obj->read.frames); + // can_read = MIN(((long)((double)can_read / obj->read.data.src_ratio))*sizeof(sample_t), jack_ringbuffer_read_space(obj->read.buffer)); + can_read = ((long)((double)can_read / obj->read.data.src_ratio))*sizeof(sample_t); + obj->read.can_process = FALSE; + bytes = jack_ringbuffer_read (obj->read.buffer, (void *)obj->read.data.data_in, can_read); + obj->read.can_process = TRUE; + obj->read.data.input_frames = bytes / sizeof(sample_t); + can_read = MIN(size, obj->read.frames); + obj->read.data.output_frames = can_read; + if ((error = src_process(obj->read.src_state, &(obj->read.data))) != 0) + g_warning("error while samplerate conversion. error: %s", src_strerror(error)); + norm = obj->read.level*obj->level*(float)0x8000; + for (i=0; i < obj->read.data.output_frames_gen; i++) { + value = obj->read.data.data_out[i]*norm; + if (value >= 32767.0) + ((short*)buf)[i] = 32767; + else if (value <= -32768.0) + ((short*)buf)[i] = -32768; + else + ((short*)buf)[i] = (short)value; + } + bytes = obj->read.data.output_frames_gen * 2; + return bytes; +} + +int jack_card_write(JackCard *obj,char *buf,int size) +{ + size_t bytes, can_write, i; + int error; + float norm; + + g_return_val_if_fail((obj->write.buffer!=NULL)&&(obj->write.src_state!=NULL),-1); + if (jack_init(obj) != 0) return -1; + size /= 2; + can_write = MIN(size, obj->write.frames); + norm = obj->write.level*obj->level/(float)0x8000; + for (i=0; iwrite.data.data_in[i] = (float)((short*)buf)[i]*norm; + } + obj->write.data.input_frames = can_write; + if ((error = src_process(obj->write.src_state, &(obj->write.data))) != 0) + g_warning("error while samplerate conversion. error: %s", src_strerror(error)); + obj->write.can_process = FALSE; + bytes = jack_ringbuffer_write (obj->write.buffer, (void *) obj->write.data.data_out, sizeof(sample_t)*obj->write.data.output_frames_gen); + obj->write.can_process = TRUE; + return bytes; +} + +void jack_card_set_level(JackCard *obj,gint way,gint a) +{ + switch(way){ + case SND_CARD_LEVEL_GENERAL: + obj->level = (float)a / 100.0; + break; + case SND_CARD_LEVEL_INPUT: + obj->read.level = (float)a / 100.0; + break; + case SND_CARD_LEVEL_OUTPUT: + obj->write.level = (float)a / 100.0; + break; + default: + g_warning("jack_card_set_level: unsupported command."); + } +} + +gint jack_card_get_level(JackCard *obj,gint way) +{ + gint value = 0; + + switch(way){ + case SND_CARD_LEVEL_GENERAL: + value = (gint)(obj->level*100.0); + break; + case SND_CARD_LEVEL_INPUT: + value = (gint)(obj->read.level*100.0); + break; + case SND_CARD_LEVEL_OUTPUT: + value = (gint)(obj->write.level*100.0); + break; + default: + g_warning("jack_card_get_level: unsupported command."); + } + return value; +} + +void jack_card_set_source(JackCard *obj,int source) +{ +} + +MSFilter *jack_card_create_read_filter(JackCard *card) +{ + MSFilter *f=ms_oss_read_new(); + ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index); + return f; +} + +MSFilter *jack_card_create_write_filter(JackCard *card) +{ + MSFilter *f=ms_oss_write_new(); + ms_oss_write_set_device(MS_OSS_WRITE(f),SND_CARD(card)->index); + return f; +} +SndCard * jack_card_new(jack_client_t *client) +{ + JackCard * obj; + SndCard *base; + + obj= g_new0(JackCard,1); + + if (!client) return NULL; + obj->client = client; + obj->jack_running = TRUE; + obj->jack_active = FALSE; + obj->can_process = FALSE; + obj->clear = TRUE; + obj->write.can_process = FALSE; + obj->write.open = FALSE; + obj->write.init = TRUE; + obj->write.port = NULL; + obj->write.phys_ports = NULL; + obj->write.buffer = NULL; + obj->read.can_process = FALSE; + obj->read.open = FALSE; + obj->read.init = TRUE; + obj->read.port = NULL; + obj->read.phys_ports = NULL; + obj->read.buffer = NULL; + + /* tell the JACK server to call `process()' whenever + there is work to be done. + */ + jack_set_process_callback (client, process, obj); + + /* tell the JACK server to call `jack_shutdown()' if + it ever shuts down, either entirely, or if it + just decides to stop calling us. + */ + jack_on_shutdown (client, jack_shutdown, obj); + + jack_set_sample_rate_callback (client, samplerate, obj); + + obj->rate = jack_get_sample_rate (client); + obj->buffer_size = jack_get_buffer_size(obj->client); + + jack_init(obj); + + base= SND_CARD(obj); + snd_card_init(base); + +#ifdef HAVE_GLIB + base->card_name=g_strdup_printf("JACK client"); +#else + base->card_name=malloc(100); + snprintf(base->card_name, 100, "JACK client"); +#endif + + base->_probe=(SndCardOpenFunc)jack_card_probe; + base->_open_r=(SndCardOpenFunc)jack_card_open_r; + base->_open_w=(SndCardOpenFunc)jack_card_open_w; + base->_can_read=(SndCardPollFunc)jack_card_can_read; + base->_set_blocking_mode=(SndCardSetBlockingModeFunc)jack_card_set_blocking_mode; + base->_read=(SndCardIOFunc)jack_card_read; + base->_write=(SndCardIOFunc)jack_card_write; + base->_close_r=(SndCardCloseFunc)jack_card_close_r; + base->_close_w=(SndCardCloseFunc)jack_card_close_w; + base->_set_rec_source=(SndCardMixerSetRecSourceFunc)jack_card_set_source; + base->_set_level=(SndCardMixerSetLevelFunc)jack_card_set_level; + base->_get_level=(SndCardMixerGetLevelFunc)jack_card_get_level; + base->_destroy=(SndCardDestroyFunc)jack_card_destroy; + base->_create_read_filter=(SndCardCreateFilterFunc)jack_card_create_read_filter; + base->_create_write_filter=(SndCardCreateFilterFunc)jack_card_create_write_filter; + + obj->read.buffer=NULL; + obj->write.buffer=NULL; + obj->buffer_size = 0; + obj->level = 1.0; + obj->write.level = 1.0; + obj->read.level = 1.0; + + return base; +} + + +gint jack_card_manager_init(SndCardManager *m, gint index) +{ + jack_client_t *client = NULL; + char* client_name; + + client_name=g_strdup_printf("linphone-%u", g_random_int()); + if ((client = jack_client_new (client_name))!= NULL) + { + g_message("Found Jack Daemon"); + g_free(client_name); + m->cards[index]=jack_card_new(client); + m->cards[index]->index=index; + return 1; + } else { + g_free(client_name); + return 0; + } +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.h new file mode 100644 index 00000000..33ec46dc --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.h @@ -0,0 +1,81 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + JACK support + Copyright (C) 2004 Tobias Gehrig tobias@gehrig.tk +*/ + +#ifndef JACK_CARD_H +#define JACK_CARD_H + +#include + +#ifdef __JACK_ENABLED__ + +#include "sndcard.h" + +#include +#include + +#include + +typedef jack_default_audio_sample_t sample_t; + +typedef struct { + jack_port_t *port; + const char **phys_ports; + float level; + jack_ringbuffer_t *buffer; + gint channels; + gint rate; + SRC_STATE* src_state; + SRC_DATA data; + size_t frames; + gboolean can_process; + gboolean open; + gboolean init; +} jackcard_mode_t; + +struct _JackCard +{ + SndCard parent; + + jack_client_t *client; + gboolean jack_running; + gboolean jack_active; + float level; + jack_nframes_t buffer_size; + gint sample_size; + gint frame_size; + gint rate; + gboolean can_process; + gboolean clear; + + jackcard_mode_t read, write; +}; + +typedef struct _JackCard JackCard; + +SndCard * jack_card_new(jack_client_t *client); + +gint jack_card_manager_init(SndCardManager *m, gint index); + +#endif + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mediastream.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mediastream.h new file mode 100644 index 00000000..3ccbab69 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mediastream.h @@ -0,0 +1,130 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MEDIASTREAM_H +#define MEDIASTREAM_H + +#include "msrtprecv.h" +#include "msrtpsend.h" +#include "ms.h" +#include "msosswrite.h" +#include "msossread.h" +#include "msread.h" +#include "mswrite.h" +#include "mstimer.h" +#include "mscodec.h" +#ifdef HAVE_SPEEX +#include "msspeexdec.h" +#endif +#include "msringplayer.h" + + +struct _AudioStream +{ + MSSync *timer; + RtpSession *send_session; + RtpSession *recv_session; + MSFilter *soundread; + MSFilter *soundwrite; + MSFilter *encoder; + MSFilter *decoder; + MSFilter *rtprecv; + MSFilter *rtpsend; +}; + + +typedef struct _AudioStream AudioStream; + +struct _RingStream +{ + MSSync *timer; + MSFilter *source; + MSFilter *sndwrite; +}; + +typedef struct _RingStream RingStream; + +/* start a thread that does sampling->encoding->rtp_sending|rtp_receiving->decoding->playing */ +AudioStream *audio_stream_start (RtpProfile * prof, int locport, char *remip, + int remport, int profile, int jitt_comp); + +AudioStream *audio_stream_start_with_sndcards(RtpProfile * prof, int locport, char *remip4, + int remport, int profile, int jitt_comp, SndCard *playcard, SndCard *captcard); + +AudioStream *audio_stream_start_with_files (RtpProfile * prof, int locport, + char *remip4, int remport, + int profile, int jitt_comp, + gchar * infile, gchar * outfile); +void audio_stream_set_rtcp_information(AudioStream *st, const char *cname); + + +/* stop the above process*/ +void audio_stream_stop (AudioStream * stream); + +RingStream *ring_start (gchar * file, gint interval, SndCard *sndcard); +RingStream *ring_start_with_cb(gchar * file, gint interval, SndCard *sndcard, MSFilterNotifyFunc func,gpointer user_data); +void ring_stop (RingStream * stream); + +/* returns the latency in samples if the audio device with id dev_id is openable in full duplex mode, else 0 */ +gint test_audio_dev (int dev_id); + +/* send a dtmf */ +gint audio_stream_send_dtmf (AudioStream * stream, gchar dtmf); + +void audio_stream_set_default_card(int cardindex); + + +#ifdef VIDEO_ENABLED + +/***************** + Video Support + *****************/ + + + +struct _VideoStream +{ + MSSync *timer; + RtpSession *send_session; + RtpSession *recv_session; + MSFilter *source; + MSFilter *output; + MSFilter *encoder; + MSFilter *decoder; + MSFilter *rtprecv; + MSFilter *rtpsend; + gboolean show_local; +}; + + +typedef struct _VideoStream VideoStream; + +VideoStream *video_stream_start(RtpProfile *profile, int locport, char *remip4, int remport, + int payload, int jitt_comp, gboolean show_local, const gchar *source, const gchar *device); +void video_stream_set_rtcp_information(VideoStream *st, const char *cname); +void video_stream_stop (VideoStream * stream); + +VideoStream * video_preview_start(const gchar *source, const gchar *device); +void video_preview_stop(VideoStream *stream); + +#endif + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.c new file mode 100644 index 00000000..cfcafa33 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.c @@ -0,0 +1,342 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "ms.h" +#include "sndcard.h" +#include "mscodec.h" + +#include +#include +#include +#include +#include +#include + +#ifdef VIDEO_ENABLED +extern void ms_video_source_register_all(); +#endif +#ifdef HAVE_ILBC +extern void ms_ilbc_codec_init(); +#endif + +/** + * ms_init: + * + * + * Initialize the mediastreamer. This must be the first function called in a program + * using the mediastreamer library. + * + * + */ +void ms_init() +{ + if (!g_thread_supported()) g_thread_init (NULL); +#ifdef HAVE_GLIB + if (!g_module_supported()){ + g_error("GModule is not supported."); + } +#endif + /* initialize the oss subsystem */ + snd_card_manager_init(snd_card_manager); + /* register the statically linked codecs */ + ms_codec_register_all(); +#ifdef VIDEO_ENABLED + ms_video_source_register_all(); +#endif +#ifdef HAVE_ILBC + ms_ilbc_codec_init(); +#endif +} + + +static gint compare(gconstpointer a, gconstpointer b) +{ + MSFilter *f1=(MSFilter*)a,*f2=(MSFilter*)b; + if (f1->klassklass) return -1; + if (f1->klass==f2->klass) return 0; + /* if f1->klass>f2->klass ....*/ + return 1; +} + +static GList *g_list_append_if_new(GList *l,gpointer data) +{ + GList *res=l; + if (g_list_find(res,data)==NULL) + res=g_list_append(res,data); + return(res); +} + +static GList *get_nexts(MSFilter *f,GList *l) +{ + int i; + MSFifo *fifo; + MSQueue *q; + GList *res=l; + + /* check fifos*/ + for (i=0;i klass->max_foutputs;i++) + { + fifo=f->outfifos[i]; + if (fifo!=NULL) res=g_list_append_if_new(res,(gpointer)fifo->next_data); + } + /* check queues*/ + for (i=0;i klass->max_qoutputs;i++) + { + q=f->outqueues[i]; + if (q!=NULL) res=g_list_append_if_new(res,(gpointer)q->next_data); + } + return(res); +} + +/* compile graphs attached to a sync source*/ +int ms_compile(MSSync *sync) +{ + int i; + GList *list1=NULL,*list2=NULL,*elem; + GList *proc_chain=NULL; + MSFilter *f; + + /* first free the old list if we are just updating*/ + if (sync->execution_list!=NULL) g_list_free(sync->execution_list); + /* get the list of filters attached to this sync*/ + for (i=0;ifilters;i++) + { + /* //printf("found filter !\n"); */ + list1=g_list_append(list1,sync->attached_filters[i]); + } + /* find the processing chain */ + while (list1!=NULL) + { + list2=NULL; + /* sort the list by types of filter*/ + list1=g_list_sort(list1,compare); + /* save into the processing chain list*/ + /* //printf("list1 :%i elements\n",g_list_length(list1)); */ + proc_chain=g_list_concat(proc_chain,list1); + /* get all following filters. They are appended to list2*/ + elem=list1; + while (elem!=NULL) + { + f=(MSFilter*)(elem->data); + /* check if filter 's status */ + if (f->klass->attributes & FILTER_CAN_SYNC) + { + sync->samples_per_tick=0; + } + list2=get_nexts(f,list2); + elem=g_list_next(elem); + } + list1=list2; + } + sync->execution_list=proc_chain; + sync->flags&=~MS_SYNC_NEED_UPDATE; + ms_trace("%i filters successfully compiled in a processing chain.",g_list_length(sync->execution_list)); + return 0; +} + +/*execute the processing chain attached to a sync source. It is called as a thread by ms_main()*/ +void *ms_thread_run(void *sync_ptr) +{ + MSSync *sync=(MSSync*) sync_ptr; + GList *filter; + MSFilter *f; + + + ms_sync_lock(sync); + while(sync->run) + { + /* //g_message("sync->run=%i",sync->run); */ + if (sync->samples_per_tick==0) ms_sync_suspend(sync); + if (sync->flags & MS_SYNC_NEED_UPDATE){ + ms_compile(sync); + ms_sync_setup(sync); + } + filter=sync->execution_list; + ms_sync_unlock(sync); + /* //ms_trace("Calling synchronisation"); */ + ms_sync_synchronize(sync); + while(filter!=NULL) + { + f=(MSFilter*)filter->data; + if (MS_FILTER_GET_CLASS(f)->attributes & FILTER_IS_SOURCE) + { + /* execute it once */ + ms_trace("Running source filter %s.",f->klass->name); + ms_filter_process(f); + } + else + { + /* make the filter process its input data until it has no more */ + while ( ms_filter_fifos_have_data(f) || ms_filter_queues_have_data(f) ) + { + ms_trace("Running filter %s.",f->klass->name); + ms_filter_process(f); + } + } + filter=g_list_next(filter); + } + ms_sync_lock(sync); + } + g_cond_signal(sync->stop_cond); /* signal that the sync thread has finished */ + ms_sync_unlock(sync); + g_message("Mediastreamer processing thread is exiting."); + return NULL; +} + +/* stop the processing chain attached to a sync source.*/ +void ms_thread_stop(MSSync *sync) +{ + if (sync->thread!=NULL) + { + if (sync->samples_per_tick==0) + { + /* to wakeup the thread */ + /* //g_cond_signal(sync->thread_cond); */ + } + g_mutex_lock(sync->lock); + sync->run=0; + sync->thread=NULL; + g_cond_wait(sync->stop_cond,sync->lock); + g_mutex_unlock(sync->lock); + } + /* //g_message("ms_thread_stop() finished."); */ +} + +/** + * ms_start: + * @sync: A synchronisation source to be started. + * + * Starts a thread that will shedule all processing chains attached to the synchronisation source @sync. + * + * + */ +void ms_start(MSSync *sync) +{ + if (sync->run==1) return; /*already running*/ + ms_compile(sync); + ms_sync_setup(sync); + /* this is to avoid race conditions, for example: + ms_start(sync); + ms_oss_write_start(ossw); + here tge ossw filter need to be compiled to run ms_oss_write_start() + */ + ms_trace("ms_start: creating new thread."); + sync->run=1; + sync->thread=g_thread_create((GThreadFunc)ms_thread_run,(gpointer)sync,TRUE,NULL); + if (sync->thread==NULL){ + g_warning("Could not create thread !"); + } +} + +/** + * ms_stop: + * @sync: A synchronisation source to be stopped. + * + * Stop the thread that was sheduling the processing chains attached to the synchronisation source @sync. + * The processing chains are kept unchanged, no object is freed. The synchronisation source can be restarted using ms_start(). + * + * + */ +void ms_stop(MSSync *sync) +{ + ms_thread_stop(sync); + ms_sync_unsetup(sync); +} + + +gint ms_load_plugin(gchar *path) +{ +#ifdef HAVE_GLIB + g_module_open(path,0); +#endif + return 0; +} + +gchar * ms_proc_get_param(gchar *parameter) +{ + gchar *file; + int fd; + int err,len; + gchar *p,*begin,*end; + gchar *ret; + fd=open("/proc/cpuinfo",O_RDONLY); + if (fd<0){ + g_warning("Could not open /proc/cpuinfo."); + return NULL; + } + file=g_malloc(1024); + err=read(fd,file,1024); + file[err-1]='\0'; + /* find the parameter */ + p=strstr(file,parameter); + if (p==NULL){ + /* parameter not found */ + g_free(file); + return NULL; + } + /* find the following ':' */ + p=strchr(p,':'); + if (p==NULL){ + g_free(file); + return NULL; + } + /* find the value*/ + begin=p+2; + end=strchr(begin,'\n'); + if (end==NULL) end=strchr(begin,'\0'); + len=end-begin+1; + ret=g_malloc(len+1); + snprintf(ret,len,"%s",begin); + /* //printf("%s=%s\n",parameter,ret); */ + g_free(file); + return ret; +} + +gint ms_proc_get_type() +{ + static int proc_type=0; + gchar *value; + if (proc_type==0){ + value=ms_proc_get_param("cpu family"); + if (value!=NULL) { + proc_type=atoi(value); + g_free(value); + }else return -1; + } + return proc_type; +} + +gint ms_proc_get_speed() +{ + char *value; + static int proc_speed=0; + if (proc_speed==0){ + value=ms_proc_get_param("cpu MHz"); + if (value!=NULL){ + proc_speed=atoi(value); + g_free(value); + }else return -1; + } + /* //printf("proc_speed=%i\n",proc_speed); */ + return proc_speed; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.h new file mode 100644 index 00000000..51c69b96 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.h @@ -0,0 +1,81 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + + +#ifndef MS_H +#define MS_H +#include "msfilter.h" +#include "mssync.h" + + +void ms_init(); + +/* compile graphs attached to a sync source*/ +int ms_compile(MSSync *source); + + +/* stop the processing chain attached to a sync source.*/ +void ms_thread_stop(MSSync *sync); + + +/** + * function_name:ms_thread_run + * @sync: The synchronization source for all the set of graphs to run. + * + * Execute the processing chain attached to a sync source. This function loops indefinitely. + * The media streamer programmer can choose to execute this function directly, or to call ms_start(), + * that will start a thread for the synchronisation source. + * + * Returns: no return value. + */ +void *ms_thread_run(void *sync); + + +/** + * function_name:ms_start + * @sync: A synchronisation source to be started. + * + * Starts a thread that will shedule all processing chains attached to the synchronisation source @sync. + * + * Returns: no return value. + */ +void ms_start(MSSync *sync); + + +/** + * function_name:ms_stop + * @sync: A synchronisation source to be stopped. + * + * Stop the thread that was sheduling the processing chains attached to the synchronisation source @sync. + * The processing chains are kept unchanged, no object is freed. The synchronisation source can be restarted using ms_start(). + * + * Returns: no return value. + */ +void ms_stop(MSSync *sync); + + +gchar * ms_proc_get_param(gchar *parameter); +gint ms_proc_get_type(); +gint ms_proc_get_speed(); + + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawdec.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawdec.c new file mode 100644 index 00000000..70cc906e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawdec.c @@ -0,0 +1,132 @@ + /* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include +#include + +extern MSFilter * ms_ALAWencoder_new(void); + +MSCodecInfo ALAWinfo={ + { + "ALAW codec", + 0, + MS_FILTER_AUDIO_CODEC, + ms_ALAWencoder_new, + "This is the classic A-law codec. Good quality, but only usable with high speed network connections." + }, + ms_ALAWencoder_new, + ms_ALAWdecoder_new, + 320, + 160, + 64000, + 8000, + 8, + "PCMA", + 1, + 1, +}; + +static MSALAWDecoderClass *ms_ALAWdecoder_class=NULL; + +MSFilter * ms_ALAWdecoder_new(void) +{ + MSALAWDecoder *r; + + r=g_new(MSALAWDecoder,1); + ms_ALAWdecoder_init(r); + if (ms_ALAWdecoder_class==NULL) + { + ms_ALAWdecoder_class=g_new(MSALAWDecoderClass,1); + ms_ALAWdecoder_class_init(ms_ALAWdecoder_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_ALAWdecoder_class); + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_ALAWdecoder_init(MSALAWDecoder *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->outfifos=r->f_outputs; + MS_FILTER(r)->r_mingran=ALAW_DECODER_RMAXGRAN; + memset(r->f_inputs,0,sizeof(MSFifo*)*MSALAWDECODER_MAX_INPUTS); + memset(r->f_outputs,0,sizeof(MSFifo*)*MSALAWDECODER_MAX_INPUTS); + +} + +void ms_ALAWdecoder_class_init(MSALAWDecoderClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"ALAWDecoder"); + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&ALAWinfo; + MS_FILTER_CLASS(klass)->max_finputs=MSALAWDECODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSALAWDECODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=ALAW_DECODER_RMAXGRAN; + MS_FILTER_CLASS(klass)->w_maxgran=ALAW_DECODER_WMAXGRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_ALAWdecoder_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_ALAWdecoder_process; +} + +void ms_ALAWdecoder_process(MSALAWDecoder *r) +{ + MSFifo *fi,*fo; + int inlen,outlen; + gchar *s,*d; + int i; + /* process output fifos, but there is only one for this class of filter*/ + + /* this is the simplest process function design: + the filter declares a r_mingran of ALAW_DECODER_RMAXGRAN, so the mediastreamer's + scheduler will call the process function each time there is ALAW_DECODER_RMAXGRAN + bytes to read in the input fifo. If there is more, then it will call it several + time in order to the fifo to be completetly processed. + This is very simple, but not very efficient because of the multiple call function + of MSFilterProcessFunc that may happen. + The MSAlawEncoder implements another design; see it. + */ + + fi=r->f_inputs[0]; + fo=r->f_outputs[0]; + g_return_if_fail(fi!=NULL); + g_return_if_fail(fo!=NULL); + + inlen=ms_fifo_get_read_ptr(fi,ALAW_DECODER_RMAXGRAN,(void**)&s); + if (s==NULL) return; + outlen=ms_fifo_get_write_ptr(fo,ALAW_DECODER_WMAXGRAN,(void**)&d); + if (d!=NULL) + { + for(i=0;i +#include + +/*this is the class that implements a ALAWdecoder filter*/ + +#define MSALAWDECODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSALAWDecoder +{ + /* the MSALAWDecoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSALAWDecoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSALAWDECODER_MAX_INPUTS]; + MSFifo *f_outputs[MSALAWDECODER_MAX_INPUTS]; +} MSALAWDecoder; + +typedef struct _MSALAWDecoderClass +{ + /* the MSALAWDecoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSALAWDecoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSALAWDecoderClass; + +/* PUBLIC */ +#define MS_ALAWDECODER(filter) ((MSALAWDecoder*)(filter)) +#define MS_ALAWDECODER_CLASS(klass) ((MSALAWDecoderClass*)(klass)) +MSFilter * ms_ALAWdecoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_ALAWdecoder_init(MSALAWDecoder *r); +void ms_ALAWdecoder_class_init(MSALAWDecoderClass *klass); +void ms_ALAWdecoder_destroy( MSALAWDecoder *obj); +void ms_ALAWdecoder_process(MSALAWDecoder *r); + +/* tuning parameters :*/ +#define ALAW_DECODER_WMAXGRAN 320 +#define ALAW_DECODER_RMAXGRAN 160 + +extern MSCodecInfo ALAWinfo; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawenc.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawenc.c new file mode 100644 index 00000000..fd1f9abe --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawenc.c @@ -0,0 +1,124 @@ + /* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "msAlawenc.h" +#include "g711common.h" + +extern MSCodecInfo ALAWinfo; + +static MSALAWEncoderClass *ms_ALAWencoder_class=NULL; + +MSFilter * ms_ALAWencoder_new(void) +{ + MSALAWEncoder *r; + + r=g_new(MSALAWEncoder,1); + ms_ALAWencoder_init(r); + if (ms_ALAWencoder_class==NULL) + { + ms_ALAWencoder_class=g_new(MSALAWEncoderClass,1); + ms_ALAWencoder_class_init(ms_ALAWencoder_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_ALAWencoder_class); + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_ALAWencoder_init(MSALAWEncoder *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->outfifos=r->f_outputs; + MS_FILTER(r)->r_mingran=ALAW_ENCODER_RMAXGRAN; /* the filter can be called as soon as there is + something to process */ + memset(r->f_inputs,0,sizeof(MSFifo*)*MSALAWENCODER_MAX_INPUTS); + memset(r->f_outputs,0,sizeof(MSFifo*)*MSALAWENCODER_MAX_INPUTS); + +} + +void ms_ALAWencoder_class_init(MSALAWEncoderClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"ALAWEncoder"); + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&ALAWinfo; + MS_FILTER_CLASS(klass)->max_finputs=MSALAWENCODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSALAWENCODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=ALAW_ENCODER_RMAXGRAN; + MS_FILTER_CLASS(klass)->w_maxgran=ALAW_ENCODER_WMAXGRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_ALAWencoder_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_ALAWencoder_process; +} + +void ms_ALAWencoder_process(MSALAWEncoder *r) +{ + MSFifo *fi,*fo; + int inlen,outlen; + gchar *s,*d; + int i; + /* process output fifos, but there is only one for this class of filter*/ + + /* this is the sophisticated design of the process function: + Here the filter declares that it can be called as soon as there is something + to read on the input fifo by setting r_mingran=0. + Then it ask for the fifo to get as many data as possible by calling: + inlen=ms_fifo_get_read_ptr(fi,0,(void**)&s); + This avoid multiple call to the process function to process all data available + on the input fifo... but the writing of the process function is a bit + more difficult, because althoug ms_fifo_get_read_ptr() returns N bytes, + we cannot ask ms_fifo_get_write_ptr to return N bytes if + N>MS_FILTER_CLASS(klass)->w_maxgran. This is forbidden by the MSFifo + mechanism. + This is an open issue. + For the moment what is done here is that ms_fifo_get_write_ptr() is called + several time with its maximum granularity in order to try to write the output. + ... + One solution: + -create a new function ms_fifo_get_rw_ptr(fifo1,p1, fifo2,p2) to + return the number of bytes able to being processed according to the input + and output fifo, and their respective data pointers + */ + + + fi=r->f_inputs[0]; + fo=r->f_outputs[0]; + + inlen=ms_fifo_get_read_ptr(fi,ALAW_ENCODER_RMAXGRAN,(void**)&s); + if (s==NULL) return; + outlen=ms_fifo_get_write_ptr(fo,ALAW_ENCODER_WMAXGRAN,(void**)&d); + if (d!=NULL) + { + for(i=0;i +#include +#include + +/*this is the class that implements a GSMdecoder filter*/ + +#define MSGSMDECODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSGSMDecoder +{ + /* the MSGSMDecoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSGSMDecoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSGSMDECODER_MAX_INPUTS]; + MSFifo *f_outputs[MSGSMDECODER_MAX_INPUTS]; + gsm gsm_handle; +} MSGSMDecoder; + +typedef struct _MSGSMDecoderClass +{ + /* the MSGSMDecoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSGSMDecoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSGSMDecoderClass; + +/* PUBLIC */ +#define MS_GSMDECODER(filter) ((MSGSMDecoder*)(filter)) +#define MS_GSMDECODER_CLASS(klass) ((MSGSMDecoderClass*)(klass)) +MSFilter * ms_GSMdecoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_GSMdecoder_init(MSGSMDecoder *r); +void ms_GSMdecoder_class_init(MSGSMDecoderClass *klass); +void ms_GSMdecoder_destroy( MSGSMDecoder *obj); +void ms_GSMdecoder_process(MSGSMDecoder *r); + +extern MSCodecInfo GSMinfo; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msGSMencoder.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msGSMencoder.h new file mode 100644 index 00000000..2deae387 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msGSMencoder.h @@ -0,0 +1,61 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSGSMENCODER_H +#define MSGSMENCODER_H + +#include "msfilter.h" +#include + +/*this is the class that implements a GSMencoder filter*/ + +#define MSGSMENCODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSGSMEncoder +{ + /* the MSGSMEncoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSGSMEncoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSGSMENCODER_MAX_INPUTS]; + MSFifo *f_outputs[MSGSMENCODER_MAX_INPUTS]; + gsm gsm_handle; +} MSGSMEncoder; + +typedef struct _MSGSMEncoderClass +{ + /* the MSGSMEncoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSGSMEncoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSGSMEncoderClass; + +/* PUBLIC */ +#define MS_GSMENCODER(filter) ((MSGSMEncoder*)(filter)) +#define MS_GSMENCODER_CLASS(klass) ((MSGSMEncoderClass*)(klass)) +MSFilter * ms_GSMencoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_GSMencoder_init(MSGSMEncoder *r); +void ms_GSMencoder_class_init(MSGSMEncoderClass *klass); +void ms_GSMencoder_destroy( MSGSMEncoder *obj); +void ms_GSMencoder_process(MSGSMEncoder *r); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10decoder.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10decoder.h new file mode 100644 index 00000000..59d9deca --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10decoder.h @@ -0,0 +1,64 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSLPC10DECODER_H +#define MSLPC10DECODER_H + +#include +#include +#include + +/*this is the class that implements a LPC10decoder filter*/ + +#define MSLPC10DECODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSLPC10Decoder +{ + /* the MSLPC10Decoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSLPC10Decoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSLPC10DECODER_MAX_INPUTS]; + MSFifo *f_outputs[MSLPC10DECODER_MAX_INPUTS]; + struct lpc10_decoder_state *lpc10_dec; +} MSLPC10Decoder; + +typedef struct _MSLPC10DecoderClass +{ + /* the MSLPC10Decoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSLPC10Decoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSLPC10DecoderClass; + +/* PUBLIC */ +#define MS_LPC10DECODER(filter) ((MSLPC10Decoder*)(filter)) +#define MS_LPC10DECODER_CLASS(klass) ((MSLPC10DecoderClass*)(klass)) +MSFilter * ms_LPC10decoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_LPC10decoder_init(MSLPC10Decoder *r); +void ms_LPC10decoder_class_init(MSLPC10DecoderClass *klass); +void ms_LPC10decoder_destroy( MSLPC10Decoder *obj); +void ms_LPC10decoder_process(MSLPC10Decoder *r); + +extern MSCodecInfo LPC10info; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10encoder.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10encoder.h new file mode 100644 index 00000000..4db16436 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10encoder.h @@ -0,0 +1,74 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSLPC10ENCODER_H +#define MSLPC10ENCODER_H + +#include "mscodec.h" + + +int +read_16bit_samples(gint16 int16samples[], float speech[], int n); + +int +write_16bit_samples(gint16 int16samples[], float speech[], int n); + +void +write_bits(unsigned char *data, gint32 *bits, int len); + +int +read_bits(unsigned char *data, gint32 *bits, int len); + + +/*this is the class that implements a LPC10encoder filter*/ + +#define MSLPC10ENCODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSLPC10Encoder +{ + /* the MSLPC10Encoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSLPC10Encoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSLPC10ENCODER_MAX_INPUTS]; + MSFifo *f_outputs[MSLPC10ENCODER_MAX_INPUTS]; + struct lpc10_encoder_state *lpc10_enc; +} MSLPC10Encoder; + +typedef struct _MSLPC10EncoderClass +{ + /* the MSLPC10Encoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSLPC10Encoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSLPC10EncoderClass; + +/* PUBLIC */ +#define MS_LPC10ENCODER(filter) ((MSLPC10Encoder*)(filter)) +#define MS_LPC10ENCODER_CLASS(klass) ((MSLPC10EncoderClass*)(klass)) +MSFilter * ms_LPC10encoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_LPC10encoder_init(MSLPC10Encoder *r); +void ms_LPC10encoder_class_init(MSLPC10EncoderClass *klass); +void ms_LPC10encoder_destroy( MSLPC10Encoder *obj); +void ms_LPC10encoder_process(MSLPC10Encoder *r); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawdec.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawdec.c new file mode 100644 index 00000000..500f2389 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawdec.c @@ -0,0 +1,130 @@ + /* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include +#include + +extern MSFilter * ms_MULAWencoder_new(void); + +MSCodecInfo MULAWinfo={ + { + "MULAW codec", + 0, + MS_FILTER_AUDIO_CODEC, + ms_MULAWencoder_new, + "This is the classic Mu-law codec. Good quality, but only usable with high speed network connections." + }, + ms_MULAWencoder_new, + ms_MULAWdecoder_new, + 320, + 160, + 64000, + 8000, + 0, + "PCMU", + 1, + 1 +}; + +static MSMULAWDecoderClass *ms_MULAWdecoder_class=NULL; + +MSFilter * ms_MULAWdecoder_new(void) +{ + MSMULAWDecoder *r; + + r=g_new(MSMULAWDecoder,1); + ms_MULAWdecoder_init(r); + if (ms_MULAWdecoder_class==NULL) + { + ms_MULAWdecoder_class=g_new(MSMULAWDecoderClass,1); + ms_MULAWdecoder_class_init(ms_MULAWdecoder_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_MULAWdecoder_class); + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_MULAWdecoder_init(MSMULAWDecoder *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->outfifos=r->f_outputs; + MS_FILTER(r)->r_mingran=MULAW_DECODER_RMAXGRAN; + memset(r->f_inputs,0,sizeof(MSFifo*)*MSMULAWDECODER_MAX_INPUTS); + memset(r->f_outputs,0,sizeof(MSFifo*)*MSMULAWDECODER_MAX_INPUTS); + +} + +void ms_MULAWdecoder_class_init(MSMULAWDecoderClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"MULAWDecoder"); + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&MULAWinfo; + MS_FILTER_CLASS(klass)->max_finputs=MSMULAWDECODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSMULAWDECODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=MULAW_DECODER_RMAXGRAN; + MS_FILTER_CLASS(klass)->w_maxgran=MULAW_DECODER_WMAXGRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_MULAWdecoder_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_MULAWdecoder_process; +} + +void ms_MULAWdecoder_process(MSMULAWDecoder *r) +{ + MSFifo *fi,*fo; + int inlen,outlen; + gchar *s,*d; + int i; + /* process output fifos, but there is only one for this class of filter*/ + + /* this is the simplest process function design: + the filter declares a r_mingran of MULAW_DECODER_RMAXGRAN, so the mediastreamer's + scheduler will call the process function each time there is MULAW_DECODER_RMAXGRAN + bytes to read in the input fifo. If there is more, then it will call it several + time in order to the fifo to be completetly processed. + This is very simple, but not very efficient because of the multiple call function + of MSFilterProcessFunc that may happen. + The MSAlawEncoder implements another design; see it. + */ + + fi=r->f_inputs[0]; + fo=r->f_outputs[0]; + + inlen=ms_fifo_get_read_ptr(fi,MULAW_DECODER_RMAXGRAN,(void**)&s); + if (s==NULL) g_error("ms_MULAWdecoder_process: internal error."); + outlen=ms_fifo_get_write_ptr(fo,MULAW_DECODER_WMAXGRAN,(void**)&d); + if (d!=NULL) + { + for(i=0;i +#include + +/*this is the class that implements a MULAWdecoder filter*/ + +#define MSMULAWDECODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSMULAWDecoder +{ + /* the MSMULAWDecoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSMULAWDecoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSMULAWDECODER_MAX_INPUTS]; + MSFifo *f_outputs[MSMULAWDECODER_MAX_INPUTS]; +} MSMULAWDecoder; + +typedef struct _MSMULAWDecoderClass +{ + /* the MSMULAWDecoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSMULAWDecoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSMULAWDecoderClass; + +/* PUBLIC */ +#define MS_MULAWDECODER(filter) ((MSMULAWDecoder*)(filter)) +#define MS_MULAWDECODER_CLASS(klass) ((MSMULAWDecoderClass*)(klass)) +MSFilter * ms_MULAWdecoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_MULAWdecoder_init(MSMULAWDecoder *r); +void ms_MULAWdecoder_class_init(MSMULAWDecoderClass *klass); +void ms_MULAWdecoder_destroy( MSMULAWDecoder *obj); +void ms_MULAWdecoder_process(MSMULAWDecoder *r); + +/* tuning parameters :*/ +#define MULAW_DECODER_WMAXGRAN 320 +#define MULAW_DECODER_RMAXGRAN 160 + +extern MSCodecInfo MULAWinfo; + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawenc.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawenc.c new file mode 100644 index 00000000..2f740d89 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawenc.c @@ -0,0 +1,99 @@ + /* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "msMUlawenc.h" +#include "g711common.h" + +extern MSCodecInfo MULAWinfo; + +static MSMULAWEncoderClass *ms_MULAWencoder_class=NULL; + +MSFilter * ms_MULAWencoder_new(void) +{ + MSMULAWEncoder *r; + + r=g_new(MSMULAWEncoder,1); + ms_MULAWencoder_init(r); + if (ms_MULAWencoder_class==NULL) + { + ms_MULAWencoder_class=g_new(MSMULAWEncoderClass,1); + ms_MULAWencoder_class_init(ms_MULAWencoder_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_MULAWencoder_class); + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_MULAWencoder_init(MSMULAWEncoder *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->outfifos=r->f_outputs; + MS_FILTER(r)->r_mingran=MULAW_ENCODER_RMAXGRAN; /* the filter can be called as soon as there is + something to process */ + memset(r->f_inputs,0,sizeof(MSFifo*)*MSMULAWENCODER_MAX_INPUTS); + memset(r->f_outputs,0,sizeof(MSFifo*)*MSMULAWENCODER_MAX_INPUTS); + +} + +void ms_MULAWencoder_class_init(MSMULAWEncoderClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"MULAWEncoder"); + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&MULAWinfo; + MS_FILTER_CLASS(klass)->max_finputs=MSMULAWENCODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSMULAWENCODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=MULAW_ENCODER_RMAXGRAN; + MS_FILTER_CLASS(klass)->w_maxgran=MULAW_ENCODER_WMAXGRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_MULAWencoder_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_MULAWencoder_process; +} + +void ms_MULAWencoder_process(MSMULAWEncoder *r) +{ + MSFifo *fi,*fo; + int inlen,outlen; + gchar *s,*d; + int i; + /* process output fifos, but there is only one for this class of filter*/ + + fi=r->f_inputs[0]; + fo=r->f_outputs[0]; + inlen=ms_fifo_get_read_ptr(fi,MULAW_ENCODER_RMAXGRAN,(void**)&s); + outlen=ms_fifo_get_write_ptr(fo,MULAW_ENCODER_WMAXGRAN,(void**)&d); + if (d!=NULL) + { + for(i=0;i + +/*this is the class that implements a AVdecoder filter*/ + +#define MSAVDECODER_MAX_INPUTS 1 /* max output per filter*/ + + +struct _MSAVDecoder +{ + /* the MSAVDecoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSAVDecoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSQueue *q_inputs[MSAVDECODER_MAX_INPUTS]; + MSQueue *q_outputs[MSAVDECODER_MAX_INPUTS]; + AVCodec *av_codec; /*the AVCodec from which this MSFilter is related */ + AVCodecContext av_context; /* the context of the AVCodec */ + gint av_opened; + int output_pix_fmt; + int width; + int height; + int skip_gob; + unsigned char buf_compressed[100000]; + int buf_size; + MSBuffer *obufwrap; /* alternate buffer, when format change is needed*/ +}; + +typedef struct _MSAVDecoder MSAVDecoder; + +struct _MSAVDecoderClass +{ + /* the MSAVDecoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSAVDecoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +}; + +typedef struct _MSAVDecoderClass MSAVDecoderClass; + +/* PUBLIC */ +#define MS_AVDECODER(filter) ((MSAVDecoder*)(filter)) +#define MS_AVDECODER_CLASS(klass) ((MSAVDecoderClass*)(klass)) + +MSFilter *ms_h263_decoder_new(); +MSFilter *ms_mpeg_decoder_new(); +MSFilter *ms_mpeg4_decoder_new(); +MSFilter * ms_AVdecoder_new_with_codec(enum CodecID codec_id); + +gint ms_AVdecoder_set_format(MSAVDecoder *dec, gchar *fmt); +void ms_AVdecoder_set_width(MSAVDecoder *av,gint w); +void ms_AVdecoder_set_height(MSAVDecoder *av,gint h); + +/* FOR INTERNAL USE*/ +void ms_AVdecoder_init(MSAVDecoder *r, AVCodec *codec); +void ms_AVdecoder_uninit(MSAVDecoder *enc); +void ms_AVdecoder_class_init(MSAVDecoderClass *klass); +void ms_AVdecoder_destroy( MSAVDecoder *obj); +void ms_AVdecoder_process(MSAVDecoder *r); + +void ms_AVCodec_init(); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msavencoder.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msavencoder.h new file mode 100644 index 00000000..6fe5cad4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msavencoder.h @@ -0,0 +1,90 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSAVENCODER_H +#define MSAVENCODER_H + +#include "msfilter.h" +#include "mscodec.h" +#include + +/*this is the class that implements a AVencoder filter*/ + +#define MSAVENCODER_MAX_INPUTS 1 /* max output per filter*/ +#define MSAVENCODER_MAX_OUTPUTS 2 + +struct _MSAVEncoder +{ + /* the MSAVEncoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSAVEncoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSQueue *q_inputs[MSAVENCODER_MAX_INPUTS]; + MSQueue *q_outputs[MSAVENCODER_MAX_OUTPUTS]; + AVCodec *av_codec; /*the AVCodec from which this MSFilter is related */ + AVCodecContext av_context; /* the context of the AVCodec */ + gint input_pix_fmt; + gint av_opened; + MSBuffer *comp_buf; + MSBuffer *yuv_buf; +}; + +typedef struct _MSAVEncoder MSAVEncoder; +/* MSAVEncoder always outputs planar YUV and accept any incoming format you should setup using + ms_AVencoder_set_format() +q_outputs[0] is the compressed video stream output +q_outputs[1] is a YUV planar buffer of the image it receives in input. +*/ + + +struct _MSAVEncoderClass +{ + /* the MSAVEncoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSAVEncoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +}; + +typedef struct _MSAVEncoderClass MSAVEncoderClass; + +/* PUBLIC */ +#define MS_AVENCODER(filter) ((MSAVEncoder*)(filter)) +#define MS_AVENCODER_CLASS(klass) ((MSAVEncoderClass*)(klass)) + +MSFilter *ms_h263_encoder_new(); +MSFilter *ms_mpeg_encoder_new(); +MSFilter *ms_mpeg4_encoder_new(); +MSFilter * ms_AVencoder_new_with_codec(enum CodecID codec_id, MSCodecInfo *info); + +gint ms_AVencoder_set_format(MSAVEncoder *enc, gchar *fmt); + +#define ms_AVencoder_set_width(av,w) (av)->av_context.width=(w) +#define ms_AVencoder_set_height(av,h) (av)->av_context.height=(h) +#define ms_AVencoder_set_bit_rate(av,r) (av)->av_context.bit_rate=(r) + +void ms_AVencoder_set_frame_rate(MSAVEncoder *enc, gint frame_rate, gint frame_rate_base); + +/* FOR INTERNAL USE*/ +void ms_AVencoder_init(MSAVEncoder *r, AVCodec *codec); +void ms_AVencoder_uninit(MSAVEncoder *enc); +void ms_AVencoder_class_init(MSAVEncoderClass *klass); +void ms_AVencoder_destroy( MSAVEncoder *obj); +void ms_AVencoder_process(MSAVEncoder *r); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.c new file mode 100644 index 00000000..4ca3c925 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.c @@ -0,0 +1,94 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msbuffer.h" +#include "msutils.h" +#include + + + +MSBuffer * ms_buffer_new(guint32 size) +{ + MSBuffer *buf; + buf=(MSBuffer*)g_malloc(sizeof(MSBuffer)+size); + buf->ref_count=0; + buf->size=size; + ms_trace("ms_buffer_new: Allocating buffer of %i bytes.",size); + /* allocate the data buffer: there is a lot of optmisation that can be done by using a pool of cached buffers*/ + buf->buffer=((char*)(buf))+sizeof(MSBuffer); /* to avoid to do two allocations, + buffer info and buffer are contigous.*/ + buf->flags=MS_BUFFER_CONTIGUOUS; + return(buf); +} + +MSBuffer *ms_buffer_alloc(gint flags) +{ + MSBuffer *buf; + buf=(MSBuffer*)g_malloc(sizeof(MSBuffer)); + buf->ref_count=0; + buf->size=0; + buf->buffer=NULL; + buf->flags=0; + return(buf); +} + + +void ms_buffer_destroy(MSBuffer *buf) +{ + if (buf->flags & MS_BUFFER_CONTIGUOUS){ + g_free(buf); + } + else { + g_free(buf->buffer); + g_free(buf); + } +} + +MSMessage *ms_message_alloc() +{ + MSMessage *m=g_malloc(sizeof(MSMessage)); + memset(m,0,sizeof(MSMessage)); + return m; +} + +MSMessage *ms_message_new(gint size) +{ + MSMessage *m=ms_message_alloc(); + MSBuffer *buf=ms_buffer_new(size); + ms_message_set_buf(m,buf); + return m; +} + +void ms_message_destroy(MSMessage *m) +{ + /* the buffer is freed if its ref_count goes to zero */ + if (m->buffer!=NULL){ + m->buffer->ref_count--; + if (m->buffer->ref_count==0) ms_buffer_destroy(m->buffer); + } + g_free(m); +} + +MSMessage * ms_message_dup(MSMessage *m) +{ + MSMessage *msg=ms_message_alloc(); + ms_message_set_buf(msg,m->buffer); + return msg; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.h new file mode 100644 index 00000000..f96b35a7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.h @@ -0,0 +1,75 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSBUFFER_H +#define MSBUFFER_H +#include + +#ifdef HAVE_GLIB +#include +#else +#include +#endif + + +#define MS_BUFFER_LARGE 4092 + + +typedef struct _MSBuffer +{ + gchar *buffer; + guint32 size; + guint16 ref_count; + guint16 flags; +#define MS_BUFFER_CONTIGUOUS (1) +}MSBuffer; + +MSBuffer * ms_buffer_new(guint32 size); +void ms_buffer_destroy(MSBuffer *buf); + +struct _MSMessage +{ + MSBuffer *buffer; /* points to a MSBuffer */ + void *data; /*points to buffer->buffer */ + guint32 size; /* the size of the buffer to read in data. It may not be the + physical size (I mean buffer->buffer->size */ + struct _MSMessage *next; + struct _MSMessage *prev; /* MSMessage are queued into MSQueues */ +}; + +typedef struct _MSMessage MSMessage; + + +MSBuffer *ms_buffer_alloc(gint flags); +MSMessage *ms_message_new(gint size); + +#define ms_message_set_buf(m,b) do { (b)->ref_count++; (m)->buffer=(b); (m)->data=(b)->buffer; (m)->size=(b)->size; }while(0) +#define ms_message_unset_buf(m) do { (m)->buffer->ref_count--; (m)->buffer=NULL; (m)->size=0; (m)->data=NULL; } while(0) + +#define ms_message_size(m) (m)->size +void ms_message_destroy(MSMessage *m); + +MSMessage * ms_message_dup(MSMessage *m); + +/* allocate a single message without buffer */ +MSMessage *ms_message_alloc(); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscodec.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscodec.c new file mode 100644 index 00000000..dafa1e87 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscodec.c @@ -0,0 +1,250 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "mscodec.h" +#include "msMUlawdec.h" + +#ifdef TRUESPEECH +extern MSCodecInfo TrueSpeechinfo; +#endif + +#ifdef VIDEO_ENABLED +extern void ms_AVCodec_init(); +#endif + +#define UDP_HDR_SZ 8 +#define RTP_HDR_SZ 12 +#define IP4_HDR_SZ 20 /*20 is the minimum, but there may be some options*/ + + + + +/* register all statically linked codecs */ +void ms_codec_register_all() +{ +/*// ms_filter_register(MS_FILTER_INFO(&GSMinfo)); + // ms_filter_register(MS_FILTER_INFO(&LPC10info));*/ + ms_filter_register(MS_FILTER_INFO(&MULAWinfo)); +#ifdef TRUESPEECH + ms_filter_register(MS_FILTER_INFO(&TrueSpeechinfo)); +#endif +#ifdef VIDEO_ENABLED + ms_AVCodec_init(); +#endif + +} + +/* returns a list of MSCodecInfo */ +GList * ms_codec_get_all_audio() +{ + GList *audio_codecs=NULL; + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if (info->type==MS_FILTER_AUDIO_CODEC){ + audio_codecs=g_list_append(audio_codecs,info); + } + elem=g_list_next(elem); + } + return audio_codecs; +} + + +MSCodecInfo * ms_audio_codec_info_get(gchar *name) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ( (info->type==MS_FILTER_AUDIO_CODEC) ){ + MSCodecInfo *codinfo=(MSCodecInfo *)info; + if (strcmp(codinfo->description,name)==0){ + return MS_CODEC_INFO(info); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +MSCodecInfo * ms_video_codec_info_get(gchar *name) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ( (info->type==MS_FILTER_VIDEO_CODEC) ){ + MSCodecInfo *codinfo=(MSCodecInfo *)info; + if (strcmp(codinfo->description,name)==0){ + return MS_CODEC_INFO(info); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +/* returns a list of MSCodecInfo */ +GList * ms_codec_get_all_video() +{ + GList *video_codecs=NULL; + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if (info->type==MS_FILTER_VIDEO_CODEC){ + video_codecs=g_list_append(video_codecs,info); + } + elem=g_list_next(elem); + } + return video_codecs; +} + +MSFilter * ms_encoder_new(gchar *name) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ((info->type==MS_FILTER_AUDIO_CODEC) || (info->type==MS_FILTER_VIDEO_CODEC)){ + MSCodecInfo *codinfo=(MSCodecInfo *)elem->data; + if (strcmp(info->name,name)==0){ + return codinfo->encoder(); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +MSFilter * ms_decoder_new(gchar *name) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ((info->type==MS_FILTER_AUDIO_CODEC) || (info->type==MS_FILTER_VIDEO_CODEC)){ + MSCodecInfo *codinfo=(MSCodecInfo *)elem->data; + if (strcmp(info->name,name)==0){ + return codinfo->decoder(); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +MSFilter * ms_encoder_new_with_pt(gint pt) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ((info->type==MS_FILTER_AUDIO_CODEC) || (info->type==MS_FILTER_VIDEO_CODEC)){ + MSCodecInfo *codinfo=(MSCodecInfo *)elem->data; + if (codinfo->pt==pt){ + return codinfo->encoder(); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +MSFilter * ms_decoder_new_with_pt(gint pt) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ((info->type==MS_FILTER_AUDIO_CODEC) || (info->type==MS_FILTER_VIDEO_CODEC)){ + MSCodecInfo *codinfo=(MSCodecInfo *)elem->data; + if (codinfo->pt==pt){ + return codinfo->decoder(); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +MSFilter * ms_decoder_new_with_string_id(gchar *id) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ((info->type==MS_FILTER_AUDIO_CODEC) || (info->type==MS_FILTER_VIDEO_CODEC)){ + MSCodecInfo *codinfo=(MSCodecInfo *)elem->data; + if (strcasecmp(codinfo->description,id)==0){ + return codinfo->decoder(); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +MSFilter * ms_encoder_new_with_string_id(gchar *id) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ((info->type==MS_FILTER_AUDIO_CODEC) || (info->type==MS_FILTER_VIDEO_CODEC)){ + MSCodecInfo *codinfo=(MSCodecInfo *)elem->data; + if (strcasecmp(codinfo->description,id)==0){ + return codinfo->encoder(); + } + } + elem=g_list_next(elem); + } + return NULL; +} +/* return 0 if codec can be used with bandwidth, -1 else*/ +int ms_codec_is_usable(MSCodecInfo *codec,double bandwidth) +{ + double codec_band; + double npacket; + double packet_size; + + if (((MSFilterInfo*)codec)->type==MS_FILTER_AUDIO_CODEC) + { + /* calculate the total bandwdith needed by codec (including headers for rtp, udp, ip)*/ + /* number of packet per second*/ + npacket=2.0*(double)(codec->rate)/(double)(codec->fr_size); + packet_size=(double)(codec->dt_size)+UDP_HDR_SZ+RTP_HDR_SZ+IP4_HDR_SZ; + codec_band=packet_size*8.0*npacket; + } + else return -1; + return(codec_band +#include +#include +#include +#include +#include + +static MSCopyClass *ms_copy_class=NULL; + +MSFilter * ms_copy_new(void) +{ + MSCopy *r; + + r=g_new(MSCopy,1); + ms_copy_init(r); + if (ms_copy_class==NULL) + { + ms_copy_class=g_new(MSCopyClass,1); + ms_copy_class_init(ms_copy_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_copy_class); + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_copy_init(MSCopy *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->outfifos=r->f_outputs; + MS_FILTER(r)->r_mingran=MSCOPY_DEF_GRAN; + memset(r->f_inputs,0,sizeof(MSFifo*)*MSCOPY_MAX_INPUTS); + memset(r->f_outputs,0,sizeof(MSFifo*)*MSCOPY_MAX_INPUTS); +} + +void ms_copy_class_init(MSCopyClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"fifocopier"); + MS_FILTER_CLASS(klass)->max_finputs=MSCOPY_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSCOPY_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=MSCOPY_DEF_GRAN; + MS_FILTER_CLASS(klass)->w_maxgran=MSCOPY_DEF_GRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_copy_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_copy_process; +} + +void ms_copy_process(MSCopy *r) +{ + MSFifo *fi,*fo; + int err1; + gint gran=MS_FILTER(r)->klass->r_maxgran; + void *s,*d; + + /* process output fifos, but there is only one for this class of filter*/ + + fi=r->f_inputs[0]; + fo=r->f_outputs[0]; + if (fi!=NULL) + { + err1=ms_fifo_get_read_ptr(fi,gran,&s); + if (err1>0) err1=ms_fifo_get_write_ptr(fo,gran,&d); + if (err1>0) + { + memcpy(d,s,gran); + } + } +} + +void ms_copy_destroy( MSCopy *obj) +{ + g_free(obj); +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscopy.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscopy.h new file mode 100644 index 00000000..2b5749b9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscopy.h @@ -0,0 +1,61 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSCOPY_H +#define MSCOPY_H + +#include "msfilter.h" + + +/*this is the class that implements a copy filter*/ + +#define MSCOPY_MAX_INPUTS 1 /* max output per filter*/ + +#define MSCOPY_DEF_GRAN 64 /* the default granularity*/ + +typedef struct _MSCopy +{ + /* the MSCopy derivates from MSFilter, so the MSFilter object MUST be the first of the MSCopy object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSCOPY_MAX_INPUTS]; + MSFifo *f_outputs[MSCOPY_MAX_INPUTS]; +} MSCopy; + +typedef struct _MSCopyClass +{ + /* the MSCopy derivates from MSFilter, so the MSFilter class MUST be the first of the MSCopy class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSCopyClass; + +/* PUBLIC */ +#define MS_COPY(filter) ((MSCopy*)(filter)) +#define MS_COPY_CLASS(klass) ((MSCopyClass*)(klass)) +MSFilter * ms_copy_new(void); + +/* FOR INTERNAL USE*/ +void ms_copy_init(MSCopy *r); +void ms_copy_class_init(MSCopyClass *klass); +void ms_copy_destroy( MSCopy *obj); +void ms_copy_process(MSCopy *r); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.c new file mode 100644 index 00000000..692bbb7b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.c @@ -0,0 +1,94 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a dispatcher of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "msfdispatcher.h" + +static MSFdispatcherClass *ms_fdispatcher_class=NULL; + +MSFilter * ms_fdispatcher_new(void) +{ + MSFdispatcher *obj; + obj=g_malloc(sizeof(MSFdispatcher)); + if (ms_fdispatcher_class==NULL){ + ms_fdispatcher_class=g_malloc(sizeof(MSFdispatcherClass)); + ms_fdispatcher_class_init(ms_fdispatcher_class); + } + MS_FILTER(obj)->klass=MS_FILTER_CLASS(ms_fdispatcher_class); + ms_fdispatcher_init(obj); + return MS_FILTER(obj); +} + + +void ms_fdispatcher_init(MSFdispatcher *obj) +{ + ms_filter_init(MS_FILTER(obj)); + MS_FILTER(obj)->infifos=obj->f_inputs; + MS_FILTER(obj)->outfifos=obj->f_outputs; + MS_FILTER(obj)->r_mingran=MS_FDISPATCHER_DEF_GRAN; + memset(obj->f_inputs,0,sizeof(MSFifo*)*MS_FDISPATCHER_MAX_INPUTS); + memset(obj->f_outputs,0,sizeof(MSFifo*)*MS_FDISPATCHER_MAX_OUTPUTS); +} + + + +void ms_fdispatcher_class_init(MSFdispatcherClass *klass) +{ + MSFilterClass *parent_class=MS_FILTER_CLASS(klass); + ms_filter_class_init(parent_class); + ms_filter_class_set_name(parent_class,"fdispatcher"); + parent_class->max_finputs=MS_FDISPATCHER_MAX_INPUTS; + parent_class->max_foutputs=MS_FDISPATCHER_MAX_OUTPUTS; + parent_class->r_maxgran=MS_FDISPATCHER_DEF_GRAN; + parent_class->w_maxgran=MS_FDISPATCHER_DEF_GRAN; + parent_class->destroy=(MSFilterDestroyFunc)ms_fdispatcher_destroy; + parent_class->process=(MSFilterProcessFunc)ms_fdispatcher_process; +} + + +void ms_fdispatcher_destroy( MSFdispatcher *obj) +{ + g_free(obj); +} + +void ms_fdispatcher_process(MSFdispatcher *obj) +{ + gint i; + MSFifo *inf=obj->f_inputs[0]; + + + if (inf!=NULL){ + void *s,*d; + /* dispatch fifos */ + while ( ms_fifo_get_read_ptr(inf,MS_FDISPATCHER_DEF_GRAN,&s) >0 ){ + for (i=0;if_outputs[i]; + + if (outf!=NULL) + { + ms_fifo_get_write_ptr(outf,MS_FDISPATCHER_DEF_GRAN,&d); + if (d!=NULL) memcpy(d,s,MS_FDISPATCHER_DEF_GRAN); + } + } + } + } +} + + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.h new file mode 100644 index 00000000..b1b457df --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.h @@ -0,0 +1,61 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a dispatcher of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSFDISPATCHER_H +#define MSFDISPATCHER_H + +#include "msfilter.h" + + +/*this is the class that implements a fdispatcher filter*/ + +#define MS_FDISPATCHER_MAX_INPUTS 1 +#define MS_FDISPATCHER_MAX_OUTPUTS 5 +#define MS_FDISPATCHER_DEF_GRAN 64 /* the default granularity*/ + +typedef struct _MSFdispatcher +{ + /* the MSFdispatcher derivates from MSFilter, so the MSFilter object MUST be the first of the MSFdispatcher object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MS_FDISPATCHER_MAX_INPUTS]; + MSFifo *f_outputs[MS_FDISPATCHER_MAX_OUTPUTS]; +} MSFdispatcher; + +typedef struct _MSFdispatcherClass +{ + /* the MSFdispatcher derivates from MSFilter, so the MSFilter class MUST be the first of the MSFdispatcher class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSFdispatcherClass; + +/* PUBLIC */ +#define MS_FDISPATCHER(filter) ((MSFdispatcher*)(filter)) +#define MS_FDISPATCHER_CLASS(klass) ((MSFdispatcherClass*)(klass)) +MSFilter * ms_fdispatcher_new(void); + +/* FOR INTERNAL USE*/ +void ms_fdispatcher_init(MSFdispatcher *r); +void ms_fdispatcher_class_init(MSFdispatcherClass *klass); +void ms_fdispatcher_destroy( MSFdispatcher *obj); +void ms_fdispatcher_process(MSFdispatcher *r); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.c new file mode 100644 index 00000000..7e783c24 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.c @@ -0,0 +1,168 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include "msutils.h" +#include "msfifo.h" + +MSFifo * ms_fifo_new(MSBuffer *buf, gint r_gran, gint w_gran, gint r_offset, gint w_offset) +{ + MSFifo *fifo; + gint saved_offset=MAX(r_gran+r_offset,w_offset); + + g_return_val_if_fail(saved_offset<=(buf->size),NULL); + fifo=g_malloc(sizeof(MSFifo)); + fifo->buffer=buf; + fifo->r_gran=r_gran; + fifo->w_gran=w_gran; + fifo->begin=fifo->wr_ptr=fifo->rd_ptr=buf->buffer+saved_offset; + fifo->readsize=0; + fifo->size=fifo->writesize=buf->size-saved_offset; + fifo->saved_offset= saved_offset; + fifo->r_end=fifo->w_end=buf->buffer+buf->size; + fifo->pre_end=fifo->w_end-saved_offset; + buf->ref_count++; + fifo->prev_data=NULL; + fifo->next_data=NULL; + ms_trace("fifo base=%x, begin=%x, end=%x, saved_offset=%i, size=%i" + ,fifo->buffer->buffer,fifo->begin,fifo->w_end,fifo->saved_offset,fifo->size); + return(fifo); +} + +MSFifo * ms_fifo_new_with_buffer(gint r_gran, gint w_gran, gint r_offset, gint w_offset, + gint min_fifo_size) +{ + MSFifo *fifo; + MSBuffer *buf; + gint saved_offset=MAX(r_gran+r_offset,w_offset); + gint fifo_size; + gint tmp; + if (min_fifo_size==0) min_fifo_size=w_gran; + + /* we must allocate a fifo with a size multiple of min_fifo_size, + with a saved_offset */ + if (min_fifo_size>MS_BUFFER_LARGE) + fifo_size=(min_fifo_size) + saved_offset; + else fifo_size=(6*min_fifo_size) + saved_offset; + buf=ms_buffer_new(fifo_size); + fifo=ms_fifo_new(buf,r_gran,w_gran,r_offset,w_offset); + ms_trace("fifo_size=%i",fifo_size); + return(fifo); +} + +void ms_fifo_destroy( MSFifo *fifo) +{ + g_free(fifo); +} + +void ms_fifo_destroy_with_buffer(MSFifo *fifo) +{ + ms_buffer_destroy(fifo->buffer); + ms_fifo_destroy(fifo); +} + +gint ms_fifo_get_read_ptr(MSFifo *fifo, gint bsize, void **ret_ptr) +{ + gchar *rnext; + + *ret_ptr=NULL; + /* //ms_trace("ms_fifo_get_read_ptr: entering.");*/ + g_return_val_if_fail(bsize<=fifo->r_gran,-EINVAL); + + if (bsize>fifo->readsize) + { + ms_trace("Not enough data: bsize=%i, readsize=%i",bsize,fifo->readsize); + return (-ENODATA); + } + + rnext=fifo->rd_ptr+bsize; + if (rnext<=fifo->r_end){ + + *ret_ptr=fifo->rd_ptr; + fifo->rd_ptr=rnext; + }else{ + int unread=fifo->r_end-fifo->rd_ptr; + *ret_ptr=fifo->begin-unread; + memcpy(fifo->buffer->buffer,fifo->r_end-fifo->saved_offset,fifo->saved_offset); + fifo->rd_ptr=(char*)(*ret_ptr) + bsize; + fifo->r_end=fifo->w_end; /* this is important ! */ + ms_trace("moving read ptr to %x",fifo->rd_ptr); + + } + /* update write size*/ + fifo->writesize+=bsize; + fifo->readsize-=bsize; + return bsize; +} + + +void ms_fifo_update_write_ptr(MSFifo *fifo, gint written){ + gint reserved=fifo->wr_ptr-fifo->prev_wr_ptr; + gint unwritten; + g_return_if_fail(reserved>=0); + unwritten=reserved-written; + g_return_if_fail(unwritten>=0); + /* fix readsize and writesize */ + fifo->readsize-=unwritten; + fifo->writesize+=unwritten; + fifo->wr_ptr+=written; +} + +gint ms_fifo_get_write_ptr(MSFifo *fifo, gint bsize, void **ret_ptr) +{ + gchar *wnext; + + *ret_ptr=NULL; + /* //ms_trace("ms_fifo_get_write_ptr: Entering.");*/ + g_return_val_if_fail(bsize<=fifo->w_gran,-EINVAL); + if (bsize>fifo->writesize) + { + ms_trace("Not enough space: bsize=%i, writesize=%i",bsize,fifo->writesize); + *ret_ptr=NULL; + return(-ENODATA); + } + wnext=fifo->wr_ptr+bsize; + if (wnext<=fifo->w_end){ + *ret_ptr=fifo->wr_ptr; + fifo->wr_ptr=wnext; + }else{ + *ret_ptr=fifo->begin; + fifo->r_end=fifo->wr_ptr; + fifo->wr_ptr=fifo->begin+bsize; + ms_trace("moving write ptr to %x",fifo->wr_ptr); + } + fifo->prev_wr_ptr=*ret_ptr; + /* update readsize*/ + fifo->readsize+=bsize; + fifo->writesize-=bsize; + /* //ms_trace("ms_fifo_get_write_ptr: readsize=%i, writesize=%i",fifo->readsize,fifo->writesize);*/ + return bsize; +} + +gint ms_fifo_get_rw_ptr(MSFifo *f1,void **p1,gint minsize1, + MSFifo *f2,void **p2,gint minsize2) +{ + gint rbsize,wbsize; + + rbsize=MIN(f1->readsize,(f1->pre_end-f1->rd_ptr)); + wbsize=MIN(f2->writesize,(f2->w_end-f2->wr_ptr)); + return 0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.h new file mode 100644 index 00000000..fde1bece --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.h @@ -0,0 +1,73 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifdef HAVE_GLIB +#include +#else +#include "glist.h" +#endif +#include "msbuffer.h" + +typedef struct _MSFifo +{ + gint r_gran; /*maximum granularity for reading*/ + gint w_gran; /*maximum granularity for writing*/ + gchar * rd_ptr; /* read pointer on the position where there is something to read on the MSBuffer */ + guint32 readsize; + gchar * wr_ptr; + gchar * prev_wr_ptr; + guint32 writesize; /* write pointer on the position where it is possible to write on the MSBuffer */ + gchar * begin; /* rd_ptr et wr_ptr must all be >=begin*/ + guint32 size; /* the length of the fifo, but this may not be equal to buffer->size*/ + guint32 saved_offset; + gchar * pre_end; /* the end of the buffer that is copied at the begginning when we wrap around*/ + gchar * w_end; /* when a wr ptr is expected to exceed end_offset, + it must be wrapped around to go at the beginning of the buffer. This is the end of the buffer*/ + gchar * r_end; /* this is the last position written at the end of the fifo. If a read ptr is expected to + exceed this pointer, it must be put at the begginning of the buffer */ + void *prev_data; /*user data, usually the writing MSFilter*/ + void *next_data; /* user data, usually the reading MSFilter */ + MSBuffer *buffer; +} MSFifo; + +/* constructor*/ +/* r_gran: max granularity for reading (in number of bytes)*/ +/* w_gran: max granularity for writing (in number of bytes)*/ +/* r_offset: number of bytes that are kept available behind read pointer (for recursive filters)*/ +/* w_offset: number of bytes that are kept available behind write pointer (for recursive filters)*/ +/* buf is a MSBuffer that should be compatible with the above parameter*/ +MSFifo * ms_fifo_new(MSBuffer *buf, gint r_gran, gint w_gran, gint r_offset, gint w_offset); + +/*does the same that ms_fifo_new(), but also allocate a compatible buffer automatically*/ +MSFifo * ms_fifo_new_with_buffer(gint r_gran, gint w_gran, gint r_offset, gint w_offset, gint min_buffer_size); + +void ms_fifo_destroy( MSFifo *fifo); + +void ms_fifo_destroy_with_buffer(MSFifo *fifo); + +/* get data to read */ +gint ms_fifo_get_read_ptr(MSFifo *fifo, gint bsize, void **ret_ptr); + +/* get a buffer to write*/ +gint ms_fifo_get_write_ptr(MSFifo *fifo, gint bsize, void **ret_ptr); + +/* in case the buffer got by ms_fifo_get_write_ptr() could not be filled completely, you must +tell it by using this function */ +void ms_fifo_update_write_ptr(MSFifo *fifo, gint written); diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfilter.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfilter.c new file mode 100644 index 00000000..c67e9f0e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfilter.c @@ -0,0 +1,537 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include +#include "msfilter.h" + + + +void ms_filter_init(MSFilter *filter) +{ + filter->finputs=0; + filter->foutputs=0; + filter->qinputs=0; + filter->qoutputs=0; + filter->infifos=NULL; + filter->outfifos=NULL; + filter->inqueues=NULL; + filter->outqueues=NULL; + filter->lock=g_mutex_new(); + filter->min_fifo_size=0x7fff; + filter->notify_event=NULL; + filter->userdata=NULL; +} + +void ms_filter_uninit(MSFilter *filter) +{ + g_mutex_free(filter->lock); +} + +void ms_filter_class_init(MSFilterClass *filterclass) +{ + filterclass->name=NULL; + filterclass->max_finputs=0; + filterclass->max_foutputs=0; + filterclass->max_qinputs=0; + filterclass->max_qoutputs=0; + filterclass->r_maxgran=0; + filterclass->w_maxgran=0; + filterclass->r_offset=0; + filterclass->w_offset=0; + filterclass->set_property=NULL; + filterclass->get_property=NULL; + filterclass->setup=NULL; + filterclass->unsetup=NULL; + filterclass->process=NULL; + filterclass->destroy=NULL; + filterclass->attributes=0; + filterclass->ref_count=0; +} + +/* find output queue */ +gint find_oq(MSFilter *m1,MSQueue *oq) +{ + gint i; + + for (i=0;imax_qoutputs;i++){ + if (m1->outqueues[i]==oq) return i; + } + + return -1; +} + +/* find input queue */ +gint find_iq(MSFilter *m1,MSQueue *iq) +{ + gint i; + for (i=0;imax_qinputs;i++){ + if (m1->inqueues[i]==iq) return i; + } + return -1; +} + +/* find output fifo */ +gint find_of(MSFilter *m1,MSFifo *of) +{ + gint i; + for (i=0;imax_foutputs;i++){ + if (m1->outfifos[i]==of) return i; + } + + return -1; +} + +/* find input fifo */ +gint find_if(MSFilter *m1,MSFifo *inf) +{ + gint i; + + for (i=0;imax_finputs;i++){ + if (m1->infifos[i]==inf) return i; + } + + return -1; +} + +#define find_free_iq(_m1) find_iq(_m1,NULL) +#define find_free_oq(_m1) find_oq(_m1,NULL) +#define find_free_if(_m1) find_if(_m1,NULL) +#define find_free_of(_m1) find_of(_m1,NULL) + +int ms_filter_add_link(MSFilter *m1, MSFilter *m2) +{ + gint m1_q=-1; + gint m1_f=-1; + gint m2_q=-1; + gint m2_f=-1; + /* determine the type of link we can add */ + m1_q=find_free_oq(m1); + m1_f=find_free_of(m1); + m2_q=find_free_iq(m2); + m2_f=find_free_if(m2); + if ((m1_q!=-1) && (m2_q!=-1)){ + /* link with queues */ + ms_trace("m1_q=%i , m2_q=%i",m1_q,m2_q); + return ms_filter_link(m1,m1_q,m2,m2_q,LINK_QUEUE); + } + if ((m1_f!=-1) && (m2_f!=-1)){ + /* link with queues */ + ms_trace("m1_f=%i , m2_f=%i",m1_f,m2_f); + return ms_filter_link(m1,m1_f,m2,m2_f,LINK_FIFO); + } + g_warning("ms_filter_add_link: could not link."); + return -1; +} +/** + * ms_filter_link: + * @m1: A #MSFilter object. + * @pin1: The pin number on @m1. + * @m2: A #MSFilter object. + * @pin2: The pin number on @m2. + * @linktype: Type of connection, it may be #LINK_QUEUE, #LINK_FIFOS. + * + * This function links two MSFilter object between them. It must be used to make chains of filters. + * All data outgoing from pin1 of m1 will go to the input pin2 of m2. + * The way to communicate can be fifos or queues, depending of the nature of the filters. Filters can have + * multiple queue pins and multiple fifo pins, but most of them have only one queue input/output or only one + * fifo input/output. Fifos are usally used by filters doing audio processing, while queues are used by filters doing + * video processing. + * + * Returns: 0 if successfull, a negative value reprensenting the errno.h error. + */ +int ms_filter_link(MSFilter *m1, gint pin1, MSFilter *m2,gint pin2, int linktype) +{ + MSQueue *q; + MSFifo *fifo; + + g_message("ms_filter_add_link: %s,%i -> %s,%i",m1->klass->name,pin1,m2->klass->name,pin2); + switch(linktype) + { + case LINK_QUEUE: + /* Are filter m1 and m2 able to accept more queues connections ?*/ + g_return_val_if_fail(m1->qoutputsmax_qoutputs,-EMLINK); + g_return_val_if_fail(m2->qinputsmax_qinputs,-EMLINK); + /* Are filter m1 and m2 valid with their inputs and outputs ?*/ + g_return_val_if_fail(m1->outqueues!=NULL,-EFAULT); + g_return_val_if_fail(m2->inqueues!=NULL,-EFAULT); + /* are the requested pins exists ?*/ + g_return_val_if_fail(pin1max_qoutputs,-EINVAL); + g_return_val_if_fail(pin2max_qinputs,-EINVAL); + /* are the requested pins free ?*/ + g_return_val_if_fail(m1->outqueues[pin1]==NULL,-EBUSY); + g_return_val_if_fail(m2->inqueues[pin2]==NULL,-EBUSY); + + q=ms_queue_new(); + m1->outqueues[pin1]=m2->inqueues[pin2]=q; + m1->qoutputs++; + m2->qinputs++; + q->prev_data=(void*)m1; + q->next_data=(void*)m2; + break; + case LINK_FIFO: + /* Are filter m1 and m2 able to accept more fifo connections ?*/ + g_return_val_if_fail(m1->foutputsmax_foutputs,-EMLINK); + g_return_val_if_fail(m2->finputsmax_finputs,-EMLINK); + /* Are filter m1 and m2 valid with their inputs and outputs ?*/ + g_return_val_if_fail(m1->outfifos!=NULL,-EFAULT); + g_return_val_if_fail(m2->infifos!=NULL,-EFAULT); + /* are the requested pins exists ?*/ + g_return_val_if_fail(pin1max_foutputs,-EINVAL); + g_return_val_if_fail(pin2max_finputs,-EINVAL); + /* are the requested pins free ?*/ + g_return_val_if_fail(m1->outfifos[pin1]==NULL,-EBUSY); + g_return_val_if_fail(m2->infifos[pin2]==NULL,-EBUSY); + + if (MS_FILTER_GET_CLASS(m1)->attributes & FILTER_IS_SOURCE) + { + /* configure min_fifo_size */ + fifo=ms_fifo_new_with_buffer(MS_FILTER_GET_CLASS(m2)->r_maxgran, + MS_FILTER_GET_CLASS(m1)->w_maxgran, + MS_FILTER_GET_CLASS(m2)->r_offset, + MS_FILTER_GET_CLASS(m1)->w_offset, + MS_FILTER_GET_CLASS(m1)->w_maxgran); + m2->min_fifo_size=MS_FILTER_GET_CLASS(m1)->w_maxgran; + } + else + { + gint next_size; + ms_trace("ms_filter_add_link: min_fifo_size=%i",m1->min_fifo_size); + fifo=ms_fifo_new_with_buffer(MS_FILTER_GET_CLASS(m2)->r_maxgran, + MS_FILTER_GET_CLASS(m1)->w_maxgran, + MS_FILTER_GET_CLASS(m2)->r_offset, + MS_FILTER_GET_CLASS(m1)->w_offset, + m1->min_fifo_size); + if (MS_FILTER_GET_CLASS(m2)->r_maxgran>0){ + next_size=(m1->min_fifo_size* + (MS_FILTER_GET_CLASS(m2)->w_maxgran)) / + (MS_FILTER_GET_CLASS(m2)->r_maxgran); + }else next_size=m1->min_fifo_size; + ms_trace("ms_filter_add_link: next_size=%i",next_size); + m2->min_fifo_size=next_size; + } + + + m1->outfifos[pin1]=m2->infifos[pin2]=fifo; + m1->foutputs++; + m2->finputs++; + fifo->prev_data=(void*)m1; + fifo->next_data=(void*)m2; + break; + } + return 0; +} +/** + * ms_filter_unlink: + * @m1: A #MSFilter object. + * @pin1: The pin number on @m1. + * @m2: A #MSFilter object. + * @pin2: The pin number on @m2. + * @linktype: Type of connection, it may be #LINK_QUEUE, #LINK_FIFOS. + * + * Unlink @pin1 of filter @m1 from @pin2 of filter @m2. @linktype specifies what type of connection is removed. + * + * Returns: 0 if successfull, a negative value reprensenting the errno.h error. + */ +int ms_filter_unlink(MSFilter *m1, gint pin1, MSFilter *m2,gint pin2,gint linktype) +{ + switch(linktype) + { + case LINK_QUEUE: + /* Are filter m1 and m2 valid with their inputs and outputs ?*/ + g_return_val_if_fail(m1->outqueues!=NULL,-EFAULT); + g_return_val_if_fail(m2->inqueues!=NULL,-EFAULT); + /* are the requested pins exists ?*/ + g_return_val_if_fail(pin1max_qoutputs,-EINVAL); + g_return_val_if_fail(pin2max_qinputs,-EINVAL); + /* are the requested pins busy ?*/ + g_return_val_if_fail(m1->outqueues[pin1]!=NULL,-ENOENT); + g_return_val_if_fail(m2->inqueues[pin2]!=NULL,-ENOENT); + /* are the two pins connected together ?*/ + g_return_val_if_fail(m1->outqueues[pin1]==m2->inqueues[pin2],-EINVAL); + + ms_queue_destroy(m1->outqueues[pin1]); + m1->outqueues[pin1]=m2->inqueues[pin2]=NULL; + m1->qoutputs--; + m2->qinputs--; + + break; + case LINK_FIFO: + /* Are filter m1 and m2 valid with their inputs and outputs ?*/ + g_return_val_if_fail(m1->outfifos!=NULL,-EFAULT); + g_return_val_if_fail(m2->infifos!=NULL,-EFAULT); + /* are the requested pins exists ?*/ + g_return_val_if_fail(pin1max_foutputs,-EINVAL); + g_return_val_if_fail(pin2max_finputs,-EINVAL); + /* are the requested pins busy ?*/ + g_return_val_if_fail(m1->outfifos[pin1]!=NULL,-ENOENT); + g_return_val_if_fail(m2->infifos[pin2]!=NULL,-ENOENT); + /* are the two pins connected together ?*/ + g_return_val_if_fail(m1->outfifos[pin1]==m2->infifos[pin2],-EINVAL); + ms_fifo_destroy_with_buffer(m1->outfifos[pin1]); + m1->outfifos[pin1]=m2->infifos[pin2]=NULL; + m1->foutputs--; + m2->finputs--; + break; + } + return 0; +} + +/** + *ms_filter_remove_links: + *@m1: a filter + *@m2: another filter. + * + * Removes all links between m1 and m2. + * + *Returns: 0 if one more link have been removed, -1 if not. +**/ +gint ms_filter_remove_links(MSFilter *m1, MSFilter *m2) +{ + int i,j; + int removed=-1; + MSQueue *qo; + MSFifo *fo; + /* takes all outputs of m1, and removes the one that goes to m2 */ + if (m1->outqueues!=NULL){ + for (i=0;imax_qoutputs;i++) + { + qo=m1->outqueues[i]; + if (qo!=NULL){ + MSFilter *rmf; + /* test if the queue connects to m2 */ + rmf=(MSFilter*)qo->next_data; + if (rmf==m2){ + j=find_iq(rmf,qo); + if (j==-1) g_error("Could not find input queue: impossible case."); + ms_filter_unlink(m1,i,m2,j,LINK_QUEUE); + removed=0; + } + } + } + } + if (m1->outfifos!=NULL){ + for (i=0;imax_foutputs;i++) + { + fo=m1->outfifos[i]; + if (fo!=NULL){ + MSFilter *rmf; + /* test if the queue connects to m2 */ + rmf=(MSFilter*)fo->next_data; + if (rmf==m2){ + j=find_if(rmf,fo); + if (j==-1) g_error("Could not find input fifo: impossible case."); + ms_filter_unlink(m1,i,m2,j,LINK_FIFO); + removed=0; + } + } + } + } + return removed; +} + +/** + * ms_filter_fifos_have_data: + * @f: a #MSFilter object. + * + * Tells if the filter has enough data in its input fifos in order to be executed succesfully. + * + * Returns: 1 if it can be executed, 0 else. + */ +gint ms_filter_fifos_have_data(MSFilter *f) +{ + gint i,j; + gint max_inputs=f->klass->max_finputs; + gint con_inputs=f->finputs; + MSFifo *fifo; + /* test fifos */ + for(i=0,j=0; (iinfifos[i]; + if (fifo!=NULL) + { + j++; + if (fifo->readsize==0) return 0; + if (fifo->readsize>=f->r_mingran) return 1; + } + } + return 0; +} + +/** + * ms_filter_queues_have_data: + * @f: a #MSFilter object. + * + * Tells if the filter has enough data in its input queues in order to be executed succesfully. + * + * Returns: 1 if it can be executed, 0 else. + */ +gint ms_filter_queues_have_data(MSFilter *f) +{ + gint i,j; + gint max_inputs=f->klass->max_qinputs; + gint con_inputs=f->qinputs; + MSQueue *q; + /* test queues */ + for(i=0,j=0; (iinqueues[i]; + if (q!=NULL) + { + j++; + if (ms_queue_can_get(q)) return 1; + } + } + return 0; +} + + + +void ms_filter_destroy(MSFilter *f) +{ + /* first check if the filter is disconnected from any others */ + g_return_if_fail(f->finputs==0); + g_return_if_fail(f->foutputs==0); + g_return_if_fail(f->qinputs==0); + g_return_if_fail(f->qoutputs==0); + f->klass->destroy(f); +} + +GList *filter_list=NULL; + +void ms_filter_register(MSFilterInfo *info) +{ + gpointer tmp; + tmp=g_list_find(filter_list,info); + if (tmp==NULL) filter_list=g_list_append(filter_list,(gpointer)info); +} + +void ms_filter_unregister(MSFilterInfo *info) +{ + filter_list=g_list_remove(filter_list,(gpointer)info); +} + +static gint compare_names(gpointer info, gpointer name) +{ + MSFilterInfo *i=(MSFilterInfo*) info; + return (strcmp(i->name,name)); +} + +MSFilterInfo * ms_filter_get_by_name(const gchar *name) +{ + GList *elem=g_list_find_custom(filter_list, + (gpointer)name,(GCompareFunc)compare_names); + if (elem!=NULL){ + return (MSFilterInfo*)elem->data; + } + return NULL; +} + + + +MSFilter * ms_filter_new_with_name(const gchar *name) +{ + MSFilterInfo *info=ms_filter_get_by_name(name); + if (info!=NULL) return info->constructor(); + g_warning("ms_filter_new_with_name: no filter named %s found.",name); + return NULL; +} + + +/* find the first codec in the left part of the stream */ +MSFilter * ms_filter_search_upstream_by_type(MSFilter *f,MSFilterType type) +{ + MSFilter *tmp=f; + MSFilterInfo *info; + + if ((tmp->infifos!=NULL) && (tmp->infifos[0]!=NULL)){ + tmp=(MSFilter*) tmp->infifos[0]->prev_data; + while(1){ + info=MS_FILTER_GET_CLASS(tmp)->info; + if (info!=NULL){ + if ( (info->type==type) ){ + return tmp; + } + } + if ((tmp->infifos!=NULL) && (tmp->infifos[0]!=NULL)) + tmp=(MSFilter*) tmp->infifos[0]->prev_data; + else break; + } + } + tmp=f; + if ((tmp->inqueues!=NULL) && (tmp->inqueues[0]!=NULL)){ + tmp=(MSFilter*) tmp->inqueues[0]->prev_data; + while(1){ + + info=MS_FILTER_GET_CLASS(tmp)->info; + if (info!=NULL){ + if ( (info->type==type)){ + return tmp; + } + }else g_warning("ms_filter_search_upstream_by_type: filter %s has no info." + ,MS_FILTER_GET_CLASS(tmp)->name); + if ((tmp->inqueues!=NULL) && (tmp->inqueues[0]!=NULL)) + tmp=(MSFilter*) tmp->inqueues[0]->prev_data; + else break; + } + } + return NULL; +} + + +int ms_filter_set_property(MSFilter *f, MSFilterProperty prop,void *value) +{ + if (f->klass->set_property!=NULL){ + return f->klass->set_property(f,prop,value); + } + return 0; +} + +int ms_filter_get_property(MSFilter *f, MSFilterProperty prop,void *value) +{ + if (f->klass->get_property!=NULL){ + return f->klass->get_property(f,prop,value); + } + return -1; +} + +void ms_filter_set_notify_func(MSFilter* filter,MSFilterNotifyFunc func, gpointer userdata) +{ + filter->notify_event=func; + filter->userdata=userdata; +} + +void ms_filter_notify_event(MSFilter *filter,gint event, gpointer arg) +{ + if (filter->notify_event!=NULL){ + filter->notify_event(filter,event,arg,filter->userdata); + } +} + +void swap_buffer(gchar *buffer, gint len) +{ + int i; + gchar tmp; + for (i=0;i + +#ifdef HAVE_GLIB +#include +#include +#else +#undef VERSION +#undef PACKAGE +#include +#endif + +#include +#include "msutils.h" +#include "msfifo.h" +#include "msqueue.h" + +struct _MSFilter; +/*this is the abstract object and class for all filter types*/ +typedef gint (*MSFilterNotifyFunc)(struct _MSFilter*, gint event, gpointer arg, gpointer userdata); + +struct _MSFilter +{ + struct _MSFilterClass *klass; + GMutex *lock; + guchar finputs; /* number of connected fifo inputs*/ + guchar foutputs; /* number of connected fifo outputs*/ + guchar qinputs; /* number of connected queue inputs*/ + guchar qoutputs; /* number of connected queue outputs*/ + gint min_fifo_size; /* set when linking*/ + gint r_mingran; /* read minimum granularity (for fifos). + It can be zero so that the filter can accept any size of reading data*/ + MSFifo **infifos; /*pointer to a table of pointer to input fifos*/ + MSFifo **outfifos; /*pointer to a table of pointer to output fifos*/ + MSQueue **inqueues; /*pointer to a table of pointer to input queues*/ + MSQueue **outqueues; /*pointer to a table of pointer to output queues*/ + MSFilterNotifyFunc notify_event; + gpointer userdata; +}; + +typedef struct _MSFilter MSFilter; + +typedef enum{ + MS_FILTER_PROPERTY_FREQ, /* value is int */ + MS_FILTER_PROPERTY_BITRATE, /*value is int */ + MS_FILTER_PROPERTY_CHANNELS,/*value is int */ + MS_FILTER_PROPERTY_FMTP /* value is string */ +}MSFilterProperty; + +#define MS_FILTER_PROPERTY_STRING_MAX_SIZE 256 + +typedef MSFilter * (*MSFilterNewFunc)(void); +typedef void (*MSFilterProcessFunc)(MSFilter *); +typedef void (*MSFilterDestroyFunc)(MSFilter *); +typedef int (*MSFilterPropertyFunc)(MSFilter *,int ,void*); +typedef void (*MSFilterSetupFunc)(MSFilter *, void *); /*2nd arg is the sync */ + +typedef struct _MSFilterClass +{ + struct _MSFilterInfo *info; /*pointer to a filter_info */ + gchar *name; + guchar max_finputs; /* maximum number of fifo inputs*/ + guchar max_foutputs; /* maximum number of fifo outputs*/ + guchar max_qinputs; /* maximum number of queue inputs*/ + guchar max_qoutputs; /* maximum number of queue outputs*/ + gint r_maxgran; /* read maximum granularity (for fifos)*/ + gint w_maxgran; /* write maximum granularity (for fifos)*/ + gint r_offset; /* size of kept samples behind read pointer (for fifos)*/ + gint w_offset; /* size of kept samples behind write pointer (for fifos)*/ + MSFilterPropertyFunc set_property; + MSFilterPropertyFunc get_property; + MSFilterSetupFunc setup; /* called when attaching to sync */ + void (*process)(MSFilter *filter); + MSFilterSetupFunc unsetup; /* called when detaching from sync */ + void (*destroy)(MSFilter *filter); + guint attributes; +#define FILTER_HAS_FIFOS (0x0001) +#define FILTER_HAS_QUEUES (0x0001<<1) +#define FILTER_IS_SOURCE (0x0001<<2) +#define FILTER_IS_SINK (0x0001<<3) +#define FILTER_CAN_SYNC (0x0001<<4) + guint ref_count; /*number of object using the class*/ +} MSFilterClass; + + + +#define MS_FILTER(obj) ((MSFilter*)obj) +#define MS_FILTER_CLASS(klass) ((MSFilterClass*)klass) +#define MS_FILTER_GET_CLASS(obj) ((MSFilterClass*)((MS_FILTER(obj)->klass))) + +void ms_filter_class_init(MSFilterClass *filterclass); +void ms_filter_init(MSFilter *filter); + +#define ms_filter_class_set_attr(filter,flag) ((filter)->attributes|=(flag)) +#define ms_filter_class_unset_attr(filter,flag) ((filter)->attributes&=~(flag)) + +#define ms_filter_class_set_name(__klass,__name) (__klass)->name=g_strdup((__name)) +#define ms_filter_class_set_info(_klass,_info) (_klass)->info=(_info) +/* public*/ + +#define ms_filter_process(filter) ((filter)->klass->process((filter))) + +#define ms_filter_lock(filter) g_mutex_lock((filter)->lock) +#define ms_filter_unlock(filter) g_mutex_unlock((filter)->lock) +/* low level connect functions */ +int ms_filter_link(MSFilter *m1, gint pin1, MSFilter *m2,gint pin2, gint linktype); +int ms_filter_unlink(MSFilter *m1, gint pin1, MSFilter *m2,gint pin2,gint linktype); + +/* high level connect functions */ +int ms_filter_add_link(MSFilter *m1, MSFilter *m2); +int ms_filter_remove_links(MSFilter *m1, MSFilter *m2); + +void ms_filter_set_notify_func(MSFilter* filter,MSFilterNotifyFunc func, gpointer userdata); +void ms_filter_notify_event(MSFilter *filter,gint event, gpointer arg); + +int ms_filter_set_property(MSFilter *f,MSFilterProperty property, void *value); +int ms_filter_get_property(MSFilter *f,MSFilterProperty property, void *value); + + +gint ms_filter_fifos_have_data(MSFilter *f); +gint ms_filter_queues_have_data(MSFilter *f); + +void ms_filter_uninit(MSFilter *obj); +void ms_filter_destroy(MSFilter *f); + +#define ms_filter_get_mingran(f) ((f)->r_mingran) +#define ms_filter_set_mingran(f,gran) ((f)->r_mingran=(gran)) + +#define LINK_DEFAULT 0 +#define LINK_FIFO 1 +#define LINK_QUEUE 2 + + +#define MSFILTER_VERSION(a,b,c) (((a)<<2)|((b)<<1)|(c)) + +enum _MSFilterType +{ + MS_FILTER_DISK_IO, + MS_FILTER_AUDIO_CODEC, + MS_FILTER_VIDEO_CODEC, + MS_FILTER_NET_IO, + MS_FILTER_VIDEO_IO, + MS_FILTER_AUDIO_IO, + MS_FILTER_OTHER +}; + +typedef enum _MSFilterType MSFilterType; + + +/* find the first codec in the left part of the stream */ +MSFilter * ms_filter_search_upstream_by_type(MSFilter *f,MSFilterType type); + +struct _MSFilterInfo +{ + gchar *name; + gint version; + MSFilterType type; + MSFilterNewFunc constructor; + char *description; /*some textual information*/ +}; + +typedef struct _MSFilterInfo MSFilterInfo; + +void ms_filter_register(MSFilterInfo *finfo); +void ms_filter_unregister(MSFilterInfo *finfo); +MSFilterInfo * ms_filter_get_by_name(const gchar *name); + +MSFilter * ms_filter_new_with_name(const gchar *name); + + + +extern GList *filter_list; +#define MS_FILTER_INFO(obj) ((MSFilterInfo*)obj) + +void swap_buffer(gchar *buffer, gint len); + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.c new file mode 100644 index 00000000..b2dfff98 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.c @@ -0,0 +1,194 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#ifdef HAVE_ILBC + + +#include "msilbcdec.h" +#include "msilbcenc.h" +#include "mscodec.h" +#include +#include + + + +extern MSFilter * ms_ilbc_encoder_new(void); + +MSCodecInfo ilbc_info={ + { + "iLBC codec", + 0, + MS_FILTER_AUDIO_CODEC, + ms_ilbc_encoder_new, + "A speech codec suitable for robust voice communication over IP" + }, + ms_ilbc_encoder_new, + ms_ilbc_decoder_new, + 0, /* not applicable, 2 modes */ + 0, /* not applicable, 2 modes */ + 15200, + 8000, + 97, + "iLBC", + 1, + 1, +}; + + +void ms_ilbc_codec_init() +{ + ms_filter_register(MS_FILTER_INFO(&ilbc_info)); +} + + + +static MSILBCDecoderClass *ms_ilbc_decoder_class=NULL; + +MSFilter * ms_ilbc_decoder_new(void) +{ + MSILBCDecoder *r; + + r=g_new(MSILBCDecoder,1); + ms_ilbc_decoder_init(r); + if (ms_ilbc_decoder_class==NULL) + { + ms_ilbc_decoder_class=g_new(MSILBCDecoderClass,1); + ms_ilbc_decoder_class_init(ms_ilbc_decoder_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_ilbc_decoder_class); + return(MS_FILTER(r)); +} + + +int ms_ilbc_decoder_set_property(MSILBCDecoder *obj, MSFilterProperty prop, char *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FMTP: + if (value == NULL) return 0; + if (strstr(value,"ptime=20")!=NULL) obj->ms_per_frame=20; + else if (strstr(value,"ptime=30")!=NULL) obj->ms_per_frame=30; + else g_warning("Unrecognized fmtp parameter for ilbc encoder!"); + break; + } + return 0; +} +int ms_ilbc_decoder_get_property(MSILBCDecoder *obj, MSFilterProperty prop, char *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FMTP: + if (obj->ms_per_frame==20) strncpy(value,"ptime=20",MS_FILTER_PROPERTY_STRING_MAX_SIZE); + if (obj->ms_per_frame==30) strncpy(value,"ptime=30",MS_FILTER_PROPERTY_STRING_MAX_SIZE); + break; + } + return 0; +} + +void ms_ilbc_decoder_setup(MSILBCDecoder *r) +{ + MSFilterClass *klass = NULL; + switch (r->ms_per_frame) { + case 20: + r->samples_per_frame = BLOCKL_20MS; + r->bytes_per_compressed_frame = NO_OF_BYTES_20MS; + break; + case 30: + r->samples_per_frame = BLOCKL_30MS; + r->bytes_per_compressed_frame = NO_OF_BYTES_30MS; + break; + default: + g_error("ms_ilbc_decoder_setup: Bad value for ptime (%i)",r->ms_per_frame); + } + g_message("Using ilbc decoder with %i ms frames mode.",r->ms_per_frame); + initDecode(&r->ilbc_dec, r->ms_per_frame /* ms frames */, /* user enhancer */ 0); +} + + +/* FOR INTERNAL USE*/ +void ms_ilbc_decoder_init(MSILBCDecoder *r) +{ + /* default bitrate */ + r->bitrate = 15200; + r->ms_per_frame = 30; + r->samples_per_frame = BLOCKL_20MS; + r->bytes_per_compressed_frame = NO_OF_BYTES_20MS; + + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->inqueues=r->q_inputs; + MS_FILTER(r)->outfifos=r->f_outputs; + memset(r->q_inputs,0,sizeof(MSFifo*)*MSILBCDECODER_MAX_INPUTS); + memset(r->f_outputs,0,sizeof(MSFifo*)*MSILBCDECODER_MAX_INPUTS); +} + +void ms_ilbc_decoder_class_init(MSILBCDecoderClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"ILBCDec"); + MS_FILTER_CLASS(klass)->max_qinputs=MSILBCDECODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSILBCDECODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->w_maxgran= ILBC_MAX_SAMPLES_PER_FRAME*2; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_ilbc_decoder_destroy; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_ilbc_decoder_set_property; + MS_FILTER_CLASS(klass)->get_property=(MSFilterPropertyFunc)ms_ilbc_decoder_get_property; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_ilbc_decoder_setup; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_ilbc_decoder_process; + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&ilbc_info; +} + +void ms_ilbc_decoder_process(MSILBCDecoder *r) +{ + MSFifo *fo; + MSQueue *qi; + int err1; + void *dst=NULL; + float speech[ILBC_MAX_SAMPLES_PER_FRAME]; + MSMessage *m; + + qi=r->q_inputs[0]; + fo=r->f_outputs[0]; + m=ms_queue_get(qi); + + ms_fifo_get_write_ptr(fo, r->samples_per_frame*2, &dst); + if (dst!=NULL){ + if (m->data!=NULL){ + if (m->sizebytes_per_compressed_frame) { + g_warning("Invalid ilbc frame ?"); + } + iLBC_decode(speech, m->data, &r->ilbc_dec, /* mode */1); + }else{ + iLBC_decode(speech,NULL, &r->ilbc_dec,0); + } + ilbc_write_16bit_samples((gint16*)dst, speech, r->samples_per_frame); + } + ms_message_destroy(m); +} + +void ms_ilbc_decoder_uninit(MSILBCDecoder *obj) +{ +} + +void ms_ilbc_decoder_destroy( MSILBCDecoder *obj) +{ + ms_ilbc_decoder_uninit(obj); + g_free(obj); +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.h new file mode 100644 index 00000000..c219aabe --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.h @@ -0,0 +1,72 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSILBCDECODER_H +#define MSILBCDECODER_H + +#include +#include +#include + +/*this is the class that implements a ILBCdecoder filter*/ + +#define MSILBCDECODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSILBCDecoder +{ + /* the MSILBCDecoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSILBCDecoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSQueue *q_inputs[MSILBCDECODER_MAX_INPUTS]; + MSFifo *f_outputs[MSILBCDECODER_MAX_INPUTS]; + iLBC_Dec_Inst_t ilbc_dec; + int bitrate; + int ms_per_frame; + int samples_per_frame; + int bytes_per_compressed_frame; +} MSILBCDecoder; + +typedef struct _MSILBCDecoderClass +{ + /* the MSILBCDecoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSILBCDecoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSILBCDecoderClass; + +/* PUBLIC */ + +/* call this before if don't load the plugin dynamically */ +void ms_ilbc_codec_init(); + +#define MS_ILBCDECODER(filter) ((MSILBCDecoder*)(filter)) +#define MS_ILBCDECODER_CLASS(klass) ((MSILBCDecoderClass*)(klass)) +MSFilter * ms_ilbc_decoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_ilbc_decoder_init(MSILBCDecoder *r); +void ms_ilbc_decoder_class_init(MSILBCDecoderClass *klass); +void ms_ilbc_decoder_destroy( MSILBCDecoder *obj); +void ms_ilbc_decoder_process(MSILBCDecoder *r); + +extern MSCodecInfo ilbc_info; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.c new file mode 100644 index 00000000..76d8b648 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.c @@ -0,0 +1,244 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#ifdef HAVE_ILBC + +#include +#include +#include "msilbcenc.h" + + +extern MSCodecInfo ilbc_info; + +/* The return value of each of these calls is the same as that + returned by fread/fwrite, which should be the number of samples + successfully read/written, not the number of bytes. */ + +int +ilbc_read_16bit_samples(gint16 int16samples[], float speech[], int n) +{ + int i; + + /* Convert 16 bit integer samples to floating point values in the + range [-1,+1]. */ + + for (i = 0; i < n; i++) { + speech[i] = int16samples[i]; + } + + return (n); +} + + + +int +ilbc_write_16bit_samples(gint16 int16samples[], float speech[], int n) +{ + int i; + float real_sample; + + /* Convert floating point samples in range [-1,+1] to 16 bit + integers. */ + for (i = 0; i < n; i++) { + float dtmp=speech[i]; + if (dtmpMAX_SAMPLE) + dtmp=MAX_SAMPLE; + int16samples[i] = (short) dtmp; + } + return (n); +} + +/* + +Write the bits in bits[0] through bits[len-1] to file f, in "packed" +format. + +bits is expected to be an array of len integer values, where each +integer is 0 to represent a 0 bit, and any other value represents a 1 +bit. This bit string is written to the file f in the form of several +8 bit characters. If len is not a multiple of 8, then the last +character is padded with 0 bits -- the padding is in the least +significant bits of the last byte. The 8 bit characters are "filled" +in order from most significant bit to least significant. + +*/ + +void +ilbc_write_bits(unsigned char *data, unsigned char *bits, int nbytes) +{ + memcpy(data, bits, nbytes); +} + + + +/* + +Read bits from file f into bits[0] through bits[len-1], in "packed" +format. + +*/ + +int +ilbc_read_bits(unsigned char *data, unsigned char *bits, int nbytes) +{ + + memcpy(bits, data, nbytes); + + return (nbytes); +} + + + + +static MSILBCEncoderClass *ms_ilbc_encoder_class=NULL; + +MSFilter * ms_ilbc_encoder_new(void) +{ + MSILBCEncoder *r; + + r=g_new(MSILBCEncoder,1); + ms_ilbc_encoder_init(r); + if (ms_ilbc_encoder_class==NULL) + { + ms_ilbc_encoder_class=g_new(MSILBCEncoderClass,1); + ms_ilbc_encoder_class_init(ms_ilbc_encoder_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_ilbc_encoder_class); + return(MS_FILTER(r)); +} + + +int ms_ilbc_encoder_set_property(MSILBCEncoder *obj, MSFilterProperty prop, char *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FMTP: + if (value == NULL) return 0; + if (strstr(value,"ptime=20")!=NULL) obj->ms_per_frame=20; + else if (strstr(value,"ptime=30")!=NULL) obj->ms_per_frame=30; + else g_warning("Unrecognized fmtp parameter for ilbc encoder!"); + break; + } + return 0; +} + + +int ms_ilbc_encoder_get_property(MSILBCEncoder *obj, MSFilterProperty prop, char *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FMTP: + if (obj->ms_per_frame==20) strncpy(value,"ptime=20",MS_FILTER_PROPERTY_STRING_MAX_SIZE); + if (obj->ms_per_frame==30) strncpy(value,"ptime=30",MS_FILTER_PROPERTY_STRING_MAX_SIZE); + break; + } + return 0; +} + +void ms_ilbc_encoder_setup(MSILBCEncoder *r) +{ + MSFilterClass *klass = NULL; + switch (r->ms_per_frame) { + case 20: + r->samples_per_frame = BLOCKL_20MS; + r->bytes_per_compressed_frame = NO_OF_BYTES_20MS; + break; + case 30: + r->samples_per_frame = BLOCKL_30MS; + r->bytes_per_compressed_frame = NO_OF_BYTES_30MS; + break; + default: + g_error("Bad bitrate value (%i) for ilbc encoder!", r->ms_per_frame); + break; + } + MS_FILTER(r)->r_mingran= (r->samples_per_frame * 2); + g_message("Using ilbc encoder with %i ms frames mode.",r->ms_per_frame); + initEncode(&r->ilbc_enc, r->ms_per_frame /* ms frames */); +} + +/* FOR INTERNAL USE*/ +void ms_ilbc_encoder_init(MSILBCEncoder *r) +{ + /* default bitrate */ + r->bitrate = 15200; + r->ms_per_frame = 20; + r->samples_per_frame = BLOCKL_20MS; + r->bytes_per_compressed_frame = NO_OF_BYTES_20MS; + + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->outqueues=r->q_outputs; + MS_FILTER(r)->r_mingran= (r->samples_per_frame * 2); + memset(r->f_inputs,0,sizeof(MSFifo*)*MSILBCENCODER_MAX_INPUTS); + memset(r->q_outputs,0,sizeof(MSFifo*)*MSILBCENCODER_MAX_INPUTS); +} + +void ms_ilbc_encoder_class_init(MSILBCEncoderClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"ILBCEnc"); + MS_FILTER_CLASS(klass)->max_finputs=MSILBCENCODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_qoutputs=MSILBCENCODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=ILBC_MAX_SAMPLES_PER_FRAME*2; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_ilbc_encoder_set_property; + MS_FILTER_CLASS(klass)->get_property=(MSFilterPropertyFunc)ms_ilbc_encoder_get_property; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_ilbc_encoder_setup; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_ilbc_encoder_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_ilbc_encoder_process; + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&ilbc_info; +} + +void ms_ilbc_encoder_process(MSILBCEncoder *r) +{ + MSFifo *fi; + MSQueue *qo; + MSMessage *m; + void *src=NULL; + float speech[ILBC_MAX_SAMPLES_PER_FRAME]; + + /* process output fifos, but there is only one for this class of filter*/ + + qo=r->q_outputs[0]; + fi=r->f_inputs[0]; + ms_fifo_get_read_ptr(fi,r->samples_per_frame*2,&src); + if (src==NULL) { + g_warning( "src=%p\n", src); + return; + } + m=ms_message_new(r->bytes_per_compressed_frame); + + ilbc_read_16bit_samples((gint16*)src, speech, r->samples_per_frame); + iLBC_encode((unsigned char *)m->data, speech, &r->ilbc_enc); + ms_queue_put(qo,m); +} + +void ms_ilbc_encoder_uninit(MSILBCEncoder *obj) +{ +} + +void ms_ilbc_encoder_destroy( MSILBCEncoder *obj) +{ + ms_ilbc_encoder_uninit(obj); + g_free(obj); +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.h new file mode 100644 index 00000000..bd8f3bf5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.h @@ -0,0 +1,84 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSILBCENCODER_H +#define MSILBCENCODER_H + +#include "mscodec.h" +#include + +#define ILBC_BITS_IN_COMPRESSED_FRAME 400 + +int +ilbc_read_16bit_samples(gint16 int16samples[], float speech[], int n); + +int +ilbc_write_16bit_samples(gint16 int16samples[], float speech[], int n); + +void +ilbc_write_bits(unsigned char *data, unsigned char *bits, int nbytes); + +int +ilbc_read_bits(unsigned char *data, unsigned char *bits, int nbytes); + + +/*this is the class that implements a ILBCencoder filter*/ + +#define MSILBCENCODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSILBCEncoder +{ + /* the MSILBCEncoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSILBCEncoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSILBCENCODER_MAX_INPUTS]; + MSQueue *q_outputs[MSILBCENCODER_MAX_INPUTS]; + iLBC_Enc_Inst_t ilbc_enc; + int ilbc_encoded_bytes; + int bitrate; + int ms_per_frame; + int samples_per_frame; + int bytes_per_compressed_frame; +} MSILBCEncoder; + +typedef struct _MSILBCEncoderClass +{ + /* the MSILBCEncoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSILBCEncoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSILBCEncoderClass; + +/* PUBLIC */ +#define MS_ILBCENCODER(filter) ((MSILBCEncoder*)(filter)) +#define MS_ILBCENCODER_CLASS(klass) ((MSILBCEncoderClass*)(klass)) +MSFilter * ms_ilbc_encoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_ilbc_encoder_init(MSILBCEncoder *r); +void ms_ilbc_encoder_class_init(MSILBCEncoderClass *klass); +void ms_ilbc_encoder_destroy( MSILBCEncoder *obj); +void ms_ilbc_encoder_process(MSILBCEncoder *r); + +#define ILBC_MAX_BYTES_PER_COMPRESSED_FRAME NO_OF_BYTES_30MS +#define ILBC_MAX_SAMPLES_PER_FRAME BLOCKL_30MS + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.c new file mode 100644 index 00000000..af5141c0 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.c @@ -0,0 +1,82 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "msnosync.h" + +static MSNoSyncClass *ms_nosync_class=NULL; + +void ms_nosync_init(MSNoSync *sync) +{ + ms_sync_init(MS_SYNC(sync)); + MS_SYNC(sync)->attached_filters=sync->filters; + memset(sync->filters,0,MSNOSYNC_MAX_FILTERS*sizeof(MSFilter*)); + MS_SYNC(sync)->samples_per_tick=160; + sync->started=0; +} + +void ms_nosync_class_init(MSNoSyncClass *klass) +{ + ms_sync_class_init(MS_SYNC_CLASS(klass)); + MS_SYNC_CLASS(klass)->max_filters=MSNOSYNC_MAX_FILTERS; + MS_SYNC_CLASS(klass)->synchronize=(MSSyncSyncFunc)ms_nosync_synchronize; + MS_SYNC_CLASS(klass)->destroy=(MSSyncDestroyFunc)ms_nosync_destroy; + /* no need to overload these function*/ + MS_SYNC_CLASS(klass)->attach=ms_sync_attach_generic; + MS_SYNC_CLASS(klass)->detach=ms_sync_detach_generic; +} + +void ms_nosync_destroy(MSNoSync *nosync) +{ + g_free(nosync); +} + +/* the synchronization function that does nothing*/ +void ms_nosync_synchronize(MSNoSync *nosync) +{ + gint32 time; + if (nosync->started==0){ + gettimeofday(&nosync->start,NULL); + nosync->started=1; + } + gettimeofday(&nosync->current,NULL); + MS_SYNC(nosync)->ticks++; + /* update the time, we are supposed to work at 8000 Hz */ + time=((nosync->current.tv_sec-nosync->start.tv_sec)*1000) + + ((nosync->current.tv_usec-nosync->start.tv_usec)/1000); + MS_SYNC(nosync)->time=time; + return; +} + + +MSSync *ms_nosync_new() +{ + MSNoSync *nosync; + + nosync=g_malloc(sizeof(MSNoSync)); + ms_nosync_init(nosync); + if (ms_nosync_class==NULL) + { + ms_nosync_class=g_new(MSNoSyncClass,1); + ms_nosync_class_init(ms_nosync_class); + } + MS_SYNC(nosync)->klass=MS_SYNC_CLASS(ms_nosync_class); + return(MS_SYNC(nosync)); +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.h new file mode 100644 index 00000000..eef52d45 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.h @@ -0,0 +1,60 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "mssync.h" + +#include +#define MSNOSYNC_MAX_FILTERS 10 + +/* MSNoSync derivates from MSSync base class*/ + +typedef struct _MSNoSync +{ + /* the MSSync must be the first field of the object in order to the object mechanism to work*/ + MSSync sync; + MSFilter *filters[MSNOSYNC_MAX_FILTERS]; + int started; + struct timeval start,current; +} MSNoSync; + + +typedef struct _MSNoSyncClass +{ + /* the MSSyncClass must be the first field of the class in order to the class mechanism to work*/ + MSSyncClass parent_class; +} MSNoSyncClass; + + +/*private*/ + +void ms_nosync_init(MSNoSync *sync); +void ms_nosync_class_init(MSNoSyncClass *sync); + +void ms_nosync_destroy(MSNoSync *nosync); +void ms_nosync_synchronize(MSNoSync *nosync); + +/*public*/ + +/* casts a MSSync object into a MSNoSync */ +#define MS_NOSYNC(sync) ((MSNoSync*)(sync)) +/* casts a MSSync class into a MSNoSync class */ +#define MS_NOSYNC_CLASS(klass) ((MSNoSyncClass*)(klass)) + +MSSync *ms_nosync_new(); diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.c new file mode 100644 index 00000000..2486c736 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.c @@ -0,0 +1,148 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msossread.h" +#include "mssync.h" +#include +#include +#include +#include + +MSFilterInfo oss_read_info={ + "OSS read", + 0, + MS_FILTER_AUDIO_IO, + ms_oss_read_new, + NULL +}; + +static MSOssReadClass *msossreadclass=NULL; + +MSFilter * ms_oss_read_new() +{ + MSOssRead *w; + + if (msossreadclass==NULL) + { + msossreadclass=g_new(MSOssReadClass,1); + ms_oss_read_class_init( msossreadclass ); + } + + w=g_new(MSOssRead,1); + MS_FILTER(w)->klass=MS_FILTER_CLASS(msossreadclass); + ms_oss_read_init(w); + + return(MS_FILTER(w)); +} + +/* FOR INTERNAL USE*/ +void ms_oss_read_init(MSOssRead *w) +{ + ms_sound_read_init(MS_SOUND_READ(w)); + MS_FILTER(w)->outfifos=w->f_outputs; + MS_FILTER(w)->outfifos[0]=NULL; + w->devid=0; + w->sndcard=NULL; + w->freq=8000; +} + +gint ms_oss_read_set_property(MSOssRead *f,MSFilterProperty prop, void *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FREQ: + f->freq=((gint*)value)[0]; + break; + } + return 0; +} +void ms_oss_read_class_init(MSOssReadClass *klass) +{ + ms_sound_read_class_init(MS_SOUND_READ_CLASS(klass)); + MS_FILTER_CLASS(klass)->max_foutputs=1; /* one fifo output only */ + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_oss_read_setup; + MS_FILTER_CLASS(klass)->unsetup=(MSFilterSetupFunc)ms_oss_read_stop; + MS_FILTER_CLASS(klass)->process= (MSFilterProcessFunc)ms_oss_read_process; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_oss_read_set_property; + MS_FILTER_CLASS(klass)->destroy= (MSFilterDestroyFunc)ms_oss_read_destroy; + MS_FILTER_CLASS(klass)->w_maxgran=MS_OSS_READ_MAX_GRAN; + MS_FILTER_CLASS(klass)->info=&oss_read_info; + MS_SOUND_READ_CLASS(klass)->set_device=(gint (*)(MSSoundRead*,gint))ms_oss_read_set_device; + MS_SOUND_READ_CLASS(klass)->start=(void (*)(MSSoundRead*))ms_oss_read_start; + MS_SOUND_READ_CLASS(klass)->stop=(void (*)(MSSoundRead*))ms_oss_read_stop; + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"OssRead"); + /* //ms_filter_class_set_attr( MS_FILTER_CLASS(klass),FILTER_CAN_SYNC|FILTER_IS_SOURCE); */ +} + +void ms_oss_read_destroy( MSOssRead *obj) +{ + g_free(obj); +} + +void ms_oss_read_process(MSOssRead *f) +{ + MSFifo *fifo; + char *p; + fifo=f->f_outputs[0]; + + g_return_if_fail(f->sndcard!=NULL); + g_return_if_fail(f->gran>0); + + if (snd_card_can_read(f->sndcard)){ + int got; + ms_fifo_get_write_ptr(fifo,f->gran,(void**)&p); + g_return_if_fail(p!=NULL); + got=snd_card_read(f->sndcard,p,f->gran); + if (got>=0 && got!=f->gran) ms_fifo_update_write_ptr(fifo,got); + } +} + + +void ms_oss_read_start(MSOssRead *r) +{ + g_return_if_fail(r->devid!=-1); + r->sndcard=snd_card_manager_get_card(snd_card_manager,r->devid); + g_return_if_fail(r->sndcard!=NULL); + /* open the device for an audio telephony signal with minimum latency */ + snd_card_open_r(r->sndcard,16,0,r->freq); + r->gran=(512*r->freq)/8000; + +} + +void ms_oss_read_stop(MSOssRead *w) +{ + g_return_if_fail(w->devid!=-1); + g_return_if_fail(w->sndcard!=NULL); + snd_card_close_r(w->sndcard); + w->sndcard=NULL; +} + + +void ms_oss_read_setup(MSOssRead *f, MSSync *sync) +{ + f->sync=sync; + ms_oss_read_start(f); +} + + +gint ms_oss_read_set_device(MSOssRead *r,gint devid) +{ + r->devid=devid; + return 0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.h new file mode 100644 index 00000000..89d5a40b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.h @@ -0,0 +1,77 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef MSOSSREAD_H +#define MSOSSREAD_H + +#include "mssoundread.h" +#include "sndcard.h" +#include "mssync.h" + + +/*this is the class that implements oss writing sink filter*/ + +#define MS_OSS_READ_MAX_INPUTS 1 /* max output per filter*/ + +#define MS_OSS_READ_MAX_GRAN (512*2) /* the maximum granularity*/ + +struct _MSOssRead +{ + /* the MSOssRead derivates from MSSoundRead so the MSSoundRead object MUST be the first of the MSOssRead object + in order to the object mechanism to work*/ + MSSoundRead filter; + MSFifo *f_outputs[MS_OSS_READ_MAX_INPUTS]; + MSSync *sync; + SndCard *sndcard; + gint freq; + gint devid; /* the sound device id it depends on*/ + gint gran; + gint flags; +#define START_REQUESTED 1 +#define STOP_REQUESTED 2 +}; + +typedef struct _MSOssRead MSOssRead; + +struct _MSOssReadClass +{ + /* the MSOssRead derivates from MSSoundRead, so the MSSoundRead class MUST be the first of the MSOssRead class + in order to the class mechanism to work*/ + MSSoundReadClass parent_class; +}; + +typedef struct _MSOssReadClass MSOssReadClass; + +/* PUBLIC */ +#define MS_OSS_READ(filter) ((MSOssRead*)(filter)) +#define MS_OSS_READ_CLASS(klass) ((MSOssReadClass*)(klass)) +MSFilter * ms_oss_read_new(void); +gint ms_oss_read_set_device(MSOssRead *w,gint devid); +void ms_oss_read_start(MSOssRead *w); +void ms_oss_read_stop(MSOssRead *w); + +/* FOR INTERNAL USE*/ +void ms_oss_read_init(MSOssRead *r); +void ms_oss_read_class_init(MSOssReadClass *klass); +void ms_oss_read_destroy( MSOssRead *obj); +void ms_oss_read_process(MSOssRead *f); +void ms_oss_read_setup(MSOssRead *f, MSSync *sync); + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.c new file mode 100644 index 00000000..cc86cd6b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.c @@ -0,0 +1,247 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msosswrite.h" +#include "mssync.h" +#include +#include + +MSFilterInfo oss_write_info={ + "OSS write", + 0, + MS_FILTER_OTHER, + ms_oss_write_new, + NULL +}; + + +static MSOssWriteClass *msosswriteclass=NULL; + +MSFilter * ms_oss_write_new() +{ + MSOssWrite *w; + + if (msosswriteclass==NULL) + { + msosswriteclass=g_new(MSOssWriteClass,1); + ms_oss_write_class_init( msosswriteclass ); + } + w=g_new(MSOssWrite,1); + MS_FILTER(w)->klass=MS_FILTER_CLASS(msosswriteclass); + ms_oss_write_init(w); + return(MS_FILTER(w)); +} + +/* FOR INTERNAL USE*/ +void ms_oss_write_init(MSOssWrite *w) +{ + ms_sound_write_init(MS_SOUND_WRITE(w)); + MS_FILTER(w)->infifos=w->f_inputs; + MS_FILTER(w)->infifos[0]=NULL; + MS_FILTER(w)->r_mingran=512; /* very few cards can do that...*/ + w->devid=0; + w->sndcard=NULL; + w->freq=8000; + w->channels=1; + w->dtmf_time=-1; +} + +gint ms_oss_write_set_property(MSOssWrite *f,MSFilterProperty prop, void *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FREQ: + f->freq=((gint*)value)[0]; + break; + case MS_FILTER_PROPERTY_CHANNELS: + f->channels=((gint*)value)[0]; + break; + } + return 0; +} + +void ms_oss_write_class_init(MSOssWriteClass *klass) +{ + ms_sound_write_class_init(MS_SOUND_WRITE_CLASS(klass)); + MS_FILTER_CLASS(klass)->max_finputs=1; /* one fifo input only */ + MS_FILTER_CLASS(klass)->r_maxgran=MS_OSS_WRITE_DEF_GRAN; + MS_FILTER_CLASS(klass)->process= (MSFilterProcessFunc)ms_oss_write_process; + MS_FILTER_CLASS(klass)->destroy= (MSFilterDestroyFunc)ms_oss_write_destroy; + MS_FILTER_CLASS(klass)->setup= (MSFilterSetupFunc)ms_oss_write_setup; + MS_FILTER_CLASS(klass)->unsetup= (MSFilterSetupFunc)ms_oss_write_stop; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_oss_write_set_property; + MS_FILTER_CLASS(klass)->info=&oss_write_info; + MS_SOUND_WRITE_CLASS(klass)->set_device=(gint (*)(MSSoundWrite*,gint))ms_oss_write_set_device; + MS_SOUND_WRITE_CLASS(klass)->start=(void (*)(MSSoundWrite*))ms_oss_write_start; + MS_SOUND_WRITE_CLASS(klass)->stop=(void (*)(MSSoundWrite*))ms_oss_write_stop; + MS_SOUND_WRITE_CLASS(klass)->set_level=(void (*)(MSSoundWrite*, gint))ms_oss_write_set_level; + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"OssWrite"); +} + +void ms_oss_write_destroy( MSOssWrite *obj) +{ + + g_free(obj); +} + +void ms_oss_write_process(MSOssWrite *f) +{ + MSFifo *fifo; + void *p; + int i; + gint gran=ms_filter_get_mingran(MS_FILTER(f)); + + /* always consume something */ + fifo=f->f_inputs[0]; + ms_fifo_get_read_ptr(fifo,gran,&p); + if (p==NULL) { + g_warning("Not enough data: gran=%i.",gran); + return; + } + g_return_if_fail(f->sndcard!=NULL); + if (f->dtmf_time!=-1){ + gint16 *buf=(gint16*)p; + /* generate a DTMF*/ + for(i=0;idtmf_time*f->lowfreq)); + buf[i]+=(gint16)(10000.0*sin(2*M_PI*(double)f->dtmf_time*f->highfreq)); + f->dtmf_time++; + /* //printf("buf[%i]=%i\n",i,buf[i]); */ + } + if (f->dtmf_time>f->dtmf_duration) f->dtmf_time=-1; /*finished*/ + } + snd_card_write(f->sndcard,p,gran); +} + +void ms_oss_write_start(MSOssWrite *w) +{ + gint bsize; + g_return_if_fail(w->devid!=-1); + w->sndcard=snd_card_manager_get_card(snd_card_manager,w->devid); + g_return_if_fail(w->sndcard!=NULL); + /* open the device for an audio telephony signal with minimum latency */ + snd_card_open_w(w->sndcard,16,w->channels==2,w->freq); + w->bsize=snd_card_get_bsize(w->sndcard); + /* //MS_FILTER(w)->r_mingran=w->bsize; */ + /* //ms_sync_set_samples_per_tick(MS_FILTER(w)->sync,bsize); */ +} + +void ms_oss_write_stop(MSOssWrite *w) +{ + g_return_if_fail(w->devid!=-1); + g_return_if_fail(w->sndcard!=NULL); + snd_card_close_w(w->sndcard); + w->sndcard=NULL; +} + +void ms_oss_write_set_level(MSOssWrite *w,gint a) +{ + +} + +gint ms_oss_write_set_device(MSOssWrite *w, gint devid) +{ + w->devid=devid; + return 0; +} + +void ms_oss_write_setup(MSOssWrite *r) +{ + /* //g_message("starting MSOssWrite.."); */ + ms_oss_write_start(r); +} + + + +void ms_oss_write_play_dtmf(MSOssWrite *w, char dtmf){ + + w->dtmf_duration=0.1*w->freq; + switch(dtmf){ + case '0': + w->lowfreq=941; + w->highfreq=1336; + break; + case '1': + w->lowfreq=697; + w->highfreq=1209; + break; + case '2': + w->lowfreq=697; + w->highfreq=1336; + break; + case '3': + w->lowfreq=697; + w->highfreq=1477; + break; + case '4': + w->lowfreq=770; + w->highfreq=1209; + break; + case '5': + w->lowfreq=770; + w->highfreq=1336; + break; + case '6': + w->lowfreq=770; + w->highfreq=1477; + break; + case '7': + w->lowfreq=852; + w->highfreq=1209; + break; + case '8': + w->lowfreq=852; + w->highfreq=1336; + break; + case '9': + w->lowfreq=852; + w->highfreq=1477; + break; + case '*': + w->lowfreq=941; + w->highfreq=1209; + break; + case '#': + w->lowfreq=941; + w->highfreq=1477; + break; + case 'A': + w->lowfreq=697; + w->highfreq=1633; + break; + case 'B': + w->lowfreq=770; + w->highfreq=1633; + break; + case 'C': + w->lowfreq=852; + w->highfreq=1633; + break; + case 'D': + w->lowfreq=941; + w->highfreq=1633; + break; + default: + g_warning("Not a dtmf key."); + return; + } + w->lowfreq=w->lowfreq/w->freq; + w->highfreq=w->highfreq/w->freq; + w->dtmf_time=0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.h new file mode 100644 index 00000000..d4775341 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.h @@ -0,0 +1,78 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef MSOSSWRITE_H +#define MSOSSWRITE_H + +#include "mssoundwrite.h" +#include "sndcard.h" + +/*this is the class that implements oss writing sink filter*/ + +#define MS_OSS_WRITE_MAX_INPUTS 1 /* max output per filter*/ + +#define MS_OSS_WRITE_DEF_GRAN (512*2) /* the default granularity*/ + +struct _MSOssWrite +{ + /* the MSOssWrite derivates from MSSoundWrite, so the MSSoundWrite object MUST be the first of the MSOssWrite object + in order to the object mechanism to work*/ + MSSoundWrite filter; + MSFifo *f_inputs[MS_OSS_WRITE_MAX_INPUTS]; + gint devid; /* the sound device id it depends on*/ + SndCard *sndcard; + gint bsize; + gint freq; + gint channels; + gdouble lowfreq; + gdouble highfreq; + gint dtmf_time; + gint dtmf_duration; +}; + +typedef struct _MSOssWrite MSOssWrite; + +struct _MSOssWriteClass +{ + /* the MSOssWrite derivates from MSSoundWrite, so the MSSoundWrite class MUST be the first of the MSOssWrite class + in order to the class mechanism to work*/ + MSSoundWriteClass parent_class; +}; + +typedef struct _MSOssWriteClass MSOssWriteClass; + +/* PUBLIC */ +#define MS_OSS_WRITE(filter) ((MSOssWrite*)(filter)) +#define MS_OSS_WRITE_CLASS(klass) ((MSOssWriteClass*)(klass)) +MSFilter * ms_oss_write_new(void); +gint ms_oss_write_set_device(MSOssWrite *w,gint devid); +void ms_oss_write_start(MSOssWrite *w); +void ms_oss_write_stop(MSOssWrite *w); +void ms_oss_write_set_level(MSOssWrite *w, gint level); +void ms_oss_write_play_dtmf(MSOssWrite *w, char dtmf); + +/* FOR INTERNAL USE*/ +void ms_oss_write_init(MSOssWrite *r); +void ms_oss_write_setup(MSOssWrite *r); +void ms_oss_write_class_init(MSOssWriteClass *klass); +void ms_oss_write_destroy( MSOssWrite *obj); +void ms_oss_write_process(MSOssWrite *f); + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.c new file mode 100644 index 00000000..6bd073b9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.c @@ -0,0 +1,91 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a dispatcher of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "msqdispatcher.h" + +static MSQdispatcherClass *ms_qdispatcher_class=NULL; + +MSFilter * ms_qdispatcher_new(void) +{ + MSQdispatcher *obj; + obj=g_malloc(sizeof(MSQdispatcher)); + if (ms_qdispatcher_class==NULL){ + ms_qdispatcher_class=g_malloc0(sizeof(MSQdispatcherClass)); + ms_qdispatcher_class_init(ms_qdispatcher_class); + } + MS_FILTER(obj)->klass=MS_FILTER_CLASS(ms_qdispatcher_class); + ms_qdispatcher_init(obj); + return MS_FILTER(obj); +} + + +void ms_qdispatcher_init(MSQdispatcher *obj) +{ + ms_filter_init(MS_FILTER(obj)); + + MS_FILTER(obj)->inqueues=obj->q_inputs; + MS_FILTER(obj)->outqueues=obj->q_outputs; + memset(obj->q_inputs,0,sizeof(MSQueue*)*MS_QDISPATCHER_MAX_INPUTS); + memset(obj->q_outputs,0,sizeof(MSQueue*)*MS_QDISPATCHER_MAX_OUTPUTS); +} + + + +void ms_qdispatcher_class_init(MSQdispatcherClass *klass) +{ + MSFilterClass *parent_class=MS_FILTER_CLASS(klass); + ms_filter_class_init(parent_class); + ms_filter_class_set_name(parent_class,"qdispatcher"); + parent_class->max_qinputs=MS_QDISPATCHER_MAX_INPUTS; + parent_class->max_qoutputs=MS_QDISPATCHER_MAX_OUTPUTS; + + parent_class->destroy=(MSFilterDestroyFunc)ms_qdispatcher_destroy; + parent_class->process=(MSFilterProcessFunc)ms_qdispatcher_process; +} + + +void ms_qdispatcher_destroy( MSQdispatcher *obj) +{ + g_free(obj); +} + +void ms_qdispatcher_process(MSQdispatcher *obj) +{ + gint i; + MSQueue *inq=obj->q_inputs[0]; + + if (inq!=NULL){ + MSQueue *outq; + MSMessage *m1,*m2; + while ( (m1=ms_queue_get(inq))!=NULL){ + /* dispatch incoming messages to output queues */ + for (i=0;iq_outputs[i]; + if (outq!=NULL){ + m2=ms_message_dup(m1); + ms_queue_put(outq,m2); + } + } + ms_message_destroy(m1); + } + } + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.h new file mode 100644 index 00000000..3b0c566d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.h @@ -0,0 +1,60 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a dispatcher of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSQDISPATCHER_H +#define MSQDISPATCHER_H + +#include "msfilter.h" + + +/*this is the class that implements a qdispatcher filter*/ + +#define MS_QDISPATCHER_MAX_INPUTS 1 +#define MS_QDISPATCHER_MAX_OUTPUTS 5 + +typedef struct _MSQdispatcher +{ + /* the MSQdispatcher derivates from MSFilter, so the MSFilter object MUST be the first of the MSQdispatcher object + in order to the object mechanism to work*/ + MSFilter filter; + MSQueue *q_inputs[MS_QDISPATCHER_MAX_INPUTS]; + MSQueue *q_outputs[MS_QDISPATCHER_MAX_OUTPUTS]; +} MSQdispatcher; + +typedef struct _MSQdispatcherClass +{ + /* the MSQdispatcher derivates from MSFilter, so the MSFilter class MUST be the first of the MSQdispatcher class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSQdispatcherClass; + +/* PUBLIC */ +#define MS_QDISPATCHER(filter) ((MSQdispatcher*)(filter)) +#define MS_QDISPATCHER_CLASS(klass) ((MSQdispatcherClass*)(klass)) +MSFilter * ms_qdispatcher_new(void); + +/* FOR INTERNAL USE*/ +void ms_qdispatcher_init(MSQdispatcher *r); +void ms_qdispatcher_class_init(MSQdispatcherClass *klass); +void ms_qdispatcher_destroy( MSQdispatcher *obj); +void ms_qdispatcher_process(MSQdispatcher *r); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.c new file mode 100644 index 00000000..46368956 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.c @@ -0,0 +1,56 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msqueue.h" +#include + +MSQueue * ms_queue_new() +{ + MSQueue *q=g_malloc(sizeof(MSQueue)); + memset(q,0,sizeof(MSQueue)); + return q; +} + +MSMessage *ms_queue_get(MSQueue *q) +{ + MSMessage *b=q->last; + if (b==NULL) return NULL; + q->last=b->prev; + if (b->prev==NULL) q->first=NULL; /* it was the only element of the queue*/ + q->size--; + b->next=b->prev=NULL; + return(b); +} + +void ms_queue_put(MSQueue *q, MSMessage *m) +{ + MSMessage *mtmp=q->first; + g_return_if_fail(m!=NULL); + q->first=m; + m->next=mtmp; + if (mtmp!=NULL) + { + mtmp->prev=m; + } + else q->last=m; /* it was the first element of the q */ + q->size++; +} + + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.h new file mode 100644 index 00000000..73ab8d8d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.h @@ -0,0 +1,49 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MSQUEUE_H +#define MSQUEUE_H + +#include "msbuffer.h" + +/* for the moment these are stupid queues limited to one element*/ + +typedef struct _MSQueue +{ + MSMessage *first; + MSMessage *last; + gint size; + void *prev_data; /*user data, usually the writting filter*/ + void *next_data; /* user data, usually the reading filter*/ +}MSQueue; + + +MSQueue * ms_queue_new(); + +MSMessage *ms_queue_get(MSQueue *q); + +void ms_queue_put(MSQueue *q, MSMessage *m); + +#define ms_queue_can_get(q) ( (q)->size!=0 ) + +#define ms_queue_destroy(q) g_free(q) + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.c new file mode 100644 index 00000000..6f0ec99d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.c @@ -0,0 +1,182 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msread.h" +#include "mssync.h" +#include +#include +#include +#include +#include + +static MSReadClass *ms_read_class=NULL; + +MSFilter * ms_read_new(char *name) +{ + MSRead *r; + int fd=-1; + + r=g_new(MSRead,1); + ms_read_init(r); + if (ms_read_class==NULL) + { + ms_read_class=g_new(MSReadClass,1); + ms_read_class_init(ms_read_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_read_class); + r->fd=-1; + if (name!=NULL) ms_read_open(r,name); + return(MS_FILTER(r)); +} + + + +gint ms_read_open(MSRead *r, gchar *name) +{ + gint fd; + fd=open(name,O_RDONLY); + if (fd<0) { + r->fd=-1; + g_warning("ms_read_new: cannot open %s : %s",name,strerror(errno)); + return -1; + } + r->fd=fd; + if (strstr(name,".wav")!=NULL){ + /* skip the header */ + lseek(fd,20,SEEK_SET); +#ifdef WORDS_BIGENDIAN + r->need_swap=1; +#else + r->need_swap=0; +#endif + } + r->state=MS_READ_STATE_STARTED; + return 0; +} + +/* FOR INTERNAL USE*/ +void ms_read_init(MSRead *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->outfifos=r->foutputs; + MS_FILTER(r)->outqueues=r->qoutputs; + memset(r->foutputs,0,sizeof(MSFifo*)*MSREAD_MAX_OUTPUTS); + memset(r->qoutputs,0,sizeof(MSQueue*)*MSREAD_MAX_OUTPUTS); + r->fd=-1; + r->gran=320; + r->state=MS_READ_STATE_STOPPED; + r->need_swap=0; + r->rate=8000; +} + +gint ms_read_set_property(MSRead *f,MSFilterProperty prop, void *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FREQ: + f->rate=((gint*)value)[0]; + break; + } + return 0; +} + +void ms_read_class_init(MSReadClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"dskreader"); + ms_filter_class_set_attr(MS_FILTER_CLASS(klass),FILTER_IS_SOURCE); + MS_FILTER_CLASS(klass)->max_foutputs=MSREAD_MAX_OUTPUTS; + MS_FILTER_CLASS(klass)->max_qoutputs=MSREAD_MAX_OUTPUTS; + MS_FILTER_CLASS(klass)->w_maxgran=MSREAD_DEF_GRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_read_destroy; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_read_setup; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_read_process; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_read_set_property; +} + +void ms_read_process(MSRead *r) +{ + MSFifo *f; + MSQueue *q; + MSMessage *msg=NULL; + int err; + gint gran=r->gran; + void *p; + + f=r->foutputs[0]; + if ((f!=NULL) && (r->state==MS_READ_STATE_STARTED)) + { + ms_fifo_get_write_ptr(f,gran,&p); + if (p!=NULL) + { + err=read(r->fd,p,gran); + if (err<0) + { + /* temp: */ + g_warning("ms_read_process: failed to read: %s.\n",strerror(errno)); + } + else if (errstate=MS_READ_STATE_STOPPED; + close(r->fd); + r->fd=-1; + } + if (r->need_swap) swap_buffer(p,gran); + } + } + /* process output queues*/ + q=r->qoutputs[0]; + if ((q!=NULL) && (r->fd>0)) + { + msg=ms_message_new(r->gran); + err=read(r->fd,msg->data,r->gran); + if (err>0){ + msg->size=err; + ms_queue_put(q,msg); + if (r->need_swap) swap_buffer(msg->data,r->gran); + }else{ + ms_filter_notify_event(MS_FILTER(r),MS_READ_EVENT_EOF,NULL); + ms_trace("End of file reached."); + r->state=MS_READ_STATE_STOPPED; + } + } +} + +void ms_read_destroy( MSRead *obj) +{ + if (obj->fd!=0) close(obj->fd); + g_free(obj); +} + +gint ms_read_close(MSRead *obj) +{ + if (obj->fd!=0) { + close(obj->fd); + obj->fd=-1; + obj->state=MS_READ_STATE_STOPPED; + } +} + + +void ms_read_setup(MSRead *r, MSSync *sync) +{ + r->sync=sync; + r->gran=(r->rate*sync->interval/1000)*2; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.h new file mode 100644 index 00000000..93177f38 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.h @@ -0,0 +1,80 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MSREAD_H +#define MSREAD_H + +#include "msfilter.h" +#include "mssync.h" + +/*this is the class that implements file reading source filter*/ + +#define MSREAD_MAX_OUTPUTS 1 /* max output per filter*/ + +#define MSREAD_DEF_GRAN 640 /* the default granularity*/ + +typedef enum{ + MS_READ_STATE_STARTED, + MS_READ_STATE_STOPPED, + MS_READ_STATE_EOF +}MSReadState; + +typedef struct _MSRead +{ + /* the MSRead derivates from MSFilter, so the MSFilter object MUST be the first of the MSRead object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *foutputs[MSREAD_MAX_OUTPUTS]; + MSQueue *qoutputs[MSREAD_MAX_OUTPUTS]; + MSSync *sync; + gint rate; + gint fd; /* the file descriptor of the file being read*/ + gint gran; /*granularity*/ /* for use with queues */ + gint need_swap; + gint state; +} MSRead; + +typedef struct _MSReadClass +{ + /* the MSRead derivates from MSFilter, so the MSFilter class MUST be the first of the MSRead class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSReadClass; + +/* PUBLIC */ +#define MS_READ(filter) ((MSRead*)(filter)) +#define MS_READ_CLASS(klass) ((MSReadClass*)(klass)) +MSFilter * ms_read_new(char *name); +/* set the granularity for reading file on disk */ +#define ms_read_set_bufsize(filter,sz) (filter)->gran=(sz) + +/* FOR INTERNAL USE*/ +void ms_read_init(MSRead *r); +void ms_read_class_init(MSReadClass *klass); +void ms_read_destroy( MSRead *obj); +void ms_read_process(MSRead *r); +void ms_read_setup(MSRead *r, MSSync *sync); + +typedef enum{ + MS_READ_EVENT_EOF /* end of file */ +} MSReadEvent; + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.c new file mode 100644 index 00000000..fb2006e8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.c @@ -0,0 +1,246 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msringplayer.h" +#include "mssync.h" +#include +#include +#include +#include +#include + +#include "waveheader.h" + +#define WAVE_HEADER_OFFSET sizeof(wave_header_t) + +enum { PLAY_RING, PLAY_SILENCE}; + +static int supported_freq[6]={8000,11025,16000,22050,32000,44100}; + +gint freq_is_supported(gint freq){ + int i; + for (i=0;i<6;i++){ + if (abs(supported_freq[i]-freq)<50) return supported_freq[i]; + } + return 0; +} + +static MSRingPlayerClass *ms_ring_player_class=NULL; + +/** + * ms_ring_player_new: + * @name: The path to the 16-bit 8khz raw file to be played as a ring. + * @seconds: The number of seconds that separates two rings. + * + * Allocates a new MSRingPlayer object. + * + * + * Returns: a pointer the the object, NULL if name could not be open. + */ +MSFilter * ms_ring_player_new(char *name, gint seconds) +{ + MSRingPlayer *r; + int fd=-1; + + if ((name!=NULL) && (strlen(name)!=0)) + { + fd=open(name,O_RDONLY); + if (fd<0) + { + g_warning("ms_ring_player_new: failed to open %s.\n",name); + return NULL; + } + + }else { + g_warning("ms_ring_player_new: Bad file name"); + return NULL; + } + + r=g_new(MSRingPlayer,1); + ms_ring_player_init(r); + if (ms_ring_player_class==NULL) + { + ms_ring_player_class=g_new(MSRingPlayerClass,1); + ms_ring_player_class_init(ms_ring_player_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_ring_player_class); + + r->fd=fd; + r->silence=seconds; + r->freq=8000; + if (strstr(name,".wav")!=NULL){ + wave_header_t header; + int freq,freq2; + /* read the header */ + read(fd,&header,sizeof(wave_header_t)); + freq=wave_header_get_rate(&header); + if ((freq2=freq_is_supported(freq))>0){ + r->freq=freq2; + }else { + g_warning("Unsupported sampling rate %i",freq); + r->freq=8000; + } + r->channel=wave_header_get_channel(&header); + lseek(fd,WAVE_HEADER_OFFSET,SEEK_SET); +#ifdef WORDS_BIGENDIAN + r->need_swap=1; +#else + r->need_swap=0; +#endif + } + ms_ring_player_set_property(r, MS_FILTER_PROPERTY_FREQ,&r->freq); + r->state=PLAY_RING; + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_ring_player_init(MSRingPlayer *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->outfifos=r->foutputs; + MS_FILTER(r)->outqueues=r->qoutputs; + memset(r->foutputs,0,sizeof(MSFifo*)*MS_RING_PLAYER_MAX_OUTPUTS); + memset(r->qoutputs,0,sizeof(MSQueue*)*MS_RING_PLAYER_MAX_OUTPUTS); + r->fd=-1; + r->current_pos=0; + r->need_swap=0; + r->sync=NULL; +} + +gint ms_ring_player_set_property(MSRingPlayer *f,MSFilterProperty prop, void *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FREQ: + f->rate=((gint*)value)[0]*2; + f->silence_bytes=f->silence*f->rate; + if (f->sync!=NULL) + f->gran=(f->rate*f->sync->interval/1000)*2; + break; + } + return 0; +} + +gint ms_ring_player_get_property(MSRingPlayer *f,MSFilterProperty prop, void *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FREQ: + ((gint*)value)[0]=f->freq; + + break; + case MS_FILTER_PROPERTY_CHANNELS: + ((gint*)value)[0]=f->channel; + break; + } + return 0; +} + +gint ms_ring_player_get_sample_freq(MSRingPlayer *obj){ + return obj->freq; +} + + +void ms_ring_player_class_init(MSRingPlayerClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"ringplay"); + ms_filter_class_set_attr(MS_FILTER_CLASS(klass),FILTER_IS_SOURCE); + MS_FILTER_CLASS(klass)->max_foutputs=MS_RING_PLAYER_MAX_OUTPUTS; + MS_FILTER_CLASS(klass)->max_qoutputs=MS_RING_PLAYER_MAX_OUTPUTS; + MS_FILTER_CLASS(klass)->w_maxgran=MS_RING_PLAYER_DEF_GRAN; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_ring_player_setup; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_ring_player_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_ring_player_process; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_ring_player_set_property; + MS_FILTER_CLASS(klass)->get_property=(MSFilterPropertyFunc)ms_ring_player_get_property; +} + +void ms_ring_player_process(MSRingPlayer *r) +{ + MSFifo *f; + gint err; + gint processed=0; + gint gran=r->gran; + char *p; + + g_return_if_fail(gran>0); + /* process output fifos*/ + + f=r->foutputs[0]; + ms_fifo_get_write_ptr(f,gran,(void**)&p); + g_return_if_fail(p!=NULL); + for (processed=0;processedstate){ + case PLAY_RING: + err=read(r->fd,&p[processed],gran-processed); + if (err<0) + { + memset(&p[processed],0,gran-processed); + processed=gran; + g_warning("ms_ring_player_process: failed to read: %s.\n",strerror(errno)); + return; + } + else if (errcurrent_pos=r->silence_bytes; + lseek(r->fd,WAVE_HEADER_OFFSET,SEEK_SET); + r->state=PLAY_SILENCE; + ms_filter_notify_event(MS_FILTER(r),MS_RING_PLAYER_END_OF_RING_EVENT,NULL); + } + if (r->need_swap) swap_buffer(&p[processed],err); + processed+=err; + break; + case PLAY_SILENCE: + err=gran-processed; + if (r->current_pos>err){ + memset(&p[processed],0,err); + r->current_pos-=gran; + processed=gran; + }else{ + memset(&p[processed],0,r->current_pos); + processed+=r->current_pos; + r->state=PLAY_RING; + } + break; + } + } +} + +/** + * ms_ring_player_destroy: + * @obj: A valid MSRingPlayer object. + * + * Destroy a MSRingPlayer object. + * + * + */ + +void ms_ring_player_destroy( MSRingPlayer *obj) +{ + if (obj->fd!=0) close(obj->fd); + g_free(obj); +} + +void ms_ring_player_setup(MSRingPlayer *r,MSSync *sync) +{ + r->sync=sync; + r->gran=(r->rate*r->sync->interval/1000)*r->channel; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.h new file mode 100644 index 00000000..1f5e67da --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.h @@ -0,0 +1,81 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MSRINGPLAYER_H +#define MSRINGPLAYER_H + +#include "msfilter.h" +#include "mssync.h" + + +/*this is the class that implements file reading source filter*/ + +#define MS_RING_PLAYER_MAX_OUTPUTS 1 /* max output per filter*/ + +#define MS_RING_PLAYER_DEF_GRAN 8192 /* the default granularity*/ + +#define MS_RING_PLAYER_END_OF_RING_EVENT 1 + +struct _MSRingPlayer +{ + /* the MSRingPlayer derivates from MSFilter, so the MSFilter object MUST be the first of the MSRingPlayer object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *foutputs[MS_RING_PLAYER_MAX_OUTPUTS]; + MSQueue *qoutputs[MS_RING_PLAYER_MAX_OUTPUTS];\ + MSSync *sync; + gint gran; + gint freq; + gint rate; + gint channel; /* number of interleaved channels */ + gint silence; /* silence time between each ring, in seconds */ + gint state; + gint fd; /* the file descriptor of the file being read*/ + gint silence_bytes; /*silence in number of bytes between each ring */ + gint current_pos; + gint need_swap; +}; + +typedef struct _MSRingPlayer MSRingPlayer; + +struct _MSRingPlayerClass +{ + /* the MSRingPlayer derivates from MSFilter, so the MSFilter class MUST be the first of the MSRingPlayer class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +}; + +typedef struct _MSRingPlayerClass MSRingPlayerClass; + +/* PUBLIC */ +#define MS_RING_PLAYER(filter) ((MSRingPlayer*)(filter)) +#define MS_RING_PLAYER_CLASS(klass) ((MSRingPlayerClass*)(klass)) +MSFilter * ms_ring_player_new(char *name, gint seconds); +gint ms_ring_player_get_sample_freq(MSRingPlayer *obj); + + +/* FOR INTERNAL USE*/ +void ms_ring_player_init(MSRingPlayer *r); +void ms_ring_player_class_init(MSRingPlayerClass *klass); +void ms_ring_player_destroy( MSRingPlayer *obj); +void ms_ring_player_process(MSRingPlayer *r); +#define ms_ring_player_set_bufsize(filter,sz) (filter)->gran=(sz) +void ms_ring_player_setup(MSRingPlayer *r,MSSync *sync); +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.c new file mode 100644 index 00000000..9b82e939 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.c @@ -0,0 +1,163 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "msrtprecv.h" + + +/* some utilities to convert mblk_t to MSMessage and vice-versa */ +MSMessage *msgb_2_ms_message(mblk_t* mp){ + MSMessage *msg; + MSBuffer *msbuf; + if (mp->b_datap->ref_count!=1) return NULL; /* cannot handle properly non-unique buffers*/ + /* create a MSBuffer using the mblk_t buffer */ + msg=ms_message_alloc(); + msbuf=ms_buffer_alloc(0); + msbuf->buffer=mp->b_datap->db_base; + msbuf->size=(char*)mp->b_datap->db_lim-(char*)mp->b_datap->db_base; + ms_message_set_buf(msg,msbuf); + msg->size=mp->b_wptr-mp->b_rptr; + msg->data=mp->b_rptr; + /* free the mblk_t */ + g_free(mp->b_datap); + g_free(mp); + return msg; +} + + +static MSRtpRecvClass *ms_rtp_recv_class=NULL; + +MSFilter * ms_rtp_recv_new(void) +{ + MSRtpRecv *r; + + r=g_new(MSRtpRecv,1); + ms_rtp_recv_init(r); + if (ms_rtp_recv_class==NULL) + { + ms_rtp_recv_class=g_new0(MSRtpRecvClass,1); + ms_rtp_recv_class_init(ms_rtp_recv_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_rtp_recv_class); + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_rtp_recv_init(MSRtpRecv *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->outfifos=r->f_outputs; + MS_FILTER(r)->outqueues=r->q_outputs; + memset(r->f_outputs,0,sizeof(MSFifo*)*MSRTPRECV_MAX_OUTPUTS); + memset(r->q_outputs,0,sizeof(MSFifo*)*MSRTPRECV_MAX_OUTPUTS); + r->rtpsession=NULL; + r->stream_started=0; +} + +void ms_rtp_recv_class_init(MSRtpRecvClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"RTPRecv"); + MS_FILTER_CLASS(klass)->max_qoutputs=MSRTPRECV_MAX_OUTPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSRTPRECV_MAX_OUTPUTS; + MS_FILTER_CLASS(klass)->w_maxgran=MSRTPRECV_DEF_GRAN; + ms_filter_class_set_attr(MS_FILTER_CLASS(klass),FILTER_IS_SOURCE); + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_rtp_recv_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_rtp_recv_process; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_rtp_recv_setup; +} + +void ms_rtp_recv_process(MSRtpRecv *r) +{ + MSFifo *fo; + MSQueue *qo; + MSSync *sync= r->sync; + void *d; + mblk_t *mp; + gint len; + gint gran=ms_sync_get_samples_per_tick(MS_SYNC(sync)); + + if (r->rtpsession==NULL) return; + /* process output fifo and output queue*/ + fo=r->f_outputs[0]; + if (fo!=NULL) + { + while( (mp=rtp_session_recvm_with_ts(r->rtpsession,r->prev_ts))!=NULL) { + /* try to get rtp packets and paste them to the output fifo */ + r->stream_started=1; + len=mp->b_cont->b_wptr-mp->b_cont->b_rptr; + ms_fifo_get_write_ptr(fo,len,&d); + if (d!=NULL){ + memcpy(d,mp->b_cont->b_rptr,len); + }else ms_warning("ms_rtp_recv_process: no space on output fifo !"); + freemsg(mp); + } + r->prev_ts+=gran; + + } + qo=r->q_outputs[0]; + if (qo!=NULL) + { + guint32 clock; + gint got=0; + /* we are connected with queues (surely for video)*/ + /* use the sync system time to compute a timestamp */ + PayloadType *pt=rtp_profile_get_payload(r->rtpsession->profile,r->rtpsession->payload_type); + if (pt==NULL) { + ms_warning("ms_rtp_recv_process(): NULL RtpPayload- skipping."); + return; + } + clock=(guint32)(((double)sync->time*(double)pt->clock_rate)/1000.0); + /*g_message("Querying packet with timestamp %u",clock);*/ + /* get rtp packet, and send them through the output queue */ + while ( (mp=rtp_session_recvm_with_ts(r->rtpsession,clock))!=NULL ){ + MSMessage *msg; + mblk_t *mdata; + /*g_message("Got packet with timestamp %u",clock);*/ + got++; + r->stream_started=1; + mdata=mp->b_cont; + freeb(mp); + msg=msgb_2_ms_message(mdata); + ms_queue_put(qo,msg); + } + } +} + +void ms_rtp_recv_destroy( MSRtpRecv *obj) +{ + g_free(obj); +} + +RtpSession * ms_rtp_recv_set_session(MSRtpRecv *obj,RtpSession *session) +{ + RtpSession *old=obj->rtpsession; + obj->rtpsession=session; + obj->prev_ts=0; + return old; +} + + +void ms_rtp_recv_setup(MSRtpRecv *r,MSSync *sync) +{ + r->sync=sync; + r->stream_started=0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.h new file mode 100644 index 00000000..8c2c2ed7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.h @@ -0,0 +1,80 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSRTPRECV_H +#define MSRTPRECV_H + +#include "msfilter.h" +#include "mssync.h" + +/* because of a conflict between config.h from oRTP and config.h from linphone:*/ +#undef PACKAGE +#undef VERSION +#include + +/*this is the class that implements a copy filter*/ + +#define MSRTPRECV_MAX_OUTPUTS 1 /* max output per filter*/ + +#define MSRTPRECV_DEF_GRAN 4096 /* the default granularity*/ + +struct _MSRtpRecv +{ + /* the MSCopy derivates from MSFilter, so the MSFilter object MUST be the first of the MSCopy object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_outputs[MSRTPRECV_MAX_OUTPUTS]; + MSQueue *q_outputs[MSRTPRECV_MAX_OUTPUTS]; + MSSync *sync; + RtpSession *rtpsession; + guint32 prev_ts; + gint stream_started; +}; + +typedef struct _MSRtpRecv MSRtpRecv; + +struct _MSRtpRecvClass +{ + /* the MSCopy derivates from MSFilter, so the MSFilter class MUST be the first of the MSCopy class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +}; + +typedef struct _MSRtpRecvClass MSRtpRecvClass; + +/* PUBLIC */ +#define MS_RTP_RECV(filter) ((MSRtpRecv*)(filter)) +#define MS_RTP_RECV_CLASS(klass) ((MSRtpRecvClass*)(klass)) +MSFilter * ms_rtp_recv_new(void); +RtpSession * ms_rtp_recv_set_session(MSRtpRecv *obj,RtpSession *session); +#define ms_rtp_recv_unset_session(obj) (ms_rtp_recv_set_session((obj),NULL)) +#define ms_rtp_recv_get_session(obj) ((obj)->rtpsession) + + + +/* FOR INTERNAL USE*/ +void ms_rtp_recv_init(MSRtpRecv *r); +void ms_rtp_recv_class_init(MSRtpRecvClass *klass); +void ms_rtp_recv_destroy( MSRtpRecv *obj); +void ms_rtp_recv_process(MSRtpRecv *r); +void ms_rtp_recv_setup(MSRtpRecv *r,MSSync *sync); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.c new file mode 100644 index 00000000..cfcb6b34 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.c @@ -0,0 +1,211 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msrtpsend.h" +#include +#include "mssync.h" +#include "mscodec.h" + + + +static MSRtpSendClass *ms_rtp_send_class=NULL; + +MSFilter * ms_rtp_send_new(void) +{ + MSRtpSend *r; + + r=g_new(MSRtpSend,1); + + if (ms_rtp_send_class==NULL) + { + ms_rtp_send_class=g_new(MSRtpSendClass,1); + ms_rtp_send_class_init(ms_rtp_send_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_rtp_send_class); + ms_rtp_send_init(r); + return(MS_FILTER(r)); +} + + +void ms_rtp_send_init(MSRtpSend *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->inqueues=r->q_inputs; + MS_FILTER(r)->r_mingran=MSRTPSEND_DEF_GRAN; + memset(r->f_inputs,0,sizeof(MSFifo*)*MSRTPSEND_MAX_INPUTS); + memset(r->q_inputs,0,sizeof(MSFifo*)*MSRTPSEND_MAX_INPUTS); + r->rtpsession=NULL; + r->ts=0; + r->ts_inc=0; + r->flags=0; + r->delay=0; +} + +void ms_rtp_send_class_init(MSRtpSendClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"RTPSend"); + MS_FILTER_CLASS(klass)->max_qinputs=MSRTPSEND_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_finputs=MSRTPSEND_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=MSRTPSEND_DEF_GRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_rtp_send_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_rtp_send_process; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_rtp_send_setup; +} + +void ms_rtp_send_set_timing(MSRtpSend *r, guint32 ts_inc, gint payload_size) +{ + r->ts_inc=ts_inc; + r->packet_size=payload_size; + if (r->ts_inc!=0) r->flags|=RTPSEND_CONFIGURED; + else r->flags&=~RTPSEND_CONFIGURED; + MS_FILTER(r)->r_mingran=payload_size; + /*g_message("ms_rtp_send_set_timing: ts_inc=%i",ts_inc);*/ +} + +guint32 get_new_timestamp(MSRtpSend *r,guint32 synctime) +{ + guint32 clockts; + /* use the sync system time to compute a timestamp */ + PayloadType *pt=rtp_profile_get_payload(r->rtpsession->profile,r->rtpsession->payload_type); + g_return_val_if_fail(pt!=NULL,0); + clockts=(guint32)(((double)synctime * (double)pt->clock_rate)/1000.0); + ms_trace("ms_rtp_send_process: sync->time=%i clock=%i",synctime,clockts); + if (r->flags & RTPSEND_CONFIGURED){ + if (RTP_TIMESTAMP_IS_STRICTLY_NEWER_THAN(clockts,r->ts+(2*r->ts_inc) )){ + r->ts=clockts; + } + else r->ts+=r->ts_inc; + }else{ + r->ts=clockts; + } + return r->ts; +} + + +void ms_rtp_send_process(MSRtpSend *r) +{ + MSFifo *fi; + MSQueue *qi; + MSSync *sync= r->sync; + int gran=ms_sync_get_samples_per_tick(sync); + guint32 ts; + void *s; + guint skip; + guint32 synctime=sync->time; + + g_return_if_fail(gran>0); + if (r->rtpsession==NULL) return; + + ms_filter_lock(MS_FILTER(r)); + skip=r->delay!=0; + if (skip) r->delay--; + /* process output fifo and output queue*/ + fi=r->f_inputs[0]; + if (fi!=NULL) + { + ts=get_new_timestamp(r,synctime); + /* try to read r->packet_size bytes and send them in a rtp packet*/ + ms_fifo_get_read_ptr(fi,r->packet_size,&s); + if (!skip){ + rtp_session_send_with_ts(r->rtpsession,s,r->packet_size,ts); + ms_trace("len=%i, ts=%i ",r->packet_size,ts); + } + } + qi=r->q_inputs[0]; + if (qi!=NULL) + { + MSMessage *msg; + /* read a MSMessage and send it through the network*/ + while ( (msg=ms_queue_get(qi))!=NULL){ + ts=get_new_timestamp(r,synctime); + if (!skip) { + /*g_message("Sending packet with ts=%u",ts);*/ + rtp_session_send_with_ts(r->rtpsession,msg->data,msg->size,ts); + + } + ms_message_destroy(msg); + } + } + ms_filter_unlock(MS_FILTER(r)); +} + +void ms_rtp_send_destroy( MSRtpSend *obj) +{ + g_free(obj); +} + +RtpSession * ms_rtp_send_set_session(MSRtpSend *obj,RtpSession *session) +{ + RtpSession *old=obj->rtpsession; + obj->rtpsession=session; + obj->ts=0; + obj->ts_inc=0; + return old; +} + +void ms_rtp_send_setup(MSRtpSend *r, MSSync *sync) +{ + MSFilter *codec; + MSCodecInfo *info; + r->sync=sync; + codec=ms_filter_search_upstream_by_type(MS_FILTER(r),MS_FILTER_AUDIO_CODEC); + if (codec==NULL) codec=ms_filter_search_upstream_by_type(MS_FILTER(r),MS_FILTER_VIDEO_CODEC); + if (codec==NULL){ + g_warning("ms_rtp_send_setup: could not find upstream codec."); + return; + } + info=MS_CODEC_INFO(codec->klass->info); + if (info->info.type==MS_FILTER_AUDIO_CODEC){ + int ts_inc=info->fr_size/2; + int psize=info->dt_size; + if (ts_inc==0){ + /* dont'use the normal frame size: this is a variable frame size codec */ + /* use the MS_FILTER(codec)->r_mingran */ + ts_inc=MS_FILTER(codec)->r_mingran/2; + psize=0; + } + ms_rtp_send_set_timing(r,ts_inc,psize); + } +} + +gint ms_rtp_send_dtmf(MSRtpSend *r, gchar dtmf) +{ + gint res; + + if (r->rtpsession==NULL) return -1; + if (rtp_session_telephone_events_supported(r->rtpsession)==-1){ + g_warning("ERROR : telephone events not supported.\n"); + return -1; + } + + ms_filter_lock(MS_FILTER(r)); + g_message("Sending DTMF."); + res=rtp_session_send_dtmf(r->rtpsession, dtmf, r->ts); + if (res==0){ + /* //r->ts+=r->ts_inc; */ + r->delay+=2; + }else g_warning("Could not send dtmf."); + + ms_filter_unlock(MS_FILTER(r)); + + return res; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.h new file mode 100644 index 00000000..b70f4e55 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.h @@ -0,0 +1,85 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSRTPSEND_H +#define MSRTPSEND_H + +#include "msfilter.h" +#include "mssync.h" + +#undef PACKAGE +#undef VERSION +#include + + +/*this is the class that implements a sending through rtp filter*/ + +#define MSRTPSEND_MAX_INPUTS 1 /* max input per filter*/ + +#define MSRTPSEND_DEF_GRAN 4096/* the default granularity*/ + +struct _MSRtpSend +{ + /* the MSCopy derivates from MSFilter, so the MSFilter object MUST be the first of the MSCopy object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSRTPSEND_MAX_INPUTS]; + MSQueue *q_inputs[MSRTPSEND_MAX_INPUTS]; + MSSync *sync; + RtpSession *rtpsession; + guint32 ts; + guint32 ts_inc; /* the timestamp increment */ + gint packet_size; + guint flags; + guint delay; /* number of _proccess call which must be skipped */ +#define RTPSEND_CONFIGURED (1) +}; + +typedef struct _MSRtpSend MSRtpSend; + +struct _MSRtpSendClass +{ + /* the MSRtpSend derivates from MSFilter, so the MSFilter class MUST be the first of the MSCopy class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +}; + +typedef struct _MSRtpSendClass MSRtpSendClass; + +/* PUBLIC */ +#define MS_RTP_SEND(filter) ((MSRtpSend*)(filter)) +#define MS_RTP_SEND_CLASS(klass) ((MSRtpSendClass*)(klass)) +MSFilter * ms_rtp_send_new(void); +RtpSession * ms_rtp_send_set_session(MSRtpSend *obj,RtpSession *session); +#define ms_rtp_send_unset_session(obj) (ms_rtp_send_set_session((obj),NULL)) +#define ms_rtp_send_get_session(obj) ((obj)->rtpsession) +void ms_rtp_send_set_timing(MSRtpSend *r, guint32 ts_inc, gint payload_size); +gint ms_rtp_send_dtmf(MSRtpSend *r, gchar dtmf); + + +/* FOR INTERNAL USE*/ +void ms_rtp_send_init(MSRtpSend *r); +void ms_rtp_send_class_init(MSRtpSendClass *klass); +void ms_rtp_send_destroy( MSRtpSend *obj); +void ms_rtp_send_process(MSRtpSend *r); +void ms_rtp_send_setup(MSRtpSend *r, MSSync *sync); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssdlout.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssdlout.h new file mode 100644 index 00000000..fd6ec547 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssdlout.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * mssdlout.h + * + * Mon Jul 11 16:18:55 2005 + * Copyright 2005 Simon Morlat + * Email simon dot morlat at linphone dot org + ****************************************************************************/ + +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef mssdlout_h +#define mssdlout_h + +#include "msfilter.h" + +#include +#include + +struct _MSSdlOut +{ + MSFilter parent; + MSQueue *input[2]; + gint width,height; + const gchar *format; + SDL_Surface *screen; + SDL_Overlay *overlay; + MSMessage *oldinm1; + gboolean use_yuv; +}; + + +typedef struct _MSSdlOut MSSdlOut; + +struct _MSSdlOutClass +{ + MSFilterClass parent_class; +}; + +typedef struct _MSSdlOutClass MSSdlOutClass; + +MSFilter * ms_sdl_out_new(void); +void ms_sdl_out_set_format(MSSdlOut *obj, const char *fmt); + +#define MS_SDL_OUT(obj) ((MSSdlOut*)obj) + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.c new file mode 100644 index 00000000..3803b018 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.c @@ -0,0 +1,39 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation + + */ + +#include "mssoundread.h" + + +void ms_sound_read_init(MSSoundRead *w) +{ + ms_filter_init(MS_FILTER(w)); + +} + +void ms_sound_read_class_init(MSSoundReadClass *klass) +{ + int i; + ms_filter_class_init(MS_FILTER_CLASS(klass)); + MS_FILTER_CLASS(klass)->max_foutputs=1; /* one fifo output only */ + + ms_filter_class_set_attr( MS_FILTER_CLASS(klass),FILTER_IS_SOURCE); +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.h new file mode 100644 index 00000000..7f2cab93 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.h @@ -0,0 +1,80 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef MSSOUNDREAD_H +#define MSSOUNDREAD_H + +#include "msfilter.h" +#include "mssync.h" + + + +struct _MSSoundRead +{ + /* the MSOssRead derivates from MSFilter, so the MSFilter object MUST be the first of the MSOssRead object + in order to the object mechanism to work*/ + MSFilter filter; +}; + +typedef struct _MSSoundRead MSSoundRead; + +struct _MSSoundReadClass +{ + /* the MSOssRead derivates from MSFilter, so the MSFilter class MUST be the first of the MSOssRead class + in order to the class mechanism to work*/ + MSFilterClass parent_class; + gint (*set_device)(MSSoundRead *, gint devid); + void (*start)(MSSoundRead *); + void (*stop)(MSSoundRead*); + void (*set_level)(MSSoundRead *, gint a); +}; + +typedef struct _MSSoundReadClass MSSoundReadClass; + +/* PUBLIC */ +#define MS_SOUND_READ(filter) ((MSSoundRead*)(filter)) +#define MS_SOUND_READ_CLASS(klass) ((MSSoundReadClass*)(klass)) + +static inline int ms_sound_read_set_device(MSSoundRead *r,gint devid) +{ + return MS_SOUND_READ_CLASS( MS_FILTER(r)->klass )->set_device(r,devid); +} + +static inline void ms_sound_read_start(MSSoundRead *r) +{ + MS_SOUND_READ_CLASS( MS_FILTER(r)->klass )->start(r); +} + +static inline void ms_sound_read_stop(MSSoundRead *w) +{ + MS_SOUND_READ_CLASS( MS_FILTER(w)->klass )->stop(w); +} + +static inline void ms_sound_read_set_level(MSSoundRead *w,gint a) +{ + MS_SOUND_READ_CLASS( MS_FILTER(w)->klass )->set_level(w,a); +} + +/* FOR INTERNAL USE*/ +void ms_sound_read_init(MSSoundRead *r); +void ms_sound_read_class_init(MSSoundReadClass *klass); + + +#endif + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.c new file mode 100644 index 00000000..9c5879f4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.c @@ -0,0 +1,39 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation + + */ + +#include "mssoundwrite.h" + + +void ms_sound_write_init(MSSoundWrite *w) +{ + ms_filter_init(MS_FILTER(w)); + +} + +void ms_sound_write_class_init(MSSoundWriteClass *klass) +{ + int i; + ms_filter_class_init(MS_FILTER_CLASS(klass)); + MS_FILTER_CLASS(klass)->max_finputs=1; /* one fifo output only */ + + ms_filter_class_set_attr( MS_FILTER_CLASS(klass),FILTER_IS_SINK); +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.h new file mode 100644 index 00000000..e6d79874 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.h @@ -0,0 +1,80 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef MSSOUNDWRITE_H +#define MSSOUNDWRITE_H + +#include "msfilter.h" +#include "mssync.h" + + + +struct _MSSoundWrite +{ + /* the MSOssWrite derivates from MSFilter, so the MSFilter object MUST be the first of the MSOssWrite object + in order to the object mechanism to work*/ + MSFilter filter; +}; + +typedef struct _MSSoundWrite MSSoundWrite; + +struct _MSSoundWriteClass +{ + /* the MSOssWrite derivates from MSFilter, so the MSFilter class MUST be the first of the MSOssWrite class + in order to the class mechanism to work*/ + MSFilterClass parent_class; + gint (*set_device)(MSSoundWrite *, gint devid); + void (*start)(MSSoundWrite *); + void (*stop)(MSSoundWrite*); + void (*set_level)(MSSoundWrite *, gint a); +}; + +typedef struct _MSSoundWriteClass MSSoundWriteClass; + +/* PUBLIC */ +#define MS_SOUND_WRITE(filter) ((MSSoundWrite*)(filter)) +#define MS_SOUND_WRITE_CLASS(klass) ((MSSoundWriteClass*)(klass)) + +static inline int ms_sound_write_set_device(MSSoundWrite *r,gint devid) +{ + return MS_SOUND_WRITE_CLASS( MS_FILTER(r)->klass )->set_device(r,devid); +} + +static inline void ms_sound_write_start(MSSoundWrite *r) +{ + MS_SOUND_WRITE_CLASS( MS_FILTER(r)->klass )->start(r); +} + +static inline void ms_sound_write_stop(MSSoundWrite *w) +{ + MS_SOUND_WRITE_CLASS( MS_FILTER(w)->klass )->stop(w); +} + +static inline void ms_sound_write_set_level(MSSoundWrite *w,gint a) +{ + MS_SOUND_WRITE_CLASS( MS_FILTER(w)->klass )->set_level(w,a); +} + +/* FOR INTERNAL USE*/ +void ms_sound_write_init(MSSoundWrite *r); +void ms_sound_write_class_init(MSSoundWriteClass *klass); + + +#endif + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.c new file mode 100644 index 00000000..b91ca360 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.c @@ -0,0 +1,218 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#ifdef HAVE_SPEEX + +#include "msspeexdec.h" + +#ifdef HAVE_GLIB +#include +#endif + +extern MSFilter * ms_speex_enc_new(); + +MSCodecInfo speex_info= +{ + { + "Speex codec", + 0, + MS_FILTER_AUDIO_CODEC, + ms_speex_dec_new, + "A high quality variable bit-rate codec from Jean Marc Valin and David Rowe." + }, + ms_speex_enc_new, + ms_speex_dec_new, + 0, /*frame size */ + 0, + 8000, /*minimal bitrate */ + -1, /* sampling frequency */ + 110, /* payload type */ + "speex", + 1, + 1 +}; + + + +void ms_speex_codec_init() +{ + + ms_filter_register(MS_FILTER_INFO(&speex_info)); + /* //ms_filter_register(MS_FILTER_INFO(&speex_lbr_info)); */ +} + +#ifdef HAVE_GLIB +gchar * g_module_check_init(GModule *module) +{ + ms_speex_codec_init(); + + return NULL; +} +#else +gchar * g_module_check_init() +{ + ms_speex_codec_init(); + + return NULL; +} +#endif + +static MSSpeexDecClass * ms_speex_dec_class=NULL; +/* //static MSSpeexDecClass * ms_speexnb_dec_class=NULL; */ + +MSFilter * ms_speex_dec_new() +{ + MSSpeexDec *obj=g_new(MSSpeexDec,1); + + if (ms_speex_dec_class==NULL){ + ms_speex_dec_class=g_new(MSSpeexDecClass,1); + ms_speex_dec_class_init(ms_speex_dec_class); + } + MS_FILTER(obj)->klass=MS_FILTER_CLASS(ms_speex_dec_class); + + ms_speex_dec_init(obj); + return MS_FILTER(obj); +} + +void ms_speex_dec_init(MSSpeexDec *obj) +{ + ms_filter_init(MS_FILTER(obj)); + obj->initialized=0; + MS_FILTER(obj)->outfifos=obj->outf; + MS_FILTER(obj)->inqueues=obj->inq; + obj->outf[0]=NULL; + obj->inq[0]=NULL; + obj->frequency=8000; /*default value */ + +} + +void ms_speex_dec_init_core(MSSpeexDec *obj,const SpeexMode *mode) +{ + int pf=1; + + obj->speex_state=speex_decoder_init(mode); + speex_bits_init(&obj->bits); + /* enable the perceptual post filter */ + speex_decoder_ctl(obj->speex_state,SPEEX_SET_PF, &pf); + + speex_mode_query(mode, SPEEX_MODE_FRAME_SIZE, &obj->frame_size); + + obj->initialized=1; +} + +int ms_speex_dec_set_property(MSSpeexDec *obj, MSFilterProperty prop, int *value) +{ + if (obj->initialized){ + /* we are called when speex is running !! forbid that! */ + ms_warning("ms_speex_dec_set_property: cannot call this function when running!"); + return -1; + } + switch(prop){ + case MS_FILTER_PROPERTY_FREQ: + obj->frequency=value[0]; + break; + } + return 0; +} + +void ms_speex_dec_setup(MSSpeexDec *obj) +{ + const SpeexMode *mode; + g_message("Speex decoder setup: freq=%i",obj->frequency); + if ( obj->frequency< 16000) mode=&speex_nb_mode; + else mode=&speex_wb_mode; + ms_speex_dec_init_core(obj,mode); +} + +void ms_speex_dec_unsetup(MSSpeexDec *obj) +{ + ms_speex_dec_uninit_core(obj); +} + +void ms_speex_dec_class_init(MSSpeexDecClass *klass) +{ + gint frame_size=0; + + ms_filter_class_init(MS_FILTER_CLASS(klass)); + /* use the largest frame size to configure fifos */ + speex_mode_query(&speex_wb_mode, SPEEX_MODE_FRAME_SIZE, &frame_size); + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_speex_dec_process; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_speex_dec_setup; + MS_FILTER_CLASS(klass)->unsetup=(MSFilterSetupFunc)ms_speex_dec_unsetup; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_speex_dec_destroy; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_speex_dec_set_property; + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"SpeexDecoder"); + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&speex_info; + MS_FILTER_CLASS(klass)->max_foutputs=1; + MS_FILTER_CLASS(klass)->max_qinputs=1; + MS_FILTER_CLASS(klass)->w_maxgran=frame_size*2; + ms_trace("ms_speex_dec_class_init: w_maxgran is %i.",MS_FILTER_CLASS(klass)->w_maxgran); +} + +void ms_speex_dec_uninit_core(MSSpeexDec *obj) +{ + speex_decoder_destroy(obj->speex_state); + obj->initialized=0; +} + +void ms_speex_dec_uninit(MSSpeexDec *obj) +{ + +} + +void ms_speex_dec_destroy(MSSpeexDec *obj) +{ + ms_speex_dec_uninit(obj); + g_free(obj); +} + +void ms_speex_dec_process(MSSpeexDec *obj) +{ + MSFifo *outf=obj->outf[0]; + MSQueue *inq=obj->inq[0]; + gint16 *output; + gint gran=obj->frame_size*2; + gint i; + MSMessage *m; + + g_return_if_fail(inq!=NULL); + g_return_if_fail(outf!=NULL); + + m=ms_queue_get(inq); + g_return_if_fail(m!=NULL); + speex_bits_reset(&obj->bits); + ms_fifo_get_write_ptr(outf,gran,(void**)&output); + g_return_if_fail(output!=NULL); + if (m->data!=NULL){ + + speex_bits_read_from(&obj->bits,m->data,m->size); + /* decode */ + speex_decode_int(obj->speex_state,&obj->bits,(short*)output); + }else{ + /* we have a missing packet */ + speex_decode_int(obj->speex_state,NULL,(short*)output); + } + ms_message_destroy(m); + +} + +#endif /* HAVE_SPEEX */ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.h new file mode 100644 index 00000000..d4e745fe --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.h @@ -0,0 +1,69 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSSPEEXDEC_H +#define MSSPEEXDEC_H + +#include +#include + +struct _MSSpeexDec +{ + MSFilter parent; + MSQueue *inq[1]; /* speex has an input q because it can be variable bit rate */ + MSFifo *outf[1]; + void *speex_state; + SpeexBits bits; + int frequency; + int frame_size; + int initialized; +}; + +typedef struct _MSSpeexDec MSSpeexDec; + + +struct _MSSpeexDecClass +{ + MSFilterClass parent; +}; + +typedef struct _MSSpeexDecClass MSSpeexDecClass; + + +#define MS_SPEEX_DEC(o) ((MSSpeexDec*)(o)) +#define MS_SPEEX_DEC_CLASS(o) ((MSSpeexDecClass*)(o)) + +/* call this before if don't load the plugin dynamically */ +void ms_speex_codec_init(); + +/* mediastreamer compliant constructor */ +MSFilter * ms_speex_dec_new(); + +void ms_speex_dec_init(MSSpeexDec *obj); +void ms_speex_dec_init_core(MSSpeexDec *obj,const SpeexMode *mode); +void ms_speex_dec_class_init(MSSpeexDecClass *klass); +void ms_speex_dec_uninit(MSSpeexDec *obj); +void ms_speex_dec_uninit_core(MSSpeexDec *obj); + +void ms_speex_dec_process(MSSpeexDec *obj); +void ms_speex_dec_destroy(MSSpeexDec *obj); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.c new file mode 100644 index 00000000..abf976e6 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.c @@ -0,0 +1,192 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#ifdef HAVE_SPEEX + +#include "msspeexenc.h" +#include "ms.h" +extern MSCodecInfo speex_info; + +static MSSpeexEncClass * ms_speex_enc_class=NULL; + +MSFilter * ms_speex_enc_new() +{ + MSSpeexEnc *obj=g_new(MSSpeexEnc,1); + + if (ms_speex_enc_class==NULL){ + ms_speex_enc_class=g_new(MSSpeexEncClass,1); + ms_speex_enc_class_init(ms_speex_enc_class); + } + MS_FILTER(obj)->klass=MS_FILTER_CLASS(ms_speex_enc_class); + ms_speex_enc_init(MS_SPEEX_ENC(obj)); + return MS_FILTER(obj); +} + +void ms_speex_enc_init(MSSpeexEnc *obj) +{ + ms_filter_init(MS_FILTER(obj)); + MS_FILTER(obj)->infifos=obj->inf; + MS_FILTER(obj)->outqueues=obj->outq; + obj->inf[0]=NULL; + obj->outq[0]=NULL; + obj->frequency=8000; + obj->bitrate=30000; + obj->initialized=0; +} + +void ms_speex_enc_init_core(MSSpeexEnc *obj,const SpeexMode *mode, gint bitrate) +{ + int proc_type, proc_speed; + gchar *proc_vendor; + int tmp; + int frame_size; + + obj->speex_state=speex_encoder_init(mode); + speex_bits_init(&obj->bits); + + if (bitrate>0) { + bitrate++; + speex_encoder_ctl(obj->speex_state, SPEEX_SET_BITRATE, &bitrate); + g_message("Setting speex output bitrate less or equal than %i",bitrate-1); + } + + proc_speed=ms_proc_get_speed(); + proc_vendor=ms_proc_get_param("vendor_id"); + if (proc_speed<0 || proc_vendor==NULL){ + g_warning("Can't guess processor features: setting speex encoder to its lowest complexity."); + tmp=1; + speex_encoder_ctl(obj->speex_state,SPEEX_SET_COMPLEXITY,&tmp); + }else if ((proc_speed!=-1) && (proc_speed<200)){ + g_warning("A cpu speed less than 200 Mhz is not enough: let's reduce the complexity of the speex codec."); + tmp=1; + speex_encoder_ctl(obj->speex_state,SPEEX_SET_COMPLEXITY,&tmp); + }else if (proc_vendor!=NULL) { + if (strncmp(proc_vendor,"GenuineIntel",strlen("GenuineIntel"))==0){ + proc_type=ms_proc_get_type(); + if (proc_type==5){ + g_warning("A pentium I is not enough fast for speex codec in normal mode: let's reduce its complexity."); + tmp=1; + speex_encoder_ctl(obj->speex_state,SPEEX_SET_COMPLEXITY,&tmp); + } + } + g_free(proc_vendor); + } + /* guess the used input frame size */ + speex_mode_query(mode, SPEEX_MODE_FRAME_SIZE, &frame_size); + MS_FILTER(obj)->r_mingran=frame_size*2; + ms_trace("ms_speex_init: using frame size of %i.",MS_FILTER(obj)->r_mingran); + + obj->initialized=1; +} + +/* must be called before the encoder is running*/ +int ms_speex_enc_set_property(MSSpeexEnc *obj,int property,int *value) +{ + if (obj->initialized){ + /* we are called when speex is running !! forbid that! */ + ms_warning("ms_speex_enc_set_property: cannot call this function when running!"); + return -1; + } + switch(property){ + case MS_FILTER_PROPERTY_FREQ: + obj->frequency=value[0]; + break; + case MS_FILTER_PROPERTY_BITRATE: /* to specify max bitrate */ + obj->bitrate=value[0]; + break; + } + return 0; +} + +void ms_speex_enc_setup(MSSpeexEnc *obj) +{ + const SpeexMode *mode; + int quality; + g_message("Speex encoder setup: freq=%i",obj->frequency); + if ( obj->frequency< 16000) mode=&speex_nb_mode; + else mode=&speex_wb_mode; + ms_speex_enc_init_core(obj,mode,obj->bitrate); + +} + +void ms_speex_enc_unsetup(MSSpeexEnc *obj) +{ + ms_speex_enc_uninit_core(obj); +} + +void ms_speex_enc_class_init(MSSpeexEncClass *klass) +{ + gint frame_size=0; + + ms_filter_class_init(MS_FILTER_CLASS(klass)); + /* we take the larger (wb) frame size */ + speex_mode_query(&speex_wb_mode, SPEEX_MODE_FRAME_SIZE, &frame_size); + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_speex_enc_process; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_speex_enc_destroy; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_speex_enc_setup; + MS_FILTER_CLASS(klass)->unsetup=(MSFilterSetupFunc)ms_speex_enc_unsetup; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_speex_enc_set_property; + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"SpeexEncoder"); + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&speex_info; + MS_FILTER_CLASS(klass)->max_finputs=1; + MS_FILTER_CLASS(klass)->max_qoutputs=1; + MS_FILTER_CLASS(klass)->r_maxgran=frame_size*2; + ms_trace("ms_speex_enc_class_init: r_maxgran is %i.",MS_FILTER_CLASS(klass)->r_maxgran); +} + +void ms_speex_enc_uninit_core(MSSpeexEnc *obj) +{ + if (obj->initialized){ + speex_encoder_destroy(obj->speex_state); + obj->initialized=0; + } +} + +void ms_speex_enc_destroy(MSSpeexEnc *obj) +{ + ms_speex_enc_uninit_core(obj); + g_free(obj); +} + +void ms_speex_enc_process(MSSpeexEnc *obj) +{ + MSFifo *inf=obj->inf[0]; + MSQueue *outq=obj->outq[0]; + gint16 *input; + gint gran=MS_FILTER(obj)->r_mingran; + gint i; + MSMessage *m; + + g_return_if_fail(inf!=NULL); + g_return_if_fail(outq!=NULL); + + ms_fifo_get_read_ptr(inf,gran,(void**)&input); + g_return_if_fail(input!=NULL); + /* encode */ + speex_bits_reset(&obj->bits); + speex_encode_int(obj->speex_state,(short*)input,&obj->bits); + m=ms_message_new(speex_bits_nbytes(&obj->bits)); + m->size=speex_bits_write(&obj->bits,m->data,m->size); + ms_queue_put(outq,m); +} + +#endif /* HAVE_SPEEX */ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.h new file mode 100644 index 00000000..41655b9f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.h @@ -0,0 +1,66 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSSPEEXENC_H +#define MSSPEEXENC_H + +#include +#include + +struct _MSSpeexEnc +{ + MSFilter parent; + MSFifo *inf[1]; + MSQueue *outq[1]; /* speex has an output q because it can be variable bit rate */ + void *speex_state; + SpeexBits bits; + int frequency; + int bitrate; + int initialized; +}; + +typedef struct _MSSpeexEnc MSSpeexEnc; + + +struct _MSSpeexEncClass +{ + MSFilterClass parent; +}; + +typedef struct _MSSpeexEncClass MSSpeexEncClass; + + +#define MS_SPEEX_ENC(o) ((MSSpeexEnc*)(o)) +#define MS_SPEEX_ENC_CLASS(o) ((MSSpeexEncClass*)(o)) + +/* generic constructor */ +MSFilter * ms_speex_enc_new(); + +void ms_speex_enc_init_core(MSSpeexEnc *obj,const SpeexMode *mode, gint quality); +void ms_speex_enc_uninit_core(MSSpeexEnc *obj); +void ms_speex_enc_init(MSSpeexEnc *obj); +void ms_speex_enc_class_init(MSSpeexEncClass *klass); + + +void ms_speex_enc_process(MSSpeexEnc *obj); +void ms_speex_enc_destroy(MSSpeexEnc *obj); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.c new file mode 100644 index 00000000..7656211b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.c @@ -0,0 +1,193 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "mssync.h" +#include + +/* TODO: + -define an uninit function that free the mutex +*/ + +/** + * function_name:ms_sync_get_bytes_per_tick + * @sync: A #MSSync object. + * + * Returns the number of bytes per tick. This is a usefull information for sources, so + * that they can know how much data they must deliver each time they are called. + * + */ + +/* private */ +void ms_sync_init(MSSync *sync) +{ + sync->klass=NULL; + sync->lock=g_mutex_new(); + sync->thread_cond=g_cond_new(); + sync->stop_cond=g_cond_new(); + sync->attached_filters=NULL; + sync->execution_list=NULL; + sync->filters=0; + sync->run=0; + sync->flags=0; + sync->samples_per_tick=0; + sync->ticks=0; + sync->time=0; + sync->thread=NULL; +} + +void ms_sync_class_init(MSSyncClass *klass) +{ + klass->max_filters=0; + klass->synchronize=NULL; + klass->attach=ms_sync_attach_generic; + klass->detach=ms_sync_detach_generic; + klass->destroy=NULL; +} + +/* public*/ + + +/** + * ms_sync_attach: + * @sync: A #MSSync object. + * @f: A #MSFilter object. + * + * Attach a chain of filters to a synchronisation source @sync. Filter @f must be the first filter of the processing chain. + * In order to be run, each chain of filter must be attached to a synchronisation source, that will be responsible for scheduling + * the processing. Multiple chains can be attached to a single synchronisation. + * + * Returns: 0 if successfull, a negative value reprensenting the errno.h error. + */ +int ms_sync_attach(MSSync *sync,MSFilter *f) +{ + gint err; + ms_sync_lock(sync); + err=sync->klass->attach(sync,f); + ms_sync_update(sync); + ms_sync_unlock(sync); + return(err); +} + +int ms_sync_attach_generic(MSSync *sync,MSFilter *f) +{ + int i; + /* //printf("attr: %i\n",f->klass->attributes); */ + g_return_val_if_fail(f->klass->attributes & FILTER_IS_SOURCE,-EINVAL); + g_return_val_if_fail(sync->attached_filters!=NULL,-EFAULT); + + + /* find a free place to attach*/ + for (i=0;iklass->max_filters;i++) + { + if (sync->attached_filters[i]==NULL) + { + sync->attached_filters[i]=f; + sync->filters++; + ms_trace("Filter succesfully attached to sync."); + return 0; + } + } + g_warning("No more link on sync !"); + return(-EMLINK); +} + +/** + * ms_sync_detach: + * @sync: A #MSSync object. + * @f: A #MSFilter object. + * + * Dettach a chain of filters to a synchronisation source. Filter @f must be the first filter of the processing chain. + * The processing chain will no more be executed. + * + * Returns: 0 if successfull, a negative value reprensenting the errno.h error. + */ +int ms_sync_detach(MSSync *sync,MSFilter *f) +{ + gint err; + ms_sync_lock(sync); + err=sync->klass->detach(sync,f); + ms_sync_update(sync); + ms_sync_unlock(sync); + return(err); +} + +int ms_sync_detach_generic(MSSync *sync,MSFilter *f) +{ + int i; + g_return_val_if_fail(f->klass->attributes & FILTER_IS_SOURCE,-EINVAL); + g_return_val_if_fail(sync->attached_filters!=NULL,-EFAULT); + for (i=0;ifilters;i++) + { + if (sync->attached_filters[i]==f) + { + sync->attached_filters[i]=NULL; + sync->filters--; + return 0; + } + } + return(-EMLINK); +} + +void ms_sync_set_samples_per_tick(MSSync *sync,gint size) +{ + if (sync->samples_per_tick==0) + { + sync->samples_per_tick=size; + g_cond_signal(sync->thread_cond); + } + else sync->samples_per_tick=size; +} + +/* call the setup func of each filter attached to the graph */ +void ms_sync_setup(MSSync *sync) +{ + GList *elem=sync->execution_list; + MSFilter *f; + while(elem!=NULL){ + f=(MSFilter*)elem->data; + if (f->klass->setup!=NULL){ + f->klass->setup(f,sync); + } + elem=g_list_next(elem); + } +} + +/* call the unsetup func of each filter attached to the graph */ +void ms_sync_unsetup(MSSync *sync) +{ + GList *elem=sync->execution_list; + MSFilter *f; + while(elem!=NULL){ + f=(MSFilter*)elem->data; + if (f->klass->unsetup!=NULL){ + f->klass->unsetup(f,sync); + } + elem=g_list_next(elem); + } +} + + +int ms_sync_uninit(MSSync *sync) +{ + g_mutex_free(sync->lock); + g_cond_free(sync->thread_cond); + g_cond_free(sync->stop_cond); +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.h new file mode 100644 index 00000000..012c068f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.h @@ -0,0 +1,136 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MS_SYNC_H +#define MS_SYNC_H + + +#include "msfilter.h" + +struct _MSSync +{ + struct _MSSyncClass *klass; + GMutex *lock; + MSFilter **attached_filters; /* pointer to a table of pointer of filters*/ + GList *execution_list; /* the list of filters to be executed. This is filled with compilation */ + gint filters; /*number of filters attached to the sync */ + gint run; /* flag to indicate whether the sync must be run or not */ + GThread * thread; /* the thread ressource if this sync is run by a thread*/ + GCond *thread_cond; + GCond *stop_cond; + guint32 flags; + gint interval; /* in miliseconds*/ +#define MS_SYNC_NEED_UPDATE (0x0001) /* a modification has occured in the processing chains + attached to this sync; so the execution list has to be updated */ + guint samples_per_tick; /* number of bytes produced by sources of the processing chains*/ + guint32 ticks; + guint32 time; /* a time since the start of the sync expressed in milisec*/ +}; + +typedef struct _MSSync MSSync; + +typedef void (*MSSyncDestroyFunc)(MSSync*); +typedef void (*MSSyncSyncFunc)(MSSync*); +typedef int (*MSSyncAttachFunc)(MSSync*,MSFilter*); +typedef int (*MSSyncDetachFunc)(MSSync*,MSFilter*); + +typedef struct _MSSyncClass +{ + gint max_filters; /* the maximum number of filters that can be attached to this sync*/ + MSSyncSyncFunc synchronize; + MSSyncDestroyFunc destroy; + MSSyncAttachFunc attach; + MSSyncDetachFunc detach; +} MSSyncClass; + +/* private */ +void ms_sync_init(MSSync *sync); +void ms_sync_class_init(MSSyncClass *klass); + +int ms_sync_attach_generic(MSSync *sync,MSFilter *f); +int ms_sync_detach_generic(MSSync *sync,MSFilter *f); + +/* public*/ + +#define MS_SYNC(sync) ((MSSync*)(sync)) +#define MS_SYNC_CLASS(klass) ((MSSyncClass*)(klass)) + +#define ms_sync_synchronize(_sync) \ +do \ +{ \ + MSSync *__sync=_sync; \ + __sync->ticks++; \ + ((__sync)->klass->synchronize((__sync))); \ +}while(0) + +void ms_sync_setup(MSSync *sync); + +void ms_sync_unsetup(MSSync *sync); + +#define ms_sync_update(sync) (sync)->flags|=MS_SYNC_NEED_UPDATE + +#define ms_sync_get_samples_per_tick(sync) ((sync)->samples_per_tick) + +void ms_sync_set_samples_per_tick(MSSync *sync,gint size); + +#define ms_sync_get_tick_count(sync) ((sync)->ticks) + +#define ms_sync_suspend(sync) g_cond_wait((sync)->thread_cond,(sync)->lock) + +#define ms_sync_lock(sync) g_mutex_lock((sync)->lock) + +#define ms_sync_unlock(sync) g_mutex_unlock((sync)->lock) + +#define ms_sync_trylock(sync) g_mutex_trylock((sync)->lock) + +/** + * function_name:ms_sync_attach + * @sync: A #MSSync object. + * @f: A #MSFilter object. + * + * Attach a chain of filters to a synchronisation source. Filter @f must be the first filter of the processing chain. + * + * Returns: 0 if successfull, a negative value reprensenting the errno.h error. + */ +int ms_sync_attach(MSSync *sync,MSFilter *f); + +/** + * ms_sync_detach: + * @sync: A #MSSync object. + * @f: A #MSFilter object. + * + * Dettach a chain of filters to a synchronisation source. Filter @f must be the first filter of the processing chain. + * The processing chain will no more be executed. + * + * Returns: 0 if successfull, a negative value reprensenting the errno.h error. + */ +int ms_sync_detach(MSSync *sync,MSFilter *f); + +int ms_sync_uninit(MSSync *sync); + +#define ms_sync_start(sync) ms_start((sync)) +#define ms_sync_stop(sync) ms_stop((sync)) + + +/*destroy*/ +#define ms_sync_destroy(sync) (sync)->klass->destroy((sync)) + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.c new file mode 100644 index 00000000..29b81d3c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.c @@ -0,0 +1,114 @@ + /* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "mstimer.h" +#include +#include +#include +#include + +static MSTimerClass *ms_timer_class=NULL; + + +void ms_timer_init(MSTimer *sync) +{ + ms_sync_init(MS_SYNC(sync)); + MS_SYNC(sync)->attached_filters=sync->filters; + memset(sync->filters,0,MSTIMER_MAX_FILTERS*sizeof(MSFilter*)); + MS_SYNC(sync)->samples_per_tick=160; + ms_timer_set_interval(sync,20); + sync->state=MS_TIMER_STOPPED; +} + +void ms_timer_class_init(MSTimerClass *klass) +{ + ms_sync_class_init(MS_SYNC_CLASS(klass)); + MS_SYNC_CLASS(klass)->max_filters=MSTIMER_MAX_FILTERS; + MS_SYNC_CLASS(klass)->synchronize=(MSSyncSyncFunc)ms_timer_synchronize; + MS_SYNC_CLASS(klass)->destroy=(MSSyncDestroyFunc)ms_timer_destroy; + /* no need to overload these function*/ + MS_SYNC_CLASS(klass)->attach=ms_sync_attach_generic; + MS_SYNC_CLASS(klass)->detach=ms_sync_detach_generic; +} + +void ms_timer_destroy(MSTimer *timer) +{ + g_free(timer); +} + + +void ms_timer_synchronize(MSTimer *timer) +{ + /* //printf("ticks=%i \n",MS_SYNC(timer)->ticks); */ + if (timer->state==MS_TIMER_STOPPED){ + timer->state=MS_TIMER_RUNNING; + gettimeofday(&timer->orig,NULL); + timer->sync.time=0; + } + else { + gint32 diff,time; + struct timeval tv,cur; + + gettimeofday(&cur,NULL); + time=((cur.tv_usec-timer->orig.tv_usec)/1000 ) + ((cur.tv_sec-timer->orig.tv_sec)*1000 ); + if ( (diff=time-timer->sync.time)>50){ + g_warning("Must catchup %i miliseconds.",diff); + } + while((diff = timer->sync.time-time) > 0) + { + tv.tv_sec = diff/1000; + tv.tv_usec = (diff%1000)*1000; + select(0,NULL,NULL,NULL,&tv); + gettimeofday(&cur,NULL); + time=((cur.tv_usec-timer->orig.tv_usec)/1000 ) + ((cur.tv_sec-timer->orig.tv_sec)*1000 ); + } + } + timer->sync.time+=timer->milisec; + return; +} + + +MSSync *ms_timer_new() +{ + MSTimer *timer; + + timer=g_malloc(sizeof(MSTimer)); + ms_timer_init(timer); + if (ms_timer_class==NULL) + { + ms_timer_class=g_new(MSTimerClass,1); + ms_timer_class_init(ms_timer_class); + } + MS_SYNC(timer)->klass=MS_SYNC_CLASS(ms_timer_class); + return(MS_SYNC(timer)); +} + +void ms_timer_set_interval(MSTimer *timer, int milisec) +{ + + MS_SYNC(timer)->ticks=0; + MS_SYNC(timer)->interval=milisec; + timer->interval.tv_sec=milisec/1000; + timer->interval.tv_usec=(milisec % 1000)*1000; + timer->milisec=milisec; + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.h new file mode 100644 index 00000000..5c7e8ede --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.h @@ -0,0 +1,68 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef MSTIMER_H +#define MSTIMER_H + +#include "mssync.h" +#include + +#define MSTIMER_MAX_FILTERS 10 + +/* MSTimer derivates from MSSync base class*/ + +typedef struct _MSTimer +{ + /* the MSSync must be the first field of the object in order to the object mechanism to work*/ + MSSync sync; + MSFilter *filters[MSTIMER_MAX_FILTERS]; + gint milisec; /* the interval */ + struct timeval interval; + struct timeval orig; + gint state; +} MSTimer; + + +typedef struct _MSTimerClass +{ + /* the MSSyncClass must be the first field of the class in order to the class mechanism to work*/ + MSSyncClass parent_class; +} MSTimerClass; + + +/*private*/ +#define MS_TIMER_RUNNING 1 +#define MS_TIMER_STOPPED 0 +void ms_timer_init(MSTimer *sync); +void ms_timer_class_init(MSTimerClass *sync); + +void ms_timer_destroy(MSTimer *timer); +void ms_timer_synchronize(MSTimer *timer); + +/*public*/ +void ms_timer_set_interval(MSTimer *timer, gint milisec); + +/* casts a MSSync object into a MSTimer */ +#define MS_TIMER(sync) ((MSTimer*)(sync)) +/* casts a MSSync class into a MSTimer class */ +#define MS_TIMER_CLASS(klass) ((MSTimerClass*)(klass)) + +MSSync *ms_timer_new(); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechdecoder.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechdecoder.h new file mode 100644 index 00000000..62477436 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechdecoder.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2003 Robert W. Brewer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSTRUESPEECHDECODER_H +#define MSTRUESPEECHDECODER_H + +#include "msfilter.h" +#include "mstruespeechencoder.h" + + + +typedef struct _MSTrueSpeechDecoder +{ + /* the MSTrueSpeechDecoder derives from MSFilter, so the MSFilter + object MUST be the first of the MSTrueSpeechDecoder object + in order for the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MS_TRUESPEECH_CODEC_MAX_IN_OUT]; + MSFifo *f_outputs[MS_TRUESPEECH_CODEC_MAX_IN_OUT]; + Win32Codec* codec; +} MSTrueSpeechDecoder; + +typedef struct _MSTrueSpeechDecoderClass +{ + /* the MSTrueSpeechDecoder derives from MSFilter, + so the MSFilter class MUST be the first of the MSTrueSpechDecoder + class + in order for the class mechanism to work*/ + MSFilterClass parent_class; + Win32CodecDriver* driver; +} MSTrueSpeechDecoderClass; + +/* PUBLIC */ +#define MS_TRUESPEECHDECODER(filter) ((MSTrueSpechMDecoder*)(filter)) +#define MS_TRUESPEECHDECODER_CLASS(klass) ((MSTrueSpeechDecoderClass*)(klass)) +MSFilter * ms_truespeechdecoder_new(void); + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechencoder.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechencoder.h new file mode 100644 index 00000000..04e40bb8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechencoder.h @@ -0,0 +1,62 @@ +/* + Copyright (C) 2003 Robert W. Brewer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSTRUESPEECHENCODER_H +#define MSTRUESPEECHENCODER_H + +#include "msfilter.h" +#include + + +#define MS_TRUESPEECH_CODEC_MAX_IN_OUT 1 /* max inputs/outputs per filter*/ + +#define TRUESPEECH_FORMAT_TAG 0x22 +#define TRUESPEECH_DLL "tssoft32.acm" + +typedef struct _MSTrueSpeechEncoder +{ + /* the MSTrueSpeechEncoder derives from MSFilter, so the MSFilter + object MUST be the first of the MSTrueSpeechEncoder object + in order for the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MS_TRUESPEECH_CODEC_MAX_IN_OUT]; + MSFifo *f_outputs[MS_TRUESPEECH_CODEC_MAX_IN_OUT]; + Win32Codec* codec; +} MSTrueSpeechEncoder; + +typedef struct _MSTrueSpeechEncoderClass +{ + /* the MSTrueSpeechEncoder derives from MSFilter, + so the MSFilter class MUST be the first of the MSTrueSpechEncoder + class + in order for the class mechanism to work*/ + MSFilterClass parent_class; + Win32CodecDriver* driver; +} MSTrueSpeechEncoderClass; + +/* PUBLIC */ +#define MS_TRUESPEECHENCODER(filter) ((MSTrueSpechMEncoder*)(filter)) +#define MS_TRUESPEECHENCODER_CLASS(klass) ((MSTrueSpeechEncoderClass*)(klass)) +MSFilter * ms_truespeechencoder_new(void); + +/* for internal use only */ +WAVEFORMATEX* ms_truespeechencoder_wf_create(); + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msutils.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msutils.h new file mode 100644 index 00000000..012b87d8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msutils.h @@ -0,0 +1,61 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef MSUTILS_H +#define MSUTILS_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_GLIB +#include +#else +#include +#endif +#include + +#ifndef ENODATA +/* this is for freeBSD .*/ +#define ENODATA EWOULDBLOCK +#endif + +#ifdef MS_DEBUG + +#define ms_trace g_message + +#else + +#define ms_trace(...) +#endif + +#define ms_warning g_warning +#define ms_error g_error + +#define VIDEO_SIZE_CIF_W 352 +#define VIDEO_SIZE_CIF_H 288 +#define VIDEO_SIZE_QCIF_W 176 +#define VIDEO_SIZE_QCIF_H 144 +#define VIDEO_SIZE_4CIF_W 704 +#define VIDEO_SIZE_4CIF_H 576 +#define VIDEO_SIZE_MAX_W VIDEO_SIZE_4CIF_W +#define VIDEO_SIZE_MAX_H VIDEO_SIZE_4CIF_H + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msv4l.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msv4l.h new file mode 100644 index 00000000..e19ac9ea --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msv4l.h @@ -0,0 +1,96 @@ + /* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MSV4L_H +#define MSV4L_H + +#include +#include +#include + +struct _MSV4l +{ + MSVideoSource parent; + int fd; + char *device; + struct video_capability cap; + struct video_channel channel; + struct video_window win; + struct video_picture pict; + struct video_mmap vmap; + struct video_mbuf vmbuf; + struct video_capture vcap; + gint bsize; + gint use_mmap; + gint frame; + guint query_frame; + gchar *mmapdbuf; /* the mmap'd buffer */ + MSBuffer img[VIDEO_MAX_FRAME]; /* the buffer wrappers used for mmaps */ + gint width; /* the capture image size - can be cropped to output size */ + gint height; + MSBuffer *allocdbuf; /* the buffer allocated for read() and mire */ + gint count; + MSBuffer *image_grabbed; + GCond *cond; + GCond *stopcond; + GThread *v4lthread; + gboolean grab_image; + gboolean thread_run; + gboolean thread_exited; +}; + +typedef struct _MSV4l MSV4l; + + +struct _MSV4lClass +{ + MSVideoSourceClass parent_class; + +}; + +typedef struct _MSV4lClass MSV4lClass; + + +/* PUBLIC API */ +#define MS_V4L(v) ((MSV4l*)(v)) +#define MS_V4L_CLASS(k) ((MSV4lClass*)(k)) +MSFilter * ms_v4l_new(); + +void ms_v4l_start(MSV4l *obj); +void ms_v4l_stop(MSV4l *obj); +int ms_v4l_set_device(MSV4l *f, const gchar *device); +gint ms_v4l_get_width(MSV4l *v4l); +gint ms_v4l_get_height(MSV4l *v4l); +void ms_v4l_set_size(MSV4l *v4l, gint w, gint h); + +/* PRIVATE API */ +void ms_v4l_init(MSV4l *obj); +void ms_v4l_class_init(MSV4lClass *klass); +int v4l_configure(MSV4l *f); + +void v4l_process(MSV4l *obj); + +void ms_v4l_uninit(MSV4l *obj); + +void ms_v4l_destroy(MSV4l *obj); + +extern MSFilterInfo v4l_info; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msvideosource.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msvideosource.h new file mode 100644 index 00000000..9a27f836 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msvideosource.h @@ -0,0 +1,74 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MSVIDEOSOURCE_H +#define MSVIDEOSOURCE_H + + +#include "msfilter.h" + +/* this is the video input abstract class */ + +#define MSVIDEOSOURCE_MAX_OUTPUTS 1 /* max output per filter*/ + +typedef struct _MSVideoSource +{ + /* the MSVideoSource derivates from MSFilter, so the MSFilter object MUST be the first of the MSVideoSource object + in order to the object mechanism to work*/ + MSFilter filter; + MSQueue *outputs[MSVIDEOSOURCE_MAX_OUTPUTS]; + gchar *dev_name; + gint width, height; + gchar *format; + gint frame_rate; + gint frame_rate_base; +} MSVideoSource; + +typedef struct _MSVideoSourceClass +{ + /* the MSVideoSource derivates from MSFilter, so the MSFilter class MUST be the first of the MSVideoSource class + in order to the class mechanism to work*/ + MSFilterClass parent_class; + gint (*set_device)(MSVideoSource *s, const gchar *name); + void (*start)(MSVideoSource *s); + void (*stop)(MSVideoSource *s); + void (*set_size)(MSVideoSource *s, gint width, gint height); + void (*set_frame_rate)(MSVideoSource *s, gint frame_rate, gint frame_rate_base); +} MSVideoSourceClass; + +/* PUBLIC */ +void ms_video_source_register_all(); +int ms_video_source_set_device(MSVideoSource *f, const gchar *device); +gchar* ms_video_source_get_device_name(MSVideoSource *f); +void ms_video_source_start(MSVideoSource *f); +void ms_video_source_stop(MSVideoSource *f); +void ms_video_source_set_size(MSVideoSource *f, gint width, gint height); +void ms_video_source_set_frame_rate(MSVideoSource *f, gint frame_rate, gint frame_rate_base); +gchar* ms_video_source_get_format(MSVideoSource *f); + +#define MS_VIDEO_SOURCE(obj) ((MSVideoSource*)(obj)) +#define MS_VIDEO_SOURCE_CLASS(klass) ((MSVideoSourceClass*)(klass)) + + +/* FOR INTERNAL USE*/ +void ms_video_source_init(MSVideoSource *f); +void ms_video_source_class_init(MSVideoSourceClass *klass); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.c new file mode 100644 index 00000000..178e294c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.c @@ -0,0 +1,121 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "mswrite.h" +#include +#include +#include +#include +#include +#include + +static MSWriteClass *ms_write_class=NULL; + +MSFilter * ms_write_new(char *name) +{ + MSWrite *r; + int fd=-1; + + r=g_new(MSWrite,1); + ms_write_init(r); + if (ms_write_class==NULL) + { + ms_write_class=g_new(MSWriteClass,1); + ms_write_class_init(ms_write_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_write_class); + if ((name!=NULL) && (strlen(name)!=0)) + { + fd=open(name,O_WRONLY | O_CREAT | O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (fd<0) g_error("ms_write_new: failed to open %s.\n",name); + } + r->fd=fd; + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_write_init(MSWrite *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->inqueues=r->q_inputs; + MS_FILTER(r)->r_mingran=MSWRITE_MIN_GRAN; + memset(r->f_inputs,0,sizeof(MSFifo*)*MSWRITE_MAX_INPUTS); + memset(r->q_inputs,0,sizeof(MSQueue*)*MSWRITE_MAX_INPUTS); + r->fd=-1; +} + +void ms_write_class_init(MSWriteClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"dskwriter"); + MS_FILTER_CLASS(klass)->max_finputs=MSWRITE_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_qinputs=MSWRITE_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=MSWRITE_DEF_GRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_write_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_write_process; +} + +void ms_write_process(MSWrite *r) +{ + MSFifo *f; + MSQueue *q; + MSMessage *buf=NULL; + int i,j,err1,err2; + gint gran=ms_filter_get_mingran(MS_FILTER(r)); + void *p; + + /* process output fifos*/ + for (i=0,j=0;(iklass->max_finputs)&&(jfinputs);i++) + { + f=r->f_inputs[i]; + if (f!=NULL) + { + if ( (err1=ms_fifo_get_read_ptr(f,gran,&p))>0 ) + { + + err2=write(r->fd,p,gran); + if (err2<0) g_warning("ms_write_process: failed to write: %s.\n",strerror(errno)); + } + j++; + } + } + /* process output queues*/ + for (i=0,j=0;(iklass->max_qinputs)&&(jqinputs);i++) + { + q=r->q_inputs[i]; + if (q!=NULL) + { + while ( (buf=ms_queue_get(q))!=NULL ){ + write(r->fd,buf->data,buf->size); + j++; + ms_message_destroy(buf); + } + } + } +} + +void ms_write_destroy( MSWrite *obj) +{ + if (obj->fd!=0) close(obj->fd); + g_free(obj); +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.h new file mode 100644 index 00000000..cd766d10 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.h @@ -0,0 +1,63 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MSWRITE_H +#define MSWRITE_H + +#include "msfilter.h" + + +/*this is the class that implements writing reading sink filter*/ + +#define MSWRITE_MAX_INPUTS 1 /* max output per filter*/ + +#define MSWRITE_DEF_GRAN 512 /* the default granularity*/ +#define MSWRITE_MIN_GRAN 64 + +typedef struct _MSWrite +{ + /* the MSWrite derivates from MSFilter, so the MSFilter object MUST be the first of the MSWrite object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSWRITE_MAX_INPUTS]; + MSQueue *q_inputs[MSWRITE_MAX_INPUTS]; + gint fd; /* the file descriptor of the file being written*/ +} MSWrite; + +typedef struct _MSWriteClass +{ + /* the MSWrite derivates from MSFilter, so the MSFilter class MUST be the first of the MSWrite class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSWriteClass; + +/* PUBLIC */ +#define MS_WRITE(filter) ((MSWrite*)(filter)) +#define MS_WRITE_CLASS(klass) ((MSWriteClass*)(klass)) +MSFilter * ms_write_new(char *name); + +/* FOR INTERNAL USE*/ +void ms_write_init(MSWrite *r); +void ms_write_class_init(MSWriteClass *klass); +void ms_write_destroy( MSWrite *obj); +void ms_write_process(MSWrite *r); + +#endif + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.c new file mode 100644 index 00000000..636c5792 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.c @@ -0,0 +1,495 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "osscard.h" + +#include "msossread.h" +#include "msosswrite.h" + +#ifdef HAVE_SYS_SOUNDCARD_H +#include + +#include +#include +#include + +#if 0 +void * oss_thread(OssCard *obj) +{ + gint i; + gint err; + g_message("oss_thread: starting **********"); + while(1){ + for(i=0;ilock); + if (obj->ref==0){ + g_cond_signal(obj->cond); + g_mutex_unlock(obj->lock); + g_thread_exit(NULL); + } + g_mutex_unlock(obj->lock); + obj->readindex=i; + + err=read(obj->fd,obj->readbuf[i],SND_CARD(obj)->bsize); + if (err<0) g_warning("oss_thread: read() error:%s.",strerror(errno)); + obj->writeindex=i; + write(obj->fd,obj->writebuf[i],SND_CARD(obj)->bsize); + memset(obj->writebuf[i],0,SND_CARD(obj)->bsize); + } + } +} +#endif +int oss_open(OssCard *obj, int bits,int stereo, int rate) +{ + int fd; + int p=0,cond=0; + int i=0; + int min_size=0,blocksize=512; + int err; + + //g_message("opening sound device"); + fd=open(obj->dev_name,O_RDWR|O_NONBLOCK); + if (fd<0) return -EWOULDBLOCK; + /* unset nonblocking mode */ + /* We wanted non blocking open but now put it back to normal ; thanks Xine !*/ + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)&~O_NONBLOCK); + + /* reset is maybe not needed but takes time*/ + /*ioctl(fd, SNDCTL_DSP_RESET, 0); */ + + +#ifdef WORDS_BIGENDIAN + p=AFMT_U16_BE; +#else + p=AFMT_U16_LE; +#endif + + err=ioctl(fd,SNDCTL_DSP_SETFMT,&p); + if (err<0){ + g_warning("oss_open: can't set sample format:%s.",strerror(errno)); + } + + + p = bits; /* 16 bits */ + err=ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &p); + if (err<0){ + g_warning("oss_open: can't set sample size to %i:%s.",bits,strerror(errno)); + } + + p = rate; /* rate in khz*/ + err=ioctl(fd, SNDCTL_DSP_SPEED, &p); + if (err<0){ + g_warning("oss_open: can't set sample rate to %i:%s.",rate,strerror(errno)); + } + + p = stereo; /* stereo or not */ + err=ioctl(fd, SNDCTL_DSP_STEREO, &p); + if (err<0){ + g_warning("oss_open: can't set mono/stereo mode:%s.",strerror(errno)); + } + + if (rate==16000) blocksize=4096; /* oss emulation is not very good at 16khz */ + else blocksize=blocksize*(rate/8000); + ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &min_size); + + /* try to subdivide BLKSIZE to reach blocksize if necessary */ + if (min_size>blocksize) + { + cond=1; + p=min_size/blocksize; + while(cond) + { + i=ioctl(fd, SNDCTL_DSP_SUBDIVIDE, &p); + //printf("SUB_DIVIDE said error=%i,errno=%i\n",i,errno); + if ((i==0) || (p==1)) cond=0; + else p=p/2; + } + } + ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &min_size); + if (min_size>blocksize) + { + g_warning("dsp block size set to %i.",min_size); + }else{ + /* no need to access the card with less latency than needed*/ + min_size=blocksize; + } + + g_message("dsp blocksize is %i.",min_size); + + /* start recording !!! Alex */ + { + int fl,res; + + fl=PCM_ENABLE_OUTPUT|PCM_ENABLE_INPUT; + res=ioctl(fd, SNDCTL_DSP_SETTRIGGER, &fl); + if (res<0) g_warning("OSS_TRIGGER: %s",strerror(errno)); + } + + obj->fd=fd; + obj->readpos=0; + obj->writepos=0; + SND_CARD(obj)->bits=bits; + SND_CARD(obj)->stereo=stereo; + SND_CARD(obj)->rate=rate; + SND_CARD(obj)->bsize=min_size; + return fd; +} + +int oss_card_probe(OssCard *obj,int bits,int stereo,int rate) +{ + + int fd; + int p=0,cond=0; + int i=0; + int min_size=0,blocksize=512; + + if (obj->fd>0) return SND_CARD(obj)->bsize; + fd=open(obj->dev_name,O_RDWR|O_NONBLOCK); + if (fd<0) { + g_warning("oss_card_probe: can't open %s: %s.",obj->dev_name,strerror(errno)); + return -1; + } + ioctl(fd, SNDCTL_DSP_RESET, 0); + + p = bits; /* 16 bits */ + ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &p); + + p = stereo; /* number of channels */ + ioctl(fd, SNDCTL_DSP_CHANNELS, &p); + + p = rate; /* rate in khz*/ + ioctl(fd, SNDCTL_DSP_SPEED, &p); + + ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &min_size); + + /* try to subdivide BLKSIZE to reach blocksize if necessary */ + if (min_size>blocksize) + { + cond=1; + p=min_size/blocksize; + while(cond) + { + i=ioctl(fd, SNDCTL_DSP_SUBDIVIDE, &p); + //printf("SUB_DIVIDE said error=%i,errno=%i\n",i,errno); + if ((i==0) || (p==1)) cond=0; + else p=p/2; + } + } + ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &min_size); + if (min_size>blocksize) + { + g_warning("dsp block size set to %i.",min_size); + }else{ + /* no need to access the card with less latency than needed*/ + min_size=blocksize; + } + close(fd); + return min_size; +} + + +int oss_card_open(OssCard *obj,int bits,int stereo,int rate) +{ + int fd; + obj->ref++; + if (obj->fd==0){ + fd=oss_open(obj,bits,stereo,rate); + if (fd<0) { + obj->fd=0; + obj->ref--; + return -1; + } + } + + obj->readbuf=g_malloc0(SND_CARD(obj)->bsize); + obj->writebuf=g_malloc0(SND_CARD(obj)->bsize); + + SND_CARD(obj)->flags|=SND_CARD_FLAGS_OPENED; + return 0; +} + +void oss_card_close(OssCard *obj) +{ + int i; + obj->ref--; + if (obj->ref==0) { + close(obj->fd); + obj->fd=0; + SND_CARD(obj)->flags&=~SND_CARD_FLAGS_OPENED; + g_free(obj->readbuf); + obj->readbuf=NULL; + g_free(obj->writebuf); + obj->writebuf=NULL; + + } +} + +void oss_card_destroy(OssCard *obj) +{ + snd_card_uninit(SND_CARD(obj)); + g_free(obj->dev_name); + g_free(obj->mixdev_name); + if (obj->readbuf!=NULL) g_free(obj->readbuf); + if (obj->writebuf!=NULL) g_free(obj->writebuf); +} + +gboolean oss_card_can_read(OssCard *obj) +{ + struct timeval tout={0,0}; + int err; + fd_set fdset; + if (obj->readpos!=0) return TRUE; + FD_ZERO(&fdset); + FD_SET(obj->fd,&fdset); + err=select(obj->fd+1,&fdset,NULL,NULL,&tout); + if (err>0) return TRUE; + else return FALSE; +} + +int oss_card_read(OssCard *obj,char *buf,int size) +{ + int err; + gint bsize=SND_CARD(obj)->bsize; + if (sizereadpos,size); + if (obj->readpos==0){ + err=read(obj->fd,obj->readbuf,bsize); + if (err<0) { + g_warning("oss_card_read: read() failed:%s.",strerror(errno)); + return -1; + } + } + + memcpy(buf,&obj->readbuf[obj->readpos],canread); + obj->readpos+=canread; + if (obj->readpos>=bsize) obj->readpos=0; + return canread; + }else{ + err=read(obj->fd,buf,size); + if (err<0) { + g_warning("oss_card_read: read-2() failed:%s.",strerror(errno)); + } + return err; + } + +} + +int oss_card_write(OssCard *obj,char *buf,int size) +{ + int err; + gint bsize=SND_CARD(obj)->bsize; + + if (sizewritepos,size); + memcpy(&obj->writebuf[obj->writepos],buf,canwrite); + obj->writepos+=canwrite; + if (obj->writepos>=bsize){ + err=write(obj->fd,obj->writebuf,bsize); + obj->writepos=0; + } + return canwrite; + }else{ + return write(obj->fd,buf,bsize); + } +} + +void oss_card_set_level(OssCard *obj,gint way,gint a) +{ + int p,mix_fd; + int osscmd; + g_return_if_fail(obj->mixdev_name!=NULL); +#ifdef HAVE_SYS_SOUNDCARD_H + switch(way){ + case SND_CARD_LEVEL_GENERAL: + osscmd=SOUND_MIXER_VOLUME; + break; + case SND_CARD_LEVEL_INPUT: + osscmd=SOUND_MIXER_IGAIN; + break; + case SND_CARD_LEVEL_OUTPUT: + osscmd=SOUND_MIXER_PCM; + break; + default: + g_warning("oss_card_set_level: unsupported command."); + return; + } + p=(((int)a)<<8 | (int)a); + mix_fd = open(obj->mixdev_name, O_WRONLY); + ioctl(mix_fd,MIXER_WRITE(osscmd), &p); + close(mix_fd); +#endif +} + +gint oss_card_get_level(OssCard *obj,gint way) +{ + int p=0,mix_fd; + int osscmd; + g_return_if_fail(obj->mixdev_name!=NULL); +#ifdef HAVE_SYS_SOUNDCARD_H + switch(way){ + case SND_CARD_LEVEL_GENERAL: + osscmd=SOUND_MIXER_VOLUME; + break; + case SND_CARD_LEVEL_INPUT: + osscmd=SOUND_MIXER_IGAIN; + break; + case SND_CARD_LEVEL_OUTPUT: + osscmd=SOUND_MIXER_PCM; + break; + default: + g_warning("oss_card_get_level: unsupported command."); + return -1; + } + mix_fd = open(obj->mixdev_name, O_RDONLY); + ioctl(mix_fd,MIXER_READ(SOUND_MIXER_VOLUME), &p); + close(mix_fd); +#endif + return p>>8; +} + +void oss_card_set_source(OssCard *obj,int source) +{ + gint p=0; + gint mix_fd; + g_return_if_fail(obj->mixdev_name!=NULL); +#ifdef HAVE_SYS_SOUNDCARD_H + if (source == 'c') + p = 1 << SOUND_MIXER_CD; + if (source == 'l') + p = 1 << SOUND_MIXER_LINE; + if (source == 'm') + p = 1 << SOUND_MIXER_MIC; + + + mix_fd = open(obj->mixdev_name, O_WRONLY); + ioctl(mix_fd, SOUND_MIXER_WRITE_RECSRC, &p); + close(mix_fd); +#endif +} + +MSFilter *oss_card_create_read_filter(OssCard *card) +{ + MSFilter *f=ms_oss_read_new(); + ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index); + return f; +} + +MSFilter *oss_card_create_write_filter(OssCard *card) +{ + MSFilter *f=ms_oss_write_new(); + ms_oss_write_set_device(MS_OSS_WRITE(f),SND_CARD(card)->index); + return f; +} + + +SndCard * oss_card_new(char *devname, char *mixdev_name) +{ + OssCard * obj= g_new0(OssCard,1); + SndCard *base= SND_CARD(obj); + snd_card_init(base); + obj->dev_name=g_strdup(devname); + obj->mixdev_name=g_strdup( mixdev_name); +#ifdef HAVE_GLIB + base->card_name=g_strdup_printf("%s (Open Sound System)",devname); +#else + base->card_name=malloc(100); + snprintf(base->card_name, 100, "%s (Open Sound System)",devname); +#endif + base->_probe=(SndCardOpenFunc)oss_card_probe; + base->_open_r=(SndCardOpenFunc)oss_card_open; + base->_open_w=(SndCardOpenFunc)oss_card_open; + base->_can_read=(SndCardPollFunc)oss_card_can_read; + base->_read=(SndCardIOFunc)oss_card_read; + base->_write=(SndCardIOFunc)oss_card_write; + base->_close_r=(SndCardCloseFunc)oss_card_close; + base->_close_w=(SndCardCloseFunc)oss_card_close; + base->_set_rec_source=(SndCardMixerSetRecSourceFunc)oss_card_set_source; + base->_set_level=(SndCardMixerSetLevelFunc)oss_card_set_level; + base->_get_level=(SndCardMixerGetLevelFunc)oss_card_get_level; + base->_destroy=(SndCardDestroyFunc)oss_card_destroy; + base->_create_read_filter=(SndCardCreateFilterFunc)oss_card_create_read_filter; + base->_create_write_filter=(SndCardCreateFilterFunc)oss_card_create_write_filter; + return base; +} + +#define DSP_NAME "/dev/dsp" +#define MIXER_NAME "/dev/mixer" + +gint oss_card_manager_init(SndCardManager *manager, gint tabindex) +{ + gchar *devname; + gchar *mixername; + gint devindex=0; + gint found=0; + + /* search for /dev/dsp and /dev/mixer */ +#ifdef HAVE_GLIB + if (g_file_test(DSP_NAME,G_FILE_TEST_EXISTS)){ + tabindex++; + devindex++; + manager->cards[0]=oss_card_new(DSP_NAME,MIXER_NAME); + manager->cards[0]->index=0; + found++; + g_message("Found /dev/dsp."); + } + for (;tabindexcards[tabindex]=oss_card_new(devname,mixername); + manager->cards[tabindex]->index=tabindex; + tabindex++; + found++; + } + g_free(devname); + g_free(mixername); + } +#else + if (access(DSP_NAME,F_OK)==0){ + tabindex++; + devindex++; + manager->cards[0]=oss_card_new(DSP_NAME,MIXER_NAME); + manager->cards[0]->index=0; + found++; + g_message("Found /dev/dsp."); + } + for (;tabindexcards[tabindex]=oss_card_new(devname,mixername); + manager->cards[tabindex]->index=tabindex; + tabindex++; + found++; + } + g_free(devname); + g_free(mixername); + } +#endif + if (tabindex==0) g_warning("No sound cards found !"); + return found; +} + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.h new file mode 100644 index 00000000..30b96c23 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.h @@ -0,0 +1,47 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* An implementation of SndCard : the OssCard */ + +#ifndef OSS_CARD_H +#define OSS_CARD_H + +#include "sndcard.h" + +#define OSS_CARD_BUFFERS 3 +struct _OssCard +{ + SndCard parent; + gchar *dev_name; /* /dev/dsp0 for example */ + gchar *mixdev_name; /* /dev/mixer0 for example */ + gint fd; /* the file descriptor of the open soundcard, 0 if not open*/ + gint ref; + gchar *readbuf; + gint readpos; + gchar *writebuf; + gint writepos; +}; + +typedef struct _OssCard OssCard; + +SndCard * oss_card_new(char *devname, char *mixdev_name); + +typedef OssCard HpuxSndCard; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.c new file mode 100644 index 00000000..9570b905 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.c @@ -0,0 +1,315 @@ +/* + Copyright (C) 2005 Remko Troncon + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include + +#include "portaudiocard.h" +#include "msossread.h" +#include "msosswrite.h" + +// Settings +#define BUFFER_SIZE 2048 + +// PortAudio settings +#define FRAMES_PER_BUFFER 256 + + +// ----------------------------------------------------------------------------- + +int readBuffer(char* buffer, char** buffer_read_p, char*buffer_write, char* buffer_end, char* target_buffer, int target_len) +{ + char *end, *tmp, *buffer_read = *buffer_read_p; + size_t remaining, len; + int read = 0; + + // First phase + tmp = buffer_read + target_len; + if (buffer_write < buffer_read) { + if (tmp > buffer_end) { + end = buffer_end; + remaining = tmp - buffer_end; + } + else { + end = tmp; + remaining = 0; + } + } + else { + end = (tmp >= buffer_write ? buffer_write : tmp); + remaining = 0; + } + //printf("end: %p\n",end); + + // Copy the data + len = end - buffer_read; + memcpy(target_buffer, buffer_read, len); + buffer_read += len; + target_buffer += len; + read += len; + + // Second phase + if (remaining > 0) { + buffer_read = buffer; + tmp = buffer_read + remaining; + len = (tmp > buffer_write ? buffer_write : tmp) - buffer_read; + memcpy(target_buffer, buffer_read, len); + buffer_read += len; + read += len; + } + + // Finish up + *buffer_read_p = buffer_read; + + return read; +} + +int writeBuffer(char* buffer, char* buffer_read, char** buffer_write_p, char* buffer_end, char* source_buffer, int source_len) +{ + char *end, *tmp, *buffer_write = *buffer_write_p; + size_t remaining, len; + int written = 0; + + // First phase + tmp = buffer_write + source_len; + if (buffer_write >= buffer_read) { + if (tmp > buffer_end) { + end = buffer_end; + remaining = tmp - buffer_end; + } + else { + end = tmp; + remaining = 0; + } + } + else { + if (tmp > buffer_read) { + printf("Warning: Dropping frame(s) %p %p\n", tmp, buffer_read); + end = buffer_read; + remaining = 0; + } + else { + end = tmp; + remaining = 0; + } + } + + len = end - buffer_write; + memcpy(buffer_write, source_buffer, len); + buffer_write += len; + source_buffer += len; + written += len; + + // Second phase + if (remaining > 0) { + buffer_write = buffer; + tmp = buffer_write + remaining; + if (tmp > buffer_read) { + printf("Warning: Dropping frame(s) %p %p\n", tmp, buffer_read); + end = buffer_read; + } + else { + end = tmp; + } + + len = end - buffer_write; + memcpy(buffer_write, source_buffer, len); + buffer_write += len; + written += len; + } + + // Finish up + *buffer_write_p = buffer_write; + return written; +} + +// ----------------------------------------------------------------------------- + +static int portAudioCallback( void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, PaTimestamp outTime, void *card_p ) +{ + PortAudioCard* card = (PortAudioCard*) card_p; + + size_t len = framesPerBuffer * Pa_GetSampleSize(paInt16); + //printf("PA::readBuffer begin %p %p %p %p %d\n",card->out_buffer,card->out_buffer_read,card->out_buffer_write,card->out_buffer_end, len); + readBuffer(card->out_buffer,&card->out_buffer_read,card->out_buffer_write,card->out_buffer_end, outputBuffer, len); + //printf("PA::readBuffer end %p %p %p %p %d\n",card->out_buffer,card->out_buffer_read,card->out_buffer_write,card->out_buffer_end, len); + writeBuffer(card->in_buffer,card->in_buffer_read,&card->in_buffer_write,card->in_buffer_end, inputBuffer, len); + return 0; +} + +// ----------------------------------------------------------------------------- + +int portaudio_card_probe(PortAudioCard *obj, int bits, int stereo, int rate) +{ + return FRAMES_PER_BUFFER * (SND_CARD(obj)->stereo ? 2 : 1) * Pa_GetSampleSize(paInt16); +} + + +int portaudio_card_open_r(PortAudioCard *obj,int bits,int stereo,int rate) +{ + fprintf(stderr,"Opening PortAudio card\n"); + + int err; + err = Pa_OpenDefaultStream(&obj->stream, 1, 1, paInt16, rate, FRAMES_PER_BUFFER, 0, portAudioCallback, obj); + if (err != paNoError) { + fprintf(stderr, "Error creating a PortAudio stream: %s\n", Pa_GetErrorText(err)); + return -1; + } + + err = Pa_StartStream(obj->stream); + if (err != paNoError) { + fprintf(stderr, "Error starting PortAudio stream: %s\n", Pa_GetErrorText(err)); + Pa_CloseStream(obj->stream); + obj->stream = NULL; + return -1; + } + + SND_CARD(obj)->bits = 16; + SND_CARD(obj)->stereo = 0; + SND_CARD(obj)->rate = rate; + // Should this be multiplied by Pa_GetMinNumBuffers(FRAMES_PER_BUFFER,sampleRate) ? + SND_CARD(obj)->bsize = FRAMES_PER_BUFFER * (SND_CARD(obj)->stereo ? 2 : 1) * Pa_GetSampleSize(paInt16); + + return 0; + +} + +void portaudio_card_close_r(PortAudioCard *obj) +{ + fprintf(stderr, "Closing PortAudio card\n"); + if (obj->stream) { + Pa_StopStream(obj->stream); + Pa_CloseStream(obj->stream); + obj->stream = NULL; + } +} + +int portaudio_card_open_w(PortAudioCard *obj,int bits,int stereo,int rate) +{ +} + +void portaudio_card_close_w(PortAudioCard *obj) +{ +} + +void portaudio_card_destroy(PortAudioCard *obj) +{ + snd_card_uninit(SND_CARD(obj)); + free(obj->in_buffer); + free(obj->out_buffer); +} + +gboolean portaudio_card_can_read(PortAudioCard *obj) +{ + return obj->in_buffer_read != obj->in_buffer_write; +} + +int portaudio_card_read(PortAudioCard *obj,char *buf,int size) +{ + //printf("read begin %p %p %p %p %d\n",obj->in_buffer,obj->in_buffer_read,obj->in_buffer_write,obj->in_buffer_end, size); + return readBuffer(obj->in_buffer,&obj->in_buffer_read,obj->in_buffer_write,obj->in_buffer_end, buf, size); + //printf("read end %p %p %p %p %d\n",obj->in_buffer,obj->in_buffer_read,obj->in_buffer_write,obj->in_buffer_end, size); +} + +int portaudio_card_write(PortAudioCard *obj,char *buf,int size) +{ + //printf("writeBuffer begin %p %p %p %p %d\n",obj->out_buffer,obj->out_buffer_read,obj->out_buffer_write,obj->out_buffer_end, size); + return writeBuffer(obj->out_buffer,obj->out_buffer_read,&obj->out_buffer_write,obj->out_buffer_end, buf, size); + //printf("writeBuffer end %p %p %p %p %d\n",obj->out_buffer,obj->out_buffer_read,obj->out_buffer_write,obj->out_buffer_end, size); +} + +void portaudio_card_set_level(PortAudioCard *obj,gint way,gint a) +{ +} + +gint portaudio_card_get_level(PortAudioCard *obj,gint way) +{ + return 0; +} + +void portaudio_card_set_source(PortAudioCard *obj,int source) +{ +} + +MSFilter *portaudio_card_create_read_filter(PortAudioCard *card) +{ + MSFilter *f=ms_oss_read_new(); + ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index); + return f; +} + +MSFilter *portaudio_card_create_write_filter(PortAudioCard *card) +{ + MSFilter *f=ms_oss_write_new(); + ms_oss_write_set_device(MS_OSS_WRITE(f),SND_CARD(card)->index); + return f; +} + + +SndCard* portaudio_card_new() +{ + // Basic stuff + PortAudioCard* obj= g_new0(PortAudioCard,1); + SndCard* base= SND_CARD(obj); + snd_card_init(base); + base->card_name=g_strdup_printf("PortAudio Card"); + base->_probe=(SndCardOpenFunc)portaudio_card_probe; + base->_open_r=(SndCardOpenFunc)portaudio_card_open_r; + base->_open_w=(SndCardOpenFunc)portaudio_card_open_w; + base->_can_read=(SndCardPollFunc)portaudio_card_can_read; + base->_read=(SndCardIOFunc)portaudio_card_read; + base->_write=(SndCardIOFunc)portaudio_card_write; + base->_close_r=(SndCardCloseFunc)portaudio_card_close_r; + base->_close_w=(SndCardCloseFunc)portaudio_card_close_w; + base->_set_rec_source=(SndCardMixerSetRecSourceFunc)portaudio_card_set_source; + base->_set_level=(SndCardMixerSetLevelFunc)portaudio_card_set_level; + base->_get_level=(SndCardMixerGetLevelFunc)portaudio_card_get_level; + base->_destroy=(SndCardDestroyFunc)portaudio_card_destroy; + base->_create_read_filter=(SndCardCreateFilterFunc)portaudio_card_create_read_filter; + base->_create_write_filter=(SndCardCreateFilterFunc)portaudio_card_create_write_filter; + + // Initialize stream + obj->stream = NULL; + + // Initialize buffers + obj->out_buffer = (char*) malloc(sizeof(char)*BUFFER_SIZE); + obj->out_buffer_read = obj->out_buffer_write = obj->out_buffer; + obj->out_buffer_end = obj->out_buffer + BUFFER_SIZE; + obj->in_buffer = (char*) malloc(sizeof(char)*BUFFER_SIZE); + obj->in_buffer_read = obj->in_buffer_write = obj->in_buffer; + obj->in_buffer_end = obj->in_buffer + BUFFER_SIZE; + + return base; +} + +gint portaudio_card_manager_init(SndCardManager *manager, gint tabindex) +{ + // Initialize portaudio lib + int err = Pa_Initialize(); + if (err != paNoError) { + fprintf(stderr,"Error initializing PortAudio: %s\n",Pa_GetErrorText(err)); + return 0; + } + + // Create new card + manager->cards[0]=portaudio_card_new(); + manager->cards[0]->index=0; + + return 1; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.h new file mode 100644 index 00000000..cbaa7982 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.h @@ -0,0 +1,35 @@ +/* + Copyright (C) 2005 Remko Troncon + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* An implementation of SndCard : the OssCard */ + +#ifndef PORTAUDIO_CARD_H +#define PORTAUDIO_CARD_H + +#include "sndcard.h" + +typedef struct _PortAudioCard +{ + SndCard parent; + PortAudioStream* stream; + char *out_buffer, *out_buffer_read, *out_buffer_write, *out_buffer_end; + char *in_buffer, *in_buffer_read, *in_buffer_write, *in_buffer_end; +} PortAudioCard; + +gint portaudio_card_manager_init(SndCardManager *manager, gint tabindex); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.c new file mode 100644 index 00000000..3a0f5d9a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.c @@ -0,0 +1,209 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "sndcard.h" +#include "msfilter.h" + +void snd_card_init(SndCard *obj) +{ + memset(obj,0,sizeof(SndCard)); +} + +void snd_card_uninit(SndCard *obj) +{ + if (obj->card_name!=NULL) g_free(obj->card_name); +} + +const gchar *snd_card_get_identifier(SndCard *obj) +{ + return obj->card_name; +} + +int snd_card_open_r(SndCard *obj, int bits, int stereo, int rate) +{ + g_return_val_if_fail(obj->_open_r!=NULL,-1); + g_message("Opening sound card [%s] in capture mode with stereo=%i,rate=%i,bits=%i",obj->card_name,stereo,rate,bits); + return obj->_open_r(obj,bits,stereo,rate); +} +int snd_card_open_w(SndCard *obj, int bits, int stereo, int rate) +{ + g_return_val_if_fail(obj->_open_w!=NULL,-1); + g_message("Opening sound card [%s] in playback mode with stereo=%i,rate=%i,bits=%i",obj->card_name,stereo,rate,bits); + return obj->_open_w(obj,bits,stereo,rate); +} + +gboolean snd_card_can_read(SndCard *obj){ + g_return_val_if_fail(obj->_can_read!=NULL,-1); + return obj->_can_read(obj); +} + +void snd_card_set_blocking_mode(SndCard *obj,gboolean yesno){ + g_return_if_fail(obj->_set_blocking_mode!=NULL); + obj->_set_blocking_mode(obj,yesno); +} + +int snd_card_read(SndCard *obj,char *buffer,int size) +{ + g_return_val_if_fail(obj->_read!=NULL,-1); + return obj->_read(obj,buffer,size); +} +int snd_card_write(SndCard *obj,char *buffer,int size) +{ + g_return_val_if_fail(obj->_write!=NULL,-1); + return obj->_write(obj,buffer,size); +} + +int snd_card_get_bsize(SndCard *obj) +{ + if (obj->flags & SND_CARD_FLAGS_OPENED){ + return obj->bsize; + } + return -1; +} + +void snd_card_close_r(SndCard *obj) +{ + g_return_if_fail(obj->_close_r!=NULL); + g_message("Closing reading channel of soundcard."); + obj->_close_r(obj); +} + +void snd_card_close_w(SndCard *obj) +{ + g_return_if_fail(obj->_close_w!=NULL); + g_message("Closing writing channel of soundcard."); + obj->_close_w(obj); +} + +gint snd_card_probe(SndCard *obj,int bits, int stereo, int rate) +{ + g_return_val_if_fail(obj->_probe!=NULL,-1); + return obj->_probe(obj,bits,stereo,rate); +} + +void snd_card_set_rec_source(SndCard *obj, int source) +{ + g_return_if_fail(obj->_set_rec_source!=NULL); + obj->_set_rec_source(obj,source); +} + +void snd_card_set_level(SndCard *obj, int way, int level) +{ + g_return_if_fail(obj->_set_level!=NULL); + obj->_set_level(obj,way,level); +} + +gint snd_card_get_level(SndCard *obj,int way) +{ + g_return_val_if_fail(obj->_get_level!=NULL,-1); + return obj->_get_level(obj,way); +} + + +MSFilter * snd_card_create_read_filter(SndCard *obj) +{ + g_return_val_if_fail(obj->_create_read_filter!=NULL,NULL); + return obj->_create_read_filter(obj); +} +MSFilter * snd_card_create_write_filter(SndCard *obj) +{ + g_return_val_if_fail(obj->_create_write_filter!=NULL,NULL); + return obj->_create_write_filter(obj); +} + + +#ifdef HAVE_SYS_AUDIO_H +gint sys_audio_manager_init(SndCardManager *manager, gint index) +{ + /* this is a quick shortcut, as multiple soundcards on HPUX does not happen + very often... */ + manager->cards[index]=hpux_snd_card_new("/dev/audio","/dev/audio"); + return 1; +} + +#endif + +#include "osscard.h" +#include "alsacard.h" +#include "jackcard.h" + +void snd_card_manager_init(SndCardManager *manager) +{ + gint index=0; + gint tmp=0; + memset(manager,0,sizeof(SndCardManager)); + #ifdef HAVE_SYS_SOUNDCARD_H + tmp=oss_card_manager_init(manager,index); + index+=tmp; + if (index>=MAX_SND_CARDS) return; + #endif + #ifdef __ALSA_ENABLED__ + tmp=alsa_card_manager_init(manager,index); + index+=tmp; + if (index>=MAX_SND_CARDS) return; + #endif + #ifdef __JACK_ENABLED__ + tmp=jack_card_manager_init(manager,index); + index+=tmp; + if (index>=MAX_SND_CARDS) return; + #endif + #ifdef HAVE_PORTAUDIO + tmp=portaudio_card_manager_init(manager,index); + index+=tmp; + if (index>=MAX_SND_CARDS) return; + #endif + #ifdef HAVE_SYS_AUDIO_H + tmp=sys_audio_manager_init(manager,index); + index+=tmp; + #endif +} + + + + + +SndCard * snd_card_manager_get_card(SndCardManager *manager,int index) +{ + g_return_val_if_fail(index>=0,NULL); + g_return_val_if_fail(indexMAX_SND_CARDS) return NULL; + return manager->cards[index]; +} + +SndCard * snd_card_manager_get_card_with_string(SndCardManager *manager,const char *cardname,int *index) +{ + int i; + for (i=0;icards[i]==NULL) continue; + card_name=manager->cards[i]->card_name; + if (card_name==NULL) continue; + if (strcmp(card_name,cardname)==0){ + *index=i; + return manager->cards[i]; + } + } + g_warning("No card %s found.",cardname); + return NULL; +} + +SndCardManager _snd_card_manager; +SndCardManager *snd_card_manager=&_snd_card_manager; diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.h new file mode 100644 index 00000000..d84757fd --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.h @@ -0,0 +1,143 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + + +#ifndef SNDCARD_H +#define SNDCARD_H + +#undef PACKAGE +#undef VERSION +#include +#undef PACKAGE +#undef VERSION + +#ifdef HAVE_GLIB +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* the base class for all soundcards: SndCard */ +struct _SndCard; + +typedef int (*SndCardOpenFunc)(struct _SndCard*,int, int, int); +typedef void (*SndCardSetBlockingModeFunc)(struct _SndCard*, gboolean ); +typedef void (*SndCardCloseFunc)(struct _SndCard*); +typedef gint (*SndCardIOFunc)(struct _SndCard*,char *,int); +typedef void (*SndCardDestroyFunc)(struct _SndCard*); +typedef gboolean (*SndCardPollFunc)(struct _SndCard*); +typedef gint (*SndCardMixerGetLevelFunc)(struct _SndCard*,gint); +typedef void (*SndCardMixerSetRecSourceFunc)(struct _SndCard*,gint); +typedef void (*SndCardMixerSetLevelFunc)(struct _SndCard*,gint ,gint); +typedef struct _MSFilter * (*SndCardCreateFilterFunc)(struct _SndCard *); + +struct _SndCard +{ + gchar *card_name; /* SB16 PCI for example */ + gint index; + gint bsize; + gint rate; + gint stereo; + gint bits; + gint flags; +#define SND_CARD_FLAGS_OPENED 1 + SndCardOpenFunc _probe; + SndCardOpenFunc _open_r; + SndCardOpenFunc _open_w; + SndCardSetBlockingModeFunc _set_blocking_mode; + SndCardPollFunc _can_read; + SndCardIOFunc _read; + SndCardIOFunc _write; + SndCardCloseFunc _close_r; + SndCardCloseFunc _close_w; + SndCardMixerGetLevelFunc _get_level; + SndCardMixerSetLevelFunc _set_level; + SndCardMixerSetRecSourceFunc _set_rec_source; + SndCardCreateFilterFunc _create_read_filter; + SndCardCreateFilterFunc _create_write_filter; + SndCardDestroyFunc _destroy; +}; + + +typedef struct _SndCard SndCard; + +void snd_card_init(SndCard *obj); +void snd_card_uninit(SndCard *obj); +gint snd_card_probe(SndCard *obj, int bits, int stereo, int rate); +int snd_card_open_r(SndCard *obj, int bits, int stereo, int rate); +int snd_card_open_w(SndCard *obj, int bits, int stereo, int rate); +int snd_card_get_bsize(SndCard *obj); +gboolean snd_card_can_read(SndCard *obj); +int snd_card_read(SndCard *obj,char *buffer,int size); +int snd_card_write(SndCard *obj,char *buffer,int size); +void snd_card_set_blocking_mode(SndCard *obj,gboolean yesno); +void snd_card_close_r(SndCard *obj); +void snd_card_close_w(SndCard *obj); + +void snd_card_set_rec_source(SndCard *obj, int source); /* source='l' or 'm'*/ +void snd_card_set_level(SndCard *obj, int way, int level); +gint snd_card_get_level(SndCard *obj,int way); + +const gchar *snd_card_get_identifier(SndCard *obj); + +struct _MSFilter * snd_card_create_read_filter(SndCard *sndcard); +struct _MSFilter * snd_card_create_write_filter(SndCard *sndcard); + + +#define SND_CARD_LEVEL_GENERAL 1 +#define SND_CARD_LEVEL_INPUT 2 +#define SND_CARD_LEVEL_OUTPUT 3 + + +int snd_card_destroy(SndCard *obj); + +#define SND_CARD(obj) ((SndCard*)(obj)) + + + + +/* SndCardManager */ + +#define MAX_SND_CARDS 20 + + +struct _SndCardManager +{ + SndCard *cards[MAX_SND_CARDS]; +}; + +typedef struct _SndCardManager SndCardManager; + +void snd_card_manager_init(SndCardManager *manager); +SndCard * snd_card_manager_get_card(SndCardManager *manager,int index); +SndCard * snd_card_manager_get_card_with_string(SndCardManager *manager,const char *cardname,int *index); + +extern SndCardManager *snd_card_manager; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/waveheader.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/waveheader.h new file mode 100644 index 00000000..6768d8f8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/waveheader.h @@ -0,0 +1,111 @@ +/* +linphone +Copyright (C) 2000 Simon MORLAT (simon.morlat@free.fr) + +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. +*/ + +/* the following code was taken from a free software utility that I don't remember the name. */ +/* sorry */ + + + +#include +#ifndef waveheader_h +#define waveheader_h + +typedef struct uint16scheme +{ + unsigned char lo_byte; + unsigned char hi_byte; +} uint16scheme_t; + +typedef struct uint32scheme +{ + guint16 lo_int; + guint16 hi_int; +} uint32scheme_t; + + +/* all integer in wav header must be read in least endian order */ +inline guint16 _readuint16(guint16 a) +{ + guint16 res; + uint16scheme_t *tmp1=(uint16scheme_t*)&a; + + ((uint16scheme_t *)(&res))->lo_byte=tmp1->hi_byte; + ((uint16scheme_t *)(&res))->hi_byte=tmp1->lo_byte; + return res; +} + +inline guint32 _readuint32(guint32 a) +{ + guint32 res; + uint32scheme_t *tmp1=(uint32scheme_t*)&a; + + ((uint32scheme_t *)(&res))->lo_int=_readuint16(tmp1->hi_int); + ((uint32scheme_t *)(&res))->hi_int=_readuint16(tmp1->lo_int); + return res; +} + +#ifdef WORDS_BIGENDIAN +#define le_uint32(a) (_readuint32((a))) +#define le_uint16(a) (_readuint16((a))) +#define le_int16(a) ( (gint16) _readuint16((guint16)((a))) ) +#else +#define le_uint32(a) (a) +#define le_uint16(a) (a) +#define le_int16(a) (a) +#endif + +typedef struct _riff_t { + char riff[4] ; /* "RIFF" (ASCII characters) */ + guint32 len ; /* Length of package (binary, little endian) */ + char wave[4] ; /* "WAVE" (ASCII characters) */ +} riff_t; + +/* The FORMAT chunk */ + +typedef struct _format_t { + char fmt[4] ; /* "fmt_" (ASCII characters) */ + guint32 len ; /* length of FORMAT chunk (always 0x10) */ + guint16 que ; /* Always 0x01 */ + guint16 channel ; /* Channel numbers (0x01 = mono, 0x02 = stereo) */ + guint32 rate ; /* Sample rate (binary, in Hz) */ + guint32 bps ; /* Bytes Per Second */ + guint16 bpsmpl ; /* bytes per sample: 1 = 8 bit Mono, + 2 = 8 bit Stereo/16 bit Mono, + 4 = 16 bit Stereo */ + guint16 bitpspl ; /* bits per sample */ +} format_t; + +/* The DATA chunk */ + +typedef struct _data_t { + char data[4] ; /* "data" (ASCII characters) */ + int len ; /* length of data */ +} data_t; + +typedef struct _wave_header_t +{ + riff_t riff_chunk; + format_t format_chunk; + data_t data_chunk; +} wave_header_t; + +#define wave_header_get_rate(header) le_uint32((header)->format_chunk.rate) +#define wave_header_get_channel(header) le_uint16((header)->format_chunk.channel) + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/Makefile.am new file mode 100644 index 00000000..1e7abcfd --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/Makefile.am @@ -0,0 +1,18 @@ +libcricketxmllite_la_SOURCES = qname.cc \ + xmlbuilder.cc \ + xmlconstants.cc \ + xmlelement.cc \ + xmlnsstack.cc \ + xmlparser.cc \ + xmlprinter.cc + +noinst_HEADERS = qname.h \ + xmlbuilder.h \ + xmlconstants.h \ + xmlelement.h \ + xmlnsstack.h \ + xmlparser.h \ + xmlprinter.h +AM_CPPFLAGS = -DPOSIX -I$(srcdir)/../.. + +noinst_LTLIBRARIES = libcricketxmllite.la diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.cc new file mode 100644 index 00000000..626cfa96 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.cc @@ -0,0 +1,167 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "talk/base/common.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/qname.h" +#include "talk/xmllite/xmlconstants.h" + +//#define new TRACK_NEW + +namespace buzz { + +static int QName_Hash(const std::string & ns, const char * local) { + int result = ns.size() * 101; + while (*local) { + result *= 19; + result += *local; + local += 1; + } + return result; +} + +static const int bits = 9; +static QName::Data * get_qname_table() { + static QName::Data qname_table[1 << bits]; + return qname_table; +} + +static QName::Data * +AllocateOrFind(const std::string & ns, const char * local) { + int index = QName_Hash(ns, local); + int increment = index >> (bits - 1) | 1; + QName::Data * qname_table = get_qname_table(); + for (;;) { + index &= ((1 << bits) - 1); + if (!qname_table[index].Occupied()) { + return new QName::Data(ns, local); + } + if (qname_table[index].localPart_ == local && + qname_table[index].namespace_ == ns) { + qname_table[index].AddRef(); + return qname_table + index; + } + index += increment; + } +} + +static QName::Data * +Add(const std::string & ns, const char * local) { + int index = QName_Hash(ns, local); + int increment = index >> (bits - 1) | 1; + QName::Data * qname_table = get_qname_table(); + for (;;) { + index &= ((1 << bits) - 1); + if (!qname_table[index].Occupied()) { + qname_table[index].namespace_ = ns; + qname_table[index].localPart_ = local; + qname_table[index].AddRef(); // AddRef twice so it's never deleted + qname_table[index].AddRef(); + return qname_table + index; + } + if (qname_table[index].localPart_ == local && + qname_table[index].namespace_ == ns) { + qname_table[index].AddRef(); + return qname_table + index; + } + index += increment; + } +} + +QName::~QName() { + data_->Release(); +} + +QName::QName() : data_(QN_EMPTY.data_) { + data_->AddRef(); +} + +QName::QName(bool add, const std::string & ns, const char * local) : + data_(add ? Add(ns, local) : AllocateOrFind(ns, local)) {} + +QName::QName(bool add, const std::string & ns, const std::string & local) : + data_(add ? Add(ns, local.c_str()) : AllocateOrFind(ns, local.c_str())) {} + +QName::QName(const std::string & ns, const char * local) : + data_(AllocateOrFind(ns, local)) {} + +static std::string +QName_LocalPart(const std::string & name) { + size_t i = name.rfind(':'); + if (i == std::string::npos) + return name; + return name.substr(i + 1); +} + +static std::string +QName_Namespace(const std::string & name) { + size_t i = name.rfind(':'); + if (i == std::string::npos) + return STR_EMPTY; + return name.substr(0, i); +} + +QName::QName(const std::string & mergedOrLocal) : + data_(AllocateOrFind(QName_Namespace(mergedOrLocal), + QName_LocalPart(mergedOrLocal).c_str())) {} + +std::string +QName::Merged() const { + if (data_->namespace_ == STR_EMPTY) + return data_->localPart_; + + std::string result(data_->namespace_); + result.reserve(result.length() + 1 + data_->localPart_.length()); + result += ':'; + result += data_->localPart_; + return result; +} + +bool +QName::operator==(const QName & other) const { + return other.data_ == data_ || + data_->localPart_ == other.data_->localPart_ && + data_->namespace_ == other.data_->namespace_; +} + +int +QName::Compare(const QName & other) const { + if (data_ == other.data_) + return 0; + + int result = data_->localPart_.compare(other.data_->localPart_); + if (result) + return result; + + return data_->namespace_.compare(other.data_->namespace_); +} + +} + + + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.h new file mode 100644 index 00000000..b1bcec61 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.h @@ -0,0 +1,87 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _qname_h_ +#define _qname_h_ + +#include + +namespace buzz { + + +class QName +{ +public: + explicit QName(); + QName(const QName & qname) : data_(qname.data_) { data_->AddRef(); } + explicit QName(bool add, const std::string & ns, const char * local); + explicit QName(bool add, const std::string & ns, const std::string & local); + explicit QName(const std::string & ns, const char * local); + explicit QName(const std::string & mergedOrLocal); + QName & operator=(const QName & qn) { + qn.data_->AddRef(); + data_->Release(); + data_ = qn.data_; + return *this; + } + ~QName(); + + const std::string & Namespace() const { return data_->namespace_; } + const std::string & LocalPart() const { return data_->localPart_; } + std::string Merged() const; + int Compare(const QName & other) const; + bool operator==(const QName & other) const; + bool operator!=(const QName & other) const { return !operator==(other); } + bool operator<(const QName & other) const { return Compare(other) < 0; } + + class Data { + public: + Data(const std::string & ns, const std::string & local) : + refcount_(1), + namespace_(ns), + localPart_(local) {} + + Data() : refcount_(0) {} + + std::string namespace_; + std::string localPart_; + void AddRef() { refcount_++; } + void Release() { if (!--refcount_) { delete this; } } + bool Occupied() { return !!refcount_; } + + private: + int refcount_; + }; + +private: + Data * data_; +}; + + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.cc new file mode 100644 index 00000000..313c4013 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.cc @@ -0,0 +1,151 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/stl_decl.h" +#include +#include +#include +#include "talk/base/common.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlbuilder.h" + +#define new TRACK_NEW + +namespace buzz { + +XmlBuilder::XmlBuilder() : + pelCurrent_(NULL), + pelRoot_(NULL), + pvParents_(new std::vector()) { +} + +void +XmlBuilder::Reset() { + pelRoot_.reset(); + pelCurrent_ = NULL; + pvParents_->clear(); +} + +XmlElement * +XmlBuilder::BuildElement(XmlParseContext * pctx, + const char * name, const char ** atts) { + QName tagName(pctx->ResolveQName(name, false)); + if (tagName == QN_EMPTY) + return NULL; + + XmlElement * pelNew = new XmlElement(tagName); + + if (!*atts) + return pelNew; + + std::set seenNonlocalAtts; + + while (*atts) { + QName attName(pctx->ResolveQName(*atts, true)); + if (attName == QN_EMPTY) { + delete pelNew; + return NULL; + } + + // verify that namespaced names are unique + if (!attName.Namespace().empty()) { + if (seenNonlocalAtts.count(attName)) { + delete pelNew; + return NULL; + } + seenNonlocalAtts.insert(attName); + } + + pelNew->AddAttr(attName, std::string(*(atts + 1))); + atts += 2; + } + + return pelNew; +} + +void +XmlBuilder::StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) { + XmlElement * pelNew = BuildElement(pctx, name, atts); + if (pelNew == NULL) { + pctx->RaiseError(XML_ERROR_SYNTAX); + return; + } + + if (!pelCurrent_) { + pelCurrent_ = pelNew; + pelRoot_.reset(pelNew); + pvParents_->push_back(NULL); + } else { + pelCurrent_->AddElement(pelNew); + pvParents_->push_back(pelCurrent_); + pelCurrent_ = pelNew; + } +} + +void +XmlBuilder::EndElement(XmlParseContext * pctx, const char * name) { + UNUSED(pctx); + UNUSED(name); + pelCurrent_ = pvParents_->back(); + pvParents_->pop_back(); +} + +void +XmlBuilder::CharacterData(XmlParseContext * pctx, + const char * text, int len) { + UNUSED(pctx); + if (pelCurrent_) { + pelCurrent_->AddParsedText(text, len); + } +} + +void +XmlBuilder::Error(XmlParseContext * pctx, XML_Error err) { + UNUSED(pctx); + UNUSED(err); + pelRoot_.reset(NULL); + pelCurrent_ = NULL; + pvParents_->clear(); +} + +XmlElement * +XmlBuilder::CreateElement() { + return pelRoot_.release(); +} + +XmlElement * +XmlBuilder::BuiltElement() { + return pelRoot_.get(); +} + +XmlBuilder::~XmlBuilder() { +} + + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.h new file mode 100644 index 00000000..b5b1be59 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.h @@ -0,0 +1,79 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _xmlbuilder_h_ +#define _xmlbuilder_h_ + +#include +#include "talk/base/scoped_ptr.h" +#include "talk/base/stl_decl.h" +#include "talk/xmllite/xmlparser.h" + +#ifdef OSX +#include "talk/third_party/expat/expat.h" +#else +#include +#endif + +namespace buzz { + +class XmlElement; +class XmlParseContext; + + +class XmlBuilder : public XmlParseHandler { +public: + XmlBuilder(); + + static XmlElement * BuildElement(XmlParseContext * pctx, + const char * name, const char ** atts); + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts); + virtual void EndElement(XmlParseContext * pctx, const char * name); + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len); + virtual void Error(XmlParseContext * pctx, XML_Error); + virtual ~XmlBuilder(); + + void Reset(); + + // Take ownership of the built element; second call returns NULL + XmlElement * CreateElement(); + + // Peek at the built element without taking ownership + XmlElement * BuiltElement(); + +private: + XmlElement * pelCurrent_; + scoped_ptr pelRoot_; + scoped_ptr > > + pvParents_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.cc new file mode 100644 index 00000000..503f832f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.cc @@ -0,0 +1,65 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "xmlconstants.h" + +using namespace buzz; + +const std::string & XmlConstants::str_empty() { + static const std::string str_empty_; + return str_empty_; +} + +const std::string & XmlConstants::ns_xml() { + static const std::string ns_xml_("http://www.w3.org/XML/1998/namespace"); + return ns_xml_; +} + +const std::string & XmlConstants::ns_xmlns() { + static const std::string ns_xmlns_("http://www.w3.org/2000/xmlns/"); + return ns_xmlns_; +} + +const std::string & XmlConstants::str_xmlns() { + static const std::string str_xmlns_("xmlns"); + return str_xmlns_; +} + +const std::string & XmlConstants::str_xml() { + static const std::string str_xml_("xml"); + return str_xml_; +} + +const std::string & XmlConstants::str_version() { + static const std::string str_version_("version"); + return str_version_; +} + +const std::string & XmlConstants::str_encoding() { + static const std::string str_encoding_("encoding"); + return str_encoding_; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.h new file mode 100644 index 00000000..8514d6f4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.h @@ -0,0 +1,61 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Because global constant initialization order is undefined +// globals cannot depend on other objects to be instantiated. +// This class creates string objects within static methods +// such that globals may refer to these constants by the +// accessor function and they are guaranteed to be initialized. + +#ifndef TALK_XMLLITE_CONSTANTS_H_ +#define TALK_XMLLITE_CONSTANTS_H_ + +#include + +#define STR_EMPTY XmlConstants::str_empty() +#define NS_XML XmlConstants::ns_xml() +#define NS_XMLNS XmlConstants::ns_xmlns() +#define STR_XMLNS XmlConstants::str_xmlns() +#define STR_XML XmlConstants::str_xml() +#define STR_VERSION XmlConstants::str_version() +#define STR_ENCODING XmlConstants::str_encoding() +namespace buzz { + +class XmlConstants { + public: + static const std::string & str_empty(); + static const std::string & ns_xml(); + static const std::string & ns_xmlns(); + static const std::string & str_xmlns(); + static const std::string & str_xml(); + static const std::string & str_version(); + static const std::string & str_encoding(); +}; + +} + +#endif // TALK_XMLLITE_CONSTANTS_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.cc new file mode 100644 index 00000000..d3619a92 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.cc @@ -0,0 +1,491 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "talk/base/common.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/qname.h" +#include "talk/xmllite/xmlparser.h" +#include "talk/xmllite/xmlbuilder.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/xmllite/xmlconstants.h" + +#define new TRACK_NEW + +namespace buzz { + +const QName QN_EMPTY(true, STR_EMPTY, STR_EMPTY); +const QName QN_XMLNS(true, STR_EMPTY, STR_XMLNS); + + +XmlChild::~XmlChild() { +} + +bool +XmlText::IsTextImpl() const { + return true; +} + +XmlElement * +XmlText::AsElementImpl() const { + return NULL; +} + +XmlText * +XmlText::AsTextImpl() const { + return const_cast(this); +} + +void +XmlText::SetText(const std::string & text) { + text_ = text; +} + +void +XmlText::AddParsedText(const char * buf, int len) { + text_.append(buf, len); +} + +void +XmlText::AddText(const std::string & text) { + text_ += text; +} + +XmlText::~XmlText() { +} + +XmlElement::XmlElement(const QName & name) : + name_(name), + pFirstAttr_(NULL), + pLastAttr_(NULL), + pFirstChild_(NULL), + pLastChild_(NULL) { +} + +XmlElement::XmlElement(const XmlElement & elt) : + XmlChild(), + name_(elt.name_), + pFirstAttr_(NULL), + pLastAttr_(NULL), + pFirstChild_(NULL), + pLastChild_(NULL) { + + // copy attributes + XmlAttr * pAttr; + XmlAttr ** ppLastAttr = &pFirstAttr_; + XmlAttr * newAttr = NULL; + for (pAttr = elt.pFirstAttr_; pAttr; pAttr = pAttr->NextAttr()) { + newAttr = new XmlAttr(*pAttr); + *ppLastAttr = newAttr; + ppLastAttr = &(newAttr->pNextAttr_); + } + pLastAttr_ = newAttr; + + // copy children + XmlChild * pChild; + XmlChild ** ppLast = &pFirstChild_; + XmlChild * newChild = NULL; + + for (pChild = elt.pFirstChild_; pChild; pChild = pChild->NextChild()) { + if (pChild->IsText()) { + newChild = new XmlText(*(pChild->AsText())); + } else { + newChild = new XmlElement(*(pChild->AsElement())); + } + *ppLast = newChild; + ppLast = &(newChild->pNextChild_); + } + pLastChild_ = newChild; + +} + +XmlElement::XmlElement(const QName & name, bool useDefaultNs) : + name_(name), + pFirstAttr_(useDefaultNs ? new XmlAttr(QN_XMLNS, name.Namespace()) : NULL), + pLastAttr_(pFirstAttr_), + pFirstChild_(NULL), + pLastChild_(NULL) { +} + +bool +XmlElement::IsTextImpl() const { + return false; +} + +XmlElement * +XmlElement::AsElementImpl() const { + return const_cast(this); +} + +XmlText * +XmlElement::AsTextImpl() const { + return NULL; +} + +const std::string & +XmlElement::BodyText() const { + if (pFirstChild_ && pFirstChild_->IsText() && pLastChild_ == pFirstChild_) { + return pFirstChild_->AsText()->Text(); + } + + return STR_EMPTY; +} + +void +XmlElement::SetBodyText(const std::string & text) { + if (text == STR_EMPTY) { + ClearChildren(); + } else if (pFirstChild_ == NULL) { + AddText(text); + } else if (pFirstChild_->IsText() && pLastChild_ == pFirstChild_) { + pFirstChild_->AsText()->SetText(text); + } else { + ClearChildren(); + AddText(text); + } +} + +const QName & +XmlElement::FirstElementName() const { + const XmlElement * element = FirstElement(); + if (element == NULL) + return QN_EMPTY; + return element->Name(); +} + +XmlAttr * +XmlElement::FirstAttr() { + return pFirstAttr_; +} + +const std::string & +XmlElement::Attr(const QName & name) const { + XmlAttr * pattr; + for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) { + if (pattr->name_ == name) + return pattr->value_; + } + return STR_EMPTY; +} + +bool +XmlElement::HasAttr(const QName & name) const { + XmlAttr * pattr; + for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) { + if (pattr->name_ == name) + return true; + } + return false; +} + +void +XmlElement::SetAttr(const QName & name, const std::string & value) { + XmlAttr * pattr; + for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) { + if (pattr->name_ == name) + break; + } + if (!pattr) { + pattr = new XmlAttr(name, value); + if (pLastAttr_) + pLastAttr_->pNextAttr_ = pattr; + else + pFirstAttr_ = pattr; + pLastAttr_ = pattr; + return; + } + pattr->value_ = value; +} + +void +XmlElement::ClearAttr(const QName & name) { + XmlAttr * pattr; + XmlAttr *pLastAttr = NULL; + for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) { + if (pattr->name_ == name) + break; + pLastAttr = pattr; + } + if (!pattr) + return; + if (!pLastAttr) + pFirstAttr_ = pattr->pNextAttr_; + else + pLastAttr->pNextAttr_ = pattr->pNextAttr_; + if (pLastAttr_ == pattr) + pLastAttr_ = pLastAttr; + delete pattr; +} + +XmlChild * +XmlElement::FirstChild() { + return pFirstChild_; +} + +XmlElement * +XmlElement::FirstElement() { + XmlChild * pChild; + for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText()) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::NextElement() { + XmlChild * pChild; + for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText()) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::FirstWithNamespace(const std::string & ns) { + XmlChild * pChild; + for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::NextWithNamespace(const std::string & ns) { + XmlChild * pChild; + for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::FirstNamed(const QName & name) { + XmlChild * pChild; + for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::NextNamed(const QName & name) { + XmlChild * pChild; + for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement(); + } + return NULL; +} + +const std::string & +XmlElement::TextNamed(const QName & name) const { + XmlChild * pChild; + for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement()->BodyText(); + } + return STR_EMPTY; +} + +void +XmlElement::InsertChildAfter(XmlChild * pPredecessor, XmlChild * pNext) { + if (pPredecessor == NULL) { + pNext->pNextChild_ = pFirstChild_; + pFirstChild_ = pNext; + } + else { + pNext->pNextChild_ = pPredecessor->pNextChild_; + pPredecessor->pNextChild_ = pNext; + } +} + +void +XmlElement::RemoveChildAfter(XmlChild * pPredecessor) { + XmlChild * pNext; + + if (pPredecessor == NULL) { + pNext = pFirstChild_; + pFirstChild_ = pNext->pNextChild_; + } + else { + pNext = pPredecessor->pNextChild_; + pPredecessor->pNextChild_ = pNext->pNextChild_; + } + + if (pLastChild_ == pNext) + pLastChild_ = pPredecessor; + + delete pNext; +} + +void +XmlElement::AddAttr(const QName & name, const std::string & value) { + ASSERT(!HasAttr(name)); + + XmlAttr ** pprev = pLastAttr_ ? &(pLastAttr_->pNextAttr_) : &pFirstAttr_; + pLastAttr_ = (*pprev = new XmlAttr(name, value)); +} + +void +XmlElement::AddAttr(const QName & name, const std::string & value, + int depth) { + XmlElement * element = this; + while (depth--) { + element = element->pLastChild_->AsElement(); + } + element->AddAttr(name, value); +} + +void +XmlElement::AddParsedText(const char * cstr, int len) { + if (len == 0) + return; + + if (pLastChild_ && pLastChild_->IsText()) { + pLastChild_->AsText()->AddParsedText(cstr, len); + return; + } + XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_; + pLastChild_ = *pprev = new XmlText(cstr, len); +} + +void +XmlElement::AddText(const std::string & text) { + if (text == STR_EMPTY) + return; + + if (pLastChild_ && pLastChild_->IsText()) { + pLastChild_->AsText()->AddText(text); + return; + } + XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_; + pLastChild_ = *pprev = new XmlText(text); +} + +void +XmlElement::AddText(const std::string & text, int depth) { + // note: the first syntax is ambigious for msvc 6 + // XmlElement * pel(this); + XmlElement * element = this; + while (depth--) { + element = element->pLastChild_->AsElement(); + } + element->AddText(text); +} + +void +XmlElement::AddElement(XmlElement *pelChild) { + if (pelChild == NULL) + return; + + XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_; + pLastChild_ = *pprev = pelChild; + pelChild->pNextChild_ = NULL; +} + +void +XmlElement::AddElement(XmlElement *pelChild, int depth) { + XmlElement * element = this; + while (depth--) { + element = element->pLastChild_->AsElement(); + } + element->AddElement(pelChild); +} + +void +XmlElement::ClearNamedChildren(const QName & name) { + XmlChild * prev_child = NULL; + XmlChild * next_child; + XmlChild * child; + for (child = FirstChild(); child; child = next_child) { + next_child = child->NextChild(); + if (!child->IsText() && child->AsElement()->Name() == name) + { + RemoveChildAfter(prev_child); + continue; + } + prev_child = child; + } +} + +void +XmlElement::ClearChildren() { + XmlChild * pchild; + for (pchild = pFirstChild_; pchild; ) { + XmlChild * pToDelete = pchild; + pchild = pchild->pNextChild_; + delete pToDelete; + } + pFirstChild_ = pLastChild_ = NULL; +} + +std::string +XmlElement::Str() const { + std::stringstream ss; + Print(&ss, NULL, 0); + return ss.str(); +} + +XmlElement * +XmlElement::ForStr(const std::string & str) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, str); + return builder.CreateElement(); +} + +void +XmlElement::Print( + std::ostream * pout, std::string xmlns[], int xmlnsCount) const { + XmlPrinter::PrintXml(pout, this, xmlns, xmlnsCount); +} + +XmlElement::~XmlElement() { + XmlAttr * pattr; + for (pattr = pFirstAttr_; pattr; ) { + XmlAttr * pToDelete = pattr; + pattr = pattr->pNextAttr_; + delete pToDelete; + } + + XmlChild * pchild; + for (pchild = pFirstChild_; pchild; ) { + XmlChild * pToDelete = pchild; + pchild = pchild->pNextChild_; + delete pToDelete; + } +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.h new file mode 100644 index 00000000..06545d89 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.h @@ -0,0 +1,231 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _xmlelement_h_ +#define _xmlelement_h_ + +#include +#include +#include "talk/base/scoped_ptr.h" +#include "talk/xmllite/qname.h" + +namespace buzz { + +extern const QName QN_EMPTY; +extern const QName QN_XMLNS; + + +class XmlChild; +class XmlText; +class XmlElement; +class XmlAttr; + +class XmlChild { +friend class XmlElement; + +public: + + XmlChild * NextChild() { return pNextChild_; } + const XmlChild * NextChild() const { return pNextChild_; } + + bool IsText() const { return IsTextImpl(); } + + XmlElement * AsElement() { return AsElementImpl(); } + const XmlElement * AsElement() const { return AsElementImpl(); } + + XmlText * AsText() { return AsTextImpl(); } + const XmlText * AsText() const { return AsTextImpl(); } + + +protected: + + XmlChild() : + pNextChild_(NULL) { + } + + virtual bool IsTextImpl() const = 0; + virtual XmlElement * AsElementImpl() const = 0; + virtual XmlText * AsTextImpl() const = 0; + + + virtual ~XmlChild(); + +private: + XmlChild(const XmlChild & noimpl); + + XmlChild * pNextChild_; + +}; + +class XmlText : public XmlChild { +public: + explicit XmlText(const std::string & text) : + XmlChild(), + text_(text) { + } + explicit XmlText(const XmlText & t) : + XmlChild(), + text_(t.text_) { + } + explicit XmlText(const char * cstr, size_t len) : + XmlChild(), + text_(cstr, len) { + } + virtual ~XmlText(); + + const std::string & Text() const { return text_; } + void SetText(const std::string & text); + void AddParsedText(const char * buf, int len); + void AddText(const std::string & text); + +protected: + virtual bool IsTextImpl() const; + virtual XmlElement * AsElementImpl() const; + virtual XmlText * AsTextImpl() const; + +private: + std::string text_; +}; + +class XmlAttr { +friend class XmlElement; + +public: + XmlAttr * NextAttr() const { return pNextAttr_; } + const QName & Name() const { return name_; } + const std::string & Value() const { return value_; } + +private: + explicit XmlAttr(const QName & name, const std::string & value) : + pNextAttr_(NULL), + name_(name), + value_(value) { + } + explicit XmlAttr(const XmlAttr & att) : + pNextAttr_(NULL), + name_(att.name_), + value_(att.value_) { + } + + XmlAttr * pNextAttr_; + QName name_; + std::string value_; +}; + +class XmlElement : public XmlChild { +public: + explicit XmlElement(const QName & name); + explicit XmlElement(const QName & name, bool useDefaultNs); + explicit XmlElement(const XmlElement & elt); + + virtual ~XmlElement(); + + const QName & Name() const { return name_; } + + const std::string & BodyText() const; + void SetBodyText(const std::string & text); + + const QName & FirstElementName() const; + + XmlAttr * FirstAttr(); + const XmlAttr * FirstAttr() const + { return const_cast(this)->FirstAttr(); } + + //! Attr will return STR_EMPTY if the attribute isn't there: + //! use HasAttr to test presence of an attribute. + const std::string & Attr(const QName & name) const; + bool HasAttr(const QName & name) const; + void SetAttr(const QName & name, const std::string & value); + void ClearAttr(const QName & name); + + XmlChild * FirstChild(); + const XmlChild * FirstChild() const + { return const_cast(this)->FirstChild(); } + + XmlElement * FirstElement(); + const XmlElement * FirstElement() const + { return const_cast(this)->FirstElement(); } + + XmlElement * NextElement(); + const XmlElement * NextElement() const + { return const_cast(this)->NextElement(); } + + XmlElement * FirstWithNamespace(const std::string & ns); + const XmlElement * FirstWithNamespace(const std::string & ns) const + { return const_cast(this)->FirstWithNamespace(ns); } + + XmlElement * NextWithNamespace(const std::string & ns); + const XmlElement * NextWithNamespace(const std::string & ns) const + { return const_cast(this)->NextWithNamespace(ns); } + + XmlElement * FirstNamed(const QName & name); + const XmlElement * FirstNamed(const QName & name) const + { return const_cast(this)->FirstNamed(name); } + + XmlElement * NextNamed(const QName & name); + const XmlElement * NextNamed(const QName & name) const + { return const_cast(this)->NextNamed(name); } + + const std::string & TextNamed(const QName & name) const; + + void InsertChildAfter(XmlChild * pPredecessor, XmlChild * pNewChild); + void RemoveChildAfter(XmlChild * pPredecessor); + + void AddParsedText(const char * buf, int len); + void AddText(const std::string & text); + void AddText(const std::string & text, int depth); + void AddElement(XmlElement * pelChild); + void AddElement(XmlElement * pelChild, int depth); + void AddAttr(const QName & name, const std::string & value); + void AddAttr(const QName & name, const std::string & value, int depth); + void ClearNamedChildren(const QName & name); + void ClearChildren(); + + static XmlElement * ForStr(const std::string & str); + std::string Str() const; + + void Print(std::ostream * pout, std::string xmlns[], int xmlnsCount) const; + +protected: + virtual bool IsTextImpl() const; + virtual XmlElement * AsElementImpl() const; + virtual XmlText * AsTextImpl() const; + +private: + QName name_; + XmlAttr * pFirstAttr_; + XmlAttr * pLastAttr_; + XmlChild * pFirstChild_; + XmlChild * pLastChild_; + +}; + + + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.cc new file mode 100644 index 00000000..4dcb6490 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.cc @@ -0,0 +1,205 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/stl_decl.h" +#include +#include +#include +#include +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlnsstack.h" +#include "talk/xmllite/xmlconstants.h" + +namespace buzz { + +XmlnsStack::XmlnsStack() : + pxmlnsStack_(new std::vector), + pxmlnsDepthStack_(new std::vector) { +} + +XmlnsStack::~XmlnsStack() {} + +void +XmlnsStack::PushFrame() { + pxmlnsDepthStack_->push_back(pxmlnsStack_->size()); +} + +void +XmlnsStack::PopFrame() { + size_t prev_size = pxmlnsDepthStack_->back(); + pxmlnsDepthStack_->pop_back(); + if (prev_size < pxmlnsStack_->size()) { + pxmlnsStack_->erase(pxmlnsStack_->begin() + prev_size, + pxmlnsStack_->end()); + } +} +const std::pair NS_NOT_FOUND(STR_EMPTY, false); +const std::pair EMPTY_NS_FOUND(STR_EMPTY, true); +const std::pair XMLNS_DEFINITION_FOUND(NS_XMLNS, true); + +const std::string * +XmlnsStack::NsForPrefix(const std::string & prefix) { + if (prefix.length() >= 3 && + (prefix[0] == 'x' || prefix[0] == 'X') && + (prefix[1] == 'm' || prefix[1] == 'M') && + (prefix[2] == 'l' || prefix[2] == 'L')) { + if (prefix == "xml") + return &(NS_XML); + if (prefix == "xmlns") + return &(NS_XMLNS); + return NULL; + } + + std::vector::iterator pos; + for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) { + pos -= 2; + if (*pos == prefix) + return &(*(pos + 1)); + } + + if (prefix == STR_EMPTY) + return &(STR_EMPTY); // default namespace + + return NULL; // none found +} + +bool +XmlnsStack::PrefixMatchesNs(const std::string & prefix, const std::string & ns) { + const std::string * match = NsForPrefix(prefix); + if (match == NULL) + return false; + return (*match == ns); +} + +std::pair +XmlnsStack::PrefixForNs(const std::string & ns, bool isattr) { + if (ns == NS_XML) + return std::make_pair(std::string("xml"), true); + if (ns == NS_XMLNS) + return std::make_pair(std::string("xmlns"), true); + if (isattr ? ns == STR_EMPTY : PrefixMatchesNs(STR_EMPTY, ns)) + return std::make_pair(STR_EMPTY, true); + + std::vector::iterator pos; + for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) { + pos -= 2; + if (*(pos + 1) == ns && + (!isattr || !pos->empty()) && PrefixMatchesNs(*pos, ns)) + return std::make_pair(*pos, true); + } + + return std::make_pair(STR_EMPTY, false); // none found +} + +std::string +XmlnsStack::FormatQName(const QName & name, bool isAttr) { + std::string prefix(PrefixForNs(name.Namespace(), isAttr).first); + if (prefix == STR_EMPTY) + return name.LocalPart(); + else + return prefix + ':' + name.LocalPart(); +} + +void +XmlnsStack::AddXmlns(const std::string & prefix, const std::string & ns) { + pxmlnsStack_->push_back(prefix); + pxmlnsStack_->push_back(ns); +} + +void +XmlnsStack::RemoveXmlns() { + pxmlnsStack_->pop_back(); + pxmlnsStack_->pop_back(); +} + +static bool IsAsciiLetter(char ch) { + return ((ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z')); +} + +static std::string AsciiLower(const std::string & s) { + std::string result(s); + size_t i; + for (i = 0; i < result.length(); i++) { + if (result[i] >= 'A' && result[i] <= 'Z') + result[i] += 'a' - 'A'; + } + return result; +} + +static std::string SuggestPrefix(const std::string & ns) { + size_t len = ns.length(); + size_t i = ns.find_last_of('.'); + if (i != std::string::npos && len - i <= 4 + 1) + len = i; // chop off ".html" or ".xsd" or ".?{0,4}" + size_t last = len; + while (last > 0) { + last -= 1; + if (IsAsciiLetter(ns[last])) { + size_t first = last; + last += 1; + while (first > 0) { + if (!IsAsciiLetter(ns[first - 1])) + break; + first -= 1; + } + if (last - first > 4) + last = first + 3; + std::string candidate(AsciiLower(ns.substr(first, last - first))); + if (candidate.find("xml") != 0) + return candidate; + break; + } + } + return "ns"; +} + + +std::pair +XmlnsStack::AddNewPrefix(const std::string & ns, bool isAttr) { + if (PrefixForNs(ns, isAttr).second) + return std::make_pair(STR_EMPTY, false); + + std::string base(SuggestPrefix(ns)); + std::string result(base); + int i = 2; + while (NsForPrefix(result) != NULL) { + std::stringstream ss; + ss << base; + ss << (i++); + ss >> result; + } + AddXmlns(result, ns); + return std::make_pair(result, true); +} + +void XmlnsStack::Reset() { + pxmlnsStack_->clear(); + pxmlnsDepthStack_->clear(); +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.h new file mode 100644 index 00000000..299ec1ce --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.h @@ -0,0 +1,62 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _xmlnsstack_h_ +#define _xmlnsstack_h_ + +#include +#include "talk/base/scoped_ptr.h" +#include "talk/base/stl_decl.h" +#include "talk/xmllite/qname.h" + +namespace buzz { + +class XmlnsStack { +public: + XmlnsStack(); + ~XmlnsStack(); + + void AddXmlns(const std::string & prefix, const std::string & ns); + void RemoveXmlns(); + void PushFrame(); + void PopFrame(); + void Reset(); + + const std::string * NsForPrefix(const std::string & prefix); + bool PrefixMatchesNs(const std::string & prefix, const std::string & ns); + std::pair PrefixForNs(const std::string & ns, bool isAttr); + std::pair AddNewPrefix(const std::string & ns, bool isAttr); + std::string FormatQName(const QName & name, bool isAttr); + +private: + + scoped_ptr > > pxmlnsStack_; + scoped_ptr > > pxmlnsDepthStack_; +}; +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.cc new file mode 100644 index 00000000..f2b56778 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.cc @@ -0,0 +1,250 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/stl_decl.h" +#include +#include +#include +#include +#include "talk/xmllite/xmlelement.h" +#include "talk/base/common.h" +#include "talk/xmllite/xmlparser.h" +#include "talk/xmllite/xmlnsstack.h" +#include "talk/xmllite/xmlconstants.h" + +#include + +#define new TRACK_NEW + +namespace buzz { + + +static void +StartElementCallback(void * userData, const char *name, const char **atts) { + (static_cast(userData))->ExpatStartElement(name, atts); +} + +static void +EndElementCallback(void * userData, const char *name) { + (static_cast(userData))->ExpatEndElement(name); +} + +static void +CharacterDataCallback(void * userData, const char *text, int len) { + (static_cast(userData))->ExpatCharacterData(text, len); +} + +static void +XmlDeclCallback(void * userData, const char * ver, const char * enc, int st) { + (static_cast(userData))->ExpatXmlDecl(ver, enc, st); +} + +XmlParser::XmlParser(XmlParseHandler *pxph) : + context_(this), pxph_(pxph), sentError_(false) { + expat_ = XML_ParserCreate(NULL); + XML_SetUserData(expat_, this); + XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback); + XML_SetCharacterDataHandler(expat_, CharacterDataCallback); + XML_SetXmlDeclHandler(expat_, XmlDeclCallback); +} + +void +XmlParser::Reset() { + if (!XML_ParserReset(expat_, NULL)) { + XML_ParserFree(expat_); + expat_ = XML_ParserCreate(NULL); + } + XML_SetUserData(expat_, this); + XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback); + XML_SetCharacterDataHandler(expat_, CharacterDataCallback); + XML_SetXmlDeclHandler(expat_, XmlDeclCallback); + context_.Reset(); + sentError_ = false; +} + +static bool +XmlParser_StartsWithXmlns(const char *name) { + return name[0] == 'x' && + name[1] == 'm' && + name[2] == 'l' && + name[3] == 'n' && + name[4] == 's'; +} + +void +XmlParser::ExpatStartElement(const char *name, const char **atts) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + const char **att; + context_.StartElement(); + for (att = atts; *att; att += 2) { + if (XmlParser_StartsWithXmlns(*att)) { + if ((*att)[5] == '\0') { + context_.StartNamespace("", *(att + 1)); + } + else if ((*att)[5] == ':') { + if (**(att + 1) == '\0') { + // In XML 1.0 empty namespace illegal with prefix (not in 1.1) + context_.RaiseError(XML_ERROR_SYNTAX); + return; + } + context_.StartNamespace((*att) + 6, *(att + 1)); + } + } + } + pxph_->StartElement(&context_, name, atts); +} + +void +XmlParser::ExpatEndElement(const char *name) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + context_.EndElement(); + pxph_->EndElement(&context_, name); +} + +void +XmlParser::ExpatCharacterData(const char *text, int len) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + pxph_->CharacterData(&context_, text, len); +} + +void +XmlParser::ExpatXmlDecl(const char * ver, const char * enc, int standalone) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + + if (ver && std::string("1.0") != ver) { + context_.RaiseError(XML_ERROR_SYNTAX); + return; + } + + if (standalone == 0) { + context_.RaiseError(XML_ERROR_SYNTAX); + return; + } + + if (enc && !((enc[0] == 'U' || enc[0] == 'u') && + (enc[1] == 'T' || enc[1] == 't') && + (enc[2] == 'F' || enc[2] == 'f') && + enc[3] == '-' && enc[4] =='8')) { + context_.RaiseError(XML_ERROR_INCORRECT_ENCODING); + return; + } + +} + +bool +XmlParser::Parse(const char *data, size_t len, bool isFinal) { + if (sentError_) + return false; + + if (XML_Parse(expat_, data, static_cast(len), isFinal) != XML_STATUS_OK) + context_.RaiseError(XML_GetErrorCode(expat_)); + + if (context_.RaisedError() != XML_ERROR_NONE) { + sentError_ = true; + pxph_->Error(&context_, context_.RaisedError()); + return false; + } + + return true; +} + +XmlParser::~XmlParser() { + XML_ParserFree(expat_); +} + +void +XmlParser::ParseXml(XmlParseHandler *pxph, std::string text) { + XmlParser parser(pxph); + parser.Parse(text.c_str(), text.length(), true); +} + +XmlParser::ParseContext::ParseContext(XmlParser *parser) : + parser_(parser), + xmlnsstack_(), + raised_(XML_ERROR_NONE) { +} + +void +XmlParser::ParseContext::StartNamespace(const char *prefix, const char *ns) { + xmlnsstack_.AddXmlns( + *prefix ? std::string(prefix) : STR_EMPTY, +// ns == NS_CLIENT ? NS_CLIENT : +// ns == NS_ROSTER ? NS_ROSTER : +// ns == NS_GR ? NS_GR : + std::string(ns)); +} + +void +XmlParser::ParseContext::StartElement() { + xmlnsstack_.PushFrame(); +} + +void +XmlParser::ParseContext::EndElement() { + xmlnsstack_.PopFrame(); +} + +QName +XmlParser::ParseContext::ResolveQName(const char *qname, bool isAttr) { + const char *c; + for (c = qname; *c; ++c) { + if (*c == ':') { + const std::string * result; + result = xmlnsstack_.NsForPrefix(std::string(qname, c - qname)); + if (result == NULL) + return QN_EMPTY; + const char * localname = c + 1; + return QName(*result, localname); + } + } + if (isAttr) { + return QName(STR_EMPTY, qname); + } + + const std::string * result; + result = xmlnsstack_.NsForPrefix(STR_EMPTY); + if (result == NULL) + return QN_EMPTY; + + return QName(*result, qname); +} + +void +XmlParser::ParseContext::Reset() { + xmlnsstack_.Reset(); + raised_ = XML_ERROR_NONE; +} + +XmlParser::ParseContext::~ParseContext() { +} + +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.h new file mode 100644 index 00000000..760802e4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.h @@ -0,0 +1,108 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _xmlparser_h_ +#define _xmlparser_h_ + +#include +#include "talk/xmllite/xmlnsstack.h" +#include + +struct XML_ParserStruct; +typedef struct XML_ParserStruct * XML_Parser; + +namespace buzz { + +class XmlParseHandler; +class XmlParseContext; +class XmlParser; + +class XmlParseContext { +public: + virtual QName ResolveQName(const char * qname, bool isAttr) = 0; + virtual void RaiseError(XML_Error err) = 0; +}; + +class XmlParseHandler { +public: + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) = 0; + virtual void EndElement(XmlParseContext * pctx, + const char * name) = 0; + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len) = 0; + virtual void Error(XmlParseContext * pctx, + XML_Error errorCode) = 0; +}; + +class XmlParser { +public: + static void ParseXml(XmlParseHandler * pxph, std::string text); + + explicit XmlParser(XmlParseHandler * pxph); + bool Parse(const char * data, size_t len, bool isFinal); + void Reset(); + virtual ~XmlParser(); + + // expat callbacks + void ExpatStartElement(const char * name, const char ** atts); + void ExpatEndElement(const char * name); + void ExpatCharacterData(const char * text, int len); + void ExpatXmlDecl(const char * ver, const char * enc, int standalone); + +private: + + class ParseContext : public XmlParseContext { + public: + ParseContext(XmlParser * parser); + virtual ~ParseContext(); + virtual QName ResolveQName(const char * qname, bool isAttr); + virtual void RaiseError(XML_Error err) { if (!raised_) raised_ = err; } + XML_Error RaisedError() { return raised_; } + void Reset(); + + void StartElement(); + void EndElement(); + void StartNamespace(const char * prefix, const char * ns); + + private: + const XmlParser * parser_; + XmlnsStack xmlnsstack_; + XML_Error raised_; + }; + + ParseContext context_; + XML_Parser expat_; + XmlParseHandler * pxph_; + bool sentError_; + + +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.cc new file mode 100644 index 00000000..892e2ebb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.cc @@ -0,0 +1,190 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/stl_decl.h" +#include +#include +#include +#include +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/xmllite/xmlnsstack.h" +#include "talk/xmllite/xmlconstants.h" + +namespace buzz { + +class XmlPrinterImpl { +public: + XmlPrinterImpl(std::ostream * pout, + const std::string * const xmlns, int xmlnsCount); + void PrintElement(const XmlElement * element); + void PrintQuotedValue(const std::string & text); + void PrintBodyText(const std::string & text); + +private: + std::ostream *pout_; + XmlnsStack xmlnsStack_; +}; + +void +XmlPrinter::PrintXml(std::ostream * pout, const XmlElement * element) { + PrintXml(pout, element, NULL, 0); +} + +void +XmlPrinter::PrintXml(std::ostream * pout, const XmlElement * element, + const std::string * const xmlns, int xmlnsCount) { + XmlPrinterImpl printer(pout, xmlns, xmlnsCount); + printer.PrintElement(element); +} + +XmlPrinterImpl::XmlPrinterImpl(std::ostream * pout, + const std::string * const xmlns, int xmlnsCount) : + pout_(pout), + xmlnsStack_() { + int i; + for (i = 0; i < xmlnsCount; i += 2) { + xmlnsStack_.AddXmlns(xmlns[i], xmlns[i + 1]); + } +} + +void +XmlPrinterImpl::PrintElement(const XmlElement * element) { + xmlnsStack_.PushFrame(); + + // first go through attrs of pel to add xmlns definitions + const XmlAttr * pattr; + for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) { + if (pattr->Name() == QN_XMLNS) + xmlnsStack_.AddXmlns(STR_EMPTY, pattr->Value()); + else if (pattr->Name().Namespace() == NS_XMLNS) + xmlnsStack_.AddXmlns(pattr->Name().LocalPart(), + pattr->Value()); + } + + // then go through qnames to make sure needed xmlns definitons are added + std::vector newXmlns; + std::pair prefix; + prefix = xmlnsStack_.AddNewPrefix(element->Name().Namespace(), false); + if (prefix.second) { + newXmlns.push_back(prefix.first); + newXmlns.push_back(element->Name().Namespace()); + } + + for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) { + prefix = xmlnsStack_.AddNewPrefix(pattr->Name().Namespace(), true); + if (prefix.second) { + newXmlns.push_back(prefix.first); + newXmlns.push_back(element->Name().Namespace()); + } + } + + // print the element name + *pout_ << '<' << xmlnsStack_.FormatQName(element->Name(), false); + + // and the attributes + for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) { + *pout_ << ' ' << xmlnsStack_.FormatQName(pattr->Name(), true) << "=\""; + PrintQuotedValue(pattr->Value()); + *pout_ << '"'; + } + + // and the extra xmlns declarations + std::vector::iterator i(newXmlns.begin()); + while (i < newXmlns.end()) { + if (*i == STR_EMPTY) + *pout_ << " xmlns=\"" << *(i + 1) << '"'; + else + *pout_ << " xmlns:" << *i << "=\"" << *(i + 1) << '"'; + i += 2; + } + + // now the children + const XmlChild * pchild = element->FirstChild(); + + if (pchild == NULL) + *pout_ << "/>"; + else { + *pout_ << '>'; + while (pchild) { + if (pchild->IsText()) + PrintBodyText(pchild->AsText()->Text()); + else + PrintElement(pchild->AsElement()); + pchild = pchild->NextChild(); + } + *pout_ << "Name(), false) << '>'; + } + + xmlnsStack_.PopFrame(); +} + +void +XmlPrinterImpl::PrintQuotedValue(const std::string & text) { + size_t safe = 0; + for (;;) { + size_t unsafe = text.find_first_of("<>&\"", safe); + if (unsafe == std::string::npos) + unsafe = text.length(); + *pout_ << text.substr(safe, unsafe - safe); + if (unsafe == text.length()) + return; + switch (text[unsafe]) { + case '<': *pout_ << "<"; break; + case '>': *pout_ << ">"; break; + case '&': *pout_ << "&"; break; + case '"': *pout_ << """; break; + } + safe = unsafe + 1; + if (safe == text.length()) + return; + } +} + +void +XmlPrinterImpl::PrintBodyText(const std::string & text) { + size_t safe = 0; + for (;;) { + size_t unsafe = text.find_first_of("<>&", safe); + if (unsafe == std::string::npos) + unsafe = text.length(); + *pout_ << text.substr(safe, unsafe - safe); + if (unsafe == text.length()) + return; + switch (text[unsafe]) { + case '<': *pout_ << "<"; break; + case '>': *pout_ << ">"; break; + case '&': *pout_ << "&"; break; + } + safe = unsafe + 1; + if (safe == text.length()) + return; + } +} + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.h new file mode 100644 index 00000000..96900d0d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.h @@ -0,0 +1,49 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _xmlprinter_h_ +#define _xmlprinter_h_ + +#include +#include +#include "talk/base/scoped_ptr.h" + +namespace buzz { + +class XmlElement; + +class XmlPrinter { +public: + static void PrintXml(std::ostream * pout, const XmlElement * pelt); + + static void PrintXml(std::ostream * pout, const XmlElement * pelt, + const std::string * const xmlns, int xmlnsCount); +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/Makefile.am new file mode 100644 index 00000000..527f7053 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/Makefile.am @@ -0,0 +1,34 @@ +## Does not compile with final +KDE_OPTIONS = nofinal + +libcricketxmpp_la_SOURCES = constants.cc \ + jid.cc \ + saslmechanism.cc \ + xmppclient.cc \ + xmppengineimpl.cc \ + xmppengineimpl_iq.cc \ + xmpplogintask.cc \ + xmppstanzaparser.cc \ + xmpptask.cc + +noinst_HEADERS = asyncsocket.h \ + prexmppauth.h \ + saslhandler.h \ + xmpplogintask.h \ + jid.h \ + saslmechanism.h \ + xmppclient.h \ + xmpppassword.h \ + constants.h \ + saslplainmechanism.h \ + xmppclientsettings.h \ + xmppstanzaparser.h \ + xmppengine.h \ + xmpptask.h \ + plainsaslhandler.h \ + saslcookiemechanism.h \ + xmppengineimpl.h + + +AM_CPPFLAGS = -DPOSIX -I$(srcdir)/../.. +noinst_LTLIBRARIES = libcricketxmpp.la diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/asyncsocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/asyncsocket.h new file mode 100644 index 00000000..fd91929b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/asyncsocket.h @@ -0,0 +1,85 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _ASYNCSOCKET_H_ +#define _ASYNCSOCKET_H_ + +#include "talk/base/sigslot.h" + +namespace cricket { + class SocketAddress; +} + +namespace buzz { + +class AsyncSocket { +public: + enum State { + STATE_CLOSED = 0, //!< Socket is not open. + STATE_CLOSING, //!< Socket is closing but can have buffered data + STATE_CONNECTING, //!< In the process of + STATE_OPEN, //!< Socket is connected +#if defined(FEATURE_ENABLE_SSL) + STATE_TLS_CONNECTING, //!< Establishing TLS connection + STATE_TLS_OPEN, //!< TLS connected +#endif + }; + + enum Error { + ERROR_NONE = 0, //!< No error + ERROR_WINSOCK, //!< Winsock error + ERROR_DNS, //!< Couldn't resolve host name + ERROR_WRONGSTATE, //!< Call made while socket is in the wrong state +#if defined(FEATURE_ENABLE_SSL) + ERROR_SSL, //!< Something went wrong with OpenSSL +#endif + }; + + virtual ~AsyncSocket() {} + virtual State state() = 0; + virtual Error error() = 0; + + virtual bool Connect(const cricket::SocketAddress& addr) = 0; + virtual bool Read(char * data, size_t len, size_t* len_read) = 0; + virtual bool Write(const char * data, size_t len) = 0; + virtual bool Close() = 0; +#if defined(FEATURE_ENABLE_SSL) + // We allow matching any passed domain. + // If both names are passed as empty, we do not require a match. + virtual bool StartTls(const std::string & domainname) = 0; +#endif + + sigslot::signal0<> SignalConnected; + sigslot::signal0<> SignalSSLConnected; + sigslot::signal0<> SignalClosed; + sigslot::signal0<> SignalRead; + sigslot::signal0<> SignalError; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.cc new file mode 100644 index 00000000..b2c833f7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.cc @@ -0,0 +1,331 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "talk/base/basicdefs.h" +#include "talk/xmllite/xmlconstants.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/qname.h" +#include "talk/xmpp/jid.h" +#include "talk/xmpp/constants.h" +namespace buzz { + +const Jid JID_EMPTY(STR_EMPTY); + +const std::string & Constants::ns_client() { + static const std::string ns_client_("jabber:client"); + return ns_client_; +} + +const std::string & Constants::ns_server() { + static const std::string ns_server_("jabber:server"); + return ns_server_; +} + +const std::string & Constants::ns_stream() { + static const std::string ns_stream_("http://etherx.jabber.org/streams"); + return ns_stream_; +} + +const std::string & Constants::ns_xstream() { + static const std::string ns_xstream_("urn:ietf:params:xml:ns:xmpp-streams"); + return ns_xstream_; +} + +const std::string & Constants::ns_tls() { + static const std::string ns_tls_("urn:ietf:params:xml:ns:xmpp-tls"); + return ns_tls_; +} + +const std::string & Constants::ns_sasl() { + static const std::string ns_sasl_("urn:ietf:params:xml:ns:xmpp-sasl"); + return ns_sasl_; +} + +const std::string & Constants::ns_bind() { + static const std::string ns_bind_("urn:ietf:params:xml:ns:xmpp-bind"); + return ns_bind_; +} + +const std::string & Constants::ns_dialback() { + static const std::string ns_dialback_("jabber:server:dialback"); + return ns_dialback_; +} + +const std::string & Constants::ns_session() { + static const std::string ns_session_("urn:ietf:params:xml:ns:xmpp-session"); + return ns_session_; +} + +const std::string & Constants::ns_stanza() { + static const std::string ns_stanza_("urn:ietf:params:xml:ns:xmpp-stanzas"); + return ns_stanza_; +} + +const std::string & Constants::ns_privacy() { + static const std::string ns_privacy_("jabber:iq:privacy"); + return ns_privacy_; +} + +const std::string & Constants::ns_roster() { + static const std::string ns_roster_("jabber:iq:roster"); + return ns_roster_; +} + +const std::string & Constants::ns_vcard() { + static const std::string ns_vcard_("vcard-temp"); + return ns_vcard_; +} + +const std::string & Constants::str_client() { + static const std::string str_client_("client"); + return str_client_; +} + +const std::string & Constants::str_server() { + static const std::string str_server_("server"); + return str_server_; +} + +const std::string & Constants::str_stream() { + static const std::string str_stream_("stream"); + return str_stream_; +} + +const std::string STR_GET("get"); +const std::string STR_SET("set"); +const std::string STR_RESULT("result"); +const std::string STR_ERROR("error"); + +const std::string STR_FROM("from"); +const std::string STR_TO("to"); +const std::string STR_BOTH("both"); +const std::string STR_REMOVE("remove"); + +const std::string STR_UNAVAILABLE("unavailable"); +const std::string STR_INVISIBLE("invisible"); + +const std::string STR_GOOGLE_COM("google.com"); +const std::string STR_GMAIL_COM("gmail.com"); +const std::string STR_GOOGLEMAIL_COM("googlemail.com"); +const std::string STR_DEFAULT_DOMAIN("default.talk.google.com"); +const std::string STR_X("x"); + +const QName QN_STREAM_STREAM(true, NS_STREAM, STR_STREAM); +const QName QN_STREAM_FEATURES(true, NS_STREAM, "features"); +const QName QN_STREAM_ERROR(true, NS_STREAM, "error"); + +const QName QN_XSTREAM_BAD_FORMAT(true, NS_XSTREAM, "bad-format"); +const QName QN_XSTREAM_BAD_NAMESPACE_PREFIX(true, NS_XSTREAM, "bad-namespace-prefix"); +const QName QN_XSTREAM_CONFLICT(true, NS_XSTREAM, "conflict"); +const QName QN_XSTREAM_CONNECTION_TIMEOUT(true, NS_XSTREAM, "connection-timeout"); +const QName QN_XSTREAM_HOST_GONE(true, NS_XSTREAM, "host-gone"); +const QName QN_XSTREAM_HOST_UNKNOWN(true, NS_XSTREAM, "host-unknown"); +const QName QN_XSTREAM_IMPROPER_ADDRESSIING(true, NS_XSTREAM, "improper-addressing"); +const QName QN_XSTREAM_INTERNAL_SERVER_ERROR(true, NS_XSTREAM, "internal-server-error"); +const QName QN_XSTREAM_INVALID_FROM(true, NS_XSTREAM, "invalid-from"); +const QName QN_XSTREAM_INVALID_ID(true, NS_XSTREAM, "invalid-id"); +const QName QN_XSTREAM_INVALID_NAMESPACE(true, NS_XSTREAM, "invalid-namespace"); +const QName QN_XSTREAM_INVALID_XML(true, NS_XSTREAM, "invalid-xml"); +const QName QN_XSTREAM_NOT_AUTHORIZED(true, NS_XSTREAM, "not-authorized"); +const QName QN_XSTREAM_POLICY_VIOLATION(true, NS_XSTREAM, "policy-violation"); +const QName QN_XSTREAM_REMOTE_CONNECTION_FAILED(true, NS_XSTREAM, "remote-connection-failed"); +const QName QN_XSTREAM_RESOURCE_CONSTRAINT(true, NS_XSTREAM, "resource-constraint"); +const QName QN_XSTREAM_RESTRICTED_XML(true, NS_XSTREAM, "restricted-xml"); +const QName QN_XSTREAM_SEE_OTHER_HOST(true, NS_XSTREAM, "see-other-host"); +const QName QN_XSTREAM_SYSTEM_SHUTDOWN(true, NS_XSTREAM, "system-shutdown"); +const QName QN_XSTREAM_UNDEFINED_CONDITION(true, NS_XSTREAM, "undefined-condition"); +const QName QN_XSTREAM_UNSUPPORTED_ENCODING(true, NS_XSTREAM, "unsupported-encoding"); +const QName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE(true, NS_XSTREAM, "unsupported-stanza-type"); +const QName QN_XSTREAM_UNSUPPORTED_VERSION(true, NS_XSTREAM, "unsupported-version"); +const QName QN_XSTREAM_XML_NOT_WELL_FORMED(true, NS_XSTREAM, "xml-not-well-formed"); +const QName QN_XSTREAM_TEXT(true, NS_XSTREAM, "text"); + +const QName QN_TLS_STARTTLS(true, NS_TLS, "starttls"); +const QName QN_TLS_REQUIRED(true, NS_TLS, "required"); +const QName QN_TLS_PROCEED(true, NS_TLS, "proceed"); +const QName QN_TLS_FAILURE(true, NS_TLS, "failure"); + +const QName QN_SASL_MECHANISMS(true, NS_SASL, "mechanisms"); +const QName QN_SASL_MECHANISM(true, NS_SASL, "mechanism"); +const QName QN_SASL_AUTH(true, NS_SASL, "auth"); +const QName QN_SASL_CHALLENGE(true, NS_SASL, "challenge"); +const QName QN_SASL_RESPONSE(true, NS_SASL, "response"); +const QName QN_SASL_ABORT(true, NS_SASL, "abort"); +const QName QN_SASL_SUCCESS(true, NS_SASL, "success"); +const QName QN_SASL_FAILURE(true, NS_SASL, "failure"); +const QName QN_SASL_ABORTED(true, NS_SASL, "aborted"); +const QName QN_SASL_INCORRECT_ENCODING(true, NS_SASL, "incorrect-encoding"); +const QName QN_SASL_INVALID_AUTHZID(true, NS_SASL, "invalid-authzid"); +const QName QN_SASL_INVALID_MECHANISM(true, NS_SASL, "invalid-mechanism"); +const QName QN_SASL_MECHANISM_TOO_WEAK(true, NS_SASL, "mechanism-too-weak"); +const QName QN_SASL_NOT_AUTHORIZED(true, NS_SASL, "not-authorized"); +const QName QN_SASL_TEMPORARY_AUTH_FAILURE(true, NS_SASL, "temporary-auth-failure"); + +const QName QN_DIALBACK_RESULT(true, NS_DIALBACK, "result"); +const QName QN_DIALBACK_VERIFY(true, NS_DIALBACK, "verify"); + +const QName QN_STANZA_BAD_REQUEST(true, NS_STANZA, "bad-request"); +const QName QN_STANZA_CONFLICT(true, NS_STANZA, "conflict"); +const QName QN_STANZA_FEATURE_NOT_IMPLEMENTED(true, NS_STANZA, "feature-not-implemented"); +const QName QN_STANZA_FORBIDDEN(true, NS_STANZA, "forbidden"); +const QName QN_STANZA_GONE(true, NS_STANZA, "gone"); +const QName QN_STANZA_INTERNAL_SERVER_ERROR(true, NS_STANZA, "internal-server-error"); +const QName QN_STANZA_ITEM_NOT_FOUND(true, NS_STANZA, "item-not-found"); +const QName QN_STANZA_JID_MALFORMED(true, NS_STANZA, "jid-malformed"); +const QName QN_STANZA_NOT_ACCEPTABLE(true, NS_STANZA, "not-acceptable"); +const QName QN_STANZA_NOT_ALLOWED(true, NS_STANZA, "not-allowed"); +const QName QN_STANZA_PAYMENT_REQUIRED(true, NS_STANZA, "payment-required"); +const QName QN_STANZA_RECIPIENT_UNAVAILABLE(true, NS_STANZA, "recipient-unavailable"); +const QName QN_STANZA_REDIRECT(true, NS_STANZA, "redirect"); +const QName QN_STANZA_REGISTRATION_REQUIRED(true, NS_STANZA, "registration-required"); +const QName QN_STANZA_REMOTE_SERVER_NOT_FOUND(true, NS_STANZA, "remote-server-not-found"); +const QName QN_STANZA_REMOTE_SERVER_TIMEOUT(true, NS_STANZA, "remote-server-timeout"); +const QName QN_STANZA_RESOURCE_CONSTRAINT(true, NS_STANZA, "resource-constraint"); +const QName QN_STANZA_SERVICE_UNAVAILABLE(true, NS_STANZA, "service-unavailable"); +const QName QN_STANZA_SUBSCRIPTION_REQUIRED(true, NS_STANZA, "subscription-required"); +const QName QN_STANZA_UNDEFINED_CONDITION(true, NS_STANZA, "undefined-condition"); +const QName QN_STANZA_UNEXPECTED_REQUEST(true, NS_STANZA, "unexpected-request"); +const QName QN_STANZA_TEXT(true, NS_STANZA, "text"); + +const QName QN_BIND_BIND(true, NS_BIND, "bind"); +const QName QN_BIND_RESOURCE(true, NS_BIND, "resource"); +const QName QN_BIND_JID(true, NS_BIND, "jid"); + +const QName QN_MESSAGE(true, NS_CLIENT, "message"); +const QName QN_BODY(true, NS_CLIENT, "body"); +const QName QN_SUBJECT(true, NS_CLIENT, "subject"); +const QName QN_THREAD(true, NS_CLIENT, "thread"); +const QName QN_PRESENCE(true, NS_CLIENT, "presence"); +const QName QN_SHOW(true, NS_CLIENT, "show"); +const QName QN_STATUS(true, NS_CLIENT, "status"); +const QName QN_LANG(true, NS_CLIENT, "lang"); +const QName QN_PRIORITY(true, NS_CLIENT, "priority"); +const QName QN_IQ(true, NS_CLIENT, "iq"); +const QName QN_ERROR(true, NS_CLIENT, "error"); + +const QName QN_SERVER_MESSAGE(true, NS_SERVER, "message"); +const QName QN_SERVER_BODY(true, NS_SERVER, "body"); +const QName QN_SERVER_SUBJECT(true, NS_SERVER, "subject"); +const QName QN_SERVER_THREAD(true, NS_SERVER, "thread"); +const QName QN_SERVER_PRESENCE(true, NS_SERVER, "presence"); +const QName QN_SERVER_SHOW(true, NS_SERVER, "show"); +const QName QN_SERVER_STATUS(true, NS_SERVER, "status"); +const QName QN_SERVER_LANG(true, NS_SERVER, "lang"); +const QName QN_SERVER_PRIORITY(true, NS_SERVER, "priority"); +const QName QN_SERVER_IQ(true, NS_SERVER, "iq"); +const QName QN_SERVER_ERROR(true, NS_SERVER, "error"); + +const QName QN_SESSION_SESSION(true, NS_SESSION, "session"); + +const QName QN_PRIVACY_QUERY(true, NS_PRIVACY, "query"); +const QName QN_PRIVACY_ACTIVE(true, NS_PRIVACY, "active"); +const QName QN_PRIVACY_DEFAULT(true, NS_PRIVACY, "default"); +const QName QN_PRIVACY_LIST(true, NS_PRIVACY, "list"); +const QName QN_PRIVACY_ITEM(true, NS_PRIVACY, "item"); +const QName QN_PRIVACY_IQ(true, NS_PRIVACY, "iq"); +const QName QN_PRIVACY_MESSAGE(true, NS_PRIVACY, "message"); +const QName QN_PRIVACY_PRESENCE_IN(true, NS_PRIVACY, "presence-in"); +const QName QN_PRIVACY_PRESENCE_OUT(true, NS_PRIVACY, "presence-out"); + +const QName QN_ROSTER_QUERY(true, NS_ROSTER, "query"); +const QName QN_ROSTER_ITEM(true, NS_ROSTER, "item"); +const QName QN_ROSTER_GROUP(true, NS_ROSTER, "group"); + +const QName QN_VCARD_QUERY(true, NS_VCARD, "vCard"); +const QName QN_VCARD_FN(true, NS_VCARD, "FN"); + +const QName QN_XML_LANG(true, NS_XML, "lang"); + +const std::string STR_TYPE("type"); +const std::string STR_ID("id"); +const std::string STR_NAME("name"); +const std::string STR_JID("jid"); +const std::string STR_SUBSCRIPTION("subscription"); +const std::string STR_ASK("ask"); + +const QName QN_ENCODING(true, STR_EMPTY, STR_ENCODING); +const QName QN_VERSION(true, STR_EMPTY, STR_VERSION); +const QName QN_TO(true, STR_EMPTY, "to"); +const QName QN_FROM(true, STR_EMPTY, "from"); +const QName QN_TYPE(true, STR_EMPTY, "type"); +const QName QN_ID(true, STR_EMPTY, "id"); +const QName QN_CODE(true, STR_EMPTY, "code"); +const QName QN_NAME(true, STR_EMPTY, "name"); +const QName QN_VALUE(true, STR_EMPTY, "value"); +const QName QN_ACTION(true, STR_EMPTY, "action"); +const QName QN_ORDER(true, STR_EMPTY, "order"); +const QName QN_MECHANISM(true, STR_EMPTY, "mechanism"); +const QName QN_ASK(true, STR_EMPTY, "ask"); +const QName QN_JID(true, STR_EMPTY, "jid"); +const QName QN_SUBSCRIPTION(true, STR_EMPTY, "subscription"); +const QName QN_SOURCE(true, STR_EMPTY, "source"); + +const QName QN_XMLNS_CLIENT(true, NS_XMLNS, STR_CLIENT); +const QName QN_XMLNS_SERVER(true, NS_XMLNS, STR_SERVER); +const QName QN_XMLNS_STREAM(true, NS_XMLNS, STR_STREAM); + +// Presence +const std::string STR_SHOW_AWAY("away"); +const std::string STR_SHOW_CHAT("chat"); +const std::string STR_SHOW_DND("dnd"); +const std::string STR_SHOW_XA("xa"); + +// Subscription +const std::string STR_SUBSCRIBE("subscribe"); +const std::string STR_SUBSCRIBED("subscribed"); +const std::string STR_UNSUBSCRIBE("unsubscribe"); +const std::string STR_UNSUBSCRIBED("unsubscribed"); + + +// JEP 0030 +const QName QN_NODE(true, STR_EMPTY, "node"); +const QName QN_CATEGORY(true, STR_EMPTY, "category"); +const QName QN_VAR(true, STR_EMPTY, "var"); +const std::string NS_DISCO_INFO("http://jabber.org/protocol/disco#info"); +const std::string NS_DISCO_ITEMS("http://jabber.org/protocol/disco#items"); +const QName QN_DISCO_INFO_QUERY(true, NS_DISCO_INFO, "query"); +const QName QN_DISCO_IDENTITY(true, NS_DISCO_INFO, "identity"); +const QName QN_DISCO_FEATURE(true, NS_DISCO_INFO, "feature"); + +const QName QN_DISCO_ITEMS_QUERY(true, NS_DISCO_ITEMS, "query"); +const QName QN_DISCO_ITEM(true, NS_DISCO_ITEMS, "item"); + + +// JEP 0115 +const std::string NS_CAPS("http://jabber.org/protocol/caps"); +const QName QN_CAPS_C(true, NS_CAPS, "c"); +const QName QN_VER(true, STR_EMPTY, "ver"); +const QName QN_EXT(true, STR_EMPTY, "ext"); + +// JEP 0091 Delayed Delivery +const std::string kNSDelay("jabber:x:delay"); +const QName kQnDelayX(true, kNSDelay, "x"); +const QName kQnStamp(true, STR_EMPTY, "stamp"); + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.h new file mode 100644 index 00000000..b05af965 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.h @@ -0,0 +1,300 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _CRICKET_XMPP_XMPPLIB_BUZZ_CONSTANTS_H_ +#define _CRICKET_XMPP_XMPPLIB_BUZZ_CONSTANTS_H_ + +#include +#include "talk/xmllite/qname.h" +#include "talk/xmpp/jid.h" + + +#define NS_CLIENT Constants::ns_client() +#define NS_SERVER Constants::ns_server() +#define NS_STREAM Constants::ns_stream() +#define NS_XSTREAM Constants::ns_xstream() +#define NS_TLS Constants::ns_tls() +#define NS_SASL Constants::ns_sasl() +#define NS_BIND Constants::ns_bind() +#define NS_DIALBACK Constants::ns_dialback() +#define NS_SESSION Constants::ns_session() +#define NS_STANZA Constants::ns_stanza() +#define NS_PRIVACY Constants::ns_privacy() +#define NS_ROSTER Constants::ns_roster() +#define NS_VCARD Constants::ns_vcard() +#define STR_CLIENT Constants::str_client() +#define STR_SERVER Constants::str_server() +#define STR_STREAM Constants::str_stream() + +namespace buzz { + +extern const Jid JID_EMPTY; + +class Constants { + public: + static const std::string & ns_client(); + static const std::string & ns_server(); + static const std::string & ns_stream(); + static const std::string & ns_xstream(); + static const std::string & ns_tls(); + static const std::string & ns_sasl(); + static const std::string & ns_bind(); + static const std::string & ns_dialback(); + static const std::string & ns_session(); + static const std::string & ns_stanza(); + static const std::string & ns_privacy(); + static const std::string & ns_roster(); + static const std::string & ns_vcard(); + + static const std::string & str_client(); + static const std::string & str_server(); + static const std::string & str_stream(); +}; + +extern const std::string STR_GET; +extern const std::string STR_SET; +extern const std::string STR_RESULT; +extern const std::string STR_ERROR; + +extern const std::string STR_FROM; +extern const std::string STR_TO; +extern const std::string STR_BOTH; +extern const std::string STR_REMOVE; + +extern const std::string STR_MESSAGE; +extern const std::string STR_BODY; +extern const std::string STR_PRESENCE; +extern const std::string STR_STATUS; +extern const std::string STR_SHOW; +extern const std::string STR_PRIOIRTY; +extern const std::string STR_IQ; + +extern const std::string STR_TYPE; +extern const std::string STR_NAME; +extern const std::string STR_ID; +extern const std::string STR_JID; +extern const std::string STR_SUBSCRIPTION; +extern const std::string STR_ASK; +extern const std::string STR_X; +extern const std::string STR_GOOGLE_COM; +extern const std::string STR_GMAIL_COM; +extern const std::string STR_GOOGLEMAIL_COM; +extern const std::string STR_DEFAULT_DOMAIN; + +extern const std::string STR_UNAVAILABLE; +extern const std::string STR_INVISIBLE; + +extern const QName QN_STREAM_STREAM; +extern const QName QN_STREAM_FEATURES; +extern const QName QN_STREAM_ERROR; + +extern const QName QN_XSTREAM_BAD_FORMAT; +extern const QName QN_XSTREAM_BAD_NAMESPACE_PREFIX; +extern const QName QN_XSTREAM_CONFLICT; +extern const QName QN_XSTREAM_CONNECTION_TIMEOUT; +extern const QName QN_XSTREAM_HOST_GONE; +extern const QName QN_XSTREAM_HOST_UNKNOWN; +extern const QName QN_XSTREAM_IMPROPER_ADDRESSIING; +extern const QName QN_XSTREAM_INTERNAL_SERVER_ERROR; +extern const QName QN_XSTREAM_INVALID_FROM; +extern const QName QN_XSTREAM_INVALID_ID; +extern const QName QN_XSTREAM_INVALID_NAMESPACE; +extern const QName QN_XSTREAM_INVALID_XML; +extern const QName QN_XSTREAM_NOT_AUTHORIZED; +extern const QName QN_XSTREAM_POLICY_VIOLATION; +extern const QName QN_XSTREAM_REMOTE_CONNECTION_FAILED; +extern const QName QN_XSTREAM_RESOURCE_CONSTRAINT; +extern const QName QN_XSTREAM_RESTRICTED_XML; +extern const QName QN_XSTREAM_SEE_OTHER_HOST; +extern const QName QN_XSTREAM_SYSTEM_SHUTDOWN; +extern const QName QN_XSTREAM_UNDEFINED_CONDITION; +extern const QName QN_XSTREAM_UNSUPPORTED_ENCODING; +extern const QName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE; +extern const QName QN_XSTREAM_UNSUPPORTED_VERSION; +extern const QName QN_XSTREAM_XML_NOT_WELL_FORMED; +extern const QName QN_XSTREAM_TEXT; + +extern const QName QN_TLS_STARTTLS; +extern const QName QN_TLS_REQUIRED; +extern const QName QN_TLS_PROCEED; +extern const QName QN_TLS_FAILURE; + +extern const QName QN_SASL_MECHANISMS; +extern const QName QN_SASL_MECHANISM; +extern const QName QN_SASL_AUTH; +extern const QName QN_SASL_CHALLENGE; +extern const QName QN_SASL_RESPONSE; +extern const QName QN_SASL_ABORT; +extern const QName QN_SASL_SUCCESS; +extern const QName QN_SASL_FAILURE; +extern const QName QN_SASL_ABORTED; +extern const QName QN_SASL_INCORRECT_ENCODING; +extern const QName QN_SASL_INVALID_AUTHZID; +extern const QName QN_SASL_INVALID_MECHANISM; +extern const QName QN_SASL_MECHANISM_TOO_WEAK; +extern const QName QN_SASL_NOT_AUTHORIZED; +extern const QName QN_SASL_TEMPORARY_AUTH_FAILURE; + +extern const QName QN_DIALBACK_RESULT; +extern const QName QN_DIALBACK_VERIFY; + +extern const QName QN_STANZA_BAD_REQUEST; +extern const QName QN_STANZA_CONFLICT; +extern const QName QN_STANZA_FEATURE_NOT_IMPLEMENTED; +extern const QName QN_STANZA_FORBIDDEN; +extern const QName QN_STANZA_GONE; +extern const QName QN_STANZA_INTERNAL_SERVER_ERROR; +extern const QName QN_STANZA_ITEM_NOT_FOUND; +extern const QName QN_STANZA_JID_MALFORMED; +extern const QName QN_STANZA_NOT_ACCEPTABLE; +extern const QName QN_STANZA_NOT_ALLOWED; +extern const QName QN_STANZA_PAYMENT_REQUIRED; +extern const QName QN_STANZA_RECIPIENT_UNAVAILABLE; +extern const QName QN_STANZA_REDIRECT; +extern const QName QN_STANZA_REGISTRATION_REQUIRED; +extern const QName QN_STANZA_REMOTE_SERVER_NOT_FOUND; +extern const QName QN_STANZA_REMOTE_SERVER_TIMEOUT; +extern const QName QN_STANZA_RESOURCE_CONSTRAINT; +extern const QName QN_STANZA_SERVICE_UNAVAILABLE; +extern const QName QN_STANZA_SUBSCRIPTION_REQUIRED; +extern const QName QN_STANZA_UNDEFINED_CONDITION; +extern const QName QN_STANZA_UNEXPECTED_REQUEST; +extern const QName QN_STANZA_TEXT; + +extern const QName QN_BIND_BIND; +extern const QName QN_BIND_RESOURCE; +extern const QName QN_BIND_JID; + +extern const QName QN_MESSAGE; +extern const QName QN_BODY; +extern const QName QN_SUBJECT; +extern const QName QN_THREAD; +extern const QName QN_PRESENCE; +extern const QName QN_SHOW; +extern const QName QN_STATUS; +extern const QName QN_LANG; +extern const QName QN_PRIORITY; +extern const QName QN_IQ; +extern const QName QN_ERROR; + +extern const QName QN_SERVER_MESSAGE; +extern const QName QN_SERVER_BODY; +extern const QName QN_SERVER_SUBJECT; +extern const QName QN_SERVER_THREAD; +extern const QName QN_SERVER_PRESENCE; +extern const QName QN_SERVER_SHOW; +extern const QName QN_SERVER_STATUS; +extern const QName QN_SERVER_LANG; +extern const QName QN_SERVER_PRIORITY; +extern const QName QN_SERVER_IQ; +extern const QName QN_SERVER_ERROR; + +extern const QName QN_SESSION_SESSION; + +extern const QName QN_PRIVACY_QUERY; +extern const QName QN_PRIVACY_ACTIVE; +extern const QName QN_PRIVACY_DEFAULT; +extern const QName QN_PRIVACY_LIST; +extern const QName QN_PRIVACY_ITEM; +extern const QName QN_PRIVACY_IQ; +extern const QName QN_PRIVACY_MESSAGE; +extern const QName QN_PRIVACY_PRESENCE_IN; +extern const QName QN_PRIVACY_PRESENCE_OUT; + +extern const QName QN_ROSTER_QUERY; +extern const QName QN_ROSTER_ITEM; +extern const QName QN_ROSTER_GROUP; + +extern const QName QN_VCARD_QUERY; +extern const QName QN_VCARD_FN; + +extern const QName QN_XML_LANG; + +extern const QName QN_ENCODING; +extern const QName QN_VERSION; +extern const QName QN_TO; +extern const QName QN_FROM; +extern const QName QN_TYPE; +extern const QName QN_ID; +extern const QName QN_CODE; +extern const QName QN_NAME; +extern const QName QN_VALUE; +extern const QName QN_ACTION; +extern const QName QN_ORDER; +extern const QName QN_MECHANISM; +extern const QName QN_ASK; +extern const QName QN_JID; +extern const QName QN_SUBSCRIPTION; + + +extern const QName QN_XMLNS_CLIENT; +extern const QName QN_XMLNS_SERVER; +extern const QName QN_XMLNS_STREAM; + +// Presence +extern const std::string STR_SHOW_AWAY; +extern const std::string STR_SHOW_CHAT; +extern const std::string STR_SHOW_DND; +extern const std::string STR_SHOW_XA; + +// Subscription +extern const std::string STR_SUBSCRIBE; +extern const std::string STR_SUBSCRIBED; +extern const std::string STR_UNSUBSCRIBE; +extern const std::string STR_UNSUBSCRIBED; + + +// JEP 0030 +extern const QName QN_NODE; +extern const QName QN_CATEGORY; +extern const QName QN_VAR; +extern const std::string NS_DISCO_INFO; +extern const std::string NS_DISCO_ITEMS; + +extern const QName QN_DISCO_INFO_QUERY; +extern const QName QN_DISCO_IDENTITY; +extern const QName QN_DISCO_FEATURE; + +extern const QName QN_DISCO_ITEMS_QUERY; +extern const QName QN_DISCO_ITEM; + + +// JEP 0115 +extern const std::string NS_CAPS; +extern const QName QN_CAPS_C; +extern const QName QN_VER; +extern const QName QN_EXT; + + +// JEP 0091 Delayed Delivery +extern const std::string kNSDelay; +extern const QName kQnDelayX; +extern const QName kQnStamp; + +} + +#endif // _CRICKET_XMPP_XMPPLIB_BUZZ_CONSTANTS_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.cc new file mode 100644 index 00000000..b742e03a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.cc @@ -0,0 +1,477 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +extern "C" { +#include +} +#include +#include "talk/xmpp/jid.h" +#include "talk/xmpp/constants.h" +#include "talk/base/common.h" +#include + +#define new TRACK_NEW + +namespace buzz { + +static int AsciiToLower(int x) { + return (x <= 'Z' && x >= 'A') ? (x + ('a' - 'A')) : x; +} + +Jid::Jid() : data_(NULL) { +} + +Jid::Jid(bool is_special, const std::string & special) { + data_ = is_special ? new Data(special, STR_EMPTY, STR_EMPTY) : NULL; +} + +Jid::Jid(const std::string & jid_string) { + if (jid_string == STR_EMPTY) { + data_ = NULL; + return; + } + + // First find the slash and slice of that part + size_t slash = jid_string.find('/'); + std::string resource_name = (slash == std::string::npos ? STR_EMPTY : + jid_string.substr(slash + 1)); + + // Now look for the node + std::string node_name; + size_t at = jid_string.find('@'); + size_t domain_begin; + if (at < slash && at != std::string::npos) { + node_name = jid_string.substr(0, at); + domain_begin = at + 1; + } else { + domain_begin = 0; + } + + // Now take what is left as the domain + size_t domain_length = + ( slash == std::string::npos + ? jid_string.length() - domain_begin + : slash - domain_begin); + + // avoid allocating these constants repeatedly + std::string domain_name; + + if (domain_length == 9 && jid_string.find("gmail.com", domain_begin) == domain_begin) { + domain_name = STR_GMAIL_COM; + } + else if (domain_length == 14 && jid_string.find("googlemail.com", domain_begin) == domain_begin) { + domain_name = STR_GOOGLEMAIL_COM; + } + else if (domain_length == 10 && jid_string.find("google.com", domain_begin) == domain_begin) { + domain_name = STR_GOOGLE_COM; + } + else { + domain_name = jid_string.substr(domain_begin, domain_length); + } + + // If the domain is empty we have a non-valid jid and we should empty + // everything else out + if (domain_name.empty()) { + data_ = NULL; + return; + } + + bool valid_node; + std::string validated_node = prepNode(node_name, + node_name.begin(), node_name.end(), &valid_node); + bool valid_domain; + std::string validated_domain = prepDomain(domain_name, + domain_name.begin(), domain_name.end(), &valid_domain); + bool valid_resource; + std::string validated_resource = prepResource(resource_name, + resource_name.begin(), resource_name.end(), &valid_resource); + + if (!valid_node || !valid_domain || !valid_resource) { + data_ = NULL; + return; + } + + data_ = new Data(validated_node, validated_domain, validated_resource); +} + +Jid::Jid(const std::string & node_name, + const std::string & domain_name, + const std::string & resource_name) { + if (domain_name.empty()) { + data_ = NULL; + return; + } + + bool valid_node; + std::string validated_node = prepNode(node_name, + node_name.begin(), node_name.end(), &valid_node); + bool valid_domain; + std::string validated_domain = prepDomain(domain_name, + domain_name.begin(), domain_name.end(), &valid_domain); + bool valid_resource; + std::string validated_resource = prepResource(resource_name, + resource_name.begin(), resource_name.end(), &valid_resource); + + if (!valid_node || !valid_domain || !valid_resource) { + data_ = NULL; + return; + } + + data_ = new Data(validated_node, validated_domain, validated_resource); +} + +std::string Jid::Str() const { + if (!IsValid()) + return STR_EMPTY; + + std::string ret; + + if (!data_->node_name_.empty()) + ret = data_->node_name_ + "@"; + + ASSERT(data_->domain_name_ != STR_EMPTY); + ret += data_->domain_name_; + + if (!data_->resource_name_.empty()) + ret += "/" + data_->resource_name_; + + return ret; +} + +bool +Jid::IsValid() const { + return data_ != NULL && !data_->domain_name_.empty(); +} + +bool +Jid::IsBare() const { + return IsValid() && + data_->resource_name_.empty(); +} + +bool +Jid::IsFull() const { + return IsValid() && + !data_->resource_name_.empty(); +} + +Jid +Jid::BareJid() const { + if (!IsValid()) + return Jid(); + if (!IsFull()) + return *this; + return Jid(data_->node_name_, data_->domain_name_, STR_EMPTY); +} + +#if 0 +void +Jid::set_node(const std::string & node_name) { + data_->node_name_ = node_name; +} +void +Jid::set_domain(const std::string & domain_name) { + data_->domain_name_ = domain_name; +} +void +Jid::set_resource(const std::string & res_name) { + data_->resource_name_ = res_name; +} +#endif + +bool +Jid::BareEquals(const Jid & other) const { + return (other.data_ == data_ || + data_ != NULL && + other.data_ != NULL && + other.data_->node_name_ == data_->node_name_ && + other.data_->domain_name_ == data_->domain_name_); +} + +bool +Jid::operator==(const Jid & other) const { + return (other.data_ == data_ || + data_ != NULL && + other.data_ != NULL && + other.data_->node_name_ == data_->node_name_ && + other.data_->domain_name_ == data_->domain_name_ && + other.data_->resource_name_ == data_->resource_name_); +} + +int +Jid::Compare(const Jid & other) const { + if (other.data_ == data_) + return 0; + if (data_ == NULL) + return -1; + if (other.data_ == NULL) + return 1; + + int compare_result; + compare_result = data_->node_name_.compare(other.data_->node_name_); + if (0 != compare_result) + return compare_result; + compare_result = data_->domain_name_.compare(other.data_->domain_name_); + if (0 != compare_result) + return compare_result; + compare_result = data_->resource_name_.compare(other.data_->resource_name_); + return compare_result; +} + + +// --- JID parsing code: --- + +// Checks and normalizes the node part of a JID. +std::string +Jid::prepNode(const std::string str, std::string::const_iterator start, + std::string::const_iterator end, bool *valid) { + *valid = false; + std::string result; + + for (std::string::const_iterator i = start; i < end; i++) { + bool char_valid = true; + char ch = *i; + if (ch <= 0x7F) { + result += prepNodeAscii(ch, &char_valid); + } + else { + // TODO: implement the correct stringprep protocol for these + result += tolower(ch); + } + if (!char_valid) { + return STR_EMPTY; + } + } + + if (result.length() > 1023) { + return STR_EMPTY; + } + *valid = true; + return result; +} + + +// Returns the appropriate mapping for an ASCII character in a node. +char +Jid::prepNodeAscii(char ch, bool *valid) { + *valid = true; + switch (ch) { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + return (char)(ch + ('a' - 'A')); + + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: + case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: + case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: + case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case ' ': case '&': case '/': case ':': case '<': case '>': case '@': + case '\"': case '\'': + case 0x7F: + *valid = false; + return 0; + + default: + return ch; + } +} + + +// Checks and normalizes the resource part of a JID. +std::string +Jid::prepResource(const std::string str, std::string::const_iterator start, + std::string::const_iterator end, bool *valid) { + *valid = false; + std::string result; + + for (std::string::const_iterator i = start; i < end; i++) { + bool char_valid = true; + char ch = *i; + if (ch <= 0x7F) { + result += prepResourceAscii(ch, &char_valid); + } + else { + // TODO: implement the correct stringprep protocol for these + result += ch; + } + } + + if (result.length() > 1023) { + return STR_EMPTY; + } + *valid = true; + return result; +} + +// Returns the appropriate mapping for an ASCII character in a resource. +char +Jid::prepResourceAscii(char ch, bool *valid) { + *valid = true; + switch (ch) { + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: + case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: + case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: + case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case 0x7F: + *valid = false; + return 0; + + default: + return ch; + } +} + +// Checks and normalizes the domain part of a JID. +std::string +Jid::prepDomain(const std::string str, std::string::const_iterator start, + std::string::const_iterator end, bool *valid) { + *valid = false; + std::string result; + + // TODO: if the domain contains a ':', then we should parse it + // as an IPv6 address rather than giving an error about illegal domain. + prepDomain(str, start, end, &result, valid); + if (!*valid) { + return STR_EMPTY; + } + + if (result.length() > 1023) { + return STR_EMPTY; + } + *valid = true; + return result; +} + + +// Checks and normalizes an IDNA domain. +void +Jid::prepDomain(const std::string str, std::string::const_iterator start, + std::string::const_iterator end, std::string *buf, bool *valid) { + *valid = false; + std::string::const_iterator last = start; + for (std::string::const_iterator i = start; i < end; i++) { + bool label_valid = true; + char ch = *i; + switch (ch) { + case 0x002E: +#if 0 // FIX: This isn't UTF-8-aware. + case 0x3002: + case 0xFF0E: + case 0xFF61: +#endif + prepDomainLabel(str, last, i, buf, &label_valid); + *buf += '.'; + last = i + 1; + break; + } + if (!label_valid) { + return; + } + } + prepDomainLabel(str, last, end, buf, valid); +} + +// Checks and normalizes a domain label. +void +Jid::prepDomainLabel(const std::string str, std::string::const_iterator start, + std::string::const_iterator end, std::string *buf, bool *valid) { + *valid = false; + + int startLen = buf->length(); + for (std::string::const_iterator i = start; i < end; i++) { + bool char_valid = true; + char ch = *i; + if (ch <= 0x7F) { + *buf += prepDomainLabelAscii(ch, &char_valid); + } + else { + // TODO: implement ToASCII for these + *buf += ch; + } + if (!char_valid) { + return; + } + } + + int count = buf->length() - startLen; + if (count == 0) { + return; + } + else if (count > 63) { + return; + } + + // Is this check needed? See comment in prepDomainLabelAscii. + if ((*buf)[startLen] == '-') { + return; + } + if ((*buf)[buf->length() - 1] == '-') { + return; + } + *valid = true; +} + + +// Returns the appropriate mapping for an ASCII character in a domain label. +char +Jid::prepDomainLabelAscii(char ch, bool *valid) { + *valid = true; + // TODO: A literal reading of the spec seems to say that we do + // not need to check for these illegal characters (an "internationalized + // domain label" runs ToASCII with UseSTD3... set to false). But that + // can't be right. We should at least be checking that there are no '/' + // or '@' characters in the domain. Perhaps we should see what others + // do in this case. + + switch (ch) { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + return (char)(ch + ('a' - 'A')); + + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: + case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: + case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: + case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: + case 0x1E: case 0x1F: case 0x20: case 0x21: case 0x22: case 0x23: + case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: + case 0x2A: case 0x2B: case 0x2C: case 0x2E: case 0x2F: case 0x3A: + case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40: + case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60: + case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: + *valid = false; + return 0; + + default: + return ch; + } +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.h new file mode 100644 index 00000000..ae7944bf --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.h @@ -0,0 +1,144 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _jid_h_ +#define _jid_h_ + +#include +#include "talk/xmllite/xmlconstants.h" + +namespace buzz { + +//! The Jid class encapsulates and provides parsing help for Jids +//! A Jid consists of three parts. The node, the domain and the resource. +//! +//! node@domain/resource +//! +//! The node and resource are both optional. A valid jid is defined to have +//! a domain. A bare jid is defined to not have a resource and a full jid +//! *does* have a resource. +class Jid { +public: + explicit Jid(); + explicit Jid(const std::string & jid_string); + explicit Jid(const std::string & node_name, + const std::string & domain_name, + const std::string & resource_name); + explicit Jid(bool special, const std::string & special_string); + Jid(const Jid & jid) : data_(jid.data_) { + if (data_ != NULL) { + data_->AddRef(); + } + } + Jid & operator=(const Jid & jid) { + if (jid.data_ != NULL) { + jid.data_->AddRef(); + } + if (data_ != NULL) { + data_->Release(); + } + data_ = jid.data_; + return *this; + } + ~Jid() { + if (data_ != NULL) { + data_->Release(); + } + } + + + const std::string & node() const { return !data_ ? STR_EMPTY : data_->node_name_; } + // void set_node(const std::string & node_name); + const std::string & domain() const { return !data_ ? STR_EMPTY : data_->domain_name_; } + // void set_domain(const std::string & domain_name); + const std::string & resource() const { return !data_ ? STR_EMPTY : data_->resource_name_; } + // void set_resource(const std::string & res_name); + + std::string Str() const; + Jid BareJid() const; + + bool IsValid() const; + bool IsBare() const; + bool IsFull() const; + + bool BareEquals(const Jid & other) const; + + bool operator==(const Jid & other) const; + bool operator!=(const Jid & other) const { return !operator==(other); } + + bool operator<(const Jid & other) const { return Compare(other) < 0; }; + bool operator>(const Jid & other) const { return Compare(other) > 0; }; + + int Compare(const Jid & other) const; + + +private: + + static std::string prepNode(const std::string str, + std::string::const_iterator start, std::string::const_iterator end, + bool *valid); + static char prepNodeAscii(char ch, bool *valid); + static std::string prepResource(const std::string str, + std::string::const_iterator start, std::string::const_iterator end, + bool *valid); + static char prepResourceAscii(char ch, bool *valid); + static std::string prepDomain(const std::string str, + std::string::const_iterator start, std::string::const_iterator end, + bool *valid); + static void prepDomain(const std::string str, + std::string::const_iterator start, std::string::const_iterator end, + std::string *buf, bool *valid); + static void prepDomainLabel(const std::string str, + std::string::const_iterator start, std::string::const_iterator end, + std::string *buf, bool *valid); + static char prepDomainLabelAscii(char ch, bool *valid); + + class Data { + public: + Data() : refcount_(1) {} + Data(const std::string & node, const std::string &domain, const std::string & resource) : + node_name_(node), + domain_name_(domain), + resource_name_(resource), + refcount_(1) {} + const std::string node_name_; + const std::string domain_name_; + const std::string resource_name_; + + void AddRef() { refcount_++; } + void Release() { if (!--refcount_) delete this; } + private: + int refcount_; + }; + + Data * data_; +}; + +} + + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/plainsaslhandler.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/plainsaslhandler.h new file mode 100644 index 00000000..659820f5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/plainsaslhandler.h @@ -0,0 +1,80 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PLAINSASLHANDLER_H_ +#define _PLAINSASLHANDLER_H_ + +#include "talk/xmpp/saslhandler.h" +#include + +namespace buzz { + +class PlainSaslHandler : public SaslHandler { +public: + PlainSaslHandler(const Jid & jid, const XmppPassword & password) : + jid_(jid), password_(password) {} + + virtual ~PlainSaslHandler() {} + + // Should pick the best method according to this handler + // returns the empty string if none are suitable + virtual std::string ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted) { + + // Do not send @google.com passwords unencrypted + if (!encrypted && jid_.domain() == "google.com") { + return ""; + } + + std::vector::const_iterator it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN"); + if (it == mechanisms.end()) { + return ""; + } + else { + return "PLAIN"; + } + } + + // Creates a SaslMechanism for the given mechanism name (you own it + // once you get it). If not handled, return NULL. + virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) { + if (mechanism == "PLAIN") { + return new SaslPlainMechanism(jid_, password_); + } + return NULL; + } + +private: + Jid jid_; + XmppPassword password_; + +}; + + +} + +#endif + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/prexmppauth.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/prexmppauth.h new file mode 100644 index 00000000..8d2aa9d4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/prexmppauth.h @@ -0,0 +1,85 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PREXMPPAUTH_H_ +#define _PREXMPPAUTH_H_ + +#include "talk/base/sigslot.h" +#include "talk/xmpp/saslhandler.h" +#include "talk/xmpp/xmpppassword.h" + +namespace cricket { + class SocketAddress; +} + +namespace buzz { + +class Jid; +class SaslMechanism; + +class CaptchaChallenge { + public: + CaptchaChallenge() : captcha_needed_(false) {} + CaptchaChallenge(const std::string& token, const std::string& url) + : captcha_needed_(true), captcha_token_(token), captcha_image_url_(url) { + } + + bool captcha_needed() const { return captcha_needed_; } + const std::string& captcha_token() const { return captcha_token_; } + + // This url is relative to the gaia server. Once we have better tools + // for cracking URLs, we should probably make this a full URL + const std::string& captcha_image_url() const { return captcha_image_url_; } + + private: + bool captcha_needed_; + std::string captcha_token_; + std::string captcha_image_url_; +}; + +class PreXmppAuth : public SaslHandler { +public: + virtual ~PreXmppAuth() {} + + virtual void StartPreXmppAuth( + const Jid & jid, + const cricket::SocketAddress & server, + const XmppPassword & pass, + const std::string & auth_cookie) = 0; + + sigslot::signal0<> SignalAuthDone; + + virtual bool IsAuthDone() = 0; + virtual bool IsAuthorized() = 0; + virtual bool HadError() = 0; + virtual CaptchaChallenge GetCaptchaChallenge() = 0; + virtual std::string GetAuthCookie() = 0; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslcookiemechanism.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslcookiemechanism.h new file mode 100644 index 00000000..a6630d90 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslcookiemechanism.h @@ -0,0 +1,67 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SASLCOOKIEMECHANISM_H_ +#define _SASLCOOKIEMECHANISM_H_ + +#include "talk/xmpp/saslmechanism.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/constants.h" + +namespace buzz { + +class SaslCookieMechanism : public SaslMechanism { + +public: + SaslCookieMechanism(const std::string & mechanism, const std::string & username, const std::string & cookie) : + mechanism_(mechanism), username_(username), cookie_(cookie) {} + + virtual std::string GetMechanismName() { return mechanism_; } + + virtual XmlElement * StartSaslAuth() { + // send initial request + XmlElement * el = new XmlElement(QN_SASL_AUTH, true); + el->AddAttr(QN_MECHANISM, mechanism_); + + std::string credential; + credential.append("\0", 1); + credential.append(username_); + credential.append("\0", 1); + credential.append(cookie_); + el->AddText(Base64Encode(credential)); + return el; + } + +private: + std::string mechanism_; + std::string username_; + std::string cookie_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslhandler.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslhandler.h new file mode 100644 index 00000000..b57d3baf --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslhandler.h @@ -0,0 +1,59 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SASLHANDLER_H_ +#define _SASLHANDLER_H_ + +#include + +namespace buzz { + +class XmlElement; +class SaslMechanism; + +// Creates mechanisms to deal with a given mechanism +class SaslHandler { + +public: + + // Intended to be subclassed + virtual ~SaslHandler() {} + + // Should pick the best method according to this handler + // returns the empty string if none are suitable + virtual std::string ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted) = 0; + + // Creates a SaslMechanism for the given mechanism name (you own it + // once you get it). + // If not handled, return NULL. + virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) = 0; +}; + +} + +#endif + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.cc new file mode 100644 index 00000000..092df104 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.cc @@ -0,0 +1,68 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/base/base64.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/saslmechanism.h" + +namespace buzz { + +XmlElement * +SaslMechanism::StartSaslAuth() { + return new XmlElement(QN_SASL_AUTH, true); +} + +XmlElement * +SaslMechanism::HandleSaslChallenge(const XmlElement * challenge) { + return new XmlElement(QN_SASL_ABORT, true); +} + +void +SaslMechanism::HandleSaslSuccess(const XmlElement * success) { +} + +void +SaslMechanism::HandleSaslFailure(const XmlElement * failure) { +} + +std::string +SaslMechanism::Base64Encode(const std::string & plain) { + return Base64::encode(plain); +} + +std::string +SaslMechanism::Base64Decode(const std::string & encoded) { + return Base64::decode(encoded); +} + +std::string +SaslMechanism::Base64EncodeFromArray(const char * plain, size_t length) { + return Base64::encodeFromArray(plain, length); +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.h new file mode 100644 index 00000000..f2e5adce --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.h @@ -0,0 +1,74 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SASLMECHANISM_H_ +#define _SASLMECHANISM_H_ + +#include + +namespace buzz { + +class XmlElement; + + +// Defines a mechnanism to do SASL authentication. +// Subclass instances should have a self-contained way to present +// credentials. +class SaslMechanism { + +public: + + // Intended to be subclassed + virtual ~SaslMechanism() {} + + // Should return the name of the SASL mechanism, e.g., "PLAIN" + virtual std::string GetMechanismName() = 0; + + // Should generate the initial "auth" request. Default is just . + virtual XmlElement * StartSaslAuth(); + + // Should respond to a SASL "" request. Default is + // to abort (for mechanisms that do not do challenge-response) + virtual XmlElement * HandleSaslChallenge(const XmlElement * challenge); + + // Notification of a SASL "". Sometimes information + // is passed on success. + virtual void HandleSaslSuccess(const XmlElement * success); + + // Notification of a SASL "". Sometimes information + // for the user is passed on failure. + virtual void HandleSaslFailure(const XmlElement * failure); + +protected: + static std::string Base64Encode(const std::string & plain); + static std::string Base64Decode(const std::string & encoded); + static std::string Base64EncodeFromArray(const char * plain, size_t length); +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslplainmechanism.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslplainmechanism.h new file mode 100644 index 00000000..7e0b0562 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslplainmechanism.h @@ -0,0 +1,65 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SASLPLAINMECHANISM_H_ +#define _SASLPLAINMECHANISM_H_ + +#include "talk/xmpp/saslmechanism.h" +#include "talk/xmpp/xmpppassword.h" + +namespace buzz { + +class SaslPlainMechanism : public SaslMechanism { + +public: + SaslPlainMechanism(const buzz::Jid user_jid, const XmppPassword & password) : + user_jid_(user_jid), password_(password) {} + + virtual std::string GetMechanismName() { return "PLAIN"; } + + virtual XmlElement * StartSaslAuth() { + // send initial request + XmlElement * el = new XmlElement(QN_SASL_AUTH, true); + el->AddAttr(QN_MECHANISM, "PLAIN"); + + FormatXmppPassword credential; + credential.Append("\0", 1); + credential.Append(user_jid_.Str()); + credential.Append("\0", 1); + credential.Append(&password_); + el->AddText(Base64EncodeFromArray(credential.GetData(), credential.GetLength())); + return el; + } + +private: + Jid user_jid_; + XmppPassword password_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.cc new file mode 100644 index 00000000..959b6f88 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.cc @@ -0,0 +1,372 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "xmppclient.h" +#include "xmpptask.h" +#include "talk/xmpp/constants.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/saslplainmechanism.h" +#include "talk/xmpp/prexmppauth.h" +#include "talk/base/scoped_ptr.h" +#include "talk/xmpp/plainsaslhandler.h" + +namespace buzz { + +Task * +XmppClient::GetParent(int code) { + if (code == XMPP_CLIENT_TASK_CODE) + return this; + else + return Task::GetParent(code); +} + +class XmppClient::Private : + public sigslot::has_slots<>, + public XmppSessionHandler, + public XmppOutputHandler { +public: + + Private(XmppClient * client) : + client_(client), + socket_(NULL), + engine_(NULL), + proxy_port_(0), + pre_engine_error_(XmppEngine::ERROR_NONE), + signal_closed_(false) {} + + // the owner + XmppClient * const client_; + + // the two main objects + scoped_ptr socket_; + scoped_ptr engine_; + scoped_ptr pre_auth_; + XmppPassword pass_; + std::string auth_cookie_; + cricket::SocketAddress server_; + std::string proxy_host_; + int proxy_port_; + XmppEngine::Error pre_engine_error_; + CaptchaChallenge captcha_challenge_; + bool signal_closed_; + + // implementations of interfaces + void OnStateChange(int state); + void WriteOutput(const char * bytes, size_t len); + void StartTls(const std::string & domainname); + void CloseConnection(); + + // slots for socket signals + void OnSocketConnected(); + void OnSocketRead(); + void OnSocketClosed(); +}; + +XmppReturnStatus +XmppClient::Connect(const XmppClientSettings & settings, AsyncSocket * socket, PreXmppAuth * pre_auth) { + if (socket == NULL) + return XMPP_RETURN_BADARGUMENT; + if (d_->socket_.get() != NULL) + return XMPP_RETURN_BADSTATE; + + d_->socket_.reset(socket); + + d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected); + d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead); + d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed); + + d_->engine_.reset(XmppEngine::Create()); + d_->engine_->SetSessionHandler(d_.get()); + d_->engine_->SetOutputHandler(d_.get()); + d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY)); + if (!settings.resource().empty()) { + d_->engine_->SetRequestedResource(settings.resource()); + } + d_->engine_->SetUseTls(settings.use_tls()); + + + d_->pass_ = settings.pass(); + d_->auth_cookie_ = settings.auth_cookie(); + d_->server_ = settings.server(); + d_->proxy_host_ = settings.proxy_host(); + d_->proxy_port_ = settings.proxy_port(); + d_->pre_auth_.reset(pre_auth); + + return XMPP_RETURN_OK; +} + +XmppEngine::State +XmppClient::GetState() { + if (d_->engine_.get() == NULL) + return XmppEngine::STATE_NONE; + return d_->engine_->GetState(); +} + +XmppEngine::Error +XmppClient::GetError() { + if (d_->engine_.get() == NULL) + return XmppEngine::ERROR_NONE; + if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) + return d_->pre_engine_error_; + return d_->engine_->GetError(); +} + +CaptchaChallenge XmppClient::GetCaptchaChallenge() { + if (d_->engine_.get() == NULL) + return CaptchaChallenge(); + return d_->captcha_challenge_; +} + +std::string +XmppClient::GetAuthCookie() { + if (d_->engine_.get() == NULL) + return ""; + return d_->auth_cookie_; +} + +static void +ForgetPassword(std::string & to_erase) { + size_t len = to_erase.size(); + for (size_t i = 0; i < len; i++) { + // get rid of characters + to_erase[i] = 'x'; + } + // get rid of length + to_erase.erase(); +} + +int +XmppClient::ProcessStart() { + if (d_->pre_auth_.get()) { + d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone); + d_->pre_auth_->StartPreXmppAuth( + d_->engine_->GetUser(), d_->server_, d_->pass_, d_->auth_cookie_); + d_->pass_.Clear(); // done with this; + return STATE_PRE_XMPP_LOGIN; + } + else { + d_->engine_->SetSaslHandler(new PlainSaslHandler( + d_->engine_->GetUser(), d_->pass_)); + d_->pass_.Clear(); // done with this; + return STATE_START_XMPP_LOGIN; + } +} + +void +XmppClient::OnAuthDone() { + Wake(); +} + +int +XmppClient::ProcessCookieLogin() { + // Don't know how this could happen, but crash reports show it as NULL + if (!d_->pre_auth_.get()) { + d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; + EnsureClosed(); + return STATE_ERROR; + } + + // Wait until pre authentication is done is done + if (!d_->pre_auth_->IsAuthDone()) + return STATE_BLOCKED; + + if (!d_->pre_auth_->IsAuthorized()) { + // maybe split out a case when gaia is down? + if (d_->pre_auth_->HadError()) { + d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; + } + else { + d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED; + d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge(); + } + d_->pre_auth_.reset(NULL); // done with this + EnsureClosed(); + return STATE_ERROR; + } + + // Save auth cookie as a result + d_->auth_cookie_ = d_->pre_auth_->GetAuthCookie(); + + // transfer ownership of pre_auth_ to engine + d_->engine_->SetSaslHandler(d_->pre_auth_.release()); + + return STATE_START_XMPP_LOGIN; +} + +int +XmppClient::ProcessStartXmppLogin() { + // Done with pre-connect tasks - connect! + if (!d_->socket_->Connect(d_->server_)) { + EnsureClosed(); + return STATE_ERROR; + } + + return STATE_RESPONSE; +} + +int +XmppClient::ProcessResponse() { + // Hang around while we are connected. + if (!delivering_signal_ && (d_->engine_.get() == NULL || + d_->engine_->GetState() == XmppEngine::STATE_CLOSED)) + return STATE_DONE; + return STATE_BLOCKED; +} + +XmppReturnStatus +XmppClient::Disconnect() { + if (d_->socket_.get() == NULL) + return XMPP_RETURN_BADSTATE; + d_->engine_->Disconnect(); + return XMPP_RETURN_OK; +} + +XmppClient::XmppClient(Task * parent) : Task(parent), + delivering_signal_(false) { + d_.reset(new Private(this)); +} + +XmppClient::~XmppClient() {} + +const Jid & +XmppClient::jid() { + return d_->engine_->FullJid(); +} + + +std::string +XmppClient::NextId() { + return d_->engine_->NextId(); +} + +XmppReturnStatus +XmppClient::SendStanza(const XmlElement * stanza) { + return d_->engine_->SendStanza(stanza); +} + +XmppReturnStatus +XmppClient::SendStanzaError(const XmlElement * old_stanza, XmppStanzaError xse, const std::string & message) { + return d_->engine_->SendStanzaError(old_stanza, xse, message); +} + +XmppReturnStatus +XmppClient::SendRaw(const std::string & text) { + return d_->engine_->SendRaw(text); +} + +XmppEngine* +XmppClient::engine() { + return d_->engine_.get(); +} + +void +XmppClient::Private::OnSocketConnected() { + engine_->Connect(); +} + +void +XmppClient::Private::OnSocketRead() { + char bytes[4096]; + size_t bytes_read; + for (;;) { + if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) { + // TODO: deal with error information + return; + } + + if (bytes_read == 0) + return; + +//#ifdef _DEBUG + client_->SignalLogInput(bytes, bytes_read); +//#endif + + engine_->HandleInput(bytes, bytes_read); + } +} + +void +XmppClient::Private::OnSocketClosed() { + engine_->ConnectionClosed(); +} + +void +XmppClient::Private::OnStateChange(int state) { + if (state == XmppEngine::STATE_CLOSED) { + client_->EnsureClosed(); + } + else { + client_->SignalStateChange((XmppEngine::State)state); + } + client_->Wake(); +} + +void +XmppClient::Private::WriteOutput(const char * bytes, size_t len) { + +//#ifdef _DEBUG + client_->SignalLogOutput(bytes, len); +//#endif + + socket_->Write(bytes, len); + // TODO: deal with error information +} + +void +XmppClient::Private::StartTls(const std::string & domain) { +#if defined(FEATURE_ENABLE_SSL) + socket_->StartTls(domain); +#endif +} + +void +XmppClient::Private::CloseConnection() { + socket_->Close(); +} + +void +XmppClient::AddXmppTask(XmppTask * task, XmppEngine::HandlerLevel level) { + d_->engine_->AddStanzaHandler(task, level); +} + +void +XmppClient::RemoveXmppTask(XmppTask * task) { + d_->engine_->RemoveStanzaHandler(task); +} + +void +XmppClient::EnsureClosed() { + if (!d_->signal_closed_) { + d_->signal_closed_ = true; + delivering_signal_ = true; + SignalStateChange(XmppEngine::STATE_CLOSED); + delivering_signal_ = false; + } +} + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.h new file mode 100644 index 00000000..f8b4798c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.h @@ -0,0 +1,157 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _XMPPCLIENT_H_ +#define _XMPPCLIENT_H_ + +#include +#include "talk/base/basicdefs.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/asyncsocket.h" +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/base/task.h" + +namespace buzz { + +class XmppTask; +class PreXmppAuth; +class CaptchaChallenge; + +// Just some non-colliding number. Could have picked "1". +#define XMPP_CLIENT_TASK_CODE 0x366c1e47 + +///////////////////////////////////////////////////////////////////// +// +// XMPPCLIENT +// +///////////////////////////////////////////////////////////////////// +// +// See Task first. XmppClient is a parent task for XmppTasks. +// +// XmppClient is a task which is designed to be the parent task for +// all tasks that depend on a single Xmpp connection. If you want to, +// for example, listen for subscription requests forever, then your +// listener should be a task that is a child of the XmppClient that owns +// the connection you are using. XmppClient has all the utility methods +// that basically drill through to XmppEngine. +// +// XmppClient is just a wrapper for XmppEngine, and if I were writing it +// all over again, I would make XmppClient == XmppEngine. Why? +// XmppEngine needs tasks too, for example it has an XmppLoginTask which +// should just be the same kind of Task instead of an XmppEngine specific +// thing. It would help do certain things like GAIA auth cleaner. +// +///////////////////////////////////////////////////////////////////// + +class XmppClient : public Task, public sigslot::has_slots<> +{ +public: + XmppClient(Task * parent); + ~XmppClient(); + + XmppReturnStatus Connect(const XmppClientSettings & settings, + AsyncSocket * socket, + PreXmppAuth * preauth); + + virtual Task * GetParent(int code); + virtual int ProcessStart(); + virtual int ProcessResponse(); + XmppReturnStatus Disconnect(); + const Jid & jid(); + + sigslot::signal1 SignalStateChange; + XmppEngine::State GetState(); + XmppEngine::Error GetError(); + + // When there is an authentication error, we may have captcha info + // that the user can use to unlock their account + CaptchaChallenge GetCaptchaChallenge(); + + // When authentication is successful, this returns the service cookie + // (if we used GAIA authentication) + std::string GetAuthCookie(); + + std::string NextId(); + XmppReturnStatus SendStanza(const XmlElement *stanza); + XmppReturnStatus SendRaw(const std::string & text); + XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal, + XmppStanzaError code, + const std::string & text); + + XmppEngine* engine(); + + sigslot::signal2 SignalLogInput; + sigslot::signal2 SignalLogOutput; + +private: + friend class XmppTask; + + void OnAuthDone(); + + // managed tasks and dispatching + void AddXmppTask(XmppTask *, XmppEngine::HandlerLevel); + void RemoveXmppTask(XmppTask *); + + sigslot::signal0<> SignalDisconnected; + +private: + // Internal state management + enum { + STATE_PRE_XMPP_LOGIN = STATE_NEXT, + STATE_START_XMPP_LOGIN = STATE_NEXT + 1, + }; + int Process(int state) { + switch (state) { + case STATE_PRE_XMPP_LOGIN: return ProcessCookieLogin(); + case STATE_START_XMPP_LOGIN: return ProcessStartXmppLogin(); + default: return Task::Process(state); + } + } + + std::string GetStateName(int state) const { + switch (state) { + case STATE_PRE_XMPP_LOGIN: return "PRE_XMPP_LOGIN"; + case STATE_START_XMPP_LOGIN: return "START_XMPP_LOGIN"; + default: return Task::GetStateName(state); + } + } + + int ProcessCookieLogin(); + int ProcessStartXmppLogin(); + void EnsureClosed(); + + class Private; + friend class Private; + scoped_ptr d_; + + bool delivering_signal_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclientsettings.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclientsettings.h new file mode 100644 index 00000000..9795682b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclientsettings.h @@ -0,0 +1,94 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _XMPPCLIENTSETTINGS_H_ +#define _XMPPCLIENTSETTINGS_H_ + +#include "talk/p2p/base/port.h" +#include "talk/xmpp/xmpppassword.h" + +namespace buzz { + +class XmppClientSettings { +public: + XmppClientSettings() : + use_tls_(false), use_cookie_auth_(false), protocol_(cricket::PROTO_TCP), + proxy_(cricket::PROXY_NONE), proxy_port_(80), use_proxy_auth_(false) {} + + void set_user(const std::string & user) { user_ = user; } + void set_host(const std::string & host) { host_ = host; } + void set_pass(const XmppPassword & pass) { pass_ = pass; } + void set_auth_cookie(const std::string & cookie) { auth_cookie_ = cookie; } + void set_resource(const std::string & resource) { resource_ = resource; } + void set_use_tls(bool use_tls) { use_tls_ = use_tls; } + void set_server(const cricket::SocketAddress & server) { + server_ = server; + } + void set_protocol(cricket::ProtocolType protocol) { protocol_ = protocol; } + void set_proxy(cricket::ProxyType f) { proxy_ = f; } + void set_proxy_host(const std::string & host) { proxy_host_ = host; } + void set_proxy_port(int port) { proxy_port_ = port; }; + void set_use_proxy_auth(bool f) { use_proxy_auth_ = f; } + void set_proxy_user(const std::string & user) { proxy_user_ = user; } + void set_proxy_pass(const XmppPassword & pass) { proxy_pass_ = pass; } + + const std::string & user() const { return user_; } + const std::string & host() const { return host_; } + const XmppPassword & pass() const { return pass_; } + const std::string & auth_cookie() const { return auth_cookie_; } + const std::string & resource() const { return resource_; } + bool use_tls() const { return use_tls_; } + const cricket::SocketAddress & server() const { return server_; } + cricket::ProtocolType protocol() const { return protocol_; } + cricket::ProxyType proxy() const { return proxy_; } + const std::string & proxy_host() const { return proxy_host_; } + int proxy_port() const { return proxy_port_; } + bool use_proxy_auth() const { return use_proxy_auth_; } + const std::string & proxy_user() const { return proxy_user_; } + const XmppPassword & proxy_pass() const { return proxy_pass_; } + +private: + std::string user_; + std::string host_; + XmppPassword pass_; + std::string auth_cookie_; + std::string resource_; + bool use_tls_; + bool use_cookie_auth_; + cricket::SocketAddress server_; + cricket::ProtocolType protocol_; + cricket::ProxyType proxy_; + std::string proxy_host_; + int proxy_port_; + bool use_proxy_auth_; + std::string proxy_user_; + XmppPassword proxy_pass_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengine.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengine.h new file mode 100644 index 00000000..ef8f2ea8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengine.h @@ -0,0 +1,332 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _xmppengine_h_ +#define _xmppengine_h_ + +// also part of the API +#include "talk/xmpp/jid.h" +#include "talk/xmllite/qname.h" +#include "talk/xmllite/xmlelement.h" + + +namespace buzz { + +class XmppEngine; +class SaslHandler; +typedef void * XmppIqCookie; + +//! XMPP stanza error codes. +//! Used in XmppEngine.SendStanzaError(). +enum XmppStanzaError { + XSE_BAD_REQUEST, + XSE_CONFLICT, + XSE_FEATURE_NOT_IMPLEMENTED, + XSE_FORBIDDEN, + XSE_GONE, + XSE_INTERNAL_SERVER_ERROR, + XSE_ITEM_NOT_FOUND, + XSE_JID_MALFORMED, + XSE_NOT_ACCEPTABLE, + XSE_NOT_ALLOWED, + XSE_PAYMENT_REQUIRED, + XSE_RECIPIENT_UNAVAILABLE, + XSE_REDIRECT, + XSE_REGISTRATION_REQUIRED, + XSE_SERVER_NOT_FOUND, + XSE_SERVER_TIMEOUT, + XSE_RESOURCE_CONSTRAINT, + XSE_SERVICE_UNAVAILABLE, + XSE_SUBSCRIPTION_REQUIRED, + XSE_UNDEFINED_CONDITION, + XSE_UNEXPECTED_REQUEST, +}; + +// XmppReturnStatus +// This is used by API functions to synchronously return status. +enum XmppReturnStatus { + XMPP_RETURN_OK, + XMPP_RETURN_BADARGUMENT, + XMPP_RETURN_BADSTATE, + XMPP_RETURN_PENDING, + XMPP_RETURN_UNEXPECTED, + XMPP_RETURN_NOTYETIMPLEMENTED, +}; + +//! Callback for socket output for an XmppEngine connection. +//! Register via XmppEngine.SetOutputHandler. An XmppEngine +//! can call back to this handler while it is processing +//! Connect, SendStanza, SendIq, Disconnect, or HandleInput. +class XmppOutputHandler { +public: + + //! Deliver the specified bytes to the XMPP socket. + virtual void WriteOutput(const char * bytes, size_t len) = 0; + + //! Initiate TLS encryption on the socket. + //! The implementation must verify that the SSL + //! certificate matches the given domainname. + virtual void StartTls(const std::string & domainname) = 0; + + //! Called when engine wants the connecton closed. + virtual void CloseConnection() = 0; +}; + +//! Callback to deliver engine state change notifications +//! to the object managing the engine. +class XmppSessionHandler { +public: + //! Called when engine changes state. Argument is new state. + virtual void OnStateChange(int state) = 0; +}; + +//! Callback to deliver stanzas to an Xmpp application module. +//! Register via XmppEngine.SetDefaultSessionHandler or via +//! XmppEngine.AddSessionHAndler. +class XmppStanzaHandler { +public: + + //! Process the given stanza. + //! The handler must return true if it has handled the stanza. + //! A false return value causes the stanza to be passed on to + //! the next registered handler. + virtual bool HandleStanza(const XmlElement * stanza) = 0; +}; + +//! Callback to deliver iq responses (results and errors). +//! Register while sending an iq via XmppEngine.SendIq. +//! Iq responses are routed to matching XmppIqHandlers in preference +//! to sending to any registered SessionHandlers. +class XmppIqHandler { +public: + //! Called to handle the iq response. + //! The response may be either a result or an error, and will have + //! an 'id' that matches the request and a 'from' that matches the + //! 'to' of the request. Called no more than once; once this is + //! called, the handler is automatically unregistered. + virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) = 0; +}; + +//! The XMPP connection engine. +//! This engine implements the client side of the 'core' XMPP protocol. +//! To use it, register an XmppOutputHandler to handle socket output +//! and pass socket input to HandleInput. Then application code can +//! set up the connection with a user, password, and other settings, +//! and then call Connect() to initiate the connection. +//! An application can listen for events and receive stanzas by +//! registering an XmppStanzaHandler via AddStanzaHandler(). +class XmppEngine { +public: + static XmppEngine * Create(); + virtual ~XmppEngine() {} + + //! Error codes. See GetError(). + enum Error { + ERROR_NONE = 0, //!< No error + ERROR_XML, //!< Malformed XML or encoding error + ERROR_STREAM, //!< XMPP stream error - see GetStreamError() + ERROR_VERSION, //!< XMPP version error + ERROR_UNAUTHORIZED, //!< User is not authorized (rejected credentials) + ERROR_TLS, //!< TLS could not be negotiated + ERROR_AUTH, //!< Authentication could not be negotiated + ERROR_BIND, //!< Resource or session binding could not be negotiated + ERROR_CONNECTION_CLOSED,//!< Connection closed by output handler. + ERROR_DOCUMENT_CLOSED, //!< Closed by + ERROR_SOCKET, //!< Socket error + }; + + //! States. See GetState(). + enum State { + STATE_NONE = 0, //!< Nonexistent state + STATE_START, //!< Initial state. + STATE_OPENING, //!< Exchanging stream headers, authenticating and so on. + STATE_OPEN, //!< Authenticated and bound. + STATE_CLOSED, //!< Session closed, possibly due to error. + }; + + // SOCKET INPUT AND OUTPUT ------------------------------------------------ + + //! Registers the handler for socket output + virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh) = 0; + + //! Provides socket input to the engine + virtual XmppReturnStatus HandleInput(const char * bytes, size_t len) = 0; + + //! Advises the engine that the socket has closed + virtual XmppReturnStatus ConnectionClosed() = 0; + + // SESSION SETUP --------------------------------------------------------- + + //! Indicates the (bare) JID for the user to use. + virtual XmppReturnStatus SetUser(const Jid & jid)= 0; + + //! Get the login (bare) JID. + virtual const Jid & GetUser() = 0; + + //! Provides different methods for credentials for login. + //! Takes ownership of this object; deletes when login is done + virtual XmppReturnStatus SetSaslHandler(SaslHandler * h) = 0; + + //! Sets whether TLS will be used within the connection (default true). + virtual XmppReturnStatus SetUseTls(bool useTls) = 0; + + //! Sets an alternate domain from which we allows TLS certificates. + //! This is for use in the case where a we want to allow a proxy to + //! serve up its own certificate rather than one owned by the underlying + //! domain. + virtual XmppReturnStatus SetTlsServerDomain(const std::string & proxy_domain) = 0; + + //! Gets whether TLS will be used within the connection. + virtual bool GetUseTls() = 0; + + //! Sets the request resource name, if any (optional). + //! Note that the resource name may be overridden by the server; after + //! binding, the actual resource name is available as part of FullJid(). + virtual XmppReturnStatus SetRequestedResource(const std::string& resource) = 0; + + //! Gets the request resource name. + virtual const std::string & GetRequestedResource() = 0; + + // SESSION MANAGEMENT --------------------------------------------------- + + //! Set callback for state changes. + virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler) = 0; + + //! Initiates the XMPP connection. + //! After supplying connection settings, call this once to initiate, + //! (optionally) encrypt, authenticate, and bind the connection. + virtual XmppReturnStatus Connect() = 0; + + //! The current engine state. + virtual State GetState() = 0; + + //! Returns true if the connection is encrypted (under TLS) + virtual bool IsEncrypted() = 0; + + //! The error code. + //! Consult this after XmppOutputHandler.OnClose(). + virtual Error GetError() = 0; + + //! The stream:error stanza, when the error is XMPP_ERROR_STREAM. + //! Notice the stanza returned is owned by the XmppEngine and + //! is deleted when the engine is destroyed. + virtual const XmlElement * GetStreamError() = 0; + + //! Closes down the connection. + //! Sends CloseConnection to output, and disconnects and registered + //! session handlers. After Disconnect completes, it is guaranteed + //! that no further callbacks will be made. + virtual XmppReturnStatus Disconnect() = 0; + + // APPLICATION USE ------------------------------------------------------- + + enum HandlerLevel { + HL_NONE = 0, + HL_PEEK, //!< Sees messages before all other processing; cannot abort + HL_SINGLE, //!< Watches for a single message, e.g., by id and sender + HL_SENDER, //!< Watches for a type of message from a specific sender + HL_TYPE, //!< Watches a type of message, e.g., all groupchat msgs + HL_ALL, //!< Watches all messages - gets last shot + HL_COUNT, //!< Count of handler levels + }; + + //! Adds a listener for session events. + //! Stanza delivery is chained to session handlers; the first to + //! return 'true' is the last to get each stanza. + virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler, HandlerLevel level = HL_PEEK) = 0; + + //! Removes a listener for session events. + virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler) = 0; + + //! Sends a stanza to the server. + virtual XmppReturnStatus SendStanza(const XmlElement * pelStanza) = 0; + + //! Sends raw text to the server + virtual XmppReturnStatus SendRaw(const std::string & text) = 0; + + //! Sends an iq to the server, and registers a callback for the result. + //! Returns the cookie passed to the result handler. + virtual XmppReturnStatus SendIq(const XmlElement* pelStanza, + XmppIqHandler* iq_handler, + XmppIqCookie* cookie) = 0; + + //! Unregisters an iq callback handler given its cookie. + //! No callback will come to this handler after it's unregistered. + virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie, + XmppIqHandler** iq_handler) = 0; + + + //! Forms and sends an error in response to the given stanza. + //! Swaps to and from, sets type to "error", and adds error information + //! based on the passed code. Text is optional and may be STR_EMPTY. + virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal, + XmppStanzaError code, + const std::string & text) = 0; + + //! The fullly bound JID. + //! This JID is only valid after binding has succeeded. If the value + //! is JID_NULL, the binding has not succeeded. + virtual const Jid & FullJid() = 0; + + //! The next unused iq id for this connection. + //! Call this when building iq stanzas, to ensure that each iq + //! gets its own unique id. + virtual std::string NextId() = 0; + +}; + +} + + +// Move these to a better location + +#define XMPP_FAILED(x) \ + ( (x) == buzz::XMPP_RETURN_OK ? false : true) \ + + +#define XMPP_SUCCEEDED(x) \ + ( (x) == buzz::XMPP_RETURN_OK ? true : false) \ + +#define IFR(x) \ + do { \ + xmpp_status = (x); \ + if (XMPP_FAILED(xmpp_status)) { \ + return xmpp_status; \ + } \ + } while (false) \ + + +#define IFC(x) \ + do { \ + xmpp_status = (x); \ + if (XMPP_FAILED(xmpp_status)) { \ + goto Cleanup; \ + } \ + } while (false) \ + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.cc new file mode 100644 index 00000000..173d711b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.cc @@ -0,0 +1,480 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define TRACK_ARRAY_ALLOC_PROBLEM + +#include +#include +#include +#include "talk/xmllite/xmlelement.h" +#include "talk/base/common.h" +#include "talk/xmpp/xmppengineimpl.h" +#include "talk/xmpp/xmpplogintask.h" +#include "talk/xmpp/constants.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/xmpp/saslhandler.h" +// #include "buzz/saslmechanism.h" + +#define new TRACK_NEW + +namespace buzz { + +static const std::string XMPP_CLIENT_NAMESPACES[] = { + "stream", "http://etherx.jabber.org/streams", + "", "jabber:client", +}; + +static const size_t XMPP_CLIENT_NAMESPACES_LEN = 4; + +XmppEngine * XmppEngine::Create() { + return new XmppEngineImpl(); +} + + +XmppEngineImpl::XmppEngineImpl() : + stanzaParseHandler_(this), + stanzaParser_(&stanzaParseHandler_), + engine_entered_(0), + user_jid_(JID_EMPTY), + password_(), + requested_resource_(STR_EMPTY), + tls_needed_(true), + login_task_(new XmppLoginTask(this)), + next_id_(0), + bound_jid_(JID_EMPTY), + state_(STATE_START), + encrypted_(false), + error_code_(ERROR_NONE), + stream_error_(NULL), + raised_reset_(false), + output_handler_(NULL), + session_handler_(NULL), + iq_entries_(new IqEntryVector()), + output_(new std::stringstream()), + sasl_handler_(NULL) { + for (int i = 0; i < HL_COUNT; i+= 1) { + stanza_handlers_[i].reset(new StanzaHandlerVector()); + } +} + +XmppEngineImpl::~XmppEngineImpl() { + DeleteIqCookies(); +} + +XmppReturnStatus +XmppEngineImpl::SetOutputHandler(XmppOutputHandler* output_handler) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + output_handler_ = output_handler; + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::SetSessionHandler(XmppSessionHandler* session_handler) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + session_handler_ = session_handler; + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::HandleInput(const char * bytes, size_t len) { + if (state_ < STATE_OPENING || state_ > STATE_OPEN) + return XMPP_RETURN_BADSTATE; + + EnterExit ee(this); + + stanzaParser_.Parse(bytes, len, false); + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::ConnectionClosed() { + if (state_ != STATE_CLOSED) { + EnterExit ee(this); + // If told that connection closed and not already closed, + // then connection was unpexectedly dropped. + SignalError(ERROR_CONNECTION_CLOSED); + } + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::SetUseTls(bool useTls) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + tls_needed_ = useTls; + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::SetTlsServerDomain(const std::string & tls_server_domain) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + tls_server_domain_= tls_server_domain; + + return XMPP_RETURN_OK; +} + +bool +XmppEngineImpl::GetUseTls() { + return tls_needed_; +} + +XmppReturnStatus +XmppEngineImpl::SetUser(const Jid & jid) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + user_jid_ = jid; + + return XMPP_RETURN_OK; +} + +const Jid & +XmppEngineImpl::GetUser() { + return user_jid_; +} + +XmppReturnStatus +XmppEngineImpl::SetSaslHandler(SaslHandler * sasl_handler) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + sasl_handler_.reset(sasl_handler); + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::SetRequestedResource(const std::string & resource) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + requested_resource_ = resource; + + return XMPP_RETURN_OK; +} + +const std::string & +XmppEngineImpl::GetRequestedResource() { + return requested_resource_; +} + +XmppReturnStatus +XmppEngineImpl::AddStanzaHandler(XmppStanzaHandler * stanza_handler, + XmppEngine::HandlerLevel level) { + if (state_ == STATE_CLOSED) + return XMPP_RETURN_BADSTATE; + + stanza_handlers_[level]->push_back(stanza_handler); + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::RemoveStanzaHandler(XmppStanzaHandler * stanza_handler) { + + bool found = false; + + for (int level = 0; level < HL_COUNT; level += 1) { + StanzaHandlerVector::iterator new_end = + std::remove(stanza_handlers_[level]->begin(), + stanza_handlers_[level]->end(), + stanza_handler); + + if (new_end != stanza_handlers_[level]->end()) { + stanza_handlers_[level]->erase(new_end, stanza_handlers_[level]->end()); + found = true; + } + } + + if (!found) { + return XMPP_RETURN_BADARGUMENT; + } + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::Connect() { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + EnterExit ee(this); + + // get the login task started + state_ = STATE_OPENING; + if (login_task_.get()) { + login_task_->IncomingStanza(NULL, false); + if (login_task_->IsDone()) + login_task_.reset(); + } + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::SendStanza(const XmlElement * element) { + if (state_ == STATE_CLOSED) + return XMPP_RETURN_BADSTATE; + + EnterExit ee(this); + + if (login_task_.get()) { + // still handshaking - then outbound stanzas are queued + login_task_->OutgoingStanza(element); + } else { + // handshake done - send straight through + InternalSendStanza(element); + } + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::SendRaw(const std::string & text) { + if (state_ == STATE_CLOSED || login_task_.get()) + return XMPP_RETURN_BADSTATE; + + EnterExit ee(this); + + (*output_) << text; + + return XMPP_RETURN_OK; +} + +std::string +XmppEngineImpl::NextId() { + std::stringstream ss; + ss << next_id_++; + return ss.str(); +} + +XmppReturnStatus +XmppEngineImpl::Disconnect() { + + if (state_ != STATE_CLOSED) { + EnterExit ee(this); + if (state_ == STATE_OPEN) + *output_ << ""; + state_ = STATE_CLOSED; + } + + return XMPP_RETURN_OK; +} + +void +XmppEngineImpl::IncomingStart(const XmlElement * pelStart) { + if (HasError() || raised_reset_) + return; + + if (login_task_.get()) { + // start-stream should go to login task + login_task_->IncomingStanza(pelStart, true); + if (login_task_->IsDone()) + login_task_.reset(); + } + else { + // if not logging in, it's an error to see a start + SignalError(ERROR_XML); + } +} + +void +XmppEngineImpl::IncomingStanza(const XmlElement * stanza) { + if (HasError() || raised_reset_) + return; + + if (stanza->Name() == QN_STREAM_ERROR) { + // Explicit XMPP stream error + SignalStreamError(stanza); + } else if (login_task_.get()) { + // Handle login handshake + login_task_->IncomingStanza(stanza, false); + if (login_task_->IsDone()) + login_task_.reset(); + } else if (HandleIqResponse(stanza)) { + // iq is handled by above call + } else { + // give every "peek" handler a shot at all stanzas + for (size_t i = 0; i < stanza_handlers_[HL_PEEK]->size(); i += 1) { + (*stanza_handlers_[HL_PEEK])[i]->HandleStanza(stanza); + } + + // give other handlers a shot in precedence order, stopping after handled + for (int level = HL_SINGLE; level <= HL_ALL; level += 1) { + for (size_t i = 0; i < stanza_handlers_[level]->size(); i += 1) { + if ((*stanza_handlers_[level])[i]->HandleStanza(stanza)) + goto Handled; + } + } + + // If nobody wants to handle a stanza then send back an error. + // Only do this for IQ stanzas as messages should probably just be dropped + // and presence stanzas should certainly be dropped. + std::string type = stanza->Attr(QN_TYPE); + if (stanza->Name() == QN_IQ && + !(type == "error" || type == "result")) { + SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY); + } + } + Handled: + ; // handled - we're done +} + +void +XmppEngineImpl::IncomingEnd(bool isError) { + if (HasError() || raised_reset_) + return; + + SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED); +} + +void +XmppEngineImpl::InternalSendStart(const std::string & to) { + // send stream-beginning + // note, we put a \r\n at tne end fo the first line to cause non-XMPP + // line-oriented servers (e.g., Apache) to reveal themselves more quickly. + *output_ << "\r\n"; +} + +void +XmppEngineImpl::InternalSendStanza(const XmlElement * element) { + // It should really never be necessary to set a FROM attribute on a stanza. + // It is implied by the bind on the stream and if you get it wrong + // (by flipping from/to on a message?) the server will close the stream. + ASSERT(!element->HasAttr(QN_FROM)); + + // TODO: consider caching the XmlPrinter + XmlPrinter::PrintXml(output_.get(), element, + XMPP_CLIENT_NAMESPACES, XMPP_CLIENT_NAMESPACES_LEN); +} + +std::string +XmppEngineImpl::ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted) { + return sasl_handler_->ChooseBestSaslMechanism(mechanisms, encrypted); +} + +SaslMechanism * +XmppEngineImpl::GetSaslMechanism(const std::string & name) { + return sasl_handler_->CreateSaslMechanism(name); +} + +void +XmppEngineImpl::SignalBound(const Jid & fullJid) { + if (state_ == STATE_OPENING) { + bound_jid_ = fullJid; + state_ = STATE_OPEN; + } +} + +void +XmppEngineImpl::SignalStreamError(const XmlElement * pelStreamError) { + if (state_ != STATE_CLOSED) { + stream_error_.reset(new XmlElement(*pelStreamError)); + SignalError(ERROR_STREAM); + } +} + +void +XmppEngineImpl::SignalError(Error errorCode) { + if (state_ != STATE_CLOSED) { + error_code_ = errorCode; + state_ = STATE_CLOSED; + } +} + +bool +XmppEngineImpl::HasError() { + return error_code_ != ERROR_NONE; +} + +void +XmppEngineImpl::StartTls(const std::string & domain) { + if (output_handler_) { + output_handler_->StartTls( + tls_server_domain_.empty() ? domain : tls_server_domain_); + encrypted_ = true; + } +} + +XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine) + : engine_(engine), + state_(engine->state_), + error_(engine->error_code_) { + engine->engine_entered_ += 1; +} + +XmppEngineImpl::EnterExit::~EnterExit() { + XmppEngineImpl* engine = engine_; + + engine->engine_entered_ -= 1; + + bool closing = (engine->state_ != state_ && + engine->state_ == STATE_CLOSED); + bool flushing = closing || (engine->engine_entered_ == 0); + + if (engine->output_handler_ && flushing) { + std::string output = engine->output_->str(); + if (output.length() > 0) + engine->output_handler_->WriteOutput(output.c_str(), output.length()); + engine->output_->str(""); + + if (closing) { + engine->output_handler_->CloseConnection(); + engine->output_handler_ = 0; + } + } + + if (engine->engine_entered_) + return; + + if (engine->raised_reset_) { + engine->stanzaParser_.Reset(); + engine->raised_reset_ = false; + } + + if (engine->session_handler_) { + if (engine->state_ != state_) + engine->session_handler_->OnStateChange(engine->state_); + // Note: Handling of OnStateChange(CLOSED) should allow for the + // deletion of the engine, so no members should be accessed + // after this line. + } +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.h new file mode 100644 index 00000000..c36f168c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.h @@ -0,0 +1,262 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _xmppengineimpl_h_ +#define _xmppengineimpl_h_ + +#include +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/xmppstanzaparser.h" + +namespace buzz { + +class XmppLoginTask; +class XmppEngine; +class XmppIqEntry; +class SaslHandler; +class SaslMechanism; + + +//! The XMPP connection engine. +//! This engine implements the client side of the 'core' XMPP protocol. +//! To use it, register an XmppOutputHandler to handle socket output +//! and pass socket input to HandleInput. Then application code can +//! set up the connection with a user, password, and other settings, +//! and then call Connect() to initiate the connection. +//! An application can listen for events and receive stanzas by +//! registering an XmppStanzaHandler via AddStanzaHandler(). +class XmppEngineImpl : public XmppEngine { +public: + XmppEngineImpl(); + virtual ~XmppEngineImpl(); + + // SOCKET INPUT AND OUTPUT ------------------------------------------------ + + //! Registers the handler for socket output + virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh); + + //! Provides socket input to the engine + virtual XmppReturnStatus HandleInput(const char * bytes, size_t len); + + //! Advises the engine that the socket has closed + virtual XmppReturnStatus ConnectionClosed(); + + // SESSION SETUP --------------------------------------------------------- + + //! Indicates the (bare) JID for the user to use. + virtual XmppReturnStatus SetUser(const Jid & jid); + + //! Get the login (bare) JID. + virtual const Jid & GetUser(); + + //! Indicates the autentication to use. Takes ownership of the object. + virtual XmppReturnStatus SetSaslHandler(SaslHandler * sasl_handler); + + //! Sets whether TLS will be used within the connection (default true). + virtual XmppReturnStatus SetUseTls(bool useTls); + + //! Sets an alternate domain from which we allows TLS certificates. + //! This is for use in the case where a we want to allow a proxy to + //! serve up its own certificate rather than one owned by the underlying + //! domain. + virtual XmppReturnStatus SetTlsServerDomain(const std::string & proxy_domain); + + //! Gets whether TLS will be used within the connection. + virtual bool GetUseTls(); + + //! Sets the request resource name, if any (optional). + //! Note that the resource name may be overridden by the server; after + //! binding, the actual resource name is available as part of FullJid(). + virtual XmppReturnStatus SetRequestedResource(const std::string& resource); + + //! Gets the request resource name. + virtual const std::string & GetRequestedResource(); + + // SESSION MANAGEMENT --------------------------------------------------- + + //! Set callback for state changes. + virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler); + + //! Initiates the XMPP connection. + //! After supplying connection settings, call this once to initiate, + //! (optionally) encrypt, authenticate, and bind the connection. + virtual XmppReturnStatus Connect(); + + //! The current engine state. + virtual State GetState() { return state_; } + + //! Returns true if the connection is encrypted (under TLS) + virtual bool IsEncrypted() { return encrypted_; } + + //! The error code. + //! Consult this after XmppOutputHandler.OnClose(). + virtual Error GetError() { return error_code_; } + + //! The stream:error stanza, when the error is XMPP_ERROR_STREAM. + //! Notice the stanza returned is owned by the XmppEngine and + //! is deleted when the engine is destroyed. + virtual const XmlElement * GetStreamError() { return stream_error_.get(); } + + //! Closes down the connection. + //! Sends CloseConnection to output, and disconnects and registered + //! session handlers. After Disconnect completes, it is guaranteed + //! that no further callbacks will be made. + virtual XmppReturnStatus Disconnect(); + + // APPLICATION USE ------------------------------------------------------- + + //! Adds a listener for session events. + //! Stanza delivery is chained to session handlers; the first to + //! return 'true' is the last to get each stanza. + virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler, + XmppEngine::HandlerLevel level); + + //! Removes a listener for session events. + virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler); + + //! Sends a stanza to the server. + virtual XmppReturnStatus SendStanza(const XmlElement * pelStanza); + + //! Sends raw text to the server + virtual XmppReturnStatus SendRaw(const std::string & text); + + //! Sends an iq to the server, and registers a callback for the result. + //! Returns the cookie passed to the result handler. + virtual XmppReturnStatus SendIq(const XmlElement* pelStanza, + XmppIqHandler* iq_handler, + XmppIqCookie* cookie); + + //! Unregisters an iq callback handler given its cookie. + //! No callback will come to this handler after it's unregistered. + virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie, + XmppIqHandler** iq_handler); + + //! Forms and sends an error in response to the given stanza. + //! Swaps to and from, sets type to "error", and adds error information + //! based on the passed code. Text is optional and may be STR_EMPTY. + virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal, + XmppStanzaError code, + const std::string & text); + + //! The fullly bound JID. + //! This JID is only valid after binding has succeeded. If the value + //! is JID_NULL, the binding has not succeeded. + virtual const Jid & FullJid() { return bound_jid_; } + + //! The next unused iq id for this connection. + //! Call this when building iq stanzas, to ensure that each iq + //! gets its own unique id. + virtual std::string NextId(); + +private: + friend class XmppLoginTask; + friend class XmppIqEntry; + + void IncomingStanza(const XmlElement *pelStanza); + void IncomingStart(const XmlElement *pelStanza); + void IncomingEnd(bool isError); + + void InternalSendStart(const std::string & domainName); + void InternalSendStanza(const XmlElement * pelStanza); + std::string ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted); + SaslMechanism * GetSaslMechanism(const std::string & name); + void SignalBound(const Jid & fullJid); + void SignalStreamError(const XmlElement * pelStreamError); + void SignalError(Error errorCode); + bool HasError(); + void DeleteIqCookies(); + bool HandleIqResponse(const XmlElement * element); + void StartTls(const std::string & domain); + void RaiseReset() { raised_reset_ = true; } + + class StanzaParseHandler : public XmppStanzaParseHandler { + public: + StanzaParseHandler(XmppEngineImpl * outer) : outer_(outer) {} + virtual void StartStream(const XmlElement * pelStream) + { outer_->IncomingStart(pelStream); } + virtual void Stanza(const XmlElement * pelStanza) + { outer_->IncomingStanza(pelStanza); } + virtual void EndStream() + { outer_->IncomingEnd(false); } + virtual void XmlError() + { outer_->IncomingEnd(true); } + private: + XmppEngineImpl * const outer_; + }; + + class EnterExit { + public: + EnterExit(XmppEngineImpl* engine); + ~EnterExit(); + private: + XmppEngineImpl* engine_; + State state_; + Error error_; + + }; + + friend class StanzaParseHandler; + friend class EnterExit; + + StanzaParseHandler stanzaParseHandler_; + XmppStanzaParser stanzaParser_; + + + // state + int engine_entered_; + Jid user_jid_; + std::string password_; + std::string requested_resource_; + bool tls_needed_; + std::string tls_server_domain_; + scoped_ptr login_task_; + + int next_id_; + Jid bound_jid_; + State state_; + bool encrypted_; + Error error_code_; + scoped_ptr stream_error_; + bool raised_reset_; + XmppOutputHandler* output_handler_; + XmppSessionHandler* session_handler_; + + typedef STD_VECTOR(XmppStanzaHandler*) StanzaHandlerVector; + scoped_ptr stanza_handlers_[HL_COUNT]; + + typedef STD_VECTOR(XmppIqEntry*) IqEntryVector; + scoped_ptr iq_entries_; + + scoped_ptr sasl_handler_; + + scoped_ptr output_; +}; + +} + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl_iq.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl_iq.cc new file mode 100644 index 00000000..eb623ed9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl_iq.cc @@ -0,0 +1,279 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include "talk/base/common.h" +#include "talk/xmpp/xmppengineimpl.h" +#include "talk/xmpp/constants.h" + +#define new TRACK_NEW + +namespace buzz { + +class XmppIqEntry { + XmppIqEntry(const std::string & id, const std::string & to, + XmppEngine * pxce, XmppIqHandler * iq_handler) : + id_(id), + to_(to), + engine_(pxce), + iq_handler_(iq_handler) { + } + +private: + friend class XmppEngineImpl; + + const std::string id_; + const std::string to_; + XmppEngine * const engine_; + XmppIqHandler * const iq_handler_; +}; + + +XmppReturnStatus +XmppEngineImpl::SendIq(const XmlElement * element, XmppIqHandler * iq_handler, + XmppIqCookie* cookie) { + if (state_ == STATE_CLOSED) + return XMPP_RETURN_BADSTATE; + if (NULL == iq_handler) + return XMPP_RETURN_BADARGUMENT; + if (!element || element->Name() != QN_IQ) + return XMPP_RETURN_BADARGUMENT; + + const std::string& type = element->Attr(QN_TYPE); + if (type != "get" && type != "set") + return XMPP_RETURN_BADARGUMENT; + + if (!element->HasAttr(QN_ID)) + return XMPP_RETURN_BADARGUMENT; + const std::string& id = element->Attr(QN_ID); + + XmppIqEntry * iq_entry = new XmppIqEntry(id, + element->Attr(QN_TO), + this, iq_handler); + iq_entries_->push_back(iq_entry); + SendStanza(element); + + if (cookie) + *cookie = iq_entry; + + return XMPP_RETURN_OK; +} + + +XmppReturnStatus +XmppEngineImpl::RemoveIqHandler(XmppIqCookie cookie, + XmppIqHandler ** iq_handler) { + + std::vector >::iterator pos; + + pos = std::find(iq_entries_->begin(), + iq_entries_->end(), + reinterpret_cast(cookie)); + + if (pos == iq_entries_->end()) + return XMPP_RETURN_BADARGUMENT; + + XmppIqEntry* entry = *pos; + iq_entries_->erase(pos); + if (iq_handler) + *iq_handler = entry->iq_handler_; + delete entry; + + return XMPP_RETURN_OK; +} + +void +XmppEngineImpl::DeleteIqCookies() { + for (size_t i = 0; i < iq_entries_->size(); i += 1) { + XmppIqEntry * iq_entry_ = (*iq_entries_)[i]; + (*iq_entries_)[i] = NULL; + delete iq_entry_; + } + iq_entries_->clear(); +} + +static void +AecImpl(XmlElement * error_element, const QName & name, + const char * type, const char * code) { + error_element->AddElement(new XmlElement(QN_ERROR)); + error_element->AddAttr(QN_CODE, code, 1); + error_element->AddAttr(QN_TYPE, type, 1); + error_element->AddElement(new XmlElement(name, true), 1); +} + + +static void +AddErrorCode(XmlElement * error_element, XmppStanzaError code) { + switch (code) { + case XSE_BAD_REQUEST: + AecImpl(error_element, QN_STANZA_BAD_REQUEST, "modify", "400"); + break; + case XSE_CONFLICT: + AecImpl(error_element, QN_STANZA_CONFLICT, "cancel", "409"); + break; + case XSE_FEATURE_NOT_IMPLEMENTED: + AecImpl(error_element, QN_STANZA_FEATURE_NOT_IMPLEMENTED, + "cancel", "501"); + break; + case XSE_FORBIDDEN: + AecImpl(error_element, QN_STANZA_FORBIDDEN, "auth", "403"); + break; + case XSE_GONE: + AecImpl(error_element, QN_STANZA_GONE, "modify", "302"); + break; + case XSE_INTERNAL_SERVER_ERROR: + AecImpl(error_element, QN_STANZA_INTERNAL_SERVER_ERROR, "wait", "500"); + break; + case XSE_ITEM_NOT_FOUND: + AecImpl(error_element, QN_STANZA_ITEM_NOT_FOUND, "cancel", "404"); + break; + case XSE_JID_MALFORMED: + AecImpl(error_element, QN_STANZA_JID_MALFORMED, "modify", "400"); + break; + case XSE_NOT_ACCEPTABLE: + AecImpl(error_element, QN_STANZA_NOT_ACCEPTABLE, "cancel", "406"); + break; + case XSE_NOT_ALLOWED: + AecImpl(error_element, QN_STANZA_NOT_ALLOWED, "cancel", "405"); + break; + case XSE_PAYMENT_REQUIRED: + AecImpl(error_element, QN_STANZA_PAYMENT_REQUIRED, "auth", "402"); + break; + case XSE_RECIPIENT_UNAVAILABLE: + AecImpl(error_element, QN_STANZA_RECIPIENT_UNAVAILABLE, "wait", "404"); + break; + case XSE_REDIRECT: + AecImpl(error_element, QN_STANZA_REDIRECT, "modify", "302"); + break; + case XSE_REGISTRATION_REQUIRED: + AecImpl(error_element, QN_STANZA_REGISTRATION_REQUIRED, "auth", "407"); + break; + case XSE_SERVER_NOT_FOUND: + AecImpl(error_element, QN_STANZA_REMOTE_SERVER_NOT_FOUND, + "cancel", "404"); + break; + case XSE_SERVER_TIMEOUT: + AecImpl(error_element, QN_STANZA_REMOTE_SERVER_TIMEOUT, "wait", "502"); + break; + case XSE_RESOURCE_CONSTRAINT: + AecImpl(error_element, QN_STANZA_RESOURCE_CONSTRAINT, "wait", "500"); + break; + case XSE_SERVICE_UNAVAILABLE: + AecImpl(error_element, QN_STANZA_SERVICE_UNAVAILABLE, "cancel", "503"); + break; + case XSE_SUBSCRIPTION_REQUIRED: + AecImpl(error_element, QN_STANZA_SUBSCRIPTION_REQUIRED, "auth", "407"); + break; + case XSE_UNDEFINED_CONDITION: + AecImpl(error_element, QN_STANZA_UNDEFINED_CONDITION, "wait", "500"); + break; + case XSE_UNEXPECTED_REQUEST: + AecImpl(error_element, QN_STANZA_UNEXPECTED_REQUEST, "wait", "400"); + break; + } +} + + +XmppReturnStatus +XmppEngineImpl::SendStanzaError(const XmlElement * element_original, + XmppStanzaError code, + const std::string & text) { + + if (state_ == STATE_CLOSED) + return XMPP_RETURN_BADSTATE; + + XmlElement error_element(element_original->Name()); + error_element.AddAttr(QN_TYPE, "error"); + + // copy attrs, copy 'from' to 'to' and strip 'from' + for (const XmlAttr * attribute = element_original->FirstAttr(); + attribute; attribute = attribute->NextAttr()) { + QName name = attribute->Name(); + if (name == QN_TO) + continue; // no need to put a from attr. Server will stamp stanza + else if (name == QN_FROM) + name = QN_TO; + else if (name == QN_TYPE) + continue; + error_element.AddAttr(name, attribute->Value()); + } + + // copy children + for (const XmlChild * child = element_original->FirstChild(); + child; + child = child->NextChild()) { + if (child->IsText()) { + error_element.AddText(child->AsText()->Text()); + } else { + error_element.AddElement(new XmlElement(*(child->AsElement()))); + } + } + + // add error information + AddErrorCode(&error_element, code); + if (text != STR_EMPTY) { + XmlElement * text_element = new XmlElement(QN_STANZA_TEXT, true); + text_element->AddText(text); + error_element.AddElement(text_element); + } + + SendStanza(&error_element); + + return XMPP_RETURN_OK; +} + + +bool +XmppEngineImpl::HandleIqResponse(const XmlElement * element) { + if (iq_entries_->empty()) + return false; + if (element->Name() != QN_IQ) + return false; + std::string type = element->Attr(QN_TYPE); + if (type != "result" && type != "error") + return false; + if (!element->HasAttr(QN_ID)) + return false; + std::string id = element->Attr(QN_ID); + std::string from = element->Attr(QN_FROM); + + for (std::vector::iterator it = iq_entries_->begin(); + it != iq_entries_->end(); it += 1) { + XmppIqEntry * iq_entry = *it; + if (iq_entry->id_ == id && iq_entry->to_ == from) { + iq_entries_->erase(it); + iq_entry->iq_handler_->IqResponse(iq_entry, element); + delete iq_entry; + return true; + } + } + + return false; +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.cc new file mode 100644 index 00000000..470c2dc2 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.cc @@ -0,0 +1,357 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include "talk/xmpp/jid.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/base/common.h" +#include "talk/xmpp/xmppengineimpl.h" +#include "talk/xmpp/constants.h" +#include "talk/base/base64.h" +#include "talk/xmpp/xmpplogintask.h" +#include "talk/xmpp/saslmechanism.h" + +#define new TRACK_NEW + +namespace buzz { + +XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) : + pctx_(pctx), + authNeeded_(true), + state_(LOGINSTATE_INIT), + pelStanza_(NULL), + isStart_(false), + iqId_(STR_EMPTY), + pelFeatures_(NULL), + fullJid_(STR_EMPTY), + streamId_(STR_EMPTY), + pvecQueuedStanzas_(new std::vector()), + sasl_mech_(NULL) { +} + +XmppLoginTask::~XmppLoginTask() { + for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) + delete (*pvecQueuedStanzas_)[i]; +} + +void +XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) { + pelStanza_ = element; + isStart_ = isStart; + Advance(); + pelStanza_ = NULL; + isStart_ = false; +} + +const XmlElement * +XmppLoginTask::NextStanza() { + const XmlElement * result = pelStanza_; + pelStanza_ = NULL; + return result; +} + +bool +XmppLoginTask::Advance() { + + for (;;) { + + const XmlElement * element = NULL; + + switch (state_) { + + case LOGINSTATE_INIT: { + pctx_->RaiseReset(); + pelFeatures_.reset(NULL); + + pctx_->InternalSendStart(pctx_->user_jid_.domain()); + state_ = LOGINSTATE_STREAMSTART_SENT; + break; + } + + case LOGINSTATE_STREAMSTART_SENT: { + if (NULL == (element = NextStanza())) + return true; + + if (!isStart_ || !HandleStartStream(element)) + return Failure(XmppEngine::ERROR_VERSION); + + state_ = LOGINSTATE_STARTED_XMPP; + return true; + } + + case LOGINSTATE_STARTED_XMPP: { + if (NULL == (element = NextStanza())) + return true; + + if (!HandleFeatures(element)) + return Failure(XmppEngine::ERROR_VERSION); + + if (pctx_->tls_needed_) { + state_ = LOGINSTATE_TLS_INIT; + continue; + } + + if (authNeeded_) { + state_ = LOGINSTATE_AUTH_INIT; + continue; + } + + state_ = LOGINSTATE_BIND_INIT; + continue; + } + + case LOGINSTATE_TLS_INIT: { + const XmlElement * pelTls = GetFeature(QN_TLS_STARTTLS); + if (!pelTls) + return Failure(XmppEngine::ERROR_TLS); + + XmlElement el(QN_TLS_STARTTLS, true); + pctx_->InternalSendStanza(&el); + state_ = LOGINSTATE_TLS_REQUESTED; + continue; + } + + case LOGINSTATE_TLS_REQUESTED: { + if (NULL == (element = NextStanza())) + return true; + if (element->Name() != QN_TLS_PROCEED) + return Failure(XmppEngine::ERROR_TLS); + + // The proper domain to verify against is the real underlying + // domain - i.e., the domain that owns the JID. Our XmppEngineImpl + // also allows matching against a proxy domain instead, if it is told + // to do so - see the implementation of XmppEngineImpl::StartTls and + // XmppEngine::SetTlsServerDomain to see how you can use that feature + pctx_->StartTls(pctx_->user_jid_.domain()); + pctx_->tls_needed_ = false; + state_ = LOGINSTATE_INIT; + continue; + } + + case LOGINSTATE_AUTH_INIT: { + const XmlElement * pelSaslAuth = GetFeature(QN_SASL_MECHANISMS); + if (!pelSaslAuth) { + return Failure(XmppEngine::ERROR_AUTH); + } + + // Collect together the SASL auth mechanisms presented by the server + std::vector mechanisms; + for (const XmlElement * pelMech = + pelSaslAuth->FirstNamed(QN_SASL_MECHANISM); + pelMech; + pelMech = pelMech->NextNamed(QN_SASL_MECHANISM)) { + + mechanisms.push_back(pelMech->BodyText()); + } + + // Given all the mechanisms, choose the best + std::string choice(pctx_->ChooseBestSaslMechanism(mechanisms, pctx_->IsEncrypted())); + if (choice.empty()) { + return Failure(XmppEngine::ERROR_AUTH); + } + + // No recognized auth mechanism - that's an error + sasl_mech_.reset(pctx_->GetSaslMechanism(choice)); + if (sasl_mech_.get() == NULL) { + return Failure(XmppEngine::ERROR_AUTH); + } + + // OK, let's start it. + XmlElement * auth = sasl_mech_->StartSaslAuth(); + if (auth == NULL) { + return Failure(XmppEngine::ERROR_AUTH); + } + + pctx_->InternalSendStanza(auth); + delete auth; + state_ = LOGINSTATE_SASL_RUNNING; + continue; + } + + case LOGINSTATE_SASL_RUNNING: { + if (NULL == (element = NextStanza())) + return true; + if (element->Name().Namespace() != NS_SASL) + return Failure(XmppEngine::ERROR_AUTH); + if (element->Name() == QN_SASL_CHALLENGE) { + XmlElement * response = sasl_mech_->HandleSaslChallenge(element); + if (response == NULL) { + return Failure(XmppEngine::ERROR_AUTH); + } + pctx_->InternalSendStanza(response); + delete response; + state_ = LOGINSTATE_SASL_RUNNING; + continue; + } + if (element->Name() != QN_SASL_SUCCESS) { + return Failure(XmppEngine::ERROR_UNAUTHORIZED); + } + + // Authenticated! + authNeeded_ = false; + state_ = LOGINSTATE_INIT; + continue; + } + + case LOGINSTATE_BIND_INIT: { + const XmlElement * pelBindFeature = GetFeature(QN_BIND_BIND); + const XmlElement * pelSessionFeature = GetFeature(QN_SESSION_SESSION); + if (!pelBindFeature || !pelSessionFeature) + return Failure(XmppEngine::ERROR_BIND); + + XmlElement iq(QN_IQ); + iq.AddAttr(QN_TYPE, "set"); + + iqId_ = pctx_->NextId(); + iq.AddAttr(QN_ID, iqId_); + iq.AddElement(new XmlElement(QN_BIND_BIND, true)); + + if (pctx_->requested_resource_ != STR_EMPTY) { + iq.AddElement(new XmlElement(QN_BIND_RESOURCE), 1); + iq.AddText(pctx_->requested_resource_, 2); + } + pctx_->InternalSendStanza(&iq); + state_ = LOGINSTATE_BIND_REQUESTED; + continue; + } + + case LOGINSTATE_BIND_REQUESTED: { + if (NULL == (element = NextStanza())) + return true; + + if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ || + element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set") + return true; + + if (element->Attr(QN_TYPE) != "result" || element->FirstElement() == NULL || + element->FirstElement()->Name() != QN_BIND_BIND) + return Failure(XmppEngine::ERROR_BIND); + + fullJid_ = Jid(element->FirstElement()->TextNamed(QN_BIND_JID)); + if (!fullJid_.IsFull()) { + return Failure(XmppEngine::ERROR_BIND); + } + + if (pctx_->user_jid_.domain() != STR_DEFAULT_DOMAIN && + fullJid_.BareJid() != pctx_->user_jid_) { + return Failure(XmppEngine::ERROR_BIND); + } + + // now request session + XmlElement iq(QN_IQ); + iq.AddAttr(QN_TYPE, "set"); + + iqId_ = pctx_->NextId(); + iq.AddAttr(QN_ID, iqId_); + iq.AddElement(new XmlElement(QN_SESSION_SESSION, true)); + pctx_->InternalSendStanza(&iq); + + state_ = LOGINSTATE_SESSION_REQUESTED; + continue; + } + + case LOGINSTATE_SESSION_REQUESTED: { + if (NULL == (element = NextStanza())) + return true; + if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ || + element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set") + return false; + + if (element->Attr(QN_TYPE) != "result") + return Failure(XmppEngine::ERROR_BIND); + + pctx_->SignalBound(fullJid_); + FlushQueuedStanzas(); + state_ = LOGINSTATE_DONE; + return true; + } + + case LOGINSTATE_DONE: + return false; + } + } +} + +bool +XmppLoginTask::HandleStartStream(const XmlElement *element) { + + if (element->Name() != QN_STREAM_STREAM) + return false; + + if (element->Attr(QN_XMLNS) != "jabber:client") + return false; + + if (element->Attr(QN_VERSION) != "1.0") + return false; + + if (!element->HasAttr(QN_ID)) + return false; + + streamId_ = element->Attr(QN_ID); + + return true; +} + +bool +XmppLoginTask::HandleFeatures(const XmlElement *element) { + if (element->Name() != QN_STREAM_FEATURES) + return false; + + pelFeatures_.reset(new XmlElement(*element)); + return true; +} + +const XmlElement * +XmppLoginTask::GetFeature(const QName & name) { + return pelFeatures_->FirstNamed(name); +} + +bool +XmppLoginTask::Failure(XmppEngine::Error reason) { + state_ = LOGINSTATE_DONE; + pctx_->SignalError(reason); + return false; +} + +void +XmppLoginTask::OutgoingStanza(const XmlElement * element) { + XmlElement * pelCopy = new XmlElement(*element); + pvecQueuedStanzas_->push_back(pelCopy); +} + +void +XmppLoginTask::FlushQueuedStanzas() { + for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) { + pctx_->InternalSendStanza((*pvecQueuedStanzas_)[i]); + delete (*pvecQueuedStanzas_)[i]; + } + pvecQueuedStanzas_->clear(); +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.h new file mode 100644 index 00000000..7f321a30 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.h @@ -0,0 +1,95 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _logintask_h_ +#define _logintask_h_ + +#include +#include "talk/xmpp/jid.h" +#include "talk/base/scoped_ptr.h" +#include "talk/xmpp/xmppengine.h" +#include "talk/base/stl_decl.h" + +namespace buzz { + +class XmlElement; +class XmppEngineImpl; +class SaslMechanism; + + +class XmppLoginTask { + +public: + XmppLoginTask(XmppEngineImpl *pctx); + ~XmppLoginTask(); + + bool IsDone() + { return state_ == LOGINSTATE_DONE; } + void IncomingStanza(const XmlElement * element, bool isStart); + void OutgoingStanza(const XmlElement *element); + +private: + enum LoginTaskState { + LOGINSTATE_INIT = 0, + LOGINSTATE_STREAMSTART_SENT, + LOGINSTATE_STARTED_XMPP, + LOGINSTATE_TLS_INIT, + LOGINSTATE_AUTH_INIT, + LOGINSTATE_BIND_INIT, + LOGINSTATE_TLS_REQUESTED, + LOGINSTATE_SASL_RUNNING, + LOGINSTATE_BIND_REQUESTED, + LOGINSTATE_SESSION_REQUESTED, + LOGINSTATE_DONE, + }; + + const XmlElement * NextStanza(); + bool Advance(); + bool HandleStartStream(const XmlElement * element); + bool HandleFeatures(const XmlElement * element); + const XmlElement * GetFeature(const QName & name); + bool Failure(XmppEngine::Error reason); + void FlushQueuedStanzas(); + + XmppEngineImpl * pctx_; + bool authNeeded_; + LoginTaskState state_; + const XmlElement * pelStanza_; + bool isStart_; + std::string iqId_; + scoped_ptr pelFeatures_; + Jid fullJid_; + std::string streamId_; + scoped_ptr > > pvecQueuedStanzas_; + + scoped_ptr sasl_mech_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpppassword.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpppassword.h new file mode 100644 index 00000000..f431b4e5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpppassword.h @@ -0,0 +1,163 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _XMPPPASSWORD_H_ +#define _XMPPPASSWORD_H_ + +#include "talk/base/linked_ptr.h" +#include "talk/base/scoped_ptr.h" + +namespace buzz { + +class XmppPasswordImpl { +public: + virtual ~XmppPasswordImpl() {} + virtual size_t GetLength() const = 0; + virtual void CopyTo(char * dest, bool nullterminate) const = 0; + virtual std::string UrlEncode() const = 0; + virtual XmppPasswordImpl * Copy() const = 0; +}; + +class EmptyXmppPasswordImpl : public XmppPasswordImpl { +public: + virtual ~EmptyXmppPasswordImpl() {} + virtual size_t GetLength() const { return 0; } + virtual void CopyTo(char * dest, bool nullterminate) const { + if (nullterminate) { + *dest = '\0'; + } + } + virtual std::string UrlEncode() const { return ""; } + virtual XmppPasswordImpl * Copy() const { return new EmptyXmppPasswordImpl(); } +}; + +class XmppPassword { +public: + XmppPassword() : impl_(new EmptyXmppPasswordImpl()) {} + size_t GetLength() const { return impl_->GetLength(); } + void CopyTo(char * dest, bool nullterminate) const { impl_->CopyTo(dest, nullterminate); } + XmppPassword(const XmppPassword & other) : impl_(other.impl_->Copy()) {} + explicit XmppPassword(const XmppPasswordImpl & impl) : impl_(impl.Copy()) {} + XmppPassword & operator=(const XmppPassword & other) { + if (this != &other) { + impl_.reset(other.impl_->Copy()); + } + return *this; + } + void Clear() { impl_.reset(new EmptyXmppPasswordImpl()); } + std::string UrlEncode() const { return impl_->UrlEncode(); } + +private: + scoped_ptr impl_; +}; + + +// Used for constructing strings where a password is involved and we +// need to ensure that we zero memory afterwards +class FormatXmppPassword { +public: + FormatXmppPassword() { + storage_ = new char[32]; + capacity_ = 32; + length_ = 0; + storage_[0] = 0; + } + + void Append(const std::string & text) { + Append(text.data(), text.length()); + } + + void Append(const char * data, size_t length) { + EnsureStorage(length_ + length + 1); + memcpy(storage_ + length_, data, length); + length_ += length; + storage_[length_] = '\0'; + } + + void Append(const XmppPassword * password) { + size_t len = password->GetLength(); + EnsureStorage(length_ + len + 1); + password->CopyTo(storage_ + length_, true); + length_ += len; + } + + size_t GetLength() { + return length_; + } + + const char * GetData() { + return storage_; + } + + + // Ensures storage of at least n bytes + void EnsureStorage(size_t n) { + if (capacity_ >= n) { + return; + } + + size_t old_capacity = capacity_; + char * old_storage = storage_; + + for (;;) { + capacity_ *= 2; + if (capacity_ >= n) + break; + } + + storage_ = new char[capacity_]; + + if (old_capacity) { + memcpy(storage_, old_storage, length_); + + // zero memory in a way that an optimizer won't optimize it out + old_storage[0] = 0; + for (size_t i = 1; i < old_capacity; i++) { + old_storage[i] = old_storage[i - 1]; + } + delete[] old_storage; + } + } + + ~FormatXmppPassword() { + if (capacity_) { + storage_[0] = 0; + for (size_t i = 1; i < capacity_; i++) { + storage_[i] = storage_[i - 1]; + } + } + delete[] storage_; + } +private: + char * storage_; + size_t capacity_; + size_t length_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.cc new file mode 100644 index 00000000..66ed44fb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.cc @@ -0,0 +1,104 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "talk/xmllite/xmlelement.h" +#include "talk/base/common.h" +#include "talk/xmpp/xmppstanzaparser.h" +#include "talk/xmpp/constants.h" + +#define new TRACK_NEW + +namespace buzz { + +XmppStanzaParser::XmppStanzaParser(XmppStanzaParseHandler *psph) : + psph_(psph), + innerHandler_(this), + parser_(&innerHandler_), + depth_(0), + builder_() { +} + +void +XmppStanzaParser::Reset() { + parser_.Reset(); + depth_ = 0; + builder_.Reset(); +} + +void +XmppStanzaParser::IncomingStartElement( + XmlParseContext * pctx, const char * name, const char ** atts) { + if (depth_++ == 0) { + XmlElement * pelStream = XmlBuilder::BuildElement(pctx, name, atts); + if (pelStream == NULL) { + pctx->RaiseError(XML_ERROR_SYNTAX); + return; + } + psph_->StartStream(pelStream); + delete pelStream; + return; + } + + builder_.StartElement(pctx, name, atts); +} + +void +XmppStanzaParser::IncomingCharacterData( + XmlParseContext * pctx, const char * text, int len) { + if (depth_ > 1) { + builder_.CharacterData(pctx, text, len); + } +} + +void +XmppStanzaParser::IncomingEndElement( + XmlParseContext * pctx, const char * name) { + if (--depth_ == 0) { + psph_->EndStream(); + return; + } + + builder_.EndElement(pctx, name); + + if (depth_ == 1) { + XmlElement *element = builder_.CreateElement(); + psph_->Stanza(element); + delete element; + } +} + +void +XmppStanzaParser::IncomingError( + XmlParseContext * pctx, XML_Error errCode) { + UNUSED(pctx); + UNUSED(errCode); + psph_->XmlError(); +} + +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.h new file mode 100644 index 00000000..1e109a3d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.h @@ -0,0 +1,96 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _xmppstanzaparser_h_ +#define _xmppstanzaparser_h_ + +#include "talk/xmllite/xmlparser.h" +#include "talk/xmllite/xmlbuilder.h" + + +namespace buzz { + +class XmlElement; + +class XmppStanzaParseHandler { +public: + virtual void StartStream(const XmlElement * pelStream) = 0; + virtual void Stanza(const XmlElement * pelStanza) = 0; + virtual void EndStream() = 0; + virtual void XmlError() = 0; +}; + +class XmppStanzaParser { +public: + XmppStanzaParser(XmppStanzaParseHandler *psph); + bool Parse(const char * data, size_t len, bool isFinal) + { return parser_.Parse(data, len, isFinal); } + void Reset(); + +private: + class ParseHandler : public XmlParseHandler { + public: + ParseHandler(XmppStanzaParser * outer) : outer_(outer) {} + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) + { outer_->IncomingStartElement(pctx, name, atts); } + virtual void EndElement(XmlParseContext * pctx, + const char * name) + { outer_->IncomingEndElement(pctx, name); } + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len) + { outer_->IncomingCharacterData(pctx, text, len); } + virtual void Error(XmlParseContext * pctx, + XML_Error errCode) + { outer_->IncomingError(pctx, errCode); } + private: + XmppStanzaParser * const outer_; + }; + + friend class ParseHandler; + + void IncomingStartElement(XmlParseContext * pctx, + const char * name, const char ** atts); + void IncomingEndElement(XmlParseContext * pctx, + const char * name); + void IncomingCharacterData(XmlParseContext * pctx, + const char * text, int len); + void IncomingError(XmlParseContext * pctx, + XML_Error errCode); + + XmppStanzaParseHandler * psph_; + ParseHandler innerHandler_; + XmlParser parser_; + int depth_; + XmlBuilder builder_; + + }; + + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.cc new file mode 100644 index 00000000..82207f3b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.cc @@ -0,0 +1,168 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/xmpp/xmpptask.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/constants.h" + +namespace buzz { + +XmppTask::XmppTask(Task * parent, XmppEngine::HandlerLevel level) + : Task(parent), client_(NULL) { + XmppClient * client = (XmppClient*)parent->GetParent(XMPP_CLIENT_TASK_CODE); + client_ = client; + id_ = client->NextId(); + client->AddXmppTask(this, level); + client->SignalDisconnected.connect(this, &XmppTask::OnDisconnect); +} + +XmppTask::~XmppTask() { + StopImpl(); +} + +void +XmppTask::StopImpl() { + while (NextStanza() != NULL) {} + if (client_) { + client_->RemoveXmppTask(this); + client_->SignalDisconnected.disconnect(this); + client_ = NULL; + } +} + +XmppReturnStatus +XmppTask::SendStanza(const XmlElement * stanza) { + if (client_ == NULL) + return XMPP_RETURN_BADSTATE; + return client_->SendStanza(stanza); +} + +XmppReturnStatus +XmppTask::SendStanzaError(const XmlElement * element_original, + XmppStanzaError code, + const std::string & text) { + if (client_ == NULL) + return XMPP_RETURN_BADSTATE; + return client_->SendStanzaError(element_original, code, text); +} + +void +XmppTask::Stop() { + StopImpl(); + Task::Stop(); +} + +void +XmppTask::OnDisconnect() { + Error(); +} + +void +XmppTask::QueueStanza(const XmlElement * stanza) { + stanza_queue_.push_back(new XmlElement(*stanza)); + Wake(); +} + +const XmlElement * +XmppTask::NextStanza() { + XmlElement * result = NULL; + if (!stanza_queue_.empty()) { + result = stanza_queue_.front(); + stanza_queue_.pop_front(); + } + next_stanza_.reset(result); + return result; +} + +XmlElement * +XmppTask::MakeIq(const std::string & type, + const buzz::Jid & to, const std::string id) { + XmlElement * result = new XmlElement(QN_IQ); + if (!type.empty()) + result->AddAttr(QN_TYPE, type); + if (to != JID_EMPTY) + result->AddAttr(QN_TO, to.Str()); + if (!id.empty()) + result->AddAttr(QN_ID, id); + return result; +} + +XmlElement * +XmppTask::MakeIqResult(const XmlElement * query) { + XmlElement * result = new XmlElement(QN_IQ); + result->AddAttr(QN_TYPE, STR_RESULT); + if (query->HasAttr(QN_FROM)) { + result->AddAttr(QN_TO, query->Attr(QN_FROM)); + } + result->AddAttr(QN_ID, query->Attr(QN_ID)); + return result; +} + +bool +XmppTask::MatchResponseIq(const XmlElement * stanza, + const Jid & to, const std::string & id) { + if (stanza->Name() != QN_IQ) + return false; + + if (stanza->Attr(QN_ID) != id) + return false; + + Jid from(stanza->Attr(QN_FROM)); + if (from != to) { + Jid me = client_->jid(); + // we address the server as "", but it is legal for the server + // to identify itself with "domain" or "myself@domain" + if (to != JID_EMPTY) { + return false; + } + + if (from != Jid(me.domain()) && from != me.BareJid()) { + return false; + } + } + + + return true; +} + +bool +XmppTask::MatchRequestIq(const XmlElement * stanza, + const std::string & type, const QName & qn) { + if (stanza->Name() != QN_IQ) + return false; + + if (stanza->Attr(QN_TYPE) != type) + return false; + + if (stanza->FirstNamed(qn) == NULL) + return false; + + return true; +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.h new file mode 100644 index 00000000..3b56a1c9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.h @@ -0,0 +1,113 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _XMPPTASK_H_ +#define _XMPPTASK_H_ + +#include +#include +#include "talk/base/sigslot.h" +#include "talk/xmpp/xmppengine.h" +#include "talk/base/task.h" + +namespace buzz { + +///////////////////////////////////////////////////////////////////// +// +// XMPPTASK +// +///////////////////////////////////////////////////////////////////// +// +// See Task and XmppClient first. +// +// XmppTask is a task that is designed to go underneath XmppClient and be +// useful there. It has a way of finding its XmppClient parent so you +// can have it nested arbitrarily deep under an XmppClient and it can +// still find the XMPP services. +// +// Tasks register themselves to listen to particular kinds of stanzas +// that are sent out by the client. Rather than processing stanzas +// right away, they should decide if they own the sent stanza, +// and if so, queue it and Wake() the task, or if a stanza does not belong +// to you, return false right away so the next XmppTask can take a crack. +// This technique (synchronous recognize, but asynchronous processing) +// allows you to have arbitrary logic for recognizing stanzas yet still, +// for example, disconnect a client while processing a stanza - +// without reentrancy problems. +// +///////////////////////////////////////////////////////////////////// + +class XmppClient; + +class XmppTask : + public Task, + public XmppStanzaHandler, + public sigslot::has_slots<> +{ +public: + XmppTask(Task * parent, XmppEngine::HandlerLevel level = XmppEngine::HL_NONE); + virtual ~XmppTask(); + + virtual XmppClient * GetClient() const { return client_; } + std::string task_id() const { return id_; } + +protected: + friend class XmppClient; + + XmppReturnStatus SendStanza(const XmlElement * stanza); + XmppReturnStatus SetResult(const std::string & code); + XmppReturnStatus SendStanzaError(const XmlElement * element_original, + XmppStanzaError code, + const std::string & text); + + virtual void Stop(); + virtual bool HandleStanza(const XmlElement * stanza) { return false; } + virtual void OnDisconnect(); + virtual int ProcessReponse() { return STATE_DONE; } + + void QueueStanza(const XmlElement * stanza); + const XmlElement * NextStanza(); + + bool MatchResponseIq(const XmlElement * stanza, const Jid & to, const std::string & task_id); + bool MatchRequestIq(const XmlElement * stanza, const std::string & type, const QName & qn); + XmlElement *MakeIqResult(const XmlElement * query); + XmlElement *MakeIq(const std::string & type, + const Jid & to, const std::string task_id); + +private: + void StopImpl(); + + XmppClient * client_; + std::deque stanza_queue_; + scoped_ptr next_stanza_; + std::string id_; + +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/voicecaller.h b/kopete/protocols/jabber/jingle/voicecaller.h new file mode 100644 index 00000000..0f0d18bb --- /dev/null +++ b/kopete/protocols/jabber/jingle/voicecaller.h @@ -0,0 +1,96 @@ +#define PsiAccount JabberAccount +class PsiAccount; + +#ifndef VOICECALLER_H +#define VOICECALLER_H + +#include "im.h" + + + + +using namespace XMPP; + +/** + * \brief An abstract class for a voice call implementation. + */ +class VoiceCaller : public QObject +{ + Q_OBJECT + +public: + /** + * \brief Base constructor. + * + * \param account the account to which this voice caller belongs + */ + VoiceCaller(PsiAccount* account) : account_(account) { }; + + /** + * \brief Retrieves the account to which this voice caller belongs. + */ + PsiAccount* account() { return account_; } + + /** + * \brief Initializes the voice caller. + * This should be called when the connection is open. + */ + virtual void initialize() = 0; + + /** + * \brief De-initializes the voice caller. + * This should be called when the connection is about to be closed. + */ + virtual void deinitialize() = 0; + + /** + * \brief Call the given JID. + */ + virtual void call(const Jid&) = 0; + + /** + * \brief Accept a call from the given JID. + */ + virtual void accept(const Jid&) = 0; + + /** + * \brief Reject the call from the given JID. + */ + virtual void reject(const Jid&) = 0; + + /** + * \brief Terminate the call from the given JID. + */ + virtual void terminate(const Jid&) = 0; + +signals: + /** + * \brief Incoming call from the given JID. + */ + void incoming(const Jid&); + + /** + * \brief Contact accepted an incoming call. + */ + void accepted(const Jid&); + + /** + * \brief Contact rejected an incoming call. + */ + void rejected(const Jid&); + + /** + * \brief Call with given JID is in progress. + */ + void in_progress(const Jid&); + + /** + * \brief Call with given JID is terminated. + */ + void terminated(const Jid&); + +private: + PsiAccount* account_; +}; + +#endif diff --git a/kopete/protocols/jabber/kioslave/Makefile.am b/kopete/protocols/jabber/kioslave/Makefile.am new file mode 100644 index 00000000..7fe4d3d6 --- /dev/null +++ b/kopete/protocols/jabber/kioslave/Makefile.am @@ -0,0 +1,25 @@ +METASOURCES = AUTO + +INCLUDES = \ + -I$(srcdir)/.. \ + -I$(srcdir)/../libiris/iris/include \ + -I$(srcdir)/../libiris/iris/xmpp-im \ + -I$(srcdir)/../libiris/iris/jabber \ + -I$(srcdir)/../libiris/qca/src \ + -I$(srcdir)/../libiris/cutestuff/util \ + -I$(srcdir)/../libiris/cutestuff/network \ + $(all_includes) + +kde_module_LTLIBRARIES = kio_jabberdisco.la + +kio_jabberdisco_la_SOURCES = jabberdisco.cpp +kio_jabberdisco_la_LIBADD = ../libjabberclient.la ../libiris/qca/src/libqca.la ../libiris/iris/include/libiris.la ../libiris/iris/xmpp-im/libiris_xmpp_im.la ../libiris/iris/xmpp-core/libiris_xmpp_core.la ../libiris/iris/jabber/libiris_jabber.la ../libiris/cutestuff/util/libcutestuff_util.la ../libiris/cutestuff/network/libcutestuff_network.la $(LIB_KIO) +kio_jabberdisco_la_LDFLAGS = -no-undefined -module $(KDE_PLUGIN) $(all_libraries) + +noinst_HEADERS = jabberdisco.h + +protocol_DATA = jabberdisco.protocol +protocoldir = $(kde_servicesdir) + +messages: rc.cpp + $(XGETTEXT) *.cpp -o $(podir)/kio_jabberdisco.pot diff --git a/kopete/protocols/jabber/kioslave/jabberdisco.cpp b/kopete/protocols/jabber/kioslave/jabberdisco.cpp new file mode 100644 index 00000000..a6775320 --- /dev/null +++ b/kopete/protocols/jabber/kioslave/jabberdisco.cpp @@ -0,0 +1,399 @@ + +/*************************************************************************** + Jabber Service Discovery KIO Slave + ------------------- + begin : Wed June 1 2005 + copyright : (C) 2005 by Till Gerken + + Kopete (C) 2001-2005 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include + +#include "jabberdisco.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "jabberclient.h" + +JabberDiscoProtocol::JabberDiscoProtocol ( const QCString &pool_socket, const QCString &app_socket ) + : KIO::SlaveBase ( "kio_jabberdisco", pool_socket, app_socket ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Slave launched." << endl; + + m_jabberClient = 0l; + m_connected = false; + +} + + +JabberDiscoProtocol::~JabberDiscoProtocol () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Slave is shutting down." << endl; + + delete m_jabberClient; + +} + +void JabberDiscoProtocol::setHost ( const QString &host, int port, const QString &user, const QString &pass ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << " Host " << host << ", port " << port << ", user " << user << endl; + + m_host = host; + m_port = !port ? 5222 : port; + m_user = QString(user).replace ( "%", "@" ); + m_password = pass; + +} + +void JabberDiscoProtocol::openConnection () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << endl; + + if ( m_connected ) + { + return; + } + + // instantiate new client backend or clean up old one + if ( !m_jabberClient ) + { + m_jabberClient = new JabberClient; + + QObject::connect ( m_jabberClient, SIGNAL ( csDisconnected () ), this, SLOT ( slotCSDisconnected () ) ); + QObject::connect ( m_jabberClient, SIGNAL ( csError ( int ) ), this, SLOT ( slotCSError ( int ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( tlsWarning ( int ) ), this, SLOT ( slotHandleTLSWarning ( int ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( connected () ), this, SLOT ( slotConnected () ) ); + QObject::connect ( m_jabberClient, SIGNAL ( error ( JabberClient::ErrorCode ) ), this, SLOT ( slotClientError ( JabberClient::ErrorCode ) ) ); + + QObject::connect ( m_jabberClient, SIGNAL ( debugMessage ( const QString & ) ), + this, SLOT ( slotClientDebugMessage ( const QString & ) ) ); + } + else + { + m_jabberClient->disconnect (); + } + + // we need to use the old protocol for now + m_jabberClient->setUseXMPP09 ( true ); + + // set SSL flag (this should be converted to forceTLS when using the new protocol) + m_jabberClient->setUseSSL ( false ); + + // override server and port (this should be dropped when using the new protocol and no direct SSL) + m_jabberClient->setOverrideHost ( true, m_host, m_port ); + + // allow plaintext password authentication or not? + m_jabberClient->setAllowPlainTextPassword ( false ); + + switch ( m_jabberClient->connect ( XMPP::Jid ( m_user + QString("/") + "JabberBrowser" ), m_password ) ) + { + case JabberClient::NoTLS: + // no SSL support, at the connecting stage this means the problem is client-side + error ( KIO::ERR_UPGRADE_REQUIRED, i18n ( "TLS" ) ); + break; + + case JabberClient::Ok: + default: + // everything alright! + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Waiting for socket to open..." << endl; + break; + } + + connected (); + +} + +void JabberDiscoProtocol::closeConnection () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << endl; + + if ( m_jabberClient ) + { + m_jabberClient->disconnect (); + } + +} + +void JabberDiscoProtocol::slave_status () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << endl; + + slaveStatus ( m_host, m_connected ); + +} + +void JabberDiscoProtocol::get ( const KURL &url ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << endl; + + m_command = Get; + m_url = url; + + mimeType ( "inode/directory" ); + + finished (); + +} + +void JabberDiscoProtocol::listDir ( const KURL &url ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << endl; + + m_command = ListDir; + m_url = url; + + openConnection (); + +} + +void JabberDiscoProtocol::mimetype ( const KURL &/*url*/ ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << endl; + + mimeType("inode/directory"); + + finished (); + +} + +void JabberDiscoProtocol::slotClientDebugMessage ( const QString &msg ) +{ + + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << msg << endl; + +} + +void JabberDiscoProtocol::slotHandleTLSWarning ( int validityResult ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Handling TLS warning..." << endl; + + if ( messageBox ( KIO::SlaveBase::WarningContinueCancel, + i18n ( "The server certificate is invalid. Do you want to continue? " ), + i18n ( "Certificate Warning" ) ) == KMessageBox::Continue ) + { + // resume stream + m_jabberClient->continueAfterTLSWarning (); + } + else + { + // disconnect stream + closeConnection (); + } + +} + +void JabberDiscoProtocol::slotClientError ( JabberClient::ErrorCode errorCode ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Handling client error..." << endl; + + switch ( errorCode ) + { + case JabberClient::NoTLS: + default: + error ( KIO::ERR_UPGRADE_REQUIRED, i18n ( "TLS" ) ); + closeConnection (); + break; + } + +} + +void JabberDiscoProtocol::slotConnected () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Connected to Jabber server." << endl; + + XMPP::JT_DiscoItems *discoTask; + + m_connected = true; + + // now execute command + switch ( m_command ) + { + case ListDir: // list a directory + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Listing directory..." << endl; + discoTask = new XMPP::JT_DiscoItems ( m_jabberClient->rootTask () ); + connect ( discoTask, SIGNAL ( finished () ), this, SLOT ( slotQueryFinished () ) ); + discoTask->get ( m_host ); + discoTask->go ( true ); + break; + + case Get: // retrieve an item + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Retrieving item..." << endl; + break; + + default: // do nothing by default + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Unknown command " << m_command << endl; + break; + } + +} + +void JabberDiscoProtocol::slotQueryFinished () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << "Query task finished" << endl; + + XMPP::JT_DiscoItems * task = (XMPP::JT_DiscoItems *) sender (); + + if (!task->success ()) + { + error ( KIO::ERR_COULD_NOT_READ, "" ); + return; + } + + XMPP::DiscoList::const_iterator itemsEnd = task->items().end (); + for (XMPP::DiscoList::const_iterator it = task->items().begin (); it != itemsEnd; ++it) + { + KIO::UDSAtom atom; + KIO::UDSEntry entry; + + atom.m_uds = KIO::UDS_NAME; + atom.m_str = (*it).jid().userHost (); + entry.prepend ( atom ); + + atom.m_uds = KIO::UDS_SIZE; + atom.m_long = 0; + entry.prepend ( atom ); + + atom.m_uds = KIO::UDS_LINK_DEST; + atom.m_str = (*it).name (); + entry.prepend ( atom ); + + atom.m_uds = KIO::UDS_MIME_TYPE; + atom.m_str = "inode/directory"; + entry.prepend ( atom ); + + atom.m_uds = KIO::UDS_SIZE; + atom.m_long = 0; + entry.prepend ( atom ); + + listEntry ( entry, false ); + + } + + listEntry ( KIO::UDSEntry(), true ); + + finished (); + +} + +void JabberDiscoProtocol::slotCSDisconnected () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Disconnected from Jabber server." << endl; + + /* + * We should delete the JabberClient instance here, + * but timers etc prevent us from doing so. Iris does + * not like to be deleted from a slot. + */ + m_connected = false; + +} + +void JabberDiscoProtocol::slotCSError ( int errorCode ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Error in stream signalled." << endl; + + if ( ( errorCode == XMPP::ClientStream::ErrAuth ) + && ( m_jabberClient->clientStream()->errorCondition () == XMPP::ClientStream::NotAuthorized ) ) + { + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Incorrect password, retrying." << endl; + + KIO::AuthInfo authInfo; + authInfo.username = m_user; + authInfo.password = m_password; + if ( openPassDlg ( authInfo, i18n ( "The login details are incorrect. Do you want to try again?" ) ) ) + { + m_user = authInfo.username; + m_password = authInfo.password; + closeConnection (); + openConnection (); + } + else + { + closeConnection (); + error ( KIO::ERR_COULD_NOT_AUTHENTICATE, "" ); + } + } + else + { + closeConnection (); + error ( KIO::ERR_CONNECTION_BROKEN, "" ); + } + +} + +bool breakEventLoop = false; + +class EventLoopThread : public QThread +{ +public: + void run (); +}; + +void EventLoopThread::run () +{ + + while ( true ) + { + qApp->processEvents (); + msleep ( 100 ); + + if ( breakEventLoop ) + break; + } + +} + +void JabberDiscoProtocol::dispatchLoop () +{ + + EventLoopThread eventLoopThread; + + eventLoopThread.start (); + SlaveBase::dispatchLoop (); + breakEventLoop = true; + eventLoopThread.wait (); + +} + +extern "C" +{ + KDE_EXPORT int kdemain(int argc, char **argv); +} + + +int kdemain ( int argc, char **argv ) +{ + KApplication app(argc, argv, "kio_jabberdisco", false, true); + + kdDebug(JABBER_DISCO_DEBUG) << k_funcinfo << endl; + + if ( argc != 4 ) + { + kdDebug(JABBER_DISCO_DEBUG) << "Usage: kio_jabberdisco protocol domain-socket1 domain-socket2" << endl; + exit(-1); + } + + JabberDiscoProtocol slave ( argv[2], argv[3] ); + slave.dispatchLoop (); + + return 0; +} + +#include "jabberdisco.moc" diff --git a/kopete/protocols/jabber/kioslave/jabberdisco.h b/kopete/protocols/jabber/kioslave/jabberdisco.h new file mode 100644 index 00000000..f2f6d78d --- /dev/null +++ b/kopete/protocols/jabber/kioslave/jabberdisco.h @@ -0,0 +1,82 @@ + +/*************************************************************************** + Jabber Service Discovery KIO Slave + ------------------- + begin : Wed June 1 2005 + copyright : (C) 2005 by Till Gerken + + Kopete (C) 2001-2005 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef _JABBERDISCO_H_ +#define _JABBERDISCO_H_ + +#include +#include +#include + +#include +#include +#include +#include + +#define JABBER_DISCO_DEBUG 0 + +class JabberClient; + +class JabberDiscoProtocol : public QObject, public KIO::SlaveBase +{ + +Q_OBJECT + +public: + JabberDiscoProtocol ( const QCString &pool_socket, const QCString &app_socket ); + virtual ~JabberDiscoProtocol (); + + void setHost ( const QString &host, int port, const QString &user, const QString &pass ); + + void openConnection (); + void closeConnection (); + + void slave_status (); + + void get ( const KURL &url ); + void listDir ( const KURL &url ); + void mimetype ( const KURL &url ); + + void dispatchLoop (); + +private slots: + void slotClientDebugMessage ( const QString &msg ); + void slotHandleTLSWarning ( int validityResult ); + void slotClientError ( JabberClient::ErrorCode errorCode ); + void slotConnected (); + void slotCSDisconnected (); + void slotCSError ( int error ); + + void slotQueryFinished (); + +private: + enum CommandType { Get, ListDir }; + + QString m_host, m_user, m_password; + int m_port; + KURL m_url; + bool m_connected; + + CommandType m_command; + + JabberClient *m_jabberClient; + +}; + +#endif diff --git a/kopete/protocols/jabber/kioslave/jabberdisco.protocol b/kopete/protocols/jabber/kioslave/jabberdisco.protocol new file mode 100644 index 00000000..01237e73 --- /dev/null +++ b/kopete/protocols/jabber/kioslave/jabberdisco.protocol @@ -0,0 +1,53 @@ +[Protocol] +exec=kio_jabberdisco +protocol=jabber +input=none +output=filesystem +reading=true +writing=false +makedir=false +linking=false +moving=false +Icon=remote +Description=A KIO slave for Jabber Service Discovery +Description[be]=Модуль kioslave Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ ÑервіÑаў Jabber +Description[bn]=Jabber সারà§à¦­à¦¿à¦¸ ডিসকভারির জনà§à¦¯ à¦à¦•à¦Ÿà¦¿ কে-আই-ও সà§à¦²à§‡à¦­ +Description[bs]=KIO slave za otkrivanje Jabber servisa +Description[ca]=Un esclau KIO pel servei de de descoberta del Jabber +Description[cs]=Pomocný protokol pro zjiÅ¡Å¥ování služeb Jabber +Description[da]=En kioslave til at opdage jabber service +Description[de]=Ein Ein-/Ausgabemodul zum Auffinden von Jabber-Diensten +Description[el]=Ένα kioslave για την ανίχνευση υπηÏεσίας Jabber +Description[es]=Un «kioslave» para el servicio de descubrimiento jabber +Description[et]=Jabberi teenuste tuvastamise KIO-moodul +Description[eu]=Jabber aurkikuntza zerbitzureako KIO morroi bat +Description[fa]=یک پیرو KIO برای خدمت اکتشاÙÛŒ Jabber +Description[fr]=Un module d'entrée / sortie pour la recherche de service Jabber +Description[gl]=Un KIO slave para Jabber Service Discovery +Description[hu]=KDE-protokoll a Jabber szolgáltatáskeresÅ‘ használatához +Description[is]=kioslave fyrir Jabber þjónustu uppgötvun +Description[it]=Un KIO slave per il servizio di discovery per Jabber +Description[ja]=Jabber Service Discovery ã® KIO スレーブ +Description[ka]=KIO slave Jabber სერვისის დირექტáƒáƒ áƒ˜áƒ˜áƒ¡áƒ—ვის +Description[kk]=Jabber қызметін байқау KIO slave қызметі +Description[km]=KIO slave មួយ​សម្រាប់​របក​គំហើញ​សáŸážœáž¶ Jabber +Description[lt]=Priedas (kioslave) FISH protokolui +Description[nb]=En kioslave for Jabber tjenestesøk +Description[nds]=En In-/Utgaavmoduul för't Finnen vun Jabber-Deensten +Description[ne]=जà¥à¤¯à¤¾à¤¬à¤° सेवा खोजीका लागि कियो सà¥à¤²à¤¾à¤­ +Description[nl]=Een kioslave voor Jabber Service Discovery +Description[nn]=Ein KIO-slave for Jabber-tenesteoppdaging +Description[pl]=Wtyczka protokoÅ‚u KIO dla usÅ‚ugi odkrywania usÅ‚ug Jabbera (Jabber Service Discovery) +Description[pt]=Um 'kioslave' para a Descoberta de Serviços do Jabber +Description[pt_BR]=Um KIO-Slave para a descoberta de serviço do Jabber +Description[ru]=Обработчик KIO Ð´Ð»Ñ Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ñлужб Jabber +Description[sk]=KIO otrok pre Jabber Service Discovery +Description[sl]=KIO slave za odkrivanje storitev za Jabber +Description[sr]=KIO Ñлуга за Jabber Service Discovery +Description[sr@Latn]=KIO sluga za Jabber Service Discovery +Description[sv]=En I/O-slav för Jabber tjänstupptäckt +Description[tr]=Jabber Servis Bulucu için KIOSlave +Description[uk]=Підлеглий Ð’/Ð’ Ð´Ð»Ñ Ð²Ð¸ÑÐ²Ð»ÐµÐ½Ð½Ñ Ñлужби Jabber +Description[zh_CN]=Jabber æœåŠ¡å‘现的 KIO slave +Description[zh_HK]=ç”¨æ–¼ç™¼ç¾ Jabber æœå‹™çš„ KIO slave +Description[zh_TW]=Jabber æœå‹™çš„ kioslave diff --git a/kopete/protocols/jabber/kopete_jabber.desktop b/kopete/protocols/jabber/kopete_jabber.desktop new file mode 100644 index 00000000..28c1f89d --- /dev/null +++ b/kopete/protocols/jabber/kopete_jabber.desktop @@ -0,0 +1,79 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=jabber_protocol +ServiceTypes=Kopete/Protocol +X-KDE-Library=kopete_jabber +X-Kopete-Messaging-Protocol=messaging/xmpp +X-KDE-PluginInfo-Author=Kopete Developers +X-KDE-PluginInfo-Email=kopete-devel@kde.org +X-KDE-PluginInfo-Name=kopete_jabber +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Protocols +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Jabber +Name[hi]=जैबर +Name[ne]=जà¥à¤¯à¤¾à¤¬à¤° +Name[pa]=ਜੱਬਰ +Name[ta]=ஜாபர௠+Comment=Protocol to connect to Jabber +Comment[ar]=البروتوكول سيتصل بـ Jabber +Comment[be]=Пратакол Jabber +Comment[bg]=Протокол за връзка Ñ Jabber +Comment[bn]=Jabber-ঠসংযোগ করতে পà§à¦°à§‹à¦Ÿà§‹à¦•à¦² +Comment[br]=Komenad kevreañ ouzh Jabber +Comment[bs]=Jabber protokol +Comment[ca]=Protocol per a connectar-se a Jabber +Comment[cs]=Protokol k pÅ™ipojení k Jabberu +Comment[cy]=Protocol i gysylltu â Jabber +Comment[da]=Protokol til at forbinde til Jabber +Comment[de]=Protokoll zur Verbindung mit Jabber +Comment[el]=ΠÏωτόκολλο για σÏνδεση στο Jabber +Comment[es]=Protocolo de conexión con Jabber +Comment[et]=Protokoll ühendumiseks Jabberiga +Comment[eu]=Jabber-era konektatzeko protokoloa +Comment[fa]=قرارداد برای اتصال به Jabber +Comment[fi]=Yhteyskäytäntö Jabber-verkkoon kytkeytymiseen +Comment[fr]=Protocole pour se connecter sur Jabber +Comment[ga]=Prótacal chun ceangal le Jabber +Comment[gl]=Protocolo para se conectar a Jabber +Comment[he]=פרוטוקול התחברות ל- Jabber +Comment[hi]=जैबर से जà¥à¤¡à¤¼à¤¨à¥‡ का पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¥‰à¤² +Comment[hr]=Protokol za povezivanje na Jabber +Comment[hu]=Protokoll a Jabber használatához +Comment[is]=Samskiptamáti til að tengjast Jabber +Comment[it]=Protocollo per connessione a Jabber +Comment[ja]=Jabber ã«æŽ¥ç¶šã™ã‚‹ãƒ—ロトコル +Comment[ka]=Jabberთáƒáƒœ დáƒáƒ™áƒáƒ•áƒ¨áƒ˜áƒ áƒ”ბის áƒáƒ¥áƒ›áƒ˜ +Comment[kk]=Jabber-ге қоÑылу протоколы +Comment[km]=ពិធីការ​ភ្ជាប់​ទៅ Jabber +Comment[lt]=Protokolas prisijungimui prie Jabber +Comment[mk]=Протокол за поврзување на Jabber +Comment[nb]=Protokoll for Ã¥ koble til Jabber +Comment[nds]=Protokoll för't Tokoppeln na Jabber +Comment[ne]=जà¥à¤¯à¤¾à¤¬à¤°à¤®à¤¾ जडान गरà¥à¤¨à¥‡ पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¤² +Comment[nl]=Protocol voor Jabber +Comment[nn]=Protokoll for Ã¥ kopla til Jabber +Comment[pl]=Protokół poÅ‚Ä…czenia z serwerem Jabbera +Comment[pt]=Um protocolo para se ligar ao Jabber +Comment[pt_BR]=Protocolo para conexão ao Jabber +Comment[ro]=Protocol de conectare la Jabber +Comment[ru]=Протокол Ð´Ð»Ñ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ðº Jabber +Comment[sk]=Protokol pre pripojenie k Jabber +Comment[sl]=Protokol za povezavo na Jabber +Comment[sr]=Протокол за повезивање на Jabber +Comment[sr@Latn]=Protokol za povezivanje na Jabber +Comment[sv]=Protokoll för att ansluta till Jabber +Comment[ta]=ஜாபரà¯à®Ÿà®©à¯ இணைகà¯à®• விதிமà¯à®±à¯ˆ +Comment[tg]=Қарордоди пайваÑтшавӣ ба Jabber +Comment[tr]=Jabber'e baÄŸlantı iletiÅŸim kuralı +Comment[uk]=Протокол Ð´Ð»Ñ Ð·'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· Jabber +Comment[uz]=Jabber uchun protokol +Comment[uz@cyrillic]=Jabber учун протокол +Comment[zh_CN]=连接到 Jabber åè®® +Comment[zh_HK]=用來連接至 Jabber 的通訊å”定 +Comment[zh_TW]=連到 Jabber çš„å”定 + diff --git a/kopete/protocols/jabber/libiris/001_last_activity.patch b/kopete/protocols/jabber/libiris/001_last_activity.patch new file mode 100644 index 00000000..24673e80 --- /dev/null +++ b/kopete/protocols/jabber/libiris/001_last_activity.patch @@ -0,0 +1,113 @@ +Index: iris/xmpp-im/xmpp_tasks.h +=================================================================== +--- iris/xmpp-im/xmpp_tasks.h (revision 419672) ++++ iris/xmpp-im/xmpp_tasks.h (working copy) +@@ -195,6 +195,29 @@ + Private *d; + }; + ++ class JT_GetLastActivity : public Task ++ { ++ Q_OBJECT ++ public: ++ JT_GetLastActivity(Task *); ++ ~JT_GetLastActivity(); ++ ++ void get(const Jid &); ++ ++ int seconds() const; ++ const QString &message() const; ++ ++ void onGo(); ++ bool take(const QDomElement &x); ++ ++ private: ++ class Private; ++ Private *d; ++ ++ QDomElement iq; ++ Jid jid; ++ }; ++ + class JT_GetServices : public Task + { + Q_OBJECT +Index: iris/xmpp-im/xmpp_tasks.cpp +=================================================================== +--- iris/xmpp-im/xmpp_tasks.cpp (revision 419672) ++++ iris/xmpp-im/xmpp_tasks.cpp (working copy) +@@ -773,6 +773,74 @@ + + + //---------------------------------------------------------------------------- ++// JT_GetLastActivity ++//---------------------------------------------------------------------------- ++class JT_GetLastActivity::Private ++{ ++public: ++ Private() {} ++ ++ int seconds; ++ QString message; ++}; ++ ++JT_GetLastActivity::JT_GetLastActivity(Task *parent) ++:Task(parent) ++{ ++ d = new Private; ++} ++ ++JT_GetLastActivity::~JT_GetLastActivity() ++{ ++ delete d; ++} ++ ++void JT_GetLastActivity::get(const Jid &j) ++{ ++ jid = j; ++ iq = createIQ(doc(), "get", jid.full(), id()); ++ QDomElement query = doc()->createElement("query"); ++ query.setAttribute("xmlns", "jabber:iq:last"); ++ iq.appendChild(query); ++} ++ ++int JT_GetLastActivity::seconds() const ++{ ++ return d->seconds; ++} ++ ++const QString &JT_GetLastActivity::message() const ++{ ++ return d->message; ++} ++ ++void JT_GetLastActivity::onGo() ++{ ++ send(iq); ++} ++ ++bool JT_GetLastActivity::take(const QDomElement &x) ++{ ++ if(!iqVerify(x, jid, id())) ++ return false; ++ ++ if(x.attribute("type") == "result") { ++ QDomElement q = queryTag(x); ++ ++ d->message = q.text(); ++ bool ok; ++ d->seconds = q.attribute("seconds").toInt(&ok); ++ ++ setSuccess(ok); ++ } ++ else { ++ setError(x); ++ } ++ ++ return true; ++} ++ ++//---------------------------------------------------------------------------- + // JT_GetServices + //---------------------------------------------------------------------------- + JT_GetServices::JT_GetServices(Task *parent) diff --git a/kopete/protocols/jabber/libiris/002_offline_event.patch b/kopete/protocols/jabber/libiris/002_offline_event.patch new file mode 100644 index 00000000..dfaa1f8e --- /dev/null +++ b/kopete/protocols/jabber/libiris/002_offline_event.patch @@ -0,0 +1,17 @@ +? 002_offline_event.patch +Index: iris/xmpp-im/types.cpp +=================================================================== +RCS file: /home/kde/kdenetwork/kopete/protocols/jabber/libiris/iris/xmpp-im/types.cpp,v +retrieving revision 1.3 +diff -u -p -r1.3 types.cpp +--- iris/xmpp-im/types.cpp 21 May 2004 14:35:44 -0000 1.3 ++++ iris/xmpp-im/types.cpp 5 Feb 2005 21:04:44 -0000 +@@ -639,6 +639,8 @@ bool Message::fromStanza(const Stanza &s + d->eventList += ComposingEvent; + else if (evtag == "delivered") + d->eventList += DeliveredEvent; ++ else if (evtag == "offline") ++ d->eventList += OfflineEvent; + } + if (d->eventList.isEmpty()) + d->eventList += CancelEvent; diff --git a/kopete/protocols/jabber/libiris/003_case_insensitive_jid.patch b/kopete/protocols/jabber/libiris/003_case_insensitive_jid.patch new file mode 100644 index 00000000..d4b0e285 --- /dev/null +++ b/kopete/protocols/jabber/libiris/003_case_insensitive_jid.patch @@ -0,0 +1,14 @@ +Index: iris/xmpp-core/jid.cpp +=================================================================== +--- iris/xmpp-core/jid.cpp (revision 469141) ++++ iris/xmpp-core/jid.cpp (working copy) +@@ -233,6 +233,9 @@ + b = d; + else + b = n + '@' + d; ++ ++ b=b.lower(); // JID are not case sensitive ++ + if(r.isEmpty()) + f = b; + else diff --git a/kopete/protocols/jabber/libiris/004_xhtml_im.patch b/kopete/protocols/jabber/libiris/004_xhtml_im.patch new file mode 100644 index 00000000..990ab4f7 --- /dev/null +++ b/kopete/protocols/jabber/libiris/004_xhtml_im.patch @@ -0,0 +1,266 @@ +Index: iris/include/xmpp.h +=================================================================== +--- iris/include/xmpp.h (revision 470311) ++++ iris/include/xmpp.h (working copy) +@@ -318,8 +318,11 @@ + + QDomDocument & doc() const; + QString baseNS() const; ++ QString xhtmlImNS() const; ++ QString xhtmlNS() const; + QDomElement createElement(const QString &ns, const QString &tagName); + QDomElement createTextElement(const QString &ns, const QString &tagName, const QString &text); ++ QDomElement createXHTMLElement(const QString &xHTML); + void appendChild(const QDomElement &e); + + Kind kind() const; +@@ -372,6 +375,8 @@ + + virtual QDomDocument & doc() const=0; + virtual QString baseNS() const=0; ++ virtual QString xhtmlImNS() const=0; ++ virtual QString xhtmlNS() const=0; + virtual bool old() const=0; + + virtual void close()=0; +@@ -479,6 +484,8 @@ + // reimplemented + QDomDocument & doc() const; + QString baseNS() const; ++ QString xhtmlImNS() const; ++ QString xhtmlNS() const; + bool old() const; + + void close(); +Index: iris/include/im.h +=================================================================== +--- iris/include/im.h (revision 470311) ++++ iris/include/im.h (working copy) +@@ -65,6 +65,7 @@ + QString lang() const; + QString subject(const QString &lang="") const; + QString body(const QString &lang="") const; ++ QString xHTMLBody(const QString &lang="") const; + QString thread() const; + Stanza::Error error() const; + +@@ -75,6 +76,7 @@ + void setLang(const QString &s); + void setSubject(const QString &s, const QString &lang=""); + void setBody(const QString &s, const QString &lang=""); ++ void setXHTMLBody(const QString &s, const QString &lang="", const QString &attr = ""); + void setThread(const QString &s); + void setError(const Stanza::Error &err); + +@@ -286,6 +288,7 @@ + bool canSearch() const; + bool canGroupchat() const; + bool canDisco() const; ++ bool canXHTML() const; + bool isGateway() const; + bool haveVCard() const; + +@@ -298,6 +301,7 @@ + FID_Disco, + FID_Gateway, + FID_VCard, ++ FID_Xhtml, + + // private Psi actions + FID_Add +Index: iris/xmpp-im/types.cpp +=================================================================== +--- iris/xmpp-im/types.cpp (revision 470311) ++++ iris/xmpp-im/types.cpp (working copy) +@@ -19,7 +19,7 @@ + */ + + #include"im.h" +- ++#include "protocol.h" + #include + #include + +@@ -180,7 +180,8 @@ + Jid to, from; + QString id, type, lang; + +- StringMap subject, body; ++ StringMap subject, body, xHTMLBody; ++ + QString thread; + Stanza::Error error; + +@@ -279,6 +280,11 @@ + return d->body[lang]; + } + ++QString Message::xHTMLBody(const QString &lang) const ++{ ++ return d->xHTMLBody[lang]; ++} ++ + QString Message::thread() const + { + return d->thread; +@@ -340,9 +346,16 @@ + void Message::setBody(const QString &s, const QString &lang) + { + d->body[lang] = s; +- //d->flag = false; + } + ++void Message::setXHTMLBody(const QString &s, const QString &lang, const QString &attr) ++{ ++ //ugly but needed if s is not a node but a list of leaf ++ ++ QString content = "\n" + s +"\n"; ++ d->xHTMLBody[lang] = content; ++} ++ + void Message::setThread(const QString &s) + { + d->thread = s; +@@ -489,7 +502,19 @@ + s.appendChild(e); + } + } +- ++ if ( !d->xHTMLBody.isEmpty()) { ++ QDomElement parent = s.createElement(s.xhtmlImNS(), "html"); ++ for(it = d->xHTMLBody.begin(); it != d->xHTMLBody.end(); ++it) { ++ const QString &str = it.data(); ++ if(!str.isEmpty()) { ++ QDomElement child = s.createXHTMLElement(str); ++ if(!it.key().isEmpty()) ++ child.setAttributeNS(NS_XML, "xml:lang", it.key()); ++ parent.appendChild(child); ++ } ++ } ++ s.appendChild(parent); ++ } + if(d->type == "error") + s.setError(d->error); + +@@ -591,6 +616,21 @@ + else if(e.tagName() == "thread") + d->thread = e.text(); + } ++ else if (e.namespaceURI() == s.xhtmlImNS()) { ++ if (e.tagName() == "html") { ++ QDomNodeList htmlNL= e.childNodes(); ++ for (unsigned int x = 0; x < htmlNL.count(); x++) { ++ QDomElement i = htmlNL.item(x).toElement(); ++ ++ if (i.tagName() == "body") { ++ QDomDocument RichText; ++ QString lang = i.attributeNS(NS_XML, "lang", ""); ++ RichText.appendChild(i); ++ d-> xHTMLBody[lang] = RichText.toString(); ++ } ++ } ++ } ++ } + else { + //printf("extension element: [%s]\n", e.tagName().latin1()); + } +@@ -1418,6 +1458,16 @@ + return test(ns); + } + ++#define FID_XHTML "http://jabber.org/protocol/xhtml-im" ++bool Features::canXHTML() const ++{ ++ QStringList ns; ++ ++ ns << FID_XHTML; ++ ++ return test(ns); ++} ++ + #define FID_GROUPCHAT "jabber:iq:conference" + bool Features::canGroupchat() const + { +Index: iris/xmpp-im/xmpp_tasks.cpp +=================================================================== +--- iris/xmpp-im/xmpp_tasks.cpp (revision 470311) ++++ iris/xmpp-im/xmpp_tasks.cpp (working copy) +@@ -1348,6 +1348,10 @@ + query.appendChild(feature); + + feature = doc()->createElement("feature"); ++ feature.setAttribute("var", "http://jabber.org/protocol/xhtml-im"); ++ query.appendChild(feature); ++ ++ feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/si/profile/file-transfer"); + query.appendChild(feature); + +Index: iris/xmpp-core/protocol.h +=================================================================== +--- iris/xmpp-core/protocol.h (revision 470311) ++++ iris/xmpp-core/protocol.h (working copy) +@@ -35,6 +35,8 @@ + #define NS_SESSION "urn:ietf:params:xml:ns:xmpp-session" + #define NS_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas" + #define NS_BIND "urn:ietf:params:xml:ns:xmpp-bind" ++#define NS_XHTML_IM "http://jabber.org/protocol/xhtml-im" ++#define NS_XHTML "http://www.w3.org/1999/xhtml" + + namespace XMPP + { +Index: iris/xmpp-core/stream.cpp +=================================================================== +--- iris/xmpp-core/stream.cpp (revision 470311) ++++ iris/xmpp-core/stream.cpp (working copy) +@@ -293,6 +293,16 @@ + return d->s->baseNS(); + } + ++QString Stanza::xhtmlImNS() const ++{ ++ return d->s->xhtmlImNS(); ++} ++ ++QString Stanza::xhtmlNS() const ++{ ++ return d->s->xhtmlNS(); ++} ++ + QDomElement Stanza::createElement(const QString &ns, const QString &tagName) + { + return d->s->doc().createElementNS(ns, tagName); +@@ -305,6 +315,16 @@ + return e; + } + ++QDomElement Stanza::createXHTMLElement(const QString &xHTML) ++{ ++ QDomDocument doc; ++ ++ doc.setContent(xHTML, true); ++ QDomElement root = doc.documentElement(); ++ //QDomElement e; ++ return (root); ++} ++ + void Stanza::appendChild(const QDomElement &e) + { + d->e.appendChild(e); +@@ -861,6 +881,16 @@ + return NS_CLIENT; + } + ++QString ClientStream::xhtmlImNS() const ++{ ++ return NS_XHTML_IM; ++} ++ ++QString ClientStream::xhtmlNS() const ++{ ++ return NS_XHTML; ++} ++ + void ClientStream::setAllowPlain(bool b) + { + d->allowPlain = b; diff --git a/kopete/protocols/jabber/libiris/005_join_muc_with_password.patch b/kopete/protocols/jabber/libiris/005_join_muc_with_password.patch new file mode 100644 index 00000000..058825db --- /dev/null +++ b/kopete/protocols/jabber/libiris/005_join_muc_with_password.patch @@ -0,0 +1,163 @@ +Index: iris/include/im.h +=================================================================== +--- iris/include/im.h (révision 498969) ++++ iris/include/im.h (copie de travail) +@@ -607,6 +607,7 @@ + FileTransferManager *fileTransferManager() const; + + bool groupChatJoin(const QString &host, const QString &room, const QString &nick); ++ bool groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &password); + void groupChatSetStatus(const QString &host, const QString &room, const Status &); + void groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &); + void groupChatLeave(const QString &host, const QString &room); +Index: iris/xmpp-im/client.cpp +=================================================================== +--- iris/xmpp-im/client.cpp (révision 498969) ++++ iris/xmpp-im/client.cpp (copie de travail) +@@ -315,6 +315,35 @@ + return true; + } + ++bool Client::groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &password) ++{ ++ Jid jid(room + "@" + host + "/" + nick); ++ for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end();) { ++ GroupChat &i = *it; ++ if(i.j.compare(jid, false)) { ++ // if this room is shutting down, then free it up ++ if(i.status == GroupChat::Closing) ++ it = d->groupChatList.remove(it); ++ else ++ return false; ++ } ++ else ++ ++it; ++ } ++ ++ debug(QString("Client: Joined: [%1]\n").arg(jid.full())); ++ GroupChat i; ++ i.j = jid; ++ i.status = GroupChat::Connecting; ++ d->groupChatList += i; ++ ++ JT_MucPresence *j = new JT_MucPresence(rootTask()); ++ j->pres(jid, Status(), password); ++ j->go(true); ++ ++ return true; ++} ++ + void Client::groupChatSetStatus(const QString &host, const QString &room, const Status &_s) + { + Jid jid(room + "@" + host); +Index: iris/xmpp-im/xmpp_tasks.h +=================================================================== +--- iris/xmpp-im/xmpp_tasks.h (révision 498969) ++++ iris/xmpp-im/xmpp_tasks.h (copie de travail) +@@ -439,6 +439,26 @@ + class Private; + Private *d; + }; ++ ++ class JT_MucPresence : public Task ++ { ++ Q_OBJECT ++ public: ++ JT_MucPresence(Task *parent); ++ ~JT_MucPresence(); ++ ++ void pres(const Status &); ++ void pres(const Jid &, const Status &, const QString &password); ++ ++ void onGo(); ++ ++ private: ++ QDomElement tag; ++ int type; ++ ++ class Private; ++ Private *d; ++ }; + } + + #endif +Index: iris/xmpp-im/xmpp_tasks.cpp +=================================================================== +--- iris/xmpp-im/xmpp_tasks.cpp (révision 498969) ++++ iris/xmpp-im/xmpp_tasks.cpp (copie de travail) +@@ -1956,3 +1956,75 @@ + return true; + } + ++//---------------------------------------------------------------------------- ++// JT_MucPresence ++//---------------------------------------------------------------------------- ++JT_MucPresence::JT_MucPresence(Task *parent) ++:Task(parent) ++{ ++ type = -1; ++} ++ ++JT_MucPresence::~JT_MucPresence() ++{ ++} ++ ++void JT_MucPresence::pres(const Status &s) ++{ ++ type = 0; ++ ++ tag = doc()->createElement("presence"); ++ if(!s.isAvailable()) { ++ tag.setAttribute("type", "unavailable"); ++ if(!s.status().isEmpty()) ++ tag.appendChild(textTag(doc(), "status", s.status())); ++ } ++ else { ++ if(s.isInvisible()) ++ tag.setAttribute("type", "invisible"); ++ ++ if(!s.show().isEmpty()) ++ tag.appendChild(textTag(doc(), "show", s.show())); ++ if(!s.status().isEmpty()) ++ tag.appendChild(textTag(doc(), "status", s.status())); ++ ++ tag.appendChild( textTag(doc(), "priority", QString("%1").arg(s.priority()) ) ); ++ ++ if(!s.keyID().isEmpty()) { ++ QDomElement x = textTag(doc(), "x", s.keyID()); ++ x.setAttribute("xmlns", "http://jabber.org/protocol/e2e"); ++ tag.appendChild(x); ++ } ++ if(!s.xsigned().isEmpty()) { ++ QDomElement x = textTag(doc(), "x", s.xsigned()); ++ x.setAttribute("xmlns", "jabber:x:signed"); ++ tag.appendChild(x); ++ } ++ ++ if(!s.capsNode().isEmpty() && !s.capsVersion().isEmpty()) { ++ QDomElement c = doc()->createElement("c"); ++ c.setAttribute("xmlns","http://jabber.org/protocol/caps"); ++ c.setAttribute("node",s.capsNode()); ++ c.setAttribute("ver",s.capsVersion()); ++ if (!s.capsExt().isEmpty()) ++ c.setAttribute("ext",s.capsExt()); ++ tag.appendChild(c); ++ } ++ } ++} ++ ++void JT_MucPresence::pres(const Jid &to, const Status &s, const QString &password) ++{ ++ pres(s); ++ tag.setAttribute("to", to.full()); ++ QDomElement x = textTag(doc(), "x", s.xsigned()); ++ x.setAttribute("xmlns", "http://jabber.org/protocol/muc"); ++ x.appendChild( textTag(doc(), "password", password.latin1()) ); ++ tag.appendChild(x); ++} ++ ++void JT_MucPresence::onGo() ++{ ++ send(tag); ++ setSuccess(); ++} diff --git a/kopete/protocols/jabber/libiris/006_private_storage.patch b/kopete/protocols/jabber/libiris/006_private_storage.patch new file mode 100644 index 00000000..288d24c5 --- /dev/null +++ b/kopete/protocols/jabber/libiris/006_private_storage.patch @@ -0,0 +1,130 @@ +Index: iris/xmpp-im/xmpp_tasks.h +=================================================================== +--- iris/xmpp-im/xmpp_tasks.h (revision 499691) ++++ iris/xmpp-im/xmpp_tasks.h (working copy) +@@ -459,6 +459,27 @@ + class Private; + Private *d; + }; ++ ++ class JT_PrivateStorage : public Task ++ { ++ Q_OBJECT ++ public: ++ JT_PrivateStorage(Task *parent); ++ ~JT_PrivateStorage(); ++ ++ void set(const QDomElement &); ++ void get(const QString &tag, const QString& xmlns); ++ ++ QDomElement element(); ++ ++ void onGo(); ++ bool take(const QDomElement &); ++ ++ private: ++ class Private; ++ Private *d; ++ }; ++ + } + + #endif +Index: iris/xmpp-im/xmpp_tasks.cpp +=================================================================== +--- iris/xmpp-im/xmpp_tasks.cpp (revision 499691) ++++ iris/xmpp-im/xmpp_tasks.cpp (working copy) +@@ -2028,3 +2028,93 @@ + send(tag); + setSuccess(); + } ++ ++ ++//---------------------------------------------------------------------------- ++// JT_PrivateStorage ++//---------------------------------------------------------------------------- ++class JT_PrivateStorage::Private ++{ ++ public: ++ Private() : type(-1) {} ++ ++ QDomElement iq; ++ QDomElement elem; ++ int type; ++}; ++ ++JT_PrivateStorage::JT_PrivateStorage(Task *parent) ++ :Task(parent) ++{ ++ d = new Private; ++} ++ ++JT_PrivateStorage::~JT_PrivateStorage() ++{ ++ delete d; ++} ++ ++void JT_PrivateStorage::get(const QString& tag, const QString& xmlns) ++{ ++ d->type = 0; ++ d->iq = createIQ(doc(), "get" , QString() , id() ); ++ QDomElement query = doc()->createElement("query"); ++ query.setAttribute("xmlns", "jabber:iq:private"); ++ d->iq.appendChild(query); ++ QDomElement s = doc()->createElement(tag); ++ if(!xmlns.isEmpty()) ++ s.setAttribute("xmlns", xmlns); ++ query.appendChild(s); ++} ++ ++void JT_PrivateStorage::set(const QDomElement& element) ++{ ++ d->type = 1; ++ d->elem=element; ++ QDomNode n=doc()->importNode(element,true); ++ ++ d->iq = createIQ(doc(), "set" , QString() , id() ); ++ QDomElement query = doc()->createElement("query"); ++ query.setAttribute("xmlns", "jabber:iq:private"); ++ d->iq.appendChild(query); ++ query.appendChild(n); ++} ++ ++void JT_PrivateStorage::onGo() ++{ ++ send(d->iq); ++} ++ ++bool JT_PrivateStorage::take(const QDomElement &x) ++{ ++ QString to = client()->host(); ++ if(!iqVerify(x, to, id())) ++ return false; ++ ++ if(x.attribute("type") == "result") { ++ if(d->type == 0) { ++ QDomElement q = queryTag(x); ++ for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { ++ QDomElement i = n.toElement(); ++ if(i.isNull()) ++ continue; ++ d->elem=i; ++ break; ++ } ++ } ++ setSuccess(); ++ return true; ++ } ++ else { ++ setError(x); ++ } ++ ++ return true; ++} ++ ++ ++QDomElement JT_PrivateStorage::element( ) ++{ ++ return d->elem; ++} ++ diff --git a/kopete/protocols/jabber/libiris/007_chatstates.patch b/kopete/protocols/jabber/libiris/007_chatstates.patch new file mode 100644 index 00000000..af32728c --- /dev/null +++ b/kopete/protocols/jabber/libiris/007_chatstates.patch @@ -0,0 +1,132 @@ +Index: iris/include/im.h +=================================================================== +--- iris/include/im.h (revision 525193) ++++ iris/include/im.h (working copy) +@@ -49,7 +49,7 @@ + typedef QValueList UrlList; + typedef QMap StringMap; + typedef enum { OfflineEvent, DeliveredEvent, DisplayedEvent, +- ComposingEvent, CancelEvent } MsgEvent; ++ ComposingEvent, CancelEvent, InactiveEvent, GoneEvent } MsgEvent; + + class Message + { +Index: iris/xmpp-im/types.cpp +=================================================================== +--- iris/xmpp-im/types.cpp (revision 525193) ++++ iris/xmpp-im/types.cpp (working copy) +@@ -544,28 +544,49 @@ + else + x.appendChild(s.createTextElement("jabber:x:event","id",d->eventId)); + } ++ else ++ s.appendChild( s.createElement(NS_CHATSTATES , "active" ) ); + ++ bool need_x_event=false; + for(QValueList::ConstIterator ev = d->eventList.begin(); ev != d->eventList.end(); ++ev) { + switch (*ev) { + case OfflineEvent: + x.appendChild(s.createElement("jabber:x:event", "offline")); ++ need_x_event=true; + break; + case DeliveredEvent: + x.appendChild(s.createElement("jabber:x:event", "delivered")); ++ need_x_event=true; + break; + case DisplayedEvent: + x.appendChild(s.createElement("jabber:x:event", "displayed")); ++ need_x_event=true; + break; + case ComposingEvent: + x.appendChild(s.createElement("jabber:x:event", "composing")); ++ need_x_event=true; ++ if (d->body.isEmpty()) ++ s.appendChild( s.createElement(NS_CHATSTATES , "composing" ) ); + break; + case CancelEvent: +- // Add nothing ++ need_x_event=true; ++ if (d->body.isEmpty()) ++ s.appendChild( s.createElement(NS_CHATSTATES , "paused" ) ); + break; ++ case InactiveEvent: ++ if (d->body.isEmpty()) ++ s.appendChild( s.createElement(NS_CHATSTATES , "inactive" ) ); ++ break; ++ case GoneEvent: ++ if (d->body.isEmpty()) ++ s.appendChild( s.createElement(NS_CHATSTATES , "gone" ) ); ++ break; + } + } +- s.appendChild(x); +- } ++ if(need_x_event) //we don't need to have the (empty) x:event element if this is only or ++ s.appendChild(x); ++ } ++ + + // xencrypted + if(!d->xencrypted.isEmpty()) +@@ -595,6 +616,7 @@ + d->subject.clear(); + d->body.clear(); + d->thread = QString(); ++ d->eventList.clear(); + + QDomElement root = s.element(); + +@@ -631,6 +653,33 @@ + } + } + } ++ else if (e.namespaceURI() == NS_CHATSTATES) ++ { ++ if(e.tagName() == "active") ++ { ++ //like in JEP-0022 we let the client know that we can receive ComposingEvent ++ // (we can do that according to §4.6 of the JEP-0085) ++ d->eventList += ComposingEvent; ++ d->eventList += InactiveEvent; ++ d->eventList += GoneEvent; ++ } ++ else if (e.tagName() == "composing") ++ { ++ d->eventList += ComposingEvent; ++ } ++ else if (e.tagName() == "paused") ++ { ++ d->eventList += CancelEvent; ++ } ++ else if (e.tagName() == "inactive") ++ { ++ d->eventList += InactiveEvent; ++ } ++ else if (e.tagName() == "gone") ++ { ++ d->eventList += GoneEvent; ++ } ++ } + else { + //printf("extension element: [%s]\n", e.tagName().latin1()); + } +@@ -664,7 +713,6 @@ + } + + // events +- d->eventList.clear(); + nl = root.elementsByTagNameNS("jabber:x:event", "x"); + if (nl.count()) { + nl = nl.item(0).childNodes(); +Index: iris/xmpp-core/protocol.h +=================================================================== +--- iris/xmpp-core/protocol.h (revision 525193) ++++ iris/xmpp-core/protocol.h (working copy) +@@ -37,6 +37,7 @@ + #define NS_BIND "urn:ietf:params:xml:ns:xmpp-bind" + #define NS_XHTML_IM "http://jabber.org/protocol/xhtml-im" + #define NS_XHTML "http://www.w3.org/1999/xhtml" ++#define NS_CHATSTATES "http://jabber.org/protocol/chatstates" + + namespace XMPP + { diff --git a/kopete/protocols/jabber/libiris/008_chatstatesfix.patch b/kopete/protocols/jabber/libiris/008_chatstatesfix.patch new file mode 100644 index 00000000..63a4f680 --- /dev/null +++ b/kopete/protocols/jabber/libiris/008_chatstatesfix.patch @@ -0,0 +1,38 @@ +Index: iris/xmpp-im/types.cpp +=================================================================== +--- iris/xmpp-im/types.cpp (revision 526236) ++++ iris/xmpp-im/types.cpp (working copy) +@@ -544,7 +544,7 @@ + else + x.appendChild(s.createTextElement("jabber:x:event","id",d->eventId)); + } +- else ++ else if (d->type=="chat" || d->type=="groupchat") + s.appendChild( s.createElement(NS_CHATSTATES , "active" ) ); + + bool need_x_event=false; +@@ -565,20 +565,20 @@ + case ComposingEvent: + x.appendChild(s.createElement("jabber:x:event", "composing")); + need_x_event=true; +- if (d->body.isEmpty()) ++ if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "composing" ) ); + break; + case CancelEvent: + need_x_event=true; +- if (d->body.isEmpty()) ++ if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "paused" ) ); + break; + case InactiveEvent: +- if (d->body.isEmpty()) ++ if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "inactive" ) ); + break; + case GoneEvent: +- if (d->body.isEmpty()) ++ if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "gone" ) ); + break; + } diff --git a/kopete/protocols/jabber/libiris/Makefile.am b/kopete/protocols/jabber/libiris/Makefile.am new file mode 100644 index 00000000..a80d204c --- /dev/null +++ b/kopete/protocols/jabber/libiris/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS = iris qca cutestuff + diff --git a/kopete/protocols/jabber/libiris/README_BEFORE_COMMITTING b/kopete/protocols/jabber/libiris/README_BEFORE_COMMITTING new file mode 100644 index 00000000..1fd42d3a --- /dev/null +++ b/kopete/protocols/jabber/libiris/README_BEFORE_COMMITTING @@ -0,0 +1,21 @@ +This library is the xmpp backend also used in Psi. (http://psi.affinix.com) +The main author is Justin Karneges (infiniti@affinix.com) and other +Psi developers, see the Psi homepage for details. + +Please DO NOT change the source unless really necessary. This is a +third-party library and any change will make synching very hard in the +future. It is best to send patches upstream so they'll end up in the +main tree. We will benefit from them at the next synch point. + +If you really really need to make a change to one of the source files, +please make sure to commit a diff to the original file in this directory in +the form of 001_your_fix_name.patch. Always pick the next free number +for your patch, the version found in this directory is meant to have +all patches applied in order. When committing, CCMAIL kopete-devel@kde.org. + +Changes to the Makefile.am files are fine and require no diffs, since Psi +uses qmake. + +This library depends on: libidn (compile time), qca-tls (runtime) + +27.02.2004, Till Gerken (till@tantalo.net) diff --git a/kopete/protocols/jabber/libiris/cutestuff/Makefile.am b/kopete/protocols/jabber/libiris/cutestuff/Makefile.am new file mode 100644 index 00000000..8f579310 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = network util diff --git a/kopete/protocols/jabber/libiris/cutestuff/README b/kopete/protocols/jabber/libiris/cutestuff/README new file mode 100644 index 00000000..c4509acc --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/README @@ -0,0 +1,13 @@ +iconset - generic classes for handling iconsets / animations +idle - detecting desktop idle +input - making life easier with text input (including richtext) +openpgp - pgp/gpg classes +richtext - richtext parsing function, xhtml conversion +ssl - SSL +tray - desktop tray icon +util - various things, see util/TODO +globalaccel - global hotkeys +network - sockets, servers, dns, and proxies +sasl - SASL library +xmlsec - XML Encryption +crash - generates some (hopefully useful) feedback when program crashes diff --git a/kopete/protocols/jabber/libiris/cutestuff/TODO b/kopete/protocols/jabber/libiris/cutestuff/TODO new file mode 100644 index 00000000..e897c854 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/TODO @@ -0,0 +1,25 @@ +test: + httppoll + sasl + xmlenc + +code: + bsocket: 'maintain' internal sockets even after destruct (till flush) + qssl: server support + securestream: wrap QSSLFilter as ByteStream + qrandom: better randomness (use /dev/urandom on unix, srand on windows) + floating TODOs in gnupg, gpgproc ? + import misha's code + finish globalaccel + finish dirwatch + trayicon? + +port: + win32: bconsole + +document: + sha1 + servsock + srvresolver + bsocket + diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/Makefile.am b/kopete/protocols/jabber/libiris/cutestuff/network/Makefile.am new file mode 100644 index 00000000..5e370089 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/Makefile.am @@ -0,0 +1,16 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libcutestuff_network.la +INCLUDES = -I$(srcdir)/../util -I$(srcdir)/../../qca/src $(all_includes) + +libcutestuff_network_la_SOURCES = \ + bsocket.cpp \ + httpconnect.cpp \ + httppoll.cpp \ + ndns.cpp \ + servsock.cpp \ + socks.cpp \ + srvresolver.cpp + +KDE_OPTIONS = nofinal + diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/bsocket.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/bsocket.cpp new file mode 100644 index 00000000..57e5fe66 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/bsocket.cpp @@ -0,0 +1,394 @@ +/* + * bsocket.cpp - QSocket wrapper based on Bytestream with SRV DNS support + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"bsocket.h" + +#include +#include +#include +#include +#include"safedelete.h" +#ifndef NO_NDNS +#include"ndns.h" +#endif +#include"srvresolver.h" + +#ifdef BS_DEBUG +#include +#endif + +#define READBUFSIZE 65536 + +// CS_NAMESPACE_BEGIN + +class BSocket::Private +{ +public: + Private() + { + qsock = 0; + } + + QSocket *qsock; + int state; + +#ifndef NO_NDNS + NDns ndns; +#endif + SrvResolver srv; + QString host; + int port; + SafeDelete sd; +}; + +BSocket::BSocket(QObject *parent) +:ByteStream(parent) +{ + d = new Private; +#ifndef NO_NDNS + connect(&d->ndns, SIGNAL(resultsReady()), SLOT(ndns_done())); +#endif + connect(&d->srv, SIGNAL(resultsReady()), SLOT(srv_done())); + + reset(); +} + +BSocket::~BSocket() +{ + reset(true); + delete d; +} + +void BSocket::reset(bool clear) +{ + if(d->qsock) { + d->qsock->disconnect(this); + + if(!clear && d->qsock->isOpen()) { + // move remaining into the local queue + QByteArray block(d->qsock->bytesAvailable()); + d->qsock->readBlock(block.data(), block.size()); + appendRead(block); + } + + d->sd.deleteLater(d->qsock); + d->qsock = 0; + } + else { + if(clear) + clearReadBuffer(); + } + + if(d->srv.isBusy()) + d->srv.stop(); +#ifndef NO_NDNS + if(d->ndns.isBusy()) + d->ndns.stop(); +#endif + d->state = Idle; +} + +void BSocket::ensureSocket() +{ + if(!d->qsock) { + d->qsock = new QSocket; +#if QT_VERSION >= 0x030200 + d->qsock->setReadBufferSize(READBUFSIZE); +#endif + connect(d->qsock, SIGNAL(hostFound()), SLOT(qs_hostFound())); + connect(d->qsock, SIGNAL(connected()), SLOT(qs_connected())); + connect(d->qsock, SIGNAL(connectionClosed()), SLOT(qs_connectionClosed())); + connect(d->qsock, SIGNAL(delayedCloseFinished()), SLOT(qs_delayedCloseFinished())); + connect(d->qsock, SIGNAL(readyRead()), SLOT(qs_readyRead())); + connect(d->qsock, SIGNAL(bytesWritten(int)), SLOT(qs_bytesWritten(int))); + connect(d->qsock, SIGNAL(error(int)), SLOT(qs_error(int))); + } +} + +void BSocket::connectToHost(const QString &host, Q_UINT16 port) +{ + reset(true); + d->host = host; + d->port = port; +#ifdef NO_NDNS + d->state = Connecting; + do_connect(); +#else + d->state = HostLookup; + d->ndns.resolve(d->host); +#endif +} + +void BSocket::connectToServer(const QString &srv, const QString &type) +{ + reset(true); + d->state = HostLookup; + d->srv.resolve(srv, type, "tcp"); +} + +int BSocket::socket() const +{ + if(d->qsock) + return d->qsock->socket(); + else + return -1; +} + +void BSocket::setSocket(int s) +{ + reset(true); + ensureSocket(); + d->state = Connected; + d->qsock->setSocket(s); +} + +int BSocket::state() const +{ + return d->state; +} + +bool BSocket::isOpen() const +{ + if(d->state == Connected) + return true; + else + return false; +} + +void BSocket::close() +{ + if(d->state == Idle) + return; + + if(d->qsock) { + d->qsock->close(); + d->state = Closing; + if(d->qsock->bytesToWrite() == 0) + reset(); + } + else { + reset(); + } +} + +void BSocket::write(const QByteArray &a) +{ + if(d->state != Connected) + return; +#ifdef BS_DEBUG + QCString cs; + cs.resize(a.size()+1); + memcpy(cs.data(), a.data(), a.size()); + QString s = QString::fromUtf8(cs); + fprintf(stderr, "BSocket: writing [%d]: {%s}\n", a.size(), cs.data()); +#endif + d->qsock->writeBlock(a.data(), a.size()); +} + +QByteArray BSocket::read(int bytes) +{ + QByteArray block; + if(d->qsock) { + int max = bytesAvailable(); + if(bytes <= 0 || bytes > max) + bytes = max; + block.resize(bytes); + d->qsock->readBlock(block.data(), block.size()); + } + else + block = ByteStream::read(bytes); + +#ifdef BS_DEBUG + QCString cs; + cs.resize(block.size()+1); + memcpy(cs.data(), block.data(), block.size()); + QString s = QString::fromUtf8(cs); + fprintf(stderr, "BSocket: read [%d]: {%s}\n", block.size(), s.latin1()); +#endif + return block; +} + +int BSocket::bytesAvailable() const +{ + if(d->qsock) + return d->qsock->bytesAvailable(); + else + return ByteStream::bytesAvailable(); +} + +int BSocket::bytesToWrite() const +{ + if(!d->qsock) + return 0; + return d->qsock->bytesToWrite(); +} + +QHostAddress BSocket::address() const +{ + if(d->qsock) + return d->qsock->address(); + else + return QHostAddress(); +} + +Q_UINT16 BSocket::port() const +{ + if(d->qsock) + return d->qsock->port(); + else + return 0; +} + +QHostAddress BSocket::peerAddress() const +{ + if(d->qsock) + return d->qsock->peerAddress(); + else + return QHostAddress(); +} + +Q_UINT16 BSocket::peerPort() const +{ + if(d->qsock) + return d->qsock->port(); + else + return 0; +} + +void BSocket::srv_done() +{ + if(d->srv.failed()) { +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Error resolving hostname.\n"); +#endif + error(ErrHostNotFound); + return; + } + + d->host = d->srv.resultAddress().toString(); + d->port = d->srv.resultPort(); + do_connect(); + //QTimer::singleShot(0, this, SLOT(do_connect())); + //hostFound(); +} + +void BSocket::ndns_done() +{ +#ifndef NO_NDNS + if(d->ndns.result()) { + d->host = d->ndns.resultString(); + d->state = Connecting; + do_connect(); + //QTimer::singleShot(0, this, SLOT(do_connect())); + //hostFound(); + } + else { +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Error resolving hostname.\n"); +#endif + error(ErrHostNotFound); + } +#endif +} + +void BSocket::do_connect() +{ +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Connecting to %s:%d\n", d->host.latin1(), d->port); +#endif + ensureSocket(); + d->qsock->connectToHost(d->host, d->port); +} + +void BSocket::qs_hostFound() +{ + //SafeDeleteLock s(&d->sd); +} + +void BSocket::qs_connected() +{ + d->state = Connected; +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Connected.\n"); +#endif + SafeDeleteLock s(&d->sd); + connected(); +} + +void BSocket::qs_connectionClosed() +{ +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Connection Closed.\n"); +#endif + SafeDeleteLock s(&d->sd); + reset(); + connectionClosed(); +} + +void BSocket::qs_delayedCloseFinished() +{ +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Delayed Close Finished.\n"); +#endif + SafeDeleteLock s(&d->sd); + reset(); + delayedCloseFinished(); +} + +void BSocket::qs_readyRead() +{ + SafeDeleteLock s(&d->sd); + readyRead(); +} + +void BSocket::qs_bytesWritten(int x) +{ +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: BytesWritten [%d].\n", x); +#endif + SafeDeleteLock s(&d->sd); + bytesWritten(x); +} + +void BSocket::qs_error(int x) +{ +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Error.\n"); +#endif + SafeDeleteLock s(&d->sd); + + // connection error during SRV host connect? try next + if(d->state == HostLookup && (x == QSocket::ErrConnectionRefused || x == QSocket::ErrHostNotFound)) { + d->srv.next(); + return; + } + + reset(); + if(x == QSocket::ErrConnectionRefused) + error(ErrConnectionRefused); + else if(x == QSocket::ErrHostNotFound) + error(ErrHostNotFound); + else if(x == QSocket::ErrSocketRead) + error(ErrRead); +} + +// CS_NAMESPACE_END + +#include "bsocket.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/bsocket.h b/kopete/protocols/jabber/libiris/cutestuff/network/bsocket.h new file mode 100644 index 00000000..bedaa54e --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/bsocket.h @@ -0,0 +1,87 @@ +/* + * bsocket.h - QSocket wrapper based on Bytestream with SRV DNS support + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_BSOCKET_H +#define CS_BSOCKET_H + +#include +#include +#include"bytestream.h" + +// CS_NAMESPACE_BEGIN + +class BSocket : public ByteStream +{ + Q_OBJECT +public: + enum Error { ErrConnectionRefused = ErrCustom, ErrHostNotFound }; + enum State { Idle, HostLookup, Connecting, Connected, Closing }; + BSocket(QObject *parent=0); + ~BSocket(); + + void connectToHost(const QString &host, Q_UINT16 port); + void connectToServer(const QString &srv, const QString &type); + int socket() const; + void setSocket(int); + int state() const; + + // from ByteStream + bool isOpen() const; + void close(); + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + + // local + QHostAddress address() const; + Q_UINT16 port() const; + + // remote + QHostAddress peerAddress() const; + Q_UINT16 peerPort() const; + +signals: + void hostFound(); + void connected(); + +private slots: + void qs_hostFound(); + void qs_connected(); + void qs_connectionClosed(); + void qs_delayedCloseFinished(); + void qs_readyRead(); + void qs_bytesWritten(int); + void qs_error(int); + void srv_done(); + void ndns_done(); + void do_connect(); + +private: + class Private; + Private *d; + + void reset(bool clear=false); + void ensureSocket(); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.cpp new file mode 100644 index 00000000..c194324a --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.cpp @@ -0,0 +1,369 @@ +/* + * httpconnect.cpp - HTTP "CONNECT" proxy + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"httpconnect.h" + +#include +#include"bsocket.h" +#include"base64.h" + +#ifdef PROX_DEBUG +#include +#endif + +// CS_NAMESPACE_BEGIN + +static QString extractLine(QByteArray *buf, bool *found) +{ + // scan for newline + int n; + for(n = 0; n < (int)buf->size()-1; ++n) { + if(buf->at(n) == '\r' && buf->at(n+1) == '\n') { + QCString cstr; + cstr.resize(n+1); + memcpy(cstr.data(), buf->data(), n); + n += 2; // hack off CR/LF + + memmove(buf->data(), buf->data() + n, buf->size() - n); + buf->resize(buf->size() - n); + QString s = QString::fromUtf8(cstr); + + if(found) + *found = true; + return s; + } + } + + if(found) + *found = false; + return ""; +} + +static bool extractMainHeader(const QString &line, QString *proto, int *code, QString *msg) +{ + int n = line.find(' '); + if(n == -1) + return false; + if(proto) + *proto = line.mid(0, n); + ++n; + int n2 = line.find(' ', n); + if(n2 == -1) + return false; + if(code) + *code = line.mid(n, n2-n).toInt(); + n = n2+1; + if(msg) + *msg = line.mid(n); + return true; +} + +class HttpConnect::Private +{ +public: + Private() {} + + BSocket sock; + QString host; + int port; + QString user, pass; + QString real_host; + int real_port; + + QByteArray recvBuf; + + bool inHeader; + QStringList headerLines; + + int toWrite; + bool active; +}; + +HttpConnect::HttpConnect(QObject *parent) +:ByteStream(parent) +{ + d = new Private; + connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected())); + connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed())); + connect(&d->sock, SIGNAL(delayedCloseFinished()), SLOT(sock_delayedCloseFinished())); + connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); + connect(&d->sock, SIGNAL(bytesWritten(int)), SLOT(sock_bytesWritten(int))); + connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int))); + + reset(true); +} + +HttpConnect::~HttpConnect() +{ + reset(true); + delete d; +} + +void HttpConnect::reset(bool clear) +{ + if(d->sock.state() != BSocket::Idle) + d->sock.close(); + if(clear) { + clearReadBuffer(); + d->recvBuf.resize(0); + } + d->active = false; +} + +void HttpConnect::setAuth(const QString &user, const QString &pass) +{ + d->user = user; + d->pass = pass; +} + +void HttpConnect::connectToHost(const QString &proxyHost, int proxyPort, const QString &host, int port) +{ + reset(true); + + d->host = proxyHost; + d->port = proxyPort; + d->real_host = host; + d->real_port = port; + +#ifdef PROX_DEBUG + fprintf(stderr, "HttpConnect: Connecting to %s:%d", proxyHost.latin1(), proxyPort); + if(d->user.isEmpty()) + fprintf(stderr, "\n"); + else + fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1()); +#endif + d->sock.connectToHost(d->host, d->port); +} + +bool HttpConnect::isOpen() const +{ + return d->active; +} + +void HttpConnect::close() +{ + d->sock.close(); + if(d->sock.bytesToWrite() == 0) + reset(); +} + +void HttpConnect::write(const QByteArray &buf) +{ + if(d->active) + d->sock.write(buf); +} + +QByteArray HttpConnect::read(int bytes) +{ + return ByteStream::read(bytes); +} + +int HttpConnect::bytesAvailable() const +{ + return ByteStream::bytesAvailable(); +} + +int HttpConnect::bytesToWrite() const +{ + if(d->active) + return d->sock.bytesToWrite(); + else + return 0; +} + +void HttpConnect::sock_connected() +{ +#ifdef PROX_DEBUG + fprintf(stderr, "HttpConnect: Connected\n"); +#endif + d->inHeader = true; + d->headerLines.clear(); + + // connected, now send the request + QString s; + s += QString("CONNECT ") + d->real_host + ':' + QString::number(d->real_port) + " HTTP/1.0\r\n"; + if(!d->user.isEmpty()) { + QString str = d->user + ':' + d->pass; + s += QString("Proxy-Authorization: Basic ") + Base64::encodeString(str) + "\r\n"; + } + s += "Proxy-Connection: Keep-Alive\r\n"; + s += "Pragma: no-cache\r\n"; + s += "\r\n"; + + QCString cs = s.utf8(); + QByteArray block(cs.length()); + memcpy(block.data(), cs.data(), block.size()); + d->toWrite = block.size(); + d->sock.write(block); +} + +void HttpConnect::sock_connectionClosed() +{ + if(d->active) { + reset(); + connectionClosed(); + } + else { + error(ErrProxyNeg); + } +} + +void HttpConnect::sock_delayedCloseFinished() +{ + if(d->active) { + reset(); + delayedCloseFinished(); + } +} + +void HttpConnect::sock_readyRead() +{ + QByteArray block = d->sock.read(); + + if(!d->active) { + ByteStream::appendArray(&d->recvBuf, block); + + if(d->inHeader) { + // grab available lines + while(1) { + bool found; + QString line = extractLine(&d->recvBuf, &found); + if(!found) + break; + if(line.isEmpty()) { + d->inHeader = false; + break; + } + d->headerLines += line; + } + + // done with grabbing the header? + if(!d->inHeader) { + QString str = d->headerLines.first(); + d->headerLines.remove(d->headerLines.begin()); + + QString proto; + int code; + QString msg; + if(!extractMainHeader(str, &proto, &code, &msg)) { +#ifdef PROX_DEBUG + fprintf(stderr, "HttpConnect: invalid header!\n"); +#endif + reset(true); + error(ErrProxyNeg); + return; + } + else { +#ifdef PROX_DEBUG + fprintf(stderr, "HttpConnect: header proto=[%s] code=[%d] msg=[%s]\n", proto.latin1(), code, msg.latin1()); + for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it) + fprintf(stderr, "HttpConnect: * [%s]\n", (*it).latin1()); +#endif + } + + if(code == 200) { // OK +#ifdef PROX_DEBUG + fprintf(stderr, "HttpConnect: << Success >>\n"); +#endif + d->active = true; + connected(); + + if(!d->recvBuf.isEmpty()) { + appendRead(d->recvBuf); + d->recvBuf.resize(0); + readyRead(); + return; + } + } + else { + int err; + QString errStr; + if(code == 407) { // Authentication failed + err = ErrProxyAuth; + errStr = tr("Authentication failed"); + } + else if(code == 404) { // Host not found + err = ErrHostNotFound; + errStr = tr("Host not found"); + } + else if(code == 403) { // Access denied + err = ErrProxyNeg; + errStr = tr("Access denied"); + } + else if(code == 503) { // Connection refused + err = ErrConnectionRefused; + errStr = tr("Connection refused"); + } + else { // invalid reply + err = ErrProxyNeg; + errStr = tr("Invalid reply"); + } + +#ifdef PROX_DEBUG + fprintf(stderr, "HttpConnect: << Error >> [%s]\n", errStr.latin1()); +#endif + reset(true); + error(err); + return; + } + } + } + } + else { + appendRead(block); + readyRead(); + return; + } +} + +void HttpConnect::sock_bytesWritten(int x) +{ + if(d->toWrite > 0) { + int size = x; + if(d->toWrite < x) + size = d->toWrite; + d->toWrite -= size; + x -= size; + } + + if(d->active && x > 0) + bytesWritten(x); +} + +void HttpConnect::sock_error(int x) +{ + if(d->active) { + reset(); + error(ErrRead); + } + else { + reset(true); + if(x == BSocket::ErrHostNotFound) + error(ErrProxyConnect); + else if(x == BSocket::ErrConnectionRefused) + error(ErrProxyConnect); + else if(x == BSocket::ErrRead) + error(ErrProxyNeg); + } +} + +// CS_NAMESPACE_END + +#include "httpconnect.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.h b/kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.h new file mode 100644 index 00000000..38129c60 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.h @@ -0,0 +1,67 @@ +/* + * httpconnect.h - HTTP "CONNECT" proxy + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_HTTPCONNECT_H +#define CS_HTTPCONNECT_H + +#include"bytestream.h" + +// CS_NAMESPACE_BEGIN + +class HttpConnect : public ByteStream +{ + Q_OBJECT +public: + enum Error { ErrConnectionRefused = ErrCustom, ErrHostNotFound, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth }; + HttpConnect(QObject *parent=0); + ~HttpConnect(); + + void setAuth(const QString &user, const QString &pass=""); + void connectToHost(const QString &proxyHost, int proxyPort, const QString &host, int port); + + // from ByteStream + bool isOpen() const; + void close(); + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + +signals: + void connected(); + +private slots: + void sock_connected(); + void sock_connectionClosed(); + void sock_delayedCloseFinished(); + void sock_readyRead(); + void sock_bytesWritten(int); + void sock_error(int); + +private: + class Private; + Private *d; + + void reset(bool clear=false); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/httppoll.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/httppoll.cpp new file mode 100644 index 00000000..4975d0e5 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/httppoll.cpp @@ -0,0 +1,666 @@ +/* + * httppoll.cpp - HTTP polling proxy + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"httppoll.h" + +#include +#include +#include +#include +#include +#include +#include"bsocket.h" +#include"base64.h" + +#ifdef PROX_DEBUG +#include +#endif + +#define POLL_KEYS 64 + +// CS_NAMESPACE_BEGIN + +static QByteArray randomArray(int size) +{ + QByteArray a(size); + for(int n = 0; n < size; ++n) + a[n] = (char)(256.0*rand()/(RAND_MAX+1.0)); + return a; +} + +//---------------------------------------------------------------------------- +// HttpPoll +//---------------------------------------------------------------------------- +static QString hpk(int n, const QString &s) +{ + if(n == 0) + return s; + else + return Base64::arrayToString( QCA::SHA1::hash( QCString(hpk(n - 1, s).latin1()) ) ); +} + +class HttpPoll::Private +{ +public: + Private() {} + + HttpProxyPost http; + QString host; + int port; + QString user, pass; + QString url; + bool use_proxy; + + QByteArray out; + + int state; + bool closing; + QString ident; + + QTimer *t; + + QString key[POLL_KEYS]; + int key_n; + + int polltime; +}; + +HttpPoll::HttpPoll(QObject *parent) +:ByteStream(parent) +{ + d = new Private; + + d->polltime = 30; + d->t = new QTimer; + connect(d->t, SIGNAL(timeout()), SLOT(do_sync())); + + connect(&d->http, SIGNAL(result()), SLOT(http_result())); + connect(&d->http, SIGNAL(error(int)), SLOT(http_error(int))); + + reset(true); +} + +HttpPoll::~HttpPoll() +{ + reset(true); + delete d->t; + delete d; +} + +void HttpPoll::reset(bool clear) +{ + if(d->http.isActive()) + d->http.stop(); + if(clear) + clearReadBuffer(); + clearWriteBuffer(); + d->out.resize(0); + d->state = 0; + d->closing = false; + d->t->stop(); +} + +void HttpPoll::setAuth(const QString &user, const QString &pass) +{ + d->user = user; + d->pass = pass; +} + +void HttpPoll::connectToUrl(const QString &url) +{ + connectToHost("", 0, url); +} + +void HttpPoll::connectToHost(const QString &proxyHost, int proxyPort, const QString &url) +{ + reset(true); + + // using proxy? + if(!proxyHost.isEmpty()) { + d->host = proxyHost; + d->port = proxyPort; + d->url = url; + d->use_proxy = true; + } + else { + QUrl u = url; + d->host = u.host(); + if(u.hasPort()) + d->port = u.port(); + else + d->port = 80; + d->url = u.encodedPathAndQuery(); + d->use_proxy = false; + } + + resetKey(); + bool last; + QString key = getKey(&last); + +#ifdef PROX_DEBUG + fprintf(stderr, "HttpPoll: Connecting to %s:%d [%s]", d->host.latin1(), d->port, d->url.latin1()); + if(d->user.isEmpty()) + fprintf(stderr, "\n"); + else + fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1()); +#endif + QGuardedPtr self = this; + syncStarted(); + if(!self) + return; + + d->state = 1; + d->http.setAuth(d->user, d->pass); + d->http.post(d->host, d->port, d->url, makePacket("0", key, "", QByteArray()), d->use_proxy); +} + +QByteArray HttpPoll::makePacket(const QString &ident, const QString &key, const QString &newkey, const QByteArray &block) +{ + QString str = ident; + if(!key.isEmpty()) { + str += ';'; + str += key; + } + if(!newkey.isEmpty()) { + str += ';'; + str += newkey; + } + str += ','; + QCString cs = str.latin1(); + int len = cs.length(); + + QByteArray a(len + block.size()); + memcpy(a.data(), cs.data(), len); + memcpy(a.data() + len, block.data(), block.size()); + return a; +} + +int HttpPoll::pollInterval() const +{ + return d->polltime; +} + +void HttpPoll::setPollInterval(int seconds) +{ + d->polltime = seconds; +} + +bool HttpPoll::isOpen() const +{ + return (d->state == 2 ? true: false); +} + +void HttpPoll::close() +{ + if(d->state == 0 || d->closing) + return; + + if(bytesToWrite() == 0) + reset(); + else + d->closing = true; +} + +void HttpPoll::http_result() +{ + // check for death :) + QGuardedPtr self = this; + syncFinished(); + if(!self) + return; + + // get id and packet + QString id; + QString cookie = d->http.getHeader("Set-Cookie"); + int n = cookie.find("ID="); + if(n == -1) { + reset(); + error(ErrRead); + return; + } + n += 3; + int n2 = cookie.find(';', n); + if(n2 != -1) + id = cookie.mid(n, n2-n); + else + id = cookie.mid(n); + QByteArray block = d->http.body(); + + // session error? + if(id.right(2) == ":0") { + if(id == "0:0" && d->state == 2) { + reset(); + connectionClosed(); + return; + } + else { + reset(); + error(ErrRead); + return; + } + } + + d->ident = id; + bool justNowConnected = false; + if(d->state == 1) { + d->state = 2; + justNowConnected = true; + } + + // sync up again soon + if(bytesToWrite() > 0 || !d->closing) + d->t->start(d->polltime * 1000, true); + + // connecting + if(justNowConnected) { + connected(); + } + else { + if(!d->out.isEmpty()) { + int x = d->out.size(); + d->out.resize(0); + takeWrite(x); + bytesWritten(x); + } + } + + if(!self) + return; + + if(!block.isEmpty()) { + appendRead(block); + readyRead(); + } + + if(!self) + return; + + if(bytesToWrite() > 0) { + do_sync(); + } + else { + if(d->closing) { + reset(); + delayedCloseFinished(); + return; + } + } +} + +void HttpPoll::http_error(int x) +{ + reset(); + if(x == HttpProxyPost::ErrConnectionRefused) + error(ErrConnectionRefused); + else if(x == HttpProxyPost::ErrHostNotFound) + error(ErrHostNotFound); + else if(x == HttpProxyPost::ErrSocket) + error(ErrRead); + else if(x == HttpProxyPost::ErrProxyConnect) + error(ErrProxyConnect); + else if(x == HttpProxyPost::ErrProxyNeg) + error(ErrProxyNeg); + else if(x == HttpProxyPost::ErrProxyAuth) + error(ErrProxyAuth); +} + +int HttpPoll::tryWrite() +{ + if(!d->http.isActive()) + do_sync(); + return 0; +} + +void HttpPoll::do_sync() +{ + if(d->http.isActive()) + return; + + d->t->stop(); + d->out = takeWrite(0, false); + + bool last; + QString key = getKey(&last); + QString newkey; + if(last) { + resetKey(); + newkey = getKey(&last); + } + + QGuardedPtr self = this; + syncStarted(); + if(!self) + return; + + d->http.post(d->host, d->port, d->url, makePacket(d->ident, key, newkey, d->out), d->use_proxy); +} + +void HttpPoll::resetKey() +{ +#ifdef PROX_DEBUG + fprintf(stderr, "HttpPoll: reset key!\n"); +#endif + QByteArray a = randomArray(64); + QString str = QString::fromLatin1(a.data(), a.size()); + + d->key_n = POLL_KEYS; + for(int n = 0; n < POLL_KEYS; ++n) + d->key[n] = hpk(n+1, str); +} + +const QString & HttpPoll::getKey(bool *last) +{ + *last = false; + --(d->key_n); + if(d->key_n == 0) + *last = true; + return d->key[d->key_n]; +} + + +//---------------------------------------------------------------------------- +// HttpProxyPost +//---------------------------------------------------------------------------- +static QString extractLine(QByteArray *buf, bool *found) +{ + // scan for newline + int n; + for(n = 0; n < (int)buf->size()-1; ++n) { + if(buf->at(n) == '\r' && buf->at(n+1) == '\n') { + QCString cstr; + cstr.resize(n+1); + memcpy(cstr.data(), buf->data(), n); + n += 2; // hack off CR/LF + + memmove(buf->data(), buf->data() + n, buf->size() - n); + buf->resize(buf->size() - n); + QString s = QString::fromUtf8(cstr); + + if(found) + *found = true; + return s; + } + } + + if(found) + *found = false; + return ""; +} + +static bool extractMainHeader(const QString &line, QString *proto, int *code, QString *msg) +{ + int n = line.find(' '); + if(n == -1) + return false; + if(proto) + *proto = line.mid(0, n); + ++n; + int n2 = line.find(' ', n); + if(n2 == -1) + return false; + if(code) + *code = line.mid(n, n2-n).toInt(); + n = n2+1; + if(msg) + *msg = line.mid(n); + return true; +} + +class HttpProxyPost::Private +{ +public: + Private() {} + + BSocket sock; + QByteArray postdata, recvBuf, body; + QString url; + QString user, pass; + bool inHeader; + QStringList headerLines; + bool asProxy; + QString host; +}; + +HttpProxyPost::HttpProxyPost(QObject *parent) +:QObject(parent) +{ + d = new Private; + connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected())); + connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed())); + connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); + connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int))); + reset(true); +} + +HttpProxyPost::~HttpProxyPost() +{ + reset(true); + delete d; +} + +void HttpProxyPost::reset(bool clear) +{ + if(d->sock.state() != BSocket::Idle) + d->sock.close(); + d->recvBuf.resize(0); + if(clear) + d->body.resize(0); +} + +void HttpProxyPost::setAuth(const QString &user, const QString &pass) +{ + d->user = user; + d->pass = pass; +} + +bool HttpProxyPost::isActive() const +{ + return (d->sock.state() == BSocket::Idle ? false: true); +} + +void HttpProxyPost::post(const QString &proxyHost, int proxyPort, const QString &url, const QByteArray &data, bool asProxy) +{ + reset(true); + + d->host = proxyHost; + d->url = url; + d->postdata = data; + d->asProxy = asProxy; + +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: Connecting to %s:%d", proxyHost.latin1(), proxyPort); + if(d->user.isEmpty()) + fprintf(stderr, "\n"); + else + fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1()); +#endif + d->sock.connectToHost(proxyHost, proxyPort); +} + +void HttpProxyPost::stop() +{ + reset(); +} + +QByteArray HttpProxyPost::body() const +{ + return d->body; +} + +QString HttpProxyPost::getHeader(const QString &var) const +{ + for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it) { + const QString &s = *it; + int n = s.find(": "); + if(n == -1) + continue; + QString v = s.mid(0, n); + if(v == var) + return s.mid(n+2); + } + return ""; +} + +void HttpProxyPost::sock_connected() +{ +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: Connected\n"); +#endif + d->inHeader = true; + d->headerLines.clear(); + + QUrl u = d->url; + + // connected, now send the request + QString s; + s += QString("POST ") + d->url + " HTTP/1.0\r\n"; + if(d->asProxy) { + if(!d->user.isEmpty()) { + QString str = d->user + ':' + d->pass; + s += QString("Proxy-Authorization: Basic ") + Base64::encodeString(str) + "\r\n"; + } + s += "Proxy-Connection: Keep-Alive\r\n"; + s += "Pragma: no-cache\r\n"; + s += QString("Host: ") + u.host() + "\r\n"; + } + else { + s += QString("Host: ") + d->host + "\r\n"; + } + s += "Content-Type: application/x-www-form-urlencoded\r\n"; + s += QString("Content-Length: ") + QString::number(d->postdata.size()) + "\r\n"; + s += "\r\n"; + + // write request + QCString cs = s.utf8(); + QByteArray block(cs.length()); + memcpy(block.data(), cs.data(), block.size()); + d->sock.write(block); + + // write postdata + d->sock.write(d->postdata); +} + +void HttpProxyPost::sock_connectionClosed() +{ + d->body = d->recvBuf.copy(); + reset(); + result(); +} + +void HttpProxyPost::sock_readyRead() +{ + QByteArray block = d->sock.read(); + ByteStream::appendArray(&d->recvBuf, block); + + if(d->inHeader) { + // grab available lines + while(1) { + bool found; + QString line = extractLine(&d->recvBuf, &found); + if(!found) + break; + if(line.isEmpty()) { + d->inHeader = false; + break; + } + d->headerLines += line; + } + + // done with grabbing the header? + if(!d->inHeader) { + QString str = d->headerLines.first(); + d->headerLines.remove(d->headerLines.begin()); + + QString proto; + int code; + QString msg; + if(!extractMainHeader(str, &proto, &code, &msg)) { +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: invalid header!\n"); +#endif + reset(true); + error(ErrProxyNeg); + return; + } + else { +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: header proto=[%s] code=[%d] msg=[%s]\n", proto.latin1(), code, msg.latin1()); + for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it) + fprintf(stderr, "HttpProxyPost: * [%s]\n", (*it).latin1()); +#endif + } + + if(code == 200) { // OK +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: << Success >>\n"); +#endif + } + else { + int err; + QString errStr; + if(code == 407) { // Authentication failed + err = ErrProxyAuth; + errStr = tr("Authentication failed"); + } + else if(code == 404) { // Host not found + err = ErrHostNotFound; + errStr = tr("Host not found"); + } + else if(code == 403) { // Access denied + err = ErrProxyNeg; + errStr = tr("Access denied"); + } + else if(code == 503) { // Connection refused + err = ErrConnectionRefused; + errStr = tr("Connection refused"); + } + else { // invalid reply + err = ErrProxyNeg; + errStr = tr("Invalid reply"); + } + +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: << Error >> [%s]\n", errStr.latin1()); +#endif + reset(true); + error(err); + return; + } + } + } +} + +void HttpProxyPost::sock_error(int x) +{ +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: socket error: %d\n", x); +#endif + reset(true); + if(x == BSocket::ErrHostNotFound) + error(ErrProxyConnect); + else if(x == BSocket::ErrConnectionRefused) + error(ErrProxyConnect); + else if(x == BSocket::ErrRead) + error(ErrProxyNeg); +} + +// CS_NAMESPACE_END + +#include "httppoll.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/httppoll.h b/kopete/protocols/jabber/libiris/cutestuff/network/httppoll.h new file mode 100644 index 00000000..8bbebee3 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/httppoll.h @@ -0,0 +1,104 @@ +/* + * httppoll.h - HTTP polling proxy + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_HTTPPOLL_H +#define CS_HTTPPOLL_H + +#include"bytestream.h" + +// CS_NAMESPACE_BEGIN + +class HttpPoll : public ByteStream +{ + Q_OBJECT +public: + enum Error { ErrConnectionRefused = ErrCustom, ErrHostNotFound, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth }; + HttpPoll(QObject *parent=0); + ~HttpPoll(); + + void setAuth(const QString &user, const QString &pass=""); + void connectToUrl(const QString &url); + void connectToHost(const QString &proxyHost, int proxyPort, const QString &url); + + int pollInterval() const; + void setPollInterval(int seconds); + + // from ByteStream + bool isOpen() const; + void close(); + +signals: + void connected(); + void syncStarted(); + void syncFinished(); + +protected: + int tryWrite(); + +private slots: + void http_result(); + void http_error(int); + void do_sync(); + +private: + class Private; + Private *d; + + void reset(bool clear=false); + QByteArray makePacket(const QString &ident, const QString &key, const QString &newkey, const QByteArray &block); + void resetKey(); + const QString & getKey(bool *); +}; + +class HttpProxyPost : public QObject +{ + Q_OBJECT +public: + enum Error { ErrConnectionRefused, ErrHostNotFound, ErrSocket, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth }; + HttpProxyPost(QObject *parent=0); + ~HttpProxyPost(); + + void setAuth(const QString &user, const QString &pass=""); + bool isActive() const; + void post(const QString &proxyHost, int proxyPort, const QString &url, const QByteArray &data, bool asProxy=true); + void stop(); + QByteArray body() const; + QString getHeader(const QString &) const; + +signals: + void result(); + void error(int); + +private slots: + void sock_connected(); + void sock_connectionClosed(); + void sock_readyRead(); + void sock_error(int); + +private: + class Private; + Private *d; + + void reset(bool clear=false); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/ndns.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/ndns.cpp new file mode 100644 index 00000000..7fe60973 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/ndns.cpp @@ -0,0 +1,378 @@ +/* + * ndns.cpp - native DNS resolution + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +//! \class NDns ndns.h +//! \brief Simple DNS resolution using native system calls +//! +//! This class is to be used when Qt's QDns is not good enough. Because QDns +//! does not use threads, it cannot make a system call asyncronously. Thus, +//! QDns tries to imitate the behavior of each platform's native behavior, and +//! generally falls short. +//! +//! NDns uses a thread to make the system call happen in the background. This +//! gives your program native DNS behavior, at the cost of requiring threads +//! to build. +//! +//! \code +//! #include "ndns.h" +//! +//! ... +//! +//! NDns dns; +//! dns.resolve("psi.affinix.com"); +//! +//! // The class will emit the resultsReady() signal when the resolution +//! // is finished. You may then retrieve the results: +//! +//! uint ip_address = dns.result(); +//! +//! // or if you want to get the IP address as a string: +//! +//! QString ip_address = dns.resultString(); +//! \endcode + +#include"ndns.h" + +#include +#include +#include +#include + +#ifdef Q_OS_UNIX +#include +#include +#include +#include +#endif + +#ifdef Q_OS_WIN32 +#include +#endif + +// CS_NAMESPACE_BEGIN + +//! \if _hide_doc_ +class NDnsWorkerEvent : public QCustomEvent +{ +public: + enum Type { WorkerEvent = QEvent::User + 100 }; + NDnsWorkerEvent(NDnsWorker *); + + NDnsWorker *worker; +}; + +class NDnsWorker : public QThread +{ +public: + NDnsWorker(QObject *, const QCString &); + + bool success; + bool cancelled; + QHostAddress addr; + +protected: + void run(); + +private: + QCString host; + QObject *par; +}; +//! \endif + +//---------------------------------------------------------------------------- +// NDnsManager +//---------------------------------------------------------------------------- +#ifndef HAVE_GETHOSTBYNAME_R +static QMutex *workerMutex = 0; +static QMutex *workerCancelled = 0; +#endif +static NDnsManager *man = 0; +bool winsock_init = false; + +class NDnsManager::Item +{ +public: + NDns *ndns; + NDnsWorker *worker; +}; + +class NDnsManager::Private +{ +public: + Item *find(const NDns *n) + { + QPtrListIterator it(list); + for(Item *i; (i = it.current()); ++it) { + if(i->ndns == n) + return i; + } + return 0; + } + + Item *find(const NDnsWorker *w) + { + QPtrListIterator it(list); + for(Item *i; (i = it.current()); ++it) { + if(i->worker == w) + return i; + } + return 0; + } + + QPtrList list; +}; + +NDnsManager::NDnsManager() +{ +#ifndef HAVE_GETHOSTBYNAME_R + workerMutex = new QMutex; + workerCancelled = new QMutex; +#endif + +#ifdef Q_OS_WIN32 + if(!winsock_init) { + winsock_init = true; + QSocketDevice *sd = new QSocketDevice; + delete sd; + } +#endif + + d = new Private; + d->list.setAutoDelete(true); + + connect(qApp, SIGNAL(aboutToQuit()), SLOT(app_aboutToQuit())); +} + +NDnsManager::~NDnsManager() +{ + delete d; + +#ifndef HAVE_GETHOSTBYNAME_R + delete workerMutex; + workerMutex = 0; + delete workerCancelled; + workerCancelled = 0; +#endif +} + +void NDnsManager::resolve(NDns *self, const QString &name) +{ + Item *i = new Item; + i->ndns = self; + i->worker = new NDnsWorker(this, name.utf8()); + d->list.append(i); + + i->worker->start(); +} + +void NDnsManager::stop(NDns *self) +{ + Item *i = d->find(self); + if(!i) + return; + // disassociate + i->ndns = 0; + +#ifndef HAVE_GETHOSTBYNAME_R + // cancel + workerCancelled->lock(); + i->worker->cancelled = true; + workerCancelled->unlock(); +#endif +} + +bool NDnsManager::isBusy(const NDns *self) const +{ + Item *i = d->find(self); + return (i ? true: false); +} + +bool NDnsManager::event(QEvent *e) +{ + if((int)e->type() == (int)NDnsWorkerEvent::WorkerEvent) { + NDnsWorkerEvent *we = static_cast(e); + we->worker->wait(); // ensure that the thread is terminated + + Item *i = d->find(we->worker); + if(!i) { + // should NOT happen + return true; + } + QHostAddress addr = i->worker->addr; + NDns *ndns = i->ndns; + delete i->worker; + d->list.removeRef(i); + + // nuke manager if no longer needed (code that follows MUST BE SAFE!) + tryDestroy(); + + // requestor still around? + if(ndns) + ndns->finished(addr); + return true; + } + return false; +} + +void NDnsManager::tryDestroy() +{ + if(d->list.isEmpty()) { + man = 0; + delete this; + } +} + +void NDnsManager::app_aboutToQuit() +{ + while(man) { + QEventLoop *e = qApp->eventLoop(); + e->processEvents(QEventLoop::WaitForMore); + } +} + + +//---------------------------------------------------------------------------- +// NDns +//---------------------------------------------------------------------------- + +//! \fn void NDns::resultsReady() +//! This signal is emitted when the DNS resolution succeeds or fails. + +//! +//! Constructs an NDns object with parent \a parent. +NDns::NDns(QObject *parent) +:QObject(parent) +{ +} + +//! +//! Destroys the object and frees allocated resources. +NDns::~NDns() +{ + stop(); +} + +//! +//! Resolves hostname \a host (eg. psi.affinix.com) +void NDns::resolve(const QString &host) +{ + stop(); + if(!man) + man = new NDnsManager; + man->resolve(this, host); +} + +//! +//! Cancels the lookup action. +//! \note This will not stop the underlying system call, which must finish before the next lookup will proceed. +void NDns::stop() +{ + if(man) + man->stop(this); +} + +//! +//! Returns the IP address as a 32-bit integer in host-byte-order. This will be 0 if the lookup failed. +//! \sa resultsReady() +uint NDns::result() const +{ + return addr.ip4Addr(); +} + +//! +//! Returns the IP address as a string. This will be an empty string if the lookup failed. +//! \sa resultsReady() +QString NDns::resultString() const +{ + return addr.toString(); +} + +//! +//! Returns TRUE if busy resolving a hostname. +bool NDns::isBusy() const +{ + if(!man) + return false; + return man->isBusy(this); +} + +void NDns::finished(const QHostAddress &a) +{ + addr = a; + resultsReady(); +} + +//---------------------------------------------------------------------------- +// NDnsWorkerEvent +//---------------------------------------------------------------------------- +NDnsWorkerEvent::NDnsWorkerEvent(NDnsWorker *p) +:QCustomEvent(WorkerEvent) +{ + worker = p; +} + +//---------------------------------------------------------------------------- +// NDnsWorker +//---------------------------------------------------------------------------- +NDnsWorker::NDnsWorker(QObject *_par, const QCString &_host) +{ + success = cancelled = false; + par = _par; + host = _host.copy(); // do we need this to avoid sharing across threads? +} + +void NDnsWorker::run() +{ + hostent *h = 0; + +#ifdef HAVE_GETHOSTBYNAME_R + hostent buf; + char char_buf[1024]; + int err; + gethostbyname_r(host.data(), &buf, char_buf, sizeof(char_buf), &h, &err); +#else + // lock for gethostbyname + QMutexLocker locker(workerMutex); + + // check for cancel + workerCancelled->lock(); + bool cancel = cancelled; + workerCancelled->unlock(); + + if(!cancel) + h = gethostbyname(host.data()); +#endif + + if(!h) { + success = false; + QApplication::postEvent(par, new NDnsWorkerEvent(this)); + return; + } + + in_addr a = *((struct in_addr *)h->h_addr_list[0]); + addr.setAddress(ntohl(a.s_addr)); + success = true; + + QApplication::postEvent(par, new NDnsWorkerEvent(this)); +} + +// CS_NAMESPACE_END + +#include "ndns.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/ndns.h b/kopete/protocols/jabber/libiris/cutestuff/network/ndns.h new file mode 100644 index 00000000..c11d1a28 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/ndns.h @@ -0,0 +1,88 @@ +/* + * ndns.h - native DNS resolution + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_NDNS_H +#define CS_NDNS_H + +#include +#include +#include +#include +#include + +// CS_NAMESPACE_BEGIN + +class NDnsWorker; +class NDnsManager; + +class NDns : public QObject +{ + Q_OBJECT +public: + NDns(QObject *parent=0); + ~NDns(); + + void resolve(const QString &); + void stop(); + bool isBusy() const; + + uint result() const; + QString resultString() const; + +signals: + void resultsReady(); + +private: + QHostAddress addr; + + friend class NDnsManager; + void finished(const QHostAddress &); +}; + +class NDnsManager : public QObject +{ + Q_OBJECT +public: + ~NDnsManager(); + class Item; + +//! \if _hide_doc_ +protected: + bool event(QEvent *); +//! \endif + +private slots: + void app_aboutToQuit(); + +private: + class Private; + Private *d; + + friend class NDns; + NDnsManager(); + void resolve(NDns *self, const QString &name); + void stop(NDns *self); + bool isBusy(const NDns *self) const; + void tryDestroy(); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/servsock.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/servsock.cpp new file mode 100644 index 00000000..4aee36dc --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/servsock.cpp @@ -0,0 +1,112 @@ +/* + * servsock.cpp - simple wrapper to QServerSocket + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"servsock.h" + +// CS_NAMESPACE_BEGIN + +//---------------------------------------------------------------------------- +// ServSock +//---------------------------------------------------------------------------- +class ServSock::Private +{ +public: + Private() {} + + ServSockSignal *serv; +}; + +ServSock::ServSock(QObject *parent) +:QObject(parent) +{ + d = new Private; + d->serv = 0; +} + +ServSock::~ServSock() +{ + stop(); + delete d; +} + +bool ServSock::isActive() const +{ + return (d->serv ? true: false); +} + +bool ServSock::listen(Q_UINT16 port) +{ + stop(); + + d->serv = new ServSockSignal(port); + if(!d->serv->ok()) { + delete d->serv; + d->serv = 0; + return false; + } + connect(d->serv, SIGNAL(connectionReady(int)), SLOT(sss_connectionReady(int))); + + return true; +} + +void ServSock::stop() +{ + delete d->serv; + d->serv = 0; +} + +int ServSock::port() const +{ + if(d->serv) + return d->serv->port(); + else + return -1; +} + +QHostAddress ServSock::address() const +{ + if(d->serv) + return d->serv->address(); + else + return QHostAddress(); +} + +void ServSock::sss_connectionReady(int s) +{ + connectionReady(s); +} + + +//---------------------------------------------------------------------------- +// ServSockSignal +//---------------------------------------------------------------------------- +ServSockSignal::ServSockSignal(int port) +:QServerSocket(port, 16) +{ +} + +void ServSockSignal::newConnection(int x) +{ + connectionReady(x); +} + +// CS_NAMESPACE_END + +#include "servsock.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/servsock.h b/kopete/protocols/jabber/libiris/cutestuff/network/servsock.h new file mode 100644 index 00000000..60a0c99d --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/servsock.h @@ -0,0 +1,68 @@ +/* + * servsock.h - simple wrapper to QServerSocket + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_SERVSOCK_H +#define CS_SERVSOCK_H + +#include + +// CS_NAMESPACE_BEGIN + +class ServSock : public QObject +{ + Q_OBJECT +public: + ServSock(QObject *parent=0); + ~ServSock(); + + bool isActive() const; + bool listen(Q_UINT16 port); + void stop(); + int port() const; + QHostAddress address() const; + +signals: + void connectionReady(int); + +private slots: + void sss_connectionReady(int); + +private: + class Private; + Private *d; +}; + +class ServSockSignal : public QServerSocket +{ + Q_OBJECT +public: + ServSockSignal(int port); + +signals: + void connectionReady(int); + +protected: + // reimplemented + void newConnection(int); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/socks.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/socks.cpp new file mode 100644 index 00000000..bae374f5 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/socks.cpp @@ -0,0 +1,1223 @@ +/* + * socks.cpp - SOCKS5 TCP proxy client/server + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"socks.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_UNIX +#include +#include +#endif + +#ifdef Q_OS_WIN32 +#include +#endif + +#include"servsock.h" +#include"bsocket.h" + +#ifdef PROX_DEBUG +#include +#endif + +// CS_NAMESPACE_BEGIN + +//---------------------------------------------------------------------------- +// SocksUDP +//---------------------------------------------------------------------------- +static QByteArray sp_create_udp(const QString &host, Q_UINT16 port, const QByteArray &buf) +{ + // detect for IP addresses + //QHostAddress addr; + //if(addr.setAddress(host)) + // return sp_set_request(addr, port, cmd1); + + QCString h = host.utf8(); + h.truncate(255); + h = QString::fromUtf8(h).utf8(); // delete any partial characters? + int hlen = h.length(); + + int at = 0; + QByteArray a(4); + a[at++] = 0x00; // reserved + a[at++] = 0x00; // reserved + a[at++] = 0x00; // frag + a[at++] = 0x03; // address type = domain + + // host + a.resize(at+hlen+1); + a[at++] = hlen; + memcpy(a.data() + at, h.data(), hlen); + at += hlen; + + // port + a.resize(at+2); + unsigned short p = htons(port); + memcpy(a.data() + at, &p, 2); + at += 2; + + a.resize(at+buf.size()); + memcpy(a.data() + at, buf.data(), buf.size()); + + return a; +} + +struct SPS_UDP +{ + QString host; + Q_UINT16 port; + QByteArray data; +}; + +static int sp_read_udp(QByteArray *from, SPS_UDP *s) +{ + int full_len = 4; + if((int)from->size() < full_len) + return 0; + + QString host; + QHostAddress addr; + unsigned char atype = from->at(3); + + if(atype == 0x01) { + full_len += 4; + if((int)from->size() < full_len) + return 0; + Q_UINT32 ip4; + memcpy(&ip4, from->data() + 4, 4); + addr.setAddress(ntohl(ip4)); + host = addr.toString(); + } + else if(atype == 0x03) { + ++full_len; + if((int)from->size() < full_len) + return 0; + unsigned char host_len = from->at(4); + full_len += host_len; + if((int)from->size() < full_len) + return 0; + QCString cs(host_len+1); + memcpy(cs.data(), from->data() + 5, host_len); + host = QString::fromLatin1(cs); + } + else if(atype == 0x04) { + full_len += 16; + if((int)from->size() < full_len) + return 0; + Q_UINT8 a6[16]; + memcpy(a6, from->data() + 4, 16); + addr.setAddress(a6); + host = addr.toString(); + } + + full_len += 2; + if((int)from->size() < full_len) + return 0; + + Q_UINT16 p; + memcpy(&p, from->data() + full_len - 2, 2); + + s->host = host; + s->port = ntohs(p); + s->data.resize(from->size() - full_len); + memcpy(s->data.data(), from->data() + full_len, s->data.size()); + + return 1; +} + +class SocksUDP::Private +{ +public: + QSocketDevice *sd; + QSocketNotifier *sn; + SocksClient *sc; + QHostAddress routeAddr; + int routePort; + QString host; + int port; +}; + +SocksUDP::SocksUDP(SocksClient *sc, const QString &host, int port, const QHostAddress &routeAddr, int routePort) +:QObject(sc) +{ + d = new Private; + d->sc = sc; + d->sd = new QSocketDevice(QSocketDevice::Datagram); + d->sd->setBlocking(false); + d->sn = new QSocketNotifier(d->sd->socket(), QSocketNotifier::Read); + connect(d->sn, SIGNAL(activated(int)), SLOT(sn_activated(int))); + d->host = host; + d->port = port; + d->routeAddr = routeAddr; + d->routePort = routePort; +} + +SocksUDP::~SocksUDP() +{ + delete d->sn; + delete d->sd; + delete d; +} + +void SocksUDP::change(const QString &host, int port) +{ + d->host = host; + d->port = port; +} + +void SocksUDP::write(const QByteArray &data) +{ + QByteArray buf = sp_create_udp(d->host, d->port, data); + d->sd->setBlocking(true); + d->sd->writeBlock(buf.data(), buf.size(), d->routeAddr, d->routePort); + d->sd->setBlocking(false); +} + +void SocksUDP::sn_activated(int) +{ + QByteArray buf(8192); + int actual = d->sd->readBlock(buf.data(), buf.size()); + buf.resize(actual); + packetReady(buf); +} + +//---------------------------------------------------------------------------- +// SocksClient +//---------------------------------------------------------------------------- +#define REQ_CONNECT 0x01 +#define REQ_BIND 0x02 +#define REQ_UDPASSOCIATE 0x03 + +#define RET_SUCCESS 0x00 +#define RET_UNREACHABLE 0x04 +#define RET_CONNREFUSED 0x05 + +// spc = socks packet client +// sps = socks packet server +// SPCS = socks packet client struct +// SPSS = socks packet server struct + +// Version +static QByteArray spc_set_version() +{ + QByteArray ver(4); + ver[0] = 0x05; // socks version 5 + ver[1] = 0x02; // number of methods + ver[2] = 0x00; // no-auth + ver[3] = 0x02; // username + return ver; +} + +static QByteArray sps_set_version(int method) +{ + QByteArray ver(2); + ver[0] = 0x05; + ver[1] = method; + return ver; +} + +struct SPCS_VERSION +{ + unsigned char version; + QByteArray methodList; +}; + +static int spc_get_version(QByteArray *from, SPCS_VERSION *s) +{ + if(from->size() < 1) + return 0; + if(from->at(0) != 0x05) // only SOCKS5 supported + return -1; + if(from->size() < 2) + return 0; + uint num = from->at(1); + if(num > 16) // who the heck has over 16 auth methods?? + return -1; + if(from->size() < 2 + num) + return 0; + QByteArray a = ByteStream::takeArray(from, 2+num); + s->version = a[0]; + s->methodList.resize(num); + memcpy(s->methodList.data(), a.data() + 2, num); + return 1; +} + +struct SPSS_VERSION +{ + unsigned char version; + unsigned char method; +}; + +static int sps_get_version(QByteArray *from, SPSS_VERSION *s) +{ + if(from->size() < 2) + return 0; + QByteArray a = ByteStream::takeArray(from, 2); + s->version = a[0]; + s->method = a[1]; + return 1; +} + +// authUsername +static QByteArray spc_set_authUsername(const QCString &user, const QCString &pass) +{ + int len1 = user.length(); + int len2 = pass.length(); + if(len1 > 255) + len1 = 255; + if(len2 > 255) + len2 = 255; + QByteArray a(1+1+len1+1+len2); + a[0] = 0x01; // username auth version 1 + a[1] = len1; + memcpy(a.data() + 2, user.data(), len1); + a[2+len1] = len2; + memcpy(a.data() + 3 + len1, pass.data(), len2); + return a; +} + +static QByteArray sps_set_authUsername(bool success) +{ + QByteArray a(2); + a[0] = 0x01; + a[1] = success ? 0x00 : 0xff; + return a; +} + +struct SPCS_AUTHUSERNAME +{ + QString user, pass; +}; + +static int spc_get_authUsername(QByteArray *from, SPCS_AUTHUSERNAME *s) +{ + if(from->size() < 1) + return 0; + unsigned char ver = from->at(0); + if(ver != 0x01) + return -1; + if(from->size() < 2) + return 0; + unsigned char ulen = from->at(1); + if((int)from->size() < ulen + 3) + return 0; + unsigned char plen = from->at(ulen+2); + if((int)from->size() < ulen + plen + 3) + return 0; + QByteArray a = ByteStream::takeArray(from, ulen + plen + 3); + + QCString user, pass; + user.resize(ulen+1); + pass.resize(plen+1); + memcpy(user.data(), a.data()+2, ulen); + memcpy(pass.data(), a.data()+ulen+3, plen); + s->user = QString::fromUtf8(user); + s->pass = QString::fromUtf8(pass); + return 1; +} + +struct SPSS_AUTHUSERNAME +{ + unsigned char version; + bool success; +}; + +static int sps_get_authUsername(QByteArray *from, SPSS_AUTHUSERNAME *s) +{ + if(from->size() < 2) + return 0; + QByteArray a = ByteStream::takeArray(from, 2); + s->version = a[0]; + s->success = a[1] == 0 ? true: false; + return 1; +} + +// connectRequest +static QByteArray sp_set_request(const QHostAddress &addr, unsigned short port, unsigned char cmd1) +{ + int at = 0; + QByteArray a(4); + a[at++] = 0x05; // socks version 5 + a[at++] = cmd1; + a[at++] = 0x00; // reserved + if(addr.isIp4Addr()) { + a[at++] = 0x01; // address type = ipv4 + Q_UINT32 ip4 = htonl(addr.ip4Addr()); + a.resize(at+4); + memcpy(a.data() + at, &ip4, 4); + at += 4; + } + else { + a[at++] = 0x04; + Q_UINT8 a6[16]; + QStringList s6 = QStringList::split(':', addr.toString(), true); + int at = 0; + Q_UINT16 c; + bool ok; + for(QStringList::ConstIterator it = s6.begin(); it != s6.end(); ++it) { + c = (*it).toInt(&ok, 16); + a6[at++] = (c >> 8); + a6[at++] = c & 0xff; + } + a.resize(at+16); + memcpy(a.data() + at, a6, 16); + at += 16; + } + + // port + a.resize(at+2); + unsigned short p = htons(port); + memcpy(a.data() + at, &p, 2); + + return a; +} + +static QByteArray sp_set_request(const QString &host, Q_UINT16 port, unsigned char cmd1) +{ + // detect for IP addresses + QHostAddress addr; + if(addr.setAddress(host)) + return sp_set_request(addr, port, cmd1); + + QCString h = host.utf8(); + h.truncate(255); + h = QString::fromUtf8(h).utf8(); // delete any partial characters? + int hlen = h.length(); + + int at = 0; + QByteArray a(4); + a[at++] = 0x05; // socks version 5 + a[at++] = cmd1; + a[at++] = 0x00; // reserved + a[at++] = 0x03; // address type = domain + + // host + a.resize(at+hlen+1); + a[at++] = hlen; + memcpy(a.data() + at, h.data(), hlen); + at += hlen; + + // port + a.resize(at+2); + unsigned short p = htons(port); + memcpy(a.data() + at, &p, 2); + + return a; +} + +struct SPS_CONNREQ +{ + unsigned char version; + unsigned char cmd; + int address_type; + QString host; + QHostAddress addr; + Q_UINT16 port; +}; + +static int sp_get_request(QByteArray *from, SPS_CONNREQ *s) +{ + int full_len = 4; + if((int)from->size() < full_len) + return 0; + + QString host; + QHostAddress addr; + unsigned char atype = from->at(3); + + if(atype == 0x01) { + full_len += 4; + if((int)from->size() < full_len) + return 0; + Q_UINT32 ip4; + memcpy(&ip4, from->data() + 4, 4); + addr.setAddress(ntohl(ip4)); + } + else if(atype == 0x03) { + ++full_len; + if((int)from->size() < full_len) + return 0; + unsigned char host_len = from->at(4); + full_len += host_len; + if((int)from->size() < full_len) + return 0; + QCString cs(host_len+1); + memcpy(cs.data(), from->data() + 5, host_len); + host = QString::fromLatin1(cs); + } + else if(atype == 0x04) { + full_len += 16; + if((int)from->size() < full_len) + return 0; + Q_UINT8 a6[16]; + memcpy(a6, from->data() + 4, 16); + addr.setAddress(a6); + } + + full_len += 2; + if((int)from->size() < full_len) + return 0; + + QByteArray a = ByteStream::takeArray(from, full_len); + + Q_UINT16 p; + memcpy(&p, a.data() + full_len - 2, 2); + + s->version = a[0]; + s->cmd = a[1]; + s->address_type = atype; + s->host = host; + s->addr = addr; + s->port = ntohs(p); + + return 1; +} + +enum { StepVersion, StepAuth, StepRequest }; + +class SocksClient::Private +{ +public: + Private() {} + + BSocket sock; + QString host; + int port; + QString user, pass; + QString real_host; + int real_port; + + QByteArray recvBuf; + bool active; + int step; + int authMethod; + bool incoming, waiting; + + QString rhost; + int rport; + + int pending; + + bool udp; + QString udpAddr; + int udpPort; +}; + +SocksClient::SocksClient(QObject *parent) +:ByteStream(parent) +{ + init(); + + d->incoming = false; +} + +SocksClient::SocksClient(int s, QObject *parent) +:ByteStream(parent) +{ + init(); + + d->incoming = true; + d->waiting = true; + d->sock.setSocket(s); +} + +void SocksClient::init() +{ + d = new Private; + connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected())); + connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed())); + connect(&d->sock, SIGNAL(delayedCloseFinished()), SLOT(sock_delayedCloseFinished())); + connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); + connect(&d->sock, SIGNAL(bytesWritten(int)), SLOT(sock_bytesWritten(int))); + connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int))); + + reset(true); +} + +SocksClient::~SocksClient() +{ + reset(true); + delete d; +} + +void SocksClient::reset(bool clear) +{ + if(d->sock.state() != BSocket::Idle) + d->sock.close(); + if(clear) + clearReadBuffer(); + d->recvBuf.resize(0); + d->active = false; + d->waiting = false; + d->udp = false; + d->pending = 0; +} + +bool SocksClient::isIncoming() const +{ + return d->incoming; +} + +void SocksClient::setAuth(const QString &user, const QString &pass) +{ + d->user = user; + d->pass = pass; +} + +void SocksClient::connectToHost(const QString &proxyHost, int proxyPort, const QString &host, int port, bool udpMode) +{ + reset(true); + + d->host = proxyHost; + d->port = proxyPort; + d->real_host = host; + d->real_port = port; + d->udp = udpMode; + +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: Connecting to %s:%d", proxyHost.latin1(), proxyPort); + if(d->user.isEmpty()) + fprintf(stderr, "\n"); + else + fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1()); +#endif + d->sock.connectToHost(d->host, d->port); +} + +bool SocksClient::isOpen() const +{ + return d->active; +} + +void SocksClient::close() +{ + d->sock.close(); + if(d->sock.bytesToWrite() == 0) + reset(); +} + +void SocksClient::writeData(const QByteArray &buf) +{ + d->pending += buf.size(); + d->sock.write(buf); +} + +void SocksClient::write(const QByteArray &buf) +{ + if(d->active && !d->udp) + d->sock.write(buf); +} + +QByteArray SocksClient::read(int bytes) +{ + return ByteStream::read(bytes); +} + +int SocksClient::bytesAvailable() const +{ + return ByteStream::bytesAvailable(); +} + +int SocksClient::bytesToWrite() const +{ + if(d->active) + return d->sock.bytesToWrite(); + else + return 0; +} + +void SocksClient::sock_connected() +{ +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: Connected\n"); +#endif + + d->step = StepVersion; + writeData(spc_set_version()); +} + +void SocksClient::sock_connectionClosed() +{ + if(d->active) { + reset(); + connectionClosed(); + } + else { + error(ErrProxyNeg); + } +} + +void SocksClient::sock_delayedCloseFinished() +{ + if(d->active) { + reset(); + delayedCloseFinished(); + } +} + +void SocksClient::sock_readyRead() +{ + QByteArray block = d->sock.read(); + + if(!d->active) { + if(d->incoming) + processIncoming(block); + else + processOutgoing(block); + } + else { + if(!d->udp) { + appendRead(block); + readyRead(); + } + } +} + +void SocksClient::processOutgoing(const QByteArray &block) +{ +#ifdef PROX_DEBUG + // show hex + fprintf(stderr, "SocksClient: client recv { "); + for(int n = 0; n < (int)block.size(); ++n) + fprintf(stderr, "%02X ", (unsigned char)block[n]); + fprintf(stderr, " } \n"); +#endif + ByteStream::appendArray(&d->recvBuf, block); + + if(d->step == StepVersion) { + SPSS_VERSION s; + int r = sps_get_version(&d->recvBuf, &s); + if(r == -1) { + reset(true); + error(ErrProxyNeg); + return; + } + else if(r == 1) { + if(s.version != 0x05 || s.method == 0xff) { +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: Method selection failed\n"); +#endif + reset(true); + error(ErrProxyNeg); + return; + } + + QString str; + if(s.method == 0x00) { + str = "None"; + d->authMethod = AuthNone; + } + else if(s.method == 0x02) { + str = "Username/Password"; + d->authMethod = AuthUsername; + } + else { +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: Server wants to use unknown method '%02x'\n", s.method); +#endif + reset(true); + error(ErrProxyNeg); + return; + } + + if(d->authMethod == AuthNone) { + // no auth, go straight to the request + do_request(); + } + else if(d->authMethod == AuthUsername) { + d->step = StepAuth; +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: Authenticating [Username] ...\n"); +#endif + writeData(spc_set_authUsername(d->user.latin1(), d->pass.latin1())); + } + } + } + if(d->step == StepAuth) { + if(d->authMethod == AuthUsername) { + SPSS_AUTHUSERNAME s; + int r = sps_get_authUsername(&d->recvBuf, &s); + if(r == -1) { + reset(true); + error(ErrProxyNeg); + return; + } + else if(r == 1) { + if(s.version != 0x01) { + reset(true); + error(ErrProxyNeg); + return; + } + if(!s.success) { + reset(true); + error(ErrProxyAuth); + return; + } + + do_request(); + } + } + } + else if(d->step == StepRequest) { + SPS_CONNREQ s; + int r = sp_get_request(&d->recvBuf, &s); + if(r == -1) { + reset(true); + error(ErrProxyNeg); + return; + } + else if(r == 1) { + if(s.cmd != RET_SUCCESS) { +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: client << Error >> [%02x]\n", s.cmd); +#endif + reset(true); + if(s.cmd == RET_UNREACHABLE) + error(ErrHostNotFound); + else if(s.cmd == RET_CONNREFUSED) + error(ErrConnectionRefused); + else + error(ErrProxyNeg); + return; + } + +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: client << Success >>\n"); +#endif + if(d->udp) { + if(s.address_type == 0x03) + d->udpAddr = s.host; + else + d->udpAddr = s.addr.toString(); + d->udpPort = s.port; + } + + d->active = true; + + QGuardedPtr self = this; + connected(); + if(!self) + return; + + if(!d->recvBuf.isEmpty()) { + appendRead(d->recvBuf); + d->recvBuf.resize(0); + readyRead(); + } + } + } +} + +void SocksClient::do_request() +{ +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: Requesting ...\n"); +#endif + d->step = StepRequest; + int cmd = d->udp ? REQ_UDPASSOCIATE : REQ_CONNECT; + QByteArray buf; + if(!d->real_host.isEmpty()) + buf = sp_set_request(d->real_host, d->real_port, cmd); + else + buf = sp_set_request(QHostAddress(), 0, cmd); + writeData(buf); +} + +void SocksClient::sock_bytesWritten(int x) +{ + int bytes = x; + if(d->pending >= bytes) { + d->pending -= bytes; + bytes = 0; + } + else { + bytes -= d->pending; + d->pending = 0; + } + if(bytes > 0) + bytesWritten(bytes); +} + +void SocksClient::sock_error(int x) +{ + if(d->active) { + reset(); + error(ErrRead); + } + else { + reset(true); + if(x == BSocket::ErrHostNotFound) + error(ErrProxyConnect); + else if(x == BSocket::ErrConnectionRefused) + error(ErrProxyConnect); + else if(x == BSocket::ErrRead) + error(ErrProxyNeg); + } +} + +void SocksClient::serve() +{ + d->waiting = false; + d->step = StepVersion; + continueIncoming(); +} + +void SocksClient::processIncoming(const QByteArray &block) +{ +#ifdef PROX_DEBUG + // show hex + fprintf(stderr, "SocksClient: server recv { "); + for(int n = 0; n < (int)block.size(); ++n) + fprintf(stderr, "%02X ", (unsigned char)block[n]); + fprintf(stderr, " } \n"); +#endif + ByteStream::appendArray(&d->recvBuf, block); + + if(!d->waiting) + continueIncoming(); +} + +void SocksClient::continueIncoming() +{ + if(d->recvBuf.isEmpty()) + return; + + if(d->step == StepVersion) { + SPCS_VERSION s; + int r = spc_get_version(&d->recvBuf, &s); + if(r == -1) { + reset(true); + error(ErrProxyNeg); + return; + } + else if(r == 1) { + if(s.version != 0x05) { + reset(true); + error(ErrProxyNeg); + return; + } + + int methods = 0; + for(int n = 0; n < (int)s.methodList.size(); ++n) { + unsigned char c = s.methodList[n]; + if(c == 0x00) + methods |= AuthNone; + else if(c == 0x02) + methods |= AuthUsername; + } + d->waiting = true; + incomingMethods(methods); + } + } + else if(d->step == StepAuth) { + SPCS_AUTHUSERNAME s; + int r = spc_get_authUsername(&d->recvBuf, &s); + if(r == -1) { + reset(true); + error(ErrProxyNeg); + return; + } + else if(r == 1) { + d->waiting = true; + incomingAuth(s.user, s.pass); + } + } + else if(d->step == StepRequest) { + SPS_CONNREQ s; + int r = sp_get_request(&d->recvBuf, &s); + if(r == -1) { + reset(true); + error(ErrProxyNeg); + return; + } + else if(r == 1) { + d->waiting = true; + if(s.cmd == REQ_CONNECT) { + if(!s.host.isEmpty()) + d->rhost = s.host; + else + d->rhost = s.addr.toString(); + d->rport = s.port; + incomingConnectRequest(d->rhost, d->rport); + } + else if(s.cmd == REQ_UDPASSOCIATE) { + incomingUDPAssociateRequest(); + } + else { + requestDeny(); + return; + } + } + } +} + +void SocksClient::chooseMethod(int method) +{ + if(d->step != StepVersion || !d->waiting) + return; + + unsigned char c; + if(method == AuthNone) { + d->step = StepRequest; + c = 0x00; + } + else { + d->step = StepAuth; + c = 0x02; + } + + // version response + d->waiting = false; + writeData(sps_set_version(c)); + continueIncoming(); +} + +void SocksClient::authGrant(bool b) +{ + if(d->step != StepAuth || !d->waiting) + return; + + if(b) + d->step = StepRequest; + + // auth response + d->waiting = false; + writeData(sps_set_authUsername(b)); + if(!b) { + reset(true); + return; + } + continueIncoming(); +} + +void SocksClient::requestDeny() +{ + if(d->step != StepRequest || !d->waiting) + return; + + // response + d->waiting = false; + writeData(sp_set_request(d->rhost, d->rport, RET_UNREACHABLE)); + reset(true); +} + +void SocksClient::grantConnect() +{ + if(d->step != StepRequest || !d->waiting) + return; + + // response + d->waiting = false; + writeData(sp_set_request(d->rhost, d->rport, RET_SUCCESS)); + d->active = true; +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: server << Success >>\n"); +#endif + + if(!d->recvBuf.isEmpty()) { + appendRead(d->recvBuf); + d->recvBuf.resize(0); + readyRead(); + } +} + +void SocksClient::grantUDPAssociate(const QString &relayHost, int relayPort) +{ + if(d->step != StepRequest || !d->waiting) + return; + + // response + d->waiting = false; + writeData(sp_set_request(relayHost, relayPort, RET_SUCCESS)); + d->udp = true; + d->active = true; +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: server << Success >>\n"); +#endif + + if(!d->recvBuf.isEmpty()) + d->recvBuf.resize(0); +} + +QHostAddress SocksClient::peerAddress() const +{ + return d->sock.peerAddress(); +} + +Q_UINT16 SocksClient::peerPort() const +{ + return d->sock.peerPort(); +} + +QString SocksClient::udpAddress() const +{ + return d->udpAddr; +} + +Q_UINT16 SocksClient::udpPort() const +{ + return d->udpPort; +} + +SocksUDP *SocksClient::createUDP(const QString &host, int port, const QHostAddress &routeAddr, int routePort) +{ + return new SocksUDP(this, host, port, routeAddr, routePort); +} + +//---------------------------------------------------------------------------- +// SocksServer +//---------------------------------------------------------------------------- +class SocksServer::Private +{ +public: + Private() {} + + ServSock serv; + QPtrList incomingConns; + QSocketDevice *sd; + QSocketNotifier *sn; +}; + +SocksServer::SocksServer(QObject *parent) +:QObject(parent) +{ + d = new Private; + d->sd = 0; + d->sn = 0; + connect(&d->serv, SIGNAL(connectionReady(int)), SLOT(connectionReady(int))); +} + +SocksServer::~SocksServer() +{ + stop(); + d->incomingConns.setAutoDelete(true); + d->incomingConns.clear(); + delete d; +} + +bool SocksServer::isActive() const +{ + return d->serv.isActive(); +} + +bool SocksServer::listen(Q_UINT16 port, bool udp) +{ + stop(); + if(!d->serv.listen(port)) + return false; + if(udp) { + d->sd = new QSocketDevice(QSocketDevice::Datagram); + d->sd->setBlocking(false); + if(!d->sd->bind(QHostAddress(), port)) { + delete d->sd; + d->sd = 0; + d->serv.stop(); + return false; + } + d->sn = new QSocketNotifier(d->sd->socket(), QSocketNotifier::Read); + connect(d->sn, SIGNAL(activated(int)), SLOT(sn_activated(int))); + } + return true; +} + +void SocksServer::stop() +{ + delete d->sn; + d->sn = 0; + delete d->sd; + d->sd = 0; + d->serv.stop(); +} + +int SocksServer::port() const +{ + return d->serv.port(); +} + +QHostAddress SocksServer::address() const +{ + return d->serv.address(); +} + +SocksClient *SocksServer::takeIncoming() +{ + if(d->incomingConns.isEmpty()) + return 0; + + SocksClient *c = d->incomingConns.getFirst(); + d->incomingConns.removeRef(c); + + // we don't care about errors anymore + disconnect(c, SIGNAL(error(int)), this, SLOT(connectionError())); + + // don't serve the connection until the event loop, to give the caller a chance to map signals + QTimer::singleShot(0, c, SLOT(serve())); + + return c; +} + +void SocksServer::writeUDP(const QHostAddress &addr, int port, const QByteArray &data) +{ + if(d->sd) { + d->sd->setBlocking(true); + d->sd->writeBlock(data.data(), data.size(), addr, port); + d->sd->setBlocking(false); + } +} + +void SocksServer::connectionReady(int s) +{ + SocksClient *c = new SocksClient(s, this); + connect(c, SIGNAL(error(int)), this, SLOT(connectionError())); + d->incomingConns.append(c); + incomingReady(); +} + +void SocksServer::connectionError() +{ + SocksClient *c = (SocksClient *)sender(); + d->incomingConns.removeRef(c); + c->deleteLater(); +} + +void SocksServer::sn_activated(int) +{ + QByteArray buf(8192); + int actual = d->sd->readBlock(buf.data(), buf.size()); + buf.resize(actual); + QHostAddress pa = d->sd->peerAddress(); + int pp = d->sd->peerPort(); + SPS_UDP s; + int r = sp_read_udp(&buf, &s); + if(r != 1) + return; + incomingUDP(s.host, s.port, pa, pp, s.data); +} + +// CS_NAMESPACE_END + +#include "socks.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/socks.h b/kopete/protocols/jabber/libiris/cutestuff/network/socks.h new file mode 100644 index 00000000..8f1e4ddc --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/socks.h @@ -0,0 +1,160 @@ +/* + * socks.h - SOCKS5 TCP proxy client/server + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_SOCKS_H +#define CS_SOCKS_H + +#include"bytestream.h" + +// CS_NAMESPACE_BEGIN + +class QHostAddress; +class SocksClient; +class SocksServer; + +class SocksUDP : public QObject +{ + Q_OBJECT +public: + ~SocksUDP(); + + void change(const QString &host, int port); + void write(const QByteArray &data); + +signals: + void packetReady(const QByteArray &data); + +private slots: + void sn_activated(int); + +private: + class Private; + Private *d; + + friend class SocksClient; + SocksUDP(SocksClient *sc, const QString &host, int port, const QHostAddress &routeAddr, int routePort); +}; + +class SocksClient : public ByteStream +{ + Q_OBJECT +public: + enum Error { ErrConnectionRefused = ErrCustom, ErrHostNotFound, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth }; + enum Method { AuthNone=0x0001, AuthUsername=0x0002 }; + enum Request { ReqConnect, ReqUDPAssociate }; + SocksClient(QObject *parent=0); + SocksClient(int, QObject *parent=0); + ~SocksClient(); + + bool isIncoming() const; + + // outgoing + void setAuth(const QString &user, const QString &pass=""); + void connectToHost(const QString &proxyHost, int proxyPort, const QString &host, int port, bool udpMode=false); + + // incoming + void chooseMethod(int); + void authGrant(bool); + void requestDeny(); + void grantConnect(); + void grantUDPAssociate(const QString &relayHost, int relayPort); + + // from ByteStream + bool isOpen() const; + void close(); + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + + // remote address + QHostAddress peerAddress() const; + Q_UINT16 peerPort() const; + + // udp + QString udpAddress() const; + Q_UINT16 udpPort() const; + SocksUDP *createUDP(const QString &host, int port, const QHostAddress &routeAddr, int routePort); + +signals: + // outgoing + void connected(); + + // incoming + void incomingMethods(int); + void incomingAuth(const QString &user, const QString &pass); + void incomingConnectRequest(const QString &host, int port); + void incomingUDPAssociateRequest(); + +private slots: + void sock_connected(); + void sock_connectionClosed(); + void sock_delayedCloseFinished(); + void sock_readyRead(); + void sock_bytesWritten(int); + void sock_error(int); + void serve(); + +private: + class Private; + Private *d; + + void init(); + void reset(bool clear=false); + void do_request(); + void processOutgoing(const QByteArray &); + void processIncoming(const QByteArray &); + void continueIncoming(); + void writeData(const QByteArray &a); +}; + +class SocksServer : public QObject +{ + Q_OBJECT +public: + SocksServer(QObject *parent=0); + ~SocksServer(); + + bool isActive() const; + bool listen(Q_UINT16 port, bool udp=false); + void stop(); + int port() const; + QHostAddress address() const; + SocksClient *takeIncoming(); + + void writeUDP(const QHostAddress &addr, int port, const QByteArray &data); + +signals: + void incomingReady(); + void incomingUDP(const QString &host, int port, const QHostAddress &addr, int sourcePort, const QByteArray &data); + +private slots: + void connectionReady(int); + void connectionError(); + void sn_activated(int); + +private: + class Private; + Private *d; +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.cpp new file mode 100644 index 00000000..0c454c49 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.cpp @@ -0,0 +1,320 @@ +/* + * srvresolver.cpp - class to simplify SRV lookups + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"srvresolver.h" + +#include +#include +#include +#include"safedelete.h" + +#ifndef NO_NDNS +#include"ndns.h" +#endif + +// CS_NAMESPACE_BEGIN + +static void sortSRVList(QValueList &list) +{ + QValueList tmp = list; + list.clear(); + + while(!tmp.isEmpty()) { + QValueList::Iterator p = tmp.end(); + for(QValueList::Iterator it = tmp.begin(); it != tmp.end(); ++it) { + if(p == tmp.end()) + p = it; + else { + int a = (*it).priority; + int b = (*p).priority; + int j = (*it).weight; + int k = (*p).weight; + if(a < b || (a == b && j < k)) + p = it; + } + } + list.append(*p); + tmp.remove(p); + } +} + +class SrvResolver::Private +{ +public: + Private() {} + + QDns *qdns; +#ifndef NO_NDNS + NDns ndns; +#endif + + bool failed; + QHostAddress resultAddress; + Q_UINT16 resultPort; + + bool srvonly; + QString srv; + QValueList servers; + bool aaaa; + + QTimer t; + SafeDelete sd; +}; + +SrvResolver::SrvResolver(QObject *parent) +:QObject(parent) +{ + d = new Private; + d->qdns = 0; + +#ifndef NO_NDNS + connect(&d->ndns, SIGNAL(resultsReady()), SLOT(ndns_done())); +#endif + connect(&d->t, SIGNAL(timeout()), SLOT(t_timeout())); + stop(); +} + +SrvResolver::~SrvResolver() +{ + stop(); + delete d; +} + +void SrvResolver::resolve(const QString &server, const QString &type, const QString &proto) +{ + stop(); + + d->failed = false; + d->srvonly = false; + d->srv = QString("_") + type + "._" + proto + '.' + server; + d->t.start(15000, true); + d->qdns = new QDns; + connect(d->qdns, SIGNAL(resultsReady()), SLOT(qdns_done())); + d->qdns->setRecordType(QDns::Srv); + d->qdns->setLabel(d->srv); +} + +void SrvResolver::resolveSrvOnly(const QString &server, const QString &type, const QString &proto) +{ + stop(); + + d->failed = false; + d->srvonly = true; + d->srv = QString("_") + type + "._" + proto + '.' + server; + d->t.start(15000, true); + d->qdns = new QDns; + connect(d->qdns, SIGNAL(resultsReady()), SLOT(qdns_done())); + d->qdns->setRecordType(QDns::Srv); + d->qdns->setLabel(d->srv); +} + +void SrvResolver::next() +{ + if(d->servers.isEmpty()) + return; + + tryNext(); +} + +void SrvResolver::stop() +{ + if(d->t.isActive()) + d->t.stop(); + if(d->qdns) { + d->qdns->disconnect(this); + d->sd.deleteLater(d->qdns); + d->qdns = 0; + } +#ifndef NO_NDNS + if(d->ndns.isBusy()) + d->ndns.stop(); +#endif + d->resultAddress = QHostAddress(); + d->resultPort = 0; + d->servers.clear(); + d->srv = ""; + d->failed = true; +} + +bool SrvResolver::isBusy() const +{ +#ifndef NO_NDNS + if(d->qdns || d->ndns.isBusy()) +#else + if(d->qdns) +#endif + return true; + else + return false; +} + +QValueList SrvResolver::servers() const +{ + return d->servers; +} + +bool SrvResolver::failed() const +{ + return d->failed; +} + +QHostAddress SrvResolver::resultAddress() const +{ + return d->resultAddress; +} + +Q_UINT16 SrvResolver::resultPort() const +{ + return d->resultPort; +} + +void SrvResolver::tryNext() +{ +#ifndef NO_NDNS + d->ndns.resolve(d->servers.first().name); +#else + d->qdns = new QDns; + connect(d->qdns, SIGNAL(resultsReady()), SLOT(ndns_done())); + if(d->aaaa) + d->qdns->setRecordType(QDns::Aaaa); // IPv6 + else + d->qdns->setRecordType(QDns::A); // IPv4 + d->qdns->setLabel(d->servers.first().name); +#endif +} + +void SrvResolver::qdns_done() +{ + if(!d->qdns) + return; + + // apparently we sometimes get this signal even though the results aren't ready + if(d->qdns->isWorking()) + return; + d->t.stop(); + + SafeDeleteLock s(&d->sd); + + // grab the server list and destroy the qdns object + QValueList list; + if(d->qdns->recordType() == QDns::Srv) + list = d->qdns->servers(); + d->qdns->disconnect(this); + d->sd.deleteLater(d->qdns); + d->qdns = 0; + + if(list.isEmpty()) { + stop(); + resultsReady(); + return; + } + sortSRVList(list); + d->servers = list; + + if(d->srvonly) + resultsReady(); + else { + // kick it off + d->aaaa = true; + tryNext(); + } +} + +void SrvResolver::ndns_done() +{ +#ifndef NO_NDNS + SafeDeleteLock s(&d->sd); + + uint r = d->ndns.result(); + int port = d->servers.first().port; + d->servers.remove(d->servers.begin()); + + if(r) { + d->resultAddress = QHostAddress(d->ndns.result()); + d->resultPort = port; + resultsReady(); + } + else { + // failed? bail if last one + if(d->servers.isEmpty()) { + stop(); + resultsReady(); + return; + } + + // otherwise try the next + tryNext(); + } +#else + if(!d->qdns) + return; + + // apparently we sometimes get this signal even though the results aren't ready + if(d->qdns->isWorking()) + return; + + SafeDeleteLock s(&d->sd); + + // grab the address list and destroy the qdns object + QValueList list; + if(d->qdns->recordType() == QDns::A || d->qdns->recordType() == QDns::Aaaa) + list = d->qdns->addresses(); + d->qdns->disconnect(this); + d->sd.deleteLater(d->qdns); + d->qdns = 0; + + if(!list.isEmpty()) { + int port = d->servers.first().port; + d->servers.remove(d->servers.begin()); + d->aaaa = true; + + d->resultAddress = list.first(); + d->resultPort = port; + resultsReady(); + } + else { + if(!d->aaaa) + d->servers.remove(d->servers.begin()); + d->aaaa = !d->aaaa; + + // failed? bail if last one + if(d->servers.isEmpty()) { + stop(); + resultsReady(); + return; + } + + // otherwise try the next + tryNext(); + } +#endif +} + +void SrvResolver::t_timeout() +{ + SafeDeleteLock s(&d->sd); + + stop(); + resultsReady(); +} + +// CS_NAMESPACE_END + +#include "srvresolver.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.h b/kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.h new file mode 100644 index 00000000..6c9ac4f3 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.h @@ -0,0 +1,65 @@ +/* + * srvresolver.h - class to simplify SRV lookups + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_SRVRESOLVER_H +#define CS_SRVRESOLVER_H + +#include +#include + +// CS_NAMESPACE_BEGIN + +class SrvResolver : public QObject +{ + Q_OBJECT +public: + SrvResolver(QObject *parent=0); + ~SrvResolver(); + + void resolve(const QString &server, const QString &type, const QString &proto); + void resolveSrvOnly(const QString &server, const QString &type, const QString &proto); + void next(); + void stop(); + bool isBusy() const; + + QValueList servers() const; + + bool failed() const; + QHostAddress resultAddress() const; + Q_UINT16 resultPort() const; + +signals: + void resultsReady(); + +private slots: + void qdns_done(); + void ndns_done(); + void t_timeout(); + +private: + class Private; + Private *d; + + void tryNext(); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/Makefile.am b/kopete/protocols/jabber/libiris/cutestuff/util/Makefile.am new file mode 100644 index 00000000..649c0fcf --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/Makefile.am @@ -0,0 +1,12 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libcutestuff_util.la +INCLUDES = $(all_includes) + +libcutestuff_util_la_SOURCES = \ + base64.cpp \ + bytestream.cpp \ + qrandom.cpp \ + safedelete.cpp \ + sha1.cpp \ + showtextdlg.cpp diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/TODO b/kopete/protocols/jabber/libiris/cutestuff/util/TODO new file mode 100644 index 00000000..42d94b7d --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/TODO @@ -0,0 +1,7 @@ +varlist +common (opening urls) +zip +showtext +format parsing +xml handling, elem2string, etc + diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/base64.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/base64.cpp new file mode 100644 index 00000000..a17ac335 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/base64.cpp @@ -0,0 +1,182 @@ +/* + * base64.cpp - Base64 converting functions + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"base64.h" + +// CS_NAMESPACE_BEGIN + +//! \class Base64 base64.h +//! \brief Base64 conversion functions. +//! +//! Converts Base64 data between arrays and strings. +//! +//! \code +//! #include "base64.h" +//! +//! ... +//! +//! // encode a block of data into base64 +//! QByteArray block(1024); +//! QByteArray enc = Base64::encode(block); +//! +//! \endcode + +//! +//! Encodes array \a s and returns the result. +QByteArray Base64::encode(const QByteArray &s) +{ + int i; + int len = s.size(); + char tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + int a, b, c; + + QByteArray p((len+2)/3*4); + int at = 0; + for( i = 0; i < len; i += 3 ) { + a = ((unsigned char)s[i] & 3) << 4; + if(i + 1 < len) { + a += (unsigned char)s[i + 1] >> 4; + b = ((unsigned char)s[i + 1] & 0xF) << 2; + if(i + 2 < len) { + b += (unsigned char)s[i + 2] >> 6; + c = (unsigned char)s[i + 2] & 0x3F; + } + else + c = 64; + } + else + b = c = 64; + + p[at++] = tbl[(unsigned char)s[i] >> 2]; + p[at++] = tbl[a]; + p[at++] = tbl[b]; + p[at++] = tbl[c]; + } + return p; +} + +//! +//! Decodes array \a s and returns the result. +QByteArray Base64::decode(const QByteArray &s) +{ + // return value + QByteArray p; + + // -1 specifies invalid + // 64 specifies eof + // everything else specifies data + + char tbl[] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,64,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + }; + + // this should be a multiple of 4 + int len = s.size(); + + if(len % 4) + return p; + + p.resize(len / 4 * 3); + + int i; + int at = 0; + + int a, b, c, d; + c = d = 0; + + for( i = 0; i < len; i += 4 ) { + a = tbl[(int)s[i]]; + b = tbl[(int)s[i + 1]]; + c = tbl[(int)s[i + 2]]; + d = tbl[(int)s[i + 3]]; + if((a == 64 || b == 64) || (a < 0 || b < 0 || c < 0 || d < 0)) { + p.resize(0); + return p; + } + p[at++] = ((a & 0x3F) << 2) | ((b >> 4) & 0x03); + p[at++] = ((b & 0x0F) << 4) | ((c >> 2) & 0x0F); + p[at++] = ((c & 0x03) << 6) | ((d >> 0) & 0x3F); + } + + if(c & 64) + p.resize(at - 2); + else if(d & 64) + p.resize(at - 1); + + return p; +} + +//! +//! Encodes array \a a and returns the result as a string. +QString Base64::arrayToString(const QByteArray &a) +{ + QByteArray b = encode(a); + QCString c; + c.resize(b.size()+1); + memcpy(c.data(), b.data(), b.size()); + return QString::fromLatin1(c); +} + +//! +//! Decodes string \a s and returns the result as an array. +QByteArray Base64::stringToArray(const QString &s) +{ + if(s.isEmpty()) + return QByteArray(); + + // Unfold data + QString us(s); + us.remove('\n'); + + const char *c = us.latin1(); + int len = strlen(c); + QByteArray b(len); + memcpy(b.data(), c, len); + QByteArray a = decode(b); + return a; +} + +//! +//! Encodes string \a s and returns the result as a string. +QString Base64::encodeString(const QString &s) +{ + QCString c = s.utf8(); + int len = c.length(); + QByteArray b(len); + memcpy(b.data(), c.data(), len); + return arrayToString(b); +} + +// CS_NAMESPACE_END diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/base64.h b/kopete/protocols/jabber/libiris/cutestuff/util/base64.h new file mode 100644 index 00000000..128472c1 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/base64.h @@ -0,0 +1,40 @@ +/* + * base64.h - Base64 converting functions + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_BASE64_H +#define CS_BASE64_H + +#include + +// CS_NAMESPACE_BEGIN + +class Base64 +{ +public: + static QByteArray encode(const QByteArray &); + static QByteArray decode(const QByteArray &); + static QString arrayToString(const QByteArray &); + static QByteArray stringToArray(const QString &); + static QString encodeString(const QString &); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/bytestream.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/bytestream.cpp new file mode 100644 index 00000000..1eccb284 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/bytestream.cpp @@ -0,0 +1,268 @@ +/* + * bytestream.cpp - base class for bytestreams + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"bytestream.h" + +// CS_NAMESPACE_BEGIN + +//! \class ByteStream bytestream.h +//! \brief Base class for "bytestreams" +//! +//! This class provides a basic framework for a "bytestream", here defined +//! as a bi-directional, asynchronous pipe of data. It can be used to create +//! several different kinds of bytestream-applications, such as a console or +//! TCP connection, or something more abstract like a security layer or tunnel, +//! all with the same interface. The provided functions make creating such +//! classes simpler. ByteStream is a pure-virtual class, so you do not use it +//! on its own, but instead through a subclass such as \a BSocket. +//! +//! The signals connectionClosed(), delayedCloseFinished(), readyRead(), +//! bytesWritten(), and error() serve the exact same function as those from +//! QSocket. +//! +//! The simplest way to create a ByteStream is to reimplement isOpen(), close(), +//! and tryWrite(). Call appendRead() whenever you want to make data available for +//! reading. ByteStream will take care of the buffers with regards to the caller, +//! and will call tryWrite() when the write buffer gains data. It will be your +//! job to call tryWrite() whenever it is acceptable to write more data to +//! the underlying system. +//! +//! If you need more advanced control, reimplement read(), write(), bytesAvailable(), +//! and/or bytesToWrite() as necessary. +//! +//! Use appendRead(), appendWrite(), takeRead(), and takeWrite() to modify the +//! buffers. If you have more advanced requirements, the buffers can be accessed +//! directly with readBuf() and writeBuf(). +//! +//! Also available are the static convenience functions ByteStream::appendArray() +//! and ByteStream::takeArray(), which make dealing with byte queues very easy. + +class ByteStream::Private +{ +public: + Private() {} + + QByteArray readBuf, writeBuf; +}; + +//! +//! Constructs a ByteStream object with parent \a parent. +ByteStream::ByteStream(QObject *parent) +:QObject(parent) +{ + d = new Private; +} + +//! +//! Destroys the object and frees allocated resources. +ByteStream::~ByteStream() +{ + delete d; +} + +//! +//! Returns TRUE if the stream is open, meaning that you can write to it. +bool ByteStream::isOpen() const +{ + return false; +} + +//! +//! Closes the stream. If there is data in the write buffer then it will be +//! written before actually closing the stream. Once all data has been written, +//! the delayedCloseFinished() signal will be emitted. +//! \sa delayedCloseFinished() +void ByteStream::close() +{ +} + +//! +//! Writes array \a a to the stream. +void ByteStream::write(const QByteArray &a) +{ + if(!isOpen()) + return; + + bool doWrite = bytesToWrite() == 0 ? true: false; + appendWrite(a); + if(doWrite) + tryWrite(); +} + +//! +//! Reads bytes \a bytes of data from the stream and returns them as an array. If \a bytes is 0, then +//! \a read will return all available data. +QByteArray ByteStream::read(int bytes) +{ + return takeRead(bytes); +} + +//! +//! Returns the number of bytes available for reading. +int ByteStream::bytesAvailable() const +{ + return d->readBuf.size(); +} + +//! +//! Returns the number of bytes that are waiting to be written. +int ByteStream::bytesToWrite() const +{ + return d->writeBuf.size(); +} + +//! +//! Writes string \a cs to the stream. +void ByteStream::write(const QCString &cs) +{ + QByteArray block(cs.length()); + memcpy(block.data(), cs.data(), block.size()); + write(block); +} + +//! +//! Clears the read buffer. +void ByteStream::clearReadBuffer() +{ + d->readBuf.resize(0); +} + +//! +//! Clears the write buffer. +void ByteStream::clearWriteBuffer() +{ + d->writeBuf.resize(0); +} + +//! +//! Appends \a block to the end of the read buffer. +void ByteStream::appendRead(const QByteArray &block) +{ + appendArray(&d->readBuf, block); +} + +//! +//! Appends \a block to the end of the write buffer. +void ByteStream::appendWrite(const QByteArray &block) +{ + appendArray(&d->writeBuf, block); +} + +//! +//! Returns \a size bytes from the start of the read buffer. +//! If \a size is 0, then all available data will be returned. +//! If \a del is TRUE, then the bytes are also removed. +QByteArray ByteStream::takeRead(int size, bool del) +{ + return takeArray(&d->readBuf, size, del); +} + +//! +//! Returns \a size bytes from the start of the write buffer. +//! If \a size is 0, then all available data will be returned. +//! If \a del is TRUE, then the bytes are also removed. +QByteArray ByteStream::takeWrite(int size, bool del) +{ + return takeArray(&d->writeBuf, size, del); +} + +//! +//! Returns a reference to the read buffer. +QByteArray & ByteStream::readBuf() +{ + return d->readBuf; +} + +//! +//! Returns a reference to the write buffer. +QByteArray & ByteStream::writeBuf() +{ + return d->writeBuf; +} + +//! +//! Attempts to try and write some bytes from the write buffer, and returns the number +//! successfully written or -1 on error. The default implementation returns -1. +int ByteStream::tryWrite() +{ + return -1; +} + +//! +//! Append array \a b to the end of the array pointed to by \a a. +void ByteStream::appendArray(QByteArray *a, const QByteArray &b) +{ + int oldsize = a->size(); + a->resize(oldsize + b.size()); + memcpy(a->data() + oldsize, b.data(), b.size()); +} + +//! +//! Returns \a size bytes from the start of the array pointed to by \a from. +//! If \a size is 0, then all available data will be returned. +//! If \a del is TRUE, then the bytes are also removed. +QByteArray ByteStream::takeArray(QByteArray *from, int size, bool del) +{ + QByteArray a; + if(size == 0) { + a = from->copy(); + if(del) + from->resize(0); + } + else { + if(size > (int)from->size()) + size = from->size(); + a.resize(size); + char *r = from->data(); + memcpy(a.data(), r, size); + if(del) { + int newsize = from->size()-size; + memmove(r, r+size, newsize); + from->resize(newsize); + } + } + return a; +} + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); + void bytesWritten(int); + void error(int); + +//! \fn void ByteStream::connectionClosed() +//! This signal is emitted when the remote end of the stream closes. + +//! \fn void ByteStream::delayedCloseFinished() +//! This signal is emitted when all pending data has been written to the stream +//! after an attempt to close. + +//! \fn void ByteStream::readyRead() +//! This signal is emitted when data is available to be read. + +//! \fn void ByteStream::bytesWritten(int x); +//! This signal is emitted when data has been successfully written to the stream. +//! \a x is the number of bytes written. + +//! \fn void ByteStream::error(int code) +//! This signal is emitted when an error occurs in the stream. The reason for +//! error is indicated by \a code. + +// CS_NAMESPACE_END +#include "bytestream.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/bytestream.h b/kopete/protocols/jabber/libiris/cutestuff/util/bytestream.h new file mode 100644 index 00000000..c33b3976 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/bytestream.h @@ -0,0 +1,78 @@ +/* + * bytestream.h - base class for bytestreams + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_BYTESTREAM_H +#define CS_BYTESTREAM_H + +#include +#include + +// CS_NAMESPACE_BEGIN + +// CS_EXPORT_BEGIN +class ByteStream : public QObject +{ + Q_OBJECT +public: + enum Error { ErrRead, ErrWrite, ErrCustom = 10 }; + ByteStream(QObject *parent=0); + virtual ~ByteStream()=0; + + virtual bool isOpen() const; + virtual void close(); + virtual void write(const QByteArray &); + virtual QByteArray read(int bytes=0); + virtual int bytesAvailable() const; + virtual int bytesToWrite() const; + + void write(const QCString &); + + static void appendArray(QByteArray *a, const QByteArray &b); + static QByteArray takeArray(QByteArray *from, int size=0, bool del=true); + +signals: + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); + void bytesWritten(int); + void error(int); + +protected: + void clearReadBuffer(); + void clearWriteBuffer(); + void appendRead(const QByteArray &); + void appendWrite(const QByteArray &); + QByteArray takeRead(int size=0, bool del=true); + QByteArray takeWrite(int size=0, bool del=true); + QByteArray & readBuf(); + QByteArray & writeBuf(); + virtual int tryWrite(); + +private: +//! \if _hide_doc_ + class Private; + Private *d; +//! \endif +}; +// CS_EXPORT_END + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/cipher.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/cipher.cpp new file mode 100644 index 00000000..3e2f3a15 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/cipher.cpp @@ -0,0 +1,357 @@ +/* + * cipher.cpp - Simple wrapper to 3DES,AES128/256 CBC ciphers + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"cipher.h" + +#include +#include +#include"bytestream.h" +#include"qrandom.h" + +static bool lib_encryptArray(const EVP_CIPHER *type, const QByteArray &buf, const QByteArray &key, const QByteArray &iv, bool pad, QByteArray *out) +{ + QByteArray result(buf.size()+type->block_size); + int len; + EVP_CIPHER_CTX c; + + unsigned char *ivp = NULL; + if(!iv.isEmpty()) + ivp = (unsigned char *)iv.data(); + EVP_CIPHER_CTX_init(&c); + //EVP_CIPHER_CTX_set_padding(&c, pad ? 1: 0); + if(!EVP_EncryptInit_ex(&c, type, NULL, (unsigned char *)key.data(), ivp)) + return false; + if(!EVP_EncryptUpdate(&c, (unsigned char *)result.data(), &len, (unsigned char *)buf.data(), buf.size())) + return false; + result.resize(len); + if(pad) { + QByteArray last(type->block_size); + if(!EVP_EncryptFinal_ex(&c, (unsigned char *)last.data(), &len)) + return false; + last.resize(len); + ByteStream::appendArray(&result, last); + } + + memset(&c, 0, sizeof(EVP_CIPHER_CTX)); + *out = result; + return true; +} + +static bool lib_decryptArray(const EVP_CIPHER *type, const QByteArray &buf, const QByteArray &key, const QByteArray &iv, bool pad, QByteArray *out) +{ + QByteArray result(buf.size()+type->block_size); + int len; + EVP_CIPHER_CTX c; + + unsigned char *ivp = NULL; + if(!iv.isEmpty()) + ivp = (unsigned char *)iv.data(); + EVP_CIPHER_CTX_init(&c); + //EVP_CIPHER_CTX_set_padding(&c, pad ? 1: 0); + if(!EVP_DecryptInit_ex(&c, type, NULL, (unsigned char *)key.data(), ivp)) + return false; + if(!pad) { + if(!EVP_EncryptUpdate(&c, (unsigned char *)result.data(), &len, (unsigned char *)buf.data(), buf.size())) + return false; + } + else { + if(!EVP_DecryptUpdate(&c, (unsigned char *)result.data(), &len, (unsigned char *)buf.data(), buf.size())) + return false; + } + result.resize(len); + if(pad) { + QByteArray last(type->block_size); + if(!EVP_DecryptFinal_ex(&c, (unsigned char *)last.data(), &len)) + return false; + last.resize(len); + ByteStream::appendArray(&result, last); + } + + memset(&c, 0, sizeof(EVP_CIPHER_CTX)); + *out = result; + return true; +} + +static bool lib_generateKeyIV(const EVP_CIPHER *type, const QByteArray &data, const QByteArray &salt, QByteArray *key, QByteArray *iv) +{ + QByteArray k, i; + unsigned char *kp = 0; + unsigned char *ip = 0; + if(key) { + k.resize(type->key_len); + kp = (unsigned char *)k.data(); + } + if(iv) { + i.resize(type->iv_len); + ip = (unsigned char *)i.data(); + } + if(!EVP_BytesToKey(type, EVP_sha1(), (unsigned char *)salt.data(), (unsigned char *)data.data(), data.size(), 1, kp, ip)) + return false; + if(key) + *key = k; + if(iv) + *iv = i; + return true; +} + +static const EVP_CIPHER * typeToCIPHER(Cipher::Type t) +{ + if(t == Cipher::TripleDES) + return EVP_des_ede3_cbc(); + else if(t == Cipher::AES_128) + return EVP_aes_128_cbc(); + else if(t == Cipher::AES_256) + return EVP_aes_256_cbc(); + else + return 0; +} + +Cipher::Key Cipher::generateKey(Type t) +{ + Key k; + const EVP_CIPHER *type = typeToCIPHER(t); + if(!type) + return k; + QByteArray out; + if(!lib_generateKeyIV(type, QRandom::randomArray(128), QRandom::randomArray(2), &out, 0)) + return k; + k.setType(t); + k.setData(out); + return k; +} + +QByteArray Cipher::generateIV(Type t) +{ + const EVP_CIPHER *type = typeToCIPHER(t); + if(!type) + return QByteArray(); + QByteArray out; + if(!lib_generateKeyIV(type, QCString("Get this man an iv!"), QByteArray(), 0, &out)) + return QByteArray(); + return out; +} + +int Cipher::ivSize(Type t) +{ + const EVP_CIPHER *type = typeToCIPHER(t); + if(!type) + return -1; + return type->iv_len; +} + +QByteArray Cipher::encrypt(const QByteArray &buf, const Key &key, const QByteArray &iv, bool pad, bool *ok) +{ + if(ok) + *ok = false; + const EVP_CIPHER *type = typeToCIPHER(key.type()); + if(!type) + return QByteArray(); + QByteArray out; + if(!lib_encryptArray(type, buf, key.data(), iv, pad, &out)) + return QByteArray(); + + if(ok) + *ok = true; + return out; +} + +QByteArray Cipher::decrypt(const QByteArray &buf, const Key &key, const QByteArray &iv, bool pad, bool *ok) +{ + if(ok) + *ok = false; + const EVP_CIPHER *type = typeToCIPHER(key.type()); + if(!type) + return QByteArray(); + QByteArray out; + if(!lib_decryptArray(type, buf, key.data(), iv, pad, &out)) + return QByteArray(); + + if(ok) + *ok = true; + return out; +} + + +class RSAKey::Private +{ +public: + Private() {} + + RSA *rsa; + int ref; +}; + +RSAKey::RSAKey() +{ + d = 0; +} + +RSAKey::RSAKey(const RSAKey &from) +{ + d = 0; + *this = from; +} + +RSAKey & RSAKey::operator=(const RSAKey &from) +{ + free(); + + if(from.d) { + d = from.d; + ++d->ref; + } + + return *this; +} + +RSAKey::~RSAKey() +{ + free(); +} + +bool RSAKey::isNull() const +{ + return d ? false: true; +} + +void * RSAKey::data() const +{ + if(d) + return (void *)d->rsa; + else + return 0; +} + +void RSAKey::setData(void *p) +{ + free(); + + if(p) { + d = new Private; + d->ref = 1; + d->rsa = (RSA *)p; + } +} + +void RSAKey::free() +{ + if(!d) + return; + + --d->ref; + if(d->ref <= 0) { + RSA_free(d->rsa); + delete d; + } + d = 0; +} + +RSAKey generateRSAKey() +{ + RSA *rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL); + RSAKey key; + if(rsa) + key.setData(rsa); + return key; +} + +QByteArray encryptRSA(const QByteArray &buf, const RSAKey &key, bool *ok) +{ + if(ok) + *ok = false; + + int size = RSA_size((RSA *)key.data()); + int flen = buf.size(); + if(flen >= size - 11) + flen = size - 11; + QByteArray result(size); + unsigned char *from = (unsigned char *)buf.data(); + unsigned char *to = (unsigned char *)result.data(); + int r = RSA_public_encrypt(flen, from, to, (RSA *)key.data(), RSA_PKCS1_PADDING); + if(r == -1) + return QByteArray(); + result.resize(r); + + if(ok) + *ok = true; + return result; +} + +QByteArray decryptRSA(const QByteArray &buf, const RSAKey &key, bool *ok) +{ + if(ok) + *ok = false; + + int size = RSA_size((RSA *)key.data()); + int flen = buf.size(); + QByteArray result(size); + unsigned char *from = (unsigned char *)buf.data(); + unsigned char *to = (unsigned char *)result.data(); + int r = RSA_private_decrypt(flen, from, to, (RSA *)key.data(), RSA_PKCS1_PADDING); + if(r == -1) + return QByteArray(); + result.resize(r); + + if(ok) + *ok = true; + return result; +} + +QByteArray encryptRSA2(const QByteArray &buf, const RSAKey &key, bool *ok) +{ + if(ok) + *ok = false; + + int size = RSA_size((RSA *)key.data()); + int flen = buf.size(); + if(flen >= size - 41) + flen = size - 41; + QByteArray result(size); + unsigned char *from = (unsigned char *)buf.data(); + unsigned char *to = (unsigned char *)result.data(); + int r = RSA_public_encrypt(flen, from, to, (RSA *)key.data(), RSA_PKCS1_OAEP_PADDING); + if(r == -1) + return QByteArray(); + result.resize(r); + + if(ok) + *ok = true; + return result; +} + +QByteArray decryptRSA2(const QByteArray &buf, const RSAKey &key, bool *ok) +{ + if(ok) + *ok = false; + + int size = RSA_size((RSA *)key.data()); + int flen = buf.size(); + QByteArray result(size); + unsigned char *from = (unsigned char *)buf.data(); + unsigned char *to = (unsigned char *)result.data(); + int r = RSA_private_decrypt(flen, from, to, (RSA *)key.data(), RSA_PKCS1_OAEP_PADDING); + if(r == -1) + return QByteArray(); + result.resize(r); + + if(ok) + *ok = true; + return result; +} diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/cipher.h b/kopete/protocols/jabber/libiris/cutestuff/util/cipher.h new file mode 100644 index 00000000..f162f16a --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/cipher.h @@ -0,0 +1,79 @@ +/* + * cipher.h - Simple wrapper to 3DES,AES128/256 CBC ciphers + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_CIPHER_H +#define CS_CIPHER_H + +#include +#include + +namespace Cipher +{ + enum Type { None, TripleDES, AES_128, AES_256 }; + + class Key + { + public: + Key() { v_type = None; } + + bool isValid() const { return (v_type == None ? false: true); } + void setType(Type x) { v_type = x; } + Type type() const { return v_type; } + void setData(const QByteArray &d) { v_data = d; } + const QByteArray & data() const { return v_data; } + + private: + Type v_type; + QByteArray v_data; + }; + + Key generateKey(Type); + QByteArray generateIV(Type); + int ivSize(Type); + QByteArray encrypt(const QByteArray &, const Key &, const QByteArray &iv, bool pad, bool *ok=0); + QByteArray decrypt(const QByteArray &, const Key &, const QByteArray &iv, bool pad, bool *ok=0); +} + +class RSAKey +{ +public: + RSAKey(); + RSAKey(const RSAKey &); + RSAKey & operator=(const RSAKey &); + ~RSAKey(); + + bool isNull() const; + void *data() const; + void setData(void *); + +private: + class Private; + Private *d; + + void free(); +}; + +RSAKey generateRSAKey(); +QByteArray encryptRSA(const QByteArray &buf, const RSAKey &key, bool *ok=0); +QByteArray decryptRSA(const QByteArray &buf, const RSAKey &key, bool *ok=0); +QByteArray encryptRSA2(const QByteArray &buf, const RSAKey &key, bool *ok=0); +QByteArray decryptRSA2(const QByteArray &buf, const RSAKey &key, bool *ok=0); + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/qrandom.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/qrandom.cpp new file mode 100644 index 00000000..3becd7c5 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/qrandom.cpp @@ -0,0 +1,24 @@ +#include"qrandom.h" + +#include + +uchar QRandom::randomChar() +{ + return rand(); +} + +uint QRandom::randomInt() +{ + QByteArray a = randomArray(sizeof(uint)); + uint x; + memcpy(&x, a.data(), a.size()); + return x; +} + +QByteArray QRandom::randomArray(uint size) +{ + QByteArray a(size); + for(uint n = 0; n < size; ++n) + a[n] = randomChar(); + return a; +} diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/qrandom.h b/kopete/protocols/jabber/libiris/cutestuff/util/qrandom.h new file mode 100644 index 00000000..92339fb0 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/qrandom.h @@ -0,0 +1,14 @@ +#ifndef CS_QRANDOM_H +#define CS_QRANDOM_H + +#include + +class QRandom +{ +public: + static uchar randomChar(); + static uint randomInt(); + static QByteArray randomArray(uint size); +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/safedelete.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/safedelete.cpp new file mode 100644 index 00000000..6bd012e9 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/safedelete.cpp @@ -0,0 +1,119 @@ +#include"safedelete.h" + +#include + +//---------------------------------------------------------------------------- +// SafeDelete +//---------------------------------------------------------------------------- +SafeDelete::SafeDelete() +{ + lock = 0; +} + +SafeDelete::~SafeDelete() +{ + if(lock) + lock->dying(); +} + +void SafeDelete::deleteLater(QObject *o) +{ + if(!lock) + deleteSingle(o); + else + list.append(o); +} + +void SafeDelete::unlock() +{ + lock = 0; + deleteAll(); +} + +void SafeDelete::deleteAll() +{ + if(list.isEmpty()) + return; + + QObjectListIt it(list); + for(QObject *o; (o = it.current()); ++it) + deleteSingle(o); + list.clear(); +} + +void SafeDelete::deleteSingle(QObject *o) +{ +#if QT_VERSION < 0x030000 + // roll our own QObject::deleteLater() + SafeDeleteLater *sdl = SafeDeleteLater::ensureExists(); + sdl->deleteItLater(o); +#else + o->deleteLater(); +#endif +} + +//---------------------------------------------------------------------------- +// SafeDeleteLock +//---------------------------------------------------------------------------- +SafeDeleteLock::SafeDeleteLock(SafeDelete *sd) +{ + own = false; + if(!sd->lock) { + _sd = sd; + _sd->lock = this; + } + else + _sd = 0; +} + +SafeDeleteLock::~SafeDeleteLock() +{ + if(_sd) { + _sd->unlock(); + if(own) + delete _sd; + } +} + +void SafeDeleteLock::dying() +{ + _sd = new SafeDelete(*_sd); + own = true; +} + +//---------------------------------------------------------------------------- +// SafeDeleteLater +//---------------------------------------------------------------------------- +SafeDeleteLater *SafeDeleteLater::self = 0; + +SafeDeleteLater *SafeDeleteLater::ensureExists() +{ + if(!self) + new SafeDeleteLater(); + return self; +} + +SafeDeleteLater::SafeDeleteLater() +{ + list.setAutoDelete(true); + self = this; + QTimer::singleShot(0, this, SLOT(explode())); +} + +SafeDeleteLater::~SafeDeleteLater() +{ + list.clear(); + self = 0; +} + +void SafeDeleteLater::deleteItLater(QObject *o) +{ + list.append(o); +} + +void SafeDeleteLater::explode() +{ + delete this; +} + +#include "safedelete.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/safedelete.h b/kopete/protocols/jabber/libiris/cutestuff/util/safedelete.h new file mode 100644 index 00000000..078d36cd --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/safedelete.h @@ -0,0 +1,60 @@ +#ifndef SAFEDELETE_H +#define SAFEDELETE_H + +#include +#include + +class SafeDelete; +class SafeDeleteLock +{ +public: + SafeDeleteLock(SafeDelete *sd); + ~SafeDeleteLock(); + +private: + SafeDelete *_sd; + bool own; + friend class SafeDelete; + void dying(); +}; + +class SafeDelete +{ +public: + SafeDelete(); + ~SafeDelete(); + + void deleteLater(QObject *o); + + // same as QObject::deleteLater() + static void deleteSingle(QObject *o); + +private: + QObjectList list; + void deleteAll(); + + friend class SafeDeleteLock; + SafeDeleteLock *lock; + void unlock(); +}; + +class SafeDeleteLater : public QObject +{ + Q_OBJECT +public: + static SafeDeleteLater *ensureExists(); + void deleteItLater(QObject *o); + +private slots: + void explode(); + +private: + SafeDeleteLater(); + ~SafeDeleteLater(); + + QObjectList list; + friend class SafeDelete; + static SafeDeleteLater *self; +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/sha1.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/sha1.cpp new file mode 100644 index 00000000..3e3eb07c --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/sha1.cpp @@ -0,0 +1,196 @@ +/* + * sha1.cpp - Secure Hash Algorithm 1 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"sha1.h" + +// CS_NAMESPACE_BEGIN + +/**************************************************************************** + SHA1 - from a public domain implementation by Steve Reid (steve@edmweb.com) +****************************************************************************/ + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15]^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +SHA1::SHA1() +{ + int wordSize; + + qSysInfo(&wordSize, &bigEndian); +} + +unsigned long SHA1::blk0(Q_UINT32 i) +{ + if(bigEndian) + return block->l[i]; + else + return (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) | (rol(block->l[i],8)&0x00FF00FF)); +} + +// Hash a single 512-bit block. This is the core of the algorithm. +void SHA1::transform(Q_UINT32 state[5], unsigned char buffer[64]) +{ + Q_UINT32 a, b, c, d, e; + + block = (CHAR64LONG16*)buffer; + + // Copy context->state[] to working vars + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + // 4 rounds of 20 operations each. Loop unrolled. + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + // Add the working vars back into context.state[] + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + // Wipe variables + a = b = c = d = e = 0; +} + +// SHA1Init - Initialize new context +void SHA1::init(SHA1_CONTEXT* context) +{ + // SHA1 initialization constants + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +// Run your data through this +void SHA1::update(SHA1_CONTEXT* context, unsigned char* data, Q_UINT32 len) +{ + Q_UINT32 i, j; + + j = (context->count[0] >> 3) & 63; + if((context->count[0] += len << 3) < (len << 3)) + context->count[1]++; + + context->count[1] += (len >> 29); + + if((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + +// Add padding and return the message digest +void SHA1::final(unsigned char digest[20], SHA1_CONTEXT* context) +{ + Q_UINT32 i, j; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); // Endian independent + } + update(context, (unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) { + update(context, (unsigned char *)"\0", 1); + } + update(context, finalcount, 8); // Should cause a transform() + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + + // Wipe variables + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +} + +QByteArray SHA1::hash(const QByteArray &a) +{ + SHA1_CONTEXT context; + QByteArray b(20); + + SHA1 s; + s.init(&context); + s.update(&context, (unsigned char *)a.data(), (unsigned int)a.size()); + s.final((unsigned char *)b.data(), &context); + return b; +} + +QByteArray SHA1::hashString(const QCString &cs) +{ + QByteArray a(cs.length()); + memcpy(a.data(), cs.data(), a.size()); + return SHA1::hash(a); +} + +QString SHA1::digest(const QString &in) +{ + QByteArray a = SHA1::hashString(in.utf8()); + QString out; + for(int n = 0; n < (int)a.size(); ++n) { + QString str; + str.sprintf("%02x", (uchar)a[n]); + out.append(str); + } + + return out; +} + +// CS_NAMESPACE_END diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/sha1.h b/kopete/protocols/jabber/libiris/cutestuff/util/sha1.h new file mode 100644 index 00000000..6b0453b4 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/sha1.h @@ -0,0 +1,63 @@ +/* + * sha1.h - Secure Hash Algorithm 1 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_SHA1_H +#define CS_SHA1_H + +#include + +// CS_NAMESPACE_BEGIN + +class SHA1 +{ +public: + static QByteArray hash(const QByteArray &); + static QByteArray hashString(const QCString &); + static QString digest(const QString &); + +private: + SHA1(); + + struct SHA1_CONTEXT + { + Q_UINT32 state[5]; + Q_UINT32 count[2]; + unsigned char buffer[64]; + }; + + typedef union { + unsigned char c[64]; + Q_UINT32 l[16]; + } CHAR64LONG16; + + void transform(Q_UINT32 state[5], unsigned char buffer[64]); + void init(SHA1_CONTEXT* context); + void update(SHA1_CONTEXT* context, unsigned char* data, Q_UINT32 len); + void final(unsigned char digest[20], SHA1_CONTEXT* context); + + unsigned long blk0(Q_UINT32 i); + bool bigEndian; + + CHAR64LONG16* block; +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.cpp new file mode 100644 index 00000000..0b02df60 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.cpp @@ -0,0 +1,61 @@ +/* + * showtextdlg.cpp - dialog for displaying a text file + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"showtextdlg.h" + +#include +#include +#include +#include +#include + + +ShowTextDlg::ShowTextDlg(const QString &fname, bool rich, QWidget *parent, const char *name) +:QDialog(parent, name, FALSE, WDestructiveClose) +{ + QString text; + + QFile f(fname); + if(f.open(IO_ReadOnly)) { + QTextStream t(&f); + while(!t.eof()) + text += t.readLine() + '\n'; + f.close(); + } + + QVBoxLayout *vb1 = new QVBoxLayout(this, 8); + QTextEdit *te = new QTextEdit(this); + te->setReadOnly(TRUE); + te->setTextFormat(rich ? QTextEdit::RichText : QTextEdit::PlainText); + te->setText(text); + + vb1->addWidget(te); + + QHBoxLayout *hb1 = new QHBoxLayout(vb1); + hb1->addStretch(1); + QPushButton *pb = new QPushButton(tr("&OK"), this); + connect(pb, SIGNAL(clicked()), SLOT(accept())); + hb1->addWidget(pb); + hb1->addStretch(1); + + resize(560, 384); +} + +#include "showtextdlg.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.h b/kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.h new file mode 100644 index 00000000..f59ae32c --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.h @@ -0,0 +1,33 @@ +/* + * showtextdlg.h - dialog for displaying a text file + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_SHOWTEXTDLG_H +#define CS_SHOWTEXTDLG_H + +#include + +class ShowTextDlg : public QDialog +{ + Q_OBJECT +public: + ShowTextDlg(const QString &fname, bool rich=FALSE, QWidget *parent=0, const char *name=0); +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/Makefile.am b/kopete/protocols/jabber/libiris/iris/Makefile.am new file mode 100644 index 00000000..03e5818f --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = include jabber xmpp-core xmpp-im diff --git a/kopete/protocols/jabber/libiris/iris/TODO b/kopete/protocols/jabber/libiris/iris/TODO new file mode 100644 index 00000000..e6cf74c6 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/TODO @@ -0,0 +1,16 @@ +- Stream::id(), Stream::lang() +- whitespace pings (but disable when using http poll) +- make stanza error conditions work for both 1.0 and old + +- xmpp-im (messages, roster, subscriptions, presence, privacy) +- document xmpp-core +- provide complete support for xmpp-core. this means all functionality from + the draft, and noting behavior issues (like IQ semantics) in the + library documentation. + +- SASL "EXTERNAL" w/ client certificate +- SASL "ANONYMOUS" ? + +credits: + MD5 algorithm by Peter Deutsch (Aladdin Enterprises) + diff --git a/kopete/protocols/jabber/libiris/iris/include/Makefile.am b/kopete/protocols/jabber/libiris/iris/include/Makefile.am new file mode 100644 index 00000000..6375392b --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/include/Makefile.am @@ -0,0 +1,7 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libiris.la +INCLUDES = -Ixmpp-core -Ixmpp-im -I../cutestuff/util -I../cutestuff/network -I../qca/src $(all_includes) + +libiris_la_SOURCES = \ + empty.cpp diff --git a/kopete/protocols/jabber/libiris/iris/include/empty.cpp b/kopete/protocols/jabber/libiris/iris/include/empty.cpp new file mode 100644 index 00000000..e69de29b diff --git a/kopete/protocols/jabber/libiris/iris/include/im.h b/kopete/protocols/jabber/libiris/iris/include/im.h new file mode 100644 index 00000000..832ec62a --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/include/im.h @@ -0,0 +1,721 @@ +/* + * im.h - XMPP "IM" library API + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMPP_IM_H +#define XMPP_IM_H + +#include +#include +#include"xmpp.h" + +namespace XMPP +{ + class Url + { + public: + Url(const QString &url="", const QString &desc=""); + Url(const Url &); + Url & operator=(const Url &); + ~Url(); + + QString url() const; + QString desc() const; + + void setUrl(const QString &); + void setDesc(const QString &); + + private: + class Private; + Private *d; + }; + + typedef QValueList UrlList; + typedef QMap StringMap; + typedef enum { OfflineEvent, DeliveredEvent, DisplayedEvent, + ComposingEvent, CancelEvent, InactiveEvent, GoneEvent } MsgEvent; + + class Message + { + public: + Message(const Jid &to=""); + Message(const Message &from); + Message & operator=(const Message &from); + ~Message(); + + Jid to() const; + Jid from() const; + QString id() const; + QString type() const; + QString lang() const; + QString subject(const QString &lang="") const; + QString body(const QString &lang="") const; + QString xHTMLBody(const QString &lang="") const; + QString thread() const; + Stanza::Error error() const; + + void setTo(const Jid &j); + void setFrom(const Jid &j); + void setId(const QString &s); + void setType(const QString &s); + void setLang(const QString &s); + void setSubject(const QString &s, const QString &lang=""); + void setBody(const QString &s, const QString &lang=""); + void setXHTMLBody(const QString &s, const QString &lang="", const QString &attr = ""); + void setThread(const QString &s); + void setError(const Stanza::Error &err); + + // JEP-0091 + QDateTime timeStamp() const; + void setTimeStamp(const QDateTime &ts); + + // JEP-0066 + UrlList urlList() const; + void urlAdd(const Url &u); + void urlsClear(); + void setUrlList(const UrlList &list); + + // JEP-0022 + QString eventId() const; + void setEventId(const QString& id); + bool containsEvents() const; + bool containsEvent(MsgEvent e) const; + void addEvent(MsgEvent e); + + // JEP-0027 + QString xencrypted() const; + void setXEncrypted(const QString &s); + + // Obsolete invitation + QString invite() const; + void setInvite(const QString &s); + + // for compatibility. delete me later + bool spooled() const; + void setSpooled(bool); + bool wasEncrypted() const; + void setWasEncrypted(bool); + + Stanza toStanza(Stream *stream) const; + bool fromStanza(const Stanza &s, int tzoffset); + + private: + class Private; + Private *d; + }; + + class Subscription + { + public: + enum SubType { None, To, From, Both, Remove }; + + Subscription(SubType type=None); + + int type() const; + + QString toString() const; + bool fromString(const QString &); + + private: + SubType value; + }; + + class Status + { + public: + Status(const QString &show="", const QString &status="", int priority=0, bool available=true); + ~Status(); + + int priority() const; + const QString & show() const; + const QString & status() const; + QDateTime timeStamp() const; + const QString & keyID() const; + bool isAvailable() const; + bool isAway() const; + bool isInvisible() const; + bool hasError() const; + int errorCode() const; + const QString & errorString() const; + + const QString & xsigned() const; + const QString & songTitle() const; + const QString & capsNode() const; + const QString & capsVersion() const; + const QString & capsExt() const; + + void setPriority(int); + void setShow(const QString &); + void setStatus(const QString &); + void setTimeStamp(const QDateTime &); + void setKeyID(const QString &); + void setIsAvailable(bool); + void setIsInvisible(bool); + void setError(int, const QString &); + void setCapsNode(const QString&); + void setCapsVersion(const QString&); + void setCapsExt(const QString&); + + void setXSigned(const QString &); + void setSongTitle(const QString &); + + private: + int v_priority; + QString v_show, v_status, v_key; + QDateTime v_timeStamp; + bool v_isAvailable; + bool v_isInvisible; + + QString v_xsigned; + // gabber song extension + QString v_songTitle; + QString v_capsNode, v_capsVersion, v_capsExt; + + int ecode; + QString estr; + + class Private; + Private *d; + }; + + class Resource + { + public: + Resource(const QString &name="", const Status &s=Status()); + ~Resource(); + + const QString & name() const; + int priority() const; + const Status & status() const; + + void setName(const QString &); + void setStatus(const Status &); + + private: + QString v_name; + Status v_status; + + class ResourcePrivate *d; + }; + + class ResourceList : public QValueList + { + public: + ResourceList(); + ~ResourceList(); + + ResourceList::Iterator find(const QString &); + ResourceList::Iterator priority(); + + ResourceList::ConstIterator find(const QString &) const; + ResourceList::ConstIterator priority() const; + + private: + class ResourceListPrivate *d; + }; + + class RosterItem + { + public: + RosterItem(const Jid &jid=""); + virtual ~RosterItem(); + + const Jid & jid() const; + const QString & name() const; + const QStringList & groups() const; + const Subscription & subscription() const; + const QString & ask() const; + bool isPush() const; + bool inGroup(const QString &) const; + + virtual void setJid(const Jid &); + void setName(const QString &); + void setGroups(const QStringList &); + void setSubscription(const Subscription &); + void setAsk(const QString &); + void setIsPush(bool); + bool addGroup(const QString &); + bool removeGroup(const QString &); + + QDomElement toXml(QDomDocument *) const; + bool fromXml(const QDomElement &); + + private: + Jid v_jid; + QString v_name; + QStringList v_groups; + Subscription v_subscription; + QString v_ask; + bool v_push; + + class RosterItemPrivate *d; + }; + + class Roster : public QValueList + { + public: + Roster(); + ~Roster(); + + Roster::Iterator find(const Jid &); + Roster::ConstIterator find(const Jid &) const; + + private: + class RosterPrivate *d; + }; + + class Features + { + public: + Features(); + Features(const QStringList &); + Features(const QString &); + ~Features(); + + QStringList list() const; // actual featurelist + void setList(const QStringList &); + + // features + bool canRegister() const; + bool canSearch() const; + bool canGroupchat() const; + bool canVoice() const; + bool canDisco() const; + bool canXHTML() const; + bool isGateway() const; + bool haveVCard() const; + + enum FeatureID { + FID_Invalid = -1, + FID_None, + FID_Register, + FID_Search, + FID_Groupchat, + FID_Disco, + FID_Gateway, + FID_VCard, + FID_Xhtml, + + // private Psi actions + FID_Add + }; + + // useful functions + bool test(const QStringList &) const; + + QString name() const; + static QString name(long id); + static QString name(const QString &feature); + + long id() const; + static long id(const QString &feature); + static QString feature(long id); + + class FeatureName; + private: + QStringList _list; + }; + + class AgentItem + { + public: + AgentItem() { } + + const Jid & jid() const { return v_jid; } + const QString & name() const { return v_name; } + const QString & category() const { return v_category; } + const QString & type() const { return v_type; } + const Features & features() const { return v_features; } + + void setJid(const Jid &j) { v_jid = j; } + void setName(const QString &n) { v_name = n; } + void setCategory(const QString &c) { v_category = c; } + void setType(const QString &t) { v_type = t; } + void setFeatures(const Features &f) { v_features = f; } + + private: + Jid v_jid; + QString v_name, v_category, v_type; + Features v_features; + }; + + typedef QValueList AgentList; + + class DiscoItem + { + public: + DiscoItem(); + ~DiscoItem(); + + const Jid &jid() const; + const QString &node() const; + const QString &name() const; + + void setJid(const Jid &); + void setName(const QString &); + void setNode(const QString &); + + enum Action { + None = 0, + Remove, + Update + }; + + Action action() const; + void setAction(Action); + + const Features &features() const; + void setFeatures(const Features &); + + struct Identity + { + QString category; + QString name; + QString type; + }; + + typedef QValueList Identities; + + const Identities &identities() const; + void setIdentities(const Identities &); + + // some useful helper functions + static Action string2action(QString s); + static QString action2string(Action a); + + DiscoItem & operator= (const DiscoItem &); + DiscoItem(const DiscoItem &); + + operator AgentItem() const { return toAgentItem(); } + AgentItem toAgentItem() const; + void fromAgentItem(const AgentItem &); + + private: + class Private; + Private *d; + }; + + typedef QValueList DiscoList; + + class FormField + { + public: + enum { username, nick, password, name, first, last, email, address, city, state, zip, phone, url, date, misc }; + FormField(const QString &type="", const QString &value=""); + ~FormField(); + + int type() const; + QString fieldName() const; + QString realName() const; + bool isSecret() const; + const QString & value() const; + void setType(int); + bool setType(const QString &); + void setValue(const QString &); + + private: + int tagNameToType(const QString &) const; + QString typeToTagName(int) const; + + int v_type; + QString v_value; + + class Private; + Private *d; + }; + + class Form : public QValueList + { + public: + Form(const Jid &j=""); + ~Form(); + + Jid jid() const; + QString instructions() const; + QString key() const; + void setJid(const Jid &); + void setInstructions(const QString &); + void setKey(const QString &); + + private: + Jid v_jid; + QString v_instructions, v_key; + + class Private; + Private *d; + }; + + class SearchResult + { + public: + SearchResult(const Jid &jid=""); + ~SearchResult(); + + const Jid & jid() const; + const QString & nick() const; + const QString & first() const; + const QString & last() const; + const QString & email() const; + + void setJid(const Jid &); + void setNick(const QString &); + void setFirst(const QString &); + void setLast(const QString &); + void setEmail(const QString &); + + private: + Jid v_jid; + QString v_nick, v_first, v_last, v_email; + }; + + class Client; + class LiveRosterItem; + class LiveRoster; + class S5BManager; + class IBBManager; + class JidLinkManager; + class FileTransferManager; + + class Task : public QObject + { + Q_OBJECT + public: + enum { ErrDisc }; + Task(Task *parent); + Task(Client *, bool isRoot); + virtual ~Task(); + + Task *parent() const; + Client *client() const; + QDomDocument *doc() const; + QString id() const; + + bool success() const; + int statusCode() const; + const QString & statusString() const; + + void go(bool autoDelete=false); + virtual bool take(const QDomElement &); + void safeDelete(); + + signals: + void finished(); + + protected: + virtual void onGo(); + virtual void onDisconnect(); + void send(const QDomElement &); + void setSuccess(int code=0, const QString &str=""); + void setError(const QDomElement &); + void setError(int code=0, const QString &str=""); + void debug(const char *, ...); + void debug(const QString &); + bool iqVerify(const QDomElement &x, const Jid &to, const QString &id, const QString &xmlns=""); + + private slots: + void clientDisconnected(); + void done(); + + private: + void init(); + + class TaskPrivate; + TaskPrivate *d; + }; + + class Client : public QObject + { + Q_OBJECT + + public: + Client(QObject *parent=0); + ~Client(); + + bool isActive() const; + void connectToServer(ClientStream *s, const Jid &j, bool auth=true); + void start(const QString &host, const QString &user, const QString &pass, const QString &resource); + void close(bool fast=false); + + Stream & stream(); + const LiveRoster & roster() const; + const ResourceList & resourceList() const; + + void send(const QDomElement &); + void send(const QString &); + + QString host() const; + QString user() const; + QString pass() const; + QString resource() const; + Jid jid() const; + + void rosterRequest(); + void sendMessage(const Message &); + void sendSubscription(const Jid &, const QString &); + void setPresence(const Status &); + + void debug(const QString &); + QString genUniqueId(); + Task *rootTask(); + QDomDocument *doc() const; + + QString OSName() const; + QString timeZone() const; + int timeZoneOffset() const; + QString clientName() const; + QString clientVersion() const; + QString capsNode() const; + QString capsVersion() const; + QString capsExt() const; + + void setOSName(const QString &); + void setTimeZone(const QString &, int); + void setClientName(const QString &); + void setClientVersion(const QString &); + void setCapsNode(const QString &); + void setCapsVersion(const QString &); + + void setIdentity(DiscoItem::Identity); + DiscoItem::Identity identity(); + + void addExtension(const QString& ext, const Features& f); + void removeExtension(const QString& ext); + const Features& extension(const QString& ext) const; + QStringList extensions() const; + + S5BManager *s5bManager() const; + IBBManager *ibbManager() const; + JidLinkManager *jidLinkManager() const; + + void setFileTransferEnabled(bool b); + FileTransferManager *fileTransferManager() const; + + bool groupChatJoin(const QString &host, const QString &room, const QString &nick); + bool groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &password); + void groupChatSetStatus(const QString &host, const QString &room, const Status &); + void groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &); + void groupChatLeave(const QString &host, const QString &room); + + signals: + void activated(); + void disconnected(); + //void authFinished(bool, int, const QString &); + void rosterRequestFinished(bool, int, const QString &); + void rosterItemAdded(const RosterItem &); + void rosterItemUpdated(const RosterItem &); + void rosterItemRemoved(const RosterItem &); + void resourceAvailable(const Jid &, const Resource &); + void resourceUnavailable(const Jid &, const Resource &); + void presenceError(const Jid &, int, const QString &); + void subscription(const Jid &, const QString &); + void messageReceived(const Message &); + void debugText(const QString &); + void xmlIncoming(const QString &); + void xmlOutgoing(const QString &); + void groupChatJoined(const Jid &); + void groupChatLeft(const Jid &); + void groupChatPresence(const Jid &, const Status &); + void groupChatError(const Jid &, int, const QString &); + + void incomingJidLink(); + + private slots: + //void streamConnected(); + //void streamHandshaken(); + //void streamError(const StreamError &); + //void streamSSLCertificateReady(const QSSLCert &); + //void streamCloseFinished(); + void streamError(int); + void streamReadyRead(); + void streamIncomingXml(const QString &); + void streamOutgoingXml(const QString &); + + void slotRosterRequestFinished(); + + // basic daemons + void ppSubscription(const Jid &, const QString &); + void ppPresence(const Jid &, const Status &); + void pmMessage(const Message &); + void prRoster(const Roster &); + + void s5b_incomingReady(); + void ibb_incomingReady(); + + public: + class GroupChat; + private: + void cleanup(); + void distribute(const QDomElement &); + void importRoster(const Roster &); + void importRosterItem(const RosterItem &); + void updateSelfPresence(const Jid &, const Status &); + void updatePresence(LiveRosterItem *, const Jid &, const Status &); + + class ClientPrivate; + ClientPrivate *d; + }; + + class LiveRosterItem : public RosterItem + { + public: + LiveRosterItem(const Jid &j=""); + LiveRosterItem(const RosterItem &); + ~LiveRosterItem(); + + void setRosterItem(const RosterItem &); + + ResourceList & resourceList(); + ResourceList::Iterator priority(); + + const ResourceList & resourceList() const; + ResourceList::ConstIterator priority() const; + + bool isAvailable() const; + const Status & lastUnavailableStatus() const; + bool flagForDelete() const; + + void setLastUnavailableStatus(const Status &); + void setFlagForDelete(bool); + + private: + ResourceList v_resourceList; + Status v_lastUnavailableStatus; + bool v_flagForDelete; + + class LiveRosterItemPrivate; + LiveRosterItemPrivate *d; + }; + + class LiveRoster : public QValueList + { + public: + LiveRoster(); + ~LiveRoster(); + + void flagAllForDelete(); + LiveRoster::Iterator find(const Jid &, bool compareRes=true); + LiveRoster::ConstIterator find(const Jid &, bool compareRes=true) const; + + private: + class LiveRosterPrivate; + LiveRosterPrivate *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/include/xmpp.h b/kopete/protocols/jabber/libiris/iris/include/xmpp.h new file mode 100644 index 00000000..5636f963 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/include/xmpp.h @@ -0,0 +1,553 @@ +/* + * xmpp.h - XMPP "core" library API + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMPP_H +#define XMPP_H + +#include +#include +#include +#include +#include +#include +#include + +namespace QCA +{ + class TLS; +} + +#ifndef CS_XMPP +class ByteStream; +#endif + +namespace XMPP +{ + // CS_IMPORT_BEGIN cutestuff/bytestream.h +#ifdef CS_XMPP + class ByteStream; +#endif + // CS_IMPORT_END + + class Debug + { + public: + virtual ~Debug(); + + virtual void msg(const QString &)=0; + virtual void outgoingTag(const QString &)=0; + virtual void incomingTag(const QString &)=0; + virtual void outgoingXml(const QDomElement &)=0; + virtual void incomingXml(const QDomElement &)=0; + }; + + void setDebug(Debug *); + + class Connector : public QObject + { + Q_OBJECT + public: + Connector(QObject *parent=0); + virtual ~Connector(); + + virtual void connectToServer(const QString &server)=0; + virtual ByteStream *stream() const=0; + virtual void done()=0; + + bool useSSL() const; + bool havePeerAddress() const; + QHostAddress peerAddress() const; + Q_UINT16 peerPort() const; + + signals: + void connected(); + void error(); + + protected: + void setUseSSL(bool b); + void setPeerAddressNone(); + void setPeerAddress(const QHostAddress &addr, Q_UINT16 port); + + private: + bool ssl; + bool haveaddr; + QHostAddress addr; + Q_UINT16 port; + }; + + class AdvancedConnector : public Connector + { + Q_OBJECT + public: + enum Error { ErrConnectionRefused, ErrHostNotFound, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth, ErrStream }; + AdvancedConnector(QObject *parent=0); + virtual ~AdvancedConnector(); + + class Proxy + { + public: + enum { None, HttpConnect, HttpPoll, Socks }; + Proxy(); + ~Proxy(); + + int type() const; + QString host() const; + Q_UINT16 port() const; + QString url() const; + QString user() const; + QString pass() const; + int pollInterval() const; + + void setHttpConnect(const QString &host, Q_UINT16 port); + void setHttpPoll(const QString &host, Q_UINT16 port, const QString &url); + void setSocks(const QString &host, Q_UINT16 port); + void setUserPass(const QString &user, const QString &pass); + void setPollInterval(int secs); + + private: + int t; + QString v_host, v_url; + Q_UINT16 v_port; + QString v_user, v_pass; + int v_poll; + }; + + void setProxy(const Proxy &proxy); + void setOptHostPort(const QString &host, Q_UINT16 port); + void setOptProbe(bool); + void setOptSSL(bool); + + void changePollInterval(int secs); + + void connectToServer(const QString &server); + ByteStream *stream() const; + void done(); + + int errorCode() const; + + signals: + void srvLookup(const QString &server); + void srvResult(bool success); + void httpSyncStarted(); + void httpSyncFinished(); + + private slots: + void dns_done(); + void srv_done(); + void bs_connected(); + void bs_error(int); + void http_syncStarted(); + void http_syncFinished(); + + private: + class Private; + Private *d; + + void cleanup(); + void do_resolve(); + void do_connect(); + void tryNextSrv(); + }; + + class TLSHandler : public QObject + { + Q_OBJECT + public: + TLSHandler(QObject *parent=0); + virtual ~TLSHandler(); + + virtual void reset()=0; + virtual void startClient(const QString &host)=0; + virtual void write(const QByteArray &a)=0; + virtual void writeIncoming(const QByteArray &a)=0; + + signals: + void success(); + void fail(); + void closed(); + void readyRead(const QByteArray &a); + void readyReadOutgoing(const QByteArray &a, int plainBytes); + }; + + class QCATLSHandler : public TLSHandler + { + Q_OBJECT + public: + QCATLSHandler(QCA::TLS *parent); + ~QCATLSHandler(); + + QCA::TLS *tls() const; + int tlsError() const; + + void reset(); + void startClient(const QString &host); + void write(const QByteArray &a); + void writeIncoming(const QByteArray &a); + + signals: + void tlsHandshaken(); + + public slots: + void continueAfterHandshake(); + + private slots: + void tls_handshaken(); + void tls_readyRead(); + void tls_readyReadOutgoing(int); + void tls_closed(); + void tls_error(int); + + private: + class Private; + Private *d; + }; + + class Jid + { + public: + Jid(); + ~Jid(); + + Jid(const QString &s); + Jid(const char *s); + Jid & operator=(const QString &s); + Jid & operator=(const char *s); + + void set(const QString &s); + void set(const QString &domain, const QString &node, const QString &resource=""); + + void setDomain(const QString &s); + void setNode(const QString &s); + void setResource(const QString &s); + + const QString & domain() const { return d; } + const QString & node() const { return n; } + const QString & resource() const { return r; } + const QString & bare() const { return b; } + const QString & full() const { return f; } + + Jid withNode(const QString &s) const; + Jid withResource(const QString &s) const; + + bool isValid() const; + bool isEmpty() const; + bool compare(const Jid &a, bool compareRes=true) const; + + static bool validDomain(const QString &s, QString *norm=0); + static bool validNode(const QString &s, QString *norm=0); + static bool validResource(const QString &s, QString *norm=0); + + // TODO: kill these later + const QString & host() const { return d; } + const QString & user() const { return n; } + const QString & userHost() const { return b; } + + private: + void reset(); + void update(); + + QString f, b, d, n, r; + bool valid; + }; + + class Stream; + class Stanza + { + public: + enum Kind { Message, Presence, IQ }; + enum ErrorType { Cancel, Continue, Modify, Auth, Wait }; + enum ErrorCond + { + BadRequest, + Conflict, + FeatureNotImplemented, + Forbidden, + InternalServerError, + ItemNotFound, + JidMalformed, + NotAllowed, + PaymentRequired, + RecipientUnavailable, + RegistrationRequired, + ServerNotFound, + ServerTimeout, + ResourceConstraint, + ServiceUnavailable, + SubscriptionRequired, + UndefinedCondition, + UnexpectedRequest + }; + + Stanza(); + Stanza(const Stanza &from); + Stanza & operator=(const Stanza &from); + virtual ~Stanza(); + + class Error + { + public: + Error(int type=Cancel, int condition=UndefinedCondition, const QString &text="", const QDomElement &appSpec=QDomElement()); + + int type; + int condition; + QString text; + QDomElement appSpec; + }; + + bool isNull() const; + + QDomElement element() const; + QString toString() const; + + QDomDocument & doc() const; + QString baseNS() const; + QString xhtmlImNS() const; + QString xhtmlNS() const; + QDomElement createElement(const QString &ns, const QString &tagName); + QDomElement createTextElement(const QString &ns, const QString &tagName, const QString &text); + QDomElement createXHTMLElement(const QString &xHTML); + void appendChild(const QDomElement &e); + + Kind kind() const; + void setKind(Kind k); + + Jid to() const; + Jid from() const; + QString id() const; + QString type() const; + QString lang() const; + + void setTo(const Jid &j); + void setFrom(const Jid &j); + void setId(const QString &id); + void setType(const QString &type); + void setLang(const QString &lang); + + Error error() const; + void setError(const Error &err); + void clearError(); + + private: + friend class Stream; + Stanza(Stream *s, Kind k, const Jid &to, const QString &type, const QString &id); + Stanza(Stream *s, const QDomElement &e); + + class Private; + Private *d; + }; + + class Stream : public QObject + { + Q_OBJECT + public: + enum Error { ErrParse, ErrProtocol, ErrStream, ErrCustom = 10 }; + enum StreamCond { + GenericStreamError, + Conflict, + ConnectionTimeout, + InternalServerError, + InvalidFrom, + InvalidXml, + PolicyViolation, + ResourceConstraint, + SystemShutdown + }; + + Stream(QObject *parent=0); + virtual ~Stream(); + + virtual QDomDocument & doc() const=0; + virtual QString baseNS() const=0; + virtual QString xhtmlImNS() const=0; + virtual QString xhtmlNS() const=0; + virtual bool old() const=0; + + virtual void close()=0; + virtual bool stanzaAvailable() const=0; + virtual Stanza read()=0; + virtual void write(const Stanza &s)=0; + + virtual int errorCondition() const=0; + virtual QString errorText() const=0; + virtual QDomElement errorAppSpec() const=0; + + Stanza createStanza(Stanza::Kind k, const Jid &to="", const QString &type="", const QString &id=""); + Stanza createStanza(const QDomElement &e); + + static QString xmlToString(const QDomElement &e, bool clip=false); + + signals: + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); + void stanzaWritten(); + void error(int); + }; + + class ClientStream : public Stream + { + Q_OBJECT + public: + enum Error { + ErrConnection = ErrCustom, // Connection error, ask Connector-subclass what's up + ErrNeg, // Negotiation error, see condition + ErrTLS, // TLS error, see condition + ErrAuth, // Auth error, see condition + ErrSecurityLayer, // broken SASL security layer + ErrBind // Resource binding error + }; + enum Warning { + WarnOldVersion, // server uses older XMPP/Jabber "0.9" protocol + WarnNoTLS // there is no chance for TLS at this point + }; + enum NegCond { + HostGone, // host no longer hosted + HostUnknown, // unknown host + RemoteConnectionFailed, // unable to connect to a required remote resource + SeeOtherHost, // a 'redirect', see errorText() for other host + UnsupportedVersion // unsupported XMPP version + }; + enum TLSCond { + TLSStart, // server rejected STARTTLS + TLSFail // TLS failed, ask TLSHandler-subclass what's up + }; + enum SecurityLayer { + LayerTLS, + LayerSASL + }; + enum AuthCond { + GenericAuthError, // all-purpose "can't login" error + NoMech, // No appropriate auth mech available + BadProto, // Bad SASL auth protocol + BadServ, // Server failed mutual auth + EncryptionRequired, // can't use mech without TLS + InvalidAuthzid, // bad input JID + InvalidMech, // bad mechanism + InvalidRealm, // bad realm + MechTooWeak, // can't use mech with this authzid + NotAuthorized, // bad user, bad password, bad creditials + TemporaryAuthFailure // please try again later! + }; + enum BindCond { + BindNotAllowed, // not allowed to bind a resource + BindConflict // resource in-use + }; + + ClientStream(Connector *conn, TLSHandler *tlsHandler=0, QObject *parent=0); + ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls=0, QObject *parent=0); // server + ~ClientStream(); + + Jid jid() const; + void connectToServer(const Jid &jid, bool auth=true); + void accept(); // server + bool isActive() const; + bool isAuthenticated() const; + + // login params + void setUsername(const QString &s); + void setPassword(const QString &s); + void setRealm(const QString &s); + void continueAfterParams(); + + // SASL information + QString saslMechanism() const; + int saslSSF() const; + + // binding + void setResourceBinding(bool); + + // security options (old protocol only uses the first !) + void setAllowPlain(bool); + void setRequireMutualAuth(bool); + void setSSFRange(int low, int high); + void setOldOnly(bool); + void setSASLMechanism(const QString &s); + void setLocalAddr(const QHostAddress &addr, Q_UINT16 port); + + // reimplemented + QDomDocument & doc() const; + QString baseNS() const; + QString xhtmlImNS() const; + QString xhtmlNS() const; + bool old() const; + + void close(); + bool stanzaAvailable() const; + Stanza read(); + void write(const Stanza &s); + + int errorCondition() const; + QString errorText() const; + QDomElement errorAppSpec() const; + + // extra + void writeDirect(const QString &s); + void setNoopTime(int mills); + + signals: + void connected(); + void securityLayerActivated(int); + void needAuthParams(bool user, bool pass, bool realm); + void authenticated(); + void warning(int); + void incomingXml(const QString &s); + void outgoingXml(const QString &s); + + public slots: + void continueAfterWarning(); + + private slots: + void cr_connected(); + void cr_error(); + + void bs_connectionClosed(); + void bs_delayedCloseFinished(); + void bs_error(int); // server only + + void ss_readyRead(); + void ss_bytesWritten(int); + void ss_tlsHandshaken(); + void ss_tlsClosed(); + void ss_error(int); + + void sasl_clientFirstStep(const QString &mech, const QByteArray *clientInit); + void sasl_nextStep(const QByteArray &stepData); + void sasl_needParams(bool user, bool authzid, bool pass, bool realm); + void sasl_authCheck(const QString &user, const QString &authzid); + void sasl_authenticated(); + void sasl_error(int); + + void doNoop(); + void doReadyRead(); + + private: + class Private; + Private *d; + + void reset(bool all=false); + void processNext(); + int convertedSASLCond() const; + bool handleNeed(); + void handleError(); + void srvProcessNext(); + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/jabber/Makefile.am b/kopete/protocols/jabber/libiris/iris/jabber/Makefile.am new file mode 100644 index 00000000..d480984d --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/Makefile.am @@ -0,0 +1,15 @@ +# we deal with s5b.moc separately since KDE's build system can't cope with Q_OBJECT in .cpp files +METASOURCES = filetransfer.moc xmpp_ibb.moc xmpp_jidlink.moc + +noinst_LTLIBRARIES = libiris_jabber.la +INCLUDES = -I$(srcdir)/../include -I$(srcdir)/../xmpp-core -I$(srcdir)/../xmpp-im -I$(srcdir)/../../cutestuff/util -I$(srcdir)/../../cutestuff/network -I$(srcdir)/../../qca/src $(all_includes) + +libiris_jabber_la_SOURCES = \ + filetransfer.cpp s5b.cpp xmpp_ibb.cpp xmpp_jidlink.cpp all_mocs.cpp + +s5b.lo: s5b.moc + +CLEANFILES = s5b.moc +s5b.moc: $(srcdir)/s5b.cpp $(srcdir)/s5b.h + ${MOC} $(srcdir)/s5b.h > $@ + ${MOC} $(srcdir)/s5b.cpp >> $@ diff --git a/kopete/protocols/jabber/libiris/iris/jabber/all_mocs.cpp b/kopete/protocols/jabber/libiris/iris/jabber/all_mocs.cpp new file mode 100644 index 00000000..f962a854 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/all_mocs.cpp @@ -0,0 +1,23 @@ +/* + * all_mocs.cpp - #include all .moc files in this directory + * Copyright (C) 2004 Richard Smith + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "filetransfer.moc" +#include "xmpp_ibb.moc" +#include "xmpp_jidlink.moc" diff --git a/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.cpp b/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.cpp new file mode 100644 index 00000000..1697b6a2 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.cpp @@ -0,0 +1,770 @@ +/* + * filetransfer.cpp - File Transfer + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"filetransfer.h" + +#include +#include +#include +#include +#include"xmpp_xmlcommon.h" +#include"s5b.h" + +#define SENDBUFSIZE 65536 + +using namespace XMPP; + +// firstChildElement +// +// Get an element's first child element +static QDomElement firstChildElement(const QDomElement &e) +{ + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + if(n.isElement()) + return n.toElement(); + } + return QDomElement(); +} + +//---------------------------------------------------------------------------- +// FileTransfer +//---------------------------------------------------------------------------- +class FileTransfer::Private +{ +public: + FileTransferManager *m; + JT_FT *ft; + Jid peer; + QString fname; + Q_LLONG size; + Q_LLONG sent; + QString desc; + bool rangeSupported; + Q_LLONG rangeOffset, rangeLength, length; + QString streamType; + bool needStream; + QString id, iq_id; + S5BConnection *c; + Jid proxy; + int state; + bool sender; +}; + +FileTransfer::FileTransfer(FileTransferManager *m, QObject *parent) +:QObject(parent) +{ + d = new Private; + d->m = m; + d->ft = 0; + d->c = 0; + reset(); +} + +FileTransfer::~FileTransfer() +{ + reset(); + delete d; +} + +void FileTransfer::reset() +{ + d->m->unlink(this); + + delete d->ft; + d->ft = 0; + + delete d->c; + d->c = 0; + + d->state = Idle; + d->needStream = false; + d->sent = 0; + d->sender = false; +} + +void FileTransfer::setProxy(const Jid &proxy) +{ + d->proxy = proxy; +} + +void FileTransfer::sendFile(const Jid &to, const QString &fname, Q_LLONG size, const QString &desc) +{ + d->state = Requesting; + d->peer = to; + d->fname = fname; + d->size = size; + d->desc = desc; + d->sender = true; + d->id = d->m->link(this); + + d->ft = new JT_FT(d->m->client()->rootTask()); + connect(d->ft, SIGNAL(finished()), SLOT(ft_finished())); + QStringList list; + list += "http://jabber.org/protocol/bytestreams"; + d->ft->request(to, d->id, fname, size, desc, list); + d->ft->go(true); +} + +int FileTransfer::dataSizeNeeded() const +{ + int pending = d->c->bytesToWrite(); + if(pending >= SENDBUFSIZE) + return 0; + Q_LLONG left = d->length - (d->sent + pending); + int size = SENDBUFSIZE - pending; + if((Q_LLONG)size > left) + size = (int)left; + return size; +} + +void FileTransfer::writeFileData(const QByteArray &a) +{ + int pending = d->c->bytesToWrite(); + Q_LLONG left = d->length - (d->sent + pending); + if(left == 0) + return; + + QByteArray block; + if((Q_LLONG)a.size() > left) { + block = a.copy(); + block.resize((uint)left); + } + else + block = a; + d->c->write(block); +} + +Jid FileTransfer::peer() const +{ + return d->peer; +} + +QString FileTransfer::fileName() const +{ + return d->fname; +} + +Q_LLONG FileTransfer::fileSize() const +{ + return d->size; +} + +QString FileTransfer::description() const +{ + return d->desc; +} + +bool FileTransfer::rangeSupported() const +{ + return d->rangeSupported; +} + +Q_LLONG FileTransfer::offset() const +{ + return d->rangeOffset; +} + +Q_LLONG FileTransfer::length() const +{ + return d->length; +} + +void FileTransfer::accept(Q_LLONG offset, Q_LLONG length) +{ + d->state = Connecting; + d->rangeOffset = offset; + d->rangeLength = length; + if(length > 0) + d->length = length; + else + d->length = d->size; + d->streamType = "http://jabber.org/protocol/bytestreams"; + d->m->con_accept(this); +} + +void FileTransfer::close() +{ + if(d->state == Idle) + return; + if(d->state == WaitingForAccept) + d->m->con_reject(this); + else if(d->state == Active) + d->c->close(); + reset(); +} + +S5BConnection *FileTransfer::s5bConnection() const +{ + return d->c; +} + +void FileTransfer::ft_finished() +{ + JT_FT *ft = d->ft; + d->ft = 0; + + if(ft->success()) { + d->state = Connecting; + d->rangeOffset = ft->rangeOffset(); + d->length = ft->rangeLength(); + if(d->length == 0) + d->length = d->size - d->rangeOffset; + d->streamType = ft->streamType(); + d->c = d->m->client()->s5bManager()->createConnection(); + connect(d->c, SIGNAL(connected()), SLOT(s5b_connected())); + connect(d->c, SIGNAL(connectionClosed()), SLOT(s5b_connectionClosed())); + connect(d->c, SIGNAL(bytesWritten(int)), SLOT(s5b_bytesWritten(int))); + connect(d->c, SIGNAL(error(int)), SLOT(s5b_error(int))); + + if(d->proxy.isValid()) + d->c->setProxy(d->proxy); + d->c->connectToJid(d->peer, d->id); + accepted(); + } + else { + reset(); + if(ft->statusCode() == 403) + error(ErrReject); + else + error(ErrNeg); + } +} + +void FileTransfer::takeConnection(S5BConnection *c) +{ + d->c = c; + connect(d->c, SIGNAL(connected()), SLOT(s5b_connected())); + connect(d->c, SIGNAL(connectionClosed()), SLOT(s5b_connectionClosed())); + connect(d->c, SIGNAL(readyRead()), SLOT(s5b_readyRead())); + connect(d->c, SIGNAL(error(int)), SLOT(s5b_error(int))); + if(d->proxy.isValid()) + d->c->setProxy(d->proxy); + accepted(); + QTimer::singleShot(0, this, SLOT(doAccept())); +} + +void FileTransfer::s5b_connected() +{ + d->state = Active; + connected(); +} + +void FileTransfer::s5b_connectionClosed() +{ + reset(); + error(ErrStream); +} + +void FileTransfer::s5b_readyRead() +{ + QByteArray a = d->c->read(); + Q_LLONG need = d->length - d->sent; + if((Q_LLONG)a.size() > need) + a.resize((uint)need); + d->sent += a.size(); + if(d->sent == d->length) + reset(); + readyRead(a); +} + +void FileTransfer::s5b_bytesWritten(int x) +{ + d->sent += x; + if(d->sent == d->length) + reset(); + bytesWritten(x); +} + +void FileTransfer::s5b_error(int x) +{ + reset(); + if(x == S5BConnection::ErrRefused || x == S5BConnection::ErrConnect) + error(ErrConnect); + else if(x == S5BConnection::ErrProxy) + error(ErrProxy); + else + error(ErrStream); +} + +void FileTransfer::man_waitForAccept(const FTRequest &req) +{ + d->state = WaitingForAccept; + d->peer = req.from; + d->id = req.id; + d->iq_id = req.iq_id; + d->fname = req.fname; + d->size = req.size; + d->desc = req.desc; + d->rangeSupported = req.rangeSupported; +} + +void FileTransfer::doAccept() +{ + d->c->accept(); +} + +//---------------------------------------------------------------------------- +// FileTransferManager +//---------------------------------------------------------------------------- +class FileTransferManager::Private +{ +public: + Client *client; + QPtrList list, incoming; + JT_PushFT *pft; +}; + +FileTransferManager::FileTransferManager(Client *client) +:QObject(client) +{ + d = new Private; + d->client = client; + + d->pft = new JT_PushFT(d->client->rootTask()); + connect(d->pft, SIGNAL(incoming(const FTRequest &)), SLOT(pft_incoming(const FTRequest &))); +} + +FileTransferManager::~FileTransferManager() +{ + d->incoming.setAutoDelete(true); + d->incoming.clear(); + delete d->pft; + delete d; +} + +Client *FileTransferManager::client() const +{ + return d->client; +} + +FileTransfer *FileTransferManager::createTransfer() +{ + FileTransfer *ft = new FileTransfer(this); + return ft; +} + +FileTransfer *FileTransferManager::takeIncoming() +{ + if(d->incoming.isEmpty()) + return 0; + + FileTransfer *ft = d->incoming.getFirst(); + d->incoming.removeRef(ft); + + // move to active list + d->list.append(ft); + return ft; +} + +void FileTransferManager::pft_incoming(const FTRequest &req) +{ + bool found = false; + for(QStringList::ConstIterator it = req.streamTypes.begin(); it != req.streamTypes.end(); ++it) { + if((*it) == "http://jabber.org/protocol/bytestreams") { + found = true; + break; + } + } + if(!found) { + d->pft->respondError(req.from, req.iq_id, 400, "No valid stream types"); + return; + } + if(!d->client->s5bManager()->isAcceptableSID(req.from, req.id)) { + d->pft->respondError(req.from, req.iq_id, 400, "SID in use"); + return; + } + + FileTransfer *ft = new FileTransfer(this); + ft->man_waitForAccept(req); + d->incoming.append(ft); + incomingReady(); +} + +void FileTransferManager::s5b_incomingReady(S5BConnection *c) +{ + QPtrListIterator it(d->list); + FileTransfer *ft = 0; + for(FileTransfer *i; (i = it.current()); ++it) { + if(i->d->needStream && i->d->peer.compare(c->peer()) && i->d->id == c->sid()) { + ft = i; + break; + } + } + if(!ft) { + c->close(); + delete c; + return; + } + ft->takeConnection(c); +} + +QString FileTransferManager::link(FileTransfer *ft) +{ + d->list.append(ft); + return d->client->s5bManager()->genUniqueSID(ft->d->peer); +} + +void FileTransferManager::con_accept(FileTransfer *ft) +{ + ft->d->needStream = true; + d->pft->respondSuccess(ft->d->peer, ft->d->iq_id, ft->d->rangeOffset, ft->d->rangeLength, ft->d->streamType); +} + +void FileTransferManager::con_reject(FileTransfer *ft) +{ + d->pft->respondError(ft->d->peer, ft->d->iq_id, 403, "Declined"); +} + +void FileTransferManager::unlink(FileTransfer *ft) +{ + d->list.removeRef(ft); +} + +//---------------------------------------------------------------------------- +// JT_FT +//---------------------------------------------------------------------------- +class JT_FT::Private +{ +public: + QDomElement iq; + Jid to; + Q_LLONG size, rangeOffset, rangeLength; + QString streamType; + QStringList streamTypes; +}; + +JT_FT::JT_FT(Task *parent) +:Task(parent) +{ + d = new Private; +} + +JT_FT::~JT_FT() +{ + delete d; +} + +void JT_FT::request(const Jid &to, const QString &_id, const QString &fname, Q_LLONG size, const QString &desc, const QStringList &streamTypes) +{ + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement si = doc()->createElement("si"); + si.setAttribute("xmlns", "http://jabber.org/protocol/si"); + si.setAttribute("id", _id); + si.setAttribute("profile", "http://jabber.org/protocol/si/profile/file-transfer"); + + QDomElement file = doc()->createElement("file"); + file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer"); + file.setAttribute("name", fname); + file.setAttribute("size", QString::number(size)); + if(!desc.isEmpty()) { + QDomElement de = doc()->createElement("desc"); + de.appendChild(doc()->createTextNode(desc)); + file.appendChild(de); + } + QDomElement range = doc()->createElement("range"); + file.appendChild(range); + si.appendChild(file); + + QDomElement feature = doc()->createElement("feature"); + feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg"); + QDomElement x = doc()->createElement("x"); + x.setAttribute("xmlns", "jabber:x:data"); + x.setAttribute("type", "form"); + + QDomElement field = doc()->createElement("field"); + field.setAttribute("var", "stream-method"); + field.setAttribute("type", "list-single"); + for(QStringList::ConstIterator it = streamTypes.begin(); it != streamTypes.end(); ++it) { + QDomElement option = doc()->createElement("option"); + QDomElement value = doc()->createElement("value"); + value.appendChild(doc()->createTextNode(*it)); + option.appendChild(value); + field.appendChild(option); + } + + x.appendChild(field); + feature.appendChild(x); + + si.appendChild(feature); + iq.appendChild(si); + + d->streamTypes = streamTypes; + d->size = size; + d->iq = iq; +} + +Q_LLONG JT_FT::rangeOffset() const +{ + return d->rangeOffset; +} + +Q_LLONG JT_FT::rangeLength() const +{ + return d->rangeLength; +} + +QString JT_FT::streamType() const +{ + return d->streamType; +} + +void JT_FT::onGo() +{ + send(d->iq); +} + +bool JT_FT::take(const QDomElement &x) +{ + if(!iqVerify(x, d->to, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement si = firstChildElement(x); + if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si") { + setError(900, ""); + return true; + } + + QString id = si.attribute("id"); + + Q_LLONG range_offset = 0; + Q_LLONG range_length = 0; + + QDomElement file = si.elementsByTagName("file").item(0).toElement(); + if(!file.isNull()) { + QDomElement range = file.elementsByTagName("range").item(0).toElement(); + if(!range.isNull()) { + int x; + bool ok; + if(range.hasAttribute("offset")) { +#if QT_VERSION >= 0x030200 + x = range.attribute("offset").toLongLong(&ok); +#else + x = range.attribute("offset").toLong(&ok); +#endif + if(!ok || x < 0) { + setError(900, ""); + return true; + } + range_offset = x; + } + if(range.hasAttribute("length")) { +#if QT_VERSION >= 0x030200 + x = range.attribute("length").toLongLong(&ok); +#else + x = range.attribute("length").toLong(&ok); +#endif + if(!ok || x < 0) { + setError(900, ""); + return true; + } + range_length = x; + } + } + } + + if(range_offset > d->size || (range_length > (d->size - range_offset))) { + setError(900, ""); + return true; + } + + QString streamtype; + QDomElement feature = si.elementsByTagName("feature").item(0).toElement(); + if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") { + QDomElement x = feature.elementsByTagName("x").item(0).toElement(); + if(!x.isNull() && x.attribute("type") == "submit") { + QDomElement field = x.elementsByTagName("field").item(0).toElement(); + if(!field.isNull() && field.attribute("var") == "stream-method") { + QDomElement value = field.elementsByTagName("value").item(0).toElement(); + if(!value.isNull()) + streamtype = value.text(); + } + } + } + + // must be one of the offered streamtypes + bool found = false; + for(QStringList::ConstIterator it = d->streamTypes.begin(); it != d->streamTypes.end(); ++it) { + if((*it) == streamtype) { + found = true; + break; + } + } + if(!found) + return true; + + d->rangeOffset = range_offset; + d->rangeLength = range_length; + d->streamType = streamtype; + setSuccess(); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_PushFT +//---------------------------------------------------------------------------- +JT_PushFT::JT_PushFT(Task *parent) +:Task(parent) +{ +} + +JT_PushFT::~JT_PushFT() +{ +} + +void JT_PushFT::respondSuccess(const Jid &to, const QString &id, Q_LLONG rangeOffset, Q_LLONG rangeLength, const QString &streamType) +{ + QDomElement iq = createIQ(doc(), "result", to.full(), id); + QDomElement si = doc()->createElement("si"); + si.setAttribute("xmlns", "http://jabber.org/protocol/si"); + + if(rangeOffset != 0 || rangeLength != 0) { + QDomElement file = doc()->createElement("file"); + file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer"); + QDomElement range = doc()->createElement("range"); + if(rangeOffset > 0) + range.setAttribute("offset", QString::number(rangeOffset)); + if(rangeLength > 0) + range.setAttribute("length", QString::number(rangeLength)); + file.appendChild(range); + si.appendChild(file); + } + + QDomElement feature = doc()->createElement("feature"); + feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg"); + QDomElement x = doc()->createElement("x"); + x.setAttribute("xmlns", "jabber:x:data"); + x.setAttribute("type", "submit"); + + QDomElement field = doc()->createElement("field"); + field.setAttribute("var", "stream-method"); + QDomElement value = doc()->createElement("value"); + value.appendChild(doc()->createTextNode(streamType)); + field.appendChild(value); + + x.appendChild(field); + feature.appendChild(x); + + si.appendChild(feature); + iq.appendChild(si); + send(iq); +} + +void JT_PushFT::respondError(const Jid &to, const QString &id, int code, const QString &str) +{ + QDomElement iq = createIQ(doc(), "error", to.full(), id); + QDomElement err = textTag(doc(), "error", str); + err.setAttribute("code", QString::number(code)); + iq.appendChild(err); + send(iq); +} + +bool JT_PushFT::take(const QDomElement &e) +{ + // must be an iq-set tag + if(e.tagName() != "iq") + return false; + if(e.attribute("type") != "set") + return false; + + QDomElement si = firstChildElement(e); + if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si") + return false; + if(si.attribute("profile") != "http://jabber.org/protocol/si/profile/file-transfer") + return false; + + Jid from(e.attribute("from")); + QString id = si.attribute("id"); + + QDomElement file = si.elementsByTagName("file").item(0).toElement(); + if(file.isNull()) + return true; + + QString fname = file.attribute("name"); + if(fname.isEmpty()) { + respondError(from, id, 400, "Bad file name"); + return true; + } + + // ensure kosher + { + QFileInfo fi(fname); + fname = fi.fileName(); + } + + bool ok; +#if QT_VERSION >= 0x030200 + Q_LLONG size = file.attribute("size").toLongLong(&ok); +#else + Q_LLONG size = file.attribute("size").toLong(&ok); +#endif + if(!ok || size < 0) { + respondError(from, id, 400, "Bad file size"); + return true; + } + + QString desc; + QDomElement de = file.elementsByTagName("desc").item(0).toElement(); + if(!de.isNull()) + desc = de.text(); + + bool rangeSupported = false; + QDomElement range = file.elementsByTagName("range").item(0).toElement(); + if(!range.isNull()) + rangeSupported = true; + + QStringList streamTypes; + QDomElement feature = si.elementsByTagName("feature").item(0).toElement(); + if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") { + QDomElement x = feature.elementsByTagName("x").item(0).toElement(); + if(!x.isNull() /*&& x.attribute("type") == "form"*/) { + QDomElement field = x.elementsByTagName("field").item(0).toElement(); + if(!field.isNull() && field.attribute("var") == "stream-method" && field.attribute("type") == "list-single") { + QDomNodeList nl = field.elementsByTagName("option"); + for(uint n = 0; n < nl.count(); ++n) { + QDomElement e = nl.item(n).toElement(); + QDomElement value = e.elementsByTagName("value").item(0).toElement(); + if(!value.isNull()) + streamTypes += value.text(); + } + } + } + } + + FTRequest r; + r.from = from; + r.iq_id = e.attribute("id"); + r.id = id; + r.fname = fname; + r.size = size; + r.desc = desc; + r.rangeSupported = rangeSupported; + r.streamTypes = streamTypes; + + incoming(r); + return true; +} diff --git a/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.h b/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.h new file mode 100644 index 00000000..9ad4d403 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.h @@ -0,0 +1,170 @@ +/* + * filetransfer.h - File Transfer + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMPP_FILETRANSFER_H +#define XMPP_FILETRANSFER_H + +#include"im.h" + +#if QT_VERSION < 0x030200 +typedef long int Q_LLONG; +#endif + +namespace XMPP +{ + class S5BConnection; + struct FTRequest; + + class FileTransfer : public QObject + { + Q_OBJECT + public: + enum { ErrReject, ErrNeg, ErrConnect, ErrProxy, ErrStream }; + enum { Idle, Requesting, Connecting, WaitingForAccept, Active }; + ~FileTransfer(); + + void setProxy(const Jid &proxy); + + // send + void sendFile(const Jid &to, const QString &fname, Q_LLONG size, const QString &desc); + Q_LLONG offset() const; + Q_LLONG length() const; + int dataSizeNeeded() const; + void writeFileData(const QByteArray &a); + + // receive + Jid peer() const; + QString fileName() const; + Q_LLONG fileSize() const; + QString description() const; + bool rangeSupported() const; + void accept(Q_LLONG offset=0, Q_LLONG length=0); + + // both + void close(); // reject, or stop sending/receiving + S5BConnection *s5bConnection() const; // active link + + signals: + void accepted(); // indicates S5BConnection has started + void connected(); + void readyRead(const QByteArray &a); + void bytesWritten(int); + void error(int); + + private slots: + void ft_finished(); + void s5b_connected(); + void s5b_connectionClosed(); + void s5b_readyRead(); + void s5b_bytesWritten(int); + void s5b_error(int); + void doAccept(); + + private: + class Private; + Private *d; + + void reset(); + + friend class FileTransferManager; + FileTransfer(FileTransferManager *, QObject *parent=0); + void man_waitForAccept(const FTRequest &req); + void takeConnection(S5BConnection *c); + }; + + class FileTransferManager : public QObject + { + Q_OBJECT + public: + FileTransferManager(Client *); + ~FileTransferManager(); + + Client *client() const; + FileTransfer *createTransfer(); + FileTransfer *takeIncoming(); + + signals: + void incomingReady(); + + private slots: + void pft_incoming(const FTRequest &req); + + private: + class Private; + Private *d; + + friend class Client; + void s5b_incomingReady(S5BConnection *); + + friend class FileTransfer; + QString link(FileTransfer *); + void con_accept(FileTransfer *); + void con_reject(FileTransfer *); + void unlink(FileTransfer *); + }; + + class JT_FT : public Task + { + Q_OBJECT + public: + JT_FT(Task *parent); + ~JT_FT(); + + void request(const Jid &to, const QString &id, const QString &fname, Q_LLONG size, const QString &desc, const QStringList &streamTypes); + Q_LLONG rangeOffset() const; + Q_LLONG rangeLength() const; + QString streamType() const; + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + + struct FTRequest + { + Jid from; + QString iq_id, id; + QString fname; + Q_LLONG size; + QString desc; + bool rangeSupported; + QStringList streamTypes; + }; + class JT_PushFT : public Task + { + Q_OBJECT + public: + JT_PushFT(Task *parent); + ~JT_PushFT(); + + void respondSuccess(const Jid &to, const QString &id, Q_LLONG rangeOffset, Q_LLONG rangeLength, const QString &streamType); + void respondError(const Jid &to, const QString &id, int code, const QString &str); + + bool take(const QDomElement &); + + signals: + void incoming(const FTRequest &req); + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/jabber/s5b.cpp b/kopete/protocols/jabber/libiris/iris/jabber/s5b.cpp new file mode 100644 index 00000000..b4b9be44 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/s5b.cpp @@ -0,0 +1,2538 @@ +/* + * s5b.cpp - direct connection protocol via tcp + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#include"s5b.h" + +#include +#include +#include +#include +#include"xmpp_xmlcommon.h" +#include"hash.h" +#include"socks.h" +#include"safedelete.h" + +#ifdef Q_OS_WIN +# include +#else +# ifdef HAVE_SYS_TYPES_H +# include +# endif +# include +#endif + +#define MAXSTREAMHOSTS 5 + +//#define S5B_DEBUG + +namespace XMPP { + +static QString makeKey(const QString &sid, const Jid &initiator, const Jid &target) +{ + QString str = sid + initiator.full() + target.full(); + return QCA::SHA1::hashToString(str.utf8()); +} + +static bool haveHost(const StreamHostList &list, const Jid &j) +{ + for(StreamHostList::ConstIterator it = list.begin(); it != list.end(); ++it) { + if((*it).jid().compare(j)) + return true; + } + return false; +} + +class S5BManager::Item : public QObject +{ + Q_OBJECT +public: + enum { Idle, Initiator, Target, Active }; + enum { ErrRefused, ErrConnect, ErrWrongHost, ErrProxy }; + enum { Unknown, Fast, NotFast }; + S5BManager *m; + int state; + QString sid, key, out_key, out_id, in_id; + Jid self, peer; + StreamHostList in_hosts; + JT_S5B *task, *proxy_task; + SocksClient *client, *client_out; + SocksUDP *client_udp, *client_out_udp; + S5BConnector *conn, *proxy_conn; + bool wantFast; + StreamHost proxy; + int targetMode; // initiator sets this once it figures it out + bool fast; // target sets this + bool activated; + bool lateProxy; + bool connSuccess; + bool localFailed, remoteFailed; + bool allowIncoming; + bool udp; + int statusCode; + Jid activatedStream; + + Item(S5BManager *manager); + ~Item(); + + void reset(); + void startInitiator(const QString &_sid, const Jid &_self, const Jid &_peer, bool fast, bool udp); + void startTarget(const QString &_sid, const Jid &_self, const Jid &_peer, const StreamHostList &hosts, const QString &iq_id, bool fast, bool udp); + void handleFast(const StreamHostList &hosts, const QString &iq_id); + + void doOutgoing(); + void doIncoming(); + void setIncomingClient(SocksClient *sc); + void incomingActivate(const Jid &streamHost); + +signals: + void accepted(); + void tryingHosts(const StreamHostList &list); + void proxyConnect(); + void waitingForActivation(); + void connected(); + void error(int); + +private slots: + void jt_finished(); + void conn_result(bool b); + void proxy_result(bool b); + void proxy_finished(); + void sc_readyRead(); + void sc_bytesWritten(int); + void sc_error(int); + +private: + void doConnectError(); + void tryActivation(); + void checkForActivation(); + void checkFailure(); + void finished(); +}; + +//---------------------------------------------------------------------------- +// S5BDatagram +//---------------------------------------------------------------------------- +S5BDatagram::S5BDatagram() +{ + _source = 0; + _dest = 0; +} + +S5BDatagram::S5BDatagram(int source, int dest, const QByteArray &data) +{ + _source = source; + _dest = dest; + _buf = data; +} + +int S5BDatagram::sourcePort() const +{ + return _source; +} + +int S5BDatagram::destPort() const +{ + return _dest; +} + +QByteArray S5BDatagram::data() const +{ + return _buf; +} + +//---------------------------------------------------------------------------- +// S5BConnection +//---------------------------------------------------------------------------- +class S5BConnection::Private +{ +public: + S5BManager *m; + SocksClient *sc; + SocksUDP *su; + int state; + Jid peer; + QString sid; + bool remote; + bool switched; + bool notifyRead, notifyClose; + int id; + S5BRequest req; + Jid proxy; + Mode mode; + QPtrList dglist; +}; + +static int id_conn = 0; +static int num_conn = 0; + +S5BConnection::S5BConnection(S5BManager *m, QObject *parent) +:ByteStream(parent) +{ + d = new Private; + d->m = m; + d->sc = 0; + d->su = 0; + + ++num_conn; + d->id = id_conn++; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: constructing, count=%d, %p\n", d->id, num_conn, this); +#endif + + reset(); +} + +S5BConnection::~S5BConnection() +{ + reset(true); + + --num_conn; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: destructing, count=%d\n", d->id, num_conn); +#endif + + delete d; +} + +void S5BConnection::reset(bool clear) +{ + d->m->con_unlink(this); + if(clear && d->sc) { + delete d->sc; + d->sc = 0; + } + delete d->su; + d->su = 0; + if(clear) { + d->dglist.setAutoDelete(true); + d->dglist.clear(); + d->dglist.setAutoDelete(false); + } + d->state = Idle; + d->peer = Jid(); + d->sid = QString(); + d->remote = false; + d->switched = false; + d->notifyRead = false; + d->notifyClose = false; +} + +Jid S5BConnection::proxy() const +{ + return d->proxy; +} + +void S5BConnection::setProxy(const Jid &proxy) +{ + d->proxy = proxy; +} + +void S5BConnection::connectToJid(const Jid &peer, const QString &sid, Mode m) +{ + reset(true); + if(!d->m->isAcceptableSID(peer, sid)) + return; + + d->peer = peer; + d->sid = sid; + d->state = Requesting; + d->mode = m; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: connecting %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1()); +#endif + d->m->con_connect(this); +} + +void S5BConnection::accept() +{ + if(d->state != WaitingForAccept) + return; + + d->state = Connecting; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: accepting %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1()); +#endif + d->m->con_accept(this); +} + +void S5BConnection::close() +{ + if(d->state == Idle) + return; + + if(d->state == WaitingForAccept) + d->m->con_reject(this); + else if(d->state == Active) + d->sc->close(); +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: closing %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1()); +#endif + reset(); +} + +Jid S5BConnection::peer() const +{ + return d->peer; +} + +QString S5BConnection::sid() const +{ + return d->sid; +} + +bool S5BConnection::isRemote() const +{ + return d->remote; +} + +S5BConnection::Mode S5BConnection::mode() const +{ + return d->mode; +} + +int S5BConnection::state() const +{ + return d->state; +} + +bool S5BConnection::isOpen() const +{ + if(d->state == Active) + return true; + else + return false; +} + +void S5BConnection::write(const QByteArray &buf) +{ + if(d->state == Active && d->mode == Stream) + d->sc->write(buf); +} + +QByteArray S5BConnection::read(int bytes) +{ + if(d->sc) + return d->sc->read(bytes); + else + return QByteArray(); +} + +int S5BConnection::bytesAvailable() const +{ + if(d->sc) + return d->sc->bytesAvailable(); + else + return 0; +} + +int S5BConnection::bytesToWrite() const +{ + if(d->state == Active) + return d->sc->bytesToWrite(); + else + return 0; +} + +void S5BConnection::writeDatagram(const S5BDatagram &i) +{ + QByteArray buf(i.data().size() + 4); + ushort ssp = htons(i.sourcePort()); + ushort sdp = htons(i.destPort()); + QByteArray data = i.data(); + memcpy(buf.data(), &ssp, 2); + memcpy(buf.data() + 2, &sdp, 2); + memcpy(buf.data() + 4, data.data(), data.size()); + sendUDP(buf); +} + +S5BDatagram S5BConnection::readDatagram() +{ + if(d->dglist.isEmpty()) + return S5BDatagram(); + S5BDatagram *i = d->dglist.getFirst(); + d->dglist.removeRef(i); + S5BDatagram val = *i; + delete i; + return val; +} + +int S5BConnection::datagramsAvailable() const +{ + return d->dglist.count(); +} + +void S5BConnection::man_waitForAccept(const S5BRequest &r) +{ + d->state = WaitingForAccept; + d->remote = true; + d->req = r; + d->peer = r.from; + d->sid = r.sid; + d->mode = r.udp ? Datagram : Stream; +} + +void S5BConnection::man_clientReady(SocksClient *sc, SocksUDP *sc_udp) +{ + d->sc = sc; + connect(d->sc, SIGNAL(connectionClosed()), SLOT(sc_connectionClosed())); + connect(d->sc, SIGNAL(delayedCloseFinished()), SLOT(sc_delayedCloseFinished())); + connect(d->sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); + connect(d->sc, SIGNAL(bytesWritten(int)), SLOT(sc_bytesWritten(int))); + connect(d->sc, SIGNAL(error(int)), SLOT(sc_error(int))); + + if(sc_udp) { + d->su = sc_udp; + connect(d->su, SIGNAL(packetReady(const QByteArray &)), SLOT(su_packetReady(const QByteArray &))); + } + + d->state = Active; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: %s [%s] <<< success >>>\n", d->id, d->peer.full().latin1(), d->sid.latin1()); +#endif + + // bytes already in the stream? + if(d->sc->bytesAvailable()) { +#ifdef S5B_DEBUG + printf("Stream has %d bytes in it.\n", d->sc->bytesAvailable()); +#endif + d->notifyRead = true; + } + // closed before it got here? + if(!d->sc->isOpen()) { +#ifdef S5B_DEBUG + printf("Stream was closed before S5B request finished?\n"); +#endif + d->notifyClose = true; + } + if(d->notifyRead || d->notifyClose) + QTimer::singleShot(0, this, SLOT(doPending())); + connected(); +} + +void S5BConnection::doPending() +{ + if(d->notifyRead) { + if(d->notifyClose) + QTimer::singleShot(0, this, SLOT(doPending())); + sc_readyRead(); + } + else if(d->notifyClose) + sc_connectionClosed(); +} + +void S5BConnection::man_udpReady(const QByteArray &buf) +{ + handleUDP(buf); +} + +void S5BConnection::man_failed(int x) +{ + reset(true); + if(x == S5BManager::Item::ErrRefused) + error(ErrRefused); + if(x == S5BManager::Item::ErrConnect) + error(ErrConnect); + if(x == S5BManager::Item::ErrWrongHost) + error(ErrConnect); + if(x == S5BManager::Item::ErrProxy) + error(ErrProxy); +} + +void S5BConnection::sc_connectionClosed() +{ + // if we have a pending read notification, postpone close + if(d->notifyRead) { +#ifdef S5B_DEBUG + printf("closed while pending read\n"); +#endif + d->notifyClose = true; + return; + } + d->notifyClose = false; + reset(); + connectionClosed(); +} + +void S5BConnection::sc_delayedCloseFinished() +{ + // echo + delayedCloseFinished(); +} + +void S5BConnection::sc_readyRead() +{ + if(d->mode == Datagram) { + // throw the data away + d->sc->read(); + return; + } + + d->notifyRead = false; + // echo + readyRead(); +} + +void S5BConnection::sc_bytesWritten(int x) +{ + // echo + bytesWritten(x); +} + +void S5BConnection::sc_error(int) +{ + reset(); + error(ErrSocket); +} + +void S5BConnection::su_packetReady(const QByteArray &buf) +{ + handleUDP(buf); +} + +void S5BConnection::handleUDP(const QByteArray &buf) +{ + // must be at least 4 bytes, to accomodate virtual ports + if(buf.size() < 4) + return; // drop + + ushort ssp, sdp; + memcpy(&ssp, buf.data(), 2); + memcpy(&sdp, buf.data() + 2, 2); + int source = ntohs(ssp); + int dest = ntohs(sdp); + QByteArray data(buf.size() - 4); + memcpy(data.data(), buf.data() + 4, data.size()); + d->dglist.append(new S5BDatagram(source, dest, data)); + + datagramReady(); +} + +void S5BConnection::sendUDP(const QByteArray &buf) +{ + if(d->su) + d->su->write(buf); + else + d->m->con_sendUDP(this, buf); +} + +//---------------------------------------------------------------------------- +// S5BManager +//---------------------------------------------------------------------------- +class S5BManager::Entry +{ +public: + Entry() + { + i = 0; + query = 0; + udp_init = false; + } + + ~Entry() + { + delete query; + } + + S5BConnection *c; + Item *i; + QString sid; + JT_S5B *query; + StreamHost proxyInfo; + QGuardedPtr relatedServer; + + bool udp_init; + QHostAddress udp_addr; + int udp_port; +}; + +class S5BManager::Private +{ +public: + Client *client; + S5BServer *serv; + QPtrList activeList; + S5BConnectionList incomingConns; + JT_PushS5B *ps; +}; + +S5BManager::S5BManager(Client *parent) +:QObject(parent) +{ + // S5B needs SHA1 + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + d = new Private; + d->client = parent; + d->serv = 0; + d->activeList.setAutoDelete(true); + + d->ps = new JT_PushS5B(d->client->rootTask()); + connect(d->ps, SIGNAL(incoming(const S5BRequest &)), SLOT(ps_incoming(const S5BRequest &))); + connect(d->ps, SIGNAL(incomingUDPSuccess(const Jid &, const QString &)), SLOT(ps_incomingUDPSuccess(const Jid &, const QString &))); + connect(d->ps, SIGNAL(incomingActivate(const Jid &, const QString &, const Jid &)), SLOT(ps_incomingActivate(const Jid &, const QString &, const Jid &))); +} + +S5BManager::~S5BManager() +{ + setServer(0); + d->incomingConns.setAutoDelete(true); + d->incomingConns.clear(); + delete d->ps; + delete d; +} + +Client *S5BManager::client() const +{ + return d->client; +} + +S5BServer *S5BManager::server() const +{ + return d->serv; +} + +void S5BManager::setServer(S5BServer *serv) +{ + if(d->serv) { + d->serv->unlink(this); + d->serv = 0; + } + + if(serv) { + d->serv = serv; + d->serv->link(this); + } +} + +S5BConnection *S5BManager::createConnection() +{ + S5BConnection *c = new S5BConnection(this); + return c; +} + +S5BConnection *S5BManager::takeIncoming() +{ + if(d->incomingConns.isEmpty()) + return 0; + + S5BConnection *c = d->incomingConns.getFirst(); + d->incomingConns.removeRef(c); + + // move to activeList + Entry *e = new Entry; + e->c = c; + e->sid = c->d->sid; + d->activeList.append(e); + + return c; +} + +void S5BManager::ps_incoming(const S5BRequest &req) +{ +#ifdef S5B_DEBUG + printf("S5BManager: incoming from %s\n", req.from.full().latin1()); +#endif + + bool ok = false; + // ensure we don't already have an incoming connection from this peer+sid + S5BConnection *c = findIncoming(req.from, req.sid); + if(!c) { + // do we have an active entry with this sid already? + Entry *e = findEntryBySID(req.from, req.sid); + if(e) { + if(e->i) { + // loopback + if(req.from.compare(d->client->jid()) && (req.id == e->i->out_id)) { +#ifdef S5B_DEBUG + printf("ALLOWED: loopback\n"); +#endif + ok = true; + } + // allowed by 'fast mode' + else if(e->i->state == Item::Initiator && e->i->targetMode == Item::Unknown) { +#ifdef S5B_DEBUG + printf("ALLOWED: fast-mode\n"); +#endif + e->i->handleFast(req.hosts, req.id); + return; + } + } + } + else { +#ifdef S5B_DEBUG + printf("ALLOWED: we don't have it\n"); +#endif + ok = true; + } + } + if(!ok) { + d->ps->respondError(req.from, req.id, 406, "SID in use"); + return; + } + + // create an incoming connection + c = new S5BConnection(this); + c->man_waitForAccept(req); + d->incomingConns.append(c); + incomingReady(); +} + +void S5BManager::ps_incomingUDPSuccess(const Jid &from, const QString &key) +{ + Entry *e = findEntryByHash(key); + if(e && e->i) { + if(e->i->conn) + e->i->conn->man_udpSuccess(from); + else if(e->i->proxy_conn) + e->i->proxy_conn->man_udpSuccess(from); + } +} + +void S5BManager::ps_incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost) +{ + Entry *e = findEntryBySID(from, sid); + if(e && e->i) + e->i->incomingActivate(streamHost); +} + +void S5BManager::doSuccess(const Jid &peer, const QString &id, const Jid &streamHost) +{ + d->ps->respondSuccess(peer, id, streamHost); +} + +void S5BManager::doError(const Jid &peer, const QString &id, int code, const QString &str) +{ + d->ps->respondError(peer, id, code, str); +} + +void S5BManager::doActivate(const Jid &peer, const QString &sid, const Jid &streamHost) +{ + d->ps->sendActivate(peer, sid, streamHost); +} + +QString S5BManager::genUniqueSID(const Jid &peer) const +{ + // get unused key + QString sid; + do { + sid = "s5b_"; + for(int i = 0; i < 4; ++i) { + int word = rand() & 0xffff; + for(int n = 0; n < 4; ++n) { + QString s; + s.sprintf("%x", (word >> (n * 4)) & 0xf); + sid.append(s); + } + } + } while(!isAcceptableSID(peer, sid)); + return sid; +} + +bool S5BManager::isAcceptableSID(const Jid &peer, const QString &sid) const +{ + QString key = makeKey(sid, d->client->jid(), peer); + QString key_out = makeKey(sid, peer, d->client->jid()); + + // if we have a server, then check through it + if(d->serv) { + if(findServerEntryByHash(key) || findServerEntryByHash(key_out)) + return false; + } + else { + if(findEntryByHash(key) || findEntryByHash(key_out)) + return false; + } + return true; +} + +S5BConnection *S5BManager::findIncoming(const Jid &from, const QString &sid) const +{ + QPtrListIterator it(d->incomingConns); + for(S5BConnection *c; (c = it.current()); ++it) { + if(c->d->peer.compare(from) && c->d->sid == sid) + return c; + } + return 0; +} + +S5BManager::Entry *S5BManager::findEntry(S5BConnection *c) const +{ + QPtrListIterator it(d->activeList); + for(Entry *e; (e = it.current()); ++it) { + if(e->c == c) + return e; + } + return 0; +} + +S5BManager::Entry *S5BManager::findEntry(Item *i) const +{ + QPtrListIterator it(d->activeList); + for(Entry *e; (e = it.current()); ++it) { + if(e->i == i) + return e; + } + return 0; +} + +S5BManager::Entry *S5BManager::findEntryByHash(const QString &key) const +{ + QPtrListIterator it(d->activeList); + for(Entry *e; (e = it.current()); ++it) { + if(e->i && e->i->key == key) + return e; + } + return 0; +} + +S5BManager::Entry *S5BManager::findEntryBySID(const Jid &peer, const QString &sid) const +{ + QPtrListIterator it(d->activeList); + for(Entry *e; (e = it.current()); ++it) { + if(e->i && e->i->peer.compare(peer) && e->sid == sid) + return e; + } + return 0; +} + +S5BManager::Entry *S5BManager::findServerEntryByHash(const QString &key) const +{ + const QPtrList &manList = d->serv->managerList(); + QPtrListIterator it(manList); + for(S5BManager *m; (m = it.current()); ++it) { + Entry *e = m->findEntryByHash(key); + if(e) + return e; + } + return 0; +} + +bool S5BManager::srv_ownsHash(const QString &key) const +{ + if(findEntryByHash(key)) + return true; + return false; +} + +void S5BManager::srv_incomingReady(SocksClient *sc, const QString &key) +{ + Entry *e = findEntryByHash(key); + if(!e->i->allowIncoming) { + sc->requestDeny(); + SafeDelete::deleteSingle(sc); + return; + } + if(e->c->d->mode == S5BConnection::Datagram) + sc->grantUDPAssociate("", 0); + else + sc->grantConnect(); + e->relatedServer = (S5BServer *)sender(); + e->i->setIncomingClient(sc); +} + +void S5BManager::srv_incomingUDP(bool init, const QHostAddress &addr, int port, const QString &key, const QByteArray &data) +{ + Entry *e = findEntryByHash(key); + if(!e->c->d->mode != S5BConnection::Datagram) + return; // this key isn't in udp mode? drop! + + if(init) { + if(e->udp_init) + return; // only init once + + // lock on to this sender + e->udp_addr = addr; + e->udp_port = port; + e->udp_init = true; + + // reply that initialization was successful + d->ps->sendUDPSuccess(e->c->d->peer, key); + return; + } + + // not initialized yet? something went wrong + if(!e->udp_init) + return; + + // must come from same source as when initialized + if(addr.toString() != e->udp_addr.toString() || port != e->udp_port) + return; + + e->c->man_udpReady(data); +} + +void S5BManager::srv_unlink() +{ + d->serv = 0; +} + +void S5BManager::con_connect(S5BConnection *c) +{ + if(findEntry(c)) + return; + Entry *e = new Entry; + e->c = c; + e->sid = c->d->sid; + d->activeList.append(e); + + if(c->d->proxy.isValid()) { + queryProxy(e); + return; + } + entryContinue(e); +} + +void S5BManager::con_accept(S5BConnection *c) +{ + Entry *e = findEntry(c); + if(!e) + return; + + if(e->c->d->req.fast) { + if(targetShouldOfferProxy(e)) { + queryProxy(e); + return; + } + } + entryContinue(e); +} + +void S5BManager::con_reject(S5BConnection *c) +{ + d->ps->respondError(c->d->peer, c->d->req.id, 406, "Not acceptable"); +} + +void S5BManager::con_unlink(S5BConnection *c) +{ + Entry *e = findEntry(c); + if(!e) + return; + + // active incoming request? cancel it + if(e->i && e->i->conn) + d->ps->respondError(e->i->peer, e->i->out_id, 406, "Not acceptable"); + delete e->i; + d->activeList.removeRef(e); +} + +void S5BManager::con_sendUDP(S5BConnection *c, const QByteArray &buf) +{ + Entry *e = findEntry(c); + if(!e) + return; + if(!e->udp_init) + return; + + if(e->relatedServer) + e->relatedServer->writeUDP(e->udp_addr, e->udp_port, buf); +} + +void S5BManager::item_accepted() +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->accepted(); // signal +} + +void S5BManager::item_tryingHosts(const StreamHostList &list) +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->tryingHosts(list); // signal +} + +void S5BManager::item_proxyConnect() +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->proxyConnect(); // signal +} + +void S5BManager::item_waitingForActivation() +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->waitingForActivation(); // signal +} + +void S5BManager::item_connected() +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + // grab the client + SocksClient *client = i->client; + i->client = 0; + SocksUDP *client_udp = i->client_udp; + i->client_udp = 0; + + // give it to the connection + e->c->man_clientReady(client, client_udp); +} + +void S5BManager::item_error(int x) +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->man_failed(x); +} + +void S5BManager::entryContinue(Entry *e) +{ + e->i = new Item(this); + e->i->proxy = e->proxyInfo; + + connect(e->i, SIGNAL(accepted()), SLOT(item_accepted())); + connect(e->i, SIGNAL(tryingHosts(const StreamHostList &)), SLOT(item_tryingHosts(const StreamHostList &))); + connect(e->i, SIGNAL(proxyConnect()), SLOT(item_proxyConnect())); + connect(e->i, SIGNAL(waitingForActivation()), SLOT(item_waitingForActivation())); + connect(e->i, SIGNAL(connected()), SLOT(item_connected())); + connect(e->i, SIGNAL(error(int)), SLOT(item_error(int))); + + if(e->c->isRemote()) { + const S5BRequest &req = e->c->d->req; + e->i->startTarget(e->sid, d->client->jid(), e->c->d->peer, req.hosts, req.id, req.fast, req.udp); + } + else { + e->i->startInitiator(e->sid, d->client->jid(), e->c->d->peer, true, e->c->d->mode == S5BConnection::Datagram ? true: false); + e->c->requesting(); // signal + } +} + +void S5BManager::queryProxy(Entry *e) +{ + QGuardedPtr self = this; + e->c->proxyQuery(); // signal + if(!self) + return; + +#ifdef S5B_DEBUG + printf("querying proxy: [%s]\n", e->c->d->proxy.full().latin1()); +#endif + e->query = new JT_S5B(d->client->rootTask()); + connect(e->query, SIGNAL(finished()), SLOT(query_finished())); + e->query->requestProxyInfo(e->c->d->proxy); + e->query->go(true); +} + +void S5BManager::query_finished() +{ + JT_S5B *query = (JT_S5B *)sender(); + Entry *e; + bool found = false; + QPtrListIterator it(d->activeList); + for(; (e = it.current()); ++it) { + if(e->query == query) { + found = true; + break; + } + } + if(!found) + return; + e->query = 0; + +#ifdef S5B_DEBUG + printf("query finished: "); +#endif + if(query->success()) { + e->proxyInfo = query->proxyInfo(); +#ifdef S5B_DEBUG + printf("host/ip=[%s] port=[%d]\n", e->proxyInfo.host().latin1(), e->proxyInfo.port()); +#endif + } + else { +#ifdef S5B_DEBUG + printf("fail\n"); +#endif + } + + QGuardedPtr self = this; + e->c->proxyResult(query->success()); // signal + if(!self) + return; + + entryContinue(e); +} + +bool S5BManager::targetShouldOfferProxy(Entry *e) +{ + if(!e->c->d->proxy.isValid()) + return false; + + // if target, don't offer any proxy if the initiator already did + const StreamHostList &hosts = e->c->d->req.hosts; + for(StreamHostList::ConstIterator it = hosts.begin(); it != hosts.end(); ++it) { + if((*it).isProxy()) + return false; + } + + // ensure we don't offer the same proxy as the initiator + if(haveHost(hosts, e->c->d->proxy)) + return false; + + return true; +} + +//---------------------------------------------------------------------------- +// S5BManager::Item +//---------------------------------------------------------------------------- +S5BManager::Item::Item(S5BManager *manager) : QObject(0) +{ + m = manager; + task = 0; + proxy_task = 0; + conn = 0; + proxy_conn = 0; + client_udp = 0; + client = 0; + client_out_udp = 0; + client_out = 0; + reset(); +} + +S5BManager::Item::~Item() +{ + reset(); +} + +void S5BManager::Item::reset() +{ + delete task; + task = 0; + + delete proxy_task; + proxy_task = 0; + + delete conn; + conn = 0; + + delete proxy_conn; + proxy_conn = 0; + + delete client_udp; + client_udp = 0; + + delete client; + client = 0; + + delete client_out_udp; + client_out_udp = 0; + + delete client_out; + client_out = 0; + + state = Idle; + wantFast = false; + targetMode = Unknown; + fast = false; + activated = false; + lateProxy = false; + connSuccess = false; + localFailed = false; + remoteFailed = false; + allowIncoming = false; + udp = false; +} + +void S5BManager::Item::startInitiator(const QString &_sid, const Jid &_self, const Jid &_peer, bool fast, bool _udp) +{ + sid = _sid; + self = _self; + peer = _peer; + key = makeKey(sid, self, peer); + out_key = makeKey(sid, peer, self); + wantFast = fast; + udp = _udp; + +#ifdef S5B_DEBUG + printf("S5BManager::Item initiating request %s [%s]\n", peer.full().latin1(), sid.latin1()); +#endif + state = Initiator; + doOutgoing(); +} + +void S5BManager::Item::startTarget(const QString &_sid, const Jid &_self, const Jid &_peer, const StreamHostList &hosts, const QString &iq_id, bool _fast, bool _udp) +{ + sid = _sid; + peer = _peer; + self = _self; + in_hosts = hosts; + in_id = iq_id; + fast = _fast; + key = makeKey(sid, self, peer); + out_key = makeKey(sid, peer, self); + udp = _udp; + +#ifdef S5B_DEBUG + printf("S5BManager::Item incoming request %s [%s]\n", peer.full().latin1(), sid.latin1()); +#endif + state = Target; + if(fast) + doOutgoing(); + doIncoming(); +} + +void S5BManager::Item::handleFast(const StreamHostList &hosts, const QString &iq_id) +{ + targetMode = Fast; + + QGuardedPtr self = this; + accepted(); + if(!self) + return; + + // if we already have a stream, then bounce this request + if(client) { + m->doError(peer, iq_id, 406, "Not acceptable"); + } + else { + in_hosts = hosts; + in_id = iq_id; + doIncoming(); + } +} + +void S5BManager::Item::doOutgoing() +{ + StreamHostList hosts; + S5BServer *serv = m->server(); + if(serv && serv->isActive() && !haveHost(in_hosts, m->client()->jid())) { + QStringList hostList = serv->hostList(); + for(QStringList::ConstIterator it = hostList.begin(); it != hostList.end(); ++it) { + StreamHost h; + h.setJid(m->client()->jid()); + h.setHost(*it); + h.setPort(serv->port()); + hosts += h; + } + } + + // if the proxy is valid, then it's ok to add (the manager already ensured that it doesn't conflict) + if(proxy.jid().isValid()) + hosts += proxy; + + // if we're the target and we have no streamhosts of our own, then don't even bother with fast-mode + if(state == Target && hosts.isEmpty()) { + fast = false; + return; + } + + allowIncoming = true; + + task = new JT_S5B(m->client()->rootTask()); + connect(task, SIGNAL(finished()), SLOT(jt_finished())); + task->request(peer, sid, hosts, state == Initiator ? wantFast : false, udp); + out_id = task->id(); + task->go(true); +} + +void S5BManager::Item::doIncoming() +{ + if(in_hosts.isEmpty()) { + doConnectError(); + return; + } + + StreamHostList list; + if(lateProxy) { + // take just the proxy streamhosts + for(StreamHostList::ConstIterator it = in_hosts.begin(); it != in_hosts.end(); ++it) { + if((*it).isProxy()) + list += *it; + } + lateProxy = false; + } + else { + // only try doing the late proxy trick if using fast mode AND we did not offer a proxy + if((state == Initiator || (state == Target && fast)) && !proxy.jid().isValid()) { + // take just the non-proxy streamhosts + bool hasProxies = false; + for(StreamHostList::ConstIterator it = in_hosts.begin(); it != in_hosts.end(); ++it) { + if((*it).isProxy()) + hasProxies = true; + else + list += *it; + } + if(hasProxies) { + lateProxy = true; + + // no regular streamhosts? wait for remote error + if(list.isEmpty()) + return; + } + } + else + list = in_hosts; + } + + conn = new S5BConnector; + connect(conn, SIGNAL(result(bool)), SLOT(conn_result(bool))); + + QGuardedPtr self = this; + tryingHosts(list); + if(!self) + return; + + conn->start(m->client()->jid(), list, out_key, udp, lateProxy ? 10 : 30); +} + +void S5BManager::Item::setIncomingClient(SocksClient *sc) +{ +#ifdef S5B_DEBUG + printf("S5BManager::Item: %s [%s] successful incoming connection\n", peer.full().latin1(), sid.latin1()); +#endif + + connect(sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); + connect(sc, SIGNAL(bytesWritten(int)), SLOT(sc_bytesWritten(int))); + connect(sc, SIGNAL(error(int)), SLOT(sc_error(int))); + + client = sc; + allowIncoming = false; +} + +void S5BManager::Item::incomingActivate(const Jid &streamHost) +{ + if(!activated) { + activatedStream = streamHost; + checkForActivation(); + } +} + +void S5BManager::Item::jt_finished() +{ + JT_S5B *j = task; + task = 0; + +#ifdef S5B_DEBUG + printf("jt_finished: state=%s, %s\n", state == Initiator ? "initiator" : "target", j->success() ? "ok" : "fail"); +#endif + + if(state == Initiator) { + if(targetMode == Unknown) { + targetMode = NotFast; + QGuardedPtr self = this; + accepted(); + if(!self) + return; + } + } + + // if we've already reported successfully connecting to them, then this response doesn't matter + if(state == Initiator && connSuccess) { + tryActivation(); + return; + } + + if(j->success()) { + // stop connecting out + if(conn || lateProxy) { + delete conn; + conn = 0; + doConnectError(); + } + + Jid streamHost = j->streamHostUsed(); + + // they connected to us? + if(streamHost.compare(self)) { + if(client) { + if(state == Initiator) { + activatedStream = streamHost; + tryActivation(); + } + else + checkForActivation(); + } + else { +#ifdef S5B_DEBUG + printf("S5BManager::Item %s claims to have connected to us, but we don't see this\n", peer.full().latin1()); +#endif + reset(); + error(ErrWrongHost); + } + } + else if(streamHost.compare(proxy.jid())) { + // toss out any direct incoming, since it won't be used + delete client; + client = 0; + allowIncoming = false; + +#ifdef S5B_DEBUG + printf("attempting to connect to proxy\n"); +#endif + // connect to the proxy + proxy_conn = new S5BConnector; + connect(proxy_conn, SIGNAL(result(bool)), SLOT(proxy_result(bool))); + StreamHostList list; + list += proxy; + + QGuardedPtr self = this; + proxyConnect(); + if(!self) + return; + + proxy_conn->start(m->client()->jid(), list, key, udp, 30); + } + else { +#ifdef S5B_DEBUG + printf("S5BManager::Item %s claims to have connected to a streamhost we never offered\n", peer.full().latin1()); +#endif + reset(); + error(ErrWrongHost); + } + } + else { +#ifdef S5B_DEBUG + printf("S5BManager::Item %s [%s] error\n", peer.full().latin1(), sid.latin1()); +#endif + remoteFailed = true; + statusCode = j->statusCode(); + + if(lateProxy) { + if(!conn) + doIncoming(); + } + else { + // if connSuccess is true at this point, then we're a Target + if(connSuccess) + checkForActivation(); + else + checkFailure(); + } + } +} + +void S5BManager::Item::conn_result(bool b) +{ + if(b) { + SocksClient *sc = conn->takeClient(); + SocksUDP *sc_udp = conn->takeUDP(); + StreamHost h = conn->streamHostUsed(); + delete conn; + conn = 0; + connSuccess = true; + +#ifdef S5B_DEBUG + printf("S5BManager::Item: %s [%s] successful outgoing connection\n", peer.full().latin1(), sid.latin1()); +#endif + + connect(sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); + connect(sc, SIGNAL(bytesWritten(int)), SLOT(sc_bytesWritten(int))); + connect(sc, SIGNAL(error(int)), SLOT(sc_error(int))); + + m->doSuccess(peer, in_id, h.jid()); + + // if the first batch works, don't try proxy + lateProxy = false; + + // if initiator, run with this one + if(state == Initiator) { + // if we had an incoming one, toss it + delete client_udp; + client_udp = sc_udp; + delete client; + client = sc; + allowIncoming = false; + activatedStream = peer; + tryActivation(); + } + else { + client_out_udp = sc_udp; + client_out = sc; + checkForActivation(); + } + } + else { + delete conn; + conn = 0; + + // if we delayed the proxies for later, try now + if(lateProxy) { + if(remoteFailed) + doIncoming(); + } + else + doConnectError(); + } +} + +void S5BManager::Item::proxy_result(bool b) +{ +#ifdef S5B_DEBUG + printf("proxy_result: %s\n", b ? "ok" : "fail"); +#endif + if(b) { + SocksClient *sc = proxy_conn->takeClient(); + SocksUDP *sc_udp = proxy_conn->takeUDP(); + delete proxy_conn; + proxy_conn = 0; + + connect(sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); + connect(sc, SIGNAL(bytesWritten(int)), SLOT(sc_bytesWritten(int))); + connect(sc, SIGNAL(error(int)), SLOT(sc_error(int))); + + client = sc; + client_udp = sc_udp; + + // activate +#ifdef S5B_DEBUG + printf("activating proxy stream\n"); +#endif + proxy_task = new JT_S5B(m->client()->rootTask()); + connect(proxy_task, SIGNAL(finished()), SLOT(proxy_finished())); + proxy_task->requestActivation(proxy.jid(), sid, peer); + proxy_task->go(true); + } + else { + delete proxy_conn; + proxy_conn = 0; + reset(); + error(ErrProxy); + } +} + +void S5BManager::Item::proxy_finished() +{ + JT_S5B *j = proxy_task; + proxy_task = 0; + + if(j->success()) { +#ifdef S5B_DEBUG + printf("proxy stream activated\n"); +#endif + if(state == Initiator) { + activatedStream = proxy.jid(); + tryActivation(); + } + else + checkForActivation(); + } + else { + reset(); + error(ErrProxy); + } +} + +void S5BManager::Item::sc_readyRead() +{ +#ifdef S5B_DEBUG + printf("sc_readyRead\n"); +#endif + // only targets check for activation, and only should do it if there is no pending outgoing iq-set + if(state == Target && !task && !proxy_task) + checkForActivation(); +} + +void S5BManager::Item::sc_bytesWritten(int) +{ +#ifdef S5B_DEBUG + printf("sc_bytesWritten\n"); +#endif + // this should only happen to the initiator, and should always be 1 byte (the '\r' sent earlier) + finished(); +} + +void S5BManager::Item::sc_error(int) +{ +#ifdef S5B_DEBUG + printf("sc_error\n"); +#endif + reset(); + error(ErrConnect); +} + +void S5BManager::Item::doConnectError() +{ + localFailed = true; + m->doError(peer, in_id, 404, "Could not connect to given hosts"); + checkFailure(); +} + +void S5BManager::Item::tryActivation() +{ +#ifdef S5B_DEBUG + printf("tryActivation\n"); +#endif + if(activated) { +#ifdef S5B_DEBUG + printf("already activated !?\n"); +#endif + return; + } + + if(targetMode == NotFast) { +#ifdef S5B_DEBUG + printf("tryActivation: NotFast\n"); +#endif + // nothing to activate, we're done + finished(); + } + else if(targetMode == Fast) { + // with fast mode, we don't wait for the iq reply, so delete the task (if any) + delete task; + task = 0; + + activated = true; + + // if udp, activate using special stanza + if(udp) { + m->doActivate(peer, sid, activatedStream); + } + else { +#ifdef S5B_DEBUG + printf("sending extra CR\n"); +#endif + // must send [CR] to activate target streamhost + QByteArray a(1); + a[0] = '\r'; + client->write(a); + } + } +} + +void S5BManager::Item::checkForActivation() +{ + QPtrList clientList; + if(client) + clientList.append(client); + if(client_out) + clientList.append(client_out); + QPtrListIterator it(clientList); + for(SocksClient *sc; (sc = it.current()); ++it) { +#ifdef S5B_DEBUG + printf("checking for activation\n"); +#endif + if(fast) { + bool ok = false; + if(udp) { + if((sc == client_out && activatedStream.compare(self)) || (sc == client && !activatedStream.compare(self))) { + clientList.removeRef(sc); + ok = true; + } + } + else { +#ifdef S5B_DEBUG + printf("need CR\n"); +#endif + if(sc->bytesAvailable() >= 1) { + clientList.removeRef(sc); + QByteArray a = sc->read(1); + if(a[0] != '\r') { + delete sc; + return; + } + ok = true; + } + } + + if(ok) { + SocksUDP *sc_udp = 0; + if(sc == client) { + delete client_out_udp; + client_out_udp = 0; + sc_udp = client_udp; + } + else if(sc == client_out) { + delete client_udp; + client_udp = 0; + sc_udp = client_out_udp; + } + + sc->disconnect(this); + clientList.setAutoDelete(true); + clientList.clear(); + client = sc; + client_out = 0; + client_udp = sc_udp; + activated = true; +#ifdef S5B_DEBUG + printf("activation success\n"); +#endif + break; + } + } + else { +#ifdef S5B_DEBUG + printf("not fast mode, no need to wait for anything\n"); +#endif + clientList.removeRef(sc); + sc->disconnect(this); + clientList.setAutoDelete(true); + clientList.clear(); + client = sc; + client_out = 0; + activated = true; + break; + } + } + + if(activated) { + finished(); + } + else { + // only emit waitingForActivation if there is nothing left to do + if((connSuccess || localFailed) && !proxy_task && !proxy_conn) + waitingForActivation(); + } +} + +void S5BManager::Item::checkFailure() +{ + bool failed = false; + if(state == Initiator) { + if(remoteFailed) { + if((localFailed && targetMode == Fast) || targetMode == NotFast) + failed = true; + } + } + else { + if(localFailed) { + if((remoteFailed && fast) || !fast) + failed = true; + } + } + + if(failed) { + if(state == Initiator) { + reset(); + if(statusCode == 404) + error(ErrConnect); + else + error(ErrRefused); + } + else { + reset(); + error(ErrConnect); + } + } +} + +void S5BManager::Item::finished() +{ + client->disconnect(this); + state = Active; +#ifdef S5B_DEBUG + printf("S5BManager::Item %s [%s] linked successfully\n", peer.full().latin1(), sid.latin1()); +#endif + connected(); +} + +//---------------------------------------------------------------------------- +// S5BConnector +//---------------------------------------------------------------------------- +class S5BConnector::Item : public QObject +{ + Q_OBJECT +public: + SocksClient *client; + SocksUDP *client_udp; + StreamHost host; + QString key; + bool udp; + int udp_tries; + QTimer t; + Jid jid; + + Item(const Jid &self, const StreamHost &_host, const QString &_key, bool _udp) : QObject(0) + { + jid = self; + host = _host; + key = _key; + udp = _udp; + client = new SocksClient; + client_udp = 0; + connect(client, SIGNAL(connected()), SLOT(sc_connected())); + connect(client, SIGNAL(error(int)), SLOT(sc_error(int))); + connect(&t, SIGNAL(timeout()), SLOT(trySendUDP())); + } + + ~Item() + { + cleanup(); + } + + void start() + { + client->connectToHost(host.host(), host.port(), key, 0, udp); + } + + void udpSuccess() + { + t.stop(); + client_udp->change(key, 0); // flip over to the data port + success(); + } + +signals: + void result(bool); + +private slots: + void sc_connected() + { + // if udp, need to send init packet before we are good + if(udp) { + // port 1 is init + client_udp = client->createUDP(key, 1, client->peerAddress(), client->peerPort()); + udp_tries = 0; + t.start(5000); + trySendUDP(); + return; + } + + success(); + } + + void sc_error(int) + { +#ifdef S5B_DEBUG + printf("S5BConnector[%s]: error\n", host.host().latin1()); +#endif + cleanup(); + result(false); + } + + void trySendUDP() + { + if(udp_tries == 5) { + t.stop(); + cleanup(); + result(false); + return; + } + + // send initialization with our JID + QCString cs = jid.full().utf8(); + QByteArray a(cs.length()); + memcpy(a.data(), cs.data(), a.size()); + client_udp->write(a); + ++udp_tries; + } + +private: + void cleanup() + { + delete client_udp; + client_udp = 0; + delete client; + client = 0; + } + + void success() + { +#ifdef S5B_DEBUG + printf("S5BConnector[%s]: success\n", host.host().latin1()); +#endif + client->disconnect(this); + result(true); + } +}; + +class S5BConnector::Private +{ +public: + SocksClient *active; + SocksUDP *active_udp; + QPtrList itemList; + QString key; + StreamHost activeHost; + QTimer t; +}; + +S5BConnector::S5BConnector(QObject *parent) +:QObject(parent) +{ + d = new Private; + d->active = 0; + d->active_udp = 0; + d->itemList.setAutoDelete(true); + connect(&d->t, SIGNAL(timeout()), SLOT(t_timeout())); +} + +S5BConnector::~S5BConnector() +{ + reset(); + delete d; +} + +void S5BConnector::reset() +{ + d->t.stop(); + delete d->active_udp; + d->active_udp = 0; + delete d->active; + d->active = 0; + d->itemList.clear(); +} + +void S5BConnector::start(const Jid &self, const StreamHostList &hosts, const QString &key, bool udp, int timeout) +{ + reset(); + +#ifdef S5B_DEBUG + printf("S5BConnector: starting [%p]!\n", this); +#endif + for(StreamHostList::ConstIterator it = hosts.begin(); it != hosts.end(); ++it) { + Item *i = new Item(self, *it, key, udp); + connect(i, SIGNAL(result(bool)), SLOT(item_result(bool))); + d->itemList.append(i); + i->start(); + } + d->t.start(timeout * 1000); +} + +SocksClient *S5BConnector::takeClient() +{ + SocksClient *c = d->active; + d->active = 0; + return c; +} + +SocksUDP *S5BConnector::takeUDP() +{ + SocksUDP *c = d->active_udp; + d->active_udp = 0; + return c; +} + +StreamHost S5BConnector::streamHostUsed() const +{ + return d->activeHost; +} + +void S5BConnector::item_result(bool b) +{ + Item *i = (Item *)sender(); + if(b) { + d->active = i->client; + i->client = 0; + d->active_udp = i->client_udp; + i->client_udp = 0; + d->activeHost = i->host; + d->itemList.clear(); + d->t.stop(); +#ifdef S5B_DEBUG + printf("S5BConnector: complete! [%p]\n", this); +#endif + result(true); + } + else { + d->itemList.removeRef(i); + if(d->itemList.isEmpty()) { + d->t.stop(); +#ifdef S5B_DEBUG + printf("S5BConnector: failed! [%p]\n", this); +#endif + result(false); + } + } +} + +void S5BConnector::t_timeout() +{ + reset(); +#ifdef S5B_DEBUG + printf("S5BConnector: failed! (timeout)\n"); +#endif + result(false); +} + +void S5BConnector::man_udpSuccess(const Jid &streamHost) +{ + // was anyone sending to this streamhost? + QPtrListIterator it(d->itemList); + for(Item *i; (i = it.current()); ++it) { + if(i->host.jid().compare(streamHost) && i->client_udp) { + i->udpSuccess(); + return; + } + } +} + +//---------------------------------------------------------------------------- +// S5BServer +//---------------------------------------------------------------------------- +class S5BServer::Item : public QObject +{ + Q_OBJECT +public: + SocksClient *client; + QString host; + QTimer expire; + + Item(SocksClient *c) : QObject(0) + { + client = c; + connect(client, SIGNAL(incomingMethods(int)), SLOT(sc_incomingMethods(int))); + connect(client, SIGNAL(incomingConnectRequest(const QString &, int)), SLOT(sc_incomingConnectRequest(const QString &, int))); + connect(client, SIGNAL(error(int)), SLOT(sc_error(int))); + + connect(&expire, SIGNAL(timeout()), SLOT(doError())); + resetExpiration(); + } + + ~Item() + { + delete client; + } + + void resetExpiration() + { + expire.start(30000); + } + +signals: + void result(bool); + +private slots: + void doError() + { + expire.stop(); + delete client; + client = 0; + result(false); + } + + void sc_incomingMethods(int m) + { + if(m & SocksClient::AuthNone) + client->chooseMethod(SocksClient::AuthNone); + else + doError(); + } + + void sc_incomingConnectRequest(const QString &_host, int port) + { + if(port == 0) { + host = _host; + client->disconnect(this); + result(true); + } + else + doError(); + } + + void sc_error(int) + { + doError(); + } +}; + +class S5BServer::Private +{ +public: + SocksServer serv; + QStringList hostList; + QPtrList manList; + QPtrList itemList; +}; + +S5BServer::S5BServer(QObject *parent) +:QObject(parent) +{ + d = new Private; + d->itemList.setAutoDelete(true); + connect(&d->serv, SIGNAL(incomingReady()), SLOT(ss_incomingReady())); + connect(&d->serv, SIGNAL(incomingUDP(const QString &, int, const QHostAddress &, int, const QByteArray &)), SLOT(ss_incomingUDP(const QString &, int, const QHostAddress &, int, const QByteArray &))); +} + +S5BServer::~S5BServer() +{ + unlinkAll(); + delete d; +} + +bool S5BServer::isActive() const +{ + return d->serv.isActive(); +} + +bool S5BServer::start(int port) +{ + d->serv.stop(); + return d->serv.listen(port, true); +} + +void S5BServer::stop() +{ + d->serv.stop(); +} + +void S5BServer::setHostList(const QStringList &list) +{ + d->hostList = list; +} + +QStringList S5BServer::hostList() const +{ + return d->hostList; +} + +int S5BServer::port() const +{ + return d->serv.port(); +} + +void S5BServer::ss_incomingReady() +{ + Item *i = new Item(d->serv.takeIncoming()); +#ifdef S5B_DEBUG + printf("S5BServer: incoming connection from %s:%d\n", i->client->peerAddress().toString().latin1(), i->client->peerPort()); +#endif + connect(i, SIGNAL(result(bool)), SLOT(item_result(bool))); + d->itemList.append(i); +} + +void S5BServer::ss_incomingUDP(const QString &host, int port, const QHostAddress &addr, int sourcePort, const QByteArray &data) +{ + if(port != 0 || port != 1) + return; + + QPtrListIterator it(d->manList); + for(S5BManager *m; (m = it.current()); ++it) { + if(m->srv_ownsHash(host)) { + m->srv_incomingUDP(port == 1 ? true : false, addr, sourcePort, host, data); + return; + } + } +} + +void S5BServer::item_result(bool b) +{ + Item *i = (Item *)sender(); +#ifdef S5B_DEBUG + printf("S5BServer item result: %d\n", b); +#endif + if(!b) { + d->itemList.removeRef(i); + return; + } + + SocksClient *c = i->client; + i->client = 0; + QString key = i->host; + d->itemList.removeRef(i); + + // find the appropriate manager for this incoming connection + QPtrListIterator it(d->manList); + for(S5BManager *m; (m = it.current()); ++it) { + if(m->srv_ownsHash(key)) { + m->srv_incomingReady(c, key); + return; + } + } + + // throw it away + delete c; +} + +void S5BServer::link(S5BManager *m) +{ + d->manList.append(m); +} + +void S5BServer::unlink(S5BManager *m) +{ + d->manList.removeRef(m); +} + +void S5BServer::unlinkAll() +{ + QPtrListIterator it(d->manList); + for(S5BManager *m; (m = it.current()); ++it) + m->srv_unlink(); + d->manList.clear(); +} + +const QPtrList & S5BServer::managerList() const +{ + return d->manList; +} + +void S5BServer::writeUDP(const QHostAddress &addr, int port, const QByteArray &data) +{ + d->serv.writeUDP(addr, port, data); +} + +//---------------------------------------------------------------------------- +// JT_S5B +//---------------------------------------------------------------------------- +class JT_S5B::Private +{ +public: + QDomElement iq; + Jid to; + Jid streamHost; + StreamHost proxyInfo; + int mode; + QTimer t; +}; + +JT_S5B::JT_S5B(Task *parent) +:Task(parent) +{ + d = new Private; + d->mode = -1; + connect(&d->t, SIGNAL(timeout()), SLOT(t_timeout())); +} + +JT_S5B::~JT_S5B() +{ + delete d; +} + +void JT_S5B::request(const Jid &to, const QString &sid, const StreamHostList &hosts, bool fast, bool udp) +{ + d->mode = 0; + + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + query.setAttribute("sid", sid); + query.setAttribute("mode", udp ? "udp" : "tcp" ); + iq.appendChild(query); + for(StreamHostList::ConstIterator it = hosts.begin(); it != hosts.end(); ++it) { + QDomElement shost = doc()->createElement("streamhost"); + shost.setAttribute("jid", (*it).jid().full()); + shost.setAttribute("host", (*it).host()); + shost.setAttribute("port", QString::number((*it).port())); + if((*it).isProxy()) { + QDomElement p = doc()->createElement("proxy"); + p.setAttribute("xmlns", "http://affinix.com/jabber/stream"); + shost.appendChild(p); + } + query.appendChild(shost); + } + if(fast) { + QDomElement e = doc()->createElement("fast"); + e.setAttribute("xmlns", "http://affinix.com/jabber/stream"); + query.appendChild(e); + } + d->iq = iq; +} + +void JT_S5B::requestProxyInfo(const Jid &to) +{ + d->mode = 1; + + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "get", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + iq.appendChild(query); + d->iq = iq; +} + +void JT_S5B::requestActivation(const Jid &to, const QString &sid, const Jid &target) +{ + d->mode = 2; + + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + query.setAttribute("sid", sid); + iq.appendChild(query); + QDomElement act = doc()->createElement("activate"); + act.appendChild(doc()->createTextNode(target.full())); + query.appendChild(act); + d->iq = iq; +} + +void JT_S5B::onGo() +{ + if(d->mode == 1) + d->t.start(15000, true); + send(d->iq); +} + +void JT_S5B::onDisconnect() +{ + d->t.stop(); +} + +bool JT_S5B::take(const QDomElement &x) +{ + if(d->mode == -1) + return false; + + if(!iqVerify(x, d->to, id())) + return false; + + d->t.stop(); + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + if(d->mode == 0) { + d->streamHost = ""; + if(!q.isNull()) { + QDomElement shost = q.elementsByTagName("streamhost-used").item(0).toElement(); + if(!shost.isNull()) + d->streamHost = shost.attribute("jid"); + } + + setSuccess(); + } + else if(d->mode == 1) { + if(!q.isNull()) { + QDomElement shost = q.elementsByTagName("streamhost").item(0).toElement(); + if(!shost.isNull()) { + Jid j = shost.attribute("jid"); + if(j.isValid()) { + QString host = shost.attribute("host"); + if(!host.isEmpty()) { + int port = shost.attribute("port").toInt(); + StreamHost h; + h.setJid(j); + h.setHost(host); + h.setPort(port); + h.setIsProxy(true); + d->proxyInfo = h; + } + } + } + } + + setSuccess(); + } + else { + setSuccess(); + } + } + else { + setError(x); + } + + return true; +} + +void JT_S5B::t_timeout() +{ + d->mode = -1; + setError(500, "Timed out"); +} + +Jid JT_S5B::streamHostUsed() const +{ + return d->streamHost; +} + +StreamHost JT_S5B::proxyInfo() const +{ + return d->proxyInfo; +} + +//---------------------------------------------------------------------------- +// JT_PushS5B +//---------------------------------------------------------------------------- +JT_PushS5B::JT_PushS5B(Task *parent) +:Task(parent) +{ +} + +JT_PushS5B::~JT_PushS5B() +{ +} + +int JT_PushS5B::priority() const +{ + return 1; +} + +bool JT_PushS5B::take(const QDomElement &e) +{ + // look for udpsuccess + if(e.tagName() == "message") { + QDomElement x = e.elementsByTagName("udpsuccess").item(0).toElement(); + if(!x.isNull() && x.attribute("xmlns") == "http://jabber.org/protocol/bytestreams") { + incomingUDPSuccess(Jid(x.attribute("from")), x.attribute("dstaddr")); + return true; + } + x = e.elementsByTagName("activate").item(0).toElement(); + if(!x.isNull() && x.attribute("xmlns") == "http://affinix.com/jabber/stream") { + incomingActivate(Jid(x.attribute("from")), x.attribute("sid"), Jid(x.attribute("jid"))); + return true; + } + return false; + } + + // must be an iq-set tag + if(e.tagName() != "iq") + return false; + if(e.attribute("type") != "set") + return false; + if(queryNS(e) != "http://jabber.org/protocol/bytestreams") + return false; + + Jid from(e.attribute("from")); + QDomElement q = queryTag(e); + QString sid = q.attribute("sid"); + + StreamHostList hosts; + QDomNodeList nl = q.elementsByTagName("streamhost"); + for(uint n = 0; n < nl.count(); ++n) { + QDomElement shost = nl.item(n).toElement(); + if(hosts.count() < MAXSTREAMHOSTS) { + Jid j = shost.attribute("jid"); + if(!j.isValid()) + continue; + QString host = shost.attribute("host"); + if(host.isEmpty()) + continue; + int port = shost.attribute("port").toInt(); + QDomElement p = shost.elementsByTagName("proxy").item(0).toElement(); + bool isProxy = false; + if(!p.isNull() && p.attribute("xmlns") == "http://affinix.com/jabber/stream") + isProxy = true; + + StreamHost h; + h.setJid(j); + h.setHost(host); + h.setPort(port); + h.setIsProxy(isProxy); + hosts += h; + } + } + + bool fast = false; + QDomElement t; + t = q.elementsByTagName("fast").item(0).toElement(); + if(!t.isNull() && t.attribute("xmlns") == "http://affinix.com/jabber/stream") + fast = true; + + S5BRequest r; + r.from = from; + r.id = e.attribute("id"); + r.sid = sid; + r.hosts = hosts; + r.fast = fast; + r.udp = q.attribute("mode") == "udp" ? true: false; + + incoming(r); + return true; +} + +void JT_PushS5B::respondSuccess(const Jid &to, const QString &id, const Jid &streamHost) +{ + QDomElement iq = createIQ(doc(), "result", to.full(), id); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + iq.appendChild(query); + QDomElement shost = doc()->createElement("streamhost-used"); + shost.setAttribute("jid", streamHost.full()); + query.appendChild(shost); + send(iq); +} + +void JT_PushS5B::respondError(const Jid &to, const QString &id, int code, const QString &str) +{ + QDomElement iq = createIQ(doc(), "error", to.full(), id); + QDomElement err = textTag(doc(), "error", str); + err.setAttribute("code", QString::number(code)); + iq.appendChild(err); + send(iq); +} + +void JT_PushS5B::sendUDPSuccess(const Jid &to, const QString &dstaddr) +{ + QDomElement m = doc()->createElement("message"); + m.setAttribute("to", to.full()); + QDomElement u = doc()->createElement("udpsuccess"); + u.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + u.setAttribute("dstaddr", dstaddr); + m.appendChild(u); + send(m); +} + +void JT_PushS5B::sendActivate(const Jid &to, const QString &sid, const Jid &streamHost) +{ + QDomElement m = doc()->createElement("message"); + m.setAttribute("to", to.full()); + QDomElement act = doc()->createElement("activate"); + act.setAttribute("xmlns", "http://affinix.com/jabber/stream"); + act.setAttribute("sid", sid); + act.setAttribute("jid", streamHost.full()); + m.appendChild(act); + send(m); +} + +//---------------------------------------------------------------------------- +// StreamHost +//---------------------------------------------------------------------------- +StreamHost::StreamHost() +{ + v_port = -1; + proxy = false; +} + +const Jid & StreamHost::jid() const +{ + return j; +} + +const QString & StreamHost::host() const +{ + return v_host; +} + +int StreamHost::port() const +{ + return v_port; +} + +bool StreamHost::isProxy() const +{ + return proxy; +} + +void StreamHost::setJid(const Jid &_j) +{ + j = _j; +} + +void StreamHost::setHost(const QString &host) +{ + v_host = host; +} + +void StreamHost::setPort(int port) +{ + v_port = port; +} + +void StreamHost::setIsProxy(bool b) +{ + proxy = b; +} + +} + +#include"s5b.moc" diff --git a/kopete/protocols/jabber/libiris/iris/jabber/s5b.h b/kopete/protocols/jabber/libiris/iris/jabber/s5b.h new file mode 100644 index 00000000..dec06969 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/s5b.h @@ -0,0 +1,341 @@ +/* + * s5b.h - direct connection protocol via tcp + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMPP_S5B_H +#define XMPP_S5B_H + +#include +#include +#include +#include +#include"im.h" +#include"bytestream.h" + +class SocksClient; +class SocksUDP; + +namespace XMPP +{ + class StreamHost; + class S5BConnection; + class S5BManager; + class S5BServer; + struct S5BRequest; + typedef QValueList StreamHostList; + typedef QPtrList S5BConnectionList; + typedef QPtrListIterator S5BConnectionListIt; + + class S5BDatagram + { + public: + S5BDatagram(); + S5BDatagram(int source, int dest, const QByteArray &data); + + int sourcePort() const; + int destPort() const; + QByteArray data() const; + + private: + int _source, _dest; + QByteArray _buf; + }; + + class S5BConnection : public ByteStream + { + Q_OBJECT + public: + enum Mode { Stream, Datagram }; + enum Error { ErrRefused, ErrConnect, ErrProxy, ErrSocket }; + enum State { Idle, Requesting, Connecting, WaitingForAccept, Active }; + ~S5BConnection(); + + Jid proxy() const; + void setProxy(const Jid &proxy); + + void connectToJid(const Jid &peer, const QString &sid, Mode m = Stream); + void accept(); + void close(); + + Jid peer() const; + QString sid() const; + bool isRemote() const; + Mode mode() const; + int state() const; + + bool isOpen() const; + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + + void writeDatagram(const S5BDatagram &); + S5BDatagram readDatagram(); + int datagramsAvailable() const; + + signals: + void proxyQuery(); // querying proxy for streamhost information + void proxyResult(bool b); // query success / fail + void requesting(); // sent actual S5B request (initiator only) + void accepted(); // target accepted (initiator only + void tryingHosts(const StreamHostList &hosts); // currently connecting to these hosts + void proxyConnect(); // connecting to proxy + void waitingForActivation(); // waiting for activation (target only) + void connected(); // connection active + void datagramReady(); + + private slots: + void doPending(); + + void sc_connectionClosed(); + void sc_delayedCloseFinished(); + void sc_readyRead(); + void sc_bytesWritten(int); + void sc_error(int); + + void su_packetReady(const QByteArray &buf); + + private: + class Private; + Private *d; + + void reset(bool clear=false); + void handleUDP(const QByteArray &buf); + void sendUDP(const QByteArray &buf); + + friend class S5BManager; + void man_waitForAccept(const S5BRequest &r); + void man_clientReady(SocksClient *, SocksUDP *); + void man_udpReady(const QByteArray &buf); + void man_failed(int); + S5BConnection(S5BManager *, QObject *parent=0); + }; + + class S5BManager : public QObject + { + Q_OBJECT + public: + S5BManager(Client *); + ~S5BManager(); + + Client *client() const; + S5BServer *server() const; + void setServer(S5BServer *s); + + bool isAcceptableSID(const Jid &peer, const QString &sid) const; + QString genUniqueSID(const Jid &peer) const; + + S5BConnection *createConnection(); + S5BConnection *takeIncoming(); + + class Item; + class Entry; + + signals: + void incomingReady(); + + private slots: + void ps_incoming(const S5BRequest &req); + void ps_incomingUDPSuccess(const Jid &from, const QString &dstaddr); + void ps_incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost); + void item_accepted(); + void item_tryingHosts(const StreamHostList &list); + void item_proxyConnect(); + void item_waitingForActivation(); + void item_connected(); + void item_error(int); + void query_finished(); + + private: + class Private; + Private *d; + + S5BConnection *findIncoming(const Jid &from, const QString &sid) const; + Entry *findEntry(S5BConnection *) const; + Entry *findEntry(Item *) const; + Entry *findEntryByHash(const QString &key) const; + Entry *findEntryBySID(const Jid &peer, const QString &sid) const; + Entry *findServerEntryByHash(const QString &key) const; + + void entryContinue(Entry *e); + void queryProxy(Entry *e); + bool targetShouldOfferProxy(Entry *e); + + friend class S5BConnection; + void con_connect(S5BConnection *); + void con_accept(S5BConnection *); + void con_reject(S5BConnection *); + void con_unlink(S5BConnection *); + void con_sendUDP(S5BConnection *, const QByteArray &buf); + + friend class S5BServer; + bool srv_ownsHash(const QString &key) const; + void srv_incomingReady(SocksClient *sc, const QString &key); + void srv_incomingUDP(bool init, const QHostAddress &addr, int port, const QString &key, const QByteArray &data); + void srv_unlink(); + + friend class Item; + void doSuccess(const Jid &peer, const QString &id, const Jid &streamHost); + void doError(const Jid &peer, const QString &id, int, const QString &); + void doActivate(const Jid &peer, const QString &sid, const Jid &streamHost); + }; + + class S5BConnector : public QObject + { + Q_OBJECT + public: + S5BConnector(QObject *parent=0); + ~S5BConnector(); + + void reset(); + void start(const Jid &self, const StreamHostList &hosts, const QString &key, bool udp, int timeout); + SocksClient *takeClient(); + SocksUDP *takeUDP(); + StreamHost streamHostUsed() const; + + class Item; + + signals: + void result(bool); + + private slots: + void item_result(bool); + void t_timeout(); + + private: + class Private; + Private *d; + + friend class S5BManager; + void man_udpSuccess(const Jid &streamHost); + }; + + // listens on a port for serving + class S5BServer : public QObject + { + Q_OBJECT + public: + S5BServer(QObject *par=0); + ~S5BServer(); + + bool isActive() const; + bool start(int port); + void stop(); + int port() const; + void setHostList(const QStringList &); + QStringList hostList() const; + + class Item; + + private slots: + void ss_incomingReady(); + void ss_incomingUDP(const QString &host, int port, const QHostAddress &addr, int sourcePort, const QByteArray &data); + void item_result(bool); + + private: + class Private; + Private *d; + + friend class S5BManager; + void link(S5BManager *); + void unlink(S5BManager *); + void unlinkAll(); + const QPtrList & managerList() const; + void writeUDP(const QHostAddress &addr, int port, const QByteArray &data); + }; + + class JT_S5B : public Task + { + Q_OBJECT + public: + JT_S5B(Task *); + ~JT_S5B(); + + void request(const Jid &to, const QString &sid, const StreamHostList &hosts, bool fast, bool udp=false); + void requestProxyInfo(const Jid &to); + void requestActivation(const Jid &to, const QString &sid, const Jid &target); + + void onGo(); + void onDisconnect(); + bool take(const QDomElement &); + + Jid streamHostUsed() const; + StreamHost proxyInfo() const; + + private slots: + void t_timeout(); + + private: + class Private; + Private *d; + }; + + struct S5BRequest + { + Jid from; + QString id, sid; + StreamHostList hosts; + bool fast; + bool udp; + }; + class JT_PushS5B : public Task + { + Q_OBJECT + public: + JT_PushS5B(Task *); + ~JT_PushS5B(); + + int priority() const; + + void respondSuccess(const Jid &to, const QString &id, const Jid &streamHost); + void respondError(const Jid &to, const QString &id, int code, const QString &str); + void sendUDPSuccess(const Jid &to, const QString &dstaddr); + void sendActivate(const Jid &to, const QString &sid, const Jid &streamHost); + + bool take(const QDomElement &); + + signals: + void incoming(const S5BRequest &req); + void incomingUDPSuccess(const Jid &from, const QString &dstaddr); + void incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost); + }; + + class StreamHost + { + public: + StreamHost(); + + const Jid & jid() const; + const QString & host() const; + int port() const; + bool isProxy() const; + void setJid(const Jid &); + void setHost(const QString &); + void setPort(int); + void setIsProxy(bool); + + private: + Jid j; + QString v_host; + int v_port; + bool proxy; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.cpp b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.cpp new file mode 100644 index 00000000..813157bf --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.cpp @@ -0,0 +1,638 @@ +/* + * ibb.cpp - Inband bytestream + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp_ibb.h" + +#include +#include"xmpp_xmlcommon.h" +#include"base64.h" + +#include + +#define IBB_PACKET_SIZE 4096 +#define IBB_PACKET_DELAY 0 + +using namespace XMPP; + +static int num_conn = 0; +static int id_conn = 0; + +//---------------------------------------------------------------------------- +// IBBConnection +//---------------------------------------------------------------------------- +class IBBConnection::Private +{ +public: + Private() {} + + int state; + Jid peer; + QString sid; + IBBManager *m; + JT_IBB *j; + QDomElement comment; + QString iq_id; + + int blockSize; + QByteArray recvbuf, sendbuf; + bool closePending, closing; + + int id; +}; + +IBBConnection::IBBConnection(IBBManager *m) +:ByteStream(m) +{ + d = new Private; + d->m = m; + d->j = 0; + reset(); + + ++num_conn; + d->id = id_conn++; + QString dstr; dstr.sprintf("IBBConnection[%d]: constructing, count=%d\n", d->id, num_conn); + d->m->client()->debug(dstr); +} + +void IBBConnection::reset(bool clear) +{ + d->m->unlink(this); + d->state = Idle; + d->closePending = false; + d->closing = false; + + delete d->j; + d->j = 0; + + d->sendbuf.resize(0); + if(clear) + d->recvbuf.resize(0); +} + +IBBConnection::~IBBConnection() +{ + reset(true); + + --num_conn; + QString dstr; dstr.sprintf("IBBConnection[%d]: destructing, count=%d\n", d->id, num_conn); + d->m->client()->debug(dstr); + + delete d; +} + +void IBBConnection::connectToJid(const Jid &peer, const QDomElement &comment) +{ + close(); + reset(true); + + d->state = Requesting; + d->peer = peer; + d->comment = comment; + + QString dstr; dstr.sprintf("IBBConnection[%d]: initiating request to %s\n", d->id, peer.full().latin1()); + d->m->client()->debug(dstr); + + d->j = new JT_IBB(d->m->client()->rootTask()); + connect(d->j, SIGNAL(finished()), SLOT(ibb_finished())); + d->j->request(d->peer, comment); + d->j->go(true); +} + +void IBBConnection::accept() +{ + if(d->state != WaitingForAccept) + return; + + QString dstr; dstr.sprintf("IBBConnection[%d]: accepting %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1()); + d->m->client()->debug(dstr); + + d->m->doAccept(this, d->iq_id); + d->state = Active; + d->m->link(this); +} + +void IBBConnection::close() +{ + if(d->state == Idle) + return; + + if(d->state == WaitingForAccept) { + d->m->doReject(this, d->iq_id, 403, "Rejected"); + reset(); + return; + } + + QString dstr; dstr.sprintf("IBBConnection[%d]: closing\n", d->id); + d->m->client()->debug(dstr); + + if(d->state == Active) { + // if there is data pending to be written, then pend the closing + if(bytesToWrite() > 0) { + d->closePending = true; + trySend(); + return; + } + + // send a close packet + JT_IBB *j = new JT_IBB(d->m->client()->rootTask()); + j->sendData(d->peer, d->sid, QByteArray(), true); + j->go(true); + } + + reset(); +} + +int IBBConnection::state() const +{ + return d->state; +} + +Jid IBBConnection::peer() const +{ + return d->peer; +} + +QString IBBConnection::streamid() const +{ + return d->sid; +} + +QDomElement IBBConnection::comment() const +{ + return d->comment; +} + +bool IBBConnection::isOpen() const +{ + if(d->state == Active) + return true; + else + return false; +} + +void IBBConnection::write(const QByteArray &a) +{ + if(d->state != Active || d->closePending || d->closing) + return; + + // append to the end of our send buffer + int oldsize = d->sendbuf.size(); + d->sendbuf.resize(oldsize + a.size()); + memcpy(d->sendbuf.data() + oldsize, a.data(), a.size()); + + trySend(); +} + +QByteArray IBBConnection::read(int) +{ + // TODO: obey argument + QByteArray a = d->recvbuf.copy(); + d->recvbuf.resize(0); + return a; +} + +int IBBConnection::bytesAvailable() const +{ + return d->recvbuf.size(); +} + +int IBBConnection::bytesToWrite() const +{ + return d->sendbuf.size(); +} + +void IBBConnection::waitForAccept(const Jid &peer, const QString &sid, const QDomElement &comment, const QString &iq_id) +{ + close(); + reset(true); + + d->state = WaitingForAccept; + d->peer = peer; + d->sid = sid; + d->comment = comment; + d->iq_id = iq_id; +} + +void IBBConnection::takeIncomingData(const QByteArray &a, bool close) +{ + // append to the end of our recv buffer + int oldsize = d->recvbuf.size(); + d->recvbuf.resize(oldsize + a.size()); + memcpy(d->recvbuf.data() + oldsize, a.data(), a.size()); + + readyRead(); + + if(close) { + reset(); + connectionClosed(); + } +} + +void IBBConnection::ibb_finished() +{ + JT_IBB *j = d->j; + d->j = 0; + + if(j->success()) { + if(j->mode() == JT_IBB::ModeRequest) { + d->sid = j->streamid(); + + QString dstr; dstr.sprintf("IBBConnection[%d]: %s [%s] accepted.\n", d->id, d->peer.full().latin1(), d->sid.latin1()); + d->m->client()->debug(dstr); + + d->state = Active; + d->m->link(this); + connected(); + } + else { + bytesWritten(d->blockSize); + + if(d->closing) { + reset(); + delayedCloseFinished(); + } + + if(!d->sendbuf.isEmpty() || d->closePending) + QTimer::singleShot(IBB_PACKET_DELAY, this, SLOT(trySend())); + } + } + else { + if(j->mode() == JT_IBB::ModeRequest) { + QString dstr; dstr.sprintf("IBBConnection[%d]: %s refused.\n", d->id, d->peer.full().latin1()); + d->m->client()->debug(dstr); + + reset(true); + error(ErrRequest); + } + else { + reset(true); + error(ErrData); + } + } +} + +void IBBConnection::trySend() +{ + // if we already have an active task, then don't do anything + if(d->j) + return; + + QByteArray a; + if(!d->sendbuf.isEmpty()) { + // take a chunk + if(d->sendbuf.size() < IBB_PACKET_SIZE) + a.resize(d->sendbuf.size()); + else + a.resize(IBB_PACKET_SIZE); + memcpy(a.data(), d->sendbuf.data(), a.size()); + d->sendbuf.resize(d->sendbuf.size() - a.size()); + } + + bool doClose = false; + if(d->sendbuf.isEmpty() && d->closePending) + doClose = true; + + // null operation? + if(a.isEmpty() && !doClose) + return; + + printf("IBBConnection[%d]: sending [%d] bytes ", d->id, a.size()); + if(doClose) + printf("and closing.\n"); + else + printf("(%d bytes left)\n", d->sendbuf.size()); + + if(doClose) { + d->closePending = false; + d->closing = true; + } + + d->blockSize = a.size(); + d->j = new JT_IBB(d->m->client()->rootTask()); + connect(d->j, SIGNAL(finished()), SLOT(ibb_finished())); + d->j->sendData(d->peer, d->sid, a, doClose); + d->j->go(true); +} + + +//---------------------------------------------------------------------------- +// IBBManager +//---------------------------------------------------------------------------- +class IBBManager::Private +{ +public: + Private() {} + + Client *client; + IBBConnectionList activeConns; + IBBConnectionList incomingConns; + JT_IBB *ibb; +}; + +IBBManager::IBBManager(Client *parent) +:QObject(parent) +{ + d = new Private; + d->client = parent; + + d->ibb = new JT_IBB(d->client->rootTask(), true); + connect(d->ibb, SIGNAL(incomingRequest(const Jid &, const QString &, const QDomElement &)), SLOT(ibb_incomingRequest(const Jid &, const QString &, const QDomElement &))); + connect(d->ibb, SIGNAL(incomingData(const Jid &, const QString &, const QString &, const QByteArray &, bool)), SLOT(ibb_incomingData(const Jid &, const QString &, const QString &, const QByteArray &, bool))); +} + +IBBManager::~IBBManager() +{ + d->incomingConns.setAutoDelete(true); + d->incomingConns.clear(); + delete d->ibb; + delete d; +} + +Client *IBBManager::client() const +{ + return d->client; +} + +IBBConnection *IBBManager::takeIncoming() +{ + if(d->incomingConns.isEmpty()) + return 0; + + IBBConnection *c = d->incomingConns.getFirst(); + d->incomingConns.removeRef(c); + return c; +} + +void IBBManager::ibb_incomingRequest(const Jid &from, const QString &id, const QDomElement &comment) +{ + QString sid = genUniqueKey(); + + // create a "waiting" connection + IBBConnection *c = new IBBConnection(this); + c->waitForAccept(from, sid, comment, id); + d->incomingConns.append(c); + incomingReady(); +} + +void IBBManager::ibb_incomingData(const Jid &from, const QString &streamid, const QString &id, const QByteArray &data, bool close) +{ + IBBConnection *c = findConnection(streamid, from); + if(!c) { + d->ibb->respondError(from, id, 404, "No such stream"); + } + else { + d->ibb->respondAck(from, id); + c->takeIncomingData(data, close); + } +} + +QString IBBManager::genKey() const +{ + QString key = "ibb_"; + + for(int i = 0; i < 4; ++i) { + int word = rand() & 0xffff; + for(int n = 0; n < 4; ++n) { + QString s; + s.sprintf("%x", (word >> (n * 4)) & 0xf); + key.append(s); + } + } + + return key; +} + +QString IBBManager::genUniqueKey() const +{ + // get unused key + QString key; + while(1) { + key = genKey(); + + if(!findConnection(key)) + break; + } + + return key; +} + +void IBBManager::link(IBBConnection *c) +{ + d->activeConns.append(c); +} + +void IBBManager::unlink(IBBConnection *c) +{ + d->activeConns.removeRef(c); +} + +IBBConnection *IBBManager::findConnection(const QString &sid, const Jid &peer) const +{ + IBBConnectionListIt it(d->activeConns); + for(IBBConnection *c; (c = it.current()); ++it) { + if(c->streamid() == sid && (peer.isEmpty() || c->peer().compare(peer)) ) + return c; + } + return 0; +} + +void IBBManager::doAccept(IBBConnection *c, const QString &id) +{ + d->ibb->respondSuccess(c->peer(), id, c->streamid()); +} + +void IBBManager::doReject(IBBConnection *c, const QString &id, int code, const QString &str) +{ + d->ibb->respondError(c->peer(), id, code, str); +} + + +//---------------------------------------------------------------------------- +// JT_IBB +//---------------------------------------------------------------------------- +class JT_IBB::Private +{ +public: + Private() {} + + QDomElement iq; + int mode; + bool serve; + Jid to; + QString streamid; +}; + +JT_IBB::JT_IBB(Task *parent, bool serve) +:Task(parent) +{ + d = new Private; + d->serve = serve; +} + +JT_IBB::~JT_IBB() +{ + delete d; +} + +void JT_IBB::request(const Jid &to, const QDomElement &comment) +{ + d->mode = ModeRequest; + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/ibb"); + iq.appendChild(query); + query.appendChild(comment); + d->iq = iq; +} + +void JT_IBB::sendData(const Jid &to, const QString &streamid, const QByteArray &a, bool close) +{ + d->mode = ModeSendData; + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/ibb"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "streamid", streamid)); + if(!a.isEmpty()) + query.appendChild(textTag(doc(), "data", Base64::arrayToString(a))); + if(close) { + QDomElement c = doc()->createElement("close"); + query.appendChild(c); + } + d->iq = iq; +} + +void JT_IBB::respondSuccess(const Jid &to, const QString &id, const QString &streamid) +{ + QDomElement iq = createIQ(doc(), "result", to.full(), id); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/ibb"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "streamid", streamid)); + send(iq); +} + +void JT_IBB::respondError(const Jid &to, const QString &id, int code, const QString &str) +{ + QDomElement iq = createIQ(doc(), "error", to.full(), id); + QDomElement err = textTag(doc(), "error", str); + err.setAttribute("code", QString::number(code)); + iq.appendChild(err); + send(iq); +} + +void JT_IBB::respondAck(const Jid &to, const QString &id) +{ + QDomElement iq = createIQ(doc(), "result", to.full(), id); + send(iq); +} + +void JT_IBB::onGo() +{ + send(d->iq); +} + +bool JT_IBB::take(const QDomElement &e) +{ + if(d->serve) { + // must be an iq-set tag + if(e.tagName() != "iq" || e.attribute("type") != "set") + return false; + + if(queryNS(e) != "http://jabber.org/protocol/ibb") + return false; + + Jid from(e.attribute("from")); + QString id = e.attribute("id"); + QDomElement q = queryTag(e); + + bool found; + QDomElement s = findSubTag(q, "streamid", &found); + if(!found) { + QDomElement comment = findSubTag(q, "comment", &found); + incomingRequest(from, id, comment); + } + else { + QString sid = tagContent(s); + QByteArray a; + bool close = false; + s = findSubTag(q, "data", &found); + if(found) + a = Base64::stringToArray(tagContent(s)); + s = findSubTag(q, "close", &found); + if(found) + close = true; + + incomingData(from, sid, id, a, close); + } + + return true; + } + else { + Jid from(e.attribute("from")); + if(e.attribute("id") != id() || !d->to.compare(from)) + return false; + + if(e.attribute("type") == "result") { + QDomElement q = queryTag(e); + + // request + if(d->mode == ModeRequest) { + bool found; + QDomElement s = findSubTag(q, "streamid", &found); + if(found) + d->streamid = tagContent(s); + else + d->streamid = ""; + setSuccess(); + } + // sendData + else { + // thank you for the ack, kind sir + setSuccess(); + } + } + else { + setError(e); + } + + return true; + } +} + +QString JT_IBB::streamid() const +{ + return d->streamid; +} + +Jid JT_IBB::jid() const +{ + return d->to; +} + +int JT_IBB::mode() const +{ + return d->mode; +} + diff --git a/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.h b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.h new file mode 100644 index 00000000..73de4ac4 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.h @@ -0,0 +1,145 @@ +/* + * ibb.h - Inband bytestream + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef JABBER_IBB_H +#define JABBER_IBB_H + +#include +#include +#include +#include +#include"bytestream.h" +#include"im.h" + +namespace XMPP +{ + class Client; + class IBBManager; + + // this is an IBB connection. use it much like a qsocket + class IBBConnection : public ByteStream + { + Q_OBJECT + public: + enum { ErrRequest, ErrData }; + enum { Idle, Requesting, WaitingForAccept, Active }; + IBBConnection(IBBManager *); + ~IBBConnection(); + + void connectToJid(const Jid &peer, const QDomElement &comment); + void accept(); + void close(); + + int state() const; + Jid peer() const; + QString streamid() const; + QDomElement comment() const; + + bool isOpen() const; + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + + signals: + void connected(); + + private slots: + void ibb_finished(); + void trySend(); + + private: + class Private; + Private *d; + + void reset(bool clear=false); + + friend class IBBManager; + void waitForAccept(const Jid &peer, const QString &sid, const QDomElement &comment, const QString &iq_id); + void takeIncomingData(const QByteArray &, bool close); + }; + + typedef QPtrList IBBConnectionList; + typedef QPtrListIterator IBBConnectionListIt; + class IBBManager : public QObject + { + Q_OBJECT + public: + IBBManager(Client *); + ~IBBManager(); + + Client *client() const; + + IBBConnection *takeIncoming(); + + signals: + void incomingReady(); + + private slots: + void ibb_incomingRequest(const Jid &from, const QString &id, const QDomElement &); + void ibb_incomingData(const Jid &from, const QString &streamid, const QString &id, const QByteArray &data, bool close); + + private: + class Private; + Private *d; + + QString genKey() const; + + friend class IBBConnection; + IBBConnection *findConnection(const QString &sid, const Jid &peer="") const; + QString genUniqueKey() const; + void link(IBBConnection *); + void unlink(IBBConnection *); + void doAccept(IBBConnection *c, const QString &id); + void doReject(IBBConnection *c, const QString &id, int, const QString &); + }; + + class JT_IBB : public Task + { + Q_OBJECT + public: + enum { ModeRequest, ModeSendData }; + JT_IBB(Task *, bool serve=false); + ~JT_IBB(); + + void request(const Jid &, const QDomElement &comment); + void sendData(const Jid &, const QString &streamid, const QByteArray &data, bool close); + void respondSuccess(const Jid &, const QString &id, const QString &streamid); + void respondError(const Jid &, const QString &id, int code, const QString &str); + void respondAck(const Jid &to, const QString &id); + + void onGo(); + bool take(const QDomElement &); + + QString streamid() const; + Jid jid() const; + int mode() const; + + signals: + void incomingRequest(const Jid &from, const QString &id, const QDomElement &); + void incomingData(const Jid &from, const QString &streamid, const QString &id, const QByteArray &data, bool close); + + private: + class Private; + Private *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.cpp b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.cpp new file mode 100644 index 00000000..eb140880 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.cpp @@ -0,0 +1,318 @@ +/* + * jidlink.cpp - establish a link between Jabber IDs + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp_jidlink.h" + +#include +#include +#include"im.h" +#include"s5b.h" +#include"xmpp_ibb.h" + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// JidLink +//---------------------------------------------------------------------------- +class JidLink::Private +{ +public: + Client *client; + ByteStream *bs; + int type; + int state; + Jid peer; +}; + +JidLink::JidLink(Client *client) +:QObject(client->jidLinkManager()) +{ + d = new Private; + d->client = client; + d->bs = 0; + + reset(); +} + +JidLink::~JidLink() +{ + reset(true); + + delete d; +} + +void JidLink::reset(bool clear) +{ + d->type = None; + d->state = Idle; + + if(d->bs) { + unlink(); + d->bs->close(); + if(clear) { + delete d->bs; + d->bs = 0; + } + } +} + +void JidLink::connectToJid(const Jid &jid, int type, const QDomElement &comment) +{ + reset(true); + if(type == DTCP) + d->bs = d->client->s5bManager()->createConnection(); + else if(type == IBB) + d->bs = new IBBConnection(d->client->ibbManager()); + else + return; + + d->type = type; + d->peer = jid; + d->state = Connecting; + + link(); + + if(type == DTCP) { + S5BConnection *c = (S5BConnection *)d->bs; + status(StatDTCPRequesting); + c->connectToJid(jid, d->client->s5bManager()->genUniqueSID(jid)); + } + else { + IBBConnection *c = (IBBConnection *)d->bs; + status(StatIBBRequesting); + c->connectToJid(jid, comment); + } +} + +void JidLink::link() +{ + if(d->type == DTCP) { + S5BConnection *c = (S5BConnection *)d->bs; + connect(c, SIGNAL(connected()), SLOT(dtcp_connected())); + connect(c, SIGNAL(accepted()), SLOT(dtcp_accepted())); + } + else { + IBBConnection *c = (IBBConnection *)d->bs; + connect(c, SIGNAL(connected()), SLOT(ibb_connected())); + } + + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(error(int)), SLOT(bs_error(int))); + connect(d->bs, SIGNAL(bytesWritten(int)), SLOT(bs_bytesWritten(int))); + connect(d->bs, SIGNAL(readyRead()), SLOT(bs_readyRead())); +} + +void JidLink::unlink() +{ + d->bs->disconnect(this); +} + +void JidLink::accept() +{ + if(d->state != WaitingForAccept) + return; + + QTimer::singleShot(0, this, SLOT(doRealAccept())); +} + +void JidLink::doRealAccept() +{ + if(d->type == DTCP) { + ((S5BConnection *)d->bs)->accept(); + d->state = Connecting; + dtcp_accepted(); + } + else { + ((IBBConnection *)d->bs)->accept(); + d->state = Active; + connected(); + } +} + +bool JidLink::setStream(ByteStream *bs) +{ + reset(true); + int type = None; + if(bs->inherits("XMPP::S5BConnection")) + type = DTCP; + else if(bs->inherits("XMPP::IBBConnection")) + type = IBB; + + if(type == None) + return false; + + d->type = type; + d->bs = bs; + d->state = WaitingForAccept; + + link(); + + if(d->type == DTCP) + d->peer = ((S5BConnection *)d->bs)->peer(); + else + d->peer = ((IBBConnection *)d->bs)->peer(); + + return true; +} + +int JidLink::type() const +{ + return d->type; +} + +Jid JidLink::peer() const +{ + return d->peer; +} + +int JidLink::state() const +{ + return d->state; +} + +bool JidLink::isOpen() const +{ + if(d->state == Active) + return true; + else + return false; +} + +void JidLink::close() +{ + if(d->state == Idle) + return; + reset(); +} + +void JidLink::write(const QByteArray &a) +{ + if(d->state == Active) + d->bs->write(a); +} + +QByteArray JidLink::read(int bytes) +{ + if(d->bs) + return d->bs->read(bytes); + else + return QByteArray(); +} + +int JidLink::bytesAvailable() const +{ + if(d->bs) + return d->bs->bytesAvailable(); + else + return 0; +} + +int JidLink::bytesToWrite() const +{ + if(d->state == Active) + return d->bs->bytesToWrite(); + else + return 0; +} + +void JidLink::dtcp_accepted() +{ + status(StatDTCPAccepted); +} + +void JidLink::dtcp_connected() +{ + d->state = Active; + status(StatDTCPConnected); + connected(); +} + +void JidLink::ibb_connected() +{ + d->state = Active; + status(StatIBBConnected); + connected(); +} + +void JidLink::bs_connectionClosed() +{ + reset(); + connectionClosed(); +} + +void JidLink::bs_error(int) +{ + reset(); + error(ErrConnect); +} + +void JidLink::bs_readyRead() +{ + readyRead(); +} + +void JidLink::bs_bytesWritten(int x) +{ + bytesWritten(x); +} + + +//---------------------------------------------------------------------------- +// JidLinkManager +//---------------------------------------------------------------------------- +class JidLinkManager::Private +{ +public: + Private() {} + + Client *client; + QPtrList incomingList; +}; + +JidLinkManager::JidLinkManager(Client *par) +:QObject(par) +{ + d = new Private; + d->client = par; +} + +JidLinkManager::~JidLinkManager() +{ + d->incomingList.setAutoDelete(true); + d->incomingList.clear(); + delete d; +} + +JidLink *JidLinkManager::takeIncoming() +{ + if(d->incomingList.isEmpty()) + return 0; + + JidLink *j = d->incomingList.getFirst(); + d->incomingList.removeRef(j); + return j; +} + +void JidLinkManager::insertStream(ByteStream *bs) +{ + JidLink *j = new JidLink(d->client); + if(j->setStream(bs)) + d->incomingList.append(j); +} diff --git a/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.h b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.h new file mode 100644 index 00000000..955fce50 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.h @@ -0,0 +1,114 @@ +/* + * jidlink.h - establish a link between Jabber IDs + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + NOTE: this is not to be confused with JEP-0041 +*/ + +#ifndef JABBER_JIDLINK_H +#define JABBER_JIDLINK_H + +#include +#include +#include"xmpp.h" + +class ByteStream; + +namespace XMPP +{ + class Client; + + class JidLink : public QObject + { + Q_OBJECT + public: + enum { None, DTCP, IBB }; + enum { Idle, Connecting, WaitingForAccept, Active }; + enum { ErrConnect, ErrStream }; + enum { StatDTCPRequesting, StatDTCPAccepted, StatDTCPConnected, StatIBBRequesting, StatIBBConnected }; + JidLink(Client *client); + ~JidLink(); + + void connectToJid(const Jid &jid, int type, const QDomElement &comment); + void accept(); + + int type() const; + Jid peer() const; + int state() const; + + bool isOpen() const; + void close(); + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + + signals: + void connected(); + void connectionClosed(); + void readyRead(); + void bytesWritten(int); + void error(int); + void status(int); + + private slots: + void dtcp_connected(); + void dtcp_accepted(); + void ibb_connected(); + + void bs_connectionClosed(); + void bs_error(int); + void bs_readyRead(); + void bs_bytesWritten(int); + + void doRealAccept(); + + private: + class Private; + Private *d; + + void reset(bool clear=false); + + void link(); + void unlink(); + + friend class JidLinkManager; + bool setStream(ByteStream *); + }; + + // the job of JidLinkManager is to keep track of streams and properly shut them down + class JidLinkManager : public QObject + { + Q_OBJECT + public: + JidLinkManager(Client *); + ~JidLinkManager(); + + JidLink *takeIncoming(); + + void insertStream(ByteStream *); + + private: + class Private; + Private *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/Makefile.am b/kopete/protocols/jabber/libiris/iris/xmpp-core/Makefile.am new file mode 100644 index 00000000..f35b1c68 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/Makefile.am @@ -0,0 +1,30 @@ +# The only Q_OBJECT lines are in securestream.{h,cpp} and we deal with them below. +# Give metasources a file with no Q_OBJECT line to stop unsermake assuming we want METASOURCES = AUTO +METASOURCES = ignore_this_warning.moc + +noinst_LTLIBRARIES = libiris_xmpp_core.la +AM_CPPFLAGS = $(IDN_CFLAGS) +INCLUDES = -I$(srcdir)/../include -I$(srcdir)/../xmpp-core -I$(srcdir)/../xmpp-im -I$(srcdir)/../../cutestuff/util -I$(srcdir)/../../cutestuff/network -I$(srcdir)/../../qca/src $(all_includes) + +libiris_xmpp_core_la_CPPFLAGS = $(IDN_CFLAGS) +libiris_xmpp_core_la_LDFLAGS = $(IDN_LIBS) +libiris_xmpp_core_la_SOURCES = \ + connector.cpp \ + jid.cpp \ + securestream.cpp \ + tlshandler.cpp \ + hash.cpp \ + protocol.cpp \ + stream.cpp \ + xmlprotocol.cpp \ + parser.cpp \ + simplesasl.cpp + +libiris_xmpp_core_la_COMPILE_FIRST = securestream.moc + +CLEANFILES = securestream.moc +securestream.moc: $(srcdir)/securestream.cpp $(srcdir)/securestream.h + ${MOC} $(srcdir)/securestream.h > $@ + ${MOC} $(srcdir)/securestream.cpp >> $@ + +KDE_OPTIONS = nofinal diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/connector.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/connector.cpp new file mode 100644 index 00000000..8ebc3ee8 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/connector.cpp @@ -0,0 +1,719 @@ +/* + * connector.cpp - establish a connection to an XMPP server + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + TODO: + + - Test and analyze all possible branches + + XMPP::AdvancedConnector is "good for now." The only real issue is that + most of what it provides is just to work around the old Jabber/XMPP 0.9 + connection behavior. When XMPP 1.0 has taken over the world, we can + greatly simplify this class. - Sep 3rd, 2003. +*/ + +#include"xmpp.h" + +#include +#include +#include"safedelete.h" + +#ifdef NO_NDNS +#include +#else +#include"ndns.h" +#endif + +#include"srvresolver.h" +#include"bsocket.h" +#include"httpconnect.h" +#include"httppoll.h" +#include"socks.h" +#include"hash.h" + +//#define XMPP_DEBUG + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// Connector +//---------------------------------------------------------------------------- +Connector::Connector(QObject *parent) +:QObject(parent) +{ + setUseSSL(false); + setPeerAddressNone(); +} + +Connector::~Connector() +{ +} + +bool Connector::useSSL() const +{ + return ssl; +} + +bool Connector::havePeerAddress() const +{ + return haveaddr; +} + +QHostAddress Connector::peerAddress() const +{ + return addr; +} + +Q_UINT16 Connector::peerPort() const +{ + return port; +} + +void Connector::setUseSSL(bool b) +{ + ssl = b; +} + +void Connector::setPeerAddressNone() +{ + haveaddr = false; + addr = QHostAddress(); + port = 0; +} + +void Connector::setPeerAddress(const QHostAddress &_addr, Q_UINT16 _port) +{ + haveaddr = true; + addr = _addr; + port = _port; +} + + +//---------------------------------------------------------------------------- +// AdvancedConnector::Proxy +//---------------------------------------------------------------------------- +AdvancedConnector::Proxy::Proxy() +{ + t = None; + v_poll = 30; +} + +AdvancedConnector::Proxy::~Proxy() +{ +} + +int AdvancedConnector::Proxy::type() const +{ + return t; +} + +QString AdvancedConnector::Proxy::host() const +{ + return v_host; +} + +Q_UINT16 AdvancedConnector::Proxy::port() const +{ + return v_port; +} + +QString AdvancedConnector::Proxy::url() const +{ + return v_url; +} + +QString AdvancedConnector::Proxy::user() const +{ + return v_user; +} + +QString AdvancedConnector::Proxy::pass() const +{ + return v_pass; +} + +int AdvancedConnector::Proxy::pollInterval() const +{ + return v_poll; +} + +void AdvancedConnector::Proxy::setHttpConnect(const QString &host, Q_UINT16 port) +{ + t = HttpConnect; + v_host = host; + v_port = port; +} + +void AdvancedConnector::Proxy::setHttpPoll(const QString &host, Q_UINT16 port, const QString &url) +{ + t = HttpPoll; + v_host = host; + v_port = port; + v_url = url; +} + +void AdvancedConnector::Proxy::setSocks(const QString &host, Q_UINT16 port) +{ + t = Socks; + v_host = host; + v_port = port; +} + +void AdvancedConnector::Proxy::setUserPass(const QString &user, const QString &pass) +{ + v_user = user; + v_pass = pass; +} + +void AdvancedConnector::Proxy::setPollInterval(int secs) +{ + v_poll = secs; +} + + +//---------------------------------------------------------------------------- +// AdvancedConnector +//---------------------------------------------------------------------------- +enum { Idle, Connecting, Connected }; +class AdvancedConnector::Private +{ +public: + int mode; + ByteStream *bs; +#ifdef NO_NDNS + QDns *qdns; +#else + NDns dns; +#endif + SrvResolver srv; + + QString server; + QString opt_host; + int opt_port; + bool opt_probe, opt_ssl; + Proxy proxy; + + QString host; + int port; + QValueList servers; + int errorCode; + + bool multi, using_srv; + bool will_be_ssl; + int probe_mode; + + bool aaaa; + SafeDelete sd; +}; + +AdvancedConnector::AdvancedConnector(QObject *parent) +:Connector(parent) +{ + d = new Private; + d->bs = 0; +#ifdef NO_NDNS + d->qdns = 0; +#else + connect(&d->dns, SIGNAL(resultsReady()), SLOT(dns_done())); +#endif + connect(&d->srv, SIGNAL(resultsReady()), SLOT(srv_done())); + d->opt_probe = false; + d->opt_ssl = false; + cleanup(); + d->errorCode = 0; +} + +AdvancedConnector::~AdvancedConnector() +{ + cleanup(); + delete d; +} + +void AdvancedConnector::cleanup() +{ + d->mode = Idle; + + // stop any dns +#ifdef NO_NDNS + if(d->qdns) { + d->qdns->disconnect(this); + d->qdns->deleteLater(); + //d->sd.deleteLater(d->qdns); + d->qdns = 0; + } +#else + if(d->dns.isBusy()) + d->dns.stop(); +#endif + if(d->srv.isBusy()) + d->srv.stop(); + + // destroy the bytestream, if there is one + delete d->bs; + d->bs = 0; + + d->multi = false; + d->using_srv = false; + d->will_be_ssl = false; + d->probe_mode = -1; + + setUseSSL(false); + setPeerAddressNone(); +} + +void AdvancedConnector::setProxy(const Proxy &proxy) +{ + if(d->mode != Idle) + return; + d->proxy = proxy; +} + +void AdvancedConnector::setOptHostPort(const QString &host, Q_UINT16 _port) +{ + if(d->mode != Idle) + return; + d->opt_host = host; + d->opt_port = _port; +} + +void AdvancedConnector::setOptProbe(bool b) +{ + if(d->mode != Idle) + return; + d->opt_probe = b; +} + +void AdvancedConnector::setOptSSL(bool b) +{ + if(d->mode != Idle) + return; + d->opt_ssl = b; +} + +void AdvancedConnector::connectToServer(const QString &server) +{ + if(d->mode != Idle) + return; + if(server.isEmpty()) + return; + + d->errorCode = 0; + d->server = server; + d->mode = Connecting; + d->aaaa = true; + + if(d->proxy.type() == Proxy::HttpPoll) { + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + HttpPoll *s = new HttpPoll; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(syncStarted()), SLOT(http_syncStarted())); + connect(s, SIGNAL(syncFinished()), SLOT(http_syncFinished())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + if(!d->proxy.user().isEmpty()) + s->setAuth(d->proxy.user(), d->proxy.pass()); + s->setPollInterval(d->proxy.pollInterval()); + + if(d->proxy.host().isEmpty()) + s->connectToUrl(d->proxy.url()); + else + s->connectToHost(d->proxy.host(), d->proxy.port(), d->proxy.url()); + } + else { + if(!d->opt_host.isEmpty()) { + d->host = d->opt_host; + d->port = d->opt_port; + do_resolve(); + } + else { + d->multi = true; + + QGuardedPtr self = this; + srvLookup(d->server); + if(!self) + return; + + d->srv.resolveSrvOnly(d->server, "xmpp-client", "tcp"); + } + } +} + +void AdvancedConnector::changePollInterval(int secs) +{ + if(d->bs && (d->bs->inherits("XMPP::HttpPoll") || d->bs->inherits("HttpPoll"))) { + HttpPoll *s = static_cast(d->bs); + s->setPollInterval(secs); + } +} + +ByteStream *AdvancedConnector::stream() const +{ + if(d->mode == Connected) + return d->bs; + else + return 0; +} + +void AdvancedConnector::done() +{ + cleanup(); +} + +int AdvancedConnector::errorCode() const +{ + return d->errorCode; +} + +void AdvancedConnector::do_resolve() +{ +#ifdef NO_NDNS + printf("resolving (aaaa=%d)\n", d->aaaa); + d->qdns = new QDns; + connect(d->qdns, SIGNAL(resultsReady()), SLOT(dns_done())); + if(d->aaaa) + d->qdns->setRecordType(QDns::Aaaa); // IPv6 + else + d->qdns->setRecordType(QDns::A); // IPv4 + d->qdns->setLabel(d->host); +#else + d->dns.resolve(d->host); +#endif +} + +void AdvancedConnector::dns_done() +{ + bool failed = false; + QHostAddress addr; + +#ifdef NO_NDNS + //if(!d->qdns) + // return; + + // apparently we sometimes get this signal even though the results aren' t ready + //if(d->qdns->isWorking()) + // return; + + //SafeDeleteLock s(&d->sd); + + // grab the address list and destroy the qdns object + QValueList list = d->qdns->addresses(); + d->qdns->disconnect(this); + d->qdns->deleteLater(); + //d->sd.deleteLater(d->qdns); + d->qdns = 0; + + if(list.isEmpty()) { + if(d->aaaa) { + d->aaaa = false; + do_resolve(); + return; + } + //do_resolve(); + //return; + failed = true; + } + else + addr = list.first(); +#else + if(d->dns.result() == 0) + failed = true; + else + addr = QHostAddress(d->dns.result()); +#endif + + if(failed) { +#ifdef XMPP_DEBUG + printf("dns1\n"); +#endif + // using proxy? then try the unresolved host through the proxy + if(d->proxy.type() != Proxy::None) { +#ifdef XMPP_DEBUG + printf("dns1.1\n"); +#endif + do_connect(); + } + else if(d->using_srv) { +#ifdef XMPP_DEBUG + printf("dns1.2\n"); +#endif + if(d->servers.isEmpty()) { +#ifdef XMPP_DEBUG + printf("dns1.2.1\n"); +#endif + cleanup(); + d->errorCode = ErrConnectionRefused; + error(); + } + else { +#ifdef XMPP_DEBUG + printf("dns1.2.2\n"); +#endif + tryNextSrv(); + return; + } + } + else { +#ifdef XMPP_DEBUG + printf("dns1.3\n"); +#endif + cleanup(); + d->errorCode = ErrHostNotFound; + error(); + } + } + else { +#ifdef XMPP_DEBUG + printf("dns2\n"); +#endif + d->host = addr.toString(); + do_connect(); + } +} + +void AdvancedConnector::do_connect() +{ +#ifdef XMPP_DEBUG + printf("trying %s:%d\n", d->host.latin1(), d->port); +#endif + int t = d->proxy.type(); + if(t == Proxy::None) { +#ifdef XMPP_DEBUG + printf("do_connect1\n"); +#endif + BSocket *s = new BSocket; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + s->connectToHost(d->host, d->port); + } + else if(t == Proxy::HttpConnect) { +#ifdef XMPP_DEBUG + printf("do_connect2\n"); +#endif + HttpConnect *s = new HttpConnect; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + if(!d->proxy.user().isEmpty()) + s->setAuth(d->proxy.user(), d->proxy.pass()); + s->connectToHost(d->proxy.host(), d->proxy.port(), d->host, d->port); + } + else if(t == Proxy::Socks) { +#ifdef XMPP_DEBUG + printf("do_connect3\n"); +#endif + SocksClient *s = new SocksClient; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + if(!d->proxy.user().isEmpty()) + s->setAuth(d->proxy.user(), d->proxy.pass()); + s->connectToHost(d->proxy.host(), d->proxy.port(), d->host, d->port); + } +} + +void AdvancedConnector::tryNextSrv() +{ +#ifdef XMPP_DEBUG + printf("trying next srv\n"); +#endif + d->host = d->servers.first().name; + d->port = d->servers.first().port; + d->servers.remove(d->servers.begin()); + do_resolve(); +} + +void AdvancedConnector::srv_done() +{ + QGuardedPtr self = this; +#ifdef XMPP_DEBUG + printf("srv_done1\n"); +#endif + d->servers = d->srv.servers(); + if(d->servers.isEmpty()) { + srvResult(false); + if(!self) + return; + +#ifdef XMPP_DEBUG + printf("srv_done1.1\n"); +#endif + // fall back to A record + d->using_srv = false; + d->host = d->server; + if(d->opt_probe) { +#ifdef XMPP_DEBUG + printf("srv_done1.1.1\n"); +#endif + d->probe_mode = 0; + d->port = 5223; + d->will_be_ssl = true; + } + else { +#ifdef XMPP_DEBUG + printf("srv_done1.1.2\n"); +#endif + d->probe_mode = 1; + d->port = 5222; + } + do_resolve(); + return; + } + + srvResult(true); + if(!self) + return; + + d->using_srv = true; + tryNextSrv(); +} + +void AdvancedConnector::bs_connected() +{ + if(d->proxy.type() == Proxy::None) { + QHostAddress h = (static_cast(d->bs))->peerAddress(); + int p = (static_cast(d->bs))->peerPort(); + setPeerAddress(h, p); + } + + // only allow ssl override if proxy==poll or host:port + if((d->proxy.type() == Proxy::HttpPoll || !d->opt_host.isEmpty()) && d->opt_ssl) + setUseSSL(true); + else if(d->will_be_ssl) + setUseSSL(true); + + d->mode = Connected; + connected(); +} + +void AdvancedConnector::bs_error(int x) +{ + if(d->mode == Connected) { + d->errorCode = ErrStream; + error(); + return; + } + + bool proxyError = false; + int err = ErrConnectionRefused; + int t = d->proxy.type(); + +#ifdef XMPP_DEBUG + printf("bse1\n"); +#endif + + // figure out the error + if(t == Proxy::None) { + if(x == BSocket::ErrHostNotFound) + err = ErrHostNotFound; + else + err = ErrConnectionRefused; + } + else if(t == Proxy::HttpConnect) { + if(x == HttpConnect::ErrConnectionRefused) + err = ErrConnectionRefused; + else if(x == HttpConnect::ErrHostNotFound) + err = ErrHostNotFound; + else { + proxyError = true; + if(x == HttpConnect::ErrProxyAuth) + err = ErrProxyAuth; + else if(x == HttpConnect::ErrProxyNeg) + err = ErrProxyNeg; + else + err = ErrProxyConnect; + } + } + else if(t == Proxy::HttpPoll) { + if(x == HttpPoll::ErrConnectionRefused) + err = ErrConnectionRefused; + else if(x == HttpPoll::ErrHostNotFound) + err = ErrHostNotFound; + else { + proxyError = true; + if(x == HttpPoll::ErrProxyAuth) + err = ErrProxyAuth; + else if(x == HttpPoll::ErrProxyNeg) + err = ErrProxyNeg; + else + err = ErrProxyConnect; + } + } + else if(t == Proxy::Socks) { + if(x == SocksClient::ErrConnectionRefused) + err = ErrConnectionRefused; + else if(x == SocksClient::ErrHostNotFound) + err = ErrHostNotFound; + else { + proxyError = true; + if(x == SocksClient::ErrProxyAuth) + err = ErrProxyAuth; + else if(x == SocksClient::ErrProxyNeg) + err = ErrProxyNeg; + else + err = ErrProxyConnect; + } + } + + // no-multi or proxy error means we quit + if(!d->multi || proxyError) { + cleanup(); + d->errorCode = err; + error(); + return; + } + + if(d->using_srv && !d->servers.isEmpty()) { +#ifdef XMPP_DEBUG + printf("bse1.1\n"); +#endif + tryNextSrv(); + } + else if(!d->using_srv && d->opt_probe && d->probe_mode == 0) { +#ifdef XMPP_DEBUG + printf("bse1.2\n"); +#endif + d->probe_mode = 1; + d->port = 5222; + d->will_be_ssl = false; + do_connect(); + } + else { +#ifdef XMPP_DEBUG + printf("bse1.3\n"); +#endif + cleanup(); + d->errorCode = ErrConnectionRefused; + error(); + } +} + +void AdvancedConnector::http_syncStarted() +{ + httpSyncStarted(); +} + +void AdvancedConnector::http_syncFinished() +{ + httpSyncFinished(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.cpp new file mode 100644 index 00000000..4d7f9e41 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.cpp @@ -0,0 +1,670 @@ +/* + * hash.cpp - hashing functions for SHA1 and MD5 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"hash.h" + +namespace XMPP +{ + +static bool bigEndian; +static bool haveEndian = false; + +static void ensureEndian() +{ + if(!haveEndian) { + haveEndian = true; + int wordSize; + qSysInfo(&wordSize, &bigEndian); + } +} + +//---------------------------------------------------------------------------- +// MD5 +//---------------------------------------------------------------------------- + +/* NOTE: the following code was modified to not need BYTE_ORDER -- Justin */ + +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id$ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef Q_UINT8 md5_byte_t; /* 8-bit byte */ +typedef Q_UINT32 md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; + + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; + + { + if(bigEndian) + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + + X = xbuf; /* (dynamic only) */ + + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } + else /* dynamic big-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} + + +//---------------------------------------------------------------------------- +// SHA1 - from a public domain implementation by Steve Reid (steve@edmweb.com) +//---------------------------------------------------------------------------- + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15]^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +struct SHA1_CONTEXT +{ + Q_UINT32 state[5]; + Q_UINT32 count[2]; + unsigned char buffer[64]; +}; + +typedef union { + unsigned char c[64]; + Q_UINT32 l[16]; +} CHAR64LONG16; + +class SHA1Context : public QCA_HashContext +{ +public: + SHA1_CONTEXT _context; + CHAR64LONG16* block; + + SHA1Context() + { + reset(); + } + + QCA_HashContext *clone() + { + return new SHA1Context(*this); + } + + void reset() + { + sha1_init(&_context); + } + + void update(const char *in, unsigned int len) + { + sha1_update(&_context, (unsigned char *)in, (unsigned int)len); + } + + void final(QByteArray *out) + { + QByteArray b(20); + sha1_final((unsigned char *)b.data(), &_context); + *out = b; + } + + unsigned long blk0(Q_UINT32 i) + { + if(bigEndian) + return block->l[i]; + else + return (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) | (rol(block->l[i],8)&0x00FF00FF)); + } + + // Hash a single 512-bit block. This is the core of the algorithm. + void transform(Q_UINT32 state[5], unsigned char buffer[64]) + { + Q_UINT32 a, b, c, d, e; + + block = (CHAR64LONG16*)buffer; + + // Copy context->state[] to working vars + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + // 4 rounds of 20 operations each. Loop unrolled. + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + // Add the working vars back into context.state[] + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + // Wipe variables + a = b = c = d = e = 0; + } + + // SHA1Init - Initialize new context + void sha1_init(SHA1_CONTEXT* context) + { + // SHA1 initialization constants + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; + } + + // Run your data through this + void sha1_update(SHA1_CONTEXT* context, unsigned char* data, Q_UINT32 len) + { + Q_UINT32 i, j; + + j = (context->count[0] >> 3) & 63; + if((context->count[0] += len << 3) < (len << 3)) + context->count[1]++; + + context->count[1] += (len >> 29); + + if((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); + } + + // Add padding and return the message digest + void sha1_final(unsigned char digest[20], SHA1_CONTEXT* context) + { + Q_UINT32 i, j; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); // Endian independent + } + sha1_update(context, (unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) { + sha1_update(context, (unsigned char *)"\0", 1); + } + sha1_update(context, finalcount, 8); // Should cause a transform() + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + + // Wipe variables + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); + } +}; + +class MD5Context : public QCA_HashContext +{ +public: + MD5Context() + { + reset(); + } + + QCA_HashContext *clone() + { + return new MD5Context(*this); + } + + void reset() + { + md5_init(&md5); + } + + void update(const char *in, unsigned int len) + { + md5_append(&md5, (const md5_byte_t *)in, len); + } + + void final(QByteArray *out) + { + QByteArray b(16); + md5_finish(&md5, (md5_byte_t *)b.data()); + *out = b; + } + + md5_state_t md5; +}; + +class HashProvider : public QCAProvider +{ +public: + HashProvider() {} + ~HashProvider() {} + + void init() + { + ensureEndian(); + } + + int qcaVersion() const + { + return QCA_PLUGIN_VERSION; + } + + int capabilities() const + { + return (QCA::CAP_SHA1 | QCA::CAP_MD5); + } + + void *context(int cap) + { + if(cap == QCA::CAP_SHA1) + return new SHA1Context; + if(cap == QCA::CAP_MD5) + return new MD5Context; + return 0; + } +}; + +QCAProvider *createProviderHash() +{ + return (new HashProvider); +} + +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.h new file mode 100644 index 00000000..a4d2eea8 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.h @@ -0,0 +1,31 @@ +/* + * hash.h - hashing functions for SHA1 and MD5 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef HASH_H +#define HASH_H + +#include"qcaprovider.h" + +namespace XMPP +{ + QCAProvider *createProviderHash(); +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/jid.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/jid.cpp new file mode 100644 index 00000000..29932513 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/jid.cpp @@ -0,0 +1,409 @@ +/* + * jid.cpp - class for verifying and manipulating Jabber IDs + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp.h" + +#include +#include + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// StringPrepCache +//---------------------------------------------------------------------------- +class StringPrepCache +{ +public: + static bool nameprep(const QString &in, int maxbytes, QString *out) + { + if(in.isEmpty()) + { + if(out) + *out = QString(); + return true; + } + + StringPrepCache *that = get_instance(); + + Result *r = that->nameprep_table.find(in); + if(r) + { + if(!r->norm) + return false; + if(out) + *out = *(r->norm); + return true; + } + + QCString cs = in.utf8(); + cs.resize(maxbytes); + if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_nameprep) != 0) + { + that->nameprep_table.insert(in, new Result); + return false; + } + + QString norm = QString::fromUtf8(cs); + that->nameprep_table.insert(in, new Result(norm)); + if(out) + *out = norm; + return true; + } + + static bool nodeprep(const QString &in, int maxbytes, QString *out) + { + if(in.isEmpty()) + { + if(out) + *out = QString(); + return true; + } + + StringPrepCache *that = get_instance(); + + Result *r = that->nodeprep_table.find(in); + if(r) + { + if(!r->norm) + return false; + if(out) + *out = *(r->norm); + return true; + } + + QCString cs = in.utf8(); + cs.resize(maxbytes); + if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_nodeprep) != 0) + { + that->nodeprep_table.insert(in, new Result); + return false; + } + + QString norm = QString::fromUtf8(cs); + that->nodeprep_table.insert(in, new Result(norm)); + if(out) + *out = norm; + return true; + } + + static bool resourceprep(const QString &in, int maxbytes, QString *out) + { + if(in.isEmpty()) + { + if(out) + *out = QString(); + return true; + } + + StringPrepCache *that = get_instance(); + + Result *r = that->resourceprep_table.find(in); + if(r) + { + if(!r->norm) + return false; + if(out) + *out = *(r->norm); + return true; + } + + QCString cs = in.utf8(); + cs.resize(maxbytes); + if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_resourceprep) != 0) + { + that->resourceprep_table.insert(in, new Result); + return false; + } + + QString norm = QString::fromUtf8(cs); + that->resourceprep_table.insert(in, new Result(norm)); + if(out) + *out = norm; + return true; + } + +private: + class Result + { + public: + QString *norm; + + Result() : norm(0) + { + } + + Result(const QString &s) : norm(new QString(s)) + { + } + + ~Result() + { + delete norm; + } + }; + + QDict nameprep_table; + QDict nodeprep_table; + QDict resourceprep_table; + + static StringPrepCache *instance; + + static StringPrepCache *get_instance() + { + if(!instance) + instance = new StringPrepCache; + return instance; + } + + StringPrepCache() + { + nameprep_table.setAutoDelete(true); + nodeprep_table.setAutoDelete(true); + resourceprep_table.setAutoDelete(true); + } +}; + +StringPrepCache *StringPrepCache::instance = 0; + +//---------------------------------------------------------------------------- +// Jid +//---------------------------------------------------------------------------- +Jid::Jid() +{ + valid = false; +} + +Jid::~Jid() +{ +} + +Jid::Jid(const QString &s) +{ + set(s); +} + +Jid::Jid(const char *s) +{ + set(QString(s)); +} + +Jid & Jid::operator=(const QString &s) +{ + set(s); + return *this; +} + +Jid & Jid::operator=(const char *s) +{ + set(QString(s)); + return *this; +} + +void Jid::reset() +{ + f = QString(); + b = QString(); + d = QString(); + n = QString(); + r = QString(); + valid = false; +} + +void Jid::update() +{ + // build 'bare' and 'full' jids + if(n.isEmpty()) + b = d; + else + b = n + '@' + d; + + b=b.lower(); // JID are not case sensitive + + if(r.isEmpty()) + f = b; + else + f = b + '/' + r; + if(f.isEmpty()) + valid = false; +} + +void Jid::set(const QString &s) +{ + QString rest, domain, node, resource; + QString norm_domain, norm_node, norm_resource; + int x = s.find('/'); + if(x != -1) { + rest = s.mid(0, x); + resource = s.mid(x+1); + } + else { + rest = s; + resource = QString(); + } + if(!validResource(resource, &norm_resource)) { + reset(); + return; + } + + x = rest.find('@'); + if(x != -1) { + node = rest.mid(0, x); + domain = rest.mid(x+1); + } + else { + node = QString(); + domain = rest; + } + if(!validDomain(domain, &norm_domain) || !validNode(node, &norm_node)) { + reset(); + return; + } + + valid = true; + d = norm_domain; + n = norm_node; + r = norm_resource; + update(); +} + +void Jid::set(const QString &domain, const QString &node, const QString &resource) +{ + QString norm_domain, norm_node, norm_resource; + if(!validDomain(domain, &norm_domain) || !validNode(node, &norm_node) || !validResource(resource, &norm_resource)) { + reset(); + return; + } + valid = true; + d = norm_domain; + n = norm_node; + r = norm_resource; + update(); +} + +void Jid::setDomain(const QString &s) +{ + if(!valid) + return; + QString norm; + if(!validDomain(s, &norm)) { + reset(); + return; + } + d = norm; + update(); +} + +void Jid::setNode(const QString &s) +{ + if(!valid) + return; + QString norm; + if(!validNode(s, &norm)) { + reset(); + return; + } + n = norm; + update(); +} + +void Jid::setResource(const QString &s) +{ + if(!valid) + return; + QString norm; + if(!validResource(s, &norm)) { + reset(); + return; + } + r = norm; + update(); +} + +Jid Jid::withNode(const QString &s) const +{ + Jid j = *this; + j.setNode(s); + return j; +} + +Jid Jid::withResource(const QString &s) const +{ + Jid j = *this; + j.setResource(s); + return j; +} + +bool Jid::isValid() const +{ + return valid; +} + +bool Jid::isEmpty() const +{ + return f.isEmpty(); +} + +bool Jid::compare(const Jid &a, bool compareRes) const +{ + // only compare valid jids + if(!valid || !a.valid) + return false; + + if(compareRes ? (f != a.f) : (b != a.b)) + return false; + + return true; +} + +bool Jid::validDomain(const QString &s, QString *norm) +{ + /*QCString cs = s.utf8(); + cs.resize(1024); + if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_nameprep) != 0) + return false; + if(norm) + *norm = QString::fromUtf8(cs); + return true;*/ + return StringPrepCache::nameprep(s, 1024, norm); +} + +bool Jid::validNode(const QString &s, QString *norm) +{ + /*QCString cs = s.utf8(); + cs.resize(1024); + if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_xmpp_nodeprep) != 0) + return false; + if(norm) + *norm = QString::fromUtf8(cs); + return true;*/ + return StringPrepCache::nodeprep(s, 1024, norm); +} + +bool Jid::validResource(const QString &s, QString *norm) +{ + /*QCString cs = s.utf8(); + cs.resize(1024); + if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_xmpp_resourceprep) != 0) + return false; + if(norm) + *norm = QString::fromUtf8(cs); + return true;*/ + return StringPrepCache::resourceprep(s, 1024, norm); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.cpp new file mode 100644 index 00000000..e1a64532 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.cpp @@ -0,0 +1,798 @@ +/* + * parser.cpp - parse an XMPP "document" + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + TODO: + + For XMPP::Parser to be "perfect", some things must be solved/changed in the + Qt library: + + - Fix weird QDomElement::haveAttributeNS() bug (patch submitted to + Trolltech on Aug 31st, 2003). + - Fix weird behavior in QXmlSimpleReader of reporting endElement() when + the '/' character of a self-closing tag is reached, instead of when + the final '>' is reached. + - Fix incremental parsing bugs in QXmlSimpleReader. At the moment, the + only bug I've found is related to attribute parsing, but there might + be more (search for '###' in $QTDIR/src/xml/qxml.cpp). + + We have workarounds for all of the above problems in the code below. + + - Deal with the processing instruction as an event type, so that we + can feed it back to the application properly. Right now it is completely + untrackable and is simply tacked into the first event's actualString. We + can't easily do this because QXmlSimpleReader eats an extra byte beyond + the processing instruction before reporting it. + + - Make QXmlInputSource capable of accepting data incrementally, to ensure + proper text encoding detection and processing over a network. This is + technically not a bug, as we have our own subclass below to do it, but + it would be nice if Qt had this already. +*/ + +#include"parser.h" + +#include +#include +#include + +using namespace XMPP; + +static bool qt_bug_check = false; +static bool qt_bug_have; + +//---------------------------------------------------------------------------- +// StreamInput +//---------------------------------------------------------------------------- +class StreamInput : public QXmlInputSource +{ +public: + StreamInput() + { + dec = 0; + reset(); + } + + ~StreamInput() + { + delete dec; + } + + void reset() + { + delete dec; + dec = 0; + in.resize(0); + out = ""; + at = 0; + paused = false; + mightChangeEncoding = true; + checkBad = true; + last = QChar(); + v_encoding = ""; + resetLastData(); + } + + void resetLastData() + { + last_string = ""; + } + + QString lastString() const + { + return last_string; + } + + void appendData(const QByteArray &a) + { + int oldsize = in.size(); + in.resize(oldsize + a.size()); + memcpy(in.data() + oldsize, a.data(), a.size()); + processBuf(); + } + + QChar lastRead() + { + return last; + } + + QChar next() + { + if(paused) + return EndOfData; + else + return readNext(); + } + + // NOTE: setting 'peek' to true allows the same char to be read again, + // however this still advances the internal byte processing. + QChar readNext(bool peek=false) + { + QChar c; + if(mightChangeEncoding) + c = EndOfData; + else { + if(out.isEmpty()) { + QString s; + if(!tryExtractPart(&s)) + c = EndOfData; + else { + out = s; + c = out[0]; + } + } + else + c = out[0]; + if(!peek) + out.remove(0, 1); + } + if(c == EndOfData) { +#ifdef XMPP_PARSER_DEBUG + printf("next() = EOD\n"); +#endif + } + else { +#ifdef XMPP_PARSER_DEBUG + printf("next() = [%c]\n", c.latin1()); +#endif + last = c; + } + + return c; + } + + QByteArray unprocessed() const + { + QByteArray a(in.size() - at); + memcpy(a.data(), in.data() + at, a.size()); + return a; + } + + void pause(bool b) + { + paused = b; + } + + bool isPaused() + { + return paused; + } + + QString encoding() const + { + return v_encoding; + } + +private: + QTextDecoder *dec; + QByteArray in; + QString out; + int at; + bool paused; + bool mightChangeEncoding; + QChar last; + QString v_encoding; + QString last_string; + bool checkBad; + + void processBuf() + { +#ifdef XMPP_PARSER_DEBUG + printf("processing. size=%d, at=%d\n", in.size(), at); +#endif + if(!dec) { + QTextCodec *codec = 0; + uchar *p = (uchar *)in.data() + at; + int size = in.size() - at; + + // do we have enough information to determine the encoding? + if(size == 0) + return; + bool utf16 = false; + if(p[0] == 0xfe || p[0] == 0xff) { + // probably going to be a UTF-16 byte order mark + if(size < 2) + return; + if((p[0] == 0xfe && p[1] == 0xff) || (p[0] == 0xff && p[1] == 0xfe)) { + // ok it is UTF-16 + utf16 = true; + } + } + if(utf16) + codec = QTextCodec::codecForMib(1000); // UTF-16 + else + codec = QTextCodec::codecForMib(106); // UTF-8 + + v_encoding = codec->name(); + dec = codec->makeDecoder(); + + // for utf16, put in the byte order mark + if(utf16) { + out += dec->toUnicode((const char *)p, 2); + at += 2; + } + } + + if(mightChangeEncoding) { + while(1) { + int n = out.find('<'); + if(n != -1) { + // we need a closing bracket + int n2 = out.find('>', n); + if(n2 != -1) { + ++n2; + QString h = out.mid(n, n2-n); + QString enc = processXmlHeader(h); + QTextCodec *codec = 0; + if(!enc.isEmpty()) + codec = QTextCodec::codecForName(enc.latin1()); + + // changing codecs + if(codec) { + v_encoding = codec->name(); + delete dec; + dec = codec->makeDecoder(); + } + mightChangeEncoding = false; + out.truncate(0); + at = 0; + resetLastData(); + break; + } + } + QString s; + if(!tryExtractPart(&s)) + break; + if(checkBad && checkForBadChars(s)) { + // go to the parser + mightChangeEncoding = false; + out.truncate(0); + at = 0; + resetLastData(); + break; + } + out += s; + } + } + } + + QString processXmlHeader(const QString &h) + { + if(h.left(5) != ""); + int startPos = h.find("encoding"); + if(startPos < endPos && startPos != -1) { + QString encoding; + do { + startPos++; + if(startPos > endPos) { + return ""; + } + } while(h[startPos] != '"' && h[startPos] != '\''); + startPos++; + while(h[startPos] != '"' && h[startPos] != '\'') { + encoding += h[startPos]; + startPos++; + if(startPos > endPos) { + return ""; + } + } + return encoding; + } + else + return ""; + } + + bool tryExtractPart(QString *s) + { + int size = in.size() - at; + if(size == 0) + return false; + uchar *p = (uchar *)in.data() + at; + QString nextChars; + while(1) { + nextChars = dec->toUnicode((const char *)p, 1); + ++p; + ++at; + if(!nextChars.isEmpty()) + break; + if(at == (int)in.size()) + return false; + } + last_string += nextChars; + *s = nextChars; + + // free processed data? + if(at >= 1024) { + char *p = in.data(); + int size = in.size() - at; + memmove(p, p + at, size); + in.resize(size); + at = 0; + } + + return true; + } + + bool checkForBadChars(const QString &s) + { + int len = s.find('<'); + if(len == -1) + len = s.length(); + else + checkBad = false; + for(int n = 0; n < len; ++n) { + if(!s.at(n).isSpace()) + return true; + } + return false; + } +}; + + +//---------------------------------------------------------------------------- +// ParserHandler +//---------------------------------------------------------------------------- +namespace XMPP +{ + class ParserHandler : public QXmlDefaultHandler + { + public: + ParserHandler(StreamInput *_in, QDomDocument *_doc) + { + in = _in; + doc = _doc; + needMore = false; + } + + ~ParserHandler() + { + eventList.setAutoDelete(true); + eventList.clear(); + } + + bool startDocument() + { + depth = 0; + return true; + } + + bool endDocument() + { + return true; + } + + bool startPrefixMapping(const QString &prefix, const QString &uri) + { + if(depth == 0) { + nsnames += prefix; + nsvalues += uri; + } + return true; + } + + bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts) + { + if(depth == 0) { + Parser::Event *e = new Parser::Event; + QXmlAttributes a; + for(int n = 0; n < atts.length(); ++n) { + QString uri = atts.uri(n); + QString ln = atts.localName(n); + if(a.index(uri, ln) == -1) + a.append(atts.qName(n), uri, ln, atts.value(n)); + } + e->setDocumentOpen(namespaceURI, localName, qName, a, nsnames, nsvalues); + nsnames.clear(); + nsvalues.clear(); + e->setActualString(in->lastString()); + + in->resetLastData(); + eventList.append(e); + in->pause(true); + } + else { + QDomElement e = doc->createElementNS(namespaceURI, qName); + for(int n = 0; n < atts.length(); ++n) { + QString uri = atts.uri(n); + QString ln = atts.localName(n); + bool have; + if(!uri.isEmpty()) { + have = e.hasAttributeNS(uri, ln); + if(qt_bug_have) + have = !have; + } + else + have = e.hasAttribute(ln); + if(!have) + e.setAttributeNS(uri, atts.qName(n), atts.value(n)); + } + + if(depth == 1) { + elem = e; + current = e; + } + else { + current.appendChild(e); + current = e; + } + } + ++depth; + return true; + } + + bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName) + { + --depth; + if(depth == 0) { + Parser::Event *e = new Parser::Event; + e->setDocumentClose(namespaceURI, localName, qName); + e->setActualString(in->lastString()); + in->resetLastData(); + eventList.append(e); + in->pause(true); + } + else { + // done with a depth 1 element? + if(depth == 1) { + Parser::Event *e = new Parser::Event; + e->setElement(elem); + e->setActualString(in->lastString()); + in->resetLastData(); + eventList.append(e); + in->pause(true); + + elem = QDomElement(); + current = QDomElement(); + } + else + current = current.parentNode().toElement(); + } + + if(in->lastRead() == '/') + checkNeedMore(); + + return true; + } + + bool characters(const QString &str) + { + if(depth >= 1) { + QString content = str; + if(content.isEmpty()) + return true; + + if(!current.isNull()) { + QDomText text = doc->createTextNode(content); + current.appendChild(text); + } + } + return true; + } + + /*bool processingInstruction(const QString &target, const QString &data) + { + printf("Processing: [%s], [%s]\n", target.latin1(), data.latin1()); + in->resetLastData(); + return true; + }*/ + + void checkNeedMore() + { + // Here we will work around QXmlSimpleReader strangeness and self-closing tags. + // The problem is that endElement() is called when the '/' is read, not when + // the final '>' is read. This is a potential problem when obtaining unprocessed + // bytes from StreamInput after this event, as the '>' character will end up + // in the unprocessed chunk. To work around this, we need to advance StreamInput's + // internal byte processing, but not the xml character data. This way, the '>' + // will get processed and will no longer be in the unprocessed return, but + // QXmlSimpleReader can still read it. To do this, we call StreamInput::readNext + // with 'peek' mode. + QChar c = in->readNext(true); // peek + if(c == QXmlInputSource::EndOfData) { + needMore = true; + } + else { + // We'll assume the next char is a '>'. If it isn't, then + // QXmlSimpleReader will deal with that problem on the next + // parse. We don't need to take any action here. + needMore = false; + + // there should have been a pending event + Parser::Event *e = eventList.getFirst(); + if(e) { + e->setActualString(e->actualString() + '>'); + in->resetLastData(); + } + } + } + + Parser::Event *takeEvent() + { + if(needMore) + return 0; + if(eventList.isEmpty()) + return 0; + + Parser::Event *e = eventList.getFirst(); + eventList.removeRef(e); + in->pause(false); + return e; + } + + StreamInput *in; + QDomDocument *doc; + int depth; + QStringList nsnames, nsvalues; + QDomElement elem, current; + QPtrList eventList; + bool needMore; + }; +} + + +//---------------------------------------------------------------------------- +// Event +//---------------------------------------------------------------------------- +class Parser::Event::Private +{ +public: + int type; + QString ns, ln, qn; + QXmlAttributes a; + QDomElement e; + QString str; + QStringList nsnames, nsvalues; +}; + +Parser::Event::Event() +{ + d = 0; +} + +Parser::Event::Event(const Event &from) +{ + d = 0; + *this = from; +} + +Parser::Event & Parser::Event::operator=(const Event &from) +{ + delete d; + d = 0; + if(from.d) + d = new Private(*from.d); + return *this; +} + +Parser::Event::~Event() +{ + delete d; +} + +bool Parser::Event::isNull() const +{ + return (d ? false: true); +} + +int Parser::Event::type() const +{ + if(isNull()) + return -1; + return d->type; +} + +QString Parser::Event::nsprefix(const QString &s) const +{ + QStringList::ConstIterator it = d->nsnames.begin(); + QStringList::ConstIterator it2 = d->nsvalues.begin(); + for(; it != d->nsnames.end(); ++it) { + if((*it) == s) + return (*it2); + ++it2; + } + return QString::null; +} + +QString Parser::Event::namespaceURI() const +{ + return d->ns; +} + +QString Parser::Event::localName() const +{ + return d->ln; +} + +QString Parser::Event::qName() const +{ + return d->qn; +} + +QXmlAttributes Parser::Event::atts() const +{ + return d->a; +} + +QString Parser::Event::actualString() const +{ + return d->str; +} + +QDomElement Parser::Event::element() const +{ + return d->e; +} + +void Parser::Event::setDocumentOpen(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts, const QStringList &nsnames, const QStringList &nsvalues) +{ + if(!d) + d = new Private; + d->type = DocumentOpen; + d->ns = namespaceURI; + d->ln = localName; + d->qn = qName; + d->a = atts; + d->nsnames = nsnames; + d->nsvalues = nsvalues; +} + +void Parser::Event::setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName) +{ + if(!d) + d = new Private; + d->type = DocumentClose; + d->ns = namespaceURI; + d->ln = localName; + d->qn = qName; +} + +void Parser::Event::setElement(const QDomElement &elem) +{ + if(!d) + d = new Private; + d->type = Element; + d->e = elem; +} + +void Parser::Event::setError() +{ + if(!d) + d = new Private; + d->type = Error; +} + +void Parser::Event::setActualString(const QString &str) +{ + d->str = str; +} + +//---------------------------------------------------------------------------- +// Parser +//---------------------------------------------------------------------------- +class Parser::Private +{ +public: + Private() + { + doc = 0; + in = 0; + handler = 0; + reader = 0; + reset(); + } + + ~Private() + { + reset(false); + } + + void reset(bool create=true) + { + delete reader; + delete handler; + delete in; + delete doc; + + if(create) { + doc = new QDomDocument; + in = new StreamInput; + handler = new ParserHandler(in, doc); + reader = new QXmlSimpleReader; + reader->setContentHandler(handler); + + // initialize the reader + in->pause(true); + reader->parse(in, true); + in->pause(false); + } + } + + QDomDocument *doc; + StreamInput *in; + ParserHandler *handler; + QXmlSimpleReader *reader; +}; + +Parser::Parser() +{ + d = new Private; + + // check for evil bug in Qt <= 3.2.1 + if(!qt_bug_check) { + qt_bug_check = true; + QDomElement e = d->doc->createElementNS("someuri", "somename"); + if(e.hasAttributeNS("someuri", "somename")) + qt_bug_have = true; + else + qt_bug_have = false; + } +} + +Parser::~Parser() +{ + delete d; +} + +void Parser::reset() +{ + d->reset(); +} + +void Parser::appendData(const QByteArray &a) +{ + d->in->appendData(a); + + // if handler was waiting for more, give it a kick + if(d->handler->needMore) + d->handler->checkNeedMore(); +} + +Parser::Event Parser::readNext() +{ + Event e; + if(d->handler->needMore) + return e; + Event *ep = d->handler->takeEvent(); + if(!ep) { + if(!d->reader->parseContinue()) { + e.setError(); + return e; + } + ep = d->handler->takeEvent(); + if(!ep) + return e; + } + e = *ep; + delete ep; + return e; +} + +QByteArray Parser::unprocessed() const +{ + return d->in->unprocessed(); +} + +QString Parser::encoding() const +{ + return d->in->encoding(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.h new file mode 100644 index 00000000..808b6c3d --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.h @@ -0,0 +1,86 @@ +/* + * parser.h - parse an XMPP "document" + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PARSER_H +#define PARSER_H + +#include +#include + +namespace XMPP +{ + class Parser + { + public: + Parser(); + ~Parser(); + + class Event + { + public: + enum Type { DocumentOpen, DocumentClose, Element, Error }; + Event(); + Event(const Event &); + Event & operator=(const Event &); + ~Event(); + + bool isNull() const; + int type() const; + + // for document open + QString nsprefix(const QString &s=QString::null) const; + + // for document open / close + QString namespaceURI() const; + QString localName() const; + QString qName() const; + QXmlAttributes atts() const; + + // for element + QDomElement element() const; + + // for any + QString actualString() const; + + // setup + void setDocumentOpen(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts, const QStringList &nsnames, const QStringList &nsvalues); + void setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName); + void setElement(const QDomElement &elem); + void setError(); + void setActualString(const QString &); + + private: + class Private; + Private *d; + }; + + void reset(); + void appendData(const QByteArray &a); + Event readNext(); + QByteArray unprocessed() const; + QString encoding() const; + + private: + class Private; + Private *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp new file mode 100644 index 00000000..dfd3253c --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp @@ -0,0 +1,1595 @@ +/* + * protocol.cpp - XMPP-Core protocol state machine + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// TODO: let the app know if tls is required +// require mutual auth for server out/in +// report ErrProtocol if server uses wrong NS +// use send() instead of writeElement() in CoreProtocol + +#include"protocol.h" + +#include +#include"base64.h" +#include"hash.h" + +#ifdef XMPP_TEST +#include"td.h" +#endif + +using namespace XMPP; + +// printArray +// +// This function prints out an array of bytes as latin characters, converting +// non-printable bytes into hex values as necessary. Useful for displaying +// QByteArrays for debugging purposes. +static QString printArray(const QByteArray &a) +{ + QString s; + for(uint n = 0; n < a.size(); ++n) { + unsigned char c = (unsigned char)a[(int)n]; + if(c < 32 || c >= 127) { + QString str; + str.sprintf("[%02x]", c); + s += str; + } + else + s += c; + } + return s; +} + +// firstChildElement +// +// Get an element's first child element +static QDomElement firstChildElement(const QDomElement &e) +{ + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + if(n.isElement()) + return n.toElement(); + } + return QDomElement(); +} + +//---------------------------------------------------------------------------- +// Version +//---------------------------------------------------------------------------- +Version::Version(int maj, int min) +{ + major = maj; + minor = min; +} + +//---------------------------------------------------------------------------- +// StreamFeatures +//---------------------------------------------------------------------------- +StreamFeatures::StreamFeatures() +{ + tls_supported = false; + sasl_supported = false; + bind_supported = false; + tls_required = false; +} + +//---------------------------------------------------------------------------- +// BasicProtocol +//---------------------------------------------------------------------------- +BasicProtocol::SASLCondEntry BasicProtocol::saslCondTable[] = +{ + { "aborted", Aborted }, + { "incorrect-encoding", IncorrectEncoding }, + { "invalid-authzid", InvalidAuthzid }, + { "invalid-mechanism", InvalidMech }, + { "mechanism-too-weak", MechTooWeak }, + { "not-authorized", NotAuthorized }, + { "temporary-auth-failure", TemporaryAuthFailure }, + { 0, 0 }, +}; + +BasicProtocol::StreamCondEntry BasicProtocol::streamCondTable[] = +{ + { "bad-format", BadFormat }, + { "bad-namespace-prefix", BadNamespacePrefix }, + { "conflict", Conflict }, + { "connection-timeout", ConnectionTimeout }, + { "host-gone", HostGone }, + { "host-unknown", HostUnknown }, + { "improper-addressing", ImproperAddressing }, + { "internal-server-error", InternalServerError }, + { "invalid-from", InvalidFrom }, + { "invalid-id", InvalidId }, + { "invalid-namespace", InvalidNamespace }, + { "invalid-xml", InvalidXml }, + { "not-authorized", StreamNotAuthorized }, + { "policy-violation", PolicyViolation }, + { "remote-connection-failed", RemoteConnectionFailed }, + { "resource-constraint", ResourceConstraint }, + { "restricted-xml", RestrictedXml }, + { "see-other-host", SeeOtherHost }, + { "system-shutdown", SystemShutdown }, + { "undefined-condition", UndefinedCondition }, + { "unsupported-encoding", UnsupportedEncoding }, + { "unsupported-stanza-type", UnsupportedStanzaType }, + { "unsupported-version", UnsupportedVersion }, + { "xml-not-well-formed", XmlNotWellFormed }, + { 0, 0 }, +}; + +BasicProtocol::BasicProtocol() +:XmlProtocol() +{ + init(); +} + +BasicProtocol::~BasicProtocol() +{ +} + +void BasicProtocol::init() +{ + errCond = -1; + sasl_authed = false; + doShutdown = false; + delayedError = false; + closeError = false; + ready = false; + stanzasPending = 0; + stanzasWritten = 0; +} + +void BasicProtocol::reset() +{ + XmlProtocol::reset(); + init(); + + to = QString(); + from = QString(); + id = QString(); + lang = QString(); + version = Version(1,0); + errText = QString(); + errAppSpec = QDomElement(); + otherHost = QString(); + spare.resize(0); + sasl_mech = QString(); + sasl_mechlist.clear(); + sasl_step.resize(0); + stanzaToRecv = QDomElement(); + sendList.clear(); +} + +void BasicProtocol::sendStanza(const QDomElement &e) +{ + SendItem i; + i.stanzaToSend = e; + sendList += i; +} + +void BasicProtocol::sendDirect(const QString &s) +{ + SendItem i; + i.stringToSend = s; + sendList += i; +} + +void BasicProtocol::sendWhitespace() +{ + SendItem i; + i.doWhitespace = true; + sendList += i; +} + +QDomElement BasicProtocol::recvStanza() +{ + QDomElement e = stanzaToRecv; + stanzaToRecv = QDomElement(); + return e; +} + +void BasicProtocol::shutdown() +{ + doShutdown = true; +} + +void BasicProtocol::shutdownWithError(int cond, const QString &str) +{ + otherHost = str; + delayErrorAndClose(cond); +} + +bool BasicProtocol::isReady() const +{ + return ready; +} + +void BasicProtocol::setReady(bool b) +{ + ready = b; +} + +QString BasicProtocol::saslMech() const +{ + return sasl_mech; +} + +QByteArray BasicProtocol::saslStep() const +{ + return sasl_step; +} + +void BasicProtocol::setSASLMechList(const QStringList &list) +{ + sasl_mechlist = list; +} + +void BasicProtocol::setSASLFirst(const QString &mech, const QByteArray &step) +{ + sasl_mech = mech; + sasl_step = step; +} + +void BasicProtocol::setSASLNext(const QByteArray &step) +{ + sasl_step = step; +} + +void BasicProtocol::setSASLAuthed() +{ + sasl_authed = true; +} + +int BasicProtocol::stringToSASLCond(const QString &s) +{ + for(int n = 0; saslCondTable[n].str; ++n) { + if(s == saslCondTable[n].str) + return saslCondTable[n].cond; + } + return -1; +} + +int BasicProtocol::stringToStreamCond(const QString &s) +{ + for(int n = 0; streamCondTable[n].str; ++n) { + if(s == streamCondTable[n].str) + return streamCondTable[n].cond; + } + return -1; +} + +QString BasicProtocol::saslCondToString(int x) +{ + for(int n = 0; saslCondTable[n].str; ++n) { + if(x == saslCondTable[n].cond) + return saslCondTable[n].str; + } + return QString(); +} + +QString BasicProtocol::streamCondToString(int x) +{ + for(int n = 0; streamCondTable[n].str; ++n) { + if(x == streamCondTable[n].cond) + return streamCondTable[n].str; + } + return QString(); +} + +void BasicProtocol::extractStreamError(const QDomElement &e) +{ + QString text; + QDomElement appSpec; + + QDomElement t = firstChildElement(e); + if(t.isNull() || t.namespaceURI() != NS_STREAMS) { + // probably old-style error + errCond = -1; + errText = e.text(); + } + else + errCond = stringToStreamCond(t.tagName()); + + if(errCond != -1) { + if(errCond == SeeOtherHost) + otherHost = t.text(); + + t = e.elementsByTagNameNS(NS_STREAMS, "text").item(0).toElement(); + if(!t.isNull()) + text = t.text(); + + // find first non-standard namespaced element + QDomNodeList nl = e.childNodes(); + for(uint n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement() && i.namespaceURI() != NS_STREAMS) { + appSpec = i.toElement(); + break; + } + } + + errText = text; + errAppSpec = appSpec; + } +} + +void BasicProtocol::send(const QDomElement &e, bool clip) +{ + writeElement(e, TypeElement, false, clip); +} + +void BasicProtocol::sendStreamError(int cond, const QString &text, const QDomElement &appSpec) +{ + QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); + QDomElement err = doc.createElementNS(NS_STREAMS, streamCondToString(cond)); + if(!otherHost.isEmpty()) + err.appendChild(doc.createTextNode(otherHost)); + se.appendChild(err); + if(!text.isEmpty()) { + QDomElement te = doc.createElementNS(NS_STREAMS, "text"); + te.setAttributeNS(NS_XML, "xml:lang", "en"); + te.appendChild(doc.createTextNode(text)); + se.appendChild(te); + } + se.appendChild(appSpec); + + writeElement(se, 100, false); +} + +void BasicProtocol::sendStreamError(const QString &text) +{ + QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); + se.appendChild(doc.createTextNode(text)); + + writeElement(se, 100, false); +} + +bool BasicProtocol::errorAndClose(int cond, const QString &text, const QDomElement &appSpec) +{ + closeError = true; + errCond = cond; + errText = text; + errAppSpec = appSpec; + sendStreamError(cond, text, appSpec); + return close(); +} + +bool BasicProtocol::error(int code) +{ + event = EError; + errorCode = code; + return true; +} + +void BasicProtocol::delayErrorAndClose(int cond, const QString &text, const QDomElement &appSpec) +{ + errorCode = ErrStream; + errCond = cond; + errText = text; + errAppSpec = appSpec; + delayedError = true; +} + +void BasicProtocol::delayError(int code) +{ + errorCode = code; + delayedError = true; +} + +QDomElement BasicProtocol::docElement() +{ + // create the root element + QDomElement e = doc.createElementNS(NS_ETHERX, "stream:stream"); + + QString defns = defaultNamespace(); + QStringList list = extraNamespaces(); + + // HACK: using attributes seems to be the only way to get additional namespaces in here + if(!defns.isEmpty()) + e.setAttribute("xmlns", defns); + for(QStringList::ConstIterator it = list.begin(); it != list.end();) { + QString prefix = *(it++); + QString uri = *(it++); + e.setAttribute(QString("xmlns:") + prefix, uri); + } + + // additional attributes + if(!isIncoming() && !to.isEmpty()) + e.setAttribute("to", to); + if(isIncoming() && !from.isEmpty()) + e.setAttribute("from", from); + if(!id.isEmpty()) + e.setAttribute("id", id); + if(!lang.isEmpty()) + e.setAttributeNS(NS_XML, "xml:lang", lang); + if(version.major > 0 || version.minor > 0) + e.setAttribute("version", QString::number(version.major) + '.' + QString::number(version.minor)); + + return e; +} + +void BasicProtocol::handleDocOpen(const Parser::Event &pe) +{ + if(isIncoming()) { + if(xmlEncoding() != "UTF-8") { + delayErrorAndClose(UnsupportedEncoding); + return; + } + } + + if(pe.namespaceURI() == NS_ETHERX && pe.localName() == "stream") { + QXmlAttributes atts = pe.atts(); + + // grab the version + int major = 0; + int minor = 0; + QString verstr = atts.value("version"); + if(!verstr.isEmpty()) { + int n = verstr.find('.'); + if(n != -1) { + major = verstr.mid(0, n).toInt(); + minor = verstr.mid(n+1).toInt(); + } + else { + major = verstr.toInt(); + minor = 0; + } + } + version = Version(major, minor); + + if(isIncoming()) { + to = atts.value("to"); + QString peerLang = atts.value(NS_XML, "lang"); + if(!peerLang.isEmpty()) + lang = peerLang; + } + // outgoing + else { + from = atts.value("from"); + lang = atts.value(NS_XML, "lang"); + id = atts.value("id"); + } + + handleStreamOpen(pe); + } + else { + if(isIncoming()) + delayErrorAndClose(BadFormat); + else + delayError(ErrProtocol); + } +} + +bool BasicProtocol::handleError() +{ + if(isIncoming()) + return errorAndClose(XmlNotWellFormed); + else + return error(ErrParse); +} + +bool BasicProtocol::handleCloseFinished() +{ + if(closeError) { + event = EError; + errorCode = ErrStream; + // note: errCond and friends are already set at this point + } + else + event = EClosed; + return true; +} + +bool BasicProtocol::doStep(const QDomElement &e) +{ + // handle pending error + if(delayedError) { + if(isIncoming()) + return errorAndClose(errCond, errText, errAppSpec); + else + return error(errorCode); + } + + // shutdown? + if(doShutdown) { + doShutdown = false; + return close(); + } + + if(!e.isNull()) { + // check for error + if(e.namespaceURI() == NS_ETHERX && e.tagName() == "error") { + extractStreamError(e); + return error(ErrStream); + } + } + + if(ready) { + // stanzas written? + if(stanzasWritten > 0) { + --stanzasWritten; + event = EStanzaSent; + return true; + } + // send items? + if(!sendList.isEmpty()) { + SendItem i; + { + QValueList::Iterator it = sendList.begin(); + i = (*it); + sendList.remove(it); + } + + // outgoing stanza? + if(!i.stanzaToSend.isNull()) { + ++stanzasPending; + writeElement(i.stanzaToSend, TypeStanza, true); + event = ESend; + } + // direct send? + else if(!i.stringToSend.isEmpty()) { + writeString(i.stringToSend, TypeDirect, true); + event = ESend; + } + // whitespace keepalive? + else if(i.doWhitespace) { + writeString("\n", TypePing, false); + event = ESend; + } + return true; + } + else { + // if we have pending outgoing stanzas, ask for write notification + if(stanzasPending) + notify |= NSend; + } + } + + return doStep2(e); +} + +void BasicProtocol::itemWritten(int id, int) +{ + if(id == TypeStanza) { + --stanzasPending; + ++stanzasWritten; + } +} + +QString BasicProtocol::defaultNamespace() +{ + // default none + return QString(); +} + +QStringList BasicProtocol::extraNamespaces() +{ + // default none + return QStringList(); +} + +void BasicProtocol::handleStreamOpen(const Parser::Event &) +{ + // default does nothing +} + +//---------------------------------------------------------------------------- +// CoreProtocol +//---------------------------------------------------------------------------- +CoreProtocol::CoreProtocol() +:BasicProtocol() +{ + init(); +} + +CoreProtocol::~CoreProtocol() +{ +} + +void CoreProtocol::init() +{ + step = Start; + + // ?? + server = false; + dialback = false; + dialback_verify = false; + + // settings + jid = Jid(); + password = QString(); + oldOnly = false; + allowPlain = false; + doTLS = true; + doAuth = true; + doBinding = true; + + // input + user = QString(); + host = QString(); + + // status + old = false; + digest = false; + tls_started = false; + sasl_started = false; +} + +void CoreProtocol::reset() +{ + BasicProtocol::reset(); + init(); +} + +void CoreProtocol::startClientOut(const Jid &_jid, bool _oldOnly, bool tlsActive, bool _doAuth) +{ + jid = _jid; + to = _jid.domain(); + oldOnly = _oldOnly; + doAuth = _doAuth; + tls_started = tlsActive; + + if(oldOnly) + version = Version(0,0); + startConnect(); +} + +void CoreProtocol::startServerOut(const QString &_to) +{ + server = true; + to = _to; + startConnect(); +} + +void CoreProtocol::startDialbackOut(const QString &_to, const QString &_from) +{ + server = true; + dialback = true; + to = _to; + self_from = _from; + startConnect(); +} + +void CoreProtocol::startDialbackVerifyOut(const QString &_to, const QString &_from, const QString &id, const QString &key) +{ + server = true; + dialback = true; + dialback_verify = true; + to = _to; + self_from = _from; + dialback_id = id; + dialback_key = key; + startConnect(); +} + +void CoreProtocol::startClientIn(const QString &_id) +{ + id = _id; + startAccept(); +} + +void CoreProtocol::startServerIn(const QString &_id) +{ + server = true; + id = _id; + startAccept(); +} + +void CoreProtocol::setLang(const QString &s) +{ + lang = s; +} + +void CoreProtocol::setAllowTLS(bool b) +{ + doTLS = b; +} + +void CoreProtocol::setAllowBind(bool b) +{ + doBinding = b; +} + +void CoreProtocol::setAllowPlain(bool b) +{ + allowPlain = b; +} + +void CoreProtocol::setPassword(const QString &s) +{ + password = s; +} + +void CoreProtocol::setFrom(const QString &s) +{ + from = s; +} + +void CoreProtocol::setDialbackKey(const QString &s) +{ + dialback_key = s; +} + +bool CoreProtocol::loginComplete() +{ + setReady(true); + + event = EReady; + step = Done; + return true; +} + +int CoreProtocol::getOldErrorCode(const QDomElement &e) +{ + QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); + if(err.isNull() || !err.hasAttribute("code")) + return -1; + return err.attribute("code").toInt(); +} + +/*QString CoreProtocol::xmlToString(const QDomElement &e, bool clip) +{ + // determine an appropriate 'fakeNS' to use + QString ns; + if(e.prefix() == "stream") + ns = NS_ETHERX; + else if(e.prefix() == "db") + ns = NS_DIALBACK; + else + ns = NS_CLIENT; + return ::xmlToString(e, ns, "stream:stream", clip); +}*/ + +bool CoreProtocol::stepAdvancesParser() const +{ + if(stepRequiresElement()) + return true; + else if(isReady()) + return true; + return false; +} + +// all element-needing steps need to be registered here +bool CoreProtocol::stepRequiresElement() const +{ + switch(step) { + case GetFeatures: + case GetTLSProceed: + case GetSASLChallenge: + case GetBindResponse: + case GetAuthGetResponse: + case GetAuthSetResponse: + case GetRequest: + case GetSASLResponse: + return true; + } + return false; +} + +void CoreProtocol::stringSend(const QString &s) +{ +#ifdef XMPP_TEST + TD::outgoingTag(s); +#endif +} + +void CoreProtocol::stringRecv(const QString &s) +{ +#ifdef XMPP_TEST + TD::incomingTag(s); +#endif +} + +QString CoreProtocol::defaultNamespace() +{ + if(server) + return NS_SERVER; + else + return NS_CLIENT; +} + +QStringList CoreProtocol::extraNamespaces() +{ + QStringList list; + if(dialback) { + list += "db"; + list += NS_DIALBACK; + } + return list; +} + +void CoreProtocol::handleStreamOpen(const Parser::Event &pe) +{ + if(isIncoming()) { + QString ns = pe.nsprefix(); + QString db; + if(server) { + db = pe.nsprefix("db"); + if(!db.isEmpty()) + dialback = true; + } + + // verify namespace + if((!server && ns != NS_CLIENT) || (server && ns != NS_SERVER) || (dialback && db != NS_DIALBACK)) { + delayErrorAndClose(InvalidNamespace); + return; + } + + // verify version + if(version.major < 1 && !dialback) { + delayErrorAndClose(UnsupportedVersion); + return; + } + } + else { + if(!dialback) { + if(version.major >= 1 && !oldOnly) + old = false; + else + old = true; + } + } +} + +void CoreProtocol::elementSend(const QDomElement &e) +{ +#ifdef XMPP_TEST + TD::outgoingXml(e); +#endif +} + +void CoreProtocol::elementRecv(const QDomElement &e) +{ +#ifdef XMPP_TEST + TD::incomingXml(e); +#endif +} + +bool CoreProtocol::doStep2(const QDomElement &e) +{ + if(dialback) + return dialbackStep(e); + else + return normalStep(e); +} + +bool CoreProtocol::isValidStanza(const QDomElement &e) const +{ + QString s = e.tagName(); + if(e.namespaceURI() == (server ? NS_SERVER : NS_CLIENT) && (s == "message" || s == "presence" || s == "iq")) + return true; + else + return false; +} + +bool CoreProtocol::grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item) +{ + for(QValueList::Iterator it = dbpending.begin(); it != dbpending.end(); ++it) { + const DBItem &i = *it; + if(i.type == type && i.to.compare(to) && i.from.compare(from)) { + const DBItem &i = (*it); + *item = i; + dbpending.remove(it); + return true; + } + } + return false; +} + +bool CoreProtocol::dialbackStep(const QDomElement &e) +{ + if(step == Start) { + setReady(true); + step = Done; + event = EReady; + return true; + } + + if(!dbrequests.isEmpty()) { + // process a request + DBItem i; + { + QValueList::Iterator it = dbrequests.begin(); + i = (*it); + dbrequests.remove(it); + } + + QDomElement r; + if(i.type == DBItem::ResultRequest) { + r = doc.createElementNS(NS_DIALBACK, "db:result"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.appendChild(doc.createTextNode(i.key)); + dbpending += i; + } + else if(i.type == DBItem::ResultGrant) { + r = doc.createElementNS(NS_DIALBACK, "db:result"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.setAttribute("type", i.ok ? "valid" : "invalid"); + if(i.ok) { + i.type = DBItem::Validated; + dbvalidated += i; + } + else { + // TODO: disconnect after writing element + } + } + else if(i.type == DBItem::VerifyRequest) { + r = doc.createElementNS(NS_DIALBACK, "db:verify"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.setAttribute("id", i.id); + r.appendChild(doc.createTextNode(i.key)); + dbpending += i; + } + // VerifyGrant + else { + r = doc.createElementNS(NS_DIALBACK, "db:verify"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.setAttribute("id", i.id); + r.setAttribute("type", i.ok ? "valid" : "invalid"); + } + + writeElement(r, TypeElement, false); + event = ESend; + return true; + } + + if(!e.isNull()) { + if(e.namespaceURI() == NS_DIALBACK) { + if(e.tagName() == "result") { + Jid to, from; + to.set(e.attribute("to"), ""); + from.set(e.attribute("from"), ""); + if(isIncoming()) { + QString key = e.text(); + // TODO: report event + } + else { + bool ok = (e.attribute("type") == "valid") ? true: false; + DBItem i; + if(grabPendingItem(from, to, DBItem::ResultRequest, &i)) { + if(ok) { + i.type = DBItem::Validated; + i.ok = true; + dbvalidated += i; + // TODO: report event + } + else { + // TODO: report event + } + } + } + } + else if(e.tagName() == "verify") { + Jid to, from; + to.set(e.attribute("to"), ""); + from.set(e.attribute("from"), ""); + QString id = e.attribute("id"); + if(isIncoming()) { + QString key = e.text(); + // TODO: report event + } + else { + bool ok = (e.attribute("type") == "valid") ? true: false; + DBItem i; + if(grabPendingItem(from, to, DBItem::VerifyRequest, &i)) { + if(ok) { + // TODO: report event + } + else { + // TODO: report event + } + } + } + } + } + else { + if(isReady()) { + if(isValidStanza(e)) { + // TODO: disconnect if stanza is from unverified sender + // TODO: ignore packets from receiving servers + stanzaToRecv = e; + event = EStanzaReady; + return true; + } + } + } + } + + need = NNotify; + notify |= NRecv; + return false; +} + +bool CoreProtocol::normalStep(const QDomElement &e) +{ + if(step == Start) { + if(isIncoming()) { + need = NSASLMechs; + step = SendFeatures; + return false; + } + else { + if(old) { + if(doAuth) + step = HandleAuthGet; + else + return loginComplete(); + } + else + step = GetFeatures; + + return processStep(); + } + } + else if(step == HandleFeatures) { + // deal with TLS? + if(doTLS && !tls_started && !sasl_authed && features.tls_supported) { + QDomElement e = doc.createElementNS(NS_TLS, "starttls"); + + send(e, true); + event = ESend; + step = GetTLSProceed; + return true; + } + + // deal with SASL? + if(!sasl_authed) { + if(!features.sasl_supported) { + // SASL MUST be supported + event = EError; + errorCode = ErrProtocol; + return true; + } + +#ifdef XMPP_TEST + TD::msg("starting SASL authentication..."); +#endif + need = NSASLFirst; + step = GetSASLFirst; + return false; + } + + if(server) { + return loginComplete(); + } + else { + if(!doBinding) + return loginComplete(); + } + + // deal with bind + if(!features.bind_supported) { + // bind MUST be supported + event = EError; + errorCode = ErrProtocol; + return true; + } + + QDomElement e = doc.createElement("iq"); + e.setAttribute("type", "set"); + e.setAttribute("id", "bind_1"); + QDomElement b = doc.createElementNS(NS_BIND, "bind"); + + // request specific resource? + QString resource = jid.resource(); + if(!resource.isEmpty()) { + QDomElement r = doc.createElement("resource"); + r.appendChild(doc.createTextNode(jid.resource())); + b.appendChild(r); + } + + e.appendChild(b); + + send(e); + event = ESend; + step = GetBindResponse; + return true; + } + else if(step == GetSASLFirst) { + QDomElement e = doc.createElementNS(NS_SASL, "auth"); + e.setAttribute("mechanism", sasl_mech); + if(!sasl_step.isEmpty()) { +#ifdef XMPP_TEST + TD::msg(QString("SASL OUT: [%1]").arg(printArray(sasl_step))); +#endif + e.appendChild(doc.createTextNode(Base64::arrayToString(sasl_step))); + } + + send(e, true); + event = ESend; + step = GetSASLChallenge; + return true; + } + else if(step == GetSASLNext) { + if(isIncoming()) { + if(sasl_authed) { + QDomElement e = doc.createElementNS(NS_SASL, "success"); + writeElement(e, TypeElement, false, true); + event = ESend; + step = IncHandleSASLSuccess; + return true; + } + else { + QByteArray stepData = sasl_step; + QDomElement e = doc.createElementNS(NS_SASL, "challenge"); + if(!stepData.isEmpty()) + e.appendChild(doc.createTextNode(Base64::arrayToString(stepData))); + + writeElement(e, TypeElement, false, true); + event = ESend; + step = GetSASLResponse; + return true; + } + } + else { + QByteArray stepData = sasl_step; +#ifdef XMPP_TEST + TD::msg(QString("SASL OUT: [%1]").arg(printArray(sasl_step))); +#endif + QDomElement e = doc.createElementNS(NS_SASL, "response"); + if(!stepData.isEmpty()) + e.appendChild(doc.createTextNode(Base64::arrayToString(stepData))); + + send(e, true); + event = ESend; + step = GetSASLChallenge; + return true; + } + } + else if(step == HandleSASLSuccess) { + need = NSASLLayer; + spare = resetStream(); + step = Start; + return false; + } + else if(step == HandleAuthGet) { + QDomElement e = doc.createElement("iq"); + e.setAttribute("to", to); + e.setAttribute("type", "get"); + e.setAttribute("id", "auth_1"); + QDomElement q = doc.createElementNS("jabber:iq:auth", "query"); + QDomElement u = doc.createElement("username"); + u.appendChild(doc.createTextNode(jid.node())); + q.appendChild(u); + e.appendChild(q); + + send(e); + event = ESend; + step = GetAuthGetResponse; + return true; + } + else if(step == HandleAuthSet) { + QDomElement e = doc.createElement("iq"); + e.setAttribute("to", to); + e.setAttribute("type", "set"); + e.setAttribute("id", "auth_2"); + QDomElement q = doc.createElementNS("jabber:iq:auth", "query"); + QDomElement u = doc.createElement("username"); + u.appendChild(doc.createTextNode(jid.node())); + q.appendChild(u); + QDomElement p; + if(digest) { + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + p = doc.createElement("digest"); + QCString cs = id.utf8() + password.utf8(); + p.appendChild(doc.createTextNode(QCA::SHA1::hashToString(cs))); + } + else { + p = doc.createElement("password"); + p.appendChild(doc.createTextNode(password)); + } + q.appendChild(p); + QDomElement r = doc.createElement("resource"); + r.appendChild(doc.createTextNode(jid.resource())); + q.appendChild(r); + e.appendChild(q); + + send(e, true); + event = ESend; + step = GetAuthSetResponse; + return true; + } + // server + else if(step == SendFeatures) { + QDomElement f = doc.createElementNS(NS_ETHERX, "stream:features"); + if(!tls_started && !sasl_authed) { // don't offer tls if we are already sasl'd + QDomElement tls = doc.createElementNS(NS_TLS, "starttls"); + f.appendChild(tls); + } + + if(sasl_authed) { + if(!server) { + QDomElement bind = doc.createElementNS(NS_BIND, "bind"); + f.appendChild(bind); + } + } + else { + QDomElement mechs = doc.createElementNS(NS_SASL, "mechanisms"); + for(QStringList::ConstIterator it = sasl_mechlist.begin(); it != sasl_mechlist.end(); ++it) { + QDomElement m = doc.createElement("mechanism"); + m.appendChild(doc.createTextNode(*it)); + mechs.appendChild(m); + } + f.appendChild(mechs); + } + + writeElement(f, TypeElement, false); + event = ESend; + step = GetRequest; + return true; + } + // server + else if(step == HandleTLS) { + tls_started = true; + need = NStartTLS; + spare = resetStream(); + step = Start; + return false; + } + // server + else if(step == IncHandleSASLSuccess) { + event = ESASLSuccess; + spare = resetStream(); + step = Start; + printf("sasl success\n"); + return true; + } + else if(step == GetFeatures) { + // we are waiting for stream features + if(e.namespaceURI() == NS_ETHERX && e.tagName() == "features") { + // extract features + StreamFeatures f; + QDomElement s = e.elementsByTagNameNS(NS_TLS, "starttls").item(0).toElement(); + if(!s.isNull()) { + f.tls_supported = true; + f.tls_required = s.elementsByTagNameNS(NS_TLS, "required").count() > 0; + } + QDomElement m = e.elementsByTagNameNS(NS_SASL, "mechanisms").item(0).toElement(); + if(!m.isNull()) { + f.sasl_supported = true; + QDomNodeList l = m.elementsByTagNameNS(NS_SASL, "mechanism"); + for(uint n = 0; n < l.count(); ++n) + f.sasl_mechs += l.item(n).toElement().text(); + } + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + if(!b.isNull()) + f.bind_supported = true; + + if(f.tls_supported) { +#ifdef XMPP_TEST + QString s = "STARTTLS is available"; + if(f.tls_required) + s += " (required)"; + TD::msg(s); +#endif + } + if(f.sasl_supported) { +#ifdef XMPP_TEST + QString s = "SASL mechs:"; + for(QStringList::ConstIterator it = f.sasl_mechs.begin(); it != f.sasl_mechs.end(); ++it) + s += QString(" [%1]").arg((*it)); + TD::msg(s); +#endif + } + + if(doAuth) { + event = EFeatures; + features = f; + step = HandleFeatures; + return true; + } + else + return loginComplete(); + } + else { + // ignore + } + } + else if(step == GetTLSProceed) { + // waiting for proceed to starttls + if(e.namespaceURI() == NS_TLS) { + if(e.tagName() == "proceed") { +#ifdef XMPP_TEST + TD::msg("Server wants us to proceed with ssl handshake"); +#endif + tls_started = true; + need = NStartTLS; + spare = resetStream(); + step = Start; + return false; + } + else if(e.tagName() == "failure") { + event = EError; + errorCode = ErrStartTLS; + return true; + } + else { + event = EError; + errorCode = ErrProtocol; + return true; + } + } + else { + // ignore + } + } + else if(step == GetSASLChallenge) { + // waiting for sasl challenge/success/fail + if(e.namespaceURI() == NS_SASL) { + if(e.tagName() == "challenge") { + QByteArray a = Base64::stringToArray(e.text()); +#ifdef XMPP_TEST + TD::msg(QString("SASL IN: [%1]").arg(printArray(a))); +#endif + sasl_step = a; + need = NSASLNext; + step = GetSASLNext; + return false; + } + else if(e.tagName() == "success") { + sasl_authed = true; + event = ESASLSuccess; + step = HandleSASLSuccess; + return true; + } + else if(e.tagName() == "failure") { + QDomElement t = firstChildElement(e); + if(t.isNull() || t.namespaceURI() != NS_SASL) + errCond = -1; + else + errCond = stringToSASLCond(t.tagName()); + + event = EError; + errorCode = ErrAuth; + return true; + } + else { + event = EError; + errorCode = ErrProtocol; + return true; + } + } + } + else if(step == GetBindResponse) { + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + if(id == "bind_1" && (type == "result" || type == "error")) { + if(type == "result") { + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + Jid j; + if(!b.isNull()) { + QDomElement je = e.elementsByTagName("jid").item(0).toElement(); + j = je.text(); + } + if(!j.isValid()) { + event = EError; + errorCode = ErrProtocol; + return true; + } + jid = j; + return loginComplete(); + } + else { + errCond = -1; + + QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); + if(!err.isNull()) { + // get error condition + QDomNodeList nl = err.childNodes(); + QDomElement t; + for(uint n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement()) { + t = i.toElement(); + break; + } + } + if(!t.isNull() && t.namespaceURI() == NS_STANZAS) { + QString cond = t.tagName(); + if(cond == "not-allowed") + errCond = BindNotAllowed; + else if(cond == "conflict") + errCond = BindConflict; + } + } + + event = EError; + errorCode = ErrBind; + return true; + } + } + else { + // ignore + } + } + else { + // ignore + } + } + else if(step == GetAuthGetResponse) { + // waiting for an iq + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + Jid from(e.attribute("from")); + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + bool okfrom = (from.isEmpty() || from.compare(Jid(to))); + if(okfrom && id == "auth_1" && (type == "result" || type == "error")) { + if(type == "result") { + QDomElement q = e.elementsByTagNameNS("jabber:iq:auth", "query").item(0).toElement(); + if(q.isNull() || q.elementsByTagName("username").item(0).isNull() || q.elementsByTagName("resource").item(0).isNull()) { + event = EError; + errorCode = ErrProtocol; + return true; + } + bool plain_supported = !q.elementsByTagName("password").item(0).isNull(); + bool digest_supported = !q.elementsByTagName("digest").item(0).isNull(); + + if(!digest_supported && !plain_supported) { + event = EError; + errorCode = ErrProtocol; + return true; + } + + // plain text not allowed? + if(!digest_supported && !allowPlain) { + event = EError; + errorCode = ErrPlain; + return true; + } + + digest = digest_supported; + need = NPassword; + step = HandleAuthSet; + return false; + } + else { + errCond = getOldErrorCode(e); + + event = EError; + errorCode = ErrAuth; + return true; + } + } + else { + // ignore + } + } + else { + // ignore + } + } + else if(step == GetAuthSetResponse) { + // waiting for an iq + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + Jid from(e.attribute("from")); + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + bool okfrom = (from.isEmpty() || from.compare(Jid(to))); + if(okfrom && id == "auth_2" && (type == "result" || type == "error")) { + if(type == "result") { + return loginComplete(); + } + else { + errCond = getOldErrorCode(e); + + event = EError; + errorCode = ErrAuth; + return true; + } + } + else { + // ignore + } + } + else { + // ignore + } + } + // server + else if(step == GetRequest) { + printf("get request: [%s], %s\n", e.namespaceURI().latin1(), e.tagName().latin1()); + if(e.namespaceURI() == NS_TLS && e.localName() == "starttls") { + // TODO: don't let this be done twice + + QDomElement e = doc.createElementNS(NS_TLS, "proceed"); + writeElement(e, TypeElement, false, true); + event = ESend; + step = HandleTLS; + return true; + } + if(e.namespaceURI() == NS_SASL) { + if(e.localName() == "auth") { + if(sasl_started) { + // TODO + printf("error\n"); + return false; + } + + sasl_started = true; + sasl_mech = e.attribute("mechanism"); + // TODO: if child text missing, don't pass it + sasl_step = Base64::stringToArray(e.text()); + need = NSASLFirst; + step = GetSASLNext; + return false; + } + else { + // TODO + printf("unknown sasl tag\n"); + return false; + } + } + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + if(!b.isNull()) { + QDomElement res = b.elementsByTagName("resource").item(0).toElement(); + QString resource = res.text(); + + QDomElement r = doc.createElement("iq"); + r.setAttribute("type", "result"); + r.setAttribute("id", e.attribute("id")); + QDomElement bind = doc.createElementNS(NS_BIND, "bind"); + QDomElement jid = doc.createElement("jid"); + Jid j = user + '@' + host + '/' + resource; + jid.appendChild(doc.createTextNode(j.full())); + bind.appendChild(jid); + r.appendChild(bind); + + writeElement(r, TypeElement, false); + event = ESend; + // TODO + return true; + } + else { + // TODO + } + } + } + else if(step == GetSASLResponse) { + if(e.namespaceURI() == NS_SASL && e.localName() == "response") { + sasl_step = Base64::stringToArray(e.text()); + need = NSASLNext; + step = GetSASLNext; + return false; + } + } + + if(isReady()) { + if(!e.isNull() && isValidStanza(e)) { + stanzaToRecv = e; + event = EStanzaReady; + setIncomingAsExternal(); + return true; + } + } + + need = NNotify; + notify |= NRecv; + return false; +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.h new file mode 100644 index 00000000..8511ce32 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.h @@ -0,0 +1,355 @@ +/* + * protocol.h - XMPP-Core protocol state machine + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include +#include"xmlprotocol.h" +#include"xmpp.h" + +#define NS_ETHERX "http://etherx.jabber.org/streams" +#define NS_CLIENT "jabber:client" +#define NS_SERVER "jabber:server" +#define NS_DIALBACK "jabber:server:dialback" +#define NS_STREAMS "urn:ietf:params:xml:ns:xmpp-streams" +#define NS_TLS "urn:ietf:params:xml:ns:xmpp-tls" +#define NS_SASL "urn:ietf:params:xml:ns:xmpp-sasl" +#define NS_SESSION "urn:ietf:params:xml:ns:xmpp-session" +#define NS_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas" +#define NS_BIND "urn:ietf:params:xml:ns:xmpp-bind" +#define NS_XHTML_IM "http://jabber.org/protocol/xhtml-im" +#define NS_XHTML "http://www.w3.org/1999/xhtml" +#define NS_CHATSTATES "http://jabber.org/protocol/chatstates" + +namespace XMPP +{ + class Version + { + public: + Version(int maj=0, int min=0); + + int major, minor; + }; + + class StreamFeatures + { + public: + StreamFeatures(); + + bool tls_supported, sasl_supported, bind_supported; + bool tls_required; + QStringList sasl_mechs; + }; + + class BasicProtocol : public XmlProtocol + { + public: + // xmpp 1.0 error conditions + enum SASLCond { + Aborted, + IncorrectEncoding, + InvalidAuthzid, + InvalidMech, + MechTooWeak, + NotAuthorized, + TemporaryAuthFailure + }; + enum StreamCond { + BadFormat, + BadNamespacePrefix, + Conflict, + ConnectionTimeout, + HostGone, + HostUnknown, + ImproperAddressing, + InternalServerError, + InvalidFrom, + InvalidId, + InvalidNamespace, + InvalidXml, + StreamNotAuthorized, + PolicyViolation, + RemoteConnectionFailed, + ResourceConstraint, + RestrictedXml, + SeeOtherHost, + SystemShutdown, + UndefinedCondition, + UnsupportedEncoding, + UnsupportedStanzaType, + UnsupportedVersion, + XmlNotWellFormed + }; + enum BindCond { + BindBadRequest, + BindNotAllowed, + BindConflict + }; + + // extend the XmlProtocol enums + enum Need { + NSASLMechs = XmlProtocol::NCustom, // need SASL mechlist + NStartTLS, // need to switch on TLS layer + NSASLFirst, // need SASL first step + NSASLNext, // need SASL next step + NSASLLayer, // need to switch on SASL layer + NCustom = XmlProtocol::NCustom+10 + }; + enum Event { + EFeatures = XmlProtocol::ECustom, // breakpoint after features packet is received + ESASLSuccess, // breakpoint after successful sasl auth + EStanzaReady, // a stanza was received + EStanzaSent, // a stanza was sent + EReady, // stream is ready for stanza use + ECustom = XmlProtocol::ECustom+10 + }; + enum Error { + ErrProtocol = XmlProtocol::ErrCustom, // there was an error in the xmpp-core protocol exchange + ErrStream, // , see errCond, errText, and errAppSpec for details + ErrStartTLS, // server refused starttls + ErrAuth, // authorization error. errCond holds sasl condition (or numeric code for old-protocol) + ErrBind, // server refused resource bind + ErrCustom = XmlProtocol::ErrCustom+10 + }; + + BasicProtocol(); + ~BasicProtocol(); + + void reset(); + + // for outgoing xml + QDomDocument doc; + + // sasl-related + QString saslMech() const; + QByteArray saslStep() const; + void setSASLMechList(const QStringList &list); + void setSASLFirst(const QString &mech, const QByteArray &step); + void setSASLNext(const QByteArray &step); + void setSASLAuthed(); + + // send / recv + void sendStanza(const QDomElement &e); + void sendDirect(const QString &s); + void sendWhitespace(); + QDomElement recvStanza(); + + // shutdown + void shutdown(); + void shutdownWithError(int cond, const QString &otherHost=""); + + // information + QString to, from, id, lang; + Version version; + + // error output + int errCond; + QString errText; + QDomElement errAppSpec; + QString otherHost; + + QByteArray spare; // filled with unprocessed data on NStartTLS and NSASLLayer + + bool isReady() const; + + enum { TypeElement, TypeStanza, TypeDirect, TypePing }; + + protected: + static int stringToSASLCond(const QString &s); + static int stringToStreamCond(const QString &s); + static QString saslCondToString(int); + static QString streamCondToString(int); + + void send(const QDomElement &e, bool clip=false); + void sendStreamError(int cond, const QString &text="", const QDomElement &appSpec=QDomElement()); + void sendStreamError(const QString &text); // old-style + + bool errorAndClose(int cond, const QString &text="", const QDomElement &appSpec=QDomElement()); + bool error(int code); + void delayErrorAndClose(int cond, const QString &text="", const QDomElement &appSpec=QDomElement()); + void delayError(int code); + + // reimplemented + QDomElement docElement(); + void handleDocOpen(const Parser::Event &pe); + bool handleError(); + bool handleCloseFinished(); + bool doStep(const QDomElement &e); + void itemWritten(int id, int size); + + virtual QString defaultNamespace(); + virtual QStringList extraNamespaces(); // stringlist: prefix,uri,prefix,uri, [...] + virtual void handleStreamOpen(const Parser::Event &pe); + virtual bool doStep2(const QDomElement &e)=0; + + void setReady(bool b); + + QString sasl_mech; + QStringList sasl_mechlist; + QByteArray sasl_step; + bool sasl_authed; + + QDomElement stanzaToRecv; + + private: + struct SASLCondEntry + { + const char *str; + int cond; + }; + static SASLCondEntry saslCondTable[]; + + struct StreamCondEntry + { + const char *str; + int cond; + }; + static StreamCondEntry streamCondTable[]; + + struct SendItem + { + QDomElement stanzaToSend; + QString stringToSend; + bool doWhitespace; + }; + QValueList sendList; + + bool doShutdown, delayedError, closeError, ready; + int stanzasPending, stanzasWritten; + + void init(); + void extractStreamError(const QDomElement &e); + }; + + class CoreProtocol : public BasicProtocol + { + public: + enum { + NPassword = NCustom, // need password for old-mode + EDBVerify = ECustom, // breakpoint after db:verify request + ErrPlain = ErrCustom // server only supports plain, but allowPlain is false locally + }; + + CoreProtocol(); + ~CoreProtocol(); + + void reset(); + + void startClientOut(const Jid &jid, bool oldOnly, bool tlsActive, bool doAuth); + void startServerOut(const QString &to); + void startDialbackOut(const QString &to, const QString &from); + void startDialbackVerifyOut(const QString &to, const QString &from, const QString &id, const QString &key); + void startClientIn(const QString &id); + void startServerIn(const QString &id); + + void setLang(const QString &s); + void setAllowTLS(bool b); + void setAllowBind(bool b); + void setAllowPlain(bool b); // old-mode + + void setPassword(const QString &s); + void setFrom(const QString &s); + void setDialbackKey(const QString &s); + + // input + QString user, host; + + // status + bool old; + + StreamFeatures features; + + //static QString xmlToString(const QDomElement &e, bool clip=false); + + class DBItem + { + public: + enum { ResultRequest, ResultGrant, VerifyRequest, VerifyGrant, Validated }; + int type; + Jid to, from; + QString key, id; + bool ok; + }; + + private: + enum Step { + Start, + Done, + SendFeatures, + GetRequest, + HandleTLS, + GetSASLResponse, + IncHandleSASLSuccess, + GetFeatures, // read features packet + HandleFeatures, // act on features, by initiating tls, sasl, or bind + GetTLSProceed, // read tls response + GetSASLFirst, // perform sasl first step using provided data + GetSASLChallenge, // read server sasl challenge + GetSASLNext, // perform sasl next step using provided data + HandleSASLSuccess, // handle what must be done after reporting sasl success + GetBindResponse, // read bind response + HandleAuthGet, // send old-protocol auth-get + GetAuthGetResponse, // read auth-get response + HandleAuthSet, // send old-protocol auth-set + GetAuthSetResponse // read auth-set response + }; + + QValueList dbrequests, dbpending, dbvalidated; + + bool server, dialback, dialback_verify; + int step; + + bool digest; + bool tls_started, sasl_started; + + Jid jid; + bool oldOnly; + bool allowPlain; + bool doTLS, doAuth, doBinding; + QString password; + + QString dialback_id, dialback_key; + QString self_from; + + void init(); + static int getOldErrorCode(const QDomElement &e); + bool loginComplete(); + + bool isValidStanza(const QDomElement &e) const; + bool grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item); + bool normalStep(const QDomElement &e); + bool dialbackStep(const QDomElement &e); + + // reimplemented + bool stepAdvancesParser() const; + bool stepRequiresElement() const; + void stringSend(const QString &s); + void stringRecv(const QString &s); + QString defaultNamespace(); + QStringList extraNamespaces(); + void handleStreamOpen(const Parser::Event &pe); + bool doStep2(const QDomElement &e); + void elementSend(const QDomElement &e); + void elementRecv(const QDomElement &e); + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/qcaprovider.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/qcaprovider.h new file mode 100644 index 00000000..a7f1805b --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/qcaprovider.h @@ -0,0 +1,191 @@ +/* + * qcaprovider.h - QCA Plugin API + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef QCAPROVIDER_H +#define QCAPROVIDER_H + +#include +#include +#include +#include +#include +#include"qca.h" + +#define QCA_PLUGIN_VERSION 1 + +class QCAProvider +{ +public: + QCAProvider() {} + virtual ~QCAProvider() {} + + virtual void init()=0; + virtual int qcaVersion() const=0; + virtual int capabilities() const=0; + virtual void *context(int cap)=0; +}; + +class QCA_HashContext +{ +public: + virtual ~QCA_HashContext() {} + + virtual QCA_HashContext *clone()=0; + virtual void reset()=0; + virtual void update(const char *in, unsigned int len)=0; + virtual void final(QByteArray *out)=0; +}; + +class QCA_CipherContext +{ +public: + virtual ~QCA_CipherContext() {} + + virtual QCA_CipherContext *clone()=0; + virtual int keySize()=0; + virtual int blockSize()=0; + virtual bool generateKey(char *out, int keysize=-1)=0; + virtual bool generateIV(char *out)=0; + + virtual bool setup(int dir, int mode, const char *key, int keysize, const char *iv, bool pad)=0; + virtual bool update(const char *in, unsigned int len)=0; + virtual bool final(QByteArray *out)=0; +}; + +class QCA_RSAKeyContext +{ +public: + virtual ~QCA_RSAKeyContext() {} + + virtual QCA_RSAKeyContext *clone() const=0; + virtual bool isNull() const=0; + virtual bool havePublic() const=0; + virtual bool havePrivate() const=0; + virtual bool createFromDER(const char *in, unsigned int len)=0; + virtual bool createFromPEM(const char *in, unsigned int len)=0; + virtual bool createFromNative(void *in)=0; + virtual bool generate(unsigned int bits)=0; + virtual bool toDER(QByteArray *out, bool publicOnly)=0; + virtual bool toPEM(QByteArray *out, bool publicOnly)=0; + + virtual bool encrypt(const QByteArray &in, QByteArray *out, bool oaep)=0; + virtual bool decrypt(const QByteArray &in, QByteArray *out, bool oaep)=0; +}; + +struct QCA_CertProperty +{ + QString var; + QString val; +}; + +class QCA_CertContext +{ +public: + virtual ~QCA_CertContext() {} + + virtual QCA_CertContext *clone() const=0; + virtual bool isNull() const=0; + virtual bool createFromDER(const char *in, unsigned int len)=0; + virtual bool createFromPEM(const char *in, unsigned int len)=0; + virtual bool toDER(QByteArray *out)=0; + virtual bool toPEM(QByteArray *out)=0; + + virtual QString serialNumber() const=0; + virtual QString subjectString() const=0; + virtual QString issuerString() const=0; + virtual QValueList subject() const=0; + virtual QValueList issuer() const=0; + virtual QDateTime notBefore() const=0; + virtual QDateTime notAfter() const=0; + virtual bool matchesAddress(const QString &realHost) const=0; +}; + +class QCA_TLSContext +{ +public: + enum Result { Success, Error, Continue }; + virtual ~QCA_TLSContext() {} + + virtual void reset()=0; + virtual bool startClient(const QPtrList &store, const QCA_CertContext &cert, const QCA_RSAKeyContext &key)=0; + virtual bool startServer(const QPtrList &store, const QCA_CertContext &cert, const QCA_RSAKeyContext &key)=0; + + virtual int handshake(const QByteArray &in, QByteArray *out)=0; + virtual int shutdown(const QByteArray &in, QByteArray *out)=0; + virtual bool encode(const QByteArray &plain, QByteArray *to_net, int *encoded)=0; + virtual bool decode(const QByteArray &from_net, QByteArray *plain, QByteArray *to_net)=0; + virtual bool eof() const=0; + virtual QByteArray unprocessed()=0; + + virtual QCA_CertContext *peerCertificate() const=0; + virtual int validityResult() const=0; +}; + +struct QCA_SASLHostPort +{ + QHostAddress addr; + Q_UINT16 port; +}; + +struct QCA_SASLNeedParams +{ + bool user, authzid, pass, realm; +}; + +class QCA_SASLContext +{ +public: + enum Result { Success, Error, NeedParams, AuthCheck, Continue }; + virtual ~QCA_SASLContext() {} + + // common + virtual void reset()=0; + virtual void setCoreProps(const QString &service, const QString &host, QCA_SASLHostPort *local, QCA_SASLHostPort *remote)=0; + virtual void setSecurityProps(bool noPlain, bool noActive, bool noDict, bool noAnon, bool reqForward, bool reqCreds, bool reqMutual, int ssfMin, int ssfMax, const QString &_ext_authid, int _ext_ssf)=0; + virtual int security() const=0; + virtual int errorCond() const=0; + + // init / first step + virtual bool clientStart(const QStringList &mechlist)=0; + virtual int clientFirstStep(bool allowClientSendFirst)=0; + virtual bool serverStart(const QString &realm, QStringList *mechlist, const QString &name)=0; + virtual int serverFirstStep(const QString &mech, const QByteArray *in)=0; + + // get / set params + virtual QCA_SASLNeedParams clientParamsNeeded() const=0; + virtual void setClientParams(const QString *user, const QString *authzid, const QString *pass, const QString *realm)=0; + virtual QString username() const=0; + virtual QString authzid() const=0; + + // continue steps + virtual int nextStep(const QByteArray &in)=0; + virtual int tryAgain()=0; + + // results + virtual QString mech() const=0; + virtual const QByteArray *clientInit() const=0; + virtual QByteArray result() const=0; + + // security layer + virtual bool encode(const QByteArray &in, QByteArray *out)=0; + virtual bool decode(const QByteArray &in, QByteArray *out)=0; +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.cpp new file mode 100644 index 00000000..6bd902d9 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.cpp @@ -0,0 +1,589 @@ +/* + * securestream.cpp - combines a ByteStream with TLS and SASL + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + Note: SecureStream depends on the underlying security layers to signal + plain-to-encrypted results immediately (as opposed to waiting for the + event loop) so that the user cannot add/remove security layers during + this conversion moment. QCA::TLS and QCA::SASL behave as expected, + but future layers might not. +*/ + +#include"securestream.h" + +#include +#include +#include + +#ifdef USE_TLSHANDLER +#include"xmpp.h" +#endif + +//---------------------------------------------------------------------------- +// LayerTracker +//---------------------------------------------------------------------------- +class LayerTracker +{ +public: + struct Item + { + int plain; + int encoded; + }; + + LayerTracker(); + + void reset(); + void addPlain(int plain); + void specifyEncoded(int encoded, int plain); + int finished(int encoded); + + int p; + QValueList list; +}; + +LayerTracker::LayerTracker() +{ + p = 0; +} + +void LayerTracker::reset() +{ + p = 0; + list.clear(); +} + +void LayerTracker::addPlain(int plain) +{ + p += plain; +} + +void LayerTracker::specifyEncoded(int encoded, int plain) +{ + // can't specify more bytes than we have + if(plain > p) + plain = p; + p -= plain; + Item i; + i.plain = plain; + i.encoded = encoded; + list += i; +} + +int LayerTracker::finished(int encoded) +{ + int plain = 0; + for(QValueList::Iterator it = list.begin(); it != list.end();) { + Item &i = *it; + + // not enough? + if(encoded < i.encoded) { + i.encoded -= encoded; + break; + } + + encoded -= i.encoded; + plain += i.plain; + it = list.remove(it); + } + return plain; +} + +//---------------------------------------------------------------------------- +// SecureStream +//---------------------------------------------------------------------------- +class SecureLayer : public QObject +{ + Q_OBJECT +public: + enum { TLS, SASL, TLSH }; + int type; + union { + QCA::TLS *tls; + QCA::SASL *sasl; +#ifdef USE_TLSHANDLER + XMPP::TLSHandler *tlsHandler; +#endif + } p; + LayerTracker layer; + bool tls_done; + int prebytes; + + SecureLayer(QCA::TLS *t) + { + type = TLS; + p.tls = t; + init(); + connect(p.tls, SIGNAL(handshaken()), SLOT(tls_handshaken())); + connect(p.tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); + connect(p.tls, SIGNAL(readyReadOutgoing(int)), SLOT(tls_readyReadOutgoing(int))); + connect(p.tls, SIGNAL(closed()), SLOT(tls_closed())); + connect(p.tls, SIGNAL(error(int)), SLOT(tls_error(int))); + } + + SecureLayer(QCA::SASL *s) + { + type = SASL; + p.sasl = s; + init(); + connect(p.sasl, SIGNAL(readyRead()), SLOT(sasl_readyRead())); + connect(p.sasl, SIGNAL(readyReadOutgoing(int)), SLOT(sasl_readyReadOutgoing(int))); + connect(p.sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + } + +#ifdef USE_TLSHANDLER + SecureLayer(XMPP::TLSHandler *t) + { + type = TLSH; + p.tlsHandler = t; + init(); + connect(p.tlsHandler, SIGNAL(success()), SLOT(tlsHandler_success())); + connect(p.tlsHandler, SIGNAL(fail()), SLOT(tlsHandler_fail())); + connect(p.tlsHandler, SIGNAL(closed()), SLOT(tlsHandler_closed())); + connect(p.tlsHandler, SIGNAL(readyRead(const QByteArray &)), SLOT(tlsHandler_readyRead(const QByteArray &))); + connect(p.tlsHandler, SIGNAL(readyReadOutgoing(const QByteArray &, int)), SLOT(tlsHandler_readyReadOutgoing(const QByteArray &, int))); + } +#endif + + void init() + { + tls_done = false; + prebytes = 0; + } + + void write(const QByteArray &a) + { + layer.addPlain(a.size()); + switch(type) { + case TLS: { p.tls->write(a); break; } + case SASL: { p.sasl->write(a); break; } +#ifdef USE_TLSHANDLER + case TLSH: { p.tlsHandler->write(a); break; } +#endif + } + } + + void writeIncoming(const QByteArray &a) + { + switch(type) { + case TLS: { p.tls->writeIncoming(a); break; } + case SASL: { p.sasl->writeIncoming(a); break; } +#ifdef USE_TLSHANDLER + case TLSH: { p.tlsHandler->writeIncoming(a); break; } +#endif + } + } + + int finished(int plain) + { + int written = 0; + + // deal with prebytes (bytes sent prior to this security layer) + if(prebytes > 0) { + if(prebytes >= plain) { + written += plain; + prebytes -= plain; + plain = 0; + } + else { + written += prebytes; + plain -= prebytes; + prebytes = 0; + } + } + + // put remainder into the layer tracker + if(type == SASL || tls_done) + written += layer.finished(plain); + + return written; + } + +signals: + void tlsHandshaken(); + void tlsClosed(const QByteArray &); + void readyRead(const QByteArray &); + void needWrite(const QByteArray &); + void error(int); + +private slots: + void tls_handshaken() + { + tls_done = true; + tlsHandshaken(); + } + + void tls_readyRead() + { + QByteArray a = p.tls->read(); + readyRead(a); + } + + void tls_readyReadOutgoing(int plainBytes) + { + QByteArray a = p.tls->readOutgoing(); + if(tls_done) + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); + } + + void tls_closed() + { + QByteArray a = p.tls->readUnprocessed(); + tlsClosed(a); + } + + void tls_error(int x) + { + error(x); + } + + void sasl_readyRead() + { + QByteArray a = p.sasl->read(); + readyRead(a); + } + + void sasl_readyReadOutgoing(int plainBytes) + { + QByteArray a = p.sasl->readOutgoing(); + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); + } + + void sasl_error(int x) + { + error(x); + } + +#ifdef USE_TLSHANDLER + void tlsHandler_success() + { + tls_done = true; + tlsHandshaken(); + } + + void tlsHandler_fail() + { + error(0); + } + + void tlsHandler_closed() + { + tlsClosed(QByteArray()); + } + + void tlsHandler_readyRead(const QByteArray &a) + { + readyRead(a); + } + + void tlsHandler_readyReadOutgoing(const QByteArray &a, int plainBytes) + { + if(tls_done) + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); + } +#endif +}; + +#include"securestream.moc" + +class SecureStream::Private +{ +public: + ByteStream *bs; + QPtrList layers; + int pending; + int errorCode; + bool active; + bool topInProgress; + + bool haveTLS() const + { + QPtrListIterator it(layers); + for(SecureLayer *s; (s = it.current()); ++it) { + if(s->type == SecureLayer::TLS +#ifdef USE_TLSHANDLER + || s->type == SecureLayer::TLSH +#endif + ) { + return true; + } + } + return false; + } + + bool haveSASL() const + { + QPtrListIterator it(layers); + for(SecureLayer *s; (s = it.current()); ++it) { + if(s->type == SecureLayer::SASL) + return true; + } + return false; + } +}; + +SecureStream::SecureStream(ByteStream *s) +:ByteStream(0) +{ + d = new Private; + + d->bs = s; + connect(d->bs, SIGNAL(readyRead()), SLOT(bs_readyRead())); + connect(d->bs, SIGNAL(bytesWritten(int)), SLOT(bs_bytesWritten(int))); + + d->layers.setAutoDelete(true); + d->pending = 0; + d->active = true; + d->topInProgress = false; +} + +SecureStream::~SecureStream() +{ + delete d; +} + +void SecureStream::linkLayer(QObject *s) +{ + connect(s, SIGNAL(tlsHandshaken()), SLOT(layer_tlsHandshaken())); + connect(s, SIGNAL(tlsClosed(const QByteArray &)), SLOT(layer_tlsClosed(const QByteArray &))); + connect(s, SIGNAL(readyRead(const QByteArray &)), SLOT(layer_readyRead(const QByteArray &))); + connect(s, SIGNAL(needWrite(const QByteArray &)), SLOT(layer_needWrite(const QByteArray &))); + connect(s, SIGNAL(error(int)), SLOT(layer_error(int))); +} + +int SecureStream::calcPrebytes() const +{ + int x = 0; + QPtrListIterator it(d->layers); + for(SecureLayer *s; (s = it.current()); ++it) + x += s->prebytes; + return (d->pending - x); +} + +void SecureStream::startTLSClient(QCA::TLS *t, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + insertData(spare); +} + +void SecureStream::startTLSServer(QCA::TLS *t, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + insertData(spare); +} + +void SecureStream::setLayerSASL(QCA::SASL *sasl, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveSASL()) + return; + + SecureLayer *s = new SecureLayer(sasl); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + + insertData(spare); +} + +#ifdef USE_TLSHANDLER +void SecureStream::startTLSClient(XMPP::TLSHandler *t, const QString &server, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + // unlike QCA::TLS, XMPP::TLSHandler has no return value + s->p.tlsHandler->startClient(server); + + insertData(spare); +} +#endif + +void SecureStream::closeTLS() +{ + SecureLayer *s = d->layers.getLast(); + if(s) { + if(s->type == SecureLayer::TLS) + s->p.tls->close(); + } +} + +int SecureStream::errorCode() const +{ + return d->errorCode; +} + +bool SecureStream::isOpen() const +{ + return d->active; +} + +void SecureStream::write(const QByteArray &a) +{ + if(!isOpen()) + return; + + d->pending += a.size(); + + // send to the last layer + SecureLayer *s = d->layers.getLast(); + if(s) + s->write(a); + else + writeRawData(a); +} + +int SecureStream::bytesToWrite() const +{ + return d->pending; +} + +void SecureStream::bs_readyRead() +{ + QByteArray a = d->bs->read(); + + // send to the first layer + SecureLayer *s = d->layers.getFirst(); + if(s) + s->writeIncoming(a); + else + incomingData(a); +} + +void SecureStream::bs_bytesWritten(int bytes) +{ + QPtrListIterator it(d->layers); + for(SecureLayer *s; (s = it.current()); ++it) + bytes = s->finished(bytes); + + if(bytes > 0) { + d->pending -= bytes; + bytesWritten(bytes); + } +} + +void SecureStream::layer_tlsHandshaken() +{ + d->topInProgress = false; + tlsHandshaken(); +} + +void SecureStream::layer_tlsClosed(const QByteArray &) +{ + d->active = false; + d->layers.clear(); + tlsClosed(); +} + +void SecureStream::layer_readyRead(const QByteArray &a) +{ + SecureLayer *s = (SecureLayer *)sender(); + QPtrListIterator it(d->layers); + while(it.current() != s) + ++it; + + // pass upwards + ++it; + s = it.current(); + if(s) + s->writeIncoming(a); + else + incomingData(a); +} + +void SecureStream::layer_needWrite(const QByteArray &a) +{ + SecureLayer *s = (SecureLayer *)sender(); + QPtrListIterator it(d->layers); + while(it.current() != s) + ++it; + + // pass downwards + --it; + s = it.current(); + if(s) + s->write(a); + else + writeRawData(a); +} + +void SecureStream::layer_error(int x) +{ + SecureLayer *s = (SecureLayer *)sender(); + int type = s->type; + d->errorCode = x; + d->active = false; + d->layers.clear(); + if(type == SecureLayer::TLS) + error(ErrTLS); + else if(type == SecureLayer::SASL) + error(ErrSASL); +#ifdef USE_TLSHANDLER + else if(type == SecureLayer::TLSH) + error(ErrTLS); +#endif +} + +void SecureStream::insertData(const QByteArray &a) +{ + if(!a.isEmpty()) { + SecureLayer *s = d->layers.getLast(); + if(s) + s->writeIncoming(a); + else + incomingData(a); + } +} + +void SecureStream::writeRawData(const QByteArray &a) +{ + d->bs->write(a); +} + +void SecureStream::incomingData(const QByteArray &a) +{ + appendRead(a); + if(bytesAvailable()) + readyRead(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.h new file mode 100644 index 00000000..c5787a2b --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.h @@ -0,0 +1,84 @@ +/* + * securestream.h - combines a ByteStream with TLS and SASL + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef SECURESTREAM_H +#define SECURESTREAM_H + +#include +#include"bytestream.h" + +#define USE_TLSHANDLER + +#ifdef USE_TLSHANDLER +namespace XMPP +{ + class TLSHandler; +} +#endif + +class SecureStream : public ByteStream +{ + Q_OBJECT +public: + enum Error { ErrTLS = ErrCustom, ErrSASL }; + SecureStream(ByteStream *s); + ~SecureStream(); + + void startTLSClient(QCA::TLS *t, const QByteArray &spare=QByteArray()); + void startTLSServer(QCA::TLS *t, const QByteArray &spare=QByteArray()); + void setLayerSASL(QCA::SASL *s, const QByteArray &spare=QByteArray()); +#ifdef USE_TLSHANDLER + void startTLSClient(XMPP::TLSHandler *t, const QString &server, const QByteArray &spare=QByteArray()); +#endif + + void closeTLS(); + int errorCode() const; + + // reimplemented + bool isOpen() const; + void write(const QByteArray &); + int bytesToWrite() const; + +signals: + void tlsHandshaken(); + void tlsClosed(); + +private slots: + void bs_readyRead(); + void bs_bytesWritten(int); + + void layer_tlsHandshaken(); + void layer_tlsClosed(const QByteArray &); + void layer_readyRead(const QByteArray &); + void layer_needWrite(const QByteArray &); + void layer_error(int); + +private: + void linkLayer(QObject *); + int calcPrebytes() const; + void insertData(const QByteArray &a); + void writeRawData(const QByteArray &a); + void incomingData(const QByteArray &a); + + class Private; + Private *d; +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.cpp new file mode 100644 index 00000000..54c4f405 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.cpp @@ -0,0 +1,459 @@ +/* + * simplesasl.cpp - Simple SASL implementation + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"simplesasl.h" + +#include +#include +#include +#include +#include +#include +#include"base64.h" + +namespace XMPP +{ + +struct Prop +{ + QCString var, val; +}; + +class PropList : public QValueList +{ +public: + PropList() : QValueList() + { + } + + void set(const QCString &var, const QCString &val) + { + Prop p; + p.var = var; + p.val = val; + append(p); + } + + QCString get(const QCString &var) + { + for(ConstIterator it = begin(); it != end(); ++it) { + if((*it).var == var) + return (*it).val; + } + return QCString(); + } + + QCString toString() const + { + QCString str; + bool first = true; + for(ConstIterator it = begin(); it != end(); ++it) { + if(!first) + str += ','; + str += (*it).var + "=\"" + (*it).val + '\"'; + first = false; + } + return str; + } + + bool fromString(const QCString &str) + { + PropList list; + int at = 0; + while(1) { + int n = str.find('=', at); + if(n == -1) + break; + QCString var, val; + var = str.mid(at, n-at); + at = n + 1; + if(str[at] == '\"') { + ++at; + n = str.find('\"', at); + if(n == -1) + break; + val = str.mid(at, n-at); + at = n + 1; + } + else { + n = str.find(',', at); + if(n != -1) { + val = str.mid(at, n-at); + at = n; + } + else { + val = str.mid(at); + at = str.length()-1; + } + } + Prop prop; + prop.var = var; + prop.val = val; + list.append(prop); + + if(str[at] != ',') + break; + ++at; + } + + // integrity check + if(list.varCount("nonce") != 1) + return false; + if(list.varCount("algorithm") != 1) + return false; + *this = list; + return true; + } + + int varCount(const QCString &var) + { + int n = 0; + for(ConstIterator it = begin(); it != end(); ++it) { + if((*it).var == var) + ++n; + } + return n; + } + + QStringList getValues(const QCString &var) + { + QStringList list; + for(ConstIterator it = begin(); it != end(); ++it) { + if((*it).var == var) + list += (*it).val; + } + return list; + } +}; + +class SimpleSASLContext : public QCA_SASLContext +{ +public: + // core props + QString service, host; + + // state + int step; + QByteArray in_buf; + QString out_mech; + QByteArray out_buf; + bool capable; + int err; + + QCA_SASLNeedParams need; + QCA_SASLNeedParams have; + QString user, authz, pass, realm; + + SimpleSASLContext() + { + reset(); + } + + ~SimpleSASLContext() + { + reset(); + } + + void reset() + { + resetState(); + resetParams(); + } + + void resetState() + { + out_mech = QString(); + out_buf.resize(0); + err = -1; + } + + void resetParams() + { + capable = true; + need.user = false; + need.authzid = false; + need.pass = false; + need.realm = false; + have.user = false; + have.authzid = false; + have.pass = false; + have.realm = false; + user = QString(); + authz = QString(); + pass = QString(); + realm = QString(); + } + + void setCoreProps(const QString &_service, const QString &_host, QCA_SASLHostPort *, QCA_SASLHostPort *) + { + service = _service; + host = _host; + } + + void setSecurityProps(bool, bool, bool, bool, bool reqForward, bool reqCreds, bool reqMutual, int ssfMin, int, const QString &, int) + { + if(reqForward || reqCreds || reqMutual || ssfMin > 0) + capable = false; + else + capable = true; + } + + int security() const + { + return 0; + } + + int errorCond() const + { + return err; + } + + bool clientStart(const QStringList &mechlist) + { + bool haveMech = false; + for(QStringList::ConstIterator it = mechlist.begin(); it != mechlist.end(); ++it) { + if((*it) == "DIGEST-MD5") { + haveMech = true; + break; + } + } + if(!capable || !haveMech) { + err = QCA::SASL::NoMech; + return false; + } + + resetState(); + step = 0; + return true; + } + + int clientFirstStep(bool) + { + return clientTryAgain(); + } + + bool serverStart(const QString &, QStringList *, const QString &) + { + return false; + } + + int serverFirstStep(const QString &, const QByteArray *) + { + return Error; + } + + QCA_SASLNeedParams clientParamsNeeded() const + { + return need; + } + + void setClientParams(const QString *_user, const QString *_authzid, const QString *_pass, const QString *_realm) + { + if(_user) { + user = *_user; + need.user = false; + have.user = true; + } + if(_authzid) { + authz = *_authzid; + need.authzid = false; + have.authzid = true; + } + if(_pass) { + pass = *_pass; + need.pass = false; + have.pass = true; + } + if(_realm) { + realm = *_realm; + need.realm = false; + have.realm = true; + } + } + + QString username() const + { + return QString(); + } + + QString authzid() const + { + return QString(); + } + + int nextStep(const QByteArray &in) + { + in_buf = in.copy(); + return tryAgain(); + } + + int tryAgain() + { + return clientTryAgain(); + } + + QString mech() const + { + return out_mech; + } + + const QByteArray *clientInit() const + { + return 0; + } + + QByteArray result() const + { + return out_buf; + } + + int clientTryAgain() + { + if(step == 0) { + out_mech = "DIGEST-MD5"; + ++step; + return Continue; + } + else if(step == 1) { + // if we still need params, then the app has failed us! + if(need.user || need.authzid || need.pass || need.realm) { + err = -1; + return Error; + } + + // see if some params are needed + if(!have.user) + need.user = true; + if(!have.authzid) + need.authzid = true; + if(!have.pass) + need.pass = true; + if(need.user || need.authzid || need.pass) + return NeedParams; + + // get props + QCString cs(in_buf.data(), in_buf.size()+1); + PropList in; + if(!in.fromString(cs)) { + err = QCA::SASL::BadProto; + return Error; + } + + // make a cnonce + QByteArray a(32); + for(int n = 0; n < (int)a.size(); ++n) + a[n] = (char)(256.0*rand()/(RAND_MAX+1.0)); + QCString cnonce = Base64::arrayToString(a).latin1(); + + // make other variables + realm = host; + QCString nonce = in.get("nonce"); + QCString nc = "00000001"; + QCString uri = service.utf8() + '/' + host.utf8(); + QCString qop = "auth"; + + // build 'response' + QCString X = user.utf8() + ':' + realm.utf8() + ':' + pass.utf8(); + QByteArray Y = QCA::MD5::hash(X); + QCString tmp = QCString(":") + nonce + ':' + cnonce + ':' + authz.utf8(); + QByteArray A1(Y.size() + tmp.length()); + memcpy(A1.data(), Y.data(), Y.size()); + memcpy(A1.data() + Y.size(), tmp.data(), tmp.length()); + QCString A2 = "AUTHENTICATE:" + uri; + QCString HA1 = QCA::MD5::hashToString(A1).latin1(); + QCString HA2 = QCA::MD5::hashToString(A2).latin1(); + QCString KD = HA1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + HA2; + QCString Z = QCA::MD5::hashToString(KD).latin1(); + + // build output + PropList out; + out.set("username", user.utf8()); + out.set("realm", host.utf8()); + out.set("nonce", nonce); + out.set("cnonce", cnonce); + out.set("nc", nc); + out.set("serv-type", service.utf8()); + out.set("host", host.utf8()); + out.set("digest-uri", uri); + out.set("qop", qop); + out.set("response", Z); + out.set("charset", "utf-8"); + out.set("authzid", authz.utf8()); + QCString s = out.toString(); + + // done + out_buf.resize(s.length()); + memcpy(out_buf.data(), s.data(), out_buf.size()); + ++step; + return Continue; + } + else { + out_buf.resize(0); + return Success; + } + } + + bool encode(const QByteArray &a, QByteArray *b) + { + *b = a.copy(); + return true; + } + + bool decode(const QByteArray &a, QByteArray *b) + { + *b = a.copy(); + return true; + } +}; + +class QCASimpleSASL : public QCAProvider +{ +public: + QCASimpleSASL() {} + ~QCASimpleSASL() {} + + void init() + { + } + + int qcaVersion() const + { + return QCA_PLUGIN_VERSION; + } + + int capabilities() const + { + return QCA::CAP_SASL; + } + + void *context(int cap) + { + if(cap == QCA::CAP_SASL) + return new SimpleSASLContext; + return 0; + } +}; + +QCAProvider *createProviderSimpleSASL() +{ + return (new QCASimpleSASL); +} + +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.h new file mode 100644 index 00000000..12a08c0e --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.h @@ -0,0 +1,31 @@ +/* + * simplesasl.h - Simple SASL implementation + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef SIMPLESASL_H +#define SIMPLESASL_H + +#include"qcaprovider.h" + +namespace XMPP +{ + QCAProvider *createProviderSimpleSASL(); +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp new file mode 100644 index 00000000..bfcc218c --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp @@ -0,0 +1,1762 @@ +/* + * stream.cpp - handles a client stream + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + Notes: + - For Non-SASL auth (JEP-0078), username and resource fields are required. + + TODO: + - sasl needParams is totally jacked? PLAIN requires authzid, etc + - server error handling + - reply with protocol errors if the client send something wrong + - don't necessarily disconnect on protocol error. prepare for more. + - server function + - deal with stream 'to' attribute dynamically + - flag tls/sasl/binding support dynamically (have the ability to specify extra stream:features) + - inform the caller about the user authentication information + - sasl security settings + - resource-binding interaction + - timeouts + - allow exchanges of non-standard stanzas + - send even if we close prematurely? + - ensure ClientStream and child classes are fully deletable after signals + - xml:lang in root () element + - sasl external + - sasl anonymous +*/ + +#include"xmpp.h" + +#include +#include +#include +#include +#include +#include"bytestream.h" +#include"base64.h" +#include"hash.h" +#include"simplesasl.h" +#include"securestream.h" +#include"protocol.h" + +#ifdef XMPP_TEST +#include"td.h" +#endif + +//#define XMPP_DEBUG + +using namespace XMPP; + +static Debug *debug_ptr = 0; +void XMPP::setDebug(Debug *p) +{ + debug_ptr = p; +} + +static QByteArray randomArray(int size) +{ + QByteArray a(size); + for(int n = 0; n < size; ++n) + a[n] = (char)(256.0*rand()/(RAND_MAX+1.0)); + return a; +} + +static QString genId() +{ + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + return QCA::SHA1::hashToString(randomArray(128)); +} + +//---------------------------------------------------------------------------- +// Stanza +//---------------------------------------------------------------------------- +Stanza::Error::Error(int _type, int _condition, const QString &_text, const QDomElement &_appSpec) +{ + type = _type; + condition = _condition; + text = _text; + appSpec = _appSpec; +} + +class Stanza::Private +{ +public: + struct ErrorTypeEntry + { + const char *str; + int type; + }; + static ErrorTypeEntry errorTypeTable[]; + + struct ErrorCondEntry + { + const char *str; + int cond; + }; + static ErrorCondEntry errorCondTable[]; + + static int stringToKind(const QString &s) + { + if(s == "message") + return Message; + else if(s == "presence") + return Presence; + else if(s == "iq") + return IQ; + else + return -1; + } + + static QString kindToString(Kind k) + { + if(k == Message) + return "message"; + else if(k == Presence) + return "presence"; + else + return "iq"; + } + + static int stringToErrorType(const QString &s) + { + for(int n = 0; errorTypeTable[n].str; ++n) { + if(s == errorTypeTable[n].str) + return errorTypeTable[n].type; + } + return -1; + } + + static QString errorTypeToString(int x) + { + for(int n = 0; errorTypeTable[n].str; ++n) { + if(x == errorTypeTable[n].type) + return errorTypeTable[n].str; + } + return QString(); + } + + static int stringToErrorCond(const QString &s) + { + for(int n = 0; errorCondTable[n].str; ++n) { + if(s == errorCondTable[n].str) + return errorCondTable[n].cond; + } + return -1; + } + + static QString errorCondToString(int x) + { + for(int n = 0; errorCondTable[n].str; ++n) { + if(x == errorCondTable[n].cond) + return errorCondTable[n].str; + } + return QString(); + } + + Stream *s; + QDomElement e; +}; + +Stanza::Private::ErrorTypeEntry Stanza::Private::errorTypeTable[] = +{ + { "cancel", Cancel }, + { "continue", Continue }, + { "modify", Modify }, + { "auth", Auth }, + { "wait", Wait }, + { 0, 0 }, +}; + +Stanza::Private::ErrorCondEntry Stanza::Private::errorCondTable[] = +{ + { "bad-request", BadRequest }, + { "conflict", Conflict }, + { "feature-not-implemented", FeatureNotImplemented }, + { "forbidden", Forbidden }, + { "internal-server-error", InternalServerError }, + { "item-not-found", ItemNotFound }, + { "jid-malformed", JidMalformed }, + { "not-allowed", NotAllowed }, + { "payment-required", PaymentRequired }, + { "recipient-unavailable", RecipientUnavailable }, + { "registration-required", RegistrationRequired }, + { "remote-server-not-found", ServerNotFound }, + { "remote-server-timeout", ServerTimeout }, + { "resource-constraint", ResourceConstraint }, + { "service-unavailable", ServiceUnavailable }, + { "subscription-required", SubscriptionRequired }, + { "undefined-condition", UndefinedCondition }, + { "unexpected-request", UnexpectedRequest }, + { 0, 0 }, +}; + +Stanza::Stanza() +{ + d = 0; +} + +Stanza::Stanza(Stream *s, Kind k, const Jid &to, const QString &type, const QString &id) +{ + d = new Private; + + Kind kind; + if(k == Message || k == Presence || k == IQ) + kind = k; + else + kind = Message; + + d->s = s; + d->e = d->s->doc().createElementNS(s->baseNS(), Private::kindToString(kind)); + if(to.isValid()) + setTo(to); + if(!type.isEmpty()) + setType(type); + if(!id.isEmpty()) + setId(id); +} + +Stanza::Stanza(Stream *s, const QDomElement &e) +{ + d = 0; + if(e.namespaceURI() != s->baseNS()) + return; + int x = Private::stringToKind(e.tagName()); + if(x == -1) + return; + d = new Private; + d->s = s; + d->e = e; +} + +Stanza::Stanza(const Stanza &from) +{ + d = 0; + *this = from; +} + +Stanza & Stanza::operator=(const Stanza &from) +{ + delete d; + d = 0; + if(from.d) + d = new Private(*from.d); + return *this; +} + +Stanza::~Stanza() +{ + delete d; +} + +bool Stanza::isNull() const +{ + return (d ? false: true); +} + +QDomElement Stanza::element() const +{ + return d->e; +} + +QString Stanza::toString() const +{ + return Stream::xmlToString(d->e); +} + +QDomDocument & Stanza::doc() const +{ + return d->s->doc(); +} + +QString Stanza::baseNS() const +{ + return d->s->baseNS(); +} + +QString Stanza::xhtmlImNS() const +{ + return d->s->xhtmlImNS(); +} + +QString Stanza::xhtmlNS() const +{ + return d->s->xhtmlNS(); +} + +QDomElement Stanza::createElement(const QString &ns, const QString &tagName) +{ + return d->s->doc().createElementNS(ns, tagName); +} + +QDomElement Stanza::createTextElement(const QString &ns, const QString &tagName, const QString &text) +{ + QDomElement e = d->s->doc().createElementNS(ns, tagName); + e.appendChild(d->s->doc().createTextNode(text)); + return e; +} + +QDomElement Stanza::createXHTMLElement(const QString &xHTML) +{ + QDomDocument doc; + + doc.setContent(xHTML, true); + QDomElement root = doc.documentElement(); + //QDomElement e; + return (root); +} + +void Stanza::appendChild(const QDomElement &e) +{ + d->e.appendChild(e); +} + +Stanza::Kind Stanza::kind() const +{ + return (Kind)Private::stringToKind(d->e.tagName()); +} + +void Stanza::setKind(Kind k) +{ + d->e.setTagName(Private::kindToString(k)); +} + +Jid Stanza::to() const +{ + return Jid(d->e.attribute("to")); +} + +Jid Stanza::from() const +{ + return Jid(d->e.attribute("from")); +} + +QString Stanza::id() const +{ + return d->e.attribute("id"); +} + +QString Stanza::type() const +{ + return d->e.attribute("type"); +} + +QString Stanza::lang() const +{ + return d->e.attributeNS(NS_XML, "lang", QString()); +} + +void Stanza::setTo(const Jid &j) +{ + d->e.setAttribute("to", j.full()); +} + +void Stanza::setFrom(const Jid &j) +{ + d->e.setAttribute("from", j.full()); +} + +void Stanza::setId(const QString &id) +{ + d->e.setAttribute("id", id); +} + +void Stanza::setType(const QString &type) +{ + d->e.setAttribute("type", type); +} + +void Stanza::setLang(const QString &lang) +{ + d->e.setAttribute("xml:lang", lang); +} + +Stanza::Error Stanza::error() const +{ + Error err; + QDomElement e = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(e.isNull()) + return err; + + // type + int x = Private::stringToErrorType(e.attribute("type")); + if(x != -1) + err.type = x; + + // condition: find first element + QDomNodeList nl = e.childNodes(); + QDomElement t; + uint n; + for(n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement()) { + t = i.toElement(); + break; + } + } + if(!t.isNull() && t.namespaceURI() == NS_STANZAS) { + x = Private::stringToErrorCond(t.tagName()); + if(x != -1) + err.condition = x; + } + + // text + t = e.elementsByTagNameNS(NS_STANZAS, "text").item(0).toElement(); + if(!t.isNull()) + err.text = t.text(); + else + err.text = e.text(); + + // appspec: find first non-standard namespaced element + nl = e.childNodes(); + for(n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement() && i.namespaceURI() != NS_STANZAS) { + err.appSpec = i.toElement(); + break; + } + } + return err; +} + +void Stanza::setError(const Error &err) +{ + // create the element if necessary + QDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(errElem.isNull()) { + errElem = d->e.ownerDocument().createElementNS(d->s->baseNS(), "error"); + d->e.appendChild(errElem); + } + + // error type/condition + if(d->s->old()) { + errElem.setAttribute("code", QString::number(err.condition)); + } + else { + QString stype = Private::errorTypeToString(err.type); + if(stype.isEmpty()) + return; + QString scond = Private::errorCondToString(err.condition); + if(scond.isEmpty()) + return; + + errElem.setAttribute("type", stype); + errElem.appendChild(d->e.ownerDocument().createElementNS(d->s->baseNS(), scond)); + } + + // text + if(d->s->old()) { + errElem.appendChild(d->e.ownerDocument().createTextNode(err.text)); + } + else { + QDomElement te = d->e.ownerDocument().createElementNS(d->s->baseNS(), "text"); + te.appendChild(d->e.ownerDocument().createTextNode(err.text)); + errElem.appendChild(te); + } + + // application specific + errElem.appendChild(err.appSpec); +} + +void Stanza::clearError() +{ + QDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(!errElem.isNull()) + d->e.removeChild(errElem); +} + +//---------------------------------------------------------------------------- +// Stream +//---------------------------------------------------------------------------- +static XmlProtocol *foo = 0; +Stream::Stream(QObject *parent) +:QObject(parent) +{ +} + +Stream::~Stream() +{ +} + +Stanza Stream::createStanza(Stanza::Kind k, const Jid &to, const QString &type, const QString &id) +{ + return Stanza(this, k, to, type, id); +} + +Stanza Stream::createStanza(const QDomElement &e) +{ + return Stanza(this, e); +} + +QString Stream::xmlToString(const QDomElement &e, bool clip) +{ + if(!foo) + foo = new CoreProtocol; + return foo->elementToString(e, clip); +} + +//---------------------------------------------------------------------------- +// ClientStream +//---------------------------------------------------------------------------- +enum { + Idle, + Connecting, + WaitVersion, + WaitTLS, + NeedParams, + Active, + Closing +}; + +enum { + Client, + Server +}; + +class ClientStream::Private +{ +public: + Private() + { + conn = 0; + bs = 0; + ss = 0; + tlsHandler = 0; + tls = 0; + sasl = 0; + in.setAutoDelete(true); + + oldOnly = false; + allowPlain = false; + mutualAuth = false; + haveLocalAddr = false; + minimumSSF = 0; + maximumSSF = 0; + doBinding = true; + + in_rrsig = false; + + reset(); + } + + void reset() + { + state = Idle; + notify = 0; + newStanzas = false; + sasl_ssf = 0; + tls_warned = false; + using_tls = false; + } + + Jid jid; + QString server; + bool oldOnly; + bool allowPlain, mutualAuth; + bool haveLocalAddr; + QHostAddress localAddr; + Q_UINT16 localPort; + int minimumSSF, maximumSSF; + QString sasl_mech; + bool doBinding; + + bool in_rrsig; + + Connector *conn; + ByteStream *bs; + TLSHandler *tlsHandler; + QCA::TLS *tls; + QCA::SASL *sasl; + SecureStream *ss; + CoreProtocol client; + CoreProtocol srv; + + QString defRealm; + + int mode; + int state; + int notify; + bool newStanzas; + int sasl_ssf; + bool tls_warned, using_tls; + bool doAuth; + + QStringList sasl_mechlist; + + int errCond; + QString errText; + QDomElement errAppSpec; + + QPtrList in; + + QTimer noopTimer; + int noop_time; +}; + +ClientStream::ClientStream(Connector *conn, TLSHandler *tlsHandler, QObject *parent) +:Stream(parent) +{ + d = new Private; + d->mode = Client; + d->conn = conn; + connect(d->conn, SIGNAL(connected()), SLOT(cr_connected())); + connect(d->conn, SIGNAL(error()), SLOT(cr_error())); + + d->noop_time = 0; + connect(&d->noopTimer, SIGNAL(timeout()), SLOT(doNoop())); + + d->tlsHandler = tlsHandler; +} + +ClientStream::ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls, QObject *parent) +:Stream(parent) +{ + d = new Private; + d->mode = Server; + d->bs = bs; + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); + connect(d->bs, SIGNAL(error(int)), SLOT(bs_error(int))); + + QByteArray spare = d->bs->read(); + + d->ss = new SecureStream(d->bs); + connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); + connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int))); + connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken())); + connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed())); + connect(d->ss, SIGNAL(error(int)), SLOT(ss_error(int))); + + d->server = host; + d->defRealm = defRealm; + + d->tls = tls; + + d->srv.startClientIn(genId()); + //d->srv.startServerIn(genId()); + //d->state = Connecting; + //d->jid = Jid(); + //d->server = QString(); +} + +ClientStream::~ClientStream() +{ + reset(); + delete d; +} + +void ClientStream::reset(bool all) +{ + d->reset(); + d->noopTimer.stop(); + + // delete securestream + delete d->ss; + d->ss = 0; + + // reset sasl + delete d->sasl; + d->sasl = 0; + + // client + if(d->mode == Client) { + // reset tls + if(d->tlsHandler) + d->tlsHandler->reset(); + + // reset connector + if(d->bs) { + d->bs->close(); + d->bs = 0; + } + d->conn->done(); + + // reset state machine + d->client.reset(); + } + // server + else { + if(d->tls) + d->tls->reset(); + + if(d->bs) { + d->bs->close(); + d->bs = 0; + } + + d->srv.reset(); + } + + if(all) + d->in.clear(); +} + +Jid ClientStream::jid() const +{ + return d->jid; +} + +void ClientStream::connectToServer(const Jid &jid, bool auth) +{ + reset(true); + d->state = Connecting; + d->jid = jid; + d->doAuth = auth; + d->server = d->jid.domain(); + + d->conn->connectToServer(d->server); +} + +void ClientStream::continueAfterWarning() +{ + if(d->state == WaitVersion) { + // if we don't have TLS yet, then we're never going to get it + if(!d->tls_warned && !d->using_tls) { + d->tls_warned = true; + d->state = WaitTLS; + warning(WarnNoTLS); + return; + } + d->state = Connecting; + processNext(); + } + else if(d->state == WaitTLS) { + d->state = Connecting; + processNext(); + } +} + +void ClientStream::accept() +{ + d->srv.host = d->server; + processNext(); +} + +bool ClientStream::isActive() const +{ + return (d->state != Idle) ? true: false; +} + +bool ClientStream::isAuthenticated() const +{ + return (d->state == Active) ? true: false; +} + +void ClientStream::setUsername(const QString &s) +{ + if(d->sasl) + d->sasl->setUsername(s); +} + +void ClientStream::setPassword(const QString &s) +{ + if(d->client.old) { + d->client.setPassword(s); + } + else { + if(d->sasl) + d->sasl->setPassword(s); + } +} + +void ClientStream::setRealm(const QString &s) +{ + if(d->sasl) + d->sasl->setRealm(s); +} + +void ClientStream::continueAfterParams() +{ + if(d->state == NeedParams) { + d->state = Connecting; + if(d->client.old) { + processNext(); + } + else { + if(d->sasl) + d->sasl->continueAfterParams(); + } + } +} + +void ClientStream::setResourceBinding(bool b) +{ + d->doBinding = b; +} + +void ClientStream::setNoopTime(int mills) +{ + d->noop_time = mills; + + if(d->state != Active) + return; + + if(d->noop_time == 0) { + d->noopTimer.stop(); + return; + } + d->noopTimer.start(d->noop_time); +} + +QString ClientStream::saslMechanism() const +{ + return d->client.saslMech(); +} + +int ClientStream::saslSSF() const +{ + return d->sasl_ssf; +} + +void ClientStream::setSASLMechanism(const QString &s) +{ + d->sasl_mech = s; +} + +void ClientStream::setLocalAddr(const QHostAddress &addr, Q_UINT16 port) +{ + d->haveLocalAddr = true; + d->localAddr = addr; + d->localPort = port; +} + +int ClientStream::errorCondition() const +{ + return d->errCond; +} + +QString ClientStream::errorText() const +{ + return d->errText; +} + +QDomElement ClientStream::errorAppSpec() const +{ + return d->errAppSpec; +} + +bool ClientStream::old() const +{ + return d->client.old; +} + +void ClientStream::close() +{ + if(d->state == Active) { + d->state = Closing; + d->client.shutdown(); + processNext(); + } + else if(d->state != Idle && d->state != Closing) { + reset(); + } +} + +QDomDocument & ClientStream::doc() const +{ + return d->client.doc; +} + +QString ClientStream::baseNS() const +{ + return NS_CLIENT; +} + +QString ClientStream::xhtmlImNS() const +{ + return NS_XHTML_IM; +} + +QString ClientStream::xhtmlNS() const +{ + return NS_XHTML; +} + +void ClientStream::setAllowPlain(bool b) +{ + d->allowPlain = b; +} + +void ClientStream::setRequireMutualAuth(bool b) +{ + d->mutualAuth = b; +} + +void ClientStream::setSSFRange(int low, int high) +{ + d->minimumSSF = low; + d->maximumSSF = high; +} + +void ClientStream::setOldOnly(bool b) +{ + d->oldOnly = b; +} + +bool ClientStream::stanzaAvailable() const +{ + return (!d->in.isEmpty()); +} + +Stanza ClientStream::read() +{ + if(d->in.isEmpty()) + return Stanza(); + else { + Stanza *sp = d->in.getFirst(); + Stanza s = *sp; + d->in.removeRef(sp); + return s; + } +} + +void ClientStream::write(const Stanza &s) +{ + if(d->state == Active) { + d->client.sendStanza(s.element()); + processNext(); + } +} + +void ClientStream::cr_connected() +{ + d->bs = d->conn->stream(); + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); + + QByteArray spare = d->bs->read(); + + d->ss = new SecureStream(d->bs); + connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); + connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int))); + connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken())); + connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed())); + connect(d->ss, SIGNAL(error(int)), SLOT(ss_error(int))); + + //d->client.startDialbackOut("andbit.net", "im.pyxa.org"); + //d->client.startServerOut(d->server); + + d->client.startClientOut(d->jid, d->oldOnly, d->conn->useSSL(), d->doAuth); + d->client.setAllowTLS(d->tlsHandler ? true: false); + d->client.setAllowBind(d->doBinding); + d->client.setAllowPlain(d->allowPlain); + + /*d->client.jid = d->jid; + d->client.server = d->server; + d->client.allowPlain = d->allowPlain; + d->client.oldOnly = d->oldOnly; + d->client.sasl_mech = d->sasl_mech; + d->client.doTLS = d->tlsHandler ? true: false; + d->client.doBinding = d->doBinding;*/ + + QGuardedPtr self = this; + connected(); + if(!self) + return; + + // immediate SSL? + if(d->conn->useSSL()) { + d->using_tls = true; + d->ss->startTLSClient(d->tlsHandler, d->server, spare); + } + else { + d->client.addIncomingData(spare); + processNext(); + } +} + +void ClientStream::cr_error() +{ + reset(); + error(ErrConnection); +} + +void ClientStream::bs_connectionClosed() +{ + reset(); + connectionClosed(); +} + +void ClientStream::bs_delayedCloseFinished() +{ + // we don't care about this (we track all important data ourself) +} + +void ClientStream::bs_error(int) +{ + // TODO +} + +void ClientStream::ss_readyRead() +{ + QByteArray a = d->ss->read(); + +#ifdef XMPP_DEBUG + QCString cs(a.data(), a.size()+1); + fprintf(stderr, "ClientStream: recv: %d [%s]\n", a.size(), cs.data()); +#endif + + if(d->mode == Client) + d->client.addIncomingData(a); + else + d->srv.addIncomingData(a); + if(d->notify & CoreProtocol::NRecv) { +#ifdef XMPP_DEBUG + printf("We needed data, so let's process it\n"); +#endif + processNext(); + } +} + +void ClientStream::ss_bytesWritten(int bytes) +{ + if(d->mode == Client) + d->client.outgoingDataWritten(bytes); + else + d->srv.outgoingDataWritten(bytes); + + if(d->notify & CoreProtocol::NSend) { +#ifdef XMPP_DEBUG + printf("We were waiting for data to be written, so let's process\n"); +#endif + processNext(); + } +} + +void ClientStream::ss_tlsHandshaken() +{ + QGuardedPtr self = this; + securityLayerActivated(LayerTLS); + if(!self) + return; + processNext(); +} + +void ClientStream::ss_tlsClosed() +{ + reset(); + connectionClosed(); +} + +void ClientStream::ss_error(int x) +{ + if(x == SecureStream::ErrTLS) { + reset(); + d->errCond = TLSFail; + error(ErrTLS); + } + else { + reset(); + error(ErrSecurityLayer); + } +} + +void ClientStream::sasl_clientFirstStep(const QString &mech, const QByteArray *stepData) +{ + d->client.setSASLFirst(mech, stepData ? *stepData : QByteArray()); + //d->client.sasl_mech = mech; + //d->client.sasl_firstStep = stepData ? true : false; + //d->client.sasl_step = stepData ? *stepData : QByteArray(); + + processNext(); +} + +void ClientStream::sasl_nextStep(const QByteArray &stepData) +{ + if(d->mode == Client) + d->client.setSASLNext(stepData); + //d->client.sasl_step = stepData; + else + d->srv.setSASLNext(stepData); + //d->srv.sasl_step = stepData; + + processNext(); +} + +void ClientStream::sasl_needParams(bool user, bool authzid, bool pass, bool realm) +{ +#ifdef XMPP_DEBUG + printf("need params: %d,%d,%d,%d\n", user, authzid, pass, realm); +#endif + if(authzid && !user) { + d->sasl->setAuthzid(d->jid.bare()); + //d->sasl->setAuthzid("infiniti.homelesshackers.org"); + } + if(user || pass || realm) { + d->state = NeedParams; + needAuthParams(user, pass, realm); + } + else + d->sasl->continueAfterParams(); +} + +void ClientStream::sasl_authCheck(const QString &user, const QString &) +{ +//#ifdef XMPP_DEBUG +// printf("authcheck: [%s], [%s]\n", user.latin1(), authzid.latin1()); +//#endif + QString u = user; + int n = u.find('@'); + if(n != -1) + u.truncate(n); + d->srv.user = u; + d->sasl->continueAfterAuthCheck(); +} + +void ClientStream::sasl_authenticated() +{ +#ifdef XMPP_DEBUG + printf("sasl authed!!\n"); +#endif + d->sasl_ssf = d->sasl->ssf(); + + if(d->mode == Server) { + d->srv.setSASLAuthed(); + processNext(); + } +} + +void ClientStream::sasl_error(int) +{ +//#ifdef XMPP_DEBUG +// printf("sasl error: %d\n", c); +//#endif + // has to be auth error + int x = convertedSASLCond(); + reset(); + d->errCond = x; + error(ErrAuth); +} + +void ClientStream::srvProcessNext() +{ + while(1) { + printf("Processing step...\n"); + if(!d->srv.processStep()) { + int need = d->srv.need; + if(need == CoreProtocol::NNotify) { + d->notify = d->srv.notify; + if(d->notify & CoreProtocol::NSend) + printf("More data needs to be written to process next step\n"); + if(d->notify & CoreProtocol::NRecv) + printf("More data is needed to process next step\n"); + } + else if(need == CoreProtocol::NSASLMechs) { + if(!d->sasl) { + d->sasl = new QCA::SASL; + connect(d->sasl, SIGNAL(authCheck(const QString &, const QString &)), SLOT(sasl_authCheck(const QString &, const QString &))); + connect(d->sasl, SIGNAL(nextStep(const QByteArray &)), SLOT(sasl_nextStep(const QByteArray &))); + connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); + connect(d->sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + + //d->sasl->setAllowAnonymous(false); + //d->sasl->setRequirePassCredentials(true); + //d->sasl->setExternalAuthID("localhost"); + + d->sasl->setMinimumSSF(0); + d->sasl->setMaximumSSF(256); + + QStringList list; + // TODO: d->server is probably wrong here + if(!d->sasl->startServer("xmpp", d->server, d->defRealm, &list)) { + printf("Error initializing SASL\n"); + return; + } + d->sasl_mechlist = list; + } + d->srv.setSASLMechList(d->sasl_mechlist); + continue; + } + else if(need == CoreProtocol::NStartTLS) { + printf("Need StartTLS\n"); + if(!d->tls->startServer()) { + printf("unable to start server!\n"); + // TODO + return; + } + QByteArray a = d->srv.spare; + d->ss->startTLSServer(d->tls, a); + } + else if(need == CoreProtocol::NSASLFirst) { + printf("Need SASL First Step\n"); + QByteArray a = d->srv.saslStep(); + d->sasl->putServerFirstStep(d->srv.saslMech(), a); + } + else if(need == CoreProtocol::NSASLNext) { + printf("Need SASL Next Step\n"); + QByteArray a = d->srv.saslStep(); + QCString cs(a.data(), a.size()+1); + printf("[%s]\n", cs.data()); + d->sasl->putStep(a); + } + else if(need == CoreProtocol::NSASLLayer) { + } + + // now we can announce stanzas + //if(!d->in.isEmpty()) + // readyRead(); + return; + } + + d->notify = 0; + + int event = d->srv.event; + printf("event: %d\n", event); + switch(event) { + case CoreProtocol::EError: { + printf("Error! Code=%d\n", d->srv.errorCode); + reset(); + error(ErrProtocol); + //handleError(); + return; + } + case CoreProtocol::ESend: { + QByteArray a = d->srv.takeOutgoingData(); + QCString cs(a.size()+1); + memcpy(cs.data(), a.data(), a.size()); + printf("Need Send: {%s}\n", cs.data()); + d->ss->write(a); + break; + } + case CoreProtocol::ERecvOpen: { + printf("Break (RecvOpen)\n"); + + // calculate key + QCString str = QCA::SHA1::hashToString("secret").utf8(); + str = QCA::SHA1::hashToString(str + "im.pyxa.org").utf8(); + str = QCA::SHA1::hashToString(str + d->srv.id.utf8()).utf8(); + d->srv.setDialbackKey(str); + + //d->srv.setDialbackKey("3c5d721ea2fcc45b163a11420e4e358f87e3142a"); + + if(d->srv.to != d->server) { + // host-gone, host-unknown, see-other-host + d->srv.shutdownWithError(CoreProtocol::HostUnknown); + } + else + d->srv.setFrom(d->server); + break; + } + case CoreProtocol::ESASLSuccess: { + printf("Break SASL Success\n"); + disconnect(d->sasl, SIGNAL(error(int)), this, SLOT(sasl_error(int))); + QByteArray a = d->srv.spare; + d->ss->setLayerSASL(d->sasl, a); + break; + } + case CoreProtocol::EPeerClosed: { + // TODO: this isn' an error + printf("peer closed\n"); + reset(); + error(ErrProtocol); + return; + } + } + } +} + +void ClientStream::doReadyRead() +{ + //QGuardedPtr self = this; + readyRead(); + //if(!self) + // return; + //d->in_rrsig = false; +} + +void ClientStream::processNext() +{ + if(d->mode == Server) { + srvProcessNext(); + return; + } + + QGuardedPtr self = this; + + while(1) { +#ifdef XMPP_DEBUG + printf("Processing step...\n"); +#endif + bool ok = d->client.processStep(); + // deal with send/received items + for(QValueList::ConstIterator it = d->client.transferItemList.begin(); it != d->client.transferItemList.end(); ++it) { + const XmlProtocol::TransferItem &i = *it; + if(i.isExternal) + continue; + QString str; + if(i.isString) { + // skip whitespace pings + if(i.str.stripWhiteSpace().isEmpty()) + continue; + str = i.str; + } + else + str = d->client.elementToString(i.elem); + if(i.isSent) + outgoingXml(str); + else + incomingXml(str); + } + + if(!ok) { + bool cont = handleNeed(); + + // now we can announce stanzas + //if(!d->in_rrsig && !d->in.isEmpty()) { + if(!d->in.isEmpty()) { + //d->in_rrsig = true; + QTimer::singleShot(0, this, SLOT(doReadyRead())); + } + + if(cont) + continue; + return; + } + + int event = d->client.event; + d->notify = 0; + switch(event) { + case CoreProtocol::EError: { +#ifdef XMPP_DEBUG + printf("Error! Code=%d\n", d->client.errorCode); +#endif + handleError(); + return; + } + case CoreProtocol::ESend: { + QByteArray a = d->client.takeOutgoingData(); +#ifdef XMPP_DEBUG + QCString cs(a.size()+1); + memcpy(cs.data(), a.data(), a.size()); + printf("Need Send: {%s}\n", cs.data()); +#endif + d->ss->write(a); + break; + } + case CoreProtocol::ERecvOpen: { +#ifdef XMPP_DEBUG + printf("Break (RecvOpen)\n"); +#endif + +#ifdef XMPP_TEST + QString s = QString("handshake success (lang=[%1]").arg(d->client.lang); + if(!d->client.from.isEmpty()) + s += QString(", from=[%1]").arg(d->client.from); + s += ')'; + TD::msg(s); +#endif + + if(d->client.old) { + d->state = WaitVersion; + warning(WarnOldVersion); + return; + } + break; + } + case CoreProtocol::EFeatures: { +#ifdef XMPP_DEBUG + printf("Break (Features)\n"); +#endif + if(!d->tls_warned && !d->using_tls && !d->client.features.tls_supported) { + d->tls_warned = true; + d->state = WaitTLS; + warning(WarnNoTLS); + return; + } + break; + } + case CoreProtocol::ESASLSuccess: { +#ifdef XMPP_DEBUG + printf("Break SASL Success\n"); +#endif + break; + } + case CoreProtocol::EReady: { +#ifdef XMPP_DEBUG + printf("Done!\n"); +#endif + // grab the JID, in case it changed + // TODO: d->jid = d->client.jid; + d->state = Active; + setNoopTime(d->noop_time); + authenticated(); + if(!self) + return; + break; + } + case CoreProtocol::EPeerClosed: { +#ifdef XMPP_DEBUG + printf("DocumentClosed\n"); +#endif + reset(); + connectionClosed(); + return; + } + case CoreProtocol::EStanzaReady: { +#ifdef XMPP_DEBUG + printf("StanzaReady\n"); +#endif + // store the stanza for now, announce after processing all events + Stanza s = createStanza(d->client.recvStanza()); + if(s.isNull()) + break; + d->in.append(new Stanza(s)); + break; + } + case CoreProtocol::EStanzaSent: { +#ifdef XMPP_DEBUG + printf("StanzasSent\n"); +#endif + stanzaWritten(); + if(!self) + return; + break; + } + case CoreProtocol::EClosed: { +#ifdef XMPP_DEBUG + printf("Closed\n"); +#endif + reset(); + delayedCloseFinished(); + return; + } + } + } +} + +bool ClientStream::handleNeed() +{ + int need = d->client.need; + if(need == CoreProtocol::NNotify) { + d->notify = d->client.notify; +#ifdef XMPP_DEBUG + if(d->notify & CoreProtocol::NSend) + printf("More data needs to be written to process next step\n"); + if(d->notify & CoreProtocol::NRecv) + printf("More data is needed to process next step\n"); +#endif + return false; + } + + d->notify = 0; + switch(need) { + case CoreProtocol::NStartTLS: { +#ifdef XMPP_DEBUG + printf("Need StartTLS\n"); +#endif + d->using_tls = true; + d->ss->startTLSClient(d->tlsHandler, d->server, d->client.spare); + return false; + } + case CoreProtocol::NSASLFirst: { +#ifdef XMPP_DEBUG + printf("Need SASL First Step\n"); +#endif + // no SASL plugin? fall back to Simple SASL + if(!QCA::isSupported(QCA::CAP_SASL)) { + // Simple SASL needs MD5. do we have that either? + if(!QCA::isSupported(QCA::CAP_MD5)) + QCA::insertProvider(createProviderHash()); + QCA::insertProvider(createProviderSimpleSASL()); + } + + d->sasl = new QCA::SASL; + connect(d->sasl, SIGNAL(clientFirstStep(const QString &, const QByteArray *)), SLOT(sasl_clientFirstStep(const QString &, const QByteArray *))); + connect(d->sasl, SIGNAL(nextStep(const QByteArray &)), SLOT(sasl_nextStep(const QByteArray &))); + connect(d->sasl, SIGNAL(needParams(bool, bool, bool, bool)), SLOT(sasl_needParams(bool, bool, bool, bool))); + connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); + connect(d->sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + + if(d->haveLocalAddr) + d->sasl->setLocalAddr(d->localAddr, d->localPort); + if(d->conn->havePeerAddress()) + d->sasl->setRemoteAddr(d->conn->peerAddress(), d->conn->peerPort()); + + d->sasl->setAllowAnonymous(false); + + //d->sasl_mech = "ANONYMOUS"; + //d->sasl->setRequirePassCredentials(true); + + //d->sasl->setExternalAuthID("localhost"); + //d->sasl->setExternalSSF(64); + //d->sasl_mech = "EXTERNAL"; + + d->sasl->setAllowPlain(d->allowPlain); + d->sasl->setRequireMutualAuth(d->mutualAuth); + + d->sasl->setMinimumSSF(d->minimumSSF); + d->sasl->setMaximumSSF(d->maximumSSF); + + QStringList ml; + if(!d->sasl_mech.isEmpty()) + ml += d->sasl_mech; + else + ml = d->client.features.sasl_mechs; + + if(!d->sasl->startClient("xmpp", d->server, ml, true)) { + int x = convertedSASLCond(); + reset(); + d->errCond = x; + error(ErrAuth); + return false; + } + return false; + } + case CoreProtocol::NSASLNext: { +#ifdef XMPP_DEBUG + printf("Need SASL Next Step\n"); +#endif + QByteArray a = d->client.saslStep(); + d->sasl->putStep(a); + return false; + } + case CoreProtocol::NSASLLayer: { + // SecureStream will handle the errors from this point + disconnect(d->sasl, SIGNAL(error(int)), this, SLOT(sasl_error(int))); + d->ss->setLayerSASL(d->sasl, d->client.spare); + if(d->sasl_ssf > 0) { + QGuardedPtr self = this; + securityLayerActivated(LayerSASL); + if(!self) + return false; + } + break; + } + case CoreProtocol::NPassword: { +#ifdef XMPP_DEBUG + printf("Need Password\n"); +#endif + d->state = NeedParams; + needAuthParams(false, true, false); + return false; + } + } + + return true; +} + +int ClientStream::convertedSASLCond() const +{ + int x = d->sasl->errorCondition(); + if(x == QCA::SASL::NoMech) + return NoMech; + else if(x == QCA::SASL::BadProto) + return BadProto; + else if(x == QCA::SASL::BadServ) + return BadServ; + else if(x == QCA::SASL::TooWeak) + return MechTooWeak; + else + return GenericAuthError; +} + +void ClientStream::doNoop() +{ + if(d->state == Active) { +#ifdef XMPP_DEBUG + printf("doPing\n"); +#endif + d->client.sendWhitespace(); + processNext(); + } +} + +void ClientStream::writeDirect(const QString &s) +{ + if(d->state == Active) { +#ifdef XMPP_DEBUG + printf("writeDirect\n"); +#endif + d->client.sendDirect(s); + processNext(); + } +} + +void ClientStream::handleError() +{ + int c = d->client.errorCode; + if(c == CoreProtocol::ErrParse) { + reset(); + error(ErrParse); + } + else if(c == CoreProtocol::ErrProtocol) { + reset(); + error(ErrProtocol); + } + else if(c == CoreProtocol::ErrStream) { + int x = d->client.errCond; + QString text = d->client.errText; + QDomElement appSpec = d->client.errAppSpec; + + int connErr = -1; + int strErr = -1; + + switch(x) { + case CoreProtocol::BadFormat: { break; } // should NOT happen (we send the right format) + case CoreProtocol::BadNamespacePrefix: { break; } // should NOT happen (we send prefixes) + case CoreProtocol::Conflict: { strErr = Conflict; break; } + case CoreProtocol::ConnectionTimeout: { strErr = ConnectionTimeout; break; } + case CoreProtocol::HostGone: { connErr = HostGone; break; } + case CoreProtocol::HostUnknown: { connErr = HostUnknown; break; } + case CoreProtocol::ImproperAddressing: { break; } // should NOT happen (we aren't a server) + case CoreProtocol::InternalServerError: { strErr = InternalServerError; break; } + case CoreProtocol::InvalidFrom: { strErr = InvalidFrom; break; } + case CoreProtocol::InvalidId: { break; } // should NOT happen (clients don't specify id) + case CoreProtocol::InvalidNamespace: { break; } // should NOT happen (we set the right ns) + case CoreProtocol::InvalidXml: { strErr = InvalidXml; break; } // shouldn't happen either, but just in case ... + case CoreProtocol::StreamNotAuthorized: { break; } // should NOT happen (we're not stupid) + case CoreProtocol::PolicyViolation: { strErr = PolicyViolation; break; } + case CoreProtocol::RemoteConnectionFailed: { connErr = RemoteConnectionFailed; break; } + case CoreProtocol::ResourceConstraint: { strErr = ResourceConstraint; break; } + case CoreProtocol::RestrictedXml: { strErr = InvalidXml; break; } // group with this one + case CoreProtocol::SeeOtherHost: { connErr = SeeOtherHost; break; } + case CoreProtocol::SystemShutdown: { strErr = SystemShutdown; break; } + case CoreProtocol::UndefinedCondition: { break; } // leave as null error + case CoreProtocol::UnsupportedEncoding: { break; } // should NOT happen (we send good encoding) + case CoreProtocol::UnsupportedStanzaType: { break; } // should NOT happen (we're not stupid) + case CoreProtocol::UnsupportedVersion: { connErr = UnsupportedVersion; break; } + case CoreProtocol::XmlNotWellFormed: { strErr = InvalidXml; break; } // group with this one + default: { break; } + } + + reset(); + + d->errText = text; + d->errAppSpec = appSpec; + if(connErr != -1) { + d->errCond = connErr; + error(ErrNeg); + } + else { + if(strErr != -1) + d->errCond = strErr; + else + d->errCond = GenericStreamError; + error(ErrStream); + } + } + else if(c == CoreProtocol::ErrStartTLS) { + reset(); + d->errCond = TLSStart; + error(ErrTLS); + } + else if(c == CoreProtocol::ErrAuth) { + int x = d->client.errCond; + int r = GenericAuthError; + if(d->client.old) { + if(x == 401) // not authorized + r = NotAuthorized; + else if(x == 409) // conflict + r = GenericAuthError; + else if(x == 406) // not acceptable (this should NOT happen) + r = GenericAuthError; + } + else { + switch(x) { + case CoreProtocol::Aborted: { r = GenericAuthError; break; } // should NOT happen (we never send ) + case CoreProtocol::IncorrectEncoding: { r = GenericAuthError; break; } // should NOT happen + case CoreProtocol::InvalidAuthzid: { r = InvalidAuthzid; break; } + case CoreProtocol::InvalidMech: { r = InvalidMech; break; } + case CoreProtocol::MechTooWeak: { r = MechTooWeak; break; } + case CoreProtocol::NotAuthorized: { r = NotAuthorized; break; } + case CoreProtocol::TemporaryAuthFailure: { r = TemporaryAuthFailure; break; } + } + } + reset(); + d->errCond = r; + error(ErrAuth); + } + else if(c == CoreProtocol::ErrPlain) { + reset(); + d->errCond = NoMech; + error(ErrAuth); + } + else if(c == CoreProtocol::ErrBind) { + int r = -1; + if(d->client.errCond == CoreProtocol::BindBadRequest) { + // should NOT happen + } + else if(d->client.errCond == CoreProtocol::BindNotAllowed) { + r = BindNotAllowed; + } + else if(d->client.errCond == CoreProtocol::BindConflict) { + r = BindConflict; + } + + if(r != -1) { + reset(); + d->errCond = r; + error(ErrBind); + } + else { + reset(); + error(ErrProtocol); + } + } +} + +//---------------------------------------------------------------------------- +// Debug +//---------------------------------------------------------------------------- +Debug::~Debug() +{ +} + +#ifdef XMPP_TEST +TD::TD() +{ +} + +TD::~TD() +{ +} + +void TD::msg(const QString &s) +{ + if(debug_ptr) + debug_ptr->msg(s); +} + +void TD::outgoingTag(const QString &s) +{ + if(debug_ptr) + debug_ptr->outgoingTag(s); +} + +void TD::incomingTag(const QString &s) +{ + if(debug_ptr) + debug_ptr->incomingTag(s); +} + +void TD::outgoingXml(const QDomElement &e) +{ + if(debug_ptr) + debug_ptr->outgoingXml(e); +} + +void TD::incomingXml(const QDomElement &e) +{ + if(debug_ptr) + debug_ptr->incomingXml(e); +} +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/td.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/td.h new file mode 100644 index 00000000..b636e190 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/td.h @@ -0,0 +1,20 @@ +#ifndef TESTDEBUG_H +#define TESTDEBUG_H + +#include + +class TD +{ +public: + TD(); + ~TD(); + + static void msg(const QString &); + static void outgoingTag(const QString &); + static void incomingTag(const QString &); + static void outgoingXml(const QDomElement &); + static void incomingXml(const QDomElement &); +}; + +#endif + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/tlshandler.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/tlshandler.cpp new file mode 100644 index 00000000..f3ac0067 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/tlshandler.cpp @@ -0,0 +1,138 @@ +/* + * tlshandler.cpp - abstract wrapper for TLS + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp.h" + +#include +#include"qca.h" + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// TLSHandler +//---------------------------------------------------------------------------- +TLSHandler::TLSHandler(QObject *parent) +:QObject(parent) +{ +} + +TLSHandler::~TLSHandler() +{ +} + + +//---------------------------------------------------------------------------- +// QCATLSHandler +//---------------------------------------------------------------------------- +class QCATLSHandler::Private +{ +public: + QCA::TLS *tls; + int state, err; +}; + +QCATLSHandler::QCATLSHandler(QCA::TLS *parent) +:TLSHandler(parent) +{ + d = new Private; + d->tls = parent; + connect(d->tls, SIGNAL(handshaken()), SLOT(tls_handshaken())); + connect(d->tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); + connect(d->tls, SIGNAL(readyReadOutgoing(int)), SLOT(tls_readyReadOutgoing(int))); + connect(d->tls, SIGNAL(closed()), SLOT(tls_closed())); + connect(d->tls, SIGNAL(error(int)), SLOT(tls_error(int))); + d->state = 0; + d->err = -1; +} + +QCATLSHandler::~QCATLSHandler() +{ + delete d; +} + +QCA::TLS *QCATLSHandler::tls() const +{ + return d->tls; +} + +int QCATLSHandler::tlsError() const +{ + return d->err; +} + +void QCATLSHandler::reset() +{ + d->tls->reset(); + d->state = 0; +} + +void QCATLSHandler::startClient(const QString &host) +{ + d->state = 0; + d->err = -1; + if(!d->tls->startClient(host)) + QTimer::singleShot(0, this, SIGNAL(fail())); +} + +void QCATLSHandler::write(const QByteArray &a) +{ + d->tls->write(a); +} + +void QCATLSHandler::writeIncoming(const QByteArray &a) +{ + d->tls->writeIncoming(a); +} + +void QCATLSHandler::continueAfterHandshake() +{ + if(d->state == 2) { + success(); + d->state = 3; + } +} + +void QCATLSHandler::tls_handshaken() +{ + d->state = 2; + tlsHandshaken(); +} + +void QCATLSHandler::tls_readyRead() +{ + readyRead(d->tls->read()); +} + +void QCATLSHandler::tls_readyReadOutgoing(int plainBytes) +{ + readyReadOutgoing(d->tls->readOutgoing(), plainBytes); +} + +void QCATLSHandler::tls_closed() +{ + closed(); +} + +void QCATLSHandler::tls_error(int x) +{ + d->err = x; + d->state = 0; + fail(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp new file mode 100644 index 00000000..c70a04a9 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp @@ -0,0 +1,543 @@ +/* + * xmlprotocol.cpp - state machine for 'jabber-like' protocols + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmlprotocol.h" + +#include"bytestream.h" + +using namespace XMPP; + +// stripExtraNS +// +// This function removes namespace information from various nodes for +// display purposes only (the element is pretty much useless for processing +// after this). We do this because QXml is a bit overzealous about outputting +// redundant namespaces. +static QDomElement stripExtraNS(const QDomElement &e) +{ + // find closest parent with a namespace + QDomNode par = e.parentNode(); + while(!par.isNull() && par.namespaceURI().isNull()) + par = par.parentNode(); + bool noShowNS = false; + if(!par.isNull() && par.namespaceURI() == e.namespaceURI()) + noShowNS = true; + + // build qName (prefix:localName) + QString qName; + if(!e.prefix().isEmpty()) + qName = e.prefix() + ':' + e.localName(); + else + qName = e.tagName(); + + QDomElement i; + uint x; + if(noShowNS) + i = e.ownerDocument().createElement(qName); + else + i = e.ownerDocument().createElementNS(e.namespaceURI(), qName); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) { + QDomAttr a = al.item(x).cloneNode().toAttr(); + + // don't show xml namespace + if(a.namespaceURI() == NS_XML) + i.setAttribute(QString("xml:") + a.name(), a.value()); + else + i.setAttributeNodeNS(a); + } + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(stripExtraNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + return i; +} + +// xmlToString +// +// This function converts a QDomElement into a QString, using stripExtraNS +// to make it pretty. +static QString xmlToString(const QDomElement &e, const QString &fakeNS, const QString &fakeQName, bool clip) +{ + QDomElement i = e.cloneNode().toElement(); + + // It seems QDom can only have one namespace attribute at a time (see docElement 'HACK'). + // Fortunately we only need one kind depending on the input, so it is specified here. + QDomElement fake = e.ownerDocument().createElementNS(fakeNS, fakeQName); + fake.appendChild(i); + fake = stripExtraNS(fake); + QString out; + { + QTextStream ts(&out, IO_WriteOnly); + fake.firstChild().save(ts, 0); + } + // 'clip' means to remove any unwanted (and unneeded) characters, such as a trailing newline + if(clip) { + int n = out.findRev('>'); + out.truncate(n+1); + } + return out; +} + +// createRootXmlTags +// +// This function creates three QStrings, one being an processing +// instruction, and the others being the opening and closing tags of an +// element, and . This basically allows us to get the raw XML +// text needed to open/close an XML stream, without resorting to generating +// the XML ourselves. This function uses QDom to do the generation, which +// ensures proper encoding and entity output. +static void createRootXmlTags(const QDomElement &root, QString *xmlHeader, QString *tagOpen, QString *tagClose) +{ + QDomElement e = root.cloneNode(false).toElement(); + + // insert a dummy element to ensure open and closing tags are generated + QDomElement dummy = e.ownerDocument().createElement("dummy"); + e.appendChild(dummy); + + // convert to xml->text + QString str; + { + QTextStream ts(&str, IO_WriteOnly); + e.save(ts, 0); + } + + // parse the tags out + int n = str.find('<'); + int n2 = str.find('>', n); + ++n2; + *tagOpen = str.mid(n, n2-n); + n2 = str.findRev('>'); + n = str.findRev('<'); + ++n2; + *tagClose = str.mid(n, n2-n); + + // generate a nice xml processing header + *xmlHeader = ""; +} + +//---------------------------------------------------------------------------- +// Protocol +//---------------------------------------------------------------------------- +XmlProtocol::TransferItem::TransferItem() +{ +} + +XmlProtocol::TransferItem::TransferItem(const QString &_str, bool sent, bool external) +{ + isString = true; + isSent = sent; + isExternal = external; + str = _str; +} + +XmlProtocol::TransferItem::TransferItem(const QDomElement &_elem, bool sent, bool external) +{ + isString = false; + isSent = sent; + isExternal = external; + elem = _elem; +} + +XmlProtocol::XmlProtocol() +{ + init(); +} + +XmlProtocol::~XmlProtocol() +{ +} + +void XmlProtocol::init() +{ + incoming = false; + peerClosed = false; + closeWritten = false; +} + +void XmlProtocol::reset() +{ + init(); + + elem = QDomElement(); + tagOpen = QString(); + tagClose = QString(); + xml.reset(); + outData.resize(0); + trackQueue.clear(); + transferItemList.clear(); +} + +void XmlProtocol::addIncomingData(const QByteArray &a) +{ + xml.appendData(a); +} + +QByteArray XmlProtocol::takeOutgoingData() +{ + QByteArray a = outData.copy(); + outData.resize(0); + return a; +} + +void XmlProtocol::outgoingDataWritten(int bytes) +{ + for(QValueList::Iterator it = trackQueue.begin(); it != trackQueue.end();) { + TrackItem &i = *it; + + // enough bytes? + if(bytes < i.size) { + i.size -= bytes; + break; + } + int type = i.type; + int id = i.id; + int size = i.size; + bytes -= i.size; + it = trackQueue.remove(it); + + if(type == TrackItem::Raw) { + // do nothing + } + else if(type == TrackItem::Close) { + closeWritten = true; + } + else if(type == TrackItem::Custom) { + itemWritten(id, size); + } + } +} + +bool XmlProtocol::processStep() +{ + Parser::Event pe; + notify = 0; + transferItemList.clear(); + + if(state != Closing && (state == RecvOpen || stepAdvancesParser())) { + // if we get here, then it's because we're in some step that advances the parser + pe = xml.readNext(); + if(!pe.isNull()) { + // note: error/close events should be handled for ALL steps, so do them here + switch(pe.type()) { + case Parser::Event::DocumentOpen: { + transferItemList += TransferItem(pe.actualString(), false); + + //stringRecv(pe.actualString()); + break; + } + case Parser::Event::DocumentClose: { + transferItemList += TransferItem(pe.actualString(), false); + + //stringRecv(pe.actualString()); + if(incoming) { + sendTagClose(); + event = ESend; + peerClosed = true; + state = Closing; + } + else { + event = EPeerClosed; + } + return true; + } + case Parser::Event::Element: { + transferItemList += TransferItem(pe.element(), false); + + //elementRecv(pe.element()); + break; + } + case Parser::Event::Error: { + if(incoming) { + // If we get a parse error during the initial element exchange, + // flip immediately into 'open' mode so that we can report an error. + if(state == RecvOpen) { + sendTagOpen(); + state = Open; + } + return handleError(); + } + else { + event = EError; + errorCode = ErrParse; + return true; + } + } + } + } + else { + if(state == RecvOpen || stepRequiresElement()) { + need = NNotify; + notify |= NRecv; + return false; + } + } + } + + return baseStep(pe); +} + +QString XmlProtocol::xmlEncoding() const +{ + return xml.encoding(); +} + +QString XmlProtocol::elementToString(const QDomElement &e, bool clip) +{ + if(elem.isNull()) + elem = elemDoc.importNode(docElement(), true).toElement(); + + // Determine the appropriate 'fakeNS' to use + QString ns; + + // first, check root namespace + QString pre = e.prefix(); + if(pre.isNull()) + pre = ""; + if(pre == elem.prefix()) { + ns = elem.namespaceURI(); + } + else { + // scan the root attributes for 'xmlns' (oh joyous hacks) + QDomNamedNodeMap al = elem.attributes(); + uint n; + for(n = 0; n < al.count(); ++n) { + QDomAttr a = al.item(n).toAttr(); + QString s = a.name(); + int x = s.find(':'); + if(x != -1) + s = s.mid(x+1); + else + s = ""; + if(pre == s) { + ns = a.value(); + break; + } + } + if(n >= al.count()) { + // if we get here, then no appropriate ns was found. use root then.. + ns = elem.namespaceURI(); + } + } + + // build qName + QString qn; + if(!elem.prefix().isEmpty()) + qn = elem.prefix() + ':'; + qn += elem.localName(); + + // make the string + return xmlToString(e, ns, qn, clip); +} + +bool XmlProtocol::stepRequiresElement() const +{ + // default returns false + return false; +} + +void XmlProtocol::itemWritten(int, int) +{ + // default does nothing +} + +void XmlProtocol::stringSend(const QString &) +{ + // default does nothing +} + +void XmlProtocol::stringRecv(const QString &) +{ + // default does nothing +} + +void XmlProtocol::elementSend(const QDomElement &) +{ + // default does nothing +} + +void XmlProtocol::elementRecv(const QDomElement &) +{ + // default does nothing +} + +void XmlProtocol::startConnect() +{ + incoming = false; + state = SendOpen; +} + +void XmlProtocol::startAccept() +{ + incoming = true; + state = RecvOpen; +} + +bool XmlProtocol::close() +{ + sendTagClose(); + event = ESend; + state = Closing; + return true; +} + +int XmlProtocol::writeString(const QString &s, int id, bool external) +{ + transferItemList += TransferItem(s, true, external); + return internalWriteString(s, TrackItem::Custom, id); +} + +int XmlProtocol::writeElement(const QDomElement &e, int id, bool external, bool clip) +{ + if(e.isNull()) + return 0; + transferItemList += TransferItem(e, true, external); + + //elementSend(e); + QString out = elementToString(e, clip); + return internalWriteString(out, TrackItem::Custom, id); +} + +QByteArray XmlProtocol::resetStream() +{ + // reset the state + if(incoming) + state = RecvOpen; + else + state = SendOpen; + + // grab unprocessed data before resetting + QByteArray spare = xml.unprocessed(); + xml.reset(); + return spare; +} + +int XmlProtocol::internalWriteData(const QByteArray &a, TrackItem::Type t, int id) +{ + TrackItem i; + i.type = t; + i.id = id; + i.size = a.size(); + trackQueue += i; + + ByteStream::appendArray(&outData, a); + return a.size(); +} + +int XmlProtocol::internalWriteString(const QString &s, TrackItem::Type t, int id) +{ + QCString cs = s.utf8(); + QByteArray a(cs.length()); + memcpy(a.data(), cs.data(), a.size()); + return internalWriteData(a, t, id); +} + +void XmlProtocol::sendTagOpen() +{ + if(elem.isNull()) + elem = elemDoc.importNode(docElement(), true).toElement(); + + QString xmlHeader; + createRootXmlTags(elem, &xmlHeader, &tagOpen, &tagClose); + + QString s; + s += xmlHeader + '\n'; + s += tagOpen + '\n'; + + transferItemList += TransferItem(xmlHeader, true); + transferItemList += TransferItem(tagOpen, true); + + //stringSend(xmlHeader); + //stringSend(tagOpen); + internalWriteString(s, TrackItem::Raw); +} + +void XmlProtocol::sendTagClose() +{ + transferItemList += TransferItem(tagClose, true); + + //stringSend(tagClose); + internalWriteString(tagClose, TrackItem::Close); +} + +bool XmlProtocol::baseStep(const Parser::Event &pe) +{ + // Basic + if(state == SendOpen) { + sendTagOpen(); + event = ESend; + if(incoming) + state = Open; + else + state = RecvOpen; + return true; + } + else if(state == RecvOpen) { + if(incoming) + state = SendOpen; + else + state = Open; + + // note: event will always be DocumentOpen here + handleDocOpen(pe); + event = ERecvOpen; + return true; + } + else if(state == Open) { + QDomElement e; + if(pe.type() == Parser::Event::Element) + e = pe.element(); + return doStep(e); + } + // Closing + else { + if(closeWritten) { + if(peerClosed) { + event = EPeerClosed; + return true; + } + else + return handleCloseFinished(); + } + + need = NNotify; + notify = NSend; + return false; + } +} + +void XmlProtocol::setIncomingAsExternal() +{ + for(QValueList::Iterator it = transferItemList.begin(); it != transferItemList.end(); ++it) { + TransferItem &i = *it; + // look for elements received + if(!i.isString && !i.isSent) + i.isExternal = true; + } +} + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.h new file mode 100644 index 00000000..5bf2cbda --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.h @@ -0,0 +1,145 @@ +/* + * xmlprotocol.h - state machine for 'jabber-like' protocols + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMLPROTOCOL_H +#define XMLPROTOCOL_H + +#include +#include +#include"parser.h" + +#define NS_XML "http://www.w3.org/XML/1998/namespace" + +namespace XMPP +{ + class XmlProtocol + { + public: + enum Need { + NNotify, // need a data send and/or recv update + NCustom = 10 + }; + enum Event { + EError, // unrecoverable error, see errorCode for details + ESend, // data needs to be sent, use takeOutgoingData() + ERecvOpen, // breakpoint after root element open tag is received + EPeerClosed, // root element close tag received + EClosed, // finished closing + ECustom = 10 + }; + enum Error { + ErrParse, // there was an error parsing the xml + ErrCustom = 10 + }; + enum Notify { + NSend = 0x01, // need to know if data has been written + NRecv = 0x02 // need incoming data + }; + + XmlProtocol(); + virtual ~XmlProtocol(); + + virtual void reset(); + + // byte I/O for the stream + void addIncomingData(const QByteArray &); + QByteArray takeOutgoingData(); + void outgoingDataWritten(int); + + // advance the state machine + bool processStep(); + + // set these before returning from a step + int need, event, errorCode, notify; + + inline bool isIncoming() const { return incoming; } + QString xmlEncoding() const; + QString elementToString(const QDomElement &e, bool clip=false); + + class TransferItem + { + public: + TransferItem(); + TransferItem(const QString &str, bool sent, bool external=false); + TransferItem(const QDomElement &elem, bool sent, bool external=false); + + bool isSent; // else, received + bool isString; // else, is element + bool isExternal; // not owned by protocol + QString str; + QDomElement elem; + }; + QValueList transferItemList; + void setIncomingAsExternal(); + + protected: + virtual QDomElement docElement()=0; + virtual void handleDocOpen(const Parser::Event &pe)=0; + virtual bool handleError()=0; + virtual bool handleCloseFinished()=0; + virtual bool stepAdvancesParser() const=0; + virtual bool stepRequiresElement() const; + virtual bool doStep(const QDomElement &e)=0; + virtual void itemWritten(int id, int size); + + // 'debug' + virtual void stringSend(const QString &s); + virtual void stringRecv(const QString &s); + virtual void elementSend(const QDomElement &e); + virtual void elementRecv(const QDomElement &e); + + void startConnect(); + void startAccept(); + bool close(); + int writeString(const QString &s, int id, bool external); + int writeElement(const QDomElement &e, int id, bool external, bool clip=false); + QByteArray resetStream(); + + private: + enum { SendOpen, RecvOpen, Open, Closing }; + class TrackItem + { + public: + enum Type { Raw, Close, Custom }; + int type, id, size; + }; + + bool incoming; + QDomDocument elemDoc; + QDomElement elem; + QString tagOpen, tagClose; + int state; + bool peerClosed; + bool closeWritten; + + Parser xml; + QByteArray outData; + QValueList trackQueue; + + void init(); + int internalWriteData(const QByteArray &a, TrackItem::Type t, int id=-1); + int internalWriteString(const QString &s, TrackItem::Type t, int id=-1); + void sendTagOpen(); + void sendTagClose(); + bool baseStep(const Parser::Event &pe); + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/Makefile.am b/kopete/protocols/jabber/libiris/iris/xmpp-im/Makefile.am new file mode 100644 index 00000000..c6dff330 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/Makefile.am @@ -0,0 +1,19 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libiris_xmpp_im.la +INCLUDES = -I$(srcdir)/../include -I$(srcdir)/../xmpp-core -I$(srcdir)/../xmpp-im -I$(srcdir)/../jabber -I$(srcdir)/../../cutestuff/util -I$(srcdir)/../../cutestuff/network -I$(srcdir)/../../qca/src $(all_includes) + +libiris_xmpp_im_la_SOURCES = \ + client.cpp \ + types.cpp \ + xmpp_tasks.cpp \ + xmpp_vcard.cpp \ + xmpp_xmlcommon.cpp + +CLEANFILES = types.moc +types.lo: types.moc +types.moc: $(top_builddir)/kopete/protocols/jabber/libiris/iris/xmpp-im/Makefile + ${MOC} -o types.moc $(srcdir)/../xmpp-im/types.cpp + +KDE_OPTIONS = nofinal + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/client.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-im/client.cpp new file mode 100644 index 00000000..0baeb820 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/client.cpp @@ -0,0 +1,1522 @@ +/* + * client.cpp - IM Client + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"im.h" +#include"safedelete.h" + +//! \class Client client.h +//! \brief Communicates with the Jabber network. Start here. +//! +//! Client controls an active Jabber connection. It allows you to connect, +//! authenticate, manipulate the roster, and send / receive messages and +//! presence. It is the centerpiece of this library, and all Tasks must pass +//! through it. +//! +//! For convenience, many Tasks are handled internally to Client (such as +//! JT_Auth). However, for accessing features beyond the basics provided by +//! Client, you will need to manually invoke Tasks. Fortunately, the +//! process is very simple. +//! +//! The entire Task system is heavily founded on Qt. All Tasks have a parent, +//! except for the root Task, and are considered QObjects. By using Qt's RTTI +//! facilities (QObject::sender(), QObject::isA(), etc), you can use a +//! "fire and forget" approach with Tasks. +//! +//! \code +//! #include "client.h" +//! using namespace Jabber; +//! +//! ... +//! +//! Client *client; +//! +//! Session::Session() +//! { +//! client = new Client; +//! connect(client, SIGNAL(handshaken()), SLOT(clientHandshaken())); +//! connect(client, SIGNAL(authFinished(bool, int, const QString &)), SLOT(authFinished(bool, int, const QString &))); +//! client->connectToHost("jabber.org"); +//! } +//! +//! void Session::clientHandshaken() +//! { +//! client->authDigest("jabtest", "12345", "Psi"); +//! } +//! +//! void Session::authFinished(bool success, int, const QString &err) +//! { +//! if(success) +//! printf("Login success!"); +//! else +//! printf("Login failed. Here's why: %s\n", err.latin1()); +//! } +//! \endcode + +#include +#include +#include +#include +#include +#include"xmpp_tasks.h" +#include"xmpp_xmlcommon.h" +#include"s5b.h" +#include"xmpp_ibb.h" +#include"xmpp_jidlink.h" +#include"filetransfer.h" + +/*#include +#include +#include +#include +#include +#include +#include"xmpp_stream.h" +#include"xmpp_tasks.h" +#include"xmpp_xmlcommon.h" +#include"xmpp_dtcp.h" +#include"xmpp_ibb.h" +#include"xmpp_jidlink.h" + +using namespace Jabber;*/ + +#ifdef Q_WS_WIN +#define vsnprintf _vsnprintf +#endif + +namespace XMPP +{ + +//---------------------------------------------------------------------------- +// Client +//---------------------------------------------------------------------------- +class Client::GroupChat +{ +public: + enum { Connecting, Connected, Closing }; + GroupChat() {} + + Jid j; + int status; +}; + +class Client::ClientPrivate +{ +public: + ClientPrivate() {} + + ClientStream *stream; + QDomDocument doc; + int id_seed; + Task *root; + QString host, user, pass, resource; + QString osname, tzname, clientName, clientVersion, capsNode, capsVersion, capsExt; + DiscoItem::Identity identity; + QMap extension_features; + int tzoffset; + bool active; + + LiveRoster roster; + ResourceList resourceList; + S5BManager *s5bman; + IBBManager *ibbman; + JidLinkManager *jlman; + FileTransferManager *ftman; + bool ftEnabled; + QValueList groupChatList; +}; + + +Client::Client(QObject *par) +:QObject(par) +{ + d = new ClientPrivate; + d->tzoffset = 0; + d->active = false; + d->osname = "N/A"; + d->clientName = "N/A"; + d->clientVersion = "0.0"; + d->capsNode = ""; + d->capsVersion = ""; + d->capsExt = ""; + + d->id_seed = 0xaaaa; + d->root = new Task(this, true); + + d->stream = 0; + + d->s5bman = new S5BManager(this); + connect(d->s5bman, SIGNAL(incomingReady()), SLOT(s5b_incomingReady())); + + d->ibbman = new IBBManager(this); + connect(d->ibbman, SIGNAL(incomingReady()), SLOT(ibb_incomingReady())); + + d->jlman = new JidLinkManager(this); + + d->ftman = 0; +} + +Client::~Client() +{ + close(true); + + delete d->ftman; + delete d->jlman; + delete d->ibbman; + delete d->s5bman; + delete d->root; + //delete d->stream; + delete d; +} + +void Client::connectToServer(ClientStream *s, const Jid &j, bool auth) +{ + d->stream = s; + //connect(d->stream, SIGNAL(connected()), SLOT(streamConnected())); + //connect(d->stream, SIGNAL(handshaken()), SLOT(streamHandshaken())); + connect(d->stream, SIGNAL(error(int)), SLOT(streamError(int))); + //connect(d->stream, SIGNAL(sslCertificateReady(const QSSLCert &)), SLOT(streamSSLCertificateReady(const QSSLCert &))); + connect(d->stream, SIGNAL(readyRead()), SLOT(streamReadyRead())); + //connect(d->stream, SIGNAL(closeFinished()), SLOT(streamCloseFinished())); + connect(d->stream, SIGNAL(incomingXml(const QString &)), SLOT(streamIncomingXml(const QString &))); + connect(d->stream, SIGNAL(outgoingXml(const QString &)), SLOT(streamOutgoingXml(const QString &))); + + d->stream->connectToServer(j, auth); +} + +void Client::start(const QString &host, const QString &user, const QString &pass, const QString &_resource) +{ + // TODO + d->host = host; + d->user = user; + d->pass = pass; + d->resource = _resource; + + Status stat; + stat.setIsAvailable(false); + d->resourceList += Resource(resource(), stat); + + JT_PushPresence *pp = new JT_PushPresence(rootTask()); + connect(pp, SIGNAL(subscription(const Jid &, const QString &)), SLOT(ppSubscription(const Jid &, const QString &))); + connect(pp, SIGNAL(presence(const Jid &, const Status &)), SLOT(ppPresence(const Jid &, const Status &))); + + JT_PushMessage *pm = new JT_PushMessage(rootTask()); + connect(pm, SIGNAL(message(const Message &)), SLOT(pmMessage(const Message &))); + + JT_PushRoster *pr = new JT_PushRoster(rootTask()); + connect(pr, SIGNAL(roster(const Roster &)), SLOT(prRoster(const Roster &))); + + new JT_ServInfo(rootTask()); + + d->active = true; +} + +void Client::setFileTransferEnabled(bool b) +{ + if(b) { + if(!d->ftman) + d->ftman = new FileTransferManager(this); + } + else { + if(d->ftman) { + delete d->ftman; + d->ftman = 0; + } + } +} + +FileTransferManager *Client::fileTransferManager() const +{ + return d->ftman; +} + +JidLinkManager *Client::jidLinkManager() const +{ + return d->jlman; +} + +S5BManager *Client::s5bManager() const +{ + return d->s5bman; +} + +IBBManager *Client::ibbManager() const +{ + return d->ibbman; +} + +bool Client::isActive() const +{ + return d->active; +} + +void Client::groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &_s) +{ + Jid jid(room + "@" + host + "/" + nick); + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + GroupChat &i = *it; + if(i.j.compare(jid, false)) { + i.j = jid; + + Status s = _s; + s.setIsAvailable(true); + + JT_Presence *j = new JT_Presence(rootTask()); + j->pres(jid, s); + j->go(true); + + break; + } + } +} + +bool Client::groupChatJoin(const QString &host, const QString &room, const QString &nick) +{ + Jid jid(room + "@" + host + "/" + nick); + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end();) { + GroupChat &i = *it; + if(i.j.compare(jid, false)) { + // if this room is shutting down, then free it up + if(i.status == GroupChat::Closing) + it = d->groupChatList.remove(it); + else + return false; + } + else + ++it; + } + + debug(QString("Client: Joined: [%1]\n").arg(jid.full())); + GroupChat i; + i.j = jid; + i.status = GroupChat::Connecting; + d->groupChatList += i; + + JT_Presence *j = new JT_Presence(rootTask()); + j->pres(jid, Status()); + j->go(true); + + return true; +} + +bool Client::groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &password) +{ + Jid jid(room + "@" + host + "/" + nick); + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end();) { + GroupChat &i = *it; + if(i.j.compare(jid, false)) { + // if this room is shutting down, then free it up + if(i.status == GroupChat::Closing) + it = d->groupChatList.remove(it); + else + return false; + } + else + ++it; + } + + debug(QString("Client: Joined: [%1]\n").arg(jid.full())); + GroupChat i; + i.j = jid; + i.status = GroupChat::Connecting; + d->groupChatList += i; + + JT_MucPresence *j = new JT_MucPresence(rootTask()); + j->pres(jid, Status(), password); + j->go(true); + + return true; +} + +void Client::groupChatSetStatus(const QString &host, const QString &room, const Status &_s) +{ + Jid jid(room + "@" + host); + bool found = false; + for(QValueList::ConstIterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + const GroupChat &i = *it; + if(i.j.compare(jid, false)) { + found = true; + jid = i.j; + break; + } + } + if(!found) + return; + + Status s = _s; + s.setIsAvailable(true); + + JT_Presence *j = new JT_Presence(rootTask()); + j->pres(jid, s); + j->go(true); +} + +void Client::groupChatLeave(const QString &host, const QString &room) +{ + Jid jid(room + "@" + host); + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + GroupChat &i = *it; + + if(!i.j.compare(jid, false)) + continue; + + i.status = GroupChat::Closing; + debug(QString("Client: Leaving: [%1]\n").arg(i.j.full())); + + JT_Presence *j = new JT_Presence(rootTask()); + Status s; + s.setIsAvailable(false); + j->pres(i.j, s); + j->go(true); + } +} + +/*void Client::start() +{ + if(d->stream->old()) { + // old has no activation step + d->active = true; + activated(); + } + else { + // TODO: IM session + } +}*/ + +// TODO: fast close +void Client::close(bool) +{ + if(d->stream) { + if(d->active) { + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + GroupChat &i = *it; + i.status = GroupChat::Closing; + + JT_Presence *j = new JT_Presence(rootTask()); + Status s; + s.setIsAvailable(false); + j->pres(i.j, s); + j->go(true); + } + } + + d->stream->disconnect(this); + d->stream->close(); + d->stream = 0; + } + disconnected(); + cleanup(); +} + +void Client::cleanup() +{ + d->active = false; + //d->authed = false; + d->groupChatList.clear(); +} + +/*void Client::continueAfterCert() +{ + d->stream->continueAfterCert(); +} + +void Client::streamConnected() +{ + connected(); +} + +void Client::streamHandshaken() +{ + handshaken(); +}*/ + +void Client::streamError(int) +{ + //StreamError e = err; + //error(e); + + //if(!e.isWarning()) { + disconnected(); + cleanup(); + //} +} + +/*void Client::streamSSLCertificateReady(const QSSLCert &cert) +{ + sslCertReady(cert); +} + +void Client::streamCloseFinished() +{ + closeFinished(); +}*/ + +static QDomElement oldStyleNS(const QDomElement &e) +{ + // find closest parent with a namespace + QDomNode par = e.parentNode(); + while(!par.isNull() && par.namespaceURI().isNull()) + par = par.parentNode(); + bool noShowNS = false; + if(!par.isNull() && par.namespaceURI() == e.namespaceURI()) + noShowNS = true; + + QDomElement i; + uint x; + //if(noShowNS) + i = e.ownerDocument().createElement(e.tagName()); + //else + // i = e.ownerDocument().createElementNS(e.namespaceURI(), e.tagName()); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) + i.setAttributeNode(al.item(x).cloneNode().toAttr()); + + if(!noShowNS) + i.setAttribute("xmlns", e.namespaceURI()); + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(oldStyleNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + return i; +} + +void Client::streamReadyRead() +{ + // HACK HACK HACK + QGuardedPtr pstream = d->stream; + + while(pstream && d->stream->stanzaAvailable()) { + Stanza s = d->stream->read(); + + QString out = s.toString(); + debug(QString("Client: incoming: [\n%1]\n").arg(out)); + xmlIncoming(out); + + QDomElement x = oldStyleNS(s.element()); + distribute(x); + } +} + +void Client::streamIncomingXml(const QString &s) +{ + QString str = s; + if(str.at(str.length()-1) != '\n') + str += '\n'; + xmlIncoming(str); +} + +void Client::streamOutgoingXml(const QString &s) +{ + QString str = s; + if(str.at(str.length()-1) != '\n') + str += '\n'; + xmlOutgoing(str); +} + +void Client::debug(const QString &str) +{ + debugText(str); +} + +QString Client::genUniqueId() +{ + QString s; + s.sprintf("a%x", d->id_seed); + d->id_seed += 0x10; + return s; +} + +Task *Client::rootTask() +{ + return d->root; +} + +QDomDocument *Client::doc() const +{ + return &d->doc; +} + +void Client::distribute(const QDomElement &x) +{ + if(x.hasAttribute("from")) { + Jid j(x.attribute("from")); + if(!j.isValid()) { + debug("Client: bad 'from' JID\n"); + return; + } + } + + if(!rootTask()->take(x)) { + debug("Client: packet was ignored.\n"); + } +} + +static QDomElement addCorrectNS(const QDomElement &e) +{ + uint x; + + // grab child nodes + /*QDomDocumentFragment frag = e.ownerDocument().createDocumentFragment(); + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) + frag.appendChild(nl.item(x).cloneNode());*/ + + // find closest xmlns + QDomNode n = e; + while(!n.isNull() && !n.toElement().hasAttribute("xmlns")) + n = n.parentNode(); + QString ns; + if(n.isNull() || !n.toElement().hasAttribute("xmlns")) + ns = "jabber:client"; + else + ns = n.toElement().attribute("xmlns"); + + // make a new node + QDomElement i = e.ownerDocument().createElementNS(ns, e.tagName()); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) { + QDomAttr a = al.item(x).toAttr(); + if(a.name() != "xmlns") + i.setAttributeNodeNS(a.cloneNode().toAttr()); + } + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(addCorrectNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + + //i.appendChild(frag); + return i; +} + +void Client::send(const QDomElement &x) +{ + if(!d->stream) + return; + + //QString out; + //QTextStream ts(&out, IO_WriteOnly); + //x.save(ts, 0); + + //QString out = Stream::xmlToString(x); + //debug(QString("Client: outgoing: [\n%1]\n").arg(out)); + //xmlOutgoing(out); + + QDomElement e = addCorrectNS(x); + Stanza s = d->stream->createStanza(e); + if(s.isNull()) { + //printf("bad stanza??\n"); + return; + } + + QString out = s.toString(); + debug(QString("Client: outgoing: [\n%1]\n").arg(out)); + xmlOutgoing(out); + + //printf("x[%s] x2[%s] s[%s]\n", Stream::xmlToString(x).latin1(), Stream::xmlToString(e).latin1(), s.toString().latin1()); + d->stream->write(s); +} + +void Client::send(const QString &str) +{ + if(!d->stream) + return; + + debug(QString("Client: outgoing: [\n%1]\n").arg(str)); + xmlOutgoing(str); + static_cast(d->stream)->writeDirect(str); +} + +Stream & Client::stream() +{ + return *d->stream; +} + +const LiveRoster & Client::roster() const +{ + return d->roster; +} + +const ResourceList & Client::resourceList() const +{ + return d->resourceList; +} + +QString Client::host() const +{ + return d->host; +} + +QString Client::user() const +{ + return d->user; +} + +QString Client::pass() const +{ + return d->pass; +} + +QString Client::resource() const +{ + return d->resource; +} + +Jid Client::jid() const +{ + QString s; + if(!d->user.isEmpty()) + s += d->user + '@'; + s += d->host; + if(!d->resource.isEmpty()) { + s += '/'; + s += d->resource; + } + + return Jid(s); +} + +void Client::ppSubscription(const Jid &j, const QString &s) +{ + subscription(j, s); +} + +void Client::ppPresence(const Jid &j, const Status &s) +{ + if(s.isAvailable()) + debug(QString("Client: %1 is available.\n").arg(j.full())); + else + debug(QString("Client: %1 is unavailable.\n").arg(j.full())); + + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + GroupChat &i = *it; + + if(i.j.compare(j, false)) { + bool us = (i.j.resource() == j.resource() || j.resource().isEmpty()) ? true: false; + + debug(QString("for groupchat i=[%1] pres=[%2], [us=%3].\n").arg(i.j.full()).arg(j.full()).arg(us)); + switch(i.status) { + case GroupChat::Connecting: + if(us && s.hasError()) { + Jid j = i.j; + d->groupChatList.remove(it); + groupChatError(j, s.errorCode(), s.errorString()); + } + else { + // don't signal success unless it is a non-error presence + if(!s.hasError()) { + i.status = GroupChat::Connected; + groupChatJoined(i.j); + } + groupChatPresence(j, s); + } + break; + case GroupChat::Connected: + groupChatPresence(j, s); + break; + case GroupChat::Closing: + if(us && !s.isAvailable()) { + Jid j = i.j; + d->groupChatList.remove(it); + groupChatLeft(j); + } + break; + default: + break; + } + + return; + } + } + + if(s.hasError()) { + presenceError(j, s.errorCode(), s.errorString()); + return; + } + + // is it me? + if(j.compare(jid(), false)) { + updateSelfPresence(j, s); + } + else { + // update all relavent roster entries + for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end(); ++it) { + LiveRosterItem &i = *it; + + if(!i.jid().compare(j, false)) + continue; + + // roster item has its own resource? + if(!i.jid().resource().isEmpty()) { + if(i.jid().resource() != j.resource()) + continue; + } + + updatePresence(&i, j, s); + } + } +} + +void Client::updateSelfPresence(const Jid &j, const Status &s) +{ + ResourceList::Iterator rit = d->resourceList.find(j.resource()); + bool found = (rit == d->resourceList.end()) ? false: true; + + // unavailable? remove the resource + if(!s.isAvailable()) { + if(found) { + debug(QString("Client: Removing self resource: name=[%1]\n").arg(j.resource())); + (*rit).setStatus(s); + resourceUnavailable(j, *rit); + d->resourceList.remove(rit); + } + } + // available? add/update the resource + else { + Resource r; + if(!found) { + r = Resource(j.resource(), s); + d->resourceList += r; + debug(QString("Client: Adding self resource: name=[%1]\n").arg(j.resource())); + } + else { + (*rit).setStatus(s); + r = *rit; + debug(QString("Client: Updating self resource: name=[%1]\n").arg(j.resource())); + } + + resourceAvailable(j, r); + } +} + +void Client::updatePresence(LiveRosterItem *i, const Jid &j, const Status &s) +{ + ResourceList::Iterator rit = i->resourceList().find(j.resource()); + bool found = (rit == i->resourceList().end()) ? false: true; + + // unavailable? remove the resource + if(!s.isAvailable()) { + if(found) { + (*rit).setStatus(s); + debug(QString("Client: Removing resource from [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); + resourceUnavailable(j, *rit); + i->resourceList().remove(rit); + i->setLastUnavailableStatus(s); + } + } + // available? add/update the resource + else { + Resource r; + if(!found) { + r = Resource(j.resource(), s); + i->resourceList() += r; + debug(QString("Client: Adding resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); + } + else { + (*rit).setStatus(s); + r = *rit; + debug(QString("Client: Updating resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); + } + + resourceAvailable(j, r); + } +} + +void Client::pmMessage(const Message &m) +{ + debug(QString("Client: Message from %1\n").arg(m.from().full())); + + if(m.type() == "groupchat") { + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + const GroupChat &i = *it; + + if(!i.j.compare(m.from(), false)) + continue; + + if(i.status == GroupChat::Connected) + messageReceived(m); + } + } + else + messageReceived(m); +} + +void Client::prRoster(const Roster &r) +{ + importRoster(r); +} + +void Client::rosterRequest() +{ + if(!d->active) + return; + + JT_Roster *r = new JT_Roster(rootTask()); + connect(r, SIGNAL(finished()), SLOT(slotRosterRequestFinished())); + r->get(); + d->roster.flagAllForDelete(); // mod_groups patch + r->go(true); +} + +void Client::slotRosterRequestFinished() +{ + JT_Roster *r = (JT_Roster *)sender(); + // on success, let's take it + if(r->success()) { + //d->roster.flagAllForDelete(); // mod_groups patch + + importRoster(r->roster()); + + for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end();) { + LiveRosterItem &i = *it; + if(i.flagForDelete()) { + rosterItemRemoved(i); + it = d->roster.remove(it); + } + else + ++it; + } + } + else { + // don't report a disconnect. Client::error() will do that. + if(r->statusCode() == Task::ErrDisc) + return; + } + + // report success / fail + rosterRequestFinished(r->success(), r->statusCode(), r->statusString()); +} + +void Client::importRoster(const Roster &r) +{ + for(Roster::ConstIterator it = r.begin(); it != r.end(); ++it) { + importRosterItem(*it); + } +} + +void Client::importRosterItem(const RosterItem &item) +{ + QString substr; + switch(item.subscription().type()) { + case Subscription::Both: + substr = "<-->"; break; + case Subscription::From: + substr = " ->"; break; + case Subscription::To: + substr = "<- "; break; + case Subscription::Remove: + substr = "xxxx"; break; + case Subscription::None: + default: + substr = "----"; break; + } + + QString dstr, str; + str.sprintf(" %s %-32s", substr.latin1(), item.jid().full().latin1()); + if(!item.name().isEmpty()) + str += QString(" [") + item.name() + "]"; + str += '\n'; + + // Remove + if(item.subscription().type() == Subscription::Remove) { + LiveRoster::Iterator it = d->roster.find(item.jid()); + if(it != d->roster.end()) { + rosterItemRemoved(*it); + d->roster.remove(it); + } + dstr = "Client: (Removed) "; + } + // Add/Update + else { + LiveRoster::Iterator it = d->roster.find(item.jid()); + if(it != d->roster.end()) { + LiveRosterItem &i = *it; + i.setFlagForDelete(false); + i.setRosterItem(item); + rosterItemUpdated(i); + dstr = "Client: (Updated) "; + } + else { + LiveRosterItem i(item); + d->roster += i; + + // signal it + rosterItemAdded(i); + dstr = "Client: (Added) "; + } + } + + debug(dstr + str); +} + +void Client::sendMessage(const Message &m) +{ + JT_Message *j = new JT_Message(rootTask(), m); + j->go(true); +} + +void Client::sendSubscription(const Jid &jid, const QString &type) +{ + JT_Presence *j = new JT_Presence(rootTask()); + j->sub(jid, type); + j->go(true); +} + +void Client::setPresence(const Status &s) +{ + JT_Presence *j = new JT_Presence(rootTask()); + j->pres(s); + j->go(true); + + // update our resourceList + ppPresence(jid(), s); + //ResourceList::Iterator rit = d->resourceList.find(resource()); + //Resource &r = *rit; + //r.setStatus(s); +} + +QString Client::OSName() const +{ + return d->osname; +} + +QString Client::timeZone() const +{ + return d->tzname; +} + +int Client::timeZoneOffset() const +{ + return d->tzoffset; +} + +QString Client::clientName() const +{ + return d->clientName; +} + +QString Client::clientVersion() const +{ + return d->clientVersion; +} + +QString Client::capsNode() const +{ + return d->capsNode; +} + +QString Client::capsVersion() const +{ + return d->capsVersion; +} + +QString Client::capsExt() const +{ + return d->capsExt; +} + +void Client::setOSName(const QString &name) +{ + d->osname = name; +} + +void Client::setTimeZone(const QString &name, int offset) +{ + d->tzname = name; + d->tzoffset = offset; +} + +void Client::setClientName(const QString &s) +{ + d->clientName = s; +} + +void Client::setClientVersion(const QString &s) +{ + d->clientVersion = s; +} + +void Client::setCapsNode(const QString &s) +{ + d->capsNode = s; +} + +void Client::setCapsVersion(const QString &s) +{ + d->capsVersion = s; +} + +DiscoItem::Identity Client::identity() +{ + return d->identity; +} + +void Client::setIdentity(DiscoItem::Identity identity) +{ + d->identity = identity; +} + +void Client::addExtension(const QString& ext, const Features& features) +{ + if (!ext.isEmpty()) { + d->extension_features[ext] = features; + d->capsExt = extensions().join(" "); + } +} + +void Client::removeExtension(const QString& ext) +{ + if (d->extension_features.contains(ext)) { + d->extension_features.remove(ext); + d->capsExt = extensions().join(" "); + } +} + +QStringList Client::extensions() const +{ + return d->extension_features.keys(); +} + +const Features& Client::extension(const QString& ext) const +{ + return d->extension_features[ext]; +} + +void Client::s5b_incomingReady() +{ + S5BConnection *c = d->s5bman->takeIncoming(); + if(!c) + return; + if(!d->ftman) { + c->close(); + c->deleteLater(); + return; + } + d->ftman->s5b_incomingReady(c); + //d->jlman->insertStream(c); + //incomingJidLink(); +} + +void Client::ibb_incomingReady() +{ + IBBConnection *c = d->ibbman->takeIncoming(); + if(!c) + return; + c->deleteLater(); + //d->jlman->insertStream(c); + //incomingJidLink(); +} + +//---------------------------------------------------------------------------- +// Task +//---------------------------------------------------------------------------- +class Task::TaskPrivate +{ +public: + TaskPrivate() {} + + QString id; + bool success; + int statusCode; + QString statusString; + Client *client; + bool insig, deleteme, autoDelete; + bool done; +}; + +Task::Task(Task *parent) +:QObject(parent) +{ + init(); + + d->client = parent->client(); + d->id = client()->genUniqueId(); + connect(d->client, SIGNAL(disconnected()), SLOT(clientDisconnected())); +} + +Task::Task(Client *parent, bool) +:QObject(0) +{ + init(); + + d->client = parent; + connect(d->client, SIGNAL(disconnected()), SLOT(clientDisconnected())); +} + +Task::~Task() +{ + delete d; +} + +void Task::init() +{ + d = new TaskPrivate; + d->success = false; + d->insig = false; + d->deleteme = false; + d->autoDelete = false; + d->done = false; +} + +Task *Task::parent() const +{ + return (Task *)QObject::parent(); +} + +Client *Task::client() const +{ + return d->client; +} + +QDomDocument *Task::doc() const +{ + return client()->doc(); +} + +QString Task::id() const +{ + return d->id; +} + +bool Task::success() const +{ + return d->success; +} + +int Task::statusCode() const +{ + return d->statusCode; +} + +const QString & Task::statusString() const +{ + return d->statusString; +} + +void Task::go(bool autoDelete) +{ + d->autoDelete = autoDelete; + + onGo(); +} + +bool Task::take(const QDomElement &x) +{ + const QObjectList *p = children(); + if(!p) + return false; + + // pass along the xml + QObjectListIt it(*p); + Task *t; + for(; it.current(); ++it) { + QObject *obj = it.current(); + if(!obj->inherits("XMPP::Task")) + continue; + + t = static_cast(obj); + if(t->take(x)) + return true; + } + + return false; +} + +void Task::safeDelete() +{ + if(d->deleteme) + return; + + d->deleteme = true; + if(!d->insig) + SafeDelete::deleteSingle(this); +} + +void Task::onGo() +{ +} + +void Task::onDisconnect() +{ + if(!d->done) { + d->success = false; + d->statusCode = ErrDisc; + d->statusString = tr("Disconnected"); + + // delay this so that tasks that react don't block the shutdown + QTimer::singleShot(0, this, SLOT(done())); + } +} + +void Task::send(const QDomElement &x) +{ + client()->send(x); +} + +void Task::setSuccess(int code, const QString &str) +{ + if(!d->done) { + d->success = true; + d->statusCode = code; + d->statusString = str; + done(); + } +} + +void Task::setError(const QDomElement &e) +{ + if(!d->done) { + d->success = false; + getErrorFromElement(e, &d->statusCode, &d->statusString); + done(); + } +} + +void Task::setError(int code, const QString &str) +{ + if(!d->done) { + d->success = false; + d->statusCode = code; + d->statusString = str; + done(); + } +} + +void Task::done() +{ + if(d->done || d->insig) + return; + d->done = true; + + if(d->deleteme || d->autoDelete) + d->deleteme = true; + + d->insig = true; + finished(); + d->insig = false; + + if(d->deleteme) + SafeDelete::deleteSingle(this); +} + +void Task::clientDisconnected() +{ + onDisconnect(); +} + +void Task::debug(const char *fmt, ...) +{ + char *buf; + QString str; + int size = 1024; + int r; + + do { + buf = new char[size]; + va_list ap; + va_start(ap, fmt); + r = vsnprintf(buf, size, fmt, ap); + va_end(ap); + + if(r != -1) + str = QString(buf); + + delete [] buf; + + size *= 2; + } while(r == -1); + + debug(str); +} + +void Task::debug(const QString &str) +{ + client()->debug(QString("%1: ").arg(className()) + str); +} + +bool Task::iqVerify(const QDomElement &x, const Jid &to, const QString &id, const QString &xmlns) +{ + if(x.tagName() != "iq") + return false; + + Jid from(x.attribute("from")); + Jid local = client()->jid(); + Jid server = client()->host(); + + // empty 'from' ? + if(from.isEmpty()) { + // allowed if we are querying the server + if(!to.isEmpty() && !to.compare(server)) + return false; + } + // from ourself? + else if(from.compare(local, false)) { + // allowed if we are querying ourself or the server + if(!to.isEmpty() && !to.compare(local, false) && !to.compare(server)) + return false; + } + // from anywhere else? + else { + if(!from.compare(to)) + return false; + } + + if(!id.isEmpty()) { + if(x.attribute("id") != id) + return false; + } + + if(!xmlns.isEmpty()) { + if(queryNS(x) != xmlns) + return false; + } + + return true; +} + +//--------------------------------------------------------------------------- +// LiveRosterItem +//--------------------------------------------------------------------------- +LiveRosterItem::LiveRosterItem(const Jid &jid) +:RosterItem(jid) +{ + setFlagForDelete(false); +} + +LiveRosterItem::LiveRosterItem(const RosterItem &i) +{ + setRosterItem(i); + setFlagForDelete(false); +} + +LiveRosterItem::~LiveRosterItem() +{ +} + +void LiveRosterItem::setRosterItem(const RosterItem &i) +{ + setJid(i.jid()); + setName(i.name()); + setGroups(i.groups()); + setSubscription(i.subscription()); + setAsk(i.ask()); + setIsPush(i.isPush()); +} + +ResourceList & LiveRosterItem::resourceList() +{ + return v_resourceList; +} + +ResourceList::Iterator LiveRosterItem::priority() +{ + return v_resourceList.priority(); +} + +const ResourceList & LiveRosterItem::resourceList() const +{ + return v_resourceList; +} + +ResourceList::ConstIterator LiveRosterItem::priority() const +{ + return v_resourceList.priority(); +} + +bool LiveRosterItem::isAvailable() const +{ + if(v_resourceList.count() > 0) + return true; + return false; +} + +const Status & LiveRosterItem::lastUnavailableStatus() const +{ + return v_lastUnavailableStatus; +} + +bool LiveRosterItem::flagForDelete() const +{ + return v_flagForDelete; +} + +void LiveRosterItem::setLastUnavailableStatus(const Status &s) +{ + v_lastUnavailableStatus = s; +} + +void LiveRosterItem::setFlagForDelete(bool b) +{ + v_flagForDelete = b; +} + +//--------------------------------------------------------------------------- +// LiveRoster +//--------------------------------------------------------------------------- +LiveRoster::LiveRoster() +:QValueList() +{ +} + +LiveRoster::~LiveRoster() +{ +} + +void LiveRoster::flagAllForDelete() +{ + for(Iterator it = begin(); it != end(); ++it) + (*it).setFlagForDelete(true); +} + +LiveRoster::Iterator LiveRoster::find(const Jid &j, bool compareRes) +{ + Iterator it; + for(it = begin(); it != end(); ++it) { + if((*it).jid().compare(j, compareRes)) + break; + } + return it; +} + +LiveRoster::ConstIterator LiveRoster::find(const Jid &j, bool compareRes) const +{ + ConstIterator it; + for(it = begin(); it != end(); ++it) { + if((*it).jid().compare(j, compareRes)) + break; + } + return it; +} + +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/types.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-im/types.cpp new file mode 100644 index 00000000..1e457584 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/types.cpp @@ -0,0 +1,1876 @@ +/* + * types.cpp - IM data types + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"im.h" +#include "protocol.h" +#include +#include + +#define NS_XML "http://www.w3.org/XML/1998/namespace" + +static QDomElement textTag(QDomDocument *doc, const QString &name, const QString &content) +{ + QDomElement tag = doc->createElement(name); + QDomText text = doc->createTextNode(content); + tag.appendChild(text); + + return tag; +} + +static QString tagContent(const QDomElement &e) +{ + // look for some tag content + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomText i = n.toText(); + if(i.isNull()) + continue; + return i.data(); + } + + return ""; +} + +static QDateTime stamp2TS(const QString &ts) +{ + if(ts.length() != 17) + return QDateTime(); + + int year = ts.mid(0,4).toInt(); + int month = ts.mid(4,2).toInt(); + int day = ts.mid(6,2).toInt(); + + int hour = ts.mid(9,2).toInt(); + int min = ts.mid(12,2).toInt(); + int sec = ts.mid(15,2).toInt(); + + QDate xd; + xd.setYMD(year, month, day); + if(!xd.isValid()) + return QDateTime(); + + QTime xt; + xt.setHMS(hour, min, sec); + if(!xt.isValid()) + return QDateTime(); + + return QDateTime(xd, xt); +} + +/*static QString TS2stamp(const QDateTime &d) +{ + QString str; + + str.sprintf("%04d%02d%02dT%02d:%02d:%02d", + d.date().year(), + d.date().month(), + d.date().day(), + d.time().hour(), + d.time().minute(), + d.time().second()); + + return str; +}*/ + +namespace XMPP +{ + +//---------------------------------------------------------------------------- +// Url +//---------------------------------------------------------------------------- +class Url::Private +{ +public: + QString url; + QString desc; +}; + +//! \brief Construct Url object with a given URL and Description. +//! +//! This function will construct a Url object. +//! \param QString - url (default: empty string) +//! \param QString - description of url (default: empty string) +//! \sa setUrl() setDesc() +Url::Url(const QString &url, const QString &desc) +{ + d = new Private; + d->url = url; + d->desc = desc; +} + +//! \brief Construct Url object. +//! +//! Overloaded constructor which will constructs a exact copy of the Url object that was passed to the constructor. +//! \param Url - Url Object +Url::Url(const Url &from) +{ + d = new Private; + *this = from; +} + +//! \brief operator overloader needed for d pointer (Internel). +Url & Url::operator=(const Url &from) +{ + *d = *from.d; + return *this; +} + +//! \brief destroy Url object. +Url::~Url() +{ + delete d; +} + +//! \brief Get url information. +//! +//! Returns url information. +QString Url::url() const +{ + return d->url; +} + +//! \brief Get Description information. +//! +//! Returns desction of the URL. +QString Url::desc() const +{ + return d->desc; +} + +//! \brief Set Url information. +//! +//! Set url information. +//! \param url - url string (eg: http://psi.affinix.com/) +void Url::setUrl(const QString &url) +{ + d->url = url; +} + +//! \brief Set Description information. +//! +//! Set description of the url. +//! \param desc - description of url +void Url::setDesc(const QString &desc) +{ + d->desc = desc; +} + +//---------------------------------------------------------------------------- +// Message +//---------------------------------------------------------------------------- +class Message::Private +{ +public: + Jid to, from; + QString id, type, lang; + + StringMap subject, body, xHTMLBody; + + QString thread; + Stanza::Error error; + + // extensions + QDateTime timeStamp; + UrlList urlList; + QValueList eventList; + QString eventId; + QString xencrypted, invite; + + bool spooled, wasEncrypted; +}; + +//! \brief Constructs Message with given Jid information. +//! +//! This function will construct a Message container. +//! \param to - specify reciver (default: empty string) +Message::Message(const Jid &to) +{ + d = new Private; + d->to = to; + d->spooled = false; + d->wasEncrypted = false; + /*d->flag = false; + d->spooled = false; + d->wasEncrypted = false; + d->errorCode = -1;*/ +} + +//! \brief Constructs a copy of Message object +//! +//! Overloaded constructor which will constructs a exact copy of the Message +//! object that was passed to the constructor. +//! \param from - Message object you want to copy +Message::Message(const Message &from) +{ + d = new Private; + *this = from; +} + +//! \brief Required for internel use. +Message & Message::operator=(const Message &from) +{ + *d = *from.d; + return *this; +} + +//! \brief Destroy Message object. +Message::~Message() +{ + delete d; +} + +//! \brief Return receiver's Jid information. +Jid Message::to() const +{ + return d->to; +} + +//! \brief Return sender's Jid information. +Jid Message::from() const +{ + return d->from; +} + +QString Message::id() const +{ + return d->id; +} + +//! \brief Return type information +QString Message::type() const +{ + return d->type; +} + +QString Message::lang() const +{ + return d->lang; +} + +//! \brief Return subject information. +QString Message::subject(const QString &lang) const +{ + return d->subject[lang]; +} + +//! \brief Return body information. +//! +//! This function will return a plain text or the Richtext version if it +//! it exists. +//! \param rich - Returns richtext if true and plain text if false. (default: false) +//! \note Richtext is in Qt's richtext format and not in xhtml. +QString Message::body(const QString &lang) const +{ + return d->body[lang]; +} + +QString Message::xHTMLBody(const QString &lang) const +{ + return d->xHTMLBody[lang]; +} + +QString Message::thread() const +{ + return d->thread; +} + +Stanza::Error Message::error() const +{ + return d->error; +} + +//! \brief Set receivers information +//! +//! \param to - Receivers Jabber id +void Message::setTo(const Jid &j) +{ + d->to = j; + //d->flag = false; +} + +void Message::setFrom(const Jid &j) +{ + d->from = j; + //d->flag = false; +} + +void Message::setId(const QString &s) +{ + d->id = s; +} + +//! \brief Set Type of message +//! +//! \param type - type of message your going to send +void Message::setType(const QString &s) +{ + d->type = s; + //d->flag = false; +} + +void Message::setLang(const QString &s) +{ + d->lang = s; +} + +//! \brief Set subject +//! +//! \param subject - Subject information +void Message::setSubject(const QString &s, const QString &lang) +{ + d->subject[lang] = s; + //d->flag = false; +} + +//! \brief Set body +//! +//! \param body - body information +//! \param rich - set richtext if true and set plaintext if false. +//! \note Richtext support will be implemented in the future... Sorry. +void Message::setBody(const QString &s, const QString &lang) +{ + d->body[lang] = s; +} + +void Message::setXHTMLBody(const QString &s, const QString &lang, const QString &attr) +{ + //ugly but needed if s is not a node but a list of leaf + + QString content = "\n" + s +"\n"; + d->xHTMLBody[lang] = content; +} + +void Message::setThread(const QString &s) +{ + d->thread = s; +} + +void Message::setError(const Stanza::Error &err) +{ + d->error = err; +} + +QDateTime Message::timeStamp() const +{ + return d->timeStamp; +} + +void Message::setTimeStamp(const QDateTime &ts) +{ + d->timeStamp = ts; +} + +//! \brief Return list of urls attached to message. +UrlList Message::urlList() const +{ + return d->urlList; +} + +//! \brief Add Url to the url list. +//! +//! \param url - url to append +void Message::urlAdd(const Url &u) +{ + d->urlList += u; +} + +//! \brief clear out the url list. +void Message::urlsClear() +{ + d->urlList.clear(); +} + +//! \brief Set urls to send +//! +//! \param urlList - list of urls to send +void Message::setUrlList(const UrlList &list) +{ + d->urlList = list; +} + +QString Message::eventId() const +{ + return d->eventId; +} + +void Message::setEventId(const QString& id) +{ + d->eventId = id; +} + +bool Message::containsEvents() const +{ + return !d->eventList.isEmpty(); +} + +bool Message::containsEvent(MsgEvent e) const +{ + return d->eventList.contains(e); +} + +void Message::addEvent(MsgEvent e) +{ + if (!d->eventList.contains(e)) { + if (e == CancelEvent || containsEvent(CancelEvent)) + d->eventList.clear(); // Reset list + d->eventList += e; + } +} + +QString Message::xencrypted() const +{ + return d->xencrypted; +} + +void Message::setXEncrypted(const QString &s) +{ + d->xencrypted = s; +} + +QString Message::invite() const +{ + return d->invite; +} + +void Message::setInvite(const QString &s) +{ + d->invite = s; +} + +bool Message::spooled() const +{ + return d->spooled; +} + +void Message::setSpooled(bool b) +{ + d->spooled = b; +} + +bool Message::wasEncrypted() const +{ + return d->wasEncrypted; +} + +void Message::setWasEncrypted(bool b) +{ + d->wasEncrypted = b; +} + +Stanza Message::toStanza(Stream *stream) const +{ + Stanza s = stream->createStanza(Stanza::Message, d->to, d->type); + if(!d->from.isEmpty()) + s.setFrom(d->from); + if(!d->id.isEmpty()) + s.setId(d->id); + if(!d->lang.isEmpty()) + s.setLang(d->lang); + + StringMap::ConstIterator it; + for(it = d->subject.begin(); it != d->subject.end(); ++it) { + const QString &str = it.data(); + if(!str.isEmpty()) { + QDomElement e = s.createTextElement(s.baseNS(), "subject", str); + if(!it.key().isEmpty()) + e.setAttributeNS(NS_XML, "xml:lang", it.key()); + s.appendChild(e); + } + } + for(it = d->body.begin(); it != d->body.end(); ++it) { + const QString &str = it.data(); + if(!str.isEmpty()) { + QDomElement e = s.createTextElement(s.baseNS(), "body", str); + if(!it.key().isEmpty()) + e.setAttributeNS(NS_XML, "xml:lang", it.key()); + s.appendChild(e); + } + } + if ( !d->xHTMLBody.isEmpty()) { + QDomElement parent = s.createElement(s.xhtmlImNS(), "html"); + for(it = d->xHTMLBody.begin(); it != d->xHTMLBody.end(); ++it) { + const QString &str = it.data(); + if(!str.isEmpty()) { + QDomElement child = s.createXHTMLElement(str); + if(!it.key().isEmpty()) + child.setAttributeNS(NS_XML, "xml:lang", it.key()); + parent.appendChild(child); + } + } + s.appendChild(parent); + } + if(d->type == "error") + s.setError(d->error); + + // timestamp + /*if(!d->timeStamp.isNull()) { + QDomElement e = s.createElement("jabber:x:delay", "x"); + e.setAttribute("stamp", TS2stamp(d->timeStamp)); + s.appendChild(e); + }*/ + + // urls + for(QValueList::ConstIterator uit = d->urlList.begin(); uit != d->urlList.end(); ++uit) { + QDomElement x = s.createElement("jabber:x:oob", "x"); + x.appendChild(s.createTextElement("jabber:x:oob", "url", (*uit).url())); + if(!(*uit).desc().isEmpty()) + x.appendChild(s.createTextElement("jabber:x:oob", "desc", (*uit).desc())); + s.appendChild(x); + } + + // events + if (!d->eventList.isEmpty()) { + QDomElement x = s.createElement("jabber:x:event", "x"); + + if (d->body.isEmpty()) { + if (d->eventId.isEmpty()) + x.appendChild(s.createElement("jabber:x:event","id")); + else + x.appendChild(s.createTextElement("jabber:x:event","id",d->eventId)); + } + else if (d->type=="chat" || d->type=="groupchat") + s.appendChild( s.createElement(NS_CHATSTATES , "active" ) ); + + bool need_x_event=false; + for(QValueList::ConstIterator ev = d->eventList.begin(); ev != d->eventList.end(); ++ev) { + switch (*ev) { + case OfflineEvent: + x.appendChild(s.createElement("jabber:x:event", "offline")); + need_x_event=true; + break; + case DeliveredEvent: + x.appendChild(s.createElement("jabber:x:event", "delivered")); + need_x_event=true; + break; + case DisplayedEvent: + x.appendChild(s.createElement("jabber:x:event", "displayed")); + need_x_event=true; + break; + case ComposingEvent: + x.appendChild(s.createElement("jabber:x:event", "composing")); + need_x_event=true; + if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "composing" ) ); + break; + case CancelEvent: + need_x_event=true; + if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "paused" ) ); + break; + case InactiveEvent: + if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "inactive" ) ); + break; + case GoneEvent: + if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "gone" ) ); + break; + } + } + if(need_x_event) //we don't need to have the (empty) x:event element if this is only or + s.appendChild(x); + } + + + // xencrypted + if(!d->xencrypted.isEmpty()) + s.appendChild(s.createTextElement("jabber:x:encrypted", "x", d->xencrypted)); + + // invite + if(!d->invite.isEmpty()) { + QDomElement e = s.createElement("jabber:x:conference", "x"); + e.setAttribute("jid", d->invite); + s.appendChild(e); + } + + return s; +} + +bool Message::fromStanza(const Stanza &s, int timeZoneOffset) +{ + if(s.kind() != Stanza::Message) + return false; + + setTo(s.to()); + setFrom(s.from()); + setId(s.id()); + setType(s.type()); + setLang(s.lang()); + + d->subject.clear(); + d->body.clear(); + d->thread = QString(); + d->eventList.clear(); + + QDomElement root = s.element(); + + QDomNodeList nl = root.childNodes(); + uint n; + for(n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement()) { + QDomElement e = i.toElement(); + if(e.namespaceURI() == s.baseNS()) { + if(e.tagName() == "subject") { + QString lang = e.attributeNS(NS_XML, "lang", ""); + d->subject[lang] = e.text(); + } + else if(e.tagName() == "body") { + QString lang = e.attributeNS(NS_XML, "lang", ""); + d->body[lang] = e.text(); + } + else if(e.tagName() == "thread") + d->thread = e.text(); + } + else if (e.namespaceURI() == s.xhtmlImNS()) { + if (e.tagName() == "html") { + QDomNodeList htmlNL= e.childNodes(); + for (unsigned int x = 0; x < htmlNL.count(); x++) { + QDomElement i = htmlNL.item(x).toElement(); + + if (i.tagName() == "body") { + QDomDocument RichText; + QString lang = i.attributeNS(NS_XML, "lang", ""); + RichText.appendChild(i); + d-> xHTMLBody[lang] = RichText.toString(); + } + } + } + } + else if (e.namespaceURI() == NS_CHATSTATES) + { + if(e.tagName() == "active") + { + //like in JEP-0022 we let the client know that we can receive ComposingEvent + // (we can do that according to §4.6 of the JEP-0085) + d->eventList += ComposingEvent; + d->eventList += InactiveEvent; + d->eventList += GoneEvent; + } + else if (e.tagName() == "composing") + { + d->eventList += ComposingEvent; + } + else if (e.tagName() == "paused") + { + d->eventList += CancelEvent; + } + else if (e.tagName() == "inactive") + { + d->eventList += InactiveEvent; + } + else if (e.tagName() == "gone") + { + d->eventList += GoneEvent; + } + } + else { + //printf("extension element: [%s]\n", e.tagName().latin1()); + } + } + } + + if(s.type() == "error") + d->error = s.error(); + + // timestamp + QDomElement t = root.elementsByTagNameNS("jabber:x:delay", "x").item(0).toElement(); + if(!t.isNull()) { + d->timeStamp = stamp2TS(t.attribute("stamp")); + d->timeStamp = d->timeStamp.addSecs(timeZoneOffset * 3600); + d->spooled = true; + } + else { + d->timeStamp = QDateTime::currentDateTime(); + d->spooled = false; + } + + // urls + d->urlList.clear(); + nl = root.elementsByTagNameNS("jabber:x:oob", "x"); + for(n = 0; n < nl.count(); ++n) { + QDomElement t = nl.item(n).toElement(); + Url u; + u.setUrl(t.elementsByTagName("url").item(0).toElement().text()); + u.setDesc(t.elementsByTagName("desc").item(0).toElement().text()); + d->urlList += u; + } + + // events + nl = root.elementsByTagNameNS("jabber:x:event", "x"); + if (nl.count()) { + nl = nl.item(0).childNodes(); + for(n = 0; n < nl.count(); ++n) { + QString evtag = nl.item(n).toElement().tagName(); + if (evtag == "id") { + d->eventId = nl.item(n).toElement().text(); + } + else if (evtag == "displayed") + d->eventList += DisplayedEvent; + else if (evtag == "composing") + d->eventList += ComposingEvent; + else if (evtag == "delivered") + d->eventList += DeliveredEvent; + else if (evtag == "offline") + d->eventList += OfflineEvent; + } + if (d->eventList.isEmpty()) + d->eventList += CancelEvent; + } + + // xencrypted + t = root.elementsByTagNameNS("jabber:x:encrypted", "x").item(0).toElement(); + if(!t.isNull()) + d->xencrypted = t.text(); + else + d->xencrypted = QString(); + + // invite + t = root.elementsByTagNameNS("jabber:x:conference", "x").item(0).toElement(); + if(!t.isNull()) + d->invite = t.attribute("jid"); + else + d->invite = QString(); + + return true; +} + +//--------------------------------------------------------------------------- +// Subscription +//--------------------------------------------------------------------------- +Subscription::Subscription(SubType type) +{ + value = type; +} + +int Subscription::type() const +{ + return value; +} + +QString Subscription::toString() const +{ + switch(value) { + case Remove: + return "remove"; + case Both: + return "both"; + case From: + return "from"; + case To: + return "to"; + case None: + default: + return "none"; + } +} + +bool Subscription::fromString(const QString &s) +{ + if(s == "remove") + value = Remove; + else if(s == "both") + value = Both; + else if(s == "from") + value = From; + else if(s == "to") + value = To; + else if(s == "none") + value = None; + else + return false; + + return true; +} + + +//--------------------------------------------------------------------------- +// Status +//--------------------------------------------------------------------------- +Status::Status(const QString &show, const QString &status, int priority, bool available) +{ + v_isAvailable = available; + v_show = show; + v_status = status; + v_priority = priority; + v_timeStamp = QDateTime::currentDateTime(); + v_isInvisible = false; + ecode = -1; +} + +Status::~Status() +{ +} + +bool Status::hasError() const +{ + return (ecode != -1); +} + +void Status::setError(int code, const QString &str) +{ + ecode = code; + estr = str; +} + +void Status::setIsAvailable(bool available) +{ + v_isAvailable = available; +} + +void Status::setIsInvisible(bool invisible) +{ + v_isInvisible = invisible; +} + +void Status::setPriority(int x) +{ + v_priority = x; +} + +void Status::setShow(const QString & _show) +{ + v_show = _show; +} + +void Status::setStatus(const QString & _status) +{ + v_status = _status; +} + +void Status::setTimeStamp(const QDateTime & _timestamp) +{ + v_timeStamp = _timestamp; +} + +void Status::setKeyID(const QString &key) +{ + v_key = key; +} + +void Status::setXSigned(const QString &s) +{ + v_xsigned = s; +} + +void Status::setSongTitle(const QString & _songtitle) +{ + v_songTitle = _songtitle; +} + +void Status::setCapsNode(const QString & _capsNode) +{ + v_capsNode = _capsNode; +} + +void Status::setCapsVersion(const QString & _capsVersion) +{ + v_capsVersion = _capsVersion; +} + +void Status::setCapsExt(const QString & _capsExt) +{ + v_capsExt = _capsExt; +} + +bool Status::isAvailable() const +{ + return v_isAvailable; +} + +bool Status::isAway() const +{ + if(v_show == "away" || v_show == "xa" || v_show == "dnd") + return true; + + return false; +} + +bool Status::isInvisible() const +{ + return v_isInvisible; +} + +int Status::priority() const +{ + return v_priority; +} + +const QString & Status::show() const +{ + return v_show; +} +const QString & Status::status() const +{ + return v_status; +} + +QDateTime Status::timeStamp() const +{ + return v_timeStamp; +} + +const QString & Status::keyID() const +{ + return v_key; +} + +const QString & Status::xsigned() const +{ + return v_xsigned; +} + +const QString & Status::songTitle() const +{ + return v_songTitle; +} + +const QString & Status::capsNode() const +{ + return v_capsNode; +} + +const QString & Status::capsVersion() const +{ + return v_capsVersion; +} + +const QString & Status::capsExt() const +{ + return v_capsExt; +} + +int Status::errorCode() const +{ + return ecode; +} + +const QString & Status::errorString() const +{ + return estr; +} + + +//--------------------------------------------------------------------------- +// Resource +//--------------------------------------------------------------------------- +Resource::Resource(const QString &name, const Status &stat) +{ + v_name = name; + v_status = stat; +} + +Resource::~Resource() +{ +} + +const QString & Resource::name() const +{ + return v_name; +} + +int Resource::priority() const +{ + return v_status.priority(); +} + +const Status & Resource::status() const +{ + return v_status; +} + +void Resource::setName(const QString & _name) +{ + v_name = _name; +} + +void Resource::setStatus(const Status & _status) +{ + v_status = _status; +} + + +//--------------------------------------------------------------------------- +// ResourceList +//--------------------------------------------------------------------------- +ResourceList::ResourceList() +:QValueList() +{ +} + +ResourceList::~ResourceList() +{ +} + +ResourceList::Iterator ResourceList::find(const QString & _find) +{ + for(ResourceList::Iterator it = begin(); it != end(); ++it) { + if((*it).name() == _find) + return it; + } + + return end(); +} + +ResourceList::Iterator ResourceList::priority() +{ + ResourceList::Iterator highest = end(); + + for(ResourceList::Iterator it = begin(); it != end(); ++it) { + if(highest == end() || (*it).priority() > (*highest).priority()) + highest = it; + } + + return highest; +} + +ResourceList::ConstIterator ResourceList::find(const QString & _find) const +{ + for(ResourceList::ConstIterator it = begin(); it != end(); ++it) { + if((*it).name() == _find) + return it; + } + + return end(); +} + +ResourceList::ConstIterator ResourceList::priority() const +{ + ResourceList::ConstIterator highest = end(); + + for(ResourceList::ConstIterator it = begin(); it != end(); ++it) { + if(highest == end() || (*it).priority() > (*highest).priority()) + highest = it; + } + + return highest; +} + + +//--------------------------------------------------------------------------- +// RosterItem +//--------------------------------------------------------------------------- +RosterItem::RosterItem(const Jid &_jid) +{ + v_jid = _jid; +} + +RosterItem::~RosterItem() +{ +} + +const Jid & RosterItem::jid() const +{ + return v_jid; +} + +const QString & RosterItem::name() const +{ + return v_name; +} + +const QStringList & RosterItem::groups() const +{ + return v_groups; +} + +const Subscription & RosterItem::subscription() const +{ + return v_subscription; +} + +const QString & RosterItem::ask() const +{ + return v_ask; +} + +bool RosterItem::isPush() const +{ + return v_push; +} + +bool RosterItem::inGroup(const QString &g) const +{ + for(QStringList::ConstIterator it = v_groups.begin(); it != v_groups.end(); ++it) { + if(*it == g) + return true; + } + return false; +} + +void RosterItem::setJid(const Jid &_jid) +{ + v_jid = _jid; +} + +void RosterItem::setName(const QString &_name) +{ + v_name = _name; +} + +void RosterItem::setGroups(const QStringList &_groups) +{ + v_groups = _groups; +} + +void RosterItem::setSubscription(const Subscription &type) +{ + v_subscription = type; +} + +void RosterItem::setAsk(const QString &_ask) +{ + v_ask = _ask; +} + +void RosterItem::setIsPush(bool b) +{ + v_push = b; +} + +bool RosterItem::addGroup(const QString &g) +{ + if(inGroup(g)) + return false; + + v_groups += g; + return true; +} + +bool RosterItem::removeGroup(const QString &g) +{ + for(QStringList::Iterator it = v_groups.begin(); it != v_groups.end(); ++it) { + if(*it == g) { + v_groups.remove(it); + return true; + } + } + + return false; +} + +QDomElement RosterItem::toXml(QDomDocument *doc) const +{ + QDomElement item = doc->createElement("item"); + item.setAttribute("jid", v_jid.full()); + item.setAttribute("name", v_name); + item.setAttribute("subscription", v_subscription.toString()); + if(!v_ask.isEmpty()) + item.setAttribute("ask", v_ask); + for(QStringList::ConstIterator it = v_groups.begin(); it != v_groups.end(); ++it) + item.appendChild(textTag(doc, "group", *it)); + + return item; +} + +bool RosterItem::fromXml(const QDomElement &item) +{ + if(item.tagName() != "item") + return false; + Jid j(item.attribute("jid")); + if(!j.isValid()) + return false; + QString na = item.attribute("name"); + Subscription s; + if(!s.fromString(item.attribute("subscription")) ) + return false; + QStringList g; + for(QDomNode n = item.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + if(i.tagName() == "group") + g += tagContent(i); + } + QString a = item.attribute("ask"); + + v_jid = j; + v_name = na; + v_subscription = s; + v_groups = g; + v_ask = a; + + return true; +} + + +//--------------------------------------------------------------------------- +// Roster +//--------------------------------------------------------------------------- +Roster::Roster() +:QValueList() +{ +} + +Roster::~Roster() +{ +} + +Roster::Iterator Roster::find(const Jid &j) +{ + for(Roster::Iterator it = begin(); it != end(); ++it) { + if((*it).jid().compare(j)) + return it; + } + + return end(); +} + +Roster::ConstIterator Roster::find(const Jid &j) const +{ + for(Roster::ConstIterator it = begin(); it != end(); ++it) { + if((*it).jid().compare(j)) + return it; + } + + return end(); +} + + +//--------------------------------------------------------------------------- +// FormField +//--------------------------------------------------------------------------- +FormField::FormField(const QString &type, const QString &value) +{ + v_type = misc; + if(!type.isEmpty()) { + int x = tagNameToType(type); + if(x != -1) + v_type = x; + } + v_value = value; +} + +FormField::~FormField() +{ +} + +int FormField::type() const +{ + return v_type; +} + +QString FormField::realName() const +{ + return typeToTagName(v_type); +} + +QString FormField::fieldName() const +{ + switch(v_type) { + case username: return QObject::tr("Username"); + case nick: return QObject::tr("Nickname"); + case password: return QObject::tr("Password"); + case name: return QObject::tr("Name"); + case first: return QObject::tr("First Name"); + case last: return QObject::tr("Last Name"); + case email: return QObject::tr("E-mail"); + case address: return QObject::tr("Address"); + case city: return QObject::tr("City"); + case state: return QObject::tr("State"); + case zip: return QObject::tr("Zipcode"); + case phone: return QObject::tr("Phone"); + case url: return QObject::tr("URL"); + case date: return QObject::tr("Date"); + case misc: return QObject::tr("Misc"); + default: return ""; + }; +} + +bool FormField::isSecret() const +{ + return (type() == password); +} + +const QString & FormField::value() const +{ + return v_value; +} + +void FormField::setType(int x) +{ + v_type = x; +} + +bool FormField::setType(const QString &in) +{ + int x = tagNameToType(in); + if(x == -1) + return false; + + v_type = x; + return true; +} + +void FormField::setValue(const QString &in) +{ + v_value = in; +} + +int FormField::tagNameToType(const QString &in) const +{ + if(!in.compare("username")) return username; + if(!in.compare("nick")) return nick; + if(!in.compare("password")) return password; + if(!in.compare("name")) return name; + if(!in.compare("first")) return first; + if(!in.compare("last")) return last; + if(!in.compare("email")) return email; + if(!in.compare("address")) return address; + if(!in.compare("city")) return city; + if(!in.compare("state")) return state; + if(!in.compare("zip")) return zip; + if(!in.compare("phone")) return phone; + if(!in.compare("url")) return url; + if(!in.compare("date")) return date; + if(!in.compare("misc")) return misc; + + return -1; +} + +QString FormField::typeToTagName(int type) const +{ + switch(type) { + case username: return "username"; + case nick: return "nick"; + case password: return "password"; + case name: return "name"; + case first: return "first"; + case last: return "last"; + case email: return "email"; + case address: return "address"; + case city: return "city"; + case state: return "state"; + case zip: return "zipcode"; + case phone: return "phone"; + case url: return "url"; + case date: return "date"; + case misc: return "misc"; + default: return ""; + }; +} + + +//--------------------------------------------------------------------------- +// Form +//--------------------------------------------------------------------------- +Form::Form(const Jid &j) +:QValueList() +{ + setJid(j); +} + +Form::~Form() +{ +} + +Jid Form::jid() const +{ + return v_jid; +} + +QString Form::instructions() const +{ + return v_instructions; +} + +QString Form::key() const +{ + return v_key; +} + +void Form::setJid(const Jid &j) +{ + v_jid = j; +} + +void Form::setInstructions(const QString &s) +{ + v_instructions = s; +} + +void Form::setKey(const QString &s) +{ + v_key = s; +} + + +//--------------------------------------------------------------------------- +// SearchResult +//--------------------------------------------------------------------------- +SearchResult::SearchResult(const Jid &jid) +{ + setJid(jid); +} + +SearchResult::~SearchResult() +{ +} + +const Jid & SearchResult::jid() const +{ + return v_jid; +} + +const QString & SearchResult::nick() const +{ + return v_nick; +} + +const QString & SearchResult::first() const +{ + return v_first; +} + +const QString & SearchResult::last() const +{ + return v_last; +} + +const QString & SearchResult::email() const +{ + return v_email; +} + +void SearchResult::setJid(const Jid &jid) +{ + v_jid = jid; +} + +void SearchResult::setNick(const QString &nick) +{ + v_nick = nick; +} + +void SearchResult::setFirst(const QString &first) +{ + v_first = first; +} + +void SearchResult::setLast(const QString &last) +{ + v_last = last; +} + +void SearchResult::setEmail(const QString &email) +{ + v_email = email; +} + +//--------------------------------------------------------------------------- +// Features +//--------------------------------------------------------------------------- + +Features::Features() +{ +} + +Features::Features(const QStringList &l) +{ + setList(l); +} + +Features::Features(const QString &str) +{ + QStringList l; + l << str; + + setList(l); +} + +Features::~Features() +{ +} + +QStringList Features::list() const +{ + return _list; +} + +void Features::setList(const QStringList &l) +{ + _list = l; +} + +bool Features::test(const QStringList &ns) const +{ + QStringList::ConstIterator it = ns.begin(); + for ( ; it != ns.end(); ++it) + if ( _list.find( *it ) != _list.end() ) + return true; + + return false; +} + +#define FID_REGISTER "jabber:iq:register" +bool Features::canRegister() const +{ + QStringList ns; + ns << FID_REGISTER; + + return test(ns); +} + +#define FID_SEARCH "jabber:iq:search" +bool Features::canSearch() const +{ + QStringList ns; + ns << FID_SEARCH; + + return test(ns); +} + +#define FID_XHTML "http://jabber.org/protocol/xhtml-im" +bool Features::canXHTML() const +{ + QStringList ns; + + ns << FID_XHTML; + + return test(ns); +} + +#define FID_GROUPCHAT "jabber:iq:conference" +bool Features::canGroupchat() const +{ + QStringList ns; + ns << "http://jabber.org/protocol/muc"; + ns << FID_GROUPCHAT; + + return test(ns); +} + +#define FID_VOICE "http://www.google.com/xmpp/protocol/voice/v1" +bool Features::canVoice() const +{ + QStringList ns; + ns << FID_VOICE; + + return test(ns); +} + +#define FID_GATEWAY "jabber:iq:gateway" +bool Features::isGateway() const +{ + QStringList ns; + ns << FID_GATEWAY; + + return test(ns); +} + +#define FID_DISCO "http://jabber.org/protocol/disco" +bool Features::canDisco() const +{ + QStringList ns; + ns << FID_DISCO; + ns << "http://jabber.org/protocol/disco#info"; + ns << "http://jabber.org/protocol/disco#items"; + + return test(ns); +} + +#define FID_VCARD "vcard-temp" +bool Features::haveVCard() const +{ + QStringList ns; + ns << FID_VCARD; + + return test(ns); +} + +// custom Psi acitons +#define FID_ADD "psi:add" + +class Features::FeatureName : public QObject +{ + Q_OBJECT +public: + FeatureName() + : QObject(qApp) + { + id2s[FID_Invalid] = tr("ERROR: Incorrect usage of Features class"); + id2s[FID_None] = tr("None"); + id2s[FID_Register] = tr("Register"); + id2s[FID_Search] = tr("Search"); + id2s[FID_Groupchat] = tr("Groupchat"); + id2s[FID_Gateway] = tr("Gateway"); + id2s[FID_Disco] = tr("Service Discovery"); + id2s[FID_VCard] = tr("VCard"); + + // custom Psi actions + id2s[FID_Add] = tr("Add to roster"); + + // compute reverse map + //QMap::Iterator it = id2s.begin(); + //for ( ; it != id2s.end(); ++it) + // s2id[it.data()] = it.key(); + + id2f[FID_Register] = FID_REGISTER; + id2f[FID_Search] = FID_SEARCH; + id2f[FID_Groupchat] = FID_GROUPCHAT; + id2f[FID_Gateway] = FID_GATEWAY; + id2f[FID_Disco] = FID_DISCO; + id2f[FID_VCard] = FID_VCARD; + + // custom Psi actions + id2f[FID_Add] = FID_ADD; + } + + //QMap s2id; + QMap id2s; + QMap id2f; +}; + +static Features::FeatureName *featureName = 0; + +long Features::id() const +{ + if ( _list.count() > 1 ) + return FID_Invalid; + else if ( canRegister() ) + return FID_Register; + else if ( canSearch() ) + return FID_Search; + else if ( canGroupchat() ) + return FID_Groupchat; + else if ( isGateway() ) + return FID_Gateway; + else if ( canDisco() ) + return FID_Disco; + else if ( haveVCard() ) + return FID_VCard; + else if ( test(FID_ADD) ) + return FID_Add; + + return FID_None; +} + +long Features::id(const QString &feature) +{ + Features f(feature); + return f.id(); +} + +QString Features::feature(long id) +{ + if ( !featureName ) + featureName = new FeatureName(); + + return featureName->id2f[id]; +} + +QString Features::name(long id) +{ + if ( !featureName ) + featureName = new FeatureName(); + + return featureName->id2s[id]; +} + +QString Features::name() const +{ + return name(id()); +} + +QString Features::name(const QString &feature) +{ + Features f(feature); + return f.name(f.id()); +} + +//--------------------------------------------------------------------------- +// DiscoItem +//--------------------------------------------------------------------------- +class DiscoItem::Private +{ +public: + Private() + { + action = None; + } + + Jid jid; + QString name; + QString node; + Action action; + + Features features; + Identities identities; +}; + +DiscoItem::DiscoItem() +{ + d = new Private; +} + +DiscoItem::DiscoItem(const DiscoItem &from) +{ + d = new Private; + *this = from; +} + +DiscoItem & DiscoItem::operator= (const DiscoItem &from) +{ + d->jid = from.d->jid; + d->name = from.d->name; + d->node = from.d->node; + d->action = from.d->action; + d->features = from.d->features; + d->identities = from.d->identities; + + return *this; +} + +DiscoItem::~DiscoItem() +{ + delete d; +} + +AgentItem DiscoItem::toAgentItem() const +{ + AgentItem ai; + + ai.setJid( jid() ); + ai.setName( name() ); + + Identity id; + if ( !identities().isEmpty() ) + id = identities().first(); + + ai.setCategory( id.category ); + ai.setType( id.type ); + + ai.setFeatures( d->features ); + + return ai; +} + +void DiscoItem::fromAgentItem(const AgentItem &ai) +{ + setJid( ai.jid() ); + setName( ai.name() ); + + Identity id; + id.category = ai.category(); + id.type = ai.type(); + id.name = ai.name(); + + Identities idList; + idList << id; + + setIdentities( idList ); + + setFeatures( ai.features() ); +} + +const Jid &DiscoItem::jid() const +{ + return d->jid; +} + +void DiscoItem::setJid(const Jid &j) +{ + d->jid = j; +} + +const QString &DiscoItem::name() const +{ + return d->name; +} + +void DiscoItem::setName(const QString &n) +{ + d->name = n; +} + +const QString &DiscoItem::node() const +{ + return d->node; +} + +void DiscoItem::setNode(const QString &n) +{ + d->node = n; +} + +DiscoItem::Action DiscoItem::action() const +{ + return d->action; +} + +void DiscoItem::setAction(Action a) +{ + d->action = a; +} + +const Features &DiscoItem::features() const +{ + return d->features; +} + +void DiscoItem::setFeatures(const Features &f) +{ + d->features = f; +} + +const DiscoItem::Identities &DiscoItem::identities() const +{ + return d->identities; +} + +void DiscoItem::setIdentities(const Identities &i) +{ + d->identities = i; + + if ( name().isEmpty() && i.count() ) + setName( i.first().name ); +} + + +DiscoItem::Action DiscoItem::string2action(QString s) +{ + Action a; + + if ( s == "update" ) + a = Update; + else if ( s == "remove" ) + a = Remove; + else + a = None; + + return a; +} + +QString DiscoItem::action2string(Action a) +{ + QString s; + + if ( a == Update ) + s = "update"; + else if ( a == Remove ) + s = "remove"; + else + s = QString::null; + + return s; +} + +} + +#include"types.moc" diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.cpp new file mode 100644 index 00000000..ffd7e6ae --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.cpp @@ -0,0 +1,2120 @@ +/* + * tasks.cpp - basic tasks + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp_tasks.h" + +#include"base64.h" +//#include"sha1.h" +#include"xmpp_xmlcommon.h" +//#include"xmpp_stream.h" +//#include"xmpp_types.h" +#include"xmpp_vcard.h" + +#include +#include + +using namespace XMPP; + + +static QString lineEncode(QString str) +{ + str.replace(QRegExp("\\\\"), "\\\\"); // backslash to double-backslash + str.replace(QRegExp("\\|"), "\\p"); // pipe to \p + str.replace(QRegExp("\n"), "\\n"); // newline to \n + return str; +} + +static QString lineDecode(const QString &str) +{ + QString ret; + + for(unsigned int n = 0; n < str.length(); ++n) { + if(str.at(n) == '\\') { + ++n; + if(n >= str.length()) + break; + + if(str.at(n) == 'n') + ret.append('\n'); + if(str.at(n) == 'p') + ret.append('|'); + if(str.at(n) == '\\') + ret.append('\\'); + } + else { + ret.append(str.at(n)); + } + } + + return ret; +} + +static Roster xmlReadRoster(const QDomElement &q, bool push) +{ + Roster r; + + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "item") { + RosterItem item; + item.fromXml(i); + + if(push) + item.setIsPush(true); + + r += item; + } + } + + return r; +} + + +//---------------------------------------------------------------------------- +// JT_Register +//---------------------------------------------------------------------------- +class JT_Register::Private +{ +public: + Private() {} + + Form form; + Jid jid; + int type; +}; + +JT_Register::JT_Register(Task *parent) +:Task(parent) +{ + d = new Private; + d->type = -1; +} + +JT_Register::~JT_Register() +{ + delete d; +} + +void JT_Register::reg(const QString &user, const QString &pass) +{ + d->type = 0; + to = client()->host(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "username", user)); + query.appendChild(textTag(doc(), "password", pass)); +} + +void JT_Register::changepw(const QString &pass) +{ + d->type = 1; + to = client()->host(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "username", client()->user())); + query.appendChild(textTag(doc(), "password", pass)); +} + +void JT_Register::unreg(const Jid &j) +{ + d->type = 2; + to = j.isEmpty() ? client()->host() : j.full(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); + + // this may be useful + if(!d->form.key().isEmpty()) + query.appendChild(textTag(doc(), "key", d->form.key())); + + query.appendChild(doc()->createElement("remove")); +} + +void JT_Register::getForm(const Jid &j) +{ + d->type = 3; + to = j; + iq = createIQ(doc(), "get", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); +} + +void JT_Register::setForm(const Form &form) +{ + d->type = 4; + to = form.jid(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); + + // key? + if(!form.key().isEmpty()) + query.appendChild(textTag(doc(), "key", form.key())); + + // fields + for(Form::ConstIterator it = form.begin(); it != form.end(); ++it) { + const FormField &f = *it; + query.appendChild(textTag(doc(), f.realName(), f.value())); + } +} + +const Form & JT_Register::form() const +{ + return d->form; +} + +void JT_Register::onGo() +{ + send(iq); +} + +bool JT_Register::take(const QDomElement &x) +{ + if(!iqVerify(x, to, id())) + return false; + + Jid from(x.attribute("from")); + if(x.attribute("type") == "result") { + if(d->type == 3) { + d->form.clear(); + d->form.setJid(from); + + QDomElement q = queryTag(x); + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "instructions") + d->form.setInstructions(tagContent(i)); + else if(i.tagName() == "key") + d->form.setKey(tagContent(i)); + else { + FormField f; + if(f.setType(i.tagName())) { + f.setValue(tagContent(i)); + d->form += f; + } + } + } + } + + setSuccess(); + } + else + setError(x); + + return true; +} + +//---------------------------------------------------------------------------- +// JT_UnRegister +//---------------------------------------------------------------------------- +class JT_UnRegister::Private +{ +public: + Private() { } + + Jid j; + JT_Register *jt_reg; +}; + +JT_UnRegister::JT_UnRegister(Task *parent) +: Task(parent) +{ + d = new Private; + d->jt_reg = 0; +} + +JT_UnRegister::~JT_UnRegister() +{ + delete d->jt_reg; + delete d; +} + +void JT_UnRegister::unreg(const Jid &j) +{ + d->j = j; +} + +void JT_UnRegister::onGo() +{ + delete d->jt_reg; + + d->jt_reg = new JT_Register(this); + d->jt_reg->getForm(d->j); + connect(d->jt_reg, SIGNAL(finished()), SLOT(getFormFinished())); + d->jt_reg->go(false); +} + +void JT_UnRegister::getFormFinished() +{ + disconnect(d->jt_reg, 0, this, 0); + + d->jt_reg->unreg(d->j); + connect(d->jt_reg, SIGNAL(finished()), SLOT(unregFinished())); + d->jt_reg->go(false); +} + +void JT_UnRegister::unregFinished() +{ + if ( d->jt_reg->success() ) + setSuccess(); + else + setError(d->jt_reg->statusCode(), d->jt_reg->statusString()); + + delete d->jt_reg; + d->jt_reg = 0; +} + +//---------------------------------------------------------------------------- +// JT_Roster +//---------------------------------------------------------------------------- +class JT_Roster::Private +{ +public: + Private() {} + + Roster roster; + QValueList itemList; +}; + +JT_Roster::JT_Roster(Task *parent) +:Task(parent) +{ + type = -1; + d = new Private; +} + +JT_Roster::~JT_Roster() +{ + delete d; +} + +void JT_Roster::get() +{ + type = 0; + //to = client()->host(); + iq = createIQ(doc(), "get", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:roster"); + iq.appendChild(query); +} + +void JT_Roster::set(const Jid &jid, const QString &name, const QStringList &groups) +{ + type = 1; + //to = client()->host(); + QDomElement item = doc()->createElement("item"); + item.setAttribute("jid", jid.full()); + if(!name.isEmpty()) + item.setAttribute("name", name); + for(QStringList::ConstIterator it = groups.begin(); it != groups.end(); ++it) + item.appendChild(textTag(doc(), "group", *it)); + d->itemList += item; +} + +void JT_Roster::remove(const Jid &jid) +{ + type = 1; + //to = client()->host(); + QDomElement item = doc()->createElement("item"); + item.setAttribute("jid", jid.full()); + item.setAttribute("subscription", "remove"); + d->itemList += item; +} + +void JT_Roster::onGo() +{ + if(type == 0) + send(iq); + else if(type == 1) { + //to = client()->host(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:roster"); + iq.appendChild(query); + for(QValueList::ConstIterator it = d->itemList.begin(); it != d->itemList.end(); ++it) + query.appendChild(*it); + send(iq); + } +} + +const Roster & JT_Roster::roster() const +{ + return d->roster; +} + +QString JT_Roster::toString() const +{ + if(type != 1) + return ""; + + QDomElement i = doc()->createElement("request"); + i.setAttribute("type", "JT_Roster"); + for(QValueList::ConstIterator it = d->itemList.begin(); it != d->itemList.end(); ++it) + i.appendChild(*it); + return lineEncode(Stream::xmlToString(i)); + return ""; +} + +bool JT_Roster::fromString(const QString &str) +{ + QDomDocument *dd = new QDomDocument; + if(!dd->setContent(lineDecode(str).utf8())) + return false; + QDomElement e = doc()->importNode(dd->documentElement(), true).toElement(); + delete dd; + + if(e.tagName() != "request" || e.attribute("type") != "JT_Roster") + return false; + + type = 1; + d->itemList.clear(); + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + d->itemList += i; + } + + return true; +} + +bool JT_Roster::take(const QDomElement &x) +{ + if(!iqVerify(x, client()->host(), id())) + return false; + + // get + if(type == 0) { + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + d->roster = xmlReadRoster(q, false); + setSuccess(); + } + else { + setError(x); + } + + return true; + } + // set + else if(type == 1) { + if(x.attribute("type") == "result") + setSuccess(); + else + setError(x); + + return true; + } + // remove + else if(type == 2) { + setSuccess(); + return true; + } + + return false; +} + + +//---------------------------------------------------------------------------- +// JT_PushRoster +//---------------------------------------------------------------------------- +JT_PushRoster::JT_PushRoster(Task *parent) +:Task(parent) +{ +} + +JT_PushRoster::~JT_PushRoster() +{ +} + +bool JT_PushRoster::take(const QDomElement &e) +{ + // must be an iq-set tag + if(e.tagName() != "iq" || e.attribute("type") != "set") + return false; + + if(!iqVerify(e, client()->host(), "", "jabber:iq:roster")) + return false; + + roster(xmlReadRoster(queryTag(e), true)); + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_Presence +//---------------------------------------------------------------------------- +JT_Presence::JT_Presence(Task *parent) +:Task(parent) +{ + type = -1; +} + +JT_Presence::~JT_Presence() +{ +} + +void JT_Presence::pres(const Status &s) +{ + type = 0; + + tag = doc()->createElement("presence"); + if(!s.isAvailable()) { + tag.setAttribute("type", "unavailable"); + if(!s.status().isEmpty()) + tag.appendChild(textTag(doc(), "status", s.status())); + } + else { + if(s.isInvisible()) + tag.setAttribute("type", "invisible"); + + if(!s.show().isEmpty()) + tag.appendChild(textTag(doc(), "show", s.show())); + if(!s.status().isEmpty()) + tag.appendChild(textTag(doc(), "status", s.status())); + + tag.appendChild( textTag(doc(), "priority", QString("%1").arg(s.priority()) ) ); + + if(!s.keyID().isEmpty()) { + QDomElement x = textTag(doc(), "x", s.keyID()); + x.setAttribute("xmlns", "http://jabber.org/protocol/e2e"); + tag.appendChild(x); + } + if(!s.xsigned().isEmpty()) { + QDomElement x = textTag(doc(), "x", s.xsigned()); + x.setAttribute("xmlns", "jabber:x:signed"); + tag.appendChild(x); + } + + if(!s.capsNode().isEmpty() && !s.capsVersion().isEmpty()) { + QDomElement c = doc()->createElement("c"); + c.setAttribute("xmlns","http://jabber.org/protocol/caps"); + c.setAttribute("node",s.capsNode()); + c.setAttribute("ver",s.capsVersion()); + if (!s.capsExt().isEmpty()) + c.setAttribute("ext",s.capsExt()); + tag.appendChild(c); + } + } +} + +void JT_Presence::pres(const Jid &to, const Status &s) +{ + pres(s); + tag.setAttribute("to", to.full()); +} + +void JT_Presence::sub(const Jid &to, const QString &subType) +{ + type = 1; + + tag = doc()->createElement("presence"); + tag.setAttribute("to", to.full()); + tag.setAttribute("type", subType); +} + +void JT_Presence::onGo() +{ + send(tag); + setSuccess(); +} + + +//---------------------------------------------------------------------------- +// JT_PushPresence +//---------------------------------------------------------------------------- +JT_PushPresence::JT_PushPresence(Task *parent) +:Task(parent) +{ +} + +JT_PushPresence::~JT_PushPresence() +{ +} + +bool JT_PushPresence::take(const QDomElement &e) +{ + if(e.tagName() != "presence") + return false; + + Jid j(e.attribute("from")); + Status p; + + if(e.hasAttribute("type")) { + QString type = e.attribute("type"); + if(type == "unavailable") { + p.setIsAvailable(false); + } + else if(type == "error") { + QString str = ""; + int code = 0; + getErrorFromElement(e, &code, &str); + p.setError(code, str); + } + else { + subscription(j, type); + return true; + } + } + + QDomElement tag; + bool found; + + tag = findSubTag(e, "status", &found); + if(found) + p.setStatus(tagContent(tag)); + tag = findSubTag(e, "show", &found); + if(found) + p.setShow(tagContent(tag)); + tag = findSubTag(e, "priority", &found); + if(found) + p.setPriority(tagContent(tag).toInt()); + + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "x" && i.attribute("xmlns") == "jabber:x:delay") { + if(i.hasAttribute("stamp")) { + QDateTime dt; + if(stamp2TS(i.attribute("stamp"), &dt)) + dt = dt.addSecs(client()->timeZoneOffset() * 3600); + p.setTimeStamp(dt); + } + } + else if(i.tagName() == "x" && i.attribute("xmlns") == "gabber:x:music:info") { + QDomElement t; + bool found; + QString title, state; + + t = findSubTag(i, "title", &found); + if(found) + title = tagContent(t); + t = findSubTag(i, "state", &found); + if(found) + state = tagContent(t); + + if(!title.isEmpty() && state == "playing") + p.setSongTitle(title); + } + else if(i.tagName() == "x" && i.attribute("xmlns") == "jabber:x:signed") { + p.setXSigned(tagContent(i)); + } + else if(i.tagName() == "x" && i.attribute("xmlns") == "http://jabber.org/protocol/e2e") { + p.setKeyID(tagContent(i)); + } + else if(i.tagName() == "c" && i.attribute("xmlns") == "http://jabber.org/protocol/caps") { + p.setCapsNode(i.attribute("node")); + p.setCapsVersion(i.attribute("ver")); + p.setCapsExt(i.attribute("ext")); + } + } + + presence(j, p); + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_Message +//---------------------------------------------------------------------------- +static QDomElement oldStyleNS(const QDomElement &e) +{ + // find closest parent with a namespace + QDomNode par = e.parentNode(); + while(!par.isNull() && par.namespaceURI().isNull()) + par = par.parentNode(); + bool noShowNS = false; + if(!par.isNull() && par.namespaceURI() == e.namespaceURI()) + noShowNS = true; + + QDomElement i; + uint x; + //if(noShowNS) + i = e.ownerDocument().createElement(e.tagName()); + //else + // i = e.ownerDocument().createElementNS(e.namespaceURI(), e.tagName()); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) + i.setAttributeNode(al.item(x).cloneNode().toAttr()); + + if(!noShowNS) + i.setAttribute("xmlns", e.namespaceURI()); + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(oldStyleNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + return i; +} + +JT_Message::JT_Message(Task *parent, const Message &msg) +:Task(parent) +{ + m = msg; + m.setId(id()); +} + +JT_Message::~JT_Message() +{ +} + +void JT_Message::onGo() +{ + Stanza s = m.toStanza(&(client()->stream())); + QDomElement e = oldStyleNS(s.element()); + send(e); + setSuccess(); +} + + +//---------------------------------------------------------------------------- +// JT_PushMessage +//---------------------------------------------------------------------------- +static QDomElement addCorrectNS(const QDomElement &e) +{ + uint x; + + // grab child nodes + /*QDomDocumentFragment frag = e.ownerDocument().createDocumentFragment(); + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) + frag.appendChild(nl.item(x).cloneNode());*/ + + // find closest xmlns + QDomNode n = e; + while(!n.isNull() && !n.toElement().hasAttribute("xmlns")) + n = n.parentNode(); + QString ns; + if(n.isNull() || !n.toElement().hasAttribute("xmlns")) + ns = "jabber:client"; + else + ns = n.toElement().attribute("xmlns"); + + // make a new node + QDomElement i = e.ownerDocument().createElementNS(ns, e.tagName()); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) { + QDomAttr a = al.item(x).toAttr(); + if(a.name() != "xmlns") + i.setAttributeNodeNS(al.item(x).cloneNode().toAttr()); + } + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(addCorrectNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + + //i.appendChild(frag); + return i; +} + +JT_PushMessage::JT_PushMessage(Task *parent) +:Task(parent) +{ +} + +JT_PushMessage::~JT_PushMessage() +{ +} + +bool JT_PushMessage::take(const QDomElement &e) +{ + if(e.tagName() != "message") + return false; + + Stanza s = client()->stream().createStanza(addCorrectNS(e)); + if(s.isNull()) { + //printf("take: bad stanza??\n"); + return false; + } + + Message m; + if(!m.fromStanza(s, client()->timeZoneOffset())) { + //printf("bad message\n"); + return false; + } + + message(m); + return true; +} + + +//---------------------------------------------------------------------------- +// JT_GetLastActivity +//---------------------------------------------------------------------------- +class JT_GetLastActivity::Private +{ +public: + Private() {} + + int seconds; + QString message; +}; + +JT_GetLastActivity::JT_GetLastActivity(Task *parent) +:Task(parent) +{ + d = new Private; +} + +JT_GetLastActivity::~JT_GetLastActivity() +{ + delete d; +} + +void JT_GetLastActivity::get(const Jid &j) +{ + jid = j; + iq = createIQ(doc(), "get", jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:last"); + iq.appendChild(query); +} + +int JT_GetLastActivity::seconds() const +{ + return d->seconds; +} + +const QString &JT_GetLastActivity::message() const +{ + return d->message; +} + +void JT_GetLastActivity::onGo() +{ + send(iq); +} + +bool JT_GetLastActivity::take(const QDomElement &x) +{ + if(!iqVerify(x, jid, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + + d->message = q.text(); + bool ok; + d->seconds = q.attribute("seconds").toInt(&ok); + + setSuccess(ok); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_GetServices +//---------------------------------------------------------------------------- +JT_GetServices::JT_GetServices(Task *parent) +:Task(parent) +{ +} + +void JT_GetServices::get(const Jid &j) +{ + agentList.clear(); + + jid = j; + iq = createIQ(doc(), "get", jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:agents"); + iq.appendChild(query); +} + +const AgentList & JT_GetServices::agents() const +{ + return agentList; +} + +void JT_GetServices::onGo() +{ + send(iq); +} + +bool JT_GetServices::take(const QDomElement &x) +{ + if(!iqVerify(x, jid, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + + // agents + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "agent") { + AgentItem a; + + a.setJid(Jid(i.attribute("jid"))); + + QDomElement tag; + bool found; + + tag = findSubTag(i, "name", &found); + if(found) + a.setName(tagContent(tag)); + + // determine which namespaces does item support + QStringList ns; + + tag = findSubTag(i, "register", &found); + if(found) + ns << "jabber:iq:register"; + tag = findSubTag(i, "search", &found); + if(found) + ns << "jabber:iq:search"; + tag = findSubTag(i, "groupchat", &found); + if(found) + ns << "jabber:iq:conference"; + tag = findSubTag(i, "transport", &found); + if(found) + ns << "jabber:iq:gateway"; + + a.setFeatures(ns); + + agentList += a; + } + } + + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_VCard +//---------------------------------------------------------------------------- +class JT_VCard::Private +{ +public: + Private() {} + + QDomElement iq; + Jid jid; + VCard vcard; +}; + +JT_VCard::JT_VCard(Task *parent) +:Task(parent) +{ + type = -1; + d = new Private; +} + +JT_VCard::~JT_VCard() +{ + delete d; +} + +void JT_VCard::get(const Jid &_jid) +{ + type = 0; + d->jid = _jid; + d->iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement v = doc()->createElement("vCard"); + v.setAttribute("xmlns", "vcard-temp"); + v.setAttribute("version", "2.0"); + v.setAttribute("prodid", "-//HandGen//NONSGML vGen v1.0//EN"); + d->iq.appendChild(v); +} + +const Jid & JT_VCard::jid() const +{ + return d->jid; +} + +const VCard & JT_VCard::vcard() const +{ + return d->vcard; +} + +void JT_VCard::set(const VCard &card) +{ + type = 1; + d->vcard = card; + d->jid = ""; + d->iq = createIQ(doc(), "set", d->jid.full(), id()); + d->iq.appendChild(card.toXml(doc()) ); +} + +void JT_VCard::onGo() +{ + send(d->iq); +} + +bool JT_VCard::take(const QDomElement &x) +{ + Jid to = d->jid; + if (to.userHost() == client()->jid().userHost()) + to = client()->host(); + if(!iqVerify(x, to, id())) + return false; + + if(x.attribute("type") == "result") { + if(type == 0) { + for(QDomNode n = x.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement q = n.toElement(); + if(q.isNull()) + continue; + + if(q.tagName().upper() == "VCARD") { + if(d->vcard.fromXml(q)) { + setSuccess(); + return true; + } + } + } + + setError(ErrDisc + 1, tr("No VCard available")); + return true; + } + else { + setSuccess(); + return true; + } + } + else { + setError(x); + } + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_Search +//---------------------------------------------------------------------------- +class JT_Search::Private +{ +public: + Private() {} + + Jid jid; + Form form; + QValueList resultList; +}; + +JT_Search::JT_Search(Task *parent) +:Task(parent) +{ + d = new Private; + type = -1; +} + +JT_Search::~JT_Search() +{ + delete d; +} + +void JT_Search::get(const Jid &jid) +{ + type = 0; + d->jid = jid; + iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:search"); + iq.appendChild(query); +} + +void JT_Search::set(const Form &form) +{ + type = 1; + d->jid = form.jid(); + iq = createIQ(doc(), "set", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:search"); + iq.appendChild(query); + + // key? + if(!form.key().isEmpty()) + query.appendChild(textTag(doc(), "key", form.key())); + + // fields + for(Form::ConstIterator it = form.begin(); it != form.end(); ++it) { + const FormField &f = *it; + query.appendChild(textTag(doc(), f.realName(), f.value())); + } +} + +const Form & JT_Search::form() const +{ + return d->form; +} + +const QValueList & JT_Search::results() const +{ + return d->resultList; +} + +void JT_Search::onGo() +{ + send(iq); +} + +bool JT_Search::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + Jid from(x.attribute("from")); + if(x.attribute("type") == "result") { + if(type == 0) { + d->form.clear(); + d->form.setJid(from); + + QDomElement q = queryTag(x); + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "instructions") + d->form.setInstructions(tagContent(i)); + else if(i.tagName() == "key") + d->form.setKey(tagContent(i)); + else { + FormField f; + if(f.setType(i.tagName())) { + f.setValue(tagContent(i)); + d->form += f; + } + } + } + } + else { + d->resultList.clear(); + + QDomElement q = queryTag(x); + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "item") { + SearchResult r(Jid(i.attribute("jid"))); + + QDomElement tag; + bool found; + + tag = findSubTag(i, "nick", &found); + if(found) + r.setNick(tagContent(tag)); + tag = findSubTag(i, "first", &found); + if(found) + r.setFirst(tagContent(tag)); + tag = findSubTag(i, "last", &found); + if(found) + r.setLast(tagContent(tag)); + tag = findSubTag(i, "email", &found); + if(found) + r.setEmail(tagContent(tag)); + + d->resultList += r; + } + } + } + setSuccess(); + } + else { + setError(x); + } + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_ClientVersion +//---------------------------------------------------------------------------- +JT_ClientVersion::JT_ClientVersion(Task *parent) +:Task(parent) +{ +} + +void JT_ClientVersion::get(const Jid &jid) +{ + j = jid; + iq = createIQ(doc(), "get", j.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:version"); + iq.appendChild(query); +} + +void JT_ClientVersion::onGo() +{ + send(iq); +} + +bool JT_ClientVersion::take(const QDomElement &x) +{ + if(!iqVerify(x, j, id())) + return false; + + if(x.attribute("type") == "result") { + bool found; + QDomElement q = queryTag(x); + QDomElement tag; + tag = findSubTag(q, "name", &found); + if(found) + v_name = tagContent(tag); + tag = findSubTag(q, "version", &found); + if(found) + v_ver = tagContent(tag); + tag = findSubTag(q, "os", &found); + if(found) + v_os = tagContent(tag); + + setSuccess(); + } + else { + setError(x); + } + + return true; +} + +const Jid & JT_ClientVersion::jid() const +{ + return j; +} + +const QString & JT_ClientVersion::name() const +{ + return v_name; +} + +const QString & JT_ClientVersion::version() const +{ + return v_ver; +} + +const QString & JT_ClientVersion::os() const +{ + return v_os; +} + + +//---------------------------------------------------------------------------- +// JT_ClientTime +//---------------------------------------------------------------------------- +/*JT_ClientTime::JT_ClientTime(Task *parent, const Jid &_j) +:Task(parent) +{ + j = _j; + iq = createIQ("get", j.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:time"); + iq.appendChild(query); +} + +void JT_ClientTime::go() +{ + send(iq); +} + +bool JT_ClientTime::take(const QDomElement &x) +{ + if(x.attribute("id") != id()) + return FALSE; + + if(x.attribute("type") == "result") { + bool found; + QDomElement q = queryTag(x); + QDomElement tag; + tag = findSubTag(q, "utc", &found); + if(found) + stamp2TS(tagContent(tag), &utc); + tag = findSubTag(q, "tz", &found); + if(found) + timezone = tagContent(tag); + tag = findSubTag(q, "display", &found); + if(found) + display = tagContent(tag); + + setSuccess(TRUE); + } + else { + setError(getErrorString(x)); + setSuccess(FALSE); + } + + return TRUE; +} +*/ + + +//---------------------------------------------------------------------------- +// JT_ServInfo +//---------------------------------------------------------------------------- +JT_ServInfo::JT_ServInfo(Task *parent) +:Task(parent) +{ +} + +JT_ServInfo::~JT_ServInfo() +{ +} + +bool JT_ServInfo::take(const QDomElement &e) +{ + if(e.tagName() != "iq" || e.attribute("type") != "get") + return false; + + QString ns = queryNS(e); + if(ns == "jabber:iq:version") { + QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:version"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "name", client()->clientName())); + query.appendChild(textTag(doc(), "version", client()->clientVersion())); + query.appendChild(textTag(doc(), "os", client()->OSName())); + send(iq); + return true; + } + //else if(ns == "jabber:iq:time") { + // QDomElement iq = createIQ("result", e.attribute("from"), e.attribute("id")); + // QDomElement query = doc()->createElement("query"); + // query.setAttribute("xmlns", "jabber:iq:time"); + // iq.appendChild(query); + // QDateTime local = QDateTime::currentDateTime(); + // QDateTime utc = local.addSecs(-getTZOffset() * 3600); + // QString str = getTZString(); + // query.appendChild(textTag("utc", TS2stamp(utc))); + // query.appendChild(textTag("tz", str)); + // query.appendChild(textTag("display", QString("%1 %2").arg(local.toString()).arg(str))); + // send(iq); + // return TRUE; + //} + else if(ns == "http://jabber.org/protocol/disco#info") { + // Find out the node + QString node; + bool found; + QDomElement q = findSubTag(e, "query", &found); + if(found) // NOTE: Should always be true, since a NS was found above + node = q.attribute("node"); + + QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/disco#info"); + if (!node.isEmpty()) + query.setAttribute("node", node); + iq.appendChild(query); + + // Identity + DiscoItem::Identity identity = client()->identity(); + QDomElement id = doc()->createElement("identity"); + if (!identity.category.isEmpty() && !identity.type.isEmpty()) { + id.setAttribute("category",identity.category); + id.setAttribute("type",identity.type); + if (!identity.name.isEmpty()) { + id.setAttribute("name",identity.name); + } + } + else { + // Default values + id.setAttribute("category","client"); + id.setAttribute("type","pc"); + } + query.appendChild(id); + + QDomElement feature; + if (node.isEmpty() || node == client()->capsNode() + "#" + client()->capsVersion()) { + // Standard features + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/bytestreams"); + query.appendChild(feature); + + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/si"); + query.appendChild(feature); + + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/si/profile/file-transfer"); + query.appendChild(feature); + + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/xhtml-im"); + query.appendChild(feature); + + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/disco#info"); + query.appendChild(feature); + + if (node.isEmpty()) { + // Extended features + QStringList exts = client()->extensions(); + for (QStringList::ConstIterator i = exts.begin(); i != exts.end(); ++i) { + const QStringList& l = client()->extension(*i).list(); + for ( QStringList::ConstIterator j = l.begin(); j != l.end(); ++j ) { + feature = doc()->createElement("feature"); + feature.setAttribute("var", *j); + query.appendChild(feature); + } + } + } + } + else if (node.startsWith(client()->capsNode() + "#")) { + QString ext = node.right(node.length()-client()->capsNode().length()-1); + if (client()->extensions().contains(ext)) { + const QStringList& l = client()->extension(ext).list(); + for ( QStringList::ConstIterator it = l.begin(); it != l.end(); ++it ) { + feature = doc()->createElement("feature"); + feature.setAttribute("var", *it); + query.appendChild(feature); + } + } + else { + // TODO: ERROR + } + } + else { + // TODO: ERROR + } + + send(iq); + return true; + } + + return false; +} + + +//---------------------------------------------------------------------------- +// JT_Gateway +//---------------------------------------------------------------------------- +JT_Gateway::JT_Gateway(Task *parent) +:Task(parent) +{ + type = -1; +} + +void JT_Gateway::get(const Jid &jid) +{ + type = 0; + v_jid = jid; + iq = createIQ(doc(), "get", v_jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:gateway"); + iq.appendChild(query); +} + +void JT_Gateway::set(const Jid &jid, const QString &prompt) +{ + type = 1; + v_jid = jid; + v_prompt = prompt; + iq = createIQ(doc(), "set", v_jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:gateway"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "prompt", v_prompt)); +} + +void JT_Gateway::onGo() +{ + send(iq); +} + +Jid JT_Gateway::jid() const +{ + return v_jid; +} + +QString JT_Gateway::desc() const +{ + return v_desc; +} + +QString JT_Gateway::prompt() const +{ + return v_prompt; +} + +bool JT_Gateway::take(const QDomElement &x) +{ + if(!iqVerify(x, v_jid, id())) + return false; + + if(x.attribute("type") == "result") { + if(type == 0) { + QDomElement query = queryTag(x); + bool found; + QDomElement tag; + tag = findSubTag(query, "desc", &found); + if(found) + v_desc = tagContent(tag); + tag = findSubTag(query, "prompt", &found); + if(found) + v_prompt = tagContent(tag); + } + else { + QDomElement query = queryTag(x); + bool found; + QDomElement tag; + tag = findSubTag(query, "prompt", &found); + if(found) + v_prompt = tagContent(tag); + } + + setSuccess(); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_Browse +//---------------------------------------------------------------------------- +class JT_Browse::Private +{ +public: + QDomElement iq; + Jid jid; + AgentList agentList; + AgentItem root; +}; + +JT_Browse::JT_Browse (Task *parent) +:Task (parent) +{ + d = new Private; +} + +JT_Browse::~JT_Browse () +{ + delete d; +} + +void JT_Browse::get (const Jid &j) +{ + d->agentList.clear(); + + d->jid = j; + d->iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement query = doc()->createElement("item"); + query.setAttribute("xmlns", "jabber:iq:browse"); + d->iq.appendChild(query); +} + +const AgentList & JT_Browse::agents() const +{ + return d->agentList; +} + +const AgentItem & JT_Browse::root() const +{ + return d->root; +} + +void JT_Browse::onGo () +{ + send(d->iq); +} + +AgentItem JT_Browse::browseHelper (const QDomElement &i) +{ + AgentItem a; + + if ( i.tagName() == "ns" ) + return a; + + a.setName ( i.attribute("name") ); + a.setJid ( i.attribute("jid") ); + + // there are two types of category/type specification: + // + // 1. + // 2. + + if ( i.tagName() == "item" || i.tagName() == "query" ) + a.setCategory ( i.attribute("category") ); + else + a.setCategory ( i.tagName() ); + + a.setType ( i.attribute("type") ); + + QStringList ns; + for(QDomNode n = i.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if ( i.tagName() == "ns" ) + ns << i.text(); + } + + // For now, conference.jabber.org returns proper namespace only + // when browsing individual rooms. So it's a quick client-side fix. + if ( !a.features().canGroupchat() && a.category() == "conference" ) + ns << "jabber:iq:conference"; + + a.setFeatures (ns); + + return a; +} + +bool JT_Browse::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + if(x.attribute("type") == "result") { + for(QDomNode n = x.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + d->root = browseHelper (i); + + for(QDomNode nn = i.firstChild(); !nn.isNull(); nn = nn.nextSibling()) { + QDomElement e = nn.toElement(); + if ( e.isNull() ) + continue; + if ( e.tagName() == "ns" ) + continue; + + d->agentList += browseHelper (e); + } + } + + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_DiscoItems +//---------------------------------------------------------------------------- +class JT_DiscoItems::Private +{ +public: + Private() { } + + QDomElement iq; + Jid jid; + DiscoList items; +}; + +JT_DiscoItems::JT_DiscoItems(Task *parent) +: Task(parent) +{ + d = new Private; +} + +JT_DiscoItems::~JT_DiscoItems() +{ + delete d; +} + +void JT_DiscoItems::get(const DiscoItem &item) +{ + get(item.jid(), item.node()); +} + +void JT_DiscoItems::get (const Jid &j, const QString &node) +{ + d->items.clear(); + + d->jid = j; + d->iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/disco#items"); + + if ( !node.isEmpty() ) + query.setAttribute("node", node); + + d->iq.appendChild(query); +} + +const DiscoList &JT_DiscoItems::items() const +{ + return d->items; +} + +void JT_DiscoItems::onGo () +{ + send(d->iq); +} + +bool JT_DiscoItems::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if( e.isNull() ) + continue; + + if ( e.tagName() == "item" ) { + DiscoItem item; + + item.setJid ( e.attribute("jid") ); + item.setName( e.attribute("name") ); + item.setNode( e.attribute("node") ); + item.setAction( DiscoItem::string2action(e.attribute("action")) ); + + d->items.append( item ); + } + } + + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_DiscoInfo +//---------------------------------------------------------------------------- +class JT_DiscoInfo::Private +{ +public: + Private() { } + + QDomElement iq; + Jid jid; + QString node; + DiscoItem item; +}; + +JT_DiscoInfo::JT_DiscoInfo(Task *parent) +: Task(parent) +{ + d = new Private; +} + +JT_DiscoInfo::~JT_DiscoInfo() +{ + delete d; +} + +void JT_DiscoInfo::get(const DiscoItem &item) +{ + DiscoItem::Identity id; + if ( item.identities().count() == 1 ) + id = item.identities().first(); + get(item.jid(), item.node(), id); +} + +void JT_DiscoInfo::get (const Jid &j, const QString &node, DiscoItem::Identity ident) +{ + d->item = DiscoItem(); // clear item + + d->jid = j; + d->node = node; + d->iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/disco#info"); + + if ( !node.isEmpty() ) + query.setAttribute("node", node); + + if ( !ident.category.isEmpty() && !ident.type.isEmpty() ) { + QDomElement i = doc()->createElement("item"); + + i.setAttribute("category", ident.category); + i.setAttribute("type", ident.type); + if ( !ident.name.isEmpty() ) + i.setAttribute("name", ident.name); + + query.appendChild( i ); + + } + + d->iq.appendChild(query); +} + + +/** + * Original requested jid. + * Is here because sometimes the responder does not include this information + * in the reply. + */ +const Jid& JT_DiscoInfo::jid() const +{ + return d->jid; +} + +/** + * Original requested node. + * Is here because sometimes the responder does not include this information + * in the reply. + */ +const QString& JT_DiscoInfo::node() const +{ + return d->node; +} + + + +const DiscoItem &JT_DiscoInfo::item() const +{ + return d->item; +} + +void JT_DiscoInfo::onGo () +{ + send(d->iq); +} + +bool JT_DiscoInfo::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + + DiscoItem item; + + item.setJid( d->jid ); + item.setNode( q.attribute("node") ); + + QStringList features; + DiscoItem::Identities identities; + + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if( e.isNull() ) + continue; + + if ( e.tagName() == "feature" ) { + features << e.attribute("var"); + } + else if ( e.tagName() == "identity" ) { + DiscoItem::Identity id; + + id.category = e.attribute("category"); + id.name = e.attribute("name"); + id.type = e.attribute("type"); + + identities.append( id ); + } + } + + item.setFeatures( features ); + item.setIdentities( identities ); + + d->item = item; + + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_DiscoPublish +//---------------------------------------------------------------------------- +class JT_DiscoPublish::Private +{ +public: + Private() { } + + QDomElement iq; + Jid jid; + DiscoList list; +}; + +JT_DiscoPublish::JT_DiscoPublish(Task *parent) +: Task(parent) +{ + d = new Private; +} + +JT_DiscoPublish::~JT_DiscoPublish() +{ + delete d; +} + +void JT_DiscoPublish::set(const Jid &j, const DiscoList &list) +{ + d->list = list; + d->jid = j; + + d->iq = createIQ(doc(), "set", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/disco#items"); + + // FIXME: unsure about this + //if ( !node.isEmpty() ) + // query.setAttribute("node", node); + + DiscoList::ConstIterator it = list.begin(); + for ( ; it != list.end(); ++it) { + QDomElement w = doc()->createElement("item"); + + w.setAttribute("jid", (*it).jid().full()); + if ( !(*it).name().isEmpty() ) + w.setAttribute("name", (*it).name()); + if ( !(*it).node().isEmpty() ) + w.setAttribute("node", (*it).node()); + w.setAttribute("action", DiscoItem::action2string((*it).action())); + + query.appendChild( w ); + } + + d->iq.appendChild(query); +} + +void JT_DiscoPublish::onGo () +{ + send(d->iq); +} + +bool JT_DiscoPublish::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + if(x.attribute("type") == "result") { + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_MucPresence +//---------------------------------------------------------------------------- +JT_MucPresence::JT_MucPresence(Task *parent) +:Task(parent) +{ + type = -1; +} + +JT_MucPresence::~JT_MucPresence() +{ +} + +void JT_MucPresence::pres(const Status &s) +{ + type = 0; + + tag = doc()->createElement("presence"); + if(!s.isAvailable()) { + tag.setAttribute("type", "unavailable"); + if(!s.status().isEmpty()) + tag.appendChild(textTag(doc(), "status", s.status())); + } + else { + if(s.isInvisible()) + tag.setAttribute("type", "invisible"); + + if(!s.show().isEmpty()) + tag.appendChild(textTag(doc(), "show", s.show())); + if(!s.status().isEmpty()) + tag.appendChild(textTag(doc(), "status", s.status())); + + tag.appendChild( textTag(doc(), "priority", QString("%1").arg(s.priority()) ) ); + + if(!s.keyID().isEmpty()) { + QDomElement x = textTag(doc(), "x", s.keyID()); + x.setAttribute("xmlns", "http://jabber.org/protocol/e2e"); + tag.appendChild(x); + } + if(!s.xsigned().isEmpty()) { + QDomElement x = textTag(doc(), "x", s.xsigned()); + x.setAttribute("xmlns", "jabber:x:signed"); + tag.appendChild(x); + } + + if(!s.capsNode().isEmpty() && !s.capsVersion().isEmpty()) { + QDomElement c = doc()->createElement("c"); + c.setAttribute("xmlns","http://jabber.org/protocol/caps"); + c.setAttribute("node",s.capsNode()); + c.setAttribute("ver",s.capsVersion()); + if (!s.capsExt().isEmpty()) + c.setAttribute("ext",s.capsExt()); + tag.appendChild(c); + } + } +} + +void JT_MucPresence::pres(const Jid &to, const Status &s, const QString &password) +{ + pres(s); + tag.setAttribute("to", to.full()); + QDomElement x = textTag(doc(), "x", s.xsigned()); + x.setAttribute("xmlns", "http://jabber.org/protocol/muc"); + x.appendChild( textTag(doc(), "password", password.latin1()) ); + tag.appendChild(x); +} + +void JT_MucPresence::onGo() +{ + send(tag); + setSuccess(); +} + + +//---------------------------------------------------------------------------- +// JT_PrivateStorage +//---------------------------------------------------------------------------- +class JT_PrivateStorage::Private +{ + public: + Private() : type(-1) {} + + QDomElement iq; + QDomElement elem; + int type; +}; + +JT_PrivateStorage::JT_PrivateStorage(Task *parent) + :Task(parent) +{ + d = new Private; +} + +JT_PrivateStorage::~JT_PrivateStorage() +{ + delete d; +} + +void JT_PrivateStorage::get(const QString& tag, const QString& xmlns) +{ + d->type = 0; + d->iq = createIQ(doc(), "get" , QString() , id() ); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:private"); + d->iq.appendChild(query); + QDomElement s = doc()->createElement(tag); + if(!xmlns.isEmpty()) + s.setAttribute("xmlns", xmlns); + query.appendChild(s); +} + +void JT_PrivateStorage::set(const QDomElement& element) +{ + d->type = 1; + d->elem=element; + QDomNode n=doc()->importNode(element,true); + + d->iq = createIQ(doc(), "set" , QString() , id() ); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:private"); + d->iq.appendChild(query); + query.appendChild(n); +} + +void JT_PrivateStorage::onGo() +{ + send(d->iq); +} + +bool JT_PrivateStorage::take(const QDomElement &x) +{ + QString to = client()->host(); + if(!iqVerify(x, to, id())) + return false; + + if(x.attribute("type") == "result") { + if(d->type == 0) { + QDomElement q = queryTag(x); + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + d->elem=i; + break; + } + } + setSuccess(); + return true; + } + else { + setError(x); + } + + return true; +} + + +QDomElement JT_PrivateStorage::element( ) +{ + return d->elem; +} + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.h b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.h new file mode 100644 index 00000000..ceb1e294 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.h @@ -0,0 +1,485 @@ +/* + * tasks.h - basic tasks + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef JABBER_TASKS_H +#define JABBER_TASKS_H + +#include +#include + +#include"im.h" +#include"xmpp_vcard.h" + +namespace XMPP +{ + class Roster; + class Status; + + class JT_Register : public Task + { + Q_OBJECT + public: + JT_Register(Task *parent); + ~JT_Register(); + + void reg(const QString &user, const QString &pass); + void changepw(const QString &pass); + void unreg(const Jid &j=""); + + const Form & form() const; + void getForm(const Jid &); + void setForm(const Form &); + + void onGo(); + bool take(const QDomElement &); + + private: + QDomElement iq; + Jid to; + + class Private; + Private *d; + }; + + class JT_UnRegister : public Task + { + Q_OBJECT + public: + JT_UnRegister(Task *parent); + ~JT_UnRegister(); + + void unreg(const Jid &); + + void onGo(); + + private slots: + void getFormFinished(); + void unregFinished(); + + private: + class Private; + Private *d; + }; + + class JT_Roster : public Task + { + Q_OBJECT + public: + JT_Roster(Task *parent); + ~JT_Roster(); + + void get(); + void set(const Jid &, const QString &name, const QStringList &groups); + void remove(const Jid &); + + const Roster & roster() const; + + QString toString() const; + bool fromString(const QString &); + + void onGo(); + bool take(const QDomElement &x); + + private: + int type; + QDomElement iq; + Jid to; + + class Private; + Private *d; + }; + + class JT_PushRoster : public Task + { + Q_OBJECT + public: + JT_PushRoster(Task *parent); + ~JT_PushRoster(); + + bool take(const QDomElement &); + + signals: + void roster(const Roster &); + + private: + class Private; + Private *d; + }; + + class JT_Presence : public Task + { + Q_OBJECT + public: + JT_Presence(Task *parent); + ~JT_Presence(); + + void pres(const Status &); + void pres(const Jid &, const Status &); + void sub(const Jid &, const QString &subType); + + void onGo(); + + private: + QDomElement tag; + int type; + + class Private; + Private *d; + }; + + class JT_PushPresence : public Task + { + Q_OBJECT + public: + JT_PushPresence(Task *parent); + ~JT_PushPresence(); + + bool take(const QDomElement &); + + signals: + void presence(const Jid &, const Status &); + void subscription(const Jid &, const QString &); + + private: + class Private; + Private *d; + }; + + class JT_Message : public Task + { + Q_OBJECT + public: + JT_Message(Task *parent, const Message &); + ~JT_Message(); + + void onGo(); + + private: + Message m; + + class Private; + Private *d; + }; + + class JT_PushMessage : public Task + { + Q_OBJECT + public: + JT_PushMessage(Task *parent); + ~JT_PushMessage(); + + bool take(const QDomElement &); + + signals: + void message(const Message &); + + private: + class Private; + Private *d; + }; + + class JT_GetLastActivity : public Task + { + Q_OBJECT + public: + JT_GetLastActivity(Task *); + ~JT_GetLastActivity(); + + void get(const Jid &); + + int seconds() const; + const QString &message() const; + + void onGo(); + bool take(const QDomElement &x); + + private: + class Private; + Private *d; + + QDomElement iq; + Jid jid; + }; + + class JT_GetServices : public Task + { + Q_OBJECT + public: + JT_GetServices(Task *); + + void get(const Jid &); + + const AgentList & agents() const; + + void onGo(); + bool take(const QDomElement &x); + + private: + class Private; + Private *d; + + QDomElement iq; + Jid jid; + AgentList agentList; + }; + + class JT_VCard : public Task + { + Q_OBJECT + public: + JT_VCard(Task *parent); + ~JT_VCard(); + + void get(const Jid &); + void set(const VCard &); + + const Jid & jid() const; + const VCard & vcard() const; + + void onGo(); + bool take(const QDomElement &x); + + private: + int type; + + class Private; + Private *d; + }; + + class JT_Search : public Task + { + Q_OBJECT + public: + JT_Search(Task *parent); + ~JT_Search(); + + const Form & form() const; + const QValueList & results() const; + + void get(const Jid &); + void set(const Form &); + + void onGo(); + bool take(const QDomElement &x); + + private: + QDomElement iq; + int type; + + class Private; + Private *d; + }; + + class JT_ClientVersion : public Task + { + Q_OBJECT + public: + JT_ClientVersion(Task *); + + void get(const Jid &); + void onGo(); + bool take(const QDomElement &); + + const Jid & jid() const; + const QString & name() const; + const QString & version() const; + const QString & os() const; + + private: + QDomElement iq; + + Jid j; + QString v_name, v_ver, v_os; + }; +/* + class JT_ClientTime : public Task + { + Q_OBJECT + public: + JT_ClientTime(Task *, const Jid &); + + void go(); + bool take(const QDomElement &); + + Jid j; + QDateTime utc; + QString timezone, display; + + private: + QDomElement iq; + }; +*/ + class JT_ServInfo : public Task + { + Q_OBJECT + public: + JT_ServInfo(Task *); + ~JT_ServInfo(); + + bool take(const QDomElement &); + }; + + class JT_Gateway : public Task + { + Q_OBJECT + public: + JT_Gateway(Task *); + + void get(const Jid &); + void set(const Jid &, const QString &prompt); + void onGo(); + bool take(const QDomElement &); + + Jid jid() const; + QString desc() const; + QString prompt() const; + + private: + QDomElement iq; + + int type; + Jid v_jid; + QString v_prompt, v_desc; + }; + + class JT_Browse : public Task + { + Q_OBJECT + public: + JT_Browse(Task *); + ~JT_Browse(); + + void get(const Jid &); + + const AgentList & agents() const; + const AgentItem & root() const; + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + + AgentItem browseHelper (const QDomElement &i); + }; + + class JT_DiscoItems : public Task + { + Q_OBJECT + public: + JT_DiscoItems(Task *); + ~JT_DiscoItems(); + + void get(const Jid &, const QString &node = QString::null); + void get(const DiscoItem &); + + const DiscoList &items() const; + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + + class JT_DiscoInfo : public Task + { + Q_OBJECT + public: + JT_DiscoInfo(Task *); + ~JT_DiscoInfo(); + + void get(const Jid &, const QString &node = QString::null, const DiscoItem::Identity = DiscoItem::Identity()); + void get(const DiscoItem &); + + const DiscoItem &item() const; + const Jid& jid() const; + const QString& node() const; + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + + class JT_DiscoPublish : public Task + { + Q_OBJECT + public: + JT_DiscoPublish(Task *); + ~JT_DiscoPublish(); + + void set(const Jid &, const DiscoList &); + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + + class JT_MucPresence : public Task + { + Q_OBJECT + public: + JT_MucPresence(Task *parent); + ~JT_MucPresence(); + + void pres(const Status &); + void pres(const Jid &, const Status &, const QString &password); + + void onGo(); + + private: + QDomElement tag; + int type; + + class Private; + Private *d; + }; + + class JT_PrivateStorage : public Task + { + Q_OBJECT + public: + JT_PrivateStorage(Task *parent); + ~JT_PrivateStorage(); + + void set(const QDomElement &); + void get(const QString &tag, const QString& xmlns); + + QDomElement element(); + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.cpp new file mode 100644 index 00000000..296c53c6 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.cpp @@ -0,0 +1,1241 @@ +/* + * xmpp_vcard.cpp - classes for handling vCards + * Copyright (C) 2003 Michail Pishchagin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "xmpp_vcard.h" + +#include "base64.h" + +#include +#include + +#include // needed for image format recognition +#include + +// Justin's XML helper functions + +static QDomElement textTag(QDomDocument *doc, const QString &name, const QString &content) +{ + QDomElement tag = doc->createElement(name); + QDomText text = doc->createTextNode(content); + tag.appendChild(text); + + return tag; +} + +static QDomElement findSubTag(const QDomElement &e, const QString &name, bool *found) +{ + if(found) + *found = FALSE; + + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + if(i.tagName().upper() == name.upper()) { // mblsha: ignore case when searching + if(found) + *found = TRUE; + return i; + } + } + + QDomElement tmp; + return tmp; +} + +// mblsha's own functions + +static QDomElement emptyTag(QDomDocument *doc, const QString &name) +{ + QDomElement tag = doc->createElement(name); + + return tag; +} + +static bool hasSubTag(const QDomElement &e, const QString &name) +{ + bool found; + findSubTag(e, name, &found); + return found; +} + +static QString subTagText(const QDomElement &e, const QString &name) +{ + bool found; + QDomElement i = findSubTag(e, name, &found); + if ( found ) + return i.text().stripWhiteSpace(); + return QString::null; +} + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// VCard +//---------------------------------------------------------------------------- +static QString image2type(const QByteArray &ba) +{ + QBuffer buf(ba); + buf.open(IO_ReadOnly); + QString format = QImageIO::imageFormat( &buf ); + + // TODO: add more formats + if ( format == "PNG" || format == "PsiPNG" ) + return "image/png"; + if ( format == "MNG" ) + return "video/x-mng"; + if ( format == "GIF" ) + return "image/gif"; + if ( format == "BMP" ) + return "image/bmp"; + if ( format == "XPM" ) + return "image/x-xpm"; + if ( format == "SVG" ) + return "image/svg+xml"; + if ( format == "JPEG" ) + return "image/jpeg"; + + qWarning("WARNING! VCard::image2type: unknown format = '%s'", format.latin1()); + + return "image/unknown"; +} + +// Long lines of encoded binary data SHOULD BE folded to 75 characters using the folding method defined in [MIME-DIR]. +static QString foldString(const QString &s) +{ + QString ret; + + for (int i = 0; i < (int)s.length(); i++) { + if ( !(i % 75) ) + ret += '\n'; + ret += s[i]; + } + + return ret; +} + +class VCard::Private +{ +public: + Private(); + ~Private(); + + QString version; + QString fullName; + QString familyName, givenName, middleName, prefixName, suffixName; + QString nickName; + + QByteArray photo; + QString photoURI; + + QString bday; + AddressList addressList; + LabelList labelList; + PhoneList phoneList; + EmailList emailList; + QString jid; + QString mailer; + QString timezone; + Geo geo; + QString title; + QString role; + + QByteArray logo; + QString logoURI; + + VCard *agent; + QString agentURI; + + Org org; + QStringList categories; + QString note; + QString prodId; + QString rev; + QString sortString; + + QByteArray sound; + QString soundURI, soundPhonetic; + + QString uid; + QString url; + QString desc; + PrivacyClass privacyClass; + QByteArray key; + + bool isEmpty(); +}; + +VCard::Private::Private() +{ + privacyClass = pcNone; + agent = 0; +} + +VCard::Private::~Private() +{ + delete agent; +} + +bool VCard::Private::isEmpty() +{ + if ( !version.isEmpty() || + !fullName.isEmpty() || + !familyName.isEmpty() || !givenName.isEmpty() || !middleName.isEmpty() || !prefixName.isEmpty() || !suffixName.isEmpty() || + !nickName.isEmpty() || + !photo.isEmpty() || !photoURI.isEmpty() || + !bday.isEmpty() || + !addressList.isEmpty() || + !labelList.isEmpty() || + !phoneList.isEmpty() || + !emailList.isEmpty() || + !jid.isEmpty() || + !mailer.isEmpty() || + !timezone.isEmpty() || + !geo.lat.isEmpty() || !geo.lon.isEmpty() || + !title.isEmpty() || + !role.isEmpty() || + !logo.isEmpty() || !logoURI.isEmpty() || + (agent && !agent->isEmpty()) || !agentURI.isEmpty() || + !org.name.isEmpty() || !org.unit.isEmpty() || + !categories.isEmpty() || + !note.isEmpty() || + !prodId.isEmpty() || + !rev.isEmpty() || + !sortString.isEmpty() || + !sound.isEmpty() || !soundURI.isEmpty() || !soundPhonetic.isEmpty() || + !uid.isEmpty() || + !url.isEmpty() || + !desc.isEmpty() || + (privacyClass != pcNone) || + !key.isEmpty() ) + { + return false; + } + return true; +} + +VCard::VCard() +{ + d = new Private; +} + +VCard::VCard(const VCard &from) +{ + d = new Private; + *this = from; +} + +VCard & VCard::operator=(const VCard &from) +{ + if(d->agent) { + delete d->agent; + d->agent = 0; + } + + *d = *from.d; + + if(from.d->agent) { + // dup the agent + d->agent = new VCard(*from.d->agent); + } + + return *this; +} + +VCard::~VCard() +{ + delete d; +} + +QDomElement VCard::toXml(QDomDocument *doc) const +{ + QDomElement v = doc->createElement("vCard"); + v.setAttribute("version", "2.0"); + v.setAttribute("prodid", "-//HandGen//NONSGML vGen v1.0//EN"); + v.setAttribute("xmlns", "vcard-temp"); + + if ( !d->version.isEmpty() ) + v.appendChild( textTag(doc, "VERSION", d->version) ); + if ( !d->fullName.isEmpty() ) + v.appendChild( textTag(doc, "FN", d->fullName) ); + + if ( !d->familyName.isEmpty() || !d->givenName.isEmpty() || !d->middleName.isEmpty() || + !d->prefixName.isEmpty() || !d->suffixName.isEmpty() ) { + QDomElement w = doc->createElement("N"); + + if ( !d->familyName.isEmpty() ) + w.appendChild( textTag(doc, "FAMILY", d->familyName) ); + if ( !d->givenName.isEmpty() ) + w.appendChild( textTag(doc, "GIVEN", d->givenName) ); + if ( !d->middleName.isEmpty() ) + w.appendChild( textTag(doc, "MIDDLE", d->middleName) ); + if ( !d->prefixName.isEmpty() ) + w.appendChild( textTag(doc, "PREFIX", d->prefixName) ); + if ( !d->suffixName.isEmpty() ) + w.appendChild( textTag(doc, "SUFFIX", d->suffixName) ); + + v.appendChild(w); + } + + if ( !d->nickName.isEmpty() ) + v.appendChild( textTag(doc, "NICKNAME", d->nickName) ); + + if ( !d->photo.isEmpty() || !d->photoURI.isEmpty() ) { + QDomElement w = doc->createElement("PHOTO"); + + if ( !d->photo.isEmpty() ) { + w.appendChild( textTag(doc, "TYPE", image2type(d->photo)) ); + w.appendChild( textTag(doc, "BINVAL", foldString( Base64::arrayToString(d->photo)) ) ); + } + else if ( !d->photoURI.isEmpty() ) + w.appendChild( textTag(doc, "EXTVAL", d->photoURI) ); + + v.appendChild(w); + } + + if ( !d->bday.isEmpty() ) + v.appendChild( textTag(doc, "BDAY", d->bday) ); + + if ( !d->addressList.isEmpty() ) { + AddressList::Iterator it = d->addressList.begin(); + for ( ; it != d->addressList.end(); ++it ) { + QDomElement w = doc->createElement("ADR"); + Address a = *it; + + if ( a.home ) + w.appendChild( emptyTag(doc, "HOME") ); + if ( a.work ) + w.appendChild( emptyTag(doc, "WORK") ); + if ( a.postal ) + w.appendChild( emptyTag(doc, "POSTAL") ); + if ( a.parcel ) + w.appendChild( emptyTag(doc, "PARCEL") ); + if ( a.dom ) + w.appendChild( emptyTag(doc, "DOM") ); + if ( a.intl ) + w.appendChild( emptyTag(doc, "INTL") ); + if ( a.pref ) + w.appendChild( emptyTag(doc, "PREF") ); + + if ( !a.pobox.isEmpty() ) + w.appendChild( textTag(doc, "POBOX", a.pobox) ); + if ( !a.extaddr.isEmpty() ) + w.appendChild( textTag(doc, "EXTADR", a.extaddr) ); + if ( !a.street.isEmpty() ) + w.appendChild( textTag(doc, "STREET", a.street) ); + if ( !a.locality.isEmpty() ) + w.appendChild( textTag(doc, "LOCALITY", a.locality) ); + if ( !a.region.isEmpty() ) + w.appendChild( textTag(doc, "REGION", a.region) ); + if ( !a.pcode.isEmpty() ) + w.appendChild( textTag(doc, "PCODE", a.pcode) ); + if ( !a.country.isEmpty() ) + w.appendChild( textTag(doc, "CTRY", a.country) ); + + v.appendChild(w); + } + } + + if ( !d->labelList.isEmpty() ) { + LabelList::Iterator it = d->labelList.begin(); + for ( ; it != d->labelList.end(); ++it ) { + QDomElement w = doc->createElement("LABEL"); + Label l = *it; + + if ( l.home ) + w.appendChild( emptyTag(doc, "HOME") ); + if ( l.work ) + w.appendChild( emptyTag(doc, "WORK") ); + if ( l.postal ) + w.appendChild( emptyTag(doc, "POSTAL") ); + if ( l.parcel ) + w.appendChild( emptyTag(doc, "PARCEL") ); + if ( l.dom ) + w.appendChild( emptyTag(doc, "DOM") ); + if ( l.intl ) + w.appendChild( emptyTag(doc, "INTL") ); + if ( l.pref ) + w.appendChild( emptyTag(doc, "PREF") ); + + if ( !l.lines.isEmpty() ) { + QStringList::Iterator it = l.lines.begin(); + for ( ; it != l.lines.end(); ++it ) + w.appendChild( textTag(doc, "LINE", *it) ); + } + + v.appendChild(w); + } + } + + if ( !d->phoneList.isEmpty() ) { + PhoneList::Iterator it = d->phoneList.begin(); + for ( ; it != d->phoneList.end(); ++it ) { + QDomElement w = doc->createElement("TEL"); + Phone p = *it; + + if ( p.home ) + w.appendChild( emptyTag(doc, "HOME") ); + if ( p.work ) + w.appendChild( emptyTag(doc, "WORK") ); + if ( p.voice ) + w.appendChild( emptyTag(doc, "VOICE") ); + if ( p.fax ) + w.appendChild( emptyTag(doc, "FAX") ); + if ( p.pager ) + w.appendChild( emptyTag(doc, "PAGER") ); + if ( p.msg ) + w.appendChild( emptyTag(doc, "MSG") ); + if ( p.cell ) + w.appendChild( emptyTag(doc, "CELL") ); + if ( p.video ) + w.appendChild( emptyTag(doc, "VIDEO") ); + if ( p.bbs ) + w.appendChild( emptyTag(doc, "BBS") ); + if ( p.modem ) + w.appendChild( emptyTag(doc, "MODEM") ); + if ( p.isdn ) + w.appendChild( emptyTag(doc, "ISDN") ); + if ( p.pcs ) + w.appendChild( emptyTag(doc, "PCS") ); + if ( p.pref ) + w.appendChild( emptyTag(doc, "PREF") ); + + if ( !p.number.isEmpty() ) + w.appendChild( textTag(doc, "NUMBER", p.number) ); + + v.appendChild(w); + } + } + + if ( !d->emailList.isEmpty() ) { + EmailList::Iterator it = d->emailList.begin(); + for ( ; it != d->emailList.end(); ++it ) { + QDomElement w = doc->createElement("EMAIL"); + Email e = *it; + + if ( e.home ) + w.appendChild( emptyTag(doc, "HOME") ); + if ( e.work ) + w.appendChild( emptyTag(doc, "WORK") ); + if ( e.internet ) + w.appendChild( emptyTag(doc, "INTERNET") ); + if ( e.x400 ) + w.appendChild( emptyTag(doc, "X400") ); + + if ( !e.userid.isEmpty() ) + w.appendChild( textTag(doc, "USERID", e.userid) ); + + v.appendChild(w); + } + } + + if ( !d->jid.isEmpty() ) + v.appendChild( textTag(doc, "JABBERID", d->jid) ); + if ( !d->mailer.isEmpty() ) + v.appendChild( textTag(doc, "MAILER", d->mailer) ); + if ( !d->timezone.isEmpty() ) + v.appendChild( textTag(doc, "TZ", d->timezone) ); + + if ( !d->geo.lat.isEmpty() || !d->geo.lon.isEmpty() ) { + QDomElement w = doc->createElement("GEO"); + + if ( !d->geo.lat.isEmpty() ) + w.appendChild( textTag(doc, "LAT", d->geo.lat) ); + if ( !d->geo.lon.isEmpty() ) + w.appendChild( textTag(doc, "LON", d->geo.lon)); + + v.appendChild(w); + } + + if ( !d->title.isEmpty() ) + v.appendChild( textTag(doc, "TITLE", d->title) ); + if ( !d->role.isEmpty() ) + v.appendChild( textTag(doc, "ROLE", d->role) ); + + if ( !d->logo.isEmpty() || !d->logoURI.isEmpty() ) { + QDomElement w = doc->createElement("LOGO"); + + if ( !d->logo.isEmpty() ) { + w.appendChild( textTag(doc, "TYPE", image2type(d->logo)) ); + w.appendChild( textTag(doc, "BINVAL", foldString( Base64::arrayToString(d->logo)) ) ); + } + else if ( !d->logoURI.isEmpty() ) + w.appendChild( textTag(doc, "EXTVAL", d->logoURI) ); + + v.appendChild(w); + } + + if ( !d->agentURI.isEmpty() || (d->agent && d->agent->isEmpty()) ) { + QDomElement w = doc->createElement("AGENT"); + + if ( d->agent && !d->agent->isEmpty() ) + w.appendChild( d->agent->toXml(doc) ); + else if ( !d->agentURI.isEmpty() ) + w.appendChild( textTag(doc, "EXTVAL", d->agentURI) ); + + v.appendChild(w); + } + + if ( !d->org.name.isEmpty() || !d->org.unit.isEmpty() ) { + QDomElement w = doc->createElement("ORG"); + + if ( !d->org.name.isEmpty() ) + w.appendChild( textTag(doc, "ORGNAME", d->org.name) ); + + if ( !d->org.unit.isEmpty() ) { + QStringList::Iterator it = d->org.unit.begin(); + for ( ; it != d->org.unit.end(); ++it ) + w.appendChild( textTag(doc, "ORGUNIT", *it) ); + } + + v.appendChild(w); + } + + if ( !d->categories.isEmpty() ) { + QDomElement w = doc->createElement("CATEGORIES"); + + QStringList::Iterator it = d->categories.begin(); + for ( ; it != d->categories.end(); ++it ) + w.appendChild( textTag(doc, "KEYWORD", *it) ); + + v.appendChild(w); + } + + if ( !d->note.isEmpty() ) + v.appendChild( textTag(doc, "NOTE", d->note) ); + if ( !d->prodId.isEmpty() ) + v.appendChild( textTag(doc, "PRODID", d->prodId) ); + if ( !d->rev.isEmpty() ) + v.appendChild( textTag(doc, "REV", d->rev) ); + if ( !d->sortString.isEmpty() ) + v.appendChild( textTag(doc, "SORT-STRING", d->sortString) ); + + if ( !d->sound.isEmpty() || !d->soundURI.isEmpty() || !d->soundPhonetic.isEmpty() ) { + QDomElement w = doc->createElement("SOUND"); + + if ( !d->sound.isEmpty() ) + w.appendChild( textTag(doc, "BINVAL", foldString( Base64::arrayToString(d->sound)) ) ); + else if ( !d->soundURI.isEmpty() ) + w.appendChild( textTag(doc, "EXTVAL", d->soundURI) ); + else if ( !d->soundPhonetic.isEmpty() ) + w.appendChild( textTag(doc, "PHONETIC", d->soundPhonetic) ); + + v.appendChild(w); + } + + if ( !d->uid.isEmpty() ) + v.appendChild( textTag(doc, "UID", d->uid) ); + if ( !d->url.isEmpty() ) + v.appendChild( textTag(doc, "URL", d->url) ); + if ( !d->desc.isEmpty() ) + v.appendChild( textTag(doc, "DESC", d->desc) ); + + if ( d->privacyClass != pcNone ) { + QDomElement w = doc->createElement("CLASS"); + + if ( d->privacyClass == pcPublic ) + w.appendChild( emptyTag(doc, "PUBLIC") ); + else if ( d->privacyClass == pcPrivate ) + w.appendChild( emptyTag(doc, "PRIVATE") ); + else if ( d->privacyClass == pcConfidential ) + w.appendChild( emptyTag(doc, "CONFIDENTIAL") ); + + v.appendChild(w); + } + + if ( !d->key.isEmpty() ) { + QDomElement w = doc->createElement("KEY"); + + // TODO: Justin, please check out this code + w.appendChild( textTag(doc, "TYPE", "text/plain")); // FIXME + w.appendChild( textTag(doc, "CRED", QString::fromUtf8(d->key)) ); // FIXME + + v.appendChild(w); + } + + return v; +} + +bool VCard::fromXml(const QDomElement &q) +{ + if ( q.tagName().upper() != "VCARD" ) + return false; + + QDomNode n = q.firstChild(); + for ( ; !n.isNull(); n = n.nextSibling() ) { + QDomElement i = n.toElement(); + if ( i.isNull() ) + continue; + + QString tag = i.tagName().upper(); + + bool found; + QDomElement e; + + if ( tag == "VERSION" ) + d->version = i.text().stripWhiteSpace(); + else if ( tag == "FN" ) + d->fullName = i.text().stripWhiteSpace(); + else if ( tag == "N" ) { + d->familyName = subTagText(i, "FAMILY"); + d->givenName = subTagText(i, "GIVEN"); + d->middleName = subTagText(i, "MIDDLE"); + d->prefixName = subTagText(i, "PREFIX"); + d->suffixName = subTagText(i, "SUFFIX"); + } + else if ( tag == "NICKNAME" ) + d->nickName = i.text().stripWhiteSpace(); + else if ( tag == "PHOTO" ) { + d->photo = Base64::stringToArray( subTagText(i, "BINVAL") ); + d->photoURI = subTagText(i, "EXTVAL"); + } + else if ( tag == "BDAY" ) + d->bday = i.text().stripWhiteSpace(); + else if ( tag == "ADR" ) { + Address a; + + a.home = hasSubTag(i, "HOME"); + a.work = hasSubTag(i, "WORK"); + a.postal = hasSubTag(i, "POSTAL"); + a.parcel = hasSubTag(i, "PARCEL"); + a.dom = hasSubTag(i, "DOM"); + a.intl = hasSubTag(i, "INTL"); + a.pref = hasSubTag(i, "PREF"); + + a.pobox = subTagText(i, "POBOX"); + a.extaddr = subTagText(i, "EXTADR"); + a.street = subTagText(i, "STREET"); + a.locality = subTagText(i, "LOCALITY"); + a.region = subTagText(i, "REGION"); + a.pcode = subTagText(i, "PCODE"); + a.country = subTagText(i, "CTRY"); + + if ( a.country.isEmpty() ) // FIXME: Workaround for Psi prior to 0.9 + if ( hasSubTag(i, "COUNTRY") ) + a.country = subTagText(i, "COUNTRY"); + + if ( a.extaddr.isEmpty() ) // FIXME: Workaround for Psi prior to 0.9 + if ( hasSubTag(i, "EXTADD") ) + a.extaddr = subTagText(i, "EXTADD"); + + d->addressList.append ( a ); + } + else if ( tag == "LABEL" ) { + Label l; + + l.home = hasSubTag(i, "HOME"); + l.work = hasSubTag(i, "WORK"); + l.postal = hasSubTag(i, "POSTAL"); + l.parcel = hasSubTag(i, "PARCEL"); + l.dom = hasSubTag(i, "DOM"); + l.intl = hasSubTag(i, "INTL"); + l.pref = hasSubTag(i, "PREF"); + + QDomNode nn = i.firstChild(); + for ( ; !nn.isNull(); nn = nn.nextSibling() ) { + QDomElement ii = nn.toElement(); + if ( ii.isNull() ) + continue; + + if ( ii.tagName().upper() == "LINE" ) + l.lines.append ( ii.text().stripWhiteSpace() ); + } + + d->labelList.append ( l ); + } + else if ( tag == "TEL" ) { + Phone p; + + p.home = hasSubTag(i, "HOME"); + p.work = hasSubTag(i, "WORK"); + p.voice = hasSubTag(i, "VOICE"); + p.fax = hasSubTag(i, "FAX"); + p.pager = hasSubTag(i, "PAGER"); + p.msg = hasSubTag(i, "MSG"); + p.cell = hasSubTag(i, "CELL"); + p.video = hasSubTag(i, "VIDEO"); + p.bbs = hasSubTag(i, "BBS"); + p.modem = hasSubTag(i, "MODEM"); + p.isdn = hasSubTag(i, "ISDN"); + p.pcs = hasSubTag(i, "PCS"); + p.pref = hasSubTag(i, "PREF"); + + p.number = subTagText(i, "NUMBER"); + + if ( p.number.isEmpty() ) // FIXME: Workaround for Psi prior to 0.9 + if ( hasSubTag(i, "VOICE") ) + p.number = subTagText(i, "VOICE"); + + d->phoneList.append ( p ); + } + else if ( tag == "EMAIL" ) { + Email m; + + m.home = hasSubTag(i, "HOME"); + m.work = hasSubTag(i, "WORK"); + m.internet = hasSubTag(i, "INTERNET"); + m.x400 = hasSubTag(i, "X400"); + + m.userid = subTagText(i, "USERID"); + + if ( m.userid.isEmpty() ) // FIXME: Workaround for Psi prior to 0.9 + if ( !i.text().isEmpty() ) + m.userid = i.text().stripWhiteSpace(); + + d->emailList.append ( m ); + } + else if ( tag == "JABBERID" ) + d->jid = i.text().stripWhiteSpace(); + else if ( tag == "MAILER" ) + d->mailer = i.text().stripWhiteSpace(); + else if ( tag == "TZ" ) + d->timezone = i.text().stripWhiteSpace(); + else if ( tag == "GEO" ) { + d->geo.lat = subTagText(i, "LAT"); + d->geo.lon = subTagText(i, "LON"); + } + else if ( tag == "TITLE" ) + d->title = i.text().stripWhiteSpace(); + else if ( tag == "ROLE" ) + d->role = i.text().stripWhiteSpace(); + else if ( tag == "LOGO" ) { + d->logo = Base64::stringToArray( subTagText(i, "BINVAL") ); + d->logoURI = subTagText(i, "EXTVAL"); + } + else if ( tag == "AGENT" ) { + e = findSubTag(i, "VCARD", &found); + if ( found ) { + VCard a; + if ( a.fromXml(e) ) { + if ( !d->agent ) + d->agent = new VCard; + *(d->agent) = a; + } + } + + d->agentURI = subTagText(i, "EXTVAL"); + } + else if ( tag == "ORG" ) { + d->org.name = subTagText(i, "ORGNAME"); + + QDomNode nn = i.firstChild(); + for ( ; !nn.isNull(); nn = nn.nextSibling() ) { + QDomElement ii = nn.toElement(); + if ( ii.isNull() ) + continue; + + if ( ii.tagName().upper() == "ORGUNIT" ) + d->org.unit.append( ii.text().stripWhiteSpace() ); + } + } + else if ( tag == "CATEGORIES") { + QDomNode nn = i.firstChild(); + for ( ; !nn.isNull(); nn = nn.nextSibling() ) { + QDomElement ee = nn.toElement(); + if ( ee.isNull() ) + continue; + + if ( ee.tagName().upper() == "KEYWORD" ) + d->categories << ee.text().stripWhiteSpace(); + } + } + else if ( tag == "NOTE" ) + d->note = i.text().stripWhiteSpace(); + else if ( tag == "PRODID" ) + d->prodId = i.text().stripWhiteSpace(); + else if ( tag == "REV" ) + d->rev = i.text().stripWhiteSpace(); + else if ( tag == "SORT-STRING" ) + d->sortString = i.text().stripWhiteSpace(); + else if ( tag == "SOUND" ) { + d->sound = Base64::stringToArray( subTagText(i, "BINVAL") ); + d->soundURI = subTagText(i, "EXTVAL"); + d->soundPhonetic = subTagText(i, "PHONETIC"); + } + else if ( tag == "UID" ) + d->uid = i.text().stripWhiteSpace(); + else if ( tag == "URL") + d->url = i.text().stripWhiteSpace(); + else if ( tag == "DESC" ) + d->desc = i.text().stripWhiteSpace(); + else if ( tag == "CLASS" ) { + if ( hasSubTag(i, "PUBLIC") ) + d->privacyClass = pcPublic; + else if ( hasSubTag(i, "PRIVATE") ) + d->privacyClass = pcPrivate; + else if ( hasSubTag(i, "CONFIDENTIAL") ) + d->privacyClass = pcConfidential; + } + else if ( tag == "KEY" ) { + // TODO: Justin, please check out this code + e = findSubTag(i, "TYPE", &found); + QString type = "text/plain"; + if ( found ) + type = e.text().stripWhiteSpace(); + + e = findSubTag(i, "CRED", &found ); + if ( !found ) + e = findSubTag(i, "BINVAL", &found); // case for very clever clients ;-) + + if ( found ) + d->key = e.text().utf8(); // FIXME + } + } + + return true; +} + +bool VCard::isEmpty() const +{ + return d->isEmpty(); +} + +// Some constructors + +VCard::Address::Address() +{ + home = work = postal = parcel = dom = intl = pref = false; +} + +VCard::Label::Label() +{ + home = work = postal = parcel = dom = intl = pref = false; +} + +VCard::Phone::Phone() +{ + home = work = voice = fax = pager = msg = cell = video = bbs = modem = isdn = pcs = pref = false; +} + +VCard::Email::Email() +{ + home = work = internet = x400 = false; +} + +VCard::Geo::Geo() +{ +} + +VCard::Org::Org() +{ +} + +// vCard properties... + +const QString &VCard::version() const +{ + return d->version; +} + +void VCard::setVersion(const QString &v) +{ + d->version = v; +} + +const QString &VCard::fullName() const +{ + return d->fullName; +} + +void VCard::setFullName(const QString &n) +{ + d->fullName = n; +} + +const QString &VCard::familyName() const +{ + return d->familyName; +} + +void VCard::setFamilyName(const QString &n) +{ + d->familyName = n; +} + +const QString &VCard::givenName() const +{ + return d->givenName; +} + +void VCard::setGivenName(const QString &n) +{ + d->givenName = n; +} + +const QString &VCard::middleName() const +{ + return d->middleName; +} + +void VCard::setMiddleName(const QString &n) +{ + d->middleName = n; +} + +const QString &VCard::prefixName() const +{ + return d->prefixName; +} + +void VCard::setPrefixName(const QString &p) +{ + d->prefixName = p; +} + +const QString &VCard::suffixName() const +{ + return d->suffixName; +} + +void VCard::setSuffixName(const QString &s) +{ + d->suffixName = s; +} + +const QString &VCard::nickName() const +{ + return d->nickName; +} + +void VCard::setNickName(const QString &n) +{ + d->nickName = n; +} + +const QByteArray &VCard::photo() const +{ + return d->photo; +} + +void VCard::setPhoto(const QByteArray &i) +{ + d->photo = i; +} + +const QString &VCard::photoURI() const +{ + return d->photoURI; +} + +void VCard::setPhotoURI(const QString &p) +{ + d->photoURI = p; +} + +const QDate VCard::bday() const +{ + return QDate::fromString(d->bday); +} + +void VCard::setBday(const QDate &date) +{ + d->bday = date.toString(); +} + +const QString &VCard::bdayStr() const +{ + return d->bday; +} + +void VCard::setBdayStr(const QString &date) +{ + d->bday = date; +} + +const VCard::AddressList &VCard::addressList() const +{ + return d->addressList; +} + +void VCard::setAddressList(const VCard::AddressList &a) +{ + d->addressList = a; +} + +const VCard::LabelList &VCard::labelList() const +{ + return d->labelList; +} + +void VCard::setLabelList(const VCard::LabelList &l) +{ + d->labelList = l; +} + +const VCard::PhoneList &VCard::phoneList() const +{ + return d->phoneList; +} + +void VCard::setPhoneList(const VCard::PhoneList &p) +{ + d->phoneList = p; +} + +const VCard::EmailList &VCard::emailList() const +{ + return d->emailList; +} + +void VCard::setEmailList(const VCard::EmailList &e) +{ + d->emailList = e; +} + +const QString &VCard::jid() const +{ + return d->jid; +} + +void VCard::setJid(const QString &j) +{ + d->jid = j; +} + +const QString &VCard::mailer() const +{ + return d->mailer; +} + +void VCard::setMailer(const QString &m) +{ + d->mailer = m; +} + +const QString &VCard::timezone() const +{ + return d->timezone; +} + +void VCard::setTimezone(const QString &t) +{ + d->timezone = t; +} + +const VCard::Geo &VCard::geo() const +{ + return d->geo; +} + +void VCard::setGeo(const VCard::Geo &g) +{ + d->geo = g; +} + +const QString &VCard::title() const +{ + return d->title; +} + +void VCard::setTitle(const QString &t) +{ + d->title = t; +} + +const QString &VCard::role() const +{ + return d->role; +} + +void VCard::setRole(const QString &r) +{ + d->role = r; +} + +const QByteArray &VCard::logo() const +{ + return d->logo; +} + +void VCard::setLogo(const QByteArray &i) +{ + d->logo = i; +} + +const QString &VCard::logoURI() const +{ + return d->logoURI; +} + +void VCard::setLogoURI(const QString &l) +{ + d->logoURI = l; +} + +const VCard *VCard::agent() const +{ + return d->agent; +} + +void VCard::setAgent(const VCard &v) +{ + if ( !d->agent ) + d->agent = new VCard; + *(d->agent) = v; +} + +const QString VCard::agentURI() const +{ + return d->agentURI; +} + +void VCard::setAgentURI(const QString &a) +{ + d->agentURI = a; +} + +const VCard::Org &VCard::org() const +{ + return d->org; +} + +void VCard::setOrg(const VCard::Org &o) +{ + d->org = o; +} + +const QStringList &VCard::categories() const +{ + return d->categories; +} + +void VCard::setCategories(const QStringList &c) +{ + d->categories = c; +} + +const QString &VCard::note() const +{ + return d->note; +} + +void VCard::setNote(const QString &n) +{ + d->note = n; +} + +const QString &VCard::prodId() const +{ + return d->prodId; +} + +void VCard::setProdId(const QString &p) +{ + d->prodId = p; +} + +const QString &VCard::rev() const +{ + return d->rev; +} + +void VCard::setRev(const QString &r) +{ + d->rev = r; +} + +const QString &VCard::sortString() const +{ + return d->sortString; +} + +void VCard::setSortString(const QString &s) +{ + d->sortString = s; +} + +const QByteArray &VCard::sound() const +{ + return d->sound; +} + +void VCard::setSound(const QByteArray &s) +{ + d->sound = s; +} + +const QString &VCard::soundURI() const +{ + return d->soundURI; +} + +void VCard::setSoundURI(const QString &s) +{ + d->soundURI = s; +} + +const QString &VCard::soundPhonetic() const +{ + return d->soundPhonetic; +} + +void VCard::setSoundPhonetic(const QString &s) +{ + d->soundPhonetic = s; +} + +const QString &VCard::uid() const +{ + return d->uid; +} + +void VCard::setUid(const QString &u) +{ + d->uid = u; +} + +const QString &VCard::url() const +{ + return d->url; +} + +void VCard::setUrl(const QString &u) +{ + d->url = u; +} + +const QString &VCard::desc() const +{ + return d->desc; +} + +void VCard::setDesc(const QString &desc) +{ + d->desc = desc; +} + +const VCard::PrivacyClass &VCard::privacyClass() const +{ + return d->privacyClass; +} + +void VCard::setPrivacyClass(const VCard::PrivacyClass &c) +{ + d->privacyClass = c; +} + +const QByteArray &VCard::key() const +{ + return d->key; +} + +void VCard::setKey(const QByteArray &k) +{ + d->key = k; +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.h b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.h new file mode 100644 index 00000000..ae8cc873 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.h @@ -0,0 +1,284 @@ +/* + * xmpp_vcard.h - classes for handling vCards + * Copyright (C) 2003 Michail Pishchagin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef JABBER_VCARD_H +#define JABBER_VCARD_H + +#include +#include +#include + +#include +#include + +class QDate; + +namespace XMPP +{ + class VCard + { + public: + VCard(); + VCard(const VCard &); + VCard & operator=(const VCard &); + ~VCard(); + + QDomElement toXml(QDomDocument *) const; + bool fromXml(const QDomElement &); + bool isEmpty() const; + + const QString &version() const; + void setVersion(const QString &); + + const QString &fullName() const; + void setFullName(const QString &); + + + const QString &familyName() const; + void setFamilyName(const QString &); + + const QString &givenName() const; + void setGivenName(const QString &); + + const QString &middleName() const; + void setMiddleName(const QString &); + + const QString &prefixName() const; + void setPrefixName(const QString &); + + const QString &suffixName() const; + void setSuffixName(const QString &); + + + const QString &nickName() const; + void setNickName(const QString &); + + + const QByteArray &photo() const; + void setPhoto(const QByteArray &); + + const QString &photoURI() const; + void setPhotoURI(const QString &); + + + const QDate bday() const; + void setBday(const QDate &); + + const QString &bdayStr() const; + void setBdayStr(const QString &); + + + class Address { + public: + Address(); + + bool home; + bool work; + bool postal; + bool parcel; + + bool dom; + bool intl; + + bool pref; + + QString pobox; + QString extaddr; + QString street; + QString locality; + QString region; + QString pcode; + QString country; + }; + typedef QValueList
    AddressList; + const AddressList &addressList() const; + void setAddressList(const AddressList &); + + class Label { + public: + Label(); + + bool home; + bool work; + bool postal; + bool parcel; + + bool dom; + bool intl; + + bool pref; + + QStringList lines; + }; + typedef QValueList